Merge remote-tracking branch 'upstream/master'

This commit is contained in:
skelsoft 2021-08-12 07:01:57 +10:00
commit 9288ca7ff7
53 changed files with 1130 additions and 1524 deletions

View file

@ -10,6 +10,6 @@
"WorldTechnicalInformation": {
"world_limit": "10",
"worldhop_delay": "20000"
}
},
"secret_key": "2009scape_development"
}

View file

@ -1,7 +1,8 @@
package ms;
import ms.classloader.ClassLoadServer;
import ms.net.NioReactor;
import ms.net.packet.IoBuffer;
import ms.net.packet.PacketHeader;
import ms.net.packet.WorldPacketRepository;
import ms.system.ShutdownSequence;
import ms.system.mysql.SQLManager;
@ -100,7 +101,6 @@ public final class Management {
new Command("-rlcache", "Reloads launcher/client resource cache") {
@Override
public void run(String... args) {
ClassLoadServer.resetResourceCache();
System.out.println("Reloaded resource cache!");
}
},
@ -118,6 +118,27 @@ public final class Management {
player.setWorldId(0);
System.out.println("Kicked player " + name + "!");
}
},
new Command("-say", "Send a message to all worlds") {
@Override
public void run(String... args) {
String message = String.join(" ", args);
message = message.substring(4);
for(GameServer server : WorldDatabase.getWorlds()){
if(server == null) continue;
String finalMessage = message;
server.getPlayers().forEach((String uname, PlayerSession p) -> {
IoBuffer buffer = new IoBuffer(5, PacketHeader.BYTE);
buffer.putString(p.getUsername());
buffer.putString("Server");
buffer.put(2);
buffer.put(2);
buffer.putString(finalMessage);
p.getWorld().getSession().write(buffer);
});
}
}
}
};
@ -136,7 +157,6 @@ public final class Management {
SQLManager.init();
//NioReactor.configure(ServerConstants.PORT).start();
NioReactor.configure(5555).start();
new ClassLoadServer().start();
Runtime.getRuntime().addShutdownHook(new ShutdownSequence());
System.out.println("Status: ready.");
System.out.println("Use -commands for a list of commands!");

View file

@ -29,18 +29,6 @@ public final class ServerConstants {
*/
public static final OperatingSystem OS = System.getProperty("os.name").toUpperCase().contains("WIN") ? OperatingSystem.WINDOWS : OperatingSystem.UNIX;
/**
* The administrators.
*/
public static final String[] ADMINISTRATORS = {
"redsparr0w",
};
public static final String[] DATABASE_NAMES = {
"server",
"global",
};
/**
* Stops from instantiating.
*/

View file

@ -1,111 +0,0 @@
package ms.classloader;
import ms.Management;
import ms.ServerConstants;
import java.io.File;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;
import java.nio.file.Files;
import java.util.HashMap;
/**
* ClassLoadServer
* @author Clayton Williams (Hope)
*/
public class ClassLoadServer extends Thread {
/**
* Listening port
*/
private static final int PORT = 5050;
/**
* serverSocket for incoming connections
*/
private ServerSocket serverSocket = null;
/**
* Holds classes and resources already added in the server
*/
protected final static HashMap<String, byte[]> resourceCache = new HashMap<String, byte[]>();
/**
* New socket
* @throws UnknownHostException
* @throws IOException
*/
public ClassLoadServer() {
try {
serverSocket = new ServerSocket(PORT);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* Listen for incoming connections
*/
@Override
public void run() {
resetResourceCache();
// System.out.println("Listening on port " + PORT + "...");
while (Management.active) {
Socket clientSocket = null;
try {
clientSocket = serverSocket.accept();
//System.out.println("New Connection from : " + clientSocket.getInetAddress());
WorkerThread wcs = new WorkerThread(clientSocket);
wcs.start();
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* Loads resources recursively from a starting root directory and adds the file bytes to our cache
* @param path
* @param pathRoot
*/
private static void loadResources(String path, String pathRoot) {
try {
path = ServerConstants.fixPath(null, path);
pathRoot = ServerConstants.fixPath(null, pathRoot);
File root = new File(path);
File[] list = root.listFiles();
if (list == null) return;
for (File f : list) {
if (f.isDirectory()) {
loadResources(f.getAbsolutePath(), pathRoot);
} else {
if (f.exists()) {
if (!f.getName().startsWith(".")) {
byte[] bytes = Files.readAllBytes(f.toPath());
String name = ServerConstants.fixPath(null, f.getAbsolutePath().substring(f.getAbsolutePath().lastIndexOf(pathRoot)));
//System.out.println("[ClassLoadServer] Loaded resource '" + f.getName() +
// "' Size: " + NumberFormat.getInstance().format(bytes.length) + " bytes");
resourceCache.put(name, bytes);
}
}
}
}
} catch(Exception e) {
e.printStackTrace();
}
}
/**
* Empties the class cache and loads resources
*/
public static void resetResourceCache() {
resourceCache.clear();
//loadResources("bin/org/keldagrim/launcher/", "org/arios/launcher/");
//loadResources("resources/", "org/keldagrim/launcher/");
//System.out.println("Loaded all resources!");
}
}

View file

@ -1,136 +0,0 @@
package ms.classloader;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
import java.util.Map;
import ms.ServerConstants;
import ms.system.OperatingSystem;
/**
* Worker thread for launcher connections
* @author Clayton Williams (Hope)
*
*/
public class WorkerThread extends Thread {
/**
* Our launcher connection
*/
private Socket launcher = null;
/**
* Input stream from launcher
*/
private ObjectInputStream is = null;
/**
* Output stream to launcher
*/
private ObjectOutputStream os = null;
/**
* The user's operating system type
*/
private OperatingSystem operatingSystem = OperatingSystem.WINDOWS;
/**
* Creates a new worker thread to handle the launcher requests
* @param socket
* @param classesCache
* @throws IOException
*/
public WorkerThread(Socket launcher) throws IOException {
super();
this.launcher = launcher;
try {
os = new ObjectOutputStream(new BufferedOutputStream(this.launcher.getOutputStream()));
os.flush();
is = new ObjectInputStream(new BufferedInputStream(launcher.getInputStream()));
} catch (IOException ioe) {
this.launcher.close();
}
}
/**
* Input Stream handler
*/
@SuppressWarnings("unused") //saving resource type for later
public void run() {
byte opcode = -1;
try {
while (true) {
opcode = is.readByte();
switch(opcode) {
case 1: //requests revision
operatingSystem = is.readUTF().contains("UNIX") ? OperatingSystem.UNIX : OperatingSystem.WINDOWS;
os.reset();
os.writeInt(ClassLoadServer.resourceCache.size());//#customresources
for (Map.Entry<String, byte[]> hm : ClassLoadServer.resourceCache.entrySet()) {
os.writeUTF(ServerConstants.fixPath(operatingSystem, (String) hm.getKey()));
os.writeInt(hm.getValue().length);
}
os.writeByte(1); //number of custom resources
//followed by 2 strings
//System.err.println(operatingSystem.name());
os.writeUTF(ServerConstants.fixPath(operatingSystem, "ms/launcher/arios-gamepack-530.jar"));
os.writeUTF("BINARY");
os.flush();
break;
case 2: //resource request
String resourceName = ServerConstants.fixPath(null, is.readUTF());
String resourceType = is.readUTF();
if (ClassLoadServer.resourceCache.containsKey(resourceName)) {
sendResource(ClassLoadServer.resourceCache.get(resourceName));
//System.out.println("Sent Resource: " + resourceName);
} else {
sendOpcode(-1);
//System.out.println("Could not send resource '" + resourceName + "'");
}
break;
default:
System.out.println("Unhandled opcode=" + opcode);
break;
}
}
} catch(Exception e) {
//System.out.println("Client force disconnect.");
} finally {
try {
is.close();
os.close();
launcher.close();
} catch (Exception e) {}
}
}
/**
* send a byte array packet to the client
* @exception IOException file read error.
*/
protected void sendResource(byte [] bytes) throws IOException {
os.reset();
os.writeByte(2);
os.writeInt(bytes.length);
for (int i = 0; i < bytes.length; i++) {
os.writeByte(bytes[i]);
}
os.flush();
}
/**
* Send no data opcodes
* @param opcode
* @throws IOException
*/
protected void sendOpcode(int opcode) throws IOException {
os.reset();
os.writeByte(opcode);
os.flush();
}
}

View file

@ -67,6 +67,10 @@ public final class IoEventHandler {
ByteBuffer buffer = ByteBuffer.allocate(100_000);
IoSession session = (IoSession) key.attachment();
if (channel.read(buffer) == -1) {
if(session == null){
key.cancel();
return;
}
if(session.getGameServer() != null){
WorldDatabase.unRegister(session.getGameServer());
}
@ -87,8 +91,10 @@ public final class IoEventHandler {
*/
public void write(SelectionKey key) {
IoSession session = (IoSession) key.attachment();
key.interestOps(key.interestOps() & ~SelectionKey.OP_WRITE);
session.write();
if(session != null) {
key.interestOps(key.interestOps() & ~SelectionKey.OP_WRITE);
session.write();
}
}
/**

View file

@ -168,9 +168,9 @@ public class IoSession {
}
writingQueue.remove(0);
}
writingLock.unlock();
} catch (IOException e) {
disconnect();
} finally {
writingLock.unlock();
}
}

View file

@ -96,6 +96,7 @@ public final class NioReactor implements Runnable {
SelectionKey key = keys.next();
keys.remove();
try {
if(key == null) continue;
if (!key.isValid() || !key.channel().isOpen()) {
key.cancel();
continue;

View file

@ -4,6 +4,7 @@ import java.nio.ByteBuffer;
import ms.net.IoReadEvent;
import ms.net.IoSession;
import ms.system.util.ManagementConstants;
import ms.world.WorldDatabase;
import ms.system.util.ByteBufferUtils;
@ -12,11 +13,6 @@ import ms.system.util.ByteBufferUtils;
* @author Emperor
*/
public final class HSReadEvent extends IoReadEvent {
/**
* The password used to verify
*/
private static final String PASSWORD = "0x14ari0SSbh98989910";
/**
* Constructs a new {@code HSReadEvent}.
@ -33,7 +29,7 @@ public final class HSReadEvent extends IoReadEvent {
switch (opcode) {
case 88:
String password = ByteBufferUtils.getString(buffer);
if (!password.equals(PASSWORD)) {
if (!password.equals(ManagementConstants.getSECRET_KEY())) {
System.out.println("Password mismatch (attempt=" + password + ")!");
session.disconnect();
break;

View file

@ -547,7 +547,7 @@ public class IoBuffer {
long second = getIntB();
if (second < 0)
second = second & 0xffffffffL;
return (first << -41780448) + second;
return (first << 32) + second;
}
/**
@ -567,7 +567,7 @@ public class IoBuffer {
if (peek <= Short.MAX_VALUE) {
return buf.getShort() & 0xFFFF;
}
return (buf.getInt() & 0xFFFFFFFF) - 32768;
return buf.getInt() - 32768;
}
/**

View file

@ -5,7 +5,7 @@ import ms.system.PunishmentStorage
import ms.system.communication.ClanRank
import ms.system.communication.ClanRepository
import ms.system.communication.CommunicationInfo
import ms.system.util.ManagementConstants.Companion.WORLD_HOP_DELAY
import ms.system.util.ManagementConstants.WORLD_HOP_DELAY
import ms.world.GameServer
import ms.world.PlayerSession
import ms.world.WorldDatabase

View file

@ -49,7 +49,7 @@ public final class PunishmentStorage {
return;
}
long end = Long.MAX_VALUE;
if (duration != -1l && duration != 0L) {
if (duration != -1L && duration != 0L) {
end = System.currentTimeMillis() + duration;
} else if (duration == 0L) {
end = 0L;

View file

@ -12,10 +12,7 @@ import java.nio.ByteBuffer;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.util.Map.Entry;
/**
@ -184,9 +181,7 @@ public final class CommunicationInfo {
String bl = set.getString("blocked");
if (bl != null && !bl.isEmpty()) {
tokens = bl.split(",");
for (String name : tokens) {
blocked.add(name);
}
blocked.addAll(Arrays.asList(tokens));
}
clanName = set.getString("clanName");
currentClan = set.getString("currentClan");
@ -236,6 +231,9 @@ public final class CommunicationInfo {
case 2:
tradeSetting = Integer.parseInt(tokens[2]);
break;
default:
System.out.println("Illegal arg count in chatsetting string: " + chatSettings);
break;
}
}
}

View file

@ -1,11 +1,11 @@
package ms.system.mysql
import ms.system.util.ManagementConstants
import ms.system.util.ManagementConstants.Companion.DATABASE_HOST_ADDRESS
import ms.system.util.ManagementConstants.Companion.DATABASE_NAME
import ms.system.util.ManagementConstants.Companion.DATABASE_PASSWORD
import ms.system.util.ManagementConstants.Companion.DATABASE_PORT
import ms.system.util.ManagementConstants.Companion.DATABASE_USERNAME
import ms.system.util.ManagementConstants.DATABASE_HOST_ADDRESS
import ms.system.util.ManagementConstants.DATABASE_NAME
import ms.system.util.ManagementConstants.DATABASE_PASSWORD
import ms.system.util.ManagementConstants.DATABASE_PORT
import ms.system.util.ManagementConstants.DATABASE_USERNAME
import java.sql.Connection
import java.sql.DriverManager
import java.sql.SQLException

View file

@ -30,19 +30,20 @@ class ManagementConfigParser(path: String) {
data = parser.parse(reader) as JSONObject
parseDatabaseInformation()
parseWorldTechnicalSettings()
ManagementConstants.SECRET_KEY = data!!["secret_key"].toString()
}
}
private fun parseDatabaseInformation(){
data ?: return
val dbData = data!!["DatabaseInformation"] as JSONObject
ManagementConstants().parseDBProp(dbData)
ManagementConstants.parseDBProp(dbData)
}
private fun parseWorldTechnicalSettings(){
data ?: return
val wtiData = data!!["WorldTechnicalInformation"] as JSONObject
ManagementConstants().parseWTIProp(wtiData)
ManagementConstants.parseWTIProp(wtiData)
}
/**

View file

@ -2,32 +2,31 @@ package ms.system.util
import org.json.simple.JSONObject
class ManagementConstants {
object ManagementConstants {
companion object {
//MySQL main database name
var DATABASE_NAME: String = "global"
//MySQL main database name
var DATABASE_NAME: String = "global"
//MySQL database username
var DATABASE_USERNAME: String = "root"
//MySQL database username
var DATABASE_USERNAME: String = "root"
//MySQL database password
var DATABASE_PASSWORD: String = ""
//MySQL database password
var DATABASE_PASSWORD: String = ""
//MySQL host
var DATABASE_HOST_ADDRESS: String = "127.0.0.1"
//MySQL host
var DATABASE_HOST_ADDRESS: String = "127.0.0.1"
//MySQL port
var DATABASE_PORT: Int = 3306
//MySQL port
var DATABASE_PORT: Int = 3306
//Max amount of worlds supported on the world list
var MAX_WORLD_AMOUNT: Int = 10
//Max amount of worlds supported on the world list
var MAX_WORLD_AMOUNT: Int = 10
//User world hop delay in seconds
var WORLD_HOP_DELAY: Long = 20_000L
//User world hop delay in seconds
var WORLD_HOP_DELAY: Long = 20_000L
}
@JvmStatic
var SECRET_KEY: String = ""
fun parseDBProp(data: JSONObject) {
DATABASE_NAME = data["database_name"].toString()

View file

@ -409,8 +409,9 @@ public final class StringUtils {
}
}
}
return -i_26_ + ((7 + i_30_) >> -662855293);
return -i_26_ + ((7 + i_30_) >> 3);
} catch (RuntimeException runtimeexception) {
runtimeexception.printStackTrace();
}
return 0;
}

View file

@ -66,7 +66,7 @@ public final class PlayerSession {
/**
* The time stamp of last disconnection.
*/
private long disconnectionTime = 0l;
private long disconnectionTime = 0L;
/**
* How long the player is banned for.
@ -463,11 +463,8 @@ public final class PlayerSession {
this.disconnectionTime = time;
}
@Override
public boolean equals(Object o) {
if(o instanceof PlayerSession) {
return username.equals(((PlayerSession) o).username);
} else return false;
public boolean equalsSession(PlayerSession o){
return username.equals(o.username);
}
@Override

View file

@ -9,12 +9,12 @@ import java.util.*
object MSLogger {
val t = Terminal()
val formatter = SimpleDateFormat("HH:mm:ss")
var tradeLog: Writer? = null
var tradeLogWriter: BufferedWriter? = null
fun getTime(): String{
val formatter = SimpleDateFormat("HH:mm:ss")
return "[" + formatter.format(Date(System.currentTimeMillis())) +"]"
}

View file

@ -62771,7 +62771,7 @@
{
"default": [],
"charm": [],
"ids": "428, 429",
"ids": "428,429",
"description": "Shade (Stronghold of Security) (So Shade Robes can drop)",
"main": [
{

Binary file not shown.

View file

@ -100,6 +100,7 @@ public abstract class CutscenePlugin extends ActivityPlugin {
unpause();
}
player.unlock();
player.getInterfaceManager().openDefaultTabs();
player.getWalkingQueue().reset();
player.getLocks().unlockMovement();
}

View file

@ -1,5 +1,6 @@
package core.game.content.activity.guild;
import api.ContentAPI;
import core.cache.def.impl.NPCDefinition;
import core.cache.def.impl.SceneryDefinition;
import core.game.content.dialogue.DialoguePlugin;
@ -60,7 +61,7 @@ public final class WizardGuildPlugin extends OptionHandler {
switch (id) {
case 1600:
case 1601:
if (player.getSkills().getStaticLevel(Skills.MAGIC) < 66) {
if (ContentAPI.getDynLevel(player, Skills.MAGIC) < 66) {
player.getDialogueInterpreter().sendDialogue("You need a Magic level of at least 66 to enter.");
return true;
}

View file

@ -1,219 +0,0 @@
package core.game.content.dialogue;
import core.game.node.entity.npc.NPC;
import core.game.node.entity.player.Player;
import core.plugin.Initializable;
import core.game.world.map.Location;
/**
* Represents the dialogue plugin used for Bill Teach
* @author Charlie
* @version 1.0
*/
@Initializable
public final class BillTeachDialogue extends DialoguePlugin {
/**
* Constructs a new {@code BarfyBill} {@code Object}.
*/
public BillTeachDialogue() {
/**
* empty.
*/
}
/**
* Constructs a new {@code BarfyBill} {@code Object}.
* @param player the player.
*/
public BillTeachDialogue(Player player) {
super(player);
}
@Override
public DialoguePlugin newInstance(Player player) {
return new BillTeachDialogue(player);
}
@Override
public boolean open(Object... args) {
npc = new NPC(3155);
player("Hello there.");
stage = 0;
return true;
}
@Override
public boolean handle(int interfaceId, int buttonId) {
switch (stage) {
case 0:
npc("Arr'! Avast ye' scallywag!", "You be lookin' to get somewhere " + (player.isMale() ? "lad?" : "lass?"));
stage = 1;
break;
case 1:
interpreter.sendOptions("Select an Option", "Yes", "No, thank you");
stage = 2;
break;
case 2:
switch (buttonId) {
case 1:
player("Yes, where can you take me?");
stage = 1000;
break;
case 2:
npc("Arr'! You be wastin' my time again..");
stage = 7;
break;
}
break;
case 1000:
npc("Aye, take a browse through me options.");
stage++;
break;
case 1001:
interpreter.sendOptions("Select an Option", "Slayer Tower", "Zanaris Fairy Ring", "Gnome Stronghold", "Rellekka", "More");
stage = 2000;
break;
case 2000:
switch (buttonId) {
case 1:
if(!player.getQuestRepository().isComplete("Priest in Peril")) {
npc("Aye, sorry there " + pirateGender() + ", but you'll be needing to ", "help King Roald with something first.");
stage = 7;
} else {
end();
player.teleport(new Location(3429, 3526, 0));
}
break;
case 2:
if(!player.getQuestRepository().isComplete("Lost City")) {
npc("Aye, sorry there " + pirateGender() + ", but you'll be needing to ", "discover Zanaris first.");
stage = 7;
} else {
end();
player.teleport(new Location(2412, 4433, 0));
}
break;
case 3:
end();
player.teleport(new Location(2461, 3444, 0));
break;
case 4:
end();
player.teleport(new Location(2669, 3631, 0));
break;
case 5:
interpreter.sendOptions("Select an Option", "Kalphite Lair", "Asgarnian Ice Dungeon", "Fremmenik Dungeon", "Taverley Dungeon", "More");
stage = 3000;
break;
}
break;
case 3000:
switch (buttonId) {
case 1:
end();
player.teleport(new Location(3227, 3107, 0));
break;
case 2:
end();
player.teleport(new Location(3007, 9550, 0));
break;
case 3:
end();
player.teleport(new Location(2808, 10002, 0));
break;
case 4:
end();
player.teleport(new Location(2884, 9798, 0));
break;
case 5:
interpreter.sendOptions("Select an Option", "Waterfall Dungeon", "Brimhaven Dungeon", "Ape Atoll Dungeon", "God Wars Dungeon", "More");
stage = 4000;
break;
}
break;
case 4000:
switch (buttonId) {
case 1:
end();
player.teleport(new Location(2575, 9861, 0));
break;
case 2:
end();
player.teleport(new Location(2713, 9564, 0));
break;
case 3:
end();
player.teleport(new Location(2715, 9184, 0));
break;
case 4:
end();
player.teleport(new Location(2898, 3710, 0));
break;
case 5:
interpreter.sendOptions("Select an Option", "Shilo Village", "Yanille", "Zul-Andra", "Piscatoris Fishing Colony", "More");
stage = 5000;
break;
}
break;
case 5000:
switch (buttonId) {
case 1:
end();
player.teleport(new Location(2867, 2952, 0));
break;
case 2:
end();
player.teleport(new Location(2544, 3096, 0));
break;
case 3:
end();
player.teleport(new Location(2193, 3055, 0));
break;
case 4:
end();
player.teleport(new Location(2343, 3663, 0));
break;
case 5:
interpreter.sendOptions("Select an Option", "Bandit Camp", "Miscellenia", "Mort'ton", "Feldip Hills", "Back");
stage = 6000;
break;
}
break;
case 6000:
switch (buttonId) {
case 1:
end();
player.teleport(new Location(3176, 2987, 0));
break;
case 2:
end();
player.teleport(new Location(2581, 3845, 0));
break;
case 3:
end();
player.teleport(new Location(3488, 3296, 0));
break;
case 4:
end();
player.teleport(new Location(2525, 2915, 0));
break;
case 5:
interpreter.sendOptions("Select an Option", "Slayer Tower", "Zanaris Fairy Ring", "Gnome Stronghold", "Rellekka", "More");
stage = 2000;
break;
}
break;
case 7:
end();
break;
}
return true;
}
@Override
public int[] getIds() {
return new int[] { 3155 };
}
}

View file

@ -137,7 +137,7 @@ public final class HansDialoguePlugin extends DialoguePlugin {
case 12:
switch(buttonId){
case 1:
options("1.0x","2.5x","10x","20x");
options("1.0x","2.5x","Stay 5.0x");
stage++;
break;
case 2:
@ -166,13 +166,9 @@ public final class HansDialoguePlugin extends DialoguePlugin {
}
break;
case 3:
player.getSkills().experienceMutiplier = 10.0;
stage = 14;
break;
case 4:
player.getSkills().experienceMutiplier = 20.0;
stage = 14;
break;
playerl(FacialExpression.FRIENDLY, "I'd rather stay 5x, thank you.");
stage = END_DIALOGUE;
return true;
}
npc("One moment, please...");
break;

View file

@ -38,7 +38,6 @@ public final class SimonTempleton extends DialoguePlugin{
return true;
}
}
player.getPacketDispatch().sendMessage("" + args.length);
npc("G'day, mate!. Got any new", "pyramid artefacts for me?");
return true;
}

View file

@ -1,913 +0,0 @@
package core.game.content.global.shop;
import api.ContentAPI;
import core.cache.def.impl.ItemDefinition;
import core.game.container.Container;
import core.game.container.ContainerType;
import org.rs09.consts.Items;
import core.game.node.entity.player.Player;
import core.game.node.entity.player.link.diary.DiaryType;
import core.game.node.item.Item;
import rs09.game.system.SystemLogger;
import rs09.game.system.config.ItemConfigParser;
import rs09.game.world.GameWorld;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* A class representing a shop.
*
* @author 'Vexia
* @author Jamix77
*/
public class Shop {
/**
* Represents the general store items.
*/
public final static Item[] GENERAL_STORE_ITEMS = new Item[]{
new Item(Items.EMPTY_POT_1931,5),
new Item(Items.JUG_1935,5),
new Item(Items.SHEARS_1735,2),
new Item(Items.BUCKET_1925,3),
new Item(Items.BOWL_1923,2),
new Item(Items.CAKE_TIN_1887,2),
new Item(Items.TINDERBOX_590,2),
new Item(Items.CHISEL_1755,2),
new Item(Items.HAMMER_2347,5),
new Item(Items.NEWCOMER_MAP_550,5),
new Item(Items.SECURITY_BOOK_9003,5)
};
/**
* Represents the coins item.
*/
private static final int COINS = 995;
/**
* Represents the tokkul item id.
*/
private static final int TOKKUL = 6529;
/**
* Represents the archery ticket item id
*/
private static final int ARCHERY_TICKET = 1464;
/**
* Represents the shop containers.
*/
private final Container[] containers = new Container[]{new Container(40, ContainerType.SHOP), new Container(40, ContainerType.SHOP)};
/**
* Represents the list of shop viewers.
*/
private final List<ShopViewer> viewers = new ArrayList<>(20);
/**
* Represents the title of the shop.
*/
private final String title;
/**
* Represents the items in the store.
*/
private Item[] items;
/**
* Represents if it's a general store.
*/
private final boolean general;
/**
* Represents the currency the shop allows.
*/
private final int currency;
/**
* If the shop buys for high alch.
*/
private final boolean highAlch;
/**
* Sell price for all shop items, if needed.
*/
private int sellAllFor;
/**
* The last restock.
*/
private int lastRestock;
/**
* If the shop should restock.
*/
private boolean restock;
/**
* If it's a point shop.
*/
private boolean pointShop;
/**
* The npcs of the shop.
*/
private int[] npcs;
/**
* Constructs a new {@code Shop} {@code Object}.
*
* @param title the title.
* @param items the items.
* @param general the general.
* @param currency the currency.
* @param highAlch if high alch.
*/
public Shop(String title, Item[] items, boolean general, int currency, boolean highAlch) {
this.title = title;
this.items = items;
this.general = general;
this.currency = currency;
this.highAlch = highAlch;
this.getContainer(0).add(items);
this.setRestock(true);
lastRestock = GameWorld.getTicks() + 100;
}
/**
* Constructs a new {@code Shop} {@code Object}.
*
* @param title the title.
* @param items the items.
* @param general the general.
* @param currency the currency.
* @param highAlch if high alch.
* @param restock if restock.
*/
public Shop(String title, Item[] items, boolean general, int currency, boolean highAlch, boolean restock, int sellAllFor) {
this(title, items, general, currency, highAlch);
this.sellAllFor = sellAllFor;
this.setRestock(restock);
}
/**
* Constructs a new {@code Shop} {@code Object}
*
* @param title the shop title
* @param items items the shop can handle
* @param general is this a general store
* @param currency what currency is used
*/
public Shop(String title, Item[] items, int[] npcs, boolean general, int currency) {
this(title, items, general, currency, false);
this.setNpcs(npcs);
}
/**
* Constructs a new {@code Shop} {@code Object}.
*
* @param title the title.
* @param items the items.
* @param general the general.
*/
public Shop(String title, Item[] items, boolean general) {
this(title, items, general, COINS, false);
}
/**
* Constructs a new {@code Shop} {@code Object}.
*
* @param title the title.
* @param general the general.
*/
public Shop(String title, boolean general) {
this(title, GENERAL_STORE_ITEMS, general);
}
/**
* Constructs a new {@code Shop} {@code Object}.
*
* @param title the title.
* @param general the general.
* @param highAlch if highAlch.
*/
public Shop(String title, boolean general, boolean highAlch) {
this(title, GENERAL_STORE_ITEMS, general, COINS, highAlch);
}
/**
* Constructs a new {@code Shop} {@code Object}.
*
* @param title the title.
* @param general the general.
* @param highAlch if highAlch.
*/
public Shop(String title, boolean general, int currency, boolean highAlch) {
this(title, GENERAL_STORE_ITEMS, general, currency, highAlch);
}
/**
* Constructs a new {@code Shop} {@code Object}.
*
* @param title the title.
* @param items the items.
* @param npcs the npcs.
* @param general the general.
*/
public Shop(String title, Item[] items, int[] npcs, boolean general) {
this(title, items, general, COINS, false);
this.setNpcs(npcs);
}
/**
* Constructs a new {@code Shop} {@code Object}.
*
* @param title the title.
* @param npcs the npcs.
* @param general the general.
*/
public Shop(String title, int[] npcs, boolean general) {
this(title, GENERAL_STORE_ITEMS, npcs, general);
this.setNpcs(npcs);
}
/**
* Constructs a new {@code Shop} {@code Object}.
*
* @param title the title.
* @param npcs the npcs.
* @param general the general.
* @param highAlch if highAlch.
*/
public Shop(String title, int[] npcs, boolean general, boolean highAlch) {
this(title, GENERAL_STORE_ITEMS, general, 995, highAlch);
this.setNpcs(npcs);
}
/**
* Method used to open the shop.
*
* @param player the shop.
*/
public void open(final Player player) {
ShopViewer.extend(player, this).open();
// Browse the Lumbridge General Store
if (getTitle().equalsIgnoreCase("Lumbridge General Store")) {
player.getAchievementDiaryManager().finishTask(player, DiaryType.LUMBRIDGE, 0, 18);
}
// Browse through Oziach's Armour Shop
if (getTitle().equalsIgnoreCase("Oziach's Armour")) {
player.getAchievementDiaryManager().finishTask(player, DiaryType.VARROCK, 1, 20);
}
}
public void give(Player player, final int slot, int amount, int tabIndex) {
final Container container = getContainer(tabIndex);
final Item item = container.get(slot);
if (item == null) {
return;
}
final Item add = new Item(item.getId(), amount);
if (add.getAmount() < 1 || !player.getInventory().hasSpaceFor(add)) {
player.getPacketDispatch().sendMessage("You have no inventory space at the moment and cannot get anything.");
return;
}
add.setAmount(getAmount(player, add));
if (add.getAmount() < 1 || !player.getInventory().hasSpaceFor(add)) {
player.getPacketDispatch().sendMessage("You have no inventory space at the moment and cannot get anything.");
return;
}
player.getInventory().add(add);
update();
}
/**
* Method used to buy an item from the shop.
*
* @param slot the slot.
* @param amount the amount.
*/
public void buy(Player player, final int slot, int amount, int tabIndex) {
if (tabIndex == 1 && player.getIronmanManager().checkRestriction()) {
return;
}
final Container container = getContainer(tabIndex);
final Item item = container.get(slot);
if (item == null) {
return;
}
if (item.getAmount() == 0) {
player.getPacketDispatch().sendMessage("There is no stock of that item at the moment.");
return;
}
if (amount > item.getAmount() && !(item.getAmount() == -1)) {
amount = item.getAmount();
}
final Item add = new Item(item.getId(), amount);
if (player.getInventory().getMaximumAdd(add) < amount) {
add.setAmount(player.getInventory().getMaximumAdd(add));
}
if (add.getAmount() < 1 || !player.getInventory().hasSpaceFor(add)) {
player.getPacketDispatch().sendMessage("You have no inventory space at the moment and cannot buy anything.");
return;
}
add.setAmount(getAmount(player, add));
if (add.getAmount() < 1 || !player.getInventory().hasSpaceFor(add)) {
player.getPacketDispatch().sendMessage("You have no inventory space at the moment and cannot buy anything.");
return;
}
int price = add.getAmount() * getBuyPrice(item, player);
final Item currency = new Item(getCurrency(), price);
if (!canBuy(player, item, price, currency)) {
return;
}
if (handleBuy(player, currency)) {
if (pointShop) {
decrementPoints(player, price);
}
if (tabIndex == 0) {
if(!(container.getAmount(item) == -1))
container.replace(new Item(item.getId(), container.getAmount(item) - add.getAmount()), slot, true);
} else {
container.remove(add);
container.shift();
}
// Achievement Diary Handlers
if (add.getId() == Items.BLACK_CHAINBODY_1107 && getTitle().equalsIgnoreCase("Wayne's Chains") && !player.getAttribute("diary:falador:black-chain-bought", false)) {
player.setAttribute("/save:diary:falador:black-chain-bought", true);
}
if (add.getId() == 12622 && getTitle().equalsIgnoreCase("Sarah's Farming Shop")) {
player.getAchievementDiaryManager().finishTask(player, DiaryType.FALADOR, 0, 0);
}
if (add.getId() == Items.CANDLE_36 && getTitle().equalsIgnoreCase("Candle Shop")) {
player.getAchievementDiaryManager().finishTask(player, DiaryType.SEERS_VILLAGE, 0, 9);
}
if (getTitle().equalsIgnoreCase("Ranging Guild Ticket Exchange")) {
player.getAchievementDiaryManager().finishTask(player, DiaryType.SEERS_VILLAGE, 1, 8);
}
player.getInventory().add(add);
update();
}
}
/**
* Method used to sell an item to the shop.
*
* @param slot the slot.
* @param amount the amount.
*/
public void sell(Player player, final int slot, int amount, int tabIndex) {
final Item item = player.getInventory().get(slot);
if (item == null) {
return;
}
final ItemDefinition def = ItemDefinition.forId(item.getId());
if (!canSell(player, item, def)) {
return;
}
final Container container = getContainer(item);
if (amount > player.getInventory().getAmount(item)) {
amount = player.getInventory().getAmount(item);
}
Item add = new Item(item.getId(), amount);
if (add.getAmount() > container.getMaximumAdd(add)) {
add.setAmount(container.getMaximumAdd(add));
}
player.debug("" + add.getAmount());
if (!container.hasSpaceFor(add) || add.getAmount() < 1) {
player.getPacketDispatch().sendMessage("The shop has ran out of space.");
return;
}
final Item currency = new Item(getCurrency(), getSellingValue(add, player));
if(item.getDefinition().isStackable()){
if (!player.getInventory().hasSpaceFor(currency)) {
player.getPacketDispatch().sendMessage("You don't have enough space for that many " + currency.getName().toLowerCase() + ".");
return;
}
}
player.debug("Selling item");
if (player.getInventory().remove(add, slot, true)) {
if (currency.getAmount() > player.getInventory().getMaximumAdd(currency)) {
currency.setAmount(player.getInventory().getMaximumAdd(currency));
}
if (!add.getDefinition().isUnnoted()) {
add = new Item(add.getNoteChange(), add.getAmount());
}
if (container.getAmount(add.getId()) == -1 || container.add(add)) {
if (currency.getAmount() > 0) {
player.debug("Adding coins to inventory");
player.getInventory().add(currency);
}
final ShopViewer viewer = player.getExtension(ShopViewer.class);
tabIndex = container == getContainers()[0] ? 0 : 1;
sendStock(player, tabIndex);
if (viewer != null) {
viewer.setTabIndex(tabIndex);
}
update();
}
}
}
/**
* Values an item.
*
* @param player the player.
* @param viewer the viewer.
* @param item the item.
* @param sell the sell.
*/
public void value(Player player, ShopViewer viewer, Item item, boolean sell) {
if (sell) {
if (pointShop || item.getId() == viewer.getShop().getCurrency() || !item.getDefinition().isTradeable() || !viewer.getShop().itemAllowed(item.getId())) {
player.getPacketDispatch().sendMessage("You can't sell this item.");
return;
}
final int value = viewer.getShop().getSellingValue(new Item(item.getId(), 1), player);
String currency = pointShop ? getPointsName() : ItemDefinition.forId(viewer.getShop().getCurrency()).getName().toLowerCase();
if (value == 1 && currency.charAt(currency.length() - 1) == 's') {
currency = currency.substring(0, currency.length() - 1);
}
player.getPacketDispatch().sendMessage(item.getName() + ": shop will buy for " + value + " " + currency + ".");
} else {
int value = viewer.getShop().getBuyPrice(item, player);
String name = pointShop ? getPointsName() + "s" : ItemDefinition.forId(viewer.getShop().getCurrency()).getName().toLowerCase();
if (value == 1 && (name.charAt(name.length() - 1) == 's')) {
name = name.substring(0, name.length() - 1);
}
player.getPacketDispatch().sendMessage("" + item.getName() + ": currently costs " + value + " " + name + ".");
}
}
/**
* Method used to send a stock
*
* @param player
* @param tabIndex
*/
public void sendStock(final Player player, int tabIndex) {
final boolean main = tabIndex == 0;
player.getPacketDispatch().sendInterfaceConfig(620, 23, !main);
player.getPacketDispatch().sendInterfaceConfig(620, 24, main);
player.getPacketDispatch().sendInterfaceConfig(620, 29, !main);
player.getPacketDispatch().sendInterfaceConfig(620, 25, main);
player.getPacketDispatch().sendInterfaceConfig(620, 27, main);
player.getPacketDispatch().sendInterfaceConfig(620, 26, false);
player.getPacketDispatch().sendAccessMask(1278, main ? 23 : 24, 620, 0, 40);
}
/**
* Method used to update the viewers.
*/
public void update() {
for (ShopViewer viewer : viewers) {
viewer.update();
}
}
/**
* Method used to restock the shop.
*/
public void restock() {
for (Container container : containers) {
for (int i = 0; i < container.toArray().length; i++) {
final boolean main = (container == containers[0]);
final Item item = container.toArray()[i];
if (item == null) {
continue;
}
boolean reduce = !main;
if (main) {
if (item.getAmount() < items[i].getAmount()) {
item.setAmount(item.getAmount() + 1);
}
reduce = item.getAmount() > items[i].getAmount();
}
if (reduce) {
int amount = item.getAmount() - 1;
if (amount < 1 && !main) {
container.remove(item);
} else {
item.setAmount(amount);
}
if (!main) {
container.shift();
}
}
}
}
update();
}
/**
* Checks if the player can sell an item to the shop.
*
* @param player the player.
* @param item the item.
* @param def the def.
* @return {@code True} if so.
*/
public boolean canSell(Player player, Item item, ItemDefinition def) {
if (pointShop || item.getDefinition().hasDestroyAction() || !def.isTradeable() || !itemAllowed(item.getId())) {
player.getPacketDispatch().sendMessage("You can't sell this item to this shop.");
return false;
}
if (item.getId() == getCurrency()) {
player.getPacketDispatch().sendMessage("You can't sell " + item.getName().toLowerCase() + " to a shop.");
return false;
}
return true;
}
/**
* Gets the amount to buy/sell.
*
* @param player the player.
* @param add the added item.
* @return the amount.
*/
public int getAmount(Player player, Item add) {
return add.getAmount();
}
/**
* Checks if the player can buy the item.
*
* @param player the player.
* @param currency the currency.
* @return {@code True} if so.
*/
public boolean handleBuy(Player player, Item currency) {
return pointShop || player.getInventory().remove(currency);
}
/**
* Checks if the player can buy from the shop.
*
* @param player the player.
* @param item the item.
* @param price the price.
* @param currency the currency.
* @return {@code True} if they can buy.
*/
public boolean canBuy(Player player, Item item, int price, Item currency) {
if (!pointShop && !player.getInventory().containsItem(currency)) {
player.getPacketDispatch().sendMessage("You don't have enough " + ItemDefinition.forId(getCurrency()).getName().toLowerCase() + ".");
return false;
}
if (pointShop && getPoints(player) < price) {
player.sendMessage("You don't have enough " + getPointsName() + "s.");
return false;
}
return true;
}
/**
* Gets the points.
*
* @param player the player.
* @return the points.
*/
public int getPoints(Player player) {
return 0;
}
/**
* Decrements the points.
*
* @param player the player.
* @param decrement the decrementation.
*/
public void decrementPoints(Player player, int decrement) {
}
/**
* Gets the points name.
*
* @return the name.
*/
public String getPointsName() {
return "";
}
/**
* Gets the value gained for selling this item to a certain shop.
*
* @param item The item to sell.
* @param player the player.
* @return The value.
*/
public int getSellingValue(Item item, Player player) {
if (!item.getDefinition().isUnnoted()) {
player.setAttribute("shop:originalId",item.getId());
item = new Item(item.getNoteChange(), item.getAmount());
}
int amount = getContainer(1).getAmount(item);
if (amount < 1) {
amount = getContainer(0).getAmount(item);
}
int value = getSellValue(player, amount, item);
return value;
}
/**
* Gets the selling value formula based.
*
* @param amount the amount.
* @param item the item.
* @return the selling value.
*/
private int getSellValue(Player player, int amount, Item item) {
int id = player.getAttribute("shop:originalId",item.getId());
if(item.getAmount() > ContentAPI.amountInInventory(player, id)){
item.setAmount(ContentAPI.amountInInventory(player, id));
player.removeAttribute("shop:originalId");
}
double diff = item.getDefinition().isStackable() ? 0.005 : 0.05;
double maxMod = 1.0 - (amount * diff);
if (maxMod < 0.25) {
maxMod = 0.25;
}
double minMod = maxMod - ((item.getAmount() - 1) * diff);
if (minMod < 0.25) {
minMod = 0.25;
}
double mod = (maxMod + minMod) / 2;
SystemLogger.logInfo("" + item.getDefinition().getAlchemyValue(highAlch) + " " + mod + " " + item.getAmount());
int baseValue = item.getDefinition().getAlchemyValue(highAlch);
int value = (int) (baseValue * mod * item.getAmount());
if(getCurrency() == Items.TOKKUL_6529 && item.getId() == Items.CHAOS_RUNE_562) value = 13;
if(getCurrency() == Items.TOKKUL_6529 && item.getId() == Items.DEATH_RUNE_560) value = 27;
if(item.getId() == 12183){
value = 25 * item.getAmount();
}
return value;
}
/**
* Gets the buying price.
*
* @param item the item.
* @return the price.
*/
public int getBuyPrice(Item item, Player player) {
item = new Item(item.getId(), 1);
int price = item.getDefinition().getMaxValue();
int sellVal = getSellingValue(item, player);
if (price < sellVal) {
price = getSellValue(player, 0, item) + sellVal - (sellVal - item.getDefinition().getMaxValue());
}
if (price < 0) {
price = 1;
}
if (getCurrency() == TOKKUL) {
int tokkul = item.getDefinition().getConfiguration("tokkul_price", -1);
if (tokkul > 0) {
price = tokkul;
}
if (player.getAchievementDiaryManager().getKaramjaGlove() != -1) {
price *= 0.86666666667;
}
}
if (getCurrency() == ARCHERY_TICKET) {
int tickets = item.getDefinition().getConfiguration(ItemConfigParser.ARCHERY_TICKET_PRICE,-1);
if (tickets > 0) {
price = tickets;
}
}
return (getSellAllFor() > 0 ? getSellAllFor() : price);
}
/**
* Checks if the item is allowed to be sold to the shop.
*
* @param itemId the item id.
* @return {@code True} if so.
*/
public boolean itemAllowed(int itemId) {
if (general) {
return true;
}
int noteId = ItemDefinition.forId(itemId).getNoteId();
if (!ItemDefinition.forId(itemId).isUnnoted()) {
noteId = ItemDefinition.forId(noteId).getNoteId();
}
for (Item id : items) {
if (itemId == id.getId() || (noteId > -1 && noteId == ItemDefinition.forId(id.getId()).getNoteId())) {
return true;
}
}
return false;
}
/**
* Gets the container the item should go to.
*
* @param item the item.
* @return the container.
*/
public Container getContainer(Item item) {
int itemId = item.getId();
int noteId = ItemDefinition.forId(itemId).getNoteId();
if (!ItemDefinition.forId(itemId).isUnnoted()) {
noteId = ItemDefinition.forId(noteId).getNoteId();
}
for (Item i : items) {
if (i.getId() == item.getId() || (noteId > -1 && noteId == ItemDefinition.forId(i.getId()).getNoteId())) {
return getContainer(0);
}
}
return getContainer(1);
}
/**
* Creates a copy of this shop.
*
* @return the shop.
*/
public Shop copy() {
return new Shop(title, items, general, currency, highAlch);
}
/**
* Gets the container on the slot.
*
* @param tabIndex the tab index.
* @return the container.
*/
public Container getContainer(int tabIndex) {
if (tabIndex > containers.length) {
throw new IndexOutOfBoundsException("Error! Shop tab index out of bounds.");
}
return containers[tabIndex];
}
/**
* Gets the viewers.
*
* @return The viewers.
*/
public List<ShopViewer> getViewers() {
return viewers;
}
/**
* Gets the title.
*
* @return The title.
*/
public String getTitle() {
return title;
}
/**
* Gets the items.
*
* @return The items.
*/
public Item[] getItems() {
return items;
}
/**
* Sets the items.
*
* @return true if the items were changed.
*/
public boolean setItems(Item... item) {
return items == item;
}
/**
* Gets the general.
*
* @return The general.
*/
public boolean isGeneral() {
return general;
}
/**
* Gets the currency.
*
* @return The currency.
*/
public int getCurrency() {
return currency;
}
/**
* Gets the containers.
*
* @return The containers.
*/
public Container[] getContainers() {
return containers;
}
/**
* Gets the bhighAlch.
*
* @return the highAlch
*/
public boolean isHighAlch() {
return highAlch;
}
@Override
public String toString() {
return "Shop [containers=" + Arrays.toString(containers) + ", viewers=" + viewers + ", title=" + title + ", items=" + Arrays.toString(items) + ", general=" + general + ", currency=" + currency + ", highAlch=" + highAlch + "]";
}
/**
* Gets the lastRestock.
*
* @return the lastRestock
*/
public int getLastRestock() {
return lastRestock;
}
/**
* Sets the balastRestock.
*
* @param lastRestock the lastRestock to set.
*/
public void setLastRestock(int lastRestock) {
this.lastRestock = lastRestock;
}
/**
* Gets the npcs.
*
* @return the npcs
*/
public int[] getNpcs() {
return npcs;
}
/**
* Sets the banpcs.
*
* @param npcs the npcs to set.
*/
public void setNpcs(int[] npcs) {
this.npcs = npcs;
}
/**
* Gets the pointShop.
*
* @return the pointShop
*/
public boolean isPointShop() {
return pointShop;
}
/**
* Sets the pointShop.
*
* @param pointShop the pointShop to set.
*/
public void setPointShop(boolean pointShop) {
this.pointShop = pointShop;
}
/**
* Check if shop should restock.
*
* @return the restock
*/
public boolean isRestock() {
return restock;
}
/**
* Sets the restock.
*
* @param reStock
*/
public void setRestock(boolean reStock) {
restock = reStock;
}
/**
* Gets the SellAllFor value.
*
* @return the sellAllFor
*/
public int getSellAllFor() {
return sellAllFor;
}
public void setItems(ArrayList<Item> items){
this.items = items.toArray(new Item[0]);
}
}

View file

@ -0,0 +1,865 @@
package core.game.content.global.shop
import rs09.game.world.GameWorld.ticks
import api.ContentAPI.amountInInventory
import rs09.game.system.SystemLogger.logInfo
import core.game.node.entity.player.link.diary.DiaryType
import core.cache.def.impl.ItemDefinition
import core.game.container.Container
import core.game.container.ContainerType
import core.game.node.entity.player.Player
import core.game.node.item.Item
import org.rs09.consts.Items.BLACK_CHAINBODY_1107
import org.rs09.consts.Items.BOWL_1923
import org.rs09.consts.Items.BUCKET_1925
import org.rs09.consts.Items.CAKE_TIN_1887
import org.rs09.consts.Items.CANDLE_36
import org.rs09.consts.Items.CHAOS_RUNE_562
import org.rs09.consts.Items.CHISEL_1755
import org.rs09.consts.Items.DEATH_RUNE_560
import org.rs09.consts.Items.EMPTY_POT_1931
import org.rs09.consts.Items.HAMMER_2347
import org.rs09.consts.Items.JUG_1935
import org.rs09.consts.Items.NEWCOMER_MAP_550
import org.rs09.consts.Items.SECURITY_BOOK_9003
import org.rs09.consts.Items.SHEARS_1735
import org.rs09.consts.Items.TINDERBOX_590
import org.rs09.consts.Items.TOKKUL_6529
import rs09.game.system.config.ItemConfigParser
import java.lang.IndexOutOfBoundsException
import java.util.Arrays
import java.util.ArrayList
/**
* A class representing a shop.
*
* @author 'Vexia
* @author Jamix77
*/
open class Shop @JvmOverloads constructor(
/**
* Represents the title of the shop.
*/
val title: String,
/**
* Represents the items in the store.
*/
var items: Array<Item>,
/**
* Represents if it's a general store.
*/
val isGeneral: Boolean,
/**
* Represents the currency the shop allows.
*/
val currency: Int = COINS,
/**
* If the shop buys for high alch.
*/
val isHighAlch: Boolean = false
) {
/**
* Gets the containers.
*
* @return The containers.
*/
/**
* Represents the shop containers.
*/
val containers = arrayOf(Container(40, ContainerType.SHOP), Container(40, ContainerType.SHOP))
/**
* Gets the viewers.
*
* @return The viewers.
*/
/**
* Represents the list of shop viewers.
*/
val viewers: List<ShopViewer> = ArrayList(20)
/**
* Gets the title.
*
* @return The title.
*/
/**
* Gets the items.
*
* @return The items.
*/
/**
* Gets the general.
*
* @return The general.
*/
/**
* Gets the currency.
*
* @return The currency.
*/
/**
* Gets the bhighAlch.
*
* @return the highAlch
*/
/**
* Gets the SellAllFor value.
*
* @return the sellAllFor
*/
/**
* Sell price for all shop items, if needed.
*/
var sellAllFor = 0
private set
/**
* Gets the lastRestock.
*
* @return the lastRestock
*/
/**
* Sets the balastRestock.
*
* @param lastRestock the lastRestock to set.
*/
/**
* The last restock.
*/
var lastRestock = 0
/**
* Check if shop should restock.
*
* @return the restock
*/
/**
* Sets the restock.
*
* @param reStock
*/
/**
* If the shop should restock.
*/
var isRestock = false
/**
* Gets the pointShop.
*
* @return the pointShop
*/
/**
* Sets the pointShop.
*
* @param pointShop the pointShop to set.
*/
/**
* If it's a point shop.
*/
var isPointShop = false
/**
* Gets the npcs.
*
* @return the npcs
*/
/**
* Sets the banpcs.
*
* @param npcs the npcs to set.
*/
/**
* The npcs of the shop.
*/
var npcs: IntArray = intArrayOf()
/**
* Constructs a new `Shop` `Object`.
*
* @param title the title.
* @param items the items.
* @param general the general.
* @param currency the currency.
* @param highAlch if high alch.
* @param restock if restock.
*/
constructor(
title: String,
items: Array<Item>,
general: Boolean,
currency: Int,
highAlch: Boolean,
restock: Boolean,
sellAllFor: Int
) : this(title, items, general, currency, highAlch) {
this.sellAllFor = sellAllFor
isRestock = restock
}
/**
* Constructs a new `Shop` `Object`
*
* @param title the shop title
* @param items items the shop can handle
* @param general is this a general store
* @param currency what currency is used
*/
constructor(title: String, items: Array<Item>, npcs: IntArray, general: Boolean, currency: Int) : this(
title,
items,
general,
currency,
false
) {
this.npcs = npcs
}
/**
* Constructs a new `Shop` `Object`.
*
* @param title the title.
* @param general the general.
*/
constructor(title: String, general: Boolean) : this(title, GENERAL_STORE_ITEMS, general) {}
/**
* Constructs a new `Shop` `Object`.
*
* @param title the title.
* @param general the general.
* @param highAlch if highAlch.
*/
constructor(title: String, general: Boolean, highAlch: Boolean) : this(
title,
GENERAL_STORE_ITEMS,
general,
COINS,
highAlch
) {
}
/**
* Constructs a new `Shop` `Object`.
*
* @param title the title.
* @param general the general.
* @param highAlch if highAlch.
*/
constructor(title: String, general: Boolean, currency: Int, highAlch: Boolean) : this(
title,
GENERAL_STORE_ITEMS,
general,
currency,
highAlch
) {
}
/**
* Constructs a new `Shop` `Object`.
*
* @param title the title.
* @param items the items.
* @param npcs the npcs.
* @param general the general.
*/
constructor(title: String, items: Array<Item>, npcs: IntArray, general: Boolean) : this(
title,
items,
general,
COINS,
false
) {
this.npcs = npcs
}
/**
* Constructs a new `Shop` `Object`.
*
* @param title the title.
* @param npcs the npcs.
* @param general the general.
*/
constructor(title: String, npcs: IntArray, general: Boolean) : this(title, GENERAL_STORE_ITEMS, npcs, general) {
this.npcs = npcs
}
/**
* Constructs a new `Shop` `Object`.
*
* @param title the title.
* @param npcs the npcs.
* @param general the general.
* @param highAlch if highAlch.
*/
constructor(title: String, npcs: IntArray, general: Boolean, highAlch: Boolean) : this(
title,
GENERAL_STORE_ITEMS,
general,
995,
highAlch
) {
this.npcs = npcs
}
/**
* Method used to open the shop.
*
* @param player the shop.
*/
fun open(player: Player) {
ShopViewer.extend(player, this).open()
// Browse the Lumbridge General Store
if (title.equals("Lumbridge General Store", ignoreCase = true)) {
player.achievementDiaryManager.finishTask(player, DiaryType.LUMBRIDGE, 0, 18)
}
// Browse through Oziach's Armour Shop
if (title.equals("Oziach's Armour", ignoreCase = true)) {
player.achievementDiaryManager.finishTask(player, DiaryType.VARROCK, 1, 20)
}
}
fun give(player: Player, slot: Int, amount: Int, tabIndex: Int) {
val container = getContainer(tabIndex)
val item = container[slot] ?: return
val add = Item(item.id, amount)
if (add.amount < 1 || !player.inventory.hasSpaceFor(add)) {
player.packetDispatch.sendMessage("You have no inventory space at the moment and cannot get anything.")
return
}
add.amount = getAmount(player, add)
if (add.amount < 1 || !player.inventory.hasSpaceFor(add)) {
player.packetDispatch.sendMessage("You have no inventory space at the moment and cannot get anything.")
return
}
player.inventory.add(add)
update()
}
/**
* Method used to buy an item from the shop.
*
* @param slot the slot.
* @param amount the amount.
*/
fun buy(player: Player, slot: Int, amount: Int, tabIndex: Int) {
var amount = amount
if (tabIndex == 1 && player.ironmanManager.checkRestriction()) {
return
}
val container = getContainer(tabIndex)
val item = container[slot] ?: return
if (item.amount == 0) {
player.packetDispatch.sendMessage("There is no stock of that item at the moment.")
return
}
if (amount > item.amount && item.amount != -1) {
amount = item.amount
}
val add = Item(item.id, amount)
if (player.inventory.getMaximumAdd(add) < amount) {
add.amount = player.inventory.getMaximumAdd(add)
}
if (add.amount < 1 || !player.inventory.hasSpaceFor(add)) {
player.packetDispatch.sendMessage("You have no inventory space at the moment and cannot buy anything.")
return
}
add.amount = getAmount(player, add)
if (add.amount < 1 || !player.inventory.hasSpaceFor(add)) {
player.packetDispatch.sendMessage("You have no inventory space at the moment and cannot buy anything.")
return
}
val price = add.amount * getBuyPrice(item, player)
val currency = Item(currency, price)
if (!canBuy(player, item, price, currency)) {
return
}
if (handleBuy(player, currency)) {
if (isPointShop) {
decrementPoints(player, price)
}
if (tabIndex == 0) {
if (container.getAmount(item) != -1) container.replace(
Item(
item.id,
container.getAmount(item) - add.amount
), slot, true
)
} else {
container.remove(add)
container.shift()
}
// Achievement Diary Handlers
if (add.id == BLACK_CHAINBODY_1107 && title.equals("Wayne's Chains", ignoreCase = true) && !player.getAttribute("diary:falador:black-chain-bought", false)) {
player.setAttribute("/save:diary:falador:black-chain-bought", true)
}
if (add.id == 12622 && title.equals("Sarah's Farming Shop", ignoreCase = true)) {
player.achievementDiaryManager.finishTask(player, DiaryType.FALADOR, 0, 0)
}
if (add.id == CANDLE_36 && title.equals("Candle Shop", ignoreCase = true)) {
player.achievementDiaryManager.finishTask(player, DiaryType.SEERS_VILLAGE, 0, 9)
}
if (title.equals("Ranging Guild Ticket Exchange", ignoreCase = true)) {
player.achievementDiaryManager.finishTask(player, DiaryType.SEERS_VILLAGE, 1, 8)
}
player.inventory.add(add)
update()
}
}
/**
* Method used to sell an item to the shop.
*
* @param slot the slot.
* @param amount the amount.
*/
fun sell(player: Player, slot: Int, amount: Int, tabIndex: Int) {
var amount = amount
var tabIndex = tabIndex
val item = player.inventory[slot] ?: return
val def = ItemDefinition.forId(item.id)
if (!canSell(player, item, def)) {
return
}
val container = getContainer(item)
if (amount > player.inventory.getAmount(item)) {
amount = player.inventory.getAmount(item)
}
var add = Item(item.id, amount)
if (add.amount > container.getMaximumAdd(add)) {
add.amount = container.getMaximumAdd(add)
}
player.debug("" + add.amount)
if (!container.hasSpaceFor(add) || add.amount < 1) {
player.packetDispatch.sendMessage("The shop has ran out of space.")
return
}
val currency = Item(currency, getSellingValue(add, player))
if (item.definition.isStackable) {
if (!player.inventory.hasSpaceFor(currency)) {
player.packetDispatch.sendMessage("You don't have enough space for that many " + currency.name.toLowerCase() + ".")
return
}
}
player.debug("Selling item")
if (player.inventory.remove(add, slot, true)) {
if (currency.amount > player.inventory.getMaximumAdd(currency)) {
currency.amount = player.inventory.getMaximumAdd(currency)
}
if (!add.definition.isUnnoted) {
add = Item(add.noteChange, add.amount)
}
if (container.getAmount(add.id) == -1 || container.add(add)) {
if (currency.amount > 0) {
player.debug("Adding coins to inventory")
player.inventory.add(currency)
}
val viewer = player.getExtension<ShopViewer>(ShopViewer::class.java)
tabIndex = if (container === containers[0]) 0 else 1
sendStock(player, tabIndex)
if (viewer != null) {
viewer.tabIndex = tabIndex
}
update()
}
}
}
/**
* Values an item.
*
* @param player the player.
* @param viewer the viewer.
* @param item the item.
* @param sell the sell.
*/
fun value(player: Player, viewer: ShopViewer, item: Item, sell: Boolean) {
if (sell) {
if (isPointShop || item.id == viewer.shop.currency || !item.definition.isTradeable || !viewer.shop.itemAllowed(
item.id
)
) {
player.packetDispatch.sendMessage("You can't sell this item.")
return
}
val value = viewer.shop.getSellingValue(Item(item.id, 1), player)
var currency =
if (isPointShop) pointsName else ItemDefinition.forId(viewer.shop.currency).name.toLowerCase()
if (value == 1 && currency[currency.length - 1] == 's') {
currency = currency.substring(0, currency.length - 1)
}
player.packetDispatch.sendMessage(item.name + ": shop will buy for " + value + " " + currency + ".")
} else {
val value = viewer.shop.getBuyPrice(item, player)
var name =
if (isPointShop) pointsName + "s" else ItemDefinition.forId(viewer.shop.currency).name.toLowerCase()
if (value == 1 && name[name.length - 1] == 's') {
name = name.substring(0, name.length - 1)
}
player.packetDispatch.sendMessage("" + item.name + ": currently costs " + value + " " + name + ".")
}
}
/**
* Method used to send a stock
*
* @param player
* @param tabIndex
*/
fun sendStock(player: Player, tabIndex: Int) {
val main = tabIndex == 0
player.packetDispatch.sendInterfaceConfig(620, 23, !main)
player.packetDispatch.sendInterfaceConfig(620, 24, main)
player.packetDispatch.sendInterfaceConfig(620, 29, !main)
player.packetDispatch.sendInterfaceConfig(620, 25, main)
player.packetDispatch.sendInterfaceConfig(620, 27, main)
player.packetDispatch.sendInterfaceConfig(620, 26, false)
player.packetDispatch.sendAccessMask(1278, if (main) 23 else 24, 620, 0, 40)
}
/**
* Method used to update the viewers.
*/
fun update() {
for (viewer in viewers) {
viewer.update()
}
}
/**
* Method used to restock the shop.
*/
fun restock() {
for (container in containers) {
for (i in container.toArray().indices) {
val main = container === containers[0]
val item = container.toArray()[i] ?: continue
var reduce = !main
if (main) {
if (item.amount < items[i].amount) {
item.amount = item.amount + 1
}
reduce = item.amount > items[i].amount
}
if (reduce) {
val amount = item.amount - 1
if (amount < 1 && !main) {
container.remove(item)
} else {
item.amount = amount
}
if (!main) {
container.shift()
}
}
}
}
update()
}
/**
* Checks if the player can sell an item to the shop.
*
* @param player the player.
* @param item the item.
* @param def the def.
* @return `True` if so.
*/
open fun canSell(player: Player, item: Item, def: ItemDefinition): Boolean {
if (isPointShop || item.definition.hasDestroyAction() || !def.isTradeable || !itemAllowed(item.id)) {
player.packetDispatch.sendMessage("You can't sell this item to this shop.")
return false
}
if (item.id == currency) {
player.packetDispatch.sendMessage("You can't sell " + item.name.toLowerCase() + " to a shop.")
return false
}
return true
}
/**
* Gets the amount to buy/sell.
*
* @param player the player.
* @param add the added item.
* @return the amount.
*/
fun getAmount(player: Player?, add: Item): Int {
return add.amount
}
/**
* Checks if the player can buy the item.
*
* @param player the player.
* @param currency the currency.
* @return `True` if so.
*/
fun handleBuy(player: Player, currency: Item?): Boolean {
return isPointShop || player.inventory.remove(currency)
}
/**
* Checks if the player can buy from the shop.
*
* @param player the player.
* @param item the item.
* @param price the price.
* @param currency the currency.
* @return `True` if they can buy.
*/
fun canBuy(player: Player, item: Item, price: Int, currency: Item): Boolean {
if (!isPointShop && !player.inventory.containsItem(currency)) {
player.packetDispatch.sendMessage(
"You don't have enough " + ItemDefinition.forId(currency.id).name.toLowerCase() + "."
)
return false
}
if (isPointShop && getPoints(player) < price) {
player.sendMessage("You don't have enough " + pointsName + "s.")
return false
}
return true
}
/**
* Gets the points.
*
* @param player the player.
* @return the points.
*/
fun getPoints(player: Player?): Int {
return 0
}
/**
* Decrements the points.
*
* @param player the player.
* @param decrement the decrementation.
*/
fun decrementPoints(player: Player?, decrement: Int) {}
/**
* Gets the points name.
*
* @return the name.
*/
val pointsName: String
get() = ""
/**
* Gets the value gained for selling this item to a certain shop.
*
* @param item The item to sell.
* @param player the player.
* @return The value.
*/
fun getSellingValue(item: Item, player: Player): Int {
var item = item
if (!item.definition.isUnnoted) {
player.setAttribute("shop:originalId", item.id)
item = Item(item.noteChange, item.amount)
}
var amount = getContainer(1).getAmount(item)
if (amount < 1) {
amount = getContainer(0).getAmount(item)
}
return getSellingValue(player, amount, item)
}
/**
* Gets the selling value formula based.
*
* @param amount the amount.
* @param item the item.
* @return the selling value.
*/
private fun getSellingValue(player: Player, amount: Int, item: Item): Int {
val id = player.getAttribute("shop:originalId", item.id)
if (item.amount > amountInInventory(player, id)) {
item.amount = amountInInventory(player, id)
player.removeAttribute("shop:originalId")
}
val diff = if (item.definition.isStackable) 0.005 else 0.05
var maxMod = 1.0 - amount * diff
if (maxMod < 0.25) {
maxMod = 0.25
}
var minMod = maxMod - (item.amount - 1) * diff
if (minMod < 0.25) {
minMod = 0.25
}
val mod = (maxMod + minMod) / 2
logInfo("" + item.definition.getAlchemyValue(isHighAlch) + " " + mod + " " + item.amount)
val baseValue = item.definition.getAlchemyValue(isHighAlch)
var value = (baseValue * mod * item.amount).toInt()
if (currency == TOKKUL_6529 && item.id == CHAOS_RUNE_562) value = 13 * item.amount
if (currency == TOKKUL_6529 && item.id == DEATH_RUNE_560) value = 27 * item.amount
if (item.id == 12183) {
value = 25 * item.amount
}
return value
}
/**
* Gets the buying price.
*
* @param item the item.
* @return the price.
*/
open fun getBuyPrice(item: Item, player: Player): Int {
var item = item
item = Item(item.id, 1)
var price = item.definition.maxValue
val sellVal = getSellingValue(item, player)
if (price < sellVal) {
price = getSellingValue(player, 0, item) + sellVal - (sellVal - item.definition.maxValue)
}
if (price < 0) {
price = 1
}
if (currency == TOKKUL) {
val tokkul = item.definition.getConfiguration("tokkul_price", -1)
if (tokkul > 0) {
price = tokkul
}
if (player.achievementDiaryManager.karamjaGlove != -1) {
price = kotlin.math.floor(price * 0.87).toInt()
}
}
if (currency == ARCHERY_TICKET) {
val tickets = item.definition.getConfiguration(ItemConfigParser.ARCHERY_TICKET_PRICE, -1)
if (tickets > 0) {
price = tickets
}
}
return if (sellAllFor > 0) sellAllFor else price
}
/**
* Checks if the item is allowed to be sold to the shop.
*
* @param itemId the item id.
* @return `True` if so.
*/
fun itemAllowed(itemId: Int): Boolean {
if (isGeneral) {
return true
}
var noteId = ItemDefinition.forId(itemId).noteId
if (!ItemDefinition.forId(itemId).isUnnoted) {
noteId = ItemDefinition.forId(noteId).noteId
}
for (id in items) {
if (itemId == id.id || noteId > -1 && noteId == ItemDefinition.forId(id.id).noteId) {
return true
}
}
return false
}
/**
* Gets the container the item should go to.
*
* @param item the item.
* @return the container.
*/
fun getContainer(item: Item): Container {
val itemId = item.id
var noteId = ItemDefinition.forId(itemId).noteId
if (!ItemDefinition.forId(itemId).isUnnoted) {
noteId = ItemDefinition.forId(noteId).noteId
}
for (i in items) {
if (i.id == item.id || noteId > -1 && noteId == ItemDefinition.forId(i.id).noteId) {
return getContainer(0)
}
}
return getContainer(1)
}
/**
* Creates a copy of this shop.
*
* @return the shop.
*/
fun copy(): Shop {
return Shop(title, items, isGeneral, currency, isHighAlch)
}
/**
* Gets the container on the slot.
*
* @param tabIndex the tab index.
* @return the container.
*/
fun getContainer(tabIndex: Int): Container {
if (tabIndex > containers.size) {
throw IndexOutOfBoundsException("Error! Shop tab index out of bounds.")
}
return containers[tabIndex]
}
override fun toString(): String {
return "Shop [containers=" + Arrays.toString(containers) + ", viewers=" + viewers + ", title=" + title + ", items=" + Arrays.toString(
items
) + ", general=" + isGeneral + ", currency=" + currency + ", highAlch=" + isHighAlch + "]"
}
fun setItems(items: ArrayList<Item>) {
this.items = items.toTypedArray()
}
companion object {
/**
* Represents the general store items.
*/
val GENERAL_STORE_ITEMS = arrayOf(
Item(EMPTY_POT_1931, 5),
Item(JUG_1935, 5),
Item(SHEARS_1735, 2),
Item(BUCKET_1925, 3),
Item(BOWL_1923, 2),
Item(CAKE_TIN_1887, 2),
Item(TINDERBOX_590, 2),
Item(CHISEL_1755, 2),
Item(HAMMER_2347, 5),
Item(NEWCOMER_MAP_550, 5),
Item(SECURITY_BOOK_9003, 5)
)
/**
* Represents the coins item.
*/
private const val COINS = 995
/**
* Represents the tokkul item id.
*/
private const val TOKKUL = 6529
/**
* Represents the archery ticket item id
*/
private const val ARCHERY_TICKET = 1464
}
/**
* Constructs a new `Shop` `Object`.
*
* @param title the title.
* @param items the items.
* @param isGeneral the general.
* @param currency the currency.
* @param isHighAlch if high alch.
*/
/**
* Constructs a new `Shop` `Object`.
*
* @param title the title.
* @param items the items.
* @param general the general.
*/
init {
this.getContainer(0).add(*items)
isRestock = true
lastRestock = ticks + 100
}
}

View file

@ -13,6 +13,8 @@ import core.game.node.entity.Entity;
import core.game.node.entity.npc.NPC;
import core.game.node.entity.player.Player;
import core.game.node.object.Scenery;
import org.json.simple.JSONObject;
import rs09.ServerStore;
import rs09.game.world.GameWorld;
import core.game.world.map.Location;
import core.game.world.map.zone.MapZone;
@ -261,13 +263,13 @@ public final class ChaosTunnelZone extends MapZone implements Plugin<Object> {
* @param player The player.
*/
private void commenceBorkBattle(Player player) {
if ((System.currentTimeMillis() - player.getSavedData().getActivityData().getLastBorkBattle()) < 24 * 60 * 60_000 && GameWorld.getSettings().isHosted()) {
if (ServerStore.getBoolean(getStoreFile(), player.getUsername().toLowerCase()) && GameWorld.getSettings().isHosted()) {
player.getPacketDispatch().sendMessage("The portal's magic is too weak to teleport you right now.");
return;
}
player.lock(10);
player.graphics(Graphics.create(110));
player.getSavedData().getActivityData().setLastBorkBattle(System.currentTimeMillis());
getStoreFile().put(player.getUsername().toLowerCase(), true);
ActivityManager.start(player, "Bork cutscene", false);
}
@ -352,4 +354,12 @@ public final class ChaosTunnelZone extends MapZone implements Plugin<Object> {
PORTALS.put(location, loc);
}
/**
* Returns the Server Store file for Bork being killed for the day
* @return The JSONObject containing the boolean of whether Bork has been killed
*/
private JSONObject getStoreFile() {
return ServerStore.getArchive("daily-bork-killed");
}
}

View file

@ -44,7 +44,7 @@ public class RoavarDialogue extends DialoguePlugin {
stage = 1;
break;
case 1:
interpreter.sendOptions("Select an Option", "Can I buy a beer?", "Can I hear some gossipp?", "Do you have a spare silver sickle?", "Nothing thanks.");
interpreter.sendOptions("Select an Option", "Can I buy a beer?", "Can I hear some gossipp?", "Nothing thanks.");
stage = 2;
break;
case 2:
@ -58,10 +58,6 @@ public class RoavarDialogue extends DialoguePlugin {
stage = 20;
break;
case 3:
interpreter.sendDialogues(player, FacialExpression.HALF_GUILTY, "Would you happen to have a spare silver sickle?");
stage = 30;
break;
case 4:
interpreter.sendDialogues(player, FacialExpression.HALF_GUILTY, "Nothing thanks.");
stage = 40;
break;

View file

@ -1,5 +1,6 @@
package core.game.node.entity.combat.spell;
import api.ContentAPI;
import core.game.node.entity.skill.Skills;
import core.game.node.entity.skill.magic.Runes;
import core.game.node.entity.Entity;
@ -181,6 +182,11 @@ public final class CurseSpells extends CombatSpell {
}
}
@Override
public void addExperience(Entity entity, int hit) {
entity.getSkills().addExperience(Skills.MAGIC, getExperience());
}
@Override
public Plugin<SpellType> newInstance(SpellType type) throws Throwable {
SpellBook.MODERN.register(2, new CurseSpells(SpellType.CONFUSE, 3, 13.0, 99, CONFUSE_START, CONFUSE_PROJECTILE, CONFUSE_END, Runes.BODY_RUNE.getItem(1), Runes.EARTH_RUNE.getItem(2), Runes.WATER_RUNE.getItem(3)));

View file

@ -513,7 +513,7 @@ public class NPC extends Entity {
if (getZoneMonitor().handleDeath(killer)) {
return;
}
if (task != null && killer instanceof Player && ((Player) killer).getSlayer().getTask() == task) {
if (task != null && killer instanceof Player && ((Player) killer).getSlayer().getTask() == task && ((Player) killer).getSlayer().hasTask()) {
((Player) killer).getSlayer().finalizeDeath(killer.asPlayer(), this);
}
if (killer instanceof Player && killer.getAttribute("jobs:id",null) != null) {

View file

@ -22,6 +22,9 @@ import org.rs09.consts.Items;
import java.nio.ByteBuffer;
import static java.lang.Math.floor;
import static java.lang.Math.max;
/**
* Represents an entity's skills.
* @author Emperor
@ -453,8 +456,8 @@ public final class Skills {
int points = 0;
int output = 0;
for (byte lvl = 1; lvl < 100; lvl++) {
points += Math.floor(lvl + 300.0 * Math.pow(2.0, lvl / 7.0));
output = (int) Math.floor(points / 4);
points += floor(lvl + 300.0 * Math.pow(2.0, lvl / 7.0));
output = (int) floor(points / 4);
if ((output - 1) >= exp) {
return lvl;
}
@ -467,8 +470,8 @@ public final class Skills {
int points = 0;
int output = 0;
for (byte lvl = 1; lvl < 100; lvl++) {
points += Math.floor(lvl + 300.0 * Math.pow(2.0, lvl / 7.0));
output = (int) Math.floor(points / 4);
points += floor(lvl + 300.0 * Math.pow(2.0, lvl / 7.0));
output = (int) floor(points / 4);
if ((output - 1) >= exp) {
return lvl;
}
@ -485,11 +488,11 @@ public final class Skills {
int points = 0;
int output = 0;
for (int lvl = 1; lvl <= level; lvl++) {
points += Math.floor(lvl + 300.0 * Math.pow(2.0, lvl / 7.0));
points += floor(lvl + 300.0 * Math.pow(2.0, lvl / 7.0));
if (lvl >= level) {
return output;
}
output = (int) Math.floor(points / 4);
output = (int) floor(points / 4);
}
return 0;
}
@ -523,19 +526,13 @@ public final class Skills {
if (entity instanceof NPC) {
return ((NPC) entity).getDefinition().getCombatLevel();
}
int combatLevel = 0;
int melee = staticLevels[ATTACK] + staticLevels[STRENGTH];
int range = (int) (1.5 * staticLevels[RANGE]);
int mage = (int) (1.5 * staticLevels[MAGIC]);
if (melee > range && melee > mage) {
combatLevel = melee;
} else if (range > melee && range > mage) {
combatLevel = range;
} else {
combatLevel = mage;
}
combatLevel = staticLevels[DEFENCE] + staticLevels[HITPOINTS] + (staticLevels[PRAYER] / 2) + (int) (1.3 * combatLevel);
return combatLevel / 4;
double base = 0.25 * (staticLevels[DEFENCE] + staticLevels[HITPOINTS] + floor(0.5 * staticLevels[PRAYER]));
double meleeBase = 0.325 * (staticLevels[ATTACK] + staticLevels[STRENGTH]);
double rangeBase = 0.325 * (floor(staticLevels[RANGE] / 2.0) * 1.5);
double magicBase = 0.325 * (floor(staticLevels[MAGIC] / 2.0) * 1.5);
return (int) (base + max(meleeBase, max(rangeBase, magicBase)));
}
/**

View file

@ -119,6 +119,11 @@ public final class PortalOptionPlugin extends OptionHandler {
player.getHouseManager().enter(player, buttonId == 2, true);
break;
case 3:
if(player.getIronmanManager().isIronman()){
end();
ContentAPI.sendMessage(player, "You can't do that as an ironman.");
return true;
}
ContentAPI.sendInputDialogue(player, false, "Enter friend's name:", (value) -> {
Player p = Repository.getPlayerByName((String) value);
if (p == null || !p.isActive()) {

View file

@ -105,10 +105,12 @@ public final class SlayerManager {
* @param npc The NPC. You're currently
*/
public void finalizeDeath(Player player, NPC npc) {
player.getSkills().addExperience(Skills.SLAYER,npc.getSkills().getMaximumLifepoints());
decrementAmount(1);
if (!hasTask()) {
clear();
if(hasTask()) {
player.getSkills().addExperience(Skills.SLAYER, npc.getSkills().getMaximumLifepoints());
decrementAmount(1);
}
if(!hasTask()){
flags.setTaskStreak(flags.getTaskStreak() + 1);
flags.setCompletedTasks(flags.getCompletedTasks() + 1);
if ((flags.getCompletedTasks() > 4 || flags.canEarnPoints() ) && flags.getMaster() != Master.TURAEL && flags.getPoints() < 64000) {
@ -251,7 +253,7 @@ public final class SlayerManager {
* @return {@code True} if so.
*/
public boolean hasTask() {
return getAmount() != 0;
return getAmount() > 0;
}
/**

View file

@ -3,6 +3,8 @@ package core.net.event;
import core.cache.misc.buffer.ByteBufferUtils;
import core.net.IoSession;
import core.net.IoWriteEvent;
import rs09.Server;
import rs09.ServerConstants;
import java.nio.ByteBuffer;
@ -12,11 +14,6 @@ import java.nio.ByteBuffer;
*/
public final class MSHSWriteEvent extends IoWriteEvent {
/**
* The password used to verify
*/
private static final String PASSWORD = "0x14ari0SSbh98989910";
/**
* Constructs a new {@code MSHSWriteEvent} {@code Object}
* @param session The session.
@ -28,9 +25,9 @@ public final class MSHSWriteEvent extends IoWriteEvent {
@Override
public void write(IoSession session, Object context) {
ByteBuffer buffer = ByteBuffer.allocate(2 + PASSWORD.length());
ByteBuffer buffer = ByteBuffer.allocate(2 + ServerConstants.MS_SECRET_KEY.length());
buffer.put((byte) 88);
ByteBufferUtils.putString(PASSWORD, buffer);
ByteBufferUtils.putString(ServerConstants.MS_SECRET_KEY, buffer);
session.queue((ByteBuffer) buffer.flip());
}

View file

@ -77,6 +77,7 @@ public final class PacketRepository {
OUTGOING_PACKETS.put(UpdateRandomFile.class, new UpdateRandomFile()); //
OUTGOING_PACKETS.put(InstancedLocationUpdate.class, new InstancedLocationUpdate()); //
OUTGOING_PACKETS.put(CSConfigPacket.class, new CSConfigPacket()); //
OUTGOING_PACKETS.put(Varbit.class, new Varbit());
INCOMING_PACKETS.put(22, new ClientFocusPacket());
INCOMING_PACKETS.put(93, new PingPacketHandler());
INCOMING_PACKETS.put(44, new CommandPacket());

View file

@ -0,0 +1,22 @@
package core.net.packet.context;
import core.game.node.entity.player.Player;
import core.net.packet.Context;
public class VarbitContext implements Context {
Player player;
public int varbitId;
public int value;
public VarbitContext(Player player, int varbitId, int value){
this.player = player;
this.varbitId = varbitId;
this.value = value;
}
@Override
public Player getPlayer() {
return player;
}
}

View file

@ -266,6 +266,7 @@ public final class InteractionPacket implements IncomingPacket {
player.debug("dir=" + object.getDirection());
VarbitDefinition def = VarbitDefinition.forObjectID(SceneryDefinition.forId(objectId).getVarbitID());
player.debug("Varp ID=" + def.getConfigId() + " Offset=" + def.getBitShift() + " Size=" + def.getBitSize());
player.debug("Varbit: " + def.getId());
if (option.getHandler() != null) {
player.debug("Object handler: " + option.getHandler().getClass().getSimpleName());
}

View file

@ -0,0 +1,21 @@
package core.net.packet.out;
import core.net.packet.IoBuffer;
import core.net.packet.OutgoingPacket;
import core.net.packet.context.VarbitContext;
public class Varbit implements OutgoingPacket<VarbitContext> {
@Override
public void send(VarbitContext varbitContext) {
IoBuffer buffer;
if(varbitContext.value > 255){
buffer = new IoBuffer(84);
buffer.putLEInt((128 | varbitContext.value) & 255);
} else {
buffer = new IoBuffer(37);
buffer.put((byte) 128 | varbitContext.value);
}
buffer.putLEShort(varbitContext.varbitId);
varbitContext.getPlayer().getSession().write(buffer);
}
}

View file

@ -16,10 +16,14 @@ import rs09.game.system.config.ServerConfigParser
import rs09.game.world.GameWorld
import rs09.game.world.repository.Repository
import java.io.File
import java.io.FileWriter
import java.lang.management.ManagementFactory
import java.lang.management.ThreadMXBean
import java.net.BindException
import java.util.*
import kotlin.system.exitProcess
/**
* The main class, for those that are unable to read the class' name.
* @author Emperor
@ -108,6 +112,13 @@ object Server {
while(running){
if(System.currentTimeMillis() - lastHeartbeat > 1800 && running){
SystemLogger.logErr("Triggering reboot due to heartbeat timeout")
SystemLogger.logErr("Creating thread dump...")
val dump = threadDump(true, true)
FileWriter("latestdump.txt").use {
it.write(dump)
it.flush()
it.close()
}
exitProcess(0)
}
delay(625)
@ -140,6 +151,15 @@ object Server {
return startTime
}
private fun threadDump(lockedMonitors: Boolean, lockedSynchronizers: Boolean): String? {
val threadDump = StringBuffer(System.lineSeparator())
val threadMXBean: ThreadMXBean = ManagementFactory.getThreadMXBean()
for (threadInfo in threadMXBean.dumpAllThreads(lockedMonitors, lockedSynchronizers)) {
threadDump.append(threadInfo.toString())
}
return threadDump.toString()
}
/**
* Sets the bastartTime.ZZ
* @param startTime the startTime to set.

View file

@ -106,6 +106,9 @@ class ServerConstants {
@JvmField
var GE_AUTOSTOCK_ENABLED = false
@JvmField
var MS_SECRET_KEY = ""
//location names for the ::to command.
val TELEPORT_DESTINATIONS = arrayOf(
arrayOf(Location.create(2974, 4383, 2), "corp", "corporal", "corporeal"),
@ -218,6 +221,9 @@ class ServerConstants {
if(data.containsKey("daily_restart")){
DAILY_RESTART = data["daily_restart"] as Boolean
}
if(data.containsKey("ms_secret_key")) MS_SECRET_KEY = data["ms_secret_key"].toString()
else MS_SECRET_KEY = "2009scape_development"
}
}
}

View file

@ -100,14 +100,17 @@ object ServerStore {
}
}
@JvmStatic
fun JSONObject.getString(key: String): String {
return this[key] as? String ?: "nothing"
}
@JvmStatic
fun JSONObject.getLong(key: String): Long {
return this[key] as? Long ?: 0L
}
@JvmStatic
fun JSONObject.getBoolean(key: String): Boolean {
return this[key] as? Boolean ?: false
}

View file

@ -2,6 +2,8 @@ package rs09.game
import core.cache.def.impl.VarbitDefinition
import core.game.node.entity.player.Player
import core.net.packet.PacketRepository
import core.net.packet.context.VarbitContext
import org.json.simple.JSONArray
import org.json.simple.JSONObject
import rs09.game.node.entity.skill.farming.FarmingPatch
@ -32,6 +34,10 @@ class VarpManager(val player: Player) {
get(def.configId).setVarbit(def.bitShift,value).send(player)
}
fun setVarbit(varbitIndex: Int, value: Int){
PacketRepository.send(core.net.packet.out.Varbit::class.java, VarbitContext(player, varbitIndex, value))
}
fun flagSave(index: Int){
get(index).save = true
}

View file

@ -44,10 +44,10 @@ class CerterEventInterface : InterfaceListener() {
player.packetDispatch.sendString(items[correct],CERTER_INTERFACE,optionFromIndex(correctIndex))
val tempOptions = falseOptions
val tempOptions = falseOptions.toMutableList()
val false1 = tempOptions.random()
tempOptions.remove(false1)
var false2 = tempOptions.random()
while(false1 == false2) false2 = tempOptions.random()
player.packetDispatch.sendString(false1,CERTER_INTERFACE,optionFromIndex(indexes[0]))
player.packetDispatch.sendString(false2,CERTER_INTERFACE,optionFromIndex(indexes[1]))

View file

@ -75,10 +75,7 @@ class WorkForInteractionListener : InteractionListener() {
val type = typeMap[node.id] ?: return@on false
jobId = if(type == 0) {
var job = gatheringMap[node.id]?.random()
while(!checkRequirement(player,job)){
job = gatheringMap[node.id]?.random()
}
var job = gatheringMap[node.id]?.filter { checkRequirement(player, it) }?.random()
amount = job?.getAmount() ?: 0
job?.ordinal ?: 0
} else {
@ -111,8 +108,7 @@ class WorkForInteractionListener : InteractionListener() {
fun checkRequirement(player: Player, jobs: GatheringJobs?): Boolean{
jobs ?: return true
val requirement: Pair<Int,Int> = Pair(jobs.lvlReq,jobs.skill)
if(player.skills.getLevel(requirement.second) < requirement.first){
if(player.skills.getLevel(jobs.skill) < jobs.lvlReq){
return false
}
return true

View file

@ -18,9 +18,6 @@ class DSEquipListeners : InteractionListener() {
Items.SARADOMIN_PLATEBODY_2661,
Items.GUTHIX_PLATEBODY_2669,
Items.GREEN_DHIDE_BODY_1135,
Items.BLUE_DHIDE_BODY_2499,
Items.RED_DHIDE_BODY_2501,
Items.BLACK_DHIDE_BODY_2503,
Items.DHIDE_BODYG_7370,
Items.DHIDE_BODY_G_7374,
Items.DHIDE_BODY_T_7372,

View file

@ -216,14 +216,12 @@ class BoneGrinderListener : InteractionListener() {
val bone = Bones.values()[player.getAttribute(LOADED_BONE_KEY,-1)]
player.lock()
Pulser.submit(object : Pulse(){
var stage = 0
override fun pulse(): Boolean {
when(stage++){
0 -> {
player.lock()
player.animator.animate(SCOOP_ANIM)
}
0 -> player.animator.animate(SCOOP_ANIM)
SCOOP_ANIM.duration -> {
player.unlock()
if(player.inventory.remove(Item(Items.EMPTY_POT_1931))){

View file

@ -41,7 +41,7 @@ class EnchantedValleyListeners : InteractionListener() {
fun getSpirit(player: Player): NPC {
val level = player.properties.currentCombatLevel
var index = Math.ceil(level / 20.0).toInt()
if(index > TREE_SPIRIT_IDS.size) index = TREE_SPIRIT_IDS.size
if(index >= TREE_SPIRIT_IDS.size) index = TREE_SPIRIT_IDS.size - 1
return NPC(TREE_SPIRIT_IDS[index])
}
}

View file

@ -53,8 +53,9 @@ fun getNewTTL(): Int{
}
fun getNewLoc(): Location {
var loc = locations.random()
while(usedLocations.contains(loc)) loc = locations.random()
val possibleLoc = locations.toTypedArray().toMutableList()
possibleLoc.removeAll(usedLocations)
val loc = possibleLoc.random()
usedLocations.add(loc)
return loc
}

View file

@ -18,6 +18,9 @@ import core.game.world.map.Location
import core.game.world.map.RegionManager
import core.game.world.update.flag.context.Animation
import core.game.world.update.flag.context.Graphics
import core.net.packet.PacketRepository
import core.net.packet.context.VarbitContext
import core.net.packet.out.Varbit
import core.plugin.Initializable
import core.plugin.Plugin
import rs09.game.system.command.CommandPlugin
@ -221,14 +224,14 @@ class VisualCommand : CommandPlugin() {
val cfg_index = (args.getOrNull(2)?.toString()?.toInt() ?: -1)
if(cfg_index == -1){
ContentAPI.submitWorldPulse(object : Pulse(3, player){
var pos = 0
var pos = 32
var shift = 0
override fun pulse(): Boolean {
for(i in 0..1999){
player?.configManager?.forceSet(i, pos shl shift, false)
}
player?.sendMessage("$pos << $shift")
if(pos++ >= 32){
player?.sendMessage("$pos shl $shift")
if(pos++ >= 63){
shift += 4
pos = 0
}
@ -246,6 +249,27 @@ class VisualCommand : CommandPlugin() {
})
}
}
"setbit" -> {
if (args!!.size < 2) {
player!!.debug("syntax error: bit value")
return true
}
var bit = toInteger(args[0]!!)
var value = toInteger(args[1]!!)
var val2 = toInteger(args[2]!!)
player!!.debug("$value $val2")
PacketRepository.send(Varbit::class.java, VarbitContext(player, value, val2))
return true
}
"setbits" -> {
args ?: return false
val start = toInteger(args[1]!!)
val end = toInteger(args[2]!!)
val value = toInteger(args[3]!!)
for(i in start until end){
player?.varpManager?.setVarbit(i, value)
}
}
"loop_anim_on_i" -> {
var anim = toInteger(args!![1]!!)
ContentAPI.submitWorldPulse(object : Pulse(3){

View file

@ -32,9 +32,10 @@ class MajorUpdateWorker {
var started = false
val sequence = UpdateSequence()
val sdf = SimpleDateFormat("HHmmss")
fun start() = GlobalScope.launch {
val worker = Thread {
Thread.currentThread().name = "Major Update Worker"
started = true
delay(600L)
Thread.sleep(600L)
while(true){
val start = System.currentTimeMillis()
val rmlist = ArrayList<Pulse>()
@ -94,7 +95,12 @@ class MajorUpdateWorker {
}
val end = System.currentTimeMillis()
delay(max(600 - (end - start), 0))
Thread.sleep(max(600 - (end - start), 0))
}
}
fun start() {
if(!started){
worker.start()
}
}
}

View file

@ -43,7 +43,8 @@
"database_username": "root",
"database_password": "",
"database_address": "127.0.0.1",
"database_port": "3306"
"database_port": "3306",
"ms_secret_key": "2009scape_development"
},
"PluginToggles": {