Skip to content

Commit 28c483c

Browse files
authored
Merge pull request #901 from ajhalme/barbs
Spawn barbarian sea units on coastal tiles, with a maximum range of 2 tiles away from the camp if closer tiles are unavailable
2 parents eedeac5 + 175424b commit 28c483c

5 files changed

Lines changed: 94 additions & 41 deletions

File tree

C7Engine/C7GameData/GameData.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,26 @@ internal void RemoveUnit(MapUnit unit) {
266266
log.Information($"Player {owner} removed unit: {unit}");
267267
}
268268

269+
internal void SpawnUnit(Player player, UnitPrototype unitType, Tile tile) {
270+
// TODO: consolidate unit spawning routines (here)
271+
272+
MapUnit newUnit = unitType.GetInstance(this);
273+
newUnit.location = tile;
274+
newUnit.owner = player;
275+
newUnit.nationality = player.civilization;
276+
// TODO: make this a conscript.
277+
newUnit.experienceLevelKey = defaultExperienceLevelKey;
278+
newUnit.experienceLevel = defaultExperienceLevel;
279+
newUnit.hitPointsRemaining = 3;
280+
281+
tile.unitsOnTile.Add(newUnit);
282+
mapUnits.Add(newUnit);
283+
player.units.Add(newUnit);
284+
285+
log.Debug("New unit of type {type} added at {tile} for player {player}",
286+
unitType.name, tile, player);
287+
}
288+
269289
public int TechCostFor(Tech tech, Player player) {
270290
// Cost formula from https://forums.civfanatics.com/threads/research-cost-formula-v1-29f.29485/.
271291
// Research Cost = [MM * [10*COST * (1 - N/[CL*1.75])]/(CF * 10)] - progress

C7Engine/C7GameData/TerrainType.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,10 @@ public bool isWater() {
4949
return Key.Equals("coast") || Key.Equals("sea") || Key.Equals("ocean");
5050
}
5151

52+
public bool isCoast() {
53+
return Key.Equals("coast");
54+
}
55+
5256
public override string ToString() {
5357
return DisplayName + "(" + baseFoodProduction + ", " + baseShieldProduction + ", " + baseCommerceProduction + ")";
5458
}

C7Engine/C7GameData/Tile.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,10 @@ public bool IsWater() {
234234
return baseTerrainType.isWater();
235235
}
236236

237+
public bool IsCoast() {
238+
return baseTerrainType.isCoast();
239+
}
240+
237241
public bool IsAllowCities() {
238242
return overlayTerrainType.allowCities && !hasBarbarianCamp;
239243
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Runtime.InteropServices;
5+
using C7GameData;
6+
7+
namespace C7Engine;
8+
9+
public class BarbarianInteractions {
10+
public static int SpawnBarbarians(GameData gameData) {
11+
Player barbPlayer = gameData.players.Find(player => player.isBarbarians);
12+
13+
// A random 5% of camps will spawn a unit each turn. Shuffle the
14+
// camps to make this random.
15+
int barbariansToSpawn = (int)Math.Ceiling(GameData.rng.Next(gameData.map.barbarianCamps.Count) / 20.0);
16+
List<int> tileIndicies = Enumerable.Range(0, gameData.map.barbarianCamps.Count).ToList();
17+
GameData.rng.Shuffle<int>(CollectionsMarshal.AsSpan(tileIndicies));
18+
19+
// Sample barbarian camps
20+
var spawningCamps = tileIndicies
21+
.Select(i => gameData.map.barbarianCamps[i])
22+
.Take(barbariansToSpawn);
23+
24+
// Spawn a unit
25+
foreach (Tile camp in spawningCamps) {
26+
UnitPrototype unitType = SelectBarbarianUnitType(gameData.barbarianInfo, camp);
27+
Tile tile = SelectSpawnTile(barbPlayer, camp, unitType);
28+
if (tile != null) {
29+
gameData.SpawnUnit(barbPlayer, unitType, tile);
30+
}
31+
}
32+
33+
return barbariansToSpawn;
34+
}
35+
36+
public static UnitPrototype SelectBarbarianUnitType(BarbarianInfo barbInfo, Tile tile) {
37+
// Coastal camps have a 20% chance of spawning a sea unit
38+
if (tile.NeighborsWater() && GameData.rng.Next(100) < 20) {
39+
return barbInfo.barbarianSeaUnitProto;
40+
}
41+
42+
// Land units are generated in a 3:1 ratio, three advanced units for every basic barbarian
43+
return GameData.rng.Next(100) < 25 ? barbInfo.advancedBarbarian : barbInfo.basicBarbarian;
44+
}
45+
46+
public static Tile SelectSpawnTile(Player player, Tile camp, UnitPrototype unitType) {
47+
// Spawn land units at the camp
48+
if (unitType.IsLandUnit())
49+
return camp;
50+
51+
// Spawn sea units on a coast tile, but not in a lake or on a tile occupied by another player
52+
bool CanSpawnSeaUnits(Tile t) =>
53+
t.IsCoast() && !t.isFreshWater && t.unitsOnTile.TrueForAll(u => u.owner == player);
54+
55+
if (unitType.IsSeaUnit()) {
56+
// Check first two ranks around camp for a suitable tile, or bail with a null
57+
return camp.FindInRing(1, CanSpawnSeaUnits)
58+
?? camp.FindInRing(2, CanSpawnSeaUnits);
59+
}
60+
61+
// Default to camp
62+
return camp;
63+
}
64+
}

C7Engine/EntryPoints/TurnHandling.cs

Lines changed: 2 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,8 @@
11
using System.Diagnostics;
2-
using System.Collections.Generic;
3-
using System.Runtime.InteropServices;
4-
using System.Linq;
2+
using System.Net.NetworkInformation;
53
using Serilog;
64

75
namespace C7Engine {
8-
using System;
96
using System.Threading.Tasks;
107
using C7GameData;
118

@@ -39,7 +36,7 @@ internal static async Task AdvanceTurn() {
3936
//at the same place in the order. Confirmed this is what Civ3 does.
4037
UnitInteractions.ClearWaitQueue();
4138

42-
SpawnBarbarians(gameData);
39+
BarbarianInteractions.SpawnBarbarians(gameData);
4340

4441
gameData.turn++;
4542
foreach (Player player in gameData.players) {
@@ -109,42 +106,6 @@ private static async Task<bool> PlayPlayerTurns(GameData gameData, bool firstTur
109106
return false;
110107
}
111108

112-
private static void SpawnBarbarians(GameData gameData) {
113-
Player barbPlayer = gameData.players.Find(player => player.isBarbarians);
114-
115-
// A random 5% of camps will spawn a unit each turn. Shuffle the
116-
// camps to make this random.
117-
int barbariansToSpawn = (int)Math.Ceiling(GameData.rng.Next(gameData.map.barbarianCamps.Count) / 20.0);
118-
List<int> tileIndicies = Enumerable.Range(0, gameData.map.barbarianCamps.Count).ToList();
119-
GameData.rng.Shuffle<int>(CollectionsMarshal.AsSpan(tileIndicies));
120-
121-
for (int i = 0; i < barbariansToSpawn; ++i) {
122-
Tile tile = gameData.map.barbarianCamps[tileIndicies[i]];
123-
124-
// Its a 3:1 ratio of advanced to basic barbarians.
125-
UnitPrototype unitType = GameData.rng.Next(100) < 25 ? gameData.barbarianInfo.advancedBarbarian : gameData.barbarianInfo.basicBarbarian;
126-
// Give costal camps a chance to spawn a boat.
127-
if (tile.NeighborsWater() && GameData.rng.Next(100) < 20) {
128-
unitType = gameData.barbarianInfo.barbarianSeaUnitProto;
129-
}
130-
131-
MapUnit newUnit = unitType.GetInstance(gameData);
132-
newUnit.location = tile;
133-
newUnit.owner = barbPlayer;
134-
newUnit.nationality = barbPlayer.civilization;
135-
// TODO: make this a conscript.
136-
newUnit.experienceLevelKey = gameData.defaultExperienceLevelKey;
137-
newUnit.experienceLevel = gameData.defaultExperienceLevel;
138-
newUnit.hitPointsRemaining = 3;
139-
140-
tile.unitsOnTile.Add(newUnit);
141-
gameData.mapUnits.Add(newUnit);
142-
barbPlayer.units.Add(newUnit);
143-
144-
log.Debug("New barbarian of type {type} added at {tile}", unitType.name, tile);
145-
}
146-
}
147-
148109
///Eventually we'll have a game year or month or whatever, but for now this provides feedback on our progression
149110
public static int GetTurnNumber() {
150111
return EngineStorage.gameData.turn;

0 commit comments

Comments
 (0)