ThingTemplate.cpp 59 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444
  1. /*
  2. ** Command & Conquer Generals(tm)
  3. ** Copyright 2025 Electronic Arts Inc.
  4. **
  5. ** This program is free software: you can redistribute it and/or modify
  6. ** it under the terms of the GNU General Public License as published by
  7. ** the Free Software Foundation, either version 3 of the License, or
  8. ** (at your option) any later version.
  9. **
  10. ** This program is distributed in the hope that it will be useful,
  11. ** but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. ** GNU General Public License for more details.
  14. **
  15. ** You should have received a copy of the GNU General Public License
  16. ** along with this program. If not, see <http://www.gnu.org/licenses/>.
  17. */
  18. ////////////////////////////////////////////////////////////////////////////////
  19. // //
  20. // (c) 2001-2003 Electronic Arts Inc. //
  21. // //
  22. ////////////////////////////////////////////////////////////////////////////////
  23. // FILE: ThingTemplate.cpp ////////////////////////////////////////////////////////////////////////
  24. // Created: Colin Day, April 2001
  25. // Desc: Thing templates
  26. ///////////////////////////////////////////////////////////////////////////////////////////////////
  27. // INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
  28. #include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
  29. #define DEFINE_POWER_NAMES // for PowerNames[]
  30. #define DEFINE_SHADOW_NAMES // for TheShadowNames[]
  31. #define DEFINE_GEOMETRY_NAMES // for GeometryNames[]
  32. #define DEFINE_BUILD_COMPLETION_NAMES // for BuildCompletionNames[]
  33. #define DEFINE_EDITOR_SORTING_NAMES // for EditorSortingNames[]
  34. #define DEFINE_RADAR_PRIORITY_NAMES // for RadarPriorityNames[]
  35. #define DEFINE_BUILDABLE_STATUS_NAMES // for BuildableStatusNames[]
  36. #include "Common/DamageFX.h"
  37. #include "Common/GameAudio.h"
  38. #include "Common/GameCommon.h"
  39. #include "Common/GlobalData.h"
  40. #include "Common/INI.h"
  41. #include "Common/MessageStream.h"
  42. #include "Common/Module.h"
  43. #include "Common/ModuleFactory.h"
  44. #include "Common/Player.h"
  45. #include "Common/PlayerList.h"
  46. #include "Common/ProductionPrerequisite.h"
  47. #include "Common/Radar.h"
  48. #include "Common/RandomValue.h"
  49. #include "Common/Science.h"
  50. #include "Common/ThingTemplate.h"
  51. #include "Common/ThingFactory.h"
  52. #include "Common/ThingSort.h"
  53. #include "Common/BitFlagsIO.h"
  54. #include "GameClient/Drawable.h"
  55. #include "GameClient/FXList.h"
  56. #include "GameClient/GameText.h"
  57. #include "GameClient/Image.h"
  58. #include "GameClient/Shadow.h"
  59. #include "GameLogic/Armor.h"
  60. #include "GameLogic/Module/AIUpdate.h"
  61. #include "GameLogic/Module/SpecialPowerModule.h"
  62. #include "GameLogic/Object.h"
  63. #include "GameLogic/Powers.h"
  64. #include "GameLogic/Weapon.h"
  65. #ifdef _INTERNAL
  66. // for occasional debugging...
  67. //#pragma optimize("", off)
  68. //#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
  69. #endif
  70. //-------------------------------------------------------------------------------------------------
  71. //-------------------------------------------------------------------------------------------------
  72. const Int USE_EXP_VALUE_FOR_SKILL_VALUE = -999;
  73. ///////////////////////////////////////////////////////////////////////////////////////////////////
  74. // PRIVATE DATA ///////////////////////////////////////////////////////////////////////////////////
  75. ///////////////////////////////////////////////////////////////////////////////////////////////////
  76. AudioEventRTS ThingTemplate::s_audioEventNoSound;
  77. /*
  78. NOTE NOTE NOTE -- s_objectFieldParseTable and s_objectReskinFieldParseTable must be updated in tandem!
  79. "s_objectReskinFieldParseTable" is intended to be used for parsing the "ObjectReskin" keyword, and
  80. should only allow you to specify things that affect the appearance of a template.
  81. The idea is that some units are functionally the same, but different visually; rather than replicate
  82. all the settings, you can specify it as "this one is like that one, but looks different".
  83. Thus, currently, the only things in the reskin table are:
  84. -- DrawModules
  85. -- DrawModuleData
  86. -- Geometry
  87. So: if you add/remove/modify any settings that deal with visual appearance, you *may* want to
  88. add 'em to the reskin table... but do so VERY CAUTIOUSLY and with careful deliberation (and
  89. after checking around for other opinions).
  90. */
  91. // NOTE NOTE NOTE -- s_objectFieldParseTable and s_objectReskinFieldParseTable must be updated in tandem -- see comment above
  92. const FieldParse ThingTemplate::s_objectFieldParseTable[] =
  93. {
  94. { "DisplayName", INI::parseAndTranslateLabel, NULL, offsetof( ThingTemplate, m_displayName ) },
  95. { "RadarPriority", INI::parseByteSizedIndexList, RadarPriorityNames, offsetof( ThingTemplate, m_radarPriority ) },
  96. { "TransportSlotCount", INI::parseUnsignedByte, NULL, offsetof( ThingTemplate, m_transportSlotCount ) },
  97. { "FenceWidth", INI::parseReal, NULL, offsetof( ThingTemplate, m_fenceWidth ) },
  98. { "FenceXOffset", INI::parseReal, NULL, offsetof( ThingTemplate, m_fenceXOffset ) },
  99. { "IsBridge", INI::parseBool, NULL, offsetof( ThingTemplate, m_isBridge ) },
  100. { "ArmorSet", ThingTemplate::parseArmorTemplateSet, NULL, 0},
  101. { "WeaponSet", ThingTemplate::parseWeaponTemplateSet,NULL, 0},
  102. { "VisionRange", INI::parseReal, NULL, offsetof( ThingTemplate, m_visionRange ) },
  103. { "ShroudClearingRange", INI::parseReal, NULL, offsetof( ThingTemplate, m_shroudClearingRange ) },
  104. { "PlacementViewAngle", INI::parseAngleReal, NULL, offsetof( ThingTemplate, m_placementViewAngle ) },
  105. { "FactoryExitWidth", INI::parseReal, NULL, offsetof( ThingTemplate, m_factoryExitWidth ) },
  106. { "FactoryExtraBibWidth", INI::parseReal, NULL, offsetof( ThingTemplate, m_factoryExtraBibWidth ) },
  107. { "SkillPointValue", ThingTemplate::parseIntList, (void*)LEVEL_COUNT, offsetof( ThingTemplate, m_skillPointValues ) },
  108. { "ExperienceValue", ThingTemplate::parseIntList, (void*)LEVEL_COUNT, offsetof( ThingTemplate, m_experienceValues ) },
  109. { "ExperienceRequired", ThingTemplate::parseIntList, (void*)LEVEL_COUNT, offsetof( ThingTemplate, m_experienceRequired ) },
  110. { "IsTrainable", INI::parseBool, NULL, offsetof( ThingTemplate, m_isTrainable ) },
  111. { "Side", INI::parseAsciiString, NULL, offsetof( ThingTemplate, m_defaultOwningSide ) },
  112. // NOTE NOTE NOTE -- s_objectFieldParseTable and s_objectReskinFieldParseTable must be updated in tandem -- see comment above
  113. { "Prerequisites", ThingTemplate::parsePrerequisites, 0, 0 },
  114. { "Buildable", INI::parseByteSizedIndexList, BuildableStatusNames, offsetof( ThingTemplate, m_buildable) },
  115. { "BuildCost", INI::parseUnsignedShort, NULL, offsetof( ThingTemplate, m_buildCost ) },
  116. { "BuildTime", INI::parseReal, NULL, offsetof( ThingTemplate, m_buildTime ) },
  117. { "RefundValue", INI::parseUnsignedShort, NULL, offsetof( ThingTemplate, m_refundValue ) },
  118. { "BuildCompletion", INI::parseByteSizedIndexList, BuildCompletionNames, offsetof( ThingTemplate, m_buildCompletion ) },
  119. { "EnergyProduction", INI::parseInt, NULL, offsetof( ThingTemplate, m_energyProduction ) },
  120. { "EnergyBonus", INI::parseInt, NULL, offsetof( ThingTemplate, m_energyBonus ) },
  121. { "IsForbidden", INI::parseBool, NULL, offsetof( ThingTemplate, m_isForbidden ) },
  122. { "IsPrerequisite", INI::parseBool, NULL, offsetof( ThingTemplate, m_isPrerequisite ) },
  123. { "DisplayColor", INI::parseColorInt, NULL, offsetof( ThingTemplate, m_displayColor ) },
  124. { "EditorSorting", INI::parseByteSizedIndexList, EditorSortingNames, offsetof( ThingTemplate, m_editorSorting ) },
  125. { "KindOf", KindOfMaskType::parseFromINI, NULL, offsetof( ThingTemplate, m_kindof ) },
  126. { "CommandSet", INI::parseAsciiString, NULL, offsetof( ThingTemplate, m_commandSetString ) },
  127. { "BuildVariations", INI::parseAsciiStringVector, NULL, offsetof( ThingTemplate, m_buildVariations ) },
  128. // NOTE NOTE NOTE -- s_objectFieldParseTable and s_objectReskinFieldParseTable must be updated in tandem -- see comment above
  129. { "Behavior", ThingTemplate::parseModuleName, (const void*)MODULETYPE_BEHAVIOR, offsetof(ThingTemplate, m_behaviorModuleInfo) },
  130. { "Body", ThingTemplate::parseModuleName, (const void*)999, offsetof(ThingTemplate, m_behaviorModuleInfo) },
  131. { "Draw", ThingTemplate::parseModuleName, (const void*)MODULETYPE_DRAW, offsetof(ThingTemplate, m_drawModuleInfo) },
  132. { "ClientUpdate", ThingTemplate::parseModuleName, (const void*)MODULETYPE_CLIENT_UPDATE, offsetof(ThingTemplate, m_clientUpdateModuleInfo) },
  133. // NOTE NOTE NOTE -- s_objectFieldParseTable and s_objectReskinFieldParseTable must be updated in tandem -- see comment above
  134. { "SelectPortrait", INI::parseAsciiString, NULL, offsetof( ThingTemplate, m_selectedPortraitImageName ) },
  135. { "ButtonImage", INI::parseAsciiString, NULL, offsetof( ThingTemplate, m_buttonImageName ) },
  136. //Code renderer handles these states now.
  137. //{ "InventoryImageEnabled", INI::parseAsciiString, NULL, offsetof( ThingTemplate, m_inventoryImage[ INV_IMAGE_ENABLED ] ) },
  138. //{ "InventoryImageDisabled", INI::parseAsciiString, NULL, offsetof( ThingTemplate, m_inventoryImage[ INV_IMAGE_DISABLED ] ) },
  139. //{ "InventoryImageHilite", INI::parseAsciiString, NULL, offsetof( ThingTemplate, m_inventoryImage[ INV_IMAGE_HILITE ] ) },
  140. //{ "InventoryImagePushed", INI::parseAsciiString, NULL, offsetof( ThingTemplate, m_inventoryImage[ INV_IMAGE_PUSHED ] ) },
  141. { "UpgradeCameo1", INI::parseAsciiString, NULL, offsetof( ThingTemplate, m_upgradeCameoUpgradeNames[ 0 ] ) },
  142. { "UpgradeCameo2", INI::parseAsciiString, NULL, offsetof( ThingTemplate, m_upgradeCameoUpgradeNames[ 1 ] ) },
  143. { "UpgradeCameo3", INI::parseAsciiString, NULL, offsetof( ThingTemplate, m_upgradeCameoUpgradeNames[ 2 ] ) },
  144. { "UpgradeCameo4", INI::parseAsciiString, NULL, offsetof( ThingTemplate, m_upgradeCameoUpgradeNames[ 3 ] ) },
  145. { "UpgradeCameo5", INI::parseAsciiString, NULL, offsetof( ThingTemplate, m_upgradeCameoUpgradeNames[ 4 ] ) },
  146. // NOTE NOTE NOTE -- s_objectFieldParseTable and s_objectReskinFieldParseTable must be updated in tandem -- see comment above
  147. { "VoiceSelect", INI::parseDynamicAudioEventRTS, NULL, offsetof( ThingTemplate, m_audioarray.m_audio[TTAUDIO_voiceSelect]) },
  148. { "VoiceGroupSelect", INI::parseDynamicAudioEventRTS, NULL, offsetof( ThingTemplate, m_audioarray.m_audio[TTAUDIO_voiceGroupSelect]) },
  149. { "VoiceMove", INI::parseDynamicAudioEventRTS, NULL, offsetof( ThingTemplate, m_audioarray.m_audio[TTAUDIO_voiceMove]) },
  150. { "VoiceAttack", INI::parseDynamicAudioEventRTS, NULL, offsetof( ThingTemplate, m_audioarray.m_audio[TTAUDIO_voiceAttack]) },
  151. { "VoiceEnter", INI::parseDynamicAudioEventRTS, NULL, offsetof( ThingTemplate, m_audioarray.m_audio[TTAUDIO_voiceEnter ]) },
  152. { "VoiceFear", INI::parseDynamicAudioEventRTS, NULL, offsetof( ThingTemplate, m_audioarray.m_audio[TTAUDIO_voiceFear ]) },
  153. { "VoiceSelectElite", INI::parseDynamicAudioEventRTS, NULL, offsetof( ThingTemplate, m_audioarray.m_audio[TTAUDIO_voiceSelectElite ]) },
  154. { "VoiceCreated", INI::parseDynamicAudioEventRTS, NULL, offsetof( ThingTemplate, m_audioarray.m_audio[TTAUDIO_voiceCreated]) },
  155. { "VoiceTaskUnable", INI::parseDynamicAudioEventRTS, NULL, offsetof( ThingTemplate, m_audioarray.m_audio[TTAUDIO_voiceTaskUnable ]) },
  156. { "VoiceTaskComplete", INI::parseDynamicAudioEventRTS, NULL, offsetof( ThingTemplate, m_audioarray.m_audio[TTAUDIO_voiceTaskComplete ]) },
  157. { "VoiceMeetEnemy", INI::parseDynamicAudioEventRTS, NULL, offsetof( ThingTemplate, m_audioarray.m_audio[TTAUDIO_voiceMeetEnemy]) },
  158. { "VoiceGarrison", INI::parseDynamicAudioEventRTS, NULL, offsetof( ThingTemplate, m_audioarray.m_audio[TTAUDIO_voiceGarrison]) },
  159. #ifdef ALLOW_SURRENDER
  160. { "VoiceSurrender", INI::parseDynamicAudioEventRTS, NULL, offsetof( ThingTemplate, m_audioarray.m_audio[TTAUDIO_voiceSurrender]) },
  161. #endif
  162. { "VoiceDefect", INI::parseDynamicAudioEventRTS, NULL, offsetof( ThingTemplate, m_audioarray.m_audio[TTAUDIO_voiceDefect]) },
  163. { "VoiceAttackSpecial", INI::parseDynamicAudioEventRTS, NULL, offsetof( ThingTemplate, m_audioarray.m_audio[TTAUDIO_voiceAttackSpecial ]) },
  164. { "VoiceAttackAir", INI::parseDynamicAudioEventRTS, NULL, offsetof( ThingTemplate, m_audioarray.m_audio[TTAUDIO_voiceAttackAir ]) },
  165. { "VoiceGuard", INI::parseDynamicAudioEventRTS, NULL, offsetof( ThingTemplate, m_audioarray.m_audio[TTAUDIO_voiceGuard ]) },
  166. { "SoundMoveStart", INI::parseDynamicAudioEventRTS, NULL, offsetof( ThingTemplate, m_audioarray.m_audio[TTAUDIO_soundMoveStart]) },
  167. { "SoundMoveStartDamaged",INI::parseDynamicAudioEventRTS, NULL, offsetof( ThingTemplate, m_audioarray.m_audio[TTAUDIO_soundMoveStartDamaged]) },
  168. { "SoundMoveLoop", INI::parseDynamicAudioEventRTS, NULL, offsetof( ThingTemplate, m_audioarray.m_audio[TTAUDIO_soundMoveLoop]) },
  169. { "SoundMoveLoopDamaged", INI::parseDynamicAudioEventRTS, NULL, offsetof( ThingTemplate, m_audioarray.m_audio[TTAUDIO_soundMoveLoopDamaged]) },
  170. { "SoundDie", INI::parseDynamicAudioEventRTS, NULL, offsetof( ThingTemplate, m_audioarray.m_audio[TTAUDIO_soundDie]) },
  171. { "SoundCrush", INI::parseDynamicAudioEventRTS, NULL, offsetof( ThingTemplate, m_audioarray.m_audio[TTAUDIO_soundCrush ]) },
  172. { "SoundAmbient", INI::parseDynamicAudioEventRTS, NULL, offsetof( ThingTemplate, m_audioarray.m_audio[TTAUDIO_soundAmbient ]) },
  173. { "SoundAmbientDamaged", INI::parseDynamicAudioEventRTS, NULL, offsetof( ThingTemplate, m_audioarray.m_audio[TTAUDIO_soundAmbientDamaged ]) },
  174. { "SoundAmbientReallyDamaged",INI::parseDynamicAudioEventRTS, NULL,offsetof( ThingTemplate, m_audioarray.m_audio[TTAUDIO_soundAmbientReallyDamaged ]) },
  175. { "SoundAmbientRubble", INI::parseDynamicAudioEventRTS, NULL, offsetof( ThingTemplate, m_audioarray.m_audio[TTAUDIO_soundAmbientRubble]) },
  176. { "SoundStealthOn", INI::parseDynamicAudioEventRTS, NULL, offsetof( ThingTemplate, m_audioarray.m_audio[TTAUDIO_soundStealthOn ]) },
  177. { "SoundStealthOff", INI::parseDynamicAudioEventRTS, NULL, offsetof( ThingTemplate, m_audioarray.m_audio[TTAUDIO_soundStealthOff ]) },
  178. { "SoundCreated", INI::parseDynamicAudioEventRTS, NULL, offsetof( ThingTemplate, m_audioarray.m_audio[TTAUDIO_soundCreated ]) },
  179. { "SoundOnDamaged", INI::parseDynamicAudioEventRTS, NULL, offsetof( ThingTemplate, m_audioarray.m_audio[TTAUDIO_soundOnDamaged ]) },
  180. { "SoundOnReallyDamaged", INI::parseDynamicAudioEventRTS, NULL, offsetof( ThingTemplate, m_audioarray.m_audio[TTAUDIO_soundOnReallyDamaged ]) },
  181. { "SoundDieFire", INI::parseDynamicAudioEventRTS, NULL, offsetof( ThingTemplate, m_audioarray.m_audio[TTAUDIO_soundDieFire ]) },
  182. { "SoundDieToxin", INI::parseDynamicAudioEventRTS, NULL, offsetof( ThingTemplate, m_audioarray.m_audio[TTAUDIO_soundDieToxin ]) },
  183. { "SoundEnter", INI::parseDynamicAudioEventRTS, NULL, offsetof( ThingTemplate, m_audioarray.m_audio[TTAUDIO_soundEnter ]) },
  184. { "SoundExit", INI::parseDynamicAudioEventRTS, NULL, offsetof( ThingTemplate, m_audioarray.m_audio[TTAUDIO_soundExit ]) },
  185. { "SoundPromotedVeteran", INI::parseDynamicAudioEventRTS, NULL, offsetof( ThingTemplate, m_audioarray.m_audio[TTAUDIO_soundPromotedVeteran ]) },
  186. { "SoundPromotedElite", INI::parseDynamicAudioEventRTS, NULL, offsetof( ThingTemplate, m_audioarray.m_audio[TTAUDIO_soundPromotedElite ]) },
  187. { "SoundPromotedHero", INI::parseDynamicAudioEventRTS, NULL, offsetof( ThingTemplate, m_audioarray.m_audio[TTAUDIO_soundPromotedHero ]) },
  188. { "SoundFallingFromPlane",INI::parseDynamicAudioEventRTS, NULL, offsetof( ThingTemplate, m_audioarray.m_audio[TTAUDIO_soundFalling ]) },
  189. { "UnitSpecificSounds", ThingTemplate::parsePerUnitSounds, NULL, offsetof(ThingTemplate, m_perUnitSounds) },
  190. { "UnitSpecificFX", ThingTemplate::parsePerUnitFX, NULL, offsetof(ThingTemplate, m_perUnitFX) },
  191. { "Scale", INI::parseReal, NULL, offsetof( ThingTemplate, m_assetScale ) },
  192. { "Geometry", GeometryInfo::parseGeometryType, NULL, offsetof( ThingTemplate, m_geometryInfo ) },
  193. { "GeometryMajorRadius", GeometryInfo::parseGeometryMajorRadius, NULL, offsetof( ThingTemplate, m_geometryInfo ) },
  194. { "GeometryMinorRadius", GeometryInfo::parseGeometryMinorRadius, NULL, offsetof( ThingTemplate, m_geometryInfo ) },
  195. { "GeometryHeight", GeometryInfo::parseGeometryHeight, NULL, offsetof( ThingTemplate, m_geometryInfo ) },
  196. { "GeometryIsSmall", GeometryInfo::parseGeometryIsSmall, NULL, offsetof( ThingTemplate, m_geometryInfo ) },
  197. { "Shadow", INI::parseBitString8, TheShadowNames, offsetof( ThingTemplate, m_shadowType ) },
  198. { "ShadowSizeX", INI::parseReal, NULL, offsetof( ThingTemplate, m_shadowSizeX ) },
  199. { "ShadowSizeY", INI::parseReal, NULL, offsetof( ThingTemplate, m_shadowSizeY ) },
  200. { "ShadowOffsetX", INI::parseReal, NULL, offsetof( ThingTemplate, m_shadowOffsetX ) },
  201. { "ShadowOffsetY", INI::parseReal, NULL, offsetof( ThingTemplate, m_shadowOffsetY ) },
  202. { "ShadowTexture", INI::parseAsciiString, NULL, offsetof( ThingTemplate, m_shadowTextureName ) },
  203. { "OcclusionDelay", INI::parseDurationUnsignedInt, NULL, offsetof( ThingTemplate, m_occlusionDelay ) },
  204. { "AddModule", ThingTemplate::parseAddModule, NULL, 0 },
  205. { "RemoveModule", ThingTemplate::parseRemoveModule, NULL, 0 },
  206. { "ReplaceModule", ThingTemplate::parseReplaceModule, NULL, 0 },
  207. { "InheritableModule", ThingTemplate::parseInheritableModule, NULL, 0 },
  208. { "Locomotor", AIUpdateModuleData::parseLocomotorSet, NULL, 0 },
  209. { "InstanceScaleFuzziness", INI::parseReal, NULL, offsetof(ThingTemplate, m_instanceScaleFuzziness ) },
  210. { "StructureRubbleHeight", INI::parseUnsignedByte, NULL, offsetof(ThingTemplate, m_structureRubbleHeight ) },
  211. { "ThreatValue", INI::parseUnsignedShort, NULL, offsetof(ThingTemplate, m_threatValue ) },
  212. { "MaxSimultaneousOfType", INI::parseUnsignedShort, NULL, offsetof(ThingTemplate, m_maxSimultaneousOfType ) },
  213. { "CrusherLevel", INI::parseUnsignedByte, NULL, offsetof( ThingTemplate, m_crusherLevel ) },
  214. { "CrushableLevel", INI::parseUnsignedByte, NULL, offsetof( ThingTemplate, m_crushableLevel ) },
  215. { 0, 0, 0, 0 } // keep this last
  216. };
  217. // NOTE NOTE NOTE -- s_objectFieldParseTable and s_objectReskinFieldParseTable must be updated in tandem -- see comment above
  218. // NOTE NOTE NOTE -- s_objectFieldParseTable and s_objectReskinFieldParseTable must be updated in tandem -- see comment above
  219. const FieldParse ThingTemplate::s_objectReskinFieldParseTable[] =
  220. {
  221. { "Draw", ThingTemplate::parseModuleName, (const void*)MODULETYPE_DRAW, offsetof(ThingTemplate, m_drawModuleInfo) },
  222. { "Geometry", GeometryInfo::parseGeometryType, NULL, offsetof( ThingTemplate, m_geometryInfo ) },
  223. { "GeometryMajorRadius", GeometryInfo::parseGeometryMajorRadius, NULL, offsetof( ThingTemplate, m_geometryInfo ) },
  224. { "GeometryMinorRadius", GeometryInfo::parseGeometryMinorRadius, NULL, offsetof( ThingTemplate, m_geometryInfo ) },
  225. { "GeometryHeight", GeometryInfo::parseGeometryHeight, NULL, offsetof( ThingTemplate, m_geometryInfo ) },
  226. { "GeometryIsSmall", GeometryInfo::parseGeometryIsSmall, NULL, offsetof( ThingTemplate, m_geometryInfo ) },
  227. { "FenceWidth", INI::parseReal, NULL, offsetof( ThingTemplate, m_fenceWidth ) },
  228. { "FenceXOffset", INI::parseReal, NULL, offsetof( ThingTemplate, m_fenceXOffset ) },
  229. { 0, 0, 0, 0 } // keep this last
  230. };
  231. // NOTE NOTE NOTE -- s_objectFieldParseTable and s_objectReskinFieldParseTable must be updated in tandem -- see comment above
  232. // ------------------------------------------------------------------------------------------------
  233. /** See if the tag string is present in any of the module info entries here */
  234. // ------------------------------------------------------------------------------------------------
  235. const ModuleInfo::Nugget *ModuleInfo::getNuggetWithTag( const AsciiString& tag ) const
  236. {
  237. std::vector< Nugget >::const_iterator it;
  238. for( it = m_info.begin(); it != m_info.end(); ++it )
  239. if( (*it).m_moduleTag == tag )
  240. return &(*it);
  241. // no match
  242. return NULL;
  243. } // end isTagPresent
  244. // ------------------------------------------------------------------------------------------------
  245. /** Add this module info to the thing template */
  246. // ------------------------------------------------------------------------------------------------
  247. void ModuleInfo::addModuleInfo(ThingTemplate *thingTemplate,
  248. const AsciiString& name,
  249. const AsciiString& moduleTag,
  250. const ModuleData* data,
  251. Int interfaceMask,
  252. Bool inheritable)
  253. {
  254. //
  255. // there must be a module tag present, and it must be unique across all module infos
  256. // for this thing template
  257. //
  258. #if defined(_DEBUG) || defined(_INTERNAL)
  259. // get module info
  260. const Nugget *nugget;
  261. nugget = thingTemplate->getBehaviorModuleInfo().getNuggetWithTag( moduleTag );
  262. if( nugget != NULL )
  263. {
  264. // compare this nugget tag against the tag for the new data we're going to submit
  265. DEBUG_ASSERTCRASH( nugget->m_moduleTag != moduleTag,
  266. ("addModuleInfo - ERROR defining module '%s' on thing template '%s'. The module '%s' has the tag '%s' which must be unique among all modules for this object, but the tag '%s' is also already on module '%s' within this object.\n\nPlease make unique tag names within an object definition\n",
  267. name.str(),
  268. thingTemplate->getName().str(),
  269. name.str(),
  270. moduleTag.str(),
  271. moduleTag.str(),
  272. nugget->first.str()) );
  273. // srj sez: prevent people from ignoring this.
  274. throw INI_INVALID_DATA;
  275. } // end if
  276. nugget = thingTemplate->getDrawModuleInfo().getNuggetWithTag( moduleTag );
  277. if( nugget != NULL )
  278. {
  279. // compare this nugget tag against the tag for the new data we're going to submit
  280. DEBUG_ASSERTCRASH( nugget->m_moduleTag != moduleTag,
  281. ("addModuleInfo - ERROR defining module '%s' on thing template '%s'. The module '%s' has the tag '%s' which must be unique among all modules for this object, but the tag '%s' is also already on module '%s' within this object.\n\nPlease make unique tag names within an object definition\n",
  282. name.str(),
  283. thingTemplate->getName().str(),
  284. name.str(),
  285. moduleTag.str(),
  286. moduleTag.str(),
  287. nugget->first.str()) );
  288. // srj sez: prevent people from ignoring this.
  289. throw INI_INVALID_DATA;
  290. } // end if
  291. nugget = thingTemplate->getClientUpdateModuleInfo().getNuggetWithTag( moduleTag );
  292. if( nugget != NULL )
  293. {
  294. // compare this nugget tag against the tag for the new data we're going to submit
  295. DEBUG_ASSERTCRASH( nugget->m_moduleTag != moduleTag,
  296. ("addModuleInfo - ERROR defining module '%s' on thing template '%s'. The module '%s' has the tag '%s' which must be unique among all modules for this object, but the tag '%s' is also already on module '%s' within this object.\n\nPlease make unique tag names within an object definition\n",
  297. name.str(),
  298. thingTemplate->getName().str(),
  299. name.str(),
  300. moduleTag.str(),
  301. moduleTag.str(),
  302. nugget->first.str()) );
  303. // srj sez: prevent people from ignoring this.
  304. throw INI_INVALID_DATA;
  305. } // end if
  306. #endif
  307. m_info.push_back(Nugget(name, moduleTag, data, interfaceMask, inheritable));
  308. }
  309. //-------------------------------------------------------------------------------------------------
  310. Bool ModuleInfo::clearModuleDataWithTag(const AsciiString& tagToClear, AsciiString& clearedModuleNameOut)
  311. {
  312. Bool cleared = false;
  313. // do NOT clear... we only want to modify this if we return true.
  314. // if we return false, we should leave this unmodified.
  315. //clearedModuleNameOut.clear();
  316. for (std::vector<Nugget>::iterator it = m_info.begin(); it != m_info.end(); /* empty */ )
  317. {
  318. if (it->m_moduleTag == tagToClear)
  319. {
  320. DEBUG_ASSERTCRASH(!cleared, ("Hmm, multiple clears in ModuleInfo::clearModuleDataWithTag, should this be possible?"));
  321. clearedModuleNameOut = it->first;
  322. it = m_info.erase(it);
  323. cleared = true;
  324. }
  325. else
  326. {
  327. ++it;
  328. }
  329. }
  330. return cleared;
  331. }
  332. //-------------------------------------------------------------------------------------------------
  333. Bool ModuleInfo::clearCopiedFromDefaultEntries(Int interfaceMask)
  334. {
  335. Bool ret = false;
  336. std::vector<Nugget>::iterator it = m_info.begin();
  337. while( it != m_info.end() )
  338. {
  339. if( (it->interfaceMask & interfaceMask) != 0 && it->copiedFromDefault && !it->inheritable )
  340. {
  341. it = m_info.erase( it );
  342. ret = true;
  343. }
  344. else
  345. {
  346. ++it;
  347. }
  348. }
  349. return ret;
  350. }
  351. //-------------------------------------------------------------------------------------------------
  352. Bool ModuleInfo::clearAiModuleInfo()
  353. {
  354. Bool ret = false;
  355. std::vector<Nugget>::iterator it = m_info.begin();
  356. while( it != m_info.end() )
  357. {
  358. if (it->second->isAiModuleData() )
  359. {
  360. it = m_info.erase( it );
  361. ret = true;
  362. }
  363. else
  364. {
  365. ++it;
  366. }
  367. }
  368. return ret;
  369. }
  370. //-------------------------------------------------------------------------------------------------
  371. void ThingTemplate::parseModuleName(INI* ini, void *instance, void* store, const void* userData)
  372. {
  373. ThingTemplate* self = (ThingTemplate*)instance;
  374. ModuleInfo* mi = (ModuleInfo*)store;
  375. ModuleType type = (ModuleType)(UnsignedInt)userData;
  376. const char* token = ini->getNextToken();
  377. AsciiString tokenStr = token;
  378. // get the tag string (it is now required)
  379. AsciiString moduleTagStr;
  380. try
  381. {
  382. moduleTagStr = ini->getNextToken();
  383. }
  384. catch( ... )
  385. {
  386. DEBUG_CRASH(( "[LINE: %d - FILE: '%s'] Module tag not found for module '%s' on thing template '%s'. Module tags are required and must be unique for all modules within an object definition\n",
  387. ini->getLineNum(), ini->getFilename().str(),
  388. tokenStr.str(), self->getName().str() ));
  389. throw;
  390. }
  391. Int interfaceMask;
  392. // ugh -- special case for "Body".
  393. if (type == 999)
  394. {
  395. type = MODULETYPE_BEHAVIOR;
  396. // what interface(s) does this module support?
  397. interfaceMask = TheModuleFactory->findModuleInterfaceMask(tokenStr, type);
  398. if ((interfaceMask & (MODULEINTERFACE_BODY)) == 0)
  399. {
  400. DEBUG_CRASH(("Only Body allowed here"));
  401. throw INI_INVALID_DATA;
  402. }
  403. }
  404. else
  405. {
  406. interfaceMask = TheModuleFactory->findModuleInterfaceMask(tokenStr, type);
  407. if ((interfaceMask & (MODULEINTERFACE_BODY)) != 0)
  408. {
  409. DEBUG_CRASH(("No Body allowed here"));
  410. throw INI_INVALID_DATA;
  411. }
  412. }
  413. // if we're overriding, we can totally skip over this block
  414. if (ini->getLoadType() == INI_LOAD_CREATE_OVERRIDES)
  415. {
  416. if (self->m_moduleParsingMode == MODULEPARSE_ADD_REMOVE_REPLACE)
  417. {
  418. // do nothing, just fall thru
  419. }
  420. else
  421. {
  422. DEBUG_CRASH(("[LINE: %d - FILE: '%s'] You must use AddModule to add modules in override INI files.\n",
  423. ini->getLineNum(), ini->getFilename().str(), self->getName().str()));
  424. throw INI_INVALID_DATA;
  425. }
  426. }
  427. else
  428. {
  429. self->m_behaviorModuleInfo.clearCopiedFromDefaultEntries(interfaceMask);
  430. self->m_drawModuleInfo.clearCopiedFromDefaultEntries(interfaceMask);
  431. self->m_clientUpdateModuleInfo.clearCopiedFromDefaultEntries(interfaceMask);
  432. }
  433. if (self->m_moduleParsingMode == MODULEPARSE_ADD_REMOVE_REPLACE
  434. && self->m_moduleBeingReplacedName.isNotEmpty()
  435. && self->m_moduleBeingReplacedName != tokenStr)
  436. {
  437. DEBUG_CRASH(("[LINE: %d - FILE: '%s'] ReplaceModule must replace modules with another module of the same type, but you are attempting to replace a %s with a %s for Object %s.\n",
  438. ini->getLineNum(), ini->getFilename().str(), self->m_moduleBeingReplacedName.str(), tokenStr.str(), self->getName().str()));
  439. throw INI_INVALID_DATA;
  440. }
  441. if (self->m_moduleParsingMode == MODULEPARSE_ADD_REMOVE_REPLACE
  442. && self->m_moduleBeingReplacedTag.isNotEmpty()
  443. && self->m_moduleBeingReplacedTag == moduleTagStr)
  444. {
  445. DEBUG_CRASH(("[LINE: %d - FILE: '%s'] ReplaceModule must specify a new, unique tag for the replaced module, but you are not doing so for %s (%s) for Object %s.\n",
  446. ini->getLineNum(), ini->getFilename().str(), moduleTagStr.str(), self->m_moduleBeingReplacedName.str(), self->getName().str()));
  447. throw INI_INVALID_DATA;
  448. }
  449. ModuleData* data = TheModuleFactory->newModuleDataFromINI(ini, tokenStr, type, moduleTagStr);
  450. if (data->isAiModuleData())
  451. {
  452. Bool replaced = mi->clearAiModuleInfo();
  453. if (replaced)
  454. {
  455. DEBUG_LOG(("replaced an AI for %s!\n",self->getName().str()));
  456. }
  457. }
  458. Bool inheritable = (self->m_moduleParsingMode == MODULEPARSE_INHERITABLE);
  459. mi->addModuleInfo(self, tokenStr, moduleTagStr, data, interfaceMask, inheritable);
  460. }
  461. //-------------------------------------------------------------------------------------------------
  462. void ThingTemplate::parseIntList(INI* ini, void *instance, void* store, const void* userData)
  463. {
  464. Int numberEntries = (Int)userData;
  465. Int *intList = (Int*)store;
  466. for( Int intIndex = 0; intIndex < numberEntries; intIndex ++ )
  467. {
  468. const char *token = ini->getNextToken();
  469. intList[intIndex] = ini->scanInt(token);
  470. }
  471. }
  472. //-------------------------------------------------------------------------------------------------
  473. static void parsePrerequisiteUnit( INI* ini, void *instance, void * /*store*/, const void* /*userData*/ )
  474. {
  475. std::vector<ProductionPrerequisite>* v = (std::vector<ProductionPrerequisite>*)instance;
  476. ProductionPrerequisite prereq;
  477. Bool orUnitWithPrevious = FALSE;
  478. for (const char *token = ini->getNextToken(); token != NULL; token = ini->getNextTokenOrNull())
  479. {
  480. prereq.addUnitPrereq( AsciiString( token ), orUnitWithPrevious );
  481. orUnitWithPrevious = TRUE;
  482. }
  483. v->push_back(prereq);
  484. }
  485. //-------------------------------------------------------------------------------------------------
  486. static void parsePrerequisiteScience( INI* ini, void *instance, void * /*store*/, const void* /*userData*/ )
  487. {
  488. std::vector<ProductionPrerequisite>* v = (std::vector<ProductionPrerequisite>*)instance;
  489. ProductionPrerequisite prereq;
  490. prereq.addSciencePrereq(INI::scanScience(ini->getNextToken()));
  491. v->push_back(prereq);
  492. }
  493. //-------------------------------------------------------------------------------------------------
  494. void ThingTemplate::parsePrerequisites( INI* ini, void *instance, void *store, const void* userData )
  495. {
  496. ThingTemplate* self = (ThingTemplate*)instance;
  497. static const FieldParse myFieldParse[] =
  498. {
  499. { "Object", parsePrerequisiteUnit, 0, 0 },
  500. { "Science", parsePrerequisiteScience, 0, 0 },
  501. { 0, 0, 0, 0 }
  502. };
  503. if (ini->getLoadType() == INI_LOAD_CREATE_OVERRIDES)
  504. {
  505. self->m_prereqInfo.clear();
  506. }
  507. ini->initFromINI(&self->m_prereqInfo, myFieldParse);
  508. }
  509. //-------------------------------------------------------------------------------------------Static
  510. static void parseArbitraryFXIntoMap( INI* ini, void *instance, void* /* store */, const void* userData )
  511. {
  512. PerUnitFXMap* mapFX = (PerUnitFXMap*)instance;
  513. const char* name = (const char*)userData;
  514. const char* token = ini->getNextToken();
  515. const FXList* fxl = TheFXListStore->findFXList(token); // could be null!
  516. DEBUG_ASSERTCRASH(fxl != NULL || stricmp(token, "None") == 0, ("FXList %s not found!\n",token));
  517. mapFX->insert(std::make_pair(AsciiString(name), fxl));
  518. }
  519. //-------------------------------------------------------------------------------------------------
  520. //-------------------------------------------------------------------------------------------------
  521. void ThingTemplate::parsePerUnitFX( INI* ini, void *instance, void *store, const void *userData )
  522. {
  523. PerUnitFXMap* fxmap = (PerUnitFXMap*)store;
  524. fxmap->clear();
  525. static const FieldParse myFieldParse[] =
  526. {
  527. { 0, parseArbitraryFXIntoMap, NULL, 0 },
  528. { 0, 0, 0, 0 }
  529. };
  530. ini->initFromINI(fxmap, myFieldParse);
  531. }
  532. //-------------------------------------------------------------------------------------------Static
  533. static void parseArbitrarySoundsIntoMap( INI* ini, void *instance, void* /* store */, const void* userData )
  534. {
  535. PerUnitSoundMap *mapSounds = (PerUnitSoundMap*) instance;
  536. const char* name = (const char*)userData;
  537. const char* token = ini->getNextToken();
  538. AudioEventRTS a;
  539. if (token)
  540. a.setEventName(token);
  541. mapSounds->insert(std::make_pair(AsciiString(name), a));
  542. }
  543. //-------------------------------------------------------------------------------------------------
  544. /** Parse Additional per unit sounds such as TankTurretMove and TankTurretMoveLoop. */
  545. //-------------------------------------------------------------------------------------------------
  546. void ThingTemplate::parsePerUnitSounds( INI* ini, void *instance, void *store, const void *userData )
  547. {
  548. PerUnitSoundMap *mapSounds = (PerUnitSoundMap*)store;
  549. mapSounds->clear();
  550. static const FieldParse myFieldParse[] =
  551. {
  552. { 0, parseArbitrarySoundsIntoMap, NULL, 0 },
  553. { 0, 0, 0, 0 }
  554. };
  555. ini->initFromINI(mapSounds, myFieldParse);
  556. }
  557. //-------------------------------------------------------------------------------------------------
  558. /** Parse modules to add to the existing set of modules. */
  559. //-------------------------------------------------------------------------------------------------
  560. void ThingTemplate::parseAddModule(INI *ini, void *instance, void *store, const void *userData)
  561. {
  562. // don't care about the result.
  563. ThingTemplate* self = (ThingTemplate*)instance;
  564. ModuleParseMode oldMode = (ModuleParseMode)self->m_moduleParsingMode;
  565. if (oldMode != MODULEPARSE_NORMAL)
  566. throw INI_INVALID_DATA;
  567. self->m_moduleParsingMode = MODULEPARSE_ADD_REMOVE_REPLACE;
  568. ini->initFromINI(self, self->getFieldParse());
  569. self->m_moduleParsingMode = oldMode;
  570. }
  571. //-------------------------------------------------------------------------------------------------
  572. /** Parse modules to remove from the existing set of modules. */
  573. //-------------------------------------------------------------------------------------------------
  574. void ThingTemplate::parseRemoveModule(INI *ini, void *instance, void *store, const void *userData)
  575. {
  576. ThingTemplate* self = (ThingTemplate*)instance;
  577. ModuleParseMode oldMode = (ModuleParseMode)self->m_moduleParsingMode;
  578. if (oldMode != MODULEPARSE_NORMAL)
  579. throw INI_INVALID_DATA;
  580. self->m_moduleParsingMode = MODULEPARSE_ADD_REMOVE_REPLACE;
  581. const char *modToRemove = ini->getNextToken();
  582. AsciiString removedModuleName;
  583. Bool removed = self->removeModuleInfo(modToRemove, removedModuleName);
  584. if (!removed)
  585. {
  586. DEBUG_ASSERTCRASH(removed, ("RemoveModule %s was not found for %s.\n",modToRemove, self->getName().str()));
  587. throw INI_INVALID_DATA;
  588. }
  589. self->m_moduleParsingMode = oldMode;
  590. }
  591. //-------------------------------------------------------------------------------------------------
  592. /** Replace the existing tagged modules with the new modules. */
  593. //-------------------------------------------------------------------------------------------------
  594. void ThingTemplate::parseReplaceModule(INI *ini, void *instance, void *store, const void *userData)
  595. {
  596. ThingTemplate* self = (ThingTemplate*)instance;
  597. ModuleParseMode oldMode = (ModuleParseMode)self->m_moduleParsingMode;
  598. if (oldMode != MODULEPARSE_NORMAL)
  599. throw INI_INVALID_DATA;
  600. self->m_moduleParsingMode = MODULEPARSE_ADD_REMOVE_REPLACE;
  601. const char *modToRemove = ini->getNextToken();
  602. AsciiString removedModuleName;
  603. Bool removed = self->removeModuleInfo(modToRemove, removedModuleName);
  604. if (!removed)
  605. {
  606. DEBUG_CRASH(("[LINE: %d - FILE: '%s'] ReplaceModule %s was not found for %s; cannot continue.\n",
  607. ini->getLineNum(), ini->getFilename().str(), modToRemove, self->getName().str()));
  608. throw INI_INVALID_DATA;
  609. }
  610. self->m_moduleBeingReplacedName = removedModuleName;
  611. self->m_moduleBeingReplacedTag = modToRemove;
  612. ini->initFromINI(self, self->getFieldParse());
  613. self->m_moduleBeingReplacedName.clear();
  614. self->m_moduleBeingReplacedTag.clear();
  615. self->m_moduleParsingMode = oldMode;
  616. }
  617. //-------------------------------------------------------------------------------------------------
  618. /** mark the module(s) as being "Inheritable". */
  619. //-------------------------------------------------------------------------------------------------
  620. void ThingTemplate::parseInheritableModule(INI *ini, void *instance, void *store, const void *userData)
  621. {
  622. ThingTemplate* self = (ThingTemplate*)instance;
  623. ModuleParseMode oldMode = (ModuleParseMode)self->m_moduleParsingMode;
  624. if (oldMode != MODULEPARSE_NORMAL)
  625. throw INI_INVALID_DATA;
  626. self->m_moduleParsingMode = MODULEPARSE_INHERITABLE;
  627. ini->initFromINI(self, self->getFieldParse());
  628. self->m_moduleParsingMode = oldMode;
  629. }
  630. //-------------------------------------------------------------------------------------------------
  631. /** Remove the module whose tag matches moduleToRemove. */
  632. //-------------------------------------------------------------------------------------------------
  633. Bool ThingTemplate::removeModuleInfo(const AsciiString& moduleToRemove, AsciiString& clearedModuleNameOut)
  634. {
  635. Bool removed = false;
  636. // do NOT clear... we only want to modify this if we return true.
  637. // if we return false, we should leave this unmodified.
  638. //clearedModuleNameOut.clear();
  639. if (m_behaviorModuleInfo.clearModuleDataWithTag(moduleToRemove, clearedModuleNameOut))
  640. {
  641. DEBUG_ASSERTCRASH(!removed, ("Hmm, multiple removed in ThingTemplate::removeModuleInfo, should this be possible?"));
  642. removed = true;
  643. }
  644. if (m_drawModuleInfo.clearModuleDataWithTag(moduleToRemove, clearedModuleNameOut))
  645. {
  646. DEBUG_ASSERTCRASH(!removed, ("Hmm, multiple removed in ThingTemplate::removeModuleInfo, should this be possible?"));
  647. removed = true;
  648. }
  649. if (m_clientUpdateModuleInfo.clearModuleDataWithTag(moduleToRemove, clearedModuleNameOut))
  650. {
  651. DEBUG_ASSERTCRASH(!removed, ("Hmm, multiple removed in ThingTemplate::removeModuleInfo, should this be possible?"));
  652. removed = true;
  653. }
  654. return removed;
  655. }
  656. //-------------------------------------------------------------------------------------------------
  657. /// @todo srj -- move this to another file
  658. void ArmorTemplateSet::parseArmorTemplateSet( INI* ini )
  659. {
  660. static const FieldParse myFieldParse[] =
  661. {
  662. { "Conditions", ArmorSetFlags::parseFromINI, NULL, offsetof( ArmorTemplateSet, m_types ) },
  663. { "Armor", INI::parseArmorTemplate, NULL, offsetof( ArmorTemplateSet, m_template ) },
  664. { "DamageFX", INI::parseDamageFX, NULL, offsetof( ArmorTemplateSet, m_fx ) },
  665. { 0, 0, 0, 0 }
  666. };
  667. ini->initFromINI(this, myFieldParse);
  668. }
  669. //-------------------------------------------------------------------------------------------------
  670. void ThingTemplate::parseArmorTemplateSet( INI* ini, void *instance, void * /*store*/, const void* /*userData*/ )
  671. {
  672. ThingTemplate* self = (ThingTemplate*)instance;
  673. if (self->m_armorCopiedFromDefault == TRUE)
  674. {
  675. self->m_armorCopiedFromDefault = FALSE;
  676. self->m_armorTemplateSets.clear();
  677. }
  678. ArmorTemplateSet ws;
  679. ws.parseArmorTemplateSet(ini);
  680. #if defined(_DEBUG) || defined(_INTERNAL)
  681. if (ini->getLoadType() != INI_LOAD_CREATE_OVERRIDES)
  682. {
  683. for (ArmorTemplateSetVector::const_iterator it = self->m_armorTemplateSets.begin(); it != self->m_armorTemplateSets.end(); ++it)
  684. {
  685. if (it->getNthConditionsYes(0) == ws.getNthConditionsYes(0))
  686. {
  687. DEBUG_CRASH(("dup armorset condition in %s\n",self->getName().str()));
  688. }
  689. }
  690. }
  691. #endif
  692. self->m_armorTemplateSets.push_back(ws);
  693. self->m_armorTemplateSetFinder.clear();
  694. }
  695. //-------------------------------------------------------------------------------------------------
  696. void ThingTemplate::parseWeaponTemplateSet( INI* ini, void *instance, void * /*store*/, const void* /*userData*/ )
  697. {
  698. ThingTemplate* self = (ThingTemplate*)instance;
  699. if (self->m_weaponsCopiedFromDefault == TRUE)
  700. {
  701. self->m_weaponsCopiedFromDefault = FALSE;
  702. self->m_weaponTemplateSets.clear();
  703. }
  704. WeaponTemplateSet ws;
  705. ws.parseWeaponTemplateSet(ini, self);
  706. #if defined(_DEBUG) || defined(_INTERNAL)
  707. if (ini->getLoadType() != INI_LOAD_CREATE_OVERRIDES)
  708. {
  709. for (WeaponTemplateSetVector::const_iterator it = self->m_weaponTemplateSets.begin(); it != self->m_weaponTemplateSets.end(); ++it)
  710. {
  711. if (it->getNthConditionsYes(0) == ws.getNthConditionsYes(0))
  712. {
  713. DEBUG_CRASH(("dup weaponset condition in %s\n",self->getName().str()));
  714. }
  715. }
  716. }
  717. #endif
  718. self->m_weaponTemplateSets.push_back(ws);
  719. self->m_weaponTemplateSetFinder.clear();
  720. }
  721. //-------------------------------------------------------------------------------------------------
  722. //-------------------------------------------------------------------------------------------------
  723. ThingTemplate::ThingTemplate() :
  724. m_geometryInfo(GEOMETRY_SPHERE, FALSE, 1, 1, 1)
  725. {
  726. m_moduleParsingMode = MODULEPARSE_NORMAL;
  727. m_reskinnedFrom = NULL;
  728. m_radarPriority = RADAR_PRIORITY_INVALID;
  729. m_nextThingTemplate = NULL;
  730. m_transportSlotCount = 0;
  731. m_fenceWidth = 0;
  732. m_fenceXOffset = 0;
  733. m_visionRange = 0.0f;
  734. m_shroudClearingRange = -1.0f;
  735. m_buildCost = 0;
  736. m_buildTime = 1;
  737. m_refundValue = 0;
  738. m_energyProduction = 0;
  739. m_energyBonus = 0;
  740. m_buildCompletion = BC_APPEARS_AT_RALLY_POINT;
  741. for( Int levelIndex = 0; levelIndex < LEVEL_COUNT; levelIndex++ )
  742. {
  743. m_experienceValues[levelIndex] = 0;
  744. m_experienceRequired[levelIndex] = 0;
  745. // -1 means "same value as experienceValues for that level"
  746. m_skillPointValues[levelIndex] = USE_EXP_VALUE_FOR_SKILL_VALUE;
  747. }
  748. m_isTrainable = FALSE;
  749. m_templateID = 0;
  750. m_kindof = KINDOFMASK_NONE;
  751. //m_defaultOwningSide = ""; // unnecessary
  752. m_isBuildFacility = FALSE;
  753. m_isPrerequisite = FALSE;
  754. m_placementViewAngle = 0.0f;
  755. m_factoryExitWidth = 0.0f;
  756. m_factoryExtraBibWidth = 0.0f;
  757. m_selectedPortraitImage = NULL;
  758. m_buttonImage = NULL;
  759. m_shadowType = SHADOW_NONE;
  760. m_shadowSizeX = 0.0f;
  761. m_shadowSizeY = 0.0f;
  762. m_shadowOffsetX = 0.0f;
  763. m_shadowOffsetY = 0.0f;
  764. m_occlusionDelay = TheGlobalData->m_defaultOcclusionDelay;
  765. m_structureRubbleHeight = 0;
  766. m_instanceScaleFuzziness = 0;
  767. m_threatValue = 0;
  768. m_maxSimultaneousOfType = 0; // unlimited
  769. m_crusherLevel = 0; //Unspecified, this object is unable to crush anything!
  770. m_crushableLevel = 255; //Unspecified, this object is unable to be crushed by anything!
  771. }
  772. //-------------------------------------------------------------------------------------------------
  773. AIUpdateModuleData *ThingTemplate::friend_getAIModuleInfo(void)
  774. {
  775. Int numModInfos = m_behaviorModuleInfo.getCount();
  776. for (int j = 0; j < numModInfos; ++j)
  777. {
  778. if (m_behaviorModuleInfo.getNthData(j) && m_behaviorModuleInfo.getNthData(j)->isAiModuleData())
  779. {
  780. return (AIUpdateModuleData *)m_behaviorModuleInfo.friend_getNthData(j);
  781. }
  782. }
  783. return NULL;
  784. }
  785. //-------------------------------------------------------------------------------------------------
  786. //-------------------------------------------------------------------------------------------------
  787. void ThingTemplate::validateAudio()
  788. {
  789. #if defined(_DEBUG) || defined(_INTERNAL)
  790. #define AUDIO_TEST(y) \
  791. if (!get##y()->getEventName().isEmpty() && get##y()->getEventName().compareNoCase("NoSound") != 0) { \
  792. DEBUG_ASSERTLOG(TheAudio->isValidAudioEvent(get##y()), ("Invalid Sound '%s' in Object '%s'. (%s?)\n", #y, getName().str(), get##y()->getEventName().str())); \
  793. }
  794. AUDIO_TEST(VoiceSelect)
  795. AUDIO_TEST(VoiceGroupSelect)
  796. AUDIO_TEST(VoiceMove)
  797. AUDIO_TEST(VoiceAttack)
  798. AUDIO_TEST(VoiceEnter)
  799. AUDIO_TEST(VoiceFear)
  800. AUDIO_TEST(VoiceSelectElite)
  801. AUDIO_TEST(VoiceCreated)
  802. AUDIO_TEST(VoiceNearEnemy)
  803. AUDIO_TEST(VoiceTaskUnable)
  804. AUDIO_TEST(VoiceTaskComplete)
  805. AUDIO_TEST(VoiceMeetEnemy)
  806. AUDIO_TEST(VoiceGarrison)
  807. #ifdef ALLOW_SURRENDER
  808. AUDIO_TEST(VoiceSurrender)
  809. #endif
  810. AUDIO_TEST(VoiceDefect)
  811. AUDIO_TEST(VoiceAttackSpecial)
  812. AUDIO_TEST(VoiceAttackAir)
  813. AUDIO_TEST(VoiceGuard)
  814. AUDIO_TEST(SoundMoveStart)
  815. AUDIO_TEST(SoundMoveStartDamaged)
  816. AUDIO_TEST(SoundMoveLoop)
  817. AUDIO_TEST(SoundMoveLoopDamaged)
  818. AUDIO_TEST(SoundDie)
  819. AUDIO_TEST(SoundCrush)
  820. AUDIO_TEST(SoundAmbient)
  821. AUDIO_TEST(SoundAmbientDamaged)
  822. AUDIO_TEST(SoundAmbientReallyDamaged)
  823. AUDIO_TEST(SoundAmbientRubble)
  824. AUDIO_TEST(SoundStealthOn)
  825. AUDIO_TEST(SoundStealthOff)
  826. AUDIO_TEST(SoundCreated)
  827. AUDIO_TEST(SoundOnDamaged)
  828. AUDIO_TEST(SoundOnReallyDamaged)
  829. AUDIO_TEST(SoundDieFire)
  830. AUDIO_TEST(SoundDieToxin)
  831. AUDIO_TEST(SoundEnter)
  832. AUDIO_TEST(SoundExit)
  833. AUDIO_TEST(SoundPromotedVeteran)
  834. AUDIO_TEST(SoundPromotedElite)
  835. AUDIO_TEST(SoundPromotedHero)
  836. #undef AUDIO_TEST
  837. const PerUnitSoundMap *perUnitSounds = getAllPerUnitSounds();
  838. if (!perUnitSounds)
  839. {
  840. return;
  841. }
  842. for (PerUnitSoundMap::const_iterator it = perUnitSounds->begin(); it != perUnitSounds->end(); ++it)
  843. {
  844. if (!it->second.getEventName().isEmpty() && it->second.getEventName().compareNoCase("NoSound") != 0)
  845. {
  846. DEBUG_ASSERTCRASH(TheAudio->isValidAudioEvent(&it->second),
  847. ("Invalid UnitSpecificSound '%s' in Object '%s'. (%s?)",
  848. it->first.str(),
  849. getName().str(),
  850. it->second.getEventName().str()));
  851. }
  852. }
  853. #endif
  854. }
  855. //-------------------------------------------------------------------------------------------------
  856. void ThingTemplate::validate()
  857. {
  858. if (m_shadowTextureName.isEmpty())
  859. {
  860. // no texture given, pick a default
  861. switch (getTemplateGeometryInfo().getGeomType())
  862. {
  863. case GEOMETRY_SPHERE:
  864. case GEOMETRY_CYLINDER:
  865. m_shadowTextureName = "shadow";
  866. break;
  867. case GEOMETRY_BOX:
  868. m_shadowTextureName = "shadows";
  869. break;
  870. }
  871. }
  872. validateAudio();
  873. #if defined(_DEBUG) || defined(_INTERNAL)
  874. if (getName() == "DefaultThingTemplate")
  875. return;
  876. // neutron missile is an old special case....
  877. if (getName() == "NeutronMissile")
  878. return;
  879. // another cheesy special case....
  880. if (getName() == "FlamethrowerProjectileStream")
  881. return;
  882. // drawable-only templates are exempt from further checks.
  883. if (isKindOf(KINDOF_DRAWABLE_ONLY))
  884. return;
  885. // build-variation templates are exempt from further checks.
  886. if (!m_buildVariations.empty())
  887. return;
  888. Bool isImmobile = isKindOf(KINDOF_IMMOBILE);
  889. if (isKindOf(KINDOF_SHRUBBERY) && !isImmobile)
  890. {
  891. DEBUG_CRASH(("SHRUBBERY %s must be marked IMMOBILE!",getName().str()));
  892. }
  893. if (isKindOf(KINDOF_STRUCTURE) && !isImmobile)
  894. {
  895. DEBUG_CRASH(("Structure %s is not marked immobile, but probably should be -- please fix it. (If we ever add mobile structures, this debug sniffer will need to be revised.)\n",getName().str()));
  896. }
  897. if (isKindOf(KINDOF_STICK_TO_TERRAIN_SLOPE) && !isImmobile)
  898. {
  899. DEBUG_CRASH(("item %s is marked STICK_TO_TERRAIN_SLOPE but not IMMOBILE -- please fix it.\n",getName().str()));
  900. }
  901. if (isKindOf(KINDOF_STRUCTURE))
  902. {
  903. if (m_armorTemplateSets.empty() || (m_armorTemplateSets.size() == 1 && m_armorTemplateSets[0].getArmorTemplate() == NULL))
  904. {
  905. DEBUG_CRASH(("Structure %s has no armor, but probably should (StructureArmor) -- please fix it.)\n",getName().str()));
  906. }
  907. for (ArmorTemplateSetVector::const_iterator it = m_armorTemplateSets.begin(); it != m_armorTemplateSets.end(); ++it)
  908. {
  909. if (it->getDamageFX() == NULL)
  910. {
  911. DEBUG_CRASH(("Structure %s has no ArmorDamageFX, and really should.\n",getName().str()));
  912. }
  913. }
  914. }
  915. #endif
  916. }
  917. //-------------------------------------------------------------------------------------------------
  918. // copy the guts of that into this, but preserve this' name, id, and list-links.
  919. void ThingTemplate::copyFrom(const ThingTemplate* that)
  920. {
  921. if (!that)
  922. return;
  923. ThingTemplate* next = this->m_nextThingTemplate;
  924. UnsignedShort id = this->m_templateID;
  925. AsciiString name = this->m_nameString;
  926. *this = *that;
  927. this->m_nextThingTemplate = next;
  928. this->m_templateID = id;
  929. this->m_nameString = name;
  930. }
  931. //-------------------------------------------------------------------------------------------------
  932. void ThingTemplate::setCopiedFromDefault()
  933. {
  934. m_armorCopiedFromDefault = true;
  935. m_weaponsCopiedFromDefault = true;
  936. m_behaviorModuleInfo.setCopiedFromDefault(true);
  937. m_drawModuleInfo.setCopiedFromDefault(true);
  938. m_clientUpdateModuleInfo.setCopiedFromDefault(true);
  939. }
  940. //-------------------------------------------------------------------------------------------------
  941. ThingTemplate::~ThingTemplate()
  942. {
  943. // note, we don't need to take any special action for Armor/WeaponSets...
  944. // though it is just a list of 'raw' pointers, we don't have ownership of 'em,
  945. // and so we MUST NOT delete them
  946. }
  947. //=============================================================================
  948. void ThingTemplate::resolveNames()
  949. {
  950. Int i, j;
  951. for (i = 0; i < m_prereqInfo.size(); i++)
  952. {
  953. m_prereqInfo[i].resolveNames();
  954. }
  955. const Int MAX_BF = 32;
  956. const ThingTemplate* tmpls[MAX_BF];
  957. for (i = 0; i < m_prereqInfo.size(); i++)
  958. {
  959. Int count = m_prereqInfo[i].getAllPossibleBuildFacilityTemplates(tmpls, MAX_BF);
  960. for (j = 0; j < count; j++)
  961. {
  962. // casting const away is a little evil, but justified in this case:
  963. // PropductionPrerequisite should only be allowed 'const' access,
  964. // but ThingTemplate can muck with stuff with gleeful abandon. (srj)
  965. if( tmpls[ j ] )
  966. const_cast<ThingTemplate*>(tmpls[j])->m_isBuildFacility = true;
  967. // DEBUG_LOG(("BF: %s is a buildfacility for %s\n",tmpls[j]->m_nameString.str(),this->m_nameString.str()));
  968. }
  969. }
  970. if (isKindOf(KINDOF_COMMANDCENTER)) {
  971. // Command centers are considered factories. jba.
  972. m_isBuildFacility = true;
  973. }
  974. // keep a pointer to portrait and button image if present for speed later
  975. if( TheMappedImageCollection )
  976. {
  977. if( m_selectedPortraitImageName.isNotEmpty() )
  978. {
  979. m_selectedPortraitImage = TheMappedImageCollection->findImageByName( m_selectedPortraitImageName );
  980. DEBUG_ASSERTCRASH( m_selectedPortraitImage, ("%s is looking for Portrait %s but can't find it. Skipping...", getName().str(), m_buttonImageName.str() ) );
  981. m_selectedPortraitImageName.clear(); // we're done with this, so nuke it
  982. }
  983. if( m_buttonImageName.isNotEmpty() )
  984. {
  985. m_buttonImage = TheMappedImageCollection->findImageByName( m_buttonImageName );
  986. DEBUG_ASSERTCRASH( m_buttonImage, ("%s is looking for ButtonImage %s but can't find it. Skipping...", getName().str(), m_buttonImageName.str() ) );
  987. m_buttonImageName.clear(); // we're done with this, so nuke it
  988. }
  989. }
  990. }
  991. //=============================================================================
  992. #ifdef LOAD_TEST_ASSETS
  993. void ThingTemplate::initForLTA(const AsciiString& name)
  994. {
  995. m_nameString = name;
  996. char buffer[1024];
  997. strncpy(buffer, name.str(), sizeof(buffer));
  998. for (int i=0; buffer[i]; i++) {
  999. if (buffer[i] == '/') {
  1000. i++;
  1001. break;
  1002. }
  1003. }
  1004. m_LTAName = AsciiString(buffer+i);
  1005. m_behaviorModuleInfo.clear();
  1006. m_drawModuleInfo.clear();
  1007. m_clientUpdateModuleInfo.clear();
  1008. AsciiString moduleTag;
  1009. moduleTag.format( "LTA_%sDestroyDie", m_LTAName.str() );
  1010. m_behaviorModuleInfo.addModuleInfo(this, "DestroyDie", moduleTag, TheModuleFactory->newModuleDataFromINI(NULL, "DestroyDie", MODULETYPE_BEHAVIOR, moduleTag), (MODULEINTERFACE_DIE), false);
  1011. moduleTag.format( "LTA_%sInactiveBody", m_LTAName.str() );
  1012. m_behaviorModuleInfo.addModuleInfo(this, "InactiveBody", moduleTag, TheModuleFactory->newModuleDataFromINI(NULL, "InactiveBody", MODULETYPE_BEHAVIOR, moduleTag), (MODULEINTERFACE_BODY), false);
  1013. moduleTag.format( "LTA_%sW3DDefaultDraw", m_LTAName.str() );
  1014. m_drawModuleInfo.addModuleInfo(this, "W3DDefaultDraw", moduleTag, TheModuleFactory->newModuleDataFromINI(NULL, "W3DDefaultDraw", MODULETYPE_DRAW, moduleTag), (MODULEINTERFACE_DRAW), false);
  1015. m_armorCopiedFromDefault = false;
  1016. m_weaponsCopiedFromDefault = false;
  1017. m_kindof = KINDOFMASK_NONE;
  1018. m_assetScale = 1.0f;
  1019. m_instanceScaleFuzziness = 0.0f; ///< tolerance to randomly vary scale per instance
  1020. m_structureRubbleHeight = 0.0f; // zero means "use global default"
  1021. m_displayName.translate( name );
  1022. m_shadowType = SHADOW_VOLUME;
  1023. m_geometryInfo.set(GEOMETRY_SPHERE, false, 10.0, 10.0, 10.0);
  1024. }
  1025. #endif
  1026. //=============================================================================
  1027. const ArmorTemplateSet* ThingTemplate::findArmorTemplateSet(const ArmorSetFlags& t) const
  1028. {
  1029. return m_armorTemplateSetFinder.findBestInfo(m_armorTemplateSets, t);
  1030. }
  1031. //=============================================================================
  1032. const WeaponTemplateSet* ThingTemplate::findWeaponTemplateSet(const WeaponSetFlags& t) const
  1033. {
  1034. return m_weaponTemplateSetFinder.findBestInfo(m_weaponTemplateSets, t);
  1035. }
  1036. //-----------------------------------------------------------------------------
  1037. // returns true iff we have at least one weaponset that contains a weapon.
  1038. // returns false if we have no weaponsets, or they are all empty.
  1039. Bool ThingTemplate::canPossiblyHaveAnyWeapon() const
  1040. {
  1041. for (WeaponTemplateSetVector::const_iterator it = m_weaponTemplateSets.begin();
  1042. it != m_weaponTemplateSets.end();
  1043. ++it)
  1044. {
  1045. if (it->hasAnyWeapons())
  1046. return true;
  1047. }
  1048. return false;
  1049. }
  1050. //-----------------------------------------------------------------------------
  1051. Int ThingTemplate::getSkillPointValue(Int level) const
  1052. {
  1053. Int value = m_skillPointValues[level];
  1054. if (value == USE_EXP_VALUE_FOR_SKILL_VALUE)
  1055. value = getExperienceValue(level);
  1056. return value;
  1057. }
  1058. //-----------------------------------------------------------------------------
  1059. const ThingTemplate *ThingTemplate::getBuildFacilityTemplate( const Player *player ) const
  1060. {
  1061. if (getPrereqCount() > 0)
  1062. {
  1063. return m_prereqInfo[0].getExistingBuildFacilityTemplate(player); // might return null
  1064. }
  1065. else
  1066. {
  1067. return NULL;
  1068. }
  1069. }
  1070. //-------------------------------------------------------------------------------------------------
  1071. BuildableStatus ThingTemplate::getBuildable() const
  1072. {
  1073. BuildableStatus bs;
  1074. if (TheGameLogic && TheGameLogic->findBuildableStatusOverride(this, bs))
  1075. return bs;
  1076. return (BuildableStatus)m_buildable;
  1077. }
  1078. //-------------------------------------------------------------------------------------------------
  1079. const FXList *ThingTemplate::getPerUnitFX(const AsciiString& fxName) const
  1080. {
  1081. if (fxName.isEmpty())
  1082. {
  1083. return NULL;
  1084. }
  1085. PerUnitFXMap::const_iterator it = m_perUnitFX.find(fxName);
  1086. if (it == m_perUnitFX.end())
  1087. {
  1088. DEBUG_CRASH(("Unknown FX name (%s) asked for in ThingTemplate (%s). ", fxName.str(), m_nameString.str()));
  1089. return NULL;
  1090. }
  1091. return (it->second);
  1092. }
  1093. //-------------------------------------------------------------------------------------------------
  1094. const AudioEventRTS *ThingTemplate::getPerUnitSound(const AsciiString& soundName) const
  1095. {
  1096. if (soundName.isEmpty())
  1097. {
  1098. return &s_audioEventNoSound;
  1099. }
  1100. PerUnitSoundMap::const_iterator it = m_perUnitSounds.find(soundName);
  1101. if (it == m_perUnitSounds.end())
  1102. {
  1103. DEBUG_LOG(("Unknown Audio name (%s) asked for in ThingTemplate (%s).\n", soundName.str(), m_nameString.str()));
  1104. return &s_audioEventNoSound;
  1105. }
  1106. return &(it->second);
  1107. }
  1108. //-------------------------------------------------------------------------------------------------
  1109. Bool ThingTemplate::isEquivalentTo(const ThingTemplate* tt) const
  1110. {
  1111. // sanity
  1112. if (!(this && tt))
  1113. return false;
  1114. // sanity
  1115. if (this == tt)
  1116. return true;
  1117. if (this->getFinalOverride() == tt->getFinalOverride())
  1118. return true;
  1119. // This reskinned from that?
  1120. if (this->m_reskinnedFrom == tt)
  1121. return true;
  1122. // That reskinned from this?
  1123. if (this == tt->m_reskinnedFrom)
  1124. return true;
  1125. // This reskinned from that reskinned from?
  1126. // Kris: added case (chassis 2 compared to chassis 3 -- NULL possible if not reskinned)
  1127. if( this->m_reskinnedFrom && this->m_reskinnedFrom == tt->m_reskinnedFrom )
  1128. return true;
  1129. // Is this thing a build variation of that thing or vice versa
  1130. Int i;
  1131. Int numVariations = m_buildVariations.size();
  1132. for (i = 0; i < numVariations; ++i)
  1133. if (m_buildVariations[i].compareNoCase(tt->getName()) == 0)
  1134. return true;
  1135. numVariations = tt->m_buildVariations.size();
  1136. for (i = 0; i < numVariations; ++i)
  1137. if (tt->m_buildVariations[i].compareNoCase(getName()) == 0)
  1138. return true;
  1139. // Guess we're not equivalent.
  1140. return false;
  1141. }
  1142. //-------------------------------------------------------------------------------------------------
  1143. Bool ThingTemplate::isBuildableItem(void) const
  1144. {
  1145. return (getBuildCost() != 0);
  1146. }
  1147. //-------------------------------------------------------------------------------------------------
  1148. /** NOTE that we're not paying attention to m_override here, instead the portions
  1149. * that retrieve template data values use the get() wrappers, which *DO* pay
  1150. * attention to the override values */
  1151. //-------------------------------------------------------------------------------------------------
  1152. Int ThingTemplate::calcCostToBuild( const Player* player) const
  1153. {
  1154. if (!player)
  1155. return 0;
  1156. // changePercent format is "-.2 equals 20% cheaper"
  1157. Real factionModifier = 1 + player->getProductionCostChangePercent( getName() );
  1158. factionModifier *= player->getProductionCostChangeBasedOnKindOf( m_kindof );
  1159. return getBuildCost() * factionModifier * player->getHandicap()->getHandicap(Handicap::BUILDCOST, this);
  1160. }
  1161. //-------------------------------------------------------------------------------------------------
  1162. /** NOTE that we're not paying attention to m_override here, instead the portions
  1163. * that retrieve template data values use the get() wrappers, which *DO* pay
  1164. * attention to the override values */
  1165. //-------------------------------------------------------------------------------------------------
  1166. Int ThingTemplate::calcTimeToBuild( const Player* player) const
  1167. {
  1168. Int buildTime = getBuildTime() * LOGICFRAMES_PER_SECOND;
  1169. buildTime *= player->getHandicap()->getHandicap(Handicap::BUILDTIME, this);
  1170. Real factionModifier = 1 + player->getProductionTimeChangePercent( getName() );
  1171. buildTime *= factionModifier;
  1172. #if defined (_DEBUG) || defined (_INTERNAL)
  1173. if( player->buildsInstantly() )
  1174. {
  1175. buildTime = 1;
  1176. }
  1177. #endif
  1178. // Adjust build time based on energy supply.
  1179. Real EnergyPercent = player->getEnergy()->getEnergySupplyRatio(); //I'm at 80% Energy
  1180. if (EnergyPercent > 1.0f)
  1181. EnergyPercent = 1.0f; // getEnergySupplyRatio() returns a true ratio, but we don't care about excess.
  1182. Real EnergyShort = 1.0f - EnergyPercent; //so I am 20% short
  1183. EnergyShort *= TheGlobalData->m_LowEnergyPenaltyModifier; //which is a 40% penalty, or a 10% penalty
  1184. Real penaltyRate = 1.0f - EnergyShort;
  1185. penaltyRate = max(penaltyRate, TheGlobalData->m_MinLowEnergyProductionSpeed); //bind so 0% does not dead stop you
  1186. if( EnergyPercent < 1.0f ) //and make 99% look like 80% (eg) since most of the time you are down only a little
  1187. penaltyRate = min(penaltyRate, TheGlobalData->m_MaxLowEnergyProductionSpeed);
  1188. if (penaltyRate <= 0.0f)
  1189. penaltyRate = 0.01f; // Design won't make the minimum 0, they promise
  1190. buildTime /= penaltyRate;//and voila. Makes sense, designers have control, all is happy.
  1191. // Multiple build facilities can have an added production bonus.
  1192. if (getBuildCompletion() == BC_APPEARS_AT_RALLY_POINT)
  1193. {
  1194. const ThingTemplate *tmpl = getBuildFacilityTemplate(player); // could be null if none exist
  1195. Int count = 0;
  1196. if (tmpl)
  1197. {
  1198. player->countObjectsByThingTemplate(1, &tmpl, false, &count);
  1199. Real factoryMult = TheGlobalData->m_MultipleFactory;
  1200. if (factoryMult > 0.0f)
  1201. {
  1202. for(int i=0; i < count - 1; i++)
  1203. buildTime *= factoryMult;
  1204. }
  1205. }
  1206. }
  1207. return(buildTime);
  1208. }
  1209. //---------------------------------------------------------------------------------------ModuleInfo
  1210. //-------------------------------------------------------------------------------------------------
  1211. ModuleData* ModuleInfo::friend_getNthData(Int i)
  1212. {
  1213. if (i >= 0 && i < m_info.size())
  1214. {
  1215. // This is kinda naughty, but its necessary.
  1216. return const_cast<ModuleData*>(m_info[i].second);
  1217. }
  1218. return NULL;
  1219. }