From aaf630a3a237b6548b79729fc89fe29932e04fbb Mon Sep 17 00:00:00 2001 From: Mads Boddum Date: Sat, 30 Apr 2016 08:52:28 +0200 Subject: [PATCH] Lots of work on leveling, XP and skills --- src/intents/experience/ExperienceIntent.java | 74 ++++++ .../experience/LevelChangedIntent.java | 64 ++++++ .../experience/SkillBoxGrantedIntent.java | 58 +++++ .../ChangeRoleIconChoice.java | 70 ++++++ .../object_controller/ObjectController.java | 1 + .../objects/creature/CreatureObject.java | 22 +- .../objects/player/PlayerObject.java | 55 +++-- .../experience/ExperienceManager.java | 143 ++++++++++++ src/services/experience/SkillService.java | 217 ++++++++++++++++++ .../experience/SkillTemplateService.java | 100 ++++++++ src/services/galaxy/GameManager.java | 4 + .../player/CharacterCreationService.java | 2 + 12 files changed, 776 insertions(+), 34 deletions(-) create mode 100644 src/intents/experience/ExperienceIntent.java create mode 100644 src/intents/experience/LevelChangedIntent.java create mode 100644 src/intents/experience/SkillBoxGrantedIntent.java create mode 100644 src/network/packets/swg/zone/object_controller/ChangeRoleIconChoice.java create mode 100644 src/services/experience/ExperienceManager.java create mode 100644 src/services/experience/SkillService.java create mode 100644 src/services/experience/SkillTemplateService.java diff --git a/src/intents/experience/ExperienceIntent.java b/src/intents/experience/ExperienceIntent.java new file mode 100644 index 000000000..2103d46ec --- /dev/null +++ b/src/intents/experience/ExperienceIntent.java @@ -0,0 +1,74 @@ +/*********************************************************************************** +* Copyright (c) 2015 /// Project SWG /// www.projectswg.com * +* * +* ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on * +* July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. * +* Our goal is to create an emulator which will provide a server for players to * +* continue playing a game similar to the one they used to play. We are basing * +* it on the final publish of the game prior to end-game events. * +* * +* This file is part of Holocore. * +* * +* -------------------------------------------------------------------------------- * +* * +* Holocore is free software: you can redistribute it and/or modify * +* it under the terms of the GNU Affero General Public License as * +* published by the Free Software Foundation, either version 3 of the * +* License, or (at your option) any later version. * +* * +* Holocore is distributed in the hope that it will be useful, * +* but WITHOUT ANY WARRANTY; without even the implied warranty of * +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +* GNU Affero General Public License for more details. * +* * +* You should have received a copy of the GNU Affero General Public License * +* along with Holocore. If not, see . * +* * +***********************************************************************************/ +package intents.experience; + +import resources.control.Intent; +import resources.objects.creature.CreatureObject; + +/** + * + * @author Mads + */ +public final class ExperienceIntent extends Intent { + + public static final String TYPE = "ExperienceIntent"; + + private final CreatureObject creatureObject; + private final String xpType; + private final int experienceGained; + private final EventType eventType; + + public ExperienceIntent(EventType eventType, CreatureObject creatureObject, String xpType, int experienceGained) { + super(TYPE); + this.eventType = eventType; + this.creatureObject = creatureObject; + this.xpType = xpType; + this.experienceGained = experienceGained; + } + + public EventType getEventType() { + return eventType; + } + + public CreatureObject getCreatureObject() { + return creatureObject; + } + + public String getXpType() { + return xpType; + } + + public int getExperienceGained() { + return experienceGained; + } + + public enum EventType { + GRANT, GIVEN + } + +} diff --git a/src/intents/experience/LevelChangedIntent.java b/src/intents/experience/LevelChangedIntent.java new file mode 100644 index 000000000..2176816af --- /dev/null +++ b/src/intents/experience/LevelChangedIntent.java @@ -0,0 +1,64 @@ +/*********************************************************************************** +* Copyright (c) 2015 /// Project SWG /// www.projectswg.com * +* * +* ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on * +* July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. * +* Our goal is to create an emulator which will provide a server for players to * +* continue playing a game similar to the one they used to play. We are basing * +* it on the final publish of the game prior to end-game events. * +* * +* This file is part of Holocore. * +* * +* -------------------------------------------------------------------------------- * +* * +* Holocore is free software: you can redistribute it and/or modify * +* it under the terms of the GNU Affero General Public License as * +* published by the Free Software Foundation, either version 3 of the * +* License, or (at your option) any later version. * +* * +* Holocore is distributed in the hope that it will be useful, * +* but WITHOUT ANY WARRANTY; without even the implied warranty of * +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +* GNU Affero General Public License for more details. * +* * +* You should have received a copy of the GNU Affero General Public License * +* along with Holocore. If not, see . * +* * +***********************************************************************************/ +package intents.experience; + +import resources.control.Intent; +import resources.objects.creature.CreatureObject; + +/** + * + * @author Mads + */ +public final class LevelChangedIntent extends Intent { + + public static final String TYPE = "LevelChangedIntent"; + + private final CreatureObject creatureObject; + private final short previousLevel; + private final short newLevel; + + public LevelChangedIntent(CreatureObject creatureObject, short previousLevel, short newLevel) { + super(TYPE); + this.creatureObject = creatureObject; + this.previousLevel = previousLevel; + this.newLevel = newLevel; + } + + public CreatureObject getCreatureObject() { + return creatureObject; + } + + public short getPreviousLevel() { + return previousLevel; + } + + public short getNewLevel() { + return newLevel; + } + +} diff --git a/src/intents/experience/SkillBoxGrantedIntent.java b/src/intents/experience/SkillBoxGrantedIntent.java new file mode 100644 index 000000000..55e2365e8 --- /dev/null +++ b/src/intents/experience/SkillBoxGrantedIntent.java @@ -0,0 +1,58 @@ +/*********************************************************************************** +* Copyright (c) 2015 /// Project SWG /// www.projectswg.com * +* * +* ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on * +* July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. * +* Our goal is to create an emulator which will provide a server for players to * +* continue playing a game similar to the one they used to play. We are basing * +* it on the final publish of the game prior to end-game events. * +* * +* This file is part of Holocore. * +* * +* -------------------------------------------------------------------------------- * +* * +* Holocore is free software: you can redistribute it and/or modify * +* it under the terms of the GNU Affero General Public License as * +* published by the Free Software Foundation, either version 3 of the * +* License, or (at your option) any later version. * +* * +* Holocore is distributed in the hope that it will be useful, * +* but WITHOUT ANY WARRANTY; without even the implied warranty of * +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +* GNU Affero General Public License for more details. * +* * +* You should have received a copy of the GNU Affero General Public License * +* along with Holocore. If not, see . * +* * +***********************************************************************************/ +package intents.experience; + +import resources.control.Intent; +import resources.objects.creature.CreatureObject; + +/** + * + * @author Mads + */ +public final class SkillBoxGrantedIntent extends Intent { + + public static final String TYPE = "SkillBoxGrantedIntent"; + + private String skillName; + private CreatureObject target; + + public SkillBoxGrantedIntent(String skillName, CreatureObject target) { + super(TYPE); + this.skillName = skillName; + this.target = target; + } + + public String getSkillName() { + return skillName; + } + + public CreatureObject getTarget() { + return target; + } + +} diff --git a/src/network/packets/swg/zone/object_controller/ChangeRoleIconChoice.java b/src/network/packets/swg/zone/object_controller/ChangeRoleIconChoice.java new file mode 100644 index 000000000..b7d7c7b54 --- /dev/null +++ b/src/network/packets/swg/zone/object_controller/ChangeRoleIconChoice.java @@ -0,0 +1,70 @@ +/*********************************************************************************** +* Copyright (c) 2015 /// Project SWG /// www.projectswg.com * +* * +* ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on * +* July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. * +* Our goal is to create an emulator which will provide a server for players to * +* continue playing a game similar to the one they used to play. We are basing * +* it on the final publish of the game prior to end-game events. * +* * +* This file is part of Holocore. * +* * +* -------------------------------------------------------------------------------- * +* * +* Holocore is free software: you can redistribute it and/or modify * +* it under the terms of the GNU Affero General Public License as * +* published by the Free Software Foundation, either version 3 of the * +* License, or (at your option) any later version. * +* * +* Holocore is distributed in the hope that it will be useful, * +* but WITHOUT ANY WARRANTY; without even the implied warranty of * +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +* GNU Affero General Public License for more details. * +* * +* You should have received a copy of the GNU Affero General Public License * +* along with Holocore. If not, see . * +* * +***********************************************************************************/ +package network.packets.swg.zone.object_controller; + +import java.nio.ByteBuffer; + +public class ChangeRoleIconChoice extends ObjectController { + + public static final int CRC = 1101; + + private int iconChoice; + + public ChangeRoleIconChoice(long objectId) { + super(objectId, CRC); + } + + public ChangeRoleIconChoice(ByteBuffer data) { + super(CRC); + decode(data); + } + + public ChangeRoleIconChoice(long objectId, int iconChoice) { + super(objectId, CRC); + this.iconChoice = iconChoice; + } + + @Override + public void decode(ByteBuffer data) { + decodeHeader(data); + iconChoice = getInt(data); + } + + @Override + public ByteBuffer encode() { + ByteBuffer data = ByteBuffer.allocate(HEADER_LENGTH + Integer.BYTES); + encodeHeader(data); + addInt(data, iconChoice); + return data; + } + + public int getIconChoice() { + return iconChoice; + } + +} diff --git a/src/network/packets/swg/zone/object_controller/ObjectController.java b/src/network/packets/swg/zone/object_controller/ObjectController.java index ba6650474..b8e93e723 100644 --- a/src/network/packets/swg/zone/object_controller/ObjectController.java +++ b/src/network/packets/swg/zone/object_controller/ObjectController.java @@ -98,6 +98,7 @@ public abstract class ObjectController extends SWGPacket { case 0x0131: return new PostureUpdate(data); case 0x0146: return new ObjectMenuRequest(data); case 0x04C5: return new IntendedTarget(data); + case ChangeRoleIconChoice.CRC: return new ChangeRoleIconChoice(data); } Log.w("ObjectController", "Unknown object controller: %08X", crc); return new GenericObjectController(crc, data); diff --git a/src/resources/objects/creature/CreatureObject.java b/src/resources/objects/creature/CreatureObject.java index 13930b4c5..9e8cb7d02 100644 --- a/src/resources/objects/creature/CreatureObject.java +++ b/src/resources/objects/creature/CreatureObject.java @@ -112,7 +112,7 @@ public class CreatureObject extends TangibleObject { private SWGSet missionCriticalObjs = new SWGSet<>(4, 13); private SWGList baseAttributes = new SWGList(1, 2); - private SWGList skills = new SWGList(1, 3, StringType.ASCII); // SWGSet + private SWGSet skills = new SWGSet(1, 3, StringType.ASCII); private SWGList hamEncumbList = new SWGList(4, 2); private SWGList attributes = new SWGList(6, 21); private SWGList maxAttributes = new SWGList(6, 22); @@ -187,7 +187,16 @@ public class CreatureObject extends TangibleObject { return appearanceList; } - public SWGList getSkills() { + public void addSkill(String skillName) { + skills.add(skillName); + skills.sendDeltaMessage(this); + } + + public boolean hasSkill(String skillName) { + return skills.contains(skillName); + } + + public SWGSet getSkills() { return skills; } @@ -263,7 +272,7 @@ public class CreatureObject extends TangibleObject { return guildId; } - public int getLevel() { + public short getLevel() { return level; } @@ -713,7 +722,10 @@ public class CreatureObject extends TangibleObject { return attributes.get(2); } - public void addAbility(String abilityName){ abilities.put(abilityName, 1); }//TODO: Figure out what the integer value should be for each ability + public void addAbility(String abilityName){ + abilities.put(abilityName, 1); //TODO: Figure out what the integer value should be for each ability + abilities.sendDeltaMessage(this); + } public void removeAbility(String abilityName) { abilities.remove(abilityName); } @@ -935,7 +947,7 @@ public class CreatureObject extends TangibleObject { bankBalance = buffer.getInt(); cashBalance = buffer.getInt(); baseAttributes = buffer.getSwgList(1, 2, Integer.class); - skills = buffer.getSwgList(1, 2, StringType.ASCII); + skills = buffer.getSwgSet(1, 2, StringType.ASCII); } protected void parseBaseline3(NetBuffer buffer) { diff --git a/src/resources/objects/player/PlayerObject.java b/src/resources/objects/player/PlayerObject.java index 616766b47..3c3209447 100644 --- a/src/resources/objects/player/PlayerObject.java +++ b/src/resources/objects/player/PlayerObject.java @@ -63,6 +63,7 @@ public class PlayerObject extends IntangibleObject { private String title = ""; private int bornDate = 0; private int playTime = 0; + private int professionIcon = 0; private String profession = ""; private int gcwPoints = 0; private int pvpKills = 0; @@ -81,7 +82,7 @@ public class PlayerObject extends IntangibleObject { private int gcwNextUpdate = 0; private String home = ""; // PLAY 08 - private SWGMap experience = new SWGMap<>(8, 0); + private SWGMap experience = new SWGMap<>(8, 0, StringType.ASCII); private SWGMap waypoints = new SWGMap<>(8, 1); private boolean citizen = false; private int guildRankTitle = 0; @@ -92,7 +93,7 @@ public class PlayerObject extends IntangibleObject { private int experimentFlag = 0; private int craftingStage = 0; private long nearbyCraftStation = 0; - private SWGList draftSchemList = new SWGList<>(9, 3); + private SWGList draftSchemList = new SWGList<>(9, 3, StringType.ASCII); private int experimentPoints = 0; private SWGList friendsList = new SWGList<>(9, 7, StringType.ASCII); private SWGList ignoreList = new SWGList<>(9, 8, StringType.ASCII); @@ -403,7 +404,7 @@ public class PlayerObject extends IntangibleObject { public void setProfWheelPosition(String profWheelPosition) { this.profWheelPosition = profWheelPosition; - sendDelta(8, 8, profWheelPosition); + sendDelta(8, 8, profWheelPosition, StringType.ASCII); } public void setFlagBitmask(PlayerFlags ... flags) { @@ -446,31 +447,27 @@ public class PlayerObject extends IntangibleObject { } } - private int getProfessionIcon() { - switch (profession) { - case "entertainer_1a": - return 5; - case "medic_1a": - return 10; - case "officer_1a": - return 15; - case "bounty_hunter_1a": - return 20; - case "smuggler_1a": - return 25; - case "commando_1a": - return 30; - case "spy_1a": - return 35; - case "force_sensitive_1a": - return 40; - case "trader_0a": - case "trader_0b": - case "trader_0c": - case "trader_0d": - default: - return 0; - } + public void setProfessionIcon(int professionIcon) { + this.professionIcon = professionIcon; + sendDelta(3, 10, professionIcon); + } + + public int getProfessionIcon() { + return professionIcon; + } + + public void addDraftSchematic(String schematic) { + draftSchemList.add(schematic); + draftSchemList.sendDeltaMessage(this); + } + + public Integer getExperiencePoints(String xpType) { + return experience.get(xpType); + } + + public void setExperiencePoints(String xpType, int experiencePoints) { + experience.put(xpType, experiencePoints); + experience.sendDeltaMessage(this); } @Override @@ -497,7 +494,7 @@ public class PlayerObject extends IntangibleObject { bb.addAscii(title); // 7 bb.addInt(bornDate); // Born Date -- 4001 = 12/15/2011 || Number of days after 12/31/2000 -- 8 bb.addInt(playTime); // 9 - bb.addInt(getProfessionIcon()); // 10 + bb.addInt(professionIcon); // 10 bb.addAscii(profession); // 11 bb.addInt(gcwPoints); // 12 bb.addInt(pvpKills); // 13 diff --git a/src/services/experience/ExperienceManager.java b/src/services/experience/ExperienceManager.java new file mode 100644 index 000000000..326a6dcd1 --- /dev/null +++ b/src/services/experience/ExperienceManager.java @@ -0,0 +1,143 @@ +/*********************************************************************************** +* Copyright (c) 2015 /// Project SWG /// www.projectswg.com * +* * +* ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on * +* July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. * +* Our goal is to create an emulator which will provide a server for players to * +* continue playing a game similar to the one they used to play. We are basing * +* it on the final publish of the game prior to end-game events. * +* * +* This file is part of Holocore. * +* * +* -------------------------------------------------------------------------------- * +* * +* Holocore is free software: you can redistribute it and/or modify * +* it under the terms of the GNU Affero General Public License as * +* published by the Free Software Foundation, either version 3 of the * +* License, or (at your option) any later version. * +* * +* Holocore is distributed in the hope that it will be useful, * +* but WITHOUT ANY WARRANTY; without even the implied warranty of * +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +* GNU Affero General Public License for more details. * +* * +* You should have received a copy of the GNU Affero General Public License * +* along with Holocore. If not, see . * +* * +***********************************************************************************/ +package services.experience; + +import intents.experience.ExperienceIntent; +import intents.experience.LevelChangedIntent; +import java.util.HashMap; +import java.util.Map; +import resources.client_info.ClientFactory; +import resources.client_info.visitors.DatatableData; +import resources.control.Intent; +import resources.control.Manager; +import resources.objects.creature.CreatureObject; +import resources.objects.player.PlayerObject; +import resources.server_info.Log; + +/** + * The {@code ExperienceManager} listens for {@link ExperienceIntent} and + * grants XP based on it. + * @author Mads + */ +public final class ExperienceManager extends Manager { + + private SkillService skillService; + private SkillTemplateService skillTemplateService; + private final Map levelXpMap; + + public ExperienceManager() { + skillService = new SkillService(); + skillTemplateService = new SkillTemplateService(); + levelXpMap = new HashMap<>(); + + registerForIntent(ExperienceIntent.TYPE); + + addChildService(skillService); + addChildService(skillTemplateService); + } + + @Override + public boolean initialize() { + DatatableData skillTemplateTable = (DatatableData) ClientFactory.getInfoFromFile("datatables/player/player_level.iff", true); + + for (int row = 0; row < skillTemplateTable.getRowCount(); row++) { + int level = (int) skillTemplateTable.getCell(row, 0); + int xpRequired = (int) skillTemplateTable.getCell(row, 1); + + levelXpMap.put((short) level, xpRequired); + } + + return super.initialize(); + } + + @Override + public void onIntentReceived(Intent i) { + switch(i.getType()) { + case ExperienceIntent.TYPE: handleExperienceGainedIntent((ExperienceIntent) i); break; + } + } + + private void handleExperienceGainedIntent(ExperienceIntent i) { + if (i.getEventType() == ExperienceIntent.EventType.GRANT) { + CreatureObject creatureObject = i.getCreatureObject(); + PlayerObject playerObject = creatureObject.getPlayerObject(); + String xpType = i.getXpType(); + int xpGained = i.getExperienceGained(); + + if (playerObject != null) { + Integer currentXp = playerObject.getExperiencePoints(xpType); + int newXpTotal; + + if (currentXp == null) { // They don't have this type of XP already + newXpTotal = xpGained; + } else { // They already have this kind of XP - add gained to current + newXpTotal = currentXp + xpGained; + } + + playerObject.setExperiencePoints(xpType, newXpTotal); + creatureObject.setTotalLevelXp(newXpTotal); + new ExperienceIntent(ExperienceIntent.EventType.GIVEN, creatureObject, xpType, xpGained).broadcast(); + Log.d(this, "%s gained %d %s XP", creatureObject, xpGained, xpType); + // TODO show +XP flytext + + // At this point, we check if their level should be adjusted. + short currentLevel = creatureObject.getLevel(); + + if (currentLevel == getMaxLevel()) { + // This player has already reached max level + Log.d(this, "%s is already max level (%d) - skipping remaining checks", creatureObject, currentLevel); + } else { + short nextLevel = (short) (currentLevel + 1); + Integer xpNextLevel = levelXpMap.get(nextLevel); + + if (xpNextLevel != null) { + if (newXpTotal >= xpNextLevel) { + new LevelChangedIntent(creatureObject, currentLevel, nextLevel).broadcast(); + creatureObject.setLevel(nextLevel); + // TODO increase health of creatureObject + // TODO flytext object.showFlyText(OutOfBand.ProsePackage("@cbt_spam:skill_up"), 2.5f, new RGB(154, 205, 50), 0, true); + // TODO client effect clienteffect/skill_granted.cef + // TODO audio sound/music_acq_bountyhunter.snd + Log.i(this, "%s leveled up to %d from %d", creatureObject, currentLevel, nextLevel); + } else { + Log.d(this, "%s didn't gain enough %s XP to level up from %d to %d", creatureObject, xpType, currentLevel, nextLevel); + } + } else { + Log.e(this, "%s can't become level %d because it was not found in the level-to-XP Map", creatureObject, nextLevel); + } + } + } else { + Log.e(this, "%d %s XP to %s failed because XP can't be given to NPCs", xpGained, xpType, creatureObject); + } + } + } + + private int getMaxLevel() { + return levelXpMap.size(); + } +} diff --git a/src/services/experience/SkillService.java b/src/services/experience/SkillService.java new file mode 100644 index 000000000..b4b12569f --- /dev/null +++ b/src/services/experience/SkillService.java @@ -0,0 +1,217 @@ +/*********************************************************************************** +* Copyright (c) 2015 /// Project SWG /// www.projectswg.com * +* * +* ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on * +* July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. * +* Our goal is to create an emulator which will provide a server for players to * +* continue playing a game similar to the one they used to play. We are basing * +* it on the final publish of the game prior to end-game events. * +* * +* This file is part of Holocore. * +* * +* -------------------------------------------------------------------------------- * +* * +* Holocore is free software: you can redistribute it and/or modify * +* it under the terms of the GNU Affero General Public License as * +* published by the Free Software Foundation, either version 3 of the * +* License, or (at your option) any later version. * +* * +* Holocore is distributed in the hope that it will be useful, * +* but WITHOUT ANY WARRANTY; without even the implied warranty of * +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +* GNU Affero General Public License for more details. * +* * +* You should have received a copy of the GNU Affero General Public License * +* along with Holocore. If not, see . * +* * +***********************************************************************************/ +package services.experience; + +import intents.experience.LevelChangedIntent; +import intents.experience.SkillBoxGrantedIntent; +import intents.network.GalacticPacketIntent; +import java.util.HashMap; +import java.util.Map; +import network.packets.Packet; +import network.packets.swg.zone.object_controller.ChangeRoleIconChoice; +import resources.client_info.ClientFactory; +import resources.client_info.visitors.DatatableData; +import resources.control.Intent; +import resources.control.Service; +import resources.objects.SWGObject; +import resources.objects.creature.CreatureObject; +import resources.objects.player.PlayerObject; +import resources.server_info.Log; + +/** + * + * @author Mads + */ +public final class SkillService extends Service { + + // Maps icon index to qualifying skill. + private Map roleIconMap; + private Map skillDataMap; + + public SkillService() { + roleIconMap = new HashMap<>(); + skillDataMap = new HashMap<>(); + registerForIntent(SkillBoxGrantedIntent.TYPE); + registerForIntent(GalacticPacketIntent.TYPE); + } + + @Override + public void onIntentReceived(Intent i) { + switch(i.getType()) { + case SkillBoxGrantedIntent.TYPE: handleSkillAddIntent((SkillBoxGrantedIntent) i); break; + case GalacticPacketIntent.TYPE: handleGalacticPacket((GalacticPacketIntent) i); + } + } + + @Override + public boolean initialize() { + DatatableData roleIconTable = (DatatableData) ClientFactory.getInfoFromFile("datatables/role/role.iff"); + + for (int i = 0; i < roleIconTable.getRowCount(); i++) { + int iconIndex = (int) roleIconTable.getCell(i, 0); + String qualifyingSkill = (String) roleIconTable.getCell(i, 2); + + roleIconMap.put(iconIndex, qualifyingSkill); + } + + DatatableData skillsTable = (DatatableData) ClientFactory.getInfoFromFile("datatables/skill/skills.iff", true); + + for (int i = 0; i < skillsTable.getRowCount(); i++) { + String skillName = (String) skillsTable.getCell(i, 0); + String requiredSkillString = (String) skillsTable.getCell(i, 11); + String xpType = (String) skillsTable.getCell(i, 12); + int xpCost = (int) skillsTable.getCell(i, 13); + String commandsString = (String) skillsTable.getCell(i, 21); + String schematicsString = (String) skillsTable.getCell(i, 23); + + String skillModsString = (String) skillsTable.getCell(i, 22); + Map skillMods = new HashMap<>(); + if(!skillModsString.isEmpty()) { + String[] skillModsStrings = skillModsString.split(","); + + for(String skillModString : skillModsStrings) { + String[] values = skillModString.split("="); + skillMods.put(values[0], Integer.parseInt(values[1])); + } + } + + String[] requiredSkills = requiredSkillString.split(","); + if(requiredSkills.length == 1 && requiredSkills[0].isEmpty()) { + requiredSkills = new String[0]; + } + + String[] commands = commandsString.split(","); + if(commands.length == 1 && commands[0].isEmpty()) { + commands = new String[0]; + } + + String[] schematics = schematicsString.split(","); + if(schematics.length == 1 && schematics[0].isEmpty()) { + schematics = new String[0]; + } + + SkillData skillData = new SkillData( + requiredSkills, + xpType, + xpCost, + commands, + skillMods, + schematics + ); + + skillDataMap.put(skillName, skillData); + } + + return super.initialize(); + } + + private void handleSkillAddIntent(SkillBoxGrantedIntent intent) { + String skillName = intent.getSkillName(); + CreatureObject target = intent.getTarget(); + SkillData skillData = skillDataMap.get(skillName); + String[] requiredSkills = skillData.requiredSkills; + PlayerObject playerObject = target.getPlayerObject(); + + for(String requiredSkill : requiredSkills) { + if(!target.hasSkill(requiredSkill)) { + Log.w(this, "%s lacks required skill %s before being granted skill %s", target, requiredSkill, skillName); + return; + } + } + + target.addSkill(skillName); + + for(String commandName : skillData.commands) { + target.addAbility(commandName); + } + + skillData.skillMods.forEach((skillModName, skillModValue) -> target.adjustSkillmod(skillModName, 0, skillModValue)); + + for(String schematic : skillData.schematics) { + playerObject.addDraftSchematic(schematic); + } + + Log.d(this, "%s was given skill %s", target, skillName); + } + + private void handleGalacticPacket(GalacticPacketIntent gpi) { + Packet packet = gpi.getPacket(); + if (packet instanceof ChangeRoleIconChoice) { + ChangeRoleIconChoice iconChoice = (ChangeRoleIconChoice) packet; + + int chosenIcon = iconChoice.getIconChoice(); + SWGObject object = gpi.getObjectManager().getObjectById(iconChoice.getObjectId()); + + if(object instanceof CreatureObject) { + changeRoleIcon((CreatureObject) object, chosenIcon); + } else { + Log.e(this, "Could not alter role icon for object %s because it's not a CreatureObject", object); + } + } + } + + private void changeRoleIcon(CreatureObject creature, int chosenIcon) { + String qualifyingSkill = roleIconMap.get(chosenIcon); + + if (qualifyingSkill != null) { + if (creature.hasSkill(qualifyingSkill)) { + PlayerObject playerObject = creature.getPlayerObject(); + + if(playerObject != null) { + playerObject.setProfessionIcon(chosenIcon); + } else { + Log.e(this, "Could not alter role icon for PlayerObject of %s because it has none attached" , creature); + } + } else { + Log.w(this, "%s cannot use role icon %d because they lack the qualifying skill %s", creature, chosenIcon, qualifyingSkill); + } + } else { + Log.w(this, "%s tried to use undefined role icon %d", creature, chosenIcon); + } + } + + private static class SkillData { + private String[] requiredSkills; + private String xpType; + private int xpCost; + private String[] commands; + private Map skillMods; + private String[] schematics; + + public SkillData(String[] requiredSkills, String xpType, int xpCost, String[] commands, Map skillMods, String[] schematics) { + this.requiredSkills = requiredSkills; + this.xpType = xpType; + this.xpCost = xpCost; + this.commands = commands; + this.skillMods = skillMods; + this.schematics = schematics; + } + + } + +} diff --git a/src/services/experience/SkillTemplateService.java b/src/services/experience/SkillTemplateService.java new file mode 100644 index 000000000..71c096c72 --- /dev/null +++ b/src/services/experience/SkillTemplateService.java @@ -0,0 +1,100 @@ +/*********************************************************************************** +* Copyright (c) 2015 /// Project SWG /// www.projectswg.com * +* * +* ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on * +* July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. * +* Our goal is to create an emulator which will provide a server for players to * +* continue playing a game similar to the one they used to play. We are basing * +* it on the final publish of the game prior to end-game events. * +* * +* This file is part of Holocore. * +* * +* -------------------------------------------------------------------------------- * +* * +* Holocore is free software: you can redistribute it and/or modify * +* it under the terms of the GNU Affero General Public License as * +* published by the Free Software Foundation, either version 3 of the * +* License, or (at your option) any later version. * +* * +* Holocore is distributed in the hope that it will be useful, * +* but WITHOUT ANY WARRANTY; without even the implied warranty of * +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +* GNU Affero General Public License for more details. * +* * +* You should have received a copy of the GNU Affero General Public License * +* along with Holocore. If not, see . * +* * +***********************************************************************************/ +package services.experience; + +import intents.experience.LevelChangedIntent; +import intents.experience.SkillBoxGrantedIntent; +import java.util.HashMap; +import java.util.Map; +import resources.client_info.ClientFactory; +import resources.client_info.visitors.DatatableData; +import resources.control.Intent; +import resources.control.Service; +import resources.objects.creature.CreatureObject; +import resources.objects.player.PlayerObject; +import resources.server_info.Log; + +/** + * + * @author Mads + */ +public final class SkillTemplateService extends Service { + + private final Map skillTemplates; + + SkillTemplateService() { + skillTemplates = new HashMap<>(); + registerForIntent(LevelChangedIntent.TYPE); + } + + @Override + public boolean initialize() { + DatatableData skillTemplateTable = (DatatableData) ClientFactory.getInfoFromFile("datatables/skill_template/skill_template.iff"); + + for (int row = 0; row < skillTemplateTable.getRowCount(); row++) { + String profession = (String) skillTemplateTable.getCell(row, 0); + String[] templates = ((String) skillTemplateTable.getCell(row, 4)).split(","); + + skillTemplates.put(profession, templates); + } + return super.initialize(); + } + + @Override + public void onIntentReceived(Intent i) { + switch(i.getType()) { + case LevelChangedIntent.TYPE: handleLevelChangedIntent((LevelChangedIntent) i); break; + } + } + + private void handleLevelChangedIntent(LevelChangedIntent i) { + short newLevel = i.getNewLevel(); + CreatureObject creatureObject = i.getCreatureObject(); + + // Skills are only awarded every third or fourth level + if ((newLevel == 4 || newLevel == 7 || newLevel == 10) || ((newLevel > 10) && (((newLevel - 10) % 4) == 0))) { + PlayerObject playerObject = creatureObject.getPlayerObject(); + String profession = playerObject.getProfession(); + String[] templates = skillTemplates.get(profession); + + if(templates == null) { + Log.w(this, "%s tried to level up to %d with invalid profession %s", creatureObject, newLevel, profession); + } else { + int skillIndex = ((newLevel <= 10) ? ((newLevel - 1) / 3) : ((((newLevel - 10) / 4)) + 3)); + + String skillName = templates[skillIndex]; + new SkillBoxGrantedIntent(skillName, creatureObject).broadcast(); + playerObject.setProfWheelPosition(skillName); + // TODO roadmap reward items + } + } else { + Log.d(this, "Level %d has no skillbox - %s is rewarded nothing", newLevel, creatureObject); + } + } + +} diff --git a/src/services/galaxy/GameManager.java b/src/services/galaxy/GameManager.java index 792ecb043..a353da990 100644 --- a/src/services/galaxy/GameManager.java +++ b/src/services/galaxy/GameManager.java @@ -31,6 +31,7 @@ import resources.control.Manager; import services.collections.CollectionBadgeManager; import services.commands.CommandService; import services.commands.EntertainmentService; +import services.experience.ExperienceManager; import services.faction.FactionService; import services.galaxy.terminals.TerminalService; import services.sui.SuiService; @@ -47,6 +48,7 @@ public class GameManager extends Manager { //private final GroupService groupService; private final SkillModService skillModService; private final EntertainmentService entertainmentService; + private final ExperienceManager experienceManager; public GameManager() { commandService = new CommandService(); @@ -59,6 +61,7 @@ public class GameManager extends Manager { // groupService = new GroupService(); skillModService = new SkillModService(); entertainmentService = new EntertainmentService(); + experienceManager = new ExperienceManager(); addChildService(commandService); addChildService(connectionService); @@ -70,5 +73,6 @@ public class GameManager extends Manager { // addChildService(groupService); addChildService(skillModService); addChildService(entertainmentService); + addChildService(experienceManager); } } diff --git a/src/services/player/CharacterCreationService.java b/src/services/player/CharacterCreationService.java index ddaa83ad7..9bc65e1b5 100644 --- a/src/services/player/CharacterCreationService.java +++ b/src/services/player/CharacterCreationService.java @@ -29,6 +29,7 @@ package services.player; import intents.GalacticIntent; import intents.PlayerEventIntent; +import intents.experience.SkillBoxGrantedIntent; import java.sql.PreparedStatement; import java.sql.ResultSet; @@ -317,6 +318,7 @@ public class CharacterCreationService extends Service { creatureObj.addObject(playerObj); // ghost slot playerObj.setAdminTag(player.getAccessLevel()); player.setCreatureObject(creatureObj); + new SkillBoxGrantedIntent(create.getStartingPhase(), creatureObj).broadcast(); return creatureObj.getObjectId(); }