Jump to content

Kenpachi

Core Developers
  • Content Count

    204
  • Joined

  • Last visited

  • Days Won

    32

Posts posted by Kenpachi


  1. Hi.
     

    The are effect type to achieve this. From doc/effect_list.md:

     420 | EF_BABYBODY                    | Eswoo (Small) (Visual Effect)
     421 | EF_BABYBODY2                   | Eswoo (Alt. Small) (Visual Effect)
     422 | EF_GIANTBODY                   | Eswoo (Normal) (Visual Effect)
     423 | EF_GIANTBODY2                  | Eswoo (Alt. Normal) (Visual Effect)

    Atcommand @size uses 420 and 422 for example.


    ~Kenpachi


  2. You can do this in OnClock0000 by running a simple SQL query.

    If your variable is a character variable:

    query_sql("DELETE FROM `char_reg_num_db` WHERE `key` = 'your_var_name'");

    If it's an account variable:

    query_sql("DELETE FROM `acc_reg_num_db` WHERE `key` = '#your_var_name'");



    ~Kenpachhi


  3. Hi.

    Alter your SQL table:

    ALTER TABLE `viptable` ADD COLUMN `insert_on` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP AFTER `email`;

    Or recreate it:

    DROP TABLE IF EXISTS `viptable`;
    CREATE TABLE `viptable` (
      `vipacc_id` int(11) unsigned NOT NULL default '0',
      `vip_id` varchar(255) NOT NULL default '',
      `days` int(11) NOT NULL default '0',
      `email` varchar(39) NOT NULL default '',
      `insert_on` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
      PRIMARY KEY  (`vipacc_id`),
      KEY `name` (`vip_id`)
    ) ENGINE=MyISAM;


    Change your function (note line 2):

    function	script	F_TicketAddVip	{
    	Query_SQL "DELETE FROM `viptable` WHERE `vipacc_id` = '"+GetCharID(3)+"' AND DATEDIFF(DATE_ADD(`insert_on`, INTERVAL `days` DAY), NOW()) < 0"; // remove expired VIP status before reapplying
    	Query_SQL "SELECT `days` FROM `viptable` WHERE `vipacc_id` = '"+GetCharID(3)+"'", .@checkDays;
    	if (.@checkDays > 0) {
    		Set .@setDays, (.@checkDays + 30);
    		Query_SQL "UPDATE `viptable` SET `days`='"+.@setDays+"' WHERE `vipacc_id`='"+GetCharID(3)+"'";
    		Query_SQL "SELECT `group_id` FROM `login` WHERE `account_id`='"+GetCharID(3)+"'", .@userLevel;
    		If (.@userLevel < 1) 
    			Query_SQL "UPDATE `login` SET `group_id`='2' WHERE `account_id`='"+GetCharID(3)+"'";
    	}
    	Else {
    		Query_SQL "UPDATE `login` SET `group_id`='2' WHERE `account_id`='"+GetCharID(3)+"'";
    		Query_SQL "SELECT `userid` FROM `login` WHERE `account_id`='"+GetCharID(3)+"'", .@userID$;
    		Query_SQL "SELECT `email` FROM `login` WHERE `account_id`='"+GetCharID(3)+"'", .@userEmail$;
    		Query_SQL "INSERT INTO `viptable` (`vipacc_id`,`vip_id`,`days`,`email`) VALUES ('"+GetCharID(3)+"','"+.@userID$+"','30','"+.@accEmail$+"')";
    	}
    	AtCommand "@reloadpcdb";
    	dispbottom "You received 30 days of VIP Account, congrats!";
    	specialeffect2 338;
    	End;
    Return;
    }

     

    The info script could look like this:

    -    script    VIP_STATUS    FAKE_NPC,{
    OnPCLoginEvent:
    	if (getgroupid() >= 2) {
    		Query_SQL "SELECT DATE(DATE_ADD(`insert_on`, INTERVAL `days` DAY)), DATEDIFF(DATE_ADD(`insert_on`, INTERVAL `days` DAY), NOW()) FROM `viptable` WHERE `vipacc_id` = '"+GetCharID(3)+"'", .@expire_date$, .@days_left;
    		dispbottom "========================";
    		dispbottom "Account Status : VIP";
    		dispbottom "Expire Date : " + .@expire_date$;
    		dispbottom "Time left : " + .@days_left;
    		dispbottom "========================";
    		end;
    	}
    }



    ~Kenpachi


  4. Sorry dude, but I wont write a complete patch for the item options system that works with your emulator version.
    it's not just updating the clif.c/.h but also packets, the socket, macros and everything else related to items.
    I spent more than an hour trying to add that diff to your emulator version, but didn't even came close to something compilable...
    Again, sorry.


    ~Kenpachi


  5. Hi.

    The actual problem is that you've changed the the data type of a parameter which is passes to the clif_add_random_options method.
    Thus you have to change the data type when calling clif_add_random_options, too.
    Double click each warning in VS to jump to the line where the data type mismatch was detected.
    You'll notice, that the method call tries to pass something like WBUFP(buf, 19) or WFIFOP(fd,21+offset) which both wont return an ItemInfo struct which clif_add_random_options expects to be passed.
    Now that you've found the faulty spots, have a look at the corresponding code parts in Hercules and change your code accordingly.
    Hope this helps.


    ~Kenpachi


  6. Thanks a lot. That's something to work with. 😊

    But this actually means, that *Athenas interpretation of water cells is far from official behavior.
    According to your code snippet every cell can be a water cell regardless of its type, but *Athena only accept type 3 or alternatively type 0 (cast to 3) if the conditions are fulfilled:

    // FROM map.c - map_readgat(struct map_data *m)
    
    if( type == 0 && water_height != NO_WATER && height > water_height )
    	type = 3; // Cell is 0 (walkable) but under water level, set to 3 (walkable water)

     

    And additionally I'm wondering about the difference in the water level check condition. According to your code snippet AEGIS checks if cell height is less than water level, while everyone else checks if cell height is greater than water level. This really confuses me. 😲


    ~Kenpachi


  7. Okay, before reading your post I did some improvements to my code.

    At first I noticed that return 0 if no RSW file was found and then ignoring it was stupid, sind 0 is an assumable value.

    So I changed it to float.NaN which brought slightly different results:

    Spoiler

    RO_GatCellTypesCollector_floatNaN.png.6c4a73b17d4b58c92be60db10de56c6a.png

     

    After this I thought about bad rounding of float values when comparing, so I changed the comparison to byte level and got really different results:

    Spoiler

    RO_GatCellTypesCollector_CompareFloatByte.png.1eb3547d231ae2a2c862c52a0af85b6f.png

     

    So I'll stick to the byte level comparison, since this should be more accurate.

    Now that I've read your post I noticed, that AEGIS uses the 4th DWORD, which noone else does.

    Hercules uses the 1st DWORD:  (Woops! Possible improvement detected. 😋)

    Spoiler
    
    // FROM map.c - map_readgat(struct map_data *m)
    
    	// Set cell properties
    	off = 14;
    	for( xy = 0; xy < num_cells; ++xy )
    	{
    		// read cell data
    		float height = *(float*)( gat + off      );
    		uint32 type = *(uint32*)( gat + off + 16 );
    		off += 20;
    
    		if( type == 0 && water_height != NO_WATER && height > water_height )
    			type = 3; // Cell is 0 (walkable) but under water level, set to 3 (walkable water)
    
    		m->cell[xy] = map->gat2cell(type);
    	}

     

     

    Let's compare those two:

    Spoiler

    RO_GatCellTypesCollector_CompareDWORD1DWORD4.png.e93e181305cfa2bbafad66c289c61367.png

     

    Okay, there's a huge difference. For a better overview only the AEGIS based results:

    Spoiler

    RO_GatCellTypesCollector_DWORD4_GREATER_WATERLEVEL.png.315023401b922bdf7686392341a27d14.png

     

    Still not what one would expect, since type 3 should be water.

    Let's see what happens when checking for value is less than water level:

    Spoiler

    RO_GatCellTypesCollector_DWORD4_LESS_WATERLEVEL.png.72807d0f66ff52ba9c72bdfc7371c71c.png

     

    Well, type 3 looks better now but overall it still looks bad. Let's include equality when comparing:

    Spoiler

    RO_GatCellTypesCollector_DWORD4_GREATER_EQUAL_WATERLEVEL.png.c556079bf1149436eb36c8536fd0ea64.png

    RO_GatCellTypesCollector_DWORD4_LESS_EQUAL_WATERLEVEL.png.54fcf2f83b37bf171908d0f0124f6100.png

     

    Still... no perfection at all. 😥

    My currently used algorithms:

    Spoiler
    
    private void ReadFile(string filePath)
    {
    	_currentFileNumber++;
    	_currentFile = filePath;
    	_worker.ReportProgress(default);
    	var waterLevel = GetWaterLevel(filePath.Replace(".gat", ".rsw"));
    	if (waterLevel == null) return;
    	using var reader = new BinaryReader(File.OpenRead(filePath));
    	reader.ReadBytes(6); // Skip magic header
    	var width = reader.ReadInt32();
    	var height = reader.ReadInt32();
    	for (var y = 0; y < height; y++)
    	{
    		for (var x = 0; x < width; x++)
    		{
    			reader.ReadBytes(12); // Skip unused bytes
    			var cellHeight = reader.ReadBytes(4);
    			var type = reader.ReadByte();
    			reader.ReadBytes(3); // Skip unknown bytes
    			var cellType = new CellType
    			{
    				Type = type,
    				MapData = $"{Path.GetFileName(filePath),-15} [x={x + 1} - y={y + 1}]",
    				Count = 1,
    				IsUnderWater = (((IStructuralComparable) cellHeight).CompareTo(waterLevel, Comparer<byte>.Default) > 0)
    			};
    			
    			bool Match(CellType o) => o.Type == cellType.Type && o.IsUnderWater == cellType.IsUnderWater;
    			if (!_types.Exists(Match))
    				_types.Add(cellType);
    			else
    				_types[_types.FindIndex(Match)].Count++;
    		}
    	}
    }
    
    private static byte[] GetWaterLevel(string filePath)
    {
    	if (!File.Exists(filePath)) return null;
    	using var reader = new BinaryReader(File.OpenRead(filePath));
    	reader.ReadBytes(4); // Skip magic header
    	var majorVersion = reader.ReadByte();
    	var minorVersion = reader.ReadByte();
    	reader.ReadBytes((majorVersion > 1 || (majorVersion == 1 && minorVersion >= 4)) ? 160 : 120); // Skip unused bytes
    	return reader.ReadBytes(4);
    }

     

     

    Any thoughts/suggestions?

     

     

    ~Kenpachi


  8. Hi there. Long time no see. 😊

    I'm currently working on a small server/client application just as a finger exercise for me. Therefor I'm using RO files and stumbled over the .gat cell types and their relation with the water level in .rsw files.

    According to *Athena and some other projects there should be just a few possible types:

    Spoiler
    
    // from Hercules\src\map\map.c - map_gat2cell()
    case 0: cell.walkable = 1; cell.shootable = 1; cell.water = 0; break; // walkable ground
    case 1: cell.walkable = 0; cell.shootable = 0; cell.water = 0; break; // non-walkable ground
    case 2: cell.walkable = 1; cell.shootable = 1; cell.water = 0; break; // ???
    case 3: cell.walkable = 1; cell.shootable = 1; cell.water = 1; break; // walkable water
    case 4: cell.walkable = 1; cell.shootable = 1; cell.water = 0; break; // ???
    case 5: cell.walkable = 0; cell.shootable = 1; cell.water = 0; break; // gap (snipable)
    case 6: cell.walkable = 1; cell.shootable = 1; cell.water = 0; break; // ???
    
    // from Hercules\src\map\map.c - map_cell2gat
    if( cell.walkable == 1 && cell.shootable == 1 && cell.water == 0 ) return 0;
    if( cell.walkable == 0 && cell.shootable == 0 && cell.water == 0 ) return 1;
    if( cell.walkable == 1 && cell.shootable == 1 && cell.water == 1 ) return 3;
    if( cell.walkable == 0 && cell.shootable == 1 && cell.water == 0 ) return 5;

     

     

    Additionally I found different ways to check if a cell is under water or not:

    Spoiler
    
    // *Athena and others
    if(gat_cell.upperLeftHeight > WATER_LEVEL_FROM_RSW)
          // cell is under water
    else
          // cell is not under water
    
    // OpenKore and others
    float averageDepth = (gat_block.upperLeftHeight + gat_block.upperRightHeight + gat_block.lowerLeftHeight + gat_block.lowerRightHeight) / 4;
    if(averageDepth > WATER_LEVEL_FROM_RSW)
          // cell is under water
    else
          // cell is not under water
    
    // Me for testing purposes
    if(gat_cell.upperLeftHeight > WATER_LEVEL_FROM_RSW || gat_cell.upperRightHeight > WATER_LEVEL_FROM_RSW ||
       gat_cell.lowerLeftHeight > WATER_LEVEL_FROM_RSW || gat_cell.lowerRightHeight > WATER_LEVEL_FROM_RSW)
          // cell is under water
    else
          // cell is not under water

     

     

    So I did a little testing a wrote a tool to collect all combinations of cell types and water level checks in every map. This is the result:

    RO_GatCellTypeCollector.png.6169d9c338c25c97fba9cc40b9f43ed4.png

     

    What? Now I really got confused. Even if only the *Athena way (IsUnderWaterUpperLeft) to check cell types  is payed attention to, there is a significant difference to what one would expect:

    RO_GatCellTypeCollector2.png.3b09cfa73cf230e7e110313b3075aeaa.png

     

    See? Maybe I'm using wrong algorithms, but they seem to be correct:

    Spoiler
    
    private void ReadFile(string filePath)
    {
    	_currentFileNumber++;
    	_currentFile = filePath;
    	_worker.ReportProgress(default);
    	var waterLevel = GetWaterLevel(filePath.Replace(".gat", ".rsw"));
    	using var reader = new BinaryReader(File.OpenRead(filePath));
    	reader.ReadBytes(6); // Skip magic header
    	var width = reader.ReadInt32();
    	var height = reader.ReadInt32();
    	for (var y = 0; y < height; y++)
    	{
    		for (var x = 0; x < width; x++)
    		{
    			var ul = reader.ReadSingle();
    			var ur = reader.ReadSingle();
    			var ll = reader.ReadSingle();
    			var lr = reader.ReadSingle();
    			var type = reader.ReadByte();
    			reader.ReadBytes(3); // Skip unknown bytes
    			var cellType = new CellType {Type = type, MapData = $"{Path.GetFileName(filePath),-15} [x={x + 1} - y={y + 1}]", Count = 1};
    
    			if (waterLevel == 0)
    				cellType.IsUnderWaterAny = cellType.IsUnderWaterAverage = cellType.IsUnderWaterUpperLeft = false;
    			else
    			{
    				cellType.IsUnderWaterAny = ul > waterLevel || ur > waterLevel || ll > waterLevel || lr > waterLevel;
    				cellType.IsUnderWaterAverage = (ul + ur + ll + lr) / 4 > waterLevel;
    				cellType.IsUnderWaterUpperLeft = ul > waterLevel;
    			}
    			
    			bool Match(CellType o) => o.Type == cellType.Type && o.IsUnderWaterAny == cellType.IsUnderWaterAny &&
    									  o.IsUnderWaterAverage == cellType.IsUnderWaterAverage &&
    									  o.IsUnderWaterUpperLeft == cellType.IsUnderWaterUpperLeft;
    			if (!_types.Exists(Match))
    				_types.Add(cellType);
    			else
    				_types[_types.FindIndex(Match)].Count++;
    		}
    	}
    }
    
    private static float GetWaterLevel(string filePath)
    {
    	if (!File.Exists(filePath)) return 0;
    	using var reader = new BinaryReader(File.OpenRead(filePath));
    	reader.ReadBytes(4); // Skip magic header
    	var majorVersion = reader.ReadByte();
    	var minorVersion = reader.ReadByte();
    	reader.ReadBytes((majorVersion > 1 || (majorVersion == 1 && minorVersion >= 4)) ? 160 : 120); // Skip unused bytes
    	return reader.ReadSingle();
    }

     

     

    So could anybody please tell me, how AEGIS handles this? Maybe Hercules can profit by this, too. 🙄

     

    ~Kenpachi


  9. Hi guys. :) ( Yes, I'm still alive. :D )

     

    First of all: great idea, great work!
     

     

    Besides messages.conf, all the dialogue utilised by 'mes' and 'select' is included in the .pot automatically

    This confused me, and so I checked the commit...

    Is it supposed to not include announce, mapannounce, areaaanounce, menu, dispbottom and probably some other string related commands?

     

    //EDIT: I put my hand up if you need a volunteer for a german translation.

     

     

    ~ Kenpachi


  10.  

    Well, backward campability should be a big point in a community project like this, but maybe that's just my opinion. And forcing people to completely rewrite their tools, isn't nice, too. Adding a field to an existing converter logic is much easier than rethinking the complete logic. I really don't know if the advatages of this update are bigger than the disadvantages. For me personally there are no advatages, so you maybe understand why I'm grumbling.

     

     

    Actually, it's been discussed since May 2013.

    Can't find a topic. Please send me a link via. PM. :)

     

     

    Feel free to release one. I personally won't, as there's no need for it to be in the repository (other than making the Hercules database usable with other server emulators, which is outside our scope.)

    Currently I don't even have time to be actively developing Hercules, so I can't do this. :(

     

     

    There's a reason not to do that. We'd be back to a hard to read file format (the import scripts). I personally find it pointless to have the item db in SQL (other than for control panels and such), because, well, it's an info that gets read once on server startup, and cached in any case. SQL is useful if you only need to look up parts of it (like a control panel does), but quite pointless (and an annoyance to keep up to date) if all you need is to SELECT * on server startup.

    If one of the two formats were to go away, to me it'd be SQL. But it's gonna stay, mostly for Control Panel purposes.

    That's the point. Think of the possibilities of SQL. You would be able to change data while server is running and this not just temporary. You could even create completely new data structures if required for a script. at runtime. (You can do this know, but not when dropping SQL.) Wouldn't whis be an advantage? "Hard to read".. Well, there are many many tools which make it easy to create SQL statements if you don't like typing them, that's not a valid reason in my opinion.

     

     

    You misunderstood the 'Inherit' point. Please re-read how it works.

    Inherit can only be used in the item_db2 to inherit the values defined in item_db for *the same item*. You can't have inheritance chains, as it's only a boolean. If your custom Knife[3] has 'Inherit: true' set, and you only define a script (i.e. because you want it to have 100 Atk instead of its standard value), it means it'll be exactly a Knife[3], with all other standard parameters from the item_db, and only the Atk you have overridden will be changed. This means that, if for some reason the official item is updated to have a different weight, you wouldn't need to edit yours, as the change will be propagated (unless you override it.) 

    Yeah, thanks for the clarification. I indeed misunderstood that one.


  11.  

    it would make sense to just start using full sql

    I suggested this many times. Noone wants it. And as long as there are just a few files available in SQL my tools will use the TXT version, simply because I don't want to implement a logic for SQL an another one for TXT.

     

     

    Can't tell you the countless times I've had to 'Page Up' to the top part to remember what row / comma section I was in...

    Well, now you have to search another item if the item you are editing inherits another item just to see the values. And perhaps this item inherits anotherone and you now have to search this now. Furthermore you don't see all the values with one view...  I my opinion this new format is pretty confusing.


  12.  

    im not saying its easy but it doesn't look too difficult

    It isn't difficult but a load of work, which isn't necessary.

     

     

    Dito, but they did promise us a converter~ so let's see bout that

    I don't think there will be a converter which is compatible with my private projects. :(

     

    What actually concerns me most is: Did you think of all the tools in use? Every single tool which reads the item DB must be rewritten because of this update.

    Furthermore RegEx replacements are nearly impossible now, too.

     

    I wish this would have been discussed before implementing, but now it's too late. :S

     

     

    My suggestion to please everyone: Provide a converter which is capable of converting the new format into the old format.

×
×
  • Create New...

Important Information

By using this site, you agree to our Terms of Use.