mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 09:10:42 +00:00
oil oil baby
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 1.7 KiB |
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="64" height="64">
|
||||
<path d="M0 0 C4.29 0 8.58 0 13 0 C14.14521712 4.62503244 15.28437087 9.25150047 16.41796875 13.87939453 C16.80433148 15.45304474 17.19233846 17.02629222 17.58203125 18.59912109 C18.14236863 20.8619121 18.6967475 23.12609275 19.25 25.390625 C19.42499023 26.09181458 19.59998047 26.79300415 19.78027344 27.51544189 C20.58128802 30.81811607 21 33.57284955 21 37 C23.97 37 26.94 37 30 37 C30 38.98 30 40.96 30 43 C28.68 43 27.36 43 26 43 C26 44.65 26 46.3 26 48 C30.29 48 34.58 48 39 48 C38 51 38 51 36 52 C32.77661746 52.23988901 29.54382172 52.18520013 26.3125 52.1875 C25.42626953 52.19974609 24.54003906 52.21199219 23.62695312 52.22460938 C18.34871799 52.2363301 13.99328962 51.72818369 9 50 C9.495 49.01 9.495 49.01 10 48 C14.62 48 19.24 48 24 48 C24 46.35 24 44.7 24 43 C13.11 43 2.22 43 -9 43 C-8.67 44.65 -8.34 46.3 -8 48 C-7.09636719 47.97679687 -6.19273438 47.95359375 -5.26171875 47.9296875 C-4.08222656 47.91164062 -2.90273438 47.89359375 -1.6875 47.875 C-0.51574219 47.85179687 0.65601563 47.82859375 1.86328125 47.8046875 C5 48 5 48 8 50 C8 50.66 8 51.32 8 52 C3.78248718 52.04946113 -0.43487168 52.08574827 -4.65258789 52.10986328 C-6.08718812 52.119917 -7.52176789 52.13356194 -8.95629883 52.15087891 C-11.01909438 52.17515549 -13.08161885 52.18648919 -15.14453125 52.1953125 C-16.38565674 52.20578613 -17.62678223 52.21625977 -18.90551758 52.22705078 C-22 52 -22 52 -25 50 C-25 49.34 -25 48.68 -25 48 C-20.71 48 -16.42 48 -12 48 C-12 46.35 -12 44.7 -12 43 C-13.32 43 -14.64 43 -16 43 C-16 41.02 -16 39.04 -16 37 C-13.03 37 -10.06 37 -7 37 C-6.8865625 35.73414063 -6.773125 34.46828125 -6.65625 33.1640625 C-6.05747512 27.77719695 -4.82344012 22.62338538 -3.5 17.375 C-2.05461945 11.59347781 -0.71378234 5.92114893 0 0 Z M3 2 C3 2.66 3 3.32 3 4 C5.64 4 8.28 4 11 4 C11 3.34 11 2.68 11 2 C8.36 2 5.72 2 3 2 Z M2 8 C2.33 8.66 2.66 9.32 3 10 C3.33 9.34 3.66 8.68 4 8 C3.34 8 2.68 8 2 8 Z M6 11 C5.01 11.66 4.02 12.32 3 13 C3 13.66 3 14.32 3 15 C4.98 15.99 4.98 15.99 7 17 C8.32 15.68 9.64 14.36 11 13 C8.67145537 11.80161791 8.67145537 11.80161791 6 11 Z M0 17 C-0.33 18.32 -0.66 19.64 -1 21 C0.32 20.34 1.64 19.68 3 19 C2.67 18.34 2.34 17.68 2 17 C1.34 17 0.68 17 0 17 Z M12 17 C11.34 17.66 10.68 18.32 10 19 C11.98 19.99 11.98 19.99 14 21 C13.67 19.68 13.34 18.36 13 17 C12.67 17 12.34 17 12 17 Z M1 24 C1 24.66 1 25.32 1 26 C3.76605639 27.71677553 3.76605639 27.71677553 7 29 C9.35460678 28.32575833 9.35460678 28.32575833 11 27 C10.67 28.32 10.34 29.64 10 31 C13.25397508 33.25885324 13.25397508 33.25885324 17 34 C16.73343428 31.06556598 16.73343428 31.06556598 16 28 C15.01 27.34 14.02 26.68 13 26 C13 25.34 13 24.68 13 24 C8.19212312 20.62149192 5.51594976 19.95121746 1 24 Z M-2 28 C-2.66 30.31 -3.32 32.62 -4 35 C-1.69 33.68 0.62 32.36 3 31 C1.62514468 29.45833358 1.62514468 29.45833358 0 28 C-0.66 28 -1.32 28 -2 28 Z M0 37 C2.64 37 5.28 37 8 37 C8 37.66 8 38.32 8 39 C1.07 39 -5.86 39 -13 39 C-13 39.66 -13 40.32 -13 41 C0.2 41 13.4 41 27 41 C27 40.34 27 39.68 27 39 C21.06 39 15.12 39 9 39 C9 38.34 9 37.68 9 37 C10.65 37 12.3 37 14 37 C8.4084135 32.02970089 5.5915865 32.02970089 0 37 Z " fill="#000000" transform="translate(25,0)"/>
|
||||
<path d="M0 0 C5.88988947 -0.02475223 11.77974854 -0.04288482 17.66967773 -0.05493164 C19.67261279 -0.05995478 21.67554419 -0.06677445 23.6784668 -0.07543945 C26.56039331 -0.08759345 29.44227125 -0.09324857 32.32421875 -0.09765625 C33.66355721 -0.10539818 33.66355721 -0.10539818 35.029953 -0.11329651 C39.1527264 -0.113667 42.97964792 -0.04768418 47 1 C47 1.99 47 2.98 47 4 C40.78866744 4.04938976 34.57744888 4.08571476 28.3659668 4.10986328 C26.25233978 4.11992894 24.1387267 4.13358257 22.02514648 4.15087891 C18.98938558 4.1751023 15.95380857 4.18647738 12.91796875 4.1953125 C11.4975251 4.21079636 11.4975251 4.21079636 10.04838562 4.22659302 C9.16769913 4.22674408 8.28701263 4.22689514 7.37963867 4.22705078 C6.60464523 4.231492 5.82965179 4.23593323 5.03117371 4.24050903 C3 4 3 4 0 2 C0 1.34 0 0.68 0 0 Z " fill="#000000" transform="translate(0,60)"/>
|
||||
<path d="M0 0 C4.95 0 9.9 0 15 0 C14 3 14 3 12 4 C3.60902256 4.40601504 3.60902256 4.40601504 0 2 C0 1.34 0 0.68 0 0 Z " fill="#000000" transform="translate(49,60)"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.2 KiB |
@@ -147,8 +147,8 @@
|
||||
"build_desc": "Description",
|
||||
"build_city": "City",
|
||||
"build_city_desc": "Increases your max population. Useful when you can't expand your territory or you're about to hit your population limit.",
|
||||
"build_factory": "Factory",
|
||||
"build_factory_desc": "Automatically builds railroads to nearby cities, ports and other factories, and can also link up with friendly neighbors. Trains spawn regularly and give you a fixed amount of gold for each building they visit along the route, with extra gold for visiting your neighbors' buildings.",
|
||||
"build_oil_rig": "Oil Rig",
|
||||
"build_oil_rig_desc": "Oil rigs can only be placed on oil fields. Automatically builds railroads to nearby cities, ports and other oil rigs, and can also link up with friendly neighbors. Trains spawn regularly and give you a fixed amount of gold for each building they visit along the route, with extra gold for visiting your neighbors' buildings.",
|
||||
"build_defense": "Defense Post",
|
||||
"build_defense_desc": "Increases defenses around nearby borders, which show a checkered pattern. Attacks from enemies are slower and have more casualties.",
|
||||
"build_port": "Port",
|
||||
@@ -482,7 +482,7 @@
|
||||
"atom_bomb": "Atom Bomb",
|
||||
"hydrogen_bomb": "Hydrogen Bomb",
|
||||
"mirv": "MIRV",
|
||||
"factory": "Factory"
|
||||
"oil_rig": "Oil Rig"
|
||||
},
|
||||
"user_setting": {
|
||||
"title": "Settings",
|
||||
@@ -525,8 +525,8 @@
|
||||
"build_controls": "Build Controls",
|
||||
"build_city": "Build City",
|
||||
"build_city_desc": "Build a City under your cursor.",
|
||||
"build_factory": "Build Factory",
|
||||
"build_factory_desc": "Build a Factory under your cursor.",
|
||||
"build_oil_rig": "Build Oil Rig",
|
||||
"build_oil_rig_desc": "Build a Oil Rig under your cursor.",
|
||||
"build_defense_post": "Build Defense Post",
|
||||
"build_defense_post_desc": "Build a Defense Post under your cursor.",
|
||||
"build_port": "Build Port",
|
||||
@@ -686,7 +686,7 @@
|
||||
"port": "Sends trade ships to generate gold",
|
||||
"defense_post": "Increases defenses of nearby borders",
|
||||
"city": "Increases max population",
|
||||
"factory": "Creates railroads and spawns trains"
|
||||
"oil_rig": "Oil rigs can only be placed on oil fields. Creates railroads and spawns trains"
|
||||
},
|
||||
"not_enough_money": "Not enough money"
|
||||
},
|
||||
@@ -927,7 +927,7 @@
|
||||
"saml": "SAM Launcher",
|
||||
"silo": "Missile Silo",
|
||||
"wshp": "Warship",
|
||||
"fact": "Factory",
|
||||
"orig": "Oil Rig",
|
||||
"trade": "Trade Ship",
|
||||
"trans": "Transport Ship",
|
||||
"abomb": "Atom Bomb",
|
||||
|
||||
@@ -966,18 +966,18 @@ export class HelpModal extends BaseModal {
|
||||
</tr>
|
||||
<tr class="bg-white/5 hover:bg-white/10 transition-colors">
|
||||
<td class="py-3 pl-4 border-b border-white/5 font-medium">
|
||||
${translateText("help_modal.build_factory")}
|
||||
${translateText("help_modal.build_oil_rig")}
|
||||
</td>
|
||||
<td class="py-3 border-b border-white/5">
|
||||
<img
|
||||
src="/images/FactoryIconWhite.svg"
|
||||
src="/images/oil-rig_2623991.svg"
|
||||
class="w-8 h-8 scale-75 origin-left"
|
||||
/>
|
||||
</td>
|
||||
<td
|
||||
class="py-3 border-b border-white/5 text-white/60 text-sm"
|
||||
>
|
||||
${translateText("help_modal.build_factory_desc")}
|
||||
${translateText("help_modal.build_oil_rig_desc")}
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="bg-white/5 hover:bg-white/10 transition-colors">
|
||||
|
||||
@@ -216,7 +216,7 @@ export class InputHandler {
|
||||
modifierKey: isMac ? "MetaLeft" : "ControlLeft",
|
||||
altKey: "AltLeft",
|
||||
buildCity: "Digit1",
|
||||
buildFactory: "Digit2",
|
||||
buildOilRig: "Digit2",
|
||||
buildPort: "Digit3",
|
||||
buildDefensePost: "Digit4",
|
||||
buildMissileSilo: "Digit5",
|
||||
@@ -396,9 +396,9 @@ export class InputHandler {
|
||||
this.setGhostStructure(UnitType.City);
|
||||
}
|
||||
|
||||
if (e.code === this.keybinds.buildFactory) {
|
||||
if (e.code === this.keybinds.buildOilRig) {
|
||||
e.preventDefault();
|
||||
this.setGhostStructure(UnitType.Factory);
|
||||
this.setGhostStructure(UnitType.OilRig);
|
||||
}
|
||||
|
||||
if (e.code === this.keybinds.buildPort) {
|
||||
|
||||
@@ -478,7 +478,7 @@ export class JoinLobbyModal extends BaseModal {
|
||||
"SAM Launcher": "unit_type.sam_launcher",
|
||||
"Missile Silo": "unit_type.missile_silo",
|
||||
Warship: "unit_type.warship",
|
||||
Factory: "unit_type.factory",
|
||||
OilRig: "unit_type.oil_rig",
|
||||
"Atom Bomb": "unit_type.atom_bomb",
|
||||
"Hydrogen Bomb": "unit_type.hydrogen_bomb",
|
||||
MIRV: "unit_type.mirv",
|
||||
|
||||
@@ -22,7 +22,7 @@ const isMac =
|
||||
const DefaultKeybinds: Record<string, string> = {
|
||||
toggleView: "Space",
|
||||
buildCity: "Digit1",
|
||||
buildFactory: "Digit2",
|
||||
buildOilRig: "Digit2",
|
||||
buildPort: "Digit3",
|
||||
buildDefensePost: "Digit4",
|
||||
buildMissileSilo: "Digit5",
|
||||
@@ -490,12 +490,12 @@ export class UserSettingModal extends BaseModal {
|
||||
></setting-keybind>
|
||||
|
||||
<setting-keybind
|
||||
action="buildFactory"
|
||||
label=${translateText("user_setting.build_factory")}
|
||||
description=${translateText("user_setting.build_factory_desc")}
|
||||
action="buildOilRig"
|
||||
label=${translateText("user_setting.build_oil_rig")}
|
||||
description=${translateText("user_setting.build_oil_rig_desc")}
|
||||
defaultKey="Digit2"
|
||||
.value=${this.getKeyValue("buildFactory")}
|
||||
.display=${this.getKeyChar("buildFactory")}
|
||||
.value=${this.getKeyValue("buildOilRig")}
|
||||
.display=${this.getKeyChar("buildOilRig")}
|
||||
@change=${this.handleKeybindChange}
|
||||
></setting-keybind>
|
||||
|
||||
|
||||
@@ -98,7 +98,7 @@ const unitOptions: { type: UnitType; translationKey: string }[] = [
|
||||
{ type: UnitType.AtomBomb, translationKey: "unit_type.atom_bomb" },
|
||||
{ type: UnitType.HydrogenBomb, translationKey: "unit_type.hydrogen_bomb" },
|
||||
{ type: UnitType.MIRV, translationKey: "unit_type.mirv" },
|
||||
{ type: UnitType.Factory, translationKey: "unit_type.factory" },
|
||||
{ type: UnitType.OilRig, translationKey: "unit_type.oil_rig" },
|
||||
];
|
||||
|
||||
const MAP_ICON = svg`<path
|
||||
|
||||
@@ -26,12 +26,12 @@ import { UIState } from "../UIState";
|
||||
import { Layer } from "./Layer";
|
||||
import warshipIcon from "/images/BattleshipIconWhite.svg?url";
|
||||
import cityIcon from "/images/CityIconWhite.svg?url";
|
||||
import factoryIcon from "/images/FactoryIconWhite.svg?url";
|
||||
import goldCoinIcon from "/images/GoldCoinIcon.svg?url";
|
||||
import mirvIcon from "/images/MIRVIcon.svg?url";
|
||||
import missileSiloIcon from "/images/MissileSiloIconWhite.svg?url";
|
||||
import hydrogenBombIcon from "/images/MushroomCloudIconWhite.svg?url";
|
||||
import atomBombIcon from "/images/NukeIconWhite.svg?url";
|
||||
import oilRigIcon from "/images/oil-rig_2623991.svg?url";
|
||||
import portIcon from "/images/PortIcon.svg?url";
|
||||
import samlauncherIcon from "/images/SamLauncherIconWhite.svg?url";
|
||||
import shieldIcon from "/images/ShieldIconWhite.svg?url";
|
||||
@@ -110,10 +110,10 @@ export const buildTable: BuildItemDisplay[][] = [
|
||||
countable: true,
|
||||
},
|
||||
{
|
||||
unitType: UnitType.Factory,
|
||||
icon: factoryIcon,
|
||||
description: "build_menu.desc.factory",
|
||||
key: "unit_type.factory",
|
||||
unitType: UnitType.OilRig,
|
||||
icon: oilRigIcon,
|
||||
description: "build_menu.desc.oil_rig",
|
||||
key: "unit_type.oil_rig",
|
||||
countable: true,
|
||||
},
|
||||
],
|
||||
|
||||
@@ -85,7 +85,7 @@ export class FxLayer implements Layer {
|
||||
case UnitType.Port:
|
||||
case UnitType.MissileSilo:
|
||||
case UnitType.SAMLauncher:
|
||||
case UnitType.Factory:
|
||||
case UnitType.OilRig:
|
||||
this.onStructureEvent(unit);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -33,9 +33,9 @@ import { SpawnBarVisibleEvent } from "./SpawnTimer";
|
||||
import allianceIcon from "/images/AllianceIcon.svg?url";
|
||||
import warshipIcon from "/images/BattleshipIconWhite.svg?url";
|
||||
import cityIcon from "/images/CityIconWhite.svg?url";
|
||||
import factoryIcon from "/images/FactoryIconWhite.svg?url";
|
||||
import goldCoinIcon from "/images/GoldCoinIcon.svg?url";
|
||||
import missileSiloIcon from "/images/MissileSiloIconWhite.svg?url";
|
||||
import factoryIcon from "/images/oil-rig_2623991.svg?url";
|
||||
import portIcon from "/images/PortIcon.svg?url";
|
||||
import samLauncherIcon from "/images/SamLauncherIconWhite.svg?url";
|
||||
import soldierIcon from "/images/SoldierIcon.svg?url";
|
||||
@@ -380,7 +380,7 @@ export class PlayerInfoOverlay extends LitElement implements Layer {
|
||||
</div>
|
||||
<div class="flex gap-0.5 lg:gap-1 items-center mt-1">
|
||||
${this.displayUnitCount(player, UnitType.City, cityIcon)}
|
||||
${this.displayUnitCount(player, UnitType.Factory, factoryIcon)}
|
||||
${this.displayUnitCount(player, UnitType.OilRig, factoryIcon)}
|
||||
${this.displayUnitCount(player, UnitType.Port, portIcon)}
|
||||
${this.displayUnitCount(
|
||||
player,
|
||||
|
||||
@@ -29,7 +29,7 @@ type RailRef = {
|
||||
const SNAPPABLE_STRUCTURES: UnitType[] = [
|
||||
UnitType.Port,
|
||||
UnitType.City,
|
||||
UnitType.Factory,
|
||||
UnitType.OilRig,
|
||||
];
|
||||
export class RailTileChangedEvent implements GameEvent {
|
||||
constructor(public tile: TileRef) {}
|
||||
|
||||
@@ -4,8 +4,8 @@ import { Cell, UnitType } from "../../../core/game/Game";
|
||||
import { GameView, PlayerView, UnitView } from "../../../core/game/GameView";
|
||||
import { TransformHandler } from "../TransformHandler";
|
||||
import anchorIcon from "/images/AnchorIcon.png?url";
|
||||
import factoryIcon from "/images/buildings/oil-rig_2623991.png?url";
|
||||
import cityIcon from "/images/CityIcon.png?url";
|
||||
import factoryIcon from "/images/FactoryUnit.png?url";
|
||||
import missileSiloIcon from "/images/MissileSiloUnit.png?url";
|
||||
import SAMMissileIcon from "/images/SamLauncherUnit.png?url";
|
||||
import shieldIcon from "/images/ShieldIcon.png?url";
|
||||
@@ -13,7 +13,7 @@ import shieldIcon from "/images/ShieldIcon.png?url";
|
||||
export const STRUCTURE_SHAPES: Partial<Record<UnitType, ShapeType>> = {
|
||||
[UnitType.City]: "circle",
|
||||
[UnitType.Port]: "pentagon",
|
||||
[UnitType.Factory]: "circle",
|
||||
[UnitType.OilRig]: "circle",
|
||||
[UnitType.DefensePost]: "octagon",
|
||||
[UnitType.SAMLauncher]: "square",
|
||||
[UnitType.MissileSilo]: "triangle",
|
||||
@@ -57,7 +57,7 @@ export class SpriteFactory {
|
||||
{ iconPath: string; image: HTMLImageElement | null }
|
||||
> = new Map([
|
||||
[UnitType.City, { iconPath: cityIcon, image: null }],
|
||||
[UnitType.Factory, { iconPath: factoryIcon, image: null }],
|
||||
[UnitType.OilRig, { iconPath: factoryIcon, image: null }],
|
||||
[UnitType.DefensePost, { iconPath: shieldIcon, image: null }],
|
||||
[UnitType.Port, { iconPath: anchorIcon, image: null }],
|
||||
[UnitType.MissileSilo, { iconPath: missileSiloIcon, image: null }],
|
||||
@@ -464,7 +464,7 @@ export class SpriteFactory {
|
||||
case UnitType.SAMLauncher:
|
||||
radius = this.game.config().samRange(level ?? 1);
|
||||
break;
|
||||
case UnitType.Factory:
|
||||
case UnitType.OilRig:
|
||||
radius = this.game.config().trainStationMaxRange();
|
||||
break;
|
||||
case UnitType.DefensePost:
|
||||
|
||||
@@ -84,7 +84,7 @@ export class StructureIconsLayer implements Layer {
|
||||
private factory: SpriteFactory;
|
||||
private readonly structures: Map<UnitType, { visible: boolean }> = new Map([
|
||||
[UnitType.City, { visible: true }],
|
||||
[UnitType.Factory, { visible: true }],
|
||||
[UnitType.OilRig, { visible: true }],
|
||||
[UnitType.DefensePost, { visible: true }],
|
||||
[UnitType.Port, { visible: true }],
|
||||
[UnitType.MissileSilo, { visible: true }],
|
||||
|
||||
@@ -9,8 +9,8 @@ import { euclDistFN, isometricDistFN } from "../../../core/game/GameMap";
|
||||
import { GameUpdateType } from "../../../core/game/GameUpdates";
|
||||
import { GameView, UnitView } from "../../../core/game/GameView";
|
||||
import cityIcon from "/images/buildings/cityAlt1.png?url";
|
||||
import factoryIcon from "/images/buildings/factoryAlt1.png?url";
|
||||
import shieldIcon from "/images/buildings/fortAlt3.png?url";
|
||||
import oilRigIcon from "/images/buildings/oil-rig_2623991.png?url";
|
||||
import anchorIcon from "/images/buildings/port1.png?url";
|
||||
import missileSiloIcon from "/images/buildings/silo1.png?url";
|
||||
import SAMMissileIcon from "/images/buildings/silo4.png?url";
|
||||
@@ -49,8 +49,8 @@ export class StructureLayer implements Layer {
|
||||
borderRadius: BASE_BORDER_RADIUS * RADIUS_SCALE_FACTOR,
|
||||
territoryRadius: BASE_TERRITORY_RADIUS * RADIUS_SCALE_FACTOR,
|
||||
},
|
||||
[UnitType.Factory]: {
|
||||
icon: factoryIcon,
|
||||
[UnitType.OilRig]: {
|
||||
icon: oilRigIcon,
|
||||
borderRadius: BASE_BORDER_RADIUS * RADIUS_SCALE_FACTOR,
|
||||
territoryRadius: BASE_TERRITORY_RADIUS * RADIUS_SCALE_FACTOR,
|
||||
},
|
||||
|
||||
@@ -114,7 +114,7 @@ export class UILayer implements Layer {
|
||||
break;
|
||||
}
|
||||
case UnitType.City:
|
||||
case UnitType.Factory:
|
||||
case UnitType.OilRig:
|
||||
case UnitType.DefensePost:
|
||||
case UnitType.Port:
|
||||
case UnitType.MissileSilo:
|
||||
@@ -334,7 +334,7 @@ export class UILayer implements Layer {
|
||||
? unit.missileReadinesss()
|
||||
: this.deletionProgress(this.game, unit);
|
||||
case UnitType.City:
|
||||
case UnitType.Factory:
|
||||
case UnitType.OilRig:
|
||||
case UnitType.Port:
|
||||
case UnitType.DefensePost:
|
||||
return this.deletionProgress(this.game, unit);
|
||||
|
||||
@@ -12,19 +12,19 @@ import { UIState } from "../UIState";
|
||||
import { Layer } from "./Layer";
|
||||
import warshipIcon from "/images/BattleshipIconWhite.svg?url";
|
||||
import cityIcon from "/images/CityIconWhite.svg?url";
|
||||
import factoryIcon from "/images/FactoryIconWhite.svg?url";
|
||||
import goldCoinIcon from "/images/GoldCoinIcon.svg?url";
|
||||
import mirvIcon from "/images/MIRVIcon.svg?url";
|
||||
import missileSiloIcon from "/images/MissileSiloIconWhite.svg?url";
|
||||
import hydrogenBombIcon from "/images/MushroomCloudIconWhite.svg?url";
|
||||
import atomBombIcon from "/images/NukeIconWhite.svg?url";
|
||||
import factoryIcon from "/images/oil-rig_2623991.svg?url";
|
||||
import portIcon from "/images/PortIcon.svg?url";
|
||||
import samLauncherIcon from "/images/SamLauncherIconWhite.svg?url";
|
||||
import defensePostIcon from "/images/ShieldIconWhite.svg?url";
|
||||
|
||||
const BUILDABLE_UNITS: UnitType[] = [
|
||||
UnitType.City,
|
||||
UnitType.Factory,
|
||||
UnitType.OilRig,
|
||||
UnitType.Port,
|
||||
UnitType.DefensePost,
|
||||
UnitType.MissileSilo,
|
||||
@@ -44,7 +44,7 @@ export class UnitDisplay extends LitElement implements Layer {
|
||||
private keybinds: Record<string, { value: string; key: string }> = {};
|
||||
private _cities = 0;
|
||||
private _warships = 0;
|
||||
private _factories = 0;
|
||||
private _oilRigs = 0;
|
||||
private _missileSilo = 0;
|
||||
private _port = 0;
|
||||
private _defensePost = 0;
|
||||
@@ -113,7 +113,7 @@ export class UnitDisplay extends LitElement implements Layer {
|
||||
this._port = player.totalUnitLevels(UnitType.Port);
|
||||
this._defensePost = player.totalUnitLevels(UnitType.DefensePost);
|
||||
this._samLauncher = player.totalUnitLevels(UnitType.SAMLauncher);
|
||||
this._factories = player.totalUnitLevels(UnitType.Factory);
|
||||
this._oilRigs = player.totalUnitLevels(UnitType.OilRig);
|
||||
this._warships = player.totalUnitLevels(UnitType.Warship);
|
||||
this.requestUpdate();
|
||||
}
|
||||
@@ -147,10 +147,10 @@ export class UnitDisplay extends LitElement implements Layer {
|
||||
)}
|
||||
${this.renderUnitItem(
|
||||
factoryIcon,
|
||||
this._factories,
|
||||
UnitType.Factory,
|
||||
"factory",
|
||||
this.keybinds["buildFactory"]?.key ?? "2",
|
||||
this._oilRigs,
|
||||
UnitType.OilRig,
|
||||
"oil_rig",
|
||||
this.keybinds["buildOilRig"]?.key ?? "2",
|
||||
)}
|
||||
${this.renderUnitItem(
|
||||
portIcon,
|
||||
|
||||
@@ -107,7 +107,7 @@ export class GameRunner {
|
||||
this.game.addExecution(...this.execManager.nationExecutions());
|
||||
}
|
||||
this.game.addExecution(new WinCheckExecution());
|
||||
if (!this.game.config().isUnitDisabled(UnitType.Factory)) {
|
||||
if (!this.game.config().isUnitDisabled(UnitType.OilRig)) {
|
||||
this.game.addExecution(
|
||||
new RecomputeRailClusterExecution(this.game.railNetwork()),
|
||||
);
|
||||
|
||||
@@ -34,7 +34,7 @@ export const otherUnits = [
|
||||
"wshp",
|
||||
"silo",
|
||||
"saml",
|
||||
"fact",
|
||||
"orig",
|
||||
] as const;
|
||||
export const OtherUnitSchema = z.enum(otherUnits);
|
||||
export type OtherUnit = z.infer<typeof OtherUnitSchema>;
|
||||
@@ -45,7 +45,7 @@ export type OtherUnitType =
|
||||
| UnitType.Port
|
||||
| UnitType.SAMLauncher
|
||||
| UnitType.Warship
|
||||
| UnitType.Factory;
|
||||
| UnitType.OilRig;
|
||||
|
||||
export const unitTypeToOtherUnit = {
|
||||
[UnitType.City]: "city",
|
||||
@@ -54,7 +54,7 @@ export const unitTypeToOtherUnit = {
|
||||
[UnitType.Port]: "port",
|
||||
[UnitType.SAMLauncher]: "saml",
|
||||
[UnitType.Warship]: "wshp",
|
||||
[UnitType.Factory]: "fact",
|
||||
[UnitType.OilRig]: "orig",
|
||||
} as const satisfies Record<OtherUnitType, OtherUnit>;
|
||||
|
||||
// Attacks
|
||||
|
||||
@@ -132,7 +132,7 @@ export interface Config {
|
||||
numTradeShips: number,
|
||||
): number;
|
||||
trainGold(rel: "self" | "team" | "ally" | "other"): Gold;
|
||||
trainSpawnRate(numPlayerFactories: number): number;
|
||||
trainSpawnRate(numPlayerOilRigs: number): number;
|
||||
trainStationMinRange(): number;
|
||||
trainStationMaxRange(): number;
|
||||
railroadMaxSize(): number;
|
||||
@@ -170,6 +170,7 @@ export interface Config {
|
||||
structureMinDist(): number;
|
||||
isReplay(): boolean;
|
||||
allianceExtensionPromptOffset(): number;
|
||||
randomSeed(): number;
|
||||
}
|
||||
|
||||
export interface Theme {
|
||||
|
||||
@@ -265,10 +265,10 @@ export class DefaultConfig implements Config {
|
||||
return BigInt(this._gameConfig.startingGold ?? 0);
|
||||
}
|
||||
|
||||
trainSpawnRate(numPlayerFactories: number): number {
|
||||
// hyperbolic decay, midpoint at 10 factories
|
||||
// expected number of trains = numPlayerFactories / trainSpawnRate(numPlayerFactories)
|
||||
return (numPlayerFactories + 10) * 18;
|
||||
trainSpawnRate(numPlayerOilRigs: number): number {
|
||||
// hyperbolic decay, midpoint at 10 oil rigs
|
||||
// expected number of trains = numPlayerOilRigs / trainSpawnRate(numPlayerOilRigs)
|
||||
return (numPlayerOilRigs + 10) * 18;
|
||||
}
|
||||
trainGold(rel: "self" | "team" | "ally" | "other"): Gold {
|
||||
const multiplier = this.goldMultiplier();
|
||||
@@ -362,7 +362,7 @@ export class DefaultConfig implements Config {
|
||||
(numUnits: number) =>
|
||||
Math.min(1_000_000, Math.pow(2, numUnits) * 125_000),
|
||||
UnitType.Port,
|
||||
UnitType.Factory,
|
||||
UnitType.OilRig,
|
||||
),
|
||||
constructionDuration: this.instantBuild() ? 0 : 2 * 10,
|
||||
upgradable: true,
|
||||
@@ -436,12 +436,12 @@ export class DefaultConfig implements Config {
|
||||
upgradable: true,
|
||||
};
|
||||
break;
|
||||
case UnitType.Factory:
|
||||
case UnitType.OilRig:
|
||||
info = {
|
||||
cost: this.costWrapper(
|
||||
(numUnits: number) =>
|
||||
Math.min(1_000_000, Math.pow(2, numUnits) * 125_000),
|
||||
UnitType.Factory,
|
||||
UnitType.OilRig,
|
||||
UnitType.Port,
|
||||
),
|
||||
constructionDuration: this.instantBuild() ? 0 : 2 * 10,
|
||||
@@ -911,4 +911,8 @@ export class DefaultConfig implements Config {
|
||||
allianceExtensionPromptOffset(): number {
|
||||
return 300; // 30 seconds before expiration
|
||||
}
|
||||
|
||||
randomSeed(): number {
|
||||
return simpleHash(JSON.stringify(this.gameConfig()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ export class PastelTheme implements Theme {
|
||||
private nationColorAllocator = new ColorAllocator(nationColors, nationColors);
|
||||
|
||||
private background = colord("rgb(60,60,60)");
|
||||
private oilField = colord("rgb(55,55,85)");
|
||||
private shore = colord("rgb(204,203,158)");
|
||||
private falloutColors = [
|
||||
colord("rgb(120,255,71)"), // Original color
|
||||
@@ -147,6 +148,9 @@ export class PastelTheme implements Theme {
|
||||
// | **Water (Shore)** | 0 | Fixed: `rgb(100, 143, 255)` | Light blue near land. |
|
||||
// | **Water (Deep)** | 1 - 10+ | `rgb(70, 132, 180)` - `rgb(61, 123, 171)` | Darker blue, adjusted slightly by distance to land. |
|
||||
terrainColor(gm: GameMap, tile: TileRef): Colord {
|
||||
if (gm.hasOilField(tile)) {
|
||||
return this.oilField;
|
||||
}
|
||||
const mag = gm.magnitude(tile);
|
||||
if (gm.isShore(tile)) {
|
||||
return this.shore;
|
||||
|
||||
@@ -19,6 +19,9 @@ export class PastelThemeDark extends PastelTheme {
|
||||
// | **Water (Deep)** | 1 - 10+ | `rgb(22, 19, 38)` - `rgb(14, 11, 30)` | Very dark blue/black. |
|
||||
|
||||
terrainColor(gm: GameMap, tile: TileRef): Colord {
|
||||
if (gm.hasOilField(tile)) {
|
||||
return colord("rgb(50,40,80)");
|
||||
}
|
||||
const mag = gm.magnitude(tile);
|
||||
if (gm.isShore(tile)) {
|
||||
return this.darkShore;
|
||||
|
||||
@@ -35,7 +35,7 @@ export class CityExecution implements Execution {
|
||||
const nearbyFactory = this.mg.hasUnitNearby(
|
||||
this.city.tile()!,
|
||||
this.mg.config().trainStationMaxRange(),
|
||||
UnitType.Factory,
|
||||
UnitType.OilRig,
|
||||
);
|
||||
if (nearbyFactory) {
|
||||
this.mg.addExecution(new TrainStationExecution(this.city));
|
||||
|
||||
@@ -2,10 +2,10 @@ import { Execution, Game, Player, Tick, Unit, UnitType } from "../game/Game";
|
||||
import { TileRef } from "../game/GameMap";
|
||||
import { CityExecution } from "./CityExecution";
|
||||
import { DefensePostExecution } from "./DefensePostExecution";
|
||||
import { FactoryExecution } from "./FactoryExecution";
|
||||
import { MirvExecution } from "./MIRVExecution";
|
||||
import { MissileSiloExecution } from "./MissileSiloExecution";
|
||||
import { NukeExecution } from "./NukeExecution";
|
||||
import { OilRigExecution } from "./OilRigExecution";
|
||||
import { PortExecution } from "./PortExecution";
|
||||
import { SAMLauncherExecution } from "./SAMLauncherExecution";
|
||||
import { WarshipExecution } from "./WarshipExecution";
|
||||
@@ -141,8 +141,8 @@ export class ConstructionExecution implements Execution {
|
||||
case UnitType.City:
|
||||
this.mg.addExecution(new CityExecution(this.structure!));
|
||||
break;
|
||||
case UnitType.Factory:
|
||||
this.mg.addExecution(new FactoryExecution(this.structure!));
|
||||
case UnitType.OilRig:
|
||||
this.mg.addExecution(new OilRigExecution(this.structure!));
|
||||
break;
|
||||
default:
|
||||
console.warn(
|
||||
@@ -159,7 +159,7 @@ export class ConstructionExecution implements Execution {
|
||||
case UnitType.DefensePost:
|
||||
case UnitType.SAMLauncher:
|
||||
case UnitType.City:
|
||||
case UnitType.Factory:
|
||||
case UnitType.OilRig:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { Execution, Game, Unit, UnitType } from "../game/Game";
|
||||
import { TrainStationExecution } from "./TrainStationExecution";
|
||||
|
||||
export class FactoryExecution implements Execution {
|
||||
export class OilRigExecution implements Execution {
|
||||
private active: boolean = true;
|
||||
private game: Game;
|
||||
private stationCreated = false;
|
||||
|
||||
constructor(private factory: Unit) {}
|
||||
constructor(private oilRig: Unit) {}
|
||||
|
||||
init(mg: Game, ticks: number): void {
|
||||
this.game = mg;
|
||||
@@ -17,7 +17,7 @@ export class FactoryExecution implements Execution {
|
||||
this.createStation();
|
||||
this.stationCreated = true;
|
||||
}
|
||||
if (!this.factory.isActive()) {
|
||||
if (!this.oilRig.isActive()) {
|
||||
this.active = false;
|
||||
return;
|
||||
}
|
||||
@@ -33,12 +33,12 @@ export class FactoryExecution implements Execution {
|
||||
|
||||
private createStation(): void {
|
||||
const structures = this.game.nearbyUnits(
|
||||
this.factory.tile()!,
|
||||
this.oilRig.tile()!,
|
||||
this.game.config().trainStationMaxRange(),
|
||||
[UnitType.City, UnitType.Port, UnitType.Factory],
|
||||
[UnitType.City, UnitType.Port, UnitType.OilRig],
|
||||
);
|
||||
|
||||
this.game.addExecution(new TrainStationExecution(this.factory, true));
|
||||
this.game.addExecution(new TrainStationExecution(this.oilRig, true));
|
||||
for (const { unit } of structures) {
|
||||
if (!unit.hasTrainStation()) {
|
||||
this.game.addExecution(new TrainStationExecution(unit));
|
||||
@@ -87,7 +87,7 @@ export class PortExecution implements Execution {
|
||||
const nearbyFactory = this.mg.hasUnitNearby(
|
||||
this.port.tile()!,
|
||||
this.mg.config().trainStationMaxRange(),
|
||||
UnitType.Factory,
|
||||
UnitType.OilRig,
|
||||
);
|
||||
if (nearbyFactory) {
|
||||
this.mg.addExecution(new TrainStationExecution(this.port));
|
||||
|
||||
@@ -53,7 +53,7 @@ export class TrainStationExecution implements Execution {
|
||||
private shouldSpawnTrain(): boolean {
|
||||
const spawnRate = this.mg
|
||||
.config()
|
||||
.trainSpawnRate(this.unit.owner().unitCount(UnitType.Factory));
|
||||
.trainSpawnRate(this.unit.owner().unitCount(UnitType.OilRig));
|
||||
for (let i = 0; i < this.unit!.level(); i++) {
|
||||
if (this.random.chance(spawnRate)) {
|
||||
return true;
|
||||
|
||||
@@ -82,7 +82,7 @@ export class NationNukeBehavior {
|
||||
UnitType.MissileSilo,
|
||||
UnitType.Port,
|
||||
UnitType.SAMLauncher,
|
||||
UnitType.Factory,
|
||||
UnitType.OilRig,
|
||||
);
|
||||
const structureTiles = structures.map((u) => u.tile());
|
||||
const difficulty = this.game.config().gameConfig().difficulty;
|
||||
@@ -571,7 +571,7 @@ export class NationNukeBehavior {
|
||||
return 50_000 * level;
|
||||
case UnitType.Port:
|
||||
return 15_000 * level;
|
||||
case UnitType.Factory:
|
||||
case UnitType.OilRig:
|
||||
return 15_000 * level;
|
||||
default:
|
||||
return 0;
|
||||
|
||||
@@ -46,7 +46,7 @@ function getStructureRatios(
|
||||
): Partial<Record<UnitType, StructureRatioConfig>> {
|
||||
return {
|
||||
[UnitType.Port]: { ratioPerCity: 0.75, perceivedCostIncreasePerOwned: 1 },
|
||||
[UnitType.Factory]: {
|
||||
[UnitType.OilRig]: {
|
||||
ratioPerCity: 0.75,
|
||||
perceivedCostIncreasePerOwned: 1,
|
||||
},
|
||||
@@ -105,7 +105,7 @@ export class NationStructureBehavior {
|
||||
const buildOrder: UnitType[] = [
|
||||
UnitType.DefensePost,
|
||||
UnitType.Port,
|
||||
UnitType.Factory,
|
||||
UnitType.OilRig,
|
||||
UnitType.SAMLauncher,
|
||||
UnitType.MissileSilo,
|
||||
];
|
||||
@@ -185,7 +185,7 @@ export class NationStructureBehavior {
|
||||
|
||||
// Heavily reduce factory spawning if we have coastal tiles
|
||||
if (
|
||||
type === UnitType.Factory &&
|
||||
type === UnitType.OilRig &&
|
||||
hasCoastalTiles &&
|
||||
!gameConfig.isUnitDisabled(UnitType.Port)
|
||||
) {
|
||||
@@ -446,7 +446,9 @@ export class NationStructureBehavior {
|
||||
const tiles =
|
||||
type === UnitType.Port
|
||||
? this.randCoastalTileArray(25)
|
||||
: randTerritoryTileArray(this.random, this.game, this.player, 25);
|
||||
: type === UnitType.OilRig
|
||||
? this.randOilFieldTileArray(25)
|
||||
: randTerritoryTileArray(this.random, this.game, this.player, 25);
|
||||
if (tiles.length === 0) return null;
|
||||
const valueFunction = this.structureSpawnTileValue(type);
|
||||
if (valueFunction === null) return null;
|
||||
@@ -470,6 +472,13 @@ export class NationStructureBehavior {
|
||||
return Array.from(this.arraySampler(tiles, numTiles));
|
||||
}
|
||||
|
||||
private randOilFieldTileArray(numTiles: number): TileRef[] {
|
||||
const tiles = Array.from(this.player.tiles()).filter((t) =>
|
||||
this.game.hasOilField(t),
|
||||
);
|
||||
return Array.from(this.arraySampler(tiles, numTiles));
|
||||
}
|
||||
|
||||
private *arraySampler<T>(a: T[], sampleSize: number): Generator<T> {
|
||||
if (a.length <= sampleSize) {
|
||||
// Return all elements
|
||||
@@ -490,7 +499,7 @@ export class NationStructureBehavior {
|
||||
): ((tile: TileRef) => number) | null {
|
||||
switch (type) {
|
||||
case UnitType.City:
|
||||
case UnitType.Factory:
|
||||
case UnitType.OilRig:
|
||||
case UnitType.MissileSilo:
|
||||
return this.interiorStructureValue(type);
|
||||
case UnitType.Port:
|
||||
@@ -652,7 +661,7 @@ export class NationStructureBehavior {
|
||||
for (const unit of player.units()) {
|
||||
switch (unit.type()) {
|
||||
case UnitType.City:
|
||||
case UnitType.Factory:
|
||||
case UnitType.OilRig:
|
||||
case UnitType.MissileSilo:
|
||||
case UnitType.Port:
|
||||
protectEntries.push({
|
||||
|
||||
@@ -249,7 +249,7 @@ export enum UnitType {
|
||||
MIRV = "MIRV",
|
||||
MIRVWarhead = "MIRV Warhead",
|
||||
Train = "Train",
|
||||
Factory = "Factory",
|
||||
OilRig = "OilRig",
|
||||
}
|
||||
|
||||
export enum TrainType {
|
||||
@@ -264,7 +264,7 @@ const _structureTypes: ReadonlySet<UnitType> = new Set([
|
||||
UnitType.SAMLauncher,
|
||||
UnitType.MissileSilo,
|
||||
UnitType.Port,
|
||||
UnitType.Factory,
|
||||
UnitType.OilRig,
|
||||
]);
|
||||
|
||||
export const StructureTypes: readonly UnitType[] = [..._structureTypes];
|
||||
@@ -318,7 +318,7 @@ export interface UnitParamsMap {
|
||||
loaded?: boolean;
|
||||
};
|
||||
|
||||
[UnitType.Factory]: Record<string, never>;
|
||||
[UnitType.OilRig]: Record<string, never>;
|
||||
|
||||
[UnitType.MissileSilo]: Record<string, never>;
|
||||
|
||||
|
||||
@@ -41,6 +41,7 @@ import {
|
||||
} from "./Game";
|
||||
import { GameMap, TileRef, TileUpdate } from "./GameMap";
|
||||
import { GameUpdate, GameUpdateType } from "./GameUpdates";
|
||||
import { generateOilFields } from "./OilFieldGenerator";
|
||||
import { PlayerImpl } from "./PlayerImpl";
|
||||
import { RailNetwork } from "./RailNetwork";
|
||||
import { createRailNetwork } from "./RailNetworkImpl";
|
||||
@@ -117,6 +118,8 @@ export class GameImpl implements Game {
|
||||
}
|
||||
this.addPlayers();
|
||||
|
||||
generateOilFields(this._map, this._config);
|
||||
|
||||
if (!_config.disableNavMesh()) {
|
||||
const graphBuilder = new AbstractGraphBuilder(this.miniGameMap);
|
||||
this._miniWaterGraph = graphBuilder.build();
|
||||
@@ -962,6 +965,12 @@ export class GameImpl implements Game {
|
||||
setOwnerID(ref: TileRef, playerId: number): void {
|
||||
return this._map.setOwnerID(ref, playerId);
|
||||
}
|
||||
hasOilField(ref: TileRef): boolean {
|
||||
return this._map.hasOilField(ref);
|
||||
}
|
||||
setOilField(ref: TileRef, value: boolean): void {
|
||||
return this._map.setOilField(ref, value);
|
||||
}
|
||||
hasFallout(ref: TileRef): boolean {
|
||||
return this._map.hasFallout(ref);
|
||||
}
|
||||
|
||||
@@ -25,6 +25,8 @@ export interface GameMap {
|
||||
hasOwner(ref: TileRef): boolean;
|
||||
|
||||
setOwnerID(ref: TileRef, playerId: number): void;
|
||||
hasOilField(ref: TileRef): boolean;
|
||||
setOilField(ref: TileRef, value: boolean): void;
|
||||
hasFallout(ref: TileRef): boolean;
|
||||
setFallout(ref: TileRef, value: boolean): void;
|
||||
isOnEdgeOfMap(ref: TileRef): boolean;
|
||||
@@ -76,6 +78,7 @@ export class GameMapImpl implements GameMap {
|
||||
|
||||
// State bits (Uint16Array)
|
||||
private static readonly PLAYER_ID_MASK = 0xfff;
|
||||
private static readonly OIL_FIELD_BIT = 12;
|
||||
private static readonly FALLOUT_BIT = 13;
|
||||
private static readonly DEFENSE_BONUS_BIT = 14;
|
||||
// Bit 15 still reserved
|
||||
@@ -192,6 +195,18 @@ export class GameMapImpl implements GameMap {
|
||||
(this.state[ref] & ~GameMapImpl.PLAYER_ID_MASK) | playerId;
|
||||
}
|
||||
|
||||
hasOilField(ref: TileRef): boolean {
|
||||
return Boolean(this.state[ref] & (1 << GameMapImpl.OIL_FIELD_BIT));
|
||||
}
|
||||
|
||||
setOilField(ref: TileRef, value: boolean): void {
|
||||
if (value) {
|
||||
this.state[ref] |= 1 << GameMapImpl.OIL_FIELD_BIT;
|
||||
} else {
|
||||
this.state[ref] &= ~(1 << GameMapImpl.OIL_FIELD_BIT);
|
||||
}
|
||||
}
|
||||
|
||||
hasFallout(ref: TileRef): boolean {
|
||||
return Boolean(this.state[ref] & (1 << GameMapImpl.FALLOUT_BIT));
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ import {
|
||||
PlayerUpdate,
|
||||
UnitUpdate,
|
||||
} from "./GameUpdates";
|
||||
import { generateOilFields } from "./OilFieldGenerator";
|
||||
import { TerrainMapData } from "./TerrainMapLoader";
|
||||
import { TerraNulliusImpl } from "./TerraNulliusImpl";
|
||||
import { UnitGrid, UnitPredicate } from "./UnitGrid";
|
||||
@@ -627,6 +628,7 @@ export class GameView implements GameMap {
|
||||
flag: nation.flag,
|
||||
} satisfies PlayerCosmetics);
|
||||
}
|
||||
generateOilFields(this._map, this._config);
|
||||
}
|
||||
|
||||
isOnEdgeOfMap(ref: TileRef): boolean {
|
||||
@@ -900,6 +902,12 @@ export class GameView implements GameMap {
|
||||
setOwnerID(ref: TileRef, playerId: number): void {
|
||||
return this._map.setOwnerID(ref, playerId);
|
||||
}
|
||||
hasOilField(ref: TileRef): boolean {
|
||||
return this._map.hasOilField(ref);
|
||||
}
|
||||
setOilField(ref: TileRef, value: boolean): void {
|
||||
return this._map.setOilField(ref, value);
|
||||
}
|
||||
hasFallout(ref: TileRef): boolean {
|
||||
return this._map.hasFallout(ref);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,258 @@
|
||||
import { Config } from "../configuration/Config";
|
||||
import { PseudoRandom } from "../PseudoRandom";
|
||||
import { simpleHash } from "../Util";
|
||||
import { GameMap, TileRef } from "./GameMap";
|
||||
|
||||
export function generateOilFields(map: GameMap, config: Config) {
|
||||
const random = new PseudoRandom(
|
||||
simpleHash("oil-fields-" + config.randomSeed()),
|
||||
);
|
||||
|
||||
const numFields = random.nextInt(7, 10);
|
||||
const width = map.width();
|
||||
const height = map.height();
|
||||
|
||||
// 1. Grid-based Seeding (Ensures spread across the map)
|
||||
const gridDivs = Math.ceil(Math.sqrt(numFields + 2));
|
||||
const cellW = width / gridDivs;
|
||||
const cellH = height / gridDivs;
|
||||
|
||||
const cells: { r: number; c: number }[] = [];
|
||||
for (let r = 0; r < gridDivs; r++) {
|
||||
for (let c = 0; c < gridDivs; c++) {
|
||||
cells.push({ r, c });
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = cells.length - 1; i > 0; i--) {
|
||||
const j = random.nextInt(0, i + 1);
|
||||
[cells[i], cells[j]] = [cells[j], cells[i]];
|
||||
}
|
||||
|
||||
const seeds: TileRef[] = [];
|
||||
for (let i = 0; i < numFields && i < cells.length; i++) {
|
||||
const cell = cells[i];
|
||||
let foundSeed = false;
|
||||
for (let attempt = 0; attempt < 100; attempt++) {
|
||||
const rx = random.nextInt(
|
||||
Math.floor(cell.c * cellW + cellW * 0.1),
|
||||
Math.floor((cell.c + 1) * cellW - cellW * 0.1),
|
||||
);
|
||||
const ry = random.nextInt(
|
||||
Math.floor(cell.r * cellH + cellH * 0.1),
|
||||
Math.floor((cell.r + 1) * cellH - cellH * 0.1),
|
||||
);
|
||||
if (map.isValidCoord(rx, ry)) {
|
||||
const t = map.ref(rx, ry);
|
||||
if (map.isLand(t)) {
|
||||
seeds.push(t);
|
||||
foundSeed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!foundSeed) {
|
||||
for (let fallback = 0; fallback < 100; fallback++) {
|
||||
const rx = random.nextInt(0, width);
|
||||
const ry = random.nextInt(0, height);
|
||||
const t = map.ref(rx, ry);
|
||||
if (map.isLand(t)) {
|
||||
seeds.push(t);
|
||||
foundSeed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Elliptical Noise Base
|
||||
const totalLand = map.numLandTiles();
|
||||
const avgTilesPerField = (totalLand * 0.045) / numFields;
|
||||
|
||||
for (const seed of seeds) {
|
||||
const targetSize = avgTilesPerField * (0.6 + random.next() * 1.8);
|
||||
const centerX = map.x(seed);
|
||||
const centerY = map.y(seed);
|
||||
|
||||
// Random rotation and aspect ratio (not too extreme to avoid "thin" shapes)
|
||||
const angle = random.next() * Math.PI;
|
||||
const cosA = Math.cos(angle);
|
||||
const sinA = Math.sin(angle);
|
||||
const ratio = 0.6 + random.next() * 0.4; // 1:1 to 1:1.6
|
||||
|
||||
// Rough radius based on area A = PI * r1 * r2 => A = PI * r * (r * ratio)
|
||||
const r1 = Math.sqrt(targetSize / (Math.PI * ratio));
|
||||
const r2 = r1 * ratio;
|
||||
|
||||
const fieldTiles = new Set<TileRef>();
|
||||
|
||||
// Fill an ellipse with noise - this is the "raw material" for the CA
|
||||
const searchRadius = Math.ceil(Math.max(r1, r2) * 1.5);
|
||||
for (let dx = -searchRadius; dx <= searchRadius; dx++) {
|
||||
for (let dy = -searchRadius; dy <= searchRadius; dy++) {
|
||||
const x = centerX + dx;
|
||||
const y = centerY + dy;
|
||||
if (!map.isValidCoord(x, y)) continue;
|
||||
|
||||
// Rotated coordinate system
|
||||
const rx = dx * cosA + dy * sinA;
|
||||
const ry = -dx * sinA + dy * cosA;
|
||||
|
||||
const dist = (rx * rx) / (r1 * r1) + (ry * ry) / (r2 * r2);
|
||||
|
||||
// Add jittered probability to create organic edges
|
||||
const prob = 0.85 - dist * 0.5;
|
||||
if (random.next() < prob && map.isLand(map.ref(x, y))) {
|
||||
const t = map.ref(x, y);
|
||||
map.setOilField(t, true);
|
||||
fieldTiles.add(t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Heavy Smoothing Pass (Cellular Automata)
|
||||
// Running 5 passes creates very clean, lumpy organic blobs
|
||||
for (let pass = 0; pass < 5; pass++) {
|
||||
const toAdd: TileRef[] = [];
|
||||
const toRemove: TileRef[] = [];
|
||||
|
||||
// Slightly larger bounds to allow smoothing to expand/contract
|
||||
const bounds = getBounds(map, fieldTiles, 2);
|
||||
for (let x = bounds.minX; x <= bounds.maxX; x++) {
|
||||
for (let y = bounds.minY; y <= bounds.maxY; y++) {
|
||||
if (!map.isValidCoord(x, y)) continue;
|
||||
const t = map.ref(x, y);
|
||||
const isLand = map.isLand(t);
|
||||
|
||||
let oilNeighbors = 0;
|
||||
for (let dx = -1; dx <= 1; dx++) {
|
||||
for (let dy = -1; dy <= 1; dy++) {
|
||||
if (dx === 0 && dy === 0) continue;
|
||||
const nx = x + dx,
|
||||
ny = y + dy;
|
||||
if (
|
||||
map.isValidCoord(nx, ny) &&
|
||||
map.hasOilField(map.ref(nx, ny))
|
||||
) {
|
||||
oilNeighbors++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (map.hasOilField(t)) {
|
||||
// Standard CA "Life" rules for smoothing:
|
||||
// Keep if 4+ neighbors, remove if 3 or less (prunes thin parts)
|
||||
if (oilNeighbors <= 3 || !isLand) {
|
||||
toRemove.push(t);
|
||||
}
|
||||
} else if (isLand) {
|
||||
// Fill if 5+ neighbors (fills gaps/holes)
|
||||
if (oilNeighbors >= 5) {
|
||||
toAdd.push(t);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const t of toAdd) {
|
||||
map.setOilField(t, true);
|
||||
fieldTiles.add(t);
|
||||
}
|
||||
for (const t of toRemove) {
|
||||
map.setOilField(t, false);
|
||||
fieldTiles.delete(t);
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Guaranteed Hole Filling (Flood fill)
|
||||
if (fieldTiles.size > 0) {
|
||||
fillHoles(map, fieldTiles);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function fillHoles(map: GameMap, fieldTiles: Set<TileRef>) {
|
||||
const bounds = getBounds(map, fieldTiles, 1);
|
||||
const outside = new Set<TileRef>();
|
||||
const queue: TileRef[] = [];
|
||||
|
||||
for (let x = bounds.minX; x <= bounds.maxX; x++) {
|
||||
for (let y = bounds.minY; y <= bounds.maxY; y++) {
|
||||
if (
|
||||
x === bounds.minX ||
|
||||
x === bounds.maxX ||
|
||||
y === bounds.minY ||
|
||||
y === bounds.maxY
|
||||
) {
|
||||
if (map.isValidCoord(x, y)) {
|
||||
const t = map.ref(x, y);
|
||||
if (!map.hasOilField(t)) {
|
||||
outside.add(t);
|
||||
queue.push(t);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while (queue.length > 0) {
|
||||
const curr = queue.shift()!;
|
||||
const cx = map.x(curr);
|
||||
const cy = map.y(curr);
|
||||
const dirs = [
|
||||
[0, 1],
|
||||
[0, -1],
|
||||
[1, 0],
|
||||
[-1, 0],
|
||||
];
|
||||
for (const [dx, dy] of dirs) {
|
||||
const nx = cx + dx,
|
||||
ny = cy + dy;
|
||||
if (
|
||||
nx >= bounds.minX &&
|
||||
nx <= bounds.maxX &&
|
||||
ny >= bounds.minY &&
|
||||
ny <= bounds.maxY &&
|
||||
map.isValidCoord(nx, ny)
|
||||
) {
|
||||
const next = map.ref(nx, ny);
|
||||
if (!map.hasOilField(next) && !outside.has(next)) {
|
||||
outside.add(next);
|
||||
queue.push(next);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (let x = bounds.minX; x <= bounds.maxX; x++) {
|
||||
for (let y = bounds.minY; y <= bounds.maxY; y++) {
|
||||
if (map.isValidCoord(x, y)) {
|
||||
const t = map.ref(x, y);
|
||||
if (!map.hasOilField(t) && !outside.has(t) && map.isLand(t)) {
|
||||
map.setOilField(t, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getBounds(map: GameMap, tiles: Set<TileRef>, margin: number) {
|
||||
let minX = Infinity,
|
||||
maxX = -Infinity,
|
||||
minY = Infinity,
|
||||
maxY = -Infinity;
|
||||
for (const t of tiles) {
|
||||
const x = map.x(t);
|
||||
const y = map.y(t);
|
||||
if (x < minX) minX = x;
|
||||
if (x > maxX) maxX = x;
|
||||
if (y < minY) minY = y;
|
||||
if (y > maxY) maxY = y;
|
||||
}
|
||||
return {
|
||||
minX: minX - margin,
|
||||
maxX: maxX + margin,
|
||||
minY: minY - margin,
|
||||
maxY: maxY + margin,
|
||||
};
|
||||
}
|
||||
@@ -1042,6 +1042,10 @@ export class PlayerImpl implements Player {
|
||||
(units === undefined || units.some((u) => isStructureType(u)))
|
||||
? this.validStructureSpawnTiles(tile)
|
||||
: [];
|
||||
const validOilRigTiles =
|
||||
tile !== null && (units === undefined || units.includes(UnitType.OilRig))
|
||||
? this.validStructureSpawnTiles(tile, true)
|
||||
: [];
|
||||
return Object.values(UnitType)
|
||||
.filter((u) => units === undefined || units.includes(u))
|
||||
.map((u) => {
|
||||
@@ -1053,7 +1057,11 @@ export class PlayerImpl implements Player {
|
||||
canUpgrade = existingUnit.id();
|
||||
}
|
||||
if (tile !== null) {
|
||||
canBuild = this.canBuild(u, tile, validTiles);
|
||||
canBuild = this.canBuild(
|
||||
u,
|
||||
tile,
|
||||
u === UnitType.OilRig ? validOilRigTiles : validTiles,
|
||||
);
|
||||
}
|
||||
}
|
||||
return {
|
||||
@@ -1117,8 +1125,12 @@ export class PlayerImpl implements Player {
|
||||
case UnitType.DefensePost:
|
||||
case UnitType.SAMLauncher:
|
||||
case UnitType.City:
|
||||
case UnitType.Factory:
|
||||
return this.landBasedStructureSpawn(targetTile, validTiles);
|
||||
case UnitType.OilRig:
|
||||
return this.landBasedStructureSpawn(
|
||||
targetTile,
|
||||
validTiles,
|
||||
unitType === UnitType.OilRig,
|
||||
);
|
||||
default:
|
||||
assertNever(unitType);
|
||||
}
|
||||
@@ -1209,18 +1221,25 @@ export class PlayerImpl implements Player {
|
||||
landBasedStructureSpawn(
|
||||
tile: TileRef,
|
||||
validTiles: TileRef[] | null = null,
|
||||
isOilRig: boolean = false,
|
||||
): TileRef | false {
|
||||
const tiles = validTiles ?? this.validStructureSpawnTiles(tile);
|
||||
const tiles = validTiles ?? this.validStructureSpawnTiles(tile, isOilRig);
|
||||
if (tiles.length === 0) {
|
||||
return false;
|
||||
}
|
||||
return tiles[0];
|
||||
}
|
||||
|
||||
private validStructureSpawnTiles(tile: TileRef): TileRef[] {
|
||||
private validStructureSpawnTiles(
|
||||
tile: TileRef,
|
||||
isOilRig: boolean = false,
|
||||
): TileRef[] {
|
||||
if (this.mg.owner(tile) !== this) {
|
||||
return [];
|
||||
}
|
||||
if (isOilRig && !this.mg.hasOilField(tile)) {
|
||||
return [];
|
||||
}
|
||||
const searchRadius = 15;
|
||||
const searchRadiusSquared = searchRadius ** 2;
|
||||
|
||||
@@ -1234,7 +1253,8 @@ export class PlayerImpl implements Player {
|
||||
const nearbyTiles = this.mg.bfs(tile, (gm, t) => {
|
||||
return (
|
||||
this.mg.euclideanDistSquared(tile, t) < searchRadiusSquared &&
|
||||
gm.ownerID(t) === this.smallID()
|
||||
gm.ownerID(t) === this.smallID() &&
|
||||
(!isOilRig || gm.hasOilField(t))
|
||||
);
|
||||
});
|
||||
const validSet: Set<TileRef> = new Set(nearbyTiles);
|
||||
|
||||
@@ -248,13 +248,13 @@ export class RailNetworkImpl implements RailNetwork {
|
||||
const maxPathSize = this.game.config().railroadMaxSize();
|
||||
|
||||
// Cannot connect if outside the max range of a factory
|
||||
if (!this.game.hasUnitNearby(tile, maxRange, UnitType.Factory)) {
|
||||
if (!this.game.hasUnitNearby(tile, maxRange, UnitType.OilRig)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const neighbors = this.game.nearbyUnits(tile, maxRange, [
|
||||
UnitType.City,
|
||||
UnitType.Factory,
|
||||
UnitType.OilRig,
|
||||
UnitType.Port,
|
||||
]);
|
||||
neighbors.sort((a, b) => a.distSquared - b.distSquared);
|
||||
@@ -293,7 +293,7 @@ export class RailNetworkImpl implements RailNetwork {
|
||||
const neighbors = this.game.nearbyUnits(
|
||||
station.tile(),
|
||||
this.game.config().trainStationMaxRange(),
|
||||
[UnitType.City, UnitType.Factory, UnitType.Port],
|
||||
[UnitType.City, UnitType.OilRig, UnitType.Port],
|
||||
);
|
||||
|
||||
const editedClusters = new Set<Cluster>();
|
||||
|
||||
@@ -45,7 +45,7 @@ export function createTrainStopHandlers(
|
||||
return {
|
||||
[UnitType.City]: new TradeStationStopHandler(),
|
||||
[UnitType.Port]: new TradeStationStopHandler(),
|
||||
[UnitType.Factory]: new FactoryStopHandler(),
|
||||
[UnitType.OilRig]: new FactoryStopHandler(),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -76,7 +76,7 @@ export class UnitImpl implements Unit {
|
||||
case UnitType.DefensePost:
|
||||
case UnitType.SAMLauncher:
|
||||
case UnitType.City:
|
||||
case UnitType.Factory:
|
||||
case UnitType.OilRig:
|
||||
this.mg.stats().unitBuild(_owner, this._type);
|
||||
}
|
||||
}
|
||||
@@ -195,7 +195,7 @@ export class UnitImpl implements Unit {
|
||||
case UnitType.DefensePost:
|
||||
case UnitType.SAMLauncher:
|
||||
case UnitType.City:
|
||||
case UnitType.Factory:
|
||||
case UnitType.OilRig:
|
||||
this.mg.stats().unitCapture(newOwner, this._type);
|
||||
this.mg.stats().unitLose(this._owner, this._type);
|
||||
break;
|
||||
@@ -290,7 +290,7 @@ export class UnitImpl implements Unit {
|
||||
case UnitType.Port:
|
||||
case UnitType.SAMLauncher:
|
||||
case UnitType.Warship:
|
||||
case UnitType.Factory:
|
||||
case UnitType.OilRig:
|
||||
this.mg.stats().unitDestroy(destroyer, this._type);
|
||||
this.mg.stats().unitLose(this.owner(), this._type);
|
||||
break;
|
||||
|
||||
@@ -30,9 +30,9 @@ vi.mock("../../../src/client/graphics/layers/BuildMenu", async () => {
|
||||
countable: true,
|
||||
},
|
||||
{
|
||||
unitType: UnitType.Factory,
|
||||
key: "unit_type.factory",
|
||||
description: "unit_type.factory_desc",
|
||||
unitType: UnitType.OilRig,
|
||||
key: "unit_type.oil_rig",
|
||||
description: "unit_type.oil_rig_desc",
|
||||
icon: "factory-icon",
|
||||
countable: true,
|
||||
},
|
||||
@@ -120,7 +120,7 @@ describe("RadialMenuElements", () => {
|
||||
mockPlayerActions = {
|
||||
buildableUnits: [
|
||||
{ type: UnitType.City, canBuild: true },
|
||||
{ type: UnitType.Factory, canBuild: true },
|
||||
{ type: UnitType.OilRig, canBuild: true },
|
||||
{ type: UnitType.AtomBomb, canBuild: true },
|
||||
{ type: UnitType.Warship, canBuild: true },
|
||||
{ type: UnitType.HydrogenBomb, canBuild: true },
|
||||
@@ -211,7 +211,7 @@ describe("RadialMenuElements", () => {
|
||||
|
||||
const subMenu = attackMenuElement.subMenu!(mockParams);
|
||||
|
||||
const constructionUnitTypes = [UnitType.City, UnitType.Factory];
|
||||
const constructionUnitTypes = [UnitType.City, UnitType.OilRig];
|
||||
const returnedUnitTypes = subMenu.map((item) => {
|
||||
const unitTypeStr = item.id.replace("attack_", "");
|
||||
return Object.values(UnitType).find(
|
||||
@@ -254,7 +254,7 @@ describe("RadialMenuElements", () => {
|
||||
expect(subMenu).toBeDefined();
|
||||
expect(subMenu.length).toBeGreaterThan(0);
|
||||
|
||||
const constructionUnitTypes = [UnitType.City, UnitType.Factory];
|
||||
const constructionUnitTypes = [UnitType.City, UnitType.OilRig];
|
||||
const returnedUnitTypes = subMenu.map((item) => {
|
||||
const unitTypeStr = item.id.replace("build_", "");
|
||||
return Object.values(UnitType).find(
|
||||
@@ -597,8 +597,8 @@ describe("RadialMenuElements", () => {
|
||||
|
||||
expect(translateText).toHaveBeenCalledWith("unit_type.city");
|
||||
expect(translateText).toHaveBeenCalledWith("unit_type.city_desc");
|
||||
expect(translateText).toHaveBeenCalledWith("unit_type.factory");
|
||||
expect(translateText).toHaveBeenCalledWith("unit_type.factory_desc");
|
||||
expect(translateText).toHaveBeenCalledWith("unit_type.oil_rig");
|
||||
expect(translateText).toHaveBeenCalledWith("unit_type.oil_rig_desc");
|
||||
});
|
||||
|
||||
it("should use translateText for tooltip items in attack menu", async () => {
|
||||
|
||||
Reference in New Issue
Block a user