Script v1_7_1_0
- AI
- Animals
- Collections
- Contracts
- Debug
- Economy
- Elements
- EnvironmentalScore
- Errors
- Events
- GUI
- Handtools
- Hud
- I3d
- Input
- Jobs
- Maps
- Materials
- Misc
- Objects
- AnimatedMapObject
- AnimatedObject
- Bale
- Basketball
- BgaSellStation
- BunkerSilo
- BuyingStation
- DigitalDisplay
- DogBall
- HelpIcons
- InlineBale
- InlineBaleSingle
- LoadingStation
- MountableObject
- NightIllumination
- Nightlight2
- NightlightFlicker
- PackedBale
- PhysicsObject
- ProductionPoint
- Rotator
- SellingStation
- SimParticleSystem
- Storage
- SunAdmirer
- TourIconsMobile
- UnloadingStation
- VehicleSellingPoint
- WildlifeSpawner
- Parameters
- Placeables
- Placement
- Player
- Shop
- Sounds
- Specialization
- Specializations
- StateMachine
- Statistics
- Tasks
- Triggers
- Utils
- Vehicles
Engine v1_7_1_0
- AI
- Animation
- Camera
- Entity
- Fillplanes
- general
- General
- I3D
- Input
- Lighting
- Math
- Network
- Node
- NoteNode
- Overlays
- Particle System
- Physics
- Rendering
- Scenegraph
- Shape
- Sound
- Spline
- String
- Terrain Detail
- Text Rendering
- Tire Track
- VoiceChat
- XML
Foundation Reference
WildlifeSpawner
DescriptionWildlife Spawner classXML Configuration Parameters
wildlifeSpawner#maxCost | / max cost of all animals generated by the spawner |
wildlifeSpawner#checkTimeInterval | / how often creation process is executed (in seconds) |
wildlifeSpawner.area#areaSpawnRadius | / radius of the main spawn circle, testing circles are placed on this radius |
wildlifeSpawner.area#areaMaxRadius | / radius where animals are removed |
wildlifeSpawner.area#spawnCircleRadius | / radius of testing circles |
wildlifeSpawner.area.species#name | / name of the animal to spawn |
wildlifeSpawner.area.species#config | / configuration file of the animal to spawn |
wildlifeSpawner.area.species.spawnRules#hours | / daytimes where we can spawn an animal |
wildlifeSpawner.area.species.spawnRules#onField | / if true, will spawn on field ground |
wildlifeSpawner.area.species.spawnRules#hasTrees | / if true, will spawn if trees in testing circle |
wildlifeSpawner.area.species.cost | / cost of one animal |
wildlifeSpawner.area.species.maxCount | / maximal amount of animal to spawn |
wildlifeSpawner.area.species.spawnCount | / how many animals to spawn in one group |
wildlifeSpawner.area.species.groupSpawnRadius | / radius within which the animals are spawned |
Functions
- addAreaOfInterest
- animalExists
- checkArea
- checkAreas
- consoleCommandAddWildlifeAnimalToDebug
- consoleCommandRemoveWildlifeAnimalToDebug
- consoleCommandToggleEnabled
- consoleCommandToggleShowWildlife
- consoleCommandToggleShowWildlifeAnimation
- consoleCommandToggleShowWildlifeId
- consoleCommandToggleShowWildlifeSteering
- countAnimalsTobeSpawned
- countTrees
- debugDraw
- delete
- getIsInWater
- getPlayerCenter
- isInDebugList
- loadMapData
- new
- onConnectionClosed
- parseSpawnRule
- removeAllAnimals
- removeFarAwayAnimals
- spawnAnimals
- treeCountTestCallback
- trySpawnAtArea
- update
- updateAreaOfInterest
- updateSpawner
addAreaOfInterest
DescriptionAdds an area of interest to checkDefinition
addAreaOfInterest(float liveTime, float posX, float posZ, float radius)Arguments
float | liveTime | how long the area is available |
float | posX | x world position |
float | posZ | z world position |
float | radius | radius of the area in m |
623 | function WildlifeSpawner:addAreaOfInterest(liveTime, posX, posZ, radius) |
624 | if #self.areasOfInterest <= self.maxAreaOfInterest then |
625 | local info = {} |
626 | info.liveTime = liveTime |
627 | info.positionX = posX |
628 | info.positionZ = posZ |
629 | info.radius = radius |
630 | info.timeToLive = self.areaOfInterestliveTime |
631 | table.insert(self.areasOfInterest, info) |
632 | end |
633 | end |
animalExists
DescriptionDefinitionanimalExists()Return Values
bool |
638 | function WildlifeSpawner:animalExists(spawnId, animalId) |
639 | for _, area in pairs(self.areas) do |
640 | for _, species in pairs(area.species) do |
641 | if species.classType == "companionAnimal" then |
642 | for _, spawn in pairs(species.spawned) do |
643 | if spawn.spawnId == spawnId and animalId < spawn.count then |
644 | return true |
645 | end |
646 | end |
647 | end |
648 | end |
649 | end |
650 | return false |
651 | end |
checkArea
DescriptionCheck area with to see if we should spawn animals (trees amount, is a field, is in water, hours of the day)Definition
checkArea(float x, float y, float z, table rules, float radius)Arguments
float | x | x world position from which areas are checked |
float | y | y world position from which areas are checked |
float | z | z world position from which areas are checked |
table | rules | rules to check against for the species |
float | radius | radius of the test |
bool | returns | true if all tests are validated |
425 | function WildlifeSpawner:checkArea(x, y, z, rules, radius, isInWater) |
426 | local validArea = false |
427 | local currentHour = math.floor(g_currentMission.environment.dayTime / (60 * 60 * 1000)) |
428 | local isOnField |
429 | local hasTrees |
430 | |
431 | for _, rule in pairs(rules) do |
432 | if (currentHour >= rule.hourFrom or currentHour <= rule.hourTo) then |
433 | local validRule = true |
434 | if rule.onField ~= WildlifeSpawner.RULE.DONT_CARE then |
435 | if isOnField == nil then |
436 | isOnField, _ = FSDensityMapUtil.getFieldDataAtWorldPosition(x, y, z) |
437 | end |
438 | validRule = (rule.onField == WildlifeSpawner.RULE.REQUIRED and isOnField) or |
439 | (rule.onField == WildlifeSpawner.RULE.NOT_ALLOWED and not isOnField) |
440 | end |
441 | |
442 | if validRule and rule.hasTrees ~= WildlifeSpawner.RULE.DONT_CARE then |
443 | if hasTrees == nil then |
444 | hasTrees = self:countTrees(x, y, z, radius) > 3 |
445 | end |
446 | validRule = (rule.hasTrees == WildlifeSpawner.RULE.REQUIRED and hasTrees) or |
447 | (rule.hasTrees == WildlifeSpawner.RULE.NOT_ALLOWED and not hasTrees) |
448 | end |
449 | |
450 | if validRule and rule.inWater ~= WildlifeSpawner.RULE.DONT_CARE then |
451 | validRule = (rule.inWater == WildlifeSpawner.RULE.REQUIRED and isInWater) or |
452 | (rule.inWater == WildlifeSpawner.RULE.NOT_ALLOWED and not isInWater) |
453 | end |
454 | |
455 | if validRule then |
456 | validArea = true |
457 | break |
458 | end |
459 | end |
460 | end |
461 | |
462 | return validArea |
463 | end |
checkAreas
DescriptionFor each areas, we try to spawn first in areas of interests that have been registered by workAreas. If there is no spawn then, we try to spawn at a random position around the player.Definition
checkAreas(float x, float y, float z)Arguments
float | x | x world position from which areas are checked |
float | y | y world position from which areas are checked |
float | z | z world position from which areas are checked |
388 | function WildlifeSpawner:checkAreas(x, y, z) |
389 | local testX = x |
390 | local testZ = z |
391 | local testY = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, testX, 0, testZ) + 0.5 |
392 | |
393 | local isInWater = self:getIsInWater(testX, testY, testZ) |
394 | |
395 | for _, area in pairs(self.areas) do |
396 | local hasSpawned = false |
397 | for _, interestArea in pairs(self.areasOfInterest) do |
398 | local distSq = (testX - interestArea.positionX) * (testX - interestArea.positionX) + (testZ - interestArea.positionZ) * (testZ - interestArea.positionZ) |
399 | if (distSq < (area.areaSpawnRadius * area.areaSpawnRadius)) then |
400 | hasSpawned = self:trySpawnAtArea(area.species, interestArea.radius, testX, testY, testZ, isInWater) |
401 | if hasSpawned then |
402 | break |
403 | end |
404 | end |
405 | end |
406 | if not hasSpawned then |
407 | local angle = math.rad(math.random(0, 360)) |
408 | testX = x + area.areaSpawnRadius * math.cos(angle) - area.areaSpawnRadius * math.sin(angle) |
409 | testZ = z + area.areaSpawnRadius * math.cos(angle) + area.areaSpawnRadius * math.sin(angle) |
410 | testY = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, testX, 0, testZ) + 0.5 |
411 | |
412 | self:trySpawnAtArea(area.species, area.spawnCircleRadius, testX, testY, testZ, isInWater) |
413 | end |
414 | end |
415 | end |
consoleCommandAddWildlifeAnimalToDebug
DescriptionDefinitionconsoleCommandAddWildlifeAnimalToDebug()Return Values
string | that | will be displayed on console |
668 | function WildlifeSpawner:consoleCommandAddWildlifeAnimalToDebug(spawnId, animalId) |
669 | local argsTest = true |
670 | spawnId = tonumber(spawnId) |
671 | if spawnId == nil then |
672 | argsTest = false |
673 | end |
674 | animalId = tonumber(animalId) |
675 | if animalId == nil then |
676 | argsTest = false |
677 | end |
678 | |
679 | if argsTest and self:animalExists(spawnId, animalId) then |
680 | table.insert(self.debugAnimalList, {spawnId = spawnId, animalId = animalId}) |
681 | return string.format("-- added [spawn(%d)][animal(%d)] to debug list.", spawnId, animalId) |
682 | else |
683 | return string.format("-- gsWildlifeAddAnimalToDebug [spawnId][animalId]") |
684 | end |
685 | end |
consoleCommandRemoveWildlifeAnimalToDebug
DescriptionDefinitionconsoleCommandRemoveWildlifeAnimalToDebug()Return Values
string | that | will be displayed on console |
690 | function WildlifeSpawner:consoleCommandRemoveWildlifeAnimalToDebug(spawnId, animalId) |
691 | local argsTest = true |
692 | spawnId = tonumber(spawnId) |
693 | if spawnId == nil then |
694 | argsTest = false |
695 | end |
696 | animalId = tonumber(animalId) |
697 | if animalId == nil then |
698 | argsTest = false |
699 | end |
700 | if argsTest then |
701 | for key, entry in pairs(self.debugAnimalList) do |
702 | if entry.spawnId == spawnId and entry.animalId == animalId then |
703 | table.remove(self.debugAnimalList, key) |
704 | return string.format("-- removed [spawn(%d)][animal(%d)] from debug list.", spawnId, animalId) |
705 | end |
706 | end |
707 | end |
708 | return string.format("-- gsWildlifeRemoveAnimalToDebug [spawnId][animalId]") |
709 | end |
consoleCommandToggleEnabled
DescriptionDefinitionconsoleCommandToggleEnabled()Code
762 | function WildlifeSpawner:consoleCommandToggleEnabled(state) |
763 | if state ~= nil then |
764 | state = Utils.stringToBoolean(state) |
765 | end |
766 | self.isEnabled = Utils.getNoNil(state, not self.isEnabled) |
767 | |
768 | if not self.isEnabled then |
769 | self:removeAllAnimals() |
770 | print("removed all wildlife animals") |
771 | end |
772 | |
773 | return string.format("Wildlife isEnabled=%s", self.isEnabled) |
774 | end |
consoleCommandToggleShowWildlife
DescriptionDefinitionconsoleCommandToggleShowWildlife()Return Values
string | that | will be displayed on console |
715 | function WildlifeSpawner:consoleCommandToggleShowWildlife() |
716 | self.debugShow = not self.debugShow |
717 | |
718 | return string.format("-- show Wildlife debug = %s", tostring(self.debugShow)) |
719 | end |
consoleCommandToggleShowWildlifeAnimation
DescriptionDefinitionconsoleCommandToggleShowWildlifeAnimation()Return Values
string | that | will be displayed on console |
754 | function WildlifeSpawner:consoleCommandToggleShowWildlifeAnimation() |
755 | self.debugShowAnimation = not self.debugShowAnimation |
756 | |
757 | return string.format("-- show Wildlife Animation = %s", tostring(self.debugShowAnimation)) |
758 | end |
consoleCommandToggleShowWildlifeId
DescriptionDefinitionconsoleCommandToggleShowWildlifeId()Return Values
string | that | will be displayed on console |
724 | function WildlifeSpawner:consoleCommandToggleShowWildlifeId() |
725 | self.debugShowId = self.debugShowId + 1 |
726 | |
727 | if self.debugShowId > WildlifeSpawner.DEBUGSHOWIDSTATES.MAX then |
728 | self.debugShowId = WildlifeSpawner.DEBUGSHOWIDSTATES.NONE |
729 | end |
730 | |
731 | local state = "" |
732 | if (self.debugShowId == WildlifeSpawner.DEBUGSHOWIDSTATES.NONE) then |
733 | state = "NONE" |
734 | elseif (self.debugShowId == WildlifeSpawner.DEBUGSHOWIDSTATES.SINGLE) then |
735 | state = "SINGLE" |
736 | elseif (self.debugShowId == WildlifeSpawner.DEBUGSHOWIDSTATES.ALL) then |
737 | state = "ALL" |
738 | end |
739 | return string.format("-- show Wildlife Id = %s", state) |
740 | end |
consoleCommandToggleShowWildlifeSteering
DescriptionDefinitionconsoleCommandToggleShowWildlifeSteering()Return Values
string | that | will be displayed on console |
745 | function WildlifeSpawner:consoleCommandToggleShowWildlifeSteering() |
746 | self.debugShowSteering = not self.debugShowSteering |
747 | |
748 | return string.format("-- show Wildlife Steering = %s", tostring(self.debugShowSteering)) |
749 | end |
countAnimalsTobeSpawned
DescriptionCalculate a random amount of animals that can be spawned for a speciesDefinition
countAnimalsTobeSpawned(table species)Arguments
table | species |
integer | returns | the number of animals to spawn |
508 | function WildlifeSpawner:countAnimalsTobeSpawned(species) |
509 | local remainingAnimal = math.floor((self.maxCost - self.totalCost) / species.cost) |
510 | |
511 | if species.minCount > remainingAnimal then |
512 | return 0 |
513 | end |
514 | local deltaNbAnimals = species.maxCount - species.minCount |
515 | local nbAnimals = species.minCount + math.random(1, deltaNbAnimals) |
516 | nbAnimals = math.min(remainingAnimal, nbAnimals) |
517 | return nbAnimals |
518 | end |
countTrees
DescriptionCount number of treesDefinition
countTrees(float x, float y, float z, radius radius)Arguments
float | x | x world position from which areas are checked |
float | y | y world position from which areas are checked |
float | z | z world position from which areas are checked |
radius | radius | of the test in m |
integer | number | of trees found |
472 | function WildlifeSpawner:countTrees(x, y, z, radius) |
473 | self.treeCount = 0 |
474 | overlapSphere(x, y, z, radius, "treeCountTestCallback", self, CollisionFlag.TREE, false, true, false) |
475 | --overlapBox(x, y, z, 0, 1, 0, radius, radius, radius, "treeCountTestCallback", self, self.collisionDetectionMask, false, true, false) |
476 | return self.treeCount |
477 | end |
debugDraw
DescriptionDisplay debug informationDefinition
debugDraw()Code
562 | function WildlifeSpawner:debugDraw() |
563 | renderText(0.02, 0.95, 0.02, string.format("Wildlife Info\nCost(%d / %d)", self.totalCost, self.maxCost)) |
564 | local passedTest, originX, originY, originZ = self:getPlayerCenter() |
565 | |
566 | if passedTest then |
567 | for _, area in pairs(self.areas) do |
568 | for _, species in pairs(area.species) do |
569 | for _, spawn in pairs(species.spawned) do |
570 | if spawn.spawnId ~= nil then |
571 | local distance = 0.0 |
572 | |
573 | if species.classType == "companionAnimal" then |
574 | distance, _ = getCompanionClosestDistance(spawn.spawnId, originX, originY, originZ) |
575 | elseif species.classType == "lightWildlife" then |
576 | distance = species.lightWildlife:getClosestDistance(originX, originY, originZ) |
577 | distance = math.sqrt(distance) |
578 | end |
579 | local text = string.format("[%s][%d]\n- nearest player distance (%.3f)", species.name, spawn.spawnId, distance) |
580 | |
581 | Utils.renderTextAtWorldPosition(spawn.posX, spawn.posY + 0.12, spawn.posZ, text, getCorrectTextSize(0.012), 0) |
582 | DebugUtil.drawDebugCubeAtWorldPos(spawn.posX, spawn.posY, spawn.posZ, 1, 0, 0, 0, 1, 0, 0.05, 0.05, 0.05, 1.0, 1.0, 0.0) |
583 | DebugUtil.drawDebugCircle(spawn.posX, spawn.posY, spawn.posZ, species.groupSpawnRadius, 10.0, {1.0, 1.0, 0.0}) |
584 | end |
585 | end |
586 | end |
587 | end |
588 | end |
589 | |
590 | for _, area in pairs(self.areas) do |
591 | for _, species in pairs(area.species) do |
592 | if species.classType == "companionAnimal" then |
593 | for _, spawn in pairs(species.spawned) do |
594 | for animalId=0, spawn.count - 1 do |
595 | local showAdditionalInfo = self:isInDebugList(spawn.spawnId, animalId) |
596 | local showId = (self.debugShowId == WildlifeSpawner.DEBUGSHOWIDSTATES.SINGLE and showAdditionalInfo) or self.debugShowId == WildlifeSpawner.DEBUGSHOWIDSTATES.ALL |
597 | companionDebugDraw(spawn.spawnId, animalId, showId, showAdditionalInfo and self.debugShowSteering, showAdditionalInfo and self.debugShowAnimation) |
598 | end |
599 | end |
600 | end |
601 | end |
602 | end |
603 | end |
delete
DescriptionDelete instanceDefinition
delete()Code
81 | function WildlifeSpawner:delete() |
82 | self:removeAllAnimals() |
83 | for _, area in pairs(self.areas) do |
84 | for _, species in pairs(area.species) do |
85 | if species.lightWildlife ~= nil then |
86 | species.lightWildlife:delete() |
87 | end |
88 | end |
89 | end |
90 | |
91 | self.areas = {} |
92 | |
93 | removeConsoleCommand("gsWildlifeToggle") |
94 | removeConsoleCommand("gsWildlifeDebug") |
95 | removeConsoleCommand("gsWildlifeDebugId") |
96 | removeConsoleCommand("gsWildlifeDebugSteering") |
97 | removeConsoleCommand("gsWildlifeDebugAnimation") |
98 | removeConsoleCommand("gsWildlifeDebugAnimalAdd") |
99 | removeConsoleCommand("gsWildlifeDebugAnimalRemove") |
100 | end |
getIsInWater
DescriptionCheck if position is in waterDefinition
getIsInWater(float x, float y, float z)Arguments
float | x | x world position from which areas are checked |
float | y | y world position from which areas are checked |
float | z | z world position from which areas are checked |
bool | returns | true if there is water |
499 | function WildlifeSpawner:getIsInWater(x, y, z) |
500 | local waterY = g_currentMission.environmentAreaSystem:getWaterYAtWorldPosition(x, y, z) or -2000 |
501 | return waterY > y |
502 | end |
getPlayerCenter
DescriptionGet player location from which the tests should be doneDefinition
getPlayerCenter()Return Values
float | x | world position. default is 0 |
float | x | world position. default is 0 |
float | x | world position. default is 0 |
344 | function WildlifeSpawner:getPlayerCenter() |
345 | if self.playerNode ~= nil and entityExists(self.playerNode) then |
346 | local x, y, z = getWorldTranslation(self.playerNode) |
347 | return true, x, y, z |
348 | end |
349 | return false, 0, 0, 0 |
350 | end |
isInDebugList
DescriptionDefinitionisInDebugList()Return Values
bool |
656 | function WildlifeSpawner:isInDebugList(spawnId, animalId) |
657 | for _, entry in pairs(self.debugAnimalList) do |
658 | if entry.spawnId == spawnId and entry.animalId == animalId then |
659 | return true |
660 | end |
661 | end |
662 | return false |
663 | end |
loadMapData
DescriptionLoads xml file (areas and containing animals)Definition
loadMapData(table xmlFile)Arguments
table | xmlFile | XML file handle |
bool | returns | true if load is successful |
134 | function WildlifeSpawner:loadMapData(xmlFile) |
135 | local filename = getXMLString(xmlFile, "map.wildlife#filename") |
136 | if filename == nil or filename == "" then |
137 | Logging.xmlInfo(xmlFile, "No wildlife config file defined") |
138 | return false |
139 | end |
140 | |
141 | filename = Utils.getFilename(filename, g_currentMission.baseDirectory) |
142 | |
143 | local wildlifeXmlFile = loadXMLFile("wildlife", filename) |
144 | if wildlifeXmlFile == 0 or wildlifeXmlFile == nil then |
145 | Logging.xmlError(xmlFile, "Could not load wildlife config file '%s'", filename) |
146 | return false |
147 | end |
148 | |
149 | self.maxCost = Utils.getNoNil(getXMLInt(wildlifeXmlFile, "wildlifeSpawner#maxCost"), 0) |
150 | self.checkTimeInterval = Utils.getNoNil(getXMLFloat(wildlifeXmlFile, "wildlifeSpawner#checkTimeInterval"), 1.0) * 1000.0 |
151 | self.maxAreaOfInterest = Utils.getNoNil(getXMLFloat(wildlifeXmlFile, "wildlifeSpawner#maxAreaOfInterest"), 1) |
152 | self.areaOfInterestliveTime = Utils.getNoNil(getXMLFloat(wildlifeXmlFile, "wildlifeSpawner#areaOfInterestliveTime"), 1.0) * 1000.0 |
153 | local i = 0 |
154 | while true do |
155 | local areaBaseString = string.format("wildlifeSpawner.area(%d)", i) |
156 | if not hasXMLProperty(wildlifeXmlFile, areaBaseString) then |
157 | break |
158 | end |
159 | local newArea = {} |
160 | newArea.areaSpawnRadius = Utils.getNoNil(getXMLFloat(wildlifeXmlFile, areaBaseString .. "#areaSpawnRadius"), 1.0) |
161 | newArea.areaMaxRadius = Utils.getNoNil(getXMLFloat(wildlifeXmlFile, areaBaseString .. "#areaMaxRadius"), 1.0) |
162 | newArea.spawnCircleRadius = Utils.getNoNil(getXMLFloat(wildlifeXmlFile, areaBaseString .. "#spawnCircleRadius"), 1.0) |
163 | |
164 | newArea.species = {} |
165 | local j = 0 |
166 | while true do |
167 | local speciesBaseString = string.format("%s.species(%d)", areaBaseString, j) |
168 | if not hasXMLProperty(wildlifeXmlFile, speciesBaseString) then |
169 | break |
170 | end |
171 | local classTypeString = getXMLString(wildlifeXmlFile, speciesBaseString .. "#classType") |
172 | local classType = nil |
173 | |
174 | if classTypeString ~= nil then |
175 | if string.lower(classTypeString) == "companionanimal" then |
176 | classType = "companionAnimal" |
177 | elseif string.lower(classTypeString) == "lightwildlife" then |
178 | classType = "lightWildlife" |
179 | end |
180 | end |
181 | if classType ~= nil then |
182 | local newSpecies = {} |
183 | newSpecies.classType = classType |
184 | newSpecies.name = getXMLString(wildlifeXmlFile, speciesBaseString .. "#name") |
185 | newSpecies.configFilename = getXMLString(wildlifeXmlFile, speciesBaseString .. "#config") |
186 | newSpecies.cost = getXMLFloat(wildlifeXmlFile, speciesBaseString .. ".cost") |
187 | newSpecies.minCount = getXMLInt(wildlifeXmlFile, speciesBaseString .. ".minCount") |
188 | newSpecies.maxCount = getXMLInt(wildlifeXmlFile, speciesBaseString .. ".maxCount") |
189 | newSpecies.currentCount = 0 |
190 | newSpecies.spawnCount = getXMLInt(wildlifeXmlFile, speciesBaseString .. ".spawnCount") |
191 | newSpecies.groupSpawnRadius = getXMLInt(wildlifeXmlFile, speciesBaseString .. ".groupSpawnRadius") |
192 | newSpecies.spawned = {} |
193 | newSpecies.lightWildlife = nil |
194 | if classType == "lightWildlife" then |
195 | if newSpecies.name == "crow" then |
196 | newSpecies.lightWildlife = CrowsWildlife.new() |
197 | newSpecies.lightWildlife:load(Utils.getNoNil(getXMLString(wildlifeXmlFile, speciesBaseString .. "#config"), "")) |
198 | end |
199 | end |
200 | |
201 | newSpecies.spawnRules = {} |
202 | local k = 0 |
203 | while true do |
204 | local spawnRuleBaseString = string.format("%s.spawnRules.rule(%d)", speciesBaseString, k) |
205 | if not hasXMLProperty(wildlifeXmlFile, spawnRuleBaseString) then |
206 | break |
207 | end |
208 | |
209 | local newRule = {} |
210 | newRule.hourFrom = getXMLInt(wildlifeXmlFile, spawnRuleBaseString .. "#hourFrom") |
211 | newRule.hourTo = getXMLInt(wildlifeXmlFile, spawnRuleBaseString .. "#hourTo") |
212 | newRule.onField = self:parseSpawnRule(getXMLString(wildlifeXmlFile, spawnRuleBaseString .. "#onField")) |
213 | newRule.hasTrees = self:parseSpawnRule(getXMLString(wildlifeXmlFile, spawnRuleBaseString .. "#hasTrees")) |
214 | newRule.inWater = self:parseSpawnRule(getXMLString(wildlifeXmlFile, spawnRuleBaseString .. "#inWater")) |
215 | table.insert(newSpecies.spawnRules, newRule) |
216 | k = k + 1 |
217 | end |
218 | |
219 | table.insert(newArea.species, newSpecies) |
220 | end |
221 | j = j + 1 |
222 | end |
223 | table.insert(self.areas, newArea) |
224 | i = i + 1 |
225 | end |
226 | delete(wildlifeXmlFile) |
227 | |
228 | return true |
229 | end |
new
DescriptionCreating instanceDefinition
new(table customMt)Arguments
table | customMt | custom meta table |
table | instance | Instance of object |
45 | function WildlifeSpawner.new(customMt) |
46 | local self = setmetatable({}, customMt or WildlifeSpawner_mt) |
47 | |
48 | self.isEnabled = true -- flag to disable all wildlife |
49 | self.collisionDetectionMask = 4096 -- dynamic_objects |
50 | self.maxCost = 0 |
51 | self.checkTimeInterval = 0.0 |
52 | self.nextCheckTime = 0.0 |
53 | self.areas = {} |
54 | self.areasOfInterest = {} |
55 | self.totalCost = 0 |
56 | self.treeCount = 0 |
57 | self.playerNode = nil |
58 | self.avoidDistance = 20 |
59 | |
60 | -- Debug |
61 | -- if g_addCheatCommands then |
62 | self.debugAnimalList = {} |
63 | self.debugShow = false |
64 | self.debugShowId = WildlifeSpawner.DEBUGSHOWIDSTATES.NONE |
65 | self.debugShowSteering = false |
66 | self.debugShowAnimation = false |
67 | addConsoleCommand("gsWildlifeToggle", "Toggle wildlife on map", "consoleCommandToggleEnabled", self) |
68 | addConsoleCommand("gsWildlifeDebug", "Toggle shows/hide all wildlife debug information.", "consoleCommandToggleShowWildlife", self) |
69 | addConsoleCommand("gsWildlifeDebugId", "Toggle shows/hide all wildlife animal id.", "consoleCommandToggleShowWildlifeId", self) |
70 | addConsoleCommand("gsWildlifeDebugSteering", "Toggle shows/hide animal steering information.", "consoleCommandToggleShowWildlifeSteering", self) |
71 | addConsoleCommand("gsWildlifeDebugAnimation", "Toggle shows/hide animal animation information.", "consoleCommandToggleShowWildlifeAnimation", self) |
72 | addConsoleCommand("gsWildlifeDebugAnimalAdd", "Adds an animal to a debug list.", "consoleCommandAddWildlifeAnimalToDebug", self) |
73 | addConsoleCommand("gsWildlifeDebugAnimalRemove", "Removes an animal to a debug list.", "consoleCommandRemoveWildlifeAnimalToDebug", self) |
74 | -- end |
75 | |
76 | return self |
77 | end |
onConnectionClosed
DescriptionDefinitiononConnectionClosed()Code
104 | function WildlifeSpawner:onConnectionClosed() |
105 | self:removeAllAnimals() |
106 | end |
parseSpawnRule
DescriptionParse rule value stringDefinition
parseSpawnRule(string input)Arguments
string | input | string to parse |
235 | function WildlifeSpawner:parseSpawnRule(ruleValue) |
236 | if string.lower(ruleValue) == "required" then |
237 | return WildlifeSpawner.RULE.REQUIRED |
238 | elseif string.lower(ruleValue) == "notallowed" then |
239 | return WildlifeSpawner.RULE.NOT_ALLOWED |
240 | elseif string.lower(ruleValue) == "dontcare" then |
241 | return WildlifeSpawner.RULE.DONT_CARE |
242 | else |
243 | return WildlifeSpawner.RULE.DONT_CARE |
244 | end |
245 | end |
removeAllAnimals
DescriptionDefinitionremoveAllAnimals()Code
110 | function WildlifeSpawner:removeAllAnimals() |
111 | for _, area in pairs(self.areas) do |
112 | for _, species in pairs(area.species) do |
113 | for i=#species.spawned, 1, -1 do |
114 | if species.classType == "companionAnimal" then |
115 | local spawn = species.spawned[i] |
116 | if spawn.spawnId ~= nil then |
117 | delete(spawn.spawnId) |
118 | spawn.spawnId = nil |
119 | end |
120 | elseif species.classType == "lightWildlife" and species.lightWildlife ~= nil then |
121 | species.lightWildlife:removeAllAnimals() |
122 | end |
123 | table.remove(species.spawned, i) |
124 | end |
125 | end |
126 | end |
127 | self.totalCost = 0 |
128 | end |
removeFarAwayAnimals
DescriptionRemoving animals that are too far awayDefinition
removeFarAwayAnimals()Code
300 | function WildlifeSpawner:removeFarAwayAnimals() |
301 | local passedTest, originX, originY, originZ = self:getPlayerCenter() |
302 | |
303 | if passedTest then |
304 | for _, area in pairs(self.areas) do |
305 | for _, species in pairs(area.species) do |
306 | if species.classType == "companionAnimal" then |
307 | for i=#species.spawned, 1, -1 do |
308 | local spawn = species.spawned[i] |
309 | if spawn.spawnId ~= nil then |
310 | local distance, _ = getCompanionClosestDistance(spawn.spawnId, originX, originY, originZ) |
311 | |
312 | if distance > area.areaMaxRadius then |
313 | delete(spawn.spawnId) |
314 | spawn.spawnId = nil |
315 | species.currentCount = species.currentCount - spawn.count |
316 | self.totalCost = self.totalCost - species.cost * spawn.count |
317 | table.remove(species.spawned, i) |
318 | end |
319 | end |
320 | end |
321 | elseif species.classType == "lightWildlife" and species.lightWildlife ~= nil then |
322 | local removedAnimalsCount = species.lightWildlife:removeFarAwayAnimals(area.areaMaxRadius, originX, originY, originZ) |
323 | if removedAnimalsCount > 0 then |
324 | species.currentCount = species.currentCount - removedAnimalsCount |
325 | self.totalCost = self.totalCost - species.cost * removedAnimalsCount |
326 | for i=#species.spawned, 1, -1 do |
327 | --local spawn = species.spawned[i] |
328 | if species.lightWildlife:countSpawned() == 0 then |
329 | table.remove(species.spawned, i) |
330 | end |
331 | end |
332 | end |
333 | end |
334 | end |
335 | end |
336 | end |
337 | end |
spawnAnimals
DescriptionSpawn animalsDefinition
spawnAnimals(table species, float spawnPosX, float spawnPosY, float spawnPosZ)Arguments
table | species | species to spawn |
float | spawnPosX | x world position to spawn |
float | spawnPosY | y world position to spawn |
float | spawnPosZ | z world position to spawn |
bool | returns | true if animals are spawned |
527 | function WildlifeSpawner:spawnAnimals(species, spawnPosX, spawnPosY, spawnPosZ) |
528 | local xmlFilename = Utils.getFilename(species.configFilename, g_currentMission.loadingMapBaseDirectory) |
529 | |
530 | if species.name == nil or |
531 | xmlFilename == nil or |
532 | g_currentMission.terrainRootNode == nil or |
533 | species.currentCount >= species.maxCount then |
534 | return false |
535 | end |
536 | local nbAnimals = self:countAnimalsTobeSpawned(species) |
537 | if nbAnimals == 0 then |
538 | return false |
539 | end |
540 | local id = nil |
541 | if species.classType == "companionAnimal" then |
542 | id = createAnimalCompanionManager(species.name, xmlFilename, "wildlifeAnimal", spawnPosX, spawnPosY, spawnPosZ, g_currentMission.terrainRootNode, g_currentMission:getIsServer(), g_currentMission:getIsClient(), nbAnimals, AudioGroup.ENVIRONMENT) |
543 | setCompanionAvoidPlayer(id, self.playerNode, self.avoidDistance) |
544 | |
545 | local groundMask = CollisionFlag.TERRAIN + CollisionFlag.STATIC_WORLD |
546 | local obstacleMask = CollisionFlag.STATIC_OBJECTS + CollisionFlag.DYNAMIC_OBJECT + CollisionFlag.VEHICLE |
547 | setCompanionCollisionMask(id, groundMask, obstacleMask, CollisionFlag.WATER) |
548 | elseif species.classType == "lightWildlife" then |
549 | id = species.lightWildlife:createAnimals(species.name, spawnPosX, spawnPosY, spawnPosZ, nbAnimals) |
550 | end |
551 | if (id ~= nil) and (id ~= 0) then |
552 | table.insert(species.spawned, {spawnId = id, posX = spawnPosX, posY = spawnPosY, posZ = spawnPosZ, count = nbAnimals, avoidNode = self.playerNode}) |
553 | species.currentCount = species.currentCount + nbAnimals |
554 | self.totalCost = self.totalCost + species.cost * nbAnimals |
555 | return true |
556 | end |
557 | return false |
558 | end |
treeCountTestCallback
DescriptionTree count callbackDefinition
treeCountTestCallback(integer transformId)Arguments
integer | transformId | - transformId of the element detected in the overlap test |
bool | true | to continue counting trees |
483 | function WildlifeSpawner:treeCountTestCallback(transformId) |
484 | if transformId ~= 0 and getHasClassId(transformId, ClassIds.SHAPE) then |
485 | local object = getParent(transformId) |
486 | if object ~= nil and getSplitType(transformId) ~= 0 then |
487 | self.treeCount = self.treeCount + 1 |
488 | end |
489 | end |
490 | return true |
491 | end |
trySpawnAtArea
DescriptionWe try to spawn one animal type if the rules are validDefinition
trySpawnAtArea(table species, float spawnCircleRadius, float testX, float testY, float testZ)Arguments
table | species | species information |
float | spawnCircleRadius | spawn circle radius in m |
float | testX | x world position to test |
float | testY | y world position to test |
float | testZ | z world position to test |
bool | returns | true if animals are spawned |
369 | function WildlifeSpawner:trySpawnAtArea(species, spawnCircleRadius, testX, testY, testZ, isInWater) |
370 | for _, animalType in pairs(species) do |
371 | if self:checkArea(testX, testY, testZ, animalType.spawnRules, spawnCircleRadius, isInWater) then |
372 | local spawnPosX = testX + math.random() * spawnCircleRadius |
373 | local spawnPosZ = testZ + math.random() * spawnCircleRadius |
374 | local spawnPosY = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, spawnPosX, 0, spawnPosZ) + 0.5 |
375 | if self:spawnAnimals(animalType, spawnPosX, spawnPosY, spawnPosZ) then |
376 | return true |
377 | end |
378 | end |
379 | end |
380 | return false |
381 | end |
update
DescriptionUpdate function. Regulate animal population.Definition
update(float dt)Arguments
float | dt | time since last call in ms |
251 | function WildlifeSpawner:update(dt) |
252 | -- remove outdated areas of interest |
253 | self:updateAreaOfInterest(dt) |
254 | |
255 | if self.isEnabled then |
256 | -- add animals |
257 | self.nextCheckTime = self.nextCheckTime - dt |
258 | if (self.nextCheckTime < 0.0) then |
259 | self.nextCheckTime = self.checkTimeInterval |
260 | self:updateSpawner() |
261 | end |
262 | -- remove animals too far away |
263 | self:removeFarAwayAnimals() |
264 | |
265 | self.playerNode = nil |
266 | if g_currentMission.controlPlayer and g_currentMission.player ~= nil then |
267 | self.playerNode = g_currentMission.player.rootNode |
268 | elseif g_currentMission.controlledVehicle ~= nil then |
269 | self.playerNode = g_currentMission.controlledVehicle.rootNode |
270 | end |
271 | |
272 | -- update animal context |
273 | for _, area in pairs(self.areas) do |
274 | for _, species in pairs(area.species) do |
275 | if species.classType == "companionAnimal" then |
276 | for _, spawn in pairs(species.spawned) do |
277 | if spawn.spawnId ~= nil then |
278 | setCompanionDaytime(spawn.spawnId, g_currentMission.environment.dayTime) |
279 | if spawn.avoidNode ~= self.playerNode then |
280 | setCompanionAvoidPlayer(spawn.spawnId, self.playerNode, self.avoidDistance) |
281 | spawn.avoidNode = self.playerNode |
282 | end |
283 | end |
284 | end |
285 | elseif species.classType == "lightWildlife" then |
286 | species.lightWildlife:update(dt) |
287 | end |
288 | end |
289 | end |
290 | |
291 | if self.debugShow then |
292 | -- debug |
293 | self:debugDraw() |
294 | end |
295 | end |
296 | end |
updateAreaOfInterest
DescriptionRemoves an area of interest if the time to live expiresDefinition
updateAreaOfInterest(float dt)Arguments
float | dt | delta time in ms |
608 | function WildlifeSpawner:updateAreaOfInterest(dt) |
609 | for key, area in pairs(self.areasOfInterest) do |
610 | area.timeToLive = area.timeToLive - dt |
611 | if area.timeToLive <= 0.0 then |
612 | table.remove(self.areasOfInterest, key) |
613 | end |
614 | end |
615 | end |
updateSpawner
DescriptionUpdate spawner logicDefinition
updateSpawner()Code
354 | function WildlifeSpawner:updateSpawner() |
355 | local passedTest, x, y, z = self:getPlayerCenter() |
356 | if passedTest then |
357 | self:checkAreas(x, y, z) |
358 | end |
359 | end |