From 4484dc9476650a18be5401c5a70c717988f1f1b0 Mon Sep 17 00:00:00 2001 From: Stabar-ti Date: Fri, 10 Jul 2026 23:31:02 +0200 Subject: [PATCH 1/3] fowvisionToken --- .../commands/fow/AddVisionToken.java | 71 ++++++++++++++++++ .../interactions/commands/fow/FOWCommand.java | 2 + .../commands/fow/RemoveVisionToken.java | 50 ++++++++++++ src/main/java/ti4/game/Tile.java | 19 +++++ src/main/java/ti4/helpers/Constants.java | 4 + src/main/java/ti4/helpers/FoWHelper.java | 26 +++++++ src/main/java/ti4/model/TileModel.java | 6 ++ src/main/java/ti4/model/TokenModel.java | 1 + src/main/resources/data/tokens.properties | 1 + src/main/resources/data/tokens/other.json | 12 +++ src/main/resources/tokens/token_fowvision.png | Bin 0 -> 14628 bytes 11 files changed, 192 insertions(+) create mode 100644 src/main/java/ti4/discord/interactions/commands/fow/AddVisionToken.java create mode 100644 src/main/java/ti4/discord/interactions/commands/fow/RemoveVisionToken.java create mode 100644 src/main/resources/tokens/token_fowvision.png diff --git a/src/main/java/ti4/discord/interactions/commands/fow/AddVisionToken.java b/src/main/java/ti4/discord/interactions/commands/fow/AddVisionToken.java new file mode 100644 index 0000000000..173075ed73 --- /dev/null +++ b/src/main/java/ti4/discord/interactions/commands/fow/AddVisionToken.java @@ -0,0 +1,71 @@ +package ti4.discord.interactions.commands.fow; + +import java.util.List; +import java.util.stream.Collectors; +import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; +import net.dv8tion.jda.api.interactions.commands.OptionType; +import net.dv8tion.jda.api.interactions.commands.build.OptionData; +import ti4.discord.interactions.commands.CommandHelper; +import ti4.discord.interactions.commands.GameStateSubcommand; +import ti4.game.Game; +import ti4.game.Player; +import ti4.game.Tile; +import ti4.helpers.Constants; +import ti4.helpers.Helper; +import ti4.image.PositionMapper; +import ti4.message.MessageHelper; + +class AddVisionToken extends GameStateSubcommand { + + public AddVisionToken() { + super( + Constants.ADD_VISION_TOKEN, + "Place a fog-vision token that reveals a system to some or all players.", + true, + true); + addOptions(new OptionData( + OptionType.STRING, Constants.POSITION, "Tile position(s) on map, comma separated", true)); + addOptions(new OptionData( + OptionType.STRING, + Constants.TARGET_FACTION_OR_COLOR, + "Faction/Color(s) to reveal to - leave empty to reveal to everyone") + .setAutoComplete(true)); + } + + @Override + public void execute(SlashCommandInteractionEvent event) { + Game game = getGame(); + List positions = + Helper.getListFromCSV(event.getOption(Constants.POSITION).getAsString()); + + // Empty target list => reveal to everyone (stored as a blank grant, read as ALL by FoWHelper). + List targets = CommandHelper.getTargetPlayersFromOption(game, event); + String grant = targets.stream().map(Player::getColor).collect(Collectors.joining(",")); + String targetDesc = targets.isEmpty() + ? "everyone" + : targets.stream().map(Player::getColor).collect(Collectors.joining(", ")); + + StringBuilder sb = new StringBuilder(); + for (String position : positions) { + if (!PositionMapper.isTilePositionValid(position)) { + MessageHelper.replyToMessage(event, "Tile position '" + position + "' is invalid"); + continue; + } + Tile tile = game.getTileByPosition(position); + if (tile == null) { + MessageHelper.replyToMessage(event, "No tile found at position '" + position + "'"); + continue; + } + tile.addToken(Constants.TOKEN_FOWVISION_PNG, Constants.SPACE); + game.setStoredValue(Constants.FOW_VISION_GRANT_PREFIX + position, grant); + sb.append("Placed a fog-vision token on ") + .append(position) + .append(" - revealed to ") + .append(targetDesc) + .append('\n'); + } + if (!sb.isEmpty()) { + MessageHelper.sendMessageToChannel(event.getChannel(), sb.toString()); + } + } +} diff --git a/src/main/java/ti4/discord/interactions/commands/fow/FOWCommand.java b/src/main/java/ti4/discord/interactions/commands/fow/FOWCommand.java index 534b6d5b1c..34ea62d6ff 100644 --- a/src/main/java/ti4/discord/interactions/commands/fow/FOWCommand.java +++ b/src/main/java/ti4/discord/interactions/commands/fow/FOWCommand.java @@ -12,6 +12,8 @@ public class FOWCommand implements ParentCommand { private final Map subcommands = Stream.of( new AddFogTile(), new RemoveFogTile(), + new AddVisionToken(), + new RemoveVisionToken(), new CheckChannels(), new PingActivePlayer(), new PingSystem(), diff --git a/src/main/java/ti4/discord/interactions/commands/fow/RemoveVisionToken.java b/src/main/java/ti4/discord/interactions/commands/fow/RemoveVisionToken.java new file mode 100644 index 0000000000..7c10428462 --- /dev/null +++ b/src/main/java/ti4/discord/interactions/commands/fow/RemoveVisionToken.java @@ -0,0 +1,50 @@ +package ti4.discord.interactions.commands.fow; + +import java.util.List; +import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; +import net.dv8tion.jda.api.interactions.commands.OptionType; +import net.dv8tion.jda.api.interactions.commands.build.OptionData; +import ti4.discord.interactions.commands.GameStateSubcommand; +import ti4.game.Game; +import ti4.game.Tile; +import ti4.helpers.Constants; +import ti4.helpers.Helper; +import ti4.image.PositionMapper; +import ti4.message.MessageHelper; + +class RemoveVisionToken extends GameStateSubcommand { + + public RemoveVisionToken() { + super(Constants.REMOVE_VISION_TOKEN, "Remove a fog-vision token from a system.", true, true); + addOptions(new OptionData( + OptionType.STRING, Constants.POSITION, "Tile position(s) on map, comma separated", true)); + } + + @Override + public void execute(SlashCommandInteractionEvent event) { + Game game = getGame(); + List positions = + Helper.getListFromCSV(event.getOption(Constants.POSITION).getAsString()); + + StringBuilder sb = new StringBuilder(); + for (String position : positions) { + if (!PositionMapper.isTilePositionValid(position)) { + MessageHelper.replyToMessage(event, "Tile position '" + position + "' is invalid"); + continue; + } + Tile tile = game.getTileByPosition(position); + if (tile == null) { + MessageHelper.replyToMessage(event, "No tile found at position '" + position + "'"); + continue; + } + boolean removed = tile.removeToken(Constants.TOKEN_FOWVISION_PNG, Constants.SPACE); + game.removeStoredValue(Constants.FOW_VISION_GRANT_PREFIX + position); + sb.append(removed ? "Removed the fog-vision token from " : "No fog-vision token was on ") + .append(position) + .append('\n'); + } + if (!sb.isEmpty()) { + MessageHelper.sendMessageToChannel(event.getChannel(), sb.toString()); + } + } +} diff --git a/src/main/java/ti4/game/Tile.java b/src/main/java/ti4/game/Tile.java index 7189188152..84b3febb02 100644 --- a/src/main/java/ti4/game/Tile.java +++ b/src/main/java/ti4/game/Tile.java @@ -39,6 +39,7 @@ import ti4.logging.LogOrigin; import ti4.model.TileModel; import ti4.model.TileModel.TileBack; +import ti4.model.TokenModel; import ti4.model.UnitModel; import ti4.model.WormholeModel; import ti4.service.emoji.TI4Emoji; @@ -677,6 +678,24 @@ public boolean hasCabalSpaceDockOrGravRiftToken(Game game) { return false; } + /** + * True if this tile carries a fog-vision token (a token whose {@link TokenModel} has + * {@code isFowVision}). Deliberately independent of {@link #isAnomaly()} — a fog-vision + * marker is not an anomaly and must not gain anomaly movement/combat/ability behaviour. + */ + @JsonIgnore + public boolean hasFowVisionToken() { + for (UnitHolder uh : unitHolders.values()) { + for (String token : uh.getTokenList()) { + TokenModel model = Mapper.getToken(token); + if (model != null && Boolean.TRUE.equals(model.getIsFowVision())) { + return true; + } + } + } + return false; + } + @JsonIgnore public boolean isAnomaly() { return isAnomaly(null, null); diff --git a/src/main/java/ti4/helpers/Constants.java b/src/main/java/ti4/helpers/Constants.java index 0dcb12669c..bcd96f99d1 100644 --- a/src/main/java/ti4/helpers/Constants.java +++ b/src/main/java/ti4/helpers/Constants.java @@ -366,6 +366,10 @@ public static String privateStaticVoidPing() { public static final String SEARCH_WARRANT = "search_warrant"; public static final String ADD_CUSTOM_ADJACENT_TILES = "add_custom_adjacent_tiles"; public static final String ADD_FOG_TILE = "add_fog_tile"; + public static final String ADD_VISION_TOKEN = "add_vision_token"; + public static final String REMOVE_VISION_TOKEN = "remove_vision_token"; + public static final String TOKEN_FOWVISION_PNG = "token_fowvision.png"; + public static final String FOW_VISION_GRANT_PREFIX = "fowVisionGrant"; public static final String ADD_ADJACENCY_OVERRIDE = "add_adjacency_override"; public static final String REMOVE_ADJACENCY_OVERRIDE = "remove_adjacency_override"; public static final String REMOVE_ALL_ADJACENCY_OVERRIDES = "remove_all_adjacency_overrides"; diff --git a/src/main/java/ti4/helpers/FoWHelper.java b/src/main/java/ti4/helpers/FoWHelper.java index a4d411e00b..b9ac3a5697 100644 --- a/src/main/java/ti4/helpers/FoWHelper.java +++ b/src/main/java/ti4/helpers/FoWHelper.java @@ -151,6 +151,8 @@ private static void initializeFog(Game game, @NotNull Player player, boolean for tilePositionsToShow.addAll(adjacentTiles); } + addFowVisionTiles(game, player, tilePositionsToShow); + String playerSweep = Mapper.getSweepID(player.getColor()); for (Tile tile : game.getTileMap().values()) { if (tile.hasCC(playerSweep)) { @@ -184,9 +186,33 @@ public static Set getTilePositionsToShow(Game game, @NotNull Player play tilePositionsToShow.add(tile.getPosition()); } } + + addFowVisionTiles(game, player, tilePositionsToShow); return tilePositionsToShow; } + /** + * Fog-vision tokens/tiles reveal a system independent of unit presence. A tile is added to the + * player's visible set if it is an intrinsic fog-vision tile (revealed to everyone) or carries a + * fog-vision token whose recipient list (stored per position; blank = everyone) includes the + * player. Only the tile's own position is added — this grants no adjacency or movement. + */ + private static void addFowVisionTiles(Game game, @NotNull Player player, Set tilePositionsToShow) { + for (Tile tile : game.getTileMap().values()) { + String pos = tile.getPosition(); + if (tile.getTileModel().isFowVision()) { + tilePositionsToShow.add(pos); // intrinsic vision tile: everyone sees it + continue; + } + if (!tile.hasFowVisionToken()) continue; // token presence is the master gate + String grant = game.getStoredValue(Constants.FOW_VISION_GRANT_PREFIX + pos); + boolean all = grant == null || grant.isBlank(); + if (all || List.of(grant.split(",")).contains(player.getColor())) { + tilePositionsToShow.add(pos); + } + } + } + public static void updateFog(Game game, Player player) { if (player != null) initializeFog(game, player, true); } diff --git a/src/main/java/ti4/model/TileModel.java b/src/main/java/ti4/model/TileModel.java index 9f4a40eb3c..a57d7a77da 100644 --- a/src/main/java/ti4/model/TileModel.java +++ b/src/main/java/ti4/model/TileModel.java @@ -60,6 +60,7 @@ public String toValue() { private @JsonProperty("isNebula") boolean nebula; private @JsonProperty("isGravityRift") boolean gravityRift; private @JsonProperty("isScar") boolean isScar; + private @JsonProperty("isFowVision") boolean fowVision; private String imageURL; private ComponentSource source; private TileBack tileBack = TileBack.BLACK; @@ -186,6 +187,11 @@ public boolean isScar() { return isScar; } + @JsonIgnore + public boolean isFowVision() { + return fowVision; + } + @JsonIgnore public boolean isAnomaly() { return asteroidField || gravityRift || nebula || supernova || isScar; diff --git a/src/main/java/ti4/model/TokenModel.java b/src/main/java/ti4/model/TokenModel.java index 3c1c271cdd..f2ae6cd183 100644 --- a/src/main/java/ti4/model/TokenModel.java +++ b/src/main/java/ti4/model/TokenModel.java @@ -24,6 +24,7 @@ public class TokenModel implements ModelInterface, EmbeddableModel { private Boolean isNova; private Boolean isScar; + private Boolean isFowVision; private List aliasList; private List wormholes; private ComponentSource source; diff --git a/src/main/resources/data/tokens.properties b/src/main/resources/data/tokens.properties index 29c436c311..27bbb610e1 100644 --- a/src/main/resources/data/tokens.properties +++ b/src/main/resources/data/tokens.properties @@ -7,6 +7,7 @@ custodianvp=token_custodianvp.png custc1=token_custc1.png custvpc1=token_custvpc1.png dmz_large=token_dmz_large.png +fowvision=token_fowvision.png frontier=token_frontier.png gamma=token_gamma.png gravityrift=token_gravityrift.png diff --git a/src/main/resources/data/tokens/other.json b/src/main/resources/data/tokens/other.json index b70d4fdc09..97ee8739b3 100644 --- a/src/main/resources/data/tokens/other.json +++ b/src/main/resources/data/tokens/other.json @@ -13,6 +13,18 @@ "imagePath": "token_big_obelisk.png", "source": "other" }, + { + "id": "fowvision", + "imagePath": "token_fowvision.png", + "spaceOrPlanet": "space", + "isFowVision": true, + "aliasList": [ + "fowvision", + "token_fowvision", + "vision" + ], + "source": "other" + }, { "id": "consulate", "imagePath": "token_consulate.png", diff --git a/src/main/resources/tokens/token_fowvision.png b/src/main/resources/tokens/token_fowvision.png new file mode 100644 index 0000000000000000000000000000000000000000..4090b7a6eadb58cf5c2d0e557c95809488aad6b9 GIT binary patch literal 14628 zcmY+r1yG$o&^L+~cQ5V^2X}Yp(BgJ*cXxMpcPYiCP>L2gxEFVKcmMdm_nq(F`^@Bt z&SvwQWV6X`qEwY-P!I_bAs`@7YA1+}J~J>D zVoG8V5Df`PuOQgZJc6UFwhIIV3&no{^5^zi@@FBcnUjJZ{lFb0y1?lHe>O$bNrtMf+C)dAQKxiS2ANW3oCme+KcvXS~4qBAzDo? zB{n5T2{TJ8S#M`Eb#LWwCf+tCe5SM_!YGJ>p8TH_?95z2WS(}m_AdOMLKOc)nEx~V zUok6%u%NT4IX_TR`hQz|=7cCLU0ogdSy??iJXkz9SR9-!SOI)|e5`EjtnBQ}pDmbO zyzE^;p3L?xl>bXW(#*xg*~-z?%E6xOKY}1*2RBzC3O6?^Q+{)hIhV1iDHpRbh{Kc_ z05AnHgG@O9%%&V1raW8#UUqYUF~$FO?`mcK|MqY1^1m>A0>b)V4J&|!?LU_POY^HZ zTbX@w@n4t10KxyM|NoTg>i_Cv8{a^~v(_{5GWH4_N} zf(KbnQtX?j!9a&su9=kA$4BkYru9yTi!}}*p%-N2*dcg^95Og61{O=zon$%|Dgc-& zTNaZX(SDdG3P2Z4m}0paCGfitGWEMr;L=0Z`O%yE#@*@n4H56mv1ZZBjK{Z5!@BdX zgIs?7gQwMl+@Cl9)Ttjo5Nht&$wOg`O~x3*&`EW5_9bHhlUU+JydZ%TV0?kz@rRJ{ zQM@6Z&dCPUJjseJDRIH0h>i`>;MP~e4;F{^=QwbaOeg?XM++VV%`ILkIc>5(A#j1m zs2NYD8Yo8J*JHFn3e88B3Va_=W+>p+IZ=lwO~4e zPYeTC#HNa>au3`byvKPRgHoM0W==dAn&&ehS|NxFDa(kSzE6A@Vol+5K)`@A9gxT; zpdU|_3bw%AFSN1Y4Oa-gbkg!)N@Fl^;er|wwc;g+S{u&nLoeZX2<3n>h)AJ5nb;uY z5$8)J{cq7Fyc#4W2h)F)mb1(k7CZNc{!$v7?!k*kR@i0mfupw$qPjcoC%taIXPhPj z?X`5yhdq~Z@`j=|rT`D!mfqISq<$L*O+0I3Up}s(wV*-)mY||E33E}KsOD(8!9Mtq zdAPyx9UYRIQaNFs*k--uGlXqrloB!7ATg6r@`DVz@6SK5y@%RxN~t4*t%4^wK}{ja z1PgjEECx?2tQ%i^mF2zO)V)&XpI-`I{#mfR?T9>5#^PGq5b*dv@4Vihusjo!_lvMH ze@MOtU?vWXIKbsY-})YdRqp-X)p=Yj4@mUNL>bVzv(${d;9KFvF{uGF*Yvw1rr^XV z_wez0YbyG4Fh8v!<9 z8Atf@pQkgPj%2*f?JzV2`bkfAj5vUy*Q|}f!^nmIjxIbCrXBBDbxhma1gl3N3B6hU zZEo4CJd|~ff~Xw4({2-wU$)BIY~0x7_;h|j1sOFrcdz@f9-c{ip1ZNHC1b4N(5Ks!)o^^d0QlSH7S$B z*V7mq;v@hEpqFIn&$ah&K-Q4Q?s0rnuK1AHR=@x$o3 z-(8{>P=-%h;6$sC>F@@PxUgY*ng^2d60Y7Bgs*Na0ar+#f6FFU;Bw!7lj)q`t=`NE z1YFK%eBfdv$Ggj3MmEbN6Rdv(d0bD3c=xrIrLt=O9E}nz3e-92KriNjFNQ8?d$L*e zy~)5Aa79-@bk0|rnBM&^*|wR)>Q2s#q=1;~Od6(#vq7(Vcv5Wh8*j3~@d};fcN!)` z(`9Mtq9&OvmZY_oP-E;q6aw}ds|t)WcSSqrcuJXLMdgL8Tz=6iu2CBUH-e1r+c4b(w<>G#NWAymFRdHb z^9zPjnN~g2(6Z_rKBNv_qgCtlWv>xsghKI~8M<>Du8fMXB;|(K{0XeVUW%VE3{4b^`5}QV{e7Sep0)G zOxyPkV+27oW@>qz-(7`z!&?epvS_f|5pU5R8l7kP|HybxTfsLJ{#zS2+F<+6nsBoG zJ04ozD7N33D!x%w9&(fD1&J(_ERr*7N;XN1W*P{!$pDF8$_)=G{S~>8BMuzMooPY= zr_&VRot~E<46dzNspNr9R?dm)o3-V8$vRzX&s#d;JHByar#uR!uX&=8L(WR?SBv08 z%FOi(H3k@_1G(c3cM;%m2VQza0?zMkx*i=`n{Kv9w+3D=!^B63e$K?`GZZ6Ichs`j z;{Ew)Hli;?r;yyrIcJ>omy5Dm$d1`;Ma+~w-DJz#%y*Rff+@Nz6^cG-@VQN#={>u^ zfo&txqG+yESYZ5i+KQh>{3(%By_oGU%kyuqudEGp8I0e2o{y?-foBX`Pb^RkmT_=w zmL{2YEnc@u*rUrBlGKP&LZpiwIGK%)w+-(Oh!a$y7?h zFAk(T(VCsNbHEW+=4n)jqF?@l7Q!wsBCD4{b<@>C!8i_mn%N|*DpE_EI7v^4jX-ei zeUV+vSiff&#Ym&7=kDq501on;NJrpIxkV7irs+|}T%=u>N3>TVK!pS@0=s`N_liuJzOCMWJ zP{wE|PSRc(3nX#$LiIb-Lh@yQE$D%r0_g z#-IOi%_El)_%@`yr1TV$XX)Fva0FjoXZbFwXOCFi$stRh>kdK#r7H#&A3^8-yO1Qy z@9=j=PQh9HVvIC$Cc(;~XNc9D<;0e!xiXr6i{|2G+2eNbVGxh-ob;pQ?ZU$&c+Ezw zstwx1xk^s7`F=k;5gfq)WWcB{mXl)lq}iqXQwo$PY+WC0a+Kr~*c@^ECSUH+$9@e1 zBfLA6tGLB5CuXL}18&erx0oH;kDGuaJb8s{#d10)`${}-^_O$3Z%Ez0Uv%kehUJ1N zxz@ZgEN<^+z7v(Docu*b1rnV7Wn6?IY}DFO$k}>_8V!G5Z16yyWY4`@wP?Ekr%vz5 z0%XaavGrK=@D%Cx+!Z5PyGKGjUm93FWzw;s%BC8csw~H{R3(uAIx|G37i6+xsunyU z`h=T|Uu2h?n~+MVKzP0OouN}|zSdxL$-=AEYZV;tae%hm~-Gi{AJ={79SckQ>9+DSy|*N0q3P^LGg zE>BezA*1C7#$|#v5TLAZalLa*Vtg{S0>4AcJiV*<_dM9YprUOA@W{jymGc==o;F8{ z!|!Br%0+G`awQ!sLxcoUb_hWKA4h!hvnE z88=Q$s}_{4?5ogs5Qr@9y;91z)uiA6z&aaa-4}D9_)H&@%I~jk;QT#B1cR!z@#Z zl0)eLp-`#wnM~vyghfnKPFf(vvC^{ix6jZVbMVM2S(z;}}A)MUOmbRM74}OBI z5j*A>I|9V4(q8FuJbAU4`!-B+|^`YTK5)uiLe#|0R zh>AJ=?%!Nyc8s=0ZOaT*s(37krQW1m466s{isK!MjFr0ERr3vE9 zBcv%y=j;JIKB*D@PP7}3^WF6?o7_L863&hDJ80?dv{S#>dcRPG<$@cK`7}W@Fyve< zJ0|pKNI}dvXsaI(;LIlFnWO$VT5+4VOD@e$vm0B1LjFd`Nhd9yKe;Zf{w#i@a0n{| z@y+!@VRmvfw8y?@IR!ftU;JgO2r!tM;Y&ivqKA4TdAX>vz&MyDoKEQ=g|PW>brrs} z_agE?Ro`t#&8^MDOJoM|wY#A52-)+31K%RDx;Kj(z^_pMi*9#mB6Leq^1N-^ok z82ORAoF8`1i=BTa-uM`4omlMj%N73>p%_}N(}~-C3Ruf7 z6*;Ro^rj=@rWa{@`11OCeleS!N1lCz4o99eT`Cu>$lfbiQiF^nhHIYtblSvs7R$kV zGsbcCO7!;j_HX}y*Y%LB!d5U%qbbhr#nY*`rw78%uVRaz#VWg~DOS33V&r>OR!-K| z{DmK0&G>dkR8KzvI0+w8j?kE0;s+Cn->&ehb=a{(XV%u1fuhm027Q@4>c;SvF6_5? zsx+f^;qq8ihrrE`K zakJp1E6TBjK{kHS-Jf5pFN@HFgG%}J7}~xzfcZ!Gv8KBG|z;T56GiD#MhG5*+Zr^8XWy}z4QXPU8WewwJ+ z*nKbfh<*MR_o=$kDar1@B=wi-q?i#_VrkT9kMFU1!A=IeBONwp>v>KOR1@j9H9s3h z&8n=7T_@puYxg;UP+u#9N2H725EU4cg3TjM+3y}Y-ktNIarQaE8H(RCP9nlsvy9xHs%*&+l6+rW(O z6XX(EiS)U=-CTr2|P1$ z!^DqY+XL}FOh=ORcjhsbSzaXTcXTr4XliFHiLAP&PJug5zn=t0)Oo`MrTGeNQyI{u z!xP>L5`20P(-GkXKjL)*ZgWX}AKLLXjbD<1b7g{hMHK<)v~-0w1hv|Nb_emDs~#KB zV_fF&O@Zt*xnP;3An~z+RnZSk9+R@jL zqlw0{zsRG-?=RN=#@nHhH{k2H1)YoI=vt(Xa(C`glmRhlEllV`PWDtsiW*eE{J8ub z2*&?wymK{v!`iuM)V5d~W74+U2_+;19gAFTZ0d-!XN?2XtPA<#eo@$@+K0n!RVddK z+hMZ95JKLUj#~F#vv5;9Y9OxJ7PIhnf0B{;e6ls5X)rv1XV}km@#a$Nee-=$UiBJ6 zfzl2d&KQ_nE#YS#eTHUla(W5l_vV+|a>S`or`6~9Ko%kdeS3#GyCx*7xj@#Yhc|LX zKw1E!Wggo=J*cB!a~ERR@l>#+s?QuOYs`)`II;Ca@|3L7W;@;bQyfHw){`_Gaw_~v z=R}CX#op^39?wOA6R8Da|9e!C5B@WEBAXnG*L`cIF!p-qyq8u7>I;t(Jg7dHTH%ou zke3UN{XvWcOD~oMN)3Fr4_bEw^w+*O2w-3~{W z5plVSxI(%D?Z}^)^19GecVI&L^o4@?b5n6g$0AU22q67BU)RrS?YKWp0c+%Yk1j)l zHuFPfY=K`=O_Z>rIXLlibtfa+4)jl#ta?1GDU9jvelEK0&Y{G3JObYa=cF*BA)mj_lmzH7q??h?qf(GkBUm;rWb9XAsK2`awZSf?wk7mx3LFZWJ;C_MFM zqqr^hYluBIhA%qn_bshXD|W4Um!zAchVSfk{&!Vv&&O3N%OAXH@_4^=;wG<7sLd0|%*vxkpooZLWd4JMYqrumy{U~kn$BwvX||`Or3G>5 zt=AI{$NqO)wTKNr1>G(Cbgez6-QOK8W!7b}jp4~TN=Lxd+Z~r!Mp8~bcfhg-7(;0Z zH=wQCa#?L*x0Pj)@74HSCFSYKOZf3G8q3;S=;ufZcvsTQt z{>;>(3GA_yv-8g%jvDYC4?dY);E23HoBkn@OGk8Mf`GIzPDxxaNU{d&_~)Na9ln6z z{q&^obyKNMVtF*vuOTQ2Tm;2xOBO|!(w2fhM12z${{&D_Cy0tQ9Hw%SER|Yip(QG^ zrt@v;t=?!5!x%v5SJxSx#+6F6SNE;md7r?7Vz)jo`Ae&+P97Ur1D-|)Md}PXvjaXJ z0tWH>PEVN}0$x@UNTL0Q1xOD@7!5~zddT$4Shx%R5F`=0CKqFd#bUWKk|7k$AzFGI zL%?f_4;OK+N)%PemUGQqId($D>xx%Wpb7mZ-c&?@$-ij&*u)0zJFPG zOte3d+6ba;5j({VIKx&>;Ff7UVH^r>z=^~bRQj;Q5n+vN!tI?%L|wGU7@dGN#eB>Q zczV_R%Rtddlzy?5iiq9LnCesYO(y)1n6R*QN=5EIqyb()iNA)wAu%+)_cs}n1Z`Lw zS?M?Ckm=l;+yj*AMT_#a;eIc1e8O4l&#>7}ia@ZBk$4LKl-1w~mv7shC8cbs^kbZW;Ha1IH z^WzgxR%0(PLiJ-l?&R1%Q_vxiqez=+)rxO#(iPH(Pv+6plBzV~aJ=j|JxpSw)i%fjViwn;|fp`ap zrN*#PmiE(9cG~L>B$nL{7gi&qA86#r^7zexlK7a6eX?{x(d0L1nZm$7yYdMXS1#z!?9@EjL7GfC{{gSakG%h@q zI728pZ*O^B#n%eGeG~t?2y7SLhKSL2@Zq(f{+g)m>7sAFP=xzgxV3g!j?2}98zYo#;?Di*(s zGDLidYVv@m|BYW00hhsY|KIT;WjDXieN|IT)i~$2Xu-I8H%fGY9`i%X1!Ds z5Pevg9yaEJTjWk88M38^j)J0>F$o^5C-4g5`0m4cZsR(=^%dB6Z7S%wp#O~#$NR;* z2W}LT4i*gDl%=_%8+IMp2_ZOW~Eorh8>}?oe2nI%_o@f<~8owAV$>0yEgpn1m z>-dl<01dKSa3p8sw3B%IToJcsAO84~MP6rrDlHh_<@KPl<9O}D|KZAc!7$)$rcXtF z${ILy@MCU23Q7rIrZh^MQv{G{d&TvS)r@zfcrk^NXBT*?fnPQ8Z&hqLjt~5Bu{(4g z0RSOQy796UDuq%T6X|Zh;KnmzEb^vC;lXGN$?%p$(y+6sj-(y2Jb;$CXEd~)1i=pt z&ce!-{dzUv)m6dq{n_@gx=)oM6#ZM(Tm>?!?b@F^&&{nF1y;Z1^*`sSae~i7egCYR z=mw?a(G;}t_t3venX{)N#nXz?0TJNh*b(-MBa$)c#Uqy^C!nmV;!Wg8F21Zgoj+M@ z9&vL6C}O6Zz932by-Ace{_1FkQDh5*9uNxLxpIaPxR)~Fho z&`&5f4K~v@qIZO`U+=rq`=jtZ{jOG~l)Gj)aU!^UT9t10(;7kdWX!V`^4-}CZ-skN z9;y`c@!2fHFt|jbnpav9fnpv;ET?iGIm?E(gRM`GUBbE*!7bQuf3vcS-!kkX~9djv{pN({{fyZ?_uA%wbEsL!SsN znZ-})=R8KD$_vzR39A2Iu~xgY$TF&lIxC<$@G!a?jgxv{!t|9kqpyitmGc%j1U<+o~wfVy_>A?dpnvj9hWbPblsbg zQL_$o5TCdD9QPqwo=B6;Ij>bIRtDAFKf1!CyDl+&m)IhSXPkWmqsA>k6z6n1Wxwr? zv=&*EcDbF=85v|I@qtK`*3*0JW<$w(KoONBY1)rpL_P|i4JR(~PtkIWnsZ;GQEN*5 z)Z}RjJ*<{pnOddRh|=yWXmr5$3#`xmIoRoJAUC~Y^ZBH15MQE3Lx6Mz&CN)r>#0`3}6VV5Qkf2v=}7rMfvE0iOvVPllRF#}0FABDS~#&vso);vCZEB(4P zYF~PO+4}8*x-K0)#_+l>y^K{%o-hRtDAw?+APh9)^_D&_VGA3pRCoFt$dvD=pJ?J{&-P1d;N8} zSd!3aKKH4oT^`OyMD0W}Axwjf$Z<BcLMVzl^6`Umi zV||OILw04a`J+^CL~K9O!TUs=qOcfc>MijI2mc!%-&2zhSx#m< zNIsX$g^yRDW)yO?uLY*}8qgxu^-s#|N6y`e zjCPx`5TRvG?59CbU0|etRjs&QtW$X6^^{7RcFQZHE6^l`CE@0GAR2^QgOKadpH;;$@1bOS(94e#Y@{AC})utKF6oPxt|o!9U0tpz^CimfQj)KoTaO zB>uPjo1>TO3Z>xVl;7!MSDbU@P<)<%a!33J;9(;`#AD50K(|%6|5Fw^tt52Iq)J(S zZAKne{9?7T8It$FX3&2e=a(CBf8%te<*WtgM=KcMwoIF!!@8GCm2_oW+@&11p>>!XA zrtVViO|=5A$vFR?Ygk{+q$Va`1@cB@5PRxSqV#3n$L}!GkHZ(ijypUfWDbs5MHQ7i zPoZx1g16HOuZw~Y0yL3VGPH|zr6#UPmGei0nHKtI)O@=lrU>!r!FyZzGNL)>q@*N< zjQju$8)=>tYl*4&Oafga>p>=SgCgSyuqzUyGyP+y;KBBRvn{UgP5*O(g@wM)*{`~n z<8D8vAADS4GX!GQQQNU(9Fj#eh{nBS#hEvUKhrN6KkwiKm6s}X&Z!M2oIF9fQCS3y zl4NmIh!Tp(It){UAefzq!KbA>5s4*k=r?FZ6(yICw+9Zx91~O1fvAQ1)Yp@4|9C~4 z=jZ1m7(4Tdir+8iK8r1WU(*}o2OgOeY0zh=FZdU`6e?GhR6N3NSUpMb@*Z*?4G<_{ z)XXz$zYq!WDm0OZO&8Bu0-78|)cm41{2u0Z#3MyI&i|Y!M;jC2UHV=46krWZn~A(_ z+3hmPnUmpQmcYiJY_+lQ?Wr<9unP*xwgG#qQXBM;ba|O{0nz%31O-_Xc43RX;?`f; zggeKAnMEtIsuW?@qhF&5S(GkCxcG;pGhKWqFLf^@g@iFF9L!8PvD zh$&)#rrTrLmsf~7dECAB8u2P{4hV~@(r_A%VKo{h=nhIH8Rw*o0jhD5qJQ2 zndYX$lgIKN+$!na7UQilmigWf&oDa!qw4@*BqDs)XYyGIxNC zrdMX#sAi+>xP0`R{x>KokILQ=iv)C@ipPnE?%^|$&WOro&bTaoq#G1=;uL8B2;G=I zsu@PxnoTE>0DZ=Bnf~k54J_d}cbs|1-O0ktcLrmo7^-pJ4hy{Wi@=7q54;D^D6+z{ z<=&k!k+ z`9|N^Km5B1(0m;7O}m4a2~{Y*pp)}>RBmd~^QXXccoVmKZ#G=@A+1H{g@9{rNnDmN z;~v4cT_LNE?TLop;WM4@=Os7bQ!)HiP!wd(4p5-APo#!gP~(-SP!9?T13Ej+D8ebX zWa6_3#XPm~-Jh+GulxB7H%3yvpfxzWkldeo#lHEta|m&dkL@DQ&YCPD+L|ycw@eez z$N{3jrGVNKBLS8fDo^+L+I+++m6N^XgYL+t34Sz6Yz|PrEr;px6q7FL^6S!7MPLY#3Zg=#Hf?1Ro~L?P+(+lPf1 zbc2JV+Styp)xk1FlLuKALMeFq-hdO${J0W9_PmAw{~*d8byFk&nox6`pUyl!AW zS8|YEFG_*$hdp^e7z?jTYebI3^X%6`xc=H2YscBj1_4n{t|UWH2>En4_LTJ2Ghen` z@9is*SV>qsCQNWW))WF)yk;?wX}jI)KkvHA>tEaGcV4-7Um7WQxC_WVx`DrX62aeUkY+yxxkze)*NY12pn+cdrhQYz?0ka zsg;!FQf;~|=;L)?_+6aTDXAEGE~o^^8zb{=P|RkJi6&Byxn(!caF3)tcF7R_l50Xf z63?aKbx#5o1s`~6jhNSa5K#8@#W0t!$|;6U!SP&__=brVriw?pHteNCjpE3T_O=k~ z$rRf9OjE*Ij{bEgven!?ASw;cy{PZ8jj3Ngo``FaA!E?ld<2j#2gSRVROJf65 z5HnTHJo2ZB;CiYL?Q-f3&H3>Vzs6j0F;Z>^+f{aTSavp5Q5xS$%s5dyw#7E|n=Elj zS=fP(Y)J!A6wqjeWMG}5t(Iccn=!Y`oO06ZJgi#l4mtEP@)yXuW%yrYHbZRGgU?(VTaN?(86x;6N_CZbg2N|$-S)8oXW z((f|D!^q8>KIHqfJ#-Z`eaOav_41x~p+M;~S-ND3SjH)$; z1*F;08h4#t_vtty66yn9+;vj0^xTJ^RxcaS%m+EWksod7_gJOE3lw|MUT@+Ge(x`f zyi;E1$8koRe-0Dnl za(3FgP7ZI^2liMjkN*9Z+;TD_V~rBOeM4? zs~vHb%ZE>~(Yi)Uwrh+GzWKEyiz(*#@^M+oYR5IiuT%&?Sm%951bJm2`)zdA{?``oWYTLZSsJE zieGGfyZuyW^%7MCss(izaLmg{H?D*zrs?8@tm|fhX%A1&58dPGP5F1W^n0CxS%FO6 zTLHe82^(%t7WS{$c9fB!akzm%S+gRhs4}YBrp#%P#f?5{yzan|m-x*Uvw6EkSt~QN z+Ksqudvx98*J%6VN;nO8MxKPm<9}wmOh`dJ>BdfN$xP(q$TRqy1Q6n~>f`>EDKS zPR8J8kdJF3ubyaDT8YUBbIJ#==#cMb?wQI_l{x$@lf5mnpC*`QXCth*1b7)#00&qe z{G1`UHDrZzKd_p0U6&z2_(tq;L+py_$w!HdwH?7#8&4I2H)HCP>A>|?Pgs@U`R9K^ z0`9`@e+euwki@M28MjI>(_k)%vnZ1e;UuO@31{r^O?CxlRnj-h5*P@LTvjqpErXlI zm&HC&#o^)jp)CJ#$Y!ImA?sks8qfBaPQigWsIllYqQ?j`Nj^(3>>E;Bi#cE%6h>xL zKHqb1M>JWYZUgXhP1i_w{CE?{6!Z;cK9M;h)cG3}C5L)0ENRgtSHdmLVUN|K>uO$( z5Q4s5C(q3pNECAy?~i^yX|_!}(tjck`fj=*W8=?C=X!1`gtq zybSPJY~(JsBDf&v_}RLI+#uxiP7IcK0)zm93TGf;3xEXY<9@{QVZS1Qbr`8RzZuC zD8>3Zn&5v)8SwUw=8F!(#t&(RiO1F=1m|$TJwjdD7rNSl>d;L8un>PR zeKqo`92pCujFqu_m5;ZyPnYB_Rn5S7^Cvix$=gYdv3)zjnswNF0~&CWX%xJOFzev82S87U7>La17ZbN-!M3+9z} zA5wXEA_e2DSU1M>n^`;EgR;~)>Vsej2Db8qKWQATDcx)?RM|PAL~W)_87;p^%^GT z_Cdo}ea+cQv1NZvO~nGua3zpxG?0q9`4tkiqf^R%V*=T;wdJivt>jcb$hz<5t2`G9 zK8~0itI)UaRH&At1j_=M5U(S60**NXK4bzeSp>^2OZ%r00`7{?F2g-u{}u>OU-yCW ztzw^EF8uDk23&?s`UT05_#rQhg4jcDiK>HDYPE`%=i&|)UGHuaS@$g^5nAdU%Sv&n zTnWirlp}{_*&#vVT`PmvLfJ?FtMsIBW^;3MOQlJS<{T>R(1S%&P^2scQ&Au?Rx5^8 zELC4JlpQ;iouz5E^RJPM?wh=h9if0^h>I^>G-bXzCa51ZflSe!yKfim_Dh2z*J{3n zbSP+Jk6Q^8VK5sKx|l2O^l4SQF2eCANsQm4 zQerug0O=}=sMAi7hSgKb;=%5bd8V?uz*ClbwlxY=C{+YdyF|MNt&&6|XRl?*3}Kd; zR$v7TKLR?9z8($QAwJ#x-POHEN63cPiteTF$D4R$H#F|MK%GER1>@%RaMW<0u0{>H zh4F;vKF8zrS7X$@eMddCkfIcG zOKfRYZb1;klsuVI_*6ENQX+60QI1HuUt1p6Ju;g)Hi$PaQQ8DLeVP|ak3E*NADl?x zwe)p;C_?x>fiM%Ii#F8rK%K8w{i+#8u*K+P@Z&tZyZu?6^p(;7Vsd@_!tf?zL7u*V zH|`x_O6LRhGJz-H*5QWqr3b|lW~32scAN-LM#qxc$^wp=dKtfY-jZAv_K>O%GiHk^ z#mp0`5xIFnb{isz84TORG=Ks3H4fTSJ{bXD*o?Byd-i+B`OhlOORLRwlbU0!TBJwU z&&M8(blVIjW5gn#A_=9y;=? zAKZ9GZTC1QSDg||J~SEim2cy1lKsj=u7*3rDM5pfKlO;nWJ0Go5?gJk@)3>k$J=`d zC#GYE_1zf~AG$W65f=NS;`8B6qopS)StvS(3QncpA0pk+ci+uxyKc|Rf5Rm*av070 zkRa?Jbjunm?wmtKN?0xYKZstRuDuE>0UO)C@mpJj^;og4aPuFTi-v=!3Q7+|d TDmM9lFB0XXlqGA$je`FVBtZw6 literal 0 HcmV?d00001 From 5867535f0be5e9e5b5c1c24755f883ddc268e1a8 Mon Sep 17 00:00:00 2001 From: Stabar-ti Date: Sat, 11 Jul 2026 10:50:36 +0200 Subject: [PATCH 2/3] lore bug fix --- .../java/ti4/service/fow/LoreEffects.java | 23 ++++++++++++++--- .../java/ti4/service/fow/LoreServiceTest.java | 25 +++++++++++++++++++ 2 files changed, 44 insertions(+), 4 deletions(-) diff --git a/src/main/java/ti4/service/fow/LoreEffects.java b/src/main/java/ti4/service/fow/LoreEffects.java index b09ab61a25..06cd1ee765 100644 --- a/src/main/java/ti4/service/fow/LoreEffects.java +++ b/src/main/java/ti4/service/fow/LoreEffects.java @@ -692,11 +692,27 @@ private static EffectDescription effectAc(EffectContext ctx) { return new EffectDescription(who(ctx) + " drew " + StringHelper.pluralize(n, "action card") + ".", false); } + /** + * Resolves a token/attachment name to its canonical stored id, in the same priority order as + * the {@code /add_token} and {@code /remove_token} slash commands: attachments (e.g. + * "positiveres" -> "attachment_positiveres.png") are tried before generic tokens (wormholes, + * DMZ, etc.), falling back to the raw input for anything the bot doesn't recognize. + * {@code Mapper.getTokenID} alone only knows generic tokens — an attachment name resolved + * that way stores the bare model id instead of its image filename. The stat modifier still + * applies either way ({@code Mapper.getAttachmentInfo} accepts both forms), but the bare id + * never resolves to an image file, so the token silently never renders on the map. + */ + private static String resolveTokenOrAttachment(String rawId) { + String attachmentId = Mapper.getAttachmentImagePath(AliasHandler.resolveAttachment(rawId)); + if (attachmentId != null) return attachmentId; + String tokenId = Mapper.getTokenID(AliasHandler.resolveToken(rawId)); + return tokenId != null ? tokenId : rawId; + } + // token -> drops a token on the target holder (name resolved via Mapper, e.g. "gravityrift") private static EffectDescription effectToken(EffectContext ctx) { if (ctx.tile == null || ctx.args.length == 0) return null; - String tokenId = Mapper.getTokenID(ctx.arg(0)); - if (tokenId == null) tokenId = ctx.arg(0); + String tokenId = resolveTokenOrAttachment(ctx.arg(0)); ctx.tile.addToken(tokenId, ctx.holder); String readableName = tokenId.replaceFirst("^token_", "").replaceFirst("\\.png$", ""); String location = ctx.tile.getPosition() + (Constants.SPACE.equals(ctx.holder) ? "" : " (" + ctx.holder + ")"); @@ -707,8 +723,7 @@ private static EffectDescription effectToken(EffectContext ctx) { // removetoken [planet] -> removes a token or planet attachment from the target holder private static EffectDescription effectRemoveToken(EffectContext ctx) { if (ctx.tile == null || ctx.args.length == 0) return null; - String tokenId = Mapper.getTokenID(ctx.arg(0)); - if (tokenId == null) tokenId = ctx.arg(0); + String tokenId = resolveTokenOrAttachment(ctx.arg(0)); String holder = ctx.holder; if (ctx.args.length >= 2) { diff --git a/src/test/java/ti4/service/fow/LoreServiceTest.java b/src/test/java/ti4/service/fow/LoreServiceTest.java index eca31daace..b850f001d0 100644 --- a/src/test/java/ti4/service/fow/LoreServiceTest.java +++ b/src/test/java/ti4/service/fow/LoreServiceTest.java @@ -453,6 +453,20 @@ void atLocationOverrideRedirectsToken() { .getTokenList() .contains("token_gravityrift.png")); } + + @Test + void attachmentNameResolvesToImageFilenameNotBareModelId() { + // Regression: "positiveres" is an attachment id (Mapper.getAttachmentInfo), not a + // generic token — Mapper.getTokenID alone can't resolve it, so it used to be stored as + // the bare, un-renderable "positiveres" string. The resource-modifier stat still + // applied either way (getAttachmentInfo accepts both the bare id and the image + // filename), which is exactly why this was invisible until the token silently never + // appeared on the rendered map image. + LoreEffects.applyLoreEffectsForTest(player, game, entry("!token positiveres"), systemTile, "mr", true); + var tokens = systemTile.getUnitHolders().get("mr").getTokenList(); + assertTrue(tokens.contains("attachment_positiveres.png"), "resolved image filename must be stored"); + assertFalse(tokens.contains("positiveres"), "bare attachment id must not be stored"); + } } // ----------------------------------------------------------------------- @@ -628,6 +642,17 @@ void missingTokenReportsNothingRemoved() { assertEquals(1, descs.size()); assertTrue(descs.get(0).contains("nothing removed")); } + + @Test + void removesAttachmentAddedByShortName() { + // Add and remove must resolve "positiveres" to the same stored id (attachment + // resolution takes priority over generic-token resolution on both sides) or removal + // silently no-ops against a token that was actually stored under a different string. + LoreEffects.applyLoreEffectsForTest(player, game, entry("!token positiveres"), systemTile, "mr", true); + LoreEffects.applyLoreEffectsForTest( + player, game, entry("!removetoken positiveres"), systemTile, "mr", true); + assertFalse(systemTile.getUnitHolders().get("mr").getTokenList().contains("attachment_positiveres.png")); + } } // ----------------------------------------------------------------------- From cfde923d22270c828a3253741e0a47161bda653b Mon Sep 17 00:00:00 2001 From: Stabar-ti Date: Mon, 13 Jul 2026 20:36:11 +0200 Subject: [PATCH 3/3] Update token_fowvision.png --- src/main/resources/tokens/token_fowvision.png | Bin 14628 -> 7800 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/src/main/resources/tokens/token_fowvision.png b/src/main/resources/tokens/token_fowvision.png index 4090b7a6eadb58cf5c2d0e557c95809488aad6b9..da0bc70db69d91a58a8db7a145dbac8fb9ff784b 100644 GIT binary patch literal 7800 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 KMNUMnLSTXbOfVV% literal 14628 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 TDmM9lFB0XXlqGA$je`FVBtZw6