Ahli / sc2xml

11 stars 1 forks source link

Having lots of units on a map causes issues #91

Open Ahli opened 1 year ago

Ahli commented 1 year ago

Having too many units present on the map breaks/bugs the game. E.g. this happens when players do the 1vs12AI challenge with only Zerg AIs. video

The Issue

The issue is the ability limit. Testing in the editor also causes the game to show warnings about it being reached. It looks like the maximum amount of abilities on alive units is 16383. In the following comment, I have created an overview how many units of each type can be created. The problems surfaces fastest when a lot of Zerg players are in a match, so that race probably needs optimizations, if possible. Units that can morph are reserving ability slots for the units they can potentially morph into. This is the primary reason why so many slots are used. The update that altered the Baneling Morph made this limit a bigger issue in some team games already, so this problem should be addressed!

Fix Steps:

Ahli commented 1 year ago

I have created a map to test the limits. It creates as many units of a type until none can be created anymore. Broodlords and Carriers contain their full un-upgraded magazine unit count.

These are the results based on VoidMulti:

UNIT - MAX COUNT ALIVE:
Drone - 2048
DroneBurrowed - 2048
Overlord - 1638
OverlordTransport - 1638
TransportOverlordCocoon - 1638
Overseer - 1638
OverseerSiegeMode - 2730
OverlordCocoon - 1638
Zergling - 1260
ZerglingBurrowed - 1260
Baneling - 1638
BanelingBurrowed - 2048
BanelingCocoon - 1638
BroodLordCocoon - 2340
BroodLord - 868
BroodlingEscort - 5461
Broodling - 5461
Changeling - 8192
ChangelingMarineShield - 4096
ChangelingMarine - 4096
ChangelingZealot - 2730
ChangelingZerglingWings - 1260
ChangelingZergling - 1260
Corruptor - 2340
Egg - 8192
Hydralisk - 1365
HydraliskBurrowed - 1365
Infestor - 2048
InfestorBurrowed - 2048
Larva - 5461
LurkerMP - 1365
LurkerMPBurrowed - 2340
LurkerMPEgg - 1365
Mutalisk - 5461
Queen - 2048
QueenBurrowed - 2048
Ravager - 1638
RavagerBurrowed - 2730
RavagerCocoon - 1638
Roach - 1638
RoachBurrowed - 1638
SwarmHostMP - 3276
SwarmHostBurrowedMP - 3276
LocustMP - 2340
LocustMPFlying - 2340
LocustMPPrecursor - 16383
Ultralisk - 2730
UltraliskBurrowed - 2730
Viper - 2730
SpineCrawlerUprooted - 2730
SpineCrawler - 2730
SporeCrawlerUprooted - 2730
SporeCrawler - 2730
CreepTumorBurrowed - 5461
CreepTumorQueen - 5461
CreepTumor - 5461
Hatchery - 2340
Lair - 2340
Hive - 2730
SpawningPool - 5461
EvolutionChamber - 5461
BanelingNest - 5461
RoachWarren - 5461
HydraliskDen - 5461
Spire - 4096
GreaterSpire - 4096
UltraliskCavern - 5461
InfestationPit - 5461
LurkerDenMP - 5461
NydusCanal - 4096
NydusNetwork - 3276
SCV - 2340
MULE - 4096
Marine - 4096
Marauder - 4096
Reaper - 4096
KD8Charge - 16383
Ghost - 1820
GhostAlternate - 1638
GhostNova - 1638
Hellion - 3276
HellionTank - 3276
SiegeTank - 3276
SiegeTankSieged - 3276
ThorAP - 3276
Thor - 3276
WidowMine - 3276
WidowMineBurrowed - 3276
Cyclone - 3276
VikingFighter - 3276
VikingAssault - 3276
Medivac - 3276
Raven - 3276
AutoTurret - 5461
Liberator - 2340
LiberatorAG - 2340
Battlecruiser - 2730
Banshee - 4096
Armory - 5461
Barracks - 1820
BarracksFlying - 1820
Bunker - 1820
CommandCenter - 910
CommandCenterFlying - 910
OrbitalCommand - 910
OrbitalCommandFlying - 1489
PlanetaryFortress - 910
EngineeringBay - 5461
Factory - 1820
FactoryFlying - 1820
FusionCore - 5461
GhostAcademy - 4096
MissileTurret - 5461
BarracksReactor - 3276
FactoryReactor - 3276
StarportReactor - 3276
SensorTower - 8389
Starport - 1820
StarportFlying - 1820
SupplyDepot - 5461
SupplyDepotLowered - 5461
BarracksTechLab - 1820
FactoryTechLab - 1820
StarportTechLab - 1820
Probe - 2730
Adept - 2340
AdeptPhaseShift - 2048
Archon - 3276
Carrier - 406
Colossus - 5461
DarkTemplar - 2340
Disruptor - 4096
DisruptorPhased - 2730
HighTemplar - 1820
Immortal - 5461
Interceptor - 5461
Mothership - 3276
Observer - 4096
ObserverSiegeMode - 4096
Oracle - 2340
OracleStasisTrap - 8192
Phoenix - 4096
Sentry - 780
Stalker - 2730
Tempest - 4096
VoidRay - 3276
WarpPrismPhasing - 2340
WarpPrism - 2340
Zealot - 2730
CyberneticsCore - 5461
DarkShrine - 5461
FleetBeacon - 5461
ForceField - 16383
Forge - 5461
Gateway - 2340
WarpGate - 2340
Nexus - 1638
PhotonCannon - 5461
Pylon - 3276
RoboticsBay - 5461
RoboticsFacility - 4096
ShieldBattery - 8192
Stargate - 4096
TemplarArchive - 5461
TwilightCouncil - 5461
Extractor - 8505 (this is a bit random despite creating only 1 unit per game loop already)
ExtractorRich - 8516 (this is a bit random despite creating only 1 unit per game loop already)
Refinery - 5552 (this is a bit random despite creating only 1 unit per game loop already)
RefineryRich - 5557 (this is a bit random despite creating only 1 unit per game loop already)
Assimilator - 8524 (this is a bit random despite creating only 1 unit per game loop already)
AssimilatorRich - 8530 (this is a bit random despite creating only 1 unit per game loop already)

Before patch 5.0.11 altered Zergling->Baneling morph implementation, these limits applied:

patch 5.0.10 - Zergling - 2048 (vs patch 5.0.11 - 1260)
patch 5.0.10 - ZerglingBurrowed - 2048 (vs patch 5.0.11 - 1260)
patch 5.0.10 - Baneling - 2048 (vs patch 5.0.11 - 1638)
patch 5.0.10 - BanelingBurrowed - 2048 (vs patch 5.0.11 - 2048)
patch 5.0.10 - BanelingCocoon - 8192 (vs patch 5.0.11 - 1638)
Ahli commented 1 year ago

There are a few optimizations possible:

TODO

other TODO:

Ahli commented 1 year ago

current changes:

Ahli commented 1 year ago

re-implementation of Sentry's hallucination using train ability + dummy unit:

    <CUnit id="Sentry">
        <AbilArray index="7" Link="Hallucination"/>
        <AbilArray index="8" removed="1"/>
        <AbilArray index="9" removed="1"/>
        <AbilArray index="10" removed="1"/>
        <AbilArray index="11" removed="1"/>
        <AbilArray index="12" removed="1"/>
        <AbilArray index="13" removed="1"/>
        <AbilArray index="14" removed="1"/>
        <AbilArray index="15" removed="1"/>
        <AbilArray index="16" removed="1"/>
        <AbilArray index="17" removed="1"/>
        <AbilArray index="18" removed="1"/>
        <AbilArray index="19" removed="1"/>
        <AbilArray index="20" removed="1"/>
        <CardLayouts index="1">
            <LayoutButtons index="0" AbilCmd="Hallucination,Train1"/>
            <LayoutButtons index="1" AbilCmd="Hallucination,Train2"/>
            <LayoutButtons index="2" AbilCmd="Hallucination,Train4"/>
            <LayoutButtons index="3" AbilCmd="Hallucination,Train5"/>
            <LayoutButtons index="4" AbilCmd="Hallucination,Train6"/>
            <LayoutButtons index="5" AbilCmd="Hallucination,Train7"/>
            <LayoutButtons index="6" AbilCmd="Hallucination,Train8"/>
            <LayoutButtons index="7" AbilCmd="Hallucination,Train9"/>
            <LayoutButtons index="8" AbilCmd="Hallucination,Train10"/>
            <LayoutButtons index="9" AbilCmd="Hallucination,Train12"/>
            <LayoutButtons index="11" AbilCmd="Hallucination,Train11"/>
            <LayoutButtons index="12" AbilCmd="Hallucination,Train3"/>
            <LayoutButtons index="13" AbilCmd="Hallucination,Train13"/>
        </CardLayouts>
    </CUnit>
    <CUnit id="HallucinationDummy">
        <Race value="Prot"/>
        <FlagArray index="Unselectable" value="1"/>
        <FlagArray index="Untargetable" value="1"/>
        <FlagArray index="Invulnerable" value="1"/>
        <FlagArray index="Unstoppable" value="1"/>
        <EffectArray index="Create" value="SuicideRemove"/>
        <SeparationRadius value="0"/>
        <EditorCategories value="ObjectFamily:Melee,ObjectType:Spell"/>
    </CUnit>

    <CActorUnit id="HallucinationDummy" parent="GenericUnitStandardNoAutoSoundLinks" unitName="HallucinationDummy">
        <Model value="Invisible"/>
    </CActorUnit>

    <CAbilTrain id="Hallucination">
        <EditorCategories value="AbilityorEffectType:Units"/>
        <Flags index="IgnoreUnitCost" value="1"/>
        <Flags index="WaitForFood" value="0"/>
        <Range value="500"/>
        <InfoArray index="Train1" Effect="HallucinationCreateProbe">
            <Vital index="Energy" value="75"/>
            <Button DefaultButtonFace="ProbeHallucination"/>
            <Unit value="HallucinationDummy"/>
        </InfoArray>
        <InfoArray index="Train2" Effect="HallucinationCreateZealot">
            <Vital index="Energy" value="75"/>
            <Button DefaultButtonFace="ZealotHallucination"/>
            <Unit value="HallucinationDummy"/>
        </InfoArray>
        <InfoArray index="Train3" Effect="HallucinationCreateAdept">
            <Vital index="Energy" value="75"/>
            <Button DefaultButtonFace="AdeptHallucination"/>
            <Unit value="HallucinationDummy"/>
        </InfoArray>
        <InfoArray index="Train4" Effect="HallucinationCreateStalker">
            <Vital index="Energy" value="75"/>
            <Button DefaultButtonFace="StalkerHallucination"/>
            <Unit value="HallucinationDummy"/>
        </InfoArray>
        <InfoArray index="Train5" Effect="HallucinationCreateImmortal">
            <Vital index="Energy" value="75"/>
            <Button DefaultButtonFace="ImmortalHallucination"/>
            <Unit value="HallucinationDummy"/>
        </InfoArray>
        <InfoArray index="Train6" Effect="HallucinationCreateHighTemplar">
            <Vital index="Energy" value="75"/>
            <Button DefaultButtonFace="HighTemplarHallucination"/>
            <Unit value="HallucinationDummy"/>
        </InfoArray>
        <InfoArray index="Train7" Effect="HallucinationCreateArchon">
            <Vital index="Energy" value="75"/>
            <Button DefaultButtonFace="ArchonHallucination"/>
            <Unit value="HallucinationDummy"/>
        </InfoArray>
        <InfoArray index="Train8" Effect="HallucinationCreateVoidRay">
            <Vital index="Energy" value="75"/>
            <Button DefaultButtonFace="VoidRayHallucination"/>
            <Unit value="HallucinationDummy"/>
        </InfoArray>
        <InfoArray index="Train9" Effect="HallucinationCreatePhoenix">
            <Vital index="Energy" value="75"/>
            <Button DefaultButtonFace="PhoenixHallucination"/>
            <Unit value="HallucinationDummy"/>
        </InfoArray>
        <InfoArray index="Train10" Effect="HallucinationCreateWarpPrism">
            <Vital index="Energy" value="75"/>
            <Button DefaultButtonFace="WarpPrismHallucination"/>
            <Unit value="HallucinationDummy"/>
        </InfoArray>
        <InfoArray index="Train11" Effect="HallucinationCreateOracle">
            <Vital index="Energy" value="75"/>
            <Button DefaultButtonFace="OracleHallucination"/>
            <Unit value="HallucinationDummy"/>
        </InfoArray>
        <InfoArray index="Train12" Effect="HallucinationCreateColossus">
            <Vital index="Energy" value="75"/>
            <Button DefaultButtonFace="ColossusHallucination"/>
            <Unit value="HallucinationDummy"/>
        </InfoArray>
        <InfoArray index="Train13" Effect="HallucinationCreateDisruptor">
            <Vital index="Energy" value="75"/>
            <Button DefaultButtonFace="DisruptorHallucination"/>
            <Unit value="HallucinationDummy"/>
        </InfoArray>
    </CAbilTrain>

requirementsAi.galaxy and tactProtAi.galaxy would need to be edited in VoidMulti as well in order to make the AI able to use Hallucinations

const string c_AB_Hallucinate               = "Hallucination";
static bool Hallucinate (int player, unit aiUnit, unitgroup scanGroup) {
    point here;
    order ord;
    int onGround;
    unitgroup targetGroupGround;

    if (AIIsCampaign(player)) {
        return false;
    }

    // wait until fighting 4+ enemy units
    //
    if (!AIIsAttackOrder(UnitOrder(aiUnit, 0))) {
        return false;
    }
    if (UnitGroupCount(scanGroup, c_unitCountAlive) < 4) {
        return false;
    }

    // Save at least 70 energy for other spells, unless we're about to die
    if (UnitGetPropertyInt(aiUnit, c_unitPropEnergy, c_unitPropCurrent) <= 170) {
        if (UnitGetPropertyInt(aiUnit, c_unitPropVitalityPercent, c_unitPropCurrent) > 25) {
            return false;
        }
    }

    ord = AICreateOrder(player, c_AB_Hallucinate, 7);
    if (!UnitOrderIsValid(aiUnit, ord)) {
        return false;
    }

    here = UnitGetPosition(aiUnit);
    if (AINearbyUnits(player, c_PU_VoidRay, here, 10, 1)) {
        AICast(aiUnit, ord, c_noMarker, c_castHold);
        return true;
    }
    if (AINearbyUnits(player, c_PU_Colossus, here, 10, 1)) {
        ord = AICreateOrder(player, c_AB_Hallucinate, 11);
        if (UnitOrderIsValid(aiUnit, ord)) {
            AICast(aiUnit, ord, c_noMarker, c_castHold);
            return true;
        }
    }
    if (AINearbyUnits(player, c_PU_Archon, here, 10, 1)) {
        ord = AICreateOrder(player, c_AB_Hallucinate, 6);
        if (UnitOrderIsValid(aiUnit, ord)) {
            AICast(aiUnit, ord, c_noMarker, c_castHold);
            return true;
        }
    }
    if (AINearbyUnits(player, c_PU_Immortal, here, 10, 1)) {
        targetGroupGround = UnitGroupFilterPlane(scanGroup, c_planeGround, 0);
        onGround = UnitGroupCount(targetGroupGround, c_unitCountAll);
        if (onGround > 1) {
            ord = AICreateOrder(player, c_AB_Hallucinate, 4);
            if (UnitOrderIsValid(aiUnit, ord)) {
                AICast(aiUnit, ord, c_noMarker, c_castHold);
                return true;
            }
        }
    }

    ord = AICreateOrder(player, c_AB_Hallucinate, 3);
    if (UnitOrderIsValid(aiUnit, ord)) {
        AICast(aiUnit, ord, c_noMarker, c_castHold);
        return true;
    }

    return false;
}

TODO dummy unit should be flagged as Never Think

Ahli commented 1 year ago

This replaces Battlecruiser's Tactical Jump's precursor from a fully fledged BC to a dummy BC without abilities. This causes the ability to require 0 additional ability slots to function. The current BC prevents the teleportation from happening.

    <CEffectCreateUnit id="HyperjumpCreatePrecursor">
        <SpawnUnit index="0" value="BattlecruiserPrecursor"/>
    </CEffectCreateUnit>

    <CUnit id="BattlecruiserPrecursor" parent="Battlecruiser">
        <Name value="Unit/Name/BattlecruiserPrecursor"/>
        <Description value="Button/Tooltip/BattlecruiserPrecursor"/>
        <ReviveType value="BattlecruiserPrecursor"/>
        <Race value="Neut"/>
        <FlagArray index="Unselectable" value="1"/>
        <FlagArray index="Untargetable" value="1"/>
        <FlagArray index="Uncursorable" value="1"/>
        <FlagArray index="PreventDestroy" value="0"/>
        <FlagArray index="UseLineOfSight" value="0"/>
        <FlagArray index="KillCredit" value="0"/>
        <FlagArray index="NoScore" value="1"/>
        <FlagArray index="ArmySelect" value="0"/>
        <EditorFlags index="NoPlacement" value="1"/>
        <EditorFlags index="BlockStructures" value="0"/>
        <Attributes index="Armored" value="0"/>
        <Attributes index="Mechanical" value="0"/>
        <Attributes index="Massive" value="0"/>
        <LifeArmor value="0"/>
        <Speed value="0"/>
        <Acceleration value="0"/>
        <Sight value="0"/>
        <Height value="0"/>
        <VisionHeight value="0"/>
        <Food value="0"/>
        <CostCategory value="None"/>
        <CostResource index="Minerals" value="0"/>
        <CostResource index="Vespene" value="0"/>
        <AttackTargetPriority value="0"/>
        <DamageDealtXP value="0"/>
        <DamageTakenXP value="0"/>
        <KillXP value="0"/>
        <AbilArray index="0" removed="1"/>
        <AbilArray index="1" removed="1"/>
        <AbilArray index="2" removed="1"/>
        <AbilArray index="3" removed="1"/>
        <AbilArray index="4" removed="1"/>
        <AbilArray index="5" removed="1"/>
        <WeaponArray index="0" removed="1"/>
        <CardLayouts index="0" removed="1"/>
        <ScoreMake value="0"/>
        <ScoreKill value="0"/>
        <ScoreResult value=""/>
        <SubgroupPriority value="0"/>
        <EditorCategories value=""/>
        <TacticalAI value="BattlecruiserPrecursor"/>
        <TacticalAIThink value=""/>
        <AIEvalFactor value="1"/>
        <LeaderAlias value="BattlecruiserPrecursor"/>
        <HotkeyAlias value="BattlecruiserPrecursor"/>
        <SelectAlias value="BattlecruiserPrecursor"/>
        <SubgroupAlias value="BattlecruiserPrecursor"/>
        <TechAliasArray index="0" removed="1"/>
        <EquipmentArray index="0" removed="1"/>
        <EquipmentArray index="1" removed="1"/>
        <GlossaryCategory value=""/>
        <GlossaryPriority value="0"/>
        <GlossaryStrongArray index="0" removed="1"/>
        <GlossaryStrongArray index="1" removed="1"/>
        <GlossaryStrongArray index="2" removed="1"/>
        <GlossaryWeakArray index="0" removed="1"/>
        <GlossaryWeakArray index="1" removed="1"/>
        <GlossaryWeakArray index="2" removed="1"/>
        <HotkeyCategory value=""/>
        <AIEvaluateAlias value="BattlecruiserPrecursor"/>
    </CUnit>

Update: The BC teleport probably does not need a precursor. I did not notice any change after removing it. The teleport's placement usage is probably making the precursor entirely pointless. At least I did not notice any difference with and without it.

    <CEffectCreatePersistent id="HyperjumpInitialCP">
        <PeriodicEffectArray index="0" value="HyperjumpTeleportOutAB"/>
    </CEffectCreatePersistent>
    <CEffectTeleport id="HyperjumpTeleport">
        <PlacementAround Effect="HyperjumpInitialCP"/>
        <TargetLocation Effect="HyperjumpInitialCP"/>
        <!--<TeleportFlags index="TestZone" value="1"/> fix for teleporting outside map boundaries -->
    </CEffectTeleport>