RS2 Pauper | A 2012 Nostalgic Experience

I am no longer able to host Pauper, so I am requesting this get moved into the Projects category.

Scratch that, I spoke too soon. We are online again teehee

There's not been many major updates worth noting here, just a lot of minor bug patches since the initial re-launch, mostly regarding the starting quest and player saves (Player Owned Houses are a pain in my ass).
 
Last edited:
Few updates:
-Scaling price changes across all items in the server that exceed 500 gold (this was added before the re-launch)
---Scales from a maximum of 30% to a minimum of 1% decrease in all prices depending on how much the item originally costed
---The higher the original price is, the less of a price decrease it gets
---This will help expensive items feel more aligned with their trade values
-When mining, will now select the best available pickaxe from any source on-hand rather than doing if/else checks in order
-Players can no longer "mine" shooting stars without required level (used to give exp but no stardust and a "not high enough level" message)
-Hunter messages when setting up traps now able to be filtered
-Smelting patched, no idea how it broke I haven't messed with it in ages. Now loops through required ore rather than two if statements
-Added a few kingly impling spawns in deep wilderness
-Zombie implings spawned in both wildernesss graveyards
-Dragon implings spawned in red dragon isle and near blue wilderness dragons
-Spotted cape now has a slightly smaller bonus than spottier cape (used to be the same)
-Completely accurate burn levels added to cooking, you may follow these Wiki articles for the lists:
---https://runescape.fandom.com/wiki/Cooking/Burn_level
---https://oldschool.runescape.wiki/w/Cooking/Burn_level
---Karambwan now gives a proper amount of experience following it's level 99 burn rate
---Cooking cape will still prevent the players from ever burning food
---The burn levels for cooking gauntlets are not accurate, but cooking gauntlets do make it 15% easier to successfully cook which may loosely translate

No big updates here, but I have learned you little nerds love to look at clean code so here's how I handle selecting the best available pickaxe from any on-hand source:
Java:
	public static MiningConfig.PickaxeDefinition getPickaxeDefinition(Player player) {
		List<MiningConfig.PickaxeDefinition> pickaxes = new ArrayList<>(MiningHandler.getAllPickaxeDefinitions());

		pickaxes.sort((a, b) -> Integer.compare(b.levelRequired(), a.levelRequired()));

		for (MiningConfig.PickaxeDefinition def : pickaxes) {
			if (!meetsLevelRequirement(player, def)) {
				continue;
			}

			if (player.getEquipment().getWeaponId() == def.pickAxeId() ||
					player.getInventory().containsItem(def.pickAxeId(), 1) ||
					player.getToolbelt().contains(def.pickAxeId())) {
				return def;
			}
		}

		return null;
	}
 
Few updates:
-Scaling price changes across all items in the server that exceed 500 gold (this was added before the re-launch)
---Scales from a maximum of 30% to a minimum of 1% decrease in all prices depending on how much the item originally costed
---The higher the original price is, the less of a price decrease it gets
---This will help expensive items feel more aligned with their trade values
-When mining, will now select the best available pickaxe from any source on-hand rather than doing if/else checks in order
-Players can no longer "mine" shooting stars without required level (used to give exp but no stardust and a "not high enough level" message)
-Hunter messages when setting up traps now able to be filtered
-Smelting patched, no idea how it broke I haven't messed with it in ages. Now loops through required ore rather than two if statements
-Added a few kingly impling spawns in deep wilderness
-Zombie implings spawned in both wildernesss graveyards
-Dragon implings spawned in red dragon isle and near blue wilderness dragons
-Spotted cape now has a slightly smaller bonus than spottier cape (used to be the same)
-Completely accurate burn levels added to cooking, you may follow these Wiki articles for the lists:
---https://runescape.fandom.com/wiki/Cooking/Burn_level
---https://oldschool.runescape.wiki/w/Cooking/Burn_level
---Karambwan now gives a proper amount of experience following it's level 99 burn rate
---Cooking cape will still prevent the players from ever burning food
---The burn levels for cooking gauntlets are not accurate, but cooking gauntlets do make it 15% easier to successfully cook which may loosely translate

No big updates here, but I have learned you little nerds love to look at clean code so here's how I handle selecting the best available pickaxe from any on-hand source:
Java:
	public static MiningConfig.PickaxeDefinition getPickaxeDefinition(Player player) {
		List<MiningConfig.PickaxeDefinition> pickaxes = new ArrayList<>(MiningHandler.getAllPickaxeDefinitions());

		pickaxes.sort((a, b) -> Integer.compare(b.levelRequired(), a.levelRequired()));

		for (MiningConfig.PickaxeDefinition def : pickaxes) {
			if (!meetsLevelRequirement(player, def)) {
				continue;
			}

			if (player.getEquipment().getWeaponId() == def.pickAxeId() ||
					player.getInventory().containsItem(def.pickAxeId(), 1) ||
					player.getToolbelt().contains(def.pickAxeId())) {
				return def;
			}
		}

		return null;
	}
public static MiningConfig.PickaxeDefinition getPickaxeDefinition(Player player) {
return MiningHandler.getAllPickaxeDefinitions().stream()
.sorted(Comparator.comparingInt(MiningConfig.PickaxeDefinition::levelRequired).reversed())
.filter(def -> meetsLevelRequirement(player, def))
.filter(def -> player.getEquipment().getWeaponId() == def.pickAxeId()
|| player.getInventory().containsItem(def.pickAxeId(), 1)
|| player.getToolbelt().contains(def.pickAxeId()))
.findFirst()
.orElse(null);
}
okay bye
 
public static MiningConfig.PickaxeDefinition getPickaxeDefinition(Player player) {
return MiningHandler.getAllPickaxeDefinitions().stream()
.sorted(Comparator.comparingInt(MiningConfig.PickaxeDefinition::levelRequired).reversed())
.filter(def -> meetsLevelRequirement(player, def))
.filter(def -> player.getEquipment().getWeaponId() == def.pickAxeId()
|| player.getInventory().containsItem(def.pickAxeId(), 1)
|| player.getToolbelt().contains(def.pickAxeId()))
.findFirst()
.orElse(null);
}
okay bye
teehee, I thought about taking a functional approach to it as well but decided I preferred the look of the imperative style.
 
Accuracy on melee at lower levels is very inaccurate.
I agree, but I've hardly worked on the combat that came existing within the original source, it scares me. It's a class with like 3,500 6,700 lines of code all inside of a total of like 3 methods... Giant, massive if/else blocks and switch statements that continue for 100 lines of code, etc. I'm shocked combat functions at all!
 
Last edited:
I agree, but I've hardly worked on the combat that came existing within the original source, it scares me. It's a class with like 3,500 6,700 lines of code all inside of a total of like 3 methods... Giant, massive if/else blocks and switch statements that continue for 100 lines of code, etc. I'm shocked combat functions at all!
Should've chosen a different source then. But it's something you definitely will want to redo eventually if you continue to stick with this source.

First impressions are everything and to be honest, that was one of the first things I noticed and I haven't played since.

Maybe others aren't such harsh critics as opposed to me.

Might give it another shot sometime soon.

Best of luck.
 
Should've chosen a different source then. But it's something you definitely will want to redo eventually if you continue to stick with this source.

First impressions are everything and to be honest, that was one of the first things I noticed and I haven't played since.

Maybe others aren't such harsh critics as opposed to me.

Might give it another shot sometime soon.

Best of luck.
I'm not sure if I should be an asshole back or not, but you've clearly not read the thread. Pauper is not a server trying to compete in the toplists. The RSPS scene is not exactly difficult to make a "#1 RSPS" lmao
 
I'm not sure if I should be an asshole back or not, but you've clearly not read the thread. Pauper is not a server trying to compete in the toplists. The RSPS scene is not exactly difficult to make a "#1 RSPS" lmao
I think he just likes remake servers that are well polished, theres not really any high revision servers that are near 1:1, people say darkan/2011scape/2009scape but theyre nothing compared to what they should be
 
  • Like
Reactions: VenderStack
I want to be clear: Pauper is very much a private server. We are not attempting to recreate 2012 RuneScape 1:1 - we are very much trying to emulate a nostalgic experience to that instead. We have come into some confusion about this especially considering the detailed nature of our content. We are more-so trying to capture a "throwback" experience of the 2012 era of RuneScape, not an extremely strict model.
 
Few updates:
-Strykewyrm drop tables patched, few drops updated
-Fishing patched to use only 1 bait at a time
-Nerfed kingly impling rewards
-Increased burn chance on karambwan

Not many game updates here as I've been working on the Groovy parser more now that I can focus on bigger aspects of the server again. The Groovy parser is now completely type-safe and is making tremendous strides in becoming a real, true, proprietary Groovy parser framework; as it stands currently it's an incredibly fancy configuration loader. It is currently capable of abstract parsing infrastructure like allowing individual skills to build their own configuration formatting, file watching, two-level caching, type-safe configuration management, is capable of multiple parsing strategies, and more. At first, I planned on just making this a hot-reload configuration system using a Groovy parser, but the system is now getting close enough in design to an actual Groovy parser itself, so I intend to finish it. But currently, it has an extremely flexible core, is completely type-safe, and is quite extensible.

My next steps in turning this to a real Groovy parser is to standardize the interface, add automatic binding, use annotation-based mapping, add dependency injection, and utilize AST processing rather than executing basic type conversion.

Much more flexibility when it comes to evaluating maps:
Java:
    protected <K, V> Map<K, V> evaluateTypedMap(File file, Function<Object, K> keyConverter,
                                                Function<Object, V> valueConverter) throws IOException {
        Object result = new GroovyShell().evaluate(file);
        validateIsMap(result, file.getName());

        Map<K, V> typedMap = new HashMap<>();
        for (Map.Entry<?, ?> entry : ((Map<?, ?>) result).entrySet()) {
            try {
                K key = convertWithHandler(() -> keyConverter.apply(entry.getKey()),
                        "Failed to convert key: " + entry.getKey());
                V value = convertWithHandler(() -> valueConverter.apply(entry.getValue()),
                        "Failed to convert value: " + entry.getValue());
                typedMap.put(key, value);
            } catch (Exception e) {
                throw errorHandler.error("Failed to convert map entry: " + entry, e);
            }
        }
        return typedMap;
    }

    private <R> R convertWithHandler(ThrowingSupplier<R> supplier, String errorMessage) throws IOException {
        try {
            return supplier.get();
        } catch (Throwable e) {
            throw errorHandler.error(errorMessage, e);
        }
    }

I have also standardized error handling in the parser as well, reducing code duplication and improving type validation :)
 
I swear Pauper is still being developed, I swear it. I am the sole developer of the project so it just proceeds very slowly at times, especially right now since I've gotten addicted to a couple of video games lately and those are the only things I wanna spend my free time doing outside of IRL relationships 😓 But I'm still working on it!

As stated before, Player files have completely been detached from Java's default serialization which is a big step in moving the source away from OOP in general. My end-goal with Pauper is to treat Entities in general as a series of components, regarding them as a set of data rather than Objects themselves. This will allow entities to have a "has-a" relationship with other classes rather than the "is-a" relationship that is necessary with inheritance.

I mentioned before that I am now using binary to save player classes rather than Java's default serialization; I am utilizing Reflection to achieve this:
Java:
        try {
            stream.writeByte(CURRENT_VERSION);

            Field[] fields = getSerializableFields(player.getClass());
            stream.writeShort(fields.length);

            for (Field field : fields) {
                try {
                    String name = field.getName();
                    Object value;

                    field.setAccessible(true);
                    value = field.get(player);

                    stream.writeString(name);
                    serializeField(stream, value);
                } catch (Exception e) {
                    Logger.log("PlayerBinarySerializer",
                            "Error serializing field " + field.getName() + ": " + e.getMessage());
                    stream.writeByte(0);
                }
            }

            byte[] data = stream.getBuffer();
            return data.length > COMPRESSION_THRESHOLD ? compressInThread(data) : data;
        }

    private static Field[] getSerializableFields(Class<?> clazz) {
        return FIELD_CACHE.computeIfAbsent(clazz, c ->
                Arrays.stream(c.getDeclaredFields())
                        .filter(f -> !Modifier.isStatic(f.getModifiers()))
                        .filter(f -> !Modifier.isTransient(f.getModifiers()))
                        .filter(f -> !Modifier.isFinal(f.getModifiers()))
                        .toArray(Field[]::new)
        );
    }

While not downright completely removed from OOP -- Players are still very much treated as Objects -- this does allow me to break inheritance and default serialization in general without harming any Player-related data. Saving Player files this way lets me decouple their data away from the rest of the server, only having relays of information being shared from Player files and server.

This will allow me to create an ECS (Entity-Component-System) which will convert all entities (Players, NPCs, Objects) into a modular set of data overtime without causing any corruption as this system expands. This style of coding is called composition, which is superior to OOP in most aspects almost all of the time. Even in simple hierarchies where it only stems 2-3 levels deep, composition is almost always more friendly to CPU. This practice will allow me to create separate systems that can encompass any and all aspects of the server to be used in modularity. This will mean, for example, that I could apply health and health-bars to things like world objects; even tin ore rocks. They could walk around and have behavioral models since they are no longer tied to the "WorldObject" class or whatever.

Sure, this is certainly possible using OOP - but it would be necessary to duplicate code in order to achieve that, rather than using the already existing code that's been completely restricted through inheritance. It is also just bad practice to treat complicated things as Objects, such as Player classes, as that provides heaps of tax to hardware systems. When correctly utilizing composition, the server will only read what relevant data it should, whereas OOP will read the entire dataset every single time.

In game design, most developers think of structuring in terms like this:
11M9KIu.png


This is essentially how Matrix is setup, with even one additional inheritance step before reaching the Player/NPC class. This makes the source wildly restrictive, fragile, and taxing on systems. If I'd like to damage a monster, the server must read the full Entity class which determines whether or not it's a real entity or a "world object." If it's determined to be a real entity - it'll ignore the WorldObject class. But now it must fully read both NPC and Player classes, all of their data from top to bottom. Once it has every single piece of data in the Actor classes, it will then read and execute relevant methods.

Now what if I wanted to create a combat dummy WorldObject that has HP and will despawn once that HP is depleted? I'll need to duplicate code from the other Entity classes in order to achieve that instead of relying on code that already exists.

Using composition will entirely wipe out these steps. If I'd like to damage an entity using composition, it only reads the relevant sets of data. Something like this:
F70hffa.png


Which, instead of limiting data to hierarchies, it will simply read relevant sets of data and apply logic correspondingly. This will apply to every single entity in the server: Players, NPCs, Objects, even world tiles and things alike. Fully converting the source over to composition would do heaps in creating a very stable RSPS source, or even engine if you'd like to get so bold. OOP is incredibly inefficient for urgent tasks, like most things involving multiplayer games.

So, long-run goal will be to slowly convert Pauper from OOP to composition almost in its entirety. Again though, this is the LONG-term goal. There is already so much content within the Matrix source, this is not going to be a quick task and will be slowly developed overtime.
 
Last edited:

Users who are viewing this thread (total: 1, members: 0, guests: 1)