diff --git a/bwapi/BWAPI/Source/BWAPI/Command.h b/bwapi/BWAPI/Source/BWAPI/Command.h index 73903d54f..38ed1484f 100755 --- a/bwapi/BWAPI/Source/BWAPI/Command.h +++ b/bwapi/BWAPI/Source/BWAPI/Command.h @@ -5,9 +5,5 @@ namespace BWAPI { - class Command : public CommandTemp - { - public: - Command(const UnitCommand& command) : CommandTemp(command) {} - }; + using Command = CommandTemp; } \ No newline at end of file diff --git a/bwapi/BWAPI/Source/BWAPI/CommandTemp.h b/bwapi/BWAPI/Source/BWAPI/CommandTemp.h index baf98583f..46ba3da9a 100755 --- a/bwapi/BWAPI/Source/BWAPI/CommandTemp.h +++ b/bwapi/BWAPI/Source/BWAPI/CommandTemp.h @@ -9,715 +9,980 @@ namespace BWAPI { - template - class CommandTemp - { - public : - CommandTemp(const UnitCommand& command); - void execute(int frame); - protected: - virtual ~CommandTemp() {}; - private : - static int getUnitID(Unit unit); - UnitCommand command; - int savedExtra = -1; - int savedExtra2 = -1; - PlayerImpl* player = nullptr; - }; - template - CommandTemp::CommandTemp(const UnitCommand& command) : command(command) - {} - template - int CommandTemp::getUnitID(Unit unit) - { - if ( !unit ) - return -1; - return unit->getID(); - } - template - void CommandTemp::execute(int frame) - { - // Immediately return if latency compensation is disabled or if the command was queued - if ( !Broodwar->isLatComEnabled() || command.isQueued() ) return; - UnitImpl* unit = static_cast(command.unit); - UnitImpl* target = static_cast(command.target); - - Position position(command.x,command.y); - TilePosition tileposition(command.x,command.y); - UnitType unitType(command.extra); - UpgradeType upgradeType(command.extra); - TechType techType(command.extra); - - // Get the player (usually the unit's owner) - if ( !player ) - player = static_cast(unit ? unit->getPlayer() : Broodwar->self()); - - // Latency test - if (frame > Broodwar->getLatency() && - command.type != UnitCommandTypes::Cancel_Construction && - command.type != UnitCommandTypes::Cancel_Train_Slot && - command.type != UnitCommandTypes::Cancel_Morph && - command.type != UnitCommandTypes::Train && - command.type != UnitCommandTypes::Gather && - command.type != UnitCommandTypes::Stop && - command.type != UnitCommandTypes::Return_Cargo && - command.type != UnitCommandTypes::Right_Click_Position && - command.type != UnitCommandTypes::Morph) - return; - - // Existence test - switch ( command.type.getID() ) + template + class CommandTemp { - case UnitCommandTypes::Enum::Cancel_Construction: - case UnitCommandTypes::Enum::Cancel_Research: - case UnitCommandTypes::Enum::Cancel_Upgrade: - break; - default: - if ( !unit->self->exists ) - return; - break; - } + public: + CommandTemp(const UnitCommand& command) : command(command) { } - // Move test - switch ( command.type.getID() ) - { - case UnitCommandTypes::Enum::Follow: - case UnitCommandTypes::Enum::Hold_Position: - case UnitCommandTypes::Enum::Move: - case UnitCommandTypes::Enum::Patrol: - case UnitCommandTypes::Enum::Right_Click_Position: - case UnitCommandTypes::Enum::Attack_Move: - if (!unit->getType().canMove()) - return; - break; - } + void execute(bool isCurrentFrame, bool pastRLF); + + template + void insertIntoCommandBuffer(std::vector< std::vector > &buf) && { + const auto addToBuffer = [&buf](auto &&command, int frames) + { + command.execute(frames == 0, frames > Broodwar->getRemainingLatencyFrames()); + + if (static_cast(buf.size()) < frames) + { + buf.resize(frames); // Will probably never trigger, since we resize + // the buffer in applyLatencyCompensation() + // but it's better to be safe. + } + + buf[frames - 1].push_back(std::forward(command)); // Forward rvalue ref + }; + + auto orderEvent = makeEvent(EventType::Order); + auto finishEvent = makeEvent(EventType::Finish); + + switch (command.type.getID()) { + // RLF: Resource event + // RLF + 1: Order event + // RLF + 2: Finish event + case UnitCommandTypes::Cancel_Construction: + addToBuffer(std::move(*this), Broodwar->getRemainingLatencyFrames()); + addToBuffer(std::move(orderEvent), Broodwar->getRemainingLatencyFrames() + 1); + addToBuffer(std::move(finishEvent), Broodwar->getRemainingLatencyFrames() + 2); + break; + + // RLF: Resource event + // RLF + 1: Order event + case UnitCommandTypes::Build_Addon: + case UnitCommandTypes::Cancel_Addon: + case UnitCommandTypes::Cancel_Research: + case UnitCommandTypes::Cancel_Upgrade: + case UnitCommandTypes::Morph: + addToBuffer(std::move(*this), Broodwar->getRemainingLatencyFrames()); + addToBuffer(std::move(orderEvent), Broodwar->getRemainingLatencyFrames() + 1); + break; + + // RLF: Resource event + // RLF + 1: Order event (only for building -> building morphs) + // RLF + 13: Finish event (only for unit -> unit morphs) + // RLF + 15: Finish event (only for building -> building morphs) + case UnitCommandTypes::Cancel_Morph: + addToBuffer(std::move(*this), Broodwar->getRemainingLatencyFrames()); + + if (auto unit = reinterpret_cast(command.unit); + unit && unit->getType().isBuilding()) + { + addToBuffer(std::move(orderEvent), Broodwar->getRemainingLatencyFrames() + 1); + addToBuffer(std::move(finishEvent), Broodwar->getRemainingLatencyFrames() + 15); + } + else + { + addToBuffer(std::move(finishEvent), Broodwar->getRemainingLatencyFrames() + 13); + } + break; + + // RLF: Resource event + // RLF + 1: Order event + // RLF + 3: Finish event + case UnitCommandTypes::Cancel_Train: + case UnitCommandTypes::Cancel_Train_Slot: + addToBuffer(std::move(*this), Broodwar->getRemainingLatencyFrames()); + addToBuffer(std::move(orderEvent), Broodwar->getRemainingLatencyFrames() + 1); + addToBuffer(std::move(finishEvent), Broodwar->getRemainingLatencyFrames() + 3); + break; + + // RLF: Order event + // RLF + 1: Finish event + case UnitCommandTypes::Halt_Construction: + eventType = EventType::Order; + addToBuffer(std::move(*this), Broodwar->getRemainingLatencyFrames()); + addToBuffer(finishEvent, Broodwar->getRemainingLatencyFrames() + 1); + break; + + default: + addToBuffer(std::move(*this), Broodwar->getRemainingLatencyFrames()); + break; + } - // Apply command changes - if (command.type == UnitCommandTypes::Attack_Move) - { - unit->self->order = Orders::AttackMove; - unit->self->targetPositionX = position.x; - unit->self->targetPositionY = position.y; - unit->self->orderTargetPositionX = position.x; - unit->self->orderTargetPositionY = position.y; - } - else if (command.type == UnitCommandTypes::Attack_Unit) - { - if ( !target || !target->self->exists) - return; - if (!unit->getType().canAttack()) - return; - unit->self->order = Orders::AttackUnit; - unit->self->target = getUnitID(target); - } - else if (command.type == UnitCommandTypes::Build) - { - unit->self->order = Orders::PlaceBuilding; - unit->self->isConstructing = true; - unit->self->isIdle = false; - unit->self->buildType = command.extra; - } - else if (command.type == UnitCommandTypes::Build_Addon) - { - unit->self->secondaryOrder = Orders::BuildAddon; - unit->self->isConstructing = true; - unit->self->isIdle = false; - unit->self->buildType = command.extra; - } - else if (command.type == UnitCommandTypes::Burrow) - { - unit->self->order = Orders::Burrowing; - } - else if (command.type == UnitCommandTypes::Cancel_Addon) - { - if (savedExtra == -1) - savedExtra = unit->self->buildType; - unitType = UnitType(savedExtra); - if (frame < Broodwar->getLatency()) - { - player->self->minerals += (int)(unitType.mineralPrice() * 0.75); - player->self->gas += (int)(unitType.gasPrice() * 0.75); - } - unit->self->remainingBuildTime = 0; - unit->self->isConstructing = false; - unit->self->order = Orders::Nothing; - unit->self->isIdle = true; - unit->self->buildType = UnitTypes::None; - unit->self->buildUnit = -1; - } - else if (command.type == UnitCommandTypes::Cancel_Construction) - { - if (savedExtra == -1) - savedExtra = unit->self->type; - if (savedExtra2 == -1) - savedExtra2 = unit->self->buildUnit; - unitType = UnitType(savedExtra); - if ((frame > Broodwar->getLatency() + 1 && Broodwar->getLatency() == 2) || - (frame > Broodwar->getLatency() + 2 && Broodwar->getLatency() > 2)) - return; - if (unitType.getRace() == Races::Terran) - { - UnitImpl* builder = static_cast( Broodwar->getUnit(savedExtra2) ); - if ( builder && builder->exists()) - { - builder->self->buildUnit = -1; - builder->self->buildType = UnitTypes::None; - builder->self->isConstructing = false; - builder->self->order = Orders::ResetCollision; - } - } - if (frame > Broodwar->getLatency()) - return; - if (!unit->self->exists) - return; - unit->self->buildUnit = -1; - if ((frame < Broodwar->getLatency() && Broodwar->getLatency()==2) || - (frame <=Broodwar->getLatency() && Broodwar->getLatency()>2)) - { - player->self->minerals += (int)(unitType.mineralPrice() * 0.75); - player->self->gas += (int)(unitType.gasPrice() * 0.75); - } - unit->self->remainingBuildTime = 0; - unit->self->isConstructing = false; - if (unitType.getRace() == Races::Zerg) - { - unit->self->type = unitType.whatBuilds().first; - unit->self->buildType = UnitTypes::None; - unit->self->isMorphing = false; - unit->self->isIdle = true; - - UnitType whatBuilds = unitType.whatBuilds().first; - if (frame < Broodwar->getLatency()) - player->self->supplyUsed[unitType.getRace()] += whatBuilds.supplyRequired(); - - if (whatBuilds.isBuilding()) - { - unit->self->order = Orders::Nothing; } - else - { - unit->self->order = Orders::ResetCollision; - } - } - else - { - unit->self->order = Orders::Die; - unit->self->isCompleted = false; - unit->self->isIdle = false; - } - } - else if (command.type == UnitCommandTypes::Cancel_Morph) - { - if (savedExtra == -1) - savedExtra = unit->self->buildType; - unitType = UnitType(savedExtra); - if (frame > Broodwar->getLatency() + 12) - return; - if (frame < Broodwar->getLatency()) - { - if (unitType.whatBuilds().first.isBuilding()) + + private: + enum struct EventType { Order, Resource, Finish }; + CommandTemp makeEvent(EventType type) { - player->self->minerals += (int)(unitType.mineralPrice()*0.75); - player->self->gas += (int)(unitType.gasPrice()*0.75); + CommandTemp other{ command }; + other.eventType = type; + return other; } - else - { - player->self->minerals += unitType.mineralPrice(); - player->self->gas += unitType.gasPrice(); - } - } - if (frame<=Broodwar->getLatency()) - { - if (unitType.isTwoUnitsInOneEgg()) - player->self->supplyUsed[Races::Zerg] -= unitType.supplyRequired() * 2 - unitType.whatBuilds().first.supplyRequired(); - else - player->self->supplyUsed[Races::Zerg] -= unitType.supplyRequired() - unitType.whatBuilds().first.supplyRequired(); - } - unit->self->buildType = UnitTypes::None; - unit->self->remainingBuildTime = 0; - unit->self->isMorphing = false; - unit->self->isConstructing = false; - unit->self->isCompleted = true; - unit->self->isIdle = true; - unit->self->type = unitType.whatBuilds().first; - if (unitType.whatBuilds().first.isBuilding()) - unit->self->order = Orders::Nothing; - else - unit->self->order = Orders::PlayerGuard; - } - else if (command.type == UnitCommandTypes::Cancel_Research) - { - if (savedExtra == -1) - savedExtra = unit->self->tech; - techType = TechType(savedExtra); + + static int getUnitID(Unit unit); + UnitCommand command; + EventType eventType = EventType::Resource; + int savedExtra = -1; + int savedExtra2 = -1; + + PlayerImpl* player = nullptr; + }; + + template + int CommandTemp::getUnitID(Unit unit) + { + if ( !unit ) + return -1; + return unit->getID(); + } + + template + void CommandTemp::execute(const bool isCurrentFrame, const bool pastRLF) + { + // Immediately return if latency compensation is disabled or if the command was queued + if (!Broodwar->isLatComEnabled() || command.isQueued()) return; + UnitImpl *unit = reinterpret_cast(command.unit); + UnitImpl *target = reinterpret_cast(command.target); - if (!unit->self->exists) - return; - unit->self->order = Orders::Nothing; - unit->self->tech = TechTypes::None; - unit->self->isIdle = true; - unit->self->remainingResearchTime = 0; - if (frame < Broodwar->getLatency()) - { - player->self->minerals += techType.mineralPrice(); - player->self->gas += techType.gasPrice(); - } - } - else if (command.type == UnitCommandTypes::Cancel_Train) - { - if (savedExtra == -1) - savedExtra = unit->self->trainingQueue[unit->self->trainingQueueCount - 1]; - if (savedExtra2 == -1) - savedExtra2 = unit->self->buildUnit; - if ((frame < Broodwar->getLatency() && Broodwar->getLatency() == 2) || - (frame <= Broodwar->getLatency()+1 && Broodwar->getLatency() > 2)) - { - unit->self->trainingQueueCount--; - if (unit->self->trainingQueueCount < 0) - unit->self->trainingQueueCount = 0; - player->self->minerals += UnitType(savedExtra).mineralPrice(); - player->self->gas += UnitType(savedExtra).gasPrice(); - } - if (unit->self->trainingQueueCount == 0) - { - unit->self->buildUnit = -1; - unit->self->isTraining = false; - unit->self->remainingTrainTime = 0; - unit->self->isIdle = true; - player->self->supplyUsed[unit->getType().getRace()] -= UnitType(savedExtra).supplyRequired(); - } - } - else if (command.type == UnitCommandTypes::Cancel_Train_Slot) - { - if (frame > Broodwar->getLatency() + 2) - return; - if (savedExtra == -1) - savedExtra = unit->self->trainingQueue[command.extra]; - if ((frame < Broodwar->getLatency() && Broodwar->getLatency() == 2) || - (frame <= Broodwar->getLatency()+1 && Broodwar->getLatency() > 2)) - { - for(int i = command.extra; i < 4; ++i) - unit->self->trainingQueue[i] = unit->self->trainingQueue[i+1]; - unit->self->trainingQueueCount--; - if (unit->self->trainingQueueCount < 0) - unit->self->trainingQueueCount = 0; - player->self->minerals += UnitType(savedExtra).mineralPrice(); - player->self->gas += UnitType(savedExtra).gasPrice(); - } - if (command.extra == 0) - { - unit->self->buildUnit = -1; - if ((frame < Broodwar->getLatency() && Broodwar->getLatency() == 2) || - (frame <= Broodwar->getLatency()-1 && Broodwar->getLatency() > 2)) - { - player->self->supplyUsed[unit->getType().getRace()] -= UnitType(savedExtra).supplyRequired(); + if (isCurrentFrame) { + switch (command.type.getID()) // Commands which do things during the current frame + { + case UnitCommandTypes::Morph: + if (!unit->getType().isBuilding()) + { + return; + } // Morph (building -> building) and Build_Addon orders may reserve resources + case UnitCommandTypes::Build_Addon: // that SC does not take until the next frame to protect bots from overspending. + case UnitCommandTypes::Train: // Train does the same but for supply. + if(eventType == EventType::Resource) + break; + return; + default: + return; + } } - - if (unit->self->trainingQueueCount == 0) + + // Get the player (usually the unit's owner) + if (!player) + player = static_cast(unit ? unit->getPlayer() : Broodwar->self()); + + // Existence test + if (!unit->self->exists) + return; + + // Move test + switch (command.type.getID()) { - unit->self->isTraining = false; - unit->self->isIdle = true; + case UnitCommandTypes::Follow: + case UnitCommandTypes::Hold_Position: + case UnitCommandTypes::Move: + case UnitCommandTypes::Patrol: + case UnitCommandTypes::Right_Click_Position: + case UnitCommandTypes::Attack_Move: + if (!unit->getType().canMove()) + return; + break; + default: + break; } - else + + // Apply command changes + switch(command.type.getID()) { - unit->self->remainingTrainTime = UnitType(unit->self->trainingQueue[0]).buildTime(); // @TODO: fix to real build time - player->self->supplyUsed[unit->getType().getRace()] += UnitType(unit->self->trainingQueue[0]).supplyRequired(); - if ((frame == Broodwar->getLatency() && Broodwar->getLatency() == 2) || - (frame == Broodwar->getLatency()+1 && Broodwar->getLatency() > 2) ) - { - player->self->supplyUsed[unit->getType().getRace()] -= UnitType(savedExtra).supplyRequired(); - } + // RLF + case UnitCommandTypes::Attack_Move: + unit->self->order = Orders::AttackMove; + unit->self->targetPositionX = command.x; + unit->self->targetPositionY = command.y; + unit->self->orderTargetPositionX = command.x; + unit->self->orderTargetPositionY = command.y; + break; + + // RLF + case UnitCommandTypes::Attack_Unit: + if (!target || !target->self->exists || !unit->getType().canAttack()) + return; + unit->self->order = Orders::AttackUnit; + unit->self->target = getUnitID(target); + break; + + // RLF + case UnitCommandTypes::Build: + unit->self->order = Orders::PlaceBuilding; + unit->self->isConstructing = true; + unit->self->isIdle = false; + unit->self->buildType = command.extra; + break; + + // For building addons, SC takes minerals on RLF + 1. + // Latcom will do as with building->building morph and reserve these resources. + // RLF: Resource event + // RLF + 1: Order event + case UnitCommandTypes::Build_Addon: { + UnitType addonType{ command.extra }; + switch (eventType) + { + case EventType::Resource: + player->self->minerals -= static_cast(addonType.mineralPrice()); + player->self->gas -= static_cast(addonType.gasPrice()); + + if (!isCurrentFrame) // We will pretend the building is busy building, this doesn't + { + unit->self->isIdle = false; + unit->self->order = Orders::PlaceAddon; + } + break; + + case EventType::Order: + unit->self->isConstructing = true; + unit->self->order = Orders::Nothing; + unit->self->secondaryOrder = Orders::BuildAddon; + unit->self->buildType = command.extra; + break; + } + } + break; + + // RLF + case UnitCommandTypes::Burrow: + unit->self->order = Orders::Burrowing; + break; + + // RLF: Resource event + // RLF + 1: Order event + case UnitCommandTypes::Cancel_Addon: + switch(eventType) + { + case EventType::Resource: { + UnitType addonType{ unit->self->buildType }; + player->self->minerals += static_cast(addonType.mineralPrice() * 0.75); + player->self->gas += static_cast(addonType.gasPrice() * 0.75); + unit->self->buildType = UnitTypes::None; + } + break; + case EventType::Order: + unit->self->remainingBuildTime = 0; + unit->self->isConstructing = false; + unit->self->order = Orders::Nothing; + unit->self->isIdle = true; + unit->self->buildUnit = -1; + break; + } + + break; + + // RLF: Resource event + // RLF + 1: Order event + // RLF + 2: Finish event + case UnitCommandTypes::Cancel_Construction: + { + if (unit->getType().getRace() == Races::Terran) + { + if (auto builder = static_cast(Broodwar->getUnit(unit->self->buildUnit)); + builder && builder->exists()) + { + switch (eventType) + { + case EventType::Resource: + builder->self->buildType = UnitTypes::None; + break; + case EventType::Order: + builder->self->isConstructing = false; + builder->self->order = Orders::ResetCollision; + break; + case EventType::Finish: + builder->self->order = Orders::PlayerGuard; + break; + } + } + } + + if (eventType == EventType::Resource) + { + unit->self->buildUnit = -1; + player->self->minerals += static_cast(unit->getType().mineralPrice() * 0.75); + player->self->gas += static_cast(unit->getType().gasPrice() * 0.75); + unit->self->remainingBuildTime = 0; + } + + if (unit->getType().getRace() == Races::Zerg) + { + switch (eventType) + { + case EventType::Resource: + unit->self->type = unit->getType().whatBuilds().first; + unit->self->buildType = UnitTypes::None; + unit->self->isMorphing = false; + unit->self->order = Orders::ResetCollision; + unit->self->isConstructing = false; + + player->self->supplyUsed[unit->getType().getRace()] += unit->getType().supplyRequired(); + break; + + case EventType::Order: + unit->self->order = Orders::PlayerGuard; + unit->self->isIdle = true; + break; + } + } + + break; + } + + + // RLF: Resource event + // RLF + 1: Order event (only for builing -> building morphs) + // RLF + 13: Finish event (only for unit -> unit morphs) + // RLF + 15: Finish event (only for building -> building morphs) + case UnitCommandTypes::Cancel_Morph: + switch(eventType) + { + case EventType::Resource: + { + UnitType builtType{ unit->self->buildType }; + UnitType newType{ builtType.whatBuilds().first }; + + if (newType.isBuilding()) + { + player->self->minerals += static_cast(builtType.mineralPrice() * 0.75); + player->self->gas += static_cast(builtType.gasPrice() * 0.75); + } + else + { + player->self->minerals += builtType.mineralPrice(); + player->self->gas += builtType.gasPrice(); + } + + player->self->supplyUsed[builtType.getRace()] -= + builtType.supplyRequired() * (1 + static_cast(builtType.isTwoUnitsInOneEgg())); + + player->self->supplyUsed[newType.getRace()] += // Could these races be different? Probably not. + // Should we handle it? Definetely. + newType.supplyRequired() * builtType.whatBuilds().second; + // Note: builtType.whatBuilds().second is always 1 but we + // might as well handle the general case, in case Blizzard + // all of a sudden allows you to cancel archon morphs + + if (newType.isBuilding() && newType.producesCreep()) + { + unit->self->order = Orders::InitCreepGrowth; + } + + if (unit->self->type != UnitTypes::Zerg_Egg) { // Issue #781 + // https://github.com/bwapi/bwapi/issues/781 + unit->self->type = newType; + } + + unit->self->buildType = UnitTypes::None; + unit->self->isConstructing = false; + unit->self->isMorphing = false; + unit->self->isCompleted = true; + unit->self->remainingBuildTime = 0; + } + + break; + + case EventType::Order: + if (unit->getType().isBuilding()) // This event would hopefully not have been created + { // if this wasn't true (see event note above) + unit->self->isIdle = true; + unit->self->order = Orders::Nothing; + if(unit->self->type == UnitTypes::Zerg_Hatchery || unit->self->type == UnitTypes::Zerg_Lair) + { // Type should have updated during last event to the cancelled type + unit->self->secondaryOrder = Orders::SpreadCreep; + } + } + + break; + + case EventType::Finish: + if(unit->self->type == UnitTypes::Zerg_Hatchery || unit->self->type == UnitTypes::Zerg_Lair) + { + unit->self->secondaryOrder = Orders::SpawningLarva; + } + else if(!unit->getType().isBuilding()) + { + unit->self->order = Orders::PlayerGuard; + unit->self->isCompleted = true; + unit->self->isConstructing = false; + unit->self->isIdle = true; + unit->self->isMorphing = false; + } + break; + } + + break; + + // RLF: Resource event + // RLF + 1: Order update + case UnitCommandTypes::Cancel_Research: + { + switch(eventType) + { + case EventType::Resource: + { + TechType techType{ unit->self->tech }; + player->self->minerals += techType.mineralPrice(); + player->self->gas += techType.gasPrice(); + unit->self->remainingResearchTime = 0; + unit->self->tech = TechTypes::None; + } + break; + + case EventType::Order: + unit->self->order = Orders::Nothing; + unit->self->isIdle = true; + break; + } + } + + break; + + // RLF: Resource event + // RLF + 1: Order event + // RLF + 3: Finish event + case UnitCommandTypes::Cancel_Train_Slot: + if (command.extra != 0) + { + if (eventType == EventType::Resource) + { + UnitType unitType{ unit->self->trainingQueue[command.extra] }; + player->self->minerals += unitType.mineralPrice(); + player->self->gas += unitType.gasPrice(); + + // Shift training queue back one slot after the cancelled unit + std::copy(unit->self->trainingQueue + command.extra + 1, + std::end(unit->self->trainingQueue), + unit->self->trainingQueue + command.extra); + + --unit->self->trainingQueueCount; + } + break; + } + + // If we're cancelling slot 0, we fall through to Cancel_Train. + [[fallthrough]]; + + // RLF: Resource event + // RLF + 1: Order event + // RLF + 3: Finish event + case UnitCommandTypes::Cancel_Train: { + switch(eventType) + { + case EventType::Resource: + { + UnitType unitType{ unit->self->trainingQueue[unit->self->trainingQueueCount - 1] }; + player->self->minerals += unitType.mineralPrice(); + player->self->gas += unitType.gasPrice(); + + unit->self->buildUnit = -1; + + if (unit->self->trainingQueueCount == 1) + { + unit->self->isIdle = false; + unit->self->isTraining = false; + } + break; + } + + case EventType::Order: + { + UnitType unitType{ unit->self->trainingQueue[--unit->self->trainingQueueCount] }; + player->self->supplyUsed[unitType.getRace()] -= unitType.supplyRequired(); + + if (unit->self->trainingQueueCount == 0) + { + unit->self->buildType = UnitTypes::None; + } + else + { + // Actual time decreases, but we'll let it be the buildTime until latency catches up. + unit->self->remainingTrainTime = + static_cast(unit->self->trainingQueue[unit->self->trainingQueueCount - 1]).buildTime(); + unit->self->buildType = unit->self->trainingQueue[unit->self->trainingQueueCount - 1]; + } + } + + break; + + case EventType::Finish: + if (unit->self->buildType == UnitTypes::None) + { + unit->self->order = Orders::Nothing; + } + break; + } + break; + } + + // RLF: Resource event + // RLF + 1: Order event + case UnitCommandTypes::Cancel_Upgrade: + switch(eventType) + { + case EventType::Resource: + { + UpgradeType upgradeType = unit->self->upgrade; + const int nextLevel = unit->getPlayer()->getUpgradeLevel(upgradeType) + 1; + + player->self->minerals += upgradeType.mineralPrice(nextLevel); + player->self->gas += upgradeType.gasPrice(nextLevel); + + unit->self->upgrade = UpgradeTypes::None; + unit->self->remainingUpgradeTime = 0; + } + break; + + case EventType::Order: + unit->self->order = Orders::Nothing; + unit->self->isIdle = true; + break; + } + + break; + + // RLF + case UnitCommandTypes::Cloak: + unit->self->order = Orders::Cloak; + unit->self->energy -= unit->getType().cloakingTech().energyCost(); + break; + + // RLF + case UnitCommandTypes::Decloak: + unit->self->order = Orders::Decloak; + break; + + // RLF + case UnitCommandTypes::Follow: + unit->self->order = Orders::Follow; + unit->self->target = getUnitID(target); + unit->self->isIdle = false; + unit->self->isMoving = true; + break; + + // RLF + case UnitCommandTypes::Gather: + unit->self->target = getUnitID(target); + unit->self->isIdle = false; + unit->self->isMoving = true; + unit->self->isGathering = true; + + // @TODO: Fully time and test this order + if (target->getType().isMineralField()) + unit->self->order = Orders::MoveToMinerals; + else if (target->getType().isRefinery()) + unit->self->order = Orders::MoveToGas; + + break; + + // RLF: Order event + // RLF + 1: Finish event + case UnitCommandTypes::Halt_Construction: + switch(eventType) + { + case EventType::Order: + if (auto building = reinterpret_cast(Broodwar->getUnit(unit->self->buildUnit)); + building) + { + building->self->buildUnit = -1; + } + unit->self->buildUnit = -1; + unit->self->order = Orders::ResetCollision; + unit->self->isConstructing = false; + unit->self->buildType = UnitTypes::None; + break; + + case EventType::Finish: + unit->self->order = Orders::PlayerGuard; + unit->self->isIdle = true; + break; + } + + break; + + // RLF + case UnitCommandTypes::Hold_Position: + unit->self->isMoving = false; + unit->self->isIdle = false; + unit->self->order = Orders::HoldPosition; + break; + + // RLF + case UnitCommandTypes::Land: + unit->self->order = Orders::BuildingLand; + unit->self->isIdle = false; + break; + + // RLF + case UnitCommandTypes::Lift: + unit->self->order = Orders::BuildingLiftOff; + unit->self->isIdle = false; + break; + + // RLF + case UnitCommandTypes::Load: + if (unit->getType() == UnitTypes::Terran_Bunker) + { + unit->self->order = Orders::PickupBunker; + unit->self->target = getUnitID(target); + } + else if (unit->getType().spaceProvided()) + { + unit->self->order = Orders::PickupTransport; + unit->self->target = getUnitID(target); + } + else if (target->getType().spaceProvided()) + { + unit->self->order = Orders::EnterTransport; + unit->self->target = getUnitID(target); + } + unit->self->isIdle = false; + + break; + + // For morph, SC takes minerals on RLF + 1 if morphing building->building. + // Latcom will do as with addons and reserve these resources. + // RLF: Resource event + // RLF + 1: Order event + case UnitCommandTypes::Morph: + { + UnitType morphType{ command.extra }; + + switch (eventType) + { + case EventType::Resource: + if(!isCurrentFrame) + { + unit->self->isCompleted = false; + unit->self->isIdle = false; + unit->self->isConstructing = true; + unit->self->isMorphing = true; + unit->self->buildType = morphType; + } + + if (unit->getType().isBuilding()) + { + if (!isCurrentFrame) + { // Actions that don't happen when we're reserving resources + unit->self->order = Orders::ZergBuildingMorph; + unit->self->type = morphType; + } + player->self->minerals -= morphType.mineralPrice(); + player->self->gas -= morphType.gasPrice(); + } + else + { + if(!isCurrentFrame) + { + unit->self->order = Orders::ZergUnitMorph; + + player->self->minerals -= morphType.mineralPrice(); + player->self->gas -= morphType.gasPrice(); + + player->self->supplyUsed[morphType.getRace()] += morphType.supplyRequired() * + (1 + static_cast(morphType.isTwoUnitsInOneEgg())) - unit->getType().supplyRequired(); + + switch(morphType) + { + case UnitTypes::Zerg_Lurker_Egg: + unit->self->type = UnitTypes::Zerg_Lurker_Egg; + break; + + case UnitTypes::Zerg_Devourer: + case UnitTypes::Zerg_Guardian: + unit->self->type = UnitTypes::Zerg_Cocoon; + break; + + default: + unit->self->type = UnitTypes::Zerg_Egg; + break; + } + } + } + break; + case EventType::Order: + if (unit->getType().isBuilding()) + { + unit->self->order = Orders::IncompleteBuilding; + } + break; + } + } + + break; + + // RLF + case UnitCommandTypes::Move: + unit->self->order = Orders::Move; + unit->self->targetPositionX = command.x; + unit->self->targetPositionY = command.y; + unit->self->orderTargetPositionX = command.x; + unit->self->orderTargetPositionY = command.y; + unit->self->isMoving = true; + unit->self->isIdle = false; + break; + + // RLF + case UnitCommandTypes::Patrol: + unit->self->order = Orders::Patrol; + unit->self->isIdle = false; + unit->self->isMoving = true; + unit->self->targetPositionX = command.x; + unit->self->targetPositionY = command.y; + unit->self->orderTargetPositionX = command.x; + unit->self->orderTargetPositionY = command.y; + break; + + // RLF + case UnitCommandTypes::Repair: + if (unit->getType() != UnitTypes::Terran_SCV) + { + return; + } + unit->self->order = Orders::Repair; + unit->self->target = getUnitID(target); + unit->self->isIdle = false; + break; + + // RLF + case UnitCommandTypes::Research: + { + TechType techType{ command.extra }; + unit->self->order = Orders::ResearchTech; + unit->self->tech = techType; + unit->self->isIdle = false; + unit->self->remainingResearchTime = techType.researchTime(); + + player->self->minerals -= techType.mineralPrice(); + player->self->gas -= techType.gasPrice(); + player->self->isResearching[techType] = true; + } + break; + + // RLF + case UnitCommandTypes::Return_Cargo: + if (!unit->self->carryResourceType) + return; + + unit->self->order = (unit->isCarryingGas() ? Orders::ReturnGas : Orders::ReturnMinerals); + unit->self->isGathering = true; + unit->self->isIdle = false; + + break; + + // RLF + case UnitCommandTypes::Right_Click_Position: + unit->self->order = Orders::Move; + unit->self->targetPositionX = command.x; + unit->self->targetPositionY = command.y; + unit->self->orderTargetPositionX = command.x; + unit->self->orderTargetPositionY = command.y; + unit->self->isMoving = true; + unit->self->isIdle = false; + break; + + // RLF + case UnitCommandTypes::Right_Click_Unit: + unit->self->target = getUnitID(target); + unit->self->isIdle = false; + unit->self->isMoving = true; + + if (unit->getType().isWorker() && target->getType().isMineralField()) + { + unit->self->isGathering = true; + unit->self->order = Orders::MoveToMinerals; + } + else if (unit->getType().isWorker() && target->getType().isRefinery()) + { + unit->self->isGathering = true; + unit->self->order = Orders::MoveToGas; + } + else if (unit->getType().isWorker() && + target->getType().getRace() == Races::Terran && + target->getType().whatBuilds().first == unit->getType() && + !target->isCompleted()) + { + unit->self->order = Orders::ConstructingBuilding; + unit->self->buildUnit = getUnitID(target); + target->self->buildUnit = getUnitID(unit); + unit->self->isConstructing = true; + target->self->isConstructing = true; + } + else if (unit->getType().canAttack() && target->getPlayer() != unit->getPlayer() && !target->getType().isNeutral()) + { + unit->self->order = Orders::AttackUnit; + } + else if(unit->getType().canMove()) + { + unit->self->order = Orders::Follow; + } + + break; + + // RLF + case UnitCommandTypes::Set_Rally_Position: + if (!unit->getType().canProduce()) + return; + + unit->self->order = Orders::RallyPointTile; + unit->self->rallyPositionX = command.x; + unit->self->rallyPositionY = command.y; + unit->self->rallyUnit = -1; + + break; + + // RLF + case UnitCommandTypes::Set_Rally_Unit: + if (!unit->getType().canProduce()) + return; + if (!target || !target->self->exists) + return; + + unit->self->order = Orders::RallyPointUnit; + unit->self->rallyUnit = getUnitID(target); + + break; + + // RLF + case UnitCommandTypes::Siege: + unit->self->order = Orders::Sieging; + break; + + // RLF + case UnitCommandTypes::Stop: + unit->self->order = Orders::Stop; + unit->self->isIdle = true; + break; + + // With train, the game does not take the supply until RLF + 1. + // We just pretend that it happens on RLF. + case UnitCommandTypes::Train: + { + UnitType unitType = command.extra; + + if (!isCurrentFrame) + { + // Happens on RLF, we don't want to duplicate this. + player->self->minerals -= unitType.mineralPrice(); + player->self->gas -= unitType.gasPrice(); + } + + // Happens on RLF + 1, we want to pretend this happens on RLF. + unit->self->trainingQueue[unit->self->trainingQueueCount++] = unitType; + player->self->supplyUsed[unitType.getRace()] += unitType.supplyRequired(); + + // Happens on RLF or RLF + 1, doesn't matter if we do twice + unit->self->isTraining = true; + unit->self->isIdle = false; + unit->self->remainingTrainTime = unitType.buildTime(); + + if (unitType == UnitTypes::Terran_Nuclear_Missile) + { + unit->self->secondaryOrder = Orders::Train; + } + } + + break; + + // RLF + case UnitCommandTypes::Unburrow: + unit->self->order = Orders::Unburrowing; + break; + + // RLF + case UnitCommandTypes::Unload: + unit->self->order = Orders::Unload; + unit->self->target = getUnitID(target); + break; + + // RLF + case UnitCommandTypes::Unload_All: + if (unit->getType() == UnitTypes::Terran_Bunker) + { + unit->self->order = Orders::Unload; + } + else + { + unit->self->order = Orders::MoveUnload; + unit->self->targetPositionX = command.x; + unit->self->targetPositionY = command.y; + unit->self->orderTargetPositionX = command.x; + unit->self->orderTargetPositionY = command.y; + } + + break; + + // RLF + case UnitCommandTypes::Unload_All_Position: + unit->self->order = Orders::MoveUnload; + unit->self->targetPositionX = command.x; + unit->self->targetPositionY = command.y; + unit->self->orderTargetPositionX = command.x; + unit->self->orderTargetPositionY = command.y; + break; + + // RLF + case UnitCommandTypes::Unsiege: + unit->self->order = Orders::Unsieging; + break; + + // RLF + case UnitCommandTypes::Upgrade: + { + UpgradeType upgradeType { command.extra }; + + unit->self->order = Orders::Upgrade; + unit->self->upgrade = upgradeType; + unit->self->isIdle = false; + + const int level = unit->getPlayer()->getUpgradeLevel(upgradeType); + unit->self->remainingUpgradeTime = upgradeType.upgradeTime(level + 1); + + player->self->minerals -= upgradeType.mineralPrice(level + 1); + player->self->gas -= upgradeType.gasPrice(level + 1); + + player->self->isUpgrading[upgradeType] = true; + } + break; + + // RLF + case UnitCommandTypes::Use_Tech: + if (static_cast(command.extra) == TechTypes::Stim_Packs + && unit->self->hitPoints > 10) + { + unit->self->hitPoints -= 10; + unit->self->stimTimer = 17; + } + break; + + // RLF + case UnitCommandTypes::Use_Tech_Position: + { + TechType techType{ command.extra }; + + if (!techType.targetsPosition()) + return; + + unit->self->order = techType.getOrder(); + unit->self->targetPositionX = command.x; + unit->self->targetPositionY = command.y; + unit->self->orderTargetPositionX = command.x; + unit->self->orderTargetPositionY = command.y; + } + + break; + + // RLF + case UnitCommandTypes::Use_Tech_Unit: + { + TechType techType{ command.extra }; + + if (!techType.targetsUnit()) + return; + + unit->self->order = techType.getOrder(); + unit->self->orderTarget = getUnitID(target); + + Position const targetPosition = target->getPosition(); + + unit->self->targetPositionX = targetPosition.x; + unit->self->targetPositionY = targetPosition.y; + unit->self->orderTargetPositionX = targetPosition.x; + unit->self->orderTargetPositionY = targetPosition.y; + + break; + } } - } - } - else if (command.type == UnitCommandTypes::Cancel_Upgrade) - { - if (savedExtra == -1) - savedExtra = unit->self->upgrade; - upgradeType = UpgradeType(savedExtra); - - if (!unit->self->exists) - return; - - unit->self->order = Orders::Nothing; - int level = unit->getPlayer()->getUpgradeLevel(upgradeType); - unit->self->upgrade = UpgradeTypes::None; - unit->self->isIdle = true; - unit->self->remainingUpgradeTime = 0; - - if (frame < Broodwar->getLatency()) - { - player->self->minerals += upgradeType.mineralPrice(level+1); - player->self->gas += upgradeType.gasPrice(level+1); - } - } - else if (command.type == UnitCommandTypes::Cloak) - { - unit->self->order = Orders::Cloak; - if (frame < Broodwar->getLatency()) - unit->self->energy -= unit->getType().cloakingTech().energyCost(); - } - else if (command.type == UnitCommandTypes::Decloak) - { - unit->self->order = Orders::Decloak; - } - else if (command.type == UnitCommandTypes::Follow) - { - unit->self->order = Orders::Follow; - unit->self->target = getUnitID(target); - unit->self->isIdle = false; - unit->self->isMoving = true; - } - else if (command.type == UnitCommandTypes::Gather) - { - if ((frame<=Broodwar->getLatency() && Broodwar->getLatency()==2) || - (frame<=Broodwar->getLatency()+1 && Broodwar->getLatency()>2)) - { - unit->self->target = getUnitID(target); - unit->self->isIdle = false; - unit->self->isMoving = true; - unit->self->isGathering = true; - if ( target->getType().isMineralField() ) - unit->self->order = Orders::MoveToMinerals; - else if ( target->getType().isRefinery() ) - unit->self->order = Orders::MoveToGas; - } - } - else if (command.type == UnitCommandTypes::Halt_Construction) - { - if (savedExtra == -1) - savedExtra = unit->self->buildUnit; - if (frame > Broodwar->getLatency()) - return; - UnitImpl* buildUnit = static_cast( Broodwar->getUnit(savedExtra) ); - if ( buildUnit ) - buildUnit->self->buildUnit = -1; - - unit->self->buildUnit = -1; - unit->self->buildType = UnitTypes::None; - unit->self->order = Orders::ResetCollision; - unit->self->isConstructing = false; - } - else if (command.type == UnitCommandTypes::Hold_Position) - { - unit->self->isMoving = false; - unit->self->isIdle = false; - unit->self->order = Orders::HoldPosition; } - else if (command.type == UnitCommandTypes::Land) - { - unit->self->order = Orders::BuildingLand; - unit->self->isIdle = false; - } - else if (command.type == UnitCommandTypes::Lift) - { - unit->self->order = Orders::BuildingLiftOff; - unit->self->isIdle = false; - } - else if (command.type == UnitCommandTypes::Load) - { - if (unit->getType() == UnitTypes::Terran_Bunker) - { - unit->self->order = Orders::PickupBunker; - unit->self->target = getUnitID(target); - } - else if ( unit->getType().spaceProvided() ) - { - unit->self->order = Orders::PickupTransport; - unit->self->target = getUnitID(target); - } - else if ( target->getType().spaceProvided() ) - { - unit->self->order = Orders::EnterTransport; - unit->self->target = getUnitID(target); - } - unit->self->isIdle = false; - } - else if (command.type == UnitCommandTypes::Morph) - { - if (frame > Broodwar->getLatency()+1) - return; - unit->self->isMorphing = true; - unit->self->isConstructing = true; - unit->self->isCompleted = false; - unit->self->isIdle = false; - unit->self->buildType = unitType; - if (unit->self->remainingBuildTime < 50) - unit->self->remainingBuildTime = unitType.buildTime(); // @TODO: Fix to real build time - if (frame > Broodwar->getLatency()) - return; - if (unitType.isBuilding()) - { - unit->self->order = Orders::ZergBuildingMorph; - player->self->minerals -= unitType.mineralPrice(); - player->self->gas -= unitType.gasPrice(); - unit->self->type = unitType; - } - else - { - unit->self->order = Orders::ZergUnitMorph; - if (frame < Broodwar->getLatency()) - { - player->self->minerals -= unitType.mineralPrice(); - player->self->gas -= unitType.gasPrice(); - } - if (unitType.isTwoUnitsInOneEgg()) - player->self->supplyUsed[Races::Zerg] += unitType.supplyRequired()*2-unitType.whatBuilds().first.supplyRequired(); - else - player->self->supplyUsed[Races::Zerg] += unitType.supplyRequired()-unitType.whatBuilds().first.supplyRequired(); - - if (unitType == UnitTypes::Zerg_Lurker) - unit->self->type = UnitTypes::Zerg_Lurker_Egg; - else if (unitType == UnitTypes::Zerg_Devourer || - unitType == UnitTypes::Zerg_Guardian) - unit->self->type = UnitTypes::Zerg_Cocoon; - else - unit->self->type = UnitTypes::Zerg_Egg; - } - } - else if (command.type == UnitCommandTypes::Move) - { - unit->self->order = Orders::Move; - unit->self->targetPositionX = position.x; - unit->self->targetPositionY = position.y; - unit->self->orderTargetPositionX = position.x; - unit->self->orderTargetPositionY = position.y; - unit->self->isMoving = true; - unit->self->isIdle = false; - } - else if (command.type == UnitCommandTypes::Patrol) - { - unit->self->order = Orders::Patrol; - unit->self->isIdle = false; - unit->self->isMoving = true; - unit->self->targetPositionX = position.x; - unit->self->targetPositionY = position.y; - unit->self->orderTargetPositionX = position.x; - unit->self->orderTargetPositionY = position.y; - } - else if (command.type == UnitCommandTypes::Repair) - { - if (unit->getType() != UnitTypes::Terran_SCV) - return; - unit->self->order = Orders::Repair; - unit->self->target = getUnitID(target); - unit->self->isIdle = false; - } - else if (command.type == UnitCommandTypes::Research) - { - unit->self->order = Orders::ResearchTech; - unit->self->tech = techType; - unit->self->isIdle = false; - unit->self->remainingResearchTime = techType.researchTime(); // @TODO: Fix to real time - if (frame < Broodwar->getLatency()) - { - player->self->minerals -= techType.mineralPrice(); - player->self->gas -= techType.gasPrice(); - } - player->self->isResearching[techType] = true; - } - else if (command.type == UnitCommandTypes::Return_Cargo) - { - if (unit->self->carryResourceType == 0) - return; - if ((frame<=Broodwar->getLatency() && Broodwar->getLatency()==2) || - (frame<=Broodwar->getLatency()+1 && Broodwar->getLatency()>2)) - { - if (unit->isCarryingGas()) - unit->self->order = Orders::ReturnGas; - else - unit->self->order = Orders::ReturnMinerals; - unit->self->isGathering = true; - unit->self->isIdle = false; - } - } - else if (command.type == UnitCommandTypes::Right_Click_Position) - { - if ((frame<=Broodwar->getLatency() && Broodwar->getLatency()==2) || - (frame<=Broodwar->getLatency()+1 && Broodwar->getLatency()>2)) - { - unit->self->order = Orders::Move; - unit->self->targetPositionX = position.x; - unit->self->targetPositionY = position.y; - unit->self->orderTargetPositionX = position.x; - unit->self->orderTargetPositionY = position.y; - unit->self->isMoving = true; - unit->self->isIdle = false; - } - } - else if (command.type == UnitCommandTypes::Right_Click_Unit) - { - unit->self->target = getUnitID(target); - unit->self->isIdle = false; - unit->self->isMoving = true; - if (unit->getType().isWorker() && target->getType().isMineralField() ) - { - unit->self->isGathering = true; - unit->self->order = Orders::MoveToMinerals; - } - else if (unit->getType().isWorker() && target->getType().isRefinery() ) - { - unit->self->isGathering = true; - unit->self->order = Orders::MoveToGas; - } - else if (unit->getType().isWorker() && - target->getType().getRace() == Races::Terran && - target->getType().whatBuilds().first == unit->getType() && - !target->isCompleted()) - { - unit->self->order = Orders::ConstructingBuilding; - unit->self->buildUnit = getUnitID(target); - target->self->buildUnit = getUnitID(unit); - unit->self->isConstructing = true; - target->self->isConstructing = true; - } - else if ( unit->getType().canAttack() && target->getPlayer() != unit->getPlayer() && !target->getType().isNeutral() ) - { - unit->self->order = Orders::AttackUnit; - } - else if ( unit->getType().canMove() ) - { - unit->self->order = Orders::Follow; - } - } - else if (command.type == UnitCommandTypes::Set_Rally_Position) - { - if (!unit->getType().canProduce()) - return; - unit->self->order = Orders::RallyPointTile; - unit->self->rallyPositionX = position.x; - unit->self->rallyPositionY = position.y; - unit->self->rallyUnit = -1; - } - else if (command.type == UnitCommandTypes::Set_Rally_Unit) - { - if (!unit->getType().canProduce()) - return; - if ( !target || !target->self->exists ) - return; - unit->self->order = Orders::RallyPointUnit; - unit->self->rallyUnit = getUnitID(target); - } - else if (command.type == UnitCommandTypes::Siege) - { - unit->self->order = Orders::Sieging; - } - else if (command.type == UnitCommandTypes::Stop) - { - if ((frame<=Broodwar->getLatency() && Broodwar->getLatency()==2) || - (frame<=Broodwar->getLatency()+1 && Broodwar->getLatency()>2)) - { - unit->self->order = Orders::Stop; - unit->self->isIdle = true; - } - } - else if (command.type == UnitCommandTypes::Train) - { - if (savedExtra == -1) - savedExtra = unit->self->trainingQueueCount; - if ((frame < Broodwar->getLatency() && Broodwar->getLatency() == 2) || - (frame <= Broodwar->getLatency() && Broodwar->getLatency() > 2)) - { - unit->self->trainingQueue[unit->self->trainingQueueCount++] = unitType; - player->self->minerals -= unitType.mineralPrice(); - player->self->gas -= unitType.gasPrice(); - } - if ((frame <= Broodwar->getLatency() && Broodwar->getLatency() == 2) || - (frame <= Broodwar->getLatency()+1 && Broodwar->getLatency() > 2)) - { - if (savedExtra == 0) - { - unit->self->remainingTrainTime = unitType.buildTime(); // @TODO: fix to real build time - player->self->supplyUsed[unitType.getRace()] += unitType.supplyRequired(); - } - } - if (frame <= Broodwar->getLatency()) - { - unit->self->isTraining = true; - unit->self->isIdle = false; - if ( unitType == UnitTypes::Terran_Nuclear_Missile ) - unit->self->secondaryOrder = Orders::Train; - } - } - else if (command.type == UnitCommandTypes::Unburrow) - { - unit->self->order = Orders::Unburrowing; - } - else if (command.type == UnitCommandTypes::Unload) - { - unit->self->order = Orders::Unload; - unit->self->target = getUnitID(target); - } - else if (command.type == UnitCommandTypes::Unload_All) - { - if (unit->getType() == UnitTypes::Terran_Bunker) - { - unit->self->order = Orders::Unload; - } - else - { - unit->self->order = Orders::MoveUnload; - unit->self->targetPositionX = position.x; - unit->self->targetPositionY = position.y; - unit->self->orderTargetPositionX = position.x; - unit->self->orderTargetPositionY = position.y; - } - } - else if (command.type == UnitCommandTypes::Unload_All_Position) - { - unit->self->order = Orders::MoveUnload; - unit->self->targetPositionX = position.x; - unit->self->targetPositionY = position.y; - unit->self->orderTargetPositionX = position.x; - unit->self->orderTargetPositionY = position.y; - } - else if (command.type == UnitCommandTypes::Unsiege) - { - unit->self->order = Orders::Unsieging; - } - else if (command.type == UnitCommandTypes::Upgrade) - { - unit->self->order = Orders::Upgrade; - unit->self->upgrade = upgradeType; - unit->self->isIdle = false; - int level = unit->getPlayer()->getUpgradeLevel(upgradeType); - unit->self->remainingUpgradeTime = upgradeType.upgradeTime(level+1); // @TODO: Fix to real time - if (frame < Broodwar->getLatency()) - { - player->self->minerals -= upgradeType.mineralPrice(level+1); - player->self->gas -= upgradeType.gasPrice(level+1); - } - player->self->isUpgrading[upgradeType] = true; - } - else if (command.type == UnitCommandTypes::Use_Tech) - { - if ( techType == TechTypes::Stim_Packs && unit->self->hitPoints > 10 ) - { - unit->self->hitPoints -= 10; - unit->self->stimTimer = 17; - } - } - else if (command.type == UnitCommandTypes::Use_Tech_Position) - { - if ( !techType.targetsPosition() ) - return; - unit->self->order = techType.getOrder();; - unit->self->targetPositionX = position.x; - unit->self->targetPositionY = position.y; - unit->self->orderTargetPositionX = position.x; - unit->self->orderTargetPositionY = position.y; - } - else if (command.type == UnitCommandTypes::Use_Tech_Unit) - { - if ( !techType.targetsUnit() ) - return; - unit->self->order = techType.getOrder(); - unit->self->orderTarget = getUnitID(target); - unit->self->targetPositionX = target->getPosition().x; - unit->self->targetPositionY = target->getPosition().y; - unit->self->orderTargetPositionX = target->getPosition().x; - unit->self->orderTargetPositionY = target->getPosition().y; - } - } - -}; +}; \ No newline at end of file diff --git a/bwapi/BWAPI/Source/BWAPI/GameCommands.cpp b/bwapi/BWAPI/Source/BWAPI/GameCommands.cpp index eb919fc15..522ac0d6e 100755 --- a/bwapi/BWAPI/Source/BWAPI/GameCommands.cpp +++ b/bwapi/BWAPI/Source/BWAPI/GameCommands.cpp @@ -11,26 +11,29 @@ namespace BWAPI { //----------------------------------------- ADD TO COMMAND BUFFER ------------------------------------------ - void GameImpl::addToCommandBuffer(Command* command) + void GameImpl::addToCommandBuffer(Command command) { - //executes latency compensation code and added it to the buffer - command->execute(0); - this->commandBuffer[this->commandBuffer.size() - 1].push_back(command); + std::move(command).insertIntoCommandBuffer(this->commandBuffer); } //----------------------------------------- APPLY LATENCY COMPENSATION void GameImpl::applyLatencyCompensation() { - //apply latency compensation - while ((int)(this->commandBuffer.size()) > this->getLatency()+15) - { - for (unsigned int i = 0; i < this->commandBuffer.front().size(); ++i) - delete this->commandBuffer.front()[i]; - this->commandBuffer.erase(this->commandBuffer.begin()); - } - this->commandBuffer.push_back(std::vector()); - for (unsigned int i = 0; i < this->commandBuffer.size(); ++i) - for (unsigned int j = 0; j < this->commandBuffer[i].size(); ++j) - this->commandBuffer[i][j]->execute(this->commandBuffer.size()-1-i); + // Remove the current frame from the buffer and execute the current frame + // (only some actions execute on the current frame, like resource reserving) + if (!this->commandBuffer.empty()) { + for (auto &command : this->commandBuffer.front()) + command.execute(true, false); + this->commandBuffer.erase(std::begin(this->commandBuffer)); + } + + // Apply latency compensation + for (auto buf = 0; buf < static_cast(this->commandBuffer.size()); ++ buf) + for (auto &command : this->commandBuffer[buf]) + command.execute(false, buf > getRemainingLatencyFrames()); + + // Prepare buffer space for new commands + if (this->commandBuffer.size() < static_cast(getRemainingLatencyFrames() + 15)) + this->commandBuffer.resize(getRemainingLatencyFrames() + 15); } //--------------------------------------------- EXECUTE COMMAND -------------------------------------------- diff --git a/bwapi/BWAPI/Source/BWAPI/GameImpl.cpp b/bwapi/BWAPI/Source/BWAPI/GameImpl.cpp index 1d2b90370..33d708c63 100755 --- a/bwapi/BWAPI/Source/BWAPI/GameImpl.cpp +++ b/bwapi/BWAPI/Source/BWAPI/GameImpl.cpp @@ -1211,9 +1211,6 @@ namespace BWAPI frameCount = s.frameCount; flags = s.flags; - for(unsigned int j = 0; j < this->commandBuffer.size(); ++j) - for (unsigned int i = 0; i < this->commandBuffer[j].size(); ++i) - delete this->commandBuffer[j][i]; this->commandBuffer.clear(); this->commandBuffer.reserve(16); diff --git a/bwapi/BWAPI/Source/BWAPI/GameImpl.h b/bwapi/BWAPI/Source/BWAPI/GameImpl.h index a2014696d..d9f5617c5 100755 --- a/bwapi/BWAPI/Source/BWAPI/GameImpl.h +++ b/bwapi/BWAPI/Source/BWAPI/GameImpl.h @@ -18,6 +18,7 @@ #include #include "CommandOptimizer.h" +#include "Command.h" #include "APMCounter.h" #include "FPSCounter.h" #include "AutoMenuManager.h" @@ -44,7 +45,6 @@ namespace BWAPI { // forwards class AIModule; - class Command; class BulletImpl; class Bulletset; @@ -256,7 +256,7 @@ namespace BWAPI static void mouseDown(int x, int y); static void mouseUp(int x, int y); - void addToCommandBuffer(Command* command); + void addToCommandBuffer(Command command); void onGameStart(); void onGameEnd(); static int stormIdToPlayerId(int dwStormId); @@ -380,7 +380,7 @@ namespace BWAPI std::unordered_map regionMap; std::array bulletArray; - std::vector< std::vector > commandBuffer; + std::vector< std::vector > commandBuffer; void updateUnits(); void updateBullets(); diff --git a/bwapi/BWAPI/Source/BWAPI/GameInternals.cpp b/bwapi/BWAPI/Source/BWAPI/GameInternals.cpp index c329ab3df..b93297fb5 100755 --- a/bwapi/BWAPI/Source/BWAPI/GameInternals.cpp +++ b/bwapi/BWAPI/Source/BWAPI/GameInternals.cpp @@ -379,9 +379,6 @@ namespace BWAPI flags.fill(false); // Clear the latency buffer - for(unsigned int j = 0; j < this->commandBuffer.size(); ++j) - for (unsigned int i = 0; i < this->commandBuffer[j].size(); ++i) - delete this->commandBuffer[j][i]; this->commandBuffer.clear(); this->commandBuffer.reserve(16); diff --git a/bwapi/BWAPI/Source/BWAPI/UnitImpl.cpp b/bwapi/BWAPI/Source/BWAPI/UnitImpl.cpp index feadeb510..98d14291b 100755 --- a/bwapi/BWAPI/Source/BWAPI/UnitImpl.cpp +++ b/bwapi/BWAPI/Source/BWAPI/UnitImpl.cpp @@ -78,7 +78,7 @@ namespace BWAPI } // Add to command optimizer if possible, as well as the latency compensation buffer - BroodwarImpl.addToCommandBuffer(new Command(command)); + BroodwarImpl.addToCommandBuffer(Command{command}); return BroodwarImpl.commandOptimizer.add(command); } bool UnitImpl::issueCommand(UnitCommand command) diff --git a/bwapi/BWAPIClient/Source/UnitImpl.cpp b/bwapi/BWAPIClient/Source/UnitImpl.cpp index 1640baf7c..aca7acb74 100755 --- a/bwapi/BWAPIClient/Source/UnitImpl.cpp +++ b/bwapi/BWAPIClient/Source/UnitImpl.cpp @@ -55,7 +55,6 @@ namespace BWAPI c.x = command.x; c.y = command.y; c.extra = command.extra; - Command(command).execute(0); static_cast(BroodwarPtr)->addUnitCommand(c); lastCommandFrame = Broodwar->getFrameCount(); lastCommand = command;