diff options
Diffstat (limited to 'Stars45/CombatGroup.cpp')
-rw-r--r-- | Stars45/CombatGroup.cpp | 1615 |
1 files changed, 1615 insertions, 0 deletions
diff --git a/Stars45/CombatGroup.cpp b/Stars45/CombatGroup.cpp new file mode 100644 index 0000000..a8cf988 --- /dev/null +++ b/Stars45/CombatGroup.cpp @@ -0,0 +1,1615 @@ +/* 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<CombatGroup> 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<CombatUnit> 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<CombatGroup> 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<CombatGroup> iter = components; + while (++iter) { + CombatGroup* g = iter.value(); + g->SetAssignedZone(z); + } +} + +void +CombatGroup::ClearUnlockedZones() +{ + if (!zone_lock) + assigned_zone = 0; + + ListIter<CombatGroup> 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<CombatGroup> 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<CombatUnit> unit = units; + while (++unit) + val += unit->GetValue(); + + ListIter<CombatGroup> comp = components; + while (++comp) + val += comp->CalcValue(); + + value = val; + return value; +} + +int +CombatGroup::CountUnits() const +{ + int n = 0; + + CombatGroup* g = (CombatGroup*) this; + + ListIter<CombatUnit> unit = g->units; + while (++unit) + n += unit->Count() - unit->DeadCount(); + + CombatGroup* pThis = ((CombatGroup*) this); + pThis->live_comp.clear(); + ListIter<CombatGroup> 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<CombatGroup> 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<CombatUnit> live; + + ListIter<CombatUnit> 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<CombatGroup> 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<CombatUnit> live; + + ListIter<CombatUnit> 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<CombatUnit> 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<CombatGroup> comp = components; + while (++comp) + comp->AssignRegion(rgn); + + ListIter<CombatUnit> 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<CombatUnit> 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<CombatUnit> 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<CombatUnit> 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<CombatUnit> 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<CombatUnit> u = g->GetUnits(); + while (++u) { + SaveCombatUnit(f, u.value()); + } + + fprintf(f, " }\n"); + + ListIter<CombatGroup> 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); + } +} |