Added offencive frontend

This commit is contained in:
Samy Avrillon 2024-07-09 19:06:30 +02:00
parent 8454e3a711
commit 6e2f916927
Signed by: Mysaa
GPG Key ID: 0220AC4A3D6A328B
15 changed files with 974 additions and 199 deletions

View File

@ -2,13 +2,11 @@ package com.bernard.greposimu;
import java.util.AbstractMap;
import java.util.AbstractSet;
import java.util.Collection;
import java.util.EnumSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Random;
import java.util.Set;
import java.util.function.Function;
import com.bernard.greposimu.model.game.Identified;
@ -82,4 +80,9 @@ public class Utils {
};
}
public static <E> E randFromSet(Random r, Set<E> set) {
E el = set.stream().sorted().skip(r.nextInt(set.size())).findFirst().get();
return el;
}
}

View File

@ -40,7 +40,6 @@ public class JSONReader {
JSONObject powersJ = json.getJSONObject("powers");
Set<Power> powers = new HashSet<>();
for(String p : powersJ.keySet()) {
System.out.println("Power "+p);
JSONObject power = powersJ.getJSONObject(p);
JSONObject metadefaults = power.isNull("meta_defaults")?null:power.getJSONObject("meta_defaults");
if(power.isNull("god_id") || power.getString("god_id").isEmpty()) {
@ -61,7 +60,6 @@ public class JSONReader {
.orElseGet(() -> power.optJSONObject("description"))
).getJSONObject(dependentKind).keySet();
for(String kind : kinds){
System.out.println("kind "+kind);
powers.add(new MultitypePower(
power.getString("id")+":"+kind,
Optional.ofNullable(power.optString("name")).orElseGet(() -> power.getJSONObject("name").getJSONObject(dependentKind).getString(kind)),

View File

@ -0,0 +1,7 @@
package com.bernard.greposimu.controller;
public class Randomizer {
}

View File

@ -4,6 +4,7 @@ import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import org.springframework.stereotype.Controller;
@ -11,13 +12,16 @@ import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import com.bernard.greposimu.GrepoSimuApplication;
import com.bernard.greposimu.Utils;
import com.bernard.greposimu.engine.game.Fight;
import com.bernard.greposimu.model.DefContext;
import com.bernard.greposimu.model.FightStats;
import com.bernard.greposimu.model.OffContext;
import com.bernard.greposimu.model.game.GameConfig;
import com.bernard.greposimu.model.game.God;
import com.bernard.greposimu.model.game.units.Unit;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
@ -26,91 +30,249 @@ import com.fasterxml.jackson.databind.SerializationFeature;
public class SimulatorController {
@GetMapping("/simulator")
public String simulator(Model model) throws IOException {
public String simulator(Model model, @RequestParam boolean random) throws IOException {
GameConfig gc = GrepoSimuApplication.GREPOLIS_GC;
model.addAttribute("heroes", gc.getHeroes());
model.addAttribute("defUnits", Fight.relevantDefUnits(gc));
model.addAttribute("defCounsellors",Fight.relevantDefCounsellors(gc));
model.addAttribute("defResearches",Fight.relevantDefResearches(gc));
model.addAttribute("defPowers",Fight.relevantDefPowers(gc));
model.addAttribute("ctx",new DefSimulatorParams());
model.addAttribute("defCounsellors",DefContext.COUNSELLORS);
model.addAttribute("defResearches",DefContext.RESEARCHES);
model.addAttribute("defPowers",DefContext.POWERS);
model.addAttribute("offUnits", Fight.relevantOffUnits(gc));
model.addAttribute("offCounsellors",OffContext.COUNSELLORS);
model.addAttribute("offResearches",OffContext.RESEARCHES);
model.addAttribute("offPowers",OffContext.POWERS);
SimulatorParams params = new SimulatorParams();
if(random)
params.randomize(new Random(), gc);
model.addAttribute("ctx",params);
return "simulator";
}
@PostMapping("/simulate")
@GetMapping("/simulate")
public String simulate(@ModelAttribute DefSimulatorParams defParams, Model model) throws IOException {
if(defParams == null)
defParams = new DefSimulatorParams();
public String simulate(@ModelAttribute SimulatorParams params, Model model) throws IOException {
if(params == null)
params = new SimulatorParams();
GameConfig gc = GrepoSimuApplication.GREPOLIS_GC;
DefContext defCtx = defParams.asDefContext(gc);
FightStats cityStats = Fight.computeDefStats(gc,defCtx);
ObjectMapper mapper = new ObjectMapper();
DefContext defCtx = params.asDefContext(gc);
OffContext offCtx = params.asOffContext(gc);
FightStats defStats = Fight.computeDefStats(gc,defCtx);
FightStats offStats = Fight.computeOffStats(gc,offCtx);
ObjectMapper mapper = new ObjectMapper();
mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
model.addAttribute("content",cityStats.toString()+"\n"+mapper.writerWithDefaultPrettyPrinter().writeValueAsString(defCtx));
model.addAttribute("content",
defStats.toString()+"\n"+
offStats.toString()+"\n"+
mapper.writerWithDefaultPrettyPrinter().writeValueAsString(defCtx)+"\n"+
mapper.writerWithDefaultPrettyPrinter().writeValueAsString(offCtx)
);
return "debug";
}
public static class DefSimulatorParams {
public static class SimulatorParams {
// unitID -> number of units
public Map<String, Integer> units = new HashMap<>();
public String hero = "";
public int heroLevel = 0;
public Map<String, Integer> defUnits = new HashMap<>();
public Map<String, Integer> offUnits = new HashMap<>();
public String defHero = "";
public int defHeroLevel = 0;
public String offHero = "";
public int offHeroLevel = 0;
public int wallLevel = 0;
public boolean hasTower = false;
public Set<String> powers = new HashSet<>();
public Set<String> researches = new HashSet<>();
public Set<String> counsellors = new HashSet<>();
public Set<String> defPowers = new HashSet<>();
public Set<String> offPowers = new HashSet<>();
public Set<String> defResearches = new HashSet<>();
public Set<String> offResearches = new HashSet<>();
public Set<String> defCounsellors = new HashSet<>();
public Set<String> offCounsellors = new HashSet<>();
public boolean nightBonus = false;
int luck, moral;
int olympicSwordGrepolympiaSummerLevel = 1;
int offOlympicSensesGrepolympiaSummerLevel = 1;
int aresRageLevel = 1;
int aresArmyFurySpent = 0;
int bloodlustFurySpent = 0;
int defOlympicSensesGrepolympiaSummerLevel = 1;
int olympicTorchGrepolympiaSummerLevel = 1;
int soteriasShrineLevel = 1;
boolean strategyBreach = false;
boolean allianceModifier = false;
public DefContext asDefContext(GameConfig gc) {
Map<Unit,Integer> unitsU = new HashMap<>(units.size());
for(String u : units.keySet())
unitsU.put(gc.getUnit(u), units.get(u));
Map<Unit,Integer> unitsU = new HashMap<>(defUnits.size());
for(String u : defUnits.keySet())
unitsU.put(gc.getUnit(u), defUnits.get(u));
return new DefContext(
unitsU,
gc.getHero(hero),
heroLevel,
gc.getHero(defHero),
defHeroLevel,
wallLevel,
hasTower,
powers,
1, 1, 1,
researches,
counsellors,
defPowers,
defOlympicSensesGrepolympiaSummerLevel, olympicTorchGrepolympiaSummerLevel, soteriasShrineLevel,
defResearches,
defCounsellors,
nightBonus
);
}
public Map<String, Integer> getUnits() {
return units;
public static Object random() {
// TODO Auto-generated method stub
return null;
}
public void setUnits(Map<String, Integer> units) {
this.units = units;
public OffContext asOffContext(GameConfig gc) {
Map<Unit,Integer> unitsU = new HashMap<>(offUnits.size());
for(String u : offUnits.keySet())
unitsU.put(gc.getUnit(u), offUnits.get(u));
return new OffContext(
unitsU,
gc.getHero(defHero),
defHeroLevel,
luck, moral, defPowers,
olympicSwordGrepolympiaSummerLevel, offOlympicSensesGrepolympiaSummerLevel, aresRageLevel, aresArmyFurySpent, bloodlustFurySpent, defResearches,
defCounsellors, allianceModifier, allianceModifier
);
}
public void randomize(Random r,GameConfig gc) {
offUnits = new HashMap<>();
God g = null;
if(r.nextDouble()<0.8)
g = Utils.randFromSet(r, gc.getGods());
for(Unit u : Fight.relevantOffUnits(gc))
if(u.getGod() == null || u.getGod().equals(g))
if(r.nextDouble() < 0.7)
offUnits.put(u.getId(), (int) ((r.nextExponential()*50+10)/Math.max(u.getPopulation(),1)));
defUnits = new HashMap<>();
g = null;
if(r.nextDouble()<0.8)
g = Utils.randFromSet(r, gc.getGods());
for(Unit u : Fight.relevantDefUnits(gc))
if(u.getGod() == null || u.getGod().equals(g))
if(r.nextDouble() < 0.7)
defUnits.put(u.getId(), (int) ((r.nextExponential()*50+10)/Math.max(u.getPopulation(),1)));
offHero = null;
offHeroLevel = 0;
if(r.nextDouble()<.7) {
offHero = Utils.randFromSet(r, gc.getHeroes()).getId();
offHeroLevel = r.nextInt(1, 21);
}
defHero = null;
defHeroLevel = 0;
if(r.nextDouble()<.7) {
defHero = Utils.randFromSet(r, gc.getHeroes()).getId();
defHeroLevel = r.nextInt(1, 21);
}
wallLevel = r.nextInt(0, 26);
hasTower = (wallLevel>20) && (r.nextDouble()<0.2);
luck = (int) (Math.tanh(r.nextGaussian())*20);
moral = 100;
if(r.nextDouble()<.7)
moral = (int) Math.max(100-(r.nextExponential()*20),0);
offPowers = new HashSet<>();
defPowers = new HashSet<>();
for(String p : OffContext.POWERS) {
if(r.nextDouble()<0.05)
offPowers.add(p);
if(r.nextDouble()<0.05)
defPowers.add(p);
}
olympicSwordGrepolympiaSummerLevel = r.nextInt(1, 5);
offOlympicSensesGrepolympiaSummerLevel = r.nextInt(1, 5);
aresRageLevel = r.nextInt(1, 11);
aresArmyFurySpent = r.nextInt(1, 5000);
bloodlustFurySpent = r.nextInt(1, 5000);
defOlympicSensesGrepolympiaSummerLevel = r.nextInt(1, 5);
olympicTorchGrepolympiaSummerLevel = r.nextInt(1, 5);
soteriasShrineLevel = r.nextInt(1, 11);
offResearches = new HashSet<>();
defResearches = new HashSet<>();
for(String p : OffContext.RESEARCHES) {
if(r.nextDouble()<0.20)
offResearches.add(p);
if(r.nextDouble()<0.20)
defResearches.add(p);
}
offCounsellors = new HashSet<>();
defCounsellors = new HashSet<>();
for(String p : OffContext.COUNSELLORS) {
if(r.nextDouble()<0.05)
offCounsellors.add(p);
if(r.nextDouble()<0.05)
defCounsellors.add(p);
}
strategyBreach = (r.nextDouble()<0.02);
allianceModifier = (r.nextDouble()<0.001);
nightBonus = (r.nextDouble()<0.02);
}
public String getHero() {
return hero;
public Map<String, Integer> getDefUnits() {
return defUnits;
}
public void setHero(String hero) {
this.hero = hero;
public void setDefUnits(Map<String, Integer> defUnits) {
this.defUnits = defUnits;
}
public int getHeroLevel() {
return heroLevel;
public Map<String, Integer> getOffUnits() {
return offUnits;
}
public void setHeroLevel(int heroLevel) {
this.heroLevel = heroLevel;
public void setOffUnits(Map<String, Integer> offUnits) {
this.offUnits = offUnits;
}
public String getDefHero() {
return defHero;
}
public void setDefHero(String defHero) {
this.defHero = defHero;
}
public int getDefHeroLevel() {
return defHeroLevel;
}
public void setDefHeroLevel(int defHeroLevel) {
this.defHeroLevel = defHeroLevel;
}
public String getOffHero() {
return offHero;
}
public void setOffHero(String offHero) {
this.offHero = offHero;
}
public int getOffHeroLevel() {
return offHeroLevel;
}
public void setOffHeroLevel(int offHeroLevel) {
this.offHeroLevel = offHeroLevel;
}
public int getWallLevel() {
@ -129,22 +291,98 @@ public static class DefSimulatorParams {
this.hasTower = hasTower;
}
public Set<String> getPowers() {
return powers;
}
public Map<String,Boolean> getPowersAsMap() {
return Utils.setToMap(powers);
public Set<String> getDefPowers() {
return defPowers;
}
public Map<String,Boolean> getResearchesAsMap() {
return Utils.setToMap(researches);
public void setDefPowers(Set<String> defPowers) {
this.defPowers = defPowers;
}
public Map<String,Boolean> getCounsellorsAsMap() {
return Utils.setToMap(counsellors);
public Set<String> getOffPowers() {
return offPowers;
}
public void setOffPowers(Set<String> offPowers) {
this.offPowers = offPowers;
}
public Set<String> getDefResearches() {
return defResearches;
}
public void setDefResearches(Set<String> defResearches) {
this.defResearches = defResearches;
}
public Set<String> getOffResearches() {
return offResearches;
}
public void setOffResearches(Set<String> offResearches) {
this.offResearches = offResearches;
}
public Set<String> getDefCounsellors() {
return defCounsellors;
}
public void setDefCounsellors(Set<String> defCounsellors) {
this.defCounsellors = defCounsellors;
}
public Set<String> getOffCounsellors() {
return offCounsellors;
}
public void setOffCounsellors(Set<String> offCounsellors) {
this.offCounsellors = offCounsellors;
}
public int getLuck() {
return luck;
}
public void setLuck(int luck) {
this.luck = luck;
}
public int getMoral() {
return moral;
}
public void setMoral(int moral) {
this.moral = moral;
}
public boolean isStrategyBreach() {
return strategyBreach;
}
public void setStrategyBreach(boolean strategyBreach) {
this.strategyBreach = strategyBreach;
}
public boolean isAllianceModifier() {
return allianceModifier;
}
public void setAllianceModifier(boolean allianceModifier) {
this.allianceModifier = allianceModifier;
}
public Map<String, Boolean> getDefPowersAsMap() {
return Utils.setToMap(defPowers);
}
public Map<String, Boolean> getDefResearchesAsMap() {
return Utils.setToMap(defResearches);
}
public Map<String, Boolean> getDefCounsellorsAsMap() {
return Utils.setToMap(defCounsellors);
}
public boolean isNightBonus() {
return nightBonus;
}
@ -153,6 +391,5 @@ public static class DefSimulatorParams {
this.nightBonus = nightBonus;
}
}
}

View File

@ -8,6 +8,7 @@ import com.bernard.greposimu.model.FightStats;
import com.bernard.greposimu.model.OffContext;
import com.bernard.greposimu.model.game.GameConfig;
import com.bernard.greposimu.model.game.researches.Research;
import com.bernard.greposimu.model.game.units.FightType;
import com.bernard.greposimu.model.game.units.NavalUnit;
import com.bernard.greposimu.model.game.units.TerrestrialUnit;
import com.bernard.greposimu.model.game.units.Unit;
@ -41,18 +42,14 @@ public class Fight {
if(u.isMythological())
unitsBonuses.put(u, FightStats.add(unitsBonuses.getOrDefault(u, FightStats.zero()), FightStats.cst(0.1)));
if(def.hasPhalanx())
for(Unit u : gc.getUnits())
if(u.isGround())
unitsBonuses.put(u, FightStats.add(unitsBonuses.getOrDefault(u, FightStats.zero()), FightStats.cst(0.1)));
everyoneStatsBonus = FightStats.add(everyoneStatsBonus, FightStats.terrestre(0.1));
if(def.hasRam())
for(Unit u : gc.getUnits())
if(u.isNaval())
unitsBonuses.put(u, FightStats.add(unitsBonuses.getOrDefault(u, FightStats.zero()), FightStats.cst(0.1)));
everyoneStatsBonus = FightStats.add(everyoneStatsBonus, FightStats.naval(0.1));
// Counsellors
if(def.hasPriest())
for(Unit u : gc.getUnits())
if(u.isGround())
if(u.isMythological())
unitsBonuses.put(u, FightStats.add(unitsBonuses.getOrDefault(u, FightStats.zero()), FightStats.cst(0.2)));
if(def.hasCommander())
everyoneStatsBonus = FightStats.add(everyoneStatsBonus, FightStats.terrestre(0.2));
@ -75,7 +72,7 @@ public class Fight {
if(def.getOlympicTorchGrepolympiaSummerLevel()!=0)
everyoneStatsBonus = FightStats.add(everyoneStatsBonus, FightStats.terrestre(+0.05*def.getOlympicTorchGrepolympiaSummerLevel()));
if(def.getSoteriasShrineLevel()!=0)
everyoneStatsBonus = FightStats.add(everyoneStatsBonus, FightStats.terrestre(+0.007*def.getOlympicTorchGrepolympiaSummerLevel()));
everyoneStatsBonus = FightStats.add(everyoneStatsBonus, FightStats.terrestre(+0.007*def.getSoteriasShrineLevel()));
if(def.hasNarcissism())
everyoneStatsBonus = FightStats.add(everyoneStatsBonus, FightStats.terrestre(-0.1));
@ -95,6 +92,16 @@ public class Fight {
));
}
// HeroStat
if(def.getHero() != null) {
total = FightStats.add(total, new FightStats(
def.getHero().getHackDef() * (1.0+0.1*def.getHeroLevel()),
def.getHero().getPierceDef() * (1.0+0.1*def.getHeroLevel()),
def.getHero().getDistanceDef() * (1.0+0.1*def.getHeroLevel()),
0.0
));
}
return total;
}
@ -109,27 +116,127 @@ public class Fight {
throw new UnsupportedOperationException("I don't know how to manage units of type "+u.getClass().getName());
}
public static List<String> relevantDefPowers(GameConfig gd) {
return List.of("acumen", "divine_senses", "myrmidion_attack", "trojan_defense", "defense_boost",
"defense_penalty", "longterm_defense_boost", "assassins_acumen", "rare_defense_boost",
"epic_defense_boost", "olympic_torch.grepolympia_summer", "olympic_senses.grepolympia_summer", "missions_power_4.missions_dionysia",
"divine_battle_strategy_rare", "divine_battle_strategy_epic", "naval_battle_strategy_rare",
"naval_battle_strategy_epic", "land_battle_strategy_rare", "land_battle_strategy_epic",
"soterias_shrine.not_cast","narcissism");
public static FightStats computeOffStats(GameConfig gc, OffContext off) {
FightStats everyoneStatsBonus = FightStats.zero();
Map<Unit,FightStats> unitsBonuses;
// Heroes
unitsBonuses = Heroes.heroFightBonuses(gc, off.getHero(), off.getHeroLevel(), false);
// Researches
if(off.hasDivineSelection())
for(Unit u : gc.getUnits())
if(u.isMythological())
unitsBonuses.put(u, FightStats.add(unitsBonuses.getOrDefault(u, FightStats.zero()), FightStats.cst(0.1)));
if(off.hasPhalanx())
everyoneStatsBonus = FightStats.add(everyoneStatsBonus, FightStats.terrestre(0.1));
if(off.hasRam())
everyoneStatsBonus = FightStats.add(everyoneStatsBonus, FightStats.naval(0.1));
// Counsellors
if(off.hasPriest())
for(Unit u : gc.getUnits())
if(u.isMythological())
unitsBonuses.put(u, FightStats.add(unitsBonuses.getOrDefault(u, FightStats.zero()), FightStats.cst(0.2)));
if(off.hasCommander())
everyoneStatsBonus = FightStats.add(everyoneStatsBonus, FightStats.terrestre(0.2));
if(off.hasCaptain())
everyoneStatsBonus = FightStats.add(everyoneStatsBonus, FightStats.naval(0.2));
// Powers
if(off.hasMyrmidionAttack())
everyoneStatsBonus = FightStats.add(everyoneStatsBonus, FightStats.terrestre(+0.1));
if(off.hasAttackBoost())
everyoneStatsBonus = FightStats.add(everyoneStatsBonus, FightStats.terrestre(+0.1));
if(off.hasAttackPenalty())
everyoneStatsBonus = FightStats.add(everyoneStatsBonus, FightStats.terrestre(-0.1));
if(off.hasLongtermAttackBoost())
everyoneStatsBonus = FightStats.add(everyoneStatsBonus, FightStats.terrestre(+0.1));
if(off.hasLuxurious_residence())
;//XXX Unimplemented
if(off.hasAttack_ship_attack_boost_small())
unitsBonuses.put(gc.getUnit("attack_ship"), FightStats.add(unitsBonuses.getOrDefault(gc.getUnit("attack_ship"), FightStats.zero()), FightStats.cst(0.1)));
if(off.hasAttack_ship_attack_boost_medium())
unitsBonuses.put(gc.getUnit("attack_ship"), FightStats.add(unitsBonuses.getOrDefault(gc.getUnit("attack_ship"), FightStats.zero()), FightStats.cst(0.2)));
if(off.hasAttack_ship_attack_boost_large())
unitsBonuses.put(gc.getUnit("attack_ship"), FightStats.add(unitsBonuses.getOrDefault(gc.getUnit("attack_ship"), FightStats.zero()), FightStats.cst(0.3)));
if(off.hasRareAttackBoost())
everyoneStatsBonus = FightStats.add(everyoneStatsBonus, FightStats.terrestre(+0.1));
if(off.hasEpicAttackBoost())
everyoneStatsBonus = FightStats.add(everyoneStatsBonus, FightStats.terrestre(+0.2));
if(off.getOlympicSwordGrepolympiaSummerLevel()!=0)
everyoneStatsBonus = FightStats.add(everyoneStatsBonus, FightStats.terrestre(+0.05*off.getOlympicSwordGrepolympiaSummerLevel()));
if(off.getAresRageLevel()!=0)
everyoneStatsBonus = FightStats.add(everyoneStatsBonus, FightStats.terrestre(+0.01*off.getAresRageLevel()));
if(off.getBloodlust()!=0)
everyoneStatsBonus = FightStats.add(everyoneStatsBonus, FightStats.terrestre(0.05+0.01*Math.floorDiv(off.getBloodlust(),200)));
if(off.hasFairWind())
everyoneStatsBonus = FightStats.add(everyoneStatsBonus, FightStats.naval(0.1));
if(off.hasDesire())
everyoneStatsBonus = FightStats.add(everyoneStatsBonus, FightStats.terrestre(-0.1));
if(off.hasStrengthOfHeroes())
//XXX check if working
everyoneStatsBonus = FightStats.add(everyoneStatsBonus, FightStats.terrestre(0.1));
if(off.hasEffortOfTheHuntress())
everyoneStatsBonus = FightStats.add(everyoneStatsBonus, FightStats.ofType(FightType.DISTANCE,0.15));
if(off.hasStrategyBreach())
everyoneStatsBonus = FightStats.add(everyoneStatsBonus, FightStats.naval(-0.5));
// Units
FightStats total = FightStats.zero();
for(Unit u : gc.getUnits()) {
// total = total + ucount * ((1+bonusA+bonusB) * ustats)
total = FightStats.add(total,
FightStats.prod(off.unitCount(u),
FightStats.mul(
FightStats.add(FightStats.one(),everyoneStatsBonus,unitsBonuses.getOrDefault(u, FightStats.zero()))
, makeOffStats(u))
));
}
if(off.getAresArmyFurySpent()!=0)
// Ading aresarmy/25 spartiates
total = FightStats.add(total,
FightStats.prod(Math.floorDiv(off.getAresArmyFurySpent(), 25),
FightStats.mul(
FightStats.add(FightStats.one(),everyoneStatsBonus,unitsBonuses.getOrDefault(gc.getUnit("spartoi"), FightStats.zero()))
, makeOffStats(gc.getUnit("spartoi")))
));
// HeroStat
if(off.getHero() != null) {
total = FightStats.add(total, new FightStats(
off.getHero().getHackDef() * (1.0+0.1*off.getHeroLevel()),
off.getHero().getPierceDef() * (1.0+0.1*off.getHeroLevel()),
off.getHero().getDistanceDef() * (1.0+0.1*off.getHeroLevel()),
0.0
));
}
return total;
}
public static List<Research> relevantDefResearches(GameConfig gd) {
return List.of("divine_selection","phalanx","ram")
.stream().map(gd::getResearch).toList();
public static FightStats makeOffStats(Unit u) {
if(u instanceof TerrestrialUnit) {
TerrestrialUnit tu = (TerrestrialUnit)u;
return FightStats.ofType(tu.getAttackType(), tu.getAttack());
}else if(u instanceof NavalUnit) {
NavalUnit nu = (NavalUnit)u;
return new FightStats(0.0, 0.0, 0.0, nu.getAttack());
}
throw new UnsupportedOperationException("I don't know how to manage units of type "+u.getClass().getName());
}
public static List<Unit> relevantDefUnits(GameConfig gc) {
return gc.getUnits().stream().toList();
}
public static List<String> relevantDefCounsellors(GameConfig data) {
return List.of("priest","commander","captain");
}
public FightStats computeOffStats(DefContext off) {
//TODO computeOffStats
throw new UnsupportedOperationException("Simulator not created");
public static List<Unit> relevantOffUnits(GameConfig gc) {
return gc.getUnits().stream().toList();
}
public FightResult simulateFight(OffContext off, DefContext def) {

View File

@ -1,21 +1,22 @@
package com.bernard.greposimu.model;
import java.util.AbstractMap;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import com.bernard.greposimu.model.game.GameConfig;
import com.bernard.greposimu.model.game.units.Hero;
import com.bernard.greposimu.model.game.units.Unit;
public class DefContext {
// UNITS
Map<Unit, Integer> units;
// Units unrelated to the attacker
Map<Unit, Integer> otherUnits;
// Units owned by allies of the attacker
Map<Unit, Integer> alliedUnits;
// Units owned by the attacher
Map<Unit, Integer> selfUnits;
// HEROS
Hero hero = null;
@ -81,8 +82,16 @@ public class DefContext {
boolean nightBonus = false;
public DefContext(Map<Unit, Integer> units, Hero hero, int heroLevel, int wallLevel, boolean hasTower,
Set<String> powers, int soteriasShrinePowerLevel, int olympicTorchGrepolympiaSummerLevel, int olympicSensesGrepolympiaSummerLevel,
Set<String> researches, Set<String> counsellors, boolean nightBonus) {
this(units,Map.of(),Map.of(),hero,heroLevel,wallLevel,hasTower,powers,soteriasShrinePowerLevel,olympicTorchGrepolympiaSummerLevel,olympicSensesGrepolympiaSummerLevel,researches,counsellors,nightBonus);
}
public DefContext(Map<Unit, Integer> otherUnits, Map<Unit, Integer> alliedUnits, Map<Unit, Integer> selfUnits, Hero hero, int heroLevel, int wallLevel, boolean hasTower,
Set<String> powers, int soteriasShrinePowerLevel, int olympicTorchGrepolympiaSummerLevel, int olympicSensesGrepolympiaSummerLevel, Set<String> researches, Set<String> counsellors, boolean nightBonus) {
this.units = units;
this.otherUnits = otherUnits;
this.alliedUnits = alliedUnits;
this.selfUnits = selfUnits;
this.hero = hero;
this.heroLevel = heroLevel;
this.wallLevel = wallLevel;
@ -96,7 +105,7 @@ public class DefContext {
if(powers.contains("longterm_defense_boost"))this.longtermDefenseBoost = true;
if(powers.contains("assassins_acumen"))this.assassinsAcumen = true;
if(powers.contains("rare_defense_boost"))this.rareDefenseBoost = true;
if(powers.contains("epic_defense_boost"))this.defenseBoost = true;
if(powers.contains("epic_defense_boost"))this.epicDefenseBoost = true;
if(powers.contains("olympic_torch"))this.olympicTorchGrepolympiaSummerLevel = olympicTorchGrepolympiaSummerLevel;
if(powers.contains("olympic_senses"))this.olympicSensesGrepolympiaSummerLevel = olympicSensesGrepolympiaSummerLevel;
if(powers.contains("missions_power_4"))this.missionsPower4 = true;
@ -117,11 +126,11 @@ public class DefContext {
this.nightBonus = nightBonus;
}
public Map<Unit, Integer> getUnits() {
return units;
}
public int unitCount(Unit u) {
return Optional.ofNullable(units.getOrDefault(u,0)).orElse(0);
return
Optional.ofNullable(otherUnits.getOrDefault(u,0)).orElse(0) +
Optional.ofNullable(alliedUnits.getOrDefault(u,0)).orElse(0) +
Optional.ofNullable(selfUnits.getOrDefault(u,0)).orElse(0);
}
public Hero getHero() {
return hero;
@ -247,4 +256,11 @@ public class DefContext {
return narcissism;
}
public static final List<String> POWERS = List.of("acumen", "divine_senses", "myrmidion_attack", "trojan_defense",
"defense_boost", "defense_penalty", "longterm_defense_boost", "assassins_acumen", "rare_defense_boost",
"epic_defense_boost", "olympic_torch", "olympic_senses", "missions_power_4", "divine_battle_strategy_rare",
"divine_battle_strategy_epic", "naval_battle_strategy_rare", "naval_battle_strategy_epic",
"land_battle_strategy_rare", "land_battle_strategy_epic", "soterias_shrine", "narcissism");
public static final List<String> RESEARCHES = List.of("divine_selection","phalanx","ram");
public static final List<String> COUNSELLORS = List.of("priest","commander","captain");
}

View File

@ -2,6 +2,8 @@ package com.bernard.greposimu.model;
import java.util.Arrays;
import com.bernard.greposimu.model.game.units.FightType;
public class FightStats implements Cloneable{
public double hack;
public double pierce;
@ -15,6 +17,19 @@ public class FightStats implements Cloneable{
this.ship = ship;
}
public static final FightStats ofType(FightType type, double value) {
switch(type) {
case HACK:
return new FightStats(value, 0.0, 0.0, 0.0);
case PIERCE:
return new FightStats(0.0, value, 0.0, 0.0);
case DISTANCE:
return new FightStats(0.0, 0.0, value, 0.0);
default:
return null;
}
}
public static final FightStats zero() {
return new FightStats(0, 0, 0, 0);
}

View File

@ -1,23 +1,288 @@
package com.bernard.greposimu.model;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import com.bernard.greposimu.model.game.units.Hero;
import com.bernard.greposimu.model.game.units.Unit;
public class OffContext {
// unitID -> number of units
public Map<String, Integer> units;
public String heros;
public int herosLevel;
Map<Unit, Integer> units;
public int luck;
public int morale;
Hero hero;
int heroLevel;
public Set<String> powers;
public Set<String> researches;
int luck;
int moral;
public Set<String> counsellors;
// RESEARCHES
boolean divineSelection= false, phalanx = false, ram=false, combatExperience=false;
// COUNSELLORS
boolean commander= false;
boolean priest = false;
boolean captain = false;
// EFFECTS
// PC x2
boolean acumen = false;
// PC x4
boolean divineSenses= false;
// Attq +10%, Def -10%
boolean myrmidionAttack= false;
// Attq +10%
boolean attackBoost= false;
// Attq -10%
boolean attackPenalty= false;
// Attq +10%
boolean longtermAttackBoost = false;
//XXX implement this :/
boolean luxuriousResidence = false;
// BF attq +10%
boolean attack_ship_attack_boost_small = false;
// BF attq +20%
boolean attack_ship_attack_boost_medium = false;
// BF attq +30%
boolean attack_ship_attack_boost_large = false;
// PC +50%
boolean assassinsAcumen= false;
// Attq +10%
boolean rareAttackBoost= false;
// Attq +20%
boolean epicAttackBoost= false;
// Attq +5%*level
int olympicSwordGrepolympiaSummerLevel = 0;
// PC+10%*level
int olympicSensesGrepolympiaSummerLevel = 0;
// +50% PC
boolean missionsPower4= false;
// PC+50% (sauf BC, transport)
boolean divineBattleStrategyRare= false;
// PC+100% (sauf BC, transport)
boolean divineBattleStrategyEpic= false;
// PC+50% against naval (sauf BC,transports)
boolean navalBattleStrategyRare= false;
// PC+100% against naval (sauf BC, transport)
boolean navalBattleStrategyEpic= false;
// PC+50% against terrestres
boolean landBattleStrategyRare= false;
// PC+100% against terrestre
boolean landBattleStrategyEpic= false;
// Attq +1%*level
int aresRageLevel = 0;
// SPELLS
// Add one sparte for every 25 fury used
int aresArmyFurySpent = 0;
// Attq +5% +(1% for each 200 fury spent)
// PC + 1% for each 100 fury spent
int bloodlustFurySpent = 0;
// Attq naval +10%
boolean fairWind = false;
// Attq -10%
boolean desire = false;
// Terr + aer +10% attq
boolean strengthOfHeroes = false;
// Attq distance +15%
boolean effortOfTheHuntress = false;
boolean strategyBreach = false;
boolean allianceModifier = false;
public OffContext(Map<Unit, Integer> units, Hero hero, int heroLevel, int luck, int moral,
Set<String> powers, int olympicSwordGrepolympiaSummerLevel, int olympicSensesGrepolympiaSummerLevel,
int aresRageLevel, int aresArmyFurySpent, int bloodlustFurySpent, Set<String> researches, Set<String> counsellors,
boolean strategyBreach, boolean allianceModifier) {
this.units = units;
this.hero = hero;
this.heroLevel = heroLevel;
this.luck = luck;
this.moral = moral;
if(powers.contains("acumen"))this.acumen = true;
if(powers.contains("divine_senses"))this.divineSenses = true;
if(powers.contains("myrmidion_attack"))this.myrmidionAttack = true;
if(powers.contains("attack_boost"))this.attackBoost = true;
if(powers.contains("attack_penalty"))this.attackPenalty = true;
if(powers.contains("longterm_attack_boost"))this.longtermAttackBoost = true;
if(powers.contains("attack_ship_attack_boost_small"))this.attack_ship_attack_boost_small = true;
if(powers.contains("attack_ship_attack_boost_medium"))this.attack_ship_attack_boost_medium = true;
if(powers.contains("attack_ship_attack_boost_large"))this.attack_ship_attack_boost_large = true;
if(powers.contains("assassins_acumen"))this.assassinsAcumen = true;
if(powers.contains("rare_attack_boost"))this.rareAttackBoost = true;
if(powers.contains("epic_attack_boost"))this.epicAttackBoost = true;
if(powers.contains("olympic_sword"))this.olympicSwordGrepolympiaSummerLevel = olympicSwordGrepolympiaSummerLevel;
if(powers.contains("olympic_senses"))this.olympicSensesGrepolympiaSummerLevel = olympicSensesGrepolympiaSummerLevel;
if(powers.contains("missions_power_4"))this.missionsPower4 = true;
if(powers.contains("divine_battle_strategy_rare"))this.divineBattleStrategyRare = true;
if(powers.contains("divine_battle_strategy_epic"))this.divineBattleStrategyEpic = true;
if(powers.contains("naval_battle_strategy_rare"))this.navalBattleStrategyRare = true;
if(powers.contains("naval_battle_strategy_epic"))this.navalBattleStrategyEpic = true;
if(powers.contains("land_battle_strategy_rare"))this.landBattleStrategyRare = true;
if(powers.contains("land_battle_strategy_epic"))this.landBattleStrategyEpic = true;
if(powers.contains("ares_rage"))this.aresRageLevel = aresRageLevel;
if(powers.contains("ares_army"))this.aresArmyFurySpent = aresArmyFurySpent;
if(powers.contains("bloodlust"))this.bloodlustFurySpent = bloodlustFurySpent;
if(powers.contains("fair_wind"))this.fairWind = true;
if(powers.contains("desire"))this.desire = true;
if(powers.contains("effort_of_the_huntress"))this.effortOfTheHuntress = true;
if(powers.contains("strength_of_heroes"))this.strengthOfHeroes = true;
if(researches.contains("divine_selection"))this.divineSelection = true;
if(researches.contains("phalanx"))this.phalanx = true;
if(researches.contains("ram"))this.ram = true;
if(researches.contains("combat_experience"))this.combatExperience = true;
if(counsellors.contains("commander"))this.commander = true;
if(counsellors.contains("priest"))this.priest = true;
if(counsellors.contains("captain"))this.captain = true;
this.strategyBreach = strategyBreach;
this.allianceModifier = allianceModifier;
}
public int unitCount(Unit u) {
return Optional.ofNullable(units.getOrDefault(u,0)).orElse(0);
}
public Map<Unit, Integer> getUnits() {
return units;
}
public Hero getHero() {
return hero;
}
public int getHeroLevel() {
return heroLevel;
}
public int getLuck() {
return luck;
}
public int getMorale() {
return moral;
}
public boolean hasDivineSelection() {
return divineSelection;
}
public boolean hasPhalanx() {
return phalanx;
}
public boolean hasRam() {
return ram;
}
public boolean hasCombatExperience() {
return combatExperience;
}
public boolean hasCommander() {
return commander;
}
public boolean hasPriest() {
return priest;
}
public boolean hasCaptain() {
return captain;
}
public boolean hasAcumen() {
return acumen;
}
public boolean hasDivineSenses() {
return divineSenses;
}
public boolean hasMyrmidionAttack() {
return myrmidionAttack;
}
public boolean hasAttackBoost() {
return attackBoost;
}
public boolean hasAttackPenalty() {
return attackPenalty;
}
public boolean hasLongtermAttackBoost() {
return longtermAttackBoost;
}
public boolean hasLuxurious_residence() {
return luxuriousResidence;
}
public boolean hasAttack_ship_attack_boost_small() {
return attack_ship_attack_boost_small;
}
public boolean hasAttack_ship_attack_boost_medium() {
return attack_ship_attack_boost_medium;
}
public boolean hasAttack_ship_attack_boost_large() {
return attack_ship_attack_boost_large;
}
public boolean hasAssassinsAcumen() {
return assassinsAcumen;
}
public boolean hasRareAttackBoost() {
return rareAttackBoost;
}
public boolean hasEpicAttackBoost() {
return epicAttackBoost;
}
public int getOlympicSwordGrepolympiaSummerLevel() {
return olympicSwordGrepolympiaSummerLevel;
}
public int getOlympicSensesGrepolympiaSummerLevel() {
return olympicSensesGrepolympiaSummerLevel;
}
public boolean hasMhassionsPower4() {
return missionsPower4;
}
public boolean hasDivineBattleStrategyRare() {
return divineBattleStrategyRare;
}
public boolean hasDivineBattleStrategyEpic() {
return divineBattleStrategyEpic;
}
public boolean hasNavalBattleStrategyRare() {
return navalBattleStrategyRare;
}
public boolean hasNavalBattleStrategyEpic() {
return navalBattleStrategyEpic;
}
public boolean hasLandBattleStrategyRare() {
return landBattleStrategyRare;
}
public boolean hasLandBattleStrategyEpic() {
return landBattleStrategyEpic;
}
public int getAresRageLevel() {
return aresRageLevel;
}
public int getAresArmyFurySpent() {
return aresArmyFurySpent;
}
public int getBloodlust() {
return bloodlustFurySpent;
}
public boolean hasFairWind() {
return fairWind;
}
public boolean hasDesire() {
return desire;
}
public boolean hasStrengthOfHeroes() {
return strengthOfHeroes;
}
public boolean hasEffortOfTheHuntress() {
return effortOfTheHuntress;
}
public boolean hasStrategyBreach() {
return strategyBreach;
}
public boolean hasAllianceModifier() {
return allianceModifier;
}
public static final List<String> POWERS = List.of("acumen", "divine_senses", "myrmidion_attack", "attack_boost",
"attack_penalty", "longterm_attack_boost", "attack_ship_attack_boost_small",
"attack_ship_attack_boost_medium", "attack_ship_attack_boost_large", "assassins_acumen",
"rare_attack_boost", "epic_attack_boost", "olympic_sword", "olympic_senses", "missions_power_4",
"divine_battle_strategy_rare", "divine_battle_strategy_epic", "naval_battle_strategy_rare",
"naval_battle_strategy_epic", "land_battle_strategy_rare", "land_battle_strategy_epic", "ares_rage",
"ares_army", "bloodlust", "fair_wind", "desire", "effort_of_the_huntress", "strength_of_heroes");
public static final List<String> RESEARCHES = List.of("divine_selection","phalanx","ram","combat_experience");
public static final List<String> COUNSELLORS = List.of("priest","commander","captain");
public boolean strategy_breach;
public boolean alliance_modifier;
}

View File

@ -31,6 +31,10 @@ public class GameConfig {
this.researches = researches;
this.powers = powers;
}
public Set<God> getGods() {
return gods;
}
public Set<Unit> getUnits() {
return Collections.unmodifiableSet(this.units);

View File

@ -1,6 +1,8 @@
package com.bernard.greposimu.model.game;
public class God implements Identified{
import java.util.Objects;
public class God implements Identified,Comparable<God>{
String id;
String name;
@ -18,5 +20,33 @@ public class God implements Identified{
public String getName() {
return name;
}
@Override
public int compareTo(God o) {
return this.getId().compareTo(o.getId());
}
@Override
public int hashCode() {
return Objects.hash(id);
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
God other = (God) obj;
return Objects.equals(id, other.id);
}
@Override
public String toString() {
return "God [id=" + id + ", name=" + name + "]";
}
}

View File

@ -1,13 +1,12 @@
package com.bernard.greposimu.model.game.units;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import com.bernard.greposimu.model.game.God;
import com.bernard.greposimu.model.game.Identified;
import com.bernard.greposimu.model.game.Resources;
public class Hero extends TerrestrialUnit{
public class Hero extends TerrestrialUnit implements Comparable<Hero>{
// Zero population
// Non mythological
@ -76,6 +75,34 @@ public class Hero extends TerrestrialUnit{
return powerValuePerLevel;
}
@Override
public int compareTo(Hero o) {
return this.getId().compareTo(o.getId());
}
@Override
public int hashCode() {
return Objects.hash(id);
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Hero other = (Hero) obj;
return Objects.equals(id, other.id);
}
@Override
public String toString() {
return "Hero [category=" + category + ", cost=" + cost + ", shortDescription=" + shortDescription
+ ", powerBaseValue=" + powerBaseValue + ", powerValuePerLevel=" + powerValuePerLevel + "]";
}
}

View File

@ -0,0 +1 @@
missions_power_4.missions_dionysia.disabled.png

View File

@ -0,0 +1 @@
missions_power_4.missions_dionysia.hover.png

View File

@ -0,0 +1 @@
missions_power_4.missions_dionysia.png

View File

@ -85,6 +85,17 @@ span.fixed50px {
border: 0px;
margin: auto;
}
.container {
display: inline-block;
}
.columns {
display: flex;
}
.formcol {
flex: 50%;
}
</style>
@ -92,95 +103,147 @@ span.fixed50px {
<body>
<form action="#" th:object="${ctx}" method="post" id="simuform" >
<fieldset id="herosFields">
<legend>Héros</legend>
<select name="heros" id="heros" th:field="*{hero}">
<option value="none">Aucun</option>
<option th:each="hero : ${heroes}" th:value="${hero.id}"><span th:text="${hero.name}"/></option>
</select>
<label for="heroLevelSlider">Niveau du héros: (<span id="heroLevelSliderInfo" class="fixed50px"></span>)</label>
<input type="range" min="1" max="20" value="1" class="slider" id="heroLevelSlider" th:field="*{heroLevel}">
<script>
var heroSlider = document.getElementById("heroLevelSlider");
var heroOutput = document.getElementById("heroLevelSliderInfo");
heroOutput.innerHTML = "lvl. "+heroSlider.value;
// Update the current slider value (each time you drag the slider handle)
heroSlider.oninput = function() {
heroOutput.innerHTML = "lvl. "+this.value;
}
</script>
</fieldset>
<fieldset>
<legend>Unités</legend>
<table cellspacing="0" cellpadding="0">
<tr>
<td th:each="unite : ${defUnits}" class="squareContainer">
<div class="columns">
<fieldset class="formcol">
<legend>Attaque</legend>
<fieldset id="herosFields">
<legend>Héros</legend>
<select name="offHeros" id="offHeros" th:field="*{offHero}">
<option value="none">Aucun</option>
<option th:each="hero : ${heroes}" th:value="${hero.id}"><span th:text="${hero.name}"/></option>
</select>
<label for="offHeroLevelSlider">Niveau du héros: (<span id="offHeroLevelSliderInfo" class="fixed50px"></span>)</label>
<input type="range" min="1" max="20" value="1" class="slider" id="offHeroLevelSlider" th:field="*{offHeroLevel}">
<script>
var offHeroSlider = document.getElementById("offHeroLevelSlider");
var offHeroOutput = document.getElementById("offHeroLevelSliderInfo");
offHeroOutput.innerHTML = "lvl. "+offHeroSlider.value;
// Update the current slider value (each time you drag the slider handle)
offHeroSlider.oninput = function() {
offHeroOutput.innerHTML = "lvl. "+this.value;
}
</script>
</fieldset>
<fieldset id="unitesFS">
<legend>Unités</legend>
<div th:each="unite : ${defUnits}" class="container">
<img class="squareImage" th:for="'unite-'+${unite.id}" th:src="@{/images/units/{uname}.png(uname=${unite.id})}"/>
<input type="number" class="squareNumber" th:id="'unite-'+${unite.id}" th:field="*{units[__${unite.id}__]}"/>
</td>
</tr>
</table>
</fieldset>
<fieldset>
<legend>Batiments</legend>
<label for="hasTower">Tour :</label>
<input type="checkbox" id="hasTower" th:field="*{hasTower}"/><br/>
<label for="wallLevelSlider">Niveau des remparts: (<span id="wallLevelSliderInfo" class="fixed50px"></span>)</label>
<input type="range" min="0" max="25" value="1" class="slider" id="wallLevelSlider" th:field="*{wallLevel}">
<script>
var wallSlider = document.getElementById("wallLevelSlider");
var wallOutput = document.getElementById("wallLevelSliderInfo");
wallOutput.innerHTML = "lvl. "+wallSlider.value;
// Update the current slider value (each time you drag the slider handle)
wallSlider.oninput = function() {
wallOutput.innerHTML = "lvl. "+this.value;
}
</script>
</fieldset>
<fieldset>
<legend>Pouvoirs</legend>
<table cellspacing="0" cellpadding="0">
<tr>
<td th:each="power : ${defPowers}" class="squareContainer">
<br/>
<input type="number" class="squareNumber" th:id="'unite-'+${unite.id}" th:field="*{offUnits[__${unite.id}__]}"/>
</div>
</fieldset>
<fieldset>
<legend>Pouvoirs</legend>
<div th:each="power : ${offPowers}" class="container">
<img class="squareImage" th:for="'power-'+${power}" th:src="@{/images/powers/{pname}.png(pname=${power})}"/>
<input type="checkbox" th:id="'power-'+${power}" th:field="*{powersAsMap[__${power}__]}" th:value="true"/>
</td>
</tr>
</table>
</fieldset>
<fieldset>
<legend>Recherches</legend>
<table cellspacing="0" cellpadding="0">
<tr>
<td th:each="research : ${defResearches}" class="squareContainer">
<img class="squareImage" th:for="'research-'+${research.id}" th:src="@{/images/researches/{rname}.png(rname=${research.id})}"/>
<input type="checkbox" th:id="'research-'+${research.id}" th:field="*{researchesAsMap[__${research.id}__]}" th:value="true"/>
</td>
</tr>
</table>
</fieldset>
<fieldset>
<legend>Conseillers</legend>
<table cellspacing="0" cellpadding="0">
<tr>
<td th:each="counsellor : ${defCounsellors}" class="squareContainer">
<input type="checkbox" th:id="'power-'+${power}" th:field="*{defPowersAsMap[__${power}__]}" th:value="true"/>
</div>
</fieldset>
<fieldset>
<legend>Recherches</legend>
<div th:each="research : ${offResearches}" class="container">
<img class="squareImage" th:for="'research-'+${research}" th:src="@{/images/researches/{rname}.png(rname=${research})}"/>
<input type="checkbox" th:id="'research-'+${research}" th:field="*{defResearchesAsMap[__${research}__]}" th:value="true"/>
</div>
</fieldset>
<fieldset>
<legend>Conseillers</legend>
<div th:each="counsellor : ${offCounsellors}" class="container">
<img class="squareImage" th:for="'counsellor-'+${counsellor}" th:src="@{/images/counsellors/{cname}.png(cname=${counsellor})}"/>
<input type="checkbox" th:id="'counsellor-'+${counsellor}" th:field="*{counsellorsAsMap[__${counsellor}__]}" th:value="true"/>
</td>
</tr>
</table>
</fieldset>
<fieldset>
<legend>Bonus de jeu</legend>
<label for="nightBonus">Bonus de nuit :</label>
<input type="checkbox" id="nightBonus" th:field="*{nightBonus}" th:value="true"/><br/>
</fieldset>
<input type="checkbox" th:id="'counsellor-'+${counsellor}" th:field="*{defCounsellorsAsMap[__${counsellor}__]}" th:value="true"/>
</div>
</fieldset>
<fieldset>
<legend>Paramètres</legend>
<label for="moralInput">Moral:</label>
<input type="number" min="0" max="100" value="1" id="moralInput" th:field="*{moral}">
<label for="luckInput">Chance:</label>
<input type="number" min="-20" max="20" value="1" id="luckInput" th:field="*{luck}">
<label for="breachInput">Percée:</label>
<input type="checkbox" id="breachInput" th:field="*{strategyBreach}">
<label for="allianceInput">Alliance:</label>
<input type="checkbox" id="allianceInput" th:field="*{allianceModifier}">
</fieldset>
</fieldset>
<fieldset class="formcol">
<legend>Défense</legend>
<fieldset id="herosFields">
<legend>Héros</legend>
<select name="defHeros" id="defHeros" th:field="*{defHero}">
<option value="none">Aucun</option>
<option th:each="hero : ${heroes}" th:value="${hero.id}"><span th:text="${hero.name}"/></option>
</select>
<label for="heroLevelSlider">Niveau du héros: (<span id="heroLevelSliderInfo" class="fixed50px"></span>)</label>
<input type="range" min="1" max="20" value="1" class="slider" id="heroLevelSlider" th:field="*{defHeroLevel}">
<script>
var heroSlider = document.getElementById("heroLevelSlider");
var heroOutput = document.getElementById("heroLevelSliderInfo");
heroOutput.innerHTML = "lvl. "+heroSlider.value;
// Update the current slider value (each time you drag the slider handle)
heroSlider.oninput = function() {
heroOutput.innerHTML = "lvl. "+this.value;
}
</script>
</fieldset>
<fieldset id="unitesFS">
<legend>Unités</legend>
<div th:each="unite : ${defUnits}" class="container">
<img class="squareImage" th:for="'unite-'+${unite.id}" th:src="@{/images/units/{uname}.png(uname=${unite.id})}"/>
<br/>
<input type="number" class="squareNumber" th:id="'unite-'+${unite.id}" th:field="*{defUnits[__${unite.id}__]}"/>
</div>
</fieldset>
<fieldset>
<legend>Batiments</legend>
<label for="hasTower">Tour :</label>
<input type="checkbox" id="hasTower" th:field="*{hasTower}"/><br/>
<label for="wallLevelSlider">Niveau des remparts: (<span id="wallLevelSliderInfo" class="fixed50px"></span>)</label>
<input type="range" min="0" max="25" value="1" class="slider" id="wallLevelSlider" th:field="*{wallLevel}">
<script>
var wallSlider = document.getElementById("wallLevelSlider");
var wallOutput = document.getElementById("wallLevelSliderInfo");
wallOutput.innerHTML = "lvl. "+wallSlider.value;
// Update the current slider value (each time you drag the slider handle)
wallSlider.oninput = function() {
wallOutput.innerHTML = "lvl. "+this.value;
}
</script>
</fieldset>
<fieldset>
<legend>Pouvoirs</legend>
<div th:each="power : ${defPowers}" class="container">
<img class="squareImage" th:for="'power-'+${power}" th:src="@{/images/powers/{pname}.png(pname=${power})}"/>
<input type="checkbox" th:id="'power-'+${power}" th:field="*{defPowersAsMap[__${power}__]}" th:value="true"/>
</div>
</fieldset>
<fieldset>
<legend>Recherches</legend>
<div th:each="research : ${defResearches}" class="container">
<img class="squareImage" th:for="'research-'+${research}" th:src="@{/images/researches/{rname}.png(rname=${research})}"/>
<input type="checkbox" th:id="'research-'+${research}" th:field="*{defResearchesAsMap[__${research}__]}" th:value="true"/>
</div>
</fieldset>
<fieldset>
<legend>Conseillers</legend>
<div th:each="counsellor : ${defCounsellors}" class="container">
<img class="squareImage" th:for="'counsellor-'+${counsellor}" th:src="@{/images/counsellors/{cname}.png(cname=${counsellor})}"/>
<input type="checkbox" th:id="'counsellor-'+${counsellor}" th:field="*{defCounsellorsAsMap[__${counsellor}__]}" th:value="true"/>
</div>
</fieldset>
<fieldset>
<legend>Bonus de jeu</legend>
<label for="nightBonus">Bonus de nuit :</label>
<input type="checkbox" id="nightBonus" th:field="*{nightBonus}" th:value="true"/><br/>
</fieldset>
</fieldset>
</div>
<button type="button" id="compute">Calculer</button>
</form>