Compare commits

...

6 Commits

Author SHA1 Message Date
github-actions
b087e1fb4f Format code [skip actions] 2024-10-03 15:31:53 +00:00
KingRainbow44
d11e13a005
fix(GameConstants.java): Remove extra period in VERSION 2024-10-03 11:30:43 -04:00
KingRainbow44
10423dbada
fix(ActionServerLuaCall.java): Handle Lua function parameter passing 2024-09-28 20:18:22 -04:00
KingRainbow44
5b9d386e17
fix(AbilityModifier.java): Always parse param[1-3] as DynamicFloats 2024-09-28 19:51:29 -04:00
KingRainbow44
5167253136
fix(5.0): Update GameConstants.java with 5.0 changes 2024-09-28 18:48:51 -04:00
KingRainbow44
3c8d1263e5
fix: Re-add handbook_auth.html
its still used by handbook authenticator; i won't judge anyone if they're still loading the handbook
2024-09-28 18:47:43 -04:00
30 changed files with 172 additions and 39 deletions

View File

@ -7,8 +7,8 @@ import emu.grasscutter.utils.objects.SparseSet;
import java.util.Arrays;
public final class GameConstants {
public static String VERSION = "4.0.0";
public static int[] VERSION_PARTS = {4, 0, 0};
public static String VERSION = "5.0.0";
public static int[] VERSION_PARTS = {5, 0, 0};
public static boolean DEBUG = false;
public static final int DEFAULT_TEAMS = 4;
@ -16,7 +16,7 @@ public final class GameConstants {
public static final int MAIN_CHARACTER_MALE = 10000005;
public static final int MAIN_CHARACTER_FEMALE = 10000007;
public static final Position START_POSITION = new Position(2747, 194, -1719);
public static final int MAX_FRIENDS = 60;
public static final int MAX_FRIENDS = 100;
public static final int MAX_FRIEND_REQUESTS = 50;
public static final int SERVER_CONSOLE_UID = 99; // The UID of the server console's "player".
public static final int BATTLE_PASS_MAX_LEVEL = 50;

View File

@ -150,7 +150,6 @@ public final class Grasscutter {
httpServer.addRouter(AuthenticationHandler.class);
httpServer.addRouter(GachaHandler.class);
httpServer.addRouter(DocumentationServerHandler.class);
httpServer.addRouter(HandbookHandler.class);
}
// Check if the HTTP server should start.

View File

@ -345,9 +345,10 @@ public class AbilityModifier implements Serializable {
public DynamicFloat valueRangeMax;
public String overrideMapKey;
public int param1;
public int param2;
public int param3;
public int paramNum;
public DynamicFloat param1 = DynamicFloat.ZERO,
param2 = DynamicFloat.ZERO,
param3 = DynamicFloat.ZERO;
public String funcName;
public LuaCallType luaCallType;
@ -358,12 +359,18 @@ public class AbilityModifier implements Serializable {
public String content;
public enum LuaCallType {
Gadget,
@SerializedName(value = "OwnerGadegt", alternate = "OwnerGadget")
OwnerGadget,
FromGroup,
CurGalleryControlGroup,
CurChallengeGroup,
OwnerFromGroup,
SpecificGroup,
CurScenePlay,
CurChallengeGroup,
CurRogueBossGroup,
CurGalleryControlGroup,
AbilityGroupSourceGroup,
CurScenePlay
LevelBankZoneContainsGroup
}
public enum DropType {

View File

@ -6,6 +6,7 @@ import lombok.Data;
@Data
public class ConfigGlobalCombat {
private DefaultAbilities defaultAbilities;
// TODO: Add more indices
@Data

View File

@ -13,6 +13,7 @@ public class RoutePoint {
private float waitTime; // optional
private float targetVelocity; // optional
private boolean hasReachEvent; // optional
// rotRoundReachDir //optional Pos with optional values
// rotRoundLeaveDir //optional Pos with optional values

View File

@ -57,6 +57,10 @@ public class DynamicFloat {
return this.get(ability.getAbilitySpecials(), 0f);
}
public int getInt(Ability ability) {
return (int) this.get(ability.getAbilitySpecials(), 0f);
}
public float get(Object2FloatMap<String> props, float defaultValue) {
if (!this.dynamic) return constant;

View File

@ -9,6 +9,7 @@ import lombok.Getter;
public class BlossomRefreshExcelConfigData extends GameResource {
@Getter(onMethod_ = @Override)
private int id;
// Map details
private long nameTextMapHash;
private long descTextMapHash;

View File

@ -21,6 +21,7 @@ public class CoopChapterData extends GameResource {
// int avatarSortId;
// String chapterIcon;
List<CoopCondition> unlockCond;
// int [] unlockCondTips;
// int openMaterialId;
// int openMaterialNum;

View File

@ -65,7 +65,9 @@ public class Ability {
data.modifiers.values().stream()
.map(
m ->
m.onAdded == null ? Collections.<AbilityModifierAction>emptyList() : Arrays.asList(m.onAdded))
m.onAdded == null
? Collections.<AbilityModifierAction>emptyList()
: Arrays.asList(m.onAdded))
.flatMap(List::stream)
.filter(action -> action.type == AbilityModifierAction.Type.AvatarSkillStart)
.map(action -> action.skillID)

View File

@ -15,9 +15,11 @@ public class ActionExecuteGadgetLua extends AbilityActionHandler {
// Investigate if we need to use target
if (owner.getEntityController() != null) {
owner
.getEntityController()
.onClientExecuteRequest(owner, action.param1, action.param2, action.param3);
var param1 = action.param1.getInt(ability);
var param2 = action.param2.getInt(ability);
var param3 = action.param3.getInt(ability);
owner.getEntityController().onClientExecuteRequest(owner, param1, param2, param3);
return true;
}

View File

@ -8,6 +8,7 @@ import emu.grasscutter.game.entity.GameEntity;
import emu.grasscutter.scripts.ScriptLoader;
import javax.script.Bindings;
import org.luaj.vm2.LuaFunction;
import org.luaj.vm2.LuaValue;
@AbilityAction(AbilityModifierAction.Type.ServerLuaCall)
public final class ActionServerLuaCall extends AbilityActionHandler {
@ -16,12 +17,14 @@ public final class ActionServerLuaCall extends AbilityActionHandler {
Ability ability, AbilityModifierAction action, ByteString abilityData, GameEntity target) {
var scene = target.getScene();
var scriptManager = scene.getScriptManager();
var functionName = action.funcName;
// Set the script library's manager.
var scriptLib = ScriptLoader.getScriptLib();
scriptLib.setCurrentEntity(target);
scriptLib.setSceneScriptManager(scriptManager);
// Attempt to call the function.
return switch (action.luaCallType) {
default -> false;
@ -33,7 +36,7 @@ public final class ActionServerLuaCall extends AbilityActionHandler {
// Set the script library's group.
scriptLib.setCurrentGroup(group);
yield ActionServerLuaCall.callFunction(script, functionName);
yield ActionServerLuaCall.callFunction(script, functionName, ability, action);
}
case SpecificGroup -> {
var groupId = action.callParamList[0];
@ -43,7 +46,16 @@ public final class ActionServerLuaCall extends AbilityActionHandler {
// Set the script library's group.
scriptLib.setCurrentGroup(group);
yield ActionServerLuaCall.callFunction(script, functionName);
yield ActionServerLuaCall.callFunction(script, functionName, ability, action);
}
case Gadget -> {
var controller = target.getEntityController();
if (controller == null || functionName.isBlank()) yield false;
// Hand off the function handling to the controller.
controller.callControllerScriptFunc(target, functionName, ability, action);
yield true;
}
};
}
@ -53,17 +65,30 @@ public final class ActionServerLuaCall extends AbilityActionHandler {
*
* @param bindings The bindings to fetch the function from.
* @param functionName The name of the function to call.
* @param ability The ability data.
* @param action The ability action data.
* @return Whether the function was called successfully.
*/
private static boolean callFunction(Bindings bindings, String functionName) {
private static boolean callFunction(
Bindings bindings, String functionName, Ability ability, AbilityModifierAction action) {
try {
// Resolve the function from the script.
var function = bindings.get(functionName);
if (!(function instanceof LuaFunction luaFunction))
throw new Exception("Function is not a LuaFunction.");
// Attempt to invoke the function.
luaFunction.call(ScriptLoader.getScriptLibLua());
// Convert parameters to Lua values.
var lParam1 = LuaValue.valueOf(action.param1.getInt(ability));
var lParam2 = LuaValue.valueOf(action.param2.getInt(ability));
var lParam3 = LuaValue.valueOf(action.param3.getInt(ability));
// Invoke the function with the parameters.
switch (action.paramNum) {
case 1 -> luaFunction.invoke(new LuaValue[] {lParam1});
case 2 -> luaFunction.invoke(new LuaValue[] {lParam1, lParam2});
case 3 -> luaFunction.invoke(new LuaValue[] {lParam1, lParam2, lParam3});
default -> luaFunction.invoke(new LuaValue[] {ScriptLoader.getScriptLibLua()});
}
return true;
} catch (Exception exception) {

View File

@ -24,6 +24,7 @@ public class PlayerActivityData {
int uid;
int activityId;
Map<Integer, WatcherInfo> watcherInfoMap;
/** the detail data of each type of activity (Json format) */
String detail;

View File

@ -86,6 +86,7 @@ public class Avatar {
// trial avatar property
@Getter @Setter private int trialAvatarId = 0;
// cannot store to db if grant reason is not integer
@Getter @Setter
private int grantReason = TrialAvatarGrantRecord.GrantReason.GRANT_REASON_INVALID.getNumber();

View File

@ -49,6 +49,7 @@ public class GachaBanner {
@Getter private int[] rateUpItems5 = {};
// This now handles default values for the fields below
@Getter private BannerType bannerType = BannerType.STANDARD;
// These don't change between banner types (apart from Standard having three extra 4star avatars)
@Getter
private int[] fallbackItems3 = {

View File

@ -25,15 +25,13 @@ import it.unimi.dsi.fastutil.ints.*;
import java.nio.file.*;
import java.util.*;
import java.util.concurrent.ThreadLocalRandom;
import lombok.Getter;
import org.greenrobot.eventbus.Subscribe;
public class GachaSystem extends BaseGameSystem {
private static final int starglitterId = 221;
private static final int stardustId = 222;
@Getter
private final Int2ObjectMap<GachaBanner> gachaBanners;
@Getter private final Int2ObjectMap<GachaBanner> gachaBanners;
private WatchService watchService;
public GachaSystem(GameServer server) {
@ -422,8 +420,7 @@ public class GachaSystem extends BaseGameSystem {
if (this.watchService == null) {
try {
this.watchService = FileSystems.getDefault().newWatchService();
FileUtils.getDataUserPath("")
.register(watchService, StandardWatchEventKinds.ENTRY_MODIFY);
FileUtils.getDataUserPath("").register(watchService, StandardWatchEventKinds.ENTRY_MODIFY);
} catch (Exception e) {
Grasscutter.getLogger()
.error(

View File

@ -195,6 +195,7 @@ public class GameMainQuest {
// .forEach(getQuestManager()::startMainQuest);
// }
}
// TODO
public void fail() {}

View File

@ -7,6 +7,7 @@ import java.util.*;
public class TowerLevelRecord {
/** floorId in config */
private int floorId;
/** LevelId - Stars */
private Map<Integer, Integer> passedLevelMap;

View File

@ -56,6 +56,7 @@ public class SceneScriptManager {
private final Map<Integer, SceneGroupInstance> cachedSceneGroupsInstances;
private ScriptMonsterTideService scriptMonsterTideService;
private ScriptMonsterSpawnService scriptMonsterSpawnService;
/** blockid - loaded groupSet */
private final Map<Integer, Set<SceneGroup>> loadedGroupSetPerBlock;
@ -846,6 +847,7 @@ public class SceneScriptManager {
.warn("failed to create entity with group {} and config {}", group.id, configId);
}
}
// Events
public Future<?> callEvent(int groupId, int eventType) {
return callEvent(new ScriptArgs(groupId, eventType));

View File

@ -28,11 +28,13 @@ public class ScriptLoader {
@Getter private static Serializer serializer;
@Getter private static ScriptLib scriptLib;
@Getter private static LuaValue scriptLibLua;
/** suggest GC to remove it if the memory is less */
private static Map<String, SoftReference<String>> scriptSources = new ConcurrentHashMap<>();
private static Map<String, SoftReference<CompiledScript>> scriptsCache =
new ConcurrentHashMap<>();
/** sceneId - SceneMeta */
private static Map<Integer, SoftReference<SceneMeta>> sceneMetaCache = new ConcurrentHashMap<>();

View File

@ -2,6 +2,7 @@ package emu.grasscutter.scripts.constants;
public class EventType {
public static final int EVENT_NONE = 0;
/** param1: monster.configId */
public static final int EVENT_ANY_MONSTER_DIE = 1;

View File

@ -18,6 +18,7 @@ public class SceneGadget extends SceneObject {
public int owner;
public SceneBossChest boss_chest;
public int interact_id;
/**
* Note: this field indicates whether the gadget should disappear permanently. For example, if
* isOneOff=true, like most chests, it will disappear permanently after interacted. If

View File

@ -13,6 +13,7 @@ public abstract class SceneObject {
public Position pos;
public Position rot;
/** not set by lua */
public transient SceneGroup group;
}

View File

@ -1,6 +1,8 @@
package emu.grasscutter.scripts.data.controller;
import emu.grasscutter.*;
import emu.grasscutter.data.binout.AbilityModifier.AbilityModifierAction;
import emu.grasscutter.game.ability.Ability;
import emu.grasscutter.game.entity.GameEntity;
import emu.grasscutter.game.props.ElementType;
import emu.grasscutter.scripts.*;
@ -58,6 +60,30 @@ public class EntityController {
return 0;
}
/**
* Invoked from {@link emu.grasscutter.game.ability.actions.ActionServerLuaCall} to call an entity
* controller function.
*
* @param entity The entity which called the function.
* @param funcName The name of the function to call.
* @param ability The ability that is calling the function.
* @param action The action that is calling the function.
* @return The return value of the function.
*/
public LuaValue callControllerScriptFunc(
GameEntity entity, String funcName, Ability ability, AbilityModifierAction action) {
var lParam1 = LuaValue.valueOf(action.param1.getInt(ability));
var lParam2 = LuaValue.valueOf(action.param2.getInt(ability));
var lParam3 = LuaValue.valueOf(action.param3.getInt(ability));
return switch (action.paramNum) {
case 1 -> this.callControllerScriptFunc(entity, funcName, lParam1);
case 2 -> this.callControllerScriptFunc(entity, funcName, lParam1, lParam2);
case 3 -> this.callControllerScriptFunc(entity, funcName, lParam1, lParam2, lParam3);
default -> this.callControllerScriptFunc(entity, funcName, LuaValue.NIL);
};
}
// TODO actual execution should probably be handle by EntityControllerScriptManager
private LuaValue callControllerScriptFunc(GameEntity entity, String funcName, LuaValue arg1) {
return callControllerScriptFunc(entity, funcName, arg1, LuaValue.NIL, LuaValue.NIL);

View File

@ -22,8 +22,8 @@ import org.java_websocket.handshake.ServerHandshake;
import org.slf4j.Logger;
/**
* This is a simple implementation of a server-to-server IPC client.
* It is implemented over WebSockets, and supports all Grasscutter versions past 1.6.0
* This is a simple implementation of a server-to-server IPC client. It is implemented over
* WebSockets, and supports all Grasscutter versions past 1.6.0
*/
@Getter
public final class DispatchClient extends WebSocketClient implements IDispatcher {

View File

@ -14,6 +14,7 @@ import java.util.*;
import java.util.concurrent.*;
/** Handles requests for the new GM Handbook. */
@Deprecated(since = "1.7.5-dev")
public final class HandbookHandler implements Router {
private String handbook;
private final boolean serve;

View File

@ -16,9 +16,10 @@ public class HandlerAvatarUpgradeReq extends PacketHandler {
// These are bundled into a list of items.
for (var item : req.getItemParamListList()) {
session
.getServer()
.getInventorySystem()
.upgradeAvatar(session.getPlayer(), req.getAvatarGuid(), item.getItemId(), item.getCount());
.getServer()
.getInventorySystem()
.upgradeAvatar(
session.getPlayer(), req.getAvatarGuid(), item.getItemId(), item.getCount());
}
}
}

View File

@ -28,8 +28,10 @@ public class PacketPlayerEnterSceneNotify extends BasePacket {
.setTargetUid(player.getUid())
.setEnterSceneToken(player.getEnterSceneToken())
.setWorldLevel(player.getWorldLevel())
// .setEnterReason(EnterReason.Login.getValue()) // Removed in 5.0; we don't know what it is
// .setIsFirstLoginEnterScene(player.isFirstLoginEnterScene()) // Removed in 5.0; we don't know what it is
// .setEnterReason(EnterReason.Login.getValue()) // Removed in 5.0; we don't know what
// it is
// .setIsFirstLoginEnterScene(player.isFirstLoginEnterScene()) // Removed in 5.0; we
// don't know what it is
// .setWorldType(1) // Removed in 5.0; we don't know what it is
.setSceneTransaction(
"3-"
@ -80,7 +82,8 @@ public class PacketPlayerEnterSceneNotify extends BasePacket {
var proto =
PlayerEnterSceneNotify.newBuilder()
// .setPrevSceneId(player.getSceneId()) // Removed in 5.0; we don't know what it is
// .setPrevPos(player.getPosition().toProto()) // Removed in 5.0; we don't know what it is
// .setPrevPos(player.getPosition().toProto()) // Removed in 5.0; we don't know what it
// is
.setSceneId(teleportProperties.getSceneId())
.setPos(teleportProperties.getTeleportTo().toProto())
.setSceneBeginTime(System.currentTimeMillis())
@ -88,7 +91,8 @@ public class PacketPlayerEnterSceneNotify extends BasePacket {
.setTargetUid(target.getUid())
.setEnterSceneToken(player.getEnterSceneToken())
.setWorldLevel(target.getWorld().getWorldLevel())
// .setEnterReason(teleportProperties.getEnterReason().getValue()) // Removed in 5.0; we don't know what it is
// .setEnterReason(teleportProperties.getEnterReason().getValue()) // Removed in 5.0; we
// don't know what it is
// .setWorldType(1) // Removed in 5.0; we don't know what it is
.setSceneTransaction(
teleportProperties.getSceneId()
@ -118,14 +122,16 @@ public class PacketPlayerEnterSceneNotify extends BasePacket {
var proto =
PlayerEnterSceneNotify.newBuilder()
// .setPrevSceneId(player.getSceneId()) // Removed in 5.0; we don't know what it is
// .setPrevPos(player.getPosition().toProto()) // Removed in 5.0; we don't know what it is
// .setPrevPos(player.getPosition().toProto()) // Removed in 5.0; we don't know what it
// is
.setSceneId(teleportProperties.getSceneId())
.setPos(teleportProperties.getTeleportTo().toProto())
.setSceneBeginTime(System.currentTimeMillis())
.setType(other ? EnterType.ENTER_TYPE_OTHER_HOME : EnterType.ENTER_TYPE_SELF_HOME)
.setTargetUid(targetUid)
.setEnterSceneToken(player.getEnterSceneToken())
// .setEnterReason(teleportProperties.getEnterReason().getValue()) // Removed in 5.0; we don't know what it is
// .setEnterReason(teleportProperties.getEnterReason().getValue()) // Removed in 5.0; we
// don't know what it is
// .setWorldType(64) // Removed in 5.0; we don't know what it is
.setSceneTransaction(
teleportProperties.getSceneId()

View File

@ -58,7 +58,8 @@ public class PacketPlayerLoginRsp extends BasePacket {
.setClientDataVersion(info.getClientDataVersion())
.setClientSilenceDataVersion(info.getClientSilenceDataVersion())
// .setClientMd5(info.getClientDataMd5()) // Removed in 5.0; we don't know what it is
// .setClientSilenceMd5(info.getClientSilenceDataMd5()) // Removed in 5.0; we don't know what it is
// .setClientSilenceMd5(info.getClientSilenceDataMd5()) // Removed in 5.0; we don't know
// what it is
.setResVersionConfig(info.getResVersionConfig())
.setClientVersionSuffix(info.getClientVersionSuffix())
.setClientSilenceVersionSuffix(info.getClientSilenceVersionSuffix())

View File

@ -199,10 +199,10 @@ public final class FileUtils {
}
public static byte[] readResource(String resourcePath) {
try (InputStream is = Grasscutter.class.getResourceAsStream(resourcePath)) {
return is.readAllBytes();
try (var is = Grasscutter.class.getResourceAsStream(resourcePath)) {
return Objects.requireNonNull(is).readAllBytes();
} catch (Exception exception) {
Grasscutter.getLogger().warn("Failed to read resource: " + resourcePath);
Grasscutter.getLogger().warn("Failed to read resource: {}", resourcePath);
Grasscutter.getLogger().debug("Failed to load resource: " + resourcePath, exception);
}

View File

@ -0,0 +1,46 @@
<!DOCTYPE html>
<html lang="en">
<head>
<style>
body {
margin: 0;
width: 100vw;
height: 100vh;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
p {
margin: 2px;
}
</style>
<title>Handbook Authentication</title>
</head>
<body>
<script type="application/javascript">
if ("{{VALUE}}" === "true") {
parent.postMessage({
type: "handbook-auth",
token: "{{SESSION_TOKEN}}",
uid: "{{PLAYER_ID}}"
}, "*");
}
</script>
<p>Input your Player UID here.</p>
<form method="post">
<label>
<input
name="playerid"
type="number"
/>
</label>
<input type="submit" />
</form>
</body>
</html>