@@ -154,10 +154,9 @@ public override void Update(Task caller)
154154 }
155155
156156 // Try to deploy a pending spawns
157- if ( spawnInProgress )
158- {
159- if ( TryPlacement ( ) )
160- GameManager . Instance . RaiseOnEncounterEvent ( ) ;
157+ if ( spawnInProgress ) {
158+ TryPlacement ( ) ;
159+ GameManager . Instance . RaiseOnEncounterEvent ( ) ;
161160 }
162161 }
163162
@@ -180,7 +179,7 @@ void CreatePendingFoeSpawn(Foe foe)
180179 pendingFoesSpawned = 0 ;
181180 }
182181
183- bool TryPlacement ( )
182+ bool TryPlacement ( bool placeEnemyBehindPlayer = true )
184183 {
185184 PlayerEnterExit playerEnterExit = GameManager . Instance . PlayerEnterExit ;
186185
@@ -195,19 +194,19 @@ bool TryPlacement()
195194 // Place in world near player depending on local area
196195 if ( playerEnterExit . IsPlayerInsideBuilding )
197196 {
198- return PlaceFoeBuildingInterior ( pendingFoeGameObjects , playerEnterExit . Interior ) ;
197+ return PlaceFoeBuildingInterior ( pendingFoeGameObjects , playerEnterExit . Interior , placeEnemyBehindPlayer ) ;
199198 }
200199 else if ( playerEnterExit . IsPlayerInsideDungeon )
201200 {
202- return PlaceFoeDungeonInterior ( pendingFoeGameObjects , playerEnterExit . Dungeon ) ;
201+ return PlaceFoeDungeonInterior ( pendingFoeGameObjects , playerEnterExit . Dungeon , placeEnemyBehindPlayer ) ;
203202 }
204203 else if ( ! playerEnterExit . IsPlayerInside && GameManager . Instance . PlayerGPS . IsPlayerInLocationRect )
205204 {
206- return PlaceFoeExteriorLocation ( pendingFoeGameObjects , GameManager . Instance . StreamingWorld . CurrentPlayerLocationObject ) ;
205+ return PlaceFoeExteriorLocation ( pendingFoeGameObjects , GameManager . Instance . StreamingWorld . CurrentPlayerLocationObject , placeEnemyBehindPlayer ) ;
207206 }
208207 else
209208 {
210- return PlaceFoeWilderness ( pendingFoeGameObjects ) ;
209+ return PlaceFoeWilderness ( pendingFoeGameObjects , placeEnemyBehindPlayer ) ;
211210 }
212211 }
213212
@@ -217,7 +216,7 @@ bool TryPlacement()
217216
218217 // Place foe somewhere near player when inside a building
219218 // Building interiors have spawn nodes for this placement so we can roll out foes all at once
220- bool PlaceFoeBuildingInterior ( GameObject [ ] gameObjects , DaggerfallInterior interiorParent )
219+ bool PlaceFoeBuildingInterior ( GameObject [ ] gameObjects , DaggerfallInterior interiorParent , bool placeEnemyBehindPlayer = true )
221220 {
222221 // Must have a DaggerfallLocation parent
223222 if ( interiorParent == null )
@@ -229,115 +228,108 @@ bool PlaceFoeBuildingInterior(GameObject[] gameObjects, DaggerfallInterior inter
229228 // Always place foes around player rather than use spawn points
230229 // Spawn points work well for "interior hunt" quests but less so for "directly attack the player"
231230 // Feel just placing freely will yield best results overall
232- return PlaceFoeFreely ( gameObjects , interiorParent . transform ) ;
231+ return PlaceFoeFreely ( gameObjects , interiorParent . transform , 5 , 20 , placeEnemyBehindPlayer ) ;
233232 }
234233
235234 // Place foe somewhere near player when inside a dungeon
236235 // Dungeons interiors are complex 3D environments with no navgrid/navmesh or known spawn nodes
237- bool PlaceFoeDungeonInterior ( GameObject [ ] gameObjects , DaggerfallDungeon dungeonParent )
236+ bool PlaceFoeDungeonInterior ( GameObject [ ] gameObjects , DaggerfallDungeon dungeonParent , bool placeEnemyBehindPlayer = true )
238237 {
239- return PlaceFoeFreely ( gameObjects , dungeonParent . transform ) ;
238+ return PlaceFoeFreely ( gameObjects , dungeonParent . transform , 5 , 20 , placeEnemyBehindPlayer ) ;
240239 }
241240
242241 // Place foe somewhere near player when outside a location navgrid is available
243242 // Navgrid placement helps foe avoid getting tangled in geometry like buildings
244- bool PlaceFoeExteriorLocation ( GameObject [ ] gameObjects , DaggerfallLocation locationParent )
243+ bool PlaceFoeExteriorLocation ( GameObject [ ] gameObjects , DaggerfallLocation locationParent , bool placeEnemyBehindPlayer = true )
245244 {
246- return PlaceFoeFreely ( gameObjects , locationParent . transform ) ;
245+ return PlaceFoeFreely ( gameObjects , locationParent . transform , 5 , 20 , placeEnemyBehindPlayer ) ;
247246 }
248247
249248 // Place foe somewhere near player when outside and no navgrid available
250249 // Wilderness environments are currently open so can be placed on ground anywhere within range
251- bool PlaceFoeWilderness ( GameObject [ ] gameObjects )
250+ bool PlaceFoeWilderness ( GameObject [ ] gameObjects , bool placeEnemyBehindPlayer = true )
252251 {
253252 // TODO this false will need to be true when start caching enemies
254253 GameManager . Instance . StreamingWorld . TrackLooseObject ( gameObjects [ pendingFoesSpawned ] , false , - 1 , - 1 , true ) ;
255- return PlaceFoeFreely ( gameObjects , null , 8f , 25f ) ;
254+ return PlaceFoeFreely ( gameObjects , null , 8f , 25f , placeEnemyBehindPlayer , false ) ;
256255 }
257256
258257 // Uses raycasts to find next spawn position just outside of player's field of view
259- bool PlaceFoeFreely ( GameObject [ ] gameObjects , Transform parent , float minDistance = 5f , float maxDistance = 20f )
258+ bool PlaceFoeFreely ( GameObject [ ] gameObjects , Transform parent , float minDistance = 5f , float maxDistance = 20f , bool placeEnemyBehindPlayer = true , bool lineOfSight = true , int spawnTries = 15 )
260259 {
261- const float overlapSphereRadius = 0.65f ;
262- const float separationDistance = 1.25f ;
263260 const float maxFloorDistance = 4f ;
264261
265262 // Must have received a valid array
266263 if ( gameObjects == null || gameObjects . Length == 0 )
267264 return false ;
268265
269- // Set parent - otherwise caller must set a parent
270- if ( parent )
271- gameObjects [ pendingFoesSpawned ] . transform . parent = parent ;
272-
273- // Select a left or right direction outside of camera FOV
274- Quaternion rotation ;
275- float directionAngle = GameManager . Instance . MainCamera . fieldOfView ;
276- directionAngle += UnityEngine . Random . Range ( 0f , 4f ) ;
277- if ( UnityEngine . Random . Range ( 0f , 1f ) > 0.5f )
278- rotation = Quaternion . Euler ( 0 , - directionAngle , 0 ) ;
279- else
280- rotation = Quaternion . Euler ( 0 , directionAngle , 0 ) ;
281-
282- // Get direction vector and create a new ray
283- Vector3 angle = ( rotation * Vector3 . forward ) . normalized ;
284- Vector3 spawnDirection = GameManager . Instance . PlayerObject . transform . TransformDirection ( angle ) . normalized ;
285- Ray ray = new Ray ( GameManager . Instance . PlayerObject . transform . position , spawnDirection ) ;
286-
287- // Check for a hit
288- Vector3 currentPoint ;
289- RaycastHit initialHit ;
290- if ( Physics . Raycast ( ray , out initialHit , maxDistance ) )
291- {
292- float cos_normal = Vector3 . Dot ( - spawnDirection , initialHit . normal . normalized ) ;
293- if ( cos_normal < 1e-6 )
294- return false ;
295- float separationForward = separationDistance / cos_normal ;
296-
297- // Must be greater than minDistance
298- float distanceSlack = initialHit . distance - separationForward - minDistance ;
299- if ( distanceSlack < 0f )
300- return false ;
301-
302- // Separate out from hit point
303- float extraDistance = UnityEngine . Random . Range ( 0f , Mathf . Min ( 2f , distanceSlack ) ) ;
304- currentPoint = initialHit . point - spawnDirection * ( separationForward + extraDistance ) ;
305- }
306- else
266+ // Skip this foe if destroyed (e.g. player left building where pending)
267+ if ( ! gameObjects [ pendingFoesSpawned ] )
307268 {
308- // Player might be in an open area (e.g. outdoors) pick a random point along spawn direction
309- currentPoint = GameManager . Instance . PlayerObject . transform . position + spawnDirection * UnityEngine . Random . Range ( minDistance , maxDistance ) ;
269+ pendingFoesSpawned ++ ;
270+ return true ;
310271 }
311272
312- // Must be able to find a surface below
313- RaycastHit floorHit ;
314- ray = new Ray ( currentPoint , Vector3 . down ) ;
315- if ( ! Physics . Raycast ( ray , out floorHit , maxFloorDistance ) )
316- return false ;
317-
318- // Ensure this is open space
319- Vector3 testPoint = floorHit . point + Vector3 . up * separationDistance ;
320- Collider [ ] colliders = Physics . OverlapSphere ( testPoint , overlapSphereRadius ) ;
321- if ( colliders . Length > 0 )
322- return false ;
273+ // Set parent - otherwise caller must set a parent
274+ if ( parent )
275+ gameObjects [ pendingFoesSpawned ] . transform . parent = parent ;
323276
324- // This looks like a good spawn position
325- pendingFoeGameObjects [ pendingFoesSpawned ] . transform . position = testPoint ;
326- FinalizeFoe ( pendingFoeGameObjects [ pendingFoesSpawned ] ) ;
327- gameObjects [ pendingFoesSpawned ] . transform . LookAt ( GameManager . Instance . PlayerObject . transform . position ) ;
277+ var enemyCC = gameObjects [ pendingFoesSpawned ] . GetComponent < CharacterController > ( ) ;
278+ var playerCC = GameManager . Instance . PlayerController ;
279+ // Set parent if none specified already
280+ if ( ! gameObjects [ pendingFoesSpawned ] . transform . parent )
281+ gameObjects [ pendingFoesSpawned ] . transform . parent = GameObjectHelper . GetBestParent ( ) ;
282+
283+ for ( int i = 0 ; i < spawnTries ; ++ i ) {
284+ float fov = GameManager . Instance . MainCamera . fieldOfView ;
285+ float randomAngle ;
286+ if ( ! lineOfSight || i > spawnTries / 3 ) // give up on line of sight checks after we've tried a few times
287+ randomAngle = UnityEngine . Random . Range ( - 180 , 180 ) ;
288+ else if ( ! placeEnemyBehindPlayer )
289+ randomAngle = UnityEngine . Random . Range ( - fov , fov ) ;
290+ else
291+ randomAngle = UnityEngine . Random . Range ( fov , 180f ) * ( UnityEngine . Random . value < 0.5f ? - 1f : 1f ) ;
292+ Quaternion rot = Quaternion . Euler ( 0 , randomAngle , 0 ) ;
293+ Vector3 angle = ( rot * Vector3 . forward ) . normalized ;
294+ Vector3 spawnDirection = GameManager . Instance . PlayerObject . transform . TransformDirection ( angle ) . normalized ;
295+ Vector3 playerFootPosition = playerCC . transform . position + playerCC . center - playerCC . height / 2f * Vector3 . down ;
296+ Vector3 spawnPos = playerFootPosition + spawnDirection * UnityEngine . Random . Range ( minDistance , maxDistance ) ;
297+ enemyCC . transform . rotation = playerCC . transform . rotation ;
298+ spawnPos += enemyCC . height / 2f * Vector3 . up + enemyCC . center ;
299+
300+ // Must be able to find a surface below
301+ if ( ! Physics . Raycast ( spawnPos , Vector3 . down , out RaycastHit floorHit , maxFloorDistance , DFULayerMasks . CorporealMask ) )
302+ continue ;
303+
304+ // Ensure this is open space
305+ spawnPos = floorHit . point + enemyCC . center + ( enemyCC . height / 2f + .5f ) * Vector3 . up ;
306+ Collider [ ] colliders = Physics . OverlapCapsule ( spawnPos - enemyCC . height / 2f * Vector3 . up , spawnPos + enemyCC . height / 2f * Vector3 . up , enemyCC . radius , DFULayerMasks . CorporealMask ) ;
307+ if ( colliders . Length > 0 )
308+ continue ;
309+
310+ Debug . Log ( $ "CreateFoe: Found an enemy spawn point in { i } tries") ;
311+
312+ // This looks like a good spawn position
313+ pendingFoeGameObjects [ pendingFoesSpawned ] . transform . position = spawnPos ;
314+ FinalizeFoe ( pendingFoeGameObjects [ pendingFoesSpawned ] ) ;
315+ gameObjects [ pendingFoesSpawned ] . transform . LookAt ( GameManager . Instance . PlayerObject . transform . position ) ;
316+
317+ // Send msg message on first spawn only
318+ if ( msgMessageID != - 1 )
319+ {
320+ ParentQuest . ShowMessagePopup ( msgMessageID , oncePerQuest : true ) ;
321+ msgMessageID = - 1 ;
322+ }
328323
329- // Send msg message on first spawn only
330- if ( msgMessageID != - 1 )
331- {
332- ParentQuest . ShowMessagePopup ( msgMessageID , oncePerQuest : true ) ;
333- msgMessageID = - 1 ;
324+ // Increment count
325+ pendingFoesSpawned ++ ;
326+ return true ;
334327 }
335328
336- // Increment count
337- pendingFoesSpawned ++ ;
338- return true ;
329+ // Couldn't find a spawn point
330+ Debug . Log ( $ "CreateFoe: Couldn't find a valid enemy spawn point after { spawnTries } tries." ) ;
331+ return false ;
339332 }
340-
341333 // Fine tunes foe position slightly based on mobility and enables GameObject
342334 void FinalizeFoe ( GameObject go )
343335 {
0 commit comments