/* Starshatter OpenSource Distribution Copyright (c) 1997-2004, Destroyer Studios LLC. All Rights Reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name "Destroyer Studios" nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. SUBSYSTEM: Stars.exe FILE: CombatGroup.cpp AUTHOR: John DiCamillo OVERVIEW ======== An element in the dynamic campaign */ #include "MemDebug.h" #include "CombatGroup.h" #include "CombatUnit.h" #include "CombatZone.h" #include "Combatant.h" #include "CombatAssignment.h" #include "Campaign.h" #include "ShipDesign.h" #include "Ship.h" #include "Game.h" #include "DataLoader.h" #include "ParseUtil.h" // +----------------------------------------------------------------------+ CombatGroup::CombatGroup(int t, int n, const char* s, int iff_code, int e, CombatGroup* p) : type(t), id(n), name(s), iff(iff_code), enemy_intel(e), parent(p), value(0), plan_value(0), unit_index(0), combatant(0), expanded(false), sorties(0), kills(0), points(0), current_zone(0), assigned_zone(0), zone_lock(false) { if (parent) parent->AddComponent(this); } CombatGroup::~CombatGroup() { assignments.destroy(); components.destroy(); units.destroy(); } // +--------------------------------------------------------------------+ void CombatGroup::AddComponent(CombatGroup* g) { if (g) { g->parent = this; components.append(g); } } // +--------------------------------------------------------------------+ bool CombatGroup::IsAssignable() const { switch (type) { case CARRIER_GROUP: case BATTLE_GROUP: case DESTROYER_SQUADRON: case ATTACK_SQUADRON: case FIGHTER_SQUADRON: case INTERCEPT_SQUADRON: case LCA_SQUADRON: return ((CombatGroup*) this)->CalcValue() > 0; } return false; } bool CombatGroup::IsTargetable() const { // neutral / non-combatants are not *strategic* targets // for any combatant: if (iff < 1 || iff >= 100) return false; // civilian / non-combatant are not strategic targets: if (type == PASSENGER || type == PRIVATE || type == MEDICAL || type == HABITAT) return false; // must have units of our own to be targetable: if (units.size() < 1) return false; return ((CombatGroup*) this)->CalcValue() > 0; } bool CombatGroup::IsDefensible() const { if (type >= SUPPORT) return ((CombatGroup*) this)->CalcValue() > 0; return false; } bool CombatGroup::IsStrikeTarget() const { if (type < BATTALION || type == MINEFIELD || // assault, not strike type == PASSENGER || type == PRIVATE || type == MEDICAL || type == HABITAT) return false; return ((CombatGroup*) this)->CalcValue() > 0; } // +--------------------------------------------------------------------+ bool CombatGroup::IsMovable() const { switch (type) { case CARRIER_GROUP: case BATTLE_GROUP: case DESTROYER_SQUADRON: case ATTACK_SQUADRON: case FIGHTER_SQUADRON: case INTERCEPT_SQUADRON: case LCA_SQUADRON: case COURIER: case MEDICAL: case SUPPLY: case REPAIR: case FREIGHT: case PASSENGER: case PRIVATE: return true; } return false; } // +--------------------------------------------------------------------+ bool CombatGroup::IsFighterGroup() const { switch (type) { case WING: case INTERCEPT_SQUADRON: case FIGHTER_SQUADRON: case ATTACK_SQUADRON: return true; } return false; } bool CombatGroup::IsStarshipGroup() const { switch (type) { case DESTROYER_SQUADRON: case BATTLE_GROUP: case CARRIER_GROUP: return true; } return false; } // +--------------------------------------------------------------------+ bool CombatGroup::IsReserve() const { if (enemy_intel <= Intel::RESERVE) return true; if (parent) return parent->IsReserve(); return false; } // +--------------------------------------------------------------------+ const int* CombatGroup::PreferredAttacker(int type) { static int p[8]; ZeroMemory(p, sizeof(p)); switch (type) { //case FLEET: case DESTROYER_SQUADRON: p[0] = DESTROYER_SQUADRON; p[1] = BATTLE_GROUP; p[2] = CARRIER_GROUP; p[3] = ATTACK_SQUADRON; break; case BATTLE_GROUP: p[0] = BATTLE_GROUP; p[1] = DESTROYER_SQUADRON; p[2] = CARRIER_GROUP; p[3] = ATTACK_SQUADRON; break; case CARRIER_GROUP: p[0] = ATTACK_SQUADRON; p[1] = BATTLE_GROUP; p[2] = DESTROYER_SQUADRON; p[3] = CARRIER_GROUP; break; //case WING: case LCA_SQUADRON: case ATTACK_SQUADRON: case INTERCEPT_SQUADRON: case FIGHTER_SQUADRON: p[0] = INTERCEPT_SQUADRON; p[1] = FIGHTER_SQUADRON; break; //case BATTALION: case STATION: p[0] = BATTLE_GROUP; p[1] = CARRIER_GROUP; break; case STARBASE: case BATTERY: case MISSILE: p[0] = ATTACK_SQUADRON; p[1] = FIGHTER_SQUADRON; break; //case C3I: case MINEFIELD: case COMM_RELAY: case EARLY_WARNING: case FWD_CONTROL_CTR: case ECM: p[0] = ATTACK_SQUADRON; p[1] = FIGHTER_SQUADRON; p[2] = DESTROYER_SQUADRON; break; //case SUPPORT: case COURIER: case MEDICAL: case SUPPLY: case REPAIR: p[0] = DESTROYER_SQUADRON; p[1] = BATTLE_GROUP; p[2] = ATTACK_SQUADRON; break; //case CIVILIAN: //case WAR_PRODuCTION: case FACTORY: case REFINERY: case RESOURCE: p[0] = ATTACK_SQUADRON; p[1] = FIGHTER_SQUADRON; break; //case INFRASTRUCTURE: case TRANSPORT: case NETWORK: case HABITAT: case STORAGE: p[0] = ATTACK_SQUADRON; p[1] = FIGHTER_SQUADRON; break; //case NON_COM: case FREIGHT: case PASSENGER: case PRIVATE: p[0] = DESTROYER_SQUADRON; p[1] = ATTACK_SQUADRON; break; } return p; } // +--------------------------------------------------------------------+ const int* CombatGroup::PreferredDefender(int type) { static int p[8]; ZeroMemory(p, sizeof(p)); switch (type) { //case FLEET: case CARRIER_GROUP: case BATTLE_GROUP: case DESTROYER_SQUADRON: //case WING: case LCA_SQUADRON: case ATTACK_SQUADRON: case INTERCEPT_SQUADRON: case FIGHTER_SQUADRON: break; //case BATTALION: case STATION: p[0] = BATTLE_GROUP; p[1] = CARRIER_GROUP; p[2] = DESTROYER_SQUADRON; break; case STARBASE: case MINEFIELD: case BATTERY: case MISSILE: p[0] = FIGHTER_SQUADRON; p[1] = INTERCEPT_SQUADRON; break; //case C3I: case COMM_RELAY: case EARLY_WARNING: case FWD_CONTROL_CTR: case ECM: p[0] = FIGHTER_SQUADRON; p[1] = INTERCEPT_SQUADRON; break; //case SUPPORT: case COURIER: case MEDICAL: case SUPPLY: case REPAIR: p[0] = DESTROYER_SQUADRON; p[1] = BATTLE_GROUP; p[2] = ATTACK_SQUADRON; break; //case CIVILIAN: //case WAR_PRODuCTION: case FACTORY: case REFINERY: case RESOURCE: p[0] = FIGHTER_SQUADRON; p[1] = INTERCEPT_SQUADRON; break; //case INFRASTRUCTURE: case TRANSPORT: case NETWORK: case HABITAT: case STORAGE: p[0] = FIGHTER_SQUADRON; p[1] = INTERCEPT_SQUADRON; break; //case NON_COM: case FREIGHT: case PASSENGER: case PRIVATE: p[0] = DESTROYER_SQUADRON; p[1] = BATTLE_GROUP; break; } return p; } // +--------------------------------------------------------------------+ CombatGroup* CombatGroup::FindGroup(int t, int n) { CombatGroup* result = 0; if (type == t && (n < 0 || id == n)) result = this; ListIter group = components; while (!result && ++group) { result = group->FindGroup(t, n); } return result; } // +--------------------------------------------------------------------+ CombatGroup* CombatGroup::Clone(bool deep) { CombatGroup* clone = new(__FILE__,__LINE__) CombatGroup(type, id, name, iff, enemy_intel); clone->combatant = combatant; clone->region = region; clone->location = location; clone->value = value; clone->expanded = expanded; for (int i = 0; i < units.size(); i++) { CombatUnit* u = new(__FILE__,__LINE__) CombatUnit(*units[i]); u->SetCombatGroup(clone); clone->units.append(u); } if (deep) { for (int i = 0; i < components.size(); i++) { CombatGroup* g = components[i]->Clone(deep); clone->AddComponent(g); if (g->Type() == FIGHTER_SQUADRON || g->Type() == INTERCEPT_SQUADRON || g->Type() == ATTACK_SQUADRON || g->Type() == LCA_SQUADRON) { if (units.size() > 0) { CombatUnit* carrier = units[0]; for (int u = 0; u < g->GetUnits().size(); u++) { CombatUnit* unit = g->GetUnits()[u]; if (unit->Type() >= Ship::FIGHTER || unit->Type() <= Ship::LCA) { unit->SetCarrier(carrier); unit->SetRegion(carrier->GetRegion()); } } } } } } return clone; } // +--------------------------------------------------------------------+ const char* CombatGroup::GetOrdinal() const { static char ordinal[16]; int last_two_digits = id % 100; if (last_two_digits > 10 && last_two_digits < 20) { sprintf_s(ordinal, "ordinal.%d", last_two_digits); Text suffix = Game::GetText(ordinal); if (suffix != ordinal) sprintf_s(ordinal, "%d%s", id, suffix.data()); else sprintf_s(ordinal, "%dth", id); } else { int last_digit = last_two_digits % 10; sprintf_s(ordinal, "ordinal.%d", last_digit); Text suffix = Game::GetText(ordinal); if (suffix != ordinal) sprintf_s(ordinal, "%d%s", id, suffix.data()); else if (last_digit == 1) sprintf_s(ordinal, "%dst", id); else if (last_digit == 2) sprintf_s(ordinal, "%dnd", id); else if (last_digit == 3) sprintf_s(ordinal, "%drd", id); else sprintf_s(ordinal, "%dth", id); } return ordinal; } const char* CombatGroup::GetDescription() const { static char desc[256]; static char name_desc[256]; if (name.length()) sprintf_s(name_desc, " \"%s\"", (const char*) name); else name_desc[0] = 0; switch (type) { case FORCE: strcpy_s(desc, (const char*) name); break; case FLEET: sprintf_s(desc, "%s %s%s", GetOrdinal(), Game::GetText("CombatGroup.FLEET").data(), name_desc); break; case CARRIER_GROUP: sprintf_s(desc, "%s %s%s", GetOrdinal(), Game::GetText("CombatGroup.CARRIER_GROUP").data(), name_desc); break; case BATTLE_GROUP: sprintf_s(desc, "%s %s%s", GetOrdinal(), Game::GetText("CombatGroup.BATTLE_GROUP").data(), name_desc); break; case DESTROYER_SQUADRON: sprintf_s(desc, "%s %s%s", GetOrdinal(), Game::GetText("CombatGroup.DESTROYER_SQUADRON").data(), name_desc); break; case WING: sprintf_s(desc, "%s %s%s", GetOrdinal(), Game::GetText("CombatGroup.WING").data(), name_desc); break; case ATTACK_SQUADRON: sprintf_s(desc, "%s %s%s", GetOrdinal(), Game::GetText("CombatGroup.ATTACK_SQUADRON").data(), name_desc); break; case FIGHTER_SQUADRON: sprintf_s(desc, "%s %s%s", GetOrdinal(), Game::GetText("CombatGroup.FIGHTER_SQUADRON").data(), name_desc); break; case INTERCEPT_SQUADRON: sprintf_s(desc, "%s %s%s", GetOrdinal(), Game::GetText("CombatGroup.INTERCEPT_SQUADRON").data(), name_desc); break; case LCA_SQUADRON: sprintf_s(desc, "%s %s%s", GetOrdinal(), Game::GetText("CombatGroup.LCA_SQUADRON").data(), name_desc); break; case BATTALION: sprintf_s(desc, "%s %s%s", GetOrdinal(), Game::GetText("CombatGroup.BATTALION").data(), name_desc); break; case STATION: sprintf_s(desc, "%s %s", Game::GetText("CombatGroup.STATION").data(), name.data()); break; case STARBASE: sprintf_s(desc, "%s %d%s", Game::GetText("CombatGroup.STARBASE").data(), id, name_desc); break; case MINEFIELD: sprintf_s(desc, "%s %s%s", GetOrdinal(), Game::GetText("CombatGroup.MINEFIELD").data(), name_desc); break; case BATTERY: sprintf_s(desc, "%s %s%s", GetOrdinal(), Game::GetText("CombatGroup.BATTERY").data(), name_desc); break; case MISSILE: sprintf_s(desc, "%s %s%s", GetOrdinal(), Game::GetText("CombatGroup.MISSILE").data(), name_desc); break; case C3I: sprintf_s(desc, "%s %s%s", GetOrdinal(), Game::GetText("CombatGroup.C3I").data(), name_desc); break; case COMM_RELAY: sprintf_s(desc, "%s %s%s", GetOrdinal(), Game::GetText("CombatGroup.COMM_RELAY").data(), name_desc); break; case EARLY_WARNING: sprintf_s(desc, "%s %s%s", GetOrdinal(), Game::GetText("CombatGroup.EARLY_WARNING").data(), name_desc); break; case FWD_CONTROL_CTR: sprintf_s(desc, "%s %s%s", GetOrdinal(), Game::GetText("CombatGroup.FWD_CONTROL_CTR").data(), name_desc); break; case ECM: sprintf_s(desc, "%s %s%s", GetOrdinal(), Game::GetText("CombatGroup.ECM").data(), name_desc); break; case SUPPORT: sprintf_s(desc, "%s %s%s", GetOrdinal(), Game::GetText("CombatGroup.SUPPORT").data(), name_desc); break; case COURIER: sprintf_s(desc, "%s %s%s", GetOrdinal(), Game::GetText("CombatGroup.COURIER").data(), name_desc); break; case SUPPLY: sprintf_s(desc, "%s %s%s", GetOrdinal(), Game::GetText("CombatGroup.SUPPLY").data(), name_desc); break; case REPAIR: sprintf_s(desc, "%s %s%s", GetOrdinal(), Game::GetText("CombatGroup.REPAIR").data(), name_desc); break; case MEDICAL: sprintf_s(desc, "%s %s%s", GetOrdinal(), Game::GetText("CombatGroup.MEDICAL").data(), name_desc); break; case CIVILIAN: case WAR_PRODUCTION: case FACTORY: case REFINERY: case RESOURCE: strcpy_s(desc, (const char*) name); break; case INFRASTRUCTURE: case TRANSPORT: case NETWORK: case HABITAT: case STORAGE: case FREIGHT: case PASSENGER: case PRIVATE: strcpy_s(desc, (const char*) name); break; default: sprintf_s(desc, "%s%s", Game::GetText("CombatGroup.default").data(), name_desc); break; } return desc; } const char* CombatGroup::GetShortDescription() const { static char desc[256]; switch (type) { case FORCE: strcpy_s(desc, (const char*) name); break; case FLEET: sprintf_s(desc, "%s %s", GetOrdinal(), Game::GetText("CombatGroup.abrv.FLEET").data()); break; case CARRIER_GROUP: sprintf_s(desc, "%s %s", GetOrdinal(), Game::GetText("CombatGroup.abrv.CARRIER_GROUP").data()); break; case BATTLE_GROUP: sprintf_s(desc, "%s %s", GetOrdinal(), Game::GetText("CombatGroup.abrv.BATTLE_GROUP").data()); break; case DESTROYER_SQUADRON: sprintf_s(desc, "%s %s", GetOrdinal(), Game::GetText("CombatGroup.abrv.DESTROYER_SQUADRON").data()); break; case WING: sprintf_s(desc, "%s %s", GetOrdinal(), Game::GetText("CombatGroup.abrv.WING").data()); break; case ATTACK_SQUADRON: sprintf_s(desc, "%s %s", GetOrdinal(), Game::GetText("CombatGroup.abrv.ATTACK_SQUADRON").data()); break; case FIGHTER_SQUADRON: sprintf_s(desc, "%s %s", GetOrdinal(), Game::GetText("CombatGroup.abrv.FIGHTER_SQUADRON").data()); break; case INTERCEPT_SQUADRON: sprintf_s(desc, "%s %s", GetOrdinal(), Game::GetText("CombatGroup.abrv.INTERCEPT_SQUADRON").data()); break; case LCA_SQUADRON: sprintf_s(desc, "%s %s", GetOrdinal(), Game::GetText("CombatGroup.abrv.LCA_SQUADRON").data()); break; case BATTALION: sprintf_s(desc, "%s %s", GetOrdinal(), Game::GetText("CombatGroup.abrv.BATTALION").data()); break; case STATION: sprintf_s(desc, "%s %s", GetOrdinal(), Game::GetText("CombatGroup.abrv.STATION").data()); break; case STARBASE: sprintf_s(desc, "%s %s", GetOrdinal(), Game::GetText("CombatGroup.abrv.STARBASE").data()); break; case MINEFIELD: sprintf_s(desc, "%s %s", GetOrdinal(), Game::GetText("CombatGroup.abrv.MINEFIELD").data()); break; case BATTERY: sprintf_s(desc, "%s %s", GetOrdinal(), Game::GetText("CombatGroup.abrv.BATTERY").data()); break; case C3I: sprintf_s(desc, "%s %s", GetOrdinal(), Game::GetText("CombatGroup.abrv.C3I").data()); break; case COMM_RELAY: sprintf_s(desc, "%s %s", GetOrdinal(), Game::GetText("CombatGroup.abrv.COMM_RELAY").data()); break; case EARLY_WARNING: sprintf_s(desc, "%s %s", GetOrdinal(), Game::GetText("CombatGroup.abrv.EARLY_WARNING").data()); break; case FWD_CONTROL_CTR: sprintf_s(desc, "%s %s", GetOrdinal(), Game::GetText("CombatGroup.abrv.FWD_CONTROL_CTR").data()); break; case ECM: sprintf_s(desc, "%s %s", GetOrdinal(), Game::GetText("CombatGroup.abrv.ECM").data()); break; case SUPPORT: sprintf_s(desc, "%s %s", GetOrdinal(), Game::GetText("CombatGroup.abrv.SUPPORT").data()); break; case COURIER: sprintf_s(desc, "%s %s", GetOrdinal(), Game::GetText("CombatGroup.abrv.COURIER").data()); break; case MEDICAL: sprintf_s(desc, "%s %s", GetOrdinal(), Game::GetText("CombatGroup.abrv.MEDICAL").data()); break; case SUPPLY: sprintf_s(desc, "%s %s", GetOrdinal(), Game::GetText("CombatGroup.abrv.SUPPLY").data()); break; case REPAIR: sprintf_s(desc, "%s %s", GetOrdinal(), Game::GetText("CombatGroup.abrv.REPAIR").data()); break; case CIVILIAN: case WAR_PRODUCTION: case FACTORY: case REFINERY: case RESOURCE: strcpy_s(desc, (const char*) name); break; case INFRASTRUCTURE: case TRANSPORT: case NETWORK: case HABITAT: case STORAGE: case FREIGHT: case PASSENGER: case PRIVATE: strcpy_s(desc, (const char*) name); break; default: sprintf_s(desc, "%s", Game::GetText("CombatGroup.abrv.default").data()); break; } return desc; } // +--------------------------------------------------------------------+ double CombatGroup::GetNextJumpTime() const { double t = 0; ListIter unit = ((CombatGroup*) this)->units; while (++unit) if (unit->GetNextJumpTime() > t) t = unit->GetNextJumpTime(); return t; } // +--------------------------------------------------------------------+ void CombatGroup::MoveTo(const Point& loc) { location = loc; } // +--------------------------------------------------------------------+ void CombatGroup::SetAssignedSystem(const char* s) { assigned_system = s; assigned_zone = 0; zone_lock = false; ListIter iter = components; while (++iter) { CombatGroup* g = iter.value(); g->SetAssignedSystem(s); } } void CombatGroup::SetAssignedZone(CombatZone* z) { assigned_zone = z; if (!assigned_zone) zone_lock = false; ListIter iter = components; while (++iter) { CombatGroup* g = iter.value(); g->SetAssignedZone(z); } } void CombatGroup::ClearUnlockedZones() { if (!zone_lock) assigned_zone = 0; ListIter iter = components; while (++iter) { CombatGroup* g = iter.value(); g->ClearUnlockedZones(); } } void CombatGroup::SetZoneLock(bool lock) { if (!assigned_zone) zone_lock = false; else zone_lock = lock; if (zone_lock) assigned_system = Text(); ListIter iter = components; while (++iter) { CombatGroup* g = iter.value(); g->SetZoneLock(lock); } } // +--------------------------------------------------------------------+ void CombatGroup::SetIntelLevel(int n) { if (n < Intel::RESERVE || n > Intel::TRACKED) return; enemy_intel = n; // if this group has been discovered, the entire // branch of the OOB tree must be exposed. Otherwise, // no missions would ever be planned against this // combat group. if (n > Intel::SECRET) { CombatGroup* p = parent; while (p) { if (p->enemy_intel < Intel::KNOWN) p->enemy_intel = Intel::KNOWN; p = p->parent; } } } // +--------------------------------------------------------------------+ int CombatGroup::CalcValue() { int val = 0; ListIter unit = units; while (++unit) val += unit->GetValue(); ListIter comp = components; while (++comp) val += comp->CalcValue(); value = val; return value; } int CombatGroup::CountUnits() const { int n = 0; CombatGroup* g = (CombatGroup*) this; ListIter unit = g->units; while (++unit) n += unit->Count() - unit->DeadCount(); CombatGroup* pThis = ((CombatGroup*) this); pThis->live_comp.clear(); ListIter iter = g->components; while (++iter) { CombatGroup* comp = iter.value(); if (!comp->IsReserve()) { int unit_count = comp->CountUnits(); if (unit_count > 0) pThis->live_comp.append(comp); n += unit_count; } } return n; } // +--------------------------------------------------------------------+ void CombatGroup::ClearAssignments() { assignments.destroy(); ListIter comp = components; while (++comp) comp->ClearAssignments(); } // +--------------------------------------------------------------------+ CombatGroup* CombatGroup::FindCarrier() { CombatGroup* p = GetParent(); while (p != 0 && p->Type() != CombatGroup::CARRIER_GROUP && p->Type() != CombatGroup::STATION && p->Type() != CombatGroup::STARBASE) p = p->GetParent(); if (p && p->GetUnits().size()) return p; return 0; } CombatUnit* CombatGroup::GetRandomUnit() { CombatUnit* result = 0; List live; ListIter unit = units; while (++unit) { if (unit->Count() - unit->DeadCount() > 0) live.append(unit.value()); } if (live.size() > 0) { int ntries = 5; while (!result && ntries-- > 0) { int index = rand() % live.size(); result = live[index]; int ship_class = result->GetShipClass(); if (ship_class >= Ship::CRUISER && ship_class <= Ship::FARCASTER) result = 0; } } if (!result) { ListIter comp = components; while (++comp && !result) { CombatUnit* u = comp->GetRandomUnit(); if (u) result = u; } } return result; } CombatUnit* CombatGroup::GetFirstUnit() { int tmp_index = unit_index; unit_index = 0; CombatUnit* result = GetNextUnit(); unit_index = tmp_index; return result; } CombatUnit* CombatGroup::GetNextUnit() { if (units.size() > 0) { List live; ListIter unit = units; while (++unit) { if (unit->Count() - unit->DeadCount() > 0) live.append(unit.value()); } if (live.size() > 0) { return live[unit_index++ % live.size()]; } } if (components.size() > 0) { return components[unit_index % components.size()]->GetNextUnit(); } return 0; } CombatUnit* CombatGroup::FindUnit(const char* name) { if (units.size() > 0) { ListIter iter = units; while (++iter) { CombatUnit* unit = iter.value(); if (unit->Name() == name) { if (unit->Count() - unit->DeadCount() > 0) return unit; else return 0; } } } return 0; } void CombatGroup::AssignRegion(Text rgn) { region = rgn; ListIter comp = components; while (++comp) comp->AssignRegion(rgn); ListIter unit = units; while (++unit) unit->SetRegion(rgn); } // +--------------------------------------------------------------------+ static const char* group_name[] = { "", "force", "wing", "intercept_squadron", "fighter_squadron", "attack_squadron", "lca_squadron", "fleet", "destroyer_squadron", "battle_group", "carrier_group", "battalion", "minefield", "battery", "missile", "station", "starbase", "c3i", "comm_relay", "early_warning", "fwd_control_ctr", "ecm", "support", "courier", "medical", "supply", "repair", "civilian", "war_production", "factory", "refinery", "resource", "infrastructure", "transport", "network", "habitat", "storage", "non_com", "freight", "passenger", "private" }; // +--------------------------------------------------------------------+ int CombatGroup::TypeFromName(const char* type_name) { for (int i = FORCE; i < PRIVATE; i++) if (!_stricmp(type_name, group_name[i])) return i; return 0; } const char* CombatGroup::NameFromType(int type) { return group_name[type]; } // +--------------------------------------------------------------------+ int ShipClassFromName(const char* type_name) { return Ship::ClassForName(type_name); } // +--------------------------------------------------------------------+ #define GET_DEF_BOOL(n) if (pdef->name()->value()==(#n)) GetDefBool((n), pdef, filename) #define GET_DEF_TEXT(n) if (pdef->name()->value()==(#n)) GetDefText((n), pdef, filename) #define GET_DEF_NUM(n) if (pdef->name()->value()==(#n)) GetDefNumber((n), pdef, filename) #define GET_DEF_VEC(n) if (pdef->name()->value()==(#n)) GetDefVec((n), pdef, filename) CombatGroup* CombatGroup::LoadOrderOfBattle(const char* filename, int team, Combatant* combatant) { CombatGroup* force = 0; DataLoader* loader = DataLoader::GetLoader(); BYTE* block; loader->LoadBuffer(filename, block, true); Parser parser(new(__FILE__,__LINE__) BlockReader((const char*) block)); Term* term = parser.ParseTerm(); if (!term) { Print("ERROR: could not parse order of battle '%s'\n", filename); return 0; } else { TermText* file_type = term->isText(); if (!file_type || file_type->value() != "ORDER_OF_BATTLE") { Print("ERROR: invalid Order of Battle file '%s'\n", filename); term->print(10); return 0; } } do { delete term; term = 0; term = parser.ParseTerm(); if (term) { TermDef* def = term->isDef(); if (def) { if (def->name()->value() == "group") { if (!def->term() || !def->term()->isStruct()) { Print("WARNING: group struct missing in '%s'\n", filename); } else { TermStruct* val = def->term()->isStruct(); char name[256]; char type[64]; char intel[64]; char region[64]; char system[64]; char parent_type[64]; int parent_id = 0; int id = 0; int iff = -1; Vec3 loc = Vec3(1.0e9f,0.0f,0.0f); List unit_list; char unit_name[64]; char unit_regnum[16]; char unit_design[64]; char unit_skin[64]; int unit_class = 0; int unit_count = 1; int unit_dead = 0; int unit_damage = 0; int unit_heading= 0; int unit_index = 0; *name = 0; *type = 0; *intel = 0; *region = 0; *system = 0; *parent_type = 0; *unit_name = 0; *unit_regnum = 0; *unit_design = 0; *unit_skin = 0; strcpy_s(intel, "KNOWN"); // all groups in this OOB default to the IFF of the main force if (force) iff = force->GetIFF(); for (int i = 0; i < val->elements()->size(); i++) { TermDef* pdef = val->elements()->at(i)->isDef(); if (pdef && (iff < 0 || team < 0 || iff == team)) { GET_DEF_TEXT(name); else GET_DEF_TEXT(type); else GET_DEF_TEXT(intel); else GET_DEF_TEXT(region); else GET_DEF_TEXT(system); else GET_DEF_VEC(loc); else GET_DEF_TEXT(parent_type); else GET_DEF_NUM(parent_id); else GET_DEF_NUM(iff); else GET_DEF_NUM(id); else GET_DEF_NUM(unit_index); else if ((iff == team || team < 0) && pdef->name()->value() == "unit") { if (!pdef->term() || !pdef->term()->isStruct()) { Print("WARNING: unit struct missing for group '%s' in '%s'\n", name, filename); } else { TermStruct* val = pdef->term()->isStruct(); char unit_region[64]; char design[256]; Vec3 unit_loc = Vec3(1.0e9f,0.0f,0.0f); unit_count = 1; ZeroMemory(unit_region, sizeof(unit_region)); ZeroMemory(design, sizeof(design)); for (int i = 0; i < val->elements()->size(); i++) { TermDef* pdef = val->elements()->at(i)->isDef(); if (pdef) { if (pdef->name()->value() == "name") { GetDefText(unit_name, pdef, filename); } else if (pdef->name()->value() == "regnum") { GetDefText(unit_regnum, pdef, filename); } else if (pdef->name()->value() == "region") { GetDefText(unit_region, pdef, filename); } else if (pdef->name()->value() == "loc") { GetDefVec(unit_loc, pdef, filename); } else if (pdef->name()->value() == "type") { char typestr[32]; GetDefText(typestr, pdef, filename); unit_class = ShipDesign::ClassForName(typestr); } else if (pdef->name()->value() == "design") { GetDefText(unit_design, pdef, filename); } else if (pdef->name()->value() == "skin") { GetDefText(unit_skin, pdef, filename); } else if (pdef->name()->value() == "count") { GetDefNumber(unit_count, pdef, filename); } else if (pdef->name()->value() == "dead_count") { GetDefNumber(unit_dead, pdef, filename); } else if (pdef->name()->value() == "damage") { GetDefNumber(unit_damage, pdef, filename); } else if (pdef->name()->value() == "heading") { GetDefNumber(unit_heading, pdef, filename); } } } if (!ShipDesign::CheckName(unit_design)) { Print("ERROR: invalid design '%s' for unit '%s' in '%s'\n", unit_design, unit_name, filename); return 0; } CombatUnit* cu = new(__FILE__,__LINE__) CombatUnit(unit_name, unit_regnum, unit_class, unit_design, unit_count, iff); cu->SetRegion(unit_region); cu->SetSkin(unit_skin); cu->MoveTo(unit_loc); cu->Kill(unit_dead); cu->SetSustainedDamage(unit_damage); cu->SetHeading(unit_heading * DEGREES); unit_list.append(cu); } } } } // elements if (iff >= 0 && (iff == team || team < 0)) { CombatGroup* parent_group = 0; if (force) { parent_group = force->FindGroup(TypeFromName(parent_type), parent_id); } CombatGroup* g = new(__FILE__,__LINE__) CombatGroup(TypeFromName(type), id, name, iff, Intel::IntelFromName(intel), parent_group); g->region = region; g->combatant = combatant; g->unit_index = unit_index; if (loc.x >= 1e9) { if (parent_group) g->location = parent_group->location; else g->location = Vec3(0,0,0); } else { g->location = loc; } if (unit_list.size()) { unit_list[0]->SetLeader(true); ListIter u = unit_list; while (++u) { u->SetCombatGroup(g); if (u->GetRegion().length() < 1) { u->SetRegion(g->GetRegion()); u->MoveTo(g->Location()); } if (parent_group && (u->Type() == Ship::FIGHTER || u->Type() == Ship::ATTACK)) { CombatUnit* carrier = 0; CombatGroup* p = parent_group; while (p && !carrier) { if (p->units.size() && p->units[0]->Type() == Ship::CARRIER) { carrier = p->units[0]; u->SetCarrier(carrier); u->SetRegion(carrier->GetRegion()); } p = p->parent; } } } g->units.append(unit_list); } if (!force) force = g; } // iff == team? } // group-struct } // group } // def } // term } while (term); loader->ReleaseBuffer(block); Print("Order of Battle Loaded (%s).\n", force ? force->Name().data() : "unknown force"); if (force) force->CalcValue(); return force; } // +--------------------------------------------------------------------+ void CombatGroup::MergeOrderOfBattle(BYTE* block, const char* filename, int team, Combatant* combatant, Campaign* campaign) { CombatGroup* force = 0; Parser parser(new(__FILE__,__LINE__) BlockReader((const char*) block)); Term* term = parser.ParseTerm(); if (!term) { Print("ERROR: could not parse order of battle '%s'\n", filename); return; } else { TermText* file_type = term->isText(); if (!file_type || file_type->value() != "SAVEGAME") { Print("ERROR: invalid Save Game file '%s'\n", filename); term->print(10); return; } } do { delete term; term = 0; term = parser.ParseTerm(); if (term) { TermDef* def = term->isDef(); if (def) { if (def->name()->value() == "group") { if (!def->term() || !def->term()->isStruct()) { Print("WARNING: group struct missing in '%s'\n", filename); } else { TermStruct* val = def->term()->isStruct(); char name[256]; char type[64]; char intel[64]; char region[64]; char system[64]; char zone[64]; bool zone_locked = false; int id = 0; int iff = -1; int sorties = -1; int kills = -1; int points = -1; Vec3 loc = Vec3(1.0e9f,0.0f,0.0f); List unit_list; char unit_name[64]; char unit_regnum[16]; char unit_design[64]; int unit_class = 0; int unit_count = 1; int unit_dead = 0; int unit_damage = 0; int unit_heading= 0; int unit_index = 0; *name = 0; *type = 0; *intel = 0; *region = 0; *system = 0; *zone = 0; *unit_name = 0; *unit_regnum = 0; *unit_design = 0; strcpy_s(intel, "KNOWN"); // all groups in this OOB default to the IFF of the main force if (force) iff = force->GetIFF(); for (int i = 0; i < val->elements()->size(); i++) { TermDef* pdef = val->elements()->at(i)->isDef(); if (pdef && (iff < 0 || team < 0 || iff == team)) { GET_DEF_TEXT(name); else GET_DEF_TEXT(type); else GET_DEF_TEXT(intel); else GET_DEF_TEXT(region); else GET_DEF_TEXT(system); else GET_DEF_TEXT(zone); else GET_DEF_BOOL(zone_locked); else GET_DEF_VEC(loc); else GET_DEF_NUM(iff); else GET_DEF_NUM(id); else GET_DEF_NUM(sorties); else GET_DEF_NUM(kills); else GET_DEF_NUM(points); else GET_DEF_NUM(unit_index); else if ((iff == team || team < 0) && pdef->name()->value() == "unit") { if (!pdef->term() || !pdef->term()->isStruct()) { Print("WARNING: unit struct missing for group '%s' in '%s'\n", name, filename); } else { TermStruct* val = pdef->term()->isStruct(); char unit_region[64]; char design[256]; Vec3 unit_loc=Vec3(0.0f,0.0f,0.0f); unit_count = 1; ZeroMemory(unit_region, sizeof(unit_region)); ZeroMemory(design, sizeof(design)); for (int i = 0; i < val->elements()->size(); i++) { TermDef* pdef = val->elements()->at(i)->isDef(); if (pdef) { if (pdef->name()->value() == "name") { GetDefText(unit_name, pdef, filename); } else if (pdef->name()->value() == "regnum") { GetDefText(unit_regnum, pdef, filename); } else if (pdef->name()->value() == "region") { GetDefText(unit_region, pdef, filename); } else if (pdef->name()->value() == "loc") { GetDefVec(unit_loc, pdef, filename); } else if (pdef->name()->value() == "type") { char typestr[32]; GetDefText(typestr, pdef, filename); unit_class = ShipDesign::ClassForName(typestr); } else if (pdef->name()->value() == "design") { GetDefText(unit_design, pdef, filename); } else if (pdef->name()->value() == "count") { GetDefNumber(unit_count, pdef, filename); } else if (pdef->name()->value() == "dead_count") { GetDefNumber(unit_dead, pdef, filename); } else if (pdef->name()->value() == "damage") { GetDefNumber(unit_damage, pdef, filename); } else if (pdef->name()->value() == "heading") { GetDefNumber(unit_heading, pdef, filename); } } } if (!ShipDesign::CheckName(unit_design)) { Print("ERROR: invalid design '%s' for unit '%s' in '%s'\n", unit_design, unit_name, filename); return; } if (force) { CombatUnit* cu = new(__FILE__,__LINE__) CombatUnit(unit_name, unit_regnum, unit_class, unit_design, unit_count, iff); cu->SetRegion(unit_region); cu->MoveTo(unit_loc); cu->Kill(unit_dead); cu->SetSustainedDamage(unit_damage); cu->SetHeading(unit_heading * DEGREES); unit_list.append(cu); } } } } } // elements if (iff >= 0 && (iff == team || team < 0)) { // have we found the force group we are looking for yet? if (!force && !_stricmp(name, combatant->Name())) { force = combatant->GetForce(); } else { if (!force) continue; // if we already have a force, and we find a second one, // it must be the start of a different combatant. // So don't process any further: if (TypeFromName(type) == CombatGroup::FORCE) { break; } } CombatGroup* g = force->FindGroup(TypeFromName(type), id); if (!g) { ::Print("WARNING: unexpected combat group %s %d '%s' in '%s'\n", type, id, name, filename); continue; } g->region = region; g->combatant = combatant; g->location = loc; g->enemy_intel = Intel::IntelFromName(intel); g->unit_index = unit_index; if (*zone) { CombatZone* combat_zone = campaign->GetZone(zone); if (combat_zone) { g->SetAssignedZone(combat_zone); g->SetZoneLock(zone_locked); } else { ::Print("WARNING: could not find combat zone '%s' for group %s %d '%s' in '%s'\n", zone, type, id, name, filename); } } else if (*system) { g->SetAssignedSystem(system); } if (sorties >= 0) g->SetSorties(sorties); if (kills >= 0) g->SetKills(kills); if (points >= 0) g->SetPoints(points); if (unit_list.size()) { ListIter u_iter = unit_list; while (++u_iter) { CombatUnit* load_unit = u_iter.value(); CombatUnit* u = g->FindUnit(load_unit->Name()); if (u) { if (load_unit->GetRegion().length() > 0) { u->SetRegion(load_unit->GetRegion()); u->MoveTo(load_unit->Location()); } else { u->SetRegion(g->GetRegion()); u->MoveTo(g->Location()); } u->SetDeadCount(load_unit->DeadCount()); u->SetSustainedDamage(load_unit->GetSustainedDamage()); u->SetHeading(load_unit->GetHeading()); } } unit_list.destroy(); } if (!force) force = g; } // iff == team? } // group-struct } // group } // def } // term } while (term); Print("Order of Battle Loaded (%s).\n", force ? force->Name().data() : "unknown force"); if (force) force->CalcValue(); } // +--------------------------------------------------------------------+ Text FormatNumber(double n) { char buffer[64]; if (fabs(n) < 1000) sprintf_s(buffer, "%d", (int) n); else if (fabs(n) < 1e6) { int nn = (int) n / 1000; sprintf_s(buffer, "%de3", nn); } else sprintf_s(buffer, "%g", n); return buffer; } void SaveCombatUnit(FILE* f, CombatUnit* u) { int type = u->Type(); if (type == 0 && u->GetDesign()) type = u->GetDesign()->type; fprintf(f, "\n unit: {"); fprintf(f, " name: \"%s\",", u->Name().data()); fprintf(f, " type: \"%s\",", Ship::ClassName(type)); fprintf(f, " design: \"%s\",", u->DesignName().data()); if (u->Count() > 1) { fprintf(f, " count: %d,", u->Count()); } else { fprintf(f, " regnum:\"%s\",", u->Registry().data()); } if (u->GetRegion().length() > 0) { fprintf(f, " region:\"%s\",", u->GetRegion().data()); Text x = FormatNumber(u->Location().x); Text y = FormatNumber(u->Location().y); Text z = FormatNumber(u->Location().z); fprintf(f, " loc:(%s, %s, %s),", x.data(), y.data(), z.data()); } fprintf(f, " dead_count: %d, damage: %d, heading: %d },", (int) u->DeadCount(), (int) u->GetSustainedDamage(), (int) (u->GetHeading() / DEGREES)); } void SaveCombatGroup(FILE* f, CombatGroup* g) { fprintf(f, "group: {"); fprintf(f, " type: %s,", CombatGroup::NameFromType(g->Type())); fprintf(f, " id: %d,", g->GetID()); fprintf(f, " name: \"%s\",", g->Name().data()); fprintf(f, " intel: %s,", Intel::NameFromIntel(g->IntelLevel())); fprintf(f, " iff: %d,", g->GetIFF()); fprintf(f, " unit_index: %d,", g->UnitIndex()); if (g->GetRegion().length()) { fprintf(f, " region:\"%s\",", g->GetRegion().data()); } if (g->GetAssignedSystem().length()) { fprintf(f, " system: \"%s\",", g->GetAssignedSystem().data()); } if (g->GetAssignedZone()) { fprintf(f, " zone: \"%s\",", g->GetAssignedZone()->Name().data()); if (g->IsZoneLocked()) { fprintf(f, " zone_locked: true,"); } } Text x = FormatNumber(g->Location().x); Text y = FormatNumber(g->Location().y); Text z = FormatNumber(g->Location().z); fprintf(f, " loc: (%s, %s, %s),", x.data(), y.data(), z.data()); CombatGroup* parent = g->GetParent(); if (parent) { fprintf(f, " parent_type:%s,", CombatGroup::NameFromType(parent->Type())); fprintf(f, " parent_id:%d,", parent->GetID()); } fprintf(f, " sorties: %d,", g->Sorties()); fprintf(f, " kills: %d,", g->Kills()); fprintf(f, " points: %d,", g->Points()); ListIter u = g->GetUnits(); while (++u) { SaveCombatUnit(f, u.value()); } fprintf(f, " }\n"); ListIter c = g->GetComponents(); while (++c) { SaveCombatGroup(f, c.value()); } } void CombatGroup::SaveOrderOfBattle(const char* filename, CombatGroup* force) { FILE* f; ::fopen_s(&f, filename, "a+"); if (f) { SaveCombatGroup(f, force); fprintf(f, "\n"); fclose(f); } }