I really admire how much you wish to contribute, it's really nice of you.
I said it on your last release too, but I really think you should create some sort of abstract system for skilling.
It just looks much cleaner and what's the point in having repetitive code?
The actions for most skills:
1. Click object
2. Wait while your character does an animation
3. Give player item and respawn the object
Now, I quickly wrote an abstract class which handles the core things for most skills.
Code:
package com.elvarg.game.content.skill.skillable;
import com.elvarg.game.entity.impl.player.Player;
import com.elvarg.game.task.Task;
/**
* Acts as an interface for a skill which can be
* trained.
*
* @author Professor Oak
*/
public interface Skillable {
/**
* Checks if the player has the requirements to start this skill.
* @param player
* @return
*/
public abstract boolean hasRequiremenets(Player player);
/**
* Handles the skill's execution. What should happen and
* what not.
* @param player
*/
public abstract void execute(Player player);
/**
* The success rate (in cycles) for this skill. Used for
* determing how long it takes for this skill to execute
* before the player receives experience/rewards.
*/
public abstract int successRate(Player player);
/**
* Gives the player materials related to this skill.
* @param player
*/
public abstract void harvestMaterial(Player player);
/**
* Finalizes thie skill, used for things
* such as respawning objects.
* @param player
*/
public abstract void finalize(Player player);
/**
* Often called upon stopping the skill.
* Should be used for resetting attributes,
* such as stopping ongoing {@link Task}s.
* @param player
*/
public abstract void reset(Player player);
}
Now, using this base, I created something I call "DefaultSkillable" which is basically an implementation of this system for a default skill (such as wc, mining).
Make sure to note the execute method.
Code:
package com.elvarg.game.content.skill.skillable.impl;
import java.util.Optional;
import com.elvarg.game.content.skill.skillable.Skillable;
import com.elvarg.game.entity.impl.player.Player;
import com.elvarg.game.model.Animation;
import com.elvarg.game.task.Task;
import com.elvarg.game.task.TaskManager;
/**
* Represents a default skillable.
* A "default" skill is where the player simply animates
* until a set amount of cycles have passed, and then
* is rewarded with items.
*
* @author Professor Oak
*
*/
public abstract class DefaultSkillable implements Skillable {
/**
* The {@link Task} which is used to process
* this skill in cycles.
*/
private Optional<Task> task = Optional.empty();
@Override
public void execute(Player player) {
//Start the anim..
player.performAnimation(animation());
//Start task..
task = Optional.of(new Task(1, player, false) {
int cycle = 0;
@Override
protected void execute() {
//Make sure we still have the requirements to keep skilling..
if(!hasRequiremenets(player)) {
reset(player);
return;
}
//Loop animation..
player.performAnimation(animation());
//Attempt to harvest material..
if(cycle++ >= successRate(player)) {
harvestMaterial(player);
cycle = 0;
}
}
});
TaskManager.submit(task.get());
}
@Override
public void reset(Player player) {
//Stop task..
task.ifPresent(task -> task.stop());
task = Optional.empty();
//Reset animation..
player.performAnimation(Animation.DEFAULT_RESET_ANIMATION);
}
@Override
public boolean hasRequiremenets(Player player) {
//Check inventory slots..
if(player.getInventory().getFreeSlots() == 0) {
player.getPacketSender().sendMessage("You don't have enough free inventory space.");
return false;
}
//Check busy..
if(player.busy()) {
return false;
}
return true;
}
@Override
public int successRate(Player player) {
return 0;
}
@Override
public void harvestMaterial(Player player) {
}
@Override
public void finalize(Player player) {
}
public abstract Animation animation();
}
Now we can use this "DefaultSkillable" to apply it for Woodcutting:
Code:
package com.elvarg.game.content.skill.skillable.impl;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import com.elvarg.game.collision.RegionClipping;
import com.elvarg.game.entity.impl.object.GameObject;
import com.elvarg.game.entity.impl.player.Player;
import com.elvarg.game.model.Animation;
import com.elvarg.game.model.Skill;
import com.elvarg.game.model.container.impl.Equipment;
import com.elvarg.game.task.TaskManager;
import com.elvarg.game.task.impl.TimedObjectSpawnTask;
import com.elvarg.util.Misc;
/**
* Represents the Woodcutting skill.
* @author Professor Oak
*/
public class Woodcutting extends DefaultSkillable {
/**
* Constructs a new {@link Woodcutting}.
* @param treeObject The tree to cut down.
* @param tree The tree's data
*/
public Woodcutting(GameObject treeObject, Tree tree) {
this.treeObject = treeObject;
this.tree = tree;
}
/**
* The {@link GameObject} to cut down.
*/
private final GameObject treeObject;
/**
* The {@code treeObject} as an enumerated type
* which contains information about it, such as
* required level.
*/
private final Tree tree;
/**
* The axe we're using to cut down the tree.
*/
private Optional<Axe> axe = Optional.empty();
@Override
public boolean hasRequiremenets(Player player) {
//Attempt to find an axe..
axe = Optional.empty();
for (Axe a : Axe.values()) {
if (player.getEquipment().getItems()[Equipment.WEAPON_SLOT].getId() == a.getId()
|| player.getInventory().contains(a.getId())) {
//If we have already found an axe,
//don't select others that are worse or can't be used
if(axe.isPresent()) {
if(player.getSkillManager().getMaxLevel(Skill.WOODCUTTING) < a .getRequiredLevel()) {
continue;
}
if(a.getRequiredLevel() < axe.get().getRequiredLevel()) {
continue;
}
}
axe = Optional.of(a);
}
}
//Check if we found one..
if(!axe.isPresent()) {
player.getPacketSender().sendMessage("You don't have an axe which you can use.");
return false;
}
//Check if we have the required level to cut down this {@code tree} using the {@link Axe} we found..
if(player.getSkillManager().getCurrentLevel(Skill.WOODCUTTING) < axe.get().getRequiredLevel()) {
player.getPacketSender().sendMessage("You don't have an axe which you have the required Woodcutting level to use.");
return false;
}
//Check if we have the required level to cut down this {@code tree}..
if(player.getSkillManager().getCurrentLevel(Skill.WOODCUTTING) < tree.getRequiredLevel()) {
player.getPacketSender().sendMessage("You need a Woodcutting level of at least "+tree.getRequiredLevel()+" to cut this tree.");
return false;
}
//Finally, check if the tree object remains there.
//Another player may have cut it down already.
if(!RegionClipping.getObject(treeObject.getId(), treeObject.getPosition()).isPresent()) {
return false;
}
return super.hasRequiremenets(player);
}
@Override
public void execute(Player player) {
player.getPacketSender().sendMessage("You swing your axe at the tree..");
super.execute(player);
}
@Override
public void harvestMaterial(Player player) {
//Add logs..
player.getInventory().add(tree.getLogId(), 1);
player.getPacketSender().sendMessage("You get some logs.");
//Add exp..
player.getSkillManager().addExperience(Skill.WOODCUTTING, tree.getXpReward());
//Regular trees should always despawn.
//Multi trees are random.
if(!tree.isMulti() || Misc.getRandom(15) >= 2) {
finalize(player);
}
}
@Override
public void finalize(Player player) {
//Stop skilling..
reset(player);
//Despawn object and respawn it after a short period of time..
TaskManager.submit(new TimedObjectSpawnTask(new GameObject(1343, treeObject.getPosition()), treeObject, false, tree.getRespawnTimer()));
}
@Override
public Animation animation() {
return axe.get().getAnimation();
}
@Override
public int successRate(Player player) {
int cycles = tree.getCycles() - ((int)((player.getSkillManager().getMaxLevel(Skill.WOODCUTTING) * 0.07) * axe.get().getSpeed()));
if(cycles < 1) {
cycles = 1;
}
return cycles;
}
public GameObject getTreeObject() {
return treeObject;
}
/**
* Holds data related to the axes
* that can be used for this skill.
*/
public static enum Axe {
BRONZE_AXE(1351, 1, 1.0, new Animation(879)),
IRON_AXE(1349, 1, 1.05, new Animation(877)),
STEEL_AXE(1353, 6, 1.1, new Animation(875)),
BLACK_AXE(1361, 6, 1.15, new Animation(873)),
MITHRIL_AXE(1355, 21, 1.2, new Animation(871)),
ADAMANT_AXE(1357, 31, 1.33, new Animation(869)),
RUNE_AXE(1359, 41, 1.51, new Animation(867)),
DRAGON_AXE(6739, 61, 1.9, new Animation(2846)),
INFERNAL(13241,61, 2.2, new Animation(2117));
private final int id;
private final int requiredLevel;
private final double speed;
private final Animation animation;
private Axe(int id, int level, double speed, Animation animation) {
this.id = id;
this.requiredLevel = level;
this.speed = speed;
this.animation = animation;
}
public int getId() {
return id;
}
public int getRequiredLevel() {
return requiredLevel;
}
public double getSpeed() {
return speed;
}
public Animation getAnimation() {
return animation;
}
}
/**
* Holds data related to the trees
* which can be used to train this skill.
*/
public static enum Tree {
NORMAL(1, 3655, 1511, new int[] { 1276, 1277, 1278, 1279, 1280, 1282, 1283, 1284, 1285, 1286, 1289, 1290, 1291, 1315, 1316, 1318, 1319, 1330, 1331, 1332, 1365, 1383, 1384, 3033, 3034, 3035, 3036, 3881, 3882, 3883, 5902, 5903, 5904 }, 5, 8, false),
ACHEY(1, 3655, 2862, new int[] { 2023 }, 5, 9, false),
OAK(15, 4684, 1521, new int[] { 1281, 3037 }, 6, 11, true),
WILLOW(30, 6346, 1519, new int[] { 1308, 5551, 5552, 5553 }, 9, 14, true),
TEAK(35, 6544, 6333, new int[] { 9036 }, 10, 16, true),
DRAMEN(36, 6581, 771, new int[] { 1292 }, 11, 17, true),
MAPLE(45, 7935, 1517, new int[] { 1307, 4677 }, 12, 18, true),
MAHOGANY(50, 8112, 6332, new int[] { 9034 }, 13, 20, true),
YEW(60, 8417, 1515, new int[] { 1309 }, 14, 28, true),
MAGIC(75, 9127, 1513, new int[] { 1306 }, 15, 40, true);
private final int[] objects;
private final int requiredLevel;
private final int xpReward;
private final int logId;
private final int cycles;
private final int respawnTimer;
private final boolean multi;
Tree(int req, int xp, int log, int[] obj, int cycles, int respawnTimer, boolean multi) {
this.requiredLevel = req;
this.xpReward = xp;
this.logId = log;
this.objects = obj;
this.cycles = cycles;
this.respawnTimer = respawnTimer;
this.multi = multi;
}
public boolean isMulti() {
return multi;
}
public int getCycles() {
return cycles;
}
public int getRespawnTimer() {
return respawnTimer;
}
public int getLogId() {
return logId;
}
public int getXpReward() {
return xpReward;
}
public int getRequiredLevel() {
return requiredLevel;
}
private static final Map<Integer, Tree> trees = new HashMap<Integer, Tree>();
static {
for (Tree t : Tree.values()) {
for (int obj : t.objects) {
trees.put(obj, t);
}
trees.put(t.getLogId(), t);
}
}
public static Optional<Tree> forObjectId(int objectId) {
Tree tree = trees.get(objectId);
if(tree != null) {
return Optional.of(tree);
}
return Optional.empty();
}
}
}
This is much more efficient and allows me to use the same system for multiple skills. I wrote up this up very quickly. I'm sure it can be improved very much, feel free to do so.
edit2: Added Mining in literally 15 minutes, woodcutting and mining are pretty much identical skills lol.
Take a look yourself:
Code:
package com.elvarg.game.content.skill.skillable.impl;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import com.elvarg.game.collision.RegionClipping;
import com.elvarg.game.entity.impl.object.GameObject;
import com.elvarg.game.entity.impl.player.Player;
import com.elvarg.game.model.Animation;
import com.elvarg.game.model.Skill;
import com.elvarg.game.model.container.impl.Equipment;
import com.elvarg.game.task.TaskManager;
import com.elvarg.game.task.impl.TimedObjectSpawnTask;
/**
* Represents the Mining skill.
* @author Professor Oak
*/
public class Mining extends DefaultSkillable {
/**
* Constructs a new {@link Mining}.
* @param rockObject The rock to mine.
* @param rock The rock's data
*/
public Mining(GameObject rockObject, Rock rock) {
this.rockObject = rockObject;
this.rock = rock;
}
/**
* The {@link GameObject} to mine.
*/
private final GameObject rockObject;
/**
* The {@code rock} as an enumerated type
* which contains information about it, such as
* required level.
*/
private final Rock rock;
/**
* The pickaxe we're using to mine.
*/
private Optional<Pickaxe> pickaxe = Optional.empty();
@Override
public boolean hasRequiremenets(Player player) {
//Attempt to find a pickaxe..
pickaxe = Optional.empty();
for (Pickaxe a : Pickaxe.values()) {
if (player.getEquipment().getItems()[Equipment.WEAPON_SLOT].getId() == a.getId()
|| player.getInventory().contains(a.getId())) {
//If we have already found a pickaxe,
//don't select others that are worse or can't be used
if(pickaxe.isPresent()) {
if(player.getSkillManager().getMaxLevel(Skill.MINING) < a .getRequiredLevel()) {
continue;
}
if(a.getRequiredLevel() < pickaxe.get().getRequiredLevel()) {
continue;
}
}
pickaxe = Optional.of(a);
}
}
//Check if we found one..
if(!pickaxe.isPresent()) {
player.getPacketSender().sendMessage("You don't have a pickaxe which you can use.");
return false;
}
//Check if we have the required level to mine this {@code rock} using the {@link Pickaxe} we found..
if(player.getSkillManager().getCurrentLevel(Skill.MINING) < pickaxe.get().getRequiredLevel()) {
player.getPacketSender().sendMessage("You don't have a pickaxe which you have the required Mining level to use.");
return false;
}
//Check if we have the required level to mine this {@code rock}..
if(player.getSkillManager().getCurrentLevel(Skill.MINING) < rock.getRequiredLevel()) {
player.getPacketSender().sendMessage("You need a Mining level of at least "+rock.getRequiredLevel()+" to mine this rock.");
return false;
}
//Finally, check if the rock object remains there.
//Another player may have mined it already.
if(!RegionClipping.getObject(rockObject.getId(), rockObject.getPosition()).isPresent()) {
return false;
}
return super.hasRequiremenets(player);
}
@Override
public void execute(Player player) {
player.getPacketSender().sendMessage("You swing your pickaxe at the rock..");
super.execute(player);
}
@Override
public void harvestMaterial(Player player) {
//Add ores..
player.getInventory().add(rock.getOreId(), 1);
player.getPacketSender().sendMessage("You get some ores.");
//Add exp..
player.getSkillManager().addExperience(Skill.MINING, rock.getXpReward());
//Despawn rock..
finalize(player);
}
@Override
public void finalize(Player player) {
//Stop skilling..
reset(player);
//Despawn object and respawn it after a short period of time..
TaskManager.submit(new TimedObjectSpawnTask(new GameObject(2704, rockObject.getPosition()), rockObject, false, rock.getRespawnTimer()));
}
@Override
public Animation animation() {
return pickaxe.get().getAnimation();
}
@Override
public int successRate(Player player) {
int cycles = rock.getCycles() - ((int)((player.getSkillManager().getMaxLevel(Skill.MINING) * 0.07) * pickaxe.get().getSpeed()));
if(cycles < 1) {
cycles = 1;
}
return cycles;
}
public GameObject getTreeObject() {
return rockObject;
}
/**
* Holds data related to the pickaxes
* that can be used for this skill.
*/
public static enum Pickaxe {
BRONZE(1265, 1, new Animation(625), 1.0),
IRON(1267, 1, new Animation(626), 1.05),
STEEL(1269, 6, new Animation(627), 1.1),
MITHRIL(1273, 21, new Animation(628), 1.2),
ADAMANT(1271, 31, new Animation(629), 1.33),
RUNE(1275, 41, new Animation(624), 1.51),
DRAGON(15259, 61, new Animation(624), 1.9);
private final int id, requiredLevel;
private final Animation animation;
private final double speed;
private Pickaxe(int id, int req, Animation animaion, double speed) {
this.id = id;
this.requiredLevel = req;
this.animation = animaion;
this.speed = speed;
}
public int getId() {
return id;
}
public int getRequiredLevel() {
return requiredLevel;
}
public Animation getAnimation() {
return animation;
}
public double getSpeed() {
return this.speed;
}
}
/**
* Holds data related to the rocks
* which can be used to train this skill.
*/
public static enum Rock {
CLAY(new int[]{9711, 9712, 9713, 15503, 15504, 15505}, 1, 450, 434, 4, 2),
COPPER(new int[]{9708, 9709, 9710, 11936, 11960, 11961, 11962, 11189, 11190, 11191, 29231, 29230, 2090}, 1, 1550, 436, 5, 4),
TIN(new int[]{9714, 9715, 9716, 11933, 11957, 11958, 11959, 11186, 11187, 11188, 2094, 29227, 29229}, 1, 1550, 438, 5, 4),
IRON(new int[]{7455, 9717, 9718, 9719, 2093, 2092, 11954, 11955, 11956, 29221, 29222, 29223}, 15, 2450, 440, 6, 5),
SILVER(new int[]{2100, 2101, 29226, 29225, 11948, 11949}, 20, 2760, 442, 7, 7),
COAL(new int[]{2097, 5770, 29216, 29215, 29217, 11965, 11964, 11963, 11930, 11931, 11932}, 30, 3450, 453, 7, 7),
GOLD(new int[]{9720, 9721, 9722, 11951, 11183, 11184, 11185, 2099}, 40, 4580, 444, 8, 10),
MITHRIL(new int[]{25370, 25368, 5786, 5784, 11942, 11943, 11944, 11945, 11946, 29236, 11947, 11942, 11943}, 50, 6300, 447, 11, 11),
ADAMANTITE(new int[]{11941, 11939, 29233, 29235}, 70, 9560, 449, 13, 14),
RUNITE(new int[]{14859, 4860, 2106, 2107}, 85, 15122, 451, 15, 45),;
private int objects[];
private int oreId, requiredLevel, xpReward, cycles, respawnTimer;
private Rock(int[] objects, int requiredLevel, int xpReward, int oreId, int cycles, int respawnTimer) {
this.objects = objects;
this.requiredLevel = requiredLevel;
this.xpReward = xpReward;
this.oreId = oreId;
this.cycles = cycles;
this.respawnTimer = respawnTimer;
}
public int getRespawnTimer() {
return respawnTimer;
}
public int getRequiredLevel() {
return requiredLevel;
}
public int getXpReward(){
return xpReward;
}
public int getOreId() {
return oreId;
}
public int getCycles() {
return cycles;
}
private static final Map<Integer, Rock> rocks = new HashMap<Integer, Rock>();
static {
for (Rock t : Rock.values()) {
for (int obj : t.objects) {
rocks.put(obj, t);
}
rocks.put(t.getOreId(), t);
}
}
public static Optional<Rock> forObjectId(int objectId) {
Rock rock = rocks.get(objectId);
if(rock != null) {
return Optional.of(rock);
}
return Optional.empty();
}
}
}