Coder Social home page Coder Social logo

melia's People

Contributors

cosrnos avatar exectails avatar felipecastagnarodecarvalho avatar gastonb avatar hirasu avatar huenato avatar kenedos avatar kythelxi avatar ovflowd avatar phstudy avatar raiken-mf avatar renatohgrimes avatar salmantkhan avatar sanasol avatar tachiorz avatar terotrous avatar vegax87 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

melia's Issues

Channel crashes when DB have item with unknown id.

[Error] - Error while handling packet '0BB9', CZ_CONNECT.
[Exception] - System.NullReferenceException: No item data found for '628130'.
   at Melia.Channel.World.Item.LoadData() in x:\Research\TOS\melia\src\ChannelServer\World\Item.cs:line 83
   at Melia.Channel.World.Item..ctor(Int32 itemId, Int32 amount) in x:\Research\TOS\melia\src\ChannelServer\World\Item.cs:line 66
   at Melia.Channel.Database.ChannelDb.LoadCharacterItems(Character character) in x:\Research\TOS\melia\src\ChannelServer\Database\ChannelDb.cs:line 192
   at Melia.Channel.Database.ChannelDb.GetCharacter(Int64 accountId, Int64 characterId) in x:\Research\TOS\melia\src\ChannelServer\Database\ChannelDb.cs:line 122
   at Melia.Channel.Network.ChannelPacketHandler.CZ_CONNECT(ChannelConnection conn, Packet packet) in x:\Research\TOS\melia\src\ChannelServer\Network\ChannelPacketHandler.cs:line 62
   at Melia.Shared.Network.PacketHandler`1.Handle(TConnection conn, Packet packet) in x:\Research\TOS\melia\src\Shared\Network\PacketHandler.cs:line 69
   at Melia.Channel.Network.ChannelConnection.HandlePacket(Packet packet) in x:\Research\TOS\melia\src\ChannelServer\Network\ChannelConnection.cs:line 40
   at Melia.Shared.Network.Connection.OnReceive(IAsyncResult result) in x:\Research\TOS\melia\src\Shared\Network\Connection.cs:line 198

Clean up binary templates

The binary templates are a little chaotic atm, and it's only gonna get worse. We'll have to clean up in there some time.

  • Move every group of related structures from common.bt into a dedicated file in a sub folder "inc".
  • Introduce a general naming scheme for variables and structs.

This will make maintaining them and keeping track of the history much easier.

Dictionary ids: explicit vs implicit

Here's the current function for Equipment Merchant Dunkel in Klaipeda:

-- Equipment Merchant Dunkel
function npc_equipmentmerchantdunkel()
    local selection,i = nselect("KLAPEDA_Akalabeth_basic28", "weapon:@dicID_^*$ETC_20150317_004443$*^", "armor:@dicID_^*$ETC_20150317_004444$*^", "!@#$Auto_JongLyo#@!")

    if selection == "weapon" then
        openshop("Klapeda_Weapon")
    elseif selection == "armor" then
        openshop("Klapeda_Armor")
    end
end

Generally I like what I'm seeing, nselect, the if/else, all looks fine. However, there's the dicIDs. For the client to recognize the id and convert it to a string from the localization dictionary this code is necessary: @dicID_^*$<id>$*^

This is the case for all dictionary ids, but for NPC names we hide that code, the key is wrapped in it behind the scenes.

addnpc(20111, "ETC_20150317_009196", "c_Klaipe", 394, -1, -475, 45, "npc_equipmentmerchantdunkel")

ETC_20150317_009196 becomes @dicID_^*$ETC_20150317_009196$*^ when sent to the client.

This is a lot of boilerplate that's hard to remember and basically not necessary. We can wrap the options of a select as well. The question is, should we actually hide that completely?

The dictionary ids aren't the only codes, as you can see above. !@#$Auto_JongLyo#@! is the code for a localized End or Cancel. (Since it's not a dictionary id I assume Auto does more than that.)

Hiding the wrap could result in confusing situations, unless you know every function and its implementation. You can't really be sure which functions convert the strings and which don't, does it convert every id in the string or only the entire string if it looks like an id, is there a shortcut for the other codes as well or not?

An alternative could be to add helper functions in a global script. For example:

local selection = select("Foobar?", dict("ETC_20150317_004443"), dict("ETC_20150317_004444"), endbtn())

This way it's much easier to remember, the implementation and what happens can be looked up without checking the core, and there's no ambiguity. One downside is that you have to concatenate strings if you need more than just the id.

local selection = nselect("Foobar?", "weapon:" .. dict("ETC_20150317_004443"), "armor:" .. dict("ETC_20150317_004444"), endbtn())

Or, there needs to be another help function.

local selection = nselect("Foobar?", ndict("weapon", "ETC_20150317_004443"), ndict("armor", "ETC_20150317_004444"), endbtn())

The advantages of this are explicitness and that it's less likely to make mistakes with all the codes. I can't really think of any actual disadvantages right now.

Combat (Range)

Getting combat to work will basically be the most important thing for TOS, as it's the defining feature of the game. Unfortunately there is one big problem with combat in Melia right now, the client attacks whatever monster is closest on the screen. It doesn't limit the range and it only ever attacks one enemy.

After the iCBT2 I've spent quite a few hours trying to figure this out, but I didn't get very far. The client obviously decides what's in range to be hit, and "hacking" videos during the betas confirm that the server didn't really check what the client sent, but there must be something that limits the client during normal operation.

I would expect there to be some value in some packet, or maybe a property on the monsters, the characters, or the items, but I just don't know what the deciding factor is yet, as nothing jumped out at me during my research.

Multi-version support

Multi-version support means being able to handle requests from different client versions, ideally with a simple switch in either a conf file or the source, to set which client version the server should work with. We're currently limited to supporting a single version, which is the latest iTOS. Supporting multiple versions of iTOS (e.g. iCBT2) or even multiple regions (e.g. kTOS) is entirely possible in theory, but there are various factors that complicate the implementation. This issue is about whether we need/want multi-version support, and if so, how we would go about implementing it.

Coming from *Athena, some of you might be familiar with how they do it, there's a global #define that specifies which packet version to use, the version being based on the date the changes were added to the client exe, and #ifs in the rest of the code for the version dependent sections. Additionally there's a db file for op changes.

That might not be the cleanest solution, but honestly, I like it. It's straight forward, you are able to add any version dependent code, and there's no performance hit, because only the code for your version makes it into the assembly.

However, even if we wanted to, we can't do this in C#. Since normal #defines aren't global and are limited to being simple booleans, they aren't well suited for this job. Every single packet or enum change would be a new #define, that's not viable in my opinion.

One alternative would be to specify the version you want to use in a conf file, and simply checking that in the packet handling and building. This works fine for structural changes in packets, but it doesn't solve the enum/const problem. If IMC adds new values in the middle of an enum, like ops and properties, we have to make that change as well.

We could turn the enums into readonly, "constant" fields, that are set during run-time, based on conditions or file input, and that might be viable for ops and maybe even object properties, but it might be a little irritating if we have to do that for every single enum that might change. Additionally those fields aren't actually constants anymore afterwards, which limits their use.

For example, in attributes you can only use compile-time constants, so you'd have to built a look-up system. The enums would be empty shells, with the methods that use them looking up the actual values from dbs. Downside: constant lookup of ops, properties, and everything else.

We tried to support multiple versions on Aura a while back, and it worked pretty well, but we stopped when we realized we'd have to handle more enums than only ops.

If you have an opinion on this topic or an idea on how to implement it, feel free to comment here.

NPC select improvement

select currently takes a variable amount of parameters, the first one is the message to display and every following one is a selectable option. The return value is the selected index.

loca selection = select("How are you?", "Good", "Bad")
if selection == 1 then
  -- Good
else
  -- Bad
end

This works well enough if there's only a few options, but with more, or if they change and the indices have to be updated, it's error prone and significantly harder to manage. Imagine you had 15 options and suddenly you wanted to insert one right at the beginning.

I'm proposing the following solution: a select function that accepts tables with key/value objects, the returned value being the selected key. It requires some boilerplate, but it does solve the issue. One downside is that it requires special code in the script manager, to turn the selected index into the string key.

loca selection = tselect("How are you?", { "good"="Good", "bad"="Bad" })
if selection == "good" then
  -- Good
else
  -- Bad
end

Remote IES modification system

Summary
Changes to the client may be made dynamically by remotely modifying IES data through specified packets. It is extremely likely that server operators running Melia will want to customize the experience for players to their liking, and since distributing modified clients is not legal, the ability to use these packets will be highly desired.

Problem
In order to send a modification, intimate knowledge of how an IES is structured must be known to the application. Further, information required may change with each client release, and an incorrect structure can crash the client. We need a system that allows for making changes easily and safely. Such a system should make it easy on the server operators to add/remove new changes without needing to spend so much effort to find required information.

Data Requirements
The following data is required in order to make an IES change:

  • Filename of the IES.
  • ClassID of the row to be modified.
  • Column name identifying the modification.
  • Previous value to be overwritten. (Necessary when revoking a revision).

Items to Consider

  • Modifications should most likely exist as a table in the database.
  • Should modifications be applicable to single characters/accounts? (More complex, but maybe there is a reason someone might want this?)
  • A large number of modifications should not be sent as one packet. They need to be split up.
  • Should we make a feature to allow GMs to make modifications through CZ_CHAT? (Validation needs to be robust since a bad packet can crash clients.)
  • Modifications are not permanent by default and reset only once the client is closed. (It seems like there is code to flush changes to the files themselves).

Manage login history

Perhaps we should create a table that keeps a record of login history for each user. Particularly, this is useful for the following reasons.

  • Currently, nothing keeps track of which users are signed into a server or offline. This would be necessary to prevent duplicate clients from logging in with the same account.
  • With the CZ_CONNECT packet, we can store the hardware address and IP address of a user. That can be used for ban implementations in the future.

Downsides:

  • Any table that tracks history can become large very quickly. Server operators might need to create an event to flush out old data.

C# 6/7

Since I'm always trying to make my applications to be as simple to set up and use as possible, I put off using C# 6 features, because up until Debian 8 (Jessie), my distribution of choice, the default Mono version was 3.X, which didn't support C# 6. This would've made it "harder" for users to compile Melia on Debian, requiring them to get a newer version from somewhere else.

Usually in such cases people would tell me to make that the user's problem anyway, but now that Debian 9 is out, which uses Mono 4, I can't think of much of a reason to wait anymore. Unless someone can think of anything, I think it's safe to start to enjoy C# 6 in Melia.

Automatic saving&loading of object properties.

TLDR:

  • save and load various object properties uniformly;
  • automatically sync object properties to client.

Various objects in TOS have single parent with global unique ID and support for special properties.
Those properties have id's and names and used to synchronize server side state of objects to client (I guess automatically). Examples in game protocol:
https://github.com/aura-project/melia/blob/bd83f147e4f50908dbdc306afdeb99eb90153854/src/LoginServer/Network/Helpers/AccountHelper.cs#L22
https://github.com/aura-project/melia/blob/9bdf2d8e579e0b9867f34358665c060f60824bb7/src/ChannelServer/Network/ChannelPacketSender.cs#L425
https://github.com/aura-project/melia/blob/9bdf2d8e579e0b9867f34358665c060f60824bb7/src/ChannelServer/Network/ChannelPacketSender.cs#L746

I suggest to do the same for melia.
Mark class properties with special attribute and they'll automatically sync to client on next ZC_OBJECT_PROPERTY (or any other packet that sends them).
example:

/// <summary>
/// Health points.
/// </summary>
[PropertyAttribute(ObjectProperty.PC.HP)]
public int Hp { get; set; }

/// <summary>
/// Maximum health points.
/// </summary>
[PropertyAttribute(ObjectProperty.PC.MHP)]
public int MaxHp { get; set; }

Also we'll be able to save and load them uniformly using JSON (or protobuf?) This will save us the need to change DB structure often and writing tons of boilerplate code.

Proof of concept code https://github.com/Tachiorz/melia/tree/properties_delegates_test It looks nasty weird naming and scary code, so I'm going to refactor it.

Equipment prices

While most normal items have fixed (sell) prices, equipment prices are calculated somehow. The client displays those calculated prices in the NPC shops, while the server uses the db value (0). We have to figure out how those prices are calculated, to make shops work properly.

Add index to Item

When I initially coded the inventory, I thought it would be nice to keep the inventory index out of the Item class, so it doesn't has to know anything about that stuff. While this still works, maybe it would be easier to just set the index on the item, so we don't have to jump through hoops to get items with their indices, like creating indexed dictionaries or tuples. Right now you can't get an item with its index in one object, I think that's not ideal.

events config idea

maybe there will be in config events stuff look like

[EVENTS]
//this will apply holiday decorations
xmass = 1
halloween = 1

and such on for holiday
0 = off 1= on

Localization

While we'll probably never need to localize any official strings, as all dialogues and notices can be found in the client's localization dictionary, there will always be situations where we send custom strings to the client, be it for a custom NPC, a custom notice, or GM command results. Since we can't utilize the client's localization in this case we would need our own way to localize them, provided that we'd want that.

Does Melia need a localization system for its non-official strings, or will the few custom messages and NPCs we will have in master, that admins didn't code in their own language to begin with, be fine in English?

In Aura we're using a gettext-like system that I like, but unlike Melia Aura actually needs to localize every string it sends to the clients itself.

It's better to decide this sooner than later, as every custom string added to the source or custom NPCs means one more string we have to update later on if we decide to add localization.

Characters HP, SP, Stamina

Characters hp, sp and stamina values is pretty major functionality that we lack off. ATM it's not dynamic and can be changed only negatively (ZC_HIT_INFO & ZC_SKILL_HIT_INFO).

Characters max HP and SP values depends on:

  • Any stats changes
    • Buffs (CON, MHP, MSP)
    • Items (Items on CON, SPR, HP, SP)
    • Stats (CON, SPR)
  • Level UP (Level value)

Character current HP and SP values can be changed by:

  • Recovery (+HP, +SP | Firewood to faster recovery)
  • Damage dealt by enemy (- HP)
  • Heal and Revive (+ HP, Resurrect)
  • Skill cast (- SP)
  • Debuff on character (Cancellation of HP restoring, ect)
  • Consumable items
  • Items stats (HP or SP recovery)

Characters zero HP and SP values trigger events like:

  • Death (zero HP)
  • Inability to use spells (zero SP)
  • Inability to run (zero Stamina)

Right now I'm working on this things. Here i will check what am I done so far.
Feel free to join me or suggest what I have forgot to mention.

Skill management

Learning, storing, and managing skills. Some work on this has already been done by contributors, but here's the backlog issue for it. Finishing it will require some more research into how exactly skills and jobs work.

Script variable system

We need to be able to carry information across multiple NPC and playing sessions, namely temporary and permanent character, account, and global variables. This could be as simple as a var function in Lua, that gets or sets a variable, based on the parameters.

local name = var("name")
if name == nil then
    msg("Who are you?")
    name = input("Please put in your name.")
    var("name", name)
end

msg("Hi, " .. name .. "!")
local timesVisited = var("visitedNpcXy")
timesVisited = var("visitedNpcXy", timesVisited + 1)
msg("This is visit number " .. timesVisited .. ".")

Given the fact that many of us will be familiar with eAthena, I propose we use the same prefixes to differentiate between variable types. [http://buwinow5.tripod.com/#Variables and scope](http://buwinow5.tripod.com/#Variables and scope)

Closing dialog with Escape

When pressing escape, an open dialog should be closed. Right now we're resuming and continuing the dialog, which is not official behavior and can get the player stuck in the dialog until relog.

The unknown int in CZ_DIALOG_ACK seems to indicate what happened, with 0 or -1 being sent when Escape was pressed and 1 otherwise.

Class level 15

Right now, if you log in with enough total class EXP to be class level 15, the client will display the level as 1, with no EXP. Similar to #141 I assume we're not sending what the client uses to calculate that. Actually, I even had to use a hack to make the Class EXP show up at all after a login, so there's definitely something missing.

We might need some in-depth research on what's sent upon login, and what could possibly be responsible for those two issues.

The Indonesian Client PacketHandler Issues

Since any information is good information for development, I figured I better log it somewhere. This is what I get when I try to enter the world on the channel server after the initial character creation and introduction movie:

6/21/2016 8:11:51 PM [Debug] - PacketHandler: No handler found for '0D34', CZ_MAP_SEARCH_INFO.
------------------------------------------------------------------------------
Op: 0D34 CZ_MAP_SEARCH_INFO, Size: 56 (Table: 55, Garbage: 1)
------------------------------------------------------------------------------
0000   34 0D 02 00 00 00 73 02  00 00 66 5F 73 69 61 75   4.....s...f_siau
0010   6C 69 61 69 5F 31 36 00  00 00 00 00 00 00 00 00   liai_16.........
0020   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................
0030   00 00 00 00 00 00 00 CD                            ........
------------------------------------------------------------------------------
6/21/2016 8:11:51 PM [Debug] - PacketHandler: No handler found for '0CF8', CZ_WIKI_RECIPE_UPDATE.
------------------------------------------------------------------------------
Op: 0CF8 CZ_WIKI_RECIPE_UPDATE, Size: 16 (Table: 10, Garbage: 6)
------------------------------------------------------------------------------
0000   F8 0C 03 00 00 00 07 01  00 00 04 91 FD 44 AD 95   .............D..
------------------------------------------------------------------------------
6/21/2016 8:11:52 PM [Warning] - Invalid padding for '0E13' (24, 11), from '98.3.252.77:65097'. Ignoring packet.
6/21/2016 8:11:52 PM [Debug] - PacketHandler: No handler found for '0D33', CZ_MAP_REVEAL_INFO.
------------------------------------------------------------------------------
Op: 0D33 CZ_MAP_REVEAL_INFO, Size: 152 (Table: 146, Garbage: 6)
------------------------------------------------------------------------------
0000   33 0D 06 00 00 00 7B 02  00 00 93 04 00 00 00 00   3...............
0010   00 00 00 00 60 60 00 00  00 00 00 00 00 00 00 00   ....``..........
0020   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................
0030   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................
0040   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................
0050   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................
0060   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................
0070   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................
0080   00 00 00 00 00 00 00 00  00 00 00 00 00 00 D3 9B   ................
0090   5E 40 8E CB 3B F9 68 46                            ^@..;.hF
-------------------------------------------------------------------------------

Then its CTD for the client from there.

Combat (Splash)

While combat range (#4) seems to be fixed now (it was the skill properties), splash damage isn't working yet. It presumably has a similar cause. The client sends only information about hitting a single enemy, no matter how many there are. The splash appears to be 0.

Ticking "Show Other Character's Effects" in game settings makes exception.

[Error] - Error while handling packet '0D20', CZ_CHANGE_CONFIG.
[Exception] - System.ArgumentException: Unknown option '48'.
   at Melia.Channel.Database.AccountSettings.Set(Option option, Int32 value) in x:\Research\TOS\melia\src\ChannelServer\Database\AccountSettings.cs:line 74
   at Melia.Channel.Network.ChannelPacketHandler.CZ_CHANGE_CONFIG(ChannelConnection conn, Packet packet) in x:\Research\TOS\melia\src\ChannelServer\Network\ChannelPacketHandler.cs:line 734
   at Melia.Shared.Network.PacketHandler`1.Handle(TConnection conn, Packet packet) in x:\Research\TOS\melia\src\Shared\Network\PacketHandler.cs:line 69
   at Melia.Channel.Network.ChannelConnection.HandlePacket(Packet packet) in x:\Research\TOS\melia\src\ChannelServer\Network\ChannelConnection.cs:line 40
   at Melia.Shared.Network.Connection.OnReceive(IAsyncResult result) in x:\Research\TOS\melia\src\Shared\Network\Connection.cs:line 198

Warehouse

Storing and retrieving items in and from the warehouse.

I suspect there to be multiple inventory types, with the inventory being one and the warehouse being another one. We'll probably need some structural changes in how we add/remove items to account for this.

Overall Project Structure

First of all, I need to congratulate all contributors on the effort. You're all doing an amazing work.

That being said, I have some points about the architecture of the project.

  • MySQL
  • External Dependencies
  • Props Database
  • Scripts
  • Migrations
  • Unit Tests
  • Packet Handling
  • Async/Await

MySQL

I won't describe here why MySQL sucks(be sure to see the humor behind this one).

I suggest that we use Postgres as database.

Props Database

From my background as a developer at eAthena/Hercules/Cronus, the server software for Ragnarok "I", I think that using files to describe items, jobs, etc, is a bad idea. This can cause several inconsistencies among several world servers.

External Dependencies

The project should use NuGet to install its dependencies.

Scripts

Lua is a great scripting language, but the way it is used in the project doesn't work well. I think we should use a managed implementation of Lua.

Migrations

Although the project is in early stage of development, it should be using an automated migration system for dealing with database changes, rather than flat SQL files.

Unit Tests

Although impractical is this kind o project, unit tests should be written to every feature after validation against the official client. Also, the project should be running in continuous integration(Travis?).

Packet Handling

As you should expect packets to always change between versions, specially the its format, actions should be decoupled from the packet parsers, and should accept a neat structure instead of a raw packet.

Async/Await

Instead of using APM, the new async/await keywords should be used when handling asynchronous code.

Help

A friend(@hails) and I want to take care of these things and I want to discuss they here.

Frozen in Place Bug

There seems to be a skill casting issue where I'm not quite sure how to pinpoint the causation of it, but I have a somewhat clear hypothesis on what causes it to happen, but no idea on how to replicate it consistently:
When casting magic or using any kind of skill, Occasionally what would happen is that the skill would finish loading, but be incapable of actuation and I'm constantly locked in place. Now, the only reason I notice when the issue happens is when casting magic, the spell would randomly cast twice within half a second from the 1st cast because the casting sound effect plays twice along with a greyed out skill for about a split second.
My theory is that it might be considering that 1st skill cast as constantly continuing to cast and then it would never complete casting because instead of being dropped, its counted as an ongoing cast that will never finish because its constantly being casted. Its a very wild theory, but thats my closest guess as to what's happening here. Hopefully I'm somewhere right on the track with that assumption if this is looked into.

Skill and Ability management

Description
Loading, saving, managing, and learning skills and abilities.

Implementation
Both skill and abilities are "objects" in ToS terms, with unique object ids and properties. As such they should both become their own objects in their own collections. There's nothing really special about them, except for how you learn them.

There's nothing that prevents you from sending any skill to the client, but it will only display those that the character's jobs can actually learn. We'll have to check that, based on our data. I think we don't have to add any skills at level 0 or something, we can simply wait for the client to request learning X, and then check if the character is actually able to learn it, adding it at level 1+.

Game Database Migration

Hi @exectails i saw your Game Package Database (Item Identification, NPC's, and etc) are statically stored in a TXT Database. In terms of performance and data integrity that's not good. Recommend you to store it in a real SQL Database. Here are some tips (in my opinion):

  1. Internal Game Data, like NPC's, are loaded one-time in the Emulator start up, directly from Database. Following the Software Design Patterns you will have Storage's and Factories. The Data is stored in the memory by a cache. And the Administrators can change it by a Database Explorer, like phpMyAdmin or Navicat, and use commands like :npc_update to Update NPC's details. Using in TXT is really easy to someone break the Data segments or corrupt it. Also is heavy to edit.
  2. File Buffers, reading the whole content of a TXT file will spent more time than SQL Queries.
  3. Is more easily to edit in a Relational Database. But those type of data doesn't actually need have any relation, only if exists direct linkage between (as example) NPC <-> Item.

Bugged (and duplicated) spawns across maps

Steps to reproduce this bug:

  1. Go to "Some Npc" (a.k.a THAT NPC) in West Siauliai Woods and use /spawn monsterID nearby ( you can spawn whatever do you like)
  2. Go to the border between West Siauliai Woods and Klaipeda, exactly where you see the green arrow on the ground
  3. You will be teleported in a random zone of Klaipeda and the custom Hair Stylist and Stat Resetter NPCs are missing in the southern part of Klaipedia.
  4. Now go to the upper left zone of Klaipedia ( above Central Plaza) and you will see again "THAT Npc" and the monster you spawned before...

Skill handling

I haven't taken a close look at how exactly ToS's skills work yet, but I'd like to talk about something before people try to implement them, as there are two conflicting approaches to it.

If you look at Athena or OdinMS for example, you will see that they basically handle skills in switch-cases. Related skills go into the same function, and what happens then is decided based on the skill's id. Here's a quick example of what something like that might look like.

void HandleDamageSkill(Skill skill, Character target)
{
	var splashRange = GetSplashRange(skill, target);
	var damageMultipler = GetDamageMultiplier(skill, target);
	
	var targets = GetTargets(target, splashRange);
	var damage = CalculateDamage(skill, target, damageMultiplier);
	targets.DealDamage(damage);
}

double GetSplashRange(Skill skill, Character target)
{
	switch (skill.Id)
	{
		case Bash:
			return 100;
		
		case Thrust:
		case PommelBeat:
			return 0;
	}
}

double GetDamageMultiplier(Skill skill, Character target)
{
	switch (skill.Id)
	{
		case Bash:
		case Thrust:
			return 1;
			
		case PommelBeat:
			if (target.IsStunned)
				return 1.5;
			break;
	}
}

RL example from eAthena:

		if(skill_num)
			switch(skill_num)
		{	//Hit skill modifiers
			//It is proven that bonus is applied on final hitrate, not hit.
			case SM_BASH:
			case MS_BASH:
				hitrate += hitrate * 5 * skill_lv / 100;
				break;
			case MS_MAGNUM:
			case SM_MAGNUM:
				hitrate += hitrate * 10 * skill_lv / 100;
				break;
			case KN_AUTOCOUNTER:
			case PA_SHIELDCHAIN:
			case NPC_WATERATTACK:
			case NPC_GROUNDATTACK:
			case NPC_FIREATTACK:
			case NPC_WINDATTACK:
			case NPC_POISONATTACK:
			case NPC_HOLYATTACK:
			case NPC_DARKNESSATTACK:
			case NPC_UNDEADATTACK:
			case NPC_TELEKINESISATTACK:
			case NPC_BLEEDING:
				hitrate += hitrate * 20 / 100;
				break;

There's basically a function and/or switch for any eventuality. Variables, buffs, effects, you name it. This approach has a huge advantage: it reduces redundancies. In your typical MMO there are very few skills that have actual special effects. For example, most damage dealing skills do pretty much the same thing, but with varying amounts of splash and damage. As a result, implementing a new damage skill in this system is generally as simple as adding a few fall-through cases, if any, because even defaulting might be an option.

This system was a little confusing to me when I started poking around eAthena as a beginner, because it wasn't compatible with what I expected. If I wanted to, say, change the damage of a skill and maybe play around with its effects, I couldn't search for a single function that handled one skill in its entirety. Instead I had to search for the general handler functions related to the thing I wanted to change, then find the switch, and either find the case for my skill, or add it if it didn't exist yet. The defaulting made it harder, because you might not even be able to search for the skill's id. It can be hard to wrap your head around this, and modding becomes harder.

For that reason I said to myself "if I ever create a server, I'll do better", so I went OOP for our Mabinogi emulator Aura. There, every skill has its own handler class, and you can change an entire skill's behavior in there. No jumping around, no searching, you want skill X, you get skill X.

class BashHandler : ISkillHandler
{
	void Handle(Skill skill, Character target)
	{
		var targets = GetTargets(target, 100);
		var damage = 123;
		targets.DealDamage(damage);
	}
}

class ThrustHandler : ISkillHandler
{
	void Handle(Skill skill, Character target)
	{
		var damage = 123;
		target.DealDamage(damage);
	}
}

class PommelBeatHandler : ISkillHandler
{
	void Handle(Skill skill, Character target)
	{
		var damage = 123;
		if (target.IsStunned)
			damage *= 1.5;
		target.DealDamage(damage);
	}
}

This, in my opinion, is much more manageable. Anybody is able to easily look up that one skill's handler and figure out what it does, because it's all right there, it leaves no questions open. The skill does exactly what it says in its handler, no more and no less.

It also eliminates one potential source of bugs. In eAthena I know of at least one case where a damage buff affected a new skill for a while that wasn't supposed to receive that buff. It was applied because the switch-case defaulted. While you could argue that that's a minor point, because in a dedicated handler design there's a potential for forgetting to include effects or buffs, I would argue that with handlers you can at least see that it's missing.

One disadvantage is redundancy, lots of it. Every slightly similar skill's handler will look pretty much the same. You can try to use inheritance to reduce that, but I've found that to not work very well, as it makes things even more confusing than the switch-case approach. Not only can't you find everything in one handler anymore, you don't even know which handler to look in from the potential countless parent handlers, and you constantly wonder where to put something new.

The other disadvantage is maintenance. If anything changes about the majority of damage dealing skills, you potentially have to modify every single damage skill handler, instead of a single switch-case.

I was glad that I decided to use dedicated handlers in Aura in the end, because Mabinogi's skills turned out to be very special. There was redundancy, but in hindsight I can't imagine doing it any other way, it would've been a mess. Many skills had multiple phases, skill specific parameters coming from the client, features you couldn't really generalize, and all in all, from dozens of skills, there might've been 3-4 instances where a switch-case would've actually worked well.

That doesn't mean that it's necessarily right for ToS and Melia, but it's something we should consider.

Stuck in character creation, keeps saying "name already exists"

Hey man -

Really appreciate all the work you've done here.

I understand there are incompatibility issues with the current state of melia and iTOS' latest version, but I still went and gave the setup a chance.

I was able to get as far as the character creation screen. Unfortunately, I'm stuck and keep getting a "name already exists" prompt. Here's a screenshot: http://i.imgur.com/xrVq81o.jpg

I'm not very familiar with databases but I had a wild guess this might have to do with checking and updating the database.

This is what the login window command line is outputting: http://i.imgur.com/gEVO4Jz.png

Is there a way I can manually change the value/property/attribute (I don't know the right word :D) of that "entry"? I think I can macguyver my way around using HeidiSQL if possible.

Thanks for your time, and hope to hear from you soon!

Channels and map servers

Intro
I've been thinking about our future server structure, and I'd like to discuss what exactly we want and need from it.

IMC designed ToS's servers for maximum efficiency. On one hand they have the ability to create dedicated map servers for a single map, for highly populated areas, on the other hand they support channels, allowing players to hop onto a different one, should a location be too crowded. This approach has obvious advantages, but it complicates the design. You basically have to assume that any command that involves another character might have to cross server borders, and you need to synchronize everything between X channel servers.

Ragnarok
I imagine many of us here will have experience with Ragnarok and Athena, where things were different. While I seem to remember that Athena (like Aegis) technically supports dedicated map servers, I don't think I've ever seen anyone use that setup, and one of the few references I just found on that feature mentions that there are multiple issues that make it tricky to use, suggesting that it was never implemented properly. There were also no channels in Ragnarok.

What Athena admins would do instead, is simply set up one set of servers for every one of their game servers. Maybe one Login+Char+Map server for a high rate, then a Char+Map for low rate, etc. The reason companies stopped using that model, and came up with map servers, channels, etc, is that it's not scaleable. It worked in the early days of MMOs, when you had only a few thousand players tops, but it doesn't work well if your player base goes into the tens or hundreds of thousands. That's the thing though, even really big pservers very rarely had to worry about numbers like that.

No problem
Not only do pservers usually have a smaller population, the players are also scattered across all the pservers out there. There's not one big publisher that has to take care of millions of players, but dozens if not hundreds of small "publishers", who manage a total of a few tens of thousands of players, leaving just thousands or hundreds for each one. Amounts that you don't usually need dedicated map servers or channels for.

Map servers are mainly a question of how much traffic you want to be able to handle. A single server can ideally handle several thousands of players, but at some point there will be a bottle-neck, be it CPU, RAM, or network. For channels however there are two sides to the argument on the user side.

Pros and Cons
One might say that channels are bad for the community, because they split the population. I remember on another game, when you would switch channels, it was like you entered a different server. Different market, people you had never seen before, forum signatures saying "I'm always on Ch ...", they were basically "sub-servers". An argument can also be made that searching for your friends across channels can be annoying.

On the other side players are competing over hunting grounds, and popular areas might make the client lag: the reasons channels became a thing. While I believe that these problems speak for poor design, because there are hotspots that attract players, with the game failing to distribute them over different fields and towns, the issue is kinda forced on us when emulating a game 1:1, because we can't really mess with the official quests, monsters, and relevant NPCs. (Or can we? Maybe a discussion for another day.)

Summary
In short, map servers and channels are more difficult to code, and I wonder whether we even need them. There's always the argument that a Melia user might want or need these features, and generally I'm in favor of providing users with everything they might have a use for, but then again I've just so rarely seen these features in action on pservers that it seems like a waste of time to design the entire server structure around them, when it would be so easy to just assume that the player will never leave the one map/channel server. And while it's not exactly the same, users could always make a second server that uses the same login database.

What do you think? Do we need channels? Do we need map servers? Do we actually need both? Personally I'm leaning towards not needing either.

Launcher and HTTPS host

Starting the client to connect to an unofficial server has always been a little bothersome, and it only got worse since the static config file is being accessed via SSL. We should create a simple launcher that players can use, and a public host for the file, so we don't have to worry about SSL on the client side anymore.

I'm thinking about a simple program that takes an IP and a port, sets up an internal web server for the serverlist, and just uses a public file for the static config. Then it temporarily replaces the client.xml, to make the client connect there.

I've basically written the code for it already, for Zemyna, I'd just have to slap a nice UI onto it, and upload the config file somewhere.

This will then also make the respective files on the web server obsolete, and in turn, potentially, the entire web server, because in production users will most likely use their own servers.

Scripting naming conventions

The naming of variables and functions in our scripts is currently not very clearly defined. While the built-in functions, like resetstatsย use all lower case without spaces, some functions inside the scripts use snake_case, and some use PascalCase. This is because of different influences, but we should agree on one and stick to it.

The reason I used nospace in the built-in functions was that I wanted to mimic eAthena a little, to the point where several functions might have the same name, making scripting for former Athena users easier. However, I didn't feel comfortable using that convention for longer function and variable names, so I switched to underscores in the custom NPCs. For variable names I then used camelCase, because that's what I'm used to from most C-like languages.

I don't think we should apply our C# conventions to Lua, as they're very different languages with different idioms. All Lua functions and variable names in official examples seem to be nospace, so that seems like the most logical choice to me. We could then use underscores for categorization. For example: npc_statresetter.

Warps

Everything to implement warps is in place, they just have to be added. Refer to the existing warps for reference and check the source to see how the addwarp function works.

https://github.com/aura-project/melia/blob/master/system/scripts/warps/f_siauliai_west.lua
https://github.com/aura-project/melia/blob/master/src/ChannelServer/Scripting/ScriptManager.cs#L427

The map names and coordinates can be taken from the where GM command, the warp name comes from the packets, but since it's just a name, I don't think they need to be official. The direction is the direction the warp arrow points to, with 0 being down.

ss 2016-04-01 at 03 18 23

Password security

Wrap md5 into something more secure - Blowfish?
Wrap coz client work with md5, but we can handle it on serverside as we want ๐Ÿ‘

NPC Shops

Buying and selling items from and to NPCs.

Notes

By the looks of it NPC shops aren't customizable without modifying the client. All that is sent by the server when you open a shop is a key, which makes the client look up the shop in the shop.ies, displaying the items specified for it. The server will need the same db, to look up which items can be bought, but it won't be able to easily change what's displayed.

A potential way to change the items in a shop might be the IES_MODIFY packets, but we have to research those first, to see how much you can really do with them.

To do

  • openshop function
  • Shops db
  • Buying
  • Selling
  • Sold item list

I'm working on this in the shops branch right now.

Feature database

This is something that doesn't really affect us just yet, but that I think we should talk about before it does affect us.

Problem

It's pretty typical for MMOs to change with updates, and often times these updates include changes that players don't like. In turn these changes are often times disabled on pservers, but I feel like most server emulators do a pretty lackluster job of providing ways to quickly and comfortably change these settings.

Let's take Ragnarok as an example, and more specifically, the destruction of Morroc. When this update hit, eAthena changed all warps and NPCs to match the new official ones. A lot of warps were even removed from the desert. Now if you wanted to restore Morroc, you not only had to modify your client, but you had to get an older version of eAthena to copy the old warps and NPCs from, while disabling the new ones. So far so doable with some tinkering, but then Morroc and the desert went through another change in RE. Now you had three Morrocs to choose from for the client, changes in more warps and NPCs, and even new entrances and exits for Ant Hell in the desert.

I recently went through puzzling together a pre-RE server with a restored Morroc, and while doable, it's simply not fun, and the entire time I was wondering why nobody had thought of a better way to handle these changes, to make them toggleable more easily. I mean... they didn't even leave the old code in, they just removed it, as if it wasn't important anymore.

Same thing goes for how they handled RE, with separate sub-folders, duplicate scripts and databases, where it gets really annoying to even find what you're looking for.

Solution

For a potential solution, let me introduce you to the "feature db" we have in our Mabinogi emulator, Aura. It's basically a list of toggles for every feature that changed or was added at some point in Mabinogi's life. Organized in a tree of "Generation" and "Season" updates (major and minor updates in Mabinogi), a user can enable and disable entire updates by changing a single bool. Don't want a certain skill to be attainable? Change one bool. Don't want pets? Change one bool. Want updates 1~5 and 7~8, but not 6, because that changed several systems, which you didn't like? Enable all bools but that one.

These are then checked in the code and the databases to adjust the game to whatever the user wants. Monsters will drop certain items that have a feature setting only if that feature is enabled, dungeons can only be entered if they're enabled, and if an item changed completely, in addition to the old one you add the new version with a feature check, so it's loaded and overrides the older version if the feature is enabled.

// Generation 2
//---------------------------------------------------------------------------
{ name: "G2", enabled: false, children: [
	// Season 1
	{ name: "G2S1", enabled: true, children: [
		// Enables G2 mainstream quest line
		{ name: "MainStreamG2", enabled: true },
		
		// Enables Emain, allowing to break the seal stones
		{ name: "EmainMacha", enabled: true },
		
		// Enables giving dresses to Nao for her to wear
		{ name: "NaoDressUp", enabled: true },
		
		// Enables collection books
		{ name: "CollectionBooks", enabled: true },
		
		// Enables the traveling NPC Price
		{ name: "Price", enabled: true },
		
		// Enables Rundal dungeon
		{ name: "RundalDungeon", enabled: true },
		
// ...
/*...*/, drops: [ /*...*/, {itemId: 64530, chance: 3.0, suffix: 40030, feature: "Fireball"}, /*...*/ ], /*...*/
public class EdernShop : NpcShopScript
{
	public override void Setup()
	{
		Add("Weapon", 40078); // Bipennis
		Add("Weapon", 40079); // Mace
		Add("Weapon", 40080); // Gladius
		Add("Weapon", 40081); // Leather Long Bow

		if (IsEnabled("Lance"))
		{
			Add("Weapon", 40404); // Physis Wooden Lance
		}

// ...

If we go back to the Ragnarok example, having such a feature db in eAthena would've meant that instead of spending an hour looking for the correct older eAthena versions to copy from, figuring out new warps that I'm then missing, and navigating the RE folders, I would've simply changed a few bools, and that would've been it. In and out in 3 minutes.

You may also consider classic servers, pre-Adv and so on, which are supposed to run at Episode XY, while they want to use the latest client for convenience and QoL functions. The further back you go, the harder it becomes to create such a server in Athena, because tons of monsters, drops, skills, and so on have to be changed. With a feature db it's, again, a matter of minutes.

I don't know what the future of Melia will look like, and how many features that are to come we will actually be able to toggle from the server, seeing how quite a few things in ToS are client sided, but I think it's worth considering, even if it ends up only affecting some warps, NPCs, and maybe skills.

Equip min. level requirements

It seems like it's not possible to equip items that have a minimum level requirement right now, even if the character's level is above it. So far I've been unable to find what exactly the client checks here. I also checked some of the client's Lua functions related to this, and it looked like the request went through all checks just fine. Unless I missed something, the check in question is probably in the client itself.

NPC dialog source

Our NPC's dialog code currently looks something like this:

if select("TUTO_GIRL_basic_02", "!@#$TUTO_GIRL_select01#@!", "!@#$Auto_JongLyo#@!") ~= 1 then
	return
end
if select("TUTO_GIRL_basic_03", "!@#$TUTO_GIRL_select02#@!", "!@#$Auto_JongLyo#@!") ~= 1 then
	return
end
if select("TUTO_GIRL_basic_04", "!@#$TUTO_GIRL_select03#@!", "!@#$Auto_JongLyo#@!") ~= 1 then
	return
end

msg("TUTO_GIRL_basic_05")

We take advantage of the fact that the client has the dialogues for all NPCs, and just send localization keys. This has the advantage that it's pretty easy to create NPCs from logs, and we don't have to worry about localization, as long as there is an official translation for your language.

However, we also put ourselves at the mercy of the client. Any string can change at any time, just like their keys, and the script we wrote might not make any sense anymore after a few updates. That wouldn't be a problem if we sent the actual text, without relying on the client. It would also allow users to actually figure out what's going on in a script, instead of just guessing, based on structure and keys.

if select("It would not be strange if the world ended tomorrow.{nl}However, I just want to know. Why the goddesses disappeared..", "Ask about the disappearance of the goddesses", "Cancel") ~= 1 then
	return
end
if select("I'm not sure I understand it completely, but what I'm sure of is that the goddesses didn't disappear all at once.", "Ask what happened", "Cancel") ~= 1 then
	return
end
if select("Goddess Vakarine left us shortly before Medzio Diena.{nl}Both Zemyna and Ausrine disappeared soon after.{nl}It's already been four years since.", "I think you forgot to mention Goddess Laima", "Cancel") ~= 1 then
	return
end

msg("She has never appeared before.{nl}Although I've read somewhere that she does appear every now and then...")

Typing all this out is certainly more work though, even if a Pale plugin could help with that, and I don't know if anyone will actually care about the dialog. It should be said though that writing the script would also be easier with actual text.

I thought I'd create a discussion for it, because I'm not sure which is better, and we should definitely stick to one.

Job system

Description
Handling of jobs, adding and removing them, changing base job and circles, and sending job lists to the client.

Implementation
Since jobs have their own properties, I'll create a Job object, storing them in a list in Character, and in their own table in the database. Their entire purpose will be to keep track of the job's circle, available skill points for that job, and later which skills and abilities are available to a character.

linux

Hi I have seen a lot of activity again in the project but i have some questions:

how i can compile in linux?
What are the specifications of the server?

Ty

Add internal server communication

The channel servers have to communicate with the login server, so it can properly display their status in the barracks.

In Aura the internal connections use the same protocol as the clients and just socket connect to each other on start up, which is a little messy. Instead we should design a clean library for this, as it surely won't be the last time the servers have to communicate, and Aura will be able to benefit from it as well.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.