diff --git a/src/main/java/emu/nebula/data/GameData.java b/src/main/java/emu/nebula/data/GameData.java index 2d06f78..ef3d3d2 100644 --- a/src/main/java/emu/nebula/data/GameData.java +++ b/src/main/java/emu/nebula/data/GameData.java @@ -188,6 +188,9 @@ public class GameData { // Activity: Task @Getter private static DataTable ActivityTaskDataTable = new DataTable<>(); @Getter private static DataTable ActivityTaskGroupDataTable = new DataTable<>(); + + // Activity: Story + @Getter private static DataTable ActivityStoryDataTable = new DataTable<>(); // Activity: Shop @Getter private static DataTable ActivityShopDataTable = new DataTable<>(); diff --git a/src/main/java/emu/nebula/data/resources/ActivityStoryDef.java b/src/main/java/emu/nebula/data/resources/ActivityStoryDef.java new file mode 100644 index 0000000..ae2ab8a --- /dev/null +++ b/src/main/java/emu/nebula/data/resources/ActivityStoryDef.java @@ -0,0 +1,27 @@ +package emu.nebula.data.resources; + +import emu.nebula.data.BaseDef; +import emu.nebula.data.ResourceType; +import emu.nebula.game.inventory.ItemParamMap; +import lombok.Getter; + +@Getter +@ResourceType(name = "ActivityStory.json") +public class ActivityStoryDef extends BaseDef { + private int Id; + private int ChapterId; + + private String FirstCompleteReward; + + private transient ItemParamMap rewards; + + @Override + public int getId() { + return Id; + } + + @Override + public void onLoad() { + this.rewards = ItemParamMap.fromJsonString(this.FirstCompleteReward); + } +} diff --git a/src/main/java/emu/nebula/game/activity/ActivityManager.java b/src/main/java/emu/nebula/game/activity/ActivityManager.java index 27d87a5..862a5a1 100644 --- a/src/main/java/emu/nebula/game/activity/ActivityManager.java +++ b/src/main/java/emu/nebula/game/activity/ActivityManager.java @@ -137,6 +137,7 @@ public class ActivityManager extends PlayerManager implements GameDatabaseObject case Task -> new TaskActivity(this, data); case Shop -> new ShopActivity(this, data); case TrekkerVersus -> new TrekkerVersusActivity(this, data); + case Story -> new StoryActivity(this, data); case PenguinCard -> new PenguinCardActivity(this, data); case Soldier -> new SoldierActivity(this, data); default -> null; diff --git a/src/main/java/emu/nebula/game/activity/ActivityModule.java b/src/main/java/emu/nebula/game/activity/ActivityModule.java index 3100192..801a94d 100644 --- a/src/main/java/emu/nebula/game/activity/ActivityModule.java +++ b/src/main/java/emu/nebula/game/activity/ActivityModule.java @@ -81,6 +81,7 @@ public class ActivityModule extends GameContextModule { // Go, My Lady! Training Handbook Plus! this.activities.add(1011101); + this.activities.add(1011102); this.activities.add(1011103); this.activities.add(1011104); diff --git a/src/main/java/emu/nebula/game/activity/type/StoryActivity.java b/src/main/java/emu/nebula/game/activity/type/StoryActivity.java new file mode 100644 index 0000000..bffdc7c --- /dev/null +++ b/src/main/java/emu/nebula/game/activity/type/StoryActivity.java @@ -0,0 +1,204 @@ +package emu.nebula.game.activity.type; + +import java.util.HashMap; +import java.util.Map; + +import dev.morphia.annotations.Entity; + +import emu.nebula.data.GameData; +import emu.nebula.data.resources.ActivityDef; +import emu.nebula.game.activity.ActivityManager; +import emu.nebula.game.activity.GameActivity; +import emu.nebula.game.player.PlayerChangeInfo; +import emu.nebula.proto.ActivityDetail.ActivityMsg; +import emu.nebula.proto.ActivityStorySettleOuterClass.ActivityStorySettle; +import emu.nebula.proto.Public.ActivityStory; +import emu.nebula.proto.Public.ActivityStoryChoice; + +import it.unimi.dsi.fastutil.ints.Int2IntMap; +import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; +import it.unimi.dsi.fastutil.ints.IntOpenHashSet; +import it.unimi.dsi.fastutil.ints.IntSet; + +import lombok.Getter; +import lombok.Setter; + +import us.hebi.quickbuf.RepeatedInt; + +@Getter +@Entity +public class StoryActivity extends GameActivity { + private Map stories; + private IntSet evidences; + private long buildId; + + @Deprecated // Morphia only + public StoryActivity() { + + } + + public StoryActivity(ActivityManager manager, ActivityDef data) { + super(manager, data); + this.stories = new HashMap<>(); + this.evidences = new IntOpenHashSet(); + } + + private int getChapterId() { + return (int) Math.floor(this.getId() / 100); + } + + public boolean apply(int storyId, long buildId) { + // Check story id + var story = GameData.getActivityStoryDataTable().get(storyId); + + if (story == null || story.getChapterId() != this.getChapterId()) { + return false; + } + + // Set build id + if (buildId != 0) { + this.buildId = buildId; + } + + return true; + } + + public PlayerChangeInfo settle(Iterable list, RepeatedInt evidences) { + // Create change info + var change = new PlayerChangeInfo(); + + // Boolean flag to check if any data was changed/added + boolean hasChanged = false; + + // Handle settle info + for (var settle : list) { + // Get story data + var story = GameData.getActivityStoryDataTable().get(settle.getStoryId()); + + // Validate story + if (story == null || story.getChapterId() != this.getChapterId()) { + continue; + } + + // Check if we have already completed this story + if (this.getStories().containsKey(story.getId())) { + continue; + } + + // Create story info + var info = new ActivityStoryInfo(story.getId()); + + for (var option : settle.getMajor()) { + info.addMajor(option.getGroup(), option.getChoice()); + } + + for (var option : settle.getPersonality()) { + info.addPersonality(option.getGroup(), option.getChoice()); + } + + // Add to story log + this.stories.put(story.getId(), info); + + // Set changed flag + hasChanged = true; + + // Add rewards + this.getPlayer().getInventory().addItems(story.getRewards(), change); + } + + // Add evidences + for (int i : evidences) { + this.getEvidences().add(i); + + // Set changed flag + hasChanged = true; + } + + // Save to database if anything has changed + if (hasChanged) { + this.save(); + } + + // Success + return change; + } + + // Proto + + @Override + public void encodeActivityMsg(ActivityMsg msg) { + var proto = msg.getMutableStoryChapter() + .setBuildId(this.getBuildId()); + + for (int i : this.getEvidences()) { + proto.addEvidences(i); + } + + for (var info : this.getStories().values()) { + proto.addStories(info.toProto()); + } + } + + @Setter + @Getter + @Entity(useDiscriminator = false) + public static class ActivityStoryInfo { + private int id; + private Int2IntMap major; + private Int2IntMap personality; + + @Deprecated // Morphia only + public ActivityStoryInfo() { + + } + + public ActivityStoryInfo(int id) { + this.id = id; + } + + public void addMajor(int group, int choice) { + if (this.major == null) { + this.major = new Int2IntOpenHashMap(); + } + + this.major.put(group, choice); + } + + public void addPersonality(int group, int choice) { + if (this.personality == null) { + this.personality = new Int2IntOpenHashMap(); + } + + this.personality.put(group, choice); + } + + // Proto + + public ActivityStory toProto() { + var proto = ActivityStory.newInstance() + .setId(this.id); + + if (this.major != null) { + for (var entry : this.major.int2IntEntrySet()) { + var choice = ActivityStoryChoice.newInstance() + .setGroup(entry.getIntKey()) + .setValue(entry.getIntValue()); + + proto.addMajor(choice); + } + } + + if (this.personality != null) { + for (var entry : this.personality.int2IntEntrySet()) { + var choice = ActivityStoryChoice.newInstance() + .setGroup(entry.getIntKey()) + .setValue(entry.getIntValue()); + + proto.addPersonality(choice); + } + } + + return proto; + } + } +} diff --git a/src/main/java/emu/nebula/game/inventory/ItemParamMap.java b/src/main/java/emu/nebula/game/inventory/ItemParamMap.java index 9ac9a05..80e5f8b 100644 --- a/src/main/java/emu/nebula/game/inventory/ItemParamMap.java +++ b/src/main/java/emu/nebula/game/inventory/ItemParamMap.java @@ -149,8 +149,10 @@ public class ItemParamMap extends Int2IntLinkedOpenHashMap implements ObjectBidi var map = new ItemParamMap(); var json = JsonUtils.decodeMap(jsonString, Integer.class, Integer.class); - for (var entry : json.entrySet()) { - map.add(entry.getKey(), entry.getValue()); + if (json != null) { + for (var entry : json.entrySet()) { + map.add(entry.getKey(), entry.getValue()); + } } return map; diff --git a/src/main/java/emu/nebula/server/handlers/HandlerActivityStoryApplyReq.java b/src/main/java/emu/nebula/server/handlers/HandlerActivityStoryApplyReq.java new file mode 100644 index 0000000..c4b959c --- /dev/null +++ b/src/main/java/emu/nebula/server/handlers/HandlerActivityStoryApplyReq.java @@ -0,0 +1,36 @@ +package emu.nebula.server.handlers; + +import emu.nebula.net.NetHandler; +import emu.nebula.net.NetMsgId; +import emu.nebula.proto.ActivityStoryApply.ActivityStoryApplyReq; +import emu.nebula.net.HandlerId; +import emu.nebula.game.activity.type.StoryActivity; +import emu.nebula.net.GameSession; + +@HandlerId(NetMsgId.activity_story_apply_req) +public class HandlerActivityStoryApplyReq extends NetHandler { + + @Override + public byte[] handle(GameSession session, byte[] message) throws Exception { + // Parse req + var req = ActivityStoryApplyReq.parseFrom(message); + + // Get activity + var activity = session.getPlayer().getActivityManager().getActivity(StoryActivity.class, req.getActivityId()); + + if (activity == null) { + return session.encodeMsg(NetMsgId.activity_story_apply_failed_ack); + } + + // Apply + boolean success = activity.apply(req.getStoryId(), req.getBuildId()); + + if (success == false) { + return session.encodeMsg(NetMsgId.activity_story_apply_failed_ack); + } + + // Encode and send + return session.encodeMsg(NetMsgId.activity_story_apply_succeed_ack); + } + +} diff --git a/src/main/java/emu/nebula/server/handlers/HandlerActivityStorySettleReq.java b/src/main/java/emu/nebula/server/handlers/HandlerActivityStorySettleReq.java new file mode 100644 index 0000000..f8cadbc --- /dev/null +++ b/src/main/java/emu/nebula/server/handlers/HandlerActivityStorySettleReq.java @@ -0,0 +1,39 @@ +package emu.nebula.server.handlers; + +import emu.nebula.net.NetHandler; +import emu.nebula.net.NetMsgId; +import emu.nebula.proto.ActivityStorySettleOuterClass.ActivityStorySettleReq; +import emu.nebula.net.HandlerId; +import emu.nebula.game.activity.type.StoryActivity; +import emu.nebula.net.GameSession; + +@HandlerId(NetMsgId.activity_story_settle_req) +public class HandlerActivityStorySettleReq extends NetHandler { + + @Override + public byte[] handle(GameSession session, byte[] message) throws Exception { + // Parse req + var req = ActivityStorySettleReq.parseFrom(message); + + // Get activity + var activity = session.getPlayer().getActivityManager().getActivity(StoryActivity.class, req.getActivityId()); + + if (activity == null) { + return session.encodeMsg(NetMsgId.activity_story_settle_failed_ack); + } + + // Apply + var change = activity.settle(req.getMutableList(), req.getMutableEvidences()); + + if (change == null) { + return session.encodeMsg(NetMsgId.activity_story_settle_failed_ack); + } + + // Handle client events for achievements + session.getPlayer().getAchievementManager().handleClientEvents(req.getEvents()); + + // Encode and send + return session.encodeMsg(NetMsgId.activity_story_settle_succeed_ack, change.toProto()); + } + +} diff --git a/src/main/resources/versions.json b/src/main/resources/versions.json index 3a647f9..ce13094 100644 --- a/src/main/resources/versions.json +++ b/src/main/resources/versions.json @@ -2,6 +2,6 @@ "global": 134, "kr": 139, "jp": 137, - "tw": 142, + "tw": 143, "cn": 133 } \ No newline at end of file