Add support for "rear" detail layer in clients

This commit is contained in:
Jordan Irwin 2023-02-09 14:24:03 -08:00
parent f0da4328b7
commit 20a671639b
8 changed files with 114 additions and 5 deletions

View file

@ -0,0 +1,5 @@
{
"detail": {
"rear": []
}
}

View file

@ -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

View file

@ -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<String> emptyForZeroIndex = Arrays.asList("dress", "mouth", "mask", "hair", "hat", "detail");
private List<Integer> 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
* <code>true</code> 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<String, Integer> layer_map = new HashMap<>();
// make a copy of layer names so it can be amended in cases of special layers
final List<String> 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 <code>null</code>.
*/
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) {

View file

@ -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();

View file

@ -60,7 +60,7 @@ public class Outfits {
// layers that can be re-colored
public static final List<String> 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<String> SKIN_LAYERS = Arrays.asList("body", "head");

View file

@ -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();

View file

@ -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
* <code>true</code> if a rear sprite is available.
*/
detailHasRearLayer(detail: number): boolean {
return this.detailRearLayers.indexOf(detail) > -1;
}
}

View file

@ -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";