Jump to content

PACI

Developer
  • Posts

    402
  • Joined

  • Days Won

    18
  • Feedback

    0%

Posts posted by PACI

  1. 13 minutes ago, Deso said:

     Here is my slightly refactored version.. 🤣

     

    #include <unordered_map>
    
    // Add a list of maps and their corresponding max distance values to a map.
    // This allows us to look up the max distance value for a given map more efficiently than using a series of if statements.
    std::unordered_map<std::string, float> mapMaxDistances = {
        {"metin2_map_n_flame_dragon", 6000.0f},
        {"metin2_12zi_stage", 5000.0f},
        {"metin2_map_defensewave", 5000.0f},
        {"metin2_map_miniboss_01", 5000.0f},
        {"metin2_map_miniboss_02", 5000.0f},
        {"metin2_map_mists_of_island", 5000.0f}
    };
    
    PyObject* appSetCameraMaxDistance(PyObject* poSelf, PyObject* poArgs)
    {
        float fMax;
        if (!PyTuple_GetFloat(poArgs, 0, &fMax))
            return Py_BuildException();
    
        const std::string c_rstrMapFileName = CPythonBackground::Instance().GetWarpMapName();
    
        // Look up the max distance value for the current map in the mapMaxDistances map.
        // If the map is not found in the map, the max distance value will be the default value of 0.0f.
        fMax = mapMaxDistances[c_rstrMapFileName];
    
        CCamera::SetCameraMaxDistance(fMax);
        return Py_BuildNone();
    }

     

    Absolutely nonsense.
    What's the point of passing fMax to define the camera max distance if it's replaced by 0?

    • kekw 1
  2. You're confusing vnum (virtual number) with vid (virtual ID). That is why it does not work.

    The quest-function d.spawn_mob() returns, on successful spawn, the vid of the monster spawned, so you must store that value in a dungeon flag and use it in your npc.set_vid_attack_mul() and npc.set_vid_damage_mul() quest-functions.

    Now, what'd they do specifically:
    - npc.set_vid_attack_mul: alters the damage multiplier of the monster's hits. The higher the value, the higher the hit damage it will give.
    - npc.set_vid_damage_mul: this is another multiplier and, unlike what you may think, does not reduce the monster's damage, it weakens it by increasing the damage it takes.

    • Good 1
    • Love 1
  3. I think that is needless to say, but the whole boat sinks the moment you share accounts with other people. Everyone is responsible for whatever happens to their accounts, and the ownership of one is usually determined through the e-mail it is registered with.

    The server you registered in may take every single security measure for their users, but if an account is stolen or accessed by another person than not the owner, that only means the owner was careless for their account. And these situations often happen when sensible data such as IDs, passwords and e-mails are shared between one another.

    That said, it not only comes down to shady staff. Some users are simply stupid.
    Good reminder nevertheless.

    Note: To clarify when I say you I don't refer to the OP specifically.

  4. Hello everyone.

    Back in WoM2, at some point in time, there was this task we had to collect the locations of all monsters featured in hunting quests. One could possibly think it'd be an easy task, but I cannot imagine any of us going through all data from all quests and note down every single one of those locations.

    This was a particular issue for WoM2 since it ran with something we called "The Dynasties". The kingdoms or empires as we know them would have been removed, mainly due to very bad seasons with the player count. As such, the concept of Dynasty fit the game just well enough. For those who don't know, we got rid of the standard first maps and built a whole new one, making it one common first village for all dynasties.

    Now what kinda problems would this bring? I'd the say the hardest to wrap our heads on is adaptation.
    As a new map comes in, knowing the best spots, the best spawns and where to find certain enemies, come to play for a player about to explore the new area their favorite server brought for them. However, we're talking about adapting a completely new server start - which is a very big deal and not always a very good idea.

    As a server that possessed many risky concepts and quests of many different kinds, guiding the player through that painful and dislikeable start was a must. And on this case, to, at least, facilitate the search of the monsters the quest was telling the player to slay.

    This little python module I named make_monster_location.py, iterates over all the maps in the map folder, reads their regen, collects groups and groups of groups, and, ultimately, notes down every single location for every monster identified in said map. This is later saved the proper Lua multidimensional table, to be used by quest-functions show_mob_location() and clear_mob_location().

    Upon finishing, a Lua module named monster_location.lua is generated. This one must be loaded (through dofile()) in one of your bootload libraries (i.e: settings.luaquestlib.lua, ...).

    In the spoiler below there is a sample of the generated Lua file:

    Spoiler
    monster_location = {
    	-- metin2_map_a1
    	[1] = {
    		[101] = {{628, 781}, {822, 620}, {546, 413}, {475, 501}, {751, 311}},
    		[102] = {{670, 829}, {586, 722}, {405, 431}, {504, 347}, {780, 264}},
    		[103] = {{670, 829}, {586, 722}, {405, 431}, {504, 347}, {780, 264}},
    		[104] = {{564, 841}, {461, 649}, {894, 571}, {305, 396}, {821, 267}, {689, 197}},
    		[105] = {{214, 485}, {682, 1070}, {609, 993}, {869, 747}, {293, 384}, {408, 238}, {532, 167}},
    		[109] = {{214, 485}, {682, 1070}, {609, 993}, {869, 747}, {293, 384}, {408, 238}, {532, 167}},
    		[110] = {{806, 830}, {164, 598}, {238, 414}, {922, 325}, {338, 255}, {483, 179}, {589, 93}},
    		[106] = {{806, 830}, {164, 598}, {238, 414}, {922, 325}, {338, 255}, {483, 179}, {589, 93}},
    		[111] = {{806, 830}, {164, 598}, {238, 414}, {922, 325}, {338, 255}, {483, 179}, {589, 93}},
    		[107] = {{448, 1044}, {373, 961}, {861, 888}, {120, 551}, {206, 374}, {277, 210}, {732, 135}, {614, 65}},
    		[114] = {{448, 1044}, {373, 961}, {861, 888}, {120, 551}, {206, 374}, {277, 210}, {732, 135}, {614, 65}},
    		[112] = {{448, 1044}, {373, 961}, {861, 888}, {120, 551}, {206, 374}, {277, 210}, {732, 135}, {614, 65}},
    		[113] = {{923, 960}, {228, 832}, {136, 749}, {681, 75}},
    		[115] = {{923, 960}, {228, 832}, {136, 749}, {681, 75}},
    		[301] = {{131, 419}, {205, 255}, {275, 185}},
    		[302] = {{131, 419}, {205, 255}, {275, 185}},
    		[351] = {{131, 419}, {205, 255}, {275, 185}},
    		[352] = {{131, 419}, {205, 255}, {275, 185}},
    		[304] = {{136, 353}, {213, 195}, {290, 89}},
    		[303] = {{136, 353}, {213, 195}, {290, 89}},
    		[395] = {{136, 353}, {213, 195}, {290, 89}},
    		[396] = {{136, 353}, {213, 195}, {290, 89}},
    		[391] = {{247, 143}, {113, 67}},
    		[392] = {{247, 143}, {113, 67}},
    		[393] = {{247, 143}, {113, 67}},
    		[398] = {{247, 143}, {113, 67}},
    		[397] = {{247, 143}, {113, 67}},
    		[394] = {{334, 141}, {128, 383}},
    	},
    
    	-- metin2_map_capedragonhead
    	[301] = {
    		[3202] = {{1246, 525}, {1347, 411}, {1099, 338}, {983, 254}, {1174, 184}},
    		[3203] = {{1099, 338}, {983, 254}, {1174, 184}},
    		[3204] = {{1077, 395}, {974, 323}},
    		[3205] = {{1077, 395}, {974, 323}},
    		[3201] = {{1246, 525}, {1342, 273}, {970, 183}},
    		[3003] = {{996, 701}, {305, 631}, {862, 556}, {501, 486}, {665, 402}, {184, 305}, {430, 235}, {595, 163}},
    		[3004] = {{862, 556}, {501, 486}, {665, 402}, {184, 305}, {430, 235}, {595, 163}},
    		[3002] = {{783, 930}, {1076, 819}, {996, 701}, {305, 631}, {862, 556}, {501, 486}, {665, 402}, {184, 305}, {430, 235}, {595, 163}},
    		[3005] = {{862, 556}, {501, 486}, {665, 402}, {184, 305}, {430, 235}, {595, 163}},
    		[3501] = {{1035, 1221}, {965, 1080}, {881, 986}, {1110, 851}, {1356, 777}, {1205, 702}, {1281, 630}},
    		[3502] = {{1035, 1221}, {965, 1080}, {881, 986}, {1110, 851}, {1356, 777}, {1205, 702}, {1281, 630}},
    		[3503] = {{1196, 1180}, {1274, 933}, {1398, 792}},
    		[3504] = {{1294, 1221}, {1438, 1138}},
    		[3505] = {{1294, 1221}, {1438, 1138}},
    		[3001] = {{378, 1086}, {295, 927}, {783, 842}, {660, 770}, {1157, 656}, {1035, 571}},
    	},
    
    }
    
    function show_mob_location(mob_vnum, map_index, map_x, map_y)
    	if not monster_location[map_index] or not monster_location[map_index][mob_vnum] or pc.get_map_index() ~= map_index then
    		return
    	end
    
    	for _, location in ipairs(monster_location[map_index][mob_vnum]) do
    		addmapsignal(location[1]*100, location[2]*100)
    	end
    
    	setmapcenterposition(map_x or 200, map_y or 0)
    end
    
    function clear_mob_location()
    	clearmapsignal()
    	setskin(NOWINDOW)
    end
    

     

     

    I talked about two new quest-functions previously. Those are the ones you should use in your quests, each one of them works as you can possibly figure:

    • show_mob_location(mob_vnum, map_index, map_x, map_y) -- Displays the locations of the given monster, as long as the player is in the specified map.
    • clear_mob_location() -- Clears the signals inserted by the previous function call.

     

    And below, there is the module:

    Spoiler
    # Number of coordinates the application will be able to store for a given mob
    MAX_COORDINATES_NUM = 10
    
    # Defines a range between coordinates so that we don't face overlaps too often
    COORDINATES_RANGE = 70
    
    # List of maps we don't want to process
    MAPS_BLACKLIST = (
    	"metin2_map_monkey_dungeon_11",
    	"metin2_map_monkey_dungeon_12",
    	"metin2_map_monkey_dungeon_13",
    	"metin2_map_deviltower1",
    	"metin2_map_trent02",
    	"metin2_map_spiderdungeon_02",
    	"metin2_map_skipia_dungeon_01",
    	"metin2_map_skipia_dungeon_02",
    	"wom_map_citadel",
    	"metin2_map_spiderdungeon",
    	"metin2_map_monkey_dungeon2",
    	"metin2_map_monkey_dungeon3",
    	"metin2_map_sungzi_snow_pass01",
    	"metin2_map_sungzi_desert_hill_02",
    	"metin2_map_empirewar01",
    	"metin2_map_empirewar02",
    	"metin2_map_empirewar03",
    	"metin2_map_dd",
    	"metin2_map_n_flame_dungeon_01",
    	"metin2_map_n_flame_02",
    	"metin2_map_spiderdungeon_03",
    	"metin2_map_holyplace_flame"
    )
    
    # The application will read all of the regen files present on this tuple
    REGEN_FILES = ("regen.txt", )
    
    # Defines all the regen types existing in the game
    REGEN_TYPE_MOB			= ("m", "ma", "mc")
    REGEN_TYPE_GROUP		= ("g", "ga", "gc")
    REGEN_TYPE_GROUP_GROUP	= ("r", "ra", "rc")
    REGEN_TYPE_EXCEPTION	= ("e", )	# Ignored
    REGEN_TYPE_RANDOM		= ("s", )	# Ignored
    
    
    # Names of the group-related files
    GROUP_FILE_NAME = ("group.txt", "group_group.txt")
    
    class Map:
    
    	class Mob:
    		def __init__(self):
    			self.spawn_vnum = -1
    			self.coordinates = []
    
    		def SetVnum(self, vnum):
    			self.spawn_vnum = vnum
    
    		def SetCoordinates(self, x, y):
    			if len(self.coordinates) >= MAX_COORDINATES_NUM:
    				return
    
    			for (_x, _y) in self.coordinates:
    				# Too close!
    				if abs(x - _x) < COORDINATES_RANGE or abs(y - _y) < COORDINATES_RANGE:
    					return
    
    			self.coordinates.append((x, y))
    
    		def GetVnum(self):
    			return self.spawn_vnum
    
    		def PopCoordinates(self):
    			if len(self.coordinates):
    				return self.coordinates.pop()
    
    			return None
    
    	def __init__(self):
    		self.index = -1
    		self.name = "NoName"
    		self.path = None
    		self.regen = []
    		self.mob_groups = None
    		self.mob_group_groups = None
    
    	def SetIndex(self, index):
    		self.index = index
    
    	def SetName(self, name):
    		self.name = name
    
    	def GetIndex(self):
    		return self.index
    
    	def GetName(self):
    		return self.name
    
    	def SetPath(self, locale_path):
    		self.path = locale_path + self.name + "/"
    
    	def BindGroups(self, groups):
    		self.mob_groups = groups
    
    	def BindGroupGroups(self, group_groups):
    		self.mob_group_groups = group_groups
    
    	def GetGroup(self, vnum):
    		for group in self.mob_groups:
    			if group.GetVnum() == vnum:
    				return group
    
    		return None
    
    	def GetGroupGroups(self, vnum):
    		for group in self.mob_group_groups:
    			if group.GetVnum() == vnum:
    				return group
    
    		return None
    
    	def FindMob(self, vnum):
    		for mob in self.regen:
    			if vnum == mob.GetVnum():
    				return mob
    
    		return None
    
    	def AppendMob(self, vnum, x, y):
    		mob = self.FindMob(vnum)
    		if mob:
    			mob.SetCoordinates(x, y)
    			return
    
    		mob = self.Mob()
    		mob.SetVnum(vnum)
    		mob.SetCoordinates(x, y)
    		self.regen.append(mob)
    
    	def ParseRegen(self):
    		if not self.path:
    			print("ERROR: Map path not defined.")
    			return
    
    		# Here we will have all the regeneration entries of this map (stone + boss + regen)
    		full_regen = []
    		
    		# Get the contents
    		for regen_file in REGEN_FILES:
    			try:
    				with open(self.path + regen_file) as file:
    					for line in file:
    						# Ignore empty lines and comments
    						if not line or line.startswith("//"):
    							continue
    
    						# Append
    						full_regen.append(line.split())
    			except IOError:
    				print(">> WARNING: Could not find %s/%s. Ignoring..." % (self.name, regen_file))
    				continue
    		
    		# Now parse the result accordingly with the types provided
    		for regen_entry in full_regen:
    			if not regen_entry: # Nothing to do
    				continue
    
    			# Unpack all the relevant data
    			(regen_type, x, y, _, _, _, _, _, _, _, vnum) = regen_entry
    
    			# Convert everything
    			x = int(x)
    			y = int(y)
    			vnum = int(vnum)
    
    			# It's just a mob after all. Simply add it
    			if regen_type in REGEN_TYPE_MOB:
    				self.AppendMob(vnum, x, y)
    				continue
    
    			# It's a group! Get all the mobs that belong to this group and append them afterwards
    			if regen_type in REGEN_TYPE_GROUP:
    				group = self.GetGroup(vnum)
    				if group:
    					for member in group.GetMembers():
    						self.AppendMob(member, x, y)
    
    				continue
    
    			# Group of groups! Iterate over it and gather all the mobs of the groups attached to it individualy
    			if regen_type in REGEN_TYPE_GROUP_GROUP:
    				group_group = self.GetGroupGroups(vnum)
    				if group_group:
    					for gg_member in group_group.GetMembers():
    						group = self.GetGroup(gg_member)
    						if group:
    							for member in group.GetMembers():
    								self.AppendMob(member, x, y)
    
    	def GetRegen(self):
    		return self.regen
    
    class Group:
    	TYPE_GROUP = 0
    	TYPE_GROUP_GROUP = 1
    
    	def __init__(self):
    		self.vnum = -1
    		self.type = self.TYPE_GROUP
    		self.members = {}
    
    	def SetType(self, type):
    		self.type = type
    
    	def SetVnum(self, vnum):
    		self.vnum = vnum
    
    	def SetMember(self, index, vnum):
    		self.members[index] = vnum
    
    	def SetLeader(self, vnum):
    		self.SetMember(0, vnum)
    
    	def IsMember(self, vnum):
    		for index, mob in self.members.items():
    			if vnum == mob["vnum"]:
    				return True
    
    		return False
    
    	def GetVnum(self):
    		return self.vnum
    
    	def GetMembers(self):
    		return self.members.values()
    
    class Main:
    
    	def __init__(self, locale_path):
    		self.locale_path = locale_path
    		self.groups = []
    		self.group_groups = []
    		self.maps = []
    
    	def ReadGroupFile(self, type):
    		print("Loading %s/%s..." % (self.locale_path, GROUP_FILE_NAME[type]))
    
    		with open("%s/%s" % (self.locale_path, GROUP_FILE_NAME[type])) as file:
    			content = file.read()
    
    			while True:
    				group_start = content.find("{")
    				group_end = content.find("}")
    
    				# Finished everything!
    				if group_start == -1 or group_end == -1:
    					break
    
    				# We get the data based off of the positions of the brackets
    				group_data = content[group_start + 1 : group_end]
    				group_data = group_data.split()
    
    				# Now we get rid of them for the next read
    				content = content.replace("}", "", 1)
    				content = content.replace("{", "", 1)
    
    				# Attempt to process the received data
    				if not group_data:
    					continue
    
    				# Initialize our group
    				group = Group()
    				group.SetType(type)
    
    				# First thing to set is the group vnum
    				group_data.pop(0) # "Vnum"
    				group_vnum = int(group_data.pop(0)) # Actual numeric value corresponding to the vnum
    				group.SetVnum(group_vnum)
    
    				# Now get all the members
    				for i in range(0, len(group_data), 3):
    					member_index = group_data[i]
    					member_vnum = int(group_data[i + 1]) if type == Group.TYPE_GROUP_GROUP else int(group_data[i + 2])
    
    					if member_index == "Leader":
    						group.SetLeader(member_vnum)
    					else:
    						group.SetMember(int(member_index), member_vnum)
    
    				if type == Group.TYPE_GROUP:
    					self.groups.append(group)
    
    				elif type == Group.TYPE_GROUP_GROUP:
    					self.group_groups.append(group)
    
    				else:
    					print("ERROR: Unknown group type (%d)." % type)
    					break
    
    	def ReadMapIndexFile(self):
    		print("Loading maps...")
    
    		with open("%s/map/index" % self.locale_path) as file:
    			content = file.read().split()
    
    			for i in range(0, len(content), 2):
    				index = int(content[i])
    				map_name = content[i + 1]
    
    				# Ooops! We don't want you!
    				if map_name in MAPS_BLACKLIST:
    					continue
    
    				print("> Loading %s..." % map_name)
    
    				# Create the map instance
    				current_map = Map()
    				current_map.SetIndex(index)
    				current_map.SetName(map_name)
    				current_map.SetPath(self.locale_path + "/map/")
    				current_map.BindGroups(self.groups)
    				current_map.BindGroupGroups(self.group_groups)
    				current_map.ParseRegen()
    
    				# Store it
    				self.maps.append(current_map)
    
    	def BuildMobLocationFile(self):
    		print("Generating monster_location.lua...")
    
    		with open(self.locale_path + "/monster_location.lua", "w") as file:
    			file.write("monster_location = {\n") # Initialize the lua table
    
    			for map in self.maps:
    				regen = map.GetRegen()
    				if not regen: # Since the regen of this map is empty, it serves no purpose using it
    					continue
    
    				file.write("\t-- %s\n" % map.GetName()) # Map identifier
    				file.write("\t[%d] = {\n" % map.GetIndex()) # Initialize the current map table
    
    				for mob in regen: # Iterate over the regen of this map
    					file.write("\t\t[%d] = {" % mob.GetVnum()) # Now for our current mob
    					
    					# Add them locations!
    					coordinates = mob.PopCoordinates()
    					while coordinates:
    						file.write("{%d, %d}" % (coordinates[0], coordinates[1]))
    						coordinates = mob.PopCoordinates()
    						if coordinates:
    							file.write(", ")
    
    					file.write("},\n") # Close mob's table
    
    				file.write("\t},\n\n") # Close map's table
    
    			file.write("}\n\n") # Close the main table
    
    			# Finally, write the functions we are going to use in our quests
    			file.write(
    				"function show_mob_location(mob_vnum, map_index, map_x, map_y)\n"
    				"	if not monster_location[map_index] or not monster_location[map_index][mob_vnum] or pc.get_map_index() ~= map_index then\n"
    				"		return\n"
    				"	end\n\n"
    				"	for _, location in ipairs(monster_location[map_index][mob_vnum]) do\n"
    				"		addmapsignal(location[1]*100, location[2]*100)\n"
    				"	end\n\n"
    				"	setmapcenterposition(map_x or 200, map_y or 0)\n"
    				"end\n\n"
    				"function clear_mob_location()\n"
    				"	clearmapsignal()\n"
    				"	setskin(NOWINDOW)\n"
    				"end"
    			)
    
    		print("Done")
    
    main = Main(".")
    main.ReadGroupFile(Group.TYPE_GROUP)
    main.ReadGroupFile(Group.TYPE_GROUP_GROUP)
    main.ReadMapIndexFile()
    main.BuildMobLocationFile()
    
    

     

    As it follows one single file, I saw no purpose in uploading it - but if I see it fits, sure can do and edit the post later.

    All credits for the idea go to Shogun.

    • Metin2 Dev 7
    • Good 8
    • Love 10
  5. 27 minutes ago, Draveniou1 said:

     

    			case SCMD_QUIT:
    				if (d)
    				{
    					d->DelayedDisconnect(2);
    					d->ChatPacket(CHAT_TYPE_COMMAND, "quit");
    				}
    				else
    				{
    					d->SetPhase(PHASE_CLOSE); // If the player tries to make a bug then the player will eat kick :)
    				}
    				break;

     

    29b27ae93247fb3adebea19dc77ea962.gif

    • Lmao 10
  6. WoM2 received a closure letter written by Gameforge demanding the shutdown of the server. A week prior the official shutdown, the entire WoM community received a clear and honest notice about the situation. It did not close without an explanation. 

    • Good 2
×
×
  • Create New...

Important Information

Terms of Use / Privacy Policy / Guidelines / We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.