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) {