diff --git a/README.md b/README.md index beb1d6c..de0384b 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ Latest nightly compiled server jar: [Nightly](https://nightly.link/Melledy/Nebul - Daily quests - Battle pass - Gacha -- Friend system (sending energy not implemented) +- Friend system - Shop (only in-game currency supported) - Commissions - Heartlink diff --git a/src/main/java/emu/nebula/game/friends/FriendList.java b/src/main/java/emu/nebula/game/friends/FriendList.java index 6629f5a..43ca09e 100644 --- a/src/main/java/emu/nebula/game/friends/FriendList.java +++ b/src/main/java/emu/nebula/game/friends/FriendList.java @@ -1,6 +1,7 @@ package emu.nebula.game.friends; import java.util.ArrayList; +import java.util.Collection; import java.util.List; import emu.nebula.GameConstants; @@ -11,9 +12,12 @@ import emu.nebula.game.player.PlayerManager; import emu.nebula.net.NetMsgId; import emu.nebula.proto.FriendListGet.FriendListGetResp; import emu.nebula.proto.Public.FriendDetail; +import emu.nebula.proto.Public.FriendEnergyState; import emu.nebula.proto.Public.FriendState; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.longs.LongArrayList; +import it.unimi.dsi.fastutil.longs.LongList; import lombok.Getter; import us.hebi.quickbuf.RepeatedLong; @@ -90,6 +94,12 @@ public class FriendList extends PlayerManager { return this.getPendingFriends().size() + this.getFriends().size(); } + public synchronized boolean hasEnergy() { + return this.getFriends().values() + .stream() + .anyMatch(f -> f.getEnergy() > 0); + } + public synchronized Player handleFriendRequest(int targetUid, boolean action) { // Make sure we have enough room if (this.getFriends().size() >= GameConstants.MAX_FRIENDSHIPS) { @@ -263,6 +273,127 @@ public class FriendList extends PlayerManager { this.cacheCooldown = 0; } + // Energy + + public synchronized LongList sendEnergy(RepeatedLong uids) { + // Get friendships + Collection list = null; + + if (uids.length() == 0) { + list = this.getFriends().values(); + } else { + list = new ArrayList<>(); + + for (long uid : uids) { + var friend = this.getFriendById((int) uid); + + if (friend == null) { + continue; + } + + list.add(friend); + } + } + + // Send energy + var results = new LongArrayList(); + + for (var friend : list) { + // Sanity + if (!friend.canSendEnergy()) { + continue; + } + + // Update last send timestamp + friend.updateLastSendEnergy(); + friend.save(); + + // Add to results + results.add(friend.getFriendUid()); + + // Send energy to friend + Player target = getGameContext().getPlayerModule().getCachedPlayerByUid(friend.getFriendUid()); + + if (target != null) { + // Get friend's friendship + var theirFriendship = target.getFriendList().getFriendById(this.getPlayer().getUid()); + + // Send energy + theirFriendship.sendEnergy(); + theirFriendship.save(); + + if (target.isLoaded()) { + // Send message to notify friend that we sent them energy + target.addNextPackage( + NetMsgId.friend_energy_state_notify, + FriendEnergyState.newInstance() + .setState(true) + ); + } + } + } + + // Clear friend list cache + if (results.size() > 0) { + this.cacheCooldown = 0; + } + + // Complete + return results; + } + + public synchronized FriendRecvEnergyResult recvEnergy(RepeatedLong uids) { + // Get friendships + Collection list = null; + + if (uids.length() == 0) { + list = this.getFriends().values(); + } else { + list = new ArrayList<>(); + + for (long uid : uids) { + var friend = this.getFriendById((int) uid); + + if (friend == null) { + continue; + } + + list.add(friend); + } + } + + // Send energy + var result = new FriendRecvEnergyResult(); + + for (var friend : list) { + // Skip if we dont have energy + if (friend.getEnergy() <= 0) { + continue; + } + + // Add uid + result.getUids().add(friend.getFriendUid()); + + // Claim energy + result.addEnergy(friend.recvEnergy()); + + // Save to database + friend.save(); + } + + // Add energy + if (result.getEnergy() > 0) { + // Add energy to player + this.getPlayer().addEnergy(result.getEnergy(), result.getChange()); + + // Clear friend list cache + this.cacheCooldown = 0; + } + + // Complete + return result; + } + // Database public synchronized void loadFromDatabase() { @@ -305,6 +436,7 @@ public class FriendList extends PlayerManager { var info = FriendDetail.newInstance() .setBase(base) .setStar(friend.isStar()) + .setSendEnergy(!friend.canSendEnergy()) .setGetEnergy(friend.getEnergy()); // Add diff --git a/src/main/java/emu/nebula/game/friends/FriendRecvEnergyResult.java b/src/main/java/emu/nebula/game/friends/FriendRecvEnergyResult.java new file mode 100644 index 0000000..313038e --- /dev/null +++ b/src/main/java/emu/nebula/game/friends/FriendRecvEnergyResult.java @@ -0,0 +1,22 @@ +package emu.nebula.game.friends; + +import emu.nebula.game.player.PlayerChangeInfo; +import it.unimi.dsi.fastutil.longs.LongArrayList; +import it.unimi.dsi.fastutil.longs.LongList; +import lombok.Getter; + +@Getter +public class FriendRecvEnergyResult { + private PlayerChangeInfo change; + private LongList uids; + private int energy; + + public FriendRecvEnergyResult() { + this.change = new PlayerChangeInfo(); + this.uids = new LongArrayList(); + } + + public void addEnergy(int amount) { + this.energy += amount; + } +} diff --git a/src/main/java/emu/nebula/game/friends/Friendship.java b/src/main/java/emu/nebula/game/friends/Friendship.java index 2e1f7a9..98b7ca9 100644 --- a/src/main/java/emu/nebula/game/friends/Friendship.java +++ b/src/main/java/emu/nebula/game/friends/Friendship.java @@ -21,7 +21,9 @@ public class Friendship implements GameDatabaseObject { @Setter private boolean isFriend; @Setter private boolean star; - @Setter private int energy; + + private int energy; + private long lastSend; @Setter private transient Player player; @@ -35,6 +37,24 @@ public class Friendship implements GameDatabaseObject { this.askerUid = asker.getUid(); this.key = Friendship.generateUniqueKey(player.getUid(), friend.getUid()); } + + public boolean canSendEnergy() { + return this.lastSend != this.getPlayer().getLastEpochDay(); + } + + public void updateLastSendEnergy() { + this.lastSend = this.getPlayer().getLastEpochDay(); + } + + public void sendEnergy() { + this.energy = 1; + } + + public int recvEnergy() { + int amount = this.energy; + this.energy = 0; + return amount; + } // Database functions diff --git a/src/main/java/emu/nebula/game/player/Player.java b/src/main/java/emu/nebula/game/player/Player.java index ccb889c..52cdb04 100644 --- a/src/main/java/emu/nebula/game/player/Player.java +++ b/src/main/java/emu/nebula/game/player/Player.java @@ -907,7 +907,8 @@ public class Player implements GameDatabaseObject { state.getMutableAchievement() .setNew(this.getAchievementManager().hasNewAchievements()); - state.getMutableFriendEnergy(); + state.getMutableFriendEnergy() + .setState(this.getFriendList().hasEnergy()); state.getMutableMallPackage(); state.getMutableTravelerDuelQuest() .setType(QuestType.TravelerDuel); diff --git a/src/main/java/emu/nebula/server/handlers/HandlerFriendReceiveEnergyReq.java b/src/main/java/emu/nebula/server/handlers/HandlerFriendReceiveEnergyReq.java new file mode 100644 index 0000000..57b6481 --- /dev/null +++ b/src/main/java/emu/nebula/server/handlers/HandlerFriendReceiveEnergyReq.java @@ -0,0 +1,34 @@ +package emu.nebula.server.handlers; + +import emu.nebula.net.NetHandler; +import emu.nebula.net.NetMsgId; +import emu.nebula.proto.FriendReceiveEnergy.FriendReceiveEnergyReq; +import emu.nebula.proto.FriendReceiveEnergy.FriendReceiveEnergyResp; +import emu.nebula.net.HandlerId; +import emu.nebula.net.GameSession; + +@HandlerId(NetMsgId.friend_receive_energy_req) +public class HandlerFriendReceiveEnergyReq extends NetHandler { + + @Override + public byte[] handle(GameSession session, byte[] message) throws Exception { + // Parse request + var req = FriendReceiveEnergyReq.parseFrom(message); + + // Send energy + var result = session.getPlayer().getFriendList().recvEnergy(req.getUIds()); + + // Build response + var rsp = FriendReceiveEnergyResp.newInstance() + .setChange(result.getChange().toProto()) + .setReceiveEnergyCnt(result.getEnergy()); + + for (long uid : result.getUids()) { + rsp.addUIds(uid); + } + + // Encode and send + return session.encodeMsg(NetMsgId.friend_receive_energy_succeed_ack, rsp); + } + +} diff --git a/src/main/java/emu/nebula/server/handlers/HandlerFriendSendEnergyReq.java b/src/main/java/emu/nebula/server/handlers/HandlerFriendSendEnergyReq.java new file mode 100644 index 0000000..4465999 --- /dev/null +++ b/src/main/java/emu/nebula/server/handlers/HandlerFriendSendEnergyReq.java @@ -0,0 +1,32 @@ +package emu.nebula.server.handlers; + +import emu.nebula.net.NetHandler; +import emu.nebula.net.NetMsgId; +import emu.nebula.proto.FriendSendEnergy.FriendSendEnergyReq; +import emu.nebula.proto.FriendSendEnergy.FriendSendEnergyResp; +import emu.nebula.net.HandlerId; +import emu.nebula.net.GameSession; + +@HandlerId(NetMsgId.friend_send_energy_req) +public class HandlerFriendSendEnergyReq extends NetHandler { + + @Override + public byte[] handle(GameSession session, byte[] message) throws Exception { + // Parse request + var req = FriendSendEnergyReq.parseFrom(message); + + // Send energy + var results = session.getPlayer().getFriendList().sendEnergy(req.getUIds()); + + // Build response + var rsp = FriendSendEnergyResp.newInstance(); + + for (long uid : results) { + rsp.addUIds(uid); + } + + // Encode and send + return session.encodeMsg(NetMsgId.friend_send_energy_succeed_ack, rsp); + } + +}