diff --git a/src/intents/experience/ExperienceIntent.java b/src/intents/experience/ExperienceIntent.java new file mode 100644 index 000000000..084064381 --- /dev/null +++ b/src/intents/experience/ExperienceIntent.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 ExperienceIntent extends Intent { + + public static final String TYPE = "ExperienceIntent"; + + private final CreatureObject creatureObject; + private final String xpType; + private final int experienceGained; + + public ExperienceIntent(CreatureObject creatureObject, String xpType, int experienceGained) { + super(TYPE); + this.creatureObject = creatureObject; + this.xpType = xpType; + this.experienceGained = experienceGained; + } + + public CreatureObject getCreatureObject() { + return creatureObject; + } + + public String getXpType() { + return xpType; + } + + public int getExperienceGained() { + return experienceGained; + } + +} 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..d3dafbc7d --- /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 = 0x044D; + + 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 f9f7c4bb0..22abeee3b 100644 --- a/src/network/packets/swg/zone/object_controller/ObjectController.java +++ b/src/network/packets/swg/zone/object_controller/ObjectController.java @@ -101,6 +101,7 @@ public abstract class ObjectController extends SWGPacket { case 0x0134: return new CombatSpam(data); case 0x0146: return new ObjectMenuRequest(data); case 0x01BD: return new ShowFlyText(data); + case 0x044D: return new ChangeRoleIconChoice(data); case 0x04C5: return new IntendedTarget(data); } Log.w("ObjectController", "Unknown object controller: %08X", crc); diff --git a/src/resources/objects/creature/CreatureObject.java b/src/resources/objects/creature/CreatureObject.java index 462824bdc..db49fe550 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; } @@ -636,25 +645,29 @@ public class CreatureObject extends TangibleObject { } public void adjustSkillmod(String skillModName, int base, int modifier) { - SkillMod skillMod = skillMods.get(skillModName); - - if(skillMod == null) { - // They didn't have this SkillMod already. - // Therefore, we send a full delta. - skillMods.put(skillModName, new SkillMod(base, modifier)); - skillMods.sendDeltaMessage(this); - } else { - // They already had this skillmod. - // All we need to do is adjust the base and the modifier and send an update from the SWGMap - skillMod.adjustBase(base); - skillMod.adjustModifier(modifier); - skillMods.update(skillModName, this); + synchronized(skillMods) { + SkillMod skillMod = skillMods.get(skillModName); + + if(skillMod == null) { + // They didn't have this SkillMod already. + // Therefore, we send a full delta. + skillMods.put(skillModName, new SkillMod(base, modifier)); + skillMods.sendDeltaMessage(this); + } else { + // They already had this skillmod. + // All we need to do is adjust the base and the modifier and send an update from the SWGMap + skillMod.adjustBase(base); + skillMod.adjustModifier(modifier); + skillMods.update(skillModName, this); + } } } public int getSkillModValue(String skillModName) { - SkillMod skillMod = skillMods.get(skillModName); - return skillMod != null ? skillMod.getValue() : 0; + synchronized(skillMods) { + SkillMod skillMod = skillMods.get(skillModName); + return skillMod != null ? skillMod.getValue() : 0; + } } public boolean isVisible() { @@ -743,7 +756,12 @@ public class CreatureObject extends TangibleObject { } } - 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){ + synchronized(abilities) { + 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); } @@ -751,6 +769,13 @@ public class CreatureObject extends TangibleObject { public Set getAbilityNames() { return abilities.keySet(); }; + public void setBaseHealth(int baseHealth) { + synchronized(baseAttributes) { + baseAttributes.set(0, baseHealth); + baseAttributes.sendDeltaMessage(this); + } + } + public void setHealth(int health) { synchronized(attributes) { attributes.set(0, health); @@ -772,6 +797,13 @@ public class CreatureObject extends TangibleObject { } } + public void setBaseAction(int baseAction) { + synchronized(baseAttributes) { + baseAttributes.set(2, baseAction); + baseAttributes.sendDeltaMessage(this); + } + } + public void setAction(int action) { synchronized(attributes) { attributes.set(2, action); @@ -1000,7 +1032,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..e089e1c62 --- /dev/null +++ b/src/services/experience/ExperienceManager.java @@ -0,0 +1,178 @@ +/*********************************************************************************** +* 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"); + + 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: + if (i instanceof ExperienceIntent) + handleExperienceGainedIntent((ExperienceIntent) i); + break; + } + } + + private void handleExperienceGainedIntent(ExperienceIntent i) { + CreatureObject creatureObject = i.getCreatureObject(); + PlayerObject playerObject = creatureObject.getPlayerObject(); + if (playerObject != null) { + int newXpTotal = awardExperience(creatureObject, playerObject, i.getXpType(), i.getExperienceGained()); + + // At this point, we check if their level should be adjusted. + short oldLevel = creatureObject.getLevel(); + attemptLevelUp(creatureObject, i.getXpType(), newXpTotal); + short newLevel = creatureObject.getLevel(); + + if (oldLevel < newLevel) { // If we've leveled up at least once + new LevelChangedIntent(creatureObject, oldLevel, newLevel).broadcast(); + adjustHealth(creatureObject, newLevel); + adjustAction(creatureObject, newLevel); + // TODO NGE: system message health and action differences. @spam:level_up_stat_gain_# + Log.i(this, "%s leveled from %d to %d", creatureObject, oldLevel, newLevel); + } + } + } + + private int awardExperience(CreatureObject creatureObject, PlayerObject playerObject, String xpType, int xpGained) { + 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); + Log.d(this, "%s gained %d %s XP", creatureObject, xpGained, xpType); + // TODO show +XP flytext + return newXpTotal; + } + + private void attemptLevelUp(CreatureObject creatureObject, String xpType, int newXpTotal) { + short currentLevel = creatureObject.getLevel(); + + if (currentLevel != getMaxLevel()) { + short nextLevel = (short) (currentLevel + 1); + Integer xpNextLevel = levelXpMap.get(nextLevel); + + if (xpNextLevel != null) { + if (newXpTotal >= xpNextLevel) { + creatureObject.setLevel(nextLevel); + + // Recursively attempt to level up again, in case we've gained enough XP to level up multiple times. + attemptLevelUp(creatureObject, xpType, newXpTotal); + } + } + } + } + + private void adjustHealth(CreatureObject creatureObject, short newLevel) { + int currentLevelHealthGranted = creatureObject.getLevelHealthGranted(); // The existing levelHealthGranted + int newLevelHealthGranted = 100 * newLevel; // new levelHealthGranted + int difference = newLevelHealthGranted - currentLevelHealthGranted; + + // Set new levelHealthGranted + creatureObject.setLevelHealthGranted(newLevelHealthGranted); + + // Add the difference to their max health + int newMaxHealth = creatureObject.getMaxHealth() + difference; + creatureObject.setMaxHealth(newMaxHealth); + + // Give them full health + creatureObject.setBaseHealth(newMaxHealth); + creatureObject.setHealth(newMaxHealth); + } + + private void adjustAction(CreatureObject creatureObject, short newLevel) { + int currentMaxAction = creatureObject.getMaxAction(); + int newLevelActionGranted = 75 * newLevel; + int difference = newLevelActionGranted - currentMaxAction; + + // Add the difference to their max action + int newMaxAction = currentMaxAction + difference; + creatureObject.setMaxAction(newMaxAction); + + // Give them full action + creatureObject.setBaseAction(newMaxAction); + creatureObject.setAction(newMaxAction); + } + + 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..b1d68b2b0 --- /dev/null +++ b/src/services/experience/SkillService.java @@ -0,0 +1,214 @@ +/*********************************************************************************** +* 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.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 final Map roleIconMap; + private final 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: + if (i instanceof SkillBoxGrantedIntent) + handleSkillAddIntent((SkillBoxGrantedIntent) i); + break; + case GalacticPacketIntent.TYPE: + if (i instanceof GalacticPacketIntent) + handleGalacticPacket((GalacticPacketIntent) i); + break; + } + } + + @Override + public boolean initialize() { + DatatableData roleIconTable = (DatatableData) ClientFactory.getInfoFromFile("datatables/role/role.iff", false); + + 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); + } + + loadSkills(); + + return super.initialize(); + } + + private void loadSkills() { + DatatableData skillsTable = (DatatableData) ClientFactory.getInfoFromFile("datatables/skill/skills.iff", false); + + for (int i = 0; i < skillsTable.getRowCount(); i++) { + String skillName = (String) skillsTable.getCell(i, 0); + String [] skillModsStrings = splitCsv((String) skillsTable.getCell(i, 22)); + Map skillMods = new HashMap<>(); + for (String skillModString : skillModsStrings) { + String [] values = skillModString.split("=", 2); + skillMods.put(values[0], Integer.parseInt(values[1])); + } + + SkillData skillData = new SkillData( + splitCsv((String) skillsTable.getCell(i, 11)), // required skills + (String) skillsTable.getCell(i, 12), // xp type + (int) skillsTable.getCell(i, 13), // xp cost + splitCsv((String) skillsTable.getCell(i, 21)), // commands + skillMods, + splitCsv((String) skillsTable.getCell(i, 23)) // schematics + ); + + skillDataMap.put(skillName, skillData); + } + } + + private String [] splitCsv(String str) { + if (str.isEmpty() || str.indexOf(',') == -1) + return new String[0]; + return str.split(","); + } + + private void handleSkillAddIntent(SkillBoxGrantedIntent intent) { + String skillName = intent.getSkillName(); + CreatureObject target = intent.getTarget(); + SkillData skillData = skillDataMap.get(skillName); + String[] requiredSkills = skillData.requiredSkills; + + 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) -> new SkillModIntent(skillModName, 0, skillModValue, target).broadcast()); + + // Ziggy: These are disabled for now, since we don't have the structs in place for it. +// for(String schematic : skillData.schematics) { + // Add schematic to PlayerObject +// } + + Log.i(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); + } + } + + @SuppressWarnings("unused") + 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; + } + + public String[] getRequiredSkills() { return requiredSkills; } + public String getXpType() { return xpType; } + public int getXpCost() { return xpCost; } + public String[] getCommands() { return commands; } + public Map getSkillMods() { return skillMods; } + public String[] getSchematics() { return schematics; } + } + +} diff --git a/src/services/experience/SkillTemplateService.java b/src/services/experience/SkillTemplateService.java new file mode 100644 index 000000000..ccd4bf789 --- /dev/null +++ b/src/services/experience/SkillTemplateService.java @@ -0,0 +1,106 @@ +/*********************************************************************************** +* 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; + +/** + * This is a service that listens for {@link LevelChangedIntent} and grants + * everything linked to a skillbox. + * @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 oldLevel = i.getPreviousLevel(); + short newLevel = i.getNewLevel(); + CreatureObject creatureObject = i.getCreatureObject(); + + for (int level = oldLevel; level <= newLevel; level++) { + // Skills are only awarded every third or fourth level + if ((level == 4 || level == 7 || level == 10) || ((level > 10) && (((level - 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, level, profession); + } else { + int skillIndex = ((level <= 10) ? ((level - 1) / 3) : ((((level - 10) / 4)) + 3)); + + String skillName = templates[skillIndex]; + new SkillBoxGrantedIntent(skillName, creatureObject).broadcast(); + playerObject.setProfWheelPosition(skillName); + // TODO roadmap reward items + // 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 + } + } else { + Log.d(this, "Level %d has no skillbox - %s is rewarded nothing", level, creatureObject); + } + } + } +} diff --git a/src/services/galaxy/GameManager.java b/src/services/galaxy/GameManager.java index bd364bcb1..ed105bc75 100644 --- a/src/services/galaxy/GameManager.java +++ b/src/services/galaxy/GameManager.java @@ -32,6 +32,7 @@ import services.collections.CollectionBadgeManager; import services.combat.CombatService; 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; @@ -49,6 +50,7 @@ public class GameManager extends Manager { private final SkillModService skillModService; private final EntertainmentService entertainmentService; private final CombatService combatService; + private final ExperienceManager experienceManager; public GameManager() { commandService = new CommandService(); @@ -62,6 +64,7 @@ public class GameManager extends Manager { skillModService = new SkillModService(); entertainmentService = new EntertainmentService(); combatService = new CombatService(); + experienceManager = new ExperienceManager(); addChildService(commandService); addChildService(connectionService); @@ -74,5 +77,6 @@ public class GameManager extends Manager { addChildService(skillModService); addChildService(entertainmentService); addChildService(combatService); + addChildService(experienceManager); } } diff --git a/src/services/player/CharacterCreationService.java b/src/services/player/CharacterCreationService.java index cf1271b3d..edfc8c03b 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; @@ -319,6 +320,7 @@ public class CharacterCreationService extends Service { playerObj.setAdminTag(player.getAccessLevel()); player.setCreatureObject(creatureObj); + new SkillBoxGrantedIntent(create.getStartingPhase(), creatureObj).broadcast(); return creatureObj.getObjectId(); } diff --git a/src/services/spawn/SpawnerService.java b/src/services/spawn/SpawnerService.java index ad72cd0c2..f0031c318 100644 --- a/src/services/spawn/SpawnerService.java +++ b/src/services/spawn/SpawnerService.java @@ -32,6 +32,7 @@ import java.sql.SQLException; import java.util.ArrayList; import java.util.Collection; +import intents.object.ObjectCreatedIntent; import intents.server.ConfigChangedIntent; import resources.Location; import resources.PvpFlag; @@ -42,19 +43,21 @@ import resources.control.Service; import resources.objects.building.BuildingObject; import resources.objects.SWGObject; import resources.objects.creature.CreatureDifficulty; -import resources.objects.creature.CreatureObject; +import resources.objects.custom.AIBehavior; +import resources.objects.custom.DefaultAIObject; import resources.objects.tangible.OptionFlag; import resources.server_info.Log; import resources.server_info.RelationalDatabase; import resources.server_info.RelationalServerFactory; import resources.spawn.SpawnerType; import resources.spawn.Spawner; +import services.objects.ObjectCreator; import services.objects.ObjectManager; public final class SpawnerService extends Service { private static final String GET_ALL_SPAWNERS_SQL = "SELECT static.x, static.y, static.z, static.heading, " // static columns - + "static.spawner_type, static.cell_id, static.active, static.mood, " // more static columns + + "static.spawner_type, static.cell_id, static.active, static.mood, static.behaviour, " // more static columns + "buildings.object_id AS building_id, buildings.terrain_name AS building_terrain, " // building columns + "creatures.iff_template AS iff, creatures.creature_name, creatures.combat_level, creatures.difficulty, creatures.attackable, " // creature columns + "npc_stats.HP, npc_stats.Action " // npc_stats columns @@ -87,20 +90,20 @@ public final class SpawnerService extends Service { ConfigChangedIntent cgi = (ConfigChangedIntent) i; String newValue, oldValue; - if(cgi.getChangedConfig().equals(ConfigFile.FEATURES)) - if(cgi.getKey().equals("NPCS-ENABLED")) { + if (cgi.getChangedConfig().equals(ConfigFile.FEATURES)) { + if (cgi.getKey().equals("NPCS-ENABLED")) { newValue = cgi.getNewValue(); oldValue = cgi.getOldValue(); - if(!newValue.equals(oldValue)) { - if(Boolean.valueOf(newValue) && spawners.isEmpty()) { // If nothing's been spawned, create it. + if (!newValue.equals(oldValue)) { + if (Boolean.valueOf(newValue) && spawners.isEmpty()) { // If nothing's been spawned, create it. loadSpawners(getConfig(ConfigFile.FEATURES).getBoolean("SPAWN-EGGS-ENABLED", false)); } else { // If anything's been spawned, delete it. removeSpawners(); } } } - + } } private void loadSpawners(boolean spawnEggs) { @@ -155,32 +158,35 @@ public final class SpawnerService extends Service { case "B": difficulty = CreatureDifficulty.BOSS; break; } - createNPC(parent, loc, set.getString("iff"), creatureName, set.getString("mood"), set.getInt("combat_level"), difficulty, set.getInt("HP"), set.getInt("Action"), set.getString("attackable")); + createNPC(parent, loc, creatureName, difficulty, set); } - private boolean createNPC(SWGObject parent, Location loc, String iff, String name, String moodAnimation, int combatLevel, CreatureDifficulty difficulty, int health, int action, String attackable) { - CreatureObject object = (CreatureObject) objectManager.createObject(parent, createTemplate(getRandomIff(iff)), loc, false); - if (object == null) - return false; + private void createNPC(SWGObject parent, Location loc, String name, CreatureDifficulty difficulty, ResultSet set) throws SQLException { + DefaultAIObject object = ObjectCreator.createObjectFromTemplate(createTemplate(getRandomIff(set.getString("iff"))), DefaultAIObject.class); + object.setLocation(loc); + if (parent != null) + parent.addObject(object); object.setName(getCreatureName(name)); - object.setLevel((short) combatLevel); + object.setLevel((short) set.getInt("combat_level")); object.setDifficulty(difficulty); - object.setMaxHealth(health); - object.setHealth(health); - object.setMaxAction(action); - object.setAction(action); + object.setMaxHealth(set.getInt("HP")); + object.setHealth(object.getMaxHealth()); + object.setMaxAction(set.getInt("Action")); + object.setAction(object.getMaxAction()); + object.setBehavior(AIBehavior.valueOf(set.getString("behaviour"))); - switch(attackable) { + switch (set.getString("attackable")) { case "AGGRESSIVE": object.addOptionFlags(OptionFlag.AGGRESSIVE); // Ziggy: There's also an AGGRESSIVE PvpFlag? case "ATTACKABLE": object.setPvpFlags(PvpFlag.ATTACKABLE); break; case "INVULNERABLE": object.addOptionFlags(OptionFlag.INVULNERABLE); break; - default: Log.w(this, "An unknown attackable type of %s was specified for %s", attackable, name); break; + default: Log.w(this, "An unknown attackable type of %s was specified for %s", set.getString("attackable"), name); break; } + String moodAnimation = set.getString("mood"); if (!moodAnimation.equals(IDLE_MOOD)) { object.setMoodAnimation(moodAnimation); } - return true; + new ObjectCreatedIntent(object).broadcast(); } private String getCreatureName(String name) {