Pārlūkot izejas kodu

Converted player groups to YAML (#6488)

Finally adds import functionality for player groups and therefore atcommand and permission defining.

Co-authored-by: Aleos <aleos89@users.noreply.github.com>
Lemongrass3110 3 gadi atpakaļ
vecāks
revīzija
51ddc63a02

+ 0 - 303
conf/groups.conf

@@ -1,303 +0,0 @@
-/*
-
-Player groups configuration file
----------------------------------
-
-This file defines "player groups" and their privileges.
-
-Each group has its id and name, lists of available commands and other 
-permissions, and a list of other groups it inherits from.
-
-
-Group settings
---------------
-<id>
-Unique group number. The only required field.
-
-<name>
-Any string. If empty, defaults to "Group <id>". It is used in several @who 
-commands.
-
-<level>
-Equivalent of GM level, which was used in revisions before r15572. You can 
-set it to any number, but usually it's between 0 (default) and 99. Members of 
-groups with lower level can not perform some actions/commands (like @kick) on 
-members of groups with higher level. It is what script command getgmlevel() 
-returns. Group level can also be used to override trade restrictions 
-(db/item_trade.txt).
-
-<commands>
-A group of settings
-	<command name> : <bool>
-or
-	<commandname> : [ <bool>, <bool> ]
-First boolean value is for atcommand, second one for charcommand. If set to 
-true, group can use command. If only atcommand value is provided, false is 
-assumed for charcommand. If a command name is not included, false is assumed for 
-both atcommand and charcommand.
-For a full list of available commands, see: doc/atcommands.txt.
-Command names must not be aliases.
-
-<log_commands>
-Boolean value. If true then all commands used by the group will be logged to 
-atcommandlog. If setting is omitted in a group definition, false is assumed.
-Requires 'log_commands' to be enabled in 'conf/log_athena.conf'.
-
-<permissions>
-A group of settings
-	<permission> : <bool>
-If a permission is not included, false is assumed.
-For a full list of available permissions, see: doc/permissions.txt
-
-<inherit>
-A list of group names that given group will inherit commands and permissions 
-from. Group names are case-sensitive.
-
-Inheritance results
--------------------
-Both multiple inheritance (Group 2 -> Group 1 and Group 3 -> Group 1) and
-recursive inheritance (Group 3 -> Group 2 -> Group 1) are allowed.
-
-Inheritance rules should not create cycles (eg Group 1 inherits from Group 2, 
-and Group inherits from Group 1 at the same time). Configuration with cycles is 
-considered faulty and can't be processed fully by server.
-
-Command or permission is inherited ONLY if it's not already defined for the 
-group.
-If group inherits from multiple groups, and the same command or permission is 
-defined for more than one of these groups, it's undefined which one will be 
-inherited.
-
-Syntax
-------
-This config file uses libconfig syntax: 
-http://www.hyperrealm.com/libconfig/libconfig_manual.html#Configuration-Files
-
-
-Upgrading from revisions before r15572
--------------------------------------
-http://rathena.org/board/index.php?showtopic=58877
-*/
-
-groups: (
-{
-	id: 0 /* group 0 is the default group for every new account */
-	name: "Player"
-	level: 0
-	inherit: ( /*empty list*/ )
-	commands: {
-		changedress: true
-		resurrect: true
-	}
-	permissions: {
-		/* without this basic permissions regular players could not 
-		trade or party */
-		can_trade: true
-		can_party: true
-		attendance: true
-	}
-},
-{
-	id: 1
-	name: "Super Player"
-	inherit: ( "Player" ) /* can do everything Players can and more */
-	level: 0
-	commands: {
-		/* informational commands */
-		commands: true
-		charcommands: true
-		help: true
-		rates: true
-		uptime: true
-		showdelay: true
-		exp: true
-		mobinfo: true
-		iteminfo: true
-		whodrops: true
-		time: true
-		jailtime: true
-		hominfo: true
-		homstats: true
-		showexp: true
-		showzeny: true
-		whereis: true
-		/* feature commands */
-		refresh: true
-		noask: true
-		noks: true
-		autoloot: true
-		alootid: true
-		autoloottype: true
-		autotrade: true
-		request: true
-		go: true
-		breakguild: true
-		channel: true
-		langtype: true
-	}
-	permissions: {
-		attendance: false
-	}
-},
-{
-	id: 2
-	name: "Support"
-	inherit: ( "Super Player" )
-	level: 1
-	commands: {
-		version: true
-		where: true
-		jumpto: true
-		who: true
-		who2: true
-		who3: true
-		whomap: true
-		whomap2: true
-		whomap3: true
-		users: true
-		broadcast: true
-		localbroadcast: true
-	}
-	log_commands: true
-	permissions: {
-		receive_requests: true
-		view_equipment: true
-	}
-},
-{
-	id: 3
-	name: "Script Manager"
-	inherit: ( "Support" )
-	level: 1
-	commands: {
-		tonpc: true
-		hidenpc: true
-		shownpc: true
-		loadnpc: true
-		unloadnpc: true
-		npcmove: true
-		addwarp: true
-	}
-	log_commands: true
-	permissions: {
-		any_warp: true
-	}
-},
-{
-	id: 4
-	name: "Event Manager"
-	inherit: ( "Support" )
-	level: 1
-	commands: {
-		monster: true
-		monstersmall: true
-		monsterbig: true
-		killmonster2: true
-		cleanarea: true
-		cleanmap: true
-		item: [true, true]
-		zeny: [true, true]
-		disguise: [true, true]
-		undisguise: [true, true]
-		size: [true, true]
-		raise: true
-		raisemap: true
-		day: true
-		night: true
-		skillon: true
-		skilloff: true
-		pvpon: true
-		pvpoff: true
-		gvgon: true
-		gvgoff: true
-		allowks: true
-		me: true
-		marry: true
-		divorce: true
-		refreshall: true
-	}
-	log_commands: true
-	permissions: {
-		can_trade: false
-		any_warp: true
-	}
-},
-{
-	id: 5
-	name: "VIP"
-	inherit: ( "Player" ) /* can do everything Players can */
-	level: 0
-	commands: {
-		rates: true
-		who: true
-	}
-	permissions: {
-		/* no permissions by default */
-	}
-},
-{
-	id: 10
-	name: "Law Enforcement"
-	inherit: ( "Support" )
-	level: 2
-	commands: {
-		hide: true
-		follow: true
-		kick: true
-		disguise: true
-		fakename: true
-		option: true
-		speed: true
-		warp: true
-		kill: true
-		recall: true
-		ban: true
-		block: true
-		jail: true
-		jailfor: true
-		mute: true
-		storagelist: true
-		cartlist: true
-		itemlist: true
-		stats: true
-	}
-	log_commands: true
-	permissions: {
-		join_chat: true
-		kick_chat: true
-		hide_session: true
-		who_display_aid: true
-		hack_info: true
-		any_warp: true
-		view_hpmeter: true
-	}
-},
-{
-	id: 99
-	name: "Admin"
-	level: 99
-	inherit: ( "Support", "Law Enforcement" )
-	commands: {
-		/* not necessary due to all_commands: true */
-	}
-	log_commands: true
-	permissions: {
-		can_trade: true
-		can_party: true
-		command_enable: true
-		all_skill: false
-		all_equipment: false
-		skill_unconditional: false
-		use_check: true
-		use_changemaptype: true
-		all_commands: true
-		channel_admin: true
-		can_trade_bounded: true
-		item_unconditional: false
-		bypass_stat_onclone: true
-		bypass_max_stat: true
-		/* all_permission: true */
-	}
-}
-)
-

+ 245 - 0
conf/groups.yml

@@ -0,0 +1,245 @@
+# This file is a part of rAthena.
+#   Copyright(C) 2022 rAthena Development Team
+#   https://rathena.org - https://github.com/rathena
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+###########################################################################
+# Player Group Database
+###########################################################################
+#
+# Player Group Settings
+#
+###########################################################################
+# - Id                    Group ID.
+#   Name                  Group name.
+#   Level                 GM Level used for ranking groups. (Default: 0)
+#   LogCommands           Whether atcommands should be logged or not. (Default: false)
+#   Commands:             List of atcommands that can be used by this group. (Default: none)
+#     <atcommand name>    Whether the specified atcommand can be used by this group or not.
+#   CharCommands:         List of charcommands that can be used by this group. (Default: none)
+#     <charcommand name>  Whether the specified charcommand can be used by this group or not.
+#   Permissions:          List of permissions the group has. (Default: none)
+#     <permission name>   Whether the group has this permission or not.
+#   Inherit:              List of groups that will be inherited. (Default: none)
+#     <group name>        Whether this group will be inherited or not.
+###########################################################################
+
+Header:
+  Type: PLAYER_GROUP_DB
+  Version: 1
+
+Body:
+  - Id: 0
+    # group 0 is the default group for every new account
+    Name: Player
+    Level: 0
+    Commands:
+      changedress: true
+      resurrect: true
+    Permissions:
+      # without this basic permissions regular players could not trade or party
+      can_trade: true
+      can_party: true
+      attendance: true
+  - Id: 1
+    Name: Super Player
+    # Can do everything Players can and more
+    Inherit:
+      Player: true
+    Level: 0
+    Commands:
+      # Informational commands
+      commands: true
+      charcommands: true
+      help: true
+      rates: true
+      uptime: true
+      showdelay: true
+      exp: true
+      mobinfo: true
+      iteminfo: true
+      whodrops: true
+      time: true
+      jailtime: true
+      hominfo: true
+      homstats: true
+      showexp: true
+      showzeny: true
+      whereis: true
+      # Feature commands
+      refresh: true
+      noask: true
+      noks: true
+      autoloot: true
+      alootid: true
+      autoloottype: true
+      autotrade: true
+      request: true
+      go: true
+      breakguild: true
+      channel: true
+      langtype: true
+    Permissions:
+      attendance: false
+  - Id: 2
+    Name: Support
+    Inherit:
+      Super Player: true
+    Level: 1
+    Commands:
+      version: true
+      where: true
+      jumpto: true
+      who: true
+      who2: true
+      who3: true
+      whomap: true
+      whomap2: true
+      whomap3: true
+      users: true
+      broadcast: true
+      localbroadcast: true
+    LogCommands: true
+    Permissions:
+      receive_requests: true
+      view_equipment: true
+  - Id: 3
+    Name: Script Manager
+    Inherit:
+      Support: true
+    Level: 1
+    Commands:
+      tonpc: true
+      hidenpc: true
+      shownpc: true
+      loadnpc: true
+      unloadnpc: true
+      npcmove: true
+      addwarp: true
+    LogCommands: true
+    Permissions:
+      any_warp: true
+  - Id: 4
+    Name: Event Manager
+    Inherit:
+      Support: true
+    Level: 1
+    Commands:
+      monster: true
+      monstersmall: true
+      monsterbig: true
+      killmonster2: true
+      cleanarea: true
+      cleanmap: true
+      item: true
+      zeny: true
+      disguise: true
+      undisguise: true
+      size: true
+      raise: true
+      raisemap: true
+      day: true
+      night: true
+      skillon: true
+      skilloff: true
+      pvpon: true
+      pvpoff: true
+      gvgon: true
+      gvgoff: true
+      allowks: true
+      me: true
+      marry: true
+      divorce: true
+      refreshall: true
+    CharCommands:
+      item: true
+      zeny: true
+      disguise: true
+      undisguise: true
+      size: true
+    LogCommands: true
+    Permissions:
+      can_trade: false
+      any_warp: true
+  - Id: 5
+    Name: VIP
+    # Can do everything Players can
+    Inherit:
+      Player: true
+    Level: 0
+    Commands:
+      rates: true
+      who: true
+  - Id: 10
+    Name: Law Enforcement
+    Inherit:
+      Support: true
+    Level: 2
+    Commands:
+      hide: true
+      follow: true
+      kick: true
+      disguise: true
+      fakename: true
+      option: true
+      speed: true
+      warp: true
+      kill: true
+      recall: true
+      ban: true
+      block: true
+      jail: true
+      jailfor: true
+      mute: true
+      storagelist: true
+      cartlist: true
+      itemlist: true
+      stats: true
+    LogCommands: true
+    Permissions:
+      join_chat: true
+      kick_chat: true
+      hide_session: true
+      who_display_aid: true
+      hack_info: true
+      any_warp: true
+      view_hpmeter: true
+  - Id: 99
+    Name: Admin
+    Level: 99
+    Inherit:
+      Support: true
+      Law Enforcement: true
+    LogCommands: true
+    Permissions:
+      can_trade: true
+      can_party: true
+      command_enable: true
+      all_skill: false
+      all_equipment: false
+      skill_unconditional: false
+      use_check: true
+      use_changemaptype: true
+      all_commands: true
+      channel_admin: true
+      can_trade_bounded: true
+      item_unconditional: false
+      bypass_stat_onclone: true
+      bypass_max_stat: true
+      #all_permission: true
+
+Footer:
+  Imports:
+  - Path: conf/import/groups.yml

+ 41 - 0
conf/import-tmpl/groups.yml

@@ -0,0 +1,41 @@
+# This file is a part of rAthena.
+#   Copyright(C) 2022 rAthena Development Team
+#   https://rathena.org - https://github.com/rathena
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+###########################################################################
+# Player Group Database
+###########################################################################
+#
+# Player Group Settings
+#
+###########################################################################
+# - Id                    Group ID.
+#   Name                  Group name.
+#   Level                 GM Level used for ranking groups. (Default: 0)
+#   LogCommands           Whether atcommands should be logged or not. (Default: false)
+#   Commands:             List of atcommands that can be used by this group. (Default: none)
+#     <atcommand name>    Whether the specified atcommand can be used by this group or not.
+#   CharCommands:         List of charcommands that can be used by this group. (Default: none)
+#     <charcommand name>  Whether the specified charcommand can be used by this group or not.
+#   Permissions:          List of permissions the group has. (Default: none)
+#     <permission name>   Whether the group has this permission or not.
+#   Inherit:              List of groups that will be inherited. (Default: none)
+#     <group name>        Whether this group will be inherited or not.
+###########################################################################
+
+Header:
+  Type: PLAYER_GROUP_DB
+  Version: 1

+ 24 - 30
src/map/atcommand.cpp

@@ -616,7 +616,7 @@ ACMD_FUNC(mapmove)
 		if (!map_search_freecell(NULL, m, &x, &y, 10, 10, 1))
 			x = y = 0; //Invalid cell, use random spot.
 	}
-	if ((map_getmapflag(m, MF_NOWARPTO) && !pc_has_permission(sd, PC_PERM_WARP_ANYWHERE)) || !pc_job_can_entermap((enum e_job)sd->status.class_, m, sd->group_level)) {
+	if ((map_getmapflag(m, MF_NOWARPTO) && !pc_has_permission(sd, PC_PERM_WARP_ANYWHERE)) || !pc_job_can_entermap((enum e_job)sd->status.class_, m, pc_get_group_level(sd))) {
 		clif_displaymessage(fd, msg_txt(sd,247)); // You are not authorized to warp to this map.
 		return -1;
 	}
@@ -794,7 +794,7 @@ ACMD_FUNC(who) {
 				case 2: {
 					StringBuf_Printf(&buf, msg_txt(sd,343), pl_sd->status.name); // "Name: %s "
 					if (pc_get_group_id(pl_sd) > 0) // Player title, if exists
-						StringBuf_Printf(&buf, msg_txt(sd,344), pc_group_id2name(pc_get_group_id(pl_sd))); // "(%s) "
+						StringBuf_Printf(&buf, msg_txt(sd,344),pl_sd->group->name.c_str()); // "(%s) "
 					StringBuf_Printf(&buf, msg_txt(sd,347), pl_sd->status.base_level, pl_sd->status.job_level,
 						job_name(pl_sd->status.class_)); // "| Lv:%d/%d | Job: %s"
 					break;
@@ -804,7 +804,7 @@ ACMD_FUNC(who) {
 						StringBuf_Printf(&buf, msg_txt(sd,912), pl_sd->status.char_id, pl_sd->status.account_id);	// "(CID:%d/AID:%d) "
 					StringBuf_Printf(&buf, msg_txt(sd,343), pl_sd->status.name); // "Name: %s "
 					if (pc_get_group_id(pl_sd) > 0) // Player title, if exists
-						StringBuf_Printf(&buf, msg_txt(sd,344), pc_group_id2name(pc_get_group_id(pl_sd))); // "(%s) "
+						StringBuf_Printf(&buf, msg_txt(sd,344), pl_sd->group->name.c_str()); // "(%s) "
 					StringBuf_Printf(&buf, msg_txt(sd,348), mapindex_id2name(pl_sd->mapindex), pl_sd->bl.x, pl_sd->bl.y); // "| Location: %s %d %d"
 					break;
 				}
@@ -814,7 +814,7 @@ ACMD_FUNC(who) {
 
 					StringBuf_Printf(&buf, msg_txt(sd,343), pl_sd->status.name); // "Name: %s "
 					if (pc_get_group_id(pl_sd) > 0) // Player title, if exists
-						StringBuf_Printf(&buf, msg_txt(sd,344), pc_group_id2name(pc_get_group_id(pl_sd))); // "(%s) "
+						StringBuf_Printf(&buf, msg_txt(sd,344), pl_sd->group->name.c_str()); // "(%s) "
 					if (p != NULL)
 						StringBuf_Printf(&buf, msg_txt(sd,345), p->party.name); // " | Party: '%s'"
 					if (g != NULL)
@@ -7284,7 +7284,7 @@ ACMD_FUNC(adjgroup)
 		return -1;
 	}
 
-	if (!pc_group_exists(new_group)) {
+	if (!player_group_db.exists(new_group)) {
 		clif_displaymessage(fd, msg_txt(sd,1227)); // Specified group does not exist.
 		return -1;
 	}
@@ -9552,11 +9552,11 @@ static void atcommand_commands_sub(struct map_session_data* sd, const int fd, At
 
 		switch( type ) {
 			case COMMAND_CHARCOMMAND:
-				if( cmd->char_groups[sd->group_pos] == 0 )
+				if( cmd->char_groups[sd->group->index] == 0 )
 					continue;
 				break;
 			case COMMAND_ATCOMMAND:
-				if( cmd->at_groups[sd->group_pos] == 0 )
+				if( cmd->at_groups[sd->group->index] == 0 )
 					continue;
 				break;
 			default:
@@ -9815,17 +9815,17 @@ ACMD_FUNC(addperm) {
 		return -1;
 	}
 
-	if( add && (sd->permissions&pc_g_permission_name[i].permission) ) {
+	if( add && pc_has_permission( sd, pc_g_permission_name[i].permission) ){
 		sprintf(atcmd_output,  msg_txt(sd,1381),sd->status.name,pc_g_permission_name[i].name); // User '%s' already possesses the '%s' permission.
 		clif_displaymessage(fd, atcmd_output);
 		return -1;
-	} else if ( !add && !(sd->permissions&pc_g_permission_name[i].permission) ) {
+	}else if( !add && !pc_has_permission( sd, pc_g_permission_name[i].permission ) ){
 		sprintf(atcmd_output,  msg_txt(sd,1382),sd->status.name,pc_g_permission_name[i].name); // User '%s' doesn't possess the '%s' permission.
 		clif_displaymessage(fd, atcmd_output);
 		sprintf(atcmd_output,msg_txt(sd,1383),sd->status.name); // -- User '%s' Permissions
 		clif_displaymessage(fd, atcmd_output);
 		for( i = 0; i < perm_size; i++ ) {
-			if( sd->permissions&pc_g_permission_name[i].permission ) {
+			if( pc_has_permission( sd, pc_g_permission_name[i].permission ) ){
 				sprintf(atcmd_output,"- %s",pc_g_permission_name[i].name);
 				clif_displaymessage(fd, atcmd_output);
 			}
@@ -9834,10 +9834,11 @@ ACMD_FUNC(addperm) {
 		return -1;
 	}
 
-	if( add )
-		sd->permissions |= pc_g_permission_name[i].permission;
-	else
-		sd->permissions &=~ pc_g_permission_name[i].permission;
+	if( add ){
+		sd->permissions.set( pc_g_permission_name[i].permission );
+	}else{
+		sd->permissions.reset( pc_g_permission_name[i].permission );
+	}
 
 
 	sprintf(atcmd_output, msg_txt(sd,1384),sd->status.name); // User '%s' permissions updated successfully. The changes are temporary.
@@ -11212,7 +11213,7 @@ bool is_atcommand(const int fd, struct map_session_data* sd, const char* message
 					return false; // No command found. Display as normal message.
 
 				info = get_atcommandinfo_byname(atcommand_alias_db.checkAlias(command + 1));
-				if (!info || info->char_groups[sd->group_pos] == 0)  // If we can't use or doesn't exist: don't even display the command failed message
+				if (!info || info->char_groups[sd->group->index] == 0)  // If we can't use or doesn't exist: don't even display the command failed message
 					return false;
 			}
 
@@ -11295,8 +11296,8 @@ bool is_atcommand(const int fd, struct map_session_data* sd, const char* message
 
 	// type == 1 : player invoked
 	if (type == 1) {
-		if ((is_atcommand && info->at_groups[sd->group_pos] == 0) ||
-			(!is_atcommand && info->char_groups[sd->group_pos] == 0) )
+		if ((is_atcommand && info->at_groups[sd->group->index] == 0) ||
+			(!is_atcommand && info->char_groups[sd->group->index] == 0) )
 			return false;
 
 		if( pc_isdead(sd) && pc_has_permission(sd,PC_PERM_DISABLE_CMD_DEAD) ) {
@@ -11319,29 +11320,22 @@ bool is_atcommand(const int fd, struct map_session_data* sd, const char* message
 	return true;
 }
 
-void atcommand_db_load_groups(int* group_ids) {
+void atcommand_db_load_groups(){
 	DBIterator *iter = db_iterator(atcommand_db);
 	AtCommandInfo* cmd;
-	int i;
+	int pc_group_max = player_group_db.size();
 
 	for (cmd = (AtCommandInfo*)dbi_first(iter); dbi_exists(iter); cmd = (AtCommandInfo*)dbi_next(iter)) {
 		cmd->at_groups = (char*)aMalloc( pc_group_max * sizeof(char) );
 		cmd->char_groups = (char*)aMalloc( pc_group_max * sizeof(char) );
-		for(i = 0; i < pc_group_max; i++) {
-			if( pc_group_can_use_command(group_ids[i], cmd->command, COMMAND_ATCOMMAND ) )
-			   cmd->at_groups[i] = 1;
-			else
-			   cmd->at_groups[i] = 0;
-		   if( pc_group_can_use_command(group_ids[i], cmd->command, COMMAND_CHARCOMMAND ) )
-			  cmd->char_groups[i] = 1;
-			else
-			  cmd->char_groups[i] = 0;
+
+		for( auto& it : player_group_db ){
+			cmd->at_groups[it.second->index] = it.second->can_use_command( cmd->command, COMMAND_ATCOMMAND );
+			cmd->char_groups[it.second->index] = it.second->can_use_command( cmd->command, COMMAND_CHARCOMMAND );
 		}
 	}
 
 	dbi_destroy(iter);
-
-	return;
 }
 void atcommand_db_clear(void) {
 

+ 1 - 1
src/map/atcommand.hpp

@@ -25,7 +25,7 @@ bool is_atcommand(const int fd, struct map_session_data* sd, const char* message
 
 void do_init_atcommand(void);
 void do_final_atcommand(void);
-void atcommand_db_load_groups(int* group_ids);
+void atcommand_db_load_groups();
 
 bool atcommand_exists(const char* name);
 

+ 1 - 1
src/map/intif.cpp

@@ -1342,7 +1342,7 @@ int intif_parse_WisEnd(int fd)
  */
 static int mapif_parse_WisToGM_sub(struct map_session_data* sd,va_list va)
 {
-	int permission = va_arg(va, int);
+	e_pc_permission permission = va_arg(va, e_pc_permission);
 	char *wisp_name;
 	char *message;
 	int len;

+ 1 - 0
src/map/map-server.vcxproj

@@ -294,6 +294,7 @@
     <Copy SourceFiles="$(SolutionDir)conf\import-tmpl\atcommands.yml" DestinationFolder="$(SolutionDir)conf\import\" ContinueOnError="true" Condition="!Exists('$(SolutionDir)conf\import\atcommands.yml')" />
     <Copy SourceFiles="$(SolutionDir)conf\import-tmpl\battle_conf.txt" DestinationFolder="$(SolutionDir)conf\import\" ContinueOnError="true" Condition="!Exists('$(SolutionDir)conf\import\battle_conf.txt')" />
     <Copy SourceFiles="$(SolutionDir)conf\import-tmpl\char_conf.txt" DestinationFolder="$(SolutionDir)conf\import\" ContinueOnError="true" Condition="!Exists('$(SolutionDir)conf\import\char_conf.txt')" />
+    <Copy SourceFiles="$(SolutionDir)conf\import-tmpl\groups.yml" DestinationFolder="$(SolutionDir)conf\import\" ContinueOnError="true" Condition="!Exists('$(SolutionDir)conf\import\groups.yml')" />
     <Copy SourceFiles="$(SolutionDir)conf\import-tmpl\inter_conf.txt" DestinationFolder="$(SolutionDir)conf\import\" ContinueOnError="true" Condition="!Exists('$(SolutionDir)conf\import\inter_conf.txt')" />
     <Copy SourceFiles="$(SolutionDir)conf\import-tmpl\inter_server.yml" DestinationFolder="$(SolutionDir)conf\import\" ContinueOnError="true" Condition="!Exists('$(SolutionDir)conf\import\inter_server.yml')" />
     <Copy SourceFiles="$(SolutionDir)conf\import-tmpl\log_conf.txt" DestinationFolder="$(SolutionDir)conf\import\" ContinueOnError="true" Condition="!Exists('$(SolutionDir)conf\import\log_conf.txt')" />

+ 1 - 1
src/map/npc.cpp

@@ -1858,7 +1858,7 @@ int npc_touch_areanpc(struct map_session_data* sd, int16 m, int16 x, int16 y, st
 	case NPCTYPE_WARP:
 		if ((!nd->trigger_on_hidden && (pc_ishiding(sd) || (sd->sc.count && sd->sc.data[SC_CAMOUFLAGE]))) || pc_isdead(sd))
 			break; // hidden or dead chars cannot use warps
-		if (!pc_job_can_entermap((enum e_job)sd->status.class_, map_mapindex2mapid(nd->u.warp.mapindex), sd->group_level))
+		if (!pc_job_can_entermap((enum e_job)sd->status.class_, map_mapindex2mapid(nd->u.warp.mapindex), pc_get_group_level(sd)))
 			break;
 		if (sd->count_rewarp > 10) {
 			ShowWarning("Prevented infinite warp loop for player (%d:%d). Please fix NPC: '%s', path: '%s'\n", sd->status.account_id, sd->status.char_id, nd->exname, nd->path);

+ 9 - 7
src/map/pc.cpp

@@ -443,7 +443,7 @@ int pc_get_group_id(struct map_session_data *sd) {
 * @return Group Level
 */
 int pc_get_group_level(struct map_session_data *sd) {
-	return sd->group_level;
+	return sd->group->level;
 }
 
 /**
@@ -12454,9 +12454,12 @@ bool pc_isautolooting(struct map_session_data *sd, t_itemid nameid)
  * @param command Command name without @/# and params
  * @param type is it atcommand or charcommand
  */
-bool pc_can_use_command(struct map_session_data *sd, const char *command, AtCommandType type)
-{
-	return pc_group_can_use_command(pc_get_group_id(sd), command, type);
+bool pc_can_use_command( struct map_session_data *sd, const char *command, AtCommandType type ){
+	return sd->group->can_use_command( command, type );
+}
+
+bool pc_has_permission( struct map_session_data* sd, e_pc_permission permission ){
+	return sd->permissions.test( permission );
 }
 
 /**
@@ -12464,9 +12467,8 @@ bool pc_can_use_command(struct map_session_data *sd, const char *command, AtComm
  * according to their group setting.
  * @param sd Player map session data
  */
-bool pc_should_log_commands(struct map_session_data *sd)
-{
-	return pc_group_should_log_commands(pc_get_group_id(sd));
+bool pc_should_log_commands( struct map_session_data *sd ){
+	return sd->group->log_commands;
 }
 
 /**

+ 6 - 3
src/map/pc.hpp

@@ -4,6 +4,7 @@
 #ifndef PC_HPP
 #define PC_HPP
 
+#include <bitset>
 #include <memory>
 #include <vector>
 
@@ -19,6 +20,7 @@
 #include "itemdb.hpp" // MAX_ITEMGROUP
 #include "map.hpp" // RC_ALL
 #include "mob.hpp" //e_size
+#include "pc_groups.hpp" // s_player_group
 #include "script.hpp" // struct script_reg, struct script_regstr
 #include "searchstore.hpp"  // struct s_search_store_info
 #include "status.hpp" // unit_data
@@ -408,8 +410,9 @@ struct map_session_data {
 	} special_state;
 	uint32 login_id1, login_id2;
 	uint64 class_;	//This is the internal job ID used by the map server to simplify comparisons/queries/etc. [Skotlex]
-	int group_id, group_pos, group_level;
-	unsigned int permissions;/* group permissions */
+	int group_id;
+	std::shared_ptr<s_player_group> group;
+	std::bitset<PC_PERM_MAX> permissions; // group permissions have to be copied, because they might be adjusted by atcommand addperm
 	int count_rewarp; //count how many time we being rewarped
 
 	int langtype;
@@ -1238,7 +1241,7 @@ bool pc_can_give_bounded_items(struct map_session_data *sd);
 bool pc_can_trade_item(map_session_data *sd, int index);
 
 bool pc_can_use_command(struct map_session_data *sd, const char *command, AtCommandType type);
-#define pc_has_permission(sd, permission) ( ((sd)->permissions&permission) != 0 )
+bool pc_has_permission( struct map_session_data* sd, e_pc_permission permission );
 bool pc_should_log_commands(struct map_session_data *sd);
 
 void pc_setrestartvalue(struct map_session_data *sd, char type);

+ 300 - 368
src/map/pc_groups.cpp

@@ -3,353 +3,343 @@
 
 #include "pc_groups.hpp"
 
-#include "../common/conf.hpp"
-#include "../common/db.hpp"
-#include "../common/malloc.hpp"
 #include "../common/showmsg.hpp"
-#include "../common/socket.hpp"
-#include "../common/strlib.hpp" // strcmp
+#include "../common/socket.hpp" // set_eof
+#include "../common/strlib.hpp" // strcmpi
+#include "../common/utilities.hpp"
 
 #include "atcommand.hpp" // AtCommandType
-#include "pc.hpp" // e_pc_permission
-
-typedef struct GroupSettings GroupSettings;
-
-// Cached config settings/pointers for quick lookup
-struct GroupSettings {
-	unsigned int id; // groups.[].id
-	int level; // groups.[].level
-	const char *name; // groups.[].name
-	config_setting_t *commands; // groups.[].commands
-	unsigned int e_permissions; // packed groups.[].permissions
-	bool log_commands; // groups.[].log_commands
-	/// Following are used only during config reading
-	config_setting_t *permissions; // groups.[].permissions
-	config_setting_t *inherit; // groups.[].inherit
-	bool inheritance_done; // have all inheritance rules been evaluated?
-	config_setting_t *root; // groups.[]
-	int group_pos;/* pos on load */
-};
-
-int pc_group_max; /* known number of groups */
-
-static config_t pc_group_config;
-static DBMap* pc_group_db; // id -> GroupSettings
-static DBMap* pc_groupname_db; // name -> GroupSettings
+#include "pc.hpp" // struct map_session_data
 
-/**
- * @retval NULL if not found
- * @private
- */
-static inline GroupSettings* id2group(int group_id)
-{
-	return (GroupSettings*)idb_get(pc_group_db, group_id);
-}
+using namespace rathena;
 
-/**
- * @retval NULL if not found
- * @private
- */
-static inline GroupSettings* name2group(const char* group_name)
-{
-	return (GroupSettings*)strdb_get(pc_groupname_db, group_name);
+const std::string PlayerGroupDatabase::getDefaultLocation() {
+	return std::string( conf_path ) + "/groups.yml";
 }
 
-/**
- * Loads group configuration from config file into memory.
- * @private
- */
-static void read_config(void)
-{
-	config_setting_t *groups = NULL;
-	const char *config_filename = "conf/groups.conf"; // FIXME hardcoded name
-	int group_count = 0;
-	
-	if (conf_read_file(&pc_group_config, config_filename))
-		return;
+bool PlayerGroupDatabase::parseCommands( const ryml::NodeRef& node, std::vector<std::string>& commands ){
+	for( const auto& it : node.children() ){
+		std::string command;
 
-	groups = config_lookup(&pc_group_config, "groups");
-
-	if (groups != NULL) {
-		GroupSettings *group_settings = NULL;
-		DBIterator *iter = NULL;
-		int i, loop = 0;
-
-		group_count = config_setting_length(groups);
-		for (i = 0; i < group_count; ++i) {
-			int id = 0, level = 0;
-			const char *groupname = NULL;
-			int log_commands = 0;
-			config_setting_t *group = config_setting_get_elem(groups, i);
-
-			if (!config_setting_lookup_int(group, "id", &id)) {
-				ShowConfigWarning(group, "pc_groups:read_config: \"groups\" list member #%d has undefined id, removing...", i);
-				config_setting_remove_elem(groups, i);
-				--i;
-				--group_count;
-				continue;
-			}
+		c4::from_chars( it.key(), &command );
 
-			if (id2group(id) != NULL) {
-				ShowConfigWarning(group, "pc_groups:read_config: duplicate group id %d, removing...", i);
-				config_setting_remove_elem(groups, i);
-				--i;
-				--group_count;
-				continue;
-			}
+		bool allowed;
 
-			config_setting_lookup_int(group, "level", &level);
-			config_setting_lookup_bool(group, "log_commands", &log_commands);
-
-			if (!config_setting_lookup_string(group, "name", &groupname)) {
-				char temp[20];
-				config_setting_t *name = NULL;
-				snprintf(temp, sizeof(temp), "Group %d", id);
-				if ((name = config_setting_add(group, "name", CONFIG_TYPE_STRING)) == NULL ||
-				    !config_setting_set_string(name, temp)) {
-					ShowError("pc_groups:read_config: failed to set missing group name, id=%d, skipping... (%s:%d)\n",
-					          id, config_setting_source_file(group), config_setting_source_line(group));
-					continue;
-				}
-				config_setting_lookup_string(group, "name", &groupname); // Retrieve the pointer
-			}
+		if( !this->asBool( node, command, allowed ) ){
+			return false;
+		}
 
-			if (name2group(groupname) != NULL) {
-				ShowConfigWarning(group, "pc_groups:read_config: duplicate group name %s, removing...", groupname);
-				config_setting_remove_elem(groups, i);
-				--i;
-				--group_count;
-				continue;
+		if( !atcommand_exists( command.c_str() ) ){
+			this->invalidWarning( it, "Unknown atcommand: %s\n", command.c_str() );
+			return false;
+		}
+
+		util::tolower( command );
+
+		if( allowed ){
+			if( util::vector_exists( commands, command ) ){
+				this->invalidWarning( it, "Group already has command \"%s\". Please check your data.\n", command.c_str() );
+				return false;
+			}else{
+				commands.push_back( command );
 			}
+		}else{
+			if( !util::vector_exists( commands, command ) ){
+				this->invalidWarning( it, "Group does not have command \"%s\". Please check your data.\n", command.c_str() );
+				return false;
+			}else{
+				util::vector_erase_if_exists( commands, command );
+			}
+		}
+	}
+
+	return true;
+}
+
+uint64 PlayerGroupDatabase::parseBodyNode( const ryml::NodeRef& node ){
+	uint32 groupId;
 
-			CREATE(group_settings, GroupSettings, 1);
-			group_settings->id = id;
-			group_settings->level = level;
-			group_settings->name = groupname;
-			group_settings->log_commands = log_commands != 0;
-			group_settings->inherit = config_setting_get_member(group, "inherit");
-			group_settings->commands = config_setting_get_member(group, "commands");
-			group_settings->permissions = config_setting_get_member(group, "permissions");
-			group_settings->inheritance_done = false;
-			group_settings->root = group;
-			group_settings->group_pos = i;
-
-			strdb_put(pc_groupname_db, groupname, group_settings);
-			idb_put(pc_group_db, id, group_settings);
-			
+	if( !this->asUInt32( node, "Id", groupId ) ){
+		return 0;
+	}
+
+	std::shared_ptr<s_player_group> group = this->find( groupId );
+	bool exists = group != nullptr;
+
+	if( !exists ){
+		if( !this->nodesExist( node, { "Name", "Level" })) {
+			return 0;
 		}
-		group_count = config_setting_length(groups); // Save number of groups
-		
-		// Check if all commands and permissions exist
-		iter = db_iterator(pc_group_db);
-		for (group_settings = (GroupSettings *)dbi_first(iter); dbi_exists(iter); group_settings = (GroupSettings *)dbi_next(iter)) {
-			config_setting_t *commands = group_settings->commands, *permissions = group_settings->permissions;
-			int count = 0, j;
-
-			// Make sure there is "commands" group
-			if (commands == NULL)
-				commands = group_settings->commands = config_setting_add(group_settings->root, "commands", CONFIG_TYPE_GROUP);
-			count = config_setting_length(commands);
-
-			for (j = 0; j < count; ++j) {
-				config_setting_t *command = config_setting_get_elem(commands, j);
-				const char *name = config_setting_name(command);
-				if (!atcommand_exists(name)) {
-					ShowConfigWarning(command, "pc_groups:read_config: non-existent command name '%s', removing...", name);
-					config_setting_remove(commands, name);
-					--j;
-					--count;
-				}
+
+		group = std::make_shared<s_player_group>();
+		group->id = groupId;
+		group->permissions.reset();
+	}
+
+	if( this->nodeExists( node, "Name" ) ){
+		std::string name;
+
+		if( !this->asString( node, "Name", name ) ){
+			return 0;
+		}
+
+		group->name = name;
+	}
+
+	if( this->nodeExists( node, "Level" ) ){
+		uint32 level;
+
+		if( !this->asUInt32( node, "Level", level ) ){
+			return 0;
+		}
+
+		if (level > 99) {
+			this->invalidWarning(node["Level"], "Group level %u exceeds 99, capping.\n", level);
+			level = 99;
+		}
+
+		group->level = level;
+	}
+
+	if( this->nodeExists( node, "LogCommands" ) ){
+		bool log;
+
+		if( !this->asBool( node, "LogCommands", log ) ){
+			return 0;
+		}
+
+		group->log_commands = log;
+	}else{
+		if( !exists ){
+			group->log_commands = false;
+		}
+	}
+
+	if( this->nodeExists( node, "Commands" ) && !this->parseCommands( node["Commands"], group->commands ) ){
+		return 0;
+	}
+
+	if( this->nodeExists( node, "CharCommands" ) && !this->parseCommands( node["CharCommands"], group->char_commands ) ){
+		return 0;
+	}
+
+	if( this->nodeExists( node, "Permissions" ) ){
+		const auto& permissions = node["Permissions"];
+
+		for( const auto& it : permissions ){
+			std::string permission;
+
+			c4::from_chars( it.key(), &permission );
+
+			bool allowed;
+
+			if( !this->asBool( permissions, permission, allowed ) ){
+				return 0;
 			}
 
-			// Make sure there is "permissions" group
-			if (permissions == NULL)
-				permissions = group_settings->permissions = config_setting_add(group_settings->root, "permissions", CONFIG_TYPE_GROUP);
-			count = config_setting_length(permissions);
-
-			for(j = 0; j < count; ++j) {
-				config_setting_t *permission = config_setting_get_elem(permissions, j);
-				const char *name = config_setting_name(permission);
-				int p;
-
-				ARR_FIND(0, ARRAYLENGTH(pc_g_permission_name), p, strcmp(pc_g_permission_name[p].name, name) == 0);
-				if (p == ARRAYLENGTH(pc_g_permission_name)) {
-					ShowConfigWarning(permission, "pc_groups:read_config: non-existent permission name '%s', removing...", name);
-					config_setting_remove(permissions, name);
-					--p;
-					--count;
+			const char* str = permission.c_str();
+			bool found = false;
+
+			for( const auto& permission_name : pc_g_permission_name ){
+				if( strcmpi( "all_permission", str ) == 0 ){
+					if( allowed ){
+						group->permissions.set();
+					}else{
+						group->permissions.reset();
+					}
+
+					found = true;
+					break;
+				}else if( strcmpi( permission_name.name, str ) == 0 ){
+					if( allowed ){
+						group->permissions.set( permission_name.permission );
+					}else{
+						group->permissions.reset( permission_name.permission );
+					}
+
+					found = true;
+					break;
 				}
 			}
+
+			if( !found ){
+				this->invalidWarning( it, "Unknown permission: %s\n", str );
+				return 0;
+			}
 		}
-		dbi_destroy(iter);
-
-		// Apply inheritance
-		i = 0; // counter for processed groups
-		while (i < group_count) {
-			iter = db_iterator(pc_group_db);
-			for (group_settings = (GroupSettings *)dbi_first(iter); dbi_exists(iter); group_settings = (GroupSettings *)dbi_next(iter)) {
-				config_setting_t *inherit = NULL,
-				                 *commands = group_settings->commands,
-					             *permissions = group_settings->permissions;
-				int j, inherit_count = 0, done = 0;
-				
-				if (group_settings->inheritance_done) // group already processed
-					continue; 
-
-				if ((inherit = group_settings->inherit) == NULL ||
-				    (inherit_count = config_setting_length(inherit)) <= 0) { // this group does not inherit from others
-					++i;
-					group_settings->inheritance_done = true;
-					continue;
-				}
-				
-				for (j = 0; j < inherit_count; ++j) {
-					GroupSettings *inherited_group = NULL;
-					const char *groupname = config_setting_get_string_elem(inherit, j);
-
-					if (groupname == NULL) {
-						ShowConfigWarning(inherit, "pc_groups:read_config: \"inherit\" array member #%d is not a name, removing...", j);
-						config_setting_remove_elem(inherit,j);
-						continue;
-					}
-					if ((inherited_group = name2group(groupname)) == NULL) {
-						ShowConfigWarning(inherit, "pc_groups:read_config: non-existent group name \"%s\", removing...", groupname);
-						config_setting_remove_elem(inherit,j);
-						continue;
-					}
-					if (!inherited_group->inheritance_done)
-						continue; // we need to do that group first
-
-					// Copy settings (commands/permissions) that are not defined yet
-					if (inherited_group->commands != NULL) {
-						int l = 0, commands_count = config_setting_length(inherited_group->commands);
-						for (l = 0; l < commands_count; ++l)
-							config_setting_copy(commands, config_setting_get_elem(inherited_group->commands, l));
-					}
+	}
 
-					if (inherited_group->permissions != NULL) {
-						int l = 0, permissions_count = config_setting_length(inherited_group->permissions);
-						for (l = 0; l < permissions_count; ++l)
-							config_setting_copy(permissions, config_setting_get_elem(inherited_group->permissions, l));
-					}
+	if( this->nodeExists( node, "Inherit" ) ){
+		const auto& inherits = node["Inherit"];
+
+		auto& inheritanceVector = this->inheritance[groupId];
+
+		for( const auto& it : inherits ){
+			std::string inherit;
+
+			c4::from_chars( it.key(), &inherit );
+
+			bool enable;
 
-					++done; // copied commands and permissions from one of inherited groups
+			if( !this->asBool( inherits, inherit, enable ) ){
+				return 0;
+			}
+
+			util::tolower( inherit );
+
+			if( enable ){
+				if( !util::vector_exists( inheritanceVector, inherit ) ){
+					inheritanceVector.push_back( inherit );
 				}
-				
-				if (done == inherit_count) { // copied commands from all of inherited groups
-					++i;
-					group_settings->inheritance_done = true; // we're done with this group
+			}else{
+				if( !util::vector_exists( inheritanceVector, inherit ) ){
+					this->invalidWarning( it, "Trying to remove inheritance of non-inherited group %s\n", inherit.c_str() );
+					return 0;
 				}
+
+				util::vector_erase_if_exists( inheritanceVector, inherit );
 			}
-			dbi_destroy(iter);
+		}
+	}
+
+	if( !exists ){
+		this->put( groupId, group );
+	}
+
+	return 1;
+}
+
+void PlayerGroupDatabase::loadingFinished(){
+	static const int MAX_CYCLES = 10;
+	int i;
+
+	for( i = 0; i < MAX_CYCLES; i++ ){
+		auto inheritanceIt = this->inheritance.begin();
 
-			if (++loop > group_count) {
-				ShowWarning("pc_groups:read_config: Could not process inheritance rules, check your config '%s' for cycles...\n",
-				            config_filename);
-				break;
+		while( inheritanceIt != this->inheritance.end() ){
+			auto& entry = *inheritanceIt;
+
+			if( entry.second.empty() ){
+				inheritanceIt = this->inheritance.erase( inheritanceIt );
+				continue;
 			}
-		} // while(i < group_count)
 
-		// Pack permissions into GroupSettings.e_permissions for faster checking
-		iter = db_iterator(pc_group_db);
-		for (group_settings = (GroupSettings *)dbi_first(iter); dbi_exists(iter); group_settings = (GroupSettings *)dbi_next(iter)) {
-			config_setting_t *permissions = group_settings->permissions;
-			int c, count = config_setting_length(permissions);
+			std::shared_ptr<s_player_group> group = this->find( entry.first );
+
+			auto it = entry.second.begin();
+
+			while( it != entry.second.end() ){
+				std::string& otherName = *it;
+				bool found = false;
+				bool inherited = false;
+
+				for( const auto& it : *this ){
+					std::shared_ptr<s_player_group> otherGroup = it.second;
+					// Copy the string
+					std::string otherGroupName = otherGroup->name;
+
+					util::tolower( otherGroupName );
 
-			for (c = 0; c < count; ++c) {
-				config_setting_t *perm = config_setting_get_elem(permissions, c);
-				const char *name = config_setting_name(perm);
-				int val = config_setting_get_bool(perm);
-				int j;
+					if( otherName == otherGroupName ){
+						found = true;
 
-				if (val == 0) // does not have this permission
+						auto* otherGroupInheritance = util::map_find( this->inheritance, otherGroup->id );
+
+						if( otherGroupInheritance != nullptr && !otherGroupInheritance->empty() ){
+							// Try it again in the next cycle
+							break;
+						}
+
+						// Inherit atcommands
+						for( auto& command : otherGroup->commands ){
+							if( !util::vector_exists( group->commands, command ) ){
+								group->commands.push_back( command );
+							}
+						}
+
+						// Inherit charcommands
+						for( auto& command : otherGroup->char_commands ){
+							if( !util::vector_exists( group->char_commands, command ) ){
+								group->char_commands.push_back( command );
+							}
+						}
+
+						// Inherit permissions
+						group->permissions |= otherGroup->permissions;
+
+						inherited = true;
+						break;
+					}
+				}
+
+				if( inherited ){
+					it = entry.second.erase( it );
+					continue;
+				}else if( !found ){
+					ShowError( "Inherited group \"%s\" for group id %u does not exist.\n", otherName.c_str(), group->id );
+					it = entry.second.erase( it );
 					continue;
-				ARR_FIND(0, ARRAYLENGTH(pc_g_permission_name), j, strcmp(pc_g_permission_name[j].name, name) == 0);
-				group_settings->e_permissions |= pc_g_permission_name[j].permission;
+				}else{
+					it++;
+				}
 			}
 		}
-		dbi_destroy(iter);
+
+		if( this->inheritance.empty() ){
+			break;
+		}
 	}
 
-	ShowStatus("Done reading '" CL_WHITE "%d" CL_RESET "' groups in '" CL_WHITE "%s" CL_RESET "'.\n", group_count, config_filename);
+	if( i == MAX_CYCLES && !this->inheritance.empty() ){
+		ShowError( "Could not process inheritance rules, check your config for cycles...\n" );
+	}
 
-	
-	if( ( pc_group_max = group_count ) ) {
-		DBIterator *iter = db_iterator(pc_group_db);
-		GroupSettings *group_settings = NULL;
-		int* group_ids = (int*)aMalloc( pc_group_max * sizeof(int) );
-		int i = 0;
-		for (group_settings = (GroupSettings *)dbi_first(iter); dbi_exists(iter); group_settings = (GroupSettings *)dbi_next(iter)) {
-			group_ids[i++] = group_settings->id;
-		}
-		
-		atcommand_db_load_groups(group_ids);
-		
-		aFree(group_ids);
-		
-		dbi_destroy(iter);
+	// Not needed anymore
+	this->inheritance.clear();
+
+	uint32 index = 0;
+	for( auto& it : *this ){
+		it.second->index = index++;
 	}
-}
 
-/**
- * Removes group configuration from memory.
- * @private
- */
-static void destroy_config(void)
-{
-	config_destroy(&pc_group_config);
+	// Initialize command cache
+	atcommand_db_load_groups();
 }
 
-/**
- * In group configuration file, setting for each command is either
- * <commandname> : <bool> (only atcommand), or
- * <commandname> : [ <bool>, <bool> ] ([ atcommand, charcommand ])
- * Maps AtCommandType enums to indexes of <commandname> value array,
- * COMMAND_ATCOMMAND (1) being index 0, COMMAND_CHARCOMMAND (2) being index 1.
- * @private
- */
-static inline int AtCommandType2idx(AtCommandType type) { return (type-1); }
+PlayerGroupDatabase player_group_db;
 
 /**
  * Checks if player group can use @/#command
- * @param group_id ID of the group
  * @param command Command name without @/# and params
  * @param type enum AtCommanndType { COMMAND_ATCOMMAND = 1, COMMAND_CHARCOMMAND = 2 }
  */
-bool pc_group_can_use_command(int group_id, const char *command, AtCommandType type)
-{
-	int result = 0;
-	config_setting_t *commands = NULL;
-	GroupSettings *group = NULL;
-
-	if (pc_group_has_permission(group_id, PC_PERM_USE_ALL_COMMANDS))
+bool s_player_group::can_use_command( const std::string& command, AtCommandType type ){
+	if( this->has_permission( PC_PERM_USE_ALL_COMMANDS ) ){
 		return true;
+	}
 
-	if ((group = id2group(group_id)) == NULL)
-		return false;
-
-	commands = group->commands;
-	if (commands != NULL) {
-		config_setting_t *cmd = NULL;
-		
-		// <commandname> : <bool> (only atcommand)
-		if (type == COMMAND_ATCOMMAND && config_setting_lookup_bool(commands, command, &result))
-			return result != 0;
-
-		// <commandname> : [ <bool>, <bool> ] ([ atcommand, charcommand ])
-		if ((cmd = config_setting_get_member(commands, command)) != NULL &&
-		    config_setting_is_aggregate(cmd) && config_setting_length(cmd) == 2)
-			return config_setting_get_bool_elem(cmd, AtCommandType2idx(type)) != 0;
+	std::string command_lower = command;
+
+	util::tolower( command_lower );
+
+	switch( type ){
+		case COMMAND_ATCOMMAND:
+			return util::vector_exists( this->commands, command_lower );
+		case COMMAND_CHARCOMMAND:
+			return util::vector_exists( this->char_commands, command_lower );
 	}
+
 	return false;
 }
+
 /**
  * Load permission for player based on group id
  * @param sd Player
  */
 void pc_group_pc_load(struct map_session_data * sd) {
-	GroupSettings *group = NULL;
-	if ((group = id2group(sd->group_id)) == NULL) {
+	std::shared_ptr<s_player_group> group = player_group_db.find( sd->group_id );
+
+	if( group == nullptr ){
 		ShowWarning("pc_group_pc_load: %s (AID:%d) logged in with unknown group id (%d)! kicking...\n",
 					sd->status.name,
 					sd->status.account_id,
@@ -357,108 +347,50 @@ void pc_group_pc_load(struct map_session_data * sd) {
 		set_eof(sd->fd);
 		return;
 	}
-	sd->permissions = group->e_permissions;
-	sd->group_pos = group->group_pos;
-	sd->group_level = group->level;
+
+	sd->group = group;
+	sd->permissions = group->permissions;
 }
+
 /**
  * Checks if player group has a permission
- * @param group_id ID of the group
  * @param permission permission to check
  */
-bool pc_group_has_permission(int group_id, int permission)
-{
-	GroupSettings *group = NULL;
-	return (group = id2group(group_id)) == NULL ? false : (group->e_permissions&permission) != 0;
+bool s_player_group::has_permission( e_pc_permission permission ){
+	return this->permissions.test( permission );
 }
 
 /**
  * Checks commands used by player group should be logged
- * @param group_id ID of the group
  */
-bool pc_group_should_log_commands(int group_id)
-{
-	GroupSettings *group = NULL;
-	return (group = id2group(group_id)) == NULL ? false : group->log_commands;
+bool s_player_group::should_log_commands(){
+	return this->log_commands;
 }
 
 /**
- * Checks if player group with given ID exists.
- * @param group_id group id
- * @returns true if group exists, false otherwise
+ * Initialize PC Groups and read config.
  */
-bool pc_group_exists(int group_id)
-{
-	return idb_exists(pc_group_db, group_id);
+void do_init_pc_groups( void ){
+	player_group_db.load();
 }
 
 /**
- * Group ID -> group name lookup. Used only in @who atcommands.
- * @param group_id group id
- * @return group name
- * @public
+ * Finalize PC Groups
  */
-const char* pc_group_id2name(int group_id)
-{
-	GroupSettings *group = id2group(group_id);
-	if (group == NULL)
-		return "Non-existent group!";
-	return group->name;
-}
-
-/**
- * Group ID -> group level lookup. A way to provide backward compatibility with GM level system.
- * @param group id
- * @return group level
- * @public
- */
-int pc_group_id2level(int group_id)
-{
-	GroupSettings *group = id2group(group_id);
-	if (group == NULL)
-		return 0;
-	return group->level;
-}
-
-/**
- * Initialize PC Groups: allocate DBMaps and read config.
- * @public
- */
-void do_init_pc_groups(void)
-{
-	pc_group_db = idb_alloc(DB_OPT_RELEASE_DATA);
-	pc_groupname_db = stridb_alloc(DB_OPT_DUP_KEY, 0);
-	read_config();
-}
-
-/**
- * Finalize PC Groups: free DBMaps and config.
- * @public
- */
-void do_final_pc_groups(void)
-{
-	if (pc_group_db != NULL)
-		db_destroy(pc_group_db);
-	if (pc_groupname_db != NULL )
-		db_destroy(pc_groupname_db);
-	destroy_config();
+void do_final_pc_groups( void ){
+	player_group_db.clear();
 }
 
 /**
  * Reload PC Groups
  * Used in @reloadatcommand
- * @public
  */
-void pc_groups_reload(void) {
-	struct map_session_data* sd = NULL;
-	struct s_mapiterator* iter;
-
-	do_final_pc_groups();
-	do_init_pc_groups();
+void pc_groups_reload( void ){
+	player_group_db.reload();
 	
 	/* refresh online users permissions */
-	iter = mapit_getallusers();
-	for (sd = (TBL_PC*)mapit_first(iter); mapit_exists(iter); sd = (TBL_PC*)mapit_next(iter))	{
+	struct s_mapiterator* iter = mapit_getallusers();
+	for( struct map_session_data* sd = (struct map_session_data*)mapit_first(iter); mapit_exists(iter); sd = (struct map_session_data*)mapit_next(iter) ){
 		pc_group_pc_load(sd);
 	}
 	mapit_free(iter);

+ 69 - 41
src/map/pc_groups.hpp

@@ -4,62 +4,58 @@
 #ifndef PC_GROUPS_HPP
 #define PC_GROUPS_HPP
 
+#include <bitset>
+#include <map>
+#include <vector>
+
 #include "../common/cbasetypes.hpp"
+#include "../common/database.hpp"
 
+// Forward declaration from atcommands.hpp
 enum AtCommandType : uint8;
 
-extern int pc_group_max;
-
-bool pc_group_exists(int group_id);
-bool pc_group_can_use_command(int group_id, const char *command, AtCommandType type);
-bool pc_group_has_permission(int group_id, int permission);
-bool pc_group_should_log_commands(int group_id);
-const char* pc_group_id2name(int group_id);
-int pc_group_id2level(int group_id);
 void pc_group_pc_load(struct map_session_data *sd);
-
 void do_init_pc_groups(void);
 void do_final_pc_groups(void);
 void pc_groups_reload(void);
 
 enum e_pc_permission : uint32 {
-	PC_PERM_NONE                = 0,
-	PC_PERM_TRADE               = 0x00000001,
-	PC_PERM_PARTY               = 0x00000002,
-	PC_PERM_ALL_SKILL           = 0x00000004,
-	PC_PERM_USE_ALL_EQUIPMENT   = 0x00000008,
-	PC_PERM_SKILL_UNCONDITIONAL = 0x00000010,
-	PC_PERM_JOIN_ALL_CHAT       = 0x00000020,
-	PC_PERM_NO_CHAT_KICK        = 0x00000040,
-	PC_PERM_HIDE_SESSION        = 0x00000080,
-	PC_PERM_WHO_DISPLAY_AID     = 0x00000100,
-	PC_PERM_RECEIVE_HACK_INFO   = 0x00000200,
-	PC_PERM_WARP_ANYWHERE       = 0x00000400,
-	PC_PERM_VIEW_HPMETER        = 0x00000800,
-	PC_PERM_VIEW_EQUIPMENT      = 0x00001000,
-	PC_PERM_USE_CHECK           = 0x00002000,
-	PC_PERM_USE_CHANGEMAPTYPE   = 0x00004000,
-	PC_PERM_USE_ALL_COMMANDS    = 0x00008000,
-	PC_PERM_RECEIVE_REQUESTS    = 0x00010000,
-	PC_PERM_SHOW_BOSS           = 0x00020000,
-	PC_PERM_DISABLE_PVM         = 0x00040000,
-	PC_PERM_DISABLE_PVP         = 0x00080000,
-	PC_PERM_DISABLE_CMD_DEAD    = 0x00100000,
-	PC_PERM_CHANNEL_ADMIN       = 0x00200000,
-	PC_PERM_TRADE_BOUNDED       = 0x00400000,
-	PC_PERM_ITEM_UNCONDITIONAL  = 0x00800000,
-	PC_PERM_ENABLE_COMMAND      = 0x01000000,
-	PC_PERM_BYPASS_STAT_ONCLONE = 0x02000000,
-	PC_PERM_BYPASS_MAX_STAT     = 0x04000000,
-	PC_PERM_ATTENDANCE          = 0x08000000,
+	PC_PERM_TRADE = 0,
+	PC_PERM_PARTY,
+	PC_PERM_ALL_SKILL,
+	PC_PERM_USE_ALL_EQUIPMENT,
+	PC_PERM_SKILL_UNCONDITIONAL,
+	PC_PERM_JOIN_ALL_CHAT,
+	PC_PERM_NO_CHAT_KICK,
+	PC_PERM_HIDE_SESSION,
+	PC_PERM_WHO_DISPLAY_AID,
+	PC_PERM_RECEIVE_HACK_INFO,
+	PC_PERM_WARP_ANYWHERE,
+	PC_PERM_VIEW_HPMETER,
+	PC_PERM_VIEW_EQUIPMENT,
+	PC_PERM_USE_CHECK,
+	PC_PERM_USE_CHANGEMAPTYPE,
+	PC_PERM_USE_ALL_COMMANDS,
+	PC_PERM_RECEIVE_REQUESTS,
+	PC_PERM_SHOW_BOSS,
+	PC_PERM_DISABLE_PVM,
+	PC_PERM_DISABLE_PVP,
+	PC_PERM_DISABLE_CMD_DEAD,
+	PC_PERM_CHANNEL_ADMIN,
+	PC_PERM_TRADE_BOUNDED,
+	PC_PERM_ITEM_UNCONDITIONAL,
+	PC_PERM_ENABLE_COMMAND,
+	PC_PERM_BYPASS_STAT_ONCLONE,
+	PC_PERM_BYPASS_MAX_STAT,
+	PC_PERM_ATTENDANCE,
 	//.. add other here
-	PC_PERM_ALLPERMISSION       = 0xFFFFFFFF,
+	PC_PERM_MAX,
 };
 
 static const struct s_pcg_permission_name {
 	const char *name;
 	enum e_pc_permission permission;
-} pc_g_permission_name[] = {
+} pc_g_permission_name[PC_PERM_MAX] = {
 	{ "can_trade", PC_PERM_TRADE },
 	{ "can_party", PC_PERM_PARTY },
 	{ "all_skill", PC_PERM_ALL_SKILL },
@@ -88,7 +84,39 @@ static const struct s_pcg_permission_name {
 	{ "bypass_stat_onclone",PC_PERM_BYPASS_STAT_ONCLONE },
 	{ "bypass_max_stat",PC_PERM_BYPASS_MAX_STAT },
 	{ "attendance",PC_PERM_ATTENDANCE },
-	{ "all_permission", PC_PERM_ALLPERMISSION },
 };
 
+struct s_player_group{
+	uint32 id;
+	std::string name;
+	uint32 level;
+	bool log_commands;
+	std::vector<std::string> commands;
+	std::vector<std::string> char_commands;
+	std::bitset<PC_PERM_MAX> permissions;
+	uint32 index;
+
+public:
+	bool can_use_command( const std::string& command, AtCommandType type );
+	bool has_permission( e_pc_permission permission );
+	bool should_log_commands();
+};
+
+class PlayerGroupDatabase : public TypesafeYamlDatabase<uint32, s_player_group>{
+private:
+	std::map<uint32, std::vector<std::string>> inheritance;
+	bool parseCommands( const ryml::NodeRef& node, std::vector<std::string>& commands );
+
+public:
+	PlayerGroupDatabase() : TypesafeYamlDatabase( "PLAYER_GROUP_DB", 1 ){
+
+	}
+
+	const std::string getDefaultLocation() override;
+	uint64 parseBodyNode( const ryml::NodeRef& node ) override;
+	void loadingFinished() override;
+};
+
+extern PlayerGroupDatabase player_group_db;
+
 #endif /* PC_GROUPS_HPP */

+ 6 - 6
src/map/script.cpp

@@ -5833,7 +5833,7 @@ BUILDIN_FUNC(warpparty)
 			}
 			// Fall through
 		case WARPPARTY_RANDOMALLAREA:
-			if(!mapdata->flag[MF_NORETURN] && !mapdata->flag[MF_NOWARP] && pc_job_can_entermap((enum e_job)pl_sd->status.class_, m, pl_sd->group_level)){
+			if(!mapdata->flag[MF_NORETURN] && !mapdata->flag[MF_NOWARP] && pc_job_can_entermap((enum e_job)pl_sd->status.class_, m, pc_get_group_level(pl_sd))){
 				if (rx || ry) {
 					int x1 = x + rx, y1 = y + ry,
 						x0 = x - rx, y0 = y - ry,
@@ -5853,7 +5853,7 @@ BUILDIN_FUNC(warpparty)
 
 				ret = pc_setpos(pl_sd, mapindex, x, y, CLR_TELEPORT);
 			}
-		break;
+			break;
 		}
 
 		if( ret != SETPOS_OK ) {
@@ -5929,7 +5929,7 @@ BUILDIN_FUNC(warpguild)
 				pc_setpos(pl_sd,sd->status.save_point.map,sd->status.save_point.x,sd->status.save_point.y,CLR_TELEPORT);
 		break;
 		case 3: // m,x,y
-			if(!map_getmapflag(pl_sd->bl.m, MF_NORETURN) && !map_getmapflag(pl_sd->bl.m, MF_NOWARP) && pc_job_can_entermap((enum e_job)pl_sd->status.class_, m, pl_sd->group_level))
+			if(!map_getmapflag(pl_sd->bl.m, MF_NORETURN) && !map_getmapflag(pl_sd->bl.m, MF_NOWARP) && pc_job_can_entermap((enum e_job)pl_sd->status.class_, m, pc_get_group_level(pl_sd)))
 				pc_setpos(pl_sd,mapindex,x,y,CLR_TELEPORT);
 		break;
 		}
@@ -15205,8 +15205,8 @@ int atcommand_sub(struct script_state* st,int type) {
 		}
 
 		// Init Group ID, Level, & permissions
-		sd->group_id = sd->group_level = 99;
-		sd->permissions |= PC_PERM_ALLPERMISSION;
+		sd->group_id = 99;
+		pc_group_pc_load( sd );
 	}
 
 	if (!is_atcommand(fd, sd, cmd, type)) {
@@ -24100,7 +24100,7 @@ BUILDIN_FUNC(jobcanentermap) {
 		jobid = sd->status.class_;
 	}
 
-	script_pushint(st, pc_job_can_entermap((enum e_job)jobid, m, sd ? sd->group_level : 0));
+	script_pushint(st, pc_job_can_entermap((enum e_job)jobid, m, sd ? pc_get_group_level(sd) : 0));
 	return SCRIPT_CMD_SUCCESS;
 }
 

+ 4 - 4
src/map/skill.cpp

@@ -10100,7 +10100,7 @@ int skill_castend_nodamage_id (struct block_list *src, struct block_list *bl, ui
 				if ((dstsd = g->member[i].sd) != NULL && sd != dstsd && !dstsd->state.autotrade && !pc_isdead(dstsd)) {
 					if (map_getmapflag(dstsd->bl.m, MF_NOWARP) && !map_flag_gvg2(dstsd->bl.m))
 						continue;
-					if (!pc_job_can_entermap((enum e_job)dstsd->status.class_, src->m, dstsd->group_level))
+					if (!pc_job_can_entermap((enum e_job)dstsd->status.class_, src->m, pc_get_group_level(dstsd)))
 						continue;
 					if(map_getcell(src->m,src->x+dx[j],src->y+dy[j],CELL_CHKNOREACH))
 						dx[j] = dy[j] = 0;
@@ -14990,7 +14990,7 @@ static int skill_unit_onplace(struct skill_unit *unit, struct block_list *bl, t_
 
 					sg->val1 = (count<<16)|working;
 
-					if (pc_job_can_entermap((enum e_job)sd->status.class_, map_mapindex2mapid(m), sd->group_level))
+					if (pc_job_can_entermap((enum e_job)sd->status.class_, map_mapindex2mapid(m), pc_get_group_level(sd)))
 						pc_setpos(sd,m,x,y,CLR_TELEPORT);
 				}
 			} else if(bl->type == BL_MOB && battle_config.mob_warp&2) {
@@ -20542,13 +20542,13 @@ static int skill_unit_timer_sub(DBKey key, DBData *data, va_list ap)
 				if(group->val1) {
 					sd = map_charid2sd(group->val1);
 					group->val1 = 0;
-					if (sd && !map_getmapflag(sd->bl.m, MF_NOWARP) && pc_job_can_entermap((enum e_job)sd->status.class_, unit->bl.m, sd->group_level))
+					if (sd && !map_getmapflag(sd->bl.m, MF_NOWARP) && pc_job_can_entermap((enum e_job)sd->status.class_, unit->bl.m, pc_get_group_level(sd)))
 						pc_setpos(sd,map_id2index(unit->bl.m),unit->bl.x,unit->bl.y,CLR_TELEPORT);
 				}
 				if(group->val2) {
 					sd = map_charid2sd(group->val2);
 					group->val2 = 0;
-					if (sd && !map_getmapflag(sd->bl.m, MF_NOWARP) && pc_job_can_entermap((enum e_job)sd->status.class_, unit->bl.m, sd->group_level))
+					if (sd && !map_getmapflag(sd->bl.m, MF_NOWARP) && pc_job_can_entermap((enum e_job)sd->status.class_, unit->bl.m, pc_get_group_level(sd)))
 						pc_setpos(sd,map_id2index(unit->bl.m),unit->bl.x,unit->bl.y,CLR_TELEPORT);
 				}
 				skill_delunit(unit);