From a4ba72ca7378f006210af37149096b866a23ee15 Mon Sep 17 00:00:00 2001 From: pWn3d Date: Wed, 7 Jan 2026 19:58:27 +0100 Subject: [PATCH] Implement Extend for Weapon and Locomotor Fix for worldbuilder crash --- .../Code/GameEngine/Include/Common/INI.h | 2 + .../GameEngine/Include/GameLogic/Locomotor.h | 1 + .../GameEngine/Include/GameLogic/Weapon.h | 3 + .../Code/GameEngine/Source/Common/INI/INI.cpp | 6 +- .../Source/Common/INI/INIWeapon.cpp | 5 ++ .../Source/GameLogic/Object/Locomotor.cpp | 50 ++++++++++++ .../Source/GameLogic/Object/Weapon.cpp | 77 +++++++++++++++++++ .../GameClient/Shadow/W3DProjectedShadow.cpp | 4 +- 8 files changed, 144 insertions(+), 4 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Include/Common/INI.h b/GeneralsMD/Code/GameEngine/Include/Common/INI.h index df42dacc0f0..b5d781d99fe 100644 --- a/GeneralsMD/Code/GameEngine/Include/Common/INI.h +++ b/GeneralsMD/Code/GameEngine/Include/Common/INI.h @@ -191,10 +191,12 @@ class INI static void parseObjectReskinDefinition( INI *ini ); static void parseObjectExtendDefinition( INI* ini ); static void parseWeaponTemplateDefinition( INI *ini ); + static void parseWeaponExtendTemplateDefinition(INI* ini); static void parseScienceDefinition( INI *ini ); static void parseRankDefinition( INI *ini ); static void parseCrateTemplateDefinition( INI *ini ); static void parseLocomotorTemplateDefinition( INI *ini ); + static void parseLocomotorExtendTemplateDefinition( INI *ini ); static void parseLanguageDefinition( INI *ini ); static void parsePlayerTemplateDefinition( INI *ini ); static void parseGameDataDefinition( INI *ini ); diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Locomotor.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Locomotor.h index 4bb6ca882cf..02f4aafe04a 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Locomotor.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Locomotor.h @@ -510,6 +510,7 @@ class LocomotorStore : public SubsystemInterface static void parseLocomotorTemplateDefinition(INI* ini); + static void parseLocomotorExtendTemplateDefinition(INI* ini); protected: diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Weapon.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Weapon.h index 62269eb165e..d28672454ac 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Weapon.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Weapon.h @@ -417,6 +417,8 @@ class WeaponTemplate : public MemoryPoolObject /// field table for loading the values from an INI const FieldParse* getFieldParse() const { return TheWeaponTemplateFieldParseTable; } + void copy_from(const WeaponTemplate& other); + /** fire the weapon. return the logic-frame in which the damage will be dealt. @@ -973,6 +975,7 @@ class WeaponStore : public SubsystemInterface void handleProjectileDetonation( const WeaponTemplate* w, const Object *source, const Coord3D* pos, WeaponBonusConditionFlags extraBonusFlags, Bool inflictDamage = TRUE ); static void parseWeaponTemplateDefinition(INI* ini); + static void parseWeaponExtendTemplateDefinition(INI* ini); protected: diff --git a/GeneralsMD/Code/GameEngine/Source/Common/INI/INI.cpp b/GeneralsMD/Code/GameEngine/Source/Common/INI/INI.cpp index 1e144647c03..ab1a9119186 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/INI/INI.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/INI/INI.cpp @@ -107,6 +107,7 @@ static const BlockParse theTypeTable[] = { "GameData", INI::parseGameDataDefinition }, { "InGameUI", INI::parseInGameUIDefinition }, { "Locomotor", INI::parseLocomotorTemplateDefinition }, + { "LocomotorExtend", INI::parseLocomotorExtendTemplateDefinition }, { "Language", INI::parseLanguageDefinition }, { "MapCache", INI::parseMapCacheDefinition }, { "MapData", INI::parseMapDataDefinition }, @@ -134,9 +135,10 @@ static const BlockParse theTypeTable[] = { "Upgrade", INI::parseUpgradeDefinition }, { "Video", INI::parseVideoDefinition }, { "WaterSet", INI::parseWaterSettingDefinition }, - { "WaterTransparency", INI::parseWaterTransparencyDefinition}, - { "Weather", INI::parseWeatherDefinition}, + { "WaterTransparency", INI::parseWaterTransparencyDefinition }, + { "Weather", INI::parseWeatherDefinition }, { "Weapon", INI::parseWeaponTemplateDefinition }, + { "WeaponExtend", INI::parseWeaponExtendTemplateDefinition }, { "WebpageURL", INI::parseWebpageURLDefinition }, { "HeaderTemplate", INI::parseHeaderTemplateDefinition }, { "StaticGameLOD", INI::parseStaticGameLODDefinition }, diff --git a/GeneralsMD/Code/GameEngine/Source/Common/INI/INIWeapon.cpp b/GeneralsMD/Code/GameEngine/Source/Common/INI/INIWeapon.cpp index bf439d7ec73..4d8eba4c7d0 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/INI/INIWeapon.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/INI/INIWeapon.cpp @@ -45,4 +45,9 @@ void INI::parseWeaponTemplateDefinition( INI* ini ) WeaponStore::parseWeaponTemplateDefinition(ini); } +void INI::parseWeaponExtendTemplateDefinition(INI* ini) +{ + WeaponStore::parseWeaponExtendTemplateDefinition(ini); +} + diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Locomotor.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Locomotor.cpp index e37b1aa5ad1..8889f586709 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Locomotor.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Locomotor.cpp @@ -641,12 +641,62 @@ LocomotorTemplate *LocomotorStore::newOverride( LocomotorTemplate *locoTemplate TheLocomotorStore->m_locomotorTemplates[namekey] = loco; } +void LocomotorStore::parseLocomotorExtendTemplateDefinition(INI* ini) +{ + if (!TheLocomotorStore) + throw INI_INVALID_DATA; + + Bool isOverride = false; + // read the Locomotor name + const char* token = ini->getNextToken(); + NameKeyType namekey = NAMEKEY(token); + + const char* token2 = ini->getNextToken(); + NameKeyType parentKey = NAMEKEY(token2); + + LocomotorTemplate* locoParent = TheLocomotorStore->findLocomotorTemplate(parentKey); + if (locoParent) { + + LocomotorTemplate* loco = TheLocomotorStore->findLocomotorTemplate(namekey); + if (loco) { + if (ini->getLoadType() == INI_LOAD_CREATE_OVERRIDES) { + loco = TheLocomotorStore->newOverride((LocomotorTemplate*)loco->friend_getFinalOverride()); + } + isOverride = true; + } + else { + loco = newInstance(LocomotorTemplate); + if (ini->getLoadType() == INI_LOAD_CREATE_OVERRIDES) { + loco->markAsOverride(); + } + } + // copy all parent values + *loco = *locoParent; + loco->friend_setName(token); + ini->initFromINI(loco, loco->getFieldParse()); + loco->validate(); + + // if this is an override, then we want the pointer on the existing named locomotor to point us + // to the override, so don't add it to the map. + if (!isOverride) + TheLocomotorStore->m_locomotorTemplates[namekey] = loco; + } + else { + DEBUG_CRASH(("LocomotorExtend '%s': parent '%s' does not exist", token, token2)); + } +} + //------------------------------------------------------------------------------------------------- /*static*/ void INI::parseLocomotorTemplateDefinition( INI* ini ) { LocomotorStore::parseLocomotorTemplateDefinition(ini); } +/*static*/ void INI::parseLocomotorExtendTemplateDefinition(INI* ini) +{ + LocomotorStore::parseLocomotorExtendTemplateDefinition(ini); +} + //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Weapon.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Weapon.cpp index 14c5f1e17c1..848f8a991e6 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Weapon.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Weapon.cpp @@ -365,6 +365,20 @@ void WeaponTemplate::reset( void ) m_historicDamage.clear(); } +void WeaponTemplate::copy_from(const WeaponTemplate& other) { + //Backup nextTemplate, name and namekey + WeaponTemplate* nextTempl = this->m_nextTemplate; + AsciiString name = this->m_name; + NameKeyType nameKey = this->m_nameKey; + + // take all values from other + *this = other; + + this->m_nextTemplate = nextTempl; + this->m_name = name; + this->m_nameKey = nameKey; +} + //------------------------------------------------------------------------------------------------- /*static*/ void WeaponTemplate::parseWeaponBonusSet( INI* ini, void *instance, void * /*store*/, const void* /*userData*/ ) { @@ -1936,6 +1950,69 @@ void WeaponStore::postProcessLoad() } + +//------------------------------------------------------------------------------------------------- +/*static*/ void WeaponStore::parseWeaponExtendTemplateDefinition(INI* ini) +{ + AsciiString name; + AsciiString parent; + + // read the weapon name + const char* c = ini->getNextToken(); + name.set(c); + + // read the parent name + const char* c2 = ini->getNextToken(); + parent.set(c2); + + // find parent if present + WeaponTemplate* parentWeapon = TheWeaponStore->findWeaponTemplatePrivate(TheNameKeyGenerator->nameToKey(parent)); + if (parentWeapon) + { + + // find existing item if present + WeaponTemplate* weapon = TheWeaponStore->findWeaponTemplatePrivate(TheNameKeyGenerator->nameToKey(name)); + if (weapon) + { + if (ini->getLoadType() == INI_LOAD_CREATE_OVERRIDES) + weapon = TheWeaponStore->newOverride(weapon); + else + { + DEBUG_CRASH(("Weapon '%s' already exists, but OVERRIDE not specified", c)); + return; + } + + } + else + { + // no item is present, create a new one + weapon = TheWeaponStore->newWeaponTemplate(name); + } + + //copy from parent + weapon->copy_from(*parentWeapon); + + // parse the ini weapon definition + ini->initFromINI(weapon, weapon->getFieldParse()); + + if (weapon->m_projectileName.isNone()) + weapon->m_projectileName.clear(); + +#if defined(RTS_DEBUG) + if (!weapon->getFireSound().getEventName().isEmpty() && weapon->getFireSound().getEventName().compareNoCase("NoSound") != 0) + { + DEBUG_ASSERTCRASH(TheAudio->isValidAudioEvent(&weapon->getFireSound()), ("Invalid FireSound %s in Weapon '%s'.", weapon->getFireSound().getEventName().str(), weapon->getName().str())); + } +#endif + + } + else + { + DEBUG_CRASH(("Weapon '%s' cannot extend parrent '%s' as it not exists", c, c2)); + } + +} + //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- diff --git a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Shadow/W3DProjectedShadow.cpp b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Shadow/W3DProjectedShadow.cpp index 5007e7e8fa4..dead53d4335 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Shadow/W3DProjectedShadow.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Shadow/W3DProjectedShadow.cpp @@ -1040,7 +1040,7 @@ void W3DProjectedShadowManager::queueDecal(W3DProjectedShadow *shadow) hmapVertex.X=(float)(i-borderSize)*MAP_XY_FACTOR; hmapVertex.Z=__max((float)hmap->getHeight(i,j)*MAP_HEIGHT_SCALE,layerHeight); - if (TheGlobalData->m_heightAboveTerrainIncludesWater) { + if (TheGlobalData->m_heightAboveTerrainIncludesWater && TheTerrainLogic != nullptr) { if (Real waterZ = 0; TheTerrainLogic->isUnderwater(hmapVertex.X, hmapVertex.Y, &waterZ)) { if (waterZ > hmapVertex.Z) hmapVertex.Z = waterZ; } @@ -1066,7 +1066,7 @@ void W3DProjectedShadowManager::queueDecal(W3DProjectedShadow *shadow) hmapVertex.X=(float)(i-borderSize)*MAP_XY_FACTOR; hmapVertex.Z=(float)hmap->getHeight(i,j)*MAP_HEIGHT_SCALE+0.01f * MAP_XY_FACTOR; - if (TheGlobalData->m_heightAboveTerrainIncludesWater) { + if (TheGlobalData->m_heightAboveTerrainIncludesWater && TheTerrainLogic != nullptr) { if (Real waterZ = 0; TheTerrainLogic->isUnderwater(hmapVertex.X, hmapVertex.Y, &waterZ)) { if (waterZ > hmapVertex.Z) hmapVertex.Z = waterZ; }