diff --git a/data/sprites/outfit/outfits.json b/data/sprites/outfit/outfits.json new file mode 100644 index 0000000000..5a363657a3 --- /dev/null +++ b/data/sprites/outfit/outfits.json @@ -0,0 +1,5 @@ +{ + "detail": { + "rear": [] + } +} diff --git a/doc/CHANGES.txt b/doc/CHANGES.txt index cc6dfe5698..8e8dd8eda3 100644 --- a/doc/CHANGES.txt +++ b/doc/CHANGES.txt @@ -43,6 +43,9 @@ Changelog - melee weapons can have longer range - descreased damage penalty compensating for player hit chance handicap +*clients* +- support "rear" layers + *web client* - added support for drawing weather effects - added login sound diff --git a/src/games/stendhal/client/OutfitStore.java b/src/games/stendhal/client/OutfitStore.java index ae478637f3..43481cb797 100644 --- a/src/games/stendhal/client/OutfitStore.java +++ b/src/games/stendhal/client/OutfitStore.java @@ -21,12 +21,16 @@ import java.awt.Color; import java.awt.Composite; import java.awt.Graphics; import java.net.URL; +import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; +import java.util.LinkedList; import java.util.List; import java.util.Map; import org.apache.log4j.Logger; +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; import games.stendhal.client.gui.OutfitColor; import games.stendhal.client.gui.wt.core.WtWindowManager; @@ -35,6 +39,7 @@ import games.stendhal.client.sprite.ImageSprite; import games.stendhal.client.sprite.Sprite; import games.stendhal.client.sprite.SpriteCache; import games.stendhal.client.sprite.SpriteStore; +import games.stendhal.client.util.JSONLoader; /** * An outfit store. @@ -48,12 +53,17 @@ public class OutfitStore { // these layers should return an empty sprite for index "0" final List emptyForZeroIndex = Arrays.asList("dress", "mouth", "mask", "hair", "hat", "detail"); + private List detailRearLayers; + + private boolean initialized = false; + /** * The singleton. */ private static final OutfitStore sharedInstance = new OutfitStore( SpriteStore.get()); + /** * The sprite store. */ @@ -67,12 +77,49 @@ public class OutfitStore { */ private OutfitStore(final SpriteStore store) { this.store = store; + detailRearLayers = new ArrayList<>(); + } + + public void init() { + if (initialized) { + logger.warn("tried to re-initialize OutfitStore"); + return; + } + initialized = true; + + final JSONLoader loader = new JSONLoader(); + loader.onDataReady = new Runnable() { + public void run() { + final JSONObject document = (JSONObject) loader.data; + final JSONArray detailRear = (JSONArray) ((JSONObject) document.get("detail")).get("rear"); + for (final Object o: detailRear) { + try { + detailRearLayers.add(Integer.parseInt(o.toString())); + } catch (final NumberFormatException e) { + logger.error("Couldn't parse rear detail layer \"" + o.toString() + "\"", e); + } + } + } + }; + loader.load(OUTFITS + "/outfits.json"); } // // OutfitStore // + /** + * Checks if we can draw a "rear" detail layer. + * + * @param detail + * Detail index. + * @return + * true if a rear sprite is available. + */ + private boolean detailHasRearLayer(final int detail) { + return detailRearLayers.contains(detail); + } + /** * Build an outfit sprite. * @@ -84,8 +131,12 @@ public class OutfitStore { private Sprite buildOutfit(final String strcode, final OutfitColor color) { final Map layer_map = new HashMap<>(); + // make a copy of layer names so it can be amended in cases of special layers + final List lnames = new LinkedList<>(); + lnames.addAll(LAYER_NAMES); + // initialize outfit parts to 0 in case some haven't been specified - for (String n: LAYER_NAMES) { + for (String n: lnames) { layer_map.put(n, 0); } @@ -100,6 +151,13 @@ public class OutfitStore { Sprite layer; + // detail rear layer + final int detailIndex = layer_map.get("detail"); + if (detailHasRearLayer(detailIndex)) { + lnames.add(0, "detail-rear"); + layer_map.put("detail-rear", detailIndex); + } + // Body layer final int bodyIndex = layer_map.get("body"); boolean busty = false; @@ -120,7 +178,7 @@ public class OutfitStore { sprite = new ImageSprite(layer); final Graphics g = sprite.getGraphics(); - for (String lname: LAYER_NAMES) { + for (String lname: lnames) { // hair is not drawn under certain hats/helmets if (lname.equals("hair") && HATS_NO_HAIR.contains(layer_map.get("hat"))) { continue; @@ -195,7 +253,7 @@ public class OutfitStore { * @return * The Sprite or null. */ - public Sprite getLayerSprite(final String layer, final int index, final OutfitColor color, + public Sprite getLayerSprite(String layer, final int index, final OutfitColor color, final boolean busty) { if (emptyForZeroIndex.contains(layer)) { if (index <= 0) { @@ -207,7 +265,13 @@ public class OutfitStore { } } - String ref = OUTFITS + "/" + layer + "/" + getSpriteSuffix(index); + String ref = getSpriteSuffix(index); + if (layer.endsWith("-rear")) { + ref += "-rear"; + layer = layer.replaceFirst("-rear$", ""); + } + + ref = OUTFITS + "/" + layer + "/" + ref; if (layer.equals("body") && WtWindowManager.getInstance().getPropertyBoolean("gamescreen.nonude", true)) { final URL nonudeURL = DataLoader.getResource(ref + "-nonude.png"); if (nonudeURL != null) { diff --git a/src/games/stendhal/client/stendhal.java b/src/games/stendhal/client/stendhal.java index 1687d6f67c..3b73bdcbf7 100644 --- a/src/games/stendhal/client/stendhal.java +++ b/src/games/stendhal/client/stendhal.java @@ -274,6 +274,8 @@ public final class stendhal { TileStore.init(); // initialize emoji data ClientSingletonRepository.getEmojiStore().init(); + // initialize outfit data + OutfitStore.get().init(); final UserContext userContext = UserContext.get(); final PerceptionDispatcher perceptionDispatch = new PerceptionDispatcher(); diff --git a/src/games/stendhal/common/Outfits.java b/src/games/stendhal/common/Outfits.java index 1d3c16e8a5..83d96d6295 100644 --- a/src/games/stendhal/common/Outfits.java +++ b/src/games/stendhal/common/Outfits.java @@ -60,7 +60,7 @@ public class Outfits { // layers that can be re-colored public static final List RECOLORABLE_OUTFIT_PARTS = Arrays.asList( - "detail", "dress", "hair", "body", "head", "eyes", "mask", "hat"); + "detail", "detail-rear", "dress", "hair", "body", "head", "eyes", "mask", "hat"); public static final List SKIN_LAYERS = Arrays.asList("body", "head"); diff --git a/srcjs/stendhal/Client.ts b/srcjs/stendhal/Client.ts index 4444c6bdd6..6f28c7f950 100644 --- a/srcjs/stendhal/Client.ts +++ b/srcjs/stendhal/Client.ts @@ -129,6 +129,8 @@ export class Client { singletons.getTileStore().init(); // initialize emoji data singletons.getEmojiStore().init(); + // initialize outfit data + stendhal.data.outfit.init(); new DesktopUserInterfaceFactory().create(); diff --git a/srcjs/stendhal/data/OutfitStore.ts b/srcjs/stendhal/data/OutfitStore.ts index d2e8ccd192..10fbe6abbf 100644 --- a/srcjs/stendhal/data/OutfitStore.ts +++ b/srcjs/stendhal/data/OutfitStore.ts @@ -9,9 +9,15 @@ * * ***************************************************************************/ +import { Paths } from "./Paths"; +import { JSONLoader } from "../util/JSONLoader"; + export class OutfitStore { + private detailRearLayers: number[] = []; + + // player pickable layers private count: {[key: string]: number} = { "hat": 19, @@ -56,6 +62,14 @@ export class OutfitStore { // do nothing } + init() { + const loader = new JSONLoader(); + loader.onDataReady = () => { + this.detailRearLayers = loader.data["detail"]["rear"]; + } + loader.load(Paths.sprites + "/outfit/outfits.json"); + } + /** * Determines if hair should be drawn under a determinted hat index. * @@ -82,4 +96,16 @@ export class OutfitStore { drawBustyDress(dress: number, body: number): boolean { return body == 1 && this.busty_dress.indexOf(dress) > -1; } + + /** + * Checks if we can draw a "rear" detail layer. + * + * @param detail + * Detail index. + * @return + * true if a rear sprite is available. + */ + detailHasRearLayer(detail: number): boolean { + return this.detailRearLayers.indexOf(detail) > -1; + } } diff --git a/srcjs/stendhal/entity/RPEntity.ts b/srcjs/stendhal/entity/RPEntity.ts index 15295344d5..1d973d0c9f 100644 --- a/srcjs/stendhal/entity/RPEntity.ts +++ b/srcjs/stendhal/entity/RPEntity.ts @@ -241,6 +241,10 @@ export class RPEntity extends ActiveEntity { if (this.octx) { this.octx.clearRect(0, 0, this.octx.canvas.width, this.octx.canvas.height); } + if (stendhal.data.outfit.detailHasRearLayer(outfit["detail"])) { + layers.splice(0, 0, "detail-rear"); + outfit["detail-rear"] = outfit["detail"]; + } for (const layer of layers) { // hair is not drawn under certain hats/helmets if (layer == "hair" && !stendhal.data.outfit.drawHair(outfit["hat"])) { @@ -287,6 +291,9 @@ export class RPEntity extends ActiveEntity { n += "-nonude"; } else if (part === "dress" && stendhal.data.outfit.drawBustyDress(index, body)) { n += "b"; + } else if (part.endsWith("-rear")) { + n += "-rear"; + part = part.replace(/-rear$/, ""); } const filename = stendhal.paths.sprites + "/outfit/" + part + "/" + n + ".png";