Implement Heartlink

This commit is contained in:
Melledy
2025-11-11 23:47:07 -08:00
parent 080d345981
commit d4a7aa0320
11 changed files with 366 additions and 6 deletions

View File

@@ -49,7 +49,7 @@ public class Character implements GameDatabaseObject {
private int skin;
private int[] skills;
private Bitset talents;
private CharacterContact contact;
private long createTime;
@Deprecated // Morphia only!
@@ -66,11 +66,13 @@ public class Character implements GameDatabaseObject {
this.playerUid = player.getUid();
this.charId = data.getId();
this.data = data;
this.createTime = Nebula.getCurrentTime();
this.level = 1;
this.skin = data.getDefaultSkinId();
this.skills = new int[] {1, 1, 1, 1, 1};
this.talents = new Bitset();
this.createTime = Nebula.getCurrentTime();
this.contact = new CharacterContact(this);
}
public void setPlayer(Player player) {
@@ -78,8 +80,20 @@ public class Character implements GameDatabaseObject {
}
public void setData(CharacterDef data) {
if (this.data == null && data.getId() == this.getCharId()) {
this.data = data;
// Sanity check
if (this.data != null || data.getId() != this.getCharId()) {
return;
}
// Set data
this.data = data;
// Check contacts
if (this.contact == null) {
this.contact = new CharacterContact(this);
this.save();
} else {
this.contact.setCharacter(this);
}
}

View File

@@ -0,0 +1,49 @@
package emu.nebula.game.character;
import dev.morphia.annotations.Entity;
import emu.nebula.data.resources.ChatDef;
import emu.nebula.proto.Public.Chat;
import lombok.Getter;
import us.hebi.quickbuf.RepeatedInt;
@Getter
@Entity(useDiscriminator = false)
public class CharacterChat {
private int id;
private int process;
private boolean end;
private int[] options;
@Deprecated // Morphia only
public CharacterChat() {
}
public CharacterChat(ChatDef data) {
this.id = data.getId();
}
// Proto
public Chat toProto() {
var proto = Chat.newInstance()
.setId(this.getId())
.setProcess(this.getProcess());
if (this.getOptions() != null) {
proto.addAllOptions(this.getOptions());
}
return proto;
}
public void report(int process, RepeatedInt options, boolean end) {
this.process = process;
this.end = end;
if (options.length() > 0 && options.length() <= 5) {
this.options = options.toArray();
}
}
}

View File

@@ -0,0 +1,154 @@
package emu.nebula.game.character;
import java.util.HashMap;
import java.util.Map;
import dev.morphia.annotations.Entity;
import emu.nebula.Nebula;
import emu.nebula.data.resources.ChatDef;
import emu.nebula.game.player.PlayerChangeInfo;
import emu.nebula.net.NetMsgId;
import emu.nebula.proto.Public.Contacts;
import emu.nebula.proto.Public.UI32;
import lombok.Getter;
import us.hebi.quickbuf.RepeatedInt;
@Getter
@Entity(useDiscriminator = false)
public class CharacterContact {
private transient Character character;
private boolean top;
private long triggerTime;
private Map<Integer, CharacterChat> chats;
@Deprecated // Morphia only
public CharacterContact() {
}
public CharacterContact(Character character) {
this.character = character;
this.chats = new HashMap<>();
this.triggerTime = character.getCreateTime();
// Get starter chat
for (var chatData : character.getData().getChats()) {
// Skip chats that have a requirement
if (chatData.getPreChatId() != 0) {
continue;
}
// Add chat for character
var chat = new CharacterChat(chatData);
this.getChats().put(chat.getId(), chat);
}
}
public void setCharacter(Character character) {
this.character = character;
}
public void addNextChat() {
// Find next chat data
ChatDef nextChatData = null;
for (var chatData : getCharacter().getData().getChats()) {
// Skip chats that we have added
if (this.getChats().containsKey(chatData.getId())) {
continue;
}
nextChatData = chatData;
break;
}
// Skip if we cant find any new chat data
if (nextChatData == null) {
return;
}
// Add chat for character
var chat = new CharacterChat(nextChatData);
this.getChats().put(chat.getId(), chat);
// Send packet
this.getCharacter().getPlayer().addNextPackage(
NetMsgId.phone_chat_change_notify,
UI32.newInstance().setValue(chat.getId())
);
}
public CharacterChat getChatById(int chatId) {
return this.getChats().get(chatId);
}
public boolean hasNew() {
return this.getChats().values()
.stream()
.anyMatch(c -> !c.isEnd());
}
public PlayerChangeInfo report(ChatDef chatData, int process, RepeatedInt options, boolean end) {
// Get chat
var chat = this.getChatById(chatData.getId());
if (chat == null) {
return null;
}
// Player change info
var change = new PlayerChangeInfo();
// Sanity check
if (chat.isEnd()) {
return change;
}
// Report
chat.report(process, options, end);
// Set trigger time
this.triggerTime = Nebula.getCurrentTime();
// Add next chat
this.addNextChat();
// TODO save more efficiently
this.getCharacter().save();
// Add rewards
if (chat.isEnd()) {
getCharacter().getPlayer().getInventory().addItem(
chatData.getReward1(),
chatData.getRewardQty1(),
change
);
}
// Success
return change.setSuccess(true);
}
public void toggleTop() {
// Toggle
this.top = !this.top;
// TODO save more efficiently
this.getCharacter().save();
}
// Proto
public Contacts toProto() {
var proto = Contacts.newInstance()
.setCharId(this.getCharacter().getCharId())
.setTop(this.isTop())
.setTriggerTime(this.getTriggerTime());
for (var chat : this.getChats().values()) {
proto.addChats(chat.toProto());
}
return proto;
}
}

View File

@@ -70,6 +70,18 @@ public class CharacterStorage extends PlayerManager {
return this.getCharacters().values();
}
public int getNewPhoneMessageCount() {
int count = 0;
for (var character : this.getCharacterCollection()) {
if (character.getContact().hasNew()) {
count++;
}
}
return count;
}
public HandbookInfo getCharacterHandbook() {
var bitset = new Bitset();

View File

@@ -710,9 +710,12 @@ public class Player implements GameDatabaseObject {
proto.addHandbook(this.getCharacters().getCharacterHandbook());
proto.addHandbook(this.getCharacters().getDiscHandbook());
// Phone
var phone = proto.getMutablePhone();
phone.setNewMessage(this.getCharacters().getNewPhoneMessageCount());
// Extra
proto.getMutableAgent();
proto.getMutablePhone();
return proto;
}