mirror of
https://github.com/kuroppoi/entralinked.git
synced 2026-09-08 00:35:12 -05:00
Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8849265805 | ||
|
|
0d964ec129 | ||
|
|
e4fff87b87 | ||
|
|
aae1abd18f | ||
|
|
038f7f5136 | ||
|
|
f41888ba58 | ||
|
|
328aac45e1 | ||
|
|
806bd5cc17 | ||
|
|
06e388ab89 | ||
|
|
60d6698f6b | ||
|
|
4800f7b6c2 | ||
|
|
a1e8cc97bc | ||
|
|
2aebbac230 | ||
|
|
09ce3a26ce | ||
|
|
e4f7a26f9c | ||
|
|
d90200e4ca |
@@ -2,8 +2,9 @@
|
||||
[](https://github.com/kuroppoi/entralinked/actions)
|
||||
|
||||
Entralinked is a standalone Game Sync emulator developed for use with Pokémon Black & White and its sequels.\
|
||||
Its purpose is to serve as a simple utility for downloading Pokémon, Items, C-Gear skins, Pokédex skins and Musicals\
|
||||
to your game without needing to edit your save file.
|
||||
Its purpose is to serve as a simple utility for downloading Pokémon, Items, C-Gear skins, Pokédex skins, Musicals\
|
||||
and, in Black 2 & White 2 only, Join Avenue visitors to your game without needing to edit your save file.\
|
||||
It can also be used to Memory Link with a Black or White save file if you don't have a second DS system.
|
||||
|
||||
## Building
|
||||
|
||||
|
||||
Submodule poke-sprites-v updated: 70e5ee79b0...04c697187a
15
src/main/java/entralinked/model/avenue/AvenueShopType.java
Normal file
15
src/main/java/entralinked/model/avenue/AvenueShopType.java
Normal file
@@ -0,0 +1,15 @@
|
||||
package entralinked.model.avenue;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonEnumDefaultValue;
|
||||
|
||||
public enum AvenueShopType {
|
||||
|
||||
@JsonEnumDefaultValue
|
||||
RAFFLE,
|
||||
FLORIST,
|
||||
SALON,
|
||||
ANTIQUE,
|
||||
DOJO,
|
||||
CAFE,
|
||||
MARKET
|
||||
}
|
||||
15
src/main/java/entralinked/model/avenue/AvenueVisitor.java
Normal file
15
src/main/java/entralinked/model/avenue/AvenueVisitor.java
Normal file
@@ -0,0 +1,15 @@
|
||||
package entralinked.model.avenue;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import entralinked.GameVersion;
|
||||
|
||||
public record AvenueVisitor(
|
||||
@JsonProperty(required = true) String name,
|
||||
@JsonProperty(required = true) AvenueVisitorType type,
|
||||
@JsonProperty(required = true) AvenueShopType shopType,
|
||||
@JsonProperty(required = true) GameVersion gameVersion,
|
||||
@JsonProperty(required = true) int countryCode,
|
||||
@JsonProperty(required = true) int stateProvinceCode,
|
||||
@JsonProperty(required = true) int personality,
|
||||
@JsonProperty(required = true) int dreamerSpecies) {}
|
||||
@@ -0,0 +1,59 @@
|
||||
package entralinked.model.avenue;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonEnumDefaultValue;
|
||||
|
||||
public enum AvenueVisitorType {
|
||||
|
||||
// 0
|
||||
@JsonEnumDefaultValue
|
||||
YOUNGSTER(0),
|
||||
LASS(0, true),
|
||||
|
||||
// 1
|
||||
ACE_TRAINER_MALE(1),
|
||||
ACE_TRAINER_FEMALE(1, true),
|
||||
|
||||
// 2
|
||||
RANGER_MALE(2),
|
||||
RANGER_FEMALE(2, true),
|
||||
|
||||
// 3
|
||||
BREEDER_MALE(3),
|
||||
BREEDER_FEMALE(3, true),
|
||||
|
||||
// 4
|
||||
SCIENTIST_MALE(4),
|
||||
SCIENTIST_FEMALE(4, true),
|
||||
|
||||
// 5
|
||||
HIKER(5),
|
||||
PARASOL_LADY(5, true),
|
||||
|
||||
// 6
|
||||
ROUGHNECK(6),
|
||||
NURSE(6, true),
|
||||
|
||||
// 7
|
||||
PRESCHOOLER_MALE(7),
|
||||
PRESCHOOLER_FEMALE(7, true);
|
||||
|
||||
private final int clientId;
|
||||
private final boolean female;
|
||||
|
||||
private AvenueVisitorType(int clientId, boolean female) {
|
||||
this.clientId = clientId;
|
||||
this.female = female;
|
||||
}
|
||||
|
||||
private AvenueVisitorType(int clientId) {
|
||||
this(clientId, false);
|
||||
}
|
||||
|
||||
public int getClientId() {
|
||||
return clientId;
|
||||
}
|
||||
|
||||
public boolean isFemale() {
|
||||
return female;
|
||||
}
|
||||
}
|
||||
7
src/main/java/entralinked/model/player/DreamDecor.java
Normal file
7
src/main/java/entralinked/model/player/DreamDecor.java
Normal file
@@ -0,0 +1,7 @@
|
||||
package entralinked.model.player;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
public record DreamDecor(
|
||||
@JsonProperty(required = true) int id,
|
||||
@JsonProperty(required = true) String name) {}
|
||||
@@ -6,31 +6,34 @@ import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import entralinked.GameVersion;
|
||||
import entralinked.model.avenue.AvenueVisitor;
|
||||
import entralinked.model.pkmn.PkmnInfo;
|
||||
|
||||
public class Player {
|
||||
|
||||
private final String gameSyncId;
|
||||
private final GameVersion gameVersion;
|
||||
private final List<DreamEncounter> encounters = new ArrayList<>();
|
||||
private final List<DreamItem> items = new ArrayList<>();
|
||||
private final List<AvenueVisitor> avenueVisitors = new ArrayList<>();
|
||||
private PlayerStatus status;
|
||||
private GameVersion gameVersion;
|
||||
private PkmnInfo dreamerInfo;
|
||||
private int levelsGained;
|
||||
private String cgearSkin;
|
||||
private String dexSkin;
|
||||
private String musical;
|
||||
|
||||
public Player(String gameSyncId, GameVersion gameVersion) {
|
||||
public Player(String gameSyncId) {
|
||||
this.gameSyncId = gameSyncId;
|
||||
this.gameVersion = gameVersion;
|
||||
}
|
||||
|
||||
public void resetDreamInfo() {
|
||||
status = PlayerStatus.AWAKE;
|
||||
gameVersion = null;
|
||||
dreamerInfo = null;
|
||||
encounters.clear();
|
||||
items.clear();
|
||||
avenueVisitors.clear();
|
||||
levelsGained = 0;
|
||||
cgearSkin = null;
|
||||
dexSkin = null;
|
||||
@@ -41,10 +44,6 @@ public class Player {
|
||||
return gameSyncId;
|
||||
}
|
||||
|
||||
public GameVersion getGameVersion() {
|
||||
return gameVersion;
|
||||
}
|
||||
|
||||
public void setEncounters(Collection<DreamEncounter> encounters) {
|
||||
if(encounters.size() <= 10) {
|
||||
this.encounters.clear();
|
||||
@@ -67,6 +66,17 @@ public class Player {
|
||||
return Collections.unmodifiableList(items);
|
||||
}
|
||||
|
||||
public void setAvenueVisitors(Collection<AvenueVisitor> avenueVisitors) {
|
||||
if(avenueVisitors.size() <= 12) {
|
||||
this.avenueVisitors.clear();
|
||||
this.avenueVisitors.addAll(avenueVisitors);
|
||||
}
|
||||
}
|
||||
|
||||
public List<AvenueVisitor> getAvenueVisitors() {
|
||||
return Collections.unmodifiableList(avenueVisitors);
|
||||
}
|
||||
|
||||
public void setStatus(PlayerStatus status) {
|
||||
this.status = status;
|
||||
}
|
||||
@@ -75,6 +85,14 @@ public class Player {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setGameVersion(GameVersion gameVersion) {
|
||||
this.gameVersion = gameVersion;
|
||||
}
|
||||
|
||||
public GameVersion getGameVersion() {
|
||||
return gameVersion;
|
||||
}
|
||||
|
||||
public void setDreamerInfo(PkmnInfo dreamerInfo) {
|
||||
this.dreamerInfo = dreamerInfo;
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||
|
||||
import entralinked.GameVersion;
|
||||
import entralinked.model.avenue.AvenueVisitor;
|
||||
import entralinked.model.pkmn.PkmnInfo;
|
||||
|
||||
/**
|
||||
@@ -22,19 +23,22 @@ public record PlayerDto(
|
||||
String musical,
|
||||
int levelsGained,
|
||||
@JsonDeserialize(contentAs = DreamEncounter.class) Collection<DreamEncounter> encounters,
|
||||
@JsonDeserialize(contentAs = DreamItem.class) Collection<DreamItem> items) {
|
||||
@JsonDeserialize(contentAs = DreamItem.class) Collection<DreamItem> items,
|
||||
@JsonDeserialize(contentAs = AvenueVisitor.class) Collection<AvenueVisitor> avenueVisitors) {
|
||||
|
||||
public PlayerDto(Player player) {
|
||||
this(player.getGameSyncId(), player.getGameVersion(), player.getStatus(), player.getDreamerInfo(), player.getCGearSkin(),
|
||||
player.getDexSkin(), player.getMusical(), player.getLevelsGained(), player.getEncounters(), player.getItems());
|
||||
this(player.getGameSyncId(), player.getGameVersion(), player.getStatus(), player.getDreamerInfo(),
|
||||
player.getCGearSkin(), player.getDexSkin(), player.getMusical(), player.getLevelsGained(),
|
||||
player.getEncounters(), player.getItems(),player.getAvenueVisitors());
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new {@link Player} object using the data in this DTO.
|
||||
*/
|
||||
public Player toPlayer() {
|
||||
Player player = new Player(gameSyncId, gameVersion);
|
||||
Player player = new Player(gameSyncId);
|
||||
player.setStatus(status);
|
||||
player.setGameVersion(gameVersion);
|
||||
player.setDreamerInfo(dreamerInfo);
|
||||
player.setCGearSkin(cgearSkin);
|
||||
player.setDexSkin(dexSkin);
|
||||
@@ -42,6 +46,7 @@ public record PlayerDto(
|
||||
player.setLevelsGained(levelsGained);
|
||||
player.setEncounters(encounters == null ? Collections.emptyList() : encounters);
|
||||
player.setItems(items == null ? Collections.emptyList() : items);
|
||||
player.setAvenueVisitors(avenueVisitors == null ? Collections.emptyList() : avenueVisitors);
|
||||
return player;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,8 +16,6 @@ import org.apache.logging.log4j.Logger;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
|
||||
import entralinked.GameVersion;
|
||||
|
||||
/**
|
||||
* Manager class for managing {@link Player} information (Global Link users)
|
||||
*/
|
||||
@@ -119,7 +117,7 @@ public class PlayerManager {
|
||||
* That is, the specified Game Sync ID wasn't already registered and the player data
|
||||
* was saved without any errors.
|
||||
*/
|
||||
public Player registerPlayer(String gameSyncId, GameVersion gameVersion) {
|
||||
public Player registerPlayer(String gameSyncId) {
|
||||
// Check for duplicate Game Sync ID
|
||||
if(playerMap.containsKey(gameSyncId)) {
|
||||
logger.warn("Attempted to register duplicate player {}", gameSyncId);
|
||||
@@ -127,7 +125,7 @@ public class PlayerManager {
|
||||
}
|
||||
|
||||
// Construct player object
|
||||
Player player = new Player(gameSyncId, gameVersion);
|
||||
Player player = new Player(gameSyncId);
|
||||
player.setStatus(PlayerStatus.AWAKE);
|
||||
|
||||
// Try to save player data
|
||||
|
||||
@@ -1,12 +1,25 @@
|
||||
package entralinked.network.http.dashboard;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import entralinked.Entralinked;
|
||||
import entralinked.model.avenue.AvenueVisitor;
|
||||
import entralinked.model.dlc.Dlc;
|
||||
import entralinked.model.dlc.DlcList;
|
||||
import entralinked.model.pkmn.PkmnGender;
|
||||
@@ -18,6 +31,7 @@ import entralinked.model.player.PlayerManager;
|
||||
import entralinked.model.player.PlayerStatus;
|
||||
import entralinked.network.http.HttpHandler;
|
||||
import entralinked.utility.GsidUtility;
|
||||
import entralinked.utility.TiledImageReader;
|
||||
import io.javalin.Javalin;
|
||||
import io.javalin.config.JavalinConfig;
|
||||
import io.javalin.http.Context;
|
||||
@@ -30,20 +44,41 @@ import io.javalin.json.JavalinJackson;
|
||||
*/
|
||||
public class DashboardHandler implements HttpHandler {
|
||||
|
||||
private static final Logger logger = LogManager.getLogger();
|
||||
private final Set<Integer> availableBlackAndWhiteSpecies = Set.of(
|
||||
505, 507, 510, 511, 513, 515, 519, 523, 525, 527, 529, 531, 533, 535, 538, 539, 542, 545, 546, 548,
|
||||
550, 553, 556, 558, 559, 561, 564, 569, 572, 575, 578, 580, 583, 587, 588, 594, 596, 600, 605, 607,
|
||||
610, 613, 616, 618, 619, 621, 622, 624, 626, 628, 630, 631, 632);
|
||||
private final Map<String, BufferedImage> skinPreviewCache = new HashMap<>();
|
||||
private final DlcList dlcList;
|
||||
private final PlayerManager playerManager;
|
||||
|
||||
public DashboardHandler(Entralinked entralinked) {
|
||||
this.dlcList = entralinked.getDlcList();
|
||||
this.playerManager = entralinked.getPlayerManager();
|
||||
|
||||
// Load & cache skin previews
|
||||
logger.info("Loading C-Gear and Pokédex skin previews ...");
|
||||
List<Dlc> skins = dlcList.getDlcList(dlc -> dlc.type().startsWith("CGEAR") || dlc.type().equals("ZUKAN"));
|
||||
|
||||
for(Dlc skin : skins) {
|
||||
try(FileInputStream inputStream = new FileInputStream(skin.path())) {
|
||||
BufferedImage image =
|
||||
skin.type().equals("ZUKAN") ? TiledImageReader.readDexSkin(inputStream) :
|
||||
skin.type().equals("CGEAR") ? TiledImageReader.readCGearSkin(inputStream, true) :
|
||||
TiledImageReader.readCGearSkin(inputStream, false); // CGEAR2
|
||||
skinPreviewCache.put(skin.name(), image);
|
||||
} catch(IOException | IndexOutOfBoundsException e) {
|
||||
logger.error("Could not load image for skin {} of type {}", skin.name(), skin.type(), e);
|
||||
}
|
||||
}
|
||||
|
||||
logger.info("Cached {} skin previews", skinPreviewCache.size());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addHandlers(Javalin javalin) {
|
||||
javalin.get("/dashboard/previewskin", this::handlePreviewSkin);
|
||||
javalin.get("/dashboard/dlc", this::handleRetrieveDlcList);
|
||||
javalin.get("/dashboard/profile", this::handleRetrieveProfile);
|
||||
javalin.post("/dashboard/profile", this::handleUpdateProfile);
|
||||
@@ -71,6 +106,22 @@ public class DashboardHandler implements HttpHandler {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* GET request handler for {@code /dashboard/previewskin}
|
||||
*/
|
||||
private void handlePreviewSkin(Context ctx) throws IOException {
|
||||
// Make sure that the name is present and exists
|
||||
String name = ctx.queryParam("name");
|
||||
|
||||
if(name == null || !skinPreviewCache.containsKey(name)) {
|
||||
ctx.status(404);
|
||||
return;
|
||||
}
|
||||
|
||||
// Write cached image data
|
||||
ImageIO.write(skinPreviewCache.get(name), "png", ctx.outputStream());
|
||||
}
|
||||
|
||||
/**
|
||||
* GET request handler for {@code /dashboard/dlc}
|
||||
*/
|
||||
@@ -84,7 +135,7 @@ public class DashboardHandler implements HttpHandler {
|
||||
}
|
||||
|
||||
// Send result
|
||||
ctx.json(dlcList.getDlcList("IRAO", type).stream().map(Dlc::name).collect(Collectors.toList()));
|
||||
ctx.json(dlcList.getDlcList("IRAO", type).stream().map(Dlc::name).sorted().collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -170,6 +221,7 @@ public class DashboardHandler implements HttpHandler {
|
||||
player.setStatus(PlayerStatus.WAKE_READY);
|
||||
player.setEncounters(request.encounters());
|
||||
player.setItems(request.items());
|
||||
player.setAvenueVisitors(request.avenueVisitors());
|
||||
player.setCGearSkin(request.cgearSkin().equals("none") ? null : request.cgearSkin());
|
||||
player.setDexSkin(request.dexSkin().equals("none") ? null : request.dexSkin());
|
||||
player.setMusical(request.musical().equals("none") ? null : request.musical());
|
||||
@@ -204,7 +256,7 @@ public class DashboardHandler implements HttpHandler {
|
||||
} else if(!availableBlackAndWhiteSpecies.contains(encounter.species())) {
|
||||
return "You have selected one or more Pokémon species that cannot be downloaded.";
|
||||
}
|
||||
} else if(encounter.move() < 1 || encounter.move() > 559) {
|
||||
} else if(encounter.move() < 0 || encounter.move() > 559) {
|
||||
return "Move ID is out of range.";
|
||||
} else if(encounter.gender() == null) {
|
||||
return "Gender is undefined.";
|
||||
@@ -232,6 +284,31 @@ public class DashboardHandler implements HttpHandler {
|
||||
}
|
||||
}
|
||||
|
||||
// Validate Join Avenue visitors
|
||||
Set<String> avenueVisitorNames = new HashSet<>(); // For duplicate checking
|
||||
|
||||
if(request.avenueVisitors().size() > 12) {
|
||||
return "Join Avenue visitor list size exceeds the limit.";
|
||||
}
|
||||
|
||||
for(AvenueVisitor visitor : request.avenueVisitors()) {
|
||||
if(visitor.type() == null) {
|
||||
return "Join Avenue visitor type is undefined.";
|
||||
} else if(visitor.shopType() == null) {
|
||||
return "Join Avenue visitor shop type is undefined.";
|
||||
} else if(visitor.name().isBlank() || visitor.name().length() > 7) {
|
||||
return "Join Avenue visitor name must be between 1 and 7 characters in length.";
|
||||
} else if(avenueVisitorNames.contains(visitor.name())) {
|
||||
return "Join Avenue visitors cannot have the same name!";
|
||||
} else if(visitor.gameVersion() == null) {
|
||||
return "Join Avenue visitor game version is undefined.";
|
||||
} else if(visitor.dreamerSpecies() < 1 || visitor.dreamerSpecies() > 649) {
|
||||
return "Tucked-in Pokémon species of the Join Avenue visitor is out of range.";
|
||||
}
|
||||
|
||||
avenueVisitorNames.add(visitor.name());
|
||||
}
|
||||
|
||||
// Validate gained levels
|
||||
if(request.gainedLevels() < 0 || request.gainedLevels() > 99) {
|
||||
return "Gained levels is out of range.";
|
||||
|
||||
@@ -2,6 +2,7 @@ package entralinked.network.http.dashboard;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import entralinked.model.avenue.AvenueVisitor;
|
||||
import entralinked.model.pkmn.PkmnInfo;
|
||||
import entralinked.model.player.DreamEncounter;
|
||||
import entralinked.model.player.DreamItem;
|
||||
@@ -16,10 +17,12 @@ public record DashboardProfileMessage(
|
||||
String musical,
|
||||
int levelsGained,
|
||||
Collection<DreamEncounter> encounters,
|
||||
Collection<DreamItem> items) {
|
||||
Collection<DreamItem> items,
|
||||
Collection<AvenueVisitor> avenueVisitors) {
|
||||
|
||||
public DashboardProfileMessage(String dreamerSprite, Player player) {
|
||||
this(player.getGameVersion().getDisplayName(), dreamerSprite, player.getDreamerInfo(), player.getCGearSkin(),
|
||||
player.getDexSkin(), player.getMusical(), player.getLevelsGained(), player.getEncounters(), player.getItems());
|
||||
this(player.getGameVersion().getDisplayName(), dreamerSprite, player.getDreamerInfo(),
|
||||
player.getCGearSkin(), player.getDexSkin(), player.getMusical(), player.getLevelsGained(),
|
||||
player.getEncounters(), player.getItems(), player.getAvenueVisitors());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,12 +5,14 @@ import java.util.List;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||
|
||||
import entralinked.model.avenue.AvenueVisitor;
|
||||
import entralinked.model.player.DreamEncounter;
|
||||
import entralinked.model.player.DreamItem;
|
||||
|
||||
public record DashboardProfileUpdateRequest(
|
||||
@JsonProperty(required = true) @JsonDeserialize(contentAs = DreamEncounter.class) List<DreamEncounter> encounters,
|
||||
@JsonProperty(required = true) @JsonDeserialize(contentAs = DreamItem.class) List<DreamItem> items,
|
||||
@JsonProperty(required = true) @JsonDeserialize(contentAs = AvenueVisitor.class) List<AvenueVisitor> avenueVisitors,
|
||||
@JsonProperty(required = true) String cgearSkin,
|
||||
@JsonProperty(required = true) String dexSkin,
|
||||
@JsonProperty(required = true) String musical,
|
||||
|
||||
@@ -67,6 +67,11 @@ public class DlsHandler implements HttpHandler {
|
||||
* POST handler for {@code /download action=list}
|
||||
*/
|
||||
private void handleRetrieveDlcList(DlsRequest request, Context ctx) throws IOException {
|
||||
String gameCode = switch(request.dlcGameCode()) {
|
||||
case "IRAJ" -> "IRAO";
|
||||
default -> request.dlcGameCode();
|
||||
};
|
||||
|
||||
// Map to generic type, I doubt there is a real difference between the language codes anyway.
|
||||
String type = switch(request.dlcType()) {
|
||||
case "CGEAR_E", "CGEAR_F", "CGEAR_I", "CGEAR_G", "CGEAR_S", "CGEAR_J", "CGEAR_K" -> "CGEAR";
|
||||
@@ -77,7 +82,7 @@ public class DlsHandler implements HttpHandler {
|
||||
};
|
||||
|
||||
// TODO NOTE: I assume that in a conventional implementation, certain DLC attributes may be omitted from the request.
|
||||
ctx.result(dlcList.getDlcListString(dlcList.getDlcList(request.dlcGameCode(), type, request.dlcIndex())));
|
||||
ctx.result(dlcList.getDlcListString(dlcList.getDlcList(gameCode, type, request.dlcIndex())));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,6 +3,7 @@ package entralinked.network.http.pgl;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
@@ -14,9 +15,11 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import entralinked.Configuration;
|
||||
import entralinked.Entralinked;
|
||||
import entralinked.model.avenue.AvenueVisitor;
|
||||
import entralinked.model.dlc.DlcList;
|
||||
import entralinked.model.pkmn.PkmnInfo;
|
||||
import entralinked.model.pkmn.PkmnInfoReader;
|
||||
import entralinked.model.player.DreamDecor;
|
||||
import entralinked.model.player.DreamEncounter;
|
||||
import entralinked.model.player.DreamItem;
|
||||
import entralinked.model.player.Player;
|
||||
@@ -28,6 +31,7 @@ import entralinked.network.http.HttpHandler;
|
||||
import entralinked.network.http.HttpRequestHandler;
|
||||
import entralinked.serialization.UrlEncodedFormFactory;
|
||||
import entralinked.serialization.UrlEncodedFormParser;
|
||||
import entralinked.utility.GsidUtility;
|
||||
import entralinked.utility.LEOutputStream;
|
||||
import io.javalin.Javalin;
|
||||
import io.javalin.http.Context;
|
||||
@@ -45,6 +49,12 @@ public class PglHandler implements HttpHandler {
|
||||
private static final String password = "2Phfv9MY"; // Best security in the world
|
||||
private final ObjectMapper mapper = new ObjectMapper(new UrlEncodedFormFactory()
|
||||
.disable(UrlEncodedFormParser.Feature.BASE64_DECODE_VALUES));
|
||||
private final List<DreamDecor> decorList = List.of(
|
||||
new DreamDecor(1, "+----------+"),
|
||||
new DreamDecor(2, "Thank you"),
|
||||
new DreamDecor(3, "for using"),
|
||||
new DreamDecor(4, "Entralinked!"),
|
||||
new DreamDecor(5, "+----------+"));
|
||||
private final Set<Integer> sleepyList = new HashSet<>();
|
||||
private final Configuration configuration;
|
||||
private final DlcList dlcList;
|
||||
@@ -89,13 +99,6 @@ public class PglHandler implements HttpHandler {
|
||||
// Deserialize the request
|
||||
PglRequest request = mapper.readValue(ctx.queryString(), PglRequest.class);
|
||||
|
||||
// Check game version
|
||||
if(request.gameVersion() == null) {
|
||||
ctx.status(HttpStatus.BAD_REQUEST);
|
||||
clearTasks(ctx);
|
||||
return;
|
||||
}
|
||||
|
||||
// Verify the service session token
|
||||
ServiceSession session = userManager.getServiceSession(request.token(), "external");
|
||||
|
||||
@@ -228,7 +231,7 @@ public class PglHandler implements HttpHandler {
|
||||
outputStream.write(dlcList.getDlcIndex(player.getMusical()));
|
||||
outputStream.write(dlcList.getDlcIndex(player.getCGearSkin()));
|
||||
outputStream.write(dlcList.getDlcIndex(player.getDexSkin()));
|
||||
outputStream.write(0); // Unknown
|
||||
outputStream.write(decorList.isEmpty() ? 0 : 1); // Seems to be a flag for indicating whether or not decor data is present
|
||||
outputStream.write(0); // Must be zero?
|
||||
|
||||
// Write item IDs
|
||||
@@ -246,6 +249,61 @@ public class PglHandler implements HttpHandler {
|
||||
|
||||
// Write quantity padding
|
||||
outputStream.writeBytes(0, (20 - items.size()));
|
||||
|
||||
// Decor data -- copied to 0x1D420 in the save file
|
||||
// Need to send 5 entries or nothing will happen
|
||||
// After decor is selected in Nacrene City, the *index* of it (default: 0x7F) will be saved to 0x1D4A6 in the save file.
|
||||
for(DreamDecor decor : decorList) {
|
||||
byte[] nameBytes = decor.name().getBytes(StandardCharsets.UTF_16LE);
|
||||
|
||||
// If any ID is 0x7E it will not work. It also appears as the default in the save file.
|
||||
outputStream.writeShort(decor.id());
|
||||
|
||||
// Name can't have more than 12 characters
|
||||
outputStream.write(nameBytes, 0, Math.min(24, nameBytes.length));
|
||||
outputStream.writeBytes(-1, 24 - nameBytes.length);
|
||||
}
|
||||
|
||||
// Write decor padding
|
||||
outputStream.writeBytes(0, (5 - decorList.size()) * 26);
|
||||
outputStream.writeShort(0); // ?
|
||||
|
||||
// Join Avenue visitor data -- copied in parts to 0x2422C in the save file.
|
||||
// Black Version 2 and White Version 2 only.
|
||||
if(player.getGameVersion().isVersion2()) {
|
||||
List<AvenueVisitor> avenueVisitors = player.getAvenueVisitors();
|
||||
|
||||
for(AvenueVisitor visitor : avenueVisitors) {
|
||||
// Write visitor name + padding. Names cannot be duplicate.
|
||||
byte[] nameBytes = visitor.name().getBytes(StandardCharsets.UTF_16LE);
|
||||
outputStream.write(nameBytes, 0, Math.min(14, nameBytes.length));
|
||||
outputStream.writeBytes(-1, 14 - nameBytes.length);
|
||||
|
||||
// Full visitor type consists of a trainer class and what I call a 'personality' index
|
||||
// that, along with the trainer class, determines which phrases the visitor uses.
|
||||
// The shope type is calculated in such an odd manner because for some reason,
|
||||
// the 'starting' index of the shop type used increases by 2 for each visitor type.
|
||||
// For example, if the visitor type is '0', then shop type '0' would be a raffle.
|
||||
// However, if the visitor type is '2', then shop type '0' results in a dojo instead.
|
||||
int visitorType = visitor.type().getClientId() + visitor.personality() * 8;
|
||||
outputStream.writeShort(-1); // Does nothing, seems to be read as part of the name.
|
||||
outputStream.write(visitorType);
|
||||
outputStream.write(visitor.shopType().ordinal() + (7 - visitorType * 2 % 7));
|
||||
outputStream.writeShort(0); // Does nothing
|
||||
outputStream.writeInt(1); // [20] Ignores if 0
|
||||
outputStream.write(visitor.countryCode());
|
||||
outputStream.write(visitor.stateProvinceCode());
|
||||
outputStream.write(0); // [26] Ignores if 1
|
||||
outputStream.write(visitor.gameVersion().getRomCode()); // Affects shop stock
|
||||
outputStream.write(visitor.type().isFemale() ? 1 : 0);
|
||||
outputStream.write(0); // [29] Does.. something
|
||||
outputStream.writeShort(visitor.dreamerSpecies());
|
||||
}
|
||||
|
||||
// Write visitor padding
|
||||
outputStream.writeBytes(0, (12 - avenueVisitors.size()) * 32);
|
||||
outputStream.writeInt(0); // 672 is the total -- there shouldn't be anything left after this. Hooray!
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -294,6 +352,7 @@ public class PglHandler implements HttpHandler {
|
||||
case "savedata.upload" -> this::handleUploadSaveData;
|
||||
case "savedata.download.finish" -> this::handleDownloadSaveDataFinish;
|
||||
case "account.create.upload" -> this::handleCreateAccount;
|
||||
case "account.createdata" -> this::handleCreateData;
|
||||
default -> throw new IllegalArgumentException("Invalid POST request type: " + request.type());
|
||||
};
|
||||
|
||||
@@ -335,11 +394,10 @@ public class PglHandler implements HttpHandler {
|
||||
// Prepare response
|
||||
LEOutputStream outputStream = new LEOutputStream(ctx.outputStream());
|
||||
|
||||
// Check if player exists, has the same game version and does not already have a Pokémon tucked in
|
||||
// Check if the player exists and does not already have a Pokémon tucked in
|
||||
Player player = playerManager.getPlayer(request.gameSyncId());
|
||||
|
||||
if(player == null || player.getGameVersion() != request.gameVersion()
|
||||
|| (player.getStatus() != PlayerStatus.AWAKE && !configuration.allowOverwritingPlayerDreamInfo())) {
|
||||
if(player == null || (player.getStatus() != PlayerStatus.AWAKE && !configuration.allowOverwritingPlayerDreamInfo())) {
|
||||
// Skip everything
|
||||
ServletInputStream inputStream = ctx.req().getInputStream();
|
||||
|
||||
@@ -369,6 +427,7 @@ public class PglHandler implements HttpHandler {
|
||||
|
||||
// Update and save player information
|
||||
player.setStatus(PlayerStatus.SLEEPING);
|
||||
player.setGameVersion(request.gameVersion());
|
||||
player.setDreamerInfo(dreamerInfo);
|
||||
|
||||
if(!playerManager.savePlayer(player)) {
|
||||
@@ -394,7 +453,37 @@ public class PglHandler implements HttpHandler {
|
||||
|
||||
// Prepare response
|
||||
LEOutputStream outputStream = new LEOutputStream(ctx.outputStream());
|
||||
String gameSyncId = request.gameSyncId();
|
||||
|
||||
// Make sure Game Sync ID is present
|
||||
if(request.gameSyncId() == null) {
|
||||
writeStatusCode(outputStream, 1); // Unauthorized
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if player doesn't exist already
|
||||
if(playerManager.doesPlayerExist(request.gameSyncId())) {
|
||||
writeStatusCode(outputStream, 2); // Duplicate Game Sync ID
|
||||
return;
|
||||
}
|
||||
|
||||
// Try to register player
|
||||
if(playerManager.registerPlayer(request.gameSyncId()) == null) {
|
||||
writeStatusCode(outputStream, 3); // Registration error
|
||||
return;
|
||||
}
|
||||
|
||||
// Write status code
|
||||
writeStatusCode(outputStream, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* POST handler for {@code /dsio/gw?p=account.createdata}
|
||||
*
|
||||
* Seems to be a funny Japanese version quirk
|
||||
*/
|
||||
private void handleCreateData(PglRequest request, Context ctx) throws IOException {
|
||||
LEOutputStream outputStream = new LEOutputStream(ctx.outputStream());
|
||||
String gameSyncId = GsidUtility.stringifyGameSyncId(Integer.parseInt(ctx.body().replace("\u0000", ""))); // So quirky
|
||||
|
||||
// Check if player doesn't exist already
|
||||
if(playerManager.doesPlayerExist(gameSyncId)) {
|
||||
@@ -403,7 +492,7 @@ public class PglHandler implements HttpHandler {
|
||||
}
|
||||
|
||||
// Try to register player
|
||||
if(playerManager.registerPlayer(gameSyncId, request.gameVersion()) == null) {
|
||||
if(playerManager.registerPlayer(gameSyncId) == null) {
|
||||
writeStatusCode(outputStream, 3); // Registration error
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -7,12 +7,12 @@ import entralinked.GameVersion;
|
||||
import entralinked.serialization.GsidDeserializer;
|
||||
|
||||
public record PglRequest(
|
||||
@JsonProperty(value = "gsid", required = true) @JsonDeserialize(using = GsidDeserializer.class) String gameSyncId,
|
||||
@JsonProperty(value = "p", required = true) String type,
|
||||
@JsonProperty(value = "rom", required = true) int romCode,
|
||||
@JsonProperty(value = "langcode", required = true) int languageCode,
|
||||
@JsonProperty(value = "dreamw", required = true) int dreamWorld, // Always 1, but what is it for?
|
||||
@JsonProperty(value = "tok", required = true) String token) {
|
||||
@JsonProperty(value = "gsid") @JsonDeserialize(using = GsidDeserializer.class) String gameSyncId,
|
||||
@JsonProperty(value = "p", required = true) String type,
|
||||
@JsonProperty(value = "tok", required = true) String token,
|
||||
@JsonProperty(value = "rom") int romCode,
|
||||
@JsonProperty(value = "langcode") int languageCode,
|
||||
@JsonProperty(value = "dreamw") int dreamWorld) { // Always 1, but what is it for?
|
||||
|
||||
public GameVersion gameVersion() {
|
||||
return GameVersion.lookup(romCode(), languageCode());
|
||||
|
||||
130
src/main/java/entralinked/utility/TiledImageReader.java
Normal file
130
src/main/java/entralinked/utility/TiledImageReader.java
Normal file
@@ -0,0 +1,130 @@
|
||||
package entralinked.utility;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* Utility class for reading tiled images (C-Gear & Pokédex skin data) into a usable {@link BufferedImage}.
|
||||
*/
|
||||
public class TiledImageReader {
|
||||
|
||||
public static final int TILE_WIDTH = 8;
|
||||
public static final int TILE_HEIGHT = 8;
|
||||
public static final int TILE_SIZE = TILE_WIDTH * TILE_HEIGHT;
|
||||
public static final int COLOR_PALETTE_SIZE = 16;
|
||||
public static final int SCREEN_WIDTH = 256;
|
||||
public static final int SCREEN_HEIGHT = 192;
|
||||
public static final int SCREEN_TILE_COUNT = SCREEN_WIDTH * SCREEN_HEIGHT / TILE_SIZE;
|
||||
|
||||
/**
|
||||
* Calls {@link #readTiledImage(InputStream, int, boolean)} with a tile count of 255.
|
||||
*
|
||||
* @param normalizeIndices Should be {@code true} if the provided C-Gear skin data is from the original Black & White.
|
||||
* @return A {@link BufferedImage} representing the read C-Gear skin data.
|
||||
*/
|
||||
public static BufferedImage readCGearSkin(InputStream inputStream, boolean normalizeIndices) throws IOException {
|
||||
return readTiledImage(inputStream, 255, normalizeIndices);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls {@link #readTiledImage(InputStream, int, boolean)} with a tile count of 768 and index normalization disabled.
|
||||
*
|
||||
* @return A {@link BufferedImage} representing the read Pokédex skin data.
|
||||
*/
|
||||
public static BufferedImage readDexSkin(InputStream inputStream) throws IOException {
|
||||
return readTiledImage(inputStream, 768, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads tiled image data from the provided {@link InputStream} and returns a {@link BufferedImage} representing the read image data.
|
||||
*
|
||||
* @param tileCount The number of tiles to be read. This should be equal to the maximum number of tiles for this image.
|
||||
* @param normalizedIndices Indicates that tile indices are not linear (Black & White C-Gear skins) and should be normalized.
|
||||
* @return A {@link BufferedImage} representing the read image data.
|
||||
*/
|
||||
public static BufferedImage readTiledImage(InputStream inputStream, int tileCount, boolean normalizeIndices) throws IOException {
|
||||
BufferedImage image = new BufferedImage(SCREEN_WIDTH, SCREEN_HEIGHT, BufferedImage.TYPE_INT_RGB); // Result image
|
||||
int[] tileData = new int[tileCount * TILE_SIZE];
|
||||
int[] tileIndices = new int[tileCount]; // Tile index lookup table
|
||||
int[] colorPalette = new int[COLOR_PALETTE_SIZE];
|
||||
|
||||
// Read tile data.
|
||||
for(int i = 0; i < tileCount; i++) {
|
||||
for(int j = 0; j < TILE_SIZE / 2; j++) {
|
||||
int paletteIndices = inputStream.read(); // Contains color palette indices for 2 adjacent pixels.
|
||||
tileData[i * TILE_SIZE + j * 2] = paletteIndices & (COLOR_PALETTE_SIZE - 1);
|
||||
tileData[i * TILE_SIZE + j * 2 + 1] = (paletteIndices >> 4) & (COLOR_PALETTE_SIZE - 1);
|
||||
}
|
||||
|
||||
// Store the index of the tile as it would be in memory so we can look it up later when we're mapping the tiles.
|
||||
tileIndices[i] = normalizeIndices ? i + i / 17 * 15 + 0xA0A0 : i;
|
||||
}
|
||||
|
||||
// Read color data.
|
||||
// Pokédex skins contain room for 240 extra colors, 64 of which are defined and appear to be used as the
|
||||
// 'background' colors in cases where the skin is only an overlay that is displayed on top of the 'true' Pokédex.
|
||||
for(int i = 0; i < COLOR_PALETTE_SIZE; i++) {
|
||||
int color = inputStream.read() | inputStream.read() << 8;
|
||||
|
||||
// Convert BGR555 to RGB888
|
||||
int red = (color & 0x1F) << 3;
|
||||
int green = ((color & 0x3E0) >> 5) << 3;
|
||||
int blue = ((color & 0x7C00) >> 10) << 3;
|
||||
colorPalette[i] = (red << 16) | (green << 8) | blue;
|
||||
}
|
||||
|
||||
// Map tiles to the resulting image.
|
||||
// In cases where the tile count is 768 or greater, which is exactly enough tiles to fill the entire screen,
|
||||
// the tiles will be applied in the order they are provided and no additional mapping data will be read.
|
||||
// This is always the case for Pokédex skins, and never the case for C-Gear skins.
|
||||
if(tileCount < SCREEN_TILE_COUNT) {
|
||||
// Not enough tiles -- read additional mapping data to figure out their placement.
|
||||
for(int i = 0; i < SCREEN_TILE_COUNT; i++) {
|
||||
int x = i * TILE_WIDTH % SCREEN_WIDTH;
|
||||
int y = i * TILE_WIDTH / SCREEN_WIDTH * TILE_HEIGHT;
|
||||
int leftBits = inputStream.read();
|
||||
int rightBits = inputStream.read();
|
||||
int memoryIndex = leftBits | (rightBits & ~12) << 8;
|
||||
int flipBits = rightBits & 12;
|
||||
|
||||
// The normalized tile index is the index of the in-memory tile index in the lookup table.
|
||||
int tileIndex = 0;
|
||||
|
||||
for(int k = 0; k < tileIndices.length; k++) {
|
||||
if(memoryIndex == tileIndices[k]) {
|
||||
tileIndex = k;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Apply the pixels of this tile to the resulting image.
|
||||
for(int j = 0; j < TILE_SIZE; j++) {
|
||||
// Get the color index for this pixel based on how the tile is flipped.
|
||||
int tilePixelIndex = switch(flipBits) {
|
||||
case 4 -> (TILE_WIDTH * (j / TILE_WIDTH) + TILE_WIDTH) - j % TILE_WIDTH - 1; // Flip horizontally
|
||||
case 8 -> TILE_SIZE - (TILE_WIDTH * (j / TILE_WIDTH) + TILE_WIDTH) + j % TILE_WIDTH; // Flip vertically
|
||||
case 12 -> TILE_SIZE - j - 1; // Flip horizontally & vertically
|
||||
default -> j; // Don't flip
|
||||
};
|
||||
|
||||
// Finally, set the pixel!
|
||||
int paletteIndex = tileData[tileIndex * TILE_SIZE + tilePixelIndex];
|
||||
image.setRGB(x + j % TILE_WIDTH, y + j / TILE_WIDTH, colorPalette[paletteIndex]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// There are enough tiles to fill up the entire screen, so let's just place them in order.
|
||||
for(int i = 0; i < SCREEN_TILE_COUNT; i++) {
|
||||
int x = i * TILE_WIDTH % SCREEN_WIDTH;
|
||||
int y = i * TILE_WIDTH / SCREEN_WIDTH * TILE_WIDTH;
|
||||
|
||||
for(int j = 0; j < TILE_SIZE; j++) {
|
||||
image.setRGB(x + j % TILE_WIDTH, y + j / TILE_WIDTH, colorPalette[tileData[i * TILE_SIZE + j]]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return image;
|
||||
}
|
||||
}
|
||||
@@ -5,10 +5,12 @@
|
||||
</head>
|
||||
<body>
|
||||
<div class="root-container">
|
||||
<label for="gsid">Game Sync ID</label><br>
|
||||
<input type='text' id="gsid" name='gsid' placeholder='XXXXXXXXXX'>
|
||||
<button id="login" onclick="postLogin()">Login</button>
|
||||
</div>
|
||||
<div>
|
||||
<label for="gsid">Game Sync ID</label><br>
|
||||
<input type='text' id="gsid" name='gsid' placeholder='XXXXXXXXXX'>
|
||||
<button id="login" onclick="postLogin()">Login</button>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
<script src="scripts/login.js"></script>
|
||||
</html>
|
||||
|
||||
@@ -6,127 +6,153 @@
|
||||
<body onload="fetchProfileData()">
|
||||
<div id="main-container" class="root-container" style="display:none;">
|
||||
<div>
|
||||
<label id="game-summary" class="header-text"></label>
|
||||
</div>
|
||||
<div>
|
||||
<!-- Dreamer Summary -->
|
||||
<label>Tucked-in Pokémon Summary</label><br>
|
||||
<table id="dreamer-summary" class="dreamer-summary">
|
||||
<tr>
|
||||
<td id="dreamer-sprite" class="dreamer-sprite" rowspan="5">
|
||||
<image src="/sprites/pokemon/normal/0.png"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Species</th>
|
||||
<td id="dreamer-species"></td>
|
||||
<th>Nature</th>
|
||||
<td id="dreamer-nature"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<td id="dreamer-name"></td>
|
||||
<th>Gender</th>
|
||||
<td id="dreamer-gender"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Trainer</th>
|
||||
<td id="dreamer-trainer"></td>
|
||||
<th>Level</th>
|
||||
<td id="dreamer-level"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Trainer ID</th>
|
||||
<td id="dreamer-trainer-id"></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div>
|
||||
<!-- Entree Forest Encounter Configuration -->
|
||||
<label>Entree Forest Encounters (Max. 10)</label><br>
|
||||
<div>
|
||||
<table id="encounter-table" class="encounter-image-table">
|
||||
<label id="game-summary" class="header-text"></label>
|
||||
</div>
|
||||
<div>
|
||||
<!-- Dreamer Summary -->
|
||||
<label>Tucked-in Pokémon Summary</label><br>
|
||||
<table id="dreamer-summary" class="dreamer-summary">
|
||||
<tr>
|
||||
<td><a id="encounter0" href="#configureEncounter" onclick="configureEncounter(0)"><image src="/sprites/pokemon/normal/0.png"/></a></td>
|
||||
<td><a id="encounter1" href="#configureEncounter" onclick="configureEncounter(1)"><image src="/sprites/pokemon/normal/0.png"/></a></td>
|
||||
<td><a id="encounter2" href="#configureEncounter" onclick="configureEncounter(2)"><image src="/sprites/pokemon/normal/0.png"/></a></td>
|
||||
<td><a id="encounter3" href="#configureEncounter" onclick="configureEncounter(3)"><image src="/sprites/pokemon/normal/0.png"/></a></td>
|
||||
<td><a id="encounter4" href="#configureEncounter" onclick="configureEncounter(4)"><image src="/sprites/pokemon/normal/0.png"/></a></td>
|
||||
<td id="dreamer-sprite" class="dreamer-sprite" rowspan="5">
|
||||
<image src="/sprites/pokemon/normal/0.png"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a id="encounter5" href="#configureEncounter" onclick="configureEncounter(5)"><image src="/sprites/pokemon/normal/0.png"/></a></td>
|
||||
<td><a id="encounter6" href="#configureEncounter" onclick="configureEncounter(6)"><image src="/sprites/pokemon/normal/0.png"/></a></td>
|
||||
<td><a id="encounter7" href="#configureEncounter" onclick="configureEncounter(7)"><image src="/sprites/pokemon/normal/0.png"/></a></td>
|
||||
<td><a id="encounter8" href="#configureEncounter" onclick="configureEncounter(8)"><image src="/sprites/pokemon/normal/0.png"/></a></td>
|
||||
<td><a id="encounter9" href="#configureEncounter" onclick="configureEncounter(9)"><image src="/sprites/pokemon/normal/0.png"/></a></td>
|
||||
<th>Species</th>
|
||||
<td id="dreamer-species"></td>
|
||||
<th>Nature</th>
|
||||
<td id="dreamer-nature"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<td id="dreamer-name"></td>
|
||||
<th>Gender</th>
|
||||
<td id="dreamer-gender"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Trainer</th>
|
||||
<td id="dreamer-trainer"></td>
|
||||
<th>Level</th>
|
||||
<td id="dreamer-level"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Trainer ID</th>
|
||||
<td id="dreamer-trainer-id"></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<!-- Item Configuration -->
|
||||
<label>Items (Max. 20)</label><br>
|
||||
<div>
|
||||
<table id="item-table" class="item-table">
|
||||
<tr>
|
||||
<td><a id="item0" href="#configureItem" onclick="configureItem(0)"><image src="/sprites/items/0.png"/></a></td>
|
||||
<td><a id="item1" href="#configureItem" onclick="configureItem(1)"><image src="/sprites/items/0.png"/></a></td>
|
||||
<td><a id="item2" href="#configureItem" onclick="configureItem(2)"><image src="/sprites/items/0.png"/></a></td>
|
||||
<td><a id="item3" href="#configureItem" onclick="configureItem(3)"><image src="/sprites/items/0.png"/></a></td>
|
||||
<td><a id="item4" href="#configureItem" onclick="configureItem(4)"><image src="/sprites/items/0.png"/></a></td>
|
||||
<td><a id="item5" href="#configureItem" onclick="configureItem(5)"><image src="/sprites/items/0.png"/></a></td>
|
||||
<td><a id="item6" href="#configureItem" onclick="configureItem(6)"><image src="/sprites/items/0.png"/></a></td>
|
||||
<td><a id="item7" href="#configureItem" onclick="configureItem(7)"><image src="/sprites/items/0.png"/></a></td>
|
||||
<td><a id="item8" href="#configureItem" onclick="configureItem(8)"><image src="/sprites/items/0.png"/></a></td>
|
||||
<td><a id="item9" href="#configureItem" onclick="configureItem(9)"><image src="/sprites/items/0.png"/></a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a id="item10" href="#configureItem" onclick="configureItem(10)"><image src="/sprites/items/0.png"/></a></td>
|
||||
<td><a id="item11" href="#configureItem" onclick="configureItem(11)"><image src="/sprites/items/0.png"/></a></td>
|
||||
<td><a id="item12" href="#configureItem" onclick="configureItem(12)"><image src="/sprites/items/0.png"/></a></td>
|
||||
<td><a id="item13" href="#configureItem" onclick="configureItem(13)"><image src="/sprites/items/0.png"/></a></td>
|
||||
<td><a id="item14" href="#configureItem" onclick="configureItem(14)"><image src="/sprites/items/0.png"/></a></td>
|
||||
<td><a id="item15" href="#configureItem" onclick="configureItem(15)"><image src="/sprites/items/0.png"/></a></td>
|
||||
<td><a id="item16" href="#configureItem" onclick="configureItem(16)"><image src="/sprites/items/0.png"/></a></td>
|
||||
<td><a id="item17" href="#configureItem" onclick="configureItem(17)"><image src="/sprites/items/0.png"/></a></td>
|
||||
<td><a id="item18" href="#configureItem" onclick="configureItem(18)"><image src="/sprites/items/0.png"/></a></td>
|
||||
<td><a id="item19" href="#configureItem" onclick="configureItem(19)"><image src="/sprites/items/0.png"/></a></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<!-- Misc Configurations -->
|
||||
<div class="grid-container">
|
||||
<!-- Entree Forest Encounter Configuration -->
|
||||
<label>Entree Forest Encounters (Max. 10)</label><br>
|
||||
<div>
|
||||
<label>CGear Skin</label>
|
||||
<select id="cgear-skin">
|
||||
<option value="none">Do not change</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label>Pokédex Skin</label>
|
||||
<select id="dex-skin">
|
||||
<option value="none">Do not change</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label>Musical</label>
|
||||
<select id="musical">
|
||||
<option value="none">Do not change</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label>Level Gain</label>
|
||||
<input id="level-gain-input" type="number" value="0" min="0" max="99"/>
|
||||
<table id="encounter-table" class="encounter-image-table">
|
||||
<tr>
|
||||
<td><a id="encounter0" href="#configureEncounter" onclick="configureEncounter(0)"><image src="/sprites/pokemon/normal/0.png"/></a></td>
|
||||
<td><a id="encounter1" href="#configureEncounter" onclick="configureEncounter(1)"><image src="/sprites/pokemon/normal/0.png"/></a></td>
|
||||
<td><a id="encounter2" href="#configureEncounter" onclick="configureEncounter(2)"><image src="/sprites/pokemon/normal/0.png"/></a></td>
|
||||
<td><a id="encounter3" href="#configureEncounter" onclick="configureEncounter(3)"><image src="/sprites/pokemon/normal/0.png"/></a></td>
|
||||
<td><a id="encounter4" href="#configureEncounter" onclick="configureEncounter(4)"><image src="/sprites/pokemon/normal/0.png"/></a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a id="encounter5" href="#configureEncounter" onclick="configureEncounter(5)"><image src="/sprites/pokemon/normal/0.png"/></a></td>
|
||||
<td><a id="encounter6" href="#configureEncounter" onclick="configureEncounter(6)"><image src="/sprites/pokemon/normal/0.png"/></a></td>
|
||||
<td><a id="encounter7" href="#configureEncounter" onclick="configureEncounter(7)"><image src="/sprites/pokemon/normal/0.png"/></a></td>
|
||||
<td><a id="encounter8" href="#configureEncounter" onclick="configureEncounter(8)"><image src="/sprites/pokemon/normal/0.png"/></a></td>
|
||||
<td><a id="encounter9" href="#configureEncounter" onclick="configureEncounter(9)"><image src="/sprites/pokemon/normal/0.png"/></a></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div id="visitor-table-container" style="display:none;">
|
||||
<!-- Join Avenue Visitor Configuration -->
|
||||
<label>Join Avenue Visitors (Max. 12)</label><br>
|
||||
<div>
|
||||
<table id="visitor-table" class="visitor-table">
|
||||
<tr>
|
||||
<td><a id="visitor0" href="#configureVisitor" onclick="configureVisitor(0)"><image src="/sprites/trainers/none.png"/></a></td>
|
||||
<td><a id="visitor1" href="#configureVisitor" onclick="configureVisitor(1)"><image src="/sprites/trainers/none.png"/></a></td>
|
||||
<td><a id="visitor2" href="#configureVisitor" onclick="configureVisitor(2)"><image src="/sprites/trainers/none.png"/></a></td>
|
||||
<td><a id="visitor3" href="#configureVisitor" onclick="configureVisitor(3)"><image src="/sprites/trainers/none.png"/></a></td>
|
||||
<td><a id="visitor4" href="#configureVisitor" onclick="configureVisitor(4)"><image src="/sprites/trainers/none.png"/></a></td>
|
||||
<td><a id="visitor5" href="#configureVisitor" onclick="configureVisitor(5)"><image src="/sprites/trainers/none.png"/></a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a id="visitor6" href="#configureVisitor" onclick="configureVisitor(6)"><image src="/sprites/trainers/none.png"/></a></td>
|
||||
<td><a id="visitor7" href="#configureVisitor" onclick="configureVisitor(7)"><image src="/sprites/trainers/none.png"/></a></td>
|
||||
<td><a id="visitor8" href="#configureVisitor" onclick="configureVisitor(8)"><image src="/sprites/trainers/none.png"/></a></td>
|
||||
<td><a id="visitor9" href="#configureVisitor" onclick="configureVisitor(9)"><image src="/sprites/trainers/none.png"/></a></td>
|
||||
<td><a id="visitor10" href="#configureVisitor" onclick="configureVisitor(10)"><image src="/sprites/trainers/none.png"/></a></td>
|
||||
<td><a id="visitor11" href="#configureVisitor" onclick="configureVisitor(11)"><image src="/sprites/trainers/none.png"/></a></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<!-- Item Configuration -->
|
||||
<label>Items (Max. 20)</label><br>
|
||||
<div>
|
||||
<table id="item-table" class="item-table">
|
||||
<tr>
|
||||
<td><a id="item0" href="#configureItem" onclick="configureItem(0)"><image src="/sprites/items/0.png"/></a></td>
|
||||
<td><a id="item1" href="#configureItem" onclick="configureItem(1)"><image src="/sprites/items/0.png"/></a></td>
|
||||
<td><a id="item2" href="#configureItem" onclick="configureItem(2)"><image src="/sprites/items/0.png"/></a></td>
|
||||
<td><a id="item3" href="#configureItem" onclick="configureItem(3)"><image src="/sprites/items/0.png"/></a></td>
|
||||
<td><a id="item4" href="#configureItem" onclick="configureItem(4)"><image src="/sprites/items/0.png"/></a></td>
|
||||
<td><a id="item5" href="#configureItem" onclick="configureItem(5)"><image src="/sprites/items/0.png"/></a></td>
|
||||
<td><a id="item6" href="#configureItem" onclick="configureItem(6)"><image src="/sprites/items/0.png"/></a></td>
|
||||
<td><a id="item7" href="#configureItem" onclick="configureItem(7)"><image src="/sprites/items/0.png"/></a></td>
|
||||
<td><a id="item8" href="#configureItem" onclick="configureItem(8)"><image src="/sprites/items/0.png"/></a></td>
|
||||
<td><a id="item9" href="#configureItem" onclick="configureItem(9)"><image src="/sprites/items/0.png"/></a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a id="item10" href="#configureItem" onclick="configureItem(10)"><image src="/sprites/items/0.png"/></a></td>
|
||||
<td><a id="item11" href="#configureItem" onclick="configureItem(11)"><image src="/sprites/items/0.png"/></a></td>
|
||||
<td><a id="item12" href="#configureItem" onclick="configureItem(12)"><image src="/sprites/items/0.png"/></a></td>
|
||||
<td><a id="item13" href="#configureItem" onclick="configureItem(13)"><image src="/sprites/items/0.png"/></a></td>
|
||||
<td><a id="item14" href="#configureItem" onclick="configureItem(14)"><image src="/sprites/items/0.png"/></a></td>
|
||||
<td><a id="item15" href="#configureItem" onclick="configureItem(15)"><image src="/sprites/items/0.png"/></a></td>
|
||||
<td><a id="item16" href="#configureItem" onclick="configureItem(16)"><image src="/sprites/items/0.png"/></a></td>
|
||||
<td><a id="item17" href="#configureItem" onclick="configureItem(17)"><image src="/sprites/items/0.png"/></a></td>
|
||||
<td><a id="item18" href="#configureItem" onclick="configureItem(18)"><image src="/sprites/items/0.png"/></a></td>
|
||||
<td><a id="item19" href="#configureItem" onclick="configureItem(19)"><image src="/sprites/items/0.png"/></a></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<!-- Misc Configurations -->
|
||||
<div class="grid-container">
|
||||
<div>
|
||||
<label>C-Gear Skin | </label><a href="#" onclick="return previewSkin('cgear-skin')">Preview</a>
|
||||
<select id="cgear-skin">
|
||||
<option value="none">Do not change</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label>Pokédex Skin | </label><a href="#" onclick="return previewSkin('dex-skin')">Preview</a>
|
||||
<select id="dex-skin">
|
||||
<option value="none">Do not change</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label>Musical Show</label>
|
||||
<select id="musical">
|
||||
<option value="none">Do not change</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label>Level Gain</label>
|
||||
<input id="level-gain-input" type="number" value="0" min="0" max="99"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<button id="save" class="big-button" onclick="postProfileData()">Save Profile</button>
|
||||
<button id="logout" class="big-button" onclick="postLogout()">Log Out</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Entree Forest Encounter Configuration Form -->
|
||||
<div id="configureEncounter" class="popup">
|
||||
<div class="content">
|
||||
@@ -135,7 +161,7 @@
|
||||
<label for="encounter-form-species">Species ID | </label><a href="/dashboard/species.html" target="_blank">View list</a>
|
||||
<input id="encounter-form-species" name="species" type="number" value="1" min="1" max="493"/>
|
||||
<label for="encounter-form-move">Move ID</label>
|
||||
<input id="encounter-form-move" name="move" type="number" value="1" min="1" max="559"/>
|
||||
<input id="encounter-form-move" name="move" type="number" value="0" min="0" max="559"/>
|
||||
<label for="encounter-form-form">Forme Index</label>
|
||||
<input id="encounter-form-form" name="form" type="number" value="0" min="0" max="31"/>
|
||||
<label for="encounter-form-gender">Gender</label>
|
||||
@@ -174,6 +200,58 @@
|
||||
<button class="big-button" onclick="removeItem()">Remove</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Join Avenue Visitor Configuration Form -->
|
||||
<div id="configureVisitor" class="popup">
|
||||
<div class="content">
|
||||
<button class="close-button" onclick="closeVisitorForm()">X</button>
|
||||
<form id="visitor-form">
|
||||
<label for="visitor-form-name">Name (Max. 7 characters)</label>
|
||||
<input id="visitor-form-name" name="name" placeholder="Trainer" maxlength="7"/>
|
||||
<label for="visitor-form-type">Trainer Class</label>
|
||||
<select id="visitor-form-type" name="type" value="ACE_TRAINER_MALE">
|
||||
<option value="YOUNGSTER">Youngster</option>
|
||||
<option value="LASS">Lass</option>
|
||||
<option value="ACE_TRAINER_MALE">Ace Trainer (Male)</option>
|
||||
<option value="ACE_TRAINER_FEMALE">Ace Trainer (Female)</option>
|
||||
<option value="RANGER_MALE">Pokémon Ranger (Male)</option>
|
||||
<option value="RANGER_FEMALE">Pokémon Ranger (Female)</option>
|
||||
<option value="BREEDER_MALE">Pokémon Breeder (Male)</option>
|
||||
<option value="BREEDER_FEMALE">Pokémon Breeder (Female)</option>
|
||||
<option value="SCIENTIST_MALE">Scientist (Male)</option>
|
||||
<option value="SCIENTIST_FEMALE">Scientist (Female)</option>
|
||||
<option value="HIKER">Hiker</option>
|
||||
<option value="PARASOL_LADY">Parasol Lady</option>
|
||||
<option value="ROUGHNECK">Roughneck</option>
|
||||
<option value="NURSE">Nurse</option>
|
||||
<option value="PRESCHOOLER_MALE">Preschooler (Male)</option>
|
||||
<option value="PRESCHOOLER_FEMALE">Preschooler (Female)</option>
|
||||
</select>
|
||||
<label for="visitor-form-shop-type">Shop Type</label>
|
||||
<select id="visitor-form-shop-type" name="shopType" value="RAFFLE">
|
||||
<option value="RAFFLE">Raffle Shop</option>
|
||||
<option value="FLORIST">Flower Shop</option>
|
||||
<option value="SALON">Beauty Salon</option>
|
||||
<option value="ANTIQUE">Antique Shop</option>
|
||||
<option value="DOJO">Training Dojo</option>
|
||||
<option value="CAFE">Café</option>
|
||||
<option value="MARKET">Market</option>
|
||||
</select>
|
||||
<label for="visitor-form-game">Game of Origin (Affects which goods/services are sold)</label>
|
||||
<select id="visitor-form-game" name="gameVersion" value="BLACK_ENGLISH">
|
||||
<option value="BLACK_ENGLISH">Black Version</option>
|
||||
<option value="WHITE_ENGLISH">White Version</option>
|
||||
<option value="BLACK_2_ENGLISH">Black Version 2</option>
|
||||
<option value="WHITE_2_ENGLISH">White Version 2</option>
|
||||
</select>
|
||||
<label for="visitor-form-personality">Personality (Affects phrases used)</label>
|
||||
<input id="visitor-form-personality" name="personality" type="number" value="0" min="0" max="7"/>
|
||||
<label for="visitor-form-dreamer">Tucked-in Pokémon Species</label>
|
||||
<input id="visitor-form-dreamer" name="personality" type="number" value="1" min="1" max="649"/>
|
||||
</form>
|
||||
<button class="big-button" onclick="saveVisitor()">Confirm</button>
|
||||
<button class="big-button" onclick="removeVisitor()">Remove</button>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
<script src="scripts/profile.js"></script>
|
||||
</html>
|
||||
|
||||
@@ -21,6 +21,14 @@ const ELEMENT_ENCOUNTER_ANIMATION = document.getElementById("encounter-form-anim
|
||||
const ELEMENT_ITEM_ID = document.getElementById("item-form-id");
|
||||
const ELEMENT_ITEM_QUANTITY = document.getElementById("item-form-quantity");
|
||||
|
||||
// Join Avenue Visitor form elements
|
||||
const ELEMENT_VISITOR_NAME = document.getElementById("visitor-form-name");
|
||||
const ELEMENT_VISITOR_TYPE = document.getElementById("visitor-form-type");
|
||||
const ELEMENT_VISITOR_SHOP_TYPE = document.getElementById("visitor-form-shop-type");
|
||||
const ELEMENT_VISITOR_GAME = document.getElementById("visitor-form-game");
|
||||
const ELEMENT_VISITOR_PERSONALITY = document.getElementById("visitor-form-personality");
|
||||
const ELEMENT_VISITOR_DREAMER = document.getElementById("visitor-form-dreamer");
|
||||
|
||||
// Misc input elements
|
||||
const ELEMENT_CGEAR_SKIN_INPUT = document.getElementById("cgear-skin");
|
||||
const ELEMENT_DEX_SKIN_INPUT = document.getElementById("dex-skin");
|
||||
@@ -33,6 +41,8 @@ ELEMENT_ENCOUNTER_MOVE.addEventListener("change", clampValue);
|
||||
ELEMENT_ITEM_ID.addEventListener("change", clampValue);
|
||||
ELEMENT_ITEM_QUANTITY.addEventListener("change", clampValue);
|
||||
ELEMENT_LEVEL_GAIN_INPUT.addEventListener("change", clampValue);
|
||||
ELEMENT_VISITOR_PERSONALITY.addEventListener("change", clampValue);
|
||||
ELEMENT_VISITOR_DREAMER.addEventListener("change", clampValue);
|
||||
|
||||
function clampValue() {
|
||||
let value = parseInt(this.value);
|
||||
@@ -55,9 +65,11 @@ const AVAILABLE_GENERATION_V_POKEMON = new Array(
|
||||
// Local variables
|
||||
var encounterTableIndex = -1;
|
||||
var itemTableIndex = -1;
|
||||
var visitorTableIndex = -1;
|
||||
var profile = {
|
||||
encounters: [],
|
||||
items: []
|
||||
items: [],
|
||||
visitors: []
|
||||
};
|
||||
|
||||
function configureEncounter(index) {
|
||||
@@ -66,7 +78,7 @@ function configureEncounter(index) {
|
||||
// Load existing settings
|
||||
let encounter = profile.encounters[encounterTableIndex];
|
||||
ELEMENT_ENCOUNTER_SPECIES.value = encounter ? encounter.species : 1;
|
||||
ELEMENT_ENCOUNTER_MOVE.value = encounter ? encounter.move : 1;
|
||||
ELEMENT_ENCOUNTER_MOVE.value = encounter ? encounter.move : 0;
|
||||
ELEMENT_ENCOUNTER_FORM.value = encounter ? encounter.form : 0;
|
||||
ELEMENT_ENCOUNTER_GENDER.value = encounter ? encounter.gender : "GENDERLESS";
|
||||
ELEMENT_ENCOUNTER_ANIMATION.value = encounter ? encounter.animation : "WALK_AROUND";
|
||||
@@ -160,6 +172,96 @@ function closeEncounterForm() {
|
||||
window.location.href = "#";
|
||||
}
|
||||
|
||||
function configureVisitor(index) {
|
||||
visitorTableIndex = Math.min(12, Math.min(index, profile.visitors.length));
|
||||
|
||||
// Load existing settings
|
||||
let visitor = profile.visitors[visitorTableIndex];
|
||||
ELEMENT_VISITOR_NAME.value = visitor ? visitor.name : "";
|
||||
ELEMENT_VISITOR_TYPE.value = visitor ? visitor.type : "ACE_TRAINER_MALE";
|
||||
ELEMENT_VISITOR_SHOP_TYPE.value = visitor ? visitor.shopType : "RAFFLE";
|
||||
ELEMENT_VISITOR_GAME.value = visitor ? visitor.gameVersion : "BLACK_ENGLISH";
|
||||
ELEMENT_VISITOR_PERSONALITY.value = visitor ? visitor.personality : 0;
|
||||
ELEMENT_VISITOR_DREAMER.value = visitor ? visitor.dreamerSpecies : 1;
|
||||
}
|
||||
|
||||
function saveVisitor() {
|
||||
if(visitorTableIndex < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if name is empty
|
||||
if(ELEMENT_VISITOR_NAME.value == "") {
|
||||
alert("Please enter a name for this visitor.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if name is duplicate
|
||||
for(i in profile.visitors) {
|
||||
if(i != visitorTableIndex) {
|
||||
let visitor = profile.visitors[i];
|
||||
|
||||
if(visitor.name == ELEMENT_VISITOR_NAME.value) {
|
||||
alert("A visitor with this name already exists!")
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// I'll make country codes configurable later... probably
|
||||
let visitorData = {
|
||||
name: ELEMENT_VISITOR_NAME.value,
|
||||
type: ELEMENT_VISITOR_TYPE.value,
|
||||
shopType: ELEMENT_VISITOR_SHOP_TYPE.value,
|
||||
gameVersion: ELEMENT_VISITOR_GAME.value,
|
||||
countryCode: 220, // United States
|
||||
stateProvinceCode: 48, // Washington, D.C.
|
||||
personality: ELEMENT_VISITOR_PERSONALITY.value,
|
||||
dreamerSpecies: ELEMENT_VISITOR_DREAMER.value
|
||||
}
|
||||
|
||||
profile.visitors[visitorTableIndex] = visitorData;
|
||||
updateVisitorCell(visitorTableIndex);
|
||||
closeVisitorForm();
|
||||
}
|
||||
|
||||
function removeVisitor() {
|
||||
if(visitorTableIndex < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
let oldLength = profile.visitors.length;
|
||||
profile.visitors.splice(visitorTableIndex, 1);
|
||||
|
||||
for(let i = visitorTableIndex; i < oldLength; i++) {
|
||||
updateVisitorCell(i);
|
||||
}
|
||||
|
||||
closeVisitorForm();
|
||||
}
|
||||
|
||||
function updateVisitorCell(index) {
|
||||
let cell = document.getElementById("visitor" + index);
|
||||
let visitor = profile.visitors[index];
|
||||
let spriteBase = "/sprites/trainers/";
|
||||
let spriteImage = spriteBase + "none.png";
|
||||
|
||||
if(visitor) {
|
||||
let newSpriteImage = spriteBase + visitor.type.toLowerCase() + ".png";
|
||||
|
||||
if(checkURL(newSpriteImage)){
|
||||
spriteImage = newSpriteImage;
|
||||
}
|
||||
}
|
||||
|
||||
cell.innerHTML = "<img src='" + spriteImage + "'/>";
|
||||
}
|
||||
|
||||
function closeVisitorForm() {
|
||||
visitorTableIndex = -1;
|
||||
window.location.href = "#";
|
||||
}
|
||||
|
||||
function configureItem(index) {
|
||||
itemTableIndex = Math.min(20, Math.min(index, profile.items.length));
|
||||
|
||||
@@ -223,6 +325,18 @@ function closeItemForm() {
|
||||
window.location.href = "#";
|
||||
}
|
||||
|
||||
function previewSkin(inputElementId) {
|
||||
let value = document.getElementById(inputElementId).value;
|
||||
|
||||
if(value == "none") {
|
||||
window.alert("Please select a skin to preview it.");
|
||||
return false;
|
||||
}
|
||||
|
||||
window.open("/dashboard/previewskin?name=" + value);
|
||||
return false;
|
||||
}
|
||||
|
||||
async function fetchData(path) {
|
||||
return fetchData(path, "GET", null);
|
||||
}
|
||||
@@ -278,6 +392,7 @@ function fetchProfileData() {
|
||||
let dreamerInfo = response["dreamerInfo"];
|
||||
let encounters = response["encounters"];
|
||||
let items = response["items"];
|
||||
let visitors = response["avenueVisitors"];
|
||||
let cgearSkin = response["cgearSkin"];
|
||||
let dexSkin = response["dexSkin"];
|
||||
let musical = response["musical"];
|
||||
@@ -291,6 +406,9 @@ function fetchProfileData() {
|
||||
if(gameVersion.includes("2")) {
|
||||
ELEMENT_ENCOUNTER_SPECIES.max = 649;
|
||||
ELEMENT_ITEM_ID.max = 638;
|
||||
|
||||
// Show Join Avenue visitor table
|
||||
document.getElementById("visitor-table-container").style.display = "block";
|
||||
}
|
||||
|
||||
// Update dreamer summary
|
||||
@@ -310,7 +428,7 @@ function fetchProfileData() {
|
||||
ELEMENT_DREAMER_NAME.innerHTML = nickname;
|
||||
ELEMENT_DREAMER_GENDER.innerHTML = stringToWord(gender);
|
||||
ELEMENT_DREAMER_TRAINER.innerHTML = trainerName;
|
||||
ELEMENT_DREAMER_TRAINER_ID.innerHTML = trainerId;
|
||||
ELEMENT_DREAMER_TRAINER_ID.innerHTML = ("0000" + trainerId).slice(-5);
|
||||
ELEMENT_DREAMER_LEVEL.innerHTML = level;
|
||||
}
|
||||
|
||||
@@ -332,6 +450,15 @@ function fetchProfileData() {
|
||||
}
|
||||
}
|
||||
|
||||
// Update Join Avenue visitor table
|
||||
if(visitors) {
|
||||
profile.visitors = visitors;
|
||||
|
||||
for(let i = 0; i < 12; i++) {
|
||||
updateVisitorCell(i);
|
||||
}
|
||||
}
|
||||
|
||||
// Update selected DLC
|
||||
profile.cgearSkin = cgearSkin ? cgearSkin : "none";
|
||||
profile.dexSkin = dexSkin ? dexSkin : "none";
|
||||
@@ -342,7 +469,7 @@ function fetchProfileData() {
|
||||
ELEMENT_LEVEL_GAIN_INPUT.value = levelsGained;
|
||||
|
||||
// Show div
|
||||
document.getElementById("main-container").style.display = "grid";
|
||||
document.getElementById("main-container").style.display = "flex";
|
||||
});
|
||||
}
|
||||
|
||||
@@ -351,6 +478,7 @@ function postProfileData() {
|
||||
let profileData = {
|
||||
encounters: profile.encounters,
|
||||
items: profile.items,
|
||||
avenueVisitors: profile.visitors,
|
||||
cgearSkin: ELEMENT_CGEAR_SKIN_INPUT.value,
|
||||
dexSkin: ELEMENT_DEX_SKIN_INPUT.value,
|
||||
musical: ELEMENT_MUSICAL_INPUT.value,
|
||||
|
||||
@@ -4,6 +4,10 @@ body {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
img {
|
||||
image-rendering: pixelated;
|
||||
}
|
||||
|
||||
label {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@@ -30,9 +30,8 @@ button:active {
|
||||
}
|
||||
|
||||
.root-container {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 25%;
|
||||
transform: translate(-50%, 0%);
|
||||
padding: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
@@ -40,18 +40,20 @@ a:link, a:visited {
|
||||
}
|
||||
|
||||
.root-container {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
padding: 10px;
|
||||
max-width: 600px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.root-container div:first-child {
|
||||
max-width: 532px;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.grid-container {
|
||||
display: grid;
|
||||
grid-auto-columns: 1fr;
|
||||
grid-auto-flow: column;
|
||||
grid-template-columns: auto auto;
|
||||
gap: 5px 10px;
|
||||
}
|
||||
|
||||
@@ -65,26 +67,30 @@ a:link, a:visited {
|
||||
}
|
||||
|
||||
.dreamer-summary {
|
||||
width: 100%;
|
||||
/* Idk why it's like this but yeah nonsense like this is why I hate CSS */
|
||||
width: calc(100% + 4px);
|
||||
margin-top: 3px;
|
||||
margin-left: -2px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.dreamer-summary th {
|
||||
text-align: left;
|
||||
padding-left: 10px;
|
||||
width: 64px;
|
||||
min-width: 76px;
|
||||
}
|
||||
|
||||
.dreamer-summary td {
|
||||
width: 96px;
|
||||
width: 50%;
|
||||
background-color: #262626;
|
||||
border-radius: 10px;
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
.dreamer-sprite {
|
||||
.dreamer-summary td#dreamer-sprite {
|
||||
width: 96px;
|
||||
text-align: center;
|
||||
padding-left: 0px;
|
||||
}
|
||||
|
||||
.header-text {
|
||||
@@ -125,13 +131,15 @@ a:link, a:visited {
|
||||
top: 25%;
|
||||
transform: translate(-50%, 0%);
|
||||
padding: 20px;
|
||||
width: 300px;
|
||||
width: 500px;
|
||||
}
|
||||
|
||||
.encounter-image-table {
|
||||
width: 100%;
|
||||
width: calc(100% + 16px);
|
||||
border-spacing: 8px;
|
||||
margin-bottom: 20px;
|
||||
margin-left: -8px;
|
||||
margin-top: -4px;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
.encounter-image-table td {
|
||||
@@ -143,9 +151,11 @@ a:link, a:visited {
|
||||
}
|
||||
|
||||
.item-table {
|
||||
width: 100%;
|
||||
width: calc(100% + 16px);
|
||||
border-spacing: 8px;
|
||||
margin-bottom: 20px;
|
||||
margin-left: -8px;
|
||||
margin-top: -4px;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
.item-table td {
|
||||
@@ -162,7 +172,24 @@ a:link, a:visited {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.visitor-table {
|
||||
width: calc(100% + 16px);
|
||||
border-spacing: 8px;
|
||||
margin-left: -8px;
|
||||
margin-top: -4px;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
.visitor-table td {
|
||||
background-color: #262626;
|
||||
border-radius: 10px;
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.big-button {
|
||||
font-size: 16px;
|
||||
padding: 16px 32px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user