diff --git a/client/src/components/sidebar/game/team-table.tsx b/client/src/components/sidebar/game/team-table.tsx index 07b3db7a..9267e57d 100644 --- a/client/src/components/sidebar/game/team-table.tsx +++ b/client/src/components/sidebar/game/team-table.tsx @@ -161,7 +161,7 @@ export const UnitsTable: React.FC = ({ teamStat, teamIdx }) => const GlobalUpgradeSection: React.FC<{ teamStat: TeamTurnStat | undefined }> = ({ teamStat }) => { const upgradeTypes: [schema.GlobalUpgradeType, string][] = [ - [schema.GlobalUpgradeType.ACTION_UPGRADE, 'Global Action Upgrade'], + [schema.GlobalUpgradeType.ACTION_UPGRADE, 'Global Attack Upgrade'], [schema.GlobalUpgradeType.CAPTURING_UPGRADE, 'Global Capturing Upgrade'], [schema.GlobalUpgradeType.HEALING_UPGRADE, 'Global Healing Upgrade'] ] diff --git a/client/src/constants.ts b/client/src/constants.ts index f2aec398..c7e07a62 100644 --- a/client/src/constants.ts +++ b/client/src/constants.ts @@ -1,7 +1,7 @@ import { schema } from 'battlecode-schema' -export const GAME_VERSION = '1.2.5' -export const SPEC_VERSION = '1.2.5' +export const GAME_VERSION = '2.0.0' +export const SPEC_VERSION = '2.0.0' export const BATTLECODE_YEAR: number = 2024 export const MAP_SIZE_RANGE = { min: 30, diff --git a/engine/src/main/battlecode/common/GameConstants.java b/engine/src/main/battlecode/common/GameConstants.java index 37d48b43..b8fa9370 100644 --- a/engine/src/main/battlecode/common/GameConstants.java +++ b/engine/src/main/battlecode/common/GameConstants.java @@ -9,7 +9,7 @@ public class GameConstants { /** * The current spec version the server compiles with. */ - public static final String SPEC_VERSION = "1.2.5"; + public static final String SPEC_VERSION = "2.0.0"; // ********************************* // ****** MAP CONSTANTS ************ @@ -72,7 +72,7 @@ public class GameConstants { public static final int DIG_COST = 20; /** Crumbs cost for filling */ - public static final int FILL_COST = 10; + public static final int FILL_COST = 30; /** Number of rounds between updating the random noisy flag broadcast location */ public static final int FLAG_BROADCAST_UPDATE_INTERVAL = 100; @@ -96,7 +96,7 @@ public class GameConstants { public static final int SETUP_ROUNDS = 200; /** Number of rounds between adding a global upgrade point */ - public static final int GLOBAL_UPGRADE_ROUNDS = 750; + public static final int GLOBAL_UPGRADE_ROUNDS = 600; /** Number of rounds robots must spend in jail before respawning */ public static final int JAILED_ROUNDS = 25; @@ -142,6 +142,6 @@ public class GameConstants { public static final int DIG_COOLDOWN = 20; /** The amount added to the action cooldown counter after filling */ - public static final int FILL_COOLDOWN = 20; + public static final int FILL_COOLDOWN = 30; } diff --git a/engine/src/main/battlecode/common/GlobalUpgrade.java b/engine/src/main/battlecode/common/GlobalUpgrade.java index 19cd721d..dcd667d4 100644 --- a/engine/src/main/battlecode/common/GlobalUpgrade.java +++ b/engine/src/main/battlecode/common/GlobalUpgrade.java @@ -1,31 +1,32 @@ package battlecode.common; /** - * Enumerates the possible types of global updates. More information about each update - * are available in the game specs. + * Enumerates the possible types of global updates. More information about each + * update + * are available in the game specs. */ public enum GlobalUpgrade { /** - * Action upgrade increases the amount cooldown drops per round by 4. + * Attack upgrade increases the base attack by 75. */ - ACTION(4, 0, 0), + ATTACK(75, 0, 0, 0), /** * Healing increases base heal by 50. */ - HEALING(0, 50, 0), + HEALING(0, 50, 0, 0), /** - * Capture upgrade increases the dropped flag delay from 4 rounds to 12 rounds. + * Capture upgrade increases the dropped flag delay from 4 rounds to 12 rounds. It also decreases the movement penalty for holding a flag by 8. */ - CAPTURING(0, 0, 8); + CAPTURING(0, 0, 8, -8); /** - * How much cooldown reduction changes + * How much base attack changes */ - public final int cooldownReductionChange; + public final int baseAttackChange; /** * How much base heal changes @@ -33,13 +34,19 @@ public enum GlobalUpgrade { public final int baseHealChange; /** - * how much dropped flag return delay changes + * How much dropped flag return delay changes */ public final int flagReturnDelayChange; - GlobalUpgrade(int cooldownReductionChange, int baseHealChange, int flagReturnDelayChange){ - this.cooldownReductionChange = cooldownReductionChange; + /** + * How much the movement penalty for holding a flag changes. + */ + public final int movementDelayChange; + + GlobalUpgrade(int baseAttackChange, int baseHealChange, int flagReturnDelayChange, int movementDelayChange) { + this.baseAttackChange = baseAttackChange; this.baseHealChange = baseHealChange; this.flagReturnDelayChange = flagReturnDelayChange; + this.movementDelayChange = movementDelayChange; } } diff --git a/engine/src/main/battlecode/common/MapInfo.java b/engine/src/main/battlecode/common/MapInfo.java index 57236ca4..183d4333 100644 --- a/engine/src/main/battlecode/common/MapInfo.java +++ b/engine/src/main/battlecode/common/MapInfo.java @@ -78,16 +78,14 @@ public boolean isSpawnZone() { } /** - * Returns 1 if this square is a Team A spawn zone, - * 2 if this square is a Team B spawn zone, and - * 0 if this square is not a spawn zone. + * Returns the team that owns that spawn zone at this location, or Team.NEUTRAL if the location is not a spawn zone. * - * @return 1 or 2 if the square is a Team A or B spawn zone, respectively; 0 otherwise + * @return The team that owns the spawn zone, or Team.NEUTRAL * * @battlecode.doc.costlymethod */ - public int getSpawnZoneTeam() { - return spawnZone; + public Team getSpawnZoneTeam() { + return spawnZone == 0 ? Team.NEUTRAL : (spawnZone == 1 ? Team.A : Team.B); } /** diff --git a/engine/src/main/battlecode/common/RobotController.java b/engine/src/main/battlecode/common/RobotController.java index f959aa20..884f9424 100644 --- a/engine/src/main/battlecode/common/RobotController.java +++ b/engine/src/main/battlecode/common/RobotController.java @@ -379,9 +379,7 @@ public strictfp interface RobotController { /** * Checks if the given location within vision radius is a legal starting flag placement. This is true when the - * location is in range for dropping the flag, is passable, and is far enough away from other placed friendly flags. - * Note that if the third condition is false, the flag can still be placed but will be teleported back to the spawn zone - * at the end of the setup phase. + * location is passable and is far enough away from other placed friendly flags. * * @param loc The location to check * @return Whether the location is a valid flag placement @@ -601,6 +599,15 @@ public strictfp interface RobotController { // ***** ATTACK / HEAL ******** // **************************** + /** + * Gets the true attack damage of this robot accounting for all effects. + * + * @return The attack damage + * + * @battlecode.doc.costlymethod + */ + int getAttackDamage(); + /** * Tests whether this robot can attack the given location. Robots can only attack * enemy robots, and attacks cannot miss. @@ -622,6 +629,15 @@ public strictfp interface RobotController { */ void attack(MapLocation loc) throws GameActionException; + /** + * Gets the true healing amount of this robot accounting for all effects. + * + * @return The heal amount + * + * @battlecode.doc.costlymethod + */ + int getHealAmount(); + /** * Tests whether this robot can heal a nearby friendly unit. * @@ -630,6 +646,8 @@ public strictfp interface RobotController { * * @param loc location of friendly unit to be healed * @return whether it is possible for this robot to heal + * + * @battlecode.doc.costlymethod */ boolean canHeal(MapLocation loc); @@ -771,6 +789,17 @@ public strictfp interface RobotController { * @battlecode.doc.costlymethod **/ void buyGlobal(GlobalUpgrade ug) throws GameActionException; + + /** + * Returns the global upgrades that the given team has + * + * @param team the team to get global upgrades for + * + * @return The global upgrades that the team has + * + * @battlecode.doc.costlymethod + */ + GlobalUpgrade[] getGlobalUpgrades(Team team); /** * Causes your team to lose the game. It's like typing "gg." diff --git a/engine/src/main/battlecode/common/SkillType.java b/engine/src/main/battlecode/common/SkillType.java index 5b19ee9e..372f6b66 100644 --- a/engine/src/main/battlecode/common/SkillType.java +++ b/engine/src/main/battlecode/common/SkillType.java @@ -35,9 +35,9 @@ public enum SkillType{ * @battlecode.doc.costlymethod */ public int getExperience(int level){ - int[] attackExperience = {0, 20, 40, 70, 100, 140, 180}; + int[] attackExperience = {0, 15, 30, 45, 75, 110, 150}; int[] buildExperience = {0, 5, 10, 15, 20, 25, 30}; - int[] healExperience = {0, 15, 30, 45, 75, 110, 150}; + int[] healExperience = {0, 20, 40, 70, 100, 140, 180}; switch(this){ case ATTACK: return attackExperience[level]; case BUILD: return buildExperience[level]; @@ -57,7 +57,7 @@ public int getExperience(int level){ * @battlecode.doc.costlymethod */ public int getCooldown(int level){ - int[] attackCooldown = {0, -5, -10, -15, -20, -30, -40}; + int[] attackCooldown = {0, -5, -7, -10, -20, -35, -60}; int[] buildCooldown = {0, -5, -10, -15, -20, -30, -50}; int[] healCooldown = {0, -5, -10, -15, -15, -15, -25}; switch(this){ @@ -79,7 +79,7 @@ public int getCooldown(int level){ * @battlecode.doc.costlymethod */ public int getSkillEffect(int level){ - int[] attackSkill = {0, 5, 10, 15, 20, 30, 50}; + int[] attackSkill = {0, 5, 7, 10, 30, 35, 60}; int[] buildSkill = {0, -10, -15, -20, -30, -40, -50}; int[] healSkill = {0, 3, 5, 7, 10, 15, 25}; switch(this){ @@ -99,9 +99,9 @@ public int getSkillEffect(int level){ * @battlecode.doc.costlymethod */ public int getPenalty(int level){ - int[] attackPenalty = {-1, -5, -5, -10, -10, -15, -15}; - int[] buildPenalty = {-1, -2, -2, -5, -5, -10, -10}; - int[] healPenalty = {-1, -2, -2, -5, -5, -10, -10}; + int[] attackPenalty = {-1, -2, -2, -5, -5, -10, -12}; + int[] buildPenalty = {-1, -2, -2, -3, -3, -4, -6}; + int[] healPenalty = {-1, -5, -5, -10, -10, -15, -18}; switch(this){ case ATTACK: return attackPenalty[level]; case BUILD: return buildPenalty[level]; diff --git a/engine/src/main/battlecode/common/TrapType.java b/engine/src/main/battlecode/common/TrapType.java index 7c37ca56..ff033c62 100644 --- a/engine/src/main/battlecode/common/TrapType.java +++ b/engine/src/main/battlecode/common/TrapType.java @@ -12,17 +12,17 @@ public enum TrapType { * When an opponent enters, explosive traps deal 750 damage to all opponents within a sqrt 13 radius * If an opponent digs/breaks under the trap, it deals 500 damage to all opponnets in radius sqrt 9 */ - EXPLOSIVE (250, 0, 13, 9, 750, 500, false, 5, true, 0), + EXPLOSIVE (250, 0, 4, 2, 750, 200, false, 5, true, 0), /** * When an opponent enters, water traps dig all unoccupied tiles within a radius of sqrt 9 */ - WATER (100, 1, 9, 0, 0, 0, true, 5, true, 0), + WATER (100, 2, 9, 0, 0, 0, true, 5, true, 0), /** * When an opponent enters, all opponent robots movement and action cooldowns are set to 40. */ - STUN (100, 1, 13, 0, 0, 0, false, 5, true, 40), + STUN (100, 2, 13, 0, 0, 0, false, 5, true, 50), NONE (100, 0, 0, 0, 0, 0, false, 0, false, 0); @@ -92,4 +92,4 @@ public enum TrapType { this.isInvisible = isInvisible; this.opponentCooldown = opponentCooldown; } -} \ No newline at end of file +} diff --git a/engine/src/main/battlecode/instrumenter/bytecode/resources/MethodCosts.txt b/engine/src/main/battlecode/instrumenter/bytecode/resources/MethodCosts.txt index 9f256616..21da8968 100644 --- a/engine/src/main/battlecode/instrumenter/bytecode/resources/MethodCosts.txt +++ b/engine/src/main/battlecode/instrumenter/bytecode/resources/MethodCosts.txt @@ -25,6 +25,7 @@ battlecode/common/MapLocation/valueOf 25 fal battlecode/common/RobotController/adjacentLocation 1 true battlecode/common/RobotController/attack 0 true battlecode/common/RobotController/canAttack 5 true +battlecode/common/RobotController/getAttackDamage 1 true battlecode/common/RobotController/canMove 10 true battlecode/common/RobotController/canSenseLocation 5 true battlecode/common/RobotController/canSenseRobot 5 true @@ -76,6 +77,7 @@ battlecode/common/RobotController/canBuild 10 tru battlecode/common/RobotController/build 0 true battlecode/common/RobotController/canHeal 10 true battlecode/common/RobotController/heal 0 true +battlecode/common/RobotController/getHealAmount 1 true battlecode/common/RobotController/hasFlag 5 true battlecode/common/RobotController/canPickupFlag 10 true battlecode/common/RobotController/pickupFlag 0 true @@ -83,6 +85,7 @@ battlecode/common/RobotController/canDropFlag 10 tru battlecode/common/RobotController/dropFlag 0 true battlecode/common/RobotController/canBuyGlobal 10 true battlecode/common/RobotController/buyGlobal 0 true +battlecode/common/RobotController/getGlobalUpgrades 10 true battlecode/common/Team/opponent 1 false battlecode/common/Team/isPlayer 1 false battlecode/common/MapInfo/getMapLocation 1 false diff --git a/engine/src/main/battlecode/server/GameMaker.java b/engine/src/main/battlecode/server/GameMaker.java index d0bf1373..3c345639 100644 --- a/engine/src/main/battlecode/server/GameMaker.java +++ b/engine/src/main/battlecode/server/GameMaker.java @@ -54,6 +54,7 @@ private enum State { */ DONE } + private State state; // this un-separation-of-concerns makes me uncomfortable @@ -70,7 +71,8 @@ private enum State { /** * We have a separate byte[] for each packet sent to the client. - * This is necessary because flatbuffers shares metadata between structures, so we + * This is necessary because flatbuffers shares metadata between structures, so + * we * can't just cut out chunks of the larger buffer :/ */ private FlatBufferBuilder packetBuilder; @@ -107,8 +109,8 @@ private enum State { private final boolean showIndicators; /** - * @param gameInfo the mapping of teams to bytes - * @param packetSink the NetServer to send packets to + * @param gameInfo the mapping of teams to bytes + * @param packetSink the NetServer to send packets to * @param showIndicators whether to write indicator dots and lines to replay */ public GameMaker(final GameInfo gameInfo, final NetServer packetSink, final boolean showIndicators) { @@ -139,8 +141,8 @@ public GameMaker(final GameInfo gameInfo, final NetServer packetSink, final bool */ private void assertState(State state) { if (this.state != state) { - throw new RuntimeException("Incorrect GameMaker state: should be "+ - state+", but is: "+this.state); + throw new RuntimeException("Incorrect GameMaker state: should be " + + state + ", but is: " + this.state); } } @@ -152,7 +154,6 @@ private void changeState(State start, State end) { this.state = end; } - /** * Convert entire game to a byte array. * @@ -209,7 +210,8 @@ public void writeGame(File saveFile) { /** * Run the same logic for both builders. * - * @param perBuilder called with each builder; return event id. Should not mutate state. + * @param perBuilder called with each builder; return event id. Should not + * mutate state. */ private void createEvent(ToIntFunction perBuilder) { // make file event and add its offset to the list @@ -257,7 +259,7 @@ public void makeGameHeader() { TeamData.addPackageName(builder, packageName); TeamData.addTeamId(builder, TeamMapping.id(Team.B)); int teamBOffset = TeamData.endTeamData(builder); - int[] teamsVec = {teamAOffset, teamBOffset}; + int[] teamsVec = { teamAOffset, teamBOffset }; int teamsOffset = GameHeader.createTeamsVector(builder, teamsVec); int specializationMetadataOffset = makeSpecializationMetadata(builder); @@ -291,23 +293,21 @@ public void makeGameHeader() { public int makeSpecializationMetadata(FlatBufferBuilder builder) { TIntArrayList specializationMetadataOffsets = new TIntArrayList(); - for(SkillType type : SkillType.values()) { - for(int l = 0; l <= 6; l++) { + for (SkillType type : SkillType.values()) { + for (int l = 0; l <= 6; l++) { SpecializationMetadata.startSpecializationMetadata(builder); SpecializationMetadata.addType(builder, skillTypeToSpecializationType(type)); SpecializationMetadata.addLevel(builder, l); SpecializationMetadata.addActionJailedPenalty(builder, type.getPenalty(l)); SpecializationMetadata.addCooldownReduction(builder, type.getCooldown(l)); int effect = type.getSkillEffect(l); - if(type == SkillType.ATTACK) { + if (type == SkillType.ATTACK) { SpecializationMetadata.addDamageIncrease(builder, effect); SpecializationMetadata.addHealIncrease(builder, 0); - } - else if(type == SkillType.BUILD) { + } else if (type == SkillType.BUILD) { SpecializationMetadata.addDamageIncrease(builder, 0); SpecializationMetadata.addHealIncrease(builder, 0); - } - else if(type == SkillType.HEAL) { + } else if (type == SkillType.HEAL) { SpecializationMetadata.addDamageIncrease(builder, 0); SpecializationMetadata.addHealIncrease(builder, effect); } @@ -319,7 +319,7 @@ else if(type == SkillType.HEAL) { public int makeBuildActionMetadata(FlatBufferBuilder builder) { TIntArrayList buildActionMetadataOffsets = new TIntArrayList(); - for(TrapType type : TrapType.values()) { + for (TrapType type : TrapType.values()) { BuildActionMetadata.startBuildActionMetadata(builder); BuildActionMetadata.addType(builder, trapTypeToBuildActionType(type)); BuildActionMetadata.addCost(builder, type.buildCost); @@ -339,9 +339,9 @@ public int makeBuildActionMetadata(FlatBufferBuilder builder) { return GameHeader.createBuildActionMetadataVector(builder, buildActionMetadataOffsets.toArray()); } - public int makeGlobalUpgradeMetadata(FlatBufferBuilder builder){ + public int makeGlobalUpgradeMetadata(FlatBufferBuilder builder) { TIntArrayList globalUpgradeMetadataOffsets = new TIntArrayList(); - for(GlobalUpgrade upgrade : GlobalUpgrade.values()) { + for (GlobalUpgrade upgrade : GlobalUpgrade.values()) { GlobalUpgradeMetadata.startGlobalUpgradeMetadata(builder); GlobalUpgradeMetadata.addType(builder, FlatHelpers.getGlobalUpgradeTypeFromGlobalUpgrade(upgrade)); GlobalUpgradeMetadata.addUpgradeAmount(builder, getUpgradeAmount(upgrade)); @@ -351,23 +351,32 @@ public int makeGlobalUpgradeMetadata(FlatBufferBuilder builder){ } private byte skillTypeToSpecializationType(SkillType type) { - if (type == SkillType.ATTACK) return SpecializationType.ATTACK; - if (type == SkillType.BUILD) return SpecializationType.BUILD; - if (type == SkillType.HEAL) return SpecializationType.HEAL; + if (type == SkillType.ATTACK) + return SpecializationType.ATTACK; + if (type == SkillType.BUILD) + return SpecializationType.BUILD; + if (type == SkillType.HEAL) + return SpecializationType.HEAL; return Byte.MIN_VALUE; } private byte trapTypeToBuildActionType(TrapType type) { - if (type == TrapType.EXPLOSIVE) return BuildActionType.EXPLOSIVE_TRAP; - if (type == TrapType.WATER) return BuildActionType.WATER_TRAP; - if (type == TrapType.STUN) return BuildActionType.STUN_TRAP; + if (type == TrapType.EXPLOSIVE) + return BuildActionType.EXPLOSIVE_TRAP; + if (type == TrapType.WATER) + return BuildActionType.WATER_TRAP; + if (type == TrapType.STUN) + return BuildActionType.STUN_TRAP; return Byte.MIN_VALUE; } private int getUpgradeAmount(GlobalUpgrade gu) { - if(gu == GlobalUpgrade.ACTION) return gu.cooldownReductionChange; - if(gu == GlobalUpgrade.HEALING) return gu.baseHealChange; - if(gu == GlobalUpgrade.CAPTURING) return gu.flagReturnDelayChange; + if (gu == GlobalUpgrade.ATTACK) + return gu.baseAttackChange; + if (gu == GlobalUpgrade.HEALING) + return gu.baseHealChange; + if (gu == GlobalUpgrade.CAPTURING) + return gu.flagReturnDelayChange; return 0; } @@ -488,7 +497,7 @@ public MatchMaker() { this.teamAComm = new TIntArrayList(); this.teamBComm = new TIntArrayList(); this.trapAddedIds = new TIntArrayList(); - this.trapAddedX = new TIntArrayList(); + this.trapAddedX = new TIntArrayList(); this.trapAddedY = new TIntArrayList(); this.trapAddedTypes = new TByteArrayList(); this.trapAddedTeams = new TByteArrayList(); @@ -533,7 +542,8 @@ public void makeMatchHeader(LiveMap gameMap) { clearData(); } - public void makeMatchFooter(Team winTeam, DominationFactor winType, int totalRounds, List profilerCollections) { + public void makeMatchFooter(Team winTeam, DominationFactor winType, int totalRounds, + List profilerCollections) { changeState(State.IN_MATCH, State.IN_GAME); createEvent((builder) -> { @@ -576,8 +586,8 @@ public void makeMatchFooter(Team winTeam, DominationFactor winType, int totalRou int profilerFilesOffset = MatchFooter.createProfilerFilesVector(builder, profilerFiles.toArray()); return EventWrapper.createEventWrapper(builder, Event.MatchFooter, - MatchFooter.createMatchFooter(builder, TeamMapping.id(winTeam), - FlatHelpers.getWinTypeFromDominationFactor(winType), totalRounds, profilerFilesOffset)); + MatchFooter.createMatchFooter(builder, TeamMapping.id(winTeam), + FlatHelpers.getWinTypeFromDominationFactor(winType), totalRounds, profilerFilesOffset)); }); matchFooters.add(events.size() - 1); @@ -609,7 +619,8 @@ public void makeRound(int roundNum) { int robotIDsP = Round.createRobotIdsVector(builder, robotIds.toArray()); int robotLocsP = createVecTable(builder, robotLocsX, robotLocsY); int robotMoveCooldownsP = Round.createRobotMoveCooldownsVector(builder, robotMoveCooldowns.toArray()); - int robotActionCooldownsP = Round.createRobotActionCooldownsVector(builder, robotActionCooldowns.toArray()); + int robotActionCooldownsP = Round.createRobotActionCooldownsVector(builder, + robotActionCooldowns.toArray()); int robotHealthsP = Round.createRobotHealthsVector(builder, robotHealths.toArray()); int attacksPerformedP = Round.createAttacksPerformedVector(builder, attacksPerformed.toArray()); int attackLevelsP = Round.createAttackLevelsVector(builder, attackLevels.toArray()); @@ -657,13 +668,15 @@ public void makeRound(int roundNum) { // The indicator dots that were set int indicatorDotIDsP = Round.createIndicatorDotIdsVector(builder, indicatorDotIds.toArray()); int indicatorDotLocsP = createVecTable(builder, indicatorDotLocsX, indicatorDotLocsY); - int indicatorDotRGBsP = createRGBTable(builder, indicatorDotRGBsRed, indicatorDotRGBsGreen, indicatorDotRGBsBlue); + int indicatorDotRGBsP = createRGBTable(builder, indicatorDotRGBsRed, indicatorDotRGBsGreen, + indicatorDotRGBsBlue); // The indicator lines that were set int indicatorLineIDsP = Round.createIndicatorLineIdsVector(builder, indicatorLineIds.toArray()); int indicatorLineStartLocsP = createVecTable(builder, indicatorLineStartLocsX, indicatorLineStartLocsY); int indicatorLineEndLocsP = createVecTable(builder, indicatorLineEndLocsX, indicatorLineEndLocsY); - int indicatorLineRGBsP = createRGBTable(builder, indicatorLineRGBsRed, indicatorLineRGBsGreen, indicatorLineRGBsBlue); + int indicatorLineRGBsP = createRGBTable(builder, indicatorLineRGBsRed, indicatorLineRGBsGreen, + indicatorLineRGBsBlue); // The bytecode usage int bytecodeIDsP = Round.createBytecodeIdsVector(builder, bytecodeIds.toArray()); @@ -794,8 +807,10 @@ public void addFillLocation(MapLocation loc) { public void addTeamInfo(Team team, int breadAmount, int[] sharedArray) { teamIDs.add(TeamMapping.id(team)); teamBreadAmounts.add(breadAmount); - if(team == Team.A) teamAComm = new TIntArrayList(sharedArray); - else if(team == Team.B) teamBComm = new TIntArrayList(sharedArray); + if (team == Team.A) + teamAComm = new TIntArrayList(sharedArray); + else if (team == Team.B) + teamBComm = new TIntArrayList(sharedArray); } public void addIndicatorString(int id, String string) { diff --git a/engine/src/main/battlecode/util/FlatHelpers.java b/engine/src/main/battlecode/util/FlatHelpers.java index 6c5677cc..c6a70f29 100644 --- a/engine/src/main/battlecode/util/FlatHelpers.java +++ b/engine/src/main/battlecode/util/FlatHelpers.java @@ -26,9 +26,9 @@ * @author james */ public class FlatHelpers { - + public static byte getTrapActionFromTrapType(TrapType type) { - switch(type) { + switch (type) { case EXPLOSIVE: return Action.EXPLOSIVE_TRAP; case WATER: @@ -41,7 +41,7 @@ public static byte getTrapActionFromTrapType(TrapType type) { } public static byte getBuildActionFromTrapType(TrapType type) { - switch(type) { + switch (type) { case EXPLOSIVE: return BuildActionType.EXPLOSIVE_TRAP; case WATER: @@ -73,13 +73,15 @@ public static byte getWinTypeFromDominationFactor(DominationFactor factor) { } public static byte getGlobalUpgradeTypeFromGlobalUpgrade(GlobalUpgrade gu) { - if(gu == GlobalUpgrade.ACTION) return GlobalUpgradeType.ACTION_UPGRADE; - if(gu == GlobalUpgrade.HEALING) return GlobalUpgradeType.HEALING_UPGRADE; - if(gu == GlobalUpgrade.CAPTURING) return GlobalUpgradeType.CAPTURING_UPGRADE; + if (gu == GlobalUpgrade.ATTACK) + return GlobalUpgradeType.ACTION_UPGRADE; + if (gu == GlobalUpgrade.HEALING) + return GlobalUpgradeType.HEALING_UPGRADE; + if (gu == GlobalUpgrade.CAPTURING) + return GlobalUpgradeType.CAPTURING_UPGRADE; return Byte.MIN_VALUE; } - /** * DO NOT CALL THIS WITH OFFSETS! * Only call it when you're adding an actual int[] to a buffer, @@ -92,17 +94,17 @@ public static byte getGlobalUpgradeTypeFromGlobalUpgrade(GlobalUpgrade gu) { * int xyzP = intVector(builder, xyz, BufferType::startXyzVector); */ // public static int intVector(FlatBufferBuilder builder, - // TIntList arr, - // ObjIntConsumer start) { - // final int length = arr.size(); - // start.accept(builder, length); - - // // arrays go backwards in flatbuffers - // // for reasons - // for (int i = length - 1; i >= 0; i--) { - // builder.addInt(arr.get(i)); - // } - // return builder.endVector(); + // TIntList arr, + // ObjIntConsumer start) { + // final int length = arr.size(); + // start.accept(builder, length); + + // // arrays go backwards in flatbuffers + // // for reasons + // for (int i = length - 1; i >= 0; i--) { + // builder.addInt(arr.get(i)); + // } + // return builder.endVector(); // } /** @@ -111,58 +113,58 @@ public static byte getGlobalUpgradeTypeFromGlobalUpgrade(GlobalUpgrade gu) { * Call this when you're adding a table of offsets, not flat ints. */ // public static int offsetVector(FlatBufferBuilder builder, - // TIntList arr, - // ObjIntConsumer start) { - // final int length = arr.size(); - // start.accept(builder, length); - - // // arrays go backwards in flatbuffers - // // for reasons - // for (int i = length - 1; i >= 0; i--) { - // builder.addOffset(arr.get(i)); - // } - // return builder.endVector(); + // TIntList arr, + // ObjIntConsumer start) { + // final int length = arr.size(); + // start.accept(builder, length); + + // // arrays go backwards in flatbuffers + // // for reasons + // for (int i = length - 1; i >= 0; i--) { + // builder.addOffset(arr.get(i)); + // } + // return builder.endVector(); // } // public static int floatVector(FlatBufferBuilder builder, - // TFloatList arr, - // ObjIntConsumer start) { - // final int length = arr.size(); - // start.accept(builder, length); - - // for (int i = length - 1; i >= 0; i--) { - // builder.addFloat(arr.get(i)); - // } - // return builder.endVector(); + // TFloatList arr, + // ObjIntConsumer start) { + // final int length = arr.size(); + // start.accept(builder, length); + + // for (int i = length - 1; i >= 0; i--) { + // builder.addFloat(arr.get(i)); + // } + // return builder.endVector(); // } // public static int byteVector(FlatBufferBuilder builder, - // TByteList arr, - // ObjIntConsumer start) { - // final int length = arr.size(); - // start.accept(builder, length); - - // for (int i = length - 1; i >= 0; i--) { - // builder.addByte(arr.get(i)); - // } - // return builder.endVector(); + // TByteList arr, + // ObjIntConsumer start) { + // final int length = arr.size(); + // start.accept(builder, length); + + // for (int i = length - 1; i >= 0; i--) { + // builder.addByte(arr.get(i)); + // } + // return builder.endVector(); // } // public static int charVector(FlatBufferBuilder builder, - // TCharList arr, - // ObjIntConsumer start) { - // final int length = arr.size(); - // start.accept(builder, length); - - // for (int i = length - 1; i >= 0; i--) { - // builder.addInt(arr.get(i)); - // } - // return builder.endVector(); + // TCharList arr, + // ObjIntConsumer start) { + // final int length = arr.size(); + // start.accept(builder, length); + + // for (int i = length - 1; i >= 0; i--) { + // builder.addInt(arr.get(i)); + // } + // return builder.endVector(); // } public static int createVecTable(FlatBufferBuilder builder, TIntList xs, TIntList ys) { if (xs.size() != ys.size()) { - throw new RuntimeException("Mismatched x/y length: "+xs.size()+" != "+ys.size()); + throw new RuntimeException("Mismatched x/y length: " + xs.size() + " != " + ys.size()); } // int xsP = intVector(builder, xs, VecTable::startXsVector); // int ysP = intVector(builder, ys, VecTable::startYsVector); @@ -173,7 +175,7 @@ public static int createVecTable(FlatBufferBuilder builder, TIntList xs, TIntLis public static int createRGBTable(FlatBufferBuilder builder, TIntList red, TIntList green, TIntList blue) { if (red.size() != green.size() || green.size() != blue.size()) { - throw new RuntimeException("Mismatched lengths: "+red.size()+", "+green.size()+", "+blue.size()); + throw new RuntimeException("Mismatched lengths: " + red.size() + ", " + green.size() + ", " + blue.size()); } // int redP = intVector(builder, red, RGBTable::startRedVector); // int greenP = intVector(builder, green, RGBTable::startGreenVector); diff --git a/engine/src/main/battlecode/world/GameWorld.java b/engine/src/main/battlecode/world/GameWorld.java index 911b273c..8272e172 100644 --- a/engine/src/main/battlecode/world/GameWorld.java +++ b/engine/src/main/battlecode/world/GameWorld.java @@ -395,7 +395,7 @@ public void triggerTrap(Trap trap, InternalRobot robot, boolean entered){ break; case EXPLOSIVE: int rad = type.interactRadius; - int dmg = type.enterDamage; + int dmg = type.interactDamage; if (entered){ rad = type.enterRadius; dmg = type.enterDamage; diff --git a/engine/src/main/battlecode/world/InternalRobot.java b/engine/src/main/battlecode/world/InternalRobot.java index 903812c0..86562da6 100644 --- a/engine/src/main/battlecode/world/InternalRobot.java +++ b/engine/src/main/battlecode/world/InternalRobot.java @@ -8,8 +8,8 @@ /** * The representation of a robot used by the server. * Comparable ordering: - * - tiebreak by creation time (priority to later creation) - * - tiebreak by robot ID (priority to lower ID) + * - tiebreak by creation time (priority to later creation) + * - tiebreak by robot ID (priority to lower ID) */ public strictfp class InternalRobot implements Comparable { @@ -50,9 +50,9 @@ public strictfp class InternalRobot implements Comparable { /** * Create a new internal representation of a robot * - * @param gw the world the robot exists in + * @param gw the world the robot exists in * @param type the type of the robot - * @param loc the location of the robot + * @param loc the location of the robot * @param team the team of the robot */ public InternalRobot(GameWorld gw, int id, Team team) { @@ -118,20 +118,20 @@ public int getHealth() { return health; } - public int getExp(SkillType skill){ - if(skill == SkillType.BUILD) + public int getExp(SkillType skill) { + if (skill == SkillType.BUILD) return buildExp; - if(skill == SkillType.HEAL) + if (skill == SkillType.HEAL) return healExp; - if(skill == SkillType.ATTACK) + if (skill == SkillType.ATTACK) return attackExp; return 0; } - public int getLevel(SkillType skill){ + public int getLevel(SkillType skill) { int exp = this.getExp(skill); - for(int i = 0; i <= 5; i++){ - if (exp < skill.getExperience(i+1)){ + for (int i = 0; i <= 5; i++) { + if (exp < skill.getExperience(i + 1)) { return i; } } @@ -201,8 +201,9 @@ public RobotInfo getRobotInfo() { return cachedRobotInfo; } - this.cachedRobotInfo = new RobotInfo(ID, team, health, location, flag != null, - SkillType.ATTACK.getLevel(attackExp), SkillType.HEAL.getLevel(healExp), SkillType.BUILD.getLevel(buildExp)); + this.cachedRobotInfo = new RobotInfo(ID, team, health, location, flag != null, + SkillType.ATTACK.getLevel(attackExp), SkillType.HEAL.getLevel(healExp), + SkillType.BUILD.getLevel(buildExp)); return this.cachedRobotInfo; } @@ -277,7 +278,8 @@ public void setIndicatorString(String string) { public void setLocation(MapLocation loc) { this.gameWorld.moveRobot(getLocation(), loc); this.gameWorld.getObjectInfo().moveRobot(this, loc); - if(flag != null) flag.setLoc(loc); + if (flag != null) + flag.setLoc(loc); this.location = loc; } @@ -292,7 +294,13 @@ public void addActionCooldownTurns(int numActionCooldownToAdd) { * Resets the movement cooldown. */ public void addMovementCooldownTurns() { - setMovementCooldownTurns(this.movementCooldownTurns + (hasFlag() ? GameConstants.FLAG_MOVEMENT_COOLDOWN : GameConstants.MOVEMENT_COOLDOWN)); + if (hasFlag() && this.gameWorld.getTeamInfo().getGlobalUpgrades(team)[1]) { + setMovementCooldownTurns(this.movementCooldownTurns + GameConstants.FLAG_MOVEMENT_COOLDOWN + - GlobalUpgrade.CAPTURING.movementDelayChange); + } else { + setMovementCooldownTurns(this.movementCooldownTurns + + (hasFlag() ? GameConstants.FLAG_MOVEMENT_COOLDOWN : GameConstants.MOVEMENT_COOLDOWN)); + } } /** @@ -329,38 +337,41 @@ public void addHealth(int healthAmount) { /** * Removes exp from a robot when it is jailed */ - public void jailedPenalty(){ - if(this.buildExp == 0 && this.attackExp == 0 && this.healExp == 0) return; - int attackLevel = getLevel(SkillType.ATTACK), buildLevel = getLevel(SkillType.BUILD), healLevel = getLevel(SkillType.HEAL); - if(attackLevel >= buildLevel && attackLevel >= healLevel) { + public void jailedPenalty() { + if (this.buildExp == 0 && this.attackExp == 0 && this.healExp == 0) + return; + int attackLevel = getLevel(SkillType.ATTACK), buildLevel = getLevel(SkillType.BUILD), + healLevel = getLevel(SkillType.HEAL); + if (attackLevel >= buildLevel && attackLevel >= healLevel) { this.attackExp += SkillType.ATTACK.getPenalty(attackLevel); this.attackExp = Math.max(0, this.attackExp); - } - else if(buildLevel >= attackLevel && buildLevel >= healLevel){ + } else if (buildLevel >= attackLevel && buildLevel >= healLevel) { this.buildExp += SkillType.BUILD.getPenalty(buildLevel); this.buildExp = Math.max(0, this.buildExp); - } - else{ + } else { this.healExp += SkillType.HEAL.getPenalty(healLevel); this.healExp = Math.max(0, this.healExp); - } + } } /** * increment exp for a robot */ public void incrementSkill(SkillType skill) { - if(skill == SkillType.BUILD) - if(this.buildExp < skill.getExperience(3) || (getLevel(SkillType.HEAL) < 4 && getLevel(SkillType.ATTACK) < 4)){ - this.buildExp ++; + if (skill == SkillType.BUILD) + if (this.buildExp < skill.getExperience(3) + || (getLevel(SkillType.HEAL) < 4 && getLevel(SkillType.ATTACK) < 4)) { + this.buildExp++; } - if(skill == SkillType.HEAL) - if(this.healExp < skill.getExperience(3) || (getLevel(SkillType.BUILD) < 4 && getLevel(SkillType.ATTACK) < 4)){ - this.healExp ++; + if (skill == SkillType.HEAL) + if (this.healExp < skill.getExperience(3) + || (getLevel(SkillType.BUILD) < 4 && getLevel(SkillType.ATTACK) < 4)) { + this.healExp++; } - if(skill == SkillType.ATTACK) - if(this.attackExp < skill.getExperience(3) || (getLevel(SkillType.BUILD) < 4 && getLevel(SkillType.HEAL) < 4)){ - this.attackExp ++; + if (skill == SkillType.ATTACK) + if (this.attackExp < skill.getExperience(3) + || (getLevel(SkillType.BUILD) < 4 && getLevel(SkillType.HEAL) < 4)) { + this.attackExp++; } } @@ -378,17 +389,17 @@ public void spawn(MapLocation loc) { this.location = loc; this.roundsAlive = 0; this.health = GameConstants.DEFAULT_HEALTH; - //this.actionCooldownTurns = GameConstants.COOLDOWN_LIMIT; - //this.movementCooldownTurns = GameConstants.COOLDOWN_LIMIT; + // this.actionCooldownTurns = GameConstants.COOLDOWN_LIMIT; + // this.movementCooldownTurns = GameConstants.COOLDOWN_LIMIT; } public void despawn() { this.spawnCooldownTurns = GameConstants.COOLDOWNS_PER_TURN * GameConstants.JAILED_ROUNDS; jailedPenalty(); - if(flag != null){ + if (flag != null) { this.gameWorld.addFlag(location, flag); this.gameWorld.getMatchMaker().addAction(flag.getId(), Action.PLACE_FLAG, locationToInt(location)); - removeFlag(); + removeFlag(); } this.spawned = false; this.diedLocation = this.location; @@ -398,9 +409,13 @@ public void despawn() { public boolean isSpawned() { return this.spawned; } - - private int getDamage() { - int damage = Math.round(SkillType.ATTACK.skillEffect * ((float) SkillType.ATTACK.getSkillEffect(this.getLevel(SkillType.ATTACK)) / 100 + 1)); + + public int getDamage() { + int baseDamage = SkillType.ATTACK.skillEffect; + if (this.gameWorld.getTeamInfo().getGlobalUpgrades(team)[0]) + baseDamage += GlobalUpgrade.ATTACK.baseAttackChange; + int damage = Math.round( + baseDamage * ((float) SkillType.ATTACK.getSkillEffect(this.getLevel(SkillType.ATTACK)) / 100 + 1)); return damage; } @@ -422,8 +437,8 @@ public void attack(MapLocation loc) { int dmg = getDamage(); int newEnemyHealth = bot.getHealth() - dmg; - if(newEnemyHealth <= 0) { - if(gameWorld.getTeamSide(getLocation()) == (team.opponent() == Team.A ? 1 : 2)) { + if (newEnemyHealth <= 0) { + if (gameWorld.getTeamSide(getLocation()) == (team.opponent() == Team.A ? 1 : 2)) { addResourceAmount(GameConstants.KILL_CRUMB_REWARD); } } @@ -436,11 +451,11 @@ public void attack(MapLocation loc) { public int getHeal() { int base_heal = SkillType.HEAL.skillEffect; - //check for upgrade - if (this.gameWorld.getTeamInfo().getGlobalUpgrades(team)[2]){ + // check for upgrade + if (this.gameWorld.getTeamInfo().getGlobalUpgrades(team)[2]) { base_heal += GlobalUpgrade.HEALING.baseHealChange; } - return Math.round(base_heal * ((float) SkillType.HEAL.getSkillEffect(this.getLevel(SkillType.HEAL)) / 100 + 1)); + return Math.round(base_heal * ((float) SkillType.HEAL.getSkillEffect(this.getLevel(SkillType.HEAL)) / 100 + 1)); } public int getBuildExp() { @@ -455,7 +470,7 @@ public int getAttackExp() { return this.attackExp; } - public void addTrapTrigger(Trap t, boolean entered){ + public void addTrapTrigger(Trap t, boolean entered) { this.trapsToTrigger.add(t); this.enteredTraps.add(entered); } @@ -472,14 +487,13 @@ public void processBeginningOfRound() { public void processBeginningOfTurn() { this.actionCooldownTurns = Math.max(0, this.actionCooldownTurns - GameConstants.COOLDOWNS_PER_TURN); - if (this.gameWorld.getTeamInfo().getGlobalUpgrades(team)[0]) this.actionCooldownTurns = Math.max(0, this.actionCooldownTurns - GlobalUpgrade.ACTION.cooldownReductionChange); this.movementCooldownTurns = Math.max(0, this.movementCooldownTurns - GameConstants.COOLDOWNS_PER_TURN); this.spawnCooldownTurns = Math.max(0, this.spawnCooldownTurns - GameConstants.COOLDOWNS_PER_TURN); this.currentBytecodeLimit = GameConstants.BYTECODE_LIMIT; } public void processEndOfTurn() { - for (int i = 0; i < trapsToTrigger.size(); i++){ + for (int i = 0; i < trapsToTrigger.size(); i++) { this.gameWorld.triggerTrap(trapsToTrigger.get(i), this, enteredTraps.get(i)); } this.trapsToTrigger = new ArrayList<>(); @@ -487,11 +501,11 @@ public void processEndOfTurn() { // bytecode stuff! this.gameWorld.getMatchMaker().addBytecodes(this.ID, this.bytecodesUsed); // indicator strings! - if(!indicatorString.equals("")) this.gameWorld.getMatchMaker().addIndicatorString(this.ID, this.indicatorString); + if (!indicatorString.equals("")) + this.gameWorld.getMatchMaker().addIndicatorString(this.ID, this.indicatorString); this.roundsAlive++; } - // ********************************* // ****** BYTECODE METHODS ********* // ********************************* diff --git a/engine/src/main/battlecode/world/RobotControllerImpl.java b/engine/src/main/battlecode/world/RobotControllerImpl.java index e4c42f3f..ae5d4e93 100644 --- a/engine/src/main/battlecode/world/RobotControllerImpl.java +++ b/engine/src/main/battlecode/world/RobotControllerImpl.java @@ -322,7 +322,7 @@ public MapLocation[] senseBroadcastFlagLocations() { @Override public boolean senseLegalStartingFlagPlacement(MapLocation loc) throws GameActionException{ assertCanSenseLocation(loc); - if(!canDropFlag(loc)) return false; + if(!gameWorld.isPassable(loc)) return false; boolean valid = true; for(Flag x : gameWorld.getAllFlags()) { if(x.getId() != robot.getFlag().getId() && x.getTeam() == robot.getTeam() && @@ -504,11 +504,7 @@ public void move(Direction dir) throws GameActionException { if (trap.getTeam() == this.robot.getTeam()){ continue; } - if (this.gameWorld.hasTrap(nextLoc) && this.gameWorld.getTrap(nextLoc) == trap) { - this.robot.addTrapTrigger(trap, true); - } else { - this.robot.addTrapTrigger(trap, false); - } + this.robot.addTrapTrigger(trap, true); } if (this.robot.hasFlag() && this.robot.getFlag().getTeam() != this.robot.getTeam() @@ -665,7 +661,6 @@ public void fill(MapLocation loc) throws GameActionException{ int cooldownIncrease = (int) Math.round(GameConstants.FILL_COOLDOWN*(1+.01*SkillType.BUILD.getCooldown(buildLevel))); int resources = (int) -Math.round(GameConstants.FILL_COST*(1+0.01*SkillType.BUILD.getSkillEffect(buildLevel))); this.robot.addActionCooldownTurns(cooldownIncrease); - this.robot.addMovementCooldownTurns(); this.robot.addResourceAmount(resources); this.gameWorld.getMatchMaker().addAction(getID(), Action.FILL, locationToInt(loc)); this.gameWorld.getMatchMaker().addFillLocation(loc); @@ -745,6 +740,11 @@ private void assertCanAttack(MapLocation loc) throws GameActionException { } } + @Override + public int getAttackDamage() { + return this.robot.getDamage(); + } + @Override public boolean canAttack(MapLocation loc) { try { @@ -760,10 +760,18 @@ public void attack(MapLocation loc) throws GameActionException { this.robot.attack(loc); } + @Override + public int getHealAmount() { + return this.robot.getHeal(); + } + private void assertCanHeal(MapLocation loc) throws GameActionException { assertNotNull(loc); assertCanActLocation(loc, GameConstants.HEAL_RADIUS_SQUARED); assertIsActionReady(); + if(getLocation().equals(loc)) { + throw new GameActionException(CANT_DO_THAT, "You can't heal yourself"); + } if(this.gameWorld.getRobot(loc) == null) { throw new GameActionException(CANT_DO_THAT, "There is no robot at this location."); } @@ -954,7 +962,7 @@ public void writeSharedArray(int index, int value) throws GameActionException { private void assertCanBuyGlobal(GlobalUpgrade ug) throws GameActionException{ int i = -1; - if(ug == GlobalUpgrade.ACTION) + if(ug == GlobalUpgrade.ATTACK) i = 0; else if(ug == GlobalUpgrade.CAPTURING) i = 1; @@ -983,6 +991,16 @@ public void buyGlobal(GlobalUpgrade ug) throws GameActionException{ this.gameWorld.getMatchMaker().addAction(getID(), Action.GLOBAL_UPGRADE, FlatHelpers.getGlobalUpgradeTypeFromGlobalUpgrade(ug)); } + @Override + public GlobalUpgrade[] getGlobalUpgrades(Team team) { + boolean[] boolUpgrades = this.gameWorld.getTeamInfo().getGlobalUpgrades(team); + ArrayList upgrades = new ArrayList<>(); + if(boolUpgrades[0]) upgrades.add(GlobalUpgrade.ATTACK); + if(boolUpgrades[1]) upgrades.add(GlobalUpgrade.CAPTURING); + if(boolUpgrades[2]) upgrades.add(GlobalUpgrade.HEALING); + return upgrades.toArray(new GlobalUpgrade[upgrades.size()]); + } + @Override public void resign() { Team team = getTeam(); diff --git a/engine/src/main/battlecode/world/TeamInfo.java b/engine/src/main/battlecode/world/TeamInfo.java index e177950d..9d1ad995 100644 --- a/engine/src/main/battlecode/world/TeamInfo.java +++ b/engine/src/main/battlecode/world/TeamInfo.java @@ -115,7 +115,7 @@ public void incrementGlobalUpgradePoints(Team team){ */ public boolean makeGlobalUpgrade(Team team, GlobalUpgrade upgrade){ if(this.globalUpgradePoints[team.ordinal()] > 0){ - if (upgrade == GlobalUpgrade.ACTION && !this.globalUpgrades[team.ordinal()][0]) { + if (upgrade == GlobalUpgrade.ATTACK && !this.globalUpgrades[team.ordinal()][0]) { this.globalUpgrades[team.ordinal()][0] = true; this.globalUpgradePoints[team.ordinal()]--; return true; diff --git a/specs/specs.md.html b/specs/specs.md.html index 29134569..7cb9fa74 100644 --- a/specs/specs.md.html +++ b/specs/specs.md.html @@ -16,7 +16,7 @@ # **Formal specification** -_This is the formal specification of the Battlecode 2024 game._ Current version: *1.2.5* +_This is the formal specification of the Battlecode 2024 game._ Current version: *2.0.0* **Welcome to Battlecode 2024: Breadwars.** @@ -40,7 +40,7 @@ The two teams will be separated by an impassable dam during the first **200** rounds of the game. Teams will use this period to collect resources, modify the terrain, and set up defenses for their flags. After the setup phase, the dam will collapse and allow both teams to move freely. -Each team has **3** flags. When the game starts, flags are located at the centers of each team’s three spawn zones. Any unit can pick up and place a flag at any time during the setup phase. When the setup phase ends, the new locations of the flags will become the default flag locations for the remainder of the game. If a flag is being carried, it will be automatically dropped at the robot’s location. Default flag placements must be a minimum distance of **6** units[^1] apart on the map. If two flags are less than 6 units apart at the end of the setup phase, all flags for the team will be teleported back to the spawn zones, which will become their default locations. +Each team has **3** flags. When the game starts, flags are located at the centers of each team’s three spawn zones. Any unit can pick up and place a flag at any time during the setup phase. When the setup phase ends, the new locations of the flags will become the default flag locations for the remainder of the game. If a flag is being carried, it will be automatically dropped at the robot’s location. Default flag placements must be a minimum distance of **$\sqrt{36}$** units apart on the map. If two flags are less than this distance apart at the end of the setup phase, all flags for the team will be teleported back to the spawn zones, which will become their default locations. **Tiebreakers** @@ -62,11 +62,11 @@ **Spawn zones** -Each team has **3** spawn zones, which are 3x3 in size and pre-placed on the map. The zones cannot be moved, destroyed, or covered with water. The centers of spawn zones for each team will be at least **6** units apart. +Each team has **3** spawn zones, which are 3x3 in size and pre-placed on the map. The zones cannot be moved, destroyed, or covered with water. The centers of spawn zones for each team will be at least **$\sqrt{36}$** units apart. **Resources** -Building, digging, and filling all require spending crumbs. Each game will start with a finite amount of crumbs scattered around the map. Either team can access the crumbs and collect them for their own use. A bot collects crumbs by simply moving onto the tile. Upon collection, the crumbs are immediately added to the team’s global resource pool. No additional crumbs will spawn on the map, but each team will passively gain **10** crumbs at the beginning of every round**.** Teams start with **400 **crumbs before collecting any crumbs from the map. +Building, digging, and filling all require spending crumbs. Each game will start with a finite amount of crumbs scattered around the map. Either team can access the crumbs and collect them for their own use. A bot collects crumbs by simply moving onto the tile. Upon collection, the crumbs are immediately added to the team’s global resource pool. No additional crumbs will spawn on the map, but each team will passively gain **10** crumbs at the beginning of every round. Teams start with **400** crumbs before collecting any crumbs from the map. **Passability and Visibility** @@ -106,7 +106,7 @@ # **Actions** -The following are all actions that a robot can perform. Robots are only allowed to perform actions when the corresponding cooldown value is **< 10**. Movement has its own cooldown counter, while all other types of actions share a single cooldown counter. Both cooldown counters decrease by **10 **each turn. +The following are all actions that a robot can perform. Robots are only allowed to perform actions when the corresponding cooldown value is **< 10**. Movement has its own cooldown counter, while all other types of actions share a single cooldown counter. Both cooldown counters decrease by **10** each turn. **Movement** @@ -114,11 +114,11 @@ **Attacking** -Robots can attack enemy robots within **2** tiles. Robots may only attack tiles that contain an enemy robot (no missed attacks are allowed). Attacking incurs a base health penalty of **-150** points to the enemy robot and adds **+20** to the attacking robot’s action cooldown. If your robot kills an enemy robot while your robot is in enemy territory, your team gains **50** crumbs. Enemy territory consists of all tiles that were originally accessible to the enemy during the setup phase. +Robots can attack enemy robots within **$\sqrt{4}$** tiles. Robots may only attack tiles that contain an enemy robot (no missed attacks are allowed). Attacking incurs a base health penalty of **-150** points to the enemy robot and adds **+20** to the attacking robot’s action cooldown. If your robot kills an enemy robot while your robot is in enemy territory, your team gains **50** crumbs. Enemy territory consists of all tiles that were originally accessible to the enemy during the setup phase. **Healing** -Heals a friendly unit within **2** tiles. Healing is only allowed when the friendly unit is below max health, and can only heal the unit up to the maximum health. Healing increases health by **+80** and has a cooldown cost of **+30**. +Heals a friendly unit within **$\sqrt{4}$** tiles. Healing is only allowed when the friendly unit is below max health, and can only heal the unit up to the maximum health. Healing increases health by **+80** and has a cooldown cost of **+30**. Units cannot heal themselves. **Building** @@ -134,7 +134,7 @@ **Filling** -Fills at a location up to **$\sqrt{2}$** units away, converting water to land. Filling costs **10** crumbs and has a base cooldown of **+20**. Filling also adds **+10** to the movement cooldown, which prevents filling and moving onto the filled tile in the same turn. +Fills at a location up to **$\sqrt{2}$** units away, converting water to land. Filling costs **30** crumbs and has a base cooldown of **+30**. # **Flags** @@ -156,54 +156,54 @@ | Name | Cost | Function | Action cooldown | --- | --- | --- | --- -| Explosive trap | 250 crumbs | Can be built on land or in water. When an opponent robot enters the cell containing this trap, it explodes dealing **750** damage to all opponent robots within a radius of $\sqrt{13}$ cells. When an opponent robot digs, fills, or tries to build on the trap, it explodes dealing **500** damage to all opponent robots within a radius of $\sqrt{9}$ cells. | 5 -| Water trap | 100 crumbs | Can only be built on land. Digs all non-occupied land in a radius of $\sqrt{9}$ when an opponent robot enters a tile within 1 unit of the trap. | 5 -| Stun trap | 100 crumbs | Can only be built on land. Stuns all enemy robots in a radius of $\sqrt{13}$ when an opponent enters a tile within 1 unit of the trap, setting all of those robots’ movement and action cooldowns to **40**. | 5 +| Explosive trap | 250 crumbs | Can be built on land or in water. When an opponent robot enters the cell containing this trap, it explodes dealing **750** damage to all opponent robots within a radius of $\sqrt{4}$ cells. When an opponent robot digs, fills, or builds on the trap, it explodes dealing **200** damage to all opponent robots within a radius of $\sqrt{2}$ cells. The build will fail when this happens, while dig and fill will succeed. | 5 +| Water trap | 100 crumbs | Can only be built on land. Digs all non-occupied land in a radius of $\sqrt{9}$ when an opponent robot enters a tile within $\sqrt{2}$ units of the trap. | 5 +| Stun trap | 100 crumbs | Can only be built on land. Stuns all enemy robots in a radius of $\sqrt{13}$ when an opponent enters a tile within $\sqrt{2}$ units of the trap, setting all of those robots’ movement and action cooldowns to **50**. | 5 # **Global Upgrades** -Every **750** rounds, each team is awarded one upgrade point. These points can be spent on global upgrades that apply to all units on the team. Each upgrade has a maximum of one level and can only be activated once. +Every **600** rounds, each team is awarded one upgrade point. These points can be spent on global upgrades that apply to all units on the team. Each upgrade has a maximum of one level and can only be activated once. | Name | Function | --- | --- | -| Action Upgrade - Swift Beaks | Increases per-round action cooldown reduction by **+4**. +| Attack Upgrade - Swift Beaks | Increases base attack by **+75**. | Healing Upgrade - Down Feathers | Increases base heal by **+50** health points. -| Capturing Upgrade - Heavy Bread | Increases the dropped flag return delay of the other team’s flag to **12** rounds. +| Capturing Upgrade - Thin Slices | Increases the dropped flag return delay of the other team’s flag to **12** rounds. Decreases movement cooldown when carrying the flag to **+12**. # **Specialization Stats** **Level requirements (# of actions)** | Level | Attack Exp | Build Exp | Heal Exp | --- | --- | --- | --- -| 0 | 0 | 0 | 0 -| 1 | 20 | 5 | 15 -| 2 | 40 | 10 | 30 -| 3 | 70 | 15 | 45 -| 4 | 100 | 20 | 75 -| 5 | 140 | 25 | 110 -| 6 | 180 | 30 | 150 +| 0 | 0 | 0 | 0 +| 1 | 15 | 5 | 20 +| 2 | 30 | 10 | 40 +| 3 | 45 | 15 | 70 +| 4 | 75 | 20 | 100 +| 5 | 110 | 25 | 140 +| 6 | 150 | 30 | 180 **Specialization Jailed Penalty** | Level | Attack | Build | Heal | --- | --- | --- | --- -| 0 | -1 | -1 | -1 -| 1 | -5 | -2 | -2 -| 2 | -5 | -2 | -2 -| 3 | -10 | -5 | -5 -| 4 | -10 | -5 | -5 -| 5 | -15 | -10 | -10 -| 6 | -15 | -10 | -10 +| 0 | -1 | -1 | -1 +| 1 | -2 | -2 | -5 +| 2 | -2 | -2 | -5 +| 3 | -5 | -3 | -10 +| 4 | -5 | -3 | -10 +| 5 | -10 | -4 | -15 +| 6 | -12 | -6 | -18 **Specialization Effects Per Action** | Level | Attack | Build | Heal | --- | --- | --- | --- | 0 | Base | Base | Base | 1 | -5% Cooldown Cost
+5% Damage | -5% Cooldown
-10% Cost | -5% Cooldown
+3% Heal -| 2 | -10% Cooldown Cost
+10% Damage | -10% Cooldown
-15% Cost | -10% Cooldown
+5% Heal -| 3 | -15% Cooldown Cost
+15% Damage | -15% Cooldown
-20% Cost | -15% Cooldown
+7% Heal -| 4 | -20% Cooldown Cost
+20% Damage | -20% Cooldown
-30% Cost | -15% Cooldown
+10% Heal -| 5 | -30% Cooldown Cost
+30% Damage | -30% Cooldown
-40% Cost | -15% Cooldown
+15% Heal -| 6 | -40% Cooldown Cost
+50% Damage | -50% Cooldown
-50% Cost | -25% Cooldown
+25% Heal +| 2 | -7% Cooldown Cost
+7% Damage | -10% Cooldown
-15% Cost | -10% Cooldown
+5% Heal +| 3 | -10% Cooldown Cost
+10% Damage | -15% Cooldown
-20% Cost | -15% Cooldown
+7% Heal +| 4 | -20% Cooldown Cost
+30% Damage | -20% Cooldown
-30% Cost | -15% Cooldown
+10% Heal +| 5 | -35% Cooldown Cost
+35% Damage | -30% Cooldown
-40% Cost | -15% Cooldown
+15% Heal +| 6 | -60% Cooldown Cost
+60% Damage | -50% Cooldown
-50% Cost | -25% Cooldown
+25% Heal # **Bytecode limits** @@ -286,6 +286,28 @@ # **Appendix: Changelog** +- Version 2.0.0 (January 17, 2024) + - Balance changes + - Decreased attack level XP requirements + - Increased heal level XP requirements + - Increased attack skill effects (greater cooldown reduction, greater damage bonus) + - Increased jailed penalties for healing, decreased for attacking and building + - Units can no longer heal themselves + - Filling crumb cost 20 -> 30, cooldown 10 -> 30, removed movement cooldown penalty + - Explosive trap nerf + - Radius when triggered by walking on trap $\sqrt{13}$ -> $\sqrt{4}$ + - Radius when triggered by building $\sqrt{9}$ -> $\sqrt{2}$ + - Damage when triggered by building 500 -> 200 (fixed bug causing damage to always be 750) + - Stun trap and explosive trap trigger radius 1 -> $\sqrt{2}$ + - Stun trap stun rounds 40 -> 50 + - Global upgrades can now be acquired every 600 rounds instead of 750 + - Changed action global upgrade to attack upgrade + - Capturing upgrade now also decreases movement cooldown while carrying a flag to +12 + - Robots can now sense the global upgrades owned by either team using getGlobalUpgrades() + - Engine improvements + - The senseLegalStartingFlagPlacement method no longer checks if the location is adjacent to the robot + - Added getAttackDamage() and getHealAmount() methods + - Version 1.2.5 (January 16, 2024) - Engine improvements - Added sprint 1 maps