diff options
465 files changed, 254970 insertions, 0 deletions
diff --git a/char-server b/char-server Binary files differnew file mode 100644 index 0000000..ae28974 --- /dev/null +++ b/char-server diff --git a/conf/atcommand_athena.conf b/conf/atcommand_athena.conf new file mode 100644 index 0000000..b0711be --- /dev/null +++ b/conf/atcommand_athena.conf @@ -0,0 +1,629 @@ +// Athena atcommand Configuration file. +// Translated by Peter Kieser <pfak@telus.net> + +// Set here the symbol that you want to use for your commands +// Only 1 character is get (default is '@'). You can set any character, +// except control-character (0x00-0x1f), '%' (party chat speaking) and '/' (standard ragnarok GM commands) +// With default character, all commands begin by a '@': <example> @revive +command_symbol: @ + + +// Sets the level of the users that can use the GM commands. +// <command name>: level +// When battle_athena.conf has atcommand_gm_only set to no, +// normal players (gm level 0) can use GM commands if you set 0 to the command level. +// Max GM level is 99. If you want forbid a command to all people, set it with level 100. + +// Default values are set to define different GM levels like follow: +// 0: normal player +// -> no special advantage (only @time to know time and if at_command_gm_only is disabled) +// 1: Super player +// -> some (very) little advantages: storage, petrename, etc... +// 10: Super player+ +// -> same of Super player with !go (very super player) +// 20: Mediator +// -> it's a GM that only need to know people, and move to their to speak with them (they can access to any command about wisps) +// 40: Sub-GM +// -> This GM can help a GM, and can not create item or zeny or modify a character (can have some information commands) +// 50: Sub-GM+ +// -> This GM can change some non-important things on a character +// 60: GM +// -> can do almost anything (excep administration, and mass commands) +// GM is the first level where we can modify a character with important value, create items or create zenys +// 80: GM Chief +// -> can do anything, except administration commands +// 99: Administrator +// -> can do anything! + + +//-------------------------- +// 0: normal player commands + +// Give server time. (6 same commands) +time: 0 +date: 0 +server_date: 0 +serverdate: 0 +server_time: 0 +servertime: 0 + +// Display your ignore list (people from which you ignore wisps) +ignorelist: 0 + +// To change your (own) email (characters protection) +// note: this command doesn't check email itself, but check structure of the email (xxx@xxx) +// if you want be sure of each e-mail disable this option (value: 100) +email: 0 + +// To become GM (need password; password is set in login_athena.conf). +// special!: only a non-GM (player with gm level 0) need to have this command. +// if you change the value, be sure of what you do! +// To be able to create a gm with @gm, you must: +// - give a level to level_new_gm (parameter of login_athena.conf) (not 0) +// - enable to level 0 the @gm command (atcommand_athena.conf) (default 100) - Only level 0 can give access to this command +// - enable gm commands to normal player (battle_athena.conf, atcommand_gm_only parameter) +// - and normal player must give correct password when he use the @gm command (gm_pass paramter in login_athena.conf) +gm: 100 + + +//------------------------- +// 1: Super player commands + +// Suicide your character. +die: 1 + +// Enables you to rename your pet. +petrename: 1 + +party: 1 + +// Brings up your personal storage wherever you are. +storage: 1 + +// Locate someone on a map, returns your coordinates if the person isn't on. +where: 1 + + +//--------------------------- +// 10: Super player+ commands + +// Spawns you to set points in major cities. +go: 10 + + +//---------------------- +// 20: Mediator commands + +// Displays helpfile in Athena base directory (2 same commands). +help: 20 +h: 20 + +// Warp yourself to a person (3 same commands + /shift). +jumpto: 20 +goto: 20 +warpto: 20 + +// follow a player (including warping to them) +follow: 20 + +// Disconnects a user from the server (1 command + right click menu for GM "(name) force to quit"). +kick: 20 + +// Changes your apperance. +model: 20 + +// To get a peco to (un)ride +mountpeco: 20 + +// Returns list of logged in characters with their position (2 same commands). +who: 20 +whois: 20 + +// Returns list of logged in characters with their job. +who2: 20 + +// Returns list of logged in characters with their party/guild. +who3: 20 + +// Returns list of logged in characters with their position in a specifical map. +whomap: 20 + +// Returns list of logged in characters with their job in a specifical map. +whomap2: 20 + +// Returns list of logged in characters with their party/guild in a specifical map. +whomap3: 20 + +// Like @who+@who2+who3, but only for GM. +whogm: 20 + +// Change your appearence to other players to a mob. +disguise: 20 + +//Restore your normal appearance. +undisguise: 20 + +// Display ignore list of a player (people from which the player ignore wisps) +charignorelist: 20 + +// Enable all wispers for a player +inall: 20 + +// Disable all wispers for a player +exall: 20 + + +//-------------------- +// 40: Sub-GM commands + +// Broadcast to the whole server. Using (1 command + /nb, /b). +broadcast: 40 + +// Broadcast to the map you are on (1 command + /lb, /nlb). +local_broadcast: 40 + +// Broadcast (with or without name). +kami: 40 +kamib: 40 + +// Enables you to go to a certain map, at (x,y) coordinates. (@mapmove + /mm or /mapmove) +mapmove: 40 + +// Enables you to view other characters stats. +charstats: 40 + +// Shows Stats Of All Characters Online +charstatsall: 40 + +// Enables GVG on a map (2 same commands). +gvgon: 40 +gpvpon: 40 + +// Turns GVG (Guild v. Guild) off on a map (2 same commands). +gvgoff: 40 +gpvpoff: 40 + +// Heals a person to full HP/SP. +heal: 40 + +// GM Hide (enables you to be invisible to characters, and most monsters) (1 command + /hide). +hide: 40 + +// Changes your job to one you specify (2 same commands). +job: 40 +jobchange: 40 + +// Enables you to to jump randomly on a map (that you are already on). +jump: 40 + +// Warps you to your last save point (2 same commands). +return: 40 +load: 40 + +// Enables lost skills. +lostskill: 40 + +// Saves a warp point. +memo: 40 + +// Set your character display options. (Visual effects of your character) +option: 40 + +//Makes an egg +makeegg: 40 + +//Hatches an egg +hatch: 40 + +// Sets the level of intemecy of your pet. +petfriendly: 40 + +// Sets hunger level of your pet. +pethungry: 40 + +// Turns PVP (Person v. Person) off on a map. +pvpoff: 40 + +// Enables PVP on a map. +pvpon: 40 + +// Enables platinum skills. +questskill: 40 + +// Sets the speed you can walk/attack at. Default is 150. +speed: 40 + +// Enables spirit sphere balls. +spiritball: 40 + +// Warp yourself to a certain map, at (x,y) coordinates (2 same commands). +rura: 40 +warp: 40 + +// Changes GM clothes color (2 same commands) +dye: 40 +ccolor: 40 + +// Changes GM hair style (2 same commands) +hairstyle: 40 +hstyle: 40 + +// Changes GM hair color (2 same commands) +haircolor: 40 +hcolor: 40 + +// Deletes all your items. +itemreset: 40 + +// Kill all monsters in map (without drops) +killmonster2: 40 + +// Sets your spawn point (aka save point). +save: 40 + +// Do some visual effect on your character +effect: 40 + +// Display all items of a player +charitemlist: 40 + +// Display all items of a player's storage +charstoragelist: 40 + +// Display all items of a player's cart +charcartlist: 40 + +// drop all your items +dropall: 40 + +// store all your items +storeall: 40 + +// allow other players to hit you out of pvp +killable: 40 + +// look up a skill by name +skillid: 40 + +// use a skill by id +useskill: 40 + +// make another player killable +charkillable: 40 + +//--------------------- +// 50: Sub-GM+ commands + +// Changes character's model +charmodel: 50 + +guild: 50 + +// Brings up your guild storage wherever you are. +gstorage: 50 + +// Spawns a monster, and a certain amount (3 same commands + /monster). +spawn: 50 +monster: 50 +summon: 50 + +// Spawns a monster with parameters not in same order of @spawn. +monster2: 50 + +// To get a peco to (un)ride for another player. +charmountpeco: 50 + +// Enables to give possibility to a player to rename his/her pet. +charpetrename: 50 + + +//---------------- +// 60: GM commands + +// Starts Guild Wars +agitstart: 60 + +// Ends Guild Wars +agitend: 60 + +// Resurects yourself. +alive: 60 + +// Levels your character to specified level (adds to your level) (3 same commands). +lvup: 60 +baselvlup: 60 +blevel: 60 + +// Raises your job level (3 same commands). +joblvup: 60 +joblvlup: 60 +jlevel: 60 + +// Sets another persons base level. +charbaselvl: 60 + +// Changes the sex of an online player (all characters on the account) +charchangesex: 60 + +// Remove items from a character +chardelitem: 60 + +// Sets another persons job level. +charjlvl: 60 + +// Sets the job of another character (2 same commands). +charjob: 60 +charjobchange: 60 + +// Set options on another character. +charoption: 60 + +// Gives another character status points +charstpoint: 60 + +// Gives another character skill points +charskpoint: 60 + +// Resets another character's stats +charreset: 60 + +// Resets another character's status, skills +charstreset: 60 +charskreset: 60 + +// Saves the respawn point of another character. +charsave: 60 + +// Changes another character's zenny +charzeny: 60 + +// Levels your guild to specified level (2 same commands). +guildlvup: 60 +guildlvlup: 60 + +idsearch: 60 + +// Creates an item of your choosing, either Item ID or Name (1 command + /item). +item: 60 + +// Creates a complet item (card, etc...) of your choosing, either Item ID or Name. +item2: 60 + +// ?? +itemcheck: 60 + +// Kill another character without hitting them. +kill: 60 + +// Kill all monsters in map (with drops) +killmonster: 60 + +// Creates yourself a pet egg, have to use Pet ID. +makeegg: 60 + +// Instantly kills player whose name is entered and deals insane damage to everything around. +// Disabled for now +//nuke: 60 + +// Enable hitting a player even when not in pvp +killer: 60 + +// Creates weapon of desired element. +produce: 60 + +// Warps a character to you (1 command + /recall). +recall: 60 + +// Refines all weapons in your items list. +refine: 60 + +// Will repair all broken items in inventory. +repairall: 60 + +// Revives a character, and heals them. +revive: 60 + +// Warp another person to a certain map, at (x,y) coordinates (2 same commands). +rura+: 60 +charwarp: 60 + +// Change Status of your character +str: 60 +agi: 60 +vit: 60 +int: 60 +dex: 60 +luk: 60 + +// Gets all skills (4 same commands) +allskill: 60 +allskills: 60 +skillall: 60 +skillsall: 60 + +// sets GM stats to maximum (4 same commands) +statall: 60 +statsall: 60 +allstats: 60 +allstat: 60 + +// Gives you job points. +stpoint: 60 + +// Gives you skill points of desired amount. +skpoint: 60 + +// Warps all online character of a guild to you. (at least one member of that guild must be on.) +guildrecall: 60 + +// Warps all online character of a party to you. (at least one party member must be online.) +partyrecall: 60 + +// Allows you to spy on any Guilds Guild chat. (at least one member of that guild must be on.) +guildspy: 60 + +//Allows you to spy on any party's party chat. (at least one party member must be online.) +partyspy: 60 + +// Gives you money (zeny) of desired amount. +zeny: 60 + +// To block definitively a player (only administrator can unblock the account) (2 same commands) +block: 60 +charblock: 60 + +// To unblock a player (2 same commands) +unblock: 60 +charunblock: 60 + +// To ban a player for a limited time (only administrator can unban the account) (4 same commands) +ban: 60 +banish: 60 +charban: 60 +charbanish: 60 + +// To unban a player (4 same commands) +unban: 60 +unbanish: 60 +charunban: 60 +charunbanish: 60 + +// To send specified character in jails +jail: 60 + +// To discharge a prisoner (2 same commands) +unjail: 60 +discharge: 60 + +// To change disguise of another player/GM +chardisguise: 60 +charundisguise: 60 + +// Enables platinum skills of another player. +charquestskill: 60 + +// Enables lost skills of another player. +charlostskill: 60 + +// turn on and off skills on a map +skillon: 60 +skilloff: 60 + +// Create a static warp portal that lasts until the next reboot +addwarp: 60 + +// drop a players possessions on the gruond +chardropall: 60 + +// put a players possessions in storage +charstoreall: 60 + +//---------------------- +// 80: GM Chief commands + +// Set the map you are on to day. +day: 80 + +// Kills everyone on the server. +doom: 80 + +// Kills everyone on the map you are on. +doommap: 80 + +// Set the map you are currently on to night. +night: 80 + +// Recalls Everyone To Your Coordinates +recallall: 80 + +// Revives all players on the map. +raisemap: 80 + +// Revives all players on the server. +raise: 80 + +// Enables a NPC. +enablenpc: 80 + +// Disables a NPC. +disablenpc: 80 + +// Move a NPC +npcmove: 80 + +// turn skills on for a map +skillon: 80 + +// turn skills off for a map +skilloff: 80 + +// Unmute a player +unmute: 60 + +//--------------------------- +// 99: Administrator commands + +// Disconnect all users from the server +kickall: 99 + +// Closes Map-Server +mapexit: 99 + +// Give information about terrain/area (debug function) +gat: 99 + +// Enables debugging +packet: 99 + +// Shows information about the map +mapinfo: 99 + +// Re-load item database (admin command) +reloaditemdb: 99 + +// Re-load monsters database (admin command) +reloadmobdb: 99 + +// Re-load skills database (admin command) +reloadskilldb: 99 + +// Re-load scripts (admin command) +reloadscript: 99 + +// Re-load GM level (admin command) +reloadgmdb: 99 + +// Refresh only status of players - SQL Only +refreshonline: 99 + + +//--------------------------------------------------------------- +// 0: Mail System - SQL Only commands - Must be enabled + +// Check # of messages. +checkmail: 1 + +// List all messages. +listmail: 1 + +// List only new mail. +listnewmail: 1 + +// Read a message. +readmail: 1 + +// Send mail. +sendmail: 1 + +// Send priority mail (tagged with Priority and cannot be deleted until read) +sendprioritymail: 80 + +// Delete a message. +deletemail: 0 + + + +//--------------------- +// OTHER: not a command + +//import: conf/import/atcommand_conf.txt diff --git a/conf/battle_athena.conf b/conf/battle_athena.conf new file mode 100644 index 0000000..a272a60 --- /dev/null +++ b/conf/battle_athena.conf @@ -0,0 +1,711 @@ + +//-------------------------------------------------------------- +//eAthena Battle Configuration File +// Made in to plainer english by Ancyker +//-------------------------------------------------------------- +//Note 1: Directives can be set using on/off, yes/no or 1/0. +//Note 2: All rates are in percents, 100 would mean 100%, 200 +// would mean 200%, etc +// Other Information: +// 1000 miliseconds is 1 second. +//-------------------------------------------------------------- + +// Do you want to debug warp points? If set to yes, warp points will appear as flags.(Note 1) +warp_point_debug: no + +// When calculating critical, should we take in to account the enimies luck? (Note 1) +enemy_critical: yes + +// Enemy's Critical Rate (Note 2) +enemy_critical_rate: 100 + +// Are enemy attacks effected by their strength? (Note 1) +enemy_str: yes + +// Can enemies have perfect flee? (Note 1) +enemy_perfect_flee: no + +// The rate of time it takes to cast a spell (Note 2, 0 = No casting time) +casting_rate: 100 + +// Delay time after casting (Note 2) +delay_rate: 100 + +// Is the delay time is dependent on the caster's DEX? (Note 1) +delay_dependon_dex: yes + +// At what dex does the cast time become zero (instacast) +castrate_dex_scale: 150 + +// Is 'Skills add a delay before you can attack' enabled? (Note 1) +skill_delay_attack_enable: no + +// Whether or not cards and attributes in the left hand are enabled (Note 1) +left_cardfix_to_right: yes + +// Increase player's attack range (in cells) +player_skill_add_range: 0 + +// If the target moves out of range while casting, do we take the items and SP for the skill anyway? (Note 1) +skill_out_range_consume: no + +// Increase Monsters attack range +monster_skill_add_range: 0 + +// If a player is attacked, will they have a delay in being able to move? (Note 1) +// (Setting to no will be like always endure) +player_damage_delay: yes + +// Damaged delay rate (Note 2) +player_damage_delay_rate: 100 + +// Is a player's defense NOT dependant on an enemies attack? (Note 1) +defunit_not_enemy: yes + +// Are summoned monsters level greater then your base level? (I think this is for dead branches) (Note 1) +random_monster_checklv: yes + +// The maximum quantity of monsters that can be summoned per GM command (0 denotes an unlimited quantity) +atcommand_spawn_quantity_limit: 100 + +// Does HP recover if hit by an attribute that's same as your own? (Note 1) +attribute_recover: yes + +// If an item is droped, does it go stright into the users inventory? (Note 1) +item_auto_get: no + +// How long does it take for an item to disappear from the floor after it is dropped? (in miliseconds) +flooritem_lifetime: 60000 + +// How long before the first person who did the most damage to a monster can get the item? (in milliseconds) +item_first_get_time: 3000 + +// How long before the second person who did the second most damage to a monster can get the item? (in milliseconds) +// (It Adds Time From The First Persons Time) +// So, It Is Like First Person's Time + Second Person's Time = Time Before Second Person Can Get The Items +item_second_get_time: 1000 + +// How long before the third person who did the third most/least damage to a monster can get the item? (in milliseconds) +// (It Adds Time From The First Persons Time And Second Persons Time) +// So, It Is Like First Person's Time + Second Person's Time + Third Person's Time = Time Before Third Person Can Get The Items +item_third_get_time: 1000 + +// How long before the first person who did the most damage to a MVP can get the item? (in milliseconds) +mvp_item_first_get_time: 10000 + +// How long before the second person who did the second most damage to a MVP can get the item? (in milliseconds) +// (It Adds Time From The First Persons Time) +// So, It Is Like First Person's Time + Second Person's Time = Time Before Second Person Can Get The Items +mvp_item_second_get_time: 10000 + +// How long before the third person who did the third most/least damage to a MVP can get the item +//(Note the Amount is in Milliseconds and It Adds Time From The First Persons Time And Second Persons Time) +//So It Is Like First Person's Time + Second Person's Time + Third Person's Time = Time Before Third Person Can Get The Items +mvp_item_third_get_time: 2000 + +// Item drop rates (Note 2) + +// The rate the common items are droped (Items that are in the ETC tab, besides card) +item_rate_common: 100 +item_drop_common_min: 1 +item_drop_common_max: 10000 + +// The rate healing items are droped (items that restore HP or SP) +item_rate_heal: 100 +item_drop_heal_min: 1 +item_drop_heal_max: 10000 + +// The rate at which usable items (in the item tab) other then healing items are droped. +item_rate_use: 100 +item_drop_use_min: 1 +item_drop_use_max: 10000 + +// The rate at which equipment is droped. +item_rate_equip: 100 +item_drop_equip_min: 1 +item_drop_equip_max: 10000 + +// The rate at which cards are droped +item_rate_card: 100 +item_drop_card_min: 1 +item_drop_card_max: 10000 + +item_drop_mvp_min: 1 +item_drop_mvp_max: 10000 + +// Can the monster's drop rate become 0? (Note 1) +drop_rate0item: no + +// Rate at which exp. is given. (Note 2) +base_exp_rate: 100 + +// Rate at which job exp. is given. (Note 2) +job_exp_rate: 100 + +// PVP exp. Do players get exp during pvp +pvp_exp: yes + +// When a player dies, how should we penalize them? +// 0 = No penalty. +// 1 = Lose % of current level when killed. +// 2 = Lose % of total experience when killed. +death_penalty_type: 1 + +// Base exp. penalty rate (Each 100 is 1% of their exp) +death_penalty_base: 100 + +// Job exp. penalty rate (Each 100 is 1% of their exp) +death_penalty_job: 100 + +// When a player dies, how much zeny should we penalize them with? +zeny_penalty: 0 + +// The amount of HP a player will respawn with, 0 is default. +// (Unit is in percentage of total HP, 100 is full heal of HP, 0 is respawn with 1HP total.) +restart_hp_rate: 0 + +// The amount of SP a player will respawn with, 0 is default. +// (Unit is in percentage of total SP, 100 is full heal of SP, 0 is respawn with 1SP total.) +restart_sp_rate: 0 + +// [MVP] Summoned monsters HP rate, that is, monsters summoned by an MVP will have this much HP. (Note 2) +mvp_hp_rate: 100 + +// [MVP] Item drop rate, that is, the overall drop rate for items droped by an MVP. (Note 2) +mvp_item_rate: 100 + +// [MVP] Exp. rate. (Note 2) +mvp_exp_rate: 100 + +// The HP rate of normal monsters (that is monsters that are not MVP's) (Note 2) +monster_hp_rate: 100 + +// The maximum attack speed of a monster +monster_max_aspd: 199 + +// (@) GM Commands available only to GM's? (Note 1) +// set to 'No', Normal players (gm level 0) can use GM commands _IF_ you set the command level to 0. +// set to 'Yes', Normal players (gm level 0) can never use a GM command even if you set the command level to 0. +atcommand_gm_only: no + +// [GM] Can use all skills? (No or mimimum GM level) +gm_all_skill: no + +// [GM] Can use all abracadabra skills? (No minimum GM level) +gm_all_skill_add_abra: no + +// [GM] Can equip anything? (No or minimum GM level, can cause client errors.) +gm_all_equipment: no + +// [GM] Raise skills unconditionally, that is, put points in to a skill not in thier jobs skill tree? (no or minimum gm level) +gm_skill_unconditional: no + +// Can a normal player by-pass the skill tree? (Note 1) +player_skillfree: no + +// When doing a skill reset, whether the skill's restriction is to be ignored or not. (Note 1) +player_skillup_limit: yes + +// Forging success rate. (Note 2) +weapon_produce_rate: 100 + +// Prepare Potion succsss rate. (Note 2) +potion_produce_rate: 100 + +// Allow monsters to be aggresive and attack first? (Note 1) +monster_active_enable: yes + +// Monster damage delay rate (Note 1) +monster_damage_delay_rate: 100 + +// Looting monster actions. +// 0 = Monster will consume the item. +// 1 = Monster will not consume the item. +monster_loot_type: 0 + +// Enable monster skills? (Note 1) +mob_skill_use: yes + +// Rate of monsters on a map, 200 would be twice as many as normal. (Note 2) +mob_count_rate: 100 + +// Quest skills can be learned? (Note 1) +// Setting this to yes can open an exploit on your server! +quest_skill_learn: no + +// When skills are reset, quest skills are reset as well? (Note 1) +// Setting this to yes can open an exploit on your server! +quest_skill_reset: no + +// You must have basic skills to be able to sit, trade, form a party or create a chatroom? (Note 1) +basic_skill_check: yes + +// When making a guild, an Emperium is consumed? (Note 1) +guild_emperium_check: yes + +// Maximum tax limit on a guild member. +guild_exp_limit: 50 + +// Maximum castles one guild can own (0 - unlimited) +guild_max_castles: 0 + +// When teleporting, or spawning to a map, how long before a monster sees you if you don't move? (time is in milliseconds) +// That is, when you go to a map and don't move, how long before the monsters will notice you. +// If you attack a monster, it will attack you back regaurdless of this setting. (I think) +player_invincible_time: 5000 + +// [PET] Rate for catching pets (Note 2) +pet_catch_rate: 100 + +// [PET] Can you name a pet more then once? (Note 1) +pet_rename: no + +// [PET] The rate a pet will get friendly by feeding it. (Note 2) +pet_friendly_rate: 100 + +// [PET] The rate at which a pet will become hungry. (Note 2) +pet_hungry_delay_rate: 100 + +// [PET] If your pet is hungry by how much will the friendlyness decrease by. (Default is 5) +// Note: The friendlyness is 0-1000 total, at 0 the pet runs away. +pet_hungry_friendly_decrease: 5 + +// [PET] Does Pet's Attack Damage Based On Str (Note 1) +pet_str: yes + +// [PET] Whether or not the pet's will use skills. (Note 1) +pet_status_support: yes + +// [PET] Does the pet need its equipment before it does its skill? (Note 1) +pet_equip_required: yes + +// [PET] Will all pets attack? (Note 1) +// Do NOT use this with pet skills! +pet_attack_support: no + +// [PET] When the master receives damage from the monster, whether or not the pet attacks back. +pet_damage_support: no + +// [PET] Rate at which a pet will support it's owner in battle. (Note 2) +pet_support_rate: 100 + +// [PET] Does the pets owner receive exp from the pets damage? +pet_attack_exp_to_master: no + +// [PET] The rate exp. is gained from the pet attacking monsters +pet_attack_exp_rate: 100 + +// Will there be a minimum skill dmg even if there is a miss? +skill_min_damage: no + +// Which finger offensive style can be used? +// 0 = Aegis style +// 1 = Athena style +finger_offensive_type: 0 + +// The rate of job exp. from using Heal skill (100 is the same as the heal amount, 200 is double. +// The balance of the exp. rate is best used with 5 to 10) +heal_exp: 0 + +// The rate of exp. that is gained by the process of resurrection, a unit is 0.01%. +// Experience calculations for the experience value * level difference of the person revived / 100 * resurrection_exp/10000 which the revived player has can be got. +resurrection_exp: 0 + +// The rate of job exp. when using discount and overcharge on an NPC (100 is normal, 200 is double.) +// The way it is calculated is (money recieved * skill lv) * shop_exp / 100. +shop_exp: 0 + +// The delay rate of monk's combo (Note 2) +combo_delay_rate: 100 + +// Item check? (Note 1) +// When logged in or moving in map if the item the player is holding isn't correct there will be a check. +item_check: no + +// Will tuxedo and wedding dresses be shown when worn? (Note 1) +wedding_modifydisplay: yes + +// The time interval for HP to restore naturally. (in milliseconds) +natural_healhp_interval: 6000 + +// The time interval for SP to restore naturally. (in milliseconds) +natural_healsp_interval: 8000 + +// Automatic healing skill's time interval. (in milliseconds) +natural_heal_skill_interval: 10000 + +// The maximum weight for a character to carry when the character stops healing naturally. (in %) +natural_heal_weight_rate: 50 + +// Override item names from GRF file? (Note 1) +item_name_override_grffile: yes + +// Are arrows are consumed when used on a bow? (Note 1) +arrow_decrement: yes + +// Maximum atk speed. (Default is 190) +max_aspd: 190 + +// Maximum HP. (Default is 32500) +max_hp: 32500 + +// Maximum SP. (Default is 32500) +max_sp: 32500 + +// Maximum user LV to send to client +// (Default is 99.. Never go above 127) +// +// this is only useful if you have adjusted your client +// to expect levels higher then 99 +max_lv: 99 + +// Max limit of char stats. (agi, str, etc.) +max_parameter: 99 + +// Max weight carts can hold. +max_cart_weight: 8000 + +// Display player skill errors in console? (for debug only) (default: off) (Note 1) +player_skill_log: off + +// Display monster skill errors in console? (for debug only) (default: off) (Note 1) +monster_skill_log: off + +// Display battle log? (for debug only) (default: off) (Note 1) +battle_log: off + +// Display save log? (for debug only) (default: off) (Note 1) +save_log: off + +// Display errors? (for debug only) (default: off) (Note 1) +error_log: off + +// Display other stuff? (for debug only) (default: off) (Note 1) +etc_log: off + +// Save Clothes color. (This will degrade performance [in txt?]) (Note 1) +save_clothcolor: yes + +// Undead type differeniate. +// 0 = element undead +// 1 = race undead +// 2 = both +undead_detect_type: 2 + +// Operational mode of automatic counter. +// 0 = disregard DEF and HIT+20 . CRI*2, 1 = 100% critical +// Players +player_auto_counter_type: 0 +// Monsters +monster_auto_counter_type: 0 + +// Type of penalty that is applied to FLEE when more than agi_penaly_count monsters are targetting player +// 0 = no penalty is applied +// 1 = agi_penaly_num is reduced from FLEE as a % +// 2 = agi_penaly_num is reduced from FLEE as an exact amount +agi_penaly_type: 1 + +// Amount of enemies required to be targetting player before FLEE begins to be penalized +agi_penaly_count: 3 + +// Amount of FLEE penalized per each attacking monster more than agi_penaly_count +agi_penaly_num: 10 + +// Type of penalty that is applied to VIT defense when more than vit_penaly_count monsters are targetting player +// 0 = no penalty is applied +// 1 = vit_penaly_num is reduced from FLEE as a % +// 2 = vit_penaly_num is reduced from FLEE as an exact amount +vit_penaly_type: 1 + +// Amount of enemies required to be targetting player before VIT defense begins to be penalized +vit_penaly_count: 3 + +// Amount of VIT defense penalized per each attacking monster more than vit_penaly_count +vit_penaly_num: 5 + +// When the player attacks an object, the calculation method of DEF. +// With 0 this will be ignored specification, at 1 or more def = subtraction of (DEF* value). +player_defense_type: 0 + +// When the monster attacks an object, the calculation method of DEF. +// With 0 this will be ignored, at 1 or more def = subtraction of (DEF* value). +monster_defense_type: 0 + +// When the pet attacks an object, the calculation method of DEF. +// With 0 this will be ignored specification, at 1 or more def = subtraction of (DEF* value). +pet_defense_type: 0 + +//MDEFTame as above....(MDEF*value) +magic_defense_type: 0 + +// Whether or not, ground skills of the players' will stack. (Note 1) +player_skill_reiteration: no + +//Whether or not, ground skills of the monsters' will pile up. (Note 1) +monster_skill_reiteration: no + +//Whether or not ground based skills of a certain type such as traps can be cast straight onto other players. (Note 1) +player_skill_nofootset: yes + +//Whether or not ground based skills of a certain type such as traps can be cast straight onto monsters. (Note 1) +monster_skill_nofootset: yes + +// When a player is cloaking, Whether the wall is checked or not. (Note 1) +// Note: Gravity announced that they were changing cloaking so it would +// not need a wall, but this was never implemented. I set to no, for fun. +player_cloak_check_type: no + +// When a monster is cloaking, Whether the wall is checked or not. (Note 1) +monster_cloak_check_type: no + +// Melee damage adjustments for WoE battles (Guild Vs Guild) (Note 2) +gvg_short_attack_damage_rate: 100 + +// Ranged damage adjustments for WoE battles (Guild Vs Guild) (Note 2) +gvg_long_attack_damage_rate: 100 + +// Magic damage adjustments for WoE battles (Guild Vs Guild) (Note 2) +gvg_magic_attack_damage_rate: 100 + +// Misc damage adjustments for WoE battles (Guild Vs Guild) (Note 2) +gvg_misc_attack_damage_rate: 100 + +// When the empelium is broken with WoE mode on, How Long Before The Declaration Of Castle Owner +// and Removal of Monsters/Players from Castle. (in milliseconds) +gvg_eliminate_time: 7000 + +// Whether or not skill is used vis-a-vis the user at the time of MOB skill motion of condition skillused. (Note 1) +// Not sure what this means, something about monsters changing target? +mob_changetarget_byskill: no + +// Player's Direction Changed When Attacking? (Note 1) +player_attack_direction_change: yes + +// Monsters's Direction Changed When Attacking? (Note 1) +monster_attack_direction_change: yes + +// If the player has Undead Elemental Equipment, should they be frozen or not. (Note 1) +player_undead_nofreeze: no + +// Will Player Skills Stay Within Land Limit or not? (Note 1) +player_land_skill_limit: yes + +// Will Monster Skills Stay Within Land Limit or not? (Note 1) +monster_land_skill_limit: yes + +// If a party uses a skill with penalties do they apply? (Note 1) +party_skill_penaly: yes + +// If monster's class is changed will it fully recover HP and SP and Ailments? (Note 1) +monster_class_change_full_recover: no + +// Do produced items have the maker's name on them? (Note 1) +produce_item_name_input: yes + +// Do produced potions have the maker's name on them? (Note 1) +produce_potion_name_input: yes + +// Do crafted arrows have the maker's name on them? (Note 1) +making_arrow_name_input: yes + +// Does created holy water have the maker's name on it? (Note 1) +holywater_name_input: yes + +// Stop logout for 10 seconds after a hit? (Note 1) +prevent_logout: yes + +// If skill fails by delay, should it display or not. (Note 1) +display_delay_skill_fail: yes + +// Can a player in chat room (in-game), be warped by a warp portal? (Note 1) +chat_warpportal: no + +// Can a monster be warped by a warp portal? (Note 1) +mob_warpportal: no + +// Is a monster summoned via dead branch aggresive? (Note 1) +dead_branch_active: yes + +// The highest value at which an item can be sold via the merchant vend skill. (in zeny) +vending_max_value: 10000000 + +// If someone loots, show name in party? (Note 1) +show_steal_in_same_party: no + +// Allow upper class (Advanced 2nd Class)? (Note 1) +// Just leave this at yes +enable_upper_class: yes + +// Is a usual attack of a pet delivered withOUT an attribute? (Note 1) +pet_attack_attr_none: no + +// Is a usual attack of a player delivered withOUT an attribute? (Note 1) +pc_attack_attr_none: no + +// Is a usual attack of a monster delivered withOUT an attribute? (Note 1) +mob_attack_attr_none: no + +// mob attacks againsts players wearing ghostring armor do full damage +mob_ghostring_fix: no + +// Does the Golden Thief Bug card only work during pvp? +// no or 0 - gtb works all the time +// 1 - 100 - percentage of magic damage reduced only during pvp (or gvg) +gtb_pvp_only: no + +// How to count the number of the enemies who do an agi penalty... +// 1 or less: It is a count altogether. +// 2: Full evasion exclusion +// 3: Full evasion and evasion exclusion +// 4 or more: Except all. +agi_penaly_count_lv: 2 + +// How to count the number of the enemies who do a vit penalty +// 1 or less: It is a count altogether. +// 2: Full evasion exclusion +// 3: Full evasion and evasion exclusion +// Four or more: Except all. +vit_penaly_count_lv: 3 + +// Grandcross Settings (Dont mess with these) +// Even if MOB (PC) has overlapped, it HIT(s) compulsion 3. (Default no) +gx_allhit: no +// The effect of the arms card of a damage %UP system is set also to GX at -L effect. (default no) +gx_cardfix: no +// The attribute affinity of GX is calculated doubly. (Default yes) +gx_dupele: yes +// Grandcross display type (Default 1) +// 0: Yellow character +// 1: White character +gx_disptype: 1 + +// If no than you can use the ensemble skills alone. (Note 1) +player_skill_partner_check: yes + +// Is the character of a GM account set as the object of a display by @ command etc. or not? +hide_GM_session: no + +// ユニット移動処-摯-@。0で-{鯖仕-l(回線負荷→重、鯖処-掾ィ軽)、1でAthena仕-l(回線負荷→軽、鯖処-掾ィ重) +// translation (babelfish): +// Unit portable place - ? @. +// With 0 - {mackerel SI -l (circuit load -> heavily, the mackerel place - the ? ? lightly), +// With 1 - Athena SI -l (circuit load -> lightly, the mackerel place - the ? ? it is heavy) +unit_movement_type: 0 + +// Are other requests accepted during [various things[party,guild]] a request or not? +// It does not accept by no accepted by yes. +invite_request_check: yes + +// リムーブトラップの仕-l 0:-{鯖仕-l、罠1個 1:Athena仕-l、使ったアイテムを使った個数 +// translation (babelfish): +// SI of ???????? -l +// 0: - {Mackerel SI -l, trap 1 +// 1:Athena SI -l, the quantity which used the item which was used +skill_removetrap_type: 0 + +// Will display experience gained from killing a monster. (Note 1) +disp_experience: no + +// Whether or not Marine Spheres and Floras summoned by Alchemist will drop items and give experience? (Note 1) +alchemist_summon_reward: no + +// Maximum level (default 255). Requires that you have an experience table that supports more than 99 levels to go +// any higher. It is left at 255 for default as to not cause problems for people who already have players at higher levels. +// @lvup command will not go higher than this value, and therefor will not loop back around 0. +maximum_level: 255 + +// Leave at 0 to use normal drop system. Anything higher than 0 will allow luk to affect drop rates, based on a percentage. +// Example 1: Setting of 10 with 50 luk would add 5 to the drop rate. So say a card has a drop rate of 2, it would become 7. +// Example 2 : Setting 100 with 99 luk would add 99 to the drop rate. +drops_by_luk: 0 + +// Do all monsters ignore GMs unless attacked? (Note 1) +monsters_ignore_gm: no + +// Turn equipment breaking on. (Note 1) +// Turning this one will allow equipment to break in battles, +// as well as some skills that have chance to break equipmen. +equipment_breaking: no + +// Overall rate of which equipment can break. (Note 2) [100 = .01% chance to break equipment before penalties.] +// Critical attacks will double chance to break equipment, and other skills (such as Power Thrust) will increase weapon breaking chance as well. +// This does not effect Sage weapon enchantment failure chance. +equipment_break_rate: 100 + +// PK Server Mode. Turns entire server pvp(excluding towns). Experience loss is doubled if killed by another player. +// When players hunt monsters over 20 levels higher, they will receive 15% additional exp., and 25% chance of receiving more items. +// There is a nopvp.txt for setting up maps not to have pk on in this mode. Novices cannot be attacked and cannot attack. +// Normal pvp counter and rank display are disabled as well. +pk_mode: no + +// Turn this on to allow a player to level up more than once from a kill. (Note 1) +multi_level_up: no + +// Does using bow to do a backstab give a 50% damage penalty? (Note 1) +backstab_bow_penalty: yes + +// Choose if server begin with night (yes) or day (no) +night_at_start: no + +// Define duration in msec of the day (default: 7,200,000 = 2 hours) +// Set to 0 to disable day cycle (but not @day GM command). +// Except 0, minimum is 60000 (1 minute). +day_duration: 7200000 + +// Define duration in msec of the night (default: 1,800,000 = 30 min) +// Set to 0 to disable night cycle (but not @night GM command). +// Except 0, minimum is 60000 (1 minute). +night_duration: 1800000 + +// Will display a mob's hp/maxhp when the mouse cursor is over them. (Note 1) +// Will not display guardian or emperium hp. +// +// Still under testing (works but doesn't update) +show_mob_hp: no + +// Ban people that try to use an other name of its name (spoof name). +// Duration of the ban, in minutes (default: 5). Value from 0 to 32767 +// to disable the ban, set 0 +ban_spoof_namer: 5 + +// Set here minimum level of a (online) GM that can receive all informations about any player that try to hack, spoof a name, etc. +// Values are from 0 to 100. +// 100: disable information +// 0: send to any people, including normal players +// default: 60, according to GM definition in atcommand_athena.conf +hack_info_GM_level: 60 + +// Set here the minimum GM level to disable the nowarp (from) and nowarpto (to) flags. +// This option is mainly used in AT_commands (@memo, @warp, @charwarp, @go, etc...). All GM commands used to move or set a new map check nowarp and nowarpto flags. +// default: 20 (first level after normal player or super'normal' player) +any_warp_GM_min_level: 20 + +// Set here which client version do you accept. Add all values of clients: +// 1: Clients before 2004-07-06 (old clients) +// 2: 2004-07-06 kRO client +// 4: 2004-07-13 kRO client +// 8: 2004-07-26 kRO client +// 16: 2004-08-09 kRO / 2004-08-16aSakray / 2004-08-17aSakray client +// 32: 2004-09-06aSakray client +// default value: 63 (all clients) +packet_ver_flag: 63 + +// Allow GMs to mute players or not? +muting_players: no + +// Mail system - Only function in sql version +mail_system: no + +// valid range of dye's and styles on the client +min_hair_style: 0 +max_hair_style: 20 +min_hair_color: 0 +max_hair_color: 9 +min_cloth_color: 0 +max_cloth_color: 4 + +// Visible area size (how many squares away from a player can they see) +area_size: 14 + +import: conf/import/battle_conf.txt + diff --git a/conf/char_athena.conf b/conf/char_athena.conf new file mode 100644 index 0000000..2a064ae --- /dev/null +++ b/conf/char_athena.conf @@ -0,0 +1,140 @@ +// Athena Character configuration file. + +// Server Communication username and password. +userid: s1 +passwd: p1 + +// Server name, use alternative character such as ASCII 160 for spaces. +server_name: Mana World + +// Wisp name for server: used to send wisp from server to players (between 4 to 23 characters) +wisp_server_name: Server + +// Login Server IP +login_ip:127.0.0.1 +// Login Server Port +login_port: 6901 + +// Character Server IP +char_ip:127.0.0.1 +// Character Server Port +char_port: 6122 + +// Option to force a player to create an e-mail. +// If a player have default e-mail, and if you activate this option, the player can only connect in the game (to arrive on a map) like follow: +// - Create at least 1 character +// - Select 1 character +// - Select DEL to enter his/her e-mail. (if OK is choosen, client says to the player: 'invalid e-mail') +// - If his/her e-mail is correct, the player enter in the game (an e-mail is saved definitively). +// - If his/her e-mail is incorrect, he/she have 'incorrect e-mail' and must select again DEL. +// - After entering in the game (when the player arrives on a map), DEL and SEL/OK button work normaly for all next connections. +// Resume: If a player have "incorrect/invalid e-mail" when he/she click on 'OK' button, +// the player must click 'DEL' button and register his/her NEW e-mail to enter in the game +// So, default is 0, because administrator must explain to their players before to activate this option. +email_creation: 0 + +// Is Character server in maintainence mode? +char_maintenance: 0 + +// Enable or disable creation of new characters. +char_new: 0 + +// Maximum users able to connect to the server. Set to 0 for unlimited. +max_connect_user: 0 + +// It's to check IP of a player between char-server and other servers (part of anti-hacking system) +// If player doesn't have same IP, connection is refused. +// Set to 0/off/no to not check IP of player. +// Set to 1/on/yes if you want to check (default) +// Note: if you enable this option, be sure that your (local/lan/wan) players use correct ip (in xml file) to contact servers, +// and that your LAN is correctly configured (!), and that LAN configuration of eathena is right. +check_ip_flag: yes + +// How often should the server save all files? (In seconds) +autosave_time: 15 + +// Character server flatfile database +char_txt: save/athena.txt + +// Choose to create or not backup file (yes/no, 0/1, etc...) +// default is 'no', because backup file take time for nothing. Actually, there is no problem on characters file creation and save. +backup_txt_flag: no + +// Character server flatfile database (backup) +backup_txt: save/athena_backup.txt + +// Start point, Map name followed by coordinates (x,y) +start_point: new_3-1.gat,29,28 + +// Starting weapon for new characters +start_weapon: 1201 + +// Starting armor for new characters +start_armor: 1202 + +// Starting zeny for new characters +start_zeny: 50 + +// Name used for unknown characters +unknown_char_name: Unknown + +// Log Filename +char_log_filename: log/char.log + +// Allow or not identical name for characters but with a different case (upper/lower): +// example: Test-test-TEST-TesT; Value: 0 not allowed (default), 1 allowed +name_ignoring_case: 0 + +// Manage possible letters/symbol in the name of charater. Control character (0x00-0x1f) are never accepted. Possible values are: +// 0: no restriction (default) +// 1: only letters/symbols in 'char_name_letters' option. +// 2: Letters/symbols in 'char_name_letters' option are forbidden. All others are possibles. +char_name_option: 0 + +// Set the letters/symbols that you want use with the 'char_name_option' option. +// Note: add 'space' between 2 others letters/symbols. +// default: void. +//char_name_letters: + +// Filename of the file which receives the online players list in text +online_txt_filename: online.txt + +// Filename of the file which receives the online players list, but in html version +online_html_filename: online.html + +// Choose how to display online players. +// (sorting operation with a lot of online players can take time on a slow computer) +// 0: no sorting (default) +// 1: by alphabetical order of their name +// 2: by number of their zenys +// 3: by their base level +// 4: by their job (and job level inside the same job) +// 5: by alphabetical order of their actual map location +online_sorting_option: 0 + +// Choose which columns that you want display in the online files. Do the addition of these values: +// (if value is 0, no file is done) +// 1: name (just the name, no function like 'GM') +// 2: job +// 4: levels +// 8: map name +// 16: mapname and coordonates +// 32: zenys +// 64: name (with 'GM' if the player is a GM) +// default value: 1 (only name) +online_display_option: 64 + +// minimum GM level to display 'GM' when we want to display it (default: 1) +online_gm_display_min_level: 20 + +// refresh time (in sec) of the html file in the explorer (default 20) +online_refresh_html: 20 + +// Anti-freeze system enable +anti_freeze_enable: 0 +// Anti-freeze system interval (in seconds) +anti_freeze_interval: 6 + +// If you want use an additional configuration file, uncomment and use this parameter +//import: path/additional_configuration_file + diff --git a/conf/eathena-monitor.conf b/conf/eathena-monitor.conf new file mode 100644 index 0000000..cf9563d --- /dev/null +++ b/conf/eathena-monitor.conf @@ -0,0 +1,14 @@ +# $Id: eathena-monitor.conf,v 1.0 2006/13/10 21:42:22 Platyna Exp $ + +# By default the eathena-monitor looks for its config file in +# $HOME/tmwserver/conf/, creates the log file in $HOME/tmwserver/log/. +# You may use this file to adjust these settings if needed or use predefined +# defaults. Lines starting with hash mark or white space are ignored. + +# login_server=/home/athena/tmwserver/login-server +# map_server=/home/athena/tmwserver/map-server +# char_server=/home/athena/tmwserver/char-server +# workdir=/home/athena/tmwserver +# In seconds +interval=2 +# logfile=/home/athena/tmwserver/log/monitor.log diff --git a/conf/gm_account.txt b/conf/gm_account.txt new file mode 100644 index 0000000..9aabf4c --- /dev/null +++ b/conf/gm_account.txt @@ -0,0 +1,2 @@ +// <account ID> <level> + diff --git a/conf/grf-files.txt b/conf/grf-files.txt new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/conf/grf-files.txt diff --git a/conf/help.txt b/conf/help.txt new file mode 100644 index 0000000..fa8de1f --- /dev/null +++ b/conf/help.txt @@ -0,0 +1,225 @@ +// put at first, the minimum level to display the line + 0:To use one command, type it inside the message window where you usually type to chat. + 20:@h/@help - display this help. + 40: + 40:--- MESSAGE CMD --- + 40:/b/@broadcast <message> - Broadcasts a GM message with name of the GM (in yellow) + 40:/nb <message>/@kami <message> - Broadcasts a GM message without name of the GM (in yellow) + 40:@kamib <message> - Broadcasts a GM message without name of the GM (in blue) + 40:/lb/@localbroadcast <message> - Broadcasts a GM message with name of the GM (in yellow) ONLY on your map + 40:/nlb <message> - Broadcasts a GM message without name of the GM (in yellow) ONLY on your map + 0: + 0:--- INFORMATION CMD --- + 20:@who/@whois [match_text] - Display a listing of who is online and where + 20:@who2 [match_text] - Display a listing of who is online and their job + 20:@who3 [match_text] - Display a listing of who is online and their party/guild + 20:@whomap/@whomap2/@whomap3 [map] - like @who/@who2/@who3 but only for specifical map + 20:@whogm [match_text] - Like @who+@who2+who3, but only for GM. + 1:@where [char name] - Tells you the location of a character + 40:@charstatsall - Displays stats of all characters. + 40:@charitemlist <char name> - Displays all items of a player. + 40:@charstoragelist <char name> - Displays all items of a player's storage. + 40:@charcartlist <char name> - Displays all items of a player's cart. + 0:@ignorelist - Displays your ignore list + 99:@mapinfo [<0-3> [map]] - Give information about a map (general info +: 0: no more, 1: players, 2: NPC, 3: shops/chat). + 0:@time/@date/@server_date/@serverdate/@server_time/@servertime - Display the date/time of the server + 60: + 60:@guildspy <guild_name/id> - You will receive all messages of the guild channel + 60:@partyspy <party_name/id> - You will receive all messages of the party channel + 1: + 1:--- CHANGE GM STATE CMD --- + 40:/hide/@hide - Makes you character invisible (GM invisibility). Type @hide again become visible. + 40:@save - Sets respawn point to current spot + 40:@load/@return - Warps you to your save point + 40:/mm//mapmove/@warp/@rura/@mapmove <mapname> <x> <y> - Warps you to the selected position + 40:@jump [x [y]]- Randomly warps you like a flywing. + 20:/shift/@jumpto/@warpto/@goto <char name> - Warps you to selected character + 20:@follow <char_name> - follow a player + 10:@go <number/city_name> - Warps you to a city. + 10: -3: (Memo point 2) 1: morocc 5: izlude 9: yuno 13: niflheim + 10: -2: (Memo point 1) 2: geffen 6: aldebaran 10: amatsu 14: louyang + 10: -1: (Memo point 0) 3: payon 7: xmas (lutie) 11: gonryun 15: start point + 10: 0: prontera 4: alberta 8: comodo 12: umbala 16: prison/jail + 10: + 1:@die ---- suicide + 60:@alive - Revives yourself from death + 40:@heal [<HP> <SP>] - Heals the desired amount of HP and SP. No value specified will do a full heal. + 20: + 40:@job/@jobchange <job ID> - Changes your job + 40: 0 Novice 7 Knight 14 Crusader 22 Formal + 40: 1 Swordman 8 Priest 15 Monk 23 Super Novice + 40: 2 Mage 9 Wizard 16 Sage + 40: 3 Archer 10 Blacksmith 17 Rogue + 40: 4 Acolyte 11 Hunter 18 Alchem + 40: 5 Merchant 12 Assassin 19 Bard + 40: 6 Thief 13 Peco-Knight 20 Dancer 21 Peco-Crusader + 40: 24 Novice High 31 Lord Knight 38 Paladin + 40: 25 Swordman High 32 High Priest 39 Monk + 40: 26 Mage High 33 High Wizard 40 Professor + 40: 27 Archer High 34 Whitesmith 41 Stalker + 40: 28 Acolyte High 35 Sniper 42 Creator + 40: 29 Merchant High 36 Assassin Cross 43 Clown + 40: 30 Thief High 37 Peco Knight 44 Gypsy 45 Peco-Paladin + 60:@lvup/@blevel/@baselvlup <number of levels> - Raises your base level the desired number of levels. The max is 255 (User Defined). + 60:@joblvup/@jlevel/@joblvlup <number of levels> -Raises your job level the desired number of levels. The max is 50 For Basic Classes. For Super Novice and Advanced Classes it is 70. + 60:@allskill/@allskills/@skillall/@skillsall - Give you all skills. + 40:@option <param1> <param2> <param3> - Adds different visual effects on or around your character + 40: <param1> <param2> <p3>(stackable) <param3> <param3> + 40: 1 Petrified (stackable) 01 Sight 32 Peco Peco riding 2048 Orc Head + 40: 2 Frozen 01 Poison 02 Hide 64 GM Perfect Hide 4096 Wedding Sprites + 40: 3 Stunned 02 Cursed 04 Cloak 128 Level 2 Cart 8192 Ruwach + 40: 4 Sleeping 04 Silenced 08 Level 1 Cart 256 Level 3 Cart + 40: 6 darkness 08 ??? 16 Falcon 512 Level 4 Cart + 40: 16 darkness 1024 Level 5 Cart + 20:@mountpeco - Give/remove you a peco (Class is required, but not skill) + 20:@disguise <monster_name_or_monster_ID> - Change your appearence to other players to a mob. + 20:@undisguise - Restore your normal appearance. + 20:@model <hair ID: 0-17> <hair color: 0-8> <clothes color: 0-4> - Changes your characters appearence. + 40:@dye/@ccolor <clothes color: 0-4> - Changes your characters appearence (only clothes color). + 40:@hairstyle/@hstyle <hair ID: 0-17> - Changes your characters appearence (only hair style). + 40:@haircolor/@hcolor <hair color: 0-8> - Changes your characters appearence (only hair color). + 40:@speed <1-1000> - Changes you walking speed. 1 being the fastest and 1000 the slowest. Default 150. + 40:@effect <effect_id> [flag] - Give an efect to your character. + 40:@dropall - throws all your possession on the ground + 40:@storeall - puts all your possessions in storage + 40:@killable - make your character killable + 60:@stpoint <number of points> - Gives you the desired number of stat points. + 60:@skpoint <number of points> - Gives you the desired number of skill points. + 60:@zeny <amount> - Gives you desired amount of Zeny. + 60:@str,@agi,@vit,@int,@dex,@luk <amount> - Adds desired amount to any stat. For example "@str 10" raises your str by 10 + 60:@statall/@statsall/@allstats/@allstat [value] - Adds value in all stats (maximum if no value). + 40:@memo [memo_position] - set/change a memo location (no position: display memo points). + 40:@spiritball <number: 1-1000> - Gives you "spirit spheres" like from the skill "Call Spirits" + 40: (If the number you use is > 1000, your server may become instable or crash) + 40:@questskill <#> - Gives you the specified quest skill + 40:@lostskill <#> - Takes away the specified quest skill from you + 40:@skillid <name> - look up a skill by name + 40:@useskill <skillid> <skillv> <target> - use a skill on target + 40: Novice Swordsman Thief Merchant + 40: 142 = Emergency Care 144 = Moving HP Recovery 149 = Throw Sand 153 = Cart Revolution + 40: 143 = Act dead 145 = Attack Weak Point 150 = Back Sliding 154 = Change Cart + 40: Archer 146 = Auto Berserk 151 = Take Stone 155 = Crazy Uproar/Loud Voice + 40: 147 = Arrow Creation Acolyte 152 = Stone Throw Magician + 40: 148 = Charge Arrows 156 = Holy Light 157 = Energy Coat + 60: @addwarp <map name> <x coord> <y coord> + 40: + 40:--- MONSTERS CMD --- + 50:/monster <monster_name> - Spawns 1 of the desired monster. + 50:@spawn/@monster/@summon <monster_name_or_monster_ID> [<number to spawn> [<desired_monster_name> [<x coord> [<y coord>]]]] + 50:@monster2 <desired_monster_name> <monster_name_or_monster_ID> [<number to spawn> [<x coord> [<y coord>]]] + 50:@spawn/@monster/@summon/@monster2 "desired monster name" <monster_name_or_monster_ID> [<number to spawn> [<x coord> [<y coord>]]] + 50:@spawn/@monster/@summon/@monster2 <monster_name_or_monster_ID> "desired monster name" [<number to spawn> [<x coord> [<y coord>]]] + 50: Spawns the desired monster with any desired name. + 60:@killmonster [map] - kill all monsters of the map (they drop) + 40:@killmonster2 - kill all monsters of your map (without drops) + 1: + 1:--- ITEMS CMD --- + 1:@storage - Opens storage + 50:@gstorage - Opens guild storage + 60:/item <item_name> - Gives you 1 of the desired item. + 60:@item <item name or ID> <quantity> - Gives you the desired item. + 60:@item2 <item name or ID> <quantity> <Identify_flag> <refine> <attribut> <Card1> <Card2> <Card3> <Card4> = Gives you the desired item. + 40:@itemreset - Remove all your items. + 60:@itemcheck - Check your items with authorised items. + 60:@idsearch <part_of_item_name> - Search all items that name have part_of_item_name + 60:@refine <equip position> <+/- amount> + 60:@produce <equip name or equip ID> <element> <# of very's> + 60: Element: 0=None 1=Ice 2=Earth 3=Fire 4=Wind + 60: It has separately with fragment 3 of the attribute + stars, you can apply. + 60:@repairall - Repair all items of your inventory + 40: + 40:--- PVP CMD --- + 40:@pvpon - Turns pvp on on the current map + 40:@pvpoff - Turns pvp off on the current map + 40:@gvgon/@gpvpon - Turns gvg on on the current map + 40:@gvgoff/@gpvpoff - Turns gvg off on the current map + 60:@agitstart - Starts War of Emperium + 60:@agitend - End War of Emperium + 1: + 1:--- GROUPS CMD --- + 1:@party <party_name> - Create a party. + 50:@guild <guild_name> - Create a guild. + 60:@guildlvup/@guildlvlup <# of levels> - Raise Guild by desired number of levels + 60:@guildrecall <guild_name/id> - Warps all online characters of a guild to you. + 60:@partyrecall <party_name/id> - Warps all online characters of a party to you. + 1: + 1:--- PETS CMD --- + 60:@hatch - Create a pet from your inventory eggs list. + 60:@makeegg <pet_id> - Gives pet egg for monster number in pet DB + 40:@petfriendly <#> - Set pet friendly amount (0-1000) 1000 = Max + 40:@pethungry <#> - Set pet hungry amount (0-100) 100 = Max + 1:@petrename - Re-enable pet rename + 20: + 20:--- REMOTE CHAR CMD --- + 60:@kill <char name> - Kills specified character. + 40:@charkillable <char name> - make another character killable + 60:@nuke <char name> - Kills specified character (nuclear effect). + 60:@chardropall <char name> - throws all a chars possession on the ground + 60:@charstoreall <char name> - puts all of anothers charactes possessions in storage + 60:/recall/@recall <char name> - Warps target character to you. + 80:@recallall - Warps every character online to you. + 60:@charwarp/@rura+ <mapname> <x> <y> <char name> - Warps character to location of choice + 60:@revive <char name> - Revives target character. + 40:@charstats <char name> - Displays a characters stats. + 20:@charignorelist <char name> - Displays ignore list of the player + 20:@inall <char name> - Allows all wispers for the player + 20:@exall <char name> - Blocks all wispers for the player + 60:@charoption <param1> <param2> <param3> <charname> - Like @option command but only to target character. + 50:@charmountpeco <charname> - Give/remove to a player a peco (Class is required, but not skill). + 50:@charpetrename <charname> - Re-enable pet rename to a player. + 60:@charsave <map> <x> <y> <charname> - Changes the target players respawn point. + 60:@charbaselvl <#> <nickname> - Change a characters base level. + 60:@charjlvl <#> <nickname> - Change a characters job level. + 60:@charjob/@charjobchange <job ID> <char name> - Changes target characters job. + 60:@charzeny <amount> <name> - Give/take a players Zeny + 60:@charstpoint <amount> <name> - Give/take a players stat points + 60:@charskpoint <amount> <name> - give/take a players skill points + 60:@charskreset <charname> - Reset skills of a character. + 60:@charstreset <charname> - Reset stats of a character. + 60:@charreset <charname> - Reset stats AND skills of a character. + 60:@charquestskill <#> <charname> - Gives to a player the specified quest skill. + 60:@charlostskill <#> <charname> - Takes away the specified quest skill from the player. + 60:@chardelitem <item_name_or_ID> <quantity> <player> - Remove items from a character + 50:@charmodel <hair type> <hair color> <clothes color> <name> - Changes a player's model + 60:@chardisguise <monster_name_or_monster_ID> <char name> - Changes disguise of a player + 60:@charundisguise <char name> - Cancels disguise of a player + 60:@charchangesex <name> - Changes sex of a player (all characters of the account) + 60:@charblock/@block <name> - Blocks definitively a account + 60:@charunblock/@unblock <name> - Unblocks a account + 60:@charban/@ban/@banish/@charbanish <time> <name> - Ban temporarily a account + 60: time usage: adjustement (+/- value) and element (y/a, m, d/j, h, mn, s) + 60: Example: @ban +1m-2mn1s-6y testplayer + 60:@charunban/@unban/@unbanish/@charunbanish <name> - Unban a account + 60:@jail <char_name> - Sends specified character in jails + 60:@trade <char_name> - Open a trade window with a another player + 20:@kick <charname> - Kicks specified character off the server + 99:@kickall - Kick all characters off the server + 99:@mapexit - Kick all players and shut down map-server. + 80:@doom - Kills all NON GM chars on the server. + 80:@doommap - Kills all non GM characters on the map. + 80:@raise - Resurrects all characters on the server. + 80:@raisemap - Resurrects all characters on the map. + 60:@unjail/@discharge <char_name> - Discharges specified character/prisoner + 80: + 80:--- ENVIRONMENT CMD --- + 80:@night - Uses @option 00 16 00 on all characters. All characters are in darkness. + 80:@day - Uses @option 00 00 00 on all characters. + 80:@skillon - turn skills on for a map + 80:@skilloff - turn skills on for a map + 0: + 0:--- ADMIN CMD --- + 99:@reloaditemdb - Reload item database (admin command) + 99:@reloadmobdb - Reload monster database (admin command) + 99:@reloadskilldb - Reload skills definition database (admin command) + 99:@reloadscript - Reload all scripts (admin command) + 99:@reloadgmdb - Reload GM levels (admin command) + 99:@adjgmlvl - Do a temporary adjustment of the GM level of a player (admin command) + 99:@adjcmdlvl - Do a temporary adjustment of the GM level of a command (admin command) + 80:@enablenpc <NPC_name> - Enable a NPC (admin command) + 80:@disablenpc <NPC_name> - Disable a NPC (admin command) + 80: + 99:@gat - For debugging (you inspect around gat) + 99:@packet - For debugging (packet variety) + 99: +100:@GM <password> - it becomes GM! + 0:@email <actual@email> <new@email> - to change your e-mail (characters protection) diff --git a/conf/inter_athena.conf b/conf/inter_athena.conf new file mode 100644 index 0000000..72a88bd --- /dev/null +++ b/conf/inter_athena.conf @@ -0,0 +1,31 @@ +// Athena InterServer configuration. + +// Storage flatfile database, used for Karfa storage. +storage_txt: save/storage.txt + +// Party flatfile database, for party names, members and other party info. +party_txt: save/party.txt + +// Guild flatfile database, for guild names, members, and other guild info. +guild_txt: save/guild.txt + +// Pet flatfile database, for pet names, and other pet info. +pet_txt: save/pet.txt + +// Castle flatfile database, for emperium war castles, etc. +castle_txt: save/castle.txt + +// Inter Log Filename +inter_log_filename: log/inter.log + +// Level range for sharing within a party +party_share_level: 10 + +// The lowest GM level on your server +lowest_gm_level: 1 + +// How often the GM accounts will be reloaded by the map-server in minutes +read_gm_interval: 10 + + + diff --git a/conf/ladmin_athena.conf b/conf/ladmin_athena.conf new file mode 100644 index 0000000..e6f5898 --- /dev/null +++ b/conf/ladmin_athena.conf @@ -0,0 +1,34 @@ +// Athena Ladmin configuration file. + +// Login Server IP +login_ip:127.0.0.1 +// Login Server Port +login_port: 6900 + +// Administrative password, used to connect remotely to server. +// NOTICE: If you enable remote administration, you should change its value for security +admin_pass: admin + +// Encoding type of the password +// 0: not encoded +// 1: key+password +// 2: password+key +passenc: 2 + +// Language of ladmin +// F: Fran軋is +// E: English (default) +defaultlanguage: E + +// Log Filename. All operations done by the software are logged in this file. +ladmin_log_filename: log/ladmin.log + +// Indicate how to display date in logs, to players, etc. +// 0: 31-12-2004 23:59:59 +// 1: 12-31-2004 23:59:59 +// 2: 2004-31-12 23:59:59 +// 3: 2004-12-31 23:59:59 (default) +date_format: 3 + +// If you want use an additional configuration file, uncomment and use this parameter +//import: path/additional_configuration_file diff --git a/conf/lan_support.conf b/conf/lan_support.conf new file mode 100644 index 0000000..bfaec69 --- /dev/null +++ b/conf/lan_support.conf @@ -0,0 +1,41 @@ +// Athena TXT version LAN configure file. +// Support Client Connect to Local Area Network (LAN) IP Address Server. +// put this fle into conf/ directory +// +// HOWTO: +// To use this file, the login-server, char-server and map-server must be on the same subnetwork +// (not necessary on the same computer). You can not use eAthena if you want to install the servers on 2 or more different LAN. +// +// First of all: you must configure your router to forward your WAN IP (one or more) and port (default: 6900, 6121 and 5121) +// to the right concerned computer(s) (if default port, forward 6900 port to the (LAN IP) login-server, 6121 port to the (LAN IP) char-server, etc.). +// After, set in char_athena.conf and map_athena.conf files the WAN IP and the right ports that you use. +// Give to WAN people (client that are not on your LAN) your WAN IP to have an access to your server. +// At this point, all players outside your LAN can access to your server(s). +// +// Now, you must parameter your LAN for the servers. +// Set the LAN IP of the char-server and the map-server in this file (lan_char_ip and lan_map_ip). +// Set the definition of your LAN in this file (subnet and subnetmask). +// When you load/start login-server and char-server, read what the server displays, and specially the section ---LAN CONFIGURATION---. +// If you see a warning or something not good, correct it. +// Now LAN client can access to your server. +// +// NB: if you want that nobody of your LAN can access to your server, put 127.0.0.1 in IP and 255.255.255.255 for the mask. +// So only the localhost computer would access to your server. +// NB2: you can use LAN name if you have some instead of IP and/or mask. +// NB3: if you want set your server only for LAN people, set your LAN IP instead of the WAN IP, and set 127.0.0.1/255.255.255.255 for the LAN IP. +// +// HOW THAT WORKS: +// When someone tries to connect to your server(s), the login-server/char-server checks its IP with the LAN subnet (subnet and subnetmask parameters). +// If it matches, the login-server sends the LAN IP of the char-server (lan_char_ip); and char-server do same for map-server (lan_map_ip). +// If not, the login-server sends the WAN IP of the char-server that it have received (char_ip in char_athena.conf) +// and the char-server sends the WAN IP of the map-server that it have received (map_ip in map_athena.conf) + +// put here the LAN IP of your char-server +lan_char_ip: 127.0.0.1 + +// put here the LAN IP of your map-server +lan_map_ip: 127.0.0.1 + +// put here the Subnet mask of your LAN +subnet: 127.0.0.1 +subnetmask: 255.255.255.255 diff --git a/conf/login_athena.conf b/conf/login_athena.conf new file mode 100644 index 0000000..44f7166 --- /dev/null +++ b/conf/login_athena.conf @@ -0,0 +1,135 @@ +// Athena Login Server configuration file. +// Translated by Peter Kieser <pfak@telus.net> + +// Port to bind Login Server to (always binds to all IP addresses) +login_port: 6901 + +// Whether remote administration is enabled or disabled (1 for enabled, 0 for disabled) +admin_state: 1 + +// Administrative password, used by ladmin (perl software) to connect remotely to server. +// NOTICE: If you enable remote administration, you should change its value for security +admin_pass: pass + +// Indicate the IP that the server accepts for remote administration. +// put: 'all', or 'xxx.xxx.' (begin of an ip finished by '.' or a complete ip), +// or a network and its mask (example: '123.456.789.012/24' or '123.456.789.012/255.255.255.0') +// or 'clear' to suppress previous parameter (use it in import file mainly) +// Add as many IP's as you wish. +ladminallowip: all + +// Gamemaster password, used with the @gm command to obtain GM commands (level of gm set with level_new_gm parameter). +// NOTICE: You should also change this one. +gm_pass: pass + +// Level of new GM created with @gm command. (default: 60) +// If you set to 0, you disable creation of new GM with @gm. +// To be able to create a gm with @gm, you must: +// - give a level to this value (not 0) +// - enable to level 0 the @gm command (atcommand_athena.conf) (default 100) +// - enable gm commands to normal player (battle_athena.conf, atcommand_gm_only parameter) +// - and normal player must give correct password when he use the @gm command +level_new_gm: 60 + +// Can you make new accounts on the server? (1 for Yes, 0 for no) +new_account: 1 + +// Account flatfile database, stores account information. +account_filename: save/account.txt + +// What account AIDs have GM privs, and what level? +gm_account_filename: conf/gm_account.txt + +// Timer to check if GM_account file has been changed and reload GM account automaticaly +// (in seconds; default: 15; value: 0 (disabled), or 2 or more) +gm_account_filename_check_timer: 15 + +// Log Filename. All operations received by the server are logged in this file. +login_log_filename: log/login.log + +// Name of the file of that logs the unknown packets (for debug or hack check) +login_log_unknown_packets_filename: log/login_unknown_packets.log + +// Indicate if the unknown packets are saved or not +//(the unknown packets coming from the char-server or ladministration does not relate to, which is always saved) +// Be careful: if you receive an attack, your hard disk can cause lag... +// So, active this option with a speed hard disk or for debug only. +save_unknown_packets: 0 + +// Indicate if you want display the parse of the packets received in a normal connection +// It's useful for debug. Possible values: 0: no (default), 1: yes +display_parse_login: 0 + +// Indicate if you want display the parse of the packets received in administration connection +// It's useful for debug. Possible values: 0: no (default), 1: yes +display_parse_admin: 0 + +// Indicate if you want display the parse of the packets received from a char-server +// It's useful for debug. Possible values: 0: no (default), 1: yes (without packet 0x2714), 2: all packets +display_parse_fromchar: 0 + +// Indicate how to display date in logs, to players, etc. +// 0: 31-12-2004 23:59:59 +// 1: 12-31-2004 23:59:59 +// 2: 2004-31-12 23:59:59 +// 3: 2004-12-31 23:59:59 (default) +date_format: 3 + +// Indicate the minimum GM level of player that the server accepts to connection. +// 0: all players (normal player are 0. it's default), 1-99: GM level at least with level x +min_level_to_connect: 0 + +// Give possibility to adjust (ladmin command: timeadd) the time of an unlimited account. +// If set to on/1/yes..., the adjustment is be done from actual time to set the final time of the account. +// If set to no/0/no..., the adjustment can not be done on an unlimited account. You must set (ladmin command: timeset) a final time before to adjust (ladmin command: timeadd) +add_to_unlimited_account: off + +// Starting additional sec from now for the limited time at creation of account +// -1: new account are created with UNlimited time (default value) +// 0 or more: new accounts was created by addition of the value (in sec) to the actual time (to set first limited time) +start_limited_time: -1 + +// It's to check IP of a player between login-server and char-server (part of anti-hacking system) +// If player doesn't have same IP, connection is refused. +// Set to 0/off/no to not check IP of player. +// Set to 1/on/yes if you want to check (default) +// Note: if you enable this option, be sure that your (local/lan/wan) players use correct ip (in xml file) to contact servers, +// and that your LAN is correctly configured (!), and that LAN configuration of eathena is right. +// if not correct, you can read list of char-servers, but not look slots of characters (rejected by server). +check_ip_flag: no + +// Specify order of IP control if necessary (option: 'deny,allow', 'allow,deny', or 'mutual-failture') +// (how to use 'allow' and 'deny' information) +//order: allow,deny + +// Indicate the IP that the server accept. +// put: 'all', or 'xxx.xxx.' (begin of an ip finished by '.' or a complete ip), +// or a network and its mask (example: '123.456.789.012/24' or '123.456.789.012/255.255.255.0') +// or 'clear' to suppress previous parameter (use it in import file mainly) +// Add as many IP's as you wish. +//allow: all + +// Indicate the IP that the server refuse. +// Add as many IP's as you wish, as long as you put deny: before it. +//deny: 123.123.123.123 +//deny: 234.234.234.234 + +// If you want use an additional configuration file, uncomment and use this parameter +//import: path/additional_configuration_file + +//Passwords in Login DB are MD5 - <passwordencrypt> cannot b used on client with this on +use_MD5_passwords: no + +//Ban features: read readme for more info if you dont know this. +ipban: 1 +dynamic_pass_failure_ban: 1 +dynamic_pass_failure_ban_time: 5 +dynamic_pass_failure_ban_how_many: 3 +dynamic_pass_failure_ban_how_long: 60 +dynamic_account_ban: 1 +dynamic_account_ban_class: 0 + +// Anti-freeze system enable +anti_freeze_enable: 0 +// Anti-freeze system interval (in seconds) +anti_freeze_interval: 15 diff --git a/conf/map_athena.conf b/conf/map_athena.conf new file mode 100644 index 0000000..898848e --- /dev/null +++ b/conf/map_athena.conf @@ -0,0 +1,98 @@ +//eAthena Map-Server Configuration File + +// Interserver communication passwords, set in account.txt (or equiv.) +userid: s1 +passwd: p1 + +// Character Server IP +char_ip:127.0.0.1 +// Character Server Port +char_port: 6122 + +// Map Server IP +map_ip:127.0.0.1 +// Map Server Port +map_port: 5122 + +// Database autosave time, in seconds. +autosave_time: 60 + +// Message of the day file, when a character logs on, this message is displayed. +motd_txt: conf/motd.txt + +// When @help or @h is typed when you are a gm, this is displayed for helping new gms understand gm commands. +help_txt: conf/help.txt + +mapreg_txt: save/mapreg.txt + +// import: /home/tmwxxabb/tmwserver/conf/import/map_conf.txt + +// Maps + +map: new_10-1.gat +map: new_9-1.gat +map: new_8-1.gat +map: new_7-1.gat +map: new_6-1.gat +map: new_5-1.gat +map: new_4-1.gat +map: new_3-1.gat +map: new_2-1.gat +map: new_1-1.gat + +// NPCs (ordered in alphabetical order by name). + +// Tulimshar 3-1 +npc: npc/tulimshar/banker.txt +npc: npc/tulimshar/barber.txt +npc: npc/tulimshar/children.txt +npc: npc/tulimshar/elanore.txt +npc: npc/tulimshar/guards.txt +npc: npc/tulimshar/guide.txt +npc: npc/tulimshar/man.txt +npc: npc/tulimshar/merchant.txt +npc: npc/tulimshar/monster_guide.txt +npc: npc/tulimshar/monsters.txt +npc: npc/tulimshar/passages.txt +npc: npc/tulimshar/rewards_master.txt +npc: npc/tulimshar/sandra.txt +npc: npc/tulimshar/vincent.txt + +// Tulimshar casino 8-1 +npc: npc/tulimshar-casino/casino.txt + +// Western desert 1-1 +// npc: npc/western-desert/dark_mage.txt +npc: npc/western-desert/merchant.txt +npc: npc/western-desert/monsters.txt +npc: npc/western-desert/nomads.txt +npc: npc/western-desert/passages.txt + +// Eastern desert 7-1 +npc: npc/eastern-desert/monsters.txt +npc: npc/eastern-desert/passages.txt + +// Woodland 9-1 +npc: npc/woodland/alchemist.txt +npc: npc/woodland/monsters.txt +npc: npc/woodland/passages.txt + +// Cave level 1 2-1 +npc: npc/cave1/miners.txt +npc: npc/cave1/monsters.txt +npc: npc/cave1/passages.txt + +// Cave level 2 (Underground Palace) 5-1 +npc: npc/cave2/chest.txt +npc: npc/cave2/monsters.txt +npc: npc/cave2/passages.txt + +// Xmas snow map 10-1 +npc: npc/xmas/monsters.txt +npc: npc/xmas/santa.txt +npc: npc/xmas/snowman.txt +npc: npc/xmas/taro.txt + +// Small cave (test map) 4-1 +npc: npc/cave-small/hermit.txt +npc: npc/cave-small/monsters.txt diff --git a/conf/motd.txt b/conf/motd.txt new file mode 100644 index 0000000..f570494 --- /dev/null +++ b/conf/motd.txt @@ -0,0 +1 @@ +Welcome to The Mana World! (running on eAthena) diff --git a/conf/msg_athena.conf b/conf/msg_athena.conf new file mode 100644 index 0000000..1aacf66 --- /dev/null +++ b/conf/msg_athena.conf @@ -0,0 +1,511 @@ +// eAthena msg_athena.conf +// Message Configuration +// For translation, just change msg here (second line), no need to modify source code. +// Format: +// // English message +// msg_number: translated message + +// 0-499: reserved for GM commands +// 500-999 reserved for others + +// Messages of GM commands +// ----------------------- + +//0: Warped. +0: Warped. +//1: Map not found. +1: Map not found. +//2: Coordinates out of range. +2: Coordinates out of range. +//3: Character not found. +3: Character not found. +//4: Jump to %s +4: Jump to %s +//5: Jump to %d %d +5: Jump to %d %d +//6: Character data respawn point saved. +6: Character data respawn point saved. +//7: Warping to respawn point. +7: Warping to respawn point. +//8: Speed changed. +8: Speed changed. +//9: Options changed. +9: Options changed. +//10: Invisible: Off +10: Invisible: Off +//11: Invisible: On +11: Invisible: On +//12: Your job has been changed. +12: Your job has been changed. +//13: A pity! You've died. +13: A pity! You've died. +//14: Character killed. +14: Character killed. +//15: Player warped (message sends to player too). +15: Player warped (message sends to player too). +//16: You've been revived! It's a miracle! +16: You've been revived! It's a miracle! +//17: HP, SP recovered. +17: HP, SP recovered. +//18: Item created. +18: Item created. +//19: Invalid item ID or name. +19: Invalid item ID or name. +//20: All of your items have been removed. +20: All of your items have been removed. +//21: Base level raised. +21: Base level raised. +//22: Base level lowered. +22: Base level lowered. +//23: Job level can't go any higher. +23: Job level can't go any higher. +//24: Job level raised. +24: Job level raised. +//25: Job level lowered. +25: Job level lowered. +//26: Help commands: +26: Help commands: +//27: File help.txt not found. +27: File help.txt not found. +//28: No player found. +28: No player found. +//29: 1 player found. +29: 1 player found. +//30: %d players found. +30: %d players found. +//31: PvP: Off. +31: PvP: Off. +//32: PvP: On. +32: PvP: On. +//33: GvG: Off. +33: GvG: Off. +//34: GvG: On. +34: GvG: On. +//35: You can't use this command with this class. +35: You can't use this command with this class. +//36: Appearence changed. +36: Appearence changed. +//37: An invalid number was specified. +37: An invalid number was specified. +//38: Invalid location number or name. +38: Invalid location number or name. +//39: All monster summoned! +39: All monster summoned! +//40: Invalid monster ID or name. +40: Invalid monster ID or name. +//41: Impossible to decrease the number/value. +41: Impossible to decrease the number/value. +//42: Stat changed. +42: Stat changed. +//43: You're not in a guild. +43: You're not in a guild. +//44: You're not the master of your guild. +44: You're not the master of your guild. +//45: Guild level change failed. +45: Guild level change failed. +//46: %s recalled! +46: %s recalled! +//47: Base level can't go any higher. +47: Base level can't go any higher. +//48: Character's job changed. +48: Character's job changed. +//49: Invalid job ID. +49: Invalid job ID. +//50: You already have some GM powers. +50: You already have some GM powers. +//51: Character revived. +51: Character revived. +//52: This option cannot be used in PK Mode. +52: This option cannot be used in PK Mode. +//53: '%s' stats: +53: '%s' stats: +//54: No player found in map '%s'. +54: No player found in map '%s'. +//55: 1 player found in map '%s'. +55: 1 player found in map '%s'. +//56: %d players found in map '%s'. +56: %d players found in map '%s'. +//57: Character's respawn point changed. +57: Character's respawn point changed. +//58: Character's options changed. +58: Character's options changed. +//59: Night has fallen. +59: Night has fallen. +//60: Day has arrived. +60: Day has arrived. +//61: The holy messenger has given judgement. +61: The holy messenger has given judgement. +//62: Judgement was made. +62: Judgement was made. +//63: Mercy has been shown. +63: Mercy has been shown. +//64: Mercy has been granted. +64: Mercy has been granted. +//65: Character's base level raised. +65: Character's base level raised. +//66: Character's base level lowered. +66: Character's base level lowered. +//67: Character's job level can't go any higher. +67: Character's job level can't go any higher. +//68: character's job level raised. +68: character's job level raised. +//69: Character's job level lowered. +69: Character's job level lowered. +//70: You have learned the skill. +70: You have learned the skill. +//71: You have forgotten the skill. +71: You have forgotten the skill. +//72: Guild siege warfare start! +72: Guild siege warfare start! +//73: Already it has started siege warfare. +73: Already it has started siege warfare. +//74: Guild siege warfare end! +74: Guild siege warfare end! +//75: Siege warfare hasn't started yet. +75: Siege warfare hasn't started yet. +//76: You have received all skills. +76: You have received all skills. +//77: The reference result of '%s' (name: id): +77: The reference result of '%s' (name: id): +//78: %s: %d +78: %s: %d +//79: It is %d affair above. +79: It is %d affair above. +//80: Give a display name and monster name/id please. +80: Give a display name and monster name/id please. +//81: Your GM level don't authorise you to do this action on this player. +81: Your GM level don't authorise you to do this action on this player. +//82: Please, use one of this number/name: +82: Please, use one of this number/name: +//83: Cannot spawn emperium. +83: Cannot spawn emperium. +//84: All stats changed! +84: All stats changed! +//85: Invalid time for ban command. +85: Invalid time for ban command. +//86: Sorry, but a player name have at least 4 characters. +86: Sorry, but a player name have at least 4 characters. +//87: Sorry, but a player name have 23 characters maximum. +87: Sorry, but a player name have 23 characters maximum. +//88: Character name sends to char-server to ask it. +88: Character name sends to char-server to ask it. +//89: Sorry, it's already the night. Impossible to execute the command. +89: Sorry, it's already the night. Impossible to execute the command. +//90: Sorry, it's already the day. Impossible to execute the command. +90: Sorry, it's already the day. Impossible to execute the command. +//91: Character's base level can't go any higher. +91: Character's base level can't go any higher. +//92: All characters recalled! +92: All characters recalled! +//93: All online characters of the %s guild are near you. +93: All online characters of the %s guild are near you. +//94: Incorrect name/ID, or no one from the guild is online. +94: Incorrect name/ID, or no one from the guild is online. +//95: All online characters of the %s party are near you. +95: All online characters of the %s party are near you. +//96: Incorrect name or ID, or no one from the party is online. +96: Incorrect name or ID, or no one from the party is online. +//97: Item database reloaded. +97: Item database reloaded. +//98: Monster database reloaded. +98: Monster database reloaded. +//99: Skill database reloaded. +99: Skill database reloaded. +//100: Scripts reloaded. +100: Scripts reloaded. +//101: Login-server asked to reload GM accounts and their level. +101: Login-server asked to reload GM accounts and their level. +//102: Mounted Peco. +102: Mounted Peco. +//103: No longer spying on the %s guild. +103: No longer spying on the %s guild. +//104: Spying on the %s guild. +104: Spying on the %s guild. +//105: No longer spying on the %s party. +105: No longer spying on the %s party. +//106: Spying on the %s party. +106: Spying on the %s party. +//107: All items have been repaired. +107: All items have been repaired. +//108: No item need to be repaired. +108: No item need to be repaired. +//109: Player has been nuked! +109: Player has been nuked! +//110: Npc Enabled. +110: Npc Enabled. +//111: This NPC doesn't exist. +111: This NPC doesn't exist. +//112: Npc Disabled. +112: Npc Disabled. +//113: %d item(s) removed by a GM. +113: %d item(s) removed by a GM. +//114: %d item(s) removed from the player. +114: %d item(s) removed from the player. +//115: %d item(s) removed. Player had only %d on %d items. +115: %d item(s) removed. Player had only %d on %d items. +//116: Character does not have the item. +116: Character does not have the item. +//117: GM has send you in jails. +117: GM has send you in jails. +//118: Player warped in jails. +118: Player warped in jails. +//119: This player is not in jails. +119: This player is not in jails. +//120: GM has discharge you. +120: GM has discharge you. +//121: Player warped to Prontera. +121: Player warped to Prontera. +//122: Disguise applied. +122: Disguise applied. +//123: Monster/NPC name/id hasn't been found. +123: Monster/NPC name/id hasn't been found. +//124: Undisguise applied. +124: Undisguise applied. +//125: You're not disguised. +125: You're not disguised. +//126: You accept any wisp (no wisper is refused). +126: You accept any wisp (no wisper is refused). +//127: You accept any wisp, except thoses from %d player(s): +127: You accept any wisp, except thoses from %d player(s): +//128: You refuse all wisps (no specifical wisper is refused). +128: You refuse all wisps (no specifical wisper is refused). +//129: You refuse all wisps, AND refuse wisps from %d player(s): +129: You refuse all wisps, AND refuse wisps from %d player(s): +//130: '%s' accept any wisp (no wisper is refused). +130: '%s' accept any wisp (no wisper is refused). +//131: '%s' accept any wisp, except thoses from %d player(s): +131: '%s' accept any wisp, except thoses from %d player(s): +//132: '%s' refuse all wisps (no specifical wisper is refused). +132: '%s' refuse all wisps (no specifical wisper is refused). +//133: '%s' refuse all wisps, AND refuse wisps from %d player(s): +133: '%s' refuse all wisps, AND refuse wisps from %d player(s): +//134: '%s' already accepts all wispers. +134: '%s' already accepts all wispers. +//135: '%s' now accepts all wispers. +135: '%s' now accepts all wispers. +//136: A GM has authorised all wispers for you. +136: A GM has authorised all wispers for you. +//137: '%s' already blocks all wispers. +137: '%s' already blocks all wispers. +//138: '%s' blocks now all wispers. +138: '%s' blocks now all wispers. +//139: A GM has blocked all wispers for you. +139: A GM has blocked all wispers for you. +//140: Character's disguise applied. +140: Character's disguise applied. +//141: Character's undisguise applied. +141: Character's undisguise applied. +//142: Character is not disguised. +142: Character is not disguised. +//143: Give a monster name/id please. +143: Give a monster name/id please. +//144: Invalid actual email. If you have default e-mail, type a@a.com. +144: Invalid actual email. If you have default e-mail, type a@a.com. +//145: Invalid new email. Please enter a real e-mail. +145: Invalid new email. Please enter a real e-mail. +//146: New email must be a real e-mail. +146: New email must be a real e-mail. +//147: New email must be different of the actual e-mail. +147: New email must be different of the actual e-mail. +//148: Information sended to login-server via char-server. +148: Information sended to login-server via char-server. +//149: Impossible to increase the number/value. +149: Impossible to increase the number/value. +//150: No GM found. +150: No GM found. +//151: 1 GM found. +151: 1 GM found. +//152: %d GMs found. +152: %d GMs found. +//153: %s is Unknown Command. +153: %s is Unknown Command. +//154: %s failed. +154: %s failed. +//155: Impossible to change your job. +155: Impossible to change your job. +//156: HP or/and SP modified. +156: HP or/and SP modified. +//157: HP and SP are already with the good value. +157: HP and SP are already with the good value. +//158: Base level can't go any lower. +158: Base level can't go any lower. +//159: Job level can't go any lower. +159: Job level can't go any lower. +//160: PvP is already Off. +160: PvP is already Off. +//161: PvP is already On. +161: PvP is already On. +//162: GvG is already Off. +162: GvG is already Off. +//163: GvG is already On. +163: GvG is already On. +//164: Your memo point #%d doesn't exist. +164: Your memo point #%d doesn't exist. +//165: All monsters killed! +165: All monsters killed! +//166: No item has been refined! +166: No item has been refined! +//167: 1 item has been refined! +167: 1 item has been refined! +//168: %d items have been refined! +168: %d items have been refined! +//169: This item (%d: '%s') is not an equipment. +169: This item (%d: '%s') is not an equipment. +//170: This item is not an equipment. +170: This item is not an equipment. +//171: %d - void +171: %d - void +//172: You replace previous memo position %d - %s (%d,%d). +172: You replace previous memo position %d - %s (%d,%d). +//173: Note: you don't have the 'Warp' skill level to use it. +173: Note: you don't have the 'Warp' skill level to use it. +//174: Number of status points changed! +174: Number of status points changed! +//175: Number of skill points changed! +175: Number of skill points changed! +//176: Number of zenys changed! +176: Number of zenys changed! +//177: Impossible to decrease a stat. +177: Impossible to decrease a stat. +//178: Impossible to increase a stat. +178: Impossible to increase a stat. +//179: Guild level changed. +179: Guild level changed. +//180: The monter/egg name/id doesn't exist. +180: The monter/egg name/id doesn't exist. +//181: You already have a pet. +181: You already have a pet. +//182: Pet friendly value changed! +182: Pet friendly value changed! +//183: Pet friendly is already the good value. +183: Pet friendly is already the good value. +//184: Sorry, but you have no pet. +184: Sorry, but you have no pet. +//185: Pet hungry value changed! +185: Pet hungry value changed! +//186: Pet hungry is already the good value. +186: Pet hungry is already the good value. +//187: You can now rename your pet. +187: You can now rename your pet. +//188: You can already rename your pet. +188: You can already rename your pet. +//189: This player can now rename his/her pet. +189: This player can now rename his/her pet. +//190: This player can already rename his/her pet. +190: This player can already rename his/her pet. +//191: Sorry, but this player has no pet. +191: Sorry, but this player has no pet. +//192: Impossible to change the character's job. +192: Impossible to change the character's job. +//193: Character's base level can't go any lower. +193: Character's base level can't go any lower. +//194: Character's job level can't go any lower. +194: Character's job level can't go any lower. +//195: All players have been kicked! +195: All players have been kicked! +//196: You already have this quest skill. +196: You already have this quest skill. +//197: This skill number doesn't exist or isn't a quest skill. +197: This skill number doesn't exist or isn't a quest skill. +//198: This skill number doesn't exist. +198: This skill number doesn't exist. +//199: This player has learned the skill. +199: This player has learned the skill. +//200: This player already has this quest skill. +200: This player already has this quest skill. +//201: You don't have this quest skill. +201: You don't have this quest skill. +//202: This player has forgotten the skill. +202: This player has forgotten the skill. +//203: This player doesn't have this quest skill. +203: This player doesn't have this quest skill. +//204: WARNING: more than 1000 spiritballs can CRASH your server and/or client! +204: WARNING: more than 1000 spiritballs can CRASH your server and/or client! +//205: You already have this number of spiritballs. +205: You already have this number of spiritballs. +//206: '%s' skill points reseted! +206: '%s' skill points reseted! +//207: '%s' stats points reseted! +207: '%s' stats points reseted! +//208: '%s' skill and stats points reseted! +208: '%s' skill and stats points reseted! +//209: Character's number of skill points changed! +209: Character's number of skill points changed! +//210: Character's number of status points changed! +210: Character's number of status points changed! +//211: Character's number of zenys changed! +211: Character's number of zenys changed! +//212: Cannot mount a Peco while in disguise. +212: Cannot mount a Peco while in disguise. +//213: You can not mount a peco with your job. +213: You can not mount a peco with your job. +//214: Unmounted Peco. +214: Unmounted Peco. +//215: This player cannot mount a Peco while in disguise. +215: This player cannot mount a Peco while in disguise. +//216: Now, this player mounts a peco. +216: Now, this player mounts a peco. +//217: This player can not mount a peco with his/her job. +217: This player can not mount a peco with his/her job. +//218: Now, this player has not more peco. +218: Now, this player has not more peco. +//219: %d day +219: %d day +//220: %d days +220: %d days +//221: %s %d hour +221: %s %d hour +//222: %s %d hours +222: %s %d hours +//223: %s %d minute +223: %s %d minute +//224: %s %d minutes +224: %s %d minutes +//225: %s and %d second +225: %s and %d second +//226: %s and %d seconds +226: %s and %d seconds +//227: Cannot wear disguise while riding a Peco. +227: Cannot wear disguise while riding a Peco. +//228: Character cannot wear disguise while riding a Peco. +228: Character cannot wear disguise while riding a Peco. +//229: Your Effect Has Changed. +229: Your Effect Has Changed. +//230: Server time (normal time): %A, %B %d %Y %X. +230: Server time (normal time): %A, %B %d %Y %X. +//231: Game time: The game is in permanent daylight. +231: Game time: The game is in permanent daylight. +//232: Game time: The game is in permanent night. +232: Game time: The game is in permanent night. +//233: Game time: The game is actualy in night for %s. +233: Game time: The game is actualy in night for %s. +//234: Game time: After, the game will be in permanent daylight. +234: Game time: After, the game will be in permanent daylight. +//235: Game time: The game is actualy in daylight for %s. +235: Game time: The game is actualy in daylight for %s. +//236: Game time: After, the game will be in permanent night. +236: Game time: After, the game will be in permanent night. +//237: Game time: After, the game will be in night for %s. +237: Game time: After, the game will be in night for %s. +//238: Game time: A day cycle has a normal duration of %s. +238: Game time: A day cycle has a normal duration of %s. +//239: Game time: After, the game will be in daylight for %s. +239: Game time: After, the game will be in daylight for %s. +//240: %d monster(s) summoned! +240: %d monster(s) summoned! +241: you be a killa.. +242: you gonna be own3d.. +243: Map skills are off +244: Map skills are on + +// Messages of others (not for GM commands) +// ---------------------------------------- + +//500: Actually, it's the night... +500: Actually, it's the night... +//501: Your account time limit is: %d-%m-%Y %H:%M:%S. +501: Your account time limit is: %d-%m-%Y %H:%M:%S. +//502: The day has arrived! +502: The day has arrived! +//503: The night has fallen... +503: The night has fallen... diff --git a/conf/script_athena.conf b/conf/script_athena.conf new file mode 100644 index 0000000..21d4a6f --- /dev/null +++ b/conf/script_athena.conf @@ -0,0 +1,2 @@ +// When choosing those which it refines setting the letter which is indicated. (Those for word use other than Japanese?) +refine_posword: Head,Body,Left hand,Right hand,Robe,Shoes,Accessory 1,Accessory 2,Head 2,Head 3,Not Equipped diff --git a/conf/water.txt b/conf/water.txt new file mode 100644 index 0000000..71f9847 --- /dev/null +++ b/conf/water.txt @@ -0,0 +1,54 @@ +// 水の設定。 +// waterは水ありでall_waterは全て水だと判断するマップ。 + +mjolnir_12.gat mapflag water +mjolnir_02.gat mapflag water +glast_01.gat mapflag water +gef_fild04.gat mapflag water +prt_fild02.gat mapflag water +prt_fild01.gat mapflag water +prt_fild00.gat mapflag water +gef_fild00.gat mapflag water +gef_fild07.gat mapflag water +gef_fild13.gat mapflag water +gef_fild09.gat mapflag water +gef_fild01.gat mapflag water +prt_fild05.gat mapflag water +gef_fild03.gat mapflag water +gef_fild10.gat mapflag water +prt_fild10.gat mapflag water +pay_arche.gat mapflag water +moc_ruins.gat mapflag water +comodo.gat mapflag water +cmd_fild01.gat mapflag water +cmd_fild02.gat mapflag water +cmd_fild03.gat mapflag water +cmd_fild04.gat mapflag water +cmd_fild05.gat mapflag water +moc_fild11.gat mapflag water +ama_fild01.gat mapflag water + +iz_dun00.gat mapflag water +iz_dun01.gat mapflag water +iz_dun02.gat mapflag water +//iz_dun03.gat mapflag all_water +//iz_dun04.gat mapflag all_water +treasure01.gat mapflag water +treasure02.gat mapflag water +mjo_dun01.gat mapflag water +orcsdun02.gat mapflag water +pay_dun01.gat mapflag water +pay_dun02.gat mapflag water +pay_dun03.gat mapflag water +prt_sewb2.gat mapflag water +prt_sewb3.gat mapflag water +gl_prison1.gat mapflag water +alde_dun03.gat mapflag water +alde_dun04.gat mapflag water +beach_dun.gat mapflag water +beach_dun2.gat mapflag water +beach_dun3.gat mapflag water +tur_dun01.gat mapflag water +gld_dun02.gat mapflag water +gld_dun03.gat mapflag water +gld_dun04.gat mapflag water diff --git a/conf/water_height.txt b/conf/water_height.txt new file mode 100644 index 0000000..9a89e29 --- /dev/null +++ b/conf/water_height.txt @@ -0,0 +1,68 @@ +// 水場の高さを設定 +//water_height.txt@AthenaDB計画 2004/03/31 18:52:09 +0900 (JST) + +xmas.gat 3 +mjolnir_01.gat 0 +mjolnir_02.gat -19 +mjolnir_12.gat 15 +prt_fild00.gat 11 +prt_fild01.gat 25 +prt_fild02.gat 42 +prt_fild04.gat 14 +prt_fild05.gat 14 +prt_fild10.gat 40 +gef_fild00.gat 10 +gef_fild01.gat 14 +gef_fild03.gat 82 +gef_fild04.gat 14 +gef_fild07.gat 19 +gef_fild09.gat 11 +gef_fild10.gat 24 +moc_fild01.gat 26 +moc_fild11.gat 9 +iz_dun00.gat 5 +iz_dun01.gat 5 +iz_dun02.gat -58 +mjo_dun01.gat 7 +orcsdun02.gat 3 +pay_dun01.gat 8 +pay_dun02.gat 5 +pay_dun03.gat 10 +prt_sewb2.gat 5 +prt_sewb3.gat 5 +treasure01.gat -4 +treasure02.gat -1 +moc_ruins.gat 6 +pay_arche.gat 8 +glast_01.gat 8 +alde_dun03.gat 2 +alde_dun04.gat 0 +gl_prison1.gat 35 +gl_sew01.gat 56 +gl_sew02.gat 12 +gl_sew03.gat 15 +gl_sew04.gat 70 +comodo.gat 14 +cmd_fild01.gat 46 +cmd_fild02.gat 4 +cmd_fild03.gat 0 +cmd_fild04.gat 4 +cmd_fild05.gat 46 +beach_dun2.gat 6 +beach_dun3.gat 0 +beach_dun.gat 9 +gef_fild13.gat 19 +gld_dun02.gat 5 +gld_dun03.gat 14 +gld_dun04.gat 3 +aldeg_cas01.gat 40 +aldeg_cas02.gat 35 +aldeg_cas03.gat 16 +aldeg_cas04.gat 31 +aldeg_cas05.gat 25 +gefg_cas02.gat 8 +gefg_cas04.gat 15 +gefg_cas05.gat 5 +prtg_cas05.gat 13 +tur_dun01.gat -65 +ama_fild01.gat 5 diff --git a/data/new_1-1.wlk b/data/new_1-1.wlk Binary files differnew file mode 100644 index 0000000..f6faa2a --- /dev/null +++ b/data/new_1-1.wlk diff --git a/data/new_10-1.wlk b/data/new_10-1.wlk Binary files differnew file mode 100644 index 0000000..94373c3 --- /dev/null +++ b/data/new_10-1.wlk diff --git a/data/new_2-1.wlk b/data/new_2-1.wlk Binary files differnew file mode 100644 index 0000000..961e6e4 --- /dev/null +++ b/data/new_2-1.wlk diff --git a/data/new_3-1.wlk b/data/new_3-1.wlk Binary files differnew file mode 100644 index 0000000..b7d006d --- /dev/null +++ b/data/new_3-1.wlk diff --git a/data/new_4-1.wlk b/data/new_4-1.wlk Binary files differnew file mode 100644 index 0000000..de55e4e --- /dev/null +++ b/data/new_4-1.wlk diff --git a/data/new_5-1.wlk b/data/new_5-1.wlk Binary files differnew file mode 100644 index 0000000..d4cd889 --- /dev/null +++ b/data/new_5-1.wlk diff --git a/data/new_6-1.wlk b/data/new_6-1.wlk Binary files differnew file mode 100644 index 0000000..dcfa789 --- /dev/null +++ b/data/new_6-1.wlk diff --git a/data/new_7-1.wlk b/data/new_7-1.wlk Binary files differnew file mode 100644 index 0000000..ddcfbe4 --- /dev/null +++ b/data/new_7-1.wlk diff --git a/data/new_8-1.wlk b/data/new_8-1.wlk Binary files differnew file mode 100644 index 0000000..2756267 --- /dev/null +++ b/data/new_8-1.wlk diff --git a/data/new_9-1.wlk b/data/new_9-1.wlk Binary files differnew file mode 100644 index 0000000..d0e3aea --- /dev/null +++ b/data/new_9-1.wlk diff --git a/data/resnametable.txt b/data/resnametable.txt new file mode 100644 index 0000000..25dbeef --- /dev/null +++ b/data/resnametable.txt @@ -0,0 +1,607 @@ +new_1-1.gnd#new_zone01.gnd# +new_2-1.gnd#new_zone01.gnd# +new_3-1.gnd#new_zone01.gnd# +new_4-1.gnd#new_zone01.gnd# +new_5-1.gnd#new_zone01.gnd# +new_1-2.gnd#new_zone02.gnd# +new_2-2.gnd#new_zone02.gnd# +new_3-2.gnd#new_zone02.gnd# +new_4-2.gnd#new_zone02.gnd# +new_5-2.gnd#new_zone02.gnd# +new_1-3.gnd#new_zone03.gnd# +new_2-3.gnd#new_zone03.gnd# +new_3-3.gnd#new_zone03.gnd# +new_4-3.gnd#new_zone03.gnd# +new_5-3.gnd#new_zone03.gnd# +new_1-4.gnd#new_zone04.gnd# +new_2-4.gnd#new_zone04.gnd# +new_3-4.gnd#new_zone04.gnd# +new_4-4.gnd#new_zone04.gnd# +new_5-4.gnd#new_zone04.gnd# +force_1-1.gnd#force_map1.gnd# +force_2-1.gnd#force_map1.gnd# +force_3-1.gnd#force_map1.gnd# +force_1-2.gnd#force_map2.gnd# +force_2-2.gnd#force_map2.gnd# +force_3-2.gnd#force_map2.gnd# +force_1-3.gnd#force_map3.gnd# +force_2-3.gnd#force_map3.gnd# +force_3-3.gnd#force_map3.gnd# +hunter_1-1.gnd#job_hunter.gnd# +hunter_2-1.gnd#job_hunter.gnd# +hunter_3-1.gnd#job_hunter.gnd# +knight_1-1.gnd#job_knight.gnd# +knight_2-1.gnd#job_knight.gnd# +knight_3-1.gnd#job_knight.gnd# +priest_1-1.gnd#job_priest.gnd# +priest_2-1.gnd#job_priest.gnd# +priest_3-1.gnd#job_priest.gnd# +sword_1-1.gnd#job_sword1.gnd# +sword_2-1.gnd#job_sword1.gnd# +sword_3-1.gnd#job_sword1.gnd# +wizard_1-1.gnd#job_wizard.gnd# +wizard_2-1.gnd#job_wizard.gnd# +wizard_3-1.gnd#job_wizard.gnd# +ordeal_1-1.gnd#ordeal_a00.gnd# +ordeal_2-1.gnd#ordeal_a00.gnd# +ordeal_3-1.gnd#ordeal_a00.gnd# +ordeal_1-2.gnd#ordeal_a02.gnd# +ordeal_2-2.gnd#ordeal_a02.gnd# +ordeal_3-2.gnd#ordeal_a02.gnd# +ordeal_1-3.gnd#ordeal_a00.gnd# +ordeal_2-3.gnd#ordeal_a00.gnd# +ordeal_3-3.gnd#ordeal_a00.gnd# +ordeal_1-4.gnd#ordeal_a02.gnd# +ordeal_2-4.gnd#ordeal_a02.gnd# +ordeal_3-4.gnd#ordeal_a02.gnd# +new_1-1.gat#new_1-1.wlk# +new_2-1.gat#new_2-1.wlk# +new_3-1.gat#new_3-1.wlk# +new_4-1.gat#new_4-1.wlk# +new_5-1.gat#new_5-1.wlk# +new_6-1.gat#new_6-1.wlk# +new_7-1.gat#new_7-1.wlk# +new_8-1.gat#new_8-1.wlk# +new_9-1.gat#new_9-1.wlk# +new_10-1.gat#new_10-1.wlk# +new_1-2.gat#new_zone02.gat# +new_2-2.gat#new_zone02.gat# +new_3-2.gat#new_zone02.gat# +new_4-2.gat#new_zone02.gat# +new_5-2.gat#new_zone02.gat# +new_1-3.gat#new_zone03.gat# +new_2-3.gat#new_zone03.gat# +new_3-3.gat#new_zone03.gat# +new_4-3.gat#new_zone03.gat# +new_5-3.gat#new_zone03.gat# +new_1-4.gat#new_zone04.gat# +new_2-4.gat#new_zone04.gat# +new_3-4.gat#new_zone04.gat# +new_4-4.gat#new_zone04.gat# +new_5-4.gat#new_zone04.gat# +force_1-1.gat#force_map1.gat# +force_2-1.gat#force_map1.gat# +force_3-1.gat#force_map1.gat# +force_1-2.gat#force_map2.gat# +force_2-2.gat#force_map2.gat# +force_3-2.gat#force_map2.gat# +force_1-3.gat#force_map3.gat# +force_2-3.gat#force_map3.gat# +force_3-3.gat#force_map3.gat# +hunter_1-1.gat#job_hunter.gat# +hunter_2-1.gat#job_hunter.gat# +hunter_3-1.gat#job_hunter.gat# +knight_1-1.gat#job_knight.gat# +knight_2-1.gat#job_knight.gat# +knight_3-1.gat#job_knight.gat# +priest_1-1.gat#job_priest.gat# +priest_2-1.gat#job_priest.gat# +priest_3-1.gat#job_priest.gat# +sword_1-1.gat#job_sword1.gat# +sword_2-1.gat#job_sword1.gat# +sword_3-1.gat#job_sword1.gat# +wizard_1-1.gat#job_wizard.gat# +wizard_2-1.gat#job_wizard.gat# +wizard_3-1.gat#job_wizard.gat# +ordeal_1-1.gat#ordeal_a00.gat# +ordeal_2-1.gat#ordeal_a00.gat# +ordeal_3-1.gat#ordeal_a00.gat# +ordeal_1-2.gat#ordeal_a02.gat# +ordeal_2-2.gat#ordeal_a02.gat# +ordeal_3-2.gat#ordeal_a02.gat# +ordeal_1-3.gat#ordeal_a03.gat# +ordeal_2-3.gat#ordeal_a03.gat# +ordeal_3-3.gat#ordeal_a03.gat# +ordeal_1-4.gat#ordeal_a04.gat# +ordeal_2-4.gat#ordeal_a04.gat# +ordeal_3-4.gat#ordeal_a04.gat# +new_1-1.rsw#new_zone01.rsw# +new_2-1.rsw#new_zone01.rsw# +new_3-1.rsw#new_zone01.rsw# +new_4-1.rsw#new_zone01.rsw# +new_5-1.rsw#new_zone01.rsw# +new_1-2.rsw#new_zone02.rsw# +new_2-2.rsw#new_zone02.rsw# +new_3-2.rsw#new_zone02.rsw# +new_4-2.rsw#new_zone02.rsw# +new_5-2.rsw#new_zone02.rsw# +new_1-3.rsw#new_zone03.rsw# +new_2-3.rsw#new_zone03.rsw# +new_3-3.rsw#new_zone03.rsw# +new_4-3.rsw#new_zone03.rsw# +new_5-3.rsw#new_zone03.rsw# +new_1-4.rsw#new_zone04.rsw# +new_2-4.rsw#new_zone04.rsw# +new_3-4.rsw#new_zone04.rsw# +new_4-4.rsw#new_zone04.rsw# +new_5-4.rsw#new_zone04.rsw# +force_1-1.rsw#force_map1.rsw# +force_2-1.rsw#force_map1.rsw# +force_3-1.rsw#force_map1.rsw# +force_1-2.rsw#force_map2.rsw# +force_2-2.rsw#force_map2.rsw# +force_3-2.rsw#force_map2.rsw# +force_1-3.rsw#force_map3.rsw# +force_2-3.rsw#force_map3.rsw# +force_3-3.rsw#force_map3.rsw# +hunter_1-1.rsw#job_hunter.rsw# +hunter_2-1.rsw#job_hunter.rsw# +hunter_3-1.rsw#job_hunter.rsw# +knight_1-1.rsw#job_knight.rsw# +knight_2-1.rsw#job_knight.rsw# +knight_3-1.rsw#job_knight.rsw# +priest_1-1.rsw#job_priest.rsw# +priest_2-1.rsw#job_priest.rsw# +priest_3-1.rsw#job_priest.rsw# +sword_1-1.rsw#job_sword1.rsw# +sword_2-1.rsw#job_sword1.rsw# +sword_3-1.rsw#job_sword1.rsw# +thief_1-1.rsw#job_thief1.rsw# +thief_2-1.rsw#job_thief1.rsw# +thief_3-1.rsw#job_thief1.rsw# +wizard_1-1.rsw#job_wizard.rsw# +wizard_2-1.rsw#job_wizard.rsw# +wizard_3-1.rsw#job_wizard.rsw# +ordeal_1-1.rsw#ordeal_a00.rsw# +ordeal_2-1.rsw#ordeal_a00.rsw# +ordeal_3-1.rsw#ordeal_a00.rsw# +ordeal_1-2.rsw#ordeal_a02.rsw# +ordeal_2-2.rsw#ordeal_a02.rsw# +ordeal_3-2.rsw#ordeal_a02.rsw# +ordeal_1-3.rsw#ordeal_a03.rsw# +ordeal_2-3.rsw#ordeal_a03.rsw# +ordeal_3-3.rsw#ordeal_a03.rsw# +ordeal_1-4.rsw#ordeal_a04.rsw# +ordeal_2-4.rsw#ordeal_a04.rsw# +ordeal_3-4.rsw#ordeal_a04.rsw# +pvp_y_1-1.gat#prontera.gat# +pvp_y_2-1.gat#prontera.gat# +pvp_y_3-1.gat#prontera.gat# +pvp_y_4-1.gat#prontera.gat# +pvp_y_5-1.gat#prontera.gat# +pvp_y_6-1.gat#prontera.gat# +pvp_y_7-1.gat#prontera.gat# +pvp_y_8-1.gat#prontera.gat# +pvp_y_1-2.gat#izlude.gat# +pvp_y_2-2.gat#izlude.gat# +pvp_y_3-2.gat#izlude.gat# +pvp_y_4-2.gat#izlude.gat# +pvp_y_5-2.gat#izlude.gat# +pvp_y_6-2.gat#izlude.gat# +pvp_y_7-2.gat#izlude.gat# +pvp_y_8-2.gat#izlude.gat# +pvp_y_1-3.gat#payon.gat# +pvp_y_2-3.gat#payon.gat# +pvp_y_3-3.gat#payon.gat# +pvp_y_4-3.gat#payon.gat# +pvp_y_5-3.gat#payon.gat# +pvp_y_6-3.gat#payon.gat# +pvp_y_7-3.gat#payon.gat# +pvp_y_8-3.gat#payon.gat# +pvp_y_1-4.gat#alberta.gat# +pvp_y_2-4.gat#alberta.gat# +pvp_y_3-4.gat#alberta.gat# +pvp_y_4-4.gat#alberta.gat# +pvp_y_5-4.gat#alberta.gat# +pvp_y_6-4.gat#alberta.gat# +pvp_y_7-4.gat#alberta.gat# +pvp_y_8-4.gat#alberta.gat# +pvp_y_1-5.gat#morocc.gat# +pvp_y_2-5.gat#morocc.gat# +pvp_y_3-5.gat#morocc.gat# +pvp_y_4-5.gat#morocc.gat# +pvp_y_5-5.gat#morocc.gat# +pvp_y_6-5.gat#morocc.gat# +pvp_y_7-5.gat#morocc.gat# +pvp_y_8-5.gat#morocc.gat# +pvp_y_1-1.gnd#prontera.gnd# +pvp_y_2-1.gnd#prontera.gnd# +pvp_y_3-1.gnd#prontera.gnd# +pvp_y_4-1.gnd#prontera.gnd# +pvp_y_5-1.gnd#prontera.gnd# +pvp_y_6-1.gnd#prontera.gnd# +pvp_y_7-1.gnd#prontera.gnd# +pvp_y_8-1.gnd#prontera.gnd# +pvp_y_1-2.gnd#izlude.gnd# +pvp_y_2-2.gnd#izlude.gnd# +pvp_y_3-2.gnd#izlude.gnd# +pvp_y_4-2.gnd#izlude.gnd# +pvp_y_5-2.gnd#izlude.gnd# +pvp_y_6-2.gnd#izlude.gnd# +pvp_y_7-2.gnd#izlude.gnd# +pvp_y_8-2.gnd#izlude.gnd# +pvp_y_1-3.gnd#payon.gnd# +pvp_y_2-3.gnd#payon.gnd# +pvp_y_3-3.gnd#payon.gnd# +pvp_y_4-3.gnd#payon.gnd# +pvp_y_5-3.gnd#payon.gnd# +pvp_y_6-3.gnd#payon.gnd# +pvp_y_7-3.gnd#payon.gnd# +pvp_y_8-3.gnd#payon.gnd# +pvp_y_1-4.gnd#alberta.gnd# +pvp_y_2-4.gnd#alberta.gnd# +pvp_y_3-4.gnd#alberta.gnd# +pvp_y_4-4.gnd#alberta.gnd# +pvp_y_5-4.gnd#alberta.gnd# +pvp_y_6-4.gnd#alberta.gnd# +pvp_y_7-4.gnd#alberta.gnd# +pvp_y_8-4.gnd#alberta.gnd# +pvp_y_1-5.gnd#morocc.gnd# +pvp_y_2-5.gnd#morocc.gnd# +pvp_y_3-5.gnd#morocc.gnd# +pvp_y_4-5.gnd#morocc.gnd# +pvp_y_5-5.gnd#morocc.gnd# +pvp_y_6-5.gnd#morocc.gnd# +pvp_y_7-5.gnd#morocc.gnd# +pvp_y_8-5.gnd#morocc.gnd# +pvp_y_1-1.rsw#prontera.rsw# +pvp_y_2-1.rsw#prontera.rsw# +pvp_y_3-1.rsw#prontera.rsw# +pvp_y_4-1.rsw#prontera.rsw# +pvp_y_5-1.rsw#prontera.rsw# +pvp_y_6-1.rsw#prontera.rsw# +pvp_y_7-1.rsw#prontera.rsw# +pvp_y_8-1.rsw#prontera.rsw# +pvp_y_1-2.rsw#izlude.rsw# +pvp_y_2-2.rsw#izlude.rsw# +pvp_y_3-2.rsw#izlude.rsw# +pvp_y_4-2.rsw#izlude.rsw# +pvp_y_5-2.rsw#izlude.rsw# +pvp_y_6-2.rsw#izlude.rsw# +pvp_y_7-2.rsw#izlude.rsw# +pvp_y_8-2.rsw#izlude.rsw# +pvp_y_1-3.rsw#payon.rsw# +pvp_y_2-3.rsw#payon.rsw# +pvp_y_3-3.rsw#payon.rsw# +pvp_y_4-3.rsw#payon.rsw# +pvp_y_5-3.rsw#payon.rsw# +pvp_y_6-3.rsw#payon.rsw# +pvp_y_7-3.rsw#payon.rsw# +pvp_y_8-3.rsw#payon.rsw# +pvp_y_1-4.rsw#alberta.rsw# +pvp_y_2-4.rsw#alberta.rsw# +pvp_y_3-4.rsw#alberta.rsw# +pvp_y_4-4.rsw#alberta.rsw# +pvp_y_5-4.rsw#alberta.rsw# +pvp_y_6-4.rsw#alberta.rsw# +pvp_y_7-4.rsw#alberta.rsw# +pvp_y_8-4.rsw#alberta.rsw# +pvp_y_1-5.rsw#morocc.rsw# +pvp_y_2-5.rsw#morocc.rsw# +pvp_y_3-5.rsw#morocc.rsw# +pvp_y_4-5.rsw#morocc.rsw# +pvp_y_5-5.rsw#morocc.rsw# +pvp_y_6-5.rsw#morocc.rsw# +pvp_y_7-5.rsw#morocc.rsw# +pvp_y_8-5.rsw#morocc.rsw# +pvp_n_1-1.gnd#prt_maze02.gnd# +pvp_n_2-1.gnd#prt_maze02.gnd# +pvp_n_3-1.gnd#prt_maze02.gnd# +pvp_n_4-1.gnd#prt_maze02.gnd# +pvp_n_5-1.gnd#prt_maze02.gnd# +pvp_n_6-1.gnd#prt_maze02.gnd# +pvp_n_7-1.gnd#prt_maze02.gnd# +pvp_n_8-1.gnd#prt_maze02.gnd# +pvp_n_1-2.gnd#job_hunter.gnd# +pvp_n_2-2.gnd#job_hunter.gnd# +pvp_n_3-2.gnd#job_hunter.gnd# +pvp_n_4-2.gnd#job_hunter.gnd# +pvp_n_5-2.gnd#job_hunter.gnd# +pvp_n_6-2.gnd#job_hunter.gnd# +pvp_n_7-2.gnd#job_hunter.gnd# +pvp_n_8-2.gnd#job_hunter.gnd# +pvp_n_1-3.gnd#job_wizard.gnd# +pvp_n_2-3.gnd#job_wizard.gnd# +pvp_n_3-3.gnd#job_wizard.gnd# +pvp_n_4-3.gnd#job_wizard.gnd# +pvp_n_5-3.gnd#job_wizard.gnd# +pvp_n_6-3.gnd#job_wizard.gnd# +pvp_n_7-3.gnd#job_wizard.gnd# +pvp_n_8-3.gnd#job_wizard.gnd# +pvp_n_1-4.gnd#job_priest.gnd# +pvp_n_2-4.gnd#job_priest.gnd# +pvp_n_3-4.gnd#job_priest.gnd# +pvp_n_4-4.gnd#job_priest.gnd# +pvp_n_5-4.gnd#job_priest.gnd# +pvp_n_6-4.gnd#job_priest.gnd# +pvp_n_7-4.gnd#job_priest.gnd# +pvp_n_8-4.gnd#job_priest.gnd# +pvp_n_1-5.gnd#job_knight.gnd# +pvp_n_2-5.gnd#job_knight.gnd# +pvp_n_3-5.gnd#job_knight.gnd# +pvp_n_4-5.gnd#job_knight.gnd# +pvp_n_5-5.gnd#job_knight.gnd# +pvp_n_6-5.gnd#job_knight.gnd# +pvp_n_7-5.gnd#job_knight.gnd# +pvp_n_8-5.gnd#job_knight.gnd# +pvp_n_1-1.gat#prt_maze02.gat# +pvp_n_2-1.gat#prt_maze02.gat# +pvp_n_3-1.gat#prt_maze02.gat# +pvp_n_4-1.gat#prt_maze02.gat# +pvp_n_5-1.gat#prt_maze02.gat# +pvp_n_6-1.gat#prt_maze02.gat# +pvp_n_7-1.gat#prt_maze02.gat# +pvp_n_8-1.gat#prt_maze02.gat# +pvp_n_1-2.gat#job_hunter.gat# +pvp_n_2-2.gat#job_hunter.gat# +pvp_n_3-2.gat#job_hunter.gat# +pvp_n_4-2.gat#job_hunter.gat# +pvp_n_5-2.gat#job_hunter.gat# +pvp_n_6-2.gat#job_hunter.gat# +pvp_n_7-2.gat#job_hunter.gat# +pvp_n_8-2.gat#job_hunter.gat# +pvp_n_1-3.gat#job_wizard.gat# +pvp_n_2-3.gat#job_wizard.gat# +pvp_n_3-3.gat#job_wizard.gat# +pvp_n_4-3.gat#job_wizard.gat# +pvp_n_5-3.gat#job_wizard.gat# +pvp_n_6-3.gat#job_wizard.gat# +pvp_n_7-3.gat#job_wizard.gat# +pvp_n_8-3.gat#job_wizard.gat# +pvp_n_1-4.gat#job_priest.gat# +pvp_n_2-4.gat#job_priest.gat# +pvp_n_3-4.gat#job_priest.gat# +pvp_n_4-4.gat#job_priest.gat# +pvp_n_5-4.gat#job_priest.gat# +pvp_n_6-4.gat#job_priest.gat# +pvp_n_7-4.gat#job_priest.gat# +pvp_n_8-4.gat#job_priest.gat# +pvp_n_1-5.gat#job_knight.gat# +pvp_n_2-5.gat#job_knight.gat# +pvp_n_3-5.gat#job_knight.gat# +pvp_n_4-5.gat#job_knight.gat# +pvp_n_5-5.gat#job_knight.gat# +pvp_n_6-5.gat#job_knight.gat# +pvp_n_7-5.gat#job_knight.gat# +pvp_n_8-5.gat#job_knight.gat# +pvp_n_1-1.rsw#prt_maze02.rsw# +pvp_n_2-1.rsw#prt_maze02.rsw# +pvp_n_3-1.rsw#prt_maze02.rsw# +pvp_n_4-1.rsw#prt_maze02.rsw# +pvp_n_5-1.rsw#prt_maze02.rsw# +pvp_n_6-1.rsw#prt_maze02.rsw# +pvp_n_7-1.rsw#prt_maze02.rsw# +pvp_n_8-1.rsw#prt_maze02.rsw# +pvp_n_1-2.rsw#job_hunter.rsw# +pvp_n_2-2.rsw#job_hunter.rsw# +pvp_n_3-2.rsw#job_hunter.rsw# +pvp_n_4-2.rsw#job_hunter.rsw# +pvp_n_5-2.rsw#job_hunter.rsw# +pvp_n_6-2.rsw#job_hunter.rsw# +pvp_n_7-2.rsw#job_hunter.rsw# +pvp_n_8-2.rsw#job_hunter.rsw# +pvp_n_1-3.rsw#job_wizard.rsw# +pvp_n_2-3.rsw#job_wizard.rsw# +pvp_n_3-3.rsw#job_wizard.rsw# +pvp_n_4-3.rsw#job_wizard.rsw# +pvp_n_5-3.rsw#job_wizard.rsw# +pvp_n_6-3.rsw#job_wizard.rsw# +pvp_n_7-3.rsw#job_wizard.rsw# +pvp_n_8-3.rsw#job_wizard.rsw# +pvp_n_1-4.rsw#job_priest.rsw# +pvp_n_2-4.rsw#job_priest.rsw# +pvp_n_3-4.rsw#job_priest.rsw# +pvp_n_4-4.rsw#job_priest.rsw# +pvp_n_5-4.rsw#job_priest.rsw# +pvp_n_6-4.rsw#job_priest.rsw# +pvp_n_7-4.rsw#job_priest.rsw# +pvp_n_8-4.rsw#job_priest.rsw# +pvp_n_1-5.rsw#job_knight.rsw# +pvp_n_2-5.rsw#job_knight.rsw# +pvp_n_3-5.rsw#job_knight.rsw# +pvp_n_4-5.rsw#job_knight.rsw# +pvp_n_5-5.rsw#job_knight.rsw# +pvp_n_6-5.rsw#job_knight.rsw# +pvp_n_7-5.rsw#job_knight.rsw# +pvp_n_8-5.rsw#job_knight.rsw# +pvp_n_room.gnd#pvp_room.gnd# +pvp_n_room.gat#pvp_room.gat# +pvp_n_room.rsw#pvp_room.rsw# +タッタタホナヘニ菎フスコ\map\pvp_n_room.bmp#タッタタホナヘニ菎フスコ\map\pvp_room.bmp# +pvp_y_room.gnd#pvp_room.gnd# +pvp_y_room.gat#pvp_room.gat# +pvp_y_room.rsw#pvp_room.rsw# +タッタタホナヘニ菎フスコ\map\pvp_y_room.bmp#タッタタホナヘニ菎フスコ\map\pvp_room.bmp# +pvp_c_room.gnd#pvp_room.gnd# +pvp_c_room.gat#pvp_room.gat# +pvp_c_room.rsw#pvp_room.rsw# + + +guild_room.gnd#quiz_01.gnd# +guild_room.gat#quiz_01.gat# +guild_room.rsw#quiz_01.rsw# +タッタタホナヘニ菎フスコ\map\guild_room.bmp#タッタタホナヘニ菎フスコ\map\quiz_01.bmp# + +nguild_prt.rsw#prtg_cas01.rsw# +nguild_prt.gat#prtg_cas01.gat# +nguild_prt.gnd#prtg_cas01.gnd# +タッタタホナヘニ菎フスコ\map\nguild_prt.bmp#タッタタホナヘニ菎フスコ\map\prtg_cas01.bmp# + +nguild_pay.rsw#payg_cas01.rsw# +nguild_pay.gat#payg_cas01.gat# +nguild_pay.gnd#payg_cas01.gnd# +タッタタホナヘニ菎フスコ\map\nguild_pay.bmp#タッタタホナヘニ菎フスコ\map\payg_cas01.bmp# + +nguild_alde.rsw#aldeg_cas01.rsw# +nguild_alde.gat#aldeg_cas01.gat# +nguild_alde.gnd#aldeg_cas01.gnd# +タッタタホナヘニ菎フスコ\map\nguild_alde.bmp#タッタタホナヘニ菎フスコ\map\aldeg_cas01.bmp# + +nguild_gef.rsw#gefg_cas01.rsw# +nguild_gef.gat#gefg_cas01.gat# +nguild_gef.gnd#gefg_cas01.gnd# +タッタタホナヘニ菎フスコ\map\nguild_gef.bmp#タッタタホナヘニ菎フスコ\map\gefg_cas01.bmp# + +タッタタホナヘニ菎フスコ\map\n_castle.bmp#タッタタホナヘニ菎フスコ\map\gefg_cas01.bmp# + + +タッタタホナヘニ菎フスコ\map\pvp_c_room.bmp#タッタタホナヘニ菎フスコ\map\pvp_room.bmp# +タッタタホナヘニ菎フスコ\map\pvp_n_1-1.bmp#タッタタホナヘニ菎フスコ\map\prt_maze02.bmp# +タッタタホナヘニ菎フスコ\map\pvp_n_2-1.bmp#タッタタホナヘニ菎フスコ\map\prt_maze02.bmp# +タッタタホナヘニ菎フスコ\map\pvp_n_3-1.bmp#タッタタホナヘニ菎フスコ\map\prt_maze02.bmp# +タッタタホナヘニ菎フスコ\map\pvp_n_4-1.bmp#タッタタホナヘニ菎フスコ\map\prt_maze02.bmp# +タッタタホナヘニ菎フスコ\map\pvp_n_5-1.bmp#タッタタホナヘニ菎フスコ\map\prt_maze02.bmp# +タッタタホナヘニ菎フスコ\map\pvp_n_6-1.bmp#タッタタホナヘニ菎フスコ\map\prt_maze02.bmp# +タッタタホナヘニ菎フスコ\map\pvp_n_7-1.bmp#タッタタホナヘニ菎フスコ\map\prt_maze02.bmp# +タッタタホナヘニ菎フスコ\map\pvp_n_8-1.bmp#タッタタホナヘニ菎フスコ\map\prt_maze02.bmp# +タッタタホナヘニ菎フスコ\map\pvp_n_1-2.bmp#タッタタホナヘニ菎フスコ\map\job_hunter.bmp# +タッタタホナヘニ菎フスコ\map\pvp_n_2-2.bmp#タッタタホナヘニ菎フスコ\map\job_hunter.bmp# +タッタタホナヘニ菎フスコ\map\pvp_n_3-2.bmp#タッタタホナヘニ菎フスコ\map\job_hunter.bmp# +タッタタホナヘニ菎フスコ\map\pvp_n_4-2.bmp#タッタタホナヘニ菎フスコ\map\job_hunter.bmp# +タッタタホナヘニ菎フスコ\map\pvp_n_5-2.bmp#タッタタホナヘニ菎フスコ\map\job_hunter.bmp# +タッタタホナヘニ菎フスコ\map\pvp_n_6-2.bmp#タッタタホナヘニ菎フスコ\map\job_hunter.bmp# +タッタタホナヘニ菎フスコ\map\pvp_n_7-2.bmp#タッタタホナヘニ菎フスコ\map\job_hunter.bmp# +タッタタホナヘニ菎フスコ\map\pvp_n_8-2.bmp#タッタタホナヘニ菎フスコ\map\job_hunter.bmp# +タッタタホナヘニ菎フスコ\map\pvp_n_1-3.bmp#タッタタホナヘニ菎フスコ\map\job_wizard.bmp# +タッタタホナヘニ菎フスコ\map\pvp_n_2-3.bmp#タッタタホナヘニ菎フスコ\map\job_wizard.bmp# +タッタタホナヘニ菎フスコ\map\pvp_n_3-3.bmp#タッタタホナヘニ菎フスコ\map\job_wizard.bmp# +タッタタホナヘニ菎フスコ\map\pvp_n_4-3.bmp#タッタタホナヘニ菎フスコ\map\job_wizard.bmp# +タッタタホナヘニ菎フスコ\map\pvp_n_5-3.bmp#タッタタホナヘニ菎フスコ\map\job_wizard.bmp# +タッタタホナヘニ菎フスコ\map\pvp_n_6-3.bmp#タッタタホナヘニ菎フスコ\map\job_wizard.bmp# +タッタタホナヘニ菎フスコ\map\pvp_n_7-3.bmp#タッタタホナヘニ菎フスコ\map\job_wizard.bmp# +タッタタホナヘニ菎フスコ\map\pvp_n_8-3.bmp#タッタタホナヘニ菎フスコ\map\job_wizard.bmp# +タッタタホナヘニ菎フスコ\map\pvp_n_1-4.bmp#タッタタホナヘニ菎フスコ\map\job_priest.bmp# +タッタタホナヘニ菎フスコ\map\pvp_n_2-4.bmp#タッタタホナヘニ菎フスコ\map\job_priest.bmp# +タッタタホナヘニ菎フスコ\map\pvp_n_3-4.bmp#タッタタホナヘニ菎フスコ\map\job_priest.bmp# +タッタタホナヘニ菎フスコ\map\pvp_n_4-4.bmp#タッタタホナヘニ菎フスコ\map\job_priest.bmp# +タッタタホナヘニ菎フスコ\map\pvp_n_5-4.bmp#タッタタホナヘニ菎フスコ\map\job_priest.bmp# +タッタタホナヘニ菎フスコ\map\pvp_n_6-4.bmp#タッタタホナヘニ菎フスコ\map\job_priest.bmp# +タッタタホナヘニ菎フスコ\map\pvp_n_7-4.bmp#タッタタホナヘニ菎フスコ\map\job_priest.bmp# +タッタタホナヘニ菎フスコ\map\pvp_n_8-4.bmp#タッタタホナヘニ菎フスコ\map\job_priest.bmp# +タッタタホナヘニ菎フスコ\map\pvp_n_1-5.bmp#タッタタホナヘニ菎フスコ\map\job_knight.bmp# +タッタタホナヘニ菎フスコ\map\pvp_n_2-5.bmp#タッタタホナヘニ菎フスコ\map\job_knight.bmp# +タッタタホナヘニ菎フスコ\map\pvp_n_3-5.bmp#タッタタホナヘニ菎フスコ\map\job_knight.bmp# +タッタタホナヘニ菎フスコ\map\pvp_n_4-5.bmp#タッタタホナヘニ菎フスコ\map\job_knight.bmp# +タッタタホナヘニ菎フスコ\map\pvp_n_5-5.bmp#タッタタホナヘニ菎フスコ\map\job_knight.bmp# +タッタタホナヘニ菎フスコ\map\pvp_n_6-5.bmp#タッタタホナヘニ菎フスコ\map\job_knight.bmp# +タッタタホナヘニ菎フスコ\map\pvp_n_7-5.bmp#タッタタホナヘニ菎フスコ\map\job_knight.bmp# +タッタタホナヘニ菎フスコ\map\pvp_n_8-5.bmp#タッタタホナヘニ菎フスコ\map\job_knight.bmp# +タッタタホナヘニ菎フスコ\map\pvp_y_1-1.bmp#タッタタホナヘニ菎フスコ\map\prontera.bmp# +タッタタホナヘニ菎フスコ\map\pvp_y_2-1.bmp#タッタタホナヘニ菎フスコ\map\prontera.bmp# +タッタタホナヘニ菎フスコ\map\pvp_y_3-1.bmp#タッタタホナヘニ菎フスコ\map\prontera.bmp# +タッタタホナヘニ菎フスコ\map\pvp_y_4-1.bmp#タッタタホナヘニ菎フスコ\map\prontera.bmp# +タッタタホナヘニ菎フスコ\map\pvp_y_5-1.bmp#タッタタホナヘニ菎フスコ\map\prontera.bmp# +タッタタホナヘニ菎フスコ\map\pvp_y_6-1.bmp#タッタタホナヘニ菎フスコ\map\prontera.bmp# +タッタタホナヘニ菎フスコ\map\pvp_y_7-1.bmp#タッタタホナヘニ菎フスコ\map\prontera.bmp# +タッタタホナヘニ菎フスコ\map\pvp_y_8-1.bmp#タッタタホナヘニ菎フスコ\map\prontera.bmp# +タッタタホナヘニ菎フスコ\map\pvp_y_1-2.bmp#タッタタホナヘニ菎フスコ\map\izlude.bmp# +タッタタホナヘニ菎フスコ\map\pvp_y_2-2.bmp#タッタタホナヘニ菎フスコ\map\izlude.bmp# +タッタタホナヘニ菎フスコ\map\pvp_y_3-2.bmp#タッタタホナヘニ菎フスコ\map\izlude.bmp# +タッタタホナヘニ菎フスコ\map\pvp_y_4-2.bmp#タッタタホナヘニ菎フスコ\map\izlude.bmp# +タッタタホナヘニ菎フスコ\map\pvp_y_5-2.bmp#タッタタホナヘニ菎フスコ\map\izlude.bmp# +タッタタホナヘニ菎フスコ\map\pvp_y_6-2.bmp#タッタタホナヘニ菎フスコ\map\izlude.bmp# +タッタタホナヘニ菎フスコ\map\pvp_y_7-2.bmp#タッタタホナヘニ菎フスコ\map\izlude.bmp# +タッタタホナヘニ菎フスコ\map\pvp_y_8-2.bmp#タッタタホナヘニ菎フスコ\map\izlude.bmp# +タッタタホナヘニ菎フスコ\map\pvp_y_1-3.bmp#タッタタホナヘニ菎フスコ\map\payon.bmp# +タッタタホナヘニ菎フスコ\map\pvp_y_2-3.bmp#タッタタホナヘニ菎フスコ\map\payon.bmp# +タッタタホナヘニ菎フスコ\map\pvp_y_3-3.bmp#タッタタホナヘニ菎フスコ\map\payon.bmp# +タッタタホナヘニ菎フスコ\map\pvp_y_4-3.bmp#タッタタホナヘニ菎フスコ\map\payon.bmp# +タッタタホナヘニ菎フスコ\map\pvp_y_5-3.bmp#タッタタホナヘニ菎フスコ\map\payon.bmp# +タッタタホナヘニ菎フスコ\map\pvp_y_6-3.bmp#タッタタホナヘニ菎フスコ\map\payon.bmp# +タッタタホナヘニ菎フスコ\map\pvp_y_7-3.bmp#タッタタホナヘニ菎フスコ\map\payon.bmp# +タッタタホナヘニ菎フスコ\map\pvp_y_8-3.bmp#タッタタホナヘニ菎フスコ\map\payon.bmp# +タッタタホナヘニ菎フスコ\map\pvp_y_1-4.bmp#タッタタホナヘニ菎フスコ\map\alberta.bmp# +タッタタホナヘニ菎フスコ\map\pvp_y_2-4.bmp#タッタタホナヘニ菎フスコ\map\alberta.bmp# +タッタタホナヘニ菎フスコ\map\pvp_y_3-4.bmp#タッタタホナヘニ菎フスコ\map\alberta.bmp# +タッタタホナヘニ菎フスコ\map\pvp_y_4-4.bmp#タッタタホナヘニ菎フスコ\map\alberta.bmp# +タッタタホナヘニ菎フスコ\map\pvp_y_5-4.bmp#タッタタホナヘニ菎フスコ\map\alberta.bmp# +タッタタホナヘニ菎フスコ\map\pvp_y_6-4.bmp#タッタタホナヘニ菎フスコ\map\alberta.bmp# +タッタタホナヘニ菎フスコ\map\pvp_y_7-4.bmp#タッタタホナヘニ菎フスコ\map\alberta.bmp# +タッタタホナヘニ菎フスコ\map\pvp_y_8-4.bmp#タッタタホナヘニ菎フスコ\map\alberta.bmp# +タッタタホナヘニ菎フスコ\map\pvp_y_1-5.bmp#タッタタホナヘニ菎フスコ\map\morocc.bmp# +タッタタホナヘニ菎フスコ\map\pvp_y_2-5.bmp#タッタタホナヘニ菎フスコ\map\morocc.bmp# +タッタタホナヘニ菎フスコ\map\pvp_y_3-5.bmp#タッタタホナヘニ菎フスコ\map\morocc.bmp# +タッタタホナヘニ菎フスコ\map\pvp_y_4-5.bmp#タッタタホナヘニ菎フスコ\map\morocc.bmp# +タッタタホナヘニ菎フスコ\map\pvp_y_5-5.bmp#タッタタホナヘニ菎フスコ\map\morocc.bmp# +タッタタホナヘニ菎フスコ\map\pvp_y_6-5.bmp#タッタタホナヘニ菎フスコ\map\morocc.bmp# +タッタタホナヘニ菎フスコ\map\pvp_y_7-5.bmp#タッタタホナヘニ菎フスコ\map\morocc.bmp# +タッタタホナヘニ菎フスコ\map\pvp_y_8-5.bmp#タッタタホナヘニ菎フスコ\map\morocc.bmp# +タッタタホナヘニ菎フスコ\map\new_1-1.bmp#タッタタホナヘニ菎フスコ\map\new_zone01.bmp# +タッタタホナヘニ菎フスコ\map\new_2-1.bmp#タッタタホナヘニ菎フスコ\map\new_zone01.bmp# +タッタタホナヘニ菎フスコ\map\new_3-1.bmp#タッタタホナヘニ菎フスコ\map\new_zone01.bmp# +タッタタホナヘニ菎フスコ\map\new_4-1.bmp#タッタタホナヘニ菎フスコ\map\new_zone01.bmp# +タッタタホナヘニ菎フスコ\map\new_5-1.bmp#タッタタホナヘニ菎フスコ\map\new_zone01.bmp# +タッタタホナヘニ菎フスコ\map\new_1-2.bmp#タッタタホナヘニ菎フスコ\map\new_zone02.bmp# +タッタタホナヘニ菎フスコ\map\new_2-2.bmp#タッタタホナヘニ菎フスコ\map\new_zone02.bmp# +タッタタホナヘニ菎フスコ\map\new_3-2.bmp#タッタタホナヘニ菎フスコ\map\new_zone02.bmp# +タッタタホナヘニ菎フスコ\map\new_4-2.bmp#タッタタホナヘニ菎フスコ\map\new_zone02.bmp# +タッタタホナヘニ菎フスコ\map\new_5-2.bmp#タッタタホナヘニ菎フスコ\map\new_zone02.bmp# +タッタタホナヘニ菎フスコ\map\new_1-3.bmp#タッタタホナヘニ菎フスコ\map\new_zone03.bmp# +タッタタホナヘニ菎フスコ\map\new_2-3.bmp#タッタタホナヘニ菎フスコ\map\new_zone03.bmp# +タッタタホナヘニ菎フスコ\map\new_3-3.bmp#タッタタホナヘニ菎フスコ\map\new_zone03.bmp# +タッタタホナヘニ菎フスコ\map\new_4-3.bmp#タッタタホナヘニ菎フスコ\map\new_zone03.bmp# +タッタタホナヘニ菎フスコ\map\new_5-3.bmp#タッタタホナヘニ菎フスコ\map\new_zone03.bmp# +タッタタホナヘニ菎フスコ\map\new_1-4.bmp#タッタタホナヘニ菎フスコ\map\new_zone04.bmp# +タッタタホナヘニ菎フスコ\map\new_2-4.bmp#タッタタホナヘニ菎フスコ\map\new_zone04.bmp# +タッタタホナヘニ菎フスコ\map\new_3-4.bmp#タッタタホナヘニ菎フスコ\map\new_zone04.bmp# +タッタタホナヘニ菎フスコ\map\new_4-4.bmp#タッタタホナヘニ菎フスコ\map\new_zone04.bmp# +タッタタホナヘニ菎フスコ\map\new_5-4.bmp#タッタタホナヘニ菎フスコ\map\new_zone04.bmp# +タッタタホナヘニ菎フスコ\map\force_1-1.bmp#タッタタホナヘニ菎フスコ\map\force_map1.bmp# +タッタタホナヘニ菎フスコ\map\force_2-1.bmp#タッタタホナヘニ菎フスコ\map\force_map1.bmp# +タッタタホナヘニ菎フスコ\map\force_3-1.bmp#タッタタホナヘニ菎フスコ\map\force_map1.bmp# +タッタタホナヘニ菎フスコ\map\force_1-2.bmp#タッタタホナヘニ菎フスコ\map\force_map2.bmp# +タッタタホナヘニ菎フスコ\map\force_2-2.bmp#タッタタホナヘニ菎フスコ\map\force_map2.bmp# +タッタタホナヘニ菎フスコ\map\force_3-2.bmp#タッタタホナヘニ菎フスコ\map\force_map2.bmp# +タッタタホナヘニ菎フスコ\map\force_1-3.bmp#タッタタホナヘニ菎フスコ\map\force_map3.bmp# +タッタタホナヘニ菎フスコ\map\force_2-3.bmp#タッタタホナヘニ菎フスコ\map\force_map3.bmp# +タッタタホナヘニ菎フスコ\map\force_3-3.bmp#タッタタホナヘニ菎フスコ\map\force_map3.bmp# +タッタタホナヘニ菎フスコ\map\hunter_1-1.bmp#タッタタホナヘニ菎フスコ\map\in_hunter.bmp# +タッタタホナヘニ菎フスコ\map\hunter_2-1.bmp#タッタタホナヘニ菎フスコ\map\in_hunter.bmp# +タッタタホナヘニ菎フスコ\map\hunter_3-1.bmp#タッタタホナヘニ菎フスコ\map\in_hunter.bmp# +タッタタホナヘニ菎フスコ\map\knight_1-1.bmp#タッタタホナヘニ菎フスコ\map\job_knight.bmp# +タッタタホナヘニ菎フスコ\map\knight_2-1.bmp#タッタタホナヘニ菎フスコ\map\job_knight.bmp# +タッタタホナヘニ菎フスコ\map\knight_3-1.bmp#タッタタホナヘニ菎フスコ\map\job_knight.bmp# +タッタタホナヘニ菎フスコ\map\priest_1-1.bmp#タッタタホナヘニ菎フスコ\map\job_priest.bmp# +タッタタホナヘニ菎フスコ\map\priest_2-1.bmp#タッタタホナヘニ菎フスコ\map\job_priest.bmp# +タッタタホナヘニ菎フスコ\map\priest_3-1.bmp#タッタタホナヘニ菎フスコ\map\job_priest.bmp# +タッタタホナヘニ菎フスコ\map\sword_1-1.bmp#タッタタホナヘニ菎フスコ\map\job_sword1.bmp# +タッタタホナヘニ菎フスコ\map\sword_2-1.bmp#タッタタホナヘニ菎フスコ\map\job_sword1.bmp# +タッタタホナヘニ菎フスコ\map\sword_3-1.bmp#タッタタホナヘニ菎フスコ\map\job_sword1.bmp# +タッタタホナヘニ菎フスコ\map\thief_1-1.bmp#タッタタホナヘニ菎フスコ\map\job_thief1.bmp# +タッタタホナヘニ菎フスコ\map\thief_2-1.bmp#タッタタホナヘニ菎フスコ\map\job_thief1.bmp# +タッタタホナヘニ菎フスコ\map\thief_3-1.bmp#タッタタホナヘニ菎フスコ\map\job_thief1.bmp# +タッタタホナヘニ菎フスコ\map\wizard_1-1.bmp#タッタタホナヘニ菎フスコ\map\job_wizard.bmp# +タッタタホナヘニ菎フスコ\map\wizard_2-1.bmp#タッタタホナヘニ菎フスコ\map\job_wizard.bmp# +タッタタホナヘニ菎フスコ\map\wizard_3-1.bmp#タッタタホナヘニ菎フスコ\map\job_wizard.bmp# +タッタタホナヘニ菎フスコ\map\ordeal_1-1.bmp#タッタタホナヘニ菎フスコ\map\ordeal_a00.bmp# +タッタタホナヘニ菎フスコ\map\ordeal_2-1.bmp#タッタタホナヘニ菎フスコ\map\ordeal_a00.bmp# +タッタタホナヘニ菎フスコ\map\ordeal_3-1.bmp#タッタタホナヘニ菎フスコ\map\ordeal_a00.bmp# +タッタタホナヘニ菎フスコ\map\ordeal_1-2.bmp#タッタタホナヘニ菎フスコ\map\ordeal_a02.bmp# +タッタタホナヘニ菎フスコ\map\ordeal_2-2.bmp#タッタタホナヘニ菎フスコ\map\ordeal_a02.bmp# +タッタタホナヘニ菎フスコ\map\ordeal_3-2.bmp#タッタタホナヘニ菎フスコ\map\ordeal_a02.bmp# +タッタタホナヘニ菎フスコ\map\ordeal_1-3.bmp#タッタタホナヘニ菎フスコ\map\ordeal_a03.bmp# +タッタタホナヘニ菎フスコ\map\ordeal_2-3.bmp#タッタタホナヘニ菎フスコ\map\ordeal_a03.bmp# +タッタタホナヘニ菎フスコ\map\ordeal_3-3.bmp#タッタタホナヘニ菎フスコ\map\ordeal_a03.bmp# +タッタタホナヘニ菎フスコ\map\ordeal_1-4.bmp#タッタタホナヘニ菎フスコ\map\ordeal_a04.bmp# +タッタタホナヘニ菎フスコ\map\ordeal_2-4.bmp#タッタタホナヘニ菎フスコ\map\ordeal_a04.bmp# +タッタタホナヘニ菎フスコ\map\ordeal_3-4.bmp#タッタタホナヘニ菎フスコ\map\ordeal_a04.bmp# + +job_sage.rsw#job_wiz.rsw# +job_sage.gat#job_wiz.gat# +job_sage.gnd#job_wiz.gnd# +タッタタホナヘニ菎フスコ\map\job_sage.bmp#タッタタホナヘニ菎フスコ\map\job_wiz.bmp# +job_cru.rsw#job_prist.rsw# +job_cru.gat#job_prist.gat# +job_cru.gnd#job_prist.gnd# +タッタタホナヘニ菎フスコ\map\job_cru.bmp#タッタタホナヘニ菎フスコ\map\job_prist.bmp# + diff --git a/db/attr_fix.txt b/db/attr_fix.txt new file mode 100644 index 0000000..2bdbbd9 --- /dev/null +++ b/db/attr_fix.txt @@ -0,0 +1,53 @@ +// 属性修正 +// 横:モンスターの属性 // 縦:攻撃の属性 +1,10 // lv1属性テーブル +// 無 水 地 火 風 毒 聖 暗 念 不死 + 100, 100, 100, 100, 100, 100, 100, 100, 25, 100, // 無 + 100, 25, 100, 150, 50, 100, 75, 100, 100, 100, // 水 + 100, 100, 100, 50, 150, 100, 75, 100, 100, 100, // 地 + 100, 50, 150, 25, 100, 100, 75, 100, 100, 125, // 火 + 100, 175, 50, 100, 25, 100, 75, 100, 100, 100, // 風 + 100, 100, 125, 125, 125, 0, 75, 50, 100, -25, // 毒 + 100, 100, 100, 100, 100, 100, 0, 125, 100, 150, // 聖 + 100, 100, 100, 100, 100, 50, 125, 0, 100, -25, // 暗 + 25, 100, 100, 100, 100, 100, 75, 75, 125, 100, // 念 + 100, 100, 100, 100, 100, 50, 100, 0, 100, 0, // 不死 +// 横:モンスターの属性 // 縦:攻撃の属性 +2,10 // lv2属性テーブル +//無 水 地 火 風 毒 聖 暗 念 不死 + 100, 100, 100, 100, 100, 100, 100, 100, 25, 100, // 無 + 100, 0, 100, 175, 25, 100, 50, 75, 100, 100, // 水 + 100, 100, 50, 25, 175, 100, 50, 75, 100, 100, // 地 + 100, 25, 175, 0, 100, 100, 50, 75, 100, 150, // 火 + 100, 175, 25, 100, 0, 100, 50, 75, 100, 100, // 風 + 100, 75, 125, 125, 125, 0, 50, 25, 75, -50, // 毒 + 100, 100, 100, 100, 100, 100, -25, 150, 100, 175, // 聖 + 100, 100, 100, 100, 100, 25, 150, -25, 100, -50, // 暗 + 0, 75, 75, 75, 75, 75, 50, 50, 150, 125, // 念 + 100, 75, 75, 75, 75, 25, 125, 0, 100, 0, // 不死 +// 横:モンスターの属性 // 縦:攻撃の属性 +3,10 // lv3属性テーブル +// 無 水 地 火 風 毒 聖 暗 念 不死 + 100, 100, 100, 100, 100, 100, 100, 100, 0, 100, // 無 + 100, -25, 100, 200, 0, 100, 25, 50, 100, 125, // 水 + 100, 100, 0, 0, 200, 100, 25, 50, 100, 75, // 地 + 100, 0, 200, -25, 100, 100, 25, 50, 100, 175, // 火 + 100, 200, 0, 100, -25, 100, 25, 50, 100, 100, // 風 + 100, 50, 100, 100, 100, 0, 25, 0, 50, -75, // 毒 + 100, 100, 100, 100, 100, 125, -50, 175, 100, 200, // 聖 + 100, 100, 100, 100, 100, 0, 175, -50, 100, -75, // 暗 + 0, 50, 50, 50, 50, 50, 25, 25, 175, 150, // 念 + 100, 50, 50, 50, 50, 0, 150, 0, 100, 0, // 不死 +// 横:モンスターの属性 // 縦:攻撃の属性 +4,10 // lv4属性テーブル +// 無 水 地 火 風 毒 聖 暗 念 不死 + 100, 100, 100, 100, 100, 100, 100, 100, 0, 100, // 無 + 100, -50, 100, 200, 0, 75, 0, 25, 100, 150, // 水 + 100, 100, -25, 0, 200, 75, 0, 25, 100, 50, // 地 + 100, 0, 200, -50, 100, 75, 0, 25, 100, 200, // 火 + 100, 200, 0, 100, -50, 75, 0, 25, 100, 100, // 風 + 100, 25, 75, 75, 75, 0, 0, -25, 25,-100, // 毒 + 100, 75, 75, 75, 75, 125,-100, 200, 100, 200, // 聖 + 100, 75, 75, 75, 75, -25, 200,-100, 100,-100, // 暗 + 0, 25, 25, 25, 25, 25, 0, 0, 200, 175, // 念 + 100, 25, 25, 25, 25, -25, 175, 0, 100, 0, // 不死 diff --git a/db/card_labels.txt b/db/card_labels.txt new file mode 100644 index 0000000..1177f0e --- /dev/null +++ b/db/card_labels.txt @@ -0,0 +1 @@ +// File created using Spamrat's Athena Database Editor v1.4.4
diff --git a/db/const.txt b/db/const.txt new file mode 100644 index 0000000..53bc1ba --- /dev/null +++ b/db/const.txt @@ -0,0 +1,231 @@ +Job_Novice 0
+Job_Swordman 1
+Job_Mage 2
+Job_Archer 3
+Job_Acolyte 4
+Job_Merchant 5
+Job_Thief 6
+Job_Knight 7
+Job_Priest 8
+Job_Wizard 9
+Job_Blacksmith 10
+Job_Hunter 11
+Job_Assassin 12
+Job_Knight2 13
+Job_Crusader 14
+Job_Monk 15
+Job_Sage 16
+Job_Rogue 17
+Job_Alchem 18
+Job_Bard 19
+Job_Dancer 20
+Job_Crusader2 21
+Job_SuperNovice 23
+Job_Novice_High 24
+Job_Swordman_High 25
+Job_Mage_High 26
+Job_Archer_High 27
+Job_Acolyte_High 28
+Job_Merchant_High 29
+Job_Thief_High 30
+Job_Lord_Knight 31
+Job_High_Priest 32
+Job_High_Wizard 33
+Job_Whitesmith 34
+Job_Sniper 35
+Job_Assassin_Cross 36
+Job_Lord_Knight2 37
+Job_Paladin 38
+Job_Champion 39
+Job_Professor 40
+Job_Stalker 41
+Job_Creator 42
+Job_Clown 43
+Job_Gypsy 44
+Job_Paladin2 45
+Job_Baby 46
+Job_Baby_Swordman 47
+Job_Baby_Mage 48
+Job_Baby_Archer 49
+Job_Baby_Acolyte 50
+Job_Baby_Merchant 51
+Job_Baby_Thief 52
+Job_Baby_Knight 53
+Job_Baby_Priest 54
+Job_Baby_Wizard 55
+Job_Baby_Blacksmith 56
+Job_Baby_Hunter 57
+Job_Baby_Assassin 58
+Job_Baby_Knight2 59
+Job_Baby_Crusader 60
+Job_Baby_Monk 61
+Job_Baby_Sage 62
+Job_Baby_Rogue 63
+Job_Baby_Alchem 64
+Job_Baby_Bard 65
+Job_Baby_Dancer 66
+Job_Baby_Crusader2 67
+Job_Super_Baby 68
+
+
+
+mf_nomemo 0
+mf_noteleport 1
+mf_nosave 2
+mf_nobranch 3
+mf_nopenalty 4
+mf_pvp 5
+mf_pvp_noparty 6
+mf_pvp_noguild 7
+mf_gvg 8
+mf_gvg_noparty 9
+mf_nozenypenalty 10
+
+StatusPoint 9 1
+BaseLevel 11 1
+SkillPoint 12 1
+Class 19 1
+Upper 56 1
+Zeny 20 1
+Sex 21 1
+Weight 24 1
+MaxWeight 25 1
+JobLevel 55 1
+BaseExp 1 1
+JobExp 2 1
+NextBaseExp 22 1
+NextJobExp 23 1
+Hp 5 1
+MaxHp 6 1
+Sp 7 1
+MaxSp 8 1
+
+bMaxHP 6
+bMaxSP 8
+bStr 13
+bAgi 14
+bVit 15
+bInt 16
+bDex 17
+bLuk 18
+bAtk 41
+bAtk2 42
+bMatk1 43
+bMatk2 44
+bDef 45
+bMdef 47
+bMdef2 48
+bHit 49
+bFlee 50
+bFlee2 51
+bCritical 52
+bAspd 53
+
+bAtkRange 1000
+bAtkEle 1001
+bDefEle 1002
+bCastrate 1003
+bMaxHPrate 1004
+bMaxSPrate 1005
+bUseSPrate 1006
+bAddEle 1007
+bAddRace 1008
+bAddSize 1009
+bSubEle 1010
+bSubRace 1011
+bAddEff 1012
+bResEff 1013
+bBaseAtk 1014
+bAspdRate 1015
+bHPrecovRate 1016
+bSPrecovRate 1017
+bSpeedRate 1018
+bCriticalDef 1019
+bNearAtkDef 1020
+bLongAtkDef 1021
+bDoubleRate 1022
+bDoubleAddRate 1023
+bMatk 1024
+bMatkRate 1025
+bIgnoreDefEle 1026
+bIgnoreDefRace 1027
+bAtkRate 1028
+bSpeedAddRate 1029
+bAspdAddRate 1030
+bMagicAtkDef 1031
+bMiscAtkDef 1032
+bIgnoreMdefEle 1033
+bIgnoreMdefRace 1034
+bMagicAddEle 1035
+bMagicAddRace 1036
+bMagicSubRace 1037
+bPerfectHitRate 1038
+bPerfectHitAddRate 1039
+bCriticalRate 1040
+bGetZenyNum 1041
+bAddGetZenyNum 1042
+bAddDamageClass 1043
+bAddMagicDamageClass 1044
+bAddDefClass 1045
+bAddMdefClass 1046
+bAddMonsterDropItem 1047
+bDefRatioAtkEle 1048
+bDefRatioAtkRace 1049
+bAddSpeed 1050
+bHitRate 1051
+bFleeRate 1052
+bFlee2Rate 1053
+bDefRate 1054
+bDef2Rate 1055
+bMdefRate 1056
+bMdef2Rate 1057
+bSplashRange 1058
+bSplashAddRange 1059
+bAutoSpell 1060
+bHPDrainRate 1061
+bSPDrainRate 1062
+bShortWeaponDamageReturn 1063
+bLongWeaponDamageReturn 1064
+bWeaponComaEle 1065
+bWeaponComaRace 1066
+bAddEff2 1067
+bMagicDamageReturn 1068
+bRandomAttackIncrease 1069
+bAllStats 1070
+bAgiVit 1071
+bAgiDexStr 1072
+bPerfectHide 1073
+
+bRestartFullRecover 2000
+bNoCastCancel 2001
+bNoSizeFix 2002
+bNoMagicDamage 2003
+bNoWeaponDamage 2004
+bNoGemStone 2005
+bNoCastCancel2 2006
+bInfiniteEndure 2007
+
+Eff_Stone 0
+Eff_Freeze 1
+Eff_Stan 2
+Eff_Sleep 3
+Eff_Poison 4
+Eff_Curse 5
+Eff_Silence 6
+Eff_Confusion 7
+Eff_Blind 8
+
+SC_Stone 128
+SC_Freeze 129
+SC_Stan 130
+SC_Sleep 131
+SC_Poison 132
+SC_Curse 133
+SC_Silence 134
+SC_Confusion 135
+SC_Blind 136
+SC_SpeedPot0 37
+SC_SpeedPot1 38
+SC_SpeedPot2 39
+SC_ATKPot 185
+SC_MATKPot 186
diff --git a/db/exp.txt b/db/exp.txt new file mode 100644 index 0000000..b1f2ef8 --- /dev/null +++ b/db/exp.txt @@ -0,0 +1,99 @@ +9,9,9,9,10,10,10,10,30,144,30,11,60,288
+16,16,16,16,18,18,18,18,43,184,43,20,86,368
+25,25,25,25,28,28,28,28,58,284,58,31,116,568
+36,36,36,36,40,40,40,40,76,348,76,44,152,696
+77,77,77,77,85,85,85,91,116,603,116,100,232,1206
+112,112,112,112,123,123,123,151,180,887,180,166,360,1774
+153,153,153,153,168,168,168,205,220,1096,220,226,440,2192
+200,200,200,200,220,220,220,268,272,1598,272,295,544,3196
+253,253,253,253,278,278,278,340,336,2540,336,374,672,5080
+320,320,320,320,400,400,400,0,520,3676,520,0,1040,7352
+385,385,385,385,41,41,41,0,604,4290,604,0,1208,8580
+490,490,490,490,613,613,613,0,699,4946,699,0,1398,9892
+585,585,585,585,731,731,731,0,802,6679,802,0,1604,13358
+700,700,700,700,875,875,875,0,948,9492,948,0,1896,18984
+830,830,830,830,1038,1038,1038,0,1125,12770,1125,0,2250,31925
+970,970,970,970,1213,1213,1213,0,1668,14344,1668,0,3336,35860
+1120,1120,1120,1120,1400,1400,1400,0,1937,16005,1937,0,3874,40013
+1260,1260,1260,1260,1575,1575,1575,0,2226,20642,2226,0,4452,51605
+1420,1420,1420,1420,1775,1775,1775,0,3040,27434,3040,0,6080,68585
+1620,1620,1620,1620,2268,2268,2268,0,3988,35108,3988,0,7976,87770
+1860,1860,1860,1860,2604,2604,2604,0,5564,38577,5564,0,11128,96443
+1990,1990,1990,1990,2786,2786,2786,0,6272,42206,6272,0,12544,105515
+2240,2240,2240,2240,3136,3136,3136,0,7021,52708,7021,0,14042,131770
+2504,2504,2504,2504,3506,3506,3506,0,9114,66971,9114,0,18228,167428
+2950,2950,2950,2950,4130,4130,4130,0,11473,82688,11473,0,28683,206720
+3426,3426,3426,3426,4796,4796,4796,0,15290,89544,15290,0,38225,223860
+3934,3934,3934,3934,5508,5508,5508,0,16891,96669,16891,0,42228,241673
+4474,4474,4474,4474,6264,6264,6264,0,18570,117821,18570,0,46425,294553
+6889,6889,6889,6889,9645,9645,9645,0,23229,144921,23229,0,58073,362303
+7995,7995,7995,7995,12392,12392,12392,0,28359,174201,28359,0,70898,479053
+9174,9174,9174,9174,14220,14220,14220,0,36478,186677,36478,0,91195,513362
+10425,10425,10425,10425,16159,16159,16159,0,39716,199584,39716,0,99290,548856
+11748,11748,11748,11748,18209,18209,18209,0,43088,238617,43088,0,107720,656197
+13967,13967,13967,13967,21649,21649,21649,0,52417,286366,52417,0,131043,787507
+15775,15775,15775,15775,24451,24451,24451,0,62495,337147,62495,0,156238,927154
+17678,17678,17678,17678,27401,27401,27401,0,78160,358435,78160,0,195408,985696
+19677,19677,19677,19677,30499,30499,30499,0,84175,380376,84175,0,210430,1046034
+21773,21773,21773,21773,33748,33748,33748,0,90404,447685,90404,0,226010,1231134
+30543,30543,30543,30543,47342,47342,47342,0,107611,526989,107611,0,269028,1449220
+34212,34212,34212,34212,58160,58160,58160,0,125915,610246,125915,0,314788,1678177
+38065,38065,38065,38065,64711,64711,64711,0,153941,644736,153941,0,384853,1773024
+42102,42102,42102,42102,71573,71573,71573,0,191781,793535,191781,0,479453,2182221
+46323,46323,46323,46323,78749,78749,78749,0,204351,921810,204351,0,510878,2534978
+53026,53026,53026,53026,90144,90144,90144,0,248352,1106758,248352,0,620880,3043585
+58419,58419,58419,58419,99312,99312,99312,0,286212,1260955,286212,0,715530,3782865
+64041,64041,64041,64041,108870,108870,108870,0,386371,1487304,386371,0,965928,4461912
+69892,69892,69892,69892,118816,118816,118816,0,409795,1557657,409795,0,1024488,4672971
+75973,75973,75973,75973,129154,129154,129154,0,482092,1990632,482092,0,1205230,5971896
+102468,102468,102468,102468,174196,174196,174196,0,509596,2083386,509596,0,1273990,6250158
+115254,115254,115254,115254,213220,213220,213220,0,0,0,982092,0,0,6875174
+128692,128692,128692,128692,238080,238080,238080,0,0,0,992092,0,0,7562691
+142784,142784,142784,142784,264150,264150,264150,0,0,0,1002092,0,0,8318960
+157528,157528,157528,157528,291427,291427,291427,0,0,0,1012092,0,0,9150856
+178184,178184,178184,178184,329640,329640,329640,0,0,0,1022092,0,0,10065942
+196300,196300,196300,196300,363155,363155,363155,0,0,0,1032092,0,0,11877812
+215198,215198,215198,215198,398116,398116,398116,0,0,0,1042092,0,0,14015818
+234879,234879,234879,234879,434526,434526,434526,0,0,0,1052092,0,0,16538655
+255341,255341,255341,255341,472381,472381,472381,0,0,0,1062092,0,0,19515624
+330188,330188,330188,330188,610848,610848,610848,0,0,0,1072092,0,0,23028437
+365914,365914,365914,365914,731828,731828,731828,0,0,0,1082092,0,0,28094693
+403224,403224,403224,403224,806448,806448,806448,0,0,0,1092092,0,0,34275525
+442116,442116,442116,442116,884232,884232,884232,0,0,0,1102092,0,0,41816141
+482590,482590,482590,482590,965180,965180,965180,0,0,0,1112092,0,0,51015692
+536948,536948,536948,536948,1073896,1073896,1073896,0,0,0,1122092,0,0,62239144
+585191,585191,585191,585191,1170382,1170382,1170382,0,0,0,1132092,0,0,79666104
+635278,635278,635278,635278,1270556,1270556,1270556,0,0,0,1142092,0,0,101972614
+687211,687211,687211,687211,1374422,1374422,1374422,0,0,0,1152092,0,0,130524946
+740988,740988,740988,740988,1481976,1481976,1481976,0,0,0,1162092,0,0,167071930
+925400,925400,925400,925400,1850800,1850800,1850800,0,0,0,1172092,0,0,213852071
+1473746,1473746,1473746,1473746,3389616,3389616,3389616,0,0,0,0,0,0,0
+1594058,1594058,1594058,1594058,3666333,3666333,3666333,0,0,0,0,0,0,0
+1718928,1718928,1718928,1718928,3953534,3953534,3953534,0,0,0,0,0,0,0
+1848355,1848355,1848355,1848355,4251217,4251217,4251217,0,0,0,0,0,0,0
+1982340,1982340,1982340,1982340,4559382,4559382,4559382,0,0,0,0,0,0,0
+2230113,2230113,2230113,2230113,5129260,5129260,5129260,0,0,0,0,0,0,0
+2386162,2386162,2386162,2386162,5488173,5488173,5488173,0,0,0,0,0,0,0
+2547417,2547417,2547417,2547417,5859059,5859059,5859059,0,0,0,0,0,0,0
+2713878,2713878,2713878,2713878,6241919,6241919,6241919,0,0,0,0,0,0,0
+3206160,3206160,3206160,3206160,7374168,7374168,7374168,0,0,0,0,0,0,0
+3681024,3681024,3681024,3681024,9570662,9570662,9570662,0,0,0,0,0,0,0
+4022472,4022472,4022472,4022472,10458427,10458427,10458427,0,0,0,0,0,0,0
+4377024,4377024,4377024,4377024,11380262,11380262,11380262,0,0,0,0,0,0,0
+4744680,4744680,4744680,4744680,12336168,12336168,12336168,0,0,0,0,0,0,0
+5125440,5125440,5125440,5125440,13326144,13326144,13326144,0,0,0,0,0,0,0
+5767272,5767272,5767272,5767272,14994907,14994907,14994907,0,0,0,0,0,0,0
+6204000,6204000,6204000,6204000,16130400,16130400,16130400,0,0,0,0,0,0,0
+6655464,6655464,6655464,6655464,17304200,17304200,17304200,0,0,0,0,0,0,0
+7121664,7121664,7121664,7121664,18516326,18516326,18516326,0,0,0,0,0,0,0
+7602600,7602600,7602600,7602600,19766760,19766760,19766760,0,0,0,0,0,0,0
+9738720,9738720,9738720,9738720,29216160,29216160,29216160,0,0,0,0,0,0,0
+11649960,11649960,11649960,11649960,34949880,34949880,34949880,0,0,0,0,0,0,0
+13643520,13643520,13643520,13643520,40930560,40930560,40930560,0,0,0,0,0,0,0
+18339300,18339300,18339300,18339300,55017900,55017900,55017900,0,0,0,0,0,0,0
+23836800,23836800,23836800,23836800,71510400,71510400,71510400,0,0,0,0,0,0,0
+35658000,35658000,35658000,35658000,106974000,106974000,106974000,0,0,0,0,0,0,0
+48687000,48687000,48687000,48687000,146061000,146061000,146061000,0,0,0,0,0,0,0
+58135000,58135000,58135000,58135000,174405000,174405000,174405000,0,0,0,0,0,0,0
+99999999,99999999,99999999,99999999,200000000,200000000,200000000,0,0,0,0,0,0,0
+0,0,0,0,0,0,0,0,0,0,0,0,0,0
diff --git a/db/exp2.txt b/db/exp2.txt new file mode 100644 index 0000000..0c81931 --- /dev/null +++ b/db/exp2.txt @@ -0,0 +1,99 @@ +10,9,9,11,60,288,144
+18,16,16,20,86,368,184
+28,25,25,31,116,568,284
+40,36,36,44,152,696,348
+85,77,77,100,232,1206,603
+123,112,112,166,360,1774,887
+168,153,153,226,440,2192,1096
+220,200,200,295,544,3196,1598
+278,253,253,374,672,5080,2540
+400,320,320,0,1040,7352,3676
+481,385,385,0,1208,8580,4290
+613,490,490,0,1398,9892,4946
+731,585,585,0,1604,13358,6679
+875,700,700,0,1896,18984,9492
+1038,830,830,0,2250,31925,12770
+1213,970,970,0,3336,35860,14344
+1400,1120,1120,0,3874,40013,16005
+1575,1260,1260,0,4452,51605,20642
+1775,1420,1420,0,6080,68585,27434
+2268,1620,1620,0,7976,87770,35108
+2604,1860,1860,0,11128,96443,38577
+2786,1990,1990,0,12544,105515,42206
+3136,2240,2240,0,14042,131770,52708
+3506,2504,2504,0,18228,167428,66971
+4136,2950,2950,0,28683,206720,82688
+4796,3426,3426,0,38225,223860,89544
+5508,3934,3934,0,42228,241673,96669
+6264,4474,4474,0,46425,294553,117821
+9645,6889,6889,0,58073,362303,144921
+12392,7995,7995,0,70898,479053,174201
+14220,9174,9174,0,91195,548856,186677
+16159,10425,10425,0,99290,656197,199584
+18209,11748,11748,0,107720,787507,238617
+21649,13967,13967,0,131043,927154,286366
+24451,15775,15775,0,156238,985696,337147
+27401,17678,17678,0,195400,1046034,358435
+30499,19677,19677,0,210438,1231134,380376
+33748,21773,21773,0,226010,1449220,447685
+47342,30543,30543,0,269028,1678177,526989
+58160,34212,34212,0,314788,1773024,610246
+64711,38065,38065,0,384853,2182221,644736
+71573,42102,42102,0,479453,2534978,793535
+78749,46323,46323,0,510878,3043585,921810
+90144,53026,53026,0,620880,3782865,1106758
+99312,58419,58419,0,715530,4461912,1260955
+108870,64041,64041,0,965928,4672971,1487304
+118816,69892,69892,0,1024488,5971896,1557657
+129154,75973,75973,0,1205230,6250158,1990632
+174196,102468,102468,0,1273990,6875174,2083386
+213220,115254,115254,0,0,7562691,0
+238080,128692,128692,0,0,8318960,0
+264150,142784,142784,0,0,9150856,0
+291427,157528,157528,0,0,10065942,0
+329640,178184,178184,0,0,11877812,0
+363155,196300,196300,0,0,14015818,0
+398116,215198,215198,0,0,16538665,0
+434526,234879,234879,0,0,19515624,0
+472381,255341,255341,0,0,23028437,0
+610848,330188,330188,0,0,28094693,0
+741828,365914,365914,0,0,34275525,0
+806448,403224,403224,0,0,41816141,0
+884232,442116,442116,0,0,51015692,0
+965180,482590,482590,0,0,62239144,0
+1073896,536948,536948,0,0,79666104,0
+1170382,585191,585191,0,0,101972614,0
+1270556,635278,635278,0,0,130624946,0
+1374422,687211,687211,0,0,167071930,0
+1481976,740988,740988,0,0,213852071,0
+1850800,925400,925400,0,0,213852071,0
+3389616,1473746,1473746,0,0,0,0
+3666333,1594058,1594058,0,0,0,0
+3953534,1718928,1718928,0,0,0,0
+4251217,1848355,1848355,0,0,0,0
+4559382,1982340,1982340,0,0,0,0
+5129260,2230113,2230113,0,0,0,0
+5488173,2386162,2386162,0,0,0,0
+5859059,2547417,2547417,0,0,0,0
+6241919,2713878,2713878,0,0,0,0
+7374168,3206160,3206160,0,0,0,0
+9570622,3681024,3681024,0,0,0,0
+10458427,4022472,4022472,0,0,0,0
+11380262,4377024,4377024,0,0,0,0
+12336168,4744680,4744680,0,0,0,0
+13326144,5125440,5125440,0,0,0,0
+14994907,5767272,5767272,0,0,0,0
+16130400,6204000,6204000,0,0,0,0
+17304206,6655464,6655464,0,0,0,0
+18586326,7121664,7121664,0,0,0,0
+19766760,7602600,7602600,0,0,0,0
+29216160,9738720,9738720,0,0,0,0
+34949880,11649960,11649960,0,0,0,0
+40930560,13643520,13643520,0,0,0,0
+55017900,18339300,18339300,0,0,0,0
+71510400,23836800,23836800,0,0,0,0
+106974000,35658000,35658000,0,0,0,0
+146061000,48687000,48687000,0,0,0,0
+174405000,58135000,58135000,0,0,0,0
+299999999,99999999,99999999,0,0,0,0
+0,0,0,0,0,0,0,0
diff --git a/db/item_bluebox.txt b/db/item_bluebox.txt new file mode 100644 index 0000000..d43f043 --- /dev/null +++ b/db/item_bluebox.txt @@ -0,0 +1,7 @@ +// File created using Spamrat's Athena Database Editor v1.4.4
+//
+// 古く青い箱から得られるアイテムを設定。
+// ItemID, ItemName(Dummy), Rate
+// アイテムID、アイテムの名前(ダミー)、確率(*10000)をした物です。500000なら50%で1000000なら100%です。)
+// アイテムIDが0の場合確率をアイテムの選択に失敗した時に得られるデフォルトアイテムのIDとして認識します。(複数である場合最後の物だけ有効です。)
+//
diff --git a/db/item_cardalbum.txt b/db/item_cardalbum.txt new file mode 100644 index 0000000..1f13a86 --- /dev/null +++ b/db/item_cardalbum.txt @@ -0,0 +1,7 @@ +// File created using Spamrat's Athena Database Editor v1.4.4
+//
+// 古いカード帖から得られるアイテムを設定。
+// ItemID, ItemName(Dummy), Rate
+// アイテムID、アイテムの名前(ダミー)、確率(*10000)をした物です。500000なら50%で1000000なら100%です。)
+// アイテムIDが0の場合確率をアイテムの選択に失敗した時に得られるデフォルトアイテムのIDとして認識します。(複数である場合最後の物だけ有効です。)
+//
diff --git a/db/item_db.txt b/db/item_db.txt new file mode 100644 index 0000000..6e7a359 --- /dev/null +++ b/db/item_db.txt @@ -0,0 +1,91 @@ +// ID,Name,Jname,Type,Price,Sell,Weight,ATK,DEF,Range,Slot,Job,Gender,Loc,wLV,eLV,View,{UseScript},{EquipScript} + +501,Cactus_Drink,Cactus Drink,0,50,25,70,,,,,10477567,2,,,,,{ itemheal 15,0; },{} +502,Cactus_Potion,Cactus Potion,0,60,35,70,,,,,10477567,2,,,,,{ itemheal 25,10; },{} +503,Casino_Coins,Casino Coins,3,10,10,1,,,,,,,,,,,{},{} +504,Decor_Candy Cane,Decor Candy Cane,3,25,10,10,,,,,,,,,,,{},{} +505,Slime_From_Maggot,Maggot Slime,3,8,4,10,,,,,,,,,,,{},{} +506,Candy_Cane,Candy Cane,0,20,10,1,,,,,10477567,2,,,,,{ itemheal 5,0; },{} +507,Scorpion_Stinger,Scorpion_Stinger,3,15,25,10,,,,,,,,,,,{},{} +508,Xmas_Cake,Xmas Cake,0,70,10,1,,,,,10477567,2,,,,,{ itemheal 10,0; },{} +509,Chocolate_Bar,Chocolate Bar,0,60,20,1,,,,,10477567,2,,,,,{ itemheal 20,0; },{} +510,Candy,Candy,0,20,10,1,,,,,10477567,2,,,,,{ itemheal 5,0; },{} +511,Santa_Hat,Santa Hat,5,400,200,200,,2,,0,10477567,2,256,,0,0,{},{} +512,Ginger_Bread_Man,Ginger_Bread_Man,0,20,25,1,,,,,10477567,2,,,,,{ itemheal 25,0; },{} +513,Slice_Of_Cake,Cake,0,20,15,1,,,,,10477567,2,,,,,{ itemheal 15,0; },{} +514,Xmas_Candy_Cane,Candy_Cane,0,20,15,1,,,,,10477567,2,,,,,{ itemheal 10,0; },{} +515,Purple_present,Purple present,3,20,25,1,,,,,,,,,,,{},{} +516,Yellow_present,Yellow present,3,30,30,1,,,,,,,,,,,{},{} +517,RedScorpionStinger,Red Scorpion Stinger,3,40,85,1,,,,,,,,,,,{},{} +518,Bug_leg,Bug leg,3,60,20,1,,,,,,,,,,,{},{} +519,CherryCake,Cherry cake,0,100,50,1,,,,,10477567,2,,,,,{ itemheal 35,0; },{} +520,Easter_egg,Easter egg,0,200,100,1,,,,,10477567,2,,,,,{ itemheal 100,0; },{} +521,Dagger,Dagger,4,1000,500,100,15,,1,0,10477567,2,2,1,1,1,{},{} +522,Sharp_Knife,Sharp knife,4,100,50,100,10,,1,0,554329694207,2,2,1,1,1,{},{} +523,Leather_Shirt,Leather Shirt,5,10,1000,100,,4,,0,10477567,2,16,,0,0,{},{} +524,Fancy_Hat,Fancy Hat,5,1600,800,200,,5,,0,10477567,2,256,,0,0,{},{} +525,Miner_Hat,Miner Hat,5,800,400,200,,4,,0,10477567,2,256,,0,0,{},{} +526,Coins_Bag,Coins Bag,3,100,50,5,,,,,,,,,,,{},{} +527,Milk,Milk,0,300,150,5,,,,,10477567,2,,,,,{ itemheal 150,0; },{} +528,Boots,Boots,5,8000,500,200,,2,,0,10477567,2,64,,0,0,{},{} +529,Iron_Arrow,Iron Arrow,10,3,0,1,50,,,,35046926835711,2,32768,,1,,{},{} +530,Short_Bow,Short Bow,4,3000,500,200,50,,5,0,10477567,2,34,1,4,11,{},{} +531,Miner_Gloves,Miner Gloves,5,3000,1000,20,,2,,0,10477567,2,4,,0,0,{},{} +532,Leather_Gloves,Leather Gloves,5,6000,2000,30,,3,,0,10477567,2,4,,0,0,{},{} +533,RoastedMaggot,Roasted_Maggot,0,100,55,5,,,,,10477567,2,,,,,{ itemheal 150,0; },{} +534,Orange_Cupcake,Orange_Cupcake,0,80,45,5,,,,,10477567,2,,,,,{ itemheal 100,10; },{} +535,Apple,Apple,0,10,5,5,,,,,10477567,2,,,,,{ itemheal 50,0; },{} +536,Short_Sword,Short Sword,4,6500,4000,100,100,,1,0,10477567,2,2,1,1,1,{},{} +537,Treasure_key,Treasure key,3,100,50,5,,,,,,,,,,,{},{} +538,Green_Present,Green present,0,20,10,10,,,,,10477567,2,,,,,{},{} +539,Beer,Beer,0,175,87,10,,,,,10477567,2,,,,,{ itemheal 200,5; },{} +540,Empty Bottle,Empty Bottle,3,20,10,10,,,,,,,,,,,{},{} +541,Bottle_Water,Bottle Water,0,200,100,10,,,,,10477567,2,,,,,{ itemheal 250,0; },{} +542,Bottled_Sand,Bottled Sand,3,70,35,10,,,,,,,,,,,{},{} +543,Head_Band,Head Band,5,5000,2000,10,,10,,,10477567,2,256,,,,{},{} +544,Silk_Headband,Silk Headband,5,800,400,10,,3,,,10477567,2,256,,,,{},{} +545,Forest_Bow,Forest Bow,4,5000,2500,10,65,,5,,10477567,2,34,,,11,{},{} +546,Desert_Shirt,Desert_Shirt,5,2000,1000,10,,6,,,10477567,2,16,,,,{},{} +547,Spear,Spear,4,20,10,10,100,,,,0,2,2,,,,{},{} +548,Pickaxe,Pickaxe,4,20,10,10,100,,,,0,2,2,,,,{},{} +549,Axe,Axe,4,20,10,10,100,,,,0,2,2,,,,{},{} +550,Blacksmith_Axe,Blacksmith's axe,4,20,10,10,100,,,,0,2,2,,,,{},{} +551,Hint1,Hint,3,0,0,10,,,,,,,,,,,{},{} +552,Hint2,Hint,3,0,0,10,,,,,,,,,,,{},{} +553,Hint3,Hint,3,0,0,10,,,,,,,,,,,{},{} +554,Hint4,Hint,3,0,0,10,,,,,,,,,,,{},{} +555,Hint5,Hint,3,0,0,10,,,,,,,,,,,{},{} +556,Hint6,Hint,3,0,0,10,,,,,,,,,,,{},{} +557,Hint7,Hint,3,0,0,10,,,,,,,,,,,{},{} +558,Hint8,Hint,3,0,0,10,,,,,,,,,,,{},{} +559,Hint9,Hint,3,0,0,10,,,,,,,,,,,{},{} +560,Hint10,Hint,3,0,0,10,,,,,,,,,,,{},{} +561,SabreSword,SabreSword,4,10000,5000,100,100,,1,0,10477567,2,2,1,1,1,{},{} +562,ChickenLeg,ChickenLeg,0,250,125,10,,,,,10477567,2,,,,,{ itemheal 500,5; },{} +563,WinterGloves,WinterGloves,5,6000,3000,20,,2,,0,10477567,2,4,,0,0,{},{} +564,WoolSweather,WoolSweather,5,2000,1000,10,,6,,,10477567,2,16,,,,{},{} +565,Petal,Petal,0,50,25,10,,,,,10477567,2,,,,,{ itemheal 50,5; },{} +566,SmallMushroom,SmallMushroom,0,50,25,10,,,,,10477567,2,,,,,{ itemheal 50,5; },{} +567,IronPotion,IronPotion,0,500,250,10,,,,,10477567,2,,,,,{ sc_start SC_ATKPOT,30,30; },{} +568,ConcentrationPotion,ConcentrationPotion,0,500,250,10,,,,,10477567,2,,,,,{ sc_start SC_SpeedPot0,1800,0; },{} +569,rawlog,rawlog,3,20,10,,,,,,,,,,,,{},{} +570,boneknife,boneknife,4,6500,4000,100,100,,1,0,10477567,2,2,1,1,1,{},{} +571,setzer,setzer,4,6500,4000,100,100,,1,0,10477567,2,2,1,1,1,{},{} +572,scimitar,scimitar,4,6500,4000,100,100,,1,0,10477567,2,2,1,1,1,{},{} +573,falcion,falcion,4,6500,4000,100,100,,1,0,10477567,2,2,1,1,1,{},{} +574,scorpion,scorpion,4,6500,4000,100,100,,1,0,10477567,2,2,1,1,1,{},{} +575,shortbow,shortbow,4,6500,4000,100,100,,1,0,10477567,2,34,1,1,11,{},{} +576,beheader,beheader,4,6500,4000,100,100,,1,0,10477567,2,2,1,1,1,{},{} +577,bonedarts,bonedarts,4,6500,4000,100,100,,1,0,10477567,2,2,1,1,1,{},{} +578,sandcutter,sandcutter,4,6500,4000,100,100,,1,0,10477567,2,2,1,1,1,{},{} +579,rocknife,rocknife,4,15000,7500,100,300,,1,0,10477567,2,34,1,1,1,{},{} +580,staffoflive,staffoflive,4,6500,4000,100,100,,1,0,10477567,2,2,1,1,1,{},{} +581,crescentrod,crescentrod,4,6500,4000,100,100,,1,0,10477567,2,2,1,1,1,{},{} +582,staffofra,staffofra,4,6500,4000,100,100,,1,0,10477567,2,2,1,1,1,{},{} +583,staffofamun,staffofamun,4,6500,4000,100,100,,1,0,10477567,2,2,1,1,1,{},{} +584,jackal,jackal,4,6500,4000,100,100,,1,0,10477567,2,2,1,1,1,{},{} +585,scarabarmelt,scarabarmlet,5,6500,4000,100,,,,0,10477567,2,32,,1,1,{},{} +1199,Arrow,Arrow,10,1,1,1,25,,,,35046926835711,2,32768,,1,,{},{} +1200,Bow,Bow,4,1000,500,200,15,,5,0,10477567,2,34,1,4,11,{},{} +1201,Knife,Knife,4,50,25,100,5,,1,0,554329694207,2,2,1,1,1,{},{} +1202,Cotton_Shirt,Cotton Shirt,5,10,5,100,,2,,0,10477567,2,16,,0,0,{},{} diff --git a/db/item_db2.txt b/db/item_db2.txt new file mode 100644 index 0000000..08f5fd0 --- /dev/null +++ b/db/item_db2.txt @@ -0,0 +1,4 @@ +// File created using Spamrat's Athena Database Editor v1.4.4
+//
+//ID,Name,Jname,Type,Price,Sell,Weight,ATK,DEF,Range,Slot,Job,Gender,Loc,wLV,eLV,View,{UseScript},{EquipScript}
+//
diff --git a/db/item_descriptions.txt b/db/item_descriptions.txt new file mode 100644 index 0000000..88d20e4 --- /dev/null +++ b/db/item_descriptions.txt @@ -0,0 +1,105 @@ +// File created using Spamrat's Athena Database Editor v1.4.4
+501#サ
+502#サ
+503#サ
+504#サ
+505#サ
+506#サ
+507#サ
+508#サ
+509#サ
+510#サ
+511#サ
+512#サ
+513#サ
+514#サ
+515#サ
+516#サ +
+517#サ + +
+518#サ + + +
+519#サ +
+520#サ + +
+521#サ + +
+522#サ +
+523#サ +
+524#サA fancy hat
+525#サA miner hat
+526#サ +
+527#サ + + +
+528#サBoots
+529#サ +
+530#サ +
+531#サ +Boots
+532#サ + +Boots
+533#サ
+534#サ
+535#サ
+536#サ + + +
+537#サ + +
+538#サ
+539#サ
+540#サ
+541#サ
+542#サ
+543#サ
+544#サ
+545#サ
+546#サ
+547#サ A spear
+548#サ A pickaxe
+549#サAn axe
+550#サAn axe usually used by blacksmiths
+551#サAn hint
+552#サAn hint
+553#サAn hint
+554#サAn hint
+555#サAn hint
+556#サAn hint
+557#サAn hint
+558#サAn hint
+559#サAn hint
+560#サAn hint
+561#サSabre sword
+562#サA chicken leg
+563#サ + +Boots
+564#サ +
+565#サ +A chicken leg
+566#サ
+567#サ +
+568#ササ
+1199#サ
+1200#サ
+1201#サ
+1202#サ
diff --git a/db/item_giftbox.txt b/db/item_giftbox.txt new file mode 100644 index 0000000..32088cd --- /dev/null +++ b/db/item_giftbox.txt @@ -0,0 +1,7 @@ +// File created using Spamrat's Athena Database Editor v1.4.4
+//
+// プレゼントボックスから得られるアイテムを設定。
+// ItemID, ItemName(Dummy), Rate
+// アイテムID、アイテムの名前(ダミー)、確率(*10000)をした物です。500000なら50%で1000000なら100%です。)
+// アイテムIDが0の場合確率をアイテムの選択に失敗した時に得られるデフォルトアイテムのIDとして認識します。(複数である場合最後の物だけ有効です。)
+//
diff --git a/db/item_noequip.txt b/db/item_noequip.txt new file mode 100644 index 0000000..5b62c4f --- /dev/null +++ b/db/item_noequip.txt @@ -0,0 +1,4 @@ +// The equipment restriction file
+// here equipment at PvP and GvG
+// where it is possible to restrict, the prescribed form: < ItemID> < Mode>
+// mode = 1- So with PvP restriction, 2- GvG restriction, 3- PvP and GvG which restriction
diff --git a/db/item_scroll.txt b/db/item_scroll.txt new file mode 100644 index 0000000..7a473ff --- /dev/null +++ b/db/item_scroll.txt @@ -0,0 +1,5 @@ +// 古い巻物から得られるアイテムを設定。
+// nameid, item_name(dummy), rate
+// アイテムID、アイテムの名前(ダミー)、確率(*10000)をした物です。500000なら50%で1000000なら100%です。)
+// アイテムIDが0の場合確率をアイテムの選択に失敗した時に得られるデフォルトアイテムのIDとして認識します。(複数である場合最後の物だけ有効です。)
+0,Jellopy,909
diff --git a/db/item_violetbox.txt b/db/item_violetbox.txt new file mode 100644 index 0000000..d0efecd --- /dev/null +++ b/db/item_violetbox.txt @@ -0,0 +1,7 @@ +// File created using Spamrat's Athena Database Editor v1.4.4
+//
+// 古い紫色の箱から得られるアイテムを設定。
+// ItemID, ItemName(Dummy), Rate
+// アイテムID、アイテムの名前(ダミー)、確率(*10000)をした物です。500000なら50%で1000000なら100%です。)
+// アイテムIDが0の場合確率をアイテムの選択に失敗した時に得られるデフォルトアイテムのIDとして認識します。(複数である場合最後の物だけ有効です。)
+//
diff --git a/db/job_db1.txt b/db/job_db1.txt new file mode 100644 index 0000000..e07029b --- /dev/null +++ b/db/job_db1.txt @@ -0,0 +1,70 @@ +//weight,hp,hp2,sp,aspd*17(素手,短剣,片手剣,両手剣,片手槍,両手槍,片手斧,両手斧,片手鈍器,両手鈍器,杖,弓,爪,楽器,鞭,本,カタール)
+//x NOV,SWO,MAG,ARC,ACO,MER,THI,KNI,PRI,WIZ,BLA,HUN,ASS,KNIp,CRU,MON,SAG,ROG,ALC,BAR,DAN,CRUp,WED,SNV
+20000, 0,500,100, 500, 650, 700,2000,2000,2000, 800,2000, 700, 700, 650,2000,2000,2000,2000,2000,2000
+28000, 70,500,200, 400, 500, 550, 600, 650, 700, 700, 750, 650, 700,2000,2000,2000,2000,2000,2000,2000
+22000, 30,500,600, 500, 600,2000,2000,2000,2000,2000,2000,2000,2000, 700,2000,2000,2000,2000,2000,2000
+26000, 50,500,200, 400, 600,2000,2000,2000,2000,2000,2000,2000,2000,2000, 700,2000,2000,2000,2000,2000
+24000, 40,500,500, 400,2000,2000,2000,2000,2000,2000,2000, 600, 600, 600,2000,2000,2000,2000,2000,2000
+28000, 40,500,300, 400, 600, 700,2000,2000,2000, 700, 750, 700, 700,2000,2000,2000,2000,2000,2000,2000
+24000, 50,500,200, 400, 500, 650,2000,2000,2000, 800,2000,2000,2000,2000, 800,2000,2000,2000,2000,2000
+28000,150,500,300, 400, 500, 500, 550, 600, 600, 700, 700, 650, 700,2000,2000,2000,2000,2000,2000,2000
+26000, 75,500,800, 400,2000,2000,2000,2000,2000,2000,2000, 600, 600, 600,2000,2000,2000,2000, 600,2000
+24000, 55,500,900, 500, 575,2000,2000,2000,2000,2000,2000,2000,2000, 625,2000,2000,2000,2000,2000,2000
+30000, 90,500,400, 400, 600, 650,2000,2000,2000, 650, 650, 675, 675,2000,2000,2000,2000,2000,2000,2000
+27000, 85,500,400, 400, 600,2000,2000,2000,2000,2000,2000,2000,2000,2000, 600,2000,2000,2000,2000,2000
+24000,110,500,400, 400, 500, 650,2000,2000,2000, 800,2000,2000,2000,2000,2000,2000,2000,2000,2000, 500
+28000,150,500,300, 400, 500, 500, 550, 600, 600, 700, 700, 650, 700,2000,2000,2000,2000,2000,2000,2000
+28000,110,700,470, 400, 500, 500, 550, 600, 600, 700, 700, 650, 700,2000,2000,2000,2000,2000,2000,2000
+26000, 90,650,470, 400,2000,2000,2000,2000,2000,2000,2000, 575, 575, 575,2000, 475,2000,2000,2000,2000
+24000, 75,500,700, 450, 525,2000,2000,2000,2000,2000,2000,2000,2000, 625,2000,2000,2000,2000, 550,2000
+24000, 85,500,500, 400, 500, 550,2000,2000,2000,2000,2000,2000,2000,2000, 650,2000,2000,2000,2000,2000
+30000, 90,500,400, 400, 550, 575,2000,2000,2000, 675, 700, 650, 650,2000,2000,2000,2000,2000,2000,2000
+27000, 75,300,600, 400, 550,2000,2000,2000,2000,2000,2000,2000,2000,2000, 650,2000, 575,2000,2000,2000
+27000, 75,300,600, 400, 550,2000,2000,2000,2000,2000,2000,2000,2000,2000, 650,2000,2000, 575,2000,2000
+28000,110,700,470, 400, 500, 500, 550, 600, 600, 700, 700, 650, 700,2000,2000,2000,2000,2000,2000,2000
+20000, 0,500,100,2000,2000,2000,2000,2000,2000,2000,2000,2000,2000,2000,2000,2000,2000,2000,2000,2000
+20000, 0,500,100, 500, 650, 700,2000,2000,2000, 800,2000, 700, 700, 650,2000,2000,2000,2000,2000,2000
+20000, 0,500,100, 500, 650, 700,2000,2000,2000, 800,2000, 700, 700, 650,2000,2000,2000,2000,2000,2000
+28000, 70,500,200, 400, 500, 550, 600, 650, 700, 700, 750, 650, 700,2000,2000,2000,2000,2000,2000,2000
+22000, 30,500,600, 500, 600,2000,2000,2000,2000,2000,2000,2000,2000, 700,2000,2000,2000,2000,2000,2000
+26000, 50,500,200, 400, 600,2000,2000,2000,2000,2000,2000,2000,2000,2000, 700,2000,2000,2000,2000,2000
+24000, 40,500,500, 400,2000,2000,2000,2000,2000,2000,2000, 600, 600, 600,2000,2000,2000,2000,2000,2000
+28000, 40,500,300, 400, 600, 700,2000,2000,2000, 700, 750, 700, 700,2000,2000,2000,2000,2000,2000,2000
+24000, 50,500,200, 400, 500, 650,2000,2000,2000, 800,2000,2000,2000,2000, 800,2000,2000,2000,2000,2000
+28000,195,500,390, 400, 500, 500, 550, 600, 600, 700, 700, 650, 700,2000,2000,2000,2000,2000,2000,2000
+26000,97.5,500,1040, 400,2000,2000,2000,2000,2000,2000,2000, 600, 600, 600,2000,2000,2000,2000, 600,2000
+24000,71.5,500,1170, 500, 575,2000,2000,2000,2000,2000,2000,2000,2000, 625,2000,2000,2000,2000,2000,2000
+30000,117,500,520, 400, 600, 650,2000,2000,2000, 650, 650, 675, 675,2000,2000,2000,2000,2000,2000,2000
+27000,110.5,500,520, 400, 600,2000,2000,2000,2000,2000,2000,2000,2000,2000, 600,2000,2000,2000,2000,2000
+24000,143,500,520, 400, 500, 650,2000,2000,2000, 800,2000,2000,2000,2000,2000,2000,2000,2000,2000, 500
+28000,195,500,390, 400, 500, 500, 550, 600, 600, 700, 700, 650, 700,2000,2000,2000,2000,2000,2000,2000
+28000,143,700,611, 400, 500, 500, 550, 600, 600, 700, 700, 650, 700,2000,2000,2000,2000,2000,2000,2000
+26000,117,650,611, 400,2000,2000,2000,2000,2000,2000,2000, 575, 575, 575,2000, 475,2000,2000,2000,2000
+24000,97.5,500,910, 450, 525,2000,2000,2000,2000,2000,2000,2000,2000, 625,2000,2000,2000,2000, 550,2000
+24000,110.5,500,650, 400, 500, 550,2000,2000,2000,2000,2000,2000,2000,2000, 650,2000,2000,2000,2000,2000
+30000,117,500,520, 400, 550, 575,2000,2000,2000, 675, 700, 650, 650,2000,2000,2000,2000,2000,2000,2000
+27000,97.5,300,780, 400, 550,2000,2000,2000,2000,2000,2000,2000,2000,2000, 650,2000, 575,2000,2000,2000
+27000,97.5,300,780, 400, 550,2000,2000,2000,2000,2000,2000,2000,2000,2000, 650,2000,2000, 575,2000,2000
+28000,143,700,611, 400, 500, 500, 550, 600, 600, 700, 700, 650, 700,2000,2000,2000,2000,2000,2000,2000
+20000, 0,500,100, 500, 650, 700,2000,2000,2000, 800,2000, 700, 700, 650,2000,2000,2000,2000,2000,2000
+28000, 70,500,200, 400, 500, 550, 600, 650, 700, 700, 750, 650, 700,2000,2000,2000,2000,2000,2000,2000
+22000, 30,500,600, 500, 600,2000,2000,2000,2000,2000,2000,2000,2000, 700,2000,2000,2000,2000,2000,2000
+26000, 50,500,200, 400, 600,2000,2000,2000,2000,2000,2000,2000,2000,2000, 700,2000,2000,2000,2000,2000
+24000, 40,500,500, 400,2000,2000,2000,2000,2000,2000,2000, 600, 600, 600,2000,2000,2000,2000,2000,2000
+28000, 40,500,300, 400, 600, 700,2000,2000,2000, 700, 750, 700, 700,2000,2000,2000,2000,2000,2000,2000
+24000, 50,500,200, 400, 500, 650,2000,2000,2000, 800,2000,2000,2000,2000, 800,2000,2000,2000,2000,2000
+28000,150,500,300, 400, 500, 500, 550, 600, 600, 700, 700, 650, 700,2000,2000,2000,2000,2000,2000,2000
+26000, 75,500,800, 400,2000,2000,2000,2000,2000,2000,2000, 600, 600, 600,2000,2000,2000,2000, 600,2000
+24000, 55,500,900, 500, 575,2000,2000,2000,2000,2000,2000,2000,2000, 625,2000,2000,2000,2000,2000,2000
+30000, 90,500,400, 400, 600, 650,2000,2000,2000, 650, 650, 675, 675,2000,2000,2000,2000,2000,2000,2000
+27000, 85,500,400, 400, 600,2000,2000,2000,2000,2000,2000,2000,2000,2000, 600,2000,2000,2000,2000,2000
+24000,110,500,400, 400, 500, 650,2000,2000,2000, 800,2000,2000,2000,2000,2000,2000,2000,2000,2000, 500
+28000,150,500,300, 400, 500, 500, 550, 600, 600, 700, 700, 650, 700,2000,2000,2000,2000,2000,2000,2000
+28000,110,700,470, 400, 500, 500, 550, 600, 600, 700, 700, 650, 700,2000,2000,2000,2000,2000,2000,2000
+26000, 90,650,470, 400,2000,2000,2000,2000,2000,2000,2000, 575, 575, 575,2000, 475,2000,2000,2000,2000
+24000, 75,500,700, 450, 525,2000,2000,2000,2000,2000,2000,2000,2000, 625,2000,2000,2000,2000, 550,2000
+24000, 85,500,500, 400, 500, 550,2000,2000,2000,2000,2000,2000,2000,2000, 650,2000,2000,2000,2000,2000
+30000, 90,500,400, 400, 550, 575,2000,2000,2000, 675, 700, 650, 650,2000,2000,2000,2000,2000,2000,2000
+27000, 75,300,600, 400, 550,2000,2000,2000,2000,2000,2000,2000,2000,2000, 650,2000, 575,2000,2000,2000
+27000, 75,300,600, 400, 550,2000,2000,2000,2000,2000,2000,2000,2000,2000, 650,2000,2000, 575,2000,2000
+28000,110,700,470, 400, 500, 500, 550, 600, 600, 700, 700, 650, 700,2000,2000,2000,2000,2000,2000,2000
\ No newline at end of file diff --git a/db/job_db2-2.txt b/db/job_db2-2.txt new file mode 100644 index 0000000..4fc13c6 --- /dev/null +++ b/db/job_db2-2.txt @@ -0,0 +1,26 @@ +// job_bonus 0-non 1-str 2-agi 3-vit 4-int 5-dex 6-luk
+//x NV,SW,MG,AC,AL,MC,TF,KN,PR,WZ,BS,HT,AS,KNp,CR,MO,SA,RG,AM,BA,DC,CRp
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
+0,1,0,0,0,3,0,0,0,5,0,0,0,1,0,0,0,3,0,0,0,5,0,0,0,6,0,0,0,2,0,0,1,0,0,5,0,3,0,1,0,3,0,6,0,2,1,0,1,1
+0,4,0,0,0,5,0,0,0,5,0,0,0,4,0,0,0,2,0,0,0,4,0,0,0,2,0,0,0,6,0,0,4,0,0,5,0,4,0,2,0,6,0,4,0,4,2,0,6,4
+0,5,0,0,0,1,0,0,0,4,0,0,0,5,0,0,0,5,0,0,0,6,0,0,0,2,0,0,0,5,0,0,2,0,0,5,0,1,0,1,0,5,0,6,0,3,4,0,2,5
+0,6,0,0,0,3,0,0,0,4,0,0,0,5,0,0,0,6,0,0,0,2,0,0,0,1,0,0,0,3,0,0,4,0,0,5,0,6,0,2,0,1,0,3,0,4,5,0,1,6
+0,3,0,0,0,5,0,0,0,1,0,0,0,5,0,0,0,3,0,0,0,1,0,0,0,4,0,0,0,3,0,0,2,0,0,6,0,5,0,1,0,5,0,1,0,6,3,0,1,5
+0,2,0,0,0,1,0,0,0,5,0,0,0,3,0,0,0,4,0,0,0,5,0,0,0,6,0,0,0,1,0,0,2,0,0,2,0,1,0,6,0,5,0,3,0,6,1,0,5,2
+1,2,6,5,3,1,1,1,0,2,5,3,4,2,0,5,2,0,1,0,0,3,0,0,1,0,6,5,3,0,5,0,1,0,0,5,2,6,0,3,1,0,3,5,0,1,1,0,5,0,0,1,2,0,0,1,1,3,0,2,0,5,0,1,2,0,4,3,0,1
+4,0,2,3,1,0,4,2,0,0,4,1,5,0,0,5,0,0,2,4,1,3,4,4,0,5,0,5,2,3,1,0,0,4,0,0,5,1,0,6,0,2,5,0,1,5,4,0,6,3,3,0,0,0,2,5,4,3,0,1,4,5,0,0,2,4,3,2,0,4
+4,5,3,0,4,0,0,2,5,4,0,6,0,4,0,0,5,2,4,1,0,5,5,4,0,2,0,4,3,0,5,4,0,2,0,0,4,4,4,1,6,0,5,0,0,4,3,0,4,2,0,0,3,0,4,2,6,0,4,1,5,4,0,0,2,3,5,0,2,4
+5,1,1,4,0,5,2,6,3,0,0,5,3,0,4,6,1,0,2,2,0,4,5,0,0,1,0,6,3,0,2,0,1,4,0,2,0,5,6,0,5,0,0,6,6,0,5,3,0,4,0,1,0,0,0,5,5,2,0,3,4,5,0,2,3,6,6,0,0,5
+5,2,5,5,4,2,0,1,0,2,2,3,0,6,0,5,5,0,0,4,2,5,0,1,6,5,0,2,0,5,6,3,2,0,5,6,0,2,0,5,0,4,2,0,1,5,0,2,0,6,5,0,0,4,3,0,6,2,0,5,1,6,0,0,4,0,0,0,5,6
+2,1,6,2,2,0,1,6,3,5,0,1,0,0,2,6,0,6,0,2,1,0,5,2,2,6,0,0,1,0,2,2,2,6,0,0,5,1,5,0,0,2,5,0,0,2,3,6,0,1,2,0,5,1,0,2,5,0,0,0,5,2,0,5,6,1,0,0,3,5
+1,2,6,5,3,1,1,1,0,2,5,3,4,3,0,5,2,0,1,0,0,3,0,0,1,0,6,5,3,0,5,0,1,0,0,5,2,6,0,3,1,0,3,5,0,1,1,0,5,0,0,1,2,0,0,1,1,3,0,2,0,5,0,1,2,0,4,3,0,1
+3,1,2,0,0,5,4,2,3,1,0,5,0,4,3,2,5,1,0,0,3,0,5,2,0,1,0,0,4,3,0,0,1,0,0,5,2,0,6,1,0,3,4,0,5,0,0,1,3,0,0,2,3,4,1,0,5,0,6,2,4,0,3,1,4,0,6,5,4,2
+1,4,3,2,0,5,0,0,1,0,4,2,6,0,3,5,1,0,0,2,2,5,0,3,0,0,1,0,2,5,0,0,4,6,0,0,1,5,3,0,0,3,0,5,2,6,4,1,0,5,0,2,5,0,0,4,0,3,1,5,0,2,0,4,1,1,5,3,4,2
+4,4,2,0,1,0,3,5,0,0,4,2,0,4,0,5,0,1,0,5,6,4,2,3,0,5,1,0,5,4,0,2,0,5,0,1,5,4,3,0,4,0,2,0,1,5,0,0,4,2,0,5,0,2,5,1,4,0,0,2,0,5,3,4,0,6,0,4,2,4
+1,2,0,6,4,3,0,0,2,5,1,2,0,0,3,5,5,0,0,6,2,1,0,6,0,5,2,0,5,0,6,1,0,2,0,0,5,5,0,0,2,3,1,4,2,0,1,0,5,6,0,5,1,0,0,5,4,2,6,5,0,1,3,2,0,5,1,0,0,2
+5,0,6,0,2,1,4,6,3,5,0,0,4,0,5,0,0,2,0,6,0,4,5,0,6,0,2,0,0,4,1,0,3,6,5,0,0,2,0,0,5,5,5,0,6,4,5,0,5,0,6,6,1,2,0,5,5,0,4,6,3,0,5,6,0,1,2,4,6,5
+2,5,0,2,1,0,5,4,2,1,6,0,2,0,5,3,0,6,1,0,4,0,5,2,0,6,0,4,0,5,0,2,1,0,0,2,0,0,5,5,4,0,5,0,1,0,6,0,2,5,0,0,2,1,0,5,5,2,3,0,5,1,5,0,2,5,0,2,4,1
+5,1,0,2,0,1,0,4,5,0,2,2,2,5,5,0,3,5,0,1,0,5,5,0,2,4,6,5,0,0,2,0,5,0,1,0,0,2,4,0,5,0,5,0,5,0,2,0,5,1,0,2,4,3,0,0,2,5,0,4,2,2,6,0,5,1,2,0,5,2
+3,1,2,0,0,5,4,2,3,1,0,5,0,4,3,2,5,1,0,0,3,0,5,2,0,1,0,0,4,3,0,0,1,0,0,5,2,0,6,1,0,3,4,0,5,0,0,1,3,0,0,2,3,4,1,0,5,0,6,2,4,0,3,1,4,0,6,5,4,2
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
diff --git a/db/job_db2.txt b/db/job_db2.txt new file mode 100644 index 0000000..e5baf62 --- /dev/null +++ b/db/job_db2.txt @@ -0,0 +1,71 @@ +// job_bonus 0-non 1-str 2-agi 3-vit 4-int 5-dex 6-luk
+//x NV,SW,MG,AC,AL,MC,TF,KN,PR,WZ,BS,HT,AS,KNp,CR,MO,SA,RG,AM,BA,DC,CRp
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
+0,1,0,0,0,3,0,0,0,5,0,0,0,1,0,0,0,3,0,0,0,5,0,0,0,6,0,0,0,2,0,0,1,0,0,5,0,3,0,1,0,3,0,6,0,2,1,0,1,1
+0,4,0,0,0,5,0,0,0,5,0,0,0,4,0,0,0,2,0,0,0,4,0,0,0,2,0,0,0,6,0,0,4,0,0,5,0,4,0,2,0,6,0,4,0,4,2,0,6,4
+0,5,0,0,0,1,0,0,0,4,0,0,0,5,0,0,0,5,0,0,0,6,0,0,0,2,0,0,0,5,0,0,2,0,0,5,0,1,0,1,0,5,0,6,0,3,4,0,2,5
+0,6,0,0,0,3,0,0,0,4,0,0,0,5,0,0,0,6,0,0,0,2,0,0,0,1,0,0,0,3,0,0,4,0,0,5,0,6,0,2,0,1,0,3,0,4,5,0,1,6
+0,3,0,0,0,5,0,0,0,1,0,0,0,5,0,0,0,3,0,0,0,1,0,0,0,4,0,0,0,3,0,0,2,0,0,6,0,5,0,1,0,5,0,1,0,6,3,0,1,5
+0,2,0,0,0,1,0,0,0,5,0,0,0,3,0,0,0,4,0,0,0,5,0,0,0,6,0,0,0,1,0,0,2,0,0,2,0,1,0,6,0,5,0,3,0,6,1,0,5,2
+3,0,3,1,6,0,0,3,0,1,5,3,2,0,1,0,3,3,5,6,1,0,3,0,0,0,1,6,3,0,5,0,1,0,0,3,6,2,0,5,0,0,3,0,0,1,1,5,5,0
+6,0,6,1,0,2,3,4,4,6,1,0,0,3,0,5,1,0,0,5,6,4,0,0,5,0,1,0,2,0,6,5,0,3,1,3,2,0,6,0,0,4,4,0,3,0,0,2,0,6
+4,5,0,4,5,2,0,0,4,2,0,1,5,0,6,0,0,4,0,0,0,4,0,2,0,5,0,0,4,0,4,5,4,2,0,6,0,3,5,4,2,0,2,0,4,2,2,4,0,4
+5,0,1,5,5,0,3,1,5,0,6,5,3,0,0,1,0,0,5,3,4,0,1,0,0,5,0,5,2,0,1,3,0,4,0,5,3,2,5,5,0,0,0,1,0,6,5,0,3,0
+5,0,4,5,6,1,0,5,0,1,1,2,0,5,6,0,3,0,2,2,5,0,3,0,0,0,5,0,6,0,2,0,5,4,0,0,0,5,2,0,4,6,5,1,0,4,2,0,5,0
+2,2,2,4,0,3,0,3,5,0,1,0,0,4,2,2,2,2,2,2,2,0,0,5,1,0,1,0,0,5,5,1,0,0,0,0,0,4,0,5,5,4,0,0,1,5,0,1,0,5
+3,0,3,1,6,0,0,3,0,1,5,3,2,0,1,0,3,3,5,6,1,0,3,0,0,0,1,6,3,0,5,0,1,0,0,3,6,2,0,5,0,0,3,0,0,1,1,5,5,0
+6,6,6,6,6,0,1,0,4,0,1,3,0,5,3,0,1,0,0,4,4,3,1,0,1,0,0,5,0,2,0,1,0,5,0,2,0,4,0,3,3,0,0,4,0,3,0,1,0,3
+1,1,0,5,0,2,3,0,0,2,0,1,1,6,0,4,0,2,0,3,0,5,2,0,3,1,1,0,0,5,0,6,3,0,2,0,0,4,0,6,3,0,5,2,0,3,0,0,1,1
+4,0,2,3,0,2,0,4,0,0,3,0,2,0,4,0,6,3,0,5,0,2,0,4,5,0,5,0,0,4,0,5,2,0,6,0,4,0,5,6,0,1,0,1,4,1,1,1,0,4
+2,3,5,0,1,3,2,0,3,0,5,0,0,3,3,2,0,5,0,5,0,0,2,0,1,0,1,0,2,1,0,0,5,5,0,1,0,4,2,0,0,1,4,0,2,0,4,0,4,5
+4,5,5,0,0,1,0,5,4,0,2,0,5,2,1,0,4,0,5,3,5,0,4,4,5,1,0,5,4,0,3,0,0,1,0,3,0,4,0,2,0,0,1,0,2,0,0,0,2,2
+5,2,1,0,4,6,5,0,6,2,2,0,4,0,5,0,3,0,5,6,4,0,0,2,0,0,0,1,0,2,0,5,3,0,2,0,0,5,0,4,6,0,3,0,0,5,4,2,0,5
+6,2,1,0,4,5,6,0,5,2,2,0,4,0,6,0,3,0,6,5,4,0,0,2,0,0,0,1,0,2,0,6,3,0,2,0,0,6,0,4,5,0,3,0,0,6,4,2,0,6
+6,6,6,6,6,0,1,0,4,0,1,3,0,5,3,0,1,0,0,4,4,3,1,0,1,0,0,5,0,2,0,1,0,5,0,2,0,4,0,3,3,0,0,4,0,3,0,1,0,3
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
+0,6,5,0,2,3,0,1,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
+0,1,0,0,0,3,0,0,0,5,0,0,0,1,0,0,0,3,0,0,0,5,0,0,0,6,0,0,0,2,0,0,1,0,0,5,0,3,0,1,0,3,0,6,0,2,1,0,1,1
+0,4,0,0,0,5,0,0,0,5,0,0,0,4,0,0,0,2,0,0,0,4,0,0,0,2,0,0,0,6,0,0,4,0,0,5,0,4,0,2,0,6,0,4,0,4,2,0,6,4
+0,5,0,0,0,1,0,0,0,4,0,0,0,5,0,0,0,5,0,0,0,6,0,0,0,2,0,0,0,5,0,0,2,0,0,5,0,1,0,1,0,5,0,6,0,3,4,0,2,5
+0,6,0,0,0,3,0,0,0,4,0,0,0,5,0,0,0,6,0,0,0,2,0,0,0,1,0,0,0,3,0,0,4,0,0,5,0,6,0,2,0,1,0,3,0,4,5,0,1,6
+0,3,0,0,0,5,0,0,0,1,0,0,0,5,0,0,0,3,0,0,0,1,0,0,0,4,0,0,0,3,0,0,2,0,0,6,0,5,0,1,0,5,0,1,0,6,3,0,1,5
+0,2,0,0,0,1,0,0,0,5,0,0,0,3,0,0,0,4,0,0,0,5,0,0,0,6,0,0,0,1,0,0,2,0,0,2,0,1,0,6,0,5,0,3,0,6,1,0,5,2
+1,2,6,5,3,1,1,1,0,2,5,3,4,2,0,5,2,0,1,0,0,3,0,0,1,0,6,5,3,0,5,0,1,0,0,5,2,6,0,3,1,0,3,5,0,1,1,0,5,0,0,1,2,0,0,1,1,3,0,2,0,5,0,1,2,0,4,3,0,1
+4,0,2,3,1,0,4,2,0,0,1,5,0,0,5,0,0,2,4,1,3,4,4,0,5,0,5,5,2,3,1,0,0,4,0,0,5,1,0,6,0,2,5,0,1,5,4,0,6,3,3,0,0,0,2,5,4,3,0,1,4,5,0,0,2,4,3,2,0,4
+4,5,3,0,4,0,0,2,5,4,0,6,0,4,0,0,5,2,4,1,0,5,5,4,0,2,0,4,3,0,5,4,0,2,0,0,4,4,4,1,6,0,5,0,0,,4,3,0,4,2,0,0,3,0,4,2,6,0,4,1,5,4,0,0,2,3,5,0,2,4
+5,1,1,4,0,5,2,6,3,0,0,5,3,0,4,6,1,0,2,2,0,4,5,0,0,1,0,6,3,0,2,5,1,4,0,2,0,5,6,0,5,0,0,6,6,0,5,3,0,4,0,1,0,0,0,5,0,0,0,3,4,5,0,2,0,6,6,0,0,5
+5,2,5,5,4,2,0,1,0,2,2,3,0,6,0,5,5,0,0,4,2,5,0,1,6,5,0,2,0,5,6,3,2,0,5,6,0,2,0,5,0,4,2,0,1,5,0,2,0,5,4,0,0,4,3,0,6,2,0,5,1,6,0,0,4,0,0,0,5,6
+2,1,6,2,2,0,1,6,3,5,0,1,0,0,2,6,0,6,0,2,1,0,5,2,2,6,0,0,5,0,2,2,2,6,0,0,5,1,5,0,0,2,5,0,0,2,3,6,0,1,2,0,5,1,0,2,5,0,0,0,5,2,0,5,6,1,0,0,3,6
+1,2,6,5,3,1,1,1,0,2,5,3,4,2,0,5,2,0,1,0,0,3,0,0,1,0,6,5,3,0,5,0,1,0,0,5,2,6,0,3,1,0,3,5,0,1,1,0,5,0,0,1,2,0,0,1,1,3,0,2,0,5,0,1,2,0,4,3,0,1
+3,1,2,0,0,5,4,2,3,1,0,5,0,4,3,2,6,1,0,0,3,0,6,2,0,1,0,0,5,4,0,0,1,0,0,6,2,0,6,1,0,3,4,0,5,0,0,1,3,0,0,2,3,4,1,0,5,0,6,2,4,0,3,1,4,0,6,5,3,2
+1,4,3,2,0,5,0,0,1,0,4,2,6,0,3,5,1,0,0,2,2,5,0,3,0,0,1,0,2,5,0,0,4,6,0,0,1,5,3,0,0,3,0,4,2,6,4,1,0,5,0,2,5,0,0,4,0,3,1,5,0,2,0,4,1,1,5,3,4,2
+4,4,2,0,1,0,3,5,0,0,4,2,0,4,0,5,0,1,0,5,6,4,2,3,0,5,1,0,5,4,0,2,0,5,0,1,5,4,3,0,4,0,2,0,1,5,0,0,4,2,0,5,0,2,5,1,4,0,0,2,0,5,3,4,0,6,0,4,2,4
+1,2,0,6,4,3,0,0,2,5,1,2,0,0,3,5,5,0,0,6,2,1,0,6,0,5,2,0,5,0,6,1,0,2,0,0,5,5,0,0,2,3,1,4,2,0,1,0,5,6,0,5,1,0,0,5,4,2,6,5,0,1,3,2,0,5,1,0,0,2
+5,0,6,0,2,1,4,6,3,5,0,0,4,0,5,0,0,2,0,6,0,4,5,0,6,0,2,0,0,4,1,0,3,6,5,0,0,2,0,0,5,5,5,0,6,4,5,0,5,0,6,6,1,2,0,5,5,0,4,6,3,0,5,6,0,1,2,4,6,5
+2,5,0,2,1,0,5,4,2,1,6,0,2,0,5,3,0,6,1,0,4,0,5,2,0,6,0,4,0,5,0,2,1,0,0,2,0,0,5,5,4,0,5,0,1,0,6,0,2,5,0,0,2,1,0,5,5,2,3,0,5,1,5,0,2,4,0,2,4,1
+5,1,0,2,0,1,0,4,5,0,2,2,2,5,5,0,3,5,0,1,0,5,5,0,2,4,6,5,0,0,2,0,5,0,1,0,0,2,4,0,5,0,5,0,5,0,2,0,5,1,0,2,4,3,0,0,2,5,0,4,2,2,6,0,5,1,2,0,5,2
+3,1,2,0,0,5,4,2,3,1,0,5,0,4,3,2,6,1,0,0,3,0,6,2,0,1,0,0,5,4,0,0,1,0,0,6,2,0,6,1,0,3,4,0,5,0,0,1,3,0,0,2,3,4,1,0,5,0,6,2,4,0,3,1,4,0,6,5,3,2
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
+0,1,0,0,0,3,0,0,0,5,0,0,0,1,0,0,0,3,0,0,0,5,0,0,0,6,0,0,0,2,0,0,1,0,0,5,0,3,0,1,0,3,0,6,0,2,1,0,1,1
+0,4,0,0,0,5,0,0,0,5,0,0,0,4,0,0,0,2,0,0,0,4,0,0,0,2,0,0,0,6,0,0,4,0,0,5,0,4,0,2,0,6,0,4,0,4,2,0,6,4
+0,5,0,0,0,1,0,0,0,4,0,0,0,5,0,0,0,5,0,0,0,6,0,0,0,2,0,0,0,5,0,0,2,0,0,5,0,1,0,1,0,5,0,6,0,3,4,0,2,5
+0,6,0,0,0,3,0,0,0,4,0,0,0,5,0,0,0,6,0,0,0,2,0,0,0,1,0,0,0,3,0,0,4,0,0,5,0,6,0,2,0,1,0,3,0,4,5,0,1,6
+0,3,0,0,0,5,0,0,0,1,0,0,0,5,0,0,0,3,0,0,0,1,0,0,0,4,0,0,0,3,0,0,2,0,0,6,0,5,0,1,0,5,0,1,0,6,3,0,1,5
+0,2,0,0,0,1,0,0,0,5,0,0,0,3,0,0,0,4,0,0,0,5,0,0,0,6,0,0,0,1,0,0,2,0,0,2,0,1,0,6,0,5,0,3,0,6,1,0,5,2
+3,0,3,1,6,0,0,3,0,1,5,3,2,0,1,0,3,3,5,6,1,0,3,0,0,0,1,6,3,0,5,0,1,0,0,3,6,2,0,5,0,0,3,0,0,1,1,5,5,0
+6,0,6,1,0,2,3,4,4,6,1,0,0,3,0,5,1,0,0,5,6,4,0,0,5,0,1,0,2,0,6,5,0,3,1,3,2,0,6,0,0,4,4,0,3,0,0,2,0,6
+4,5,0,4,5,2,0,0,4,2,0,1,5,0,6,0,0,4,0,0,0,4,0,2,0,5,0,0,4,0,4,5,4,2,0,6,0,3,5,4,2,0,2,0,4,2,2,4,0,4
+5,0,1,5,5,0,3,1,5,0,6,5,3,0,0,1,0,0,5,3,4,0,1,0,0,5,0,5,2,0,1,3,0,4,0,5,3,2,5,5,0,0,0,1,0,6,5,0,3,0
+5,0,4,5,6,1,0,5,0,1,1,2,0,5,6,0,3,0,2,2,5,0,3,0,0,0,5,0,6,0,2,0,5,4,0,0,0,5,2,0,4,6,5,1,0,4,2,0,5,0
+2,2,2,4,0,3,0,3,5,0,1,0,0,4,2,2,2,2,2,2,2,0,0,5,1,0,1,0,0,5,5,1,0,0,0,0,0,4,0,5,5,4,0,0,1,5,0,1,0,5
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
+6,6,6,6,6,0,1,0,4,0,1,3,0,5,3,0,1,0,0,4,4,3,1,0,1,0,0,5,0,2,0,1,0,5,0,2,0,4,0,3,3,0,0,4,0,3,0,1,0,3
+1,1,0,5,0,2,3,0,0,2,0,1,1,6,0,4,0,2,0,3,0,5,2,0,3,1,1,0,0,5,0,6,3,0,2,0,0,4,0,6,3,0,5,2,0,3,0,0,1,1
+4,0,2,3,0,2,0,4,0,0,3,0,2,0,4,0,6,3,0,5,0,2,0,4,5,0,5,0,0,4,0,5,2,0,6,0,4,0,5,6,0,1,0,1,4,1,1,1,0,4
+2,3,5,0,1,3,2,0,3,0,5,0,0,3,3,2,0,5,0,5,0,0,2,0,1,0,1,0,2,1,0,0,5,5,0,1,0,4,2,0,0,1,4,0,2,0,4,0,4,5
+4,5,5,0,0,1,0,5,4,0,2,0,5,2,1,0,4,0,5,3,5,0,4,4,5,1,0,5,4,0,3,0,0,1,0,3,0,4,0,2,0,0,1,0,2,0,0,0,2,2
+5,2,1,0,4,6,5,0,6,2,2,0,4,0,5,0,3,0,5,6,4,0,0,2,0,0,0,1,0,2,0,5,3,0,2,0,0,5,0,4,6,0,3,0,0,5,4,2,0,5
+6,2,1,0,4,5,6,0,5,2,2,0,4,0,6,0,3,0,6,5,4,0,0,2,0,0,0,1,0,2,0,6,3,0,2,0,0,6,0,4,5,0,3,0,0,6,4,2,0,6
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
diff --git a/db/mob_branch.txt b/db/mob_branch.txt new file mode 100644 index 0000000..78811b7 --- /dev/null +++ b/db/mob_branch.txt @@ -0,0 +1,9 @@ +// File created using Spamrat's Athena Database Editor v1.4.4
+//
+// 古木の枝で召喚できるモンスターを設定
+// MobID, MobName(Dummy), Rate
+// モンスターID、モンスターの名前(ダミー)、確率(*10000)をした物です。500000なら50%で1000000なら100%です。)
+// モンスターIDが0の場合確率をモンスターの選択に失敗した時に召還されるモンスターのIDとして認識します。(複数である場合最後の物だけ有効です。)
+// クライアント側で未実装のものを書くとクライアントが重力エラー
+// そしてその敵がキャラクターの付近にいる間ログインできないので注意。
+//
diff --git a/db/mob_db.txt b/db/mob_db.txt new file mode 100644 index 0000000..f25f90b --- /dev/null +++ b/db/mob_db.txt @@ -0,0 +1,18 @@ +// ID,Name,JName,LV,HP,SP,EXP,JEXP,Range1,ATK1,ATK2,DEF,MDEF,STR,AGI,VIT,INT,DEX,LUK,Range2,Range3,Scale,Race,Element,Mode,Speed,ADelay,aMotion,dMotion,Drop1id,Drop1per,Drop2id,Drop2per,Drop3id,Drop3per,Drop4id,Drop4per,Drop5id,Drop5per,Drop6id,Drop6per,Drop7id,Drop7per,Drop8id,Drop8per,Item1,Item2,MEXP,ExpPer,MVP1id,MVP1per,MVP2id,MVP2per,MVP3id,MVP3per + +1002,Maggot,Maggot,1,45,0,5,2,1,5,10,0,5,1,1,1,0,6,30,1,1,1,3,21,129,800,1872,672,480,505,800,501,150,518,400,533,150,502,70,522,1,909,0,0,0,0,0,0,0,,,,,, +1003,Scorpion,Scorpion,10,150,0,15,4,1,10,15,0,5,1,1,1,0,15,30,1,1,1,3,21,129,2000,1872,672,480,507,700,510,100,509,50,518,700,909,0,909,0,909,0,0,0,0,0,0,0,,,,,, +1004,RedScorpion,Red Scorpion,90,550,0,180,145,1,50,55,4,6,10,10,10,10,30,10,1,1,1,0,20,129,1000,1872,672,480,517,2000,509,100,518,500,1200,10,1199,760,1201,540,524,5,0,0,0,0,0,0,,,,,, +1005,GreenSlime,Green Slime,40,300,0,60,8,1,25,30,2,7,1,30,1,0,30,30,1,1,1,0,20,131,1500,1872,672,480,502,200,513,100,501,100,521,100,522,190,526,10,503,500,535,750,0,0,0,0,,,,,, +1006,GiantMaggot,Giant Maggot,80,500,0,160,16,2,45,50,2,7,10,8,2,1,22,1,2,1,1,0,20,129,2000,1872,672,480,1199,900,519,100,518,750,501,5000,502,3000,503,11,526,10,0,0,0,0,0,0,,,,,, +1007,Yellow_slime,Yellow Slime,60,400,0,120,2,1,35,40,2,7,10,8,2,1,34,1,1,1,1,0,20,131,1400,1800,672,480,534,200,519,100,501,350,502,250,522,10,909,0,909,0,0,0,0,0,0,0,,,,,, +1008,Red_slime,Red Slime,70,450,0,140,56,1,40,45,2,7,15,10,2,1,25,1,1,1,1,0,20,135,1300,1500,672,480,1201,300,509,110,521,200,523,40,525,80,535,750,528,250,531,150,0,0,0,0,,,,,, +1009,BlackScorpion,Black Scorpion,100,600,0,250,70,1,60,65,4,6,20,20,10,10,35,10,1,1,1,0,20,133,1000,1500,672,480,523,150,509,100,518,800,909,0,909,0,909,0,909,0,0,0,0,0,0,0,,,,,, +1010,Snake,Snake,150,850,0,375,100,1,85,90,4,6,20,20,10,10,35,10,2,1,1,0,20,133,1000,1500,672,480,524,300,0,0,0,0,909,0,909,0,909,0,909,0,0,0,0,0,0,0,,,,,, +1011,FireGoblin,Fire Goblin,20,200,0,30,2,1,15,20,0,5,1,1,1,0,6,30,1,1,1,3,21,129,800,1872,672,480,505,800,501,150,518,800,502,150,521,70,522,1,909,0,0,0,0,0,0,0,,,,,, +1012,Spider,Spider,140,800,0,350,280,1,80,85,4,6,20,20,10,10,35,10,2,1,1,0,25,175,1000,1500,672,480,537,500,535,100,0,0,909,0,909,0,909,0,909,0,0,0,0,0,0,0,,,,,, +1013,EvilMushroom,EvilMushroom,110,650,0,275,110,1,65,70,4,6,20,20,10,10,35,10,1,1,1,3,25,137,800,1800,672,480,535,500,540,5,534,100,566,500,0,0,0,0,909,0,0,0,0,0,0,0,,,,,, +1014,SleepFlower,SleepFlower,120,700,0,300,110,4,70,75,0,5,20,20,20,20,20,20,5,5,1,3,25,128,800,800,672,480,535,100,540,5,1199,100,526,400,565,500,0,0,909,0,0,0,0,0,0,0,,,,,, +1015,SantaSlime,SantaSlime,130,750,0,325,0,2,75,80,2,7,1,30,1,0,30,30,2,2,1,0,20,161,1500,1872,672,480,512,800,513,700,514,600,519,500,527,400,538,500,0,0,0,0,0,0,0,0,,,,,, +1016,RudolphSlime,RudolphSlime,50,350,0,100,16,1,30,35,2,7,1,30,1,0,30,30,1,1,1,0,20,131,1000,1872,672,480,504,800,506,500,508,200,509,300,510,600,515,800,516,500,0,0,0,0,0,0,,,,,, +1017,Bat,Bat,30,250,0,45,2,1,7,10,0,5,1,1,1,0,20,25,1,1,1,3,21,129,800,1872,672,480,505,800,501,150,518,400,533,150,521,70,522,1,909,0,0,0,0,0,0,0,,,,,, diff --git a/db/skill_db.txt b/db/skill_db.txt new file mode 100644 index 0000000..71b76e6 --- /dev/null +++ b/db/skill_db.txt @@ -0,0 +1,18 @@ +//skill_db.txt@AthenaDB計画 2004/06/06 14:27:11 +0900 (JST) +//id,range,hit,inf,pl,nk,max,list_num,castcancel,cast_defence_rate,inf2,maxcount,skill_type,blow_count +1,0,0,0,0,0,9,0,no,0,0,0,none,0 //NV_BASIC +2,0,0,0,0,0,10,0,no,0,0,0,weapon,0 //SM_SWORD +4,0,0,0,0,0,10,0,no,0,0,0,none,0 //SM_RECOVERY +9,0,0,0,0,0,10,0,no,0,0,0,none,0 //MG_SRECOVERY +36,0,0,0,0,0,10,0,no,0,0,0,none,0 //MC_INCCARRY +37,0,0,0,0,0,10,0,no,0,0,0,none,0 //MC_DISCOUNT +38,0,0,0,0,0,10,0,no,0,0,0,none,0 //MC_OVERCHARGE +48,0,0,0,0,0,10,0,no,0,0,0,weapon,0 //TF_DOUBLE +49,0,0,0,0,0,10,0,no,0,0,0,weapon,0 //TF_MISS +142,0,6,4,0,1,1,1,no,0,1,0,none,0 //NV_FIRSTAID +144,0,0,0,0,0,1,0,no,0,1,0,none,0 //SM_MOVINGRECOVERY +146,0,0,0,0,0,1,0,no,0,1,0,weapon,0 //SM_AUTOBERSERK +150,0,6,4,0,1,1,1,no,0,1,0,weapon,5 //TF_BACKSLIDING +//NPC Skills +196,0,0,4,0,1,10,1,no,0,2,0,magic,0 //NPC_SUMMONSLAVE +197,0,0,4,0,1,10,1,no,0,2,0,none,0 //NPC_EMOTION diff --git a/db/skill_require_db.txt b/db/skill_require_db.txt new file mode 100644 index 0000000..a7c6314 --- /dev/null +++ b/db/skill_require_db.txt @@ -0,0 +1,5 @@ +//skill_require_db.txt@AthenaDB計画 2004/06/16 02:45:31 +0900 (JST) +//id,list_hp,list_sp,list_hp_rate,list_sp_rate,list_zeny,list_weapon,state,spiritball,itemid1,amount1,itemid2,amount2,itemid3,amount3,itemid4,amount4,itemid5,amount5,itemid6,amount6,itemid7,amount7,itemid8,amount8,itemid9,amount9,itemid10,amount10 + +142,0,3,0,0,0,99,none,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 //NV_FIRSTAID#応急手当# +150,0,7,0,0,0,99,none,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 //TF_BACKSLIDING#バックステップ#
\ No newline at end of file diff --git a/db/skill_tree.txt b/db/skill_tree.txt new file mode 100644 index 0000000..51b14da --- /dev/null +++ b/db/skill_tree.txt @@ -0,0 +1,15 @@ +// JobNo,Skill-ID,MaxLV,RequiredSkill-ID,RequredSkill-Lv,.v5.ツ.ワ.ナ.J..ヤ.オ + +//Novice +0,1,3,0,0,0,0,0,0,0,0,0,0//NV_BASIC # basic skill + +// Soul-less +1,1,9,0,0,0,0,0,0,0,0,0,0//NV_BASIC # basic skill +1,2,5,0,0,0,0,0,0,0,0,0,0//SM_SWORD # sword damage add skill +1,4,10,0,0,0,0,0,0,0,0,0,0//SM_RECOVERY # hp regen (requires first aid) +1,9,10,4,10,0,0,0,0,0,0,0,0//MG_SRECOVERY # sp regen (requies first aid) +1,36,10,0,0,0,0,0,0,0,0,0,0//MC_INCCARRY # increase max carry weight +1,37,10,0,0,0,0,0,0,0,0,0,0//MC_DISCOUNT # get cheaper prices from npc +1,38,10,0,0,0,0,0,0,0,0,0,0//MC_OVERCHARGE # sell stuff for better price to npc +1,48,10,2,5,0,0,0,0,0,0,0,0//TF_DOUBLE # a chance for double hits, requires sword damage add lvl 5 +1,49,10,2,10,0,0,0,0,0,0,0,0//TF_MISS # increased chance of dodging, requires backsliding
\ No newline at end of file diff --git a/db/statpoint.txt b/db/statpoint.txt new file mode 100644 index 0000000..fd1409e --- /dev/null +++ b/db/statpoint.txt @@ -0,0 +1,255 @@ +48
+51
+54
+57
+60
+64
+68
+72
+76
+80
+85
+90
+95
+100
+105
+111
+117
+123
+129
+135
+142
+149
+156
+163
+170
+178
+186
+194
+202
+210
+219
+228
+237
+246
+255
+265
+275
+285
+295
+305
+316
+327
+338
+349
+360
+372
+384
+396
+408
+420
+433
+446
+459
+472
+485
+499
+513
+527
+541
+555
+570
+585
+600
+615
+630
+646
+662
+678
+694
+710
+727
+744
+761
+778
+795
+813
+831
+849
+867
+885
+904
+923
+942
+961
+980
+1000
+1020
+1040
+1060
+1080
+1101
+1122
+1143
+1164
+1185
+1207
+1229
+1251
+1273
+1295
+1318
+1341
+1364
+1387
+1410
+1434
+1458
+1482
+1506
+1530
+1555
+1580
+1605
+1630
+1655
+1681
+1707
+1733
+1759
+1785
+1812
+1839
+1866
+1893
+1920
+1948
+1976
+2004
+2032
+2060
+2089
+2118
+2147
+2176
+2205
+2235
+2265
+2295
+2325
+2355
+2386
+2417
+2448
+2479
+2510
+2542
+2574
+2606
+2638
+2670
+2703
+2736
+2769
+2802
+2835
+2869
+2903
+2937
+2971
+3005
+3040
+3075
+3110
+3145
+3180
+3216
+3252
+3288
+3324
+3360
+3397
+3434
+3471
+3508
+3545
+3583
+3621
+3659
+3697
+3735
+3774
+3813
+3852
+3891
+3930
+3970
+4010
+4050
+4090
+4130
+4171
+4212
+4253
+4294
+4335
+4377
+4419
+4461
+4503
+4545
+4588
+4631
+4674
+4717
+4760
+4804
+4848
+4892
+4936
+4980
+5025
+5070
+5115
+5160
+5205
+5251
+5297
+5343
+5389
+5435
+5482
+5529
+5576
+5623
+5670
+5718
+5766
+5814
+5862
+5910
+5959
+6008
+6057
+6106
+6155
+6205
+6255
+6305
+6355
+6405
+6456
+6507
+6558
+6609
+6660
+6712
+6764
+6816
+6868
+6920
+6973
+7026
+7079
+7132
+7185
diff --git a/doc/Changelog.txt b/doc/Changelog.txt new file mode 100644 index 0000000..bc4d7df --- /dev/null +++ b/doc/Changelog.txt @@ -0,0 +1,1729 @@ +Date Added +10/15 + * another fix to oA2eA-rc5.sql to fix the sell_price [MouseJstr] + * Redid readme, updated content a bit, and made ps2 poster in preperation for 1.0 RC5 release. [Evera] + * made MAX_HAIR_STYLE, MAX_HAIR_COLOR, and MAX_CLOTH_COLOR + configurable via battle_config. [MouseJstr] + * made the dex spell cast time scaling configurable via battle_config + [MouseJstr] + * made the visible area_size configurable via battle_config + [MouseJstr] + * Reorganized and added the warps into their respective folders, updated map_athena.conf. [Nana] + * Added 2 script commands GETLOOK, GETSAVEPOINT (thanks to Lorky). [Lupus] + * Added kafra_bank NPC - a new bank with daily interst of 0.1#%. [Lupus] + * Changed cast time for champion soul collect to 1 sec (thanks to midas). [Valaris] + * Added midas's fix for +30 hitrate with Sonic Blow. [Valaris] + +10/14 + * Fixed help.txt for some @gm commands [MouseJstr] + * made login.c compile on gcc 2.95 [MouseJstr] + * Fix backwards compatability with old mob skill formats [MouseJstr] + * Added mail system commands and @refreshonline to help.txt. [Valaris] + * Fixed char guild storage bug [MouseJstr] + * Prevented breakage of Weapons/Armor if the respective Alchemist Chemical Protection skill is applied. [spira] + * Added so nochat end will set manner to 0. [Valaris] + * Added broken item check before breaking (to prevent an item from breaking more than once) [Valaris] + * Re-wrote parts of equipment breaking. It will check to make sure it was not a miss before doing break calculations. [Valaris] + * Re-added negative value exploit fix. [Valaris] + * Fixed guardian opposition search. [Valaris] + * Added @unmute. [Valaris] + * Added check for map existing before cleanup (server will exit instead of crashing if maps aren't found). [Valaris] + +10/13 + * Fixed parse of antifreeze enable/disable flag. [Valaris] + * Fixed end-of-line flags in source tree [MouseJstr] + +10/12 + * Prevent attack/skill usage while wearing tux/wedding dress. [Valaris] + * Remove item unequip on tux/wedding equip. [Valaris] + +10/11 + * Added mail system (for SQL version only). [Valaris] + - @checkmail + - @listmail + - @listnewmail + - @readmail <#> + - @deletemail <#> + - @sendmail <name> <message> + - @sendprioritymail <name> <message> + - Added battle_athena.conf option to enable/disable mail system. + - Added mail.sql for sql mail system. + - Use "*" for name to send to all players. Level must be >= to @sendprioritymail level. + + * Added @refreshonline to refresh player online status in SQL version. [Valaris] + +10/10 + * Modified ja blacksmith repair code to function with our broken equipment system. [Valaris] + * Removed ja repair script commands, they aren't compatible with our broken equipment. [Valaris] + * Fixed error in Morroc jewel dealer. [Valaris] + * Added so supernovices will get flee from improve dodge. [Valaris] + * Fixed assassin and rogue flee bonus, was giving +50 instead of +40. [Valaris] + * Fixed bug with using remove trap on a snared monsters and the monster would stay stuck. [Valaris] + * Added a check for row return in an sql statement in login. [Valaris] + * Added some pointer initializations in map.c. [Valaris] + * Calculate pc status when equipping or unequipping pet items. [Valaris] + * Fixed crash in looting pet skills if loot size was greater than 10. [Valaris] + +10/05 + * Fixed all mob spawn names (typos, errors, wrong names) according to the mob_db.txt [Lupus] + * Added YggdrasilKid's fixed exp.txt. [valaris] + +10/04 + * Added missed close file functions in pc.c [Lupus] + * Added some new items, fixed some item names and translated all grabled items in item_db.txt [Lupus] + * Fixed bug in MOB_DB and in MOB_BRANCH DB related to wrong "Elder" ID number [Lupus] + * Fixed toggle for using sql item db in char-server. [Valaris] + * Added interval settings for anti-freeze system. [Valaris] + * Added enable/disable options in char_athena.conf and login_athena.conf for anti-freeze system. [Valaris] + * Re-added anti-freeze system. [Valaris] + * Cleaned up warning in src/common/db.c. [MouseJstr] + * Removed afm map checking from char. [Valaris] + * npc.c - Re-enabled new "OnTouch" function. Added a missing line to npc_event in npc.c(line 667). + pc.c - Added 2 missing "else" statements on (line 3702 and 3842). Now ontouch works correctly and activates + everytime instead of only once like in RC4. [kobra_k88] + * clif.c - Added: npc_scriptcont(sd,RFIFOL(fd,2)); to "void cliff_parse_NpcCloseClicked" function. + Now "close2" script command works properly. Previously, it would freeze the server.[kobra_k88] + +10/03 + * added oA to eA database conversion .sql [MouseJstr] + * renamed sql to sql-files to eliminate a problem with make [MouseJstr] + * Fixed Assassin Quest where "Nameless One" NPC would freeze after clicking "next". [Shinigami] + * charkillable now returns status on target [MouseJstr] + * skill names now have descriptions pulled from the client [Mousejstr] + * Mapflag "petals" has been changed to "sakura". [Valaris] + * The weather @commands will now set that particular mapflag on until server is restarted, and will send weather effect + to everyone on that map. It also will not allow the occurance of the command more than once. This method means + everyone coming onto the map after the command has been used will see the effect. [Valaris] + * Added yor's latest ladmin.c. [Valaris] + * Fixed bug in delitem script command that caused it to delete all of an item. [Valaris] + * Modified Makefile's to work better in non-sql environment [MouseJstr] + * A lot of work on source tree making it more ANSI C compliant [MouseJstr] + * Finalized the timers on shutdown + turned off malloc debugging on db's. [MouseJstr] + * Added release hook's into db's [MouseJstr] + * Added runflag to core to allow cleaner shutdown [MouseJstr] + * Work on LCCWIN32 for building under Windows natively [MouseJstr] + * modified @mapexit to use runflag for cleaner shutdown [MouseJstr] + * Improve cleanup on exit of map server of all objects [MouseJstr] + * statpoint reader can now be larger then 1432 bytes [MouseJstr] + * Fixed so dancing effect is cleared when warping. [Valaris] + +10/02 + * Modified battle config muting players option to work more effectively. [Valaris] + * Fixed mute from showing red bubble to players. [Valaris] + * Fixed disguise sprite staying when teleporting. [Valaris] + * Added a special mob ai check in mob.c. [Valaris] + * Fixed petit pet skill's damage from ever increasing. [Valaris] + * Removed pet weight and pet loot options, someone re-added them, and this conflicts with pet skills. [Valaris] + * Added new skills to mob_skill_db (skills for Alchemist summon marine sphere among others) [Valaris] + * Updated skill_cast_db and skill_require_db for summon marine sphere and biocanniablize skills. [Valaris] + * Fix for drops by luck crashing. [Valaris] + * Removed @nuke for now. [Valaris] + * Added japanese ankle snare code. [Valaris] + * Using better code for alchemist marine sphere. [Valaris] + * Reverted back to old method of OnTouch for now for compatibility with older scripts. [Valaris] + * Fixed an infinite loop with preventing multi level up and high novices. [Valaris] + * Storage saving in SQL was based on MAX_INVENTORY. Switched to MAX_STORAGE and MAX_GUILD_STORAGE. [Valaris] + Should resolve items disappearing. Thanks to fov for pointing this out. + * Updated skill_db entries for biocannibalize and summon marine sphere. [Valaris] + * Changed sql item name row sizes to 24, and changed memory allocation to 25 bytes instead of 29. [Valaris] + * Added upgrade_1.0.0-rc5_database.sql (will set name columns of item db to varchar(24). [Valaris] + * Removed anti-freeze system. [Valaris] + * Began removing AFM, unless someone wants to complete this. [Valaris] + * Added an online status timer. Will check online status of players every hour (for sql version). [Valaris] + * Added malloc to map.c. [Valaris] + * Added some variables for new mob exclusion. [Valaris] + * Using malloc in pc.c. [Valaris] + * Added skill reset checks to high/advanced/baby classes. [Valaris] + +10/01 + * Fixed up const.db. [Valaris] + - Added + bBreakWeaponRate 1068 + bBreakArmorRate 1069 + bAddStealRate 1070 + + - Fixed + bMagicDamageReturn 1071 + bRandomAttackIncrease 1072 + bAllStats 1073 + bAgiVit 1074 + bAgiDexStr 1075 + bPerfectHide 1076 + bDisguise 1077 + + * Rewrote mute disable option in clif.c, it did not need to end status. [Valaris] + * Added a missing check for SC_NOCHAT in clif.c. [Valaris] + * Changed comments on max_lv in battle_config to be less + confusing [MouseJstr] + * Fixed Gypsy Job Quest, added Crusader Job Quest [Lupus] + * Adding missing noquests from previous tree [MouseJstr] + * merged conf files between txt and sql [MouseJstr] + * According to item_db.txt and mob_db.txt fixed some name differences, wrong item names in + pet_db.txt,mob_branch.txt,item_violetbox.txt,item_giftbox.txt,item_cardalbum.txt [Lupus] + * Added specialeffect2 script command. Works the same as specialeffect, but the effect will be applied + to the player interacting with the npc instead of being applied on the npc. [Valaris] + * Added hasitems script command. If a player has any items it will return 1 in an if statement. [Valaris] + * Added npctalk script command. Syntax : npctalk "These are my words"; [Valaris] + * Removed script::say in favor of Valaris's version. [MouseJstr] + * Fixed a bug in guild alliances where it was treating a friend as a foe. [MouseJstr] + * Fixed crash in weapon breaking. Was not doing a block type check on the source. [Valaris] + + +09/29 + * Fixed small, almost non-existing memoryleak, in grfio.c that could + cause some systems to exit the application [Kalaspuff] + +09/26 + (Dated On Aegis Website) + *--Released 1.0.0 RC4--* +09/25 + * Reorganized Npc folder for release + * Updated map_athena.conf + * Remove certain statuses when warping (Cloaking, Sitting, Gangster's Paradise) that would allow the effect to + continue and a player to walk normally. [Valaris] + * Remove speed increase of theif and high thief from improve dodge (only assassins and assassin cross's are + supposed to get this bonus) [Valaris] + * removed more #ifdef's between TXT and SQL [MouseJstr] +09/24 + + * Added @charkillable (to make players killable) [MouseJstr] + * Added @dropall (throws all items on ground) [MouseJstr] + * Added @chardropall (throws all players items on ground) [MouseJstr] + * Added @storeall (put all items in store) [MouseJstr] + * Added @charstoreall (put all players items in storage) [MouseJstr] + * Added @skillid (look up a skill by name) [MouseJstr] + * Added @useskill (use a skill by id) [MouseJstr] + +09/23 + * Update Some Npc Stuff In Payon [Darkchild] + * Added Sage Quest [Darkchild] + * added @killable - all players can hit you, even out of pvp [MouseJstr] + * Changed Basilica to prevent the priest from walking [MouseJstr] + * Restore base files in save directory [Yor] + * Castle spawn/conquering fix: [Akaru] + - Spawns monster when the guild is not owned when the server starts + - Spawns emperium with the monsters when guild is not owned yet + - If AgitStart and AgitEnd occurs while castle had not been owned, it is just ignored so that the monsters that are guarding the castle and the emperium would not be killed. + - Prevent spawn of emperium/monsters after castle is owned + - In short, you can clear and conquer the castle any time even when it's not agitstart'ed if the castle is unowned, like in official servers + +09/22 + * added @killer to let GM's hit players outside of pvp [MouseJstr] + * added @skilloff to turn off skills on a map [MouseJstr] + * added @skillon to turn on skills on a map [MouseJstr] + * added @follow to follow players (including warping after them) [MouseJstr] + * added battle_config option to control the max number of castles a guild can have [MouseJstr] + * upgraded the guild alliance checker [MouseJstr] + * added battle_config to control if ghosty armor works for mobs [MouseJstr] + * Fixed battle_config to control how much GTB actually helps [MouseJstr] + * Fixed battle_config to control if pvp battles give exp or drops [MouseJstr] + * Added map aliasing [MouseJstr] + * changed the map_nick2sd to be able to handle sub-strings for refering to players [MouseJstr] + * made turning of skills on a map actually work [MouseJstr] + * Updated Makefiles/GNUMakefiles to include the compilation of /common/malloc.o [PoW] + +09/19 + * Corrected @charstoragelist: [Yor] + - because account2storage function create a void storage if it's not exist, somethimes a player could lost its storage. + -> creation of account2storage2 to just ask pointer of storage if exist and use it in @charstoragelist. + * Improved range system of @monster/@spawn (nearest of GM when less monsters) [Yor] + * Client until 2004-09-06 (included) is now completely recognized [Yor] + * Fixed kafra storage request for all new clients [Yor] + +09/18 + * Changed to Yor's code that doesn't try and read when it's already not found [Akaru] + * Removed check for resnametable in data folder exit -> the resnametable isn't even used if present and it should be optional and not compulsory to have a resnametable in the data folder! [Akaru] + * Added an option to choose which clients are accepted on the server (2nd part and finish: Check accepted versions) [Yor] + * Added an option to choose which clients are accepted on the server (1st part: reading of the option) [Yor] + * Fixed action request (sit, attack, etc..) for all new clients [Yor] + * Fixed solve char name for all new clients [Yor] + * Fixed get char name for all new clients [Yor] + * Fixed an error about packet number on char name function [Yor] + * Added items drop packet for all new clients [Yor] + * Fixed: in clif_send (global send, not individual send) don't send a packet that client doesn't recognize [Yor] + +09/17 + * Added check to make sure mob is already moving before initiating random walk + when the item they are going to loot disappears. [Valaris] + * Client of 2004-07-13 is now completely recognized [Yor] + * Fixed items take packet for all new clients [Yor] + * Corrected packet sizes for each client version (not set packet size if packet doesn't exist) [Yor] + * Fixed direction position for all new clients [Yor] + +09/16 + * Client of 2004-07-06 is now completely recognized [Yor] + * Fixed skills to position with all new clients [Yor] + * Fixed incomplete packet of authentification [Yor] + * Fixed size of packets for all clients [Yor] + * Improved detection of client version at authentification [Yor] + * Improved packet parsing of client (search packet version before to parse) [Yor] + * Added packets size of 0x20d and 0x20e [Yor] + +09/15 + * Added "nude" script command, will strip player of all equipment. Syntax : nude; [Valaris] + * Removed sandstorm flag, since it does not work as intended (is not a constant effect like believed) [Valaris] + * Added specialeffect script command. Syntax: specialeffect #; [Valaris] + * Moved noicewall flag to a better position. [Valaris] + * Added mob type check to skill_castfix to prevent crashing. [Valaris] + * corrected error: skill to id (RFIFOW->RFIFOL) [Yor] + * Added packets size of 0x204 and 0x20b [Yor] + * Fixed skill_to_id with all new clients [Yor] + * Fixed tick sending with all new clients [Yor] + * Fixed items use with all new clients [Yor] + * Fixed move with all new clients [Yor] + * Speedup WantToConnect function [Yor] + * Call correct function with any clients packet - size is not correctly set actually [Yor] + +09/14 + * Resetlvl will unequip items that require more than level 1. [Valaris] + * Added message about new clients (not actually supported) [Yor] + * Added correct call of clif_parse_WantToConnection function for all client types [Yor] + * Added size of packet 0x0214 [Yor] + * Fixed calls of 'clif_send' function [Yor] + +09/13 + * Modified item_db to have more name consistency, added more translations for items [Akaru] + * Fixed in clif_sitting usage of a buffer [Yor] + * Fixed in clif_movechar usage of a buffer for clif_set007b [Yor] + * Fixed in clif_spawnpc usage of a buffer for clif_set0078 [Yor] + * Corrected a commented printf in packet 0x2b0e for debug [Yor] + +09/12 + * Put (commented) parse printf in char.c at the right place (to have all informations) [Yor] + * Added Alchemist JobQuest [Darkchild] + * Added Inn Npcs [Darkchild] + +09/11 + * Added @charitemlist/@charstoragelist/@charcartlist GM commands to display items of a player [Yor] + +09/10 + * Added a better id control of monster id in @spawn/@monster... GM commands [Yor] + * Correction of an error at reading of 'wisp_server_name' option (char.c) [Yor] + +09/09 + * Change @spawn/@monster2... GM commands to spawn in close area [Yor] + * commented some printf in char.c to reduce (a little) lag [Yor] + * Put @timer messages in msg_athena.conf [Yor] + * Add a file for SQL developpers [Yor] + * Changed name of newpacket variable of session (packet_ver) and add a message for nex client version [Yor] + * Added new client packet sizes and expanded packet acception. [Valaris] + * Added snow, fog, rain, leaves, petals, and sandstorm map flags. [Valaris] + +09/08 + * added clif_specialeffect to clif.c, for displaying a wide variety of effects (information from rofx) [Valaris] + * Corrected in map: creation of new session before to delete previous session_data [Yor] + * Corrected in login/char/ladmin: creation of new session before to delete previous session_data [Yor] + * Prevented redeal dupe. Checks to make sure inventory index has not already been added (client prevents + the possibility of stacking items, but server did not.) [Valaris] + +09/07 + * Added an option to determinate at which GM level nowarp and nowarpto flags are not more used [Yor] + - new option in battle.conf: any_warp_GM_min_level + - new checks and message about 'from' (nowarp) and 'to' (nowarpto) in GM commands + * Third part of new authentification method (clif_parse_CloseKafra): ignore new 0xF7 [Yor] + * Change name of new0x0072 variable by new_auth (because now, auth packet is 0x72 or 0x7E) [Yor] + * Second part of new authentification method (clif_parse_WantToConnection): Auth by char-server of new packets resolved [Yor] + * Improved check of new packet 0x7E to enter in WantToConnect Function [Yor] + +09/06 + * First part of new authentification method (clif_parse_WantToConnection) [Yor] + - accepted new 0x7E for wantto connect + - begin to found first authentificaiton and answer to client, but not found + * Improved Char-server: [Yor] + - Fixed a possible error on multi-map servers (no lastpoint) + - Added log about character with same name of wisp server name. + - Added a sub function to disconnect a player (used 3 times) + * Improved. In global message, use buffer to send to other (avoid possible overwriting). [Yor] + * Added Super Novice Job Quest [Darkchild] + +09/05 + * Fixed noskill flag to actually stop all usable skills when used. [AppleGirl] + * Removed last lag on char-server causing by too much savings (on accreg) [Yor] + * Removed a lag on char-server with a lot of players (for party, like guild) [Yor] + * Removed a big lag on char-server with a lot of players: [Yor] + - guilds was saved every time a player is online and a lot of other time (removed). + - guilds are save only when characters are saved (no more bring forward with characters file, and less savings) + * Added GM message that informs when a player blocks wisps of the server (against some bots that auto ignore wisps from a player) [Yor] + * Added perfect GM hide in @where GM command [Yor on suggestion of PoW] + * Correct NEW shops.txt with error displayed AT START of map-server! [Yor] + * Correct NEW payon.txt that crash map-server AT START! [Yor] + * More changes and official locations for new payon, things in correct place: + - Guild Flags + - Kafra + - Shops/Refiners (except Pet Shop!) + - Most Normal Npcs (not all!) + All Based On ScreenShots From kRO!! [Darkchild] + * Payon Warps about 80% finished, others were no screens taken from! [Darkchild] + * Removed nowarp mapflag and new internal check about gmlevel [Yor] + - gmlevel are checked before to call the function (of the GM comand). If you give gmlevel, people can use the function, including if you give level 0. + if you want refuse some GM commands to normal players, set the level of the command with a value upper than 0 (of set GM_only_command option to yes). + Not add new tests for nothing and use more cpu. + - mapflag: no limit for GM commands: what'is the interest to have gm commands limited like player? (it's rule for all GM commands) + A gm can have the possibility to go anywhere, specialy to check/control player ==> no mapflag! + * Stopped client crashes when jobchanging into another class that doesn't support the weapon you are holding. [Valaris] + +09/04 + * Added nowarp mapflag [PoW] + * Fixed nowarpto mapflag in atcommand.c [PoW] + * Improved: On hack about name in global message, GM of ALL map-server received hack messages. [Yor] + * Moved some GM messages of GM command from at_command.c to msg_athena.txt. [Yor] + * Added @whogm GM command. [Yor] + * Added a warning when a character has same name than wisp server name. [Yor] + +09/03 + * Added gm level display in all @who(map) GM command. [Yor] + * Correction: [Yor] - Thanks to [EvilEden] + - Soul Strike mistake (according to http://iro.ragnarokonline.com/game/jobmagskill.asp) - corrected cast time + - npc/quests/monstertamers.txt: The Monster Tamer Shogo gives the Deviruchi 'Contracts in Shadow' (641) -> the Bapho Jr. Tamming Item 'Book_of_theDevil' (642). + * Added monster/egg name to create egg in @makeegg GM command. [Yor] + * Added an option to fix started limited time of a new account. [Yor] + * Added @hatch in help.txt. [Yor] + * Fixed ChaseWalk so now you can't use skills while in chase walk mode. [AppleGirl] + * Fixed Looping of Broken Weapon if you continued to use it even after its broken [AppleGirl] + * Updated Weapon Breaking So Now Icon Will Disappear when you unequip the broken item [AppleGirl] + * Re-Added @hatch and added makepet to atcommand_athena.conf [Darkchild] + * Moved job monsters to their respected files [Darkchild] + * Fixed a bug in sence (showing wrong info!) [Darkchild] + * Changed the npc/jobs/ folders heavily! [Darkchild] + * Added COMPLETE Umbala NPC script [Darkchild] + * Added New Payon maps @ bottom of the map_athena.conf [Darkchild] + * Added New Payon NPCs and Warps [Darkchild] + * Fixed typo and made layout a litle bit better of the readme files [Darkchild] + * Added Items Ability to be unbreakable (using bonus bUnbreakable,100;) it will totally unbreakable + any lower than a 100 it still has a chance of breaking but call it more durable ;p[AppleGirl] + * Added Start_Weapon and Start_Armor to all the other starts in char_athena.conf [AppleGirl] + * Updated skill_require_db and skill_cast_db.txt [AppleGirl] + * Finish to add a (reserved) wisp name for server - part 3: in map-server replace name where server name is used for wisp [Yor] + * Continue to add a (reserved) wisp name for server - part 2: in map-server, reception of the name [Yor] + * Begin to add a (reserved) wisp name for server - part 1: in char-server [Yor] + +09/02 + * Added Meltdown's weapon breaking ability [AppleGirl] + * Added GM level in front of each line to display only enable GM command of the help.txt. [Yor] + * Removed message about limited time if your have unlimited account (that was for tests. sorry). [Yor] + * Set day/night messages in msg_athena.conf (for foreign people). [Yor] + * Added a function to return a string of msg_table outside of at_command. [Yor] + * Fixed clean database at disconnect when session is not auth. [Yor] + * Fixed a bug (but not solve the bug - need more research to found where solve it) in map_id2sd function. [Yor] + * Set initialisation of ignore list after authentification (not need to be done before) [Yor] + * Reduced size of packet 0x2afe (removed not used value) [Yor] + * Improve first auth part (wanttoconnect) [Yor] + * Added TODO 35 [Yor] + * Made a few adjustments to Archer based class skills making more like normal classes instead of Uber Classes. [AppleGirl] + * Updated the weapon and armor breaking to be more like official RO adding status icons and so on. [AppleGirl] + * Updated a few skills based off a few sites, also fixed other skills that were coded incorrectly. [AppleGirl] + * Fixed double connection with same account [Yor] + - disconnect immediatly + - no enter in database (block or others) + - no creation of session + - no more need to have 'new_fd' + -> less memory usage, less tests + * Removed 'other_fd' not used [Yor] + +09/01 + * Fixed Rainbow Egg quest in quests_lutie NPC [PoW] + * Added packet length for client server-side friend list, if they choose add the client will not disconnect. [Valaris] + Need to figure out the rest of the packets, then can implement. + * Added server-side friend list 'add' send packet info to packet documentation. [Valaris] + * Fixed: in chrif_authreq, don't send a request to char server if session is not found [Yor] + * Fixed: amatsu.txt, when Carter Moores say: remove 10000 zeny, he removes them now! [Yor] + * Removed an unknown item in shops.txt [Yor] + * Fixed 3 goto in father - acolyte.txt [Yor] + * Fixed bug with colors of 5 numbers/digits (warp to city instead of display with color) - wizard.txt [Yor] + * Reduce size of mage.txt and fix a text in mes (no bug) [Yor] + * Fixed thief.txt script (error on a goto) [Yor] + * Fixed account length (WFIFOL(fd,2) instead of WFIFOW(fd,2)) when char-srv doesn't auth an account for map-srv (0x2afe packet) [Yor] + * Added @job and @charjobchange to be similar with @charjob and @jobchange [Yor] + +08/31 + * Fixed messages of @(char)jobchange GM commands. [Yor] + * Fixed a return value of jobchange function (pc.c). [Yor] + * Added coredumps by system when crash. [Yor] - thanks MagicalTux + +08/30 + * Added some comments on gravity commands. [Yor] + * Improved /mm, /monster and /item to have some answers. [Yor] + * Modified skill_cast_db and skill.c to allow for status effect chance of wizard skills + (stun for WZ_METEOR and blind for WZ_VERMILLION) [moonsoul] + * Modified skill_cast_db and skill_db to properly reflect cast params and delay times for assassin cross + skills Create Deadly Poison(407) and Soul Breaker(379) [moonsoul] + * Added damage calcs to battle.c for Assassin Cross skill Soul Breaker(379) [moonsoul] + +08/29 + * Modified skill_require_db to reflect item requirements for assassin cross skills Create Deadly Poison(407) + and Enchant Deadly Poison(378) [moonsoul] + * Added @charmountpeco/@charpetrename/@charquestskill/@charlostskill GM commands. [Yor] + * Added noicewall.txt to map_athena.conf (commented out), noicewall flag [Valaris] + will not function when pvp is enabled on map. + * Added noicewall flag, noicewall.txt. [Valaris] + * Added wiz's temp fix for higher skill ids and skillnodex db. [Valaris] + * Added: Usage of At command when failed (100%). + * Modified: try to have samme presentation for all at-command code (100%). + * Improved/fixed some GM commands [Yor] + * Added some 'usages' in some GM commands [Yor] + * Added baby classes to equipment codes, they will equip the same as their normal and advanced counter-parts. [Valaris] + * Improved some GM commands [Yor] + - Added in @(char)baselvlup/@(char)joblevelup GM command: + When there are some status/skill points available and we reduce level -> remove points. + - Added counter in @statsall GM command. + - Fixed in @charsave GM command when map doesn't exist. + - Fixed @charbaselvl GM command overflow + - Fixed skill value (and crash) in @questskill/@lostskill GM command + - Fixed number of spiritballs (@spiritball GM command) + * Fixed random PvP crash bug [Lupus] + +08/28 + * Improved some GM commands [Yor] + - Added memo points in @go GM commmand. + - Added negativ value for @heal GM command. + - Fixed @(6stat-adjustement) GM commands overflow. + - Simplified @Statall GM commands. + - Fixed @guildlvup GM commands. + +08/27 + * Fixed @(char)zeny/stpoint/skpoint GM commands (no more overflow, etc...) [Yor] + * Improved @memo GM command: [Yor] + You can memo anywhere (it's GM command, not /memo command like for any player). + Without value, display actual memo points. + * Improved @refine GM command: you can reduce refinement. [Yor] + * Fixed @broadcast GM command send broadcast to all Map-servers. [Yor] + * Fixed @(char)base/joblevelup command (any adjustement can be entered). [Yor] + +08/26 + * In at_command.c: [Yor] + Fixed: initialisation of all strings. + Added: Usage of At command when failed (75%). + Modified: try to have samme presentation for all at-command code (75%). + * Made it so that the pvp_nightmaredrop mapflag would work even without pvp being enabled, now works any time it is set. [Ancyker] + * Added level check on /item and /monster. GM level must be >= both @monster and @item [Valaris] + level specifications (both since they share the same packet) + * Added my own extream mode map flags (disabled by default), makes players drop items at random everywhere (except towns). Full details in map_athena.conf. [Ancyker] + +08/25 + * Added: display usage when GM command failed (some GM commands) [Yor] + * Added @email GM command to change your account e-mail [Yor] + +08/24 + * Modified changesex script command use same function as @charchangesex now [Yor] + * Added @charchangesex GM command can be used on offline players [Yor] + * Added gm ladmin command to change GM level of an account [Yor] + +08/23 + * Fixed some at_command messages: [yor] + - remove all unused messages + - create commented line of all messages in msg_athena.conf to have english, and under, translation if necessary + - add some (arround 80, but not all) messages in msg_athena.conf + +08/22 + * Fixed: In GM commands, add a 'standard' message when GM level is too low [Yor] + * Changed in battle_athena.conf [Yor] + - atcommand_spawn_quantity_limit: 100 (20 is too short for mass spawnings.) + - unknow: give translation of babelfish to help on the explanation (not writing: 'unknow' to not lost the information!) + - day_duration: 7200000 & night_duration: 1800000 (set default to 30 min night, 2 hours day to show improvement of eathena by DEFAULT) + +08/21 + * Updated battle_athena.conf to be in English (not Engrish) [Ancyker] + * Updated WZ_FIREPILLAR according 8-10 patch [AppleGirl] + * Updated skill_cast_db and skill_require_db according to 8-10 kRO patch [AppleGirl] + * Added how to use skill_castnodex_db.txt [AppleGirl] + * Added skill_castnodex_db.txt, allowing some skills to be casted with out dex's effect on them + Midas' idea [AppleGirl] + * Improved answer messages of char-server on @(un)ban/@(un)block GM commands [Yor] + * Added answer messages of char-server on @(un)ban/@(un)block GM commands [Yor] + * Added block command (ladmin c) [Yor] + * Added block command (ladmin perl) [Yor] + * Fixed job_db1.txt comments to be in english, also spaced the columns to make it easier to read. [Ancyker] + * Added: possibility to use "" or '' to give an account name in ladmin (Perl) (no more problem with account name which have spaces). [Yor] + * Changed: atcommand_gm_only is set to 'no' by default, because: [Yor] + - GM commands level now works correctly + - GM commands levels are now set by default with diffrent types of GM + * Added @inall/@exall GM command to block/unblock ALL wispers of a player [Yor] + +08/20 + * Added unblock command (ladmin C) [Yor] + * Added unblock command (ladmin perl) [Yor] + * Fixed: when limited time is in past, add new duration starts from actual time [Yor] + * Added chardisguise/charundisguise [Kalaspuff] + * Added default time [23:59:59] for timeset/banset ladmin (C) commands [Yor] + * Fixed sage rebirth ability at professor job change NPC. [PoW] + * Fixed pecopeco knight/crusader rebirth ability at LordKnight and Paladin job change NPCs. [PoW] + * Added default time [23:59:59] for timeset/banset ladmin (perl) commands [Yor] + * Added a check_ip_flag option in char-server [Yor] + * Added a check_ip_flag option in login-server [Yor] + * Added a display at start of login-server about IP checking configuration. [Yor] + +08/19 + * Added unban/unbanish command (ladmin c) [Yor] + * Added unban/unbanish command (ladmin perl) [Yor] + * Added parameter to choose how works timeadd (ladmin command) with unlimited time accounts [Yor] + * Fixed @option/@charoption when player is disguised. [Yor] + * Added: some information about TODO 19. [Yor] + * Added: possibility to use "" or '' to give an account name in ladmin (C) (no more problem with account name which have spaces). [Yor] + +08/18 + * Guardians were immune to skills/spells, fixed. [Valaris] + * Added custom draculax.txt script to display npcskilleffect command. [Valaris] + * Added npc/events/custom folder. [Valaris] + * Fixed Umbala Language Quest NPC bug [PoW] + * Added temporary prevention of crash caused by peco + disguise, will look into a better solution. [Valaris] + * Changed working of putting disguise on (much like Yor's setpos with undisguise) [Valaris] + * Changed disguise id check to > 23 instead of max_pc_class. [Valaris] + * Added bDisguise script command for items. [Valaris] + Syntax : bonus bDisguise,npc_id/mob_id; Example bonus bDisguise,1002; for poring disguise + * Added flag so @disguise command will override any disguise scripts. [Valaris] + * Updated @disguise description in help.txt. [Valaris] + * Added: @ignorelist/@charignorelist to know from which people a player ignore wisps [Yor] + * Renamed: @makepet -> @makeegg (@makepet will be created later to create pet, not a egg) [Yor] + * Fixed Phantom of Opera quest, fixed quests_aldebaren requirements [PoW] + * Changed: anti-freeze disconnection in char-server set from 1mn 15s to 30s [Yor] + * Fixed: free block memory on NULL pointer in char-server do_final [Yor] + * Fixed: reset server information when map-server disconnected from char-server [Yor] + * Changed: anti-freeze disconnection in login-server set from 1mn 15s to 30s [Yor] + * Improved pecopeco checks in @option/@charoption/@jobchange/@charjob [Yor] + +08/17 + * Fixed pecopeco displayings in @option/@charoption/@jobchange/@charjob [Yor] + * Correction of @rura, @where, @rurap and some other things in GM commands. [Yor] + * Correction of a parameters' errors in GM commands. [Yor] + * Correction of @charzeny Gm command (+ @zeny/@charzeny can add and remove zeny without problem). [Yor] + * Acolyte Job Quest: Fixed Marthilda, Yosuke NPC bugs. [Lupus] + * Added some comments in .conf about @gm [Yor] + * Fixed max value of level_new_gm parameter (not 100, but 99) [Yor] + * Added level_new_gm parameter in login-server to disable or set level of all GM created by @gm [Yor] + * Fixed @GM GM command [Yor] + +08/16 + * Translation of final message of @gm [Yor] + * Fixed on @GM: When login server is offline, char server sends impossible to create GM [Yor] + * Fixed: refuse @gm GM command to ... a GM :) [Yor] + * Added complete answers of /in /ex /inall /exall. [Yor] + * Added option to send information to online GM when there is a hack, a spoof name, etc. [Yor] + * Added individual ignore management, and wisp checks [Yor] + * Added TODO 33 [Yor] + * Fixed possible overflow with @ban GM command [Yor] + * Speed up a little @ban GM command [Yor] + * Added ignore all for wisps (to same map-server). Sorry, before, I was added only for not same map-server [Yor] + +08/15 + * Added a resume for 'email_creation' parameter [Yor] + * Fixed double messages when a player wisp/page itself [Yor] + * Added ignore all for wisps [Yor] + * Added better explanation for the new 'email_creation' parameter [Yor] + * Fixed: now, GM accounts are sended to all servers when auto-detect change of GM file is actived [Yor] + * Fixed: at auto-creation of e-mail, don't ask the player if login-server is offline (we need login-server to save e-mail) [Yor] + * Used config_switch instead of atoi for email_creation parameter [Yor] + * Added an option to create e-mail at connection with client [Yor] + * Added authentification with login_id2 (1040) - activated by default [Yor] + * Added partial part of authentification with login_id2 (1040) [Yor] + * Added some missing GM commands in help.txt [Yor] + * Improved search of map-server when map is not found [Yor] + * Added banish in ladmin (c) to be like of GM commands [Yor] + * Fixed Angel Helm quest, fixed Spore Doll quest exploit, Fixed Morgenstein quest bug [Lupus] + +08/14 + * Added banish in ladmin (perl) to be like of GM commands [Yor] + * Fixed some possible errors with maps management in char-server [Yor] + * Fixed bug of map searching when to few maps on map-servers [Yor] + * Changed some console displayings in char-server [Yor] + * By default, activation of player ip check [Yor] + * Added some of 1040: check of player ip between each server [Yor] + * Fixed dupes in warp scripts, reorganized, thanks to midas fro GH warp [kobra_k88] + * Added possibility to disable automatic reload of GM accounts file [Yor] + * Added log when GM accounts file can not be readed [Yor] + * Changed default of GM accounts file check for 120 sec to 15 sec [Yor] + * Added automatic reload of GM accounts file if it was modified [Yor] + * Added @mapmove, @broadcast, and @localbroadcast in help.txt [Yor] + * Fixed possible error of monster id (GM command) if monster begins by a number. [Yor] + * Added jobname in @charstats command. [Yor] + * Fixed: If a GM command uses NULL pointer as command function, there is no more crash. [Yor] + * Fixed Niflheim&Umbala guides. Fixed Niflheim city. removed 2 NPC clones. [Lupus] + * Added Archer Skills quests, fixed Thief Skill Quest [kobra_k88] + +08/13 + * Add an option to fix a ban for hacker that spoof name (to set minutes of ban) [Yor] + * Fixed incorrect mob IDs with Valaris's mob_db [Ancyker] + * Added possibility to execute GM commands when you wisp someone [Yor] + * Added a console message when a player try to spoof his name in Global message [Yor] + * Added size of packet in packet send by map-srvr to char-srvr about online players [Yor] + * Added check on individual stat at creation of a character [Yor] + * Added npcskilleffect script command. Will allow npc to show effects of certain skills on specified XY coordinate. + Syntax : npcskilleffect 21,10,148,150; skillid, skilllv, x, y. [Valaris] + * Increase maximum weight can be used with an item now, thanks to orn. [Valaris] + * Fixed mob names in Gonryun , thanks to unsul and Filougarou. [Valaris] + * Fixed muramasa curse rate, thanks to OxiMoron. [Valaris] + * Fixed Aldeabran&Comodo towns. Added Cheese quest to Comodo. Fixed Thief Job Quest [kobra_k88] + +08/12 + * Begin to add 1040 in login/char/chrif about authentification. not finished [Yor] + * @mapmove, @broadcast, and @localbroadcast can now be used. [Valaris] + * Looting mobs will not continue to item if it disappears, instead will walk away. [Valaris] + * Unitinalized nameid in script.c [Valaris] + * Added position in log when unknown packets are saved (login-server). [Yor] + * Corrected possible error to contact not good player when a wisp concerns player on an other map-server. [Yor] + * Added a function to obtain character name with index in auth structure (char.c). [Yor] + * Corrected check/test error in mapif_send. [Yor] + +08/11 + * itemdb_searchname fixed (now firstly looks for item aliases 'name', if not found looks for item name 'jname'). [Lupus] + * Optimized getitem, delitem. getweight, fixed there possible scripts exploits [Lupus] + * Thief bug's long name in mob_db.txt was Thief Bug Larva, fixed. [Valaris] + * Thief bug names were mixed around in monster.txt, fixed. [Valaris] + * Fixed incorrect parse of usable item rate. [Valaris] + * Added ban command in ladmin (C), like GM command [Yor] + * Terminated some translations about wisp/page, and control/improvement/correction of wisp/page. All 'found' bugs are corrected [Yor] + * Added translations in inter.c about wisp/page. [Yor] + * Corrected printf datas in intif_parse_WisMessage when map-srv receives wisp message from inter-srv. [Yor] + * Sended an answer when a wisp/page is supressed because of a timeout. [Yor] + * Improved: if inter-srv is asked for a wisp, verify first if the character exists. Don't ask all map-server if it not exists. [Yor] + * Changed Printf in intif_wis_message to have better information. [Yor] + * Improved: Don't ask inter-server for a whisp/page if player is on the same map-server. [Yor] + * Completed some printf and comments in char.c [Yor] + +08/10 + * Improved map_nick2sd function. Now, sensitiv case is removed when it's possible [Yor] + * Corrected split of broadcast messages in char-server [Yor] + * Message to Valaris from Yor (special file) + * Improved accounts file saving: [Yor] + - be sure that accounts file is save at least every minute. + - save accounts file at end of login-server + * Reduce displaying size on some errors about accounts file reading. [Yor] + * Created log when change sex packet give an invalid value (login-server) [Yor] + * Correction of unknow_packet displaying (in log). Separate is done after 8th char [Yor] + * Give possibility to use account name with spaces in prompt commands finished by account name in ladmin (perl). [Yor] + +08/09 + * Fixed char-server lag. 3 tests from 3 people appeared to function properly again. [Valaris] + * Basic Implementation of Basilica [AppleGirl] + * Added ban command (ladmin perl) [Yor] + * Correction of bug about auth_fifo when we block a player. Invalid value in array (login.c) [Yor] + * Correction of some tests on server_fd array in login.c [Yor] + * Improved Item_searching in all GM commands [Yor] + * Created @undisguise GM command [Yor] + * Added Umbala town&quests&guide. Some fixes in Yuno warps&shops&guides. Kafra fix. Added Valhallen quest. [kobra_k88],[Lupus] + * Corrected char_divorce (char-server) to remove ring to both partner [Yor] + * Eventual crashfix for clif_authfail_fd [Kalaspuff] + * Corrected @jump GM command. [Yor] + * Reduced size of conf/help.txt (max 200 lines in chat window). [Yor] + +08/08 + * Added NPCs to @disguise. [Valaris] + * Mob disguises will now show up to player using it as well. Attack animation and sitting do show up to them yet. [Valaris] + * Added @disguise GM command in help files [Yor] + * Added @go 16 to visit prisoners [Yor] + * Reduced number of accounts file saving when informations are not important: [Yor] + - save immediatly any modification/creation/deletion of account (like before) + - use counter before saving if only ip/time of last connection is changed (normal authentification) + because these values are already save in log file. + * Speed up account searching in login-server. [Yor] + * Speed up character searching for @(un)block/@(un)ban. [Yor] + * Created @unjail/@discharge GM command. [Yor] + * According to GM definition level in at_command.conf, set level 20 to online_gm_display_min_level for online files. [Yor] + * Speed up some sortings of online creation (strings based sortings) [Yor] + * Fixed nullpointer crash with disguise in clif_changelook [Kalaspuff] + +08/07 + * Fixed so spells will still be cast if target walks out of range. (please don't overwrite this again) [Valaris] + * Corrected a little error in online files creation (only 1 player was visible) [Yor] + * Optimized memory management of online list [Yor] + - less memory usage + - less tests and loops (more speed) + - more efficient (use directly char_dat position instead of search it) + * Added Official Assassin Job Quest. Now eAthena contains all 2-1 Job Quests! [kobra_k88] + * Fixed scripts: 32hats, warper2, platinum_skills, added/changed some mapflags and other misc script changes [Lupus] + * Added @disguise command (enter a mob_id or name, and you will appear to others as that mob/npc!) + * Fixed code for mob disguises, should not crash clients now. + * Improved online management code when we receive char_id. [Yor] + * Added code for mob disguises. Can not set yet. Atcommand needs created. [Valaris] + * Improved mmo_char_send006b function [Yor] + * Corrected: when save file of character can not be created, try backup file if flag is set to create it. [Yor] + * Added an option to create backup of characters file [Yor] + The backup_txt file was created because char deletion bug existed. + Now this bug is corrected and no character disappear. + But, create a file with a lot of characters can use CPU usage and decrease hard disk speed. + So, I create an option with default value: no create backup. + * Improved online management code and some others little codes (char.c). [Yor] + * Respawn points of prisoners set to the jail rooms. Jail.txt mapflag addeed. So imprisoned players can't escape. [Lupus] + +08/06 + * Improved/Optimized some little code (char.c/login.c). [Yor] + * Improved 0x2afa and 0x2afb (map transmissions between char and map). [Yor] + * Optimized global message nick spoof fix. [Valaris] + * Fixed crash in attacking guardians in a null guild. [Valaris] + * Capped earned exp at 1000000000. [Valaris] + * Set area size back to 20 for now, some things in path.c need to be fixed. [Valaris] + * Fixed crash in guardian search. [Valaris] + +08/05 + * Added @jail <char_name> GM command [Yor] + * Added explanation of @idsearch and @mapinfo in help.txt [Yor] + * Improved @idsearch GM command [Yor] + * Improved a little 3 loops in ladmin.c [Yor] + * Improve some codes in char.c: [Yor] + - less tests in online creation + - mmo_char_send006b: remove duplicated memset, create a char_dat structure pointer + - server_fd[] not seted/modified/checked correctly + * Set max_walk_path back to 48. [Valaris] + * Added translated Gonryun town. Fixed map names of Louyang shops. Added missing Blacksmith, + added extra Rapairmen into refine.txt. In Inns added 4 different prices based on Base Lvl.[kobra_k88] + +---------eAthena 1.0.0 RC3 TXT--------- +08/04 + * Set battle_athena.conf to more accurate settings. [Valaris] + * Decreased default damage delay (stun after being hit) by 75%. [Valaris] + * Max walk path is now correct (17). [Valaris] + * Area size is now correct (14). [Valaris] + * Improved some very little codes in char.c. [Yor] + * Remove displayings of logs on console of map-server [Yor] + use it for DEBUG, not by default, that decrease a lot the performance + * Added (commented) printf in freeze function for debug if necessary [Yor] + * Fixed melee ctrl-attack targetting. [Valaris] + * Reset attack target when equipping arrows. [Valaris] + * Improved (log and remove_control_chars functions). [Yor] + * Added 'available free bytes' in displaying when size is expanded. [Yor] + * Fixed incorrect returns in clif_authok. [Valaris] + * Added pointer checks to chrif.c [Valaris] + * Added some pointer checks to the mob ai. [Valaris] + * New (lag-free) fix for NPCs / mobs not showing up when chars walk around [Kalaspuff] + +08/03 + * Made so if exp given is less than 0, 0 is given instead of 1 (plants were giving exp) [Valaris] + * Added console displaying to have complete informations when we expand Wdata session. [Yor] + * IMPORTANT: Add a TODO 31 for Guilds' coders. [Yor] + * Old packet 0x2b16 use packet number 0x2b0e. [Yor] + * remove packets 0x2b0e and 0x2b0f. [Yor] + * on character_name ask packet: add account_id of asker. [Yor] + * Improved script code in novice.txt. [Yor] + * Fixed error in alberta.txt. [Valaris] + * Allow player placement on afm-type maps. [Valaris] + * Added afm loading. [Valaris] + * Removed useless code for mob equipment. [Valaris] + * Initial packet setup for possible playable mobs. [Valaris] + * Added check for save_clothcolor for the dyefix, and added another check to make sure player is dyed before using the fix. [Valaris] + * Improvement: Character asking of map-server to char-server is not more case sensitive. [Yor] + * Added TODO 29 & 30. [Yor] + +08/02 + * Re-added "player not attached" error reporting. [Valaris] + * Fixed errors in 32 hat quest that was causing player not attached errors. [Valaris] + * Fixed input number function: commented out negative input check by Valaris in script.c (all scripts have been revised and fixed), + made actual bug fix of buildin_input func (wrong variable type conversion (int)->(unsigned int) in clif.c + Due to the fixed bug Merchant Job Quest works fine now [Lupus] + * Fixed number input bugs/possible exploits: mage.txt IceCream.txt event_valentine.txt refine.txt + milk_trader.txt grandpa_pharmacist.txt aldebaran.txt alberta.txt juice_maker.txt [Lupus] + * Finished: offline player can be @ban/@block/@unban/@unblock by their character name. [Yor] + * Added packet between map to char to work on offline players (@ban/@block/@unban/@unblock) [Yor] + * Added check on character name with less than 4 characters [Yor] + * Added @chardelitem GM command [Yor] + * Fixed Kafras (no more buttonless msg bugs, Kafra Pass works fine) [Lupus] + * Changed input exploit, only checks for negative now instead of having an input cap. Fixed up vending exploit. [Valaris] + * Added check for src with High Wizard's soul drain. [Valaris] + * Fixed up npc_suicide and npc_selfdestruction, should fix up crashes caused by them. [Valaris] + * Added more pointer checks in the clif_authok function. [Valaris] + * Added some pointer checks to clif.c, should prevent crashes. [Valaris] + * Added some checks about writing errors of item names in GM commands (check cases). [Yor] + * Fixed Hunter Job Quest 1.6 [Lupus] + * Removed Kalaspuff's fix for mob/npc data not always being recieved. It was causing + excessive amounts of lag. [Valaris] + +08/01 + * Added ability to spawn character pets. [Valaris] + * Added show_mob_hp. [Valaris] + * @item/@item2 GM commands now work correctly with name begining by a number. [Yor] + * @spawn/@monster2 GM commands authorise spawn of guardians. [Yor] + * Fixed so guardians may be spawned outside of castles. [Valaris] + * Fixed/Improved @spawn/@monster2 GM command. [Yor] + @spawn/@monster2 GM command: you can use space in the desired name now (use "") [Yor] + * Added TODO 26 and 27. [Yor] + * Guardians if owned by a guild will display guild name and castle on name request. [Valaris] + * Added checks for null blocks in mapforeachin functions. [Valaris] + * Addition of a GM level 99 account for test of GM commands. [Yor] + * Creation of a default structure for GM levels (sub-gm, gm, admin, etc.). [Yor] + * Improved @time GM command (better code and display more informations). [Yor] + * Added informations about game time in @time command [Yor] + * Added @time command to have server time [Yor] + * Fix night at start if administrator want night and there is no duration for night and day [Yor] + * Fix for NPCs / mobs not showing up when chars walk around [Kalaspuff] + * Begin @unban/@unblock GM command (structure done). [Yor] + * It's now possible to disable Night or Day (set to 0 in battle.conf). [Yor] + * @day/@night: when already the desired cycle, display a message. [Yor] + * Fixed overlapping Ice-Cream Maker NPC, duplicated NPCs, restored duplicated Akaru's MrSmile [Lupus] + +07/31 + * Terminated: night/day cycles. [Yor] + * When a state comes back to normal, player stay in night if it's night. [Yor] + * Started to add management of day/night: [Yor] + creation of 3 parameters in battle.conf + TODO: usage of these parameters (later). + * added line '// $Id: Changelog.txt,v 1.65 2004/09/29 17:31:42 kalaspuff Exp $' in all code source files to avoid overwrite in CVS. [Yor] + * Fixed number/quantity in @item command. [Yor] + * Fixed when night and disconnect/reconnect, it's night. [Yor] + * Removed extension to check a map in char.c (less tests). [Yor] + * Added savepoint coordonates (x,y) if player is set to a new map (char.c). [Yor] + * Added izlude to check major cities. [Yor] + * Began adding developer mobs. [Valaris] + * Added dyes and fixed client crashes with player mobs. [Valaris] + +07/30 + * Added temp fix for "nullpo player not attached" error message (script.c script_rid2sd) [Lupus] + * Added checks for major cities in afm format. [Valaris] + * Added preliminary AFM(Advanced Fusion Map) support. Actual map-reading works, but rest of server needs to recognize them. + Thanks to alexkreuz [Valaris] + * Changed Map and NPC loading display. [Valaris] + * Removed unneeded 'End' and duplicate 'end' script commands. [Valaris] + * Added 'language <language>' command in ladmin (perl) to change language of displaying. [Yor] + * Fixed atcommand_gm_only parameter: [Yor] + 0: you can define level command '0' for normal players (gm level 0) + 1: even you define a level 0 for a command, normal player can not use it. Only GM level 1 or more can use command (if command level is possible for this GM) + Note: This parameter is not like atcommand_for_all (this parameter doesn't exist actually) . + * Fixed Kafras (Cart Service for Super Novice), some warps in Morroc, Aldebaran. [Lupus] + Added official shop into St.Abbey. [Lupus] + Added quests: Lutie Town Hat Quest by TonyMan, 23 new hats custom quest. [Lupus] + Fixed jobchange.txt. [Lupus] + Improved pvp.txt. [Lupus] + Rearranged and updated! (now in 2 variants) mapflags. [Lupus] + Updated map_athena.conf [Lupus] + * Fixed: battle_athena config's atcommand_gm_only; yes was no and vice versa [Kalaspuff] + +07/29 + * Added 'language <language>' command in ladmin.c to change language of displaying. [Yor] + * Re-added guardians don't attack guild members, someone must have removed it by accident. [Valaris] + * Restore correct displaying (LAN/WAN) previous was good :) (sorry... prabably tiredness). [Yor] + * Fixed some errors in novice.txt script. [Yor] + * Kashy's script fixes. [Valaris] + * Displaying of correct information in LAN/WAN test (displaying was reversed) in char.c [Yor] + * Creation of a char_unblocked directory (char directory is blocked) [Yor] + - modification of makefile + * Added a note in TODO 14: encrypted password - problem with client versions [Yor] + +---------eAthena 1.0.0 RC2 TXT--------- +07/28 + * Added metaller to equipped mobs. [Valaris] + * Added a note in TODO 19 [Yor] + * REMOVED last changelog: cvs server: [14:29:29] waiting for cvs's lock in /usr/cvsroot/athena/src/char [Yor] + * Displaying of correct information in LAN/WAN test (displaying was reversed) in char.c [Yor] + * Added "OMG" emotion to weapon/armor breaking. [Valaris] + * Removed space in a pointer in mob.c. [Valaris] + * Restored previous version of lan management in login.c, because: [Yor] + - check test was incorrect (no mask for controled ip). + - in check test, we recalculate every time the subnetwork (loss of time). + - impossible to have a name definition for the sub-network (some network administrator use a name to define the sub-network). + - no more default configuration of sub-network. + - possible errors in the reading function of the lan file. + - no more logs about sub-network. + - BUT, conserved: - new default name for lan file + - color for displaying of LAN/WAN + * Removed some memsets from chrif.c that caused segfaults. [Valaris] + * Added fix for anklesnare and spiderweb. [Valaris] + * Added Kashy's Lan Support code. [Valaris] + * Removed variables and assignment used by prevent_multi_login. [Valaris] + * Removed prevent_multi_login. (many problems reported with it) [Valaris] + * Fixed armor breaking. Was being broken when pc was attacking instead of when being attacked. [Valaris] + * Fixed checkcart, checkfalcon, checkriding (they didn't return any value) in script.c [Lupus] + * Fixed breeder.txt renter.npc, kafras NPC (added correct class check, added correct checkcart, checkfalcon, checkriding) [Lupus] + * Fixed Kafra functions_kafras.txt NPC (added correct checkcart, fixed cmall cart giving bug) [Lupus] + * Fixed Kafra functions_kafras.txt NPC (fixed Kafra Pass exploit) [Kobra_k88] + * Fixed Hunter.txt coords of the Guild entrance warp [Lupus] + +07/27 + * Improved check command in ladmin.c [Yor] + * Added map_id check in map_foreachinarea, to prevent eventual crashes [Kalaspuff] + * Added @enablenpc and @disablenpc in help files [Yor] + * Fixed Thunder Storm range (thanks midas) [Kalaspuff] + * Added Anthell NPC trigger in Morroc.txt warps/town [Lupus] + * Added missing Warps for Job Quests of Swordman, Hunter and Thief in jobquests.txt [Lupus] + * Updated atcommand_conf, missing GM-level for @enablenpc and @disablenpc [Yor] + * Removed possible overflow error in @enablenpc and @disablenpc [Yor] + * Updated atcommand_conf, missing GM-level for haircolor [Kalaspuff] + * Improved best job test in novice.txt [Yor] + * Improved @go command: [Yor] + - give list of cities if no value + - added start point (to welcome newbies) + - give possibilities to use city names (@go geffen): at least 3 characters, and some writing errors are tested + * Replaced every Job Quest Script (excluding Assassin, 2-2 and 2-2-X ). Now all 2-1 Jobs have big quests. [Kobra_k88] + * Replaced all Skill Quests (Added Sand Attack Skill Quest). [Kobra_k88] + * Added Legendary Swords quest. [Kobra_k88] + * Replaced 6 towns with new scripts: Izlude, Prontera, Morocc, Geffen, Alberta, Al De Baran. Splitted all towns quests into files. [Kobra_k88] + * Splitted and optimized Kafras & Guides. [Kobra_k88] + * Implemented Kafra Pass! [Kobra_k88] + * Removed free Breeders (replaced by the correct ones). [Kobra_k88] + * Placed all Sign Posts/Signs into a single file. [Lupus] + * Files arrangement, additional warps/scripts correction. Fixed several scripts (NPC overlapping, bad sprites, etc). [Lupus] + +07/26 + * More accurate pvp point system. It skips ranks, needs more work. [Valaris] + * Fixed crash in executioner card code. [Valaris] + * Fixed mob respawn after death bug. [Valaris] + * Fixed exp problem caused by bounds checking. [Valaris] + * Fixed pvp rank so only one person may be rank 1. [Valaris] + * Any time a person is on a pvp map, the rank will calculate, preventing bad rank packets from being sent. [Valaris] + * Added Job Agencies for Training Ground (novice.txt). [Yor] + * PVP respawn client crash fix. [Valaris] + * Fixed small error in kafra.txt [Syrus22] + * Finished adding special equipped mobs. [Valaris] + * Added 2 living statues behind the NPC 'Monster Master'. [Yor] + * Removed infinite possibilities to have items from helper (novice.txt, new_1-4.gat,60,149). [Yor] + * Added 2 living statues behind the NPC 'Monster Expert'. [Yor] + * Improved a little 1st course of novice training. [Yor] + +07/25 + * Allow mobs to be equipped with pet armor. mob_avail and clone mobs need to be created. [Valaris] + * Removed [AppleGirl]'s armor breaking code. [Valaris] + * Commented out data_dir. [Valaris] + * Finished exp bounds checking. [valaris] + * Added negative value checks to clif_updatestatus. [Valaris] + * Added exp bounds checking for those rediculously high rate servers. [Valaris] + * Added bounds checking to the @zeny and @charzeny commands. [Valaris] + * Improved and corrected some errors 1st course of novice training. [Yor] + * Removed no guild check for Guardians. Guardians SHOULD attack people with no guild. [Syrus22] + * Fixed Alliance check in mob.c [Syrus22] + * Added armor breaking to normal battle (crits will double the chance) [Valaris] + * Fixed mistake in map.c causing compile errors. [Syrus22] + * Fixed backstab bow penalty option. [Syrus22] + * Fixed Alliance check in battle.c for emp/guardian damage. [Syrus22] + +07/24 + * Added backstab bow penalty option [Akaru] + * Fixed OnGuardianDied events on prontera castles 3 to 5. [Valaris] + * Another vending fix. [Valaris] + * Fixed bugs in npc headers caused by someone using spaces and not tabs. (kafra.txt, guide.txt, yuno.txt) [Valaris] + * Fixed a vending bug. [Valaris] + +---------eAthena 1.0.0 RC1 TXT--------- +07/22 + * Fixed numerous startup errors in aldebaron castles and one geffen castle. [Valaris] + * Full Guild Wars Script Complete!!! [Akaru] + * Fixed rice ball item. [Valaris] + * Fixed problems with options and peco riding. [Valaris] + * Updated item_db with fix for Sleipnir and more translations. [Akaru] + * Translated item_violetbox, mob_poring, mob_branch, mob_boss. Fixed wizard.txt (wrong item id), headgeatquest.txt(added Zeny check) [Lupus] + +07/21 + * Updated refine.txt. Added optional features and optimized the file. [Syrus22] + * Evened out the # of columns in mob_db, filled in some blanks. [Valaris] + * Undid compilation errors caused by Akaru's removal of nullpo.o from map-server compile [Valaris] + +07/20 + * Removed Ghostring from gef_fild13.gat [rg] + +07/19 + * Fixed Graffiti [Valaris] + * Zeny Bug In Vending Fixed [Darkchild] + +07/18 + * Added a check about level of at_command when reading the file [Yor] + * Updated /help and conf/help.txt [Yor] + * Added @nuke command in conf/help.txt [Yor] + * More WoE Castles Done [Hikaru] + * Changed: inter.txt->inter.log in log directory [Yor] + * Added some char_log when character can not be created (invalid value, invalid name, etc...) [Yor] + * Changed: char.txt->char.log in log directory [Yor] + * Update features.html/changlog.html [Yor] + * Fixed compilationissue on FreeBSD [Kalaspuff] + +07/16 + * Remove limit for kami/kamib ladmin command. [Yor] + * Fixed crash in weddingtxt.txt where it was checking an invalid equip index. [Valaris] + * Added some TODO for next version. [Yor] + +07/15 + * Changed: login.txt->login.log in log directory [Yor] + * added system to choice authorised ip for remote administration [Yor] + * Added listBan/listOk commands in ladmin's. [Yor] + * Speed up a little search_mapserver function. [Yor] + * Init map strings of each servers when map-server send informations. [Yor] + * Added some TODO for next version. [Yor] + +07/14 + * Fixed another lockup with multi_level_up [Valaris] + * Added kami(yellow)/kamib(blue) commands in ladmin (terminated). [Yor] + * Fixed name of ladmin_athena.conf. [Yor] + * Fixed mvp item reading. [Valaris/Syrus22] + * Added new move packet in map (ver.13jully04) [Yor] + * Added new auth packet in map (ver.13jully04) [Yor] + * Added kami/kamib commands in ladmin. not terminated: to do: login->char [Yor] + * Updated Some Npcs [Darkchild] + * Added Gefenia Warps [Darkchild] + * Added Berzebub Quest [Darkchild] + * Removed random alchemist marine sphere code, until some other system is worked out. [Valaris] + * Fixed infinite loop caused by turning multi level off. [Valaris] + * Added display id the char-server is freezed. [Yor] + * Added debug printf about the max_connect_user in char-server. [Yor] + * Added banadd command in ladmin.c [Yor] + * Removed duplicate check in chrif.c. [Yor] + +07/13 + * Fixed GM_level code in map-server. [Yor] + problem is in the hash system of the db -> use simple db until we found solution. + note: for this db, it's not necessary to use a db system (little db, 2 values: key, value) + * Improved GM_level code in map-srv, but not solve the problem. [Yor] + * Fixed error of 'return' without value in chrif_parse (chrif.c). [Yor] + * Added character name, account id and gm level on console in map-server when auth is accepted. [Yor] + * Added gm_level of the account on console in char-server when auth is accepted. [Yor] + * Added gm_level of the account on console in login-server when auth is accepted. [Yor] + * Added timeadd command in ladmin.c [Yor] + * Added help for each command (help <command>) in ladmin (perl and c). [Yor] + * Fixed global message (normal speak) name spoof exploit. [Valaris] + * Added timeset command in ladmin.c [Yor] + * Added banset command in ladmin.c [Yor] + * Added vending and trading dupe fixes, thanks to Kinko and Kazzy [AppleGirl] + * Added multi_level_up command to battle_athena and commandline. [Valaris] + Turning it off will allow a player to only level up once from a monster. + * Added: explanations of ladmin_athena.conf keys in conf_ref.txt. [Yor] + * Fixed: init mmo_map_server structure (to 0) in char-server. [Yor] + * Added Gefenia Maps [Darkchild] + * Added @nuke command do to user request. [Valaris] + * Removed nick spoof fix code for now, it broke chat. [Valaris] + * Added some code for @nuke command. [Valaris] + * Finished adding battle_athena options to command line. Every option in battle_athena.conf + can now be passed directly through the command prompt. [Valaris] + +07/12 + * Added many battle_athena options to command line arguments. [Valaris] + * Allow battle_config_switch to be used globally in map-server. [Valaris] + * Rewrote map-server command-line code. Will now begin implemented nice commands for map-server. [Valaris] + * Added BETA version of ladmin in C. [Yor] + * Added new packet structure of authentification and move - automatic detection. [Yor] + * Added /item command. It is same as /monster. Both commands will search for mobid first, if not found will give item. [Valaris] + * Fixed a displaying error in state command (perl ladmin). [Yor] + * Added /monster command. Syntax is /monster <name/id>. [Valaris] + * Fixed chat spoofing in global messages. [Valaris] + * Prevent nick spoofing in whispers. [Valaris] + * Added check about lenght of packet 0x72 for new client version [Yor] + * Added Pet Equip Items Quest Npc + * Added Pet Taming Items Quest Npc + * Added Slotted Sunglasses Quest Npc + * Added pet_equip_required option for pet skills. [Valaris] + * Changed: create a sub-function for help command in ladmin (perl). [Yor] + +07/11 + * Added Petit pet skill. [Valaris] + * Added some code for Petite Heaven Drive, still does not work, but doesn't crash. [Valaris] + * Fixed dokebi and baby desert wolf pet skills. [Valaris] + * Added Orc Warrior, Hunter Fly, Poison Spore, Baby Desert Wolf, Baphomet Jr, and Dokebi pet skills. [Valaris] + * Added script command petskillattack.[Valaris] + * Fixed search command in ldamin (perl) ignore sensitive case now. [Yor] + * Fixed default to 0 for save_unknown_packets configuration in login. [Yor] + * Fixed the warnings about implicit declarations. [Kalaspuff] + * Added @guildrecall/@partyrecall commands. [Yor] + * Added Isis pet skill. [Valaris] + * Added petmag script command for magnificat. [Valaris] + +07/10 + * Added Banker NPC because alot of users wanted one. [Syrus22] + +07/09 + * Added Sohee pet skill. [Valaris] + * Added petheal command. [Valaris] + * Added Smokie pet skill. [Valaris] + * Added bonus bPerfectHide for Smokie pet skill. [Valaris] + * Added Spore pet skill. [Valaris] + * Created petrecovery script command. [Valaris] + * Added Poring, Drops, Poporing, and Yoyo loot skills. [Valaris] + * Added petloot script command for pet looting. [Valaris] + * Removed pet_loot config settings. [Valaris] + * If pk_mode is on, a message will show up in map-server. [Valaris] + * Fixed pk_mode extra experience and drops so will occur if monsters is 20 levels or higher than player. [Valaris] + * Fixed @killmonster crash caused by implementation of pk_mode. [Valaris] + * Finished setting up pk_mode, should be 100% complete now. [Valaris] + * Added nopvp.txt for pk_mode. [Valaris] + * Prevent novice engagement in pk_mode. [Valaris] + * Fixed up and changed the exp penalty system. [Valaris] + * Pk_mode will now give double exp loss if killed by player. [Valaris] + * Updated conf/help with new commands [Yor] + * Removed more pvp timer stuff from pk_mode [Valaris] + * Increase drop rates +25% if over level 20 on pk_mode. [Valaris] + * Changed +25% exp increase on pk_mode to 15%. [Valaris] + +07/08 + * Disabled pvp rank and timer if pk_mode is on. [Valaris] + * All maps made pvp if pk_mode is on. [Valaris] + * pk_mode additional 25% exp given over level 20 [Valaris] + * Disable @pvpon and @pvpoff commands if pk_mode is on. [Valaris] + * Added pk_mode option in battle_athena.conf (not yet implemented) [Valaris] + * Reworked prevent_multi_login, should work perfectly now. [Valaris] + * Removed need for eof=2 for prevent multilogin, will now just delete the blocks containing both sessions. [Valaris] + * Added map-servers anti-freezed connection in char-server. [Yor] + * Added char-servers anti-freezed connection in login-server. [Yor] + * Fixed spy commands so that inputting the same id/name turns off the command. [Syrus22] + * Created @partyspy command. [Syrus22] + * Renamed search_guildname function to conform with normal naming standards in guild.c. [Syrus22] + * Created @whomap/@whomap2/@whomap3 commands to show online players on a specifical map. [Yor] + * Updated and Shrunk the Kafra Script. [Syrus22] + * Create @reloadgmdb gm command. [Yor] + +07/07 + (Dated On Aegis Website) + *--Released 1.0.0 RC3--* + * Fixed crashed with prevent_multi_login. [Valaris] + * Allow infinited local logins if prevent multi_login is on. [Valaris] + * If prevent_multi_login is on, it will disconnect both clients on the same ip. [Valaris] + * Prevent_multi_login will now list the character names of both accounts when logged out, and give a message. [Valaris] + * added updated const.txt and pet_db.txt [Valaris] + * GM accounts/level updating without restarting completed (by reloadgm ladmin command). [Yor] + * Removed gm_account_filename definition from map.conf. [Yor] + * Updated GM level by reloadGM ladmin command. [Yor] + * Added a packet between char and map to send GM accounts and their level. [Yor] + * Added a GM minimum level option to display 'GM' in online files. [Yor] + * Added a warning when a GM account is defined twice in the file. [Yor] + * Check for castle before guardian searches for emblem. [Valaris] + * Prevent stealing from treasure boxes. [Valaris] + * Enable mounted classes to use pedestrian counterpart's items. [Valaris] + * Change so petskillbonus will only update stats(client-side) if need be to prevent errors. [Valaris] + * Fixed crash with putting pets with skills back into egg. [Valaris] + * Added Steel ChonChon, Rocker, and Deviruchi pet skills. [Valaris] + * Added bAllStats(SP_ALL_STATS), bAgiVit(SP_AGI_VIT), bAgiDexStr(SP_AGI_DEX_STR) bonuses for pet skills. [Valaris] + * Added ChonChon, Lunatic, Picky, and Savage Babe pet skills to pet_db.txt. [Valaris] + * Added petskillbonus command for pet skills. Added pet_skill_bonus functions in pet.c. Made pointers for pet skills. [Valaris] + * Added 'GM' display option for online files [Yor] + * Improved GM accounts file reading in login-server [Yor] + +07/06 + * Changed heal dog in prontera to a poring. [Valaris] + * NPCs with mob sprites can now be used in scripts. [Valaris] + * Removed sd->brokencounter. Made getbrokenid more scripter friendly. Updated refine.txt getbrokenid commands. [Valaris] + * Mounted classes will now use equipment of their pedestrian counterpart. [Valaris] + * Improved management of GM account structure in char-server. [Yor] + * Added packet betwen login to char to send GM accounts value. [Yor] + * Added reloadGM command in ladmin to reload GM accounts file without stop the login-server. [Yor] + * Added listGM/lsGM command in ladmin to list only GM. [Yor] + * Correct an error in loop of char_divorce function (incorrect variable). [Yor] + * Added some comments. [Yor] + * Added a check on start_point.map when configuration is readed. [Yor] + * Modified final message of login log at end of login-server. [Yor] + +07/05 + * Flamelauncher,frostweapon,lightningloader,seismicweapon, and enchant poison now check to make sure target's weapon is not already enchanted. [Valaris] + * If sage breaks another person's weapon due to enchant failure, it will tell caster. [Valaris] + * Modified venom splasher to hopefully stop crashes caused by spamming. [Valaris] + * SA_FLAMELAUNCHER,SA_FROSTWEAPON,SA_LIGHTNINGLOADER,SA_SEISMICWEAPON now check to make sure target is holding a weapon. [Valaris] + * SA_FLAMELAUNCHER,SA_FROSTWEAPON,SA_LIGHTNINGLOADER,SA_SEISMICWEAPON now will break target's weapon on failure (if one + is being held and caster met requirements) [Valaris] + * Prevent unidentified and broken items from being sold. [Valaris] + * Added buildin_repair for equipment repair npc. [Valaris] + * Added repair npc to forgery in prontera. (refine.txt) [Valaris] + * Added sd->brokencounter and buildin_getbrokenid for item repair npc. [Valaris] + * Corrected some item names [rg] + * Fixed so @repairall success message and effect will only display once. [Valaris] + * Added "No items needed to be repaired" message and added forge success effect to @repairall. [Valaris] + * Added @repairall command. [Valaris] + * Added equipment_breaking option, changed weapon_break_chance to weapon_break_rate (changed to %) [Valaris] + * Crit's will now double weapon breaking chance if turned on. [Valaris] + * Added missing commands in atcommand_athena.conf. [Yor] + * Added @warpto command (same @jumpto). [Yor] + * Added increase in chance to break weapon if using powerthrust. [Valaris] + * Added weapon_break_chance to battle_athena.conf. [Valaris] + * Modified multiple login from one ip prevention(and remove gm bypass). [Valaris] + * Broken weapons will now have their description names in red. [Valaris] + * Speed up characters saving [Yor] + * Improved logs when a character isn't readed [Yor] + +07/04 + * Completed Prontera guild castles [Akaru] + * break_weapon_chance now works, but broken weapon will not be displayed any differently than a normal weapon. + Also does not yet affect a dual dagger assassin's 2nd weapon. No way to repair yet, and no skills/stats affect breaking chances. [Valaris] + * Update int_storage to include broken column on all items (updates from old version) [Valaris] + * Added @guildspy command. [Syrus22] + * Added weapon_break_chance. (Not implemented yet) [Valaris] + * Added break column for items in athena.txt (will upgrade older versions automatically) [Valaris] + * Changed default required GM levels for GM commands (effective if corresponding directive(s) in /conf/atcommand_athena.conf is/are missing) to 1 [rg] + * Added packet_table_en.txt in doc folder. Has some translations of the client_packet.txt. [Valaris] + * Prevent @monster and @spawn of guardians/emperium. [Valaris] + * Changed killmonster so it will not destroy guardians. [Valaris] + * Added prevent_multi_login in battle_athena.conf to disable multiple logins from same ip (ignores gms, and will + display ip of offending ip if turned on) [Valaris] + * Added checks on player trading to prevent possible exploits. [Valaris] + * Make sure cart is on before vending. [Valaris] + * Cleaned up vending exploit fixes. Now checks to make sure not vending more than max items per skill level. [Valaris] + +07/03 + * Prevent use of potion pitcher on oneself, fixed potion pitcher so can be used on other targets. [Valaris] + * Fixed the damage code for Falcon Assault, so its not totally useless. [?] + * Update peco riders for people upgrading athena, fixed bug in unmounting pecos. [Valaris] + * Removed option 32 from @option, added @mountpeco command. [?] + * Removing peco will revert to proper job level, fixed so jobchanging from peco status to peco user without peco status, + will update job to peco status. (ie going from Mounted crusader to Unmounted knight, will jobchange to mounted knight) [Valaris] + * Fixed so Peco mounting will not reset job level. Set to remove peco status if jobchanging to a class that does not use them. [Valaris] + * Peco mounting will now jobchange accordingly. [Valaris] + * Added checkfalcon and checkriding script commands. [Valaris] + * Added checkcart script command (since was already being used in scripts) [Valaris] + * Re-added unix fd_setsize definitions, makefile will now pass -DFD_SETSIZE=4096 only for windows compiles. + Tested new implementation of using -DFD_SETSIZE=4096 in makefile on windows box, and got past 64 connections even. [Valaris] + * Improved messages between servers about connections. [Yor] + * Improved pc_resetlvl, fixed the bug about options being left. [?] + +07/02 + * Added optional match_test in @who/who2/who3 commands (no sensitive case) [Yor] + * If there is no map-server, send right message to client (char-server) [Yor] + * Improved counter of users (char-server) [Yor] + * Improved save of characters (char-server) [Yor] + * Improved sorting of account before save (login-server) [Yor] + * Improved map search at selection of a character (char-server) [Yor] + +07/01 + * Removed FD_SETSIZE definitions from socket.h, added -DFD_SETSIZE=4096 argument to makefiles. [Valaris] + * Changed exploit fix in chrif.c [Valaris] + * Added assassin mask view_id in item_db [Valaris] + * Added a parameter to authorise minimum GM level at connection (login) [Yor] + * Fixed crash caused by making raw connection to map-server. [Valaris] + * Corrected a possible error at check of online players [Yor] + * Improved characters names control/check [Yor] + * Improved save/load of REG2 strings and values (login) [Yor] + * When there is no char-server, login-server sends proper message instead of a void list of servers [Yor] + +06/30 + * Fixed a crash when used @charmodel,@charstpoint,@charskpoint and + @charzeny with the wrong name [Kalaspuff] + * Added possibilities for switchs in battle.conf (add some foreign language) [Yor] + * Protected char-server again disconnection of login-server [Yor] + * Added possible protection against packet exploits in chrif.c. [Valaris] + * Login-server: Added an option for the format of the date (log, etc...) [Yor] + Improved some little code. + Added log for char-server packets. + * Correction of prtg_cas03.txt that crash server AT START! [Yor] + * Added functions of mapflag noskill [Kalaspuff] + +06/29 + (Dated On Aegis Website) + *--Released 1.0.0 RC2--* + +06/28 + * Added monsters_ignore_gm option. Monster won't attack GMs if turned on unless attacked and within 1 cell. [Valaris] + * Added drops_by_luk option in battle_athena.conf. Anything higher than 0 will turn this option on, and act as a mutiplier. + Example : Setting of 10 with 50 luk would add 5 to the drop rate. So say a card has a drop rate of 2, it would become 7. [Valaris] + * Fixed range and removed skill failed message from Venom Splasher, also moved some of it's code around. [Valaris] + * When a player arrive on map-server, time limit of its account is displayed if not unlimited [Yor] + * Fixed problem where warp portals broke in npc.c [Valaris] + * Updated atcommand_heal so it works like it should [Kalaspuff] + +06/27 + * Changed Venom Splasher so it will increase damage based on level of Poison React (had it set so the player had to be + using it, but it turns out it doesn't need to be) [Valaris] + * Venom Splasher now works except for the counter part. Damage is instantly dealt if skill is successful. [Valaris] + * Improved @item command to make correctly pet eggs [Yor] + * Updated Chase Walk so you can't attack while you have it casted [?] + * Removed un-needed code for graffiti from clif.c [Valaris] + * Added @ban command (to ban a player for a limited time) [Yor] + * Added @charblock command (you have been blocked by GM team) [Yor] + * Added the mapflag nowarpto [Kalaspuff] + * Updated the function of nowarp [Kalaspuff] + +06/26 + * When a player is banned (or with a state != 0), he is disconnected [Yor] + * When sex is changed, skills of other sex are reseted (and skill points increased of the same number) [Yor] + * To avoid problem with change sex and equipement, changed sex character is unequipped of all equipment [Yor] + +06/25 + * Added @charchangesex GM command [Yor] + * Changed: Changesex is now done after that the login-server has confirmed the change [Yor]. + becuase sex is saved in account file. + +06/24 + * Added new classes in change sex script command (buildin_changesex). [Yor] + * Translated pet_db.txt again [Valaris] + * Initial implementation of Venom Splasher. Runs checks on target poison status and whether or not hp is less then 2/3. + Will display skill failed if checks do not pass. Shows effect when successful. [Valaris] + * Added administration system to change final date of a banishment. [Yor] + * Added information about banishment in admin packets about an account. [Yor] + * Updated Chase Walk so it cancels when recasted to fix it. [?] + * Initial implementation for Chase Walk skill for Stalker Class [?] + * When an account is banned, message_error_7 is not more modified [Yor]. + +06/23 + (Dated On Aegis Website) + *--Released 1.0.0 RC1--* + * Added bRandomAttackIncrease for Executioner card. Chance stacks, attack does not. [Valaris] + * Fixed magic_damage_return so it will actually work (for Maya card). [Valaris] + * Add a ban timestamp in the structure of the accounts. Management not yet make [Yor] + +06/22 + * Don't send a message when it's void (packet 0x8e) - client doesn't display it [Yor]. + * Add a refresh time parameter for the html online file (refresh time in the explorer) [Yor]. + * Create a job_name function in atcommand to have the name of the job (suppress repeated code) [Yor]. + * Added New City: Jawaii + * Fix free memory of online structure at end of char-server [Yor]. + * Remove possible duplicated online players (multiple map-servers) [Yor]. + * Add examples in state command (ladmin) [Yor]. + * Use a function to display warnings in login-server to avoid duplicated messages with import option [Yor]. + * Iinitial implementation for magicdamagereturn for Maya Card [?] + * skill_out_range_consume - If it is set 'no' the skill will still be cast (like real servers). + If it is set to yes, skill will fail and sp and items required will be lost. [Valaris] + +06/21 + * Updated Sacrifice skill code to be more flexible for user usage [?] + * Changed SC_ATTACKPOTION and SC_MATTACKPOTION to SC_ATKPOT and SC_MATKPOT, also added it in item_db.txt [?] + * Guild Territory will now display the # of castles owned or "None Taken". [Valaris] + * Changed SC_ATTACKPOTION and SC_MATTACKPOTION so the increase can be specified in itemdb.txt. + Example : sc_start SC_AtkPot,18,30; (+30 atk for 30 seconds) [Valaris] + * Added SC_ATTACKPOTION and SC_MATTACKPOTION for +30 atk for specified time period (need to get the correct id's yet, + right now giving wrong icons and wrong message). Added entries in const.txt, need more info to complete these. [Valaris] + * Fixed so players will always spawn with guild emblem if one is needed. [Valaris] + * Reduce number of tests in atcommand_character_stats_all (@charstatsall). [Yor] + * Fix memory management for online players list. [Yor] + * Party HP now updates instantly on change. [Valaris] + * Fixed crash when non-guild members are in the area of guardians in attack mode.(Will ignore them) [Valaris] + * A higher level GM is not displayed by who/who2/who3 if he uses HIDEGM. [Yor] + * When a GM with HIDEGM relogs, he is always HIDEGM (only GM). [Yor] + * Improve presentation of online.txt file. [Yor] + * In /npc/quests/magicalhatquest, corrected checking for and deletion of Mage Hat instead of Wizard Hat. [rg] + +06/20 + * Fixed problem with guardian emblems disappearing [Valaris] + * If a GM use GM HIDE, he is not counted in the number of players [Yor] + * Setup prtg_cas01 to load guardians on server startup and to spawn them when purchased. Also switched from GuardianDied + to OnGuardianDied (other way wasn't working). Changed so guardians won't be killed on agitend. [Valaris] + * Guardians cannot attack and cannot be hurt during non woe time. [Valaris] + * Switched checking of castle.txt format so it won't wipe guardian hp everytime it loads. [Valaris] + * Made it so if guardians were installed in old db, that it will set guardians to full hp based on defense and class. [Valaris] + * Autosave will save guardian HP data. [Valaris] + * Castle.txt visibleG flags will be set when guardians are killed. [Valaris] + * Moved emperium defense upgrade to mob.c. [Valaris] + * Removed guardian hp saving from agitend. [Valaris] + * Added option to choose which columns are displayed in the online files [Yor] + * Added option to choose how to sort online players in the online files [Yor] + * Correction of a new error on guild (from [Valaris]). Old castle.txt files couldn't be readed. [Yor] + * Kafra Points And Rewards fixed [Darkchild] + * Kafra file made a lot smaller with DoEvents [Darkchild] + * Fixed lotsa bugs in Kafra's [Darkchild] + * Initial implementation for paladin's skill sacrifice [?] + * Loading/Saving of guardian hp (loads on agitstart, saves on agitend) [Valaris] + * Added so guardian hp will change accordingly. Moved the guardian defense increase to mob.c [Valaris] + * Implemented guardian and guardianinfo script commands [Valaris] + +06/19 + * Added Ghp0-7 into castle database [Valaris] + * Added configuration parameters to choose online files filename [Yor] + * Added online files (txt and html) [Yor] + * Added choose of authorised letters/symbols for characters names [Yor] + * Added 3 new Dragon Boat Festival monsters with temporary stats to mob_db [Akaru] + * Translated more of item_db [Akaru] + * Added correct effects for Dragon Boat Festival items [Akaru] + * Solve problem about the change of MAX_GUILDPOSITION by [Valaris] when we load an old guild.txt file. [Yor] + * Improve allow/deny configuration. Write warnings if necessary. [Yor] + +06/18 + * Add heal_payment.txt as an alternative to heal.txt. [Yor] + * Sex change (char.C): Correct error in jobchange. Disconnect player if connected. [Yor] + * Sex change/account deletion: Change authentification to avoid that player comes back on char-server within the 5 secondes before disconnection. [Yor] + * Save configuration of login-server in log file at start. [Yor] + +06/17 + * Added fritz's vending exploit fixes. [Valaris] + * Increased max guild castle size to accomodate for novice guild castles. [Valaris] + * Fixed investment in prontera castle 1. [Valaris] + * Implemented of showing guardian hp on guardian investment in prontera castle 1 (factors in defense investment) [Valaris] + * Added strmobinfo script command. Syntax is strmobinfo(x,y). 'y' is the mob's id. x will show different values. + 1=english name, 2=jap name, 3=level, 4=max_hp, 5=max_sp,6=base_exp,7=job_exp. [Valaris] + * Disable % and / for 1st symbol of commands (party chat symbol and standard ragnarok GM commands) [Yor] + +06/16 + * Added fully functional economy for Prontera Castle 2 Guild Wars script [Akaru] + * added fix for cross-class ensemble skills. [?] + * Increased max guild member limit to accomodate for +2 member increase per extension level + and increased max position to 56 to accomodate for all members. [Valaris] + * More of fov's fixes for atcommand.c, chrif.c, and clif.c. [Valaris] + * added fixes for class checking skills like bard & dancer skills and a priest skill. [?] + * updated skill_cast_db and skill_require_db.txt [?] + * changed int_guild.c added +4 for extension skill to match kRO [?] + * remote administration: add a command/packet to change sex of an account [Yor] + * Log detailled reason of refused connection in remote administration [Yor] + * Create a mmo_auth_tostr for accounts [Yor] + * Add a message when char-server is terminated [Yor] + * Save deleted accounts (administration deletion) in log file [Yor] + * Add a message when login-server is terminated [Yor] + +06/15 + * Added in fov's fixes for socket.c, atcommand.c, npc.c and skill.c [Valaris] + * Added a char_log function. Save unreadable characters in log instead of a specific file [Yor] + * Save invalid account lines in log file. Account will be never lost [Yor] + * Sort characters of same player with the slot number in the characters file [Yor] + +06/14 + * Fix errors in prtg_cas01, prtg_cas05 and MrSmile scripts [Akaru] + * Added fully functional economy for Prontera Castle 1 Guild Wars script [Akaru] + +06/13 + * If player is on map-server when the account is deleted, player is now disconnected [Yor] + * Correction of char deletion bug when account is deleted [Yor] + * Added Prontera Castle 5 guild wars script [Akaru] + * Added Prontera Castle 4 guild wars script [Akaru] + * Add checks about duplicated character ids and names [Yor] + * Don't save a reg of a character if its string is void [Yor] + * Read a character even if a reg string is void (don't suppress the char for that) [Yor] + * Save characters in account_id order [Yor] + * Save not readed characters in a file (char file name + "not_readed.txt") [Yor] + * Display line number when a character can not be readed [Yor] + * Initialise char_num! Display number of readed characters [Yor] + * Do right initialization of char_dat [Yor] + * Add red color for ERROR displays in char.c [Yor] + +06/12 + * Change 0 to '\0' for char in login.c. Add red color for ERROR displays [Yor] + * Optimised Mr. Smile NPC script [Akaru] + * Add a configuration in atcommand.conf to set the 1st character of ALL commands (Now, you can choose @, #, !..., any char that is not control character) [Yor] + * Optimised Prontera Castle 1 and 2 guild wars scripts [Akaru] + * Added Prontera Castle 3 guild wars script [Akaru] + * Changed Graffiti placement, will allow placement of one painting at a time (old one will be replaced). [Valaris] + * Graffiti displays to other plays and remains on map for set time period. [Valaris] + +06/11 + * Initial implementation of graffiti (does not change directions yet (vertical/upsidedown) and doesn't dissappear [Valaris] + * Add config_switch (0/1, yes/no, ...) for char-server configuration [Yor] + * Add a display when a player does a connection [Yor] + * Add a display when a remote administration does a connection [Yor] + +06/10 + * Improve compiling instructions for cygwin (socket.h) (depends of the cygwin version). [Yor] with help of [Lostsoul] + * Add parameters in login.conf to display or not parse information [Yor] + * Remove some repeated codes in ladmin / add example when error of command [Yor] + * Remove delete_session error in login.c (sorry) [Yor] + * Display correct message for char-server disconnection [Yor] + * Finish translation of ladmin [Yor] + * Fixed Various Npc Bugs [Darkchild] + * Added green colour for READY displays in char-server, login-server and map-server [Akaru] + +06/09 + * Add some comments in ladmin [Yor] + * Remove displaying of packet 0x2714 from login-server [Yor] + * Correct length of password send to login-server in char-server [Yor] + * Char-server/login-server: put default lan to 127.0.0.1 instead of any hasardous value [Yor] + * Change displaying of the title [Yor] + * Optimised twin towers script to use duplicate [Akaru] + * Edited to a more user friendly error message for invalid server communication password [Akaru] + * Updated the item_db for more item name consistencies [Akaru] + * Reverted training grounds back to new_1-?.gat maps. [Valaris] + * Added added more variety to sending packets to guild members (same map, same map w/out self, ect) [Valaris] + +06/08 + * Put a HOWTO in lan_support.conf. [Yor] + * Removed extra semicolons in char.c, map.c, and pc.c. [Valaris] + * Added missing } to clif_storageitemlist in clif.c and found a couple lines ending with 2 semicolons, removed them. [Valaris] + +06/07 + * Translated refine_db.txt. [Valaris] + * Negative vending fix. [Fritz] + * Correction of mktime parameters in ladmin + some translations [Yor]. + * Add some checks on login-server configuration parameters [Yor]. + +06/06 + * Fixed error in skill_tree.txt that would crash some people's servers when changing to Professor. [Valaris] + * Added admin_state directive for enabling and disabling remote administration, instead of testing whether admin_pass == "" [rg] + * Add title to the servers. [Yor] + * Add warnings about default password usage (administration and gm passwords). [Yor] + * Modify adduser.c for the default configuration. [Yor] + * Write the complete admin_packet.txt. [Yor] + * Champion NPC was looking for a priest instead of monk, changed to correct value. [Valaris] + * Small fixes in guide.txt, kafra.txt, and swordsman.txt pointed out by StiNKy. [Valaris] + * Begin splitting monsters.txt [Akaru] + * Removed obselete monster spawn files [Akaru] + * Complete Guild Wars for Prontera Castle 2 script done [Akaru] + * Fixed prevent_logout option. [Valaris] + +06/05 + * Implemented guild castle regen.(Stackable 2x for castle owners) [Valaris] + * Fixed player logout display on map-server console. [Valaris] + * Added atcommand_spawn_quantity_limit directive to /conf/battle_athena.conf [rg] + * Fixed noskill map flag [?] + * Complete information about login configuration in conf_ref.txt [Yor] + * Add possible configuration values on/off or yes/no in login-server [Yor] + * Guardians and emp will now get +2000 hp for every defense investment within a castle [Valaris] + * Changed the login server to reject all remote administration authentication if the admin_pass directive isn't set, and commented-out the admin_pass directive in /conf/login_athena.conf [rg] + * Changed /src/common/grfio.c so it doesn't try to read GRF files with no respective directive in /conf/grf-files.txt [rg] + * Changed so no one can spawn inside castles. [Valaris] + * Updated item_db with more consistant names, fixed some unknown_items [Akaru] + * Complete Guild Wars for Prontera Castle 1 script done [Akaru] + * Disabled guild breaking, alliance breaking, and alliance making during WoE. [Valaris] + * Will not default to prontera.gat if map-server is not connected. [Valaris] + * Warp players who are not in guild out of castles when WoE starts. [Valaris] + +06/04 + * Allies now do no damage to guardians or emperium. [Valaris] + * Prevent allies from being attacked by guardians. [Valaris] + * Optimized guardian emblem code. [Valaris] + * Fixed map-server crashing with spawning guardians in untaken castles. [Valaris] + * Guardian emblems will change if castle is taken, but client needs to refresh map. [Valaris] + * Fixed problem where guild emblem would vanish from guild info screen. [Valaris] + +06/03 + * Improve e-mails checks and LAN/WAN checks on char-server. [Yor]. + * Add some explanations in front of accounts file. [Yor] + * Set a non LAN configuration for basic configuration in lan_support.conf. Explain parameters. [Yor] + * add missing parameters of char_athena.conf, and explain them. [Yor] + * Restore default admin pass and gm pass of login_athena.conf, and add missing parameters. [Yor] + * fix missing include in char.c. [Yor] + * Translation of checkversion. Add some explanations in front of file. [Yor] + * Translation of getlogincount [Yor] + * Finish translation of new login.c [Yor] + * Implemented Guardian Emblems [Valaris] + +06/02 + * Added maximum_level option in battle_athena.conf [Valaris] + * Added maximum level cap to all the level up commands. [Valaris] + * Added "Deal has been cancelled" message to Fritz's input exploit fix. [Valaris] + +06/01 + * Fixed training ground npcs and warps, removed depreceated maps, using new ones. [Valaris] + +05/30 + * Small map-server crashing fix with Leo and Guide npcs in training ground [Valaris] + +05/29 + * Removed item_value_db.txt and all references to it [Valaris] + * Removed unused class_equip_db.txt [Valaris] + * Fixed stat and level reset bug where needed status points wouldnt reset unless relogged [Valaris] + * @monster will summon monster without an amount specified [Valaris] + +05/28 + * Added heal and usable item rate modifier [Valaris] + * Added pet equipment to equipment rate modifier [Valaris] + * Added option to turn alchemist summon experience and drops on and off [Valaris] + * Alchemist Marine Spheres now randomly explode [Valaris] + * Fixed bug where if certain items lowered max hp (4 mysteltain, and 1 eddga) below 0 + would loop to server max hp value. [Valaris] + +05/26 + * Added New Hats [Darkchild] + * Added New Monsters [Darkchild] +05/23 + * Added @charstatsall, views all characters (easy for money bug scaning etc! [Fritz] + * Max to input npc command, 0 is lowest, 99999999 is max, this to prevent money bugs! [Fritz] + +05/21 + (Dated On Aegis Website) + *--Released 0.5.2--* + +05/20 + (Dated On Aegis Website) + *--Released 0.5.1--* + * got dye working again [Darkchild/fritz] + +05/19 + (Dated On Aegis Website) + *--Released 0.5.0--* + +05/09 + * added Prontera Guild Castle 3 test guild wars test script [Akaru] + * modified Prontera Guild Castle 1 and 2 guild wars test script [Akaru] + * fixed several valkyrie jobchangers [Akaru] +05/08 + (Dated On Aegis Website) + *--Released 0.4.2--* + *--Released 0.4.1--* + * added Prontera Guild Castle 2 test guild wars test script [Akaru] + +05/07 (Dated On Aegis Website) + *--Released 0.4.0--* + +05/06 + * added Prontera Guild Castle 1 test guild wars test script [Akaru] +05/04 + * removed parses and added ENGLISH! [?] + +04/29 (Dated On Aegis Website) + *--Released 953 Delta--* + +04/28 (Dated On Aegis Website) (Whose Ideas Were These?) + *--Released 953 Gamma--* +04/27 + *--Released 951 Beta--* + * fixed @jobchange crash [credits to Mugendai, commited by Akaru] + +04/25 (Dated On Aegis Website) + *--Released 947 Alpha--* +04/23 + * added more Professor Skills and added checks [?] +04/10 + * added more upper skills from moonsoul's works [?] +02/12-04/10 + * CVS Down - Not Many changes could be made...* + +02/22 (Dated On Aegis Website) + *--Released 817--* + +02/12 + * added more mob skill conditions (friendstatuseq, mysyatuseq, friendhpltmaxrate) [RoVeRT] +02/06 + * dumped @skillall for @allskill [?] +02/05 + * fixed provoke so it doesnt work on undead [RoVeRT] + * added TyrNemesis^ card removal code and min/max settings [RoVeRT] +02/04 + * added start_zeny and party_level_range to char_athena.conf [RoVeRT] +02/03 + * Improved the prontera.gat map fallback. [Sara-chan] + * Improved the way guild emblems act when logging in. [Sara-chan] + * Undead-class armor, and Undead monster themselves will never be frozen [RoVeRT] + * fixed negative values for NPC to always be 0 +01/26 + * mob_warpslave correction [RoVeRT] +01/25 + * added poison hp reduction [AppleGirl] +01/20 + * added intimidate [RoVeRT] + * added mvp checks for some skills +01/17 + * added skill check for empelium attack and removed drops from NULL kills [RoVeRT] +01/16 + * added secondary effects for when characters with appropriate elemental armor + are within area of effect of sage spells SA_VOLCANO(atk up), SA_DELUGE(max hp up), + SA_VIOLENTGALE(flee up) [moonsoul] + * removeal of @randmon as it isnt needed [RoVeRT] +01/15 + * fixed auto spell so it works the prober way [RoVeRT] +01/12 + * added mob_warpslave [RoVeRT] + * added mob_warp to check noteleport mapflag +01/07 + * added OnCommand for NPC [RoVeRT] + * added new on death method for NPC spawned mobs [RoVeRT] + * added mobcount [RoVeRT] +01/06 + * fixed icon status for spear quicken [AppleGirl] + * added quoted name support for @monster [RoVeRT] +01/05 + * added indivudal support for card and equip drop rates [RoVeRT] +01/04 + * added TF_PICKSTONE and skill check condition for TF_THROWSTONE [AppleGirl] + * added updated cast_db.txt and fixed SA_VOLCANO, SA_DELUGE, + SA_VIOLENTGALE, and SA_LANDPROTECTOR GRAPHICS [AppleGirl] + * Fixed Effects of a few bard Skills. [AppleGirl] + * added option to stop logout for 10 seconds after taking a hit [RoVeRT] +01/03 + * added inet_ip support to char and map [RoVeRT] + * added checkcart, checkfalcon and checkriding npc commands +01/02 + * added new npc timer support that is independant of a player [RoVeRT] +12/31 + * Added @refineall [Mark] +12/30 + * added support for custom_item-db.txt with battle_athena.conf option [RoVeRT] + * fixed @charzeny bug + * translated help.txt to english anong with a few other files [RoVeRT] +12/29 + * added umbala maps to map_athena.conf [RoVeRT] +12/26 + * added Skill_range based on level, and partially working AutoGuard [Moonsoul] + * added Correction of Whip and Instrument Damage Again [Sara-Chan] +12/25 + * Spear Quicken Correct Graphics,Fixed Magnum Break (Which Fixes All Other + Splash skills), and Fixed Brandish Spear [AppleGirl] + * Updated Command For @SkillAll Added Atcommand_athena.conf [RoVeRT] +12/24 + * Added Side Effects for multiple skills for 2-2 classes [AppleGirl] +12/23 + * Added GM Command Called @Skillall [RoVeRT] + * @skillall to skill-up all your current skills [RoVeRT] + * @hide does hide you from all monsters [RoVeRT] + * fire wall limited to 5 per map [RoVeRT] + * to turn on PVP without @pvpon and to disable flywing search for mapflag [RoVeRT] + * no luck with @morph at this time yet [RoVeRT] + * splash attack added but still kinda buggy [RoVeRT] + * Added in Dancing and Song Playing for Bard and Dancer [AppleGirl] + * Added Skill Arrow Check For Archer Skills [AppleGirl] + * Added Skill Status Recovery [AppleGirl] + * Added Skill Bard and Dancer Skills Last Longer [AppleGirl] + * Added Skill Grimtooth does splash damage [AppleGirl] + * Added Skill Steal Fixed and Snatcher [AppleGirl] + * Added Skill SonicBlow only works with Katars Now [AppleGirl] +12/22 + * Added Skills Shield Boomerang, Shield Charge, and Defender [AppleGirl] diff --git a/doc/LICENCE b/doc/LICENCE new file mode 100644 index 0000000..45645b4 --- /dev/null +++ b/doc/LICENCE @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+ 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ 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 2 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, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Library General
+Public License instead of this License.
diff --git a/doc/LICENCE_JA b/doc/LICENCE_JA new file mode 100644 index 0000000..0888c67 --- /dev/null +++ b/doc/LICENCE_JA @@ -0,0 +1,416 @@ + GNU 一般公衆利用許諾契約書
+ バージョン2、1991年6月
+ 日本語訳、2002年5月20日
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+ 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ この利用許諾契約書を、一字一句そのままに複製し頒布することは許可する。
+ しかし変更は認めない。
+
+ This is an unofficial translation of the GNU General Public License
+ into Japanese. It was not published by the Free Software Foundation,
+ and does not legally state the distribution terms for software that
+ uses the GNU GPL--only the original English text of the GNU GPL does
+ that. However, we hope that this translation will help Japanese
+ speakers understand the GNU GPL better.
+
+ (訳: 以下はGNU General Public Licenseの非公式な日本語訳です。これはフ
+ リーソフトウェア財団(the Free Software Foundataion)によって発表された
+ ものではなく、GNU GPLを適用したソフトウェアの頒布条件を法的に有効な形
+ で述べたものではありません。頒布条件としてはGNU GPLの英語版テキストで
+ 指定されているもののみが有効です。しかしながら、私たちはこの翻訳が、
+ 日本語を使用する人々にとってGNU GPLをより良く理解する助けとなることを
+ 望んでいます。)
+
+ 翻訳は 八田真行<mhatta@gnu.org>が行った。原文は
+ http://www.gnu.org/licenses/gpl.txtである。誤訳の指摘や改善案を歓迎す
+ る。
+ はじめに
+
+ソフトウェア向けライセンスの大半は、あなたがそのソフトウェアを共有した
+り変更したりする自由を奪うように設計されています。対照的に、GNU 一般公
+衆利用許諾契約書は、あなたがフリーソフトウェアを共有したり変更したりす
+る自由を保証する--すなわち、ソフトウェアがそのユーザすべてにとってフリー
+であることを保証することを目的としています。この一般公衆利用許諾契約書
+はフリーソフトウェア財団のソフトウェアのほとんどに適用されており、また
+GNU GPLを適用すると決めたフリーソフトウェア財団以外の作者によるプログ
+ラムにも適用されています(いくつかのフリーソフトウェア財団のソフトウェ
+アには、GNU GPLではなくGNU ライブラリ一般公衆利用許諾契約書が適用され
+ていることもあります)。あなたもまた、ご自分のプログラムにGNU GPLを適用
+することが可能です。
+
+私たちがフリーソフトウェアと言うとき、それは利用の自由について言及して
+いるのであって、価格は問題にしていません。私たちの一般公衆利用許諾契約
+書は、あなたがフリーソフトウェアの複製物を頒布する自由を保証するよう設
+計されています(希望に応じてその種のサービスに手数料を課す自由も保証さ
+れます)。また、あなたがソースコードを受け取るか、あるいは望めばそれを
+入手することが可能であるということ、あなたがソフトウェアを変更し、その
+一部を新たなフリーのプログラムで利用できるということ、そして、以上で述
+べたようなことができるということがあなたに知らされるということも保証さ
+れます。
+
+あなたの権利を守るため、私たちは誰かがあなたの有するこれらの権利を否定
+することや、これらの権利を放棄するよう要求することを禁止するという制限
+を加える必要があります。よって、あなたがソフトウェアの複製物を頒布した
+りそれを変更したりする場合には、これらの制限のためにあなたにある種の責
+任が発生することになります。
+
+例えば、あなたがフリーなプログラムの複製物を頒布する場合、有料か無料に
+関わらず、あなたは自分が有する権利を全て受領者に与えなければなりません。
+また、あなたは彼らもソースコードを受け取るか手に入れることができるよう
+保証しなければなりません。そして、あなたは彼らに対して以下で述べる条件
+を示し、彼らに自らの持つ権利について知らしめるようにしなければなりませ
+ん。
+
+私たちはあなたの権利を二段階の手順を踏んで保護します。(1) まずソフトウェ
+アに対して著作権を主張し、そして (2) あなたに対して、ソフトウェアの複
+製や頒布または改変についての法的な許可を与えるこの契約書を提示します。
+
+また、各作者や私たちを保護するため、私たちはこのフリーソフトウェアには
+何の保証も無いということを誰もが確実に理解するようにし、またソフトウェ
+アが誰か他人によって改変され、それが次々と頒布されていったとしても、そ
+の受領者は彼らが手に入れたソフトウェアがオリジナルのバージョンでは無い
+こと、そして原作者の名声は他人によって持ち込まれた可能性のある問題によっ
+て影響されることがないということを周知させたいと思います。
+
+最後に、ソフトウェア特許がいかなるフリーのプログラムの存在にも不断の脅
+威を投げかけていますが、私たちは、フリーなプログラムの再頒布者が個々に
+特許ライセンスを取得することによって、事実上プログラムを独占的にしてし
+まうという危険を避けたいと思います。こういった事態を予防するため、私た
+ちはいかなる特許も誰もが自由に利用できるようライセンスされるか、全くラ
+イセンスされないかのどちらかでなければならないことを明確にしました。
+
+(訳注: 本契約書で「独占的(proprietary)」とは、ソフトウェアの利用や再頒
+布、改変が禁止されているか、許可を得ることが必要とされているか、あるい
+は厳しい制限が課せられていて自由にそうすることが事実上できなくなってい
+る状態のことを指す。詳しくは
+http://www.gnu.org/philosophy/categories.ja.html#ProprietarySoftwareを
+参照せよ。)
+
+複製や頒布、改変についての正確な条件と制約を以下で述べていきます。
+
+ GNU 一般公衆利用許諾契約書
+ 複製、頒布、改変に関する条件と制約
+
+0. この利用許諾契約書は、そのプログラム(またはその他の著作物)をこの一
+般公衆利用許諾契約書の定める条件の下で頒布できるという告知が著作権者に
+よって記載されたプログラムまたはその他の著作物全般に適用される。以下で
+は、「『プログラム』」とはそのようにしてこの契約書が適用されたプログラ
+ムや著作物全般を意味し、また「『プログラム』を基にした著作物」とは『プ
+ログラム』やその他著作権法の下で派生物と見なされるもの全般を指す。すな
+わち、『プログラム』かその一部を、全く同一のままか、改変を加えたか、あ
+るいは他の言語に翻訳された形で含む著作物のことである(「改変」という語
+の本来の意味からはずれるが、以下では翻訳も改変の一種と見なす)。それぞ
+れの契約者は「あなた」と表現される。
+
+複製や頒布、改変以外の活動はこの契約書ではカバーされない。それらはこの
+契約書の対象外である。『プログラム』を実行する行為自体に制限はない。ま
+た、そのような『プログラム』の出力結果は、その内容が『プログラム』を基
+にした著作物を構成する場合のみこの契約書によって保護される(『プログラ
+ム』を実行したことによって作成されたということとは無関係である)。この
+ような線引きの妥当性は、『プログラム』が何をするのかに依存する。
+
+1. それぞれの複製物において適切な著作権表示と保証の否認声明(disclaimer
+of warranty)を目立つよう適切に掲載し、またこの契約書および一切の保証の
+不在に触れた告知すべてをそのまま残し、そしてこの契約書の複製物を『プロ
+グラム』のいかなる受領者にも『プログラム』と共に頒布する限り、あなたは
+『プログラム』のソースコードの複製物を、あなたが受け取った通りの形で複
+製または頒布することができる。媒体は問わない。
+
+あなたは、物理的に複製物を譲渡するという行為に関して手数料を課しても良
+いし、希望によっては手数料を取って交換における保護の保証を提供しても良
+い。
+
+2. あなたは自分の『プログラム』の複製物かその一部を改変して『プログラ
+ム』を基にした著作物を形成し、そのような改変点や著作物を上記第1節の定
+める条件の下で複製または頒布することができる。ただし、そのためには以下
+の条件すべてを満たしていなければならない:
+
+ a) あなたがそれらのファイルを変更したということと変更した日時が良
+ く分かるよう、改変されたファイルに告示しなければならない。
+
+ b) 『プログラム』またはその一部を含む著作物、あるいは『プログラム』
+ かその一部から派生した著作物を頒布あるいは発表する場合には、その全
+ 体をこの契約書の条件に従って第三者へ無償で利用許諾しなければならな
+ い。
+
+ c) 改変されたプログラムが、通常実行する際に対話的にコマンドを読む
+ ようになっているならば、そのプログラムを最も一般的な方法で対話的に
+ 実行する際、適切な著作権表示、無保証であること(あるいはあなたが保
+ 証を提供するということ)、ユーザがプログラムをこの契約書で述べた条
+ 件の下で頒布することができるということ、そしてこの契約書の複製物を
+ 閲覧するにはどうしたらよいかというユーザへの説明を含む告知が印刷さ
+ れるか、あるいは画面に表示されるようにしなければならない(例外とし
+ て、『プログラム』そのものは対話的であっても通常そのような告知を印
+ 刷しない場合には、『プログラム』を基にしたあなたの著作物にそのよう
+ な告知を印刷させる必要はない)。
+
+以上の必要条件は全体としての改変された著作物に適用される。著作物の一部
+が『プログラム』から派生したものではないと確認でき、それら自身別の独立
+した著作物であると合理的に考えられるならば、あなたがそれらを別の著作物
+として分けて頒布する場合、そういった部分にはこの契約書とその条件は
+適用されない。しかし、あなたが同じ部分を『プログラム』を基にした著作物
+全体の一部として頒布するならば、全体としての頒布物は、この契約書が
+課す条件に従わなければならない。というのは、この契約書が他の契約者
+に与える許可は『プログラム』丸ごと全体に及び、誰が書いたかは関係なく各
+部分のすべてを保護するからである。
+
+よって、すべてあなたによって書かれた著作物に対し、権利を主張したりあな
+たの権利に異議を申し立てることはこの節の意図するところではない。むしろ、
+その趣旨は『プログラム』を基にした派生物ないし集合著作物の頒布を管理す
+る権利を行使するということにある。
+
+また、『プログラム』を基にしていないその他の著作物を『プログラム』(あ
+るいは『プログラム』を基にした著作物)と一緒に集めただけのものを一巻の
+保管装置ないし頒布媒体に収めても、その他の著作物までこの契約書が保
+護する対象になるということにはならない。
+
+3. あなたは上記第1節および2節の条件に従い、『プログラム』(あるいは第2
+節における派生物)をオブジェクトコードないし実行形式で複製または頒布す
+ることができる。ただし、その場合あなたは以下のうちどれか一つを実施しな
+ければならない:
+
+ a) 著作物に、『プログラム』に対応した完全かつ機械で読み取り可能な
+ ソースコードを添付する。ただし、ソースコードは上記第1節および2節の
+ 条件に従いソフトウェアの交換で習慣的に使われる媒体で頒布しなければ
+ ならない。あるいは、
+
+ b) 著作物に、いかなる第三者に対しても、『プログラム』に対応した完
+ 全かつ機械で読み取り可能なソースコードを、頒布に要する物理的コスト
+ を上回らない程度の手数料と引き換えに提供する旨述べた少なくとも3年
+ 間は有効な書面になった申し出を添える。ただし、ソースコードは上記第
+ 1節および2節の条件に従いソフトウェアの交換で習慣的に使われる媒体で
+ 頒布しなければならない。あるいは、
+
+ c) 対応するソースコード頒布の申し出に際して、あなたが得た情報を一
+ 緒に引き渡す(この選択肢は、営利を目的としない頒布であって、かつあ
+ なたが上記小節bで指定されているような申し出と共にオブジェクトコー
+ ドあるいは実行形式のプログラムしか入手していない場合に限り許可され
+ る)。
+
+著作物のソースコードとは、それに対して改変を加える上で好ましいとされる
+著作物の形式を意味する。ある実行形式の著作物にとって完全なソースコード
+とは、それが含むモジュールすべてのソースコード全部に加え、関連するイン
+ターフェース定義ファイルのすべてとライブラリのコンパイルやインストール
+を制御するために使われるスクリプトをも加えたものを意味する。しかし特別
+な例外として、そのコンポーネント自体が実行形式に付随するのでは無い限り、
+頒布されるものの中に、実行形式が実行されるオペレーティングシステムの主
+要なコンポーネント(コンパイラやカーネル等)と通常一緒に(ソースかバイナ
+リ形式のどちらかで)頒布されるものを含んでいる必要はないとする。
+
+実行形式またはオブジェクトコードの頒布が、指定された場所からコピーする
+ためのアクセス手段を提供することで為されるとして、その上でソースコード
+も同等のアクセス手段によって同じ場所からコピーできるようになっているな
+らば、第三者がオブジェクトコードと一緒にソースも強制的にコピーさせられ
+るようになっていなくてもソースコード頒布の条件を満たしているものとする。
+
+4. あなたは『プログラム』を、この契約書において明確に提示された行
+為を除き複製や改変、サブライセンス、あるいは頒布してはならない。他に
+『プログラム』を複製や改変、サブライセンス、あるいは頒布する企てはすべ
+て無効であり、この契約書の下でのあなたの権利を自動的に終結させるこ
+とになろう。しかし、複製物や権利をこの契約書に従ってあなたから得た
+人々に関しては、そのような人々がこの契約書に完全に従っている限り彼
+らのライセンスまで終結することはない。
+
+5. あなたはこの契約書を受諾する必要は無い。というのは、あなたはこ
+れに署名していないからである。しかし、この契約書以外にあなたに対し
+て『プログラム』やその派生物を変更、頒布する許可を与えるものは存在しな
+い。これらの行為は、あなたがこの契約書を受け入れない限り法によって
+禁じられている。そこで、『プログラム』(あるいは『プログラム』を基にし
+た著作物のすべて)を改変ないし頒布することにより、あなたは自分がそのよ
+うな行為を行うためにこの契約書を受諾したということ、そして『プログ
+ラム』とそれに基づく著作物の複製や頒布、改変についてこの契約書が課
+す制約と条件をすべて受け入れたということを示したものと見なす。
+
+6. あなたが『プログラム』(または『プログラム』を基にした著作物全般)を
+再頒布するたびに、その受領者は元々のライセンス許可者から、この契約書で
+指定された条件と制約の下で『プログラム』を複製や頒布、あるいは改変する
+許可を自動的に得るものとする。あなたは、受領者がここで認められた権利を
+行使することに関してこれ以上他のいかなる制限も課すことができない。あな
+たには、第三者がこの契約書に従うことを強制する責任はない。
+
+7. 特許侵害あるいはその他の理由(特許関係に限らない)から、裁判所の判決
+あるいは申し立ての結果としてあなたに(裁判所命令や契約などにより)この契
+約書の条件と矛盾する制約が課された場合でも、あなたがこの契約書の条件を
+免除されるわけではない。もしこの契約書の下であなたに課せられた責任と他
+の関連する責任を同時に満たすような形で頒布できないならば、結果としてあ
+なたは『プログラム』を頒布することが全くできないということである。例え
+ば特許ライセンスが、あなたから直接間接を問わずコピーを受け取った人が誰
+でも『プログラム』を使用料無料で再頒布することを認めていない場合、あな
+たがその制約とこの契約書を両方とも満たすには『プログラム』の頒布を完全
+に中止するしかないだろう。
+
+この節の一部分が特定の状況の下で無効ないし実施不可能な場合でも、節の残
+りの部分は適用されるよう意図されている。その他の状況では節が全体として
+適用されるよう意図されている。
+
+特許やその他の財産権を侵害したり、そのような権利の主張の効力に異議を唱
+えたりするようあなたを誘惑することがこの節の目的ではない。この節には、
+人々によってライセンス慣行として実現されてきた、フリーソフトウェア頒布
+のシステムの完全性を護るという目的しかない。多くの人々が、フリーソフト
+ウェアの頒布システムが首尾一貫して適用されているという信頼に基づき、こ
+のシステムを通じて頒布される多様なソフトウェアに寛大な貢献をしてきたの
+は事実であるが、人がどのようなシステムを通じてソフトウェアを頒布したい
+と思うかはあくまでも作者/寄与者次第であり、あなたが選択を押しつけるこ
+とはできない。
+
+この節は、この契約書のこの節以外の部分の一帰結になると考えられるケー
+スを徹底的に明らかにすることを目的としている。
+
+8. 『プログラム』の頒布や利用が、ある国においては特許または著作権が主
+張されたインターフェースのいずれかによって制限されている場合、『プログ
+ラム』にこの契約書を適用した元の著作権者は、そういった国々を排除し
+た明確な地理的頒布制限を加え、そこで排除されていない国の中やそれらの国々
+の間でのみ頒布が許可されるようにしても構わない。その場合、そのような制
+限はこの契約書本文で書かれているのと同様に見なされる。
+
+9. フリーソフトウェア財団は、時によって改訂または新版の一般公衆利用許
+諾書を発表することができる。そのような新版は現在のバージョンとその精神
+においては似たものになるだろうが、新たな問題や懸念を解決するため細部で
+は異なる可能性がある。
+
+それぞれのバージョンには、見分けが付くようにバージョン番号が振られてい
+る。『プログラム』においてそれに適用されるこの契約書のバージョン番号が
+指定されていて、更に「それ以降のいかなるバージョン」も適用して良いとなっ
+ていた場合、あなたは従う条件と制約として、指定のバージョンか、フリーソ
+フトウェア財団によって発行された指定のバージョン以降の版のどれか一つの
+どちらかを選ぶことが出来る。『プログラム』でライセンスのバージョン番号
+が指定されていないならば、あなたは今までにフリーソフトウェア財団から発
+行されたバージョンの中から好きに選んで構わない。
+
+10. もしあなたが『プログラム』の一部を、その頒布条件がこの契約書と
+異なる他のフリーなプログラムと統合したいならば、作者に連絡して許可を求
+めよ。フリーソフトウェア財団が著作権を保有するソフトウェアについては、
+フリーソフトウェア財団に連絡せよ。私たちは、このような場合のために特別
+な例外を設けることもある。私たちが決定を下すにあたっては、私たちのフリー
+ソフトウェアの派生物すべてがフリーな状態に保たれるということと、一般的
+にソフトウェアの共有と再利用を促進するという二つの目標を規準に検討され
+るであろう。
+ 無保証について
+
+11. 『プログラム』は代価無しに利用が許可されるので、適切な法が認める限
+りにおいて、『プログラム』に関するいかなる保証も存在しない。書面で別に
+述べる場合を除いて、著作権者、またはその他の団体は、『プログラム』を、
+表明されたか言外にかは問わず、商業的適性を保証するほのめかしやある特定
+の目的への適合性(に限られない)を含む一切の保証無しに「あるがまま」で提
+供する。『プログラム』の質と性能に関するリスクのすべてはあなたに帰属す
+る。『プログラム』に欠陥があると判明した場合、あなたは必要な保守点検や
+補修、修正に要するコストのすべてを引き受けることになる。
+
+12. 適切な法か書面での同意によって命ぜられない限り、著作権者、または上
+記で許可されている通りに『プログラム』を改変または再頒布したその他の団
+体は、あなたに対して『プログラム』の利用ないし利用不能で生じた一般的、
+特別的、偶然的、必然的な損害(データの消失や不正確な処理、あなたか第三
+者が被った損失、あるいは『プログラム』が他のソフトウェアと一緒に動作し
+ないという不具合などを含むがそれらに限らない)に一切の責任を負わない。
+そのような損害が生ずる可能性について彼らが忠告されていたとしても同様で
+ある。
+
+ 条件と制約終わり
+
+ 以上の条項をあなたの新しいプログラムに適用する方法
+
+あなたが新しいプログラムを開発したとして、公衆によってそれが利用される
+可能性を最大にしたいなら、そのプログラムをこの契約書の条項に従って
+誰でも再頒布あるいは変更できるようフリーソフトウェアにするのが最善です。
+
+そのためには、プログラムに以下のような表示を添付してください。その場合、
+保証が排除されているということを最も効果的に伝えるために、それぞれのソー
+スファイルの冒頭に表示を添付すれば最も安全です。少なくとも、「著作権表
+示」という行と全文がある場所へのポインタだけは各ファイルに含めて置いて
+ください。
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ 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 2 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, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+ (訳:
+
+ <プログラムの名前と、それが何をするかについての簡単な説明。>
+ Copyright (C) <西暦年> <作者の名前>
+
+ このプログラムはフリーソフトウェアです。あなたはこれを、フリーソフ
+ トウェア財団によって発行された GNU 一般公衆利用許諾契約書(バージョ
+ ン2か、希望によってはそれ以降のバージョンのうちどれか)の定める条件
+ の下で再頒布または改変することができます。
+
+ このプログラムは有用であることを願って頒布されますが、*全くの無保
+ 証* です。商業可能性の保証や特定の目的への適合性は、言外に示された
+ ものも含め全く存在しません。詳しくはGNU 一般公衆利用許諾契約書をご
+ 覧ください。
+
+ あなたはこのプログラムと共に、GNU 一般公衆利用許諾契約書の複製物を
+ 一部受け取ったはずです。もし受け取っていなければ、フリーソフトウェ
+ ア財団まで請求してください(宛先は the Free Software Foundation,
+ Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA)。
+
+ )
+
+電子ないし紙のメールであなたに問い合わせる方法についての情報も書き加え
+ましょう。
+
+プログラムが対話的なものならば、対話モードで起動した際に出力として以下
+のような短い告知が表示されるようにしてください:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+ (訳:
+
+ Gnomovision バージョン 69, Copyright (C) 年 作者の名前
+ Gnomovision は*全くの無保証*で提供されます。詳しくは「show w」
+ とタイプして下さい。これはフリーソフトウェアであり、ある条件の下で
+ 再頒布することが奨励されています。詳しくは「show c」とタイプして下
+ さい。
+
+ )
+
+ここで、仮想的なコマンド「show w」と「show c」は一般公衆利用許諾契約書
+の適切な部分を表示するようになっていなければなりません。もちろん、あな
+たが使うコマンドを「show w」や「show c」と呼ぶ必然性はありませんので、
+あなたのプログラムに合わせてマウスのクリックやメニューのアイテムにして
+も結構です。
+
+またあなたは、必要ならば(プログラマーとして働いていたら)あなたの雇用主、
+あるいは場合によっては学校から、そのプログラムに関する「著作権放棄声明
+(copyright disclaimer)」に署名してもらうべきです。以下は例ですので、名
+前を変えてください:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+ (訳:
+
+ Yoyodyne社はここに、James Hackerによって書かれたプログラム
+ 「Gnomovision」(コンパイラへ通すプログラム)に関する一切の著作権の利
+ 益を放棄します。
+
+ <Ty Coon氏の署名>、1989年4月1日
+ Ty Coon、副社長
+
+ )
+
+この一般公衆利用許諾契約書では、あなたのプログラムを独占的なプログラム
+に統合することを認めていません。あなたのプログラムがサブルーチンライブ
+ラリならば、独占的なアプリケーションとあなたのライブラリをリンクするこ
+とを許可したほうがより便利であると考えるかもしれません。もしこれがあな
+たの望むことならば、この契約書の代わりにGNU ライブラリ一般公衆利用許諾
+契約書を適用してください。
diff --git a/doc/Readme-jap b/doc/Readme-jap new file mode 100644 index 0000000..3e2ac86 --- /dev/null +++ b/doc/Readme-jap @@ -0,0 +1,17507 @@ +--------------------
+//1032 by (凸)
+・1031で何故か削除されていたbuildin_getitemname()を復活
+・バグスレなどに出た修正を反映
+・その他細かい修正
+
+ (map/)
+ clif.c
+ clif_disp_onlyself() NULLチェック追加
+ map.c
+ map_nick2sd() nickがNULLだとすぐNULLを返すように変更
+ mob.c
+ mob_setdelayspawn() NULLチェック変更
+ mob_delete() 修正
+ npc.c
+ npc_parse_warp() 修正
+ script.c
+ buildin_getitemname() 復活
+
+----------------------------------------
+//1031 by huge
+・NPCのscriptに、makepetを追加。
+ makepet 卵ID; で、ペットを作成します。
+・NPCのscriptに、getexpを追加。
+ getexp Base,Job; で、それぞれの経験値を増やします。
+・ペットの卵をNPCのdelitemで消したりshopで売った時、ペットセーブデータから削除するよう修正。
+・ディボーション成功条件修正。(未確認)
+・経験値表示を可能にしてみました。confにて設定してください。
+
+ (conf/)
+ battle_athena.conf 修正
+ (doc/)
+ conf_ref.txt 修正
+ script_ref.txt 修正
+ (map/)
+ battle.c
+ battle.h
+ disp_experience 追加
+ clif.c
+ clif.h
+ clif_disp_onlyself() 追加
+ pc.c
+ pc_gainexp() 修正
+ script.c
+ buildin_delitem() 修正
+ buildin_makepet() 追加
+ buildin_getexp() 追加
+ npc.c
+ #include 修正
+ npc_selllist() 修正
+ skill.c
+ skill_castend_nodamage_id() 修正
+
+
+--------------------
+//1030 by (凸)
+・map_athena.confに新婚島ザワイをコメントアウトして追加
+・クローンスキルで覚えたスキルを自動セーブごとに忘れていたのでとりあえずログオフ時にのみ忘れるように変更したつもり
+・mobskill_castend_posの無害nullpoチェックを変更
+・Emotionの設定がないMobがスキルを使用するときに/!を出していたのを修正
+・バグスレに投げたtrade.cを添付。とりあえず相手が所持できる種類の限界を超えた場合は渡さずに元に戻すように変更
+
+ (conf/)
+ map_athena.conf ザワイ追加
+ (doc/)
+ client_packet.txt パケット長の更新
+ (map/)
+ map.c
+ map_quit() 終了時にクローンスキルで覚えたスキルを忘れるように変更
+ mob.c
+ mobskill_castend_pos() nullpoチェック変更
+ mob_readskilldb() 修正
+ pc.c
+ pc_makesavestatus() クローンスキルを忘れるのをmap_quitに任せた
+ trade.c
+ trade_tradecommit() 所持できる種類以上を取引した場合にアイテムが消えないように
+
+--------------------
+//1029 by (凸)
+・20040619RagexeHC_jp.rgzの0x204と0x20bパケットに対応
+・charとloginも知らないパケットが来たらパケットダンプを出力するようにclif.cからコピペ
+
+ (doc/)
+ client_packet.txt 新パケット追加
+ (char/)
+ char.c
+ parse_char() 0x20b対応
+ (login/)
+ login.c
+ parse_login() 0x204対応
+
+--------------------
+//1028 by (凸)
+・ウンバラ以降、Mobがスキルを使用するときにエモーションを出すようになったので、mob_skill_dbを拡張
+ サンプルでオークウォーリアーが喫煙すると「/…」を出すのとオークレディが速度を使うと「/ちゅ」を出します
+・アイテム682,683を使用すると30秒間ATKやMATKが増えるらしいのでそれっぽく
+・job_db2.txtに謎の行が2行あったのを削除
+・範囲魔法などでスキルユニット相手にステータス変更をかけようとした場合にnullpoが出たのを修正
+
+ (db/)
+ const.txt SC_INCATK SC_INCMATK追加
+ item_db.txt ↑を682,683に追加
+ job_db2.txt 謎の2行を削除
+ mob_skill_db.txt Emotion追加
+ (map/)
+ mob.c
+ mobskill_use() スキル使用時にエモーションを出すように変更
+ mob_readskilldb() Emotionを読み込むように変更
+ mob.h 変更
+ pc.c
+ pc_calcstatus() 変更
+ skill.c
+
+ skill_status_change_end() 変更
+ skill_status_change_start() 変更とNULLチェック修正
+ skill.h 変更
+
+--------------------
+//1027 by Ni+S
+・getitemname関数追加
+・スクリプトにgetitemname関数を追加
+・itemidより、jnameを文字列で返します
+・詳しくはscript_ref.txtで。
+
+ script.c
+ getitemname()追加
+
+----------------------------------------
+//1026 by (凸)
+・1023で入れてなかったclif.hを同梱
+・バイオプラントとスフィアーマインで出したmobを倒すとmob_timer_delete()でnullpoが出る問題を解決したつもり
+・バグ報告スレッド part6 >>63 Destさんのscript.c修正を取り込み
+
+ (map/)
+ clif.h 変更
+ map.h 変更
+ mob.c
+ mob_changestate() 変更
+ skill.c
+ skill_castend_pos2() 変更
+ mob_spawn() 変更
+ do_init_mob() add_timer_func_listにmob_timer_deleteが無かったので追加してみた
+ script.c
+ script_load_mapreg() 変更
+
+------------------------
+//1025 by Sel
+・ロードナイトのジョブ補正が間違っていたのを修正
+・オーラブレード効果時間を修正
+・オーラブレード武器制限を素手以外全てに修正
+・コンセントレーション武器制限を両手槍のみから片手槍+両手槍へ修正
+・トゥルーサイト効果時間を修正
+・ファルコンアサルト武器制限を解除、素手を含めた全てで使用可能に
+
+ (db/)
+ job_db2-2.txt 変更
+ skill_cast_db.txt 変更
+ skill_require_db.txt 変更
+--------------------
+//1024 by mare
+・ファーマシー材料の変更、製造可能薬品の追加
+ 6/8日本鯖にきたものと同じにしました
+
+ (db/)
+ produce_db.txt 変更
+
+----------------------------------------
+//1023 by (凸)
+・1022でエンバグしたnpc_parse_script()を戻し
+・スパイラルピアースの重量追加ダメージ計算式をちょっと変更
+・魔法力増幅の計算式をちょっと変更
+・テンションリラックスが座って使うのではなく使うと座るという情報を見かけたので変更
+・↑に伴いskill_requireのsitting条件廃止
+・バーサークをGvGで使用できないように変更
+
+ (db/)
+ skill_cast_db.txt 変更
+ skill_require_db.txt 変更
+ (doc/)
+ db_ref.txt 修正
+ (map/)
+ battle.c
+ battle_calc_pc_weapon_attack() 変更
+ clif.c
+ clif_sitting() 追加
+ clif_parse_ActionRequest() 変更
+ npc.c
+ npc_parse_script() 修正
+ pc.h 変更
+ pc.c
+ pc_calcstatus() 変更
+ pc_natural_heal_hp() 変更
+ pc_setstand() 追加
+ skill.c
+ skill_castend_nodamage_id() テンションリラックスを使うと座るように
+ skill_check_condition() ST_SITTING廃止
+ skill_use_id() バーサークをGvGで使用できないように
+ skill_status_change_timer() テンションリラックスは10秒ごとにSPを12消費
+ skill_status_change_start() テンションリラックス変更
+ skill_readdb() sitting廃止
+ skill.h 変更
+
+--------------------
+//1022 by (凸)
+・NULLチェック変更
+・login,char,map終了時に開放されていなかったメモリを微妙に開放する努力をしてみた
+・スパノビ爆裂波動実装、クリティカル+50
+・スパノビボーナスを変更、最初から一度も死んでないJobLv70にAll+15、BaseLv99にMHP+2000
+
+ (doc/)
+ client_packet.txt S 01ed追加
+ (char/)
+ do_final() 変更
+ do_init() 変更
+ (login/)
+ do_final() 追加
+ do_init() 変更
+ (map/)
+ atcommand.c Destさんの変更を取り込み
+ battle.c 同上
+ chat.c NULLチェック変更
+ chrif.c NULLチェック変更
+ clif.c NULLチェック変更
+ clif_parse() 変更
+ clif_parse_sn_explosionspirits() 追加
+ clif_parse_sn_doridori() 名前変更
+ guild.c NULLチェック変更
+ intif.c NULLチェック変更
+ itemdb.c NULLチェック変更
+ map.c NULLチェック変更
+ do_final() 変更
+ mob.c NULLチェック変更
+ npc.c NULLチェック変更
+ npc_parse_script() buf開放忘れ?を開放
+ party.c NULLチェック変更
+ path.c NULLチェック変更
+ pc.c NULLチェック変更
+ pc_calcstatus() スパノビ変更
+ pet.c NULLチェック変更
+ storage.c NULLチェック変更
+ trade.c NULLチェック変更
+ vending.c NULLチェック変更
+
+--------------------
+//1021 by Kalen
+・プロンテラ冠婚品NPCにてタキシード販売
+・Umbalaのワープ見直し
+ D2Fのワープを本鯖使用に変更。重複ポイント修正
+・UmbalaNPC修正
+ ラベルを使用しなくいい場所は極力削除(-)
+ セーブポイント修正
+ バンジージャンプ台追加
+ 骸骨門追加
+ 分解、合成処理追加
+ カプラ、案内要員をあるべき場所へ移動。
+ ※一部Emoについて、癌呆自身が逆に取り違えているみたいなので独自で変えました。
+ 見てもらえば分かりますが18と28を逆にするとNPCの会話内容に合うEmoが出たので
+ クエストフラグの条件追加
+ これによりすでに終えている場合でも途中になる可能性があります。
+・スパノビ転職NPC追加
+ 凸さんのログを基に作成しました。
+・アルケミストギルドのノビの場合の対応修正(凸さんからのログより)
+・結婚NPC追加
+ ただし、まだテスト段階です。問題点があるため結婚不可能です。
+ (/script)
+ (/warp)
+ npc_warp_umbala.txt
+ (/npc)
+ (/town)
+ npc_town_umbala.txt
+ npc_town_kafra.txt
+ npc_town_guide.txt
+ npc_town_prontera.txt
+ (/quest)
+ npc_event_marriage.txt(新・テスト)
+ (/job)
+ npc_job_alchemist.txt
+ npc_job_supernovice.txt(新)
+
+--------------------
+//1020 by (凸)
+・nullpoの変更に対応してmap_athena.confの設定削除&skill.c書き換え
+・座っているとHPRとSPRが通常の半分で発動したのを修正
+・スパノビが一定条件(クライアント依存)で/doridoriするとSPR回復量が倍になるように変更
+・結婚式用のエフェクトをスクリプトからwedding命令で発生させることが出来るようした
+・合奏を開始したPCは合奏中に終了できないようにしたつもり(未確認)
+
+ (conf/)
+ map_athena.conf nullpo_check削除
+ (doc/)
+ client_packet.txt 更新
+ conf_ref.txt nullpo_check削除
+ (map/)
+ map.c
+ map_config_read() nullpo_check削除
+ map.h 同上
+ skill.c NULLチェック再度総入れ替え
+ clif.c
+ clif_wedding_effect() 追加
+ clif_parse_QuitGame() 合奏開始者は合奏中に終了できないように変更
+ clif_parse_doridori() 追加
+ clif_parse() doridori追加
+ clif.h 変更
+ pc.c
+ pc_authok() doridori初期化追加
+ pc_natural_heal_hp() 座っているときのHPR時間修正
+ pc_natural_heal_sp() 座っているときのHPR時間修正、doridori追加
+ script.c
+ buildin_wedding_effect() 追加
+
+
+--------------------
+//1019 by Dest
+・nullpoモジュールにコーディングミス発見/修正
+・同、voidな関数から呼ばれた時のnullpo_retv_f()を追加
+・同、条件コンパイルに対応
+
+ (common/)
+ nullpo.c
+ nullpo_info_core() コーディングミス修正
+ nullpo.h
+ NULLPO_CHECKフラグによる条件コンパイル追加
+ nullpo_retv_f() 追加
+
+--------------------
+//1018 by chloe
+・ウンバラモンスターを追加
+
+ (script/mob/)
+ npc_monster.txt 変更
+ 各ウンバラマップにMob配置
+ (db/)
+ mob_db.txt 変更
+ 1495,STONE_SHOOTER,フレイムシューター 修正
+ 1511,AMON_RA,アモンラー 修正
+
+--------------------
+//1017 by (凸)
+・バグ報告スレッド part6 >>46 Destさんのnullpoモジュールを追加&voidな関数から呼ばれた時のnullpo_retv()を追加(とりあえずskill.cのNULLチェックだけ入れ替え)
+・同>>39 Selさんから報告があったコンセントレーションを修正
+・仕様について語り合うスレッド >>33 Kalenさんの情報をclient_packet.txtに反映
+・本鯖相違スレッド part3 >>24 M @zqcM6jBwさんの情報を↑
+・同>>30 はちさんの修正を反映
+
+ (conf/)
+ map_athena.conf nullpo_check追加
+ (db/)
+ item_db.txt 結婚指輪を武器-アクセサリに変更&最新版
+ (doc/)
+ client_packet.txt 更新
+ conf_ref.txt nullpo_check追加
+ (common/)
+ Makefile 変更
+ nullpo.c 追加
+ nullpo.h 追加
+ (map/)
+ Makefile 変更
+ map.c
+ map_config_read() nullpo_check追加
+ map.h 同上
+ skill.c NULLチェック総入れ替え
+ skill_status_change_start() コンセントレーション修正
+ skill_castend_nodamage_id() テレポート修正
+ clif.c
+ clif_skill_setunit() コメント修正
+ pc.c
+ pc_calcstatus() コンセントレーション修正
+
+--------------------
+//1016 by な。
+・Athena雑談スレッドPart4 42 かる氏作のウンバラNPCを追加
+
+ (script/npc/town/)
+ npc_town_umbala.txt追加
+ イベントデバッグ様(230〜256行目)はコメントアウト
+ (conf/)
+ map_athena.conf town に npc: script/npc/town/npc_town_umbala.txt 追加
+
+--------------------
+//1015 by (凸)
+・リムーブトラップを本鯖仕様とAthena仕様で選べるようにした
+・スパノビにAll+10する条件が良く分からなかったけど最初から+10じゃないのは確かなのでとりあえずBase99で一度も死んでなければという条件に変更
+・ダンス中に吹き飛ばされてもエフェクトは移動しないそうなので変更
+・@go 13微調整
+
+
+ (conf/)
+ battle_athena.conf skill_removetrap_type追加
+ (doc/)
+ client_packet.txt 本鯖相違スレッド part3 23 M @zqcM6jBwさんの情報を追加
+ conf_ref.txt skill_removetrap_type追加
+ (map/)
+ atcommand.c
+ atcommand_go() ウンバラの出現位置微調整
+ battle.c
+ battle_config_read() skill_removetrap_type追加
+ battle.h 同上
+ pc.c
+ pc_calcstatus() スパノビAll+10条件変更
+ skill.c
+ skill_blown() ダンス中の吹き飛ばしを元に戻した
+ skill_castend_nodamage_id() リムーブトラップ仕様変更
+
+ --------------------
+//1014 by (Pepermint)
+I fixed again the problem if you put minus sign(-) in front of digits,
+the error comes up when you puchase a item.
+When you put a minus sign(-), the error sign will be changed shrotage of
+amount as original server dose.
+
+I tested with it in ver. 1013, it was working
+
+--------------------
+//1012 by (凸)
+・help.txtにある@goの説明から13と14を削除
+ 機能は消えてないので使えることは使えますが、helpに載せるのはjROに来てからということで
+・cast_db.txtをskill_cast_db.txtに改名
+・ついでにmake cleanでGNUmakefileも消してみることにする
+・u-0さんの検証などを総合してダンスユニット関連を変更
+ 演奏・ダンス中のハエはユニット付きで飛ぶそうです
+ 演奏・ダンス中にワープポイントに乗ったら状態が解除されるようです
+ 合奏中に片方がハエ飛びしたらエフェクトは元の場所に残り、飛んだ先では合奏状態が継続して動けない&時間ごとにSP消費するようです
+・PC_DIE_COUNTERがスクリプトから変更された時にすぐに反映されるように変更
+
+ (conf/)
+ help.txt @go説明変更
+ (db/)
+ skill_cast_db.txt 改名
+ (map/)
+ pc.c
+ pc_setpos() ダンス中断のタイミング変更
+ pc_setglobalreg() PC_DIE_COUNTER特別処理追加
+ skill.c
+ skill_castend_id() NULLチェック変更
+ skill_stop_dancing() マップ移動などでの挙動を変更
+ skill_readdb() skill_cast_dbに改名
+ skill_blown() 飛ばされたらダンス移動
+ npc.c
+ npc_touch_areanpc() ワープポイントに乗ったらダンス解除
+
+--------------------
+//1011 by パイン
+・パーティ要請やギルド要請を出しているときに他の要請を拒否る処理を追加
+ battle_athena.confで切り替えが出来るようになっています。
+・make clean で事が足りるので、objectdel.batを消去
+
+ (map)
+ clif.c
+ clif_party_invite()変更(nullpoの文言違ってました)
+ guild.c
+ guild_invite()変更
+ party.c
+ party_invite()変更
+ battle.c
+ battle_config_read()変更
+ battle.h変更
+ trade.c
+ trade_traderequest()変更
+
+--------------------
+//1010 by (凸)
+・gcc 2.95対策
+・スパノビの1度死ぬまでAll+10をスクリプト変数で実装してみる試み
+ PC_DIE_COUNTER 死ぬと+1、転職すると0になります
+ ↑はキャラクタ永続なスクリプト変数なのでNPCで参照&再設定とか比較的楽かも
+
+ (map/)
+ chrif.c
+ chrif_divorce() gcc2.95対策
+ skill.c
+ skill_attack() gcc2.95対策
+ skill_unit_move_unit_group() gcc2.95対策
+ map.h map_session_dataにdie_counterを追加
+ npc.c
+ npc_click() nullpoメッセージを修正
+ pc.c
+ pc_setrestartvalue() nullpoメッセージを修正
+ pc_authok() die_counter初期化の追加
+ pc_calcstatus() die_counter=0のスパノビはAll+10
+ pc_damage() PC_DIE_COUNTER設定
+ pc_jobchange() PC_DIE_COUNTER設定
+ pc_readparam() gcc2.95対策
+ pc_divorce() gcc2.95対策
+ pc_get_partner() gcc2.95対策
+
+--------------------
+//1009 by ぴざまん
+・ダンス移動軽量化実装
+ battle_athena.confで切り替え可能です。詳しくはconf_refを
+ オリジナルアップデートの為、デフォルトではoffにしています
+ また、この軽量化モードは回線負荷を大幅に削減できる(つもり)ですが、
+ その分サーバー側の処理が重たくなります(といってもある程度のCPUがあれば全然問題にならない程度の負荷ですが)
+・結婚システム/結婚スキル実装
+ 結婚指輪の特別扱いが未実装です(落とせたり取引に出せたりします)
+ 離婚のみ未テストです。
+・結婚用スクリプト(marriage、divorce)追加。
+ ・marriage <partner_name>
+ <partner_name>: 結婚相手の名前
+ 戻り値: 成功:1 失敗:0
+ 結婚処理を行います。対象は話し掛けたプレイヤーと<partner_name>のキャラクターで、どちらかが既婚の場合は失敗します。
+ 1回のmarriageスクリプトで新郎→新婦と新婦→新郎の結婚処理を同時に行います。
+ また、このスクリプトで結婚指輪は与えられません。
+ ・devorce
+ 引数無し
+ 戻り値: 成功:1 失敗:0
+ 離婚処理を行います。対象は話し掛けたプレイヤーで、未婚の場合は失敗します。
+ marriageスクリプトと同様に互いの離婚処理を同時に行います。
+ また、このスクリプトが成功すると自動的に対象キャラクターの結婚指輪は剥奪されます。
+
+ どちらのスクリプトの処理も、2人共ログインしていないと成功しません。
+
+ (map/)
+ pc.c
+ pc_ismarried()、pc_marriage()、pc_divorce()、pc_authok()変更
+ pc_get_partner() 追加
+ map.c
+ map_quit() 変更
+ skill.c
+ skill_use_id()、skill_castend_nodamage_id() 変更
+ skill_unitsetting()、skill_unit_onlimit() 変更
+ script.c
+ buildin_marriage()、buildin_devorce() 追加
+ pc.h 変更
+ battle.h 変更
+
+--------------------
+//1008 by ぴざまん
+・GMセッション隠し実装(未テスト)
+ GMアカウントのキャラクターを@who等で表示するかどうか設定できます。
+ 詳しくはconf_ref.txtを参照してください。
+・結婚システム仮実装
+ char-map間通信のみ実装です。まだ結婚することは出来ません。
+ 尚、このパッチからathena.txtのバージョンが変わります。
+ データの互換性は保ったつもりですが、念の為バックアップを取っておく事を強く推奨します。
+
+ (map/)
+ clif.c
+ clif_countusers() 変更
+ battle.c
+ battle_config_read() 変更
+ atcommand.c
+ atcommand_who() 変更
+ pc.c
+ pc_ismarried()、pc_marriage()、pc_divorce() 追加
+ chrif.c
+ chrif_divorce() 追加
+ chrif_parse() 変更
+ (char/)
+ char.c
+ char_divorce() 追加
+ char_delete()、mmo_char_fromstr()、mmo_char_tostr() 変更
+
+--------------------
+//1007 by (凸)
+・NULLチェックの見直し
+
+ (map/)
+ clif.c
+ clif_send() 変更
+ skill.c
+ skill_delunit() 変更
+
+--------------------
+//1006 by (ruhu)
+・@goに洛陽とニフルヘイム追加
+
+ (conf/)
+ help.txt ニフルヘイム、洛陽追加
+ (map/)
+ atcommand.c
+ atcommand go() ニフルヘイム、洛陽追加
+--------------------
+//1005 by (凸)
+・NULLチェックの見直しと追加、大量に変更したので問題が出る可能性が大です
+・ウンバラ分割パッチが配布されたので@go 12にウンバラを追加
+・ロードナイト バーサークをちょっとそれっぽく(ASPD増加などは未実装)
+・クラウン・ジプシー 月明りの泉に落ちる花びらの使用条件を合奏と同じに変更(効果等は未実装)
+・ブレッシングが本鯖ではステータスアップ→エフェクトだったのでそのように変更
+・本鯖相違スレッド part2 >>145 zzzさんの報告を反映
+・同スレ >>143 plalaさんの報告を反映
+
+ (db/)
+ skill_require_db.txt 変更
+ (conf/)
+ help.txt ウンバラ追加
+ map_athena.conf ウンバラ関連のコメントアウトを解除
+ (map/)
+ atcommand.c NULLチェック強化
+ atcommand() @mapmoveなどで落ちる問題を修正
+ atcommand_go() ウンバラ追加
+ battle.c NULLチェック強化
+ battle_calc_mob_weapon_attack() バーサーク時ダメージ150%
+ battle_calc_pc_weapon_attack() バーサーク時ダメージ150%
+ chat.c NULLチェック強化
+ chrif.c NULLチェック強化
+ clif.c NULLチェック強化
+ clif_parse_ActionRequest() ダンス時の処理を若干変更
+ clif_parse_GlobalMessage() バーサーク時は会話が出来ないように変更
+ clif_parse_Wis() バーサーク時は会話が出来ないように変更
+ clif_parse_PartyMessage() 同上
+ clif_parse_GuildMessage() 同上
+ clif_parse_TakeItem() バーサーク時はアイテムを取れないように変更
+ clif_parse_DropItem() バーサーク時はアイテムを落とせないように変更
+ clif_parse_UseItem() バーサーク時はアイテムを使えないように変更
+ clif_parse_EquipItem() バーサーク時は装備できないように変更
+ clif_parse_UnequipItem() バーサーク時は装備解除できないように変更
+ clif_parse_UseSkillToId() バーサーク時はスキル使用ができないように変更
+ clif_parse_UseSkillToPos() 同上
+ clif_parse_UseSkillMap() 同上
+ guild.c NULLチェック強化
+ intif.c NULLチェック強化
+ itemdb.c NULLチェック強化
+ map.c NULLチェック強化
+ map_quit() バーサーク時にログアウトするとHP 100/SP 0になるように変更
+ mob.c
+ mob_attack() clif_fixmobpos()を送信しないように変更
+ mob_timer() NULLチェック条件を変更
+ mobskill_castend_id() 同上、バーサーク時スキルを使えないように変更
+ mobskill_castend_pos() バーサーク時スキルを使えないように変更
+ mobskill_use_id() 同上
+ mobskill_use_pos() 同上
+ npc.c NULLチェック強化
+ party.c NULLチェック強化
+ path.c NULLチェック強化
+ pc.c NULLチェック強化
+ pc_calcstatus() バーサーク時は速度UP&MHP3倍、メディテイティオのSP回復増加をSPRではなく通常回復にかかるようにした
+ pc_heal() バーサーク時は回復しない
+ pc_jobchange() 転職直後1歩動かないと服の色が反映されなかったのを修正
+ pc_natural_heal_sub() バーサーク中はSPが自然回復しないように変更(HPは良く分からなかったので回復するようにしてある)
+ pet.c NULLチェック強化
+ skill.c
+ skill_castend_nodamage_id() ブレッシング等のパケット順を変更、ストーンカースを不死には無効にした
+ skill_castend_map() しつこいくらいにバーサーク時にスキルを使えないように変更
+ skill_check_condition() 同上
+ skill_use_pos() 同上
+ skill_use_id() 同上&月明りの泉に落ちる花びら追加
+ skill_status_change_end() バーサーク時にはIAアイコンを消去するように変更
+ skill_status_change_timer() NULLチェック条件を変更、月明りの泉に落ちる花びら時SP消費、バーサーク時HP100以上なら10秒あたり1%減らすように変更
+ skill_status_change_start() バーサーク時にはIAアイコンを表示するように変更
+ skill_delunit() NULLチェック条件を変更
+ skill_check_condition_char_sub() 月明りの泉に落ちる花びら追加
+ skill_check_condition_use_sub() 同上
+ skill_is_danceskill() 同上
+ skill_initunitgroup() 同上
+ trade.c NULLチェック強化
+ vending.c NULLチェック強化
+
+--------------------
+//1004 by (凸)
+・NULLチェックでエンバグしていたところをいくつか修正と他のNULLチェック強化
+・skill.cでもnullpoを表示するように変更
+
+ (map/)
+ battle.c
+ battle_damage() NULLチェック強化
+ battle_heal() 同上
+ clif.c
+ clif_damage() 同上
+ map.c
+ map_addflooritem() NULLチェック強化
+ mob.c
+ mob_once_spawn() 余計なNULLチェックを削除
+ mob_once_spawn_area() 同上
+ mob_damage() 同上
+ mob_counttargeted() 同上
+ mobskill_castend_id() 同上
+ mob_summonslave() メッセージ間違いを修正
+ pc.c
+ pc_damage() NULLチェック強化
+ skill.c ほぼ全部 NULL関連修正
+
+--------------------
+//1003 by (凸)
+・jROクライアントで/account使用時に繋がらない原因の0x200パケット問題を修正
+・0x1c9パケットの穴埋めを引き続き継続中
+・一人で聖体や合奏を使用できる設定(player_skill_partner_check)を追加
+ 一人で合奏を実行した場合には通常のダンスと挙動が同じになります
+・プロボックは不死に対して発動しないように変更
+・FWの吹き飛ばし判定を変更
+・デリュージに水場判定を追加、聖水を作れるのを確認
+・ハイディングの有効時間が正しく機能するように修正
+・アスペルシオを不死に使用した場合、聖属性の40ダメージを与えるように変更
+・アスペルシオを不死以外のMOBに使用しても効果が無いように変更
+・合奏、ダンス、演奏中の経過時間によるSP消費を実装
+・報告のあったガーディアンがギルド未加入PCをターゲットしたらmapが落ちる問題の修正(ガーディアン実装当時からずっと落ちてた?)
+・mob.c、storage.cのnullチェックを強化
+ エラーで落ちるべきところを無理やり通常処理に戻しているので他で問題がでるかもしれません
+ その場合、コンソールに「関数名 nullpo」と表示されるので表示された場合は報告をお願いします
+ もしかしたら正常な処理でも表示されるかもしれませんが、その際も報告をお願いします
+ 本来エラー、実は正常、どちらにしてもこれが表示されるのはバグです
+
+ (conf/)
+ battle_athena.conf player_skill_partner_check追加
+ (db/)
+ skill_db.txt アスペルシオの属性を聖に変更
+ (doc/)
+ conf_ref.txt player_skill_partner_check説明追加
+ (login/)
+ login.c
+ parse_login() 0x200パケット対応
+ (map/)
+ mob.c NULLチェックでほぼ全部
+ storage.c NULLチェックでほぼ全部
+ battle.h 変更
+ battle.c
+ battle_calc_magic_attack() アスペルシオを追加、FWを変更
+ battle_config_read() 設定追加
+ clif.c
+ clif_getareachar_skillunit() 調査結果の反映
+ clif_skill_setunit() 同上
+ [1001と1002の間の変更点]
+ skill.c NULLチェックを沢山
+ skill_castend_damage_id() アスペルシオ追加
+ skill_castend_nodamage_id() アスペルシオ、プロボック処理変更
+ skill_castend_id() アスペルシオ処理変更
+ skill_check_condition_char_sub() player_skill_partner_checkに対応
+ skill_check_condition_use_sub() 同上
+ skill_use_id() 同上
+ skill_check_condition() 同上&デリュージ対応
+ skill_status_change_timer() ハイディング修正、ダンス演奏合奏中のSP消費実装
+ skill_initunitgroup() ダンスSP消費用変更
+ skill_status_change_start() 同上&プロボックをボスに効かないように(でもbattle.cで止めてるから通常ここまで来ない)
+
+--------------------
+//1002 by ぴざまん
+・ポータルバグ修正
+・スキル関係のNullチェック強化(by(凸))
+ (map/)
+ skill.c
+ skill_castend_map() 修正
+
+--------------------
+//1001 by (凸)
+・0x1c9でいくつかパケットを見比べて変化のないところを固定値で埋め込み(今後情報が集まると変わる可能性大)
+・ダンス中はSPだけ回復しないように変更
+・聖体で相方のSPが10以下だと使用失敗にして使用したらSPを10減らしてみる(未確認)
+・合奏のスキル使用パケットを若干変更
+
+ (map/)
+ clif.c
+ clif_getareachar_skillunit() 0x1c9の穴埋め開始
+ clif_skill_setunit() 0x1c9の穴埋め開始
+ pc.c
+ pc_natural_heal_sub() ダンス中はSPのみ回復しないように変更
+ skill.c
+ skill_check_condition() 変更
+ skill_check_condition_char_sub() 聖体は相方のSPチェックするように変更
+ skill_check_condition_use_sub() 聖体は相方のSPを減らすように変更
+
+--------------------
+//1000 by ぴざまん
+・0999の変更取り戻し
+・トラップの巻き込み実装
+・イドゥンの林檎でNPCまで回復した(ように見える)問題修正(未テスト)
+・デモンストレーションのエフェクト変更(こちらに明記)
+ (map/)
+ skill.c
+ skill_count_target() 追加
+ skill_unit_onplace()、skill_trap_splash() 変更
+--------------------
+//0999 by eAthena Dev Team (Yor's Fixes)
+(login/)
+ added email for accounts
+(char/)
+ added email for character deletion
+--------------------
+//0998 by (凸)
+・battle.cで引数の存在を確認せずに値を見に行ってる関数をいくつか修正
+・ダンス中は座れないようにした(本鯖相違スレッド part2 >>114 DoTさん)
+・ダンス中はHP、SPが回復しないように変更(同上)
+・ダンス中は通常攻撃できないようにした(本鯖相違スレッド part2 >>116 ・・・さん)
+・合奏中片方が落ちた場合、残ったほうで演奏を継続するようにした(同上)
+・合奏中はアドリブ以外できないように変更(同上)
+・合奏発動条件にダンス中じゃない&座っていないを追加(本鯖相違スレッド part2 >>118 ろろさん)
+・阿修羅使用後にHPも回復しなかったのを修正
+
+ (map/)
+ battle.c
+ battle_counttargeted() 修正
+ battle_get系 たぶん全部修正
+ clif.c
+ clif_parse_WalkToXY() 合奏の判断方法の変更
+ clif_parse_ActionRequest() ダンス中は殴らない座らないように変更
+ map.c
+ map_quit() ダンス中断追加
+ mob.c
+ mob_damage() skill_stop_dancingの引数増加による変更
+ pc.c
+ pc_setpos() 同上
+ pc_damage() 同上
+ pc_equipitem() 同上
+ pc_natural_heal_sub() ダンス中は自然回復しないように&阿修羅時はSPのみ回復しないように
+ skill.c
+ skill_castend_nodamage_id() skill_stop_dancingの引数増加による変更
+ skill_status_change_start() 同上
+ skill_check_condition_char_sub() 相手がダンス中や座っていても合奏できないように変更
+ skill_check_condition_use_sub() 同上
+ skill_use_id() 合奏の判断方法の変更&合奏中はアドリブ以外禁止に
+ skill_status_change_end() 合奏のダンス状態解除は相手のval4を0にするようにした
+ skill_is_danceskill() 戻り値変更
+ skill_stop_dancing() 引数増加、合奏で片方だけ落ちたときの処理追加
+ skill_delunitgroup() 合奏時のステータス変更処理を↑に移した
+ skill_clear_unitgroup() 自分のbl->idとユニットグループのgroup->src_idが違うときは削除しないように変更
+ skill.h 変更
+
+--------------------
+//0997 by (凸)
+・二人で合奏、三人で聖体を実装
+ 合奏 バード・ダンサーが隣接したセルにいて同じスキルを持っている時に発動
+ スキルレベルは両者のスキルレベルの中間
+ 本鯖仕様が分からないけど斜め隣接もOK
+ 聖体 使用者のX軸で-1と+1の場所に一人ずつアコライトかプリーストがいる時に発動すると思う(未テスト)
+ 使用者 ○ アコプリ ●
+ OK ●○●
+
+ NG ●
+ ○●
+・トーキーボックスを使って設置時に座っていた時はスキル使用失敗にしてみた
+
+ (db/)
+ item_db.txt 村正の呪い率を5%に(by e2さん)
+ (map/)
+ skill.c
+ skill_check_condition_char_sub() 追加
+ skill_check_condition_use_sub() 追加
+ skill_check_condition() 変更
+ skill_use_id() 変更
+ skill_initunitgroup() 変更
+ skill_delunitgroup() 変更
+ clif.c
+ clif_parse_UseSkillToPos() 変更
+
+--------------------
+//0996 by (凸)
+・アドリブの5秒制限をcast_db.txtのupkeep_time2で制御できるように変更
+・村正で自分を呪わせるためにbonus2 bAddEff2を追加
+・呪われていて終了できない時にも「今は終了できません」が表示されるようにした
+・ダンスエフェクト等移動時に効果の適用判断が移動前に行われていたのを移動後に変更&使わなくていい変数を廃止
+
+ (db/)
+ cast_db.txt
+ const.txt
+ item_db.txt
+ (doc/)
+ item_bonus.txt
+ (src/)
+ clif.c
+ clif_parse_QuitGame() 変更
+ map.h addeff2,arrow_addeff2 追加
+ pc.c
+ pc_calcstatus() 変更
+ pc_bonus2() 変更
+ skill.c
+ skill_additional_effect() 変更
+ skill_check_condition() 変更
+ skill_unit_move_unit_group() 変更
+
+--------------------
+//0995 by (凸)
+・battle.pet_lootitemのデフォルトがyesになっていたのを修正
+・battle.pet_lootitemの適用をforeach前でやるように修正
+・ペットの初期化でpd->lootitemがある時しか初期化されていなかったのを修正
+・領域が移動して対象が領域から抜けても効果が解除されない問題を修正
+・アドリブはダンス発動から5秒以上経たないと使用できないようにした
+
+ (map/)
+ pet.c
+ pet_data_init() 修正
+ pet_ai_sub_hard() 修正
+ pet_ai_sub_hard_lootsearch() 修正
+ skill.c
+ skill_blown() 変更
+ skill_unit_onlimit() エラーメッセージ修正
+ skill_check_condition() 変更
+ skill_initunitgroup() sd_data[SC_DANCING].val3にgettick()
+ skill_unit_move_unit_group_sub() 追加
+ skill_unit_move_unit_group() 変更
+ skill.h skill_unit_move_unit_group() 引数変更
+ battle.c
+ battle_config_read() pet_lootitem=0に修正
+ pc.c
+ pc_walk() 変更
+
+--------------------
+//0994 by huge
+・battle.pet_lootitemが適応されていなかったのを修正。
+・ペットにパフォーマンスをさせた後、10秒間くらいは拾わせないように。
+
+ (map/)
+ map.h pet_dataにlootitem_timer追加。
+ pet.c
+ pet_ai_sub_hard_lootsearch() 修正。
+ pet_lootitem_drop() 修正。
+
+--------------------
+//0993 by (凸)
+・一人で合奏だけど合奏スキル中は動けないように
+・アンコール実装。直前に使ったダンススキルを半分のSPで使えます
+・ダンス中に移動するとエフェクトも移動するようにした
+・未実装のスキルが使われるとUnknown skillと表示されることがあるかもしれません
+・Sageのキャストキャンセルで実は前のスキルを覚えていなかった問題の修正
+
+ (map/)
+ clif.c
+ clif_parse_WalkToXY() 変更
+ map.h アンコール用変数の追加
+ pc.c
+ pc_walk()
+ skill.c
+ skill_blown
+ skill_castend_nodamage_id
+ skill_unitsetting() 変更
+ skill_unit_onplace() 罠で同じ処理をしているcaseをまとめた
+ skill_unit_onout() 使われてないunit2を削除
+ skill_check_condition() 変更
+ skill_use_id
+ skill_initunitgroup() 使ったダンススキルを変数に入れるようにした
+ skill_unit_move_unit_group() 追加
+ skill.h 変更
+
+--------------------
+//0992 by nokia
+
+map_quitを修正してメモリを釈放する時何度もを釈放するためメモリの間違いが起こる問題を招く
+
+ (map/)
+ map.c
+ map_quit()
+
+--------------------
+//0991 by (凸)
+・トーキーボックスを自分が踏んでも発動しない本鯖仕様に変更
+・スキル詠唱中とディレイ中はクライアントを終了できないようにしたけど、敵に攻撃されているときは終了できます(ごめんなさいっ!!)
+
+ (map/)
+ clif.c
+ clif_parse_QuitGame() 変更
+ skill.c
+ skill_unitsetting() 変更
+
+--------------------
+//0990 by ぴざまん
+・一部のトラップを範囲攻撃に変更(巻き込みは未実装)。
+・mapflagにpvp_nocalcrankとpvp_nightmaredrop追加。
+ ・<gatname><tab>mapflag<tab>pvp_nocalcrank<tab>dummy
+ PvPによるランキング計算をしないようにします。
+ ・<gatname><tab>mapflag<tab>pvp_nightmaredrop<tab><item>,<type>,<per>
+ PvPにて死亡した場合、<per>の確率でアイテムをドロップします。
+ <item>: ドロップするアイテムIDを指定します。randomと記述すると所持品からランダムにドロップします。
+ <type>: ドロップするアイテムのタイプを指定します。inventory:所持品 equip:装備品 all:全部
+ <per>: ドロップする確率です。万分率で指定します。
+ (map/)
+ skill.c
+ skill_unit_onplace() 変更
+ skill_trap_splash() 追加
+ pc.c
+ pc_damage() 変更
+ npc.c
+ npc_parse_mapflag() 変更
+ map.h
+ map_dataにメンバ追加
+ (conf/)
+ mapflag.txt
+ ナイトメアモードにドロップ設定追加
+
+--------------------
+//0989 by (凸)
+・スプリングトラップを某所で見たSSを元に実装してみた。違ったら相違スレへ
+・ディテクティングを某所で見た解説文を元に実装してみた。違ったら相違スレへ
+・キャストキャンセル時のdelete_timerエラーに追加でスキルIDを表示するようにした。あわせて報告するとエラーの原因が分かるかも
+
+ (map/)
+ skill.c
+ skill_castend_nodamage_id() 変更
+ skill_castend_pos2() 変更
+ skill_castcancel() 変更
+
+--------------------
+//0988 by (凸)
+・トーキーボックスは常に足元に置けるように変更とKalenさんに貰った情報等を元にパケットを本鯖にあわせてみた
+・ショックウェーブトラップに効果を追加したつもり(未確認)
+・ブラストマインとクレイモアトラップを殴ると吹き飛ぶようにした
+・リムーブトラップで罠(skill_require_dbで指定したアイテム)を回収できるようにした
+・↑の実装により罠の時間切れで罠が出るオリジナル仕様はコメントアウト
+・ブラストマインの効果時間が長いのを修正
+
+ (db/)
+ cast_db.txt
+ (map/)
+ battle.c
+ battle_check_target() 変更
+ mob.c
+ mobskill_castend_pos() 変更
+ skill.c
+ skill_additional_effect() 変更
+ skill_blown() 変更
+ skill_castend_nodamage_id() 変更
+ skill_castend_pos2() 変更
+ skill_unitsetting() 変更
+ skill_unit_onplace() 変更
+ skill_unit_ondamaged() 変更
+ skill_castend_pos() 変更
+ skill_unit_timer_sub() 変更
+
+--------------------
+//0987 by 胡蝶蘭
+
+・ユーザー定義関数/サブルーティンに引数を指定可能に
+ 詳しくは script_ref.txt を参照
+
+ (map/)
+ scirpt.c
+ buildin_getarg()追加
+ buildin_callsub(),buildin_callfunc(),run_func()修正
+ (doc/)
+ script_ref.txt
+ 引数についての説明追加
+
+・getguildname,getpartyname,getcastlename,strcharinfo修正
+ C_STRで定数文字列(C_CONSTSTR)を返していた問題修正
+ strcharinfoでギルド名などを所得する際、未所属だったときの問題
+
+ (map/)
+ script.c
+ buildin_getguildname(),buildin_getpartyname()
+ buildin_getcastlename(),buildin_strcharinfo()
+
+--------------------
+//0986 by (凸)
+・client_packetの記述から推察してトーキーボックスを実装してみたつもり
+
+ (db/)
+ skill_db.txt
+ (map/)
+ clif.c
+ clif_talkiebox() 追加
+ clif_parse_UseSkillToPos() 変更
+ clif_parse() 変更
+ clif.h 変更
+ map.h 変更
+ skill.c
+ skill_castend_pos2() 変更
+ skill_unitsetting() 変更
+ skill_unit_onplace() 変更
+
+--------------------
+//0985 by (凸)
+・サーバーsnapshot
+・/script/extensionに語り部を追加したり
+・0984でAthena雑談スレッド part3>>92 Michaelさんの修正を取り込んでた
+・バグ報告スレッド part5 >>45に転載されてた あやねさんの修正を取り込み
+・バグ報告スレッド part5 >>54 rariさんのたぬき帽修正を取り込み
+
+ (script/extension/)
+ npc_event_kataribe6.txt 追加
+ npc_event_kataribe7.txt 追加
+ (script/npc/)
+ quest/npc_event_hat.txt 修正
+ job/npc_job_magician.txt 修正
+
+--------------------
+//0984 by (凸)
+・セージのフリーキャストというスキルの存在を知らなかったので0983の変更を一部戻し
+・アイテム更新したけど垂れ猫とインディアンバンダナが装備できない?
+・マップフラグとモンスター配置を最新版に
+
+ (db/)
+ item_db.txt
+ (conf/)
+ mapflag.txt
+ (script/mob/)
+ npc_monster.txt
+ (map/)
+ clif.c
+ clif_parse_WalkToXY() 修正
+
+--------------------
+//0983 by (凸)
+・長い詠唱中に歩きまわれた気がするので修正
+・ペットルーレット回転中に対象が叩き殺されたらmap-serverが困るのを修正
+・プロフェッサー ライフ置き換えの手抜き処理をちょっとマシにした
+
+ (map/)
+ clif.c
+ clif_parse_WalkToXY() 修正
+ pet.c
+ pet_catch_process2() 修正
+ skill.c
+ skill_castend_nodamage_id() 修正
+
+--------------------
+//0982 by (凸)
+・転生スキルの〜
+ ストーカー リジェクトソード 相手がPCの場合は剣じゃなければ跳ね返さない予定
+ プロフェッサー メモライズ 12秒の固定キャストタイム、その後スキル使用のキャストタイムが3回だけ1/3になる
+ プロフェッサー ライフ置き換え HPを10%減らしてSPを増やす。減ったHPのエフェクトは無しにしてみた
+・見切りと回避率増加のFlee上昇がステータス変化時にしか反映されていなかったのを修正
+・0981で自分以外は歩いたら服の色が戻ってしまうのをなんとかしたつもり
+
+ (db/)
+ skill_require_db.txt
+ (map/)
+ battle.c
+ battle_calc_damage() 修正
+ clif.c
+ clif_movechar() 修正
+ clif_getareachar_pc() 修正
+ pc.c
+ pc_calcstatus() 修正
+ skill.c
+ SkillStatusChangeTable 変更
+ skill_castend_nodamage_id() 変更
+ skill_use_id() 変更
+ skill_use_pos() 変更
+ skill_status_change_timer() 変更
+ skill_status_change_start() 変更
+ skill.h 変更
+
+--------------------
+//0981 by (凸)
+・転生スキルの〜
+ ストーカー リジェクトソード 一定確率でダメージを半分にして減らした分を相手に跳ね返すようにした…が、跳ね返したダメージのエフェクト出ません
+・服の色を変更&保存している場合にリログすると元の色に戻ってるように見える問題の修正
+
+ (db/)
+ skill_db.txt
+ (map/)
+ clif.c
+ clif_parse_LoadEndAck() 修正
+ battle.c
+ battle_calc_damage() 修正
+ skill.c
+ skill_status_change_timer() 修正
+ skill_status_change_start() 修正
+
+--------------------
+//0980 by (凸)
+・例によって転生スキルとモンク関連
+ スナイパー シャープシューティング クリティカル率調整?
+ ハイウィザード ソウルドレイン MSP増量とMobを倒したときにSP(mobLv*(65+15*SkillLv)/100)回復。でも本当は範囲攻撃の場合は回復しないらしい?
+ ハイウィザード 魔法力増幅 使ったらMATKがSkillLv%増量。次のスキル使用時に元に戻る
+ モンク 気奪 ちょっと先取りして20%の確率で敵のLv*2のSPを吸収。成功したときはターゲットを取得するようにしてみた
+・アイテム名をjROウンバラに準拠させてみたつもり
+
+ (db/)
+ cast_db.txt
+ item_db.txt
+ (map/)
+ battle.c
+ battle_calc_pc_weapon_attack() 変更
+ mob.c
+ mob_damage() 変更
+ pc.c
+ pc_calcstatus() 変更
+ skill.c
+ skill_castend_nodamage_id() 変更
+ skill_use_id() 変更
+ skill_use_pos() 変更
+ skill_status_change_end() 変更
+ skill_status_change_timer() 変更
+ skill_status_change_start() 変更
+
+--------------------
+//0979 by (凸)
+・転生スキルをちょっと調整
+ ハイウィザード マジッククラッシャー 武器攻撃でBaseATK計算をMATK2でしてみる
+・息吹を本鯖仕様風にHPとSPの回復タイマーを分けて座っていなくても動かなければタイマーが進むようにした
+・阿修羅使用後にHPとSPが5分間自然回復しない本鯖使用風にした(csat_db.txtのupkeep_time2で調整可能)
+・古いgccでskill_unit_timer_sub_onplace()あたりでコンパイルエラーになったのを修正
+
+ (db/)
+ cast_db.txt
+ (map/)
+ battle.c
+ battle_calc_pet_weapon_attack() 変更
+ battle_calc_mob_weapon_attack() 変更
+ battle_calc_pc_weapon_attack() 変更
+ map.h
+ pc.c
+ pc_authok() 変更
+ pc_walk() 変更
+ pc_spirit_heal() 削除
+ pc_spirit_heal_hp() 追加
+ pc_spirit_heal_sp() 追加
+ pc_natural_heal_sub() 変更
+ skill.c
+ skill_additional_effect() 変更
+ skill_castend_damage_id() 変更
+ skill_status_change_start() 変更
+ skill_unit_timer_sub_onplace() 修正
+
+--------------------
+//0978 by (凸)
+・転生スキルを修正したり色々
+ スナイパー ファルコンアサルト とりあえず飛ぶだけだと思ってください
+ スナイパー シャープシューティング ダメージ増加だけど1体だけ
+ クラウン・ジプシー アローバルカン ダメージ増加と9回攻撃
+ ハイウィザード マジッククラッシャー エフェクトだけ
+
+・アイテム交換で重量の計算が違っていたのを修正
+ (doc/)
+ client_packet.txt パケット長テーブル更新
+ (map/)
+ battle.c
+ battle_calc_misc_attack() 変更
+ battle_calc_pet_weapon_attack() 変更
+ battle_calc_mob_weapon_attack() 変更
+ battle_calc_pc_weapon_attack() 変更
+ clif.c パケット長の定義を更新
+ skill.c
+ skill_castend_damage_id() 変更
+ trade.c
+ trade_tradeadditem() 修正
+
+--------------------
+//0977 by (凸)
+・転生スキルを修正したり色々
+ アサシンクロス メテオアサルト エフェクトが違う?
+ ロードナイト プレッシャー 必中ダメージにしてみた
+ ロードナイト オーラブレードの必中damage2が他でも適用されていたのを修正
+ ロードナイト ゴスペル エフェクト出現位置の調整
+ ハイプリースト アシャンプティオ 効果実装
+ ハイプリースト メディテイティオ 効果実装
+ ハイプリースト バジリカ SGみたいにMobが侵入しようとすると吹き飛ばされるようにした
+ ホワイトスミス カートブースト 効果実装
+ ホワイトスミス メルトダウン エフェクトと状態異常時間だけ(実際の状態変化は無し)
+ ホワイトスミス クリエイトコイン 名前入りの金貨とか作れるだけ
+ ストーカー リジェクトソード エフェクトと状態異常時間だけ(実際の状態変化は無し)
+ クラウン・ジプシー マリオネットコントロール エフェクトと状態異常時間だけ(実際の状態変化は無し)
+ プロフェッサー フォグウォール エフェクトと有効時間だけ
+ スナイパー ウインドウォーク 速度上昇とQM、私を忘れないでがかかると解除されるようにした
+ スナイパー トゥルーサイト QM、私を忘れないでで解除されるようにしてみた
+・トゥルーサイトの綴り間違いを修正
+・storage.cでコンパイル警告が出ないようにしたつもり
+
+ (db/)
+ cast_db.txt
+ skill_db.txt
+ skill_require_db.txt
+ produce_db.txt
+ (map/)
+ battle.c
+ battle_get_str() 修正
+ battle_get_agi() 修正
+ battle_get_vit() 修正
+ battle_get_int() 修正
+ battle_get_dex() 修正
+ battle_get_luk() 修正
+ battle_get_flee() 修正
+ battle_get_hit() 修正
+ battle_get_critical() 修正
+ battle_get_baseatk() 修正
+ battle_get_atk() 修正
+ battle_get_atk2() 修正
+ battle_get_def() 修正
+ battle_get_def2() 修正
+ battle_get_speed() 修正
+ battle_calc_damage() 修正
+ battle_calc_pet_weapon_attack() 変更
+ battle_calc_mob_weapon_attack() 変更
+ battle_calc_pc_weapon_attack() 変更
+ pc.c
+ pc_calcstatus() 修正
+ skill.c
+ skill_get_unit_id() 修正
+ skill_additional_effect() 修正
+ skill_castend_nodamage_id() 修正
+ skill_castend_pos2() 修正
+ skill_unit_group() 修正
+ skill_unit_onplace() 修正
+ skill_unit_onout() 修正
+ skill_castend_pos() 修正
+ skill_check_condition() 修正
+ skill_status_change_end() 修正
+ skill_status_change_start() 修正
+ skill_can_produce_mix() 修正
+ skill_produce_mix() 修正
+ skill.h 修正
+ storage.c
+ storage_comp_item() 修正
+ storage.h 修正
+
+--------------------
+//0976 by (凸)
+・転生スキルを修正したり色々
+・準備だけして実装できてないスキルもあります
+ ロードナイト オーラブレード 多分こんな感じ?
+ ロードナイト パリイング 跳ね返すけど攻撃を1回止めるのは未実装
+ ロードナイト コンセントレーション インデュア〜は良く分からないので放置
+ ロードナイト スパイラルピアース 通常ダメージ増加と重量ダメージ増加と一応5回攻撃(なんか違う気がする)
+ ロードナイト ヘッドクラッシュ ダメージ増加とステータス変更?
+ ロードナイト ジョイントビート ダメージ増加とステータス変更?
+ アサシンクロス アドバンスドカタール研究 たぶんこんな感じ?
+ スナイパー トゥルーサイト たぶんこんな感じ?
+ スナイパー ウィンドウォーク たぶんこんな感じ?でも速度上昇とかと競合した時の処理は未実装
+ スパイダーウェッブ とりあえずアンクルスネアと同じような感じ&回避率半減
+ チャンピオン 狂気功 適当に増やしていたのをちゃんと増やすようにした
+・出血状態と骨折状態の取り扱いがよくわかりませんっ!!
+
+ (db/)
+ cast_db.txt
+ skill_db.txt
+ skill_require_db.txt
+ (doc/)
+ db_ref.txt
+ (map/)
+ battle.c
+ battle_get_str() 修正
+ battle_get_agi() 修正
+ battle_get_vit() 修正
+ battle_get_int() 修正
+ battle_get_dex() 修正
+ battle_get_luk() 修正
+ battle_get_flee() 修正
+ battle_get_hit() 修正
+ battle_get_critical() 修正
+ battle_get_baseatk() 修正
+ battle_get_atk() 修正
+ battle_get_atk2() 修正
+ battle_get_def() 修正
+ battle_get_def2() 修正
+ battle_get_speed() 修正
+ battle_calc_damage() 修正
+ clif.c
+ clif_parse_WalkToXY() 修正
+ mob.c
+ mob_can_move() 修正
+ mobskill_castend_pos() 修正
+ pc.c
+ pc_calcstatus() 修正
+ pc_checkallowskill() 修正
+ skill.c
+ skill_get_unit_id() 修正
+ skill_additional_effect() 修正
+ skill_castend_nodamage_id() 修正
+ skill_castend_pos2() 修正
+ skill_unit_group() 修正
+ skill_unit_onplace() 修正
+ skill_unit_onout() 修正
+ skill_castend_pos() 修正
+ skill_check_condition() 修正
+ skill_status_change_end() 修正
+ skill_status_change_start() 修正
+ skill_readdb() 修正
+ skill.h 修正
+--------------------
+//0975 by Sin
+・0973で実装されたスクリプトによるBaseLv, JobLvの変更時に、
+ ステータスポイントやスキルポイントを取得できるように修正。
+ ※質問スレpart5 >>115 悩める人 さんのpc.cを参考にさせて頂きました。多謝。
+
+ (map/)
+ pc.c
+ pc_setparam()
+ case SP_BASELEVEL: 修正
+ case SP_JOBLEVEL: 修正
+--------------------
+//0974 by latte
+・グランドクロスを本鯖に基づき修正
+ アンデッド悪魔強制暗闇付与
+ 反動ダメージ半減、モーションなし
+ MOB(PC)が重なったときのHIT数
+ %UP系武器カード効果なし
+ 属性相性二重計算
+ MOBダメージ表示白
+
+ 後半4項目は設定可
+
+・戦闘基本計算を本鯖に基づき微修正(DEXサイズ補正、弓最低ダメ、PCサイズ補正)
+
+・完全回避
+ スタン等で完全回避が発生しなかったのを修正
+ AGIVITペナルティが完全回避の敵もカウントして計算されていたのを修正 設定可
+ オートカウンターは未修正
+
+・倉庫を閉じるとき、アイテムIDでソートするようにした
+
+・kalenさんのプロ1執事NPCスクリプトを改造して
+ 商業防衛値、投資金額、宝箱の数を本鯖に準拠(商業値は1~100)
+ 全砦に設置
+
+ (/script/npc)
+ aldeg_cas01.txt ... prtg_cas05.txt 修正
+ (/script/npc/gvg)
+ aldeg_cas01.txt ... prtg_cas05.txt 追加
+ tbox.txt 追加
+
+ (/conf)
+ battle_athena.conf
+ 6項目追加
+
+ (/map)
+ mob.c/mob.h
+ mob_attack() 修正
+ mob_counttargeted_sub(),mob_counttargeted() 修正
+ mobskill_use() 修正(↑の引数だけ)
+ pc.c/pc.h
+ pc_counttargeted_sub(),pc_counttargeted() 修正
+ pc_attack_timer() 修正
+ pet.c
+ pet_attack() 修正
+
+ map.h
+ pc_data, mob_data, map_data 変数1つ追加
+ enum1つ追加
+ map.c/map.h
+ map_count_oncell() 追加 skill.cでよかったかも・・・
+
+ skill.c
+ GX関連修正(skill_additional_effect(), skill_attack(), skill_castend_damage_id(), skill_unit_onplace())
+
+ battle.c 修正
+ battle.h 修正
+
+ storage.c/storage.h
+ storage_comp_item() 追加
+ sortage_sortitem(), sortage_gsortitem() 追加
+ storage_storageclose(), storage_guild_storageclose() 修正
+--------------------
+//0973 by 獅子o^.^o
+・スクリプトのBASELEVEL,JOBLEVEL命令追加
+ 例: set BASELEVEL,1;
+ 例: set JOBLEVEL,1;
+ (map/)
+ pc.c
+ pc_setparam()
+ case SP_BASELEVEL: 項目追加
+ case SP_JOBLEVEL: 項目追加
+
+--------------------
+//0972 by (凸)
+・転生スキルをエフェクトだけいくつか追加したり
+・状態変化はそのうち誰かが
+ エフェクト(ステータス変化アイコン含む)のみ
+ SC_AURABLADE: /* オーラブレード */
+ SC_PARRYING: /* パリイング */
+ SC_CONCENTRATION: /* コンセントレーション */
+ SC_TENSIONRELAX: /* テンションリラックス */
+ SC_BERSERK: /* バーサーク */
+ SC_ASSUMPTIO: /* */
+ SC_TURESIGHT: /* トゥルーサイト */
+ SC_CARTBOOST: /* カートブースト */
+ SC_WINDWALK: /* ウインドウォーク */
+
+ (db/)
+ cast_db.txt
+ (map/)
+ skill.h
+ skill.c
+ SkillStatusChangeTable[] 項目追加
+ skill_castend_nodamage_id() 項目追加
+ skill_status_change_end() 項目追加
+ skill_status_change_start() 項目追加
+
+--------------------
+//0971 by (凸)
+・atcommand.hに残っていたjobchange2とかの残骸を削除
+・転生スキルをエフェクトだけいくつか追加したりチャンピオンはそれなりに追加したり
+ エフェクトのみ
+ ハイプリースト バジリカ(HP_BASILICA)
+ ホワイトスミス カートブースト(WS_CARTBOOST)
+ スナイパー トゥルーサイト(SN_SIGHT)
+ ジプシー 月明りの泉に落ちる花びら(CG_MOONLIT)
+ パラディン ゴスペル(PA_GOSPEL)
+ 追加ダメージ等なし
+ ロードナイト ヘッドクラッシュ(LK_HEADCRUSH)
+ ロードナイト ジョイントビート(LK_JOINTBEAT)
+ ロードナイト スパイラルピアース(LK_SPIRALPIERCE)
+ パラディン プレッシャー(PA_PRESSURE)
+ パラディン サクリファイス(PA_SACRIFICE)
+ それなり(コンボは繋がりますがディレイは適当、ダメージ追加はあるけどそれ以外の追加効果は無し)
+ チャンピオン 猛虎硬派山(CH_PALMSTRIKE)
+ チャンピオン 伏虎拳(CH_TIGERFIST)
+ チャンピオン 連柱崩撃(CH_CHAINCRUSH)
+ チャンピオン 狂気功(CH_SOULCOLLECT)
+
+ (db/)
+ cast_db.txt
+ skill_db.txt
+ skill_require_db.txt
+ (map/)
+ atcommand.h ゴミ削除
+ battle.c
+ battle_calc_pet_weapon_attack() 変更
+ battle_calc_mob_weapon_attack() 変更
+ battle_calc_pc_weapon_attack() 変更
+ clif.c
+ clif_parse_UseSkillToId() 変更
+ skill.c
+ skill_get_unit_id() 項目追加
+ skill_attack() チャンピオンコンボ処理追加
+ skill_castend_damage_id() 変更
+ skill_castend_nodamage_id() 変更
+ skill_castend_id() 変更
+ skill_unitsetting() 変更
+ skill_check_condition() 変更
+ skill_use_id() 変更
+
+--------------------
+//0970 by (凸)
+・ドレイクのウォーターボールが異常に痛い(121発食らう)のでLv5以上の場合は25発に制限
+・シグナムクルシスの計算式を14+SkillLvから10+SkillLv*2変更
+・ソースの気が向いたところに落書き
+・DB関係をまとめて同梱
+
+ (map/)
+ battle.c コメント_〆(。。)カキカキ
+ skill.c skill_status_change_start()
+ (db/)
+ cast_db.txt
+ item_db.txt
+ mob_skill_db.txt
+ skill_db.txt
+ skill_require_db.txt
+ skill_tree.txt
+
+--------------------
+//0969 by ぴざまん
+
+・白刃取り状態で片方が死亡した場合、片方の白刃取りが解除されない問題修正
+・battle_athena.confに項目追加
+ ペット・プレイヤー・モンスターの無属性通常攻撃を属性無しにするか否かを設定できます
+ 詳しくはconf_refを。
+・@コマンド@idsearch実装
+ ロードしたitem_dbから検索語句にマッチするアイテムとIDを羅列するコマンドです
+ 例えば「@idsearch レイ」と入力した場合、ブレイドやレイドリックカード等が引っかかります
+・アシッドテラーとデモンストレーション実装
+ 装備破壊は未実装です
+・イドゥンの林檎の回復仕様を丸ごと変更。
+ (map/)
+ battle.c
+ battle.h
+ 属性補正の修正やアシッドテラー・デモンストレーションのダメージ算出式追加等。
+ battle_configに項目追加
+ skill.c
+ skill_idun_heal()追加。foreachinareaで処理するように変更
+ アシッドテラーとデモンストレーションの処理追加。
+ atcommand.c
+ atcommand.h
+ @idsearch追加。
+
+--------------------
+//0968 by 胡蝶蘭
+
+・キャラクターIDが使いまわされないように修正
+・キャラクター削除時、パーティー、ギルドを脱退するように修正
+・アカウント削除時、キャラクターと倉庫を削除するように修正
+・倉庫/ギルド倉庫削除時、倉庫内のペットを削除するように修正
+ ・注意:ログインしているアカウントを削除した場合の動作は不明
+
+ (char/)
+ char.c
+ パケット2730の処理、char_delete()追加、削除処理修正など
+ int_storage.c/int_party.c/int_guild.c/int_party.h/int_guild.h
+ inter_party_leave(),inter_guild_leave()追加、
+ inter_storage_delete(),inter_guild_storage_delete()修正など
+ (login/)
+ login.c
+ parse_admin()をアカウント削除時にパケット2730を送るように修正
+
+・athena-start stop で停止させた場合、データが保存されない問題を修正
+ killで送るシグナルをSIGKILLからSIGTERMに変更。
+ どうしてもSIGKILLを送りたい場合は athena-start kill を使ってください。
+
+ athena-start
+ stop修正、kill追加
+
+--------------------
+//0967 by Asong
+・モンスターの残影を実装。
+ 通常モンスターはスキルによるフィルターがかからないので残像が出ません。
+ PC型モンスターには残像が出ます。
+・モンスタースキル使用対象を追加。
+ around5〜around8はターゲットの周辺セルを対象にします。
+
+ (map)
+ mob.c
+ mobskill_use() 修正
+ mob_readskill() 修正
+ mob.h 修正
+ skill.c
+ skill_castend_pos2() 修正
+
+--------------------
+//0966 by (凸)
+・サーバーsnapshot
+・ディレクトリ構造を変更(common,login,char,mapは/src以下に移転)
+ それに伴うMakefile等のパス書き換え
+・npc_turtle.txtをnpc_town_alberta.txtに統合
+・モンクのコンボに関するディレイを変更
+・battle_config.enemy_criticalのデフォルトをnoに変更
+・転生職等を無効にするenable_upper_classの追加
+・@joblvup,@charjlvlでJobレベルが最高のときに負数を指定してもレベルを下げられなかった問題を修正
+
+ (conf)
+ battle_athena.conf 修正
+ (doc)
+ conf_ref.txt 修正
+ (map)
+ atcommand.c
+ atcommand_joblevelup() 修正
+ atcommand_character_joblevel() 修正
+ battle.c
+ battle_calc_attack() 修正
+ battle_config_read() 修正
+ battle.h 修正
+ pc.c
+ pc_calc_skilltree() 修正
+ pc_calc_base_job() 修正
+ pc_jobchange() 修正
+ pc_readdb() 修正
+ skill.c
+ skill_attack() 修正
+--------------------
+//0965 by ぴざまん
+・@mapexit実行時全セッションをkickするように変更。
+・白刃取り時に片方が倒れても、もう片方の白刃が解除されない問題修正。(未テスト)
+・スティール情報公開機能実装。(未テスト)
+ スティールに成功すると、何をスティールしたのか
+ 画面内のPTメンバー全員に知らせる機能です。
+ battle_athena.confのshow_steal_in_same_partyで設定できます。
+ オリジナルアップデートの為、デフォルトはnoにしています。
+・イドゥンの林檎の回復効果実装。
+
+ (conf/)
+ battle_athena.confに項目追加。
+ (map/)
+ atcommand.c
+ atcommand_mapexit() 修正。
+ pc.c
+ pc_steal_item() 修正。
+ pc_show_steal() 追加。
+ skill.c
+ skill_unitsetting()、skill_unit_onplace() 修正。
+ battle.c
+ battle_config_read() 修正。
+ battle.h 修正。
+ (doc/)
+ conf_ref.txt 抜けてたのを色々追加。
+
+--------------------
+//0964 by (凸)
+
+・この前追加したskill_tree2.txtを廃止したので削除してください
+・skill_tree.txtのフォーマットを変更&Kalenさんなどの情報を元に転生ツリーの見直し
+・それにともなってpc.cのファイル読み出し部分などを変更
+・Athena雑談スレッド part3 >>14 miyaさんの指摘があるまですっかり忘れていたatcommand_athena.confの修正を同梱
+
+ (conf/)
+ atcommand_athena.conf 修正
+ (db/)
+ skill_tree.txt 修正
+ skill_tree2.txt 廃止
+ (map/)
+ map.h PC_CLASS_BASE等追加
+ pc.c
+ pc_calc_skilltree() 修正
+ pc_allskillup() 修正
+ pc_readdb() 修正
+
+--------------------
+//0963 by (凸)
+
+・@jobchange2, @jobchange3廃止 @jobchangeに引数追加 @help参照
+ 例: @jobchange2 10 → @jobchange 10 1
+・同様に@charjob2, @charjob3廃止 @charjobに引数追加 @help参照
+ 例: @charjob2 10 ほげほげ → @charjob 10 1 ほげほげ
+・同様にスクリプトのjobchange2, jobchange3命令廃止 jobchangeに引数追加 script_ref.txt参照
+ 例: jobchange2 10; → jobchange 10,1;
+・↑どれも追加された引数は省略可能です。なので、転生ノービスは現状のスクリプトで転生一次職に転職できます。
+ 例: Novice High → @jobchange 10 → Whitesmith
+ Novice → @jobchange 10 → Blacksmith
+・スクリプトから転生しているか判定するためにUpperを追加しました。Upper 0=通常, 1=転生, 2=養子
+ Upper=0の時にBaseLevel=99なら転生させる〜とかそういうスクリプト誰か書いてください
+ その時に元の職業は記憶していないので永続変数とかで覚えさせて判定させないと転生後何にでも転職できちゃう?
+・バイオプラントとスフィアマインで呼び出されるMobの名前を--ja--にしてmob_db.txtから読むようにした
+
+ (conf/)
+ help.txt 修正
+ (db/)
+ const.txt
+ (doc/)
+ help.txt 修正
+ script_ref.txt 修正
+ (map/)
+ atcommand.c
+ atcommand_jobchange() 修正
+ atcommand_jobchange2() 削除
+ atcommand_jobchange3() 削除
+ atcommand_character_job() 修正
+ atcommand_character_job2() 削除
+ atcommand_character_job3() 削除
+ map.h 修正
+ pc.c
+ pc_readparam() 修正
+ pc_jobchange() 修正
+ pc.h 修正
+ script.c
+ buildin_jobchange() 修正
+ buildin_jobchange2() 削除
+ buildin_jobchange3() 削除
+ skill.c
+ skill_castend_pos2() 修正
+
+--------------------
+//0962 by (凸)
+
+・職業は0〜23で処理したいので転生職用のスキルツリー追加、eAthenaを参考に拡張
+ っていうか韓国本サーバでの実装の資料が見当たらないので適当
+・sakexe.exeを解析してskill_db.txt変更、これもeAthenaを参考に拡張
+ どれが本サーバで実装されているスキルか分かりませんっ!!
+※スキルツリーが表示されたからといって使えるわけじゃありませんっ!!
+
+ (common/)
+ mmo.h 定数修正
+ (db/)
+ skill_db.txt 変更
+ skill_require_db.txt 変更
+ skill_tree2.txt 追加
+ (map/)
+ skill.h 定数修正
+ pc.c
+ pc_calcstatus() 修正
+ pc_allskillup() 修正
+ pc_calc_skilltree() 修正
+ pc_readdb() 修正
+
+--------------------
+//0961 by 胡蝶蘭
+
+・スクリプトにサブルーチン/ユーザー定義関数機能追加
+ 詳しくはサンプルとscript_ref.txtを読んでください。
+ 地味に大改造なので、スクリプト関係でバグがあるかもしれません。
+
+ (map/)
+ map.h/map.c
+ struct map_session_data にスクリプト情報退避用のメンバ追加
+ map_quit()修正
+ script.h/script.c
+ 色々修正(run_script(),run_func()が主)
+ npc.c
+ npc_parse_function()追加他
+ (conf/sample)
+ npc_test_func.txt
+ ユーザー定義関数/サブルーティンのテストスクリプト
+ (doc/)
+ script_ref.txt
+ サブルーティンなどの説明追加
+
+--------------------
+//0960 by (凸)
+・本鯖相違スレッド part2 >>62 KKさんのアンクルスネア修正を同梱
+・バグ報告スレッド part5 >>14-16 rbさんのバグ修正を同梱
+・For English User Forum >>15 Mugendaiさんの指摘で0x1d7を使うのはVal>255に修正(0xc3のValは1バイトだから0x1d7を使うのかと納得)
+・pc_calc_base_job()を変更して元jobだけでなくノビか一次職か二次職(type)、通常か転生か養子(upper)を返すようにした
+
+ (map/)
+ atcommand.c
+ atcommand_joblevelup() 修正
+ atcommand_character_joblevel() 修正
+ clif.c
+ clif_changelook() 修正
+ pc.h 修正
+ pc.c
+ pc_setrestartvalue() 修正
+ pc_equippoint() 修正
+ pc_isequip() 修正
+ pc_calc_skilltree() 修正
+ pc_calcstatus() 修正
+ pc_isUseitem() 修正
+ pc_calc_base_job() 修正
+ pc_allskillup() 修正
+ pc_damage() 修正
+ pc_jobchange() 修正
+ pc_equipitem() 修正
+ script.c
+ buildin_changesex() 修正
+ skill.c
+ skill_castend_nodamage_id() 修正
+ skill_unit_onplace() 修正
+
+--------------------
+//0959 by (凸)
+・help.txtがdocじゃなくてconfのが読み出されてた_| ̄|○
+・gamejokeを参考に転生二次職のステータス加重値をjob_db2-2.txtに記述
+・スクリプトにjobchange2とjobchange3を追加それぞれ転生職と養子職へ転職させる命令です
+
+ (conf/)
+ help.txt 修正
+ (db/)
+ job_db2.txt 修正
+ job_db2-2.txt 追加
+ (doc/)
+ help.txt 修正
+ script_ref.txt 修正
+ (map/)
+ pc.c
+ pc_calcstatus() 修正
+ pc_readdb() 修正
+ script.c
+ buildin_jobchange() 修正
+ buildin_jobchange2() 追加
+ buildin_jobchange3() 追加
+
+--------------------
+//0958 by (凸)
+・転生職方面の実装を色々
+・重量制限は良く分からないので元の職業の値をそのまま使っています(モンク=チャンピオン等)
+・装備品も同上、HPやSPのテーブルも同上なので、転生してもHPなどが増えないガッカリ仕様です
+
+ (map/)
+ atcommand.c
+ atcommand_joblevelup() 修正
+ atcommand_character_joblevel() 修正
+ pc.c
+ pc_setrestartvalue() 修正
+ pc_equippoint() 修正
+ pc_isequip() 修正
+ pc_calcstatus() 修正
+ pc_isUseitem() 修正
+ pc_calc_base_job() 追加
+ pc_damage() 修正
+ pc_jobchange() 修正
+ pc_equipitem() 修正
+ pc.h 修正
+ script.c
+ buildin_changesex() 修正
+ skill.c
+ skill_castend_nodamage_id() 修正
+
+--------------------
+//0957 by (凸)
+・@charjob2と@charjob3を追加、関係としては@charjob⇔@jobchange、@charjob2⇔@jobchange2、(ry
+・@mapexitを追加、map-serverを落とすコマンドですatcommand_athena.confでは99設定にされてますので使用には十分注意してください。
+
+ (map/)
+ atcommand.c
+ atcommand_character_job2() 追加
+ atcommand_character_job3() 追加
+ atcommand.h 修正
+ (conf/)
+ atcommand_athena.conf 修正
+ (doc/)
+ help.txt 修正
+
+--------------------
+//0956 by (凸)
+・転生職仮実装(@jobchange2)、見た目と経験値テーブルだけです
+・養子職仮実装(@jobchange3)、見た目だけです現状では経験値は転生二次職と同じというマゾ仕様
+※上記2点は転生職が実装されているクライアントでなければ実行するとエラー落ちするので注意!!
+ その後直接セーブデータを弄らないとキャラセレにも行けなくなります!!
+・Athena雑談スレッド part2 >>149 稀枝さんの報告を元にガーディアンを修正
+・砦以外でガーディアンとかエンペリウムを殴るとmap-serverが落ちていたのも修正(未確認)
+・スパノビのJobテーブルはFor English User Forum >>13 kingboさんのデータを元に修正
+・転生職の経験値テーブルはOWNを参照してBase99の経験値は不明だったので適当に設定
+
+ (map/)
+ atcommand.c
+ atcommand_jobchange() 修正
+ atcommand_jobchange2() 追加
+ atcommand_jobchange3() 追加
+ atcommand.h 修正
+ clif.c
+ clif_changelook() 修正
+ pc.c
+ pc_nextbaseexp() 修正
+ pc_nextjobexp() 修正
+ pc_jobchange() 修正
+ pc_readdb() 修正
+ battle.c
+ battle_calc_damage() 修正
+ mob_can_reach() 修正
+
+--------------------
+//0955 by huge
+・ペットのルート機能。
+ ・仕様はmobのルートに近い感じですが、射程を短くしてます。
+ ・拾ったアイテムは、パフォーマンスをすると床に落とします。
+ ・卵に戻したり、ログオフしたときはPCの手元に入るようにしました。(重量超過はドロップ)
+ ・拾える個数はルートmob同様の10個ですが、11個目は拾いに行きません。
+ ・ルート権の問題から、アイテムにfirst_idが入っていて、それが飼い主以外だったら、何秒経とうと拾いません。(未確認)
+ ・それと、荷物持ちにされると可哀想なので、重量制限もつけました。これはconfで設定可能です。
+・atcommandで、@whereがうまく働いてなかったので修正(またウチだけかなぁ・・・)
+・@memoでmemoする時は、mapflagを無視するように。
+・スフィアマインの名前だけ修正。
+
+ (conf/)
+ battle_athena.conf
+ pet_lootitem,pet_weight 追加
+ (doc/)
+ conf_ref.txt 修正
+ (map/)
+ atcommand.c
+ atcommand_memo() 修正
+ atcommand_where() 修正
+ battle.c
+ battle_config_read() 修正
+ battle.h 修正
+ map.c
+ map_quit() 修正
+ map.h
+ pet_data{} 修正
+ pc.c
+ pc_memo() 修正
+ pet.c
+ pet_performance() 修正
+ pet_return_egg() 修正
+ pet_data_init() 修正
+ pet_ai_sub_hard() 修正
+ pet_lootitem_drop() 追加
+ pet_delay_item_drop2() 追加
+ pet_ai_sub_hard_lootsearch() 追加
+ pet.h 修正
+ skill.c
+ skill_castend_pos2() 修正
+
+--------------------
+//0954 by (凸)
+・object_del.batで各server.exeも削除するようにした
+・For English User Forum >>11 kingboさんの修正を取り込み
+・バグ報告スレッド part5 >>10 Sinさんの修正を取り込み
+・ついでに見かけたatcommand_character_joblevelの不具合を修正
+・@コマンドでジョブレベルを上げるときにスパノビはJob70まで対応(未確認)
+
+ (/)
+ object_del.bat 修正
+ (map/)
+ atcommand.c
+ atcommand_joblevelup() 修正
+ atcommand_character_joblevel() 修正
+ atcommand_character_baselevel() 修正
+
+ code by kingbo 2004/4/29 PM 06:15
+ base on 0953
+ now i sure it works well
+ (map/)
+ mob.c
+ mob_can_reach() fix
+
+--------------------
+//0953 by (凸)
+・mob_skill_db.txtの条件値に0以外入っていなかったのを訂正
+・gcc 2.95でコンパイルできるように訂正(by バグスレpart5 >>2 茜さん)
+・↑やLinuxなどを考慮してstartやMakefileなどの改行をLFに変更
+・0952で出たコンパイル警告を出ないように修正
+・0952で更新されなかったconf_ref.txtを修正
+
+ (/)
+ start 改行コード変更
+ athena-start 改行コード変更
+ (db/)
+ mob_skill_db.txt 修正
+ (doc/)
+ conf_ref.txt 修正
+ (login/)
+ Makefile 改行コード変更
+ (map/)
+ Makefile 改行コード変更
+ atcommand.c
+ atcommand() 宣言位置修正
+ atcommand_where() 宣言位置修正
+ battle.c
+ battle_calc_pet_weapon_attack() 修正
+ battle_calc_mob_weapon_attack() 修正
+ battle_calc_pc_weapon_attack() 修正
+ battle_calc_magic_attack() 修正
+ clif.c
+ clif_skill_fail() 宣言位置修正
+ guild.c
+ guild_gvg_eliminate_timer() 宣言位置修正
+ mob.c
+ mob_damage() 宣言位置修正
+ script.c
+ buildin_deletearray() 宣言位置修正
+ buildin_getequipcardcnt() 宣言位置修正
+ buildin_successremovecards() 宣言位置修正
+
+--------------------
+//0952 by CG
+・confでDEFとMDEFの計算方法を選択できるように。
+
+ (conf/)
+ battle_athena.conf 変更
+ (map/)
+ battle.c
+ battle_calc_pet_weapon_attack() 修正
+ battle_calc_mob_weapon_attack() 修正
+ battle_calc_pc_weapon_attack() 修正
+ battle_calc_magic_attack() 修正
+ battle.h 修正
+
+--------------------
+//0951 by (凸)
+・サーバーsnapshot
+・バグ報告スレッド part4 >>95 KAJIKENさんの修正を同梱
+・同 >>138 バグかな?さんの修正を同梱
+・Athena雑談スレッド part2 >>112 名無しさんのPVPナイトメアモードのアンダークロスマップワープポイントを同梱
+・同 >>96 稀枝さんのnpc_gldcheck.txtを同梱
+・スナップショットにsave/を入れるのをやめました。無い場合はathena-startが作ってくれます
+・athena-startでlog/が無い場合に作るように変更
+・その他?
+
+ (/)
+ athena-start 変更
+ (db/)
+ mob_db.txt 変更
+ (conf/)
+ map_athena.conf 変更
+ (conf/extension/)
+ npc_gldcheck.txt 追加
+ (conf/npc/)
+ npc_event_ice.txt 変更
+ npc_job_alchemist.txt 変更
+ npc_event_valentine.txt 変更
+ npc_town_geffen.txt 変更
+ npc_event_whiteday.txt 変更
+ npc_event_potion.txt 変更
+ npc_town_comodo.txt 変更
+ (conf/warp/)
+ npc_warp_pvp.txt 追加
+
+--------------------
+//0950 by (凸)
+・mob_dbのModeフラグに以下の物を追加
+ 0x40(64) ダメージを1に固定(草やクリスタルなど)
+ 0x80(128) 攻撃を受けたときに反撃をする
+・上記の変更のためmob_db.txtほぼ全部変更、mob_db2.txtを作っている人は
+ 草など1ダメ固定にはModeに64を足さないと普通にダメージ
+ その他MobはModeに128を足さないと反撃してこなくなるので注意
+
+ (db/)
+ mob_db.txt 修正
+ (map/)
+ battle.c
+ battle_calc_pet_weapon_attack()
+ battle_calc_mob_weapon_attack()
+ battle_calc_pc_weapon_attack()
+ battle_calc_magic_attack()
+ mob.c
+ mob_once_spawn()
+ mob_attack()
+ mob_target()
+ mob_ai_sub_hard()
+
+--------------------
+//0949 by ぴざまん
+
+・ステータス異常耐性全面修正。
+ 耐性算出式全面修正。
+ ステータス異常耐性100%のキャラクターには状態異常を行わないように修正。
+・ディスペルの仕様変更。
+ 解除したらシステム上問題のあるステータス変化以外片っ端から解除するように修正。
+・フロストダイバーの仕様変更。
+ 凍結率修正(マジスレテンプレ準拠)。
+・リカバリーの仕様変更。
+ ノンアクティブモンスターに使用するとターゲットがリセットされるように修正。
+・クァグマイアの仕様がアレだったので修正。
+ 演奏や属性場と同様にrangeで処理するように修正。
+ DEX/AGI半減の影響が詠唱以外にも及ぶ様に修正。
+・スキルターゲット中に死んだ振りを使用してもスキルが回避できない問題修正。
+・白刃取りが動作しない問題修正(cast_dbが抜けてました)。
+ (map/)
+ pc.c
+ pc_calcstatus()修正
+ skill.c
+ skill_additional_effect()、skill_attack() 修正
+ skill_status_change_start()、skill_unitsetting() 修正
+ (db/)
+ cast_db.txt 修正。
+
+--------------------
+//0948 by 胡蝶蘭
+
+・warpwaitingpcが正しくPCを転送できない問題を修正
+・スクリプトの読み込み時にエラーまたは警告が出る場合、警告音を鳴らすように。
+ (流れたログを見ない人対策です)
+
+ (map/)
+ script.c
+ buildin_warpwaitingpc()修正
+ disp_error_message()修正
+
+・atcommand.c修正
+ ・atcommand_athena.confのmapmoveを読むように
+ ・@strなどの省略時の必須レベルを0に。
+ ・@paramは使わないのでコメント化
+
+ (map/)
+ atcommand.c
+ 該当個所修正
+
+・mobが最大15秒ほど移動しない場合がある問題修正
+ ・手抜きでないmob処理で、移動しない時間が7秒以上続かないように修正
+
+ (map/)
+ mob.c
+ mob_ai_sub_hard()修正
+
+・快速船員の伊豆港行きの判別式修正 (by ID:F8nKKuY)
+ (conf/npc/)
+ npc_town_comodo.txt
+
+--------------------
+//0947 by (凸)
+・取り巻きは取り巻きを召喚しないように修正
+・露天の販売価格の上限をbattle_athena.confで設定できるように修正
+
+ (conf/)
+ battle_athena.conf
+ vending_max_value追加
+ (doc/)
+ conf_ref.txt 修正
+ (map/)
+ skill.c
+ skill_castend_nodamage_id() 修正
+ battle.c
+ battle_config_read() 修正
+ battle.h 修正。
+ vending.c
+ vending_openvending() 修正。
+
+--------------------
+//0946 by Kalen
+・プロ北Warp見直し
+参考:本鯖(1F,2F)らぐなの何か(3F)
+ (conf/warp/)
+ npc_warp.txt
+
+・語り部の2週3週追加(どうせ見ないと思いますが…)
+ (conf/npc/)
+ npc_event_kataribe.txt
+
+--------------------
+//0945 by 胡蝶蘭
+
+・NPCタイマーラベルデータが正しくインポートされない問題を修正
+・NPCタイマー初期値やタイマーIDが正しく初期化されない問題を修正
+・NPCのduplicateを行うとアクセス違反が起こる場合がある問題を修正
+
+ (map/)
+ npc.c
+ npc_parse_script修正
+
+・パッチアップスレ4の87のpc.cとりこみ
+ (map/)
+ pc.c
+ カード重量制限を元に戻したもの
+
+--------------------
+//0944 by huge
+・ギルドの上納経験値の上限を、confで制限できるように。
+・露店の販売価格を10Mまでに制限。
+・カートの重量制限が一桁下がってたんですが、ウチだけですか?修正してみましたが。
+
+ (conf/)
+ battle_athena.conf
+ guild_exp_limit追加
+ (doc/)
+ conf_ref.txt 修正
+ (map/)
+ atcommand.c
+ 蘇生時のSP回復で、細かい修正。
+ battle.c
+ battle_config_read() 修正
+ battle.h 修正。
+ guild.c
+ guild_change_position() 修正。
+ pc.c
+ pc_calcstatus() 修正。
+ vending.c
+ vending_openvending() 修正。
+
+--------------------
+//0943 by (凸)
+・battle_athena.confでdead_branch_activeをyesにすると古木の枝で召喚されるモンスターがアクティブになるように変更
+・微妙に変更したclient_packet.txtを同梱
+
+ (conf/)
+ battle_athena.conf
+ dead_branch_active追加
+ (doc/)
+ client_packet.txt 修正
+ conf_ref.txt 修正
+ (map/)
+ battle.c
+ battle_config_read() 修正
+ battle.h 修正
+ map.h 修正
+ mob.c
+ mob_once_spawn() 修正
+ mob_attack() 修正
+ mob_target() 修正
+ mob_ai_sub_hard_lootsearch() 修正
+ mob_ai_sub_hard() 修正
+
+・英語スレのkingboさんの変更を同梱
+ code by kingbo 2004/4/16 PM 09:47
+
+ support guildcastle guardian
+ maybe still have problems..need to try
+ Good Luck Q^^Q
+ P.S: sorry my poor english ^^a
+
+ (map/)
+ mob.c
+ mob_can_reach() fix
+ battle.c
+ battle_calc_damage() fix
+
+ (conf/gvg/)
+ prtg_cas01_guardian.txt
+
+--------------------
+//0942 by 胡蝶蘭
+
+・アクセスコントロールで不正なメモリにアクセスする場合があるバグを修正
+ (login/)
+ login.c
+ check_ipmask()修正
+
+・スクリプトリファレンス少し追加と修正
+ (doc/)
+ script_ref.txt
+ 修正
+
+--------------------
+//0941 by (凸)
+
+・e2さんの報告を元に召喚された手下のスピードを召喚主と同じにしてみる
+
+ (map/)
+ battle.c
+ battle_get_speed() 修正
+ mob.c
+ mob_spawn() 修正
+ mob_summonslave() 修正
+
+--------------------
+//0940 by End_of_exam
+
+・ヒールやポーションピッチャーを使用しても回復しないバグを修正(0938〜)。
+
+ Thanks for Pepermint, reporting the bug that using PotionPitcher with
+ BluePotion was no effective.
+ (=ポーションピッチャー+青Pで効果がない事を報告してくれたPepermint氏に感謝)
+
+ (map/)
+ battle.c
+ battle_heal() 修正
+
+--------------------
+//0939 by (凸)
+・cutinパケットを0x145(ファイル名16文字)から0x1b3(64文字)に変更
+・ついでに雑談スレに上げたathena-startを同梱
+
+ (/)
+ athena-start saveファイルが無いときに作るように
+ (map/)
+ clif.c
+ clif_cutin() 本鯖パケット準拠に変更
+ (doc/)
+ client_packet.txt 修正
+
+--------------------
+//0938 by ぴざまん
+
+・ポーションピッチャーで青ポを投げてもエフェクトだけだったバグ修正。
+・露店開設が特定のアイテム配置で失敗するバグ修正。
+・スクリプト関数getareadropitem実装。
+ 指定エリア内のドロップアイテムをカウントする関数です
+
+ 書式:getareadropitem <mapname>,<x0>,<y0>,<x1>,<y1>,<item>;
+ mapname:対象マップ名(例:prontera.gat)
+ x0とx1:対象X座標範囲
+ y0とy1:対象Y座標範囲
+ item:カウントする対象アイテム
+
+ 戻り値:mapname内座標(x0,y0)-(x1,y1)の範囲内に落ちているitemの総個数
+ 取得失敗時には-1を返します。
+ ・itemの値はIDでもアイテム名("Red_Jemstone"とか)でもいいです。
+
+ (map/)
+ battle.c
+ battle_heal() 修正。
+ vending.c
+ vending_openvending() 修正。
+ script.c
+ ローカルプロトタイプ宣言修正。
+ struct buildin_func[] 修正。
+ buildin_getareadropitem()、buildin_getareadropitem_sub() 追加。
+
+--------------------
+//0937 by netwarrior
+
+- Fix Japanese remarks problem in 0936
+- Fix minor problem in battle_heal()
+
+--------------------
+//0936 by Pepermint
+
+Retouch about problem of increase in quantity at the CART,
+when enter the an minus quantity in the CLIENT.
+
+Retouch about problem of not recovery,use POTIONPITCHER skill.
+
+ (map/)
+ battle.c
+ battle_heal()
+
+ vending.c
+ vending_purchasereq()
+
+--------------------
+//0935 by 胡蝶蘭
+
+・内容の同じスクリプトNPCを何度も記述しなくても言いように修正
+ ・NPC定義の"script"と書く部分を"duplicate(NPC名)"とすると、
+ 該当のNPCとスクリプトを共有するように。NPC名は表示名ではなく
+ エクスポートされる名前を指定します。
+ <例>
+prontera.gat,165,195,1 duplicate(カプラ職員) カプラ職員2 112
+
+ ・共有元のNPCは同じマップサーバーに存在する必要があるため、
+ 同じマップでない場合はduplicateすべきではない。
+ ただし、NPCの位置を"-"にすることで、マップ上には存在しないが、
+ マップサーバー内には存在するNPCを作成できるので、
+ そのNPCを共有元にするのであればどのマップへも共有できる。
+ <例>
+
+- script テスト::test1 112,{ // このNPCグラフィックIDは使用しない
+// (略)
+}
+prontera.gat,165,195,1 duplicate(test1) テスト2 112
+geffen.gat,99,99,1 duplicate(test1) テスト3 112
+
+ ・上のマップに存在しないNPCはイベントにもすることができる。
+ (どのマップサーバーからでも必ず呼び出せるイベントになる)
+
+ (map/)
+ map.h
+ struct npc_label_list追加,struct npc_data修正
+ npc.c
+ npc_parse_script()修正
+ 不要になったラベルデータベース関連の関数を削除
+
+--------------------
+//0934 by ぴざまん
+
+・MOBの状態異常耐性がやたら高かったのを修正。
+・速度減少の仕様変更(成功率計算式変更・失敗時にはエフェクト無し)。
+・何時の間にか状態異常の継ぎ足し禁止がコメントアウトされていたので戻し。
+・ポイズンリアクトのアイコン表示が無くなっていたので修正(でも出るだけ…)
+・白刃取り実装。
+
+ (map/)
+ battle.c
+ battle_weapon_attack() 修正。
+ clif.c
+ clif_parse_WalkToXY()、clif_parse_ActionRequest() 修正。
+ clif_parse_TakeItem()、clif_parse_UseItem() 修正。
+ clif_parse_DropItem()、clif_parse_EquipItem() 修正。
+ clif_parse_UnequipItem() 修正。
+ mob.c
+ mob_can_move()、mob_attack()、mob_ai_sub_hard() 修正。
+ mobskill_use_id()、mobskill_use_pos() 修正。
+ mobskill_castend_id()、mobskill_castend_pos() 修正。
+ pc.c
+ pc_attack_timer()、pc_setpos() 修正。
+ skill.c
+ SkillStatusChangeTable[] 修正。
+ skill_additional_effect()、skill_castend_damage_id() 修正。
+ skill_castend_nodamage_id()、skill_check_condition() 修正。
+ skill_status_change_start()、skill_status_change_end() 修正。
+ skill_use_id() 修正。
+ skill.h 修正。
+ (db/)
+ cast_db.txt 修正。
+
+--------------------
+//0933 by 胡蝶蘭
+
+・ウィザード転職所NPC仮実装
+ ・eathenaのデータの翻訳、および某所の昔のデータを元に作ったので
+ 癌鯖とは微妙に異なってると思います。
+ 「古い巻物」とか使えませんし。
+ ・現行のjob_2nd.txtと一緒に読み込むと、NPCが重なるので、
+ job_2nd.txtの該当スクリプトをコメント化してください。
+ ただし、その場合はセージに転職させてもらえないので注意してください。
+
+ (conf/npc/)
+ npc_job_wizard.txt
+ 追加
+ (conf/warp/)
+ npc_warp_job.txt
+ ウィザード転職所のワープをコメント化
+
+・バグ修正
+ ・gotoやmenuでラベルが見つからないときスクリプトの実行を中断するように.
+ ・1回のスクリプトの実行において、実行命令数が約8000を超えるか、
+ gotoやmenuの実行回数が約500回を超えると実行を中断するように。
+ ・関数/命令実行時にもエラーチェックを入れた
+ ・ギルド/パーティ/ペットの名前に日本語が使えない問題修正
+
+
+ (char/)
+ int_guild.c/int_party.c
+ 名前問題修正
+ (map/)
+ pet.c
+ 名前問題修正
+ script.c
+ 修正
+
+--------------------
+//0932 by End_of_exam
+
+・0930でギルド倉庫の中身が消えるバグを修正。
+
+ (char/)
+ int_storage.c guild_storage_tostr() 修正。
+
+--------------------
+//0931 by (凸)
+・サーバーsnapshot
+・AthenaDB計画から更新のあった物を反映
+・atcommand_athena.confにstr等を追加
+・ワープポータルの本鯖相違点を修正
+・霧さんのaldeg_cas03〜05を同梱
+・pさんのnpc_event_kataribe.txt同梱
+・KAJIKENさんのnpc_warp_louyang.txt同梱
+
+ (db/)
+ item_db.txt 変更
+ (conf/)
+ atcommand_athena.conf 変更
+ map_athena.conf 変更
+ (conf/gvg/)
+ aldeg_cas03.txt 変更
+ aldeg_cas04.txt 変更
+ aldeg_cas05.txt 変更
+ (conf/mob/)
+ npc_monster.txt 変更
+ (conf/npc/)
+ npc_event_kataribe.txt 追加
+ (conf/warp/)
+ npc_warp_louyang.txt 追加
+ (map/)
+ pc.c pc_memo() 変更
+
+--------------------
+//0930 by 胡蝶蘭
+
+ 既存の char/ にあるlock.cとlock.hは削除してください。
+ これらは common/ に移動されます。
+
+・スクリプト追加修正
+ ・setarray(配列へリスト代入)追加
+ ・cleararray(配列を指定値でクリア)追加
+ ・copyarray(配列をコピー)追加
+ ・getarraysize(配列の有効な要素数を求める)追加
+ ・deletearray(配列の要素を削除してつめる)追加
+ ・warpwaitingpc:人数やアカウントIDをマップ変数にセットするように修正
+
+ (map/)
+ script.c
+ 色々
+ (doc/)
+ script_ref.txt
+ 命令追加など
+ (npc/sample/)
+ npc_test_array.txt
+ 配列系テスト用NPCスクリプト
+
+・バグ修正など
+ ・キャラクター名/アカウント名/パーティ名/ギルド名/ペット名に
+ コントロールコードを使えないように修正。
+ ・char.cなどの保存の効率を更にアップ。
+ ・倉庫ファイル/ギルド倉庫ファイルに空行が残らないように修正
+ ・lock.*をcommon/に移動、ファイルが保存できなくなるバグ修正、
+ アカウントとマップ変数の保存にもlock_fopenを使うように。
+
+ (common/)
+ lock.c/lock.h
+ フォルダ移動、lock_fopen修正
+ (login/)
+ login.c
+ mmo_auth_new()修正
+ (char/)
+ char.c
+ mmo_char_tostr(),make_new_char()修正
+ int_storage.c
+ storage_tostr(),guild_storage_tostr(),
+ inter_storage_save_sub(),inter_guild_storage_save_sub()修正
+ int_party.c
+ int_guild.c
+ 名前問題修正
+ (map/)
+ pet.c
+ 名前問題修正
+
+--------------------
+//0929 by ぴざまん
+
+・ランドプロテクター完全実装。
+・プロボックのスキル仕様変更(詠唱妨害・凍結、石化、睡眠解除)。
+・リカバリーのスキル仕様変更(アンデッドに使用すると一定確率で暗闇効果)。
+・状態異常中でも装備の切り替えが出来るように変更。
+・アイテム自動取得中に@killmonsterを使用すると落ちるバグ修正。
+・胡蝶蘭さんが上げていたnpc.cを同梱しました。
+・skill_dbで気になってたとこをちょこっと修正。
+
+ (map/)
+ clif.c
+ clif_parse_EquipItem() 修正。
+ skill.c
+ skill_castend_nodamage_id() 修正。
+ skill_unitsetting()、skill_unit_onplace() 修正。
+ skill_landprotector() 追加。
+ mob.c
+ mob_delay_item_drop()、mob_delay_item_drop2() 修正。
+ npc.c 修正
+
+ (db/)
+ skill_db.txt 修正。
+
+--------------------
+//0928 by End_of_exam
+
+・キャラや倉庫内アイテムが消える問題に暫定対処(キャラ鯖の改良)。
+
+ 1.キャラデータ・倉庫データのデータ変換処理を改良。
+ (char/char.c , char/int_storage.c)
+
+ 2.ファイル書き出しが終わるまで、旧ファイルを残すように修正。
+ (char/lock.c, char/lock.h の追加。メイクファイルの修正。
+ char/int_storage.c,int_guild.c,int_party.c,int_pet.c,char.c,inter.c
+ 内にある、データ書き出し処理を変更。)
+
+--------------------
+//0927 by ぴざまん
+
+・武器攻撃以外に種族補正が入っていなかったのを修正。
+・演奏中に楽器・鞭以外の武器に持ち変えると演奏が止まる様に修正。
+・演奏の効果が演奏者自身にはかからないように修正。
+・アイテム自動取得機能実装
+ 敵を倒した時に、アイテムがドロップされるのではなく、その敵に一番多くのダメージを
+ 与えた人(ルート権1位の人)にドロップアイテムが自動で与えられる機能です。
+ battle_athena.confのitem_auto_getで設定できます。
+ オリジナルアップデートの為、battle_athena.confでのデフォルトではnoにしています。
+・属性場(デリュージ・バイオレントゲイル・ボルケーノ・ランドプロテクター)仮実装
+ 一部の機能が未実装です
+ デリュージ:水場を用いたウォーターボール
+ バイオレントゲイル:ファイアーウォールの持続時間補正
+ ボルケーノ:アイスウォール禁止
+ ランドプロテクター:オブジェクト設置系スキル禁止(つまり出るだけのLPです)
+
+ (map/)
+ battle.c
+ battle_calc_damage()、battle_calc_magic_attack() 修正。
+ battle_calc_misc_attack()、battle_config_read() 修正。
+ mob.c
+ mob_delay_item_drop()、mob_delay_item_drop2() 修正。
+ pc.c
+ pc_calcstatus()、pc_equipitem() 修正。
+ skill.c
+ ローカルプロトタイプ宣言修正。
+ SkillStatusChangeTable[] 修正。
+ skill_castend_nodamage_id()、skill_castend_pos2() 修正。
+ skill_unitsetting()、skill_unit_onplace() 修正。
+ skill_unit_onout()、skill_unit_ondelete() 修正。
+ skill_status_change_start()、skill_status_change_end() 修正。
+ skill_clear_element_field() 追加。
+ battle.h 修正。
+ skill.h 修正。
+ (conf/)
+ battle_athena.conf
+ item_auto_get項目を追加。
+ (db/)
+ cast_db.txt 修正。
+
+--------------------
+//0926 by 胡蝶蘭
+
+・ギルド城の初期化方法変更
+ ・全てのギルド城(+占拠ギルド情報)を所得したときにOnAgitInitが
+ よばれるように。GetCastleDataやRequestGuildInfoの必要がなくなります。
+ 従って、現在のgvgスクリプトのOnInterIfInitやOnRecvCastleXXXは
+ 必要なくなります。(おそらく現行のスクリプトも動作は可能です)
+ 初期化が必要なNPCは変わりにOnAgitInitを使ってください。
+ なお、不要になった命令などを削除する予定はありません。
+
+ (char/)
+ inter.c/inter.h/char.c/int_guild.c/int_guild.h
+ マップサーバー接続時に処理を行えるように修正
+ 接続時にギルド城データを送信するように
+ (map/)
+ intif.c/guild.c/guild.h
+ 接続時のギルド城データ一括受信処理&ギルド情報要求、
+ OnAgitInitの呼び出しなど
+ chrif.c
+ 旧OnAgitInitの呼び出し削除
+ (doc/)
+ inter_server_packet.txt
+ ギルド城データ一括送信パケット0x3842追加
+
+・スクリプトにNPC主体イベント実行命令追加
+ ・donpcevent(NPC主体イベント実行)追加
+ こちらはdoeventと違いブロードキャスト可能です。詳細はscript_ref。
+ ・isloggedinのコンパイル警告がでないように修正
+ (map/)
+ script.c
+ buildin_donpcevent()追加など
+ (doc/)
+ script_ref.txt
+ doevent,donpcevent,OnAgitInit追加など
+
+・その他修正
+ (map/)
+ clif.c
+ clif_parse_GMReqNoChat()追加
+
+
+--------------------
+//0925 by 胡蝶蘭
+
+・スクリプトのバグ修正
+ ・monster,areamonsterの問題修正
+ これらはscript_rid2sdを使用しないようにしました。
+ sdがNULLのとき、レベルチェックを行わないようにしました。
+
+ (map/)
+ script.c/mob.c
+ buildin_monster(),buildin_areamonster(),mob_once_spawn()他
+
+・スクリプトの機能追加
+ ・detachrid命令(プレイヤーのデタッチ)追加
+ ・isloggedin関数(プレイヤーのログインチェック)追加
+ ・getitem,getitem2命令,countitem,checkweight関数で
+ nameidにアイテムの名前を指定可能に。
+ (item_dbなどに依存するため、使用すべきではありませんが、一応)
+
+ (map/)
+ script.c
+ 色々
+ (doc/)
+ script_ref.txt
+ 変更部分とエラー説明修正
+
+・athena-startとstartを修正
+ ・athena-start stopでプロセスが終了するまで待つように
+ ・startで再起動させるときathena-start restartを呼ぶように。
+
+--------------------
+//0924 by (凸)
+
+・バグ報告スレッド part4 >>66のnpcを取り込み
+・同 >>51のguild.cを反映
+・同 >>38のatcommand_athena.confへの変更を取り込み
+・なぜかnpcフォルダにあってmap_athena.confないファイルを一覧に追加。ついでにギルドフラッグのコメントアウトを解除
+・conf/npc/npc_job_1st.txt npc_script2.txt npc_shop3.txtはスナップショットから削除してください
+
+ (map/)
+ guild.c 修正
+ (conf/)
+ map_athena.conf 修正
+ atcommand_athena.conf 修正
+ (conf/npc/)
+ npc_event_hat.txt 修正
+ npc_event_oni.txt 修正
+ npc_job_1st.txt 削除
+ npc_script2.txt 削除
+ npc_shop3.txt 削除
+
+--------------------
+//0923 by 胡蝶蘭
+
+・スクリプトのバグ修正
+・script_rid2sdが失敗してもサーバーを終了しないように変更
+ ・monster,areamonsterなどが実行できない問題修正
+
+ (map/)
+ script.c/mob.c
+ buildin_monster()など修正
+
+ (doc/)
+ script_ref.txt
+ ラベルとエラーの説明修正
+
+--------------------
+//0922 by 胡蝶蘭
+
+・スクリプトエラーの行番号が正しく表示されるように
+
+ (map/)
+ itemdb.c / npc.c
+ itemdb_readdb(),npc_parse_script()修正
+
+・キャラクター情報にアクセスできない状態でアクセスするとエラーを出すように修正
+・goto/menuでラベルが指定される場所にラベル以外が指定されると警告を出すように
+・script_refにエラーメッセージの説明追加
+・イベント起動されたスクリプトでキャラクターを使用できるようにする関数追加
+ ・attachrid(指定したIDの情報を使用できるようにする)追加
+ ・getcharid(3でアカウントIDを所得できるように)修正
+ (map/)
+ script.c
+ script_rid2sd(),buildin_attachrid()追加
+ 多々修正。
+ (doc/)
+ script_ref.txt
+ エラーメッセージの説明追加、変数/ラベルの説明修正
+ 他修正
+
+--------------------
+//0921 by RR
+・スクリプトバグ修正(ご迷惑をおかけしました)
+ (conf/npc/)
+ npc_event_tougijou.txt
+
+・steal率修正(自DEX - 敵DEX + SLv*3 +10の部分で一度判定をしていたので)
+・0918で0914以前に巻き戻ってしまっていた部分を元に戻した
+ (map/)
+ pc.c
+ pc_steal_item() 修正
+ itemdb.c
+ pet.c
+ skill.c 修正
+
+--------------------
+//0920 by 獅子o^.^o
+・ Steal率 = Drop率 * (自DEX - 敵DEX + SLv*3 +10) /100
+ (map/)
+ pc.c
+ int pc_steal_item()修正
+
+--------------------
+//0919 by RR
+・atcommandのlvupを使うと取得ステータスポイントがおかしい問題の修正
+・バグ修正(バグ報告スレで修正の出たものの取り込み 胡蝶蘭さん、pさん、共にお疲れ様です)
+ (map/)
+ atcommand.c
+ atcommand_baselevelup()修正
+ guild.c
+ guild_gvg_eliminate_timer()修正
+ pc.c
+ pc_setreg(),pc_setregstr()修正
+ (login/)
+ login.c
+ parse_login()修正
+--------------------
+//0918 by 聖
+・item_db自体がオーバーライド可能になったので、class_equip_db.txtの廃止。
+・pet_db.txt、produce_db.txtもオーバーライド可能に修正。
+ (map/)
+ itemdb.c
+ do_init_itemdb() 修正。
+ pet.c
+ read_petdb() 修正。
+ skill.c
+ skill_readdb() 修正。
+
+--------------------
+//0917 by RR
+・スクリプト修正
+ 桃太郎イベントと闘技場イベントをNPCタイマーに変更
+ (conf/npc/)
+ npc_event_momotarou.txt
+ npc_event_tougijou.txt 修正
+・スキルツリー修正(バグ報告スレ25より)
+ (db/)
+ skill_db.txt 修正
+
+--------------------
+//0916 by (凸)
+・npc_monsterにnpc_mob_jobを統合。npc_mob_job.txtは削除してかまいません
+
+ (conf/)
+ map_athena.conf npc_mob_jobを削除
+ (conf/mob/)
+ npc_monster.txt 更新
+
+--------------------
+//0915 by 胡蝶蘭
+
+・NPCタイマー関係の命令追加&修正他
+ ・delwaitingroom(NPCチャット終了)引数を見てなかったので修正
+ ・initnpctimer(NPCタイマー初期化)追加
+ ・stopnpctimer(NPCタイマー停止)追加
+ ・startnpctimer(NPCタイマー開始)追加
+ ・getnpctimer(NPCタイマー情報所得)追加
+ ・setnpctimer(NPCタイマー値設定)追加
+
+ 既存のaddtimerなどはプレイヤー単位のため、NPC単位のタイマーを作りました。
+ こちらは、addtimerなどとは違い、OnTimerXXXという風にラベルを指定します。
+ 詳しくはサンプルとscrit_ref.txtを参照。
+
+ (map/)
+ map.h
+ struct npc_data 修正、struct npc_timerevent_list追加
+ npc.c / npc.h
+ npc_timerevent(),npc_timerevent_start(),npc_timerevent_stop(),
+ npc_gettimerevent_tick(),npc_settimerevent_tick()追加
+ npc_parse_script()修正
+ script.c
+ buildin_*npctimer()追加など
+ (conf/sample/)
+ npc_test_npctimer.txt
+ NPCタイマー使用サンプル
+ (doc/)
+ script_ref.txt
+ NPCタイマー関係の命令/関数追加、定数ラベルの説明修正
+
+・Sageのアーススパイクの所得条件修正
+ (db/)
+ skill_tree.txt
+ アーススパイクの行(サイズミックウェポンをLv1に)
+
+--------------------
+//0914 by p
+・範囲スキル使用時に解放済みメモリを参照していた問題に対応
+・メモリを初期化せずに使用していた領域を、初期化してから使用するように変更
+ (common/)
+ db.c
+ grfio.c
+ socket.c
+ timer.c
+ (char/)
+ char.c
+ int_guild.c
+ int_party.c
+ int_pet.c
+ int_storage.c
+ inter.c
+ (login/)
+ login.c
+ (map/)
+ ほとんど.c
+
+--------------------
+//0913 by Kalen
+
+・GVGScriptの修正
+ 911対応
+ フラグからアジトへ戻る機能追加
+ 戻るときに聞かれるように修正(TESTscript)
+ 砦取得時::OnRecvCastleXXXを発動するように修正
+ (conf/gvg/)
+ ほとんど.txt
+
+--------------------
+//0912 by (凸)
+・このファイルの文字化けとTEST_prtg_cas01_AbraiJの文字化けを修正
+・バグ報告スレの>>19-20を取り込み
+・昔やっちまったbattle_athena.confの誤字の訂正
+
+ (common)
+ mmo.h
+ #define MAX_STAR 3に修正
+ (conf)
+ battle_athena.conf
+ (conf/gvg/)
+ TEST_prtg_cas01_AbraiJ.txt
+ (map)
+ atcommand.c
+ get_atcommandinfo_byname() 修正
+
+
+--------------------
+//0911 by Michael_Huang
+
+ Mounting Emblem of the Flag-NPC.
+ (Added Script Command: FlagEmblem).
+
+(conf/gvg/)
+ TEST_prtg_cas01_AbraiJ.txt (FlagEmblem Test)
+
+ (map/)
+ map.h struct npc_data{}
+ clif.c clif_npc0078()
+ script.c buildin_flagemblem()
+
+--------------------
+//0910 by RR
+・スクリプトの間違いを修正
+(conf/gvg/)
+ ev_agit_payg.txt
+ ev_agit_gefg.txt
+
+・ひな祭りに一度入ったらマップ変数が残ったままになるので、マップ変数を使わないよう変更
+(一時的マップ変数にすれば問題ないとも言えますが、
+town_guideとtown_kafraに時期限定の物が常駐してしまうのが気になったので、
+それらをevent_hinamatsuriへ移動し、普段のをdisableしています)
+ (conf/npc/)
+ npc_event_hinamatsuri
+ npc_town_guide
+ npc_town_kafra
+
+・スキルリセット時のスキル取得制限判定をスキルポイント48以上消費から、
+ スキルポイント58以上消費か残りスキルポイントがJOBLEVELより小さくなったときに変更
+・@model時の服色染色制限を緩和(男アサ、ローグのみへ)
+ (map/)
+ pc.c pc_calc_skilltree()
+ atcommand.c atcommand_model()
+
+
+--------------------
+//0909 by 胡蝶蘭
+
+・NPCチャット関係の命令追加
+ ・waitingroom(NPCチャット作成)修正(イベントを起こす人数を指定可能)
+ ・delwaitingroom(NPCチャット終了)追加
+ ・enablewaitingroomevent(NPCチャットイベント有効化)追加
+ ・disablewaitingroomevent(NPCチャットイベント無効化)追加
+ ・getwaitingroomstate(NPCチャット状態所得)追加
+ ・warpwaitingpc(NPCチャットメンバーワープ)修正
+ 詳しくはscript_ref.txtを参照
+
+ (map/)
+ script.c/npc.c/npc.h/chat.c/chat.h/clif.c
+ 多々修正
+ (doc/)
+ script_ref.txt
+ 修正
+ (conf/sample/)
+ npc_test_chat.txt
+ 追加命令のテストスクリプト
+
+・スクリプトの間違いを修正
+ (conf/npc/)
+ npc_event_skillget.txt
+ npc_event_yuno.txt
+ npc_town_lutie.txt
+ npc_turtle.txt
+ 謎命令additemをgetitemに置換
+ npc_town_guide.txt
+ 謎命令scriptlabelをコメント化
+ npc_event_momotaro.txt
+ npc_job_swordman.txt
+ npc_job_magician.txt
+ ';'付け忘れ修正
+ (conf/gvg/)
+ ev_agit_aldeg.txt
+ @GID4を@GIDa4に置換
+ ev_agit_gefg.txt
+ ev_agit_payg.txt
+ Annouceに色指定と';'の付け忘れを習性
+
+
+・AthenaDB計画のデータとりこみ、その他修正
+ 安定しているデータかどうかわかりませんが。
+
+ (db/)
+ item_db.txt/mob_db.txt/mob_skill_db.txt
+ AthenaDB計画のデータとりこみ
+ mob_skill_db.txt.orig
+ 以前のデータ(コメント部分などの参考に)
+ (conf/)
+ water_height.txt/mapflag.txt
+ AthenaDB計画のデータとりこみ
+ map_athena.conf
+ npc_monster3*.txtを削除
+ 追加マップデータ (by ID:UVsq5AE)
+ (conf/mob/)
+ npc_monster.txt
+ AthenaDB計画のデータとりこみ
+
+--------------------
+//0908 by 胡蝶蘭
+
+・スクリプトのエラーチェック処理を増やした
+ ・文字列の途中で改行があるとエラーを出すように。
+ ・関数呼び出し演算子'('の直前に関数名以外があるとエラーを出すように。
+ ・命令があるべきところに関数名以外があるとエラーを出すように。
+ ・命令および関数の引数区切りの','を省略すると警告を出すように。
+ ・命令および関数の引数の数が異なると警告を出すように。
+
+ (map/)
+ script.c
+ 色々修正
+
+・NPCスクリプト修正
+ (conf/npc/)
+ npc_town_guide.txt
+ 4行目はいらないようなのでエラーが出ないようにコメント化
+ npc_event_hat.txt
+ コモドの仮面職人とフェイヨンの青年 (by ID:dS8kRnc)
+ (conf/sample/)
+ npc_card_remover.txt
+ @menuを使って短くした&文章少し修正
+
+・その他
+ (db/)
+ skill_tree.txt
+ Sage応急手当
+
+--------------------
+//0907 by p
+・atcommand() の肥大化がひどいのでリファクタリング
+ @ コマンドを追加する場合は、atcommand.h 内で定数を、atcommand.c 内で
+ 関数定義マクロとマッピングテーブル、処理用の関数を記述してください。
+・global 変数の atcommand_config を消去。
+ @ コマンド毎のレベルは get_atcommand_level() で取得してください。
+・一部のキャラ名を取る @ コマンドで、半角スペースを含む名前のキャラを
+ 正常に処理できていなかった問題を修正。
+ この影響により、@rura+ など、キャラ名がパラメータの途中にあったものは
+ 全て最後に回されています。
+・@ コマンドの文字列を正常に取得できなかった場合に、バッファの内容を
+ チェックせずに処理を行おうとしていた部分を修正しました。
+
+ (common/)
+ mmo.h
+ (map/)
+ atcommand.h
+ atcommand.c
+ clif.h
+ clif.c
+
+--------------------
+//0906 by Selena
+・胡蝶蘭さんの修正にあわせて、バルキリーレルム1以外のスクリプトの修正。
+・@コマンド入力ミスの際にエラーメッセージを表示。
+ (conf/gvg/)
+ ev_agit_aldeg.txt
+ ev_agit_gefg.txt
+ ev_agit_payg.txt
+ ev_agit_prtg.txt
+ aldeg_cas01〜05.txt
+ gefg_cas01〜05.txt
+ payg_cas01〜05.txt
+ prtg_cas02〜05.txt
+ (map/)
+ atcommand.c
+
+--------------------
+//0905 by 管理人
+
+・サーバーsnapshot
+・前スレのファイル取り忘れた人がいるかもしれないので
+
+--------------------
+//0904 by 胡蝶蘭
+
+・スクリプト処理修正
+ ・char/interサーバーに接続した時にOnCharIfInit/OnInterIfInitイベントが
+ 呼ばれるようになりました。
+ OnAgitInitはOnInterIfInitに変更すべきです。
+ ・getcastledata命令で第2パラメータが0のとき、第3パラメータに
+ イベント名を設定できます。このイベントはギルド城のデータを
+ Interサーバーから所得完了したときに実行されます。
+ ・起こすNPCイベント名を"::"で始めると、同名ラベルを持つ全NPCのイベント
+ を実行できます。
+ たとえば、getcastledata "prtg_cas01.gat",0,"::OnRecvCastleP01";
+ とすると全てのNPCの OnRecvCastleP01ラベルが実行されます。
+ ・requestguildinfo命令追加。特定ギルドの情報をInterサーバーに
+ 要求できます。第1パラメータはギルドID、第2パラメータはイベント名で
+ このイベントはギルド情報をInterサーバーから所得完了したときに
+ 実行されます。
+
+ (map/)
+ guild.c/guild.h/npc.c/npc.h/script.c/intif.c/chrif.c
+ 色々修正
+
+・ギルド城関連NPC修正
+ (バルキリーレルム1のみ修正。他の城のスクリプトは各自で弄ってください。
+ というか、むしろ弄ったらあっぷしましょう)
+ ・初期化処理をOnAgitInitでなくOnInterIfInitに変更。
+ ・城データ所得完了処理としてOnRecvCastleP01を追加。
+ ・鯖再起動時、ギルド専属カプラが正しく表示されるように。
+ ・ギルド専属カプラの名前を"カプラ職員::kapra_prtg01"に変更。
+ ("::"以降はエクスポートされる名前で、"::"以前が表示名)
+ "カプラ職員#prt"より名前を長くして競合しにくくするためです。
+ この関係で、disablenpcなどのパラメータを"kapra_prtg01"に修正。
+ (conf/gvg/)
+ prtg_cas01.txt
+ ギルド専属カプラ修正
+ ev_agit_prtg.txt
+ 初期化処理修正(バルキリーレルム1のみ)
+ TEST_prtg_cas01_AbraiJ.txt
+ ギルド専属カプラ雇用/城破棄修正
+
+・NPCの修正
+ (conf/npc/)
+ npc_job_swordman.txt
+ npc_event_hat.txt
+ 修正
+
+・アカウントを削除してもアカウントIDを再利用しないように修正
+・ギルド/パーティについても一応同等の処理追加(コメント化されています。
+ ギルドやパーティはIDを再利用してもおそらく問題ないため)
+
+ (login/)
+ login.c
+ 読み込み/保存処理修正
+ (char/)
+ int_guild.c/int_party.c
+ 読み込み/保存処理修正
+
+--------------------
+//0903 by 胡蝶蘭
+
+・l14/l15およびプレフィックスlを"推奨されない(deprecated)"機能としました。
+ ・まだ使用できますが、今後の動作が保障されないので、速やかに代替機能を
+ 使用するように移行してください。
+ ・プレフィックス'l'は代替機能のプレフィックス'@'を使用してください。
+ ・l15は代替機能の@menuを使用してください。
+ ・l14は代替機能はありません。input命令の引数を省略しないで下さい。
+ ・これらの推奨されない機能を使用すると警告メッセージがでます。
+
+ (map/)
+ script.c
+ parse_simpleexpr()修正
+ (conf/warp/)
+ npc_warp.txt/npc_warp25.txt/npc_warp30.txt
+ 変数名l0を@warp0に修正
+ (conf/npc/)
+ npc_event_hat.txt
+ 変数名l15を@menuに修正
+ (doc/)
+ script_ref.txt
+ 配列変数の説明追加
+ 変数のプレフィックス'l'、input命令のl14、menu命令のl15の
+ 説明を修正
+
+--------------------
+//0902 by 胡蝶蘭
+
+・スクリプトが配列変数に対応。
+ ・array[number]のように使います。数値型、文字列型両方使えます。
+ ・使えるプレフィックスは @, $, $@ です。
+ (一時的キャラクター変数、一時的/永続的マップサーバー変数)
+ ・number==0は配列じゃない変数と値を共有します。
+ (@hoge[0]と@hogeは同じ変数を表す)
+ ・まだ仮実装段階なのでバグ報告よろしくお願いします。
+・マップサーバー変数の読込中にCtrl+Cをするとデータ破損の可能性がある問題を修正.
+・マップファイル読み込み画面がさびしいのでせめてファイル名を表示するように。
+
+ (conf/sample/)
+ npc_test_array.txt
+ 配列変数テストスクリプト
+ (map/)
+ script.c
+ buildin_set(),buildin_input(),get_val(),
+ parse_simpleexpr()修正
+ buildin_getelementofarray()追加
+ do_final_script()修正など
+ map.c
+ map_readmap(),map_readallmap()修正
+
+--------------------
+//0901 by ぴざまん
+
+・露店バグの修正
+
+ (map/)
+ pc.c
+ pc_cartitem_amount() 追加。
+ vending.c
+ vending_openvending() 修正。
+ clif.c
+ clif_parse_NpcClicked() 修正。
+ pc.h 修正。
+
+--------------------
+//0900 by ぴざまん
+
+・アブラカダブラのランダムスキル発動率をabra_db.txtで設定できるように。
+・スフィアーマインとバイオプラントの微修正。
+・Noreturnマップで蝶が消費だけされるバグ修正。
+・一部のアブラ固有スキルが正しく動作しなかったバグ修正。
+ (map/)
+ mob.c
+ mob_damage()、mobskill_use() 修正。
+ mob_skillid2skillidx() 追加。
+ skill.c
+ skill_readdb()、skill_abra_dataset() 修正。
+ skill_castend_nodamage_id()、skill_castend_pos2() 修正。
+ script.c
+ buildin_warp() 修正。
+
+ skill.h 修正。
+ map.h 修正。
+ (db/)
+ abra_db.txt 追加。
+ skill_db.txt 修正。
+
+--------------------
+//0899 by 胡蝶蘭
+
+・取り巻きMOBの処理修正
+ ・取り巻き召喚でコアを吐くバグ修正
+ ・主が別マップに飛ぶと、テレポートで追いかけるように修正
+ ・取り巻き処理をより軽く変更
+
+ (map/)
+ mob.c
+ mob_ai_sub_hard_mastersearch()をmob_ai_sub_hard_slavemob()
+ に名前を変えて処理修正。
+ mob_summonslave()修正
+
+--------------------
+//0898 by 胡蝶蘭
+
+・eathenaからCardRemoverNPCの取り込み
+ NPCデータも日本語訳してますが、かなり適当です。
+
+ (map/)
+ script.c
+ buildin_getequipcardcnt(),buildin_successremovecards()
+ buildin_failedremovecards()追加
+ (conf/sample/)
+ npc_card_remover.txt
+ カード取り外しNPCの日本語訳
+ プロンテラの精錬所の中の左下の部屋にいます
+
+・ポータルで別マップに飛ばしたMOBがそのマップに沸き直すバグ修正
+ (map/)
+ map.h
+ struct mob_dataにmメンバ追加
+ mob.c
+ mob_spawn(),mob_once_spawn()修正
+ npc.c
+ npc_parse_mob()修正
+
+
+--------------------
+//0897 by ぴざまん
+
+・細かい調整
+・ストリップ系とケミカルプロテクション系スキルの全実装
+ 本鯖での細かい仕様が分ったので実装しました。
+ 確率は暫定です。
+
+ (map/)
+ pc.c
+ pc_isequip() 修正
+ skill.c
+ skill_status_change_start()、skill_castend_nodamage_id() 修正。
+ skill_abra_dataset() 修正。
+ battle.c
+ battle_get_def()、battle_get_atk2() 修正。
+ battle_get_vit()、battle_get_int() 修正。
+ (db/)
+ const.txt 修正。
+ skill_db.txt 修正。
+ cast_db.txt 修正。
+
+--------------------
+//0896 by 胡蝶蘭
+
+・永続的マップ変数機能追加
+・マップ変数を文字列型変数としても使用できるようにした
+ ・今までのプレフィックス $ は永続的になります。
+ 一時的マップ変数を使用する場合はプレフィックス $@ を指定してください.
+
+ ・永続的/一時的ともに文字列型に対応しています。
+ 文字列型のポストフィックスは$です。
+
+ <例> $@hoge 数値型一時マップ変数、$hoge$ 文字列型永続マップ変数
+ ・永続マップ変数はデフォルトでは save/mapreg.txt に保存されます。
+ これはmap_athena.confのmapreg_txtで設定できます。
+
+・str_dataが再割り当てされるとマップ変数が正常に使用できないバグ修正
+ ・strdbからnumdbにして、変数名はstr_bufに入れるように。
+
+・map_athena.confのdelnpc,npc:clearが正しく働かないバグ修正
+
+ (map/)
+ npc.c
+ npc_delsrcfile(),npc_clearsrcfile()修正
+ script.c / script.h
+ マップ変数系かなり修正
+ map.c
+ map_read_config()修正など
+ (conf/)
+ map_athena.conf
+ mapreg_txt追加
+ (doc/)
+ conf_ref.txt
+ mapreg_txt,help_txt,motd_txt追加
+ script_ref.txt
+ 文字列型変数の説明修正
+
+--------------------
+//0895 by Selena
+
+・mapflagにnozenypenaltyを追加。
+ GVGや街中のテロなどで死亡した際に、Zenyペナルティー発生を外す用。
+
+ (map/)
+ pc.c
+ pc_setrestartvalue() 修正
+ script.c
+ buildin_setmapflag()、buildin_removemapflag() 修正
+ npc.c
+ npc_parse_mapflag() 修正
+ map.h
+ map_data() 修正
+ (db/)
+ const.txt 修正。
+
+--------------------
+//0894 by ぴざまん
+
+・コーマ以外のアブラカダブラ固有スキル全実装。
+ オートスペルにはレベルアップ以外多分全部乗せれます。(オートスペルレベルアップは未テスト)
+・アブラカダブラ仮実装
+ 発動スキルがレベル依存じゃありません。
+ 全ての発動率が理論上均一です。
+ アイテムスキルを使って実装しているので一部の使用条件を無視します(ジェム罠気球等)
+・アイテムスキルがキャスト・ディレイ無しだったのを修正。
+
+ (map/)
+ skill.c
+ skill_castend_nodamage_id()、skill_use_id()、skill_use_pos() 修正。
+ skill_abra_dataset() 追加。
+ (db/)
+ skill_db.txt 修正。
+
+--------------------
+//0893 by 胡蝶蘭
+
+・他マップからポータルの上にワープしてきたPCがワープしない問題を修正
+・チャット中のPCをワープポータルで飛ばすかどうか設定可能に
+・MOBをワープポータルで飛ばすかどうか設定可能に
+ MOBのワープポータルを許可すると、テロが簡単にできるので注意。
+
+・アカウント変数変更と同時にファイルに書き出すように修正
+・マップデータのロード部分のログ表示はあまり重要じゃないと思うので変更。
+
+ (char/)
+ inter.c
+ mapif_parse_AccReg()でinter_accreg_save()を呼ぶように修正
+ (map/)
+ mob.c/mob.h
+ mob_warp()の引数変更と修正
+ battle.c/battle.h
+ mob_warp()呼び出しの引数修正
+ battle_config関連
+ map.c
+ map_readallmap(),map_readmap()修正
+ pc.c
+ pc_setpos()修正
+ skill.c
+ mob_warp()呼び出しの引数修正
+ skill_unit_onplace()修正
+ (conf/)
+ battle_athena.conf
+ chat_warpportal,mob_warpportalの追加
+ (doc/)
+ conf_ref.txt
+ chat_warpportal,mob_warpportalの追加
+
+--------------------
+//0892 by 胡蝶蘭
+
+・各種confファイルで別ファイルをインポートできるようにした
+ ・自分のサーバー用の設定を別ファイルに記述できるようになります。
+ ・全て「import: ファイル名」形式で記述します。
+ ・各種confファイル(login,char,map,inter,atcommand,battle)の最後に
+ conf/import/*_conf を読むように指定したので、そこに自分用の設定を
+ 書いておけば、変更部分のみオーバーライドします。
+ msg,scriptのconfについては、この限りではありませんが、import命令の
+ 処理は追加されているので、自分でimport命令を書けば動きます。
+ ・新しいスナップショットが出た場合などに、このconf/importフォルダを
+ 昔のAthenaからコピーするだけで自分用の設定を適用できるようになります.
+
+・map_athena.confのmapとnpcで追加したファイルを削除できるようにした
+ ・上に関連する変更です。
+ ・delmap,delnpc命令を使用すれば、map,npc命令で追加したファイルを
+ 読み込まないように指定できます。ここでファイル名ではなく、
+ all と指定するとそれまでに指定されたファイルを全て読み込まなくします.
+ ・map,npc命令で、ファイル名にclearを指定すると、
+ delmap,delnpcのallと同等の動作をするようになりました。
+
+・login_athena.confのallowとdenyをクリアできるようにsた
+ ・allowおよびdeny命令でclearを指定すると以前のホスト情報を全削除します.
+
+ (conf/)
+ 各種confファイルの最後にimport命令追加
+ (conf/import)
+ *.txt
+ インポートされるファイル。これらに自分用の設定を書くとよい。
+ (login/)
+ login.c
+ login_read_config()修正
+ (char/)
+ char.c/inter.c
+ char_read_config(),inter_read_config()修正
+ (map/)
+ map.c
+ map_read_config(),map_addmap()修正、map_delmap()追加
+ npc.c
+ npc_addsrcfile()修正,npc_delsrcfile(),npc_clearsrcfile()追加
+ battle.c/atcommand.c/script.c
+ battle_read_config(),atcommand_read_config(),
+ msg_read_config(),script_read_config()修正
+ (doc/)
+ conf_ref.txt
+ 修正
+
+--------------------
+//0891 by (凸)
+
+・「スキル使用の後は、しばらくお待ちください」を表示するかどうか設定できるようにした。
+ ・本鯖相違スレッド 其のU>>5さんのコードをパクリました。
+ (doc/)
+ conf_ref.txt 修正。
+ (conf/)
+ battle_athena.conf 修正。
+ (map/)
+ battle.h 修正。
+ battle.c
+ battle_config_read() 修正。
+ clif.c
+ clif_skill_fail() 修正。
+
+--------------------
+//0890 by 死神
+
+・ギルド倉庫を一度に一人だけが使用するように変更。(未テスト)
+・battle_athena.confからplayer_undead_nofreeze 削除。
+・@コマンド@gstorage 追加。
+・スクリプトguildstorageをguildopenstorageに変更。
+・その他細かいバグ修正。
+ (doc/)
+ conf_ref.txt 修正。
+ script_ref.txt 修正。
+ (conf/)
+ atcommand_athena.conf 修正。
+ battle_athena.conf 修正。
+ help.txt 修正。
+ (conf/sample/)
+ gstorage_test.txt 追加。
+ (char/)
+ makefile 修正。
+ int_storage.h 修正。
+ int_storage.c
+ inter_storage_delete()、inter_guild_storage_delete() 追加。
+ int_guild.c
+ guild_check_empty()、mapif_parse_BreakGuild() 修正。
+ (map/)
+ makefile 修正。
+ battle.h 修正。
+ battle.c
+ battle_config_read() 修正。
+ guild.c
+ guild_broken() 修正。
+ storage.h 修正。
+ storage.c
+ storage_guild_storageopen() 修正。
+ storage_delete()、guild_storage_delete() 追加。
+ script.c
+ buildin_guildstorage() を buildin_guildopenstorage()に変更。
+ intif.c
+ intif_parse_LoadGuildStorage() 修正。
+ mob.c
+ mob_summonslave()、mob_damage()、mob_delete() 修正。
+ mob_catch_delete()、mob_readdb() 修正。
+ skill.c
+ skill_castend_nodamage_id()、skill_status_change_start() 修正。
+ clif.c
+ clif_parse_ActionRequest() 修正。
+ atcommand.h 修正。
+ atcommand.c
+ atcommand() 修正。
+
+--------------------
+//0889 by 胡蝶蘭
+
+・文字列型一時的キャラクター変数機能追加。
+ ・プレフィックス@,ポストフィックス$を使用します。(@hoge$など)
+ ・inputで文字列変数を指定すると文字列入力になります。
+ ・関係演算子(比較演算子)で文字列どうしを指定すると文字列の比較が
+ できます。数値と文字列を混ぜて比較することはできません。
+ ・とりあえずサンプル付けてます。
+
+ (map/)
+ map.h
+ struct map_session_dataにnpc_str,regstr,regstr_numメンバ追加
+ script.c
+ buildin_set(),get_val(),buildin_input(),op_2num()など修正
+ op_2str(),op_2()追加
+ clif.c / clif.h
+ 01d5パケット長修正
+ clif_parse_NpcStringInput(),clif_scriptinputstr()追加
+ pc.c / pc.h
+ pc_readregstr(),pc_setregstr()追加
+ (doc/)
+ script_ref.txt
+ 演算子の説明追加、変数の説明修正、input,menu修正
+ (conf/sample/)
+ npc_test_str.txt
+ 文字列変数を使用したスクリプトの例。
+ 文字列の代入、結合、比較、入力などのテストを行うもの。
+
+--------------------
+//0888 by 死神
+
+・設計から間違っていたギルド倉庫修正。(ただ複数人の使用によるバグがある可能性はまだあります。)
+・細かいバグ修正。
+ (doc/)
+ inter_server_packet.txt 修正。
+ conf_ref.txt 修正。
+ (conf/)
+ inter_athena.conf 修正。
+ help.txt 修正。
+ (common/)
+ mmo.h 修正。
+ (char/)
+ makefile 修正。
+ int_storage.h 修正。
+ int_storage.c
+ account2storage()、inter_storage_init()、storage_fromstr() 修正。
+ inter_storage_save()、mapif_load_storage() 修正。
+ mapif_parse_SaveStorage() 修正。
+ guild_storage_fromstr()、guild_storage_tostr() 追加。
+ inter_storage_save_sub()、inter_guild_storage_save_sub() 追加。
+ inter_guild_storage_save()、mapif_parse_LoadGuildStorage() 追加。
+ mapif_parse_SaveGuildStorage()、mapif_load_guild_storage() 追加。
+ mapif_save_guild_storage_ack()、guild2storage() 追加。
+ int_party.c
+ inter_party_init() 修正。
+ int_guild.h 修正。
+ int_guild.c
+ inter_guild_init() 修正。
+ inter_guild_search() 追加。
+ int_pet.c
+ inter_pet_init() 修正。
+ inter.c
+ inter_init()、inter_save()、inter_config_read() 修正。
+ (map/)
+ makefile 修正。
+ map.h 修正。
+ map.c
+ map_quit()、do_init() 修正。
+ pc.c
+ pc_setpos() 修正。
+ storage.h 修正。
+ storage.c
+ do_init_storage()、do_final_storage()、account2storage() 修正。
+ storage_storageopen()、storage_storageadd()、storage_storageget() 修正。
+ storage_storageaddfromcart()、storage_storagegettocart() 修正。
+ storage_storageclose()、storage_storage_quit() 修正。
+ storage_storage_save() 修正。
+ guild2storage()、storage_guild_storageopen() 追加。
+ guild_storage_additem() 、guild_storage_delitem() 追加。
+ storage_guild_storageadd()、storage_guild_storageget() 追加。
+ storage_guild_storageaddfromcart()、storage_guild_storagegettocart() 追加。
+ storage_guild_storageclose()、storage_guild_storage_quit() 追加。
+ intif.h 修正。
+ intif.c
+ intif_send_storage()、intif_parse_LoadStorage()、intif_parse() 修正。
+ intif_request_guild_storage()、intif_send_guild_storage() 追加。
+ intif_parse_SaveGuildStorage()、intif_parse_LoadGuildStorage() 追加。
+ clif.h 修正。
+ clif.c
+ clif_additem()、clif_parse_MoveToKafra() 修正。
+ clif_parse_MoveFromKafra()、clif_parse_MoveToKafraFromCart() 修正。
+ clif_parse_MoveFromKafraToCart()、clif_parse_CloseKafra() 修正。
+ clif_parse_LoadEndAck() 修正。
+ clif_guildstorageitemlist()、clif_guildstorageequiplist() 追加。
+ clif_updateguildstorageamount()、clif_guildstorageitemadded() 追加。
+ guild.c
+ guild_broken() 修正。
+ script.c
+ buildin_openstorage()、buildin_guildstorage() 修正。
+ skill.c
+ skill_castend_nodamage_id() 修正。
+ mob.c
+ mob_summonslave()、mob_damage() 修正。
+ atcommand.c
+ atkillmonster_sub()、atcommand() 修正。
+
+--------------------
+//0887 by 獅子o^.^o
+
+・(db/)
+ skill_tree.txt 修正
+
+--------------------
+//0886 by ぴざまん
+
+・サーバーsnapshot
+・ファイル調整
+
+--------------------
+//0885 by huge
+
+・ギルド共有倉庫の実装。guildstorageで開けます。
+ 自分の鯖で実験はしてみましたが、過疎地なので多人数ギルドになるとどう動くか分かりません。
+ (念のためバックアップは必ず取っておいて下さい)
+・areawarpで、対象マップ名を"Random"にすると、同マップ内でランダムに飛ぶように修正。
+・GMコマンドで生き返したときにSPも全回復するように修正。
+・ディボーションの条件をちょっと修正。
+
+ (char/)
+ int_storage.c
+ mapif_load_storage() 修正。
+ mapif_parse_SaveStorage() 修正。
+ inter.c
+ inter_send_packet_length[] 修正。
+ inter_recv_packet_length[] 修正。
+ (map/)
+ atcommand.c
+ @alive,@raise,@raisemap 修正。
+ intif.c
+ packet_len_table[] 修正。
+ intif_request_storage() 修正。
+ intif_send_storage() 修正。
+ intif_parse_LoadStorage() 修正。
+
+ map.h
+ map_session_data stateにstorage_flag 追加。
+ script.c
+ buildin_areawarp_sub() 修正。
+ buildin_openstorage() 修正。
+ buildin_guildstorage() 追加。
+ skill.c
+ skill_castend_nodamage_id() 修正。
+ storage.c
+ account2storage() 修正。
+ storage_storageopen() 修正。
+ storage_storage_save() 修正。
+
+--------------------
+//0884 by 死神
+
+・細かいバグ修正。
+・battle_athena.confにpet_str、zeny_penalty、resurrection_exp 追加。
+・0878の銀行関係のコードはもういらないので全て削除。
+・zeny_penaltyを設定して使う場合は手数料はなくした方がいいかも。
+・ポーションピッチャーでpercenthealにもPPとLPによる回復ボーナスが付くように変更。(ただvitやint、HPR、MPRによる回復ボーナスが付きません。)
+・ほとんど未テスト。
+ (common/)
+ mmo.h 修正。
+ (conf/)
+ battle_athena.conf 修正。
+ (doc/)
+ conf_ref.txt 修正。
+ (map/)
+ map.c
+ do_init()、do_final() 修正。
+ script.c
+ buildin_openbank() 削除。
+ buildin_failedrefitem() 修正。
+ storage.h 修正。
+ storage.c
+ do_init_bank()、do_final_bank()、account2bank() 削除。
+ storage_bank()、storage_readbank() 削除。
+ skill.c
+ skill_castend_nodamage_id()、skill_attack() 修正。
+ battle.h 修正。
+ battle.c
+ battle_calc_pet_weapon_attack()、battle_config_read() 修正。
+ pc.c
+ pc_setrestartvalue() 修正。
+ clif.c
+ clif_skill_nodamage()、clif_refine() 修正。
+ itemdb.c
+ itemdb_isequip3() 修正。
+ atcommand.c
+ atcommand() 修正。
+
+--------------------
+//0883 by Kalen
+
+・Warp色々修正
+ ・アサシンギルド周り修正(昔のままのリンクだったので現在の状態に修正。)
+ ・YunoのWarp全面見直し(YumilLoop修正、SageCastleRandomWarp追加、女医さんの家追加)
+ ・モンクギルド周り追加
+・NPC色々修正
+ ・帽子作成NPCを別ファイルへ。一部追加(ep2.5追加分)
+ 参考Data(R.O.M776): ttp://green.sakura.ne.jp/~youc/ro/data/itemmaking.html#04
+ ・アサシンギルド修正
+ ・二次職転職関係NPC一部追加(これでコモド小劇場へ行けます)
+ ・マスターアルケミストの台詞修正
+ ・アルデバランの案内要員を移動&台詞修正&イメージ追加
+ ・BBSにあがっていたコモドスクリプト追加(event_hat等へ分散)
+ ・コンロンクエスト関係NPC一部追加(女医[yuno]、ネル[prontera])
+ (conf/warp/)
+ npc_warp.txt
+ npc_warp30.txt
+ npc_warp_job.txt
+ (conf/npc/)
+ npc_event_hat.txt(新規)
+ npc_job_2nd.txt
+ npc_job_alchemist.txt
+ npc_town_aldebaran.txt
+ npc_town_comodo.txt
+ npc_town_gonryun.txt
+ npc_town_guide.txt
+ npc_town_yuno.txt
+ npc_town_lutie.txt
+
+--------------------
+//0882 by 胡蝶蘭
+
+・スクリプトに0881相当のアカウント共有変数機能のプレフィックス変更
+ ・0881のアカウント変数はプレフィックス##になりました。
+ ・0881のアカウント変数は全ワールドで共有されます。
+ ・変数の個数はmmo.hのACCOUNT_REG2_NUMで定義されています(16)。
+・ワールド内のアカウント共有変数機能追加
+ ・変数名のプレフィックスは#です。
+ ・変数の個数はmmo.hのACCOUNT_REG_NUMで定義されています(16)。
+ ・0881の銀行スクリプトはこちらを使用するようになります。
+ よって以前のデータがつかえないのであらかじめ引き出しておいてください.
+ ・変数データは save/accreg.txt に保存されます。
+ このファイル名は inter_athena.conf で変更可能です。conf_ref.txt参照。
+
+ (common/)
+ mmo.h
+ ACCOUNT_REG_NUMを16に、ACCOUNT_REG_NUM2追加
+ struct mmo_charstatusにaccount_reg2_num,account_reg2メンバ追加
+ (login/)
+ login.c
+ account_regを全てaccount_reg2に置き換え
+ (char/)
+ char.c
+ account_regを全てaccount_reg2に置き換え
+ inter.c
+ ワールド内アカウント変数機能追加。
+ inter_accreg*()追加、accreg_db追加など。
+ (map/)
+ chrif.c/chrif.h
+ account_regを全てaccount_reg2に置き換え
+ 0881でのバグを修正
+ intif.c/intif.h
+ ワールド内アカウント変数機能追加。
+ pc.c/pc.h
+ pc_*accountreg()=>pc_*accountreg2()に。
+ pc_setaccountreg(),pc_readaccountreg()追加。
+ script.c
+ buildin_set(),buildin_get_val(),buildin_input()修正
+ (doc/)
+ inter_server_packet.txt
+ ワールド内アカウント変数関係
+ conf_ref.txt
+ accreg_txt追加
+
+--------------------
+//0881 by 胡蝶蘭
+
+・スクリプトにアカウント共有変数機能追加
+ ・変数名にプレフィックス#を付けることでアカウント共有変数になります。
+ ・アカウント変数は変更した時点で全サーバーにポストされるので
+ 頻繁に書き換えるとサーバー間通信が肥大化します。
+ ・アカウント変数は変更した時点(そしてそれがlogin鯖に届いた時点)で
+ account.txtに書き出されます。
+ ・グローバル変数(永続変数)の個数を96に減らし、減った32個分を
+ アカウント変数にしていますが、mmo_charstatusのサイズが
+ 16000byteを超えない限り増やすことができます。<0879の変更を参照
+ 変数の個数はmmo.hのACCOUNT_REG_NUMで定義されています。
+ ・0878の銀行をアカウント変数を使用するように修正
+ bank.txtのデータが使えなくなるのであらかじめ引き出しておいて下さい。
+
+ (common/)
+ mmo.h
+ GLOBAL_REG_NUMを96に、ACCOUNT_REG_NUMを追加
+ struct mmo_charstatusにaccount_reg_num,account_regメンバ追加
+ (login/)
+ login.c
+ パケット2728処理追加
+ (char/)
+ char.c
+ パケット2729,2b10処理追加
+ (map/)
+ chrif.c
+ chrif_saveaccountreg(),chrif_accountreg()
+ (パケット2b10,2b11処理)追加。
+ pc.c/pc.h
+ pc_readaccountreg(),pc_setaccountreg()追加
+ script.c
+ buildin_set(),buildin_get_val(),buildin_input()修正
+ (conf/sample/)
+ bank_test.txt
+ アカウント変数使用版の銀行スクリプト
+
+--------------------
+//0880 by 死神
+
+・ポーションピッチャーを正しく実装とちょっと機能拡張。
+・ポーションピッチャーでレベル別に使えるアイテムをskill_require_db.txtに設定できるようにしました。ただポーションピッチャーで使えるアイテムはitemheal、percentheal、sc_start、sc_end以外の物が入っていると正しく動作しません。
+レベル5までは本鯖に合わせていますが最大レベルを10まで拡張するとレベル6 - マステラの実、7 - ローヤルゼリー、8 - イグドラシルの種、9 - イグドラシルの実、10 - バーサークポーションに設定しています。skill_db.txtを修正すればこれが有効になります。(どこを修正するかもわからない人は諦めることです。) ポーションピッチャーによるアイテム使用は使用条件を無視します。少しはアルケミストに希望ができたかも...(多分無理...)
+・battle_athane.confにproduce_item_name_input、produce_potion_name_input、making_arrow_name_input、holywater_name_input 追加。
+・パーティ員にだけ使うスキルとギルド員にだけ使うスキルを設定できるように修正。
+・その他細かい修正。
+ (conf/)
+ battle_athane.conf 修正。
+ (doc/)
+ conf_ref.txt 修正。
+ db_ref.txt 修正。
+ (db/)
+ skill_db.txt 修正。
+ skill_require_db.txt 修正。
+ (map/)
+ map.h 修正。
+ skill.h 修正。
+ skill.c
+ skill_status_change_timer()、skill_attack()、skill_use_id() 修正。
+ skill_castend_nodamage_id()、skill_castend_damage_id() 修正。
+ skill_castend_id()、skill_castend_pos()、skill_produce_mix() 修正。
+ skill_arrow_create()、skill_check_condition() 修正。
+ skill_status_change_clear()、skill_readdb() 修正。
+ mob.c
+ mobskill_use_id()、mob_changestate() 修正。
+ pc.c
+ pc_itemheal()、pc_percentheal()、pc_calcstatus() 修正。
+ battle.h 修正。
+ battle.c
+ battle_delay_damage()、battle_damage()、battle_heal() 修正。
+ battle_get_adelay()、battle_get_amotion() 修正。
+ battle_calc_mob_weapon_attack()、battle_calc_pc_weapon_attack() 修正。
+ battle_weapon_attack()、battle_config_read() 修正。
+ clif.c
+ clif_skill_fail() 修正。
+ script.c
+ buildin_sc_start()、buildin_sc_end() 修正。
+ makefile 修正。
+
+--------------------
+//0879 by 胡蝶蘭
+
+・送信FIFOのバッファオーバーフローの脆弱性の修正
+ ・2048バイト以上のパケットを送るとき、FIFOが満杯に近ければ
+ バッファオーバーフローによる不正アクセスが起こっていた問題修正。
+ ・FIFOが満杯に近いときWFIFOSETされたパケットが捨てられていた問題修正。
+ ・FIFOがオーバーフローする場合、自動的にFIFOを拡張するようにした。
+ (ただし、一度にWFIFOSETするパケットが16384バイト以下と仮定している)
+ ・「socket: ? wdata expanded to ???? bytes」はFIFOが拡張されたときに
+ でるログだが、エラーではなく、パケットは正しく送信される。
+ ・「socket: ? wdata lost !!」はパケットが喪失したことを表すログで、
+ エラーであるが64KBを超える超巨大なパケットをWFIFOSETしないと出ない。
+ ・16384バイトを超えるパケットをWFIFOSETするとエラーメッセージなしに、
+ 不正アクセスが起こる可能性があるので、超えないようにすること。
+
+ (common/)
+ socket.c /socket.h
+ WFIFOSET()をマクロから関数に変更
+ realloc_fifo()追加
+
+・サーバー間通信FIFOのバッファサイズを大きくした
+ ・大量のデータが通信されたときにデータ処理遅延が起きにくくするため。
+ ・メモリ使用量が増えた。(ぎりぎりの人は65536に設定すると元通りになる)
+ ・サーバー間通信のFIFOサイズは mmo.h で定義されている。
+ 変更する場合は64KB(65536)以上の値にすること。
+ 大きくすると巨大データ受信時の遅延が減るがメモリを多く使う。
+ ・@kickall時などにデータ送信が激しくなるので変更したが、
+ 同時ログイン人数が少ないと増やしても意味は無い。
+
+ (common/)
+ mmo.h
+ FIFOSIZE_SERVERLINKマクロ追加。
+ (login/)
+ login.c
+ 2710パケットでrealloc_fifo()を呼ぶように
+ (char/)
+ char.c
+ 2af8パケットでrealloc_fifo()を呼ぶように
+ check_connect_login_server()でrealloc_fifo()を呼ぶように
+ (map/)
+ chrif.c
+ check_connect_char_server()でrealloc_fifo()を呼ぶように
+
+--------------------
+//0878 by huge
+
+・カプラ銀行サービス。
+ 自分の鯖で実装してたんですが、意外と好感触だったので出してみます。
+ NPCscriptで、openbank(0);で預金額を返して、中に数字を入れると出し入れします。
+ 詳しくはサンプルを同封したので、それを参照。
+
+ (common/)
+ mmo.h
+ struct bank 追加。
+ (map/)
+ map.c
+ do_final(),do_init() 修正。
+ script.c
+ buildin_openbank() 追加。
+ storage.c
+ storage.h
+ グローバル変数追加。
+ do_init_bank(),do_final_bank(),account2bank() 追加。
+ storage_bank(),storage_readbank() 追加。
+
+--------------------
+//0877 by 胡蝶蘭
+
+・login鯖のアクセスコントロールがネットマスク表記に対応
+ 192.168.0.0/24 や 192.168.0.0/255.255.0.0 といった表記に対応。
+・battle_athena.confにGMが無条件で装備品を装備できる&
+ 無条件でスキルを使用できる設定追加
+ これらはデバグ用なので動作に不都合があるかもしれません。
+
+ (login/)
+ login.c
+ check_ip()修正,check_ipmask()追加
+ (map/)
+ battle.c/battle.h
+ battle_configにgm_allequip,gm_skilluncond追加
+ battle_config_read()修正更
+ skill.c
+ skill_check_conditio()修正
+ pc.c
+ pc_isequp()修正
+ (doc/)
+ conf_ref.txt
+ allow変更、gm_all_equipment、gm_skill_unconditional追加
+
+--------------------
+//0876 by 死神
+
+・細かいバグ修正。
+・@コマンドにテストの為に入れていた物が入っていたので修正。
+・ハンマーフォールの射程を5から4に修正(本鯖射程は不明)とリザレクションが無属性だったのを聖属性に修正。
+ (db/)
+ skill_db.txt 修正。
+ (map/)
+ mob.c
+ mob_catch_delete()、mob_stop_walking() 修正。
+ storage.c
+ storage_additem() 修正。
+ pc.c
+ pc_damage()、pc_stop_walking() 修正。
+ clif.c
+ clif_parse_UseSkillToId()、clif_parse_UseSkillToPos() 修正。
+ battle.c
+ battle_calc_magic_attack() 修正。
+ skill.c
+ skill_check_condition() 修正。
+ atcommand.c 修正。
+
+--------------------
+//0875 by 胡蝶蘭
+
+・party_share_levelをinter_athena.confに移した
+ (パーティ関連の処理の管轄がinter鯖のため)
+・inter_athena.confにinter_log_file項目追加
+・ギルド作成/解散/城占領/城破棄がログに残るように
+・ギルド解散時にメモリリークしていた問題を修正
+ (char/)
+ char.c/char.h
+ party_share_level関連
+ (inter/)
+ inter.c/inter.h
+ party_share_level / inter_log_file 関連
+ ログ出力用にinter_log()追加
+ int_guild.c
+ 作成/解散/城占領/城破棄をログに出力
+ メモリリーク修正
+ (doc/)
+ conf_ref.txt
+ 修正
+
+・サーバー状態確認用CGIスクリプト添付など
+ ・自己責任&詳細な解説無し、質問されてもスルーする可能性有り
+ ・エディタで開いたら少し説明有り
+ ・CGI設置の基本さえわかれば問題ないはず
+
+ (tool/cgi/)
+ serverstatus.cgi
+ サーバー状態確認用CGIスクリプト
+ addaccount.cgi
+ 説明修正
+
+--------------------
+//0874 by Kalen
+・WhiteDayイベント追加
+ conf/npc/npc_event_whiteday.txt(新規)
+ ただ、お菓子売ってるだけみたい…GMがなにやるのかは知りませんが。
+ sakROのほうではホワイトチョコらしきものが追加されたのに
+ jROで追加されたのは雛壇撤去パッチのみ(*´д`;)…
+
+・Alchemistギルドで乳鉢、製造書を変えるように
+ conf/npc/npc_job_alchemist.txt(新規)
+ 転職クエストが分からなかったので温めていましたが
+ 買えないと不便と聞いたので、追加
+
+・染色NPC実装
+ conf/npc/npc_event_dye.txt(更新)
+ 髪型変更がsakROに来たらしいので
+ なんとなーく更新
+
+--------------------
+//0873 by 死神
+
+・@コマンドitem2とkillmonster 追加。
+・スクリプトgetitem2とkillmonsterall 追加。
+・矢作成で作られた矢も製造者の名前が付くように修正。
+・battle_athena.confにmonster_class_change_full_recover追加。
+・装備スクリプトにbWeaponComaEleとbWeaponComaRace 追加。
+・少し間違いがあったダメージ計算式修正。
+・bInfiniteEndureの処理をインデュア表示なしで内部処理するように変更。
+・オートスペルでcastend_nodamage_id()を呼ぶスキルも使用できるように修正。
+・その他細かい修正とバグ修正。
+・ほとんど未テストなのでバグがあったら報告お願いします。
+ (conf/)
+ help.txt 修正。
+ atcommand_athena.conf 修正。
+ battle_athena.conf 修正。
+ char_athena.conf 修正。
+ (db/)
+ const.txt 修正。
+ item_db.txt 修正。
+ (doc/)
+ item_bonus.txt 修正。
+ script_ref.txt 修正。
+ conf_ref.txt 修正。
+ (map/)
+ map.h 修正。
+ map.c
+ map_quit() 修正。
+ skill.h 修正。
+ skill.c
+ skill_castend_nodamage_id()、skill_status_change_clear() 修正。
+ skill_castend_id()、skill_castend_pos()、skill_arrow_create() 修正。
+ skill_status_change_timer() 修正。
+ pc.c
+ pc_calcstatus()、pc_bonus2()、pc_equipitem() 修正。
+ pc_unequipitem()、pc_damage() 修正。
+ battle.h 修正。
+ battle.c
+ battle_get_dmotion()、battle_weapon_attack() 修正。
+ battle_calc_mob_weapon_attack()、battle_calc_pc_weapon_attack() 修正。
+ battle_calc_magic_attack()、battle_config_read() 修正。
+ clif.c
+ clif_parse_LoadEndAck()、clif_damage()、clif_skill_damage() 修正。
+ clif_skill_damage2() 修正。
+ itemdb.h 修正。
+ itemdb.c
+ itemdb_isequip3() 追加。
+ mob.h 修正。
+ mob.c
+ mob_delay_item_drop()、mob_damage()、mob_changestate() 修正。
+ mob_class_change()、mob_delete()、mob_catch_delete() 修正。
+ script.c
+ buildin_getitem() 修正。
+ buildin_killmonsterall_sub()、buildin_killmonsterall() 追加。
+ atcommand.h 修正。
+ atcommand.c
+ atcommand() 修正。
+ atkillmonster_sub() 追加。
+
+--------------------
+//0872 by ElFinLazz
+
+・スキルポーションピッチャー修正
+・スキルギムソバングドンボルオッネ具現
+・スキルアブラカダブなら義コーマ具現
+・コーマの武器オプション追加(種族, 千分率)
+・オプション説明追加
+ (db/)
+ const.txt 修正.
+ (doc/)
+ item_bonus.txt 修正.
+ (map/)
+ map.h 修正.
+ skill.c
+ skill_castend_nodamage_id(), skill_unit_group(), skill_status_change_start() 修正.
+ pc.c
+ pc_calcstatus(), pc_bonus2(), pc_gainexp() 修正.
+ battle.c
+ battle_weapon_attack() 修正.
+
+--------------------
+//0871 by 死神
+
+・0869のバグ修正。
+・char_athena.confとlogin_athena.confに項目追加。(キャラ鯖とログイン鯖のログファイルを変えることができるようにしました。デフォルトでlog/フォルダーに入るのでlogフォルダーを作る必要があります。)
+・エナジーコートの処理を少し修正。モンスターが使った場合はスキルレベル*6%の物理ダメージを減らすように変更。
+・武器以外の物でも製造者の名前を表示するように変更。(本鯖ではプレゼントボックスと手作りチョコレット以外は表示されませんがパケットはあることだし入れてみました。)
+・その他スキル関係の細かい修正。
+・@コマンド一つとスクリプト一つを追加しましたが説明は後のパッチで書きます。
+ (conf/)
+ char_athena.conf 修正。
+ login_athena.conf 修正。
+ (doc/)
+ conf_ref.txt 修正。
+ (login/)
+ login.c
+ parse_login()、login_config_read()、login_log() 修正。
+ (char/)
+ char.h 修正。
+ char.c
+ char_config_read()、make_new_char()、parse_char() 修正。
+ int_party.c 修正。
+ int_storage.c 修正。
+ int_guild.c 修正。
+ int_pet.c 修正。
+ (map/)
+ map.h 修正。
+ skill.c
+ skill_status_change_start()、skill_additional_effect() 修正。
+ skill_castend_nodamage_id()、skill_check_condition() 修正。
+ skill_status_change_clear()、skill_produce_mix() 修正。
+ skill_status_change_timer() 修正。
+ pc.c
+ pc_calcstatus()、pc_insert_card()、pc_additem()、pc_cart_additem() 修正。
+ storage.c
+ storage_additem() 修正。
+ battle.c
+ battle_get_adelay()、battle_get_amotion()、battle_calc_damage() 修正。
+ clif.c
+ clif_additem()、clif_equiplist()、clif_storageequiplist() 修正。
+ clif_tradeadditem()、clif_storageitemadded()、clif_use_card() 修正。
+ clif_cart_additem()、clif_cart_equiplist()、clif_vendinglist() 修正。
+ clif_openvending()、clif_arrow_create_list() 修正。
+ clif_skill_produce_mix_list()、clif_parse_SelectArrow() 修正。
+ clif_parse_ProduceMix() 修正。
+ script.c
+ buildin_produce() 修正。
+ buildin_getitem2() 追加。
+ atcommand.c
+ atcommand() 修正。
+
+--------------------
+//0870 by shuto
+
+・mapflagの攻城戦MAPにnomemo追加
+・ギルド宝箱で、宝箱出現と同時にMAP鯖が落ちる問題修正(by ぴざまん)
+
+--------------------
+//0869 by 死神
+
+・battle_athena.confにplayer_land_skill_limit、monster_land_skill_limit、party_skill_penaly 追加。
+・char_athena.confにparty_share_level 追加。
+・その他細かい修正。
+ (conf/)
+ char_athena.conf 修正。
+ battle_athena.conf 修正。
+ (doc/)
+ conf_ref.txt 修正。
+ (char/)
+ char.h 修正。
+ char.c
+ char_config_read() 修正。
+ int_party.c
+ party_check_exp_share() 修正。
+ (map/)
+ map.h 修正。
+ skill.c
+ skill_attack()、skill_castend_damage_id() 修正。
+ skill_castend_nodamage_id()、skill_status_change_start() 修正。
+ skill_castend_pos() 修正。
+ pc.c
+ pc_calcstatus() 修正。
+ mob.c
+ mobskill_castend_pos() 修正。
+ battle.h
+ battle.c
+ battle_get_adelay()、battle_get_amotion()、battle_calc_damage() 修正。
+ battle_config_read() 修正。
+ pet.c
+ pet_data_init() 修正。
+
+--------------------
+//0868 by 死神
+
+・マジックロッド実装とスペルブレイカー修正。
+・マジックロッドの場合本鯖で使ってもなんの表示もなく発動の前には使ったかどうかの確認ができないのでスキル詠唱パケット(0x13e)を利用して使用する時スキル名が出るようにしています。(本鯖と違うぞとかで文句がこないように)
+・スペルブレイカー詠唱キャンセルに関係なくskill_db.txtに設定されてるskill_typeがmagicのスキルのみ破ることができます。(ラグナゲートの説明を適用)
+・skill_db.txtの書式が変わったので注意してください。ノックバック距離の設定もできますが念の為にいっておきますがA鯖でのテストでFWのノックバック距離は2でサンクも2であることを確認しています。韓国の2003年11月19日パッチ前の鯖ではありますが2-2は適用されている所なので本鯖の違いはないと思います。
+・その他スキル関係の細かい修正。
+・0867で書き忘れ。モンスターのヒールでアンデッドモンスターが攻撃されて自滅するのでヒールやリザの場合mob_skill_db.txtのval1(値1)に1を入れるとアンデッドモンスターも攻撃を受けず回復するようになります。本鯖ではモンスターのヒールはアンデッドに関係なく回復するようです。ただ個人的にはゾンビがヒールして自滅する方が正しいと思うのでmob_skill_db.txtで設定できるようにしております。
+ (doc/)
+ db_ref.txt 修正。
+ (db/)
+ cast_db.txt 修正。
+ skill_db.txt 修正。
+ (map/)
+ skill.h 修正。
+ skill.c
+ skill_status_change_start()、skill_status_change_end() 修正。
+ skill_castend_damage_id()、skill_castend_nodamage_id() 修正。
+ skill_attack()、skill_status_change_timer()、skill_castcancel() 修正。
+ skill_unit_onplace()、skill_use_id()、skill_castend_id() 修正。
+ skill_readdb() 修正。
+ skill_get_blewcount() 追加。
+ mob.c
+ mobskill_use_id()、mob_spawn()、mob_attack() 修正。
+ battle.c
+ battle_calc_pet_weapon_attack()、battle_calc_mob_weapon_attack() 修正。
+ battle_calc_pc_weapon_attack()、battle_calc_magic_attack() 修正。
+ battle_calc_misc_attack()、battle_weapon_attack() 修正。
+ clif.c
+ clif_damage() 修正。
+ pet.c
+ pet_attack() 修正。
+ pc.c
+ pc_attack_timer()、pc_authok() 修正。
+ pc_spirit_heal()、pc_natural_heal_sub() 修正。
+
+--------------------
+//0867 by 死神
+
+・スキル関係の細かい修正。
+・battle_athena.confにplayer_undead_nofreeze追加。
+・新しいアイテムパケットに対応。(PACKETVERを5以上にする必要があります。)
+・mob_avail.txtでプレイヤーの姿を指定した時ペコペコや鷹を付けることができるように変更。頭下段次にオプションを設定できます。(ただハイディングとクローキングは指定できないようになっています。)
+ makefile 修正。
+ (conf/)
+ battle_athena.conf 修正。
+ (doc/)
+ conf_ref.txt 修正。
+ client_packet.txt 修正。
+ (map/)
+ battle.h 修正。
+ battle.c
+ battle_calc_pet_weapon_attack()、battle_calc_mob_weapon_attack() 修正。
+ battle_calc_pc_weapon_attack()、battle_config_read() 修正。
+ clif.c
+ clif_mob_class_change()、clif_spawnmob()、clif_spawnpet() 修正。
+ clif_damage()、clif_skill_damage()、clif_skill_damage2() 修正。
+ clif_itemlist()、clif_cart_itemlist()、clif_storageitemlist() 修正。
+ clif_mob0078()、clif_mob007b()、clif_pet0078()、clif_pet007b() 修正。
+ pc.c
+ pc_attack_timer() 修正。
+ skill.c
+ skill_castend_nodamage_id()、skill_additional_effect() 修正。
+ skill_status_change_start() 修正。
+ mob.h 修正。
+ mob.c
+ mobskill_castend_id()、mob_getfriendstatus_sub() 修正。
+ mob_readdb_mobavail() 修正。
+
+--------------------
+//0866 by ぴざまん
+
+・MOTDのメッセージを全て編集できるように変更。
+・クローンスキル実装。
+ ドル服のヒールアタックによるヒール習得は未テストです。
+・ギルド宝箱仮実装。
+ ヴァルキリー1のみです。
+ 商業投資による宝箱個数の算出式は適当です(初期個数4個としか知らないので)。
+ Onclockイベントで動作させています。任意の時刻に変更してください。
+・AthenaDB計画のmob_db.txtとmapflag.txtを入れておきました。
+
+ (map/)
+ pc.c
+ pc_makesavestatus()、pc_calc_skilltree() 修正。
+ pc_allskillup()、pc_calc_skillpoint() 修正。
+ pc_resetskill()、pc_authok() 修正。
+ skill.c
+ skill_attack() 修正。
+ map.h 修正。
+ (conf/)
+ gvg/TEST_prtg_cas01_AbraiJ.txt 修正。
+ motd.txt 修正。
+ mapflag.txt 修正。
+ (db/)
+ mob_db.txt 修正。
+
+--------------------
+//0865 by ぴざまん
+
+・自分が占領しているアジトのエンペリウムを攻撃できたバグ修正。
+・アブライが占領ギルドメンバー全員をマスターとみなしていたバグ修正。
+ この修正に伴ってスクリプトリファレンスに改変があります。
+ ・getcharid(0)で、自分のcharIDを返すように。
+ ・getguildmasterid(<n>)追加。
+ <n>=ギルドID
+ 該当ギルドのマスターのcharIDを返します。
+
+ (map/)
+ guild.c
+ guild_mapname2gc() 追加。
+ battle.c
+ battle_calc_damage() 修正。
+ script.c
+ buildin_getcharid() 修正。
+ buildin_getguildmasterid() 追加。
+ ローカルプロトタイプ宣言の一部を修正、追加。
+ guild.h 修正。
+
+--------------------
+//0864 by 胡蝶蘭
+
+・inter鯖のwisの処理変更
+ ・自前リンクリストからdb.hで提供されているデータベースを使用するように
+ ・WISのIDを16ビットから32ビットに増やした(パケットも修正)
+ ・メッセージのサイズチェックを入れた
+ ・パケットスキップが二回行われる可能性があるバグ修正
+
+ (char/)
+ inter.c
+ wis関係大幅変更
+ (map/)
+ intif.c
+ wis関係の修正。主にパケット処理。
+ (doc/)
+ inter_server_packet.txt
+ パケット3002,3801を変更
+
+--------------------
+//0863 by 死神
+
+・細かい修正。
+・battle_athena.confにplayer_attack_direction_change追加。
+・mob_skill_db.txtを修正する時挑発の修正を間違って修正。
+・モンスターのスキル自爆問題修正。(未テスト)
+ (conf/)
+ battle_athena.conf 修正。
+ (doc/)
+ conf_ref.txt 修正。
+ (db/)
+ mob_skill_db.txt 修正。
+ (map/)
+ mob.c
+ mobskill_use_id()、mobskill_use()、mobskill_castend_id() 修正。
+ pc.c
+ pc_skill()、pc_attack_timer() 修正。
+ skill.c
+ skill_castend_damage_id() 修正。
+ battle.h 修正。
+ battle.c
+ battle_weapon_attack()、battle_config_read() 修正。
+
+--------------------
+//0862 by 胡蝶蘭
+
+・mobスキル使用条件追加
+ ・friendhpltmaxrate : 味方のHPが指定%未満のとき(テスト済み)
+ ・friendstatuson : 味方が指定したステータス異常になっているとき
+ ・friendstatusoff : 味方が指定したステータス異常になっていないとき
+ ・mystatuson : 自分が指定したステータス異常になっているとき
+ ・mystatusoff : 自分が指定したステータス異常になっていないとき
+ ステータス系は未テストです。mob_skill_db.txtに指定方法を書いています。
+ たとえば自分が毒かどうかは mystatus,poison で、
+ ハイディング中かどうかは mystatuson,hiding で指定します。
+・mobスキル使用ターゲット追加
+ ・friend : 味方
+ ・around : 自分の周囲(現在の仕様では周囲81マス)のどれか
+ ・around1〜around4 : 自分の周囲9,25,49,81マスのどれか(範囲を明示)
+ friendは条件がfriend系(friendhpltmaxrateなど)のときに使用可能。
+ around系は場所指定スキルで使用可能。
+
+ (map/)
+ mob.c / mob.h
+ mob_getfriend*()追加、mobskill_use()修正など
+ (db/)
+ mob_skill_db.txt
+ 最初の説明のみ修正。データは修正していません。
+
+--------------------
+//0861 by いど
+
+・サーバーsnapshot
+
+--------------------
+//0860 by J
+
+・死神さんの手下召喚の修正に合わせてMOBスキルDBを修正
+(/conf)
+ mob_skill_db.txt 修正。
+
+--------------------
+//0859 by 獅子o^.^o
+Alchemist warp 修正(Aegis参考)
+(/conf)
+ (/warp)
+ npc_warp_job.txt 修正
+
+--------------------
+//0858 by 死神
+
+・細かい修正。
+・MAX_MOBSKILLを24から32に変更。(ただ少しですがまたメモリー使用量が増えます。)
+・プロボケーションで取る行動をmob_skill_db.txtのval1(値1)で設定できるように修正。
+・手下召喚で複数の種類を設定出切るように修正。(最大5つまで)
+・メタモルフォーシスとトランスフォーメーションも複数の種類を設定できるように修正。
+ (db/)
+ skill_db.txt 修正。
+ mob_skill_db.txt 修正。
+ (map/)
+ skill.c
+ skill_castend_damage_id()、skill_castend_nodamage_id() 修正。
+ map.h 修正。
+ mob.h 修正。
+ mob.c
+ mob_readskilldb()、mob_summonslave()、mob_class_change() 修正。
+
+--------------------
+//0857 by J
+
+・OWN Ragnarokにのっていた情報を元にMOBスキルを修正。
+・chase(突撃)が実装されているとのことなので突撃(?)をchaseにかえて
+コメントアウトをはずしました。
+・死神さんが実装したMOBスキルを使用するモンスターを狩場情報に載ってる情報を元に実装。
+ (/conf)
+ mob_skill_db.txt
+
+--------------------
+//0856 by 死神
+
+・バグ修正と細かい修正。
+・battle_athena.confにmonster_attack_direction_change追加。
+・battle_athena.confのbasic_skill_checkとカプラの倉庫利用を合わせていましたがいつのまにかなくなったので取り戻し。(basic_skill_checkがnoなら基本機能スキルレベルに関係なく倉庫を使えます。)
+・ピアーシングアタックの射程を3セルに変更して近接攻撃として認識するように修正。
+・A鯖でのテストでアンデッドの認識を属性によってすることがわかったのでundead_detect_typeのデフォルトを0に変更。
+・メタモルフォーシスやトランスフォーメーションで見た目がプレイヤーなら0x1b0パケットを送らないように変更。
+・ニューマバグは修正してみましたがスキルユニットの時間による作動仕様はまだ分析が完全じゃないので他の不具合が出てくるかも...
+ (conf/)
+ battle_athena.conf 修正。
+ mapflag.txt 修正。(普通のダンジョンがシーズモードであるはずがないので)
+ (conf/npc/)
+ npc_town_kafra.txt 修正。
+ (db/)
+ skill_db.txt 修正。
+ (doc/)
+ conf_ref.txt 修正。
+ script_ref.txt 修正。
+ (map/)
+ pc.c
+ pc_modifybuyvalue()、pc_modifysellvalue() 修正。
+ battle.h
+ battle.c
+ battle_calc_pet_weapon_attack()、battle_calc_mob_weapon_attack() 修正。
+ battle_calc_pc_weapon_attack()、battle_weapon_attack() 修正。
+ battle_config_read() 修正。
+ skill.c
+ skill_unitsetting()、skill_castend_damage_id() 修正。
+ skill_castend_nodamage_id() 修正。
+ mob.c
+ mob_attack() 修正。
+ pet.c
+ pet_attack() 修正。
+ clif.c
+ mob_class_change() 修正。
+
+--------------------
+//0855 by asong
+
+・メタモルフォーシスでPCとして表示するMOBを指定した場合倉落ちするバグを「暫定」修正。
+・0x1b0パケでは無く0x7bを使うことで何とかしています。
+・使い分けをしたいところですが当方Cの知識が無く条件分岐が上手くいきませんでした。
+・もしかしたらプパの孵化(羽化?)がおかしくなってるかもしれません。
+ (/map)
+ clif.c
+ mob_class_change() 修正。
+
+--------------------
+//0854 by Kalen
+
+・不足していた一次職転職クエスト追加及び、それに伴うWarp、Mob修正)
+ (/conf)
+ (/npc)
+ npc_job_archer.txt
+ npc_job_swordman.txt
+ npc_job_thief.txt(台詞修正、点数処理変更)
+ npc_job_magician.txt
+ (/warp)
+ npc_warp25.txt(一部移動)
+ npc_warp.txt(一部移動)
+ npc_warp_job.txt(新設)
+ (/mob)
+ npc_mob_job.txt
+・雛祭りQuest追加及び、それに伴うNPC修正。アマツ行き船で料金を取らなかった問題修正
+ (/conf)
+ npc_event_hinamatsuri.txt
+ npc_town_amatsu.txt
+ npc_town_guide.txt
+ npc_town_kafra.txt
+ 雛祭りを有効にするとアマツカプラをWに、
+ アルベルタ南カプラを削除にするようにしています。
+
+--------------------
+//0853 by 死神
+
+・バグ修正とNPCスキル関係の修正。
+・ダークブレスをMISC攻撃に変更。(ただ命中判定有り)
+・クリティカルスラッシュ、コンボアタック、ガイデッドアタック、スプラッシュアタック、ブラインドアタック、カースアタック、ペトリファイアタック、ポイズンアタック、サイレンスアタック、スリープアタック、スタンアタック、ランダムアタック、ダークネスアタック、ファイアアタック、グラウンドアタック、ホーリーアタック、ポイズンアタック、テレキネスアタック、ウォーターアタック、ウィンドアタック、マジカルアタック、ブラッドドレイン、メンタルブレイカーはモンスターの武器射程に変更。そしてこれらのスキルをモンスターの攻撃射程によって遠距離攻撃と近距離攻撃になるように変更。
+・ピアーシングアタックは武器射程+2に変更。
+・エナジードレイン、ハルシネーションは魔法射程に変更。
+・ダークブレッシングの射程を4に変更とかかる確率を50+スキルレベル*5%に変更。(一応これも魔法なので少し射程を広くしました。基本魔法射程である8に変えるべきなのかどうかは微妙...)
+・ガイデッドアタックはセイフティウォールとニューマを無効にする報告がありましたのでセイフティウォールとニューマが効かないように修正。
+・ディフェンダーはエフェクトだけ出るように修正。(スキルの仕様等をわかる方は情報をお願いします。)
+・トランスフォーメーション実装。(メタモーフォシスと同じ物だそうです。ただこれは全然関係ない別のモンスターになる物らしいです。ニフルヘイムに使うやつがいるみたいです。)
+・Athena雑談スレッド 其の弐の80をscript_ref.txtとして追加とちょっと修正。
+ (db/)
+ skill_db.txt 修正。
+ (doc/)
+ script_ref.txt 追加。
+ (map/)
+ battle.c
+ battle_calc_damage()、battle_calc_pet_weapon_attack() 修正。
+ battle_calc_mob_weapon_attack()、battle_calc_pc_weapon_attack() 修正。
+ battle_calc_magic_attack()、battle_calc_misc_attack() 修正。
+ skill.c
+ castend_damage_id()、castend_nodamage_id()、skill_use_pos() 修正。
+ clif.c
+ clif_spawnnpc()、clif_parse_Restart()、clif_parse_QuitGame() 修正。
+ mob.c
+ mobskill_castend_id()、mobskill_castend_pos() 修正。
+ mobskill_use_id()、mobskill_use_pos() 修正。
+
+--------------------
+//0852 by ぴざまん
+
+・亀島4F・蟻D2F・アマツD1Fをテレポ不可、シーズモードに変更。
+・nosaveの引数にSavePointが指定できてなかったので追加。
+・PVPのmapflagをmapflag.txtに統合。
+ (map/)
+ npc.c
+ npc_parse_mapflag() 修正。
+ (conf/)
+ mapflag.txt 修正。
+ npc/npc_pvp.txt 修正。
+
+--------------------
+//0851 by 胡蝶蘭
+
+・ログイン時の暗号化keyが常に同じという大きな問題があったので修正
+・ログイン管理者ログイン(ladminで使用)でパスワードの暗号化に対応
+ (login/)
+ login.c
+ login_session_data作成、暗号化keyをクライアントごとに作成など
+ (tool/)
+ ladmin
+ ver.1.05に。デフォルトでパスワードを暗号化するように。
+ 暗号化のためにDigest::MD5モジュールを使用します。
+ Digest::MD5が無い場合はパスワードの暗号化を行いません。
+ (doc/)
+ admin_packet.txt
+ ログインサーバー管理ログイン部分変更
+
+--------------------
+//0850 by 死神
+
+・NPCスキル実装。(ハルシネーション、キーピング、リック、メンタルブレイカー、プロボケーション、バリヤー、ダークブレッシング、ダークブレス)
+・スキル自爆の制限はmob_skill_db.txtでやればいいものなので取り戻し。
+・battle_athena.confにpet_hungry_friendly_decrease追加。
+・ペットの腹が完全に減ると支援攻撃を中止するように変更。
+・属性変更スキルが作動しなかった問題修正。
+・メンタルブレイカーは10+スキルレベル*5%のSPを減らす。(攻撃は通常武器スキル攻撃)
+・リックは必中でSP-100、スタン確率スキルレベル*5%。(ダメージは無し、bNoWeaponDamageで無効)
+・プロボケーションはモーションが準備されてないモンスターは入れてもなんの効果もなし。
+・ダークブレッシングはかかるとHPが1になる。耐性は魔法防御で適用。
+・ダークブレスは500+(スキルレベル-1)*1000+rand(0,1000)のダメージ。回避できるが防御無視で近距離物理攻撃だがセイフティウォールは無視して闇属性攻撃。(本鯖の計算式にあっている可能性はないかも。ただダメージ量と命中補正以外は本鯖合わせ)
+・NPCスキルの維持時間は適度に設定。
+・モンスターの属性攻撃とガイデッドアタックがセイフティウォールを無視するとの報告を受けたのですが修正するかどうかはちょっと微妙。(スプラッシュアタックもセイフティウォール無視かも)
+ (conf/)
+ battle_athena.conf 修正。
+ (doc/)
+ conf_ref.txt 修正。
+ (db/)
+ cast_db.txt 修正。
+ skill_db.txt 修正。
+ (map/)
+ mob.c
+ mob_damage() 修正。
+ clif.h 修正。
+ clif.c
+ clif_skill_estimation()、clif_damage()、clif_skill_damage() 修正。
+ clif_skill_damage2()、clif_pet_performance() 修正。
+ pet.c
+ pet_performance()、pet_target_check()、pet_hungry() 修正。
+ skill.h 修正。
+ skill.c
+ skill_additional_effect()、skill_castend_damage_id() 修正。
+ skill_castend_nodamage_id()、skill_status_change_start() 修正。
+ battle.h 修正。
+ battle.c
+ battle_get_def()、battle_get_mdef()、battle_calc_damage() 修正。
+ battle_calc_pet_weapon_attack()、battle_calc_mob_weapon_attack() 修正。
+ battle_calc_pc_weapon_attack()、battle_config_read() 修正。
+
+--------------------
+//0849 by lapis
+
+・街中のテストギルドフラグの表示がおかしかったのを修正。
+・ギルドメンバーは旗からアジトに飛べるように修正。
+ (conf/gvg)
+ TEST_prtg_cas01_AbraiJ.txt 修正。
+
+--------------------
+//0848 by huge
+
+・スキル自爆を、HPが全回復している時は使えないよう修正。
+・スフィアマイン・バイオプラント・・・ターゲット変更できない...。
+・mobにターゲット無視IDを設定できるようにしました。(Gv用mobに使える?)
+ (map/)
+ map.h 修正。
+ mob_dataに int exclusion_src,exclusion_party,exclusion_guild 追加。
+ mob.h 修正。
+ mob.c
+ mob_exclusion_add() 追加。
+ mob_exclusion_check() 追加。
+ mob_timer_delete() 追加。
+ mob_attack() 修正。
+ mob_target() 修正。
+ mob_ai_sub_hard_activesearch() 修正。
+ mob_ai_sub_hard_mastersearch() 修正。
+ mob_ai_sub_hard() 修正。
+ skill.c
+ skill_castend_damage_id() 修正。
+ skill_castend_pos2() 修正。
+
+--------------------
+//0847 by 死神
+
+・露店バグ修正。
+ (map/)
+ clif.c
+ clif_vendinglist()、clif_openvending() 修正。
+ vending.c
+ vending_openvending() 修正。
+ skill.c
+ skill_castend_nodamage_id() 修正。
+
+--------------------
+//0846 by 死神
+
+・バグ修正と細かい修正。
+・battle_athena.confのenemy_strがペットにも適用するように変更。
+・bHPDrainRateとbSPDrainRateでxがマイナスでも作動するように変更。
+・PCやNPCの姿をしたモンスターも死ぬと5秒後マップから消えるように変更。
+ (map/)
+ battle.c
+ battle_calc_pet_weapon_attack()、battle_weapon_attack() 修正。
+ skill.c
+ skill_attack()、skill_castend_damage_id() 修正。
+ pc.c
+ pc_allskillup() 修正。
+ clif.h 修正。
+ clif.c
+ clif_openvending()、do_init_clif() 修正。
+ clif_clearchar_delay()、clif_clearchar_delay_sub() 追加。
+ mob.c
+ mob_damage() 修正。
+
+--------------------
+//0845 by ぽぽぽ
+
+・mob_avail.txtでPCグラフィック(0〜23)を指定したペットが出現したときクライアントエラーがでるのを暫定修正。
+・mob_avail.txtでペットにもPCキャラの性別・髪型&色・武器・盾・頭装備を指定できるようにしました。
+・MOBのATK計算にSTRを適用するかどうか設定可能にした。
+ (map/)
+ clif.c
+ clif_pet0078()、clif_pet007b()、clif_spawnpet()修正。
+ battle.h修正。
+ battle.c
+ battle_config_read()、battle_calc_mob_weapon_attack()修正。
+
+--------------------
+//0844 by ぽぽぽ
+
+・mob_avail.txtでPCグラフィック(0〜23)を指定したMOBが出現したときクライアントエラーがでるのを暫定修正。
+・mob_avail.txtでPCキャラの性別・髪型&色・武器・盾・頭装備を指定できるようにしました。
+ グラフィックすり替え先IDが0〜23の時だけ有効で、指定方法は
+ MOB-ID,グラフィックすり替え先ID,性別(0=female,1=male),髪型,髪色,武器,盾,上段頭装備,中段頭装備,下段頭装備
+ となります。装備はitem_dbのView欄参照のこと。
+ (map/)
+ clif.c
+ clif_mob_0078()、clif_mob007b()、clif_spawnmob()修正。
+ mob.h修正。
+ mob.c
+ mob_get_sex()、mob_get_hair()、mob_get_hair_color()、ob_get_weapon()、
+ mob_get_shield()、mob_get_head_top()、mob_get_head_mid()、mob_get_head_buttom()追加。
+ mob_readdb()、mob_readdb_mobavail()修正。
+
+--------------------
+//0843 by 死神
+
+・リフレクトシールド実装。
+・アイテムスクリプトにbShortWeaponDamageReturnと
+bLongWeaponDamageReturn 追加。
+・その他スキル関係や他の所修正。
+ (db/)
+ item_db.txt 修正。
+ skill_db.txt 修正。
+ cast_db.txt 修正。
+ const.txt 修正。
+ (doc/)
+ item_bonus.txt 修正。
+ (map/)
+ map.h 修正。
+ battle.c
+ battle_get_def()、battle_get_def2()、battle_calc_pet_weapon_attack() 修正。
+ battle_calc_mob_weapon_attack()、battle_calc_pc_weapon_attack() 修正。
+ battle_weapon_attack()、battle_calc_magic_attack() 修正。
+ pc.c
+ pc_calcstatus()、pc_bonus()、pc_bonus2()、pc_equipitem() 修正。
+ pc_unequipitem()、pc_checkallowskill() 修正。
+ skill.c
+ skill_attack()、skill_unit_onplace()、skill_status_change_start() 修正。
+ skill_status_change_end()、skill_status_change_timer() 修正。
+ skill_castend_nodamage_id() 修正。
+ clif.c
+ clif_additem()、clif_equiplist()、clif_storageequiplist() 修正。
+ clif_tradeadditem()、clif_storageitemadded()、clif_cart_additem() 修正。
+ clif_cart_equiplist()、clif_vendinglist()、clif_openvending() 修正。
+ clif_damage()、clif_skill_damage()、clif_parse_LoadEndAck() 修正。
+
+--------------------
+//0842 by 死神
+
+・スキル関係の修正と細かい修正。
+・aegis鯖で色々と検証した物を適用。
+・メテオの範囲を7*7、LoV13*13、SG11*11、FN5*5に修正。
+・シグナム実装。(ただPVPでプレイヤーにかかるかどうかがわからなかったので
+かかる方向で実装。)これで1次職業のスキルはクリアかも...
+・装備スクリプトにbHPDrainRateとbSPDrainRate追加。
+・その他細かい修正少し。
+ (doc/)
+ item_bonus.txt 修正。
+ (db/)
+ cast_db.txt 修正。
+ item_db.txt 修正。
+ const.txt 修正。
+ (map/)
+ map.h 修正。
+ skill.c
+ skill_castend_damage_id()、skill_castend_nodamage_id() 修正。
+ skill_unitsetting()、skill_castend_pos2()、skill_castend_id() 修正。
+ skill_status_change_start()、skill_status_change_timer() 修正。
+ skill_status_change_end()、skill_unit_onplace() 修正。
+ skill_frostjoke_scream()、skill_attack() 修正。
+ skill_attack_area() 追加。
+ battle.c
+ battle_calc_magic_attack()、battle_get_element()、battle_get_def() 修正。
+ battle_get_def2()、battle_get_mdef()、battle_damage() 修正。
+ battle_calc_damage()、battle_calc_pet_weapon_attack() 修正。
+ battle_calc_mob_weapon_attack()、battle_calc_pc_weapon_attack() 修正。
+ battle_weapon_attack() 修正。
+ mob.c
+ mobskill_castend_id() 修正。
+ pc.c
+ pc_calcstatus()、pc_bonus2()、pc_attack_timer() 修正。
+ clif.c
+ clif_spawnmob()、clif_spawnpet()、clif_spawnnpc() 修正。
+ clif_parse_ActionRequest() 修正。
+
+--------------------
+//0841 by Kalen
+
+・水溶液が作れなかったので追加
+ conf/npc/npc_job_magician.txt
+
+--------------------
+//0840 by Kalen
+
+・鬼イベント追加
+ conf/npc/npc_event_oni.txt
+
+・map_athena.conf修正(バレンタインコメントアウト。鬼追加)
+ conf/map_athena.conf
+
+--------------------
+//0839 by shuto
+
+・コンロンNPC追加(カン ソンソンが抜けてた)
+
+--------------------
+//0838 by 死神
+
+・スキルサイトラッシャー実装。
+・モンスターのクローキングとマキシマイズパワーは持続時間をレベル*5秒に変更。
+・その他細かいバグ修正。
+ (db/)
+ skill_db.txt 修正。
+ (map/)
+ skill.c
+ skill_castend_damage_id()、skill_castend_nodamage_id() 修正。
+ skill_castend_pos2()、skill_unitsetting()、skill_get_unit_id() 修正。
+ skill_status_change_start() 修正。
+ battle.c
+ battle_calc_magic_attack() 修正。
+
+--------------------
+//0837 by 死神
+
+・スキル関係の細かい修正。
+・フロストノヴァをユニット設置式に変更。
+・ロードオブヴァーミリオンの範囲を11*11に修正と40ヒットするように変更。(ラグナゲートの情報。
+13*13説もありますが...)
+・ユピテルサンダーのノックバックを2~7に変更。
+・ストームガストの攻撃回数をレベル依存から10回に固定。
+・サンクチュアリのノックバックを3から2に変更。(aegis鯖でノックバックがあることは確認しましたがどれぐらいなのかが不明だったので少し減らしてみました。)
+・モンスターの詠唱時間が早くなっていた問題修正。(dex補正が入ってしまったせいです。)
+・その他オートスペル当たりの細かい修正。
+ (db/)
+ skill_db.txt 修正。
+ (map/)
+ skill.c
+ skill_castfix()、skill_delayfix()、skill_timerskill() 修正。
+ skill_castend_pos2()、skill_unitsetting()、skill_castend_damage_id() 修正。
+ skill_castend_nodamage_id()、skill_get_unit_id()、skill_attack() 修正。
+ battle.c
+ battle_calc_magic_attack()、battle_weapon_attack() 修正。
+
+--------------------
+//0836 by 釈尊
+
+・モンスターの取り巻き召還の間隔を中ボス以外完全修正。
+ (db/)
+ mob_skill_db.txt 修正。
+
+--------------------
+//0835 by (凸)
+
+・白刃取りでポーズをとるようにした。
+(ポーズだけなので、実際に攻撃を受け止めたりはできません)
+clif_bladestop()を呼ぶことで白刃取り状態のON、OFFのパケットが送れます。
+
+ (map/)
+ clif.h 修正。
+ clif,c
+ clif_bladestop() 追加
+ skill.c
+ skill_castend_nodamage_id() 修正。
+
+--------------------
+//0834 by 釈尊
+
+・モンスター取り巻き召還の間隔が短すぎるとの事で応急処置。(今回は黄金蟲のみ)
+ (db/)
+ mob_skill_db.txt 修正。
+
+--------------------
+//0833 by (凸)
+
+・memo禁止地域で/memo時の修正。
+・ついでにitem_dbを更新。
+
+ (doc/)
+ client_packet.txt
+ R 0189 更新。
+ (db/)
+ item_db.txt 最新版へ更新。
+ (map/)
+ pc.c
+ pc_memo() 修正。
+
+--------------------
+//0832 by 死神
+
+・コード最適化と細かい修正。
+・オートスペルを地面魔法に対応。
+・サンダーストームとヘヴンズドライブをユニット設置式に変更。
+・ディフェンダーの攻撃速度低下を本鯖にあわせ。
+・その他細かい修正。
+ (doc/)
+ item_bonus.txt 修正。
+ (db/)
+ skill_require_db.txt 修正。
+ cast_db.txt 修正。
+ (map/)
+ map.h 修正。
+ path.c
+ calc_index()、path_search() 修正。
+ skill.c
+ skill_unitsetting()、skill_castend_pos2()、skill_get_unit_id() 修正。
+ skill_status_change_timer_sub()、skill_castend_nodamage_id() 修正。
+ skill_additional_effect()、skill_frostjoke_scream() 修正。
+ pc.c
+ pc_calcstatus()、pc_skill()、pc_allskillup() 修正。
+ battle.c
+ battle_get_speed()、battle_get_adelay()、battle_get_amotion() 修正。
+ battle_weapon_attack() 修正。
+
+--------------------
+//0831 by 死神
+
+・少し修正。
+・オートスペル修正。装備による物とスキルによる物を別々に適用、発動確率修正。
+・装備によるオートスペルは指定したレベルより2つ下まで判定をします。つまりレベル5を設定するとレベル3から5まで発動します。
+・battle_athana.confのplayer_cloak_check_wall、monster_cloak_check_wallをplayer_cloak_check_type、monster_cloak_check_typeに変更。
+・アイテムルート権限時間を本鯖に合わせて修正。
+・その他スキル関係の細かい修正。
+ (doc/)
+ conf_ref.txt 修正。
+ db_ref.txt 修正。
+ item_bonus.txt 修正。
+ (conf/)
+ battle_athana.conf 修正。
+ (db/)
+ item_db.txt 修正。
+ (map/)
+ map.h
+ map.c
+ block_free_max、BL_LIST_MAX 修正。
+ skill.h 修正。
+ skill.c
+ skill_additional_effect()、skill_attack()、skill_castend_damage_id() 修正。
+ skill_castend_nodamage_id()、skill_unit_onplace() 修正。
+ skill_status_change_end()、skill_status_change_start() 修正。
+ skill_initunitgroup()、skill_unitsetting()、skill_castfix() 修正。
+ skill_delayfix()、skill_autospell()、skill_use_id()、skill_use_pos() 修正。
+ skill_check_cloaking()、skill_unit_timer_sub()、skill_check_condition() 修正。
+ battle.h 修正。
+ battle.c
+ battle_damage()、battle_get_agi()、battle_get_speed() 修正。
+ battle_get_adelay()、battle_get_amotion()、battle_get_flee() 修正。
+ battle_weapon_attack()、battle_calc_magic_attack() 修正。
+ battle_config_read() 修正。
+ mob.c
+ mob_attack()、mob_damage()、mobskill_use_id() 修正。
+ mobskill_use_pos()、mob_spawn()、mob_class_change() 修正。
+ mob_can_move() 修正。
+ pc.c
+ pc_attack_timer()、pc_checkweighticon()、pc_calcstatus() 修正。
+ pc_damage()、pc_equipitem()、pc_unequipitem() 修正。
+ pc_bonus2()、pc_bonus3()、pc_memo()、pc_authok() 修正。
+ pc_isUseitem() 修正。
+ clif.h 修正。
+ clif.c
+ clif_changeoption()、clif_parse_LoadEndAck()、clif_autospell() 修正。
+ clif_skill_memo() 修正。
+ clif_skill_teleportmessage() 追加。
+ script.c
+ buildin_sc_start() 修正。
+ atcommnad.c
+ atcommand() 修正。
+
+--------------------
+//0830 by huge
+
+・オートスペルで、自分の習得してるレベルよりも高いレベルで
+ 魔法が発動していたであろう問題を修正。
+・オートスペルでも、ちゃんとSPが減るように修正(ど忘れ)
+ (map/)
+ battle.c
+ battle_weapon_attack() 修正。
+ skill.c
+ skill_autospell() 修正。
+
+--------------------
+//0829 by Kalen
+
+・mob_db修正
+ 自鯖用のと間違ってUPしてしまったようです。
+ 本来のものに直しました。
+
+--------------------
+//0828 by 聖
+
+・サーバのSnapshot
+・MVPボス系からバカンスチケットが大量に出ていた問題を修正。
+ (common/)
+ version.h 修正。
+ (db/)
+ mob_db.txt 修正。
+
+--------------------
+//0827 by J
+
+・固定MOBのはずのメガリスと人面桃樹が歩いていたのを修正。
+・闘技場のMOBにスキルを実装。
+・パサナとファラオがスキルが設定されていなかったのを実装。
+ (db/)
+ mob_db.txt 修正。
+ mob_skill_db.txt 修正。
+
+--------------------
+//0826 by ぴざまん
+
+・一部の演奏スキルを使うと鯖が落ちるバグ修正。
+・クローキング中にスキルを使用でき、
+ 使用するとクローキングが解除されるように修正。
+・Kalenさんのmob_db.txtをまとめました。
+ (map/)
+ skill.c
+ skill_unit_onplace()、skill_unit_onout() 修正。
+ skill_status_change_start()、skill_status_change_timer() 修正。
+ skill_use_id()、skill_use_pos() 修正。
+ (db/)
+ mob_db.txt 修正。
+
+--------------------
+//0825 by 死神
+
+・細かい修正。(細かいことのわりには修正した所が多いけど...)
+・ワープポータルの中に止まった時以外はワープしないように修正。
+・battle_athena.confにplayer_skill_nofootset、monster_skill_nofootset 追加。
+・NPCをクリックした後露店をクリックして露店を閉じると動けなくなるバグ修正。
+ただ露店を閉じる時何のパケットも転送してこないので露店をクリックするとNPCの処理から抜けるようにしました。(本鯖ではNPCの処理が抜けないらしいですがそれ以外方法がなかったのっで。)
+・killmonsterのAllで召喚されたモンスターだけ消すように変更。
+・ソース最適化やスキル関係の細かい修正多数。
+ (doc/)
+ conf_ref.txt 修正。
+ db_ref.txt 修正。
+ (conf/)
+ battle_athana.conf 修正。
+ atcommnad_athena.conf 修正。
+ (db/)
+ item_db.txt 修正。
+ skill_db.txt 修正。
+ (login/)
+ parse_fromchar() 修正。
+ (map/)
+ map.h 修正。
+ clif.c
+ clif_closevendingboard()、clif_parse_VendingListReq() 修正。
+ clif_mob0078()、clif_mob007b()、clif_pet0078()、clif_pet007b() 修正。
+ skill.h 修正。
+ skill.c
+ skill_check_condition()、skill_castend_pos2() 修正。
+ skill_castend_damage_id()、skill_castend_nodamage_id() 修正。
+ skill_castend_id()、skill_status_change_start()、skill_castfix() 修正。
+ skill_delayfix()、skill_check_unit_range_sub() 修正。
+ skill_check_unit_range()、skill_castend_pos()、skill_stop_dancing() 修正。
+ skill_unit_onplace()、skill_readdb()、skill_timerskill()、skill_blown() 修正。
+ skill_check_unit_range2_sub()、skill_check_unit_range2() 追加。
+ skill_get_maxcount() 追加。
+ mob.c
+ mobskill_castend_id()、mobskill_castend_pos()、mob_deleteslave() 修正。
+ mob_stop_walking()、mob_walk()、mob_damage() 修正。
+ pc.c
+ pc_calcstatus()、pc_checkskill()、pc_stop_walking() 修正。
+ pc_walk()、pc_damage() 修正。
+ npc.c
+ npc_touch_areanpc() 修正。
+ pet.c
+ pet_stop_walking() 修正。
+ script.c
+ buildin_killmonster()、buildin_killmonster_sub() 修正。
+ battle.h
+ battle.c
+ battle_calc_magic_attack()、battle_get_flee()、battle_get_flee2() 修正。
+ battle_get_adelay()、battle_get_amotion()、battle_get_max_hp() 修正。
+ battle_get_hit()、battle_get_critical()、battle_get_atk2() 修正。
+ battle_damage()、battle_config_read() 修正。
+ atcommand.h 修正。
+ atcommand.c 修正。
+
+--------------------
+//0824 by ぴざまん
+
+・セイフティウォール・ニューマの足元置きができなかった問題修正。
+・エンペリウムにヒール等の支援スキルが効いていた問題修正。
+・闘技場でモンスターリセットができなかった問題修正。
+ killmonsterは"killmonster <mapname>,<eventname>"と記述して
+ 該当eventnameを持つモンスターを削除しますが
+ eventnameにAllと入れると該当MAPの全モンスターを消去するようにしました。
+
+ (map/)
+ skill.c
+ skill_check_unit_range_sub()、skill_castend_nodamage_id() 修正。
+ script.c
+ buildin_killmonster()、buildin_killmonster_sub() 修正。
+ (conf/npc/)
+ npc_event_tougijou.txt 修正。
+
+--------------------
+//0823 by Kalen
+
+・闘技場データ揃ったので、完成
+ conf/npc/npc_event_tougijou.txt
+ ただし、こちらでチェックしたところkillmonsterがうまくいかず、
+ 失敗、時間切れした場合モンスターリセットが出来ません。
+ イベントが設定されているモンスターは処理できないのかと思いましたが
+ AgitのほうのエンペのKillmonsterはちゃんと動いてますし…
+ 原因分かる方お願いします<(_ _)>
+
+・gon_testのmapflag追加
+ conf/mapflag.txt
+
+・mob_db更新
+ 1419〜1491が既存のMobの定義ばかりなので追加しませんでしたが、
+ 調べた所闘技場のMobのデータであることが分かりました(Dropを弄ったもの)
+ 本鯖では闇りんごが報告されています。が、こちらはDrop空白で処理しました。
+ クライアント上では同名でしたが、区別のため接頭にG_をつけて区別してます。
+ mob_skill_dbありがとうございました↓
+
+--------------------
+//0822 by ぴざまん
+
+・演奏スキルでの補正をダンサーにも適用。
+・サンクチュアリバグ修正。
+・KalenさんのMOBスキルデータベースをまとめときました。
+
+ (map/)
+ skill.c
+ skill_status_change_start()、skill_unit_onplace() 修正。
+ battle.c
+ battle_get_critical()、battle_get_hit() 修正。
+ pc.c
+ pc_calcstatus() 修正。
+
+--------------------
+//0821 by huge
+
+・オートスペル仮実装。
+・timerで判定しようかとも思いましたが、装備の無限オートスペルの為にsc_[].val1で見てマス。
+・bonus2 bAutoSpell追加。一応どんなスキルでも指定できるようにしてますが(番号はskill_tree参照)
+ skill_castend_damage_idのタイプ以外のスキルを指定しないでください。
+ 発動確率は、Lv1:50%、Lv2:35%、Lv3:15%、それ以上は 5%固定です。
+ あと、スキルレベルも指定できますが、限界を超えた数字を入れると墜ちるかもしれません。
+■書き方例:(ファイアボルトLv3の時)bonus2 bAutoSpell 19,3;
+
+ (db/)
+ const.txt 修正。
+ (map/)
+ battle.c
+ battle_weapon_attack() 修正。
+ clif.h
+ clif.c
+ packet_len_table 修正。
+ clif_autospell() 追加。
+ clif_parse_AutoSpell() 追加。
+ map.h 修正。
+ pc.c
+ pc_bonus2() 修正。
+ pc_equipitem() 修正。
+ pc_unequipitem() 修正。
+ skill.h
+ skill.c
+ skill_castend_nodamage_id() 修正。
+ skill_autospell() 追加。
+ skill_status_change_end() 修正。
+ skill_status_change_start() 修正。
+ status_changeの番号テーブル修正。
+
+--------------------
+//0820 by ぴざまん
+
+・アドリブのメッセージが入ってなかったので修正
+・バードの演奏スキルで楽器の練習や自ステータスの補正が入ってなかったのを修正。
+ struct status_changeのvalが3つ必要だったので(val4は予約されてたっぽいので)val5を追加しました
+ (map/)
+ map.h 修正。
+ skill.c
+ skill_status_change_start()、skill_castend_nodamage_id() 修正。
+ skill_castfix()、skill_delayfix() 修正。
+ battle.c
+ battle_get_flee()、battle_get_max_hp() 修正。
+ battle_get_adelay()、battle_get_amotion() 修正。
+ battle_calc_misc_attack() 修正。
+ pc.c
+ pc_calcstatus() 修正。
+
+--------------------
+//0819 by Kalen
+
+・コンロン(NPC、Warp)修正
+ conf/npc/npc_town_gonryun.txt(案内員補充)
+ conf/npc/npc_event_tougijou.txt
+ conf/warp/npc_warp_gonryun.txt(宿2FとD2Fなど)
+
+・MOB修正
+ conf/mob/npc_monster30.txt(一反木綿不足追加)
+ conf/mob/npc_monster35.txt(コンロンMob追加)
+
+・DB修正
+ db/mob_db.txt(コンロン[全て]+ウンバラ[定義]追加。Aspeed等適当です。まぁ無いよりましということで)
+ db/mob_skill_db.txt(情報を元にコンロンのMob分追加)
+ db/item_db.txt(Athena DB Project 2/19 21:10DL分)
+
+--------------------
+//0818 by あゆみ
+
+・テレポートスキルLv1で、選択ウインドウが出てこないバグを修正。
+・重量が90%以上の場合でも、一部のスキルが使用可能だったバグを修正。
+・@allskillコマンドの修正とか。
+
+ (conf/)
+ msg_athena.conf 修正。
+ (map/)
+ atcommand.c
+ atcommand() 修正。
+ pc.c
+ pc_allskillup() 修正。
+ skill.c
+ skill_castend_nodamage_id() 修正。
+ skill_check_condition() 修正。
+
+--------------------
+//0817 by huge
+
+・ディボーションの処理修正
+ ・糸の出し方はパケを貰ったのでできましたが、アイコンの方はまだ分からないです。
+ ・あと、自分の環境で2人以上に同時に掛けれなかったので、複数人にかけた場合
+ 多分0の羅列の所に2人目、3人目・・・のIDが入るんじゃないかなぁという予測でやってます。
+・ハイディング中、及びクローキング中にダメージを受けると解けるよう修正。
+
+ (map/)
+ battle.c
+ battle_damage() 修正。
+ clif.c
+ clif_devotion() 修正。
+ pc.c
+ pc_walk() 修正。
+ skill.c
+ skill.h
+ skill_castend_nodamage_id() 修正。
+ skill_devotion() skill_devotion2() 修正。
+ skill_devotion3()skill_devotion_end() 修正。
+
+--------------------
+//0816 by ぴざまん
+・ファイアーウォール3枚制限実装。
+・重ね置き禁止をプレイヤー・モンスターにも適用するように修正。
+・寒いジョーク・スクリームのPvP・GvGで、効果が自分にも及ぶバグ修正。
+ ついでにPTメンバーには低確率でかかるのも実装。
+・寒いジョーク・スクリーム・スピアブーメランのディレイ修正。
+ ミュージカルストライク・矢撃ちの詠唱時間修正。
+ (map/)
+ skill.c
+ skill_check_condition()、skill_check_unit_range_sub() 修正。
+ skill_check_unit_range()、skill_delunitgroup() 修正。
+ skill_castend_pos2()、skill_frostjoke_scream() 修正。
+ map.h 修正。
+ (db/)
+ cast_db.txt 修正。
+
+--------------------
+//0815 by 死神
+
+・0814のバグ修正と細かい修正。
+・mapflag monster_noteleport、noreturn追加とnoteleportの仕様変更。
+noteleportはプレイヤーのハエとテレポート、ワープスキルの制限をするが蝶は制限しないように変更、monster_noteleportはモンスターのテレポートを制限する物でnoreturnは蝶の使用を制限する物です。ただmapflag.txtは修正していません。(noreturnを設定する必要があります。)
+・battle_athena.confのplayer_auto_counter_typeとmonster_auto_counter_typeが説明通りに機能しなかった問題修正。
+・battle_athena.confにplayer_cloak_check_wall とmonster_cloak_check_wall 追加。
+・ボスモンスターの認識をMVP経験とmodeの0x20で行なっていた物をmodeだけにするように変更。(本鯖のイベントモンスターでMVP経験をくれるが状態異常に掛かるやつがあったらしく修正。HPが1億もあって毒じゃないと倒せなかったらしいので...) つまりMVP経験があってもボス扱いではないモンスターを作ることも可能です。
+・状態異常に掛かった状態で接続切断ができないように修正。(ただタイマーチェックではなくopt1とopt2をチェックするだけなので見た目が変わる状態異常だけに適用されます。)
+・今さらですが昔のyareCVS(2003年9月バージョン)で適用されていたラグを減らす為の処理を入れてみました。どんな効果があるかは自分でもわかりません。(ただ入れてみただけ...)
+・シーズモードとPVPで禁止装備が外されても効果が消えないバグ修正。
+・その他細かい修正。
+・未テストの物もかなりあります。
+ (common/)
+ socket.c
+ connect_client()、make_listen_port()、make_connection() 修正。
+ (conf/)
+ battle_athena.conf 修正。
+ (doc/)
+ conf_ref.txt 修正。
+ (map/)
+ battle.h 修正。
+ battle.c
+ battle_calc_mob_weapon_attack()、battle_calc_pc_weapon_attack() 修正。
+ battle_calc_magic_attack()、battle_config_read() 修正。
+ skill.c
+ skill_unit_onplace()、skill_status_change_timer() 修正。
+ skill_castend_nodamage_id()、skill_use_id() 修正。
+ skill_check_unit_range_sub()、skill_timerskill() 修正。
+ skill_additional_effect()、skill_attack()、skill_status_change_start() 修正。
+ skill_check_cloaking() 修正。
+ clif.c
+ clif_item_identify_list()、clif_parse_QuitGame()、clif_GM_kick() 修正。
+ pc.c
+ pc_attack_timer()、pc_isUseitem()、pc_checkitem 修正。
+ mob.c
+ mob_warp()、mob_walk()、mob_attack()、mob_target() 修正。
+ mob_ai_sub_hard_activesearch()、mob_ai_sub_hard_mastersearch() 修正。
+ mob_ai_sub_lazy()、mob_damage() 修正。
+ npc.c
+ npc_parse_mapflag() 修正。
+ map.h 修正。
+
+--------------------
+//0814 by 死神
+
+・バグ修正と細かい修正。
+・battle_athena.confのplayer_auto_counter_typeとmonster_auto_counter_typeの仕様を変更。(本鯖ではスキル反撃はできないみたいなので設定できるように変更。)
+・毒と石化によるHP減少を本鯖に合わせて修正と完全石化の前では動けるように変更。(毒は1秒に3+最大HPの1.5%(モンスターは0.5%)、石化は5秒に1%) 未テスト
+・MVP経験値は本鯖でいつも入るようになったので修正。
+・スティールの確率を少し下げ。
+・モンスターのハイディング、クローキング、マキシマイズパワーがすぐに解除される問題修正。(モンスターにはSPがないせいです。取り敢えずクローキングはハイディングの時間を適用してマキシマイズパワーはウエポンパーフェクションの時間を適用します。) 未テスト
+・サンクチュアリを人数から回数に変更。
+・PVPで自分のトラップに攻撃対象になるように変更。
+・vitペナルティの適用で乗算防御も減るように変更。(未テスト)
+・その他細かいバグ修正。
+ (conf/)
+ battle_athena.conf
+ (doc/)
+ conf_ref.txt
+ (db/)
+ skill_db.txt
+ (map/)
+ map.h 修正。
+ script.c
+ buildin_itemskill() 修正。
+ mob.c
+ mob_can_move()、mob_ai_sub_hard()、mob_damage() 修正。
+ skill.c
+ skill_unitsetting()、skill_unit_onplace()、skill_castend_nodamage_id() 修正。
+ skill_attack()、skill_status_change_start() 修正。
+ skill_status_change_timer()、skill_status_change_timer_sub() 修正。
+ skill_addtimerskill()、skill_cleartimerskill() 修正。
+ skill_check_unit_range_sub() 修正。
+ battle.c
+ battle_calc_damage()、battle_check_target() 修正。
+ battle_calc_pet_weapon_attack()、battle_calc_mob_weapon_attack() 修正。
+ battle_calc_pc_weapon_attack() 修正。
+ pc.c
+ pc_steal_item() 修正。
+ clif.c
+ clif_parse_WalkToXY()、clif_closevendingboard() 修正。
+
+--------------------
+//0813 by Kalen
+・コンロンNPC追加
+ conf/npc/npc_town_gonryun.txt(クエストは不明
+ conf/npc/npc_town_kafra.txt
+ conf/npc/npc_town_guide.txt
+ (conf/npc/npc_event_tougijou.txt)データ不足
+
+・全GvGMAPのMobデータ追加
+ conf/mob/npc_monster_gvg.txt
+
+・TESTmobからテストギルドフラグ移動
+ conf/gvg/TEST_prtg_cas01_AbraiJ.txt
+
+--------------------
+//0812 by huge
+
+・ディボーションの仮実装
+ ・パケットが全然分かりませんでしたので、
+ 青い糸も出ないし、アイコンすら表示できません。
+ ・ダメ移動だけで、ノックバック・オートガードは適応されません。
+
+ (map/)
+ clif.c
+ clif.h
+ clif_devotion() 追加。
+ skill.c
+ skill_castend_nodamage_id() 修正。
+ skill_devotion() skill_devotion2() 追加。
+ skill_devotion3() skill_devotion_end() 追加。
+ skill_status_change_end() 修正。
+ skill_status_change_start() 修正。
+ skill_brandishspear_first() 修正。
+ skill_brandishspear_dir() 修正。
+ pc.c
+ pc_authok() 修正。
+ pc_walk() 修正。
+ pc_damage() 修正。
+ battle.c
+ battle_damage() 修正。
+ map.h
+ map_session_data{}
+ struct square dev 追加。
+ skill.h のbrandishをsquareに改名し、
+ (common/) mmo.h に移動
+
+--------------------
+//0811 by ぴざまん
+
+・攻城中は蝶が使えるように修正
+・寒いジョーク・スクリーム実装(PTメンバーに低確率で云々は未実装です)
+・GVGスクリプトを修正(試行錯誤しすぎてどこをどうしたか覚えてません…)
+ GVGスクリプトに関してですが、既知のバグがあります
+ それは、占領ギルドマスター以外のPCが、占領ギルドマスターより先にNPCに話し掛けると
+ マップサーバが落ちるというものです。
+ これは、getguildmaster・getguildnameを使用しているスクリプトすべてに起こり得ることであり
+ 先に占領ギルドマスター以外で話し掛けると、guild_searchが何故か(該当IDのギルドがあるにも関わらず)
+ NULLを返す事に起因します。
+ 正直スクリプト関係はよくわかっていないので、これは私の技術では修正のしようがありません。
+ 暫定的な対処としてNULLを返してMAP鯖が落ちるくらいなら文字列「null」を返すようにしました。
+
+ (map/)
+ skill.c
+ skill_frostjoke_scream() 追加。
+ skill_additional_effect()、skill_timerskill() 修正。
+ skill_castend_nodamage_id() 修正。
+ pc.c
+ pc_isUseitem() 修正。
+ script.c
+ buildin_getpartyname()、buildin_getguildname() 修正。
+ buildin_getguildmaster() 修正。
+ (db/)
+ cast_db.txt 修正。
+ (conf/gvg/)
+ ev_*.txt以外のtxt全てを修正。
+
+--------------------
+//0810 by 聖
+
+・MVPの処理を変更。(確率で10000があっても、他のアイテムも出るようにしました)
+・MVPで装備を入手した場合、鑑定済みで入手していた問題を修正。
+・スキル詠唱中にイグ葉や拡大鏡を使うとプレーヤーの使用可能な
+ 全スキルのLVが1に固定されてしまう問題を修正。
+・オークアーチャー等、モンスターによる罠スキルの有効期限が切れたときに、
+ 設置用トラップが出る問題を修正。
+・ログインエラー(パスワード入力ミスやBAN等)のエラーメッセージが
+ 正しくクライアントに通知されない問題を修正。
+・その他細かな修正。
+ (common/)
+ version.h 修正。
+ (login/)
+ login.c
+ parse_login() 修正。
+ parse_fromchar() 修正。
+ (char/)
+ int_guild.c
+ mapif_parse_GuildLeave() 修正。
+ (map/)
+ itemdb.c
+ itemdb_read_itemnametable() 修正。
+ atcommand.c
+ atcommand() 修正。
+ skill.c
+ skill_unit_timer_sub() 修正。
+ script.c
+ buildin_itemskill() 修正。
+ mob.c
+ mob_damage() 修正。
+
+--------------------
+//0809 by Kalen
+
+・東湖城ワープポイント修正
+・アマツ寿司屋修正
+・バグスレ129の問題修正?
+
+
+--------------------
+//0808 by ぴざまん
+
+・装備制限が上手く動作していなかったのを修正。
+・モンハウギルドはGvG開始時に作られるように変更。
+・GvG開始時に該当マップにいる全PC(占領ギルド員以外)をセーブポイントに戻すように修正。
+・モンハウギルドアジトでエンペを壊すと、モンハウが消えるように修正。
+ この修正に伴ってmaprespawnguildidの引数のflagの仕様を変更しました
+ flagはビットフラグになり、
+ 1ビット目:占領ギルド員をセーブポイントに戻すか
+ 2ビット目:占領ギルド員以外をセーブポイントに戻すか
+ 3ビット目:エンペ・ガーディアン以外のMOBを消すか
+ いずれも、0=NO、1=YESになります
+
+ (conf/gvg/)
+ ev_agit_aldeg.txt 修正。
+ ev_agit_gefg.txt 修正。
+ ev_agit_payg.txt 修正。
+ ev_agit_prtg.txt 修正。
+ TEST_prtg_cas01_AbraiJ.txt 修正。
+ TEST_prtg_cas01_mob.txt 修正。
+ (map/)
+ pc.c
+ pc_checkitem() 修正。
+ script.c
+ buildin_maprespawnguildid_sub() 修正。
+ buildin_maprespawnguildid() 修正。
+
+--------------------
+//0807 by 死神
+
+・0805でFD_SETSIZEを修正する所を間違ったので修正しました。56名止まりが治ると言う保証はありませんが...
+・一度に転送するパケットの長さを32768bytesから65536bytesに変更。
+ (common/)
+ mmo.h 修正。
+ socket.h 修正。
+ socket.c 修正。
+
+--------------------
+//0806 by Kalen
+
+・agitフォルダ→gvgフォルダへ移行
+ 諸意見あると思いますが、jROでは攻城戦をgvgと呼ぶことが一般的なのでこちらに統合します。
+ conf/gvg/
+ ###agitフォルダを削除してください### (Please delete the "agit" folder.)
+ getmaster対応
+
+・アマツの寿司屋バグ修正と項目追加(thx 114
+ conf/npc/npc_town_amatsu.txt
+
+・map_flag再修正
+ conf/map_flag.txt
+ [GVGMAP]確かに常にシーズモードなら問題ないですが、削除されましたので
+ 常にシーズモードではありません。従って解除時(時間外)には枝、テレポが使えます
+ 時間前に枝撒き、まだ実装してませんが宝箱奪取も可能になるので枝、テレポは常に使用不可で問題ないと思います。
+
+後前回書き忘れましたが、momotaroイベントですが、ちょっと不安定な可能性があります。
+原因がわからないのですが、ループしてるかもしれません。一応コメントアウトしてあります
+
+--------------------
+//0805 by 死神
+
+・文字化け修正。
+・シーズモードでのテレポート禁止や古木の枝使用禁止はソースレベルで
+処理しているのでmapflag.txtから削除。(因みにnopenaltyもソースレベルで
+処理しています。)
+・battle_athena.confのagit_eliminate_timeをgvg_eliminate_timeに変更。
+・@コマンド@GM削除。
+・FD_SETSIZEかcygwinで64に設定されていたのせいで最大接続人数が56名を
+越えるとマップ鯖が無限ループする問題修正。(ただテストができなかった物なので本当に大丈夫になったかどうかは不明です。あくまでも自分の予測にすぎない物ですが...)
+・文字化けのせいでどこをどう修正したか覚えてないので修正したファイルだけ。
+ (conf/)
+ atcommand_athena.conf
+ battle_athena.conf
+ mapflag.txt
+ (db/)
+ castle_db.txt
+ (doc/)
+ conf_ref.txt
+ (common/)
+ mmo.h
+ (login/)
+ login.c
+ (char/)
+ inter.c
+ int_guild.c
+ (map/)
+ atcommand.h
+ atcommand.c
+ battle.h
+ battle.c
+ chrif.c
+ guild.h
+ guild.c
+ intif.h
+ intif.c
+ map.h
+ map.c
+ mob.c
+ npc.c
+ npc.h
+ script.c
+ skill.c
+ pc.c
+ makefile
+
+--------------------
+//0804 by 釈尊
+
+・アルベルタのぬいぐるみイベントでうさぎのぬいぐるみをあげるとサーバーが落ちるバグ修正
+
+ (conf/npc/)
+ npc_event_doll.txt 修正。
+
+--------------------
+//0803 by ぴざまん
+
+ GvGでエンペリウム崩壊時gvg_eliminate_timeの値に関わらず即座に退去させられていたバグ修正
+ GvGのセリフを一部修正
+ inter鯖でcastle.txtがないと起こる色々なエラーを修正
+ help.txtを修正(@gvgstart→@agitstart云々)
+
+ (conf/)
+ gvg/TEST_prtg_cas01_AbraiJ.txt 修正。
+ agit/ev_agit_prtgJ.txt 修正。
+ help.txt 修正。
+ (map/)
+ int_guild.c
+ inter_guild_init() 修正。
+
+--------------------
+//0802 by Michael_Huang
+
+ Added NPC Script - 'GetGuildMaster' Command.
+ (common/)
+ version.h
+ Mod_Version 0802
+ (map/)
+ script.c
+ buildin_getguildmaster_sub() buildin_getguildmaster()
+
+--------------------
+//0801 by Kalen
+・アマツ修正
+ 実装前のデータ、抜けてるデータなどを調査し修正
+ conf/npc/npc_town_guide.txt
+ conf/npc/npc_town_amatsu.txt
+ conf/npc/npc_event_momotaro.txt
+ conf/npc/npc_event_alchemist.txt
+ conf/mob/npc_monster35.txt
+ conf/warp/npc_warp_amatsu.txt
+・map_flag修正
+ [GVGMAP]枝、テレポは常に使用不可
+・GVG関係
+ 0800のコマンドに対応
+
+--------------------
+//0800 by Michael_Huang
+
+ Added Agit NPC Script & Command.
+ Fix FreeBSD GCC compatibility.
+ Attachment Agit Demo NPCs.
+
+ (char/)
+ int_guild.c
+ mapif_guild_castle_dataload() mapif_guild_castle_datasave()
+ int mapif_parse_GuildCastleDataLoad() int mapif_parse_GuildCastleDataSave()
+ inter_guild_parse_frommap() inter_guildcastle_tostr() inter_guildcastle_fromstr()
+ inter.c
+ inter_send_packet_length[] inter_recv_packet_length[]
+ (common/)
+ mmo.h
+ GLOBAL_REG_NUM, struct global_reg {}
+ version.h
+ Mod_Version 0799.
+ (conf/)
+ atcommand_athena.conf
+ agitstart: 1,agitend: 1
+ battle_athena.conf
+ agit_eliminate_time: 7000
+ map_athena.conf
+ conf/agit/ev_agit_*.txt
+ (doc/)
+ conf_ref.txt
+ battle_athena.cnf
+ agitdb_ref.txt
+ (login/)
+ login.c
+ parse_login()
+ (map/)
+ atcommand.h
+ agitster, agitend
+
+ atcommand.c
+ @agitstart, @agitend
+ battle.h
+ battle_config.agit_eliminate_time
+ battle.c
+ battle_config_read()
+ chrif.c
+ chrif_changedsex() chrif_connectack()
+ guild.h
+ guild_agit_start() guild_agit_end() guild_agit_break()
+ guild.c
+ guild_read_castledb() do_init_guild()
+ guild_agit_start() guild_agit_end() guild_agit_eliminate_timer() guild_agit_break()
+ intif.h
+ intif_guild_castle_dataload() intif_guild_castle_datasave()
+ intif.c
+ packet_len_table[] intif_guild_castle_dataload() intif_guild_castle_datasave()
+ intif_parse_GuildCastleDataLoad() intif_parse_GuildCastleDataSave() intif_parse()
+ map.h
+ agit_flag
+ map.c
+ agit_flag
+ npc.h
+ npc_event_doall() npc_event_do()
+ npc.c
+ npc_event_do_sub() npc_event_do()
+ script.c
+ buildin_maprespawnguildid() buildin_agitstart() buildin_agitend()
+ buildin_getcastlename() buildin_getcastledata() buildin_setcastledata()
+ skill.c
+ skill_unit_onplace()
+ skill_gangster_count()
+
+--------------------
+//0799 by ぴざまん
+
+・GvG実装の為にinter-map間の通信仕様変更
+・0798のコンパイルエラー修正(byバグ報告スレ82氏)
+ (login/)
+ login.c
+ parse_login() 修正。
+ (map/)
+ intif.c
+ packet_len_table[] 修正。
+ intif_parse_GuildCastleInfo() 修正。
+ intif_parse_GuildCastleChange()をintif_parse_GuildCastleChangeErr()に改名・修正。
+ intif_parse() 修正。
+ guild.c
+ guild_read_castledb() 修正。
+
+ (char/)
+ inter.c
+ inter_send_packet_length[] 修正。
+ int_guild.c
+ inter_guildcastle_tostr() 修正。
+ inter_guildcastle_fromstr() 修正。
+ mapif_parse_GuildChangeCastle() 修正。
+ mapif_parse_GuildCastleInfo() 修正。
+ mapif_guild_castle_info() 修正。
+ mapif_guild_change_castle()をmapif_guild_change_castle_err()に改名・修正。
+ (common/)
+ mmo.h 修正。
+ version.h 修正。
+
+--------------------
+//0798 by 胡蝶蘭
+
+・login-serverのログイン失敗パケットの長さがおかしかったのを修正
+・login-serverにアクセスコントロール機能追加
+ ・login_athena.cnfにorder,allow,denyを記述することで、
+ IP単位(前方一致)でアクセスを禁止する機能。
+ ・指定方法は doc/conf_ref.txt を参照
+
+ (doc/)
+ conf_ref.txt
+ login_athena.cnfの部分修正
+ (login/)
+ login.c
+ グローバル変数 access_* 追加
+ parse_login()修正,check_ip()追加
+
+・アカウント作成用CGIスクリプト追加
+ ・自己責任&詳細な解説無し、質問されてもスルーする可能性有り
+ ・エディタで開いたら少し説明有り
+ ・CGI設置の基本さえわかれば問題ないはず
+ ・メッセージは英語、日本語両対応
+ (Accept-Languageがjaなら日本語に変換します)
+ ・管理者パスワードなしで動くのでセキュリティには注意(.htaccessなど推奨)
+
+ (tool/cgi/)
+ addaccount.cgi
+ アカウント作成用CGI。
+
+・その他
+ (tool/)
+ backup
+ castle.txtもバックアップするように
+
+--------------------
+//0797 by 死神
+
+・少し修正。
+・battle_athena.confの項目変更。(lootitem_time 削除、item_first_get_time、
+item_second_get_time、item_third_get_time、mvp_item_first_get_time、
+mvp_item_second_get_time、mvp_item_third_get_time 追加。)
+・アイテムルート権限を正しく実装。最初攻撃ではなく与えたダメージの
+量によって収得権限を与えるように変更。(最初収得権限のみテスト)
+パーティの場合パーティの設定に合わせる必要がありますがまだパケットが
+不明な所がある為同じパーティなら収得できるようになっています。
+・ボウリングバッシュのバグ修正。(多分修正されたはず...)
+・装備スクリプトbonusにbSplashRangeとbSplashAddRange追加。
+bSplashRangeとbSplashAddRangeは武器でダメージを与えた時のみ発動、通常の武器攻撃扱いなので避けられるが(Flee2による完全回避は不可能)クリは出ないようになっていて武器による状態異常は発生しません。本鯖仕様なんて知りません。
+・スキルの重ね置き処理を本鯖に合わせて修正。
+・mapflagのgvgはいつもなっているわけじゃないので削除。
+・その他細かい修正。
+ athena-start 修正。
+ (common/)
+ mmo.h 修正。
+ (conf/)
+ mapflag.txt 修正。
+ battle_athena.conf 修正。
+ (db/)
+ const.txt 修正。
+ item_db.txt 修正。
+ (doc/)
+ conf_ref.txt 修正。
+ item_bonus.txt 修正。
+ (map/)
+ mob.c
+ mob_spawn()、mob_damage()、mob_class_change()、mob_warp() 修正。
+ mob_delay_item_drop()、mob_delay_item_drop2() 修正。
+ mobskill_castend_pos() 修正。
+ pc.c
+ pc_takeitem()、pc_dropitem()、pc_equipitem() 修正。
+ pc_calcstatus()、pc_bonus() 修正。
+ skill.c
+ skill_attack()、skill_additional_effect()、skill_castend_damage_id() 修正。
+ skill_castend_nodamage_id()、skill_produce_mix() 修正。
+ skill_arrow_create()、skill_unit_timer_sub()、skill_castend_pos() 修正。
+ map.h 修正。
+ map.c
+ map_addflooritem() 修正。
+ script.c
+ buildin_getitem() 修正。
+ pet.c
+ pet_return_egg()、pet_get_egg()、pet_unequipitem() 修正。
+ battle.h 修正。
+ battle.c
+ battle_calc_pet_weapon_attack()、battle_calc_mob_weapon_attack() 修正。
+ battle_calc_pc_weapon_attack() 修正。
+ battle_weapon_attack()、battle_config_read() 修正。
+
+--------------------
+//0796 by huge
+
+・細かい修正
+ (conf/npc/)
+ npc_event_valentine.txt 修正。
+
+ (map/)
+ pc.c
+ pc_takeitem() 修正。
+ skill.c
+ skill_unit_timer_sub() 修正。
+
+--------------------
+//0795 by Kalen
+
+・雑談341のnpc_warp_niflheim.txt追加
+ conf/warp/npc_warp_niflheim.txt
+
+・mapflag.txt修正(GVGMAP設定)
+ nosaveはしていません
+
+・map_athena.confの修正
+ umbala niflheim等の追加
+ コメントアウトしてあります。
+
+・バレンタインスクリプト追加
+ conf/npc/npc_event_valentine.txt
+ チョコ達のDropは弄っていません。お好みでどうぞ
+ 尚、手作りチョコを食べたときのエフェクトは
+ 最新クライアントにすれば見えます。
+
+・GVGScript追加
+ conf/gvg/以下
+ Weiss時代に作成したものをAthena用にコンバート&台詞修正
+ prtg_cas01以外は旗のみです。
+ GVGDATAは鯖再起動で消滅します。
+ エラーが出るようならコメントアウトしてください。
+ MobData、執事Scriptもprtg_cas01のみです。(TestScript)
+ あくまでテストスクリプトです。GVG実装の参考にどうぞ
+
+--------------------
+//0794 by DRG
+
+・skillusedで指定したIW,QMなどにのってる間MOBスキルを使用するように変更
+・アップスレ3の7のathena-startを一応含めときました
+
+ athena-start
+ (map/)
+ skill.c
+ skill_unit_onplace() 修正。
+
+--------------------
+//0793 by huge
+
+・サーバーsnapshot
+・サーバーがクラッシュするバグを修正
+・発動せずに罠が消えたら、設置用トラップが返ってくるように修正。
+・ルート権限で、同じパーティーのキャラはすぐ拾えるように修正。
+・バグ報告スレ3 >>54 のバグ修正。
+・ログイン時にサーバー側にIDを表示するようにしました。
+ (login/)
+ login.c
+ parse_login() 修正。
+ (conf/npc/)
+ npc_event_doll.txt 修正。
+ (map/)
+ skill.c
+ skill_unit_timer_sub() 修正。
+ mob.c
+ mob_spawn_dataset() 修正。
+ mob_damage() 修正。
+ pc.c
+ pc_additem() 修正。
+
+--------------------
+//0791 by 聖
+
+・マップサーバがクラッシュするバグ修正。
+・イグ葉を使ってもジェムが必要になる問題を修正。
+・PvP強制送還実装。
+・PvPでリザレクションが出来なかった問題を修正。
+・その他細かな修正。
+ (map/)
+ guild.c
+ mob.c
+ pc.c
+ skill.c
+
+--------------------
+//0790 by 死神
+
+・バグ修正。
+ (conf/)
+ battle_athena.conf 誤字修正。
+ (doc/)
+ conf_ref.txt 誤字修正。
+ (common/)
+ mmo.h 修正。
+ (map/)
+ itemdb.h 修正。
+ map.h 修正。
+ skill.c
+ skill_check_condition()、skill_use_pos()、skill_unit_onplace() 修正。
+
+--------------------
+//0789 by huge
+
+・ドロップアイテムにルート権限を実装。
+・最初に攻撃した人以外がドロップアイテムを拾えるまでの時間を設定できるように。
+ (/conf)
+ battle_athena.conf 項目追加。
+
+ (/doc)
+ conf_doc.txt 説明追加。
+
+ (/map)
+ battle.h
+ Battle_Config{} 修正。
+ battle.c
+ battle_config_read() 修正。
+ itemdb.h
+ item_data {} 修正。
+ map.h
+ flooritem_data {} 修正。
+ mob_data {} 修正。
+ map.c
+ map_addflooritem() 修正。
+ mob.c
+ delay_item_drop{} 修正。
+ mob_spawn() 修正。
+ mob_damage() 修正。
+ mob_delay_item_drop() 修正。
+ mob_warp() 修正。
+ pc.c
+ pc_takeitem() 修正。
+
+--------------------
+//0788 by あゆみ
+
+・cardスキルの処理?を修正。
+・@allskillコマンドの再修正。
+
+ (map/)
+ pc.c
+ pc_skill() 修正。
+ pc_allskillup() 修正。
+
+--------------------
+//0787 by ぽぽぽ
+
+・ペットにもmob_avail.txtの設定を適用するようにした。
+・MOBスキルのskillusedでval1に0を入れるとあらゆるスキルに対して発動するようにした。
+・skillusedで発動したスキルの対象を、発動させたPCにするかどうか設定できるようにした(対IWハメなど?)。
+ (/map)
+ clif.c
+ clif_pet0078()、clif_pet007b()修正。
+ mob.c
+ mobskill_use()修正。
+ skill.c
+ skill_attack() 、skill_castend_damage_id()修正。
+ battle.h
+ battle.c
+ battle_config_read()修正。
+
+--------------------
+//0786 by huge
+
+・BDS修正
+ 前から吹き飛ばして行くと良くないかもしれないので、後ろから処理
+ 有効範囲の修正
+
+ (/map)
+
+ skill.h
+ skill.c
+ skill_castend_damage_id() 修正。
+ skill_castend_nodamage_id() 修正。
+ skill_brandishspear_first() 修正。
+ skill_brandishspear_dir() 修正。
+
+--------------------
+//0785 by 死神
+
+・本鯖に合わせて修正。(韓国鞍のパッチnoticeを参考して修正しました。)
+・BBとBSのキャスティング時間を0.7秒にしてディレイは0に変更。
+・ghostの変わりにマップ移動後の無敵時間を設定。この時間の間はどんな攻撃も受けないが移動や攻撃、スキル使用、アイテム使用でこの時間はなくなります。シーズモードではこの無敵時間を2倍として適用。
+・シーズモードで死んでも経験が減らないように修正。(mapflagのnopenaltyを設定する必要はありません。)
+・スキッドで滑べる距離増加。
+・既に沈黙にかかってる対象にレックスディビーナを使うと沈黙が解除されるように変更。
+・呪いにかかると移動速度も減るように修正。
+・battle_athena.confに項目変更。
+・スキルの重ね置きを判断処理を少し変更。
+・HPの自然回復時間が4秒ではなく6秒だとわかったのでデフォルト修正とbattle_athena.conf修正。
+・その他細かい修正やバグ修正。
+・殆どテストしてません。
+ (conf/)
+ atcommand_athena.conf 修正。
+ battle_athena.conf 修正。
+ (doc/)
+ conf_ref.txt 修正。
+ (db/)
+ cast_db.txt 修正。
+ (map/)
+ atcommand.c 修正。
+ clif.c
+ clif_mob0078()、clif_mob007b()、clif_skill_estimation() 修正。
+ clif_mob_class_change()、clif_parse_WalkToXY() 修正。
+ clif_parse_ActionRequest()、clif_parse_LoadEndAck() 修正。
+ clif_parse_UseItem()、clif_parse_UseSkillToId() 修正。
+ clif_parse_UseSkillToPos()、clif_parse_UseSkillMap() 修正。
+ mob.h 修正。
+ mob.c
+ mob_get_viewclass()、mob_attack()、mob_target() 修正。
+ mob_ai_sub_hard_activesearch()、mob_ai_sub_hard() 修正。
+ mobskill_castend_id()、mobskill_castend_pos() 修正。
+ skill.h 修正。
+ skill.c
+ skill_can_produce_mix()、skill_castend_damage_id() 修正。
+ skill_castend_nodamage_id()、skill_castend_id() 修正。
+ skill_castend_pos()、skill_use_id()、skill_readdb() 修正。
+ skill_check_condition()、skill_unit_onplace()、skill_unitsetting() 修正。
+ skill_additional_effect()、skill_check_unit_range() 修正。
+ skill_check_unit_range_sub()、skill_status_change_end() 修正。
+ skill_status_change_start() 修正。
+ pc.h
+ pc.c
+ pc_ghost_timer()、pc_setghosttimer()、pc_delghosttimer() 削除。
+ pc_gvg_invincible_timer() -> pc_invincible_timer()に変更。
+ pc_setgvginvincibletimer() -> pc_setinvincibletimer()に変更。
+ pc_delgvginvincibletimer() -> pc_delinvincibletimer()に変更。
+ pc_authok()、pc_attack_timer()、pc_calcstatus() 修正。
+ pc_setrestartvalue()、pc_damage()、pc_allskillup() 修正。
+ do_init_pc() 修正。
+ battle.h 修正。
+ battle.c
+ battle_config_read()、battle_weapon_attack()、battle_check_target() 修正。
+ battle_calc_pet_weapon_attack()、battle_calc_mob_weapon_attack() 修正。
+ battle_calc_pc_weapon_attack()、battle_get_speed() 修正。
+ map.h 修正。
+ map.c
+ map_quit() 修正。
+
+--------------------
+//0784 by あゆみ
+
+・カードスキルを覚えている状態で@allskillコマンドを使用するとmap-serverが暴走する問題を修正。
+
+ (map/)
+ pc.c
+ pc_allskillup() 修正。
+
+--------------------
+//0783 by huge
+
+・ブランディッシュスピアの修正
+ 範囲指定、斜めの際の格子範囲、攻撃力計算
+ 多分合ってると思うんですけど、イマイチ自信が持てない・・・
+ (参考)みすとれ巣 -スキル関連豆情報
+・スペルブレイカーをちょっと修正
+・プロボックをMVPmobに効かないよう修正
+・バグ報告スレ3 >>8 で報告されたものの取り込み
+
+ (/db)
+ create_arrow.txt 修正。
+ skill_db.txt 修正。
+
+ (/map)
+ battle.c
+ battle_calc_pet_weapon_attack() 修正。
+ battle_calc_mob_weapon_attack() 修正。
+ battle_calc_pc_weapon_attack() 修正。
+
+ skill.h
+ skill.c
+ skill_brandishspear_first() 追加。
+ skill_brandishspear_dir() 追加。
+ skill_castend_nodamage_id() 修正。
+ skill_castend_damage_id() 修正。
+ skill_unit_group() 修正。
+
+--------------------
+//0782 by ぴざまん
+・@allskillコマンドの使用条件が参照されていなかったバグ修正
+ (/map)
+ atcommand.c
+ atcommand() 修正。
+
+--------------------
+//0781 by Chunglyeng
+・バド, ダンサー音楽具現
+ (/map)
+ skill.c 修正。
+
+--------------------
+//0780 by reia
+・ペコペコの卵などが孵化するとノビになってしまう問題修正。
+・GMコマンド「@kickall」が何時の間にか無効になっていたので修正。
+
+ (/conf)
+ atcommand_athena.conf 修正。
+ (db/)
+ mob_skill_db.txt 修正。
+ (map/)
+ atcommand.c
+ atcommand_config_read() 修正。
+
+--------------------
+//0779 by あゆみ
+
+・全スキル取得コマンドの追加。
+ ・GMで全スキルを覚えられるようにしている場合や、スキルの数が多い職業は一部のスキル表示がおかしくなります。その場合はリログして下さい。
+ @allskill : 現在の職業で取得可能な全スキルを取得する。(クエストスキル含む)
+
+ (conf/)
+ battle_athena.conf 修正。
+ help.txt 修正。
+ (map/)
+ atcommand.h 修正。
+ atcommand.c
+ atcommand() 修正。
+ pc.h 修正。
+ pc.c
+ pc_allskillup() 追加。
+ (doc/)
+ conf_ref.txt 修正。
+ help.txt 修正。
+
+--------------------
+//0778 by huge
+
+・スペルブレイカーの修正
+ ・キャスティングタイムの無いスキルには効かないように修正。
+ ・使用された相手の消費SPの修正。
+
+ (map/)
+ skill.c
+ skill_castend_nodamage_id() 修正。
+
+--------------------
+//0777 by ぴざまん
+
+・ギルド攻城戦仮実装
+ ・基本的な部分だけしか実装してない上、いくつかの点で本鯖と相違があります。
+ ・攻城戦開始と終了は@コマンドで行います
+ @gvgstart : 攻城戦開始
+ @gvgend : 攻城戦終了
+ ・battle_athena.confに項目追加。
+ (char/)
+ int_guild.c
+ inter_guild_init()、inter_guild_readdb()、inter_guild_save() 修正。
+ inter_castle_save_sub()、mapif_guild_castle_info() 追加。
+ mapif_guild_change_castle()、mapif_parse_GuildCastleInfo() 追加。
+ mapif_parse_GuildChangeCastle() 追加。
+ inter.c
+ inter_config_read() 修正。
+ inter_send_packet_length[]、inter_recv_packet_length[] 修正。
+ int_guild.h 修正。
+ (map/)
+ atcommand.c
+ atcommand()、atcommand_config_read() 修正。
+ battle.c
+ battle_config_read() 修正。
+ guild.c
+ guild_castle_search()、guild_read_castledb()追加。
+ do_init_guild() 修正。
+ guild_gvg_init()、guild_gvg_final()、guild_gvg_final_sub() 追加。
+ guild_gvg_eliminate()、guild_gvg_eliminate_sub() 追加。
+ guild_gvg_eliminate_timer()、guild_gvg_empelium_pos() 追加。
+ guild_gvg_break_empelium() 追加。
+ intif.c
+ intif_parse()、packet_len_table[] 修正。
+ intif_parse_GuildCastleInfo()、intif_parse_GuildCastleChange() 追加。
+ intif_guild_castle_info()、intif_guild_castle_change() 追加。
+ mob.c
+ mob_damage() 修正。
+ atcommand.h 修正。
+ battle.h 修正。
+ guild.h 修正。
+ intif.h 修正。
+ mob.h 修正。
+ (common/)
+ mmo.h 修正。
+ (conf/)
+ battle_athena.conf 修正。
+ inter_athena.conf 修正。
+ msg_athena.conf 修正。
+ atcommand_athena.conf 修正。
+ (db/)
+ castle_db.txt 追加。
+--------------------
+//0776 by 死神
+
+・NPCスキル孵化実装。(mob_skill_db.txtのval1を使います。)
+・mob_skill_db.txtの確率を千分率から万分率に変更。(ただmob_skill_db.txtの修正はしてません。)
+・モンスターがダブルアタックする問題修正。(修正されたかどうかの自信はありませんが...)
+・その他細かい修正。
+ (db/)
+ mob_skill_db.txt 修正。
+ skill_db.txt 修正。
+ (map/)
+ map.h 修正。
+ mob.h 修正。
+ mob.c
+ mob_spawn_dataset()、mob_spawn() 修正。
+ mob_changestate()、mobskill_use() 修正。
+ mob_class_change() 追加。
+ npc.c
+ npc_parse_mob() 修正。
+ battle.c
+ battle_check_target() 修正。
+ clif.h 修正。
+ clif.c
+ clif_mob_class_change() 追加。
+ skill.c
+ skill_castend_nodamage_id() 修正。
+
+--------------------
+//0775 by 死神
+
+・シーズモードの処理修正。
+・シーズモードの無敵時間の間はどんな攻撃も受けないように修正。
+・シーズモードの無敵時間が時間切れになる前には解除されないように修正。
+・battle_athena.confに項目追加。
+・@hideや/hideによるGMハイディング中は自分に自動使用されるスキル以外のスキル使用や攻撃を受けないように修正。
+・ハイディング中地属性スキル以外の攻撃を受けないように修正。(トラップやクァグマイア等のスキルは影響を受けるかどうか不明なので今までと同じように影響を受けるように処理。)
+・トンネルドライブの移動速度を本鯖に合わせました。
+・その他バグ修正や細かい修正。(殆ど未テスト)
+ (conf/)
+ battle_athena.conf 修正。
+ (doc/)
+ conf_ref.txt 修正。
+ (map/)
+ battle.h 修正。
+ battle.c
+ battle_check_target()、battle_calc_damage() 修正。
+ battle_calc_pet_weapon_attack()、battle_calc_mob_weapon_attack() 修正。
+ battle_calc_pc_weapon_attack()、battle_calc_magic_attack() 修正。
+ battle_calc_misc_attack() 修正。
+ battle_config_read()、battle_weapon_attack() 修正。
+ pc.h 修正。
+ pc.c
+ do_init_pc()、pc_authok() 修正。
+ pc_attack()、pc_attack_timer() 修正。
+ pc_setgvg_ghosttimer()、pc_delgvg_ghosttimer()を
+ pc_setgvginvincibletimer()、pc_delgvginvincibletimer()に修正。
+ pc_gvg_invincible_timer() 追加。
+ pc_attack_timer()、pc_steal_item()、pc_calcstatus() 修正。
+ clif.c
+ clif_parse_ActionRequest()、clif_parse_UseItem() 修正。
+ clif_parse_UseSkillToId()、clif_parse_UseSkillToPos() 修正。
+ clif_parse_UseSkillMap()、clif_parse_WalkToXY() 修正。
+ map.h 修正。
+ map.c
+ map_quit() 修正。
+ mob.c
+ mob_attack()、mob_target()、mob_ai_sub_hard_activesearch() 修正。
+ mob_ai_sub_hard_mastersearch()、mob_ai_sub_hard() 修正。
+ mob_damage()、mobskill_castend_id()、mobskill_castend_pos() 修正。
+ skill.c
+ skill_castend_damage_id()、skill_attack() 修正。
+ skill_castend_id()、skill_castend_pos()、skill_castend_map() 修正。
+
+--------------------
+//0774 by 獅子o^.^o
+・Monk job bouns 修正
+・ドケビ 修正
+(db/)
+ job_db2.txt 修正
+ pet_db.txt 修正
+
+--------------------
+//0773 by 聖
+
+・細かいバグ修正
+ (map/)
+ skill.c 修正。
+ battle.c 修正。
+
+--------------------
+//0772 by ぴざまん
+
+・シーズモード下で以下の点を修正
+ ・連続して攻撃できなくなっていたバグ修正
+ ・ダメージ軽減率が正しく設定できなかったバグ修正
+ ・無敵時間実装。battle_athena.confのgvg_ghost_timeで設定できます
+
+・ハイディングで魔法攻撃等を回避できなかったバグ修正
+
+ (map/)
+ skill.c
+ skill_attack()、skill_unit_onplace()、skill_check_condition() 修正。
+ clif.c
+ clif_parse_ActionRequest()、clif_parse_UseItem() 修正。
+ clif_parse_UseSkillToId()、clif_parse_UseSkillToPos() 修正。
+ clif_parse_UseSkillMap()、clif_parse_WalkToXY() 修正。
+ pc.c
+ do_init_pc()、pc_authok() 修正。
+ pc_attack()、pc_attack_timer() 修正。
+ pc_setgvg_ghosttimer()、pc_delgvg_ghosttimer() 追加。
+ pc_gvg_ghost_timer() 追加。
+ map.c
+ map_quit() 修正
+ battle.c
+ battle_config_read()、battle_weapon_attack() 修正。
+ battle.h 修正。
+ pc.h 修正。
+
+--------------------
+//0771 by huge
+
+・スペルブレイカー実装
+ (map/)
+ skill.c
+ skill_castend_nodamage_id() 修正。
+ (db/)
+ skill_db.txt 修正。
+
+--------------------
+//0770 by 聖
+
+・青箱系の処理変更
+・その他バグ修正
+ (map/)
+ battle.c 修正。
+ itemdb.c 修正。
+ mob.c 修正。
+ script.c 修正。
+
+--------------------
+//0769 by 死神
+
+・シーズモード修正。
+・無敵時間の方はghost_timer以外の方法で実装するつもりなので今は削除しています。
+・スクリプトviewpointが正しく動作しない問題修正。
+・produce_db.txtを修正。(乳鉢はskill_require_db.txtで処理しています。そしてアイテムの数を0にすれば消耗はされないけど作る時必要なアイテムになります。)
+・その他細かい修正。
+ (conf/)
+ battle_athena.conf 修正。
+ (doc/)
+ conf_ref.txt 修正。
+ (db/)
+ produce_db.txt 修正。
+ (map/)
+ battle.h 修正。
+ battle.c
+ battle_calc_damage()、battle_calc_weapon_attack() 修正。
+ battle_calc_pet_weapon_attack()、battle_calc_mob_weapon_attack() 修正。
+ battle_calc_pc_weapon_attack()、battle_calc_magic_attack() 修正。
+ battle_calc_misc_attack()、battle_check_target()、battle_config_read() 修正。
+ skill.c
+ skill_unit_onplace()、skill_castend_damage_id()、skill_castend_id() 修正。
+ skill_use_id()、skill_use_pos()、skill_check_condition() 修正。
+ skill_can_produce_mix() 修正。
+ pc.c
+ pc_attack_timer()、pc_attack()、pc_isUseitem()、pc_delitem() 修正。
+ pc_damage() 修正。
+ mob.c
+ mob_damage()、mobskill_use_id()、mobskill_use_pos() 修正。
+ script.c
+ buildin_viewpoint()、buildin_emotion() 修正。
+ makefile 修正。
+
+--------------------
+//0768 by ぴざまん
+
+・シーズモード下で、以下の点を修正
+ ・死亡したらセーブポイントに強制送還するように修正
+ ・特定のアイテムが使用できないように修正(アンティペインメント・ハエの羽)
+ ・特定のスキルが使用できないように修正(ワープポータル・バックステップ・インティミデイト・テレポート・インデュア)
+ ・同盟ギルドには無条件で攻撃できないように修正
+ ・敵対ギルドには無条件で攻撃できるように修正
+ ・無敵時間中は両者とも攻撃できないように修正
+
+ソースレベルでテレポ、ハエの使用を禁じているため攻城戦MAPのmapflagにnoteleportは必要ありません
+また、無敵時間はghost_timer依存です。つまりbattle_athena.conf内のghost_timeが無敵時間になります
+
+ (map/)
+ skill.c
+ skill_castend_damage_id()、skill_castend_id() 修正
+ skill_check_condition() 修正
+ pc.c
+ pc_damage() 修正
+ battle.c
+ battle_weapon_attack() 修正
+
+--------------------
+//0767 by huge
+
+・ファーマシーで、製造の書が減る問題を修正
+・武器製造DBで、いくつか抜けていたのを修正
+
+ (map/)
+ skill.c 修正。
+ (db/)
+ produce_db.txt 修正。
+
+--------------------
+//0766 by ぴざまん
+
+・シーズモード下で、以下の点を修正
+ ・正規ギルド承認がないとエンペリウムに攻撃が効かないように修正
+ ・エンペリウムに対するスキル攻撃が効かないように修正
+ ・魔法攻撃、遠距離攻撃、罠のダメージ補正を実装
+ 魔法攻撃:50% 遠距離攻撃:75% 罠:60%
+ これは人にもエンペリウムにも適用されます
+ (map/)
+ battle.c
+ #include "guild.h" 追加
+ battle_calc_damage()、battle_calc_weapon_attack() 修正
+
+--------------------
+//0765 by ぴざまん
+
+・装備制限実装
+・装備制限がかかった装備品は該当マップに移動した際に自動的に装備が外れ、
+ 再装備もできなくなります
+・制限できるのは装備品のみです。カード類は制限できません
+ (db/)
+ item_noequip.txt 追加
+ (map/)
+ itemdb.h 修正
+ itemdb.c
+ do_init_itemdb()、itemdb_search() 修正
+ itemdb_read_noequip 追加
+ pc.c
+ pc_checkitem()、pc_isequip() 修正
+
+--------------------
+//0764 by 死神
+
+・全てのダメージが1になる防御を10000から1000000に変更。
+・battle_athena.confに項目追加。
+・モンスターから経験値を貰う処理を本鯖のように修正。
+・スキルスローポイズン実装。
+・交換バグ修正。
+・その他細かい修正。
+・テストは殆どしてません。
+ (db/)
+ mob_db.txt 修正。
+ skill_db.txt 修正。
+ (doc/)
+ conf_ref.txt 修正。
+ db_ref.txt 修正。
+ (conf/)
+ battle_athena.conf 修正。
+ (map/)
+ makefile 修正。
+ battle.h 修正。
+ battle.c
+ battle_get_def()、battle_get_mdef() 修正。
+ battle_calc_pet_weapon_attack()、battle_calc_mob_weapon_attack() 修正。
+ battle_calc_pc_weapon_attack()、battle_calc_magic_attack() 修正。
+ battle_calc_misc_attack()、battle_config_read() 修正。
+ skill.h 修正。
+ skill.c
+ skill_castend_nodamage_id()、skill_castend_damage_id() 修正。
+ skill_check_condition()、skill_status_change_timer() 修正。
+ skill_status_change_end()、skill_status_change_start() 修正。
+ skill_additional_effect()、skill_produce_mix()、skill_unit_timer_sub() 修正。
+ skill_check_unit_sub()を skill_check_unit_range_sub()に変更。
+ skill_check_unit_range() 追加。
+ skill_castend_pos()、skill_area_sub_count() 修正。
+ mob.c
+ mobskill_castend_pos()、mob_damage() 修正。
+ clif.c
+ clif_parse_TradeRequest()、clif_parse_TradeAck() 修正。
+ clif_parse_TradeAddItem()、clif_parse_TradeOk() 修正。
+ clif_parse_TradeCansel()、clif_parse_TradeCommit() 修正。
+ map.h 修正。
+ map.c
+ do_init() 修正。
+ pc.c
+ pc_calc_skilltree()、pc_calcstatus() 修正。
+ tarde.c
+ trade_tradeack() 修正。
+
+--------------------
+//0763 by 胡蝶蘭
+
+・サーバーsnapshot
+ フォルダを整理したので注意してください。
+ 以前のセーブデータ(account.txtなど)はsaveフォルダに置いてください
+ cnfファイルは各種ファイルのパスが変わってるので、
+ この古いものをコピーではなく、新しく書き換えなおしてください。
+
+・フォルダ整理
+ confフォルダ
+ NPC関連をconf/npc/、MOB配置関連をconf/mob/、WARP関連をconf/warp/
+ テストやサンプルをconf/sample/に移動しました。
+ dbフォルダ
+ sampleフォルダのうちdb関係を db/sampleに移動しました。
+ help.txt/motd.txt
+ confフォルダに移動しました
+ account.txt/athena.txt/guild.txt/party.txt/pet.txt/storage.txt
+ saveフォルダに移動しました
+ tool/backup
+ パス修正
+
+・motd.txt/help.txtのパスをmap_athena.cnfで指定できるように
+ map.h/map.c/atcommand.c/atcommand.h修正
+
+・athena-startにrestartオプション追加
+ ./athena-start restartでAthenaを再起動します
+
+--------------
+//0761 by ぽぽぽ
+
+・MOBのMDEFに10000以上指定してもファイアピラーで普通にダメージを与えてしまうのを修正。
+ (map/)
+ battle.c
+
+--------------
+//0760 by ll3y
+
+・文字化け修正
+ (map/)
+ script.c
+
+--------------
+//0759 by 獅子o^.^o
+・スピアクイッケン 修正
+・Dancer skill tree 修正
+(db/)
+ cast_db.txt 修正
+ skill_tree.txt 修正
+
+--------------
+//0758 by hack
+・Put GM messages into msg_table which is loaded from msg_athena.conf.
+(Easy to translate into other language)
+ (map/)
+ atcommand.h
+ atcommand.c
+ msg_conf_read() Read conf/msg_athena.conf
+ Put messages into msg_table which is loaded from msg_athena.conf.
+ map.c
+ do_init()
+ (conf/)
+ msg_athena.conf Store the message of atcommand, easy to translate into other language.
+
+--------------
+//0757 by Michael
+ (map/)
+ script.c
+ buildin_viewpoint()
+ Fix packet sequence of viewpoint command.
+
+--------------
+//0756 by ll3y
+
+・Interix(Windows Services for Unix 3.5)でコンパイルが通るように修正
+ Interop Systems(http://www.interopsystems.com/)よりgmakeとzlibを取ってくるか、
+ 自前でInterix用を用意する必要があります。
+ (common/)
+ socket.h 修正。
+
+--------------
+//0755 by 死神
+
+・バグ修正と説明追加。(報告されたのは多分全て修正されたのかと...)
+・0751でスキルの最大レベルを100まで設定できるようにしました。
+・cast_db.txtに入っている状態異常の維持時間は自分が適度に入れた物です。本鯖の仕様なんて知りませんので。
+ (doc/)
+ db_ref.txt 修正。
+ (db/)
+ cast_db.txt 修正。
+ skill_db.txt 修正。
+ (map/)
+ skill.h 修正。
+ skill.c
+ skill_check_unit_sub()、skill_castend_id()、skill_use_id() 修正。
+ skill_status_change_end()、skill_status_change_start() 修正。
+ skill_castend_map() 修正。
+ mob.c
+ mobskill_castend_id()、mobskill_castend_pos() 修正。
+ pc.c
+ pc_calcstatus() 修正。
+ battle.c
+ battle_calc_pc_weapon_attack()
+ battle_calc_mob_weapon_attack()
+ battle_calc_magic_attack()
+
+--------------
+//0754 by 獅子o^.^o
+(db/)
+ cast_db.txt 修正
+
+--------------
+//0753 by 聖
+
+・IWの発生ポイントを指定するとメテオのエフェクトが一切出なくなる
+ 問題が復活していたので修正。
+・warningを出ないようにコード修正。
+ (map/)
+ skill.c
+ skill_castend_pos2() 修正。
+ chrif.h
+
+--------------
+//0752 by ぴざまん
+
+・changesexスクリプト実装。性別を反転させることができます
+ 性別反転成功後は、そのプレイヤーは強制的に接続を切断されます
+ また、ダンサー・バードの互換性はかなり怪しいです
+ ダンサー・バードがスロット内どこかに居るアカウントでの反転は、以下の点に注意して下さい
+ ・必ず反転させる前にそのキャラクターをスキルリセットしてください
+ そのまま反転させると、共通するスキル(楽器の練習等)しか残らなくなってしまいます
+ ・ダンサー・バード専用武器を装備している場合は、外してから反転させてください
+ そのまま反転させると、そのキャラクターの開始時に
+ クライアントエラーが出ます(出るだけで、落ちることはないのですが…)
+・データベース修正 by 獅子o^.^o
+ (map/)
+ chrif.c
+ packet_len_table[]、chrif_parse()修正
+ chrif_changesex()、chrif_changedsex()追加
+ chrif.h 修正
+ (char/)
+ char.c
+ parse_frommap()、parse_tologin()修正
+ (login/)
+ login.c
+ parse_fromchar()修正
+ (db/)
+ cast_db.txt 修正
+ skill_require_db.txt 修正
+
+--------------
+//0751 by 死神
+
+・skill_db.txtとcast_db.txtの変更とskill_require_db.txtの追加。
+・毒にかかるとHPが減るように変更。HPは1秒に最大HPの1%減ります。(未テスト)
+・石化を進行中の物と完全な物に分けてHPが減るように変更。(1秒に最大HPの1%)ブレッシングで完全石化だけ治せるように修正。(未テスト)
+・ハンターのトラップにエフェクト実装。ただランドマインとショックウェーブは爆発エフェクトが出ないようです。ランドマインはファイアピラーの爆発エフェクトが出るように変えています。
+・オートカウンターの方向チェックをするように変更と本鯖仕様に合わせました。
+・バックスタブも方向チェックをするように変更。
+・インティミデイトの処理変更。
+・ディフェンダーの移動速度減少を本鯖に合わせました。ASPDは勝手ながら
+(30 - (skilllv*5))%が減るようにしましたが本鯖でいくら程減るのかの情報をお願いします。
+・トンネルドライブLV1で移動速度が150から312になるのが確認されて計算を変更しましたがレベルによってどれぐらい増加するかは不明です。情報を求めます。(今の計算式は適度に作った物です。)
+・ポーション製造の計算式変更とちょっと修正。
+・一部地面スキルの重ね置きを禁止。
+・bNoMagicDamageで魔法による異常やステータスアップ効果が出ないように修正。(リザレクション以外の魔法は無効になります。)
+・battle_athena.confに項目追加。
+・その他色々と修正。
+・変更されたskill_db.txt、castdb.txtと追加されたskill_require_db.txtの構造は今の所自分しか知らないのでdb_ref.txtに説明を追加する予定なのでそれまではこれらの変更は控えてください。
+ (char/)
+ int_guild.c 修正。
+ (conf/)
+ battle_athena.conf 修正。
+ (doc/)
+ conf_ref.txt 修正。
+ (db/)
+ skill_db.txt 修正。
+ skill_require_db.txt 修正。
+ cast_db.txt 修正。
+ (map/)
+ battle.h 修正。
+ battle.c
+ battle_counttargeted()、battle_calc_pet_weapon_attack() 修正。
+ battle_calc_mob_weapon_attack()、battle_calc_pc_weapon_attack() 修正。
+ battle_weapon_attack()、battle_config_read() 修正。
+ skill.h 修正。
+ skill.c
+ skill_attack()、skill_castend_damage_id() 修正。
+ skill_castend_nodamage_id()、skill_status_change_start() 修正。
+ skill_check_condition()、skill_castend_pos() 修正。
+ skill_use_id()、skill_use_pos() 修正。
+ skill_status_change_timer()、skill_status_change_start() 修正。
+ skill_check_unit_sub() 追加。
+ pc.h 修正。
+ pc.c
+ pc_damage()、pc_counttargeted()、pc_counttargeted_sub() 修正。
+ mob.h 修正。
+ mob.c
+ mob_countslave()、mob_counttargeted()、mob_counttargeted_sub() 修正。
+ mobskill_use()、mob_can_move()、mob_damage() 修正。
+ mobskill_use_id()、mobskill_use_pos()、mobskill_castend_id() 修正。
+ mobskill_castend_pos() 修正。
+ map.c
+ map_quit() 修正。
+
+--------------
+//0750 by CHRIS
+
+・スキル関係のDBを調整
+ (db/)
+ skill_db.txt
+ cast_db.txt
+ skill_require_db.txt
+
+--------------
+//0749 by 死神
+
+・色々と変更と修正。
+・スキルの仕様変更や実装、状態異常の仕様変更や実装。
+・スキルの使用条件をdbに設定できるように変更。
+・skill_db.txtとcast_db.txtの仕様変更。
+・マップ鯖の無限ループ可能性がある部分を修正。(あくまでも可能性が
+あっただけの物です。無限ループの原因とは断言できません。)
+・トラップの発動実装。(ただ実際に動作はまだ修正していません。
+見た目が変わっただけです。)
+・battle_athena.confに項目追加を削除。
+・0748の修正削除と文字化け修正。
+・skill_db.txt、cast_db.txt、skill_require_db.txtの方がまだ完成されていないので
+かなりの量のスキルが正しく動作しません。(db_ref.txtに設定方法を入れないと
+ けないのですが時間がなかったので...) そして修正はしましたがテストは
+殆んどしてませんので注意してください。
+ (char/)
+ char.c 修正。
+ int_party.h 修正。
+ int_party.c 修正。
+ int_guild.h 修正。
+ int_guild.c 修正。
+ int_pet.h 修正。
+ int_pet.c 修正。
+ int_storage.h 修正。
+ int_storage.c 修正。
+ charの方は大した修正はしてません。
+ (conf/)
+ battle_athena.conf 修正。
+ (doc/)
+ conf_ref.txt 修正。
+ (db/)
+ skill_db.txt 修正。
+ cast_db.txt 修正。
+ skill_require_db.txt 追加。
+ produce_db.txt 修正。
+ (map/)
+ map.h 修正。
+ map.c
+ map_check_dir() 追加。
+ map_readmap()、map_addblock()、map_delblock() 修正。
+ map_foreachinarea()、map_foreachinmovearea() 修正。
+ map_addflooritem() 修正。
+ pc.h 修正。
+ pc.c
+ pc_spiritball_timer()、pc_addspiritball()、pc_delspiritball() 修正。
+ pc_steal_item()、pc_steal_coin()、pc_calcstatus() 修正。
+ pc_checkallowskill()、pc_jobchange()、pc_checkweighticon() 修正。
+ pc_damage()、pc_equipitem()、pc_walk()、pc_stop_walking() 修正。
+ pc_authok()、pc_counttargeted()、pc_counttargeted_sub() 修正。
+ pc_damage()、pc_setpos() 修正。
+ skill.h 修正。
+ skill.c
+ skill_get_range()、skill_get_sp()、skill_get_num() 修正。
+ skill_get_cast()、skill_get_delay() 修正。
+ skill_get_hp()、skill_get_zeny()、skill_get_time() 追加。
+ skill_get_time2()、skill_get_weapontype() 追加。
+ skill_get_unit_id()、skill_blown()、skill_additional_effect() 修正。
+ skill_attack()、skill_castend_damage_id() 修正。
+ skill_castend_nodamage_id()、skill_castend_id() 修正。
+ skill_castend_pos()、skill_unit_onplace() 修正。
+ skill_unit_timer_sub_onplace()、skill_unitsetting() 修正。
+ skill_use_id()、skill_use_pos()、skill_check_condition() 修正。
+ skill_status_change_end()、skill_status_change_timer() 修正。
+ skill_status_change_start()、skill_can_produce_mix() 修正。
+ skill_produce_mix()、skill_gangsterparadise() 修正。
+ skill_gangster_out()、skill_gangster_in() 修正。
+ skill_gangster_count() 追加。
+ skill_readdb() 修正。
+ battle.h 修正。
+ battle.c
+ distance()、battle_counttargeted()、battle_get_range() 追加。
+ battle_get_dir() 追加。
+ battle_get_maxhp()、battle_get_str()、battle_get_agi() 修正。
+ battle_get_vit()、battle_get_dex()、battle_get_int() 修正。
+ battle_get_luk()、battle_get_flee()、battle_get_hit() 修正。
+ battle_get_flee2()、battle_get_critical()、battle_get_baseatk() 修正。
+ battle_get_atk()、battle_get_atk2()、battle_get_def() 修正。
+ battle_get_def2()、battle_get_mdef()、battle_get_speed() 修正。
+ battle_get_adelay()、battle_get_amotion()、battle_get_party_id() 修正。
+ battle_get_guild_id()、battle_get_size() 修正。
+ battle_check_undead() 追加。
+ battle_check_target()、battle_addmastery() 修正。
+ battle_calc_pet_weapon_attack()、battle_calc_mob_weapon_attack() 修正。
+ battle_calc_pc_weapon_attack()、battle_weapon_attack() 修正。
+ clif.h 修正。
+ clif.c
+ clif_skillinfo()、clif_skillinfoblock()、clif_skillup() 修正。
+ clif_item_skill()、clif_changeoption()、clif_parse_LoadEndAck() 修正。
+ clif_01ac() 追加。
+ clif_parse_WalkToXY()、clif_parse_ActionRequest() 修正。
+ clif_parse_TakeItem()、clif_parse_DropItem() 修正。
+ mob.h 修正。
+ mob.c
+ mobskill_castend_id()、mobskill_castend_pos() 修正。
+ mobskill_use_id()、mobskill_use_pos()、mob_heal() 修正。
+ mob_spawn()、mob_damage()、mob_walk() 修正。
+ mob_stop_walking()、mob_warp()、mob_counttargeted() 修正。
+ mob_counttargeted_sub()、mob_countslave() 修正。
+ mob_attack()、mob_target()、mob_ai_sub_hard_activesearch() 修正。
+ mob_ai_sub_hard_mastersearch()、mob_ai_sub_hard() 修正。
+ script.c
+ buildin_sc_start() 修正。
+ path.c
+ can_move() 修正。
+ pet.c
+ pet_data_init()、pet_stop_walking() 修正。
+ npc.c
+ npc_parse_warp()、npc_parse_shop()、npc_parse_script() 修正。
+
+--------------
+//0748 by Michael
+ (map/)
+ pc.c
+ pc_walk();
+ Fix Player cannot move in ICEWALL but have Path.
+ mob.c
+ mob_walk();
+ Fix Monster cannot move in ICEWALL but have Path.
+ path.c
+ can_move();
+ Fix Player&Monster cannot move in ICEWALL.
+
+--------------
+//0747 by 聖
+・ペットがエモを出すとmap-serverが落ちることがあった問題を修正。
+ (map/)
+ clif_parse_SendEmotion() 修正。
+
+--------------
+//0746 by Michael
+ (map/)
+ script.c
+ Add Script command - checkoption(type);
+ Attach a npc_testchkoption.txt npc script!
+
+--------------
+//0745 by ぴざまん
+・ギャングスターパラダイス実装
+・PvPエリアのmapflagを修正(同士討ちが無くなったかと思います)
+・シーズモードでノックバックがあったバグを修正
+・インティミの遅延時間を少し調整
+ (map/)
+ skill.c
+ skill_attack()、skill_additional_effect()修正
+ skill_gangsterparadise()、skill_gangster_in()、skill_gangster_out()追加
+ clif.c
+ clif_parse_ActionRequest()修正
+ mob.c
+ mob_target()、mob_attack()修正
+ mob_ai_sub_hard()、mob_ai_sub_hard_mastersearch()修正
+ mob_ai_sub_hard_activesearch()修正
+ map.h 修正
+ skill.h 修正
+ (conf/)
+ npc_pvp.txt 修正
+
+--------------
+//0744 by 聖
+
+・アイスウォール、メテオストームのコンボでメテオストームのエフェクトが表示されなくなる問題を修正。
+・HP吸収スキルのエフェクト修正。
+・battle_athena.confに項目追加。
+・パケ周りの細かい修正。
+ (conf/)
+ battle_athena.conf
+ (doc/)
+ conf_ref.txt
+ (map/)
+ battle.c
+ battle.h
+ clif.c
+ pc.c
+ pet.c
+ skill.c
+
+--------------
+//0743 by J
+
+・取り巻き召喚などを本鯖に似せる為の修正。
+ あと本鯖相違スレにあったゴスリンの取り巻きを修正。
+ デリーターの空と地のスキルが逆になっていたのを修正。
+ (db/)
+ mob_skill_db.txt 修正
+
+--------------
+//0742 by ぴざまん
+
+・インティミデイトを実装
+ 攻撃とワープの分別がうまくいかなかったので
+ SC_INTIMIDATEを使って遅延処理をしました
+・skill_dbの誤字等を修正
+ (map/)
+ skill.c
+ skill_additional_effect()、skill_castend_map()修正
+ skill_castend_nodamage_id()、修正
+ skill_status_change_start()、skill_status_change_end()修正
+ map.h 修正
+ skill.h 修正
+ (db/)
+ skill_db.txt 修正
+
+--------------------
+//0741 by whitedog
+
+snapshot
+
+--------------
+//0740 by ぽぽぽ
+・PCがMOBにタゲられたとき3匹目から防御と回避が減るようにした。
+ 1匹につき回避は10%、防御は5%減ります。
+ (map/)
+ pc.h
+ pc.c
+ pc_counttargeted()、pc_counttargeted_sub()追加
+ battle.c
+ battle_get_flee()、battle_get_def()、battle_get_def2()修正。
+
+--------------
+//0739 by 聖
+・ファイアーウォール等の設置系スキルが正しく表示されない問題を修正。
+・マリンスフィアが自爆するとサンダーストーム等のダメージが表示されなくなる問題を修正。
+・HP吸収系スキルで敵が回復してるエフェクトが出るよう修正。
+ (map/)
+ skill.c
+ skill_castend_damage_id() 修正。
+ battle.c
+ battle_calc_misc_attack() 修正。
+ clif.c
+ clif_getareachar_skillunit() 修正。
+ clif_skill_setunit() 修正。
+
+--------------
+//0738 by ぴざまん
+・ストームガストを完全に本鯖仕様に修正(3回で絶対凍結&凍結状態の敵はSGをくらわない)
+・サフラギウムが自分にかけられるバグ修正
+ (map/)
+ skill.c
+ skill_additional_effect()、skill_attack()修正
+ skill_castend_nodamage_id()修正
+ map.h 修正
+
+--------------
+//0737 by ぽぽぽ
+・アンクルが歩いている敵に効かない&複数の敵に効くのを修正。
+ (map/)
+ skill.c
+ skill_unit_onplace()、skill_unit_onout()修正
+ mob.c
+ mob_stop_walking()修正
+
+--------------
+//0736 by ぴざまん
+・状態異常耐性が効果時間にも及ぶ様に修正。発動率と同率で効果時間が割り引かれます
+・ストーンカースの効果時間を永久からマジスレテンプレ準拠に
+・攻撃を受けた時にペットの支援攻撃を受けられないよう修正(コメントアウトしただけ)
+ これはVIT型にペットを付けて放置するだけで自動でレベル上げができるのを
+ 防ぐための暫定的な処置です
+ (map/)
+ skill.c
+ skill_castend_nodamage_id()、skill_addisional_effect()修正
+ skill_status_change_start()修正
+ pc.c
+ pc_damage() 修正
+
+--------------
+//0735 by ぽぽぽ
+
+・敵を倒してレベルが上がったときPT公平範囲のチェックをするようにした。
+・オートカウンター仮実装。
+ 向きや射程チェックはしていません。またタイミングがおかしいかもしれません。
+ MOBスキルとして使うときはターゲットをselfにしてください。
+ (conf/)
+ battle_athena.conf項目追加
+ (doc/)
+ conf_ref修正
+ (map/)
+ battle.h
+ battle.c
+ battle_calc_mob_weapon_attack()、battle_calc_pc_weapon_attack()
+ battle_config_read()修正
+ pc.c
+ pc_checkbaselevelup()、pc_attack_timer()修正
+ skill.c
+ skill_castend_nodamage_id()、skill_status_change_start()修正
+ clif.c
+ clif_parse_WalkToXY()修正
+ mob.c
+ mob_attack()修正
+
+--------------
+//0734 by 死神
+
+・player_skillup_limitの処理修正と細かい修正。
+・player_skillup_limitがyesの場合skill_tree.txtで設定されてるその下位職業の
+スキルツリーを使いますのでその職業では無くなるはずのスキルが出ることが
+ありますがこれは仕様でありバグではありません。バグ報告されても無視します。
+ (doc/)
+ conf_ref.txt 修正。
+ (char/)
+ char.c
+ mmo_char_sync_timer()、do_init() 修正。
+ inter.c
+ inter_init() 修正。
+ inter_save_timer() 削除。
+ (map/)
+ pc.c
+ pc_calc_skilltree() 修正。
+ pc_resetskill() 修正。
+
+--------------
+//0733 by 死神
+
+・バグ修正と細かい修正。
+・死んだ後にすぐにセーブポイントに戻らずにしばらく放置してると、
+放置してる時間によって経験値が減少するバグ修正。(未テスト)
+・mob_availe.txtで設定したモンスターにモンスター情報を使うち鞍落ちする問題修正。
+・battle_athena.confに項目追加。
+・その他細かい修正。
+ (conf/)
+ battle_athena.conf 修正。
+ (doc/)
+ conf_ref.txt 修正。
+ (map/)
+ clif.c
+ clif_skill_estimation()、clif_parse_Restart() 修正。
+ pc.c
+ pc_setrestartvalue()、pc_makesavestatus() 修正。
+ pc_read_gm_account()、pc_calc_skilltree() 修正。
+ pc_calc_skillpoint() 追加。
+ map.c
+ map_quit() 修正。
+ mob.c
+ mob_damage() 修正。
+ skill.c
+ skill_unit_timer_sub()、skill_unit_timer() 修正。
+ battle.h 修正。
+ battle.c
+ battle_config_read() 修正。
+
+--------------
+//0732 by Kalen
+
+・npc_town_kafra.txtの全面見直し
+ カプラ利用券の廃止
+ 倉庫利用料を本鯖(jRO)と同一価格に調整
+ カート使用料を本鯖(jRO)と同一価格に調整
+ ポイント参照変更
+ ジュノーのセーブポイント修正
+ アマツのセーブポイント修正
+
+--------------
+//0731 by ぽぽぽ
+
+・服の色を保存するかbattle_athena.confで選択できるように。
+ 弊害があるので保存しないようにと書いてあったのでデフォルトでは保存しません。
+・スクリプト追加
+ strcharinfo(1) 自分のパーティー名を取得します。
+ strcharinfo(2) 自分のギルド名を取得します。
+ getcharid(1) 自分のパーティーIDを取得します。
+ getcharid(2) 自分のギルドIDを取得します。
+ getpartyname(ID) IDで指定したパーティーの名前を取得します。
+ getguildname(ID) IDで指定したギルドの名前を取得します。
+ (map/)
+ battle.h
+ battle.c
+ battle_config_read()修正
+ pc.c
+ pc_makesavestatus()修正
+ script.c
+ buildin_strcharinfo()修正
+ buildin_getcharid()、buildin_getpartyname()、buildin_getpartyname_sub()
+ buildin_getguildname()、buildin_getguildname_sub()追加
+
+--------------
+//0730 by ぴざまん
+
+・ストームガストの凍結時間を本鯖にあわせて修正(スキルレベルに関係なく一定の凍結時間(10秒)になります)
+・スタン、暗闇、沈黙、毒の状態異常時間の「継ぎ足し」ができないように修正
+・状態異常が掛かりにくすぎてたのでMOBの状態異常耐性を緩和(また調整するかも)
+ (map/)
+ skill.c
+ skill_castend_nodamage_id()、skill_addisional_effect()修正
+ skill_status_change_start()修正
+
+--------------
+//0729 by DRG
+
+・カートレボリューションがカートなしで使えた不具合の修正
+・カートレボリューションがJOBLV30で覚えれたのを修正
+ (conf/)
+ npc_event_skillget.txt
+ カートレボリューション項修正
+ (map/)
+ skill.c
+ skill_check_condition()修正
+
+--------------
+//0728 by ぽぽぽ
+
+・職が変わってもギルドの職業欄が更新されない不具合の修正。
+
+ (char/)
+ inter.c
+ パケット長リスト修正。
+ int_guild.c
+ mapif_guild_memberinfoshort()、mapif_parse_GuildChangeMemberInfoShort()、
+ inter_guild_parse_frommap()修正
+ (map/)
+ intif.h
+ intif.c
+ intif_guild_memberinfoshort()、intif_parse_GuildMemberInfoShort()
+ intif_parse()修正
+ guild.h
+ guild_send_memberinfoshort()、guild_recv_memberinfoshort()修正
+
+
+--------------
+//0727 by 聖
+
+・武器研究スキルによってホルグレンなどの精錬NPCが
+ 正常に動作しない問題を修正。
+
+ (map/)
+ pc.c
+ pc_percentrefinery() 修正。
+
+--------------
+//0726 by 胡蝶蘭
+
+・mob_skill_db2.txtがあればmob_skill_db.txtをオーバーライドするように修正
+ オリジナルのMOB使用時や、現行MOBの使用スキルを変更したい場合に。
+
+・mob_skill_db.txtでmob_idの次のダミー文字列が"clear"だった場合、
+ そのMOBのスキルを初期化する機能追加。
+ ・mob_skill_db2.txtであるMOBのスキルを完全に書き換えるときに使用して
+ ください。
+ ・clearしなかった場合はmob_skill_db.txtのものに追加されます。
+
+ mob.c
+ mob_readskilldb()修正
+
+
+・アイテム名/MOB名が全角12文字(24バイト)あるアイテム/MOBが、
+ @コマンドで取り寄せ/召喚できない問題修正。
+ mob.c
+ mobdb_searchname()修正
+ itemdb.c
+ itemdb_searchname_sub()修正
+
+・現在時刻でイベントを起こす「時計イベント」機能を追加
+ ・OnInitと同じようにそれぞれのNPCで、On〜で始まるラベルを定義します。
+ OnMinute?? :毎時、??分にイベントを起こします。(0-59)
+ OnHour?? :毎日、??時にイベントを起こします。(0-23)
+ OnClock???? :毎日、??時??分にイベントを起こします。
+ OnDate???? :毎年、??月??日にイベントを起こします。
+ ・詳しくは npc_test_ev.txt を参照
+
+ (conf/)
+ npc_test_ev.txt
+ 内容追加
+ (map/)
+ npc.c
+ 色々修正
+
+・その他
+ clif.c
+ コンパイル警告が出ないように修正
+
+--------------
+//0725 by 死神
+
+・鯖落ちバグ修正。
+・モンスターにイベントが設定されていて自殺やなにかでダメージを与えた
+物がない場合鯖落ち確定なのでそのマップにあるプレイヤーを利用して
+イベントスクリプトを実行するように変更。
+ (map/)
+ makefile 修正。
+ mob.c
+ mob_timer()、mob_damage() 修正。
+
+--------------
+//0724 by 死神
+
+・バグ修正と安定化の為の修正。
+・ペットの攻撃でイベントが処理されず鯖落ちになる問題修正。(未テスト)
+・モンスターの大量発生で鯖が落ちる問題修正。(モンスターを10000匹を呼んで
+魔法で倒すことを5回程テスト。ただ動かないやつのみ。)
+・取り巻きがボスと一緒に死ぬ時アイテムを落とさないように変更。(未テスト)
+・battle_athena.confのpc_skillfleeをplayer_skillfreeに変更して処理を変更。
+・アイスウォールにskill_unit_settingを使うスキルで攻撃できないように修正。
+・その他細かい修正少し。安定化されたかどうかはまだわかりませんがXP1800+、512M、モンスター配置50%で10000匹召喚して異常なかったので大丈夫になったと思います。大丈夫じゃなくても責任はとれませんが...
+ (conf/)
+ battle_athena.conf 修正。
+ (doc/)
+ conf_ref.txt 修正。
+ (map/)
+ mob.h 修正。
+ mob.c
+ mob_timer()、mob_deleteslave_sub()、mob_damage() 修正。
+ npc.c
+ npc_event() 修正。
+ skill.c
+ skill_area_sub()、skill_unit_onplace()、skill_castend_nodamage_id() 修正。
+ clif.c
+ clif_parse_GMKick() 修正。
+ battle.h
+ battle.c
+ battle_damage()、battle_check_target()、battle_config_read() 修正。
+ pc.c
+ pc_calc_skilltree()、pc_checkskill() 修正。
+ map.h 修正。
+ map.c
+ map_foreachinarea()、map_foreachinmovearea() 修正。
+ map_foreachobject() 修正。
+
+--------------
+//0723 by DRG
+
+・0719の修正
+ (map/)
+ pc.c pc_calc_skilltree()修正
+
+--------------
+//0722 by パイン
+
+・gcc 2.29系列でもコンパイルが通るように修正。
+ これは以前にも直したはずなのですが、なぜか元に戻っていましたので
+ 皆さん注意をお願いします。
+ あと、gcc3系列なら定数はどこに書いても問題ないのですが、
+ gcc2.29系列では「必ずブロック要素の一番最初」に書かないとコンパイルが
+ 通りませんのでこちらもご注意願います。
+
+コンパイルが通る例
+void hoge() {
+ const char booboo = 1;
+ …
+
+コンパイルが通らない例
+void hoge() {
+ …
+ const char booboo = 1;
+ …
+
+ (map/)
+ skill.h マクロを修正
+ skill.c skill_addisional_effect()修正
+
+--------------
+//0721 by 聖
+
+・ボスにレックスデビーナが効いた問題を修正。
+・ボスにカードによる状態異常が効かなかった問題を修正。
+ 本鯖ではマリナカード等でオークヒーローなどを殴ると時々凍結します。
+ (結構微妙な実装方法なので、何か問題があった場合
+ その辺詳しい方おりましたら修正してやってください(^^; ))
+
+--------------
+//0720 by 胡蝶蘭
+
+・PCにIWを重ねるとMOBが攻撃してこない問題を修正
+ ・IWに重なっていても、隣接可能ならMOBが近寄ってきます
+ ・どんな地形にいても、隣接しているなら攻撃可能になります
+ ・ただし、MOBが遠距離攻撃可能で、攻撃範囲内にPCがいても、
+ 隣接不可能なら攻撃してきません。これの解決はかなり面倒なので。
+
+ mob.c
+ mob_can_reach()修正
+ battle.c
+ battle_check_range()修正
+
+--------------
+//0719 by DRG
+
+・下位スキルがない場合は上位スキルがふれないようにしました。
+ battle_athena.confのskillfleeで設定可能です。
+ 下位スキルがないまま上位スキルをふった状態で、このオプションを使う場合はスキルリセットする必要があります。
+ 一般アカにスキルリセットを解放したいときに使ってやって下さい。
+ (conf/)
+ battle_athena.conf
+ (map/)
+ battle.c
+ battle.h
+ pc.c pc_calc_skilltree(),pc_checkskill()修正
+
+--------------
+//0718 by 死神
+
+・色々と修正。
+・毒によって防御が減るように変更。(HPはまだ減りません。)
+・アイスウォールに攻撃できるように変更。(今は全ての攻撃に当たります。)
+ただ鞍のバグらしくアイスウォールをクリックすると鞍から0x89パケットが30回以上連続で送ってくることが起こりますが原因は不明です。多分鞍のバグだと思いますが...)
+・戦闘に関わる計算等を修正。
+・ゼニが増えるバグ修正。(多分これでこのバグはなくなると思いますがどうなのか報告をお願いします。)
+・二刀流の左手武器の種族、属性、Sizeのダメージ補正を右手武器に適用するかどうかを設定できるように変更。
+・その他修正はしたはずですが覚えてません。(修正してない物もありますがdiff当ての途中でどれを作業したのかを忘れたので...)
+ (common/)
+ mmo.h 修正。
+ (conf/)
+ battle_athena.conf 修正。
+ (doc/)
+ conf_ref.txt 修正。
+ (map/)
+ battle.h 修正。
+ battle.c
+ battle_get_baseatk()、battle_get_speed()、battle_get_adelay() 追加。
+ battle_get_amotion() 、battle_calc_pet_weapon_attack() 修正。
+ battle_calc_mob_weapon_attack()、battle_calc_pc_weapon_attack() 修正。
+ battle_get_atk()、battle_get_atk_()、battle_get_atk2() 修正。
+ battle_get_attack_element()、battle_get_attack_element2() 修正。
+ battle_get_str()、battle_get_agi()、battle_get_vit()、battle_get_int() 修正。
+ battle_get_dex()、battle_get_luk()、battle_get_flee() 修正。
+ battle_get_flee2()、battle_get_hit()、battle_get_critical() 修正。
+ battle_get_def()、battle_get_def2()、battle_get_mdef() 修正。
+ battle_get_element()、battle_check_target()、battle_check_range() 修正。
+ battle_weapon_attack()、battle_config_read() 修正。
+ clif.c
+ clif_skill_estimation()、clif_mob0078()、clif_mob007b() 修正。
+ pc.c
+ pc_attack_timer()、pc_attack()、pc_calcstatus()、pc_payzeny() 修正。
+ pc_getzeny() 修正。
+ npc.c
+ npc_buylist()、npc_selllist() 修正。
+ pet.c
+ pet_attack()、pet_randomwalk()、pet_ai_sub_hard() 修正。
+ mob.h 修正。
+ mob.c
+ calc_next_walk_step()、mob_attack()、mobskill_castend_id() 修正。
+ mobskill_use_id()、mobskill_use_pos()、mob_ai_sub_hard() 修正。
+ mob_damage()、mob_changestate() 修正。
+ mob_get_adelay()、mob_get_speed() 削除。
+ skill.h 修正。
+ skill.c
+ skill_unitsetting()、skill_unit_ondamaged()、skill_unit_timer_sub() 修正。
+ skill_unit_timer()、skill_area_sub()、skill_unit_onplace() 修正。
+ skill_status_change_start() 修正。
+ chat.c 修正。
+ makefile 修正。
+ chrif.c 修正。
+ guild.c 修正。
+ itemdb.c 修正。
+ map.c 修正。
+ party.c 修正。
+ script.c 修正。
+ path.c 修正。
+
+--------------
+//0717 by 聖
+
+・大量にモンスターを召還して一度に倒すとmap-serverが落ちる問題を修正。
+ (カホを100体ずつ召還して50回テストをしたので恐らく大丈夫だと思います。)
+・その他結構細かい修正
+ (common/)
+ mmo.h
+ (map/)
+ chat.c
+ chrif.c
+ clif.c
+ guild.c
+ itemdb.c
+ map.c
+ mob.c
+ npc.c
+ party.c
+ path.c
+ pc.c
+ pet.c
+ script.c
+ skill.c
+ skill.h
+
+--------------
+//0716 by 聖
+
+・精錬成功率に対してBSの武器研究が正しく適用されていなかった問題を修正。
+ (map/)
+ pc.c
+ pc_percentrefinery() 修正。
+
+--------------
+//0715 by 死神
+
+・マップサーバーから表示される物を表示するかどうかの設定ができるようにしました。スキル表示だけでもなくしてやるとサーバーがかなり楽になったりもします。
+開発やバグトレースの時は表示することをお勧めします。
+・その他細かい修正。
+・修正した所を全て書けないのでファイルだけ。
+ (doc/)
+ conf_ref.txt
+ (conf/)
+ battle_athena.conf
+ (map/)
+ makefile
+ skill.c
+ script.c
+ pet.c
+ pc.c
+ path.c
+ party.c
+ npc.c
+ itemdb.c
+ intif.c
+ guild.c
+ chat.c
+ battle.h
+ battle.c
+ chrif.c
+ atcommand.c
+ clif.c
+ mob.c
+ map.c
+
+--------------
+//0714 by 死神
+
+・細かい修正。
+・シールドブーメランで盾の重量と精錬によってダメージが増えるように修正。精錬ダメージを足す時適用でダメージ+重量+盾精錬*4(この4はrefine_db.txtの防具の過剰精錬ボーナスを使ってるので変更可能です。)になります。
+・スキルによる吹き飛ばし処理で0x88パケットを使っていましたがそのパケットの優先順位がかなり低いらしく後で来るパケットによって無視されることもあるようなのでプレイヤーだけに適用してモンスターには0x78を使うように変更しました。
+でも位置ずれは完全になくならないようです。(恐らく鞍のバグだと思います。鯖の
+座標を確認してみましたが鯖の方は問題がありませんでした。)
+プレイヤーの場合0x78(PACKETVERが4以上なら0x1d8)が使えません。分身を作ってしまうので...
+・バグ報告スレッド2 の47を取り込みました。
+・その他修正した所少しあり。
+ (db/)
+ refine_db.txt 修正。
+ item_db.txt 修正。
+ (map/)
+ battle.c
+ battle_stopattack()、battle_stopwalking() 修正。
+ battle_get_attack_element2()、battle_calc_pc_weapon_attack() 修正。
+ battle_weapon_attack() 修正。
+ path.c
+ path_blownpos() 修正。
+ pc.h 修正。
+ pc.c
+ pc_stop_walking()、pc_damage() 修正。
+ pc_getrefinebonus() 追加。
+ mob.c
+ mob_damage() 修正。
+ pet.c
+ pet_target_check()、pet_stop_walking()、pet_performance() 修正。
+ skill.c
+ skill_attack()、skill_blown()、skill_status_change_start() 修正。
+ skill_castend_damage_id() 修正。
+ makefile 修正。
+
+--------------
+//0713 by ぽぽぽ
+
+・mob_avail.txt追加。item_avail.txtと同様の指定でモンスターの見た目を他のIDのものに変更します。
+ モンスターのID以外を指定したりするとPCやNPCの姿をしたMOBに一方的に攻撃される場合があるので注意。
+ (db/)
+ mob_avail.txt 追加。
+ (map/)
+ clif.c
+ clif_mob0078()、clif_mob007b() 修正。
+ mob.h 修正。
+ mob.c
+ mob_readdb_mobavail()、mob_get_viewclass()追加。
+ do_init_mob()、mob_readdb() 修正。
+
+--------------
+//0712 by 死神
+
+・シールドチャージ、シールドブーメラン実装。
+・オートガードはとりあえずエフェクトが出るように変更しました。
+・0708で書き忘れ。ディフェンダーを使った時ASPDと移動速度は20%低下します。
+本鯖で低下するのは確かのようですがどれぐらい下がるのかはさっぱりわかりまんので...
+・その他細かい修正。
+ (db/)
+ cast_db.txt 修正。
+ skill_db.txt 修正。
+ (map/)
+ battle.h 修正。
+ battle.c
+ battle_calc_damage()、battle_calc_pet_weapon_attack() 修正。
+ battle_calc_mob_weapon_attack()、battle_calc_pc_weapon_attack() 修正。
+ battle_calc_magic_attack()、battle_calc_misc_attack() 修正。
+ skill.c
+ skill_additional_effect()、skill_attack()、skill_castend_nodamage_id() 修正。
+ skill_check_condition()、skill_status_change_start() 修正。
+ skill_castend_damage_id() 修正。
+ pc.h 修正。
+ pc.c
+ pc_calcstatus()、pc_checkallowskill()、pc_unequipitem() 修正。
+
+--------------
+//0711 by npc
+
+・鉱石製造エフェクトの修正
+・スクリプト埋め込み変数にHp,MaxHp,Sp,MaxSpを追加(読み込みのみ)
+ (map/)
+ skill.c
+ skill_produce_mix()修正。
+ pc.c
+ pc_readparam()修正。
+ (db/)
+ const.txt 修正。
+
+--------------
+//0710 by 胡蝶蘭
+
+・名前に半角スペースが入ったパーティーを作成したとき、および、
+ 半角スペースが入った名前のPCをパーティメンバにしたとき、
+ party.txtが正しく読み込めなくなる問題を修正
+
+ (char/)
+ int_party.c
+ inter_party_fromstr()修正
+
+・Message of the Day 機能追加
+ ・ログインしたユーザーにMOTDを表示させることが出来ます。
+ ・map-server.exe実行時のカレントディレクトリ(help.txtと同じ
+ ディレクトリ)にmotd.txtを作ると表示します。
+ ・MOTDが表示されるタイミングは、
+ 「マップサーバーにログインした直後の、マップロード完了時」です。
+ つまり、ログイン直後、キャラセレ直後および、
+ マップサーバー間移動の時(マップサーバーの分散を行っている場合のみ)
+ のマップロードが終わった時に表示されます。
+ ・表示方法はhelp.txtと同じで普通のメッセージとして送信します。
+ (ギルド告知メッセージは文字数制限があり、GMアナウンスは長時間
+ 画面の上部に表示されてしまうため)
+ ・会話と区別がつくように、"< Message of the Day >"、"< End of MOTD >"
+ の文で上下を囲います。
+
+ (map/)
+ pc.c
+ pc_authok()修正
+
+
+--------------
+//0709 by ぽぽぽ
+
+・スクリプトにemotion追加
+ emotion n;と使うとNPCがエモを出します。nは0〜33が使用可能。
+・精錬と街ガイドのNPCを本鯖の台詞に合わせて修正。
+ (conf/)
+ npc_town_refine.txt、npc_town_guide.txt 修正。
+ (map/)
+ script.c
+ buildin_emotion() 追加。
+
+--------------
+//0708 by 死神
+
+・スキルキャストキャンセル、ディフェンダー、オートガード実装。
+・オートガードの場合ガードしてもエフェクトは出ません。ミスになるだけです。本鯖の方は表示されるかどうかもわからないしパケット情報もないので...
+・ディフェンダーは未テスト。bLongAtkDefを使ってるのでホルンカードのようにbLongAtkDefを上げる物を装備して使うと遠距離物理攻撃を全て無効にできます。(これも本鯖の仕様がどうなのかはわかりません。)
+・その他細かい修正。
+ (db/)
+ cast_db.txt 修正。
+ (map/)
+ map.h 修正。
+ map.c
+ map_quit() 修正。
+ skill.h 修正。
+ skill.c
+ skill_castend_nodamage_id()、skill_use_id()、skill_check_condition() 修正。
+ skill_castend_id()、skill_castend_nodamage_id()、skill_castcancel() 修正。
+ pc.c
+ pc_calcstatus()、pc_setpos()、pc_damage() 修正。
+ battle.c
+ battle_calc_damage()、battle_damage() 修正。
+ clif.c
+ clif_parse_UseSkillToId()、clif_parse_UseSkillToPos() 修正。
+ mob.c
+ mob_damage() 修正。
+ itemdb.c
+ itemdb_searchrandomid() 修正。
+
+--------------
+//0707 by 死神
+
+・0705の阿修羅覇鳳拳のバグ修正。
+ (db/)
+ skill_db.txt
+ (map/)
+ skill.c
+ skill_castend_id()
+ skill_castend_pos()
+ battle.c
+ battle_calc_pc_weapon_attack()
+ clif.c
+ clif_parse_UseSkillToId()
+
+--------------
+//0706 by kalen
+・修正
+ conf/npc_warp_umbala.txt
+
+--------------
+//0705 by 死神
+
+・色々と修正。
+・プレイヤーのクリティカル計算にバグがあったので修正。
+・爆裂波動の処理修正。
+・モンクのコンボを修正。
+・阿修羅覇鳳拳の使用によってマップ鯖の無限ループバグ修正。(これかなり致命的な物だったようです。)
+・コンボで使う阿修羅覇鳳拳は敵をクリックする必要がないように修正。
+・猛龍拳で敵を吹き飛ばす距離を5セルに変更。よってコンボで使う阿修羅覇鳳拳は距離チェックをしません。5セル飛ばされた敵は阿修羅覇鳳拳の射程から離れたわけなので距離チェックなしで発動します。(本鯖の仕様なんて知りません。)
+・マップの名前を16byteから24bytesに変更。(大した意味はありませんが安全の為の物です。)
+・ウェディングキャラによる鞍落ちを防ぐ為に修正。
+・その他少し修正。(テストは殆んどしてません。)
+ (conf/)
+ battle_athena.conf 修正。
+ (db/)
+ skill_db.txt 修正。
+ (common/)
+ mmo.h 修正。
+ (doc/)
+ conf_ref.txt 修正。
+ item_bonus.txt 修正。
+ (map/)
+ battle.h 修正。
+ battle.c
+ battle_get_flee2()、battle_calc_pet_weapon_attack() 修正。
+ battle_calc_mob_weapon_attack()、battle_calc_pc_weapon_attack() 修正。
+ battle_weapon_attack()、battle_calc_magic_attack() 修正。
+ battle_config_read() 修正。
+ skill.h 修正。
+ skill.c
+ skill_status_change_start()、skill_castend_damage_id() 修正。
+ skill_check_condition()、skill_use_id()、skill_blown() 修正。
+ skill_castend_map()、skill_unit_onlimit()、skill_attack() 修正。
+ pc.c
+ pc_attack_timer()、pc_setpos()、pc_setsavepoint() 修正。
+ pc_movepos()、pc_calcstatus()、pc_bonus() 修正。
+ clif.h 修正。
+ clif.c
+ clif_set0078()、clif_set007b() 修正。
+ clif_updatestatus()、clif_initialstatus()、clif_parse_UseSkillToId() 修正。
+ clif_skillinfo() 追加。
+ map.h 修正。
+ map.c
+ map_setipport()、map_addmap() 修正。
+ その他抜けた所少しあり。
+
+--------------------
+//0704 by kalen
+
+・Umbala Warp追加
+ conf/npc_warp_umbala.txt
+
+--------------------
+//0703 by いど
+
+・サーバーsnapshot
+
+--------------
+//0702 by ぽぽぽ
+
+・ファーマシーのエフェクトを本来のものに変更
+・スクリプトでの埋め込み変数にBaseExp,JobExp,NextBaseExp,NextJobExp追加
+ (map/)
+ skill.c
+ skill_produce_mix() 修正。
+ pc.c
+ pc_readparam()、pc_setparam() 修正。
+ (db/)
+ const.txt 修正。
+
+--------------
+//0701 by ぴざまん
+
+・ステータス異常判別式導入。各ステータス異常の発動率がVIT/INT/MDEFに影響するようになります。持続時間短縮はまた今度で_| ̄|○
+・不死に凍結が効いたバグ修正。
+ (map/)
+ skill.c
+ skill_additional_effect()、skill_castend_nodamage_id() 修正。
+
+--------------
+//0700 by 南
+
+・697のバグ修正。
+ (db/)
+ mob_db.txt
+
+--------------
+//0699 by 死神
+
+・装備のボーナスクリティカルは自分の間違いだったのでbCriticalRateをbCriticalに変更。それと0695で書き忘れですがASPDを上げるカードや装備の一部をbAspdAddRateからbAspdRateに変更しました。みすとれ巣のシミュレーターによるとドッペルカードは複数でも一つしか適用されないみたいだったので。
+ (db/)
+ item_db.txt
+
+--------------
+//0698 by 死神
+
+・一部のキャラに重量が0になってカプラなど何もPC,NCPが表示されなくなるバグ修正。(それだけ)
+ (common/)
+ mmo.h 修正。
+ (map/)
+ clif.c
+ clif_updatestatus() 修正。
+ pc.c
+ pc_calcstatus() 修正。
+
+--------------
+//0697 by 南
+
+・mob_db修正
+ ドロップを中心に修正。
+ (db/)
+ mob_db.txt
+
+--------------
+//0696 by 死神
+
+・バグ修正。
+・テレポートやワープ等の時スキルユニットから抜ける処理が入って
+なかったのでSAFETYWALL等によって鯖落ちが起こったようです。(確か報告も
+あったと思いますが...) よって修正はしましたが確認はしてません。報告を
+お願いします。
+・スキルによる吹き飛ばし処理をちょっと修正とモンスターのコードを少し修正。
+多分変になったことはないと思いますが変だったら報告してください。
+・その他細かい修正。
+ (map/)
+ skill.h 修正。
+ skill.c
+ skill_blown()、skill_attack()、skill_unit_move() 修正。
+ skill_castend_nodamage_id()、skill_castend_damage_id() 修正。
+ skill_unit_out_all()、skill_unit_out_all_sub() 追加。
+ mob.c
+ mob_stop_walking()、mob_spawn()、mob_warp() 修正。
+ mob_can_move()、mob_changestate() 修正。
+ map.h 修正。
+ pc.c
+ pc_setpos() 修正。
+ battle.c
+ battle_calc_pet_weapon_attack()、battle_calc_mob_weapon_attack() 修正。
+ battle_calc_pc_weapon_attack() 修正。
+
+--------------
+//0695 by 死神
+
+・少し修正。
+・プレイヤーの基本パラメータを2byteに拡張。
+・item_db.txtをラグナゲートの説明に合わせて修正。
+・bAddEffとbResEffの確率を百分率から万分率に変更。
+・スクリプトstatusupとstatusup2追加。
+statusup bStr; のように使って機能はステータスポイントを減らして
+基本パラメータを1上げる。
+statusup2 bInt,n; のように使って機能はステータスポイントを減らさずに
+基本パラメータをn上げる。
+・その他細かい修正。
+ (conf/)
+ battle_athena.conf 修正。
+ (db/)
+ item_db.txt 修正。
+ const.txt 修正。
+ (doc/)
+ item_bonus.txt 修正。
+ conf_ref.txt 修正。
+ (common/)
+ mmo.h 修正。
+ (char/)
+ char.c
+ mmo_char_send006b()、parse_char() 修正。
+ (map/)
+ map.h 修正。
+ clif.h 修正。
+ clif.c
+ clif_initialstatus()、clif_updatestatus() 修正。
+ pc.h 修正。
+ pc.c
+ pc_bonus()、pc_calcstatus()、pc_equippoint()、pc_equipitem() 修正。
+ pc_jobchange()、pc_checkbaselevelup()、pc_statusup() 修正。
+ pc_statusup2() 追加。
+ battle.h 修正。
+ battle.c
+ battle_calc_pet_weapon_attack()、battle_calc_mob_weapon_attack() 修正。
+ battle_calc_pc_weapon_attack()、battle_config_read() 修正。
+ skill.c
+ skill_additional_effect()、skill_status_change_start() 修正。
+ script.c
+ buildin_statusup()、buildin_statusup2() 追加。
+ atcommnad.c 修正。
+
+--------------
+//0694 by 死神
+
+・バグ修正と細かい修正。
+・bCriticalRateが正しく適用されなかった問題修正。
+・ペットによるステータスボーナス追加。ステータスボーナスは装備の
+スクリプトによって設定します。ただペットによるボーナスはカードによる物と同じ扱いをします。そして属性の適用は一番優先順位低いです。今は何も入ってませんが...
+ (conf/)
+ battle_athena.conf 修正。
+ (db/)
+ const.txt 修正。
+ pet_db.txt 修正。
+ (doc/)
+ item_bonus.txt 修正。
+ conf_ref.txt 修正。
+ db_ref.txt 修正。
+ (map/)
+ map.h 修正。
+ map.c
+ map_quit() 修正。
+ battle.h 修正。
+ battle.c
+ battle_calc_pc_weapon_attack()、battle_weapon_attack() 修正。
+ battle_calc_magic_attack()、battle_calc_misc_attack() 修正。
+ battle_config_read() 修正。
+ pc.c
+ pc_calcstatus()、pc_bonus()、pc_setpos()、pc_authok() 修正。
+ pc_damage()、pc_autosave_sub() 修正。
+ pet.h 修正。
+ pet.c
+ pet_hungry()、pet_birth_process()、pet_recv_petdata()、pet_food() 修正。
+ pet_return_egg()、pet_ai_sub_hard()、read_petdb() 修正。
+ clif.c
+ clif_sendegg()、clif_parse_LoadEndAck() 修正。
+ atcommand.c 修正。
+ makefile 修正。
+
+--------------
+//0693 by 胡蝶蘭
+
+・SC_*の列挙表をリナンバリング
+ クライアントに通知するのを64未満から128未満に増やした
+ パケット情報に合うようにリナンバー
+ StatusChangeの配列を128から192に増やしたのでメモリ使用量が増えます。
+
+ (db/)
+ const.txt
+ SC_* の数値を変更
+ (map/)
+ skill.h
+ SC_* の列挙の数値を変更
+ map.h
+ MAX_STATUSCHANGEを128から192に増やした
+ skill.c
+ skill_status_change_start(),skill_status_change_end(),
+ skill_status_change_clear()の通知処理を変更
+
+・演奏/ダンスの処理を変更
+ 演奏/ダンス中かどうかをSC_DANCINGで判定するように変更
+ (判定処理が多少高速化されたはず)
+ ワープ(マップ移動や蝿など)すると演奏/ダンスを中断するように変更
+
+ skill.h/skill.c
+ skill_check_dancing()削除、skill_stop_dancing()追加
+ skill_delunitgroup(),skill_initunitgroup()変更
+ skill_status_change_start()変更
+ skill_castend_nodamage_id()変更
+ 書き損じがあるかも・・
+ pc.c
+ pc_calcstatus(),pc_setpos(),pc_damage()変更
+
+・不協和音スキルの修正
+ (db/)
+ skill_db.txt
+ 不協和音スキルのHIT数修正
+ (map/)
+ skill.c
+ skill_status_change_timer()変更
+ battle.c
+ battle_calc_misc_attack()修正
+
+--------------
+//0692 by 胡蝶蘭
+
+・アドリブスキルが使用できない問題修正(skill_dbの添付し忘れ)
+ (db/)
+ skill_db.txt
+ アドリブの消費SPを1に修正
+
+・mob_db2.txtがあればmob_db.txtにオーバーライドするように
+ オリジナルmobを作ってる人は使うと便利かもしれません。
+
+ mob.c
+ mob_readdb()
+
+・鯖落ちバグ報告時のためのスタックバックトレースログ所得方法を紹介
+ 鯖落ちバグの報告時に、この情報をコピペすると開発者が喜びます。
+ Cygwinでcoreの吐かせる方法も紹介してます。
+
+ (doc/)
+ coredump_report.txt
+
+--------------
+//0691 by 胡蝶蘭
+
+・item_db2.txtがあればitem_db.txtにオーバーライドするように
+ オリジナルアイテムを作ってる人は使うと便利かもしれません。
+
+ itemdb.c
+ itemdb_readdb()修正
+
+・演奏/ダンス系スキル仮実装
+ ・演奏/ダンス中は移動が遅く、スキルも使えないようになりました
+ ・アドリブスキルで演奏/ダンスを中断できるようになりました
+ ・演奏/ダンスは石化などの異常、MHPの1/4以上のダメージで中断します
+ ・キャラクターグラフィックは演奏/ダンスしません
+ ・演奏/ダンス中のSP消費は未実装です
+ ・移動しても効果範囲はついてきません
+ ・重複しても不協和音などに変化しません
+ ・エフェクトが出ても効果は未実装のものがあります
+ ・ほとんど未テストなので多数の不都合があると思います
+
+ skill.h
+ SC_* の列挙表を修正
+ skill.c
+ skill_check_dancing()追加
+ SkillStatusChangeTable[]修正
+ skill_unit_onout(), skill_status_change_start(),
+ skill_status_change_timer(),skill_unitsetting(),
+ skill_castend_id(),skill_castend_pos(),skill_castend_map(),
+ skill_castend_nodamage_id()修正
+ その他は忘れました
+ pc.c
+ pc_calcstatus(),pc_damage()修正
+
+--------------
+//0690 by 波浪
+
+・細かい修正
+ (db/)
+ item_db.txt 錐とメギンギョルドのbonusを修正。
+ (doc/)
+ item_bonus.txt 修正。
+
+--------------
+//0689 by 死神
+
+・倉庫バグ修正と細かい修正。
+ (map/)
+ pc.c
+ pc_modifybuyvalue()、pc_modifysellvalue() 修正。
+ storage.c
+ storage_storageopen() 修正。
+ storage_storage_quit()、storage_storage_save() 修正。
+
+--------------
+//0688 by 聖
+
+・ディスカウント、コムパルションディスカウント、オーバーチャージが適用されなかった問題を修正。
+ (map/)
+ pc.c
+ pc_modifybuyvalue() 修正。
+ pc_modifysellvalue() 修正。
+
+--------------
+//0687 by 死神
+
+・少し修正。
+・battle_athena.confに項目追加。(詳しいことはconf_ref.txtで)
+・item_avail.txtの処理を変更。アイテムIDの後に0を入れると今まで通りに使用不可能になるが0以外の数値を入れると使用不可能ではなくその数値をアイテムのIDとして見た目だけをそれに変更します。よって鞍落ちアイテムを別の物に表示して鞍落ちを防ぐことができます。(表示だけ変えて鯖の処理は本当のアイテムIDの物として認識します。修正は全てしたと思いますが抜けた所があるかも知りませので見た目変更したアイテムで鞍落ちが起こったら報告してください。) 鯖の処理はこれが限界です。(少なくとも自分には) アイテムが同じ物が二つ表示されて間違い安いとかどうこうとかの文句を言いたい人は鞍作れよ。以上。
+・ジルタスとアリスのコマントアウト解除。item_avail.txtで卵をルビーとアクアマリンで表示して捕獲アイテムも他の物に表示するように変更しています。
+・ダメージ計算のバグ修正。(大した物じゃありませんが弓だけちょっと問題があったようです。)
+・青箱等のアイテムで得た装備品は未鑑定になるように変更。
+・装備ボーナスの内部処理修正と少し変更。(詳しいことはitem_bonus.txtで)
+・キャラ鯖にテータを送る時キャラ、倉庫、ペットのテータを同時に送るように変更。(キャラ鯖とマップ鯖の間の転送量が増えるかも知りれませんがデータを同期化の為です。)
+・FWの動作間隔を0.25秒から0.1秒に変更。(これで摺り抜は少し減るはずです。)
+・カートレボリュションでどんな状態異常もかからないように変更。
+ (conf/)
+ battle_athena.conf 修正。
+ (db/)
+ const.txt 修正。
+ item_avail.txt 修正。
+ pet_db.txt 修正。
+ (doc/)
+ conf_ref.txt 修正。
+ item_bonus.txt 修正。
+ (map/)
+ map.h 修正。
+ map.c
+ map_quit() 修正。
+ battle.h 修正。
+ battle.c
+ battle_calc_pc_weapon_attack()、battle_calc_mob_weapon_attack() 修正。
+ battle_calc_pet_weapon_attack()、battle_calc_magic_attack() 修正。
+ battle_get_dmotion()、battle_config_read() 修正。
+ pc.c
+ pc_calcstatus()、pc_bonus()、pc_bonus3()、pc_setpos() 修正。
+ pc_makesavestatus()、pc_autosave_sub()、pc_modifybuyvalue() 修正。
+ pc_modifysellvalue()、pc_stop_walking() 修正。
+ skill.c
+ skill_additional_effect()、skill_unitsetting() 修正。
+ clif.c
+ clif_buylist()、clif_selllist()、clif_set009e()、clif_set0078() 修正。
+ clif_set007b()、clif_additem()、clif_itemlist()、clif_equiplist() 修正。
+ clif_storageitemlist()、clif_storageequiplist()、clif_changelook() 修正。
+ clif_arrow_create_list()、clif_useitemack()、clif_tradeadditem() 修正。
+ clif_storageitemadded()、clif_getareachar_item() 修正。
+ clif_skill_produce_mix_list()、clif_cart_additem()、clif_cart_itemlist() 修正。
+ clif_cart_equiplist()、clif_vendinglist()、clif_openvending() 修正。
+ clif_produceeffect()、clif_sendegg()、clif_pet_equip()、clif_mvp_item() 修正。
+ clif_pet0078()、clif_pet007b() 修正。
+ itemdb.h 修正。
+ itemdb.c
+ itemdb_searchrandomid()、itemdb_search()、itemdb_readdb() 修正。
+ itemdb_read_itemavail()、itemdb_read_itemvaluedb() 修正。
+ itemdb_equippoint() 削除。
+ storage.h 修正。
+ storage.c
+ storage_storage_quitsave() ->storage_storage_quit()に変更と修正。
+ storage_storageclose() 修正。
+ atcommand.c 修正。
+ pet.c
+ pet_change_name()、pet_equipitem()、pet_unequipitem() 修正。
+ pet_birth_process()、pet_return_egg() 修正。
+ script.c
+ buildin_getitem() 修正。
+ mob.c
+ mob_stop_walking() 修正。
+ makefile 修正。
+
+--------------
+//0686 by 聖
+
+・細かい修正。
+ (map/)
+ pc.h 修正。
+
+--------------
+//0685 by 波浪
+
+・0683、0684でのbonusの追加にともなってitem_db.txtを修正
+・他色々修正
+ (db/)
+ item_db.txt 修正。
+ (doc/)
+ item_bonus.txt 修正。
+
+--------------
+//0684 by 死神
+
+・細かい修正。
+・死んだふりの時スキルとアイテムが使えないように変更。
+・bInfiniteEndure追加。機能は無限インデュア。
+・ダメージ表示の処理少し変更。
+ (db/)
+ const.txt 修正。
+ (doc/)
+ item_bonus.txt 修正。
+ (map/)
+ map.h 修正。
+ pc.c
+ pc_calcstatus() 修正。
+ pc_equipitem()、pc_unequipitem() 修正。
+ clif.c
+ clif_parse_UseItem()、clif_parse_UseSkillToId() 修正。
+ clif_parse_UseSkillToPos()、clif_parse_UseSkillMap() 修正。
+ clif_damage()、clif_skill_damage()、clif_skill_damage2() 修正。
+ clif_parse_LoadEndAck() 修正。
+ skill.c
+ skill_status_change_timer() 修正。
+
+--------------
+//0683 by 死神
+
+・バグ修正とbonus追加。
+・倉庫バグ、属性バグ修正とその他のバグ修正。
+・スクリプトbonus3追加。今はbAddMonsterDropItemだけが対応になっています。
+・bonus bRestartFullRecover;n;等でnは無意味だけど消すのはちょっとまずいですので0にして入れた方がいいです。bonusは2つの数値が必要なスクリプトなので。
+・bDefRatioAtkを防御無視に変更。
+・0677で書き忘れ。
+・武器の属性適用優先順位を製造>カード>武器に変更。製造が最優先です。(属性がある時に話です。属性がない場合属性ある物に上書きされたりはしません。)
+・装備で適用される効果の優先順位を右手>左手>体>頭上>頭中>頭下>ローブ>靴>アクセサリー1>アクセサリー2>矢に設定。(本鯖仕様がどうなのか分かることができそうな物でもないのでアテナの仕様と言うことで。) 右手が最優先です。
+・武器の射程を右手と左手の武器の中で長い物を適用するように変更。
+ (db/)
+ const.txt 修正。
+ (doc/)
+ item_bonus.txt 修正。
+ (map/)
+ map.h 修正。
+ battle.c
+ battle_calc_pc_weapon_attack()、battle_calc_mob_weapon_attack() 修正。
+ battle_calc_pet_weapon_attack()、battle_calc_magic_attack() 修正。
+ battle_damage() 修正。
+ pc.c
+ pc_autosave_sub()、pc_calcstatus() 修正。
+ pc_bonus()、pc_bonus2() 修正。
+ pc_bonus3() 追加。
+ script.c
+ buildin_bonus3() 追加。
+ mob.c
+ mob_once_spawn()、mob_damage() 修正。
+ storage.h 修正。
+ storage.c
+ storage_storage_save() 追加
+ atcommand.c 修正。
+
+--------------
+//0682 by 聖
+
+・スピードアップポーション系のバグ修正
+ (map/)
+ pc.c
+ pc_calcstatus() 修正。
+
+--------------
+//0681 by 死神
+
+・装備バグ修正。
+ (map/)
+ pc.c
+ pc_equipitem() 修正。
+
+--------------
+//0680 by 聖
+
+・細かい修正。
+・「@monster」コマンドでモンスターIDの指定に「-1」、「-2」等を指定すると
+ モンスターをランダムで召還できる機能を追加。
+ (map/)
+ mob.c
+ atcommand.c
+
+--------------
+//0679 by 波浪
+
+・0676で新しいアイテム効果が実装されたので、item_db.txtを修正(bonus bAddMonsterDropItem,n,x; は、種族判定ができないのでとりあえず保留しました。)
+・他色々修正
+ (db/)
+ item_db.txt
+ job_db1.txt
+ (doc/)
+ item_bonus.txt
+
+--------------
+//0678 by 聖
+
+・召還関連の細かい修正。
+ (map/)
+ mob.c
+ mob_once_spawn_area() 修正。
+
+--------------
+//0677 by 死神
+
+・細かい修正。
+・アイテム売買によって得られる経験値をカードによるスキルでは得られないように修正。
+・毒に掛かると自然回復できないように修正。
+・0676で書き忘れ。製造武器の場合製造によって与えた属性が武器の属性より優先して適用されるように変更。(製造武器が無属性の場合は適用されません。)
+ (doc/)
+ item_bonus.txt 誤字修正。
+ (map/)
+ npc.c
+ npc_buylist()、npc_selllist() 修正。
+ pc.c
+ pc_calcstatus()、pc_natural_heal_sub() 修正。
+
+--------------
+//0676 by 死神
+
+・色々と修正。
+・battle_athena.confに項目追加。(詳しいことはconf_ref.txtで)
+・みすとれ巣を参考してダメージ計算を少し修正。
+・装備bonusに色々と追加。(詳しいことはitem_bonus.txtで)
+・自動セーブする時(キャラ鯖にデータを送る時)倉庫のデータも送るように変更。
+・0667で言い忘れ。カートを外してもアイテムが消えないように変更。(本鯖で消えるのが仕様だと思っていたけど修正されたみたいなので。)
+・取引要請を受ける側は基本スキルをチェックしないように修正。(受ける側の基本スキルチェックは自分が入れた物ではないです。いつの間にか入っていたので削除しました。)
+・防具の精錬ボーナスを端数無視に変更。(これが本鯖の仕様みたいなので)
+・アンクルの処理少し変更。(かからないと言う報告がありましたので...)
+・プレイヤーのステータス計算で問題ありそうな所修正。
+・カードのIDで機能が決まっていたカードもスクリプトによって変えることができるように変更。(詳しいことはitem_bonus.txtで)
+・aspd計算方法少し変更。
+・矢にbCritical、bAtkEle、bHit、bAddEle、bAddRace、bAddSize、bAddEffを適用できるように変更。矢を使うスキルや弓による攻撃だけに矢のbCritical、bAtkEle、bHit、bAddEle、bAddRace、bAddSize、bAddEffが適用されるように修正。
+・キリの実装に為に修正はしましたがキリが防御無視なのかどうかがわからなかったので防御無視はしないようになっています。
+・テストした物はbAddMonsterDropItemとbGetZenyNumだけなので正常に動作するかどうかの報告が欲しい所です。(ついでにitem_dbの修正も...これで吸収系とオートスペル系以外は殆ど実装できるはずです。多分...)
+・その他は覚えてないけど修正した所が少しあるかも...
+ (conf/)
+ battle_athena.conf 修正。
+ (doc/)
+ conf_ref.txt 修正。
+ item_bonus.txt 修正。
+ (db/)
+ const.txt 修正。
+ (char/)
+ inter.c
+ inter_init() 修正。
+ int_storage.c
+ mapif_parse_SaveStorage() 修正。
+ (map/)
+ trade.c
+ trade_traderequest() 修正。
+ pc.h 修正。
+ pc.c
+ pc_autosave_sub()、pc_calcstatus() 修正。
+ pc_bonus()、pc_bonus2() 修正。
+ pc_setrestartvalue()、pc_setequipindex() 修正。
+ pc_check_equip_wcard()、pc_check_equip_dcard()、pc_check_equip_card() 削除
+ その他修正。
+ skill.h 修正。
+ skill.c 修正。
+ skill_castend_nodamage_id()、skill_unit_onplace() 修正。
+ skill_check_condition()、skill_additional_effect() 修正。
+ skill_attack()、skill_status_change_start() 修正。
+ map.h 修正。
+ battle.h 修正。
+ battle.c
+ battle_get_def()、battle_get_mdef2() 修正。
+ battle_weapon_attack()、battle_damage() 修正。
+ battle_calc_magic_attack()、battle_calc_pc_weapon_attack() 修正。
+ battle_calc_mob_weapon_attack()、battle_calc_pet_weapon_attack() 修正。
+ battle_calc_misc_attack()、battle_config_read() 修正。
+ mob.c
+ mob_damage() 修正。
+ pet.c
+ pet_target_check() 修正。
+ clif.c
+ clif_set0078()、clif_set007b()、clif_changelook() 修正。
+ atcommand.c 修正。
+
+--------------
+//0675 by 波浪
+
+・韓鯖で村正の効果が変更されたので報告を元にitem_db.txtを修正
+・job_db1.txtを修正
+ (db/)
+ item_db.txt
+ job_db1.txt
+
+--------------
+//0674 by npc
+
+・ファーマシーの仮実装。
+ (db/)
+ produce_db.txt
+ (map/)
+ skill.c
+
+--------------
+//0673 by 南
+
+・集中力向上に装備品の補正が入っていなかったのを修正。
+ (map/)
+ pc.c
+
+--------------
+//0672 by 南
+
+・集中力向上に装備品の補正が入っていなかったのを修正。
+ (map/)
+ pc.c
+
+--------------
+//0672 by 聖
+
+・モンスター系のバグ修正(すみません、まだ残ってました(^^; )
+ (map/)
+ mob.c
+
+--------------
+//0671 by 聖
+
+・IDチェック範囲の修正他。
+・@monsterで数を指定しなくても召還できるように修正。
+ (map/)
+ atcommand.c
+ battle.c
+ battle.h
+ mob.c
+ pet.c
+ (conf/)
+ battle_athena.conf
+ (doc/)
+ conf_ref.txt
+
+--------------
+//0670 by RR
+
+・モンスタードロップ率を修正(本鯖準拠、DBでの設定+1/10000)。
+・落下率0に設定したアイテムを落とすかどうかをbattle_athena.confで設定可能に。
+ (map/)
+ mob.c
+ mob_damage() 修正。
+ battle.c
+ battle.h
+ (conf/)
+ battle_athena.conf
+
+--------------
+//0669 by 聖
+
+・モンスタードロップの修正。
+ (map/)
+ mob.c
+ mob_damage() 修正。
+
+--------------
+//0668 by 聖
+
+・モンスターIDの範囲チェックを修正。
+ (map/)
+ mob.c
+ mob_db、mob_once_spawn()、mob_once_spawn_area()、
+ mob_summonslave()、mob_read_randommonster()、mob_readdb() 修正。
+
+--------------
+//0667 by 死神
+
+・最大HP計算式をミストレ巣を参考して修正。(多分これで本鯖にあっていると思います。)
+・防具の精錬ボーナスを0.7に変更。(今は端数を四捨五入していますが本鯖が端数無視なら修正しておきます。)
+・@refineコマンドで装備場所IDに0を入れると装備している全ての装備を精錬するように変更。
+・その他細かい修正。
+ (db/)
+ item_db.txt
+ 7140、7142を元に戻して0666の物はコマントアウトしました。
+ job_db1.txt 修正。
+ refine_db.txt 修正。
+ (map/)
+ mob.c
+ mob_once_spawn() 修正。
+ itemdb.c
+ itemdb_read_randomitem() 修正。
+ pet.c
+ pet_food() 修正。
+ pc.c
+ pc_readdb()、do_init_pc()、pc_calcstatus()、pc_setoption() 修正。
+ pc_calc_sigma() 追加。
+ その他修正。
+ map.h 修正。
+ battle.c
+ battle_calc_magic_attack()、battle_calc_misc_attack() 修正。
+ atcommand.c 修正。
+
+--------------
+//0666 by 聖
+
+・ランダムアイテムの細かい修正。
+・battle_athena.confの項目追加。
+・古木の枝で召還するモンスターの確率を設定出来るようにしました。
+・モンスター召還アイテムを複数作る事が出来るようにしました。
+・召還アイテムのサンプルとして
+ 生命の種子をポリン系召還、
+ エンブリオをMVPボス系召還にしてみました。
+ あまりいいサンプルを思いつかなかったので、
+ 何かいい案を思いついた人は書き換えてやってください(^^;
+ (conf/)
+ battle_athena.conf
+ (doc/)
+ conf_ref.txt
+ (map/)
+ mob.h
+ mob_db 修正。
+ mob.c
+ mob_once_spawn()、mob_makedummymobdb()、mob_readdb() 修正。
+ mob_readbranch() -> mob_read_randommonster()に変更。
+ battle.h
+ battle_config 修正。
+ battle.c
+ battle_config_read() 修正。
+ itemdb.c
+ itemdb_read_randomitem() 修正。
+ (db/)
+ item_db.txt
+ item_bluebox.txt
+ item_cardalbum.txt
+ item_giftbox.txt
+ item_scroll.txt
+ item_violetbox.txt
+ mob_branch.txt
+ mob_poring.txt 追加。
+ mob_boss.txt 追加。
+
+--------------
+//0665 by J
+
+・怨霊武士の取り巻きがカブキ忍者になっていたのを酒天狗に修正。
+・オットーにフェイクエンジェルが出すはずの取り巻きがついてたのを修正。
+ (db/)
+ mob_skill_db.txt
+
+--------------
+//0664 by 聖
+
+・精錬失敗時他のプレーヤーにもエフェクトが表示されるように修正。
+ (map/)
+ script.c
+ buildin_failedrefitem() 修正。
+
+--------------
+//0663 by lide
+
+・ブランディッシュスピア修正
+ (map/)
+ battle.c
+ skill.c
+
+--------------
+//0662 by 死神
+
+・細かい修正とバグ修正。
+・プロボックによってモンスターは乗算防御と減算防御が減るように修正してプレイヤーは減算防御だけ減るように修正。
+・スクリプトgetgmlevel追加。機能はそのNPCと話しているプレイヤーのGMレベルを返します。
+・0659の書き忘れですがペットのパフォマンスの種類が親密度によって増えるように変更しました。
+ (map/)
+ clif.c
+ pc.c
+ script.c
+
+--------------
+//0661 by 死神
+
+・細かい修正。
+・接続した時のペットのメッセージを親密度がきわめて親しいの時のみに出るように変更。
+・0659で書き忘れですがペットの支援攻撃は親密度がきわめて親しいの時のみに発生します。(それと親密度によって支援攻撃確率が少し変化します。)
+・ジルタスとアリスの卵のIDをitem_db.txtに合わせました。(自分が作ったpet_db.txtの方が自分勝手に設定していた物でしたので。て言うか未実装アイテムだから番号がわからなかっただけですが...)
+・pet_db.txtのattack_rateが正しく適用されなかったバグ修正。
+ (db/)
+ pet_db.txt
+ (map/)
+ clif.c
+ clif_parse_LoadEndAck() 修正。
+ pc.c
+ pc_attack_timer() 修正。
+
+--------------------
+//0660 by いど
+
+・サーバーsnapshot
+
+--------------
+//0659 by 死神
+
+・ペットを色々と修正。(ペットのコードをほとんど変えました。)
+・手動的だったペットの動きをモンスターのようにAIとして処理。
+・接続した時のペットのメッセージ実装。(本鯖はどうなのかわかりませんが
+Athenaは接続すると100%話すようになっています。)
+・ペットのスペシャルパフォマンス実装。(ただ台詞がちょっと変です。いくら探しても該当するパケットが見つからなかったので。)
+・ペットの台詞を他のペットの物に変更する機能追加。(詳しいことはdb_ref.txtとpet_db.txtで。)
+・ペットによる支援攻撃変更。pet_db.txtで攻撃する時と攻撃を受けた時の支援攻撃
+確率を別々に設定できます。攻撃する時の場合攻撃する度にチェックをしますので
+攻撃速度が速いと支援攻撃を受けやすくなります。攻撃を受けた時も同じです。(こちらはダメージを喰らう度になりますが。) 支援攻撃確率はソヒー、ジルタス、アリスだけ自分勝手に設定しています。(他のは全部1%に。ペットの支援攻撃は同じモンスターにはできないようになっています。そしてペットの戦闘能力はモンスターと同じです。)
+・/hideコマンド実装。
+・プロボックによって乗算防御も減るように修正。
+・フリーキャストのバグ修正。
+・ノービスのステータスボーナス削除。
+・battle_athena.confの項目追加と削除。
+・修正したファイルだけ。未テストした物もかなりありますので問題があったら報告をお願いします。
+ (conf/)
+ battle_athena.conf
+ (doc/)
+ conf_ref.txt
+ db_ref.txt 追加。(今説明が入っているのはpet_db.txtのみです。 )
+ client_packet.txt
+ (db/)
+ pet_db.txt
+ job_db2.txt
+ (map)
+ clif.h
+ clif.c
+ map.h
+ map.c
+ pet.h
+ pet.c
+ pc.c
+ mob.h
+ mob.c
+ npc.c
+ atcommand.c
+ skill.c
+ battle.h
+ battle.c
+
+--------------
+//0658 by huge
+
+・ペットがとどめをさすと、飼い主に経験値が入るようにしました。
+・固定値ダメージじゃ味が無いのでATK1〜ATK2の間で乱数を取るようにしました。
+・あと、ペットがとどめをさすかどうかの設定を、battle_athena.confに加えました。
+
+ (conf/)
+ battle_athena.conf pet_finish追加。
+ (map/)
+ battle.c
+ battle_config_read() 修正。
+ battle.h 修正。
+ pet.c
+ pet_attack() 修正。
+ (doc/)
+ conf_ref.txt 説明追記。
+
+--------------
+//0657 by huge
+
+・ペットによる攻撃を実装。
+・ペットを持っていて、ペットが装備品をつけてて、さらにランダムによる判定で発動します。
+・ただの遊び心ですw
+・battle_athena.confで頻度を設定できます。詳細はdocで。
+
+ (conf/)
+ battle_athena.conf pet_attack追加。
+
+ (map/)
+ battle.c
+ battle_config_read() 修正。
+ battle.h 修正。
+ pc.c
+ pc_attack_timer() 修正。
+ pet.c
+ pet.h
+ pet_attack() 追加。
+ (doc/)
+ conf_ref.txt 説明追記。
+
+ とりあえず、ペットが動いてるなぁって感じと、ダメ回数を増やした程度です。
+
+--------------
+//0656 by 死神
+
+・グランドクロスの修正。(おいおい何度目だ...)
+・グランドクロス計算式間違いで修正。(÷3がまずかったみたいです。)
+でもまだ反射ダメージがみすとれ巣よりちょっと高いです。(10ぐらいだから
+関係ないかも)
+・モンクの気球を必中に修正。(自分の間違いのようですので...)
+ (map/)
+ skill.c 修正。
+ battle.c 修正。
+
+--------------
+//0655 by 死神
+
+・グランドクロスの修正。
+・自分なりに情報を収集してみた結果グランドクロス反射ダメージは
+プレイヤーキャラがそのキャラ自身にグランドクロスを使った時の
+ダメージだそうなので修正しました。(みすとれ巣の計算とはかなり違うような
+気もしますが...)
+・魔法とトラップ、鷹の攻撃にも属性耐性と種族耐性を適用するように修正。
+(本鯖の仕様にあっているかどうかは不明ですが適用した方が正しいと思ったので
+修正しました。)
+ (map/)
+ skill.c 修正。
+ map.h 修正。
+ battle.c 修正。
+
+--------------
+//0654 by 死神
+
+・グランドクロスの修正と細かい修正。(計算式間違いで修正。)
+・0653で書き忘れ。気功による追加ダメージは必中ではないらしいので
+修練の加算と同じ所に計算するように変更しました。
+・カートにバグがありそうだったのでちょっと修正。
+・ダメージ計算をほんの少し修正。(ダメージ量が変わったりはしません。)
+ (map/)
+ battle.c
+ battle_calc_magic_attack() 修正。
+ battle_calc_mob_weapon_attack()、battle_calc_pc_weapon_attack() 修正。
+ skill.c
+ pc_damage_feedback() -> skill_damage_feedback()に変更。
+ skill_unit_timer() 修正。
+ pc.c
+ pc_setoption() 修正。
+ atcommand.c 修正。
+
+--------------
+//0653 by 死神
+
+・0652の修正と細かい修正。今まで通り未テストも多いです。
+・グランドクロスの処理修正。(本鯖にあっているかどうかの自身はありません。)
+ラグナーゲートの説明によると始めに現在HPの20%が消耗されてその後敵に与えた
+ダメージの中で一番高い物が戻ってくるようです。そしてその戻ってきた
+ダメージは聖の属性を持ちトラストによって聖の耐性が50%になっているので
+半分を喰らうことになるようです。(聖の耐性上がる装備をしていれば戻ってくる
+ダメージは受けないようです。)
+問題なのはプレイヤーの防御属性を計算するかどうかです。今は防御属性計算の
+後で聖の属性を計算しています。そして戻ってくるダメージはHPバーは減るけど
+表示はされません。本鯖の方がどうなのか不明なので...
+それと一応モンスターもグランドクロスの使用が可能です。ただモンスターの場合
+現在HPの20%消耗の後のダメージは受けません。(モンスターが使う
+グランドクロスのテストはしてません。)
+・ダメージによるディレイ中にまたディレイがかからないように修正。(大した意味はないかも...)
+・値段がゼロのアイテムも売れるように変更。
+・@コマンドhealの処理少し修正。
+・移動コード少し修正。
+ (map/)
+ clif.c
+ clif_selllist() 修正。
+ battle.c
+ battle_calc_mob_weapon_attack()、battle_calc_pc_weapon_attack() 修正。
+ battle_calc_magic_attack()、battle_calc_misc_attack() 修正。
+ skill.c
+ skill_additional_effect()、skill_unit_onplace() 修正。
+ skill_status_change_start()、skill_unit_onplace() 修正。
+ skill_castend_damage_id()、skill_castend_id()、skill_attack() 修正。
+ skill_unitsetting()、skill_check_condition() 修正。
+ skill_use_id()、skill_use_pos() 修正。
+ npc.c
+ npc_parse_script() 修正。
+ pc.h 修正。
+ pc.c
+ pc_walk()、pc_walktoxy_sub()、pc_stop_walking() 修正。
+ map.h 修正。
+ mob.h 修正。
+ mob.c
+ mob_stop_walking()、mob_changestate()、mob_walk() 修正。
+ pet.c
+ pet_changestate() 修正。
+ atcommand.c 修正。
+ (db/)
+ skill_db.txt グランドクロス修正。
+ cast_db.txt グランドクロス修正。
+
+--------------
+//0652 by 月詠み
+
+・グランドクロスを仮実装
+ (db/)
+ skill_db.txt
+ cast_db.txt
+ (map/)
+ battle.c
+ Damage battle_calc_misc_attack
+ Damage battle_calc_magic_attack
+ skill.c
+ skill_additional_effect
+ skill_castend_damage_id
+ skill_castend_pos2
+ skill_unit_group *skill_unitsetting
+ skill_unit_onplace
+ skill_check_condition
+
+--------------
+//0651 by 波浪
+
+・item_db.txtを修正
+ (db/)
+ item_db.txt
+ 装備品のアイテム効果を修正
+
+--------------
+//0650 by 死神
+
+・三段掌の発動条件を弓と二刀流以外に変更。
+・表示をせずに内部で処理だけするNPCのCLASSを111から32767に変更。
+・細かい修正。
+ (map/)
+ clif.c
+ clif_getareachar_npc()、clif_spawnnpc()、clif_pcoutsight() 修正。
+ npc.h 修正。
+ battle.c
+ battle_calc_pc_weapon_attack()、battle_calc_mob_weapon_attack() 修正。
+
+--------------
+//0649 by 波浪
+
+・DBとDOC修正
+ (db/)
+ item_db.txt
+ グングニールはLV4武器なので、韓国のデータにあわせて風属性ではなくします。
+ mob_db.txt
+ size_fix.txt
+ 楽器は大型に対して75%だそうです。
+ (doc/)
+ item_bonus.txt
+
+--------------
+//0648 by 死神
+
+・ショップの値段に-を入れると鯖が落ちる問題修正。(itemdbの初期化をnpcより
+先にするように変更。) それだけです。
+ (map/)
+ map.c
+ do_init()
+
+--------------
+//0647 by nini
+
+・item_db修正
+・スナッチャー仕様変更。弓以外のすべての武器で出るようになってます。
+ (/map/)
+ battle.c
+ 三段掌の発動条件追加
+ skill.c
+ スナッチャーの発動条件追加
+ (/db/)
+ item_db.txt
+ シルクハットにSP上昇追加
+
+--------------
+//0646 by last
+
+・item_db.txtの修正(属性関連)
+ (/db/)
+ item_db.txt
+
+--------------
+//0645 by るるる(&ree_ron)
+
+・item_value_db.txtにディスカウント&オーバーチャージ等のスキルによる価格変動を受けるかどうかのフラグメントを追加。
+ 実際の形式はサンプルとして用意したitem_value_db.sample.txtを見てください。(設定価格は完全に独断と偏見です)
+ 同様のサンプルとしてNPC設置スクリプトも添付しておきます。
+・item_value_db.txtのアイテム価格設定で、売値と買値の設定を独立。(item_db.txtは従来どおり買値は売値の半額として自動処理)
+・NPCショップにて、1NPCで扱えるアイテム数を最大64から最大100に変更。(クラ自体は120ぐらいまで可能ですが)
+ (/db)
+ item_value_db.txt
+ カラム数を整理しただけです。内容はまったく変更していません。
+ (/map/)
+ clif.c
+ clif_buylist() clif_selllist() 変更
+ itemdb.h
+ item_data 構造体変更
+ itemdb_value_buy() itemdb_value_sell() itemdb_value_notdc() itemdb_value_notoc() マクロ追加
+ itemdb.c
+ itemdb_search() itemdb_readdb() itemdb_read_itemvaluedb() 変更
+ itemdb_sellvalue() 削除
+ npc.c
+ npc_buylist() npc_selllist() npc_parse_shop() 変更
+ (/sample/)
+ オマケです。次回SnapShotには含まないで宜しいです。
+
+コメント
+原型は私の友人ree_ronが行い、私が更に細かいミスを直しただけですが、テストはしましたので大丈夫でしょう。
+元々この処理を導入する理由として、特定アイテムの売値が1z固定にできないものか、という点だったからです。
+そしてやっていくうちに、NPCショップを利用したレアアイテムの販売とかで本鯖露店に近いことが出来るのではないか、
+ということが判ってきたわけです。
+それで一応はデータを用意しましたが、あくまでもサンプルとして利用してください。もし可能ならば、
+さらに修正を加えてアテナ独自として本採用としたデータをパッチアップしてくれればとも思いますがw
+
+
+--------------
+//0644 by nini
+
+・DBの間違い、643で追加されたスクリプト追加。
+ (/db/)
+ item_db.txt
+ cast_db.txt
+ チャージアローのキャスト追加。
+ exp_guild.txt
+ 46-50までのexp抜けに追加。
+ size_fix.txt
+ 楽器、鞭、ナックルのサイズ補正修正。
+
+--------------
+//0643 by 死神
+
+・色々と修正。
+・bMVPaddAtkRate削除。bAddRaceで処理するように変更。
+・bIgnoreDefEleとbIgnoreDefRace追加。
+bonus bIgnoreDefEle,n; n属性の敵の防御無視
+bonus bIgnoreDefRace,n; n種族の敵の防御無視
+・bMatkRate追加。魔法攻撃力を+n%上げます。よってbattle.cで計算していたロッドによる魔法攻撃力増幅の計算はなくしました。ステータス画面に上がった数値は表示されません。ダメージ計算の時に適用しています。
+・bCriticalDefに-を入れるとクリティカルを喰らう確率が上がるように変更。
+・NPC番号111は透明NPCですが落とし穴等のことを考えて表示を一切せずに
+内部で処理だけするように変更。(flagを使うと何とかなりそうですがその
+処理が全然わからなかったので透明NPCにクリックや名前の表示もできないように変更しました。)
+・ショップの値段に-を入れるとitem_db.txtもしくはitem_value_db.txtの物を使うように変更。
+・スキルルアフのエフェクトがサイトと同じだったので修正。ついでにルアフの
+ダメージも修正。
+・みすとれ巣によるとモンスター情報で表示される防御と魔法防御は乗算ではなく減算みたいなので修正。
+・他力本願ですがitem_db.txtの修正をお願いします。(全てのロッドにbonus bMatkRate,15; を入れる必要があります。その他の修正も必要です。)
+・テストしていない物もかなりありますので問題があったら報告してください。
+ (map/)
+ map.h 修正。
+ map.c
+ map_quit() 修正。
+ pc.h 修正。
+ pc.c
+ pc_walk()、pc_stop_walking()、pc_setpos()、pc_authok() 修正。
+ pc_calcstatus()、pc_bonus()、pc_natural_heal_sub() 修正。
+ npc.h 修正。
+ npc.c
+ npc_touch_areanpc()、npc_parse_shop() 修正。
+ clif.c
+ clif_quitsave()、clif_getareachar_npc()、clif_spawnnpc() 修正。
+ clif_skill_estimation() 修正。
+ battle.c
+ battle_calc_magic_attack()、battle_calc_pc_weapon_attack() 修正。
+ battle_calc_mob_weapon_attack() 修正。
+ mob.c
+ mobskill_use() 修正。
+ skill.c
+ skill_status_change_end()、skill_status_change_timer() 修正。
+ skill_status_change_start() 修正。
+ (db/)
+ const.txt 修正。
+ (doc/)
+ item_bonus.txt 修正。
+
+--------------
+//0642 by 死神
+
+・装備バグ修正。(それだけ)
+ (map/)
+ pc.c
+ pc_authok()、pc_checkitem() 修正。
+
+--------------
+//0641 by 死神
+
+・bAspdRateとbSpeedRateのバグ修正。(それだけ)0640で計算をちょっと変えて見ましたがそれがまずかったみたいです。今度も計算式を変えましたがもう大丈夫だと思います。(多分)
+ (map/)
+ pc.c
+ pc_calcstatus()、pc_bonus()、pc_delitem()
+
+--------------
+//0640 by 死神
+
+・バグ修正と少し修正。
+・みすとれ巣を参考してダメージ計算を少し修正。
+・battle_athena.confに項目追加。(詳しいことはconf_ref.txtで)
+・キャラのHPとSPを2byteから4byteに変更。(テストはしていますがバグが出る
+可能性もかなりあります。ただキャラセレクト画面でHPやSPが32768を越える時
+表示は32768になるけど内部の処理は正常に動きますのでそれはバグではありません。
+パケットの長さのせいでそれ以外手段がなかったので...)
+・bCriticalDef(クリティカルを喰らわない確率+n%)の処理変更。100にすれば
+クリティカルを喰らわないようになります。)
+・bInnerAtkをbBaseAtkに変更。みすとれ巣でカードの攻撃は基本攻撃力の方に足されるとありましたので変更しました。今度は上がった攻撃力が表示されます。
+・bDoubleRateの処理変更。確率を足さずに一番高い物だけ適用します。それと左手
+装備の場合無視するように変更しまた。(左手はダブルが適用されませんので)
+・bDoubleAddRate追加。機能はダブルアタック確率+n%(武器無視)です。
+左手装備は無視されます。
+・0635で攻撃力表示を本鯖にあわせました。そして今度は弓だけではなく
+楽器とムチもdexによって攻撃力が上がるように変更しました。
+・装備した武器が消えるバグ修正の為に少し修正はしましたが本当に
+大丈夫なのかは不明です。報告をお願いします。
+ (conf/)
+ battle_athena.conf 修正。
+ (db/)
+ const.txt 修正。
+ item_db.txt 修正。
+ (doc/)
+ item_bonus.txt 修正。
+ conf_ref.txt 修正。
+ (map/)
+ map.h 修正。
+ pc.c
+ pc_calcstatus()、pc_bonus()、pc_equipitem() 修正。
+ battle.h 修正。
+ battle.c
+ battle_calc_mob_weapon_attack()、battle_calc_pc_weapon_attack() 修正。
+ battle_config_read() 修正。
+ clif.c
+ clif_updatestatus()、clif_parse_LoadEndAck()、clif_party_hp() 修正。
+ (common/)
+ mmo.h 修正。
+ (char/)
+ char.c
+ mmo_char_send006b()、parse_char() 修正。
+
+--------------
+//0639 by 胡蝶蘭
+
+・ladminの修正など
+ ・プロンプトの入力にTerm::ReadLineを使うようにした
+ (入力履歴やコマンドラインの編集が可能に)
+ ・POSIX関係の処理の例外エラーをトラップするようにしました
+ (POSIXが全く使えない環境でも最低限、動くようになったかもしれない)
+ ・細部修正
+
+ (tool/)
+ ladmin
+ Ver.1.04に。
+
+・MODバージョンがおかしい問題を修正
+ (common/)
+ version.h
+ ATHENA_MOD_VERSIONが8進数で記述されている問題を修正
+ 数字の頭に0をつけると8進数になるので注意してください
+
+--------------
+//0638 by 波浪
+
+・0635・0637で新しくアイテム効果が実装されたので、それに伴ってitem_db.txtを修正
+・item_bonus.txtを修正
+ (db/)
+ item_db.txt 修正
+ (doc/)
+ item_bonus.txt 修正
+
+--------------
+//0637 by 死神
+
+・0635のバグ修正。
+・battle_athena.confに項目追加。(詳しいことはconf_ref.txtを見てください。)
+・時間が遅すぎて0635で説明してなかったです。(寝不足だったので...)
+まず仕様が変わったのは二刀流のダメージを武器別に完全に分けて行うように
+変更とアサシンじゃなくても左手修練を覚えていれば二刀流を使えるように
+変更しました。それとダメージの計算をちょっと修正。
+そしてbonusに追加されたのは
+bonus bInnerAtk,n; 内部攻撃力+n
+カードの引き上げダメージ用です。表示はされないけどダメージに計算されます。
+bonus bSpeed,n; 移動速度+n
+移動速度をn上げます。
+bonus bAspd,n; 攻撃速度+n
+攻撃速度をn上げます。
+bonus bSpeedRate,n; 移動速度+n%
+移動速度をn%上げます。
+bonus bAspdRate,n; 攻撃速度+n%
+攻撃速度をn%上げます。
+bonus bHPrecovRate,n; HP自動回復率+n%
+自動回復するHPの量をn%上げます。スキルによる回復には影響がありません。本鯖の仕様とあっているかは不明です。
+bonus bSPrecovRate,n; SP自動回復率+n%
+自動回復するSPの量をn%上げます。スキルによる回復には影響がありません。本鯖の仕様とあっているかは不明です。
+bonus bCriticalDef,n; クリティカルを喰らわない確率+n%
+クリティカルの耐性をn上げます。10000以上にするとクリティカルを喰らいません。
+bonus bMVPaddAtkRate,n; MVPモンスターにn%の追加ダメージ
+ボスモンスターにn%の追加ダメージを与えます。深淵の騎士カード用。
+bonus bNearAtkDef,n; 近距離攻撃のダメージをn%の減らす
+全て近距離攻撃のダメージをn%の減らします。(魔法とトラップ、鷹を除く)
+bonus bLongAtkDef,n; 遠距離攻撃のダメージをn%の減らす
+全て遠距離攻撃のダメージをn%の減らします。(魔法とトラップ、鷹を除く)
+bonus bDoubleRate,n; ダブルアタック確率+n%(武器無視)
+武器に関係なく発動するダブルアタック確率をn%上げます。
+ダブルアタックスキルと別の判定を行う為ダブルアタックスキルが
+あってもスキルによるダブルアタック確率が上がるわけではありません。
+サイドワインダーカード用。
+ (map/)
+ pc.c
+ pc_bonus()、pc_calcstatus() 修正。
+ pc_natural_heal_sub() 修正。
+ battle.h
+ struct Battle_Config {} 修正。
+ battle.c
+ battle_calc_pc_weapon_attack()、battle_calc_mob_weapon_attack() 修正。
+ battle_config_read() 修正。
+ (db/)
+ skill_db.txt
+ スティールのSPを10に修正。
+ (conf/)
+ battle_athena.conf 修正。
+ (doc/)
+ conf_ref.txt 修正。
+
+--------------
+//0636 by Sin
+
+・デバッグ用ポタ子さんスクリプト(npc_pota.txt)のアマツ・コンロンへの対応。
+ すでに自力実装されていらっしゃる方々も多いかと思いますが…。
+ コンロンダンジョンの名前がわからないため「崑崙D1」などとさせていただいています。
+ (conf/) npc_pota.txt
+
+--------------
+//0635 by 死神
+
+・battle_athena.confに項目追加。(詳しいことはconf_ref.txtを見てください。)
+・bonusにbInnerAtk(カード等で表示はされないけど実際には攻撃力に反映される物用です。)等を追加。他のはitem_bonus.txtを見てください。(追加はしたけどitem_db.txtは殆んど修正してません。)
+・その他バグ修正や仕様変更もやりましたが一々書く時間がないので...
+ (map/)
+ makeile 修正。
+ pc.c 修正。
+ map.h 修正。
+ clif.c 修正。
+ battle.h 修正。
+ battle.c 修正。
+ itemdb.c 修正。
+ (conf/)
+ battle_athena.conf 修正。
+ (doc/)
+ conf_ref.txt 修正。
+ item_bonus.txt 修正。
+ (db/)
+ const.txt 修正。
+ item_db.txt 修正。
+
+--------------
+//0634 by 死神
+
+・weddingクラスには転職できないように修正。
+・スーパーノービスの為にexpテーブルをbase 4つjob 4つに拡張。
+exp.txtが変わりスーパーノービスだけの経験値を設定できます。(exp.txtの
+4つ目がスーパーノービスのbase expで8つ目がjob expです。今は2次職業の物を
+コピーした物に過ぎませんが。) exp.txtの設定方法も知らない方はいないと
+思いますので説明は省略します。
+・スーパーノービスは転生のテストの為に韓国サクライだけ実装している物と
+思われますが(転生が実装されればなくなると予測しています。)それを
+実装していいのかと思ったりもしますが...
+・結婚衣裳は既に0629で実装しているのにまたパッチとして
+アップされるのもちょっと変(?)ですね。そういえば説明してなかったんですね。
+・装備専用スクリプトであるchangebaseの追加によってタキシードと
+ウェディングドレスが実装しています。これは職業を変更せずに見た目だけ
+変える物です。weddingクラス以外の適用も可能で変装セットとかも作れる
+わけですが内部処理は変更せずに見た目だけ変えているので0631で説明したように
+装備できない物を装備している場合鞍落ちが起こる可能性がありますので
+他の職業で使うのはお勧めしません。仮実装なのは今の仕様はタキシードと
+ウェディングドレスを装備するだけで見た目が変わるからです。韓国サクライの
+方では何かの条件が必要だと思っているのでその条件がまだ実装されいないから
+仮実装です。それにweddingクラスを職業にしてしまうと結婚するとスキル等が
+リセットされるか変になるかのどちらなので変だと思ってなかったのでしょうか?
+ help.txt 修正。
+ (db/)
+ job_db1.txt 修正。
+ exp.txt 修正。
+ (map/)
+ pc.c
+ pc_jobchange()、pc_readdb() 修正。
+ pc_nextbaseexp()、pc_nextjobexp() 修正。
+
+--------------
+//0633 by 波浪
+
+・装備の設定修正。結婚衣裳の職は、実際に転職するのではなくペコナイト(13)、ペコクルセ(21)のように画像を使うだけだと思うので
+ 何も装備できない設定にしました。スパノビはノビが装備できるものだけ設定しました。
+・古木の枝の出現モンスターを追加
+・アマツのモンスの沸き具合を本鯖に近くなるように修正(まだまだ違いますが・・・)
+ (conf/)
+ npc_monster.txt モンス名修正
+ npc_monster_amatsu.txt 修正
+ (db/)
+ item_avail.txt 鞍落ちアイテム追加
+ item_db.txt 装備設定を修正、他多数
+ mob_branch.txt 修正
+ mob_db.txt モンス名修正
+ skill_tree.txt 修正
+
+--------------
+//0632 by nini
+
+・@jobchangeで結婚衣裳とスーパーノービスになれるように。(注意:韓国桜井クライアントのみ)
+・Sノビのステ、スキルなども暫定追加。(ノービスのコピーですが)
+ とりあえず見た目だけということで、結婚衣裳でも攻撃できますが(ただしノーモーション)、本来はできません。
+・上にあわせてitem_db編集。
+ 結婚衣裳で武器もつとact、sprエラー出すので、結婚衣裳では武器を持てないようにした(はず)。
+ (db/)
+ job_db1.txt
+ job_db2.txt
+ item_db.txt
+ 結婚衣裳、Sノビのデータ
+ skill_tree.txt
+ Sノビのスキル
+ (map/)
+ map.h
+ MAX_PC_CLASSに追加
+
+--------------
+//0631 by 死神
+
+・細かい修正。
+・タキシードとウェディングドレスの表示をbattle_athena.confで設定できる
+ように変更。
+・武器グラパッチについてですがパッチ前は使えない職業が装備をしても表示は
+されないだけで鞍落ちまでは起こらなかったけど武器グラパッチの後はその武器を
+装備することができない職業(本鯖で)が装備してしまった場合鞍落ちが起こる
+ことがありますので注意してください。
+ (db/)
+ item_db.txt
+ 1161、2338、7170 修正。
+ (conf/)
+ battle_athena.conf 修正。
+ (doc/)
+ conf_ref.txt 修正。
+ (map/)
+ battle.h
+ struct Battle_Configにwedding_modifydisplay 追加。
+ battle.c
+ battle_config_read() 修正。
+ pc.h
+ pc_cart_delitem() 修正。
+ pc.c
+ pc_jobchange()、pc_additem()、pc_delitem()、pc_cart_delitem() 修正。
+ pc_checkitem()、pc_getitemfromcart() 修正。
+ clif.c
+ clif_changelook()、clif_send()、clif_parse_GlobalMessage() 修正。
+ script.c
+ buildin_changebase() 修正。
+ storage.c
+ storage_storageaddfromcart() 修正。
+ vending.c
+ vending_purchasereq() 修正。
+
+--------------
+//0630 by 引退人
+
+・ギルド脱退時にcharサーバが落ちることがあるのを修正
+・water_height.txtを更新
+・0627に関連してmodifydisplay関連を削除
+ (char/)
+ int_guild.c
+ mapif_guild_leaved()のバッファ容量が足りなかったので、
+ unsigned char buf[64]; -> unsigned char buf[128];
+ と修正。
+ (conf/)
+ battle_athena.conf
+ equip_modifydisplayを削除
+ water_height.txt
+ prt_fild04.gatとmoc_fild01.gatの分を追加
+ (doc/)
+ conf_ref.txt
+ equip_modifydisplayの説明を削除
+ (map/)
+ battle.h
+ struct Battle_Config からequip_modifydisplayを削除
+
+--------------
+//0629 by 死神
+
+・0627のバグ一部修正と新しい移動パケットに対応。(自分の間違いでした。
+モンスターやNPCも新しい移動パケットを使うと思っていたのですが
+新しい移動パケットはプレイヤーのみのようです。)
+・タキシードとウェディングドレス仮実装。(韓国のサクライ鞍じゃないと
+鞍落ちされます。使用する時はitem_db.txtのコマントアウトされている
+2338と7170を解除して使ってください。)
+・SP回復アイテムもintによって効果が増えるように変更。
+・0627で書き忘れですがカートのアイテム計算とitemdb_を呼ぶのを最小化する
+処理を入ってるせいでpc_additem()、pc_delitem()、pc_cart_additem()、pc_cart_delitem()以外の方法でカートアイテムやアイテムに変動がある場合
+正常に動作する保証がないので修正のさいには注意してください。
+ readme0754をreadme0574に修正。
+ makefile 修正。
+ (map/)
+ map.h 修正。
+ clif.c 修正。
+ pc.c 修正。
+ battle.c 修正。
+ mob.c 修正。
+ script.c 修正。
+ (db/)
+ item_db.txt 修正。
+ class_equip_db.txt 修正。
+ skill_db.txt 修正。(誤字を治しただけです。)
+ (conf/)
+ npc_event_doll.txt 修正。(流浪人さんありがとうございます。)
+
+--------------
+//0628 by NOCTURNE
+
+・snapshot
+・snapshotからsnapshotまでのReadme分割
+・要望が多かったのでsnap作成(プログラム的な変更点は無し
+
+--------------
+//0627 by 死神
+
+・コードの最適化と少し修正。(少しは軽くなると思います。)
+・ @modifydisplayコマンド削除。
+・新しい移動パケットに対応だと思ったら0x1d8、0x1d9、0x1daパケットの一部が
+0x78、0x79、0x7bと変わってるみたいです。つまり今のままでは対応できません。
+X,Yの座標の部分の書式が変わったのかと予測はしていますが...
+情報を求みます。(makefileのDPACKETVERを4にすれば0x1d8、0x1d9、0x1daを
+使いますが座標がずれたらしく何も標示されません。)
+・100000からだったchar_idを150000からに変更。(ペットの卵の問題で武器の名前がちょっと変になったので修正しました。)
+・ペットのコード少し修正。(pet_idをcard[2]とcard[3]からcard[1]とcard[2]に変更しました。よって前に作った卵は使えません。鞍でcard[3]の機能が変わったので
+仕方なく修正しました。今度はconvertツールがありません。作る時間がなかったので...)
+・最適化の為に修正した所が多いですが全て正常動作する保証はありません。
+鯖落ちバグが発生したら報告お願いします。(batte.cはまだ最適化してません。)
+カートのアイテム計算やアイテムの重量の計算を最初にだけするようにしているので表示に少し問題があるかも...
+・修正したファイルだけ書いておきます。
+ makefile
+ help.txt
+ (common/)
+ mmo.h
+ (map/)
+ map.h
+ atcommnad.h
+ atcommnad.c
+ pc.h
+ pc.c
+ clif.c
+ script.c
+ trade.c
+ itemdb.h
+ itemdb.c
+ battle.h
+ battle.c
+ pet.c
+ map.c
+ mob.c
+ (char/)
+ char.c
+ (conf/)
+ battle_athena-conf
+ atcommand_athena.conf
+
+--------------
+//0626 by 引退人
+
+・パケット長テーブル(新移動パケットなど)修正
+ (doc/)
+ client_packet.txt パケット解析スレ Mさんの情報を反映
+ conf_ref.txt 0624に合わせて修正
+ (map/)
+ clif.c
+ packet_len_table[] client_packet.txtに合わせて修正
+
+--------------
+//0625 by 引退人
+
+・@hide透明化をBOSSなどに見破られないように修正
+ (map/)
+ pc.h
+ #define pc_iscarton(sd) 修正
+ #define pc_isinvisible(sd) 追加
+ mob.c
+ mob_attack()
+ mob_target()
+ mob_ai_sub_hard_activesearch()
+ mob_ai_sub_hard_mastersearch()
+ mob_ai_sub_hard()
+ 透明(pc_isinvisible(sd)!=0)で死人と同様に判定されるように修正
+ (conf/)
+ npc_cTower.txt 修正(thx to holyzardさん)
+
+--------------
+//0624 by るるる
+
+・武器画像表示処理の一新(新移動パケット使用)
+・上と関連して、@modifydisplayコマンドを設けた
+ 機能としては、現在のアサシン武器などのがおかしい場合に、または気に入らないとかで、
+ キャラ毎に旧パケを使用するようにしている。
+
+ (map/)
+ atcommand.c
+ atcommand() @modifydisplayコマンドを追加
+ atcommand.h
+ struct Atcommand_Config {
+'7d 変更
+ clif.c
+ clif_set0078_and01d8() , clif_set007b_and01da() 関数名&処理の変更
+ clif_spawnpc() , clif_movechar() , clif_changelook() , clif_getareachar_pc() ,
+ clif_fixpcpos() , clif_parse_LoadEndAck() 変更
+ map.h
+ struct map_session_data ・b} 変更
+ pc.c
+ pc_setnewpc() , pc_calcstatus() , pc_equiplookall() , pc_changelook() 変更
+
+ (conf/)
+ atcommand_athena.conf
+ equip_modifydisplay 追加
+
+ 変更箇所は全てキーワード「modifydisplay」でサーチすればほぼわかるかと。
+
+コメント:もうこれで問題は無いはず。実は大いなる勘違いをしてた個所があったのは内緒(マテw
+
+--------------
+//0623 by 引退人
+
+・@hideで透明化(見られない&MOBにタゲられない)するように修正など
+ (map/)
+ atcommand.c
+ @hideのoption設定を0x04から0x40に変更
+ mob.c
+ mob_attack()
+ mob_target()
+ mob_ai_sub_hard_activesearch()
+ mob_ai_sub_hard_mastersearch()
+ mob_ai_sub_hard()
+ option判定を0x06から0x46に修正
+ (conf/)
+ npc_event_potion.txt MORISON_MEAT修正(thx to holyzardさん)
+
+--------------
+//0622 by 引退人
+
+・mobがスキル使用に失敗した場合、通常攻撃するように
+ (map/)
+ mob.c
+ mobskill_use_id() スキル使用失敗で0、成功で1を返すように修正
+ mobskill_use_pos() スキル使用失敗で0、成功で1を返すように修正
+ mobskill_use() 上記を反映して失敗時には0を返すように修正
+
+--------------
+//0621 by 胡蝶蘭
+
+・アイテムチェックを行うかどうかconf/battle_athena.cnfに書けるように
+・アイテムチェックで不正と判断するかどうかをdb/item_avail.txtに書けるように
+・@itemcheckで明示的にアイテムチェックできるように
+
+ デバグやテストなどで色々なアイテムIDを使用したい場合は
+ アイテムチェックを無効にして下さい。(item_check: off)
+ 無効にした場合でも@itemcheckコマンドでチェックすることが出来ます。
+ cnfファイルは用意してないので必要なら各自適当に書き換えてください。
+
+ (db/)
+ item_avail.txt
+ 新規追加。不正アイテムの列挙に使用。未完成。他力本願。
+ item_db.txtに定義されてるが実際には使用できないアイテムを書く。
+ (doc/)
+ conf_ref.txt
+ battle_athena.cnfとatcommand_athena.cnfの説明修正
+ (map/)
+ itemdb.c/itemdb.h
+ itemdb_availableマクロ追加
+ itemdb_read_itemavail()追加
+ itemdb_readdb()でavailable=1にするように
+ itemdb_search()で存在しないIDはavailable=0でデータを作るように
+ do_init_itemdb()でitemdb_read_itemavail()を呼ぶように
+ pc.c/pc.h
+ pc_checkitem()をエクスポート
+ pc_checkitem()でavailableとbattleconfigをチェックするように
+ atcommand.c/atcommand.h
+ @itemでbattleconfigをチェックするように
+ @itemcheckコマンド追加
+ atcommandconfigにitemcheckメンバ追加
+ battle.c/battle.h
+ battle_configにitem_checkメンバ追加
+
+・ladminの修正など
+ アカウント追加、パスワード変更の際にパスワードを省略すると、
+ パスワード用のエコーしない専用プロンプトで入力できます(&入力確認)。
+ 追加の際にパスワードが表示されたら困る場合などに。
+ パスワード入力中はCtrl+Cが効かないので注意してください。
+
+ パスワードの不正文字の表示が、何文字目かで表示するようになりました。
+ その他微妙にチェック追加など。
+
+ Cygwinでしか動作確認していません。POSIXモジュールを使っているので、
+ POSIXでない(&エミュレーションもできない)プラットフォームだと
+ 動かないかもしれません。
+ UNIX系ではnkfなどで改行コードを変換しないとだめかも?
+
+ (tool/)
+ ladmin
+ Ver.1.03に。
+
+-------------
+//0620 by 月詠み
+
+・ホーリークロス実装
+
+ (db)
+ skill_db.txt 修正
+ (map)
+ skill.c
+ skill_additional_effect()修正(コメントのみ)
+ battle.c
+ Damage battle_calc_weapon_attack()修正
+
+-------------
+//0619 by るるる
+
+・パッチ0617のでやり忘れと微妙な修正
+
+ clif.c
+ clif_movechar(),clif_parse_LoadEndAck() 修正
+
+--------------
+//0618 by nini
+
+・リザレクションの詠唱、ディレイ追加。回復量修正。
+・消費SP修正
+・アローシャワーの範囲を5*5にして2セル吹き飛ばし。
+・チャージアローの使用武器条件無し。
+・スピアスタブの飛距離を6セルに。
+ (/db)
+ cast_db.txt 修正
+ skill_db.txt 修正
+ (/map)
+ battle.c
+ battle_calc_weapon_attack() 修正
+ skill.c
+ skill_castend_damage_id() 修正
+ skill_check_condition() 修正
+ skill_castend_nodamage_id() 修正
+
+--------------
+//0617 by るるる
+
+・武器画像表示で他キャラが表示されないのを「とりあえず」修正
+・靴表示のパケットを送信停止(現時点ではムダ。コメントしただけですが)
+ clif.c
+ clif_spawnpc(),clif_getareachar_pc(),clif_fixpcpos(),clif_changelook()修正
+ pc.c
+ clif_changelook()がある部分を修正(武器ー>盾と順になるように処理の入れ替え)
+
+コメント。
+新マップ移動パケ(0x1d8〜0x1da)を色々とやったが、そのパケ1つで武器表示が新式のに対応してる
+というわけではないっぽい。旧移動パケだと自分以外のキャラが移動すると旧式表示になってしなう。
+更に、新武器表示パケは武器と盾の同時処理が出来てない。おそらくクライアントの問題だと思う。
+とりあえず、キャラが動くたびに新武器パケ=>旧盾パケの2つの装備パケを送ることで解決させている。
+本鯖ではどうなのかの実際のところのデータが無いため、これ以上のことはムリ。
+
+--------------
+//0616 by 胡蝶蘭
+
+・water_height.txtを読んでいないとサーバーが落ちるバグ修正
+ map.c
+ map_waterheight()修正
+
+・PCのマップ移動時のアイテムチェックでアイテムIDの存在をチェックするように修正
+・一部の@コマンドでアイテムIDの存在をチェックするように修正
+ pc.c
+ pc_checkitem()修正
+ pc_authok()修正 pc_checkitem()追加
+ clif.c
+ clif_parse_LoadEndAck()修正
+ itemdb.c
+ itemdb_exists()追加(itemdb_searchと同じだが、dbに存在しない
+ 場合は新しいデータを作らずにNULLを返す)
+ itemdb_read_classequipdb()修正 itemdb_search=>itemdb_exists
+ itemdb_read_itemnametable()修正 itemdb_search=>itemdb_exists
+ itemdb_read_itemvaluedb()修正 itemdb_search=>itemdb_exists
+ atcommand.c
+ @item修正 itemdb_search=>itemdb_exists
+ @produce修正 itemdb_existsでチェックするように
+
+--------------
+//0615 by 波浪
+
+・アイテムDATA大幅修正
+ 主な修正箇所は、回復アイテムの回復量の修正、消費アイテムをclass_equip_db.txt無しでも使用できる様に修正、
+ 装備品の装備可能職を全て修正、カード効果を修正、etc・・・です。
+
+--------------
+//0614 by Nikita
+
+・アイテムDATAの修正(主に回復量)
+・スキル解毒の射程修正
+・0612の細かい修正
+ (conf/)
+ npc_town_prontera.txt 修正
+ (db/)
+ item_db.txt 修正
+ skill_db.txt 修正
+
+--------------
+//0613 by 引退人
+・checkweight修正
+ (conf/)
+ npc_event_making.txt checkweight部分を修正
+ npc_event_potion.txt ポーション、ジュースNPCのcheckweight修正
+
+--------------
+//0612 by nini
+
+・アイテムDATA修正
+ (db/)
+ item_db.txt 修正
+ (conf/)
+ npc_town_***.txt 修正
+ R.O.M776さんを参照しました。
+
+--------------
+//0611 by 死神
+
+・アイテム使用条件があわない時0xa8パケットを送るように変更。(バグ報告スレッド 243のno nameさん情報提供ありがとうございます。)
+・QMで集中力向上と速度上昇、アドレナリンラッシュ、ラウドボイス、スピアクイッケン、ツーハンドクイッケンを解除するように修正。
+・速度上昇と速度減少で逆のスキルが解除されるように修正。
+・0609で書き忘れ。モンスターがQMの範囲から抜けても効果が維持するように
+変更とブレッシングで呪いと石化が解除されるように修正。
+ clif.c
+ clif_useitemack() 修正。
+ skill.c
+ skill_status_change_start() 修正。
+ pc.c
+ pc_insert_card() 修正。(これはカードバグとは関係ない修正です。そのバグの修正は自分が05xx当たりで修正しましたので。)
+
+--------------
+//0610 by 波浪
+
+・アイテムDATA修正
+ (db/)
+ item_db.txt 修正
+
+--------------
+//0609 by 死神
+
+・色々と修正。
+・モンスターが止まるように動く問題修正。
+・指弾のディレイ修正。
+・矢作成のコード片付け。
+・敵がスキル範囲から逃げた場合スキルが失敗するように変更。
+・class_equip_db.txtの仕様変更。
+ 性別と装備レベルも設定可能に変更と使用アイテムの使用職業、性別と使用
+ レベルの設定ができるように変更。(ただデータが多いせいで修正した
+ class_equip_db.txtはサンプル程度の物です。埋めてください。他力本願ですが...)
+ それとアイテム使用条件があわないとアイテムが使わないようにしては
+ いますが01c8パケットの<type>を0にしてもアイテムを使用した時と同じ
+ エフェクトが出ます。本鯖のアイテム使用パケットが分からないままじゃ
+ こうするしかなかったのですが...
+・battle_athena.confに項目追加。
+・その他スキル少し修正と細かい修正。
+・修正した所を全て覚えてませんのでファイルだけ。
+ (map/)
+ clif.c 修正。
+ mob.c 修正。
+ mob.h 修正。
+ pc.c 修正。
+ map.h 修正。
+ skill.c 修正。
+ skill.h 修正。
+ itemdb.c 修正。
+ battle.c 修正。
+ battle.h 修正。
+ (conf/)
+ battle_athena.conf 修正。
+ (db/)
+ cast_db.txt 修正。
+ skill_db.txt 修正。
+ create_arrow_db.txt 修正。
+ class_equip_db.txt 修正。
+ item_db.txt 修正。
+ (doc/)
+ client_packet.txt 修正。
+ conf_ref.txt 修正。
+
+--------------
+//0608 by sk
+・アマツNPC追加
+ (conf/)
+ npc_town_amatsu.txt 城内NPC追加
+ npc_warp_amatsu.txt 城内ワープポイント追加
+
+--------------
+//0607 by J
+・アサルトタートルの手下召喚のバグ修正(報告ありがとうございます るるるさん)
+ (db/)
+ mob_skill_db.txt アサルトの修正ついでにテレポを使うMOBのスキルディレイも修正
+
+--------------
+//0606 by 引退人
+・スキルレベル最大値以上にクリックした時点で他スキルが上げられなくなるバグを修正(Thanx to 227さん)
+ (map/)
+ clif.c
+ clif_skillup()
+ スキルレベルが最大値のとき、パケット末尾を0にするように修正
+
+--------------
+//0605 by るるる
+
+・武器属性付与スキルの不都合修正
+ 武器を持ち替えたり外したりした場合も、属性付与を解除するようにしました。
+ 但し、素手=>武器装備のみ状態維持します。
+・スピアクイッケンのステータスアイコンを正しく表示
+・2HQ、スピアクイッケン、アドレナリンラッシュで該当以外の武器持ち替えで状態消滅
+ スピアクイッケンは未確認ですが、2HQは確実なので2HQと不公平な仕様とは
+ 考えにくいので同様なパターンとしました。本鯖と相違がある場合は報告願います。
+・敵のQMで集中力向上と速度上昇を解除
+ QMで影響するスキルはこれ2つだけかな? 私の記憶と掲示板での報告とで
+ 判断したのですが、もし相違がありましたら報告願います。
+
+ (map/)
+ clif.c
+ clif_parse_UnequipItem() 修正
+ pc.c
+ pc_checkallowskill() pc_equipitem() 修正
+ skill.h
+ skill_encchant_eremental_end() 追加
+ skill.c
+ skill_status_change_start() skill_status_change_end()
+ skill_status_change_clear() skill_encchant_eremental_end() 修正
+ その他細かいところ少々
+
+--------------
+//0604 by J
+・MOBスキル再修正
+・MOBDB修正
+ (db/)
+ mob_skill_db.txt
+ アークエンジェリングとタートルジェネラルが1回に2種類までしか
+ MOBを出さなかったのを修正
+ mob_db.txt
+ ロードオブデスのドロップでエラーが出るのを修正(未確認)
+ 怨霊武士のドロップとMVPを追加(未確認)
+--------------
+//0603 by 引退人
+・新規アイテム時にも所持可能個数チェックをするように修正
+ (map/)
+ pc.c
+ pc_checkadditem()
+ 新規アイテム時にMAX_AMOUNTを超えていたら
+ ADDITEM_OVERAMOUNTを返すように修正
+
+--------------
+//0602 by 引退人
+・Geffen鍛冶屋で落ちる問題を修正
+ (conf/)
+ npc_town_geffen.txt if (!checkweight(,)) から if (!(checkweight(,))) に修正
+
+--------------
+//0601 by J
+・MOBスキルの危ない所をいくらか修正
+・覚醒と狂気の使える職を修正
+・ゲフェニアダンジョンの配置をカボチャイベントで入れたときの配置に修正
+ ただしボスがDOP2体ではなくドラキュラにしています。
+ (conf/)
+ npc_monster.txt モンスター配置微変更
+ (db/)
+ mob_skill_db.txt 怪しい設定などの修正
+ item_db.txt 増速POTの修正
+
+--------------
+//0600 by 引退人
+・プロンテラ精錬所の横のファンに話し掛けると固まる問題を修正
+・MOBスキル取り込み(Thanx to Jさん)
+ (conf/)
+ npc_event_skillget.txt ファンのLabelを修正
+ npc_town_prontera.txt ファンが重複していたので削除
+ (db/)
+ mob_skill_db.txt ジュノー以降のMOBスキル追加
+
+--------------
+//0599 by るるる
+
+・セージの武器属性付与スキルの不都合修正とステータスアイコン表示
+ アスペルシオとエンチャントポイズンとで多重にかかってしまってたので、
+ 最後に付与したもの1つになるようにしました。
+ そのついでにステータスアイコンも表示するようにもしました。
+ (未テストですが、アイコン出なかったスピアクイッケンもでるはずです。)
+・アイテムDBにて、増速ポーションの使用制限を追加
+ Jazzさん提供です。
+ それと私の趣味でラグナロクTシャツをアレナニしましたがw 気に入らなければ
+ 消すなり元通りに修正するなりしてしちゃってくださいませ〜。
+
+ (conf/)
+ battle_athena.conf 598での入れ忘れ
+ (map/)
+ skill_encchant_eremental_end() 追加
+ skill_status_change_end() skill_status_change_start() skill_status_change_clear() 修正
+ (db/)
+ iten_db.txt 修正
+
+--------------
+//0598 by るるる
+
+・装備武器の画像変更に対応
+ 一応ながらクルセイダーの両手槍とかプリーストの鈍器とかはテストしましたが、
+ 全ての職をチェックはいません。またこの時点ではクライアント自体の表示データに
+ 問題の有るのが多いのも付け加えておきます。
+ あと、靴も一応は対応しました。但しこれは現時点では本鯖すらも未対応なのですが。
+ 表示が化けて嫌だという場合は従来のやり方も出来ます。
+
+ (conf/)
+ battle_athena.conf
+ オプション equip_modifydisplay を追加
+ (map/)
+ battle.h
+ Battle_Config 修正
+ battle.c
+ battle_config_read() 修正
+ clif.c
+ packet_len_table[] clif_changelook() 修正
+ map.h
+ enum {} 修正
+ pc.c
+ pc_calcstatus() pc_equiplookall() pc_changelook() 修正
+ (common/)
+ mmo.h
+ mmo_charstatus {} 修正
+
+--------------
+//0597 by 波浪
+
+・アマツに関する修正&微修正
+ (conf/)
+ npc_mob_job.txt
+ npc_monster.txt
+ npc_monster30.txt
+ モンス名修正
+ npc_monster_amatsu.txt
+ 追加(モンス数がかなり手抜きです・・・
+ npc_town_amatsu.txt
+ ショップNPCを統合(npc_shop3.txtを消してもOKです
+ (db/)
+ mob_db.txt
+ アマツのモンスデータを現在分かる範囲で修正&草ときのこのdef,mdefを修正
+
+--------------
+//0596 by 死神
+
+・0595の修正と細かい修正。
+・フリーキャストでキャストしている間は攻撃可能ですがキャストした後の
+ディレイタイムでは攻撃できないようになっています。本鯖の仕様がどうなのかは
+わかりません。
+・動いているPCにモンスターが攻撃できない問題修正。(テストしてませんが
+多分これで大丈夫かと。)
+ (map/)
+ skill.h
+ SC_FREECAST 削除。
+ skill.c
+ skill_use_id()、skill_use_pos() 修正。
+ skill_castend_id()、skill_castend_pos() 修正。
+ その他少し修正。
+ pc.c
+ calc_next_walk_step()、pc_attack_timer()、pc_calcstatus() 修正。
+ clif.c
+ clif_parse_ActionRequest()、clif_parse() 修正。
+ map.h
+ struct map_session_dataにprev_speed追加。
+ mob.c
+ mob_ai_sub_hard()、mob_changestate()、mob_attack() 修正。
+
+--------------
+//0595 by PRevEv
+・フリーキャスト修正、実装(キャスティング中攻撃もできます。)
+ (/map)
+ pc.c
+ pc_calcstatus() 修正。
+ calc_next_walk_step() 修正。
+ pc_attack_timer() 修正。
+ skill.c
+ skill_castend_id()、skill_castend_pos()、skill_use_id()、skill_use_pos() 修正。
+
+--------------
+//0594 by 死神
+
+・韓国鞍のパーティ問題修正と細かい修正。
+・@partyコマンド修正と@guildコマンド追加。
+・battle_athena.confにguild_emperium_check追加。
+・スキル使用が失敗してもディレイがかかる問題修正。
+ help.txt 修正。
+ (map/)
+ clif.c
+ clif_parse_CreateParty2() 追加。
+ clif_parse_ItemIdentify() 修正。
+ その他少し修正。
+ atcommand.h、atcommand.c 修正。
+ battle.h
+ struct Battle_Configにguild_emperium_check追加。
+ battle.c
+ battle_config_read() 修正。
+ guild.c
+ guild_create()、guild_created() 修正。
+ skill.c
+ skill_castend_id()、skill_castend_pos() 修正。
+ Makefile 修正。
+ (doc/)
+ client_packet.txt
+ パケット0x01e8 追加。
+ conf_ref.txt 修正。
+ (conf/)
+ atcommand_athena.conf 修正。
+ battle_athena.conf 修正。
+
+--------------
+//0593 by 死神
+
+・メテオとバミリオンのダメージ修正と細かい修正。
+・メテオの隕石が落ちてくる範囲を7*7から5*5に変更。(これで中央は全ての隕石の
+ダメージを受けることになります。)
+・自動回復計算式変更。
+ スキルレベル*5 + (max_hp/50)から
+ スキルレベル*5 + (max_hp*スキルレベル/500)に変更。(SPと息吹も同じように変更。)
+・GM右クリック命令「使用者強制終了」でatcommand_athena.confのkickの
+レベルをチェックするように変更。
+ (db/)
+ skill_db.txt
+ スキル気功のspを10から8に修正。
+ (map/)
+ map.c
+ NO_WATERを100から1000000に変更。
+ battle.c
+ battle_calc_magic_attack() 修正。
+ skill.c
+ skill_castend_pos2() 修正。
+ pc.c
+ pc_natural_heal_hp()、pc_natural_heal_sp()、pc_spirit_heal() 修正。
+ clif.c
+ clif_parse_GMKick() 修正。
+ (doc/)
+ conf_ref.txt 修正。
+
+--------------
+//0592 by 引退人
+
+・水場の有り無しをwater_height.txtだけで決めるように変更。npc_water.txtは不要に。
+・カードスキルではレベル上げできないようになったため不要になった処理を削除。
+ (conf/)
+ map_athena.conf
+ npc: conf/npc_water.txt 消去
+ water_height.txt
+ デフォルト高さ3のマップ分を追加&all_waterを高さ-100としてコメントで追加
+ (map/)
+ map.c
+ waterlistはmap_readwater()内でメモリ確保
+ gat設定後は不要なのでmap_readallmap()でメモリ開放しています
+ map.h
+ struct map_dataのflagからwater_flagを消去
+ npc.c
+ npc_parse_mapflag()
+ マップフラグwaterとall_waterを消去
+ pc.c
+ pc_skillup()
+ 裏でスキルLvUPできなくなったのでskill[id].flagの分は消去
+ skill.c
+ skill_check_condition()
+ map_getcellで水場判定するように修正
+
+--------------
+//0591 by CHRIS
+
+・モンクが氣弾をもっているとき、氣弾*3の必中ダメージが入る様になりました。─ battle.c修正
+・モンクスキル「指弾」と「発勁」と「気孔」の詠唱時間が正しく修正されました。─ cast_db.txt修正
+
+--------------
+//0590 by 死神
+
+・grf-files.txtやconfファイル、account.txtファイルの名前と位置を変えるように変更。
+・マップ移動による鯖落ちを防ぐ為に修正。(マップが二度ロードされて鯖落ちが
+起こったとの報告を受けたので。)
+・0586をちょっと修正。装備によるスキルの場合レベル上げができないように修正。
+ただ装備によるスキルをスキルポイントを使って上げる時は装備を外す必要が
+あります。
+・@partyちょっと修正。(名前に空白があっても大丈夫なように。)
+・水場の高さをwater_height.txtで読み込むように変更。
+・confファイルで設定する物をファイルのパスに空白があっても大丈夫な
+ように変更。
+・GM右クリック命令「使用者強制終了」でモンスターを倒せるように変更。
+(原因は不明ですがこれでモンスターを殺すと鯖がめちゃくちゃに遅くなることが
+あります。)
+・その他少し修正。
+・テストは殆んどしてませんので注意してください。
+ athena-start 修正。
+ (map/)
+ pc.c
+ pc_skill()、pc_resetskill()、pc_setpos()、pc_read_gm_account() 修正。
+ pc_set_gm_account_fname() 追加。
+ pc.h
+ pc_set_gm_account_fname() 追加。
+ clif.c
+ clif_skillinfoblock()、clif_parse_LoadEndAck()、clif_parse_GMKick() 修正。
+ clif_changemap() 修正。
+ atcommand.c
+ @partty 修正。
+ skill.c
+ skill_castend_nodamage_id() 修正。
+ map.c
+ map_config_read()、map_readwater() 修正。
+ script.c
+ script_config_read()、do_init_script() 修正。
+ script.h
+ script_config_read() 追加。
+ (common/)
+ version.h 修正。
+ grfio.h
+ grfio.c
+ grfio_init() 修正。
+ mmo.h
+ GRF_PATH_FILENAME 追加。
+ (conf/)
+ map_athena.conf 修正。
+ npc_water.txt 修正。
+ water_height.txt 追加。
+ login_athena.conf 修正。
+ (login/)
+ login.c
+ login_config_read()、read_gm_account() 修正。
+ (char/)
+ char.c
+ do_init() 修正。
+ char_config_read() 追加。
+ inter.c
+ inter_config_read() 修正。
+ (doc/)
+ conf_ref.txt 修正。
+
+--------------
+//0589 by 胡蝶蘭
+
+・GMの右クリックで切断される問題を修正
+ とりあえず01dfパケットを無視するようにしました。
+ なんとなくこのパケットはチャット禁止回数とは関係無いような気も……
+
+ clif.c
+ clif_parse_GMReqNoChatCount()追加
+
+・startをathena.shでなくathena-startを使うように変更
+ start
+ athena.sh => athena-start startに置き換えしただけ
+
+--------------
+//0588 by Kalen
+
+・AmatsuNPC追加
+
+--------------
+//0587 by 胡蝶蘭
+
+・loginサーバーの管理パケットの仕様を変更(0579のログイン拒否情報に対応)
+ (login/)
+ login.c
+ アカウントバン状態変更パケット追加(7936,7937)
+ アカウントリスト所得パケット修正(7921)
+ (doc/)
+ admin_packet.txt
+
+・ladminの機能追加
+ ・バン状態を変更するコマンド追加
+ ・リスト表示と検索でバン状態も表示されるようになった
+ ・"?"でもヘルプが出るように修正
+ ・シンボリックリンクにstateaccountが追加されました。
+ 使う人はladminの--makesymlinkをもう一度実行してください
+
+ (tool/)
+ ladmin
+ 機能追加
+
+--------------
+//0586 by 引退人
+・カードスキルを修正
+ (/map)
+ pc.c
+ pc_calc_skilltree()
+ pc_skill()
+ 覚えられないスキルならskill[id].flag=1とする
+ またはskill[id].flagに本来のlvを+2して記憶
+ pc_skillup()
+ skill[id].flagも増やす
+ clif.c
+ clif_skillinfoblock()
+ skill[id].flag==1なら覚えられないスキル
+ (/char)
+ char.c
+ mmo_char_tostr()
+ skill[id].flagから本来のlv値を保存する
+
+--------------
+//0585 by kalen
+・script修正
+ npc_town_guid.txt 外見変更
+
+--------------
+//0584 by 引退人
+・カードスキルを修正
+ (/map)
+ pc.c
+ pc_calc_skilltree() cardスキルを忘れさせる処理を追加
+ pc_skill() 整理
+
+--------------
+//0583 by kalen
+・script修正
+ npc_event_doll.txt 抜けていた部分の会話追加
+ npc_town_guid.txt 町の案内要員の画像を表示できるように修正
+ 基本的に最新jROで問題なしです。
+
+--------------
+//0582 by PRevEv
+・580のバグ修正。
+ (/map)
+ skill.c
+ skill_use_pos() 修正。
+--------------
+//0581 by 引退人
+・水場高さ設定関連を少し修正
+ (/map)
+ map.c
+ map_waterheight()
+ map_readwater()
+ map_readmap()
+ waterlist[512] -> *waterlistにして、mallocでメモリ確保するように修正。
+ map_readallmap()
+ free(waterlist);追加
+
+・バグ報告スレッドの修正パッチを取り込み
+ (conf/)
+ npc_town_refine.txt セミコロン抜け修正
+ (db/)
+ item_db.txt 1161,バルムン修正
+
+--------------
+//0580 by PRevEv
+・フリーキャスト仮実装(キャスティング中攻撃は不可能)
+ (/map)
+ clif.c
+ clif_parse_WalkToXY() 修正。
+ pc.c
+ pc_calcstatus() 修正。
+ skill.c
+ skill_castend_id()、skill_castend_pos()、skill_use_id()、skill_use_pos() 修正。
+--------------
+//0579 by Aの人
+・ログインを弾く処理を仮実装
+ conf/login.c
+ auth_dat構造体にstateを追加
+ mmo_auth関数修正
+ mmo_auth_new関数修正
+ mmo_auth_sync関数修正
+ mmo_auth_init関数修正
+
+ この値を変更するツール、改善策。他力本願です(><;
+--------------
+//0578 by 引退人
+・バグ報告スレッドの修正パッチを取り込みなど(thanx to るるるさん,Athefansさん,sageさん,zupportさん)
+ help.txt @goの説明ちょっと修正
+ (conf/)
+ npc_mob_job.txt 第4列目をTab区切りに修正
+ npc_water.txt 水場高さ修正
+ (db/)
+ item_db.txt 640,...,{ pet 1155; },{},,に修正
+ mob_db.txt 1162,RAFFLESIA,ラフレシア...修正
+
+--------------
+//0577 by るるる
+・@コマンドを追加&修正
+ atcommand.c
+ atcommand() @itemresetコマンド追加 @goコマンド修正(アマツ・コンロンを追加)
+ atcommand_config_read() 上に合わせてitemreset使用レベル指定を追加
+
+ doc/conf_ref.txt
+ conf/atcommand_athena.conf itemreset使用レベル指定を追加
+ help.txt @itemresetの説明追加と@goの説明修正
+
+-------------
+//0576 by V&S
+・ゴーストリングカードとバースリーカードの効果が逆になっていたのを修正
+ { bonus bDefEle,7; }→ゴーストリングカード(念)
+ { bonus bDefEle,8; }→バースリーカード(闇)
+ ↑だったのを↓に修正
+ { bonus bDefEle,7; }→バースリーカード(闇)
+ { bonus bDefEle,8; }→ゴーストリングカード(念)
+
+ 鋼鉄の重量を修正
+
+--------------
+//0575 by 引退人
+・水場ファイルが"conf/npc_water.txt"固定だったのを修正
+(conf/map_athena.confのnpc:に書かれているファイルをみて水場高さ設定するように)
+ map.c
+ struct waterlist[512]; 新規追加。マップファイル名と水場高さを記憶。
+ map_waterheight() 新規追加。水場の高さを返す。
+ map_readwater() 水場ファイルをみてwaterlistを設定するように修正。
+ map_readmap() map_waterheight()を呼ぶように修正。
+ map_config_read() "npc"でmap_readwater(w2);追加。
+
+--------------
+//0574 by いど
+
+・サーバーSnapshot
+・conf/shop_*.txtの内容をconf/npc_town_*.txtに統合
+
+--------------
+//0573 by Jazz
+
+・mapの penalty, nomemo, noteleport, nobranchの optionを設定。
+・map サーバーが cpuを無限占有することを検査するための script 添付. cygwin環境で作成と実験をしました.
+ (/conf)
+ mapflag.txt 追加。
+ (/tool)
+ mapcheck.sh 追加。
+
+--------------
+//0572 by 引退人
+・"conf/npc_water.txt"の第4列で水場高さ設定
+ (ちゃんとした水場判定が実装されるまでのつなぎとして・・・)
+ (/conf)
+ npc_water.txt サンプル修正。
+ ・第4列で水場の高さを設定します。
+ ・高さを書かなかった場合のデフォルト値は3になります。
+ (/map)
+ map.c
+ ・水場高さ設定関数 map_readwater() 追加。
+
+--------------
+//0571 by code
+天津フィールドのMOBの配置とワープポイントの設定の修正
+天津パッチに崑崙が含まれているのを確認したので崑崙のワープとmobを配置
+
+conf/npc_monster35.txt
+ mobの配置
+
+conf/npc_warp_amatsu.txt
+ 天津warp pointの設置
+
+conf/npc_warp_gonryun.txt
+ 崑崙warp pointの設置
+
+conf/npc_town_amatsu.txt
+ 暫定的にプロンテラ噴水前←→天津港/プロンテラ噴水前←→崑崙の接続NPC
+
+--------------
+//0570 by code
+天津フィールドのMOBの配置とワープポイントの設定です。
+conf/npc_monster35.txt
+ mobの配置
+
+conf/npc_warp_amatsu.txt
+ warp pointの設置
+
+conf/npc_town_amatsu.txt
+ 暫定的にプロンテラ噴水前←→天津港の接続NPC
+
+--------------
+//0569 by 死神
+
+・0561の@jobcange での♀バード&♂ダンサーによる鞍落ち防止をpc_jobchange()でするように変更。
+・@コマンド@party追加。パーティを作る命令です。韓国鞍を使うとパーティを作る時止まるので臨時的にこれを使ってパーティを作ってください。
+・水の判断処理修正。
+・マップフラグにwaterとall_water追加。詳しくはnpc_water.txtを参考してください。
+iz_dun0xだけ入力していますので他のは埋めてください。マップフラグwaterかall_waterが入ってないとセルのtypeが3でも水として認識しません。そして水だらけのiz_dun02から04までは全て水として認識するようにall_waterを入れています。(これ以外は方法がなかったので...)
+・battle_athena.confに項目追加。一部はWeissを参考して作った物です。
+・テストしてない物も少しあります。
+ (/conf)
+ atcommand_athena.conf 修正。
+ battle_athena.conf 修正。
+ map_athena.conf 修正。
+ npc_water.txt 追加。
+ (/doc)
+ conf_ref.txt 修正。
+ (/map)
+ atcommand.h、atcommand.c 修正。
+ battle.h 修正。
+ battle.c
+ battle_config_read() 修正。
+ pc.c
+ pc_jobchange()、pc_stop_walking() 修正。
+ npc.c
+ npc_parse_warp()、do_init_npc()、npc_parse_mapflag() 修正。
+ mob.c
+ mob_ai_sub_hard() 修正。
+ pet.c
+ pet_food() 修正。
+ skill.c
+ skill_check_condition() 修正。
+ map.h
+ struct map_data 修正。
+
+--------------
+//0568 by 引退人
+
+・アクアベネディクタ水場判定など
+・ウォーターボール水場判定(read_gat(m,x,y)==3で水場と判定)
+ skill.c
+ skill_castend_nodamage_id()
+ case AL_HOLYWATER: アクアベネディクタ(聖水取得)
+ skill_check_condition()
+ case AL_HOLYWATER: アクアベネディクタ(水場判定)
+ case WZ_WATERBALL: ウォーターボール(水場判定)
+
+--------------
+//0567 by るるる
+
+・アコライトのアクアベネディクタを仮実装(水場限定使用のみ未実装)
+・プリーストのアスペルシオ、セージのフレイムランチャーでスキル使用時にアイテム消費
+・ミストレスカード装備時にセージの属性原石&ハンターの罠が消費されないバグを修正
+
+ (/map)
+ skill.c
+ skill_check_condition() 修正
+
+--------------
+//0566 by パイン
+
+・0563のスキル解除条件が間違っていたので修正。武器をはずす&武器を変えた場合は
+ 無条件で解除するようにした。
+
+# pc_checkallowskill について(前回説明書くのを忘れていたので…)
+ 一応今後の含みとしてreturnを返すようにしていますが、現在は(戻り先では)使っていません。
+ 今現在は騎士・クルセイダーくらいしかスキル使用時の武器制限がありませんが、今後出てこない
+ とも限らないので、もし(2次上位か3次?)出てきたらここでチェックしてください。
+
+ (/map)
+ pc.c
+ pc_checkallowskill() 修正
+
+--------------
+//0565 by 引退人
+
+・マップ移動時に矢装備が外れないように修正
+・ログイン時に矢装備が表示されるように修正
+ (/common)
+ mmo.h
+ (矢装備は0x8000なので)shortだとintへのキャスト時などに
+ 負値となってしまうためunsigned shortに修正
+ struct item
+ short equip; -> unsigned short equip;
+ (/map)
+ clif.c
+ clif_itemlist() アイテムリストの矢のついでに矢装備もチェック
+ clif_arrowequip() シンプル化
+ pc.c
+ pc_equipitem() 修正
+
+--------------
+//0564 by 紅葉
+
+・@modelの服染め不可能判定修正。
+・@modelで、選べるハズの髪形に変更出来なかった部分を修正。
+ 上記変更点に合わせてhelp.txtの修正。
+
+--------------
+//0563 by パイン
+
+・MOBの暗闇スキルを食らった後に回復しないのを修正。…なんだけど、適正な値が分からないので
+ 毒や沈黙と同じ時間にしてあります
+ 今後、また手を加えるかもしれません。
+・2HQとスピアクイッケンを使用中に武器を変えた場合は解除するように変更。
+
+ (/map)
+ skill.c
+ skill_castend_damage_id() 修正。
+ skill_status_change_timer() 修正。
+ pc.c
+ pc_checkallowskill() 新設。
+ pc_equipitem() 修正。
+ pc.h
+ pc_checkallowskill() 新設。
+
+--------------
+//0562 by huge
+
+・矢を弓装備時以外でも装備できるように戻しました。
+・矢の属性を適用するのを弓装備時のみに修正。
+
+ pc.c
+ pc_equipitem() 修正。
+ pc_calcstatus() 修正。
+
+--------------
+//0561 by 引退人
+
+・Linuxでもコンパイルできるように
+ (/map)
+ skill.c
+ skill_castend_damage_id() 変数dx,dyの宣言位置変更
+ Makefile
+ LIBS に -lm 追加
+
+・@jobcange での♀バード&♂ダンサーによる鞍落ち防止。 by (no name)さん
+ atcommand.c
+ @jobchange,@charjobに性別チェック追加
+
+// ナナスさん修正
+・clif.c内でatcommand.hを2度includeしていたので一つ削除。
+・パーティー会話、ギルド会話でも@コマンドをチェックするように修正。
+ (/map)
+ clif.c
+ clif_parse_PartyMessage()、clif_parse_GuildMessage 修正。
+
+--------------
+//0560 by パイン
+
+・0559 の athena-start を Unix Like OS でも動くようにリファイン。
+
+--------------
+//0559 by rowla
+
+・athena.shを全面的に書き直し、athena-startに。athena-start startで開始、athena-start stopでサーバー停止。cygwinでテスト、*BSD|Linuxでは未テスト(環境がないため)。
+
+--------------
+//0558 by 死神
+
+・ブリッツビートを自動だけ弓を装備していないと発動できないように変更。(手動は武器に関係なく使えます。) 未テスト。
+・トラップの重さ修正。(何故かは知らないけど100になっていたのを10に修正。因みに倉の表示は100が10で10が1です。)
+・弓で使うスキルの場合矢が減らないのが仕様だったと覚えているので矢をチェックしないように修正。
+・モンクスキル三段掌の表示をパッシブに変更。
+・マップフラグをセットする時dummyがなくてもセットできるように修正。
+(mapflag nomomo dummyからmapflag nomemoでも大丈夫なように変更。)
+未テスト。
+ (/db)
+ item_db.txt 修正。
+ skill_db.txt 修正。
+ (/map)
+ skill.c
+ skill_check_condition()、skill_additional_effect() 修正。
+ skill_status_change_start() 修正。
+ npc.c
+ do_init_npc() 修正。
+
+--------------
+//0557 by huge
+
+・矢を、弓装備時のみ装備できるように修正。
+・弓を装備から外したら、矢も外れるように修正。
+・矢を消費するスキルをいくつか修正。
+・鷹を、弓を装備しているときのみ発動するように修正。(未テスト)
+
+ pc.c
+ pc_equipitem() 修正。
+ pc_unequipitem() 修正。
+ skill.c
+ skill_additional_effect() 修正。
+ skill_check_condition() 修正。
+
+--------------
+//0555 by 死神
+
+・細かい修正とプレゼントボックス、古い巻物のバグ修正。
+・@コマンド@refine、@produce少し修正。
+・サーバーのIPにDNS名を使えるように変更。(今さらですがYareから
+持ってきた物です。)
+・スティール計算式変更とMVPアイテム処理変更。
+・店NPCを利用によるジョブ経験値獲得計算式変更。
+ 獲得ジョブ経験値 = ln(金*スキルレベル) * shop_exp / 100
+・ほとんどテストしてないのでバグの可能性があります。
+ help.txt 修正。
+ (/conf)
+ atcommand_athena.conf 修正。
+ battle_athena.conf 修正。
+ (/db)
+ item_db.txt 修正。
+ (/doc)
+ conf_ref.txt 修正。
+ (/char)
+ char.c
+ do_init()、check_connect_login_server() 修正と少し修正。
+ (/map)
+ mob.c
+ mob_damage() 修正。
+ pc.c
+ pc_getitemfromcart()、pc_steal_item() 修正。
+ pet.c
+ pet_return_egg()、pet_get_egg()、pet_unequipitem() 修正。
+ script.c
+ buildin_getitem() 修正。
+ skill.c
+ skill_produce_mix() 修正。
+ storage.c
+ storage_storageget() 修正。
+ atcommand.c 修正。
+ map.c
+ map_config_read() 修正と少し修正。
+ chrif.c
+ check_connect_char_server()、do_init_chrif()、chrif_setip() 修正と少し修正。
+ npc.c
+ npc_buylist()、npc_selllist() 修正。
+
+--------------
+//0554 by NOCTURNE
+・サーバーSnapShot
+・too/addaccountの削除
+・help.txtの更新
+
+--------------
+//0553 by 胡蝶蘭
+
+・ladminのバグ修正と機能追加
+ ・キーワードによるアカウント検索機能追加
+ ・シェルコマンドとして使用できるようにプロンプトを使わないモード追加
+ ・追加機能についてはladminを見てください
+ ・ ladminの--makesymlinkにより、シンボリックリンクとしてaddaccountを
+ 作成するため、以前のaddaccountは削除する必要があります。
+ これらのシンボリックリンク(Cygwinではショートカット)と、
+ 古いaddaccountは鯖snapshotには含まないで下さい。
+
+ (tool/)
+ ladmin
+ 機能追加と修正
+
+・女性アカウントしか作成できないバグ修正
+・ladmin、checkversion使用時loginサーバーが暴走するバグ修正
+・GMアカウント周辺のIDを避けるためにSTART_ACCOUNT_NUMを変更
+ (既にGMアカウントは避ける仕様になっていますが、混乱防止のため)
+
+ (login/)
+ login.h
+ START_ACCOUNT_NUMを500000から2000000に変更
+ login.c
+ 7532(切断)パケットの処理修正
+ mmo_auth_new()修正
+
+・backupがバックアップするファイルにpet.txtを追加
+ (tool/)
+ backup
+ ファイル追加修正
+
+--------------
+//0552 by 死神
+
+・安定性を上げる為の修正ですが本当に安定性上がったか
+どうかは不明です。
+・PVPによりクライアントが落ちる問題修正。
+ atcommand.c
+ @pvpoff、@pvpon、@gvgon、@gvgoff 修正。
+ script.c
+ buildin_pvpon()、buildin_pvpoff()、buildin_gvgon()、buildin_gvgoff() 修正。
+ clif.c
+ clif_pvpset() 修正。
+ skill.c
+ skill_attack()、skill_unit_onplace()、skill_unit_onout() 修正。
+ skill_unit_ondelete() 修正。
+
+--------------
+//0551 by Kalen
+・DB修正
+ db/create_arrow_db.txt 完成
+ SourceID順にソートしました。
+
+--------------
+//0550 by huge
+
+・矢作成スキル実装
+
+ clif.c
+ clif.h
+ clif_arrow_create_list() 追加
+ clif_arrow_created() 追加
+ clif_parse() 修正
+
+ pc.c
+ pc_search_inventory() 修正
+
+ skill.c
+ skill.h
+ skill_arrow_db() 追加
+ skill_readdb() 修正
+ skill_castend_damage_id() 修正
+
+ db/create_arrow_db.txt 追加
+ db/skill_db.txt 修正
+
+ まだdbは未完成です。
+
+--------------
+//0549 by Kalen
+
+・map_athena.conf
+ オリジナルスクリプト、季節限定スクリプトを整頓
+ shop3.txt追加
+
+・各種NPC追加&修正
+ npc_event_yuno.txt [追加]ジュノーイベント(青石5個GET)
+ npc_cTower.txt [追加]地上地下の鍵NPC
+ npc_town_yuno.txt [修正]台詞修正
+
+ npc_event_carnival.txt [追加]旧鯖カーニバルイベント時のNPC
+
+--------------
+//0548 by huge
+
+・矢を装備した時の表示バグ問題を修正。
+ clif.c
+ clif_arrowequip() 修正。
+ pc.c
+ pc_equipitem() 修正。
+
+あとは、マップを移動するたびに装備が外れちゃう点ですね・・・。
+
+--------------
+//0547 by 死神
+
+・安定性を上げる為の修正と細かい修正。
+・スキルユニットの判定をしている間メモリーを解除できないように変更。
+ map.c
+ map_foreachinarea()、map_foreachinmovearea() 修正。
+ map_foreachobject() 修正。
+ block_free_maxを32000から50000に変更。
+ pc.c
+ pc_calcstatus() 修正。
+ skill.c
+ do_init_skill()、skill_unit_timer()、skill_status_change_clear() 修正。
+ skill.c、battle.c、battle.h
+ struct battle_configのsanctury_typeをsanctuary_typeに変更。
+ (英語スペル間違いで修正。)
+ battle_athena.conf
+ sanctury_typeをsanctuary_typeに変更。
+ conf_ref.txt
+ sanctury_typeをsanctuary_typeに変更。
+
+--------------
+//0546 by 獅子o^.^o
+
+conf/npc_shop2.txt
+.バ一ド、ダンサ一用の武器。コモドの武器屋で販売している。
+.モンク用の武器。カピト一リナ修道院で販売している。
+
+--------------
+//0545 by 死神
+
+・ブリッツのダメージを自動で分散、手動で普通になるように変更。
+・オートブリッツバグ修正。(これで大丈夫だといいけど...)
+ map.c
+ block_free_maxを16000から32000に修正。
+ block_list_maxを4096から5120に修正。
+ battle.c
+ battle_weapon_attack() 修正。
+ skill.c
+ skill_attack()、skill_castend_damage_id() 修正。
+
+--------------
+//0544 by Diex
+・猛龍拳から阿修羅覇凰拳へのコンボ実装。
+・阿修羅覇凰拳発動後、敵の背後に移動するよう、修正。
+・三段掌のダメージ修正。
+ (/map)
+ skill.c
+ skill_castend_damage_id() 修正。
+ skill_check_condition() 修正。
+ skill_use_id() 修正。
+ pc.c
+ pc_attack_timer() 修正。
+ pc_authok() 修正。
+ battle.c
+ battle_calc_weapon_attack() 修正。
+ battle.h
+ struct Battle_Config 修正。
+ map.h
+ struct map_session_data 修正。
+ (/conf)
+ battle_athena.conf 修正。
+
+はっきりいってコンボ繋げづらいです。そのため阿修羅へのコンボはかなり甘い判定に
+してます(一時的にですが)。繋げづらければbattle_athena.confのほうでデュレイ時
+間を大きくしてみてください。
+ 動画見てて気づいたのですが、阿修羅覇凰拳は猛龍拳が発動した後、即時発動のスキ
+ルに変わってるようなのです。他力本願ですが、阿修羅までのコンボのパケを記録した
+物をどなたかアップしてもらえないでしょうか?詳細がわかり次第、修正します。
+
+--------------
+//0543 by 死神
+
+・ブリッツのダメージを分散されるように変更。
+・普通のアカウント作りではGMアカウントを作れないように変更。
+(前に自分が入れた物がなくなったので戻しただけですが...)
+・取り巻きが主と一緒に死ぬように変更。(ただちょっと重くなる
+可能性があります。) 未テスト。
+・MVP経験値が表示だけされて実際には入ってない問題修正。
+ (/login)
+ login.c
+ mmo_auth_new() 修正。
+ (/map)
+ skill.c
+ skill_castend_damage_id() 修正。
+ battle.c
+ battle_calc_misc_attack() 修正。
+ mob.c
+ mob_damage() 修正。
+ mob_deleteslave()、mob_deleteslave_sub() 追加。
+
+--------------
+//0542 by 死神
+
+・オートブリッツバグ修正。(今度こそ大丈夫のはず...)
+・自分に使ったヒールでは経験が入らないように変更。
+・店NPCを利用によるジョブ経験値獲得計算式変更。
+ 獲得ジョブ経験値 = ln(金) * shop_exp / 100
+になります。
+logを使うことで金が多くても入る経験値が多く入らないように変更しました。
+ (/map)
+ battle.c
+ battle_damage() 修正。
+ skill.c
+ skill_attack()、skill_castend_damage_id() 修正。
+ skill_castend_nodamage_id 修正。
+ npc.c
+ npc_buylist()、npc_selllist() 修正。
+ map.c
+ map_foreachinarea()、map_foreachinmovearea()、map_foreachobject()
+ 修正。(大した修正ではないです。)
+ (/conf)
+ battle_athena.conf 修正。
+ (/doc)
+ conf_ref.txt 修正。
+
+--------------
+//0541 by huge
+
+・矢をまとめて持てるように修正。
+・弓で攻撃したときに、装備している矢を消費するように修正。
+
+ itemdb.c
+ itemdb_search() 修正
+ itemdb_isequip() 修正
+
+ battle.c
+ battle_weapon_attack() 修正
+ battle_calc_weapon_attack() 修正
+
+ clif.c
+ clif.h
+ clif_arrow_fail() 追加
+ clif_parse_EquipItem() 修正
+
+--------------
+//0540 by 死神
+
+・バグ修正と問題ありそうな所修正。(これでWZ_FIREPILLARとブリッツに
+よる鯖ダウンはなくなるはず...)
+ map.c
+ map_foreachinarea()、map_foreachinmovearea() 修正。
+ skill.c
+ skill_unitsetting()、skill_delunitgroup() 修正。
+ pc.c
+ pc_damage() 修正。
+ battle.c
+ battle_damage() 修正。
+ npc.c
+ npc_parse_mob() 修正。
+ mob.c
+ mob_spawn_dataset() 修正。
+
+--------------
+//0539 by 死神
+
+・clif_pvpset()をマップからAREAかマップかを設定できるように変更。(pvpの時の処理は0535以前の物に戻そました。自分だけに転送してもいいような気もしますが...)
+ clif.h、clif.c
+ clif_pvpset() 修正。
+ clif_parse_LoadEndAck() 修正。
+ script.c
+ buildin_pvpoff() 修正。
+ buildin_pvpon() 修正。
+ atcommand.c 修正。
+・攻撃途中でアイテムを拾うと攻撃が止まるように修正。
+ pc.c
+ pc_takeitem() 修正。
+・0535説明が爆裂波動になっているがそれは金剛に間違いです。
+・0537で説明を忘れましたがモンスターのdefとmdefを10000以上に設定すれば全ての攻撃に1ダメージになるモンスターになります。そしてモンスター情報でdefと
+mdefが10000以上の場合def 100、mdef 99に表示するように変更。本鯖仕様に
+するにはmob_db.txtを修正してください。
+
+--------------
+//0538 by huge
+
+・グリムトゥースを範囲攻撃に修正
+・サプライズアタック実装 (有効範囲って、これであってるのかな?)
+・バックスタブの仮実装
+ 本鯖でやってる人から話を聞いて、場所指定じゃなくて
+ タゲ取っても良さそうだったので変更しました。(やりやすかったので (^^;
+ まだ、mobの後ろに居るかどうかの判定は入ってません。
+
+・battle.c
+ battle_calc_weapon_attack() 修正
+
+・skill.c
+ skill_additional_effect() 修正
+ skill_castend_damage_id() 修正
+ skill_check_condition() 修正
+ skill_use_id() 修正
+ skill_castend_nodamage_id() 修正
+
+・skilldb.txt
+ バックスタブの種類を[場所]から[敵]へ変更
+
+--------------
+//0537 by 死神
+
+・スティールバグ修正とbattle_athena.confの項目追加、仕様変更と細かい修正です。
+(スティールは計算式に問題があったので修正して確率を更に落としました。)
+ battle.h
+ finger_offencive_typeをfinger_offensive_typeに修正。(英語スペル間違いで修正しました。)
+ struct battle_configにrestart_hp_rate、restart_sp_rate 追加。
+ battle.c
+ battle_calc_weapon_attack()、battle_calc_magic_attack() 修正。
+ skill.c
+ skill_attack() 修正。
+ clif_skill_nodamage()にスキルレベルを送るように変更。(Mさんの指摘により修正。)
+ clif.c
+ clif_skill_estimation() 修正。
+ conf_ref.txt
+ finger_offencive_typeをfinger_offensive_typeに修正と少し追加。
+ mob.c
+ mob_ai_sub_hard()、mob_target()、mob_damage() 修正。
+ pc.c
+ pc_steal_item() 修正。
+ atcommnad.c、atcommnd.h
+ @コマンド@gvgon , @gvgoff 追加。
+ battle_athena.conf
+ finger_offencive_typeをfinger_offensive_typeに修正と少し追加。
+
+--------------
+//0536 by hogefuga3 (Athena staff)
+
+・新GRFファイルフォーマット対応
+ - Athena staff 様の作成されたパッチを適用しました。
+ 更新履歴の部分はパッチミスになったので手動で組み込み。
+
+(変更)
+ common/
+ grfio.c
+
+--------------
+//0535 by 死神
+
+・0533の問題がありそうな部分全て修正。修正した所を全部チェックしてなかったので修正したファイルだけ...
+・スクリプトsetmapflagnosave 追加。
+ setmapflagnosave マップ名、セーブするマップ名、座標(X、Y)
+ nosaveフラグをonにします。
+・battle_athena.confに追加と一部仕様変更。(詳しくはconf_ref.txtを参考してください。)
+・モンスターのdefとmdefが10000以上の場合全ての攻撃(クリティカル含めて)が1ダメージになるように変更。(トラップやブリッツの場合両方が10000以上の場合のみ1になります。) 草とキノコに1ダメージ固定は削除しました。(元々本鯖でも1固定ではないです。精練等による引き上げダメージはそのまま出ますので... 固定したいのならdefとmdefを10000にしてください。実はこれはクリスタルに為に作ろうとした物ですが...)
+・爆裂波動の時アイテムによるスキルは使用できるように修正。
+・その他少し修正。(修正の物の中にテストしてない物もあります。)
+ (/doc)
+ conf_ref.txt 修正。
+ (/conf)
+ battle_athena.conf 修正。
+ (/map)
+ battle.h 修正。
+ battle.c 修正。
+ mob.h 修正。
+ mob.c 修正。
+ skill.c 修正。
+ npc.c 修正。
+ pc.c 修正。
+ script.c 修正。
+ clif.c 修正。
+ chrif.c 修正。
+
+--------------
+//0534 by Diex
+
+・コンボシステム仮実装
+ map/
+ battle.c
+ battle_weapon_attack() 修正。
+ clif.c
+ clif.h
+ clif_combo_delay() 関数追加。
+ map.h
+ map_session_data 変数追加。
+ pc.c
+ pc_authok() 変数追加。
+ pc_attack_timer() 修正。
+ skill.c
+ skill_castend_damage_id() 修正。
+ skill_check_condition 修正。
+ skill_use_id 修正。
+ db/
+ skill_db.txt 修正。
+
+注)猛龍拳から阿修羅覇鳳拳にはまだつなげません。
+ 阿修羅覇鳳拳を放った後、PCはMOBの背後(?)に移動してるっぽいのですが、
+ そこらへんの情報が足りません。情報提供お願いします。
+
+--------------
+//0533 by るるる
+
+・草とキノコに1ダメージ固定
+battle.c の battle_weapon_attack() と battle_calc_attack() を修正
+battle.c の battle_get_mobid() を追加
+mob.c の mob_makedummymobdb() と mob_readdb() を修正
+
+・スキルログにモブの固有番号&PCのID番号を表示
+(battle.c の battle_get_mobid() を追加したのでそのついでに)
+mob.c skill.c の変更箇所多数(汗
+("MOB %d" もしくは "PC %d" で検索すれば変更箇所がわかるかと)
+
+・ハンターの罠を使ったスキルで罠を消費するようにした
+batttle.c の skill_check_condition() を修正
+(ジェム消費処理の流用っぽいことをやってるんだけど処理中身は理解してないw)
+
+・サンクチュアリ&マグヌスのダメージ判定を不死属性&悪魔種族に再度修正
+0532で再び元に戻ってしまったのをなおしました。
+ただし、回数&人数判定には手を加えていません(ってか自分にはまだムリ)
+
+以上。
+切った貼ったの見様見真似でやったので言語的に果たしてこれでよいのか。。。
+もし処理方法に問題有りだったら修正なりをしていただけると嬉しいです。
+
+--------------
+//0532 by 死神
+
+・修正した所を全然チェックしてなかったので修正したファイルだけ...汗
+・mapflagにnopenalty追加。使用方法は
+ mapflag nopenalty dummy
+です。機能はそのマップで死んだ時経験が減らないようにします。
+・mapflagにpvp_noparty、pvp_noguild、gvg、gvg_noparty追加。
+pvp_nopartyはPVPモードで同じパーティに攻撃が当たらない、pvp_noguildはPVPモードで同じギルドに攻撃が当たらない、gvgはシーズモードに、gvg_nopartyはシーズモードで同じパーティに攻撃が当たらない物です。
+・可動してないタイマーは全て-1になるように変更。
+・吹き飛ばし処理修正。
+・マップロード直後気功が見えない問題修正。
+・残影の処理修正。
+・マップをロードすると死んだふりが解除されるように変更。
+・PVPを少し変更。
+・古木の枝で出るモンスターを自分のレベルより高い物は出ないように変更。
+・加速ポーションのsc_start SC_SpeedPot0,1,0;をsc_start SC_SpeedPot0,1800,0;のように変更。SC_SpeedPot?の後の数値は持続時間です。(単位は秒)
+・@コマンド@pvpを@pvponに変更と@pvponと@pvpoff、@gatの機能変更。
+・battle_athena.confのpvp削除。
+・battle_athena.confにdeath_penalty_type追加。
+・ペナルティの適用を死んだ時から死んだ後リスタートした時に変更。(リザで復活すると経験が減りません。本鯖の仕様がかなり気にいらなかったので変更しました。)
+・スクリプトsetmapflag、removemapflag、pvpon、pvpoff、gvgon、gvgoff追加。
+ setmapflag マップ名、マップフラグタイプ
+ 指定したマップフラグをonします。(ただpvp、gvgはpvpon、gvgonでできるので指定しても動作しません。あとnosaveの場合処理がちょっと複雑になるので対応してません。)
+ removemapflag マップ名、マップフラグタイプ
+ 指定したマップフラグをoffします。(ただpvp、gvgはpvpoff、gvgoffでできるので指定しても動作しません。こちらはnosaveも可能です。)
+ pvpon マップ名
+ 指定したマップをPVPモードにします。
+ pvpoff マップ名
+ 指定したマップのPVPモードを解除します。
+ gvgon マップ名
+ 指定したマップをシーズモードにします。
+ gvgoff マップ名
+ 指定したマップのシーズモードを解除します。
+ただ全てのスクリプトの動作は確認してませんので注意してください。
+・サンクチュアリ、マグヌスエクソシズムの処理を0529に戻しました。
+自分の調査ではサンクチュアリは人数の制限があります。(レベル1で4名で
+1レベルに一人ずつ増えます。)
+・その後少し修正。(したはず...)
+ (db/)
+ const.txt 修正。
+ item_db.txt 修正。
+ (conf/)
+ battle_athena.conf 修正。
+ (doc/)
+ conf_ref.txt 修正。
+ (map/)
+ clif.h、clif.c 修正。
+ mob.c 修正。
+ pc.h、pc.c 修正。
+ skill.c 修正。
+ pet.c 修正。
+ npc.c 修正。
+ map.h、map.c 修正。
+ battle.h、battle.c 修正。
+ atcommand.h、atcommand.c 修正。
+ script.c 修正。
+ makefile 修正。
+
+--------------
+//0531 by 獅子o^.^o
+
+conf/npc_turtle.txt
+.タートルアイランドに行く時、サ一バ一を落って問題修正
+.npc_turtle.txtの508行目
+set Zeny - 10000,0; --> set Zeny,Zeny-10000; 修正
+
+--------------
+//0530 by RR
+・スキル「サンクチュアリ」で攻撃対象をアンデット/悪魔種族から不死属性/悪魔種族に変更
+・スキル「マグヌスエクソシズム」で攻撃対象をアンデット/悪魔種族から不死属性/悪魔種族に変更
+・スキル「サンクチュアリ」の回復回数を人数からカウントに変更
+ skill.c
+ skill_unit_onplace()修正
+ skill_unit_onout()修正
+
+多分この仕様で合ってるはずです…。
+
+--------------
+//0529 by 胡蝶蘭
+
+・MOBがスキル「ヒール」を使用するとサーバーが落ちる場合があった問題を修正
+・スキル「サンクチュアリ」で攻撃対象を不死属性からアンデット/悪魔種族に変更
+
+ skill.c
+ skill_unit_onplace()修正
+ skill_castend_nodamage_id()修正
+
+・ログインサーバーのアカウントデータベース保守ツールを添付
+ Perl製なので実行にはPerlが必要です。
+ 使用方法などはエディタで開いて見てください。
+ 使い方が良くわからない人は手を出さないほうがいいです。
+
+ 特に理由が無い限りアカウント作成もこちらのツールを使ってください。
+ addaccountはパケットの都合上パスワード文字数の制限がきついので。
+
+ アカウントを削除してもキャラクターデータ、倉庫データ、
+ その他のアカウント以外のデータは消えません。相手がログイン中だった場合
+ 強制切断はされませんが、次回からはログインできないはずです。
+ (つまりは、単にlogin-server上のアカウントを消しているだけです)
+
+ (login/)
+ login.c
+ parse_admin()追加、parse_login()修正
+ (doc/)
+ admin_packet.txt
+ 新規追加。管理パケット情報
+ (tool/)
+ ladmin
+ login-server administration toolのPerlスクリプト
+
+
+--------------
+//0528 by RR
+・スキル「ヒール」を使用した際に回復量に比例した分だけジョブ経験値が獲得できるように変更
+・商人系職業が店NPCを利用した際にジョブ経験値が獲得できるように変更
+・両方ともbattle_athena.confで調整可能にしました。初期設定は0倍(非適用)
+・map_athena.confにてかぼちゃクエストのものが入ってなかったのでコメントアウトしながら追加。
+
+ map_athena.conf
+
+ battle.c battle_config_read()
+ battle.h Battle_Config
+ battle_athena.conf
+ 以上、battle_athena.conf利用ために変更
+
+ pc.c pc_heal()
+ 戻り値をhp+spに。戻り値を利用してる部分がなさそうだったので使わせて貰いました。バグが起きたらすみません。
+
+ skill.c skill_casted_nodamage_id()のヒール部にてジョブ経験値獲得するよう変更
+
+ npc.c npc_buylist()
+ npc_selllist() 変更
+ これらとの兼ね合いでskill.hをinclude。
+
+
+商人の店利用ジョブ経験値獲得ですが、計算式はまだ考え中なので仮で。
+計算式はアイテム購入が 代金 * スキルレベル(ディスカウント)/ ((1+300/アイテム個数) * 4000)
+アイテム売却が 代金 * スキルレベル(オーバーチャージ) / ((1+500/アイテム個数) * 4000)です。
+常に矢をたくさん一緒に購入することで経験値を多量に稼ぐことが可能ですね…。
+どなたかいい式を思いついたら変更お願いします。
+
+ところで転職NPCが一部かぶってるんですが修正しないでいいんでしょうか?
+
+--------------
+//0527 by 死神
+
+・0526のバグ修正。(テストの為に変えていた物を入れたままアップしてしまったのが原因でした。)
+ skill.c 修正。
+ pc.c 修正。
+ mob.c 修正。
+ clif.c 修正。
+
+--------------
+//0526 by 死神
+
+・0525のリザレクションを死んだキャラに使えないバグ修正。(テストはしてませんが治ったはずです。多分...)
+ skill.c 修正。
+ clif.c 修正。
+
+--------------
+//0525 by 死神
+
+・dmotionの間はキャラが動かないように変更。(テストしてません。)
+・メテオのダメージ表示タイミング修正。(少し遅い気もしますが...)
+・バックスライディングの時にモーションが出るように変更。(スキル使用後
+0.2秒後にスキル使用パケットを送るように変更しました。ラグ等によって変な動作をする可能性もあります。)
+・0524の修正。
+・ハイディングしている時自然回復しないように変更。
+・0519で間違ったconfの修正と細かい所修正。
+ map.h
+ struct map_session_dataのcanmove_tickをcanact_tickに変更。
+ skillcanmove_tickをcanmove_tickに変更。
+ skill.c 修正。
+ pc.c 修正。
+ pc.h 修正。
+ clif.c 修正。
+ battle.c 修正。
+ battle.h 修正。
+ mob.c 修正。
+ mob.h 修正。
+ char_athena.conf 修正。
+ map_athena.conf 修正。
+
+--------------
+//0524 by huge
+
+・ローグ トンネルドライブ実装
+ clif.c
+ clif_parse_WalkToXY()
+ pc.c
+ pc_calcstatus()
+
+ どのくらい速度減少するのか分からなかったので、暫定的に
+ speed += speed * (20-スキルレベル)/40
+ と、しました。本鯖仕様が分かる方居ましたら修正お願いします。
+
+--------------
+//0523 by NOCTURNE
+
+・npc_event_rental.txtにクルセイダー用のペコペコ管理兵を追加
+--------------
+//0522 by 波浪
+
+・mob_db.txtをジュノー後のデータに修正
+
+--------------
+//0521 by 胡蝶蘭
+
+・mapサーバーに繋がらない問題を修正
+ clif.c
+ clif_parse()の修正
+
+--------------
+//0520 by 胡蝶蘭
+
+・charサーバーログの「set map X.Y HOGE.gat」が表示されなくなりました
+ 変わりに、「set map M from XX.YY.ZZ.WW:PP (CC maps)」
+ というふうに何個のマップをセットしたかだけを表示するようになります。
+
+ char/char.c
+ parse_frommap修正
+
+・複数mapサーバーに仮対応
+ ・NPCのマップサーバー変数は鯖間では共有されません。共有すべき変数を
+ 持つNPCがいるマップ同士は同じmapサーバーで動かすべきです。
+ おそらくPCのグローバル変数は共有できると思います(未テスト)
+ ・暫定的に動くようにしただけなので、不都合があるかもしれません。
+ 特に、パーティ/ギルド/倉庫/ペット/Wisなどのinterサーバーを使う機能が
+ 正しく作動するか全くチェックしていません。
+ ・「recv map on XX.YY.ZZ.WW:PP (CC maps)」というログが表示されます。
+ これは他のmapサーバーが担当するマップのリストが、このmapサーバーに
+ 正常に受信されたという意味です。
+
+ (char/)
+ char.c/char.h
+ parse_frommap()修正
+ mapif_sendallwos()追加
+ (map/)
+ map.c/map.h
+ map_setipport()を修正
+ struct map_session_dataのstateメンバにwaitingdisconnect追加
+ chrif.c/chrif.h
+ 色々追加
+ clif.c
+ waitingdisconnectが1ならパケットを無視するようにした
+ pc.c
+ pc_setpos()修正(マップサーバー変更処理など)
+ pc_setnewpc()修正
+
+--------------
+//0519 by 死神
+
+・サーバーsnapshotと色々修正。
+・死んだキャラに攻撃が当たるバグ修正。(テストしてません。本当に治ったかどうか報告お願いします。)
+・0517のアイスウォールの処理を少し変更。
+・メテオをモンスターも使えるように変更。(テストしてません。報告お願いします。) でもちょっとメモリーの使用量が増えました。(約10M程上がるようです。)
+・ボスの取り巻きがボスと一緒に行動するように変更。(テストしてませんのでどんな動きをするかは確認してません。攻撃も受けてないのにボスの隣から勝手に離れるかどうかの確認をお願いします。)
+・その他細かい物修正。
+ client-packet.txt 修正。
+ map.h
+ AREA_SIZEを15から20に変更。
+ struct map_session_data、struct mob_data、struct skill_timerskill 修正。
+ map.c
+ map_quit() 修正。
+ clif.h
+ clif_changemapcell() 修正。
+ clif.c
+ clif_getareachar_skillunit()、clif_clearchar_skillunit()、clif_changemapcell() 修正。
+ skill.c
+ skill_unitsetting()、skill_unit_onlimit()、skill_castend_pos2() 修正。
+ skill_castend_nodamage_id()、skill_check_condition()、skill_attack() 修正。
+ skill_timerskill()、skill_addtimerskill()、skill_cleartimerskill() 修正。
+ その他少し修正。
+ skill.h
+ skill_addtimerskill()、skill_cleartimerskill() 修正。
+ pc.c
+ pc_movepos()、pc_walk()、pc_authok() 修正。
+ mob.c
+ mob_spawn_dataset()、mob_spawn() 修正。
+ mob_changestate()、mob_damage() 修正。
+ mob_ai_sub_hard_mastersearch()、mob_ai_sub_hard() 修正。
+ その他少し修正。
+ battle.c
+ battle_calc_weapon_attack()、battle_weapon_attack() 修正。
+ その他少し修正。
+
+--------------
+//0518 by Kalen
+・Event_pumpkin関連のフラグ不具合修正
+
+--------------
+//0517 by 死神
+
+・アイスウォールで摺り抜る問題修正と少し修正。(Mさんパケットの提供ありがとうございます。)
+ clif.h
+ clif_changemapcell() 追加。
+ clif.c
+ clif_changemapcell() 追加。
+ skill.h
+ SC_STEELBODYを84から87に変更。
+ skill.c
+ skill_unitsetting()、skill_unit_onlimit() 修正。
+ skill_status_change_end()、skill_status_change_start() 修正。
+ client_packet.txt 修正。
+
+--------------
+//0516 by 死神
+
+・モンスターのメテオによる鯖ダウンを臨時に防いで置きました。(テストはしてません。) モンスターのスキルについてちょっと分析不足ですので分析した後に修正して置きます。
+・スキル指弾の仕様をbattle_athena.confで決めるように変更。(0515の物がちょっともったいなかったので...)
+ skill.c
+ skill_castend_pos2() 修正。
+ battlc.h、battle.c
+ battle_configにfinger_offencive_type 追加。
+ battle_calc_weapon_attack() 修正。
+ battle_athena.conf 修正。
+ conf_ref.txt 修正。
+
+--------------
+//0515 by 死神
+
+・スキルメテオと指弾修正とパケット修正、0512の落とし物修正と少しだけの仕様変更です。
+・指弾の場合説明を見てこんな感じかなと思って作った物です。以前の物が本鯖にあっているなら元に戻します。
+・メテオの1発の範囲は5*5セル(range = 2)です。
+・アイテム使用パケットを新しい物に変更したがエフェクトが出ない物は出ないようです。(色々エフェクトが入っているみたいだから後は使って確認ですけどね。)
+・0512でhitrateが10000以上で必中ではなく100000以上で必中ですのでコードの修正のさいには気をっつけてください。
+・battle_athenaに設定されている武器の製造率とペットの捕獲確率の計算方法を少し変えました。(気にする必要もない物ですけどね。)
+ skill.h
+ skill_addtimerskill()、skill_cleartimerskill 追加。
+ skill.c
+ skill_attack() fix、skill_use_id()、skill_use_pos() 修正。
+ skill_castend_damage_id()、skill_castend_nodamage_id() 修正。
+ skill_timerskill()、skill_addtimerskill()、skill_cleartimerskill 追加。
+ skill_castcancel()、skill_castend_pos2()、skill_unitsetting() 修正。
+ skill_produce_mix()、do_init_skill() 修正。
+ mob.c
+ mob_damage() 修正。
+ battle.c
+ battle_calc_weapon_attack() 修正。
+ map.h
+ struct skill_timerskill 追加。
+ struct map_session_data 修正。
+ map.c
+ map_quit() 修正。
+ pc.c
+ pc_authok() 修正。
+ pc_damage() 修正。
+ clif.c
+ clif_parse_WalkToXY() 修正。
+ clif_useitemack() 修正。(資料提供: Kalenさん)
+ pet.c
+ pet_catch_process2() 修正。
+ skill_db.txt
+ 気功のSPを10に変更。(ネットの検索では10だったので変更しました。韓国蔵では15と表示されますが...)
+ client_packet.txt
+ 01c8の変更です。Kalenさん情報提供ありがとうございます。
+
+--------------
+//0514 by Kalen
+
+・script修正+追加
+
+ conf/npc_event_pumpkin.txt (新規)カボチャイベント
+ conf/npc_town_guide.txt (修正)Junoの「+」アイコンカラー修正
+ conf/npc_town_lutie.txt (一部追加)カボチャイベントに影響するNPCの会話追加
+
+--------------
+//0513 by RR
+
+・転職時に装備が全て外れるようにしました。関数位置の変更してないので、ひょっとしたらおかしくなってるかもしれません。うちの環境(win2k cygwin)では平気でしたのでそのままにしてあります。。
+・ノービス時の死亡では、最大HPの2分の1で復活できるよう修正。(スキルによる復活は未確認)
+・デスペナルティによる経験値減少を追加。battle_athena.confにて、減少率を変更できるように設定。減る経験値は小数点以下切り捨てなので、必要経験値が低いうちにはちょうどその%分引かれるという風にはなりません。
+ battle.h
+ Battle_Configにdeath_penalty_baseとdeath_penalty_jobを追加。
+ battle.c
+ battle_config_readでdeath_penalty_baseとdeath_penalty_jobを読むように修正。
+ pc.c
+ pc_makesavestatus() 修正。
+ pc_damege() 修正。
+ pc_jobchange() 修正。
+ battle_athena.conf
+ death_penalty_base,death_penalty_job追加。
+
+--------------
+//0512 by 死神
+
+・問題になりそうな部分の修正と新しいパケットの対応がメインです。後バグも少し治しました。(動けない状態異常になっても動く問題の修正等です。)
+ athena.sh 修正。(いつも鯖を個別に実行していたので気がつきませんでした。)
+ makefile
+ DPACKETVERを2から3 に修正。ジューノ以後の蔵を使うのなら3にして使ってください。(その以前なら2か1)
+ clif.c
+ DPACKETVER=3に対応(今の所0x114を0x1deに変換と0x11fを01c9に変換するのみ対応)
+ clif_skill_damage3() 削除。
+ clif_skillcastcancel() 追加。
+ clif_skill_damage()、clif_getareachar_skillunit()、clif_skill_setunit() 修正。
+ clif_fixmobpos()、clif_fixpetpos()、clif_fixpcpos() 修正。
+ 他に少し修正。
+ clif.h
+ clif_skill_damage3() 削除。
+ clif_skillcastcancel() 追加。
+ battle.c
+ battle_calc_weapon_attack() 修正。
+ hitrateを10000以上にすれば必中になるように変更。(今の仕様ではモンスターの必中攻撃以外は必中になりません。)
+ 他に少し修正。
+ client_packet.txt
+ 新しいパケット情報追加。
+ pc.c
+ pc_spiritball_timer()、pc_delspiritball() 修正。
+ pc_damage()、pc_skill() 修正。
+ skill.h
+ SC_EXPLOSIONSPIRITSを89から86に変更。(86 = 0x56)
+ SC_DELUGEを86から89に変更。
+ skill.c
+ skill_castcancel()、skill_use_id()、skill_use_pos() 修正。
+ skill_check_condition() 修正。
+ skill_castend_damage_id()、skill_castend_nodamage_id 修正。
+ skill_status_change_end()、skill_status_change_start() 修正。
+ skill_db.txt
+ 気功の消費SPを修正。(前の15が本鯖にあっているみたいですので...)
+ mob.c
+ mobskill_use_id()、mobskill_use_pos() 修正。
+ map.c
+ map_quit() 修正。
+ atcommand.h
+ atcommand.c
+ @コマンド@spiritball追加。(機能は使えばわかります。ただ1000以上は入れない方がいいです。蔵がパンクしますので...)
+ atcommand_athena.conf
+ 修正。
+ conf_ref.txt
+ 修正。
+・0x196パケットに新しい物が追加されているので状態変化に直接に関係ないSC_xxxxの番号を調整する必要があります。(今はSC_EXPLOSIONSPIRITSにだけ対応しました。) それとskill_status_change_end()、skill_status_change_start()でclif_status_change()を呼ぶtypeの範囲が64(0x40)未満になっているがそれも追加されている物に合わせて修正する必要がありますが追加されている物が全てわかったわけでもないのでSC_EXPLOSIONSPIRITSにだけ対応しました。今度からは爆裂波動の解除が正確に見えます。金剛はデータを見つけられませんでした。
+※新しいパケットに対応する作業をしていますが情報が不足です。
+イグドラシルの実やイグドラシルの種のエフェクトが出るようにする為に01c8を使ってみましたが駄目でした。client_packet.txtのデータでは何も起こらないので何方が本鯖でイグドラシルの実やイグドラシルの種を使った時のパケットを提供してくれませんか?S 00a7の後00a8が来るのかそれとも01c8が来るのかの確認と00a8の後に01c8が来るのかの確認ができれば何とかなると思いますが...
+それと01c9の後に来る?.81bがわかればアイスウォールを摺り抜る問題も解決できると思いますが...
+情報提供をお願いします。
+
+--------------
+//0511 by Diex
+
+・指弾の攻撃回数修正。
+・阿修羅覇鳳拳、発勁が修練を無視し、無属性になるように修正。
+・金剛使用時、MDEFが正しく表示されてなかったバグを修正。
+・気功の消費SPを修正。
+ pc.c
+ pc_calcstatus() 修正。
+ battle.c
+ battle_calc_weapon_attack() 修正。
+ skill.c
+ skill_check_condition() 修正。
+
+ skill_db.txt 修正。
+
+--------------
+//0510 by Diex
+
+・三段掌の表示バグ修正
+・指弾が気弾が無くても撃てるバグを修正
+ map.h
+ struct map_session_dataにspiritball_old変数追加。
+ skill.c
+ skill_check_condition() 修正。
+ clif.c
+ clif_skill_damage3() 修正。
+ battle.c
+ battle_weapon_attack()、battle_calc_weapon_attack 修正。
+
+--------------
+//0509 by
+
+・npc_warp.txt
+ プロ城→プロフィールドになっていたのを、
+ プロ城→ヴァルキリーレルムに修正。
+ プロフィールド→プロ城になっていたのをプロフィールド→ヴァルキリーレルムに修正
+
+--------------
+//0508 by 死神
+
+・バグ修正と息吹、気功、気奪の修正がメインです。(今度からは他の人にも気がちゃんと見えます。)
+・死んだキャラに経験値が入る問題修正。(テストはしてません。どうなのか報告をお願いします。)
+ pc.h
+ pc_addspiritball()、pc_delspiritball() 追加。
+ pc_is50overweight() 修正。
+ pc.c
+ pc_gainexp() 修正。
+ pc_insert_card()、pc_item_identify() 修正。(大した修正じゃありませんが...)
+ pc_authok() 修正。
+ pc_addspiritball()、pc_delspiritball() 追加。
+ pc_spiritball_timer() 追加。
+ do_init_pc()、pc_calcstatus()修正。
+ pc_spirit_heal() 追加。
+ pc_natural_heal()に関わる物の修正。
+ map.h
+ struct map_session_data 修正。
+ map.c
+ map_quit() 修正。
+ map_addflooritem() 修正。
+ clif.h
+ clif_spiritball_int()をclif_spiritball()に変更。
+ clif_spiritball_ext() 削除。
+ clif.c
+ clif_spiritball_int() をclif_spiritball()に変えて修正。
+ clif_spiritball_ext() 削除。
+ clif_set01e1() 追加。
+ clif_getareachar_pc() 修正。
+ skill.h
+ SC_CALLSPIRITS 削除。
+ skill.c
+ SC_CALLSPIRITS 削除。
+ skill_castend_nodamage_id()、skill_check_condition() 修正。
+ skill_status_change_start() 修正。
+・床に落ちたアイテムが消えるまでの時間を設定できるように変更。
+ battle.h、battle.c
+ battle_config_read() 修正。
+ conf_ref.txt 修正。
+ battle_athena.conf 修正。
+
+--------------
+//0507 by Diex
+0505の修正
+・三段掌のエフェクト実装
+
+--------------
+//0506 by hoenny
+全体的に少し式修正
+セイジのスキルがアイテムを消耗するように修正
+(スキルのDBに zeny, spiritball, item, equipを入れたいが時間がなくて臨時的に ...)
+
+--------------
+//0505 by Diex
+
+・阿修羅覇鳳拳のダメージ修正
+・猛龍拳のダメージ修正
+・指弾実装
+・発勁実装
+・金剛が減算DEFと減算MDEFが90に固定されていたのを乗算DEFと乗算MDEFが90になるように修正
+・三段掌実装(ただしコンボは未実装)
+ map/clif.c
+ map/clif.h
+ clif_skill_damage3()追加
+ map/pc.c
+ map/skill.c
+ map/battle.c
+ 修正・及び追加
+(計算式は+ Acolyte Maniax +を参考にしました。)
+
+--------------
+//0504 by 死神
+
+・atcommand.c 修正。(たった2文字を追加しただけです。)
+atcommnad_gm_onlyがnoじゃなくても使用レベル設定を0にしたコマンドはGMじゃないキャラでも使えます。テストはしてません。
+
+--------------
+//0503 by nabe
+
+・精錬修正とLinux用にちょっと変更など
+ conf/map_athena.conf
+ npc_event_doll.txt,
+ npc_turtle.txt,
+ //npc_pota.txt項目追加
+ conf/npc_pota.txt追加(socieさん作のダンジョンポタ子さん)
+ conf/npc_shop.txt
+ イズルード武器商人の価格修正
+ conf/script_athena.conf
+ 0499での文字化け?修正
+ login/login.c
+ #include <time.h>追加
+ map/script.c
+ buildin_getequipname()
+ 精錬メニューのmallocを修正
+ buildin_getequipisenableref()
+ 精錬可能条件修正(Athefansさんの条件文にしてみました)
+ map/skill.c
+ skill_check_condition()変数宣言位置の変更のみ
+
+--------------
+//0502 by 死神
+
+・カプラの倉庫もbattle_athena.confのbasic_skill_checkによって基本スキルが足りなくても使用することができるように修正しました。(プロンテラ中央のカプラのみ確認。)
+・クェストスキルの取得は0492のせいです。0481にskillの最後が,2じゃなく,0だと書いたのですが何故か0492で,2になっていたので,0に修正しました。
+・スクリプトgetbaseskillcheckをbaseskillcheckに変更と自分で修正しましたが命令の後になんの数値も入らない場合はスクリプトが正しく作動しなかったので使用する場合はbaseskillcheck(0)にして使わないと正しい結果を得ることができません。
+(詳しくはnpc_town_kafra.txt参照。)
+ npc_event_skillget.txt 修正。
+ npc_town_kafra.txt 修正。
+ script.c 修正。
+・GM_account.txtに自分がテストの為に使っていた500000が入っていたので削除しました。(GM_account.txt作った理由は自分勝手にアカウントをGMにすることができるようにする為です。つまりGMとして表示されなくてもGMとして色んな権限を使うことができます。さすがにGM専用の右クリックコマンドは使えませんが... でもこれを活用している方はいないみたいなんですね...)
+
+--------------
+//0501 by hoenny
+
+・500の問題点修正
+-HP回復力向上,SP回復力向上
+ map/pc.c
+ pc_natural_heal_hp()修正
+ pc_natural_heal_sp()修正
+
+500SP回復力向上
+--------------
+//0500 by hoenny
+
+・修道僧の息吹実装
+-基本的に座った時 10秒ごとに回復します.
+-所持量が 50%をオーバーした場合 20秒ごとに回復します.
+・修道僧の気奪実装
+-他の修道僧の氣球も吸収が可能です.
+・修道僧の金剛実装
+-金剛状態ではすべてのアクティブスキルを使うことができないです.
+ db/skill_db.txt
+ 気奪修正
+ 金剛修正
+ map/pc.h
+ pc_is50overweight()追加
+ pc_is90overweight()追加
+ map/pc.c
+ pc_calcstatus()修正
+ pc_spheal()修正
+ pc_hpheal()修正
+ pc_natural_heal_hp()修正
+ pc_natural_heal_sp()修正
+ pc_natural_heal_sub()修正
+ map/skill.c
+ skill_check_condition()修正
+ skill_castend_nodamage_id()修正
+ skill_status_change_start()修正
+
+--------------
+//0499 by 死神
+
+・サーバーsnapshotとバグ修正。
+・スティールコインのゼニ量をモンスターレベル*10 + rand(100)に変更。
+・キャスティングタイムがないスキルはタイマーを使わないように変更。
+・カードの使用によるマップ鯖ダウンを防ぐ為に修正。(今度こそ治ったはず...) そして拡大鏡も同じようなことができるので修正。(これは鯖ダウンまでは起こさないようですが...)
+・キャラにマップのロードが終わるまでペットのデータが来ないとマップ鯖が落ちる問題修正。(滅多なことがない限り起こらないですけどね。)
+・オートバーサーク、重さのアイコンとチェックが正しく適用されるように修正。
+・増速ポーションの適用順番変更。今まではスピードアップポーションが最優先で次がハイスピードポーション、最後がバーサークポーションだったのですが順番を逆に変更しました。
+・アイテムで使うスキルはキャスティングタイムとディレイが0になるように変更。
+・アイテムで使うスキルのレベルがitem_dbに設定しているレベルより高くなるバグ修正。
+ pc.h
+ pc_move()をpc_movepos()に変更。
+ pc.c
+ pc_steal_coin()、pc_insert_card()、pc_item_identify()、pc_authok()、
+ pc_calcstatus()、pc_checkweighticon()、pc_damage() 修正。
+ skill.c
+ skill_castend_pos2()、skill_check_condition()、skill_use_id()、
+ skill_use_pos() 修正。
+ pet.c
+ pet_recv_petdata()、pet_change_name() 修正。
+ map.h
+ struct map_session_dataにskillitemlv 追加。
+ script.c
+ buildin_itemskill() 修正。
+ clif.c
+ clif_parse_UseSkillToId()、clif_parse_UseSkillToPos()、
+ clif_parse_LoadEndAck() 修正。
+ mob.c
+ mobskill_use_id()、mobskill_use_pos() 修正。
+
+--------------
+//0498 by hoenny
+
+.氣球がすっかり見えるように修正(消耗スキル使用の時消耗するように修正)
+.スキル残影を使用の時氣球を消耗するように修正
+ doc/client_packet.txt
+ 0x1d0追加
+ map/map.h
+ sdのstructureに spiritball追加
+ map/clif.h
+ clif_spiritball_del() -> clif_spiritball_int()修正
+ clif_spiritball_cre() -> clif_spiritball_ext()修正
+ map/clif.c
+ packet_len_table[]修正
+ clif_spiritball_del() -> clif_spiritball_int()修正
+ clif_spiritball_cre() -> clif_spiritball_ext()修正
+ map/pc.h
+ pc_item_steal() -> pc_steal_item()修正
+ pc_coin_steal() -> pc_steal_coin()修正
+ map/pc.c
+ pc_item_steal() -> pc_steal_item()修正
+ pc_coin_steal() -> pc_steal_coin()修正
+ pc_calcstatus()修正
+ map/skill.c
+ skill_check_conditon()修正
+
+--------------
+//0497 by 死神
+
+・0491のスキル残影のバグ修正とスティールとスティールコイン、スナッチャーの修正、mob_targetのバグ修正。
+・交換、座り、パーティ結成等の時に基本スキルをチェックするように修正。(battle_athena.confでチェックするかどうかを決めることができます。)
+ただカプラの倉庫はスクリプトで制限をかけるしかありません。
+・スクリプトgetbasicskillcheck追加。
+ 使用方法> getbasicskillcheck
+ 戻り値はbattle_athena.confのbasic_skill_checkです。0の場合は基本スキルのチェックなしで1の場合は基本スキルをチェックするのを意味します。
+ skill.c
+ skill_castend_pos2() 修正。
+ skill_additional_effect() 修正。
+ pc.c
+ pc_move() 追加。
+ pc_item_steal()、pc_coin_steal() 修正。
+ pc.h
+ pc_move() 追加。
+ map.h
+ struct mob_data 修正。
+ mob.c
+ mob_spawn() 修正。
+ mob_target() 修正。
+ clif.c
+ clif_pcinsight()、clif_pcoutsight() 修正。
+ clif_parse_ActionRequest()、clif_parse_Emotion()、
+ clif_parse_TradeRequest()、clif_parse_CreateParty()、
+ clif_parse_ReplyPartyInvite() 修正。
+ battle_athena.conf 修正。
+ conf_ref.txt 修正。
+ skill_db.txt
+ スティールの射程を3から1に修正。
+ battle.h、battle.c
+ battle_configにbasic_skill_check 追加。
+ battle_config_read() 修正。
+ trade.c
+ trade_traderequest() 修正。
+ script.c
+ buildin_getbasicskillcheck() 追加。
+ map/makefile 修正。
+※スティールとスティールコインの計算式は適当に作った物です。本鯖の方がどうなのか全然わからないので...
+
+ スティール率 = (モンスターのアイテムdrop率 * (キャラレベル*0.5 + dex*0.4 +スキルレベル*5))%
+ スティールコイン率 = (スキルレベル + (キャラレベル - モンスターのレベル)*0.3 + dex*0.2 + luk*0.2)%
+ スナッチャー発動率 = (5.5 + スキルレベル*1.5 +スティールのスキルレベル)%
+
+本鯖の計算式がわかる方は情報提供をお願いします。
+スティールコインのゼニの量はモンスターのレベル*100になっています。これについても情報提供をお願いします。
+※残影の場合使った後普通に歩く前にはペットの装備が見えないバグがありますが原因がわからないので放置することにしました。
+※基本スキルが足りない時出るメッセージは殆ど合わせていますがパーティに入る時に基本レベルが足りない時に合う物がなかったのでパーティを作れない(基本スキルレベル7の物)と表示して勧誘した方には拒絶されたと表示されます。
+
+--------------
+//0496 by hoenny
+
+.WZのメテオストーム実装
+.スティールコイン修正
+ db/skill_db.txt
+ メテオストーム修正
+ map/pc.c
+ pc_coin_steal()修正
+ map/skill.c
+ skill_castend_pos2()修正
+
+--------------
+//495 by nini
+
+・ARが片手斧、両手斧でしか発動しなかったところ修正→片手斧、両手斧、鈍器
+ map/skill.c
+ skill_check_condition() 修正
+
+前回修正のとき鈍器入れ忘れてたようです。
+
+--------------
+//0494 by 獅子o^.^o
+
+conf/mpc_warp.txt
+.ハンタ一転職地出ていない修正
+
+--------------
+//0493 by 波浪
+
+・script修正
+ npc_town_comodo.txt コモド案内要員部分を削除(npc_town_guide.txtと重複していたので)
+ npc_town_guide.txt ジュノー案内要員を追加(viewpointの色が…)
+ npc_town_kafra.txt ジュノーカプラ部分をnpc_town_yuno.txtから移動
+ npc_town_refine.txt ジュノー精錬所部分をnpc_town_yuno.txtから移動
+ npc_town_yuno.txt 案内要員とカプラと精錬所部分を削除
+ npc_turtle.txt 会話を微修正
+
+--------------
+//0492 by Kalen
+
+・script修正+追加
+
+ conf/npc_event_doll.txt (新規)
+ conf/npc_turtle.txt (新規)亀島関連NPC+亀島クエスト(航海日誌)追加
+
+ conf/npc_event_skillget.txt (修正)応急処置の不具合+へんなtab削除etc..
+ conf/npc_town_alberta.txt (修正)Turtle分離、ちびっ子削除(Event_dollへ移動)
+ conf/npc_town_guide.txt (修正)台詞がかなり変更されていたので、修正
+
+
+--------------
+//0491 by 死神
+
+・スキル残影(韓国クライアントでは弓身彈影)実装。(ただ気弾のチェックはしてません。)
+ pc.h
+ pc.c
+ pc_can_reach() 追加。
+ skill.c
+ skill_check_condition()、skill_castend_pos2() 修正。
+ skill_db.txt
+ 残影修正。
+・script.c
+ set_posword() 修正。
+※一人でテストは済んでいますが他の人に正しく見えるかどうかは未確認です。
+変なのかどうか報告をお願いします。
+
+--------------
+//0490 by nabe
+
+・場所スキルエフェクトでマップ鯖が落ちることがあったのを修正。
+ clif.c
+ clif_skill_poseffect()の
+ unsigned char buf[16];を、unsigned char buf[32];に修正。
+
+--------------
+//0489 by 死神
+
+・0483のバグ修正。battle_athena.confのquest_skill_learnが正しく適用されるように変更と問題があった部分の修正。(テスト済み)
+ pc.c
+ pc_calc_skilltree()、pc_skill() 修正。
+ atcommand.c
+ @lostskill 少し修正。
+・カードの使用によるマップ鯖ダウンを防ぐために少し修正。(ただカードの使用によるマップ鯖ダウンを再現できなかったので本当に治ったかどうかは不明...汗)
+ pc.c
+ pc_insert_card() 修正。
+・pc.h
+ pc_ishiding() 修正。
+
+--------------
+//0488 by hoenny
+
+・RGのスティールコイン実装
+・スティール修正
+(二スキル皆一度スチールした場合またスチールすることができない.そしてスキル成功の時モンスターは攻撃するように修正した.エフェクトは成功の時だけ出るように修正した.)
+ db/skill_db.txt
+ スティールコイン修正
+ map/pc.h
+ pc_coin_steal()追加
+ map/pc.c
+ pc_coin_steal()追加
+ pc_item_steal()修正
+ map/skill.c
+ skill_castend_nodamage_id()修正
+
+--------------
+//0487 by hoenny
+
+・485の問題点ちょっと修正
+ map/pc.c
+ pc_item_steal()修正
+ map/skill.c
+ skill_castend_nodamage_id()修正
+
+--------------
+//0486 by 獅子o^.^o
+
+db/class_equip_db.txt修正
+裂けた大地の書、燃える太陽の書、乾いてる風の書、默示録、プリーストは装備することができない問題修正
+
+--------------
+//0485 by hoenny
+
+・ RGのスナッチャー実装
+・ スティール修正
+・ 露店開設の時 skill_check_conditionで状態をチェクするように修正
+ map/pc.h
+ pc_ishiding()追加
+ pc_item_steal()追加
+ map/pc.c
+ pc_item_steal()追加
+ map/skill.c
+ skill_castend_nodamage_id()修正
+ skill_additional_effect()修正
+ skill_check_condition()修正
+
+--------------
+//0484 by 胡蝶蘭
+
+・覚えてないクエストスキルにスキルポイントを振れる問題修正
+ pc.c
+ pc_calc_skilltree()でクエストスキルのチェック追加
+ skill.c
+ skill_readdb()でinf2を読むように修正
+
+・ペコペコ騎乗、ファルコンのアイコンがログイン直後には表示されない問題修正
+ clif.c
+ clif_parse_LoadEndAck()修正
+
+--------------
+//0483 by 死神
+
+・0482の適用。
+ npc_event_skillget.txt 修正。
+ script.c 修正。
+ skill.c 修正。
+ pc.c 修正。
+
+--------------
+//0482 by 胡蝶蘭
+
+・クエストスキルのスクリプト少し修正
+・スクリプトgetskilllvを呼ぶとマップサーバーが落ちるバグ修正
+ (conf/)
+ npc_event_skillget.txt
+ 出来るだけ変数を使わないように修正(未テスト)
+ (map/)
+ script.c
+ buildin_getskilllv()修正
+
+・スティールで失敗時のエフェクト変更
+・同じMOBには1回しかスティールできないように修正
+ (map/)
+ skill.c
+ skill_castend_nodamage_id()修正
+ map.h
+ struct mob_dataにsteal_countメンバ追加
+ mob.c
+ mob_spawn()修正、steal_countを0に初期化するように
+
+・イドゥンの林檎でHPが32767を超えるとサーバーが落ちるバグ修正(未テスト)
+ (map/)
+ pc.c
+ pc_calcstatus()修正
+
+--------------
+//0481 by 死神
+
+・これの適用には気をつけてください。0478の胡蝶蘭さんの物を Athena.txtのデータ形式変更せずにクェストスキルを覚えるように作った物です。自分が作ってる最中に胡蝶蘭さんが同じ物をアップしてくれたのですがデータは変えない方がいいと思って自分の物もアップしました。注意することは0478のathena.txtは使えないと言うことです。0478前の物を使ってください。
+・skill_db.txtにinf2を追加してこれを使ってクェストスキルかどうかを判断する仕組みです。
+ skill.h 修正。
+ skill.c
+ skill_readdb()修正と少し修正。
+ skill_get_inf2() 追加。
+ skill_db.txt 修正。
+ skill_tree.txt 修正。(0478前の物)
+ clif.c
+ clif_skillinfoblock() 修正。
+ char.c 修正。(0478前の物)
+・battle_athena.confにquest_skill_learn追加。
+ battle.h 修正。
+ battle.c
+ battle_config_read() 修正。
+ battle_athena.conf 修正。
+・/resetskillをbattle_athena.confにquest_skill_learnの設定に合わせてquest_skill_learnがyesの場合はスキルポイントに加算してquest_skill_learnがnoならリセットはされるがスキルポイントに加算されません。
+ pc.c
+ pc_skill()、pc_resetskill() 修正と少し修正。
+ pc.h 修正。
+ atcommand.c 修正。
+ atcommnad_athena.conf 修正。
+・スクリプトのskillコマンドでクエストスキルを覚えられるのは同じですが最後のフラグが2から0に変わってますので注意してください。
+ npc_test_skill.txt 修正。
+ npc_event_skillget.txt 修正。
+ conf_ref.txt 修正。
+ client_packet.txt 修正。
+
+--------------
+//0480 by Kalen
+
+・Eventskill追加
+ conf/npc_event_skillget.txt
+
+・map_athena.conf変更
+ warp.txtの読み込み優先度を変更
+ prt_castle等、旧EPのワープと異なる場所に変更された場合
+ 先に読み込んだ方が優先されるので、EPの高い順のがよろしいかと
+ conf/map_athena.conf
+
+--------------
+//0478 by 胡蝶蘭
+
+***
+ Athena.txtのデータ形式変更!! (自動的に変換されます)
+ バックアップを忘れずに!
+ Data format of athena.txt is changed!! (convert automatically)
+ DONT FORGET BACKUP!!
+***
+
+・クエストスキル実装
+・スクリプトでスキルレベルをチェックできるように
+ ・スクリプトのskillコマンドでクエストスキルを覚えられます。
+ 使用方法> skill スキルID,スキルLV[,フラグ]
+ フラグは省略可能で、省略すると1を指定したことになります。
+ 1で装備品による一時的な習得、2でクエストによる恒久的な習得です。
+ 恒久的な習得の場合、skill_tree.txtに依存します
+ ・getskilllvコマンド追加
+ 使用方法> getskilllv(スキルID) 戻り値はレベルです。0で未習得。
+
+ (conf/)
+ npc_test_skill.txt
+ サンプル
+ (db/)
+ skill_tree.txt
+ クエストスキルとして必要スキルIDに-1を設定。
+ (char/)
+ char.c
+ フラグもathena.txtに保存するように。
+ 以前の形式のデータも読み込めます。
+ (map/)
+ pc.c/pc.h
+ pc_skill(),pc_calc_skilltree()など修正
+ script.c
+ buildin_skill(),buildin_getskillid()など修正
+
+・@questskill,@lostskill追加
+ ・@questskill スキルID でクエストスキルを覚えます。(クエストスキルのみ)
+ ・@lostskill スキルID でスキルを忘れます。(クエストスキル以外もOK)
+
+ atcommand.c/atcommand.h
+ struct Atcommand_Configにlostskill,questskillメンバ追加
+ @questskill,@lostskill処理追加
+
+--------------
+//0477 by nabe
+
+・一部変数の宣言位置の変更のみ(Linux等でコンパイルしやすいように)。
+ atcommand.c,battle.c,clif.c,mob.c,npc.c,skill.c
+
+--------------
+//0476 by nabe
+
+・conf/ ちょっと整理
+ conf/map_athena.conf修正。
+ tortoise.txtをnpc_town_alberta.txt中に移動。
+ npc_script2.txtのコモドガイドをnpc_town_comodo.txt中に移動。
+
+・npcがキャラ名を喋るときのバグ修正
+ map/script.c
+ buildin_strcharinfo()でキャラ名用のメモリを
+ staticに確保してしまっていたのを、mallocに修正。
+
+--------------
+//0475 by hoenny
+
+泥棒のスティール実装。
+ギルド生成の時エンペリウム消耗するように修正。
+ map/guild.c
+ guild_create()修正。
+ guild_created()修正。
+ map/skill.c
+ skill_castend_nodamage_id()修正。
+
+--------------
+//0474 by 死神
+
+・0471の精練の時表示される文字の設定をmap_athena.confからscript_athena.confに変更。
+ script.c
+ do_init_script() 修正と少し修正。
+ script.h 修正。
+ script_athena.conf 追加。
+ map_athena.conf 修正。
+ map.c
+ map_config_read() 修正。
+・古い巻物、プレゼントボックス実装と少し仕様変更。
+ランダムでアイテムを得る物にデフォルトで出るアイテムを設定できるように変更。今の仕様では1000回までアイテムが選択されなかったらデフォルトアイテムが出るようになっています。デフォルトアイテムが0の場合はアイテムを得られません。
+設定する確率を*1000から*10000に変更。ただitem_~.txtの修正はやっていません。誰かやってください。(他力本願)
+ itemdb.c
+ temdb_read_randomitem() 修正。
+ itemdb_searchrandomid() 修正。
+ item_purplebox.txt から item_violetbox.txt に修正。
+ item_giftbox.txt、item_scroll.txt 追加。(moveさんありがとう。)
+ item_db.txt
+ 古い巻物、プレゼントボックス 修正。
+・trade.c
+ trade_tradecommit() 修正。pc_delitem()を使うように変更。
+※テストはやっていませんので問題があったら報告してください。
+
+--------------
+//0473 by Kuro
+
+・class_equip_dbを一部修正
+ db/class_equip_db.txt
+
+--------------
+//0471 by hoenny
+
+製錬の時出る文を変えることができるようにしました.(map_athena.confで調節可能)
+気功の数字が市廛の時実際水路表示図緑修正
+ conf/map_athena.conf
+ refine_posword:追加。
+ map/map.c
+ map_config_read()修正。
+ map/script.h
+ do_set_posword()追加。
+ map/script.c
+ do_set_posword()追加。
+ buildin_getequipname()修正。
+ map/skill.c
+ skill_status_change_start()修正。
+
+--------------
+//0470 by 死神
+
+・製造の時属性石が二度減る問題修正。(実は二度減るように見えるだけでマップを移動すると正しく表示されますが...)
+ pc.h、pc.c
+ pc_delitem() 修正。
+ npc.c、script.c、storage.c、pet.c
+ pc_delitem()を全て修正。
+ skill.c
+ skill_produce_mix() 修正。
+
+--------------
+//469 by 波浪
+
+・npc_mob_job.txt、npc_monster.txt、npc_monster30.txt、mob_db.txtのモンス名を修正
+・item_db.txtの回復アイテムの回復量をジュノー後のものに修正
+
+--------------
+//468 by Kuro
+
+・魔剣製作クエスト追加
+ conf/npc_event_ma_sword.txt
+
+--------------
+//467 by nini
+
+・BBが両手剣でしか発動しなかったところ修正→すべての武器で
+・ARが両手斧でしか発動しなかったところ修正→片手斧、両手斧、鈍器
+・スピアクイッケン発動を槍だけに
+・キャストキャンセルされないものにグランドクロス、ローグのストリップシリーズ追加
+・ブリッツビートがキャストキャンセルされなくなってた点修正
+ map/skill.c
+ skill_use_id() 修正
+ skill_check_condition() 修正
+・2-2職スキルのキャスト・ディレイ追加
+ db/cast_db.txt
+
+
+--------------
+//466 by hoenny
+
+・阿修羅覇鳳拳修正(公式修正及び sp消耗がすぐ見えるように)
+・蓄気の時気弾が見えるように修正(Mr.NO NAME様のパケ情報ありがとうございます.気弾が消えるパケ情報が不足です.)
+・パリの羽やテレポート1を連続使用の時,鯖オーバーが発生しないように仮初めで修正
+ doc/client_packet.txt
+ 0x1e1パケ情報追加
+ map/battle.c
+ battle_calc_weapon_attack()修正
+ map/clif.h
+ clif_spiritball_cre()追加
+ clif_spiritball_del()追加
+ map/clif.c
+ packet_len_table[]修正
+ clif_spiritball_cre()追加
+ clif_spiritball_del()追加
+ clif_changemap()修正
+ map/skill.c
+ skill_castend_nodamage_id()修正
+ skill_check_condition()修正
+
+--------------
+//0465 by 死神
+
+・リザレクションとハイディング、ブリッツビートのバグ修正。(ブリッツビートは報告はなかったのですが分析したら問題があったので修正。)
+ skill.c
+ skill_use_id() 修正。
+ skill_castend_nodamage_id() 修正。
+・0455のNPCを元に戻しました。
+ npc_event_ice.txt 修正。
+ npc_event_potion.txt 修正。
+ npc_town_geffen.txt 修正。(454の物に戻しました。)
+・0451のαマップをコマントアウトしました。必要な方はコマントアウトをなくして使ってください。
+ map_athena.conf 修正。
+・リザレクションは0442の問題でハイディング、ブリッツビートは0445の問題でした。それと0445の修正でスキル番号をenumで宣言した文字に変えていますがそれに落としがあるようです。(ハイディング、ブリッツビートはそのせいでした。)前の番号ソースと比べて問題がある部分は修正する必要があります。ちょっと面倒ですが...
+
+--------------
+//464 by 波浪
+
+・モンクスキルの部分について修正(未実装スレに書かれていたものを追加しただけです。
+ skill.c
+ skill_use_id()修正
+ cast_db.txt
+ モンクスキル追加
+
+--------------
+//463 by 胡蝶蘭
+
+・462のバグ修正
+ ・NPCのSHOPの不都合修正
+ ・READMEの間違い修正(warpwaitingpcがwarpwaitingroomになっていた)
+
+ map.h
+ struct npc_dataのchat_idの位置を修正
+
+--------------
+//462 by 胡蝶蘭
+
+・NPCチャット作成
+ ・waitingroom命令でNPCチャットを作成します。
+ 引数は waitingroom "チャット名",制限人数,イベント名 です。
+ イベント名は人数が最大になったときに起こすイベント名で、省略可能。
+ ・warpwaitingpc命令で、チャット内にいるPC全員をワープできます。
+ 引数はwarpと同じで、warpwaitingpc "マップ名",x,y です。
+
+ map.h
+ struct npc_dataとchat_dataを修正
+ script.c
+ buildin_waitingroom(),buildin_warpwaitingpc()追加
+ chat.c/chat.h
+ 色々修正
+ clif.c
+ clif_getareachar_npc()、clif_joinchatok()など修正
+
+・NPCのOnInitイベントをサーバー起動時に呼ぶように。
+・エクスポートするときのNPC名と表示上のNPC名を別々に設定可能に。
+ ・同じNPC名のイベントは重複できないため、エクスポート用NPC名を使い、
+ 同じNPC名でも別のNPCとして識別できるようにしなければなりません。
+ (もちろん、イベント処理を行わない場合はその必要はありません。)
+ ・npc_*.txtのscript命令でNPCの名前を設定するとき、
+ 「表示名::エクスポート名」とすると、表示する名前と、イベント用に
+ エクスポートする名前を別々に指定できます。
+ 表示名が全く同じ別々のNPCでイベントを動作させるときに使用します。
+ ・ややこしいのでPVPのnpcスクリプトを見てイメージを掴んでください。
+
+ npc.c/npc.h
+ npc_parse_*()の修正
+ npc_event_do_oninit(),npc_event_do_oninit_sub()など追加
+ map.c/map.h
+ do_init()でnpc_event_do_oninit()を呼ぶように
+ struct npc_data修正
+
+・スクリプトgetmapusers、getareausersの致命的なバグ修正
+ ・該当マップが存在しない場合、マップサーバーが落ちるのを修正。
+ ・マップが存在しないと、-1を返すようにした。
+
+ script.c
+ buildin_getmapusers(),buildin_getareausers()修正
+
+・pvpのスクリプト修正
+ ・チャットルームを作るようにした
+
+ (conf/)
+ npc_pvproom.txt
+ 全てのNPCのエクスポート名(pvp??r)設定
+ OnInit:でwaitingroomを実行するように
+
+--------------
+//461 by Kuro
+・アコライト転職クエスト一部修正
+ conf/npc_job_aco.txt
+
+--------------
+//460 by sagitarius
+・item_dbの間違い修正
+ 4032,Ambernite_Card,アンバーナイトカード,6,20,,10,,2,,,,,32,,,,{},{},,修正
+
+--------------
+//459 by hoenny
+・アドレナリンラッシュ使用の時斧チェック(自分だけ)
+・Old_Blue_Boxの確率が高いという報告によって修正
+・Script.cは0455以前ことで引換(NPCをここに合わせて修正してください)
+・阿修羅覇鳳拳修正(公式を修正したんですが, 正確かはよく分からないですね.)
+・修道僧の蓄気,爆期の仮実装.(阿修羅覇鳳拳使用の時蓄気,爆期状態をチェックします. )
+・ペコペコに乗った後に、ADPDが間違ったこと修正(バグを直してあげたが, 直す前ことに変わるせいでまた修正)
+その外にも修正をしたようなのに覚えないですね.そして誤った部分があれば指摘してください.
+ conf/npc_event_ice.txt
+ checkweight修正
+ conf/npc_town_geffen.txt
+ checkweight修正
+ db/item_purplebox.txt
+ Old_Blue_Box修正
+ map/battle.c
+ battle_calc_weapon_attack()修正
+ map/pc.c
+ pc_spheal()修正
+ pc_calcstatus()修正
+ map/script.c
+ buildin_checkweight()修正
+ map/skill.h
+ SC_ EXPLOSIONSPIRITS追加
+ map/skill.c
+ SkillStatusChangeTable[]修正
+ skill_castend_nodamage_id()修正
+ skill_check_condition()修正
+
+--------------
+//458 by Kuro
+・アコライト転職クエスト追加
+ conf/npc_job_aco.txt
+ 会話文が分からなかったので適当にしてあります。また、NPCの外見の変え方が分からなかったので適当にしてあります。
+ 分かる方は修正しておいて下さい。
+
+--------------
+//0457 by Kalen
+
+・PVP関連のNPC追加
+ conf/npc_pvp.txt
+ conf/npc_pvproom.txt
+
+--------------
+//0456 by 死神
+
+・モンスターの最初攻撃時間が長すぎる問題修正。
+モンスターの最初攻撃時間を今まではmob_dbのaDelayを使っていましたがこれをaMotionに変更しました。今まではmob_dbのaMotionはゴミでしたが今度からはゴミではありません。aMotionさえ正しければモンスターの攻撃のモーションの前にダメージが出てくる問題もなくなるはずです。
+ mob.c
+ mob_changestate() 修正。
+・アイテムを入手できない時その理由に当たるメッセージが出るように変更。
+ pc.c
+ pc_additem() 修正。
+・job_db1.txtので問題になった.を,に修正。
+
+--------------
+//455 by Mr.NO NAME
+・NPCとのアイテム交換や買い物関係のScriptが出来上がった当初の
+ NPCデータ(npc_event_making.txt、npc_town_geffen.txt等)に乗っ取り、以下を修正。
+ conf/npc_event_ice.txt
+ npc_event_potion.txt
+ npc_town_geffen.txt(454以前の物に戻しました。)
+ map/script.c
+ buildin_checkweight()を修正。
+
+--------------
+//454 by Kuro
+・ゲッフェン鍛冶屋で買い物が出来るように修正
+ conf/npc_town_geffen.txt
+
+--------------
+//0451 by code
+・ 今更ですがαクライアントに対応(αクライアントのdata.grfをadata.grfとしてgrf-files.txtのadataのところに書いてください)
+ common/grfio.c
+ grfio_setadatafile()追加
+ /grfio.h
+ grfio_setadatafile()追加
+ conf/map_athena.conf
+ αクライアントのマップを読み込むように変更
+ /npc_warp_a.txt
+ αマップのワープポイントの設定(ちょっとずれてるかも)
+ /grf-files.txt
+ αクライアントのdata.grfをadata.grfとして読み込むように設定
+ adata: に記述
+※αクライアントは
+ ttp://www.castledragmire.com/ragnarok/
+ あたりから入手してください。
+
+--------------
+//0450 by hoenny
+・ 騎兵修練実装
+・ コムパルションディスカウント実装
+・ ディスカウント・オーバーチャージ修正(数が高い場合計算法が間違ったことを直しました.)
+・ 鉄拳修正(素手な時も適用されるように)
+・ 斧修練修正(片手斧な時も適用されるように)
+・ ボンゴンが攻撃するように修正
+ map/pc.c
+ pc_calcstatus()修正
+ pc_modifybuyvalue()修正
+ pc_modifysellvalue()修正
+ map/battle.c
+ battle_addmastery()修正
+ db/mob_db.txt
+ ボンゴン修正
+
+--------------
+//0449 by 死神
+
+・変になった所修正。
+ const.txt
+ bAtkとbDef 追加。
+ battle.c
+ battle_calc_weapon_attack()を元に戻しました。(0445の物)
+ map.h
+ map_session_dataを元に戻しました。(0445の物)
+ pc.c
+ pc_calcstatus() 修正。
+ pc_bonus() 修正。
+ item_db.txtを元に戻しました。(0446の物)
+
+--------------
+//0448 by hoenny
+・増速修正(pc_walk()から pc_calcstatus()に移動)
+・最大所持量修正
+ map/pc.c
+ pc_calcstatus()修正。
+
+--------------
+//0447 by ゆう
+・二刀流・矢の属性を正しく適応
+・ATKの上がるカードの効果を武器サイズ修正なしの底上げに変更
+・ATK・DEFの上がるカードの効果の適応の仕方を変更
+
+map.h
+ map_session_dataにcatk(カードATK)を追加
+
+pc.c
+ pc_calcstatus()
+ アサシンの二刀流の攻撃速度を修正した
+ スクリプトによる属性を左右正しく適応するようにした
+ 矢の属性を正しく適応するようにした(弓の属性優先)
+ ただし、矢がすべての攻撃に適応されます
+ カードATKの処理を追加した
+
+battle.c
+ battle_calc_weapon_attack()
+ カートATKを底上げダメージとして計算するようにした
+
+item_db.txt
+ カードの bonus bAtk、bDef を削除
+ かわりに、装備と同様にATKとDEFを設定
+ (変更前をitem_db2.txtとしているので、不具合があれば戻してください)
+
+
+--------------
+//0446 by hoenny
+・ミストレスカード実装。
+・スキル使用の時ジェムストーン消費。
+・スキル使用の時装備チェック。(ハンマーフォールだけ修正しようとしたが...)
+・ハンマーフォールの範囲を 半径5セル(全25セル)ロ修正
+ map/skill.c
+ skill_check_condition()修正。
+ skill_castend_pos2()修正。
+
+・ミストレスカード修正。
+ db/item_db.txt
+
+--------------
+//0445 by Aya
+
+・基本ASPDと計算処理を修正。
+ db/job_db1.txt
+ map/pc.c
+・SP係数と計算処理を修正。
+ db/job_db1.txt
+ map/pc.c
+・スキル名をenumで宣言し、それを使うように変更。
+ map/skill.h
+ map/battle.c
+ map/pc.c
+ map/skill.c
+・リカバリーのスキルIDがスローポイズンになっていたのを修正。
+ map/skill.c
+・集中力向上にカード効果が適用されていた問題の修正。
+ map/pc.c
+・リムーブトラップ、スプリングトラップ、ポイズンリアクトのターゲットを修正。
+ db/skill_db.txt
+・GMアカウントをjROのclientinfo.xmlから追加。
+ conf/GM_account.txt
+・warning修正。
+ map/party.c
+・キャラセレ認証時にlogin_id2はチェックしないように変更。
+ login/login.c
+・object_def.bat以外全ファイルの改行コードをLFに変更。
+・*.cnfファイルを*.confファイルに名前変更。
+
+--------------
+//0444 by 死神
+
+・GMコマンドや@コマンドにコマンド別に使用レベルを設定できるように変更と@コマンド少し修正。(@whereと@day、@nightの修正と他のキャラに使うコマンドの場合GMレベルが自分以上の場合使えないように修正。)
+ atcommand.h 修正。
+ atcommand.c 修正。
+ clif.c 修正。
+ map/makefile 修正。
+ map.c
+ do_init() 修正。
+ conf/atcommand_athena.cnf 追加。
+・細かい修正。
+ pc.c
+ pc_setghosttimer()、pc_skill() 修正。
+ script.c
+ buildin_skill() 修正。
+・conf_ref.txt 修正。
+・item_db.txt
+ 彼女の想い修正。
+
+--------------
+//0442 by 胡蝶蘭
+
+・増速ポーション実装
+ ・Lvや職業判定は行いません
+
+ (db/)
+ const.txt
+ SC_SpeedPot0,SC_SpeedPot1,SC_SpeedPot2追加
+ item_db.txt
+ 増速ポーションのスクリプト追加
+ (map/)
+ skill.c
+ skill_status_change_start()修正
+ pc.c
+ pc_calcstatus()修正
+
+・PvPシステムの仮実装
+ ・pvpマップでは自動的に、PCのpvpフラグon、順位通知などを行います。
+ ・マップにpvpフラグをつけるサンプルをnpc_pvp.txtとして添付しています。
+ ・pvpの詳しいルールがよくわからなかったので、次のようにしています。
+ ・最初の持ち点は5点、倒すと1点、倒されると-5点。
+ ・0点以下のPCはリザレクションが掛からない
+ ・GMはpvpマップにいても足元にサークルが出現しないようです。
+ (クライアントの仕様?)
+ ・pvpマップで@pvpoff/@pvpすると休憩したり、休憩をやめたりできますが、
+ 使用するべきではありません。
+
+ (conf/)
+ npc_pvp.txt
+ pvpフラグを入れるサンプル。
+ nosaveフラグや受け付けnpcなどを追加するとよいと思われる。
+ (map/)
+ clif.c
+ clif_parse_LoadEndAck()修正
+ npc.c
+ npc_parse_mapflag()修正
+ skill.c
+ skill_castend_nodamage_id()修正
+ pc.c
+ pc_damage()引数修正
+ atcommand.c
+ pc_damage()引数修正に伴う修正
+ battle.c
+
+・その他修正
+ ・@pvpoff/@pvpで順位やサークルの表示をやめた
+ ・@jumptoでスペースの入ったキャラクターも指定できるように
+ ・@kamibコマンド復活(青文字天の声)
+ ・非PVPのときに、対象が敵のスキル使用時、敵味方判定を行うように
+
+ skill.c
+ skill_castend_id()で敵味方判定
+ atcommand.c
+ 各コマンド修正
+
+--------------
+//0440 by 中の人
+
+・本家を再現する方向なら意味はないかもしれませんが
+ pc.c「スクリプトによるスキル所得」を若干変更して
+ カードによるスキル一時習得の際でも1レベル以上を設定できるように致しました。
+
+ 単純に符号を変えてごまかしただけですので
+ 必要にあわせて修正をして下さい。
+
+--------------
+//0439 by hoenny
+・阿修羅覇鳳拳の修正。
+ db/skill_db.txt
+・モンスター情報の修正。
+ map/clif.c
+・見切りの実装。
+ map/pc.c
+
+--------------
+//0438 by Aの人
+・古木の枝が使える場所をNPCスクリプトから制御可能
+ mapflagにnobranchとすればそのマップは古木の枝使用不可になります。
+ map.h
+ enumにMF_NOBRANCH 追加。
+ npc.c
+ npc_parse_mapflag() 修正。
+ pc.c
+ pc_useitem() 修正。
+ソース汚くしてしまったかも・・・.
+勉強不足です
+
+--------------
+//0437 by 波浪
+・item_db.txtの英名を大幅修正。(s付きとそうでない武器の英名がいつの間にやら
+ 同じになっていたのでそれを直すついでに他の部分も修正しました。
+ まったく違う名前になってるものもありますが、こっちの方が正しいと思います。
+・item_purplebox.txtを本家仕様っぽく作成(大体こんな感じかと
+・アルベルタとイズルードNPCを修正
+
+--------------
+//0436 by hoenny
+・morocc 宝石商人の修正
+ conf/npc_shop.txt
+・ハンマーフォールの実装(Alchemist氏ソースを参照ありがとう!)
+ map/skill.c
+以前に文字化けは低のせい!
+次から気を付けます.
+
+--------------
+//0434 by Avethes
+
+・タートルアイランドへ行くNPC修正
+・ユノーNPC修正
+(前回のバグはすみませんでした)
+
+--------------
+//0433 by 死神
+
+・製造バグ修正。
+ 何故かはわからないがskill.cのskill_readdb()が変になっていたので修正。(自分がやった修正ではありませんが...)
+ skill.c
+ skill_readdb() 修正。
+
+--------------
+//0432 by 死神
+
+・0429で一部のアイテムのスキルが出ない問題修正。
+ clif.c
+ clif_parse_UseSkillToId()、clif_parse_UseSkillToPos() 修正。
+・skill.c
+ skill_use_id() 修正。(大した修正ではないです。)
+・item_db.txtの文字化け修正。言語設定が日本語ではない場合保存する時には気をつけましょう。
+・攻撃されたモンスターの反撃が早すぎる問題修正。始めての攻撃がモンスターの攻撃ディレイに関係なく100ms後になっていたので攻撃ディレイに合わせるように変更。(ただ少し反撃が遅いと思われたりもしますが...)
+ mob.c
+ mob_changestate() 修正。
+・鯖に接続する最大人数を決めるように変更。
+ char.c 修正。
+ conf_ref.txt 修正。
+ char_athena.cnf 修正。
+
+--------------
+//0430 by Avethes
+
+・NPC関係。ほとんどテスト。
+本家会話情報が揃えば修正。
+
+--------------
+//0429 by 死神
+
+・ギルドのレベルアップをキャラのレベルアップのように変更。
+ int_guild.c
+ guild_calcinfo() 修正。
+ guild_next_exp() 追加。
+ exp_guild.txt 修正。(レベルが上がらないようにしたいレベルのexpに0を入れればそれ以上にレベルが上がらなくなります。)
+・スクリプトresetstatus、resetskill 追加。
+ pc.c
+ pc_resetskill() 修正。
+ script.c
+ buildin_resetstatus()、buildin_resetskill() 追加。
+・0425の続きで少し修正。
+ clif.c
+ clif_parse_ を少し修正。
+・ショートカットに覚えているスキルレベル以上のスキルが登録されていても覚えているスキルレベルまでのスキルを使うように変更。
+ clif.c
+ clif_parse_UseSkillToId()、clif_parse_UseSkillToPos() 修正。
+・メモの最大数を10個に変更。(あくまでも拡張の為の物です。まだ機能はしません。)
+ mmo.h
+ struct mmo_charstatusのmemo_pointを3から10に変更。
+ char.c
+ mmo_char_tostr() 修正。
+・mob,c
+ mob_once_spawn()、mob_summonslave() 修正。(別に意味がある修正じゃありませんが...)
+・@monster コマンドで座標を指定しない時モンスターが一か所に集中して出るのをキャラの10*10マス以内にランダムで現れるように変更。
+ atcomand.c 修正。
+
+--------------
+//0428 by Avethes
+
+・conf/npc_smilegirl.txt
+ スマイルマスクガールスクリプト。
+ 0427のおかしい部分とか修正。
+ 提供された各都市の座標に配置。(NONAMEさん提供ありがとう!)
+
+--------------
+//0426 by 胡蝶蘭
+
+・アイテムの名前をdata.grfから読み込むようにした
+ itemdb.cのITEMDB_OVERRIDE_NAMEを定義しなければ読み込みません。
+ ITEMDB_OVERRIDE_NAME_VERBOSEはitemdb.txtのデバグ用にどうぞ。
+ 普通は変える必要はないと思うのでbattle_configには入れていません。
+
+ itemdb.c
+ itemdb_read_itemnametable()追加
+ do_init_itemdb()修正
+
+・データベース読み込み部の不安定性の修正(結構致命的だったみたいです)
+ なくても問題ないDB(item_value_db.txtなど)のファイルがない場合に
+ 鯖が落ちたりする現象が発生していた場合はこれで直っているかもしれません.
+
+ skill.c
+ skill_readdb()でNULLポインタチェックを追加
+ itemdb.c
+ item_readdb()を複数に分けた。
+ ランダムアイテムデータベースの読み込み部を1つに纏めた。
+ do_init_itemdb()修正
+
+・細かいバグ修正
+ ・ワープポータルの開くまでの秒数調整
+
+ skill.c
+ skill_unitsetting()修正
+
+・その他修正(by 某M氏)
+ db/job_db1.txt
+ ちょこっと修正
+ db/job_db2.txt
+ 2-2次職の足りないJobボーナスを追加(参考:R.O.M 776)
+ conf/npc_town_kafra.txt
+ オークD前と炭鉱前にカプラ配置(動作未確認)
+ conf/npc_shop3.txt
+ ジューノ販売NPC(拾い物)
+ conf/npc_town_yuno.txt
+ ジューノNPC(拾い物を改良。動作未確認)
+
+--------------
+//0425 by 死神
+
+・0419で書き忘れた物ですがスキルラーニングポーションがSP回復アイテムにも効果があるように変更。
+・今度はバグ修正がメインです。鯖落ちがなりそうな所の修正とテレポートの時死んだまま移動できる問題と0419でアクティブモンスターの先攻問題修正、死んでいるのに他の人には死んだように見えない問題の修正です。少しテストはしましたが本当に治ったかどうかは不明です。報告をお願いします。
+ pc.c
+ pc_attack_timer()、pc_damage()、pc_walk() 修正。
+ map.c
+ map_quit() 修正。
+ mob_db.txt
+ ビッグフットのmodeを修正(アクティブになっていた為)
+ clif.c
+ clif_parse_WalkToXY()、clif_pcoutsight()、clif_pcinsight()、
+ clif_getareachar_pc()、clif_getareachar_mob()、clif_getareachar_pet() 修正。
+ mob.c
+ mob_ai_sub_hard_activesearch()、mob_ai_sub_hard_mastersearch()、
+ mob_walk() 修正。
+ pet.c
+ pet_walk() 修正。
+
+--------------
+//0424 by hoenny
+
+・クリップボーナス SP 10追加
+ db/item_db.txt
+・warp_test_yuno.txtを npc_warp30.txtに含んで, ちょっと修正
+ conf/npc_warp30.txt
+・他のサーバーが落ちても復旧されるように修正
+ /startクリップ
+
+--------------
+//0420 by 紅葉
+
+・EP 3.0でのカード効果変更に解る範囲で対応。
+ ほぼ全ての変更点について、出来る限り修正してあります。
+ ATK修正が正しく適用されているようなので追加してあります。(アンドレCなど)
+
+--------------
+//0419 by 死神
+
+・0414で書き忘れた物ですが MOBのmodeで0x20(32)を復活させました。ボスじゃなくてもmodeに0x20が入っている場合普通のMOBでも死んだふりを破れます。
+(今の所機能はそれだけです。本鯖はAI強化みたいですが...) ただゴーストはボスでも破ることはできません。
+それと取り巻きのAIで取り巻きがターゲットした時主がターゲットしてないと主が取り巻きのターゲットをターゲットする部分をコマントアウトしました。(これが本鯖にあっていると思いましたので...)
+・古い青い箱、古い紫色の箱、古いカード帖で出るアイテムをファイルで設定できるように変更。
+ script.c
+ buildin_getitem() 修正。
+ item_db.txt
+ 古い青い箱、古い紫色の箱、古いカード帖修正。
+ item_bluebox.txt、item_purplebox.txt、item_cardalbum.txt 追加。(使用例程度の物です。どのアイテムが出るようにするかは自分で設定して使ってください。ただクライアントを落とすアイテムは出ないように設定してください。)
+ itemdb.h
+ struct random_item_data 追加。
+ itemdb.c
+ itemdb_searchrandomid()、itemdb_readdb() 修正。
+・mob.c
+ mob_target()、mob_ai_sub_hard() 修正。(問題がありそうな部分だけ修正。)
+・pc.c
+ pc_itemheal()、pc_walktoxy_sub() 修正。
+・ペットの出現をMOBと同じように変更。
+ clif.c
+ clif_spawnpet() 修正。
+ pet.c
+ pet_change_name() 修正。
+・0418を少し修正。(if文の条件を少し修正しただけです。)
+
+--------------
+
+//0418 by hoenny
+・ /mm(/mapmove) /nb /b /bb /resetskill /resetstate GM 命令語使用の制限
+clif_parse_MapMove ,clif_parse_ResetChar ,clif_parse_GMmessage 修正
+ map/clif.c
+
+--------------
+//0417 by れあ
+
+・0412でitem_db.txtがおかしくなっていたのを修正
+
+--------------
+//0416 by 紅葉
+
+・ジュノー周辺のワープ定義と敵の配置。
+ ワープ定義はnpc_warp30.txtとし、追加する形にしてあります。
+ 敵の配置についてもnpc_monster.txtとは統合せず、npc_monster30.txtとしてあります。
+ 問題が無いようであれば統合して下さい。
+・上記定義ファイル追加に従いmap_athena.cnfを変更。
+・@goコマンドへジュノー追加。
+ 要望があったようなので追加しました。
+
+--------------
+//0415 by 中の人
+
+・今は亡き旧ROエミュ鯖開発スレッド Lv02での死神氏の説明に従って
+ モンスター定義データを若干変更させて頂きました。
+ ・過去のnpc_monster.txtから通常マップ上(ルティエ等除く)にいるサンタポリン、アンソニを抽出し
+ 新たに作った「npc_x-masmonster.txt」に移転
+ ・上記の修正にあわせてmap_athena.cnfを修正。
+ map_athenaにコメントアウト状態で「npc: conf/npc_x-masmonster.txt」を追加しました。
+ 必要にあわせてコメントアウトをして下さい。
+
+--------------
+//0414 by 死神
+
+・strcasecmpをstrcmpiに変更。
+・dbや設定ファイルを読む時// をコマントアウトとして認識するように修正。
+・ペットと離れすぎるとペットが早く動くように変更。(キャラの2倍の速度で動きます。)
+・ルートモンスターがアイテムをターゲットした時攻撃を受けても攻撃してこない問題修正。
+・同族モンスターのAIを変更。今まではtraget_idを使うせいでモンスターが攻撃した相手を攻撃する仕組みだったが今度はattacked_idを使う為攻撃してきた相手を攻撃するように変更。
+ただ今の仕様だと同族モンスターを攻撃して逃げる場合攻撃を受けた時その場になかったモンスターはついて来なくなっています。本鯖の仕様にあってるかどうかは不明ですので情報提供をお願いします。(attacked_idはいつもリセットされる為です。対策がいないわけでもないですが本鯖の仕様を知らないので...)
+・メモリーの使用量を減らす為struct mob_dataとstruct npc_dataを変更。(0412で
+map-serverのメモリーの使用量が164???KBytesだったが0414では152???KBytesになりました。ほんの少し減っただけですが増えるよりはましだと思いますので...)
+・ゴーストタイム実装。
+ マップ移動やテレポート、復活した時に敵に狙われない時間を重力ではゴーストタイム呼んでいます。そのゴーストタイムの実装です。
+battle_athena.cnfで時間を設定できます。時間を0にするとゴーストタイムは作動しません。ただこのゴーストタイムは攻撃行動、スキル使用、アイテム使用をするとなくなります。
+ char/int_guild.c
+ char/int_party.c
+ conf/battle_athena.cnf
+ db/mob_db.txt
+ doc/conf_ref.txt
+ login/login.c
+ map/atcommand.c
+ map/battle.c
+ map/battle.h
+ map/clif.c
+ map/itemdb.c
+ map/map.c
+ map/map.h
+ map/mob.c
+ map/npc.c
+ map/pc.c
+ map/pc.h
+ map/pet.c
+ map/skill.c を修正。(db/mob_db.txtは//を入れただけですが...)
+ 修正した所を全て覚えてませんのでファイルだけ知らせます。
+
+--------------
+//0412 by いど
+
+・モンスター定義データ(日本語)の再整理
+ 旧掲示板で指摘のあった事項について大体の範囲で修正
+ snapshot387のバージョンをベースに修正しました。
+ conf/npc_monster.txt
+
+・アイテム名の定義を大幅修正
+ (root)
+ item.list
+ (db/)
+ item_db.txt
+ item_value_db.txt
+
+・マップデータの定義でコメントアウトしていたジュノー関連マップのコメントアウトを解除
+ conf/map_athena.cnf
+
+--------------
+//0411 by 死神
+
+・鯖snapshotです。それとlogin_port、char_port、map_portの設定がなくても
+デフォルトで6900、6121、5121を使うように変更。
+・login.c、char.c、chrif.c、clif.c 少し修正。
+・conf_ref.txt 修正。
+・login_portを6900から他の物に変えた場合はclientinfo.xmlを変える必要があります。
+
+--------------
+//0410 by 死神
+
+GM用右クリックメニュー「(name)使用者強制終了」実装。(テストはしてません。@コマンドはテスト済みですが...)
+0407のEXPに関する修正に問題があるらしいので修正しました。今度はテスト済みです。
+GMのアカウントIDを設定できるように変更とGMをレベル別に分けるように変更。
+(GMのレベルによる@コマンド等に制限をかけるつもりですが今制限がかけている物は@kick、@kickallのみになっています。)
+・pc.c
+ pc_readdb()、pc_gainexp()、pc_nextbaseexp()、pc_nextjobexp()、
+ pc_checkbaselevelup()、pc_checkjoblevelup() 修正。
+ pc_isGM()、pc_read_gm_account() 追加。
+・pc.h
+ pc_isGM() 修正。
+ pc_read_gm_account() 追加。
+・exp.txt
+ レベルが上がらない数値を999999999から0以下に変更。
+ レベルを上げる為に必要なEXPを999999999以上にすることも可能。
+・clif.c
+ clif_GM_kickack()、clif_GM_kick()、clif_parse_GMKick() 追加。
+・clif.h
+ clif_GM_kickack()、clif_GM_kick() 追加。
+・atcomand.c
+ strncmpiをstrcmpiに変更。
+ @kick、@kickall コマンド追加。
+ @kick <キャラ名>
+ 自分以外のキャラの接続を強制終了させる。(自分よりGMレベルが
+ 低いキャラにしか使えない。GMではないキャラのGMレベルは0)
+ @kickall
+ 鯖に接続している全てのキャラの接続を強制終了させる。(自分と
+ GMを含めて) 鯖ダウン用のコマンドです。GMレベルが99じゃないと
+ 使えない。
+・conf/GM_account.txt 追加。
+ GMとして認識するアカウントIDを設定するファイルです。
+・mmo.h
+ DEFAULT_WALK_SPEEDを140から150に変更。(これが本鯖にあってる数値
+ みたいですので...)
+ struct gm_account 追加。
+・client_packet.txt
+ パケット0x00cd 追加。
+・login_portをcnfで読むように変更。(ただ6900からポートを変えるとクライアントが認識できない模様なので無駄なことだったりもしますが...)
+ char.c、login.c、char_athena.cnf、login_athena.cnf 修正。
+・普通のアカウント作成ではGMになれないようにlogin.cを変更。
+・login/makefile、map/makefile 修正。
+
+--------------
+//0408 by 胡蝶蘭
+
+・405の新しい@コマンドを以前のatcommand.cに取り込みました。
+ ・@kamiを修正
+ ・@kill,@recall,@charjob,@revive,@charstats,@charoption,@charsave,
+ @night,@day,@doom,@doommap,@raise,@raisemap,@charbaselvl,@charjlvl
+ を追加&メッセージを日本語に変更&少し修正
+
+ atcommand.c
+ 追加と修正
+
+・一部のスキルの効果実装
+ ・不死身のジークフリード、イドゥンの林檎、幸運のキス、
+ フレイムランチャー、フロストウェポン、ライトニングローダー、
+ サイズミックウェポン
+
+ map.h
+ struct skill_unitにrangeを追加。
+ skill.c
+ 色々修正
+ skill.h
+ enumの修正など
+--------------
+//0407 by 死神
+
+・ペットのバグ修正。(ただ自分で再現できなかったので問題になりそうな所だけ修正しました。)
+・ペットの移動速度をpet_dbに追加。
+ pet.h
+ struct pet_dbにspeed追加。
+ pet.c
+ pet_catch_process2()、read_petdb() 修正。
+ pet_db.txt
+ 移動速度追加。
+ (コマントアウトしているのはジルタスとアリスです。捕獲用の
+ アイテムが存在することとパフォーマンスをすることから考えて
+ 追加される予定の物と考えられます。ただその捕獲用のアイテムが
+ あるとクライアントを落ちますので注意してください。追加しても
+ 台詞はポリンの物ですので... 捕獲用のアイテム以外は適当に入れた
+ 物です。)
+・pc.c、clif.c
+ pc_equipitem() 修正。
+ clif_parse_EquipItem() 修正。
+ pc_equipitem()の未鑑定アイテムのチェックをclif_parse_EquipItem()に
+ 移動しました。(ペットの装備もありますので...)
+・レベルを99以上にあげるように変更と職業別にベースレベルの限界レベルを設定できるように修正。
+ map.h
+ MAX_LEVEL追加。
+ pc.c
+ pc_nextbaseexp(), pc_nextjobexp() 修正。
+ pc_readdb() 修正。
+・exp.txt 修正。職業レベルと同じようにベースレベルもEXPテーブルを3つ作りました。レベルアップを止めたいレベルのexpを999999999にすればそれ以上レベルが上がりません。つまりnovice、1次職業と2次職業のベースレベルの限界を違うように設定できます。そしてベースレベル99以上に上がるようにすることもできます。(exp.txtの修正が必要ですが本鯖と違うように設定したい場合に修正して使ってください。)
+・属性による回復をbattle_athena.cnfで設定できるように変更。
+ attr_fix.txt 修正。
+ battle.h
+ struct Battle_Configにattr_recover 追加。
+ battle.c
+ battle_config_read() 修正。
+ battle_athena.cnf 修正。
+・conf_ref.txt 修正。
+・client_packet.txt 修正。ペットパケット追加と少し修正。
+
+--------------
+//0402 by 胡蝶蘭
+
+・400のバグを一部修正
+ ・掛かってないスキル効果によるステータス計算が行われてしまうバグ修正
+ ・効果修正:あくまで効果の計算の修正で、使えないスキルは使えません。
+ スピアクィッケン、プロヴィデンス、戦太鼓の響き、
+ 夕陽のアサシンクロス、口笛、不死身のジークフリード、
+ イドゥンの林檎、サービスフォーユー、幸運のキス
+ ・効果追加:あくまで効果の計算の追加で、使えないスキルは使えません。
+ ハミング、私を忘れないで…、ニーベルングの指輪(武器レベル無視)、
+ エターナルカオス、ドラゴノロジー
+ ・効果付加系はちょっとでも怪しいスキルは全て使用できないように修正
+ ・攻撃系スキルはほとんど見てないのでたぶんバグ多いです。
+ ・全て未テストです。怪しすぎる部分を修正しただけです。
+
+ map.h
+ MAX_STATUSCHANGEを128に修正
+ pc.c
+ pc_calcstatus()修正
+ skill.c/skill.h
+ enumを修正
+ skill_status_change_start()修正
+ battle.c
+ battle_calc_weapon_attack()など修正
+
+--------------
+//0400 by AppleGirl
+
+Can Someone Help Me.
+2-2 Skills added.
+All The Mastery Skills.
+SpearQuicken,Providence
+New Bard Skill Assassin Cross Of Sunset
+Providence
+Frost Joke
+Apple of Idun
+Service For You
+Meteor Strike (Different Style)
+Assassin Cross Of Sunset (not tested)
+All Masteries Done
+Providence
+Musical Strike
+Throw Arrow
+Frost Weapon << (Problems with elements)?
+Flame Launcher << (Problems with elements)?
+Seismic Weapon << (Problems with elements)?
+Lightning Loader << (Problems with elements)?
+Spirit Recovery
+Potion Pitcher (Tato)
+Axe Mastery (Tato)
+Spear Quicken
+Not Totally Working:
+Combo Finish
+Quadruple strike
+Triple Attack
+(skills in skill.c) (need to be finished.)
+CP_ARMOR
+CP_HELM
+CP_SHIELD
+CP_WEAPON
+STRIP_HELM
+STRIP_WEAPON
+STRIP_SHIELD
+STRIP_ARMOR
+
+* 適当な和訳 *
+2-2次職スキルを追加しました
+全ての修練スキル、スピアクイッケン、プロヴィデンス、
+夕陽のアサシンクロス(未テスト)、寒いジョーク、イドゥンの林檎、
+サービスフォーユー、メテオストライク(少し違う)、
+ミュージカルストライク、矢撃ち、フロストウェポン(属性が問題あり?)
+フレームランチャー(〃)、サイズミックウェポン(〃)、ライトニングローダー(〃)
+息吹、ポーションピッチャー
+完全には働かないスキル:
+猛龍拳、漣環全身掌、三段掌
+(skills in skill.c) (完了される必要がある)
+ケミカルアーマーチャージ、ケミカルヘルムチャージ、
+ケミカルシールドチャージ、ケミカルウェポンチャージ、
+ストリップヘルム、ストリップウェポン
+ストリップシールド、ストリップアーマー
+
+*注意 !! CAUTION !! by 胡蝶蘭*
+この400にはバグが大量に含まれています。注意してください。
+there are many many BUGS in this update(400) !! Be careful !!
+
+--------------
+//0399 by 胡蝶蘭
+
+・MOBスキル使用条件や行動を修正
+ ・無行動MOBが待機時のスキルを使用できない問題を修正
+ ・条件スキル反応(skillused)がどのスキルにも反応していたバグ修正
+ ・非移動MOBが追撃してくる問題を修正
+
+ mob.c
+ mob_ai_sub_hard()修正
+ mobskill_event()修正
+ mobskill_use()修正
+ skill.c
+ skill_attack()修正
+
+・MOBスキル一部実装
+ ・自決(エフェクト無し?)、自爆、タバコを吸う、範囲攻撃
+ HP吸収2つ(通常/魔法)(回復エフェクト無し?)実装
+
+ (db)
+ skill_db.txt
+ スモーキングなどを修正
+ (map/)
+ skill.c
+ skill_castend_damage_id(),skill_castend_nodamage_id()修正
+ battle.c
+ battle_calc_misc_damage()修正
+
+・未鑑定アイテムが装備できなくなりました
+・未鑑定アイテムにカードがさせなくなりました
+
+ pc.c
+ pc_equipitem(),pc_insert_card()修正
+ clif.c
+ clif_use_card()修正
+
+・battle_athena.cnfにMOBの配置割合を定義できるようになりました
+ ・配置数が1のMOBについては適用されません
+ ・計算後の配置数が1未満の場合1に修正されます。
+
+ (conf/)
+ battle_athena.cnf
+ mob_count_rate追加
+ (doc/)
+ conf_ref.txt
+ 修正
+ (map/)
+ battle.c/battle.h
+ struct BattleConfig に mob_count_rate メンバ追加
+ npc.c
+ npc_parse_mob()の修正
+
+・ボーリングバッシュが相手が1匹でもとりあえず当たるようになった。
+
+ skill.c
+ skill_castend_damage_id()修正
+
+・学生帽作成イベントの修正
+
+ (conf/)
+ npc_event_making.txt
+ アロエベラ(606)をアロエ(704)に。
+
+・パケット情報修正
+
+ (doc/)
+ client_packet.txt
+ 0199パケット修正
+
+--------------
+//0397 by いど
+
+・モンスター定義データ(日本語)の整理
+ ・npc_monster25.txtをnpc_monster.txtにリネームし、内容を整理(現在mob数:13450)
+ ・その軽量版としてnpc_monster_lite.txtを作成(現在mob数:11959)
+ ・上記の修正にあわせてmap_athena.cnfを修正
+
+--------------
+//0395 by 胡蝶蘭
+
+・取り巻きMOBの行動修正
+ ・アンクルなどで移動できない場合主に近づかないように修正
+ ・ロックしていると主に近寄る処理をしないように修正
+ ・主がテレポートすると追いかけるように修正(付近10x10マス程度)
+ ・主のそばにいるときはランダム歩行をしないように修正
+
+ mob.c
+ mob_ai_sub_hard_mastersearch()修正
+ mob_can_move()追加
+ mob_ai_sub_hard()修正
+
+・MOBの行動修正
+ ・スキル使用ディレイ処理がおかしかったのを修正
+ ・詠唱のないスキルはtimerを使わないように修正(死亡時処理対策)
+
+ mob.c
+ mobskill_use(),mobskill_use_id()修正
+
+・MOBエモーションの実装
+ ・エモーションの種類がわからないものは全て「!」になります。
+ 抜けているデータを埋めてくれるとうれしいです。
+
+ (db/)
+ mob_skill_db.txt
+ いくつかのMOBのエモーションの項目の値1に種類を入れた。
+
+ (map/)
+ skill.c
+ skill_castend_nodamage_id()修正
+ clif.c/clif.h
+ clif_emotion()追加
+
+・パケット解析.txtをclient_packet.txtに改名&修正
+
+ (doc/)
+ client_packet.txt
+ エモーションの説明追加
+
+・どうやら取り巻きMOBの種類は古いデータだったっぽいです。
+ しかもMOB召喚では手下召喚と違うMOBを召喚するみたいですね。
+ 詳しい人はmob_skill_db.txtを直してくれると。
+
+--------------
+//0393 by いど
+
+・char鯖でのlogin鯖のポート設定を6900に固定し、変更できないようにした
+ (login側でポート6900固定になっていたのでchar側もそれに合わせました。)
+ char/char.c
+ conf/char_athena.cnf
+ doc/conf_ref.txt
+
+--------------
+//0392 by 胡蝶蘭
+
+・MOBの行動修正
+ ・何故かlast_thinktickが初期化されていない問題修正
+ ・上に関連してPCが近くにいても手抜き処理が行われる問題修正
+ (どうやら初期からのバグだった模様? このバグと、
+ 新しい手抜き処理の仕様がタッグを組んで残像を作っていた模様)
+ ・取り巻きMOB用のAI処理追加(まだ怪しいです)
+ ・MOBのスキルディレイをスキル項目ごとに持つように変更
+ ・スキルディレイが大きな項目ではオーバーフローしていた問題を修正
+
+ map.h
+ struct mob_dataの skilldelayを配列にしてunsigned intに変更
+ mob.h
+ struct mob_skillのcasttime,delayをintに変更
+ mob.c
+ mob_ai_sub_hard_mastersearch()追加
+ mob_changestate(),mob_delete(),mob_catch_delete(),mob_damage(),
+ mobskill_use(),mobskill_use_id(),mobskill_use_pos(),
+ mobskill_castend_id(),mobskill_castend_pos(),
+ mob_ai_sub_hard(),mob_ai_sub_lazy()など修正
+
+・MOBスキルの手下召喚とモンスター召喚実装
+ ・mob_skill_db.txtの書式変更(最後に値を1つ追加、取り巻きMOBのID)
+ ・取り巻きMOBがわからなかったものはコメント化しています
+ わかる人は入力よろしくお願いします。
+ ・現在は取り巻きは一度倒したら沸きなおしません。
+ ・ボスがテレポートしても取り巻きは追いかけません。
+ ・本鯖でどうなってるのか知らないので、間違ってる場合は教えてください。
+
+ (db/)
+ mob_skill_db.txt
+ 手下召喚などのデータ修正
+
+ (map/)
+ skill.c
+ skill_castend_nodamage_id()修正
+
+--------------
+//0391 by 死神
+
+・ペットの移動中にパフォーマンスをするとペットが停止するように変更。
+ (ペットの位置がずれるため修正しました。)
+ pet.c
+ pet_performance() 修正。
+・死んだモンスターはどんな行動もとれないように変更。(これで無敵
+ モンスターがいなくなるといいですが...)
+ mob.c
+ mob_changestate(),mob_delete(),mob_catch_delete(),mob_damage(),
+ mob_ai_sub_hard(),mob_ai_sub_lazy() 修正。
+・PC、NPC、床アイテムが使うIDの範囲を調整。
+ 床アイテムは0から500000まででPCは500000から100000000、NPC
+ (モンスターを含めて)は110000000から約21億までになります。
+ (-を含めるともっと範囲が広くなりますがさすがにそこまでは必要ないと
+ 思いますので...)
+ map.h
+ MAX_FLOORITEM 追加(これを変えると床アイテムの最大数を変える
+ ことができます。今は100000になっています。ただこれは必ず
+ 500000以下にしてください。そうしないと正しく動くかどうか
+ 保証できません。)
+ map.c
+ map.hに合わせて少し修正。
+ npc.h
+ START_NPC_NUM 追加。
+ npc.c
+ npc.hに合わせて少し修正。
+ login.h
+ START_ACCOUNT_NUMとEND_ACCOUNT_NUM 追加。
+ login.c
+ login.hに合わせて修正。END_ACCOUNT_NUM以上にaccountを
+ 作れないように変更。
+・カートレボリューションに武器研究を2回適用するように変更。
+ (結局は元に戻すことになりました...^^;)
+ battle.c
+ Damage battle_calc_weapon_attack() 修正。
+・mobのスキル使用をbattle_athena.cnfで決めるように変更。
+ mob.c
+ mobskill_use() 修正。
+ battle.h
+ battle.c
+ struct Battle_Configにmob_skill_use追加。
+ battle_athena.cnf
+ mob_skill_use追加。(設定しないとnoです。)
+・battle_athena.cnf
+ mobを二重で読めないようにnpc: conf/npc_monster.txtを削除。
+ (最新はnpc_monster25.txtなので...)
+
+--------------
+//390 by 胡蝶蘭
+
+・バージョン情報所得部分を少し変更
+ ・MODバージョンを定義できるようになりました。詳細はversion.hを。
+ 気が向いたときか、大きな更新があるときなどに変更してください。
+ ・バージョンcheck時のset eofログが出ないようにパケット7532追加。
+
+ (common/)
+ version.h
+ MODバージョンを定義できるように。
+ (tool/)
+ checkversion
+ MODバージョンを表示するように。
+ (login/char/map)
+ login.c/char.c/clif.c
+ MODバージョンの処理追加、
+ パケット7532(切断)処理追加。
+
+・その他色々修正
+ ・こまごました修正ばかりですが、あまり覚えていません。
+ ・MOBスキル条件でslavelt,attackpcgt処理実装(未テスト)。
+ ・MOBの手下召喚のための機構追加(まだ召喚できません)。
+ ・範囲スキル効果範囲に死亡PCがいると鯖が落ちるバグ修正。
+ ・MOB残像が出なく…なってたらいいな。
+
+ (map/)
+ mob.c/mob.h/map.h/battle.c
+ 色々追加
+
+ (db/)
+ mob_skill_db.txt
+ ルート時処理と、属性変更スキルのコメントを外した。
+ (属性変更は本鯖で動いてないらしいものもコメントを外してます。
+ 問題がある場合は再びコメント化してください)
+
+--------------
+//389 by いど
+
+・388の変更
+ バージョン情報をcommon/version.h内の定数を使用するように変更
+
+--------------
+//388 by 胡蝶蘭
+
+・バージョン情報所得ツール添付
+ Perl製なので実行にはPerlが必要です。
+ 使用方法などはエディタで開いて見てください。
+ 使い方が良くわからない人は手を出さないほうがいいです。
+
+ バージョンを確認する用途よりは、サーバーの生存確認用といったかんじです
+ パケット7530/7531の詳細はソースを見てください。
+
+ (tool/)
+ checkversion
+ バージョン確認ツールPerlスクリプト
+
+ (login/)
+ login.c
+ パケット7530/7531の処理追加
+ (char/)
+ char.c
+ パケット7530/7531の処理追加
+ (map/)
+ clif.c
+ パケット7530/7531の処理追加
+
+・384以前のathena.txtも読み込めるようにしました
+ ・convertが面倒な人向け。
+ ・正しく読み込める保証無し。バックアップを忘れずに。
+
+ (char/)
+ char.c
+ 384の方式で読み込めないデータは384以前の方式も試すように。
+
+・conf_ref.txt/help.txt/getaccount修正
+ help.txt
+ petコマンドの説明追加
+ (doc/)
+ conf_ref.txt
+ pet関連の設定の説明追加
+ (tool/)
+ getlogincount
+ 表示の修正
+
+--------------
+//387 by いど
+・confフォルダ内のNPC定義データの整理
+ 以下のファイルを削除しました
+ npc_kafraJ.txt
+ npc_mind_prtmons.txt
+ npc_script2J.txt(npc_event_mobtim.txtに同じものがあったため)
+ npc_testJ.txt(ほぼ同じことが@コマンドで出来るため)
+ npc_warp25.txt(npc_warp.txtに統合)
+
+ 以下のファイルの名前を変更しました
+ npc_monster3.txt -> nop_monster2E.txt
+ npc_monster3J.txt -> npc_monster25.txt
+ npc_monster.txt -> npc_monsterE.txt
+ npc_monsterJ.txt -> npc_monster.txt
+ npc_sampleJ.txt -> npc_sample.txt
+ npc_script3j.txt -> npc_script2.txt
+ npc_script25J.txt -> npc_town_lutie.txt
+ npc_shop1J.txt -> npc_shop_test.txt
+ npc_shop2J.txt -> npc_shop_mobtim.txt
+ npc_shop3J.txt -> npc_shop2.txt
+ npc_shop.txt -> npc_shopE.txt
+ npc_shopJ.txt -> npc_shop.txt
+ npc_testJ.txt -> npc_test.txt
+ npc_warp3.txt -> npc_warp2.txt
+ npc_warp4.txt -> npc_warp25.txt
+
+・マップ定義の追加
+ ジュノーアップデートで追加されるマップと、韓鯖独自(?)のクイズゾーン
+ (コモドアップデート)と天津アップデートのマップ定義を追加
+ 現在、日鞍に無いものに関してはコメントアウトしていま。
+ conf/map_athena.cnf
+
+--------------
+//385 by 胡蝶蘭
+
+・MOBの行動修正
+ ・手抜き処理で移動しないモードのMOBも歩く問題修正
+ ・MOBを倒したとき、再spawn時刻がおかしな値になる場合がある問題修正
+ (MOBが沸かなくなる問題が修正されたはず)
+ ・MOBのワープで場所検索に1000回失敗したら元の場所に出るように修正
+ ・MOBを詠唱中に倒すと、タイマーを削除するように修正
+
+ mob.c
+ mob_delete(),mob_catch(),mob_damage(),
+ mob_ai_sub_lazy(),mob_ai_sub_hard()など修正
+ mobskill_deltimer()追加
+
+--------------
+//0384 by 死神
+
+・ペット実装。
+思ったより長くかかりました。一周もかかったせいで何処を修正したか
+覚えてない問題がありますが... それで念の為にmapとcharのファイルは全て
+含めてアップします。
+それとmakefileとathena.shは自分が使ている物です。
+Yare-launcherは使てませんがいつも鯖の実行ファイルで実行していますので...
+ char/char.c、char/char.h、char/inter.c、char/makefile 修正。
+ char/int_pet.c、char/int_pet.h 追加。
+ map/makefile 修正。
+ map/intif.c、map/intif.h、map/map.c、map/map.h、map/mob.c、map/mob.h、
+ map/npc.c、map/npc.h、map/battle.c、map/battle.h、atcomand.c、map/pc.c、
+ map/clif.c、map/clif.h、map/script.c 色々修正。
+ map/pet.c、map/pet.h は殆どを自分の物に書き換えました。
+ common/mmo.h 修正。
+ db/pet_db.txt 修正。
+ db/item_db.txt 修正。(携帯卵孵化機のbpet スクリプトが抜けていたので
+ 入れただけですが...)
+ doc/INTER鯖パケット.txtの名前をinter_server_packet.txtに変更とペットの
+ 保存等に使うパケットを追加。
+* 今度のペット実装によりキャラファイルの構造が変わり以前の物と互換できない
+ ので tool/convert.c を追加しました。
+ 単独でコンパイルできますのでコンパイルしたあと実行してキャラ
+ ファイルを変換してください。そうしないとキャラが全部飛びますので...
+* ペットの親密度が0になるとペットはその場で動けなくなりその状態で他の
+ マップに移動するか終了するとペットは消滅します。一応ペットの逃走を
+ 実装するつもりで作ったのですが本鯖にあってるかどうかはわかりません。
+* 移動速度が遅いペットの場合離れ過ぎるとついて来れなくなります。でも
+ この場合マップを移動してもちゃんとついて来ます。
+ 消滅したりはしません。
+* 移動速度が速いペットはキャラより先に移動します。本鯖の方がどうなのか
+ わからないのでペットの移動はモンスターの移動速度で移動する
+ ようにしました。
+・battle_athena.cnf
+pet_catch_rate 追加。
+ ペットの捕獲倍率を設定します。(設定しないと100)
+ 基本的にペットの捕獲に使ってる公式は
+ (pet_db.txtの捕獲率 + (キャラレベル - モンスターレベル)*0.3 + luk *0.2)
+ * (2 - モンスターの現在HP/モンスターの最大HP)
+ になります。自分なりに作った物ですので本鯖とはかなりの
+ 違いがあるかも知れません。(モンスターのHPを減らせば減らす程捕獲率が
+ 上がる仕組みですが...)
+pet_rename 追加。
+ ペットの名前を変更するかどうかを決めます。(設定しないとno)
+ yesは何度でも名前の変更が可能。
+ noは一度変更するともう変更不可能になる
+pet_hungry_delay_rate 追加。
+ ペットの腹が減る時間の倍率です。(設定しないと100)
+ 倍率が高いと腹が減り難くなります
+mvp_exp_rate 変更。
+ すでにstruct mob_dbのmexpperはゴミになっているので(MVP EXPは
+ MVPアイテムが取れなかった場合入るので意味がありません。)
+ MVP EXPの量の倍率になるように変更。(mob.cを修正)
+・char_athena.cnf
+autosave_time 追加。
+ 自動保存する時間を決めます。(設定しないと300)
+ 單位は秒です。(ファイルに保存する時間の間隔です。)
+・map_athena.cnf
+autosave_time 追加。
+ 自動保存する時間を決めます。(設定しないと60)
+ 單位は秒です。(キャラ鯖にデータを送る時間の間隔です。これは
+ ファイルに保存する時間の間隔じゃありません。)
+・inter_athena.cnf
+pet_txt 追加。
+ ペットのデータを保存するファイルを決めます。(設定しないとpet.txt)
+・@makepet コマンド追加。
+ ペットの実装によって@itemで作った卵は使っても無駄になりますので
+ これを使って卵を作ってください。
+ @makepet <モンスターのID or 卵のID>
+・@petfriendly コマンド追加。
+ @petfriendly <数字>
+ ペットを連れている時にペットの親密度を変更。(0~1000)
+・@pethungry コマンド追加。
+ @pethungry <数字>
+ ペットを連れている時にペットの満腹度を変更。(0~100)
+・@petrename コマンド追加。
+ @petrename
+ ペットを連れている時にペットの名前を変更できるように変更。
+・int_guild.c、int_party.c 読み込むファイルにエラーがあってもプログラムを
+ 終了せずに進むように変更。
+・pc_walk 123 != 1234 等のエラーが出ないように
+ if((i=calc_next_walk_step(sd))>0) {
+ sd->walktimer=add_timer(tick+i/2,pc_walk,id,sd->walkpath.path_pos);
+ を
+ if((i=calc_next_walk_step(sd))>0) {
+ i = i/2;
+ if(i <= 0)
+ i = 1;
+ sd->walktimer=add_timer(tick+i,pc_walk,id,sd->walkpath.path_pos);
+ のように変更しました。
+ tickが同じ数値になるのを防いたのですがこれでどんな影響が出るかは
+ さっぱりわかりません。
+ pc.c、mob.cを修正。
+ でもこの修正をしても連続でクリックしたりするとキャラがしばらく
+ 止まるようです。(ペットのせいと思いましたがペットがなくても
+ 同じだったので他の原因かと...)
+* doc/code_ref.txtとhelp.txtは面倒くさいので修正してません。
+・gm_all_skill: yesで2-2のスキルも表示されるように変更。(試いせはいませんが...)
+ pc.c
+ pc_calc_skilltre() 修正。
+・カートレボリューションのダメージ計算を修正。
+ 武器研究を二重計算していたので修正。
+ battle.c
+ Damage battle_calc_weapon_attack() 修正。
+
+--------------
+//381 by 胡蝶蘭
+
+・MOBの行動修正
+ ・PCのいないマップのMOBは時々ワープするようになりました
+ ・PCのいるマップのMOBは歩く以外に、時々沸き直すようになりました
+ (これまたパフォーマンスに影響があるかもしれません:少し重くなるかも)
+ ・手抜き処理でブロックの有効判定を行うようにしました
+ (HP無限MOB問題修正?)
+ ・ルート時スキル使用機構実装
+
+ mob.c/mob.h
+ mob_ai_sub_lazy(),mob_ai_sub_hard()修正
+ MSS_LOOT追加,mob_readskilldb()修正
+
+・MOBスキルの属性変更を実装しました。
+
+ map.h
+ struct mob_dataに def_eleメンバ追加
+ mob.c
+ mob_spawn()でdef_eleをセットするように変更
+ battle.c
+ battle_get_element()でdef_eleを読むように変更
+ skill.c
+ skill_castend_nodamage_id()修正
+
+・クァグマイアの効果範囲から出ると効果が切れるようになりました
+
+ skill.c
+ ユニット系処理修正
+
+--------------
+//380 by Aの人
+
+・カートレヴォリューションのダメージ計算実装
+ battle.cを変更。
+
+CHRISさん、ありがとう御座います。
+ノックバック実装できなくて、困ってました(><;
+
+--------------
+//379 by CHRIS
+
+・カートレヴォリューションの実装
+ skill.cとbattle.cを変更。
+
+・マグナムブレイクにノックバックを追加。
+ battle.cを変更。
+
+(ソースを弄ったのは初めてなので、有ってるかどうか分かりませんが、自分では出来ました。)
+(プログラム関係の書籍を買って勉強して初めて弄ったのです・・・。ガンバリマス!。)
+
+--------------
+//377 by 胡蝶蘭
+
+・MOBの行動修正
+ ・近くにPCのいないMOBが時々ワープする仕様を止めました。
+ ・PCのいないマップのMOBは全く動かなくなりました。
+ ・PCのいるマップで、近くにPCのいないMOBは時々歩くようになりました。
+ ・その他細かいところ修正
+ (パフォーマンスに影響があるかもしれません:少し重くなるかも)
+
+ mob.c
+ mob_randomwalk()追加
+ mob_ai_sub_lazy(),mob_ai_sub_hard()修正など
+
+・スキル修正
+ ・MOBがテレポートできるようになりました
+
+ mob.c
+ mob_warp()追加
+ skill.c
+ skill_castend_nodamage_id()修正
+
+・ステータス異常の一部を実装/修正
+ ・PC/MOBともに速度減少の効果が現れるように(AGIの表示は変わらず)
+ ・PCのエンジェラス、インポシティオマヌス、速度上昇の効果を修正
+ ・MOBの2HQ、アドレナリンラッシュ、エンジェラス、インポシティオマヌス、
+ 速度上昇/減少、グロリア、ブレッシングなどの効果実装
+ ・睡眠、凍結、スタンの必中効果実装
+ ・睡眠のクリティカル倍効果実装
+ ・暗黒の命中率、回避率減少効果実装
+ ・呪いのATK減少効果、LUK減少効果実装
+
+ battle.c
+ battle_get_*()修正
+ battle_calc_weapon_damage()修正
+ mob.c
+ mob_get_speed(),mob_get_adelay()追加
+ pc.c
+ pc_calcstatus()修正
+
+・item_value_db.txtでアイテムの価格を設定できるようになりました
+ ・価格データをオーバーライドできるようにしました。
+ ・これでitem_db2.txtを用意する必要がありません。
+
+ (db/)
+ item_value_db2.txt
+ item_db2.txtの価格データ。
+ item_value_db.txtにリネームすると読み込みます。
+ (map/)
+ itemdb.c
+ itemdb_readdb()修正
+
+・古木の枝を使うとMOBの名前が 0 になる問題の修正
+
+ (db/)
+ item_db.txt
+ 古木の枝のデータ修正
+
+--------------
+//375 by 胡蝶蘭
+
+・MOB専用スキルの効果をいくつか実装
+ 多段攻撃、毒などの追加効果付与攻撃、属性付き攻撃、魔法打撃攻撃
+ 必中攻撃、防御無視攻撃、ランダムATK攻撃など。
+ ただし、**全くテストしてません**。
+
+ (db/)
+ skill_db.txt
+ MOB用スキルのデータを修正
+ mob_skill_db.txt
+ 少し追加
+ (map/)
+ skill.c
+ skill_castend_damage_id()修正
+ skill_status_change_start()修正
+ skill_additional_effect()修正
+ battle.c
+ battle_calc_weapon_attack()修正
+
+・スキルを少し修正
+ ・ウォーターボールで敵が死んでいても撃つモーションをする問題修正
+
+ skill.c
+ skill_status_change_timer()修正
+
+・MOBデータが変なので某Wのデータベースを流用してみる
+ ・データの並び順とか全く同じなんですね
+
+ (db/)
+ mob_db.txt
+ 某Wのmob_db.txt
+
+・各種confのリファレンスを添付
+ あくまでリファレンスなので、HowToなんかは書いてません。
+
+ (doc/)
+ conf_ref.txt
+ confのリファレンス+α
+
+
+--------------
+//373 by 胡蝶蘭
+
+・MOBスキル使用機構仮実装
+ ・スキル使用時の処理はプレイヤーと共用(skill.c)です。
+ ・不都合が多いと思うので報告お願いします。
+ ・mob_skill_db.txtを埋めてくれる人も募集。
+ このデータは「ラグナロクのたまご」を参考にしています。
+
+ (db/)
+ mob_skill_db.txt
+ MOBスキルデータベース(未完成)
+ テスト用のデータしか入ってません。
+ (map/)
+ mob.c/mob.h
+ mobskill_*追加、その他多数修正
+ map.h
+ struct mob_data に skill* 追加
+ skill.c/skill.h
+ skill_castcancel()やスキルユニット処理をMOBに対応させた
+ battle.c
+ battle_calc_damage()など修正
+
+・ギルドのスキルが触れない問題修正
+ ・いつのまにかpc_skillupが古いものに変わっていたので修正
+
+ pc.c
+ pc_skillup(),pc_checkskill()修正
+
+--------------
+//368 by 胡蝶蘭
+
+・MOB系の修正など
+ ・MOBが策敵範囲内のPC/アイテムを等確率でロックするようになりました
+ (アクティブ、ルート:いままでは該当ブロックのリンクリストの順などに
+ 依存していた)
+ ・射程範囲内かつ、到達不可能地帯のPCをMOBがロックすると、
+ MOBが停止したり、その場で暴れだしたりする問題の修正
+ ・MOBロック中にIWなどで到達不可能になった場合、ロックを解除するように。
+ ・AEGIS方式で敵の移動を計算して移動不可能なら、Athena式で計算するように
+ ・ロックが解除されるときに数秒その場で停止するようにした
+ ・歩行が遅いMOBがとまらない/次の歩行開始が早すぎる問題を修正しました
+ ・ルート関連処理を少し修正
+
+ mob.c
+ mob_ai_sub_hard*()修正
+ mob_can_reach()追加
+
+・スキル使用時にターゲットブロックの有効性判定を行うように修正
+・ルアフのダメージが武器計算になっているのバグを魔法計算に修正
+
+ skill.c
+ skill_castend_id()修正
+ skill_status_change_timer_sub()修正
+
+
+----------
+//364 by いど
+・以下のパケットの説明を変更
+ doc/パケット解析.txt
+ R 006a <error No>.B
+ R 0081 <type>.B
+
+・363でビルド時にwarningが出る不具合を修正
+ map/guild.h
+
+--------------
+//363 by 胡蝶蘭
+
+・ギルドの修正
+ ・ログインしていないPCを追放するとマップ鯖が落ちるバグ修正
+ ・メンバー追加直後に追加されたPCがギルド表示に追加されない問題修正
+ ・同じギルドに同垢別キャラが要るPCが脱退する/追放されると別キャラが
+ 脱退してしまう場合があるバグ修正
+ ・メンバーがいるのに解散しようとするとマップ鯖が落ちるバグ修正
+
+ (char/)
+ int_guild.c
+ guild_calcinfo(),mapif_parse_GuildAddMember()修正
+ (map/)
+ guild.c
+ guild_member_leaved(),guild_member_added()
+ guild_recv_info(),guild_break()修正
+
+--------------
+//362 by 胡蝶蘭
+
+・ギルド解散実装
+
+ (char/)
+ int_guild.c
+ 解散処理を追加
+ (map/)
+ guild.c/guild.h
+ guild_break(),guild_broken(),guild_broken_sub()など追加
+ clif.c/clif.h
+ clif_guild_broken(),clif_parse_GuildBreak()追加
+ intif.c/intif.h
+ intif_parse_GuildBroken()追加
+
+--------------
+//361 by いど
+
+・360での@healの変更間違いを訂正
+ map/atcommand.c
+
+--------------
+//360 by いど
+
+・353の修正を削除
+・@healで変更後の値がマイナスにならないように修正
+
+--------------
+//359 by いど
+
+・class_equip_db.txtの文字化け修正
+
+--------------
+//358 by 胡蝶蘭
+
+・ログイン時のdelete_timerのエラーを出ないようにした
+ pc.c
+ pc_authok()の修正
+
+・ギルド関係の修正
+ ・メンバ勧誘時に最大人数の確認を行うように
+ ・データ通知処理をいくつか修正
+
+ (char/)
+ int_guild.c
+ 色々修正
+ (map/)
+ clif.c/clif.h
+ clif_guild_inviting_refused()をclif_guild_inviteack()に改名
+ guild.c/intif.c
+ 色々修正
+
+・@guildlvupコマンド作成。ギルドレベルが調整できます。
+
+ (char/)
+ int_guild.c
+ 色々修正
+ (map/)
+ atcommand.c
+ @guildlvup処理追加
+
+・Makefikeのclean部分を修正
+
+ (char/ map/ login/)
+ Makefile
+ ・削除する実行ファイルのパスを ../athena/ から ../ に修正
+
+--------------
+//357 by 胡蝶蘭
+
+・pc.cの文字化け修正
+ 文字化けしたファイルをアップするのも、それを改造するのも禁止しませんか?
+ 直すの面倒くさすぎます。
+
+ pc.c
+ 文字化けの修正
+
+・パーティやギルドに勧誘された状態でマップ移動やログアウトすると、
+ 勧誘を拒否するように修正
+
+ pc.c
+ pc_setpos()修正
+ map.c
+ map_quit()修正
+
+・I-Athena自動復旧システム(B-NSJ氏作)をAthena用に改造して添付しました
+ プログラムの性質上./toolフォルダではなく./にあります。
+ athena.shの変わりにstartで起動するとmap鯖が落ちても10秒程度で復旧します
+ プロセスは「map」で調べてますが他のプロセスに反応するときは
+ 「map-server」などに変えてみてください。
+
+ start
+ map鯖自動復旧システムのシェルスクリプト
+
+
+--------------
+//0356 by 死神
+
+・athena.shを使わなくてもYare-launcherを使えるように変更。(自分試してましたが
+一応動きました。でも窓の場合login-server.exeがlogin-server.exに登録されてしまい
+Yare-launcherがlogin-server.exe続けて実行する問題があります。これはathenaの
+問題ではありませんが...)
+ comm/makefile以外のmakefile全てを修正。
+ athena.sh修正。
+ 実行ファイルは.,/athena フォルダーじゃなく./ フォルダーに作られます。
+・ 新規accountの許容するかどうかをlogin_athena.cnfで決めるように変更。(これは
+ YareCVSを参考した物です。)
+ login.c
+ int mmo_auth() 修正。
+ login_athena.cnf
+ new_account 追加。
+・char.c、login.c、inter.c、map.c、battle.cで一部のstrcmpをstrcmpiに変更。
+
+--------------
+//355 by ゆう
+
+・左手装備も考慮した二刀流に修正
+ (ダメージ計算のみで見た目等は変更なし)
+
+map.h
+ map_session_dataに左手用の変数を追加
+
+battle.h
+ battle_get_attack_element2()追加
+
+battle.c
+ battle_get_attack_element2()追加
+ battle_calc_weapon_attack()に
+ 二刀流の処理を追加修正
+ クリティカルよりダブルアタックを先に判定するように修正
+ 過剰精錬の追加ダメージを精錬ダメージの次に処理するように修正
+ (これらは独自に調べたもので間違っている可能性あり)
+
+pc.c
+ pc_calcstatus()に左手用の変数に値を入れる処理を追加
+ pc_equipitem()の二刀流装備の場所がおかしかったのを修正
+
+--------------
+//353 by いど
+
+・Yare-launcherを使うことが出来るようにMakefileとathena.shを変更
+
+--------------
+//352 by 胡蝶蘭
+
+・詠唱中にクライアントを終了するとmap鯖が落ちる問題の修正
+ skill.c
+ skill_castend_id(),skill_castend_pos(),skill_castend_map()修正
+ map.c
+ map_quit()修正
+
+
+・データバックアップ用のツール添付
+ Perl製なので実行にはPerlが必要です。
+ 使用方法などはエディタで開いて見てください。
+ 使い方が良くわからない人は手を出さないほうがいいです。
+ データが消えても責任は持ちません
+
+ (tool/)
+ backup
+ データバックアップ用Perlスクリプト
+
+--------------
+//0351 by 死神
+skill.c
+ skill_use_id()に詠唱反応モンスターの処理を変更。(攻撃状態以外の場合
+ 詠唱反応を最優先にします。)
+mob.c
+ mob_ai_sub_hard_castsearch() 詠唱反応モンスターを二重処理して
+ いたので削除。
+ mob_ai_sub_hard() 詠唱反応モンスターを二重処理しないように変更。
+pet.c - 0344に戻しました。(修正は少し分析をしてからにします。)
+pet.h - 0344に戻しました。
+char.h
+ CHAR_CONF_NAME 追加。
+char.c
+ do_init() 実行する時ファイル名が入力されていないとCHAR_CONF_NAMEを
+ 使うように変更。
+map.h
+ MAP_CONF_NAME 追加。
+map.c
+ do_init() 実行する時ファイル名が入力されていないとMAP_CONF_NAMEを
+ 使うように変更。
+これでlogin.exe、char.exe、map.exeをathenaフォルダーにコピーした後名前を
+login-server.exe、char-server.exe、map-server.exeに変更するとYare-launcherを使う
+ことができます。これを使うと鯖が落ちる度に自動的に再実行してくれます。
+
+--------------
+//0345 by 死神
+・キャスティング探知実装。
+ mob.c
+ mob_ai_sub_hard_lootsearch() 修正。
+ mob_ai_sub_hard() 修正。
+ mob_ai_sub_hard_castsearch() 追加。
+ mob_target(), mob_ai_sub_hard_activesearch() ボスモンスターを
+ mvp経験値によって認識するように変更。
+ mob_ai_sub_hard_linksearch() 修正。
+ mob_attack() 死んだふり、ハイディングをチェックするように変更。
+ mob_readdb() 修正。
+・pet.h
+ MAX_PET_DBを100に変更。
+・pet.c
+ read_petdb() 修正。
+
+--------------
+//0344 by 過去の人i1
+・ ペット腹減り実装およびそのほか色々修正
+・ ペット餌やり実装
+
+ pet.c
+ pet_calcrate(struct map_session_data *sd);
+ ペットの獲得確率計算
+ pet_food(struct map_session_data *sd);
+ ペット餌やりシステム
+ pet_hungry_change( int tid, unsigned int tick, int id,int data );
+ ペットが腹を減るロジック
+ pet_status_int(struct map_session_data *sd);
+ 親密度計算
+ pet_status_hungry(struct map_session_data *sd);
+ 満腹度計算
+ pet_status_1a3(struct map_session_data *sd);
+ パケット1a3設定関数
+ pet_initstate(struct map_session_data *sd);
+ ペットが初めて生まれたときの初期ステータス設定
+ pet.h
+ int pet_calcrate(struct map_session_data *sd);
+ int pet_food(struct map_session_data *sd);
+ int pet_hungry_change( int tid, unsigned int tick, int id,int data );
+ int pet_status_int(struct map_session_data *sd);
+ int pet_status_hungry(struct map_session_data *sd);
+ int pet_status_1a3(struct map_session_data *sd);
+ int pet_initstate(struct map_session_data *sd);
+ を追加
+ clif.c
+ clif_pet_emotion(int fd,struct map_session_data *sd)
+ 餌をあげたときにエモーションを行う
+ clif.h
+ clif_pet_emotion(int fd,struct map_session_data *sd);
+
+--------------
+//0341 by 死神
+・ルートモンスター実装。
+ map.h
+ LOOTITEM_SIZEを20に修正。
+ struct mob_dataにint lootitem_count 追加。
+ mob.c
+ mob_spawn() 少し修正。
+ mob_ai_sub_hard_lootsearch() 追加。
+ mob_ai_sub_hard() 修正。
+ struct delay_item_drop2 追加。
+ mob_delay_item_drop2() 追加。
+ mob_damage() 修正。
+ battle.h
+ struct Battle_Configにint monster_loot_type 追加。
+ battle.c
+ battle_config_read() 修正。
+ battle_athena.cnf
+ monster_loot_type: 0 追加。(基本的に0になっています。
+ 0の場合はLOOTITEM_SIZEまでアイテムを食べても
+ またアイテムを食べて前のアイテムが消える仕様です。
+ 1の場合はLOOTITEM_SIZEまでアイテムを食べると
+ もうアイテムを食べなくなります。
+
+--------------
+//0340 by 死神
+・mvpバグ修正。
+ mob.c
+ mob_damage()でjに変えたはずの物に見落としありましたので
+ 修正しました。これでmvpアイテムで変な物が出なくなるはずです。
+・class_equip_db.txt
+ EUC-JISをS-JISに変更。(意味はありませんが他のファイルは
+ 全部S-JISだったので...単なるミスですが...)
+
+----------
+//339 by いど
+
+・338を適用した状態でビルドエラーが発生する不具合を修正
+
+----------
+//338 by 過去の人i1
+
+・ pet_db.txtに対応しました。
+・ pet_dbをつかったプログラムの書き方に修正しました。
+・ ペットの名前を変更する事が出来ます
+・ ペットにアクセサリーをつける事が出来ます。
+・ 現在ペット餌やり進行中
+
+ (map/)
+ clif.c/clif.h
+ ・ pet関連の関数をほぼ修正及び追加いたしました。
+ ・ clif_parse_EquipItem()内部でペット用装備であるかどうかの判定を行ってます
+ ・ clif_parse()を修正しました。
+
+ battle.h/battle.c
+ ・ battle_config.pet_rate変数を増やしました。mobに対する卵の獲得率
+ を設定する事が可能となります
+
+ pet.c/pet.h
+ ・ pet_initstate(struct map_session_data *sd);
+ 初期のペットステータスを設定する関数です
+ ・ pet_npcid(struct map_session_data *sd,int egg_name_id);
+ ペットに割り当てられたnpc_idを返します
+ ・ pet_itemid(struct map_session_data *sd,int mob_id);
+ モンスターIDから卵のIDを割り出します
+ ・ pet_equip(struct map_session_data *sd,int equip_id);
+ ペットのアクセサリー装備です
+ ・ pet_unequip(struct map_session_data *sd);
+ ペットのアクセサリー解除です
+ ・ pet_calcrate(struct map_session_data *sd);
+ 卵獲得確率計算を行い1or0を返します。
+ ・ pet_food(struct map_session_data *sd);
+ ペット餌やり考案です。まだ正常に動作しません。
+ ・ read_petdb()
+ pet_db.txtを読み込みpet_db[]に値を入れる関数です
+
+ ・ do_init_pet()
+ map鯖初期化でよびだしpet_db[]を使えるようにする為の
+ ペット情報初期化関数です。
+
+ map.c/map.h
+ ・ BL_PET変数を加えました
+ ・ map鯖初期化の時にdo_init_pet()を呼び出します。
+
+ mmo.h
+ ・ s_pet構造体に変数追加。キャラクターがペットのデータを保持する為のシステム
+ の為今後も変数はそのつど増加する予定
+
+ npc.c
+ 改善しました。
+
+ (conf/)
+ battle_athena.cnfに卵の獲得確率pet_rateを加えました。
+
+----------
+//337 by 胡蝶蘭
+
+・ギルドの追加と修正
+ ・ギルドに経験値を上納すると、上納されるEXPが異常な値になるバグ修正
+ ・ギルドの敵対関係の追加
+
+ guild.c/guild.h
+ guild_payexp()の修正(上納EXP処理)
+ guild_opposition()追加
+ guild_allianceack(),guild_reqalliance(),
+ guild_reply_reqalliance()の修正
+ clif.c/clif.h
+ clif_guild_oppositionack(),clif_parse_GuildOpposition追加
+
+・ディレイ時間がdexの影響を受けるかどうかをbattle_athena.cnfに書けるように
+
+ (conf/)
+ battle_athena.conf
+ delay_dependon_dex を追加
+ (map/)
+ skill.c
+ skill_delay_fix()の修正
+ battle.c/battle.h
+ struct Battle_Configにdelay_dependon_dex追加
+ battle_config_read()の修正(読み込み処理も変えてます)
+
+--------------
+//0336 by 死神
+・スキルインデュアを少し修正。
+・clif.c
+ clif_skill_damage()、clif_skill_damage2() インデュア合わせて修正。
+ (ただスキルや魔法になるとモーションが出ないパケットを
+ 見つけなかったので完全じゃありません。)
+ clif_parse_ActionRequest()、clif_parse_UseSkillToId()、clif_parse_UseSkillToPos()
+ スキルディレイの時にメッセージが出るように修正。
+・バックステップ実装、オリデオコン研究実装。
+・skill.c
+ skill_castend_damage_id()にあったスキルバックステップの処理を
+ skill_castend_nodamage_id()に移動しました。
+ スキルバックステップの処理でclif_skill_damage2()を呼ぶのをclif_fixpos()を
+ 呼ぶように変更。(これでダメージのモーションが出ずに
+ 移動できます。)
+ バックステップと叫ぶように変更。
+ skill_produce_mix() オリデオコン研究適用。武器レベルが3以上の時に
+ スキルレベル*1%がボーナスとして製造確率に付きます。
+ エルニウムの確率判定追加。
+・skill_db.txt - バックステップのnkを0から1に変更。(スキル番号150の物です。)
+・produce_db.txt オリデオコン,エルニウムを追加。(これは本鯖にはない物です。
+ よってクライアントには必要なアイテムが表示されません。)
+ オリデオコン研究を少し使える物にするために追加しました。
+ オリデオコンの場合はオリデオコン原石3つと石炭1つが必要で
+ エルニウムはエルニウム原石3つと石炭1つが必要です。
+・pc.c 少し修正。
+ pc_heal()pc_percentheal() 少しだけ修正。
+ pc_gainexp() ギルドにexpを上納する時にexpがマイナスにならないように
+ 修正。同時に2つ以上のレベルが上がるように変更。
+ 最大レベル以上にレベルが上がらないように修正。
+ pc_checkbaselevelup()、pc_checkjoblevelup() 追加。レベルアップを
+ チェックします。
+ pc_itemheal() 追加。アイテムを使う時にVITとスキルによってボーナスが
+ 付く物です。スキルラーニングポーション実装。
+・pc.h
+ pc_checkbaselevelup(),pc_checkjoblevelup() 追加。
+ pc_itemheal() 追加。
+・script.c - スクリプトfixhealを除去。itemhealを追加。healがfixhealの機能をする
+ ように変更。
+ buildin_fixheal() を消しbuildin_heal()を元の物に戻しました。(つまり
+ buildin_heal()がbuildin_fixheal()になりました。)
+ buildin_itemheal() 追加。アイテムによる回復はこれを呼ぶようにして
+ ください。
+ buildin_heal()からボーナスの計算を除去。
+・item_db.txt、 item_db2.txt - healをitemhealに変更。
+・mob.c
+ mob_damage() 0335でmvpに少し間違いがありましたので修正しました。
+・skill.h
+ MAX_SKILL_PRODUCE_DBを64から100に変更。
+
+
+//0335 by 死神
+・char/char2.cの一部にRETCODEが適用されてなかったのでそれを修正。
+・char/char2.cのparse_char()でキャラを消す時に問題がありそうな所を修正。
+・char/cha2.c,login/ login2.cをchar/cha.c, login/login.cに変更。
+・char/makefile,login/makefileを変更。
+・makefileとcommon/mmo.hを変更してOSを自動認識してRETCODEを自動に
+ 適用するように変更。
+・common/grfio.cのgrfio_init()を修正。(コードをちょっときれい(?)に
+ しただけですが,,,)
+・インデュア実装。よってアンティペインメントも実装。
+・map/clif.c
+ clif_parse_LoadEndAck() 韓国クライアントのパッチに合わせて少し変更。
+ (マップが変わる度に武器とシールドが見えなくなるためです。まだ
+ 日本クラとは関係ありませんが...)
+ clif_skillinfoblock() upはいらないと思うので消しました。スキルポイントが
+ 256、512等の時スキルツリーが正しく表示されないことは
+ もうありません。
+ clif_guild_skillinfo() 同じようにupを消しました。
+ clif_birthpet() pc_delitem() 呼ぶように変更。
+ clif_damage() インデュアに対応するように変更。
+・map/pc.c
+ pc_percentheal() マイナスを入れても動くように変更。少し修正。
+ pc_heal() 少し修正。
+ natual_heal() 少し修正。
+ do_init_pc() natual_healの修正に合わせて変更。
+ pc_calcstatus() 弓を装備してないとワシの目が適用されないように変更。
+ トラスト実装。
+ pc_damage() インデュアに対応するように変更。
+・map/pc.h
+ pc_checkoverhp(), pc_checkoversp()を追加。
+・map/map.h
+ MAX_PC_CLASSを+1に。
+・map/atcomand.c
+ comandをcommandに変更。
+ strncmpをstrcmpiに変更。よってコマンドが大文字、小文字を区別する
+ 必要がなくなりました。
+・map/npc.h
+ npc_parse_mob()を追加。(意味はありませんが...)
+・map/temdb.c
+ itemdb_readdb()でclass_equip_db.txtを読むように変更。
+・db/class_equip_db.txt を追加。ここで装備するクラスを指定します。ない場合は
+ item_db.txtにあるjobを使います。含まれてる物は完全な物ではなく
+ 使用例程の物です。
+・map/skill.c - skill_status_change_start() インデュアの時間を正しく変更。
+・map/battle.h
+ battle_configのexp_rateをbase_exp_rateに変更。,job_exp_rateを追加。
+ battle_get_mexp()を追加。
+・map/battle.c
+ battle_configのexp_rateをbase_exp_rateに変更。,job_exp_rateを追加。
+ battle_get_mexp()を追加。
+ battle_calc_magic_attack()を変更。ダーンアンデッドでボスの認識をmvp
+ expでするように変更。
+・map/mob.c
+ mob_readdb() base_exp_rate,job_exp_rateに対応。
+ mob_readdb() ボスの認識をmvp経験値でするように変更。
+ mob_damage() mvpを取る時の処理を変更。重さが50%を越えると床に
+ 落ちるように変更と色々。
+・conf/battle_athena.cnf
+ base_exp_rate、,job_exp_rateを追加。
+・0308で忘れた物
+ 古く青い箱、古いカード帖、古い紫色の箱の使用で得られたアイテムを
+ 持ってなくなったらアイテムを床に落とすように変更。
+ 製造はすでに0302で適用。
+
+----------
+//334 by C}{RIS
+
+・あぷろだの332.txtをpet_db.txtとして同梱。
+
+・各種テキストファイルのミスを修正
+ ・attr_fix.txt 属性修正がマイナスに働いて敵が回復する問題を修正。
+ >元の回復仕様に戻したい場合attr_fix.txtをリネームし、attr_fix_old.txtをattr_fix.txtにリネームして下さい。
+ ・mob_db.txt モンスターの日本語名を本鯖と統一。
+ ・cast_db.txt 詠唱時間、ディレイを本鯖と統一。
+
+----------
+//333 by 胡蝶蘭
+
+・ギルド機能追加
+ ・ギルドの同盟と同盟解消
+
+ (char/)
+ inter.c/int_guild.c
+ パケット長/ギルド処理追加
+ (map/)
+ clif.c/clif.h
+ clif_guild_reqalliance,clif_guild_allianceack,
+ clif_guild_delalliance,clif_parse_GuildRequestAlliance,
+ lif_parse_GuildReplyAlliance,clif_parse_GuildDelAlliance追加
+ (ギルド同盟関係のパケット処理)
+ intif.c/intif.h
+ ギルド同盟関係のパケット処理追加
+ guild.c/guild.h
+ ギルド同盟関係の処理追加
+ map.h
+ struct map_session_dataにguild_alliance,guild_alliance_account追加
+
+・ギルドエンブレムの変更がマップ鯖を再起動しないと有効にならないバグ修正
+
+ guild.c/guild.h
+ guild_emblem_changedの修正
+
+----------
+//331 by 過去の人i1
+
+・ペットシステム修正(完成度25%)
+
+ ・各種捕獲用アイテムをそれぞれ対応する敵に対して使用することで
+ 正しく卵が手に入るようになりました。
+ ・各種捕獲用アイテムを対応しない敵に使用した場合はルーレットが必ず失敗します。
+ ・ペットが瞬時に表示されるようになりました。
+ ・ペットを右クリックするとメニューが出るようになりました
+ ・他色々危険な要素を修正しました。(アイテムの数の減少等のバグを直しました)
+
+ (db/)
+ item_db.txt
+ 各種捕獲用アイテムに対応するようpetコマンドを正しく書きました。
+
+ (common/)
+ mmo.h
+ s_pet ペット構造体にpet_item_idを追加
+
+ (map/)
+ clif.c
+ clif_birthpet()を修正。正しく卵が減る、正しい卵のIDを取得するよう修正
+ これによって、正しくnpc_pet関数が動きます。
+
+ clif_spawnnpc()を修正(WBUFPOS(buf,36,nd->bl.x,nd->bl.y)と、数値を26から36へ変更しました)
+ これによってペットが瞬時に表示されるようになりました。
+
+ npc.c
+ npc_pet()を修正。
+
+ pet.c/pet.h
+ ペット関連の細かな関数をこちらにまとめる為、追加しました。
+ 現在はペットの判定関係をまとめてあります。
+
+ script.c
+ 特に大きな修正はありません。
+----------
+//330 by 過去の人i1
+
+・ペットシステム導入(完成度20%)
+ ・敵に対して捕獲用アイテムを使用する事が可能となりました。
+ (まだ熟していないリンゴのみ使用可能、敵につかってもポリンの卵が手に入ります。)
+ ・各種卵に対してペットを表示する事ができます、ただしなぜか一度画面外に
+ でないとペットが表示されません。
+ (恐らくペット表示の際に行っているNPCステータスが正しく入っていない)
+ ・表示されたペットがパフォーマンスを行います。
+
+ (db/)
+ item_db.txt
+ 携帯卵孵化機を使用可能に bpetコマンド(携帯卵孵化機使用)
+ 「まだ熟してないリンゴ」を使用可能に petコマンド(あるmobに対して使用可能にする)
+ (common/)
+ mmo.h
+ s_pet ペット構造体追加
+ mmo_charstatus ペット構造体宣言変数追加(pet)
+ (map/)
+ clif.c/clif.h
+
+ int clif_catchpet(struct map_session_data *sd,int pet_id);
+ void clif_ruletpet(int fd,struct map_session_data *sd);
+ int clif_judgepet(struct map_session_data *sd,int target_id);
+ int clif_sendegg(struct map_session_data *sd);
+ void clif_listpet(int fd,struct map_session_data *sd);
+ int clif_birthpet(struct map_session_data *sd,int pet_id);
+ void clif_menupet(int fd,struct map_session_data *sd);
+
+ 以上の関数を追加しペットに関する処理を行っております。
+ (ルーレット、卵選択窓、パフォーマンス、そのほか色々)
+
+ npc.c/npc.h
+ int npc_pet(struct map_session_data *sd,int name_id);
+ ペット表示の為の関数を追加
+ (この関数内部でペットの表示処理を行っています。適切に修正してください)
+
+ script.c
+ int buildin_catchpet(struct script_state *st);
+ int buildin_birthpet(struct script_state *st);
+ を追加しました。スクリプトにpet,bpetを追加しました。
+----------
+//329 by 胡蝶蘭
+
+・ギルド機能追加
+ ・ギルドへのEXPの上納
+ ・ギルドレベルアップ
+ ・ギルドスキルの割り振り
+
+ (db/)
+ exp_guild.txt
+ ギルドレベルの経験値データベース
+ (common/)
+ mmo.h
+ GBI_*,GMI_*の定義の追加など
+ (char/)
+ int_guild.c
+ EXPやレベルアップ、スキルアップ処理追加など
+ inter.c
+ パケット長追加
+ (map/)
+ guild.c/guild.h
+ ギルド処理追加
+ intif.c/intif.h
+ ギルドパケット処理追加
+ clif.c
+ clif_guild_skillinfo()修正
+ clif_guild_skillup()追加
+ pc.c
+ pc_skillup()でギルドスキルの場合はguild_skillup()を呼ぶように
+ pc_gainexp()で上納EXPのためにguild_payexp()を呼ぶように
+
+・ギルド機能修正
+ ・ギルドメンバーがログインやログアウトすると、
+ ログイン中のギルドメンバーにギルド系パケットが送られなくなる問題修正
+
+ guild.c
+ guild_recv_memberinfoshort()の修正
+
+----------
+//328 by 胡蝶蘭
+
+・ギルド機能の追加など
+ ・追放機能仮実装(追放したキャラも再加入可能&一部情報がダミー)
+ ・スキルの表示(表示だけです。上げたりは出来ません)
+
+ (common/)
+ mmo.h
+ struct guild_explusionの変更
+ (char/)
+ int_guild.c
+ 追放処理追加
+ 空ギルドチェックなど追加
+ (map/)
+ guild.c/guild.h
+ ギルドスキル関係のアクセサなど追加
+ clif.c
+ clif_guild_skillinfo()の修正
+ clif_guild_explusionlist()追加
+
+・ターンアンデッドがボス系アンデッドでMISSになるバグ修正
+ ・ターンアンデッド失敗時のダメージが使われます
+
+ battle.c
+ battle_calc_magic_damage()の修正
+
+----------
+//327 by いど
+・経験値テーブルに1箇所間違いがあったので修正
+ db/exp.txt
+
+----------
+//326 by いど
+・char鯖の新鯖,メンテナンス中のフラグ情報を設定できるようにした
+・char鯖の鯖名の長さが16バイトになっていた部分を20バイトに修正
+
+ (login/)
+ login2.c
+ parse_login()の修正
+ login.h
+ struct mmo_char_serverの修正
+ (char/)
+ char2.c
+ check_connect_login_server(),do_init()の修正
+ (conf/)
+ char_athena.cnf
+ ・char_maintenanceを1にするとログイン人数の後ろに(点検中)がつく
+ ・char_newを1にすると鯖名の前に[新]がつく
+ (doc/)
+ パケット解析.txt
+ ・今回の変更に伴う一部修正
+
+----------
+//325 by Mind Twist(224&0293)
+・敵(NPC)スキルの追加(ツリーに追加のみ)
+ db/skill_db.txt
+ ・ギルドスキル相変わらず不明…この形式じゃないのかな?
+
+----------
+//324 by non
+
+・mobのターゲット後移動を修正
+・mobの移動速度をDBから反映させるように
+
+ (map/)
+ mob.c
+ mob_ai_sub_hard()の修正
+
+・斜め判定を修正
+ ・FW等での斜め範囲を本鯖仕様に
+
+ (map/)
+ map.c
+ map_calc_dir()の修正
+
+----------
+//322 by 胡蝶蘭
+
+・ログイン時に必ずパーティーから除名されるバグ修正
+ ・0318の「パーティを除名されたのに〜」での修正ミス
+
+ (char/)
+ int_party.c
+ パーティー競合時のメッセージに改行追加
+ (map/)
+ party.c
+ party_check_member()の修正
+
+・ギルド会話実装&修正など
+ (char/)
+ int_guild.c
+ ギルド競合時のメッセージに改行追加
+ (map/)
+ guild.c/guild.c
+ guild_send_message(),guild_recv_message()など追加
+ intif.c/intif.h
+ intif_parse_GuildMessage()など追加
+ clif.c/clif.h
+ clif_guild_message(),clif_parse_GuildMessage()など追加
+ clif_guild_basicinfo()のパケットを0150から01b6に変更
+
+・バックステップがものすごく怪しいけど仮実装
+ ・使うとダメージを1喰らったように見えます。(実際はダメージ0です)
+
+ skill.c
+ skill_castend_damage_id()修正
+
+------------------
+//321 by 過去の人i
+・弓矢装備効果のみ実装(多々おかしい点は存在するが装備が出来、矢の効果が出る)
+ map/clif.c map/clif.h
+ clif_itemlist()追加
+ 持ち物の中で弓矢に割り当てるパケット番号を0x8000とする処理追加
+
+ clif_arrowequip(struct map_session_data *sd,int val); //self
+ を追加。この中で弓矢装備パケット処理を行ってます
+ map/pc.c
+ pc_equipitem() 弓矢装備追加
+
+ 残された問題点:
+ ・ 矢が減らない
+ ・ リログすると矢が1になる
+ ・ 装備している矢が表示されない(←多分装備posの設定をしていない為かと)
+ ・ 矢を装備解除できない(他の矢を装備しなおせば装備した矢の効果になります
+
+----------
+//320 by いど
+・以下のパケットのエラーコードの説明を追加
+ doc/パケット解析.txt
+ R 006a <error No>.B
+ R 0070 <error No>.B
+ R 0081 <type>.B
+
+----------
+//319 by mk
+・NPCとの会話中に装備変更、アイテム、スキルを使用できないようにしました
+ map/clif.c
+ 各所でsd->npc_idをチェックしてます
+
+・一部の被り物でも精錬後に装備箇所がおかしくなるバグを修正
+ map/script.c
+ buildin_successrefitem のコードを修正
+
+・カードによる追加効果および異常耐性発動処理の修正
+ map/skill.c
+ インデックス初期値が間違っていると思われるので修正(SC_POISON -> SC_STONE)
+
+・ボウリングバッシュの仮実装、グリムトゥースの使用条件追加
+ ・ボウリングバッシュを仮実装。吹き飛ばし処理やってるので
+ battle.c のblewcountをコメントアウトしました
+ ・グリムトゥースの使用条件(ハイディング&カタール装備)チェック追加
+ こんな感じで他のスキル使用条件も追加してもよいのかな?
+ ・skill_blown の吹き飛ばし方向をターゲットとの位置が重なっているときは
+ ランダムではなくキャラクターを後ろへ吹き飛ばすように変更しました
+ map/skill.c
+ skill_castend_damage_id()変更
+ skill_check_condition()変更
+ skill_blown()変更
+
+・アサシン二刀流処理に関する修正?
+ ・装備一覧へdrag&drop時に両手が赤くマーキングされるように修正
+ アサシンでは武器装備箇所が両手になるようitem_equippointを変更
+ それに伴いpc.cのpc_equipitemを修正
+ ・逆手ダメージを表示してみました
+ まともにダメージ計算やってないので攻撃回数の確認だけです
+ ・カタール追撃と左手攻撃のMISSを無理やり表示
+ (map/)
+ itemdb.c (itemdb.h、clif.c、pc.c)
+ itemdb_equippoint() を変更 (二刀流を考慮)
+ 引数を(int nameid) -> (struct map_session_data *sd,int nameid)に
+ 引数変更に伴い宣言(itemdb.h)と呼び出し側(clif.c ,pc.c)も変更
+ pc.c
+ pc_equipitem(), pc_checkitem()装備チェックを修正
+ battle.c
+ battle_calc_weapon_attack(),battle_weapon_attack()
+ 上にも書きましたが左手のダメージ計算は行ってません
+ (カード、属性等も未適用)右手と同じダメージ入れて左右修練適用してます
+ カタール追撃と左手ダメージをミスさせる方法(パケット?)がわからないので
+ 計算後のダメージが-1の場合、damage=0を送るようにしています
+ もっとよい方法があるのでしたら修正お願いします
+
+・完全回避の計算をLukではなくFlee2で判断するように修正
+ map/battle.c
+ battle_calc_weapon_attack()
+
+ (db)
+ item_db.txt、item_db2.txt
+ 装備品の一部ボーナス効果を追加
+ mob_db.txt
+ 某所でうpされていたものを少し修正
+ まだModeやDropに問題点があるかもしれません
+
+----------
+//0318 by 胡蝶蘭
+
+・ある公平分配PTとそのPTに属さないPCが共闘すると鯖が暴走するバグ修正
+
+ mob.c
+ mob_damage()のEXP分配処理修正
+
+・パーティを除名されたのに所属しているように見えるバグ修正
+ ・該当キャラがログアウト状態の時に除名され、その後、同垢別キャラが
+ 同パーティに所属しなおし、さらに元のキャラでログインすると、
+ 除名判定に失敗してパーティに所属したままであるとされてしまう問題修正
+
+ party.c
+ party_check_member()の修正
+
+
+----------
+//0317 by nabe
+
+・「〜さんから取引を要請されました。」が自分の名前になっていたのを修正
+ trade.c/clif.c/clif.h
+ clif_traderequest()で取引相手のキャラ名を渡すように変更
+
+----------
+//0316 by nabe
+
+・両手武器を精錬すると片手装備になるバグを修正
+ script.c
+ buildin_successrefitem()で、両手武器の場合等に装備箇所チェック
+
+----------
+//0315 by 胡蝶蘭
+
+・PCのSPAWNタイミングを変更
+ ・マップ移動(ログイン)時、ローディング終了後にSPAWNするように。
+ ・ロード中に攻撃されたりしなくなります。
+ ・ギルド/パーティ情報読み込み前に名前解決パケットが来る問題も
+ 修正されるはずです。
+
+ pc.c
+ pc_setpos()でmap_addblock,clif_spawnpcの呼び出しを止めた。
+ clif.c
+ clif_parse_LoadEndAckでmap_addblock,clif_spawnpcを呼ぶように。
+
+・inter鯖パケット処理の重大な問題を修正
+ ・一度にパケットを送信できなかった場合、無限ループに陥る問題修正
+
+ (char/)
+ char2.c
+ parse_frommap()修正
+ inter.c
+ inter_parse_frommap()修正
+ (map/)
+ chrif.c
+ chrif_parse()修正
+ intif.c
+ intif_parse()修正
+
+・ギルドの機能追加
+ ・他人のエンブレムが見えるように。
+ ・ログイン直後の自分のエンブレムが見えるように。
+ ・脱退できるように。(追放はまだです)
+
+ <パケット情報引き続き模集>
+ ・016c,016d,0163,015cなどの詳細な情報
+ ・ギルドスキルのIDがわかる人、教えてほしいです。
+ 158,205,331あたり調べましたがダメな模様。
+
+ (common/)
+ mmo.h
+ MAX_GUILDを36に。
+ (char/)
+ int_guild.c
+ 脱退のパケット変更
+ inter.c
+ パケット長修正
+ (map/)
+ clif.c/clif.h
+ clif_set0078,clif_set007bの修正
+ clif_guild_belonginfoの修正
+ clif_guild_skillinfo追加
+ guild.c/guild.h
+ 脱退などの処理追加
+ intif.c/intif.h
+ 脱退などの処理追加など
+
+
+----------
+//0314 by いど
+
+・char鯖とmap鯖の鯖数上限を30に引き上げ
+ login/login.h
+ char/char.h
+
+・map_athena1.cnfをmap_athena.cnfにリネーム
+ それに伴い、athena.shを変更
+
+・パケット解析資料をdoc/に移動
+
+
+----------
+//0313 by 胡蝶蘭
+
+・ギルドの機能追加
+ ・ギルド勧誘/役職内容変更/メンバーの役職変更など
+ ・guild.txtの書式がまた変わりましたが、前のデータも読み込めるはずです。
+
+ <パケット情報引き続き模集>
+ ・016c,016d,0163,015cなどの詳細な情報
+ ・自分以外のPCの所属ギルドIDを通知するパケット
+
+ (common/)
+ mmo.h
+ struct guild を変更
+ (char/)
+ int_guild.c/inter.c
+ 処理追加/パケット長追加
+ (map/)
+ guild.c/guild.h/intif.c/intif.h
+ 処理追加
+ clif.c/clif.h
+ ギルド関係のパケット処理追加
+
+・help.txtを修正
+ help.txt
+
+・0311による文字化けを修正
+ README
+ map/pc.c
+
+----------
+//0312 by いど
+
+・@hコマンドを@helpに変更
+・読み込むマップデータの定義部分を変更
+
+----------
+//0311 by tk44
+・Assassin 二刀流装備、ASPD問題修正
+ - map\pc.c
+ pc_equipitem(), pc_calcstatus(), pc_checkitem()
+
+・二刀流傷害計算修正まだ処理中
+
+----------
+//0310 by 胡蝶蘭
+
+・ログやデータに使う改行コードをmmo.hで設定可能に
+ (common/)
+ mmo.h
+ RETCODEで改行コードを文字列で定義します。
+ Windows系だとCR/LFなので"\r\n",UNIX系だと"\n"です。
+ 別に正しく指定しなくてもAthena自体は問題なく動作するはずです。
+ (login/)
+ login2.c
+ (char/)
+ char2.c/int_storage.c/int_party.c/int_guild.c
+ 保存する改行コードをRETCODE依存に変更。
+ 改行コードに依存せずに読めるように修正。
+
+・クライアントから不明なパケットが来たらダンプするように
+ ・#define DUMP_UNKNOWN_PACKET 1 をコメント化すればダンプしません。
+
+ clif.c
+ 不明パケットの処理でダンプ処理を追加。
+
+・ギルド機能の追加
+ ・エンブレム変更/告知変更実装
+ ・guild.txtの書式が変わりましたが、前のデータも読み込めるはずです
+
+ (char/)
+ int_guild.c/inter.c
+ ギルドパケット処理、パケット長
+ (map/)
+ guild.c/guild.h
+ 機能追加
+ intif.c/intif.h
+ ギルド関係パケット追加
+ clif.c/clif.h
+ ギルド関係パケット追加
+
+----------
+//0309 by C}{RIS
+
+・ボウリングバッシュを範囲攻撃化しました。
+・プロンテラに装備品販売NPCを追加しました。
+ map_athena1.cnfの
+ npc_shop1J.txtをコメントアウトすれば無効に出来ます。
+・アイテムの名前とIDを書いたテキストファイルを添付。(item.list)
+
+----------
+//0308 by 死神
+・自然回復のプログラムを修正しました。
+ これで大丈夫だといいですが...
+ pc.c
+ pc_natural_heal()等を修正。
+ pc_percentheal()を少し修正。
+ pc_checkskill()を少し修正。スキルがない場合0を返すように
+ 変更しました。他の.cファイルも修正する必要がありましたので
+ 修正しました。
+・4人目と5人目のキャラを消せない問題を修正。
+・始めからナイフとコットンシャツを持つように変更。
+・LOOK_SHEILDをLOOK_SHIELDに修正。
+・mmo_charstatusのsheildをshieldに修正。
+・.logファイルやaccount.txtファイルをnotepadで開くと列が全部繋いでいる
+ 問題を修正。
+・乱数を時間によって初期化するように変更。
+ map.c
+ do_init()を少し修正。
+
+他に変更したファイルもありますが全部覚えてませんので...
+
+----------
+//0307 by 胡蝶蘭
+
+・新規PCの初期位置をchar_athena.cnfに書けるようにした
+ start_point: マップ名,x,y のように指定します。
+ <例> start_point: new_1-1.gat,53,111
+
+ (char/)
+ char2.c
+
+・ギルドの一部機能
+ ・ギルド作成くらいしか動きません
+ ・勧誘/脱退/解散/情報の変更/エンブレム/告知などはすべて未実装です
+
+ <切実な要望>
+ ギルド関係のパケット情報が全然足りません。わかる人は教えてください。
+ 現在のままではエンブレムと告知くらいしか実装できない可能性が…。
+
+ (common/)
+ mmo.h
+ ギルド関係の構造体と定数追加
+ (char/)
+ inter.c
+ パケット長情報追加
+ int_guild.c/int_guild.h
+ 実際の処理追加
+ (map/)
+ map.h
+ struct map_session_dataにギルド関係のメンバ追加
+ guild.c/guild.h
+ 新規追加。ギルド機能用
+ pc.c
+ pc_authok()でギルド所属時、guild_request_info()を呼ぶように。
+ clif.c/clif.h
+ ギルドパケット追加
+ intif.c/intif.h
+ ギルドパケット追加
+
+・0303での修正「MAXHPなどがサーバーとクライアントで〜」を元に戻した
+ ・新PCを作るときに正しくHPなどを計算してくれるようになったので
+ 戻しても平気だろうと予測。
+ ・ログイン直後に重量警告が出てしまうため。
+
+ pc.c
+ pc_authok()の修正
+
+・範囲指定沸きの処理修正
+ ・できるだけ指定した数と同じだけ沸くように
+ (障害物などによる沸き妨害の回避失敗時、前の回避結果を使う)
+
+ mob.c
+ mob_once_spawn_area()の修正
+
+----------
+//0305 by いど
+・新規PCの位置を初心者修練場に変更。
+・map鯖がchar鯖に接続できない不具合の修正。
+
+----------
+//0304 by 死神
+・自然回復の量と時間を変更。韓国鯖に適用されてる物ですが日本にも
+ 適用されてるはずです。(多分... やってませんのでわかりません。汗)
+ HPは毎4秒に 1 + vit/6 + max_hp/200 を回復、
+ SPは毎8秒に 1 + int/6 + max_sp/100 を回復します。
+・スキルHP回復力向上による回復を
+ スキルレベル*5 + max_hp/50に変更。
+・スキルSP回復力向上による回復を
+ スキルレベル*3 + max_sp/50に変更。
+・スキル移動時HP回復実装。
+ 今の所止まってるのと比べて1/4の量を回復します。(時間は同じです。)
+・vitとintによって回復時間が短くなるのではなく回復量が増えます。
+・最大HPと最大SPの計算公式を変更。
+ map.h
+ int inchealtickの変わりにint inchealhptick;と int inchealsptick;を追加。
+ int parame[6] を追加。最大SPの計算の為の物で装備によって上がった
+ パラメータを持っつ。
+ pc.c
+ pc_hpheal(),pc_spheal(),pc_natural_heal_sub(),pc_natural_heal()を
+ 自分のコードに書き換えましたが一応正常に動きますが
+ 他のコードに影響がないかどうかはわかりません。
+ pc_additem()を少しだけ修正。
+ hp_coefficientをintからdoubleに変更。
+ pc_calcstatus()とpc_readdb()を修正。
+ job_db1.txt
+ 職業の計数を変更しました。(クルセイダー等のデータは
+ 完全な物じゃありません。)
+
+・char2.cを少しだけ変更。
+ char2.c
+ make_new_char()を少しだけ変更。(作った直後にHPとSPが完全に
+ 回復してるように変えました。)
+ parse_char()を少しだけ修正。韓国のクライアントで繋いても
+ 異常がないようにしました。(0x187パケットの処理を入れただけ
+ ですが... これはYareから持ってきた物です。)
+・strcmpi等のdefineをatcomand.hからmmo.hに移動しました。
+ atcomand.h, mmo.h 修正。
+・回復アイテムを使用する時vitとスキルHP回復力向上によるボーナスが付く
+ ように変更。ボーナスは
+ 回復量 *(1 + HP回復力向上スキルレベル*0.1 + vit/100)
+ になります。
+・イグドラシルの実とイグドラシルの種を仮実装。(回復はしますがエフェクトが
+ 出ません。item_dbでタイプを変えても駄目でした。)
+ script.c
+ buildin_fixheal()とbuildin_percentheal()を追加。
+ buildin_fixheal()はbuildin_heal()がスキルとvitによって回復量が変わる
+ 仕様になったので元のbuildin_heal()の名前だけを変えた物です。
+ buildin_percentheal()は入力された数字を%としてHPとSPを最大HPと
+ 最大SPを %比率に回復します。
+ スクリプトfixheal 、 percentheal 追加。使用方法はhealと同じです。
+ fixhealはvitとスキルHP回復力向上によるボーナスがない物で
+ percentheal は後の数字を %に認識します。
+ pc.h
+ pc.c
+ pc_percentheal()を追加。
+ item_db.txt、item_db2.txt
+ イグドラシルの実とイグドラシルの種を変更。
+
+----------
+//0303 by 胡蝶蘭
+
+・*.grfのパスをmap_athena.cnfにも書けるようにした。
+ ・map_athena.cnfに「data_grf: ../data/data.grf」や
+ 「sdata_grf: ../sakurai/sdata.grf」のようにパス指定できます。
+ ・grf-files.txtがある場合そちらの設定が優先されます
+
+ (common/)
+ grfio.c/grfio.h
+ grfio_setdatafile(),grfio_setsdatafile()追加。
+ data_file,sdata_fileをファイルローカルなグローバル変数に変更。
+
+・@stpoint,@skpointに負値指定の範囲チェックを追加
+・@zenyコマンド追加(ゼニーの調整)
+・@str,@agi,@vit,@int,@dex,@lukコマンド追加(基本パラメータ調整)
+
+ atcommand.c
+ @stpoint,@skpoint修正
+ @zeny,@str,@agi,@vit,@int,@dex,@luk追加
+
+・メマーナイトを使うとぼったくられていた問題を修正
+・武器製造部分のコードを多少変更
+ ・材料消費処理をアイテムが複数インデックスに分かれている場合に対応させた
+ (3万個限界を超えると別インデックスを使う仕様だった気がするので)
+ ・失敗時にも周りに通知するようにした
+
+ skill.c
+ skill_check_condition()の修正
+ skill_produce_mix()の修正
+
+・武器製造確率をconfファイルで倍率指定できるように修正
+
+ (conf/)
+ battle_athena.cnf
+ weapon_produce_rate追加
+ (map/)
+ skill.c
+ skill_produce_mix()の修正
+
+・武器ATKサイズ補正テーブルを外部から読み込むようにした
+・精錬成功確率/精錬ボーナスなどを外部から読み込むようにした
+・過剰精錬ボーナスによる追加ダメージ実装
+
+ (db/)
+ size_fix.txt
+ サイズ補正テーブル
+ refine_db.txt
+ 精錬関係データ
+ (map/)
+ pc.c
+ pc_readdb()で読み込み
+ battle.c
+ battle_calc_weapon_attack()に過剰精錬ボーナス処理追加
+
+・MAXHPなどがサーバーとクライアントで違う値に見えるバグ修正
+ ログイン直後のステータス計算の結果を直ちに送信するようにした
+
+ (map/)
+ pc.c
+ pc_authok()でのpc_calcstatus()のフラグを0にした
+ これでpc_calcstatus()のフラグパラメータは未使用?
+
+・item_dbの「忍者ス−ツ」を「忍者スーツ」に修正
+ (db/)
+ item_db.txt/item_db2.txt
+ 忍者スーツの名称変更
+
+・ログイン人数を調べるツールを添付
+ Perl製なので実行にはPerlが必要です。
+ 使用方法などはエディタで開いて見てください。
+ 使い方が良くわからない人は手を出さないほうがいいです。
+
+ (tool/)
+ getlogincount
+ ログイン人数所得Perlスクリプト
+
+----------
+//0302 by 死神
+・アイテム製造 確率判定実装。
+ 鉄の場合成功率は
+ (20 + base_level*0.3 + DEX*0.2 + LUK*0.1 + 要求スキルレベル*6)%
+ 鋼鉄と属性石、星のかけらの場合
+ (10 + base_level*0.3 + DEX*0.2 + LUK*0.1 + 要求スキルレベル*5)%
+ 武器は
+ ((2.5 + base_level*0.15 + DEX*0.1 + LUK*0.05 + 要求スキルレベル*5 +
+ 金敷 - 属性石と星のかけら) * (1 - (武器レベル - 1)*0.2) +
+ スキル武器研究レベル*1)%
+ 金敷: ない場合 -5%で金敷は 0%、オリデオコンの金敷は
+ 2.5%、黄金の金敷は 5%、エンペリウムの金敷は 7.5%
+ 属性石と星のかけら: 属性石がある場合 5%で更に
+ 星のかけらの数 * 5%を足します。
+ になりますがちょっと確率が低すぎる気もしますので
+ base_level*0.3 + DEX*0.2 + LUK*0.1をbase_level*0.5 + DEX*0.4 + LUK*0.3に
+ base_level*0.15 + DEX*0.1 + LUK*0.05をbase_level*0.4 + DEX*0.3 + LUK*0.2
+ 程度に変えた方がいいかも知りません。
+ skill.c
+ skill_can_produce_mix() と skill_produce_mix() を修正。
+ produce_db.txt
+ 星のかけらをスキル属性石製造が必要に変更。
+・*.grf等を設置せずディレクトリからの読み込むように修正。(これはYareから
+ 持ち込んだ物ですが...)
+ grfio.c
+ grfio_init()を修正。
+ grf-file.txt
+ 新規追加。grfファイルがあるディレクトリ設定用。
+・読み込むマップの最大数を512に修正。
+ mmo.h
+ MAX_MAP_PER_SERVERを384から512に修正。
+・pc.cにpc_search_inventory()を追加。
+ 機能はitem_idのアイテムを持ってるかどうかを確認して
+ 持ってる場合そのindexを返す。
+ item_idが0の場合は空けてる所のindexを返す。
+ pc_additem()とpc_takeitem()だけを少し修正。
+・GMコマンドに@stpointと@skpointを追加。
+ @stpoint 数字 - ステータスポイントを上げる。
+ @skpoint 数字 - スキルポイントを上げる。
+ atcomand.c
+ 修正。
+ atcomand.h
+ strcmpi等をLinuxでも使えるように修正。
+
+----------
+//0301 by 胡蝶蘭
+
+・最大HPが32767を超えると異常な値になる問題の修正
+・Lvが99を超えるときもエフェクトを出すようにした(自分には見えない模様)
+・配置MOBによるイベントでイベント名が4バイト以上という制限をつけた
+・teleport時に取引中断、チャット退室、倉庫保存処理をするようにした
+
+ pc.c
+ pc_calcstatus()の修正(HP計算)
+ pc_setpos()の修正(取引中断など)
+ clif.c
+ clif_set0078(),clif_set007b(),clif_spawnpc()の修正(Lv99エフェクト)
+ npc.c
+ npc_parse_mob()の修正
+
+・@hでhelp.txtが読めないときに落ちるバグ修正
+・@lvup/@joblvupで負値を入れるとLvダウンが可能になった
+
+ atcommand.c
+ @h,@lvup,@joblvup処理の修正
+
+・テレポートなどの消滅エフェクトの修正
+
+ skill.c
+ テレポの消滅エフェクトを変更
+
+・状態異常に関するスクリプト実装 [sc_start]と[sc_end]。
+・緑POT、緑ハーブなど実装
+・装備ボーナスデータ追加
+
+ (db/)
+ item_db.txt/item_db2.txt
+ 装備ボーナスデータを追加
+ 緑POT、緑ハーブなどのスクリプト追加
+ (map/)
+ script.c
+ buildin_warp()で消滅エフェクトを変更
+ buildin_sc_start(),buildin_sc_end()追加
+
+----------
+//0299 by 胡蝶蘭
+
+・NPCイベントでエクスポートされたラベルを指定できるようにした
+ NPCスクリプトでOn〜で始まるラベルを定義すると、エクスポートします。
+ NPCイベントで"NPC名(orイベント名)::ラベル名"とすると、
+ 指定したラベルから実行できます。
+ ラベル名は24バイト以内にして下さい。
+ あとプログラム的にメモリ効率悪いです。後日修正予定
+ <例>
+ NPC「test」のスクリプト内で OnEvent: とラベル定義した場合、
+ NPCイベント「test::OnEvent」で指定位置から実行できます。
+
+ (conf/)
+ npc_test_ev.txt
+ ラベル指定のサンプルもちょこっと追加
+ (map/)
+ script.c/script.h
+ script_get_label_db()などの追加。
+ parse_scriptでscriptlabel_dbにラベルデータを追加する
+ npc.c/npc.h
+ npc_event_export()など追加
+ npc_parse_scriptでラベルデータをエクスポートする
+ map.h
+ struct map_session_data のeventqueueのイベント名のサイズを
+ 50バイトにした。
+
+・AGIとDEXによるASPD計算の最大値を180から190に変更
+ pc.c
+ pc_calcstatus()のASPD計算修正
+
+・skill_db.txt/cast_db.txtの読み込みをskill.cに変更
+
+ pc.c
+ pc_readdb()の修正
+ skill.c
+ skill_readdb()の追加
+
+・アイテム製造仮実装
+ 確率判定が未実装です。必ず成功します。
+
+ (db/)
+ item_db.txt/item_db2.txt
+ アイテム製造に対応(携帯用溶鉱炉、金槌など)
+ produce_db.txt
+ 新規追加。製造リスト。
+ (map/)
+ skill.c/skill.h
+ struct skill_produce_db追加
+ skill_readdb()でproduce_db.txtを読むように
+ clif.c/clif.h
+ clif_skill_produce_mix_list(),clif_parse_ProduceMix()追加
+ script.c/script.h
+ 製造用コマンド[produce]作成。
+ 引数は製造用数値で、1-4が武器製造(Lv)、16が鉱石
+
+
+----------
+//0298 by 胡蝶蘭
+
+・Login鯖のパスワード暗号化タイプを自動認識できるように変更
+ login.hのPASSWORDENCを3にすると自動認識します。
+ 最初にpasswordencryptでチェックし、失敗すれば
+ passwordencrypt2でチェックします。
+
+ (login/)
+ login2.c/login.h
+ 暗号化パスワードの照合部分を修正
+
+・アカウント作成ツールを添付
+ Perl製なので実行にはPerlが必要です。
+ 使用方法などはエディタで開いて見てください。
+ 使い方が良くわからない人は手を出さないほうがいいです。
+
+ (tool/)
+ addaccount
+ アカウント作成ツールPerlスクリプト
+
+・スキルの追加修正
+ ・ファイヤーウォールの回数制限をグループ毎からユニット毎に修正
+ ・クァグマイア仮実装 (敵の移動速度、キャラの表示数値は変化せず)
+ ・ウォーターボール仮実装(動作が正しいのか不明)
+ ・フロストノヴァ仮実装(エフェクトが良くわからないので適当)
+ ・ベノムダスト仮実装(範囲とかが正しいかどうか不安)
+ ・プロボック、オートバーサーク、聖体降福、砂まき、石投げの実装
+ ・エンチャントポイズンの毒付与実装
+
+ *注意* 毒状態は見た目だけで効果は未実装
+
+ (db/)
+ skill_db.txt
+ 砂まき/石投げ/ウォーターボールなど修正
+ (map/)
+ skill.c/skill.h
+ 色々修正
+ mob.c/mob.h
+ mob_target()追加。MOBのタゲ用
+ battle.c
+ battle_get_*()系修正など
+ pc.c
+ pc_calcstatus()修正
+
+----------
+//0297 by 胡蝶蘭
+
+・Login鯖がパスワード暗号化に対応
+ 暗号化keyは鯖起動時に一度だけ作成します。
+
+ **注意**
+ 暗号化パスワードを使っている場合は、アカウントを作成できません。
+ アカウントを作る場合はclientinfo.xmlを編集するなどして、
+ パスワードを暗号化しないクライアントを使う必要があります。
+
+ (login/)
+ login2.c/login.h
+ 暗号化パスワードのパケット処理追加
+ 暗号化keyの生成処理追加
+ md5calc.c/md5calc.h
+ 新規追加。md5計算用
+
+
+・スキル使用ディレイにDEXが反映されないように修正
+ skill.c
+ skill_delay_fix()の修正
+
+・死亡後も一部の状態異常の効果が持続する問題を修正
+ pc.c
+ pc_damage()で死亡時にpc_calcstatus()を呼ぶように修正
+ atcommand.c
+ 死亡時処理を一本化するため@dieではpc_damageを呼ぶように修正
+
+
+----------
+//0295 by 胡蝶蘭
+
+・クレイモアートラップなどにスキルを使うとマップ鯖が落ちる問題の修正
+
+ battle.c
+ battle_get_*()などでBL_PC,BL_MOBじゃないときの処理を追加
+ skill.c/skill.h
+ skill_unit_ondamage()追加
+
+・約21Mzを超えるアイテムをNPCで扱うときOC,DC計算で値段がおかしくなるバグ修正
+ DCでは20Mz、OCでは70Mzを超えるアイテムはdouble型にして計算します
+
+ pc.c
+ pc_modifysellvalue(),pc_modifysellvalue()の修正
+
+----------
+//0294 by 胡蝶蘭
+
+・スクリプトコマンドでエリア指定のMOB沸き命令を作成
+ areamonster "マップ名",x0,y0,x1,y1,"MOB表示名",MOBのclass,数,"イベント名"
+ 座標指定が(x0,y0)-(x1,y1)の任意ポイントになるだけで他はmonster命令と同じです
+
+ script.c
+ buildin_areamonster()追加
+ mob.c/mob.h
+ mob_once_spawn_area()追加
+
+・アイスウォールの鯖側処理仮実装
+ 攻撃できないなどの問題はあるものの、鯖側ではとりあえず動きます。
+ ただ、クライアントに進入不可能エリアを教えるパケットがわからないので、
+ 鯖側ではIWの回り込みを行う場合でも、クライアント側ではすり抜けている
+ ように見えます。
+
+ skill.c
+ 該当処理追加など
+
+----------
+//0293
+・2-2次職のスキルコメントの修正(一部追加)
+ (db/)
+ skill_db.txt
+ skill_tree.txt
+
+
+----------
+//0292 by 胡蝶蘭
+
+・SHOP NPCに話し掛けるとNPCが反応しなくなる問題のまともな?修正Part2
+ ・売買できなかった問題修正
+
+ map.h
+ struct map_session_data にnpc_shopidメンバ追加
+ npc.c
+ npc_click()など修正
+
+・スクリプト命令追加
+ ・指定エリアのユーザー数の所得
+ getareausers("マップ名",x0,y0,x1,y1)
+ 指定マップの(x0,y0)-(x1,y1)にいるPCの数を計算
+ ・指定エリアのユーザーのワープ
+ areawarp "転送元マップ名",x0,y0,x1,y1,"転送先マップ名",x,y;
+ 指定マップの(x0,y0)-(x1,y1)にいる全PCを指定マップの(x,y)に転送。
+
+ script.c
+ buildin_areawarp(),buildin_getareausers()追加
+
+・スキル修正
+ ・テレポート使用時に「テレポート!!」と叫ぶように。
+ ・ストーンカースの成功率が低いのを修正
+
+ skill.c
+ skill_castend_nodamage_id()修正
+
+----------
+//0291 by 胡蝶蘭
+
+・スクリプト命令追加
+ ・NPCの有効無効が切り替えられるようになりました
+ disablenpc "NPC名"で無効化、enablenpc "NPC名"で有効化。
+ NPC名が重複しているときの動作は不定です。
+ 主にワープポイントを無効化するときに使います。
+
+ ・タイマーのカウントを変更するスクリプト命令追加
+ addtimercount "イベント名",ミリ秒
+ で、タイマの期限を延ばせます(負値を指定して減らすことも出来ます)
+
+ ・アナウンスの拡張
+ mapannounce "マップ名","アナウンス文字列",フラグ
+ で指定マップにアナウンスを流します。フラグは0で黄色、16で青です。
+ areaannounce "マップ",x0,y0,x1,y1,"文字列",フラグ
+ で指定マップの(x0,y0)-(x1,y1)のエリアにアナウンスを流します。
+ フラグはmapannounceと同じで、0で黄色、0x10で青です。
+
+ (conf/)
+ npc_test_arena.txt
+ サンプルの修正
+ (map/)
+ script.c
+ buildin_disablenpc(),buildin_enablenpc(),
+ buildin_mapannounce(),buildin_areaannounce(),
+ buildin_addtimercount()の追加
+ npc.c/npc.h
+ NPCの有効無効処理追加
+ map.h
+ struct npc_dataにflagメンバ追加(1ビット目が無効フラグ)
+ clif.c
+ clif_getareachar_npc()の修正
+ pc.c/pc.h
+ pc_addeventtimercount()追加
+
+・SHOP NPCに話し掛けるとNPCが反応しなくなる問題のまともな?修正
+ SHOP NPCと取引中でもイベントが起こるようになります。
+ これはROの仕様上避けるのが難しいためこのような結果で落ち着きました。
+
+ npc.c
+ npc_click()等の修正
+
+・スキッドトラップで残像が残る問題修正
+ skill.c
+ skill_blown()でclif_walkok()などを呼ぶように。
+ skill_unit_onplace()のclif_fix*pos()を削除。
+
+----------
+//0290 by 胡蝶蘭
+
+・スクリプトでMAP鯖内共有変数が使えるようになりました。
+ 変数名を$で開始すると、MAP鯖内の全員で共有する変数になります。
+
+ 言葉の問題ですが、PCのglobalregは「大域的」というより「永続性のある」
+ 変数であって、MAP鯖内共有変数のほうが大域的ってイメージが強いんですが…
+
+ script.c
+ mapval_db定義
+ buildin_set(),buildin_input()の修正
+ do_init_script()追加
+ map.c
+ do_init()でdo_init_script()を呼ぶように。
+
+・イベントキューが実装されました
+ ・キューサイズは2です。必要なら増やしますが。
+
+ サンプルの[ev_doテスト]がちゃんと動くようになったと思います。
+
+ map.h
+ struct map_session_dataにeventqueueメンバ追加
+ npc.c
+ npc_event_timer()追加
+ script.c
+ run_script()でEND処理でキューの処理追加
+
+・スクリプトでタイマーが使用できるようになりました
+ 使用方法は、addtimer ミリ秒,"イベント名" でタイマー追加、
+ deltimer "イベント名" でタイマー削除。
+
+ (common/)
+ timer.c/timer.h
+ get_timer(),addtick_timer()追加
+ (map/)
+ map.c/map.h
+ struct map_session_dataにeventtimerメンバ追加
+ map_quit()でpc_cleareventtimer()を呼ぶように。
+ pc.c
+ pc_addeventtimer(),pc_deleventtimer(),pc_eventtimer(),
+ pc_cleaereventtimer()追加
+ pc_authok()でeventimerの初期化
+ script.c
+ buildin_addtimer(),buildin_deltimer()追加
+
+・スクリプトの追加
+ ・getusers,getmapusers,killmonsterの追加
+ getusers(x)はユーザー数所得、x=0でPCのMAP,1=全MAP,8=NPCのMAP。
+ getmapusers("マップ名")は指定マップのユーザー数を所得する。
+ killmonster "マップ名","イベント名"で該当のマップにいる、
+ 該当のイベント駆動指定モンスターを全て削除。
+ ・announceコマンド拡張
+ フラグの0x08ビットが1ならマップやエリア計算にPCでなくNPCを使う
+
+ mob.c/mob.h
+ mob_delete()追加
+ script.c
+ buildin_getusers(),buildin_getmapusers(),
+ buildin_killmonster()追加
+ clif.c/clif.h
+ clif_GMmessage()の引数変更
+
+・イベントサンプル追加
+ 簡単なアリーナのサンプルを追加。
+
+ (conf/)
+ npc_test_ev.txt
+ 従来のサンプルの修正
+ npc_test_arena.txt
+ 簡単なアリーナのサンプル
+ ワープポイントの無効化コマンドなどが必要と思われる。
+
+・SHOP NPCに話し掛けるとNPCが反応しなくなる問題修正
+
+ npc.c
+ npc_buylist(),npc_selllist()修正
+
+----------
+//0289 by 胡蝶蘭
+
+・イベント駆動型スクリプトの修正など
+ ・他のNPCに話し掛けているときはイベントが無視されるようになりました
+ =>キューに入れるなどの処理がいると思われる。
+
+ この関係で、サンプルの[ev_doテスト]NPCをクリックしても
+ IDエラーが出て何も起きません。イベントキューを作れば直るはず。
+
+ npc.c
+ npc_event(),npc_click()にnpc_idチェックを追加
+ script.c
+ 終了時にnpc_idをクリアするように
+
+・スクリプトコマンド[announce]の追加
+ ・GMメッセージによるannounce。
+ 第1引数は文字列、第2引数はフラグで、
+ フラグの下位4ビットが0=全て、1=同じマップ、
+ 2=画面内、3=自分のみ、4=同じマップ鯖に送信。
+ フラグの4ビット目は色フラグで、0x10=青、0x00=黄色
+
+ script.c
+ buildin_announce()の追加
+ clif.c
+ clif_send()でSELFの処理追加
+ clif_GMmessage()の引数変更
+ intif.c
+ intif_GMmessage()の引数変更
+
+・メモ禁止、テレポ禁止、セーブ禁止マップが指定できるようになりました。
+ ・NPCで、mapflagというタイプで、名前を nomemo 、 noteleportで
+ メモとテレポ禁止。nosave で、引数にセーブするマップ名と座標を指定。
+
+ 詳しくは同梱のconf/npc_test_ev.txtを参照。
+
+ (conf/)
+ npc_test_ev.txt
+ 修正
+ (map/)
+ map.h
+ struct map_data にflag,savemap,savex,saveyメンバ追加
+ npc.c
+ npc_parse_mapflag()追加
+ do_init_npc()の修正
+ pc.c
+ pc_memo()でメモ禁止かどうかを確認
+ pc_makesavestatus()でセーブ禁止ならマップを変更
+ pc_randomwarp()でテレポ禁止かどうか確認
+ skill.c
+ テレポとポタでテレポ禁止か確認
+
+
+・ファイヤーウォールで落ちる問題修正…だといいな
+
+ skill.c
+ さらにチェックを追加
+ skill_blown()に落ちる原因っぽいもの発見したので修正
+
+----------
+//0288 by 胡蝶蘭
+
+・自動鷹発動時に「ブリッツビート!!」と叫ばなくなりました
+ skill.c
+ skill_attack(),skill_additional_effect(),
+ skill_castend_damage_id()の修正
+
+・イベント駆動型スクリプトが記述できるようになりました
+・モンスターを倒したときにイベントスクリプトを動かせるようになりました
+
+ NPC定義のscriptで表示クラスを-1にするとイベント扱いになります。
+ NPC定義のmonsterにイベント名を設定できます。
+ スクリプトのmonsterコマンドにイベントを起こす引数追加。
+ 詳しくはサンプルを見てください。
+ 今後タイマーでイベントを起こせるようにしようと思っています。
+
+ 現状では、NPCウィンドウ操作中にイベントがおきて、
+ そのイベントのスクリプトでNPCウィンドウを出すと問題が起きます。
+ この辺は今後の課題ということで。
+
+ (db/)
+ item_db.txt/item_db2.txt
+ monsterコマンドの変更による修正(古木の枝)。
+ (conf/)
+ npc_test_ev.txt
+ サンプル
+ (map/)
+ npc.c
+ npc_event()追加
+ npc_parse_script()修正
+ npc_checknear()修正
+ clif.c
+ clif_getareachar_npc()修正
+ map.h
+ struct mob_dataにnpc_eventメンバ追加
+ mob.c/mob.h
+ mob_once_spawn()の引数変更
+ mob_damage()で死亡時にイベントを起こすように
+ atcommand.h
+ mob_once_spawn()の引数変更
+ script.c
+ buildin_monster()の修正
+
+----------
+//0287 by 胡蝶蘭
+
+・モンスター情報スキルでHPが65535を越えていると正常な値が見れないバグ修正
+
+ clif.c
+ clif_skill_estimation()の修正
+
+・古木の枝アイテムでクライアントがリソースエラーを出す問題が修正されました
+・古木の枝アイテムで召喚できる敵が指定できるようになりました
+
+ (db/)
+ mob_branch.txt
+ 召喚可能な敵のリスト
+ (map/)
+ mob.c/mob.h
+ struct mob_dataにsummonflagメンバ追加。召喚可能性。
+ mob_once_spawn()の修正
+ mob_readbranch()の追加
+ do_init_mob()でmob_readbranch()を呼ぶように。
+
+・古く青い箱、古い紫色の箱が実装されました。
+ 一部の未実装アイテムも出ます。item_db.txtにあるデータを検索しています。
+ スクリプトgetitemで負の値を指定すると、その絶対値をフラグとして
+ ランダムにアイテムを選択します。
+
+ (db/)
+ item_db.txt/item_db2.txt
+ 該当部分のスクリプト修正
+ (map/)
+ script.c
+ buildin_getitem()の修正
+ itemdb.c/itemdb.h
+ itemdb_searchrandomid(),itemdb_searchrandomid_sub()追加
+
+・詠唱データの一部修正
+ (db/)
+ cast_db.txt
+ 速度上昇などの修正
+
+・NPCの向き修正など
+ (conf/)
+ npc_*.txt
+
+----------
+//0286 by 胡蝶蘭
+
+・モンスター情報スキルでクライアントが落ちるバグ修正
+ clif.c
+ clif_skill_estimation()の修正
+
+・詠唱反応モンスターが反応しないことがある問題を修正
+ skill.c
+ skill_use_id(),詠唱反応時、最低追跡距離を13に設定するように。
+
+・スクリプトコマンド[warp]でセーブポイント移動やランダム移動が可能になりました
+・ハエの羽、蝶の羽アイテムが実装されました
+ スクリプトwarpでマップ名に"SavePoint"や"Random"が指定できます。
+
+ (db/)
+ item_db.txt/item_db2.txt
+ ハエの羽、蝶の羽のスクリプト修正
+ script.c
+ buildin_warp()の修正
+
+・@monsterコマンドによるMOBが復活しないようになりました
+・スクリプトコマンド[monster]でMOBを発生させることができるようになりました
+・古木の枝アイテムが実装されました
+
+ スクリプト引数は monster マップ名,x,y,MOB名,MOBのID,数 です。
+ マップ名が"this"の場合、スクリプトを実行したプレイヤーのいるマップ、
+ x,yが-1ならプレイヤーの座標(どちらか一方のみそろえることも可能)、
+ MOB名が"--en--"の場合、本来の英語名になり、"--ja--"の場合、
+ 本来の日本語名になります。MOBのIDが-1の場合、適当なIDになります。
+
+ (db/)
+ item_db.txt/item_db2.txt
+ 古木の枝のスクリプト修正
+ (map/)
+ mob.c/mob.h
+ mob_once_spawn()追加
+ mob_setdelayspwan()で復活禁止処理追加。
+ npc.c/npc.h
+ npc_get_new_npc_id()追加
+ script.c
+ buildin_monster()追加
+ atcommand.c
+ @monsterの修正
+
+・@itemコマンドの修正(装備品などの問題)
+
+ atcommand.c
+ @itemの修正
+
+----------
+//0284 by 胡蝶蘭
+
+・障害物があると遠距離攻撃ができなくなりました
+・対地スキルが障害物上に使用できなくなりました
+
+ path.c
+ path_search(),can_move()の修正、can_place()の追加
+ battle.c/battle.h
+ battle_check_range()追加、射程と障害物判定。
+ battle_weapon_attack()でbattle_check_range()を呼ぶ。
+ skill.c
+ skill_use_id()、skill_use_pos()でbattle_check_range()を呼ぶ。
+ mob.c
+ mob_ai_sub_hard()の処理を修正
+
+・詠唱反応/リンクモンスターが実装されました
+ skill.c
+ skill_use_id()に詠唱反応モンスの処理追加
+ mob.c
+ mob_ai_sub_hard_linksearch()の追加
+ mob_ai_sub_hard()にリンク処理を修正
+
+----------
+//0283 by れあ
+・リザレクションの修正
+ 0282で生きてるPCにリザがかけれるのに、
+ 死んでるPCにはリザがかからなくなって
+ いたのを修正しました。
+
+----------
+//0282 by 胡蝶蘭
+
+・スキルの修正と追加実装
+ ・キリエエレイソンのエフェクトの問題修正。
+ ・リザレクションが生きているPCには掛けられないようになりました
+ ・ターンアンデッド/攻撃リザレクションがBOSSには効かないようになりました
+ ・ファイヤーウォールのヒット数制限を実装
+ ・ストームガストの実装
+ ただし、本鯖と違い凍結は確率のみで、最大ヒット数などが変です。
+
+ skill.c
+ skill_castend_nodamage_id()の修正
+ ストームガストの処理追加
+
+・スキルユニット処理に意地になって安全性チェックを追加。
+ (落ちなくなる日は遠い??)
+
+ map.h
+ MAX_SKILLUNITGROUPを32に増やした
+ skill.c
+ skill_status_change_*()にNULLチェック追加
+ battle.c
+ battle_calc_damage()に生存チェック追加
+ map.c
+ map_freeblock_unlock()にロック数チェックを追加
+
+・その他修正
+ ・PCの最大HPが30000に制限されました。
+ ・PCの回復処理が修正されました
+ ・吹き飛ばし処理の修正
+ ・0281のitem_db.txtの変更をitem_db2.txtにも適用
+
+ (map/)
+ pc.c
+ pc_heal(),pc_calcstatus()の修正
+ path.c
+ path_blownpos()の修正
+ (db/)
+ item_db2.txt
+ 0281の名前修正などを適用
+
+----------
+//0280 by 胡蝶蘭
+
+・管理者のシステムを作成
+ "conf/login_athena.cnf"の作成、管理者パス、GMパスの設定。
+ アカウントを作るとき、
+ <例> ID: hoge_M Pass: foobar@admin
+ のように、パスワードの後ろに「@管理者パス」が必要に。
+ login_athena.cnfのadmin_passの欄を消せば、今までのようにもつかえます。
+ (その場合、admin_passの後ろだけでなく、行ごと消してください)
+
+ (login/)
+ login.h
+ 設定ファイルのデフォルト名追加
+ login2.c
+ アカウント作成のところを修正
+ 設定ファイルの読み込み追加
+
+・@GMコマンド復活
+ ただし、「@gm GMパス」として使います。
+ GMパスはlogin_athena.cnfのものです。
+ 鯖の再起動の必要はありませんが、
+ クライアントはリログする必要があります。
+
+ <注意>
+ 同じアカウントの全てのキャラはPTから抜けて置いてください。
+ また、倉庫のアイテムは全部引き出して置いてください。
+ そうしないとゴミデータが残ります。
+
+ (login/)
+ login2.c
+ アカウントID変更処理追加
+ (char/)
+ char2.c
+ アカウントID変更処理追加
+ (map/)
+ chrif.c/chrif.h
+ chrif_changegm(),chrif_changedgm()追加
+
+・@pvpoffコマンド復活
+ clif.c/clif.h
+ clif_pvpoff()追加
+ atcommand.c
+ @pvpoffの処理追加
+
+・空の倉庫データは保存されないように変更
+ (char/)
+ int_storage.c
+ inter_storage_save()、storage_tostr()を修正
+
+・@memoコマンド追加。
+ 任意の記憶域にメモを取れるようになりました。
+
+ atcommand.c
+ @memoの処理追加
+
+
+----------
+//0279 by 胡蝶蘭
+
+・スキルユニット処理の問題対策
+ とりあえずひたすらチェックを入れました。
+
+ skill.c
+ skill_unit_timer_sub(),skill_unit_move_sub(),
+ skill_delunit()にユニットの生存判定を追加。
+ skill_unitgrouptickset_search(),skill_unitgrouptickset_delete()
+ skill_delunitgroup()にNULLポインタチェックを追加。
+
+・スキルの追加実装と修正
+ テレポート、ワープポータルの実装
+ キリエエレイソンをMOBに掛けると落ちるバグ修正
+
+ (db/)
+ cast_db.txt
+ ワープポータルの詠唱時間設定
+ (map/)
+ map.h
+ struct skill_unit_groupのvalstrをポインタに変更
+ clif.c/clif.h
+ clif_parse_UseSkillMap(),clif_skill_warppoint()、
+ clif_parse_Memo(),clif_skill_memo()追加
+ skill.c/skill.h
+ skill_castend_map(),skill_unit_onlimit()の追加
+ skill_unit_*系の処理いろいろ追加。
+ skill_status_change_start()のキリエの処理修正
+ pc.c/pc.h
+ pc_randomwarp(),pc_memo()追加
+
+----------
+//0278 by nabe
+
+・スキルポイントを振ったときにステータスを更新
+ pc.c
+ pc_skillup()でpc_calc_skilltree()の代わりにpc_calcstatus()
+・所持量増加を修正
+ pc.c
+ pc_calcstatus()の所持量増加によるmax_weight増分をskill*1000に
+
+----------
+//0277 by nabe
+
+・付属品(カート、鷹、ペコ)の付け外しを改良
+ (conf/)
+ npc_event_rental.txt
+ 鷹、ペコを付けるスクリプトコマンドを変更
+ (map/)
+ battle.c
+ battle_addmastery(),battle_calc_weapon_attack()で
+ ペコペコ騎乗時の槍攻撃力補正を実装
+ clif.c
+ clif_parse_CartOffをclif_parse_RemoveOptionに変更
+ pc.c/pc.h
+ pc_calcstatus()でカート、ペコペコ乗りによる速度変化を計算
+ pc_setoption(),pc_setcart()改良
+ pc_setfalcon(),pc_setriding()追加
+ pc.hにpc_isfalcon(),pc_isriding()マクロ追加
+ script.c
+ buildin_setfalcon() 鷹付加
+ buildin_setriding() ペコペコ乗り
+
+----------
+//0276 by nabe
+
+・精錬NPC実装
+ (conf/)
+ npc_town_refine.txt
+ 精錬NPCスクリプトファイル新規追加
+ (map/)
+ pc.c/pc.h
+ 精錬成功率の表percentrefinery[5][10]を追加
+ script.cから呼ばれる関数
+ pc_percentrefinery() 精錬成功率
+ pc_equipitemindex() 装備品インデックス
+ を追加
+ script.c
+ buildin_getequipname() 装備名文字列(精錬メニュー用)
+ buildin_getequipisequiped() 装備チェック
+ buildin_getequipisenableref() 装備品精錬可能チェック
+ buildin_getequipisidentify() 装備品鑑定チェック
+ buildin_getequiprefinerycnt() 装備品精錬度
+ buildin_getequipweaponlv() 装備品武器LV
+ buildin_getequippercentrefinery() 装備品精錬成功率
+ buildin_successrefitem() 精錬成功
+ buildin_failedrefitem() 精錬失敗
+ を追加
+
+・スクリプトにWeight,MaxWeightパラメータを追加
+ const.txt
+ Weight,MaxWeightを追加
+
+・スクリプトでのキャラ名表示方式を変更
+ (conf/)
+ npc_job_merchant.txt/npc_job_thief.txt/npc_town_kafra.txt
+ mes "$charaname"; を mes strcharinfo(0); に変更
+ (map/)
+ script.c
+ buildin_strcharinfo()を追加
+
+----------
+//0275 by 胡蝶蘭
+
+・MVPの実装
+ MVPの判定にはdmglogを使ってます。すなわち与ダメだけが計算対象です。
+ 被ダメは考慮されてません。
+ 経験値は無条件で入り、確率でさらにアイテムが入ります。
+ アイテムは複数手に入ることもあります。
+
+ clif.c/clif.h
+ clif_mvp_effect(),clif_mvp_item(),clif_mvp_exp()追加
+ mob.c
+ mob_damage()にMVP処理追加
+
+・スキルの追加実装と修正
+ ・マグナムブレイク、アローシャワー実装
+ ・吹き飛ばし系スキルが一部使用されないバグ修正
+ ・ダブルストレイフィングのダメージ計算式修正
+ ・武器攻撃系属性付きスキルで属性が反映されない問題修正
+ ・場所指定スキルが攻撃しながら詠唱できた問題を修正
+
+ battle.c
+ battle_calc_weapon_attack()の該当個所修正&追加
+ skill.c
+ skill_castend_damage_id()に処理追加
+ skill_use_pos()に攻撃停止処理追加
+
+・カードスキルがカードを外しても使用可能な問題を修正
+ pc.c
+ pc_calc_skilltree()を修正
+
+・アイテムドロップ率、exp所得倍率などの調整機能追加
+ battle_athena.cnfで調整できる項目が増えました。
+ 詳しくはそちらを参照してください。
+
+ (conf/)
+ battle_athena.cnf
+ mvp_hp_rate,item_rate,exp_rate,mvp_item_rate,mvp_exp_rate追加
+ (map/)
+ battle.c/battle.h
+ 増えた項目を読み込むように処理追加
+ mob.c
+ mob_db.txt読み込み時、データを調整する処理追加
+
+----------
+//0274 by 胡蝶蘭
+
+・スキルの追加実装
+ スキッドトラップ、ランドマイン、ブラストマイン、クレイモアートラップ、
+ フリージングトラップ、サンドマン、アンクルスネア
+
+ 睡眠や凍結などの確率は適当です。
+ 罠発動時のエフェクトが出ません。ていうか出し方がわかりません。
+ わかる人は教えてください。もしくは本鯖で罠発動時の複合化済みの
+ 生パケットデータでもいいので教えてください。
+
+ (db/)
+ skill_db.txt
+ 属性の修正
+ (map/)
+ skill.c
+ 該当個所
+ battle.c/battle.h
+ battle_calc_misc_damage()の該当個所
+ battle_stopwalking()追加
+ clif.c/clif.h
+ clif_fixpcpos()追加
+ clif_parse_WalkToXY()にアンクルで動けなくする処理追加
+ mob.c
+ mob_ai_sub_hard()にアンクルで動けなくする処理追加
+
+・装備などのクリティカルボーナスが1/10になっているバグが修正されました
+ battle.c
+ battle_calc_weapon_attack()に追加分を計算する処理修正
+
+・ブリッツビートの計算式が全然違うバグが修正されました
+ battle.c
+ battle_calc_attack()のBF_MISCの処置が間違っていたのを修正
+
+・SWとニューマが武器攻撃ならどんなレンジの攻撃でも防いでいた問題を修正
+ battle.c
+ battle_calc_damage()のレンジ判定を修正
+
+・オーバートラストとウェポンパーフェクションがPTメンバにもかかるように修正。
+ 効果は使用者とPTメンバで違いはありません。
+
+ skill.c
+ skill_castend_nodamage_id()の修正。
+
+----------
+//0273 by 胡蝶蘭
+
+・キャラクターが消失したり幻影が出る問題が修正されました
+ ・吹き飛ばしスキルを受けると発生していた
+ ・斜め以外の歩行で発生していた
+
+ map.c
+ map_foreachinmovearea()の修正。
+ skill.c
+ skill_blown()に表示範囲更新処理を追加。
+ mob.c
+ mob_walk()で歩き終わったときに位置を再送信するように修正
+ pc.c
+ pc_walk()で歩き終わったときに位置を再送信するように修正
+
+・スキルの追加実装と修正
+ ファイヤーウォール、ファイヤーピラーの実装
+ サンクチュアリでのノックバック方向を修正
+
+ (db/)
+ skill_db.txt
+ ファイヤーピラー、ブリッツビートのヒット数修正
+ (map/)
+ skill.c
+ skill_blown()に対象の向きによるノックバック処理追加
+ その他必要な場所修正
+ mob.c
+ mob_walk(),mob_attack()で向きを保存
+ pc.c
+ pc_walk(),pc_attck()で向きを保存
+ map.c
+ map_calc_dir()追加。相対的な方向を求める
+
+
+・クリティカル増加装備が戦闘時に計算されてないバグが修正されました
+ battle.c
+ battle_calc_weapon_attack()に追加分を計算する処理追加
+
+
+・防御ユニット(SW/ニューマ)が敵に影響を及ぼすかどうかを
+ battle_athena.cnfで制御できるようになりました
+ デフォルトは「及ぼさない」です。
+
+ (conf/)
+ battle_athena.cnf
+ 項目defunit_not_enemyを追加
+ (map/)
+ battle.c/battle.h
+ struct Battle_Config に defnotenemyメンバ追加。
+ battle_read_config()の処理を修正。
+ skill.c
+ skill_unitsetting()でSW/ニューマの処理を修正
+
+・フェンカード装備時、死んでも詠唱が続くバグを修正
+ (詠唱終了前に復活すれば魔法が発動する問題も修正になります)
+
+ pc.c
+ pc_damage()で死亡時skill_castcancel()を呼ぶように修正
+
+・敵味方判定処理にバグがあったのを修正
+ battle.c
+ battle_check_target()の修正
+
+----------
+//0272 by 胡蝶蘭
+
+・スキルの追加実装と修正
+ ・セイフティウォール、ニューマが実装されました。
+ ・ロードオブバーミリオン発動中に効果範囲外から範囲内に入ってきたとき、
+ 敵味方の区別無く攻撃が当たる問題が修正されました。
+ ・サンクチュアリの射程が修正されました。
+ ・範囲魔法で倒した敵がHP0で残る場合がある問題が修正されました。
+
+ (db/)
+ skill_db.txt
+ セイフティウォールとサンクチュアリの射程を8に変更
+ (map/)
+ skill.c
+ skill_unit_onplace(),~ondelete(),~onout()などに、
+ セイフティウォールとニューマの処理追加。
+ skill_unit_move()にターゲットの敵味方判定を追加。
+ skill_unit_timer_onplace(),~ondelete()にユニット生存判定を追加.
+ skill_clear_unitgroup()追加。ユニットグループの全削除をする。
+ battle.c
+ battle_calc_damage()にセイフティウォールとニューマの処理追加。
+ map_foreachinarea()など修正
+ map.c
+ map_quit()でskill_clear_unitgroup()を呼ぶように。
+
+・スキルの吹き飛ばし処理を実装
+ ユピテルサンダー、サンクチュアリ、スピアスタブ、
+ ボーリングバッシュ、チャージアローの吹き飛ばし処理実装
+
+ path.c/map.h
+ path_blownpos()追加
+ battle.c/battle.h
+ struct Damageにblewcountメンバ追加
+ battle_calc_*_damage()でblewcountをセットするように。
+ skill.c/skill.h
+ skill_blown()追加。吹き飛ばし処理。
+ skill_attack()でskill_blown()を呼ぶように。
+ skill_attack()のflagの吹き飛ばしビットは未使用に。
+
+・歩行中のモンスターに攻撃したとき、モンスターにディレイが入るようになりました
+ (攻撃のモーションの遅延を考えてないのであんまり意味がないかも?)
+
+ (map/)
+ mob.c/mob.h
+ stateにMS_DELAYを追加。
+ mob_damage(),mob_timer()などの修正
+
+・歩行中のモンスターに攻撃したとき、位置がずれる問題の応急処置
+ (まだ位置はずれるようです)
+
+ clif.c/clif.h
+ clif_fixmobpos()を追加
+ mob.c
+ mob_attack()でclif_fixmobpos()を呼ぶように。
+
+・その他修正
+ pc.c
+ pc_stop_walking()でpath_lenを初期化するように。
+
+----------
+//0271 by れあ
+
+・PTに関して少しだけ修正
+ 公平にしてからキャラを加入させると公平が解除されないバグを修正
+
+----------
+//0270 by 胡蝶蘭
+
+・スキルの追加実装と修正
+ ロードオブバーミリオン、サンクチュアリ、マグヌスエクソシズム
+
+ (db/)
+ skill_db.txt
+ マグヌスのヒット数、属性調整
+ サンクチュアリの属性調整
+ (map/)
+ map.h
+ struct map_session_dataの修正
+ clif.c/clif.h
+ clif_skill_setunit(),clif_skill_delunit()、
+ clif_getareachar_skillunit(),clif_clearchar_skillunit()追加
+ clif_pcoutsight(),clif_pcinsight(),clif_getareachar()修正
+ skill.c/skill.h
+ 忘れるほど多数変更。主にスキルユニット関連部分。
+ pc.c
+ pc_authok()でskillunit,skillunittickを初期化するように。
+ pc_walk()でskill_unit_move()を呼ぶように。
+ mob.c
+ mob_spwan()でskillunittickを初期化するように。
+ mob_walk()でskill_unit_move()を呼ぶように。
+ battle.c/battle.h
+ battle_calc_magic_attack()修正
+ battle_check_target()修正
+ map.c
+ map_foreachobject()など修正
+
+・こまかいバグ修正など
+ ・mobが回復しない問題修正
+
+ battle.c
+ battle_damage()修正
+
+----------
+//0266 by 胡蝶蘭
+
+・魔法計算式の修正
+ 魔法倍率をダメージに掛けていたのをMATKにかけるようにしました。
+ …こっちが正しいとしていいのかな?違うなら教えてください。
+
+ battle.c
+ battle_calc_magic_attack()の修正
+
+・スキルの追加実装
+ サイト、ルアフ、ロードオブヴァーミリオン
+
+ ロードオブバーミリオンは3回の判定時に詠唱音が鳴ります…。
+ clif_skill_damage()のtypeを色々変えてみましたがどうもうまくいきません。
+ 直せる人は直してくれるとうれしいです。
+ (うーん、ひょっとしたら本来はグラフィックのないスキルユニットを
+ 設置して、そのユニットのIDでダメージを与えるのかも??)
+
+ (db/)
+ skill_db.txt
+ ロードオブバーミリオンのヒット数を3から10に変更。
+ (map/)
+ skill.c
+ skill_status_change_timer_sub()追加。
+ skill_status_change_*()に処理追加。
+
+・blockのメモリ解放の安全性の向上
+ map_foreachinarea,party_foreachsamemapで回っているときに
+ blockをチェインから削除すると、うまく回らない可能性がある問題修正。
+ さらに、blockをメモリから解放すると危険な問題も修正。
+
+ ・foreach内で関数を呼ぶ前にblockがチェインから外れてないかチェック。
+ ・foreachに入ったときにロックしてメモリから解放されないようにする。
+ これはユーザーがfreeじゃなくmap_freeblockによって解放するように
+ プログラムする必要がある。(ループから呼ばれる可能性のある関数を作る
+ 場合のことで、普通はfreeでもいちおう動く。)
+ ・map_foreachinmoveareaについては改良していないが、
+ このループでblockを削除することはありえない気がするのでいいとする。
+
+ これは今後を見越した改良であって、現在の不安定さを直すものではない。
+ (現在はforeach内でメモリを解放していない…はずなので。
+ ただ、スキルユニットなど一時オブジェクトを多用し始めると効果がある)
+
+ map.c
+ map_freeblock(),map_freeblock_lock(),~_unlock()追加。
+ map_delobject()のfree()をmap_freeblock()に置換。
+ map_foreachinareaでロックと安全性チェック。
+ party.c
+ party_foreachsamemap()でロックと安全性チェック
+
+
+・スキルユニット機構実装
+ 設置系のスキルのための機構実装。実際のスキルの実装はまだです。
+
+ skill.c
+ なんかもう色々追加しました。
+ map.c
+ do_init()でdo_skill_init()を呼ぶように。
+ map.h
+ struct skill_unit,skill_unit_groupなど追加。
+ map_session_dataの書き換えなど。
+
+・その他細かいところを修正したと思うけど忘れました。
+
+----------
+//0264 by nabe
+
+・$charanameを喋るNPCと話した時、map鯖が落ちることがあるバグを修正しました。
+ script.c
+ replacestr()がおかしかったのを手直ししました。
+
+----------
+//0263 by nabe
+
+・露店開設中にカートアイテムを出し入れできないよう修正
+ pc.c
+ pc_putitemtocart(),pc_getitemfromcart()に、露店判定を追加
+
+・露店アイテム購入のチェックを追加
+ vending.c
+ vending_purchasereq()で諸々の条件判定を追加
+
+----------
+//0261 by 胡蝶蘭
+
+・拡大鏡、イグドラシルの葉が実装されました
+ スクリプトにitemskillコマンド作成。一時的にスキルが使用できます。
+
+ (map/)
+ script.c
+ buildin_itemskill()の追加など。
+ skill.c
+ アイテムスキルならSPなどを検査&消費しないように修正
+ clif.c/clif.h
+ clif_item_skill()の追加。
+ (db/)
+ item_db.txt/item_db2.txt
+ スキル使用アイテムのスクリプト修正
+
+・パーティスキルの実装
+ アンゼルス、マグニフィカート、グロリア、アドレナリンラッシュが
+ 画面内のパーティ全員に効果を及ぼすようになりました。
+
+ skill.c
+ skill_castend_nodamage_id()の該当個所の修正
+ party.c
+ party_foreachsamemap()の修正
+
+・スキル関係の修正
+ キリエエレイソンが即時発動になっているのを修正。
+ ストーンカースでエフェクトが存在しないバグ修正。
+
+ (db/)
+ skill_db.txt
+ キリエエレイソイン修正
+ (map/)
+ skill.c/skill.h
+ skill_check_condition()追加。スキル使用条件検査の一本化。
+ skill_castend_nodamage_id()でストーンカース修正
+
+・スクリプトのコードを整理
+ get_val()でconst.txtの定数を所得できるように修正。
+
+ (map/)
+ script.c
+ get_val()の修正(const.txtのtype==0の値が所得可能に)
+ bonus(),bonus2()などの修正。
+ (db/)
+ const.txt
+ type=1である必要が無いものを0に。
+ item_db.txt/item_db.txt
+ const.txtの変更に伴う修正。
+
+----------
+//0260 by 胡蝶蘭
+
+・戦闘関係の設定がファイルに書けるになりました
+ map鯖の第2引数にファイル名が設定されていると、それを使い、
+ 設定されてない場合は "conf/battle_athena.cnf"を使います。
+
+ あと、一応範囲攻撃スキルについて説明。
+ 鯖が常にPVPに設定されている場合、パーティメンバじゃないPCにも範囲攻撃が
+ あたります。嫌な場合はパーティを組むか、常にPVPをoffにして下さい。
+ 常にPVPがoffでも、@pvpでpvpフラグを入れた人の間では攻撃が当たります。
+ ただし、一度pvpをonにすると、リログするまでonのままなので注意。
+
+ (conf/)
+ battle_athena.cnf
+ 中に説明書いてるので各自好きなように書き換えてください。
+
+ (map/)
+ battle.c/battle.h
+ struct Battle_Configの定義。
+ battle_config_read()など追加。
+ skill.c
+ CASTFIX,DELAYFIXの廃止とBattle_Configによる修正の追加。
+ atcommand.c
+ @pvpコマンドでpvpフラグをセットするように。
+ (鯖設定の常にPVPがoffの時、両人がpvpをonにしてたら戦闘可能)
+ map.c/map.h
+ struct map_session_dataにpvp_flagを追加
+ do_init()でbattle_config_read()を読むように。
+
+・戦闘関係のコードが少し整理されました
+ battle.c/battle.h
+ battle_weapon_attack()追加。
+ battle_calc_weapon_attack()の引数変更
+ battle_calc_attack()を追加してbattle_calc_*_attack()を一本化。
+ skill.c/skill.h
+ skill_weapon_attack(),~_magic_~(),~_misc_~()の廃止、
+ skill_attack()に一本化。
+ pc.c/mob.c
+ 攻撃処理をbattle_weapon_attack()に一本化。
+
+・アイテム鑑定スキルを実装
+ 商人のスキルの方です。虫眼鏡はまだです。
+
+ skill.c
+ スキル処理追加
+ pc.c/pc.h
+ pc_item_identify()追加
+ clif.c/clif.h
+ clif_item_identify_list(),clif_item_identified()追加
+ clif_parse_ItemIdentify()追加
+
+・スキルデータベースのコメント修正
+ (db/)
+ skill_db.txt
+ 商人のスキルのコメントがずれていたのを修正
+
+----------
+//0259 by れあ
+・mob_db.txtの修正
+ 亀島モンスターやBOSSのステータス調整
+ 亀島モンスに適当にドロップを付けました。
+ 本鯖と異なる物を落とす場合もあります。
+
+----------
+//0258 by 胡蝶蘭
+
+・パーティで一度公平にしたら各自所得に戻せないバグ修正
+ (char/)
+ int_party.c
+ mapif_parse_PartyChangeOption()の判定修正
+
+・スキルの追加実装(主に範囲攻撃系)
+ ナパームビート(分散対応)、ファイヤーボール、
+ サンダーストーム、ヘブンズドライブ、
+ ブリッツビート(自動鷹込み)、スチールクロウ
+ スキンテンパリング
+
+ (db/)
+ skill_db.txt/skill_tree.txt
+ 一部修正
+ (map/)
+ battle.c/battle.h
+ battle_check_target()を追加。対象になるかを検討する。
+ battle_calc_magic_damage()の引数変更。ダメージ分散処理追加。
+ battle_calc_misc_damage()追加。
+ battle_calc_weapon_damage()修正。
+ clif.c/clif.h
+ clif_skill_damage(),clif_skill_damage2()の引数変更。
+ clif_skill_poseffect()追加。
+ skill.c/skill.h
+ skill_weapon_attack(),skill_magic_attack()に微妙に処理を纏めた.
+ skill_area_sub()追加。範囲スキル用。
+ skill_area_sub_count()追加。skill_area_sub()用、敵カウント。
+ skill_castend_damage_id()修正。引数と処理を追加。
+ skill_castend_nodamage_id()修正。引数と処理を追加。
+ skill_misc_attack()追加。
+ skill_additional_effect()修正(自動鷹)
+ skill_castend_pos()修正。
+ skill_castend_pos2()追加。
+
+・弓で攻撃したとき計算にDEXでなくSTRが使われる問題を修正。
+ battle.c
+ battle_calc_weapon_damage()修正。
+
+----------
+//0257 by 胡蝶蘭
+
+・item_db.txtの職業フラグと、カードの装備個所フラグを修正
+ 装備品はI-Athenaのデータを参考にして機械的にコンバートさせました。
+ I-Athena側にない装備品は、あきらかに変なのは修正しましたが、
+ 知らないものが多すぎて、ほとんど放置です。
+ カードは、武器用カードの装備個所が0になってるのを2(左手)に修正。
+ 両手武器の場合は別に判定してるので両手武器も問題ないはず。
+
+ (db/)
+ item_db.txt/item_db2.txt
+ 該当個所修正
+
+・カード追加実装
+ スタンなどの追加効果、それらへの耐性系統、オークヒーローカード実装
+
+ (db/)
+ item_db.txt/item_db2.txt
+ スクリプトの修正
+ (map/)
+ map.h
+ struct map_session_dataにaddeffなどのメンバを追加
+ pc.c
+ pc_calcstatus()、pc_bonus2()の修正
+ pc_attack()でskill_additional_effct()を呼ぶように。
+ skill.c/skill.h
+ skill_additional_effect()でカードによる判定追加
+ skill_status_change_start()で耐性を付けた。
+ battle.c
+ battle_calc_weapon_attack()でオークヒーローカード
+ (クリティカル耐性)の処理を追加
+
+・回避判定の修正
+ 攻撃者がPCの場合、最大命中率95%制限をなしにしました。
+ battle.c
+ battle_calc_weapon_attack()を修正
+
+
+・完全回避を実装
+ へんてこな処理してます&計算式適当です。
+
+ battle.c
+ battle_calc_weapon_attack()に処理追加。
+
+・倉庫を開いたままログアウトしたときmap鯖内では開きっぱなしになってる問題を修正
+ storage.c
+ storage_storage_quitsave()を修正
+
+・@item,@monster,@produceで名前指定できるように変更
+ 英語名、日本語名どちらでもOK。英語の場合は大文字小文字区別しません。
+
+ atcommand.c
+ 該当個所修正
+ itemdb.c/itemdb.h
+ itemdb_searchname(),itemdb_searchname_sub()追加
+ mob.c/mob.h
+ mobdb_searchname()追加
+
+・@refineで上げる数値を指定できるように変更
+ atcommand.c
+ 該当個所修正
+
+・@produceによる製造時のエフェクトを正しいものに修正
+ clif.c/clif.h
+ clif_produceeffect()追加
+ atcommand.c
+ 該当個所修正
+
+・露店スキル使用時の処理を少し修正
+ skill.c
+ skill_castend_id()でなく、skill_castend_nodamage_id()で
+ 露店開設を呼ぶようにした。
+
+・stricmpの変わりにstrcasecmpを使うようにした
+ (_WIN32か__EMX__が定義されているとstricmpを使います)
+
+ (char/)
+ int_party.c
+ (map/)
+ itemdb.c/mob.c
+ マクロ定義の修正など
+
+・スキルを少し修正
+ グリムトゥースがハイディングで使えない問題修正
+ 武器研究の命中修正を実装
+
+ skill.c
+ skill_use_id()の修正
+ pc.c
+ pc_calcstatus()で武器研究に従って命中修正
+
+----------
+//0256 by nabe
+
+・露店アイテム購入のバグ修正
+ clif.c
+ clif_vendinglist()で売り切れたアイテムは表示しないように
+
+----------
+//0255 by nabe
+
+・露店アイテム購入のバグ修正
+ vending.c
+ vending_purchasereq()でzeny,weight部分修正
+
+----------
+//0254 by nabe
+
+・露店を実装
+ vending.c/vending.h
+ 新規追加。露店メイン処理
+ skill.c
+ skill_castend_id()に露店開設スキル処理を追加
+ clif.h/clif.h
+ 露店関連パケット処理を追加
+ map.h
+ struct map_session_dataに、
+ int vender_id;
+ int vend_num;
+ char message[80];
+ struct vending vending[12];
+ を追加
+
+----------
+//0253 by 胡蝶蘭
+
+・stricmp未定義エラーがでる環境用の修正
+ エラーが出た場合、int_party.cの最初のマクロ定義のコメント化のうち、
+ どちらかを外してやり直してみると、うまくいくかも。
+ 最悪、下を有効にしたらうまくいくはず。(大文字小文字を区別するようになります)
+
+ (char/)
+ int_party.c
+ コメント化済みのマクロ定義追加
+
+----------
+//0252 by 胡蝶蘭
+
+・カードの一部実装
+ (ステータス変化全般、武器属性、スキルはすでに実装済み)
+ 防具属性、詠唱時間変化、属性攻撃耐性、種族耐性、種族追加ダメージ、
+ 属性追加ダメージ、サイズ追加ダメージ、MAXHP、MAXSP増減、使用SP変化系、
+ フェン、ドレイク、ホルン、深淵の騎士、黄金蟲、オシリスカードを実装
+
+ (db/)
+ const.txt
+ bonus用の定数追加、bonus2の定数も追加
+ item_db.txt/item_db2.txt
+ カードのスクリプト追加
+ (map/)
+ map.h
+ struct map_session_dataにhprateなど多数メンバ追加
+ script.c
+ bonus2コマンド追加
+ buildin_bonus2()追加
+ pc.c/pc.h
+ pc_bonus2()追加
+ pc_bonus()の処理追加
+ pc_calcstatus()で各種追加メンバの初期化を行うようにし、
+ hprateやsprateに従いmax_hp,max_spの調整もするように変更。
+ pc_makesavestatus()でオシリスカード修正
+ skill.c
+ skill_castfix()でcastrateに従い、詠唱時間を調整。
+ skill_castend_id()でdsprateに従い、使用SPを調整。
+ skill_castend_nodamage_id()でカード修正を追加
+ battle.c
+ battle_calc_weapon_attack()でカード修正を追加
+ battle_calc_magic_attack()でカード修正を追加
+ battle_damage()でフェンカード修正を追加
+
+・ステータス割り振りの表示上の問題修正
+ STRを上げてもATKが変わらない問題、INTを上げてもMATKが変わらない問題修正
+
+ map.h
+ struct map_session_dataにmatk1,matk2メンバ追加
+ pc.c
+ pc_calcstatus()の修正
+ clif.c
+ clif_initialstatus()の修正
+ battle.c
+ battle_calc_magic_attack()の修正
+
+
+----------
+//0251 by nabe
+
+・0250のバグ修正など
+ カートを付けずにログインまたはマップ移動した後にカートを付けると、
+ カートの中身が2倍の量に表示されてしまっていたのを修正。
+ カートのアイテム数を更新するように修正。
+ pc.h/pc.c
+ pc_iscarton()マクロを追加
+ pc_cart_additem(),pc_cart_delitem()にそれぞれ
+ sd->cart_num++;とsd->cart_num--;処理を追加
+ clif.c
+ clif_parse_LoadEndAck()で、
+ カートを付けているときのみカート情報を送信するようにした
+
+----------
+//0250 by nabe
+
+・カートOFF、チェンジカート実装。
+ (map/)
+ pc.c/pc.h
+ pc_setcart()を追加
+ script.c
+ buildin_setcart()を追加
+ スクリプトコマンド「setcart;」でカートがつく
+ clif.c/clif.h
+ clif_parse_CartOff()追加。(カートをはずす)
+ clif_parse_ChangeCart()追加。(チェンジカートのカート選択)
+ (conf/)
+ npc_town_kafra.txt
+ カートサービスを「setcart;」に置換
+
+
+----------
+//0249 by 胡蝶蘭
+
+・パーティのデータベースの矛盾を出来るだけ抑えるように。
+ 複数パーティに所属してるデータの検査、追加に失敗したときに脱退など。
+
+ (char/)
+ int_party.c
+ party_check_conflict(),party_check_conflict_sub(),
+ mapif_parse_PartyCheck()追加
+ inter.c
+ パケット長リストに0x3028追加
+ INTER鯖パケット.txt
+ パケット0x3028追加
+ (map/)
+ party.c/party.h
+ party_check_conflict()追加。
+ party_invite()で同アカウント所属チェックを行うように。
+ party_member_added(),party_send_movemap()で
+ party_check_conflict()を呼ぶように。
+ intif.c/intif.h
+ intif_party_checkconflict()追加
+
+・パーティの座標、HP通知を実装
+ 変化があれば1秒に一回送信。
+
+ map.h
+ struct map_session_dataにparty_x,~_y,~_hpの3メンバ追加
+ party.c/party.h
+ party_send_xyhp_timer_sub(),party_send_xyhp_timer(),
+ party_send_xy_clear(),party_send_hp_check()追加。
+ party_recv_movemap()でsd->party_*を初期化するように。
+ clif.c/clif.h
+ clif_sendのPARTY*フラグを有効に。
+ (PARTY,PARTY_SAMEMAP,PARTY_AREA,PARTY*_WOSの6種)
+ clif_party_xy(),clif_party_hp()追加。
+ pc.c/pc.h
+ pc_authok()でsd->party_*を初期化するように。
+ pc_walk()でパーティメンバが視界内に入ってきたときに
+ party_hpを初期化するように。
+
+・パーティのexp公平分配を実装
+ party.c/party.h
+ party_share_exp()追加
+ mob.c/mob.h
+ mob_damage()で公平分配処理追加
+
+・スキルの修正と追加実装
+ バッシュ、ピアースの命中率修正実装
+ ピアースのサイズによる回数変動実装(プレイヤーは中型と仮定)
+ バッシュ、ソニックブロウのスタン効果実装
+ ストーンカース、フロストダイバ、インベナム、
+ アスペルシオ、エンチャントポイズン、レックスデビーナ実装
+
+ skill.c
+ skill_additional_effect()追加
+ skill_castend_damage_id()該当個所修正
+ skill_castend_nodamage_id()該当個所修正
+ skill_use_id(),skill_use_pos()でスキルが使用できないときは
+ 何もしないように修正。
+ battle.c
+ battle_calc_weapon_attack()の該当個所修正
+ battle_get_dmotion(),battle_get_attack_element()修正
+ clif.c
+ clif_mob007b(),clif_mob0078でoptionなどを送るように修正
+ pc.c
+ pc_attack(),pc_walktoxy()で行動不可能なときは何もしないように。
+ mob.c
+ mob_stopattack()修正
+ mob_ai_sub_hard()で行動不能なときは何もしないように。
+
+・攻撃射程の判定追加
+ 相手が移動して届かないときは、移動パケットを送信
+
+ clif.c/clif.h
+ clif_movetoattack()追加
+ pc.c
+ pc_attack()で射程判定、届かないならclif_movetoattack()を呼ぶ。
+
+----------
+//0248 by nabe
+
+・パーティ作成時に既にパーティに所属していた場合の処理を追加
+ party.c
+ party_create()に、既にパーティに所属していた場合
+ clif_party_created(sd,2)を追加
+
+・ディスカウント、オーバーチャージを計算
+ pc.c
+ pc_modifybuyvalue()、pc_modifysellvalue()で値段を計算
+
+
+----------
+//0247 by 胡蝶蘭
+
+・パーティ実装
+ 公平分配は設定しても実際には公平分配されてない。
+ パーティスキルはまだ自分にしかかからない
+
+ (char/)
+ int_party.c/int_party.h
+ まともに実装
+ inter.c
+ パケット長リスト追加
+ INTER鯖パケット.txt
+ パーティのパケット追加
+ (map/)
+ party.c/party.h
+ 新規追加
+ map.c/map.h
+ struct map_session_dataにparty_sendedメンバ追加
+ do_init()でdo_party_init()を呼ぶ
+ map_quit()でparty_send_logout()を呼ぶ
+ intif.c/intif.h
+ パーティ関連の部分追加
+ clif.c/clif.h
+ パーティ関連の部分追加
+ clif_parse_LoadEndAck()でparty_send_movemap()を呼び出す
+ pc.c
+ pc_authok()でparty_request_info()を呼ぶようにし、
+ party_sendedを初期化するように。
+
+・詠唱妨害されたとき画面上で詠唱をやめるように修正
+ (map/)
+ skill.c
+ skill_castcancel()で詠唱中止パケ(合ってるのかな?)を送信
+
+・超遠距離攻撃だと敵が反撃してこない問題を修正
+ (map/)
+ map.h
+ struct mob_dataにmin_chaseメンバ追加(最低追跡距離)
+ mob.c
+ mob_attack()でmin_chaseを13に初期化する
+ mob_walk()でmin_chaseが13より大きいなら少しずつ引いていく
+ mob_ai_sub_hard()でmin_chaseにより追跡を判断、
+ 攻撃を受けた時にmin_chaseを彼我距離+13に設定
+
+----------
+//0246 by 胡蝶蘭
+
+・カート実装
+ map.h
+ struct map_session_dataにcart_weightなど4つメンバ追加
+ pc.c/pc.h
+ pc_cart_additem(),pc_cart_delitem(),
+ pc_cart_putitemtocart(),pc_cart_getitemfromcart()追加
+ pc_calcstatus()でカート重量や個数などの情報を計算
+ clif.c/clif.h
+ clif_cart_itemlist(),clif_cart_equiplist(),
+ clif_cart_additem(),clif_cart_delitem(),
+ clif_parse_PutItemToCart(),clif_parse_GetItemFromCart()追加
+ clif_parse_LoadEndAck()でカート情報、内容送信
+ clif_updatestatus()でSP_CARTINFOでカート情報を送れるように
+ clif_parse_MoveFromKafraToCart(),~ToKafraFromCart()追加
+ storage.c/storage.h
+ storage_additem(),storage_delitem()追加
+ storage_storageadditemfromcart,~getitemtocart()追加
+ storage_storageadd(),storage_storageget()で、
+ storage_additem(),storage_delitem()を呼ぶように変更
+
+・スキル詠唱ディレイなど実装
+ clif.c
+ clif_parse_WalkToXY()にskilltimerによる移動可否を追加
+ clif_parse_UseSkillToId(),clif_parse_UseSkillToPos()に
+ canmove_tickによる攻撃可否追加
+ skill.c/skill.h
+ skill_castcancel()を追加
+ skill_use_id(),skill_use_pos()でディレイ時間計算および、
+ canmove_tickの設定
+ battle.c
+ battle_damage()でskill_castcancel()の呼び出し追加
+
+・0245のアイテムデータベース修正の通常価格版用意
+ (db/)
+ item_db.txt
+ item_db2.txtに前のitem_db.txtの価格情報をマージしただけです。
+
+----------
+//0245 by れあ
+ また例によって、相場修正版のみです。
+・item_db2.txtの修正
+ 亀島新装備の効果を実装しました。
+ ウィザードが杖を装備できないのを修正
+ ウィザードがマジシャンハット、とんがり帽を
+ 装備できないのを修正
+----------
+//0244 by れあ
+・mob_db.txtの修正
+ 亀島モンスターのデータをいれました。
+ ただ、間違ってる部分がかなりあります。
+ Speed,Delayは適当です。
+ また、わからないのは韓国版のデータなので
+ Mdefとか異常に高い気も。
+----------
+//0242 by 胡蝶蘭
+
+・取引関連の変更と修正
+ 取引に使う変数をmmo_charstatusからmap_session_dataに移動しました
+
+ (common/)
+ mmo.h
+ struct mmo_charstatusから取引関係のメンバ削除
+ (map/)
+ map.h
+ struct map_session_dataに取引関係のメンバ追加
+ trade.c
+ 構造体の変更にあわせて修正
+ map.c
+ map_quit()で取引中ならキャンセルするようにした
+
+・カードの組み合わせ実装
+ pc.c/pc.h
+ pc_insert_card()でカードを実際に挿入する
+ clif.c/clif.h
+ clif_parse_UseCard(),clif_parse_InsertCard()追加
+ clif_use_card(),clif_insert_card()追加
+
+・一部のカード効果実装
+ スキル習得カード、ステータスボーナスカードなど。
+
+ (map/)
+ map.h
+ struct map_session_dataに装備カード検索用の変数追加
+ pc.c/pc.h
+ pc_calcstatus()でカードの処理追加
+ あるIDのカードが装備済みか検索するための関数、
+ pc_equip_card(),pc_equip_wcard(),pc_equip_dcard()を用意
+
+・重量オーバー/鷹/騎乗アイコンの表示
+ (map/)
+ pc.c/pc.h
+ pc_checkweighticon()追加、重量のアイコン処理
+ clif.c
+ clif_updatestatus()で重量送信時にpc_checkweighticon()の実行
+ clif_changeoption()で鷹と騎乗のアイコン処理
+
+・0241のアイテムデータベース修正の通常価格版用意
+ (db/)
+ item_db.txt
+ item_db2.txtに前のitem_db.txtの価格情報をマージしただけです。
+
+
+----------
+//0241 by れあ
+・アイテムデータベースの修正
+ 新頭装備のグラフィックが異なるのを修正
+ 装備の効果の実装
+ 上段・中段が間違ってたのを少し修正
+ 速報版ってことで間違え多いかも。
+ テストもあまりしてません。
+ あと、相場調整版しか用意してません。
+
+ item_db2.txt
+ 亀島にあわせて調整
+
+----------
+//0240 by nabe
+
+・取引を実装しました。
+ (common/)
+ mmo.h
+ struct mmo_charstatus に
+ int trade_partner;
+ int deal_item_index[10];
+ int deal_item_amount[10];
+ int deal_zeny;
+ short deal_locked;
+ を追加
+ (map/)
+ clif.c,clif.h
+ clif_traderequest() : 0xe5(取り引き要請受け)
+ clif_tradestart() : 0xe7(取り引き要求応答)
+ clif_tradeadditem() : 0xe9(相手方からのアイテム追加)
+ clif_tradeitemok() : 0xea(アイテム追加成功)
+ clif_tradedeal_lock() : 0xec(ok押し)
+ clif_tradecancelled() : 0xee(取り引きキャンセル)
+ clif_tradecompleted() : 0xf0(取り引き完了)
+ を追加。
+ trade.c,trade.h
+ trade_traderequest() : 取引要請を相手に送る
+ trade_tradeack() : 取引要請
+ trade_tradeadditem() : アイテム追加
+ trade_tradeok() : アイテム追加完了(ok押し)
+ trade_tradecancel() : 取引キャンセル
+ trade_tradecommit() : 取引許諾(trade押し)
+ を実装。それぞれclif.c::clif_parse_Trade*から呼ばれる。
+
+
+----------
+//0238 by れあ
+
+・速度変更に関して少し修正
+ atcommand.c
+ 速度変更の部分を少し修正
+ これで一応動くみたい?
+ pc.c
+ ついでにですが
+ 速度上昇で歩行速度が上がるようにした。
+ 一応動くみたいですが適当なので
+ おかしなところがあればお願いします。
+
+----------
+//0236 by nabe
+
+・スクリプトでmenuで飛んだ先で直ぐmenuを書くと誤動作するバグを修正しました。
+ script.c
+ goto動作の後のRERUNLINEに対処するため、
+ goto,menuで飛んだ後には、st.state==GOTOでrerun_posを更新。
+
+
+----------
+//0233 by nabe
+
+・アイテムを装備する際の装備判定を追加しました。
+ pc.c
+ pc_equipitem()に装備判定(性別判定、装備LV判定、職業判定)追加
+
+・重量判定スクリプトコマンドを追加しました。
+ if (checkweight(アイテムID,アイテム数量))
+ でそのアイテム×数量を取得できるかどうか判定できます。
+ script.c
+ buildin_checkweight()を追加
+
+・スクリプト詰め合わせをathena dev-2.1.1用に移植しました。
+ map_athena1.cnf
+ npc_event_*.txt イベントNPC
+ npc_job_*.txt 転職NPC
+ npc_mob_job.txt 転職用モンスター
+ npc_town_*.txt 町NPC
+
+
+----------
+//0232 by 胡蝶蘭
+
+・装備ボーナスが実装されました
+ ボーナスに使うスクリプト(bonus,skill)を実装
+ スクリプトはI-Athenaのデータを使ってコンバートしました。
+ (まだカードには対応していません)
+
+ (common/)
+ mmo.h
+ struct skillにflagメンバ追加(カードスキルかどうか)
+ (map/)
+ map.h
+ struct map_session_dataにatk_eleなどのメンバ追加
+ enumでSP_ATKELEMENTなど追加
+ pc.c
+ pc_bonus()の実装、pc_skill()追加
+ script.c
+ buildin_skill()の追加
+ buildin_bonus()の修正(const.txtの定数が使えるように)
+ clif.c
+ clif_skillinfoblock()の修正(カードスキルは上げられない)
+ (db/)
+ const.txt
+ bonusに使うための定数追加
+ item_db.txt
+ 標準のデータに装備スクリプトを追加したもの
+ item_db2.txt
+ 0213で相場調整されたデータに装備スクリプトを追加したもの
+
+・詠唱関係のバグが修正されました
+ (map/)
+ skill.c
+ skill_use_id(),skill_use_pos()を修正
+ (db/)
+ cast_db.txt
+ 少し追加(ブリッツビートなど)
+
+・攻撃属性が適用されるようになりました
+・星のかけらの修正が適用されるようになりました
+ map.h
+ struct map_session_dataにstarメンバ追加
+ pc.c
+ pc_calcstatus()で属性初期化
+ battle.c
+ battle_get_element(),battle_get_attack_element()修正
+ battle_calc_weapon_damage()の該当個所修正
+
+・杖装備時にMATK+15%が適用されるようになりました
+ battle.c
+ battle_calc_magic_damage()の該当個所修正
+
+・製造武器のキャラクター名が正しく表示されるようになりました
+
+ 原理としては、map鯖内のキャラクタ名データベースを検索して、
+ 存在すれば即返信、存在しなければchar鯖に解決要求を出す。
+ このとき、名前を要求してきたクライアントのIDをデータベースに登録する。
+ char鯖から名前データがくると、対応するデータベースに名前をセットし、
+ 要求してきたクライアントに名前を返信する。
+ 未解決の同じキャラID解決を複数のクライアントが要求してきた場合、
+ 最後に要求してきたクライアントにしか返信しないが、
+ 返信されなかったクライアントは数秒後に再び解決要求を送ってくる
+ (そしてそのときはmap鯖から即返信される)ので大きな問題はない。
+
+ パケット0x2b08,0x2b09でmap鯖とchar鯖が通信してます。
+
+ (char/)
+ char.h
+ UNKNOWN_CHAR_NAME定義(キャラデータが無いときに返される名前)
+ char2.c
+ parse_frommap()にパケット0x2b08の処理を追加
+
+ (map/)
+ chrif.c/chif.h
+ chrif_searchcharid()追加
+ chrif_parse()で0x2b09の処理追加
+ map.c
+ データベース charid_db 宣言
+ struct charid2nick宣言。nickは名前、
+ req_idは0で名前解決済み、0以外で未解決で解決待ちのブロックID
+ map_addchariddb()追加。データベースへ名前登録、要求に返信。
+ map_reqchariddb()追加。要求があったことをデータベースへ追加。
+ map_charid2nick()でデータベースの検索
+ do_init()で charid_db の初期化を追加
+ clif.c/clif.h
+ clif_parse_SolveCharName(),clif_solved_charname()追加
+
+
+----------
+//0231 by nabe
+
+・スクリプトで mes "$charaname"; 等と書くとキャラの名前をしゃべる機能を追加。
+ script.c
+ buildin_mes()内で
+ mes内部の$charanameをキャラの名前に置換する処理を追加。
+ #同様にして変数の値などをmes内部で表示するようにすることも
+ #できますが、これについては未実装です・・・。
+ #とりあえず
+ # mes Global_Val;
+ #のように直接書くことで対処してください。
+
+・敵に攻撃されたときにmap鯖が落ちることがあるのを修正。
+ battle.c
+ battle_calc_weapon_attack()の
+ ディバインプロテクションのスキルチェック部分、
+ pc_checkskill(sd,22)を、
+ pc_checkskill(tsd,22)に。
+
+----------
+//0230 by nabe
+
+・回避率増加スキルをステータスに反映。
+ pc.c
+ 0228でのpc_calcstatus()の回避率増加分を元に戻しfleeを増加。
+ battle.c
+ battle_calc_weapon_attack()のhitrate計算で回避率保証を計算。
+・グローバル変数を実装。
+ '@'もしくは'l'で始まらない変数名は、全てグローバル変数とみなされます。
+ mmo.h
+ struct mmo_charstatus に
+ int global_reg_num;
+ struct global_reg global_reg[GLOBAL_REG_NUM];
+ を追加。
+ pc.c
+ pc_readglobalreg(),pc_setglobalreg()を追加。
+ script.c
+ get_val(),buildin_input(),buildin_set()に
+ グローバル変数のための処理を追加。
+ char2.c
+ mmo_char_tostr(),mmo_char_fromstr()に
+ グローバル変数のための処理を追加。
+
+----------
+//0229 by 胡蝶蘭
+
+・一部スキルの実装/修正
+ ディバインプロテクション、デーモンベイン、ビーストベイン実装
+ エナジーコート修正(魔法による攻撃にはスキルが働かないように修正)
+ 武器攻撃系スキル修正(エフェクトを通常攻撃からスキルに変更)
+
+ battle.c
+ battle_addmastery()でベイン系追加
+ battle_calc_damage()でエナジーコート修正
+ skill.c
+ skill_castend_damage_id()の武器攻撃系スキルの部分を修正
+
+・敵攻撃計算をPCのものと一本化
+ これでPCvsPC、PCvsMOB、MOBvsPC、MOBvsMOB(!?)を1つの関数で計算できます
+
+ battle.c/battle.h
+ battle_calc_weapon_attack()を修正
+ battle_calc_weapon_attack_pc(),~mob()を削除
+ mob.c
+ mob_attack()で計算にbattle_calc_weapon_attack()を使うように修正
+
+・詠唱時間データがない場合のデフォルトの詠唱時間を0に変更
+ 今までは1秒にしてましたが、バッシュとかがおかしくなるので。
+ (バッシュとかのデータを用意すればこうしなくても直るんですが)
+
+ pc.c
+ pc_readdb()で1000msをセットするのを止めた
+
+・遠距離攻撃してこないバグ、その他を修正
+ mob.c
+ mob_attack()の射程を修正し忘れていた
+ mob_ai_sub_hard()で射程距離外の時、無移動の敵は
+ ターゲットを外すようにした
+
+
+----------
+//0228 by nabe
+
+・ダブルアタックのSkillIDを修正。
+ battle.c
+ battle_calc_weapon_attack_pc()で
+ pc_checkskill(sd,49) -> pc_checkskill(sd,48)に。
+・回避率向上を陽に表さない
+ pc.c
+ pc_calcstatus()でのfleeの回避率向上分を削除し、
+ mob.c
+ mob_attack()のhitrate計算で回避率向上を計算。
+・盗蟲、盗蟲雌、盗蟲雄を正常化。
+ npc_monster3J.txt
+ mob_db.txtに合わせて、たぶん正しいと思われるIDに修正。
+ 盗蟲 1006 -> 1051
+ 盗蟲雌 1017 -> 1053
+ 盗蟲雄 1021 -> 1054
+・デバッグメッセージの消し忘れ(?)を削除。
+ pc.c
+ printf("pc.c 63 clif_clearchar_area\n");をコメントアウト
+
+----------
+//0227 by 胡蝶蘭
+
+・一部のスキル効果が実装されました
+ HP回復向上、SP回復向上、マグニフィカート、
+ ハイディング、クローキング、死んだふり、応急手当
+
+ map.h
+ struct map_session_data に inchealtick メンバ追加
+ pc.c
+ pc_spheal()でマグニフィカート処理追加
+ pc_natual_heal_sub()で回復向上スキル処理追加
+ pc_authok()でinchealtickを初期化するように変更
+ pc_walk()でincheaktickを再設定するように変更
+ pc_walk()でクローキングの終了条件を調査するように変更
+ pc_walktoxy()で状態によって移動不可能にした
+ skill.c/skill.h
+ skill_status_change_start(),~timer(),~end()に処理追加
+ skill_check_cloaking()追加、クローキングの終了条件を検査
+ battle.c/battle.h
+ battle_stopattack()追加
+ battle_calc_weapon_attack()で攻撃を止める処理追加
+ mob.c
+ mob_ai_sub_hard()で攻撃を止める処理追加
+
+・通常攻撃処理、対MOB、対PCを共用に。
+ pc.c
+ pc_attack_mob(),pc_attack_pc()削除
+ pc_attack()に攻撃処理追加
+
+・モンスターの行動の一部実装
+ アクティヴ、無反応、移動しない、遠距離攻撃一部
+
+ mob.c
+ mob_ai_sub_hard()に行動追加
+ mob_ai_sub_hard_activesearch()追加、近くのPCへの策敵
+
+・オーバートラストの増加倍率が100倍になっているバグが修正されました
+ battle.c
+ battle_calc_weapon_attack()で、該当個所を修正
+
+
+----------
+//0226 by 胡蝶蘭
+
+やっぱりテストはあんまりしていません
+
+・一部のスキル効果が実装されました
+ 速度増加、エンジェラス、キュアー
+ インポシティオマヌス、サフラギウム、リカバリー、グロリア
+ ふくろうの目、ワシの目、集中力向上、回避率向上、解毒
+ 所持量増加、ラウドボイス、アドレナリンラッシュ、オーバートラスト
+ ウェポンパーフェクション、マキシマイズパワー、2HQ
+
+ (map/)
+ map.h
+ struct map_session_dataにwatk2,def2など追加
+ pc.c
+ pc_calcstatus()にスキル修正追加
+ atk2なども送信するように変更
+ battle.c/battle.h
+ battle_get_def2()など多数追加
+ battle_calc_weapon_damage()で敵減算防御の所得を
+ battle_get_def2()に変更
+ battle_calc_magic_damage()で敵減算魔法防御の所得を
+ battle_get_mdef2()に変更
+ battle_calc_weapon_damage()でスキル修正を追加
+ skill.c/skill.h
+ skill_use_nodamage_id()の該当個所追加
+ skill_status_change_start()の該当個所追加
+ clif.c
+ clif_updatestatus()のatk2などの処理追加
+ clif_initialstatus()でatk2などの扱い変更、aspdなど送信追加
+
+・精錬ダメージ修正/精錬防御修正が適用されました
+ (map/)
+ pc.c
+ pc_calcstatus()でwatk2とdefの追加計算追加
+ battle.c
+ battle_calc_weapon_damage()でwatk2をダメージに追加
+
+・inter鯖のパケット解析部の致命的な問題が修正されました
+ TCP/IPプログラムでやってはいけないことをそのままやってました(汗
+ inter鯖のパケット長データをinter.cに持つように修正されました。
+
+ (char/)
+ inter.c/inter.h
+ パケット長データ inter_*_packet_length[] を追加
+ パケット長チェック inter_check_length() を追加
+ mapif_parse_*()でRFIFOSKIPをなしに変更
+ int_storage.c/int_storage.h
+ mapif_parse_*()でRFIFOSKIPをなしに変更
+ int_party.c/int_guild.c
+ 仕様変更に対応させた変更
+ INTER鯖パケット.txt
+ パケット長リスト追加
+
+・ちょっとした修正
+ (char/)
+ inter.h
+ inter_cfgNameを"conf/inter_athena.cnf"に修正
+ char2.c
+ char.exe第2引数省略時、inter_cfgNameを使うように修正
+ (db/)
+ cast_db.txt
+ ホーリーライトの詠唱時間追加(ディレイは適当)
+ 詳しい人追加求む
+
+----------
+//0225 by 胡蝶蘭
+
+なんかかなり弄りましたが相変わらずテストはあんまりしてません。
+
+・スキル使用時の変数を変更
+ よく見たら最初から用意されてましたね。
+
+ map.h
+ struct map_session_dataのcast_*を削除
+ skill.c
+ cast_*の変数をskill*に変更。
+
+・ステータス異常スキルの処理を追加(効果は未実装)
+ 見かけ上、ステータス異常に掛かったりとかだけ。
+ 効果はまだなし。
+
+ skill.c/skill.h
+ skill_status_change_start(),~end(),~timer(),~clear()追加。
+ それぞれステータス異常の開始、終了、タイマ処理、全消去。
+ map.c/map.h
+ map_quit()でskill_status_change_clear()を呼ぶようにした。
+ struct map_session_dataにsc_data,sc_count追加。
+ struct mob_dataにsc_data,sc_count,option,opt1,opt2追加。
+ pc.c
+ pc_authok()でsc_data,sc_countを初期化するようにした。
+ pc_setoptionでclif_changeoption()の引数変更。
+ pc_damage()で死亡時にskill_status_change_clear()を呼ぶように。
+ mob.c
+ mob_spawn()でsc_data,sc_countを初期化するようにした。
+ mob_attack()でbattle_calc_damage()を呼ぶようにした。
+ mob_damage()で死亡時にskill_status_change_clear()を呼ぶように。
+ battle.c/battle.h
+ battle_get_*()たくさん追加。
+ battle_calc_damage()追加。最終的なダメージ計算用。
+ battle_calc_magic_attack(),battle_calc_weapon_attack()で
+ battle_calc_damage()を呼ぶようにした。
+ clif.h/clif.c
+ clif_status_change()追加。ステータス異常アイコン表示用。
+ clif_changeoption()の引数変更。
+ atcommand.c
+ clif_changeoption()を呼んでいる2ヶ所で引数変更。
+ @dieでskill_status_change_clear()を呼ぶように。
+
+・マグヌスエクソシズムの習得条件が間違っているのを修正。
+ db/skill_tree.txt
+ 該当個所修正。(レックスエーテルナの必要Lvを1に)
+
+・アクティブな敵は攻撃するとき時々ターゲットが変わるようになりました
+ mob.c
+ mob_ai_sub_hard()の攻撃されたか確認する部分に
+ アクティブなら25%の確率でターゲットが変わるように変更。
+
+・一部のスキル効果が実装されました
+ キリエエレイソン、エナジーコート、レックスエーテルナ、
+ ホーリーライト、リザレクション、ターンアンデッド、モンスター情報
+
+ skill.c/skill.h
+ skill_castend_nodamage_id()にスキルの処理を追加。
+ skill_castend_*_id()の引数を変更
+ battle.c
+ battle_calc_damage()にスキルの処理を追加。
+ battle_damage(),battle_heal()の引数変更
+ battle_calc_weapon_damage(),battle_calc_magic_damage()引数変更
+ clif.c/clif.h
+ clif_skill_estimation()追加。モンスター情報送信用
+ pc.c
+ battle_calc_weapon_damage()呼び出しの引数変更
+
+・storage.txtが無い場合inter鯖が強制終了する仕様を変更しました
+ (char/)
+ int_storage.c
+ inter_storage_init()でファイルが読めないとexitしてたのを修正
+
+
+----------
+//0224
+・2-2次職のスキルをツリーに追加しました(実装はまだです)
+ (db/)
+ skill_db.txt
+ skill_tree.txt
+
+
+----------
+//0223 by 胡蝶蘭
+・カプラ倉庫をinter鯖に対応させました
+ いままでのstorage.txtはそのまま使えます。
+ inter鯖用の設定ファイルとしてconf/inter_athena.cnfを使います。
+ (設定ファイルはchar.exeの第2引数で他のファイルを指定できます)
+
+ カプラ倉庫のinter鯖実装の概要
+
+ inter鯖はstorage.txtの全データを持つ。map鯖はアカウントが要求するまで
+ そのアカウントの倉庫データを持たない。クライアントから倉庫を開く要求が
+ あったとき、map鯖は対応するアカウントの倉庫データをinter鯖に要求する。
+ inter鯖からデータが届くとクライアントに倉庫データを送る。
+ 倉庫の出し入れはクライアントとmap鯖間の通信だけで行われる。
+ クライアントが倉庫を閉じるか終了すると、map鯖は該当アカウントの
+ 倉庫データをinter鯖に送る。このときinter鯖の応答を待たずにクライアントに
+ 倉庫クローズを送る。inter鯖は倉庫データを受け取ると、
+ 全員分のデータをファイルに保存して、map鯖に成功ステータスを返す。
+ map鯖は成功ステータスを無視する。(デバッグ用に画面に出力するだけ)
+ inter鯖終了時にも倉庫データをファイルに保存する。
+
+ map鯖でaccount2storageで新しい倉庫データを作るとき、
+ すでに閉じられている倉庫データのメモリを使いまわしたほうがメモリが
+ 節約できるかも?(これは実装していません)
+
+ (common/)
+ mmo.h
+ struct storage を map/storage.h から移動。
+ inter鯖とmap鯖両方で使用するため。
+ (char/)
+ char2.c
+ do_final()を作成、終了時にmmo_char_sync()以外にinter_save()を
+ 呼ぶようにした(これでinter_*_save()は全部呼ばれます)
+ inter_init()をchar.exeの第2引数もしくは"conf/inter.cnf"で
+ 呼ぶようにした(athena.shにinter鯖コンフィグファイルを指定できます)
+ inter.c/inter.h
+ inter_storage_init(),inter_storage_save(),
+ inter_storage_parse_frommap()を呼ぶように。
+ inter_init()にコンフィグファイル名の引数を付けた。
+ inter_config_read()追加、コンフィグファイルから
+ 倉庫とパーティー、ギルドのファイル名を読み込みます。
+ int_storage.c/int_storage.h
+ 新規追加。倉庫部分のinter鯖機能。
+ int_party.h/int_party.c/int_guild.h/int_guild.c/
+ ファイル名変数の宣言追加
+ INTER鯖パケット.txt
+ 倉庫パケットの解説追加
+
+ (map/)
+ storage.h/storage.c
+ storage_fromstr(),storage_tostr()をchar/int_storage.cに移動。
+ 同じくdo_init,do_finalでのファイル処理も移動。
+ do_final()は処理なし、do_init()は変数初期化のみに変更。
+ storage_storageopen()では単にintif_request_storage()を呼ぶだけに。
+ storage_storageclose()にintif_send_storage()を追加
+ storage_storage_quitsave()追加。クライアント終了時に
+ カプラ倉庫が開いていればintif_send_storage()を呼ぶ関数。
+ intif.h/intif.c
+ intif_parse_LoadStorage(),intif_parse_SaveStorage(),
+ intif_send_storage(),intif_request_storage()追加
+ map.c
+ map_quit()でstorage_storage_quitsave()を呼ぶように。
+
+ (conf/)
+ inter_athena.cnf
+ 新規追加。inter鯖用のコンフィグレーションファイル
+
+
+----------
+//0221 by 胡蝶蘭
+
+・スキルターゲットのIDが正しく所得できない問題修正
+ clif.c
+ clif_parse_UseSkillToId()でIDをWORDとして扱ってたのをLONGに修正
+
+・スキル詠唱時間と属性表、および魔法系スキルの属性修正実装
+ 属性ダメージ修正は battle_attr_fix() で計算します。
+ atk_elemは属性そのまま、def_elemは(属性lv*20+属性)です。
+ 詠唱時間はskill.cのCASTFIXの値を変えることで倍率を調整できます
+
+ pc.c
+ pc_readdb()でcast_db.txtとattr_fix.txtの読み込み追加
+ skill.c/skill.h
+ struct skill_db にcast,delay追加、それらのアクセサも追加
+ スキル詠唱時間を skill_get_cast() で所得するようにした
+ battle.c/battle.h
+ attr_fix_table定義
+ battle_attr_fix()追加、属性修正を計算する
+ 属性系アクセサ(battle_get_element()など)を追加
+ battle_calc_magic_damage()に属性修正を追加
+ cast_db.txt
+ 新規追加。詠唱時間とディレイのデータベース
+ 全然足りないので、誰か追加希望。
+ attr_fix.txt
+ 新規追加。属性修正テーブル
+
+・ヒールの実装
+ clif.c/clif.h
+ clif_skill_nodamage()追加、支援系や回復のエフェクト
+ skill.c/skill.h
+ skill_castend_damage_id()、skill_castend_nodamage_id()追加、
+ 攻撃系と支援/回復系で関数を分けた
+ ヒール計算マクロ skill_calc_heal() 追加
+ battle.c
+ battle_calc_magic_damage()でヒールのダメージ計算追加
+
+
+----------
+//0220 by れあ
+
+0216の修正
+HITの計算がおかしかったので修正してみました。
+間違ってたらごめんなさい。
+
+・battle.c
+ 256行目の
+ hitrate=battle_get_hit(&sd->bl) - battle_get_flee(&sd->bl) + 80;
+ がたぶん、自分のHITと自分のFLEEで計算してる気がするので
+ hitrate=battle_get_hit(&sd->bl) - battle_get_flee(target) + 80;
+ に修正しました。
+
+
+----------
+//0218 by 胡蝶蘭
+
+実際に分散させてテストしていなかったり。
+
+・map鯖分散処理用にinter鯖機能をつけてみる(今後のための拡張)
+ char鯖にinter鯖を寄生させました。複数のmap鯖間の通信に利用します。
+ map鯖を分散して処理できるようにするための機能です。
+ 今後partyやguild実装時にきっと役にたってくれるかと。
+
+ 倉庫の実装もinter鯖に移動すべきかもしれません。
+ どのキャラクターがどのmap鯖にいるか検索する機能もいるかも。
+
+ 使うパケットのIDは以下のようになります
+ map鯖=>inter鯖はパケット0x3000〜
+ inter鯖=>map鯖はパケット0x3800〜
+ パケットを作った場合は、INTER鯖パケット.txtに書いてください
+
+ この機能によるメリット
+ map鯖分散にも対応できる
+ この機能によるデメリット
+ inter鯖経由の全ての命令の動作速度が落ちる
+ (一回inter鯖まで渡すため)
+ 鯖とクライアントを同じPCで使っているとつらいかも
+
+ (char/)
+ char2.c/char.h
+ mapif_sendall()追加(全MAP鯖にパケットを送る)
+ mapif_send()追加(特定MAP鯖に送る:生存判定付き)
+ parse_frommap()でinter_parse_frommap()を呼ぶようにした
+ (inter鯖のmap鯖解析部をchar鯖に寄生させたことになる)
+ inter.h/inter.c
+ 新規追加。inter鯖の中核。
+ inter_parse_frommapでMAP鯖からのパケットを解析します。
+ int_party.h/int_party.c/int_guild.h/int_guild.c
+ 新規追加。今後のための予約。パーティやギルド機能用
+ initでデータを読んで、saveで保存すべき?
+ saveはまだ呼ばれない。parseでパケット解析。
+ common/mmo.hあたりでパーティーやギルドの構造体を
+ 定義する必要があると思われる。
+ INTER鯖パケット.txt
+ パケットのリスト
+
+ (map/)
+ intif.h/intif.c
+ inter鯖と通信する部分。
+ inter_parse()でinter鯖からのパケットを解析します。
+ inter鯖へデータを送るときはinter_fdを使います。
+ chrif.h/chrif.c
+ chrif_parse()でinter_parse()を呼ぶようにした
+ (intif.cのinter鯖解析部をchar鯖解析部に寄生させたことになる)
+
+・@kamiコマンドをinter鯖経由に変更
+ 原理としては次のような感じです
+ クライアント=>map鯖=>inter鯖=>全map鯖=>全クライアント
+
+ (char/)
+ inter.c
+ mapif_GMmessage()追加
+ (map/)
+ intif.h/intif.c
+ intif_GMmessage()追加
+ intif_parseでGMメッセージの処理を追加
+ clif.c/clif.h
+ clif_GMmessage()の引数を変更
+ atcommand.c
+ @kami部分でintif_GMmessage()を呼ぶようにした
+
+・Wisをinter鯖経由に変更
+ 原理としては次のような感じです
+
+ 送り主クライアント=>送り主map鯖=>inter鯖=>全マップ鯖=>(分岐A)
+ [分岐A]
+ 1.相手の人いるmap鯖=>相手のクライアント
+ 〃 =>inter鯖=>送り主map鯖=>送り主クライアント
+ 2.相手のいないmap鯖=>inter鯖(分岐B)
+ [分岐B]
+ 1.全map鯖が応答したinter鯖 =>送り主map鯖=>送り主クライアント
+ 2.(全部は応答してないときは、全map鯖の応答を待つ)
+
+ ものすごい複雑になってますね。
+
+ (char/)
+ inter.c
+ struct WisList 定義(Wisデータのリンクリスト)
+ add_wislist(),del_wislist(),search_wislist(),
+ check_ttl_wislist()追加,リンクリストを扱う関数群
+ mapif_wis_message(),mapif_wis_end()追加
+ (map/)
+ intif.h/intif.c
+ intif_wis_message(),intif_wis_end()追加
+ intif_parse_WisMessage()追加,intif_parse()から呼ばれるように
+ clif.c/clif.h
+ clif_wis_message(),clif_wis_end()追加
+ clif_parse_Wis()を変更,intif_wis_message()を呼ぶようにした
+
+・スキル使用時のヒット数/消費SP所得のバグ修正
+ skill.c
+ skill_get_sp(),skill_get_num()で参照する配列インデックスをlv-1にした
+
+
+----------
+//0216 by 胡蝶蘭
+
+いつもどおりテストほとんどしてないので、バグ大量かも。
+
+・0213の修正?のよくわからないところ修正
+ itemdb.c
+ コンパイルが通らないのでitemdb_equipointの引数リスト変更
+
+・Athena dev 2.1.1の適用
+ dev-2.1.1で適用された修正を適用しました
+
+ timer.c
+ 2.1.1のものと差し替え
+ script.c
+ C_NE: の修正の適用
+ README
+ 最後の文章を2.1.1のものに差し替え
+
+・スキルデータベースの修正
+ 一部の消費SPやヒット数などを修正。
+
+ skill_db.txt
+ 該当個所の修正
+
+・スキル攻撃の実装変更&追加実装
+ バッシュ、メマーナイト、ダブルストレイフィング、ピアース
+ スピアブーメラン、スピアスタブ、ボーリングバッシュ
+ ソニックブロー、グリムトゥース などの実装変更
+
+ ナパームビート、ソウルストライク、
+ ファイヤーボルト、コールドボルト、ライトニングボルト、アーススパイク、
+ ユピテルサンダー などを追加実装
+ (全て、範囲攻撃やステータス異常などは未実装)
+
+ pc.c/pc.h
+ 0213の変更をなかったことにした
+ pc_attack_mob()の修正、計算はbattle_calc_weapon_attack()に任せ、
+ その計算結果を適用するだけに変更
+ clif.c/clif.h
+ clif_skill_fail(),clif_skill_damage(),clif_skill_damage2()追加
+ それぞれ使用失敗、使用エフェクト、吹き飛ばし付き使用エフェクト
+ skill.c/skill.h
+ 0213の変更をなかったことにした(ダメージ倍率計算がおかしい)
+ skill_castend_id()にSP/Zeny確認と消費部分を追加、
+ 種類別に処理を追加。
+ battle.c/battle.h
+ 新規追加
+ 武器攻撃計算用にbattle_calc_weapon_attack(),
+ 魔法攻撃計算用にbattle_calc_magic_attack()を用意
+ (双方とも、MOBとPC両方計算可能なはず)
+ ファイル増やしすぎという意見も…(汗)
+
+
+----------
+//0214 by れあ
+・ダブルアタックがおかしかったところを修正。
+・スキルの一部実装
+ バッシュ・メマーナイト・ダブルストレイフィング・ピアース
+ スピアブーメラン・スピアスタブ・ボーリングバッシュ
+ ソニックブロー・グリムトゥースなどです。
+
+ 適当なのでどこか、不具合があるかもしれません。
+ あと、テストもあまりしてませんのでおかしいところがあったら修正をお願いします。
+ 他にも問題があったら手直しをお願いします。
+ 変更内容は以下の通りです。
+
+ clif.c,clif.h
+ clif_skill_damage()を追加しました。
+
+ pc.c,pc.h
+ pc_attack_mob()の引数を一つ追加。
+ ダブルアタックがおかしかったので正常に動作するように修正。
+
+ skill.c
+ 一部スキルの実装をしてみました。
+
+
+----------
+//0213 by れあ
+・0208の@コマンドで少し修正
+ atcommand.c
+ @itemで個数指定が無い場合、入手個数を1個にするようにした。
+ @itemでIDの指定が無い場合、アイテムを入手してたことになって
+ いたのを修正
+ itemdb.c
+ item_db.txtでSellの項目を店売りの値段としてみた。
+ item_db2.txt
+ 試しにカードやレアアイテムの店売り価格を値段を本鯖の相場にし
+ てみたもの。使用する場合はitem_db.txtと差し替えてください。
+
+
+----------
+//0208 by nabe
+
+・@コマンド実装。
+ atcommand.h,atcommand.c
+ ほぼI-Athenaの@コマンド相当ですが、@GMとPVPは未実装です。
+ help.txtも同梱しています。
+ GM(アカウントID=704554〜704583)専用にするには、
+ atcommand.cの該当部分のコメントアウトを解除して下さい。
+ clif.h,clif.c
+ clif_displaymessage()
+ clif_GMmessage()
+ clif_heal()
+ clif_resurrection()
+ clif_pvpon()
+ clif_pvpset()
+ clif_refine()
+ を追加しました。
+ clif_parse_GlobalMessage()内でatcommand()を呼んでいます。
+
+・ちょっとだけ修正。
+ script.c
+ {buildin_openstorage,"openstorage","s"},
+ から
+ {buildin_openstorage,"openstorage",""},
+ に修正しました。
+
+
+----------
+//0206 by 胡蝶蘭
+・スキルツリー/スキル使用機構の実装
+ mmo.h
+ MAX_SKILLを増やした
+ char2.c
+ mmo_char_fromstr()
+ mmo_charstatusのskillのインデックスにスキル番号を使うようにした
+ =>スキルの検索高速化のため(かわりにメモリ使用量が増える)
+ pc.h/pc.c
+ pc_skillup(),pc_calc_skilltree()追加
+ pc_checkskill()変更(インデックスをスキル番号に)
+ pc_readdb()でskill_db.txtも読むようにした
+ pc_authok()でcast_timerを初期化するようにした
+ pc_calcstatus()でpc_calc_skilltree()とclif_skillinfoblock()を
+ 呼ぶようにした
+ clif.c/clif.h
+ clif_skillinfoblock(),clif_skillcasting(),
+ clif_skillup()を追加
+ clif_parse_SkillUp(),clif_parse_UseSkillToId(),
+ clif_parse_UseSkillToPos()を実装
+ skill.h/skill.c
+ ファイル追加(map/)
+ map.h
+ struct map_session_dataにcast_*を追加
+ skill_db.txt
+ ファイル追加(db/)
+ (I-Athena0200のskill_info2.txtをコンバートしたもの)
+ (スキル使用部分開発者向け情報)
+ スキルの効果を実装する場所はskill.cの
+ skill_castend_id(),skill_castend_pos()です。
+ ターゲットや使用スキルは sd->cast_* から得ます
+ スキルデータベースへは skill_get_* でアクセスしてください
+ 今後、キャスティングタイムもデータベースに入れる予定
+
+----------
+//0205 by nabe
+
+・storage.cのバグフィクス。
+・倉庫データを、マップ鯖起動時に読み、マップ鯖終了時に書くように変更。
+ storage.h,storage.c
+ storage_init()をdo_init_storage()に改名。
+ storage_save()をdo_final_storage()に改名。
+ fcloseを忘れていたのを追加。
+ map.c
+ #include "storage.h"を追加。
+ do_final()にdo_final_storage()を追加。
+ do_init()にdo_init_storage()を追加。
+
+----------
+
+//0203(unofficial) by なみ
+
+item_db.txtの書き換えのみです。
+
+・アイテムの回復量を追加/変更
+ 赤ポーション HP 30- 44
+ 紅ポーション HP 70- 89
+ 黄色いポーション HP 175-234
+ 白いポーション HP 350-429
+ 青いポーション SP 40- 99
+ 赤いハーブ HP 12- 19
+ 黄色いハーブ HP 21- 29
+ 白いハーブ HP 80-111
+ 青いハーブ SP 15- 44
+ リンゴ HP 12- 15
+ バナナ HP 11- 16
+ ブドウ SP 10- 24
+ いも HP 11- 15
+ にく HP 70- 99
+ ハチの蜜 HP 72- 97 / SP 20- 59
+ ミルク HP 25- 34
+ キャンディ HP 31- 74
+ スティックキャンディ HP 46-109
+ リンゴジュース ※ HP 28- 32
+ バナナジュース HP 27- 33
+ ブドウジュース SP 15- 39
+ ニンジンジュース ※ HP 29- 32
+ カボチャ HP 14
+ ペットフード HP 53- 83
+ よく焼いたクッキー HP 80-177
+ ひとくちケーキー HP 251-359
+ ひなあられ HP 175-234
+ 菱餅 HP 350-429
+ レッドスリムポーション ※ HP 30- 44
+ イエロスリムポーション ※ HP 175-234
+ ホワイトスリムポーション ※ HP 350-429
+ 現在のAthenaではVITやスキルによるボーナスは加味されません。
+ (適用する場合はscript.c内のbuildin_heal関数あたりにに手を加える必要あり)
+ なお、※付のアイテムのデータは適当です。
+・古いカード帖を実装(UseScript)
+・その他修正
+ ひなあられ 重量なし→重量0.1に修正
+ 菱餅 重量なし→重量0.1に修正
+ バルムン 重量0.1S4片手剣→重量100S0両手剣に修正
+ なお、Sellの項目はあるだけ無駄っぽいので全部消しました。
+
+----------
+
+//0202 by nabe
+
+・カプラ倉庫の「同一アカウントなのに共有できないバグ」を改良しました。
+ 各キャラに倉庫データを持たせるのは無駄が多い気がするので、
+ アカウントIDで管理するように仕様を変更しました。
+ ついでに、倉庫データは全てstorage.cでまかない、
+ char鯖は関与しないようにしました。
+ これに伴い、char_athena.cnf,mmo.h,char2.cは元に戻しました。
+ また、倉庫ファイル名は“storage.txt”に固定しています。
+
+ 改変、追加したのは次のファイルです。
+ map/storage.h,
+ map/storage.c,
+ map/clif.h,//引数変更だけ
+ map/clif.c,//引数変更だけ
+ conf/char_athena.cnf,//元に戻しただけ
+ common/mmo.h,//元に戻しただけ
+ char/char2.c,//元に戻しただけ
+ map/itemdb.h,//itemdb_equippoint()引数宣言変更だけ
+ map/itemdb.c,//itemdb_equippoint()引数宣言変更だけ
+ map/pc.c,//itemdb_equippoint()引数宣言変更だけ
+
+----------
+
+//0201 by nabe
+
+・カプラ倉庫を実装しました。
+
+ スクリプトから呼び出すには、スクリプト内で
+ openstorage;
+ としてください。
+ サンプルとしてnpc_kafraJ.txtを付けてあります。
+ 併せてnpc_script3J.txtの該当部分も改変しました。
+
+ char_athena.cnfの
+ stor_txt:
+ で倉庫ファイル名を指定しています。
+
+ 改変、追加したのは次のファイルです。
+ map/Makefile,
+ map/storage.c,
+ map/storage.h,
+ map/clif.c,
+ map/clif.h,
+ map/script.c,
+ char/char2.c,
+ common/mmo,h
+ 詳しくは、上記ファイルのコメントなどを参考にしてください。
+
+・カプラ倉庫実装に伴い、map_athena1.cnfを少し書き換えました。
+
+・全てのコメント文をEUCからSJISに変換しました。
+
+----------
+
+ Athena Dev. v2.1.1 Released: Middle July, 2003
+ (c) 2003 Athena Project.
+ http://project-yare.de/
+
+1. Athena(アテナ)について
+2. このリリースについて
+3. 必要な物
+4. 使い方
+5. 現在の仕様
+6. 祝辞
+7. 免責事項
+8. 募集
+9. English
+
+
+1. アテナについて
+ アテナとは2003年1月半ばにでた0052.lzhをベースとして作られているエミュレータの一つです。
+ 基本的なライセンスはオリジナルがGPLの下に配布されている為、
+ これに従いGPLの下配布を許可します。
+ /*
+ 改良版を配布する場合は必ずこのREADMEを書き換えてください。
+ 何処を改良したのか報告(athena@project-yare.deまで)して貰えると助かります。
+ バイナリのみの配布はGPL違反ですので"必ず"ソースも添付してください。
+ */
+ 動作の確認は以下の通りのみ行っています。
+ // ただし完璧に動く事を保証するものでありません
+ 対象CPU: Intel Pentium系 // PentiumII以上で確認.
+ FreeBSD 4.8R, 4.6.2R
+ Linux RedHat 7.3
+ cygwin + gcc 3.2 20020927 (prerelease)
+ 開発元URL: http://project-yare.de/
+
+
+2. このリリースについて
+ 今回のリリースは前回(V2.1)同様開発版のリリースのみです。
+ 2.1に比べ下記の点が修正されています。
+ mapのデフォルト設定が韓国data.grfのみ正常に動作するようになっていた点
+ common/timer.cやmap/script.cの幾つかのバグ
+
+ 迅速にUpdateを強く推奨するものではありませんが各自の判断で行って下さい。
+
+
+3. 必要な物
+ data.grf //sdata.grfは必要に応じて
+ account.txt //存在しない場合athena.shが自動生成します
+ conf/*.cnf //Map用とChar用の二種類あります
+ conf/npc*.txt //npc設定用ファイルです。複数のファイルに分けることが可能です。
+ db/*.txt //アイテム、job情報など
+
+
+4. 使い方
+ > tar xvfz athena-d?.?.tar.gz
+ > cd athena-d?.?.tar.gz
+ > make
+ > vi conf/char_athena.cnf //IP(127.0.0.1)の部分を環境に合わせて変更してください
+ > vi conf/map_athena.cnf //同上、またmap設定などは、このファイルで行います。
+ > ./athena.sh
+ 上記を行えば"たぶん"起動します。
+
+ 補足:
+ conf/npc_sampleJ.txtにはスクリプトの書き方について色々な説明が記載されています。
+ もし、独自のMap設定を行ってみたい人や、スクリプトを弄りたい方は参考にしてください。
+ ただし、開発中のためスクリプトの仕様が変更される可能性が高いです。
+ command.txtには実装済みの特殊コマンドについての説明を記載しています。
+
+
+5. 現在の仕様
+ 本鯖と比べておかしい(例えばプバが歩く、ポリンがアイテムを拾わないなど)点は、
+ 全て現在開発中に因るものです。
+ 現状としてキャラクタ系及びモンスター系のバグ報告は無視される可能性が高いです。
+
+ バグ報告について必ず発生条件をお書き下さい。
+ 下にある報告用テンプレートを使って報告して頂くと助かります。
+ 報告先はエミュ板の開発スレにでも。
+ ---- Athena v 2.0 (stable or develop) ----
+ 【gcc ver】gcc -vを実行時に表示される内容
+ 【動作システム】FreeBSD, Linux(ディストリビュージョンも), cygwinなど
+ 【発生内容】mapが落ちてしまった時の表示されていたデバッグ情報など具体的に書いてください。
+ 【操作内容】具体的にどんな操作を行ったかを書いてください。
+ ------------------ END -------------------
+ 理想はテンプレに加えてmap.coreなどcoreファイルをUploaderにアップして頂くことですが
+ 問題のMapだけの状態にしcoreの吐く容量に注意してください。
+ /*
+ 確認した限りでは324個ほどmapデータを読み込ませると、
+ 40MB近いcoreファイルを吐き出します @FreeBSD
+ cygwinの場合はstackdumpというファイルになるそうです。
+ しかし、coreファイルなどをgzip圧縮などすれば大幅に小さくなります。
+ 大凡30MBのcoreファイルが2.9MBほどになるようです。
+ ですので、もしアップロードする場合はgzip圧縮など各自行ってください。
+ */
+
+ 今回のリリースだけでなくHISTORYを作成すると大量に記述が必要な為省略しています。
+ // 多い日だと本当に結構ありますので‥‥。
+
+
+6. 祝辞
+ 今回このAthena開発版を出すに当たって感謝したい方々(順番不同)
+ Lemming氏 (Project YARE)
+ 0052氏 (Uploader)
+ 35氏 (エミュ開発スレ)
+ Johan Lindh氏(Author of memwatch)
+ YARE forumのNPC情報を作成した方々
+ weiss研究会BBSの様々な情報ファイルを作成した方々
+ 最後に、.coreファイル達
+
+
+7. 免責事項
+ Athena Projectは一切Athenaの動作に関する保証等は行いません。
+ つまり、Athenaは無保証です。
+ athena@project-yare.deに動作・操作等に関する質問などを送られても一切お答えできません。
+ 又Athenaを用いたことにより生じた被害・問題等の責任は一切Athena Projectは負いません。
+
+
+8. 募集
+ athenaの開発に参加したい//興味があるという方ご連絡下さい。
+ 我々は貴方の参加をお待ちしています。
+ // 最新版が欲しいだけで何ら協力して頂けないという方はお断りです;-)
+ [募集要項: プログラマ(2-3人)]
+ 年齢: 不問
+ 性別: 不問
+ 言語: 日本語が理解可能
+ 内容: C言語もしくはC++による開発。(特にネットワークやDBの経験が有る方大募集!)
+ [募集要項: 翻訳(?人)]
+ 年齢: 不問
+ 性別: 不問
+ 言語: 日本語、英語が理解可能
+ 内容: 仏蘭西語、独逸語、西班牙語、伊太利亜語、泰(タイ)語、朝鮮語、中国語へ文献、サイトなどの翻訳
+ 連絡先: athena@project-yare.de 雑務担当まで。
+
+
+9. English
+ This release is just fixed some bugs in timer.c, script.c and map_athena1.conf.
+
+
+(c) 2003 Athena Project.
diff --git a/doc/Readme2.txt b/doc/Readme2.txt new file mode 100644 index 0000000..0f938fa --- /dev/null +++ b/doc/Readme2.txt @@ -0,0 +1,368 @@ + ______ __ __ + /\ _ \/\ \__/\ \ + __\ \ \L\ \ \ ,_\ \ \___ __ ___ __ + /'__`\ \ __ \ \ \/\ \ _ `\ /'__`\/' _ `\ /'__`\ +/\ __/\ \ \/\ \ \ \_\ \ \ \ \/\ __//\ \/\ \/\ \L\.\_ +\ \____\\ \_\ \_\ \__\\ \_\ \_\ \____\ \_\ \_\ \__/.\_\ + \/____/ \/_/\/_/\/__/ \/_/\/_/\/____/\/_/\/_/\/__/\/_/ + _ _ _ _ _ _ _ _ _ _ _ _ _ + / \ / \ / \ / \ / \ / \ / \ / \ / \ / \ / \ / \ / \ +( e | n | g | l | i | s | h ) ( A | t | h | e | n | a ) + \_/ \_/ \_/ \_/ \_/ \_/ \_/ \_/ \_/ \_/ \_/ \_/ \_/ + + +-------------------------------------------------------------- +eAthena VERSION +-------------------------------------------------------------- +Version 1.0.0 Release Candidate 3 +-------------------------------------------------------------- +This is a release candidate to iron out any bugs before +official release. +-------------------------------------------------------------- +Caution : Make sure you are giving your users account id's +greater than 2000000, failure to do so can cause a number of +problems. +-------------------------------------------------------------- +eATHENA README +-------------------------------------------------------------- +Thank you for downloading eAthena. +We hope that you'll enjoy using this software. +-------------------------------------------------------------- +-------------------------------------------------------------- +eAthena Features +-------------------------------------------------------------- +> eAthena Completed Features +-------------------------------------------------------------- +- PvP (Player VS Player) +- GvG (Guild VS Guild) +- qPets (Cute Pets) +- Monster skills +- 2-2 Jobs (Alternate 2nd Jobs) +- SuperNovice (Alternate 1st Job) +- WoE (War of Emperium) +- Remote administration of accounts (ladmin softwares) +- Pet-Equipped Mobs +- Management of day/night +-------------------------------------------------------------- +> eAthena Incompleted Features (List) +-------------------------------------------------------------- +- Advanced Classes (Upper Jobs) +- Baby Classes (Baby Jobs) +- Pet Skills +- Equipment Breaking System +- PK Server Mode +-------------------------------------------------------------- +> eAthena Features - Estimated completion rate (Detailed) +-------------------------------------------------------------- +- Guild Wars (War of Emperium) - 100% Complete (4/4) +- Novice Skills - 100% Complete:(3/3) +- 1st Job Skills - 100% + * Swordsman - 100% Complete:(10/10) + * Acolyte - 100% Complete:(16/16) + * Archer - 100% Complete:(7/7) + * Magician - 100% Complete:(14/14) + * Merchant - 100% Complete:(10/10) + * Thief - 100% Complete:(10/10) +- 2-1 Skills (2nd Job Skills) - 100% + * Knight - 100% Complete:(10/10) + * Priest - 100% Complete:(18/18) + * BlackSmith - 100% Complete:(21/21) + * Wizard - 100% Complete:(13/13) + * Assassin - 100% Complete:(10/10) + * Hunter - 100% Complete:(17/17) +- 2-2 Skills (Alternate 2nd Job Skills) - 90.5% + * Crusader - 100% Complete:(18/18) + * Monk - 100% Complete:(15/15) + * Alchemist - 54% Complete:(12/22) Complete:(10/22) (Blame Gravity For The Missing Skills) + * Sage - 100% Complete:(20/20) + * Rogue - 80% Complete:(12/15) Incomplete:(3/15) + * Bard - 100% Complete:(16/16) + * Dancer - 100% Complete:(16/16) +- 2-1-1 Skills (Advanced 2-1 Job Skills) - 80.5% + * Lord Knight - 94% - Complete:(7/8) Incomplete:(1/8) + * High Priest - 66% - Complete:(1/3) Incomplete:(2/3) + * High Wizard - 100% Complete:(4/4) + * Whitesmith - 75% Complete:(3/4) Incomplete:(1/4) + * Sniper - 88% - Complete:(3/4) Incomplete:(1/4) + * Assassin Cross - 60% Complete:(3/5) Incomplete:(2/5) +- 2-2-1 Skills (Advanced 2-2 Job Skills) - 47% + * Paladin - 65% + * Champion - 100% + * Professor - 100% + * Stalker - 65% + * Creator - 0% + * Clown - 0% + * Gypsy - 0% +- Pet Skills - 95% + * Loot Skills - 100% + * Buff Skills - 100% + * Attack Skills - 90% + * Assist Skills - 100% +- Equipment Breaking System - 50% + * Weapon Breaking - 85% + * Armor Breaking - 80% + * Equipment Repair - 80% +- PK Server Mode - ??% + * Need more info on this mode to be called complete. +-------------------------------------------------------------- +> eAthena - What we currently do not have +-------------------------------------------------------------- +- Wedding System (In Progress!) +-------------------------------------------------------------- +-------------------------------------------------------------- + + +-------------------------------------------------------------- +-------------------------------------------------------------- +System Requirements +-------------------------------------------------------------- +> Minimum System Requirements +-------------------------------------------------------------- +800Mhz Processor +256MB RAM + +NOTE: For this setup, it's recommended to not load as many maps +and spawn as many monsters as it comes default with eAthena. +To do this, comment off unused maps and control monster spawn +rate using mob_count in battle_athena.conf. + +-------------------------------------------------------------- +> Recommended System Requirements +-------------------------------------------------------------- +1+Ghz Processor +512+MB RAM + +NOTE: For this setup, generally, running the eAthena default +should be fine. If you have alot of extra RAM, you might even +consider raising the mob_count in battle_athena.conf so that +there will be more than normal monsters in your server +-------------------------------------------------------------- +-------------------------------------------------------------- + + +-------------------------------------------------------------- +-------------------------------------------------------------- +Setting up eAthena +-------------------------------------------------------------- +> Setting up a new eAthena +-------------------------------------------------------------- +1. Run the setupwizard program. This will assist you in + configuring Athena for the first time. Just follow the + instructions in the program. +2. Edit motd.txt, grf-files.txt & .conf files as neccessary +3. If you want to add a user, go to tools and use the AddUser + tool +4. Run the login-server, char-server and map-server in the + main Athena directory +5. You're done! + +-------------------------------------------------------------- +> Upgrading from older eAthena: +-------------------------------------------------------------- +- From a release newer than 953 or 817: +1. Transfer your old /save/ folder contents (Athena.txt, + Account.txt, Storage.txt, etc.) to the new /safe/ folder +2. Transfer/Edit grf-files.txt, gm-account.txt and motd.txt as + neccessary +3. Transfer any extra custom NPCs to the /npc/ folder and + edit if neccessary +4. Edit .conf files to setup your server +5. You're done! + +NOTE: It is not recommended to transfer your DB files +as the DB files are constantly updated by the eAthena/jAthena +team. Just read your changes to the new DB files instead of +replacing the new DB file with your old one. +-------------------------------------------------------------- +- From a release older than 670: +1. Transfer your old data files (Athena.txt, Account.txt, + Storage.txt, Pet.txt, etc) into the /save/ folder +2. Transfer/Edit grf-files.txt, gm-account.txt as neccessary +3. Transfer any extra custom NPCs/DBs to the new /npc/ folder + and edit if neccessary +4. Edit .conf files to setup your server +5. You're done! + +NOTE: It is not recommended to transfer your .conf files from +your old eAthena setup to this new one because there are always +changes and new additions to these .conf files all the time + +NOTE 2: It is also not recommended to transfer your DB files +as the DB files are constantly updated by the eAthena/jAthena +team. Just readd your changes to the new DB files instead of +replacing the new DB file with your old one. +-------------------------------------------------------------- +-------------------------------------------------------------- + + +-------------------------------------------------------------- +-------------------------------------------------------------- +Playing eAthena +-------------------------------------------------------------- +1. Make sure all the files are setup properly (conf files) +2. Load eAthena + 2a. Run login-server + 2b. Run char-server + 2c. Run map-server +3. Wait for map-server to finish loading. +4. Play using a custom XML. +-------------------------------------------------------------- +-------------------------------------------------------------- + + +-------------------------------------------------------------- +-------------------------------------------------------------- +Frequently Asked Questions +-------------------------------------------------------------- + +A: You're missing the cygwin dlls. Please get the latest dll at: + http://www.cygwin.com/snapshot. If you're unsure, asking + around in our IRC chatroom will get you around too, but + always remember - use common sense and search before asking. + +Q: My map-server won't load! It appears to be loading things + before it dissapeared suddenly! HELP! +A: Use command line to load map-server. It should tell you the + error. If you're missing a map, update your kRO Sakray or + uncomment the map from map_athena.conf. If you have an + errornous NPC, fix it or comment it off. Anything other than + that, feel free to ask around. + +Q: My map-server failed to load 'adata.grf'! Where do I find + this adata.grf? My map-server won't load without it! +A: The error that caused the map-server to not load is not the + adata.grf. You do NOT need to have adata.grf so that the map + server can load. The error is probably something else. + +Q: All 3 of the servers are loaded, but I am still having + problems accessing it! What do I do? +A: First, check the IPs in map_athena.conf and char_athena.conf. + If that's correct, check the ports to make sure they match. + If that's correct too, you probably cannot handle the server + load. Lower the monster spawning rate using mob_count in + battle_athena.conf and it should be fine. + +Q: You said eAthena has 2-2 skills. Well, I tried it and there + are no 2-2 skills when I use 2-2 characters! You lie! +A: No, I did not. You have an outdated Ragexe/Sakexe that does + not support 2-2 skills. Update it to a later one which does. + +Q: How do I start Guild Wars/War of Emperium??? HELP!!! +A: Read the help.txt for full list of commands that GMs can use. + @agitstart and @agitend is also listed there as commands to + start and end Guild Wars. + +Q: My Ragnarok Online crashed while playing with eAthena! What + do I do now? +A: Well, if your Ragnarok crashes, it's most probably not + anything to do with eAthena. Something is wrong with your + Ragnarok installation. Try reinstalling or updating. + +Q: Is eAthena compatible with mySQL? Can I use mySQL as the DB + instead of using text files? +A: Yes, eAthena is compatible with mySQL. A tutorial on how to + setup this is coming soon. + +Q: Is eAthena compatible with msSQL? Can I use msSQL as the DB + instead of using text files? +A: No, eAthena is not compatible with msSQL. You cannot use + msSQL with eAthena. + +Q: I found a bug! Where do I report it? +A: Drop the developers a line at the IRC chatroom. Or just post + it in the bug report forum. We check them out too. :) + +Q: I know alot of C and I'm able to help improve eAthena and + add new features. How can I join your development team? +A: Try talking to one of the current developers in the eAthena + channel. + +Q: What is this AthenaAdvanced/omniAthena that I keep hearing of? +A: Huh? What's that? + +Q: Why does eAthena crash, and how come you can't prevent this? +A: eAthena is a working in progress we never stated eAthena is + bug free, most likely no software is bugfree, if they state + this they are lying sons of beeps and you should sue them + for false advertisement. +-------------------------------------------------------------- +-------------------------------------------------------------- + + +-------------------------------------------------------------- +-------------------------------------------------------------- +Support +-------------------------------------------------------------- +For support, first check the Frequent Asked Questions. +If the question doesn't exist, check the forums. Search before +posting to see if the question had been asked and answered. +If not, post and we'll try and help you. +Our forums is located at: http://eathena.deltaanime.net +You can also visit our IRC chatroom and ask your questions +there. +Our IRC chatroom is located at: irc.deltaanime.net #athena +-------------------------------------------------------------- +-------------------------------------------------------------- + + +-------------------------------------------------------------- +-------------------------------------------------------------- +Development +-------------------------------------------------------------- +eAthena is based on the original Athena, by the Japanese +Development Team. A big thanks to them for working hard on +Athena. + +The eAthena Development Team: +AppleGirl +Akaru +Evera/Lorri +Wizputer +Valaris +Yor +Kalaspuff +Syrus22 +Darkchild +CLOWNISIUS +RichG +Kashy +Lupus + +NOTE: Sorry if I missed anyone while writing this. If you are +in the team but not included in this list, PM me. +-------------------------------------------------------------- +-------------------------------------------------------------- + + +-------------------------------------------------------------- +-------------------------------------------------------------- +Special Thanks and Shoutouts! +-------------------------------------------------------------- +The eAthena development team would like to specially thank: +- the Japanese Development Team. A big thanks to them for + working hard on Athena which eAthena is based on. +- RoVeRT, our ex-developer. eAthena wouldn't have this many + features without him! +- sara-chan, who had also contributed alot to the development + of eAthena! Thanks! +- Gravity, for making Ragnarok Online. eAthena wouldn't even + exist if it wasn't for them! +- ASB for hosting our forum section :) +- Deltaanime for hosting our new official forums :) +- And others who gave continuously endless support to eAthena! +- fov and Kinko for their bugfixes. + +NOTE: Sorry if I missed anyone while writing this. If you are +in the team but not included in this list, PM me. +-------------------------------------------------------------- +-------------------------------------------------------------- + + +-------------------------------------------------------------- +-------------------------------------------------------------- +License: +-------------------------------------------------------------- +eAthena is licensed under the GPL. Please refer to LICENSE in +your Athena folder for the full GPL License statement. +-------------------------------------------------------------- +-------------------------------------------------------------- diff --git a/doc/admin_packet.txt b/doc/admin_packet.txt new file mode 100644 index 0000000..670a038 --- /dev/null +++ b/doc/admin_packet.txt @@ -0,0 +1,281 @@ +=========================================================================== +=========================================================================== + DETAIL OF ADMINISTRATION PACKETS +--------------------------------------------------------------------------- +--------------------------------------------------------------------------- + +=========================================================================== + COMMON PACKETS FOR ALL SERVERS +--------------------------------------------------------------------------- +S 7530 (void) + It's a request to obtain the version of the server. + Answer packet: 0x7531. + +R 7531 <major_version>.B <minor_version>.B <revision>.B <release_flag>.B <official_flag>.B <server_type>.B <mod_version>.w + It's the reply to a request about the server version (0x7530). + It gives the information about the version of the server. + release_flag : 0 = release, 1 = debug + official_flag: 0 = official, 1 = mod + server_type: 1 = login, 2 = char, 4 = inter, 8 = map (add all values if the server have more than 1 type) + +S 7532 (void) + End of connection (no reply is sended back) + +=========================================================================== + ADMINISTRATION PACKETS OF THE LOGIN-SERVER +--------------------------------------------------------------------------- + +NB: All information about an account can be obtain by an administration packet, except the password. + The login-server will never send back the password af an account. + If someone sniffs the password from the login-server, he can not obtain it. + But, it exists a packet to check the password. + It's to secure the password. + +CONNECTION SYSTEM IN ADMINSTRATION MODE + +S 7918 <encrypt_flag>.w <administration_password>.24(16)B + It's a request to obtain an (administration) connection on the login-server. + The packet size changes according to the encrypted password or not. + A non-encrypted password is sended with 24 bytes (plain text with NULL terminate), + and an encrypted password is sended with 16 bytes. + Answer packet: 0x7919. + encrypt_flag: 0 = plain text, 1 = md5 (key + password), 2 = md5 (password + key) + +R 7919 <flag>.B + It's the reply to a request for an (administration) connection (0x7918). + Flag: 0 = accepted, 1 = refused + +S 791a (void) + It's a request to obtain the encrypted key. This key is used to create the encrypted password. + This packet must be called before the packet 0x7918 if you want connect you with an encrypted password. + The reply is the packet 0x01dc, as like a player sends a connection request with the packet 0x01db. + +PACKETS OF ADMINISTRATION MODE + +R 791f (void) + Ready signal (no reply is sended back). NOT USED. + +S 7920 <start_id>.l <end_id>.l + It's a request to obtain an accounts list. + start_id: 0 for begining of the list, or presumed start id value of the first account. + end_id: 0 to go until the last id, or the upper id limit value that you want. + If 'end_id' is lower to 'start_id', you give no limit for the last id. + Answer packet: 0x7921. + +R 7921 <length>.w { <account_id>.l <GM_level>.B <account_name>.24B <sex>.B <count>.l <state>.l }.38B* + It's tje reply packet of the packet 0x7920. + It gives an accounts list. + If you receive at least an account, you must do an additional request (packet 0x7920 begining with the last received account id +1) + to be sure that you receive all asked accounts, because this packet is limited in size when it sends information. + if <state> is 7, there are 2 possibilities: banishement or state{Your are Prohibited to log in until %s}. + +S 7930 <account_name>.24B <password>.24B <sex>.B <E-mail>.40B + It's a request to create a new account. + If e-mail is not valid, the e-mail is replaced by the default e-mail (a@a.com). + The default e-mail is like no e-mail to protect your characters against a deletion. + Answer packet: 0x7931. + +R 7931 <account_id>.l <account_name>.24B + It's the reply to an account creation (0x7930). + If the account_id value is -1, the login-server fails to create the new account + (an account already exists with the same name, the request is invalid, etc.). + Otherwise, the account_id is the id of the new account. + +S 7932 <account_name>.24B + It's a request to delete an account. + Answer packet: 0x7933. + +R 7933 <account_id>.l <account_name>.24B + It's the reply to an account deletion (0x7932). + If the account_id value is -1, the login-server fails to delete the account (the account doesn't exist). + Otherwise, the account_id is the id of the deleted account. + The login-server returns the correct sensitive case account_name (use strcmpi to compare). + +S 7934 <account_name>.24B <password>.24B + It's a request to modify the password of an account. + Answer packet: 0x7935. + +R 7935 <account_id>.l <account_name>.24B + It's the reply to an password modification (0x7934). + If the account_id value is -1, the login-server fails to modify the password (the account doesn't exist). + Otherwise, the account_id is the id of the account that the login-server have modified the password. + The login-server returns the correct sensitive case account_name (use strcmpi to compare). + +S 7936 <account_name>.24B <state>.l <error_message>.20B + It's a request to modify the state of an account. + State: It's the state like the packet 0x006a +1. + Error_message: it's the message of the error code #6 = Your are Prohibited to login until <error_message> (packet 0x006a). + If state is 7 (6+1), the error_message is saved in the account. + For another state, the error_message is deleted (voided) in the account. + Answer packet: 0x7937. + +R 7937 <account_id>.l <account_name>.24B <state>.l + It's the reply to a state modification (0x7936). + If the account_id value is -1, the login-server fails to modify the state (the account doesn't exist). + Otherwise, the account_id is the id of the account that the login-server have modified the state. + The login-server returns the correct sensitive case account_name (use strcmpi to compare). + +S 7938 (void) + It's a request to obtain information about the servers connected to the login-server (servers list with details) + Answer packet: 0x7939. + +R 7939 <length>.w {<IP>.l <port>.w <server_name>.20B <#_of_users>.w <maintenance>.w <new>.w}.32B* + It's the reply to an request to obtain servers information (0x7938). + It returns all information about the servers connected on the login-server. + +S 793a <account_name>.24B <password>.24B + It's a request to test a password. REMEMBER: Never create a packet to send the password from the login-server. + Answer packet: 0x793b. + +S 793b <account_id>.l <account_name>.24B + It's the reply to a password check (0x793a). + If the account_id value is -1, the login-server doesn't valid the password (the account doesn't exist or the password is not the correct one). + Otherwise, the account_id is the id of the account that the login-server have checked the password. + The login-server returns the correct sensitive case account_name (use strcmpi to compare). + +S 793c <account_name>.24B <sex>.B + It's a request to modify the sex of an account. + Answer packet: 0x793d. + +R 793d <account_id>.l <account_name>.24B + It's the reply to a request to modify a sex (0x793c). + If the account_id value is -1, the login-server fails to modify the sex (the account doesn't exist or the sex is already the good sex). + Otherwise, the account_id is the id of the account that the login-server have modified the sex. + The login-server returns the correct sensitive case account_name (use strcmpi to compare). + +S 793e <account_name>.24B <GM_level>.B + It's a request to modify the GM level of an account. + Answer packet: 0x793f. + +R 793f <account_id>.l <account_name>.24B + It's the reply to a request to modify a GM level (0x793e). + If the account_id value is -1, the login-server fails to modify the GM level (the account doesn't exist, or GM_account file can not be modified or the GM level is already the good sex). + Otherwise, the account_id is the id of the account that the login-server have modified the GM level. + The login-server returns the correct sensitive case account_name (use strcmpi to compare). + +S 7940 <account_name>.24B <e-mail>.40B + It's a request to modify the email of an account. + Answer packet: 0x7941. + +R 7941 <account_id>.l <account_name>.24B + It's the reply to a request to modify an email (0x7940). + If the account_id value is -1, the login-server fails to modify the e-mail (the account doesn't exist). + Otherwise, the account_id is the id of the account that the login-server have modified the email. + The login-server returns the correct sensitive case account_name (use strcmpi to compare). + +S 7942 <account_name>.24B <length_of_memo>.w <memo>.(length_of_memo)B + It's a request to modify the memo of an account. + The length can be 0 to have a void memo. + Answer packet: 0x7943. + +R 7943 <account_id>.l <account_name>.24B + It's the reply to an request to modify a memo (0x7942). + If the account_id value is -1, the login-server fails to modify the memo (the account doesn't exist). + Otherwise, the account_id is the id of the account that the login-server have modified the memo. + The login-server returns the correct sensitive case account_name (use strcmpi to compare). + +S 7944 <account_name>.24B + It's a request to obtain (by the name) the id of an account. + Answer packet: 0x7945. + +R 7945 <account_id>.l <account_name>.24B + It's the reply to an request (by the name) to obtain an account id (0x7944). + If the account_id value is -1, the login-server fails to obtain the account id (the account doesn't exist). + Otherwise, the account_id is the id of the found account. + The login-server returns the correct sensitive case account_name (use strcmpi to compare). + +S 7946 <account_id>.l + It's a request to obtain (by the id) the name of an account. + Answer packet: 0x7947. + +R 7947 <account_id>.l <account_name>.24B + It's the reply to an request (by the id) to obtain an account name (0x7946). + If the account_name value is "" (void), the login-server fails to obtain the account name (the account id doesn't exist). + Otherwise, the account_name is the name of the found account. + +S 7948 <account_name>.24B <validity>.l + It's a request to set the validity date of an account. + This packet fixes an absolute validity time. + Answer packet: 0x7949. + +R 7949 <account_id>.l <account_name>.24B <validity>.l + It's the reply to an request to set the validity date of an account (0x7948). + If the account_id value is -1, the login-server fails to set the validity date (the account doesn't exist). + Otherwise, the account_id is the id of the account that the login-server have fixed the validity date. + The login-server returns the correct sensitive case account_name (use strcmpi to compare). + +S 794a <account_name>.24B <end_date_of_banishment>.l + It's a request to set the final date of a banishment of an account. + This packet fixes an absolute time for the end of the banishment. + Answer packet: 0x794b. + +R 794b <account_id>.l <account_name>.24B <end_date_of_banishment>.l + It's the reply to an request to set the final date of a banishment of an account (0x794a). + If the account_id value is -1, the login-server fails to set the final date of a banishment (the account doesn't exist). + Otherwise, the account_id is the id of the account that the login-server have fixed the final date of a banishment. + The login-server returns the correct sensitive case account_name (use strcmpi to compare). + +S 794c <account_name>.24B <+/-years_modification>.w <+/-months_modification>.w <+/-days_modification>.w <+/-hours_modification>.w <+/-minutes_modification>.w <+/-seconds_modification>.w + It's a request to adjust the final date of a banishment of an account. + This packet modifies the final date of a banishment by add/sustract of some parameters. + If you adjust a non-banished account, you fix a banishment with actual time + adjustments. + Answer packet: 0x794d. + +R 794d <account_id>.l <account_name>.24B <end_date_of_banishment>.l + It's the reply to an request to adjust the final date of a banishment of an account (0x794c). + If the account_id value is -1, the login-server fails to adjust the final date of a banishment (the account doesn't exist). + Otherwise, the account_id is the id of the account that the login-server have adjusted the final date of a banishment. + The login-server returns the correct sensitive case account_name (use strcmpi to compare). + Note: if value is impossible, the returned value is the (unchanged) value of the final date of the banishment. + +S 794e <broadcast_type>.W <length_of_message>.L <message>.(length_of_message)B + It's a request to send a broadcast to all map-servers. + The length can not be 0. + broadcast_type: + 0: normal (yellow) + 0x10: blue + Answer packet: 794f. + +R 794f <answaer>.W + It's the reply to an request to send a broadcast to all map-servers (794e). + If the answer value is -1, the login-server fails to send the message (no char-server are conected). + Otherwise, the answer is 0. + +S 7950 <account_name>.24B <+/-years_modification>.w <+/-months_modification>.w <+/-days_modification>.w <+/-hours_modification>.w <+/-minutes_modification>.w <+/-seconds_modification>.w + It's a request to adjust the validity date of an account. + This packet modifies the validity date by add/sustract of some parameters. + You can not adjust an unlimited validity date. + Answer packet: 0x7951. + +R 7951 <account_id>.l <account_name>.24B <validity>.l + It's the reply to an request to adjust the validity date of an account (0x7950). + If the account_id value is -1, the login-server fails to adjust the validity date (the account doesn't exist). + Otherwise, the account_id is the id of the account that the login-server have found to try the adjustement of the validity date. + The login-server returns the correct sensitive case account_name (use strcmpi to compare). + If the returned validity date is 0, the login-server haven't modify the validity date (the account have an unlimited validity, + the modification is impossible because the new date is too far in the future, etc...). + If the returned validity date is not 0, it's the new validity date of the account. + +S 7952 <account_name>.24B + It's a request (by name) to obtain complete information about an account. + Answer packet: 0x7953. + +R 7953 <account_id>.l <GM_level>.B <account_name>.24B <sex>.B <count>.l <state>.l <error_message>.20B <last_login_time>.24B <last_authorised_login_ip>.16B <email>.40B <validity_date>.l <until_ban_date>.l <length_of_memo>.w <memo>.(length_of_memo)B + It's the reply to an request to obtain complete information about an account (0x7952 and 0x7954). + It sends all informations about an account. + Reply of 0x7952: + If the account_id value is -1, the login-server fails to found the account (the account doesn't exist). + Otherwise, the account_id is the id of the found account. + The login-server returns the correct sensitive case account_name (use strcmpi to compare). + Reply of 0x7954: + If the account_name value is "" (void), the login-server fails to found the account (the account id doesn't exist). + Otherwise, the account_name is the name of the found account. + +S 7954 <account_id>.l + It's a request (by id) to obtain complete information about an account. + Answer packet: 0x7953. + +S 7955 + It's a request to re-load GM file definition. + Answer packet: NONE. + diff --git a/doc/agitdb_ref.txt b/doc/agitdb_ref.txt Binary files differnew file mode 100644 index 0000000..5cd9bde --- /dev/null +++ b/doc/agitdb_ref.txt diff --git a/doc/client_packet.txt b/doc/client_packet.txt new file mode 100644 index 0000000..1d1df8c --- /dev/null +++ b/doc/client_packet.txt @@ -0,0 +1,1072 @@ + + 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +#0x0040 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 55, 17, 3, 37, 46, -1, 23, -1, 3,108, 3, 2, + 3, 28, 19, 11, 3, -1, 9, 5, 54, 53, 58, 60, 41, 2, 6, 6, +#0x0080 + 7, 3, 2, 2, 2, 5, 16, 12, 10, 7, 29, 23, -1, -1, -1, 0, + 7, 22, 28, 2, 6, 30, -1, -1, 3, -1, -1, 5, 9, 17, 17, 6, + 23, 6, 6, -1, -1, -1, -1, 8, 7, 6, 7, 4, 7, 0, -1, 6, + 8, 8, 3, 3, -1, 6, 6, -1, 7, 6, 2, 5, 6, 44, 5, 3, +#0x00C0 + 7, 2, 6, 8, 6, 7, -1, -1, -1, -1, 3, 3, 6, 6, 2, 27, + 3, 4, 4, 2, -1, -1, 3, -1, 6, 14, 3, -1, 28, 29, -1, -1, + 30, 30, 26, 2, 6, 26, 3, 3, 8, 19, 5, 2, 3, 2, 2, 2, + 3, 2, 6, 8, 21, 8, 8, 2, 2, 26, 3, -1, 6, 27, 30, 10, +#0x0100 + 2, 6, 6, 30, 79, 31, 10, 10, -1, -1, 4, 6, 6, 2, 11, -1, + 10, 39, 4, 10, 31, 35, 10, 18, 2, 13, 15, 20, 68, 2, 3, 16, + 6, 14, -1, -1, 21, 8, 8, 8, 8, 8, 2, 2, 3, 4, 2, -1, + 6, 86, 6, -1, -1, 7, -1, 6, 3, 16, 4, 4, 4, 6, 24, 26, +#0x0140 + 22, 14, 6, 10, 23, 19, 6, 39, 8, 9, 6, 27, -1, 2, 6, 6, + 110, 6, -1, -1, -1, -1, -1, 6, -1, 54, 66, 54, 90, 42, 6, 42, + -1, -1, -1, -1, -1, 30, -1, 3, 14, 3, 30, 10, 43, 14,186,182, + 14, 30, 10, 3, -1, 6,106, -1, 4, 5, 4, -1, 6, 7, -1, -1, +#0x0180 + 6, 3,106, 10, 10, 34, 0, 6, 8, 4, 4, 4, 29, -1, 10, 6, + 90, 86, 24, 6, 30,102, 9, 4, 8, 4, 14, 10, 4, 6, 2, 6, + 3, 3, 35, 5, 11, 26, -1, 4, 4, 6, 10, 12, 6, -1, 4, 4, + 11, 7, -1, 67, 12, 18,114, 6, 3, 6, 26, 26, 26, 26, 2, 3, +#0x01C0 + 2, 14, 10, -1, 22, 22, 4, 2, 13, 97, 0, 9, 9, 29, 6, 28, + 8, 14, 10, 35, 6, 8, 4, 11, 54, 53, 60, 2, -1, 47, 33, 6, + 30, 8, 34, 14, 2, 6, 26, 2, 28, 81, 6, 10, 26, 2, -1, -1, + -1, -1, 20, 10, 32, 9, 34, 14, 2, 6, 48, 56, -1, 4, 5, 10, +#0x200 + 26, 0, 0, 0, 18, 0, 0, 0, 0, 0, 0, 19, + +S クライアントから見て送信 +R クライアントから見て受信 + +B バイト +w ワード=2B +l ロングワード=4B +* 0個以上くりかえし + +S 0064 <version>.l <account name>.24B <password>.24B <version2>.B + アカウントID&パスワード送信 +S 0065 <account ID>.l <login ID1>.l <login ID2>.l ?.2B <sex>.B + キャラセレ鯖接続要求 +S 0066 <charactor number>.B + キャラクタ選択要求 +S 0067 <charactor name>.24B <param etc>.11B + キャラクタ作成要求 +S 0068 <charactor ID>.l <mail address>.40B + キャラクタ削除要求 +R 0069 <len>.w <login ID1>.l <account ID>.l <login ID2>.l ?.32B <sex>.B {<IP>.l <port>.w <server name>.20B <login users>.w <maintenance>.w <new>.w}.32B* + login成功&鯖情報 +R 006a <error No>.B + login失敗 その1 + err No=00 未登録のIDです + err No=01 パスワードが違います + err No=02 使用期間が終了しています + err No=03 サーバーから接続拒否されました + err No=04 解約されたID、またはアカウントブロックされているIDです + err No=05 最新のパッチではありません + err No=06 解約されたID、またはアカウントブロックされているIDです + err No=07 サーバーが混雑しています +R 006b <len>.w <charactor select data>.106B* + キャラセレ鯖接続成功&キャラクタデータ + <charactor select data> = <charactor ID>.l <base exp>.l <zeny>.l <job exp>.l <job level>.l ?.8B <option>.l <karma>.l <manner>.l ?.2B <HP>.w <MaxHP>.w <SP>.w <MaxSP>.w <speed>.w <class>.w <hair>.w <weapon>.2w <base level>.w <skill point>.w <head_bottom>.w <sheild>.w <head option top>.w <head option mid>.w <hair color>.w ?.w <name>.24B <STR>.B <AGI>.B <VIT>.B <INT>.B <DEX>.B <LUK>.B <charactor number>.B ?.B +R 006c <error No>.B + キャラクタ選択失敗 +R 006d <charactor select data>.106B + キャラクタ作成成功 +R 006e <error No>.B + キャラクタ作成失敗 +R 006f + キャラクタ削除成功 +R 0070 <error No>.B + キャラクタ削除失敗 + err No=00 メールアドレスが違う + err No=01 削除が拒否された +R 0071 <charactor ID>.l <map name>.16B <ip>.l <port>.w + キャラクタ選択成功&マップ名&ゲーム鯖IP/port +S 0072 <account ID>.l <charactor ID>.l <login ID1>.l <login ID2>.l <sex>.b + ゲーム鯖接続要求 +R 0073 <server tick>.l <coordidate>.3B ?.2B + ゲーム鯖接続成功&サーバ側1ms時計&出現位置 +R 0078 <ID>.l <speed>.w <opt1>.w <opt2>.w <option>.w <class>.w <hair>.w <weapon>.w <head option bottom>.w <sheild>.w <head option top>.w <head option mid>.w <hair color>.w ?.w <head dir>.w <guild>.l <emblem>.l <manner>.w <karma>.B <sex>.B <X_Y_dir>.3B ?.B ?.B <sit>.B <Lv>.B + マップロード時&移動時用、向き付き用キャラ情報? +R 0079 <ID>.l <speed>.w <opt1>.w <opt2>.w <option>.w <class>.w <hair>.w <weapon>.w <head option bottom>.w <sheild>.w <head option top>.w <head option mid>.w <hair color>.w ?.w <head dir>.w <guild>.l <emblem>.l <manner>.w <karma>.B <sex>.B <X_Y_dir>.3B ?.B ?.B <Lv>.B + テレポ等の表示範囲内沸きキャラ用、向き付き無しキャラ情報? +R 007b <ID>.l <speed>.w <opt1>.w <opt2>.w <option>.w <class>.w <hair>.w <weapon>.w <head option bottom>.w <server tick>.l <sheild>.w <head option top>.w <head option mid>.w <hair color>.w ?.w <head dir>.w <guild>.l <emblem>.l <manner>.w <karma>.B <sex>.B <X_Y_X_Y>.5B ?.B ?.B ?.B <Lv>.B + 表示範囲内キャラ移動情報 +R 007c <ID>.l <speed>.w ?.6w <class>.w ?.7w <X_Y>.3B ?.2B + NPC用表示範囲内キャラ情報 +S 007d + mapロード終り +S 007e <client tick>.l + クライアント側1msタイマ送信 +R 007f <server tick>.l + サーバ側1msタイマ送信 +R 0080 <ID>.l <type>.B + type=00 キャラ消滅 (画面外移動。死体消滅等?) + type=01 キャラ死亡 + type=02 キャラ消滅 (logout等?) + type=03 テレポート (テレポ,蝿,蝶等?) +R 0081 <type>.B + login失敗 その2 + type=01 サーバー接続終了 + type=02 同じIDで他の使用者がログインしました + type=03 サーバーとの同期に失敗しました + type=04 地域の収容人員超過で接続できません + type=05 18歳以下は接続できません + type=06 決済されたアカウントではありません + type=07 サーバーが混雑しています + type=08 前の接続情報が残っています + type=0b アカウントが保留されました + type=0c 課金システム変更のため、一時終了します + type=0d IPが一致しないため、接続を終了します + type=10 有料サービスになりました + type=11 チケットが買われていないか、有効期限が切れています +S 0085 <X_Y>.3B + 移動要求 +R 0087 <server tick>.l <X_Y_X_Y>.5B ?.B + 移動応答 +R 0088 <ID>.l <X>.w <Y>.w + 移動途中停止 +S 0089 <target ID>.l <type>.B + type=00 targetを1回殴る + type=02 座る + type=03 立ち上る + type=07 targetを殴り続ける +R 008a <src ID>.l <dst ID>.l <server tick>.l <src speed>.l <dst speed>.l <param1>.w <param2>.w <type>.B <param3>.w + type=00 param1=0 miss + type=00 param1:ダメージ(の合計?) param2:分割数 param3:アサシン2刀流逆手ダメージ + NPCからの攻撃の場合、param2,param3はゴミデータ + speedはPCの場合内部ASPDと一致 + type=01 itemを拾う ID*2以外ゴミ + type=02 座る src ID以外ゴミ + type=03 立つ src ID以外ゴミ + type=08 複数攻撃 + type=09 ダメージモーションなしにダメージだけ表示される物(インデュア) + type=0a クリティカル + type=0b 完全回避 +S 008c <len>.w <str>.?B + 通常発言送信。チャット中はチャット内発言用になる + 先頭の"<nick> : "の部分はクライアント側で付ける事 +R 008d <len>.w <ID>.l <str>.?B + IDさんの発言受信。チャット中はチャット内発言用になる +R 008e <len>.w <str>.?B + 自分の発言受信。チャット中はチャット内発言用になる +S 0090 <ID>.l <type?>.B + NPCに話しかける。typeは01しか見た事無し +R 0091 <map name>.16B <X>.w <Y>.w + 鯖内マップ間移動、テレポ,蝿等用 +R 0092 <map name>.16B <X>.w <Y>.w <IP>.l <port>.w + 鯖間移動 +R 0093 + 8月中に1回だけ観測。謎 +S 0094 <ID>.l + IDのキャラ名等要求。0095か0195の返答があるはず +R 0095 <ID>.l <nick>.24B + NPC,ギルド未所属PCの0094への返答 + 0193 <charID>.l で問い合わせて + 0194 <charID>.l <name>.24B の応答で得てます。 + +S 0096 <len>.w <nick>.24B <message>.?B + wis送信 +R 0097 <len>.w <nick>.24B <message>.?B + wis受信 +R 0098 <type>.B + type=00 wis送信成功 + type=01 wis相手がloginしてない? + type=02 wis相手からignoreされてる? +S 0099 + 神の声送信 +R 009a <len>.w <message>.?B + GMからの天の声 +S 009b <head dir>.w <dir>.B + 体&頭の方向変更要求。クライアントへの応答は無い模様 + dirは00〜07で体の向き。00で北から反時計回りに45°単位で07まで + head dirは00,01,02で頭の向き。00で体と同じ、01が右、02が左 +R 009c <ID>.l <head dir>.w <dir>.B + IDの体&頭の方向変更 +R 009d <ID>.l <item ID>.w <identify flag>.B <X>.w <Y>.w <amount>.w <subX>.B <subY>.B + 移動等で床アイテムが画面内に入ってきた時 +R 009e <ID>.l <item ID>.w <identify flag>.B <X>.w <Y>.w <subX>.B <subY>.B <amount>.w + item drop。何故か009dとマス目内位置&個数が入れ変っている +S 009f <ID>.l + IDの床アイテムを拾う +R 00a0 <index>.w <amount>.w <item ID>.w <identify flag>.B <attribute?>.B <refine>.B <card>.4w <equip type>.w <type>.B <fail>.B + fail=02 取得失敗? + fail=06 ルート権無し。取得失敗 +R 00a1 <ID>.l + IDの床アイテム消去 +S 00a2 <index>.w <amount>.w + 所有アイテムを落す +R 00a3 <len>.w {<index>.w <item ID>.w <type>.B <identify flag>.B <amount>.w ?.2B}.10B* + 所有消耗品&収集品リスト +R 00a4 <len>.w {<index>.w <item ID>.w <type>.B <identify flag>.B <equip type>.w <equip point>.w <attribute?>.B <refine>.B <card>.4w}.20B* + 所有装備リスト +R 00a5 <len>.w {<index>.w <item ID>.w <type>.B <identify flag>.B <amount>.w ?.2B}.10B* + カプラさんに預けてある消耗品&収集品リスト +R 00a6 <len>.w {<index>.w <item ID>.w <type>.B <identify flag>.B <equip type>.w <equip point>.w <attribute?>.B <refine>.B <card>.4w}.20B* + カプラさんに預けてある装備リスト +S 00a7 <index>.w <ID>.l + 所持アイテムindexを使用する。IDは自分のみ? +R 00a8 <index>.w <amount>.w <type>.B + アイテム使用応答。type=00の場合使用失敗? amountもゴミの模様 + type=01の場合成功で、amountは使用後の残り個数 +S 00a9 <index>.w <equip type>.w + アイテム装備 +R 00aa <index>.w <equip point>.w <type>.B + アイテム装備応答。type=00の場合装備失敗? equip pointもゴミの模様 +S 00ab <index>.w + 装備解除 +R 00ac <index>.w <equip point>.w <type>.B + 装備解除応答。type=00の場合失敗? equip pointもゴミの模様 +R 00af <index>.w <amount>.w + アイテム数減少。amount個だけ減らす +R 00b0 <type>.w <val>.l + 色々な能力値の更新。以下type:対応する数値を列挙 + 0000:speed 0003:悪行値 0004:マナーポイント 0005:HP 0006:MaxHP + 0007:SP 0008:MaxSP 0009:ステータスポイント 000b:ベースレベル + 000c:スキルポイント 0018:重量(表示されてる数字の10倍) + 0019:最大重量(表示されてる数字の10倍) + 0029:ATK前 002a:ATK後 002b:MATK前 002c:MATK後 + 002d:DEF前 002e:DEF後 002f:MDEF前 0030:MDEF後 + 0031:HIT 0032:FLEE前 0033:FLEE後 0034:クリティカル + 0035:ASPD(2ms単位の時間?) 0037:ジョブレベル + 0082:謎 ATK後と同じ数字? +R 00b1 <type>.w <val>.l + 色々な能力値の更新。以下type:対応する数値を列挙 + 0001:ベース側経験値 0002:ジョブ側経験値 0014:zeny + 0016:ベース側必要経験値 0017:ジョブ側必要経験値 + β1では00b0はvalがshort、00b1はvalがlongという使い分けがあったんだけど + 今となっては差が無くなって、盲腸みたいなもの? +S 00b2 <type>.B + type=00 死亡時リスタート + type=01 キャラセレ要求 +R 00b3 <type>.B + type=01 キャラセレ応答 +R 00b4 <len>.w <ID>.l <str>.?B + IDのNPCからのメッセージ +R 00b5 <ID>.l + IDのNPCとのメッセージウィンドウに"NEXT"アイコンを出す +R 00b6 <ID>.l + IDのNPCとのメッセージウィンドウに"CLOSE"アイコンを出す +R 00b7 <len>.w <ID>.l <str>.?B + IDのNPCの会話で選択項目表示。各項目は':'で区切られる +S 00b8 <ID>.l <select>.B + IDのNPCの会話の選択。各項目に順に1〜が振られる。ffでキャンセル? +S 00b9 <ID>.l + IDのNPCとの会話。NEXTボタンを押した +S 00bb <type>.w <amount>.B + ステータスup要求。typeは000dから0012が順にSTR,AGI,VIT,INT,DEX,LUKに対応 +R 00bc <type>.w <fail>.B <val>.B + ステータスup応答。fail=01なら成功。typeは00bbと同じ。valは上った後の数字 + 失敗例は見た事無いので謎。ステータスポイントが足りない状態で + 00bbを発行できるクライアントが有れば、fail=00になるのではないかと予想 +R 00bd <status point>.w <STR>.B <STRupP>.B <AGI>.B <AGIupP>.B <VIT>.B <VITupP>.B <INT>.B <INTupP>.B <DEX>.B <DEXupP>.B <LUK>.B <LUKupP>.B <ATK>.w <ATKbonus>.w <MATKmax>.w <MATKmin>.w <DEF>.w <DEFbonus>.w <MDEF>.w <MDEFbonus>.w <HIT>.w <FLEE>.w <FLEEbonus>.w <critical>.w ?.w + まとめてステータス情報を送るパケット +R 00be <type>.w <val>.B + 必要ステータスポイント更新パケット。typeは0020〜0025が順にSTR〜LUKに対応 +S 00bf <type>.B + エモーションを出す。typeは00-0c(,0d)がALT+1〜ALT+9,ALT+0,チョキ,グー,パー(,韓国旗)に対応 + 00=! 01=? 02=うれしい(♪) 03=ハート 04=汗 05=あはは(電球) + 06=いやだな(#) 07=怒り(もやもや)08=お金($) 09=… 0a=チョキ + 0b=グー 0c=パー 0d=韓国旗 0e=大きいハート 0f=ありがとう(thanks) + 10=無念 11=ごめん(sorry) 12=笑い 13=汗かき 14=あの + 15=最高(GoodJob)16=キョロキョロ 17=ショック 18=まる 19=バツ + 1a=ヘルプ(help) 1b=go 1c=えーん 1d=くすくす 1e=ちゅ + 1f=ちゅちゅ 20=ふん 21=うんうん +R 00c0 <ID>.l <type>.B + IDの人がエモーションを出した。typeは00bfと同じ +S 00c1 + login人数問い合わせ +R 00c2 <val>.l + login人数応答 +R 00c3 <ID>.l <type>.B <val>.B + 見た目変更。typeは00で本体(転職時等)、02が武器、03が頭(下)、04が頭(上)、05が頭(中)、08が盾 +R 00c4 <ID>.l + 話かけたNPCが商人だったのでbuy/sell選択窓出 +R 00c5 <ID>.l <type>.B + buy/sell選択。type=00ならbuy。type=01ならsell +R 00c6 <len>.w {<value>.l <DCvalue>.l <type>.B <item ID>.w}.11B* + NPCのお店buy選択時。DCvalueは商人DC後の値段 +R 00c7 <len>.w {<index>.w <value>.l <OCvalue>.l}.10B* + NPCのお店sell選択時。OCvalueは商人OC後の値段 +S 00c8 <len>.w {<amount>.w <item ID>.w}.4B* + NPCのお店から買う +S 00c9 <len>.w {<index>.w <amount>.w}.4B* + NPCのお店に売る +R 00ca <type>.B + NPCから購入終了。type=00成功 +R 00cb <type>.B + NPCへ売却終了。type=00成功 +S 00cc <ID>.l + GM用右クリックメニュー「(name)使用者強制終了」使用 +R 00cd <ID?>.l + GM用右クリックメニュー「(name)使用者強制終了」の応答 + <ID>が0の場合は失敗と表示。(このパケットはただの応答です。接続終了の機能はありません。) +S 00cf <nick>.24B <type>.B + type=00 nickからの発言受け付け拒否 (/ex nick) + type=01 nickからの発言受け付け許可 (/in nick) +S 00d0 <type>len.B + type=00 全ての発言受け付け拒否 (/exall) + type=01 全ての発言受け付け許可 (/inall) +R 00d1 <type>.B <fail>.B + fail=00 発言受け付け拒否成功 + fail=01 発言受け付け拒否失敗 +R 00d2 <type>.B <fail>.B + fail=00 全発言受け付け拒否成功 + fail=01 全発言受け付け拒否失敗 +S 00d5 <len>.w <limit>.w <pub>.B <passwd>.8B <title>.?B + チャット立て。ここからチャット関係が続くけど調べが甘いので補完よろ +R 00d6 <fail>.B + チャット立て応答 +R 00d7 <len>.w <owner ID>.l <chat ID>.l <limit>.w <users>.w <pub>.B <title>.?B + 画面内チャット情報 +R 00d8 <chat ID>.l + チャット消去 +S 00d9 <chat ID>.l <passwd>.8B + チャット参加要請 +R 00da <fail>.B + チャット参加失敗 +R 00db <len>.w <chat ID>.l {<index>.l <nick>.24B}.28B* + チャット参加者リスト +R 00dc <users>.w <nick>.24B + チャットへの参加者追加(?) +R 00dd <index>.w <nick>.24B <fail>.B + チャットから参加者抜け +S 00de <len>.w <limit>.w <pub>.B <passwd>.8B <title>.?B + チャットステータス変更 +R 00df <len>.w <owner ID>.l <chat ID>.l <limit>.w <users>.w <pub>.B <title>.?B + チャットステータス変更成功 +S 00e0 ?.l <nick>.24B + チャットルーム所有者変更要求? +R 00e1 <index>.l <nick>.24B + チャット参加者番号付け直し? +S 00e2 <nick>.24B + チャットkick +S 00e3 + チャット抜け +S 00e4 <ID>.l + 取り引き要求 +R 00e5 <nick>.24B + 取り引き要請受け +S 00e6 <type>.B + type=03 取り引き要請ok + type=04 取り引き要請キャンセル +R 00e7 <fail>.B + 取り引き要求応答 + fail=00 距離が遠過ぎ + fail=03 要請受けてくれた + fail=04 キャンセルされた? +S 00e8 <index>.w <amount>.l + アイテム追加。index=0でzeny追加。正規クライアントではzenyは00ebの直前のみ +R 00e9 <amount>.l <type ID>.w <identify flag>.B <attribute?>.B <refine>.B <card>.4w + 相手方からのアイテム追加 +R 00ea <index>.w <fail>.B + fail=00 アイテム追加成功 + fail=01 追加失敗。相手側重量オーバ +S 00eb + アイテム追加完了(ok押し) +R 00ec <fail>.B + fail=00 自分からのok受領 + fail=01 相手からのok受領 +S 00ed + 取り引きキャンセル +R 00ee + 取り引きがキャンセルされました +S 00ef + 取り引き許諾(trade押し) +R 00f0 + 取り引き完了 +R 00f2 <num>.w <limit>.w + カプラさん許容アイテム個数&現状 +S 00f3 <index>.w <amount>.l + カプラさん倉庫にアイテム放り込み +R 00f4 <index>.w <amount>.l <type ID>.w <identify flag>.B <attribute?>.B <refine>.B <card>.4w + カプラさん倉庫のアイテム追加 +S 00f5 <index>.w <amount>.l + カプラさん倉庫からアイテム取り出し要求 +R 00f6 <index>.w <amount>.l + カプラさん倉庫からアイテム取り出し応答 +S 00f7 + カプラさん倉庫閉じ要求 +R 00f8 + カプラさん倉庫閉じ応答 +S 00f9 <party name>.24B + パーティ作成要求 +R 00fa <fail>.B + パーティ作成応答 + fail=00 「パーティーを結成しました。」 + fail=01 「同じ名前のパーティーがあります。」 + fail=02 「すでにパーティーに加入しています。」 +R 00fb <len>.w <party name>.24B {<ID>.l <nick>.24B <map name>.16B <leader>.B <offline>.B}.46B* + パーティ情報まとめて送り +S 00fc <ID>.l + パーティ勧誘する +R 00fd <nick>.24B <fail>.B + fail=00 相手は既にパーティに入っていた + fail=01 相手に拒否された + fail=02 勧誘成功 +R 00fe <ID>.l <party name>.24B + パーティに誘われた +S 00ff <ID>.l <fail>.l + パーティに誘われた時の返答。fail=1 ok返答? +S 0100 + パーティ脱退要求 +R 0101 <exp>.w <item?>.w + パーティ設定現状? exp=2の場合は公平配分設定失敗? +S 0102 <exp>.w <item?>.w + パーティ設定変更 +R 0104 <ID>.l ?.l <X>.w <Y>.w <offline>.B <party name>.24B <nick>.24B <map name>.16B + パーティ1人分情報更新 +R 0105 <ID>.l <nick>.24B <fail>.B + nickさんがパーティから離脱 +R 0106 <ID>,l <HP>.w <MaxHP>.w + パーティメンバHP更新 +R 0107 <ID>.l <X>.w <Y>.w + パーティメンバ位置更新 +S 0108 <len>.w <message>.?B + パーティ内発言 +R 0109 <len>.w <ID>.l <message>.?B + パーティ内発言受信 +R 010a <type ID>.w + MVPアイテム取得 +R 010b <exp>.l + MVP経験値取得 +R 010c <ID>.l + MVPキャラ表示 +R 010e <skill ID>.w <lv>.w <sp>.w <range>.w <up>.B + スキル情報更新。spは未使用? +R 010f <len>.w {<skill ID>.w <target type>.w ?.w <lv>.w <sp>.w <range>.w <skill name>.24B <up>.B}.37B* + スキル情報の塊。skill nameは一部流れて来ない物がある>AL_PNEUMA,PR_SLOWPOISON等 + target typeは0-パッシブ、1-敵、2-場所、4-即時発動、16-味方 + lv=0 up=0の場合はリストに出してない? +R 0110 <skill ID>.w <basic type>.w ?.w <fail>.B <type>.B + fail=00の時にスキル利用失敗? + type 00:basic typeの方 01:SP不足 02:HP不足 03:memo無し 04:delay中 + 05:お金無し(めまー) 06:武器がよろしくない 07:赤ジェム無し 08:青ジェム無し 09:謎 + basic type 00:取り引き 01:emotion 02:座り 03:チャット 04:パーティ + 05:shout? 06:PK 07:マナーポイント +R 0111 <skill ID>.w <target type>.w ?.w <lv>.w <sp>.w <range>.w <skill name>.24B <up>.B + 010fの1つ分。β2だと未使用? + 20040415転職時に観測 +S 0112 <skill ID>.w + スキルlvup要求 +S 0113 <level>.w <skill ID>.w <ID>.l + IDをターゲットにskillを使う +R 0114 <skill ID>.w <src ID>.l <dst ID>.l <server tick>.l <src speed>.l <dst speed>.l <param1>.w <param2>.w <param3>.w <type>.B + 攻撃系スキルエフェクト@ + type=04 火壁で観測 type=06とほぼ同じ? + type=05 NB/FBlの分散したダメージ用? + type=06 単発もの? param1はダメージ合計、param2はlevel、param3は1固定と予想 + type=07 ダメージ表示無し? + type=08 連打もの? param1はダメージ合計、param2はlevel、param3は分割数と予想 + type=09 ダメージモーションなしにダメージだけ表示される物(インデュア)と思ったのだがダメージモーションが出る物。(機能は謎) +R 0115 <skill ID>.w <src ID>.l <dst ID>.l <server tick>.l <src speed>.l <dst speed>.l <X>.w <Y>.w <param1>.w <param2>.w <param3>.w <type>.B + 弾き飛ばし有り攻撃系スキルエフェクト + type=05 ダメージ&弾き飛ばし。param1はダメージ合計、param2はlevel、param3は分割数と予想 + type=06 爆心地? 少なくともparam1はゴミの模様 + type=09 ダメージモーションなしにダメージだけ表示される物(インデュア)と思ったのだがダメージモーションが出る物。(機能は謎) +S 0116 <level>.w <skill ID>.w <X>.w <Y>.w + (X,Y)をターゲットにskillを使う +R 0117 <skill ID>.w <src ID>.l <val>.w <X>.w <Y>.w <server tick>.l + 場所相手のスキルエフェクト表示。valはレベルか、一部固さ?(氷壁) +S 0118 + 攻撃キャンセル +R 0119 <ID>.l <param1>.w <param2>.w <param3>.w ?.B + 見た目変更 + param1=02 フロストダイバで凍り漬け? + param2=01 毒? + param2=20 ANGELUS状態? + param3=01 サイトかルワッチ? + param3=0b ハイディング状態? + param3=0b クローキング状態? + param3=0d カート付き + param3=0e 鷹付き + param3=0f ペコペコ乗り + +R 011a <skill ID>.w <val>.w <dst ID>.l <src ID>.l <fail>.B + 非ダメージ系スキル表示。ヒールの場合valは回復量 + fail=00の場合失敗ぽいが、スチール以外では見た事無し +S 011b <skill ID>.w <map name>.16B + 011cへの応答。使わない場合"cancel"、マップ内ランダムの場合"Random"を送る +R 011c <skill ID>.w <map1>.16B <map2>.16B <map3>.16B <map4>.16B + テレポ/ポタの場所選択。 + テレポの場合、Random/セーブ場所、ポタの場合、セーブ場所/memo1/memo2/memo3となる + マップ名のみ送られる +S 011d + 現在居る所をメモ要求 +R 011e <fail>.B + fail=00 メモ成功 + fail=01 メモ失敗 +R 011f <dst ID>.l <src ID>.l <X>.w <Y>.w <type>.B <fail>.B + スキル効能地作成 + type 7e:SW 7f:火壁 80:ポタ発動中 81:ポタ発動前 83:サンク 85:フニューマ + 86:バーミリオン 8c:トーキーボックス発動時 8d:氷壁 8e:くあぐまいやー 91:あんくるすねあ + 93:らんどまいん 97:?? 99:トーキーボックス発動前 + 他情報求む +R 0120 <ID>.l + スキル効能地消去 +R 0121 <num>.w <num limit>.w <weight>.l <weight limit>l + カートの種類&重さの現在値&上限 +R 0122 <len>.w {<index>.w <item ID>.w <type>.B <identify flag>.B <equip type>.w <equip point>.w <attribute?>.B <refine>.B <card>.4w}.20B* + カート内アイテム。装備品 +R 0123 <len>.w {<index>.w <item ID>.w <type>.B <identify flag>.B <amount>.w ?.2B}.10B* + カート内アイテム。消耗品/収集品 +R 0124 <index>.w <amount>.l <item ID>.w <identify flag>.B <attribute?>.B <refine>.B <card>.4w + カートにアイテム追加 +R 0125 <index>.w <amount>.l + カートからアイテム削除 +S 0126 <index>.w <amount>.l + カートにアイテムを入れる +S 0127 <index>.w <amount>.l + カートからアイテムを取り出す +S 0128 <index>.w <amount>.l + カプラさんからカートへアイテムを移す +S 0129 <index>.w <amount>.l + カートからカプラさんへアイテムを移す +R 012c <fail>.B + fail=00 「重量オーバーです。」 + fail=01 「アイテム最大種類数を超過しました。」 +R 012d <num>.w + 露店開設。アイテムリスト要求。numは置ける最大数 +S 012e + 露店閉鎖 +S 012f <len>.w <message>.80B {<index>.w <amount>.w <value>.l}.8B* + 露店開設、露店名&アイテム,値段リスト +S 0130 <ID>.l + 露店アイテムリスト要求 +R 0131 <ID>.l <message>.80B + 露店看板表示 +R 0132 <ID>.l + 露店看板消去 +R 0133 <len>.w <ID>.l {<value>.l <amount>.w <index>.w <type>.B <item ID>.w <identify flag>.B <attribute?>.B <refine>.B <card>.4w}.22B + 露店アイテムリスト +S 0134 <len>.w <ID>.l {<amount>.w <index>.w}.4B* + 露店アイテム購入 +R 0135 <index>.w <amount>.w <fail>.B + 露店アイテム購入失敗。 + fail=1 「お金が足りません。」 + fail=2 「重量オーバーです。」 +R 0136 <len>.w <ID>.l {<value>.l <index>.w <amount>.w <type>.B <item ID>.w <identify flag>.B <attribute?>.B <refine>.B <card>.4w}.22B* + 露店開設成功 +R 0137 <index>.w <amount>.w + 露店アイテム販売報告 +R 0139 <ID>.l <X>.w <Y>.w <X2>.w <Y2>.w <range>.w + IDの敵は(X,Y)に居て自分は(X2,Y2)に居るので攻撃が届きませんでした + 攻撃可能距離はrangeなので、近寄って下さい? +R 013a <val>.w + 攻撃射程 +R 013b <type>.w + 各種メッセージ表示。3=矢が装備できました +R 013c <ID>.w + 装備された矢のItemID。0で、未装備状態。 +R 013d <type>.w <val>.w + HP回復スキル/SP回復スキルによる回復 + type=5ならHP type=7ならSP +R 013c <index>.w + 装備している矢 + +R 013e <src ID>.l <dst ID>.l <X>.w <Y>.w <lv?>.w ?.w <wait>.l + スキル詠唱中。PC/NPCが相手の場合は(X,Y)は0。場所がターゲットの場合はdst IDは0になる + + 0x013e の offset+16(dword) はスキル属性です(調査済)。 + 00:無 01:水 02:地 03:火 04:風 05:毒 06:聖 07:暗 08:念 09:死 + 将来的に、詠唱中のエフェクトが属性で変わるのかと。 + + waitはms単位かな? +R 0141 <type>.l <base>.l <bonus>.l + ステータス情報。typeは0dから12が順にSTR,AGI,VIT,INT,DEX,LUKに対応 + base+bonusと表示される +R 0142 <ID>.l + 数値入力用窓表示 +S 0143 <ID>.l <amount>.l + 0142の窓に入力した数値の送信 +R 0144 <ID>.l <type>.l <X>.l <Y>.l <point ID>.B <color>.3B ?.B + 案内員用、マップ上アイコン表示パケット + type=1 アイコンを表示 + type=2 アイコンを消去 +R 0145 <file name>.16B <type>.B + (今の所)カプラさんcutin表示 + type=02 表示 + type=ff 消去 +S 0146 <ID>.l + IDのNPCとの会話。CLOSEボタンを押した。ack無しにNPCメッセージウィンドを同時に閉じる +R 0147 <skill ID>.w <target type>.w ?.w <lv>.w <sp>.w <range>.w <skill name>.24B <up>.B + アイテム利用の結果一時的に得られたスキル情報 +R 0148 <ID>.l + リザレクションの相手決め? @ β1 +S 0149 <ID>.l <type>.B <time>.w + IDにチャット禁止時間を付加 type=0 マイナス type=1 プラス + tymeは分単位 +R 014a <fail>.l + マナーポイントを与えた結果。fail=0 成功 fail=1 失敗 +R 014b <type>.B <nick>.24B + マナーポイントを貰った。type=00 プラス type=01 マイナス +R 014C <len>.w (<type>.l <guildID>.l <guild name>.24B).* + 同盟・敵対ギルド表示 + type=0 同盟 + type=1 敵対 +S 014D + ギルド情報表示開始? +R 014E <type?>.l + type=0x57 一般ギルド団員 + type=0xD7 ギルドマスター +S 014F <page>.l + ギルド表示タブ送信 +R 0150 <guildID>.l <guildLv>.l <接続数>.l <定員>.l <Avl.lvl>.l <経験値>.l <next_exp>.l <上納ポイント>.l <性向左右VW>.l <性向上下RF>.l <人数?>.l <guild name>.24B <guild master>.24B <本拠地>.16B + ギルド情報 +S 0151 <guild ID>.l + エンブレム要求 +R 0152 <len>.w <guild ID>.l <emblem ID(変更回数?)>.l <emblem data>.?B + エンブレムイメージ送付 +S 0153 <len>.w <emblem data>.?B + エンブレムイメージ変更 +R 0154 <len>.w {<accID>.l <charactorID>.l <髪型>.w <髪の色>.w <性別?>.w <job>.w <lvl?>.w <上納経験値>.l <online>.l <Position>.l <メモ?>.50B <nick>.24B}* + ギルドメンバリスト +S 0155 <len>.w {<accID>.l <charaID>.l <index>.l}.12B* + 役職変更 +R 0156 <len>.w {<accID>.l <charaID>.l <index>.l}.12B* + 役職変更通知 +S 0159 <guildID>.l <accID>.l <charID>.l <mess>.40B + ギルド脱退送信 +R 015A <nic>.24B <mess>.40B + ギルド脱退(全員)受信 +S 015B <guildID>.l <accID>.l <charID>.l <mess>.40B + ギルド追放送信 +R 015C <nick>.24B <mess>.40B <アカウントID>.24B + ギルド追放(全員)受信 +S 015D <guild name>.24B <?>.16B + ギルド解散 +R 015E <fail>.l + ギルド解散正否通知 + 0x00:成功 + 0x01:住民登録番号不一致 +R 0160 <len>.w {<index>.l <mode>.l <index>.l <exp_mode>.l}.16B + 職位情報 +S 0161 <len>.w {<index>.l <mode>.l <index>.l <exp_mode>.l <nickname>.24B}.40B* + 職位変更 +R 0162 <len>.w <skill_point>.w {<skill ID>.w <target type>.w ?.w <lv>.w <sp>.w <range>.w <skill name>.24B <up>.B}.37B* + ギルドスキル +R 0163 <len>.w { <nick>.24B <アカウントID>.24B <追放理由>.40B }.88B* + ギルド追放リスト +S 0165 <myaccID>.l <guild name>.24B + ギルド作成 +R 0166 <len>.w {<index>.l <name>.24B }.28B* + 職位名リスト +R 0167 <type>.b + ギルド作成合否 + type = 0 ギルド作成成功 + type = 1 すでにギルドに所属している + type = 2 同名のギルドがある + type = 3 エンペリウム無し +S 0168 <TargetAccID>.l <sourceAccID>.l <myCharactorID>.l + ギルド勧誘 +R 0169 <type>.B + ギルド勧誘結果 + type = 0 他のギルドに加入している + type = 1 拒否された + type = 2 加入した + type = 3 定員を超過 +R 016A <guild ID>.l <guild name>.24B + ギルド勧誘された +S 016B <guild ID>.l <type>.l + ギルド勧誘返信 + type=0 拒否する + type=1 許諾する +R 016c <guild ID>.l <emblem ID>.l <mode>.l ?.5B <guild name>.24B + login時ギルド情報 + mode=自分の所属職位のmode +R 016d <ID>.l <charactor ID>.l <online>.l + ギルドメンバがloginした抜けた等 +S 016E <guildID>.l <mess1>.60B <mess2>.120B + ギルド告知設定 +R 016F <mess1>.60B <mess2>.120B + ギルド告知 +S 0170 <TargetAccID>.l <sourceAccID>.l <myCharactorID>.l + 同盟要請勧誘 +R 0171 <SorceAccID>.l <guild name>.24B + 同盟要請勧誘された +S 0172 <SorceAccID>.l <type>.l + 同盟要請返信 + type=0 拒否する + type=1 許諾する +R 0173 <type>.B + type = 0 すでに同盟関係 + type = 1 同盟拒否された + type = 2 同盟成功 + type = 3 相手ギルドの同盟ギルド数が超過 + type = 4 自分のギルドの同盟ギルド数が超過 +R 0174 <len>.w {<index>.l <mode>.l <index>.l <exp_mode>.l <nickname>.24B}.40B* + 職位変更通知 +R 0177 <len>.w <index>.w* + 鑑定可能アイテムリスト +S 0178 <index>.w + アイテム鑑定 +R 0179 <index>.w <fail>.B + アイテム鑑定結果。fail=00で成功。fail=01ってあるのか? +S 017A <index>.w + カードWクリック +R 017B <len>.w {<index>.w}* + カード挿入できるアイテムIndex番号 +S 017C <SrcIndex>.w <DescIndex>.w + Src をDescに突っ込む +R 017D <DescIndex>.w <SrcIndex>.w <fail>.b + Src をDescに突っ込み<fail> 0=成功 1=失敗? +S 017e <len>.w <message>.?B + ギルド内メッセージ発言 +R 017f <len>.w <message>.?B + ギルド内メッセージ受信 +S 0180 <charactorID>.l + ギルド敵対 +R 0181 <flag>.b + ギルド敵対可否 + flag=0 敵対成功 + flag=1 敵対ギルド数が多すぎる + flag=2 すでに敵対関係 +R 0182 <accID>.l <charactorID>.l <髪型>.w <髪の色>.w <性別?>.w <job>.w <lvl?>.w <上納経験値>.l <online>.l <Position>.l ?.50B <nick>.24B + +S 0183 <guild_id>.l <type>.l + ギルド関係解消 +R 0184 <guild_id>.l <type>.l + ギルド関係解消通知 +R 0185 <flag>.l <guild_id>.l <guild_name>.24B + ギルド関係追加 flag=0 同盟/ 1 敵対 +R 0187 <account ID>.l + alive信号? +R 0188 <fail?>.w <index>.w <val>.w + 武器精錬。結果+val武器に +R 0189 <fail?>.w + 謎。テレポ(ワープポータルmemo)失敗? + 1 memo禁止地域 +S 018a ?.w + ゲーム終了 +R 018b <fail>.w + ゲーム終了/キャラセレ応答。fail=0成功。fail=1失敗? +R 018C <MonsID>.w <等級>.w <大小>.w <生命値>.w <?>.w <防御力>.w <種族>.w <魔法防御力>.w <属性>.w <魔法相性属性>.9b + wizの敵のセンス結果 + 0 小型 + 1 中型 + 2 大型 +R 018d <length>.w { <ItemID>,w ?.w <CharID?>.l }.8B* + 製造可能アイテムリスト表示(?の部分は0012固定) +S 018e <MakeItemID>.w <slot1ItemID>.w <slot2ItemID>,w <slot3.ItemID>.w + 製造アイテムの選択 +R 018f <flag>.w [00] <MakeItemID>.w + 製造結果(flag = 00なら成功 01なら失敗) +S 0190 <skill lv>.w <skill code>.w <x & y>.l <message>.80b + メッセージ(トーキーボックス) +R 0191 <ID>.l <message>.80B + トーキーボックスのメッセージ +R 0192 <x & y>.l <type>.w <mapname>.16b + アイスウォール等による地形属性変化 +S 0193 <ID>.l + ギルドメンバ名前引き? +R 0194 <ID>.l <nick>.24B + ギルドメンバ名前引き応答? +R 0195 <ID>.l <nick>.24B <party name>.24B <guild name>.24B <class name>.24B + ギルド所属PCの場合の0094返答 +R 0196 <type>.w <ID>.l + 増強系スキル使用時のメッセージ色々。IDはtargetと思われるが自分相手のみしか来ない? + type=00 2HQ付与「攻撃速度が増加しました。」 + type=01 2HQ解除「攻撃速度が減少しました。」 + type=02 IMPOSITIO付与「武器の攻撃力が増加しました。」 + type=03 IMPOSITIO解除「武器の攻撃力が減少しました。」 + type=04 「スキル使用ディレイが減少しました。」 + type=05 「スキル使用ディレイが元に戻りました。」 + type=06 「武器に毒属性が付与されました。」 + type=07 ASPERSIO付与「武器に聖属性が付与されました。」 + type=08 ASPERSIO解除「武器の属性が元に戻りました。」 + type=09 「防具に聖属性が付与されました。」 + type=0a 「防具の属性が元に戻りました。」 + type=0b KYRIE付与「バリア状態になりました。」 + type=0c KYRIE解除「バリア状態が解除されました。」 + type=0d 「ウェポンパーフェクションモードになりました。」 + type=0e 「ウェポンパーフェクションモードが解除されました。」 + type=0f 「オーバートラストモードになりました。」 + type=10 「オーバートラストモードが解除されました。」 + type=11 「マキシマイズパワーモードになりました。」 + type=12 「マキシマイズパワーモードが解除されました。」 +R 0196 <type>.w <ID>.l <switch>.b (コモド以降) + switch=0で解除,1で付加 + type=00 プロボック + type=01 インデュア + type=02 「攻撃速度が増加しました。」(2HQ) + type=03 集中力向上 + type=04 ハイディング + type=05 クローキング + type=06 「武器に毒属性が付与されました。」(エンチャントポイズン) + type=07 「毒を反射できる状態になりました。」(ポイズンリアクト) + type=08 「クァグマイア状態になり、・・・」 + type=09 「エンジェラス状態になり、・・・」 + type=0a ブレッシング + type=0b シグナムクルシス + type=0c 「速さが増加しました」(速度増加) + type=0d 「速さが減少しました」(速度減少) + type=0e 「スローポイズン状態になりました」(スローポイズン) + type=0f 「武器の攻撃力が増加しました」(インポシティオマヌス) + type=10 「スキル使用ディレイが減少しました」(サフラギウム) + type=11 「武器に聖属性が付与されました」(アスペルシオ) + type=12 「防具に聖属性が付与されました」(聖体降臨) + type=13 「バリア状態になりました」(キリエエレイソン) + type=14 「マグニフィカート状態になりました」 + type=15 「グロリア状態になりました」 + type=16 「レックスエーテルナ状態になりました」 + type=17 「アドレナリンラッシュ状態になりました」 + type=18 「ウェポンパーフェクションモードになりました」 + type=19 「オーバートラストモードになりました」 + type=1a 「マキシマイズパワーモードになりました」 + type=1b ペコペコ騎乗 + type=1c 鷹 + type=1d 死んだふり + type=1e 「叫びました」(ラウドボイス) + type=1f 「エナジーコート状態になりました」 + type=20 「防具が壊れました」 + type=21 「武器が壊れました」 + type=22 謎(目のアップというアイコン:盲目??) + type=23 重量50%超え + type=24 重量90%超え + type=25 謎「攻撃速度が増加しました」(x2アイコン:速度系ポーション?) + type=26 謎「攻撃速度が増加しました」(x2アイコン:速度系ポーション?) + type=27 謎「攻撃速度が増加しました」(x2アイコン:速度系ポーション?) + type=28 (未使用っぽい?:不可と解除で効果が違う) + type=29 謎「速さが増加しました」(白いアイコン) + type=32 ストリップウエポン + type=33 ストリップシールド + type=34 ストリップアーマー + type=35 ストリップヘルム + type=36 ケミカルウェポンチャージ + type=37 ケミカルシールドチャージ + type=38 ケミカルアーマーチャージ + type=39 ケミカルヘルムチャージ + type=3a オートガード + type=3b リフレクトシールド + type=3d プロヴィデンス + type=3e ディフェンダー + type=41 オートスペル + type=44 スピアクィッケン + type=56 爆裂波動(アイコンは表示されません。) + type=57 金剛(表示はされないが金剛の解除はされるようです。) + type=59 コンボディレイ + type=5a フレイムランチャー + type=5b フロストウェポン + type=5c ライトニングローダー + type=5d サイズミックウェポン + +S 0197 <type>.w + type=0 /resetstate + type=1 /resetskill + 効能は無し? +R 0199 <type>.w + type=1 pvpモード開始? + type=3 gvgモード開始? +R 019a <ID>.l <rank>.l <num>.l + pvp順位 rank/num +R 019b <ID>.l <type>.l + 他人のlvupや武器精錬等の表示? + type=0 base lvup? + type=1 job lvup? + type=2 武器精錬失敗 + type=3 武器精錬成功 + +R 019d <?>.4B + GMコマンド/hide + +S 0149 <ID>.l <type>.B <time>.w + GM用右クリックメニュー「チャット禁止時間を下げる(解ける)」使用 → type=00 + GM用右クリックメニュー「チャット禁止時間を上げる(掛ける)」使用 → type=01 + timeは分単位です(確か + +R 019e + 捕獲モンスター決め +S 019f <ID>.l + 捕獲モンスター指定 +R 01a0 <fail>.B + 捕獲判定 + fail=01で成功、00で失敗 +S 01a1 <param>.1B + <param> + 0x00:ペット状態表示 + 0x01:餌を与える + 0x02:パフォーマンス + 0x03:卵に戻す + 0x04:アクセサリ解除 +R 01a2 <pet name>.24B <name flag>.B <lv>.w <hungry>.w <friendly>.w <accessory>.w + ペットの状態 + name flag:00=名前未設定 01=名前設定済み(変更不可) + lv=ペットのレベル、hungry=満腹度(0~100)、friendly=親密度(初期値250?)、accessory=アクセサリのItemID +R 01a3 <fail>.B <itemID>.w + <fail> + 0x00:餌やり失敗 + 0x01:餌やり成功 +R 01a4 <type>.B <ID>.l <val>.l + ペット関連通知 + type=00,val=00 ペット孵化時に送られてくる。ペット認識用? + type=01 親密度変化 + type=02 満腹度変化 + type=03 アクセサリ変化(0で未装備) + type=04 パフォーマンス 確認されたval=1~3 + (4はスペシャルパフォーマンス?) + type=05 ?確認されたval=0x14 +S 01a5 <pet name>.24B + ペットの名前決め +R 01a6 <len>.w <index>.w* + ペットの卵リスト +S 01a7 <index>.w + ペットの卵リストが選択された +S 01a9 <emotion>.l + ペットエモーション送信 +R 01aa <ID>.l <emotion>.l + ペットエモーション受信 + <emotion> + 33以下のとき:エモーション + 34以上のとき:発言テーブル? +R 01ac <object id>.l + アンクルの発動(≠設置)時のみ毎回出現(機能は謎) +R 01ad <len>.l <item>.w + 矢作りの作成可能ITEM表受信 +S 01ae <itemID>.w + 矢作りで使う材料送信 +S 01af <type>.w + チェンジカート(カート選択) + type=1 ノーマルカート +R 01b0 <monster id>.l <?>.b <new monster code>.l + 油のクラスチェンジ + <new monster code>はチェンジ後のコード(1001〜)をdwordで +S 01b2 <len>.w <message>.80B <flag>.B {<index>.w <amount>.w <value>.l}.8B* + 露店開設 + flag : 0=キャンセル , 1=オープン +R 01b3 <filename>.64B <type>.B + R 0145の上位互換 +R 01B6 <guildID>.l <guildLv>.l <connum>.l <定員>.l <Avl.lvl>.l <now_exp>.l <next_exp>.l <上納ポイント>.l <性向F-V>.l <性向R-W>.l <members>.l <guild name>.24B <guild master>.24B <agit?>.20B + ギルド情報 +R 01b9 <ID>.I + 被ダメ等によるIDの詠唱中断 +R 01c4 <index>.w <amount>.l <itemID>.w <item data>.12B + カプラ倉庫アイテム +R 01c8 <index>.w <item ID>.w <ID>.l <amount left>.w <type>.B + アイテム使用応答。(00a8の上位バーション?) + type=00の場合使用失敗? amountもゴミの模様 + type=01の場合成功で、amountは使用後の残り個数 +R 01c9 <dst ID>.l <src ID>.l <X>.w <Y>.w <type>.B <fail>.B ?.81b + スキル効能地作成(011fの上位バーション?) + type 0x7e:SW、0x7f:火壁、0x80 ポタ開き中、0x81 ポタ開き直前 + 0x82 聖体、0x83 サンク、0x84 マグヌス、0x85 ニューマ + 0x86 0x86 大魔法(SG/MS/LoV/GX)、0x87 ファイヤーピラ待機 + 0x88 ファイヤーピラ爆発、0x87〜0x8B 表示無し、 + 0x8c トーキーボックス(発動中)、0x8D アイスウォール + 0x8E クワグマイア、0x8f ブラストマイン、0x90 スキッド + 0x91 アンクル、0x92 ベノムダスト、0x93 ランドマイン + 0x94 ショックウェーブトラップ、0x95 サンドマン + 0x96 フラッシャー、0x97 フリージングトラップ + 0x98 クレイモアートラップ、0x99 トーキーボックス + 0x9A ボルケーノ、0x9B デリュージ、0x9C バイオレントゲイル + 0x9D ランドプロテクター、0x9E Zenyマーク、0x9F Zeny袋 + 0xA0 回る緑の輪、0xA1 ピンクの音符 (二連符有り + 0xA2 真ん中に点のある光の玉、0xA3 ピンクのスプリング + 0xA4 深淵の中に、0xA5 回る青い輪、0xA6 不協和音 + 0xA7 口笛、0xA8 夕陽のアサシンクロス、0xA9 ブラギの詩 + 0xAA イドゥンの林檎、0xAB 自分勝手なダンス、0xAC ハミング + 0xAD 私を忘れないで…、0xAE サービスフォーユー + 0xAF ピンクのスプリング、0xB0 表示無し + 0xB0 グラフィティ, + 0xB1 デモンストレーション、0xB2〜0xBF 表示無し + 0xB2 ピンクのワープポータル風 + 0xB3 小さな十字架がふよふよ + 0xB4 バジリカ、0xB5 エフェクトなし? + 0xB6 黒い×が立体的に浮かび上がる + 0xB7 クモの巣、0xB8〜 エフェクトなし? + + 他情報求む + ?.81bは謎。 +R 01cd (<sid>.l)x7 + オートスペル選択肢受信 + <sid>x7 には NB,CB,FB,LB,SS,FBL,FD の順でスキルコードがdwordで入る + まだ選択できないスキルの部分は <sid> = 0x00000000 が入る +S 01ce <sid>.l + オートスペル選択肢送信 +R 01cf <crusader id>.l <target id>.l <?>.18b + 献身状態ターゲットON/OFF。献身が切れると <target id> が 0x00000000 になる + +R 01d0 <ID>.l <num>.w + <num> : 気功の数(非Lv) +R 01d1 <monk id>.l <target monster id>.l <bool>.l + 白羽取り状態ON/OFF。<bool> は白刃取り成立時に 0x00000001 解除時に 0x00000000 が来る +R 01d2 <id>.l <delay>.l + モンクのコンボディレイ(msec) + 三段・連打は基本ディレイ1000(+300)、猛龍は基本ディレイ700(+300) +R 01d4 <ID>.l + 文字列入力窓表示(IDはNPCのIDが入る) +S 01d5 <len>.w <ID>.l <input>.?B 00 + 文字列入力内容送信(IDはNPCのIDが入る) +R 01d7 <ID>.l <equip point>.b <item id1>.w <item id2>.w + 装備グラフィック <equip point> は 02手と09足のみ確認。id2は左手 +R 01d8 <ID>.l <speed>.w <opt1>.w <opt2>.w <option>.w <class>.w <hair>.w <item id1>.w <item id2>.w <head option bottom>.w <head option top>.w <head option mid>.w <hair color>.w ?.w <head dir>.w <guild>.l <emblem>.l <manner>.w <karma>.B <sex>.B <X_Y_dir>.3B ?.B ?.B <sit>.B <Lv>.B ?.B + マップロード時&移動時用、向き付き用キャラ情報?(0078の上位バージョン) +R 01d9 <ID>.l <speed>.w <opt1>.w <opt2>.w <option>.w <class>.w <hair>.w <item id1>.w <item id2>.w.<head option bottom>.w <head option top>.w <head option mid>.w <hair color>.w ?.w <head dir>.w <guild>.l <emblem>.l <manner>.w <karma>.B <sex>.B <X_Y_dir>.3B ?.B ?.B <Lv>.B ?.B + テレポ等の表示範囲内沸きキャラ用、向き付き無しキャラ情報?(0079の上位バージョン) +R 01da <ID>.l <speed>.w <opt1>.w <opt2>.w <option>.w <class>.w <hair>.<item id1>.w <item id2>.w <head option bottom>.w <server tick>.l <head option top>.w <head option mid>.w <hair color>.w ?.w <head dir>.w <guild>.l <emblem>.l <manner>.w <karma>.B <sex>.B <X_Y_X_Y>.5B ?.B ?.B ?.B <Lv>.B ?.B + 表示範囲内キャラ移動情報(007bの上位バージョン) +S 01db + 暗号化key要求 +R 01dc <len>.w <key>.?B + 暗号化key送付 +S 01dd <version>.l <account name>.24B <md5 binary>.16B <version2>.1B + id&暗号化済みpass送信 + 順にクライアントが01dbを送る、 + 鯖が01dcでkeyを返す、 + クライアントが"<key><password>"についてmd5計算し + <md5 binary>の所を埋めて01ddを送る。 + <passwordencrypt2>の時は + "<key><password>"に対してmd5計算としている所を + "<password><key>"と変更する +R 01de <skill ID>.w <src ID>.l <dst ID>.l <server tick>.l <src speed>.l <dst speed>.l <param1>.l <param2>.w <param3>.w <type>.B + 攻撃系スキルエフェクト@(0114の上位バーション?) + type=04 火壁で観測 type=06とほぼ同じ? + type=05 NB/FBlの分散したダメージ用? + type=06 単発もの? param1はダメージ合計、param2はlevel、param3は1固定と予想 + type=07 ダメージ表示無し? + type=08 連打もの? param1はダメージ合計、param2はlevel、param3は分割数と予想 + type=09 ダメージモーションなしにダメージだけ表示される物(インデュア)と思ったのだがダメージモーションが出る物。(機能は謎) +S 01df <ID>.| + GM右クリックによるIDのチャット禁止回数参照? +R 01e1 <ID>.l <num>.w + <num> : 気功の数(非Lv) 一度表示したら後どんなnumが来ても無視される。 +R 01e6 <partner name>.24B + 結婚スキルあなたに逢いたい使用時の叫び声 +S 01e7 + スパノビで/doridoriしたら飛んでくる。SPR回復量2倍フラグを立てるパケット +S 01e8 <party name>.24B <item1>B <item2>B + <item1>アイテム収集方法。0で個人別、1でパーティ公有 + <item2>アイテム分配方法。0で個人別、1でパーティに均等分配 + (00f9の上位バーション) +R 01ea <ID>.l + 結婚エフェクト(音楽、紙吹雪) + IDは新婦のものが入る? +S 01ed + スパノビが爆裂波動になるフラグを立てるパケット +R 01ee <len>.w {<index>.w <item ID>.w <type>.B <identify flag>.B <amount>.w ?.2B <card>.4w}.18B* + 所有消耗品&収集品リスト + 矢の場合は?.2Bが0x8000になる + 00a3から変更 +R 01ef <len>.w {<index>.w <item ID>.w <type>.B <identify flag>.B <amount>.w ?.2B <card>.4w}.18B* + カート内アイテム。消耗品/収集品 + 0123から変更 +R 01f0 <len>.w {<index>.w <item ID>.w <type>.B <identify flag>.B <amount>.w ?.2B <card>.4w}.18B* + カプラさんに預けてある消耗品&収集品リスト + 00a5から変更 +R 01f4 <name>.24B <trade id?>.L <LV>.w + 先方から取引要請 + 00e5から変更 +R 01f5 <result>.B <trade id?>.L <LV>.w + こちらからの取引要請に対する反応 + 00e7から変更 +S 0200 <login name>.24B + ragexeに/accountオプションをつけて起動するとログイン要求に付加されるパケット +S 0204 <?>.16B + ログイン要求に付加されるパケット。16バイトは固定? +S 020B <?>.17B + キャラクタサーバ接続要求0065に付加されるパケット。1+0204の16バイトで17バイト? diff --git a/doc/command.txt b/doc/command.txt new file mode 100644 index 0000000..c53035c --- /dev/null +++ b/doc/command.txt @@ -0,0 +1,40 @@ +This file has explained some special commands. +It begins to explain the declared commands in this file. +Furthermore, many of the special command which is not stated in this file are not mounted. + + +Index + 1. mapmove + 2. b and nb + + +1. MapMove + MapMove command is a warping command which a GM can use on the server. + There is no restriction such, that presently you can use, just have GM enabled. The usage where it is possible + to use as /mm or /mapmove is as follows. + /mm <fieldname>* X Y + /mm <fieldname.gat>* X Y + /mapmove <fieldname>* X Y + /mapmove <fieldname.gat>* X Y + * means for field name type /where, please input the map name which appears in the execution time. gat is not always a necessary. + (X Y) X coordinate and the Y-coordinate of the place where you would like to go, exact coordinates. However it does + if no X or Y, it will jump randomly. + Example: + /mm payon + /mapmove prontera.gat 167 158 + +2. Broadcast + Broadcastコマンドとは実際のサーバでGMが使えるGMメッセージと同様の働きをします。 + /bもしくは/nbとして使うことができます使用方法は以下の通りです。 + /b <message> + /nb <message> + + /b + 実行したユーザ名付きでメッセージを表示します。 + /nb + 実行したユーザ名を表示せずメッセージを表示します。 + + 例: + /b テスト + /nb テスト +$Id: command.txt,v 1.1 2004/05/28 03:55:43 wizputer Exp $ diff --git a/doc/conf_ref.txt b/doc/conf_ref.txt new file mode 100644 index 0000000..7f3fbcd --- /dev/null +++ b/doc/conf_ref.txt @@ -0,0 +1,1981 @@ +========================================================================== +eAthena dev 1.0.0 mod1004 Reference +alpha of the present conf +-------------------------------------------------------------------------- + +< What this file is. > + + It is the reference of the setting method of an Athena setting file. +although it is not a HowTo, it not Those who cannot use Athena even if they see this + To give up obediently is safer. + + +< The list of conf > + + login_athena.conf A setup of login-server + char_athena.conf A setup of char-server + inter_athena.conf A setup of inter-server + map_athena.conf A setup of map-server + battle_athena.conf A setup of map-server (setup of a special rule etc.) + atcommand_athena.conf A setup of map-server (setup of the GM command or @ command) + ladmin_athena.conf A setup of ladmin ('c' version) + + +< The fundamental setting method > + +One line "key: Enter as a value." +<Example> +key: value + +Head of the sentence It will become a comment if it begins by //. +<Example> +//Since this line is a comment, it is not processed. + +< Two or more same items > + +Priority is given to what was written later unless it is written especially clearly that two or more same items are written. +login_athena.conf allow and deny -- and -- map_athena.conf map, npc, etc. +Another processing will be carried out if two or more lines are written. + +========================================================================== +1. conf/login_athena.conf +-------------------------------------------------------------------------- + +< What this file is. > + + A setup of login-server (server which manages account) is described. + It mainly becomes a setup of an administrator. + + +< Explanation of a key > + +If you change one of these parameters, you must restart login-server to update. +If you repeat one parameter (except 'allow', 'deny' or 'ladminallowip') in the configuration file, only the latest will be validated. + 'Allow', 'deny' and 'ladminallowip' parameters are list parameters. Add as many 'allow', 'deny' or 'ladminallowip' as you need. + +login_port + Port to bind login-server to (always binds to all IP addresses) + It is the port used by login-server. It can be omitted and a default is 6900. + Default value: 6900. + +admin_pass + It is the administrator password used to administrate the login-server through a remote connection. + You will found some tools in the tool directory, and specially the tool + ./tool/ladmin (for Login ADMINistration), a perl software, which manages all accounts. + Void password will not work. + NOTICE: You must change this or attackers can exploit your server. + Default value: admin. CHANGES this default value to avoid hack. + +ladminallowip + It's a list parameter. To add an item in this list, just add a new line. Each line can only have 1 parameter. + This list indicates the IP that the server accepts for a remote administration. + This parameter accepts IP descriptions, like: + IP or the begining of IP: it's a characters match. Write an IP (123.456.789.012) or the begining of the IP (123.456.). + Because it's a characters match, the IP 123.4 matches with 123.4.xxx.yyy and 123.4z.xxx.yyy. So, add a final '.' to be sure of the IP. + Example: + allow: 127.0.0.1 + allow: 192.168.10. + IP with number of bits for a network: it's a logical match. Write the network like this: 123.456.789.012/<#_of_mask_bits> + Don't use the final '.', but use all four values. + Example: + allow: 127.0.0.1/32 (match only 1 IP, because 32 bits match all bits). + allow: 192.168.10/24 (matches the network begining by 192.168.10). + IP with mask of a network: it's a logical match. Write the network like this: 123.456.789.012/345.678.901.234 + Don't use the final '.', but use all four values for the IP and the mask. + Example: + allow: 127.0.0.1/255.255.255.255 (match only 1 IP). + allow: 192.168.10.0/255.255.255.0 (matches the network begining by 192.168.10). + all: matches any IP. + Example: + allow: all. + clear: clears the list at this point. Really useful for 'import' parameter, in the new configuration file. + Example: + allow: clear. + Add as many IP's as you wish. + Default value: all. + +gm_pass + It is the required password when a player wants to change its (normal) account to a GM (Game Master) account. + It is used by the @gm command. + Level of gm is set with level_new_gm parameter. + NOTICE: You should also change this one. + Default value: gm. CHANGES this default value to avoid player hack. + +level_new_gm + Level of new GM created with @gm command. (default: 60) + If you set to 0, you disable creation of new GM with @gm. + To be able to create a gm with @gm, you must: + - give a level to this value (not 0) + - enable to level 0 the @gm command (atcommand_athena.conf) (default 100) + - enable gm commands to normal player (battle_athena.conf, atcommand_gm_only parameter) + - and normal player must give correct password when he use the @gm command + Possible value: 0 to 99 + Default value: 60 + +new_account + It is whether to permit new account creation. + When allowed, the player must add _F or _M at the end of its login account to create a new account. + This extension of the account gives the sex of the new created account. + Without the _F/M, the account must have at least 4 characters. + The account will not have e-mail to protect characters against deletion. + The given password at first connection (when the account is created) is the account password. + The value can be: 1 (to allow creation) or 0 (to forbid creation). + Default value: 1. + +account_filename + It specifies the accounts save file. + Gives the accounts txt database name and path to stores accounts information. + Look into the file to have a short description of the database structure. + Can be omited, a default is save/account.txt. + Default value: save/account.txt. + +gm_account_filename + It specifies which account IDs have GM privileges, and what level they have. + We recommand to use only ID value lower than first normal player ID (2000000). + Levels ranges from 0 (no privilege/normal player) to 99 (highest privilege). + If you change a value inside this file, you must restart login-server to use the new value + or use reloadgm ladmin command. + Can be omited, a default is conf/GM_account.txt. + Default value: conf/GM_account.txt + +gm_account_filename_check_timer + Timer to check if GM_account file has been changed and reload GM account automaticaly + (in seconds) + Value: 0 (disabled), or 2 or more. + Default: 15 + +login_log_filename + Gives the log file name and path to stores logs information. + All operations done by the login-server are written in this file with a time stamp. + Default value: log/login.log + +login_log_unknown_packets_filename + Gives the file name and path of the file that logs the received unknown packets. + It's used for debug or hack check. + All information are displayed with the time stamp, the ip of the source, the packet number, + the number of received bytes and the detail of the packet with hex and text values. Example: + 01-06-2004 21:25:21.579: receiving of an unknown packet -> disconnection + parse_login: connection #5 (ip: 82.64.111.96), packet: 0x4e92 (with being read: 28). + Detail (in hex): + 92 4e 00 00 00 00 00 00 00 00 00 00 00 00 00 00 誰.............. + 00 00 00 00 00 00 00 00 00 00 00 00 ............ + Default value: log/login_unknown_packets.log + +save_unknown_packets + It indicates if the unknown packets are saved or not. + The unknown packets received from the char-server or remote administration does not relate to this parameter, + because they are always saved. + The value can be: 1 (to save unknown packets) or 0 (to not save them). + Be careful: if you receive an attack, your hard disk can cause lag... + So, active this option with a speed hard disk or for debug only. + Default value: 0 + +display_parse_login + It indicates if you want display the parse of the packets received in a normal connection. + At all received packets in normal connection, the server display a message about the size and the value. + It's useful for debug. Possible values: 0: no (default), 1: yes. + Default value: 0 + +display_parse_admin + It's same of 'display_parse_login' parameter, but only for remote administration received packets. + It's useful for debug. Possible values: 0: no (default), 1: yes. + Default value: 0 + +display_parse_fromchar + It's same of 'display_parse_login' parameter, but only for char-server received packets. + It's useful for debug. Possible values: 0: no (default), 1: yes (without packet 0x2714), 2: all packets. + Default value: 0 + +date_format: + indicate how to display date in logs, to players, etc. + 0: 31-12-2004 23:59:59 + 1: 12-31-2004 23:59:59 + 2: 2004-31-12 23:59:59 + 3: 2004-12-31 23:59:59 + Default value: 3 + +min_level_to_connect + Indicate the minimum GM level of player that the server accepts to connection. + 0: all players (normal player are 0. it's default), or + 1-99: GM level at least with level x + Default value: 0 (any player or GM) + +add_to_unlimited_account + Give possibility to adjust (ladmin command: timeadd) the time of an unlimited account. + If set to on/1/yes..., the adjustment is be done from actual time to set the final time of the account. + If set to no/0/no..., the adjustment can not be done on an unlimited account. + You must set (ladmin command: timeset) a final time before to adjust (ladmin command: timeadd) + Default value: no + +start_limited_time + Starting additional sec from now for the limited time at creation of account + -1: new account are created with UNlimited time (default value) + 0 or more: new accounts was created by addition of the value (in sec) to the actual time (to set first limited time) + Default value: -1 + +check_ip_flag + It's to check IP of a player between login-server and char-server (part of anti-hacking system) + If player doesn't have same IP, connection is refused. + Set to 0/off/no to not check IP of player. + Set to 1/on/yes if you want to check (default) + Note: if you enable this option, be sure that your (local/lan/wan) players use correct ip (in xml file) to contact servers, + and that your LAN is correctly configured (!), and that LAN configuration of eathena is right. + if not correct, you can read list of char-servers, but not look slots of characters (rejected by server). + Default value: yes + +order + This parameter controls how the login-server must use the 'allow' and 'deny' lists. + 'Allow' and 'deny' are used to do IP lists. + 3 possibilities: + 'deny,allow': to sum it up, it's like 'allow if not deny'. The login-server only checks the 'deny' list. + If the connected IP is in the 'deny' list, the login-server refuses the connection, otherwise it accepts it. + 'allow,deny': to sum it up, it's like 'deny if not allow'. The login-server only checks the 'allow' list. + If the connected IP is in the 'allow' list, the login-server accepts the connection, otherwise it refuses it. + 'mutual-failture': to sum it up, it's like 'allow if in allow list and not in the deny list'. + The login-server checks the 'allow' list. If the connected IP is in the 'allow' list, + the login-server checks it in the 'deny' list. If the connectec IP is not in the 'deny' list, + the login-server accepts the conection, otherwise it refuses it. + In this case, a non 'allow' IP or a 'deny' IP will be never accepted. + If you don't use allow AND deny, all ip are authorised. + Default value: deny,allow. + +allow + It's a list parameter. To add an item in this list, just add a new line. Each line can only have 1 parameter. + This list depends on the 'order' parameter (read 'order' parameter to known what the login-server do with this list). + This parameter accepts IP descriptions, like: + IP or the begining of IP: it's a characters match. Write an IP (123.456.789.012) or the begining of the IP (123.456.). + Because it's a characters match, the IP 123.4 matches with 123.4.xxx.yyy and 123.4z.xxx.yyy. So, add a final '.' to be sure of the IP. + Example: + allow: 127.0.0.1 + allow: 192.168.10. + IP with number of bits for a network: it's a logical match. Write the network like this: 123.456.789.012/<#_of_mask_bits> + Don't use the final '.', but use all four values. + Example: + allow: 127.0.0.1/32 (match only 1 IP, because 32 bits match all bits). + allow: 192.168.10/24 (matches the network begining by 192.168.10). + IP with mask of a network: it's a logical match. Write the network like this: 123.456.789.012/345.678.901.234 + Don't use the final '.', but use all four values for the IP and the mask. + Example: + allow: 127.0.0.1/255.255.255.255 (match only 1 IP). + allow: 192.168.10.0/255.255.255.0 (matches the network begining by 192.168.10). + all: matches any IP. + Example: + allow: all. + clear: clears the list at this point. Really useful for 'import' parameter, in the new configuration file. + Example: + allow: clear. + It does not support the backward match of host name. + Default value: <no list>. + +deny + This list works exactly like the 'allow' list, but for the 'deny' list. + +import + Gives an other configuration file to include in. + You must write the additionnal configuration file name and path. + The mentionned file can include any parameter of the login configuration. + You can create a chain or configuration files if necessary. + Default value: <no_additional_configuration_file>. + +<Example> +login_port: 6900 +admin_pass: admin +ladminallowip: all +gm_pass: gm +level_new_gm: 60 +new_account: 1 +account_filename: save/account.txt +gm_account_filename: conf/GM_account.txt +gm_account_filename_check_timer: 15 +login_log_filename: log/login.log +login_log_unknown_packets_filename: log/login_unknown_packets.log +save_unknown_packets: 0 +display_parse_login: 0 +display_parse_admin: 0 +display_parse_fromchar: 0 +date_format: 3 +min_level_to_connect: 0 +add_to_unlimited_account: off +start_limited_time: -1 +check_ip_flag: yes +order: deny,allow +//deny: all +//allow: 127.0.0.1 +//allow: 10.0. +//allow: 172.16.0.0/16 +//allow: 192.168.0.0/255.255.255.0 +//import: import/new_login.conf + +========================================================================== +2. conf/char_athena.conf +-------------------------------------------------------------------------- + +< What this file is. > + + A setup of char-server (server which manages the character data in one world) + It ???. The name of a world, the password of a world, a server's IP, + A data file name etc. is described. + + +<Explanation of a key> + +userid + It is ID which this world uses. It registers with login-server. + ID of account is specified. + And also [ it uses it for connecting to map-server used in this world ] + It is used also for discernment of the world within login-server. + It is each when registering two or more worlds into the same login-server. + It is necessary to use another ID by char-server. + +passwd + It is a password corresponding to ID which this world uses. + It is used for the time of the registration to login-server, and connecting to map-server. + +server_name + It is the name of this world. It is displayed when logged in by the client. + +wisp_server_name + Wisp name for server: used to send wisp from server to players (between 4 to 23 characters) + Default: Server + +login_ip + It is the IP address of login-server which registers a world seen from char-server. + +login_port + It is the port used by login-server. It can omit and a default is 6900. + +char_ip + It is the IP address of char-server seen from the client. + +char_port + It is the port used by char-server. It can omit and a default is 6121. + +email_creation + Option to force a player to create an e-mail. + If a player have default e-mail, and if you activate this option, the player can only connect in the game (to arrive on a map) like follow: + - Create at least 1 character + - Select 1 character + - Select DEL to enter his/her e-mail. (if OK is choosen, client says to the player: 'invalid e-mail') + - If his/her e-mail is correct, the player enter in the game (an e-mail is saved definitively). + - If his/her e-mail is incorrect, he/she have 'incorrect e-mail' and must select again DEL. + - After entering in the game (when the player arrives on a map), DEL and SEL/OK button work normaly for all next connections. + Resume: If a player have "incorrect/invalid e-mail" when he/she click on 'OK' button, + the player must click 'DEL' button and register his/her NEW e-mail to enter in the game + So, default is 0, because administrator must explain to their players before to activate this option. + +char_txt + It is the data file name which stores character data. + It is not omissible. + +char_maintenance + If it is made 1, it will be in a maintenance state. + It can omit and a default is 0. + +char_new + If it is made 1, the time (new) of being displayed on a client will stick. + It can omit and a default is 0. + +max_connect_user + It is the maximum number of the user linked to a Char-server. + If it is made 0, the maximum number restrictions will be lost. + Please use to apply restriction of the connection number. + It can omit and a default is 0. + +check_ip_flag + It's to check IP of a player between char-server and other servers (part of anti-hacking system) + If player doesn't have same IP, connection is refused. + Set to 0/off/no to not check IP of player. + Set to 1/on/yes if you want to check (default) + Note: if you enable this option, be sure that your (local/lan/wan) players use correct ip (in xml file) to contact servers, + and that your LAN is correctly configured (!), and that LAN configuration of eathena is right. + default: yes + +autosave_time + It is time to save data automatically at a file. A unit is a second. + It can omit and a default is 300 (5 minutes). + +start_point + When a new character is created, it is the place where they start. + It describes like "a map file name, X coordinates, and Y coordinates." + It can omit and is a default. It is new_1-1.gat and 53,111. + +start_weapon: + Starting weapon for new characters + default value: 1201 (Knife) + +start_armor: + Starting armor for new characters + default value: 2301 (Cotton Shirt) + +start_zeny + When new character is made, the quantity of ZENY which it has from the start is set up. + Being able to omit, a default is 500. + +unknown_char_name + The name returned when the name request of character which does not exist in a character server is carried out + It sets up. Being able to omit, a default is Unknown. + +char_log_filename + A character server's log file is specified. + Being able to omit, a default is log/char.log. + +name_ignoring_case + Allow or not identical name for characters but with a different case (upper/lower): example: Test-test-TEST-TesT + 0 (default): no character can have same name, instead of the case (no Test-TEST...) + 1: more than 1 of character can have same name if names are not using same case (one with Test, another with TEST, etc...) + +char_name_option + Manage possible letters/symbol in the name of charater. Control character (0x00-0x1f) are never accepted. Possible values are: + 0: no restriction (default) + 1: only letters/symbols in 'char_name_letters' option. + 2: Letters/symbols in 'char_name_letters' option are forbidden. All others are possibles. + default: 0. + +char_name_letters + Set the letters/symbols that you want use with the 'char_name_option' option. + Note: add 'space' between 2 others letters/symbols. + default: void. + +online_txt_filename + It sets the filename of the file which receives the online players list in text + default: online.txt + +online_html_filename + It sets the filename of the file which receives the online players list, but in html version + default: online.html + +online_sorting_option + It sets how to display online players in the txt/html files. + 0: no sorting (default) + 1: by alphabetical order of their name + 2: by number of their zenys + 3: by their base level + 4: by their job (and job level inside the same job) + 5: by alphabetical order of their actual map location + Note: sorting operation with a lot of online players can take time on a slow computer. + +online_display_option + It sets which columns that you want display in the online files. Do the addition of these values: + (if value is 0, no file is done) + 1: name (just the name, no function like 'GM') + 2: job + 4: levels + 8: map name + 16: mapname and coordonates + 32: zenys + 64: name (with 'GM' if the player is a GM) + default value: 1 (only name) + +online_gm_display_min_level + minimum GM level to display 'GM' when we want to display it. + default value: 1 (any GM) + +online_refresh_html + refresh time (in sec) of the html file in the explorer + default: 20 + +import + The line is replaced with the contents of another file. + + +< Example > +userid: s1 +passwd: p1 +server_name: eAthena +wisp_server_name: Server +login_ip: 127.0.0.1 +login_port: 6900 +char_ip: 127.0.0.1 +char_port: 6121 +email_creation: 0 +char_txt: save/athena.txt +char_maintenance: 0 +char_new: 0 +max_connect_user: 0 +check_ip_flag: yes +autosave_time: 15 +start_point: new_1-1.gat,53,111 +start_weapon: 1201 +start_armor: 2301 +start_zeny: 500 +unknown_char_name: Unknown +char_log_filename: log/char.log +name_ignoring_case: 0 +char_name_option: 0 +//char_name_letters: +online_txt_filename: online.txt +online_html_filename: online.html +online_sorting_option: 0 +online_display_option: 1 +online_gm_display_min_level: 1 +online_refresh_html: 20 +//import: import/new_char.conf + + +========================================================================== +3. conf/inter_athena.conf +-------------------------------------------------------------------------- + +< What this file is. > + + A setup of inter-server (server which manages the global data in one world) + It ???. A data file name etc. is described. + (It is operating as a part of char-server in program now.) + +< Explanation of a key > + +storage_txt + It is the file name which stores warehouse data. + It can omit and is a default. It is save/storage.txt. + +party_txt + It is the file name which stores party data. + It can omit and is a default. It is save/party.txt. + +guild_txt + It is the file name which stores guild data. + It can omit and is a default. It is save/guild.txt. + +pet_txt + It is the file name which stores pet data. + It can omit and is a default. It is save/pet.txt. + +castle_txt + It is the file name which stores the castle data of a guild. + It can omit and is a default. It is save/castle.txt. + +guild_storage_txt + It is the file name which stores guild warehouse data. + Being able to omit, a default is save/g_storage.txt. + +accreg_txt + It is the file name which stores the account share variable data in a world. + It can omit and is a default. It is save/accreg.txt. + +party_share_level + The restriction level of a fair distribution party is set up. + Being able to omit, a default is 10. + +inter_log_filename + An interchange server's log file is specified. + Being able to omit, a default is log/inter.log. + +import + The line is replaced with the contents of another file. + + +< Example > +storage_txt: save/storage.txt +party_txt: save/party.txt +guild_txt: save/guild.txt +pet_txt: save/pet.txt +castle_txt: save/castle.txt +guild_storage_txt: save/g_storage.txt +accreg_txt: save/accreg.txt +party_share_level: 10 +inter_log_file: log/inter.log + + +========================================================================== +4. conf/map_athena.conf +-------------------------------------------------------------------------- + +< What this file is. > + + A fundamental setup of map-server (server which manages game advance on the map in his duty) + It ???. + + +< Explanation of a key > + +userid + It is ID which this world uses. It is used for the connecting to char-server. + +passwd + It is a password corresponding to ID which this world uses. + +char_ip + map-serverからみた、このサーバーが担当するマップのワールドを管理する + char-serverのIPです。 + +char_port + マップを登録するchar-serverのポートです。省略可能でデフォルトは6121です。 + +map_ip + クライアントから見たこのmap-serverのIPです。 + +map_port + map-serverで使用するポートです。省略可能でデフォルトは5121です。 + +autosave_time + データを自動的にキャラ鯖に送る時間です。単位は秒です。 + 省略可能でデフォルトは60(1分)です。 + +water_height + 水場の高さを指定するファイルを決めます。 + 省略可能で、デフォルトはconf/water_height.txtです。 + +motd_txt + Message of the Dayファイルを指定します。 + 省略可能で、デフォルトはconf/motd.txtです。 + +help_txt + @helpで表示するファイルを指定します。 + 省略可能で、デフォルトはconf/help.txtです。 + +mapreg_txt + MAPサーバー内キャラクター共有変数を保存するファイルを指定します。 + 省略可能で、デフォルトはsave/mapreg_txtです。 + +data_grf + ROデータファイル data.grf へのパスです。 + 省略可能で、デフォルトは ./data.grf です。 + grf-files.txtがある場合そちらの設定が優先されます。 + +sdata_grf + サクライデータファイル sdata.grf へのパスです。 + 省略可能で、デフォルトは ./sdata.grf です。 + grf-files.txtがある場合そちらの設定が優先されます。 + +adata_grf + αデータファイル adata.grf へのパスです。 + 省略可能で、デフォルトは ./adata.grf です。 + grf-files.txtがある場合そちらの設定が優先されます。 + +npc + 読み込むnpcデータファイルへのパスです。 + 複数指定可能で、指定した順にロードします。 + clear を指定するとそれまでに登録したパスを全て削除します。 + +delnpc + 読み込まないnpcファイルへのパスです。 + 指定したパスはnpcで指定されたデータファイルリストから削除されます。 + all を指定すると全て削除します( npc: clear と同義)。 + +map + このマップが担当するマップファイル名です。 + 複数指定可能で、指定した順にロードします。 + 存在しないマップを指定した場合エラーになります。 + clear を指定するとそれまでに登録したファイル名を全て削除します。 + +delmap + 読み込まないマップファイルへのパスです。 + 指定したファイルはmapで指定されたリストから削除されます。 + all を指定すると全て削除します( map: clear と同義)。 + +import + その行を別ファイルの中身と置き換えます。 + +< Example > + +userid: s1 +passwd: p1 +char_ip: 127.0.0.1 +char_port: 6121 +map_ip: 127.0.0.1 +map_port: 5121 +autosave_time: 60 +nullpo_check: 1 +water_height: conf/water_height.txt +data_grf: ./data.grf +sdata_grf: ./sdata.grf +npc: conf/warp/npc_warp.txt +npc: conf/warp/npc_warp25.txt +npc: conf/warp/npc_warp3.txt +npc: conf/mob/npc_monster3J.txt +map: prontera.gat +map: prt_castle.gat +(npc、mapは多いので省略) +delnpc: conf/sample/npc_test.txt +npc: clear +delmap: prontera.gat +delmap: all + +========================================================================== +5. conf/battle_athena.conf +-------------------------------------------------------------------------- + +< What this file is. > + + Battle relation of map-server (server which manages game advance on the map in his duty), + Other setup is described. + All setup can be omitted and a default value is used at the time of an abbreviation. + +< The special character sequence which can be specified to be a value > + + yes on It is processed as 1. (Effective meaning) + no off It is processed as 0. (Invalid meaning) + + +< Explanation of a key > + +warp_point_debug + ワープポイントを普通に表示するかどうかです。これをyesにすると + ワープポイントのかわりにギルドフラグがその場所に出てワープ + ポイントの名前を確認することができます。デフォルトはnoです。 + +enemy_critical + プレイヤーと同じLUKによるクリティカル判定をMOBとペットに有効にするかどうかです。 + このクリティカルはもちろん必中なので、onにすると高Fleeでも、 + LUKの高い敵の攻撃が避けづらくなります。デフォルトはnoです。 + +enemy_critical_rate + モンスターとペットのクリティカル頻度の百分率です。enemy_criticalがyesじゃないと設定しても何の意味もありません。デフォルトは100です。 + +enemy_str + モンスターのが攻撃するときのATK計算にSTRを使用するかどうかです。 + デフォルトはyesです。 + +enemy_perfect_flee + 敵が完全回避をするかどうかです。これをyesにすると敵も完全回避を + するようになります。デフォルトはnoです。 + +casting_rate + スキルの詠唱時間を百分率で調整します。 + 200にすると詠唱時間が倍になり、0にすると詠唱がなくなります。 + 0-1000程度の数値を指定してください。デフォルトは100です。 + +delay_rate + スキル使用後ディレイを百分率で調整します。 + 200にするとディレイが倍になり、0にするとディレイがなくなります。 + 0-1000程度の数値を指定してください。デフォルトは100です。 + +delay_dependon_dex + スキル使用後ディレイが詠唱時間と同じようにDEXで影響を受けるかどうかを + 指定します。デフォルトはnoです。 + +skill_delay_attack_enable + スキルディレイの間攻撃できるかどうかです。yesにすればスキルディレイの間スキルは使えないけど攻撃はできます。 + デフォルトはnoです。 + +left_cardfix_to_right + 二刀流の左手武器の種族、属性、Sizeのダメージ補正を右手武器に適用するかどうかです。これをyesにすると左手武器には種族、属性、Sizeのダメージ補正が掛からなくなります。デフォルトはnoです。 + +player_skill_add_range + プレイヤーのスキル射程から敵が離れた時どれぐらいの距離までスキルを使用可能にするかを決めます。スキルの射程+player_skill_add_rangeまでスキルが届きます。デフォルトは0ですが少しは入れた方がいいです。 + +skill_out_range_consume + スキルの射程から敵が離れてスキルが失敗した時SPやアイテムを消耗するかどうかです。デフォルトはyesです。 + +monster_skill_add_range + モンスターのスキル射程から敵が離れた時どれぐらいの距離までスキルを使用可能にするかを決めます。スキルの射程+monster_skill_add_rangeまでスキルが届きます。デフォルトは0です。 + +player_damage_delay + プレイヤーキャラがダメージを受けた時移動できないディレイを入れるかどうかです。 + yesにするとインデュアでも使わない限りダメージを受けた時 + しばらくは動きません。デフォルトはyesです。 + +player_damage_delay_rate + プレイヤーキャラがダメージを受けた時移動できないディレイを百分率で調整します。 + 200にするとディレイが倍になり、0にするとディレイがなくなります。 + player_damage_delayがyesにしてないと意味がありません。 + デフォルトは100です。 + +defunit_not_enemy + 防御ユニット(セイフティウォール/ニューマなど)がMOBに効果を + 及ぼさないようにするかどうかです。デフォルトはyesです。 + +random_monster_checklv + モンスター召還アイテムを使ったときに自分よりLVの高いモンスターを召還するかどうかです。 + yesにすると、自分よりLVの高いモンスターを召還しないようになります。 + デフォルトはyesです。 + +attribute_recover + 属性によって攻撃されても回復するかどうかです。noの場合は-属性を + 0にします。デフォルトはyesです。 + +item_auto_get + アイテム自動取得機能を使用するかどうかです。 + yesにするとアイテムドロップをモンスターに一番多くダメージを与えたキャラが + 自動でアイテムを取得するようになります。 + デフォルトはnoです。 + +flooritem_lifetime + 床に落ちたアイテムが消えるまでかかる時間です。単位はms(ミリ秒)です。 + デフォルトは60000(60秒)で最小は1000(1秒)です。1000未満ならデフォルトにセットされます。 + +item_first_get_time + モンスターに一番ダメージを多く与えたキャラ以外がそのモンスターの + ドロップアイテムを取れるようになるまでの時間です。 + 単位はms(ミリ秒)です。デフォルトは10000(10秒)です。 + +item_second_get_time + item_first_get_timeの後モンスターに二番目にダメージを多く与えた + キャラ以外がそのモンスターのドロップアイテムを取れるようになるまでの + 時間です。単位はms(ミリ秒)です。デフォルトは7000(7秒)です。 + +item_third_get_time + item_second_get_timeの後モンスターに三番目にダメージを多く与えた + ャラ以外がそのモンスターのドロップアイテムを取れるようになるまでの + 時間です。単位はms(ミリ秒)です。デフォルトは5000(5秒)です。 + +mvp_item_first_get_time + モンスターに一番ダメージを多く与えたキャラ以外がそのモンスターの + MVPアイテムを取れるようになるまでの時間です。 + 単位はms(ミリ秒)です。デフォルトは10000(10秒)です。 + +mvp_item_second_get_time + mvp_item_first_get_timeの後モンスターに二番目にダメージを多く与えた + キャラ以外がそのモンスターのMVPアイテムを取れるようになるまでの + 時間です。単位はms(ミリ秒)です。デフォルトは10000(10秒)です。 + +mvp_item_third_get_time + mvp_item_second_get_timeの後モンスターに三番目にダメージを多く与えた + キャラ以外がそのモンスターのMVPアイテムを取れるようになるまでの + 時間です。単位はms(ミリ秒)です。デフォルトは2000(2秒)です。 + +item_rate + アイテムドロップ率を百分率で調整します。 + 0-1000程度の数値を指定してください。デフォルトは100です。 + +drop_rate0item + 落下確率0のアイテム(一部モンスターにおけるリンゴ)を落下するかどうかの設定です。 + デフォルトはnoです。 + +base_exp_rate + BaseEXPの所得倍率を百分率で調整します。 + 0-1000程度の数値を指定してください。デフォルトは100です。 + +job_exp_rate + JobEXPの所得倍率を百分率で調整します。 + 0-1000程度の数値を指定してください。デフォルトは100です。 + +death_penalty_type + デスペナルティのタイプを決定します。 + 0で死んだ後リスタートする時に適用で持っているEXPの量から比率の分を減らす仕様、1で死んだ直後に適用で持っているEXPの量から比率の分を減らす仕様です。 + 2で死んだ後リスタートする時に適用で次のレベルアップまでのEXPから比率の分を減らす仕様、3で死んだ直後に適用で次のレベルアップまでのEXPから比率の分を減らす仕様です。 + デフォルトは0です。 + +death_penalty_base + デスペナルティによるBASE経験値減少率を百分率で調整します。 + あまり低すぎる値を使うと減りません。単位は0.01%です。 + デフォルトは0です。 + +death_penalty_job + デスペナルティによるJOB経験値減少率を百分率で調整します。 + あまり低すぎる値を使うと減りません。単位は0.01%です。 + デフォルトは0です。 + +zeny_penalty + 死んだ時無くなるゼニ量の比率です。単位は0.01%です。死んだ時 + 減るわけではなく死んだ後セーヴポイントに戻る時適用されます。 + デフォルトは0です。 + +restart_hp_rate + リスタートする時に回復するHP比率を百分率で調整します。単位は%です。 + デフォルトは0です。0の場合1回復になります。 + +restart_sp_rate + リスタートする時に回復するSP比率を百分率で調整します。単位は%です。 + デフォルトは0です。0の場合は回復しません。 + そしてSPが比率より高い場合も回復しません。 + +mvp_hp_rate + MVP モンスターのHPを百分率で調整します。 + 0-1000程度の数値を指定してください。デフォルトは100です。 + +mvp_item_rate + MVPアイテムの所得倍率を百分率で調整します。 + 0-1000程度の数値を指定してください。デフォルトは100です。 + +mvp_exp_rate + MVP EXPの所得倍率を百分率で調整します。 + 0-1000程度の数値を指定してください。デフォルトは100です。 + +monster_hp_rate + MVP 以外のモンスターのHPを百分率で調整します。 + 0-1000程度の数値を指定してください。デフォルトは100です。 + +monster_max_aspd + モンスターの最大攻撃速度を設定します。 + デフォルトは199です。最大は199で最小は100です。 + +atcommand_gm_only + @コマンドをGM専用にするかどうかです。デフォルトはnoです。 + +gm_all_skill + 全てのスキルを覚えられるようにするGMのレベルを設定します。 + これを0以外にすると、そのGMレベル以上のGMはJOBやスキル所得条件に関係なく全スキルが覚えられます。(クェストスキルも含めて) + デフォルトは 0 です。0の場合はGMではない全てのキャラの意味ではなく全てのGMが全てのスキルを覚えられないと言うことです。 + +gm_all_equipment + 全ての装備品を装備できるようにするGMのレベルを設定します。 + これを0以外にすると、そのGMレベル以上のGMはJOBやレベル、性別に関係なく + 全装備品を装備できるようになります。ただし、クライアント側でエラーを + 起こす組み合わせもあると思います。デフォルトは 0 です。 + 0の場合は全てのGMは通常プレイヤーと同じ判定が行われます。 + +gm_skill_unconditional + 無条件にスキルを使用できるようにするGMのレベルを設定します。 + これを0以外にすると、そのGMレベル以上のGMは装備武器や消費アイテムの有無 + などに関係なく、そして何も消費することなくスキルを使用できるように + なります。判定処理を無視するので動作に不都合がでる可能性があります。 + デフォルトは 0 です。 0の場合は全てのGMは通常プレイヤーと同じ判定が + 行われます。 + +player_skillfree + スキルツリーに関係なくスキルを上げることができるかどうかです。 + これをyesにすればプレイヤーの職業で習うことができるスキル全てを + スキルツリーに関係なく上げることができます。デフォルトはnoです。 + +player_skillup_limit + スキルリセット等をした時スキルを制限なしに上げるかどうかです。 + これをyesにすれば始めのスキルポイント9つはノービスで習うスキルにしか + 使えません。そしてその後の39は1次職業で習う物にだけ使えてその後の + ポイントは自由に使うことができます。デフォルトはnoです。 + +weapon_produce_rate + 武器製造スキルでの製造成功率を百分率で調整します。 + 0-1000程度の数値を指定してください。デフォルトは100です。 + +potion_produce_rate + ポーション製造スキルでの製造成功率を百分率で調整します。 + 0-1000程度の数値を指定してください。デフォルトは100です。 + +monster_active_enable + 先攻モンスターを先攻にするかどうかです。これをnoにすると + 先攻モンスターが非先攻になります。デフォルトはyesです。 + +monster_damage_delay_rate + モンスターがダメージを受けた時移動できないディレイを百分率で調整します。 + 200にするとディレイが倍になり、0にするとディレイがなくなります。 + デフォルトは100です。 + +monster_loot_type + ルートモンスターの行動の仕方を指定します。 + 0の場合はLOOTITEM_SIZEまでアイテムを食べてもまたアイテムを食べて、 + 前のアイテムが消える仕様。1の場合はLOOTITEM_SIZEまでアイテムを食べると + もうアイテムを食べなくなる仕様。デフォルトは0です。 + +mob_skill_use + MOBがスキルを使ってくるかどうかです。デフォルトはyesです。 + +mob_count_rate + map_athena.confで指定されたnpcデータを読み込む際、 + monsterで定義された配置MOBの数を百分率で調整します。 + 例外として、配置数1として定義されたMOBの数は変わりません。(BOSS対策) + また、配置数を下方修正したとき、1未満になった場合は1として処理します。 + 0-1000程度で指定してください。デフォルトは100です。 + +quest_skill_learn + クェストスキルを普通に習得するかどうかです。 + これをyesにするとクェストスキルが普通に表示されてスキルポイントを使って習得することができます。 + デフォルトは noです。 + +quest_skill_reset + スキルをリセットする時クェストスキルをリセットするかどうかです。 + デフォルトはyesです。 + noにしてもquest_skill_learnをyesにすればリセットされます。 + +basic_skill_check + 座り、交換、パーティ結成、チャットルーム作り等の時基本スキルをチェックするかどうかです。 + これをnoにすれば座り、交換等の基本スキルが必要な行動を基本スキルに関係なく使うことができます。 + デフォルトは yesです。 + +guild_emperium_check + ギルドを作る時エンペリウムを消費するかどうかです。これをnoにすれば + エンペリウムなしでもギルドが作れます。 + デフォルトは yesです。 + +guild_exp_limit + ギルドの役職に設定できる上納経験値の割合の上限を設定できます。 + デフォルトは50(%)です。 + +player_invincible_time + マップ移動やテレポート、復活した時の無敵時間を設定します。単囲は + ms(ミリ秒)。移動、攻撃行動、スキル使用、アイテム使用をするとこの + 時間はなくなる。(シーズモードでは時間を2倍にして適用) + デフォルトは5000(5秒)です。 + +pet_catch_rate + ペットの捕獲倍率を百分率で設定します。 + 0-1000程度の数値を指定してください。デフォルトは100です。 +pet_rename + ペットの名前を変更するかどうかを決めます。デフォルトはnoです。 + yesは何度でも名前の変更が可能。noは一度変更するともう変更不可能になる。 + +pet_friendly_rate + ペットに餌をあげた時上がる親密度の倍率です。 + 親密度が減る場合は適用されません。デフォルトは100です。 + +pet_hungry_delay_rate + ペットの腹が減る時間の倍率です。 + 倍率が高いと腹が減り難くなります。デフォルトは100です。 + +pet_hungry_friendly_decrease + ペットの腹が完全に減った時減る親密度の量です。デフォルトは5です。 + +pet_str + ペットのATK計算にSTRを適用するかどうかです。 + デフォルトはyesです。 + +pet_status_support + ペットによるステータスボーナスを適用するかどうかです。yesにすると + ペットを持ってる時ペット毎に設定されているステータスボーナスが + 付きます。デフォルトはnoです。 + +pet_attack_support +pet_damage_support + 主人がモンスターにダメージを与えたとき、受けたときに + ペットが支援攻撃をするかどうかです。yesにするとペットの親密度が + きわめて親しいの時だけ支援攻撃をしてくれます。デフォルトはnoです。 + +pet_support_rate + ペットの支援攻撃確率の倍率です。(100で通常、200で倍です) + 倍率が高いと支援攻撃よくしてくれるようになります。デフォルトは100です。 + +pet_attack_exp_to_master + ペットが与えたダメージの分の経験値を主人が収得するかどうかです。 + これをyesにするとペットの攻撃によるダメージも主人が与えた物になり + 主人が経験値を収得することができます。デフォルトはnoです。 + +pet_attack_exp_rate + ペットが与えたダメージの分の経験値を主人が収得する時の倍率です。 + デフォルトは100です。 +pet_lootitem + ペットがアイテムをルートするかどうかの設定です。 + デフォルトはnoです。 + +pet_weight + ペットにルートさせるときの重量制限です。 + デフォルトは1000です。 + +skill_min_damage + スキルを使った時ダメージが連打数より未満の場合全てミスになるか1ダメージになるかを決定します。 + デフォルトはnoです。 + +finger_offensive_type + スキル指弾の表示タイプを決定します。 + 0は本サーバー仕様で1はアテナ仕様です。デフォルトは0です。 + +heal_exp + スキル「ヒール」を使った際にもらえるジョブ経験値量の設定です。 + 100で回復した量と同量になります。 + モンスターの経験値を変更してない場合は5〜10程度が適当だと思われます。 + デフォルトは0です。 + +resurrection_exp + スキル「リザレクション」を使った際にもらえる経験値量の設定です。 + 単位は0.01%です。復活したプレイヤーが持っている経験値 * レベル差/100 * resurrection_exp/10000 分の経験が貰えます。 + デフォルトは0です。 + +shop_exp + スキルディスカウントとオーバーチャージを習得してる場合NPC利用金額に応じたJOB経験値獲得倍率です。(100で通常、200で倍になります) + 計算式はln(代金*スキルレベル) * shop_exp / 100 で買う場合はディスカウントがある時のみ適用で売る場合オーバーチャージがある時のみ適用されます。 + 計算式は適度に作った物です。 + デフォルトは0です。 + +combo_delay_rate + モンクのコンボディレイの時間の倍率です。(100で通常、200で倍になります。) + ただ注意するべきなのは高く設定するのがいいことではないことです。 + コンボディレイが長いとコンボの繋ぎはよくなりますがその長くなった + 時間の間は行動できないからです。デフォルトは100です。 + +item_check + アイテムのチェックを行うかどうかです。 + ログイン時とマップ移動時に所持アイテムに不正アイテムがないかチェックします。 + また@itemで不正アイテムを所得できなくします。 + デバグやアイテムの確認を行うときなどはoffにしてください。 + デフォルトはonです。 + +wedding_modifydisplay + タキシードとウェディングドレスを表示するかどうかです。 + 結婚キャラを表示したい場合はこれをyesにしてください。 + デフォルトはnoです。 + +natural_healhp_interval + HPが自動回復するまで掛かる時間です。単位はms(ミリ秒)です。 + デフォルトは6000でNATURAL_HEAL_INTERVAL未満にすることはできません。 + +natural_healsp_interval + SPが自動回復するまで掛かる時間です。単位はms(ミリ秒)です。 + デフォルトは8000でNATURAL_HEAL_INTERVAL未満にすることはできません。 + +natural_heal_skill_interval + スキルによって自動回復する場合掛かる時間です。単位はms(ミリ秒)です。 + デフォルトは10000でNATURAL_HEAL_INTERVAL未満にすることはできません。 + +natural_heal_weight_rate + 自動回復ができなくなる重量を設定します。単位は%です。 + 最小は50で最大は101です。最大が101なのは重量が + natural_heal_weight_rate未満の時に自動回復するからです。(つまり101なら + いつでも自動回復できます。) + デフォルトは50です。 + +item_name_override_grffile + アイテムの名前(英語以外の名前です。)を.grfファイルから読むかどうかです。 + noにするとitem_db.txtの名前を使います。 + デフォルトはyesです。 + +arrow_decrement + 弓を使う時矢を消耗するかどうかです。これをnoにすると + 矢が消耗されません。(矢を装備する必要はあります。) + デフォルトはyesです。 + +max_aspd + プレイヤーの最大攻撃速度を設定します。 + デフォルトは199です。最大は199で最小は100です。 + +max_hp + 最大HPを設定します。 + デフォルトは32500です。最大は1000000で最小は100です。 + +max_sp + 最大SPを設定します。 + デフォルトは32500です。最大は1000000で最小は100です。 + +max_parameter + プレイヤーの基本パラメータの最大値を設定します。 + デフォルトは99です。最大は10000で最小は10です。 + 基本パラメータ大きい過ぎるとクライアント落ちが起こるので + 適当な値を設定する方が良いです。 + +max_cart_weight + カートの最大重量を設定します。 + デフォルトは8000です。最大は1000000で最小は100です。 + +player_skill_log + プレイヤーのスキル使用ログを表示するかどうかです。 + デフォルトはnoです。 + +monster_skill_log + モンスターのスキル使用ログを表示するかどうかです。 + デフォルトはnoです。 + +battle_log + 戦闘関係のログを表示するかどうかです。 + デフォルトはnoです。 + +save_log + キャラの保存に関するログを表示するかどうか + デフォルトはnoです。 + +error_log + エラーログを表示するかどうかです。 + デフォルトはyesです。 + +etc_log + スキル、戦闘、キャラ保存、エラー以外のログを表示するかどうか + デフォルトはyesです。 + +save_clothcolor + 服の色を保存するかどうか、有効にすると問題があるかもしれません。 + デフォルトはnoです。 + +undead_detect_type + アンデッドを認識する方法を設定します。0で属性のみ、1で種族のみ、 + 2で属性と種族の両方のどちらでもあってる場合になります。 + デフォルトは0です。 + +player_auto_counter_type + プレイヤーのオートカウンターの仕様を設定します。0で100%クリティカル + でスキル反撃無し、1で防御無視、Hit+20、クリティカル率2倍でスキル反撃 + 無し、2で100%クリティカルでスキル反撃有り、3で防御無視、Hit+20、 + クリティカル率2倍でスキル反撃有りです。 + デフォルトは1です。 + +monster_auto_counter_type + モンスターのオートカウンターの仕様を設定します。0で100%クリティカル + でスキル反撃無し、1で防御無視、Hit+20、クリティカル率2倍でスキル反撃 + 無し、2で100%クリティカルでスキル反撃有り、3で防御無視、Hit+20、 + クリティカル率2倍でスキル反撃有りです。 + デフォルトは1です。 + +agi_penaly_type + agi_penaly_count以上の敵に攻撃された時のagiペナルティの仕様を設定します。 + 0でなし、1でagi_penaly_num%ずつ減って、2でagi_penaly_numだけ減ります。 + デフォルトは0です。 + +agi_penaly_count + agiペナルティを適用する敵の数を設定します。 + 2未満に設定することはできません。デフォルトは3です。 + +agi_penaly_num + agiペナルティによって減る回避の量を設定します。 + デフォルトは0です。 + +vit_penaly_type + vit_penaly_count以上の敵に攻撃された時のvitペナルティの仕様を設定します。 + 0でなし、1でvit_penaly_num%ずつ減って、2でvit_penaly_numだけ減ります。 + (減るのはvitによる防御のみ) + デフォルトは0です。 + +vit_penaly_count + vitペナルティを適用する敵の数を設定します。 + 2未満に設定することはできません。デフォルトは3です。 + +vit_penaly_num + vitペナルティによって減る回避の量を設定します。 + デフォルトは0です。 + +player_defense_type + プレイヤーが対象に攻撃する時のDEFの計算方法。0で本鯖仕様、1以上で減算(DEF*値)。 + +monster_defense_type + モンスターが対象に攻撃する時のDEFの計算方法。0で本鯖仕様、1以上で減算(DEF*値)。 + +pet_defense_type + ペットが対象に攻撃する時のDEFの計算方法。0で本鯖仕様、1以上で減算(DEF*値)。 + +magic_defense_type + MDEFの計算方法。0で本鯖仕様、1以上で減算(MDEF*値) + +player_skill_reiteration + プレイヤーが使う一部の地面スキルの重ね置きを許可するかどうかです。 + これをyesにするとSWやニューマ、トラップを重ねて置くことができます。 + デフォルトはnoです。 + +monster_skill_reiteration + モンスターが使う一部の地面スキルの重ね置きを許可するかどうかです。 + これをyesにするとSWやニューマ、トラップを重ねて置くことができます。 + デフォルトはnoです。 + +player_skill_nofootset + プレイヤーが使う一部の地面スキルをプレイヤーやモンスターの足元に + 置くのを禁止するかどうかです。これをyesにするとスキルを設置する時その + 効果範囲にプレイヤーやモンスターがいるとスキルを置くことができません。 + デフォルトはnoです。 + +monster_skill_nofootset + モンスターが使う一部の地面スキルをプレイヤーやモンスターの足元に + 置くのを禁止するかどうかです。これをyesにするとスキルを設置する時その + 効果範囲にプレイヤーやモンスターがいるとスキルを置くことができません。 + デフォルトはnoです。 + +player_cloak_check_type + プレイヤークローキングの仕様を設定します。 + 0は壁チェック有り、スキル使用と攻撃で解除される。 + 1は壁チェック無し、スキル使用と攻撃で解除される。 + 2はは壁チェック有り、スキル使用と攻撃で解除されない。 + 2はは壁チェック無し、スキル使用と攻撃で解除されない。 + デフォルトは0です。 + +monster_cloak_check_type + モンスタークローキングの仕様を設定します。 + 0は壁チェック有り、スキル使用と攻撃で解除される。 + 1は壁チェック無し、スキル使用と攻撃で解除される。 + 2はは壁チェック有り、スキル使用と攻撃で解除されない。 + 2はは壁チェック無し、スキル使用と攻撃で解除されない。 + デフォルトは0です。 + +gvg_short_attack_damage_rate + シーズモードで近距離物理攻撃のダメージ倍率です。デフォルトは100です。 + +gvg_long_attack_damage_rate + シーズモードで遠距離物理攻撃のダメージ倍率です。デフォルトは100です。 + +gvg_magic_attack_damage_rate + シーズモードで魔法攻撃のダメージ倍率です。デフォルトは100です。 + +gvg_misc_attack_damage_rate + シーズモードで物理攻撃と魔法攻撃以外の攻撃(鷹やトラップ等)の + ダメージ倍率です。デフォルトは100です。 + +mob_changetarget_byskill: no + 条件がskillusedのスキルをMOBがPCに使用するとき、その対象をスキル使用者にするかどうかです。 + MOBスキル使用後にタゲは戻ります。デフォルトはnoです。 + +player_attack_direction_change + プレイヤーが攻撃した時方向を変更するかどうかです。本鯖では動けない限り方向が変えらないのですがバグぽい仕様なのでデフォルトはyesです。 + noにすれば動くことだけで方向が変えるようになります。 + +monster_attack_direction_change + モンスターが攻撃した時方向を変更するかどうかです。本鯖では動けない限り方向が変えらないのですがバグぽい仕様なのでデフォルトはyesです。 + noにすれば動くことだけで方向が変えるようになります。 + +player_land_skill_limit + skill_db.txtで設定されている地面スキルの数制限をプレイヤーに + 適用するかどうかです。これをyesにすれば設定されている数以上は + 地面に設置することができなくなりますが数制限をどう変えても + MAX_SKILLUNITGROUPを越える数の設置はできません。 + デフォルトはyesです。 + +monster_land_skill_limit + skill_db.txtで設定されている地面スキルの数制限をモンスターに + 適用するかどうかです。これをyesにすれば設定されている数以上は + 地面に設置することができなくなりますが数制限をどう変えても + MAX_MOBSKILLUNITGROUPを越える数の設置はできません。 + デフォルトはyesです。 + +party_skill_penaly + 一部のパーティスキルを使用者とパーティでスキル効果が違うように + するかどうかです。これの影響を受けるスキルは今の所 + アドレナリンラッシュ、ウエポンパーフェクション、オーバートラストのみです。 + デフォルトはyesです。 + +monster_class_change_full_recover + モンスターがメタモルフォーシスとトランスフォーメーション等によって + 他のモンスターに変わった時そのモンスターのHPを最大なで回復させるか + どうかです。これをyesにすると変わったモンスターの最大HPまで + 回復します。noなら変わる前のHPと最大HPの比率の分のHPになります。 + デフォルトはnoです。 + +produce_item_name_input + 製造で作られた鉄や属性石に製造者の名前を付けるかどうかです。 + デフォルトはyesです。 + +produce_potion_name_input + 製造で作られたポーションに製造者の名前を付けるかどうかです。 + デフォルトはyesです。 + +making_arrow_name_input + 矢作成で作られた矢に製造者の名前を付けるかどうかです。 + デフォルトはyesです。 + +holywater_name_input + アクアベネディクタで作られた聖水に製造者の名前を付けるかどうかです。 + デフォルトはyesです。 + +display_delay_skill_fail + スキル使用のディレイ中に「スキル使用の後は、しばらくお待ちください」を表示するかどうかです。 + デフォルトはyesです。 + +chat_warpportal + チャット中のPCをワープポータルで飛ばせるかどうかです。 + デフォルトはnoです。 + +mob_warpportal + MOBをワープポータルで飛ばせるかどうかです。 + デフォルトはnoです。 + +dead_branch_active + 古木の枝などmonster命令でmobidを負数に指定した場合に召喚されるモンスターをアクティブにするかどうかです。 + デフォルトはnoです。 + +vending_max_value + 露店で置けるアイテム価格の最高値です。 + デフォルトは10000000です。 + +show_steal_in_same_party + スティール成功時、画面内のPTメンバー(自分含む)に + スティールしたアイテムの情報を公開するかどうかです。 + デフォルトはnoです。 + +enable_upper_class + 転生、養子職を有効にするかどうかです。 + デフォルトはnoです。 + +pet_atack_attr_none + ペットによる無属性通常攻撃を + 属性無し(属性による補正無し)にするかどうかです。 + デフォルトはnoです + +pc_atack_attr_none + プレイヤーによる無属性通常攻撃を + 属性無し(属性による補正無し)にするかどうかです。 + デフォルトはnoです + +mob_atack_attr_none + モンスターによる無属性通常攻撃を + 属性無し(属性による補正無し)にするかどうかです。 + デフォルトはyesです + +player_skill_partner_check + 聖体降福や合奏スキルを行う際にパートナーの存在をチェックするかどうかです。 + デフォルトはyes(チェックする)です。 + +hide_GM_session + GMアカウントのキャラクターを@コマンド等で表示の対象にするかどうかです。 + デフォルトはnoです +unit_movement_type + ユニット移動処理方法を選択します。 + 0で本鯖仕様(回線負荷→重、鯖処理→軽)、1でAthena仕様(回線負荷→軽、鯖処理→重) + デフォルトは0(本鯖仕様)です +invite_request_check + プレイヤーが各種要請中(PT加入、Guild加入、取引)に他の要請を受け入れるかどうかです。 + yesで本鯖仕様、noでAthena仕様(受け入れない) + デフォルトはyes(受け入れる)です。 +skill_removetrap_type + リムーブトラップの仕様を選択します。 + 0:本鯖仕様で罠1個を取得する + 1:Athena仕様で使ったアイテムを使った個数を取得する +disp_experience + 経験値を表示するかどうかです。 + yesにすると、敵を倒した時など経験値が入った時に本人にのみ表示されます。 + デフォルトはnoです。 + +night_at_start + Choose if server begin with night (yes) or day (no) + Default: No + +day_duration + Define duration in msec of the day. + Set to 0 to disable day cycle (but not @day command). + Except 0, minimum is 60000 (1 minute). + Default: 7,200,000 = 2 hours + +night_duration + Define duration in msec of the night. + Set to 0 to disable night cycle (but not @night command). + Except 0, minimum is 60000 (1 minute). + Default: 1,800,000 = 30 min + +ban_spoof_namer + Ban people that try to use an other name of its name (spoof name). + Duration of the ban, in minutes. + Value from 0 to 32767. Set to 0 to do no ban. + Default: 5 (minutes) + +hack_info_GM_level + Set here minimum level of a (online) GM that can receive all informations about any player that try to hack, spoof a name, etc. + Values are from 0 to 100. + 100: disable information + 0: send to any people, including normal players + default: 60, according to GM definition in atcommand_athena.conf + +any_warp_GM_min_level + Set here the minimum GM level to disable the nowarp (from) and nowarpto (to) flags. + This option is mainly used in AT_commands (@memo, @warp, @charwarp, @go, etc...). All GM commands used to move or set a new map check nowarp and nowarpto flags. + default: 20 (first level after normal player or super'normal' player) + +packet_ver_flag + Set here which client version do you accept. Add all values of clients: + 1: Clients before 2004-07-06 (old clients) + 2: 2004-07-06 kRO client + 4: 2004-07-13 kRO client + 8: 2004-07-26 kRO client + 16: 2004-08-09 kRO / 2004-08-16aSakray / 2004-08-17aSakray client + 32: 2004-09-06aSakray client + default value: 63 (all clients) + +import + その行を別ファイルの中身と置き換えます。 + +< Example > + +warp_point_debug: no +enemy_critical: yes +enemy_critical_rate: 100 +enemy_perfect_flee: no +casting_rate: 100 +delay_rate: 100 +delay_dependon_dex: no +skill_delay_attack_enable: no +left_cardfix_to_right: no +player_skill_add_range: 0 +skill_out_range_consume: yes +monster_skill_add_range: 0 +player_damage_delay: yes +player_damage_delay_rate: 100 +defunit_not_enemy: yes +random_monster_checklv: yes +attribute_recover: yes +item_auto_get: no +flooritem_lifetime: 60000 +item_first_get_time: 10000 +item_second_get_time: 7000 +item_third_get_time: 5000 +mvp_item_first_get_time: 10000 +mvp_item_second_get_time: 10000 +mvp_item_third_get_time: 2000 +item_rate: 100 +drop_rate0item: no +base_exp_rate: 100 +job_exp_rate: 100 +death_penalty_type: 0 +death_penalty_base: 100 +death_penalty_job: 100 +zeny_penalty: 0 +restart_hp_rate: 0 +restart_sp_rate: 0 +mvp_hp_rate: 100 +mvp_item_rate: 100 +mvp_exp_rate: 100 +monster_hp_rate: 100 +monster_max_aspd: 199 +atcommand_gm_only: Yes +gm_all_skill: 0 +gm_all_equipment: 0 +gm_skill_unconditional: 0 +player_skillfree: no +player_skillup_limit: no +weapon_produce_rate: 100 +potion_produce_rate: 100 +monster_active_enable: yes +monster_damage_delay_rate: 100 +monster_loot_type: 0 +mob_skill_use: yes +mob_count_rate: 100 +quest_skill_learn: no +quest_skill_reset: yes +basic_skill_check: yes +guild_emperium_check: yes +player_invincible_time: 5000 +pet_catch_rate: 100 +pet_rename: no +pet_friendly_rate: 100 +pet_hungry_delay_rate: 100 +pet_hungry_friendly_decrease: 5 +pet_str: yes +pet_status_support: no +pet_support: no +pet_support_rate: 100 +pet_attack_exp_to_master: no +pet_attack_exp_rate: no +pet_lootitem: no +pet_weight: 1000 +skill_min_damage: no +finger_offensive_type: 0 +heal_exp: 0 +resurrection_exp: 0 +hop_exp: 0 +combo_delay_rate: 100 +item_check: on +wedding_modifydisplay: no +natural_healhp_interval:4000 +natural_healsp_interval:8000 +natural_heal_skill_interval:10000 +natural_heal_weight_rate: 50 +item_name_override_grffile: yes +arrow_decrement: yes +max_aspd: 199 +max_hp: 32500 +max_sp: 32500 +max_parameter: 99 +max_cart_weight: 8000 +player_skill_log: off +monster_skill_log: off +battle_log: off +save_log: off +error_log: on +etc_log: on +save_clothcolor: no +undead_detect_type: 2 +player_auto_counter_type: 1 +monster_auto_counter_type: 0 +agi_penaly_type: 0 +agi_penaly_count: 3 +agi_penaly_num: 0 +vit_penaly_type: 0 +vit_penaly_count: 3 +vit_penaly_num: 0 +player_defense_type: 0 +monster_defense_type: 0 +pet_defense_type: 0 +magic_defense_type: 0 +player_skill_reiteration: no +monster_skill_reiteration: no +player_skill_nofootset: no +monster_skill_nofootset: no +player_cloak_check_type: 0 +monster_cloak_check_type: 0 +gvg_short_attack_damage_rate: 100 +gvg_long_attack_damage_rate: 100 +gvg_magic_attack_damage_rate: 100 +gvg_misc_attack_damage_rate: 100 +mob_changetarget_byskill: no +player_attack_direction_change:yes +monster_attack_direction_change:yes +player_land_skill_limit: yes +monster_land_skill_limit: yes +party_skill_penaly: yes +monster_class_change_full_recover: no +produce_item_name_input: yes +produce_potion_name_input: yes +making_arrow_name_input: yes +holywater_name_input: yes +display_delay_skill_fail: yes +chat_warpportal: no +mob_warpportal: no +dead_branch_active: no +vending_max_value: 10000000 +show_steal_in_same_party: no +enable_upper_class: no +pet_atack_attr_none: no +pc_atack_attr_none: no +mob_atack_attr_none: yes +player_skill_partner_check: yes +hide_GM_session: no +invite_request_check: yes +unit_movement_type: 0 +disp_experience: no +night_at_start: No +day_duration: 7200000 +night_duration: 1800000 +ban_spoof_namer: 5 +hack_info_GM_level: 60 +any_warp_GM_min_level: 20 +packet_ver_flag: 63 + + +========================================================================== +6. atcommand_athena.conf +-------------------------------------------------------------------------- + +< What this file is. > + + GMコマンド(/mm、/nb等)や@コマンドを使うことができるGMのレベルを設定する物です。 + 設定は全て省略可能で、省略時はデフォルト値が利用されます。(デフォルトは0です。) + +command_symbol + Set here the symbol that you want to use for your commands + Only 1 character is get (default is '@'). You can set any character, + except control-character (0x00-0x1f), '%' (party chat speaking) and '/' (standard ragnarok GM commands) + With default character, all commands begin by a '@': <example> @revive + default: @ + +Sets the level of the users that can use the GM commands. +<command name>: level +// When battle_athena.conf has atcommand_gm_only set to no, +// normal players (gm level 0) can use GM commands if you set 0 to the command level. +Max GM level is 99. If you want forbid a command to all people, set it with level 100. + +broadcast + GMコマンド /nb、/b、/bb +local_broadcast + GMコマンド /lb +mapmove + GMコマンド /mm +resetstate + GMコマンド /resetstate、/resetskill +rura+ + @コマンド @rura+ +rura + @コマンド @rura +where + @コマンド @where +jumpto + @コマンド @jumpto +jump + @コマンド @jump +who + @コマンド @who +save + @コマンド @save +load + @コマンド @load +speed + @コマンド @speed +storage + @コマンド @storage +gstorage + @コマンド @gstorage +option + @コマンド @option +hide + GMコマンド /hideと@コマンド @hide +jobchange + @コマンド @jobchange +die + @コマンド @die +kill + @コマンド @kill +alive + @コマンド @alive +kami + @コマンド @kami、@kamib +heal + @コマンド @heal +item + @コマンド @item、@item2 +itemreset + @コマンド @itemreset +itemcheck + @コマンド @itemcheck +lvup + @コマンド @lvup +joblvup + @コマンド @joblvup +help + @コマンド @help、@h +gm + To become GM (need password; password is set in login_athena.conf). + special!: only a non-GM (player with gm level 0) need to have this command. + if you change the value, be sure of what you do! + To be able to create a gm with @gm, you must: + - give a level to level_new_gm (parameter of login_athena.conf) (not 0) + - enable to level 0 the @gm command (atcommand_athena.conf) (default 100) - Only level 0 can give access to this command + - enable gm commands to normal player (battle_athena.conf, atcommand_gm_only parameter) + - and normal player must give correct password when he use the @gm command (gm_pass paramter in login_athena.conf) +pvpoff + @コマンド @pvpoff +pvpon + @コマンド @pvpon +gvgoff + @コマンド @gvgoff +gvgon + @コマンド @gvgon +model + @コマンド @model +go + @コマンド @go +monster + @コマンド @monster +killmonster + @コマンド @killmonster、@killmonster2 +refine + @コマンド @refine +produce + @コマンド @produce +memo + @コマンド @memo +gat + @コマンド @gat +packet + @コマンド @packet +stpoint + @コマンド @stpoint +skpoint + @コマンド @skpoint +zeny + @コマンド @zeny +param + @コマンド @str、@agi、@vit、@int、@dex、@luk +guildlvup + @コマンド @guildlvup +makeegg + @コマンド @makeegg +petfriendly + @コマンド @petfriendly +pethungry + @コマンド @pethungry +petrename + @コマンド @petrename +recall + @コマンド @recall +charjob + @コマンド @charjob +revive + @コマンド @revive +charstats + @コマンド @charstats +charoption + @コマンド @charoption +charsave + @コマンド @charsave +charload + @コマンド @charload +night + @コマンド @night +day + @コマンド @day +doom + @コマンド @doom +doommap + @コマンド @doommap +raise + @コマンド @raise +raisemap + @コマンド @raisemap +charbaselvl + @コマンド @charbaselvl +charjlvl + @コマンド @charjlvl +kick + @コマンド @kickとGM右クリック命令「使用者強制終了」 +allskill + @コマンド @allskill +questskill + @コマンド @questskill +lostskill + @コマンド @lostskill +spiritball + @コマンド @spiritball +party + @コマンド @party +guild + @コマンド @guild +agitstart + @コマンド @agitstart +agitend + @コマンド @agitend +mapexit + @コマンド @mapexit +idsearch + @コマンド @idsearch +charchangesex + Changes the sex of an online player (all characters on the account) +ignorelist + Displays your ignore list +charignorelist + Displays ignore list of a player +inall + Allows all wispers for the player +exall + Blocks all wispers for the player +chardisguise + Changes disguise of a player +charundisguise + Cancels disguise of a player +email + To change your (own) email (characters protection) + note: this command doesn't check email itself, but check structure of the email (xxx@xxx) + if you want be sure of each e-mail disable this option (value: 100) + + +import + その行を別ファイルの中身と置き換えます。 + + +< Example > +command_symbol: @ + +broadcast: 1 +local_broadcast: 1 +mapmove: 1 +resetstate: 1 +rura+: 1 +rura: 1 +where: 1 +jumpto: 1 +jump: 1 +who: 1 +save: 1 +load: 1 +speed: 1 +storage: 1 +gstorage: 1 +option: 1 +hide: 1 +jobchange: 1 +die: 1 +kill: 1 +alive: 1 +kami: 1 +heal: 1 +item: 1 +itemreset: 1 +itemcheck: 1 +lvup: 1 +joblvup: 1 +help: 1 +gm: 100 +pvpoff: 1 +pvpon: 1 +gvgoff: 1 +gvgon: 1 +model: 1 +go: 1 +monster: 1 +killmonster: 1 +refine: 1 +produce: 1 +memo: 1 +gat: 1 +packet: 1 +stpoint : 1 +skpoint : 1 +zeny: 1 +param: 1 +guildlvup: 1 +makeegg: 60 +petfriendly: 1 +pethungry: 1 +petrename: 1 +recall: 1 +charjob: 1 +revive: 1 +charstats: 1 +charoption: 1 +charsave: 1 +charload: 1 +night: 1 +day: 1 +doom: 1 +doommap: 1 +raise: 1 +raisemap: 1 +charbaselvl: 1 +charjlvl: 1 +kick: 1 +allskill: 1 +questskill: 1 +lostskill: 1 +spiritball: 1 +party: 1 +guild: 1 +agitstart: 1 +agitend: 1 +mapexit: 1 +idsearch: 1 +charchangesex: 1 +ignorelist: 0 +charignorelist: 20 +inall: 20 +exall: 20 +chardisguise: 60 +charundisguise: 60 +email: 0 + +========================================================================== +7. conf/ladmin_athena.conf +-------------------------------------------------------------------------- + +< What this file is. > + + A setup of ladmin (remote administration of the login-server) is described. + It mainly becomes a setup of an administrator. + Only 'c' version of the ladmin is concerned by this configuration file. + 'Perl' ladmin doesn't use it. + + +< Explanation of a key > + +If you change one of these parameters, you must restart ladmin to update. +If you repeat one parameter in the configuration file, only the latest will be validated. + +login_ip + IP adress to contact login-server. + It is the IP adress of the login-server. + Default value: 127.0.0.1 (same computer). + +login_port + Port to contact login-server. + It is the port used by login-server. + Default value: 6900. + +admin_pass + It is the administrator password used to administrate the login-server. + You define it in login-athena.conf. + Void password will not work. + NOTICE: You must change this or attackers can exploit your server. + Default value: admin. CHANGES this default value to avoid hack. + +passenc: + You specify here how ladmin send password to login-server. 3 values are possible: + 0: plain text + 1: use md5 key (format: key + password) + 2: use md5 key (format: password + key) (default) + default: 2 + +defaultlanguage + You can specified a language to display information and for logs. + There are 2 values: + F: Fran軋is + E: English (default) + If a displayed information isn't translated, English is used. + Default value: E + +ladmin_log_filename + Gives the log file name and path to stores logs information. + All operations done by the ladmin are written in this file with a time stamp. + Default value: log/ladmin.log + +date_format: + Indicate how to display date in logs, to players, etc. + 0: 31-12-2004 23:59:59 + 1: 12-31-2004 23:59:59 + 2: 2004-31-12 23:59:59 + 3: 2004-12-31 23:59:59 + Default value: 3 + +import + Gives an other configuration file to include in. + You must write the additionnal configuration file name and path. + The mentionned file can include any parameter of the login configuration. + You can create a chain or configuration files if necessary. + Default value: <no_additional_configuration_file>. + +<Example> +login_ip:127.0.0.1 +login_port: 6900 +admin_pass: admin +passenc: 2 +defaultlanguage: E +ladmin_log_filename: log/ladmin.log +date_format: 3 +//import: path/additional_configuration_file + + +========================================================================== +EOF +-------------------------------------------------------------------------- diff --git a/doc/coredump_report.txt b/doc/coredump_report.txt new file mode 100644 index 0000000..670472f --- /dev/null +++ b/doc/coredump_report.txt @@ -0,0 +1,109 @@ +========================================================================== + サーバーが強制終了する場合のcoredumpによる詳細なバグ報告方法 +-------------------------------------------------------------------------- + +< このファイルは何? > + + Athena使用中にmap-server.exeなどが突然落ちた場合に、落ちたときのスタックの + バックトレースをを開発者に伝える方法を解説する。 + バグ報告の時に併用すると開発者が喜ぶかもしれない。 + + ここでの「落ちる」はあくまでサーバーであり、クライアントではない。 + またプロセスがcore(またはstackdump)を吐く現象のことであり、 + 無限ループなどのプロセスは生きているがサーバーの機能は提供されない状態の + ことではない。 + + +-------------------------------------------------------------------------- +< 目次 > + + * Cygwinでのstackdumpとcore + Cygwin上でcoreファイルを吐く方法を紹介する。 + + * coreファイルからスタックのバックトレースを得る + プログラムが吐いたcoreからバックトレースを得る方法を紹介する。 + + * 例 + 実際にとったログの例を示す。 + + +-------------------------------------------------------------------------- +< Cygwinでのstackdumpとcore > + + Cygwinでプログラムが強制終了する(アクセス違反などによるもの)場合、標準では + coreではなくstackdumpを吐く。これは全くといって良いほど役に立たないため、 + stackdumpをコピペされても開発者はおそらく見ないだろう。 + + よって次の方法で、stackdumpではなくcoreを吐くようにする。 + ** 環境変数に「error_start=dumper.exe」を追加する ** + + よくわからない場合、次のように作業するといい。(Win2000でのみ確認) + * デスクトップの「マイコンピュータ」を右クリックして「プロパティ」を出す。 + * [詳細]タブを開き、[環境変数]ボタンをクリックする。 + * ユーザー環境変数、システム環境変数のどちらか「CYGWIN」という変数がないか探す + * ある場合は、選択して[編集]ボタンを押し、[変数値]に「error_start=dumper.exe」 + を追加する。既に何かの単語がある場合は、単語を区切るため、 + 追加する部分の最初に半角スペースを入れることを忘れないこと。 + * ない場合は、システム環境変数に(Administrator権限がないならユーザー環境変数) + の[新規]ボタンを押して、[変数名]に「CYGWIN」、辺数値に + 「error_start=dumper.exe」を入力する。 + * OKを押してウィンドウを閉じ、Cygwinを起動しなおせばよい + + こうしておくと、stackdumpの変わりにcoreを吐くようになる。 + サイズが大きい場合、coreを吐くのには多少時間がかかる。 + またcoreを吐いている間、dumper.exeというプログラムのウィンドウが表示される。 + + +-------------------------------------------------------------------------- +< coreファイルからスタックのバックトレースを得る > + + coreを吐く場合、まず開発者はスタックのバックトレースを欲しがる。エラー個所を + 判断しやすいからだ。よって、gdbでバックトレースを取り出そう。 + まず、次のようにしてgdbを起動する。ここではmap-server.exeを例に出す。 + UNIX系OSではコアファイル名を修正する必要があるだろう。(「core」など) + + $ gdb -c map-server.exe.core + + なにやら色々英文が表示され、最後に (gdb) というプロンプトが出たはずだ。 + この直前にエラーの起こった関数やファイル名などと、その内容が表示されている + はずなので、これはコピペすべきだ。 + + また、ここで「bt」と入力すると、スタックのバックトレースが表示される。 + これもコピペするとよい。ただし、あまりにも長い場合は最初の十数行程度で + 十分だろう。 + + ちなみに、「p 変数名」のように入力すると変数を見たりも出来る。 + 関連しそうな変数の値を色々表示して一緒にコピペすると開発者が喜ぶかもしれない。 + + gdbを終了する場合は、「q」と打ち込む。 + + +-------------------------------------------------------------------------- +< 例 > + + 以下はmob.cのmob_warp()関数でわざとアクセス違反を起こしてとったログである。 + エラーの場所、どういう順で呼び出されたかがわかるだろう。 + + もちろん、Athenaのパッチ番号の報告を忘れないこと。 + パッチが違うと、ソースファイルが変わるので、行番号が役に立たなくなるためだ。 + + なお以下の例では、バックトレース以外に、 + pコマンドを使って該当のMOBの名前(英語)と、マップの名前を表示している。 + (FAKE_ANGEL, gef_dun03.gat) + +#0 mob_warp (md=0x10119c88, x=-1, y=-1, type=-1) at mob.c:1845 +1845 memset(NULL,0,1); +(gdb) bt +#0 mob_warp (md=0x10119c88, x=-1, y=-1, type=-1) at mob.c:1845 +#1 0x0042609d in mob_ai_sub_lazy (key=0x68e77f5, data=0x10119c88, + app=0x22fe88 "、\"") at mob.c:1412 +#2 0x00455b54 in db_foreach (table=0x22fe88, func=0x610691f2 <select+242>) + at db.c:414 +#3 0x10119c88 in ?? () +#4 0x0022fe88 in ?? () +#5 0x610691f2 in select () +(gdb) p mob_db[md->class].name +$1 = "FAKE_ANGEL\000\203t\203F\203C\203N\203G\203\223\203" +(gdb) p map[md->bl.m].name +$2 = "gef_dun03.gat\000\000r" + diff --git a/doc/db_ref.txt b/doc/db_ref.txt new file mode 100644 index 0000000..b4fd904 --- /dev/null +++ b/doc/db_ref.txt @@ -0,0 +1,147 @@ +========================================================================== + Athena dev 2.1.1 mod0659 現在のdbのリファレンス+α +-------------------------------------------------------------------------- + +< このファイルは何? > + + Athenaのdbファイルの設定方法のリファレンスです。 + + +< dbのリスト > + +cast_db.txt スキルのキャスティング時間とディレイ、維持時間等を設定。 +skill_db.txt スキルのデータを設定。 +skill_require_db.txt スキル使用条件を設定。 +pet_db.txt ペットのデータを設定。 + + +========================================================================== +1. db/cast_db.txt +-------------------------------------------------------------------------- + +id,cast_list,delay_list,upkeep_time,upkeep_time2 + +id: スキルのIDです。 +cast_list: スキルのキャスティング時間を設定します。レベル別に設定する場合は「:」を使います。 +delay_list: スキルのディレイ時間を設定します。レベル別に設定する場合は「:」を使います。 +upkeep_time:スキルの維持時間を設定します。レベル別に設定する場合は「:」を使います。 +upkeep_time2:スキルによって起こる状態異常の維持時間を設定します。レベル別に設定する場合は「:」を使います。(ただ速度減少はupkeep_timeを使いますので注意してください。) + +※武器の追加効果による状態異常はMG_STONECURSE(石化)、MG_FROSTDIVER(凍結)、NPC_STUNATTACK(スタン)、NPC_SLEEPATTACK(睡眠)、TF_POISON(毒)、NPC_CURSEATTACK(呪い)、NPC_SILENCEATTACK(沈黙)、NPC_BLINDATTACK(暗黒)のupkeep_time2を使います。(レベルは7で適用) +※急所攻撃の場合最大レベルは1ですがバッシュのレベルによって異常時間を変更できるので複数設定も可能です。 +※マキシマイズパワーとクローキングのupkeep_timeは維持時間ではなくSPが1減る時間です。 + + +========================================================================== +2. db/skill_db.txt +-------------------------------------------------------------------------- + +id,range,hit,inf,pl,nk,max,list_num,castcancel,cast_defence_rate,inf2,maxcount,skill_type,blow_count + +id: スキルのIDです。 +range: スキルの射程距離です。レベル別に設定する場合は「:」を使います。-1はキャラの武器射程と同じと言う意味で-2ならキャラの武器射程+1、-3ならキャラの武器射程+2になります。 +hit: 連打なら8、単発なら6(スキルのヒット数じゃありません。) +inf: スキル情報です。 + 0-パッシブ、1-敵、2-場所、4-即時発動、16-味方、32-罠 + 複数の物を入れると(数値を足して)正しく動作しません。 +pl: スキルの属性です。 + 0-無 1-水 2-地 3-火 4-風 5-毒 6-聖 7-暗 8-念 9-不死 +nk: 付加 1効果 2吹き飛ばし +MaxLv: スキルの最大レベルです。 +list_num: Hit回数のリストです。レベル別に設定する場合は「:」を使います。 +castcancel: スキルがキャンセルされるかどうかを設定します。yesはキャンセルされる物でnoはキャンセルされない物です。 +cast_defence_rate: キャスティング中に低下する防御力の比率です。ボウリングバッシュやクランドクロスのようにスキルキャスティング中に防御が減るスキルの設定に使います。 +inf2: スキル情報2です。1 - クェストスキル、2 - npc スキル、4-敵、8-場所、16-即時発動、32-味方、64-罠、128-PVPモードで自分を含む全ての物にダメージが入るスキル(地面スキルのみ)、256-普通の使用で自分に必ずダメージが入るスキル(地面スキルのみ)、512-自分には使うことができないスキル、1024 - 自分か自分のパーティにしか使えないスキル。2048 - 自分か自分のギルドにしか使えないスキル、同盟ギルドチェックは無し。(4、8、16、32、64は一部のアクティブスキルが使用。今は阿修羅覇鳳拳のみ使用。) +maxcount: 地面スキルのみに適用される物でスキルを置ける最大数です。0は置けないと言う意味ではなく制限がない物と言う意味です。 +skill_type: スキルの種類を設定します。weaponは武器スキルでmagicは魔法スキル、miscは武器でも魔法でもないスキルです。noneは決め難しい物等に設定します。ただこれを変えたとしてもスキルのダメージ計算がこれに合わせて変わるわけではなくダメージの計算はプログラムレベルで行なっています。これはスペルブレイカーで詠唱中止されるかどうかを設定する為の物です。(他のスキルでこの設定を使用する可能性もありますが今の所スペルブレイカーのみです。) magicに設定するとスペルブレイカーで詠唱中止されます。 +blow_count: スキルよるノックバック距離です。レベル別に設定する場合は「:」を使います。 + + +========================================================================== +3. db/skill_require_db.txt +-------------------------------------------------------------------------- + +id,list_hp,list_sp,list_hp_rate,list_sp_rate,list_zeny,list_weapon,state,spiritball,itemid1,amount1,itemid2,amount2,itemid3,amount3,itemid4,amount4,itemid5,amount5,itemid6,amount6,itemid7,amount7,itemid8,amount8,itemid9,amount9,itemid10,amount10 + +id: スキルのIDです。 +list_hp: スキル使用で減るHPの量です。レベル別に設定する場合は「:」を使います。 +list_sp: スキル使用で減るSPの量です。レベル別に設定する場合は「:」を使います。 +list_hp_rate: スキル使用で減るHPの比率です。レベル別に設定する場合は「:」を使います。(最大HPの比率ではなく現在HPの比率です。) +list_sp_rate: スキル使用で減るSPの比率です。レベル別に設定する場合は「:」を使います。(最大SPの比率ではなく現在SPの比率です。) +list_zeny: スキル使用で減るゼニの量です。レベル別に設定する場合は「:」を使います。 +list_weapon: スキルを使うことができる武器を設定します。 + 99 - 全ての武器、0 - 素手、1 - 短剣、2 - 片手剣、3 - 両手剣、4 - 片手槍、 + 5 - 両手槍、6 - 片手斧、7 - 両手斧、8 - 片手鈍器、9 - 両手鈍器、10 - ロッド、 + 11 - 弓、12 - ナックル、13 - 楽器、14 - 鞭、15 - 本、16 - カタール、 + 17~22: 二刀流 + 複数を設定する場合は「:」を使います。 +state:スキルを使用する為の条件を設定します。複数の設定はできません。 + none - 条件無し + hiding - ハイディング状態 + cloacking - クローキング状態。 + hidden: ハイディングやクロッキング状態 + riding: ペコペコに乗っている + falcon: 鷹を連れている + cart: カートを着けている + shield: シールドを持っている + sight: サイト状態 + explosionspirits: 爆裂波動状態 + recover_weight_rate: 自然回復できる重量 + move_enable: スキル使用位置が移動可能な場所 + water: 足元が水 +spiritball: スキル使用に必要な気弾の数です。レベル別に設定する場合は「:」を使います。 +itemid1: スキル使用に必要なアイテムのID、ポーションピッチャーの場合レベル別に使うことになるアイテムID +amount1: スキル使用に必要なアイテムの数(数が0の場合アイテムのIDがあるとアイテムは減らないけど触媒としてそのアイテムを持っている必要があるようになります。) ポーションピッチャーの場合レベル別に使うことになるアイテム数 +itemid2: 上同 +amount2: 上同 +itemid3: 上同 +amount3: 上同 +itemid4: 上同 +amount4: 上同 +itemid5: 上同 +amount5: 上同 +itemid6: 上同 +amount6: 上同 +itemid7: 上同 +amount7: 上同 +itemid8: 上同 +amount8: 上同 +itemid9: 上同 +amount9: 上同 +itemid10: 上同 +amount10: 上同 + + +========================================================================== +4. db/pet_db.txt +-------------------------------------------------------------------------- + + MobID,Name,JName,ItemID,EggID,AcceID,FoodID,Fullness,HungryDelay ,R_Hungry,R_Full,Intimate,Die,Capture,Speed,S_Performance,talk_convert_class,attack_rate,defence_attack_rate,change_target_rate,pet_script + +MobID: モンスターIDです。 +Name: 英語名前(ダミー) +JName: 名前 +ItemID: 捕獲用のアイテムID +EggID: 卵のアイテムID +AcceID: 装備アクセサリーのアイテムID +FoodID: 餌のアイテムID +Fullness: 1回の餌での満腹度増加率% +HungryDelay: 満腹度が1%減る為にかかる時間(秒) +R_Hungry: 空腹時餌やり親密度増加量 +R_Full: とても満腹時餌やり親密度減少量 +Intimate: 捕獲時親密度 +Die: 死亡時親密度減少量 +Capture: 捕獲率(万分率) +Speed: 移動速度 +S_Performance: スペシャルパフォマンスがあるかどうか(1であり0でなし) +talk_convert_class: 台詞を他のペットの物に変更。変更したいペットのモンスターIDを入れます。0の場合変更なしでマイナスを入れるとエモーション以外の物(台詞)は全て無視されます。 +attack_rate: 支援攻撃確率。(万分率) 主人が攻撃をしかけた場合。 +defence_attack_rate: 支援攻撃確率。(万分率) 主人が攻撃を受けた場合。 +change_target_rate: 攻撃目標を変更する確率。(万分率) +pet_script: ペットを持っている時適用されるステータスボーナスを設定。 + + + +========================================================================== +EOF +-------------------------------------------------------------------------- diff --git a/doc/help.txt b/doc/help.txt new file mode 100644 index 0000000..7b83258 --- /dev/null +++ b/doc/help.txt @@ -0,0 +1,453 @@ +GM Commands Help File +By Akaru +----------------------- +To use these commands, type them inside the message window where you usually type to chat. + +<ANNOUNCEMENT COMMANDS> + +/nb <message> +@kami <message> +Lets you make a GM global announcement without a name (in yellow) + +@kamib <message> +Lets you make a GM global announcement without a name (in blue) + +/b/@broadcast <message> +Lets you make a GM global announcement with your name + +/lb/@localbroadcast <message> +Broadcasts a GM message with name of the GM (in yellow) ONLY on your map + +/nlb <message> +Broadcasts a GM message without name of the GM (in yellow) ONLY on your map + +<INFORMATION COMMANDS> + +@who [match_text] +Lists who is currently online in your server and their location +@who2 [match_text] +Lists who is currently online in your server and their job +@who3 [match_text] +Lists who is currently online in your server and their party/guild + +@whomap [map] +Display a listing of who is online and where in a specifical map +@whomap2 [map] +Display a listing of who is online and their job in a specifical map +@whomap3 [map] +Display a listing of who is online and their party/guild in a specifical map + +@whogm [match_text] +Like @who+@who2+who3, but only for GM. + +@where <char name> +Tells you the location of a character + +@charstatsall +Displays stats of all characters. + +@charitemlist <char name> +Displays all items of a player. + +@charstoragelist <char name> +Displays all items of a player's storage. + +@charcartlist <char name> +Displays all items of a player's cart. + +@time/@date/@server_date/@serverdate/@server_time/@servertime +Display the date/time of the server + +@idsearch <part_of_item_name> +Search all items that name have part_of_item_name + +@ignorelist +Displays your ignore list + +@mapinfo [<1-3> [map]] +Give information about a map (general info +: 0: no more, 1: players, 2: NPC, 3: shops/chat). + +<CONTROL COMMANDS> + +@die +Kill yourself :) + +@alive +Revive yourself from death + +@kill <char name> +Kills specified character name +Example: +@kill TestChar -> The character named TestChar is dead + +@nuke <char name> +Kills specified character (nuclear effect). + +@save +Sets save point as current location + +@load +Warps you to your save point (a.k.a. butterfly wing) + +@warp <map name> <x> <y> +@rura <map name> <x> <y> +@mapmove <map name> <x> <y> +/mm <map name> <x> <y> +/mapmove <map name> <x> <y> +Warps you to the selected position +Example: +@warp morocc 150 160 -> Warps you to Morroc (X:150, Y:160) +@rura prontera 50 80 -> Warps you to Prontera (X:50, Y:80) + +@jump [x [y]] +Teleports you randomly in the map (a.k.a. fly wing) + +/shift/@jumpto/@warpto/@goto <char name> +Warps you to selected character +Example: +@jumpto TestChar -> You are warped to TestChar's current location + +@go <number/city_name> +Warps you to a set city: + -3: (Memo point 2) 1: morocc 5: izlude 9: yuno 13: niflheim + -2: (Memo point 1) 2: geffen 6: aldebaran 10: amatsu 14: louyang + -1: (Memo point 0) 3: payon 7: xmas (lutie) 11: gonryun 15: start point + 0: prontera 4: alberta 8: comodo 12: umbala 16: prison/jail + +/hide/@hide +GM Hide. Perfect hide that's totally invinsible. + +@heal [<HP> <SP>] - Heals the desired amount of HP and SP. No value specified will do a full heal. +@storage - Opens storage + +/recall/@recall <char name> - Warps target character to you. +@recallall - Recalls everyone on the server to you. +@guildrecall <guild_name/id> - Warps all online character of a guild to you. +@partyrecall <party_name/id> - Warps all online character of a party to you. +@revive <char name> - Revives target character. + +<SPAWNING COMMANDS> + +/item <item_name> - Gives you 1 of the desired item. +@item <item name or ID> <quantity> - Gives you the desired item. + +/monster <monster_name> - Spawns 1 of the desired monster. +@spawn/@monster/@summon <monster_name_or_monster_ID> [<number to spawn> [<desired_monster_name> [<x coord> [<y coord>]]]] +@monster2 <desired_monster_name> <monster_name_or_monster_ID> [<number to spawn> [<x coord> [<y coord>]]] +@spawn/@monster/@summon/@monster2 "desired monster name" <monster_name_or_monster_ID> [<number to spawn> [<x coord> [<y coord>]]] +@spawn/@monster/@summon/@monster2 <monster_name_or_monster_ID> "desired monster name" [<number to spawn> [<x coord> [<y coord>]]] + Spawns the desired monster with any desired name. + +<MAP OPTIONS COMMANDS> + +@pvpon - Turns pvp on on the current map +@pvpoff - Turns pvp off on the current map +@gvgon - Turns gvg on on the current map +@gvgoff - Turns gvg off on the current map + +<CHARACTER CONTROL COMMANDS> +@baselvlup <number of levels> - Raises your base level the desired number of levels. The max is 99 +@joblvlup <number of levels> - Raises your job level the desired number of levels. The max is 50 + +@job/@jobchange <job ID> +Changes your job to the job assigned to the ID + 0 Novice 7 Knight 14 Crusader 22 Formal + 1 Swordman 8 Priest 15 Monk 23 Super Novice + 2 Mage 9 Wizard 16 Sage + 3 Archer 10 Blacksmith 17 Rogue + 4 Acolyte 11 Hunter 18 Alchem + 5 Merchant 12 Assassin 19 Bard + 6 Thief 13 Knight2 20 Dancer + 21 Crusader2 + 24 Novice High 31 Lord Knight 38 Paladin + 25 Swordman High 32 High Priest 39 Monk + 26 Mage High 33 High Wizard 40 Professor + 27 Archer High 34 Whitesmith 41 Stalker + 28 Acolyte High 35 Sniper 42 Creator + 29 Merchant High 36 Assassin Cross 43 Clown + 30 Thief High 37 Peko Knight 44 Gypsy + 45 Paladin2 + +@option <param1> <param2> <param3> +Changes options for your character + <param1> <param2> (Stackable) <param3> (Stackable) + 01 Petrified 01 Poison 01 Sight + 02 Frozen 02 Cursed 02 Hide + 03 Stunned 04 Silenced 04 Cloak + 04 Sleeping 08 ??? 08 Level 1 Cart + 06 darkness 16 darkness 16 Falcon + 32 Peco Peco riding + 64 GM Perfect Hide + 128 Level 2 Cart + 256 Level 3 Cart + 512 Level 4 Cart + 1024 Level 5 Cart + 2048 Orc Head + 4096 Wedding Sprites + 8192 Ruwach + +@speed <1-1000> +Changes you walking speed. +1(Fastest)<---140(Default)----------------->1000(Slowest) + +@disguise <monster_name_or_monster_ID> +Change your appearence to other players to a mob. + +@undisguise +Restore your normal appearance. + +@model <hair ID> <hair color> <clothes color> +Changes your characters appearance (Hair type, Hair Colour and/or Clothes Colour) + + Hair ID (0-17) Hair Colour (0-8) Clothes Colour (0-4) + 0 Default 0 Default + 1 Blonde 1 Red + 2 Purple 2 Green + 3 Brown 3 White + 4 Green 4 Brown + 5 Blue + 6 White + 7 Black + 8 Red + +@effect <effect_id> [flag] +Give an efect to your character. + +@stpoint <number of points> +Gives you the desired number of stat points. + +@skpoint <number of points> +Gives you the desired number of skill points. + +@zeny <amount> +Gives you desired amount of Zeny. + +@str <amount> +@agi <amount> +@vit <amount> +@int <amount> +@dex <amount> +@luk <amount> +Adds desired amount to any stat. For example "@str 10" raises your str by 10 + +@spiritball <number> +Number = 1-1000 +Gives you monk "spirit spheres" like from the skill "Call Spirits" +(If the number you use is > 1000, your server may become instable or crash) + +@memo [memo_position] +set/change a memo location (no position: display memo points). + +@questskill <id> +Gives you the specified quest skill + +@lostskill <id> +Takes away the specified quest skill from you + +Quest Skill ID: + Novice + 142 = Emergency Care + 143 = Act dead + Swordsman + 144 = Moving HP Recovery + 145 = Attack Weak Point + 146 = Auto Berserk + Magician + 157 = Energy Coat + Archer + 147 = Arrow Creation + 148 = Charge Arrows + Acolyte + 156 = Holy Light + Merchant + 153 = Cart Revolution + 154 = Change Cart + 155 = Crazy Uproar/Loud Voice + Thief + 149 = Throw Sand + 150 = Back Sliding + 151 = Take Stone + 152 = Stone Throw + +<GUILD CONTROL COMMANDS> + +@guildlvup/@guildlvlup <# of levels> - Raise Guild by desired number of levels + +<EQUIPMENT COMMANDS> + +@refine <position> <+/-amount> +Upgrades equipment at the position specified (Stackable) +0 - All +1 - Lower Head +2 - Right Hand +4 - Robe/Garment +8 - Left Accessory +16 - Body/Armor +32 - Left Hand +64 - Feet +128 - Right Accessory +256 - Top Head +512 - Mid Head + +Example: +@refine 34 10 - Refines a 2 handed weapon to +10 +@refine 16 4 - Refines the body/armor to +4 + +@produce <equip name or equip ID> <element> <# of very's> +Element: 0=None 1=Ice 2=Earth 3=Fire 4=Wind +# of very's: 0=None 1=Very Strong 2=Very Very Strong 3=Very Very Very Strong + +Example: @produce 1163 3 3 - Produces a Very Very Very Strong (Your Nick)'s Fire Claymore + +<Q-PET/cutePET COMMANDS> +@hatch - Create a pet from your inventory eggs list. +@makeegg <ID> - Gives pet egg for monster ID in pet DB +@petfriendly <#> - Set pet friendly amount (0-1000) 0 = Min, 1000 = Max +@pethungry <#> - Set pet hungry amount (0-100) 0 = Min, 100 = Max +@petrename - Re-enable pet rename + +<REMOTE CHAR COMMANDS> +@charwarp <map name> <x> <y> <char name> +Warps character to location of choice +Example: +@charwarp morocc 150 160 TestChar -> Warps TestChar to Morroc (X:150, Y:160) + +@charstats <char name> +Displays the character's stats. + +@charignorelist <char name> +Displays ignore list of the player + +@inall <char name> +Allows all wispers for the player + +@exall <char name> +Blocks all wispers for the player + +@charoption <param1> <param2> <param3> <char name> +Does the same as the @option command only to target character. + +@charmountpeco <charname> +Give/remove to a player a peco (Class is required, but not skill). + +@charpetrename <charname> +Re-enable pet rename to a player. + +@charsave <map> <x> <y> <char name> +Changes the target player's respawn point. + +@charbaselvl <#> <char name> +Change a character's base level. + +@charjlvl <#> <char name> +Change a character's job level. + +@charjob/@charjobchange <job ID> <char name> +Changes target character's job. + +@charzeny <amount> <char name> +Give/take a player's Zeny + +@charstpoint <amount> <char name> +Give/take a player's stat points + +@charskpoint <amount> <char name> +give/take a player's skill points + +@charquestskill <#> <charname> +Gives to a player the specified quest skill. + +@charlostskill <#> <charname> +Takes away the specified quest skill from the player. + +@chardelitem <item_name_or_ID> <quantity> <player> +Remove items from a character + +@charmodel <hair type> <hair color> <clothes color> <char name> +Changes a player's model + +@chardisguise <monster_name_or_monster_ID> <char name> +Changes disguise of a player + +@charundisguise <char name> +Cancels disguise of a player + +@charchangesex <name> +Changes sex of a player (all characters of the account) + +@charblock/@block <name> +Blocks definitively a account + +@charunblock/@unblock <name> +Unblocks a account + +@charban/@ban/@banish/@charbanish <time> <name> +Ban temporarily a account + time usage: adjustement (+/- value) and element (y/a, m, d/j, h, mn, s) + Example: @ban +1m-2mn1s-6y testplayer + +@charunban/@unban/@unbanish/@uncharbanish <name> +Unban a account + +@jail <char_name> +Sends specified character in jails + +@unjail/@discharge <char_name> +Discharges specified character/prisoner + +<MASS CONTROL COMMANDS> +@night +All characters are in darkness + +@day +@option 00 00 00 are used on all characters + +@doom +Kills all NON GM chars on the server. + +@doommap +Kills all non GM characters on the map. + +@raise +Resurrects all characters on the server. + +@raisemap +Resurrects all characters on the map. + +@kick <charname> +Kicks specified character off the server + +@kickall +Kick all characters off the server + +<OTHER COMMANDS> + +@gat ---- デバッグ用(周辺gatを調べる) +@packet ---- デバッグ用(パケット色々) + +@mapexit +Kick all players and shut down map-server. + +@reloaditemdb +Reload item database (admin command) + +@reloadmobdb +Reload monster database (admin command) + +@reloadskilldb +Reload skills definition database (admin command) + +@reloadscript +Reload all scripts (admin command) + +@reloadgmdb +Reload GM levels (admin command) + +@enablenpc <NPC_name> +Enable a NPC (admin command) + +@disablenpc <NPC_name> +Disable a NPC (admin command) + +@email <actual@email> <new@email> +to change your e-mail (characters protection) diff --git a/doc/help1.txt b/doc/help1.txt new file mode 100644 index 0000000..865738d --- /dev/null +++ b/doc/help1.txt @@ -0,0 +1,398 @@ + ______ __ __ + /\ _ \/\ \__/\ \ + __\ \ \L\ \ \ ,_\ \ \___ __ ___ __ + /'__`\ \ __ \ \ \/\ \ _ `\ /'__`\/' _ `\ /'__`\ +/\ __/\ \ \/\ \ \ \_\ \ \ \ \/\ __//\ \/\ \/\ \L\.\_ +\ \____\\ \_\ \_\ \__\\ \_\ \_\ \____\ \_\ \_\ \__/.\_\ + \/____/ \/_/\/_/\/__/ \/_/\/_/\/____/\/_/\/_/\/__/\/_/ + _ _ _ _ _ _ _ _ _ _ _ _ _ + / \ / \ / \ / \ / \ / \ / \ / \ / \ / \ / \ / \ / \ +( e | n | g | l | i | s | h ) ( A | t | h | e | n | a ) + \_/ \_/ \_/ \_/ \_/ \_/ \_/ \_/ \_/ \_/ \_/ \_/ \_/ + +-------------------------------------------------------------- +GM COMMANDS +--------------------------------------------------------------------------------- +Note: + To use these commands, type them inside the message window where you usually + type to chat. +--------------------------------------------------------------------------------- +<> = type of parameter that the command need to have +[] = optionnal parameter +--------------------------------------------------------------------------------- + +@h/@help = display commands help inside the game. + +================================================================================= +ANNOUNCEMENT COMMANDS +================================================================================= +/b/@broadcast <message> = Broadcasts a GM message with name of the GM + (in yellow) +/nb/@kami <message> = Broadcasts a GM message without name of the GM + (in yellow) +@kamib <message> = Broadcasts a GM message without name of the GM + (in blue) +/lb/@localbroadcast <message> = Broadcasts a GM message with name of the GM + (in yellow) ONLY on your map +/nlb <message> = Broadcasts a GM message without name of the GM + (in yellow) ONLY on your map + +================================================================================= +INFORMATION COMMANDS +================================================================================= +@who [match_text] = Lists which characters are currently online in your + server and their location. [match_text] is a parameter + to search only characters that have this text in their + name. +@who2 [match_text] = Lists which characters are currently online in your + server and their job. [match_text] is a parameter to + search only characters that have this text in their + name. +@who3 [match_text] = Lists which characters are currently online in your + server and their party/guild. [match_text] is a + parameter to search only characters that have this text + in their name. +@whomap [map] = Displays a listing of which characters are online and + where they are in a specifical map. If [map] isn't + specified, you display characters on your map. +@whomap2 [map] = Displays a listing of which characters are online and + their job in a specifical map. If [map] isn't specified, + you display characters on your map. +@whomap3 [map] = Displays a listing of which characters are online and + their party/guild in a specifical map. If [map] isn't + specified, you display characters on your actual map. +@whogm [match_text] = Like @who+@who2+who3, but only for GM. +@where [char name] = Tells you the location of a character. If [char name] + isn't specified, you display your own location. +@charstatsall = Displays stats of all characters. +@charitemlist <char name> = Displays all items of a player. +@charstoragelist <char name> = Displays all items of a player's storage. +@charcartlist <char name> = Displays all items of a player's cart. +@ignorelist = Displays your ignore list +@mapinfo [<0-3> [map]] = Give information about a map (general info +: + 0: no more, 1: players, 2: NPC, 3: shops/chat). + +@time/@date/@server_date/@serverdate/@server_time/@servertime = Display the date/ + time of the server + +@guildspy <guild_name/id> = You will receive all messages of the specified guild + channel +@partyspy <party_name/id> = You will receive all messages of the specified party + channel + +================================================================================= +(YOURSELF-)CONTROL COMMANDS +================================================================================= +MOVE COMMANDS +------------- +/hide/@hide = GM Hide. Perfect hide that's totally + invisible. Type @hide again become + visible. +@save = Sets save point as current location +@load/@return = Warps you to your save point (like + butterfly wing) +/mm <mapname> <xpos> <ypos> = Warps you to the selected position +/mapmove <map_name> <x> <y> = Warps you to the selected position +@warp/@rura/@mapmove <mapname> <x> <y> = Warps you to the selected position + Example: @warp morocc 150 160 -> Warps + you to Morroc (X:150, Y:160) +@jump [x [y]] = Teleports you randomly in the map (like + fly wing) +/shift/@jumpto/@warpto/@goto <charname> = Warps you to selected character + Example: @jumpto TestChar -> You are + warped to TestChar's current + location +@go <number/city_name> = Warps you to a set city: + -3=(Memo point 2) 4=Alberta 11=Gon Ryun + -2=(Memo point 1) 5=Izlude 12=Umbala + -1=(Memo point 0) 6=Al de Baran 13=Niflheim + 0=Prontera 7=Lutie 14=Lou Yang + 1=Morroc 8=Comodo 15=Start point + 2=Geffen 9=Yuno 16=Prison + 3=Payon 10=Amatsu + +HEALTH COMMANDS +--------------- +@die = Kill yourself :) (suicide) +@alive = Revives yourself from death +@heal [<HP> <SP>] = Heals the desired amount of HP and SP. No value specified + will do a full heal. + +OTHER COMMANDS +--------------- +/resetstatus = Resets all status points for you to reuse. +/resetskill = Resets all skill points for you to reuse. +@job/@jobchange <job> = Changes your job to the job assigned to the ID: + 0 Novice 7 Knight 14 Crusader 22 Formal + 1 Swordman 8 Priest 15 Monk 23 Super Novice + 2 Mage 9 Wizard 16 Sage + 3 Archer 10 Blacksmith 17 Rogue + 4 Acolyte 11 Hunter 18 Alchem + 5 Merchant 12 Assassin 19 Bard + 6 Thief 13 Knight2 20 Dancer + 21 Crusader2 + 24 Novice High 31 Lord Knight 38 Paladin + 25 Swordman High 32 High Priest 39 Monk + 26 Mage High 33 High Wizard 40 Professor + 27 Archer High 34 Whitesmith 41 Stalker + 28 Acolyte High 35 Sniper 42 Creator + 29 Merchant High 36 Assassin Cross 43 Clown + 30 Thief High 37 Peko Knight 44 Gypsy + 45 Paladin2 +@lvup/@blevel/@baselvlup <number of levels> = Raises your base level the + desired number of levels. The max + is 99/255 (User Defined). +@joblvup/@jlevel/@joblvlup <number of levels> = Raises your job level the desired + number of levels. The max is 50 + For Basic Classes. For Super + Novice and Advanced Classes it is + 70. +@allskill/@allskills/@skillall/@skillsall = Give you all skills. +@option <param1> <param2> <param3> = Changes options of your character + Example: @option 0 0 16 - would give falcon + <param1> <param2> <param3> <param3> + 01 Petrified 01 Poison 01 Sight 128 Level 2 Cart + 02 Frozen 02 Cursed 02 Hide 256 Level 3 Cart + 03 Stunned 04 Silenced 04 Cloak 512 Level 4 Cart + 04 Sleeping 08 ??? 08 Level 1 Cart 1024 Level 5 Cart + 06 darkness 16 darkness 16 Falcon 2048 Orc Head + 32 Peco Peco riding 4096 Wedding Sprites + 64 GM Perfect Hide 8192 Ruwach +@mountpeco = Give/remove you a peco. (Class is required, but not skill) +@disguise <monster_name/monster_ID/NPC_ID> = Change your appearence to a mob or npc. + If using NPC ID 104 Will become an effect. + Speed of player will determine effect, be very careful with this ID + it can create client crashes with improper ids and can easily lag players + off of the server. +@undisguise = Restore your normal appearance. +@model <hair ID: 0-17> <hair color: 0-8> <clothes color: 0-4> + = Changes your characters appearance + (Hair type/colour and/or Clothes colour) + Hair ID (0-17) Hair Colour (0-8) Clothes Colour (0-4) + 0 Default 0 Default + 1 Blonde 1 Red + 2 Purple 2 Green + 3 Brown 3 White + 4 Green 4 Brown + 5 Blue + 6 White + 7 Black + 8 Red +@dye/@ccolor <clothes color: 0-4> = Changes your characters appearence + (only clothes color). +@hairstyle/@hstyle <hair ID: 0-17> = Changes your characters appearence + (only hair style). +@haircolor/@hcolor <hair color: 0-8> = Changes your characters appearence + (only hair color). +@speed <1-1000> = Changes you walking speed (1 being the + fastest & 1000 the slowest. Default 150. +@effect <effect_id> [flag] = Give an efect to your character. +@stpoint <number of points> = Gives you the desired number of stat + points. +@skpoint <number of points> = Gives you the desired number of skill + points. +@zeny <amount> = Gives you desired amount of Zeny. +@str,@agi,@vit,@int,@dex,@luk <amount> = Adds desired amount to any stat. For + example "@str 10" raises your str by 10. +@statall/@statsall/@allstats/@allstat [value] = Adds value in all stats (maximum + if no value). +@memo [memo_position] = set/change a memo location. + (no position: display memo points). +@spiritball <number: 1-1000> = Gives you monk "spirit spheres" like + from the skill "Call Spirits" (If the + number you use is > 1000, your server + may become instable or crash) +@questskill <id> = Gives you the specified quest skill +@lostskill <id> = Takes away the specified quest skill + from you + Novice Archer Swordsman + 142 = Emergency Care 147 = Arrow Creation 144 = Moving HP Recovery + 143 = Act dead 148 = Charge Arrows 145 = Attack Weak Point + Thief 146 = Auto Berserk + 149 = Throw Sand Merchant + 150 = Back Sliding 153 = Cart Revolution + 151 = Take Stone 154 = Change Cart + 152 = Stone Throw 155 = Crazy Uproar/Loud Voice + Acolyte Magician + 156 = Holy Light 157 = Energy Coat + +================================================================================= +MONSTERS COMMANDS +================================================================================= +/monster <monster_name> = Spawns 1 of the desired monster. +@spawn/@monster/@summon <monster_name_or_monster_ID> [<number to spawn> [<desired_monster_name> [<x coord> [<y coord>]]]] + = Spawns the desired monster with any desired name, + quantity and x and y location (if specified). +@monster2 <desired_monster_name> <monster_name_or_monster_ID> [<number to spawn> [<x coord> [<y coord>]]] + = Spawns the desired monster with any desired name. +@spawn/@monster/@summon/@monster2 "desired monster name" <monster_name_or_monster_ID> [<number to spawn> [<x coord> [<y coord>]]] +@spawn/@monster/@summon/@monster2 <monster_name_or_monster_ID> "desired monster name" [<number to spawn> [<x coord> [<y coord>]]] + = There 2 last forms can use spaces for desired names. +@killmonster [map] = kill all monsters of the map (they drop items) +@killmonster2 = kill all monsters of your map (without drops) + +================================================================================= +ITEMS COMMANDS +================================================================================= +@storage = Opens storage +@gstorage = Opens guild storage +/item <item_name> = Gives you 1 of the desired item. +@item <item name or ID> [quantity] = Gives you the desired item. +@item2 <item name or ID> <quantity> <Identify_flag> <refine> <attribut> <Card1> <Card2> <Card3> <Card4> + = Gives you the desired item. +@itemreset = Remove all your items. +@itemcheck = Check your items with authorised items. +@idsearch <part_of_item_name> = search all items that name have + part_of_item_name +@refine <equip position> <+/- amount> = Upgrades equipment at the position + specified (Stackable) + 0 - All + 1 - Lower Head + 2 - Right Hand + 4 - Robe/Garment + 8 - Left Accessory + 16 - Body/Armor + 32 - Left Hand + 64 - Foot Gear + 128 - Right Accessory + 256 - Top Head + 512 - Mid Head + Example: @refine 34 10 - Refines a 2 handed weapon to +10 + @refine 16 4 - Refines the body/armor to +4 +@produce <equip name or equip ID> <element> <# of very's> + Element: 0=None 1=Ice 2=Earth 3=Fire 4=Wind + It has separately with fragment 3 of the attribute + stars, you can apply. + # of very's: 0=None 1=Very Strong 2=Very Very Strong 3=Very Very Very Strong + Example: @produce 1163 3 3 - Produces a Very Very Very Strong (Your Nick)'s + Fire Claymore +@repairall = Repair all items of your inventory + +================================================================================= +PVP COMMANDS +================================================================================= +@pvpon = Turns pvp on on the current map +@pvpoff = Turns pvp off on the current map +@gvgon/@gpvpon = Turns gvg on on the current map +@gvgoff/@gpvpoff = Turns gvg off on the current map +@agitstart = Starts Guild Wars (War of Emperium) +@agitend = End Guild Wars (War of Emperium) + +================================================================================= +GROUPS COMMANDS +================================================================================= +@party <party_name> = Create a party +@guild <guild_name> = Create a guild. +@guildlvup/@guildlvlup <# of levels> = Raise Guild by desired number of levels +@guildrecall <guild_name/id> = Warps all online character of a guild to you. +@partyrecall <party_name/id> = Warps all online character of a party to you. + +================================================================================= +PETS COMMANDS +================================================================================= +@hatch = Create a pet from your inventory eggs list. +@makeegg <ID> = Gives pet egg for monster ID in pet DB +@petfriendly <#> = Set pet friendly amount (0-1000) 0 = Min, 1000 = Max +@pethungry <#> = Set pet hungry amount (0-100) 0 = Min, 100 = Max +@petrename = Re-enable pet rename + +================================================================================= +REMOTE CHARACTERS COMMANDS +================================================================================= +@kill <char name> = Kills specified character name + Example: @kill TestChar -> The character named + TestChar is dead +@jail <char_name> = Sends specified character in jails +/recall/@recall <char name> = Warps target character to you. +@recallall = Warps every character online to you. +@unjail/@discharge <char_name> = Discharges specified character + or prisoner +@charwarp/@rura+ <mapname> <x> <y> <char name> = Warps character to location of + choice: Example: + @charwarp morocc 150 160 testet +@revive <char name> = Revives target character. +@charstats <char name> = Displays the character's stats. +@charignorelist <char name> = Displays ignore list of the player +@inall <char name> = Allows all wispers for the player +@exall <char name> = Blocks all wispers for the player +@charoption <param1> <param2> <param3> <char name> = Does the same as the @option + command only to target + character. +@charmountpeco <charname> = Give/remove to a player a peco (Class is + required, but not skill). +@charpetrename <charname> = Re-enable pet rename to a player. +@charsave <map> <x> <y> <char name> = Changes the target player's respawn point. +@charbaselvl <#> <char name> = Change a character's base level. +@charjlvl <#> <char name> = Change a character's job level. +@charjob/@charjobchange <job ID> <char name> = Changes target character's job. +@charzeny <amount> <char name> = Give/take a player's Zeny +@charstpoint <amount> <char name> = Give/take a player's stat points +@charskpoint <amount> <char name> = Give/take a player's skill points +@charskreset <charname> = Reset skills of a character. +@charstreset <charname> = Reset stats of a character. +@charreset <charname> = Reset stats AND skills of a character. +@charquestskill <#> <charname> = Gives to a player the specified quest skill. +@charlostskill <#> <charname> = Takes away the specified quest skill from + the player. +@chardelitem <item_name_or_ID> <quantity> <player> = Remove items from a character +@charmodel <hair type> <hair color> <clothes color> <char name> = Changes a + player's model +@chardisguise <monster_name/ID> <char name> = Changes disguise of a player +@charundisguise <char name> = Cancels disguise of a player +@charchangesex <name> = Changes sex of a player (all characters of + the account) +@charblock/@block <name> = Blocks definitively a account +@charunblock/@unblock <name> = Unblocks a account +@charban/@ban/@banish/@charbanish <time> <name> = Ban temporarily a account + Time usage: adjustement + (+/- value) and element + (y/a, m, d/j, h, mn, s) + Example: + @ban +1m-2mn1s-6y testplayer +@charunban/@unban/@unbanish/@uncharbanish <name> = Unban a account +@kick <charname> = Kicks specified character off the server +@kickall = Kick all characters off the server +@mapexit = Kick all players and shut down map-server. +@doom = Kills all NON GM chars on the server. +@doommap = Kills all non GM characters on the map. +@raise = Resurrects all characters on the server. +@raisemap = Resurrects all characters on the map. + +================================================================================= +ENVIRONMENT COMMANDS +================================================================================= +@night = Uses @option 00 16 00 on all characters. All characters are in darkness +@day = Uses @option 00 00 00 on all characters. + +================================================================================= +MAIL SYSTEM (SQL Only) +================================================================================= +@checkmail = Checks # of messages in your mailbox. +@listmail = Lists all the messages in your mailbox. +@listnewmail = Lists all new messages in your mailbox. +@readmail <#> = Reads a message in your mailbox. +@deletemail <#> = Deletes a message in your mailbox. + +@sendmail <name> <message> = Sends a message to another player. Use quotes if + the player has spaces in their name. + +@sendprioritymail <name> <message> = Send priority mail to a player. + +Use * for name to send to all players. +================================================================================= +ADMINISTRATION COMMANDS +================================================================================= +@reloaditemdb = Reload item database (admin command) +@reloadmobdb = Reload monster database (admin command) +@reloadskilldb = Reload skills definition database (admin command) +@reloadscript = Reload all scripts (admin command) +@reloadgmdb = Reload GM levels (admin command) +@enablenpc <NPC_name> - Enable a NPC (admin command) +@disablenpc <NPC_name> - Disable a NPC (admin command) + +@gat = For debugging (you inspect around gat) +@packet = For debugging (packet variety) + +@GM <password> = it becomes GM! +@email <actual@email> <new@email> = to change your e-mail (characters protection) + +@refreshonline = Rechecks to make sure online column is correct (SQL Only)
\ No newline at end of file diff --git a/doc/history.txt b/doc/history.txt new file mode 100644 index 0000000..f0d33b4 --- /dev/null +++ b/doc/history.txt @@ -0,0 +1,173 @@ +Version 1.4.4
+===========================================================
+- Fixed a bug that caused the drop chance for all MVP drops
+ to become 0 when the "View Mob" option was used in the
+ Mob Drop List. (This was forgotten in the previous
+ update.)
+- Finally updated the "item_descriptions.txt" file.
+
+Version 1.4.3
+===========================================================
+- Fixed a bug that caused the drop chance for all drops to
+ become 0 when the "View Mob" option was used in the Mob
+ Drop List.
+- Fixed a bug that caused other mob entries to show up when
+ searching for a specific mob with an ID that can be found
+ as a string subset within the other mob's entry.
+- Fixed a bug that caused all new entries to go into the
+ "item_db2.txt" or "mob_db2.txt" files.
+- Tweaked some of the code for the Mob Skill Editor's
+ interface to better help with editing/creating skill
+ entries.
+
+Version 1.4.2
+===========================================================
+- Corrected the labels on the Mob Location Editor window.
+ Thanks to ShadowLady for pointing this out.
+
+Version 1.4.1
+===========================================================
+- Added functionality to the "Locations" button in the Mob
+ View. It will now open up the Mob Location Editor window.
+- Added a list of map names with a brief description to the
+ "ID Helper.txt" file to be used with the Map Helper.
+- Added a new option in the "ADE.ini" file that points to
+ the location of the files with the mobs' spawn points.
+- Fixed a bug with the Drop List feature that would keep
+ the window open till it was closed by the cancel button
+ or the window's close button.
+- Fixed a bug that caused the selected entry to not
+ properly update when saved. Thanks to Taike for reporting
+ this bug.
+- Redid the code for handling backups. So now when you
+ restore the backup files, it won't reload both the item
+ and mob related files, it only restores the backups for
+ item related files while using the Item View and mob
+ related files for the Mob View, and it will no longer
+ rename files as a backup file if they have no backup to
+ replace them with.
+
+Version 1.4.0
+===========================================================
+- Added the Mob Skill Editor feature to ADE. It is
+ available through the Skills button in the Mob View.
+ This adds handling of the "mob_skill_db.txt" file.
+- Fixed a bug with the Mob's Mode ID Helper that caused
+ the value to keep increasing everytime it was opened
+ beyond the first time. Thanks to Lupus for pointing this
+ out.
+- Changed the font to Microsoft Sans Serif from MS Sans
+ Serif to better support the garbled text some items,
+ mobs, or other things may have.
+
+Version 1.3.2
+===========================================================
+- Edited the print functions related to the the following
+ DB files: "item_bluebox.txt", "item_cardalbum.txt",
+ "item_giftbox.txt", "item_violetbox.txt", and
+ "mob_branch.txt". Thanks to ShadowLady for pointing out
+ this problem, as well as mentioning the label change
+ mentioned below.
+- Edited a label for Mob Modes in ID Helper from
+ "Detects Hidden (Sensor)" to "Detects Hidden".
+- Redid some of the code in the data processing subroutine
+ for the Item and Mob DB files. This should prevent any
+ overflow/invalid index errors. Thanks to Cheex for
+ pointing this out.
+- Fixed a bug that caused ADE to crash if any of the files
+ it would normally create backups of weren't there. This
+ is more common among eAthena 1.0 RCx Text installs, due
+ to their lack of an "item_db2.txt" file. (This occured
+ while trying to save changes.)
+
+Version 1.3.1
+===========================================================
+- Added an ID Helper for the Mob Mode ID section.
+- You can now see a list of all mobs that drop the selected
+ item in ADE's Item View by clicking on the button in the
+ upper right corner that's labeled "Mob Drop List".
+
+Version 1.3.0
+===========================================================
+- Added support for handling five more of the server's DB
+ files. ADE now handles: "item_bluebox.txt",
+ "item_cardalbum.txt", "item_giftbox.txt",
+ "item_violetbox.txt", and "mob_branch.txt".
+- Added a "Restore Backup Files" option in the File menu.
+- Changed "Revert" to "Revert to Last Save".
+- ADE now inserts a blank comment line after the initial
+ header line and the last line for most of the server
+ files.
+- ADE will automatically set the compatability mode to
+ support higher jobs, if it reads in a job ID for any such
+ jobs while set for low jobs only.
+- Fixed a couple of minor bugs and tweaked some of the code.
+
+Version 1.2.0
+===========================================================
+- Added support for generating the item files related to
+ card labels. The files are "cardpostfixnametable.txt" and
+ "cardprefixnametable.txt". The file "card_labels.txt" is
+ used to hold the card labels. (To create it, just copy
+ any "cardprefixnametable.txt" file to the DB folder being
+ used and rename it to "card_labels.txt".)
+- Fixed a bug found by Solid that caused the program to end
+ when a Mob's entry had an invalid Race ID or Element ID.
+ Also applied this same fix to some of the other IDs that
+ were vunerable.
+- Resized the maximum entries allowed for both Items and
+ Mobs. Items now have a current max of 15,000 entries and
+ Mobs have a max of 1,000. Both used to have 10,000.
+- Included a minimum ID and max ID allowed when dealing
+ with either Items or Mobs. The new Item ID range is 501
+ to 15000, and the new Mob ID range is 1001 to 2000. Any
+ entries with IDs out of the range won't be kept.
+- Slightly redid some of the code with printing the headers
+ of all files made with the program.
+
+Version 1.1.1
+===========================================================
+- Fixed a bug that caused the duplicate scan process to be
+ used again during adding and removing entries.
+- Replaced the text box for the Item's Sex ID with a combo
+ box. (The Item's Sex ID is used to determine if the item
+ is limited to a character's sex or can be used by all.)
+- Added the ID Helper for the Item View ID text box, and
+ included a file called "Item Helper.txt" that contains
+ the data for it.
+- Fixed a bug that caused the "Allow High Jobs" option for
+ compatibility to be turned on when even with it set to
+ being off by default.
+- Added a small bit of code to print "All Low Jobs" for
+ the description files for any items that only allow
+ characters with low jobs only to use the item.
+- Added a temp fix for any forms that can get stuck behind
+ the main window by allowing them to show up in the
+ taskbar.
+
+Version 1.1.0
+===========================================================
+- Added support for higher jobs like Knight Lord, Assassin
+ Cross, High Priest, etc.
+- Added duplication scanning for the DB entries. This will
+ remove duplicates found. (An entry is considered a
+ duplicate if the ID or DBName is the same.)
+- Fixed a bug that would cause the program to keep loading
+ if you closed the progress bar during startup.
+
+Version 1.0.1
+===========================================================
+- Fixed a bug that caused a null mob DB entry.
+- Added some code to let the program load even if it didn't
+ find the files used for loading.
+- Redid small portions of the code to increase performance.
+
+Version 1.0.0
+===========================================================
+- Merged the previous programs.
+- Added a mob DB file editor.
+
+Version 0.X
+===========================================================
+- Created Stand-alone Item Database Editor.
+- Created Stand-alone Item File Generator.
diff --git a/doc/inter_server_packet.txt b/doc/inter_server_packet.txt new file mode 100644 index 0000000..2532803 --- /dev/null +++ b/doc/inter_server_packet.txt @@ -0,0 +1,204 @@ +S map鯖=>inter鯖 +R inter鯖=>map鯖 + +パケット長リスト +R 3800-388f + -1,-1,27, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + -1, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 35,-1,11,15, 34,29, 7,-1, 0, 0, 0, 0, 0, 0, 0, 0, + 10,-1,15, 0, 79,17, 7,-1, 0,-1,-1,-1, 14,67,186,-1, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 11,-1, 7, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +S 3000-308f + -1,-1, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 6,-1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 72, 6,52,14, 10,29, 6,-1, 34, 0, 0, 0, 0, 0, 0, 0, + -1, 6,-1, 0, 55,17, 6,-1, 14,-1,-1,-1, 14,19,186,-1, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 48,14,-1, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + + + +S 3000 <len>.w <message>.?B + GMメッセージ送信要求 +R 3800 <len>.w <message>.?B + GMメッセージ +S 3001 <len>.w <src-nick>.24B <dst-nick>.24B <message>.?B + Wis送信要求 +R 3801 <len>.w <wis-id>.l <src-nick>.24B <dst-nick>.24B <message>.?B + Wisデータ受信 + wis-id=inter鯖内部のWis-id:map鯖では3002で送信するためだけに使う +S 3002 <wis-id>.l <flag>.B + このmap鯖でのWisの送信結果 + flag=0 送信完了(このmap鯖にいたので、送信した) + flag=1 送信失敗(このmap鯖にはそんな人いません) + flag=2 送信終了(このmap鯖にいるが、ignoreされている) +R 3802 <src-nick>.24B <flag>.B + 全map鯖でのWis送信結果 + flag=0 送信完了 + flag=1 送信失敗(どのmap鯖にもそんな人いません) + flag=2 送信失敗(ignoreされた) +S 3010 <account_id>.l + 倉庫データ要求 +R 3810 <len>.w <account_id>.l <storage>.?B + 倉庫データ受信 +S 3011 <len>.w <account_id>.l <storage>.?B + 倉庫データ送信&保存要求 +R 3811 <account_id>.l <flag>.b + 倉庫データ保存終了 + flag=0 成功(現在の仕様では必ず0なので、返信を待たなくてもいい) + +S 3020 <account_id>.l <party_name>.24B <nick_name>.24B <map_name>.16B <level>.w + パーティ作成要求 +R 3820 <account_id>.l <fail>.B <party_id>.l <party_name>.24B + パーティ成功可否(自分のマップ鯖のみ) + fail=00 パーティ作成成功 + fail=01 失敗(party_id,pary_nameはゴミ) +S 3021 <party_id>.l + パーティ情報要求 +R 3821 <len>.w <struct party>.?B + (struct partyの最初4バイトはparty_id) + len=8 パーティは存在しない(受信したら該当キャラを未所属に変更する) + len>8 パーティ情報(受信したらクライアントに送ること) + (要求してきたマップ鯖へ) +S 3022 <party_id>.l <account_id>.l <nick>.24B <map_name>.16B <level>.w + パーティ追加要求 +R 3822 <party_id>.l <account_id>.l <fail>.B + パーティ追加通知(要求してきたマップ鯖へ) + fail=00で成功、fail=01で失敗 + (成功時はこの直後に全鯖にパーティ情報が送られる) +S 3023 <party_id>.l <account_id>.l <exp>.w <item>.w + パーティ設定変更要求 +R 3823 <party_id>.l <account_id>.l <exp>.w <item>.w <fail>.B + パーティ設定変更通知(成功の場合全マップ鯖へ通知) + fail=0x00 パーティ設定変更完了 + fail=0x01 expの変更失敗 + fail=0x10 itemの変更失敗 +S 3024 <party_id>.l <account_id>.l + パーティ脱退要求 +R 3824 <party_id>.l <account_id>.l <nick>.24B + パーティ脱退通知(全マップ鯖へ) +S 3025 <party_id>.l <account_id>.l <map_name>.16B <online>.B <level>.w + パーティマップ更新/オンライン要求 +R 3825 <party_id>.l <account_id>.l <map_name>.16B <online>.B <level>.w + パーティマップ更新通知(全マップ鯖へ) +S 3026 <party_id>.l + パーティ解散要求(送られることはないと思われる) +R 3026 <party_id>.l <fail>.B + パーティ解散 + fail=00 パーティは解散された(今のところ必ず00) + (map鯖内の不要データ削除のためだけに使われる) +S 3027 <len>.w <party_id>.l <account_id>.l <message>.?B + パーティ内発言要求 +R 3827 <len>.w <party_id>.l <account_id>.l <message>.?B + パーティ内発言通知(全マップ鯖へ) +S 3028 <party_id>.l <account_id>.l <nick>.24B + 別パーティに所属していないかチェック<party_id>は本来の所属 + + +S 3030 <len>.w <account_id>.l <guild_name>.24B <struct guild_member>.?B + ギルド作成要求 +R 3830 <account_id>.l <guild_id>.l + ギルド作成可否(guild_id=0で失敗) +S 3031 <guild_id>.l + ギルド情報要求 +R 3831 <len>.w <struct guild>.?B + ギルド情報 + len=8 ギルドは存在しない + len>8 ギルド情報 +S 3032 <len>.w <guild_id>.l <struct guild_member>.?B + ギルドメンバ追加要求 +R 3832 <guild_id>.l <account_id>.l <charactor_id>.l <fail>.B + ギルド追加メンバ通知 + fail=0で成功,1で失敗 +S 3034 <guild_id>.l <account_id>.l <charactor_id>.l <flag>.B <mes>.40B + ギルド脱退/追放要求 + flag=0 脱退 / 1 追放 +R 3834 <guild_id>.l <account_id>.l <charactor_id>.l <flag>.B <mes>.40B <nick>.24B + ギルド脱退/追放通知 +S 3035 <guild_id>.l <account_id>.l <charactor_id>.l <online>.B <lv>.w <class>.w + ギルドメンバ情報更新要求 +R 3835 <guild_id>.l <account_id>.l <charactor_id>.l <online>.B <lv>.w <class>.w + ギルドメンバ情報更新通知 +S 3036 <guild_id>.l + ギルド解散要求 +R 3836 <guild_id>.l <fail>.B + ギルド解散通知 + flag=00 解散成功 / 01 失敗 +S 3037 <len>.w <guild_id>.l <account_id>.l <message>.?B + ギルド発言要求 +R 3837 <len>.w <guild_id>.l <account_id>.l <message>.?B + ギルド発言通知 +S 3038 <guild_id>.l <account_id>.l <charactor_id>.l + 別ギルドに所属していないかチェック +S 3039 <len>.w <guild_id>.l <type>.w <data>.?B + 各種基本情報更新要求 +R 3839 <len>.w <guild_id>.l <type>.w <data>.?B + 各種基本情報更新通知 +S 303A <len>.w <guild_id>.l <account_id>.l <char_id>.l <type>.w <data>.?B + 各種ギルドメンバ情報更新要求 +R 383A <len>.w <guild_id>.l <account_id>.l <char_id>.l <type>.w <data>.?B + 各種ギルドメンバ情報更新通知 +S 303B <len>.w <guild_id>.l <position>.l <struct guild_position>.?B + ギルド役職変更要求 +R 383B <len>.w <guild_id>.l <position>.l <struct guild_position>.?B + ギルド役職変更通知 +S 303C <guild_id>.l <skill_num>.l <account_id>.l + ギルドスキル割り振り +R 383C <guild_id>.l <skill_num>.l <account_id>.l + ギルドスキル割り振り(skill_num==0で失敗) +S 303D <guild_id1>.l <guild_id2>.l <account_id1>.l <account_id2>.l <flag>.B + ギルド同盟/敵対要求 + flag=0 同盟 / 1 敵対 / 8 同盟解除 / 9 敵対解除 +R 383D <guild_id1>.l <guild_id2>.l <account_id1>.l <account_id2>.l <flag>.B <name1>.24B <name2>.24B + ギルド同盟/敵対通知 + flag=0 同盟 / 1 敵対 + flag=0x10 同盟失敗 / 0x11 敵対失敗 +S 303E <guild_id>.l <message1>.60B <message2>.120B + ギルド告知設定要求 +R 383E <guild_id>.l <message1>.60B <message2>.120B + ギルド告知設定通知 +S 303F <len>.w <guild_id>.l <dummy>.l <emblem_data>.?B + ギルドエンブレム変更要求 dummyは0固定 +R 383F <len>.w <guild_id>.l <dummy>.l <emblem_data>.?B + ギルドエンブレム変更通知 +(以下、ギルド系追加予定) + +S 3080 <account_id>.l <char_id>.I <pet_type>.w <pet_lv>.w <pet_egg_id>.w + <pet_equip>.w <pet_intimate>.w <pet_hungry>.w <rename_flag>.B <incuvate>.B + <pet_name>.24B + ペット生成要求 +R 3880 <account_id>.l <fail>.B <pet_id>.l + ペット生成成功可否 + fail=00 ペット生成成功 + fail=01 失敗(pet_idはゴミ) +S 3081 <account_id>.l <char_id>.I <pet_id>.l + ペットデータ要求 +R 3881 <len>.w <account_id>.l <fail>.B <struct s_pet>.?B + ペットデータ受信 + fail=00 ペットデータ転送 + fail=01 ペットのデータがないかもしくはaccount_idかchar_idが合わないので + 失敗(s_petはゴミ) +S 3082 <len>.w <account_id>.l <struct s_pet>.?B + ペットデータ送信&保存要求 +R 3882 <account_id>.l <flag>.b + ペットデータ保存終了 + flag=0 成功(現在の仕様ではメモリ不足以外には必ず0なので、返信を + 待たなくてもいい) + flag=1 失敗 +S 3083 <pet_id>.l + ペットデータ削除要求 +R 3883 <flag>.b + ペットデータ削除終了 + flag=0 成功(現在の仕様では既にデータがいない場合以外は必ず0なので、 + 返信を待たなくてもいい) + flag=1 失敗(pet_idに当たるデータがない場合の物ですが既にデータが + いないと言うことは削除する必要がないことにもなりますので + 意味はありません) + diff --git a/doc/item.txt b/doc/item.txt new file mode 100644 index 0000000..0629364 --- /dev/null +++ b/doc/item.txt @@ -0,0 +1,1451 @@ +ID Ename + +0 Default +501 Red Potion +502 Orange Potion +503 Yellow Potion +504 White Potion +505 Blue Potion +506 Green Potion +507 Red Herb +508 Yellow Herb +509 White Herb +510 Blue Herb +511 Green Herb +512 Apple +513 Banana +514 Grape +515 Carrot +516 Sweet Potato +517 Meat +518 Honey +519 Milk +520 Hinalle Leaflet +521 Aloe Leaflet +522 Mastela Fruit +523 Holy Water +525 Panacea +526 Royal Jelly +528 Monster's Feed +529 Candy +530 Candy Cane +531 Apple Juice +532 Banana Juice +533 Grape Juice +534 Carrot Juice +535 Pumpkin +536 Ice Cream +537 Pet Food +538 Well-baked Cookie +539 Piece of Cake +540 Falcon food +541 Pecopeco food +542 Festive Cookie +543 Festive Rainbow Cake +544 Raw Fish +545 Red Potion Bottle +546 Yellow Potion Bottle +547 White Potion Bottle +548 Cheese +549 Hot Potato +550 Rice Popper +551 Sushi +552 Ketupat +553 Dumpling +554 Mochi +555 ?? +556 ?????? +557 ??????_???_?????? +558 Chocolate +559 Hand-made Chocolate +601 Fly Wing +602 Butterfly Wing +603 Old Blue Box +604 Branch of Dead tree +605 Anodyne +606 Aloevera +607 Yggdrasilberry +608 Yggdrasil Seed +609 Amulet +610 Yggdrasil Leaf +611 Magnifier +612 Mini Furnace +613 Iron Hammer +614 Golden Hammer +615 Oridecon Hammer +616 Old Card Album +617 Old Violet Box +618 Worn Out Scroll +619 Unripe Apple +620 Orange Juice +621 Bitter Herb +622 Rainbow Carrot +623 Earthworm the Dude +624 Rotten Fish +625 Rusty Iron +626 Monster Juice +627 Sweet Milk +628 Well Dried Bone +629 Singing Flower +630 Dew Laden Moss +631 Deadly Noxious Herb +632 Fatty Chubby Earthworm +633 Baked Yam +634 Tropical Banana +635 Horror of Tribe +636 No Recipient +637 Old Broom +638 Silver Knife of Chastity +639 Armlet of Obedience +640 Shining Stone +641 Contracts in Shadow +642 Book of Devil +643 Pet Incubator +644 Gift Box +645 Concentration Potion +656 Awakening Potion +657 Berserk Potion +658 Tribal Solidarity +659 Her Heart +660 Red Candle +661 Sky Apron +663 Rice Cake +664 Gift Box +665 Gift Box +666 Gift Box +667 Gift Box +668 Angpow +669 ??????_??????_???? +670 ???? +671 ?? +672 ???? +673 ?? +674 ????? +675 ?? +676 ???? +677 ??? +678 ?? +679 ??? +701 Ora Ora +702 Animal Gore +703 Hinalle +704 Aloe +705 Clover +706 Four-Leaf Clover +707 Singing Plant +708 Ment +709 Izidor +710 Illusion Flower +711 Shoot +712 Flower +713 Empty Bottle +714 Emperium +715 Yellow Gemstone +716 Red Gemstone +717 Blue Gemstone +718 Garnet +719 Amethyst +720 Aquamarine +721 Emerald +722 Pearl +723 Ruby +724 Cursed Ruby +725 Sardonyx +726 Sapphire +727 Opal +728 Topaz +729 Zircon +730 1 Carat Diamond +731 2 Carat Diamond +732 3 Carat Diamond +733 Cracked Diamond +734 Red Frame +735 Chung Jah +736 China +737 Black Ladle +738 Pencil Case +739 Rouge +740 Puppet +741 Poring Doll +742 Chonchon Doll +743 Spore Doll +744 Bouquet +745 Wedding Bouquet +746 Glass Bead +747 Crystal Mirror +748 Witherless Rose +749 Frozen Rose +750 Baphomet Doll +751 Osiris Doll +752 Grasshopper Doll +753 Yoyo Doll +754 Raccoon Doll +756 Rough Oridecon +757 Rough Elunium +901 Danggie +902 Tree Root +903 Reptile Tongue +904 Scorpion Tail +905 Stem +906 Pointed Scale +907 Resin +908 Spawn +909 Jellopy +910 Garlet +911 Scell +912 Zargon +913 Tooth of Bat +914 Fluff +915 Chrysalis +916 Feather of Birds +917 Talon +918 Sticky Webfoot +919 Animal Skin +920 Wolf Claw +921 Mushroom Spore +922 Orc's Fang +923 Evil Horn +924 Powder of Butterfly +925 Bill of Birds +926 Snake Scale +928 Insect Feeler +929 Immortal Heart +930 Rotten Bandage +931 Orcish Voucher +932 Skel-Bone +934 Memento +935 Shell +936 Scale Shell +937 Venom Canine +938 Sticky Mucus +939 Bee Sting +940 Grasshopper's Leg +941 Nose Ring +942 Yoyo Tail +943 Solid Shell +944 Horseshoe +945 Raccoon Leaf +946 Snail's Shell +947 Horn +948 Bear's Footskin +949 Feather +950 Heart of Mermaid +951 Fin +952 Cactus Needle +953 Stone Heart +954 Shining Scale +955 Worm Peeling +956 Gill +957 Decayed Nail +958 Horrendous Mouth +959 Stinky Scale +960 Nipper +961 Conch +962 Tentacle +963 Sharp scale +964 Crap Shell +965 Clam Shell +966 Clam Flesh +967 Turtle Shell +968 Heroic Emblem +969 Gold +970 Alchol +971 Detrimindexta +972 Karvodailnirol +973 Counteragent +974 Mixture +975 Scarlet Dyestuffs +976 Lemon Dyestuffs +978 Cobaltblue Dyestuffs +979 Darkgreen Dyestuffs +980 Orange Dyestuffs +981 Violet Dyestuffs +982 White Dyestuffs +983 Black Dyestuffs +984 Oridecon +985 Elunium +986 Anvil +987 Oridecon Anvil +988 Golden Anvil +989 Emperium Anvil +990 Red Blood +991 Crystal Blue +992 Wind of Verdure +993 Green Live +994 Flame Heart +995 Mystic Frozen +996 Rough Wind +997 Great Nature +998 Iron +999 Steel +1000 Star Crumb +1001 Star Dust +1002 Iron Ore +1003 Coal +1004 Chivalry Emblem +1005 Hammer of Blacksmith +1006 Old Magic Book +1007 Necklace of Wisdom +1008 Necklace of Oblivion +1009 Hand of God +1010 Phracon +1011 Emveretarcon +1012 Frill +1013 Rainbow Shell +1014 Ant Jaw +1015 Tongue +1016 Rat Tail +1017 Mole Whiskers +1018 Mole Claw +1019 Trunk +1020 Black Hair +1021 Dokkaebi Horn +1022 Nine Tails +1023 Fish Tail +1024 Squid ink +1025 Cobweb +1026 Acorn +1027 Porcupine Quill +1028 Mane +1029 Tiger Skin +1030 Tiger's Footskin +1031 Mantis Scythe +1032 Maneater Blossom +1033 Maneater Root +1034 Blue Hair +1035 Dragon Canine +1036 Dragon Scale +1037 Dragon Tail +1038 Little Evil Horn +1039 Little Evil Wing +1040 Elder Pixie's Moustache +1041 Lantern +1042 Bug Leg +1043 Orc Claw +1044 Zenorc's Fang +1045 Cultish Masque +1046 Scorpion Nipper +1047 Dead Medusa +1048 Horrendous Hair +1049 Skirt of Virgin +1050 Tendon +1051 Detonator +1052 Single Cell +1053 Ancient Tooth +1054 Ancient Lips +1055 Earthworm Peeling +1056 Grit +1057 Moth Dust +1058 Moth Wings +1059 Fabric +1060 Golden Hair +1061 Witched Starsand +1062 Jack o' Pumpkin +1063 Fang +1064 Reins +1065 Trap +1066 Fine-grained Trunk +1067 Solid Trunk +1068 Barren Trunk +1069 Orange Net Mushroom +1070 Orange Gooey Mushroom +1071 Unknown Testtube +1072 DEL Message +1073 Voucher +1074 Voucher +1075 Voucher +1076 Voucher +1077 Voucher +1078 Voucher +1079 Voucher +1080 Voucher +1081 DEL Box +1082 DEL Box +1083 DEL Box +1084 Kapra Pass +1085 Unknown Testtube +1086 Unknown Testtube +1087 Unknown Testtube +1088 Morocc Solution +1089 Payon Solution +1090 Unknown Testtube +1091 DEL Box +1092 Empty Testtube +1093 Empty Potion Bottle +1094 Short Daenggie +1095 Needle of Alarm +1096 Round Shell +1097 Worn Out Page +1098 Manacles +1099 Worn-out Prison Uniform +1101 Sword +1102 Sword +1103 Sword +1104 Falchion +1105 Falchion +1106 Falchion +1107 Blade +1108 Blade +1109 Blade +1110 Rapier +1111 Rapier +1112 Rapier +1113 Scimiter +1114 Scimiter +1115 Scimiter +1116 Katana +1117 Katana +1118 Katana +1119 Tsurugi +1120 Tsurugi +1121 Tsurugi +1122 Ring Pommel Saber +1123 Haedonggum +1124 Orcish sword +1125 Ring Pommel Saber +1126 Saber +1127 Saber +1128 Haedonggum +1129 Flamberge +1130 Nagan +1131 Ice Falchon +1132 Edge +1133 Fire Brand +1134 Caesar's Sword +1135 Cutlas +1136 Solar Sword +1137 Excalibur +1138 Mysteltainn +1139 Talefing +1140 Byeollungum +1141 Immaterial Sword +1142 Crystal Sword +1143 Gaia Sword +1144 Sashimi +1145 Holy Avenger +1151 Slayer +1152 Slayer +1153 Slayer +1154 Bastard Sword +1155 Bastard Sword +1156 Bastard Sword +1157 Two-handed Sword +1158 Two-handed Sword +1159 Two-handed Sword +1160 Broad Sword +1161 Balmung +1162 Broad Sword +1163 Claymore +1164 Muramasa +1165 Masamune +bonus bStr +bonus bAspd +bonus bDefRate +bonus bDef2Rate + } +1166 Dragon Slayer +1167 Schweizersabel +1168 Zweihander +1169 Executioner +1170 Katzbalger +1201 Knife +1202 Knife +1203 Knife +1204 Cutter +1205 Cutter +1206 Cutter +1207 Main Gauche +1208 Main Gauche +1209 Main Gauche +1210 Dirk +1211 Dirk +1212 Dirk +1213 Dagger +1214 Dagger +1215 Dagger +1216 Stiletto +1217 Stiletto +1218 Stiletto +1219 Gladius +1220 Gladius +1221 Gladius +1222 Damascus +1223 Fortune Sword +1224 Swordbreaker +1225 Mailbreaker +1226 Damascus +1227 Weeder Knife +1228 Combat Knife +1229 Mama's Knife +1230 House Auger +1231 Bazerald +1232 Assasin Dagger +1233 Excorcise +1234 Walgwanggum +1235 Azoth +1236 Sucsamad +1237 Grimtooth +1238 Zeny Knife +1239 Poison Knife +1240 Princess Knife +1241 Cursed Knife +1242 Counter Knife +1243 Novice's Main Gauche +1250 Jur +1251 Jur +1252 Katar +1253 Katar +1254 Jamadhar +1255 Jamadhar +1256 Katar of Cold Icicle +1257 Katar of Dusty Thornbush +1258 Katar of Raging Blaze +1259 Katar of Piercing Wind +1260 Sharppened Legbone of Gh +1261 Infiltrator +1301 Axe +1302 Axe +1303 Axe +1304 Orcish Axe +1305 Cleaver +1306 War Axe +1351 Battle Axe +1352 Battle Axe +1353 Battle Axe +1354 Hammer +1355 Hammer +1356 Hammer +1357 Buster +1358 Buster +1359 Buster +1360 Two-handed Axe +1361 Two-handed Axe +1362 Two-handed Axe +1363 Bloody Axe +1364 Great Axe +1365 Sabbath +1366 Light Epsilon +1367 Slaughter +1368 Tomahawk +1369 Guillotine +1401 Javelin +1402 Javelin +1403 Javelin +1404 Spear +1405 Spear +1406 Spear +1407 Pike +1408 Pike +1409 Pike +1410 Lance +1411 Lance +1412 Lance +1413 Gungnir +1414 Gelerdria +1415 Brocca +1416 Tjungkuletti +1417 Pole Axe +1451 Guisarme +1452 Guisarme +1453 Guisarme +1454 Glaive +1455 Glaive +1456 Glaive +1457 Partizan +1458 Partizan +1459 Partizan +1460 Trident +1461 Trident +1462 Trident +1463 Halberd +1464 Halberd +1465 Halberd +1466 Crescent Scythe +1467 Bill Guisarme +1468 Zephyrus +1469 Longinus's Spear +1470 Brionac +1471 Hellfire +1472 Soul Staff +1473 Wizardy staff +1501 Club +1502 Club +1503 Club +1504 Mace +1505 Mace +1506 Mace +1507 Smasher +1508 Smasher +1509 Smasher +1510 Flail +1511 Flail +1512 Flail +1513 Morning Star +1514 Morning Star +1515 Morning Star +1516 Sword Mace +1517 Sword Mace +1518 Sword Mace +1519 Chain +1520 Chain +1521 Chain +1522 Stunner +1523 Spike +1524 Golden Mace +1525 Long Mace +1526 Slash +1527 Quadrille +1528 Grand Cross +1529 Iron Driver +1530 Mjollnir +1531 Spanner +1550 Book +1551 Bible +1552 Tablet +1553 Book of Billows +1554 Book of Mother Earth +1555 Book of Blazing Sun +1556 Book of Gust of Wind +1557 Book of the Apocalypse +1558 Girl's Diary +1599 Angra Manyu +1601 Rod +1602 Rod +1603 Rod +1604 Wand +1605 Wand +1606 Wand +1607 Staff +1608 Staff +1609 Staff +1610 Arc Wand +1611 Arc Wand +1612 Arc Wand +1613 Mighty Staff +1614 Wand of Occult +1615 Bone Wand +1701 Bow +1702 Bow +1703 Bow +1704 Composite Bow +1705 Composite Bow +1706 Composite Bow +1707 Great Bow +1708 Great Bow +1709 Great Bow +1710 Cross Bow +1711 Cross Bow +1712 Cross Bow +1713 Arbalest +1714 Gakkung +1715 Arbalest +1716 Gakkung +1718 Hunter Bow +1719 Roguemaster's Bow +1720 Rudra's Bow +1721 Repeating Crossbow +1722 Ballista +1750 Arrow +1751 Silver Arrow +1752 Fire Arrow +1753 Steel Arrow +1754 Crystal Arrow +1755 Arrow of Wind +1756 Stone Arrow +1757 Immaterial Arrow +1758 Stun Arrow +1759 Freeze Arrow +1760 Flash Arrow +1761 Curse Arrow +1762 Rusted Arrow +1763 Poison Arrow +1764 Sharp Arrow +1765 Oridecon Arrow +1766 Arrow of Counter Evil +1767 Shadow Arrow +1768 Sleep Arrow +1769 Mute Arrow +1770 Iron Arrow +1801 Waghnakh +1802 Waghnakh +1803 Knuckle Duster +1804 Knuckle Duster +1805 Hora +1806 Hora +1807 Fist +1808 Fist +1809 Claw +1810 Claw +1811 Finger +1812 Finger +1813 Kaiser Knuckle +1814 Berserk +1901 Violin +1902 Violin +1903 Mandolin +1904 Mandolin +1905 Lute +1906 Lute +1907 Guitar +1908 Guitar +1909 Harp +1910 Harp +1911 Guhmoongoh +1912 Guhmoongoh +1950 Rope +1951 Rope +1952 Line +1953 Line +1954 Wire +1955 Wire +1956 Rante +1957 Rante +1958 Tail +1959 Tail +1960 Whip +1961 Whip +1962 Lariat +1963 Rapture Rose +1964 Chemeti +2101 Guard +2102 Guard +2103 Buckler +2104 Buckler +2105 Shield +2106 Shield +2107 Mirror Shield +2108 Mirror Shield +2109 Book of Summoning +2110 Holy Guard +2111 Evangelist +2112 Novice Guard +2199 Ahura Mazda +2201 Sunglasses +2202 Sunglasses +2203 Glasses +2204 Glasses +2205 Diver's Goggles +2206 Wedding Veil +2207 Fancy Flower +2208 Ribbon +2209 Ribbon +2210 Hair Band +2211 Bandana +2212 Eye Patch +2213 Kitty Band +2214 Bunny Band +2215 Flower Band +2216 Biretta +2217 Biretta +2218 Flu Mask +2219 Flu Mask +2220 Hat +2221 Hat +2222 Turban +2223 Turban +2224 Goggle +2225 Goggle +2226 Cap +2227 Cap +2228 Helm +2229 Helm +2230 Gemmed Sallet +2231 Gemmed Sallet +2232 Circlet +2233 Circlet +2234 Tiara +2235 Crown +2236 Santa's Hat +2237 Bandit Beard +2238 Moustache +2239 Single Glass +2240 Beard +2241 Granpa Beard +2242 Purple Glasses +2243 Geek Glasses +2244 Big Ribbon +2245 Sweet Gent +2246 Golden Gear +2247 Romantic Gent +2248 Western Grace +2249 Coronet +2250 Cute Ribbon +2251 Monk Hat +2252 Wizard Hat +2253 Sunflower +2254 Angel Wing +2255 Evil Wing +2256 Majestic Goat +2257 Snow Horn +2258 Spiky Band +2259 Mini Propeller +2260 Mini Glasses +2261 Army Cap +2262 Pierrot Nose +2263 Zoro Masque +2264 Munak Hat +2265 Ganster Mask +2266 Iron Cane +2267 Cigar +2268 Pipe +2269 Romantic Flower +2270 Romantic Leaf +2271 Jack a Dandy +2272 Stop Post +2273 Doctor Band +2274 Ghost Bandana +2275 Red Bandana +2276 Eagle Eyes +2277 Nurse Cap +2278 Mr. Smile +2279 Bomb Wick +2280 Sakkat +2281 Opera Masque +2282 Heaven Ring +2283 Ear Mufs +2284 Antler +2285 Apple o' Archer +2286 Elven Ears +2287 Pirate Bandana +2288 Mr. Scream +2289 Poo Poo Hat +2290 Funeral Hat +2291 Masquerade +2292 Welding Mask +2293 Pretend Murdered +2294 Stellar +2295 Blinker +2296 Binoculars +2297 Goblini Mask +2298 Green Feeler +2299 Orc Helm +2301 Cotton Shirt +2302 Cotton Shirt +2303 Leather Jacket +2304 Leather Jacket +2305 Adventurer's Suit +2306 Adventurer's Suit +2307 Mantle +2308 Mantle +2309 Coat +2310 Coat +2311 Mink Coat +2312 Padded Armor +2313 Padded Armor +2314 Chain Mail +2315 Chain Mail +2316 Full Plate +2317 Full Plate +2318 Lord's Clothes +2319 Glittering Jacket +2320 Formal Suit +2321 Silk Robe +2322 Silk Robe +2323 Scapulare +2324 Scapulare +2325 Saint's Robe +2326 Saint's Robe +2327 Holy Robe +2328 Wooden Mail +2329 Wooden Mail +2330 Tights +2331 Tights +2332 Silver Robe +2333 Silver Robe +2334 Mage Coat +2335 Thief Clothes +2336 Thief Clothes +2337 Ninja Suit +2338 Wedding Dress +2339 Pantie +2340 Novice Breastplate +2341 Full Plate Armor +2342 Full Plate Armor +2343 Casting Robe +2344 Fire Armor +2345 Fire Armor +2346 Water Armor +2347 Water Armor +2348 Wind Armor +2349 Wind Armor +2350 Earth Armor +2351 Earth Armor +2352 Novice Armor +2401 Sandals +2402 Sandals +2403 Shoes +2404 Shoes +2405 Boots +2406 Boots +2407 Crystal Pumps +2408 Ball'n'Chain +2409 Highheals +2410 Sleipnir +2411 Greaves +2412 Greaves +2413 Safety Shoes +2414 Novice Sandal +2501 Hood +2502 Hood +2503 Muffler +2504 Muffler +2505 Manteau +2506 Manteau +2507 Cape of Old Marquess +2508 Ragamuffin Manteau +2509 Manteau of Life +2510 Novice Hood +2601 Ring +2602 Earring +2603 Necklace +2604 Glove +2605 Brooch +2607 Clip +2608 Rosary +2609 Skull Ring +2610 Gold Ring +2611 Silver Ring +2612 Flower Ring +2613 Diamond Ring +2614 Eye of Dullahan +2615 Safety Ring +2616 Critical Ring +2617 Celebrant's Mitten +2618 Matyr's Leash +2619 Bow Thimble +2620 Rogue's Treasure +2621 Ancient Ring +2622 Ancient Earring +2623 Ancient Necklace +2624 Ancient Glove +2625 Ancient Brooch +2626 Ancient Rosary +2627 Ancient Belt +2628 Novice Armlet +2629 Magingiorde +2630 Brysinggamen +2631 Pebble Ring +2634 Wedding Ring +2635 Wedding Ring +2636 Gold Christmas Ring +2637 Silver Christmas Ring +4001 Poring Card +4002 Fabre Card +4003 Pupa Card +4004 Drops Card +4005 Poring Card +4006 Lunatic Card +4007 Pecopeco Egg Card +4008 Picky Card +4009 Chonchon Card +4010 Wilow Card +4011 Picky Card +4012 Thief Bug Egg Card +4013 Andre Egg Card +4014 Roda Frog Card +4015 Condor Card +4016 Thief Bug Card +4017 Savage Babe Card +4018 Andre Larva Card +4019 Hornet Card +4020 Farmiliar Card +4021 Rocker Card +4022 Spore Card +4023 Desert Wolf Babe Card +4024 Plankton Card +4025 Skeleton Card +4026 Thief bug Female Card +4027 Kukre Card +4028 Tarou Card +4029 Wolf Card +4030 Mandragora Card +4031 Pecopeco Card +4032 Ambernite Card +4033 Poporing Card +4034 Worm Tail Card +4035 Hydra Card +4036 Muka Card +4037 Snake Card +4038 Zombie Card +4039 Stainer Card +4040 Creamy Card +4041 Coco Card +4042 Steel Chonchon Card +4043 Andre Card +4044 Smokie Card +4045 Horn Card +4046 Martin Card +4047 Ghostring Card +4048 Poison Spore Card +4049 Vadon Card +4050 Thief bug Male Card +4051 Yoyo Card +4052 Elder Wilow Card +4053 Vitata Card +4054 Angeling Card +4055 Marina Card +4056 Dustiness Card +4057 Metaller Card +4058 Thara Frog Card +4059 Soldier Andre Card +4060 Goblin Card +4061 Cornutus Card +4062 Anacondaq Card +4063 Caramel Card +4064 Zerom Card +4065 Kaho Card +4066 Orc Warrior Card +4067 Megalodon Card +4068 Scorpion Card +4069 Drainliar Card +4070 Eggyra Card +4071 Orc Zombie Card +4072 Golem Card +4073 Pirate Skeleton Card +4074 BigFoot Card +4075 Argos Card +4076 Magnolia Card +4077 Phen Card +4078 Savage Card +4079 Mantis Card +4080 Flora Card +4081 Hode Card +4082 Desert Wolf Card +4083 Rafflesia Card +4084 Marine Sphere Card +4085 Orc Skeleton Card +4086 Soldier Skeleton Card +4087 Giearth Card +4088 Frilldora Card +4089 Swordfish Card +4090 Munak Card +4091 Kobold Card +4092 Skel Worker Card +4093 Obeaune Card +4094 Archer Skeleton Card +4095 Marse Card +4096 Zenorc Card +4097 Matyr Card +4098 Dokebi Card +4099 Pasana Card +4100 Sohee Card +4101 Sandman Card +4102 Whisper Card +4103 Horong Card +4104 Requiem Card +4105 Marc Card +4106 Mummy Card +4107 Verit Card +4108 Myst Card +4109 Jakk Card +4110 Ghoul Card +4111 Strouf Card +4112 Marduk Card +4113 Marionette Card +4114 Argiope Card +4115 Hunter Fly Card +4116 Isis Card +4117 Sidewinder Card +4118 Petit Card +4119 Bathory Card +4120 Petit Card +4121 Phreeoni Card +4122 Deviruchi Card +4123 Eddga Card +4124 Medusa Card +4125 Deviace Card +4126 Minorous Card +4127 Nightmare Card +4128 Golden Bug Card +4129 Baphomet Card +4130 Scorpion King Card +4131 Moonlight Flower Card +4132 Mistress Card +4133 Raydric Card +4134 Dracula Card +4135 Orc Lord Card +4136 Khalitzburg Card +4137 Drake Card +4138 Anubis Card +4139 Joker Card +4140 Knight Of Abyss Card +4141 Evil Druid Card +4142 Doppelganger Card +4143 Orc Hero Card +4144 Osiris Card +4145 Berzebub Card +4146 Maya Card +4147 Baphomet Card +4148 Pharaoh Card +4149 Bon Gun Card +4150 Orc Archer Card +4151 Mimic Card +4152 Wraith Card +4153 Alarm Card +4154 Arclouse Card +4155 Rideword Card +4156 Skel Prisoner Card +4157 Zombie Prisoner Card +4158 Dark Priest Card +4159 Punk Card +4160 Zherlthsh Card +4161 Mysteltainn Card +4162 Tirfing Card +4163 Executioner Card +4164 Anolian Card +4165 Sting Card +4166 Wander Man Card +4167 Cramp Card +4168 Filamentous Card +4169 Brilight Card +4170 Iron Fist Card +4171 High Orc Card +4172 Choco Card +4173 Stem Worm Card +4174 Penomena Card +4175 Marin Card +4176 Sasquatch Card +4177 Antonio Card +4178 Cruiser Card +4179 Mystcase Card +4180 Chepet Card +4181 Knight Of Windstorm Card +4182 Garm Card +4183 Gargoyle Card +4184 Raggler Card +4185 Neraid Card +4186 Pest Card +4187 Injustice Card +4188 Goblin Archer Card +4189 Gryphon Card +4190 Dark Frame Card +4191 Wind Ghost Card +4192 Merman Card +4193 Cookie Card +4194 Aster Card +4195 Carat Card +4196 Bloody Knight Card +4197 Clock Card +4198 C Tower Manager Card +4199 Alligator Card +4200 Dark Lord Card +4201 Orc Lady Card +4202 Megalith Card +4203 Alice Card +4204 Raydric Archer Card +4205 Greatest General Card +4206 Stalactic Golem Card +4207 Tri Joint Card +4208 Steam Goblin Card +4209 Sage Worm Card +4210 Kobold archer Card +4211 Chimera Card +5001 Headset +5002 Jewel Crown +5003 Joker Jester +5004 Oxygen Mask +5005 Gas Mask +5006 Machoman's Glasses +5007 Grand Circlet +5008 Puppy Love +5009 Safety Helmet +5010 Indian Fillet +5011 Aerial +5012 Ph.D Hat +5013 Lord Kaho's Horn +5014 Fin Helm +5015 Egg Shell +5016 Boys Cap +5017 Bone Helm +5018 Feather Bonnet +5019 Corsair +5020 Kafra Band +5021 Money Loser's Grief +5022 Solar God Helm +5023 Parcel Hat +5024 Cake Hat +5025 Angel Helm +5026 Chef's Hat +5027 Magic Instructor's Hat +5028 Candle +5029 Spore Hat +5030 Panda Cap +5031 Miner's Helmet +5032 Sunday Hat +5033 Smokie Hat +5034 Lightbulb Hairband +5035 Poring hat +5036 Cross Hairband +5037 Apple Hat +5038 Deviruchi Hat +5039 Spotted Eggshell +5040 Innocence of Maiden +5041 Heart Hairpin +5042 Dumpling Decoration +5043 Opera Ghost Mask +5044 Wing's of Demon +5045 Magic Hat +5046 Bongun hat +5047 Fashion Sunglasses +5048 Cresent Hairpin +5049 Striped Bandana +5050 Mysterious Apple Hat +5051 Bell of Pussycat +5052 Blue Bandana +5053 Sphinx Hat +5054 Assassin Mask +5055 Novice Eggshell +5056 ??? +7001 Mould Powder +7002 Ogre Tooth +7003 Anolian Skin +7004 Mud Lump +7005 Skull +7006 Wing of Red Bat +7007 Claw of Rat +7008 Stiff Horn +7009 Glitter Shell +7010 Tail of Steel Scorpion +7011 Claw of Monkey +7012 Tough Scalelike Stem +7013 Coral Reef +7014 Old Portrait +7015 Bookclip in Memory +7016 Spoon Stub +7017 Executioner's Mitten +7018 Young Twig +7019 Loki's Whispers +7020 Mother's Nightmare +7021 Foolishness of the Blind +7022 Old Hilt +7023 Blade Lost in Darkness +7024 Bloody Edge +7025 Lucifer's Lament +7026 Key of Clock Tower +7027 Key of Underground +7028 Invite for Duel +7029 Admission for Duel +7030 Claw of Desert Wolf +7031 Old Frying Fan +7032 Piece of Egg Shell +7033 Poison Spore +7034 Red Socks with Holes +7035 Matchstick +7036 Fang of Garm +7037 Coupon +7038 Yarn +7039 Novice Nametag +7040 Megaphone +7041 Fine Grit +7042 Leather Bag of Infinity +7043 Fine Sand +7044 Vigorgra +7045 Magic Paint +7046 Cart Parts +7047 Alice's Apron +7048 Talon of Griffon +7049 Stone +7050 Cotton Mat +7051 Silk Mat +7052 Wasted Magazine +7053 Cyfar +7054 Brigan +7055 Animal Poop +7056 Payment Statement for Ka +7057 Gallar Horn +7058 Gullraifnir +7059 Free Ticket for Kafra St +7060 Free Ticket for Kafra Tr +7061 Free Ticket for the Cart +7062 Broken Turtle Shell +7063 Soft Feather +7064 Wing of Dragonfly +7065 Sea Otter Fur +7066 Ice Cubes +7067 Piece of Rock +7068 Half Burnt Log +7069 Broken Armor Piece +7070 Broken Shell +7071 Tattered Clothes +7072 Old Shuriken +7073 Freyja's Jewel +7074 Thor's Guntlet +7075 Iron Maiden +7076 Wheel of the Unknown +7077 Silver Ornament +7078 Wrath of Valkyrie +7079 Feather of Angel Wing +7080 Footprints of Cat +7081 Woman's Moustache +7082 Root of Stone +7083 Spirit of Fish +7084 Saliva of Bird +7085 Tendon of Bear +7086 Solar Bead +7087 Breath of Soul +7088 Snow Crystal +7089 Omen of Tempest +7090 Ripple +7091 Billow +7092 Drifting Air +7093 Metal Wheel +7094 Cabinet Chip +7095 Tooth Fragment +7096 Hardened Lava +7097 Burning Heart +7098 Fire Seed +7099 Old Magical Circle +7100 Sharpened Leaf +7101 Peco's Feather +7102 Nightmare +7103 Yellwo Liquid Bottle +7104 Imitation Angel's Wing +7105 Imitation Soul's Band +7106 Horn of Goat +7107 Fur of Goat +7108 Broken Shield +7109 Shiny Spear Tip +7110 Sharp Sword +7111 String Paper +7112 Transparent Paper +7113 Onion Wand +7114 Sphinx Mask +7115 Blood Feather +7116 Tooth of Lowblood +7117 Torn Spell Book +7118 Torn Scroll +7119 Hypha Body +7120 Burning Horseshoe +7121 Honey Jar +7122 Hot Feather +7123 Dragon's Skin +7124 Sand Lump +7125 Crab Shot +7126 Large Jellopy +7127 Alcohol Making Book +7128 Fire Bottle Making Book +7129 Acid Bottle Making Book +7130 Plant Bottle Making Book +7131 Mine Bottle Making Book +7132 Coating Wax Making Book +7133 Slim Potion Making Book +7134 Medicine Bowl +7135 Fire Bottle +7136 Hydrobolic Acid Bottle +7137 Water Bottle +7138 Mine Bottle +7139 Coating Wax +7140 Seed of Life +7141 Water Flow +7142 Ancient Life +7143 Seperation Tubes +7144 Potion Making Book +7145 Ragnarok T-Shirt +7146 Vacation Ticket +7147 Jasmine +7148 Mother's Letter +7149 Yellow Plate +7150 Bamboo Trunk +7151 Oiled Paper +7152 Glossy Hair +7153 Old Kimono +7154 Poison Powder +7155 Poisonous Toad's Skin +7156 Broken Shuriken +7157 Black Mask +7158 Broken Liquor Bottle +7159 Demon's Nose +7160 Passport From King +7161 Skin of the Black Bear +7162 Piece of Cloud +7163 Hard Antennae +7164 Very Hard Peach +7165 Etherial Winged Clothing +7166 Soft Silk Fabric +7167 Strange Piece of Iron +7168 Big Wing of Butterfly +7169 Tae Guk Tablet +7170 Tuxedo +7171 Skin of Panther +7172 Claw of Panther +7173 Bun Buster Bag +7174 Wrapping Thread +7175 Wrapper +7176 King's Proof Document +7177 ?????_?????_???? +7178 ?????_????? +7179 ????_??????? +7180 0 +7181 0 +7182 Cacao +7183 ???? +7184 ?????? +7185 ?????? +7186 ??? +7187 ????? +7188 ???? +7189 ???? +7190 ???? +7191 ? +7192 ???? +7193 ????? +7194 ???????? +7195 ???? +7196 ???? +7197 ???????? +7198 ?????? +7199 20 +7200 ??? +7201 ?? +7202 ???????? +7203 ????? +7204 ?? +9001 Poring Egg +9002 Drops Egg +9003 Poporing Egg +9004 Lunatic Egg +9005 Picky Egg +9006 Chonchon Egg +9007 Steel Chonchon Egg +9008 Hunter Fly Egg +9009 Savage Babe Egg +9010 Baby Desert Wolf Egg +9011 Rocker Egg +9012 Spore Egg +9013 Poison Spore Egg +9014 PecoPeco Egg +9015 Smokie Egg +9016 Yoyo Egg +9017 Orc Warrior Egg +9018 Munak Egg +9019 Dokkaebi Egg +9020 Sohee Egg +9021 Isis Egg +9022 Green Petite Egg +9023 Deviruchi Egg +9024 Bapho Jr. Egg +9025 Bongun Egg +9026 Alice Egg +9027 Zherlthsh Egg +9028 Test Egg +9029 Test Egg +10001 Skull Helm +10002 Monster Oxygen Mask +10003 Transparent Headgear +10004 Pacifier +10005 Wig +10006 Queen's Hair Ornament +10007 Silk Ribbon +10008 Punisher +10009 Wild Flower +10010 Battered Pot +10011 Stellar Hairpin +10012 Tiny Egg Shell +10013 Backpack +10014 Rocker Glasses +10015 Green Lace +10016 Golden Bell +10017 Bark Shorts +10018 Monkey Circlet +10019 Red Muffler +10020 Sword of Chinese Exorcis diff --git a/doc/item_bonus.txt b/doc/item_bonus.txt new file mode 100644 index 0000000..30202ee --- /dev/null +++ b/doc/item_bonus.txt @@ -0,0 +1,137 @@ +サソskill n,x; skill n of level x + +bonus bStr,n; STR + n +bonus bAgi,n; AGI + n +bonus bVit,n; VIT + n +bonus bInt,n; INT + n +bonus bDex,n; DEX + n +bonus bLuk,n; LUK + n + +bonus bMaxHP,n; MAXHP + n +bonus bMaxSP,n; MAXSP + n +bonus bMaxHPrate,n; MAXHP + n% +bonus bMaxSPrate,n; MAXSP + n% +bonus bAtk,n; ATK + n +bonus bAtk2,n; ATK2 + n +bonus bAtkRate attack power + n% +bonus bBaseAtk,n; Basic attack power + n +bonus bMatk,n; Magical attack power 1 + n and magical attack power 2 + n +bonus bMatk1,n; Magical attack power 1 + n +bonus bMatk2,n; Magical attack power 2 + n +bonus bMatkRate,n; Magical attack power + n% +bonus bMdef,n; Magical defensive power + n +bonus bDef,n; DEF + n +bonus bHit,n; On-target hit power + n +bonus bCritical,n; Critical + n +bonus bCriticalRate,n; Critical ratio + n% +bonus bFlee,n; Evasion power + n +bonus bFlee2,n; Perfection evasion + n +bonus bSpeed,n; Drift speed + n +bonus bAspd,n; Attack speed + n +bonus bSpeedRate,n; Drift speed + n% (just high ones application) +bonus bAspdRate,n; Attack speed + n% (just high ones application) +bonus bSpeedAddRate drift speed + n% +bonus bAspdAddRate attack speed + n% +bonus bAtkRange,n; Attack Range + n +bonus bCastrate,n; Cast rate + n% +bonus bUseSPrate,n; SP consumption + n% +bonus bHPrecovRate,n; HP automatic recovery ratio + n% (you exclude the recovery with skill) +bonus bSPrecovRate,n; SP automatic recovery ratio + n% (you exclude the recovery with skill) +bonus bDoubleRate,n; attack probability n% (with weapon disregard just high ones application) +bonus bDoubleAddRate,n; Double attack probability + n% (weapon disregard) +bonus bPerfectHitRate,n; On-target impact attack probability n% (just high ones application) +bonus bPerfectHitAddRate,n; On-target impact attack probability +n% +bonus bGetZenyNum,n; When pushing down the monster with physical attack, rand () 繧シ繝 of %n+1 is obtained, (as for n just high ones application) +bonus bAddGetZenyNum,n; When pushing down the monster with physical attack, rand () 繧シ繝 of %n+1 is obtained, (n is done +) +bonus bCriticalDef,n; Critical 蝟ー and others the trap it is, probability + n% +bonus bNearAtkDef,n; The damage of short-range attack n% reduction (magic and the trap, the 鮃ケ is excluded) +bonus bLongAtkDef,n; damage of stand off attack n% reduction (magic and the trap, the 鮃ケ is excluded) +bonus bMagicAtkDef the damage of magical attack n% reduction +bonus bMiscAtkDef MISC attack (the trap and ?) the damage n% reduction + +bonus bIgnoreDefRace,n Defense disregard of enemy of n race + 0,Intangibility 1,Immortality 2,Animal 3,Plant 4,Insect 5,Fish and shellfish 6,Demon 7,Human 8,Angel 9,Dragon family 10:Boss monster 11:Other than (normal monster) boss monster +bonus bIgnoreDefEle,n; Defense disregard of enemy of n attribute + 0,Nothing 1,Water 2,Earth 3,Fire 4,Wind 5,Poison 6,Saint 7,Darkness 8,Sense 9,Immortality +bonus bIgnoreMDefRace n race the magical defensive power disregard damage + 0,Intangibility 1,Immortality 2,Animal 3,Plant 4,Insect 5,Fish and shellfish 6,Demon 7,Human 8,Angel 9,Dragon family 10:Boss monster 11:Other than boss monster (normal monster) +bonus bIgnoreMDefEle n attribute the magical defensive power disregard damage + 0,Nothing 1,Water 2,Earth 3,Fire 4,Wind 5,Poison 6,Saint 7,Darkness 8,Sense 9,Immortality +bonus bDefRatioAtkRace,n; n race if defensive power is high the high extent big damage is given, (defense disregard) + 0,Intangibility 1,Immortality 2,Animal 3,Plant 4,Insect 5,Fish and shellfish 6,Demon 7,Human 8,Angel 9,Dragon family 10:Boss monster 11:Other than boss monster (normal monster) +bonus bDefRatioAtkEle,n; n attribute if defensive power is high the high extent big damage is given, (defense disregard) + 0,Nothing 1,Water 2,Earth 3,Fire 4,Wind 5,Poison 6,Saint 7,Darkness 8,Sense 9,Immortality +bonus bAtkEle,n; Attack with element n + 0,Nothing 1,Water 2,Earth 3,Fire 4,Wind 5,Poison 6,Saint 7,Darkness 8,Sense 9,Immortality +bonus bDefEle,n; Guard against element n + 0,Nothing 1,Water 2,Earth 3,Fire 4,Wind 5,Poison 6,Saint 7,Darkness 8,Sense 9,Immortality +bonus bHitRate,n; on-target hit ratio +n% +bonus bFleeRate,n; evasion ratio +n% +bonus bFlee2Rate,n; complete evasion ratio +n% +bonus bDefRate,n; earned-run average +n% (equipment) +bonus bDef2Rate,n; earned-run average +n% (those due to vit) +bonus bMdefRate,n; magical earned-run average +n% (equipment) +bonus bMdef2Rate,n; magical earned-run average +n% (those due to int) +bonus bSplashRange n; damage is given to the peripheral n cell of the target with usual weapon attack, if (as for n just high ones application, 1 if the 3*3, 2 the 5*5) +bonus bSplashAddRange n; damage is given to the peripheral n cell of the target with usual weapon attack, (range + n) + +bonus bInfiniteEndure,n; Unlimited Endure (n is meaningless) +bonus bRestartFullRecover,n; When reviving, HP and SP all recoveries (non mind there is no n) +bonus bNoCastCancel,n; The casting is not cancelled (non mind there is no n) +bonus bNoCastCancel2,n; The casting is not cancelled (is not cancelled even with GVG, n is meaningless) +bonus bNoSizeFix,n; The attack revision with the size of the monster is not received, (non mind there is no n) +bonus bNoWeaponDamage,n; The damage is not received to physical attack, (non mind there is no n) +bonus bNoMagicDamage,n; The damage is not received to magic, (including also the heel, non mind there is no n) +bonus bNoGemStone,n; When using skill, the gemstone is not consumed (non mind there is no n) + + +bonus2 bAddEff,Eff_Blind,n; With the establishment of n% dark grant +bonus2 bAddEff,Eff_Sleep,n; With the establishment of n% sleep grant +bonus2 bAddEff,Eff_Poison,n; With the establishment of n% poison grant +bonus2 bAddEff,Eff_Freeze,n; With the establishment of n% freezing grant +bonus2 bAddEff,Eff_Silence,n; With the establishment of n% silence grant +bonus2 bAddEff,Eff_Stan,n; With the establishment of n% stun grant +bonus2 bAddEff,Eff_Curse,n; You curse with the establishment of n%, grant +bonus2 bAddEff,Eff_Confusion,n; With the establishment of n% confusion grant +bonus2 bAddEff,Eff_Stone,n; With the establishment of n% petrochemical grant + +bonus2 bResEff,Eff_Blind,n; Dark tolerance + n% +bonus2 bResEff,Eff_Sleep,n; Sleep tolerance + n% +bonus2 bResEff,Eff_Poison,n; Poison tolerance + n% +bonus2 bResEff,Eff_Freeze,n; Freezing tolerance + n% +bonus2 bResEff,Eff_Silence,n; Silence tolerance + n% +bonus2 bResEff,Eff_Stan,n; Stun tolerance + n% +bonus2 bResEff,Eff_Curse,n; Cursing tolerance + n% +bonus2 bResEff,Eff_Confusion,n; Confusion tolerance + n% +bonus2 bResEff,Eff_Stone,n; Petrochemical tolerance + n% + +bonus2 bAddSize,n,x; In n size the damage addition of x% + 0,Small size 1,Medium size 2,Large size +bonus2 bAddRace,n,x; In n race the damage addition of x% + 0,Intangibility 1,Immortality 2,Animal 3,Plant 4,Insect 5,Fish and shellfish 6,Demon 7,Human 8,Angel 9,Dragon family 10:Boss monster 11:Other than boss monster (normal monster) +bonus2 bSubRace,n,x; Damage x% reduction from n race + 0,Intangibility 1,Immortality 2,Animal 3,Plant 4,Insect 5,Fish and shellfish 6,Demon 7,Human 8,Angel 9,Dragon family 10:Boss monster 11:Other than boss monster (normal monster) +bonus2 bMagicAddRace,n,x; In n race the damage addition of x% (only magical attack) + 0,Intangibility 1,Immortality 2,Animal 3,Plant 4,Insect 5,Fish and shellfish 6,Demon 7,Human 8,Angel 9,Dragon family 10:Boss monster 11:Other than boss monster (normal monster) +bonus2 bMagicSubRace,n,x; Magical damage x% reduction from n race + 0,Intangibility 1,Immortality 2,Animal 3,Plant 4,Insect 5,Fish and shellfish 6,Demon 7,Human 8,Angel 9,Dragon family 10:Boss monster 11:Other than boss monster (normal monster) +bonus2 bAddEle,n,x; In n attribute the damage addition of x% + 0,Nothing 1,Water 2,Earth 3,Fire 4,Wind 5,Poison 6,Saint 7,Darkness 8,Sense 9,Immortality +bonus2 bMagicAddEle,n,x; In n attribute the damage addition of x% (only magical attack) + 0,Nothing 1,Water 2,Earth 3,Fire 4,Wind 5,Poison 6,Saint 7,Darkness 8,Sense 9,Immortality +bonus2 bSubEle,n,x; Damage x% reduction from n attribute + 0,Nothing 1,Water 2,Earth 3,Fire 4,Wind 5,Poison 6,Saint 7,Darkness 8,Sense 9,Immortality +bonus2 bAddDamageClass,n,x; In monster of class n the damage addition of x% (only physical attack), in case of prayer in n occupation the damage addition of x% +bonus2 bAddMagicDamageClass,n,x; In monster of class n in case of the magical damage addition and prayer of x% in n occupation the magical damage addition of x% +bonus2 bAddDefClass,n,x; In monster of class n the damage reduction of x% (only physical attack), in case of prayer in n occupation the damage reduction of x% +bonus2 bAddMDefClass,n,x; In monster of class n in case of the magical damage reduction and prayer of x% in n occupation the magical damage reduction of x% +bonus2 bHPDrainRate,n,x; it obtained to the enemy -- ? ME ? JI -- n % probability -- x % -- HP -- absorption (+ n and x are carried out) +bonus2 bSPDrainRate,n,x; it obtained to the enemy -- ? ME ? JI -- n % probability -- x % -- SP -- absorption (+ n and x are carried out) + + +bonus3 bAddMonsterDropItem,n,x; When pushing down the monster with physical attack, the probability which drops item n +x% (the item which the monster drops unrelated ones) + 0,Intangibility 1,Immortality 2,Animal 3,Plant 4,Insect 5,Fish and shellfish 6,Demon 7,Human 8,Angel 9,Dragon family 10:Boss monster 11:Other than boss monster (normal monster) +bonus3 bAutoSpell,n,x,y; Auto Spell casting of spell n at level x with y% chance + +// bAddDamageClass, bAddMagicDamageClass and bAddMonsterDropItem it is setting possible up to 10. Those which exceed 10 are ignored. +// those which can be used with the arrow are only bCritical, bAtkEle, bHit, bAddEle, bAddRace, bAddSize and bAddEff. The other things are ignored. diff --git a/doc/packet_table_en.txt b/doc/packet_table_en.txt new file mode 100644 index 0000000..5a44a66 --- /dev/null +++ b/doc/packet_table_en.txt @@ -0,0 +1,1324 @@ +here is a translation for "packet_table.txt". +i leave original japanese sentenses and write translation below that. + + +詳しくは知りませんが、GMはアカウントID=704554付近を指定すると +クライアントがGMだと認識して表示するみたいです。 +数字が半端なのは気にしないで・・・ + +i don't know for sure, but if you set account id around 704554, +the ro client recognizes you as GM ( i don't know about other client like +iro or something. this is talking about jro.) + + +パケット長リスト。-1はパケット種別の直後に長さがあるパケット + +list of packet length. "-1" means a packet that have its packet length +just after the packet number. + + 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 55, 17, 3, 37, 46, -1, 23, -1, 3,108, 3, 2, + 3, 28, 19, 11, 3, -1, 9, 5, 52, 51, 56, 58, 41, 2, 6, 6, + + 7, 3, 2, 2, 2, 5, 16, 12, 10, 7, 29, 23, -1, -1, -1, 0, + 7, 22, 28, 2, 6, 30, -1, -1, 3, -1, -1, 5, 9, 17, 17, 6, + 23, 6, 6, -1, -1, -1, -1, 8, 7, 6, 7, 4, 7, 0, -1, 6, + 8, 8, 3, 3, -1, 6, 6, -1, 7, 6, 2, 5, 6, 44, 5, 3, + + 7, 2, 6, 8, 6, 7, -1, -1, -1, -1, 3, 3, 6, 3, 2, 27, + 3, 4, 4, 2, -1, -1, 3, -1, 6, 14, 3, -1, 28, 29, -1, -1, + 30, 30, 26, 2, 6, 26, 3, 3, 8, 19, 5, 2, 3, 2, 2, 2, + 3, 2, 6, 8, 21, 8, 8, 2, 2, 26, 3, -1, 6, 27, 30, 10, + + + 2, 6, 6, 30, 79, 31, 10, 10, -1, -1, 4, 6, 6, 2, 11, -1, + 10, 39, 4, 10, 31, 35, 10, 18, 2, 13, 15, 20, 68, 2, 3, 16, + 6, 14, -1, -1, 21, 8, 8, 8, 8, 8, 2, 2, 3, 4, 2, -1, + 6, 86, 6, -1, -1, 7, -1, 6, 3, 16, 4, 4, 4, 6, 24, 26, + + 22, 14, 6, 10, 23, 19, 6, 39, 6, 7, 6, 27, -1, 2, 6, 6, + 110, 6, -1, -1, -1, -1, -1, 6, -1, 54, 66, 54, 90, 42, 6, 42, + -1, -1, -1, -1, -1, 30, -1, 3, 14, 3, 30, 10, 43, 14,186,182, + 14, 30, 10, 3, -1, 6,106, -1, 4, 5, 4, -1, 6, 7, -1, -1, + + 6, 3,106, 10, 10, 34, 0, 6, 8, 4, 4, 4, 29, -1, 10, 6, + 90, 86, 24, 6, 30,102, 8, 4, 8, 4, 14, 10 + +S クライアントから見て送信 +S means a packet that will be sent from client. + +R クライアントから見て受信 +R means a packet that will be received by client. + +B バイト +B means a byte. + +w ワード=2B +w means word( 2 bytes) + +l ロングワード=4B +l means long word(4bytes) + +* 0個以上くりかえし +* means repeat + + +S 0064 <version>.l <account name>.24B <password>.24B <version2>.B + アカウントID&パスワード送信 + send account id & password +S 0065 <account ID>.l <login ID1>.l <login ID2>.l ?.2B <sex>.B + キャラセレ鯖接続要求 + request connection to character select server +S 0066 <charactor number>.B + キャラクタ選択要求 + request to select character +S 0067 <charactor name>.24B <param etc>.11B + キャラクタ作成要求 + request to create new character +S 0068 <charactor ID>.l <mail address>.40B + キャラクタ削除要求 + request to delete character +R 0069 <len>.w <login ID1>.l <account ID>.l <login ID2>.l ?.32B <sex>.B {<IP>.l <port>.w <server name>.20B <login users>.l ?.2B}.32B* + login成功&鯖情報 + information about a success of login to login server +R 006a <error No>.B + login失敗 + fail to login to login server +R 006b <len>.w <charactor select data>.106B* + キャラセレ鯖接続成功&キャラクタデータ + information about a success of connection to character select server & character server + <charactor select data> = <charactor ID>.l <base exp>.l <zeny>.l <job exp>.l <job level>.l ?.8B <option>.l <karma>.l <manner>.l ?.2B <HP>.w <MaxHP>.w <SP>.w <MaxSP>.w <speed>.w <class>.w <hair>.w <weapon>.2w <base level>.w <skill point>.w <head_bottom>.w <sheild>.w <head option top>.w <head option mid>.w <hair color>.w ?.w <name>.24B <STR>.B <AGI>.B <VIT>.B <INT>.B <DEX>.B <LUK>.B <charactor number>.B ?.B +R 006c <error No>.B + キャラクタ選択失敗 + fail to select character +R 006d <charactor select data>.106B + キャラクタ作成成功 + success to create new character +R 006e <error No>.B + キャラクタ作成失敗 + fail to create new character +R 006f + キャラクタ削除成功 + success to delete character +R 0070 <error No>.B + キャラクタ削除失敗 + fail to delete character +R 0071 <charactor ID>.l <map name>.16B <ip>.l <port>.w + キャラクタ選択成功&マップ名&ゲーム鯖IP/port + success to select character & map name and ip/port number for game server +S 0072 <account ID>.l <charactor ID>.l <login ID1>.l <login ID2>.l <sex>.b + ゲーム鯖接続要求 + request connection to game server +R 0073 <server tick>.l <coordidate>.3B ?.2B + ゲーム鯖接続成功&サーバ側1ms時計&出現位置 + success to connect to game server & server time & spawn point +R 0078 <ID>.l <speed>.w ?.w ?.w <option>.w <class>.w <hair>.w <weapon>.w <head option bottom>.w <sheild>.w <head option top>.w <head option mid>.w <hair color>.w ?.w <head dir>.w <guild>.w ?.w ?.w <manner>.w <karma>.w ?.B <sex>.B <X_Y_dir>.3B ?.B ?.B <sit>.B + マップロード時&移動時用、向き付き用キャラ情報? + a packet for map load or moving, infermation about a direction for character? +R 0079 <ID>.l <speed>.w ?.w ?.w <option>.w <class>.w <hair>.w <weapon>.w <head option bottom>.w <sheild>.w <head option top>.w <head option mid>.w <hair color>.w ?.w <head dir>.w <guild>.w ?.w ?.w <manner>.w <karma>.w ?.B <sex>.B <X_Y_dir>.3B ?.B ?.B + テレポ等の表示範囲内沸きキャラ用、向き付き無しキャラ情報? + information about characters in a range of a skill like teleport, no infor about direction for character? +R 007b <ID>.l <speed>.w ?.w ?.w <option>.w <class>.w <hair>.w <weapon>.w <head option bottom>.w <server tick>.l <sheild>.w <head option top>.w <head option mid>.w <hair color>.w ?.w <head dir>.w <guild>.w ?.w ?.w <manner>.w <karma>.w ?.B <sex>.B <X_Y_X_Y>.5B ?.B ?.B ?.B + 表示範囲内キャラ移動情報 + character information about walking in a range of the character can see +R 007c <ID>.l <speed>.w ?.6w <class>.w ?.7w <X_Y>.3B ?.2B + NPC用表示範囲内キャラ情報 + character information for npc in a range the character can see +S 007d + mapロード終り + end of load +S 007e <client tick>.l + クライアント側1msタイマ送信 + send 1ms timer at client +R 007f <server tick>.l + サーバ側1msタイマ送信 + send 1ms timer at server +R 0080 <ID>.l <type>.B + type=00 キャラ消滅 (画面外移動。死体消滅等?) + character disappear( walk out of screen. died and disappear?) + type=01 キャラ死亡 + character died + type=02 キャラ消滅 (テレポ,蝿,蝶,logout等?) + character disappear( teleport, fly's wing, butterfly's wing, logout?) +R 0081 <type>.B + type=03 speed hack + speed hack + type=08 2重ログイン + duplicated login +S 0085 <X_Y>.3B + 移動要求 + request to walk +R 0087 <server tick>.l <X_Y_X_Y>.5B ?.B + 移動応答 + response to the request to walk +R 0088 <ID>.l <X>.w <Y>.w + 移動途中停止 + stop walking +S 0089 <target ID>.l <type>.B + type=00 targetを1回殴る + hit target once + type=02 座る + sit down + type=03 立ち上る + stand up + type=07 targetを殴り続ける + continue to hit target +R 008a <src ID>.l <dst ID>.l <server tick>.l <src speed>.l <dst speed>.l <param1>.w <param2>.w <type>.B <param3>.w + type=00 param1=0 miss + param1=0 miss + type=00 param1:ダメージ(の合計?) param2:分割数 param3:アサシン2刀流逆手ダメージ + param1:damage(of total?) param2:number of division param3:damage of assasin's left hand + NPCからの攻撃の場合、param2,param3はゴミデータ + if the attack was by npc, param2 and param3 are not used + speedはPCの場合内部ASPDと一致 + speed match the aspd if it's player character + type=01 itemを拾う ID*2以外ゴミ + pick up item, unused data except ID*2 + type=02 座る src ID以外ゴミ + sit down, unused data except src ID + type=03 立つ src ID以外ゴミ + stand up, unused data except src ID + type=08 複数攻撃 + multiple attack + type=0a クリティカル + critical attack + type=0b 完全回避 + perfect evade +R 008c <len>.w <str>.?B + 通常発言送信。チャット中はチャット内発言用になる + send normal speech. it become a speech for chat during a chat + 先頭の"<nick> : "の部分はクライアント側で付ける事 + client adds "<nick> : " part. +R 008d <len>.w <ID>.l <str>.?B + IDさんの発言受信。チャット中はチャット内発言用になる + receive a speech by ID. it become speech for chat during a chat +R 008e <len>.w <str>.?B + 自分の発言受信。チャット中はチャット内発言用になる + receive my character's speech. it become speech for chat during a chat +S 0090 <ID>.l <type?>.B + NPCに話しかける。typeは01しか見た事無し + talk to npc. i havent seen type setted to 01. +R 0091 <map name>.16B <X>.w <Y>.w + 鯖内マップ間移動、テレポ,蝿等用 + map change in the same server, for instance, teleport, fly's wing... +R 0092 <map name>.16B <X>.w <Y>.w <IP>.l <port>.w + 鯖間移動 + map change to the other server +R 0093 + 8月中に1回だけ観測。謎 + this packet observed in august once. i dont know what it is. +S 0094 <ID>.l + IDのキャラ名等要求。0095か0195の返答があるはず + request a character name for ID. 0095 or 0195 response is expected. +R 0095 <ID>.l <nick>.24B + NPC,ギルド未所属PCの0094への返答 + response for 0094 request from npc or player character without guild. + 0193 <charID>.l で問い合わせて + request by <charID>.l + 0194 <charID>.l <name>.24B の応答で得てます。 + get response by <charID>.l <name>.24B + +S 0096 <len>.w <nick>.24B <message>.?B + wis送信 + send wisper +R 0097 <len>.w <nick>.24B <message>.?B + wis受信 + receive wisper +R 0098 <type>.B + type=00 wis送信成功 + success to send wisper + type=01 wis相手がloginしてない? + target character is not loged in? + type=02 wis相手からignoreされてる? + ignored by target +R 009a <len>.w <message>.?B + GMからの天の声 + GM announce +S 009b <head dir>.w <dir>.B + 体&頭の方向変更要求。クライアントへの応答は無い模様 + request a change of head and body direction. no response to client. + dirは00〜07で体の向き。00で北から反時計回りに45°単位で07まで + dir can be 00-07 and it's for body direction. 00 means north and go counter-clockwise upto 07 by 45 degrees. + head dirは00,01,02で頭の向き。00で体と同じ、01が右、02が左 + head dir can be 00,01,02. 00 means the same direction of body, 01 means right, 02 menas left. +R 009c <ID>.l <head dir>.w <dir>.B + IDの体&頭の方向変更 + change body and head direction for ID. +R 009d <ID>.l <item ID>.w <identify flag>.B <X>.w <Y>.w <amount>.w <subX>.B <subY>.B + 移動等で床アイテムが画面内に入ってきた時 + when the item on the floor will appear on the screen by walking etc. +R 009e <ID>.l <item ID>.w <identify flag>.B <X>.w <Y>.w <subX>.B <subY>.B <amount>.w + item drop。何故か009dとマス目内位置&個数が入れ変っている + item drop. coordinate and amount is different from 009d. +S 009f <ID>.l + IDの床アイテムを拾う + pick up item on the floor. +R 00a0 <index>.w <amount>.w <item ID>.w <identify flag>.B <attribute?>.B <refine>.B <card>.4w <equip type>.w <type>.B <fail>.B + fail=02 取得失敗? + fail to pick up? + fail=06 ルート権無し。取得失敗 + no right to pick up. fail to pick up. +R 00a1 <ID>.l + IDの床アイテム消去 + disappear the floor item +S 00a2 <index>.w <amount>.w + 所有アイテムを落す + drop inventory item. +R 00a3 <len>.w {<index>.w <item ID>.w <type>.B <identify flag>.B <amount>.w ?.2B}.10B* + 所有消耗品&収集品リスト + list of consumptive item and collecter item that you have +R 00a4 <len>.w {<index>.w <item ID>.w <type>.B <identify flag>.B <equip type>.w <equip point>.w <attribute?>.B <refine>.B <card>.4w}.20B* + 所有装備リスト + list of equipments that you have +R 00a5 <len>.w {<index>.w <item ID>.w <type>.B <identify flag>.B <amount>.w ?.2B}.10B* + カプラさんに預けてある消耗品&収集品リスト + list of consumptive item and collecter item that you leave with capra. +R 00a6 <len>.w {<index>.w <item ID>.w <type>.B <identify flag>.B <equip type>.w <equip point>.w <attribute?>.B <refine>.B <card>.4w}.20B* + カプラさんに預けてある装備リスト + list of equipments that you leave with capra. +S 00a7 <index>.w <ID>.l + 所持アイテムindexを使用する。IDは自分のみ? + use index item. ID can be only myself? +R 00a8 <index>.w <amount>.w <type>.B + アイテム使用応答。type=00の場合使用失敗? amountもゴミの模様 + response to use item. type=00 means failed. amount is unused. + type=01の場合成功で、amountは使用後の残り個数 + type=01 means success to use item, amount is a number of rest of the item. +S 00a9 <index>.w <equip type>.w + アイテム装備 + equip item. +R 00aa <index>.w <equip point>.w <type>.B + アイテム装備応答。type=00の場合装備失敗? equip pointもゴミの模様 + response to equip item. type=00 means fail. equip point is unused. +S 00ab <index>.w + 装備解除 + take off equipment. +R 00ac <index>.w <equip point>.w <type>.B + 装備解除応答。type=00の場合失敗? equip pointもゴミの模様 + response to take off equipment. type=00 means fail? equip point is unused. +R 00af <index>.w <amount>.w + アイテム数減少。amount個だけ減らす + decrease number of item by amount. +R 00b0 <type>.w <val>.l + 色々な能力値の更新。以下type:対応する数値を列挙 + update values of various status. + 0000:speed 0003:悪行値 0004:マナーポイント 0005:HP 0006:MaxHP + 0000:speed 0003:carma 0004:manner point 0005:HP 0006: MaxHP + 0007:SP 0008:MaxSP 0009:ステータスポイント 000b:ベースレベル + 0007:SP 0008:MaxSP 0009:status point 000b: base level + 000c:スキルポイント 0018:重量(表示されてる数字の10倍) + 000c:skil point 0018:weight(this number must be 10 times greater than it's been diplayed.) + 0019:最大重量(表示されてる数字の10倍) + 0019:max weight(this number must be 10 times greater than it's been diplayed.) + 0029:ATK前 002a:ATK後 002b:MATK前 002c:MATK後 + 0029:attack in front 002a:attack in back 002b:matk in front 002c:matk in back + 002d:DEF前 002e:DEF後 002f:MDEF前 0030:MDEF後 + 002d:deffence in front 002e:deffence in back 002f:mdef in front 0030:mdef in back + 0031:HIT 0032:FLEE前 0033:FLEE後 0034:クリティカル + 0031:hit 0032:flee in front 0033:flee in back 0034: critical + 0035:ASPD(2ms単位の時間?) 0037:ジョブレベル + 0035:aspd(time by 2ms?) 0037:job level + 0082:謎 ATK後と同じ数字? + 0082:unknown. is it the same number as atk in back? +R 00b1 <type>.w <val>.l + 色々な能力値の更新。以下type:対応する数値を列挙 + update valies of various status. below is the list of corresponding type and value. + 0001:ベース側経験値 0002:ジョブ側経験値 0014:zeny + 0001:base experience 0002:job experience 0014:zeny + 0016:ベース側必要経験値 0017:ジョブ側必要経験値 + 0016:base experience that needed to level up 0017:job experience that needed to level up + β1では00b0はvalがshort、00b1はvalがlongという使い分けがあったんだけど + 今となっては差が無くなって、盲腸みたいなもの? + in beta1, value of 00b0 was short and value of 00b1 was long, + but not there are no difference. +S 00b2 <type>.B + type=00 死亡時リスタート + restart game when character died + type=01 キャラセレ要求 + request character select +R 00b3 <type>.B + type=01 キャラセレ応答 + response to character select +R 00b4 <len>.w <ID>.l <str>.?B + IDのNPCからのメッセージ + message from npc id +R 00b5 <ID>.l + IDのNPCとのメッセージウィンドウに"NEXT"アイコンを出す + display the "NEXT" icon to npc message window +R 00b6 <ID>.l + IDのNPCとのメッセージウィンドウに"CLOSE"アイコンを出す + display the "CLOSE" icon to npc message window +R 00b7 <len>.w <ID>.l <str>.?B + IDのNPCの会話で選択項目表示。各項目は':'で区切られる + display select options in npc message window. each options devided by ":". +S 00b8 <ID>.l <select>.B + IDのNPCの会話の選択。各項目に順に1〜が振られる。ffでキャンセル? + select options in ncp message window. number starts from 1 for each options. cancel for ff? +S 00b9 <ID>.l + IDのNPCとの会話。NEXTボタンを押した + "NEXT" button clicked in ncp message window. +S 00bb <type>.w <amount>.B + ステータスup要求。typeは000dから0012が順にSTR,AGI,VIT,INT,DEX,LUKに対応 + request update status. type can be 000d for STR, 000e for AGI, 000f for VIT, 0010 for INT, 0011 for DEX, 0012 for LUK. +R 00bc <type>.w <fail>.B <val>.B + ステータスup応答。fail=01なら成功。typeは00bbと同じ。valは上った後の数字 + respnse to update status. it's successeeded if fail=01. type is the same value as packet number 00bb. val is a value of increase. + 失敗例は見た事無いので謎。ステータスポイントが足りない状態で + 00bbを発行できるクライアントが有れば、fail=00になるのではないかと予想 + it's unknown when it's failed because i havent ever seen. i think it will be fail=00 when it's failed. +R 00bd <status point>.w <STR>.B <STRupP>.B <AGI>.B <AGIupP>.B <VIT>.B <VITupP>.B <INT>.B <INTupP>.B <DEX>.B <DEXupP>.B <LUK>.B <LUKupP>.B <ATK>.w <ATKbonus>.w <MATKmax>.w <MATKmin>.w <DEF>.w <DEFbonus>.w <MDEF>.w <MDEFbonus>.w <HIT>.w <FLEE>.w <FLEEbonus>.w <critical>.w ?.w + まとめてステータス情報を送るパケット + packet of information for various status. +R 00be <type>.w <val>.B + 必要ステータスポイント更新パケット。typeは0020〜0025が順にSTR〜LUKに対応 + packet to update required status point. type can be 0020-0025 for STR-LUK.( see packet number 00bb for detals.) +S 00bf <type>.B + エモーションを出す。typeは00-0c(,0d)がALT+1〜ALT+9,ALT+0,チョキ,グー,パー(,韓国旗)に対応 + display emotion. type can be 00-0c(,0d) for ALT+1-ALT+9,ALT+0,CTRL+-,CTRL++,CTRL+\(,korean flag). +R 00c0 <ID>.l <type>.B + IDの人がエモーションを出した。typeは00bfと同じ + emotion by ID. type is the same as 00bf. +S 00c1 + login人数問い合わせ + request to ask loged in people. +R 00c2 <val>.l + login人数応答 + response to asking loged in people. +R 00c3 <ID>.l <type>.B <val>.B + 見た目変更。typeは00で本体(転職時等)、02が武器、03が頭(下)、04が頭(上)、05が頭(中)、08が盾 + change looks. type=00 means body(by jobs), 02 is weapon, 03 is head(lower), 04 is head(upper), 05 is head(middle), 08 is shield. +R 00c4 <ID>.l + 話かけたNPCが商人だったのでbuy/sell選択窓出 + display "BUY" or "SELL" window by npc ID +R 00c5 <ID>.l <type>.B + buy/sell選択。type=00ならbuy。type=01ならsell + select "BUY" or "SELL". type=00 is buy, type=01 is sell. +R 00c6 <len>.w {<value>.l <DCvalue>.l <type>.B <item ID>.w}.11B* + NPCのお店buy選択時。DCvalueは商人DC後の値段 + list of marchandizes when clicked "BUY". DCvalue is a value of Discount Skill applied. +R 00c7 <len>.w {<index>.w <value>.l <OCvalue>.l}.10B* + NPCのお店sey選択時。OCvalueは商人OC後の値段 + list of items when clicked "SELL". OCvalue is a value of Over Charge Skill applied. +S 00c8 <len>.w {<amount>.w <item ID>.w}.4B* + NPCのお店から買う + buy item from npc shop. +S 00c9 <>.w {<index>.w <amount>.w}.4B* + NPCのお店に売る + sell item to npc shop. +R 00ca <type>.B + NPCから購入終了。type=00成功 + response for buying item. type=00 meanse successetion. +R 00cb <type>.B + NPCへ売却終了。type=00成功 + response for selling item. type=00 means success. +S 00cf <nick>.24B <type>.B + type=00 nickからの発言受け付け拒否 (/ex nick) + deny speech from nick(/ex nick). + type=01 nickからの発言受け付け許可 (/in nick) + allow speech from nick(/in nick) +S 00d0 <type>len.B + type=00 全ての発言受け付け拒否 (/exall) + deny all speech(/exall) + type=01 全ての発言受け付け許可 (/inall) + allow all speech(/inall) +R 00d1 <type>.B <fail>.B + fail=00 発言受け付け拒否成功 + success to deny speech + fail=01 発言受け付け拒否失敗 + fail to deny speech +R 00d2 <type>.B <fail>.B + fail=00 全発言受け付け拒否成功 + seccess to allow speech + fail=01 全発言受け付け拒否失敗 + fail to alloe speech +S 00d5 <len>.w <limit>.w <pub>.B <passwd>.8B <title>.?B + チャット立て。ここからチャット関係が続くけど調べが甘いので補完よろ + create chat room.(from now on, im not sure for packets about chat.) +R 00d6 <fail>.B + チャット立て応答 + response to create chat room. +R 00d7 <len>.w <owner ID>.l <chat ID>.l <limit>.w <users>.w <pub>.B <title>.?B + 画面内チャット情報 + information for chat room in the screen. +R 00d8 <chat ID>.l + チャット消去 + delete chat room. +S 00d9 <chat ID>.l <passwd>.8B + チャット参加要請 + request to join the chat. +R 00da <fail>.B + チャット参加失敗 + fail to join the chat. +R 00db <len>.w <chat ID>.l {<index>.l <nick>.24B}.28B* + チャット参加者リスト + list of people in the chat room. +R 00dc <users>.w <nick>.24B + チャットへの参加者追加(?) + add person to the chat room. +R 00dd <index>.w <nick>.24B <fail>.B + チャットから参加者抜け + leave the chat room. +S 00de <len>.w <limit>.w <pub>.B <passwd>.8B <title>.?B + チャットステータス変更 + change chat room status. +R 00df <len>.w <owner ID>.l <chat ID>.l <limit>.w <users>.w <pub>.B <title>.?B + チャットステータス変更成功 + success to change chat room status. +S 00e0 ?.l <nick>.24B + チャットルーム所有者変更要求? + request to change owner of the chat room? +R 00e1 <index>.l <nick>.24B + チャット参加者番号付け直し? + re-number people in the chat room? +S 00e2 <nick>.24B + チャットkick + kick nick from chat room. +S 00e3 + チャット抜け + leave chat room. +S 00e4 <ID>.l + 取り引き要求 + request trade. +R 00e5 <nick>.24B + 取り引き要請受け + recieve a request to trade. +S 00e6 <type>.B + type=03 取り引き要請ok + trade ok. + type=04 取り引き要請キャンセル + trade canceled. +R 00e7 <fail>.B + 取り引き要求応答 + response to requesting trade. + fail=00 距離が遠過ぎ + too far. + fail=03 要請受けてくれた + allowed the trade. + fail=04 キャンセルされた? + trade canceled? +S 00e8 <index>.w <amount>.l + アイテム追加。index=0でzeny追加。正規クライアントではzenyは00ebの直前のみ + add item. index=0 means adding zeny. for official client, zeny can be added only in packet number 00eb. +R 00e9 <amount>.l <type ID>.w <identify flag>.B <attribute?>.B <refine>.B <card>.4w + 相手方からのアイテム追加 + added item from other character. +R 00ea <index>.w <fail>.B + fail=00 アイテム追加成功 + success to add item. + fail=01 追加失敗。相手側重量オーバ + fail to add item. the player was over weighted. +S 00eb + アイテム追加完了(ok押し) + completed adding item.(cliecked OK) +R 00ec <fail>.B + fail=00 自分からのok受領 + recieved "OK" from myself + fail=01 相手からのok受領 + recieved "OK" from the other. +S 00ed + 取り引きキャンセル + trade canceled. +R 00ee + 取り引きがキャンセルされました + message of "trade canceled" +S 00ef + 取り引き許諾(trade押し) + trade OKed. (cliecked Trade) +R 00f0 + 取り引き完了 + trade completed. +R 00f2 <num>.w <limit>.w + カプラさん許容アイテム個数&現状 + number of item that capra can stock and number of item that capra stocks now. +S 00f3 <index>.w <amount>.l + カプラさん倉庫にアイテム放り込み + put item to capra's warehouse. +R 00f4 <index>.w <amount>.l <type ID>.w <identify flag>.B <attribute?>.B <refine>.B <card>.4w + カプラさん倉庫のアイテム追加 + add item to capra's warehouse. +S 00f5 <index>.w <amount>.l + カプラさん倉庫からアイテム取り出し + take out item from capra's warehouse. +R 00f6 <index>.w <amount>.l + カプラさん倉庫のアイテム削除 + delete item in the capra's warehouse. +S 00f7 + カプラさん倉庫閉じ + request to close capra's warehouse. +R 00f8 + カプラさん倉庫閉じ応答 + response to close capra's warehouse. +S 00f9 <party name>.24B + パーティ作成 + create party. +R 00fa <fail>.B + fail=00 作成成功 + success to create party. +R 00fb <len>.w <party name>.24B {<ID>.l <nick>.24B <map name>.16B <leader>.B <offline>.B}.46B* + パーティ情報まとめて送り + packet for various information about party. +S 00fc <ID>.l + パーティ勧誘する + invate player to the party. +R 00fd <nick>.24B <fail>.B + fail=00 相手は既にパーティに入っていた + the player is already in other party. + fail=01 相手に拒否された + invitaion was denied. + fail=02 勧誘成功 + success to invite, +R 00fe <ID>.l <party name>.24B + パーティに誘われた + invited to party. +S 00ff <ID>.l <fail>.l + パーティに誘われた時の返答。fail=1 ok返答? + response when player was invited to party. fail=1 means OK? +R 0100 + ? パーティ関連? + unknown. related to party? +S 0101 <exp>.w <item?>.w + パーティ設定変更 + change party setting. +R 0102 <exp>.w <item?>.w + パーティ設定現状? exp=2の場合は公平配分設定失敗? + party setting status. when exp=2, fail to set "equality for experience"? +R 0104 <ID>.l ?.l <X>.w <Y>.w <offline>.B <party name>.24B <nick>.24B <map name>.16B + パーティ1人分情報更新 + information about a one player in th party. +R 0105 <ID>.l <nick>.24B <fail>.B + nickさんがパーティから離脱 + nick leaves the party. +R 0106 <ID>,l <HP>.w <MaxHP>.w + パーティメンバHP更新 + update HP of party members. +R 0107 <ID>.l <X>.w <Y>.w + パーティメンバ位置更新 + update coordinates of party members. +S 0108 <len>.w <message>.?B + パーティ内発言 + send speech for party memebers. +R 0109 <len>.w <ID>.l <message>.?B + パーティ内発言受信 + receive speech for party memebers. +R 010a <type ID>.w + MVPアイテム取得 + get MVP item. +R 010b <exp>.l + MVP経験値取得 + get MVP experience. +R 010c <ID>.l + MVPキャラ表示 + display MVP character. +R 010e <skill ID>.w <lv>.w <sp>.w <range>.w <up>.B + スキル情報更新。spは未使用? + update skill sinformation. sp is unused? +R 010f <len>.w {<skill ID>.w <target type>.w ?.w <lv>.w <sp>.w <range>.w <skill name>.24B <up>.B}.37B* + スキル情報の塊。skill nameは一部流れて来ない物がある>AL_PNEUMA,PR_SLOWPOISON等 + bunch of information about skill. some of skill name is not send (AL_PNEUMA, PR_SLOWPOISON etc). + target typeは0-パッシブ、1-敵、2-場所、4-即時発動、16-味方 + target type is 0 for novice skill, 1 for enemy, 2 for place, 4 for immediate invoke, 16 for party member + lv=0 up=0の場合はリストに出してない? + it will not be on list when lv=0 up=0? +R 0110 <skill ID>.w <basic type>.w ?.w <fail>.B <type>.B + fail=00の時にスキル利用失敗? + fail to use skill when fail=00? + type 00:basic typeの方 01:SP不足 02:HP不足 03:memo無し 04:delay中 + type 00:basic type 01:lack of SP, 02:lack of HP, 03:no memo, 04:in delay + 05:お金無し(めまー) 06:武器がよろしくない 07:赤ジェム無し 08:青ジェム無し 09:謎 + 05:lack of money, 06:weapon does not satisfy, 07:no red jewel, 08:no blue jewel, 09:unkown + basic type 00:取り引き 01:emotion 02:座り 03:チャット 04:パーティ + basic type 00:trade 01:emotion 02:sit down, 03:chat, 04:party + 05:shout? 06:PK 07:マナーポイント + 05:shout? 06:PK, 07:manner point +R 0111 <skill ID>.w <target type>.w ?.w <lv>.w <sp>.w <range>.w <skill name>.24B <up>.B + 010fの1つ分。β2だと未使用? + just one skill information. not used in beta2? +S 0112 <skill ID>.w + スキルlvup要求 + request to skill level up. +S 0113 <level>.w <skill ID>.w <ID>.l + IDをターゲットにskillを使う + use skill to the target. +R 0114 <skill ID>.w <src ID>.l <dst ID>.l <server tick>.l <src speed>.l <dst speed>.l <param1>.w <param2>.w <param3>.w <type>.B + 攻撃系スキルエフェクト@ + skill effect for attack. + type=04 火壁で観測 type=06とほぼ同じ? + rtpe=04 observed when firewall was used. is that the almost same as type=06? + type=06 単発もの? param1はダメージ合計、param2はlevel、param3は1固定と予想 + type=06 skill for just one hit? param1 is total damage, param2 is level, param3 will always stay 1. + type=08 連打もの? param1はダメージ合計、param2はlevel、param3は分割数と予想 + type=08 skill for multiple hits? param1 is totak damage, param2 is level, param3 will be a number of hit. +R 0115 <skill ID>.w <src ID>.l <dst ID>.l <server tick>.l <src speed>.l <dst speed>.l <X>.w <Y>.w <param1>.w <param2>.w <param3>.w <type>.B + 弾き飛ばし有り攻撃系スキルエフェクト + blow up type skill effect. + type=05 ダメージ&弾き飛ばし。param1はダメージ合計、param2はlevel、param3は分割数と予想 + type=05 damage and blow up. param1 is total damage, param2 is level, param3 will be a number of hit. + type=06 爆心地? 少なくともparam1はゴミの模様 + type=06 a point of explorsion? param1 is unused at least. +S 0116 <level>.w <skill ID>.w <X>.w <Y>.w + (X,Y)をターゲットにskillを使う + use skill at (X,Y). +R 0117 <skill ID>.w <src ID>.l <val>.w <X>.w <Y>.w <server tick>.l + 場所相手のスキルエフェクト表示。valはレベルか、一部固さ?(氷壁) + display skill effect at (X,Y). is val level? or how hard it is (like ice wall)? +S 0118 + 攻撃キャンセル + cancel to attack +R 0119 <ID>.l <param1>.w <param2>.w <param3>.w ?.B + 見た目変更 + change looks. + param1=02 フロストダイバで凍り漬け? + param1=02 flozen diva? + param2=01 毒? + param2=01 poison? + param2=20 ANGELUS状態? + param2=20 ANGELUS? + param3=01 サイトかルワッチ? + param3-01 site or ruwatch? (i dont know skill names in english :() + param3=0b ハイディング状態? + param3=0b hiding? + param3=0b クローキング状態? + param3-0b cloking? + param3=0d カート付き + param3=0d with cart + param3=0e 鷹付き + param3-0e with hawk + param3=0f ペコペコ乗り + param3=0f with pekopeko + +R 011a <skill ID>.w <val>.w <dst ID>.l <src ID>.l <fail>.B + 非ダメージ系スキル表示。ヒールの場合valは回復量 + display no-damage skill effect. val is an amount of HP cured when it's heal. + fail=00の場合失敗ぽいが、スチール以外では見た事無し + fail=00 must mean fail, but i havent seend it except steal. +S 011b <skill ID>.w <map name>.16B + 011cへの応答。使わない場合"cancel"を送る + response to packet number 011c. send "cancel" for no-use. +R 011c <skill ID>.w <map1>.16B <map2>.16B <map3>.16B <map4>.16B + テレポ/ポタの場所選択。 + select place for teleport or portal warp. + テレポの場合、Random/セーブ場所、ポタの場合、セーブ場所/memo1/memo2/memo3となる + マップ名のみ送られる + in case of teleport, Ramdom/save point will be sent, + in case of portal warp, save point/memo1/memo2/memo3 will be sent. + only map name wil be sent. +S 011d + 現在居る所をメモ要求 + request to take a memo at this point. +R 011e <fail>.B + fail=00 メモ成功 + success to take memo. + fail=01 メモ失敗 + fail to take memo. +R 011f <dst ID>.l <src ID>.l <X>.w <Y>.w <type>.B <fail>.B + スキル効能地作成 + create ground effect for skills like firewall. + type 7e:SW 7f:火壁 80:ポタ発動中 81:ポタ発動前 83:サンク 85:フニューマ + type 7e:SW, 7f:firewall, 80:portal warp(invoking), 81:portal warp(before invoking), 83:sank, 85:funewma( i really don know skill names :() + 86:バーミリオン 8c:トーキーボックス発動時 8d:氷壁 8e:くあぐまいやー 91:あんくるすねあ + 86: bermillion, 8c:talky box(invoked), 8d:frost diva, 8e:kuagumire, 91:uncle snear + 93:らんどまいん 97:?? 99:トーキーボックス発動前 + 93:land mine, 97:??, 99:talky box(befor invoked) + 他情報求む +R 0120 <ID>.l + スキル効能地消去 + delete ground effect. +R 0121 <num>.w <num limit>.w <weight>.l <weight limit>l + カートの種類&重さの現在値&上限 + kind of cart, weight and max weight. +R 0122 <len>.w {<index>.w <item ID>.w <type>.B <identify flag>.B <equip type>.w <equip point>.w <attribute?>.B <refine>.B <card>.4w}.20B* + カート内アイテム。装備品 + equipments in cart. +R 0123 <len>.w {<index>.w <item ID>.w <type>.B <identify flag>.B <amount>.w ?.2B}.10B* + カート内アイテム。消耗品/収集品 + cunsumptive and collector items in cart. +R 0124 <index>.w <amount>.l <item ID>.w <identify flag>.B <attribute?>.B <refine>.B <card>.4w + カートにアイテム追加 + add item to cart. +R 0125 <index>.w <amount>.l + カートからアイテム削除 + delete item in cart. +S 0126 <index>.w <amount>.l + カートにアイテムを入れる + put item to cart. +S 0127 <index>.w <amount>.l + カートからアイテムを取り出す + take out item from cart. +S 0128 <index>.w <amount>.l + カプラさんからカートへアイテムを移す + move item from capra's warehouse to cart. +S 0129 <index>.w <amount>.l + カートからカプラさんへアイテムを移す + move item from cart to capra's warehouse. +R 012c <fail>.B + fail=00 重量制限を越えてカートにアイテムを入れられませんでした? + fail=00 over the weight and could not add item to cart. +R 012d <num>.w + 露店開設。アイテムリスト要求。numは置ける最大数 + create shop (marchant skill). request item list. num is a number of kind of item that can be sell. +S 012e + 露店閉鎖 + close shop. +S 012f <len>.w <message>.80B {<index>.w <amount>.w <value>.l}.8B* + 露店開設、露店名&アイテム,値段リスト + create shop, shop name, item, price list. +S 0130 <ID>.l + 露店アイテムリスト要求 + request item list for shop( not npc shop). +R 0131 <ID>.l <message>.80B + 露店看板表示 + display shop name tag. +R 0132 <ID>.l + 露店看板消去 + delete shop name tag. +R 0133 <len>.w <ID>.l {<value>.l <amount>.w <index>.w <type>.B <item ID>.w <identify flag>.B <attribute?>.B <refine>.B <card>.4w}.22B + 露店アイテムリスト + item list for shop(not npc shop). +S 0134 <len>.w <ID>.l {<amount>.w <index>.w}.4B* + 露店アイテム購入 + buy item from shop (not npc). +R 0135 <index>.w <amount>.w <fail>.B + 露店アイテム購入失敗。failは原因 + fail to buy item from non npc shop. fail tells you reasons. +R 0136 <len>.w <ID>.l {<value>.l <index>.w <amount>.w <type>.B <item ID>.w <identify flag>.B <attribute?>.B <refine>.B <card>.4w}.22B* + 露店開設成功 + success to create non-npc-shop. +R 0137 <index>.w <amount>.w + 露店アイテム販売報告 + report of selling item. +R 0139 <ID>.l <X>.w <Y>.w <X2>.w <Y2>.w <range>.w + IDの敵は(X,Y)に居て自分は(X2,Y2)に居るので攻撃が届きませんでした + the enemy at(X,Y) was too far to attack from my coordinate (X2,Y2). + 攻撃可能距離はrangeなので、近寄って下さい? + possible range to attack enemy is "range", so be closer? +R 013a <val>.w + 攻撃射程 + attack range. +R 013b <type>.w + 各種メッセージ表示。3=矢が装備できました + various message. 3="arrow has been equiped" +R 013c <ID>.w + 装備された矢のItemID。0で、未装備状態。 + item id of equiped arrow. 0 means no arrow is equiped. +R 013d <type>.w <val>.w + HP回復スキル/SP回復スキルによる回復 + recovery of HP/SP by HP/SP recovery skill. + type=5ならHP type=7ならSP + type=5 is HP, type=7 is SP. +R 013c <index>.w + 装備している矢 + id of equiped arrow. + +R 013e <src ID>.l <dst ID>.l <X>.w <Y>.w <lv?>.w ?.w <wait>.l + スキル詠唱中。PC/NPCが相手の場合は(X,Y)は0。場所がターゲットの場合はdst IDは0になる + skill has been casting. (X,Y) will be 0 when target is player character or NPC. dst ID will be 0 when target is place. + + 0x013e の offset+16(dword) はスキル属性です(調査済)。 + offset+16(dword) in packet number 0x013e is skill attribute. + + 00:無 01:水 02:地 03:火 04:風 05:毒 06:聖 07:暗 08:念 09:死 + 00:none, 01:water, 02:ground, 03:fire, 04:wind, 05:poinson, 06:holly, 07:dark, 08:spirit(i don know how to translate.), 09:death + 将来的に、詠唱中のエフェクトが属性で変わるのかと。 + casting effect might differ by skill attribute in the future. + + waitはms単位かな? + wait in mili second? +R 0141 <type>.l <base>.l <bonus>.l + ステータス情報。typeは0dから12が順にSTR,AGI,VIT,INT,DEX,LUKに対応 + information for status. type is 0d-12 for STR,AGI,VIT,INT,DEX,LUK. + base+bonusと表示される + base+bonus will be displayed. +R 0144 <ID>.l <type>.l <X>.l <Y>.l <point ID>.B <color>.3B ?.B + 案内員用、マップ上アイコン表示パケット + for guid npc, packet for display icon on map. + type=1 アイコンを表示 + display icon. + type=2 アイコンを消去 + delete icon. +R 0145 <file name>.16B <type>.B + (今の所)カプラさんcutin表示 + display capra picture(at this time). + type=02 表示 + display. + type=ff 消去 + delete. +S 0146 <ID>.l + IDのNPCとの会話。CLOSEボタンを押した。ack無しにNPCメッセージウィンドを同時に閉じる + talk to npc with ID. Clicked CLOSE button. +R 0147 <skill ID>.w <target type>.w ?.w <lv>.w <sp>.w <range>.w <skill name>.24B <up>.B + アイテム利用の結果一時的に得られたスキル情報 + effect for skill by using item. +S 0148 <ID>.l + リザレクションの相手決め? @ β1 + decide target of a skill rezarection? in beta1. +S 0149 <ID>.l <type>.B + IDにマナーポイントを与える。type=00 プラス type=01 マイナス + give manner point to ID. type=00 is plus, type=01 is minus. +R 014a <fail>.l + マナーポイントを与えた結果。fail=0 成功 fail=1 失敗 + result of giving manner point. fail=0 is success, fail=1 is fail. +R 014b <type>.B <nick>.24B + マナーポイントを貰った。type=00 プラス type=01 マイナス + get manner point. type=00 is plus, type-01 is minus. +R 014C <len>.w (<type>.l <guildID>.l <guild name>.24B).* + 同盟・敵対ギルド表示 + display alliance and opposition guild. + type=0 同盟 + alliance. + type=1 敵対 + opposition. +S 014D + ギルド情報表示開始? + start of guild information? +R 014E <type?>.l + type=0x57 一般ギルド団員 + normal guild member. + type=0xD7 ギルドマスター + guild master. +S 014F <page>.l + ギルド表示タブ送信 + send packet for guild "DISPLAY" tab. +R 0150 <guildID>.l <guildLv>.l <connum>.l <定員>.l <Avl.lvl>.l ?.l <next_exp>.l ?.16B <guild name>.24B <guild master>.24B ?.16B + ギルド情報 + guild info. +S 0151 <guild ID>.l + エンブレム要求 + request for guild emblem. +R 0152 <len>.w <guild ID>.l <emblem ID?>.l <emblem data>.?B + エンブレムイメージ送付 + return emblem image. +R 0154 <len>.w {<accID>.l <charactorID>.l <髪型>.w <髪の色>.w <性別?>.w <job>.w <lvl?>.w <上納経験値>.l <online>.l <Position>.l ?.50B <nick>.24B}* + ギルドメンバリスト? + guild member list? +S 0159 <guildID>.l <accID>.l <charID>.l <mess>.40B + ギルド脱退送信 + send packet for leaving guild. +R 015A <nic>.24B <mess>.40B + ギルド脱退(全員)受信 + receive packet for leaving guild(all members). +S 015B <guildID>.l <accID>.l <charID>.l <mess>.40B + ギルド追放送信 + send packet for kicking member out of the guild. +R 015C <nick>.24B <mess>.40B <アカウントID>.24B + ギルド追放(全員)受信 + receive packet for kicking member out of the guild.(all member) +R 0163 <len>.w <nick>.24B <accountID>.24B <kicking reason>.40B + +S 0165 <myaccID>.l <guild name>.24B + ギルド作成 + create guild +R 0166 <len>.w ?.28B* + 役職名リスト? + list for roll of members? +R 0167 <type>.b + ギルド作成合否 + response to vreating guild. + type = 0 ギルド作成成功 + success. + type = 2 同名のギルドがある + there is a guild with the same name. +S 0168 <TargetAccID>.l <sourceAccID>.l <myCharactorID>.l + ギルド勧誘 + invite to the guild. +R 0169 <type>.B + ギルド勧誘拒否された + invitation denied. +R 016A <guild ID>.l <guild name>.24B + ギルド勧誘された + invited to the guild. +S 016B <guild ID>.l <type>.l + ギルド勧誘返信 + response to invitaion for joining to guild. + type=0 拒否する + deny. + type=1 許諾する + OK. +R 016c <guild ID>.l ?.13B <guild name>.24B + login時ギルド情報 + guild information when loged in. +R 016d <ID>.l <charactor ID>.l <online>.l + ギルドメンバがloginした抜けた等 + information about guild member loged in or loged out etc. +R 016f <message>.180B + ギルドお題目? + guild message? +S 016E <guildID>.l <mess1>.60B <mess2>.120B + ギルド告知設定 + set guild announcement. +R 016F <mess1>.60B <mess2>.120B + ギルド告知 + guild announcement. +S 0170 <TargetAccID>.l <sourceAccID>.l <myCharactorID>.l + 同盟要請勧誘 + invite the guild to be alliance. +R 0171 <SorceAccID>.l <guild name>.24B + 同盟要請勧誘された + invited to be a alliance. +S 0172 <SorceAccID>.l <type>.l + 同盟要請返信 + response for invitiation to be alliance. + type=0 拒否する + deny. + type=1 許諾する + OK. +R 0173 <type>.B + type = 0 すでに同盟関係 + the guild is already alliance. + type = 1 同盟拒否された + denied to be alliance. + type = 2 同盟成功 + success to invite to be alliance. +R 0177 <len>.w <index>.w* + 鑑定可能アイテムリスト + list of items that need to be judge( i mean unkown items.) +S 0178 <index>.w + アイテム鑑定 + judge item. +R 0179 <index>.w <fail>.B + アイテム鑑定結果。fail=00で成功。fail=01ってあるのか? + response to judging item. fail=00 is success. is there fail=01? +S 017A <index>.w + カードWクリック + card is double clicked. +R 017B <len>.w {<index>.w}* + カード挿入できるアイテムIndex番号 + item index number for items that can be inserted card. +S 017C <SrcIndex>.w <DescIndex>.w + Src をDescに突っ込む + insert Src to Desc. +R 017D <DescIndex>.w <SrcIndex>.w <fail>.b + Src をDescに突っ込み<fail> 0=成功 1=失敗? + response to insert Src to Desc. fail=0 is success, fail=01 is fail? +S 017e <len>.w <message>.?B + ギルド内メッセージ発言 + send speach for guild members. +R 017f <len>.w <message>.?B + ギルド内メッセージ受信 + receive guild message. +R 0182 <accID>.l <charactorID>.l <hair type>.w <hair color>.w <sex?>.w <job>.w <lvl?>.w <experience?>.l <online>.l <Position>.l ?.50B <nick>.24B + +R 0187 <account ID>.l + alive信号? + alive signal? +R 0188 <fail?>.w <index>.w <val>.w + 武器精錬。結果+val武器に + weapon refiling. result+val to weapon +R 0189 ?.w + 謎。テレポ失敗? + unknown. fail to teleport? +S 018a ?.w + ゲーム終了 + game quited. +R 018b <fail>.w + ゲーム終了/キャラセレ応答。fail=0成功。fail=1失敗? + game quited/character select sever response. fail=0 is success, fail=1 is fail? +R 018C <MonsID>.w <class>.w <size>.w <HP>.w <?>.w <deffence>.w <kind of monster>.w <magic deffence>.w <attribute>.w <anti-attribute?>.9b + wizの敵のセンス結果 + response to sense skill by wizard. + 0 小型 + small + 1 中型 + middle + 2 大型 + big +R 0191 <ID>.l <message>.80B + トーキーボックスのメッセージ + message of talky box. +S 0193 <ID>.l + ギルドメンバ名前引き? + name search for guild member? +R 0194 <ID>.l <nick>.24B + ギルドメンバ名前引き応答? + response to name search for guild member? +R 0195 <ID>.l <nick>.24B <party name>.24B <guild name>.24B <class name>.24B + ギルド所属PCの場合の0094返答 + response to packet number 0094 that if the player joined guild. +R 0196 <type>.w <ID>.l + 増強系スキル使用時のメッセージ色々。IDはtargetと思われるが自分相手のみしか来ない? + various message of skill that effect status. ID must be target, but only m ID and other's ID are sent? + type=00 2HQ付与「攻撃速度が増加しました。」 + 2HQ casted. "attack speed insreased." + type=01 2HQ解除「攻撃速度が減少しました。」 + 2HQ ended. "attack speed decreased." + type=02 IMPOSITIO付与「武器の攻撃力が増加しました。」 + IMPOSITIO casted. "power of the weapon increased." + type=03 IMPOSITIO解除「武器の攻撃力が減少しました。」 + IMPOSITIO ened. "power of the weapon decreased." + type=04 「スキル使用ディレイが減少しました。」 + "casting delay become short" + type=05 「スキル使用ディレイが元に戻りました。」 + "casting delay return to defailt" + type=06 「武器に毒属性が付与されました。」 + "attribute of poison is given to the weapon" + type=07 ASPERSIO付与「武器に聖属性が付与されました。」 + ASPERSIO casted. "attribute of holly is given to the weapon" + type=08 ASPERSIO解除「武器の属性が元に戻りました。」 + ASPERSIO ended. "attribute of weapon return to default" + type=09 「防具に聖属性が付与されました。」 + "armor got holly attribute" + type=0a 「防具の属性が元に戻りました。」 + "armor's attribute return to default" + type=0b KYRIE付与「バリア状態になりました。」 + KYRIE casted. "barrier" + type=0c KYRIE解除「バリア状態が解除されました。」 + KYRIE ended. "barrier end" + type=0d 「ウェポンパーフェクションモードになりました。」 + "became weapon ferfection mode" + type=0e 「ウェポンパーフェクションモードが解除されました。」 + "end weapon perfection mode" + type=0f 「オーバートラストモードになりました。」 + "became over trust mode" + type=10 「オーバートラストモードが解除されました。」 + "end over trust mode" + type=11 「マキシマイズパワーモードになりました。」 + "became maximize power mode" + type=12 「マキシマイズパワーモードが解除されました。」 + "end maximize power mode" +S 0197 <type>.w + type=0 /resetstate + type=1 /resetskill + 効能は無し? + no effect? +R 019b <ID>.l <type>.l + 他人のlvupや武器精錬等の表示? + display other's level up effect or weapon refiling? + type=0 base lvup? + type=1 job lvup? + type=3 武器精錬 + weapon refiling +R 0199 <type>.w + type=1 pvpモード開始? + start pvp mode? +R 019a <ID>.l <rank>.l <num>.l + pvp順位 rank/num + pvp rank rank/num +R 019b <ID>.l <type>.l + 他人のlvupや武器精錬等の表示? + type=0 base lvup? + type=1 job lvup? + type=2 武器精錬失敗 + type=3 武器精錬成功 + +R 019d <?>.4B + GMコマンド/hide + +S 00CC <ID>.l + GM用右クリックメニュー「(name)使用者強制終了」使用 + use special right click menu for GM "(name) force to quit" + +S 0149 <ID>.l <type>.B <time>.w + GM用右クリックメニュー「チャット禁止時間を下げる(解ける)」使用 → type=00 + use special right click menu for GM "decrease prohibited time to create chat room". type=00 + GM用右クリックメニュー「チャット禁止時間を上げる(掛ける)」使用 → type=01 + use special right click menu for GM "increase prohibited time to create chat room". type=01 + timeは分単位です(確か + unit is minute (maybe + +R 019e + 捕獲モンスター決め +S 019f <ID>.l + 捕獲モンスター指定 +R 01a0 <fail>.B + 捕獲判定 + fail=01で成功、00で失敗 +S 01a1 <param>.1B + <param> + 0x00:ペット状態表示 + 0x01:餌を与える + 0x02:パフォーマンス + 0x03:卵に戻す + 0x04:アクセサリ解除 +R 01a2 <pet name>.24B <name flag>.B <lv>.w <hungry>.w <friendly>.w <accessory>.w + ペットの状態 + name flag:00=名前未設定 01=名前設定済み(変更不可) + lv=ペットのレベル、hungry=満腹度(0~100)、friendly=親密度(初期値250?)、accessory=アクセサリのItemID +R 01a3 <fail>.B <itemID>.w + <fail> + 0x00:餌やり失敗 + 0x01:餌やり成功 +R 01a4 <type>.B <ID>.l <val>.l + ペット関連通知 + type=00,val=00 ペット孵化時に送られてくる。ペット認識用? + type=01 親密度変化 + type=02 満腹度変化 + type=03 アクセサリ変化(0で未装備) + type=04 パフォーマンス 確認されたval=1~3 + (4はスペシャルパフォーマンス?) + type=05 ?確認されたval=0x14 +S 01a5 <pet name>.24B + ペットの名前決め +R 01a6 <len>.w <index>.w* + ペットの卵リスト +S 01a7 <index>.w + ペットの卵リストが選択された +S 01a9 <emotion>.l + ペットエモーション送信 +R 01aa <ID>.l <emotion>.l + ペットエモーション受信 + <emotion> + 33以下のとき:エモーション + 34以上のとき:発言テーブル? +R 01ac <object id>.l + アンクルの発動(≠設置)時のみ毎回出現(機能は謎) +R 01ad <len>.l <item>.w + 矢作りの作成可能ITEM表受信 +S 01ae <itemID>.w + 矢作りで使う材料送信 +S 01af <type>.w + チェンジカート(カート選択) + type=1 ノーマルカート +R 01b0 <monster id>.l <?>.b <new monster code>.l + 油のクラスチェンジ + <new monster code>はチェンジ後のコード(1001〜)をdwordで +S 01b2 <len>.w <message>.80B <flag>.B {<index>.w <amount>.w <value>.l}.8B* + 露店開設 + flag : 0=キャンセル , 1=オープン +R 01b3 <filename>.64B <type>.B + R 0145の上位互換 +R 01B6 <guildID>.l <guildLv>.l <connum>.l <定員>.l <Avl.lvl>.l <now_exp>.l <next_exp>.l <上納ポイント>.l <性向F-V>.l <性向R-W>.l <members>.l <guild name>.24B <guild master>.24B <agit?>.20B + ギルド情報 +R 01b9 <ID>.I + 被ダメ等によるIDの詠唱中断 +R 01c4 <index>.w <amount>.l <itemID>.w <item data>.12B + カプラ倉庫アイテム +R 01c8 <index>.w <item ID>.w <ID>.l <amount left>.w <type>.B + アイテム使用応答。(00a8の上位バーション?) + type=00の場合使用失敗? amountもゴミの模様 + type=01の場合成功で、amountは使用後の残り個数 +R 01c9 <dst ID>.l <src ID>.l <X>.w <Y>.w <type>.B <fail>.B ?.81b + スキル効能地作成(011fの上位バーション?) + type 0x7e:SW、0x7f:火壁、0x80 ポタ開き中、0x81 ポタ開き直前 + 0x82 聖体、0x83 サンク、0x84 マグヌス、0x85 ニューマ + 0x86 0x86 大魔法(SG/MS/LoV/GX)、0x87 ファイヤーピラ待機 + 0x88 ファイヤーピラ爆発、0x87〜0x8B 表示無し、 + 0x8c トーキーボックス(発動中)、0x8D アイスウォール + 0x8E クワグマイア、0x8f ブラストマイン、0x90 スキッド + 0x91 アンクル、0x92 ベノムダスト、0x93 ランドマイン + 0x94 ショックウェーブトラップ、0x95 サンドマン + 0x96 フラッシャー、0x97 フリージングトラップ + 0x98 クレイモアートラップ、0x99 トーキーボックス + 0x9A ボルケーノ、0x9B デリュージ、0x9C バイオレントゲイル + 0x9D ランドプロテクター、0x9E Zenyマーク、0x9F Zeny袋 + 0xA0 回る緑の輪、0xA1 ピンクの音符 (二連符有り + 0xA2 真ん中に点のある光の玉、0xA3 ピンクのスプリング + 0xA4 深淵の中に、0xA5 回る青い輪、0xA6 不協和音 + 0xA7 口笛、0xA8 夕陽のアサシンクロス、0xA9 ブラギの詩 + 0xAA イドゥンの林檎、0xAB 自分勝手なダンス、0xAC ハミング + 0xAD 私を忘れないで…、0xAE サービスフォーユー + 0xAF ピンクのスプリング、0xB0 表示無し + 0xB0 グラフィティ, + 0xB1 デモンストレーション、0xB2〜0xBF 表示無し + 0xB2 ピンクのワープポータル風 + 0xB3 小さな十字架がふよふよ + 0xB4 バジリカ、0xB5 エフェクトなし? + 0xB6 黒い×が立体的に浮かび上がる + 0xB7 クモの巣、0xB8〜 エフェクトなし? + + 他情報求む + ?.81bは謎。 +R 01cd (<sid>.l)x7 + オートスペル選択肢受信 + <sid>x7 には NB,CB,FB,LB,SS,FBL,FD の順でスキルコードがdwordで入る + まだ選択できないスキルの部分は <sid> = 0x00000000 が入る +S 01ce <sid>.l + オートスペル選択肢送信 +R 01cf <crusader id>.l <target id>.l <?>.18b + 献身状態ターゲットON/OFF。献身が切れると <target id> が 0x00000000 になる + +R 01d0 <ID>.l <num>.w + <num> : 気功の数(非Lv) +R 01d1 <monk id>.l <target monster id>.l <bool>.l + 白羽取り状態ON/OFF。<bool> は白刃取り成立時に 0x00000001 解除時に 0x00000000 が来る +R 01d2 <id>.l <delay>.l + モンクのコンボディレイ(msec) + 三段・連打は基本ディレイ1000(+300)、猛龍は基本ディレイ700(+300) +R 01d4 <ID>.l + 文字列入力窓表示(IDはNPCのIDが入る) +S 01d5 <len>.w <ID>.l <input>.?B 00 + 文字列入力内容送信(IDはNPCのIDが入る) +R 01d7 <ID>.l <equip point>.b <item id1>.w <item id2>.w + 装備グラフィック <equip point> は 02手と09足のみ確認。id2は左手 +R 01d8 <ID>.l <speed>.w <opt1>.w <opt2>.w <option>.w <class>.w <hair>.w <item id1>.w <item id2>.w <head option bottom>.w <head option top>.w <head option mid>.w <hair color>.w ?.w <head dir>.w <guild>.l <emblem>.l <manner>.w <karma>.B <sex>.B <X_Y_dir>.3B ?.B ?.B <sit>.B <Lv>.B ?.B + マップロード時&移動時用、向き付き用キャラ情報?(0078の上位バージョン) +R 01d9 <ID>.l <speed>.w <opt1>.w <opt2>.w <option>.w <class>.w <hair>.w <item id1>.w <item id2>.w.<head option bottom>.w <head option top>.w <head option mid>.w <hair color>.w ?.w <head dir>.w <guild>.l <emblem>.l <manner>.w <karma>.B <sex>.B <X_Y_dir>.3B ?.B ?.B <Lv>.B ?.B + テレポ等の表示範囲内沸きキャラ用、向き付き無しキャラ情報?(0079の上位バージョン) +R 01da <ID>.l <speed>.w <opt1>.w <opt2>.w <option>.w <class>.w <hair>.<item id1>.w <item id2>.w <head option bottom>.w <server tick>.l <head option top>.w <head option mid>.w <hair color>.w ?.w <head dir>.w <guild>.l <emblem>.l <manner>.w <karma>.B <sex>.B <X_Y_X_Y>.5B ?.B ?.B ?.B <Lv>.B ?.B + 表示範囲内キャラ移動情報(007bの上位バージョン) +S 01db + 暗号化key要求 +R 01dc <len>.w <key>.?B + 暗号化key送付 +S 01dd <version>.l <account name>.24B <md5 binary>.16B <version2>.1B + id&暗号化済みpass送信 + 順にクライアントが01dbを送る、 + 鯖が01dcでkeyを返す、 + クライアントが"<key><password>"についてmd5計算し + <md5 binary>の所を埋めて01ddを送る。 + <passwordencrypt2>の時は + "<key><password>"に対してmd5計算としている所を + "<password><key>"と変更する +R 01de <skill ID>.w <src ID>.l <dst ID>.l <server tick>.l <src speed>.l <dst speed>.l <param1>.l <param2>.w <param3>.w <type>.B + 攻撃系スキルエフェクト@(0114の上位バーション?) + type=04 火壁で観測 type=06とほぼ同じ? + type=05 NB/FBlの分散したダメージ用? + type=06 単発もの? param1はダメージ合計、param2はlevel、param3は1固定と予想 + type=07 ダメージ表示無し? + type=08 連打もの? param1はダメージ合計、param2はlevel、param3は分割数と予想 + type=09 ダメージモーションなしにダメージだけ表示される物(インデュア)と思ったのだがダメージモーションが出る物。(機能は謎) +S 01df <ID>.| + GM右クリックによるIDのチャット禁止回数参照? +R 01e1 <ID>.l <num>.w + <num> : 気功の数(非Lv) 一度表示したら後どんなnumが来ても無視される。 +R 01e6 <partner name>.24B + 結婚スキルあなたに逢いたい使用時の叫び声 +S 01e7 + スパノビで/doridoriしたら飛んでくる。SPR回復量2倍フラグを立てるパケット +S 01e8 <party name>.24B <item1>B <item2>B + <item1>アイテム収集方法。0で個人別、1でパーティ公有 + <item2>アイテム分配方法。0で個人別、1でパーティに均等分配 + (00f9の上位バーション) +R 01ea <ID>.l + 結婚エフェクト(音楽、紙吹雪) + IDは新婦のものが入る? +S 01ed + スパノビが爆裂波動になるフラグを立てるパケット +R 01ee <len>.w {<index>.w <item ID>.w <type>.B <identify flag>.B <amount>.w ?.2B <card>.4w}.18B* + 所有消耗品&収集品リスト + 矢の場合は?.2Bが0x8000になる + 00a3から変更 +R 01ef <len>.w {<index>.w <item ID>.w <type>.B <identify flag>.B <amount>.w ?.2B <card>.4w}.18B* + カート内アイテム。消耗品/収集品 + 0123から変更 +R 01f0 <len>.w {<index>.w <item ID>.w <type>.B <identify flag>.B <amount>.w ?.2B <card>.4w}.18B* + カプラさんに預けてある消耗品&収集品リスト + 00a5から変更 +R 01f4 <name>.24B <trade id?>.L <LV>.w + 先方から取引要請 + 00e5から変更 +R 01f5 <result>.B <trade id?>.L <LV>.w + こちらからの取引要請に対する反応 + 00e7から変更 +S 0200 <login name>.24B + ragexeに/accountオプションをつけて起動するとログイン要求に付加されるパケット +R 0201 ?.1B<Flag?>.1B (?.8B <Character Name>.24B)x20 + Flag seems to need to be 1 to function. + 8 unknown bytes and 24 bytes for name need to be repeated for each friend. +S 0202 <Character name>.24B + Character name to add to friend list (for server-side friend list enabled clients) +S 0204 <?>.16B + ログイン要求に付加されるパケット。16バイトは固定? +S 020B <?>.17B + キャラクタサーバ接続要求0065に付加されるパケット。1+0204の16バイトで17バイト? + + +パケット長の追加。019e〜01aaが増えてるので、0190〜を切り抜き +added packet lenth. 019e-01aa is a new, so here is a packet length table from 0190. + + 90, 86, 24, 6, 30,102, 8, 4, 8, 4, 14, 10, -1, 6, 2, 6, + 3, 3, 35, 5, 11, 26, -1, 4, 4, 6, 10 diff --git a/doc/readme.html b/doc/readme.html new file mode 100644 index 0000000..3f87c20 --- /dev/null +++ b/doc/readme.html @@ -0,0 +1,163 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html> +<head> + <title>eAthena 1.0 RC5 Readme</title> + <meta http-equiv="expires" content="-1"> + <meta http-equiv="pragma" content="no-cache"> + <meta name="author" content="eAthena Development Team"> + <meta name="description" content="eAthena, ro server emulator"> + <meta name="keywords" + content="eathena, ro, ragnarok online, server emulator, server software"> + <link href="./readme/rc5.css" media="screen" rel="stylesheet" + type="text/css"> +</head> +<body> +<table class="wrapper"> + <tbody> + <tr class="topbanner"> + <td class="topnav"><b><a href="./readme.html">Intro</a></b></td> + <td class="topnav"><b><a href="./readme/changelog.html">Changelog</a></b></td> + <td class="topnav"><b><a href="./readme/features.html">Features</a></b></td> + <td class="topnav"><b><a href="./readme/Npcfeatures.html">Npc Features</a></b></td> + <td class="topnav"><b><a href="./readme/settingup.html">Setting Up</a></b></td> + <td class="topnav"><b><a href="./readme/gmcommands.html">GM Commands</a></b></td> + <td class="topnav"><b><a href="./readme/faq.html">FAQ</a></b></td> + <td class="topnav"><b><a href="./readme/support.html"> Support</a></b></td> + </tr> + <tr> + <td class="banner" colspan="8"> + <div align="right"><img src="./readme/midbanner.jpg" border="0"></div> + </td> + </tr> + <tr> + <td class="omgspacer"><br> + </td> + </tr> + <tr> + <td colspan="8" align="center"> + <table class="content"> + <tbody> + <tr> + <td class="contenttitle"><b>Thank you for downloading +eAthena 1.0.0 RC5</b></td> + </tr> + <tr> + <td class="contentbg">eAthena is an emulator for Ragnarok +Online servers. +It's written in C, and is +cross-platform. The officially supported platforms are Win32 and Linux. + <p>We hope you enjoy using it!</p> + <p>eAthena is licensed under the GPL, meaning it's open +source :) There +is no public CVS provided, however.</p> + </td> + </tr> + </tbody> + </table> + </td> + </tr> + <tr> + <td class="omgspacer"><br> + </td> + </tr> + <tr> + <td colspan="8" align="center"> + <table class="content"> + <tbody> + <tr> + <td class="contenttitle"><b>The eAthena Development Team</b></td> + </tr> + <tr> + <td class="contentbg">eAthena is based on the original +Athena, by the +Japanese Development Team.<br> +A big thanks to them for working hard on Athena.<br> + <br> +Valaris<br> +Mugendai<br> +fritz<br> +Wizputer<br> +Evera<br> +Nana<br> +Shinigami<br> +Darkchild<br> +Lupus<br> +moonsoul<br> +MouseJstr<br> +kobra_k88<br> +Codemaster<br> +Davidsiaw<br> +Mass Zero<br> +MC_Cameri<br> +Spira<br> +Lord<br> + <p></p> + </td> + </tr> + </tbody> + </table> + </td> + </tr> + <tr> + <td class="omgspacer"><br> + </td> + </tr> + <tr> + <td colspan="8" align="center"> + <table class="content"> + <tbody> + <tr> + <td class="contenttitle"><b>Special Thanks</b></td> + </tr> + <tr> + <td class="contentbg">The eAthena development team would +like to specially +thank:<br> + <br> +The Japanese Development Team. A big thanks to them for +working hard on Athena which eAthena is based on.<br> + <br> +RoVeRT, our ex-developer. eAthena wouldn't have this many +features without him!<br> + <br> +sara-chan, who had also contributed alot to the development +of eAthena! Thanks!<br> + <br> +AppleGirl, who lead the team and contributed alot of work.<br> + <br> +yor, for his work he did on login and char servers, not to mention +ladmin.<br> + <br> +Gravity, for making Ragnarok Online. eAthena wouldn't even +exist if it wasn't for them!<br> + <br> +ASB for hosting our forum section :)<br> + <br> +DeltaAnime for hosting our official forums among other things :)<br> + <br> +Ketonom for putting together a nice Win32 installer for the noobs and +converting mob and item databases to sql.<br> + <br> + <br> + <br> + <br> + <br> +And others who gave continuously endless support to eAthena!<br> + </td> + </tr> + </tbody> + </table> + </td> + </tr> + <tr> + <td class="spaceromg"><br> + </td> + </tr> + <tr> + <td class="copyright" colspan="8">© Copyright 2004 eAthena +Development Team<br>Design © Copyright 2004 Evera</td> + </tr> + </tbody> +</table> +</body> +</html> diff --git a/doc/readme.txt b/doc/readme.txt new file mode 100644 index 0000000..59c7eb7 --- /dev/null +++ b/doc/readme.txt @@ -0,0 +1,122 @@ +*************************** NOTICE ***************************
+**************************************************************
+
+This program is free to use for any non-commercial private RO
+server running an Athena based server, and is povided as-is.
+The author of this program is only responsible for errors
+that occur only to this program while it is in operation,
+provided the program has not been modified in any way. If you
+come across any errors or would like to leave comments and/or
+suggestions about this program, please feel free to notify the
+author of this program at spamrat42@gmail.com.
+
+Please make sure to create a backup of your current database
+files and your data folder prior to using this program.
+
+==============================================================
+
+This package contains the following files...
+
+ADE.ini
+Athena Database Editor.exe
+history.txt
+ID Helper.txt
+readme.txt
+
+sample data (empty folder)
+
+sample db (folder)
+ card_labels.txt
+ item_descriptions.txt
+
+sample mobs (empty folder)
+
+==============================================================
+= NOTE: All packages from 1.4.1 and newer will not include the
+= files from the latest eAthena server distribution. So
+= make sure to copy your current files from your server
+= into the appropriate folders listed above, or set the
+= correct paths in the ADE.ini file.
+==============================================================
+
+Thank you for downloading this tool I wrote for the eAthena
+817 Text based server, but it should work even with the more
+recent versions like eAthena 1.0 RC5. I hope you find this
+tool to be useful.
+
+The files in the db folder that come with this program are
+custom files that are to be used with ADE. To change the
+directory that this program looks in when opening or saving
+files, open the ADE.ini file and enter the full or relative
+path. The ADE.ini when first installed will already be
+pointing to the relative folders listed above. You can also
+simply copy your current db files to those folders, allowing
+you to edit them without editing the db files you're
+currently using.
+
+All entries that have been commented out will not be read,
+and won't be carried over into the new DB files. So please
+make sure to back them up before using this program for the
+first time. I will eventually add support for handling
+commented lines in future versions.
+
+One of the main features of this program will let you create
+updated files for the client's data folder as well as update
+the other relative db files for the server. When using this
+feature, the following files will be created...
+
+DB Folder - Server Files
+================================
+class_equip_db.txt
+item_value_db.txt
+
+Data Folder - Client Files
+================================
+cardpostfixnametable.txt
+cardprefixnametable.txt
+idnum2itemdesctable.txt
+idnum2itemdisplaynametable.txt
+itemslotcounttable.txt
+num2itemdesctable.txt
+num2itemdisplaynametable.txt
+
+Note: There is a checkbox that will say to click it if the
+ descriptions are garbled. Please only check after
+ trying the default description files it produces.
+
+If there are any files you think that were left out
+that should have been updated as well for the client's
+data folder, please let me know at the email address
+provided above.
+
+The item_descriptions.txt file is where all of the
+descriptions are stored. Please note if you're manually
+changing something in it, that you should the サ character
+for a new line. (Use ALT + 175 to type it.) It should
+currently have the majority of the basic descriptions.
+You won't need to add how much defense or the attack
+power of equipment, as it will automatically be added
+when the files are created. (It gets the values directly
+from the item db files.)
+
+The card_labels.txt file is where all the card labels are
+stored. If you already have a custom set of card labels,
+just copy the cardprefixnametable.txt file from your data
+folder into the db folder and rename it to card_labels.txt.
+Any missing labels will or blank labels will result in the
+default label of "Carded" as a prefix.
+
+I will eventually try to update this program to try
+including other db files as well as to help make sure
+it works with all versions of the eAthena text based
+server from 817 to the current version. I'll also try
+to add more support for other files that are also needed
+to be generated for the client's data folder.
+
+==============================================================
+= NOTE 2: The item_descriptions.txt file that comes with this
+= package has incorrect and blank entries. Please make
+= sure to go over all the descriptions if you plan on
+= using it to generate description files for your
+= server's data folder.
+==============================================================
diff --git a/doc/readme/changelog.html b/doc/readme/changelog.html new file mode 100644 index 0000000..4cae56a --- /dev/null +++ b/doc/readme/changelog.html @@ -0,0 +1,115 @@ +<html> +<head><title>eAthena 1.0 RC5 Readme</title> +<meta http-equiv="expires" content="-1" /> +<meta http-equiv="pragma" content="no-cache" /> +<meta name="author" content="eAthena Development Team" / +<meta name="description" content="eAthena, ro server emulator" /> +<meta name="keywords" content="eathena, ro, ragnarok online, server emulator, server software" /> +<link href="./rc5.css" media="screen" rel="stylesheet" type="text/css"> +</head> +<body> +<table class="wrapper"> + <tr class="topbanner"> + <td class="topnav"><b><a href="../readme.html">Intro</a></b></td> + <td class="topnav"><b><a href="./changelog.html">Changelog</a></b></td> + <td class="topnav"><b><a href="./features.html">Features</a></b></td> + <td class="topnav"><b><a href="./Npcfeatures.html">Npc Features</a></b></td> + <td class="topnav"><b><a href="./settingup.html">Setting Up</a></b></td> + <td class="topnav"><b><a href="./gmcommands.html">GM Commands</a></b></td> + <td class="topnav"><b><a href="./faq.html">FAQ</a></b></td> + <td class="topnav"><b><a href="./support.html"> Support</a></b></td> + </tr> + <tr> + <td class="banner" colspan="8"> + <div align="right"><img src="./midbanner.jpg" border="0"></div> + </td> + </tr> + <tr> + <td class="omgspacer"></td> + </tr> + <tr> + <td align="middle" colspan="8"> + <table class="content"> + <tr> + <td class="contenttitle"><b>eAthena Changelog</b></td> + </tr> + <tr> + <td class="contentbg"> +This is what has changed from eAthena 1.0.0 RC1 -> +eAthena 1.0.0 RC5 +</td> + </tr> + </table> + </td> + </tr> + <tr> + <td class="omgspacer"></td> + </tr> + <tr> + <td align="middle" colspan="8"> + <table class="content"> + <tr> + <td class="contenttitle"><b>Detailed Changelog</td> + </tr> + <tr> + <td class="contentbg"> +<font face="Courier New" size="2"> +<pre> .ZWBBBBBBBBBBBBBBBB0WWWWWWWWW@@@WW@WWWWW@M00Wi + 2MMMWBBBBBBBBWWWWWWWM@@WBBB808Z8ZZZ8Z88Z88Z800WMM; + MMMWBBBWBWWWW@BBW0088088aZZ888W00B0B0888ZZ8Z80800@MX + WMMWWWWWW@@WB08aZZZ80WM0ZMMM@WWW@@M@WW@W@@W@@WW088880MW + :MMM@@@WB08Zaa80BW@M@W@M0riSMMWBWBBM;MMW0WWB@B0WW@@WMMW0@M + 7MMWB0ZZZZZ8BW@WWWBW0WBM0SZ; 2MM@BBBM@ @MW0B0WMB0BW0W0MMMMM@ + 7M@Z888Z80BWMWWBBB@WWWB@M.M7 ;@M@WBWM0 BM@BB0MMW8BBW0@@WWMM0 + M@800Z0W@WWBWBWWB@M7MW@Mr:M . WMMWWWM MMMB0WM@@0WB0B@@WW@Mr + MM880W@WWBBBW@0BBBM2;MBMBX.W ,, .MMWBMZ :ZMMB@8@M@BMB8B@WWWMM. + 2M80BWWWWWBB@ZWBBB@M ;MWMra M .,,,. MMMMM X8MM@@2MMWWMB0@W@WWMM + MMW@WBWWBBBBMiM0WBMB 7MM0r M .,,,.,.; rMMM; XMMM;MMW@@@BBW@WW@M2 + MMWBBB@BWWW@8:M@WMMr iMMWr M .,,,,,. ;MM0 2MMi MMW@WW@WWWWWMM. + M@BWBB@0WBBMZZM0WM7 MMi M, ,..,,,., MMMMMMMMMX rM@WWWW@WW@WWMM + MBBBBBWBBBMM. MBMZ MM . 7S ,,,,,,,, MMMMMMMMa,MMM 2MWWWW@WWWWWWMM + SMBBBBBWBWBMS MWM0MMMMMM ,.:W ,,:,,,,: M@BBBB@M8 MMMiMM@MWWW@WWWWWMB + MMWBBWBWBW@M, MMZWMMWMMW ,. . :.,,,,., .M0WWWBBWMS aM;MZM@WW@WW@WWMMi + M@BBWB0MBBMB MMMaMWB0BWM ,,,.,.,,,,,,: BM0@@@WWBMM aMM;MM@W@W@WWWWMM. + MBWBWB0MWWM ;M2 MMBBWWBBMM .,,,,,,,.,,,i BM0W@@MWWBM@ MZ ZMWWWWWWWWWWMM + M0WBBB0M8WM.MZ MWWWM@MWBM :,.,,.:,,,.: iM0W@W@@BWMM M2 ZM@WWW@WWW@WWMM + M0WBBB0MB0MMM MBBWMBBW0MM ::,,:.,.,,,, MM@MM@@BWMM MX.a@M@WWWWBWWWW@Mi + M0BBWB0@M MMZ MWWWMWW@0MM :,..,.,,,,,,, MMMMMM@a8MM M2:ZiMMWWWWWWWWWWMM + MBWBWBBBM MZ MMMMMM@M0@M i:,,,.,,,.,,: M88MMi M 7MrWMWWWWWWWWWWWW + MWBBB0BBM, MM MM,,:MMW8MM :.,,,.,,,,,,.: 2;.SM0@M8 20ZrMMWWWWWWWWW@W + M@BBWBW0MM MM .@8aMB ,,..,.,,,..,,,. XMMMMMM. i:, .2BM.MMWWWWWWWWWWW + MMBBW0B0@M MM :MMMMMMMMr ,,,,,. .,,..,. .,, a@7:rMW@WWWWWWWWW + WMBB0BWB0Mi X ZMMMM2 .,,.. aS: .,,..,..,...,.,,,, 2@r;.M0MWWWWWWWWW + ,MBBBBBB0MM ,:, i ..,,,,, B78r..:,.,,.,,,,,...,...aMi,0Ma@WWWWWWWWW + MWBBBBBB@M .,,,.. .....,,,,,. ..,,.,,.,.,,,,::,, .Z@ ZMM8B@WWWWWWWW + M@BBB000BMB ,,.,.....,,.,.,,,....,..,,....,..,,,.,,.,:. :a28MM@W0@@WWWWWWW + MMWBBBWB8MM ..,.,:,...,:..,,,,,..,,...;:.,,,.,:.,,.., iWMM@W@@0B@WWWWWWW + 0M0BBBBBBBM; .,,.,,.,,,,,..,,,. eAthena .,,,,,,,,,.,. iaMMWWW@MB8@@WWWWWW + MWBBBBBB0MM .,.,,.,,,,,.,,.,,. 1.0.0 ..,.,,,,.,:, iBMMWWWWW@WBBMWWWWWW + MM0WBBBBBBM@ .,,,,,..,,,.,,,,, RC5,.,.:,,,,:,., 8MMMWWWWW@WW00@WWWWWW + ZM0WBBBBBZMMM ...,,,,.,,,,,..,.,,,.:,.,:. ZMMMWBW@WWWWWMW8W@@WWWW + M@WBWBBB0WMMMMM, ,..,.,,,,,,,,,,,,,,,. .MMMM@W@@WW@@W@BW@0B@@WWWW + 8MBWBBWBB0@W@MMMMMB: ..,,i.,,:,,,... rMMMMWWWWW@@WWWWMBMMB8W@@@WW + MMBBWBBWBW@WWWW@MMMMMW; .,.,.. WMM,MWWW@WWWMWWM@@@@W@W0BMBWWW + 8MB0@B0WB0@@@W@B@W@@MMMMMMM2. BMMZ7r,MWW@@W@BWMXMM@@WWW@B0MW@WW + MM0WM00W8W@@B@WWWWWWWWMM SWMMMMWBW0MM877XSSiMW@WWWWWWM2 MMWWWW@00WWWWW + 2@B0MWBWB0WWWWWWWWWWWWWMS77rrXX222SX7XXSSSX;MB@WW@WWWMM MWWWW@WBB@WWW</pre> +</font> +<br> +<br> +<iframe src="../Changelog.txt" name="Changelog" + title="eAthena Changelog" marginwidth="10" marginheight="10" border="0" + allowtransparency="true" frameborder="0" height="500" width="100%"></iframe></font> +</td> + </tr> + </table> + </td> + </tr> + <tr> + <td class="spaceromg"></td> + </tr> + <tr> + <td class="copyright" colspan="8">© Copyright 2004 eAthena Development Team<br>Design © Copyright 2004 Evera</td> + </tr> +</table> +</body>
\ No newline at end of file diff --git a/doc/readme/faq.html b/doc/readme/faq.html new file mode 100644 index 0000000..2cc1a2c --- /dev/null +++ b/doc/readme/faq.html @@ -0,0 +1,146 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html> +<head> + <title>eAthena 1.0 RC5 Readme</title> + <meta http-equiv="expires" content="-1"> + <meta http-equiv="pragma" content="no-cache"> + <meta name="author" content="eAthena Development Team"> + <meta name="description" content="eAthena, ro server emulator"> + <meta name="keywords" + content="eathena, ro, ragnarok online, server emulator, server software"> + <link href="./rc5.css" media="screen" rel="stylesheet" type="text/css"> +</head> +<body> +<table class="wrapper"> + <tbody> + <tr class="topbanner"> + <td class="topnav"><b><a href="../readme.html">Intro</a></b></td> + <td class="topnav"><b><a href="./changelog.html">Changelog</a></b></td> + <td class="topnav"><b><a href="./features.html">Features</a></b></td> + <td class="topnav"><b><a href="./Npcfeatures.html">Npc Features</a></b></td> + <td class="topnav"><b><a href="./settingup.html">Setting Up</a></b></td> + <td class="topnav"><b><a href="./gmcommands.html">GM Commands</a></b></td> + <td class="topnav"><b><a href="./faq.html">FAQ</a></b></td> + <td class="topnav"><b><a href="./support.html"> Support</a></b></td> + </tr> + <tr> + <td class="banner" colspan="8"> + <div align="right"><img src="./midbanner.jpg" border="0"></div> + </td> + </tr> + <tr> + <td class="omgspacer"><br> + </td> + </tr> + <tr> + <td colspan="8" align="center"> + <table class="content"> + <tbody> + <tr> + <td class="contenttitle"><b>Frequently Asked Questions</b></td> + </tr> + <tr> + <td class="contentbg"><span style="font-weight: bold;">Q</span>: +I get the error message +"This application has failed to start because cygwin1.dll was not +found. Re-installing the application may fix this problem."<br> + <span style="font-weight: bold;">A</span>: You're missing +the cygwin +dlls. Please get the latest dll at: http://www.cygwin.com/snapshot. If +you're unsure, asking around in our IRC chatroom will get you around +too, but always remember - use common sense and search before asking.<br> + <br> + <span style="font-weight: bold;">Q</span>: My map-server +won't load! It +appears to be loading things before it dissapeared suddenly! HELP!<br> + <span style="font-weight: bold;">A</span>: Use command line +to load +map-server. It should tell you the error. If you're missing a map, +update your kRO Sakray or comment the map from map_athena.conf. If you +have an errornous NPC, fix it or comment it off. Anything other than +that, feel free to ask around.<br> + <br> + <span style="font-weight: bold;">Q</span>: My map-server +failed to load +'adata.grf'! Where do I find this adata.grf? My map-server won't load +without it!<br> + <span style="font-weight: bold;">A</span>: The error that +caused the +map-server to not load is not the adata.grf. adata.grf is NOT a +requirement for the map-server to load. The error is probably related +to something else.<br> + <br> + <span style="font-weight: bold;">Q</span>: All 3 of the +servers are +loaded, but I am still having problems accessing it! What do I do?<br> + <span style="font-weight: bold;">A</span>: First, check the +IPs in +map_athena.conf and char_athena.conf. If those are correct, check the +ports to make sure they match. If that's correct too, you probably +cannot handle the server load. Lower the monster spawning rate using +mob_count in battle_athena.conf and it should be fine.<br> + <br> + <span style="font-weight: bold;">Q</span>: How do I start +Guild +Wars/War of Emperium??? HELP!!!<br> + <span style="font-weight: bold;">A</span>: Read the GM +Command page for +full list of commands that GMs can use, including the command for this.<br> + <br> + <span style="font-weight: bold;">Q</span>: My Ragnarok +Online crashed +while playing with eAthena! What do I do now?<br> + <span style="font-weight: bold;">A</span>: Well, if your +Ragnarok +crashes, it's most probably not anything to do with eAthena. Something +is wrong with your Ragnarok installation. Try reinstalling or updating.<br> + <br> + <span style="font-weight: bold;">Q</span>: Is eAthena +compatible with +mySQL? Can I use mySQL as the DB instead of using text files?<br> + <span style="font-weight: bold;">A</span>: Yes, eAthena is +compatible +with mySQL. A tutorial on how to setup this is coming soon.<br> + <br> + <span style="font-weight: bold;">Q</span>: Is eAthena +compatible with +msSQL? Can I use msSQL as the DB instead of using text files?<br> + <span style="font-weight: bold;">A</span>: No, eAthena is +not +compatible with msSQL. You cannot use msSQL with eAthena.<br> + <br> + <span style="font-weight: bold;">Q</span>: I found a bug! +Where do I +report it?<br> + <span style="font-weight: bold;">A</span>: Drop the +developers a line +at the IRC chatroom. Or just post it in the bug report forum. We check +them out too. :)<br> + <br> + <span style="font-weight: bold;">Q</span>: I know alot of C +and I'm +able to help improve eAthena and add new features. How can I join your +development team?<br> + <span style="font-weight: bold;">A</span>: Try talking to +one of the +current developers in the eAthena channel.<span + style="font-weight: bold;"></span><span style="font-weight: bold;"></span><br> + </td> + </tr> + </tbody> + </table> + </td> + </tr> + <tr> + <td class="spaceromg"><br> + </td> + </tr> + <tr> + <td class="copyright" colspan="8">© Copyright 2004 eAthena +Development Team<br> +Design © Copyright 2004 Evera</td> + </tr> + </tbody> +</table> +</body> +</html> diff --git a/doc/readme/features.html b/doc/readme/features.html new file mode 100644 index 0000000..8a448bd --- /dev/null +++ b/doc/readme/features.html @@ -0,0 +1,239 @@ +<html> +<head><title>eAthena 1.0 RC5 Readme</title> +<meta http-equiv="expires" content="-1" /> +<meta http-equiv="pragma" content="no-cache" /> +<meta name="author" content="eAthena Development Team" / +<meta name="description" content="eAthena, ro server emulator" /> +<meta name="keywords" content="eathena, ro, ragnarok online, server emulator, server software" /> +<link href="./rc5.css" media="screen" rel="stylesheet" type="text/css"> +</head> +<body> +<table class="wrapper"> + <tr class="topbanner"> + <td class="topnav"><b><a href="../readme.html">Intro</a></b></td> + <td class="topnav"><b><a href="./changelog.html">Changelog</a></b></td> + <td class="topnav"><b><a href="./features.html">Features</a></b></td> + <td class="topnav"><b><a href="./Npcfeatures.html">Npc Features</a></b></td> + <td class="topnav"><b><a href="./settingup.html">Setting Up</a></b></td> + <td class="topnav"><b><a href="./gmcommands.html">GM Commands</a></b></td> + <td class="topnav"><b><a href="./faq.html">FAQ</a></b></td> + <td class="topnav"><b><a href="./support.html"> Support</a></b></td> + </tr> + <tr> + <td class="banner" colspan="8"> + <div align="right"><img src="./midbanner.jpg" border="0"></div> + </td> + </tr> + <tr> + <td class="omgspacer"></td> + </tr> + <tr> + <td align="middle" colspan="8"> + <table class="content"> + <tr> + <td class="contenttitle"><b>eAthena Completed Features</b></td> + </tr> + <tr> + <td class="contentbg"> +<ul> + <li> PvP (Player VS Player)</li> + <li> GvG (Guild VS Guild)</li> + <li> qPets (Cute Pets)</li> + <li> Monster skills</li> + <li> 2-2 Jobs (Alternate 2nd Jobs)</li> + <li> SuperNovice (Alternate 1st Job)</li> + <li> WoE (War of Emperium)</li> + <li> Remote administration of accounts (ladmin softwares)</li> + <li> Weddings</li> + <li> Pet equipped mobs.<br> + </li> + <li> Management of day/night.</li> + <li> Mob Disguises</li> + <li> Weather and other special effects.<br> + </li> +</ul> +eAthena 1.0.0 RC5 +</td> + </tr> + </table> + </td> + </tr> + <tr> + <td class="omgspacer"></td> + </tr> + <tr> + <td align="middle" colspan="8"> + <table class="content"> + <tr> + <td class="contenttitle"><b>eAthena Features - Estimated completion rate (Detailed) </b></td> + </tr> + <tr> + <td class="contentbg"> +<ul> + <li>Novice Skills - 100% (3/3)</li> + <li>1st Job Skills - 100%</li> + <ul> + <li> Swordsman - 100% (10/10)</li> + </ul> + <ul> + <li> Acolyte - 100% (16/16)</li> + </ul> + <ul> + <li> Archer - 100% (7/7)</li> + </ul> + <ul> + <li> Magician - 100% (14/14)</li> + </ul> + <ul> + <li> Merchant - 100% (10/10)</li> + </ul> + <ul> + <li> Thief - 100% (10/10)</li> + </ul> + <li>2-1 Skills (2nd Job Skills) - 100%</li> + <ul> + <li> Knight - 100% (10/10)</li> + </ul> + <ul> + <li> Priest - 100% (18/18)</li> + </ul> + <ul> + <li> BlackSmith - 100% (21/21)</li> + </ul> + <ul> + <li> Wizard - 100% (13/13)</li> + </ul> + <ul> + <li> Assassin - 100% (10/10)</li> + </ul> + <ul> + <li> Hunter - 100% (17/17)</li> + </ul> + <li>2-2 Skills (Alternate 2nd Job Skills) - 91%</li> + <ul> + <li> Crusader - 100% (18/18)</li> + </ul> + <ul> + <li> Monk - 100% (15/15)</li> + </ul> + <ul> + <li> Alchemist - 63% (14/22)</li> + </ul> + <ul> + <li> Sage - 100% (20/20)</li> + </ul> + <ul> + <li> Rogue - 80% Complete:(12/15) Incomplete:(3/15)</li> + </ul> + <ul> + <li> Bard - 100% (16/16)</li> + </ul> + <ul> + <li> Dancer - 100% (16/16)</li> + </ul> + <li>2-1-1 Skills (Advanced 2-1 Job Skills) - 80.5%</li> + <ul> + <li> Lord Knight - 94% - Complete:(7/8) +Incomplete:(1/8)</li> + </ul> + <ul> + <li> High Priest - 66% - Complete:(1/3) +Incomplete:(2/3)</li> + </ul> + <ul> + <li> High Wizard - 100% (4/4)</li> + </ul> + <ul> + <li> Whitesmith - 75% (3/4)</li> + </ul> + <ul> + <li> Sniper - 88% - Complete:(3/4) Incomplete:(1/4)</li> + </ul> + <ul> + <li> Assassin Cross - 60% (3/5)</li> + </ul> + <li>2-2-1 Skills (Advanced 2-2 Job Skills) - 47%</li> + <ul> + <li> Paladin - 65%</li> + </ul> + <ul> + <li> Champion - 100%</li> + </ul> + <ul> + <li> Professor - 100%</li> + </ul> + <ul> + <li> Stalker - 65%</li> + </ul> + <ul> + <li> Creator - 0%</li> + </ul> + <ul> + <li> Clown - 0%</li> + </ul> + <ul> + <li> Gypsy - 0%</li> + </ul> + <li>Pet Skills - 95%</li> + <ul> + <li> Loot Skills - 100%</li> + </ul> + <ul> + <li> Buff Skills - 100%</li> + </ul> + <ul> + <li> Attack Skills - 90%</li> + </ul> + <ul> + <li> Assist Skills - 100%</li> + </ul> + <li>Equipment Breaking System - 80%</li> + <ul> + <li> Weapon Breaking - 95%</li> + </ul> + <ul> + <li> Armor Breaking - 95%</li> + </ul> + <ul> + <li> Equipment Repair - 50%</li> + </ul> + <li>PK Server Mode - ??%</li> + <ul> + <li> Need more info on this mode to be called complete.</li> + </ul> +</ul> +</td> + </tr> + </table> + </td> + </tr> + <tr> + <td class="omgspacer"></td> + </tr> + <tr> + <td align="middle" colspan="8"> + <table class="content"> + <tr> + <td class="contenttitle"><b>eAthena Incomplete Features</b></td> + </tr> + <tr> + <td class="contentbg"> +<ul> + <li>Advanced Classes (Upper Jobs)</li> + <li>Baby Classes (Baby Jobs)</li> + <li>PK Server Mode<font face="Courier New" size="2"></font></li> +</ul> +eAthena 1.0.0 RC5 +</td> + </tr> + </table> + </td> + </tr> + <tr> + <td class="spaceromg"></td> + </tr> + <tr> + <td class="copyright" colspan="8">© Copyright 2004 eAthena Development Team<br>Design © Copyright 2004 Evera</td> + </tr> +</table> +</body>
\ No newline at end of file diff --git a/doc/readme/gmcommands.html b/doc/readme/gmcommands.html new file mode 100644 index 0000000..80a938c --- /dev/null +++ b/doc/readme/gmcommands.html @@ -0,0 +1,692 @@ +<html> +<head><title>eAthena 1.0 RC5 Readme</title> +<meta http-equiv="expires" content="-1" /> +<meta http-equiv="pragma" content="no-cache" /> +<meta name="author" content="eAthena Development Team" / +<meta name="description" content="eAthena, ro server emulator" /> +<meta name="keywords" content="eathena, ro, ragnarok online, server emulator, server software" /> +<link href="./rc5.css" media="screen" rel="stylesheet" type="text/css"> +</head> +<body> +<table class="wrapper"> + <tr class="topbanner"> + <td class="topnav"><b><a href="../readme.html">Intro</a></b></td> + <td class="topnav"><b><a href="./changelog.html">Changelog</a></b></td> + <td class="topnav"><b><a href="./features.html">Features</a></b></td> + <td class="topnav"><b><a href="./Npcfeatures.html">Npc Features</a></b></td> + <td class="topnav"><b><a href="./settingup.html">Setting Up</a></b></td> + <td class="topnav"><b><a href="./gmcommands.html">GM Commands</a></b></td> + <td class="topnav"><b><a href="./faq.html">FAQ</a></b></td> + <td class="topnav"><b><a href="./support.html"> Support</a></b></td> + </tr> + <tr> + <td class="banner" colspan="8"> + <div align="right"><img src="./midbanner.jpg" border="0"></div> + </td> + </tr> + <tr> + <td class="omgspacer"></td> + </tr> + <tr> + <td align="middle" colspan="8"> + <table class="content"> + <tr> + <td class="contenttitle"><b>eAthena GM Commands</b></td> + </tr> + <tr> + <td class="contentbg"> +The following is a list of all GM Commands in eAthena. +To use them, just type @ in front of the command into the message +window where you usually type to chat. +<p> The @ can be changed by the server admin to another character. +</td> + </tr> + </table> + </td> + </tr> + <tr> + <td class="omgspacer"></td> + </tr> + <tr> + <td align="middle" colspan="8"> + <table class="content"> + <tr> + <td class="contenttitle"><b>GM Command List</b></td> + </tr> + <tr> + <td class="contentbg"> +<font face="Verdana"><span class="GMCommandTitle">ANNOUNCEMENT COMMANDS</span> +<span class="GMCommand">/b <message><br>@broadcast <message></span> +Lets you make a GM global announcement with your name + +<span class="GMCommand">/nb <message><br>@kamu <message></span> +Lets you make a GM global announcement withouth your name + +<span class="GMCommand">@kamib <message><br>/bb <message></span> +Lets you make a GM global announcement withouth your name in blue + +<span class="GMCommandTitle">INFORMATION COMMANDS</span> +<span class="GMCommand">@who [match_text]</span> <br>Lists who is currently online in your server and their location. [match_text] is optional <br><br><span + class="GMCommand">@who2 [match_text] </span> +Lists who is currently online in your server and their job. [match_text] is optional + +<span class="GMCommand">@who3 [match_text]</span> <br>Lists who is currently online in your server and their party/guild. [match_text] is optional<br><br><span + class="GMCommand">@whomap [map]</span> <br>Display a listing of who is online and where in a specifical map<br><br><span + class="GMCommand">@whomap2 [map]</span> <br>Display a listing of who is online and their job in a specifical map<br><br><span + class="GMCommand">@whomap3 [map]</span> <br>Display a listing of who is online and their party/guild in a specifical map<br><br><span + class="GMCommand">@whogm [match_text]</span> <br>Like @who+@who2+who3, but only for GM. [match_text] is optional <br><br><span + class="GMCommand">@where <char name></span> +Tells you the location of a character + +<span class="GMCommand">@charstatsall</span> + Displays stats of all characters. + +<span class="GMCommand">@charitemlist <char name></span> + Displays all items of a player. + +<span class="GMCommand">@charstoragelist <char name></span> + Displays all items of a player's storage. + +<span class="GMCommand">@charcartlist <char name></span> + Displays all items of a player's cart. + +<span class="GMCommand">@time<br>@date<br>@server_date<br>@serverdate<br>@server_time<br>@servertime</span> +Display the date/time of the server + +<span class="GMCommand">@idsearch <part_of_item_name></span> +Search all items that name have part_of_item_name + +<span class="GMCommand">@ignorelist</span> +Displays your ignore list + +<span class="GMCommand">@mapinfo [<1-3> [map]]</span> +Give information about a map (general info +: 0: no more, 1: players, 2: NPC, 3: shops/chat). + +<span class="GMCommandTitle">CONTROL COMMANDS</span> +<span class="GMCommand">@die</span> +Kill yourself :) + +<span class="GMCommand">@alive</span> +Revive yourself from death + +<span class="GMCommand">@kill <char name></span> +Kills specified character name + +<span class="GMCommand"></span><span class="GMCommand">@save</span> +Sets save point as current location + +<span class="GMCommand">@load</span> +Warps you to your save point (a.k.a. butterfly wing) + +<span class="GMCommand">@warp <map name> <x> <y><br>@rura <map name> <x> <y><br>@mapmove <map name> <x> <y><br>/mm <map name> <x> <y><br>/mapmove <map name> <x> <y></span> +Warps you to the selected position +Example: +@warp morocc 150 160 -> Warps you to Morroc (X:150, Y:160) +@rura prontera 50 80 -> Warps you to Prontera (X:50, Y:80) + + +<span class="GMCommand">@rura+ <map name> <x> <y> <char name><br>@charwarp <map name> <x> <y> <char name><br>#banish <map name> <x> <y> <char name></span> +Warps the selected character to the selected position +Example: +@charwarp morocc 150 160 Sora -> Warps the character 'Sora' to Morroc (X:150, Y:160) +@rura+ prontera 50 80 Haley -> Warps the character 'Haley' to Prontera (X:50, Y:80) + +<span class="GMCommand">@jump [x [y]]</span> +Teleports you randomly in the map (a.k.a. fly wing). Or to the specified location with x and y. + +<span class="GMCommand">/shift <char name><br>@jumpto <char name><br>@warpto <char name><br>@goto <char name></span> +Warps you to selected character + +<span class="GMCommand">@go <number/city_name></span> +Warps you to a set city:</font></pre> +<table id="table1" border="1" width="90%"> + <tbody> + <tr> + <td width="25%"><font face="Verdana" size="2">-3: (Memo Point 2)</font></td> + <td width="25%"><font face="Verdana" size="2">2: Geffen</font></td> + <td width="25%"><font face="Verdana" size="2">7: Lutie</font></td> + <td width="25%"><font face="Verdana" size="2">12: Umbala</font></td> + </tr> + <tr> + <td width="25%"><font face="Verdana" size="2">-2: (Memo Point 1)</font></td> + <td width="25%"><font face="Verdana" size="2">3: Payon </font> </td> + <td width="25%"><font face="Verdana" size="2">8: Comodo</font></td> + <td width="25%"><font face="Verdana" size="2">13: Niflheim</font></td> + </tr> + <tr> + <td width="25%"><font face="Verdana" size="2">-1: (Memo Point 0)</font></td> + <td width="25%"><font face="Verdana" size="2">4: Alberta</font></td> + <td width="25%"><font face="Verdana" size="2">9: Yuno</font></td> + <td width="25%"><font face="Verdana" size="2">14: Louyang</font></td> + </tr> + <tr> + <td width="25%"><font face="Verdana" size="2">0: Prontera</font></td> + <td width="25%"><font face="Verdana" size="2">5: Izlude</font></td> + <td width="25%"><font face="Verdana" size="2">10: Amatsu</font></td> + <td width="25%"><font face="Verdana" size="2">15: Start Point</font></td> + </tr> + <tr> + <td width="25%"><font face="Verdana" size="2">1: Morroc</font></td> + <td width="25%"><font face="Verdana" size="2">6: Al de Baran</font></td> + <td width="25%"><font face="Verdana" size="2">11: Gon Ryun</font></td> + <td width="25%"><font face="Verdana" size="2">16: Prison/Jail</font></td> + </tr> + </tbody> +</table> +<pre class="style1"><font face="Verdana"><span class="GMCommand">/hide<br>@hide</span> +GM Hide. Perfect hide that's totally invisible. + +<span class="GMCommand">@heal [<HP> <HP>]</span> +Heals the desired amount of HP and SP. No value specified will do a full heal. + +<span class="GMCommand">@storage </span> +Opens storage + +<span class="GMCommand">/recall <char name><br>@recall <char name></span> +Recalls target character to you. + +<span class="GMCommand">@recallall</span> +Recalls everyone on the server to you. + +<span class="GMCommand">@guildrecall <guild_name/id></span> +Warps all online character of a guild to you. + +<span class="GMCommand">@partyrecall <party_name/id></span> +Warps all online character of a party to you. + +<span class="GMCommand">@revive <char name></span> +Revives target character. + +<span class="GMCommand">@killmonster</span> +Kills all spawned monsters on maps + +<span class="GMCommandTitle">SPAWNING COMMANDS</span> +<span class="GMCommand">/item <item_name><br></span>Gives you 1 of the desired monster<span + class="GMCommand"> + +@item <item name or ID> <quantity></span> +Gives you the desired item with the desired quantity + +<span class="GMCommand">/monster <monster_name></span><span + class="faqlist"> +Spawns 1 of the desired monster + +</span><span class="GMCommand"><font size="2">@spawn <monster_name_or_monster_ID> [<quantity> [<desired_monster_name> [<x coord> [<y coord>]]]]<br>@monster <monster_name_or_monster_ID> [<quantity> [<desired_monster_name> [<x coord> [<y coord>]]]]<br>@summon <monster_name_or_monster_ID> [<quantity> [<desired_monster_name> [<x coord> [<y coord>]]]]</font></span><font + size="2"><span class="faqlist"> +</span><span class="GMCommand">@monster2 <desired_monster_name> <monster_name_or_monster_ID> [<quantity> [<x coord> [<y coord>]]]<br>OR<br>@spawn "desired monster name" <monster_name_or_monster_ID> [<quantity> [<x coord> [<y coord>]]]<br>@monster "desired monster name" <monster_name_or_monster_ID> [<quantity> [<x coord> [<y coord>]]]<br>@summon "desired monster name" <monster_name_or_monster_ID> [<quantity> [<x coord> [<y coord>]]]<br>@monster2 "desired monster name" <monster_name_or_monster_ID> [<quantity> [<x coord> [<y coord>]]]<br>OR<br>@spawn <monster_name_or_monster_ID> "desired monster name" [<quantity> [<x coord> [<y coord>]]]<br>@monster <monster_name_or_monster_ID> "desired monster name" [<quantity> [<x coord> [<y coord>]]]<br>@summon <monster_name_or_monster_ID> "desired monster name" [<quantity> [<x coord> [<y coord>]]]<br>@monster2 <monster_name_or_monster_ID> "desired monster name" [<quantity> [<x coord> [<y coord>]]]</span> +</font> +Spawns the desired monster with any desired name, quantity and x and y location (if specified). +There 2 last forms can use spaces for desired names (between the ""). + +<span class="GMCommandTitle">MAP OPTIONS COMMANDS</span> + +<span class="GMCommand">@pvpon</span> +Turns pvp on on the current map +<span class="GMCommand">@pvpoff</span> +Turns pvp off on the current map +<span class="GMCommand">@gvgon</span> +Turns gvg on on the current map. This does NOT enable War of Emperium. +<span class="GMCommand">@gvgoff </span> +Turns gvg off on the current map. This does NOT disable War of Emperium. + +<span class="GMCommandTitle">CHARACTER CONTROL COMMANDS</span> +<span class="GMCommand">@baselvlup <number of levels></span> +<span class="GMCommand">@lvup <number of levels><br>@blevel <number of levels></span> +Raises your base level the desired number of levels. The max is 99 (unless you change it in battle_athena.conf). + +<span class="GMCommand">@joblvlup <number of levels></span> +<span class="GMCommand">@joblvup <number of levels><br>@jlevel <number of levels></span> +Raises your job level the desired number of levels. The max is 50 for 1st and 2nd class, and 70 for advanced classes. + +<span class="GMCommand">@job <job ID><br>@jobchange <job ID></span> +Changes your job to the job assigned to the ID</font></pre> +<table id="table2" border="1" width="90%"> + <tbody> + <tr> + <td width="25%"><font style="font-size: 11pt;" face="Verdana">0: +Novice</font></td> + <td width="25%"><font style="font-size: 11pt;" face="Verdana">6: +Thief</font></td> + <td width="25%"><font style="font-size: 11pt;" face="Verdana">12: +Assassin</font></td> + <td width="25%"><font style="font-size: 11pt;" face="Verdana">18: +Alchemist</font></td> + </tr> + <tr> + <td width="25%"><font style="font-size: 11pt;" face="Verdana">1: +Swordsman</font></td> + <td width="25%"><font style="font-size: 11pt;" face="Verdana">7: +Knight</font></td> + <td width="25%"><font style="font-size: 11pt;" face="Verdana">13: +Knight w/ Peco</font></td> + <td width="25%"><font style="font-size: 11pt;" face="Verdana">19: +Bard</font></td> + </tr> + <tr> + <td width="25%"><font style="font-size: 11pt;" face="Verdana">2: +Mage</font></td> + <td width="25%"><font style="font-size: 11pt;" face="Verdana">8: +Priest</font></td> + <td width="25%"><font style="font-size: 11pt;" face="Verdana">14: +Crusader</font></td> + <td width="25%"><font style="font-size: 11pt;" face="Verdana">20: +Dancer</font></td> + </tr> + <tr> + <td width="25%"><font style="font-size: 11pt;" face="Verdana">3: +Archer</font></td> + <td width="25%"><font style="font-size: 11pt;" face="Verdana">9: +Wizard</font></td> + <td width="25%"><font style="font-size: 11pt;" face="Verdana">15: +Monk</font></td> + <td width="25%"><font style="font-size: 11pt;" face="Verdana">21: +Crusader w/ Peco</font></td> + </tr> + <tr> + <td width="25%"><font style="font-size: 11pt;" face="Verdana">4: +Acolyte</font></td> + <td width="25%"><font style="font-size: 11pt;" face="Verdana">10: +Blacksmith</font></td> + <td width="25%"><font style="font-size: 11pt;" face="Verdana">16: +Sage</font></td> + <td width="25%"><font style="font-size: 11pt;" face="Verdana">22: +Wedding</font></td> + </tr> + <tr> + <td width="25%"><font style="font-size: 11pt;" face="Verdana">5: +Merchant</font></td> + <td width="25%"><font style="font-size: 11pt;" face="Verdana">11: +Hunter</font></td> + <td width="25%"><font style="font-size: 11pt;" face="Verdana">17: +Rogue</font></td> + <td width="25%"><font style="font-size: 11pt;" face="Verdana">23: +Super Novice</font></td> + </tr> + </tbody> +</table> +<table id="table3" border="1" width="90%"> + <tbody> + <tr> + <td width="25%"><font style="font-size: 9pt;" face="Verdana">24/4001: +Novice High</font></td> + <td width="25%"><font style="font-size: 9pt;" face="Verdana">31/4007: +Thief High</font></td> + <td width="25%"><font style="font-size: 9pt;" face="Verdana">40/4013: +Assassin Cross</font></td> + <td width="25%"><font style="font-size: 9pt;" face="Verdana">46/4019: +Creator</font></td> + </tr> + <tr> + <td width="25%"><font style="font-size: 9pt;" face="Verdana">25/4002: +Swordsman High</font></td> + <td width="25%"><font style="font-size: 9pt;" face="Verdana">35/4008: +Lord Knight</font></td> + <td width="25%"><font style="font-size: 9pt;" face="Verdana">41/4014: +Lord Knight w/ Peco</font></td> + <td width="25%"><font style="font-size: 9pt;" face="Verdana">47/4020: +Clown</font></td> + </tr> + <tr> + <td width="25%"><font style="font-size: 9pt;" face="Verdana">26/4003: +Mage High</font></td> + <td width="25%"><font style="font-size: 9pt;" face="Verdana">36/4009: +High Priest</font></td> + <td width="25%"><font style="font-size: 9pt;" face="Verdana">42/4015: +Paladin</font></td> + <td width="25%"><font style="font-size: 9pt;" face="Verdana">48/4021: +Gypsy</font></td> + </tr> + <tr> + <td width="25%"><font style="font-size: 9pt;" face="Verdana">28/4004: +Archer High</font></td> + <td width="25%"><font style="font-size: 9pt;" face="Verdana">37/4010: +High Wizard</font></td> + <td width="25%"><font style="font-size: 9pt;" face="Verdana">43/4016: +Champion</font></td> + <td width="25%"><font style="font-size: 9pt;" face="Verdana">49/4022: +Paladin w/ Peco</font></td> + </tr> + <tr> + <td width="25%"><font style="font-size: 9pt;" face="Verdana">29/4005: +Acolyte High</font></td> + <td width="25%"><font style="font-size: 9pt;" face="Verdana">38/4011: +Whitesmith</font></td> + <td width="25%"><font style="font-size: 9pt;" face="Verdana">44/4017: +Professor</font></td> + <td width="25%"> </td> + </tr> + <tr> + <td width="25%"><font style="font-size: 9pt;" face="Verdana">30/4006: +Merchant High</font></td> + <td width="25%"><font style="font-size: 9pt;" face="Verdana">39/4012: +Sniper</font></td> + <td width="25%"><font style="font-size: 9pt;" face="Verdana">45/4018: +Stalker</font></td> + <td width="25%"> </td> + </tr> + </tbody> +</table> +<pre class="style1"><font face="Verdana"><span class="GMCommand">@option <param1> <param2> <param3></span> +Changes options for your character + <param1> <param2> (Stackable) <param3> (Stackable) + 01 Petrified 01 Poison 01 Sight + 02 Frozen 02 Cursed 02 Hide + 03 Stunned 04 Silenced 04 Cloak + 04 Sleeping 08 ??? 08 Level 1 Cart + 06 ??? 16 Darkness 16 Falcon + 32 (Depreciated) + 64 GM Perfect Hide + 128 Level 2 Cart + 256 Level 3 Cart + 512 Level 4 Cart + 1024 Level 5 Cart + 2048 Orc Head + 4096 Wedding Sprites + 8192 Ruwach + +<span class="GMCommand">@mountpeco</span> +Will (un)mount on a peco. (Class is required, but not skill) + +<span class="GMCommand">@speed <1-1000></span> +Changes you walking speed. +1(Fastest)<---140(Default)----------------->1000(Slowest) + +<span class="GMCommand">@disguise <monster_name_or_monster_ID></span> +Change your appearence to other players to a mob. + +<span class="GMCommand">@undisguise</span> +Restore your normal appearance. + +<span class="GMCommand">@model <hair ID> <hair color> <clothes color></span> +Changes your characters appearance (Hair type, Hair Colour and/or Clothes Colour) + Hair ID (0-17) Hair Colour (0-8) Clothes Colour (0-4) + 0 Default 0 Default + 1 Blonde 1 Red + 2 Purple 2 Green + 3 Brown 3 White + 4 Green 4 Brown + 5 Blue + 6 White + 7 Black + 8 Red + +<span class="GMCommand">@effect <effect_id> [flag]</span> + Give an efect to your character. + +<span class="GMCommand">@stpoint <number of points></span> +Gives you the desired number of stat points. + +<span class="GMCommand">@skpoint <number of points></span> +Gives you the desired number of skill points. + +<span class="GMCommand">@zeny <amount></span> +Gives you desired amount of Zeny. + +<span class="GMCommand">@str <amount><br>@agi <amount><br>@vit <amount><br>@int <amount><br>@dex <amount><br>@luk <amount></span> +Adds desired amount to any stat. For example "@str 10" raises your str by 10 + +<span class="GMCommand">@spiritball <number></span> +Number 1-1000 +Gives you monk "spirit spheres" like from the skill "Call Spirits" +(If the number you use is > 1000, your server may become instable or crash). 1000 Lags Your Client + +<span class="GMCommand">@memo [memo_position]</span> +set/change a memo location (no position: display memo points). + +<span class="GMCommand">@questskill <id></span> +Gives you the specified quest skill + +<span class="GMCommand">@lostskill <id></span> +Takes away the specified quest skill from you + +Quest Skill ID: + Novice + 142 Emergency Care + 143 Act dead + Swordsman + 144 Moving HP Recovery + 145 Attack Weak Point + 146 Auto Berserk + Archer + 147 Arrow Creation + 148 Charge Arrows + Thief + 149 Throw Sand + 150 Back Sliding + 151 Take Stone + 152 Stone Throw + Merchant + 153 Cart Revolution + 154 Change Cart + 155 Crazy Uproar/Loud Voice + Acolyte + 156 Holy Light + Magician + 157 Energy Coat + +<span class="GMCommand">@email <actual@email> <new@email></span> +to change your e-mail (characters protection) + +<span class="GMCommandTitle">GUILD/WoE CONTROL COMMANDS</span> +<span class="GMCommand">@guildlvup/@guildlvlup <# of levels></span> +Raise Guild by desired number of levels + +<span class="GMCommand">@guild <Desired Guild Name></span> +Creates a guild with the disered name (Spaces are allow, no need to use "") + +<span class="GMCommand">@agitstart</span> +Starts Guild Wars (War of Emperium) + +<span class="GMCommand">@agitend</span> +Ends Guild Wars (War of Emperium) + +<span class="GMCommandTitle">EQUIPMENT COMMANDS</span> +<span class="GMCommand">@refine <position> <+/-amount></span> +Upgrades equipment at the position specified (Stackable) +0 - All +1 - Lower Head +2 - Right Hand +4 - Robe/Garment +8 - Left Accessory +16 - Body/Armor +32 - Left Hand +64 - Feet +128 - Right Accessory +256 - Top Head +512 - Mid Head + +Example: +@refine 34 10 - Refines a 2 handed weapon to +10 +@refine 16 4 - Refines the body/armor to +4 + +<span class="GMCommand">@produce <equip name or equip ID> <element> <# of very's></span> +Element: 0 None 1 Ice 2 Earth 3 Fire 4 Wind +# of very's: 0 None 1 Very Strong 2 Very Very Strong 3 Very Very Very Strong + +Example: @produce 1163 3 3 - Produces a Very Very Very Strong (Your Nick)'s Fire Claymore + +<span class="GMCommandTitle">PET COMMANDS</span> +<span class="GMCommand">@makeegg <ID></span> +Gives pet egg for monster ID in pet DB + +<span class="GMCommand">@petfriendly <#></span> +Set pet friendly amount (0-1000) 0 Min, 1000 Max + +<span class="GMCommand">@pethungry <#></span> +Set pet hungry amount (0-100) 0 Min, 100 Max + +<span class="GMCommand">@petrename</span> +Re-enable pet rename + +<span class="GMCommand">@hatch</span> +Brings up the hatch pet menu + +<span class="GMCommandTitle">REMOTE CHAR COMMANDS</span> +<span class="GMCommand">@charwarp <map name> <x> <y> <char name></span> +Warps character to location of choice +Example: +@charwarp morocc 150 160 TestChar -> Warps TestChar to Morroc (X:150, Y:160) + +<span class="GMCommand">@charstats <char name></span> +Displays the character's stats. + +<span class="GMCommand">@charignorelist <char name></span> +Displays ignore list of the player + +<span class="GMCommand">@inall <char name></span> +Allows all wispers for the player + +<span class="GMCommand">@exall <char name></span> +Blocks all wispers for the player + +<span class="GMCommand">@charoption <param1> <param2> <param3> <char name></span> +Does the same as the @option command only to target character. + +<span class="GMCommand">@charmountpeco <charname></span> +Give/remove to a player a peco (Class is required, but not skill). + +<span class="GMCommand">@charpetrename <charname></span> +Re-enable pet rename to a player. + +<span class="GMCommand">@charsave <map> <x> <y> <char name></span> +Changes the target player's respawn point. + +<span class="GMCommand">@charbaselvl <#> <char name></span> +Change a character's base level. + +<span class="GMCommand">@charjlvl <#> <char name></span> +Change a character's job level. + +<span class="GMCommand">@charjob <job ID> <char name><br>@charjobchange <job ID> <char name></span> +Changes target character's job. + +<span class="GMCommand">@charzeny <amount> <char name></span> +Give/take a player's Zeny + +<span class="GMCommand">@charstpoint <amount> <char name></span> +Give/take a player's stat points + +<span class="GMCommand">@charskpoint <amount> <char name></span> +give/take a player's skill points + +<span class="GMCommand">@charquestskill <#> <char name></span> +Gives to a player the specified quest skill. + +<span class="GMCommand">@charlostskill <#> <char name></span> +Takes away the specified quest skill from the player. + +<span class="GMCommand">@chardelitem <item_name_or_ID> <quantity> <char name></span> +Remove items from a character + +<span class="GMCommand">@charmodel <hair type> <hair color> <clothes color> <char name></span> +Changes a player's model + +<span class="GMCommand">@chardisguise <monster_name_or_monster_ID> <char name></span> +Changes disguise of a player + +<span class="GMCommand">@charundisguise <char name></span> +Cancels disguise of a player + +<span class="GMCommand">@charreset <char name></span> +Resets the character + +<span class="GMCommand">@chardyeclothes <#> <char name></span> +Changes a player's clothes colour + +<span class="GMCommand">@chardyehair <#> <char name></span> +Changes a player's hair colour + +<span class="GMCommand">@charstylehair <#> <char name></span> +Changes a player's hair style + +<span class="GMCommand">@charchangesex <name></span> +Changes sex of a player (all characters of the account) + +<span class="GMCommand">@charblock <name></span> +Blocks definitively a account + +<span class="GMCommand">@charunblock <name></span> +<span class="GMCommand">@unblock <name></span> +Unblocks a account + +<span class="GMCommand">@charban <time> <name></span> +Ban temporarily a account + time usage: adjustement (+/- value) and element (y/a, m, d/j, h, mn, s) + Example: @ban +1m-2mn1s-6y testplayer + +<span class="GMCommand">@charunban <name><br>@unban <name><br>@unbanish <name><br>@uncharbanish <name></span> +Unban a account + +<span class="GMCommand">@jail <char_name></span> +Sends specified character in jails + +<span class="GMCommand">@unjail <charname><br>@discharge <char_name></span> +Discharges specified character/prisoner + +<span class="GMCommandTitle">MASS CONTROL COMMANDS</span> +<span class="GMCommand">@night</span> +All characters are in darkness + +<span class="GMCommand">@day</span> +@option 00 00 00 are used on all characters + +<span class="GMCommand">@doom</span> +Kills all NON GM chars on the server. + +<span class="GMCommand">@doommap</span> +Kills all non GM characters on the map. + +<span class="GMCommand">@raise</span> +Resurrects all characters on the server. + +<span class="GMCommand">@raisemap</span> +Resurrects all characters on the map. + +<span class="GMCommand">@kick <charname></span> +Kicks specified character off the server + +<span class="GMCommand">@kickall</span> +Kick all characters off the server + +<span class="GMCommandTitle">OTHER COMMANDS</span> +<span class="GMCommand">@gat<br>@packet</span> +Unknown + +<span class="GMCommandTitle">Server Control Commands</span> +<span class="GMCommand">@reloadscript<br></span>Reloads all the scripts for the server<span + class="GMCommand"> + +@reloaditemdb</span> +Reloads the itemdb + +<span class="GMCommand">@reloadgmdb</span> +Reload GM levels + +<span class="GMCommand">@mapexit</span> +Kick all players and shut down map-server. + +<span class="GMCommand">@reloadmobdb</span> +Reload monster database + +<span class="GMCommand">@reloadskilldb</span> +Reload skills definition database + +<span class="GMCommand">@enablenpc <NPC_name></span> +Enable a NPC + +<span class="GMCommand">@disablenpc <NPC_name></span> +Disable a NPC</font> +</pre> +</td> + </tr> + </table> + </td> + </tr> + <tr> + <td class="spaceromg"></td> + </tr> + <tr> + <td class="copyright" colspan="8">© Copyright 2004 eAthena Development Team<br>Design © Copyright 2004 Evera</td> + </tr> +</table> +</body>
\ No newline at end of file diff --git a/doc/readme/midbanner.jpg b/doc/readme/midbanner.jpg Binary files differnew file mode 100644 index 0000000..d928dfc --- /dev/null +++ b/doc/readme/midbanner.jpg diff --git a/doc/readme/npcfeatures.html b/doc/readme/npcfeatures.html new file mode 100644 index 0000000..a2e52ec --- /dev/null +++ b/doc/readme/npcfeatures.html @@ -0,0 +1,143 @@ +<html> +<head><title>eAthena 1.0 RC5 Readme</title> +<meta http-equiv="expires" content="-1" /> +<meta http-equiv="pragma" content="no-cache" /> +<meta name="author" content="eAthena Development Team" / +<meta name="description" content="eAthena, ro server emulator" /> +<meta name="keywords" content="eathena, ro, ragnarok online, server emulator, server software" /> +<link href="./rc5.css" media="screen" rel="stylesheet" type="text/css"> +</head> +<body> +<table class="wrapper"> + <tr class="topbanner"> + <td class="topnav"><b><a href="../readme.html">Intro</a></b></td> + <td class="topnav"><b><a href="./changelog.html">Changelog</a></b></td> + <td class="topnav"><b><a href="./features.html">Features</a></b></td> + <td class="topnav"><b><a href="./Npcfeatures.html">Npc Features</a></b></td> + <td class="topnav"><b><a href="./settingup.html">Setting Up</a></b></td> + <td class="topnav"><b><a href="./gmcommands.html">GM Commands</a></b></td> + <td class="topnav"><b><a href="./faq.html">FAQ</a></b></td> + <td class="topnav"><b><a href="./support.html"> Support</a></b></td> + </tr> + <tr> + <td class="banner" colspan="8"> + <div align="right"><img src="./midbanner.jpg" border="0"></div> + </td> + </tr> + <tr> + <td class="omgspacer"></td> + </tr> + <tr> + <td align="middle" colspan="8"> + <table class="content"> + <tr> + <td class="contenttitle"><b>eAthena Npc Scripts</b></td> + </tr> + <tr> + <td class="contentbg"> +<ul> +<li>Town Npcs (11/13)</li> +<ul><li> Prontera - 100%</li></ul> +<ul><li> Morocc - 100%</li></ul> +<ul><li> Geffen - 100%</li></ul> +<ul><li> Izlude - 100%</li></ul> +<ul><li> Alberta - 100%</li></ul> +<ul><li> Al de Baran - 100%</li></ul> +<ul><li> Lutie - 100%</li></ul> +<ul><li> Amatsu - 100%</li></ul> +<ul><li> Comodo - 100%</li></ul> +<ul><li> Niflheim - 100%</li></ul> +<ul><li> Umbala - 100%</li></ul> +<ul><li> Gonryun - 60%</li></ul> +<ul><li> Payon(New Maps) - 50%</li></ul> +<li>Job Quests (16/33)</li> +<ul><li> Novice Class (2/2)</li></ul> +<ul><ul><li> Novice - 100%</li></ul></ul> +<ul><ul><li> Super Novice - 100%</li></ul></ul> +<ul><li> 1-1 Class (6/6)</li></ul> +<ul><ul><li> Swordman - 100%</li></ul></ul> +<ul><ul><li> Mage - 100%</li></ul></ul> +<ul><ul><li> Archer - 100%</li></ul></ul> +<ul><ul><li> Acolyte - 100%</li></ul></ul> +<ul><ul><li> Merchant - 100%</li></ul></ul> +<ul><ul><li> Thief - 100%</li></ul></ul> +<ul><li> 2-1 Class (6/6)</li></ul> +<ul><ul><li> Knight - 100%</li></ul></ul> +<ul><ul><li> Priest - 100%</li></ul></ul> +<ul><ul><li> Wizard - 100%</li></ul></ul> +<ul><ul><li> Blacksmith - 100%</li></ul></ul> +<ul><ul><li> Hunter - 100%</li></ul></ul> +<ul><ul><li> Assassin - 100%</li></ul></ul> +<ul><li> 2-2 Class (2/7)</li></ul> +<ul><ul><li> Crusader - 0%</li></ul></ul> +<ul><ul><li> Monk - 0% (Soon)</li></ul></ul> +<ul><ul><li> Sage - 0% (Soon)</li></ul></ul> +<ul><ul><li> Rogue - 100%</li></ul></ul> +<ul><ul><li> Alchemist - 100%</li></ul></ul> +<ul><ul><li> Bard - 0%</li></ul></ul> +<ul><ul><li> Dancer - 100%</li></ul></ul> +<ul><li> 2-1-1 Class (0/6)</li></ul> +<ul><li> 2-2-1 Class (0/6)</li></ul> +<li>Kafras - 100%</li> +<li>Guides - 100%</li> +<li> War Of Emperium (4/5)</li> +<ul><li> Prontera - 100%</li></ul> +<ul><li> Geffen - 100%</li></ul> +<ul><li> Payon - 100%</li></ul> +<ul><li> Al De Baran - 100%</li></ul> +<ul><li> Novice - 0%</li></ul> +<li>Quests (24/25)</li> +<ul><li> Skill Quests (7/7)</li></ul> +<ul><ul><li> Novice - 100%</li></ul></ul> +<ul><ul><li> Swordman - 100%</li></ul></ul> +<ul><ul><li> Mage - 100%</li></ul></ul> +<ul><ul><li> Archer - 100%</li></ul></ul> +<ul><ul><li> Acolyte - 100%</li></ul></ul> +<ul><ul><li> Merchant - 100%</li></ul></ul> +<ul><ul><li> Thief - 100%</li></ul></ul> +<ul><li>Town Quests (9/10)</li></ul> +<ul><ul><li> Prontera - 100%</li></ul></ul> +<ul><ul><li> Morocc - 100%</li></ul></ul> +<ul><ul><li> Geffen - 100%</li></ul></ul> +<ul><ul><li> Izlude - 100%</li></ul></ul> +<ul><ul><li> Alberta - 100%</li></ul></ul> +<ul><ul><li> Al De Baran - 100%</li></ul></ul> +<ul><ul><li> Yuno - 100%</li></ul></ul> +<ul><ul><li> Lutie - 100%</li></ul></ul> +<ul><ul><li> Comodo - 100%</li></ul></ul> +<ul><ul><li> Payon - 20%</li></ul></ul> +<ul><li>Other Quests (8/8)</li></ul> +<ul><ul><li> Dye - 100%</li></ul></ul> +<ul><ul><li> MrSmile - 100%</li></ul></ul> +<ul><ul><li> Juice Making - 100%</li></ul></ul> +<ul><ul><li> Doomed Swords - 100%</li></ul></ul> +<ul><ul><li> Bongun Taming - 100%</li></ul></ul> +<ul><ul><li> Munak Taming - 100%</li></ul></ul> +<ul><ul><li> Tamking - 100%</li></ul></ul> +<ul><ul><li> Bongun Sword - 100%</li></ul></ul> +<ul><li>Other (7/10)</li></ul> +<ul><ul><li> Card Remover - 100%</li></ul></ul> +<ul><ul><li> PvP (old) - 100%</li></ul></ul> +<ul><ul><li> Time Arena - 100%</li></ul></ul> +<ul><ul><li> Bank - 100%</li></ul></ul> +<ul><ul><li> Wedding - 80%</li></ul></ul> +<ul><ul><li> Valkyrie - 10%</li></ul></ul> +<ul><ul><li> Gefenia - 10%</li></ul></ul> +<ul><ul><li> Heal Npc - 100%</li></ul></ul> +<ul><ul><li> Warp Npc - 100%</li></ul></ul> +<ul><ul><li> Jobchange - 100%</li></ul></ul> + </li> +</ul> +</td> + </tr> + </table> + </td> + </tr> + <tr> + <td class="spaceromg"></td> + </tr> + <tr> + <td class="copyright" colspan="8">© Copyright 2004 eAthena Development Team<br>Design © Copyright 2004 Evera</td> + </tr> +</table> +</body>
\ No newline at end of file diff --git a/doc/readme/rc5.css b/doc/readme/rc5.css new file mode 100644 index 0000000..e6c0809 --- /dev/null +++ b/doc/readme/rc5.css @@ -0,0 +1,68 @@ + A {color: #000; text-decoration: none; background-color : inherit;} + A:link {color: #000; text-decoration: none; background-color : inherit;} + A:visited { color: #000; text-decoration: none; background-color : inherit;} + A:active { color: #000; text-decoration: none; background-color : inherit;} + A:hover { color: #8282AE; text-decoration: underline; background-color : inherit;} + +body { + margin: 0px; + font-size: 11px; + font-family: Lucila, Arial, Helvetica; + background: #E1E1F4; + color: #9999CD; +} + +.wrapper { + width: 100%; + padding: 0px; + margin: 0px; +} + +.topbanner { + background: #FFF; + height: 25px; +} + +.topnav { + border-top: 1px solid #ADADD8; + border-left: 1px dotted #ADADD8; + border-right: 1px dotted #ADADD8; + width: 12.5%; +} + +.banner { + border-bottom: 1px solid #000; + border-top: 1px solid #000; + background: #9999CD; + height: 136px; +} + +.omgspacer { + height: 10px; +} + +.content { + width: 75%; + padding: 3px; + border: 1px dashed #ADADD8; +} + +.contenttitle { + font-size: 20px; + background: #D3D3EB; + color: #8282AE; +} + +.contentbg { + font-size: 15px; + background: #F1F1FA; +} + +.spaceromg { + height: 20px; +} + +.copyright { + background: #BDBDE0; + height: 10px; + color: #000;
\ No newline at end of file diff --git a/doc/readme/settingup.html b/doc/readme/settingup.html new file mode 100644 index 0000000..b35f4d8 --- /dev/null +++ b/doc/readme/settingup.html @@ -0,0 +1,126 @@ +<html> +<head><title>eAthena 1.0 RC5 Readme</title> +<meta http-equiv="expires" content="-1" /> +<meta http-equiv="pragma" content="no-cache" /> +<meta name="author" content="eAthena Development Team" / +<meta name="description" content="eAthena, ro server emulator" /> +<meta name="keywords" content="eathena, ro, ragnarok online, server emulator, server software" /> +<link href="./rc5.css" media="screen" rel="stylesheet" type="text/css"> +</head> +<body> +<table class="wrapper"> + <tr class="topbanner"> + <td class="topnav"><b><a href="../readme.html">Intro</a></b></td> + <td class="topnav"><b><a href="./changelog.html">Changelog</a></b></td> + <td class="topnav"><b><a href="./features.html">Features</a></b></td> + <td class="topnav"><b><a href="./Npcfeatures.html">Npc Features</a></b></td> + <td class="topnav"><b><a href="./settingup.html">Setting Up</a></b></td> + <td class="topnav"><b><a href="./gmcommands.html">GM Commands</a></b></td> + <td class="topnav"><b><a href="./faq.html">FAQ</a></b></td> + <td class="topnav"><b><a href="./support.html"> Support</a></b></td> + </tr> + <tr> + <td class="banner" colspan="8"> + <div align="right"><img src="./midbanner.jpg" border="0"></div> + </td> + </tr> + <tr> + <td class="omgspacer"></td> + </tr> + <tr> + <td align="middle" colspan="8"> + <table class="content"> + <tr> + <td class="contenttitle"><b>How to setup eAthena</b></td> + </tr> + <tr> + <td class="contentbg"> +<ol> + <li>Edit motd.txt, grf-files.txt & .conf files as neccessary.</li> + <li>If you want to add a user, run adduser.exe in the main eAthena +directory before starting the +server.<br> + </li> + <li>Run the login-server.exe, char-server.exe and map-server.exe in +the main eAthena directory.</li> + <li>You're done!<br> + </li> +</ol> +</td> + </tr> + </table> + </td> + </tr> + <tr> + <td class="omgspacer"></td> + </tr> + <tr> + <td align="middle" colspan="8"> + <table class="content"> + <tr> + <td class="contenttitle"><b>Upgrading eAthena</b></td> + </tr> + <tr> + <td class="contentbg"> + <span style="font-weight: bold;"> +<br> +From a release newer than 953 or 817:</span><br> +<ol> + <li>Transfer your old /save/ folder contents (Athena.txt, +Account.txt, Storage.txt, etc.) to the new /save/ folder.</li> + <li>Transfer/Edit grf-files.txt, gm-account.txt and motd.txt as +neccessary.</li> + <li>Transfer any extra custom NPCs to the /npc/ folder and edit if +neccessary.</li> + <li>Edit .conf files to setup your server.</li> + <li>You're done!</li> +</ol> + <span style="font-style: italic;">NOTE</span>: +It is not recommended to transfer your DB files<br> + as the DB files are constantly +updated by the eAthena/jAthena<br> + team. Just readd your changes to +the new DB files instead of<br> + replacing the new DB file with +your old one.<br> +<br> +<span style="font-weight: bold;"><br> +From a release older than 670:</span><br> +<ol> + <li>Transfer your old /save/ folder contents (Athena.txt, +Account.txt, Storage.txt, etc.) to the new /save/ folder.</li> + <li>Transfer/Edit grf-files.txt, gm-account.txt and motd.txt as +neccessary.</li> + <li>Transfer any extra custom NPCs to the /npc/ folder and edit if +neccessary.</li> + <li>Edit .conf files to setup your server.</li> + <li>You're done!</li> +</ol> + <span style="font-style: italic;">NOTE</span>: +It is not recommended to transfer your .conf files from<br> + your old eAthena setup to this +new one because there are always<br> + changes and new additions to +these .conf files all the time<br> +<br> + <span style="font-style: italic;">NOTE +2</span>: It is also not recommended to transfer your DB files<br> + as the DB files are constantly +updated by the eAthena/jAthena<br> + team. Just readd your changes to +the new DB files instead of<br> + replacing the new DB file with +your old one.<br> +</td> + </tr> + </table> + </td> + </tr> + <tr> + <td class="spaceromg"></td> + </tr> + <tr> + <td class="copyright" colspan="8">© Copyright 2004 eAthena Development Team<br>Design © Copyright 2004 Evera</td> + </tr> +</table> +</body>
\ No newline at end of file diff --git a/doc/readme/support.html b/doc/readme/support.html new file mode 100644 index 0000000..b0cb6a9 --- /dev/null +++ b/doc/readme/support.html @@ -0,0 +1,65 @@ +<html> +<head><title>eAthena 1.0 RC5 Readme</title> +<meta http-equiv="expires" content="-1" /> +<meta http-equiv="pragma" content="no-cache" /> +<meta name="author" content="eAthena Development Team" / +<meta name="description" content="eAthena, ro server emulator" /> +<meta name="keywords" content="eathena, ro, ragnarok online, server emulator, server software" /> +<link href="./rc5.css" media="screen" rel="stylesheet" type="text/css"> +</head> +<body> +<table class="wrapper"> + <tr class="topbanner"> + <td class="topnav"><b><a href="../readme.html">Intro</a></b></td> + <td class="topnav"><b><a href="./changelog.html">Changelog</a></b></td> + <td class="topnav"><b><a href="./features.html">Features</a></b></td> + <td class="topnav"><b><a href="./Npcfeatures.html">Npc Features</a></b></td> + <td class="topnav"><b><a href="./settingup.html">Setting Up</a></b></td> + <td class="topnav"><b><a href="./gmcommands.html">GM Commands</a></b></td> + <td class="topnav"><b><a href="./faq.html">FAQ</a></b></td> + <td class="topnav"><b><a href="./support.html"> Support</a></b></td> + </tr> + <tr> + <td class="banner" colspan="8"> + <div align="right"><img src="./midbanner.jpg" border="0"></div> + </td> + </tr> + <tr> + <td class="omgspacer"></td> + </tr> + <tr> + <td align="middle" colspan="8"> + <table class="content"> + <tr> + <td class="contenttitle"><b>Forums:</b></td> + </tr> + <tr> + <td class="contentbg"> +<a + href="http://eathena.deltaanime.net">http://eathena.deltaanime.net</a> +Our Official Webpage.<br> +<a href="http://forum.asb-sakray.net">http://forum.asb-sakray.net</a> Aegis +Support Board.<br> +<a href="http://www.evera-aura.net">http://www.evera-aura.net</a> Evera's Site<br> +<a href="http://eathena.systeminplace.net">eathena.systeminplace.net</a> Valaris's Site<br> +<br> +<span style="font-weight: bold;">IRC Channel:<br> +<span style="font-weight: bold;"></span></span>irc.deltaanime.net +#athena (Please make sure to check both forums before coming into the +channel to ask questions, or else you may not be answered and possibly +ridiculed.)<span style="font-weight: bold;"><span + style="font-weight: bold;"><span style="font-weight: bold;"></span></span><br> +</span><br> +</td> + </tr> + </table> + </td> + </tr> + <tr> + <td class="spaceromg"></td> + </tr> + <tr> + <td class="copyright" colspan="8">© Copyright 2004 eAthena Development Team<br>Design © Copyright 2004 Evera</td> + </tr> +</table> +</body>
\ No newline at end of file diff --git a/doc/script_ref.txt b/doc/script_ref.txt new file mode 100644 index 0000000..06df8f4 --- /dev/null +++ b/doc/script_ref.txt @@ -0,0 +1,1424 @@ +AthenaNPCScript + +- Table of contents + 0. Introduction + 1. Definition of NPC + 2. Explanation of Script, and Fundamental Rule + 3. Imperative Sentence, Function, and Constant Label + 4. Error Message + 5. Postscript + +0. Introduction + The function and form which are contained in this text refer to npc_sample.txt contained in the newest snapshot, and are described. + The convenience of the editor which shows this text is considered, <tab> is written and <n> etc. is written. [ a tab character ] [ arbitrary values ]. + Although it is a coordinate system, please make a lower figure reference. + The increase in ?Y + ( 0,200)--(200,200) + | | + | | + | | + | | + | | + ( 0, 0)--(200, 0)-> the increase in X + +1. Definition of NPC + if it is below an athena directory -- anywhere -- being good (if it being able to do below athena/npc) -- please create txt for the time being + The NPC describes first what is shown (a way of speaking called NPC depending on the case is unsuitable). + + * Warp point : perform movement between MAP. + <gatname>,<x>,<y><tab>warp<tab><displayname><tab><dx>,<dy>,<destination_gatname>,<destination_x>,<destination_y> + + gatname The MAP file name on which a warp point is put is specified. Please do not forget gat. + x The horizontal coordinates on which a warp point is put are specified. + y The vertical coordinates on which a warp point is put are specified. + displayname It is a warp point discernment child. You may overlap. It uses by debugging. + dx It is the horizontal effect range of a warp point. + dy It is the vertical effect range of a warp point. Probably I do not write a circle but think that it is a region. + Example of dx and dy (- is x and y) : + 0,0 1,0 2,2 + *@* ***** ******* + *** ***** ******* + *@* ***** ******* + *@ ******* + *@ ******* + *@ ******* + *@ ******* + *@ + * I hear that and it will leap if the cell of - is stepped on. + *@ + destination_gatname It is a warp place. . Even if there is gat and there is not, don't care about it. + destination_x They are warp place horizontal coordinates. + destination_y They are warp place vertical coordinates. + + Notes : + A warp point displays only that plurality is described to be at the end when located on this position. + When the coordinates of a warp place are move prohibition cells, it leaps to somewhere in the MAP. + + * Monster : manage the spawning(aka apperance in japanese.) of a monster. + <gatname>,<x>,<y>,<xs>,<ys><tab>monster<tab><displayname><tab><npcid>,<number>,<spawn_delay1>,<spawn_delay2>[,<event>] + + gatname The appearing MAP file name is specified. + x The appearing horizontal coordinates are specified. Random at 0. + y The appearing vertical coordinates are specified. Random at 0. + xs The appearing horizontal range is specified. + ys The appearing vertical range is specified. + Example of xs and ys (- is x and y) : + 0,0 2,1 + *@* ***** + *@ ***** + *@ ***** + *@ + * And a monster appears from the cell of -. + + displayname It is the display name of the appearing monster. + npcid Please refer to mob_db.txt. id of the monster made to appear is specified. + number It is the number of the maximum appearances in the MAP and the appearance range. + spawn_delay1 After appearing, if specified the amount of time in milliseconds before it re-appears. + spawn_delay2 After dying, if specified the amount of time in milliseconds before it re-appears. + event The specified event is generated. An abbreviation is possible. + + Notes: + spawn_delay1 and spawn_delay2 judge and give priority to whether it re-appears having been based the latest [ direction ] on which as a result. + + * Store : sell an item. + <gatname>,<x>,<y>,<direction><tab>shop<tab><displayname><tab><npcid>,<item_id>:<price>,<item_id>:<price>,<item_id>:<price> + + gatname The MAP file name to arrange is specified. + x The horizontal coordinates to arrange are specified. + y The vertical coordinates to arrange are specified. + direction Direction is specified. + Details of direction : + 7 0 1 + 6 2 + 5 4 3 + + displayname The display name of the store to arrange is specified. + npcid The display sprite ID of the store to arrange is specified. + item_id The item ID put on the store to arrange is specified. Please refer to item_db. + price The price of the item specified by item_id is set up. + each <item_id>:<price> is divided by a comma (,), and more than one can be specified. + example: + item_id:price,item_id2,price2 + + * Script : create NPC. + <gatname>,<x>,<y>,<direction><tab>script<tab><displayname><tab><npcid>,<xs>,<ys>,{ <script> ... } + + gatname The MAP file name to arrange is specified. + x The horizontal coordinates to arrange are specified. + y The vertical coordinates to arrange are specified. + direction Direction is specified. + displayname The display name of NPC to arrange is specified. It becomes an event name when npcid is -1. + When making said display name another operation event, it can be described as a display name::discernment child. + npcid The display sprite ID of NPC to arrange is specified. If -1 is specified, it will become an event in map. + xs The horizontal range which performs a script automatically is specified. + ys The vertical range which performs a script automatically is specified. It is the same as a warp. + + Explanation about the inside of {} (inside parenthesis). + Collecting by the party cannot recommend you. If it can do + gatname etc, + { + //comment + script; + label: + script; + } + Let's write by the said touch. Comment out is //and comment area is /* and */. + The ? which does not leave; (semicolon) in the script ending. + + 「<gatname>,<x>,<y>,<direction><tab>」の部分を、「-<tab>」とすることで、 + マップサーバー内には存在していても、実際のマップには配置されないNPCを作成できます。 + これは後述のデュプリケートスクリプトでコピー元として使用します。 + + *デュプリケートスクリプト:既存のNPC(のスクリプト)をコピーします。 + <gatname>,<x>,<y>,<direction><tab>duplicate(<source>)<tab><displayname><tab><npcid>,<xs><ys> + + source以外のパラメータは通常のスクリプトと同じです。 + sourceにはコピー元となるNPCの識別名を入力します。 + + コピー元のNPCがマップ上に配置されている場合、同じマップである必要があります。 + マップ上に配置されていない場合は、どのマップへもコピー可能です。 + + *ユーザー定義関数スクリプト:スクリプトから呼び出されるユーザー定義関数を作成します。 + function<tab>script<tab><name><tab>{ <script> ... } + + callfunc命令で呼び出すことの出来る関数を作成します。 + 関数の最後には必ずreturn命令を入れてください。 + + *マップフラグ:MAPのルールを管理します。 + <gatname><tab>mapflag<tab><const> + + gatname ルールを設定するMAPファイル名を指定します。 + const ルールの内容を指定します。 + + constの一覧。 + nosave<tab><gatname>,<x>,<y> + リログインした際<gatname>の座標<x>,<y>に移動します。 + nomemo<tab>dummy + メモを取ることを禁止します。 + notereport<tab>dummy + SavePointまたはRandomを指定したwarp文、ワープポータル、テレポートを禁止します。 + nobranch<tab>dummy + 古木の枝の使用を禁止します。 + pvp<tab>dummy + PVP可能MAPになります。 + nopenalty<tab>dummy + デスペナルティ無しになります。 + pvp_noparty<tab>dummy + PVPにおいて、同パーティー攻撃不可になります。 + pvp_noguild<tab>dummy + PVPにおいて、同ギルド攻撃不可になります。 + gvg<tab>dummy + シーズモードになります。 + gvg_noparty<tab>dummy + シーズモードにおいて、同パーティー攻撃不可になります。 + +2.スクリプトの説明と基本的な規則 + *数字 + 符号付の整数と16進数表記整数を使用することができます。 + 符合付整数は半角数字で123456等と記述します。 + 16進数表記整数は0x12等0xを付けて記述します。 + + *文字列 + "(ダブルクォーテーション)で囲んだ文字は文字列として評価されます。 + "(ダブルクォーテーション)記号を扱いたい場合は\"と記述します。 + \記号を扱いたい場合は\\と記述します。 + なお表示関係の物に関しては^000000等の色変更を使うことができます。 + 変数 + "文字列"といった文字列結合もできます。 + + *単項演算子 + 以下の数値専用の単項演算子が用意されています。 + - 符号逆転(2の補数) + ~ ビット論理否定(1の補数) + ! 論理否定 + + *2項演算子 + 以下2項演算子は数値と文字列で動作が異なります。 + + 加算/結合 + 数値どうしの場合は加算します。 + それ以外の場合は文字列とみなして結合します。 + + 以下の2項演算子は数値専用です。 + - 減算 + * 乗算 + / 除算 + % 剰余 + & ビット論理積 + | ビット論理和 + ^ ビット排他的論理和 + && 論理積 + || 論理和 + + 以下の2項演算子は数値どうし、または文字列どうしの比較を行います。 + これらの関係演算子は関係が成り立つと1、成り立たないと0を返します。 + == 等しい + != 等しくない + > より大きい + >= より大きいか等しい(以上) + < より小さい(未満) + <= より小さいか等しい(以下) + + *変数 + 半角英数字を使用することができます。 + 変数のスコープとライフタイムはプレフィックスにより指定します。 + 小文字のエルはプレフィックスとして扱われるので注意してください。 + (小文字のエルは今後の動作を保障されないので使用しないで下さい) + + プレフィックス スコープ ライフタイム + (なし) キャラクター 永続的 + @ キャラクター 一時的 + l 同上 同上(推奨されない) + $ マップサーバー 永続的 + $@ マップサーバー 一時的 + # アカウント 永続的 + ## アカウント(全ワールド) 永続的 + + つまり、普通の一時的な変数は@, 保存する必要のある変数は + プレフィックスなし、全てのキャラクターで共有すべき変数は $、 + 同一アカウントで共有すべき変数は # や ## を使用することになります。 + + また、変数の型はポストフィックスにより指定します。 + ただし、文字列型はキャラクター一時変数、および、 + 永続的/一時的マップサーバー変数でのみ使用できます。 + (プレフィックス @、$、$@ ) + + ポストフィックス 型 + (なし) 整数 + $ 文字列 + + <例> @hoge$ 文字列型一時的キャラクター変数 + hoge 数値型永続的キャラクター変数 + $hoge 数値型永続的全キャラクター共有変数 + + 一時的でない変数は多用すべきではありません。 + 保存する必要のないものは極力一時変数で済ませるべきです。 + 保存する必要があるのかないのかはよく考慮してください。 + 特に永続的なキャラクター/アカウント変数は、数に制限があります。 + 使用が終わって二度と使用することがないとわかっている変数は + 値を0に設定することで削除することが出来ます。 + + *配列変数 + 変数名の後に括弧 [ ] で括った式を指定することで配列変数になります。 + 変数名と"["の間に空白文字を入れることはできません。 + + <例> hoge[10] fuga[ @temp ] + + 配列の要素番号は0〜127が指定できますが、番号0は同名の変数と + 値を共有します。たとえば、hoge[0] と hoge は同じ変数です。 + + 配列変数は一時的キャラクター変数、一時的/永続的マップサーバー変数で使用できます。 + 変数の型は数値、文字列両方とも利用できます。 + + *ラベル + 半角英数およびアンダーバーが使用できます。 + 変数や命令などと区別するため L_ を先頭につけることが推奨されます。 + L_hoge: といった風に使用します。 + if文やmenu文のジャンプ先に指定されます。 + + *定数 + athenaはdb/const.txtに準拠した定数を提供します。 + スクリプト内でのみ使用可能です。 + + *埋め込み変数 + 話かけたプレイヤーのステータスなどを参照できます。 + db/const.txtに記述されています。 + スクリプト内でのみ使用可能です。 + なお、一部を除いて値の代入はできません。 + + *式 + 命令文の引数が数値だった場合、そこで利用することができます。 + スペースは要らないようですがあった方が見易いです。 + 比較演算子及び論理演算子は値が真であったとき数値の1、偽であったとき0を返します。 + + *イベント + 形を持たないスクリプトです。 + タイムアタックなどの作成に使います。 + イベント名を記述する部分では、イベント名::ラベル名とすることでそのイベントの指定したラベルから + スクリプトを開始させることができます。 + + *命令構文 + 引数は半角スペースを空けて記述してください。 + +3.命令文及び関数及び定数ラベル + *命令文 + mes命令 + mes <string>; + + string 文字列 + + <string>に記述された文字列をメッセージウィンドウに出力します。 + + next命令 + next; + + メッセージウィンドウにnextボタンを表示し、待機します。 + + close命令 + close; + + メッセージウィンドウにcloseボタンを表示し、スクリプトを終了します。 + + menu命令 + menu <string1>,<label1>[,<stringN>,<labelN>...]; + + stringN 文字列 + labelN ラベル + + メニューを表示します。<stringN>に記述された文字列を選択すると<labelN>からスクリプトを開始します。 + また、選ばれたラベルの番号は変数@menuに代入されます。 + (l15にも代入されますが、こちらは今後動作が保障されないのでl15は使用しないで下さい) + + goto命令 + goto <label>; + + label ラベル + + <label>からスクリプトを開始します。 + + cutin命令 + cutin <filename>,<position>; + + filename 文字列 + position 数値 + + カプラ職員などのカットインを表示します。<filename>は表示したいファイル名、<position>は表示位置を指定します。 + position:0,左下、1,中央下、2,右下、255,カットイン消去 + + jobchange命令 + jobchange <job>[, <upper>]; + + job 数値 + upper 数値 + + 職業を変更します。<job>はdb/const.txtを参照してください。 + <upper>は0=通常,1=転生,2=養子,-1 or 無し=現在の<upper>になります。 + jobLvは自動で1になります。 + バードとダンサーには注意してください。 + + input命令 + input [<variable>]; + + variable 変数、省略可 + + 入力ウィンドウを開き、入力データを<variable>に代入します。 + 変数の型が文字列型のときは文字列入力ウィンドウ、整数型のときは数値入力ウィンドウになります。 + <variable>を省略した場合には数値入力ウィンドウを出し、データは変数l14 (小文字のエル+14)に代入されます。 + (l14は今後の動作が保障されないので、引数は省略しないで下さい) + + warp命令 + warp <gatname>,<x>,<y>; + + gatname 文字列 + x,y 数値 + + <gatname>に指定されたMAPの座標<x>,<y>にワープします。 + <gatname>をSavePointにした場合、セーブポイントに移動します。 + Randomにした場合、そのMAP内のどこかに移動します。即ち<x><y>は無視。 + + setlook命令 + setlook <n1>,<n2>; + + n1,n2 数値 + + 外見を変更します。<n1>は部品を、<n2>は種類を指定します。 + n1:1,髪型、2,武器、3,頭上段、4,頭中段、5,頭下段、6,髪色、7,服色、8,盾 + 装備品は変更されません。アサシン男とローグ男に関しては服の色がありません。 + + + set命令 + set <variable>,<n>; + + variable 変数 + n 数値/文字列 + + <variable>に<n>を代入します。 + 文字列型を使用するときは変数名にポストフィックスを忘れないで下さい。 + + setarray命令 + setarray <variable>[,<n0>[,<n1>…]]; + + variable 変数 + nx 数値/文字列 + + 配列<variable>に値<n0>,<n1>,…のリストを代入します。 + <variable>は配列名を指定すると最初から、要素番号も指定すれば途中から代入できます。 + <例> setarray @hoge[2],16,24,32; @hogeの要素2から4までを16,24,32にする。 + + cleararray命令 + cleararray <variable>,<n>,<count>; + + variable 変数 + n 数値/文字列 + count 数値 + + 配列<variable>に値<n>を<count>個分代入します。 + <variable>は配列名を指定すると最初から、要素番号も指定すれば途中から代入できます。 + <例> cleararray @hoge[3],0,6; @hogeの要素3から8までを0にセットする + + copyarray命令 + copyarray <var1>,<var2>,<n>; + + <var1>,<var2> 変数 + n 個数 + + 配列<var1>に配列<var2>の要素<n>個をコピーします。 + <var1>,<var2>は配列名を指定すると最初から、要素番号も指定すれば途中からコピーできます。 + + deletearray命令 + + deletearray <variable>,<n>; + + variable 変数 + n 数値 + + 配列<variable>から<n>個の要素を削除し、後ろの要素を前に詰める + <variable>は配列名を指定すると最初から、要素番号も指定すれば途中から削除できます。 + + if命令 + if (<cond>) goto <label>; + + cond 数値 + label ラベル + + <cond>が0以外の場合、<label>からスクリプトを開始します。 + + getitem命令 + getitem <itemid>,<num>; + + itemid 数値または文字列 + num 数値 + + <itemid>に指定されたアイテムIDを持つアイテムを<num>個分入手します。 + <itemid>が文字列の場合、その名前(name,jname)を持つアイテムのIDを使用します。 + ただし、アイテム名はitem_db.txtなどに依存するため、テスト目的以外では使用すべきではありません。 + + getitem2命令 + getitem <itemid>,<num>,<identify>,<refine>,<attribute>,<card1>, + <card2>,<card3>,<card4> + + itemid 数値または文字列 + num,identify,refine,attribute,card1,card2,card3,card4 数値 + + <itemid>に指定されたアイテムIDを持つアイテムを<num>個分入手します。 + <identify> 鑑定状態(0で未鑑定、1で鑑定) + <refine> 精錬値 + <attribute> アイテムの状態 + <card1> 差さているカード、製造武器なら255で装備以外のアイテムでキャラの名前を入れたい時は254 + <card2> 差さているカード、製造武器なら属性と星のかけらの数を設定。星のかけらの数(範囲:0~3)*5*256 + 属性(無:0、火:3、水:1、風:4、土:2) + <card3> 差さているカード、製造武器やキャラの名前が入るアイテムはキャラIDの下の2バイト + <card4> 差さているカード、製造武器やキャラの名前が入るアイテムはキャラIDの上の2バイト + + <itemid>が文字列の場合、その名前(name,jname)を持つアイテムのIDを使用します。 + ただし、アイテム名はitem_db.txtなどに依存するため、テスト目的以外では使用すべきではありません。 + + delitem命令 + delitem <itemid>,<num>; + + itemid_num 数値 + + <itemid>に指定されたアイテムIDを持つアイテムを<num>個分失います。 + + getexp命令 + getexp <base_exp>,<job_exp>; + + base_exp 数値 + job_exp 数値 + + <base_exp>に指定された数だけBaseに経験値が入ります。 + <job_exp>に指定された数だけJobに経験値が入ります。 + マイナスの数字は適応されません。 + + makepet命令 + makepet <petid>; + + petid 数値 + + <petid>に指定されたアイテムIDを持つペットの卵を作成します。 + + viewpoint命令 + viewpoint <type>,<x>,<y>,<id>,<color>; + + type,x,y,id 数値 + color 不明 + + 資料不足なので詳しい説明はできませんが、ミニMAPに点滅する点を表示、削除します。 + type:1,表示、2,削除 + + heal命令 + heal <hp>,<sp>; + + hp,sp 数値 + + <hp>分HPを、<sp>分SPを回復します。 + + itemheal命令 + itemheal <hp>,<sp>; + + hp,sp 数値 + + <hp>分HPを、<sp>分SPを回復します。healとは違い回復するHPとSPの量にVIT(SPの場合INT)とスキルによる補正が付きます。 + + end命令 + end; + + スクリプトの実行を終了します。 + + setoption命令 + setoption <string>; + + string 文字列 + + 引用: + PCに以下で示す付属品(?)を付けます。 + 0x0000 - 付属品削除 + 0x0001 - ? + 0x0002 - ハイド(影付き) + 0x0004 - ?? + 0x0008 - カート + 0x0010 - 鷹 + 0x0020 - ペコペコ(ナイト,クルセイダーの時のみ有効) + 0x0040 - ハイド(影無し) + 0x0080 - カート2 + 0x0100 - カート3 + 0x0200 - カート4 + 0x0400 - カート5 + 0x0800 - 頭がオーク(Sageのスキル、リバースオーキッシュがかかった状態になる) + + 一端すべて消されてから付け直すので、複数指定したい場合には合計を指定してください。 + + savepoint命令 + savepoint <gatname>,<x>,<y>; + + gatname 文字列 + x,y 数値 + + <gatname>の座標<x>,<y>をセーブポイントに設定します。 + + openstorage命令 + openstorage; + + 倉庫を開きます。 + + setcart命令 + setcart; + + カートを付けます。 + + successrefitem命令 + successrefitem <n>; + + n 数値 + + 精錬成功エフェクトを表示します。 + n:1,頭装備、2,鎧、3,左手、4,右手、5,かけるもの、6,靴、7,アクセ1、8,アクセ2、9,頭中段、10,頭下段 + + failedrefitem命令 + failedrefitem <n>; + + n 数値 + + 精錬失敗エフェクトを表示します。 + n:1,頭装備、2,鎧、3,左手、4,右手、5,かけるもの、6,靴、7,アクセ1、8,アクセ2、9,頭中段、10,頭下段 + + setfalcon命令 + setfalcon; + + 鷹を付けます。 + + setriding命令 + setriding; + + ペコペコに乗ります。 + + monster命令 + monster <gatname>,<x>,<y>,<mobname>,<mobid>,<num>[,<event>]; + + gatname,mobname 文字列 + x,y,mobid,num 数値 + event 文字列、省略可 + + <gatname>の座標<x>,<y>に<mobname>を持つ<mobid>に指定されたIDを持つモンスターを<num>体出現させます。 + <gatname>がthisの場合スクリプトを実行したプレイヤーがいるMAP、 + <x><y>が-1の場合、スクリプトを実行したプレイヤーの座標、 + <mobname>が--en--の場合英語名、--ja--の場合日本語名、<mobid>が-1の場合ランダム。 + そのモンスターを倒したとき<event>を開始します。 + + announce命令 + announce <string>,<flag>; + + string 文字列 + flag 数値 + + <string>をGMアナウンスで表示します。 + <flag>は以下に示す通りです。 + エリアフラグ + 0x00 すべてのMAPに送信 + 0x01 同じMAP + 0x02 画面内 + 0x03 自分のみ + 0x04 同じMAP鯖 + 色フラグ + 0x00 黄色 + 0x10 青色 + 特殊フラグ + 0x00 特に無し + 0x08 イベント用 + + エリアフラグと色フラグと特殊フラグの合計を指定してください。 + + killmonster命令 + killmonster <gatname>[,<event>]; + + gatname 文字列 + event 文字列、省略可 + + <gatname>に存在するモンスターをすべて殺します。 + <event>によって呼び出されたモンスターのみ殺すことも可能です。<event>をAllにするとそのマップにある一時的に召喚されたモンスターを全て殺します。 + + killmonsterall命令 + killmonsterall <gatname> + + gatname 文字列 + + <gatname>に存在するモンスターをすべて殺します。killmonsterとは違ってそのマップに始めから配置されていたモンスターまで全て殺すことができます。 + + + addtimer命令 + addtimer <ms>,<event>; + + ms 数値 + event 文字列 + + 現在のプレイヤーに、<ms>ミリ秒経過した後<event>を開始するタイマーを作成します。 + このタイマーによって実行されるイベントは、このプレイヤーの情報にアクセスできます。 + プレイヤーがログアウトするとタイマーは無効になるため注意してください。 + アリーナマップなどで使用する場合はこれではなくNPCタイマーを使用してください。 + + deltimer命令 + deltimer <event>; + + event 文字列 + + 現在のプレイヤーの<event>を開始するタイマーを消去します。 + + addtimercount命令 + addtimercount <event>,<ms>; + + event 文字列 + ms 数値 + + 現在のプレイヤーの<event>を開始するタイマーの開始までの時間を<ms>ミリ秒追加します。 + + initnpctimer命令 + initnpctimer [<name>]; + + <name>で指定されたNPCが持つNPCタイマーの値を0にし、カウントを開始します。 + nameを省略すると、命令を実行したNPCが対象になります。 + + このNPCタイマーはミリ秒単位でOnTimerXXXXというラベルイベントを実行します。 + <例> OnTimer1000: <= 1秒後, OnTimer30000: <= 30秒後 + + stopnpctimer命令 + stopnpctimer [<name>]; + + <name>で指定されたNPCが持つNPCタイマーのカウントを停止します。 + nameを省略すると、命令を実行したNPCが対象になります。 + + startnpctimer命令 + stopnpctimer [<name>]; + + <name>で指定されたNPCが持つNPCタイマーのカウントを再開します。 + nameを省略すると、命令を実行したNPCが対象になります。 + こちらはinitnpctimerと違い、カウントを0にリセットしません。 + stopnpctimerとセットで使用します。 + + setnpctimer命令 + setnpctimer <tick>[,<name>] + + <name>で指定されたNPCが持つNPCタイマーのカウントを変更します。 + nameを省略すると、命令を実行したNPCが対象になります。 + getnpctimerを利用すれば、カウントを増減できます。 + + disablenpc命令 + disablenpc <npcname>; + + npcname 文字列 + + <npcname>を無効にします。 + + enablenpc命令 + enablenpc <npcname>; + + npcname 文字列 + + <npcname>を有効にします。 + + mapannounce命令 + mapannounce <gatname>,<string>,<flag>; + + gatname,string 文字列 + flag 数値 + + <gatname>全体に<string>をGMアナウンスで表示します。 + flag:0,黄色文字、16,青文字 + + areaannounce命令 + areannounce <gatname>,<x0>,<y0>,<x1>,<y1>,<string>,<flag>; + + gatname,string 文字列 + x0,y0,x1,y1,flag 数値 + + <gatname>の<x0>,<y0>から<x1>,<y1>の範囲内に対し<string>をGMアナウンスで表示します。 + flag:0,黄色文字、16,青文字 + + areawarp命令 + areawarp <gatname>,<x0>,<y0>,<x1>,<y1>,<gatname2>,<x>,<y>; + + gatname,gatname2 文字列 + x0,y0,x1,y1,x,y 数値 + + <gatname>の<x0>,<y0>から<x1>,<y1>の範囲内にいるPCを<gatname2>の座標<x>,<y>に移動させます。 + + areamonster命令 + areamonster <gatname>,<x0>,<y0>,<x1>,<y1>,<mobname>,<mobid>,<num>[,<event>]; + + gatname,mobname 文字列 + x0,y0,x1,y1,mobid,num 数値 + event 文字列、省略可 + + <gatname>の<x0>,<y0>から<x1>,<y1>の範囲内に<mobname>を持つ<mobid>に指定されたIDを持つモンスターを<num>体出現させます。 + <gatname>がthisの場合スクリプト実行MAP、<x><y>が-1の場合ランダム座標、 + <mobname>が--en--の場合英語名、--ja--の場合日本語名、<mobid>が-1の場合ランダム。 + そのモンスターを倒したとき<event>を開始します。 + + percentheal命令 + percentheal <hp>,<sp>; + + hp,sp 数値 + + HPとSPを<hp>%、<sp>%分回復します。 + + resetstatus命令 + resetstatus; + + ステータスポイントをリセットします。 + + resetskill命令 + resetskill; + + スキルポイントをリセットします。 + + statusup命令 + statusup <st>; + + st 数値 + + <st>で指定された基本ステータス値を、ステータスポイントを消費して1上げる。 + <st>は bStr, bVit, bInt, bAgi, bDex, bLuk で指定する。 + + statusup2命令 + statusup2 <st>,<n>; + + st,n 数値 + + <st>で指定された基本ステータス値を、ステータスポイントを消費せずに<n>上げる。 + <st>は bStr, bVit, bInt, bAgi, bDex, bLuk で指定する。 + + skill命令 + skill <skillid>,<skilllv>,<flag>; + + skillid,skilllv,flag 数値 + + <skillid>に指定されたIDを持つLV<skilllv>スキルを習得します。 + flag:0,恒久的なスキル取得(スキルツリーにない物は無効)、1,一時的(範囲は不明)な取得、 + <skilllv>を0にするとこでそのスキルを忘れさせることも可能です。 + + waitingroom命令 + waitingroom <title>,<limit>[,<event>[,<trigger>]]; + + title 文字列 + limit 数値 + event 文字列、省略可 + trigger 数値、省略可 + + <title>をタイトルとしてチャットルームを表示させます。 + <trigger>を満たしたとき、<event>を動作させることが可能です。 + <trigger>を省略すると<limit>の数値が使用されます。 + + delwaitingroom命令 + delewaitingroom [<name>] + + name 文字列、省略可 + + 指定したNPCのチャットルームを閉じます。 + <name>を省略すると、命令を実行したNPCが対象になります。 + + disablewaitingroomevent命令 + disablewaitingroomevent [<name>] + + name 文字列、省略可 + + <name>で指定したNPCのチャットルームのイベントを無効にします。 + <name>を省略すると、命令を実行したNPCが対象になります。 + + enablewaitingroomevent命令 + enablewaitingroomevent [<name>] + + name 文字列、省略可 + + <name>で指定したNPCのチャットルームのイベントを有効にします。 + <name>を省略すると、命令を実行したNPCが対象になります。 + また、既にイベントが起こる人数に達している場合、 + 即座にイベントを実行します。 + + warpwaitingnpc命令 + warpwaitingnpc <gatname>,<x>,<y>[,<num>]; + + gatname 文字列 + x,y,num 数値 + + 命令を実行したNPCのチャットルームに入っているPCのうち、 + <num>で指定した人数を、<gatname>の座標<x>,<y>に移動させます。 + <num>を省略すると waitingroomの<trigger>で指定した人数を使用します。 + + ワープさせた人数を $@warpwaitingpcnum に、ワープさせた人のアカウントIDを + 配列 $@warpwaitingpc にセットします(先頭から人数分)。 + + + emotion命令 + emotion <n>; + + n 数値 + + <n>エモーションを出します。 + + setmapflag命令 + setmapflag <gatname>,<flag>; + + gatname 文字列 + flag 数値 + + <gatname>のmapflagを追加します。 + <flag>はdb/const.txtを参照してください。 + + removemapflag命令 + removemapflag <gatname>,<flag>; + + gatname 文字列 + flag 数値 + + <gatname>のmapflagを消去します。 + <flag>はdb/const.txtを参照してください。 + + pvpon命令 + pvpon <gatname>; + + gatname 文字列 + + <gatname>をPVP可能MAPにします。 + + pvpoff命令 + pvpoff <gatname>; + + gatname 文字列 + + <gatname>をPVP不可MAPにします。 + + gvgon命令 + gvgon <gatname>; + + gatname 文字列 + + <gatname>をシーズモードにします。 + + gvgoff命令 + gvgoff <gatname>; + + gatname 文字列 + + <gatname>を非シーズモードにします。 + + setmapflagnosave命令 + setmapflagnosave <gatname>,<savegatname>,<x>,<y>; + + gatname,nosavegat 文字列 + x,y 数値 + + <gatname>のmapflagにnosave、引数として<savegatname>,<x>,<y>を設定します。 + + detachrid命令 + detachrid; + + NPCにアタッチされているIDをクリアします。 + 以後、キャラクター情報を必要とする命令が実行できなくなります。 + + doevent命令 + doevent <name>; + + name 文字列 + + プレイヤー主体のイベントを起こします。<name>にはイベント名を指定します。 + プレイヤーが他のNPCと会話中などで実行できない場合、キューに入り、実行可能になるまで待って実行されます。 + キューのサイズはとても小さいので、連続で起こるとイベントが無視されるかもしれません。 + ラベル付きイベントも指定できますが、NPC名を省略できないので注意してください。 + + donpcevent命令 + donpcevent <name>; + + name 文字列 + + プレイヤーがアタッチされていない(NPC主体の)イベントを起こします。 + <name>にはイベント名を指定します。イベントは即座に実行されます。 + ラベル付きイベントも指定でき、NPC名を省略することで、イベントをブロードキャストできます。 + (複数のNPCの同じ名前のラベルを実行できる。 例>"::OnEvent") + + callsub命令 + callsub <label> + + label ラベル + + 同一スクリプト内のラベル<label>をサブルーティンとして実行します。 + サブルーティンから復帰するときはreturn命令を使用してください。 + この命令はreturn命令の実行状況によっては関数として使用することも出来ます。 + + callfunc命令 + callfunc <func> + + func 文字列 + + <func>で定義されたユーザー定義関数を実行します。 + ユーザー定義関数から復帰するときはreturn命令を使用してください。 + この命令はreturn命令の実行状況によっては関数として使用することも出来ます。 + + return命令 + return <retval> + + retval 数値または文字列、省略可 + + 直ちにサブルーティンもしくはユーザー定義関数を抜け、呼び出し元に戻ります。 + <retval>は戻り値で、callsubまたはcallfuncが関数として呼び出されているとき、この戻り値が使用されます。 + callsubまたはfuncが命令として呼ばれているときは、<retval>は省略してください。 + + *関数 + rand関数 + rand(<n1>[,<n2>]) + + n1 数値 + n2 数値、省略可 + + <n1>のみ指定されていた場合、0から<n1>-1までの数値をランダムに選んで返します。 + <n2>を指定した場合、<n1>から<n2>までの数チをランダムに選んで返します。 + + getitemname関数 + getitemname(<itemid>) + + itemid 数値 + + <itemid>に指定されたIDを持つアイテムのjnameを文字列で返します。 + なお、アイテム名はitem_db.txtを参照します + + countitem関数 + countitem(<itemid>) + + itemid 数値または文字列 + + <itemid>に指定されたIDを持つアイテムの所持数を返します。 + <itemid>が文字列の場合、その名前(name,jname)を持つアイテムのIDを使用します。 + ただし、アイテム名はitem_db.txtなどに依存するため、テスト目的以外では使用すべきではありません。 + + checkweight関数 + checkweight(<itemid>,<num>) + + itemid 数値または文字列 + num 数値 + + <itemid>に指定されたIDを持つアイテムを<num>個持つことができるのならば1を、 + できなければ0を返します。 + <itemid>が文字列の場合、その名前(name,jname)を持つアイテムのIDを使用します。 + ただし、アイテム名はitem_db.txtなどに依存するため、テスト目的以外では使用すべきではありません。 + + strcharinfo関数 + strcharinfo(<n>) + + n 数値 + + キャラ情報を返します。 + n:0,キャラ名、1,パーティー名、2,ギルド名 + + getequipname関数 + strcharinfo(<n>) + + n 数値 + + 装備品名を返します。 + n:1,頭装備、2,鎧、3,左手、4,右手、5,かけるもの、6,靴、7,アクセ1、8,アクセ2、9,頭中段、10,頭下段 + + getequipisequiped関数 + getequipisequiped(<n>) + + n 数値 + + 装備していたら1、していなかったら0を返します。 + n:1,頭装備、2,鎧、3,左手、4,右手、5,かけるもの、6,靴、7,アクセ1、8,アクセ2、9,頭中段、10,頭下段 + + getequipisenableref関数 + getequipisenableref(<n>) + + n 数値 + + 精錬できる場合は1、できない場合は0を返します。 + n:1,頭装備、2,鎧、3,左手、4,右手、5,かけるもの、6,靴、7,アクセ1、8,アクセ2、9,頭中段、10,頭下段 + + getequipisidentify関数 + getequipisidentify(<n>) + + n 数値 + + 鑑定済みの場合1、未鑑定の場合0を返します。 + n:1,頭装備、2,鎧、3,左手、4,右手、5,かけるもの、6,靴、7,アクセ1、8,アクセ2、9,頭中段、10,頭下段 + + getequiprefinerycnt関数 + getequiprefinerycnt(<n>) + + n 数値 + + 精錬の度合いを返します。 + n:1,頭装備、2,鎧、3,左手、4,右手、5,かけるもの、6,靴、7,アクセ1、8,アクセ2、9,頭中段、10,頭下段 + + getequipweaponlv関数 + getequipweaponlv(<n>) + + n 数値 + + 武器LVを返します。防具の場合には0、あとは武器LVに対応します。 + n:1,頭装備、2,鎧、3,左手、4,右手、5,かけるもの、6,靴、7,アクセ1、8,アクセ2、9,頭中段、10,頭下段 + + getequippercentrefinery関数 + getequippercentrefinery(<n>) + + n 数値 + + 精錬成功率を返します。 + n:1,頭装備、2,鎧、3,左手、4,右手、5,かけるもの、6,靴、7,アクセ1、8,アクセ2、9,頭中段、10,頭下段 + + getusers関数 + getusers(<n>) + + n 数値 + + 人数を返します。 + n:0,PCのいるMAPの総人数、1,全MAPの総人数(即ちログイン人数)、8,NPCの存在するMAPの総人数 + + getmapusers関数 + getmapusers(<gatname>) + + gatname 文字列 + + <gatname>に存在する総人数を返します。 + + getareausers関数 + getareausers(<gatname>,<x0>,<y0>,<x1>,<y1>) + + gatname 文字列 + x0,y0,x1,y1 数値 + + <gatname>の<x0>,<y0>から<x1>,<y1>の範囲内にいる人数を返します。 + + getskilllv関数 + getskilllv(<skillid>) + + skillid 数値 + + <skillid>で指定したIDを持つスキルのLVを返します。習得していない場合は0を返します。 + + getcharid関数 + getcharid(<n>) + + n 数値 + + キャラ情報をIDで返します。 + n=0 キャラID + n=1 パーティー + n=2 ギルド + n=3 アカウントID + + getpartyname関数 + getpartyname(<n>) + + n 数値 + + <n>で指定したIDを持つパーティー名を返します。 + + getguildname関数 + getguildname(<n>) + + n 数値 + + <n>で指定したIDを持つギルド名を返します。 + + getguildmaster関数 + getguildname(<n>) + + n 数値 + + <n>で指定したIDを持つギルドのマスターの名前を返します。 + + getguildmasterid関数 + getguildmasterid(<n>) + + n 数値 + + <n>で指定したIDを持つギルドのマスターのキャラクターIDを返します。 + + basicskillcheck関数 + basicskillcheck(0); + + battle_athena.confのbasic_skill_checkの設定値を返します。0は意味はありませんが何も入れなかった場合エラーになります。 + basic_skill_checkとカプラの倉庫利用を合わせる為に作った物でそれ以外の機能はありません。戻った数値が0ならbasic_skill_checkがno、1ならyesです。 + + getgmlevel関数 + getgmlevel(0); + + プレイヤーのGMレベルを返します。 + + guildopenstorage関数 + guildopenstorage(0); + + ギルド倉庫を開きます。 + 返た数値が2ならギルドに所属してないキャラと言う意味で + 1なら他のギルドメンバーが倉庫を使用中の意味です。 + 0なら成功的にギルド倉庫が開いたとのことです。 + + getwaitingroomstate関数 + getwaitingroomstate(<num>,[<name>]) + + num 数値 + name 文字列、省略可 + + <name>で指定したNPCのチャットルームの状態を返します。 + <name>を省略すると、命令を実行したNPCが対象になります。 + <num>で得たい情報を指定します。 + + num=0 現在チャットルームに入っている人数(数値) + num=1 チャットルームの限界人数(数値) + num=2 チャットルームのイベントを起こす人数(数値) + num=3 チャットルームのイベントが有効かどうか(数値) + num=4 チャットルームのタイトル(文字列) + num=5 チャットルームのパスワード(文字列) + num=16 チャットルームのイベント名(文字列) + num=32 チャットルームが満員かどうか(数値) + num=33 チャットルームでイベントが起こる人数かどうか(数値) + + getnpctimer関数 + getnpctimer(<num>[,<name>]) + + num 数値 + + <name>で指定されたNPCが持つNPCタイマーの情報を得ます。 + nameを省略すると、命令を実行したNPCが対象になります。 + <num>で得たい情報を指定します。 + + num=0 現在のNPCタイマーのカウント値 + num=1 現在NPCタイマーが動作しているかどうか + num=2 指定NPCのタイマーイベントラベルの総数 + + attachrid関数 + attachrid(<num>) + + num 数値 + + <num>で指定されたIDのキャラクターを実行したスクリプトにアタッチします。 + 以後、キャラクターに関する命令や関数/変数などは全て新しいキャラクターが対象になります。 + これはスクリプトが終了/中断する(close,end,menu,next,inputなどの実行)まで有効です。 + 主にイベントで起動されたスクリプト内でマップ変数を使って別キャラクターを + アタッチするのに使用します。getcharid(3)で所得したアカウントIDを使ってください。 + なお、デタッチにはdetachrid命令を使います。 + + 注意すべき点としては、この命令でPCをアタッチした場合、mes,menu,nextなどの + ウィンドウ(やボタン)が出る命令を実行してはいけません。 + 相手が他のNPCと会話中の場合、これらの命令は正しく動作しません。 + 情報所得命令などだけで済ませるべきです。 + + この関数はアタッチに成功したかどうかを返します。 + 偽(0)が返って来た場合は、該当キャラクターが存在していません。 + + isloggedin関数 + isloggedin(<num>) + + num 数値 + + <num>で指定されたIDのキャラクターがこのマップサーバーに + ログインしているかどうか調べます。 + + getarraysize関数 + getarraysize(<variable>) + + variable 変数 + + 配列<variable>の有効なサイズを調べます。 + ここでのサイズは要素が0(文字列変数では"")でない、 + 最大の要素番号+1 になります。 + 配列名ではなく要素番号付きで指定すると、 + 少なくともその要素までは全て有効であると仮定します。 + <例> 配列@hogeが 1,2,3,4,5 だとすると、 + getarraysize(@hoge)=5, getarraysize(@hoge[10])=10; + + callsub関数 + callsub <label> + + callsub命令を関数として実行します。詳しくはcallsub命令を見てください。 + + callfunc関数 + callfunc <func> + + callfunc命令を関数として実行します。詳しくはcallfunc命令を見てください。 + + *定数ラベル + -ラベル + if文やmenu文で使用します。次の行からスクリプトを開始します。 + + OnInitラベル + MAPがロードされたときスクリプトを開始します。 + + OnInterIfInitラベル + MAPサーバーがInterサーバーに接続したときに実行します。 + + OnCharIfInitラベル + MAPサーバーがCharサーバーに接続したときに実行します。 + + OnMinuteXXラベル + 毎時XX分に実行します。数値は十進数2桁です。 + + OnClockXXXXラベル + 毎日XX時XX分に実行します。数値は十進数4桁です。 + + OnHourXXラベル + 毎日XX時00分に実行します。数値は十進数2桁です。 + + OnDayXXラベル + 毎月XX日00時00分に実行します。数値は十進数2桁です。 + + OnTimerXラベル + NPCタイマーのカウントがXになったときに実行されます。 + このXはミリ秒単位です。桁数は関係ありません。 + + OnAgitInitラベル + ギルド城データと占拠ギルド情報がマップサーバー内に + 所得されたときに実行されます。 + ギルド城関係のNPCの初期化に使用します。 + + OnAgitStartラベル + ギルド攻城戦が始まったときに実行されます。 + + OnAgitEndラベル + ギルド攻城戦が終わったときに実行されます。 + + OnAgitBreakラベル + エンペリウムを破壊したときに実行されます。 + このラベルは破壊したプレイヤーを主体にして実行されます。 + + OnAgitEliminateラベル + エンペリウム破壊後、ギルドの所有者が書き換わるときに + 呼ばれます。 + + *注意事項 + 文字列と説明されている引数は""で囲ってください。 + +4. Error Message + + * Make an error at the time of compile (it is a thing at the time of map server starting). + A place is displayed for the line number of an error. + + unexpected expr end + It is the end of an unexpected formula. + ', ', and';' are in the beginning of a formula. + + unmatch ')' + ')' does not match. + Correspondence of parenthesis'('')' is amusing. + + unexpected newline @ string + It is the new-line which is not expected in a character sequence. + There is a new-line in the middle of a character sequence (surrounded by '"'). + Probably it is a failure of '"' to close. + + unexpected eof @ string + It is the file terminus which is not expected in a character sequence. + The file finished in the middle of the character sequence. + Probably it is a failure of '"' to close. + + unexpected character + unexpected char + It is an unexpected character. + It is thought that the variable etc. is not following a naming rule. + + l14 and l15 is DEPRECATED. use @menu instead of l15. + l14 and l15 are not recommended. Please use @menu instead of l15. + + prefix 'l' is DEPRECATED. use prefix '@' instead. + Prefix'l' is not recommended. Please use '@' instead. + + unmatch ']' + ']' does not carry out an interval. + Correspondence of parenthesis']' is missing. + + expect function + 関数を期待していました + 関数呼び出し演算子'('の前に関数以外のシンボルがあります。 + おそらく関数名を間違えています。 + + expect ',' or ')' at func params + 関数の引数において','か')'を期待していました + おそらく引数区切りの','か')'を忘れています。 + + func request '(' ')' + 関数呼び出しの括弧対応問題 + おそらく引数の数が128を超えました。 + + illeagal number of parameters + パラメータの数が不正です + 関数/命令パラメータの個数が異なります。 + 引数の個数を確認してください。 + エラー位置は全ての引数の後になります。 + + expect command + 命令を期待していました + 命令以外のシンボルが突然出現しています。 + おそらく命令名を間違えています。 + + expect ',' or ';' at cmd params + 命令の引数において','か';'を期待していました + おそらく引数区切りの','か';'を忘れています。 + + need ';' + ';'が必要です + おそらく引数の数が128を超えました。 + + 実行時のエラー + fatal error ! player not attached! + 致命的エラー!プレイヤーがアタッチされていません! + + キャラクターを特定できないイベントで実行されている + スクリプトの中でキャラクター情報が必要な命令や関数、 + 変数へアクセスしました。attachrid関数を使うか、 + キャラクター情報が不要な命令を使用してください。 + もしくは、attachrid関数で設定された情報が不正です。 + なお、このエラーが起こると直後にコアを吐くと思われます。 + + NPC主体イベントでannounceしたときにフラグ0x08を指定していない + 場合もこのエラーがでます。 + + + illeagal scope string variable. + 文字列変数のスコープが不正です。 + 未対応のプレフィックスで文字列変数が使用されました。 + プレフィックスを確認してください。 + + illeagal scope + スコープが不正です。配列変数未対応のプレフィックスを + 持つ変数に配列変数系の命令を実行した場合など。 + + not label ! + goto/menu命令でラベルが指定されるべきところに + ラベル以外のシンボルが指定されています。 + もしくは、ラベル名と変数名がバッティングしています。 + + buildin_set: not name + set命令で第一引数が変数名ではありません。 + + getelementofarray (operator[]): param2 illeagal number + 配列変数で[]内の値が不正です + []内の値が0未満か128以上になりました + + getelementofarray (operator[]): param1 not name + 配列変数で[]の前のシンボルが変数名ではありません。 + + op_2: int&str, str&int not allow. + 関係演算子(比較演算子)で、数値と文字列、もしくは + 文字列と数値が指定されました。 + + infinity loop ! + スクリプトの実行命令数か、goto/menu命令実行回数が + 多すぎるので、無限ループと判断しました。 + スクリプトの実行は強制的に中断されました。 + + not function and command ! + 関数実行/命令実行部分で、関数でも命令でもない + シンボルがありました。 + if文のなかである可能性が高いです。 + + return without callfunc or callsub ! + callfuncやcallsubされていないのにreturn命令を実行しました。 + + stack.sp(?) != default(?) + スタックポインタが基準スタックポインタと異なっています。 + 命令を実行した結果、スタックポインタが狂いました。 + 関数を命令として実行した可能性があります。 + + +5. Postscript + NPC contained in snapshot was made reference in creating this text. + I appreciate people which created NPC. + +It corrects based on text by asong (2004/3/1). + + diff --git a/eathena-monitor b/eathena-monitor Binary files differnew file mode 100755 index 0000000..fd1d36c --- /dev/null +++ b/eathena-monitor diff --git a/eathena.sh b/eathena.sh new file mode 100755 index 0000000..edfabf9 --- /dev/null +++ b/eathena.sh @@ -0,0 +1,52 @@ +#!/bin/bash + +# $Id: eathena.sh,v 1.5 2006/03/10 21:41:09 Platyna Exp $ +#---------------------------------------------------------------------- +# Description: Simple script to control eAthena locally. +# Author: Zuzanna K. Filutowska <platyna@platinum.linux.pl> +# Created at: Fri Feb 17 18:23:56 CET 2006 +# License: GPL +# Copyright (c) 2006 Zuzanna K. Filutowska All rights reserved. +# +#---------------------------------------------------------------------- +# Configure section: +PATH=$PATH:. +SRVHOMEDIR=$HOME/tmwserver +#---------------------------------------------------------------------- +# main() + +cd ${SRVHOMEDIR} + +eathena_start() { + if [ -x ${SRVHOMEDIR}/eathena-monitor ]; + then echo "Starting eathena monitor..." + ${SRVHOMEDIR}/eathena-monitor + else echo "Eathena monitor binary is not executable or not found." + fi +} + +eathena_stop() { + echo "Shutting down eathena monitor..." + killall eathena-monitor +} + +eathena_restart() { + eathena_stop + echo "Waiting for all eathena processes to end..." + sleep 1 + eathena_start +} + +case "$1" in +'start') + eathena_start + ;; +'stop') + eathena_stop + ;; +'restart') + eathena_restart + ;; +*) + echo "usage $0 start|stop|restart" +esac Binary files differdiff --git a/login-server b/login-server Binary files differnew file mode 100644 index 0000000..768713c --- /dev/null +++ b/login-server diff --git a/map-server b/map-server Binary files differnew file mode 100644 index 0000000..255dcb3 --- /dev/null +++ b/map-server diff --git a/misc/scripts/char-server.sh b/misc/scripts/char-server.sh new file mode 100644 index 0000000..77931f9 --- /dev/null +++ b/misc/scripts/char-server.sh @@ -0,0 +1,4 @@ +#!/bin/bash +while [ true ] ; do + nice -n 19./char-server +done diff --git a/misc/scripts/char_start b/misc/scripts/char_start new file mode 100644 index 0000000..425140f --- /dev/null +++ b/misc/scripts/char_start @@ -0,0 +1,3 @@ +#!/bin/bash +cd /home/tmw-server/tmw-server +./char-server >/dev/null & diff --git a/misc/scripts/login-server.sh b/misc/scripts/login-server.sh new file mode 100644 index 0000000..8a3fe69 --- /dev/null +++ b/misc/scripts/login-server.sh @@ -0,0 +1,4 @@ +#!/bin/bash +while [ true ] ; do + nice -n 19 ./login-server +done diff --git a/misc/scripts/login_start b/misc/scripts/login_start new file mode 100644 index 0000000..f3630d3 --- /dev/null +++ b/misc/scripts/login_start @@ -0,0 +1,3 @@ +#!/bin/bash +cd /home/tmw-server/tmw-server/ +./login-server >/dev/null & diff --git a/misc/scripts/map-server.sh b/misc/scripts/map-server.sh new file mode 100644 index 0000000..7105add --- /dev/null +++ b/misc/scripts/map-server.sh @@ -0,0 +1,4 @@ +#!/bin/bash +while [ true ] ; do + nice -n 19 ./map-server +done diff --git a/misc/scripts/map_start b/misc/scripts/map_start new file mode 100644 index 0000000..c498ac1 --- /dev/null +++ b/misc/scripts/map_start @@ -0,0 +1,3 @@ +#!/bin/bash +cd /home/tmw-server/tmw-server/ +./map-server >/dev/null & diff --git a/misc/scripts/object_del.bat b/misc/scripts/object_del.bat new file mode 100644 index 0000000..a93e53f --- /dev/null +++ b/misc/scripts/object_del.bat @@ -0,0 +1,12 @@ +echo Y | del src\char\*.o +echo Y | del src\char\GNUmakefile +echo Y | del src\char_sql\*.o +echo Y | del src\char_sql\GNUmakefile +echo Y | del src\common\*.o +echo Y | del src\common\GNUmakefile +echo Y | del src\login\*.o +echo Y | del src\login\GNUmakefile +echo Y | del src\login_sql\*.o +echo Y | del src\login_sql\GNUmakefile +echo Y | del src\map\*.o +echo Y | del src\map\GNUmakefile diff --git a/misc/scripts/start b/misc/scripts/start new file mode 100644 index 0000000..f73fafd --- /dev/null +++ b/misc/scripts/start @@ -0,0 +1,32 @@ +# /bin/bash
+
+./athena-start start
+sleep 30
+
+while [ true ] ; do
+if [ " 0" = "$(ps | grep -e login | wc -l)" ] ||
+ [ " 0" = "$(ps | grep -e char | wc -l)" ] ||
+ [ " 0" = "$(ps | grep -e map | wc -l)" ]; then
+printf "Error:"
+date
+ sleep 10
+ printf "Checking:"
+ date
+ if [ " 0" = "$(ps | grep -e login | wc -l)" ] ||
+ [ " 0" = "$(ps | grep -e char | wc -l)" ] ||
+ [ " 0" = "$(ps | grep -e map | wc -l)" ]; then
+ printf "Error Confirmation:"
+ date
+ printf "Restoration:"
+ date
+ ./athena-start start
+ else
+ printf "Check Miss Sorry:"
+ date
+ fi
+else
+#printf "Check OK:"
+#date
+fi
+sleep 10
+done
\ No newline at end of file diff --git a/misc/src/Makefile b/misc/src/Makefile new file mode 100644 index 0000000..00ec3b9 --- /dev/null +++ b/misc/src/Makefile @@ -0,0 +1,126 @@ +# $Id: Makefile 158 2004-10-01 03:45:15Z PoW $
+
+CC = gcc -pipe
+PACKETDEF = -DPACKETVER=5 -DNEW_006b
+#PACKETDEF = -DPACKETVER=4 -DNEW_006b
+#PACKETDEF = -DPACKETVER=3 -DNEW_006b
+#PACKETDEF = -DPACKETVER=2 -DNEW_006b
+#PACKETDEF = -DPACKETVER=1 -DNEW_006b
+
+PLATFORM = $(shell uname)
+
+ifeq ($(findstring FreeBSD,$(PLATFORM)), FreeBSD)
+MAKE = gmake
+else
+MAKE = make
+endif
+
+OPT = -g -O2
+
+ifeq ($(findstring CYGWIN,$(PLATFORM)), CYGWIN)
+OS_TYPE = -DCYGWIN
+CFLAGS = $(OPT) -Wall -DFD_SETSIZE=4096 -I../common $(PACKETDEF) $(OS_TYPE)
+else
+OS_TYPE =
+CFLAGS = $(OPT) -Wall -I../common $(PACKETDEF) $(OS_TYPE)
+endif
+
+MYSQLFLAG_INCLUDE_DEFAULT = /usr/local/include/mysql
+
+ifdef SQLFLAG
+MYSQLFLAG_CONFIG = $(shell which mysql_config)
+ifeq ($(findstring /,$(MYSQLFLAG_CONFIG)), /)
+MYSQLFLAG_VERSION = $(shell $(MYSQLFLAG_CONFIG) --version | sed s:\\..*::)
+endif
+
+ifeq ($(findstring 5,$(MYSQLFLAG_VERSION)), 5)
+MYSQLFLAG_CONFIG_ARGUMENT = --include
+endif
+ifeq ($(findstring 4,$(MYSQLFLAG_VERSION)), 4)
+MYSQLFLAG_CONFIG_ARGUMENT = --include
+endif
+ifndef MYSQLFLAG_CONFIG_ARGUMENT
+MYSQLFLAG_CONFIG_ARGUMENT = --cflags
+endif
+
+ifeq ($(findstring /,$(MYSQLFLAG_CONFIG)), /)
+MYSQLFLAG_INCLUDE = $(shell $(MYSQLFLAG_CONFIG) $(MYSQLFLAG_CONFIG_ARGUMENT))
+else
+MYSQLFLAG_INCLUDE = -I$(MYSQLFLAG_INCLUDE_DEFAULT)
+endif
+
+LIB_S_DEFAULT = -L/usr/local/lib/mysql -lmysqlclient -lz
+MYSQLFLAG_CONFIG = $(shell which mysql_config)
+ifeq ($(findstring /,$(MYSQLFLAG_CONFIG)), /)
+LIB_S = $(shell $(MYSQLFLAG_CONFIG) --libs)
+else
+LIB_S = $(LIB_S_DEFAULT)
+endif
+
+MYLIB = CC="$(CC)" CFLAGS="$(CFLAGS) $(MYSQLFLAG_INCLUDE)" LIB_S="$(LIB_S)"
+
+endif
+
+MKDEF = CC="$(CC)" CFLAGS="$(CFLAGS)"
+
+all: conf txt
+
+conf:
+ cp -r conf-tmpl conf
+ rm -rf conf/.svn conf/*/.svn
+
+txt : src/common/GNUmakefile src/login/GNUmakefile src/char/GNUmakefile src/map/GNUmakefile src/ladmin/GNUmakefile conf
+ cd src ; cd common ; $(MAKE) $(MKDEF) $@ ; cd ..
+ cd src ; cd login ; $(MAKE) $(MKDEF) $@ ; cd ..
+ cd src ; cd char ; $(MAKE) $(MKDEF) $@ ; cd ..
+ cd src ; cd map ; $(MAKE) $(MKDEF) $@ ; cd ..
+ cd src ; cd ladmin ; $(MAKE) $(MKDEF) $@ ; cd ..
+
+
+ifdef SQLFLAG
+sql: src/common/GNUmakefile src/login_sql/GNUmakefile src/char_sql/GNUmakefile src/map/GNUmakefile src/ladmin/GNUmakefile src/txt-converter/login/GNUmakefile src/txt-converter/char/GNUmakefile conf
+ cd src ; cd common ; $(MAKE) $(MKDEF) $@ ; cd ..
+ cd src ; cd login_sql ; $(MAKE) $(MYLIB) $@ ; cd ..
+ cd src ; cd char_sql ; $(MAKE) $(MYLIB) $@ ; cd ..
+ cd src ; cd map ; $(MAKE) $(MYLIB) $@ ; cd ..
+ cd src ; cd ladmin ; $(MAKE) $(MKDEF) $@ ; cd ..
+ cd src ; cd txt-converter ; cd login ; $(MAKE) $(MYLIB) $@ ; cd ..
+ cd src ; cd txt-converter ; cd char ; $(MAKE) $(MYLIB) $@ ; cd ..
+else
+sql:
+ $(MAKE) CC="$(CC)" OPT="$(OPT)" SQLFLAG=1 $@
+endif
+
+clean: src/common/GNUmakefile src/login/GNUmakefile src/char/GNUmakefile src/map/GNUmakefile src/ladmin/GNUmakefile src/txt-converter/login/GNUmakefile src/txt-converter/char/GNUmakefile
+ cd src ; cd common ; $(MAKE) $(MKDEF) $@ ; cd ..
+ cd src ; cd login ; $(MAKE) $(MKDEF) $@ ; cd ..
+ cd src ; cd login_sql ; $(MAKE) $(MKLIB) $@ ; cd ..
+ cd src ; cd char ; $(MAKE) $(MKDEF) $@ ; cd ..
+ cd src ; cd char_sql ; $(MAKE) $(MKLIB) $@ ; cd ..
+ cd src ; cd map ; $(MAKE) $(MKLIB) $@ ; cd ..
+ cd src ; cd ladmin ; $(MAKE) $(MKDEF) $@ ; cd ..
+ cd src ; cd txt-converter ; cd login ; $(MAKE) $(MKLIB) $@ ; cd ..
+ cd src ; cd txt-converter ; cd char ; $(MAKE) $(MKLIB) $@ ; cd ..
+
+tools:
+ cd tool && $(MAKE) $(MKDEF) && cd ..
+ $(CC) -o setupwizard setupwizard.c
+
+src/common/GNUmakefile: src/common/Makefile
+ sed -e 's/$$>/$$^/' src/common/Makefile > src/common/GNUmakefile
+src/login/GNUmakefile: src/login/Makefile
+ sed -e 's/$$>/$$^/' src/login/Makefile > src/login/GNUmakefile
+src/login_sql/GNUmakefile: src/login_sql/Makefile
+ sed -e 's/$$>/$$^/' src/login_sql/Makefile > src/login_sql/GNUmakefile
+src/char/GNUmakefile: src/char/Makefile
+ sed -e 's/$$>/$$^/' src/char/Makefile > src/char/GNUmakefile
+src/char_sql/GNUmakefile: src/char_sql/Makefile
+ sed -e 's/$$>/$$^/' src/char_sql/Makefile > src/char_sql/GNUmakefile
+src/map/GNUmakefile: src/map/Makefile
+ sed -e 's/$$>/$$^/' src/map/Makefile > src/map/GNUmakefile
+src/ladmin/GNUmakefile: src/ladmin/Makefile
+ sed -e 's/$$>/$$^/' src/ladmin/Makefile > src/ladmin/GNUmakefile
+src/txt-converter/login/GNUmakefile: src/txt-converter/login/Makefile
+ sed -e 's/$$>/$$^/' src/txt-converter/login/Makefile > src/txt-converter/login/GNUmakefile
+src/txt-converter/char/GNUmakefile: src/txt-converter/char/Makefile
+ sed -e 's/$$>/$$^/' src/txt-converter/char/Makefile > src/txt-converter/char/GNUmakefile
diff --git a/misc/src/Makefile-optimized b/misc/src/Makefile-optimized new file mode 100644 index 0000000..63dc5ff --- /dev/null +++ b/misc/src/Makefile-optimized @@ -0,0 +1,49 @@ +# $Id: Makefile-optimized,v 1.7 2004/07/29 09:35:21 Yor Exp $
+
+CC = gcc -pipe
+PACKETDEF = -DPACKETVER=5 -DNEW_006b
+#PACKETDEF = -DPACKETVER=4 -DNEW_006b
+#PACKETDEF = -DPACKETVER=3 -DNEW_006b
+#PACKETDEF = -DPACKETVER=2 -DNEW_006b
+#PACKETDEF = -DPACKETVER=1 -DNEW_006b
+
+PLATFORM = $(shell uname)
+
+ifeq ($(findstring FreeBSD,$(PLATFORM)), FreeBSD)
+MAKE = gmake
+else
+MAKE = make
+endif
+
+ifeq ($(findstring CYGWIN,$(PLATFORM)), CYGWIN)
+OS_TYPE = -DCYGWIN
+CFLAGS = -O2 -Wall -DFD_SETSIZE=4096 -I../common $(PACKETDEF) $(OS_TYPE)
+else
+OS_TYPE =
+CFLAGS = -O2 -Wall -I../common $(PACKETDEF) $(OS_TYPE)
+endif
+
+MKDEF = CC="$(CC)" CFLAGS="$(CFLAGS)"
+
+
+all clean: src/common/GNUmakefile src/login/GNUmakefile src/char_unblocked/GNUmakefile src/map/GNUmakefile src/ladmin/GNUmakefile
+ cd src ; cd common ; $(MAKE) $(MKDEF) $@ ; cd ..
+ cd src ; cd login ; $(MAKE) $(MKDEF) $@ ; cd ..
+ cd src ; cd char_unblocked ; $(MAKE) $(MKDEF) $@ ; cd ..
+ cd src ; cd map ; $(MAKE) $(MKDEF) $@ ; cd ..
+ cd src ; cd ladmin ; $(MAKE) $(MKDEF) $@ ; cd ..
+
+tools:
+ cd tool && $(MAKE) $(MKDEF) && cd ..
+ $(CC) -o setupwizard setupwizard.c
+
+src/common/GNUmakefile: src/common/Makefile
+ sed -e 's/$$>/$$^/' src/common/Makefile > src/common/GNUmakefile
+src/login/GNUmakefile: src/login/Makefile
+ sed -e 's/$$>/$$^/' src/login/Makefile > src/login/GNUmakefile
+src/char_unblocked/GNUmakefile: src/char_unblocked/Makefile
+ sed -e 's/$$>/$$^/' src/char_unblocked/Makefile > src/char_unblocked/GNUmakefile
+src/map/GNUmakefile: src/map/Makefile
+ sed -e 's/$$>/$$^/' src/map/Makefile > src/map/GNUmakefile
+src/ladmin/GNUmakefile: src/ladmin/Makefile
+ sed -e 's/$$>/$$^/' src/ladmin/Makefile > src/ladmin/GNUmakefile
diff --git a/misc/src/char/GNUmakefile b/misc/src/char/GNUmakefile new file mode 100644 index 0000000..dd21288 --- /dev/null +++ b/misc/src/char/GNUmakefile @@ -0,0 +1,17 @@ +all: char-server
+txt: char-server
+
+COMMON_OBJ = ../common/core.o ../common/socket.o ../common/timer.o ../common/db.o ../common/lock.o ../common/malloc.o
+COMMON_H = ../common/core.h ../common/socket.h ../common/timer.h ../common/mmo.h ../common/db.h ../common/lock.h ../common/timer.h ../common/malloc.h
+char-server: char.o inter.o int_party.o int_guild.o int_storage.o int_pet.o $(COMMON_OBJ)
+ $(CC) -o ../../$@ $^
+
+char.o: char.c char.h inter.h int_pet.h $(COMMON_H) ../common/version.h
+inter.o: inter.c inter.h int_party.h int_guild.h int_storage.h int_pet.h char.h $(COMMON_H)
+int_party.o: int_party.c int_party.h inter.h char.h $(COMMON_H)
+int_guild.o: int_guild.c int_guild.h int_storage.h inter.h char.h $(COMMON_H)
+int_storage.o: int_storage.c int_storage.h int_guild.h inter.h char.h $(COMMON_H)
+int_pet.o: int_pet.c int_pet.h inter.h char.h $(COMMON_H)
+
+clean:
+ rm -f *.o ../../char-server
diff --git a/misc/src/char/Makefile b/misc/src/char/Makefile new file mode 100644 index 0000000..6eaae48 --- /dev/null +++ b/misc/src/char/Makefile @@ -0,0 +1,17 @@ +all: char-server
+txt: char-server
+
+COMMON_OBJ = ../common/core.o ../common/socket.o ../common/timer.o ../common/db.o ../common/lock.o ../common/malloc.o
+COMMON_H = ../common/core.h ../common/socket.h ../common/timer.h ../common/mmo.h ../common/db.h ../common/lock.h ../common/timer.h ../common/malloc.h
+char-server: char.o inter.o int_party.o int_guild.o int_storage.o int_pet.o $(COMMON_OBJ)
+ $(CC) -o ../../$@ $>
+
+char.o: char.c char.h inter.h int_pet.h $(COMMON_H) ../common/version.h
+inter.o: inter.c inter.h int_party.h int_guild.h int_storage.h int_pet.h char.h $(COMMON_H)
+int_party.o: int_party.c int_party.h inter.h char.h $(COMMON_H)
+int_guild.o: int_guild.c int_guild.h int_storage.h inter.h char.h $(COMMON_H)
+int_storage.o: int_storage.c int_storage.h int_guild.h inter.h char.h $(COMMON_H)
+int_pet.o: int_pet.c int_pet.h inter.h char.h $(COMMON_H)
+
+clean:
+ rm -f *.o ../../char-server
diff --git a/misc/src/char/char.c b/misc/src/char/char.c new file mode 100644 index 0000000..c279102 --- /dev/null +++ b/misc/src/char/char.c @@ -0,0 +1,3149 @@ +// $Id: char.c,v 1.3 2004/09/13 16:52:16 Yor Exp $ +// original : char2.c 2003/03/14 11:58:35 Rev.1.5 + +#include <sys/types.h> +#include <sys/socket.h> +#include <stdio.h> +#include <stdlib.h> +#include <netinet/in.h> +#include <sys/time.h> +#include <time.h> +#include <sys/ioctl.h> +#include <unistd.h> +#include <signal.h> +#include <fcntl.h> +#include <string.h> +#include <arpa/inet.h> +#include <netdb.h> +#include <stdarg.h> + +#include "core.h" +#include "socket.h" +#include "timer.h" +#include "mmo.h" +#include "version.h" +#include "lock.h" +#include "char.h" + +#include "inter.h" +#include "int_pet.h" +#include "int_guild.h" +#include "int_party.h" +#include "int_storage.h" + +#ifdef MEMWATCH +#include "memwatch.h" +#endif + +struct mmo_map_server server[MAX_MAP_SERVERS]; +int server_fd[MAX_MAP_SERVERS]; +int server_freezeflag[MAX_MAP_SERVERS]; // Map-server anti-freeze system. Counter. 5 ok, 4...0 freezed +int anti_freeze_enable = 0; +int ANTI_FREEZE_INTERVAL = 6; + +int login_fd, char_fd; +char userid[24]; +char passwd[24]; +char server_name[20]; +char wisp_server_name[24] = "Server"; +char login_ip_str[16]; +int login_ip; +int login_port = 6900; +char char_ip_str[16]; +int char_ip; +int char_port = 6121; +int char_maintenance; +int char_new; +int email_creation = 0; // disabled by default +char char_txt[1024]; +char backup_txt[1024]; //By zanetheinsane +char backup_txt_flag = 0; // The backup_txt file was created because char deletion bug existed. Now it's finish and that take a lot of time to create a second file when there are a lot of characters. => option By [Yor] +char unknown_char_name[1024] = "Unknown"; +char char_log_filename[1024] = "log/char.log"; +//Added for lan support +char lan_map_ip[128]; +int subneti[4]; +int subnetmaski[4]; +int name_ignoring_case = 0; // Allow or not identical name for characters but with a different case by [Yor] +int char_name_option = 0; // Option to know which letters/symbols are authorised in the name of a character (0: all, 1: only those in char_name_letters, 2: all EXCEPT those in char_name_letters) by [Yor] +char char_name_letters[1024] = ""; // list of letters/symbols authorised (or not) in a character name. by [Yor] + +struct char_session_data{ + int account_id, login_id1, login_id2, sex; + int found_char[9]; + char email[40]; // e-mail (default: a@a.com) by [Yor] + time_t connect_until_time; // # of seconds 1/1/1970 (timestamp): Validity limit of the account (0 = unlimited) +}; + +#define AUTH_FIFO_SIZE 256 +struct { + int account_id, char_id, login_id1, login_id2, ip, char_pos, delflag, sex; + time_t connect_until_time; // # of seconds 1/1/1970 (timestamp): Validity limit of the account (0 = unlimited) +} auth_fifo[AUTH_FIFO_SIZE]; +int auth_fifo_pos = 0; + +int check_ip_flag = 1; // It's to check IP of a player between char-server and other servers (part of anti-hacking system) + +int char_id_count = 150000; +struct mmo_charstatus *char_dat; +int char_num, char_max; +int max_connect_user = 0; +int autosave_interval = DEFAULT_AUTOSAVE_INTERVAL; +int start_zeny = 500; +int start_weapon = 1201; +int start_armor = 1202; + +// Initial position (it's possible to set it in conf file) +struct point start_point = {"new_1-1.gat", 53, 111}; + +struct gm_account *gm_account = NULL; +int GM_num = 0; + +// online players by [Yor] +char online_txt_filename[1024] = "online.txt"; +char online_html_filename[1024] = "online.html"; +int online_sorting_option = 0; // sorting option to display online players in online files +int online_display_option = 1; // display options: to know which columns must be displayed +int online_refresh_html = 20; // refresh time (in sec) of the html file in the explorer +int online_gm_display_min_level = 20; // minimum GM level to display 'GM' when we want to display it + +int *online_chars; // same size of char_dat, and id value of current server (or -1) +time_t update_online; // to update online files when we receiving information from a server (not less than 8 seconds) + +//------------------------------ +// Writing function of logs file +//------------------------------ +int char_log(char *fmt, ...) { + FILE *logfp; + va_list ap; + struct timeval tv; + char tmpstr[2048]; + + va_start(ap, fmt); + + logfp = fopen(char_log_filename, "a"); + if (logfp) { + if (fmt[0] == '\0') // jump a line if no message + fprintf(logfp, RETCODE); + else { + gettimeofday(&tv, NULL); + strftime(tmpstr, 24, "%d-%m-%Y %H:%M:%S", localtime(&(tv.tv_sec))); + sprintf(tmpstr + 19, ".%03d: %s", (int)tv.tv_usec / 1000, fmt); + vfprintf(logfp, tmpstr, ap); + } + fclose(logfp); + } + + va_end(ap); + return 0; +} + +//----------------------------------------------------- +// Function to suppress control characters in a string. +//----------------------------------------------------- +int remove_control_chars(unsigned char *str) { + int i; + int change = 0; + + for(i = 0; str[i]; i++) { + if (str[i] < 32) { + str[i] = '_'; + change = 1; + } + } + + return change; +} + +//---------------------------------------------------------------------- +// Determine if an account (id) is a GM account +// and returns its level (or 0 if it isn't a GM account or if not found) +//---------------------------------------------------------------------- +int isGM(int account_id) { + int i; + + for(i = 0; i < GM_num; i++) + if (gm_account[i].account_id == account_id) + return gm_account[i].level; + return 0; +} + +//---------------------------------------------- +// Search an character id +// (return character index or -1 (if not found)) +// If exact character name is not found, +// the function checks without case sensitive +// and returns index if only 1 character is found +// and similar to the searched name. +//---------------------------------------------- +int search_character_index(char* character_name) { + int i, quantity, index; + + quantity = 0; + index = -1; + for(i = 0; i < char_num; i++) { + // Without case sensitive check (increase the number of similar character names found) + if (stricmp(char_dat[i].name, character_name) == 0) { + // Strict comparison (if found, we finish the function immediatly with correct value) + if (strcmp(char_dat[i].name, character_name) == 0) + return i; + quantity++; + index = i; + } + } + // Here, the exact character name is not found + // We return the found index of a similar account ONLY if there is 1 similar character + if (quantity == 1) + return index; + + // Exact character name is not found and 0 or more than 1 similar characters have been found ==> we say not found + return -1; +} + +//------------------------------------- +// Return character name with the index +//------------------------------------- +char * search_character_name(int index) { + + if (index >= 0 && index < char_num) + return char_dat[index].name; + + return unknown_char_name; +} + +//------------------------------------------------- +// Function to create the character line (for save) +//------------------------------------------------- +int mmo_char_tostr(char *str, struct mmo_charstatus *p) { + int i; + char *str_p = str; + + // on multi-map server, sometimes it's posssible that last_point become void. (reason???) We check that to not lost character at restart. + if (p->last_point.map[0] == '\0') { + memcpy(p->last_point.map, "prontera.gat", 16); + p->last_point.x = 273; + p->last_point.y = 354; + } + + str_p += sprintf(str_p, "%d\t%d,%d\t%s\t%d,%d,%d\t%d,%d,%d\t%d,%d,%d,%d\t%d,%d,%d,%d,%d,%d\t%d,%d" + "\t%d,%d,%d\t%d,%d,%d\t%d,%d,%d\t%d,%d,%d,%d,%d" + "\t%s,%d,%d\t%s,%d,%d,%d\t", + p->char_id, p->account_id, p->char_num, p->name, // + p->class, p->base_level, p->job_level, + p->base_exp, p->job_exp, p->zeny, + p->hp, p->max_hp, p->sp, p->max_sp, + p->str, p->agi, p->vit, p->int_, p->dex, p->luk, + p->status_point, p->skill_point, + p->option, p->karma, p->manner, // + p->party_id, p->guild_id, p->pet_id, + p->hair, p->hair_color, p->clothes_color, + p->weapon, p->shield, p->head_top, p->head_mid, p->head_bottom, + p->last_point.map, p->last_point.x, p->last_point.y, // + p->save_point.map, p->save_point.x, p->save_point.y, + p->partner_id); + for(i = 0; i < 10; i++) + if (p->memo_point[i].map[0]) { + str_p += sprintf(str_p, "%s,%d,%d", p->memo_point[i].map, p->memo_point[i].x, p->memo_point[i].y); + } + *(str_p++) = '\t'; + + for(i = 0; i < MAX_INVENTORY; i++) + if (p->inventory[i].nameid) { + str_p += sprintf(str_p, "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d ", + p->inventory[i].id, p->inventory[i].nameid, p->inventory[i].amount, p->inventory[i].equip, + p->inventory[i].identify, p->inventory[i].refine, p->inventory[i].attribute, + p->inventory[i].card[0], p->inventory[i].card[1], p->inventory[i].card[2], p->inventory[i].card[3], + p->inventory[i].broken); + } + *(str_p++) = '\t'; + + for(i = 0; i < MAX_CART; i++) + if (p->cart[i].nameid) { + str_p += sprintf(str_p, "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d ", + p->cart[i].id, p->cart[i].nameid, p->cart[i].amount, p->cart[i].equip, + p->cart[i].identify, p->cart[i].refine, p->cart[i].attribute, + p->cart[i].card[0], p->cart[i].card[1], p->cart[i].card[2], p->cart[i].card[3], + p->cart[i].broken); + } + *(str_p++) = '\t'; + + for(i = 0; i < MAX_SKILL; i++) + if (p->skill[i].id && p->skill[i].flag != 1) { + str_p += sprintf(str_p, "%d,%d ", p->skill[i].id, (p->skill[i].flag == 0) ? p->skill[i].lv : p->skill[i].flag-2); + } + *(str_p++) = '\t'; + + for(i = 0; i < p->global_reg_num; i++) + if (p->global_reg[i].str[0]) + str_p += sprintf(str_p, "%s,%d ", p->global_reg[i].str, p->global_reg[i].value); + *(str_p++) = '\t'; + + *str_p = '\0'; + return 0; +} + +//------------------------------------------------------------------------- +// Function to set the character from the line (at read of characters file) +//------------------------------------------------------------------------- +int mmo_char_fromstr(char *str, struct mmo_charstatus *p) { + int tmp_int[256]; + int set, next, len, i; + + // initilialise character + memset(p, '\0', sizeof(struct mmo_charstatus)); + + // If it's not char structure of version 1008 and after + if ((set = sscanf(str, "%d\t%d,%d\t%[^\t]\t%d,%d,%d\t%d,%d,%d\t%d,%d,%d,%d\t%d,%d,%d,%d,%d,%d\t%d,%d" + "\t%d,%d,%d\t%d,%d,%d\t%d,%d,%d\t%d,%d,%d,%d,%d" + "\t%[^,],%d,%d\t%[^,],%d,%d,%d%n", + &tmp_int[0], &tmp_int[1], &tmp_int[2], p->name, // + &tmp_int[3], &tmp_int[4], &tmp_int[5], + &tmp_int[6], &tmp_int[7], &tmp_int[8], + &tmp_int[9], &tmp_int[10], &tmp_int[11], &tmp_int[12], + &tmp_int[13], &tmp_int[14], &tmp_int[15], &tmp_int[16], &tmp_int[17], &tmp_int[18], + &tmp_int[19], &tmp_int[20], + &tmp_int[21], &tmp_int[22], &tmp_int[23], // + &tmp_int[24], &tmp_int[25], &tmp_int[26], + &tmp_int[27], &tmp_int[28], &tmp_int[29], + &tmp_int[30], &tmp_int[31], &tmp_int[32], &tmp_int[33], &tmp_int[34], + p->last_point.map, &tmp_int[35], &tmp_int[36], // + p->save_point.map, &tmp_int[37], &tmp_int[38], &tmp_int[39], &next)) != 43) { + tmp_int[39] = 0; // partner id + // If not char structure from version 384 to 1007 + if ((set = sscanf(str, "%d\t%d,%d\t%[^\t]\t%d,%d,%d\t%d,%d,%d\t%d,%d,%d,%d\t%d,%d,%d,%d,%d,%d\t%d,%d" + "\t%d,%d,%d\t%d,%d,%d\t%d,%d,%d\t%d,%d,%d,%d,%d" + "\t%[^,],%d,%d\t%[^,],%d,%d%n", + &tmp_int[0], &tmp_int[1], &tmp_int[2], p->name, // + &tmp_int[3], &tmp_int[4], &tmp_int[5], + &tmp_int[6], &tmp_int[7], &tmp_int[8], + &tmp_int[9], &tmp_int[10], &tmp_int[11], &tmp_int[12], + &tmp_int[13], &tmp_int[14], &tmp_int[15], &tmp_int[16], &tmp_int[17], &tmp_int[18], + &tmp_int[19], &tmp_int[20], + &tmp_int[21], &tmp_int[22], &tmp_int[23], // + &tmp_int[24], &tmp_int[25], &tmp_int[26], + &tmp_int[27], &tmp_int[28], &tmp_int[29], + &tmp_int[30], &tmp_int[31], &tmp_int[32], &tmp_int[33], &tmp_int[34], + p->last_point.map, &tmp_int[35], &tmp_int[36], // + p->save_point.map, &tmp_int[37], &tmp_int[38], &next)) != 42) { + // It's char structure of a version before 384 + tmp_int[26] = 0; // pet id + set = sscanf(str, "%d\t%d,%d\t%[^\t]\t%d,%d,%d\t%d,%d,%d\t%d,%d,%d,%d\t%d,%d,%d,%d,%d,%d\t%d,%d" + "\t%d,%d,%d\t%d,%d\t%d,%d,%d\t%d,%d,%d,%d,%d" + "\t%[^,],%d,%d\t%[^,],%d,%d%n", + &tmp_int[0], &tmp_int[1], &tmp_int[2], p->name, // + &tmp_int[3], &tmp_int[4], &tmp_int[5], + &tmp_int[6], &tmp_int[7], &tmp_int[8], + &tmp_int[9], &tmp_int[10], &tmp_int[11], &tmp_int[12], + &tmp_int[13], &tmp_int[14], &tmp_int[15], &tmp_int[16], &tmp_int[17], &tmp_int[18], + &tmp_int[19], &tmp_int[20], + &tmp_int[21], &tmp_int[22], &tmp_int[23], // + &tmp_int[24], &tmp_int[25], // + &tmp_int[27], &tmp_int[28], &tmp_int[29], + &tmp_int[30], &tmp_int[31], &tmp_int[32], &tmp_int[33], &tmp_int[34], + p->last_point.map, &tmp_int[35], &tmp_int[36], // + p->save_point.map, &tmp_int[37], &tmp_int[38], &next); + set += 2; + //printf("char: old char data ver.1\n"); + // Char structure of version 1007 or older + } else { + set++; + //printf("char: old char data ver.2\n"); + } + // Char structure of version 1008+ + } else { + //printf("char: new char data ver.3\n"); + } + if (set != 43) + return 0; + + p->char_id = tmp_int[0]; + p->account_id = tmp_int[1]; + p->char_num = tmp_int[2]; + p->class = tmp_int[3]; + p->base_level = tmp_int[4]; + p->job_level = tmp_int[5]; + p->base_exp = tmp_int[6]; + p->job_exp = tmp_int[7]; + p->zeny = tmp_int[8]; + p->hp = tmp_int[9]; + p->max_hp = tmp_int[10]; + p->sp = tmp_int[11]; + p->max_sp = tmp_int[12]; + p->str = tmp_int[13]; + p->agi = tmp_int[14]; + p->vit = tmp_int[15]; + p->int_ = tmp_int[16]; + p->dex = tmp_int[17]; + p->luk = tmp_int[18]; + p->status_point = tmp_int[19]; + p->skill_point = tmp_int[20]; + p->option = tmp_int[21]; + p->karma = tmp_int[22]; + p->manner = tmp_int[23]; + p->party_id = tmp_int[24]; + p->guild_id = tmp_int[25]; + p->pet_id = tmp_int[26]; + p->hair = tmp_int[27]; + p->hair_color = tmp_int[28]; + p->clothes_color = tmp_int[29]; + p->weapon = tmp_int[30]; + p->shield = tmp_int[31]; + p->head_top = tmp_int[32]; + p->head_mid = tmp_int[33]; + p->head_bottom = tmp_int[34]; + p->last_point.x = tmp_int[35]; + p->last_point.y = tmp_int[36]; + p->save_point.x = tmp_int[37]; + p->save_point.y = tmp_int[38]; + p->partner_id = tmp_int[39]; + + // Some checks + for(i = 0; i < char_num; i++) { + if (char_dat[i].char_id == p->char_id) { + printf("\033[1;31mmmo_auth_init: ******Error: a character has an identical id to another.\n"); + printf(" character id #%d -> new character not readed.\n", p->char_id); + printf(" Character saved in log file.\033[0m\n"); + return -1; + } else if (strcmp(char_dat[i].name, p->name) == 0) { + printf("\033[1;31mmmo_auth_init: ******Error: character name already exists.\n"); + printf(" character name '%s' -> new character not readed.\n", p->name); + printf(" Character saved in log file.\033[0m\n"); + return -2; + } + } + + if (strcmpi(wisp_server_name, p->name) == 0) { + printf("mmo_auth_init: ******WARNING: character name has wisp server name.\n"); + printf(" Character name '%s' = wisp server name '%s'.\n", p->name, wisp_server_name); + printf(" Character readed. Suggestion: change the wisp server name.\n"); + char_log("mmo_auth_init: ******WARNING: character name has wisp server name: Character name '%s' = wisp server name '%s'." RETCODE, + p->name, wisp_server_name); + } + + if (str[next] == '\n' || str[next] == '\r') + return 1; // 新規データ + + next++; + + for(i = 0; str[next] && str[next] != '\t'; i++) { + if (sscanf(str+next, "%[^,],%d,%d%n", p->memo_point[i].map, &tmp_int[0], &tmp_int[1], &len) != 3) + return -3; + p->memo_point[i].x = tmp_int[0]; + p->memo_point[i].y = tmp_int[1]; + next += len; + if (str[next] == ' ') + next++; + } + + next++; + + for(i = 0; str[next] && str[next] != '\t'; i++) { + if (sscanf(str + next, "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d%n", + &tmp_int[0], &tmp_int[1], &tmp_int[2], &tmp_int[3], + &tmp_int[4], &tmp_int[5], &tmp_int[6], + &tmp_int[7], &tmp_int[8], &tmp_int[9], &tmp_int[10], &tmp_int[11], &len) == 12) { + // do nothing, it's ok + } else if (sscanf(str + next, "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d%n", + &tmp_int[0], &tmp_int[1], &tmp_int[2], &tmp_int[3], + &tmp_int[4], &tmp_int[5], &tmp_int[6], + &tmp_int[7], &tmp_int[8], &tmp_int[9], &tmp_int[10], &len) == 11) { + tmp_int[11] = 0; // broken doesn't exist in this version -> 0 + } else // invalid structure + return -4; + p->inventory[i].id = tmp_int[0]; + p->inventory[i].nameid = tmp_int[1]; + p->inventory[i].amount = tmp_int[2]; + p->inventory[i].equip = tmp_int[3]; + p->inventory[i].identify = tmp_int[4]; + p->inventory[i].refine = tmp_int[5]; + p->inventory[i].attribute = tmp_int[6]; + p->inventory[i].card[0] = tmp_int[7]; + p->inventory[i].card[1] = tmp_int[8]; + p->inventory[i].card[2] = tmp_int[9]; + p->inventory[i].card[3] = tmp_int[10]; + p->inventory[i].broken = tmp_int[11]; + next += len; + if (str[next] == ' ') + next++; + } + + next++; + + for(i = 0; str[next] && str[next] != '\t'; i++) { + if (sscanf(str + next, "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d%n", + &tmp_int[0], &tmp_int[1], &tmp_int[2], &tmp_int[3], + &tmp_int[4], &tmp_int[5], &tmp_int[6], + &tmp_int[7], &tmp_int[8], &tmp_int[9], &tmp_int[10], &tmp_int[11], &len) == 12) { + // do nothing, it's ok + } else if (sscanf(str + next, "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d%n", + &tmp_int[0], &tmp_int[1], &tmp_int[2], &tmp_int[3], + &tmp_int[4], &tmp_int[5], &tmp_int[6], + &tmp_int[7], &tmp_int[8], &tmp_int[9], &tmp_int[10], &len) == 11) { + tmp_int[11] = 0; // broken doesn't exist in this version -> 0 + } else // invalid structure + return -5; + p->cart[i].id = tmp_int[0]; + p->cart[i].nameid = tmp_int[1]; + p->cart[i].amount = tmp_int[2]; + p->cart[i].equip = tmp_int[3]; + p->cart[i].identify = tmp_int[4]; + p->cart[i].refine = tmp_int[5]; + p->cart[i].attribute = tmp_int[6]; + p->cart[i].card[0] = tmp_int[7]; + p->cart[i].card[1] = tmp_int[8]; + p->cart[i].card[2] = tmp_int[9]; + p->cart[i].card[3] = tmp_int[10]; + p->cart[i].broken = tmp_int[11]; + next += len; + if (str[next] == ' ') + next++; + } + + next++; + + for(i = 0; str[next] && str[next] != '\t'; i++) { + if (sscanf(str + next, "%d,%d%n", &tmp_int[0], &tmp_int[1], &len) != 2) + return -6; + p->skill[tmp_int[0]].id = tmp_int[0]; + p->skill[tmp_int[0]].lv = tmp_int[1]; + next += len; + if (str[next] == ' ') + next++; + } + + next++; + + for(i = 0; str[next] && str[next] != '\t' && str[next] != '\n' && str[next] != '\r'; i++) { // global_reg実装以前のathena.txt互換のため一応'\n'チェック + if (sscanf(str + next, "%[^,],%d%n", p->global_reg[i].str, &p->global_reg[i].value, &len) != 2) { + // because some scripts are not correct, the str can be "". So, we must check that. + // If it's, we must not refuse the character, but just this REG value. + // Character line will have something like: nov_2nd_cos,9 ,9 nov_1_2_cos_c,1 (here, ,9 is not good) + if (str[next] == ',' && sscanf(str + next, ",%d%n", &p->global_reg[i].value, &len) == 1) + i--; + else + return -7; + } + next += len; + if (str[next] == ' ') + next++; + } + p->global_reg_num = i; + + return 1; +} + +//--------------------------------- +// Function to read characters file +//--------------------------------- +int mmo_char_init(void) { + char line[65536]; + int i; + int ret, line_count; + FILE *fp; + + char_max = 256; + char_dat = calloc(sizeof(struct mmo_charstatus) * 256, 1); + if (!char_dat) { + printf("out of memory: mmo_char_init (calloc of char_dat).\n"); + exit(1); + } + online_chars = calloc(sizeof(int) * 256, 1); + if (!online_chars) { + printf("out of memory: mmo_char_init (calloc of online_chars).\n"); + exit(1); + } + for(i = 0; i < char_max; i++) + online_chars[i] = -1; + + char_num = 0; + + fp = fopen(char_txt, "r"); + if (fp == NULL) { + printf("Characters file not found: %s.\n", char_txt); + char_log("Characters file not found: %s." RETCODE, char_txt); + char_log("Id for the next created character: %d." RETCODE, char_id_count); + return 0; + } + + line_count = 0; + while(fgets(line, sizeof(line)-1, fp)) { + int i, j; + line_count++; + + if (line[0] == '/' && line[1] == '/') + continue; + line[sizeof(line)-1] = '\0'; + + j = 0; + if (sscanf(line, "%d\t%%newid%%%n", &i, &j) == 1 && j > 0) { + if (char_id_count < i) + char_id_count = i; + continue; + } + + if (char_num >= char_max) { + char_max += 256; + char_dat = realloc(char_dat, sizeof(struct mmo_charstatus) * char_max); + if (!char_dat) { + printf("Out of memory: mmo_char_init (realloc of char_dat).\n"); + char_log("Out of memory: mmo_char_init (realloc of char_dat)." RETCODE); + exit(1); + } + online_chars = realloc(online_chars, sizeof(int) * char_max); + if (!online_chars) { + printf("Out of memory: mmo_char_init (realloc of online_chars).\n"); + char_log("Out of memory: mmo_char_init (realloc of online_chars)." RETCODE); + exit(1); + } + for(i = char_max - 256; i < char_max; i++) + online_chars[i] = -1; + } + + ret = mmo_char_fromstr(line, &char_dat[char_num]); + if (ret > 0) { // negative value or zero for errors + if (char_dat[char_num].char_id >= char_id_count) + char_id_count = char_dat[char_num].char_id + 1; + char_num++; + } else { + printf("mmo_char_init: in characters file, unable to read the line #%d.\n", line_count); + printf(" -> Character saved in log file.\n"); + switch (ret) { + case -1: + char_log("Duplicate character id in the next character line (character not readed):" RETCODE); + break; + case -2: + char_log("Duplicate character name in the next character line (character not readed):" RETCODE); + break; + case -3: + char_log("Invalid memo point structure in the next character line (character not readed):" RETCODE); + break; + case -4: + char_log("Invalid inventory item structure in the next character line (character not readed):" RETCODE); + break; + case -5: + char_log("Invalid cart item structure in the next character line (character not readed):" RETCODE); + break; + case -6: + char_log("Invalid skill structure in the next character line (character not readed):" RETCODE); + break; + case -7: + char_log("Invalid register structure in the next character line (character not readed):" RETCODE); + break; + default: // 0 + char_log("Unabled to get a character in the next line - Basic structure of line (before inventory) is incorrect (character not readed):" RETCODE); + break; + } + char_log("%s", line); + } + } + fclose(fp); + + if (char_num == 0) { + printf("mmo_char_init: No character found in %s.\n", char_txt); + char_log("mmo_char_init: No character found in %s." RETCODE, char_txt); + } else if (char_num == 1) { + printf("mmo_char_init: 1 character read in %s.\n", char_txt); + char_log("mmo_char_init: 1 character read in %s." RETCODE, char_txt); + } else { + printf("mmo_char_init: %d characters read in %s.\n", char_num, char_txt); + char_log("mmo_char_init: %d characters read in %s." RETCODE, char_num, char_txt); + } + + char_log("Id for the next created character: %d." RETCODE, char_id_count); + + return 0; +} + +//--------------------------------------------------------- +// Function to save characters in files (speed up by [Yor]) +//--------------------------------------------------------- +void mmo_char_sync(void) { + char line[65536]; + int i, j, k; + int lock; + FILE *fp; + int id[char_num]; + + // Sorting before save (by [Yor]) + for(i = 0; i < char_num; i++) { + id[i] = i; + for(j = 0; j < i; j++) { + if ((char_dat[i].account_id < char_dat[id[j]].account_id) || + // if same account id, we sort by slot. + (char_dat[i].account_id == char_dat[id[j]].account_id && + char_dat[i].char_num < char_dat[id[j]].char_num)) { + for(k = i; k > j; k--) + id[k] = id[k-1]; + id[j] = i; // id[i] + break; + } + } + } + + // Data save + fp = lock_fopen(char_txt, &lock); + if (fp == NULL) { + printf("WARNING: Server can't not save characters.\n"); + char_log("WARNING: Server can't not save characters." RETCODE); + } else { + for(i = 0; i < char_num; i++) { + // create only once the line, and save it in the 2 files (it's speeder than repeat twice the loop and create twice the line) + mmo_char_tostr(line, &char_dat[id[i]]); // use of sorted index + fprintf(fp, "%s" RETCODE, line); + } + fprintf(fp, "%d\t%%newid%%" RETCODE, char_id_count); + lock_fclose(fp, char_txt, &lock); + } + + // Data save (backup) + if (backup_txt_flag) { // The backup_txt file was created because char deletion bug existed. Now it's finish and that take a lot of time to create a second file when there are a lot of characters. => option By [Yor] + fp = lock_fopen(backup_txt, &lock); + if (fp == NULL) { + printf("WARNING: Server can't not create backup of characters file.\n"); + char_log("WARNING: Server can't not create backup of characters file." RETCODE); + return; + } + for(i = 0; i < char_num; i++) { + // create only once the line, and save it in the 2 files (it's speeder than repeat twice the loop and create twice the line) + mmo_char_tostr(line, &char_dat[id[i]]); // use of sorted index + fprintf(fp, "%s" RETCODE, line); + } + fprintf(fp, "%d\t%%newid%%" RETCODE, char_id_count); + lock_fclose(fp, backup_txt, &lock); + } + + return; +} + +//---------------------------------------------------- +// Function to save (in a periodic way) datas in files +//---------------------------------------------------- +int mmo_char_sync_timer(int tid, unsigned int tick, int id, int data) { + mmo_char_sync(); + inter_save(); + return 0; +} + +//----------------------------------- +// Function to create a new character +//----------------------------------- +int make_new_char(int fd, unsigned char *dat) { + int i, j; + struct char_session_data *sd; + + sd = session[fd]->session_data; + + // remove control characters from the name + dat[23] = '\0'; + if (remove_control_chars(dat)) { + char_log("Make new char error (control char received in the name): (connection #%d, account: %d)." RETCODE, + fd, sd->account_id); + return -1; + } + + // check lenght of character name + if (strlen(dat) < 4) { + char_log("Make new char error (character name too small): (connection #%d, account: %d, name: '%s')." RETCODE, + fd, sd->account_id, dat); + return -1; + } + + // Check Authorised letters/symbols in the name of the character + if (char_name_option == 1) { // only letters/symbols in char_name_letters are authorised + for (i = 0; dat[i]; i++) + if (strchr(char_name_letters, dat[i]) == NULL) { + char_log("Make new char error (invalid letter in the name): (connection #%d, account: %d), name: %s, invalid letter: %c." RETCODE, + fd, sd->account_id, dat, dat[i]); + return -1; + } + } else if (char_name_option == 2) { // letters/symbols in char_name_letters are forbidden + for (i = 0; dat[i]; i++) + if (strchr(char_name_letters, dat[i]) != NULL) { + char_log("Make new char error (invalid letter in the name): (connection #%d, account: %d), name: %s, invalid letter: %c." RETCODE, + fd, sd->account_id, dat, dat[i]); + return -1; + } + } // else, all letters/symbols are authorised (except control char removed before) + + if (dat[24] + dat[25] + dat[26] + dat[27] + dat[28] + dat[29] != 5*6 || // stats + dat[30] >= 9 || // slots (dat[30] can not be negativ) + dat[33] <= 0 || dat[33] >= 20 || // hair style + dat[31] >= 12) { // hair color (dat[31] can not be negativ) + char_log("Make new char error (invalid values): (connection #%d, account: %d) slot %d, name: %s, stats: %d+%d+%d+%d+%d+%d=%d, hair: %d, hair color: %d" RETCODE, + fd, sd->account_id, dat[30], dat, dat[24], dat[25], dat[26], dat[27], dat[28], dat[29], dat[24] + dat[25] + dat[26] + dat[27] + dat[28] + dat[29], dat[33], dat[31]); + return -1; + } + + // check individual stat value + for(i = 24; i <= 29; i++) { + if (dat[i] < 1 || dat[i] > 9) { + char_log("Make new char error (invalid stat value: not between 1 to 9): (connection #%d, account: %d) slot %d, name: %s, stats: %d+%d+%d+%d+%d+%d=%d, hair: %d, hair color: %d" RETCODE, + fd, sd->account_id, dat[30], dat, dat[24], dat[25], dat[26], dat[27], dat[28], dat[29], dat[24] + dat[25] + dat[26] + dat[27] + dat[28] + dat[29], dat[33], dat[31]); + return -1; + } + } + + for(i = 0; i < char_num; i++) { + if ((name_ignoring_case != 0 && strcmp(char_dat[i].name, dat) == 0) || + (name_ignoring_case == 0 && strcmpi(char_dat[i].name, dat) == 0)) { + char_log("Make new char error (name already exists): (connection #%d, account: %d) slot %d, name: %s (actual name of other char: %d), stats: %d+%d+%d+%d+%d+%d=%d, hair: %d, hair color: %d." RETCODE, + fd, sd->account_id, dat[30], dat, char_dat[i].name, dat[24], dat[25], dat[26], dat[27], dat[28], dat[29], dat[24] + dat[25] + dat[26] + dat[27] + dat[28] + dat[29], dat[33], dat[31]); + return -1; + } + if (char_dat[i].account_id == sd->account_id && char_dat[i].char_num == dat[30]) { + char_log("Make new char error (slot already used): (connection #%d, account: %d) slot %d, name: %s (actual name of other char: %d), stats: %d+%d+%d+%d+%d+%d=%d, hair: %d, hair color: %d." RETCODE, + fd, sd->account_id, dat[30], dat, char_dat[i].name, dat[24], dat[25], dat[26], dat[27], dat[28], dat[29], dat[24] + dat[25] + dat[26] + dat[27] + dat[28] + dat[29], dat[33], dat[31]); + return -1; + } + } + + if (strcmp(wisp_server_name, dat) == 0) { + char_log("Make new char error (name used is wisp name for server): (connection #%d, account: %d) slot %d, name: %s (actual name of other char: %d), stats: %d+%d+%d+%d+%d+%d=%d, hair: %d, hair color: %d." RETCODE, + fd, sd->account_id, dat[30], dat, char_dat[i].name, dat[24], dat[25], dat[26], dat[27], dat[28], dat[29], dat[24] + dat[25] + dat[26] + dat[27] + dat[28] + dat[29], dat[33], dat[31]); + return -1; + } + + if (char_num >= char_max) { + char_max += 256; + char_dat = realloc(char_dat, sizeof(struct mmo_charstatus) * char_max); + if (!char_dat) { + printf("Out of memory: make_new_char (realloc of char_dat).\n"); + char_log("Out of memory: make_new_char (realloc of char_dat)." RETCODE); + exit(1); + } + online_chars = realloc(online_chars, sizeof(int) * char_max); + if (!online_chars) { + printf("Out of memory: make_new_char (realloc of online_chars).\n"); + char_log("Out of memory: make_new_char (realloc of online_chars)." RETCODE); + exit(1); + } + for(j = char_max - 256; j < char_max; j++) + online_chars[j] = -1; + } + + char_log("Creation of New Character: (connection #%d, account: %d) slot %d, character Name: %s, stats: %d+%d+%d+%d+%d+%d=%d, hair: %d, hair color: %d." RETCODE, + fd, sd->account_id, dat[30], dat, dat[24], dat[25], dat[26], dat[27], dat[28], dat[29], dat[24] + dat[25] + dat[26] + dat[27] + dat[28] + dat[29], dat[33], dat[31]); + + memset(&char_dat[i], 0, sizeof(struct mmo_charstatus)); + + char_dat[i].char_id = char_id_count++; + char_dat[i].account_id = sd->account_id; + char_dat[i].char_num = dat[30]; + strcpy(char_dat[i].name, dat); + char_dat[i].class = 0; + char_dat[i].base_level = 1; + char_dat[i].job_level = 1; + char_dat[i].base_exp = 0; + char_dat[i].job_exp = 0; + char_dat[i].zeny = start_zeny; + char_dat[i].str = dat[24]; + char_dat[i].agi = dat[25]; + char_dat[i].vit = dat[26]; + char_dat[i].int_ = dat[27]; + char_dat[i].dex = dat[28]; + char_dat[i].luk = dat[29]; + char_dat[i].max_hp = 40 * (100 + char_dat[i].vit) / 100; + char_dat[i].max_sp = 11 * (100 + char_dat[i].int_) / 100; + char_dat[i].hp = char_dat[i].max_hp; + char_dat[i].sp = char_dat[i].max_sp; + char_dat[i].status_point = 0; + char_dat[i].skill_point = 0; + char_dat[i].option = 0; + char_dat[i].karma = 0; + char_dat[i].manner = 0; + char_dat[i].party_id = 0; + char_dat[i].guild_id = 0; + char_dat[i].hair = dat[33]; + char_dat[i].hair_color = dat[31]; + char_dat[i].clothes_color = 0; + char_dat[i].inventory[0].nameid = start_weapon; // Knife + char_dat[i].inventory[0].amount = 1; + char_dat[i].inventory[0].equip = 0x02; + char_dat[i].inventory[0].identify = 1; + char_dat[i].inventory[0].broken = 0; + char_dat[i].inventory[1].nameid = start_armor; // Cotton Shirt + char_dat[i].inventory[1].amount = 1; + char_dat[i].inventory[1].equip = 0x10; + char_dat[i].inventory[1].identify = 1; + char_dat[i].inventory[1].broken = 0; + char_dat[i].weapon = 1; + char_dat[i].shield = 0; + char_dat[i].head_top = 0; + char_dat[i].head_mid = 0; + char_dat[i].head_bottom = 0; + memcpy(&char_dat[i].last_point, &start_point, sizeof(start_point)); + memcpy(&char_dat[i].save_point, &start_point, sizeof(start_point)); + char_num++; + + mmo_char_sync(); + return i; +} + +//---------------------------------------------------- +// This function return the name of the job (by [Yor]) +//---------------------------------------------------- +char * job_name(int class) { + switch (class) { + case 0: return "Novice"; + case 1: return "Swordsman"; + case 2: return "Mage"; + case 3: return "Archer"; + case 4: return "Acolyte"; + case 5: return "Merchant"; + case 6: return "Thief"; + case 7: return "Knight"; + case 8: return "Priest"; + case 9: return "Wizard"; + case 10: return "Blacksmith"; + case 11: return "Hunter"; + case 12: return "Assassin"; + case 13: return "Knight 2"; + case 14: return "Crusader"; + case 15: return "Monk"; + case 16: return "Sage"; + case 17: return "Rogue"; + case 18: return "Alchemist"; + case 19: return "Bard"; + case 20: return "Dancer"; + case 21: return "Crusader 2"; + case 22: return "Wedding"; + case 23: return "Super Novice"; + case 4001: return "Novice High"; + case 4002: return "Swordsman High"; + case 4003: return "Mage High"; + case 4004: return "Archer High"; + case 4005: return "Acolyte High"; + case 4006: return "Merchant High"; + case 4007: return "Thief High"; + case 4008: return "Lord Knight"; + case 4009: return "High Priest"; + case 4010: return "High Wizard"; + case 4011: return "Whitesmith"; + case 4012: return "Sniper"; + case 4013: return "Assassin Cross"; + case 4014: return "Peko Knight"; + case 4015: return "Paladin"; + case 4016: return "Champion"; + case 4017: return "Professor"; + case 4018: return "Stalker"; + case 4019: return "Creator"; + case 4020: return "Clown"; + case 4021: return "Gypsy"; + case 4022: return "Peko Paladin"; + case 4023: return "Baby Novice"; + case 4024: return "Baby Swordsman"; + case 4025: return "Baby Mage"; + case 4026: return "Baby Archer"; + case 4027: return "Baby Acolyte"; + case 4028: return "Baby Merchant"; + case 4029: return "Baby Thief"; + case 4030: return "Baby Knight"; + case 4031: return "Baby Priest"; + case 4032: return "Baby Wizard"; + case 4033: return "Baby Blacksmith"; + case 4034: return "Baby Hunter"; + case 4035: return "Baby Assassin"; + case 4036: return "Baby Peco Knight"; + case 4037: return "Baby Crusader"; + case 4038: return "Baby Monk"; + case 4039: return "Baby Sage"; + case 4040: return "Baby Rogue"; + case 4041: return "Baby Alchemist"; + case 4042: return "Baby Bard"; + case 4043: return "Baby Dancer"; + case 4044: return "Baby Peco Crusader"; + case 4045: return "Super Baby"; + } + return "Unknown Job"; +} + +//------------------------------------------------------------- +// Function to create the online files (txt and html). by [Yor] +//------------------------------------------------------------- +void create_online_files(void) { + int i, j, k, l; // for loops + int players; // count the number of players + FILE *fp; // for the txt file + FILE *fp2; // for the html file + char temp[256]; // to prepare what we must display + time_t time_server; // for number of seconds + struct tm *datetime; // variable for time in structure ->tm_mday, ->tm_sec, ... + int id[char_num]; + + if (online_display_option == 0) // we display nothing, so return + return; + + //char_log("Creation of online players files." RETCODE); + + // Get number of online players, id of each online players + players = 0; + // sort online characters. + for(i = 0; i < char_num; i++) { + if (online_chars[i] != -1) { + id[players] = i; + // use sorting option + switch (online_sorting_option) { + case 1: // by name (without case sensitive) + { + char *p_name = char_dat[i].name; //speed up sorting when there are a lot of players. But very rarely players have same name. + for(j = 0; j < players; j++) + if (stricmp(p_name, char_dat[id[j]].name) < 0 || + // if same name, we sort with case sensitive. + (stricmp(p_name, char_dat[id[j]].name) == 0 && + strcmp(p_name, char_dat[id[j]].name) < 0)) { + for(k = players; k > j; k--) + id[k] = id[k-1]; + id[j] = i; // id[players] + break; + } + } + break; + case 2: // by zeny + for(j = 0; j < players; j++) + if (char_dat[i].zeny < char_dat[id[j]].zeny || + // if same number of zenys, we sort by name. + (char_dat[i].zeny == char_dat[id[j]].zeny && + stricmp(char_dat[i].name, char_dat[id[j]].name) < 0)) { + for(k = players; k > j; k--) + id[k] = id[k-1]; + id[j] = i; // id[players] + break; + } + break; + case 3: // by base level + for(j = 0; j < players; j++) + if (char_dat[i].base_level < char_dat[id[j]].base_level || + // if same base level, we sort by base exp. + (char_dat[i].base_level == char_dat[id[j]].base_level && + char_dat[i].base_exp < char_dat[id[j]].base_exp)) { + for(k = players; k > j; k--) + id[k] = id[k-1]; + id[j] = i; // id[players] + break; + } + break; + case 4: // by job (and job level) + for(j = 0; j < players; j++) + if (char_dat[i].class < char_dat[id[j]].class || + // if same job, we sort by job level. + (char_dat[i].class == char_dat[id[j]].class && + char_dat[i].job_level < char_dat[id[j]].job_level) || + // if same job and job level, we sort by job exp. + (char_dat[i].class == char_dat[id[j]].class && + char_dat[i].job_level == char_dat[id[j]].job_level && + char_dat[i].job_exp < char_dat[id[j]].job_exp)) { + for(k = players; k > j; k--) + id[k] = id[k-1]; + id[j] = i; // id[players] + break; + } + break; + case 5: // by location map name + { + int cpm_result; // A lot of player maps are identical. So, test if done often twice. + for(j = 0; j < players; j++) + if ((cpm_result = strcmp(char_dat[i].last_point.map, char_dat[id[j]].last_point.map)) < 0 || // no map are identical and with upper cases (not use stricmp) + // if same map name, we sort by name. + (cpm_result == 0 && + stricmp(char_dat[i].name, char_dat[id[j]].name) < 0)) { + for(k = players; k > j; k--) + id[k] = id[k-1]; + id[j] = i; // id[players] + break; + } + } + break; + default: // 0 or invalid value: no sorting + break; + } + players++; + } + } + + // write files + fp = fopen(online_txt_filename, "w"); + if (fp != NULL) { + fp2 = fopen(online_html_filename, "w"); + if (fp2 != NULL) { + // get time + time(&time_server); // get time in seconds since 1/1/1970 + datetime = localtime(&time_server); // convert seconds in structure + strftime(temp, sizeof(temp), "%d %b %Y %X", datetime); // like sprintf, but only for date/time (05 dec 2003 15:12:52) + // write heading + fprintf(fp2, "<HTML>\n"); + fprintf(fp2, " <META http-equiv=\"Refresh\" content=\"%d\">\n", online_refresh_html); // update on client explorer every x seconds + fprintf(fp2, " <HEAD>\n"); + fprintf(fp2, " <TITLE>Online Players on %s</TITLE>\n", server_name); + fprintf(fp2, " </HEAD>\n"); + fprintf(fp2, " <BODY>\n"); + fprintf(fp2, " <H3>Online Players on %s (%s):</H3>\n", server_name, temp); + fprintf(fp, "Online Players on %s (%s):\n", server_name, temp); + fprintf(fp, "\n"); + + // If we display at least 1 player + if (players > 0) { + j = 0; // count the number of characters for the txt version and to set the separate line + fprintf(fp2, " <table border=\"1\" cellspacing=\"1\">\n"); + fprintf(fp2, " <tr>\n"); + if ((online_display_option & 1) || (online_display_option & 64)) { + fprintf(fp2, " <td><b>Name</b></td>\n"); + if (online_display_option & 64) { + fprintf(fp, "Name "); // 30 + j += 30; + } else { + fprintf(fp, "Name "); // 25 + j += 25; + } + } + if ((online_display_option & 6) == 6) { + fprintf(fp2, " <td><b>Job (levels)</b></td>\n"); + fprintf(fp, "Job Levels "); // 27 + j += 27; + } else if (online_display_option & 2) { + fprintf(fp2, " <td><b>Job</b></td>\n"); + fprintf(fp, "Job "); // 19 + j += 19; + } else if (online_display_option & 4) { + fprintf(fp2, " <td><b>Levels</b></td>\n"); + fprintf(fp, " Levels "); // 8 + j += 8; + } + if (online_display_option & 24) { // 8 or 16 + fprintf(fp2, " <td><b>Location</b></td>\n"); + if (online_display_option & 16) { + fprintf(fp, "Location ( x , y ) "); // 23 + j += 23; + } else { + fprintf(fp, "Location "); // 13 + j += 13; + } + } + if (online_display_option & 32) { + fprintf(fp2, " <td ALIGN=CENTER><b>zenys</b></td>\n"); + fprintf(fp, " Zenys "); // 16 + j += 16; + } + fprintf(fp2, " </tr>\n"); + fprintf(fp, "\n"); + for (k = 0; k < j; k++) + fprintf(fp, "-"); + fprintf(fp, "\n"); + + // display each player. + for (i = 0; i < players; i++) { + // get id of the character (more speed) + j = id[i]; + fprintf(fp2, " <tr>\n"); + // displaying the character name + if ((online_display_option & 1) || (online_display_option & 64)) { // without/with 'GM' display + strcpy(temp, char_dat[j].name); + l = isGM(char_dat[j].account_id); + if (online_display_option & 64) { + if (l >= online_gm_display_min_level) + fprintf(fp, "%-24s (GM) ", temp); + else + fprintf(fp, "%-24s ", temp); + } else + fprintf(fp, "%-24s ", temp); + // name of the character in the html (no < >, because that create problem in html code) + fprintf(fp2, " <td>"); + if ((online_display_option & 64) && l >= online_gm_display_min_level) + fprintf(fp2, "<b>"); + for (k = 0; temp[k]; k++) { + switch(temp[k]) { + case '<': // < + fprintf(fp2, "<"); + break; + case '>': // > + fprintf(fp2, ">"); + break; + default: + fprintf(fp2, "%c", temp[k]); + break; + }; + } + if ((online_display_option & 64) && l >= online_gm_display_min_level) + fprintf(fp2, "</b> (GM)"); + fprintf(fp2, "</td>\n"); + } + // displaying of the job + if (online_display_option & 6) { + char * jobname = job_name(char_dat[j].class); + if ((online_display_option & 6) == 6) { + fprintf(fp2, " <td>%s %d/%d</td>\n", jobname, char_dat[j].base_level, char_dat[j].job_level); + fprintf(fp, "%-18s %3d/%3d ", jobname, char_dat[j].base_level, char_dat[j].job_level); + } else if (online_display_option & 2) { + fprintf(fp2, " <td>%s</td>\n", jobname); + fprintf(fp, "%-18s ", jobname); + } else if (online_display_option & 4) { + fprintf(fp2, " <td>%d/%d</td>\n", char_dat[j].base_level, char_dat[j].job_level); + fprintf(fp, "%3d/%3d ", char_dat[j].base_level, char_dat[j].job_level); + } + } + // displaying of the map + if (online_display_option & 24) { // 8 or 16 + // prepare map name + memset(temp, 0, sizeof(temp)); + strncpy(temp, char_dat[j].last_point.map, 16); + if (strchr(temp, '.') != NULL) + temp[strchr(temp, '.') - temp] = '\0'; // suppress the '.gat' + // write map name + if (online_display_option & 16) { // map-name AND coordonates + fprintf(fp2, " <td>%s (%d, %d)</td>\n", temp, char_dat[j].last_point.x, char_dat[j].last_point.y); + fprintf(fp, "%-12s (%3d,%3d) ", temp, char_dat[j].last_point.x, char_dat[j].last_point.y); + } else { + fprintf(fp2, " <td>%s</td>\n", temp); + fprintf(fp, "%-12s ", temp); + } + } + // displaying number of zenys + if (online_display_option & 32) { + // write number of zenys + if (char_dat[j].zeny == 0) { // if no zeny + fprintf(fp2, " <td ALIGN=RIGHT>no zeny</td>\n"); + fprintf(fp, " no zeny "); + } else { + fprintf(fp2, " <td ALIGN=RIGHT>%d z</td>\n", char_dat[j].zeny); + fprintf(fp, "%13d z ", char_dat[j].zeny); + } + } + fprintf(fp, "\n"); + fprintf(fp2, " </tr>\n"); + } + fprintf(fp2, " </table>\n"); + fprintf(fp, "\n"); + } + + // Displaying number of online players + if (players == 0) { + fprintf(fp2, " <p>No user is online.</p>\n"); + fprintf(fp, "No user is online.\n"); + // no display if only 1 player + } else if (players == 1) { + } else { + fprintf(fp2, " <p>%d users are online.</p>\n", players); + fprintf(fp, "%d users are online.\n", players); + } + fprintf(fp2, " </BODY>\n"); + fprintf(fp2, "</HTML>\n"); + fclose(fp2); + } + fclose(fp); + } + + return; +} + +//--------------------------------------------------------------------- +// This function return the number of online players in all map-servers +//--------------------------------------------------------------------- +int count_users(void) { + int i, users; + + users = 0; + for(i = 0; i < MAX_MAP_SERVERS; i++) + if (server_fd[i] >= 0) + users += server[i].users; + + return users; +} + +//---------------------------------------- +// Function to send characters to a player +//---------------------------------------- +int mmo_char_send006b(int fd, struct char_session_data *sd) { + int i, j, found_num; + struct mmo_charstatus *p; +#ifdef NEW_006b + const int offset = 24; +#else + const int offset = 4; +#endif + + found_num = 0; + for(i = 0; i < char_num; i++) { + if (char_dat[i].account_id == sd->account_id) { + sd->found_char[found_num] = i; + found_num++; + if (found_num == 9) + break; + } + } + for(i = found_num; i < 9; i++) + sd->found_char[i] = -1; + + memset(WFIFOP(fd,0), 0, offset + found_num * 106); + WFIFOW(fd,0) = 0x6b; + WFIFOW(fd,2) = offset + found_num * 106; + + for(i = 0; i < found_num; i++) { + p = &char_dat[sd->found_char[i]]; + j = offset + (i * 106); // increase speed of code + + WFIFOL(fd,j) = p->char_id; + WFIFOL(fd,j+4) = p->base_exp; + WFIFOL(fd,j+8) = p->zeny; + WFIFOL(fd,j+12) = p->job_exp; + WFIFOL(fd,j+16) = p->job_level; + + WFIFOL(fd,j+20) = 0; + WFIFOL(fd,j+24) = 0; + WFIFOL(fd,j+28) = p->option; + + WFIFOL(fd,j+32) = p->karma; + WFIFOL(fd,j+36) = p->manner; + + WFIFOW(fd,j+40) = p->status_point; + WFIFOW(fd,j+42) = (p->hp > 0x7fff) ? 0x7fff : p->hp; + WFIFOW(fd,j+44) = (p->max_hp > 0x7fff) ? 0x7fff : p->max_hp; + WFIFOW(fd,j+46) = (p->sp > 0x7fff) ? 0x7fff : p->sp; + WFIFOW(fd,j+48) = (p->max_sp > 0x7fff) ? 0x7fff : p->max_sp; + WFIFOW(fd,j+50) = DEFAULT_WALK_SPEED; // p->speed; + WFIFOW(fd,j+52) = p->class; + WFIFOW(fd,j+54) = p->hair; + WFIFOW(fd,j+56) = p->weapon; + WFIFOW(fd,j+58) = p->base_level; + WFIFOW(fd,j+60) = p->skill_point; + WFIFOW(fd,j+62) = p->head_bottom; + WFIFOW(fd,j+64) = p->shield; + WFIFOW(fd,j+66) = p->head_top; + WFIFOW(fd,j+68) = p->head_mid; + WFIFOW(fd,j+70) = p->hair_color; + WFIFOW(fd,j+72) = p->clothes_color; + + memcpy(WFIFOP(fd,j+74), p->name, 24); + + WFIFOB(fd,j+98) = (p->str > 255) ? 255 : p->str; + WFIFOB(fd,j+99) = (p->agi > 255) ? 255 : p->agi; + WFIFOB(fd,j+100) = (p->vit > 255) ? 255 : p->vit; + WFIFOB(fd,j+101) = (p->int_ > 255) ? 255 : p->int_; + WFIFOB(fd,j+102) = (p->dex > 255) ? 255 : p->dex; + WFIFOB(fd,j+103) = (p->luk > 255) ? 255 : p->luk; + WFIFOB(fd,j+104) = p->char_num; + } + + WFIFOSET(fd,WFIFOW(fd,2)); + + return 0; +} + +int set_account_reg2(int acc, int num, struct global_reg *reg) { + int i, c; + + c = 0; + for(i = 0; i < char_num; i++) { + if (char_dat[i].account_id == acc) { + memcpy(char_dat[i].account_reg2, reg, sizeof(char_dat[i].account_reg2)); + char_dat[i].account_reg2_num = num; + c++; + } + } + return c; +} + +// 離婚(char削除時に使用) +int char_divorce(struct mmo_charstatus *cs) { + if (cs == NULL) + return 0; + + if (cs->partner_id > 0){ + int i, j; + for(i = 0; i < char_num; i++) { + if (char_dat[i].char_id == cs->partner_id && char_dat[i].partner_id == cs->char_id) { + cs->partner_id = 0; + char_dat[i].partner_id = 0; + for(j = 0; j < MAX_INVENTORY; j++) + if (char_dat[i].inventory[j].nameid == WEDDING_RING_M || char_dat[i].inventory[j].nameid == WEDDING_RING_F) + memset(&char_dat[i].inventory[j], 0, sizeof(char_dat[i].inventory[0])); + if (cs->inventory[j].nameid == WEDDING_RING_M || cs->inventory[j].nameid == WEDDING_RING_F) + memset(&cs->inventory[j], 0, sizeof(cs->inventory[0])); + return 0; + } + } + } + return 0; +} + +//------------------------------------------------------------ +// E-mail check: return 0 (not correct) or 1 (valid). by [Yor] +//------------------------------------------------------------ +int e_mail_check(unsigned char *email) { + char ch; + unsigned char* last_arobas; + + // athena limits + if (strlen(email) < 3 || strlen(email) > 39) + return 0; + + // part of RFC limits (official reference of e-mail description) + if (strchr(email, '@') == NULL || email[strlen(email)-1] == '@') + return 0; + + if (email[strlen(email)-1] == '.') + return 0; + + last_arobas = strrchr(email, '@'); + + if (strstr(last_arobas, "@.") != NULL || + strstr(last_arobas, "..") != NULL) + return 0; + + for(ch = 1; ch < 32; ch++) { + if (strchr(last_arobas, ch) != NULL) { + return 0; + break; + } + } + + if (strchr(last_arobas, ' ') != NULL || + strchr(last_arobas, ';') != NULL) + return 0; + + // all correct + return 1; +} + +//---------------------------------------------------------------------- +// Force disconnection of an online player (with account value) by [Yor] +//---------------------------------------------------------------------- +int disconnect_player(int accound_id) { + int i; + struct char_session_data *sd; + + // disconnect player if online on char-server + for(i = 0; i < fd_max; i++) { + if (session[i] && (sd = session[i]->session_data)) { + if (sd->account_id == accound_id) { + session[i]->eof = 1; + return 1; + } + } + } + + return 0; +} + +// キャラ削除に伴うデータ削除 +static int char_delete(struct mmo_charstatus *cs) { + int j; + + // ペット削除 + if (cs->pet_id) + inter_pet_delete(cs->pet_id); + for (j = 0; j < MAX_INVENTORY; j++) + if (cs->inventory[j].card[0] == (short)0xff00) + inter_pet_delete(*((long *)(&cs->inventory[j].card[2]))); + for (j = 0; j < MAX_CART; j++) + if (cs->cart[j].card[0] == (short)0xff00) + inter_pet_delete(*((long *)(&cs->cart[j].card[2]))); + // ギルド脱退 + if (cs->guild_id) + inter_guild_leave(cs->guild_id, cs->account_id, cs->char_id); + // パーティー脱退 + if (cs->party_id) + inter_party_leave(cs->party_id, cs->account_id); + // 離婚 + if (cs->partner_id){ + // 離婚情報をmapに通知 + char buf[10]; + WBUFW(buf,0) = 0x2b12; + WBUFL(buf,2) = cs->char_id; + WBUFL(buf,6) = cs->partner_id; + mapif_sendall(buf,10); + // 離婚 + char_divorce(cs); + } + return 0; +} + +int parse_tologin(int fd) { + int i; + struct char_session_data *sd; + + // only login-server can have an access to here. + // so, if it isn't the login-server, we disconnect the session (fd != login_fd). + if (fd != login_fd || session[fd]->eof) { + if (fd == login_fd) { + printf("Char-server can't connect to login-server (connection #%d).\n", fd); + login_fd = -1; + } + close(fd); + delete_session(fd); + return 0; + } + + sd = session[fd]->session_data; + + while(RFIFOREST(fd) >= 2) { +// printf("parse_tologin: connection #%d, packet: 0x%x (with being read: %d bytes).\n", fd, RFIFOW(fd,0), RFIFOREST(fd)); + + switch(RFIFOW(fd,0)) { + case 0x2711: + if (RFIFOREST(fd) < 3) + return 0; + if (RFIFOB(fd,2)) { +// printf("connect login server error : %d\n", RFIFOB(fd,2)); + printf("Can not connect to login-server.\n"); + printf("The server communication passwords (default s1/p1) is probably invalid.\n"); + printf("Also, please make sure your accounts file (default: accounts.txt) has those values present.\n"); + printf("If you changed the communication passwords, change them back at map_athena.conf and char_athena.conf\n"); + exit(1); + } else { + printf("Connected to login-server (connection #%d).\n", fd); + // if no map-server already connected, display a message... + for(i = 0; i < MAX_MAP_SERVERS; i++) + if (server_fd[i] >= 0 && server[i].map[0][0]) // if map-server online and at least 1 map + break; + if (i == MAX_MAP_SERVERS) + printf("Awaiting maps from map-server.\n"); + } + RFIFOSKIP(fd,3); + break; + + case 0x2713: + if (RFIFOREST(fd) < 51) + return 0; +// printf("parse_tologin 2713 : %d\n", RFIFOB(fd,6)); + for(i = 0; i < fd_max; i++) { + if (session[i] && (sd = session[i]->session_data) && sd->account_id == RFIFOL(fd,2)) { + if (RFIFOB(fd,6) != 0) { + WFIFOW(i,0) = 0x6c; + WFIFOB(i,2) = 0x42; + WFIFOSET(i,3); + } else if (max_connect_user == 0 || count_users() < max_connect_user) { +// if (max_connect_user == 0) +// printf("max_connect_user (unlimited) -> accepted.\n"); +// else +// printf("count_users(): %d < max_connect_user (%d) -> accepted.\n", count_users(), max_connect_user); + memcpy(sd->email, RFIFOP(fd, 7), 40); + if (e_mail_check(sd->email) == 0) + strncpy(sd->email, "a@a.com", 40); // default e-mail + sd->connect_until_time = (time_t)RFIFOL(fd,47); + // send characters to player + mmo_char_send006b(i, sd); + } else { + // refuse connection: too much online players +// printf("count_users(): %d < max_connect_use (%d) -> fail...\n", count_users(), max_connect_user); + WFIFOW(i,0) = 0x6c; + WFIFOW(i,2) = 0; + WFIFOSET(i,3); + } + break; + } + } + RFIFOSKIP(fd,51); + break; + + // Receiving of an e-mail/time limit from the login-server (answer of a request because a player comes back from map-server to char-server) by [Yor] + case 0x2717: + if (RFIFOREST(fd) < 50) + return 0; + for(i = 0; i < fd_max; i++) { + if (session[i] && (sd = session[i]->session_data)) { + if (sd->account_id == RFIFOL(fd,2)) { + memcpy(sd->email, RFIFOP(fd,6), 40); + if (e_mail_check(sd->email) == 0) + strncpy(sd->email, "a@a.com", 40); // default e-mail + sd->connect_until_time = (time_t)RFIFOL(fd,46); + break; + } + } + } + RFIFOSKIP(fd,50); + break; + + case 0x2721: // gm reply + if (RFIFOREST(fd) < 10) + return 0; + { + unsigned char buf[10]; + WBUFW(buf,0) = 0x2b0b; + WBUFL(buf,2) = RFIFOL(fd,2); // account + WBUFL(buf,6) = RFIFOL(fd,6); // GM level + mapif_sendall(buf,10); +// printf("parse_tologin: To become GM answer: char -> map.\n"); + } + RFIFOSKIP(fd,10); + break; + + case 0x2723: // changesex reply (modified by [Yor]) + if (RFIFOREST(fd) < 7) + return 0; + { + int acc, sex, i, j; + unsigned char buf[7]; + acc = RFIFOL(fd,2); + sex = RFIFOB(fd,6); + RFIFOSKIP(fd, 7); + if (acc > 0) { + for (i = 0; i < char_num; i++) { + if (char_dat[i].account_id == acc) { + int jobclass = char_dat[i].class; + char_dat[i].sex = sex; + auth_fifo[i].sex = sex; + if (jobclass == 19 || jobclass == 20 || + jobclass == 4020 || jobclass == 4021 || + jobclass == 4042 || jobclass == 4043) { + // job modification + if (jobclass == 19 || jobclass == 20) { + char_dat[i].class = (sex) ? 19 : 20; + } else if (jobclass == 4020 || jobclass == 4021) { + char_dat[i].class = (sex) ? 4020 : 4021; + } else if (jobclass == 4042 || jobclass == 4043) { + char_dat[i].class = (sex) ? 4042 : 4043; + } + // remove specifical skills of classes 19, 4020 and 4042 + for(j = 315; j <= 322; j++) { + if (char_dat[i].skill[j].id > 0 && !char_dat[i].skill[j].flag) { + char_dat[i].skill_point += char_dat[i].skill[j].lv; + char_dat[i].skill[j].id = 0; + char_dat[i].skill[j].lv = 0; + } + } + // remove specifical skills of classes 20, 4021 and 4043 + for(j = 323; j <= 330; j++) { + if (char_dat[i].skill[j].id > 0 && !char_dat[i].skill[j].flag) { + char_dat[i].skill_point += char_dat[i].skill[j].lv; + char_dat[i].skill[j].id = 0; + char_dat[i].skill[j].lv = 0; + } + } + } + // to avoid any problem with equipment and invalid sex, equipment is unequiped. + for (j = 0; j < MAX_INVENTORY; j++) { + if (char_dat[i].inventory[j].nameid && char_dat[i].inventory[j].equip) + char_dat[i].inventory[j].equip = 0; + } + char_dat[i].weapon = 0; + char_dat[i].shield = 0; + char_dat[i].head_top = 0; + char_dat[i].head_mid = 0; + char_dat[i].head_bottom = 0; + } + } + // disconnect player if online on char-server + disconnect_player(acc); + } + WBUFW(buf,0) = 0x2b0d; + WBUFL(buf,2) = acc; + WBUFB(buf,6) = sex; + mapif_sendall(buf, 7); + } + break; + + case 0x2726: // Request to send a broadcast message (no answer) + if (RFIFOREST(fd) < 8 || RFIFOREST(fd) < (8 + RFIFOL(fd,4))) + return 0; + if (RFIFOL(fd,4) < 1) + char_log("Receiving a message for broadcast, but message is void." RETCODE); + else { + // at least 1 map-server + for(i = 0; i < MAX_MAP_SERVERS; i++) + if (server_fd[i] >= 0) + break; + if (i == MAX_MAP_SERVERS) + char_log("'ladmin': Receiving a message for broadcast, but no map-server is online." RETCODE); + else { + char buf[128]; + char message[RFIFOL(fd,4) + 1]; // +1 to add a null terminated if not exist in the packet + int lp; + char *p; + memset(message, '\0', sizeof(message)); + memcpy(message, RFIFOP(fd,8), RFIFOL(fd,4)); + message[sizeof(message)-1] = '\0'; + remove_control_chars(message); + // remove all first spaces + p = message; + while(p[0] == ' ') + p++; + // if message is only composed of spaces + if (p[0] == '\0') + char_log("Receiving a message for broadcast, but message is only a lot of spaces." RETCODE); + // else send message to all map-servers + else { + if (RFIFOW(fd,2) == 0) { + char_log("'ladmin': Receiving a message for broadcast (message (in yellow): %s)" RETCODE, + message); + lp = 4; + } else { + char_log("'ladmin': Receiving a message for broadcast (message (in blue): %s)" RETCODE, + message); + lp = 8; + } + // split message to max 80 char + while(p[0] != '\0') { // if not finish + if (p[0] == ' ') // jump if first char is a space + p++; + else { + char split[80]; + char* last_space; + sscanf(p, "%79[^\t]", split); // max 79 char, any char (\t is control char and control char was removed before) + split[sizeof(split)-1] = '\0'; // last char always \0 + if ((last_space = strrchr(split, ' ')) != NULL) { // searching space from end of the string + last_space[0] = '\0'; // replace it by NULL to have correct length of split + p++; // to jump the new NULL + } + p += strlen(split); + // send broadcast to all map-servers + WBUFW(buf,0) = 0x3800; + WBUFW(buf,2) = lp + strlen(split) + 1; + WBUFL(buf,4) = 0x65756c62; // only write if in blue (lp = 8) + memcpy(WBUFP(buf,lp), split, strlen(split) + 1); + mapif_sendall(buf, WBUFW(buf,2)); + } + } + } + } + } + RFIFOSKIP(fd,8 + RFIFOL(fd,4)); + break; + + // account_reg2変更通知 + case 0x2729: + if (RFIFOREST(fd) < 4 || RFIFOREST(fd) < RFIFOW(fd,2)) + return 0; + { + struct global_reg reg[ACCOUNT_REG2_NUM]; + unsigned char buf[4096]; + int j, p, acc; + acc = RFIFOL(fd,4); + for (p = 8, j = 0; p < RFIFOW(fd,2) && j < ACCOUNT_REG2_NUM; p += 36, j++) { + memcpy(reg[j].str, RFIFOP(fd,p), 32); + reg[j].value = RFIFOL(fd,p+32); + } + set_account_reg2(acc, j, reg); + // 同垢ログインを禁止していれば送る必要は無い + memcpy(buf, RFIFOP(fd,0), RFIFOW(fd,2)); + WBUFW(buf,0) = 0x2b11; + mapif_sendall(buf, WBUFW(buf,2)); + RFIFOSKIP(fd,RFIFOW(fd,2)); +// printf("char: save_account_reg_reply\n"); + } + break; + + // Account deletion notification (from login-server) + case 0x2730: + if (RFIFOREST(fd) < 6) + return 0; + // Deletion of all characters of the account + for(i = 0; i < char_num; i++) { + if (char_dat[i].account_id == RFIFOL(fd,2)) { + char_delete(&char_dat[i]); + if (i < char_num - 1) { + memcpy(&char_dat[i], &char_dat[char_num-1], sizeof(struct mmo_charstatus)); + // if moved character owns to deleted account, check again it's character + if (char_dat[i].account_id == RFIFOL(fd,2)) { + i--; + // Correct moved character reference in the character's owner by [Yor] + } else { + int j, k; + struct char_session_data *sd2; + for (j = 0; j < fd_max; j++) { + if (session[j] && (sd2 = session[j]->session_data) && + sd2->account_id == char_dat[char_num-1].account_id) { + for (k = 0; k < 9; k++) { + if (sd2->found_char[k] == char_num-1) { + sd2->found_char[k] = i; + break; + } + } + break; + } + } + } + } + char_num--; + } + } + // Deletion of the storage + inter_storage_delete(RFIFOL(fd,2)); + // send to all map-servers to disconnect the player + { + unsigned char buf[6]; + WBUFW(buf,0) = 0x2b13; + WBUFL(buf,2) = RFIFOL(fd,2); + mapif_sendall(buf, 6); + } + // disconnect player if online on char-server + disconnect_player(RFIFOL(fd,2)); + RFIFOSKIP(fd,6); + break; + + // State change of account/ban notification (from login-server) by [Yor] + case 0x2731: + if (RFIFOREST(fd) < 11) + return 0; + // send to all map-servers to disconnect the player + { + unsigned char buf[11]; + WBUFW(buf,0) = 0x2b14; + WBUFL(buf,2) = RFIFOL(fd,2); + WBUFB(buf,6) = RFIFOB(fd,6); // 0: change of statut, 1: ban + WBUFL(buf,7) = RFIFOL(fd,7); // status or final date of a banishment + mapif_sendall(buf, 11); + } + // disconnect player if online on char-server + disconnect_player(RFIFOL(fd,2)); + RFIFOSKIP(fd,11); + break; + + // Receiving GM acounts info from login-server (by [Yor]) + case 0x2732: + if (RFIFOREST(fd) < 4 || RFIFOREST(fd) < RFIFOW(fd,2)) + return 0; + { + char buf[32000]; + if (gm_account != NULL) + free(gm_account); + gm_account = calloc(sizeof(struct gm_account) * ((RFIFOW(fd,2) - 4) / 5), 1); + GM_num = 0; + for (i = 4; i < RFIFOW(fd,2); i = i + 5) { + gm_account[GM_num].account_id = RFIFOL(fd,i); + gm_account[GM_num].level = (int)RFIFOB(fd,i+4); + //printf("GM account: %d -> level %d\n", gm_account[GM_num].account_id, gm_account[GM_num].level); + GM_num++; + } + printf("From login-server: receiving of %d GM accounts information.\n", GM_num); + char_log("From login-server: receiving of %d GM accounts information." RETCODE, GM_num); + create_online_files(); // update online players files (perhaps some online players change of GM level) + // send new gm acccounts level to map-servers + memcpy(buf, RFIFOP(fd,0), RFIFOW(fd,2)); + WBUFW(buf,0) = 0x2b15; + mapif_sendall(buf, RFIFOW(fd,2)); + } + RFIFOSKIP(fd,RFIFOW(fd,2)); + break; + + default: + session[fd]->eof = 1; + return 0; + } + } + RFIFOFLUSH(fd); + + return 0; +} + +//-------------------------------- +// Map-server anti-freeze system +//-------------------------------- +int map_anti_freeze_system(int tid, unsigned int tick, int id, int data) { + int i; + + //printf("Entering in map_anti_freeze_system function to check freeze of servers.\n"); + for(i = 0; i < MAX_MAP_SERVERS; i++) { + if (server_fd[i] >= 0) {// if map-server is online + //printf("map_anti_freeze_system: server #%d, flag: %d.\n", i, server_freezeflag[i]); + if (server_freezeflag[i]-- < 1) { // Map-server anti-freeze system. Counter. 5 ok, 4...0 freezed + printf("Map-server anti-freeze system: char-server #%d is freezed -> disconnection.\n", i); + char_log("Map-server anti-freeze system: char-server #%d is freezed -> disconnection." RETCODE, + i); + session[server_fd[i]]->eof = 1; + } + } + } + + return 0; +} + +int parse_frommap(int fd) { + int i, j; + int id; + + for(id = 0; id < MAX_MAP_SERVERS; id++) + if (server_fd[id] == fd) + break; + if (id == MAX_MAP_SERVERS || session[fd]->eof) { + if (id < MAX_MAP_SERVERS) { + printf("Map-server %d (session #%d) has disconnected.\n", id, fd); + memset(&server[id], 0, sizeof(struct mmo_map_server)); + server_fd[id] = -1; + for(j = 0; j < char_num; j++) + if (online_chars[j] == fd) + online_chars[j] = -1; + create_online_files(); // update online players files (to remove all online players of this server) + } + close(fd); + delete_session(fd); + return 0; + } + + while(RFIFOREST(fd) >= 2) { +// printf("parse_frommap: connection #%d, packet: 0x%x (with being read: %d bytes).\n", fd, RFIFOW(fd,0), RFIFOREST(fd)); + + switch(RFIFOW(fd,0)) { + // request from map-server to reload GM accounts. Transmission to login-server (by Yor) + case 0x2af7: + if (login_fd > 0) { // don't send request if no login-server + WFIFOW(login_fd,0) = 0x2709; + WFIFOSET(login_fd, 2); +// printf("char : request from map-server to reload GM accounts -> login-server.\n"); + } + RFIFOSKIP(fd,2); + break; + + // Receiving map names list from the map-server + case 0x2afa: + if (RFIFOREST(fd) < 4 || RFIFOREST(fd) < RFIFOW(fd,2)) + return 0; + memset(server[id].map, 0, sizeof(server[id].map)); + j = 0; + for(i = 4; i < RFIFOW(fd,2); i += 16) { + memcpy(server[id].map[j], RFIFOP(fd,i), 16); +// printf("set map %d.%d : %s\n", id, j, server[id].map[j]); + j++; + } + { + unsigned char *p = (unsigned char *)&server[id].ip; + printf("Map-Server %d connected: %d maps, from IP %d.%d.%d.%d port %d.\n", + id, j, p[0], p[1], p[2], p[3], server[id].port); + printf("Map-server %d loading complete.\n", id); + char_log("Map-Server %d connected: %d maps, from IP %d.%d.%d.%d port %d. Map-server %d loading complete." RETCODE, + id, j, p[0], p[1], p[2], p[3], server[id].port, id); + } + WFIFOW(fd,0) = 0x2afb; + WFIFOB(fd,2) = 0; + memcpy(WFIFOP(fd,3), wisp_server_name, 24); // name for wisp to player + WFIFOSET(fd,27); + { + unsigned char buf[16384]; + int x; + if (j == 0) { + printf("WARNING: Map-Server %d have NO map.\n", id); + char_log("WARNING: Map-Server %d have NO map." RETCODE, id); + // Transmitting maps information to the other map-servers + } else { + WBUFW(buf,0) = 0x2b04; + WBUFW(buf,2) = j * 16 + 10; + WBUFL(buf,4) = server[id].ip; + WBUFW(buf,8) = server[id].port; + memcpy(WBUFP(buf,10), RFIFOP(fd,4), j * 16); + mapif_sendallwos(fd, buf, WBUFW(buf,2)); + } + // Transmitting the maps of the other map-servers to the new map-server + for(x = 0; x < MAX_MAP_SERVERS; x++) { + if (server_fd[x] >= 0 && x != id) { + WFIFOW(fd,0) = 0x2b04; + WFIFOL(fd,4) = server[x].ip; + WFIFOW(fd,8) = server[x].port; + j = 0; + for(i = 0; i < MAX_MAP_PER_SERVER; i++) + if (server[x].map[i][0]) + memcpy(WFIFOP(fd,10+(j++)*16), server[x].map[i], 16); + if (j > 0) { + WFIFOW(fd,2) = j * 16 + 10; + WFIFOSET(fd,WFIFOW(fd,2)); + } + } + } + } + RFIFOSKIP(fd,RFIFOW(fd,2)); + break; + + // 認証要求 + case 0x2afc: + if (RFIFOREST(fd) < 22) + return 0; + //printf("auth_fifo search: account: %d, char: %d, secure: %08x-%08x\n", RFIFOL(fd,2), RFIFOL(fd,6), RFIFOL(fd,10), RFIFOL(fd,14)); + for(i = 0; i < AUTH_FIFO_SIZE; i++) { + if (auth_fifo[i].account_id == RFIFOL(fd,2) && + auth_fifo[i].char_id == RFIFOL(fd,6) && + auth_fifo[i].login_id1 == RFIFOL(fd,10) && +#if CMP_AUTHFIFO_LOGIN2 != 0 + // here, it's the only area where it's possible that we doesn't know login_id2 (map-server asks just after 0x72 packet, that doesn't given the value) + (auth_fifo[i].login_id2 == RFIFOL(fd,14) || RFIFOL(fd,14) == 0) && // relate to the versions higher than 18 +#endif + (!check_ip_flag || auth_fifo[i].ip == RFIFOL(fd,18)) && + !auth_fifo[i].delflag) { + auth_fifo[i].delflag = 1; + WFIFOW(fd,0) = 0x2afd; + WFIFOW(fd,2) = 16 + sizeof(struct mmo_charstatus); + WFIFOL(fd,4) = RFIFOL(fd,2); + WFIFOL(fd,8) = auth_fifo[i].login_id2; + WFIFOL(fd,12) = (unsigned long)auth_fifo[i].connect_until_time; + char_dat[auth_fifo[i].char_pos].sex = auth_fifo[i].sex; + memcpy(WFIFOP(fd,16), &char_dat[auth_fifo[i].char_pos], sizeof(struct mmo_charstatus)); + WFIFOSET(fd, WFIFOW(fd,2)); + //printf("auth_fifo search success (auth #%d, account %d, character: %d).\n", i, RFIFOL(fd,2), RFIFOL(fd,6)); + break; + } + } + if (i == AUTH_FIFO_SIZE) { + WFIFOW(fd,0) = 0x2afe; + WFIFOL(fd,2) = RFIFOL(fd,2); + WFIFOSET(fd,6); + printf("auth_fifo search error! account %d not authentified.\n", RFIFOL(fd,2)); + } + RFIFOSKIP(fd,22); + break; + + // MAPサーバー上のユーザー数受信 + case 0x2aff: + if (RFIFOREST(fd) < 6 || RFIFOREST(fd) < RFIFOW(fd,2)) + return 0; + server[id].users = RFIFOW(fd,4); + if(anti_freeze_enable) + server_freezeflag[id] = 5; // Map anti-freeze system. Counter. 5 ok, 4...0 freezed + // remove all previously online players of the server + for(i = 0; i < char_num; i++) + if (online_chars[i] == id) + online_chars[i] = -1; + // add online players in the list by [Yor] + for(i = 0; i < server[id].users; i++) { + int char_id = RFIFOL(fd,6+i*4); + for(j = 0; j < char_num; j++) + if (char_dat[j].char_id == char_id) { + online_chars[j] = id; + //printf("%d\n", char_id); + break; + } + } + if (update_online < time(NULL)) { // Time is done + update_online = time(NULL) + 8; + create_online_files(); // only every 8 sec. (normally, 1 server send users every 5 sec.) Don't update every time, because that takes time, but only every 2 connection. + // it set to 8 sec because is more than 5 (sec) and if we have more than 1 map-server, informations can be received in shifted. + } + RFIFOSKIP(fd,6+i*4); + break; + + // キャラデータ保存 + case 0x2b01: + if (RFIFOREST(fd) < 4 || RFIFOREST(fd) < RFIFOW(fd,2)) + return 0; + for(i = 0; i < char_num; i++) { + if (char_dat[i].account_id == RFIFOL(fd,4) && + char_dat[i].char_id == RFIFOL(fd,8)) + break; + } + if (i != char_num) + memcpy(&char_dat[i], RFIFOP(fd,12), sizeof(struct mmo_charstatus)); + RFIFOSKIP(fd,RFIFOW(fd,2)); + break; + + // キャラセレ要求 + case 0x2b02: + if (RFIFOREST(fd) < 18) + return 0; + if (auth_fifo_pos >= AUTH_FIFO_SIZE) + auth_fifo_pos = 0; + //printf("auth_fifo set (auth #%d) - account: %d, secure: %08x-%08x\n", auth_fifo_pos, RFIFOL(fd,2), RFIFOL(fd,6), RFIFOL(fd,10)); + auth_fifo[auth_fifo_pos].account_id = RFIFOL(fd,2); + auth_fifo[auth_fifo_pos].char_id = 0; + auth_fifo[auth_fifo_pos].login_id1 = RFIFOL(fd,6); + auth_fifo[auth_fifo_pos].login_id2 = RFIFOL(fd,10); + auth_fifo[auth_fifo_pos].delflag = 2; + auth_fifo[auth_fifo_pos].char_pos = 0; + auth_fifo[auth_fifo_pos].connect_until_time = 0; // unlimited/unknown time by default (not display in map-server) + auth_fifo[auth_fifo_pos].ip = RFIFOL(fd,14); + auth_fifo_pos++; + WFIFOW(fd,0) = 0x2b03; + WFIFOL(fd,2) = RFIFOL(fd,2); + WFIFOB(fd,6) = 0; + WFIFOSET(fd,7); + RFIFOSKIP(fd,18); + break; + + // マップサーバー間移動要求 + case 0x2b05: + if (RFIFOREST(fd) < 49) + return 0; + if (auth_fifo_pos >= AUTH_FIFO_SIZE) + auth_fifo_pos = 0; + WFIFOW(fd,0) = 0x2b06; + memcpy(WFIFOP(fd,2), RFIFOP(fd,2), 42); + //printf("auth_fifo set (auth#%d) - account: %d, secure: 0x%08x-0x%08x\n", auth_fifo_pos, RFIFOL(fd,2), RFIFOL(fd,6), RFIFOL(fd,10)); + auth_fifo[auth_fifo_pos].account_id = RFIFOL(fd,2); + auth_fifo[auth_fifo_pos].char_id = RFIFOL(fd,14); + auth_fifo[auth_fifo_pos].login_id1 = RFIFOL(fd,6); + auth_fifo[auth_fifo_pos].login_id2 = RFIFOL(fd,10); + auth_fifo[auth_fifo_pos].delflag = 0; + auth_fifo[auth_fifo_pos].sex = RFIFOB(fd,44); + auth_fifo[auth_fifo_pos].connect_until_time = 0; // unlimited/unknown time by default (not display in map-server) + auth_fifo[auth_fifo_pos].ip = RFIFOL(fd,45); + for(i = 0; i < char_num; i++) + if (char_dat[i].account_id == RFIFOL(fd,2) && + char_dat[i].char_id == RFIFOL(fd,14)) { + auth_fifo[auth_fifo_pos].char_pos = i; + auth_fifo_pos++; + WFIFOL(fd,6) = 0; + break; + } + if (i == char_num) + WFIFOW(fd,6) = 1; + WFIFOSET(fd,44); + RFIFOSKIP(fd,49); + break; + + // キャラ名検索 + case 0x2b08: + if (RFIFOREST(fd) < 6) + return 0; + for(i = 0; i < char_num; i++) { + if (char_dat[i].char_id == RFIFOL(fd,2)) + break; + } + WFIFOW(fd,0) = 0x2b09; + WFIFOL(fd,2) = RFIFOL(fd,2); + if (i != char_num) + memcpy(WFIFOP(fd,6), char_dat[i].name, 24); + else + memcpy(WFIFOP(fd,6), unknown_char_name, 24); + WFIFOSET(fd,30); + RFIFOSKIP(fd,6); + break; + + // it is a request to become GM + case 0x2b0a: + if (RFIFOREST(fd) < 4 || RFIFOREST(fd) < RFIFOW(fd,2)) + return 0; +// printf("parse_frommap: change gm -> login, account: %d, pass: '%s'.\n", RFIFOL(fd,4), RFIFOP(fd,8)); + if (login_fd > 0) { // don't send request if no login-server + WFIFOW(login_fd,0) = 0x2720; + memcpy(WFIFOP(login_fd,2), RFIFOP(fd,2), RFIFOW(fd,2)-2); + WFIFOSET(login_fd, RFIFOW(fd,2)); + } else { + WFIFOW(fd,0) = 0x2b0b; + WFIFOL(fd,2) = RFIFOL(fd,4); + WFIFOL(fd,6) = 0; + WFIFOSET(fd, 10); + } + RFIFOSKIP(fd, RFIFOW(fd,2)); + break; + + // Map server send information to change an email of an account -> login-server + case 0x2b0c: + if (RFIFOREST(fd) < 86) + return 0; + if (login_fd > 0) { // don't send request if no login-server + memcpy(WFIFOP(login_fd,0), RFIFOP(fd,0), 86); // 0x2722 <account_id>.L <actual_e-mail>.40B <new_e-mail>.40B + WFIFOW(login_fd,0) = 0x2722; + WFIFOSET(login_fd, 86); + } + RFIFOSKIP(fd, 86); + break; + + // Map server ask char-server about a character name to do some operations (all operations are transmitted to login-server) + case 0x2b0e: + if (RFIFOREST(fd) < 44) + return 0; + { + char character_name[24]; + int acc = RFIFOL(fd,2); // account_id of who ask (-1 if nobody) + memcpy(character_name, RFIFOP(fd,6), 24); + character_name[sizeof(character_name) -1] = '\0'; + // prepare answer + WFIFOW(fd,0) = 0x2b0f; // answer + WFIFOL(fd,2) = acc; // who want do operation + WFIFOW(fd,30) = RFIFOW(fd, 30); // type of operation: 1-block, 2-ban, 3-unblock, 4-unban, 5-changesex + // search character + i = search_character_index(character_name); + if (i >= 0) { + memcpy(WFIFOP(fd,6), search_character_name(i), 24); // put correct name if found + WFIFOW(fd,32) = 0; // answer: 0-login-server resquest done, 1-player not found, 2-gm level too low, 3-login-server offline + switch(RFIFOW(fd, 30)) { + case 1: // block + if (acc == -1 || isGM(acc) >= isGM(char_dat[i].account_id)) { + if (login_fd > 0) { // don't send request if no login-server + WFIFOW(login_fd,0) = 0x2724; + WFIFOL(login_fd,2) = char_dat[i].account_id; // account value + WFIFOL(login_fd,6) = 5; // status of the account + WFIFOSET(login_fd, 10); +// printf("char : status -> login: account %d, status: %d \n", char_dat[i].account_id, 5); + } else + WFIFOW(fd,32) = 3; // answer: 0-login-server resquest done, 1-player not found, 2-gm level too low, 3-login-server offline + } else + WFIFOW(fd,32) = 2; // answer: 0-login-server resquest done, 1-player not found, 2-gm level too low, 3-login-server offline + break; + case 2: // ban + if (acc == -1 || isGM(acc) >= isGM(char_dat[i].account_id)) { + if (login_fd > 0) { // don't send request if no login-server + WFIFOW(login_fd, 0) = 0x2725; + WFIFOL(login_fd, 2) = char_dat[i].account_id; // account value + WFIFOW(login_fd, 6) = RFIFOW(fd,32); // year + WFIFOW(login_fd, 8) = RFIFOW(fd,34); // month + WFIFOW(login_fd,10) = RFIFOW(fd,36); // day + WFIFOW(login_fd,12) = RFIFOW(fd,38); // hour + WFIFOW(login_fd,14) = RFIFOW(fd,40); // minute + WFIFOW(login_fd,16) = RFIFOW(fd,42); // second + WFIFOSET(login_fd,18); +// printf("char : status -> login: account %d, ban: %dy %dm %dd %dh %dmn %ds\n", +// char_dat[i].account_id, (short)RFIFOW(fd,32), (short)RFIFOW(fd,34), (short)RFIFOW(fd,36), (short)RFIFOW(fd,38), (short)RFIFOW(fd,40), (short)RFIFOW(fd,42)); + } else + WFIFOW(fd,32) = 3; // answer: 0-login-server resquest done, 1-player not found, 2-gm level too low, 3-login-server offline + } else + WFIFOW(fd,32) = 2; // answer: 0-login-server resquest done, 1-player not found, 2-gm level too low, 3-login-server offline + break; + case 3: // unblock + if (acc == -1 || isGM(acc) >= isGM(char_dat[i].account_id)) { + if (login_fd > 0) { // don't send request if no login-server + WFIFOW(login_fd,0) = 0x2724; + WFIFOL(login_fd,2) = char_dat[i].account_id; // account value + WFIFOL(login_fd,6) = 0; // status of the account + WFIFOSET(login_fd, 10); +// printf("char : status -> login: account %d, status: %d \n", char_dat[i].account_id, 0); + } else + WFIFOW(fd,32) = 3; // answer: 0-login-server resquest done, 1-player not found, 2-gm level too low, 3-login-server offline + } else + WFIFOW(fd,32) = 2; // answer: 0-login-server resquest done, 1-player not found, 2-gm level too low, 3-login-server offline + break; + case 4: // unban + if (acc == -1 || isGM(acc) >= isGM(char_dat[i].account_id)) { + if (login_fd > 0) { // don't send request if no login-server + WFIFOW(login_fd, 0) = 0x272a; + WFIFOL(login_fd, 2) = char_dat[i].account_id; // account value + WFIFOSET(login_fd, 6); +// printf("char : status -> login: account %d, unban request\n", char_dat[i].account_id); + } else + WFIFOW(fd,32) = 3; // answer: 0-login-server resquest done, 1-player not found, 2-gm level too low, 3-login-server offline + } else + WFIFOW(fd,32) = 2; // answer: 0-login-server resquest done, 1-player not found, 2-gm level too low, 3-login-server offline + break; + case 5: // changesex + if (acc == -1 || isGM(acc) >= isGM(char_dat[i].account_id)) { + if (login_fd > 0) { // don't send request if no login-server + WFIFOW(login_fd, 0) = 0x2727; + WFIFOL(login_fd, 2) = char_dat[i].account_id; // account value + WFIFOSET(login_fd, 6); +// printf("char : status -> login: account %d, change sex request\n", char_dat[i].account_id); + } else + WFIFOW(fd,32) = 3; // answer: 0-login-server resquest done, 1-player not found, 2-gm level too low, 3-login-server offline + } else + WFIFOW(fd,32) = 2; // answer: 0-login-server resquest done, 1-player not found, 2-gm level too low, 3-login-server offline + break; + } + } else { + // character name not found + memcpy(WFIFOP(fd,6), character_name, 24); + WFIFOW(fd,32) = 1; // answer: 0-login-server resquest done, 1-player not found, 2-gm level too low, 3-login-server offline + } + // send answer if a player ask, not if the server ask + if (acc != -1) { + WFIFOSET(fd, 34); + } + RFIFOSKIP(fd, 44); + break; + } + +// case 0x2b0f: not more used (available for futur usage) + + // account_reg保存要求 + case 0x2b10: + if (RFIFOREST(fd) < 4 || RFIFOREST(fd) < RFIFOW(fd,2)) + return 0; + { + struct global_reg reg[ACCOUNT_REG2_NUM]; + int p, acc; + acc = RFIFOL(fd,4); + for(p = 8, j = 0; p < RFIFOW(fd,2) && j < ACCOUNT_REG2_NUM; p += 36, j++) { + memcpy(reg[j].str, RFIFOP(fd,p), 32); + reg[j].value = RFIFOL(fd, p+32); + } + set_account_reg2(acc, j, reg); + // loginサーバーへ送る + if (login_fd > 0) { // don't send request if no login-server + WFIFOW(login_fd, 0) = 0x2728; + memcpy(WFIFOP(login_fd,0), RFIFOP(fd,0), RFIFOW(fd,2)); + WFIFOSET(login_fd, WFIFOW(login_fd,2)); + } + // ワールドへの同垢ログインがなければmapサーバーに送る必要はない + //memcpy(buf, RFIFOP(fd,0), RFIFOW(fd,2)); + //WBUFW(buf,0) = 0x2b11; + //mapif_sendall(buf, WBUFW(buf,2)); + RFIFOSKIP(fd, RFIFOW(fd,2)); +// printf("char: save_account_reg (from map)\n"); + break; + } + + default: + // inter server処理に渡す + { + int r = inter_parse_frommap(fd); + if (r == 1) // 処理できた + break; + if (r == 2) // パケット長が足りない + return 0; + } + // inter server処理でもない場合は切断 + printf("char: unknown packet 0x%04x (%d bytes to read in buffer)! (from map).\n", RFIFOW(fd,0), RFIFOREST(fd)); + session[fd]->eof = 1; + return 0; + } + } + return 0; +} + +int search_mapserver(char *map) { + int i, j; + char temp_map[16]; + int temp_map_len; + +// printf("Searching the map-server for map '%s'... ", map); + strncpy(temp_map, map, sizeof(temp_map)); + temp_map[sizeof(temp_map)-1] = '\0'; + if (strchr(temp_map, '.') != NULL) + temp_map[strchr(temp_map, '.') - temp_map + 1] = '\0'; // suppress the '.gat', but conserve the '.' to be sure of the name of the map + + temp_map_len = strlen(temp_map); + for(i = 0; i < MAX_MAP_SERVERS; i++) + if (server_fd[i] >= 0) + for (j = 0; server[i].map[j][0]; j++) + //printf("%s : %s = %d\n", server[i].map[j], map, strncmp(server[i].map[j], temp_map, temp_map_len)); + if (strncmp(server[i].map[j], temp_map, temp_map_len) == 0) { +// printf("found -> server #%d.\n", i); + return i; + } + +// printf("not found.\n"); + return -1; +} + +// char_mapifの初期化処理(現在はinter_mapif初期化のみ) +static int char_mapif_init(int fd) { + return inter_mapif_init(fd); +} + +//----------------------------------------------------- +// Test to know if an IP come from LAN or WAN. by [Yor] +//----------------------------------------------------- +int lan_ip_check(unsigned char *p){ + int i; + int lancheck = 1; + +// printf("lan_ip_check: to compare: %d.%d.%d.%d, network: %d.%d.%d.%d/%d.%d.%d.%d\n", +// p[0], p[1], p[2], p[3], +// subneti[0], subneti[1], subneti[2], subneti[3], +// subnetmaski[0], subnetmaski[1], subnetmaski[2], subnetmaski[3]); + for(i = 0; i < 4; i++) { + if ((subneti[i] & subnetmaski[i]) != (p[i] & subnetmaski[i])) { + lancheck = 0; + break; + } + } + printf("LAN test (result): %s source\033[0m.\n", (lancheck) ? "\033[1;36mLAN" : "\033[1;32mWAN"); + return lancheck; +} + +int parse_char(int fd) { + int i, ch; + char email[40]; + struct char_session_data *sd; + unsigned char *p = (unsigned char *) &session[fd]->client_addr.sin_addr; + + if (login_fd < 0 || session[fd]->eof) { // disconnect any player (already connected to char-server or coming back from map-server) if login-server is diconnected. + if (fd == login_fd) + login_fd = -1; + close(fd); + delete_session(fd); + return 0; + } + + sd = session[fd]->session_data; + + while (RFIFOREST(fd) >= 2) { +// if (RFIFOW(fd,0) < 30000) +// printf("parse_char: connection #%d, packet: 0x%x (with being read: %d bytes).\n", fd, RFIFOW(fd,0), RFIFOREST(fd)); + + switch(RFIFOW(fd,0)) { + case 0x20b: //20040622暗号化ragexe対応 + if (RFIFOREST(fd) < 19) + return 0; + RFIFOSKIP(fd,19); + break; + + case 0x65: // 接続要求 + if (RFIFOREST(fd) < 17) + return 0; + { + int GM_value; + if ((GM_value = isGM(RFIFOL(fd,2)))) + printf("Account Logged On; Account ID: %d (GM level %d).\n", RFIFOL(fd,2), GM_value); + else + printf("Account Logged On; Account ID: %d.\n", RFIFOL(fd,2)); + if (sd == NULL) { + sd = session[fd]->session_data = calloc(sizeof(struct char_session_data), 1); + memset(sd, 0, sizeof(struct char_session_data)); + memcpy(sd->email, "no mail", 40); // put here a mail without '@' to refuse deletion if we don't receive the e-mail + sd->connect_until_time = 0; // unknow or illimited (not displaying on map-server) + } + sd->account_id = RFIFOL(fd,2); + sd->login_id1 = RFIFOL(fd,6); + sd->login_id2 = RFIFOL(fd,10); + sd->sex = RFIFOB(fd,16); + // send back account_id + WFIFOL(fd,0) = RFIFOL(fd,2); + WFIFOSET(fd,4); + // search authentification + for(i = 0; i < AUTH_FIFO_SIZE; i++) { + if (auth_fifo[i].account_id == sd->account_id && + auth_fifo[i].login_id1 == sd->login_id1 && +#if CMP_AUTHFIFO_LOGIN2 != 0 + auth_fifo[i].login_id2 == sd->login_id2 && // relate to the versions higher than 18 +#endif + (!check_ip_flag || auth_fifo[i].ip == session[fd]->client_addr.sin_addr.s_addr) && + auth_fifo[i].delflag == 2) { + auth_fifo[i].delflag = 1; + if (max_connect_user == 0 || count_users() < max_connect_user) { + if (login_fd > 0) { // don't send request if no login-server + // request to login-server to obtain e-mail/time limit + WFIFOW(login_fd,0) = 0x2716; + WFIFOL(login_fd,2) = sd->account_id; + WFIFOSET(login_fd,6); + } + // send characters to player + mmo_char_send006b(fd, sd); + } else { + // refuse connection (over populated) + WFIFOW(fd,0) = 0x6c; + WFIFOW(fd,2) = 0; + WFIFOSET(fd,3); + } + break; + } + } + // authentification not found + if (i == AUTH_FIFO_SIZE) { + if (login_fd > 0) { // don't send request if no login-server + WFIFOW(login_fd,0) = 0x2712; // ask login-server to authentify an account + WFIFOL(login_fd,2) = sd->account_id; + WFIFOL(login_fd,6) = sd->login_id1; + WFIFOL(login_fd,10) = sd->login_id2; // relate to the versions higher than 18 + WFIFOB(login_fd,14) = sd->sex; + WFIFOL(login_fd,15) = session[fd]->client_addr.sin_addr.s_addr; + WFIFOSET(login_fd,19); + } else { // if no login-server, we must refuse connection + WFIFOW(fd,0) = 0x6c; + WFIFOW(fd,2) = 0; + WFIFOSET(fd,3); + } + } + } + RFIFOSKIP(fd,17); + break; + + case 0x66: // キャラ選択 + if (RFIFOREST(fd) < 3) + return 0; + + // if we activated email creation and email is default email + if (email_creation != 0 && strcmp(sd->email, "a@a.com") == 0 && login_fd > 0) { // to modify an e-mail, login-server must be online + WFIFOW(fd, 0) = 0x70; + WFIFOB(fd, 2) = 0; // 00 = Incorrect Email address + WFIFOSET(fd, 3); + + // otherwise, load the character + } else { + for (ch = 0; ch < 9; ch++) + if (sd->found_char[ch] >= 0 && char_dat[sd->found_char[ch]].char_num == RFIFOB(fd,2)) + break; + if (ch != 9) { + char_log("Character Selected, Account ID: %d, Character Slot: %d, Character Name: %s." RETCODE, + sd->account_id, RFIFOB(fd,2), char_dat[sd->found_char[ch]].name); + // searching map server + i = search_mapserver(char_dat[sd->found_char[ch]].last_point.map); + // if map is not found, we check major cities + if (i < 0) { + if ((i = search_mapserver("prontera.gat")) >= 0) { // check is done without 'gat'. + memcpy(char_dat[sd->found_char[ch]].last_point.map, "prontera.gat", 16); + char_dat[sd->found_char[ch]].last_point.x = 273; // savepoint coordonates + char_dat[sd->found_char[ch]].last_point.y = 354; + } else if ((i = search_mapserver("geffen.gat")) >= 0) { // check is done without 'gat'. + memcpy(char_dat[sd->found_char[ch]].last_point.map, "geffen.gat", 16); + char_dat[sd->found_char[ch]].last_point.x = 120; // savepoint coordonates + char_dat[sd->found_char[ch]].last_point.y = 100; + } else if ((i = search_mapserver("morocc.gat")) >= 0) { // check is done without 'gat'. + memcpy(char_dat[sd->found_char[ch]].last_point.map, "morocc.gat", 16); + char_dat[sd->found_char[ch]].last_point.x = 160; // savepoint coordonates + char_dat[sd->found_char[ch]].last_point.y = 94; + } else if ((i = search_mapserver("alberta.gat")) >= 0) { // check is done without 'gat'. + memcpy(char_dat[sd->found_char[ch]].last_point.map, "alberta.gat", 16); + char_dat[sd->found_char[ch]].last_point.x = 116; // savepoint coordonates + char_dat[sd->found_char[ch]].last_point.y = 57; + } else if ((i = search_mapserver("payon.gat")) >= 0) { // check is done without 'gat'. + memcpy(char_dat[sd->found_char[ch]].last_point.map, "payon.gat", 16); + char_dat[sd->found_char[ch]].last_point.x = 87; // savepoint coordonates + char_dat[sd->found_char[ch]].last_point.y = 117; + } else if ((i = search_mapserver("izlude.gat")) >= 0) { // check is done without 'gat'. + memcpy(char_dat[sd->found_char[ch]].last_point.map, "izlude.gat", 16); + char_dat[sd->found_char[ch]].last_point.x = 94; // savepoint coordonates + char_dat[sd->found_char[ch]].last_point.y = 103; + } else { + int j; + // get first online server (with a map) + i = 0; + for(j = 0; j < MAX_MAP_SERVERS; j++) + if (server_fd[j] >= 0 && server[j].map[0][0]) { // change save point to one of map found on the server (the first) + i = j; + memcpy(char_dat[sd->found_char[ch]].last_point.map, server[j].map[0], 16); + printf("Map-server #%d found with a map: '%s'.\n", j, server[j].map[0]); + // coordonates are unknown + break; + } + // if no map-server is connected, we send: server closed + if (j == MAX_MAP_SERVERS) { + WFIFOW(fd,0) = 0x81; + WFIFOL(fd,2) = 1; // 01 = Server closed + WFIFOSET(fd,3); + RFIFOSKIP(fd,3); + break; + } + } + } + WFIFOW(fd,0) = 0x71; + WFIFOL(fd,2) = char_dat[sd->found_char[ch]].char_id; + memcpy(WFIFOP(fd,6), char_dat[sd->found_char[ch]].last_point.map, 16); + printf("Character selection '%s' (account: %d, slot: %d).\n", char_dat[sd->found_char[ch]].name, sd->account_id, ch); + printf("--Send IP of map-server. "); + if (lan_ip_check(p)) + WFIFOL(fd, 22) = inet_addr(lan_map_ip); + else + WFIFOL(fd, 22) = server[i].ip; + WFIFOW(fd,26) = server[i].port; + WFIFOSET(fd,28); + if (auth_fifo_pos >= AUTH_FIFO_SIZE) + auth_fifo_pos = 0; + //printf("auth_fifo set #%d - account %d, char: %d, secure: %08x-%08x\n", auth_fifo_pos, sd->account_id, char_dat[sd->found_char[ch]].char_id, sd->login_id1, sd->login_id2); + auth_fifo[auth_fifo_pos].account_id = sd->account_id; + auth_fifo[auth_fifo_pos].char_id = char_dat[sd->found_char[ch]].char_id; + auth_fifo[auth_fifo_pos].login_id1 = sd->login_id1; + auth_fifo[auth_fifo_pos].login_id2 = sd->login_id2; + auth_fifo[auth_fifo_pos].delflag = 0; + auth_fifo[auth_fifo_pos].char_pos = sd->found_char[ch]; + auth_fifo[auth_fifo_pos].sex = sd->sex; + auth_fifo[auth_fifo_pos].connect_until_time = sd->connect_until_time; + auth_fifo[auth_fifo_pos].ip = session[fd]->client_addr.sin_addr.s_addr; + auth_fifo_pos++; + } + } + RFIFOSKIP(fd,3); + break; + + case 0x67: // 作成 + if (RFIFOREST(fd) < 37) + return 0; + i = make_new_char(fd, RFIFOP(fd,2)); + if (i < 0) { + WFIFOW(fd,0) = 0x6e; + WFIFOB(fd,2) = 0x00; + WFIFOSET(fd,3); + RFIFOSKIP(fd,37); + break; + } + + WFIFOW(fd,0) = 0x6d; + memset(WFIFOP(fd,2), 0, 106); + + WFIFOL(fd,2) = char_dat[i].char_id; + WFIFOL(fd,2+4) = char_dat[i].base_exp; + WFIFOL(fd,2+8) = char_dat[i].zeny; + WFIFOL(fd,2+12) = char_dat[i].job_exp; + WFIFOL(fd,2+16) = char_dat[i].job_level; + + WFIFOL(fd,2+28) = char_dat[i].karma; + WFIFOL(fd,2+32) = char_dat[i].manner; + + WFIFOW(fd,2+40) = 0x30; + WFIFOW(fd,2+42) = (char_dat[i].hp > 0x7fff) ? 0x7fff : char_dat[i].hp; + WFIFOW(fd,2+44) = (char_dat[i].max_hp > 0x7fff) ? 0x7fff : char_dat[i].max_hp; + WFIFOW(fd,2+46) = (char_dat[i].sp > 0x7fff) ? 0x7fff : char_dat[i].sp; + WFIFOW(fd,2+48) = (char_dat[i].max_sp > 0x7fff) ? 0x7fff : char_dat[i].max_sp; + WFIFOW(fd,2+50) = DEFAULT_WALK_SPEED; // char_dat[i].speed; + WFIFOW(fd,2+52) = char_dat[i].class; + WFIFOW(fd,2+54) = char_dat[i].hair; + + WFIFOW(fd,2+58) = char_dat[i].base_level; + WFIFOW(fd,2+60) = char_dat[i].skill_point; + + WFIFOW(fd,2+64) = char_dat[i].shield; + WFIFOW(fd,2+66) = char_dat[i].head_top; + WFIFOW(fd,2+68) = char_dat[i].head_mid; + WFIFOW(fd,2+70) = char_dat[i].hair_color; + + memcpy(WFIFOP(fd,2+74), char_dat[i].name, 24); + + WFIFOB(fd,2+98) = (char_dat[i].str > 255) ? 255 : char_dat[i].str; + WFIFOB(fd,2+99) = (char_dat[i].agi > 255) ? 255 : char_dat[i].agi; + WFIFOB(fd,2+100) = (char_dat[i].vit > 255) ? 255 : char_dat[i].vit; + WFIFOB(fd,2+101) = (char_dat[i].int_ > 255) ? 255 : char_dat[i].int_; + WFIFOB(fd,2+102) = (char_dat[i].dex > 255) ? 255 : char_dat[i].dex; + WFIFOB(fd,2+103) = (char_dat[i].luk > 255) ? 255 : char_dat[i].luk; + WFIFOB(fd,2+104) = char_dat[i].char_num; + + WFIFOSET(fd,108); + RFIFOSKIP(fd,37); + for(ch = 0; ch < 9; ch++) { + if (sd->found_char[ch] == -1) { + sd->found_char[ch] = i; + break; + } + } + + case 0x68: // delete char //Yor's Fix + if (RFIFOREST(fd) < 46) + return 0; + memcpy(email, RFIFOP(fd,6), 40); + if (e_mail_check(email) == 0) + strncpy(email, "a@a.com", 40); // default e-mail + + // if we activated email creation and email is default email + if (email_creation != 0 && strcmp(sd->email, "a@a.com") == 0 && login_fd > 0) { // to modify an e-mail, login-server must be online + // if sended email is incorrect e-mail + if (strcmp(email, "a@a.com") == 0) { + WFIFOW(fd, 0) = 0x70; + WFIFOB(fd, 2) = 0; // 00 = Incorrect Email address + WFIFOSET(fd, 3); + RFIFOSKIP(fd,46); + // we act like we have selected a character + } else { + // we change the packet to set it like selection. + for (i = 0; i < 9; i++) + if (char_dat[sd->found_char[i]].char_id == RFIFOL(fd,2)) { + // we save new e-mail + memcpy(sd->email, email, 40); + // we send new e-mail to login-server ('online' login-server is checked before) + WFIFOW(login_fd,0) = 0x2715; + WFIFOL(login_fd,2) = sd->account_id; + memcpy(WFIFOP(login_fd, 6), email, 40); + WFIFOSET(login_fd,46); + // skip part of the packet! (46, but leave the size of select packet: 3) + RFIFOSKIP(fd,43); + // change value to put new packet (char selection) + RFIFOW(fd, 0) = 0x66; + RFIFOB(fd, 2) = char_dat[sd->found_char[i]].char_num; + // not send packet, it's modify of actual packet + break; + } + if (i == 9) { + WFIFOW(fd, 0) = 0x70; + WFIFOB(fd, 2) = 0; // 00 = Incorrect Email address + WFIFOSET(fd, 3); + RFIFOSKIP(fd,46); + } + } + + // otherwise, we delete the character + } else { + if (strcmpi(email, sd->email) != 0) { // if it's an invalid email + WFIFOW(fd, 0) = 0x70; + WFIFOB(fd, 2) = 0; // 00 = Incorrect Email address + WFIFOSET(fd, 3); + // if mail is correct + } else { + for (i = 0; i < 9; i++) { + struct mmo_charstatus *cs = NULL; + if ((cs = &char_dat[sd->found_char[i]])->char_id == RFIFOL(fd,2)) { + char_delete(cs); // deletion process + + if (sd->found_char[i] != char_num - 1) { + memcpy(&char_dat[sd->found_char[i]], &char_dat[char_num-1], sizeof(struct mmo_charstatus)); + // Correct moved character reference in the character's owner + { + int j, k; + struct char_session_data *sd2; + for (j = 0; j < fd_max; j++) { + if (session[j] && (sd2 = session[j]->session_data) && + sd2->account_id == char_dat[char_num-1].account_id) { + for (k = 0; k < 9; k++) { + if (sd2->found_char[k] == char_num-1) { + sd2->found_char[k] = sd->found_char[i]; + break; + } + } + break; + } + } + } + } + + char_num--; + for(ch = i; ch < 9-1; ch++) + sd->found_char[ch] = sd->found_char[ch+1]; + sd->found_char[8] = -1; + WFIFOW(fd,0) = 0x6f; + WFIFOSET(fd,2); + break; + } + } + + if (i == 9) { + WFIFOW(fd,0) = 0x70; + WFIFOB(fd,2) = 0; + WFIFOSET(fd,3); + } + } + RFIFOSKIP(fd,46); + } + break; + + case 0x2af8: // マップサーバーログイン + if (RFIFOREST(fd) < 60) + return 0; + WFIFOW(fd,0) = 0x2af9; + for(i = 0; i < MAX_MAP_SERVERS; i++) { + if (server_fd[i] < 0) + break; + } + if (i == MAX_MAP_SERVERS || strcmp(RFIFOP(fd,2), userid) || strcmp(RFIFOP(fd,26), passwd)){ + WFIFOB(fd,2) = 3; + WFIFOSET(fd,3); + RFIFOSKIP(fd,60); + } else { + int len; + WFIFOB(fd,2) = 0; + session[fd]->func_parse = parse_frommap; + server_fd[i] = fd; + if(anti_freeze_enable) + server_freezeflag[i] = 5; // Map anti-freeze system. Counter. 5 ok, 4...0 freezed + server[i].ip = RFIFOL(fd,54); + server[i].port = RFIFOW(fd,58); + server[i].users = 0; + memset(server[i].map, 0, sizeof(server[i].map)); + WFIFOSET(fd,3); + RFIFOSKIP(fd,60); + realloc_fifo(fd, FIFOSIZE_SERVERLINK, FIFOSIZE_SERVERLINK); + char_mapif_init(fd); + // send gm acccounts level to map-servers + len = 4; + WFIFOW(fd,0) = 0x2b15; + for(i = 0; i < GM_num; i++) { + WFIFOL(fd,len) = gm_account[i].account_id; + WFIFOB(fd,len+4) = (unsigned char)gm_account[i].level; + len += 5; + } + WFIFOW(fd,2) = len; + WFIFOSET(fd,len); + return 0; + } + break; + + case 0x187: // Alive信号? + if (RFIFOREST(fd) < 6) + return 0; + RFIFOSKIP(fd, 6); + break; + + case 0x7530: // Athena情報所得 + WFIFOW(fd,0) = 0x7531; + WFIFOB(fd,2) = ATHENA_MAJOR_VERSION; + WFIFOB(fd,3) = ATHENA_MINOR_VERSION; + WFIFOB(fd,4) = ATHENA_REVISION; + WFIFOB(fd,5) = ATHENA_RELEASE_FLAG; + WFIFOB(fd,6) = ATHENA_OFFICIAL_FLAG; + WFIFOB(fd,7) = ATHENA_SERVER_INTER | ATHENA_SERVER_CHAR; + WFIFOW(fd,8) = ATHENA_MOD_VERSION; + WFIFOSET(fd,10); + RFIFOSKIP(fd,2); + return 0; + + case 0x7532: // 接続の切断(defaultと処理は一緒だが明示的にするため) + session[fd]->eof = 1; + return 0; + + default: + session[fd]->eof = 1; + return 0; + } + } + RFIFOFLUSH(fd); + return 0; +} + +// 全てのMAPサーバーにデータ送信(送信したmap鯖の数を返す) +int mapif_sendall(unsigned char *buf, unsigned int len) { + int i, c; + + c = 0; + for(i = 0; i < MAX_MAP_SERVERS; i++) { + int fd; + if ((fd = server_fd[i]) >= 0) { + memcpy(WFIFOP(fd,0), buf, len); + WFIFOSET(fd,len); + c++; + } + } + return c; +} + +// 自分以外の全てのMAPサーバーにデータ送信(送信したmap鯖の数を返す) +int mapif_sendallwos(int sfd, unsigned char *buf, unsigned int len) { + int i, c; + + c = 0; + for(i = 0; i < MAX_MAP_SERVERS; i++) { + int fd; + if ((fd = server_fd[i]) >= 0 && fd != sfd) { + memcpy(WFIFOP(fd,0), buf, len); + WFIFOSET(fd, len); + c++; + } + } + return c; +} +// MAPサーバーにデータ送信(map鯖生存確認有り) +int mapif_send(int fd, unsigned char *buf, unsigned int len) { + int i; + + if (fd >= 0) { + for(i = 0; i < MAX_MAP_SERVERS; i++) { + if (fd == server_fd[i]) { + memcpy(WFIFOP(fd,0), buf, len); + WFIFOSET(fd,len); + return 1; + } + } + } + return 0; +} + +int send_users_tologin(int tid, unsigned int tick, int id, int data) { + int users = count_users(); + char buf[16]; + + if (login_fd > 0 && session[login_fd]) { + // send number of user to login server + WFIFOW(login_fd,0) = 0x2714; + WFIFOL(login_fd,2) = users; + WFIFOSET(login_fd,6); + } + // send number of players to all map-servers + WBUFW(buf,0) = 0x2b00; + WBUFL(buf,2) = users; + mapif_sendall(buf, 6); + + return 0; +} + +int check_connect_login_server(int tid, unsigned int tick, int id, int data) { + if (login_fd <= 0 || session[login_fd] == NULL) { + printf("Attempt to connect to login-server...\n"); + login_fd = make_connection(login_ip, login_port); + session[login_fd]->func_parse = parse_tologin; + realloc_fifo(login_fd, FIFOSIZE_SERVERLINK, FIFOSIZE_SERVERLINK); + WFIFOW(login_fd,0) = 0x2710; + memset(WFIFOP(login_fd,2), 0, 24); + memcpy(WFIFOP(login_fd,2), userid, strlen(userid) < 24 ? strlen(userid) : 24); + memset(WFIFOP(login_fd,26), 0, 24); + memcpy(WFIFOP(login_fd,26), passwd, strlen(passwd) < 24 ? strlen(passwd) : 24); + WFIFOL(login_fd,50) = 0; + WFIFOL(login_fd,54) = char_ip; + WFIFOL(login_fd,58) = char_port; + memset(WFIFOP(login_fd,60), 0, 20); + memcpy(WFIFOP(login_fd,60), server_name, strlen(server_name) < 20 ? strlen(server_name) : 20); + WFIFOW(login_fd,80) = 0; + WFIFOW(login_fd,82) = char_maintenance; + WFIFOW(login_fd,84) = char_new; + WFIFOSET(login_fd,86); + } + return 0; +} + +//---------------------------------------------------------- +// Return numerical value of a switch configuration by [Yor] +// on/off, english, fran軋is, deutsch, espaol +//---------------------------------------------------------- +int config_switch(const char *str) { + if (strcmpi(str, "on") == 0 || strcmpi(str, "yes") == 0 || strcmpi(str, "oui") == 0 || strcmpi(str, "ja") == 0 || strcmpi(str, "si") == 0) + return 1; + if (strcmpi(str, "off") == 0 || strcmpi(str, "no") == 0 || strcmpi(str, "non") == 0 || strcmpi(str, "nein") == 0) + return 0; + + return atoi(str); +} + +//------------------------------------------- +// Reading Lan Support configuration by [Yor] +//------------------------------------------- +int lan_config_read(const char *lancfgName) { + int j; + struct hostent * h = NULL; + char line[1024], w1[1024], w2[1024]; + FILE *fp; + + // set default configuration + strncpy(lan_map_ip, "127.0.0.1", sizeof(lan_map_ip)); + subneti[0] = 127; + subneti[1] = 0; + subneti[2] = 0; + subneti[3] = 1; + for(j = 0; j < 4; j++) + subnetmaski[j] = 255; + + fp = fopen(lancfgName, "r"); + + if (fp == NULL) { + printf("LAN support configuration file not found: %s\n", lancfgName); + return 1; + } + + printf ("---start reading of Lan Support configuration...\n"); + + while(fgets(line, sizeof(line)-1, fp)) { + if (line[0] == '/' && line[1] == '/') + continue; + + line[sizeof(line)-1] = '\0'; + if (sscanf(line, "%[^:]: %[^\r\n]", w1, w2) != 2) + continue; + + remove_control_chars(w1); + remove_control_chars(w2); + if (strcmpi(w1, "lan_map_ip") == 0) { // Read map-server Lan IP Address + h = gethostbyname(w2); + if (h != NULL) { + sprintf(lan_map_ip, "%d.%d.%d.%d", (unsigned char)h->h_addr[0], (unsigned char)h->h_addr[1], (unsigned char)h->h_addr[2], (unsigned char)h->h_addr[3]); + } else { + strncpy(lan_map_ip, w2, sizeof(lan_map_ip)); + lan_map_ip[sizeof(lan_map_ip)-1] = 0; + } + printf("LAN IP of map-server: %s.\n", lan_map_ip); + } else if (strcmpi(w1, "subnet") == 0) { // Read Subnetwork + for(j = 0; j < 4; j++) + subneti[j] = 0; + h = gethostbyname(w2); + if (h != NULL) { + for(j = 0; j < 4; j++) + subneti[j] = (unsigned char)h->h_addr[j]; + } else { + sscanf(w2, "%d.%d.%d.%d", &subneti[0], &subneti[1], &subneti[2], &subneti[3]); + } + printf("Sub-network of the map-server: %d.%d.%d.%d.\n", subneti[0], subneti[1], subneti[2], subneti[3]); + } else if (strcmpi(w1, "subnetmask") == 0){ // Read Subnetwork Mask + for(j = 0; j < 4; j++) + subnetmaski[j] = 255; + h = gethostbyname(w2); + if (h != NULL) { + for(j = 0; j < 4; j++) + subnetmaski[j] = (unsigned char)h->h_addr[j]; + } else { + sscanf(w2, "%d.%d.%d.%d", &subnetmaski[0], &subnetmaski[1], &subnetmaski[2], &subnetmaski[3]); + } + printf("Sub-network mask of the map-server: %d.%d.%d.%d.\n", subnetmaski[0], subnetmaski[1], subnetmaski[2], subnetmaski[3]); + } + } + fclose(fp); + + // sub-network check of the map-server + { + unsigned int a0, a1, a2, a3; + unsigned char p[4]; + sscanf(lan_map_ip, "%d.%d.%d.%d", &a0, &a1, &a2, &a3); + p[0] = a0; p[1] = a1; p[2] = a2; p[3] = a3; + printf("LAN test of LAN IP of the map-server: "); + if (lan_ip_check(p) == 0) { + printf("\033[1;31m***ERROR: LAN IP of the map-server doesn't belong to the specified Sub-network.\033[0m\n"); + } + } + + printf("---End reading of Lan Support configuration...\n"); + + return 0; +} + +int char_config_read(const char *cfgName) { + struct hostent *h = NULL; + char line[1024], w1[1024], w2[1024]; + FILE *fp = fopen(cfgName, "r"); + + if (fp == NULL) { + printf("Configuration file not found: %s.\n", cfgName); + exit(1); + } + + while(fgets(line, sizeof(line)-1, fp)) { + if (line[0] == '/' && line[1] == '/') + continue; + + line[sizeof(line)-1] = '\0'; + if (sscanf(line, "%[^:]: %[^\r\n]", w1, w2) != 2) + continue; + + remove_control_chars(w1); + remove_control_chars(w2); + if (strcmpi(w1, "userid") == 0) { + memcpy(userid, w2, 24); + } else if (strcmpi(w1, "passwd") == 0) { + memcpy(passwd, w2, 24); + } else if (strcmpi(w1, "server_name") == 0) { + memcpy(server_name, w2, sizeof(server_name)); + server_name[sizeof(server_name) - 1] = '\0'; + printf("%s server has been intialized\n", w2); + } else if (strcmpi(w1, "wisp_server_name") == 0) { + if (strlen(w2) >= 4) { + memcpy(wisp_server_name, w2, sizeof(wisp_server_name)); + wisp_server_name[sizeof(wisp_server_name) - 1] = '\0'; + } + } else if (strcmpi(w1, "login_ip") == 0) { + h = gethostbyname(w2); + if (h != NULL) { + printf("Login server IP address : %s -> %d.%d.%d.%d\n", w2, (unsigned char)h->h_addr[0], (unsigned char)h->h_addr[1], (unsigned char)h->h_addr[2], (unsigned char)h->h_addr[3]); + sprintf(login_ip_str, "%d.%d.%d.%d", (unsigned char)h->h_addr[0], (unsigned char)h->h_addr[1], (unsigned char)h->h_addr[2], (unsigned char)h->h_addr[3]); + } else + memcpy(login_ip_str, w2, 16); + } else if (strcmpi(w1, "login_port") == 0) { + login_port = atoi(w2); + } else if (strcmpi(w1, "char_ip") == 0) { + h = gethostbyname(w2); + if (h != NULL) { + printf("Character server IP address : %s -> %d.%d.%d.%d\n", w2, (unsigned char)h->h_addr[0], (unsigned char)h->h_addr[1], (unsigned char)h->h_addr[2], (unsigned char)h->h_addr[3]); + sprintf(char_ip_str, "%d.%d.%d.%d", (unsigned char)h->h_addr[0], (unsigned char)h->h_addr[1], (unsigned char)h->h_addr[2], (unsigned char)h->h_addr[3]); + } else + memcpy(char_ip_str, w2, 16); + } else if (strcmpi(w1, "char_port") == 0) { + char_port = atoi(w2); + } else if (strcmpi(w1, "char_maintenance") == 0) { + char_maintenance = atoi(w2); + } else if (strcmpi(w1, "char_new") == 0) { + char_new = atoi(w2); + } else if (strcmpi(w1, "email_creation") == 0) { + email_creation = config_switch(w2); + } else if (strcmpi(w1, "char_txt") == 0) { + strcpy(char_txt, w2); + } else if (strcmpi(w1, "backup_txt") == 0) { //By zanetheinsane + strcpy(backup_txt, w2); + } else if (strcmpi(w1, "backup_txt_flag") == 0) { // The backup_txt file was created because char deletion bug existed. Now it's finish and that take a lot of time to create a second file when there are a lot of characters. By [Yor] + backup_txt_flag = config_switch(w2); + } else if (strcmpi(w1, "max_connect_user") == 0) { + max_connect_user = atoi(w2); + if (max_connect_user < 0) + max_connect_user = 0; // unlimited online players + } else if (strcmpi(w1, "check_ip_flag") == 0) { + check_ip_flag = config_switch(w2); + } else if (strcmpi(w1, "autosave_time") == 0) { + autosave_interval = atoi(w2)*1000; + if (autosave_interval <= 0) + autosave_interval = DEFAULT_AUTOSAVE_INTERVAL; + } else if (strcmpi(w1, "start_point") == 0) { + char map[32]; + int x, y; + if (sscanf(w2, "%[^,],%d,%d", map, &x, &y) < 3) + continue; + if (strstr(map, ".gat") != NULL) { // Verify at least if '.gat' is in the map name + memcpy(start_point.map, map, 16); + start_point.x = x; + start_point.y = y; + } + } else if (strcmpi(w1, "start_zeny") == 0) { + start_zeny = atoi(w2); + if (start_zeny < 0) + start_zeny = 0; + } else if (strcmpi(w1, "start_weapon") == 0) { + start_zeny = atoi(w2); + if (start_weapon < 0) + start_weapon = 0; + } else if (strcmpi(w1, "start_armor") == 0) { + start_zeny = atoi(w2); + if (start_armor < 0) + start_armor = 0; + } else if (strcmpi(w1, "unknown_char_name") == 0) { + strcpy(unknown_char_name, w2); + unknown_char_name[24] = 0; + } else if (strcmpi(w1, "char_log_filename") == 0) { + strcpy(char_log_filename, w2); + } else if (strcmpi(w1, "name_ignoring_case") == 0) { + name_ignoring_case = config_switch(w2); + } else if (strcmpi(w1, "char_name_option") == 0) { + char_name_option = atoi(w2); + } else if (strcmpi(w1, "char_name_letters") == 0) { + strcpy(char_name_letters, w2); +// online files options + } else if (strcmpi(w1, "online_txt_filename") == 0) { + strcpy(online_txt_filename, w2); + } else if (strcmpi(w1, "online_html_filename") == 0) { + strcpy(online_html_filename, w2); + } else if (strcmpi(w1, "online_sorting_option") == 0) { + online_sorting_option = atoi(w2); + } else if (strcmpi(w1, "online_display_option") == 0) { + online_display_option = atoi(w2); + } else if (strcmpi(w1, "online_gm_display_min_level") == 0) { // minimum GM level to display 'GM' when we want to display it + online_gm_display_min_level = atoi(w2); + if (online_gm_display_min_level < 5) // send online file every 5 seconds to player is enough + online_gm_display_min_level = 5; + } else if (strcmpi(w1, "online_refresh_html") == 0) { + online_refresh_html = atoi(w2); + if (online_refresh_html < 1) + online_refresh_html = 1; + } else if(strcmpi(w1,"anti_freeze_enable")==0){ + anti_freeze_enable = config_switch(w2); + } else if (strcmpi(w1, "anti_freeze_interval") == 0) { + ANTI_FREEZE_INTERVAL = atoi(w2); + if (ANTI_FREEZE_INTERVAL < 5) + ANTI_FREEZE_INTERVAL = 5; // minimum 5 seconds + } else if (strcmpi(w1, "import") == 0) { + char_config_read(w2); + } + } + fclose(fp); + + return 0; +} + +void do_final(void) { + int i; + + // write online players files with no player + for(i = 0; i < char_num; i++) + online_chars[i] = -1; + create_online_files(); + free(online_chars); + + mmo_char_sync(); + inter_save(); + + if (gm_account != NULL) + free(gm_account); + + free(char_dat); + delete_session(login_fd); + delete_session(char_fd); + + char_log("----End of char-server (normal end with closing of all files)." RETCODE); +} + +int do_init(int argc, char **argv) { + int i; + + // a newline in the log... + char_log(""); + char_log("The char-server starting..." RETCODE); + + char_config_read((argc < 2) ? CHAR_CONF_NAME : argv[1]); + lan_config_read((argc > 1) ? argv[1] : LOGIN_LAN_CONF_NAME); + + login_ip = inet_addr(login_ip_str); + char_ip = inet_addr(char_ip_str); + + for(i = 0; i < MAX_MAP_SERVERS; i++) { + memset(&server[i], 0, sizeof(struct mmo_map_server)); + server_fd[i] = -1; + } + + mmo_char_init(); + + update_online = time(NULL); + create_online_files(); // update online players files at start of the server + + inter_init((argc > 2) ? argv[2] : inter_cfgName); // inter server 初期化 + + set_termfunc(do_final); + set_defaultparse(parse_char); + + char_fd = make_listen_port(char_port); + + add_timer_func_list(check_connect_login_server, "check_connect_login_server"); + add_timer_func_list(send_users_tologin, "send_users_tologin"); + add_timer_func_list(mmo_char_sync_timer, "mmo_char_sync_timer"); + + i = add_timer_interval(gettick() + 1000, check_connect_login_server, 0, 0, 10 * 1000); + i = add_timer_interval(gettick() + 1000, send_users_tologin, 0, 0, 5 * 1000); + i = add_timer_interval(gettick() + autosave_interval, mmo_char_sync_timer, 0, 0, autosave_interval); + + if(anti_freeze_enable > 0) { + add_timer_func_list(map_anti_freeze_system, "map_anti_freeze_system"); + i = add_timer_interval(gettick() + 1000, map_anti_freeze_system, 0, 0, ANTI_FREEZE_INTERVAL * 1000); // checks every X seconds user specifies + } + + char_log("The char-server is ready (Server is listening on the port %d)." RETCODE, char_port); + + printf("The char-server is \033[1;32mready\033[0m (Server is listening on the port %d).\n\n", char_port); + + return 0; +} diff --git a/misc/src/char/char.h b/misc/src/char/char.h new file mode 100644 index 0000000..989ca2f --- /dev/null +++ b/misc/src/char/char.h @@ -0,0 +1,31 @@ +// $Id: char.h,v 1.1.1.1 2004/09/10 17:26:50 MagicalTux Exp $ +#ifndef _CHAR_H_ +#define _CHAR_H_ + +#define MAX_MAP_SERVERS 30 + +#define CHAR_CONF_NAME "conf/char_athena.conf" + +#define LOGIN_LAN_CONF_NAME "conf/lan_support.conf" + +#define DEFAULT_AUTOSAVE_INTERVAL 300*1000 + +struct mmo_map_server{ + long ip; + short port; + int users; + char map[MAX_MAP_PER_SERVER][16]; +}; + +int search_character_index(char* character_name); +char * search_character_name(int index); + +int mapif_sendall(unsigned char *buf, unsigned int len); +int mapif_sendallwos(int fd,unsigned char *buf, unsigned int len); +int mapif_send(int fd,unsigned char *buf, unsigned int len); + +int char_log(char *fmt, ...); + +extern int autosave_interval; + +#endif diff --git a/misc/src/char/diff.diff b/misc/src/char/diff.diff new file mode 100644 index 0000000..61e91c7 --- /dev/null +++ b/misc/src/char/diff.diff @@ -0,0 +1,4242 @@ +--- char.c 2006-02-17 04:15:48.000000000 +0100 ++++ newchar/char.c 2006-03-15 19:55:35.000000000 +0100 +@@ -1,73 +1,100 @@ +-// $Id: char.c,v 1.3 2004/09/13 16:52:16 Yor Exp $ +-// original : char2.c 2003/03/14 11:58:35 Rev.1.5 ++// Copyright (c) Athena Dev Teams - Licensed under GNU GPL
++// For more information, see LICENCE in the main folder
+ + #include <sys/types.h> +-#include <sys/socket.h> + #include <stdio.h> + #include <stdlib.h> ++
++#ifdef _WIN32
++#include <winsock.h>
++typedef long in_addr_t;
++#else
++#include <sys/socket.h>
+ #include <netinet/in.h> +-#include <sys/time.h> +-#include <time.h> ++#include <arpa/inet.h>
++#include <netdb.h>
+ #include <sys/ioctl.h> ++#include <sys/time.h>
+ #include <unistd.h> ++#endif
++
++#include <time.h>
+ #include <signal.h> + #include <fcntl.h> + #include <string.h> +-#include <arpa/inet.h> +-#include <netdb.h> + #include <stdarg.h> ++#include <limits.h>
+ +-#include "core.h" +-#include "socket.h" +-#include "timer.h" +-#include "mmo.h" +-#include "version.h" +-#include "lock.h" +-#include "char.h" ++#include "../common/strlib.h"
++#include "../common/core.h"
++#include "../common/socket.h"
++#include "../common/timer.h"
++#include "../common/mmo.h"
++#include "../common/db.h"
++#include "../common/version.h"
++#include "../common/lock.h"
++#include "../common/showmsg.h"
++#include "../common/malloc.h"
+ ++#include "char.h"
+ #include "inter.h" + #include "int_pet.h" + #include "int_guild.h" + #include "int_party.h" + #include "int_storage.h" +- +-#ifdef MEMWATCH +-#include "memwatch.h" ++#ifdef ENABLE_SC_SAVING
++#include "int_status.h"
+ #endif + + struct mmo_map_server server[MAX_MAP_SERVERS]; + int server_fd[MAX_MAP_SERVERS]; +-int server_freezeflag[MAX_MAP_SERVERS]; // Map-server anti-freeze system. Counter. 5 ok, 4...0 freezed +-int anti_freeze_enable = 0; +-int ANTI_FREEZE_INTERVAL = 6; + + int login_fd, char_fd; + char userid[24]; + char passwd[24]; + char server_name[20]; +-char wisp_server_name[24] = "Server"; ++char wisp_server_name[NAME_LENGTH] = "Server";
++int login_ip_set_ = 0;
+ char login_ip_str[16]; +-int login_ip; ++in_addr_t login_ip;
+ int login_port = 6900; ++int char_ip_set_ = 0;
+ char char_ip_str[16]; +-int char_ip; ++int bind_ip_set_ = 0;
++char bind_ip_str[16];
++in_addr_t char_ip;
+ int char_port = 6121; + int char_maintenance; + int char_new; ++int char_new_display;
+ int email_creation = 0; // disabled by default +-char char_txt[1024]; +-char backup_txt[1024]; //By zanetheinsane ++char char_txt[1024]="save/athena.txt";
++char backup_txt[1024]="save/backup.txt"; //By zanetheinsane
++char friends_txt[1024]="save/friends.txt"; // davidsiaw
+ char backup_txt_flag = 0; // The backup_txt file was created because char deletion bug existed. Now it's finish and that take a lot of time to create a second file when there are a lot of characters. => option By [Yor] + char unknown_char_name[1024] = "Unknown"; + char char_log_filename[1024] = "log/char.log"; +-//Added for lan support +-char lan_map_ip[128]; +-int subneti[4]; +-int subnetmaski[4]; ++char db_path[1024]="db";
++
++// Advanced subnet check [LuzZza]
++struct _subnet {
++ long subnet;
++ long mask;
++ long char_ip;
++ long map_ip;
++} subnet[16];
++
++int subnet_count = 0;
++
+ int name_ignoring_case = 0; // Allow or not identical name for characters but with a different case by [Yor] + int char_name_option = 0; // Option to know which letters/symbols are authorised in the name of a character (0: all, 1: only those in char_name_letters, 2: all EXCEPT those in char_name_letters) by [Yor] ++//The following are characters that are trimmed regardless because they cause confusion and problems on the servers. [Skotlex]
++#define TRIM_CHARS "\032\t\n "
+ char char_name_letters[1024] = ""; // list of letters/symbols authorised (or not) in a character name. by [Yor] + ++int log_char = 1; // loggin char or not [devil]
++int log_inter = 1; // loggin inter or not [devil]
++
+ struct char_session_data{ + int account_id, login_id1, login_id2, sex; + int found_char[9]; +@@ -83,18 +110,31 @@ + int auth_fifo_pos = 0; + + int check_ip_flag = 1; // It's to check IP of a player between char-server and other servers (part of anti-hacking system) ++static int online_check = 1; //If one, it won't let players connect when their account is already registered online and will send the relevant map server a kick user request. [Skotlex]
++
++int char_id_count = START_CHAR_NUM;
++struct character_data {
++ struct mmo_charstatus status;
++ int global_num;
++ struct global_reg global[GLOBAL_REG_NUM];
++} *char_dat;
+ +-int char_id_count = 150000; +-struct mmo_charstatus *char_dat; + int char_num, char_max; + int max_connect_user = 0; ++int gm_allow_level = 99;
+ int autosave_interval = DEFAULT_AUTOSAVE_INTERVAL; ++int save_log = 1;
+ int start_zeny = 500; + int start_weapon = 1201; +-int start_armor = 1202; ++int start_armor = 2301;
++
++//Custom limits for the fame lists. [Skotlex]
++int fame_list_size_chemist = MAX_FAME_LIST;
++int fame_list_size_smith = MAX_FAME_LIST;
++int fame_list_size_taekwon = MAX_FAME_LIST;
+ + // Initial position (it's possible to set it in conf file) +-struct point start_point = {"new_1-1.gat", 53, 111}; ++struct point start_point = { 0, 53, 111};
+ + struct gm_account *gm_account = NULL; + int GM_num = 0; +@@ -107,16 +147,31 @@ + int online_refresh_html = 20; // refresh time (in sec) of the html file in the explorer + int online_gm_display_min_level = 20; // minimum GM level to display 'GM' when we want to display it + +-int *online_chars; // same size of char_dat, and id value of current server (or -1) ++//These are used to aid the map server in identifying valid clients. [Skotlex]
++static int max_account_id = DEFAULT_MAX_ACCOUNT_ID, max_char_id = DEFAULT_MAX_CHAR_ID;
++
++struct online_char_data {
++ int account_id;
++ int char_id;
++ short server;
++ unsigned waiting_disconnect :1;
++};
++
++struct dbt *online_char_db; //Holds all online characters.
++
+ time_t update_online; // to update online files when we receiving information from a server (not less than 8 seconds) + ++int console = 0;
++
+ //------------------------------ + // Writing function of logs file + //------------------------------ + int char_log(char *fmt, ...) { ++ if(log_char)
++ {
+ FILE *logfp; + va_list ap; +- struct timeval tv; ++ time_t raw_time;
+ char tmpstr[2048]; + + va_start(ap, fmt); +@@ -126,33 +181,16 @@ + if (fmt[0] == '\0') // jump a line if no message + fprintf(logfp, RETCODE); + else { +- gettimeofday(&tv, NULL); +- strftime(tmpstr, 24, "%d-%m-%Y %H:%M:%S", localtime(&(tv.tv_sec))); +- sprintf(tmpstr + 19, ".%03d: %s", (int)tv.tv_usec / 1000, fmt); ++ time(&raw_time);
++ strftime(tmpstr, 24, "%d-%m-%Y %H:%M:%S", localtime(&raw_time));
++ sprintf(tmpstr + 19, ": %s", fmt);
+ vfprintf(logfp, tmpstr, ap); + } + fclose(logfp); + } +- + va_end(ap); +- return 0; +-} +- +-//----------------------------------------------------- +-// Function to suppress control characters in a string. +-//----------------------------------------------------- +-int remove_control_chars(unsigned char *str) { +- int i; +- int change = 0; +- +- for(i = 0; str[i]; i++) { +- if (str[i] < 32) { +- str[i] = '_'; +- change = 1; +- } + } +- +- return change; ++ return 0;
+ } + + //---------------------------------------------------------------------- +@@ -183,9 +221,9 @@ + index = -1; + for(i = 0; i < char_num; i++) { + // Without case sensitive check (increase the number of similar character names found) +- if (stricmp(char_dat[i].name, character_name) == 0) { ++ if (stricmp(char_dat[i].status.name, character_name) == 0) {
+ // Strict comparison (if found, we finish the function immediatly with correct value) +- if (strcmp(char_dat[i].name, character_name) == 0) ++ if (strcmp(char_dat[i].status.name, character_name) == 0)
+ return i; + quantity++; + index = i; +@@ -206,30 +244,140 @@ + char * search_character_name(int index) { + + if (index >= 0 && index < char_num) +- return char_dat[index].name; ++ return char_dat[index].status.name;
+ + return unknown_char_name; + } + ++static void * create_online_char_data(DBKey key, va_list args) {
++ struct online_char_data* character;
++ character = aCalloc(1, sizeof(struct online_char_data));
++ character->account_id = key.i;
++ character->char_id = -1;
++ character->server = -1;
++ return character;
++}
++
++static int chardb_waiting_disconnect(int tid, unsigned int tick, int id, int data);
++
+ //------------------------------------------------- +-// Function to create the character line (for save) ++// Set Character online/offline [Wizputer]
+ //------------------------------------------------- +-int mmo_char_tostr(char *str, struct mmo_charstatus *p) { ++
++void set_char_online(int map_id, int char_id, int account_id) {
++ struct online_char_data* character;
++
++ if ( char_id != 99 && (max_account_id < account_id || max_char_id < char_id))
++ { //Notify map-server of the new max IDs [Skotlex]
++ if (account_id > max_account_id)
++ max_account_id = account_id;
++ if (char_id > max_char_id)
++ max_char_id = char_id;
++ mapif_send_maxid(max_account_id, max_char_id);
++ }
++ character = idb_ensure(online_char_db, account_id, create_online_char_data);
++ if (online_check && character->char_id != -1 && character->server > -1 && character->server != map_id)
++ {
++ ShowNotice("set_char_online: Character %d:%d marked in map server %d, but map server %d claims to have (%d:%d) online!\n",
++ character->account_id, character->char_id, character->server, map_id, account_id, char_id);
++ mapif_disconnectplayer(server_fd[character->server], character->account_id, character->char_id, 2);
++ }
++ character->waiting_disconnect = 0;
++ character->char_id = (char_id==99)?-1:char_id;
++ character->server = (char_id==99)?-1:map_id;
++
++ if (login_fd <= 0 || session[login_fd]->eof)
++ return;
++ WFIFOHEAD(login_fd, 6);
++ WFIFOW(login_fd,0) = 0x272b;
++ WFIFOL(login_fd,2) = account_id;
++ WFIFOSET(login_fd,6);
++
++ //printf ("set online\n");
++}
++void set_char_offline(int char_id, int account_id) {
++ struct online_char_data* character;
++
++ if ((character = idb_get(online_char_db, account_id)) != NULL)
++ { //We don't free yet to avoid aCalloc/aFree spamming during char change. [Skotlex]
++ character->char_id = -1;
++ character->server = -1;
++ character->waiting_disconnect = 0;
++ }
++ if (login_fd <= 0 || session[login_fd]->eof)
++ return;
++ WFIFOHEAD(login_fd, 6);
++ WFIFOW(login_fd,0) = 0x272c;
++ WFIFOL(login_fd,2) = account_id;
++ WFIFOSET(login_fd,6);
++
++}
++
++static int char_db_setoffline(DBKey key, void* data, va_list ap) {
++ struct online_char_data* character = (struct online_char_data*)data;
++ int server = va_arg(ap, int);
++ if (server == -1) {
++ character->char_id = -1;
++ character->server = -1;
++ character->waiting_disconnect = 0;
++ } else if (character->server == server)
++ character->server = -2; //In some map server that we aren't connected to.
++ return 0;
++}
++
++void set_all_offline(void) {
++ online_char_db->foreach(online_char_db,char_db_setoffline,-1);
++ if (login_fd <= 0 || session[login_fd]->eof)
++ return;
++ WFIFOHEAD(login_fd, 6);
++ WFIFOW(login_fd,0) = 0x272c;
++ WFIFOL(login_fd,2) = 99;
++ WFIFOSET(login_fd,6);
++
++ //printf ("set all offline\n");
++}
++
++/*---------------------------------------------------
++ Make a data line for friends list
++ --------------------------------------------------*/
++
++int mmo_friends_list_data_str(char *str, struct mmo_charstatus *p) {
+ int i; + char *str_p = str; ++ str_p += sprintf(str_p, "%d", p->char_id);
++
++ for (i=0;i<MAX_FRIENDS;i++){
++ if (p->friends[i].account_id > 0 && p->friends[i].char_id > 0 && p->friends[i].name[0])
++ str_p += sprintf(str_p, ",%d,%d,%s", p->friends[i].account_id, p->friends[i].char_id, p->friends[i].name);
++ else
++ str_p += sprintf(str_p,",,,");
++ }
++
++ str_p += '\0';
++
++ return 0;
++}
++
++//-------------------------------------------------
++// Function to create the character line (for save)
++//-------------------------------------------------
++int mmo_char_tostr(char *str, struct mmo_charstatus *p, struct global_reg *reg, int reg_num) {
++ int i,j;
++ char *str_p = str;
+ ++ /* We shouldn't need this anymore... [Skotlex]
+ // on multi-map server, sometimes it's posssible that last_point become void. (reason???) We check that to not lost character at restart. +- if (p->last_point.map[0] == '\0') { +- memcpy(p->last_point.map, "prontera.gat", 16); ++ if (!p->last_point.map) {
++ p->last_point.map = mapindex_name2id(MAP_PRONTERA);
+ p->last_point.x = 273; + p->last_point.y = 354; + } +- +- str_p += sprintf(str_p, "%d\t%d,%d\t%s\t%d,%d,%d\t%d,%d,%d\t%d,%d,%d,%d\t%d,%d,%d,%d,%d,%d\t%d,%d" ++ */
++ str_p += sprintf(str_p, "%d\t%d,%d\t%s\t%d,%d,%d\t%u,%u,%d\t%d,%d,%d,%d\t%d,%d,%d,%d,%d,%d\t%d,%d"
+ "\t%d,%d,%d\t%d,%d,%d\t%d,%d,%d\t%d,%d,%d,%d,%d" +- "\t%s,%d,%d\t%s,%d,%d,%d\t", ++ "\t%s,%d,%d\t%s,%d,%d,%d,%d,%d,%d,%d\t",
+ p->char_id, p->account_id, p->char_num, p->name, // +- p->class, p->base_level, p->job_level, ++ p->class_, p->base_level, p->job_level,
+ p->base_exp, p->job_exp, p->zeny, + p->hp, p->max_hp, p->sp, p->max_sp, + p->str, p->agi, p->vit, p->int_, p->dex, p->luk, +@@ -238,32 +386,34 @@ + p->party_id, p->guild_id, p->pet_id, + p->hair, p->hair_color, p->clothes_color, + p->weapon, p->shield, p->head_top, p->head_mid, p->head_bottom, +- p->last_point.map, p->last_point.x, p->last_point.y, // +- p->save_point.map, p->save_point.x, p->save_point.y, +- p->partner_id); ++ mapindex_id2name(p->last_point.map), p->last_point.x, p->last_point.y, //
++ mapindex_id2name(p->save_point.map), p->save_point.x, p->save_point.y,
++ p->partner_id,p->father,p->mother,p->child,p->fame);
+ for(i = 0; i < 10; i++) +- if (p->memo_point[i].map[0]) { +- str_p += sprintf(str_p, "%s,%d,%d", p->memo_point[i].map, p->memo_point[i].x, p->memo_point[i].y); ++ if (p->memo_point[i].map) {
++ str_p += sprintf(str_p, "%s,%d,%d", mapindex_id2name(p->memo_point[i].map), p->memo_point[i].x, p->memo_point[i].y);
+ } + *(str_p++) = '\t'; + + for(i = 0; i < MAX_INVENTORY; i++) + if (p->inventory[i].nameid) { +- str_p += sprintf(str_p, "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d ", ++ str_p += sprintf(str_p,"%d,%d,%d,%d,%d,%d,%d",
+ p->inventory[i].id, p->inventory[i].nameid, p->inventory[i].amount, p->inventory[i].equip, +- p->inventory[i].identify, p->inventory[i].refine, p->inventory[i].attribute, +- p->inventory[i].card[0], p->inventory[i].card[1], p->inventory[i].card[2], p->inventory[i].card[3], +- p->inventory[i].broken); ++ p->inventory[i].identify,p->inventory[i].refine,p->inventory[i].attribute);
++ for(j=0; j<MAX_SLOTS; j++)
++ str_p += sprintf(str_p,",%d",p->inventory[i].card[j]);
++ str_p += sprintf(str_p," ");
+ } + *(str_p++) = '\t'; + + for(i = 0; i < MAX_CART; i++) + if (p->cart[i].nameid) { +- str_p += sprintf(str_p, "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d ", ++ str_p += sprintf(str_p,"%d,%d,%d,%d,%d,%d,%d",
+ p->cart[i].id, p->cart[i].nameid, p->cart[i].amount, p->cart[i].equip, +- p->cart[i].identify, p->cart[i].refine, p->cart[i].attribute, +- p->cart[i].card[0], p->cart[i].card[1], p->cart[i].card[2], p->cart[i].card[3], +- p->cart[i].broken); ++ p->cart[i].identify,p->cart[i].refine,p->cart[i].attribute);
++ for(j=0; j<MAX_SLOTS; j++)
++ str_p += sprintf(str_p,",%d",p->cart[i].card[j]);
++ str_p += sprintf(str_p," ");
+ } + *(str_p++) = '\t'; + +@@ -273,9 +423,9 @@ + } + *(str_p++) = '\t'; + +- for(i = 0; i < p->global_reg_num; i++) +- if (p->global_reg[i].str[0]) +- str_p += sprintf(str_p, "%s,%d ", p->global_reg[i].str, p->global_reg[i].value); ++ for(i = 0; i < reg_num; i++)
++ if (reg[i].str[0])
++ str_p += sprintf(str_p, "%s,%s ", reg[i].str, reg[i].value);
+ *(str_p++) = '\t'; + + *str_p = '\0'; +@@ -285,20 +435,62 @@ + //------------------------------------------------------------------------- + // Function to set the character from the line (at read of characters file) + //------------------------------------------------------------------------- +-int mmo_char_fromstr(char *str, struct mmo_charstatus *p) { ++int mmo_char_fromstr(char *str, struct mmo_charstatus *p, struct global_reg *reg, int *reg_num) {
++ char tmp_str[3][128]; //To avoid deleting chars with too long names.
+ int tmp_int[256]; +- int set, next, len, i; ++ unsigned int tmp_uint[2]; //To read exp....
++ int set, next, len, i, j;
+ + // initilialise character + memset(p, '\0', sizeof(struct mmo_charstatus)); + +- // If it's not char structure of version 1008 and after +- if ((set = sscanf(str, "%d\t%d,%d\t%[^\t]\t%d,%d,%d\t%d,%d,%d\t%d,%d,%d,%d\t%d,%d,%d,%d,%d,%d\t%d,%d" ++ // If it's not char structure of version 1488 and after
++ if ((set = sscanf(str, "%d\t%d,%d\t%127[^\t]\t%d,%d,%d\t%u,%u,%d\t%d,%d,%d,%d\t%d,%d,%d,%d,%d,%d\t%d,%d"
++ "\t%d,%d,%d\t%d,%d,%d\t%d,%d,%d\t%d,%d,%d,%d,%d"
++ "\t%127[^,],%d,%d\t%127[^,],%d,%d,%d,%d,%d,%d,%d%n",
++ &tmp_int[0], &tmp_int[1], &tmp_int[2], tmp_str[0],
++ &tmp_int[3], &tmp_int[4], &tmp_int[5],
++ &tmp_uint[0], &tmp_uint[1], &tmp_int[8],
++ &tmp_int[9], &tmp_int[10], &tmp_int[11], &tmp_int[12],
++ &tmp_int[13], &tmp_int[14], &tmp_int[15], &tmp_int[16], &tmp_int[17], &tmp_int[18],
++ &tmp_int[19], &tmp_int[20],
++ &tmp_int[21], &tmp_int[22], &tmp_int[23], //
++ &tmp_int[24], &tmp_int[25], &tmp_int[26],
++ &tmp_int[27], &tmp_int[28], &tmp_int[29],
++ &tmp_int[30], &tmp_int[31], &tmp_int[32], &tmp_int[33], &tmp_int[34],
++ tmp_str[1], &tmp_int[35], &tmp_int[36],
++ tmp_str[2], &tmp_int[37], &tmp_int[38], &tmp_int[39],
++ &tmp_int[40], &tmp_int[41], &tmp_int[42], &tmp_int[43], &next)) != 47)
++ {
++ tmp_int[43] = 0;
++ // If it's not char structure of version 1363 and after
++ if ((set = sscanf(str, "%d\t%d,%d\t%127[^\t]\t%d,%d,%d\t%u,%u,%d\t%d,%d,%d,%d\t%d,%d,%d,%d,%d,%d\t%d,%d"
++ "\t%d,%d,%d\t%d,%d,%d\t%d,%d,%d\t%d,%d,%d,%d,%d"
++ "\t%127[^,],%d,%d\t%127[^,],%d,%d,%d,%d,%d,%d%n",
++ &tmp_int[0], &tmp_int[1], &tmp_int[2], tmp_str[0], //
++ &tmp_int[3], &tmp_int[4], &tmp_int[5],
++ &tmp_uint[0], &tmp_uint[1], &tmp_int[8],
++ &tmp_int[9], &tmp_int[10], &tmp_int[11], &tmp_int[12],
++ &tmp_int[13], &tmp_int[14], &tmp_int[15], &tmp_int[16], &tmp_int[17], &tmp_int[18],
++ &tmp_int[19], &tmp_int[20],
++ &tmp_int[21], &tmp_int[22], &tmp_int[23], //
++ &tmp_int[24], &tmp_int[25], &tmp_int[26],
++ &tmp_int[27], &tmp_int[28], &tmp_int[29],
++ &tmp_int[30], &tmp_int[31], &tmp_int[32], &tmp_int[33], &tmp_int[34],
++ tmp_str[1], &tmp_int[35], &tmp_int[36], //
++ tmp_str[2], &tmp_int[37], &tmp_int[38], &tmp_int[39],
++ &tmp_int[40], &tmp_int[41], &tmp_int[42], &next)) != 46)
++ {
++ tmp_int[40] = 0; // father
++ tmp_int[41] = 0; // mother
++ tmp_int[42] = 0; // child
++ // If it's not char structure of version 1008 and before 1363
++ if ((set = sscanf(str, "%d\t%d,%d\t%127[^\t]\t%d,%d,%d\t%u,%u,%d\t%d,%d,%d,%d\t%d,%d,%d,%d,%d,%d\t%d,%d"
+ "\t%d,%d,%d\t%d,%d,%d\t%d,%d,%d\t%d,%d,%d,%d,%d" +- "\t%[^,],%d,%d\t%[^,],%d,%d,%d%n", +- &tmp_int[0], &tmp_int[1], &tmp_int[2], p->name, // ++ "\t%127[^,],%d,%d\t%127[^,],%d,%d,%d%n",
++ &tmp_int[0], &tmp_int[1], &tmp_int[2], tmp_str[0], //
+ &tmp_int[3], &tmp_int[4], &tmp_int[5], +- &tmp_int[6], &tmp_int[7], &tmp_int[8], ++ &tmp_uint[0], &tmp_uint[1], &tmp_int[8],
+ &tmp_int[9], &tmp_int[10], &tmp_int[11], &tmp_int[12], + &tmp_int[13], &tmp_int[14], &tmp_int[15], &tmp_int[16], &tmp_int[17], &tmp_int[18], + &tmp_int[19], &tmp_int[20], +@@ -306,16 +498,17 @@ + &tmp_int[24], &tmp_int[25], &tmp_int[26], + &tmp_int[27], &tmp_int[28], &tmp_int[29], + &tmp_int[30], &tmp_int[31], &tmp_int[32], &tmp_int[33], &tmp_int[34], +- p->last_point.map, &tmp_int[35], &tmp_int[36], // +- p->save_point.map, &tmp_int[37], &tmp_int[38], &tmp_int[39], &next)) != 43) { ++ tmp_str[1], &tmp_int[35], &tmp_int[36], //
++ tmp_str[2], &tmp_int[37], &tmp_int[38], &tmp_int[39], &next)) != 43)
++ {
+ tmp_int[39] = 0; // partner id + // If not char structure from version 384 to 1007 +- if ((set = sscanf(str, "%d\t%d,%d\t%[^\t]\t%d,%d,%d\t%d,%d,%d\t%d,%d,%d,%d\t%d,%d,%d,%d,%d,%d\t%d,%d" ++ if ((set = sscanf(str, "%d\t%d,%d\t%127[^\t]\t%d,%d,%d\t%u,%u,%d\t%d,%d,%d,%d\t%d,%d,%d,%d,%d,%d\t%d,%d"
+ "\t%d,%d,%d\t%d,%d,%d\t%d,%d,%d\t%d,%d,%d,%d,%d" +- "\t%[^,],%d,%d\t%[^,],%d,%d%n", +- &tmp_int[0], &tmp_int[1], &tmp_int[2], p->name, // ++ "\t%127[^,],%d,%d\t%127[^,],%d,%d%n",
++ &tmp_int[0], &tmp_int[1], &tmp_int[2], tmp_str[0], //
+ &tmp_int[3], &tmp_int[4], &tmp_int[5], +- &tmp_int[6], &tmp_int[7], &tmp_int[8], ++ &tmp_uint[0], &tmp_uint[1], &tmp_int[8],
+ &tmp_int[9], &tmp_int[10], &tmp_int[11], &tmp_int[12], + &tmp_int[13], &tmp_int[14], &tmp_int[15], &tmp_int[16], &tmp_int[17], &tmp_int[18], + &tmp_int[19], &tmp_int[20], +@@ -323,16 +516,17 @@ + &tmp_int[24], &tmp_int[25], &tmp_int[26], + &tmp_int[27], &tmp_int[28], &tmp_int[29], + &tmp_int[30], &tmp_int[31], &tmp_int[32], &tmp_int[33], &tmp_int[34], +- p->last_point.map, &tmp_int[35], &tmp_int[36], // +- p->save_point.map, &tmp_int[37], &tmp_int[38], &next)) != 42) { ++ tmp_str[1], &tmp_int[35], &tmp_int[36], //
++ tmp_str[2], &tmp_int[37], &tmp_int[38], &next)) != 42)
++ {
+ // It's char structure of a version before 384 + tmp_int[26] = 0; // pet id +- set = sscanf(str, "%d\t%d,%d\t%[^\t]\t%d,%d,%d\t%d,%d,%d\t%d,%d,%d,%d\t%d,%d,%d,%d,%d,%d\t%d,%d" ++ set = sscanf(str, "%d\t%d,%d\t%127[^\t]\t%d,%d,%d\t%u,%u,%d\t%d,%d,%d,%d\t%d,%d,%d,%d,%d,%d\t%d,%d"
+ "\t%d,%d,%d\t%d,%d\t%d,%d,%d\t%d,%d,%d,%d,%d" +- "\t%[^,],%d,%d\t%[^,],%d,%d%n", +- &tmp_int[0], &tmp_int[1], &tmp_int[2], p->name, // ++ "\t%127[^,],%d,%d\t%127[^,],%d,%d%n",
++ &tmp_int[0], &tmp_int[1], &tmp_int[2], tmp_str[0], //
+ &tmp_int[3], &tmp_int[4], &tmp_int[5], +- &tmp_int[6], &tmp_int[7], &tmp_int[8], ++ &tmp_uint[0], &tmp_uint[1], &tmp_int[8],
+ &tmp_int[9], &tmp_int[10], &tmp_int[11], &tmp_int[12], + &tmp_int[13], &tmp_int[14], &tmp_int[15], &tmp_int[16], &tmp_int[17], &tmp_int[18], + &tmp_int[19], &tmp_int[20], +@@ -340,8 +534,8 @@ + &tmp_int[24], &tmp_int[25], // + &tmp_int[27], &tmp_int[28], &tmp_int[29], + &tmp_int[30], &tmp_int[31], &tmp_int[32], &tmp_int[33], &tmp_int[34], +- p->last_point.map, &tmp_int[35], &tmp_int[36], // +- p->save_point.map, &tmp_int[37], &tmp_int[38], &next); ++ tmp_str[1], &tmp_int[35], &tmp_int[36], //
++ tmp_str[2], &tmp_int[37], &tmp_int[38], &next);
+ set += 2; + //printf("char: old char data ver.1\n"); + // Char structure of version 1007 or older +@@ -351,19 +545,30 @@ + } + // Char structure of version 1008+ + } else { ++ set += 3;
+ //printf("char: new char data ver.3\n"); + } +- if (set != 43) ++ // Char structture of version 1363+
++ } else {
++ set++;
++ //printf("char: new char data ver.4\n");
++ }
++ // Char structure of version 1488+
++ } else {
++ //printf("char: new char data ver.5\n");
++ }
++ if (set != 47)
+ return 0; + ++ memcpy(p->name, tmp_str[0], NAME_LENGTH-1); //Overflow protection [Skotlex]
+ p->char_id = tmp_int[0]; + p->account_id = tmp_int[1]; + p->char_num = tmp_int[2]; +- p->class = tmp_int[3]; ++ p->class_ = tmp_int[3];
+ p->base_level = tmp_int[4]; + p->job_level = tmp_int[5]; +- p->base_exp = tmp_int[6]; +- p->job_exp = tmp_int[7]; ++ p->base_exp = tmp_uint[0];
++ p->job_exp = tmp_uint[1];
+ p->zeny = tmp_int[8]; + p->hp = tmp_int[9]; + p->max_hp = tmp_int[10]; +@@ -391,31 +596,37 @@ + p->head_top = tmp_int[32]; + p->head_mid = tmp_int[33]; + p->head_bottom = tmp_int[34]; ++ p->last_point.map = mapindex_name2id(tmp_str[1]);
+ p->last_point.x = tmp_int[35]; + p->last_point.y = tmp_int[36]; ++ p->save_point.map = mapindex_name2id(tmp_str[2]);
+ p->save_point.x = tmp_int[37]; + p->save_point.y = tmp_int[38]; + p->partner_id = tmp_int[39]; ++ p->father = tmp_int[40];
++ p->mother = tmp_int[41];
++ p->child = tmp_int[42];
++ p->fame = tmp_int[43];
+ + // Some checks + for(i = 0; i < char_num; i++) { +- if (char_dat[i].char_id == p->char_id) { +- printf("\033[1;31mmmo_auth_init: ******Error: a character has an identical id to another.\n"); +- printf(" character id #%d -> new character not readed.\n", p->char_id); +- printf(" Character saved in log file.\033[0m\n"); ++ if (char_dat[i].status.char_id == p->char_id) {
++ ShowError(CL_RED"mmmo_auth_init: a character has an identical id to another.\n");
++ ShowError(" character id #%d -> new character not readed.\n", p->char_id);
++ ShowError(" Character saved in log file."CL_RESET"\n");
+ return -1; +- } else if (strcmp(char_dat[i].name, p->name) == 0) { +- printf("\033[1;31mmmo_auth_init: ******Error: character name already exists.\n"); +- printf(" character name '%s' -> new character not readed.\n", p->name); +- printf(" Character saved in log file.\033[0m\n"); ++ } else if (strcmp(char_dat[i].status.name, p->name) == 0) {
++ ShowError(CL_RED"mmmo_auth_init: a character name already exists.\n");
++ ShowError(" character name '%s' -> new character not read.\n", p->name);
++ ShowError(" Character saved in log file."CL_RESET"\n");
+ return -2; + } + } + + if (strcmpi(wisp_server_name, p->name) == 0) { +- printf("mmo_auth_init: ******WARNING: character name has wisp server name.\n"); +- printf(" Character name '%s' = wisp server name '%s'.\n", p->name, wisp_server_name); +- printf(" Character readed. Suggestion: change the wisp server name.\n"); ++ ShowWarning("mmo_auth_init: ******WARNING: character name has wisp server name.\n");
++ ShowWarning(" Character name '%s' = wisp server name '%s'.\n", p->name, wisp_server_name);
++ ShowWarning(" Character readed. Suggestion: change the wisp server name.\n");
+ char_log("mmo_auth_init: ******WARNING: character name has wisp server name: Character name '%s' = wisp server name '%s'." RETCODE, + p->name, wisp_server_name); + } +@@ -426,8 +637,9 @@ + next++; + + for(i = 0; str[next] && str[next] != '\t'; i++) { +- if (sscanf(str+next, "%[^,],%d,%d%n", p->memo_point[i].map, &tmp_int[0], &tmp_int[1], &len) != 3) ++ if (sscanf(str+next, "%[^,],%d,%d%n", tmp_str[0], &tmp_int[0], &tmp_int[1], &len) != 3)
+ return -3; ++ p->memo_point[i].map = mapindex_name2id(tmp_str[0]);
+ p->memo_point[i].x = tmp_int[0]; + p->memo_point[i].y = tmp_int[1]; + next += len; +@@ -438,18 +650,10 @@ + next++; + + for(i = 0; str[next] && str[next] != '\t'; i++) { +- if (sscanf(str + next, "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d%n", ++ if(sscanf(str + next, "%d,%d,%d,%d,%d,%d,%d%[0-9,-]%n",
+ &tmp_int[0], &tmp_int[1], &tmp_int[2], &tmp_int[3], +- &tmp_int[4], &tmp_int[5], &tmp_int[6], +- &tmp_int[7], &tmp_int[8], &tmp_int[9], &tmp_int[10], &tmp_int[11], &len) == 12) { +- // do nothing, it's ok +- } else if (sscanf(str + next, "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d%n", +- &tmp_int[0], &tmp_int[1], &tmp_int[2], &tmp_int[3], +- &tmp_int[4], &tmp_int[5], &tmp_int[6], +- &tmp_int[7], &tmp_int[8], &tmp_int[9], &tmp_int[10], &len) == 11) { +- tmp_int[11] = 0; // broken doesn't exist in this version -> 0 +- } else // invalid structure +- return -4; ++ &tmp_int[4], &tmp_int[5], &tmp_int[6], tmp_str[0], &len) == 8)
++ {
+ p->inventory[i].id = tmp_int[0]; + p->inventory[i].nameid = tmp_int[1]; + p->inventory[i].amount = tmp_int[2]; +@@ -457,31 +661,23 @@ + p->inventory[i].identify = tmp_int[4]; + p->inventory[i].refine = tmp_int[5]; + p->inventory[i].attribute = tmp_int[6]; +- p->inventory[i].card[0] = tmp_int[7]; +- p->inventory[i].card[1] = tmp_int[8]; +- p->inventory[i].card[2] = tmp_int[9]; +- p->inventory[i].card[3] = tmp_int[10]; +- p->inventory[i].broken = tmp_int[11]; ++
++ for(j = 0; j < MAX_SLOTS && tmp_str[0] && sscanf(tmp_str[0], ",%d%[0-9,-]",&tmp_int[0], tmp_str[0]) > 0; j++)
++ p->inventory[i].card[j] = tmp_int[0];
++
+ next += len; + if (str[next] == ' ') + next++; ++ } else // invalid structure
++ return -4;
+ } +- + next++; + + for(i = 0; str[next] && str[next] != '\t'; i++) { +- if (sscanf(str + next, "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d%n", +- &tmp_int[0], &tmp_int[1], &tmp_int[2], &tmp_int[3], +- &tmp_int[4], &tmp_int[5], &tmp_int[6], +- &tmp_int[7], &tmp_int[8], &tmp_int[9], &tmp_int[10], &tmp_int[11], &len) == 12) { +- // do nothing, it's ok +- } else if (sscanf(str + next, "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d%n", ++ if(sscanf(str + next, "%d,%d,%d,%d,%d,%d,%d%[0-9,-]%n",
+ &tmp_int[0], &tmp_int[1], &tmp_int[2], &tmp_int[3], +- &tmp_int[4], &tmp_int[5], &tmp_int[6], +- &tmp_int[7], &tmp_int[8], &tmp_int[9], &tmp_int[10], &len) == 11) { +- tmp_int[11] = 0; // broken doesn't exist in this version -> 0 +- } else // invalid structure +- return -5; ++ &tmp_int[4], &tmp_int[5], &tmp_int[6], tmp_str[0], &len) == 8)
++ {
+ p->cart[i].id = tmp_int[0]; + p->cart[i].nameid = tmp_int[1]; + p->cart[i].amount = tmp_int[2]; +@@ -489,14 +685,15 @@ + p->cart[i].identify = tmp_int[4]; + p->cart[i].refine = tmp_int[5]; + p->cart[i].attribute = tmp_int[6]; +- p->cart[i].card[0] = tmp_int[7]; +- p->cart[i].card[1] = tmp_int[8]; +- p->cart[i].card[2] = tmp_int[9]; +- p->cart[i].card[3] = tmp_int[10]; +- p->cart[i].broken = tmp_int[11]; ++
++ for(j = 0; j < MAX_SLOTS && tmp_str && sscanf(tmp_str[0], ",%d%[0-9,-]",&tmp_int[0], tmp_str[0]) > 0; j++)
++ p->cart[i].card[j] = tmp_int[0];
++
+ next += len; + if (str[next] == ' ') + next++; ++ } else // invalid structure
++ return -5;
+ } + + next++; +@@ -514,11 +711,11 @@ + next++; + + for(i = 0; str[next] && str[next] != '\t' && str[next] != '\n' && str[next] != '\r'; i++) { // global_reg実装以前のathena.txt互換のため一応'\n'チェック +- if (sscanf(str + next, "%[^,],%d%n", p->global_reg[i].str, &p->global_reg[i].value, &len) != 2) { ++ if (sscanf(str + next, "%[^,],%[^ ] %n", reg[i].str, reg[i].value, &len) != 2) {
+ // because some scripts are not correct, the str can be "". So, we must check that. + // If it's, we must not refuse the character, but just this REG value. + // Character line will have something like: nov_2nd_cos,9 ,9 nov_1_2_cos_c,1 (here, ,9 is not good) +- if (str[next] == ',' && sscanf(str + next, ",%d%n", &p->global_reg[i].value, &len) == 1) ++ if (str[next] == ',' && sscanf(str + next, ",%[^ ] %n", reg[i].value, &len) == 1)
+ i--; + else + return -7; +@@ -527,39 +724,127 @@ + if (str[next] == ' ') + next++; + } +- p->global_reg_num = i; ++ *reg_num = i;
+ + return 1; + } ++//---------------------------------
++// Function to read friend list
++//---------------------------------
++
++int parse_friend_txt(struct mmo_charstatus *p)
++{
++ char line[1024], temp[1024];
++ int pos = 0, count = 0, next;
++ int i,len;
++ FILE *fp;
++
++ // Open the file and look for the ID
++ fp = fopen(friends_txt, "r");
++
++ if(fp == NULL)
++ return -1;
++
++ while(fgets(line, sizeof(line)-1, fp)) {
++
++ if(line[0] == '/' && line[1] == '/')
++ continue;
++ if (sscanf(line, "%d%n",&i, &pos) < 1 || i != p->char_id)
++ continue; //Not this line...
++ //Read friends
++ len = strlen(line);
++ next = pos;
++ for (count = 0; next < len && count < MAX_FRIENDS; count++)
++ { //Read friends.
++ if (sscanf(line+next, ",%d,%d,%23[^,]%n",&p->friends[count].account_id,&p->friends[count].char_id, p->friends[count].name, &pos) < 3)
++ { //Invalid friend?
++ memset(&p->friends[count], 0, sizeof(p->friends[count]));
++ break;
++ }
++ next+=pos;
++ //What IF the name contains a comma? while the next field is not a
++ //number, we assume it belongs to the current name. [Skotlex]
++ //NOTE: Of course, this will fail if someone sets their name to something like
++ //Bob,2005 but... meh, it's the problem of parsing a text file (encasing it in "
++ //won't do as quotes are also valid name chars!)
++ while(next < len && sscanf(line+next, ",%23[^,]%n", temp, &len) > 0)
++ {
++ if (atoi(temp))
++ { //We read the next friend, just continue.
++ break;
++ } else { //Append the name.
++ next+=len;
++ if (strlen(p->friends[count].name) + strlen(temp) +1 < NAME_LENGTH)
++ {
++ strcat(p->friends[count].name, ",");
++ strcat(p->friends[count].name, temp);
++ }
++ }
++ } //End Guess Block
++ } //Friend's for.
++ break; //Finished reading.
++ }
++ /*
++ //Character names must not exceed the 23+\0 limit. [Skotlex]
++ sscanf(line, "%d,%d,%23[^,],%d,%23[^,],%d,%23[^,],%d,%23[^,],%d,%23[^,],%d,%23[^,],%d,%23[^,],%d,%23[^,],%d,%23[^,],%d,%23[^,],%d,%23[^,],%d,%23[^,],%d,%23[^,],%d,%23[^,],%d,%23[^,],%d,%23[^,],%d,%23[^,],%d,%23[^,],%d,%23[^,],%d,%23s",&cid,
++ &temp[0],p->friend_name[0],
++ &temp[1],p->friend_name[1],
++ &temp[2],p->friend_name[2],
++ &temp[3],p->friend_name[3],
++ &temp[4],p->friend_name[4],
++ &temp[5],p->friend_name[5],
++ &temp[6],p->friend_name[6],
++ &temp[7],p->friend_name[7],
++ &temp[8],p->friend_name[8],
++ &temp[9],p->friend_name[9],
++ &temp[10],p->friend_name[10],
++ &temp[11],p->friend_name[11],
++ &temp[12],p->friend_name[12],
++ &temp[13],p->friend_name[13],
++ &temp[14],p->friend_name[14],
++ &temp[15],p->friend_name[15],
++ &temp[16],p->friend_name[16],
++ &temp[17],p->friend_name[17],
++ &temp[18],p->friend_name[18],
++ &temp[19],p->friend_name[19]);
++ if (cid == p->char_id)
++ break;
++ }
++ // No register of friends list
++ if (cid == 0) {
++ fclose(fp);
++ return 0;
++ }
++
++ // Fill in the list
++
++ for (i=0; i<MAX_FRIENDS; i++)
++ p->friend_id[i] = temp[i];
++*/
++ fclose(fp);
++ return count;
++}
+ + //--------------------------------- + // Function to read characters file + //--------------------------------- + int mmo_char_init(void) { + char line[65536]; +- int i; + int ret, line_count; + FILE *fp; + + char_max = 256; +- char_dat = calloc(sizeof(struct mmo_charstatus) * 256, 1); ++ char_dat = (struct character_data*)aCalloc(sizeof(struct character_data) * 256, 1);
+ if (!char_dat) { +- printf("out of memory: mmo_char_init (calloc of char_dat).\n"); +- exit(1); +- } +- online_chars = calloc(sizeof(int) * 256, 1); +- if (!online_chars) { +- printf("out of memory: mmo_char_init (calloc of online_chars).\n"); ++ ShowFatalError("out of memory: mmo_char_init (calloc of char_dat).\n");
+ exit(1); + } +- for(i = 0; i < char_max; i++) +- online_chars[i] = -1; +- + char_num = 0; + + fp = fopen(char_txt, "r"); ++
+ if (fp == NULL) { +- printf("Characters file not found: %s.\n", char_txt); ++ ShowError("Characters file not found: %s.\n", char_txt);
+ char_log("Characters file not found: %s." RETCODE, char_txt); + char_log("Id for the next created character: %d." RETCODE, char_id_count); + return 0; +@@ -583,30 +868,26 @@ + + if (char_num >= char_max) { + char_max += 256; +- char_dat = realloc(char_dat, sizeof(struct mmo_charstatus) * char_max); ++ char_dat = (struct character_data*)aRealloc(char_dat, sizeof(struct character_data) * char_max);
+ if (!char_dat) { +- printf("Out of memory: mmo_char_init (realloc of char_dat).\n"); ++ ShowFatalError("Out of memory: mmo_char_init (realloc of char_dat).\n");
+ char_log("Out of memory: mmo_char_init (realloc of char_dat)." RETCODE); + exit(1); + } +- online_chars = realloc(online_chars, sizeof(int) * char_max); +- if (!online_chars) { +- printf("Out of memory: mmo_char_init (realloc of online_chars).\n"); +- char_log("Out of memory: mmo_char_init (realloc of online_chars)." RETCODE); +- exit(1); +- } +- for(i = char_max - 256; i < char_max; i++) +- online_chars[i] = -1; + } + +- ret = mmo_char_fromstr(line, &char_dat[char_num]); ++ ret = mmo_char_fromstr(line, &char_dat[char_num].status, char_dat[char_num].global, &char_dat[char_num].global_num);
++
++ // Initialize friends list
++ parse_friend_txt(&char_dat[char_num].status); // Grab friends for the character
++
+ if (ret > 0) { // negative value or zero for errors +- if (char_dat[char_num].char_id >= char_id_count) +- char_id_count = char_dat[char_num].char_id + 1; ++ if (char_dat[char_num].status.char_id >= char_id_count)
++ char_id_count = char_dat[char_num].status.char_id + 1;
+ char_num++; + } else { +- printf("mmo_char_init: in characters file, unable to read the line #%d.\n", line_count); +- printf(" -> Character saved in log file.\n"); ++ ShowError("mmo_char_init: in characters file, unable to read the line #%d.\n", line_count);
++ ShowError(" -> Character saved in log file.\n");
+ switch (ret) { + case -1: + char_log("Duplicate character id in the next character line (character not readed):" RETCODE); +@@ -639,13 +920,13 @@ + fclose(fp); + + if (char_num == 0) { +- printf("mmo_char_init: No character found in %s.\n", char_txt); ++ ShowNotice("mmo_char_init: No character found in %s.\n", char_txt);
+ char_log("mmo_char_init: No character found in %s." RETCODE, char_txt); + } else if (char_num == 1) { +- printf("mmo_char_init: 1 character read in %s.\n", char_txt); ++ ShowStatus("mmo_char_init: 1 character read in %s.\n", char_txt);
+ char_log("mmo_char_init: 1 character read in %s." RETCODE, char_txt); + } else { +- printf("mmo_char_init: %d characters read in %s.\n", char_num, char_txt); ++ ShowStatus("mmo_char_init: %d characters read in %s.\n", char_num, char_txt);
+ char_log("mmo_char_init: %d characters read in %s." RETCODE, char_num, char_txt); + } + +@@ -658,20 +939,21 @@ + // Function to save characters in files (speed up by [Yor]) + //--------------------------------------------------------- + void mmo_char_sync(void) { +- char line[65536]; ++ char line[65536],f_line[1024];
+ int i, j, k; + int lock; +- FILE *fp; +- int id[char_num]; ++ FILE *fp,*f_fp;
++ //int *id = (int *) aMalloc(sizeof(int) * char_num);
++ CREATE_BUFFER(id, int, char_num);
+ + // Sorting before save (by [Yor]) + for(i = 0; i < char_num; i++) { + id[i] = i; + for(j = 0; j < i; j++) { +- if ((char_dat[i].account_id < char_dat[id[j]].account_id) || ++ if ((char_dat[i].status.account_id < char_dat[id[j]].status.account_id) ||
+ // if same account id, we sort by slot. +- (char_dat[i].account_id == char_dat[id[j]].account_id && +- char_dat[i].char_num < char_dat[id[j]].char_num)) { ++ (char_dat[i].status.account_id == char_dat[id[j]].status.account_id &&
++ char_dat[i].status.char_num < char_dat[id[j]].status.char_num)) {
+ for(k = i; k > j; k--) + id[k] = id[k-1]; + id[j] = i; // id[i] +@@ -683,12 +965,12 @@ + // Data save + fp = lock_fopen(char_txt, &lock); + if (fp == NULL) { +- printf("WARNING: Server can't not save characters.\n"); ++ ShowWarning("Server can't not save characters.\n");
+ char_log("WARNING: Server can't not save characters." RETCODE); + } else { + for(i = 0; i < char_num; i++) { + // create only once the line, and save it in the 2 files (it's speeder than repeat twice the loop and create twice the line) +- mmo_char_tostr(line, &char_dat[id[i]]); // use of sorted index ++ mmo_char_tostr(line, &char_dat[id[i]].status, char_dat[id[i]].global, char_dat[id[i]].global_num); // use of sorted index
+ fprintf(fp, "%s" RETCODE, line); + } + fprintf(fp, "%d\t%%newid%%" RETCODE, char_id_count); +@@ -699,19 +981,33 @@ + if (backup_txt_flag) { // The backup_txt file was created because char deletion bug existed. Now it's finish and that take a lot of time to create a second file when there are a lot of characters. => option By [Yor] + fp = lock_fopen(backup_txt, &lock); + if (fp == NULL) { +- printf("WARNING: Server can't not create backup of characters file.\n"); ++ ShowWarning("Server can't not create backup of characters file.\n");
+ char_log("WARNING: Server can't not create backup of characters file." RETCODE); ++ //aFree(id); // free up the memory before leaving -.- [Ajarn]
++ DELETE_BUFFER(id);
+ return; + } + for(i = 0; i < char_num; i++) { + // create only once the line, and save it in the 2 files (it's speeder than repeat twice the loop and create twice the line) +- mmo_char_tostr(line, &char_dat[id[i]]); // use of sorted index ++ mmo_char_tostr(line, &char_dat[id[i]].status,char_dat[id[i]].global, char_dat[id[i]].global_num); // use of sorted index
+ fprintf(fp, "%s" RETCODE, line); + } + fprintf(fp, "%d\t%%newid%%" RETCODE, char_id_count); + lock_fclose(fp, backup_txt, &lock); + } + ++ // Friends List data save (davidsiaw)
++ f_fp = lock_fopen(friends_txt, &lock);
++ for(i = 0; i < char_num; i++) {
++ mmo_friends_list_data_str(f_line, &char_dat[id[i]].status);
++ fprintf(f_fp, "%s" RETCODE, f_line);
++ }
++
++ lock_fclose(f_fp, friends_txt, &lock);
++
++ //aFree(id);
++ DELETE_BUFFER(id);
++
+ return; + } + +@@ -719,6 +1015,8 @@ + // Function to save (in a periodic way) datas in files + //---------------------------------------------------- + int mmo_char_sync_timer(int tid, unsigned int tick, int id, int data) { ++ if (save_log)
++ ShowInfo("Saving all files...\n");
+ mmo_char_sync(); + inter_save(); + return 0; +@@ -728,21 +1026,33 @@ + // Function to create a new character + //----------------------------------- + int make_new_char(int fd, unsigned char *dat) { +- int i, j; ++ int i;
+ struct char_session_data *sd; ++ char name[NAME_LENGTH];
+ +- sd = session[fd]->session_data; ++ sd = (struct char_session_data*)session[fd]->session_data;
+ + // remove control characters from the name +- dat[23] = '\0'; +- if (remove_control_chars(dat)) { ++ strncpy(name, dat, NAME_LENGTH);
++ name[NAME_LENGTH-1] = '\0'; //Trunc name to max possible value (23)
++
++ trim(name,TRIM_CHARS); //Trim character name. [Skotlex]
++
++ //check name != main chat nick [LuzZza]
++ if(strcmpi(name, main_chat_nick) == 0) {
++ char_log("Create char failed (%d): this nick (%s) reserved for mainchat messages." RETCODE,
++ sd->account_id, name);
++ return -1;
++ }
++
++ if (remove_control_chars((unsigned char *)name)) {
+ char_log("Make new char error (control char received in the name): (connection #%d, account: %d)." RETCODE, + fd, sd->account_id); + return -1; + } + + // check lenght of character name +- if (strlen(dat) < 4) { ++ if (strlen(name) < 4) {
+ char_log("Make new char error (character name too small): (connection #%d, account: %d, name: '%s')." RETCODE, + fd, sd->account_id, dat); + return -1; +@@ -750,15 +1060,15 @@ + + // Check Authorised letters/symbols in the name of the character + if (char_name_option == 1) { // only letters/symbols in char_name_letters are authorised +- for (i = 0; dat[i]; i++) +- if (strchr(char_name_letters, dat[i]) == NULL) { ++ for (i = 0; i < NAME_LENGTH && name[i]; i++)
++ if (strchr(char_name_letters, name[i]) == NULL) {
+ char_log("Make new char error (invalid letter in the name): (connection #%d, account: %d), name: %s, invalid letter: %c." RETCODE, +- fd, sd->account_id, dat, dat[i]); ++ fd, sd->account_id, name, name[i]);
+ return -1; + } + } else if (char_name_option == 2) { // letters/symbols in char_name_letters are forbidden +- for (i = 0; dat[i]; i++) +- if (strchr(char_name_letters, dat[i]) != NULL) { ++ for (i = 0; i < NAME_LENGTH && name[i]; i++)
++ if (strchr(char_name_letters, name[i]) != NULL) {
+ char_log("Make new char error (invalid letter in the name): (connection #%d, account: %d), name: %s, invalid letter: %c." RETCODE, + fd, sd->account_id, dat, dat[i]); + return -1; +@@ -767,8 +1077,8 @@ + + if (dat[24] + dat[25] + dat[26] + dat[27] + dat[28] + dat[29] != 5*6 || // stats + dat[30] >= 9 || // slots (dat[30] can not be negativ) +- dat[33] <= 0 || dat[33] >= 20 || // hair style +- dat[31] >= 12) { // hair color (dat[31] can not be negativ) ++ dat[33] <= 0 || dat[33] >= 24 || // hair style
++ dat[31] >= 9) { // hair color (dat[31] can not be negativ)
+ char_log("Make new char error (invalid values): (connection #%d, account: %d) slot %d, name: %s, stats: %d+%d+%d+%d+%d+%d=%d, hair: %d, hair color: %d" RETCODE, + fd, sd->account_id, dat[30], dat, dat[24], dat[25], dat[26], dat[27], dat[28], dat[29], dat[24] + dat[25] + dat[26] + dat[27] + dat[28] + dat[29], dat[33], dat[31]); + return -1; +@@ -781,98 +1091,96 @@ + fd, sd->account_id, dat[30], dat, dat[24], dat[25], dat[26], dat[27], dat[28], dat[29], dat[24] + dat[25] + dat[26] + dat[27] + dat[28] + dat[29], dat[33], dat[31]); + return -1; + } ++ } // now we know that every stat has proper value but we have to check if str/int agi/luk vit/dex pairs are correct
++
++ if( ((dat[24]+dat[27]) > 10) || ((dat[25]+dat[29]) > 10) || ((dat[26]+dat[28]) > 10) ) {
++ if (log_char) {
++ char_log("Make new char error (invalid stat value): (connection #%d, account: %d) slot %d, name: %s, stats: %d+%d+%d+%d+%d+%d=%d, hair: %d, hair color: %d" RETCODE,
++ fd, sd->account_id, dat[30], dat, dat[24], dat[25], dat[26], dat[27], dat[28], dat[29], dat[24] + dat[25] + dat[26] + dat[27] + dat[28] + dat[29], dat[33], dat[31]);
++ return -1;
+ } ++ } // now when we have passed all stat checks
+ + for(i = 0; i < char_num; i++) { +- if ((name_ignoring_case != 0 && strcmp(char_dat[i].name, dat) == 0) || +- (name_ignoring_case == 0 && strcmpi(char_dat[i].name, dat) == 0)) { ++ if ((name_ignoring_case != 0 && strncmp(char_dat[i].status.name, name, NAME_LENGTH) == 0) ||
++ (name_ignoring_case == 0 && strncmpi(char_dat[i].status.name, name, NAME_LENGTH) == 0)) {
+ char_log("Make new char error (name already exists): (connection #%d, account: %d) slot %d, name: %s (actual name of other char: %d), stats: %d+%d+%d+%d+%d+%d=%d, hair: %d, hair color: %d." RETCODE, +- fd, sd->account_id, dat[30], dat, char_dat[i].name, dat[24], dat[25], dat[26], dat[27], dat[28], dat[29], dat[24] + dat[25] + dat[26] + dat[27] + dat[28] + dat[29], dat[33], dat[31]); ++ fd, sd->account_id, dat[30], dat, char_dat[i].status.name, dat[24], dat[25], dat[26], dat[27], dat[28], dat[29], dat[24] + dat[25] + dat[26] + dat[27] + dat[28] + dat[29], dat[33], dat[31]);
+ return -1; + } +- if (char_dat[i].account_id == sd->account_id && char_dat[i].char_num == dat[30]) { ++ if (char_dat[i].status.account_id == sd->account_id && char_dat[i].status.char_num == dat[30]) {
+ char_log("Make new char error (slot already used): (connection #%d, account: %d) slot %d, name: %s (actual name of other char: %d), stats: %d+%d+%d+%d+%d+%d=%d, hair: %d, hair color: %d." RETCODE, +- fd, sd->account_id, dat[30], dat, char_dat[i].name, dat[24], dat[25], dat[26], dat[27], dat[28], dat[29], dat[24] + dat[25] + dat[26] + dat[27] + dat[28] + dat[29], dat[33], dat[31]); ++ fd, sd->account_id, dat[30], dat, char_dat[i].status.name, dat[24], dat[25], dat[26], dat[27], dat[28], dat[29], dat[24] + dat[25] + dat[26] + dat[27] + dat[28] + dat[29], dat[33], dat[31]);
+ return -1; + } + } + +- if (strcmp(wisp_server_name, dat) == 0) { ++ if (strcmp(wisp_server_name, name) == 0) {
+ char_log("Make new char error (name used is wisp name for server): (connection #%d, account: %d) slot %d, name: %s (actual name of other char: %d), stats: %d+%d+%d+%d+%d+%d=%d, hair: %d, hair color: %d." RETCODE, +- fd, sd->account_id, dat[30], dat, char_dat[i].name, dat[24], dat[25], dat[26], dat[27], dat[28], dat[29], dat[24] + dat[25] + dat[26] + dat[27] + dat[28] + dat[29], dat[33], dat[31]); ++ fd, sd->account_id, dat[30], name, char_dat[i].status.name, dat[24], dat[25], dat[26], dat[27], dat[28], dat[29], dat[24] + dat[25] + dat[26] + dat[27] + dat[28] + dat[29], dat[33], dat[31]);
+ return -1; + } + + if (char_num >= char_max) { + char_max += 256; +- char_dat = realloc(char_dat, sizeof(struct mmo_charstatus) * char_max); ++ char_dat = (struct character_data*)aRealloc(char_dat, sizeof(struct character_data) * char_max);
+ if (!char_dat) { +- printf("Out of memory: make_new_char (realloc of char_dat).\n"); ++ ShowFatalError("Out of memory: make_new_char (realloc of char_dat).\n");
+ char_log("Out of memory: make_new_char (realloc of char_dat)." RETCODE); + exit(1); + } +- online_chars = realloc(online_chars, sizeof(int) * char_max); +- if (!online_chars) { +- printf("Out of memory: make_new_char (realloc of online_chars).\n"); +- char_log("Out of memory: make_new_char (realloc of online_chars)." RETCODE); +- exit(1); +- } +- for(j = char_max - 256; j < char_max; j++) +- online_chars[j] = -1; + } + + char_log("Creation of New Character: (connection #%d, account: %d) slot %d, character Name: %s, stats: %d+%d+%d+%d+%d+%d=%d, hair: %d, hair color: %d." RETCODE, +- fd, sd->account_id, dat[30], dat, dat[24], dat[25], dat[26], dat[27], dat[28], dat[29], dat[24] + dat[25] + dat[26] + dat[27] + dat[28] + dat[29], dat[33], dat[31]); ++ fd, sd->account_id, dat[30], name, dat[24], dat[25], dat[26], dat[27], dat[28], dat[29], dat[24] + dat[25] + dat[26] + dat[27] + dat[28] + dat[29], dat[33], dat[31]);
+ +- memset(&char_dat[i], 0, sizeof(struct mmo_charstatus)); ++ memset(&char_dat[i], 0, sizeof(struct character_data));
+ +- char_dat[i].char_id = char_id_count++; +- char_dat[i].account_id = sd->account_id; +- char_dat[i].char_num = dat[30]; +- strcpy(char_dat[i].name, dat); +- char_dat[i].class = 0; +- char_dat[i].base_level = 1; +- char_dat[i].job_level = 1; +- char_dat[i].base_exp = 0; +- char_dat[i].job_exp = 0; +- char_dat[i].zeny = start_zeny; +- char_dat[i].str = dat[24]; +- char_dat[i].agi = dat[25]; +- char_dat[i].vit = dat[26]; +- char_dat[i].int_ = dat[27]; +- char_dat[i].dex = dat[28]; +- char_dat[i].luk = dat[29]; +- char_dat[i].max_hp = 40 * (100 + char_dat[i].vit) / 100; +- char_dat[i].max_sp = 11 * (100 + char_dat[i].int_) / 100; +- char_dat[i].hp = char_dat[i].max_hp; +- char_dat[i].sp = char_dat[i].max_sp; +- char_dat[i].status_point = 0; +- char_dat[i].skill_point = 0; +- char_dat[i].option = 0; +- char_dat[i].karma = 0; +- char_dat[i].manner = 0; +- char_dat[i].party_id = 0; +- char_dat[i].guild_id = 0; +- char_dat[i].hair = dat[33]; +- char_dat[i].hair_color = dat[31]; +- char_dat[i].clothes_color = 0; +- char_dat[i].inventory[0].nameid = start_weapon; // Knife +- char_dat[i].inventory[0].amount = 1; +- char_dat[i].inventory[0].equip = 0x02; +- char_dat[i].inventory[0].identify = 1; +- char_dat[i].inventory[0].broken = 0; +- char_dat[i].inventory[1].nameid = start_armor; // Cotton Shirt +- char_dat[i].inventory[1].amount = 1; +- char_dat[i].inventory[1].equip = 0x10; +- char_dat[i].inventory[1].identify = 1; +- char_dat[i].inventory[1].broken = 0; +- char_dat[i].weapon = 1; +- char_dat[i].shield = 0; +- char_dat[i].head_top = 0; +- char_dat[i].head_mid = 0; +- char_dat[i].head_bottom = 0; +- memcpy(&char_dat[i].last_point, &start_point, sizeof(start_point)); +- memcpy(&char_dat[i].save_point, &start_point, sizeof(start_point)); ++ char_dat[i].status.char_id = char_id_count++;
++ char_dat[i].status.account_id = sd->account_id;
++ char_dat[i].status.char_num = dat[30];
++ strcpy(char_dat[i].status.name,name);
++ char_dat[i].status.class_ = 0;
++ char_dat[i].status.base_level = 1;
++ char_dat[i].status.job_level = 1;
++ char_dat[i].status.base_exp = 0;
++ char_dat[i].status.job_exp = 0;
++ char_dat[i].status.zeny = start_zeny;
++ char_dat[i].status.str = dat[24];
++ char_dat[i].status.agi = dat[25];
++ char_dat[i].status.vit = dat[26];
++ char_dat[i].status.int_ = dat[27];
++ char_dat[i].status.dex = dat[28];
++ char_dat[i].status.luk = dat[29];
++ char_dat[i].status.max_hp = 40 * (100 + char_dat[i].status.vit) / 100;
++ char_dat[i].status.max_sp = 11 * (100 + char_dat[i].status.int_) / 100;
++ char_dat[i].status.hp = char_dat[i].status.max_hp;
++ char_dat[i].status.sp = char_dat[i].status.max_sp;
++ char_dat[i].status.status_point = 0;
++ char_dat[i].status.skill_point = 0;
++ char_dat[i].status.option = 0;
++ char_dat[i].status.karma = 0;
++ char_dat[i].status.manner = 0;
++ char_dat[i].status.party_id = 0;
++ char_dat[i].status.guild_id = 0;
++ char_dat[i].status.hair = dat[33];
++ char_dat[i].status.hair_color = dat[31];
++ char_dat[i].status.clothes_color = 0;
++ char_dat[i].status.inventory[0].nameid = start_weapon; // Knife
++ char_dat[i].status.inventory[0].amount = 1;
++ char_dat[i].status.inventory[0].equip = 0x02;
++ char_dat[i].status.inventory[0].identify = 1;
++ char_dat[i].status.inventory[1].nameid = start_armor; // Cotton Shirt
++ char_dat[i].status.inventory[1].amount = 1;
++ char_dat[i].status.inventory[1].equip = 0x10;
++ char_dat[i].status.inventory[1].identify = 1;
++ char_dat[i].status.weapon = 1;
++ char_dat[i].status.shield = 0;
++ char_dat[i].status.head_top = 0;
++ char_dat[i].status.head_mid = 0;
++ char_dat[i].status.head_bottom = 0;
++ memcpy(&char_dat[i].status.last_point, &start_point, sizeof(start_point));
++ memcpy(&char_dat[i].status.save_point, &start_point, sizeof(start_point));
+ char_num++; + + mmo_char_sync(); +@@ -882,8 +1190,8 @@ + //---------------------------------------------------- + // This function return the name of the job (by [Yor]) + //---------------------------------------------------- +-char * job_name(int class) { +- switch (class) { ++char * job_name(int class_) {
++ switch (class_) {
+ case 0: return "Novice"; + case 1: return "Swordsman"; + case 2: return "Mage"; +@@ -957,108 +1265,138 @@ + return "Unknown Job"; + } + +-//------------------------------------------------------------- +-// Function to create the online files (txt and html). by [Yor] +-//------------------------------------------------------------- +-void create_online_files(void) { +- int i, j, k, l; // for loops +- int players; // count the number of players +- FILE *fp; // for the txt file +- FILE *fp2; // for the html file +- char temp[256]; // to prepare what we must display +- time_t time_server; // for number of seconds +- struct tm *datetime; // variable for time in structure ->tm_mday, ->tm_sec, ... +- int id[char_num]; +- +- if (online_display_option == 0) // we display nothing, so return +- return; ++static int create_online_files_sub(DBKey key, void* data, va_list va)
++{
++ struct online_char_data *character;
++ int* players;
++ int *id;
++ int j,k,l;
++ character = (struct online_char_data*) data;
++ players = va_arg(va, int*);
++ id = va_arg(va, int*);
+ +- //char_log("Creation of online players files." RETCODE); ++ // check if map-server is online
++ if (character->server == -1 || character->char_id == -1) { //Character not currently online.
++ return -1;
++ }
+ +- // Get number of online players, id of each online players +- players = 0; +- // sort online characters. +- for(i = 0; i < char_num; i++) { +- if (online_chars[i] != -1) { +- id[players] = i; ++ j = character->server;
++ if (server_fd[j] < 0) {
++ server[j].users = 0;
++ if (kick_on_disconnect)
++ {
++ character->char_id = -1;
++ character->server = -1;
++ }
++ return -1;
++ }
++ // search position of character in char_dat and sort online characters.
++ for(j = 0; j < char_num; j++) {
++ if (char_dat[j].status.char_id != character->char_id)
++ continue;
++ id[*players] = j;
+ // use sorting option + switch (online_sorting_option) { + case 1: // by name (without case sensitive) +- { +- char *p_name = char_dat[i].name; //speed up sorting when there are a lot of players. But very rarely players have same name. +- for(j = 0; j < players; j++) +- if (stricmp(p_name, char_dat[id[j]].name) < 0 || ++ for(k = 0; k < *players; k++)
++ if (stricmp(char_dat[j].status.name, char_dat[id[k]].status.name) < 0 ||
+ // if same name, we sort with case sensitive. +- (stricmp(p_name, char_dat[id[j]].name) == 0 && +- strcmp(p_name, char_dat[id[j]].name) < 0)) { +- for(k = players; k > j; k--) +- id[k] = id[k-1]; +- id[j] = i; // id[players] ++ (stricmp(char_dat[j].status.name, char_dat[id[k]].status.name) == 0 &&
++ strcmp(char_dat[j].status.name, char_dat[id[k]].status.name) < 0)) {
++ for(l = *players; l > k; l--)
++ id[l] = id[l-1];
++ id[k] = j; // id[*players]
+ break; + } +- } + break; + case 2: // by zeny +- for(j = 0; j < players; j++) +- if (char_dat[i].zeny < char_dat[id[j]].zeny || ++ for(k = 0; k < *players; k++)
++ if (char_dat[j].status.zeny < char_dat[id[k]].status.zeny ||
+ // if same number of zenys, we sort by name. +- (char_dat[i].zeny == char_dat[id[j]].zeny && +- stricmp(char_dat[i].name, char_dat[id[j]].name) < 0)) { +- for(k = players; k > j; k--) +- id[k] = id[k-1]; +- id[j] = i; // id[players] ++ (char_dat[j].status.zeny == char_dat[id[k]].status.zeny &&
++ stricmp(char_dat[j].status.name, char_dat[id[k]].status.name) < 0)) {
++ for(l = *players; l > k; l--)
++ id[l] = id[l-1];
++ id[k] = j; // id[*players]
+ break; + } + break; + case 3: // by base level +- for(j = 0; j < players; j++) +- if (char_dat[i].base_level < char_dat[id[j]].base_level || ++ for(k = 0; k < *players; k++)
++ if (char_dat[j].status.base_level < char_dat[id[k]].status.base_level ||
+ // if same base level, we sort by base exp. +- (char_dat[i].base_level == char_dat[id[j]].base_level && +- char_dat[i].base_exp < char_dat[id[j]].base_exp)) { +- for(k = players; k > j; k--) +- id[k] = id[k-1]; +- id[j] = i; // id[players] ++ (char_dat[j].status.base_level == char_dat[id[k]].status.base_level &&
++ char_dat[j].status.base_exp < char_dat[id[k]].status.base_exp)) {
++ for(l = *players; l > k; l--)
++ id[l] = id[l-1];
++ id[k] = j; // id[*players]
+ break; + } + break; + case 4: // by job (and job level) +- for(j = 0; j < players; j++) +- if (char_dat[i].class < char_dat[id[j]].class || ++ for(k = 0; k < *players; k++)
++ if (char_dat[j].status.class_ < char_dat[id[k]].status.class_ ||
+ // if same job, we sort by job level. +- (char_dat[i].class == char_dat[id[j]].class && +- char_dat[i].job_level < char_dat[id[j]].job_level) || ++ (char_dat[j].status.class_ == char_dat[id[k]].status.class_ &&
++ char_dat[j].status.job_level < char_dat[id[k]].status.job_level) ||
+ // if same job and job level, we sort by job exp. +- (char_dat[i].class == char_dat[id[j]].class && +- char_dat[i].job_level == char_dat[id[j]].job_level && +- char_dat[i].job_exp < char_dat[id[j]].job_exp)) { +- for(k = players; k > j; k--) +- id[k] = id[k-1]; +- id[j] = i; // id[players] ++ (char_dat[j].status.class_ == char_dat[id[k]].status.class_ &&
++ char_dat[j].status.job_level == char_dat[id[k]].status.job_level &&
++ char_dat[j].status.job_exp < char_dat[id[k]].status.job_exp)) {
++ for(l = *players; l > k; l--)
++ id[l] = id[l-1];
++ id[k] = j; // id[*players]
+ break; + } + break; + case 5: // by location map name + { +- int cpm_result; // A lot of player maps are identical. So, test if done often twice. +- for(j = 0; j < players; j++) +- if ((cpm_result = strcmp(char_dat[i].last_point.map, char_dat[id[j]].last_point.map)) < 0 || // no map are identical and with upper cases (not use stricmp) ++ const char *map1, *map2;
++ map1 = mapindex_id2name(char_dat[j].status.last_point.map);
++
++ for(k = 0; k < *players; k++) {
++ map2 = mapindex_id2name(char_dat[id[k]].status.last_point.map);
++ if (!map1 || !map2 || //Avoid sorting if either one failed to resolve.
++ stricmp(map1, map2) < 0 ||
+ // if same map name, we sort by name. +- (cpm_result == 0 && +- stricmp(char_dat[i].name, char_dat[id[j]].name) < 0)) { +- for(k = players; k > j; k--) +- id[k] = id[k-1]; +- id[j] = i; // id[players] ++ (stricmp(map1, map2) == 0 &&
++ stricmp(char_dat[j].status.name, char_dat[id[k]].status.name) < 0)) {
++ for(l = *players; l > k; l--)
++ id[l] = id[l-1];
++ id[k] = j; // id[*players]
+ break; + } + } ++ }
+ break; + default: // 0 or invalid value: no sorting + break; + } +- players++; ++ (*players)++;
++ break;
+ } ++ return 0;
+ } ++//-------------------------------------------------------------
++// Function to create the online files (txt and html). by [Yor]
++//-------------------------------------------------------------
++void create_online_files(void) {
++ unsigned int k, j; // for loop with strlen comparing
++ int i, l; // for loops
++ int players; // count the number of players
++ FILE *fp; // for the txt file
++ FILE *fp2; // for the html file
++ char temp[256]; // to prepare what we must display
++ time_t time_server; // for number of seconds
++ struct tm *datetime; // variable for time in structure ->tm_mday, ->tm_sec, ...
++ int id[4096];
++
++ if (online_display_option == 0) // we display nothing, so return
++ return;
++
++ // Get number of online players, id of each online players, and verify if a server is offline
++ players = 0;
++ online_char_db->foreach(online_char_db, create_online_files_sub, &players, &id);
+ + // write files + fp = fopen(online_txt_filename, "w"); +@@ -1080,8 +1418,9 @@ + fprintf(fp, "Online Players on %s (%s):\n", server_name, temp); + fprintf(fp, "\n"); + +- // If we display at least 1 player +- if (players > 0) { ++ for (i = 0; i < players; i++) {
++ // if it's the first player
++ if (i == 0) {
+ j = 0; // count the number of characters for the txt version and to set the separate line + fprintf(fp2, " <table border=\"1\" cellspacing=\"1\">\n"); + fprintf(fp2, " <tr>\n"); +@@ -1128,16 +1467,14 @@ + for (k = 0; k < j; k++) + fprintf(fp, "-"); + fprintf(fp, "\n"); +- +- // display each player. +- for (i = 0; i < players; i++) { ++ }
++ fprintf(fp2, " <tr>\n");
+ // get id of the character (more speed) + j = id[i]; +- fprintf(fp2, " <tr>\n"); + // displaying the character name + if ((online_display_option & 1) || (online_display_option & 64)) { // without/with 'GM' display +- strcpy(temp, char_dat[j].name); +- l = isGM(char_dat[j].account_id); ++ strcpy(temp, char_dat[j].status.name);
++ l = isGM(char_dat[j].status.account_id);
+ if (online_display_option & 64) { + if (l >= online_gm_display_min_level) + fprintf(fp, "%-24s (GM) ", temp); +@@ -1149,7 +1486,7 @@ + fprintf(fp2, " <td>"); + if ((online_display_option & 64) && l >= online_gm_display_min_level) + fprintf(fp2, "<b>"); +- for (k = 0; temp[k]; k++) { ++ for (k = 0; k < strlen(temp); k++) {
+ switch(temp[k]) { + case '<': // < + fprintf(fp2, "<"); +@@ -1168,48 +1505,51 @@ + } + // displaying of the job + if (online_display_option & 6) { +- char * jobname = job_name(char_dat[j].class); ++ char * jobname = job_name(char_dat[j].status.class_);
+ if ((online_display_option & 6) == 6) { +- fprintf(fp2, " <td>%s %d/%d</td>\n", jobname, char_dat[j].base_level, char_dat[j].job_level); +- fprintf(fp, "%-18s %3d/%3d ", jobname, char_dat[j].base_level, char_dat[j].job_level); ++ fprintf(fp2, " <td>%s %d/%d</td>\n", jobname, char_dat[j].status.base_level, char_dat[j].status.job_level);
++ fprintf(fp, "%-18s %3d/%3d ", jobname, char_dat[j].status.base_level, char_dat[j].status.job_level);
+ } else if (online_display_option & 2) { + fprintf(fp2, " <td>%s</td>\n", jobname); + fprintf(fp, "%-18s ", jobname); + } else if (online_display_option & 4) { +- fprintf(fp2, " <td>%d/%d</td>\n", char_dat[j].base_level, char_dat[j].job_level); +- fprintf(fp, "%3d/%3d ", char_dat[j].base_level, char_dat[j].job_level); ++ fprintf(fp2, " <td>%d/%d</td>\n", char_dat[j].status.base_level, char_dat[j].status.job_level);
++ fprintf(fp, "%3d/%3d ", char_dat[j].status.base_level, char_dat[j].status.job_level);
+ } + } + // displaying of the map + if (online_display_option & 24) { // 8 or 16 + // prepare map name +- memset(temp, 0, sizeof(temp)); +- strncpy(temp, char_dat[j].last_point.map, 16); +- if (strchr(temp, '.') != NULL) +- temp[strchr(temp, '.') - temp] = '\0'; // suppress the '.gat' ++ memcpy(temp, mapindex_id2name(char_dat[j].status.last_point.map), MAP_NAME_LENGTH);
++ temp[MAP_NAME_LENGTH] = '\0';
++ if (strstr(temp, ".gat") != NULL) {
++ temp[strstr(temp, ".gat") - temp] = 0; // suppress the '.gat'
++ }
+ // write map name +- if (online_display_option & 16) { // map-name AND coordonates +- fprintf(fp2, " <td>%s (%d, %d)</td>\n", temp, char_dat[j].last_point.x, char_dat[j].last_point.y); +- fprintf(fp, "%-12s (%3d,%3d) ", temp, char_dat[j].last_point.x, char_dat[j].last_point.y); ++ if (online_display_option & 16) { // map-name AND coordinates
++ fprintf(fp2, " <td>%s (%d, %d)</td>\n", temp, char_dat[j].status.last_point.x, char_dat[j].status.last_point.y);
++ fprintf(fp, "%-12s (%3d,%3d) ", temp, char_dat[j].status.last_point.x, char_dat[j].status.last_point.y);
+ } else { + fprintf(fp2, " <td>%s</td>\n", temp); + fprintf(fp, "%-12s ", temp); + } + } +- // displaying number of zenys ++ // displaying nimber of zenys
+ if (online_display_option & 32) { + // write number of zenys +- if (char_dat[j].zeny == 0) { // if no zeny ++ if (char_dat[j].status.zeny == 0) { // if no zeny
+ fprintf(fp2, " <td ALIGN=RIGHT>no zeny</td>\n"); + fprintf(fp, " no zeny "); + } else { +- fprintf(fp2, " <td ALIGN=RIGHT>%d z</td>\n", char_dat[j].zeny); +- fprintf(fp, "%13d z ", char_dat[j].zeny); ++ fprintf(fp2, " <td ALIGN=RIGHT>%d z</td>\n", char_dat[j].status.zeny);
++ fprintf(fp, "%13d z ", char_dat[j].status.zeny);
+ } + } + fprintf(fp, "\n"); + fprintf(fp2, " </tr>\n"); + } ++ // If we display at least 1 player
++ if (players > 0) {
+ fprintf(fp2, " </table>\n"); + fprintf(fp, "\n"); + } +@@ -1218,8 +1558,9 @@ + if (players == 0) { + fprintf(fp2, " <p>No user is online.</p>\n"); + fprintf(fp, "No user is online.\n"); +- // no display if only 1 player + } else if (players == 1) { ++ fprintf(fp2, " <p>%d user is online.</p>\n", players);
++ fprintf(fp, "%d user is online.\n", players);
+ } else { + fprintf(fp2, " <p>%d users are online.</p>\n", players); + fprintf(fp, "%d users are online.\n", players); +@@ -1254,15 +1595,17 @@ + int mmo_char_send006b(int fd, struct char_session_data *sd) { + int i, j, found_num; + struct mmo_charstatus *p; +-#ifdef NEW_006b ++//#ifdef NEW_006b
+ const int offset = 24; +-#else +- const int offset = 4; +-#endif ++//#else
++// const int offset = 4;
++//#endif
++
++ set_char_online(0, 99,sd->account_id);
+ + found_num = 0; + for(i = 0; i < char_num; i++) { +- if (char_dat[i].account_id == sd->account_id) { ++ if (char_dat[i].status.account_id == sd->account_id) {
+ sd->found_char[found_num] = i; + found_num++; + if (found_num == 9) +@@ -1272,18 +1615,19 @@ + for(i = found_num; i < 9; i++) + sd->found_char[i] = -1; + ++ WFIFOHEAD(fd, offset + found_num * 106);
+ memset(WFIFOP(fd,0), 0, offset + found_num * 106); + WFIFOW(fd,0) = 0x6b; + WFIFOW(fd,2) = offset + found_num * 106; + + for(i = 0; i < found_num; i++) { +- p = &char_dat[sd->found_char[i]]; ++ p = &char_dat[sd->found_char[i]].status;
+ j = offset + (i * 106); // increase speed of code + + WFIFOL(fd,j) = p->char_id; +- WFIFOL(fd,j+4) = p->base_exp; ++ WFIFOL(fd,j+4) = p->base_exp>LONG_MAX?LONG_MAX:p->base_exp;
+ WFIFOL(fd,j+8) = p->zeny; +- WFIFOL(fd,j+12) = p->job_exp; ++ WFIFOL(fd,j+12) = p->job_exp>LONG_MAX?LONG_MAX:p->job_exp;
+ WFIFOL(fd,j+16) = p->job_level; + + WFIFOL(fd,j+20) = 0; +@@ -1299,9 +1643,16 @@ + WFIFOW(fd,j+46) = (p->sp > 0x7fff) ? 0x7fff : p->sp; + WFIFOW(fd,j+48) = (p->max_sp > 0x7fff) ? 0x7fff : p->max_sp; + WFIFOW(fd,j+50) = DEFAULT_WALK_SPEED; // p->speed; +- WFIFOW(fd,j+52) = p->class; ++ WFIFOW(fd,j+52) = p->class_;
+ WFIFOW(fd,j+54) = p->hair; +- WFIFOW(fd,j+56) = p->weapon; ++
++ // pecopeco knights/crusaders crash fix
++ if (p->class_ == 13 || p->class_ == 21 ||
++ p->class_ == 4014 || p->class_ == 4022 ||
++ p->class_ == 4036 || p->class_ == 4044)
++ WFIFOW(fd,j+56) = 0;
++ else WFIFOW(fd,j+56) = p->weapon;
++
+ WFIFOW(fd,j+58) = p->base_level; + WFIFOW(fd,j+60) = p->skill_point; + WFIFOW(fd,j+62) = p->head_bottom; +@@ -1311,7 +1662,7 @@ + WFIFOW(fd,j+70) = p->hair_color; + WFIFOW(fd,j+72) = p->clothes_color; + +- memcpy(WFIFOP(fd,j+74), p->name, 24); ++ memcpy(WFIFOP(fd,j+74), p->name, NAME_LENGTH);
+ + WFIFOB(fd,j+98) = (p->str > 255) ? 255 : p->str; + WFIFOB(fd,j+99) = (p->agi > 255) ? 255 : p->agi; +@@ -1327,34 +1678,20 @@ + return 0; + } + +-int set_account_reg2(int acc, int num, struct global_reg *reg) { +- int i, c; ++// 離婚(char削除時に使用)
++int char_divorce(struct mmo_charstatus *cs) {
++ if (cs == NULL)
++ return 0;
+ +- c = 0; ++ if (cs->partner_id > 0){
++ int i, j;
+ for(i = 0; i < char_num; i++) { +- if (char_dat[i].account_id == acc) { +- memcpy(char_dat[i].account_reg2, reg, sizeof(char_dat[i].account_reg2)); +- char_dat[i].account_reg2_num = num; +- c++; +- } +- } +- return c; +-} +- +-// 離婚(char削除時に使用) +-int char_divorce(struct mmo_charstatus *cs) { +- if (cs == NULL) +- return 0; +- +- if (cs->partner_id > 0){ +- int i, j; +- for(i = 0; i < char_num; i++) { +- if (char_dat[i].char_id == cs->partner_id && char_dat[i].partner_id == cs->char_id) { ++ if (char_dat[i].status.char_id == cs->partner_id && char_dat[i].status.partner_id == cs->char_id) {
+ cs->partner_id = 0; +- char_dat[i].partner_id = 0; ++ char_dat[i].status.partner_id = 0;
+ for(j = 0; j < MAX_INVENTORY; j++) +- if (char_dat[i].inventory[j].nameid == WEDDING_RING_M || char_dat[i].inventory[j].nameid == WEDDING_RING_F) +- memset(&char_dat[i].inventory[j], 0, sizeof(char_dat[i].inventory[0])); ++ if (char_dat[i].status.inventory[j].nameid == WEDDING_RING_M || char_dat[i].status.inventory[j].nameid == WEDDING_RING_F)
++ memset(&char_dat[i].status.inventory[j], 0, sizeof(char_dat[i].status.inventory[0]));
+ if (cs->inventory[j].nameid == WEDDING_RING_M || cs->inventory[j].nameid == WEDDING_RING_F) + memset(&cs->inventory[j], 0, sizeof(cs->inventory[0])); + return 0; +@@ -1364,12 +1701,22 @@ + return 0; + } + ++int char_married(int pl1,int pl2) {
++ return (char_dat[pl1].status.char_id == char_dat[pl2].status.partner_id && char_dat[pl2].status.char_id == char_dat[pl1].status.partner_id);
++}
++
++int char_child(int parent_id, int child_id) {
++ return (char_dat[parent_id].status.child == char_dat[child_id].status.char_id &&
++ ((char_dat[parent_id].status.char_id == char_dat[child_id].status.father) ||
++ (char_dat[parent_id].status.char_id == char_dat[child_id].status.mother)));
++}
++
+ //------------------------------------------------------------ + // E-mail check: return 0 (not correct) or 1 (valid). by [Yor] + //------------------------------------------------------------ +-int e_mail_check(unsigned char *email) { ++int e_mail_check(char *email) {
+ char ch; +- unsigned char* last_arobas; ++ char* last_arobas;
+ + // athena limits + if (strlen(email) < 3 || strlen(email) > 39) +@@ -1412,7 +1759,7 @@ + + // disconnect player if online on char-server + for(i = 0; i < fd_max; i++) { +- if (session[i] && (sd = session[i]->session_data)) { ++ if (session[i] && (sd = (struct char_session_data*)session[i]->session_data)) {
+ if (sd->account_id == accound_id) { + session[i]->eof = 1; + return 1; +@@ -1432,20 +1779,20 @@ + inter_pet_delete(cs->pet_id); + for (j = 0; j < MAX_INVENTORY; j++) + if (cs->inventory[j].card[0] == (short)0xff00) +- inter_pet_delete(*((long *)(&cs->inventory[j].card[2]))); ++ inter_pet_delete(MakeDWord(cs->inventory[j].card[1],cs->inventory[j].card[2]));
+ for (j = 0; j < MAX_CART; j++) + if (cs->cart[j].card[0] == (short)0xff00) +- inter_pet_delete(*((long *)(&cs->cart[j].card[2]))); ++ inter_pet_delete( MakeDWord(cs->cart[j].card[1],cs->cart[j].card[2]) );
+ // ギルド脱退 + if (cs->guild_id) + inter_guild_leave(cs->guild_id, cs->account_id, cs->char_id); + // パーティー脱退 + if (cs->party_id) +- inter_party_leave(cs->party_id, cs->account_id); ++ inter_party_leave(cs->party_id, cs->account_id, cs->char_id);
+ // 離婚 + if (cs->partner_id){ + // 離婚情報をmapに通知 +- char buf[10]; ++ unsigned char buf[10];
+ WBUFW(buf,0) = 0x2b12; + WBUFL(buf,2) = cs->char_id; + WBUFL(buf,6) = cs->partner_id; +@@ -1459,22 +1806,24 @@ + int parse_tologin(int fd) { + int i; + struct char_session_data *sd; ++ RFIFOHEAD(fd);
+ + // only login-server can have an access to here. + // so, if it isn't the login-server, we disconnect the session (fd != login_fd). +- if (fd != login_fd || session[fd]->eof) { ++ if (fd != login_fd)
++ session[fd]->eof = 1;
++ if(session[fd]->eof) {
+ if (fd == login_fd) { +- printf("Char-server can't connect to login-server (connection #%d).\n", fd); ++ ShowWarning("Connection to login-server lost (connection #%d).\n", fd);
+ login_fd = -1; + } +- close(fd); +- delete_session(fd); ++ do_close(fd);
+ return 0; + } + +- sd = session[fd]->session_data; ++ sd = (struct char_session_data*)session[fd]->session_data;
+ +- while(RFIFOREST(fd) >= 2) { ++ while(RFIFOREST(fd) >= 2 && !session[fd]->eof) {
+ // printf("parse_tologin: connection #%d, packet: 0x%x (with being read: %d bytes).\n", fd, RFIFOW(fd,0), RFIFOREST(fd)); + + switch(RFIFOW(fd,0)) { +@@ -1483,19 +1832,21 @@ + return 0; + if (RFIFOB(fd,2)) { + // printf("connect login server error : %d\n", RFIFOB(fd,2)); +- printf("Can not connect to login-server.\n"); +- printf("The server communication passwords (default s1/p1) is probably invalid.\n"); +- printf("Also, please make sure your accounts file (default: accounts.txt) has those values present.\n"); +- printf("If you changed the communication passwords, change them back at map_athena.conf and char_athena.conf\n"); ++ ShowError("Can not connect to the login-server.\n");
++ ShowError("The server communication passwords (default s1/p1) are probably invalid.\n");
++ ShowInfo("Also, please make sure your accounts file (default: accounts.txt) has those values present.\n");
++ ShowInfo("The communication passwords can be changed in map_athena.conf and char_athena.conf\n");
+ exit(1); + } else { +- printf("Connected to login-server (connection #%d).\n", fd); ++ ShowStatus("Connected to login-server (connection #%d).\n", fd);
++ if (kick_on_disconnect)
++ set_all_offline();
+ // if no map-server already connected, display a message... + for(i = 0; i < MAX_MAP_SERVERS; i++) +- if (server_fd[i] >= 0 && server[i].map[0][0]) // if map-server online and at least 1 map ++ if (server_fd[i] >= 0 && server[i].map[0]) // if map-server online and at least 1 map
+ break; + if (i == MAX_MAP_SERVERS) +- printf("Awaiting maps from map-server.\n"); ++ ShowStatus("Awaiting maps from map-server.\n");
+ } + RFIFOSKIP(fd,3); + break; +@@ -1505,8 +1856,9 @@ + return 0; + // printf("parse_tologin 2713 : %d\n", RFIFOB(fd,6)); + for(i = 0; i < fd_max; i++) { +- if (session[i] && (sd = session[i]->session_data) && sd->account_id == RFIFOL(fd,2)) { ++ if (session[i] && (sd = (struct char_session_data*)session[i]->session_data) && sd->account_id == RFIFOL(fd,2)) {
+ if (RFIFOB(fd,6) != 0) { ++ WFIFOHEAD(i, 3);
+ WFIFOW(i,0) = 0x6c; + WFIFOB(i,2) = 0x42; + WFIFOSET(i,3); +@@ -1521,9 +1873,14 @@ + sd->connect_until_time = (time_t)RFIFOL(fd,47); + // send characters to player + mmo_char_send006b(i, sd); ++ } else if(isGM(sd->account_id) >= gm_allow_level) {
++ sd->connect_until_time = (time_t)RFIFOL(fd,47);
++ // send characters to player
++ mmo_char_send006b(i, sd);
+ } else { + // refuse connection: too much online players + // printf("count_users(): %d < max_connect_use (%d) -> fail...\n", count_users(), max_connect_user); ++ WFIFOHEAD(fd, 3);
+ WFIFOW(i,0) = 0x6c; + WFIFOW(i,2) = 0; + WFIFOSET(i,3); +@@ -1539,7 +1896,7 @@ + if (RFIFOREST(fd) < 50) + return 0; + for(i = 0; i < fd_max; i++) { +- if (session[i] && (sd = session[i]->session_data)) { ++ if (session[i] && (sd = (struct char_session_data*)session[i]->session_data)) {
+ if (sd->account_id == RFIFOL(fd,2)) { + memcpy(sd->email, RFIFOP(fd,6), 40); + if (e_mail_check(sd->email) == 0) +@@ -1552,6 +1909,40 @@ + RFIFOSKIP(fd,50); + break; + ++ // login-server alive packet
++ case 0x2718:
++ if (RFIFOREST(fd) < 2)
++ return 0;
++ RFIFOSKIP(fd,2);
++ break;
++
++ // Receiving authentification from Freya-type login server (to avoid char->login->char)
++ case 0x2719:
++ if (RFIFOREST(fd) < 18)
++ return 0;
++ // to conserv a maximum of authentification, search if account is already authentified and replace it
++ // that will reduce multiple connection too
++ for(i = 0; i < AUTH_FIFO_SIZE; i++)
++ if (auth_fifo[i].account_id == RFIFOL(fd,2))
++ break;
++ // if not found, use next value
++ if (i == AUTH_FIFO_SIZE) {
++ if (auth_fifo_pos >= AUTH_FIFO_SIZE)
++ auth_fifo_pos = 0;
++ i = auth_fifo_pos;
++ auth_fifo_pos++;
++ }
++ auth_fifo[i].account_id = RFIFOL(fd,2);
++ auth_fifo[i].char_id = 0;
++ auth_fifo[i].login_id1 = RFIFOL(fd,6);
++ auth_fifo[i].login_id2 = RFIFOL(fd,10);
++ auth_fifo[i].delflag = 2; // 0: auth_fifo canceled/void, 2: auth_fifo received from login/map server in memory, 1: connection authentified
++ auth_fifo[i].char_pos = 0;
++ auth_fifo[i].connect_until_time = 0; // unlimited/unknown time by default (not display in map-server)
++ auth_fifo[i].ip = RFIFOL(fd,14);
++ RFIFOSKIP(fd,18);
++ break;
++
+ case 0x2721: // gm reply + if (RFIFOREST(fd) < 10) + return 0; +@@ -1576,49 +1967,55 @@ + sex = RFIFOB(fd,6); + RFIFOSKIP(fd, 7); + if (acc > 0) { +- for (i = 0; i < char_num; i++) { +- if (char_dat[i].account_id == acc) { +- int jobclass = char_dat[i].class; +- char_dat[i].sex = sex; ++ for(i = 0; i < AUTH_FIFO_SIZE; i++) {
++ if (auth_fifo[i].account_id == acc)
+ auth_fifo[i].sex = sex; ++ }
++ for (i = 0; i < char_num; i++) {
++ if (char_dat[i].status.account_id == acc) {
++ int jobclass = char_dat[i].status.class_;
++ char_dat[i].status.sex = sex;
+ if (jobclass == 19 || jobclass == 20 || + jobclass == 4020 || jobclass == 4021 || + jobclass == 4042 || jobclass == 4043) { + // job modification + if (jobclass == 19 || jobclass == 20) { +- char_dat[i].class = (sex) ? 19 : 20; ++ char_dat[i].status.class_ = (sex) ? 19 : 20;
+ } else if (jobclass == 4020 || jobclass == 4021) { +- char_dat[i].class = (sex) ? 4020 : 4021; ++ char_dat[i].status.class_ = (sex) ? 4020 : 4021;
+ } else if (jobclass == 4042 || jobclass == 4043) { +- char_dat[i].class = (sex) ? 4042 : 4043; ++ char_dat[i].status.class_ = (sex) ? 4042 : 4043;
+ } + // remove specifical skills of classes 19, 4020 and 4042 + for(j = 315; j <= 322; j++) { +- if (char_dat[i].skill[j].id > 0 && !char_dat[i].skill[j].flag) { +- char_dat[i].skill_point += char_dat[i].skill[j].lv; +- char_dat[i].skill[j].id = 0; +- char_dat[i].skill[j].lv = 0; ++ if (char_dat[i].status.skill[j].id > 0 && !char_dat[i].status.skill[j].flag) {
++ char_dat[i].status.skill_point += char_dat[i].status.skill[j].lv;
++ char_dat[i].status.skill[j].id = 0;
++ char_dat[i].status.skill[j].lv = 0;
+ } + } + // remove specifical skills of classes 20, 4021 and 4043 + for(j = 323; j <= 330; j++) { +- if (char_dat[i].skill[j].id > 0 && !char_dat[i].skill[j].flag) { +- char_dat[i].skill_point += char_dat[i].skill[j].lv; +- char_dat[i].skill[j].id = 0; +- char_dat[i].skill[j].lv = 0; ++ if (char_dat[i].status.skill[j].id > 0 && !char_dat[i].status.skill[j].flag) {
++ char_dat[i].status.skill_point += char_dat[i].status.skill[j].lv;
++ char_dat[i].status.skill[j].id = 0;
++ char_dat[i].status.skill[j].lv = 0;
+ } + } + } + // to avoid any problem with equipment and invalid sex, equipment is unequiped. + for (j = 0; j < MAX_INVENTORY; j++) { +- if (char_dat[i].inventory[j].nameid && char_dat[i].inventory[j].equip) +- char_dat[i].inventory[j].equip = 0; ++ if (char_dat[i].status.inventory[j].nameid && char_dat[i].status.inventory[j].equip)
++ char_dat[i].status.inventory[j].equip = 0;
+ } +- char_dat[i].weapon = 0; +- char_dat[i].shield = 0; +- char_dat[i].head_top = 0; +- char_dat[i].head_mid = 0; +- char_dat[i].head_bottom = 0; ++ char_dat[i].status.weapon = 0;
++ char_dat[i].status.shield = 0;
++ char_dat[i].status.head_top = 0;
++ char_dat[i].status.head_mid = 0;
++ char_dat[i].status.head_bottom = 0;
++
++ if (char_dat[i].status.guild_id) //If there is a guild, update the guild_member data [Skotlex]
++ inter_guild_sex_changed(char_dat[i].status.guild_id, acc, char_dat[i].status.char_id, sex);
+ } + } + // disconnect player if online on char-server +@@ -1644,14 +2041,14 @@ + if (i == MAX_MAP_SERVERS) + char_log("'ladmin': Receiving a message for broadcast, but no map-server is online." RETCODE); + else { +- char buf[128]; +- char message[RFIFOL(fd,4) + 1]; // +1 to add a null terminated if not exist in the packet ++ unsigned char buf[128];
++ char message[4096]; // +1 to add a null terminated if not exist in the packet
+ int lp; + char *p; + memset(message, '\0', sizeof(message)); + memcpy(message, RFIFOP(fd,8), RFIFOL(fd,4)); + message[sizeof(message)-1] = '\0'; +- remove_control_chars(message); ++ remove_control_chars((unsigned char *)message);
+ // remove all first spaces + p = message; + while(p[0] == ' ') +@@ -1702,22 +2099,13 @@ + case 0x2729: + if (RFIFOREST(fd) < 4 || RFIFOREST(fd) < RFIFOW(fd,2)) + return 0; +- { +- struct global_reg reg[ACCOUNT_REG2_NUM]; +- unsigned char buf[4096]; +- int j, p, acc; +- acc = RFIFOL(fd,4); +- for (p = 8, j = 0; p < RFIFOW(fd,2) && j < ACCOUNT_REG2_NUM; p += 36, j++) { +- memcpy(reg[j].str, RFIFOP(fd,p), 32); +- reg[j].value = RFIFOL(fd,p+32); +- } +- set_account_reg2(acc, j, reg); +- // 同垢ログインを禁止していれば送る必要は無い ++ { //Receive account_reg2 registry, forward to map servers.
++ unsigned char buf[ACCOUNT_REG2_NUM*(256+32+2)+16];
+ memcpy(buf, RFIFOP(fd,0), RFIFOW(fd,2)); +- WBUFW(buf,0) = 0x2b11; ++// WBUFW(buf,0) = 0x2b11;
++ WBUFW(buf,0) = 0x3804; //Map server can now receive all kinds of reg values with the same packet. [Skotlex]
+ mapif_sendall(buf, WBUFW(buf,2)); + RFIFOSKIP(fd,RFIFOW(fd,2)); +-// printf("char: save_account_reg_reply\n"); + } + break; + +@@ -1727,20 +2115,20 @@ + return 0; + // Deletion of all characters of the account + for(i = 0; i < char_num; i++) { +- if (char_dat[i].account_id == RFIFOL(fd,2)) { +- char_delete(&char_dat[i]); ++ if (char_dat[i].status.account_id == RFIFOL(fd,2)) {
++ char_delete(&char_dat[i].status);
+ if (i < char_num - 1) { +- memcpy(&char_dat[i], &char_dat[char_num-1], sizeof(struct mmo_charstatus)); ++ memcpy(&char_dat[i], &char_dat[char_num-1], sizeof(struct character_data));
+ // if moved character owns to deleted account, check again it's character +- if (char_dat[i].account_id == RFIFOL(fd,2)) { ++ if (char_dat[i].status.account_id == RFIFOL(fd,2)) {
+ i--; + // Correct moved character reference in the character's owner by [Yor] + } else { + int j, k; + struct char_session_data *sd2; + for (j = 0; j < fd_max; j++) { +- if (session[j] && (sd2 = session[j]->session_data) && +- sd2->account_id == char_dat[char_num-1].account_id) { ++ if (session[j] && (sd2 = (struct char_session_data*)session[j]->session_data) &&
++ sd2->account_id == char_dat[char_num-1].status.account_id) {
+ for (k = 0; k < 9; k++) { + if (sd2->found_char[k] == char_num-1) { + sd2->found_char[k] = i; +@@ -1792,10 +2180,10 @@ + if (RFIFOREST(fd) < 4 || RFIFOREST(fd) < RFIFOW(fd,2)) + return 0; + { +- char buf[32000]; ++ unsigned char buf[32000];
+ if (gm_account != NULL) +- free(gm_account); +- gm_account = calloc(sizeof(struct gm_account) * ((RFIFOW(fd,2) - 4) / 5), 1); ++ aFree(gm_account);
++ gm_account = (struct gm_account*)aCalloc(sizeof(struct gm_account) * ((RFIFOW(fd,2) - 4) / 5), 1);
+ GM_num = 0; + for (i = 4; i < RFIFOW(fd,2); i = i + 5) { + gm_account[GM_num].account_id = RFIFOL(fd,i); +@@ -1803,8 +2191,8 @@ + //printf("GM account: %d -> level %d\n", gm_account[GM_num].account_id, gm_account[GM_num].level); + GM_num++; + } +- printf("From login-server: receiving of %d GM accounts information.\n", GM_num); +- char_log("From login-server: receiving of %d GM accounts information." RETCODE, GM_num); ++ ShowStatus("From login-server: receiving information of %d GM accounts.\n", GM_num);
++ char_log("From login-server: receiving information of %d GM accounts." RETCODE, GM_num);
+ create_online_files(); // update online players files (perhaps some online players change of GM level) + // send new gm acccounts level to map-servers + memcpy(buf, RFIFOP(fd,0), RFIFOW(fd,2)); +@@ -1814,7 +2202,101 @@ + RFIFOSKIP(fd,RFIFOW(fd,2)); + break; + ++ // Receive GM accounts [Freya login server packet by Yor]
++ case 0x2733:
++ // add test here to remember that the login-server is Freya-type
++ // sprintf (login_server_type, "Freya");
++ if (RFIFOREST(fd) < 7)
++ return 0;
++ {
++ unsigned char buf[32000];
++ int new_level = 0;
++ for(i = 0; i < GM_num; i++)
++ if (gm_account[i].account_id == RFIFOL(fd,2)) {
++ if (gm_account[i].level != (int)RFIFOB(fd,6)) {
++ gm_account[i].level = (int)RFIFOB(fd,6);
++ new_level = 1;
++ }
++ break;
++ }
++ // if not found, add it
++ if (i == GM_num) {
++ // limited to 4000, because we send information to char-servers (more than 4000 GM accounts???)
++ // int (id) + int (level) = 8 bytes * 4000 = 32k (limit of packets in windows)
++ if (((int)RFIFOB(fd,6)) > 0 && GM_num < 4000) {
++ if (GM_num == 0) {
++ gm_account = (struct gm_account*)aMalloc(sizeof(struct gm_account));
++ } else {
++ gm_account = (struct gm_account*)aRealloc(gm_account, sizeof(struct gm_account) * (GM_num + 1));
++ }
++ gm_account[GM_num].account_id = RFIFOL(fd,2);
++ gm_account[GM_num].level = (int)RFIFOB(fd,6);
++ new_level = 1;
++ GM_num++;
++ if (GM_num >= 4000) {
++ ShowWarning("4000 GM accounts found. Next GM accounts are not readed.\n");
++ char_log("***WARNING: 4000 GM accounts found. Next GM accounts are not readed." RETCODE);
++ }
++ }
++ }
++ if (new_level == 1) {
++ int len;
++ ShowStatus("From login-server: receiving GM account information (%d: level %d).\n", RFIFOL(fd,2), (int)RFIFOB(fd,6));
++ char_log("From login-server: receiving a GM account information (%d: level %d)." RETCODE, RFIFOL(fd,2), (int)RFIFOB(fd,6));
++ //create_online_files(); // not change online file for only 1 player (in next timer, that will be done
++ // send gm acccounts level to map-servers
++ len = 4;
++ WBUFW(buf,0) = 0x2b15;
++
++ for(i = 0; i < GM_num; i++) {
++ WBUFL(buf, len) = gm_account[i].account_id;
++ WBUFB(buf, len+4) = (unsigned char)gm_account[i].level;
++ len += 5;
++ }
++ WBUFW(buf, 2) = len;
++ mapif_sendall(buf, len);
++ }
++ }
++ RFIFOSKIP(fd,7);
++ break;
++
++ //Login server request to kick a character out. [Skotlex]
++ case 0x2734:
++ if (RFIFOREST(fd) < 6)
++ return 0;
++ {
++ struct online_char_data* character;
++ int aid = RFIFOL(fd,2);
++ if ((character = idb_get(online_char_db, aid)) != NULL)
++ { //Kick out this player.
++ if (character->server > -1)
++ { //Kick it from the map server it is on.
++ mapif_disconnectplayer(server_fd[character->server], character->account_id, character->char_id, 2);
++ if (!character->waiting_disconnect)
++ add_timer(gettick()+15000, chardb_waiting_disconnect, character->account_id, 0);
++ character->waiting_disconnect = 1;
++ } else { //Manual kick from char server.
++ struct char_session_data *tsd;
++ int i;
++ for(i = 0; i < fd_max; i++) {
++ if (session[i] && (tsd = (struct char_session_data*)session[i]->session_data) && tsd->account_id == aid)
++ {
++ WFIFOHEAD(fd, 3);
++ WFIFOW(i,0) = 0x81;
++ WFIFOB(i,2) = 2;
++ WFIFOSET(i,3);
++ break;
++ }
++ }
++ if (i == fd_max) //Shouldn't happen, but just in case.
++ set_char_offline(99, aid);
++ }
++ }
++ RFIFOSKIP(fd,6);
++ }
++ break;
+ default: ++ ShowWarning("Unknown packet 0x%04x received from login-server, disconnecting.\n", RFIFOW(fd,0));
+ session[fd]->eof = 1; + return 0; + } +@@ -1824,57 +2306,130 @@ + return 0; + } + +-//-------------------------------- +-// Map-server anti-freeze system +-//-------------------------------- +-int map_anti_freeze_system(int tid, unsigned int tick, int id, int data) { +- int i; ++int request_accreg2(int account_id, int char_id) {
++ if (login_fd > 0) {
++ WFIFOW(login_fd, 0) = 0x272e;
++ WFIFOL(login_fd, 2) = account_id;
++ WFIFOL(login_fd, 6) = char_id;
++ WFIFOSET(login_fd, 10);
++ return 1;
++ }
++ return 0;
++}
+ +- //printf("Entering in map_anti_freeze_system function to check freeze of servers.\n"); +- for(i = 0; i < MAX_MAP_SERVERS; i++) { +- if (server_fd[i] >= 0) {// if map-server is online +- //printf("map_anti_freeze_system: server #%d, flag: %d.\n", i, server_freezeflag[i]); +- if (server_freezeflag[i]-- < 1) { // Map-server anti-freeze system. Counter. 5 ok, 4...0 freezed +- printf("Map-server anti-freeze system: char-server #%d is freezed -> disconnection.\n", i); +- char_log("Map-server anti-freeze system: char-server #%d is freezed -> disconnection." RETCODE, +- i); +- session[server_fd[i]]->eof = 1; ++//Send packet forward to login-server for account saving
++int save_accreg2(unsigned char* buf, int len) {
++ if (login_fd > 0) {
++ WFIFOHEAD(login_fd, len+4);
++ memcpy(WFIFOP(login_fd,4), buf, len);
++ WFIFOW(login_fd,0) = 0x2728;
++ WFIFOW(login_fd,2) = len+4;
++ WFIFOSET(login_fd,len+4);
++ return 1;
+ } ++ return 0;
+ } ++
++//Receive Registry information for a character.
++int char_parse_Registry(int account_id, int char_id, unsigned char *buf, int buf_len) {
++ int i,j,p,len;
++ for (i = 0; i < char_num; i++) {
++ if (char_dat[i].status.account_id == account_id && char_dat[i].status.char_id == char_id)
++ break;
++ }
++ if(i >= char_num) //Character not found?
++ return 1;
++ for(j=0,p=0;j<GLOBAL_REG_NUM && p<buf_len;j++){
++ sscanf(WBUFP(buf,p), "%31c%n",char_dat[i].global[j].str,&len);
++ char_dat[i].global[j].str[len]='\0';
++ p +=len+1; //+1 to skip the '\0' between strings.
++ sscanf(WBUFP(buf,p), "%255c%n",char_dat[i].global[j].value,&len);
++ char_dat[i].global[j].value[len]='\0';
++ p +=len+1;
++ }
++ char_dat[i].global_num = j;
++ return 0;
+ } + ++//Reply to map server with acc reg values.
++int char_account_reg_reply(int fd,int account_id,int char_id) {
++ int i,j,p;
++ WFIFOHEAD(login_fd, GLOBAL_REG_NUM*288 + 13);
++ WFIFOW(fd,0)=0x3804;
++ WFIFOL(fd,4)=account_id;
++ WFIFOL(fd,8)=char_id;
++ WFIFOB(fd,12)=3; //Type 3: char acc reg.
++ for (i = 0;i < char_num; i++) {
++ if (char_dat[i].status.account_id == account_id && char_dat[i].status.char_id == char_id)
++ break;
++ }
++ if(i >= char_num){ //Character not found? Sent empty packet.
++ WFIFOW(fd,2)=13;
++ }else{
++ for (p=13,j = 0; j < char_dat[i].global_num; j++) {
++ if (char_dat[i].global[j].str[0]) {
++ p+= sprintf(WFIFOP(fd,p), "%s", char_dat[i].global[j].str)+1; //We add 1 to consider the '\0' in place.
++ p+= sprintf(WFIFOP(fd,p), "%s", char_dat[i].global[j].value)+1;
++ }
++ }
++ WFIFOW(fd,2)=p;
++ }
++ WFIFOSET(fd,WFIFOW(fd,2));
+ return 0; + } + ++int search_mapserver(unsigned short map, long ip, short port);
++
+ int parse_frommap(int fd) { + int i, j; + int id; ++ RFIFOHEAD(fd);
+ + for(id = 0; id < MAX_MAP_SERVERS; id++) + if (server_fd[id] == fd) + break; +- if (id == MAX_MAP_SERVERS || session[fd]->eof) { ++ if(id==MAX_MAP_SERVERS)
++ session[fd]->eof=1;
++ if(session[fd]->eof){
+ if (id < MAX_MAP_SERVERS) { +- printf("Map-server %d (session #%d) has disconnected.\n", id, fd); +- memset(&server[id], 0, sizeof(struct mmo_map_server)); ++ unsigned char buf[16384];
++ ShowStatus("Map-server %d has disconnected.\n", id);
++ //Notify other map servers that this one is gone. [Skotlex]
++ WBUFW(buf,0) = 0x2b20;
++ WBUFL(buf,4) = server[id].ip;
++ WBUFW(buf,8) = server[id].port;
++ j = 0;
++ for(i = 0; i < MAX_MAP_PER_SERVER; i++)
++ if (server[id].map[i])
++ WBUFW(buf,10+(j++)*4) = server[id].map[i];
++ if (j > 0) {
++ WBUFW(buf,2) = j * 4 + 10;
++ mapif_sendallwos(fd, buf, WBUFW(buf,2));
++ }
+ server_fd[id] = -1; +- for(j = 0; j < char_num; j++) +- if (online_chars[j] == fd) +- online_chars[j] = -1; +- create_online_files(); // update online players files (to remove all online players of this server) ++ online_char_db->foreach(online_char_db,char_db_setoffline,i); //Tag relevant chars as 'in disconnected' server.
+ } +- close(fd); +- delete_session(fd); ++ do_close(fd);
++ create_online_files();
+ return 0; + } + +- while(RFIFOREST(fd) >= 2) { ++ while(RFIFOREST(fd) >= 2 && !session[fd]->eof) {
+ // printf("parse_frommap: connection #%d, packet: 0x%x (with being read: %d bytes).\n", fd, RFIFOW(fd,0), RFIFOREST(fd)); + + switch(RFIFOW(fd,0)) { ++
++ // map-server alive packet
++ case 0x2718:
++ if (RFIFOREST(fd) < 2)
++ return 0;
++ RFIFOSKIP(fd,2);
++ break;
++
+ // request from map-server to reload GM accounts. Transmission to login-server (by Yor) + case 0x2af7: + if (login_fd > 0) { // don't send request if no login-server ++ WFIFOHEAD(login_fd, 2);
+ WFIFOW(login_fd,0) = 0x2709; + WFIFOSET(login_fd, 2); + // printf("char : request from map-server to reload GM accounts -> login-server.\n"); +@@ -1888,36 +2443,41 @@ + return 0; + memset(server[id].map, 0, sizeof(server[id].map)); + j = 0; +- for(i = 4; i < RFIFOW(fd,2); i += 16) { +- memcpy(server[id].map[j], RFIFOP(fd,i), 16); +-// printf("set map %d.%d : %s\n", id, j, server[id].map[j]); ++ for(i = 4; i < RFIFOW(fd,2); i += 4) {
++ server[id].map[j] = RFIFOW(fd,i);
+ j++; + } + { + unsigned char *p = (unsigned char *)&server[id].ip; +- printf("Map-Server %d connected: %d maps, from IP %d.%d.%d.%d port %d.\n", ++ ShowStatus("Map-Server %d connected: %d maps, from IP %d.%d.%d.%d port %d.\n",
+ id, j, p[0], p[1], p[2], p[3], server[id].port); +- printf("Map-server %d loading complete.\n", id); ++ ShowStatus("Map-server %d loading complete.\n", id);
+ char_log("Map-Server %d connected: %d maps, from IP %d.%d.%d.%d port %d. Map-server %d loading complete." RETCODE, + id, j, p[0], p[1], p[2], p[3], server[id].port, id); ++ if (kick_on_disconnect)
++ set_all_offline();
++ if (max_account_id != DEFAULT_MAX_ACCOUNT_ID || max_char_id != DEFAULT_MAX_CHAR_ID)
++ mapif_send_maxid(max_account_id, max_char_id); //Send the current max ids to the server to keep in sync [Skotlex]
+ } ++ WFIFOHEAD(fd, 3 + NAME_LENGTH);
+ WFIFOW(fd,0) = 0x2afb; + WFIFOB(fd,2) = 0; +- memcpy(WFIFOP(fd,3), wisp_server_name, 24); // name for wisp to player +- WFIFOSET(fd,27); ++ memcpy(WFIFOP(fd,3), wisp_server_name, NAME_LENGTH); // name for wisp to player
++ WFIFOSET(fd,3+NAME_LENGTH);
++ //WFIFOSET(fd,27);
+ { + unsigned char buf[16384]; + int x; + if (j == 0) { +- printf("WARNING: Map-Server %d have NO map.\n", id); ++ ShowWarning("Map-Server %d have NO map.\n", id);
+ char_log("WARNING: Map-Server %d have NO map." RETCODE, id); + // Transmitting maps information to the other map-servers + } else { + WBUFW(buf,0) = 0x2b04; +- WBUFW(buf,2) = j * 16 + 10; ++ WBUFW(buf,2) = j * 4 + 10;
+ WBUFL(buf,4) = server[id].ip; + WBUFW(buf,8) = server[id].port; +- memcpy(WBUFP(buf,10), RFIFOP(fd,4), j * 16); ++ memcpy(WBUFP(buf,10), RFIFOP(fd,4), j * 4);
+ mapif_sendallwos(fd, buf, WBUFW(buf,2)); + } + // Transmitting the maps of the other map-servers to the new map-server +@@ -1928,10 +2488,10 @@ + WFIFOW(fd,8) = server[x].port; + j = 0; + for(i = 0; i < MAX_MAP_PER_SERVER; i++) +- if (server[x].map[i][0]) +- memcpy(WFIFOP(fd,10+(j++)*16), server[x].map[i], 16); ++ if (server[x].map[i])
++ WFIFOW(fd,10+(j++)*4) = server[x].map[i];
+ if (j > 0) { +- WFIFOW(fd,2) = j * 16 + 10; ++ WFIFOW(fd,2) = j * 4 + 10;
+ WFIFOSET(fd,WFIFOW(fd,2)); + } + } +@@ -1940,83 +2500,95 @@ + RFIFOSKIP(fd,RFIFOW(fd,2)); + break; + +- // 認証要求 ++ //Packet command is now used for sc_data request. [Skotlex]
+ case 0x2afc: +- if (RFIFOREST(fd) < 22) ++ if (RFIFOREST(fd) < 10)
+ return 0; +- //printf("auth_fifo search: account: %d, char: %d, secure: %08x-%08x\n", RFIFOL(fd,2), RFIFOL(fd,6), RFIFOL(fd,10), RFIFOL(fd,14)); +- for(i = 0; i < AUTH_FIFO_SIZE; i++) { +- if (auth_fifo[i].account_id == RFIFOL(fd,2) && +- auth_fifo[i].char_id == RFIFOL(fd,6) && +- auth_fifo[i].login_id1 == RFIFOL(fd,10) && +-#if CMP_AUTHFIFO_LOGIN2 != 0 +- // here, it's the only area where it's possible that we doesn't know login_id2 (map-server asks just after 0x72 packet, that doesn't given the value) +- (auth_fifo[i].login_id2 == RFIFOL(fd,14) || RFIFOL(fd,14) == 0) && // relate to the versions higher than 18 ++ {
++#ifdef ENABLE_SC_SAVING
++ int aid, cid;
++ struct scdata *data;
++ aid = RFIFOL(fd,2);
++ cid = RFIFOL(fd,6);
+ #endif +- (!check_ip_flag || auth_fifo[i].ip == RFIFOL(fd,18)) && +- !auth_fifo[i].delflag) { +- auth_fifo[i].delflag = 1; +- WFIFOW(fd,0) = 0x2afd; +- WFIFOW(fd,2) = 16 + sizeof(struct mmo_charstatus); +- WFIFOL(fd,4) = RFIFOL(fd,2); +- WFIFOL(fd,8) = auth_fifo[i].login_id2; +- WFIFOL(fd,12) = (unsigned long)auth_fifo[i].connect_until_time; +- char_dat[auth_fifo[i].char_pos].sex = auth_fifo[i].sex; +- memcpy(WFIFOP(fd,16), &char_dat[auth_fifo[i].char_pos], sizeof(struct mmo_charstatus)); ++ RFIFOSKIP(fd, 10);
++#ifdef ENABLE_SC_SAVING
++ data = status_search_scdata(aid, cid);
++ if (data->count > 0)
++ { //Deliver status change data.
++ int i;
++
++ WFIFOW(fd,0) = 0x2b1d;
++ WFIFOW(fd,2) = 14 + data->count*sizeof(struct status_change_data);
++ WFIFOL(fd,4) = aid;
++ WFIFOL(fd,8) = cid;
++ WFIFOW(fd,12) = data->count;
++ for (i = 0; i < data->count; i++)
++ memcpy(WFIFOP(fd,14+i*sizeof(struct status_change_data)), &data->data[i], sizeof(struct status_change_data));
+ WFIFOSET(fd, WFIFOW(fd,2)); +- //printf("auth_fifo search success (auth #%d, account %d, character: %d).\n", i, RFIFOL(fd,2), RFIFOL(fd,6)); +- break; ++ status_delete_scdata(aid, cid); //Data sent, so it needs be discarded now.
+ } ++#endif
++ break;
+ } +- if (i == AUTH_FIFO_SIZE) { +- WFIFOW(fd,0) = 0x2afe; +- WFIFOL(fd,2) = RFIFOL(fd,2); +- WFIFOSET(fd,6); +- printf("auth_fifo search error! account %d not authentified.\n", RFIFOL(fd,2)); ++
++ //set MAP user count
++ case 0x2afe:
++ if (RFIFOREST(fd) < 4)
++ return 0;
++ if (RFIFOW(fd,2) != server[id].users) {
++ server[id].users = RFIFOW(fd,2);
++ ShowInfo("User Count: %d (Server: %d)\n", server[id].users, id);
+ } +- RFIFOSKIP(fd,22); ++ RFIFOSKIP(fd, 4);
+ break; +- +- // MAPサーバー上のユーザー数受信 ++ //set MAP users
+ case 0x2aff: + if (RFIFOREST(fd) < 6 || RFIFOREST(fd) < RFIFOW(fd,2)) + return 0; + server[id].users = RFIFOW(fd,4); +- if(anti_freeze_enable) +- server_freezeflag[id] = 5; // Map anti-freeze system. Counter. 5 ok, 4...0 freezed +- // remove all previously online players of the server +- for(i = 0; i < char_num; i++) +- if (online_chars[i] == id) +- online_chars[i] = -1; +- // add online players in the list by [Yor] ++ // add online players in the list by [Yor], adapted to use dbs by [Skotlex]
++ j = 0;
++ online_char_db->foreach(online_char_db,char_db_setoffline,id); //Set all chars from this server as 'unknown'
+ for(i = 0; i < server[id].users; i++) { +- int char_id = RFIFOL(fd,6+i*4); +- for(j = 0; j < char_num; j++) +- if (char_dat[j].char_id == char_id) { +- online_chars[j] = id; +- //printf("%d\n", char_id); +- break; ++ int aid, cid;
++ struct online_char_data* character;
++ aid = RFIFOL(fd,6+i*8);
++ cid = RFIFOL(fd,6+i*8+4);
++ character = idb_ensure(online_char_db, aid, create_online_char_data);
++ if (online_check && character->server > -1 && character->server != id)
++ {
++ ShowNotice("Set map user: Character (%d:%d) marked on map server %d, but map server %d claims to have (%d:%d) online!\n",
++ character->account_id, character->char_id, character->server, id, aid, cid);
++ mapif_disconnectplayer(server_fd[character->server], character->account_id, character->char_id, 2);
+ } ++ character->char_id = cid;
++ character->server = id;
+ } + if (update_online < time(NULL)) { // Time is done + update_online = time(NULL) + 8; + create_online_files(); // only every 8 sec. (normally, 1 server send users every 5 sec.) Don't update every time, because that takes time, but only every 2 connection. + // it set to 8 sec because is more than 5 (sec) and if we have more than 1 map-server, informations can be received in shifted. + } +- RFIFOSKIP(fd,6+i*4); ++ //If any chars remain in -2, they will be cleaned in the cleanup timer.
++ RFIFOSKIP(fd,6+i*8);
+ break; + + // キャラデータ保存 ++ // Recieve character data from map-server
+ case 0x2b01: + if (RFIFOREST(fd) < 4 || RFIFOREST(fd) < RFIFOW(fd,2)) + return 0; + for(i = 0; i < char_num; i++) { +- if (char_dat[i].account_id == RFIFOL(fd,4) && +- char_dat[i].char_id == RFIFOL(fd,8)) ++ if (char_dat[i].status.account_id == RFIFOL(fd,4) &&
++ char_dat[i].status.char_id == RFIFOL(fd,8))
+ break; + } + if (i != char_num) +- memcpy(&char_dat[i], RFIFOP(fd,12), sizeof(struct mmo_charstatus)); ++ memcpy(&char_dat[i].status, RFIFOP(fd,13), sizeof(struct mmo_charstatus));
++ if (RFIFOB(fd,12)) { //Flag, set character offline. [Skotlex]
++ set_char_offline(RFIFOL(fd,8),RFIFOL(fd,4));
++ }
+ RFIFOSKIP(fd,RFIFOW(fd,2)); + break; + +@@ -2026,7 +2598,6 @@ + return 0; + if (auth_fifo_pos >= AUTH_FIFO_SIZE) + auth_fifo_pos = 0; +- //printf("auth_fifo set (auth #%d) - account: %d, secure: %08x-%08x\n", auth_fifo_pos, RFIFOL(fd,2), RFIFOL(fd,6), RFIFOL(fd,10)); + auth_fifo[auth_fifo_pos].account_id = RFIFOL(fd,2); + auth_fifo[auth_fifo_pos].char_id = 0; + auth_fifo[auth_fifo_pos].login_id1 = RFIFOL(fd,6); +@@ -2043,35 +2614,58 @@ + RFIFOSKIP(fd,18); + break; + +- // マップサーバー間移動要求 ++ // request "change map server"
+ case 0x2b05: +- if (RFIFOREST(fd) < 49) ++ if (RFIFOREST(fd) < 35)
+ return 0; +- if (auth_fifo_pos >= AUTH_FIFO_SIZE) +- auth_fifo_pos = 0; +- WFIFOW(fd,0) = 0x2b06; +- memcpy(WFIFOP(fd,2), RFIFOP(fd,2), 42); +- //printf("auth_fifo set (auth#%d) - account: %d, secure: 0x%08x-0x%08x\n", auth_fifo_pos, RFIFOL(fd,2), RFIFOL(fd,6), RFIFOL(fd,10)); +- auth_fifo[auth_fifo_pos].account_id = RFIFOL(fd,2); +- auth_fifo[auth_fifo_pos].char_id = RFIFOL(fd,14); +- auth_fifo[auth_fifo_pos].login_id1 = RFIFOL(fd,6); +- auth_fifo[auth_fifo_pos].login_id2 = RFIFOL(fd,10); +- auth_fifo[auth_fifo_pos].delflag = 0; +- auth_fifo[auth_fifo_pos].sex = RFIFOB(fd,44); +- auth_fifo[auth_fifo_pos].connect_until_time = 0; // unlimited/unknown time by default (not display in map-server) +- auth_fifo[auth_fifo_pos].ip = RFIFOL(fd,45); +- for(i = 0; i < char_num; i++) +- if (char_dat[i].account_id == RFIFOL(fd,2) && +- char_dat[i].char_id == RFIFOL(fd,14)) { +- auth_fifo[auth_fifo_pos].char_pos = i; +- auth_fifo_pos++; +- WFIFOL(fd,6) = 0; ++ {
++ unsigned short name;
++ int map_id, map_fd = -1, i;
++ struct online_char_data* data;
++ struct mmo_charstatus* char_data;
++
++ name = RFIFOW(fd,18);
++ map_id = search_mapserver(name, RFIFOL(fd,24), RFIFOW(fd,28)); //Locate mapserver by ip and port.
++ if (map_id >= 0)
++ map_fd = server_fd[map_id];
++ for(i = 0; i < char_num; i++) {
++ if (char_dat[i].status.account_id == RFIFOL(fd,2) &&
++ char_dat[i].status.char_id == RFIFOL(fd,14))
+ break; + } +- if (i == char_num) +- WFIFOW(fd,6) = 1; +- WFIFOSET(fd,44); +- RFIFOSKIP(fd,49); ++ char_data = i< char_num? &char_dat[i].status:NULL;
++ //Tell the new map server about this player using Kevin's new auth packet. [Skotlex]
++ if (map_fd>=0 && session[map_fd] && char_data)
++ { //Send the map server the auth of this player.
++ //Update the "last map" as this is where the player must be spawned on the new map server.
++ char_data->last_point.map = RFIFOW(fd,18);
++ char_data->last_point.x = RFIFOW(fd,20);
++ char_data->last_point.y = RFIFOW(fd,22);
++
++ WFIFOW(map_fd,0) = 0x2afd;
++ WFIFOW(map_fd,2) = 20 + sizeof(struct mmo_charstatus);
++ WFIFOL(map_fd,4) = RFIFOL(fd, 2); //Account ID
++ WFIFOL(map_fd,8) = RFIFOL(fd, 6); //Login1
++ WFIFOL(map_fd,16) = RFIFOL(fd,10); //Login2
++ WFIFOL(map_fd,12) = (unsigned long)0; //TODO: connect_until_time, how do I figure it out right now?
++ memcpy(WFIFOP(map_fd,20), char_data, sizeof(struct mmo_charstatus));
++ WFIFOSET(map_fd, WFIFOW(map_fd,2));
++ data = idb_ensure(online_char_db, RFIFOL(fd, 2), create_online_char_data);
++ data->char_id = char_data->char_id;
++ data->server = map_id; //Update server where char is.
++
++ //Reply with an ack.
++ WFIFOW(fd, 0) = 0x2b06;
++ memcpy(WFIFOP(fd,2), RFIFOP(fd,2), 28);
++ WFIFOSET(fd, 30);
++ } else { //Reply with nak
++ WFIFOW(fd, 0) = 0x2b06;
++ memcpy(WFIFOP(fd,2), RFIFOP(fd,2), 28);
++ WFIFOL(fd, 6) = 0; //Set login1 to 0.
++ WFIFOSET(fd, 30);
++ }
++ RFIFOSKIP(fd, 35);
++ }
+ break; + + // キャラ名検索 +@@ -2079,16 +2673,17 @@ + if (RFIFOREST(fd) < 6) + return 0; + for(i = 0; i < char_num; i++) { +- if (char_dat[i].char_id == RFIFOL(fd,2)) ++ if (char_dat[i].status.char_id == RFIFOL(fd,2))
+ break; + } + WFIFOW(fd,0) = 0x2b09; + WFIFOL(fd,2) = RFIFOL(fd,2); + if (i != char_num) +- memcpy(WFIFOP(fd,6), char_dat[i].name, 24); ++ memcpy(WFIFOP(fd,6), char_dat[i].status.name, NAME_LENGTH);
+ else +- memcpy(WFIFOP(fd,6), unknown_char_name, 24); +- WFIFOSET(fd,30); ++ memcpy(WFIFOP(fd,6), unknown_char_name, NAME_LENGTH);
++ WFIFOSET(fd,6+NAME_LENGTH);
++ //WFIFOSET(fd,30);
+ RFIFOSKIP(fd,6); + break; + +@@ -2127,10 +2722,10 @@ + if (RFIFOREST(fd) < 44) + return 0; + { +- char character_name[24]; ++ char character_name[NAME_LENGTH];
+ int acc = RFIFOL(fd,2); // account_id of who ask (-1 if nobody) +- memcpy(character_name, RFIFOP(fd,6), 24); +- character_name[sizeof(character_name) -1] = '\0'; ++ memcpy(character_name, RFIFOP(fd,6), NAME_LENGTH-1);
++ character_name[NAME_LENGTH -1] = '\0';
+ // prepare answer + WFIFOW(fd,0) = 0x2b0f; // answer + WFIFOL(fd,2) = acc; // who want do operation +@@ -2138,14 +2733,15 @@ + // search character + i = search_character_index(character_name); + if (i >= 0) { +- memcpy(WFIFOP(fd,6), search_character_name(i), 24); // put correct name if found +- WFIFOW(fd,32) = 0; // answer: 0-login-server resquest done, 1-player not found, 2-gm level too low, 3-login-server offline ++ memcpy(WFIFOP(fd,6), search_character_name(i), NAME_LENGTH); // put correct name if found
++ WFIFOW(fd,6+NAME_LENGTH) = 0; // answer: 0-login-server resquest done, 1-player not found, 2-gm level too low, 3-login-server offline
++ //WFIFOW(fd,32) = 0; // answer: 0-login-server resquest done, 1-player not found, 2-gm level too low, 3-login-server offline
+ switch(RFIFOW(fd, 30)) { + case 1: // block +- if (acc == -1 || isGM(acc) >= isGM(char_dat[i].account_id)) { ++ if (acc == -1 || isGM(acc) >= isGM(char_dat[i].status.account_id)) {
+ if (login_fd > 0) { // don't send request if no login-server + WFIFOW(login_fd,0) = 0x2724; +- WFIFOL(login_fd,2) = char_dat[i].account_id; // account value ++ WFIFOL(login_fd,2) = char_dat[i].status.account_id; // account value
+ WFIFOL(login_fd,6) = 5; // status of the account + WFIFOSET(login_fd, 10); + // printf("char : status -> login: account %d, status: %d \n", char_dat[i].account_id, 5); +@@ -2155,10 +2751,10 @@ + WFIFOW(fd,32) = 2; // answer: 0-login-server resquest done, 1-player not found, 2-gm level too low, 3-login-server offline + break; + case 2: // ban +- if (acc == -1 || isGM(acc) >= isGM(char_dat[i].account_id)) { ++ if (acc == -1 || isGM(acc) >= isGM(char_dat[i].status.account_id)) {
+ if (login_fd > 0) { // don't send request if no login-server + WFIFOW(login_fd, 0) = 0x2725; +- WFIFOL(login_fd, 2) = char_dat[i].account_id; // account value ++ WFIFOL(login_fd, 2) = char_dat[i].status.account_id; // account value
+ WFIFOW(login_fd, 6) = RFIFOW(fd,32); // year + WFIFOW(login_fd, 8) = RFIFOW(fd,34); // month + WFIFOW(login_fd,10) = RFIFOW(fd,36); // day +@@ -2174,10 +2770,10 @@ + WFIFOW(fd,32) = 2; // answer: 0-login-server resquest done, 1-player not found, 2-gm level too low, 3-login-server offline + break; + case 3: // unblock +- if (acc == -1 || isGM(acc) >= isGM(char_dat[i].account_id)) { ++ if (acc == -1 || isGM(acc) >= isGM(char_dat[i].status.account_id)) {
+ if (login_fd > 0) { // don't send request if no login-server + WFIFOW(login_fd,0) = 0x2724; +- WFIFOL(login_fd,2) = char_dat[i].account_id; // account value ++ WFIFOL(login_fd,2) = char_dat[i].status.account_id; // account value
+ WFIFOL(login_fd,6) = 0; // status of the account + WFIFOSET(login_fd, 10); + // printf("char : status -> login: account %d, status: %d \n", char_dat[i].account_id, 0); +@@ -2187,10 +2783,10 @@ + WFIFOW(fd,32) = 2; // answer: 0-login-server resquest done, 1-player not found, 2-gm level too low, 3-login-server offline + break; + case 4: // unban +- if (acc == -1 || isGM(acc) >= isGM(char_dat[i].account_id)) { ++ if (acc == -1 || isGM(acc) >= isGM(char_dat[i].status.account_id)) {
+ if (login_fd > 0) { // don't send request if no login-server + WFIFOW(login_fd, 0) = 0x272a; +- WFIFOL(login_fd, 2) = char_dat[i].account_id; // account value ++ WFIFOL(login_fd, 2) = char_dat[i].status.account_id; // account value
+ WFIFOSET(login_fd, 6); + // printf("char : status -> login: account %d, unban request\n", char_dat[i].account_id); + } else +@@ -2199,10 +2795,10 @@ + WFIFOW(fd,32) = 2; // answer: 0-login-server resquest done, 1-player not found, 2-gm level too low, 3-login-server offline + break; + case 5: // changesex +- if (acc == -1 || isGM(acc) >= isGM(char_dat[i].account_id)) { ++ if (acc == -1 || isGM(acc) >= isGM(char_dat[i].status.account_id)) {
+ if (login_fd > 0) { // don't send request if no login-server + WFIFOW(login_fd, 0) = 0x2727; +- WFIFOL(login_fd, 2) = char_dat[i].account_id; // account value ++ WFIFOL(login_fd, 2) = char_dat[i].status.account_id; // account value
+ WFIFOSET(login_fd, 6); + // printf("char : status -> login: account %d, change sex request\n", char_dat[i].account_id); + } else +@@ -2213,12 +2809,14 @@ + } + } else { + // character name not found +- memcpy(WFIFOP(fd,6), character_name, 24); +- WFIFOW(fd,32) = 1; // answer: 0-login-server resquest done, 1-player not found, 2-gm level too low, 3-login-server offline ++ memcpy(WFIFOP(fd,6), character_name, NAME_LENGTH);
++ WFIFOW(fd,8+NAME_LENGTH) = 1; // answer: 0-login-server resquest done, 1-player not found, 2-gm level too low, 3-login-server offline
++ //WFIFOW(fd,32) = 1; // answer: 0-login-server resquest done, 1-player not found, 2-gm level too low, 3-login-server offline
+ } + // send answer if a player ask, not if the server ask + if (acc != -1) { +- WFIFOSET(fd, 34); ++ //WFIFOSET(fd, 34);
++ WFIFOSET(fd, 10+NAME_LENGTH);
+ } + RFIFOSKIP(fd, 44); + break; +@@ -2226,34 +2824,157 @@ + + // case 0x2b0f: not more used (available for futur usage) + +- // account_reg保存要求 +- case 0x2b10: +- if (RFIFOREST(fd) < 4 || RFIFOREST(fd) < RFIFOW(fd,2)) ++ //Packet 0x2b10 deprecated in favor of packet 0x3004 for registry saving. [Skotlex]
++ //case 0x2b10:
++
++ // Recieve rates [Wizputer]
++ case 0x2b16:
++ if (RFIFOREST(fd) < 6 || RFIFOREST(fd) < RFIFOW(fd,8))
++ return 0;
++ // Txt doesn't need this packet, so just skip it
++ RFIFOSKIP(fd,RFIFOW(fd,8));
++ break;
++
++ // Character disconnected set online 0 [Wizputer]
++ case 0x2b17:
++ if (RFIFOREST(fd) < 6)
++ return 0;
++ //printf("Setting %d char offline\n",RFIFOL(fd,2));
++ set_char_offline(RFIFOL(fd,2),RFIFOL(fd,6));
++ RFIFOSKIP(fd,10);
++ break;
++
++ // Reset all chars to offline [Wizputer]
++ case 0x2b18:
++ ShowNotice("Map server [%d] requested to set all characters offline.\n", id);
++ set_all_offline();
++ RFIFOSKIP(fd,2);
++ break;
++
++ // Character set online [Wizputer]
++ case 0x2b19:
++ if (RFIFOREST(fd) < 6)
++ return 0;
++ //printf("Setting %d char online\n",RFIFOL(fd,2));
++ set_char_online(id, RFIFOL(fd,2),RFIFOL(fd,6));
++ RFIFOSKIP(fd,10);
++ break;
++
++ // Request sending of fame list
++ case 0x2b1a:
++ if (RFIFOREST(fd) < 2)
+ return 0; + { +- struct global_reg reg[ACCOUNT_REG2_NUM]; +- int p, acc; +- acc = RFIFOL(fd,4); +- for(p = 8, j = 0; p < RFIFOW(fd,2) && j < ACCOUNT_REG2_NUM; p += 36, j++) { +- memcpy(reg[j].str, RFIFOP(fd,p), 32); +- reg[j].value = RFIFOL(fd, p+32); ++ int i, j, k, len = 8;
++ unsigned char buf[32000];
++ struct fame_list fame_item;
++ //struct mmo_charstatus *dat;
++ //dat = (struct mmo_charstatus *)aCalloc(char_num, sizeof(struct mmo_charstatus *));
++ CREATE_BUFFER(id, int, char_num);
++
++ // copy character list into buffer
++ //for (i = 0; i < char_num; i++)
++ // dat[i] = char_dat[i];
++ // sort according to fame
++ // qsort(dat, char_num, sizeof(struct mmo_charstatus *), sort_fame);
++
++ for(i = 0; i < char_num; i++) {
++ id[i] = i;
++ for(j = 0; j < i; j++) {
++ if (char_dat[i].status.fame > char_dat[id[j]].status.fame) {
++ for(k = i; k > j; k--)
++ id[k] = id[k-1];
++ id[j] = i; // id[i]
++ break;
+ } +- set_account_reg2(acc, j, reg); +- // loginサーバーへ送る +- if (login_fd > 0) { // don't send request if no login-server +- WFIFOW(login_fd, 0) = 0x2728; +- memcpy(WFIFOP(login_fd,0), RFIFOP(fd,0), RFIFOW(fd,2)); +- WFIFOSET(login_fd, WFIFOW(login_fd,2)); + } +- // ワールドへの同垢ログインがなければmapサーバーに送る必要はない +- //memcpy(buf, RFIFOP(fd,0), RFIFOW(fd,2)); +- //WBUFW(buf,0) = 0x2b11; +- //mapif_sendall(buf, WBUFW(buf,2)); ++ }
++
++ // starting to send to map
++ WBUFW(buf,0) = 0x2b1b;
++ // add list for blacksmiths
++ for (i = 0, j = 0; i < char_num && j < fame_list_size_smith; i++) {
++ if (char_dat[id[i]].status.fame && (char_dat[id[i]].status.class_ == 10 ||
++ char_dat[id[i]].status.class_ == 4011 ||
++ char_dat[id[i]].status.class_ == 4033))
++ {
++ fame_item.id = char_dat[id[i]].status.char_id;
++ fame_item.fame = char_dat[id[i]].status.fame;
++ strncpy(fame_item.name, char_dat[id[i]].status.name, NAME_LENGTH);
++
++ memcpy(WBUFP(buf, len), &fame_item, sizeof(struct fame_list));
++ len += sizeof(struct fame_list);
++ j++;
++ }
++ }
++ // add blacksmith's block length
++ WBUFW(buf, 6) = len;
++
++ // add list for alchemists
++ for (i = 0, j = 0; i < char_num && j < fame_list_size_chemist; i++) {
++ if (char_dat[id[i]].status.fame && (char_dat[id[i]].status.class_ == 18 ||
++ char_dat[id[i]].status.class_ == 4019 ||
++ char_dat[id[i]].status.class_ == 4041))
++ {
++ fame_item.id = char_dat[id[i]].status.char_id;
++ fame_item.fame = char_dat[id[i]].status.fame;
++ strncpy(fame_item.name, char_dat[id[i]].status.name, NAME_LENGTH);
++
++ memcpy(WBUFP(buf, len), &fame_item, sizeof(struct fame_list));
++ len += sizeof(struct fame_list);
++ j++;
++ }
++ }
++ // add alchemist's block length
++ WBUFW(buf, 4) = len;
++
++ // adding list for taekwons
++ for (i = 0, j = 0; i < char_num && j < fame_list_size_taekwon; i++) {
++ if (char_dat[id[i]].status.fame && char_dat[id[i]].status.class_ == 4046)
++ {
++ fame_item.id = char_dat[id[i]].status.char_id;
++ fame_item.fame = char_dat[id[i]].status.fame;
++ strncpy(fame_item.name, char_dat[id[i]].status.name, NAME_LENGTH);
++
++ memcpy(WBUFP(buf, len), &fame_item, sizeof(struct fame_list));
++ len += sizeof(struct fame_list);
++ j++;
++ }
++ }
++ // add total packet length
++ WBUFW(buf, 2) = len;
++
++ // sending to all maps
++ mapif_sendall(buf, len);
++ // done!
++ //aFree(dat);
++ DELETE_BUFFER(id);
++ RFIFOSKIP(fd,2);
++ break;
++ }
++ //Request to save status change data. [Skotlex]
++ case 0x2b1c:
++ if (RFIFOREST(fd) < 4 || RFIFOREST(fd) < RFIFOW(fd,2))
++ return 0;
++ {
++#ifdef ENABLE_SC_SAVING
++ int count, aid, cid, i;
++ struct scdata *data;
++ aid = RFIFOL(fd, 4);
++ cid = RFIFOL(fd, 8);
++ count = RFIFOW(fd, 12);
++ data = status_search_scdata(aid, cid);
++ if (data->count != count)
++ {
++ data->count = count;
++ data->data = aRealloc(data->data, count*sizeof(struct status_change_data));
++ }
++ for (i = 0; i < count; i++)
++ memcpy (&data->data[i], RFIFOP(fd, 14+i*sizeof(struct status_change_data)), sizeof(struct status_change_data));
++#endif
+ RFIFOSKIP(fd, RFIFOW(fd,2)); +-// printf("char: save_account_reg (from map)\n"); + break; + } +- + default: + // inter server処理に渡す + { +@@ -2264,7 +2985,7 @@ + return 0; + } + // inter server処理でもない場合は切断 +- printf("char: unknown packet 0x%04x (%d bytes to read in buffer)! (from map).\n", RFIFOW(fd,0), RFIFOREST(fd)); ++ ShowError("Unknown packet 0x%04x from map server, disconnecting.\n", RFIFOW(fd,0));
+ session[fd]->eof = 1; + return 0; + } +@@ -2272,28 +2993,20 @@ + return 0; + } + +-int search_mapserver(char *map) { ++int search_mapserver(unsigned short map, long ip, short port) {
+ int i, j; +- char temp_map[16]; +- int temp_map_len; +- +-// printf("Searching the map-server for map '%s'... ", map); +- strncpy(temp_map, map, sizeof(temp_map)); +- temp_map[sizeof(temp_map)-1] = '\0'; +- if (strchr(temp_map, '.') != NULL) +- temp_map[strchr(temp_map, '.') - temp_map + 1] = '\0'; // suppress the '.gat', but conserve the '.' to be sure of the name of the map + +- temp_map_len = strlen(temp_map); + for(i = 0; i < MAX_MAP_SERVERS; i++) + if (server_fd[i] >= 0) +- for (j = 0; server[i].map[j][0]; j++) +- //printf("%s : %s = %d\n", server[i].map[j], map, strncmp(server[i].map[j], temp_map, temp_map_len)); +- if (strncmp(server[i].map[j], temp_map, temp_map_len) == 0) { +-// printf("found -> server #%d.\n", i); ++ for (j = 0; server[i].map[j]; j++)
++ if (server[i].map[j] == map) {
++ if (ip > 0 && server[i].ip != ip)
++ continue;
++ if (port > 0 && server[i].port != port)
++ continue;
+ return i; + } + +-// printf("not found.\n"); + return -1; + } + +@@ -2302,48 +3015,81 @@ + return inter_mapif_init(fd); + } + +-//----------------------------------------------------- +-// Test to know if an IP come from LAN or WAN. by [Yor] +-//----------------------------------------------------- +-int lan_ip_check(unsigned char *p){ ++//--------------------------------------------
++// Test to know if an IP come from LAN or WAN.
++// Rewrote: Adnvanced subnet check [LuzZza]
++//--------------------------------------------
++int lan_subnetcheck(long *p) {
++
+ int i; +- int lancheck = 1; ++ unsigned char *sbn, *msk, *src = (unsigned char *)p;
+ +-// printf("lan_ip_check: to compare: %d.%d.%d.%d, network: %d.%d.%d.%d/%d.%d.%d.%d\n", +-// p[0], p[1], p[2], p[3], +-// subneti[0], subneti[1], subneti[2], subneti[3], +-// subnetmaski[0], subnetmaski[1], subnetmaski[2], subnetmaski[3]); +- for(i = 0; i < 4; i++) { +- if ((subneti[i] & subnetmaski[i]) != (p[i] & subnetmaski[i])) { +- lancheck = 0; +- break; ++ for(i=0; i<subnet_count; i++) {
++
++ if((subnet[i].subnet & subnet[i].mask) == (*p & subnet[i].mask)) {
++
++ sbn = (unsigned char *)&subnet[i].subnet;
++ msk = (unsigned char *)&subnet[i].mask;
++
++ ShowInfo("Subnet check [%u.%u.%u.%u]: Matches "CL_CYAN"%u.%u.%u.%u/%u.%u.%u.%u"CL_RESET"\n",
++ src[0], src[1], src[2], src[3], sbn[0], sbn[1], sbn[2], sbn[3], msk[0], msk[1], msk[2], msk[3]);
++
++ return subnet[i].map_ip;
+ } + } +- printf("LAN test (result): %s source\033[0m.\n", (lancheck) ? "\033[1;36mLAN" : "\033[1;32mWAN"); +- return lancheck; ++
++ ShowInfo("Subnet check [%u.%u.%u.%u]: "CL_CYAN"WAN"CL_RESET"\n", src[0], src[1], src[2], src[3]);
++ return 0;
+ } + + int parse_char(int fd) { ++
+ int i, ch; ++ unsigned short cmd;
+ char email[40]; ++ int map_fd;
+ struct char_session_data *sd; + unsigned char *p = (unsigned char *) &session[fd]->client_addr.sin_addr; ++ long subnet_map_ip;
++
++ RFIFOHEAD(fd);
++
++ sd = (struct char_session_data*)session[fd]->session_data;
+ +- if (login_fd < 0 || session[fd]->eof) { // disconnect any player (already connected to char-server or coming back from map-server) if login-server is diconnected. ++ if (login_fd < 0)
++ session[fd]->eof = 1;
++ if(session[fd]->eof) { // disconnect any player (already connected to char-server or coming back from map-server) if login-server is diconnected.
+ if (fd == login_fd) + login_fd = -1; +- close(fd); +- delete_session(fd); ++ if (sd != NULL)
++ {
++ struct online_char_data* data = idb_get(online_char_db, sd->account_id);
++ if (!data || data->server== -1) //If it is not in any server, send it offline. [Skotlex]
++ set_char_offline(99,sd->account_id);
++ }
++ do_close(fd);
+ return 0; + } + +- sd = session[fd]->session_data; ++ while(RFIFOREST(fd) >= 2 && !session[fd]->eof) {
++ cmd = RFIFOW(fd,0);
++ // crc32のスキップ用
++ if( sd==NULL && // 未ログインor管理パケット
++ RFIFOREST(fd)>=4 && // 最低バイト数制限 & 0x7530,0x7532管理パケ除去
++ RFIFOREST(fd)<=21 && // 最大バイト数制限 & サーバーログイン除去
++ cmd!=0x20b && // md5通知パケット除去
++ (RFIFOREST(fd)<6 || RFIFOW(fd,4)==0x65) ){ // 次に何かパケットが来てるなら、接続でないとだめ
++ RFIFOSKIP(fd,4);
++ cmd = RFIFOW(fd,0);
++ ShowDebug("parse_char : %d crc32 skipped\n",fd);
++ if(RFIFOREST(fd)==0)
++ return 0;
++ }
+ +- while (RFIFOREST(fd) >= 2) { +-// if (RFIFOW(fd,0) < 30000) +-// printf("parse_char: connection #%d, packet: 0x%x (with being read: %d bytes).\n", fd, RFIFOW(fd,0), RFIFOREST(fd)); ++//For use in packets that depend on an sd being present [Skotlex]
++#define FIFOSD_CHECK(rest) { if(RFIFOREST(fd) < rest) return 0; if (sd==NULL) { RFIFOSKIP(fd,rest); return 0; } }
+ +- switch(RFIFOW(fd,0)) { ++ switch(cmd){
+ case 0x20b: //20040622暗号化ragexe対応 + if (RFIFOREST(fd) < 19) + return 0; +@@ -2356,13 +3102,15 @@ + { + int GM_value; + if ((GM_value = isGM(RFIFOL(fd,2)))) +- printf("Account Logged On; Account ID: %d (GM level %d).\n", RFIFOL(fd,2), GM_value); ++ ShowInfo("Account Logged On; Account ID: %d (GM level %d).\n", RFIFOL(fd,2), GM_value);
+ else +- printf("Account Logged On; Account ID: %d.\n", RFIFOL(fd,2)); ++ ShowInfo("Account Logged On; Account ID: %d.\n", RFIFOL(fd,2));
+ if (sd == NULL) { +- sd = session[fd]->session_data = calloc(sizeof(struct char_session_data), 1); +- memset(sd, 0, sizeof(struct char_session_data)); +- memcpy(sd->email, "no mail", 40); // put here a mail without '@' to refuse deletion if we don't receive the e-mail ++ sd = (struct char_session_data*)aCalloc(sizeof(struct char_session_data), 1);
++ session[fd]->session_data = sd;
++
++// memset(sd, 0, sizeof(struct char_session_data)); aCalloc does this [Skotlex]
++ strncpy(sd->email, "no mail", 40); // put here a mail without '@' to refuse deletion if we don't receive the e-mail
+ sd->connect_until_time = 0; // unknow or illimited (not displaying on map-server) + } + sd->account_id = RFIFOL(fd,2); +@@ -2370,6 +3118,7 @@ + sd->login_id2 = RFIFOL(fd,10); + sd->sex = RFIFOB(fd,16); + // send back account_id ++ WFIFOHEAD(fd, 4);
+ WFIFOL(fd,0) = RFIFOL(fd,2); + WFIFOSET(fd,4); + // search authentification +@@ -2382,6 +3131,44 @@ + (!check_ip_flag || auth_fifo[i].ip == session[fd]->client_addr.sin_addr.s_addr) && + auth_fifo[i].delflag == 2) { + auth_fifo[i].delflag = 1; ++
++ if (online_check)
++ { // check if character is not online already. [Skotlex]
++ struct online_char_data* character;
++ character = idb_get(online_char_db, sd->account_id);
++
++ if (character)
++ {
++ if(character->server > -1)
++ { //Kick it from the map server it is on.
++ mapif_disconnectplayer(server_fd[character->server], character->account_id, character->char_id, 2);
++ if (!character->waiting_disconnect)
++ add_timer(gettick()+20000, chardb_waiting_disconnect, character->account_id, 0);
++ character->waiting_disconnect = 1;
++ /* Not a good idea because this would trigger when you do a char-change from the map server! [Skotlex]
++ } else { //Manual kick from char server.
++ struct char_session_data *tsd;
++ int i;
++ for(i = 0; i < fd_max; i++) {
++ if (session[i] && i!=fd && (tsd = (struct char_session_data*)session[i]->session_data) && tsd->account_id == sd->account_id)
++ {
++ WFIFOW(i,0) = 0x81;
++ WFIFOB(i,2) = 2;
++ WFIFOSET(i,3);
++ break;
++ }
++ }
++ if (i == fd_max) //Shouldn't happen, but just in case.
++ set_char_offline(99, sd->account_id);
++ */
++ WFIFOW(fd,0) = 0x81;
++ WFIFOB(fd,2) = 8;
++ WFIFOSET(fd,3);
++ break;
++ }
++ }
++ }
++
+ if (max_connect_user == 0 || count_users() < max_connect_user) { + if (login_fd > 0) { // don't send request if no login-server + // request to login-server to obtain e-mail/time limit +@@ -2421,89 +3208,103 @@ + break; + + case 0x66: // キャラ選択 +- if (RFIFOREST(fd) < 3) +- return 0; ++ FIFOSD_CHECK(3);
++ {
++ int char_num = RFIFOB(fd,2);
++ struct mmo_charstatus *cd;
++ RFIFOSKIP(fd,3);
+ + // if we activated email creation and email is default email + if (email_creation != 0 && strcmp(sd->email, "a@a.com") == 0 && login_fd > 0) { // to modify an e-mail, login-server must be online ++ WFIFOHEAD(fd, 3);
+ WFIFOW(fd, 0) = 0x70; + WFIFOB(fd, 2) = 0; // 00 = Incorrect Email address + WFIFOSET(fd, 3); +- ++ break;
++ }
+ // otherwise, load the character +- } else { + for (ch = 0; ch < 9; ch++) +- if (sd->found_char[ch] >= 0 && char_dat[sd->found_char[ch]].char_num == RFIFOB(fd,2)) ++ if (sd->found_char[ch] >= 0 && char_dat[sd->found_char[ch]].status.char_num == char_num)
+ break; +- if (ch != 9) { ++ if (ch == 9)
++ { //Not found?? May be forged packet.
++ break;
++ }
++ cd = &char_dat[sd->found_char[ch]].status;
+ char_log("Character Selected, Account ID: %d, Character Slot: %d, Character Name: %s." RETCODE, +- sd->account_id, RFIFOB(fd,2), char_dat[sd->found_char[ch]].name); ++ sd->account_id, char_num, cd->name);
++
++ cd->sex = sd->sex;
++
+ // searching map server +- i = search_mapserver(char_dat[sd->found_char[ch]].last_point.map); ++ i = search_mapserver(cd->last_point.map,-1,-1);
+ // if map is not found, we check major cities + if (i < 0) { +- if ((i = search_mapserver("prontera.gat")) >= 0) { // check is done without 'gat'. +- memcpy(char_dat[sd->found_char[ch]].last_point.map, "prontera.gat", 16); +- char_dat[sd->found_char[ch]].last_point.x = 273; // savepoint coordonates +- char_dat[sd->found_char[ch]].last_point.y = 354; +- } else if ((i = search_mapserver("geffen.gat")) >= 0) { // check is done without 'gat'. +- memcpy(char_dat[sd->found_char[ch]].last_point.map, "geffen.gat", 16); +- char_dat[sd->found_char[ch]].last_point.x = 120; // savepoint coordonates +- char_dat[sd->found_char[ch]].last_point.y = 100; +- } else if ((i = search_mapserver("morocc.gat")) >= 0) { // check is done without 'gat'. +- memcpy(char_dat[sd->found_char[ch]].last_point.map, "morocc.gat", 16); +- char_dat[sd->found_char[ch]].last_point.x = 160; // savepoint coordonates +- char_dat[sd->found_char[ch]].last_point.y = 94; +- } else if ((i = search_mapserver("alberta.gat")) >= 0) { // check is done without 'gat'. +- memcpy(char_dat[sd->found_char[ch]].last_point.map, "alberta.gat", 16); +- char_dat[sd->found_char[ch]].last_point.x = 116; // savepoint coordonates +- char_dat[sd->found_char[ch]].last_point.y = 57; +- } else if ((i = search_mapserver("payon.gat")) >= 0) { // check is done without 'gat'. +- memcpy(char_dat[sd->found_char[ch]].last_point.map, "payon.gat", 16); +- char_dat[sd->found_char[ch]].last_point.x = 87; // savepoint coordonates +- char_dat[sd->found_char[ch]].last_point.y = 117; +- } else if ((i = search_mapserver("izlude.gat")) >= 0) { // check is done without 'gat'. +- memcpy(char_dat[sd->found_char[ch]].last_point.map, "izlude.gat", 16); +- char_dat[sd->found_char[ch]].last_point.x = 94; // savepoint coordonates +- char_dat[sd->found_char[ch]].last_point.y = 103; ++ unsigned short j;
++ ShowWarning("Unable to find map-server for '%s', resorting to sending to a major city.\n", mapindex_id2name(cd->last_point.map));
++ if ((i = search_mapserver((j=mapindex_name2id(MAP_PRONTERA)),-1,-1)) >= 0) {
++ cd->last_point.map = j;
++ cd->last_point.x = 273; // savepoint coordinates
++ cd->last_point.y = 354;
++ } else if ((i = search_mapserver((j=mapindex_name2id(MAP_GEFFEN)),-1,-1)) >= 0) {
++ cd->last_point.map = j;
++ cd->last_point.x = 120; // savepoint coordinates
++ cd->last_point.y = 100;
++ } else if ((i = search_mapserver((j=mapindex_name2id(MAP_MORROC)),-1,-1)) >= 0) {
++ cd->last_point.map = j;
++ cd->last_point.x = 160; // savepoint coordinates
++ cd->last_point.y = 94;
++ } else if ((i = search_mapserver((j=mapindex_name2id(MAP_ALBERTA)),-1,-1)) >= 0) {
++ cd->last_point.map = j;
++ cd->last_point.x = 116; // savepoint coordinates
++ cd->last_point.y = 57;
++ } else if ((i = search_mapserver((j=mapindex_name2id(MAP_PAYON)),-1,-1)) >= 0) {
++ cd->last_point.map = j;
++ cd->last_point.x = 87; // savepoint coordinates
++ cd->last_point.y = 117;
++ } else if ((i = search_mapserver((j=mapindex_name2id(MAP_IZLUDE)),-1,-1)) >= 0) {
++ cd->last_point.map = j;
++ cd->last_point.x = 94; // savepoint coordinates
++ cd->last_point.y = 103;
+ } else { +- int j; + // get first online server (with a map) + i = 0; + for(j = 0; j < MAX_MAP_SERVERS; j++) +- if (server_fd[j] >= 0 && server[j].map[0][0]) { // change save point to one of map found on the server (the first) ++ if (server_fd[j] >= 0 && server[j].map[0]) { // change save point to one of map found on the server (the first)
+ i = j; +- memcpy(char_dat[sd->found_char[ch]].last_point.map, server[j].map[0], 16); +- printf("Map-server #%d found with a map: '%s'.\n", j, server[j].map[0]); +- // coordonates are unknown ++ cd->last_point.map = server[j].map[0];
++ ShowInfo("Map-server #%d found with a map: '%s'.\n", j, mapindex_id2name(server[j].map[0]));
++ // coordinates are unknown
+ break; + } + // if no map-server is connected, we send: server closed + if (j == MAX_MAP_SERVERS) { ++ WFIFOHEAD(fd, 3);
+ WFIFOW(fd,0) = 0x81; +- WFIFOL(fd,2) = 1; // 01 = Server closed ++ WFIFOB(fd,2) = 1; // 01 = Server closed
+ WFIFOSET(fd,3); +- RFIFOSKIP(fd,3); + break; + } + } + } ++ WFIFOHEAD(fd, 28);
+ WFIFOW(fd,0) = 0x71; +- WFIFOL(fd,2) = char_dat[sd->found_char[ch]].char_id; +- memcpy(WFIFOP(fd,6), char_dat[sd->found_char[ch]].last_point.map, 16); +- printf("Character selection '%s' (account: %d, slot: %d).\n", char_dat[sd->found_char[ch]].name, sd->account_id, ch); +- printf("--Send IP of map-server. "); +- if (lan_ip_check(p)) +- WFIFOL(fd, 22) = inet_addr(lan_map_ip); ++ WFIFOL(fd,2) = cd->char_id;
++ memcpy(WFIFOP(fd,6), mapindex_id2name(cd->last_point.map), MAP_NAME_LENGTH);
++ ShowInfo("Character selection '%s' (account: %d, slot: %d).\n", cd->name, sd->account_id, ch);
++
++ // Andvanced subnet check [LuzZza]
++ if((subnet_map_ip = lan_subnetcheck((long *)p)))
++ WFIFOL(fd,22) = subnet_map_ip;
+ else + WFIFOL(fd, 22) = server[i].ip; ++
+ WFIFOW(fd,26) = server[i].port; + WFIFOSET(fd,28); + if (auth_fifo_pos >= AUTH_FIFO_SIZE) + auth_fifo_pos = 0; +- //printf("auth_fifo set #%d - account %d, char: %d, secure: %08x-%08x\n", auth_fifo_pos, sd->account_id, char_dat[sd->found_char[ch]].char_id, sd->login_id1, sd->login_id2); + auth_fifo[auth_fifo_pos].account_id = sd->account_id; +- auth_fifo[auth_fifo_pos].char_id = char_dat[sd->found_char[ch]].char_id; ++ auth_fifo[auth_fifo_pos].char_id = cd->char_id;
+ auth_fifo[auth_fifo_pos].login_id1 = sd->login_id1; + auth_fifo[auth_fifo_pos].login_id2 = sd->login_id2; + auth_fifo[auth_fifo_pos].delflag = 0; +@@ -2511,62 +3312,109 @@ + auth_fifo[auth_fifo_pos].sex = sd->sex; + auth_fifo[auth_fifo_pos].connect_until_time = sd->connect_until_time; + auth_fifo[auth_fifo_pos].ip = session[fd]->client_addr.sin_addr.s_addr; +- auth_fifo_pos++; ++
++ //Send NEW auth packet [Kevin]
++ if ((map_fd = server_fd[i]) < 1 || session[map_fd] == NULL)
++ { //0 Should not be a valid server_fd [Skotlex]
++ ShowError("parse_char: Attempting to write to invalid session %d! Map Server #%d disconnected.\n", map_fd, i);
++ server_fd[i] = -1;
++ memset(&server[i], 0, sizeof(struct mmo_map_server));
++ //Send server closed.
++ WFIFOW(fd,0) = 0x81;
++ WFIFOB(fd,2) = 1; // 01 = Server closed
++ WFIFOSET(fd,3);
++ break;
+ } ++ WFIFOW(map_fd,0) = 0x2afd;
++ WFIFOW(map_fd,2) = 20 + sizeof(struct mmo_charstatus);
++ WFIFOL(map_fd,4) = auth_fifo[auth_fifo_pos].account_id;
++ WFIFOL(map_fd,8) = auth_fifo[auth_fifo_pos].login_id1;
++ WFIFOL(map_fd,16) = auth_fifo[auth_fifo_pos].login_id2;
++ WFIFOL(map_fd,12) = (unsigned long)auth_fifo[auth_fifo_pos].connect_until_time;
++ memcpy(WFIFOP(map_fd,20), cd, sizeof(struct mmo_charstatus));
++ WFIFOSET(map_fd, WFIFOW(map_fd,2));
++
++ set_char_online(i, cd->char_id, cd->account_id);
++ //Sets char online in the party and breaks even share if needed.
++ inter_party_logged(cd->party_id, cd->account_id, cd->char_id);
++
++ auth_fifo_pos++;
+ } +- RFIFOSKIP(fd,3); + break; + + case 0x67: // 作成 +- if (RFIFOREST(fd) < 37) +- return 0; ++ FIFOSD_CHECK(37);
++
++ if(char_new == 0) //turn character creation on/off [Kevin]
++ i = -2;
++ else
+ i = make_new_char(fd, RFIFOP(fd,2)); +- if (i < 0) { ++
++ if(i == -1){ //added some better faile reporting to client on the txt version [Kevin]
++ //already exists
++ WFIFOHEAD(fd, 3);
+ WFIFOW(fd,0) = 0x6e; + WFIFOB(fd,2) = 0x00; + WFIFOSET(fd,3); + RFIFOSKIP(fd,37); + break; ++ }else if(i == -2){
++ //denied
++ WFIFOHEAD(fd, 3);
++ WFIFOW(fd, 0) = 0x6e;
++ WFIFOB(fd, 2) = 0x02;
++ WFIFOSET(fd, 3);
++ RFIFOSKIP(fd, 37);
++ break;
++ }else if(i == -3){
++ //underaged XD
++ WFIFOHEAD(fd, 3);
++ WFIFOW(fd, 0) = 0x6e;
++ WFIFOB(fd, 2) = 0x01;
++ WFIFOSET(fd, 3);
++ RFIFOSKIP(fd, 37);
++ break;
+ } + ++ WFIFOHEAD(fd, 108);
+ WFIFOW(fd,0) = 0x6d; + memset(WFIFOP(fd,2), 0, 106); + +- WFIFOL(fd,2) = char_dat[i].char_id; +- WFIFOL(fd,2+4) = char_dat[i].base_exp; +- WFIFOL(fd,2+8) = char_dat[i].zeny; +- WFIFOL(fd,2+12) = char_dat[i].job_exp; +- WFIFOL(fd,2+16) = char_dat[i].job_level; ++ WFIFOL(fd,2) = char_dat[i].status.char_id;
++ WFIFOL(fd,2+4) = char_dat[i].status.base_exp>LONG_MAX?LONG_MAX:char_dat[i].status.base_exp;
++ WFIFOL(fd,2+8) = char_dat[i].status.zeny;
++ WFIFOL(fd,2+12) = char_dat[i].status.job_exp>LONG_MAX?LONG_MAX:char_dat[i].status.job_exp;
++ WFIFOL(fd,2+16) = char_dat[i].status.job_level;
+ +- WFIFOL(fd,2+28) = char_dat[i].karma; +- WFIFOL(fd,2+32) = char_dat[i].manner; ++ WFIFOL(fd,2+28) = char_dat[i].status.karma;
++ WFIFOL(fd,2+32) = char_dat[i].status.manner;
+ + WFIFOW(fd,2+40) = 0x30; +- WFIFOW(fd,2+42) = (char_dat[i].hp > 0x7fff) ? 0x7fff : char_dat[i].hp; +- WFIFOW(fd,2+44) = (char_dat[i].max_hp > 0x7fff) ? 0x7fff : char_dat[i].max_hp; +- WFIFOW(fd,2+46) = (char_dat[i].sp > 0x7fff) ? 0x7fff : char_dat[i].sp; +- WFIFOW(fd,2+48) = (char_dat[i].max_sp > 0x7fff) ? 0x7fff : char_dat[i].max_sp; +- WFIFOW(fd,2+50) = DEFAULT_WALK_SPEED; // char_dat[i].speed; +- WFIFOW(fd,2+52) = char_dat[i].class; +- WFIFOW(fd,2+54) = char_dat[i].hair; +- +- WFIFOW(fd,2+58) = char_dat[i].base_level; +- WFIFOW(fd,2+60) = char_dat[i].skill_point; +- +- WFIFOW(fd,2+64) = char_dat[i].shield; +- WFIFOW(fd,2+66) = char_dat[i].head_top; +- WFIFOW(fd,2+68) = char_dat[i].head_mid; +- WFIFOW(fd,2+70) = char_dat[i].hair_color; +- +- memcpy(WFIFOP(fd,2+74), char_dat[i].name, 24); +- +- WFIFOB(fd,2+98) = (char_dat[i].str > 255) ? 255 : char_dat[i].str; +- WFIFOB(fd,2+99) = (char_dat[i].agi > 255) ? 255 : char_dat[i].agi; +- WFIFOB(fd,2+100) = (char_dat[i].vit > 255) ? 255 : char_dat[i].vit; +- WFIFOB(fd,2+101) = (char_dat[i].int_ > 255) ? 255 : char_dat[i].int_; +- WFIFOB(fd,2+102) = (char_dat[i].dex > 255) ? 255 : char_dat[i].dex; +- WFIFOB(fd,2+103) = (char_dat[i].luk > 255) ? 255 : char_dat[i].luk; +- WFIFOB(fd,2+104) = char_dat[i].char_num; ++ WFIFOW(fd,2+42) = (char_dat[i].status.hp > 0x7fff) ? 0x7fff : char_dat[i].status.hp;
++ WFIFOW(fd,2+44) = (char_dat[i].status.max_hp > 0x7fff) ? 0x7fff : char_dat[i].status.max_hp;
++ WFIFOW(fd,2+46) = (char_dat[i].status.sp > 0x7fff) ? 0x7fff : char_dat[i].status.sp;
++ WFIFOW(fd,2+48) = (char_dat[i].status.max_sp > 0x7fff) ? 0x7fff : char_dat[i].status.max_sp;
++ WFIFOW(fd,2+50) = DEFAULT_WALK_SPEED; // char_dat[i].status.speed;
++ WFIFOW(fd,2+52) = char_dat[i].status.class_;
++ WFIFOW(fd,2+54) = char_dat[i].status.hair;
++
++ WFIFOW(fd,2+58) = char_dat[i].status.base_level;
++ WFIFOW(fd,2+60) = char_dat[i].status.skill_point;
++
++ WFIFOW(fd,2+64) = char_dat[i].status.shield;
++ WFIFOW(fd,2+66) = char_dat[i].status.head_top;
++ WFIFOW(fd,2+68) = char_dat[i].status.head_mid;
++ WFIFOW(fd,2+70) = char_dat[i].status.hair_color;
++
++ memcpy(WFIFOP(fd,2+74), char_dat[i].status.name, NAME_LENGTH);
++
++ WFIFOB(fd,2+98) = (char_dat[i].status.str > 255) ? 255 : char_dat[i].status.str;
++ WFIFOB(fd,2+99) = (char_dat[i].status.agi > 255) ? 255 : char_dat[i].status.agi;
++ WFIFOB(fd,2+100) = (char_dat[i].status.vit > 255) ? 255 : char_dat[i].status.vit;
++ WFIFOB(fd,2+101) = (char_dat[i].status.int_ > 255) ? 255 : char_dat[i].status.int_;
++ WFIFOB(fd,2+102) = (char_dat[i].status.dex > 255) ? 255 : char_dat[i].status.dex;
++ WFIFOB(fd,2+103) = (char_dat[i].status.luk > 255) ? 255 : char_dat[i].status.luk;
++ WFIFOB(fd,2+104) = char_dat[i].status.char_num;
+ + WFIFOSET(fd,108); + RFIFOSKIP(fd,37); +@@ -2578,8 +3426,8 @@ + } + + case 0x68: // delete char //Yor's Fix +- if (RFIFOREST(fd) < 46) +- return 0; ++ FIFOSD_CHECK(46);
++
+ memcpy(email, RFIFOP(fd,6), 40); + if (e_mail_check(email) == 0) + strncpy(email, "a@a.com", 40); // default e-mail +@@ -2596,7 +3444,7 @@ + } else { + // we change the packet to set it like selection. + for (i = 0; i < 9; i++) +- if (char_dat[sd->found_char[i]].char_id == RFIFOL(fd,2)) { ++ if (char_dat[sd->found_char[i]].status.char_id == RFIFOL(fd,2)) {
+ // we save new e-mail + memcpy(sd->email, email, 40); + // we send new e-mail to login-server ('online' login-server is checked before) +@@ -2608,7 +3456,7 @@ + RFIFOSKIP(fd,43); + // change value to put new packet (char selection) + RFIFOW(fd, 0) = 0x66; +- RFIFOB(fd, 2) = char_dat[sd->found_char[i]].char_num; ++ RFIFOB(fd, 2) = char_dat[sd->found_char[i]].status.char_num;
+ // not send packet, it's modify of actual packet + break; + } +@@ -2630,7 +3478,7 @@ + } else { + for (i = 0; i < 9; i++) { + struct mmo_charstatus *cs = NULL; +- if ((cs = &char_dat[sd->found_char[i]])->char_id == RFIFOL(fd,2)) { ++ if ((cs = &char_dat[sd->found_char[i]].status)->char_id == RFIFOL(fd,2)) {
+ char_delete(cs); // deletion process + + if (sd->found_char[i] != char_num - 1) { +@@ -2640,8 +3488,8 @@ + int j, k; + struct char_session_data *sd2; + for (j = 0; j < fd_max; j++) { +- if (session[j] && (sd2 = session[j]->session_data) && +- sd2->account_id == char_dat[char_num-1].account_id) { ++ if (session[j] && (sd2 = (struct char_session_data*)session[j]->session_data) &&
++ sd2->account_id == char_dat[char_num-1].status.account_id) {
+ for (k = 0; k < 9; k++) { + if (sd2->found_char[k] == char_num-1) { + sd2->found_char[k] = sd->found_char[i]; +@@ -2682,7 +3530,7 @@ + if (server_fd[i] < 0) + break; + } +- if (i == MAX_MAP_SERVERS || strcmp(RFIFOP(fd,2), userid) || strcmp(RFIFOP(fd,26), passwd)){ ++ if (i == MAX_MAP_SERVERS || strcmp((char*)RFIFOP(fd,2), userid) || strcmp((char*)RFIFOP(fd,26), passwd)){
+ WFIFOB(fd,2) = 3; + WFIFOSET(fd,3); + RFIFOSKIP(fd,60); +@@ -2691,8 +3539,6 @@ + WFIFOB(fd,2) = 0; + session[fd]->func_parse = parse_frommap; + server_fd[i] = fd; +- if(anti_freeze_enable) +- server_freezeflag[i] = 5; // Map anti-freeze system. Counter. 5 ok, 4...0 freezed + server[i].ip = RFIFOL(fd,54); + server[i].port = RFIFOW(fd,58); + server[i].users = 0; +@@ -2735,9 +3581,6 @@ + return 0; + + case 0x7532: // 接続の切断(defaultと処理は一緒だが明示的にするため) +- session[fd]->eof = 1; +- return 0; +- + default: + session[fd]->eof = 1; + return 0; +@@ -2747,6 +3590,30 @@ + return 0; + } + ++// Console Command Parser [Wizputer]
++int parse_console(char *buf) {
++ char *type,*command;
++
++ type = (char *)aCalloc(64,1);
++ command = (char *)aCalloc(64,1);
++
++// memset(type,0,64);
++// memset(command,0,64);
++
++ ShowStatus("Console: %s\n",buf);
++
++ if ( sscanf(buf, "%[^:]:%[^\n]", type , command ) < 2 )
++ sscanf(buf,"%[^\n]",type);
++
++ ShowDebug("Type of command: %s || Command: %s \n",type,command);
++
++ if(buf) aFree(buf);
++ if(type) aFree(type);
++ if(command) aFree(command);
++
++ return 0;
++}
++
+ // 全てのMAPサーバーにデータ送信(送信したmap鯖の数を返す) + int mapif_sendall(unsigned char *buf, unsigned int len) { + int i, c; +@@ -2755,6 +3622,16 @@ + for(i = 0; i < MAX_MAP_SERVERS; i++) { + int fd; + if ((fd = server_fd[i]) >= 0) { ++ if (session[fd] == NULL)
++ { //Could this be the crash's source? [Skotlex]
++ ShowError("mapif_sendall: Attempting to write to invalid session %d! Map Server #%d disconnected.\n", fd, i);
++ server_fd[i] = -1;
++ memset(&server[i], 0, sizeof(struct mmo_map_server));
++ continue;
++ }
++ WFIFOHEAD(fd, len);
++ if (WFIFOSPACE(fd) < len) //Increase buffer size.
++ realloc_writefifo(fd, len);
+ memcpy(WFIFOP(fd,0), buf, len); + WFIFOSET(fd,len); + c++; +@@ -2771,6 +3648,9 @@ + for(i = 0; i < MAX_MAP_SERVERS; i++) { + int fd; + if ((fd = server_fd[i]) >= 0 && fd != sfd) { ++ WFIFOHEAD(fd, len);
++ if (WFIFOSPACE(fd) < len) //Increase buffer size.
++ realloc_writefifo(fd, len);
+ memcpy(WFIFOP(fd,0), buf, len); + WFIFOSET(fd, len); + c++; +@@ -2785,6 +3665,9 @@ + if (fd >= 0) { + for(i = 0; i < MAX_MAP_SERVERS; i++) { + if (fd == server_fd[i]) { ++ WFIFOHEAD(fd, len);
++ if (WFIFOSPACE(fd) < len) //Increase buffer size.
++ realloc_writefifo(fd, len);
+ memcpy(WFIFOP(fd,0), buf, len); + WFIFOSET(fd,len); + return 1; +@@ -2796,10 +3679,11 @@ + + int send_users_tologin(int tid, unsigned int tick, int id, int data) { + int users = count_users(); +- char buf[16]; ++ unsigned char buf[16];
+ + if (login_fd > 0 && session[login_fd]) { + // send number of user to login server ++ WFIFOHEAD(login_fd, 6);
+ WFIFOW(login_fd,0) = 0x2714; + WFIFOL(login_fd,2) = users; + WFIFOSET(login_fd,6); +@@ -2812,17 +3696,52 @@ + return 0; + } + ++static int send_accounts_tologin_sub(DBKey key, void* data, va_list ap) {
++ struct online_char_data* character = (struct online_char_data*)data;
++ int *i = va_arg(ap, int*);
++ int count = va_arg(ap, int);
++ if ((*i) >= count)
++ return 0; //This is an error that shouldn't happen....
++ if(character->server > -1) {
++ WFIFOHEAD(login_fd, 8+count*4);
++ WFIFOL(login_fd, 8+(*i)*4) =character->account_id;
++ (*i)++;
++ return 1;
++ }
++ return 0;
++}
++
++int send_accounts_tologin(int tid, unsigned int tick, int id, int data) {
++ int users = count_users(), i=0;
++
++ if (login_fd > 0 && session[login_fd]) {
++ // send account list to login server
++ WFIFOHEAD(login_fd, 8+users*4);
++ WFIFOW(login_fd,0) = 0x272d;
++ WFIFOL(login_fd,4) = users;
++ online_char_db->foreach(online_char_db, send_accounts_tologin_sub, &i);
++ WFIFOW(login_fd,2) = 8+ i*4;
++ if (i > 0)
++ WFIFOSET(login_fd,WFIFOW(login_fd,2));
++ }
++ return 0;
++}
++
+ int check_connect_login_server(int tid, unsigned int tick, int id, int data) { + if (login_fd <= 0 || session[login_fd] == NULL) { +- printf("Attempt to connect to login-server...\n"); ++ ShowInfo("Attempt to connect to login-server...\n");
+ login_fd = make_connection(login_ip, login_port); ++ if (login_fd == -1)
++ { //Try again later... [Skotlex]
++ login_fd = 0;
++ return 0;
++ }
+ session[login_fd]->func_parse = parse_tologin; + realloc_fifo(login_fd, FIFOSIZE_SERVERLINK, FIFOSIZE_SERVERLINK); ++ WFIFOHEAD(login_fd, 86);
+ WFIFOW(login_fd,0) = 0x2710; +- memset(WFIFOP(login_fd,2), 0, 24); +- memcpy(WFIFOP(login_fd,2), userid, strlen(userid) < 24 ? strlen(userid) : 24); +- memset(WFIFOP(login_fd,26), 0, 24); +- memcpy(WFIFOP(login_fd,26), passwd, strlen(passwd) < 24 ? strlen(passwd) : 24); ++ memcpy(WFIFOP(login_fd,2), userid, 24);
++ memcpy(WFIFOP(login_fd,26), passwd, 24);
+ WFIFOL(login_fd,50) = 0; + WFIFOL(login_fd,54) = char_ip; + WFIFOL(login_fd,58) = char_port; +@@ -2830,12 +3749,28 @@ + memcpy(WFIFOP(login_fd,60), server_name, strlen(server_name) < 20 ? strlen(server_name) : 20); + WFIFOW(login_fd,80) = 0; + WFIFOW(login_fd,82) = char_maintenance; +- WFIFOW(login_fd,84) = char_new; ++
++ WFIFOW(login_fd,84) = char_new_display; //only display (New) if they want to [Kevin]
++
+ WFIFOSET(login_fd,86); + } + return 0; + } + ++//------------------------------------------------
++//Invoked 15 seconds after mapif_disconnectplayer in case the map server doesn't
++//replies/disconnect the player we tried to kick. [Skotlex]
++//------------------------------------------------
++static int chardb_waiting_disconnect(int tid, unsigned int tick, int id, int data)
++{
++ struct online_char_data* character;
++ if ((character = idb_get(online_char_db, id)) != NULL && character->waiting_disconnect)
++ { //Mark it offline due to timeout.
++ set_char_offline(character->char_id, character->account_id);
++ }
++ return 0;
++}
++
+ //---------------------------------------------------------- + // Return numerical value of a switch configuration by [Yor] + // on/off, english, fran軋is, deutsch, espaol +@@ -2849,92 +3784,56 @@ + return atoi(str); + } + +-//------------------------------------------- +-// Reading Lan Support configuration by [Yor] +-//------------------------------------------- +-int lan_config_read(const char *lancfgName) { +- int j; +- struct hostent * h = NULL; +- char line[1024], w1[1024], w2[1024]; +- FILE *fp; ++//----------------------------------
++// Reading Lan Support configuration
++// Rewrote: Anvanced subnet check [LuzZza]
++//----------------------------------
++int char_lan_config_read(const char *lancfgName) {
+ +- // set default configuration +- strncpy(lan_map_ip, "127.0.0.1", sizeof(lan_map_ip)); +- subneti[0] = 127; +- subneti[1] = 0; +- subneti[2] = 0; +- subneti[3] = 1; +- for(j = 0; j < 4; j++) +- subnetmaski[j] = 255; +- +- fp = fopen(lancfgName, "r"); ++ FILE *fp;
++ int line_num = 0;
++ char line[1024], w1[64], w2[64], w3[64], w4[64], w5[64];
+ +- if (fp == NULL) { +- printf("LAN support configuration file not found: %s\n", lancfgName); ++ if((fp = fopen(lancfgName, "r")) == NULL) {
++ ShowWarning("LAN Support configuration file is not found: %s\n", lancfgName);
+ return 1; + } + +- printf ("---start reading of Lan Support configuration...\n"); ++ ShowInfo("Reading the configuration file %s...\n", lancfgName);
+ + while(fgets(line, sizeof(line)-1, fp)) { +- if (line[0] == '/' && line[1] == '/') ++
++ line_num++;
++ if ((line[0] == '/' && line[1] == '/') || line[0] == '\n' || line[1] == '\n')
+ continue; + + line[sizeof(line)-1] = '\0'; +- if (sscanf(line, "%[^:]: %[^\r\n]", w1, w2) != 2) +- continue; ++ if(sscanf(line,"%[^:]: %[^/]/%[^:]:%[^:]:%[^\r\n]", w1, w2, w3, w4, w5) != 5) {
+ +- remove_control_chars(w1); +- remove_control_chars(w2); +- if (strcmpi(w1, "lan_map_ip") == 0) { // Read map-server Lan IP Address +- h = gethostbyname(w2); +- if (h != NULL) { +- sprintf(lan_map_ip, "%d.%d.%d.%d", (unsigned char)h->h_addr[0], (unsigned char)h->h_addr[1], (unsigned char)h->h_addr[2], (unsigned char)h->h_addr[3]); +- } else { +- strncpy(lan_map_ip, w2, sizeof(lan_map_ip)); +- lan_map_ip[sizeof(lan_map_ip)-1] = 0; +- } +- printf("LAN IP of map-server: %s.\n", lan_map_ip); +- } else if (strcmpi(w1, "subnet") == 0) { // Read Subnetwork +- for(j = 0; j < 4; j++) +- subneti[j] = 0; +- h = gethostbyname(w2); +- if (h != NULL) { +- for(j = 0; j < 4; j++) +- subneti[j] = (unsigned char)h->h_addr[j]; +- } else { +- sscanf(w2, "%d.%d.%d.%d", &subneti[0], &subneti[1], &subneti[2], &subneti[3]); +- } +- printf("Sub-network of the map-server: %d.%d.%d.%d.\n", subneti[0], subneti[1], subneti[2], subneti[3]); +- } else if (strcmpi(w1, "subnetmask") == 0){ // Read Subnetwork Mask +- for(j = 0; j < 4; j++) +- subnetmaski[j] = 255; +- h = gethostbyname(w2); +- if (h != NULL) { +- for(j = 0; j < 4; j++) +- subnetmaski[j] = (unsigned char)h->h_addr[j]; +- } else { +- sscanf(w2, "%d.%d.%d.%d", &subnetmaski[0], &subnetmaski[1], &subnetmaski[2], &subnetmaski[3]); +- } +- printf("Sub-network mask of the map-server: %d.%d.%d.%d.\n", subnetmaski[0], subnetmaski[1], subnetmaski[2], subnetmaski[3]); +- } ++ ShowWarning("Error syntax of configuration file %s in line %d.\n", lancfgName, line_num);
++ continue;
+ } +- fclose(fp); + +- // sub-network check of the map-server +- { +- unsigned int a0, a1, a2, a3; +- unsigned char p[4]; +- sscanf(lan_map_ip, "%d.%d.%d.%d", &a0, &a1, &a2, &a3); +- p[0] = a0; p[1] = a1; p[2] = a2; p[3] = a3; +- printf("LAN test of LAN IP of the map-server: "); +- if (lan_ip_check(p) == 0) { +- printf("\033[1;31m***ERROR: LAN IP of the map-server doesn't belong to the specified Sub-network.\033[0m\n"); +- } ++ remove_control_chars((unsigned char *)w1);
++ remove_control_chars((unsigned char *)w2);
++ remove_control_chars((unsigned char *)w3);
++ remove_control_chars((unsigned char *)w4);
++ remove_control_chars((unsigned char *)w5);
++
++ if(strcmpi(w1, "subnet") == 0) {
++
++ subnet[subnet_count].subnet = inet_addr(w2);
++ subnet[subnet_count].mask = inet_addr(w3);
++ subnet[subnet_count].char_ip = inet_addr(w4);
++ subnet[subnet_count].map_ip = inet_addr(w5);
++
++ subnet_count++;
+ } + +- printf("---End reading of Lan Support configuration...\n"); ++ ShowStatus("Information about %d subnetworks readen.\n", subnet_count);
++ }
+ ++ fclose(fp);
+ return 0; + } + +@@ -2944,10 +3843,11 @@ + FILE *fp = fopen(cfgName, "r"); + + if (fp == NULL) { +- printf("Configuration file not found: %s.\n", cfgName); ++ ShowFatalError("Configuration file not found: %s.\n", cfgName);
+ exit(1); + } + ++ ShowInfo("Reading configuration file %s...\n", cfgName);
+ while(fgets(line, sizeof(line)-1, fp)) { + if (line[0] == '/' && line[1] == '/') + continue; +@@ -2956,86 +3856,122 @@ + if (sscanf(line, "%[^:]: %[^\r\n]", w1, w2) != 2) + continue; + +- remove_control_chars(w1); +- remove_control_chars(w2); +- if (strcmpi(w1, "userid") == 0) { +- memcpy(userid, w2, 24); ++ remove_control_chars((unsigned char *)w1);
++ remove_control_chars((unsigned char *)w2);
++ if(strcmpi(w1,"timestamp_format") == 0) {
++ strncpy(timestamp_format, w2, 20);
++ } else if(strcmpi(w1,"console_silent")==0){
++ msg_silent = 0; //To always allow the next line to show up.
++ ShowInfo("Console Silent Setting: %d\n", atoi(w2));
++ msg_silent = atoi(w2);
++ } else if (strcmpi(w1, "userid") == 0) {
++ strncpy(userid, w2, 24);
+ } else if (strcmpi(w1, "passwd") == 0) { +- memcpy(passwd, w2, 24); ++ strncpy(passwd, w2, 24);
+ } else if (strcmpi(w1, "server_name") == 0) { + memcpy(server_name, w2, sizeof(server_name)); + server_name[sizeof(server_name) - 1] = '\0'; +- printf("%s server has been intialized\n", w2); ++ ShowStatus("%s server has been initialized\n", w2);
+ } else if (strcmpi(w1, "wisp_server_name") == 0) { + if (strlen(w2) >= 4) { + memcpy(wisp_server_name, w2, sizeof(wisp_server_name)); + wisp_server_name[sizeof(wisp_server_name) - 1] = '\0'; + } + } else if (strcmpi(w1, "login_ip") == 0) { ++ login_ip_set_ = 1;
+ h = gethostbyname(w2); + if (h != NULL) { +- printf("Login server IP address : %s -> %d.%d.%d.%d\n", w2, (unsigned char)h->h_addr[0], (unsigned char)h->h_addr[1], (unsigned char)h->h_addr[2], (unsigned char)h->h_addr[3]); ++ ShowStatus("Login server IP address : %s -> %d.%d.%d.%d\n", w2, (unsigned char)h->h_addr[0], (unsigned char)h->h_addr[1], (unsigned char)h->h_addr[2], (unsigned char)h->h_addr[3]);
+ sprintf(login_ip_str, "%d.%d.%d.%d", (unsigned char)h->h_addr[0], (unsigned char)h->h_addr[1], (unsigned char)h->h_addr[2], (unsigned char)h->h_addr[3]); + } else + memcpy(login_ip_str, w2, 16); + } else if (strcmpi(w1, "login_port") == 0) { + login_port = atoi(w2); + } else if (strcmpi(w1, "char_ip") == 0) { ++ char_ip_set_ = 1;
+ h = gethostbyname(w2); + if (h != NULL) { +- printf("Character server IP address : %s -> %d.%d.%d.%d\n", w2, (unsigned char)h->h_addr[0], (unsigned char)h->h_addr[1], (unsigned char)h->h_addr[2], (unsigned char)h->h_addr[3]); ++ ShowStatus("Character server IP address : %s -> %d.%d.%d.%d\n", w2, (unsigned char)h->h_addr[0], (unsigned char)h->h_addr[1], (unsigned char)h->h_addr[2], (unsigned char)h->h_addr[3]);
+ sprintf(char_ip_str, "%d.%d.%d.%d", (unsigned char)h->h_addr[0], (unsigned char)h->h_addr[1], (unsigned char)h->h_addr[2], (unsigned char)h->h_addr[3]); + } else + memcpy(char_ip_str, w2, 16); ++ } else if (strcmpi(w1, "bind_ip") == 0) {
++ bind_ip_set_ = 1;
++ h = gethostbyname(w2);
++ if (h != NULL) {
++ ShowStatus("Character server binding IP address : %s -> %d.%d.%d.%d\n", w2, (unsigned char)h->h_addr[0], (unsigned char)h->h_addr[1], (unsigned char)h->h_addr[2], (unsigned char)h->h_addr[3]);
++ sprintf(bind_ip_str, "%d.%d.%d.%d", (unsigned char)h->h_addr[0], (unsigned char)h->h_addr[1], (unsigned char)h->h_addr[2], (unsigned char)h->h_addr[3]);
++ } else
++ memcpy(bind_ip_str, w2, 16);
+ } else if (strcmpi(w1, "char_port") == 0) { + char_port = atoi(w2); + } else if (strcmpi(w1, "char_maintenance") == 0) { + char_maintenance = atoi(w2); + } else if (strcmpi(w1, "char_new") == 0) { + char_new = atoi(w2); ++ } else if (strcmpi(w1, "char_new_display") == 0) {
++ char_new_display = atoi(w2);
+ } else if (strcmpi(w1, "email_creation") == 0) { + email_creation = config_switch(w2); + } else if (strcmpi(w1, "char_txt") == 0) { + strcpy(char_txt, w2); ++ } else if (strcmpi(w1, "scdata_txt") == 0) { //By Skotlex
++ strcpy(scdata_txt, w2);
+ } else if (strcmpi(w1, "backup_txt") == 0) { //By zanetheinsane + strcpy(backup_txt, w2); ++ } else if (strcmpi(w1, "friends_txt") == 0) { //By davidsiaw
++ strcpy(friends_txt, w2);
+ } else if (strcmpi(w1, "backup_txt_flag") == 0) { // The backup_txt file was created because char deletion bug existed. Now it's finish and that take a lot of time to create a second file when there are a lot of characters. By [Yor] + backup_txt_flag = config_switch(w2); + } else if (strcmpi(w1, "max_connect_user") == 0) { + max_connect_user = atoi(w2); + if (max_connect_user < 0) + max_connect_user = 0; // unlimited online players ++ } else if(strcmpi(w1, "gm_allow_level") == 0) {
++ gm_allow_level = atoi(w2);
++ if(gm_allow_level < 0)
++ gm_allow_level = 99;
+ } else if (strcmpi(w1, "check_ip_flag") == 0) { + check_ip_flag = config_switch(w2); ++ } else if (strcmpi(w1, "online_check") == 0) {
++ online_check = config_switch(w2);
+ } else if (strcmpi(w1, "autosave_time") == 0) { + autosave_interval = atoi(w2)*1000; + if (autosave_interval <= 0) + autosave_interval = DEFAULT_AUTOSAVE_INTERVAL; ++ } else if (strcmpi(w1, "save_log") == 0) {
++ save_log = config_switch(w2);
+ } else if (strcmpi(w1, "start_point") == 0) { + char map[32]; + int x, y; + if (sscanf(w2, "%[^,],%d,%d", map, &x, &y) < 3) + continue; + if (strstr(map, ".gat") != NULL) { // Verify at least if '.gat' is in the map name +- memcpy(start_point.map, map, 16); ++ start_point.map = mapindex_name2id(map);
++ if (!start_point.map) {
++ ShowError("Specified start_point %s not found in map-index cache.\n", map);
++ start_point.map = 0;
++ }
+ start_point.x = x; + start_point.y = y; + } ++ } else if(strcmpi(w1,"log_char")==0) { //log char or not [devil]
++ log_char = atoi(w2);
+ } else if (strcmpi(w1, "start_zeny") == 0) { + start_zeny = atoi(w2); + if (start_zeny < 0) + start_zeny = 0; + } else if (strcmpi(w1, "start_weapon") == 0) { +- start_zeny = atoi(w2); ++ start_weapon = atoi(w2);
+ if (start_weapon < 0) + start_weapon = 0; + } else if (strcmpi(w1, "start_armor") == 0) { +- start_zeny = atoi(w2); ++ start_armor = atoi(w2);
+ if (start_armor < 0) + start_armor = 0; + } else if (strcmpi(w1, "unknown_char_name") == 0) { + strcpy(unknown_char_name, w2); +- unknown_char_name[24] = 0; ++ unknown_char_name[NAME_LENGTH-1] = '\0';
+ } else if (strcmpi(w1, "char_log_filename") == 0) { + strcpy(char_log_filename, w2); + } else if (strcmpi(w1, "name_ignoring_case") == 0) { +@@ -3061,52 +3997,130 @@ + online_refresh_html = atoi(w2); + if (online_refresh_html < 1) + online_refresh_html = 1; +- } else if(strcmpi(w1,"anti_freeze_enable")==0){ +- anti_freeze_enable = config_switch(w2); +- } else if (strcmpi(w1, "anti_freeze_interval") == 0) { +- ANTI_FREEZE_INTERVAL = atoi(w2); +- if (ANTI_FREEZE_INTERVAL < 5) +- ANTI_FREEZE_INTERVAL = 5; // minimum 5 seconds ++ } else if(strcmpi(w1,"db_path")==0) {
++ strcpy(db_path,w2);
++ } else if (strcmpi(w1, "console") == 0) {
++ if(strcmpi(w2,"on") == 0 || strcmpi(w2,"yes") == 0 )
++ console = 1;
++ } else if (strcmpi(w1, "fame_list_alchemist") == 0) {
++ fame_list_size_chemist = atoi(w2);
++ if (fame_list_size_chemist > MAX_FAME_LIST) {
++ ShowWarning("Max fame list size is %d (fame_list_alchemist)\n", MAX_FAME_LIST);
++ fame_list_size_chemist = MAX_FAME_LIST;
++ }
++ } else if (strcmpi(w1, "fame_list_blacksmith") == 0) {
++ fame_list_size_smith = atoi(w2);
++ if (fame_list_size_smith > MAX_FAME_LIST) {
++ ShowWarning("Max fame list size is %d (fame_list_blacksmith)\n", MAX_FAME_LIST);
++ fame_list_size_smith = MAX_FAME_LIST;
++ }
++ } else if (strcmpi(w1, "fame_list_taekwon") == 0) {
++ fame_list_size_taekwon = atoi(w2);
++ if (fame_list_size_taekwon > MAX_FAME_LIST) {
++ ShowWarning("Max fame list size is %d (fame_list_taekwon)\n", MAX_FAME_LIST);
++ fame_list_size_taekwon = MAX_FAME_LIST;
++ }
+ } else if (strcmpi(w1, "import") == 0) { + char_config_read(w2); + } + } + fclose(fp); + ++ ShowInfo("done reading %s.\n", cfgName);
+ return 0; + } + ++int chardb_final(int key, void* data, va_list va)
++{
++ aFree(data);
++ return 0;
++}
+ void do_final(void) { +- int i; +- ++ ShowStatus("Terminating server.\n");
+ // write online players files with no player +- for(i = 0; i < char_num; i++) +- online_chars[i] = -1; ++ online_char_db->clear(online_char_db, NULL); //clean the db...
+ create_online_files(); +- free(online_chars); ++ online_char_db->destroy(online_char_db, NULL); //dispose the db...
+ + mmo_char_sync(); + inter_save(); ++ set_all_offline();
+ +- if (gm_account != NULL) +- free(gm_account); ++ if(gm_account) aFree(gm_account);
++ if(char_dat) aFree(char_dat);
+ +- free(char_dat); + delete_session(login_fd); + delete_session(char_fd); + ++#ifdef ENABLE_SC_SAVING
++ status_final();
++#endif
++ inter_final();
++ mapindex_final();
++
+ char_log("----End of char-server (normal end with closing of all files)." RETCODE); + } + ++void set_server_type(void)
++{
++ SERVER_TYPE = ATHENA_SERVER_CHAR;
++}
++
++static int online_data_cleanup_sub(DBKey key, void *data, va_list ap)
++{
++ struct online_char_data *character= (struct online_char_data*)data;
++ if (character->server == -2) //Unknown server.. set them offline
++ set_char_offline(character->char_id, character->account_id);
++ if (character->server < 0)
++ //Free data from players that have not been online for a while.
++ db_remove(online_char_db, key);
++ return 0;
++}
++
++static int online_data_cleanup(int tid, unsigned int tick, int id, int data)
++{
++ online_char_db->foreach(online_char_db, online_data_cleanup_sub);
++ return 0;
++}
++
+ int do_init(int argc, char **argv) { + int i; + ++ mapindex_init(); //Needed here for the start-point reading.
++ start_point.map = mapindex_name2id("new_1-1.gat");
++ char_config_read((argc < 2) ? CHAR_CONF_NAME : argv[1]);
++ char_lan_config_read((argc > 3) ? argv[3] : LOGIN_LAN_CONF_NAME);
++
++ if (strcmp(userid, "s1")==0 && strcmp(passwd, "p1")==0) {
++ ShowError("Using the default user/password s1/p1 is NOT RECOMMENDED.\n");
++ ShowNotice("Please edit your save/account.txt file to create a proper inter-server user/password (gender 'S')\n");
++ ShowNotice("And then change the user/password to use in conf/char_athena.conf (or conf/import/char_conf.txt)\n");
++ }
++
+ // a newline in the log... + char_log(""); ++ // moved behind char_config_read in case we changed the filename [celest]
+ char_log("The char-server starting..." RETCODE); + +- char_config_read((argc < 2) ? CHAR_CONF_NAME : argv[1]); +- lan_config_read((argc > 1) ? argv[1] : LOGIN_LAN_CONF_NAME); ++ if ((naddr_ != 0) && (login_ip_set_ == 0 || char_ip_set_ == 0)) {
++ // The char server should know what IP address it is running on
++ // - MouseJstr
++ int localaddr = ntohl(addr_[0]);
++ unsigned char *ptr = (unsigned char *) &localaddr;
++ char buf[16];
++ sprintf(buf, "%d.%d.%d.%d", ptr[0], ptr[1], ptr[2], ptr[3]);;
++ if (naddr_ != 1)
++ ShowStatus("Multiple interfaces detected.. using %s as our IP address\n", buf);
++ else
++ ShowStatus("Defaulting to %s as our IP address\n", buf);
++ if (login_ip_set_ == 0)
++ strcpy(login_ip_str, buf);
++ if (char_ip_set_ == 0)
++ strcpy(char_ip_str, buf);
++
++ if (ptr[0] == 192 && ptr[1] == 168)
++ ShowWarning("Firewall detected.. edit lan_support.conf and char_athena.conf\n");
++ }
+ + login_ip = inet_addr(login_ip_str); + char_ip = inet_addr(char_ip_str); +@@ -3116,34 +4130,45 @@ + server_fd[i] = -1; + } + +- mmo_char_init(); ++ online_char_db = db_alloc(__FILE__,__LINE__,DB_INT,DB_OPT_RELEASE_DATA,sizeof(int));
+ ++ mmo_char_init();
++#ifdef ENABLE_SC_SAVING
++ status_init();
++#endif
+ update_online = time(NULL); + create_online_files(); // update online players files at start of the server + + inter_init((argc > 2) ? argv[2] : inter_cfgName); // inter server 初期化 + +- set_termfunc(do_final); + set_defaultparse(parse_char); + +- char_fd = make_listen_port(char_port); ++ if (bind_ip_set_)
++ char_fd = make_listen_bind(inet_addr(bind_ip_str),char_port);
++ else
++ char_fd = make_listen_bind(INADDR_ANY,char_port);
+ + add_timer_func_list(check_connect_login_server, "check_connect_login_server"); + add_timer_func_list(send_users_tologin, "send_users_tologin"); ++ add_timer_func_list(send_accounts_tologin, "send_accounts_tologin");
+ add_timer_func_list(mmo_char_sync_timer, "mmo_char_sync_timer"); ++ add_timer_func_list(chardb_waiting_disconnect, "chardb_waiting_disconnect");
++ add_timer_func_list(online_data_cleanup, "online_data_cleanup");
+ +- i = add_timer_interval(gettick() + 1000, check_connect_login_server, 0, 0, 10 * 1000); +- i = add_timer_interval(gettick() + 1000, send_users_tologin, 0, 0, 5 * 1000); +- i = add_timer_interval(gettick() + autosave_interval, mmo_char_sync_timer, 0, 0, autosave_interval); +- +- if(anti_freeze_enable > 0) { +- add_timer_func_list(map_anti_freeze_system, "map_anti_freeze_system"); +- i = add_timer_interval(gettick() + 1000, map_anti_freeze_system, 0, 0, ANTI_FREEZE_INTERVAL * 1000); // checks every X seconds user specifies ++ add_timer_interval(gettick() + 600*1000, online_data_cleanup, 0, 0, 600 * 1000);
++ add_timer_interval(gettick() + 1000, check_connect_login_server, 0, 0, 10 * 1000);
++ add_timer_interval(gettick() + 1000, send_users_tologin, 0, 0, 5 * 1000);
++ add_timer_interval(gettick() + 3600*1000, send_accounts_tologin, 0, 0, 3600*1000); //Sync online accounts every hour
++ add_timer_interval(gettick() + autosave_interval, mmo_char_sync_timer, 0, 0, autosave_interval);
++
++ if(console) {
++ set_defaultconsoleparse(parse_console);
++ start_console();
+ } + + char_log("The char-server is ready (Server is listening on the port %d)." RETCODE, char_port); + +- printf("The char-server is \033[1;32mready\033[0m (Server is listening on the port %d).\n\n", char_port); ++ ShowStatus("The char-server is "CL_GREEN"ready"CL_RESET" (Server is listening on the port %d).\n\n", char_port);
+ + return 0; + } diff --git a/misc/src/char/int_guild.c b/misc/src/char/int_guild.c new file mode 100644 index 0000000..665e017 --- /dev/null +++ b/misc/src/char/int_guild.c @@ -0,0 +1,1441 @@ +// $Id: int_guild.c,v 1.2 2004/09/25 19:36:53 Akitasha Exp $ +#include "inter.h" +#include "int_guild.h" +#include "int_storage.h" +#include "mmo.h" +#include "char.h" +#include "socket.h" +#include "db.h" +#include "lock.h" + +#include <string.h> +#include <stdio.h> +#include <stdlib.h> + +char guild_txt[1024] = "save/guild.txt"; +char castle_txt[1024] = "save/castle.txt"; + +static struct dbt *guild_db; +static struct dbt *castle_db; + +static int guild_newid = 10000; + +static int guild_exp[100]; + +int mapif_parse_GuildLeave(int fd, int guild_id, int account_id, int char_id, int flag, const char *mes); +int mapif_guild_broken(int guild_id, int flag); +int guild_check_empty(struct guild *g); +int guild_calcinfo(struct guild *g); +int mapif_guild_basicinfochanged(int guild_id, int type, const void *data, int len); +int mapif_guild_info(int fd, struct guild *g); +int guild_break_sub(void *key, void *data, va_list ap); + +// ギルドデータの文字列への変換 +int inter_guild_tostr(char *str, struct guild *g) { + int i, c, len; + + // 基本データ + len = sprintf(str, "%d\t%s\t%s\t%d,%d,%d,%d,%d\t%s#\t%s#\t", + g->guild_id, g->name, g->master, + g->guild_lv, g->max_member, g->exp, g->skill_point, g->castle_id, + g->mes1, g->mes2); + // メンバー + for(i = 0; i < g->max_member; i++) { + struct guild_member *m = &g->member[i]; + len += sprintf(str + len, "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d\t%s\t", + m->account_id, m->char_id, + m->hair, m->hair_color, m->gender, + m->class, m->lv, m->exp, m->exp_payper, m->position, + ((m->account_id > 0) ? m->name : "-")); + } + // 役職 + for(i = 0; i < MAX_GUILDPOSITION; i++) { + struct guild_position *p = &g->position[i]; + len += sprintf(str + len, "%d,%d\t%s#\t", p->mode, p->exp_mode, p->name); + } + // エンブレム + len += sprintf(str + len, "%d,%d,", g->emblem_len, g->emblem_id); + for(i = 0; i < g->emblem_len; i++) { + len += sprintf(str + len, "%02x", (unsigned char)(g->emblem_data[i])); + } + len += sprintf(str + len, "$\t"); + // 同盟リスト + c = 0; + for(i = 0; i < MAX_GUILDALLIANCE; i++) + if (g->alliance[i].guild_id > 0) + c++; + len += sprintf(str + len, "%d\t", c); + for(i = 0; i < MAX_GUILDALLIANCE; i++) { + struct guild_alliance *a = &g->alliance[i]; + if (a->guild_id > 0) + len += sprintf(str + len, "%d,%d\t%s\t", a->guild_id, a->opposition, a->name); + } + // 追放リスト + c = 0; + for(i = 0; i < MAX_GUILDEXPLUSION; i++) + if (g->explusion[i].account_id > 0) + c++; + len += sprintf(str + len, "%d\t", c); + for(i = 0; i < MAX_GUILDEXPLUSION; i++) { + struct guild_explusion *e = &g->explusion[i]; + if (e->account_id > 0) + len += sprintf(str + len, "%d,%d,%d,%d\t%s\t%s\t%s#\t", + e->account_id, e->rsv1, e->rsv2, e->rsv3, + e->name, e->acc, e->mes ); + } + // ギルドスキル + for(i = 0; i < MAX_GUILDSKILL; i++) { + len += sprintf(str + len, "%d,%d ", g->skill[i].id, g->skill[i].lv); + } + len += sprintf(str + len, "\t"); + + return 0; +} + +// ギルドデータの文字列からの変換 +int inter_guild_fromstr(char *str, struct guild *g) { + int i, j, c; + int tmp_int[16]; + char tmp_str[4][256]; + char tmp_str2[4096]; + char *pstr; + + // 基本データ + memset(g, 0, sizeof(struct guild)); + if (sscanf(str, "%d\t%[^\t]\t%[^\t]\t%d,%d,%d,%d,%d\t%[^\t]\t%[^\t]\t", &tmp_int[0], + tmp_str[0], tmp_str[1], + &tmp_int[1], &tmp_int[2], &tmp_int[3], &tmp_int[4], &tmp_int[5], + tmp_str[2], tmp_str[3]) < 8) + return 1; + + g->guild_id = tmp_int[0]; + g->guild_lv = tmp_int[1]; + g->max_member = tmp_int[2]; + g->exp = tmp_int[3]; + g->skill_point = tmp_int[4]; + g->castle_id = tmp_int[5]; + memcpy(g->name, tmp_str[0], 24); + memcpy(g->master, tmp_str[1], 24); + memcpy(g->mes1, tmp_str[2], 60); + memcpy(g->mes2, tmp_str[3], 120); + g->mes1[strlen(g->mes1)-1] = 0; + g->mes2[strlen(g->mes2)-1] = 0; + + for(j = 0; j < 6 && str != NULL; j++) // 位置スキップ + str = strchr(str + 1, '\t'); +// printf("GuildBaseInfo OK\n"); + + // メンバー + for(i = 0; i < g->max_member; i++) { + struct guild_member *m = &g->member[i]; + if (sscanf(str + 1, "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d\t%[^\t]\t", + &tmp_int[0], &tmp_int[1], &tmp_int[2], &tmp_int[3], &tmp_int[4], + &tmp_int[5], &tmp_int[6], &tmp_int[7], &tmp_int[8], &tmp_int[9], + tmp_str[0]) < 11) + return 1; + m->account_id = tmp_int[0]; + m->char_id = tmp_int[1]; + m->hair = tmp_int[2]; + m->hair_color = tmp_int[3]; + m->gender = tmp_int[4]; + m->class = tmp_int[5]; + m->lv = tmp_int[6]; + m->exp = tmp_int[7]; + m->exp_payper = tmp_int[8]; + m->position = tmp_int[9]; + memcpy(m->name, tmp_str[0], 24); + + for(j = 0; j < 2 && str != NULL; j++) // 位置スキップ + str = strchr(str + 1, '\t'); + } +// printf("GuildMemberInfo OK\n"); + // 役職 + i = 0; + while (sscanf(str+1, "%d,%d%n", &tmp_int[0], &tmp_int[1], &j) == 2 && str[1+j] == '\t') { + struct guild_position *p = &g->position[i]; + if (sscanf(str+1, "%d,%d\t%[^\t]\t", &tmp_int[0], &tmp_int[1], tmp_str[0]) < 3) + return 1; + p->mode = tmp_int[0]; + p->exp_mode = tmp_int[1]; + tmp_str[0][strlen(tmp_str[0])-1] = 0; + memcpy(p->name, tmp_str[0], 24); + + for(j = 0; j < 2 && str != NULL; j++) // 位置スキップ + str = strchr(str+1, '\t'); + i++; + } +// printf("GuildPositionInfo OK\n"); + // エンブレム + tmp_int[1] = 0; + if (sscanf(str + 1, "%d,%d,%[^\t]\t", &tmp_int[0], &tmp_int[1], tmp_str2)< 3 && + sscanf(str + 1, "%d,%[^\t]\t", &tmp_int[0], tmp_str2) < 2) + return 1; + g->emblem_len = tmp_int[0]; + g->emblem_id = tmp_int[1]; + for(i = 0, pstr = tmp_str2; i < g->emblem_len; i++, pstr += 2) { + int c1 = pstr[0], c2 = pstr[1], x1 = 0, x2 = 0; + if (c1 >= '0' && c1 <= '9') x1 = c1 - '0'; + if (c1 >= 'a' && c1 <= 'f') x1 = c1 - 'a' + 10; + if (c1 >= 'A' && c1 <= 'F') x1 = c1 - 'A' + 10; + if (c2 >= '0' && c2 <= '9') x2 = c2 - '0'; + if (c2 >= 'a' && c2 <= 'f') x2 = c2 - 'a' + 10; + if (c2 >= 'A' && c2 <= 'F') x2 = c2 - 'A' + 10; + g->emblem_data[i] = (x1<<4) | x2; + } +// printf("GuildEmblemInfo OK\n"); + str=strchr(str + 1, '\t'); // 位置スキップ + + // 同盟リスト + if (sscanf(str + 1, "%d\t", &c) < 1) + return 1; + str = strchr(str + 1, '\t'); // 位置スキップ + for(i = 0; i < c; i++) { + struct guild_alliance *a = &g->alliance[i]; + if (sscanf(str + 1, "%d,%d\t%[^\t]\t", &tmp_int[0], &tmp_int[1], tmp_str[0]) < 3) + return 1; + a->guild_id = tmp_int[0]; + a->opposition = tmp_int[1]; + memcpy(a->name, tmp_str[0], 24); + + for(j = 0; j < 2 && str != NULL; j++) // 位置スキップ + str = strchr(str + 1, '\t'); + } +// printf("GuildAllianceInfo OK\n"); + // 追放リスト + if (sscanf(str+1, "%d\t", &c) < 1) + return 1; + str = strchr(str + 1, '\t'); // 位置スキップ + for(i = 0; i < c; i++) { + struct guild_explusion *e = &g->explusion[i]; + if (sscanf(str + 1, "%d,%d,%d,%d\t%[^\t]\t%[^\t]\t%[^\t]\t", + &tmp_int[0], &tmp_int[1], &tmp_int[2], &tmp_int[3], + tmp_str[0], tmp_str[1], tmp_str[2]) < 6) + return 1; + e->account_id = tmp_int[0]; + e->rsv1 = tmp_int[1]; + e->rsv2 = tmp_int[2]; + e->rsv3 = tmp_int[3]; + memcpy(e->name, tmp_str[0], 24); + memcpy(e->acc, tmp_str[1], 24); + tmp_str[2][strlen(tmp_str[2])-1] = 0; + memcpy(e->mes, tmp_str[2], 40); + + for(j = 0; j < 4 && str != NULL; j++) // 位置スキップ + str = strchr(str + 1, '\t'); + } +// printf("GuildExplusionInfo OK\n"); + // ギルドスキル + for(i = 0; i < MAX_GUILDSKILL; i++) { + if (sscanf(str+1,"%d,%d ", &tmp_int[0], &tmp_int[1]) < 2) + break; + g->skill[i].id = tmp_int[0]; + g->skill[i].lv = tmp_int[1]; + str = strchr(str + 1, ' '); + } + str = strchr(str + 1, '\t'); +// printf("GuildSkillInfo OK\n"); + + return 0; +} + +// ギルド城データの文字列への変換 +int inter_guildcastle_tostr(char *str, struct guild_castle *gc) { + int len; + + len = sprintf(str, "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d", // added Guardian HP [Valaris] + gc->castle_id, gc->guild_id, gc->economy, gc->defense, gc->triggerE, + gc->triggerD, gc->nextTime, gc->payTime, gc->createTime, gc->visibleC, + gc->visibleG0, gc->visibleG1, gc->visibleG2, gc->visibleG3, gc->visibleG4, + gc->visibleG5, gc->visibleG6, gc->visibleG7, gc->Ghp0, gc->Ghp1, gc->Ghp2, + gc->Ghp3, gc->Ghp4, gc->Ghp5, gc->Ghp6, gc->Ghp7); + + return 0; +} + +// ギルド城データの文字列からの変換 +int inter_guildcastle_fromstr(char *str, struct guild_castle *gc) { + int tmp_int[26]; + + memset(gc, 0, sizeof(struct guild_castle)); + // new structure of guild castle + if (sscanf(str, "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d", + &tmp_int[0], &tmp_int[1], &tmp_int[2], &tmp_int[3], &tmp_int[4], &tmp_int[5], &tmp_int[6], + &tmp_int[7], &tmp_int[8], &tmp_int[9], &tmp_int[10], &tmp_int[11], &tmp_int[12], &tmp_int[13], + &tmp_int[14], &tmp_int[15], &tmp_int[16], &tmp_int[17], &tmp_int[18], &tmp_int[19], &tmp_int[20], + &tmp_int[21], &tmp_int[22], &tmp_int[23], &tmp_int[24], &tmp_int[25]) == 26) { + gc->castle_id = tmp_int[0]; + gc->guild_id = tmp_int[1]; + gc->economy = tmp_int[2]; + gc->defense = tmp_int[3]; + gc->triggerE = tmp_int[4]; + gc->triggerD = tmp_int[5]; + gc->nextTime = tmp_int[6]; + gc->payTime = tmp_int[7]; + gc->createTime = tmp_int[8]; + gc->visibleC = tmp_int[9]; + gc->visibleG0 = tmp_int[10]; + gc->visibleG1 = tmp_int[11]; + gc->visibleG2 = tmp_int[12]; + gc->visibleG3 = tmp_int[13]; + gc->visibleG4 = tmp_int[14]; + gc->visibleG5 = tmp_int[15]; + gc->visibleG6 = tmp_int[16]; + gc->visibleG7 = tmp_int[17]; + gc->Ghp0 = tmp_int[18]; + gc->Ghp1 = tmp_int[19]; + gc->Ghp2 = tmp_int[20]; + gc->Ghp3 = tmp_int[21]; + gc->Ghp4 = tmp_int[22]; + gc->Ghp5 = tmp_int[23]; + gc->Ghp6 = tmp_int[24]; + gc->Ghp7 = tmp_int[25]; // end additions [Valaris] + // old structure of guild castle + } else if (sscanf(str, "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d", + &tmp_int[0], &tmp_int[1], &tmp_int[2], &tmp_int[3], &tmp_int[4], &tmp_int[5], &tmp_int[6], + &tmp_int[7], &tmp_int[8], &tmp_int[9], &tmp_int[10], &tmp_int[11], &tmp_int[12], &tmp_int[13], + &tmp_int[14], &tmp_int[15], &tmp_int[16], &tmp_int[17]) == 18) { + gc->castle_id = tmp_int[0]; + gc->guild_id = tmp_int[1]; + gc->economy = tmp_int[2]; + gc->defense = tmp_int[3]; + gc->triggerE = tmp_int[4]; + gc->triggerD = tmp_int[5]; + gc->nextTime = tmp_int[6]; + gc->payTime = tmp_int[7]; + gc->createTime = tmp_int[8]; + gc->visibleC = tmp_int[9]; + gc->visibleG0 = tmp_int[10]; + gc->visibleG1 = tmp_int[11]; + gc->visibleG2 = tmp_int[12]; + gc->visibleG3 = tmp_int[13]; + gc->visibleG4 = tmp_int[14]; + gc->visibleG5 = tmp_int[15]; + gc->visibleG6 = tmp_int[16]; + gc->visibleG7 = tmp_int[17]; + if (gc->visibleG0 == 1) + gc->Ghp0 = 15670 + 2000 * gc->defense; + else + gc->Ghp0 = 0; + if (gc->visibleG1 == 1) + gc->Ghp1 = 15670 + 2000 * gc->defense; + else + gc->Ghp1 = 0; + if (gc->visibleG2 == 1) + gc->Ghp2 = 15670 + 2000 * gc->defense; + else + gc->Ghp2 = 0; + if (gc->visibleG3 == 1) + gc->Ghp3 = 30214 + 2000 * gc->defense; + else + gc->Ghp3 = 0; + if (gc->visibleG4 == 1) + gc->Ghp4 = 30214 + 2000 * gc->defense; + else + gc->Ghp4 = 0; + if (gc->visibleG5 == 1) + gc->Ghp5 = 28634 + 2000 * gc->defense; + else + gc->Ghp5 = 0; + if (gc->visibleG6 == 1) + gc->Ghp6 = 28634 + 2000 * gc->defense; + else + gc->Ghp6 = 0; + if (gc->visibleG7 == 1) + gc->Ghp7 = 28634 + 2000 * gc->defense; + else + gc->Ghp7 = 0; + } else { + return 1; + } + + return 0; +} + +// ギルド関連データベース読み込み +int inter_guild_readdb() { + int i; + FILE *fp; + char line[1024]; + + fp = fopen("db/exp_guild.txt", "r"); + if (fp == NULL) { + printf("can't read db/exp_guild.txt\n"); + return 1; + } + i = 0; + while(fgets(line, sizeof(line)-1, fp) && i < 100){ + if (line[0] == '/' && line[1] == '/') + continue; + guild_exp[i] = atoi(line); + i++; + } + fclose(fp); + + return 0; +} + +// ギルドデータの読み込み +int inter_guild_init() { + char line[16384]; + struct guild *g; + struct guild_castle *gc; + FILE *fp; + int i, j, c = 0; + + inter_guild_readdb(); + + guild_db = numdb_init(); + castle_db = numdb_init(); + + if ((fp = fopen(guild_txt,"r")) == NULL) + return 1; + while(fgets(line, sizeof(line)-1, fp)) { + j = 0; + if (sscanf(line, "%d\t%%newid%%\n%n", &i, &j) == 1 && j > 0 && guild_newid <= i) { + guild_newid = i; + continue; + } + + g = calloc(sizeof(struct guild), 1); + if(g == NULL){ + printf("int_guild: out of memory!\n"); + exit(0); + } + memset(g, 0, sizeof(struct guild)); + if (inter_guild_fromstr(line, g) == 0 && g->guild_id > 0) { + if (g->guild_id >= guild_newid) + guild_newid = g->guild_id + 1; + numdb_insert(guild_db, g->guild_id, g); + guild_check_empty(g); + guild_calcinfo(g); + } else { + printf("int_guild: broken data [%s] line %d\n", guild_txt, c); + free(g); + } + c++; + } + fclose(fp); +// printf("int_guild: %s read done (%d guilds)\n", guild_txt, c); + + c = 0;//カウンタ初期化 + + if ((fp = fopen(castle_txt, "r")) == NULL) { + return 1; + } + + while(fgets(line, sizeof(line)-1, fp)) { + gc = calloc(sizeof(struct guild_castle), 1); + if(gc == NULL){ + printf("int_guild: out of memory!\n"); + exit(0); + } + memset(gc, 0, sizeof(struct guild_castle)); + if (inter_guildcastle_fromstr(line, gc) == 0) { + numdb_insert(castle_db, gc->castle_id, gc); + } else { + printf("int_guild: broken data [%s] line %d\n", castle_txt, c); + free(gc); + } + c++; + } + + if (!c) { + printf(" %s - making Default Data...\n", castle_txt); + //デフォルトデータを作成 + for(i = 0; i < MAX_GUILDCASTLE; i++) { + gc = calloc(sizeof(struct guild_castle), 1); + if (gc == NULL) { + printf("int_guild: out of memory!\n"); + exit(0); + } + memset(gc, 0, sizeof(struct guild_castle)); + gc->castle_id = i; + gc->guild_id = 0; + gc->economy = 0; + gc->defense = 0; + gc->triggerE = 0; + gc->triggerD = 0; + gc->nextTime = 0; + gc->payTime = 0; + gc->createTime = 0; + gc->visibleC = 0; + gc->visibleG0 = 0; + gc->visibleG1 = 0; + gc->visibleG2 = 0; + gc->visibleG3 = 0; + gc->visibleG4 = 0; + gc->visibleG5 = 0; + gc->visibleG6 = 0; + gc->visibleG7 = 0; + gc->Ghp0 = 0; // guardian HP [Valaris] + gc->Ghp1 = 0; + gc->Ghp2 = 0; + gc->Ghp3 = 0; + gc->Ghp4 = 0; + gc->Ghp5 = 0; + gc->Ghp6 = 0; + gc->Ghp7 = 0; // end additions [Valaris] + numdb_insert(castle_db, gc->castle_id, gc); + } + printf(" %s - making done\n",castle_txt); + return 0; + } + + fclose(fp); + + return 0; +} + +struct guild *inter_guild_search(int guild_id) { + struct guild *g; + + g=numdb_search(guild_db, guild_id); + + return g; +} + +// ギルドデータのセーブ用 +int inter_guild_save_sub(void *key,void *data,va_list ap) { + char line[16384]; + FILE *fp; + + inter_guild_tostr(line,(struct guild *)data); + fp=va_arg(ap,FILE *); + fprintf(fp,"%s" RETCODE,line); + + return 0; +} + +// ギルド城データのセーブ用 +int inter_castle_save_sub(void *key, void *data, va_list ap) { + char line[16384]; + FILE *fp; + + inter_guildcastle_tostr(line, (struct guild_castle *)data); + fp = va_arg(ap, FILE *); + fprintf(fp, "%s" RETCODE, line); + + return 0; +} + +// ギルドデータのセーブ +int inter_guild_save() { + FILE *fp; + int lock; + + if ((fp = lock_fopen(guild_txt, &lock)) == NULL) { + printf("int_guild: cant write [%s] !!! data is lost !!!\n", guild_txt); + return 1; + } + numdb_foreach(guild_db, inter_guild_save_sub, fp); +// fprintf(fp, "%d\t%%newid%%\n", guild_newid); + lock_fclose(fp, guild_txt, &lock); +// printf("int_guild: %s saved.\n", guild_txt); + + if ((fp = lock_fopen(castle_txt,&lock)) == NULL) { + printf("int_guild: cant write [%s] !!! data is lost !!!\n", castle_txt); + return 1; + } + numdb_foreach(castle_db, inter_castle_save_sub, fp); + lock_fclose(fp, castle_txt, &lock); + + return 0; +} + +// ギルド名検索用 +int search_guildname_sub(void *key, void *data, va_list ap) { + struct guild *g = (struct guild *)data, **dst; + char *str; + + str = va_arg(ap, char *); + dst = va_arg(ap, struct guild **); + if (strcmpi(g->name, str) == 0) + *dst = g; + return 0; +} + +// ギルド名検索 +struct guild* search_guildname(char *str) { + struct guild *g = NULL; + numdb_foreach(guild_db, search_guildname_sub, str, &g); + return g; +} + +// ギルドが空かどうかチェック +int guild_check_empty(struct guild *g) { + int i; + + for(i = 0; i < g->max_member; i++) { + if (g->member[i].account_id > 0) { + return 0; + } + } + // 誰もいないので解散 + numdb_foreach(guild_db, guild_break_sub, g->guild_id); + numdb_erase(guild_db, g->guild_id); + inter_guild_storage_delete(g->guild_id); + mapif_guild_broken(g->guild_id, 0); + free(g); + + return 1; +} + +// キャラの競合がないかチェック用 +int guild_check_conflict_sub(void *key, void *data, va_list ap) { + struct guild *g = (struct guild *)data; + int guild_id, account_id, char_id, i; + + guild_id = va_arg(ap, int); + account_id = va_arg(ap, int); + char_id = va_arg(ap, int); + + if (g->guild_id == guild_id) // 本来の所属なので問題なし + return 0; + + for(i = 0; i < MAX_GUILD; i++) { + if (g->member[i].account_id == account_id && g->member[i].char_id == char_id) { + // 別のギルドに偽の所属データがあるので脱退 + printf("int_guild: guild conflict! %d,%d %d!=%d\n", account_id, char_id, guild_id, g->guild_id); + mapif_parse_GuildLeave(-1, g->guild_id, account_id, char_id, 0, "**データ競合**"); + } + } + + return 0; +} +// キャラの競合がないかチェック +int guild_check_conflict(int guild_id, int account_id, int char_id) { + numdb_foreach(guild_db, guild_check_conflict_sub, guild_id, account_id, char_id); + + return 0; +} + +int guild_nextexp(int level) { + if (level < 100) + return guild_exp[level-1]; + + return 0; +} + +// ギルドスキルがあるか確認 +int guild_checkskill(struct guild *g, int id){ + return g->skill[id-10000].lv; +} + +// ギルドの情報の再計算 +int guild_calcinfo(struct guild *g) { + int i, c, nextexp; + struct guild before = *g; + + // スキルIDの設定 + for(i = 0; i < 5; i++) + g->skill[i].id = i + 10000; + + // ギルドレベル + if (g->guild_lv <= 0) + g->guild_lv = 1; + nextexp = guild_nextexp(g->guild_lv); + if (nextexp > 0) { + while(g->exp >= nextexp) { // レベルアップ処理 + g->exp -= nextexp; + g->guild_lv++; + g->skill_point++; + nextexp = guild_nextexp(g->guild_lv); + } + } + + // ギルドの次の経験値 + g->next_exp = guild_nextexp(g->guild_lv); + + // メンバ上限(ギルド拡張適用) + g->max_member = 16 + guild_checkskill(g, 10004) * 2; + + // 平均レベルとオンライン人数 + g->average_lv = 0; + g->connect_member = 0; + c = 0; + for(i = 0; i < g->max_member; i++) { + if (g->member[i].account_id > 0) { + g->average_lv += g->member[i].lv; + c++; + if (g->member[i].online > 0) + g->connect_member++; + } + } + g->average_lv /= c; + + // 全データを送る必要がありそう + if (g->max_member != before.max_member || + g->guild_lv != before.guild_lv || + g->skill_point != before.skill_point) { + mapif_guild_info(-1, g); + return 1; + } + + return 0; +} + +//------------------------------------------------------------------- +// map serverへの通信 + +// ギルド作成可否 +int mapif_guild_created(int fd, int account_id, struct guild *g) { + WFIFOW(fd,0) = 0x3830; + WFIFOL(fd,2) = account_id; + if (g != NULL) { + WFIFOL(fd,6) = g->guild_id; + printf("int_guild: created! %d %s\n", g->guild_id, g->name); + }else{ + WFIFOL(fd,6) = 0; + } + WFIFOSET(fd,10); + return 0; +} + +// ギルド情報見つからず +int mapif_guild_noinfo(int fd, int guild_id) { + WFIFOW(fd,0) = 0x3831; + WFIFOW(fd,2) = 8; + WFIFOL(fd,4) = guild_id; + WFIFOSET(fd,8); + printf("int_guild: info not found %d\n", guild_id); + + return 0; +} + +// ギルド情報まとめ送り +int mapif_guild_info(int fd, struct guild *g) { + unsigned char buf[4 + sizeof(struct guild)]; + + WBUFW(buf,0) = 0x3831; + memcpy(buf + 4, g, sizeof(struct guild)); + WBUFW(buf,2) = 4 + sizeof(struct guild); +// printf("int_guild: sizeof(guild)=%d\n", sizeof(struct guild)); + if (fd < 0) + mapif_sendall(buf, WBUFW(buf,2)); + else + mapif_send(fd, buf, WBUFW(buf,2)); +// printf("int_guild: info %d %s\n", p->guild_id, p->name); + + return 0; +} + +// メンバ追加可否 +int mapif_guild_memberadded(int fd, int guild_id, int account_id, int char_id, int flag) { + WFIFOW(fd,0) = 0x3832; + WFIFOL(fd,2) = guild_id; + WFIFOL(fd,6) = account_id; + WFIFOL(fd,10) = char_id; + WFIFOB(fd,14) = flag; + WFIFOSET(fd, 15); + + return 0; +} + +// 脱退/追放通知 +int mapif_guild_leaved(int guild_id, int account_id, int char_id, int flag, const char *name, const char *mes) { + unsigned char buf[79]; + + WBUFW(buf, 0) = 0x3834; + WBUFL(buf, 2) = guild_id; + WBUFL(buf, 6) = account_id; + WBUFL(buf,10) = char_id; + WBUFB(buf,14) = flag; + memcpy(WBUFP(buf,15), mes, 40); + memcpy(WBUFP(buf,55), name, 24); + mapif_sendall(buf, 79); + printf("int_guild: guild leaved %d %d %s %s\n", guild_id, account_id, name, mes); + + return 0; +} + +// オンライン状態とLv更新通知 +int mapif_guild_memberinfoshort(struct guild *g, int idx) { + unsigned char buf[19]; + + WBUFW(buf, 0) = 0x3835; + WBUFL(buf, 2) = g->guild_id; + WBUFL(buf, 6) = g->member[idx].account_id; + WBUFL(buf,10) = g->member[idx].char_id; + WBUFB(buf,14) = g->member[idx].online; + WBUFW(buf,15) = g->member[idx].lv; + WBUFW(buf,17) = g->member[idx].class; + mapif_sendall(buf, 19); + return 0; +} + +// 解散通知 +int mapif_guild_broken(int guild_id, int flag) { + unsigned char buf[7]; + + WBUFW(buf,0) = 0x3836; + WBUFL(buf,2) = guild_id; + WBUFB(buf,6) = flag; + mapif_sendall(buf, 7); + printf("int_guild: broken %d\n", guild_id); + + return 0; +} + +// ギルド内発言 +int mapif_guild_message(int guild_id, int account_id, char *mes, int len) { + unsigned char buf[len+12]; + + WBUFW(buf,0) = 0x3837; + WBUFW(buf,2) = len + 12; + WBUFL(buf,4) = guild_id; + WBUFL(buf,8) = account_id; + memcpy(WBUFP(buf,12), mes, len); + mapif_sendall(buf, len + 12); + + return 0; +} + +// ギルド基本情報変更通知 +int mapif_guild_basicinfochanged(int guild_id, int type, const void *data, int len) { + unsigned char buf[2048]; + + WBUFW(buf,0) = 0x3839; + WBUFW(buf,2) = len+10; + WBUFL(buf,4) = guild_id; + WBUFW(buf,8) = type; + memcpy(WBUFP(buf,10),data,len); + mapif_sendall(buf,len+10); + return 0; +} + +// ギルドメンバ情報変更通知 +int mapif_guild_memberinfochanged(int guild_id, int account_id, int char_id, int type, const void *data, int len) { + unsigned char buf[len + 18]; + + WBUFW(buf, 0) = 0x383a; + WBUFW(buf, 2) = len + 18; + WBUFL(buf, 4) = guild_id; + WBUFL(buf, 8) = account_id; + WBUFL(buf,12) = char_id; + WBUFW(buf,16) = type; + memcpy(WBUFP(buf,18), data, len); + mapif_sendall(buf,len+18); + + return 0; +} + +// ギルドスキルアップ通知 +int mapif_guild_skillupack(int guild_id, int skill_num, int account_id) { + unsigned char buf[14]; + + WBUFW(buf, 0) = 0x383c; + WBUFL(buf, 2) = guild_id; + WBUFL(buf, 6) = skill_num; + WBUFL(buf,10) = account_id; + mapif_sendall(buf, 14); + + return 0; +} + +// ギルド同盟/敵対通知 +int mapif_guild_alliance(int guild_id1, int guild_id2, int account_id1, int account_id2, int flag, const char *name1, const char *name2) { + unsigned char buf[67]; + + WBUFW(buf, 0) = 0x383d; + WBUFL(buf, 2) = guild_id1; + WBUFL(buf, 6) = guild_id2; + WBUFL(buf,10) = account_id1; + WBUFL(buf,14) = account_id2; + WBUFB(buf,18) = flag; + memcpy(WBUFP(buf,19), name1, 24); + memcpy(WBUFP(buf,43), name2, 24); + mapif_sendall(buf, 67); + + return 0; +} + +// ギルド役職変更通知 +int mapif_guild_position(struct guild *g, int idx) { + unsigned char buf[sizeof(struct guild_position) + 12]; + + WBUFW(buf,0) = 0x383b; + WBUFW(buf,2) = sizeof(struct guild_position) + 12; + WBUFL(buf,4) = g->guild_id; + WBUFL(buf,8) = idx; + memcpy(WBUFP(buf,12), &g->position[idx], sizeof(struct guild_position)); + mapif_sendall(buf, WBUFW(buf,2)); + + return 0; +} + +// ギルド告知変更通知 +int mapif_guild_notice(struct guild *g) { + unsigned char buf[186]; + + WBUFW(buf,0) = 0x383e; + WBUFL(buf,2) = g->guild_id; + memcpy(WBUFP(buf,6), g->mes1, 60); + memcpy(WBUFP(buf,66), g->mes2, 120); + mapif_sendall(buf, 186); + + return 0; +} + +// ギルドエンブレム変更通知 +int mapif_guild_emblem(struct guild *g) { + unsigned char buf[2048]; + + WBUFW(buf,0) = 0x383f; + WBUFW(buf,2) = g->emblem_len + 12; + WBUFL(buf,4) = g->guild_id; + WBUFL(buf,8) = g->emblem_id; + memcpy(WBUFP(buf,12), g->emblem_data, g->emblem_len); + mapif_sendall(buf, WBUFW(buf,2)); + + return 0; +} + +int mapif_guild_castle_dataload(int castle_id, int index, int value) { + unsigned char buf[9]; + + WBUFW(buf,0) = 0x3840; + WBUFW(buf,2) = castle_id; + WBUFB(buf,4) = index; + WBUFL(buf,5) = value; + mapif_sendall(buf,9); + + return 0; +} + +int mapif_guild_castle_datasave(int castle_id, int index, int value) { + unsigned char buf[9]; + + WBUFW(buf,0) = 0x3841; + WBUFW(buf,2) = castle_id; + WBUFB(buf,4) = index; + WBUFL(buf,5) = value; + mapif_sendall(buf,9); + + return 0; +} + +int mapif_guild_castle_alldataload_sub(void *key, void *data, va_list ap) { + int fd = va_arg(ap, int); + int *p = va_arg(ap, int*); + + memcpy(WFIFOP(fd,*p), (struct guild_castle*)data, sizeof(struct guild_castle)); + (*p) += sizeof(struct guild_castle); + + return 0; +} + +int mapif_guild_castle_alldataload(int fd) { + int len = 4; + + WFIFOW(fd,0) = 0x3842; + numdb_foreach(castle_db, mapif_guild_castle_alldataload_sub, fd, &len); + WFIFOW(fd,2) = len; + WFIFOSET(fd, len); + + return 0; +} + +//------------------------------------------------------------------- +// map serverからの通信 + +// ギルド作成要求 +int mapif_parse_CreateGuild(int fd, int account_id, char *name, struct guild_member *master) { + struct guild *g; + int i; + + for(i = 0; i < 24 && name[i]; i++) { + if (!(name[i] & 0xe0) || name[i] == 0x7f) { + printf("int_guild: illeagal guild name [%s]\n", name); + mapif_guild_created(fd, account_id, NULL); + return 0; + } + } + + if ((g = search_guildname(name)) != NULL) { + printf("int_guild: same name guild exists [%s]\n", name); + mapif_guild_created(fd, account_id, NULL); + return 0; + } + g = calloc(sizeof(struct guild), 1); + if (g == NULL) { + printf("int_guild: CreateGuild: out of memory !\n"); + mapif_guild_created(fd, account_id, NULL); + exit(0); + } + memset(g, 0, sizeof(struct guild)); + g->guild_id = guild_newid++; + memcpy(g->name, name, 24); + memcpy(g->master, master->name, 24); + memcpy(&g->member[0], master, sizeof(struct guild_member)); + + g->position[0].mode = 0x11; + strcpy(g->position[ 0].name, "GuildMaster"); + strcpy(g->position[MAX_GUILDPOSITION-1].name, "Newbie"); + for(i = 1; i < MAX_GUILDPOSITION-1; i++) + sprintf(g->position[i].name, "Position %d", i + 1); + + // ここでギルド情報計算が必要と思われる + g->max_member = 16; + g->average_lv = master->lv; + for(i = 0; i < 5; i++) + g->skill[i].id = i + 10000; + + numdb_insert(guild_db, g->guild_id, g); + + mapif_guild_created(fd, account_id, g); + mapif_guild_info(fd, g); + + inter_log("guild %s (id=%d) created by master %s (id=%d)" RETCODE, + name, g->guild_id, master->name, master->account_id); + + return 0; +} + +// ギルド情報要求 +int mapif_parse_GuildInfo(int fd, int guild_id) { + struct guild *g; + + g = numdb_search(guild_db, guild_id); + if (g != NULL){ + guild_calcinfo(g); + mapif_guild_info(fd, g); + } else + mapif_guild_noinfo(fd, guild_id); + + return 0; +} + +// ギルドメンバ追加要求 +int mapif_parse_GuildAddMember(int fd, int guild_id, struct guild_member *m) { + struct guild *g; + int i; + + g = numdb_search(guild_db, guild_id); + if (g == NULL) { + mapif_guild_memberadded(fd, guild_id, m->account_id, m->char_id, 1); + return 0; + } + + for(i = 0; i < g->max_member; i++) { + if (g->member[i].account_id == 0) { + memcpy(&g->member[i], m, sizeof(struct guild_member)); + mapif_guild_memberadded(fd, guild_id, m->account_id, m->char_id, 0); + guild_calcinfo(g); + mapif_guild_info(-1, g); + + return 0; + } + } + mapif_guild_memberadded(fd, guild_id, m->account_id, m->char_id, 1); + + return 0; +} + +// ギルド脱退/追放要求 +int mapif_parse_GuildLeave(int fd, int guild_id, int account_id, int char_id, int flag, const char *mes) { + struct guild *g = NULL; + int i, j; + + g = numdb_search(guild_db, guild_id); + if (g != NULL) { + for(i = 0; i < MAX_GUILD; i++) { + if (g->member[i].account_id == account_id && g->member[i].char_id == char_id) { +// printf("%d %d\n", i, (int)(&g->member[i])); +// printf("%d %s\n", i, g->member[i].name); + + if (flag) { // 追放の場合追放リストに入れる + for(j = 0; j < MAX_GUILDEXPLUSION; j++) { + if (g->explusion[j].account_id == 0) + break; + } + if (j == MAX_GUILDEXPLUSION) { // 一杯なので古いのを消す + for(j = 0; j < MAX_GUILDEXPLUSION - 1; j++) + g->explusion[j] = g->explusion[j+1]; + j = MAX_GUILDEXPLUSION - 1; + } + g->explusion[j].account_id = account_id; + memcpy(g->explusion[j].acc, "dummy", 24); + memcpy(g->explusion[j].name, g->member[i].name, 24); + memcpy(g->explusion[j].mes, mes, 40); + } + + mapif_guild_leaved(guild_id, account_id, char_id, flag, g->member[i].name, mes); +// printf("%d %d\n", i, (int)(&g->member[i])); +// printf("%d %s\n", i, (&g->member[i])->name); + memset(&g->member[i], 0, sizeof(struct guild_member)); + + if (guild_check_empty(g) == 0) + mapif_guild_info(-1,g);// まだ人がいるのでデータ送信 + + return 0; + } + } + } + return 0; +} + +// オンライン/Lv更新 +int mapif_parse_GuildChangeMemberInfoShort(int fd, int guild_id, int account_id, int char_id, int online, int lv, int class) { + struct guild *g; + int i, alv, c; + + g = numdb_search(guild_db, guild_id); + if (g == NULL) + return 0; + + g->connect_member = 0; + + alv = 0; + c = 0; + for(i = 0; i < MAX_GUILD; i++) { + if (g->member[i].account_id == account_id && g->member[i].char_id == char_id) { + g->member[i].online = online; + g->member[i].lv = lv; + g->member[i].class = class; + mapif_guild_memberinfoshort(g, i); + } + if (g->member[i].account_id > 0) { + alv += g->member[i].lv; + c++; + } + if (g->member[i].online) + g->connect_member++; + } + // 平均レベル + g->average_lv = alv / c; + + return 0; +} + +// ギルド解散処理用(同盟/敵対を解除) +int guild_break_sub(void *key, void *data, va_list ap) { + struct guild *g = (struct guild *)data; + int guild_id = va_arg(ap, int); + int i; + + for(i = 0; i < MAX_GUILDALLIANCE; i++) { + if (g->alliance[i].guild_id == guild_id) + g->alliance[i].guild_id = 0; + } + return 0; +} + +// ギルド解散要求 +int mapif_parse_BreakGuild(int fd, int guild_id) { + struct guild *g; + + g = numdb_search(guild_db, guild_id); + if(g == NULL) + return 0; + + numdb_foreach(guild_db, guild_break_sub, guild_id); + numdb_erase(guild_db, guild_id); + inter_guild_storage_delete(guild_id); + mapif_guild_broken(guild_id, 0); + + inter_log("guild %s (id=%d) broken" RETCODE, g->name, guild_id); + free(g); + + return 0; +} + +// ギルドメッセージ送信 +int mapif_parse_GuildMessage(int fd, int guild_id, int account_id, char *mes, int len) { + return mapif_guild_message(guild_id, account_id, mes, len); +} + +// ギルド基本データ変更要求 +int mapif_parse_GuildBasicInfoChange(int fd, int guild_id, int type, const char *data, int len) { + struct guild *g; + short dw = *((short *)data); + + g = numdb_search(guild_db, guild_id); + if (g == NULL) + return 0; + + switch(type) { + case GBI_GUILDLV: + if (dw > 0 && g->guild_lv + dw <= 50) { + g->guild_lv+=dw; + g->skill_point+=dw; + } else if (dw < 0 && g->guild_lv + dw >= 1) + g->guild_lv += dw; + mapif_guild_info(-1, g); + return 0; + default: + printf("int_guild: GuildBasicInfoChange: Unknown type %d\n", type); + break; + } + mapif_guild_basicinfochanged(guild_id, type, data, len); + + return 0; +} + +// ギルドメンバデータ変更要求 +int mapif_parse_GuildMemberInfoChange(int fd, int guild_id, int account_id, int char_id, int type, const char *data, int len) { + int i; + struct guild *g; + + g = numdb_search(guild_db, guild_id); + if(g == NULL) + return 0; + + for(i = 0; i < g->max_member; i++) + if (g->member[i].account_id == account_id && g->member[i].char_id == char_id) + break; + if (i == g->max_member) { + printf("int_guild: GuildMemberChange: Not found %d,%d in %d[%s]\n", account_id, char_id, guild_id, g->name); + return 0; + } + switch(type) { + case GMI_POSITION: // 役職 + g->member[i].position = *((int *)data); + break; + case GMI_EXP: // EXP + { + int exp, oldexp = g->member[i].exp; + exp = g->member[i].exp = *((unsigned int *)data); + g->exp += (exp - oldexp); + guild_calcinfo(g); // Lvアップ判断 + mapif_guild_basicinfochanged(guild_id, GBI_EXP, &g->exp, 4); + } + break; + default: + printf("int_guild: GuildMemberInfoChange: Unknown type %d\n", type); + break; + } + mapif_guild_memberinfochanged(guild_id, account_id, char_id, type, data, len); + + return 0; +} + +// ギルド役職名変更要求 +int mapif_parse_GuildPosition(int fd, int guild_id, int idx, struct guild_position *p) { + struct guild *g = numdb_search(guild_db, guild_id); + + if (g == NULL || idx < 0 || idx >= MAX_GUILDPOSITION) { + return 0; + } + memcpy(&g->position[idx], p, sizeof(struct guild_position)); + mapif_guild_position(g, idx); + printf("int_guild: position changed %d\n", idx); + + return 0; +} + +// ギルドスキルアップ要求 +int mapif_parse_GuildSkillUp(int fd, int guild_id, int skill_num, int account_id) { + struct guild *g = numdb_search(guild_db, guild_id); + int idx = skill_num - 10000; + + if (g == NULL || skill_num < 10000) + return 0; + + if (g->skill_point > 0 && g->skill[idx].id > 0 && g->skill[idx].lv < 10) { + g->skill[idx].lv++; + g->skill_point--; + if (guild_calcinfo(g) == 0) + mapif_guild_info(-1, g); + mapif_guild_skillupack(guild_id, skill_num, account_id); + printf("int_guild: skill %d up\n", skill_num); + } + + return 0; +} + +// ギルド同盟要求 +int mapif_parse_GuildAlliance(int fd, int guild_id1, int guild_id2, int account_id1, int account_id2, int flag) { + struct guild *g[2]; + int j, i; + + g[0] = numdb_search(guild_db, guild_id1); + g[1] = numdb_search(guild_db, guild_id2); + if (g[0] == NULL || g[1] == NULL) + return 0; + + if (!(flag & 0x8)) { + for(i = 0; i < 2 - (flag & 1); i++) { + for(j = 0; j < MAX_GUILDALLIANCE; j++) + if (g[i]->alliance[j].guild_id == 0) { + g[i]->alliance[j].guild_id = g[1-i]->guild_id; + memcpy(g[i]->alliance[j].name, g[1-i]->name, 24); + g[i]->alliance[j].opposition = flag & 1; + break; + } + } + } else { // 関係解消 + for(i = 0; i < 2 - (flag & 1); i++) { + for(j = 0; j < MAX_GUILDALLIANCE; j++) + if (g[i]->alliance[j].guild_id == g[1-i]->guild_id && g[i]->alliance[j].opposition == (flag & 1)) { + g[i]->alliance[j].guild_id = 0; + break; + } + } + } + mapif_guild_alliance(guild_id1, guild_id2, account_id1, account_id2, flag, g[0]->name, g[1]->name); + + return 0; +} + +// ギルド告知変更要求 +int mapif_parse_GuildNotice(int fd, int guild_id, const char *mes1, const char *mes2) { + struct guild *g; + + g = numdb_search(guild_db, guild_id); + if (g == NULL) + return 0; + memcpy(g->mes1, mes1, 60); + memcpy(g->mes2, mes2, 120); + + return mapif_guild_notice(g); +} + +// ギルドエンブレム変更要求 +int mapif_parse_GuildEmblem(int fd, int len, int guild_id, int dummy, const char *data) { + struct guild *g; + + g = numdb_search(guild_db, guild_id); + if (g == NULL) + return 0; + memcpy(g->emblem_data, data, len); + g->emblem_len = len; + g->emblem_id++; + + return mapif_guild_emblem(g); +} + +int mapif_parse_GuildCastleDataLoad(int fd, int castle_id, int index) { + struct guild_castle *gc = numdb_search(castle_db, castle_id); + + if (gc == NULL) { + return mapif_guild_castle_dataload(castle_id, 0, 0); + } + switch(index) { + case 1: return mapif_guild_castle_dataload(gc->castle_id, index, gc->guild_id); + case 2: return mapif_guild_castle_dataload(gc->castle_id, index, gc->economy); + case 3: return mapif_guild_castle_dataload(gc->castle_id, index, gc->defense); + case 4: return mapif_guild_castle_dataload(gc->castle_id, index, gc->triggerE); + case 5: return mapif_guild_castle_dataload(gc->castle_id, index, gc->triggerD); + case 6: return mapif_guild_castle_dataload(gc->castle_id, index, gc->nextTime); + case 7: return mapif_guild_castle_dataload(gc->castle_id, index, gc->payTime); + case 8: return mapif_guild_castle_dataload(gc->castle_id, index, gc->createTime); + case 9: return mapif_guild_castle_dataload(gc->castle_id, index, gc->visibleC); + case 10: return mapif_guild_castle_dataload(gc->castle_id, index, gc->visibleG0); + case 11: return mapif_guild_castle_dataload(gc->castle_id, index, gc->visibleG1); + case 12: return mapif_guild_castle_dataload(gc->castle_id, index, gc->visibleG2); + case 13: return mapif_guild_castle_dataload(gc->castle_id, index, gc->visibleG3); + case 14: return mapif_guild_castle_dataload(gc->castle_id, index, gc->visibleG4); + case 15: return mapif_guild_castle_dataload(gc->castle_id, index, gc->visibleG5); + case 16: return mapif_guild_castle_dataload(gc->castle_id, index, gc->visibleG6); + case 17: return mapif_guild_castle_dataload(gc->castle_id, index, gc->visibleG7); + case 18: return mapif_guild_castle_dataload(gc->castle_id, index, gc->Ghp0); // guardian HP [Valaris] + case 19: return mapif_guild_castle_dataload(gc->castle_id, index, gc->Ghp1); + case 20: return mapif_guild_castle_dataload(gc->castle_id, index, gc->Ghp2); + case 21: return mapif_guild_castle_dataload(gc->castle_id, index, gc->Ghp3); + case 22: return mapif_guild_castle_dataload(gc->castle_id, index, gc->Ghp4); + case 23: return mapif_guild_castle_dataload(gc->castle_id, index, gc->Ghp5); + case 24: return mapif_guild_castle_dataload(gc->castle_id, index, gc->Ghp6); + case 25: return mapif_guild_castle_dataload(gc->castle_id, index, gc->Ghp7); // end additions [Valaris] + + default: + printf("mapif_parse_GuildCastleDataLoad ERROR!! (Not found index=%d)\n", index); + return 0; + } + + return 0; +} + +int mapif_parse_GuildCastleDataSave(int fd, int castle_id, int index, int value) { + struct guild_castle *gc=numdb_search(castle_db, castle_id); + + if (gc == NULL) { + return mapif_guild_castle_datasave(castle_id, index, value); + } + switch(index) { + case 1: + if (gc->guild_id != value) { + int gid = (value) ? value : gc->guild_id; + struct guild *g = numdb_search(guild_db, gid); + inter_log("guild %s (id=%d) %s castle id=%d" RETCODE, + (g) ? g->name : "??", gid, (value) ? "occupy" : "abandon", index); + } + gc->guild_id = value; + break; + case 2: gc->economy = value; break; + case 3: gc->defense = value; break; + case 4: gc->triggerE = value; break; + case 5: gc->triggerD = value; break; + case 6: gc->nextTime = value; break; + case 7: gc->payTime = value; break; + case 8: gc->createTime = value; break; + case 9: gc->visibleC = value; break; + case 10: gc->visibleG0 = value; break; + case 11: gc->visibleG1 = value; break; + case 12: gc->visibleG2 = value; break; + case 13: gc->visibleG3 = value; break; + case 14: gc->visibleG4 = value; break; + case 15: gc->visibleG5 = value; break; + case 16: gc->visibleG6 = value; break; + case 17: gc->visibleG7 = value; break; + case 18: gc->Ghp0 = value; break; // guardian HP [Valaris] + case 19: gc->Ghp1 = value; break; + case 20: gc->Ghp2 = value; break; + case 21: gc->Ghp3 = value; break; + case 22: gc->Ghp4 = value; break; + case 23: gc->Ghp5 = value; break; + case 24: gc->Ghp6 = value; break; + case 25: gc->Ghp7 = value; break; // end additions [Valaris] + default: + printf("mapif_parse_GuildCastleDataSave ERROR!! (Not found index=%d)\n", index); + return 0; + } + + return mapif_guild_castle_datasave(gc->castle_id, index, value); +} + +// ギルドチェック要求 +int mapif_parse_GuildCheck(int fd, int guild_id, int account_id, int char_id) { + return guild_check_conflict(guild_id, account_id, char_id); +} + +// map server からの通信 +// ・1パケットのみ解析すること +// ・パケット長データはinter.cにセットしておくこと +// ・パケット長チェックや、RFIFOSKIPは呼び出し元で行われるので行ってはならない +// ・エラーなら0(false)、そうでないなら1(true)をかえさなければならない +int inter_guild_parse_frommap(int fd) { + switch(RFIFOW(fd,0)) { + case 0x3030: mapif_parse_CreateGuild(fd, RFIFOL(fd,4), RFIFOP(fd,8), (struct guild_member *)RFIFOP(fd,32)); break; + case 0x3031: mapif_parse_GuildInfo(fd, RFIFOL(fd,2)); break; + case 0x3032: mapif_parse_GuildAddMember(fd, RFIFOL(fd,4), (struct guild_member *)RFIFOP(fd,8)); break; + case 0x3034: mapif_parse_GuildLeave(fd, RFIFOL(fd,2), RFIFOL(fd,6), RFIFOL(fd,10), RFIFOB(fd,14), RFIFOP(fd,15)); break; + case 0x3035: mapif_parse_GuildChangeMemberInfoShort(fd, RFIFOL(fd,2), RFIFOL(fd,6), RFIFOL(fd,10), RFIFOB(fd,14), RFIFOW(fd,15), RFIFOW(fd,17)); break; + case 0x3036: mapif_parse_BreakGuild(fd, RFIFOL(fd,2)); break; + case 0x3037: mapif_parse_GuildMessage(fd, RFIFOL(fd,4), RFIFOL(fd,8), RFIFOP(fd,12), RFIFOW(fd,2)-12); break; + case 0x3038: mapif_parse_GuildCheck(fd, RFIFOL(fd,2), RFIFOL(fd,6), RFIFOL(fd,10)); break; + case 0x3039: mapif_parse_GuildBasicInfoChange(fd, RFIFOL(fd,4), RFIFOW(fd,8), RFIFOP(fd,10), RFIFOW(fd,2)-10); break; + case 0x303A: mapif_parse_GuildMemberInfoChange(fd, RFIFOL(fd,4), RFIFOL(fd,8), RFIFOL(fd,12), RFIFOW(fd,16), RFIFOP(fd,18), RFIFOW(fd,2)-18); break; + case 0x303B: mapif_parse_GuildPosition(fd, RFIFOL(fd,4), RFIFOL(fd,8), (struct guild_position *)RFIFOP(fd,12)); break; + case 0x303C: mapif_parse_GuildSkillUp(fd, RFIFOL(fd,2), RFIFOL(fd,6), RFIFOL(fd,10)); break; + case 0x303D: mapif_parse_GuildAlliance(fd, RFIFOL(fd,2), RFIFOL(fd,6), RFIFOL(fd,10), RFIFOL(fd,14), RFIFOB(fd,18)); break; + case 0x303E: mapif_parse_GuildNotice(fd, RFIFOL(fd,2), RFIFOP(fd,6), RFIFOP(fd,66)); break; + case 0x303F: mapif_parse_GuildEmblem(fd, RFIFOW(fd,2)-12, RFIFOL(fd,4), RFIFOL(fd,8), RFIFOP(fd,12)); break; + case 0x3040: mapif_parse_GuildCastleDataLoad(fd, RFIFOW(fd,2), RFIFOB(fd,4)); break; + case 0x3041: mapif_parse_GuildCastleDataSave(fd, RFIFOW(fd,2), RFIFOB(fd,4), RFIFOL(fd,5)); break; + + default: + return 0; + } + + return 1; +} + +// マップサーバーの接続時処理 +int inter_guild_mapif_init(int fd) { + return mapif_guild_castle_alldataload(fd); +} + +// サーバーから脱退要求(キャラ削除用) +int inter_guild_leave(int guild_id, int account_id, int char_id) { + return mapif_parse_GuildLeave(-1, guild_id, account_id, char_id, 0, "**サーバー命令**"); +} diff --git a/misc/src/char/int_guild.h b/misc/src/char/int_guild.h new file mode 100644 index 0000000..555f5e1 --- /dev/null +++ b/misc/src/char/int_guild.h @@ -0,0 +1,16 @@ +// $Id: int_guild.h,v 1.1.1.1 2004/09/10 17:26:51 MagicalTux Exp $ +#ifndef _INT_GUILD_H_ +#define _INT_GUILD_H_ + +int inter_guild_init(); +int inter_guild_save(); +int inter_guild_parse_frommap(int fd); +struct guild *inter_guild_search(int guild_id); +int inter_guild_mapif_init(int fd); + +int inter_guild_leave(int guild_id,int account_id,int char_id); + +extern char guild_txt[1024]; +extern char castle_txt[1024]; + +#endif diff --git a/misc/src/char/int_party.c b/misc/src/char/int_party.c new file mode 100644 index 0000000..0fd58fa --- /dev/null +++ b/misc/src/char/int_party.c @@ -0,0 +1,595 @@ +// $Id: int_party.c,v 1.1.1.1 2004/09/10 17:26:51 MagicalTux Exp $ +#include "inter.h" +#include "int_party.h" +#include "mmo.h" +#include "char.h" +#include "socket.h" +#include "db.h" +#include "lock.h" +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +char party_txt[1024] = "save/party.txt"; + +static struct dbt *party_db; +static int party_newid = 100; + +int mapif_party_broken(int party_id, int flag); +int party_check_empty(struct party *p); +int mapif_parse_PartyLeave(int fd, int party_id, int account_id); + +// パーティデータの文字列への変換 +int inter_party_tostr(char *str, struct party *p) { + int i, len; + + len = sprintf(str, "%d\t%s\t%d,%d\t", p->party_id, p->name, p->exp, p->item); + for(i = 0; i < MAX_PARTY; i++) { + struct party_member *m = &p->member[i]; + len += sprintf(str + len, "%d,%d\t%s\t", m->account_id, m->leader, ((m->account_id > 0) ? m->name : "NoMember")); + } + + return 0; +} + +// パーティデータの文字列からの変換 +int inter_party_fromstr(char *str, struct party *p) { + int i, j; + int tmp_int[16]; + char tmp_str[256]; + + memset(p, 0, sizeof(struct party)); + +// printf("sscanf party main info\n"); + if (sscanf(str, "%d\t%[^\t]\t%d,%d\t", &tmp_int[0], tmp_str, &tmp_int[1], &tmp_int[2]) != 4) + return 1; + + p->party_id = tmp_int[0]; + strcpy(p->name, tmp_str); + p->exp = tmp_int[1]; + p->item = tmp_int[2]; +// printf("%d [%s] %d %d\n", tmp_int[0], tmp_str[0], tmp_int[1], tmp_int[2]); + + for(j = 0; j < 3 && str != NULL; j++) + str = strchr(str + 1, '\t'); + + for(i = 0; i < MAX_PARTY; i++) { + struct party_member *m = &p->member[i]; + if (str == NULL) + return 1; +// printf("sscanf party member info %d\n", i); + + if (sscanf(str + 1, "%d,%d\t%[^\t]\t", &tmp_int[0], &tmp_int[1], tmp_str) != 3) + return 1; + + m->account_id = tmp_int[0]; + m->leader = tmp_int[1]; + strncpy(m->name, tmp_str, sizeof(m->name)); +// printf(" %d %d [%s]\n", tmp_int[0], tmp_int[1], tmp_str); + + for(j = 0; j < 2 && str != NULL; j++) + str = strchr(str + 1, '\t'); + } + + return 0; +} + +// パーティデータのロード +int inter_party_init() { + char line[8192]; + struct party *p; + FILE *fp; + int c = 0; + int i, j; + + party_db = numdb_init(); + + if ((fp = fopen(party_txt, "r")) == NULL) + return 1; + + while(fgets(line, sizeof(line) - 1, fp)) { + j = 0; + if (sscanf(line, "%d\t%%newid%%\n%n", &i, &j) == 1 && j > 0 && party_newid <= i) { + party_newid = i; + continue; + } + + p = calloc(sizeof(struct party), 1); + if (p == NULL){ + printf("int_party: out of memory!\n"); + exit(0); + } + memset(p, 0, sizeof(struct party)); + if (inter_party_fromstr(line, p) == 0 && p->party_id > 0) { + if (p->party_id >= party_newid) + party_newid = p->party_id + 1; + numdb_insert(party_db, p->party_id, p); + party_check_empty(p); + } else { + printf("int_party: broken data [%s] line %d\n", party_txt, c + 1); + free(p); + } + c++; + } + fclose(fp); +// printf("int_party: %s read done (%d parties)\n", party_txt, c); + + return 0; +} + +// パーティーデータのセーブ用 +int inter_party_save_sub(void *key, void *data, va_list ap) { + char line[8192]; + FILE *fp; + + inter_party_tostr(line, (struct party *)data); + fp = va_arg(ap, FILE *); + fprintf(fp, "%s" RETCODE, line); + + return 0; +} + +// パーティーデータのセーブ +int inter_party_save() { + FILE *fp; + int lock; + + if ((fp = lock_fopen(party_txt, &lock)) == NULL) { + printf("int_party: cant write [%s] !!! data is lost !!!\n", party_txt); + return 1; + } + numdb_foreach(party_db, inter_party_save_sub, fp); +// fprintf(fp, "%d\t%%newid%%\n", party_newid); + lock_fclose(fp,party_txt, &lock); +// printf("int_party: %s saved.\n", party_txt); + + return 0; +} + +// パーティ名検索用 +int search_partyname_sub(void *key,void *data,va_list ap) { + struct party *p = (struct party *)data,**dst; + char *str; + + str = va_arg(ap, char *); + dst = va_arg(ap, struct party **); + if (strcmpi(p->name, str) == 0) + *dst = p; + + return 0; +} + +// パーティ名検索 +struct party* search_partyname(char *str) { + struct party *p = NULL; + numdb_foreach(party_db, search_partyname_sub, str, &p); + + return p; +} + +// EXP公平分配できるかチェック +int party_check_exp_share(struct party *p) { + int i; + int maxlv = 0, minlv = 0x7fffffff; + + for(i = 0; i < MAX_PARTY; i++) { + int lv = p->member[i].lv; + if (p->member[i].online) { + if (lv < minlv) + minlv = lv; + if (maxlv < lv) + maxlv = lv; + } + } + + return (maxlv == 0 || maxlv-minlv <= party_share_level); +} + +// パーティが空かどうかチェック +int party_check_empty(struct party *p) { + int i; + +// printf("party check empty %08X\n", (int)p); + for(i = 0; i < MAX_PARTY; i++) { +// printf("%d acc=%d\n", i, p->member[i].account_id); + if (p->member[i].account_id > 0) { + return 0; + } + } + // 誰もいないので解散 + mapif_party_broken(p->party_id, 0); + numdb_erase(party_db, p->party_id); + free(p); + + return 1; +} + +// キャラの競合がないかチェック用 +int party_check_conflict_sub(void *key, void *data, va_list ap) { + struct party *p = (struct party *)data; + int party_id, account_id, i; + char *nick; + + party_id=va_arg(ap, int); + account_id=va_arg(ap, int); + nick=va_arg(ap, char *); + + if (p->party_id == party_id) // 本来の所属なので問題なし + return 0; + + for(i = 0; i < MAX_PARTY; i++) { + if (p->member[i].account_id == account_id && strcmp(p->member[i].name, nick) == 0) { + // 別のパーティに偽の所属データがあるので脱退 + printf("int_party: party conflict! %d %d %d\n", account_id, party_id, p->party_id); + mapif_parse_PartyLeave(-1, p->party_id, account_id); + } + } + + return 0; +} + +// キャラの競合がないかチェック +int party_check_conflict(int party_id, int account_id, char *nick) { + numdb_foreach(party_db, party_check_conflict_sub, party_id, account_id, nick); + + return 0; +} + +//------------------------------------------------------------------- +// map serverへの通信 + +// パーティ作成可否 +int mapif_party_created(int fd,int account_id, struct party *p) { + WFIFOW(fd,0) = 0x3820; + WFIFOL(fd,2) = account_id; + if (p != NULL) { + WFIFOB(fd,6) = 0; + WFIFOL(fd,7) = p->party_id; + memcpy(WFIFOP(fd,11), p->name, 24); + printf("int_party: created! %d %s\n", p->party_id, p->name); + } else { + WFIFOB(fd,6) = 1; + WFIFOL(fd,7) = 0; + memcpy(WFIFOP(fd,11), "error", 24); + } + WFIFOSET(fd,35); + + return 0; +} + +// パーティ情報見つからず +int mapif_party_noinfo(int fd, int party_id) { + WFIFOW(fd,0) = 0x3821; + WFIFOW(fd,2) = 8; + WFIFOL(fd,4) = party_id; + WFIFOSET(fd,8); + printf("int_party: info not found %d\n", party_id); + + return 0; +} + +// パーティ情報まとめ送り +int mapif_party_info(int fd, struct party *p) { + unsigned char buf[4 + sizeof(struct party)]; + + WBUFW(buf,0) = 0x3821; + memcpy(buf + 4, p, sizeof(struct party)); + WBUFW(buf,2) = 4 + sizeof(struct party); + if (fd < 0) + mapif_sendall(buf, WBUFW(buf,2)); + else + mapif_send(fd, buf, WBUFW(buf,2)); +// printf("int_party: info %d %s\n", p->party_id, p->name); + + return 0; +} + +// パーティメンバ追加可否 +int mapif_party_memberadded(int fd, int party_id, int account_id, int flag) { + WFIFOW(fd,0) = 0x3822; + WFIFOL(fd,2) = party_id; + WFIFOL(fd,6) = account_id; + WFIFOB(fd,10) = flag; + WFIFOSET(fd,11); + + return 0; +} + +// パーティ設定変更通知 +int mapif_party_optionchanged(int fd,struct party *p, int account_id, int flag) { + unsigned char buf[15]; + + WBUFW(buf,0) = 0x3823; + WBUFL(buf,2) = p->party_id; + WBUFL(buf,6) = account_id; + WBUFW(buf,10) = p->exp; + WBUFW(buf,12) = p->item; + WBUFB(buf,14) = flag; + if (flag == 0) + mapif_sendall(buf, 15); + else + mapif_send(fd, buf, 15); + printf("int_party: option changed %d %d %d %d %d\n", p->party_id, account_id, p->exp, p->item, flag); + + return 0; +} + +// パーティ脱退通知 +int mapif_party_leaved(int party_id,int account_id, char *name) { + unsigned char buf[34]; + + WBUFW(buf,0) = 0x3824; + WBUFL(buf,2) = party_id; + WBUFL(buf,6) = account_id; + memcpy(WBUFP(buf,10), name, 24); + mapif_sendall(buf, 34); + printf("int_party: party leaved %d %d %s\n", party_id, account_id, name); + + return 0; +} + +// パーティマップ更新通知 +int mapif_party_membermoved(struct party *p, int idx) { + unsigned char buf[29]; + + WBUFW(buf,0) = 0x3825; + WBUFL(buf,2) = p->party_id; + WBUFL(buf,6) = p->member[idx].account_id; + memcpy(WBUFP(buf,10), p->member[idx].map, 16); + WBUFB(buf,26) = p->member[idx].online; + WBUFW(buf,27) = p->member[idx].lv; + mapif_sendall(buf, 29); + + return 0; +} + +// パーティ解散通知 +int mapif_party_broken(int party_id, int flag) { + unsigned char buf[7]; + WBUFW(buf,0) = 0x3826; + WBUFL(buf,2) = party_id; + WBUFB(buf,6) = flag; + mapif_sendall(buf, 7); + printf("int_party: broken %d\n", party_id); + + return 0; +} + +// パーティ内発言 +int mapif_party_message(int party_id, int account_id, char *mes, int len) { + unsigned char buf[len+12]; + + WBUFW(buf,0) = 0x3827; + WBUFW(buf,2) = len + 12; + WBUFL(buf,4) = party_id; + WBUFL(buf,8) = account_id; + memcpy(WBUFP(buf,12), mes, len); + mapif_sendall(buf,len + 12); + + return 0; +} + +//------------------------------------------------------------------- +// map serverからの通信 + + +// パーティ +int mapif_parse_CreateParty(int fd, int account_id, char *name, char *nick, char *map, int lv) { + struct party *p; + int i; + + for(i = 0; i < 24 && name[i]; i++) { + if (!(name[i] & 0xe0) || name[i] == 0x7f) { + printf("int_party: illegal party name [%s]\n", name); + mapif_party_created(fd, account_id, NULL); + return 0; + } + } + + if ((p = search_partyname(name)) != NULL) { + printf("int_party: same name party exists [%s]\n", name); + mapif_party_created(fd, account_id, NULL); + return 0; + } + p = calloc(sizeof(struct party), 1); + if (p == NULL) { + printf("int_party: out of memory !\n"); + mapif_party_created(fd,account_id,NULL); + return 0; + } + memset(p, 0, sizeof(struct party)); + p->party_id = party_newid++; + memcpy(p->name, name, 24); + p->exp = 0; + p->item = 0; + p->member[0].account_id = account_id; + memcpy(p->member[0].name, nick, 24); + memcpy(p->member[0].map, map, 16); + p->member[0].leader = 1; + p->member[0].online = 1; + p->member[0].lv = lv; + + numdb_insert(party_db, p->party_id, p); + + mapif_party_created(fd, account_id, p); + mapif_party_info(fd, p); + + return 0; +} + +// パーティ情報要求 +int mapif_parse_PartyInfo(int fd, int party_id) { + struct party *p; + + p = numdb_search(party_db, party_id); + if (p != NULL) + mapif_party_info(fd, p); + else + mapif_party_noinfo(fd, party_id); + + return 0; +} + +// パーティ追加要求 +int mapif_parse_PartyAddMember(int fd, int party_id, int account_id, char *nick, char *map, int lv) { + struct party *p; + int i; + + p = numdb_search(party_db, party_id); + if (p == NULL) { + mapif_party_memberadded(fd, party_id, account_id, 1); + return 0; + } + + for(i = 0; i < MAX_PARTY; i++) { + if (p->member[i].account_id == 0) { + int flag = 0; + + p->member[i].account_id = account_id; + memcpy(p->member[i].name, nick, 24); + memcpy(p->member[i].map, map, 16); + p->member[i].leader = 0; + p->member[i].online = 1; + p->member[i].lv = lv; + mapif_party_memberadded(fd, party_id, account_id, 0); + mapif_party_info(-1, p); + + if (p->exp > 0 && !party_check_exp_share(p)) { + p->exp = 0; + flag = 0x01; + } + if (flag) + mapif_party_optionchanged(fd, p, 0, 0); + return 0; + } + } + mapif_party_memberadded(fd, party_id, account_id, 1); + + return 0; +} + +// パーティー設定変更要求 +int mapif_parse_PartyChangeOption(int fd, int party_id, int account_id, int exp, int item) { + struct party *p; + int flag = 0; + + p = numdb_search(party_db, party_id); + if (p == NULL) + return 0; + + p->exp = exp; + if (exp>0 && !party_check_exp_share(p)) { + flag |= 0x01; + p->exp = 0; + } + + p->item = item; + + mapif_party_optionchanged(fd, p, account_id, flag); + return 0; +} + +// パーティ脱退要求 +int mapif_parse_PartyLeave(int fd, int party_id, int account_id) { + struct party *p; + int i; + + p = numdb_search(party_db, party_id); + if (p != NULL) { + for(i = 0; i < MAX_PARTY; i++) { + if (p->member[i].account_id == account_id) { + mapif_party_leaved(party_id, account_id, p->member[i].name); + + memset(&p->member[i], 0, sizeof(struct party_member)); + if (party_check_empty(p) == 0) + mapif_party_info(-1, p);// まだ人がいるのでデータ送信 + return 0; + } + } + } + + return 0; +} + +// パーティマップ更新要求 +int mapif_parse_PartyChangeMap(int fd, int party_id, int account_id, char *map, int online, int lv) { + struct party *p; + int i; + + p = numdb_search(party_db, party_id); + if (p == NULL) + return 0; + + for(i = 0; i < MAX_PARTY; i++) { + if (p->member[i].account_id == account_id) { + int flag = 0; + + memcpy(p->member[i].map, map, 16); + p->member[i].online = online; + p->member[i].lv = lv; + mapif_party_membermoved(p, i); + + if (p->exp > 0 && !party_check_exp_share(p)) { + p->exp = 0; + flag = 1; + } + if (flag) + mapif_party_optionchanged(fd, p, 0, 0); + break; + } + } + + return 0; +} + +// パーティ解散要求 +int mapif_parse_BreakParty(int fd, int party_id) { + struct party *p; + + p = numdb_search(party_db, party_id); + if (p == NULL) + return 0; + + numdb_erase(party_db, party_id); + mapif_party_broken(fd, party_id); + + return 0; +} + +// パーティメッセージ送信 +int mapif_parse_PartyMessage(int fd, int party_id, int account_id, char *mes, int len) { + return mapif_party_message(party_id, account_id, mes, len); +} +// パーティチェック要求 +int mapif_parse_PartyCheck(int fd, int party_id, int account_id, char *nick) { + return party_check_conflict(party_id, account_id, nick); +} + +// map server からの通信 +// ・1パケットのみ解析すること +// ・パケット長データはinter.cにセットしておくこと +// ・パケット長チェックや、RFIFOSKIPは呼び出し元で行われるので行ってはならない +// ・エラーなら0(false)、そうでないなら1(true)をかえさなければならない +int inter_party_parse_frommap(int fd) { + switch(RFIFOW(fd,0)) { + case 0x3020: mapif_parse_CreateParty(fd, RFIFOL(fd,2), RFIFOP(fd,6), RFIFOP(fd,30), RFIFOP(fd,54), RFIFOW(fd,70)); break; + case 0x3021: mapif_parse_PartyInfo(fd, RFIFOL(fd,2)); break; + case 0x3022: mapif_parse_PartyAddMember(fd, RFIFOL(fd,2), RFIFOL(fd,6), RFIFOP(fd,10), RFIFOP(fd,34), RFIFOW(fd,50)); break; + case 0x3023: mapif_parse_PartyChangeOption(fd, RFIFOL(fd,2), RFIFOL(fd,6), RFIFOW(fd,10), RFIFOW(fd,12)); break; + case 0x3024: mapif_parse_PartyLeave(fd, RFIFOL(fd,2), RFIFOL(fd,6)); break; + case 0x3025: mapif_parse_PartyChangeMap(fd, RFIFOL(fd,2), RFIFOL(fd,6), RFIFOP(fd,10), RFIFOB(fd,26), RFIFOW(fd,27)); break; + case 0x3026: mapif_parse_BreakParty(fd, RFIFOL(fd,2)); break; + case 0x3027: mapif_parse_PartyMessage(fd, RFIFOL(fd,4), RFIFOL(fd,8), RFIFOP(fd,12), RFIFOW(fd,2)-12); break; + case 0x3028: mapif_parse_PartyCheck(fd, RFIFOL(fd,2), RFIFOL(fd,6), RFIFOP(fd,10)); break; + default: + return 0; + } + + return 1; +} + +// サーバーから脱退要求(キャラ削除用) +int inter_party_leave(int party_id, int account_id) { + return mapif_parse_PartyLeave(-1, party_id, account_id); +} + diff --git a/misc/src/char/int_party.h b/misc/src/char/int_party.h new file mode 100644 index 0000000..b265b4c --- /dev/null +++ b/misc/src/char/int_party.h @@ -0,0 +1,14 @@ +// $Id: int_party.h,v 1.1.1.1 2004/09/10 17:26:51 MagicalTux Exp $ +#ifndef _INT_PARTY_H_ +#define _INT_PARTY_H_ + +int inter_party_init(); +int inter_party_save(); + +int inter_party_parse_frommap(int fd); + +int inter_party_leave(int party_id,int account_id); + +extern char party_txt[1024]; + +#endif diff --git a/misc/src/char/int_pet.c b/misc/src/char/int_pet.c new file mode 100644 index 0000000..cff1e43 --- /dev/null +++ b/misc/src/char/int_pet.c @@ -0,0 +1,364 @@ +// $Id: int_pet.c,v 1.1.1.1 2004/09/10 17:26:51 MagicalTux Exp $ +#include "inter.h" +#include "int_pet.h" +#include "mmo.h" +#include "char.h" +#include "socket.h" +#include "db.h" +#include "lock.h" +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + + +char pet_txt[1024]="save/pet.txt"; + +static struct dbt *pet_db; +static int pet_newid = 100; + +int inter_pet_tostr(char *str,struct s_pet *p) +{ + int len; + + if(p->hungry < 0) + p->hungry = 0; + else if(p->hungry > 100) + p->hungry = 100; + if(p->intimate < 0) + p->intimate = 0; + else if(p->intimate > 1000) + p->intimate = 1000; + + len=sprintf(str,"%d,%d,%s\t%d,%d,%d,%d,%d,%d,%d,%d,%d", + p->pet_id,p->class,p->name,p->account_id,p->char_id,p->level,p->egg_id, + p->equip,p->intimate,p->hungry,p->rename_flag,p->incuvate); + + return 0; +} + +int inter_pet_fromstr(char *str,struct s_pet *p) +{ + int s; + int tmp_int[16]; + char tmp_str[256]; + + memset(p,0,sizeof(struct s_pet)); + +// printf("sscanf pet main info\n"); + s=sscanf(str,"%d,%d,%[^\t]\t%d,%d,%d,%d,%d,%d,%d,%d,%d",&tmp_int[0],&tmp_int[1],tmp_str,&tmp_int[2], + &tmp_int[3],&tmp_int[4],&tmp_int[5],&tmp_int[6],&tmp_int[7],&tmp_int[8],&tmp_int[9],&tmp_int[10]); + + if(s!=12) + return 1; + + p->pet_id = tmp_int[0]; + p->class = tmp_int[1]; + memcpy(p->name,tmp_str,24); + p->account_id = tmp_int[2]; + p->char_id = tmp_int[3]; + p->level = tmp_int[4]; + p->egg_id = tmp_int[5]; + p->equip = tmp_int[6]; + p->intimate = tmp_int[7]; + p->hungry = tmp_int[8]; + p->rename_flag = tmp_int[9]; + p->incuvate = tmp_int[10]; + + if(p->hungry < 0) + p->hungry = 0; + else if(p->hungry > 100) + p->hungry = 100; + if(p->intimate < 0) + p->intimate = 0; + else if(p->intimate > 1000) + p->intimate = 1000; + + return 0; +} + +int inter_pet_init() +{ + char line[8192]; + struct s_pet *p; + FILE *fp; + int c=0; + + pet_db=numdb_init(); + + if( (fp=fopen(pet_txt,"r"))==NULL ) + return 1; + while(fgets(line,sizeof(line),fp)){ + p=calloc(sizeof(struct s_pet), 1); + if(p==NULL){ + printf("int_pet: out of memory!\n"); + exit(0); + } + memset(p,0,sizeof(struct s_pet)); + if(inter_pet_fromstr(line,p)==0 && p->pet_id>0){ + if( p->pet_id >= pet_newid) + pet_newid=p->pet_id+1; + numdb_insert(pet_db,p->pet_id,p); + }else{ + printf("int_pet: broken data [%s] line %d\n",pet_txt,c); + free(p); + } + c++; + } + fclose(fp); +// printf("int_pet: %s read done (%d pets)\n",pet_txt,c); + return 0; +} + +int inter_pet_save_sub(void *key,void *data,va_list ap) +{ + char line[8192]; + FILE *fp; + inter_pet_tostr(line,(struct s_pet *)data); + fp=va_arg(ap,FILE *); + fprintf(fp,"%s" RETCODE,line); + return 0; +} + +int inter_pet_save() +{ + FILE *fp; + int lock; + if( (fp=lock_fopen(pet_txt,&lock))==NULL ){ + printf("int_pet: cant write [%s] !!! data is lost !!!\n",pet_txt); + return 1; + } + numdb_foreach(pet_db,inter_pet_save_sub,fp); + lock_fclose(fp,pet_txt,&lock); +// printf("int_pet: %s saved.\n",pet_txt); + return 0; +} + +int inter_pet_delete(int pet_id) +{ + struct s_pet *p; + p = numdb_search(pet_db,pet_id); + if( p == NULL) + return 1; + else { + numdb_erase(pet_db,pet_id); + printf("pet_id: %d deleted\n",pet_id); + } + return 0; +} + +int mapif_pet_created(int fd,int account_id,struct s_pet *p) +{ + WFIFOW(fd,0)=0x3880; + WFIFOL(fd,2)=account_id; + if(p!=NULL){ + WFIFOB(fd,6)=0; + WFIFOL(fd,7)=p->pet_id; + printf("int_pet: created! %d %s\n",p->pet_id,p->name); + }else{ + WFIFOB(fd,6)=1; + WFIFOL(fd,7)=0; + } + WFIFOSET(fd,11); + + return 0; +} + +int mapif_pet_info(int fd,int account_id,struct s_pet *p) +{ + WFIFOW(fd,0)=0x3881; + WFIFOW(fd,2)=sizeof(struct s_pet) + 9; + WFIFOL(fd,4)=account_id; + WFIFOB(fd,8)=0; + memcpy(WFIFOP(fd,9),p,sizeof(struct s_pet)); + WFIFOSET(fd,WFIFOW(fd,2)); + + return 0; +} + +int mapif_pet_noinfo(int fd,int account_id) +{ + WFIFOW(fd,0)=0x3881; + WFIFOW(fd,2)=sizeof(struct s_pet) + 9; + WFIFOL(fd,4)=account_id; + WFIFOB(fd,8)=1; + memset(WFIFOP(fd,9),0,sizeof(struct s_pet)); + WFIFOSET(fd,WFIFOW(fd,2)); + + return 0; +} + +int mapif_save_pet_ack(int fd,int account_id,int flag) +{ + WFIFOW(fd,0)=0x3882; + WFIFOL(fd,2)=account_id; + WFIFOB(fd,6)=flag; + WFIFOSET(fd,7); + + return 0; +} + +int mapif_delete_pet_ack(int fd,int flag) +{ + WFIFOW(fd,0)=0x3883; + WFIFOB(fd,2)=flag; + WFIFOSET(fd,3); + + return 0; +} + +int mapif_create_pet(int fd,int account_id,int char_id,short pet_class,short pet_lv,short pet_egg_id, + short pet_equip,short intimate,short hungry,char rename_flag,char incuvate,char *pet_name) +{ + struct s_pet *p; + p=malloc(sizeof(struct s_pet)); + if(p==NULL){ + printf("int_pet: out of memory !\n"); + mapif_pet_created(fd,account_id,NULL); + return 0; + } + memset(p,0,sizeof(struct s_pet)); + p->pet_id = pet_newid++; + memcpy(p->name,pet_name,24); + if(incuvate == 1) + p->account_id = p->char_id = 0; + else { + p->account_id = account_id; + p->char_id = char_id; + } + p->class = pet_class; + p->level = pet_lv; + p->egg_id = pet_egg_id; + p->equip = pet_equip; + p->intimate = intimate; + p->hungry = hungry; + p->rename_flag = rename_flag; + p->incuvate = incuvate; + + if(p->hungry < 0) + p->hungry = 0; + else if(p->hungry > 100) + p->hungry = 100; + if(p->intimate < 0) + p->intimate = 0; + else if(p->intimate > 1000) + p->intimate = 1000; + + numdb_insert(pet_db,p->pet_id,p); + + mapif_pet_created(fd,account_id,p); + + return 0; +} + +int mapif_load_pet(int fd,int account_id,int char_id,int pet_id) +{ + struct s_pet *p; + p=numdb_search(pet_db,pet_id); + if(p!=NULL) { + if(p->incuvate == 1) { + p->account_id = p->char_id = 0; + mapif_pet_info(fd,account_id,p); + } + else if(account_id == p->account_id && char_id == p->char_id) + mapif_pet_info(fd,account_id,p); + else + mapif_pet_noinfo(fd,account_id); + } + else + mapif_pet_noinfo(fd,account_id); + + return 0; +} + +int mapif_save_pet(int fd,int account_id,struct s_pet *data) +{ + struct s_pet *p; + int pet_id; + int len=RFIFOW(fd,2); + if(sizeof(struct s_pet)!=len-8) { + printf("inter pet: data size error %d %d\n",sizeof(struct s_pet),len-8); + } + else{ + pet_id = data->pet_id; + p=numdb_search(pet_db,pet_id); + if(p == NULL) { + p=malloc(sizeof(struct s_pet)); + if(p==NULL){ + printf("int_pet: out of memory !\n"); + mapif_save_pet_ack(fd,account_id,1); + return 0; + } + memset(p,0,sizeof(struct s_pet)); + p->pet_id = data->pet_id; + if(p->pet_id == 0) + data->pet_id = p->pet_id = pet_newid++; + numdb_insert(pet_db,p->pet_id,p); + } + if(data->hungry < 0) + data->hungry = 0; + else if(data->hungry > 100) + data->hungry = 100; + if(data->intimate < 0) + data->intimate = 0; + else if(data->intimate > 1000) + data->intimate = 1000; + memcpy(p,data,sizeof(struct s_pet)); + if(p->incuvate == 1) + p->account_id = p->char_id = 0; + + mapif_save_pet_ack(fd,account_id,0); + } + + return 0; +} + +int mapif_delete_pet(int fd,int pet_id) +{ + mapif_delete_pet_ack(fd,inter_pet_delete(pet_id)); + + return 0; +} + +int mapif_parse_CreatePet(int fd) +{ + mapif_create_pet(fd,RFIFOL(fd,2),RFIFOL(fd,6),RFIFOW(fd,10),RFIFOW(fd,12),RFIFOW(fd,14),RFIFOW(fd,16),RFIFOL(fd,18), + RFIFOL(fd,20),RFIFOB(fd,22),RFIFOB(fd,23),RFIFOP(fd,24)); + return 0; +} + +int mapif_parse_LoadPet(int fd) +{ + mapif_load_pet(fd,RFIFOL(fd,2),RFIFOL(fd,6),RFIFOL(fd,10)); + return 0; +} + +int mapif_parse_SavePet(int fd) +{ + mapif_save_pet(fd,RFIFOL(fd,4),(struct s_pet *)RFIFOP(fd,8)); + return 0; +} + +int mapif_parse_DeletePet(int fd) +{ + mapif_delete_pet(fd,RFIFOL(fd,2)); + return 0; +} + +// map server からの通信 +// ・1パケットのみ解析すること +// ・パケット長データはinter.cにセットしておくこと +// ・パケット長チェックや、RFIFOSKIPは呼び出し元で行われるので行ってはならない +// ・エラーなら0(false)、そうでないなら1(true)をかえさなければならない +int inter_pet_parse_frommap(int fd) +{ + switch(RFIFOW(fd,0)){ + case 0x3080: mapif_parse_CreatePet(fd); break; + case 0x3081: mapif_parse_LoadPet(fd); break; + case 0x3082: mapif_parse_SavePet(fd); break; + case 0x3083: mapif_parse_DeletePet(fd); break; + default: + return 0; + } + return 1; +} + diff --git a/misc/src/char/int_pet.h b/misc/src/char/int_pet.h new file mode 100644 index 0000000..993f913 --- /dev/null +++ b/misc/src/char/int_pet.h @@ -0,0 +1,13 @@ +// $Id: int_pet.h,v 1.1.1.1 2004/09/10 17:26:51 MagicalTux Exp $ +#ifndef _INT_PET_H_ +#define _INT_PET_H_ + +int inter_pet_init(); +int inter_pet_save(); +int inter_pet_delete(int pet_id); + +int inter_pet_parse_frommap(int fd); + +extern char pet_txt[1024]; + +#endif diff --git a/misc/src/char/int_storage.c b/misc/src/char/int_storage.c new file mode 100644 index 0000000..8b656fc --- /dev/null +++ b/misc/src/char/int_storage.c @@ -0,0 +1,504 @@ +// $Id: int_storage.c,v 1.1.1.1 2004/09/10 17:26:51 MagicalTux Exp $ +#include "inter.h" +#include "int_storage.h" +#include "int_pet.h" +#include "int_guild.h" +#include "mmo.h" +#include "char.h" +#include "socket.h" +#include "db.h" +#include "lock.h" + +#include <string.h> +#include <stdlib.h> + +// ファイル名のデフォルト +// inter_config_read()で再設定される +char storage_txt[1024]="save/storage.txt"; +char guild_storage_txt[1024]="save/g_storage.txt"; + +static struct dbt *storage_db; +static struct dbt *guild_storage_db; + +// 倉庫データを文字列に変換 +int storage_tostr(char *str,struct storage *p) +{ + int i,f=0; + char *str_p = str; + str_p += sprintf(str_p,"%d,%d\t",p->account_id,p->storage_amount); + + for(i=0;i<MAX_STORAGE;i++) + if( (p->storage[i].nameid) && (p->storage[i].amount) ){ + str_p += sprintf(str_p,"%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d ", + p->storage[i].id,p->storage[i].nameid,p->storage[i].amount,p->storage[i].equip, + p->storage[i].identify,p->storage[i].refine,p->storage[i].attribute, + p->storage[i].card[0],p->storage[i].card[1],p->storage[i].card[2],p->storage[i].card[3],p->storage[i].broken); + f++; + } + + *(str_p++)='\t'; + + *str_p='\0'; + if(!f) + str[0]=0; + return 0; +} + +// 文字列を倉庫データに変換 +int storage_fromstr(char *str,struct storage *p) +{ + int tmp_int[256]; + int set,next,len,i; + + set=sscanf(str,"%d,%d%n",&tmp_int[0],&tmp_int[1],&next); + p->storage_amount=tmp_int[1]; + + if(set!=2) + return 1; + if(str[next]=='\n' || str[next]=='\r') + return 0; + next++; + for(i=0;str[next] && str[next]!='\t';i++){ + if(sscanf(str + next, "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d%n", + &tmp_int[0], &tmp_int[1], &tmp_int[2], &tmp_int[3], + &tmp_int[4], &tmp_int[5], &tmp_int[6], + &tmp_int[7], &tmp_int[8], &tmp_int[9], &tmp_int[10], &tmp_int[11], &len) == 12) { + p->storage[i].id = tmp_int[0]; + p->storage[i].nameid = tmp_int[1]; + p->storage[i].amount = tmp_int[2]; + p->storage[i].equip = tmp_int[3]; + p->storage[i].identify = tmp_int[4]; + p->storage[i].refine = tmp_int[5]; + p->storage[i].attribute = tmp_int[6]; + p->storage[i].card[0] = tmp_int[7]; + p->storage[i].card[1] = tmp_int[8]; + p->storage[i].card[2] = tmp_int[9]; + p->storage[i].card[3] = tmp_int[10]; + p->storage[i].broken = tmp_int[11]; + next += len; + if (str[next] == ' ') + next++; + } + + else if(sscanf(str + next, "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d%n", + &tmp_int[0], &tmp_int[1], &tmp_int[2], &tmp_int[3], + &tmp_int[4], &tmp_int[5], &tmp_int[6], + &tmp_int[7], &tmp_int[8], &tmp_int[9], &tmp_int[10], &len) == 11) { + p->storage[i].id = tmp_int[0]; + p->storage[i].nameid = tmp_int[1]; + p->storage[i].amount = tmp_int[2]; + p->storage[i].equip = tmp_int[3]; + p->storage[i].identify = tmp_int[4]; + p->storage[i].refine = tmp_int[5]; + p->storage[i].attribute = tmp_int[6]; + p->storage[i].card[0] = tmp_int[7]; + p->storage[i].card[1] = tmp_int[8]; + p->storage[i].card[2] = tmp_int[9]; + p->storage[i].card[3] = tmp_int[10]; + p->storage[i].broken = 0; + next += len; + if (str[next] == ' ') + next++; + } + + else return 1; + } + return 0; +} + +int guild_storage_tostr(char *str,struct guild_storage *p) +{ + int i,f=0; + char *str_p = str; + str_p+=sprintf(str,"%d,%d\t",p->guild_id,p->storage_amount); + + for(i=0;i<MAX_GUILD_STORAGE;i++) + if( (p->storage[i].nameid) && (p->storage[i].amount) ){ + str_p += sprintf(str_p,"%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d ", + p->storage[i].id,p->storage[i].nameid,p->storage[i].amount,p->storage[i].equip, + p->storage[i].identify,p->storage[i].refine,p->storage[i].attribute, + p->storage[i].card[0],p->storage[i].card[1],p->storage[i].card[2],p->storage[i].card[3],p->storage[i].broken); + f++; + } + + *(str_p++)='\t'; + + *str_p='\0'; + if(!f) + str[0]=0; + return 0; +} + +int guild_storage_fromstr(char *str,struct guild_storage *p) +{ + int tmp_int[256]; + int set,next,len,i; + + set=sscanf(str,"%d,%d%n",&tmp_int[0],&tmp_int[1],&next); + p->storage_amount=tmp_int[1]; + + if(set!=2) + return 1; + if(str[next]=='\n' || str[next]=='\r') + return 0; + next++; + for(i=0;str[next] && str[next]!='\t';i++){ + if(sscanf(str + next, "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d%n", + &tmp_int[0], &tmp_int[1], &tmp_int[2], &tmp_int[3], + &tmp_int[4], &tmp_int[5], &tmp_int[6], + &tmp_int[7], &tmp_int[8], &tmp_int[9], &tmp_int[10], &tmp_int[11], &len) == 12) { + p->storage[i].id = tmp_int[0]; + p->storage[i].nameid = tmp_int[1]; + p->storage[i].amount = tmp_int[2]; + p->storage[i].equip = tmp_int[3]; + p->storage[i].identify = tmp_int[4]; + p->storage[i].refine = tmp_int[5]; + p->storage[i].attribute = tmp_int[6]; + p->storage[i].card[0] = tmp_int[7]; + p->storage[i].card[1] = tmp_int[8]; + p->storage[i].card[2] = tmp_int[9]; + p->storage[i].card[3] = tmp_int[10]; + p->storage[i].broken = tmp_int[11]; + next += len; + if (str[next] == ' ') + next++; + } + + else if(sscanf(str + next, "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d%n", + &tmp_int[0], &tmp_int[1], &tmp_int[2], &tmp_int[3], + &tmp_int[4], &tmp_int[5], &tmp_int[6], + &tmp_int[7], &tmp_int[8], &tmp_int[9], &tmp_int[10], &len) == 11) { + p->storage[i].id = tmp_int[0]; + p->storage[i].nameid = tmp_int[1]; + p->storage[i].amount = tmp_int[2]; + p->storage[i].equip = tmp_int[3]; + p->storage[i].identify = tmp_int[4]; + p->storage[i].refine = tmp_int[5]; + p->storage[i].attribute = tmp_int[6]; + p->storage[i].card[0] = tmp_int[7]; + p->storage[i].card[1] = tmp_int[8]; + p->storage[i].card[2] = tmp_int[9]; + p->storage[i].card[3] = tmp_int[10]; + p->storage[i].broken = 0; + next += len; + if (str[next] == ' ') + next++; + } + + else return 1; + } + return 0; +} + +// アカウントから倉庫データインデックスを得る(新規倉庫追加可能) +struct storage *account2storage(int account_id) +{ + struct storage *s; + s=numdb_search(storage_db,account_id); + if(s == NULL) { + s = calloc(sizeof(struct storage), 1); + if(s==NULL){ + printf("int_storage: out of memory!\n"); + exit(0); + } + memset(s,0,sizeof(struct storage)); + s->account_id=account_id; + numdb_insert(storage_db,s->account_id,s); + } + return s; +} + +struct guild_storage *guild2storage(int guild_id) +{ + struct guild_storage *gs = NULL; + if(inter_guild_search(guild_id) != NULL) { + gs=numdb_search(guild_storage_db,guild_id); + if(gs == NULL) { + gs = calloc(sizeof(struct guild_storage), 1); + if(gs==NULL){ + printf("int_storage: out of memory!\n"); + exit(0); + } + memset(gs,0,sizeof(struct guild_storage)); + gs->guild_id=guild_id; + numdb_insert(guild_storage_db,gs->guild_id,gs); + } + } + return gs; +} + +//--------------------------------------------------------- +// 倉庫データを読み込む +int inter_storage_init() +{ + char line[65536]; + int c=0,tmp_int; + struct storage *s; + struct guild_storage *gs; + FILE *fp; + + storage_db = numdb_init(); + + fp=fopen(storage_txt,"r"); + if(fp==NULL){ + printf("cant't read : %s\n",storage_txt); + return 1; + } + while(fgets(line,65535,fp)){ + sscanf(line,"%d",&tmp_int); + s=calloc(sizeof(struct storage), 1); + if(s==NULL){ + printf("int_storage: out of memory!\n"); + exit(0); + } + memset(s,0,sizeof(struct storage)); + s->account_id=tmp_int; + if(s->account_id > 0 && storage_fromstr(line,s) == 0) { + numdb_insert(storage_db,s->account_id,s); + } + else{ + printf("int_storage: broken data [%s] line %d\n",storage_txt,c); + free(s); + } + c++; + } + fclose(fp); + + c = 0; + guild_storage_db = numdb_init(); + + fp=fopen(guild_storage_txt,"r"); + if(fp==NULL){ + printf("cant't read : %s\n",guild_storage_txt); + return 1; + } + while(fgets(line,65535,fp)){ + sscanf(line,"%d",&tmp_int); + gs=calloc(sizeof(struct guild_storage), 1); + if(gs==NULL){ + printf("int_storage: out of memory!\n"); + exit(0); + } + memset(gs,0,sizeof(struct guild_storage)); + gs->guild_id=tmp_int; + if(gs->guild_id > 0 && guild_storage_fromstr(line,gs) == 0) { + numdb_insert(guild_storage_db,gs->guild_id,gs); + } + else{ + printf("int_storage: broken data [%s] line %d\n",guild_storage_txt,c); + free(gs); + } + c++; + } + fclose(fp); + + return 0; +} + +int inter_storage_save_sub(void *key,void *data,va_list ap) +{ + char line[65536]; + FILE *fp; + storage_tostr(line,(struct storage *)data); + fp=va_arg(ap,FILE *); + if(*line) + fprintf(fp,"%s" RETCODE,line); + return 0; +} +//--------------------------------------------------------- +// 倉庫データを書き込む +int inter_storage_save() +{ + FILE *fp; + int lock; + if( (fp=lock_fopen(storage_txt,&lock))==NULL ){ + printf("int_storage: cant write [%s] !!! data is lost !!!\n",storage_txt); + return 1; + } + numdb_foreach(storage_db,inter_storage_save_sub,fp); + lock_fclose(fp,storage_txt,&lock); +// printf("int_storage: %s saved.\n",storage_txt); + return 0; +} + +int inter_guild_storage_save_sub(void *key,void *data,va_list ap) +{ + char line[65536]; + FILE *fp; + if(inter_guild_search(((struct guild_storage *)data)->guild_id) != NULL) { + guild_storage_tostr(line,(struct guild_storage *)data); + fp=va_arg(ap,FILE *); + if(*line) + fprintf(fp,"%s" RETCODE,line); + } + return 0; +} +//--------------------------------------------------------- +// 倉庫データを書き込む +int inter_guild_storage_save() +{ + FILE *fp; + int lock; + if( (fp=lock_fopen(guild_storage_txt,&lock))==NULL ){ + printf("int_storage: cant write [%s] !!! data is lost !!!\n",guild_storage_txt); + return 1; + } + numdb_foreach(guild_storage_db,inter_guild_storage_save_sub,fp); + lock_fclose(fp,guild_storage_txt,&lock); +// printf("int_storage: %s saved.\n",guild_storage_txt); + return 0; +} + +// 倉庫データ削除 +int inter_storage_delete(int account_id) +{ + struct storage *s = numdb_search(storage_db,account_id); + if(s) { + int i; + for(i=0;i<s->storage_amount;i++){ + if(s->storage[i].card[0] == (short)0xff00) + inter_pet_delete(*((long *)(&s->storage[i].card[2]))); + } + numdb_erase(storage_db,account_id); + free(s); + } + return 0; +} + +// ギルド倉庫データ削除 +int inter_guild_storage_delete(int guild_id) +{ + struct guild_storage *gs = numdb_search(guild_storage_db,guild_id); + if(gs) { + int i; + for(i=0;i<gs->storage_amount;i++){ + if(gs->storage[i].card[0] == (short)0xff00) + inter_pet_delete(*((long *)(&gs->storage[i].card[2]))); + } + numdb_erase(guild_storage_db,guild_id); + free(gs); + } + return 0; +} + +//--------------------------------------------------------- +// map serverへの通信 + +// 倉庫データの送信 +int mapif_load_storage(int fd,int account_id) +{ + struct storage *s=account2storage(account_id); + WFIFOW(fd,0)=0x3810; + WFIFOW(fd,2)=sizeof(struct storage)+8; + WFIFOL(fd,4)=account_id; + memcpy(WFIFOP(fd,8),s,sizeof(struct storage)); + WFIFOSET(fd,WFIFOW(fd,2)); + return 0; +} +// 倉庫データ保存完了送信 +int mapif_save_storage_ack(int fd,int account_id) +{ + WFIFOW(fd,0)=0x3811; + WFIFOL(fd,2)=account_id; + WFIFOB(fd,6)=0; + WFIFOSET(fd,7); + return 0; +} + +int mapif_load_guild_storage(int fd,int account_id,int guild_id) +{ + struct guild_storage *gs=guild2storage(guild_id); + WFIFOW(fd,0)=0x3818; + if(gs) { + WFIFOW(fd,2)=sizeof(struct guild_storage)+12; + WFIFOL(fd,4)=account_id; + WFIFOL(fd,8)=guild_id; + memcpy(WFIFOP(fd,12),gs,sizeof(struct guild_storage)); + } + else { + WFIFOW(fd,2)=12; + WFIFOL(fd,4)=account_id; + WFIFOL(fd,8)=0; + } + WFIFOSET(fd,WFIFOW(fd,2)); + + return 0; +} +int mapif_save_guild_storage_ack(int fd,int account_id,int guild_id,int fail) +{ + WFIFOW(fd,0)=0x3819; + WFIFOL(fd,2)=account_id; + WFIFOL(fd,6)=guild_id; + WFIFOB(fd,10)=fail; + WFIFOSET(fd,11); + return 0; +} + +//--------------------------------------------------------- +// map serverからの通信 + +// 倉庫データ要求受信 +int mapif_parse_LoadStorage(int fd) +{ + mapif_load_storage(fd,RFIFOL(fd,2)); + return 0; +} +// 倉庫データ受信&保存 +int mapif_parse_SaveStorage(int fd) +{ + struct storage *s; + int account_id=RFIFOL(fd,4); + int len=RFIFOW(fd,2); + if(sizeof(struct storage)!=len-8){ + printf("inter storage: data size error %d %d\n",sizeof(struct storage),len-8); + } + else { + s=account2storage(account_id); + memcpy(s,RFIFOP(fd,8),sizeof(struct storage)); + mapif_save_storage_ack(fd,account_id); + } + return 0; +} + +int mapif_parse_LoadGuildStorage(int fd) +{ + mapif_load_guild_storage(fd,RFIFOL(fd,2),RFIFOL(fd,6)); + return 0; +} +int mapif_parse_SaveGuildStorage(int fd) +{ + struct guild_storage *gs; + int guild_id=RFIFOL(fd,8); + int len=RFIFOW(fd,2); + if(sizeof(struct guild_storage)!=len-12){ + printf("inter storage: data size error %d %d\n",sizeof(struct guild_storage),len-12); + } + else { + gs=guild2storage(guild_id); + if(gs) { + memcpy(gs,RFIFOP(fd,12),sizeof(struct guild_storage)); + mapif_save_guild_storage_ack(fd,RFIFOL(fd,4),guild_id,0); + } + else + mapif_save_guild_storage_ack(fd,RFIFOL(fd,4),guild_id,1); + } + return 0; +} + +// map server からの通信 +// ・1パケットのみ解析すること +// ・パケット長データはinter.cにセットしておくこと +// ・パケット長チェックや、RFIFOSKIPは呼び出し元で行われるので行ってはならない +// ・エラーなら0(false)、そうでないなら1(true)をかえさなければならない +int inter_storage_parse_frommap(int fd) +{ + switch(RFIFOW(fd,0)){ + case 0x3010: mapif_parse_LoadStorage(fd); break; + case 0x3011: mapif_parse_SaveStorage(fd); break; + case 0x3018: mapif_parse_LoadGuildStorage(fd); break; + case 0x3019: mapif_parse_SaveGuildStorage(fd); break; + default: + return 0; + } + return 1; +} diff --git a/misc/src/char/int_storage.h b/misc/src/char/int_storage.h new file mode 100644 index 0000000..d918f5f --- /dev/null +++ b/misc/src/char/int_storage.h @@ -0,0 +1,16 @@ +// $Id: int_storage.h,v 1.1.1.1 2004/09/10 17:26:51 MagicalTux Exp $ +#ifndef _INT_STORAGE_H_ +#define _INT_STORAGE_H_ + +int inter_storage_init(); +int inter_storage_save(); +int inter_guild_storage_save(); +int inter_storage_delete(int account_id); +int inter_guild_storage_delete(int guild_id); + +int inter_storage_parse_frommap(int fd); + +extern char storage_txt[1024]; +extern char guild_storage_txt[1024]; + +#endif diff --git a/misc/src/char/inter.c b/misc/src/char/inter.c new file mode 100644 index 0000000..29ec57b --- /dev/null +++ b/misc/src/char/inter.c @@ -0,0 +1,561 @@ +// $Id: inter.c,v 1.1.1.1 2004/09/10 17:26:51 MagicalTux Exp $ +#include "mmo.h" +#include "char.h" +#include "socket.h" +#include "timer.h" +#include "db.h" +#include <string.h> +#include <stdlib.h> + +#include "inter.h" +#include "int_party.h" +#include "int_guild.h" +#include "int_storage.h" +#include "int_pet.h" +#include "lock.h" + +#define WISDATA_TTL (60*1000) // Existence time of Wisp/page data (60 seconds) + // that is the waiting time of answers of all map-servers +#define WISDELLIST_MAX 256 // Number of elements of Wisp/page data deletion list + +char inter_log_filename[1024] = "log/inter.log"; + +char accreg_txt[1024] = "save/accreg.txt"; +static struct dbt *accreg_db = NULL; + +struct accreg { + int account_id, reg_num; + struct global_reg reg[ACCOUNT_REG_NUM]; +}; + +int party_share_level = 10; + + +// 送信パケット長リスト +int inter_send_packet_length[] = { + -1,-1,27,-1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + -1, 7, 0, 0, 0, 0, 0, 0, -1,11, 0, 0, 0, 0, 0, 0, + 35,-1,11,15, 34,29, 7,-1, 0, 0, 0, 0, 0, 0, 0, 0, + 10,-1,15, 0, 79,19, 7,-1, 0,-1,-1,-1, 14,67,186,-1, + 9, 9,-1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 11,-1, 7, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +}; +// 受信パケット長リスト +int inter_recv_packet_length[] = { + -1,-1, 7,-1, -1, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 6,-1, 0, 0, 0, 0, 0, 0, 10,-1, 0, 0, 0, 0, 0, 0, + 72, 6,52,14, 10,29, 6,-1, 34, 0, 0, 0, 0, 0, 0, 0, + -1, 6,-1, 0, 55,19, 6,-1, 14,-1,-1,-1, 14,19,186,-1, + 5, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 48,14,-1, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +}; + +struct WisData { + int id, fd, count, len; + unsigned long tick; + unsigned char src[24], dst[24], msg[1024]; +}; +static struct dbt * wis_db = NULL; +static int wis_dellist[WISDELLIST_MAX], wis_delnum; + + +//-------------------------------------------------------- + +// アカウント変数を文字列へ変換 +int inter_accreg_tostr(char *str, struct accreg *reg) { + int j; + char *p = str; + + p += sprintf(p, "%d\t", reg->account_id); + for(j = 0; j < reg->reg_num; j++) { + p += sprintf(p,"%s,%d ", reg->reg[j].str, reg->reg[j].value); + } + + return 0; +} + +// アカウント変数を文字列から変換 +int inter_accreg_fromstr(const char *str, struct accreg *reg) { + int j, v, n; + char buf[128]; + const char *p = str; + + if (sscanf(p, "%d\t%n", ®->account_id, &n ) != 1 || reg->account_id <= 0) + return 1; + + for(j = 0, p += n; j < ACCOUNT_REG_NUM; j++, p += n) { + if (sscanf(p, "%[^,],%d %n", buf, &v, &n) != 2) + break; + memcpy(reg->reg[j].str, buf, 32); + reg->reg[j].value = v; + } + reg->reg_num = j; + + return 0; +} + +// アカウント変数の読み込み +int inter_accreg_init() { + char line[8192]; + FILE *fp; + int c = 0; + struct accreg *reg; + + accreg_db = numdb_init(); + + if( (fp = fopen(accreg_txt, "r")) == NULL) + return 1; + while(fgets(line, sizeof(line)-1, fp)){ + line[sizeof(line)-1] = '\0'; + + reg = calloc(sizeof(struct accreg), 1); + if (reg == NULL) { + printf("inter: accreg: out of memory!\n"); + exit(0); + } + if (inter_accreg_fromstr(line, reg) == 0 && reg->account_id > 0) { + numdb_insert(accreg_db, reg->account_id, reg); + } else { + printf("inter: accreg: broken data [%s] line %d\n", accreg_txt, c); + free(reg); + } + c++; + } + fclose(fp); +// printf("inter: %s read done (%d)\n", accreg_txt, c); + + return 0; +} + +// アカウント変数のセーブ用 +int inter_accreg_save_sub(void *key, void *data, va_list ap) { + char line[8192]; + FILE *fp; + struct accreg *reg = (struct accreg *)data; + + if (reg->reg_num > 0) { + inter_accreg_tostr(line,reg); + fp = va_arg(ap, FILE *); + fprintf(fp, "%s" RETCODE, line); + } + + return 0; +} + +// アカウント変数のセーブ +int inter_accreg_save() { + FILE *fp; + int lock; + + if ((fp = lock_fopen(accreg_txt,&lock)) == NULL) { + printf("int_accreg: cant write [%s] !!! data is lost !!!\n", accreg_txt); + return 1; + } + numdb_foreach(accreg_db, inter_accreg_save_sub,fp); + lock_fclose(fp, accreg_txt, &lock); +// printf("inter: %s saved.\n", accreg_txt); + + return 0; +} + +//-------------------------------------------------------- + +/*========================================== + * 設定ファイルを読み込む + *------------------------------------------ + */ +int inter_config_read(const char *cfgName) { + char line[1024], w1[1024], w2[1024]; + FILE *fp; + + fp = fopen(cfgName, "r"); + if (fp == NULL) { + printf("file not found: %s\n", cfgName); + return 1; + } + while(fgets(line, sizeof(line) - 1, fp)) { + if (line[0] == '/' && line[1] == '/') + continue; + line[sizeof(line)-1] = '\0'; + + if (sscanf(line,"%[^:]: %[^\r\n]", w1, w2) != 2) + continue; + + if (strcmpi(w1, "storage_txt") == 0) { + strncpy(storage_txt, w2, sizeof(storage_txt)); + } else if (strcmpi(w1, "party_txt") == 0) { + strncpy(party_txt, w2, sizeof(party_txt)); + } else if (strcmpi(w1, "guild_txt") == 0) { + strncpy(guild_txt, w2, sizeof(guild_txt)); + } else if (strcmpi(w1, "pet_txt") == 0) { + strncpy(pet_txt, w2, sizeof(pet_txt)); + } else if (strcmpi(w1, "castle_txt") == 0) { + strncpy(castle_txt, w2, sizeof(castle_txt)); + } else if (strcmpi(w1, "accreg_txt") == 0) { + strncpy(accreg_txt, w2, sizeof(accreg_txt)); + } else if (strcmpi(w1, "guild_storage_txt") == 0) { + strncpy(guild_storage_txt, w2, sizeof(guild_storage_txt)); + } else if (strcmpi(w1, "party_share_level") == 0) { + party_share_level = atoi(w2); + if (party_share_level < 0) + party_share_level = 0; + } else if (strcmpi(w1, "inter_log_filename") == 0) { + strncpy(inter_log_filename, w2, sizeof(inter_log_filename)); + } else if (strcmpi(w1, "import") == 0) { + inter_config_read(w2); + } + } + fclose(fp); + + return 0; +} + +// ログ書き出し +int inter_log(char *fmt,...) { + FILE *logfp; + va_list ap; + + va_start(ap,fmt); + logfp = fopen(inter_log_filename, "a"); + if (logfp) { + vfprintf(logfp, fmt, ap); + fclose(logfp); + } + va_end(ap); + + return 0; +} + +// セーブ +int inter_save() { + inter_party_save(); + inter_guild_save(); + inter_storage_save(); + inter_guild_storage_save(); + inter_pet_save(); + inter_accreg_save(); + + return 0; +} + +// 初期化 +int inter_init(const char *file) { + inter_config_read(file); + + wis_db = numdb_init(); + + inter_party_init(); + inter_guild_init(); + inter_storage_init(); + inter_pet_init(); + inter_accreg_init(); + + return 0; +} + +// マップサーバー接続 +int inter_mapif_init(int fd) { + inter_guild_mapif_init(fd); + + return 0; +} + +//-------------------------------------------------------- +// sended packets to map-server + +// GMメッセージ送信 +int mapif_GMmessage(unsigned char *mes, int len) { + unsigned char buf[len]; + + WBUFW(buf,0) = 0x3800; + WBUFW(buf,2) = len; + memcpy(WBUFP(buf,4), mes, len - 4); + mapif_sendall(buf, len); +// printf("inter server: GM:%d %s\n", len, mes); + + return 0; +} + +// Wisp/page transmission to all map-server +int mapif_wis_message(struct WisData *wd) { + unsigned char buf[56 + wd->len]; + + WBUFW(buf, 0) = 0x3801; + WBUFW(buf, 2) = 56 + wd->len; + WBUFL(buf, 4) = wd->id; + memcpy(WBUFP(buf, 8), wd->src, 24); + memcpy(WBUFP(buf,32), wd->dst, 24); + memcpy(WBUFP(buf,56), wd->msg, wd->len); + wd->count = mapif_sendall(buf, WBUFW(buf,2)); + + return 0; +} + +// Wisp/page transmission result to map-server +int mapif_wis_end(struct WisData *wd, int flag) { + unsigned char buf[27]; + + WBUFW(buf, 0) = 0x3802; + memcpy(WBUFP(buf, 2), wd->src, 24); + WBUFB(buf,26) = flag; // flag: 0: success to send wisper, 1: target character is not loged in?, 2: ignored by target + mapif_send(wd->fd, buf, 27); +// printf("inter server wis_end: flag: %d\n", flag); + + return 0; +} + +// アカウント変数送信 +int mapif_account_reg(int fd, unsigned char *src) { + unsigned char buf[WBUFW(src,2)]; + + memcpy(WBUFP(buf,0),src,WBUFW(src,2)); + WBUFW(buf, 0) = 0x3804; + mapif_sendallwos(fd, buf, WBUFW(buf,2)); + + return 0; +} + +// アカウント変数要求返信 +int mapif_account_reg_reply(int fd,int account_id) { + struct accreg *reg = numdb_search(accreg_db,account_id); + + WFIFOW(fd,0) = 0x3804; + WFIFOL(fd,4) = account_id; + if (reg == NULL) { + WFIFOW(fd,2) = 8; + } else { + int j, p; + for(j = 0, p = 8; j < reg->reg_num; j++, p += 36) { + memcpy(WFIFOP(fd,p), reg->reg[j].str, 32); + WFIFOL(fd,p+32) = reg->reg[j].value; + } + WFIFOW(fd,2) = p; + } + WFIFOSET(fd,WFIFOW(fd,2)); + + return 0; +} + +//-------------------------------------------------------- + +// Existence check of WISP data +int check_ttl_wisdata_sub(void *key, void *data, va_list ap) { + unsigned long tick; + struct WisData *wd = (struct WisData *)data; + tick = va_arg(ap, unsigned long); + + if (DIFF_TICK(tick, wd->tick) > WISDATA_TTL && wis_delnum < WISDELLIST_MAX) + wis_dellist[wis_delnum++] = wd->id; + + return 0; +} + +int check_ttl_wisdata() { + unsigned long tick = gettick(); + int i; + + do { + wis_delnum = 0; + numdb_foreach(wis_db, check_ttl_wisdata_sub, tick); + for(i = 0; i < wis_delnum; i++) { + struct WisData *wd = numdb_search(wis_db, wis_dellist[i]); + printf("inter: wis data id=%d time out : from %s to %s\n", wd->id, wd->src, wd->dst); + // removed. not send information after a timeout. Just no answer for the player + //mapif_wis_end(wd, 1); // flag: 0: success to send wisper, 1: target character is not loged in?, 2: ignored by target + numdb_erase(wis_db, wd->id); + free(wd); + } + } while(wis_delnum >= WISDELLIST_MAX); + + return 0; +} + +//-------------------------------------------------------- +// received packets from map-server + +// GMメッセージ送信 +int mapif_parse_GMmessage(int fd) { + mapif_GMmessage(RFIFOP(fd,4), RFIFOW(fd,2)); + + return 0; +} + +// Wisp/page request to send +int mapif_parse_WisRequest(int fd) { + struct WisData* wd; + static int wisid = 0; + int index; + + if (RFIFOW(fd,2)-52 >= sizeof(wd->msg)) { + printf("inter: Wis message size too long.\n"); + return 0; + } else if (RFIFOW(fd,2)-52 <= 0) { // normaly, impossible, but who knows... + printf("inter: Wis message doesn't exist.\n"); + return 0; + } + + // search if character exists before to ask all map-servers + if ((index = search_character_index(RFIFOP(fd,28))) == -1) { + unsigned char buf[27]; + WBUFW(buf, 0) = 0x3802; + memcpy(WBUFP(buf, 2), RFIFOP(fd, 4), 24); + WBUFB(buf,26) = 1; // flag: 0: success to send wisper, 1: target character is not loged in?, 2: ignored by target + mapif_send(fd, buf, 27); + // Character exists. So, ask all map-servers + } else { + // to be sure of the correct name, rewrite it + memset(RFIFOP(fd,28), 0, 24); + strncpy(RFIFOP(fd,28), search_character_name(index), 24); + // if source is destination, don't ask other servers. + if (strcmp(RFIFOP(fd,4),RFIFOP(fd,28)) == 0) { + unsigned char buf[27]; + WBUFW(buf, 0) = 0x3802; + memcpy(WBUFP(buf, 2), RFIFOP(fd, 4), 24); + WBUFB(buf,26) = 1; // flag: 0: success to send wisper, 1: target character is not loged in?, 2: ignored by target + mapif_send(fd, buf, 27); + } else { + + wd = (struct WisData *)calloc(sizeof(struct WisData), 1); + if (wd == NULL){ + printf("inter: WisRequest: out of memory !\n"); + return 0; + } + + // Whether the failure of previous wisp/page transmission (timeout) + check_ttl_wisdata(); + + wd->id = ++wisid; + wd->fd = fd; + wd->len= RFIFOW(fd,2)-52; + memcpy(wd->src, RFIFOP(fd, 4), 24); + memcpy(wd->dst, RFIFOP(fd,28), 24); + memcpy(wd->msg, RFIFOP(fd,52), wd->len); + wd->tick = gettick(); + numdb_insert(wis_db, wd->id, wd); + mapif_wis_message(wd); + } + } + + return 0; +} + +// Wisp/page transmission result +int mapif_parse_WisReply(int fd) { + int id = RFIFOL(fd,2), flag = RFIFOB(fd,6); + struct WisData *wd = numdb_search(wis_db, id); + + if (wd == NULL) + return 0; // This wisp was probably suppress before, because it was timeout of because of target was found on another map-server + + if ((--wd->count) <= 0 || flag != 1) { + mapif_wis_end(wd, flag); // flag: 0: success to send wisper, 1: target character is not loged in?, 2: ignored by target + numdb_erase(wis_db, id); + free(wd); + } + + return 0; +} + +// Received wisp message from map-server for ALL gm (just copy the message and resends it to ALL map-servers) +int mapif_parse_WisToGM(int fd) { + unsigned char buf[RFIFOW(fd,2)]; // 0x3003/0x3803 <packet_len>.w <wispname>.24B <min_gm_level>.w <message>.?B + + memcpy(WBUFP(buf,0), RFIFOP(fd,0), RFIFOW(fd,2)); + WBUFW(buf, 0) = 0x3803; + mapif_sendall(buf, RFIFOW(fd,2)); + + return 0; +} + +// アカウント変数保存要求 +int mapif_parse_AccReg(int fd) { + int j, p; + struct accreg *reg = numdb_search(accreg_db, RFIFOL(fd,4)); + + if (reg == NULL) { + if ((reg = calloc(sizeof(struct accreg), 1)) == NULL) { + printf("inter: accreg: out of memory !\n"); + exit(0); + } + reg->account_id = RFIFOL(fd,4); + numdb_insert(accreg_db, RFIFOL(fd,4), reg); + } + + for(j = 0, p = 8; j < ACCOUNT_REG_NUM && p < RFIFOW(fd,2); j++, p += 36) { + memcpy(reg->reg[j].str, RFIFOP(fd,p), 32); + reg->reg[j].value = RFIFOL(fd, p + 32); + } + reg->reg_num = j; + + mapif_account_reg(fd, RFIFOP(fd,0)); // 他のMAPサーバーに送信 + + return 0; +} + +// アカウント変数送信要求 +int mapif_parse_AccRegRequest(int fd) { +// printf("mapif: accreg request\n"); + return mapif_account_reg_reply(fd, RFIFOL(fd,2)); +} + +//-------------------------------------------------------- + +// map server からの通信(1パケットのみ解析すること) +// エラーなら0(false)、処理できたなら1、 +// パケット長が足りなければ2をかえさなければならない +int inter_parse_frommap(int fd) { + int cmd = RFIFOW(fd,0); + int len = 0; + + // inter鯖管轄かを調べる + if (cmd < 0x3000 || cmd >= 0x3000 + (sizeof(inter_recv_packet_length) / sizeof(inter_recv_packet_length[0]))) + return 0; + + // パケット長を調べる + if ((len = inter_check_length(fd, inter_recv_packet_length[cmd - 0x3000])) == 0) + return 2; + + switch(cmd) { + case 0x3000: mapif_parse_GMmessage(fd); break; + case 0x3001: mapif_parse_WisRequest(fd); break; + case 0x3002: mapif_parse_WisReply(fd); break; + case 0x3003: mapif_parse_WisToGM(fd); break; + case 0x3004: mapif_parse_AccReg(fd); break; + case 0x3005: mapif_parse_AccRegRequest(fd); break; + default: + if (inter_party_parse_frommap(fd)) + break; + if (inter_guild_parse_frommap(fd)) + break; + if (inter_storage_parse_frommap(fd)) + break; + if (inter_pet_parse_frommap(fd)) + break; + return 0; + } + RFIFOSKIP(fd, len); + + return 1; +} + +// RFIFOのパケット長確認 +// 必要パケット長があればパケット長、まだ足りなければ0 +int inter_check_length(int fd, int length) { + if (length == -1) { // 可変パケット長 + if (RFIFOREST(fd) < 4) // パケット長が未着 + return 0; + length = RFIFOW(fd,2); + } + + if (RFIFOREST(fd) < length) // パケットが未着 + return 0; + + return length; +} + diff --git a/misc/src/char/inter.h b/misc/src/char/inter.h new file mode 100644 index 0000000..b004c9b --- /dev/null +++ b/misc/src/char/inter.h @@ -0,0 +1,19 @@ +// $Id: inter.h,v 1.1.1.1 2004/09/10 17:26:51 MagicalTux Exp $ +#ifndef _INTER_H_ +#define _INTER_H_ + +int inter_init(const char *file); +int inter_save(); +int inter_parse_frommap(int fd); +int inter_mapif_init(int fd); + +int inter_check_length(int fd,int length); + +int inter_log(char *fmt,...); + +#define inter_cfgName "conf/inter_athena.conf" + +extern int party_share_level; +extern char inter_log_filename[1024]; + +#endif diff --git a/misc/src/char_sql/GNUmakefile b/misc/src/char_sql/GNUmakefile new file mode 100644 index 0000000..9c7b5e5 --- /dev/null +++ b/misc/src/char_sql/GNUmakefile @@ -0,0 +1,20 @@ +all: char-server_sql +sql: char-server_sql + +COMMON_OBJ = ../common/core.o ../common/socket.o ../common/timer.o ../common/db.o ../common/malloc.o +COMMON_H = ../common/core.h ../common/socket.h ../common/timer.h ../common/db.h ../common/malloc.h + +char-server_sql: char.o inter.o int_party.o int_guild.o int_storage.o int_pet.o strlib.o itemdb.o $(COMMON_OBJ) + $(CC) -o ../../$@ $^ $(LIB_S) + +char.o: char.c char.h strlib.h itemdb.h +inter.o: inter.c inter.h int_party.h int_guild.h int_storage.h int_pet.h ../common/mmo.h char.h ../common/socket.h +int_party.o: int_party.c int_party.h inter.h ../common/mmo.h char.h ../common/socket.h ../common/timer.h ../common/db.h +int_guild.o: int_guild.c int_guild.h inter.h ../common/mmo.h char.h ../common/socket.h ../common/db.h +int_storage.o: int_storage.c int_storage.h char.h itemdb.h +int_pet.o: int_pet.c int_pet.h inter.h char.h ../common/mmo.h ../common/socket.h ../common/db.h +strlib.o: strlib.c strlib.h +itemdb.o: itemdb.c itemdb.h ../common/db.h ../common/mmo.h + +clean: + rm -f *.o ../../char-server_sql diff --git a/misc/src/char_sql/Makefile b/misc/src/char_sql/Makefile new file mode 100644 index 0000000..cefe36e --- /dev/null +++ b/misc/src/char_sql/Makefile @@ -0,0 +1,20 @@ +all: char-server_sql
+sql: char-server_sql
+
+COMMON_OBJ = ../common/core.o ../common/socket.o ../common/timer.o ../common/db.o ../common/malloc.o
+COMMON_H = ../common/core.h ../common/socket.h ../common/timer.h ../common/db.h ../common/malloc.h
+
+char-server_sql: char.o inter.o int_party.o int_guild.o int_storage.o int_pet.o strlib.o itemdb.o $(COMMON_OBJ)
+ $(CC) -o ../../$@ $^ $(LIB_S)
+
+char.o: char.c char.h strlib.h itemdb.h
+inter.o: inter.c inter.h int_party.h int_guild.h int_storage.h int_pet.h ../common/mmo.h char.h ../common/socket.h
+int_party.o: int_party.c int_party.h inter.h ../common/mmo.h char.h ../common/socket.h ../common/timer.h ../common/db.h
+int_guild.o: int_guild.c int_guild.h inter.h ../common/mmo.h char.h ../common/socket.h ../common/db.h
+int_storage.o: int_storage.c int_storage.h char.h itemdb.h
+int_pet.o: int_pet.c int_pet.h inter.h char.h ../common/mmo.h ../common/socket.h ../common/db.h
+strlib.o: strlib.c strlib.h
+itemdb.o: itemdb.c itemdb.h ../common/db.h ../common/mmo.h
+
+clean:
+ rm -f *.o ../../char-server_sql
diff --git a/misc/src/char_sql/char.c b/misc/src/char_sql/char.c new file mode 100644 index 0000000..11b6a49 --- /dev/null +++ b/misc/src/char_sql/char.c @@ -0,0 +1,2917 @@ +// $Id: char.c,v 1.16 2004/09/23 18:31:16 MouseJstr Exp $ +// original : char2.c 2003/03/14 11:58:35 Rev.1.5 +// +// original code from athena +// SQL conversion by Jioh L. Jung +// TXT 1.105 +#include <sys/types.h> + +#ifdef LCCWIN32 +#include <winsock.h> +#pragma lib <libmysql.lib> +#else +#include <sys/socket.h> +#include <netinet/in.h> +#include <sys/time.h> +#include <sys/ioctl.h> +#include <arpa/inet.h> +#include <netdb.h> +#endif + +#include "utils.h" +#include <stdio.h> +#include <stdlib.h> +#include <netinet/in.h> +#include <sys/time.h> +#include <time.h> +#include <sys/ioctl.h> +#include <unistd.h> +#include <signal.h> +#include <fcntl.h> +#include <string.h> +#include <arpa/inet.h> +#include <netdb.h> +#include <stdarg.h> + +#include "char.h" +#include "strlib.h" +#include "itemdb.h" +#include "inter.h" + +#ifdef MEMWATCH +#include "memwatch.h" +#endif + +char char_db[256] = "char"; +char cart_db[256] = "cart_inventory"; +char inventory_db[256] = "inventory"; +char charlog_db[256] = "charlog"; +char storage_db[256] = "storage"; +char interlog_db[256] = "interlog"; +char reg_db[256] = "global_reg_value"; +char skill_db[256] = "skill"; +char memo_db[256] = "memo"; +char guild_db[256] = "guild"; +char guild_alliance_db[256] = "guild_alliance"; +char guild_castle_db[256] = "guild_castle"; +char guild_expulsion_db[256] = "guild_expulsion"; +char guild_member_db[256] = "guild_member"; +char guild_position_db[256] = "guild_position"; +char guild_skill_db[256] = "guild_skill"; +char guild_storage_db[256] = "guild_storage"; +char party_db[256] = "party"; +char pet_db[256] = "pet"; +char login_db[256] = "login"; + +char login_db_account_id[32] = "account_id"; +char login_db_level[32] = "level"; + +int lowest_gm_level = 1; + +unsigned char *SQL_CONF_NAME = "conf/inter_athena.conf"; + +struct mmo_map_server server[MAX_MAP_SERVERS]; +int server_fd[MAX_MAP_SERVERS]; +int server_freezeflag[MAX_MAP_SERVERS]; // Map-server anti-freeze system. Counter. 5 ok, 4...0 freezed + +int anti_freeze_enable = 0; +int ANTI_FREEZE_INTERVAL = 6; + +int login_fd, char_fd; +char userid[24]; +char passwd[24]; +char server_name[20]; +char wisp_server_name[24] = "Server"; +char login_ip_str[128]; +int login_ip; +int login_port = 6900; +char char_ip_str[128]; +int char_ip; +int char_port = 6121; +int char_maintenance; +int char_new; +int name_ignoring_case = 0; // Allow or not identical name for characters but with a different case by [Yor] +int char_name_option = 0; // Option to know which letters/symbols are authorised in the name of a character (0: all, 1: only those in char_name_letters, 2: all EXCEPT those in char_name_letters) by [Yor] +char char_name_letters[1024] = ""; // list of letters/symbols used to authorise or not a name of a character. by [Yor] + +char lan_map_ip[128]; // Lan map ip added by kashy +int subnetmaski[4]; // Subnetmask added by kashy +char unknown_char_name[1024] = "Unknown"; + +struct char_session_data{ + int account_id,login_id1,login_id2,sex; + int found_char[9]; + char email[40]; // e-mail (default: a@a.com) by [Yor] + time_t connect_until_time; // # of seconds 1/1/1970 (timestamp): Validity limit of the account (0 = unlimited) +}; + +#define AUTH_FIFO_SIZE 256 +struct { + int account_id,char_id,login_id1,login_id2,ip,char_pos,delflag,sex; + time_t connect_until_time; // # of seconds 1/1/1970 (timestamp): Validity limit of the account (0 = unlimited) +} auth_fifo[AUTH_FIFO_SIZE]; +int auth_fifo_pos = 0; + +int check_ip_flag = 1; // It's to check IP of a player between char-server and other servers (part of anti-hacking system) + +int char_id_count = 150000; +struct mmo_charstatus *char_dat; +int char_num,char_max; +int max_connect_user = 0; +int autosave_interval = DEFAULT_AUTOSAVE_INTERVAL; +int start_zeny = 500; +int start_weapon = 1201; +int start_armor = 2301; + +// check for exit signal +// 0 is saving complete +// other is char_id +unsigned int save_flag = 0; + +// start point (you can reset point on conf file) +struct point start_point = {"new_1-1.gat", 53, 111}; + +struct gm_account *gm_account = NULL; +int GM_num = 0; + +//----------------------------------------------------- +// Function to suppress control characters in a string. +//----------------------------------------------------- +int remove_control_chars(unsigned char *str) { + int i; + int change = 0; + + for(i = 0; i < strlen(str); i++) { + if (str[i] < 32) { + str[i] = '_'; + change = 1; + } + } + + return change; +} + +//---------------------------------------------------------------------- +// Determine if an account (id) is a GM account +// and returns its level (or 0 if it isn't a GM account or if not found) +//---------------------------------------------------------------------- +// Removed since nothing GM related goes on in the char server [CLOWNISIUS] +int isGM(int account_id) { + int i; + + for(i = 0; i < GM_num; i++) + if (gm_account[i].account_id == account_id) + return gm_account[i].level; + return 0; +} + +void read_gm_account(void) { + if (gm_account != NULL) + free(gm_account); + GM_num = 0; + + sprintf(tmp_lsql, "SELECT `%s`,`%s` FROM `%s` WHERE `%s`>='%d'",login_db_account_id,login_db_level,login_db,login_db_level,lowest_gm_level); + if (mysql_query(&lmysql_handle, tmp_lsql)) { + printf("DB server Error (select %s to Memory)- %s\n",login_db,mysql_error(&lmysql_handle)); + } + lsql_res = mysql_store_result(&lmysql_handle); + if (lsql_res) { + gm_account = calloc(sizeof(struct gm_account) * mysql_num_rows(lsql_res), 1); + while ((lsql_row = mysql_fetch_row(lsql_res))) { + gm_account[GM_num].account_id = atoi(lsql_row[0]); + gm_account[GM_num].level = atoi(lsql_row[1]); + GM_num++; + } + } + + mysql_free_result(lsql_res); +} + +//===================================================================================================== +int mmo_char_tosql(int char_id, struct mmo_charstatus *p){ + int i=0,party_exist,guild_exist; + int eqcount=1; + int noteqcount=1; + char temp_str[32]; + + struct itemtemp mapitem; + if (char_id!=p->char_id) return 0; + + save_flag = p->char_id; + printf("(\033[1;32m%d\033[0m) %s \trequest save char data - ",char_id,char_dat[0].name); + + + +//for(testcount=1;testcount<50;testcount++){//---------------------------test count-------------------- +// printf("test count : %d\n", testcount); +// eqcount=1; +// noteqcount=1; +// dbeqcount=1; +// dbnoteqcount=1; +//----------------------------------------------------------------------------------------------------- + +//=========================================map inventory data > memory =============================== + //map inventory data + for(i=0;i<MAX_INVENTORY;i++){ + if(p->inventory[i].nameid>0){ + if(itemdb_isequip(p->inventory[i].nameid)==1){ + mapitem.equip[eqcount].flag=0; + mapitem.equip[eqcount].id = p->inventory[i].id; + mapitem.equip[eqcount].nameid=p->inventory[i].nameid; + mapitem.equip[eqcount].amount = p->inventory[i].amount; + mapitem.equip[eqcount].equip = p->inventory[i].equip; + mapitem.equip[eqcount].identify = p->inventory[i].identify; + mapitem.equip[eqcount].refine = p->inventory[i].refine; + mapitem.equip[eqcount].attribute = p->inventory[i].attribute; + mapitem.equip[eqcount].card[0] = p->inventory[i].card[0]; + mapitem.equip[eqcount].card[1] = p->inventory[i].card[1]; + mapitem.equip[eqcount].card[2] = p->inventory[i].card[2]; + mapitem.equip[eqcount].card[3] = p->inventory[i].card[3]; + mapitem.equip[eqcount].broken = p->inventory[i].broken; + eqcount++; + } + else if(itemdb_isequip(p->inventory[i].nameid)==0){ + mapitem.notequip[noteqcount].flag=0; + mapitem.notequip[noteqcount].id = p->inventory[i].id; + mapitem.notequip[noteqcount].nameid=p->inventory[i].nameid; + mapitem.notequip[noteqcount].amount = p->inventory[i].amount; + mapitem.notequip[noteqcount].equip = p->inventory[i].equip; + mapitem.notequip[noteqcount].identify = p->inventory[i].identify; + mapitem.notequip[noteqcount].refine = p->inventory[i].refine; + mapitem.notequip[noteqcount].attribute = p->inventory[i].attribute; + mapitem.notequip[noteqcount].card[0] = p->inventory[i].card[0]; + mapitem.notequip[noteqcount].card[1] = p->inventory[i].card[1]; + mapitem.notequip[noteqcount].card[2] = p->inventory[i].card[2]; + mapitem.notequip[noteqcount].card[3] = p->inventory[i].card[3]; + mapitem.notequip[noteqcount].broken = p->inventory[i].broken; + noteqcount++; + } + } + } + //printf("- Save item data to MySQL!\n"); + memitemdata_to_sql(mapitem, eqcount, noteqcount, p->char_id,TABLE_INVENTORY); + +//=========================================map cart data > memory ==================================== + eqcount=1; + noteqcount=1; + + //map cart data + for(i=0;i<MAX_CART;i++){ + if(p->cart[i].nameid>0){ + if(itemdb_isequip(p->cart[i].nameid)==1){ + mapitem.equip[eqcount].flag=0; + mapitem.equip[eqcount].id = p->cart[i].id; + mapitem.equip[eqcount].nameid=p->cart[i].nameid; + mapitem.equip[eqcount].amount = p->cart[i].amount; + mapitem.equip[eqcount].equip = p->cart[i].equip; + mapitem.equip[eqcount].identify = p->cart[i].identify; + mapitem.equip[eqcount].refine = p->cart[i].refine; + mapitem.equip[eqcount].attribute = p->cart[i].attribute; + mapitem.equip[eqcount].card[0] = p->cart[i].card[0]; + mapitem.equip[eqcount].card[1] = p->cart[i].card[1]; + mapitem.equip[eqcount].card[2] = p->cart[i].card[2]; + mapitem.equip[eqcount].card[3] = p->cart[i].card[3]; + mapitem.equip[eqcount].broken = p->cart[i].broken; + eqcount++; + } + else if(itemdb_isequip(p->cart[i].nameid)==0){ + mapitem.notequip[noteqcount].flag=0; + mapitem.notequip[noteqcount].id = p->cart[i].id; + mapitem.notequip[noteqcount].nameid=p->cart[i].nameid; + mapitem.notequip[noteqcount].amount = p->cart[i].amount; + mapitem.notequip[noteqcount].equip = p->cart[i].equip; + mapitem.notequip[noteqcount].identify = p->cart[i].identify; + mapitem.notequip[noteqcount].refine = p->cart[i].refine; + mapitem.notequip[noteqcount].attribute = p->cart[i].attribute; + mapitem.notequip[noteqcount].card[0] = p->cart[i].card[0]; + mapitem.notequip[noteqcount].card[1] = p->cart[i].card[1]; + mapitem.notequip[noteqcount].card[2] = p->cart[i].card[2]; + mapitem.notequip[noteqcount].card[3] = p->cart[i].card[3]; + mapitem.notequip[noteqcount].broken = p->cart[i].broken; + noteqcount++; + } + } + } + + //printf("- Save cart data to MySQL!\n"); + memitemdata_to_sql(mapitem, eqcount, noteqcount, p->char_id,TABLE_CART); + +//===================================================================================================== + +//}//---------------------------test count------------------------------ + //check party_exist + party_exist=0; + sprintf(tmp_sql, "SELECT count(*) FROM `%s` WHERE `party_id` = '%d'",party_db, p->party_id); + if (mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + sql_res = mysql_store_result(&mysql_handle); + sql_row = mysql_fetch_row(sql_res); + if (sql_row) party_exist = atoi(sql_row[0]); + mysql_free_result(sql_res); + + //check guild_exist + guild_exist=0; + sprintf(tmp_sql, "SELECT count(*) FROM `%s` WHERE `guild_id` = '%d'",guild_db, p->guild_id); + if (mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + sql_res = mysql_store_result(&mysql_handle); + sql_row = mysql_fetch_row(sql_res); + if (sql_row) guild_exist = atoi(sql_row[0]); + mysql_free_result(sql_res); + + if (guild_exist==0) p->guild_id=0; + if (party_exist==0) p->party_id=0; + + //sql query + //`char`( `char_id`,`account_id`,`char_num`,`name`,`class`,`base_level`,`job_level`,`base_exp`,`job_exp`,`zeny`, //9 + //`str`,`agi`,`vit`,`int`,`dex`,`luk`, //15 + //`max_hp`,`hp`,`max_sp`,`sp`,`status_point`,`skill_point`, //21 + //`option`,`karma`,`manner`,`party_id`,`guild_id`,`pet_id`, //27 + //`hair`,`hair_color`,`clothes_color`,`weapon`,`shield`,`head_top`,`head_mid`,`head_bottom`, //35 + //`last_map`,`last_x`,`last_y`,`save_map`,`save_x`,`save_y`) + //printf("- Save char data to MySQL!\n"); + sprintf(tmp_sql ,"UPDATE `%s` SET `class`='%d', `base_level`='%d', `job_level`='%d'," + "`base_exp`='%d', `job_exp`='%d', `zeny`='%d'," + "`max_hp`='%d',`hp`='%d',`max_sp`='%d',`sp`='%d',`status_point`='%d',`skill_point`='%d'," + "`str`='%d',`agi`='%d',`vit`='%d',`int`='%d',`dex`='%d',`luk`='%d'," + "`option`='%d',`karma`='%d',`manner`='%d',`party_id`='%d',`guild_id`='%d',`pet_id`='%d'," + "`hair`='%d',`hair_color`='%d',`clothes_color`='%d',`weapon`='%d',`shield`='%d',`head_top`='%d',`head_mid`='%d',`head_bottom`='%d'," + "`last_map`='%s',`last_x`='%d',`last_y`='%d',`save_map`='%s',`save_x`='%d',`save_y`='%d',`partner_id`='%d' WHERE `account_id`='%d' AND `char_id` = '%d'", + char_db, p->class, p->base_level, p->job_level, + p->base_exp, p->job_exp, p->zeny, + p->max_hp, p->hp, p->max_sp, p->sp, p->status_point, p->skill_point, + p->str, p->agi, p->vit, p->int_, p->dex, p->luk, + p->option, p->karma, p->manner, p->party_id, p->guild_id, p->pet_id, + p->hair, p->hair_color, p->clothes_color, + p->weapon, p->shield, p->head_top, p->head_mid, p->head_bottom, + p->last_point.map, p->last_point.x, p->last_point.y, + p->save_point.map, p->save_point.x, p->save_point.y, p->partner_id, p->account_id, p->char_id + ); + + if(mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error (update `char`)- %s\n", mysql_error(&mysql_handle)); + } + + //printf("- Save memo data to MySQL!\n"); + //`memo` (`memo_id`,`char_id`,`map`,`x`,`y`) + sprintf(tmp_sql,"DELETE FROM `%s` WHERE `char_id`='%d'",memo_db, p->char_id); + if(mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error (delete `memo`)- %s\n", mysql_error(&mysql_handle)); + } + + //insert here. + for(i=0;i<10;i++){ + if(p->memo_point[i].map[0]){ + sprintf(tmp_sql,"INSERT INTO `%s`(`char_id`,`map`,`x`,`y`) VALUES ('%d', '%s', '%d', '%d')", + memo_db, char_id, p->memo_point[i].map, p->memo_point[i].x, p->memo_point[i].y); + if(mysql_query(&mysql_handle, tmp_sql)) + printf("DB server Error (insert `memo`)- %s\n", mysql_error(&mysql_handle)); + } + } + + //printf("- Save skill data to MySQL!\n"); + //`skill` (`char_id`, `id`, `lv`) + sprintf(tmp_sql,"DELETE FROM `%s` WHERE `char_id`='%d'",skill_db, p->char_id); + if(mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error (delete `skill`)- %s\n", mysql_error(&mysql_handle)); + } + //printf("- Insert skill \n"); + //insert here. + for(i=0;i<MAX_SKILL;i++){ + if(p->skill[i].id){ + if (p->skill[i].id && p->skill[i].flag!=1) { + sprintf(tmp_sql,"INSERT delayed INTO `%s`(`char_id`, `id`, `lv`) VALUES ('%d', '%d','%d')", + skill_db, char_id, p->skill[i].id, (p->skill[i].flag==0)?p->skill[i].lv:p->skill[i].flag-2); + if(mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error (insert `skill`)- %s\n", mysql_error(&mysql_handle)); + } + } + } + } + + + //printf("- Save global_reg_value data to MySQL!\n"); + //`global_reg_value` (`char_id`, `str`, `value`) + sprintf(tmp_sql,"DELETE FROM `%s` WHERE `type`=3 AND `char_id`='%d'",reg_db, p->char_id); + if (mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error (delete `global_reg_value`)- %s\n", mysql_error(&mysql_handle)); + } + + //insert here. + for(i=0;i<p->global_reg_num;i++){ + if (p->global_reg[i].str) { + if(p->global_reg[i].value !=0){ + sprintf(tmp_sql,"INSERT INTO `%s` (`char_id`, `str`, `value`) VALUES ('%d', '%s','%d')", + reg_db, char_id, jstrescapecpy(temp_str,(unsigned char*)p->global_reg[i].str), p->global_reg[i].value); + if(mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error (insert `global_reg_value`)- %s\n", mysql_error(&mysql_handle)); + } + } + } + } + printf("saving char is done.\n"); + save_flag = 0; + + return 0; +} + +int memitemdata_to_sql(struct itemtemp mapitem, int eqcount, int noteqcount, int char_id, int tableswitch){ + //equ + int i, j; + int dbeqcount = 1; + int dbnoteqcount = 1; + struct itemtemp dbitem; + char tablename[16]; + char selectoption[16]; + + switch (tableswitch){ + case TABLE_INVENTORY: + sprintf(tablename,"%s",inventory_db); + sprintf(selectoption,"char_id"); + break; + case TABLE_CART: + sprintf(tablename,"%s",cart_db); + sprintf(selectoption,"char_id"); + break; + case TABLE_STORAGE: + sprintf(tablename,"%s",storage_db); + sprintf(selectoption,"account_id"); + break; + case TABLE_GUILD_STORAGE: + sprintf(tablename,"%s",guild_storage_db); + sprintf(selectoption,"guild_id"); + break; + } + //printf("Working Table : %s \n",tablename); + + //=======================================mysql database data > memory=============================================== + + sprintf(tmp_sql, "SELECT `id`, `nameid`, `amount`, `equip`, `identify`, `refine`, `attribute`, `card0`, `card1`, `card2`, `card3` , `broken` " + "FROM `%s` WHERE `%s`='%d'",tablename ,selectoption ,char_id); + if (mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error (select `%s` to Memory)- %s\n",tablename ,mysql_error(&mysql_handle)); + } + sql_res = mysql_store_result(&mysql_handle); + if (sql_res) { + for(i=0;(sql_row = mysql_fetch_row(sql_res));i++){ + if (itemdb_isequip(atoi(sql_row[1]))==1){ + dbitem.equip[dbeqcount].flag=0; + dbitem.equip[dbeqcount].id = atoi(sql_row[0]); + dbitem.equip[dbeqcount].nameid = atoi(sql_row[1]); + dbitem.equip[dbeqcount].amount = atoi(sql_row[2]); + dbitem.equip[dbeqcount].equip = atoi(sql_row[3]); + dbitem.equip[dbeqcount].identify = atoi(sql_row[4]); + dbitem.equip[dbeqcount].refine = atoi(sql_row[5]); + dbitem.equip[dbeqcount].attribute = atoi(sql_row[6]); + dbitem.equip[dbeqcount].card[0] = atoi(sql_row[7]); + dbitem.equip[dbeqcount].card[1] = atoi(sql_row[8]); + dbitem.equip[dbeqcount].card[2] = atoi(sql_row[9]); + dbitem.equip[dbeqcount].card[3] = atoi(sql_row[10]); + dbitem.equip[dbeqcount].broken = atoi(sql_row[11]); + dbeqcount++; + }else { + dbitem.notequip[dbnoteqcount].flag=0; + dbitem.notequip[dbnoteqcount].id = atoi(sql_row[0]); + dbitem.notequip[dbnoteqcount].nameid = atoi(sql_row[1]); + dbitem.notequip[dbnoteqcount].amount = atoi(sql_row[2]); + dbitem.notequip[dbnoteqcount].equip = atoi(sql_row[3]); + dbitem.notequip[dbnoteqcount].identify = atoi(sql_row[4]); + dbitem.notequip[dbnoteqcount].refine = atoi(sql_row[5]); + dbitem.notequip[dbnoteqcount].attribute = atoi(sql_row[6]); + dbitem.notequip[dbnoteqcount].card[0] = atoi(sql_row[7]); + dbitem.notequip[dbnoteqcount].card[1] = atoi(sql_row[8]); + dbitem.notequip[dbnoteqcount].card[2] = atoi(sql_row[9]); + dbitem.notequip[dbnoteqcount].card[3] = atoi(sql_row[10]); + dbitem.notequip[dbnoteqcount].broken = atoi(sql_row[11]); + dbnoteqcount++; + } + } + mysql_free_result(sql_res); + } + + //==============================================Memory data > SQL =============================== + //======================================Equip ITEM======================================= + if((eqcount==1) && (dbeqcount==1)){//printf("%s Equip Empty\n",tablename); + //item empty + } else { + + for(i=1;i<eqcount;i++){ + for(j=1;j<dbeqcount;j++){ + if(mapitem.equip[i].flag==1) break; + if(!(dbitem.equip[j].flag==1)){ + if(mapitem.equip[i].nameid==dbitem.equip[j].nameid){ + if ((mapitem.equip[i].equip==dbitem.equip[j].equip) && (mapitem.equip[i].identify==dbitem.equip[j].identify) && (mapitem.equip[i].amount==dbitem.equip[j].amount) && + + (mapitem.equip[i].refine==dbitem.equip[j].refine) && (mapitem.equip[i].attribute==dbitem.equip[j].attribute) && (mapitem.equip[i].card[0]==dbitem.equip[j].card[0]) && + (mapitem.equip[i].card[1]==dbitem.equip[j].card[1]) && (mapitem.equip[i].card[2]==dbitem.equip[j].card[2]) && (mapitem.equip[i].card[3]==dbitem.equip[j].card[3]) && + (mapitem.equip[i].broken == dbitem.equip[j].broken)) { + mapitem.equip[i].flag = 1; + dbitem.equip[j].flag = 1; + //printf("the same item : %d , equip : %d , i : %d , flag : %d\n", mapitem.equip[i].nameid,mapitem.equip[i].equip , i, mapitem.equip[i].flag); //DEBUG-STRING + } else { + sprintf(tmp_sql,"UPDATE `%s` SET `equip`='%d', `identify`='%d', `refine`='%d'," + "`attribute`='%d', `card0`='%d', `card1`='%d', `card2`='%d', `card3`='%d', `broken`='%d', `amount`='%d' WHERE `id`='%d' LIMIT 1", + tablename, mapitem.equip[i].equip, mapitem.equip[i].identify, mapitem.equip[i].refine,mapitem.equip[i].attribute, mapitem.equip[i].card[0], + mapitem.equip[i].card[1], mapitem.equip[i].card[2], mapitem.equip[i].card[3], mapitem.equip[i].broken, mapitem.equip[i].amount, dbitem.equip[j].id); + //printf("%s\n",tmp_sql); + if(mysql_query(&mysql_handle, tmp_sql)) + printf("DB server Error (UPdate `equ %s`)- %s\n", tablename, mysql_error(&mysql_handle)); + mapitem.equip[i].flag=1; + dbitem.equip[j].flag=1; + //printf("not the same item : %d ; i : %d ; flag : %d\n", mapitem.equip[i].nameid, i, mapitem.equip[i].flag); + } + } + } + } + } + + //printf("dbeqcount = %d\n",dbeqcount); + + for(i=1;i<dbeqcount;i++){ + //printf("dbitem.equip[i].flag = %d , dbitem.equip[i].id = %d\n",dbitem.equip[i].flag,dbitem.equip[i].id); + if (!(dbitem.equip[i].flag == 1)) { + sprintf(tmp_sql,"DELETE from `%s` where `id`='%d'",tablename , dbitem.equip[i].id); + //printf("%s", tmp_sql); + if(mysql_query(&mysql_handle, tmp_sql)) + printf("DB server Error (DELETE `equ %s`)- %s\n", tablename ,mysql_error(&mysql_handle)); + } + } + for(i=1;i<eqcount;i++){ + if(!(mapitem.equip[i].flag==1)){ + sprintf(tmp_sql,"INSERT INTO `%s`(`%s`, `nameid`, `amount`, `equip`, `identify`, `refine`, `attribute`, `card0`, `card1`, `card2`, `card3`, `broken`)" + " VALUES ( '%d','%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d')", + tablename, selectoption, char_id, mapitem.equip[i].nameid, mapitem.equip[i].amount, mapitem.equip[i].equip, mapitem.equip[i].identify, mapitem.equip[i].refine, + mapitem.equip[i].attribute, mapitem.equip[i].card[0], mapitem.equip[i].card[1], mapitem.equip[i].card[2], mapitem.equip[i].card[3], mapitem.equip[i].broken); + //printf("%s", tmp_sql); + if(mysql_query(&mysql_handle, tmp_sql)) + printf("DB server Error (INSERT `equ %s`)- %s\n",tablename ,mysql_error(&mysql_handle)); + } + } + + //======================================DEBUG================================================= + +// gettimeofday(&tv,NULL); +// strftime(tmpstr,24,"%Y-%m-%d %H:%M:%S",localtime(&(tv.tv_sec))); +// printf("\n\n"); +// printf("Working Table Name : EQU %s, Count : map %3d | db %3d \n",tablename ,eqcount ,dbeqcount); +// printf("*********************************************************************************\n"); +// printf("======================================MAP===================Char ID %10d===\n",char_id); +// printf("==flag ===name ===equip===ident===amoun===attri===card0===card1===card2===card3==\n"); +// for(j=1;j<eqcount;j++) +// printf("| %5d | %5d | %5d | %5d | %5d | %5d | %5d | %5d | %5d | %5d |\n", mapitem.equip[j].flag,mapitem.equip[j].nameid, mapitem.equip[j].equip, mapitem.equip[j].identify, mapitem.equip[j].refine,mapitem.equip[j].attribute, mapitem.equip[j].card[0], mapitem.equip[j].card[1], mapitem.equip[j].card[2], mapitem.equip[j].card[3]); +// printf("======================================DB=========================================\n"); +// printf("==flag ===name ===equip===ident===refin===attri===card0===card1===card2===card3==\n"); +// for(j=1;j<dbeqcount;j++) +// printf("| %5d | %5d | %5d | %5d | %5d | %5d | %5d | %5d | %5d | %5d |\n", dbitem.equip[j].flag ,dbitem.equip[j].nameid, dbitem.equip[j].equip, dbitem.equip[j].identify, dbitem.equip[j].amount,dbitem.equip[j].attribute, dbitem.equip[j].card[0], dbitem.equip[j].card[1], dbitem.equip[j].card[2], dbitem.equip[j].card[3]); +// printf("=================================================================================\n"); +// printf("=================================================Data Time %s===\n", tmpstr); +// printf("=================================================================================\n"); + + } + + //======================================DEBUG================================================== + + //=============================Not Equip ITEM========================================== + if((noteqcount==1) && (dbnoteqcount==1)){ + //printf("%s Not Equip Empty\n",tablename); + //item empty + } else { + + for(i=1;i<noteqcount;i++){ + for(j=1;j<dbnoteqcount;j++){ + if(mapitem.notequip[i].flag==1) break; + if(!(dbitem.notequip[j].flag==1)){ + if(mapitem.notequip[i].nameid==dbitem.notequip[j].nameid){ + if ((mapitem.notequip[i].amount==dbitem.notequip[j].amount) && (mapitem.notequip[i].equip==dbitem.notequip[j].equip) && (mapitem.notequip[i].identify==dbitem.notequip[j].identify) + && (mapitem.notequip[i].attribute==dbitem.notequip[j].attribute)) + { mapitem.notequip[i].flag=1; + dbitem.notequip[j].flag=1; + //printf("the same item : %d ; i : %d ; flag : %d\n", mapitem.notequip[i].nameid, i, mapitem.notequip[i].flag); //DEBUG-STRING + } + else{ + sprintf(tmp_sql,"UPDATE `%s` SET `amount`='%d', `equip`='%d', `identify`='%d'," + "`attribute`='%d' WHERE `%s`='%d' AND `nameid`='%d'", + tablename, mapitem.notequip[i].amount, mapitem.notequip[i].equip, mapitem.notequip[i].identify, mapitem.notequip[i].attribute, + selectoption, char_id, mapitem.notequip[i].nameid); + //printf("%s",tmp_sql); + if(mysql_query(&mysql_handle, tmp_sql)) + printf("DB server Error (UPdate `notequ %s`)- %s\n",tablename ,mysql_error(&mysql_handle)); + + mapitem.notequip[i].flag=1; + dbitem.notequip[j].flag=1; + } + } + } + } + } + + //printf("dbnoteqcount = %d\n",dbnoteqcount); + + for(i=1;i<dbnoteqcount;i++){ + //printf("dbitem.notequip[i].flag = %d , dbitem.notequip[i].id = %d\n",dbitem.notequip[i].flag,dbitem.notequip[i].id); + if(!(dbitem.notequip[i].flag==1)){ + sprintf(tmp_sql,"DELETE from `%s` where `id`='%d'", tablename, dbitem.notequip[i].id); + //printf("%s", tmp_sql); + if(mysql_query(&mysql_handle, tmp_sql)) + printf("DB server Error (DELETE `notequ %s`)- %s\n", tablename ,mysql_error(&mysql_handle)); + } + } + for(i=1;i<noteqcount;i++){ + if(!(mapitem.notequip[i].flag==1)){ + sprintf(tmp_sql,"INSERT INTO `%s`( `%s`, `nameid`, `amount`, `equip`, `identify`, `refine`, `attribute`, `card0`, `card1`, `card2`, `card3`, `broken`)" + " VALUES ('%d','%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d')", + tablename ,selectoption , char_id, mapitem.notequip[i].nameid, mapitem.notequip[i].amount, mapitem.notequip[i].equip, mapitem.notequip[i].identify, mapitem.notequip[i].refine, + mapitem.notequip[i].attribute, mapitem.notequip[i].card[0], mapitem.notequip[i].card[1], mapitem.notequip[i].card[2], mapitem.notequip[i].card[3], mapitem.equip[i].broken); + //printf("%s", tmp_sql); + if(mysql_query(&mysql_handle, tmp_sql)) + printf("DB server Error (INSERT `notequ %s`)- %s\n", tablename, mysql_error(&mysql_handle)); + } + } + + //======================================DEBUG================================================= + +// gettimeofday(&tv,NULL); +// strftime(tmpstr,24,"%Y-%m-%d %H:%M:%S",localtime(&(tv.tv_sec))); +// printf("\n\n"); +// printf("Working Table Name : Not EQU %s, Count : map %3d | db %3d \n",tablename ,noteqcount ,dbnoteqcount); +// printf("*********************************************************************************\n"); +// printf("======================================MAP===================Char ID %10d===\n",char_id); +// printf("==flag ===name ===equip===ident===refin===attri===card0===card1===card2===card3==\n"); +// for(j=1;j<noteqcount;j++) +// printf("| %5d | %5d | %5d | %5d | %5d | %5d | %5d | %5d | %5d | %5d |\n", mapitem.notequip[j].flag,mapitem.notequip[j].nameid, mapitem.notequip[j].equip, mapitem.notequip[j].identify, mapitem.notequip[j].refine,mapitem.notequip[j].attribute, mapitem.notequip[j].card[0], mapitem.notequip[j].card[1], mapitem.notequip[j].card[2], mapitem.notequip[j].card[3]); +// printf("======================================DB=========================================\n"); +// printf("==flag ===name ===equip===ident===refin===attri===card0===card1===card2===card3==\n"); +// for(j=1;j<dbnoteqcount;j++) +// printf("| %5d | %5d | %5d | %5d | %5d | %5d | %5d | %5d | %5d | %5d |\n", dbitem.notequip[j].flag ,dbitem.notequip[j].nameid, dbitem.notequip[j].equip, dbitem.notequip[j].identify, dbitem.notequip[j].refine,dbitem.notequip[j].attribute, dbitem.notequip[j].card[0], dbitem.notequip[j].card[1], dbitem.notequip[j].card[2], dbitem.notequip[j].card[3]); +// printf("=================================================================================\n"); +// printf("=================================================Data Time %s===\n", tmpstr); +// printf("=================================================================================\n"); +// + } + return 0; +} +//===================================================================================================== +int mmo_char_fromsql(int char_id, struct mmo_charstatus *p, int online){ + int i, n; + + memset(p, 0, sizeof(struct mmo_charstatus)); + + p->char_id = char_id; + printf("Loaded: "); + //`char`( `char_id`,`account_id`,`char_num`,`name`,`class`,`base_level`,`job_level`,`base_exp`,`job_exp`,`zeny`, //9 + //`str`,`agi`,`vit`,`int`,`dex`,`luk`, //15 + //`max_hp`,`hp`,`max_sp`,`sp`,`status_point`,`skill_point`, //21 + //`option`,`karma`,`manner`,`party_id`,`guild_id`,`pet_id`, //27 + //`hair`,`hair_color`,`clothes_color`,`weapon`,`shield`,`head_top`,`head_mid`,`head_bottom`, //35 + //`last_map`,`last_x`,`last_y`,`save_map`,`save_x`,`save_y`) + //splite 2 parts. cause veeeery long SQL syntax + + sprintf(tmp_sql, "SELECT `char_id`,`account_id`,`char_num`,`name`,`class`,`base_level`,`job_level`,`base_exp`,`job_exp`,`zeny`," + "`str`,`agi`,`vit`,`int`,`dex`,`luk`, `max_hp`,`hp`,`max_sp`,`sp`,`status_point`,`skill_point` FROM `%s` WHERE `char_id` = '%d'",char_db, char_id); + + if (mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error (select `char`)- %s\n", mysql_error(&mysql_handle)); + } + + sql_res = mysql_store_result(&mysql_handle); + + if (sql_res) { + sql_row = mysql_fetch_row(sql_res); + + p->char_id = char_id; + p->account_id = atoi(sql_row[1]); + p->char_num = atoi(sql_row[2]); + strcpy(p->name, sql_row[3]); + p->class = atoi(sql_row[4]); + p->base_level = atoi(sql_row[5]); + p->job_level = atoi(sql_row[6]); + p->base_exp = atoi(sql_row[7]); + p->job_exp = atoi(sql_row[8]); + p->zeny = atoi(sql_row[9]); + p->str = atoi(sql_row[10]); + p->agi = atoi(sql_row[11]); + p->vit = atoi(sql_row[12]); + p->int_ = atoi(sql_row[13]); + p->dex = atoi(sql_row[14]); + p->luk = atoi(sql_row[15]); + p->max_hp = atoi(sql_row[16]); + p->hp = atoi(sql_row[17]); + p->max_sp = atoi(sql_row[18]); + p->sp = atoi(sql_row[19]); + p->status_point = atoi(sql_row[20]); + p->skill_point = atoi(sql_row[21]); + //free mysql result. + mysql_free_result(sql_res); + } else + printf("char1 - failed\n"); //Error?! ERRRRRR WHAT THAT SAY!? + printf("(\033[1;32m%d\033[0m)\033[1;32m%s\033[0m\t[",p->char_id,p->name); + printf("char1 "); + + sprintf(tmp_sql, "SELECT `option`,`karma`,`manner`,`party_id`,`guild_id`,`pet_id`,`hair`,`hair_color`," + "`clothes_color`,`weapon`,`shield`,`head_top`,`head_mid`,`head_bottom`," + "`last_map`,`last_x`,`last_y`,`save_map`,`save_x`,`save_y`, `partner_id` FROM `%s` WHERE `char_id` = '%d'",char_db, char_id); + if (mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error (select `char2`)- %s\n", mysql_error(&mysql_handle)); + } + + sql_res = mysql_store_result(&mysql_handle); + if (sql_res) { + sql_row = mysql_fetch_row(sql_res); + + + p->option = atoi(sql_row[0]); p->karma = atoi(sql_row[1]); p->manner = atoi(sql_row[2]); + p->party_id = atoi(sql_row[3]); p->guild_id = atoi(sql_row[4]); p->pet_id = atoi(sql_row[5]); + + p->hair = atoi(sql_row[6]); p->hair_color = atoi(sql_row[7]); p->clothes_color = atoi(sql_row[8]); + p->weapon = atoi(sql_row[9]); p->shield = atoi(sql_row[10]); + p->head_top = atoi(sql_row[11]); p->head_mid = atoi(sql_row[12]); p->head_bottom = atoi(sql_row[13]); + strcpy(p->last_point.map,sql_row[14]); p->last_point.x = atoi(sql_row[15]); p->last_point.y = atoi(sql_row[16]); + strcpy(p->save_point.map,sql_row[17]); p->save_point.x = atoi(sql_row[18]); p->save_point.y = atoi(sql_row[19]); + p->partner_id = atoi(sql_row[20]); + + //free mysql result. + mysql_free_result(sql_res); + } else + printf("char2 - failed\n"); //Error?! ERRRRRR WHAT THAT SAY!? + + printf("char2 "); + + //read memo data + //`memo` (`memo_id`,`char_id`,`map`,`x`,`y`) + sprintf(tmp_sql, "SELECT `map`,`x`,`y` FROM `%s` WHERE `char_id`='%d'",memo_db, char_id); + if (mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error (select `memo`)- %s\n", mysql_error(&mysql_handle)); + } + sql_res = mysql_store_result(&mysql_handle); + + if (sql_res) { + for(i=0;(sql_row = mysql_fetch_row(sql_res));i++){ + strcpy (p->memo_point[i].map,sql_row[0]); + p->memo_point[i].x=atoi(sql_row[1]); + p->memo_point[i].y=atoi(sql_row[2]); + //i ++; + } + mysql_free_result(sql_res); + } + printf("memo "); + + //read inventory + //`inventory` (`id`,`char_id`, `nameid`, `amount`, `equip`, `identify`, `refine`, `attribute`, `card0`, `card1`, `card2`, `card3`) + sprintf(tmp_sql, "SELECT `id`, `nameid`, `amount`, `equip`, `identify`, `refine`, `attribute`, `card0`, `card1`, `card2`, `card3`, `broken` " + "FROM `%s` WHERE `char_id`='%d'",inventory_db, char_id); + if (mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error (select `inventory`)- %s\n", mysql_error(&mysql_handle)); + } + sql_res = mysql_store_result(&mysql_handle); + if (sql_res) { + for(i=0;(sql_row = mysql_fetch_row(sql_res));i++){ + p->inventory[i].id = atoi(sql_row[0]); + p->inventory[i].nameid = atoi(sql_row[1]); + p->inventory[i].amount = atoi(sql_row[2]); + p->inventory[i].equip = atoi(sql_row[3]); + p->inventory[i].identify = atoi(sql_row[4]); + p->inventory[i].refine = atoi(sql_row[5]); + p->inventory[i].attribute = atoi(sql_row[6]); + p->inventory[i].card[0] = atoi(sql_row[7]); + p->inventory[i].card[1] = atoi(sql_row[8]); + p->inventory[i].card[2] = atoi(sql_row[9]); + p->inventory[i].card[3] = atoi(sql_row[10]); + p->inventory[i].broken = atoi(sql_row[11]); + } + mysql_free_result(sql_res); + } + printf("inventory "); + + + //read cart. + //`cart_inventory` (`id`,`char_id`, `nameid`, `amount`, `equip`, `identify`, `refine`, `attribute`, `card0`, `card1`, `card2`, `card3`) + sprintf(tmp_sql, "SELECT `id`, `nameid`, `amount`, `equip`, `identify`, `refine`, `attribute`, `card0`, `card1`, `card2`, `card3`, `broken` " + "FROM `%s` WHERE `char_id`='%d'",cart_db, char_id); + if (mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error (select `cart_inventory`)- %s\n", mysql_error(&mysql_handle)); + } + sql_res = mysql_store_result(&mysql_handle); + if (sql_res) { + for(i=0;(sql_row = mysql_fetch_row(sql_res));i++){ + p->cart[i].id = atoi(sql_row[0]); + p->cart[i].nameid = atoi(sql_row[1]); + p->cart[i].amount = atoi(sql_row[2]); + p->cart[i].equip = atoi(sql_row[3]); + p->cart[i].identify = atoi(sql_row[4]); + p->cart[i].refine = atoi(sql_row[5]); + p->cart[i].attribute = atoi(sql_row[6]); + p->cart[i].card[0] = atoi(sql_row[7]); + p->cart[i].card[1] = atoi(sql_row[8]); + p->cart[i].card[2] = atoi(sql_row[9]); + p->cart[i].card[3] = atoi(sql_row[10]); + p->cart[i].broken = atoi(sql_row[11]); + } + mysql_free_result(sql_res); + } + printf("cart "); + + //read skill + //`skill` (`char_id`, `id`, `lv`) + sprintf(tmp_sql, "SELECT `id`, `lv` FROM `%s` WHERE `char_id`='%d'",skill_db, char_id); + if (mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error (select `skill`)- %s\n", mysql_error(&mysql_handle)); + } + sql_res = mysql_store_result(&mysql_handle); + if (sql_res) { + for(i=0;(sql_row = mysql_fetch_row(sql_res));i++){ + n = atoi(sql_row[0]); + p->skill[n].id = n; //memory!? shit!. + p->skill[n].lv = atoi(sql_row[1]); + } + mysql_free_result(sql_res); + } + printf("skill "); + + //global_reg + //`global_reg_value` (`char_id`, `str`, `value`) + sprintf(tmp_sql, "SELECT `str`, `value` FROM `%s` WHERE `type`=3 AND `char_id`='%d'",reg_db, char_id); + if (mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error (select `global_reg_value`)- %s\n", mysql_error(&mysql_handle)); + } + i = 0; + sql_res = mysql_store_result(&mysql_handle); + if (sql_res) { + for(i=0;(sql_row = mysql_fetch_row(sql_res));i++){ + strcpy (p->global_reg[i].str, sql_row[0]); + p->global_reg[i].value = atoi (sql_row[1]); + } + mysql_free_result(sql_res); + } + p->global_reg_num=i; + + if (online) { + sprintf(tmp_sql, "UPDATE `%s` SET `online`='%d' WHERE `char_id`='%d'",char_db,online,char_id); + if (mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error (set char online)- %s\n", mysql_error(&mysql_handle)); + } + } + + printf("global_reg]\n"); //ok. all data load successfuly! + + //printf("char cloade"); + + return 1; +} +//========================================================================================================== +int mmo_char_sql_init(void) { + int i; + + printf("init start.......\n"); + // memory initialize + // no need to set twice size in this routine. but some cause segmentation error. :P + printf("initializing char memory...(%d byte)\n",sizeof(struct mmo_charstatus)*2); + CREATE(char_dat, struct mmo_charstatus, 2); + + memset(char_dat, 0, sizeof(struct mmo_charstatus)*2); +/* Initialized in inter.c already [Wizputer] + // DB connection initialized + // for char-server session only + mysql_init(&mysql_handle); + printf("Connect DB server....(char server)\n"); + if(!mysql_real_connect(&mysql_handle, char_server_ip, char_server_id, char_server_pw, char_server_db ,char_server_port, (char *)NULL, 0)) { + // SQL connection pointer check + printf("%s\n",mysql_error(&mysql_handle)); + exit(1); + } else { + printf("connect success! (char server)\n"); + } +*/ + sprintf(tmp_sql , "SELECT count(*) FROM `%s`", char_db); + if (mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + sql_res = mysql_store_result(&mysql_handle) ; + sql_row = mysql_fetch_row(sql_res); + printf("total char data -> '%s'.......\n",sql_row[0]); + i = atoi (sql_row[0]); + mysql_free_result(sql_res); + + if (i !=0) { + sprintf(tmp_sql , "SELECT max(`char_id`) FROM `%s`", char_db); + if (mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + sql_res = mysql_store_result(&mysql_handle) ; + sql_row = mysql_fetch_row(sql_res); + char_id_count = atoi (sql_row[0]); + + mysql_free_result(sql_res); + } else + printf("set char_id_count: %d.......\n",char_id_count); + + sprintf(tmp_sql,"UPDATE `%s` SET `online`='0' WHERE `online`='1'", char_db); + if (mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error (reset_online `%s`)- %s\n", char_db, mysql_error(&mysql_handle)); + } + printf("init end.......\n"); + + return 0; +} + +//========================================================================================================== + +int make_new_char_sql(int fd, unsigned char *dat) { + struct char_session_data *sd; + char t_name[100]; + int i; + //aphostropy error check! - fixed! + jstrescapecpy(t_name, dat); + printf("making new char -"); + + sd = session[fd]->session_data; + + // Check Authorised letters/symbols in the name of the character + if (char_name_option == 1) { // only letters/symbols in char_name_letters are authorised + for (i = 0; i < strlen(dat); i++) + if (strchr(char_name_letters, dat[i]) == NULL) + return -1; + } else if (char_name_option == 2) { // letters/symbols in char_name_letters are forbidden + for (i = 0; i < strlen(dat); i++) + if (strchr(char_name_letters, dat[i]) != NULL) + return -1; + } // else, all letters/symbols are authorised (except control char removed before) + + //check stat error + if ((dat[24]+dat[25]+dat[26]+dat[27]+dat[28]+dat[29]!=5*6 ) || + (dat[30] >= 9) || + (dat[33] <= 0) || (dat[33] >= 20) || + (dat[31] >= 9)) { + + // check individual stat value + for(i = 24; i <= 29; i++) { + if (dat[i] < 1 || dat[i] > 9) { + return -1; + } + } + + // char.log to charlog + sprintf(tmp_sql,"INSERT INTO `%s` (`time`, `char_msg`,`account_id`,`char_num`,`name`,`str`,`agi`,`vit`,`int`,`dex`,`luk`,`hair`,`hair_color`)" + "VALUES (NOW(), '%s', '%d', '%d', '%s', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d')", + charlog_db,"make new char error", sd->account_id, dat[30], dat, dat[24], dat[25], dat[26], dat[27], dat[28], dat[29], dat[33], dat[31]); + //query + if (mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + printf("make new char error %d-%d %s %d, %d, %d, %d, %d, %d %d, %d" RETCODE, + fd, dat[30], dat, dat[24], dat[25], dat[26], dat[27], dat[28], dat[29], dat[33], dat[31]); + return -1; + } + + // char.log to charlog + sprintf(tmp_sql,"INSERT INTO `%s`(`time`, `char_msg`,`account_id`,`char_num`,`name`,`str`,`agi`,`vit`,`int`,`dex`,`luk`,`hair`,`hair_color`)" + "VALUES (NOW(), '%s', '%d', '%d', '%s', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d')", + charlog_db,"make new char", sd->account_id, dat[30], dat, dat[24], dat[25], dat[26], dat[27], dat[28], dat[29], dat[33], dat[31]); + //query + if (mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + printf("make new char %d-%d %s %d, %d, %d, %d, %d, %d - %d, %d" RETCODE, + fd, dat[30], dat, dat[24], dat[25], dat[26], dat[27], dat[28], dat[29], dat[33], dat[31]); + + sprintf(tmp_sql, "SELECT count(*) FROM `%s` WHERE `name` = '%s'",char_db, t_name); + if (mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + return -1; + } + sql_res = mysql_store_result(&mysql_handle); + sql_row = mysql_fetch_row(sql_res); + printf("\033[1;32m name check result : %s -\033[0m ",sql_row[0]); + if (atoi(sql_row[0]) > 0) { + mysql_free_result(sql_res); + return -1; + } else + mysql_free_result(sql_res); + + // check char slot. + sprintf(tmp_sql, "SELECT count(*) FROM `%s` WHERE `account_id` = '%d' AND `char_num` = '%d'",char_db, sd->account_id, dat[30]); + if (mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + sql_res = mysql_store_result(&mysql_handle); + sql_row = mysql_fetch_row(sql_res); + + //printf("slot check result : %s\n",sql_row[0]); + if (atoi(sql_row[0]) > 0) { + mysql_free_result(sql_res); + return -1; + } else + mysql_free_result(sql_res); + + char_id_count++; + + // make new char. + sprintf(tmp_sql,"INSERT INTO `%s` (`char_id`,`account_id`,`char_num`,`name`,`zeny`,`str`,`agi`,`vit`,`int`,`dex`,`luk`,`max_hp`,`hp`,`max_sp`,`sp`,`hair`,`hair_color`)" + " VALUES ('%d', '%d', '%d', '%s', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d','%d', '%d','%d', '%d')", + char_db, char_id_count, sd->account_id , dat[30] , t_name, start_zeny, dat[24], dat[25], dat[26], dat[27], dat[28], dat[29], + (40 * (100 + dat[26])/100) , (40 * (100 + dat[26])/100 ), (11 * (100 + dat[27])/100), (11 * (100 + dat[27])/100), dat[33], dat[31]); + if (mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error (insert `char`)- %s\n", mysql_error(&mysql_handle)); + } + + //`inventory` (`id`,`char_id`, `nameid`, `amount`, `equip`, `identify`, `refine`, `attribute`, `card0`, `card1`, `card2`, `card3`) + sprintf(tmp_sql,"INSERT INTO `%s` (`char_id`,`nameid`, `amount`, `equip`, `identify`) VALUES ('%d', '%d', '%d', '%d', '%d')", + inventory_db, char_id_count, 1201,1,0x02,1); //add Knife + if (mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error (insert `inventory`)- %s\n", mysql_error(&mysql_handle)); + } + + sprintf(tmp_sql,"INSERT INTO `%s` (`char_id`,`nameid`, `amount`, `equip`, `identify`) VALUES ('%d', '%d', '%d', '%d', '%d')", + inventory_db, char_id_count, 2301,1,0x10,1); //add Cotton Shirt + if (mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error (insert `inventory`)- %s\n", mysql_error(&mysql_handle)); + } + // respawn map and start point set + sprintf(tmp_sql,"UPDATE `%s` SET `last_map`='%s',`last_x`='%d',`last_y`='%d',`save_map`='%s',`save_x`='%d',`save_y`='%d' WHERE `char_id` = '%d'", + char_db, start_point.map,start_point.x,start_point.y, start_point.map,start_point.x,start_point.y, char_id_count); + if (mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error (update `char`)- %s\n", mysql_error(&mysql_handle)); + } + printf("making new char success - id:(\033[1;32m%d\033[0m\tname:\033[1;32%s\033[0m\n", char_id_count, t_name); + return char_id_count; +} + +//========================================================================================================== + +void mmo_char_sync(void){ + printf("mmo_char_sync() - nothing to do\n"); +} + +// to do +/////////////////////////// + +int mmo_char_sync_timer(int tid, unsigned int tick, int id, int data) { + printf("mmo_char_sync_timer() tic - no works to do\n"); + return 0; +} + +int count_users(void) { + int i, users; + + if (login_fd > 0 && session[login_fd]){ + users = 0; + for(i = 0; i < MAX_MAP_SERVERS; i++) { + if (server_fd[i] >= 0) { + users += server[i].users; + } + } + return users; + } + return 0; +} + +int mmo_char_send006b(int fd, struct char_session_data *sd) { + int i, j, found_num = 0; + struct mmo_charstatus *p = NULL; +// hehe. commented other. anyway there's no need to use older version. +// if use older packet version just uncomment that! +//#ifdef NEW_006b + const int offset = 24; +//#else +// int offset = 4; +//#endif + + printf("mmo_char_send006b start.. (account:%d)\n",sd->account_id); +// printf("offset -> %d...\n",offset); + + //search char. + sprintf(tmp_sql, "SELECT `char_id` FROM `%s` WHERE `account_id` = '%d'",char_db, sd->account_id); + if (mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + sql_res = mysql_store_result(&mysql_handle); + if (sql_res) { + found_num = mysql_num_rows(sql_res); + printf("number of chars: %d\n", found_num); + i = 0; + while((sql_row = mysql_fetch_row(sql_res))) { + sd->found_char[i] = atoi(sql_row[0]); + i++; + } + mysql_free_result(sql_res); + } + +// printf("char fetching end (total: %d)....\n", found_num); + + for(i = found_num; i < 9; i++) + sd->found_char[i] = -1; + + memset(WFIFOP(fd, 0), 0, offset + found_num * 106); + WFIFOW(fd, 0) = 0x6b; + WFIFOW(fd, 2) = offset + found_num * 106; + + printf("(\033[1;13m%d\033[0m) Request Char Data:\n",sd->account_id); + + for(i = 0; i < found_num; i++) { + mmo_char_fromsql(sd->found_char[i], char_dat, 0); + + p = &char_dat[0]; + + j = offset + (i * 106); // increase speed of code + + WFIFOL(fd,j) = p->char_id; + WFIFOL(fd,j+4) = p->base_exp; + WFIFOL(fd,j+8) = p->zeny; + WFIFOL(fd,j+12) = p->job_exp; + WFIFOL(fd,j+16) = p->job_level; + + WFIFOL(fd,j+20) = 0; + WFIFOL(fd,j+24) = 0; + WFIFOL(fd,j+28) = p->option; + + WFIFOL(fd,j+32) = p->karma; + WFIFOL(fd,j+36) = p->manner; + + WFIFOW(fd,j+40) = p->status_point; + WFIFOW(fd,j+42) = (p->hp > 0x7fff) ? 0x7fff : p->hp; + WFIFOW(fd,j+44) = (p->max_hp > 0x7fff) ? 0x7fff : p->max_hp; + WFIFOW(fd,j+46) = (p->sp > 0x7fff) ? 0x7fff : p->sp; + WFIFOW(fd,j+48) = (p->max_sp > 0x7fff) ? 0x7fff : p->max_sp; + WFIFOW(fd,j+50) = DEFAULT_WALK_SPEED; // p->speed; + WFIFOW(fd,j+52) = p->class; + WFIFOW(fd,j+54) = p->hair; + WFIFOW(fd,j+56) = p->weapon; + WFIFOW(fd,j+58) = p->base_level; + WFIFOW(fd,j+60) = p->skill_point; + WFIFOW(fd,j+62) = p->head_bottom; + WFIFOW(fd,j+64) = p->shield; + WFIFOW(fd,j+66) = p->head_top; + WFIFOW(fd,j+68) = p->head_mid; + WFIFOW(fd,j+70) = p->hair_color; + WFIFOW(fd,j+72) = p->clothes_color; + + memcpy(WFIFOP(fd,j+74), p->name, 24); + + WFIFOB(fd,j+98) = (p->str > 255) ? 255 : p->str; + WFIFOB(fd,j+99) = (p->agi > 255) ? 255 : p->agi; + WFIFOB(fd,j+100) = (p->vit > 255) ? 255 : p->vit; + WFIFOB(fd,j+101) = (p->int_ > 255) ? 255 : p->int_; + WFIFOB(fd,j+102) = (p->dex > 255) ? 255 : p->dex; + WFIFOB(fd,j+103) = (p->luk > 255) ? 255 : p->luk; + WFIFOB(fd,j+104) = p->char_num; + } + + WFIFOSET(fd,WFIFOW(fd,2)); +// printf("mmo_char_send006b end..\n"); + return 0; +} + +int parse_tologin(int fd) { + int i; + struct char_session_data *sd; + + // only login-server can have an access to here. + // so, if it isn't the login-server, we disconnect the session. + //session eof check! + if (fd != login_fd || session[fd]->eof) { + if (fd == login_fd) { + printf("Char-server can't connect to login-server (connection #%d).\n", fd); + login_fd = -1; + } + close(fd); + delete_session(fd); + return 0; + } + + sd = session[fd]->session_data; + + // hehe. no need to set user limite on SQL version. :P + // but char limitation is good way to maintain server. :D + while(RFIFOREST(fd) >= 2) { +// printf("parse_tologin : %d %d %x\n", fd, RFIFOREST(fd), RFIFOW(fd, 0)); + + switch(RFIFOW(fd, 0)){ + case 0x2711: + if (RFIFOREST(fd) < 3) + return 0; + if (RFIFOB(fd, 2)) { + //printf("connect login server error : %d\n", RFIFOB(fd, 2)); + printf("Can not connect to login-server.\n"); + printf("The server communication passwords (default s1/p1) is probably invalid.\n"); + printf("Also, please make sure your login db has the username/password present and the sex of the account is S.\n"); + printf("If you changed the communication passwords, change them back at map_athena.conf and char_athena.conf\n"); + return 0; + //exit(1); //fixed for server shutdown. + }else { + printf("Connected to login-server (connection #%d).\n", fd); + // if no map-server already connected, display a message... + for(i = 0; i < MAX_MAP_SERVERS; i++) + if (server_fd[i] >= 0 && server[i].map[0][0]) // if map-server online and at least 1 map + break; + if (i == MAX_MAP_SERVERS) + printf("Awaiting maps from map-server.\n"); + } + RFIFOSKIP(fd, 3); + break; + + case 0x2713: + if(RFIFOREST(fd)<51) + return 0; + for(i = 0; i < fd_max; i++) { + if (session[i] && (sd = session[i]->session_data) && sd->account_id == RFIFOL(fd,2)) { + if (RFIFOB(fd,6) != 0) { + WFIFOW(i,0) = 0x6c; + WFIFOB(i,2) = 0x42; + WFIFOSET(i,3); + } else if (max_connect_user == 0 || count_users() < max_connect_user) { +// if (max_connect_user == 0) +// printf("max_connect_user (unlimited) -> accepted.\n"); +// else +// printf("count_users(): %d < max_connect_user (%d) -> accepted.\n", count_users(), max_connect_user); + sd->connect_until_time = (time_t)RFIFOL(fd,47); + // send characters to player + mmo_char_send006b(i, sd); + } else { + // refuse connection: too much online players +// printf("count_users(): %d < max_connect_use (%d) -> fail...\n", count_users(), max_connect_user); + WFIFOW(i,0) = 0x6c; + WFIFOW(i,2) = 0; + WFIFOSET(i,3); + } + } + } + RFIFOSKIP(fd,51); + break; + + case 0x2717: + if (RFIFOREST(fd) < 50) + return 0; + for(i = 0; i < fd_max; i++) { + if (session[i] && (sd = session[i]->session_data)) { + if (sd->account_id == RFIFOL(fd,2)) { + sd->connect_until_time = (time_t)RFIFOL(fd,46); + break; + } + } + } + RFIFOSKIP(fd,50); + break; + +/* case 0x2721: // gm reply. I don't want to support this function. + printf("0x2721:GM reply\n"); + { + int oldacc, newacc; + unsigned char buf[64]; + if (RFIFOREST(fd) < 10) + return 0; + oldacc = RFIFOL(fd, 2); + newacc = RFIFOL(fd, 6); + RFIFOSKIP(fd, 10); + if (newacc > 0) { + for(i=0;i<char_num;i++){ + if(char_dat[i].account_id==oldacc) + char_dat[i].account_id=newacc; + } + } + WBUFW(buf,0)=0x2b0b; + WBUFL(buf,2)=oldacc; + WBUFL(buf,6)=newacc; + mapif_sendall(buf,10); +// printf("char -> map\n"); + } + break; +*/ + case 0x2723: // changesex reply (modified by [Yor]) + if (RFIFOREST(fd) < 7) + return 0; + { + int acc, sex; + unsigned char buf[16]; + + acc = RFIFOL(fd,2); + sex = RFIFOB(fd,6); + RFIFOSKIP(fd, 7); + if (acc > 0) { + sprintf(tmp_sql, "SELECT `char_id`,`class`,`skill_point` FROM `%s` WHERE `account_id` = '%d'",char_db, acc); + if (mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error (select `char`)- %s\n", mysql_error(&mysql_handle)); + } + sql_res = mysql_store_result(&mysql_handle); + + if (sql_res) { + sql_row = mysql_fetch_row(sql_res); + int char_id = atoi(sql_row[0]); + int jobclass = atoi(sql_row[1]); + int skill_point = atoi(sql_row[2]); + int class = jobclass; + if (jobclass == 19 || jobclass == 20 || + jobclass == 4020 || jobclass == 4021 || + jobclass == 4042 || jobclass == 4043) { + // job modification + if (jobclass == 19 || jobclass == 20) { + class = (sex) ? 19 : 20; + } else if (jobclass == 4020 || jobclass == 4021) { + class = (sex) ? 4020 : 4021; + } else if (jobclass == 4042 || jobclass == 4043) { + class = (sex) ? 4042 : 4043; + } + // remove specifical skills of classes 19,20 4020,4021 and 4042,4043 + sprintf(tmp_sql, "SELECT `lv` FROM `%s` WHERE `char_id` = '%d' AND `id` >= '315' AND `id` <= '330'",skill_db, char_id); + if (mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error (select `char`)- %s\n", mysql_error(&mysql_handle)); + } + sql_res = mysql_store_result(&mysql_handle); + if (sql_res) { + while(( sql_row = mysql_fetch_row(sql_res))) { + skill_point += atoi(sql_row[0]); + } + } + sprintf(tmp_sql, "DELETE FROM `%s` WHERE `char_id` = '%d' AND `id` >= '315' AND `id` <= '330'",skill_db, char_id); + if (mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error (select `char`)- %s\n", mysql_error(&mysql_handle)); + } + } + // to avoid any problem with equipment and invalid sex, equipment is unequiped. + sprintf(tmp_sql, "UPDATE `%s` SET `equip` = '0' WHERE `char_id` = '%d'",inventory_db, char_id); + if (mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error (select `char`)- %s\n", mysql_error(&mysql_handle)); + } + sprintf(tmp_sql, "UPDATE `%s` SET `class`='%d' , `skill_point`='%d' , `weapon`='0' , `shield='0' , `head_top`='0' , `head_mid`='0' , `head_bottom`='0' WHERE `char_id` = '%d'",char_db, class, skill_point, char_id); + if (mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error (select `char`)- %s\n", mysql_error(&mysql_handle)); + } + } + } + // disconnect player if online on char-server + for(i = 0; i < fd_max; i++) { + if (session[i] && (sd = session[i]->session_data)) { + if (sd->account_id == acc) { + session[i]->eof = 1; + break; + } + } + } + + WBUFW(buf,0) = 0x2b0d; + WBUFL(buf,2) = acc; + WBUFB(buf,6) = sex; + + mapif_sendall(buf, 7); + } + break; + + // account_reg2変更通知 + case 0x2729: + if (RFIFOREST(fd) < 4 || RFIFOREST(fd) < RFIFOW(fd,2)) + return 0; + { + struct global_reg reg[ACCOUNT_REG2_NUM]; + unsigned char buf[4096]; + int j, p, acc; + acc = RFIFOL(fd,4); + for(p = 8, j = 0; p < RFIFOW(fd,2) && j < ACCOUNT_REG2_NUM; p += 36, j++) { + memcpy(reg[j].str, RFIFOP(fd,p), 32); + reg[j].value = RFIFOL(fd,p+32); + } + // set_account_reg2(acc,j,reg); + // 同垢ログインを禁止していれば送る必要は無い + memcpy(buf,RFIFOP(fd,0), RFIFOW(fd,2)); + WBUFW(buf,0) = 0x2b11; + mapif_sendall(buf, WBUFW(buf,2)); + RFIFOSKIP(fd, RFIFOW(fd,2)); +// printf("char: save_account_reg_reply\n"); + } + break; + + // State change of account/ban notification (from login-server) by [Yor] + case 0x2731: + if (RFIFOREST(fd) < 11) + return 0; + // send to all map-servers to disconnect the player + { + unsigned char buf[16]; + WBUFW(buf,0) = 0x2b14; + WBUFL(buf,2) = RFIFOL(fd,2); + WBUFB(buf,6) = RFIFOB(fd,6); // 0: change of statut, 1: ban + WBUFL(buf,7) = RFIFOL(fd,7); // status or final date of a banishment + mapif_sendall(buf, 11); + } + // disconnect player if online on char-server + for(i = 0; i < fd_max; i++) { + if (session[i] && (sd = session[i]->session_data)) { + if (sd->account_id == RFIFOL(fd,2)) { + session[i]->eof = 1; + break; + } + } + } + RFIFOSKIP(fd,11); + break; + + default: + printf("set eof.\n"); + session[fd]->eof = 1; + return 0; + } + } + + RFIFOFLUSH(fd); + + return 0; +} + +//-------------------------------- +// Map-server anti-freeze system +//-------------------------------- +int map_anti_freeze_system(int tid, unsigned int tick, int id, int data) { + int i; + + for(i = 0; i < MAX_MAP_SERVERS; i++) { + if (server_fd[i] >= 0) {// if map-server is online + printf("map_anti_freeze_system: server #%d, flag: %d.\n", i, server_freezeflag[i]); + if (server_freezeflag[i]-- < 1) {// Map-server anti-freeze system. Counter. 5 ok, 4...0 freezed + printf("Map-server anti-freeze system: char-server #%d is frozen -> disconnection.\n", i); + session[server_fd[i]]->eof = 1; + sprintf(tmp_sql, "DELETE FROM `ragsrvinfo` WHERE `index`='%d'", server_fd[i]); + if (mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + } + } + } + + return 0; +} + +int parse_frommap(int fd) { + int i = 0, j = 0; + int id; + + // Sometimes fd=0, and it will cause server crash. Don't know why. :( + if (fd <= 0) { + printf("parse_frommap error fd=0\n"); + return 0; + } + + for(id = 0; id < MAX_MAP_SERVERS; id++) + if (server_fd[id] == fd) + break; + if(id == MAX_MAP_SERVERS || session[fd]->eof) { + if (id < MAX_MAP_SERVERS) { + memset(&server[id], 0, sizeof(struct mmo_map_server)); + printf("Map-server %d (session #%d) has disconnected.\n", id, fd); + sprintf(tmp_sql, "DELETE FROM `ragsrvinfo` WHERE `index`='%d'", server_fd[id]); + if (mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + server_fd[id] = -1; + } + close(fd); + delete_session(fd); + return 0; + } + + while(RFIFOREST(fd) >= 2) { +// printf("parse_frommap : %d %d %x\n", fd, RFIFOREST(fd), RFIFOW(fd,0)); + + switch(RFIFOW(fd, 0)) { + case 0x2af7: + RFIFOSKIP(fd,2); + read_gm_account(); + break; + + // mapserver -> map names recv. + case 0x2afa: + if (RFIFOREST(fd) < 4 || RFIFOREST(fd) < RFIFOW(fd,2)) + return 0; + memset(server[id].map, 0, sizeof(server[id].map)); + j = 0; + for(i = 4; i < RFIFOW(fd,2); i += 16) { + memcpy(server[id].map[j], RFIFOP(fd,i), 16); +// printf("set map %d.%d : %s\n", id, j, server[id].map[j]); + j++; + } + i = server[id].ip; + { + unsigned char *p = (unsigned char *)&server[id].ip; + printf("Map-Server %d connected: %d maps, from IP %d.%d.%d.%d port %d.\n", + id, j, p[0], p[1], p[2], p[3], server[id].port); + printf("Map-server %d loading complete.\n", id); + } + WFIFOW(fd,0) = 0x2afb; + WFIFOB(fd,2) = 0; + memcpy(WFIFOP(fd,3), wisp_server_name, 24); // name for wisp to player + WFIFOSET(fd,27); + { + unsigned char buf[16384]; + int x; + if (j == 0) { + printf("WARNING: Map-Server %d have NO maps.\n", id); + // Transmitting maps information to the other map-servers + } else { + WBUFW(buf,0) = 0x2b04; + WBUFW(buf,2) = j * 16 + 10; + WBUFL(buf,4) = server[id].ip; + WBUFW(buf,8) = server[id].port; + memcpy(WBUFP(buf,10), RFIFOP(fd,4), j * 16); + mapif_sendallwos(fd, buf, WBUFW(buf,2)); + } + // Transmitting the maps of the other map-servers to the new map-server + for(x = 0; x < MAX_MAP_SERVERS; x++) { + if (server_fd[x] >= 0 && x != id) { + WFIFOW(fd,0) = 0x2b04; + WFIFOL(fd,4) = server[x].ip; + WFIFOW(fd,8) = server[x].port; + j = 0; + for(i = 0; i < MAX_MAP_PER_SERVER; i++) + if (server[x].map[i][0]) + memcpy(WFIFOP(fd,10+(j++)*16), server[x].map[i], 16); + if (j > 0) { + WFIFOW(fd,2) = j * 16 + 10; + WFIFOSET(fd,WFIFOW(fd,2)); + } + } + } + } + RFIFOSKIP(fd,RFIFOW(fd,2)); + break; + + // auth request + case 0x2afc: + if (RFIFOREST(fd) < 22) + return 0; +// printf("(AUTH request) auth_fifo search %d %d %d\n", RFIFOL(fd, 2), RFIFOL(fd, 6), RFIFOL(fd, 10)); + for(i = 0; i < AUTH_FIFO_SIZE; i++) { + if (auth_fifo[i].account_id == RFIFOL(fd,2) && + auth_fifo[i].char_id == RFIFOL(fd,6) && + auth_fifo[i].login_id1 == RFIFOL(fd,10) && +#if CMP_AUTHFIFO_LOGIN2 != 0 + // here, it's the only area where it's possible that we doesn't know login_id2 (map-server asks just after 0x72 packet, that doesn't given the value) + (auth_fifo[i].login_id2 == RFIFOL(fd,14) || RFIFOL(fd,14) == 0) && // relate to the versions higher than 18 +#endif + (!check_ip_flag || auth_fifo[i].ip == RFIFOL(fd,18)) && + !auth_fifo[i].delflag) { + auth_fifo[i].delflag = 1; + WFIFOW(fd,0) = 0x2afd; + WFIFOW(fd,2) = 16 + sizeof(struct mmo_charstatus); + WFIFOL(fd,4) = RFIFOL(fd,2); + WFIFOL(fd,8) = auth_fifo[i].login_id2; + WFIFOL(fd,12) = (unsigned long)auth_fifo[i].connect_until_time; + mmo_char_fromsql(auth_fifo[i].char_id, char_dat, 1); + char_dat[0].sex = auth_fifo[i].sex; + memcpy(WFIFOP(fd,16), &char_dat[0], sizeof(struct mmo_charstatus)); + WFIFOSET(fd, WFIFOW(fd,2)); + //printf("auth_fifo search success (auth #%d, account %d, character: %d).\n", i, RFIFOL(fd,2), RFIFOL(fd,6)); + break; + } + } + if (i == AUTH_FIFO_SIZE) { + WFIFOW(fd,0) = 0x2afe; + WFIFOL(fd,2) = RFIFOL(fd,2); + WFIFOSET(fd,6); +// printf("(AUTH request) auth_fifo search error!\n"); + } + RFIFOSKIP(fd,22); + break; + + // set MAP user + case 0x2aff: + if (RFIFOREST(fd) < 6 || RFIFOREST(fd) < RFIFOW(fd,2)) + return 0; + if (RFIFOW(fd,4) != server[id].users) + printf("map user: %d\n", RFIFOW(fd,4)); + server[id].users = RFIFOW(fd,4); + if(anti_freeze_enable) + server_freezeflag[id] = 5; // Map anti-freeze system. Counter. 5 ok, 4...0 freezed + RFIFOSKIP(fd,RFIFOW(fd,2)); + break; + + // char saving + case 0x2b01: + i = 0; + if (RFIFOREST(fd) < 4 || RFIFOREST(fd) < RFIFOW(fd,2)) + return 0; + //check account + sprintf(tmp_sql, "SELECT count(*) FROM `%s` WHERE `account_id` = '%d' AND `char_id`='%d'",char_db, RFIFOL(fd,4),RFIFOL(fd,8)); + if (mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + sql_res = mysql_store_result(&mysql_handle); + if (sql_res) { + sql_row = mysql_fetch_row(sql_res); + if (sql_row) + i = atoi(sql_row[0]); + } + mysql_free_result(sql_res); + + if (i == 1) { + memcpy(&char_dat[0], RFIFOP(fd,12), sizeof(struct mmo_charstatus)); + mmo_char_tosql(RFIFOL(fd,8), char_dat); + //save to DB + } + RFIFOSKIP(fd,RFIFOW(fd,2)); + break; + + // req char selection + case 0x2b02: + if (RFIFOREST(fd) < 18) + return 0; + + if (auth_fifo_pos >= AUTH_FIFO_SIZE) + auth_fifo_pos = 0; + +// printf("(charselect) auth_fifo set %d - account_id:%08x login_id1:%08x\n", auth_fifo_pos, RFIFOL(fd, 2), RFIFOL(fd, 6)); + auth_fifo[auth_fifo_pos].account_id = RFIFOL(fd, 2); + auth_fifo[auth_fifo_pos].char_id = 0; + auth_fifo[auth_fifo_pos].login_id1 = RFIFOL(fd, 6); + auth_fifo[auth_fifo_pos].login_id2 = RFIFOL(fd,10); + auth_fifo[auth_fifo_pos].delflag = 2; + auth_fifo[auth_fifo_pos].char_pos = 0; + auth_fifo[auth_fifo_pos].connect_until_time = 0; // unlimited/unknown time by default (not display in map-server) + auth_fifo[auth_fifo_pos].ip = RFIFOL(fd,14); + auth_fifo_pos++; + + WFIFOW(fd, 0) = 0x2b03; + WFIFOL(fd, 2) = RFIFOL(fd, 2); + WFIFOB(fd, 6) = 0; + WFIFOSET(fd, 7); + + RFIFOSKIP(fd, 18); + break; + + // request "change map server" + case 0x2b05: + if (RFIFOREST(fd) < 49) + return 0; + + if (auth_fifo_pos >= AUTH_FIFO_SIZE) + auth_fifo_pos = 0; + + WFIFOW(fd, 0) = 0x2b06; + memcpy(WFIFOP(fd,2), RFIFOP(fd,2), 42); +// printf("(map change) auth_fifo set %d - account_id:%08x login_id1:%08x\n", auth_fifo_pos, RFIFOL(fd, 2), RFIFOL(fd, 6)); + auth_fifo[auth_fifo_pos].account_id = RFIFOL(fd, 2); + auth_fifo[auth_fifo_pos].login_id1 = RFIFOL(fd, 6); + auth_fifo[auth_fifo_pos].login_id2 = RFIFOL(fd,10); + auth_fifo[auth_fifo_pos].char_id = RFIFOL(fd,14); + auth_fifo[auth_fifo_pos].delflag = 0; + auth_fifo[auth_fifo_pos].sex = RFIFOB(fd,44); + auth_fifo[auth_fifo_pos].ip = RFIFOL(fd,45); + + sprintf(tmp_sql, "SELECT count(*) FROM `%s` WHERE `account_id` = '%d' AND `char_id`='%d'", char_db, RFIFOL(fd,2), RFIFOL(fd,14)); + if (mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + sql_res = mysql_store_result(&mysql_handle); + + if (( sql_row = mysql_fetch_row(sql_res))) { + i = atoi(sql_row[0]); + mysql_free_result(sql_res); + + auth_fifo[auth_fifo_pos].char_pos = auth_fifo[auth_fifo_pos].char_id; + auth_fifo_pos++; + + WFIFOL(fd,6) = 0; + break; + } + if (i == 0) + WFIFOW(fd,6) = 1; + + WFIFOSET(fd,44); + RFIFOSKIP(fd,49); + break; + + // char name check + case 0x2b08: + if (RFIFOREST(fd) < 6) + return 0; + + sprintf(tmp_sql, "SELECT `name` FROM `%s` WHERE `char_id`='%d'", char_db, RFIFOL(fd,2)); + if (mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + sql_res = mysql_store_result(&mysql_handle); + + sql_row = mysql_fetch_row(sql_res); + + WFIFOW(fd,0) = 0x2b09; + WFIFOL(fd,2) = RFIFOL(fd,2); + + if (sql_row) + memcpy(WFIFOP(fd,6), sql_row[0], 24); + else + memcpy(WFIFOP(fd,6), unknown_char_name, 24); + mysql_free_result(sql_res); + + WFIFOSET(fd,30); + + RFIFOSKIP(fd,6); + break; + +/* // I want become GM - fuck! + case 0x2b0a: + if(RFIFOREST(fd)<4) + return 0; + if(RFIFOREST(fd)<RFIFOW(fd,2)) + return 0; + memcpy(WFIFOP(login_fd,2),RFIFOP(fd,2),RFIFOW(fd,2)-2); + WFIFOW(login_fd,0)=0x2720; + WFIFOSET(login_fd,RFIFOW(fd,2)); +// printf("char : change gm -> login %d %s %d\n", RFIFOL(fd, 4), RFIFOP(fd, 8), RFIFOW(fd, 2)); + RFIFOSKIP(fd, RFIFOW(fd, 2)); + break; + */ + + // account_reg保存要求 + case 0x2b10: + if (RFIFOREST(fd) < 4 || RFIFOREST(fd) < RFIFOW(fd,2)) + return 0; + { + struct global_reg reg[ACCOUNT_REG2_NUM]; + int j,p,acc; + acc=RFIFOL(fd,4); + for(p=8,j=0;p<RFIFOW(fd,2) && j<ACCOUNT_REG2_NUM;p+=36,j++){ + memcpy(reg[j].str,RFIFOP(fd,p),32); + reg[j].value=RFIFOL(fd,p+32); + } + // set_account_reg2(acc,j,reg); + // loginサーバーへ送る + if (login_fd > 0) { // don't send request if no login-server + WFIFOW(login_fd, 0) = 0x2728; + memcpy(WFIFOP(login_fd,0), RFIFOP(fd,0), RFIFOW(fd,2)); + WFIFOSET(login_fd, WFIFOW(login_fd,2)); + } + // ワールドへの同垢ログインがなければmapサーバーに送る必要はない + //memcpy(buf,RFIFOP(fd,0),RFIFOW(fd,2)); + //WBUFW(buf,0)=0x2b11; + //mapif_sendall(buf,WBUFW(buf,2)); + RFIFOSKIP(fd,RFIFOW(fd,2)); +// printf("char: save_account_reg (from map)\n"); + } + break; + + // Map server send information to change an email of an account -> login-server + case 0x2b0c: + if (RFIFOREST(fd) < 86) + return 0; + if (login_fd > 0) { // don't send request if no login-server + memcpy(WFIFOP(login_fd,0), RFIFOP(fd,0), 86); // 0x2722 <account_id>.L <actual_e-mail>.40B <new_e-mail>.40B + WFIFOW(login_fd,0) = 0x2722; + WFIFOSET(login_fd, 86); + } + RFIFOSKIP(fd, 86); + break; + + // Receiving from map-server a status change resquest. Transmission to login-server (by Yor) + case 0x2b0e: + if (RFIFOREST(fd) < 44) + return 0; + { + char character_name[24]; + int acc = RFIFOL(fd,2); // account_id of who ask (-1 if nobody) + memcpy(character_name, RFIFOP(fd,6), 24); + character_name[sizeof(character_name) -1] = '\0'; + // prepare answer + WFIFOW(fd,0) = 0x2b0f; // answer + WFIFOL(fd,2) = acc; // who want do operation + WFIFOW(fd,30) = RFIFOW(fd, 30); // type of operation: 1-block, 2-ban, 3-unblock, 4-unban + sprintf(tmp_sql, "SELECT `account_id`,`name` FROM `%s` WHERE `name` = '%s'",char_db, character_name); + if (mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error (select `char`)- %s\n", mysql_error(&mysql_handle)); + } + + sql_res = mysql_store_result(&mysql_handle); + + if (sql_res) { + if (mysql_num_rows(sql_res)) { + sql_row = mysql_fetch_row(sql_res); + memcpy(WFIFOP(fd,6), sql_row[1], 24); // put correct name if found + WFIFOW(fd,32) = 0; // answer: 0-login-server resquest done, 1-player not found, 2-gm level too low, 3-login-server offline + switch(RFIFOW(fd, 30)) { + case 1: // block + if (acc == -1 || isGM(acc) >= isGM(atoi(sql_row[0]))) { + if (login_fd > 0) { // don't send request if no login-server + WFIFOW(login_fd,0) = 0x2724; + WFIFOL(login_fd,2) = atoi(sql_row[0]); // account value + WFIFOL(login_fd,6) = 5; // status of the account + WFIFOSET(login_fd, 10); +// printf("char : status -> login: account %d, status: %d \n", char_dat[i].account_id, 5); + } else + WFIFOW(fd,32) = 3; // answer: 0-login-server resquest done, 1-player not found, 2-gm level too low, 3-login-server offline + } else + WFIFOW(fd,32) = 2; // answer: 0-login-server resquest done, 1-player not found, 2-gm level too low, 3-login-server offline + break; + case 2: // ban + if (acc == -1 || isGM(acc) >= isGM(atoi(sql_row[0]))) { + if (login_fd > 0) { // don't send request if no login-server + WFIFOW(login_fd, 0) = 0x2725; + WFIFOL(login_fd, 2) = atoi(sql_row[0]); // account value + WFIFOW(login_fd, 6) = RFIFOW(fd,32); // year + WFIFOW(login_fd, 8) = RFIFOW(fd,34); // month + WFIFOW(login_fd,10) = RFIFOW(fd,36); // day + WFIFOW(login_fd,12) = RFIFOW(fd,38); // hour + WFIFOW(login_fd,14) = RFIFOW(fd,40); // minute + WFIFOW(login_fd,16) = RFIFOW(fd,42); // second + WFIFOSET(login_fd,18); +// printf("char : status -> login: account %d, ban: %dy %dm %dd %dh %dmn %ds\n", +// char_dat[i].account_id, (short)RFIFOW(fd,32), (short)RFIFOW(fd,34), (short)RFIFOW(fd,36), (short)RFIFOW(fd,38), (short)RFIFOW(fd,40), (short)RFIFOW(fd,42)); + } else + WFIFOW(fd,32) = 3; // answer: 0-login-server resquest done, 1-player not found, 2-gm level too low, 3-login-server offline + } else + WFIFOW(fd,32) = 2; // answer: 0-login-server resquest done, 1-player not found, 2-gm level too low, 3-login-server offline + break; + case 3: // unblock + if (acc == -1 || isGM(acc) >= isGM(atoi(sql_row[0]))) { + if (login_fd > 0) { // don't send request if no login-server + WFIFOW(login_fd,0) = 0x2724; + WFIFOL(login_fd,2) = atoi(sql_row[0]); // account value + WFIFOL(login_fd,6) = 0; // status of the account + WFIFOSET(login_fd, 10); +// printf("char : status -> login: account %d, status: %d \n", char_dat[i].account_id, 0); + } else + WFIFOW(fd,32) = 3; // answer: 0-login-server resquest done, 1-player not found, 2-gm level too low, 3-login-server offline + } else + WFIFOW(fd,32) = 2; // answer: 0-login-server resquest done, 1-player not found, 2-gm level too low, 3-login-server offline + break; + case 4: // unban + if (acc == -1 || isGM(acc) >= isGM(atoi(sql_row[0]))) { + if (login_fd > 0) { // don't send request if no login-server + WFIFOW(login_fd, 0) = 0x272a; + WFIFOL(login_fd, 2) = atoi(sql_row[0]); // account value + WFIFOSET(login_fd, 6); +// printf("char : status -> login: account %d, unban request\n", char_dat[i].account_id); + } else + WFIFOW(fd,32) = 3; // answer: 0-login-server resquest done, 1-player not found, 2-gm level too low, 3-login-server offline + } else + WFIFOW(fd,32) = 2; // answer: 0-login-server resquest done, 1-player not found, 2-gm level too low, 3-login-server offline + break; + case 5: // changesex + if (acc == -1 || isGM(acc) >= isGM(atoi(sql_row[0]))) { + if (login_fd > 0) { // don't send request if no login-server + WFIFOW(login_fd, 0) = 0x2727; + WFIFOL(login_fd, 2) = atoi(sql_row[0]); // account value + WFIFOSET(login_fd, 6); +// printf("char : status -> login: account %d, change sex request\n", char_dat[i].account_id); + } else + WFIFOW(fd,32) = 3; // answer: 0-login-server resquest done, 1-player not found, 2-gm level too low, 3-login-server offline + } else + WFIFOW(fd,32) = 2; // answer: 0-login-server resquest done, 1-player not found, 2-gm level too low, 3-login-server offline + break; + } + } else { + // character name not found + memcpy(WFIFOP(fd,6), character_name, 24); + WFIFOW(fd,32) = 1; // answer: 0-login-server resquest done, 1-player not found, 2-gm level too low, 3-login-server offline + } + // send answer if a player ask, not if the server ask + if (acc != -1) { + WFIFOSET(fd, 34); + } + } + } + RFIFOSKIP(fd, 44); + break; + + // Recieve rates [Wizputer] + case 0x2b16: + if (RFIFOREST(fd) < 6 || RFIFOREST(fd) < RFIFOW(fd,8)) + return 0; + sprintf(tmp_sql, "INSERT INTO `ragsrvinfo` SET `index`='%d',`name`='%s',`exp`='%d',`jexp`='%d',`drop`='%d',`motd`='%s'", + fd, server_name, RFIFOW(fd,2), RFIFOW(fd,4), RFIFOW(fd,6), RFIFOP(fd,10)); + if (mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + RFIFOSKIP(fd,RFIFOW(fd,8)); + break; + + // Character disconnected set online 0 [Wizputer] + case 0x2b17: + if (RFIFOREST(fd) < 6 ) + return 0; + //printf("Setting %d char offline\n",RFIFOL(fd,2)); + sprintf(tmp_sql,"UPDATE `%s` SET `online`='0' WHERE `char_id`='%d'", char_db, RFIFOL(fd,2)); + if (mysql_query(&mysql_handle, tmp_sql)) + printf("DB server Error (update online `%s`)- %s\n", char_db, mysql_error(&mysql_handle)); + RFIFOSKIP(fd,6); + break; + + default: + // inter server - packet + { + int r = inter_parse_frommap(fd); + if (r == 1) break; // processed + if (r == 2) return 0; // need more packet + } + + // no inter server packet. no char server packet -> disconnect + printf("parse_frommap: unknown packet %x! \n", RFIFOW(fd,0)); + session[fd]->eof = 1; + return 0; + } + } + return 0; +} + +int search_mapserver(char *map) { + int i, j; + char temp_map[16]; + int temp_map_len; + +// printf("Searching the map-server for map '%s'... ", map); + strncpy(temp_map, map, sizeof(temp_map)); + temp_map[sizeof(temp_map)-1] = '\0'; + if (strchr(temp_map, '.') != NULL) + temp_map[strchr(temp_map, '.') - temp_map + 1] = '\0'; // suppress the '.gat', but conserve the '.' to be sure of the name of the map + + temp_map_len = strlen(temp_map); + for(i = 0; i < MAX_MAP_SERVERS; i++) + if (server_fd[i] >= 0) + for (j = 0; server[i].map[j][0]; j++) + //printf("%s : %s = %d\n", server[i].map[j], map, strncmp(server[i].map[j], temp_map, temp_map_len)); + if (strncmp(server[i].map[j], temp_map, temp_map_len) == 0) { +// printf("found -> server #%d.\n", i); + return i; + } + +// printf("not found.\n"); + return -1; +} + +int char_mapif_init(int fd) { + return inter_mapif_init(fd); +} + +//----------------------------------------------------- +// Test to know if an IP come from LAN or WAN. by [Yor] +//----------------------------------------------------- +int lan_ip_check(unsigned char *p){ + int i; + int lancheck = 1; + int subneti[4]; + unsigned int k0, k1, k2, k3; + + sscanf(lan_map_ip, "%d.%d.%d.%d", &k0, &k1, &k2, &k3); + subneti[0] = k0; subneti[1] = k1; subneti[2] = k2; subneti[3] = k3; + +// printf("lan_ip_check: to compare: %d.%d.%d.%d, network: %d.%d.%d.%d/%d.%d.%d.%d\n", +// p[0], p[1], p[2], p[3], +// subneti[0], subneti[1], subneti[2], subneti[3], +// subnetmaski[0], subnetmaski[1], subnetmaski[2], subnetmaski[3]); + for(i = 0; i < 4; i++) { + if ((subneti[i] & subnetmaski[i]) != (p[i] & subnetmaski[i])) { + lancheck = 0; + break; + } + } +// printf("LAN test (result): %s source\033[0m.\n", (lancheck) ? "\033[1;36mLAN" : "\033[1;32mWAN"); + return lancheck; +} + +int parse_char(int fd) { + int i, ch = 0; + char email[40]; + struct char_session_data *sd; + unsigned char *p = (unsigned char *) &session[fd]->client_addr.sin_addr; + + if(login_fd < 0 || session[fd]->eof) { + if (fd == login_fd) + login_fd = -1; + close(fd); + delete_session(fd); + return 0; + } + + sd = session[fd]->session_data; + + while(RFIFOREST(fd) >= 2) { +// if (RFIFOW(fd, 0) < 30000) +// printf("parse_char : %d %d %x\n", fd, RFIFOREST(fd), RFIFOW(fd, 0)); + + switch(RFIFOW(fd, 0)) { + case 0x20b: //20040622 encryption ragexe correspondence + if (RFIFOREST(fd) < 19) + return 0; + RFIFOSKIP(fd,19); + break; + + case 0x65: // request to connect + printf("request connect - account_id:%d/login_id1:%d/login_id2:%d\n", RFIFOL(fd, 2), RFIFOL(fd, 6), RFIFOL(fd, 10)); + if (RFIFOREST(fd) < 17) + return 0; + { +/*removed from isGM setup + if (isGM(RFIFOL(fd,2))) + printf("Account Logged On; Account ID: %d (GM level %d).\n", RFIFOL(fd,2), isGM(RFIFOL(fd,2))); + else + printf("Account Logged On; Account ID: %d.\n", RFIFOL(fd,2)); +*/ + if (sd == NULL) { + CREATE(session[fd]->session_data, struct char_session_data, 1); + sd = session[fd]->session_data; + sd->connect_until_time = 0; // unknow or illimited (not displaying on map-server) + } + sd->account_id = RFIFOL(fd, 2); + sd->login_id1 = RFIFOL(fd, 6); + sd->login_id2 = RFIFOL(fd, 10); + sd->sex = RFIFOB(fd, 16); + + WFIFOL(fd, 0) = RFIFOL(fd, 2); + WFIFOSET(fd, 4); + + for(i = 0; i < AUTH_FIFO_SIZE; i++) { + if (auth_fifo[i].account_id == sd->account_id && + auth_fifo[i].login_id1 == sd->login_id1 && +#if CMP_AUTHFIFO_LOGIN2 != 0 + auth_fifo[i].login_id2 == sd->login_id2 && // relate to the versions higher than 18 +#endif + (!check_ip_flag || auth_fifo[i].ip == session[fd]->client_addr.sin_addr.s_addr) && + auth_fifo[i].delflag == 2) { + auth_fifo[i].delflag = 1; + + if (max_connect_user == 0 || count_users() < max_connect_user) { + if (login_fd > 0) { // don't send request if no login-server + // request to login-server to obtain e-mail/time limit + WFIFOW(login_fd,0) = 0x2716; + WFIFOL(login_fd,2) = sd->account_id; + WFIFOSET(login_fd,6); + } + // send characters to player + mmo_char_send006b(fd, sd); + } else { + // refuse connection (over populated) + WFIFOW(fd,0) = 0x6c; + WFIFOW(fd,2) = 0; + WFIFOSET(fd,3); + } +// printf("connection request> set delflag 1(o:2)- account_id:%d/login_id1:%d(fifo_id:%d)\n", sd->account_id, sd->login_id1, i); + break; + } + } + if (i == AUTH_FIFO_SIZE) { + if (login_fd > 0) { // don't send request if no login-server + WFIFOW(login_fd,0) = 0x2712; // ask login-server to authentify an account + WFIFOL(login_fd,2) = sd->account_id; + WFIFOL(login_fd,6) = sd->login_id1; + WFIFOL(login_fd,10) = sd->login_id2; + WFIFOB(login_fd,14) = sd->sex; + WFIFOL(login_fd,15) = session[fd]->client_addr.sin_addr.s_addr; + WFIFOSET(login_fd,19); + } else { // if no login-server, we must refuse connection + WFIFOW(fd,0) = 0x6c; + WFIFOW(fd,2) = 0; + WFIFOSET(fd,3); + } + } + } + RFIFOSKIP(fd, 17); + break; + + case 0x66: // char select +// printf("0x66> request connect - account_id:%d/char_num:%d\n",sd->account_id,RFIFOB(fd, 2)); + if (RFIFOREST(fd) < 3) + return 0; + + sprintf(tmp_sql, "SELECT `char_id` FROM `%s` WHERE `account_id`='%d' AND `char_num`='%d'",char_db, sd->account_id, RFIFOB(fd, 2)); + if (mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + sql_res = mysql_store_result(&mysql_handle); + + sql_row = mysql_fetch_row(sql_res); + + if (sql_row) + mmo_char_fromsql(atoi(sql_row[0]), char_dat, 0); + else { + mysql_free_result(sql_res); + RFIFOSKIP(fd, 3); + break; + } + + sprintf(tmp_sql,"INSERT INTO `%s`(`time`, `account_id`,`char_num`,`name`) VALUES (NOW(), '%d', '%d', '%s')", + charlog_db, sd->account_id, RFIFOB(fd, 2), char_dat[0].name); + //query + if(mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + printf("(\033[1;64m%d\033[0m) char selected (\033[1;32m%d\033[0m) \033[1;32m%s\033[0m" RETCODE, sd->account_id, RFIFOB(fd, 2), char_dat[0].name); + + i = search_mapserver(char_dat[0].last_point.map); + + // if map is not found, we check major cities + if (i < 0) { + if ((i = search_mapserver("prontera.gat")) >= 0) { // check is done without 'gat'. + memcpy(char_dat[0].last_point.map, "prontera.gat", 16); + char_dat[0].last_point.x = 273; // savepoint coordonates + char_dat[0].last_point.y = 354; + } else if ((i = search_mapserver("geffen.gat")) >= 0) { // check is done without 'gat'. + memcpy(char_dat[0].last_point.map, "geffen.gat", 16); + char_dat[0].last_point.x = 120; // savepoint coordonates + char_dat[0].last_point.y = 100; + } else if ((i = search_mapserver("morocc.gat")) >= 0) { // check is done without 'gat'. + memcpy(char_dat[0].last_point.map, "morocc.gat", 16); + char_dat[0].last_point.x = 160; // savepoint coordonates + char_dat[0].last_point.y = 94; + } else if ((i = search_mapserver("alberta.gat")) >= 0) { // check is done without 'gat'. + memcpy(char_dat[0].last_point.map, "alberta.gat", 16); + char_dat[0].last_point.x = 116; // savepoint coordonates + char_dat[0].last_point.y = 57; + } else if ((i = search_mapserver("payon.gat")) >= 0) { // check is done without 'gat'. + memcpy(char_dat[0].last_point.map, "payon.gat", 16); + char_dat[0].last_point.x = 87; // savepoint coordonates + char_dat[0].last_point.y = 117; + } else if ((i = search_mapserver("izlude.gat")) >= 0) { // check is done without 'gat'. + memcpy(char_dat[0].last_point.map, "izlude.gat", 16); + char_dat[0].last_point.x = 94; // savepoint coordonates + char_dat[0].last_point.y = 103; + } else { + int j; + // get first online server + i = 0; + for(j = 0; j < MAX_MAP_SERVERS; j++) + if (server_fd[j] >= 0 && server[j].map[0][0]) { + i = j; + printf("Map-server #%d found with a map: '%s'.\n", j, server[j].map[0]); + break; + } + // if no map-servers are connected, we send: server closed + if (j == MAX_MAP_SERVERS) { + WFIFOW(fd,0) = 0x81; + WFIFOL(fd,2) = 1; // 01 = Server closed + WFIFOSET(fd,3); + RFIFOSKIP(fd,3); + break; + } + } + } + WFIFOW(fd, 0) =0x71; + WFIFOL(fd, 2) =char_dat[0].char_id; + memcpy(WFIFOP(fd, 6), char_dat[0].last_point.map, 16); + //Lan check added by Kashy + if (lan_ip_check(p)) + WFIFOL(fd, 22) = inet_addr(lan_map_ip); + else + WFIFOL(fd, 22) = server[i].ip; + WFIFOW(fd, 26) = server[i].port; + WFIFOSET(fd, 28); + + if (auth_fifo_pos >= AUTH_FIFO_SIZE) { + auth_fifo_pos = 0; + } +// printf("auth_fifo set (auth_fifo_pos:%d) - account_id:%d char_id:%d login_id1:%d\n", auth_fifo_pos, sd->account_id, char_dat[0].char_id, sd->login_id1); + auth_fifo[auth_fifo_pos].account_id = sd->account_id; + auth_fifo[auth_fifo_pos].char_id = char_dat[0].char_id; + auth_fifo[auth_fifo_pos].login_id1 = sd->login_id1; + auth_fifo[auth_fifo_pos].login_id2 = sd->login_id2; + auth_fifo[auth_fifo_pos].delflag = 0; + //auth_fifo[auth_fifo_pos].char_pos = sd->found_char[ch]; + auth_fifo[auth_fifo_pos].char_pos = 0; + auth_fifo[auth_fifo_pos].sex = sd->sex; + auth_fifo[auth_fifo_pos].connect_until_time = sd->connect_until_time; + auth_fifo[auth_fifo_pos].ip = session[fd]->client_addr.sin_addr.s_addr; + auth_fifo_pos++; +// printf("0x66> end\n"); + RFIFOSKIP(fd, 3); + break; + + case 0x67: // make new +// printf("0x67>request make new char\n"); + if (RFIFOREST(fd) < 37) + return 0; + i = make_new_char_sql(fd, RFIFOP(fd, 2)); + if (i < 0) { + WFIFOW(fd, 0) = 0x6e; + WFIFOB(fd, 2) = 0x00; + WFIFOSET(fd, 3); + RFIFOSKIP(fd, 37); + break; + } + + WFIFOW(fd, 0) = 0x6d; + memset(WFIFOP(fd, 2), 0x00, 106); + + mmo_char_fromsql(i, char_dat, 0); + i = 0; + WFIFOL(fd, 2) = char_dat[i].char_id; + WFIFOL(fd,2+4) = char_dat[i].base_exp; + WFIFOL(fd,2+8) = char_dat[i].zeny; + WFIFOL(fd,2+12) = char_dat[i].job_exp; + WFIFOL(fd,2+16) = char_dat[i].job_level; + + WFIFOL(fd,2+28) = char_dat[i].karma; + WFIFOL(fd,2+32) = char_dat[i].manner; + + WFIFOW(fd,2+40) = 0x30; + WFIFOW(fd,2+42) = (char_dat[i].hp > 0x7fff) ? 0x7fff : char_dat[i].hp; + WFIFOW(fd,2+44) = (char_dat[i].max_hp > 0x7fff) ? 0x7fff : char_dat[i].max_hp; + WFIFOW(fd,2+46) = (char_dat[i].sp > 0x7fff) ? 0x7fff : char_dat[i].sp; + WFIFOW(fd,2+48) = (char_dat[i].max_sp > 0x7fff) ? 0x7fff : char_dat[i].max_sp; + WFIFOW(fd,2+50) = DEFAULT_WALK_SPEED; // char_dat[i].speed; + WFIFOW(fd,2+52) = char_dat[i].class; + WFIFOW(fd,2+54) = char_dat[i].hair; + + WFIFOW(fd,2+58) = char_dat[i].base_level; + WFIFOW(fd,2+60) = char_dat[i].skill_point; + + WFIFOW(fd,2+64) = char_dat[i].shield; + WFIFOW(fd,2+66) = char_dat[i].head_top; + WFIFOW(fd,2+68) = char_dat[i].head_mid; + WFIFOW(fd,2+70) = char_dat[i].hair_color; + + memcpy(WFIFOP(fd,2+74), char_dat[i].name, 24); + + WFIFOB(fd,2+98) = char_dat[i].str; + WFIFOB(fd,2+99) = char_dat[i].agi; + WFIFOB(fd,2+100) = char_dat[i].vit; + WFIFOB(fd,2+101) = char_dat[i].int_; + WFIFOB(fd,2+102) = char_dat[i].dex; + WFIFOB(fd,2+103) = char_dat[i].luk; + WFIFOB(fd,2+104) = char_dat[i].char_num; + + WFIFOSET(fd, 108); + RFIFOSKIP(fd, 37); + //to do + for(ch = 0; ch < 9; ch++) { + if (sd->found_char[ch] == -1) { + sd->found_char[ch] = char_dat[i].char_id; + break; + } + } + + case 0x68: // delete + if (RFIFOREST(fd) < 46) + return 0; + printf("\033[1;31m Request Char Del:\033[0m \033[1;32m%d\033[0m(\033[1;32m%d\033[0m)\n", sd->account_id, RFIFOL(fd, 2)); + memcpy(email, RFIFOP(fd,6), 40); + sprintf(tmp_sql, "SELECT `email` FROM `%s` WHERE `%s`='%d'",login_db, login_db_account_id, sd->account_id); + if (mysql_query(&lmysql_handle, tmp_sql)) { + printf("\033[1;31m DB server Error Delete Char data - %s \033[0m \n", mysql_error(&lmysql_handle)); + } + sql_res = mysql_store_result(&lmysql_handle); + if (sql_res) { + sql_row = mysql_fetch_row(sql_res); + if (strcmp(email,sql_row[0]) == 0) { + mysql_free_result(sql_res); + } else { + WFIFOW(fd, 0) = 0x70; + WFIFOB(fd, 2) = 0; + WFIFOSET(fd, 3); + RFIFOSKIP(fd, 46); + mysql_free_result(sql_res); + break; + } + } else { + WFIFOW(fd, 0) = 0x70; + WFIFOB(fd, 2) = 0; + WFIFOSET(fd, 3); + RFIFOSKIP(fd, 46); + mysql_free_result(sql_res); + break; + } + sprintf(tmp_sql, "SELECT `name`,`partner_id` FROM `%s` WHERE `char_id`='%d'",char_db, RFIFOL(fd,2)); + if (mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + sql_res = mysql_store_result(&mysql_handle); + if(sql_res) + sql_row = mysql_fetch_row(sql_res); + + if (sql_res && sql_row[0]) { + //delete char from SQL + sprintf(tmp_sql,"DELETE FROM `%s` WHERE `char_id`='%d'",pet_db, RFIFOL(fd, 2)); + if(mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + sprintf(tmp_sql,"DELETE FROM `%s` WHERE `char_id`='%d'",inventory_db, RFIFOL(fd, 2)); + if(mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + + sprintf(tmp_sql,"DELETE FROM `%s` WHERE `char_id`='%d'",cart_db, RFIFOL(fd, 2)); + if(mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + + sprintf(tmp_sql,"DELETE FROM `%s` WHERE `char_id`='%d'",memo_db, RFIFOL(fd, 2)); + if(mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + + sprintf(tmp_sql,"DELETE FROM `%s` WHERE `char_id`='%d'",skill_db, RFIFOL(fd, 2)); + if(mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + + sprintf(tmp_sql,"DELETE FROM `%s` WHERE `char_id`='%d'",char_db, RFIFOL(fd, 2)); + if(mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + + //Divorce [Wizputer] + if (sql_row[1] != 0) { + char buf[64]; + sprintf(tmp_sql,"UPDATE `%s` SET `partner_id`='0' WHERE `char_id`='%d'",char_db,atoi(sql_row[1])); + if(mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + sprintf(tmp_sql,"DELETE FROM `%s` WHERE (`nameid`='%d' OR `nameid`='%d') AND `char_id`='%d'",inventory_db,WEDDING_RING_M,WEDDING_RING_F,atoi(sql_row[1])); + if(mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + WBUFW(buf,0) = 0x2b12; + WBUFL(buf,2) = atoi(sql_row[0]); + WBUFL(buf,6) = atoi(sql_row[1]); + mapif_sendall(buf,10); + } + // Also delete info from guildtables. + sprintf(tmp_sql,"DELETE FROM `%s` WHERE `char_id`='%d'",guild_member_db, RFIFOL(fd,2)); + if (mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + + sprintf(tmp_sql, "SELECT `guild_id` FROM `%s` WHERE `master` = '%s'", guild_db, sql_row[0]); + + if (mysql_query(&mysql_handle, tmp_sql) == 0) { + sql_res = mysql_store_result(&mysql_handle); + + if (sql_res != NULL) { + if (mysql_num_rows(sql_res) != 0) { + sql_row = mysql_fetch_row(sql_res); + + sprintf(tmp_sql,"DELETE FROM `%s` WHERE `guild_id` = '%d'", guild_db, atoi(sql_row[0])); + if (mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + + sprintf(tmp_sql,"DELETE FROM `%s` WHERE `guild_id` = '%d'", guild_member_db, atoi(sql_row[0])); + if (mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + + sprintf(tmp_sql,"DELETE FROM `%s` WHERE `guild_id` = '%d'", guild_castle_db, atoi(sql_row[0])); + if (mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + + sprintf(tmp_sql,"DELETE FROM `%s` WHERE `guild_id` = '%d'", guild_storage_db, atoi(sql_row[0])); + if (mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + + sprintf(tmp_sql,"DELETE FROM `%s` WHERE `guild_id` = '%d' OR `alliance_id` = '%d'", guild_alliance_db, atoi(sql_row[0]), atoi(sql_row[0])); + if (mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + + sprintf(tmp_sql,"DELETE FROM `%s` WHERE `guild_id` = '%d'", guild_position_db, atoi(sql_row[0])); + if (mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + + sprintf(tmp_sql,"DELETE FROM `%s` WHERE `guild_id` = '%d'", guild_skill_db, atoi(sql_row[0])); + if (mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + + sprintf(tmp_sql,"DELETE FROM `%s` WHERE `guild_id` = '%d'", guild_expulsion_db, atoi(sql_row[0])); + if (mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + + mysql_free_result(sql_res); + } + } else { + if (mysql_errno(&mysql_handle) != 0) { + printf("Database server error: %s\n", mysql_error(&mysql_handle)); + } + } + } else { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + } + + for(i = 0; i < 9; i++) { + printf("char comp: %d - %d (%d)\n", sd->found_char[i], RFIFOL(fd, 2), sd->account_id); + if (sd->found_char[i] == RFIFOL(fd, 2)) { + for(ch = i; ch < 9-1; ch++) + sd->found_char[ch] = sd->found_char[ch+1]; + sd->found_char[8] = -1; + break; + } + } + if (i == 9) { // reject + WFIFOW(fd, 0) = 0x70; + WFIFOB(fd, 2) = 0; + WFIFOSET(fd, 3); + } else { // deleted! + WFIFOW(fd, 0) = 0x6f; + WFIFOSET(fd, 2); + } + RFIFOSKIP(fd, 46); + break; + + case 0x2af8: // login as map-server + if (RFIFOREST(fd) < 60) + return 0; + WFIFOW(fd, 0) = 0x2af9; + for(i = 0; i < MAX_MAP_SERVERS; i++) { + if (server_fd[i] < 0) + break; + } + if (i == MAX_MAP_SERVERS || strcmp(RFIFOP(fd,2), userid) || strcmp(RFIFOP(fd,26), passwd)) { + WFIFOB(fd,2) = 3; + WFIFOSET(fd, 3); + } else { +// int len; + WFIFOB(fd,2) = 0; + WFIFOSET(fd, 3); + session[fd]->func_parse = parse_frommap; + server_fd[i] = fd; + if(anti_freeze_enable) + server_freezeflag[i] = 5; // Map anti-freeze system. Counter. 5 ok, 4...0 freezed + server[i].ip = RFIFOL(fd, 54); + server[i].port = RFIFOW(fd, 58); + server[i].users = 0; + memset(server[i].map, 0, sizeof(server[i].map)); + realloc_fifo(fd, FIFOSIZE_SERVERLINK, FIFOSIZE_SERVERLINK); + char_mapif_init(fd); + // send gm acccounts level to map-servers +/* removed by CLOWNISIUS due to isGM + len = 4; + WFIFOW(fd,0) = 0x2b15; + for(i = 0; i < GM_num; i++) { + WFIFOL(fd,len) = gm_account[i].account_id; + WFIFOB(fd,len+4) = (unsigned char)gm_account[i].level; + len += 5; + } + WFIFOW(fd,2) = len; + WFIFOSET(fd,len);*/ + } + RFIFOSKIP(fd,60); + break; + + case 0x187: // Alive? + if (RFIFOREST(fd) < 6) { + return 0; + } + RFIFOSKIP(fd, 6); + break; + + case 0x7530: // Athena info get + WFIFOW(fd, 0) = 0x7531; + WFIFOB(fd, 2) = ATHENA_MAJOR_VERSION; + WFIFOB(fd, 3) = ATHENA_MINOR_VERSION; + WFIFOB(fd, 4) = ATHENA_REVISION; + WFIFOB(fd, 5) = ATHENA_RELEASE_FLAG; + WFIFOB(fd, 6) = ATHENA_OFFICIAL_FLAG; + WFIFOB(fd, 7) = ATHENA_SERVER_INTER | ATHENA_SERVER_CHAR; + WFIFOW(fd, 8) = ATHENA_MOD_VERSION; + WFIFOSET(fd, 10); + RFIFOSKIP(fd, 2); + return 0; + + case 0x7532: // disconnect(default also disconnect) + default: + session[fd]->eof = 1; + return 0; + } + } + RFIFOFLUSH(fd); + + return 0; +} + +// MAP send all +int mapif_sendall(unsigned char *buf, unsigned int len) { + int i, c; + int fd; + + c = 0; + for(i = 0; i < MAX_MAP_SERVERS; i++) { + if ((fd = server_fd[i]) >= 0) { + memcpy(WFIFOP(fd,0), buf, len); + WFIFOSET(fd,len); + c++; + } + } + + return c; +} + +int mapif_sendallwos(int sfd, unsigned char *buf, unsigned int len) { + int i, c; + int fd; + + c = 0; + for(i=0, c=0;i<MAX_MAP_SERVERS;i++){ + if ((fd = server_fd[i]) >= 0 && fd != sfd) { + memcpy(WFIFOP(fd,0), buf, len); + WFIFOSET(fd, len); + c++; + } + } + + return c; +} + +int mapif_send(int fd, unsigned char *buf, unsigned int len) { + int i; + + if (fd >= 0) { + for(i = 0; i < MAX_MAP_SERVERS; i++) { + if (fd == server_fd[i]) { + memcpy(WFIFOP(fd,0), buf, len); + WFIFOSET(fd,len); + return 1; + } + } + } + return 0; +} + +int send_users_tologin(int tid, unsigned int tick, int id, int data) { + int users = count_users(); + char buf[16]; + + if (login_fd > 0 && session[login_fd]) { + // send number of user to login server + WFIFOW(login_fd,0) = 0x2714; + WFIFOL(login_fd,2) = users; + WFIFOSET(login_fd,6); + } + // send number of players to all map-servers + WBUFW(buf,0) = 0x2b00; + WBUFL(buf,2) = users; + mapif_sendall(buf, 6); + + return 0; +} + +int check_connect_login_server(int tid, unsigned int tick, int id, int data) { + if (login_fd <= 0 || session[login_fd] == NULL) { + printf("Attempt to connect to login-server...\n"); + login_fd = make_connection(login_ip, login_port); + session[login_fd]->func_parse = parse_tologin; + realloc_fifo(login_fd, FIFOSIZE_SERVERLINK, FIFOSIZE_SERVERLINK); + WFIFOW(login_fd,0) = 0x2710; + memset(WFIFOP(login_fd,2), 0, 24); + memcpy(WFIFOP(login_fd,2), userid, strlen(userid) < 24 ? strlen(userid) : 24); + memset(WFIFOP(login_fd,26), 0, 24); + memcpy(WFIFOP(login_fd,26), passwd, strlen(passwd) < 24 ? strlen(passwd) : 24); + WFIFOL(login_fd,50) = 0; + WFIFOL(login_fd,54) = char_ip; + WFIFOL(login_fd,58) = char_port; + memset(WFIFOP(login_fd,60), 0, 20); + memcpy(WFIFOP(login_fd,60), server_name, strlen(server_name) < 20 ? strlen(server_name) : 20); + WFIFOW(login_fd,80) = 0; + WFIFOW(login_fd,82) = char_maintenance; + WFIFOW(login_fd,84) = char_new; + WFIFOSET(login_fd,86); + } + return 0; +} + +//---------------------------------------------------------- +// Return numerical value of a switch configuration by [Yor] +// on/off, english, fran軋is, deutsch, espaol +//---------------------------------------------------------- +int config_switch(const char *str) { + if (strcmpi(str, "on") == 0 || strcmpi(str, "yes") == 0 || strcmpi(str, "oui") == 0 || strcmpi(str, "ja") == 0 || strcmpi(str, "si") == 0) + return 1; + if (strcmpi(str, "off") == 0 || strcmpi(str, "no") == 0 || strcmpi(str, "non") == 0 || strcmpi(str, "nein") == 0) + return 0; + + return atoi(str); +} + +// Lan Support conf reading added by Kashy +int char_lan_config_read(const char *lancfgName){ + char subnetmask[128]; + char line[1024], w1[1024], w2[1024]; + FILE *fp; + struct hostent * h = NULL; + + if ((fp = fopen(lancfgName, "r")) == NULL) { + printf("file not found: %s\n", lancfgName); + return 1; + } + + printf("Start reading of Lan Support configuration file\n"); + + while(fgets(line, sizeof(line)-1, fp)){ + if (line[0] == '/' && line[1] == '/') + continue; + + if (sscanf(line, "%[^:]: %[^\r\n]", w1, w2) != 2) + continue; + + else if (strcmpi(w1, "lan_map_ip") == 0) { + h = gethostbyname(w2); + if (h != NULL) { + sprintf(lan_map_ip, "%d.%d.%d.%d", (unsigned char)h->h_addr[0], (unsigned char)h->h_addr[1], (unsigned char)h->h_addr[2], (unsigned char)h->h_addr[3]); + } else { + strncpy(lan_map_ip, w2, sizeof(lan_map_ip)); + lan_map_ip[sizeof(lan_map_ip)-1] = 0; + } + printf("set Lan_map_IP : %s\n", lan_map_ip); + } + + else if (strcmpi(w1, "subnetmask") == 0) { + unsigned int k0, k1, k2, k3; + strcpy(subnetmask, w2); + sscanf(subnetmask, "%d.%d.%d.%d", &k0, &k1, &k2, &k3); + subnetmaski[0] = k0; subnetmaski[1] = k1; subnetmaski[2] = k2; subnetmaski[3] = k3; + printf("set subnetmask : %s\n", w2); + } + } + fclose(fp); + + printf("End reading of Lan Support configuration file\n"); + return 0; +} + +void do_final(void) { + printf("Doing final stage...\n"); + //mmo_char_sync(); + //inter_save(); + do_final_itemdb(); + //check SQL save progress. + //wait until save char complete + printf("waiting until char saving complete...\n"); + do { + sleep (0); + }while (save_flag != 0); + + sprintf(tmp_sql,"UPDATE `%s` SET `online`='0' WHERE `online`='1'", char_db); + if (mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error (insert `char`)- %s\n", mysql_error(&mysql_handle)); + } + + sprintf(tmp_sql,"DELETE FROM `ragsrvinfo"); + if (mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error (insert `char`)- %s\n", mysql_error(&mysql_handle)); + } + + if (gm_account != NULL) + free(gm_account); + + free(char_dat); + free(gm_account); + delete_session(login_fd); + delete_session(char_fd); + + mysql_close(&mysql_handle); + mysql_close(&lmysql_handle); + + printf("ok! all done...\n"); +} + +void sql_config_read(const char *cfgName){ /* Kalaspuff, to get login_db */ + char line[1024], w1[1024], w2[1024]; + FILE *fp; + + printf("reading configure: %s\n", cfgName); + + if ((fp = fopen(cfgName, "r")) == NULL) { + printf("file not found: %s\n", cfgName); + exit(1); + } + + while(fgets(line, sizeof(line)-1, fp)){ + if(line[0] == '/' && line[1] == '/') + continue; + + if (sscanf(line, "%[^:]: %[^\r\n]", w1, w2) != 2) + continue; + + if(strcmpi(w1, "login_db") == 0) { + strcpy(login_db, w2); + }else if(strcmpi(w1,"char_db")==0){ + strcpy(char_db,w2); + }else if(strcmpi(w1,"cart_db")==0){ + strcpy(cart_db,w2); + }else if(strcmpi(w1,"inventory_db")==0){ + strcpy(inventory_db,w2); + }else if(strcmpi(w1,"charlog_db")==0){ + strcpy(charlog_db,w2); + }else if(strcmpi(w1,"storage_db")==0){ + strcpy(storage_db,w2); + }else if(strcmpi(w1,"reg_db")==0){ + strcpy(reg_db,w2); + }else if(strcmpi(w1,"skill_db")==0){ + strcpy(skill_db,w2); + }else if(strcmpi(w1,"interlog_db")==0){ + strcpy(interlog_db,w2); + }else if(strcmpi(w1,"memo_db")==0){ + strcpy(memo_db,w2); + }else if(strcmpi(w1,"guild_db")==0){ + strcpy(guild_db,w2); + }else if(strcmpi(w1,"guild_alliance_db")==0){ + strcpy(guild_alliance_db,w2); + }else if(strcmpi(w1,"guild_castle_db")==0){ + strcpy(guild_castle_db,w2); + }else if(strcmpi(w1,"guild_expulsion_db")==0){ + strcpy(guild_expulsion_db,w2); + }else if(strcmpi(w1,"guild_member_db")==0){ + strcpy(guild_member_db,w2); + }else if(strcmpi(w1,"guild_skill_db")==0){ + strcpy(guild_skill_db,w2); + }else if(strcmpi(w1,"guild_position_db")==0){ + strcpy(guild_position_db,w2); + }else if(strcmpi(w1,"guild_storage_db")==0){ + strcpy(guild_storage_db,w2); + }else if(strcmpi(w1,"party_db")==0){ + strcpy(party_db,w2); + }else if(strcmpi(w1,"pet_db")==0){ + strcpy(pet_db,w2); + //Map server option to use SQL db or not + }else if(strcmpi(w1,"use_sql_db")==0){ // added for sql item_db read for char server [Valaris] + if (strcmpi(w2, "yes")) { + db_use_sqldbs = 1; + } else if (strcmpi(w2, "no")) { + db_use_sqldbs = 0; + } + printf("Using SQL dbs: %s\n",w2); + //custom columns for login database + }else if(strcmpi(w1,"login_db_level")==0){ + strcpy(login_db_level,w2); + }else if(strcmpi(w1,"login_db_account_id")==0){ + strcpy(login_db_account_id,w2); + }else if(strcmpi(w1,"lowest_gm_level")==0){ + lowest_gm_level = atoi(w2); + printf("set lowest_gm_level : %s\n",w2); + } + } + fclose(fp); + printf("reading configure done.....\n"); +} + +int char_config_read(const char *cfgName) { + struct hostent *h = NULL; + char line[1024], w1[1024], w2[1024]; + FILE *fp; + + if ((fp = fopen(cfgName, "r")) == NULL) { + printf("Configuration file not found: %s.\n", cfgName); + exit(1); + } + + while(fgets(line, sizeof(line)-1, fp)) { + if (line[0] == '/' && line[1] == '/') + continue; + + line[sizeof(line)-1] = '\0'; + if (sscanf(line,"%[^:]: %[^\r\n]", w1, w2) != 2) + continue; + + remove_control_chars(w1); + remove_control_chars(w2); + if (strcmpi(w1, "userid") == 0) { + memcpy(userid, w2, 24); + } else if (strcmpi(w1, "passwd") == 0) { + memcpy(passwd, w2, 24); + } else if (strcmpi(w1, "server_name") == 0) { + memcpy(server_name, w2, 16); + printf("%s server has been intialized\n", w2); + } else if (strcmpi(w1, "wisp_server_name") == 0) { + if (strlen(w2) >= 4) { + memcpy(wisp_server_name, w2, sizeof(wisp_server_name)); + wisp_server_name[sizeof(wisp_server_name) - 1] = '\0'; + } + } else if (strcmpi(w1, "login_ip") == 0) { + h = gethostbyname (w2); + if (h != NULL) { + printf("Login server IP address : %s -> %d.%d.%d.%d\n", w2, (unsigned char)h->h_addr[0], (unsigned char)h->h_addr[1], (unsigned char)h->h_addr[2], (unsigned char)h->h_addr[3]); + sprintf(login_ip_str, "%d.%d.%d.%d", (unsigned char)h->h_addr[0], (unsigned char)h->h_addr[1], (unsigned char)h->h_addr[2], (unsigned char)h->h_addr[3]); + } else + memcpy(login_ip_str,w2,16); + } else if (strcmpi(w1, "login_port") == 0) { + login_port=atoi(w2); + } else if (strcmpi(w1, "char_ip") == 0) { + h = gethostbyname (w2); + if(h != NULL) { + printf("Character server IP address : %s -> %d.%d.%d.%d\n", w2, (unsigned char)h->h_addr[0], (unsigned char)h->h_addr[1], (unsigned char)h->h_addr[2], (unsigned char)h->h_addr[3]); + sprintf(char_ip_str, "%d.%d.%d.%d", (unsigned char)h->h_addr[0], (unsigned char)h->h_addr[1], (unsigned char)h->h_addr[2], (unsigned char)h->h_addr[3]); + } else + memcpy(char_ip_str, w2, 16); + } else if (strcmpi(w1, "char_port") == 0) { + char_port = atoi(w2); + } else if (strcmpi(w1, "char_maintenance") == 0) { + char_maintenance = atoi(w2); + } else if (strcmpi(w1, "char_new")==0){ + char_new = atoi(w2); + } else if (strcmpi(w1, "max_connect_user") == 0) { + max_connect_user = atoi(w2); + if (max_connect_user < 0) + max_connect_user = 0; // unlimited online players + } else if (strcmpi(w1, "check_ip_flag") == 0) { + check_ip_flag = config_switch(w2); + } else if (strcmpi(w1, "autosave_time") == 0) { + autosave_interval = atoi(w2)*1000; + if (autosave_interval <= 0) + autosave_interval = DEFAULT_AUTOSAVE_INTERVAL; + } else if (strcmpi(w1, "start_point") == 0) { + char map[32]; + int x, y; + if (sscanf(w2,"%[^,],%d,%d", map, &x, &y) < 3) + continue; + if (strstr(map, ".gat") != NULL) { // Verify at least if '.gat' is in the map name + memcpy(start_point.map, map, 16); + start_point.x = x; + start_point.y = y; + } + } else if (strcmpi(w1, "start_zeny") == 0) { + start_zeny = atoi(w2); + if (start_zeny < 0) + start_zeny = 0; + } else if (strcmpi(w1, "start_weapon") == 0) { + start_zeny = atoi(w2); + if (start_weapon < 0) + start_weapon = 0; + } else if (strcmpi(w1, "start_armor") == 0) { + start_zeny = atoi(w2); + if (start_armor < 0) + start_armor = 0; + } else if (strcmpi(w1, "unknown_char_name") == 0) { + strcpy(unknown_char_name, w2); + unknown_char_name[24] = 0; + } else if (strcmpi(w1, "name_ignoring_case") == 0) { + name_ignoring_case = config_switch(w2); + } else if (strcmpi(w1, "char_name_option") == 0) { + char_name_option = atoi(w2); + } else if (strcmpi(w1, "char_name_letters") == 0) { + strcpy(char_name_letters, w2); + } else if (strcmpi(w1, "check_ip_flag") == 0) { + check_ip_flag = config_switch(w2); + // anti-freeze options [Valaris] + } else if(strcmpi(w1,"anti_freeze_enable")==0){ + anti_freeze_enable = config_switch(w2); + } else if (strcmpi(w1, "anti_freeze_interval") == 0) { + ANTI_FREEZE_INTERVAL = atoi(w2); + if (ANTI_FREEZE_INTERVAL < 5) + ANTI_FREEZE_INTERVAL = 5; // minimum 5 seconds + } else if (strcmpi(w1, "import") == 0) { + char_config_read(w2); + } + } + fclose(fp); + +//Read ItemDB + do_init_itemdb(); + + return 0; +} + +int do_init(int argc, char **argv){ + int i; + + for(i = 0; i < MAX_MAP_SERVERS; i++) { + memset(&server[i], 0, sizeof(struct mmo_map_server)); + server_fd[i] = -1; + } + + char_config_read((argc < 2) ? CHAR_CONF_NAME : argv[1]); + char_lan_config_read((argc > 1) ? argv[1] : LAN_CONF_NAME); + sql_config_read(SQL_CONF_NAME); + + printf("charserver configuration reading done.....\n"); + + inter_init((argc > 2) ? argv[2] : inter_cfgName); // inter server テハア篳ュ + printf("interserver configuration reading done.....\n"); + + printf("start char server initializing.....\n"); + mmo_char_sql_init(); + printf("char server initializing done.....\n"); + + printf("set parser -> parse_char().....\n"); + set_defaultparse(parse_char); + + printf("set terminate function -> do_final().....\n"); + set_termfunc(do_final); + + printf("open port %d.....\n",char_port); + char_fd = make_listen_port(char_port); + + login_ip = inet_addr(login_ip_str); + char_ip = inet_addr(char_ip_str); + + // send ALIVE PING to login server. + printf("add interval tic (check_connect_login_server)....\n"); + i = add_timer_interval(gettick() + 10, check_connect_login_server, 0, 0, 10 * 1000); + + // send USER COUNT PING to login server. + printf("add interval tic (send_users_tologin)....\n"); + i = add_timer_interval(gettick() + 10, send_users_tologin, 0, 0, 5 * 1000); + + //no need to set sync timer on SQL version. + //printf("add interval tic (mmo_char_sync_timer)....\n"); + //i = add_timer_interval(gettick() + 10, mmo_char_sync_timer, 0, 0, autosave_interval); + + if(anti_freeze_enable > 0) { + add_timer_func_list(map_anti_freeze_system, "map_anti_freeze_system"); + i = add_timer_interval(gettick() + 1000, map_anti_freeze_system, 0, 0, ANTI_FREEZE_INTERVAL * 1000); // checks every X seconds user specifies + } + + read_gm_account(); + + printf("char server init func end (now unlimited loop start!)....\n"); + printf("The char-server is \033[1;32mready\033[0m (Server is listening on the port %d).\n\n", char_port); + return 0; +} + + diff --git a/misc/src/char_sql/char.h b/misc/src/char_sql/char.h new file mode 100644 index 0000000..116a301 --- /dev/null +++ b/misc/src/char_sql/char.h @@ -0,0 +1,82 @@ +#include "../common/core.h" +#include "../common/socket.h" +#include "../common/timer.h" +#include "../common/mmo.h" +#include "../common/version.h" +#include "../common/db.h" + +#ifndef _CHAR_H_ +#define _CHAR_H_ + +#define MAX_MAP_SERVERS 30 + +#define LAN_CONF_NAME "conf/lan_support.conf" + +#define DEFAULT_AUTOSAVE_INTERVAL 300*1000 + +struct mmo_map_server{ + long ip; + short port; + int users; + char map[MAX_MAP_PER_SERVER][16]; +}; +struct itemtmp { + int flag;//checked = 1 else 0 + int id; + short nameid; + short amount; + unsigned short equip; + char identify; + char refine; + char attribute; + short card[4]; + short broken; +}; +enum { + TABLE_INVENTORY, + TABLE_CART, + TABLE_STORAGE, + TABLE_GUILD_STORAGE, +}; +struct itemtemp{ + struct itemtmp equip[MAX_GUILD_STORAGE],notequip[MAX_GUILD_STORAGE]; +}; +int memitemdata_to_sql(struct itemtemp mapitem, int eqcount, int noteqcount, int char_id,int tableswitch); +int mapif_sendall(unsigned char *buf,unsigned int len); +int mapif_sendallwos(int fd,unsigned char *buf,unsigned int len); +int mapif_send(int fd,unsigned char *buf,unsigned int len); + +extern int autosave_interval; +extern char char_db[256]; +extern char cart_db[256]; +extern char inventory_db[256]; +extern char charlog_db[256]; +extern char storage_db[256]; +extern char interlog_db[256]; +extern char reg_db[256]; +extern char skill_db[256]; +extern char memo_db[256]; +extern char guild_db[256]; +extern char guild_alliance_db[256]; +extern char guild_castle_db[256]; +extern char guild_expulsion_db[256]; +extern char guild_member_db[256]; +extern char guild_position_db[256]; +extern char guild_skill_db[256]; +extern char guild_storage_db[256]; +extern char party_db[256]; +extern char pet_db[256]; + +int db_use_sqldbs; // added for sql item_db read for char server [Valaris] +extern char login_db_level[32]; +extern char login_db_account_id[32]; + +extern int lowest_gm_level; + +#endif + +#include "inter.h" +#include "int_pet.h" +#include "int_guild.h" +#include "int_party.h" +#include "int_storage.h" diff --git a/misc/src/char_sql/int_guild.c b/misc/src/char_sql/int_guild.c new file mode 100644 index 0000000..1983896 --- /dev/null +++ b/misc/src/char_sql/int_guild.c @@ -0,0 +1,1604 @@ +// +// original code from athena +// SQL conversion by hack +// + +#include "char.h" +#include "strlib.h" +#include "int_storage.h" +#include "inter.h" +#include "int_guild.h" +#include "int_storage.h" +#include "mmo.h" +#include "socket.h" + +#include <string.h> +#include <stdio.h> +#include <stdlib.h> + +static struct guild *guild_pt; +static struct guild *guild_pt2; +static struct guild_castle * guildcastle_pt; +static int guild_newid=10000; + +static int guild_exp[100]; + +int mapif_parse_GuildLeave(int fd,int guild_id,int account_id,int char_id,int flag,const char *mes); +int mapif_guild_broken(int guild_id,int flag); +int guild_check_empty(struct guild *g); +int guild_calcinfo(struct guild *g); +int mapif_guild_basicinfochanged(int guild_id,int type,const void *data,int len); +int mapif_guild_info(int fd,struct guild *g); +int guild_break_sub(void *key,void *data,va_list ap); + + +// Save guild into sql +int inter_guild_tosql(struct guild *g,int flag) +{ + // 1 `guild` (`guild_id`, `name`,`master`,`guild_lv`,`connect_member`,`max_member`,`average_lv`,`exp`,`next_exp`,`skill_point`,`castle_id`,`mes1`,`mes2`,`emblem_len`,`emblem_id`,`emblem_data`) + // 2 `guild_member` (`guild_id`,`account_id`,`char_id`,`hair`,`hair_color`,`gender`,`class`,`lv`,`exp`,`exp_payper`,`online`,`position`,`rsv1`,`rsv2`,`name`) + // 4 `guild_position` (`guild_id`,`position`,`name`,`mode`,`exp_mode`) + // 8 `guild_alliance` (`guild_id`,`opposition`,`alliance_id`,`name`) + // 16 `guild_expulsion` (`guild_id`,`name`,`mes`,`acc`,`account_id`,`rsv1`,`rsv2`,`rsv3`) + // 32 `guild_skill` (`guild_id`,`id`,`lv`) + + char t_name[100],t_master[24],t_mes1[60],t_mes2[120],t_member[24],t_position[24],t_alliance[24]; // temporay storage for str convertion; + char t_ename[24],t_emes[40]; + char emblem_data[4096]; + int i=0; + int guild_exist=0,guild_member=0,guild_online_member=0; + + if (g->guild_id<=0) return -1; + + printf("(\033[1;35m%d\033[0m) Request save guild - ",g->guild_id); + + jstrescapecpy(t_name, g->name); + + //printf("- Check if guild %d exists\n",g->guild_id); + sprintf(tmp_sql, "SELECT count(*) FROM `%s` WHERE `guild_id`='%d'",guild_db,g->guild_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (delete `guild`)- %s\n", mysql_error(&mysql_handle) ); + } + sql_res = mysql_store_result(&mysql_handle) ; + if (sql_res!=NULL && mysql_num_rows(sql_res)>0) { + sql_row = mysql_fetch_row(sql_res); + guild_exist = atoi (sql_row[0]); + //printf("- Check if guild %d exists : %s\n",g->guild_id,((guild_exist==0)?"No":"Yes")); + } + mysql_free_result(sql_res) ; //resource free + + if (guild_exist >0){ + // Check members in party + sprintf(tmp_sql,"SELECT count(*) FROM `%s` WHERE `guild_id`='%d'",guild_member_db, g->guild_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle) ); + return -1; + } + sql_res = mysql_store_result(&mysql_handle) ; + if (sql_res!=NULL && mysql_num_rows(sql_res)>0) { + sql_row = mysql_fetch_row(sql_res); + + guild_member = atoi (sql_row[0]); + // printf("- Check members in guild %d : %d \n",g->guild_id,guild_member); + + } + mysql_free_result(sql_res) ; //resource free + + // Delete old guild from sql + if (flag&1||guild_member==0){ + // printf("- Delete guild %d from guild\n",g->guild_id); + sprintf(tmp_sql, "DELETE FROM `%s` WHERE `guild_id`='%d'",guild_db, g->guild_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (delete `guild`)- %s\n", mysql_error(&mysql_handle) ); + } + } + if (flag&2||guild_member==0){ + // printf("- Delete guild %d from guild_member\n",g->guild_id); + sprintf(tmp_sql, "DELETE FROM `%s` WHERE `guild_id`='%d'",guild_member_db, g->guild_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (delete `guild_member`)- %s\n", mysql_error(&mysql_handle) ); + } + sprintf(tmp_sql, "UPDATE `%s` SET `guild_id`='0' WHERE `guild_id`='%d'",char_db, g->guild_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (update `char`)- %s\n", mysql_error(&mysql_handle) ); + } + } + if (flag&32||guild_member==0){ + // printf("- Delete guild %d from guild_skill\n",g->guild_id); + sprintf(tmp_sql, "DELETE FROM `%s` WHERE `guild_id`='%d'",guild_skill_db, g->guild_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (delete `guild_skill`)- %s\n", mysql_error(&mysql_handle) ); + } + } + if (flag&4||guild_member==0){ + // printf("- Delete guild %d from guild_position\n",g->guild_id); + sprintf(tmp_sql, "DELETE FROM `%s` WHERE `guild_id`='%d'",guild_position_db, g->guild_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (delete `guild_position`)- %s\n", mysql_error(&mysql_handle) ); + } + } + if (flag&16||guild_member==0){ + // printf("- Delete guild %d from guild_expulsion\n",g->guild_id); + sprintf(tmp_sql, "DELETE FROM `%s` WHERE `guild_id`='%d'",guild_expulsion_db, g->guild_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (delete `guild_expulsion`)- %s\n", mysql_error(&mysql_handle) ); + } + } + if (flag&8||guild_member==0){ + // printf("- Delete guild %d from guild_alliance\n",g->guild_id); + sprintf(tmp_sql, "DELETE FROM `%s` WHERE `guild_id`='%d' OR `alliance_id`='%d'",guild_alliance_db, g->guild_id,g->guild_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (delete `guild_alliance`)- %s\n", mysql_error(&mysql_handle) ); + } + } + if (flag&2||guild_member==0){ + // printf("- Delete guild %d from char\n",g->guild_id); + sprintf(tmp_sql, "UPDATE `%s` SET `guild_id`='0' WHERE `guild_id`='%d'",char_db, g->guild_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (delete `guild_alliance`)- %s\n", mysql_error(&mysql_handle) ); + } + } + if (guild_member==0){ + // printf("- Delete guild %d from guild_castle\n",g->guild_id); + sprintf(tmp_sql, "DELETE FROM `%s` WHERE `guild_id`='%d'",guild_castle_db, g->guild_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (delete `guild_castle`)- %s\n", mysql_error(&mysql_handle) ); + } + } + } + + guild_online_member = 0; + i=0; + while (i<g->max_member) { + if (g->member[i].account_id>0) guild_online_member++; + i++; + } + + // No member in guild , no need to create it in sql + if (guild_member <= 0 && guild_online_member <=0) { + inter_guild_storage_delete(g->guild_id); + printf("No member in guild %d , break it! \n",g->guild_id); + return -2; + } + + // Insert new guild to sqlserver + if (flag&1||guild_member==0){ + int len=0; + //printf("- Insert guild %d to guild\n",g->guild_id); + for(i=0;i<g->emblem_len;i++){ + len+=sprintf(emblem_data+len,"%02x",(unsigned char)(g->emblem_data[i])); + //printf("%02x",(unsigned char)(g->emblem_data[i])); + } + emblem_data[len] = '\0'; + //printf("- emblem_len = %d \n",g->emblem_len); + sprintf(tmp_sql,"INSERT INTO `%s` " + "(`guild_id`, `name`,`master`,`guild_lv`,`connect_member`,`max_member`,`average_lv`,`exp`,`next_exp`,`skill_point`,`castle_id`,`mes1`,`mes2`,`emblem_len`,`emblem_id`,`emblem_data`) " + "VALUES ('%d', '%s', '%s', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%s', '%s', '%d', '%d', '%s')", + guild_db, g->guild_id,t_name,jstrescapecpy(t_master,g->master), + g->guild_lv,g->connect_member,g->max_member,g->average_lv,g->exp,g->next_exp,g->skill_point,g->castle_id, + jstrescapecpy(t_mes1,g->mes1),jstrescapecpy(t_mes2,g->mes2),g->emblem_len,g->emblem_id,emblem_data); + //printf(" %s\n",tmp_sql); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (insert `guild`)- %s\n", mysql_error(&mysql_handle) ); + } + } + + if (flag&2||guild_member==0){ + //printf("- Insert guild %d to guild_member\n",g->guild_id); + for(i=0;i<g->max_member;i++){ + if (g->member[i].account_id>0){ + struct guild_member *m = &g->member[i]; + sprintf(tmp_sql,"DELETE FROM `%s` WHERE `char_id`='%d'",guild_member_db, m->char_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (delete `guild_member`)- %s\n", mysql_error(&mysql_handle) ); + } + sprintf(tmp_sql,"INSERT INTO `%s` " + "(`guild_id`,`account_id`,`char_id`,`hair`,`hair_color`,`gender`,`class`,`lv`,`exp`,`exp_payper`,`online`,`position`,`rsv1`,`rsv2`,`name`) " + "VALUES ('%d','%d','%d','%d','%d', '%d','%d','%d','%d','%d','%d','%d','%d','%d','%s')", + guild_member_db, g->guild_id, + m->account_id,m->char_id, + m->hair,m->hair_color,m->gender, + m->class,m->lv,m->exp,m->exp_payper,m->online,m->position, + 0,0, + jstrescapecpy(t_member,m->name)); + //printf(" %s\n",tmp_sql); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (insert `guild_member`)- %s\n", mysql_error(&mysql_handle) ); + } + sprintf(tmp_sql, "UPDATE `%s` SET `guild_id`='%d' WHERE `account_id`='%d' AND `char_id`='%d'",char_db, g->guild_id,m->account_id,m->char_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (update `char`)- %s\n", mysql_error(&mysql_handle) ); + } + } + } + } + + if (flag&4||guild_member==0){ + //printf("- Insert guild %d to guild_position\n",g->guild_id); + for(i=0;i<MAX_GUILDPOSITION;i++){ + struct guild_position *p = &g->position[i]; + sprintf(tmp_sql,"INSERT INTO `%s` (`guild_id`,`position`,`name`,`mode`,`exp_mode`) VALUES ('%d','%d', '%s','%d','%d')", + guild_position_db, g->guild_id, i, jstrescapecpy(t_position,p->name),p->mode,p->exp_mode); + //printf(" %s\n",tmp_sql); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (insert `guild_position`)- %s\n", mysql_error(&mysql_handle) ); + } + } + } + + if (flag&8||guild_member==0){ + //printf("- Insert guild %d to guild_alliance\n",g->guild_id); + for(i=0;i<MAX_GUILDALLIANCE;i++){ + struct guild_alliance *a=&g->alliance[i]; + if(a->guild_id>0){ + sprintf(tmp_sql,"INSERT INTO `%s` (`guild_id`,`opposition`,`alliance_id`,`name`) " + "VALUES ('%d','%d','%d','%s')", + guild_alliance_db, g->guild_id,a->opposition,a->guild_id,jstrescapecpy(t_alliance,a->name)); + //printf(" %s\n",tmp_sql); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (insert `guild_alliance`)- %s\n", mysql_error(&mysql_handle) ); + } + sprintf(tmp_sql,"INSERT INTO `%s` (`guild_id`,`opposition`,`alliance_id`,`name`) " + "VALUES ('%d','%d','%d','%s')", + guild_alliance_db, a->guild_id,a->opposition,g->guild_id,t_name); + //printf(" %s\n",tmp_sql); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (insert `guild_alliance`)- %s\n", mysql_error(&mysql_handle) ); + } + } + } + } + + if (flag&16||guild_member==0){ + //printf("- Insert guild %d to guild_expulsion\n",g->guild_id); + for(i=0;i<MAX_GUILDEXPLUSION;i++){ + struct guild_explusion *e=&g->explusion[i]; + if(e->account_id>0){ + sprintf(tmp_sql,"INSERT INTO `%s` (`guild_id`,`name`,`mes`,`acc`,`account_id`,`rsv1`,`rsv2`,`rsv3`) " + "VALUES ('%d','%s','%s','%s','%d','%d','%d','%d')", + guild_expulsion_db, g->guild_id, + jstrescapecpy(t_ename,e->name),jstrescapecpy(t_emes,e->mes),e->acc,e->account_id,e->rsv1,e->rsv2,e->rsv3 ); + //printf(" %s\n",tmp_sql); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (insert `guild_expulsion`)- %s\n", mysql_error(&mysql_handle) ); + } + } + } + } + + if (flag&32||guild_member==0){ + //printf("- Insert guild %d to guild_skill\n",g->guild_id); + for(i=0;i<MAX_GUILDSKILL;i++){ + if (g->skill[i].id>0){ + sprintf(tmp_sql,"INSERT INTO `%s` (`guild_id`,`id`,`lv`) VALUES ('%d','%d','%d')", + guild_skill_db, g->guild_id,g->skill[i].id,g->skill[i].lv); + //printf("%s\n",tmp_sql); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (insert `guild_skill`)- %s\n", mysql_error(&mysql_handle) ); + } + } + } + } + + printf("Save guild done\n"); + return 0; +} + +// Read guild from sql +int inter_guild_fromsql(int guild_id,struct guild *g) +{ + int i; + char emblem_data[4096]; + char *pstr; + + if (g==NULL) return 0; + memset(g,0,sizeof(struct guild)); + if (guild_id==0) return 0; + +// printf("Retrieve guild information from sql ......\n"); +// printf("- Read guild %d from sql \n",guild_id); + + sprintf(tmp_sql,"SELECT `guild_id`, `name`,`master`,`guild_lv`,`connect_member`,`max_member`,`average_lv`,`exp`,`next_exp`,`skill_point`,`castle_id`,`mes1`,`mes2`,`emblem_len`,`emblem_id`,`emblem_data` " + "FROM `%s` WHERE `guild_id`='%d'",guild_db, guild_id); + //printf(" %s\n",tmp_sql); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (select `guild`)- %s\n", mysql_error(&mysql_handle) ); + return 0; + } + + sql_res = mysql_store_result(&mysql_handle) ; + if (sql_res!=NULL && mysql_num_rows(sql_res)>0) { + sql_row = mysql_fetch_row(sql_res); + if (sql_row==NULL) { + mysql_free_result(sql_res); + return 0; + } + + g->guild_id=atoi(sql_row[0]); + strncpy(g->name,sql_row[1],24); + strncpy(g->master,sql_row[2],24); + g->guild_lv=atoi(sql_row[3]); + g->connect_member=atoi(sql_row[4]); + g->max_member=atoi(sql_row[5]); + g->average_lv=atoi(sql_row[6]); + g->exp=atoi(sql_row[7]); + g->next_exp=atoi(sql_row[8]); + g->skill_point=atoi(sql_row[9]); + g->castle_id=atoi(sql_row[10]); + strncpy(g->mes1,sql_row[11],60); + strncpy(g->mes2,sql_row[12],120); + g->emblem_len=atoi(sql_row[13]); + g->emblem_id=atoi(sql_row[14]); + strncpy(emblem_data,sql_row[15],4096); + for(i=0,pstr=emblem_data;i<g->emblem_len;i++,pstr+=2){ + int c1=pstr[0],c2=pstr[1],x1=0,x2=0; + if(c1>='0' && c1<='9')x1=c1-'0'; + if(c1>='a' && c1<='f')x1=c1-'a'+10; + if(c1>='A' && c1<='F')x1=c1-'A'+10; + if(c2>='0' && c2<='9')x2=c2-'0'; + if(c2>='a' && c2<='f')x2=c2-'a'+10; + if(c2>='A' && c2<='F')x2=c2-'A'+10; + g->emblem_data[i]=(x1<<4)|x2; + } + } + mysql_free_result(sql_res); + + //printf("- Read guild_member %d from sql \n",guild_id); + sprintf(tmp_sql,"SELECT `guild_id`,`account_id`,`char_id`,`hair`,`hair_color`,`gender`,`class`,`lv`,`exp`,`exp_payper`,`online`,`position`,`rsv1`,`rsv2`,`name` " + "FROM `%s` WHERE `guild_id`='%d' ORDER BY `position`", guild_member_db, guild_id); + //printf(" %s\n",tmp_sql); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (select `guild_member`)- %s\n", mysql_error(&mysql_handle) ); + return 0; + } + sql_res = mysql_store_result(&mysql_handle) ; + if (sql_res!=NULL && mysql_num_rows(sql_res)>0) { + int i; + for(i=0;((sql_row = mysql_fetch_row(sql_res))&&i<g->max_member);i++){ + struct guild_member *m = &g->member[i]; + m->account_id=atoi(sql_row[1]); + m->char_id=atoi(sql_row[2]); + m->hair=atoi(sql_row[3]); + m->hair_color=atoi(sql_row[4]); + m->gender=atoi(sql_row[5]); + m->class=atoi(sql_row[6]); + m->lv=atoi(sql_row[7]); + m->exp=atoi(sql_row[8]); + m->exp_payper=atoi(sql_row[9]); + m->online=atoi(sql_row[10]); + m->position=atoi(sql_row[11]); + strncpy(m->name,sql_row[14],24); + } + } + mysql_free_result(sql_res); + + //printf("- Read guild_position %d from sql \n",guild_id); + sprintf(tmp_sql,"SELECT `guild_id`,`position`,`name`,`mode`,`exp_mode` FROM `%s` WHERE `guild_id`='%d'",guild_position_db, guild_id); + //printf(" %s\n",tmp_sql); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (select `guild_position`)- %s\n", mysql_error(&mysql_handle) ); + return 0; + } + sql_res = mysql_store_result(&mysql_handle) ; + if (sql_res!=NULL && mysql_num_rows(sql_res)>0) { + int i; + for(i=0;((sql_row = mysql_fetch_row(sql_res))&&i<MAX_GUILDPOSITION);i++){ + int position = atoi(sql_row[1]); + struct guild_position *p = &g->position[position]; + strncpy(p->name,sql_row[2],24); + p->mode=atoi(sql_row[3]); + p->exp_mode=atoi(sql_row[4]); + } + } + mysql_free_result(sql_res); + + //printf("- Read guild_alliance %d from sql \n",guild_id); + sprintf(tmp_sql,"SELECT `guild_id`,`opposition`,`alliance_id`,`name` FROM `%s` WHERE `guild_id`='%d'",guild_alliance_db, guild_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (select `guild_alliance`)- %s\n", mysql_error(&mysql_handle) ); + return 0; + } + sql_res = mysql_store_result(&mysql_handle) ; + if (sql_res!=NULL && mysql_num_rows(sql_res)>0) { + int i; + for(i=0;((sql_row = mysql_fetch_row(sql_res))&&i<MAX_GUILDALLIANCE);i++){ + struct guild_alliance *a = &g->alliance[i]; + a->opposition=atoi(sql_row[1]); + a->guild_id=atoi(sql_row[2]); + strncpy(a->name,sql_row[3],24); + } + } + mysql_free_result(sql_res); + + //printf("- Read guild_expulsion %d from sql \n",guild_id); + sprintf(tmp_sql,"SELECT `guild_id`,`name`,`mes`,`acc`,`account_id`,`rsv1`,`rsv2`,`rsv3` FROM `%s` WHERE `guild_id`='%d'",guild_expulsion_db, guild_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (select `guild_expulsion`)- %s\n", mysql_error(&mysql_handle) ); + return 0; + } + sql_res = mysql_store_result(&mysql_handle) ; + if (sql_res!=NULL && mysql_num_rows(sql_res)>0) { + int i; + for(i=0;((sql_row = mysql_fetch_row(sql_res))&&i<MAX_GUILDEXPLUSION);i++){ + struct guild_explusion *e = &g->explusion[i]; + + strncpy(e->name,sql_row[1],24); + strncpy(e->mes,sql_row[2],40); + strncpy(e->acc,sql_row[3],24); + e->account_id=atoi(sql_row[4]); + e->rsv1=atoi(sql_row[5]); + e->rsv2=atoi(sql_row[6]); + e->rsv3=atoi(sql_row[7]); + + } + } + mysql_free_result(sql_res); + + //printf("- Read guild_skill %d from sql \n",guild_id); + sprintf(tmp_sql,"SELECT `guild_id`,`id`,`lv` FROM `%s` WHERE `guild_id`='%d' ORDER BY `id`",guild_skill_db, guild_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (select `guild_skill`)- %s\n", mysql_error(&mysql_handle) ); + return 0; + } + sql_res = mysql_store_result(&mysql_handle) ; + if (sql_res!=NULL && mysql_num_rows(sql_res)>0) { + int i; + for(i=0;((sql_row = mysql_fetch_row(sql_res))&&i<MAX_GUILDSKILL);i++){ + g->skill[i].id=atoi(sql_row[1]); + g->skill[i].lv=atoi(sql_row[2]); + } + } + mysql_free_result(sql_res); + +// printf("Successfully retrieve guild information from sql!\n"); + + return 0; + +} + +// Save guild_castle to sql +int inter_guildcastle_tosql(struct guild_castle *gc) +{ + // `guild_castle` (`castle_id`, `guild_id`, `economy`, `defense`, `triggerE`, `triggerD`, `nextTime`, `payTime`, `createTime`, `visibleC`, `visibleG0`, `visibleG1`, `visibleG2`, `visibleG3`, `visibleG4`, `visibleG5`, `visibleG6`, `visibleG7`) + + if (gc==NULL) return 0; + //printf("Save to guild_castle\n"); + sprintf(tmp_sql,"DELETE FROM `%s` WHERE `castle_id`='%d'",guild_castle_db, gc->castle_id); + //printf(" %s\n",tmp_sql); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle) ); + return 0; + } + + sprintf(tmp_sql,"INSERT INTO `%s` " + "(`castle_id`, `guild_id`, `economy`, `defense`, `triggerE`, `triggerD`, `nextTime`, `payTime`, `createTime`," + "`visibleC`, `visibleG0`, `visibleG1`, `visibleG2`, `visibleG3`, `visibleG4`, `visibleG5`, `visibleG6`, `visibleG7`," + "`Ghp0`, `Ghp1`, `Ghp2`, `Ghp3`, `Ghp4`, `Ghp5`, `Ghp6`, `Ghp7`)" + "VALUES ('%d','%d','%d','%d','%d','%d','%d','%d','%d','%d','%d','%d','%d','%d','%d','%d','%d','%d','%d','%d','%d','%d','%d','%d','%d','%d')", + guild_castle_db, gc->castle_id, gc->guild_id, gc->economy, gc->defense, gc->triggerE, gc->triggerD, gc->nextTime, gc->payTime, + gc->createTime, gc->visibleC, gc->visibleG0, gc->visibleG1, gc->visibleG2, gc->visibleG3, gc->visibleG4, gc->visibleG5, + gc->visibleG6, gc->visibleG7, gc->Ghp0, gc->Ghp1, gc->Ghp2, gc->Ghp3, gc->Ghp4, gc->Ghp5, gc->Ghp6, gc->Ghp7); + //printf(" %s\n",tmp_sql); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle) ); + return 0; + } + + sprintf(tmp_sql,"UPDATE `%s` SET `castle_id`='-1' WHERE `castle_id`='%d'",guild_db, gc->castle_id); + //printf(" %s\n",tmp_sql); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle) ); + return 0; + } + + sprintf(tmp_sql,"UPDATE `%s` SET `castle_id`='%d' WHERE `guild_id`='%d'",guild_db, gc->castle_id,gc->guild_id); + //printf(" %s\n",tmp_sql); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle) ); + return 0; + } + + return 0; +} +// Read guild_castle from sql +int inter_guildcastle_fromsql(int castle_id,struct guild_castle *gc) +{ + + if (gc==NULL) return 0; + //printf("Read from guild_castle\n"); + memset(gc,0,sizeof(struct guild_castle)); + gc->castle_id=castle_id; + if (castle_id==-1) return 0; + sprintf(tmp_sql,"SELECT `castle_id`, `guild_id`, `economy`, `defense`, `triggerE`, `triggerD`, `nextTime`, `payTime`, `createTime`, " + "`visibleC`, `visibleG0`, `visibleG1`, `visibleG2`, `visibleG3`, `visibleG4`, `visibleG5`, `visibleG6`, `visibleG7`," + "`Ghp0`, `Ghp1`, `Ghp2`, `Ghp3`, `Ghp4`, `Ghp5`, `Ghp6`, `Ghp7`" + " FROM `%s` WHERE `castle_id`='%d'",guild_castle_db, castle_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle) ); + return 0; + } + sql_res = mysql_store_result(&mysql_handle) ; + if (sql_res!=NULL && mysql_num_rows(sql_res)>0) { + sql_row = mysql_fetch_row(sql_res); + if (sql_row==NULL){ + mysql_free_result(sql_res); + return 0; + } + + gc->guild_id = atoi (sql_row[1]); + gc->economy = atoi (sql_row[2]); + gc->defense = atoi (sql_row[3]); + gc->triggerE = atoi (sql_row[4]); + gc->triggerD = atoi (sql_row[5]); + gc->nextTime = atoi (sql_row[6]); + gc->payTime = atoi (sql_row[7]); + gc->createTime = atoi (sql_row[8]); + gc->visibleC = atoi (sql_row[9]); + gc->visibleG0 = atoi (sql_row[10]); + gc->visibleG1 = atoi (sql_row[11]); + gc->visibleG2 = atoi (sql_row[12]); + gc->visibleG3 = atoi (sql_row[13]); + gc->visibleG4 = atoi (sql_row[14]); + gc->visibleG5 = atoi (sql_row[15]); + gc->visibleG6 = atoi (sql_row[16]); + gc->visibleG7 = atoi (sql_row[17]); + gc->Ghp0 = atoi (sql_row[18]); + gc->Ghp1 = atoi (sql_row[19]); + gc->Ghp2 = atoi (sql_row[20]); + gc->Ghp3 = atoi (sql_row[21]); + gc->Ghp4 = atoi (sql_row[22]); + gc->Ghp5 = atoi (sql_row[23]); + gc->Ghp6 = atoi (sql_row[24]); + gc->Ghp7 = atoi (sql_row[25]); + + //printf("Read Castle %d of guild %d from sql \n",castle_id,gc->guild_id); + + } + mysql_free_result(sql_res) ; //resource free + return 0; +} + +// Read exp_guild.txt +int inter_guild_readdb() +{ + int i; + FILE *fp; + char line[1024]; + for (i=0;i<100;i++) guild_exp[i]=0; + + fp=fopen("db/exp_guild.txt","r"); + if(fp==NULL){ + printf("can't read db/exp_guild.txt\n"); + return 1; + } + i=0; + while(fgets(line,256,fp) && i<100){ + if(line[0]=='/' && line[1]=='/') + continue; + guild_exp[i]=atoi(line); + i++; + } + fclose(fp); + + return 0; +} + + +// Initialize guild sql +int inter_guild_sql_init() +{ + int i; + + printf("interserver guild memory initialize.... (%d byte)\n",sizeof(struct guild)); + guild_pt = calloc(sizeof(struct guild), 1); + guild_pt2= calloc(sizeof(struct guild), 1); + guildcastle_pt=calloc(sizeof(struct guild_castle), 1); + + inter_guild_readdb(); // Read exp + + sprintf(tmp_sql,"UPDATE `%s` SET `online`='0'",guild_member_db); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (update `char`)- %s\n", mysql_error(&mysql_handle) ); + exit(0); + } + + sprintf (tmp_sql , "SELECT count(*) FROM `%s`",guild_db); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle) ); + exit(0); + } + sql_res = mysql_store_result(&mysql_handle) ; + sql_row = mysql_fetch_row(sql_res); + printf("total guild data -> '%s'.......\n",sql_row[0]); + i = atoi (sql_row[0]); + mysql_free_result(sql_res); + + if (i > 0) { + //set party_newid + sprintf (tmp_sql , "SELECT max(`guild_id`) FROM `%s`",guild_db); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle) ); + exit(0); + } + + sql_res = mysql_store_result(&mysql_handle) ; + sql_row = mysql_fetch_row(sql_res); + guild_newid = atoi(sql_row[0])+1; + mysql_free_result(sql_res); + } + + printf("set guild_newid: %d.......\n",guild_newid); + + return 0; +} + + +// Get guild by its name +struct guild* search_guildname(char *str) +{ + struct guild *g=guild_pt; + char t_name[24]; + int guild_id=0; + printf("search_guildname\n"); + sprintf (tmp_sql , "SELECT `guild_id` FROM `%s` WHERE `name`='%s'",guild_db, jstrescapecpy(t_name,str)); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle) ); + } + sql_res = mysql_store_result(&mysql_handle) ; + if (sql_res!=NULL && mysql_num_rows(sql_res)>0) { + sql_row = mysql_fetch_row(sql_res); + guild_id = atoi (sql_row[0]); + } + mysql_free_result(sql_res); + inter_guild_fromsql(guild_id,g); + return g; +} + +// Check if guild is empty +int guild_check_empty(struct guild *g) +{ + int i; + for(i=0;i<g->max_member;i++){ + if(g->member[i].account_id>0){ + return 0; + } + } + + // 誰もいないので解散 + mapif_guild_broken(g->guild_id,0); + inter_guild_storage_delete(g->guild_id); + inter_guild_tosql(g,255); + memset(g,0,sizeof(struct guild)); + return 1; +} + +int guild_nextexp(int level) +{ + if(level < 100 && level >0) // Change by hack + return guild_exp[level-1]; + + return 0; +} + +// ギルドスキルがあるか確認 +int guild_checkskill(struct guild *g,int id){ return g->skill[id-10000].lv; } + +// ギルドの情報の再計算 +int guild_calcinfo(struct guild *g) +{ + int i,c,nextexp; + struct guild before=*g; + + // スキルIDの設定 + for(i=0;i<5;i++) + g->skill[i].id=i+10000; + + // ギルドレベル + if(g->guild_lv<=0) g->guild_lv=1; + nextexp = guild_nextexp(g->guild_lv); + if(nextexp > 0) { + while(g->exp >= nextexp && nextexp>0){ // Change by hack + g->exp-=nextexp; + g->guild_lv++; + g->skill_point++; + nextexp = guild_nextexp(g->guild_lv); + } + } + + // ギルドの次の経験値 + g->next_exp = guild_nextexp(g->guild_lv); + + // メンバ上限(ギルド拡張適用) + g->max_member=16+guild_checkskill(g,10004)*2; // Updated max_members [PoW] + + // 平均レベルとオンライン人数 + g->average_lv=0; + g->connect_member=0; + for(i=c=0;i<g->max_member;i++){ + if(g->member[i].account_id>0){ + g->average_lv+=g->member[i].lv; + c++; + + if(g->member[i].online>0) + g->connect_member++; + } + } + g->average_lv/=c; + + // 全データを送る必要がありそう + if( g->max_member!=before.max_member || + g->guild_lv!=before.guild_lv || + g->skill_point!=before.skill_point ){ + mapif_guild_info(-1,g); + return 1; + } + + return 0; +} + +//------------------------------------------------------------------- +// map serverへの通信 + +// ギルド作成可否 +int mapif_guild_created(int fd,int account_id,struct guild *g) +{ + WFIFOW(fd,0)=0x3830; + WFIFOL(fd,2)=account_id; + if(g!=NULL){ + WFIFOL(fd,6)=g->guild_id; + printf("int_guild: created! %d %s\n",g->guild_id,g->name); + }else{ + WFIFOL(fd,6)=0; + } + WFIFOSET(fd,10); + return 0; +} +// ギルド情報見つからず +int mapif_guild_noinfo(int fd,int guild_id) +{ + WFIFOW(fd,0)=0x3831; + WFIFOW(fd,2)=8; + WFIFOL(fd,4)=guild_id; + WFIFOSET(fd,8); + printf("int_guild: info not found %d\n",guild_id); + return 0; +} +// ギルド情報まとめ送り +int mapif_guild_info(int fd,struct guild *g) +{ + unsigned char buf[16384]; + WBUFW(buf,0)=0x3831; + memcpy(buf+4,g,sizeof(struct guild)); + WBUFW(buf,2)=4+sizeof(struct guild); +// printf("int_guild: sizeof(guild)=%d\n",sizeof(struct guild)); + if(fd<0) + mapif_sendall(buf,WBUFW(buf,2)); + else + mapif_send(fd,buf,WBUFW(buf,2)); +// printf("int_guild: info %d %s\n",p->guild_id,p->name); + return 0; +} + +// メンバ追加可否 +int mapif_guild_memberadded(int fd,int guild_id,int account_id,int char_id,int flag) +{ + WFIFOW(fd,0)=0x3832; + WFIFOL(fd,2)=guild_id; + WFIFOL(fd,6)=account_id; + WFIFOL(fd,10)=char_id; + WFIFOB(fd,14)=flag; + WFIFOSET(fd,15); + return 0; +} +// 脱退/追放通知 +int mapif_guild_leaved(int guild_id,int account_id,int char_id,int flag, + const char *name,const char *mes) +{ + unsigned char buf[128]; + WBUFW(buf, 0)=0x3834; + WBUFL(buf, 2)=guild_id; + WBUFL(buf, 6)=account_id; + WBUFL(buf,10)=char_id; + WBUFB(buf,14)=flag; + memcpy(WBUFP(buf,15),mes,40); + memcpy(WBUFP(buf,55),name,24); + mapif_sendall(buf,79); + printf("int_guild: guild leaved %d %d %s %s\n",guild_id,account_id,name,mes); + return 0; +} + +// オンライン状態とLv更新通知 +int mapif_guild_memberinfoshort(struct guild *g,int idx) +{ + unsigned char buf[32]; + WBUFW(buf, 0)=0x3835; + WBUFL(buf, 2)=g->guild_id; + WBUFL(buf, 6)=g->member[idx].account_id; + WBUFL(buf,10)=g->member[idx].char_id; + WBUFB(buf,14)=g->member[idx].online; + WBUFW(buf,15)=g->member[idx].lv; + WBUFW(buf,17)=g->member[idx].class; + mapif_sendall(buf,19); + return 0; +} + +// 解散通知 +int mapif_guild_broken(int guild_id,int flag) +{ + unsigned char buf[16]; + WBUFW(buf,0)=0x3836; + WBUFL(buf,2)=guild_id; + WBUFB(buf,6)=flag; + mapif_sendall(buf,7); + printf("int_guild: broken %d\n",guild_id); + return 0; +} + +// ギルド内発言 +int mapif_guild_message(int guild_id,int account_id,char *mes,int len) +{ + unsigned char buf[512]; + WBUFW(buf,0)=0x3837; + WBUFW(buf,2)=len+12; + WBUFL(buf,4)=guild_id; + WBUFL(buf,8)=account_id; + memcpy(WBUFP(buf,12),mes,len); + mapif_sendall(buf,len+12); + return 0; +} + +// ギルド基本情報変更通知 +int mapif_guild_basicinfochanged(int guild_id,int type,const void *data,int len) +{ + unsigned char buf[2048]; + WBUFW(buf, 0)=0x3839; + WBUFW(buf, 2)=len+10; + WBUFL(buf, 4)=guild_id; + WBUFW(buf, 8)=type; + memcpy(WBUFP(buf,10),data,len); + mapif_sendall(buf,len+10); + return 0; +} +// ギルドメンバ情報変更通知 +int mapif_guild_memberinfochanged(int guild_id,int account_id,int char_id, + int type,const void *data,int len) +{ + unsigned char buf[2048]; + WBUFW(buf, 0)=0x383a; + WBUFW(buf, 2)=len+18; + WBUFL(buf, 4)=guild_id; + WBUFL(buf, 8)=account_id; + WBUFL(buf,12)=char_id; + WBUFW(buf,16)=type; + memcpy(WBUFP(buf,18),data,len); + mapif_sendall(buf,len+18); + return 0; +} +// ギルドスキルアップ通知 +int mapif_guild_skillupack(int guild_id,int skill_num,int account_id) +{ + unsigned char buf[16]; + WBUFW(buf, 0)=0x383c; + WBUFL(buf, 2)=guild_id; + WBUFL(buf, 6)=skill_num; + WBUFL(buf,10)=account_id; + mapif_sendall(buf,14); + return 0; +} +// ギルド同盟/敵対通知 +int mapif_guild_alliance(int guild_id1,int guild_id2,int account_id1,int account_id2, + int flag,const char *name1,const char *name2) +{ + unsigned char buf[128]; + WBUFW(buf, 0)=0x383d; + WBUFL(buf, 2)=guild_id1; + WBUFL(buf, 6)=guild_id2; + WBUFL(buf,10)=account_id1; + WBUFL(buf,14)=account_id2; + WBUFB(buf,18)=flag; + memcpy(WBUFP(buf,19),name1,24); + memcpy(WBUFP(buf,43),name2,24); + mapif_sendall(buf,67); + return 0; +} + +// ギルド役職変更通知 +int mapif_guild_position(struct guild *g,int idx) +{ + unsigned char buf[128]; + WBUFW(buf,0)=0x383b; + WBUFW(buf,2)=sizeof(struct guild_position)+12; + WBUFL(buf,4)=g->guild_id; + WBUFL(buf,8)=idx; + memcpy(WBUFP(buf,12),&g->position[idx],sizeof(struct guild_position)); + mapif_sendall(buf,WBUFW(buf,2)); + return 0; +} + +// ギルド告知変更通知 +int mapif_guild_notice(struct guild *g) +{ + unsigned char buf[256]; + WBUFW(buf,0)=0x383e; + WBUFL(buf,2)=g->guild_id; + memcpy(WBUFP(buf,6),g->mes1,60); + memcpy(WBUFP(buf,66),g->mes2,120); + mapif_sendall(buf,186); + return 0; +} +// ギルドエンブレム変更通知 +int mapif_guild_emblem(struct guild *g) +{ + unsigned char buf[2048]; + WBUFW(buf,0)=0x383f; + WBUFW(buf,2)=g->emblem_len+12; + WBUFL(buf,4)=g->guild_id; + WBUFL(buf,8)=g->emblem_id; + memcpy(WBUFP(buf,12),g->emblem_data,g->emblem_len); + mapif_sendall(buf,WBUFW(buf,2)); + return 0; +} + +int mapif_guild_castle_dataload(int castle_id,int index,int value) // <Agit> +{ + unsigned char buf[16]; + WBUFW(buf, 0)=0x3840; + WBUFW(buf, 2)=castle_id; + WBUFB(buf, 4)=index; + WBUFL(buf, 5)=value; + mapif_sendall(buf,9); + return 0; +} + +int mapif_guild_castle_datasave(int castle_id,int index,int value) // <Agit> +{ + unsigned char buf[16]; + WBUFW(buf, 0)=0x3841; + WBUFW(buf, 2)=castle_id; + WBUFB(buf, 4)=index; + WBUFL(buf, 5)=value; + mapif_sendall(buf,9); + return 0; +} + +int mapif_guild_castle_alldataload(int fd) { + struct guild_castle* gc = guildcastle_pt; + int i, len = 4; + + WFIFOW(fd,0) = 0x3842; + sprintf(tmp_sql, "SELECT * FROM `%s` ORDER BY `castle_id`", guild_castle_db); + if (mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle) ); + } + sql_res = mysql_store_result(&mysql_handle); + if (sql_res != NULL && mysql_num_rows(sql_res) > 0) { + for(i = 0; ((sql_row = mysql_fetch_row(sql_res)) && i < MAX_GUILDCASTLE); i++) { + memset(gc, 0, sizeof(struct guild_castle)); + gc->castle_id = atoi(sql_row[0]); + gc->guild_id = atoi(sql_row[1]); + gc->economy = atoi(sql_row[2]); + gc->defense = atoi(sql_row[3]); + gc->triggerE = atoi(sql_row[4]); + gc->triggerD = atoi(sql_row[5]); + gc->nextTime = atoi(sql_row[6]); + gc->payTime = atoi(sql_row[7]); + gc->createTime = atoi(sql_row[8]); + gc->visibleC = atoi(sql_row[9]); + gc->visibleG0 = atoi(sql_row[10]); + gc->visibleG1 = atoi(sql_row[11]); + gc->visibleG2 = atoi(sql_row[12]); + gc->visibleG3 = atoi(sql_row[13]); + gc->visibleG4 = atoi(sql_row[14]); + gc->visibleG5 = atoi(sql_row[15]); + gc->visibleG6 = atoi(sql_row[16]); + gc->visibleG7 = atoi(sql_row[17]); + gc->Ghp0 = atoi(sql_row[18]); + gc->Ghp1 = atoi(sql_row[19]); + gc->Ghp2 = atoi(sql_row[20]); + gc->Ghp3 = atoi(sql_row[21]); + gc->Ghp4 = atoi(sql_row[22]); + gc->Ghp5 = atoi(sql_row[23]); + gc->Ghp6 = atoi(sql_row[24]); + gc->Ghp7 = atoi(sql_row[25]); + memcpy(WFIFOP(fd,len), gc, sizeof(struct guild_castle)); + len += sizeof(struct guild_castle); + } + } + mysql_free_result(sql_res); + WFIFOW(fd,2) = len; + WFIFOSET(fd,len); + + return 0; +} + + +//------------------------------------------------------------------- +// map serverからの通信 + + +// ギルド作成要求 +int mapif_parse_CreateGuild(int fd,int account_id,char *name,struct guild_member *master) +{ + struct guild *g; + int i; + + printf("CreateGuild\n"); + g=search_guildname(name); + if(g!=NULL&&g->guild_id>0){ + printf("int_guild: same name guild exists [%s]\n",name); + mapif_guild_created(fd,account_id,NULL); + return 0; + } + g=guild_pt; + memset(g,0,sizeof(struct guild)); + g->guild_id=guild_newid++; + memcpy(g->name,name,24); + memcpy(g->master,master->name,24); + memcpy(&g->member[0],master,sizeof(struct guild_member)); + + g->position[0].mode=0x11; + strcpy(g->position[ 0].name,"GuildMaster"); + strcpy(g->position[MAX_GUILDPOSITION-1].name,"Newbie"); + for(i=1;i<MAX_GUILDPOSITION-1;i++) + sprintf(g->position[i].name,"Position %d",i+1); + + // Initialize guild property + g->max_member=16; + g->average_lv=master->lv; + g->castle_id=-1; + for(i=0;i<5;i++) + g->skill[i].id=i+10000; + + // Save to sql + printf("Create initialize OK!\n"); + i=inter_guild_tosql(g,255); + + if (i<0) { + mapif_guild_created(fd,account_id,NULL); + return 0; + } + + // Report to client + mapif_guild_created(fd,account_id,g); + mapif_guild_info(fd,g); + + inter_log("guild %s (id=%d) created by master %s (id=%d)" RETCODE, + name, g->guild_id, master->name, master->account_id ); + + + return 0; +} +// Return guild info to client +int mapif_parse_GuildInfo(int fd,int guild_id) +{ + struct guild *g; + g=guild_pt; + inter_guild_fromsql(guild_id,g); + if(g!=NULL&&g->guild_id>0){ + guild_calcinfo(g); + mapif_guild_info(fd,g); + //inter_guild_tosql(g,1); // Change guild + }else + mapif_guild_noinfo(fd,guild_id); + return 0; +} +// Add member to guild +int mapif_parse_GuildAddMember(int fd,int guild_id,struct guild_member *m) +{ + struct guild *g; + int i; + g=guild_pt; + inter_guild_fromsql(guild_id,g); + + if(g==NULL||g->guild_id<=0){ + mapif_guild_memberadded(fd,guild_id,m->account_id,m->char_id,1); + return 0; + } + + for(i=0;i<g->max_member;i++){ + if(g->member[i].account_id==0){ + + memcpy(&g->member[i],m,sizeof(struct guild_member)); + mapif_guild_memberadded(fd,guild_id,m->account_id,m->char_id,0); + guild_calcinfo(g); + mapif_guild_info(-1,g); + inter_guild_tosql(g,3); // Change guild & guild_member + return 0; + } + } + mapif_guild_memberadded(fd,guild_id,m->account_id,m->char_id,1); + //inter_guild_tosql(g,3); // Change guild & guild_member + return 0; +} +// Delete member from guild +int mapif_parse_GuildLeave(int fd,int guild_id,int account_id,int char_id,int flag,const char *mes) +{ + struct guild *g=NULL; + g=guild_pt; + inter_guild_fromsql(guild_id,g); + + if(g!=NULL&&g->guild_id>0){ + int i; + for(i=0;i<g->max_member;i++){ + if( g->member[i].account_id==account_id && + g->member[i].char_id==char_id){ + printf("%d %d\n",i, (int)(&g->member[i])); + printf("%d %s\n",i, g->member[i].name); + + if(flag){ // 追放の場合追放リストに入れる + int j; + for(j=0;j<MAX_GUILDEXPLUSION;j++){ + if(g->explusion[j].account_id==0) + break; + } + if(j==MAX_GUILDEXPLUSION){ // 一杯なので古いのを消す + for(j=0;j<MAX_GUILDEXPLUSION-1;j++) + g->explusion[j]=g->explusion[j+1]; + j=MAX_GUILDEXPLUSION-1; + } + g->explusion[j].account_id=account_id; + memcpy(g->explusion[j].acc,"dummy",24); + memcpy(g->explusion[j].name,g->member[i].name,24); + memcpy(g->explusion[j].mes,mes,40); + } + + mapif_guild_leaved(guild_id,account_id,char_id,flag,g->member[i].name,mes); + printf("%d %d\n",i, (int)(&g->member[i])); + printf("%d %s\n",i, (&g->member[i])->name); + memset(&g->member[i],0,sizeof(struct guild_member)); + + if( guild_check_empty(g)==0 ) + mapif_guild_info(-1,g);// まだ人がいるのでデータ送信 + /* + else + inter_guild_save(); // 解散したので一応セーブ + return 0;*/ + } + } + guild_calcinfo(g); + inter_guild_tosql(g,19); // Change guild & guild_member & guild_expulsion + }else{ + sprintf(tmp_sql, "UPDATE `%s` SET `guild_id`='0' WHERE `account_id`='%d' AND `char_id`='%d'",char_db, account_id,char_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (update `char`)- %s\n", mysql_error(&mysql_handle) ); + } + /* mapif_guild_leaved(guild_id,account_id,char_id,flag,g->member[i].name,mes); */ + } + + + return 0; +} +// Change member info +int mapif_parse_GuildChangeMemberInfoShort(int fd,int guild_id, + int account_id,int char_id,int online,int lv,int class) +{ + // Could speed up by manipulating only guild_member + struct guild *g; + int i,alv,c; + g=guild_pt; + inter_guild_fromsql(guild_id,g); + + if(g==NULL||g->guild_id<=0){ + return 0; + } + + g->connect_member=0; + + for(i=0,alv=0,c=0;i<g->max_member;i++){ + if( g->member[i].account_id==account_id && + g->member[i].char_id==char_id){ + + g->member[i].online=online; + g->member[i].lv=lv; + g->member[i].class=class; + mapif_guild_memberinfoshort(g,i); + } + if( g->member[i].account_id>0 ){ + alv+=g->member[i].lv; + c++; + } + if( g->member[i].online ) + g->connect_member++; + } + // 平均レベル + g->average_lv=alv/c; + + inter_guild_tosql(g,3); // Change guild & guild_member + + return 0; +} + +// BreakGuild +int mapif_parse_BreakGuild(int fd,int guild_id) +{ + struct guild *g; + g=guild_pt; + inter_guild_fromsql(guild_id,g); + if(g==NULL){ + return 0; + } + + // Delete guild from sql + //printf("- Delete guild %d from guild\n",guild_id); + sprintf(tmp_sql, "DELETE FROM `%s` WHERE `guild_id`='%d'",guild_db, guild_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (delete `guild`)- %s\n", mysql_error(&mysql_handle) ); + } + //printf("- Delete guild %d from guild_member\n",guild_id); + sprintf(tmp_sql, "DELETE FROM `%s` WHERE `guild_id`='%d'",guild_member_db, guild_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (delete `guild_member`)- %s\n", mysql_error(&mysql_handle) ); + } + //printf("- Delete guild %d from guild_skill\n",guild_id); + sprintf(tmp_sql, "DELETE FROM `%s` WHERE `guild_id`='%d'",guild_skill_db, guild_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (delete `guild_skill`)- %s\n", mysql_error(&mysql_handle) ); + } + //printf("- Delete guild %d from guild_position\n",guild_id); + sprintf(tmp_sql, "DELETE FROM `%s` WHERE `guild_id`='%d'",guild_position_db, guild_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (delete `guild_position`)- %s\n", mysql_error(&mysql_handle) ); + } + //printf("- Delete guild %d from guild_expulsion\n",guild_id); + sprintf(tmp_sql, "DELETE FROM `%s` WHERE `guild_id`='%d'",guild_expulsion_db, guild_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (delete `guild_expulsion`)- %s\n", mysql_error(&mysql_handle) ); + } + //printf("- Delete guild %d from guild_alliance\n",guild_id); + sprintf(tmp_sql, "DELETE FROM `%s` WHERE `guild_id`='%d' OR `alliance_id`='%d'",guild_alliance_db, guild_id,guild_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (delete `guild_position`)- %s\n", mysql_error(&mysql_handle) ); + } + + //printf("- Delete guild %d from guild_castle\n",guild_id); + sprintf(tmp_sql, "DELETE FROM `%s` WHERE `guild_id`='%d'",guild_castle_db, guild_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (delete `guild_position`)- %s\n", mysql_error(&mysql_handle) ); + } + + //printf("- Update guild %d of char\n",guild_id); + sprintf(tmp_sql, "UPDATE `%s` SET `guild_id`='0' WHERE `guild_id`='%d'",char_db, guild_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (delete `guild_position`)- %s\n", mysql_error(&mysql_handle) ); + } + + inter_guild_storage_delete(guild_id); + mapif_guild_broken(guild_id,0); + + inter_log("guild %s (id=%d) broken" RETCODE,g->name,guild_id); + + return 0; +} + +// ギルドメッセージ送信 +int mapif_parse_GuildMessage(int fd,int guild_id,int account_id,char *mes,int len) +{ + return mapif_guild_message(guild_id,account_id,mes,len); +} +// ギルド基本データ変更要求 +int mapif_parse_GuildBasicInfoChange(int fd,int guild_id, + int type,const char *data,int len) +{ + struct guild *g; +// int dd=*((int *)data); + short dw=*((short *)data); + g=guild_pt; + inter_guild_fromsql(guild_id,g); + + if(g==NULL||g->guild_id<=0){ + return 0; + } + switch(type){ + case GBI_GUILDLV: { + printf("GBI_GUILDLV\n"); + if(dw>0 && g->guild_lv+dw<=50){ + g->guild_lv+=dw; + g->skill_point+=dw; + }else if(dw<0 && g->guild_lv+dw>=1) + g->guild_lv+=dw; + mapif_guild_info(-1,g); + inter_guild_tosql(g,1); + } return 0; + default: + printf("int_guild: GuildBasicInfoChange: Unknown type %d\n",type); + break; + } + mapif_guild_basicinfochanged(guild_id,type,data,len); + //inter_guild_tosql(g,1); // Change guild + return 0; +} + +// ギルドメンバデータ変更要求 +int mapif_parse_GuildMemberInfoChange(int fd,int guild_id,int account_id,int char_id, + int type,const char *data,int len) +{ + // Could make some improvement in speed, because only change guild_member + int i; + struct guild *g; + g=guild_pt; + inter_guild_fromsql(guild_id,g); + //printf("GuildMemberInfoChange %s \n",(type==GMI_EXP)?"GMI_EXP":"OTHER"); + + if(g==NULL){ + return 0; + } + for(i=0;i<g->max_member;i++) + if( g->member[i].account_id==account_id && + g->member[i].char_id==char_id ) + break; + if(i==g->max_member){ + printf("int_guild: GuildMemberChange: Not found %d,%d in %d[%s]\n", + account_id,char_id,guild_id,g->name); + return 0; + } + switch(type){ + case GMI_POSITION: // 役職 + g->member[i].position=*((int *)data); + break; + case GMI_EXP: { // EXP + int exp,oldexp=g->member[i].exp; + exp=g->member[i].exp=*((unsigned int *)data); + g->exp+=(exp-oldexp); + guild_calcinfo(g); // Lvアップ判断 + mapif_guild_basicinfochanged(guild_id,GBI_EXP,&g->exp,4); + }break; + default: + printf("int_guild: GuildMemberInfoChange: Unknown type %d\n",type); + break; + } + mapif_guild_memberinfochanged(guild_id,account_id,char_id,type,data,len); + inter_guild_tosql(g,3); // Change guild & guild_member + return 0; +} + +// ギルド役職名変更要求 +int mapif_parse_GuildPosition(int fd,int guild_id,int idx,struct guild_position *p) +{ + // Could make some improvement in speed, because only change guild_position + struct guild *g; + g=guild_pt; + inter_guild_fromsql(guild_id,g); + + if(g==NULL || idx<0 || idx>=MAX_GUILDPOSITION){ + return 0; + } + memcpy(&g->position[idx],p,sizeof(struct guild_position)); + mapif_guild_position(g,idx); + printf("int_guild: position changed %d\n",idx); + inter_guild_tosql(g,4); // Change guild_position + return 0; +} +// ギルドスキルアップ要求 +int mapif_parse_GuildSkillUp(int fd,int guild_id,int skill_num,int account_id) +{ + // Could make some improvement in speed, because only change guild_position + struct guild *g; + g=guild_pt; + inter_guild_fromsql(guild_id,g); + int idx=skill_num-10000; + if(g==NULL || skill_num<10000) + return 0; + //printf("GuildSkillUp\n"); + + if( g->skill_point>0 && g->skill[idx].id>0 && + g->skill[idx].lv<10 ){ + g->skill[idx].lv++; + g->skill_point--; + if(guild_calcinfo(g)==0) + mapif_guild_info(-1,g); + mapif_guild_skillupack(guild_id,skill_num,account_id); + printf("int_guild: skill %d up\n",skill_num); + inter_guild_tosql(g,33); // Change guild & guild_skill + } + + return 0; +} +// ギルド同盟要求 +int mapif_parse_GuildAlliance(int fd,int guild_id1,int guild_id2, + int account_id1,int account_id2,int flag) +{ + // Could speed up + struct guild *g[2]; + int j,i; + g[0]=guild_pt; + g[1]=guild_pt2; + inter_guild_fromsql(guild_id1,g[0]); + inter_guild_fromsql(guild_id2,g[1]); + + if(g[0]==NULL || g[1]==NULL || g[0]->guild_id ==0 || g[1]->guild_id==0) + return 0; + + if(!(flag&0x8)){ + for(i=0;i<2-(flag&1);i++){ + for(j=0;j<MAX_GUILDALLIANCE;j++) + if(g[i]->alliance[j].guild_id==0){ + g[i]->alliance[j].guild_id=g[1-i]->guild_id; + memcpy(g[i]->alliance[j].name,g[1-i]->name,24); + g[i]->alliance[j].opposition=flag&1; + break; + } + } + }else{ // 関係解消 + for(i=0;i<2-(flag&1);i++){ + for(j=0;j<MAX_GUILDALLIANCE;j++) + if( g[i]->alliance[j].guild_id==g[1-i]->guild_id && + g[i]->alliance[j].opposition==(flag&1)){ + g[i]->alliance[j].guild_id=0; + break; + } + } + } + mapif_guild_alliance(guild_id1,guild_id2,account_id1,account_id2,flag, + g[0]->name,g[1]->name); + inter_guild_tosql(g[0],8); // Change guild_alliance + inter_guild_tosql(g[1],8); // Change guild_alliance + return 0; +} +// ギルド告知変更要求 +int mapif_parse_GuildNotice(int fd,int guild_id,const char *mes1,const char *mes2) +{ + struct guild *g; + g=guild_pt; + inter_guild_fromsql(guild_id,g); + + if(g==NULL||g->guild_id<=0) + return 0; + memcpy(g->mes1,mes1,60); + memcpy(g->mes2,mes2,120); + inter_guild_tosql(g,1); // Change mes of guild + return mapif_guild_notice(g); +} +// ギルドエンブレム変更要求 +int mapif_parse_GuildEmblem(int fd,int len,int guild_id,int dummy,const char *data) +{ + struct guild *g; + g=guild_pt; + inter_guild_fromsql(guild_id,g); + + if(g==NULL||g->guild_id<=0) + return 0; + memcpy(g->emblem_data,data,len); + g->emblem_len=len; + g->emblem_id++; + inter_guild_tosql(g,1); // Change guild + return mapif_guild_emblem(g); +} + +int mapif_parse_GuildCastleDataLoad(int fd,int castle_id,int index) // <Agit> +{ + struct guild_castle *gc=guildcastle_pt; + inter_guildcastle_fromsql(castle_id, gc); + if(gc==NULL||gc->castle_id==-1){ + return mapif_guild_castle_dataload(castle_id,0,0); + } + switch(index){ + case 1: return mapif_guild_castle_dataload(gc->castle_id,index,gc->guild_id); break; + case 2: return mapif_guild_castle_dataload(gc->castle_id,index,gc->economy); break; + case 3: return mapif_guild_castle_dataload(gc->castle_id,index,gc->defense); break; + case 4: return mapif_guild_castle_dataload(gc->castle_id,index,gc->triggerE); break; + case 5: return mapif_guild_castle_dataload(gc->castle_id,index,gc->triggerD); break; + case 6: return mapif_guild_castle_dataload(gc->castle_id,index,gc->nextTime); break; + case 7: return mapif_guild_castle_dataload(gc->castle_id,index,gc->payTime); break; + case 8: return mapif_guild_castle_dataload(gc->castle_id,index,gc->createTime); break; + case 9: return mapif_guild_castle_dataload(gc->castle_id,index,gc->visibleC); break; + case 10: return mapif_guild_castle_dataload(gc->castle_id,index,gc->visibleG0); break; + case 11: return mapif_guild_castle_dataload(gc->castle_id,index,gc->visibleG1); break; + case 12: return mapif_guild_castle_dataload(gc->castle_id,index,gc->visibleG2); break; + case 13: return mapif_guild_castle_dataload(gc->castle_id,index,gc->visibleG3); break; + case 14: return mapif_guild_castle_dataload(gc->castle_id,index,gc->visibleG4); break; + case 15: return mapif_guild_castle_dataload(gc->castle_id,index,gc->visibleG5); break; + case 16: return mapif_guild_castle_dataload(gc->castle_id,index,gc->visibleG6); break; + case 17: return mapif_guild_castle_dataload(gc->castle_id,index,gc->visibleG7); break; + case 18: return mapif_guild_castle_dataload(gc->castle_id,index,gc->Ghp0); break; // guardian HP [Valaris] + case 19: return mapif_guild_castle_dataload(gc->castle_id,index,gc->Ghp1); break; + case 20: return mapif_guild_castle_dataload(gc->castle_id,index,gc->Ghp2); break; + case 21: return mapif_guild_castle_dataload(gc->castle_id,index,gc->Ghp3); break; + case 22: return mapif_guild_castle_dataload(gc->castle_id,index,gc->Ghp4); break; + case 23: return mapif_guild_castle_dataload(gc->castle_id,index,gc->Ghp5); break; + case 24: return mapif_guild_castle_dataload(gc->castle_id,index,gc->Ghp6); break; + case 25: return mapif_guild_castle_dataload(gc->castle_id,index,gc->Ghp7); break; // end additions [Valaris] + default: + printf("mapif_parse_GuildCastleDataLoad ERROR!! (Not found index=%d)\n", index); + return 0; + } +} + +int mapif_parse_GuildCastleDataSave(int fd,int castle_id,int index,int value) // <Agit> +{ + struct guild_castle *gc=guildcastle_pt; + inter_guildcastle_fromsql(castle_id, gc); + if(gc==NULL||gc->castle_id==-1){ + return mapif_guild_castle_datasave(castle_id,index,value); + } + switch(index){ + case 1: + if( gc->guild_id!=value ){ + int gid=(value)?value:gc->guild_id; + struct guild *g=guild_pt; + inter_guild_fromsql(gid, g); + inter_log("guild %s (id=%d) %s castle id=%d" RETCODE, + (g)?g->name:"??" ,gid, (value)?"occupy":"abandon", index); + } + gc->guild_id = value; + break; + case 2: gc->economy = value; break; + case 3: gc->defense = value; break; + case 4: gc->triggerE = value; break; + case 5: gc->triggerD = value; break; + case 6: gc->nextTime = value; break; + case 7: gc->payTime = value; break; + case 8: gc->createTime = value; break; + case 9: gc->visibleC = value; break; + case 10: gc->visibleG0 = value; break; + case 11: gc->visibleG1 = value; break; + case 12: gc->visibleG2 = value; break; + case 13: gc->visibleG3 = value; break; + case 14: gc->visibleG4 = value; break; + case 15: gc->visibleG5 = value; break; + case 16: gc->visibleG6 = value; break; + case 17: gc->visibleG7 = value; break; + case 18: gc->Ghp0 = value; break; // guardian HP [Valaris] + case 19: gc->Ghp1 = value; break; + case 20: gc->Ghp2 = value; break; + case 21: gc->Ghp3 = value; break; + case 22: gc->Ghp4 = value; break; + case 23: gc->Ghp5 = value; break; + case 24: gc->Ghp6 = value; break; + case 25: gc->Ghp7 = value; break; // end additions [Valaris] + default: + printf("mapif_parse_GuildCastleDataSave ERROR!! (Not found index=%d)\n", index); + return 0; + } + inter_guildcastle_tosql(gc); + return mapif_guild_castle_datasave(gc->castle_id,index,value); +} + +// ギルドチェック要求 +int mapif_parse_GuildCheck(int fd,int guild_id,int account_id,int char_id) +{ + // What does this mean? Check if belong to another guild? + return 0; +} + +// map server からの通信 +// ・1パケットのみ解析すること +// ・パケット長データはinter.cにセットしておくこと +// ・パケット長チェックや、RFIFOSKIPは呼び出し元で行われるので行ってはならない +// ・エラーなら0(false)、そうでないなら1(true)をかえさなければならない +int inter_guild_parse_frommap(int fd) +{ + switch(RFIFOW(fd,0)){ + case 0x3030: mapif_parse_CreateGuild(fd,RFIFOL(fd,4),RFIFOP(fd,8),(struct guild_member *)RFIFOP(fd,32)); break; + case 0x3031: mapif_parse_GuildInfo(fd,RFIFOL(fd,2)); break; + case 0x3032: mapif_parse_GuildAddMember(fd,RFIFOL(fd,4),(struct guild_member *)RFIFOP(fd,8)); break; + case 0x3034: mapif_parse_GuildLeave(fd,RFIFOL(fd,2),RFIFOL(fd,6),RFIFOL(fd,10),RFIFOB(fd,14),RFIFOP(fd,15)); break; + case 0x3035: mapif_parse_GuildChangeMemberInfoShort(fd,RFIFOL(fd,2),RFIFOL(fd,6),RFIFOL(fd,10),RFIFOB(fd,14),RFIFOW(fd,15),RFIFOW(fd,17)); break; + case 0x3036: mapif_parse_BreakGuild(fd,RFIFOL(fd,2)); break; + case 0x3037: mapif_parse_GuildMessage(fd,RFIFOL(fd,4),RFIFOL(fd,8),RFIFOP(fd,12),RFIFOW(fd,2)-12); break; + case 0x3038: mapif_parse_GuildCheck(fd,RFIFOL(fd,2),RFIFOL(fd,6),RFIFOL(fd,10)); break; + case 0x3039: mapif_parse_GuildBasicInfoChange(fd,RFIFOL(fd,4),RFIFOW(fd,8),RFIFOP(fd,10),RFIFOW(fd,2)-10); break; + case 0x303A: mapif_parse_GuildMemberInfoChange(fd,RFIFOL(fd,4),RFIFOL(fd,8),RFIFOL(fd,12),RFIFOW(fd,16),RFIFOP(fd,18),RFIFOW(fd,2)-18); break; + case 0x303B: mapif_parse_GuildPosition(fd,RFIFOL(fd,4),RFIFOL(fd,8),(struct guild_position *)RFIFOP(fd,12)); break; + case 0x303C: mapif_parse_GuildSkillUp(fd,RFIFOL(fd,2),RFIFOL(fd,6),RFIFOL(fd,10)); break; + case 0x303D: mapif_parse_GuildAlliance(fd,RFIFOL(fd,2),RFIFOL(fd,6),RFIFOL(fd,10),RFIFOL(fd,14),RFIFOB(fd,18)); break; + case 0x303E: mapif_parse_GuildNotice(fd,RFIFOL(fd,2),RFIFOP(fd,6),RFIFOP(fd,66)); break; + case 0x303F: mapif_parse_GuildEmblem(fd,RFIFOW(fd,2)-12,RFIFOL(fd,4),RFIFOL(fd,8),RFIFOP(fd,12)); break; + case 0x3040: mapif_parse_GuildCastleDataLoad(fd,RFIFOW(fd,2),RFIFOB(fd,4)); break; + case 0x3041: mapif_parse_GuildCastleDataSave(fd,RFIFOW(fd,2),RFIFOB(fd,4),RFIFOL(fd,5)); break; + + default: + return 0; + } + return 1; +} + +int inter_guild_mapif_init(int fd) +{ + return mapif_guild_castle_alldataload(fd); +} + +// サーバーから脱退要求(キャラ削除用) +int inter_guild_leave(int guild_id,int account_id,int char_id) +{ + return mapif_parse_GuildLeave(-1,guild_id,account_id,char_id,0,"**サーバー命令**"); +} diff --git a/misc/src/char_sql/int_guild.h b/misc/src/char_sql/int_guild.h new file mode 100644 index 0000000..8f4203d --- /dev/null +++ b/misc/src/char_sql/int_guild.h @@ -0,0 +1,10 @@ +#ifndef _INT_GUILD_H_ +#define _INT_GUILD_H_ + +int inter_guild_parse_frommap(int fd); +int inter_guild_sql_init(); +int inter_guild_mapif_init(int fd); + +int inter_guild_leave(int guild_id,int account_id,int char_id); + +#endif diff --git a/misc/src/char_sql/int_party.c b/misc/src/char_sql/int_party.c new file mode 100644 index 0000000..84de078 --- /dev/null +++ b/misc/src/char_sql/int_party.c @@ -0,0 +1,755 @@ +// +// original code from athena +// SQL conversion by hack +// + +#include "char.h" +#include "strlib.h" +#include "socket.h" +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +static struct party *party_pt; +static int party_newid=100; + +int mapif_party_broken(int party_id,int flag); +int party_check_empty(struct party *p); +int mapif_parse_PartyLeave(int fd,int party_id,int account_id); + +// Save party to mysql +int inter_party_tosql(int party_id,struct party *p) +{ + // 'party' ('party_id','name','exp','item','leader') + + char t_name[100]; + char t_member[24]; + int party_member = 0, party_online_member = 0; + int party_exist = 0; + int leader_id = 0; + int i = 0; + + printf("(\033[1;64m%d\033[0m) Request save party - ",party_id); + + jstrescapecpy(t_name, p->name); + + if (p==NULL || party_id==0 || p->party_id ==0 || party_id!=p->party_id) { + printf("- Party pointer or party_id error \n"); + return 0; + } + + // Check if party exists + sprintf(tmp_sql,"SELECT count(*) FROM `%s` WHERE `party_id`='%d'",party_db, party_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle) ); + return 0; + } + sql_res = mysql_store_result(&mysql_handle) ; + if (sql_res!=NULL && mysql_num_rows(sql_res)>0) { + sql_row = mysql_fetch_row(sql_res); + party_exist = atoi (sql_row[0]); + //printf("- Check if party %d exists : %s\n",party_id,party_exist==0?"No":"Yes"); + } + mysql_free_result(sql_res) ; //resource free + + if (party_exist >0){ + // Check members in party + sprintf(tmp_sql,"SELECT count(*) FROM `%s` WHERE `party_id`='%d'",char_db, party_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle) ); + return 0; + } + sql_res = mysql_store_result(&mysql_handle) ; + if (sql_res!=NULL && mysql_num_rows(sql_res)>0) { + sql_row = mysql_fetch_row(sql_res); + + party_member = atoi (sql_row[0]); + // printf("- Check members in party %d : %d \n",party_id,party_member); + + } + mysql_free_result(sql_res) ; //resource free + + party_online_member = 0; + i=0; + while (i<MAX_PARTY){ + if (p->member[i].account_id>0) party_online_member++; + i++; + } + + //if (party_online_member==0) printf("- No member online \n"); else printf("- Some member %d online \n", party_online_member); + + if (party_member <= 0 && party_online_member == 0) { + + // Delete the party, if has no member. + sprintf(tmp_sql,"DELETE FROM `%s` WHERE `party_id`='%d'",party_db, party_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle) ); + } + // printf("No member in party %d, break it \n",party_id); + memset(p, 0, sizeof(struct party)); + return 0; + } else { + // Update party information, if exists + + int i=0; + + for (i=0;i<MAX_PARTY;i++){ + + if (p->member[i].account_id>0){ + sprintf(tmp_sql,"UPDATE `%s` SET `party_id`='%d', `online`='%d' WHERE `account_id`='%d' AND `name`='%s'", + char_db, party_id, p->member[i].online, p->member[i].account_id,jstrescapecpy(t_member,p->member[i].name)); + //printf("%s",tmp_sql); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (update `char`)- %s\n", mysql_error(&mysql_handle) ); + } + } + } + + + sprintf(tmp_sql,"UPDATE `%s` SET `name`='%s', `exp`='%d', `item`='%d', `leader_id`=`leader_id` WHERE `party_id`='%d'", + party_db, t_name,p->exp,p->item,party_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (inset/update `party`)- %s\n", mysql_error(&mysql_handle) ); + } + + + // printf("- Update party %d information \n",party_id); + } + } else { + // Add new party, if not exist + int i = 0; + while (i<MAX_PARTY&&((p->member[i].account_id>0&&p->member[i].leader==0)||(p->member[i].account_id<0))) i++; + if (i<MAX_PARTY) leader_id = p->member[i].account_id; + sprintf(tmp_sql,"INSERT INTO `%s` (`party_id`, `name`, `exp`, `item`, `leader_id`) VALUES ('%d', '%s', '%d', '%d', '%d')", + party_db, party_id, t_name, p->exp, p->item,leader_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (inset/update `party`)- %s\n", mysql_error(&mysql_handle) ); + return 0; + } + + sprintf(tmp_sql,"UPDATE `%s` SET `party_id`='%d', `online`='1' WHERE `account_id`='%d' AND `name`='%s'", + char_db, party_id,leader_id, jstrescapecpy(t_member,p->member[i].name)); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (inset/update `party`)- %s\n", mysql_error(&mysql_handle) ); + } + + //printf("- Insert new party %d \n",party_id); + } + + printf("Party save success\n"); + return 0; + +} + +// Read party from mysql +int inter_party_fromsql(int party_id,struct party *p) +{ + int leader_id=0; + printf("(\033[1;64m%d\033[0m) Request load party - ",party_id); + + memset(p, 0, sizeof(struct party)); + + sprintf(tmp_sql,"SELECT `party_id`, `name`,`exp`,`item`, `leader_id` FROM `%s` WHERE `party_id`='%d'",party_db, party_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (select `party`)- %s\n", mysql_error(&mysql_handle) ); + return 0; + } + + sql_res = mysql_store_result(&mysql_handle) ; + if (sql_res!=NULL && mysql_num_rows(sql_res)>0) { + sql_row = mysql_fetch_row(sql_res); + // printf("- Read party %d from MySQL\n",party_id); + p->party_id = party_id; + strcpy(p->name, sql_row[1]); + p->exp = atoi(sql_row[2]); + p->item = atoi(sql_row[3]); + leader_id = atoi(sql_row[4]); + } else { + mysql_free_result(sql_res); + // printf("- Cannot find party %d \n",party_id); + return 0; + } + + mysql_free_result(sql_res); + + // Load members + sprintf(tmp_sql,"SELECT `account_id`, `name`,`base_level`,`last_map`,`online` FROM `%s` WHERE `party_id`='%d'",char_db, party_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (select `party`)- %s\n", mysql_error(&mysql_handle) ); + return 0; + } + sql_res = mysql_store_result(&mysql_handle) ; + if (sql_res!=NULL && mysql_num_rows(sql_res)>0) { + int i; + for(i=0;(sql_row = mysql_fetch_row(sql_res));i++){ + struct party_member *m = &p->member[i]; + m->account_id = atoi(sql_row[0]); + if (m->account_id == leader_id) m->leader = 1; else m->leader = 0; + strncpy(m->name,sql_row[1],sizeof(m->name)); + m->lv = atoi(sql_row[2]); + strncpy(m->map,sql_row[3],sizeof(m->map)); + m->online = atoi(sql_row[4]); + } + // printf("- %d members found in party %d \n",i,party_id); + } + mysql_free_result(sql_res); + + + printf("Party load success\n"); + return 0; + +} + +int inter_party_sql_init(){ + int i; + + //memory alloc + printf("interserver party memory initialize.... (%d byte)\n",sizeof(struct party)); + party_pt = calloc(sizeof(struct party), 1); + + sprintf(tmp_sql,"UPDATE `%s` SET `online`='0'", char_db); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (update `char`)- %s\n", mysql_error(&mysql_handle) ); + } + + sprintf (tmp_sql , "SELECT count(*) FROM `%s`",party_db); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle) ); + } + sql_res = mysql_store_result(&mysql_handle) ; + sql_row = mysql_fetch_row(sql_res); + printf("total party data -> '%s'.......\n",sql_row[0]); + i = atoi (sql_row[0]); + mysql_free_result(sql_res); + + if (i > 0) { + //set party_newid + sprintf (tmp_sql , "SELECT max(`party_id`) FROM `%s`", party_db); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle) ); + } + + sql_res = mysql_store_result(&mysql_handle) ; + + sql_row = mysql_fetch_row(sql_res); + party_newid = atoi (sql_row[0])+1; + mysql_free_result(sql_res); + } + + printf("set party_newid: %d.......\n",party_newid); + + return 0; +} + + +// Search for the party according to its name +struct party* search_partyname(char *str) +{ + struct party *p=NULL; + int leader_id = 0; + char t_name[24]; + + sprintf(tmp_sql,"SELECT `party_id`, `name`,`exp`,`item`,`leader_id` FROM `%s` WHERE `name`='%s'",party_db, jstrescapecpy(t_name,str)); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (select `party`)- %s\n", mysql_error(&mysql_handle) ); + } + sql_res = mysql_store_result(&mysql_handle) ; + if (sql_res==NULL || mysql_num_rows(sql_res)<=0) { mysql_free_result(sql_res); return p; } + sql_row = mysql_fetch_row(sql_res); + p = party_pt; + p->party_id = atoi(sql_row[0]); + strcpy(p->name, sql_row[1]); + p->exp = atoi(sql_row[2]); + p->item = atoi(sql_row[3]); + leader_id = atoi(sql_row[4]); + mysql_free_result(sql_res); + + // Load members + sprintf(tmp_sql,"SELECT `account_id`, `name`,`base_level`,`last_map`,`online` FROM `%s` WHERE `party_id`='%d'",char_db, p->party_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (select `party`)- %s\n", mysql_error(&mysql_handle) ); + return 0; + } + sql_res = mysql_store_result(&mysql_handle) ; + if (sql_res!=NULL && mysql_num_rows(sql_res)>0) { + int i; + for(i=0;(sql_row = mysql_fetch_row(sql_res));i++){ + struct party_member *m = &p->member[i]; + m->account_id = atoi(sql_row[0]); + if (m->account_id == leader_id) m->leader = 1; else m->leader = 0; + strncpy(m->name,sql_row[1],sizeof(m->name)); + m->lv = atoi(sql_row[2]); + strncpy(m->map,sql_row[3],sizeof(m->map)); + m->online = atoi(sql_row[4]); + } + printf("- %d members found in party %d \n",i,p->party_id); + } + mysql_free_result(sql_res); + + return p; +} + +// EXP公平分配できるかチェック +int party_check_exp_share(struct party *p) +{ + int i; + int maxlv=0,minlv=0x7fffffff; + for(i=0;i<MAX_PARTY;i++){ + int lv=p->member[i].lv; + if( p->member[i].online ){ + if( lv < minlv ) minlv=lv; + if( maxlv < lv ) maxlv=lv; + } + } + return (maxlv==0 || maxlv-minlv<=party_share_level); +} +// Is there any member in the party? +int party_check_empty(struct party *p) +{ + int i; + if (p==NULL||p->party_id==0) return 1; +// printf("party check empty %08X\n",(int)p); + for(i=0;i<MAX_PARTY;i++){ +// printf("%d acc=%d\n",i,p->member[i].account_id); + if(p->member[i].account_id>0){ + return 0; + } + } + // If there is no member, then break the party + mapif_party_broken(p->party_id,0); + inter_party_tosql(p->party_id,p); + return 1; +} + + +// Check if a member is in two party, not necessary :) +int party_check_conflict(int party_id,int account_id,char *nick) +{ + return 0; +} + +//------------------------------------------------------------------- +// map serverへの通信 + +// パーティ作成可否 +int mapif_party_created(int fd,int account_id,struct party *p) +{ + WFIFOW(fd,0)=0x3820; + WFIFOL(fd,2)=account_id; + if(p!=NULL){ + WFIFOB(fd,6)=0; + WFIFOL(fd,7)=p->party_id; + memcpy(WFIFOP(fd,11),p->name,24); + printf("int_party: created! %d %s\n",p->party_id,p->name); + }else{ + WFIFOB(fd,6)=1; + WFIFOL(fd,7)=0; + memcpy(WFIFOP(fd,11),"error",24); + } + WFIFOSET(fd,35); + return 0; +} + +// パーティ情報見つからず +int mapif_party_noinfo(int fd,int party_id) +{ + WFIFOW(fd,0)=0x3821; + WFIFOW(fd,2)=8; + WFIFOL(fd,4)=party_id; + WFIFOSET(fd,8); + printf("int_party: info not found %d\n",party_id); + return 0; +} +// パーティ情報まとめ送り +int mapif_party_info(int fd,struct party *p) +{ + unsigned char buf[1024]; + WBUFW(buf,0)=0x3821; + memcpy(buf+4,p,sizeof(struct party)); + WBUFW(buf,2)=4+sizeof(struct party); + if(fd<0) + mapif_sendall(buf,WBUFW(buf,2)); + else + mapif_send(fd,buf,WBUFW(buf,2)); +// printf("int_party: info %d %s\n",p->party_id,p->name); + return 0; +} +// パーティメンバ追加可否 +int mapif_party_memberadded(int fd,int party_id,int account_id,int flag) +{ + WFIFOW(fd,0)=0x3822; + WFIFOL(fd,2)=party_id; + WFIFOL(fd,6)=account_id; + WFIFOB(fd,10)=flag; + WFIFOSET(fd,11); + return 0; +} +// パーティ設定変更通知 +int mapif_party_optionchanged(int fd,struct party *p,int account_id,int flag) +{ + unsigned char buf[16]; + WBUFW(buf,0)=0x3823; + WBUFL(buf,2)=p->party_id; + WBUFL(buf,6)=account_id; + WBUFW(buf,10)=p->exp; + WBUFW(buf,12)=p->item; + WBUFB(buf,14)=flag; + if(flag==0) + mapif_sendall(buf,15); + else + mapif_send(fd,buf,15); + //printf("int_party: option changed %d %d %d %d %d\n",p->party_id,account_id,p->exp,p->item,flag); + return 0; +} +// パーティ脱退通知 +int mapif_party_leaved(int party_id,int account_id,char *name) +{ + unsigned char buf[64]; + WBUFW(buf,0)=0x3824; + WBUFL(buf,2)=party_id; + WBUFL(buf,6)=account_id; + memcpy(WBUFP(buf,10),name,24); + mapif_sendall(buf,34); + //printf("int_party: party leaved %d %d %s\n",party_id,account_id,name); + return 0; +} +// パーティマップ更新通知 +int mapif_party_membermoved(struct party *p,int idx) +{ + unsigned char buf[32]; + WBUFW(buf,0)=0x3825; + WBUFL(buf,2)=p->party_id; + WBUFL(buf,6)=p->member[idx].account_id; + memcpy(WBUFP(buf,10),p->member[idx].map,16); + WBUFB(buf,26)=p->member[idx].online; + WBUFW(buf,27)=p->member[idx].lv; + mapif_sendall(buf,29); + return 0; +} +// パーティ解散通知 +int mapif_party_broken(int party_id,int flag) +{ + unsigned char buf[16]; + WBUFW(buf,0)=0x3826; + WBUFL(buf,2)=party_id; + WBUFB(buf,6)=flag; + mapif_sendall(buf,7); + //printf("int_party: broken %d\n",party_id); + return 0; +} +// パーティ内発言 +int mapif_party_message(int party_id,int account_id,char *mes,int len) +{ + unsigned char buf[512]; + WBUFW(buf,0)=0x3827; + WBUFW(buf,2)=len+12; + WBUFL(buf,4)=party_id; + WBUFL(buf,8)=account_id; + memcpy(WBUFP(buf,12),mes,len); + mapif_sendall(buf,len+12); + return 0; +} + +//------------------------------------------------------------------- +// map serverからの通信 + + +// Create Party +int mapif_parse_CreateParty(int fd,int account_id,char *name,char *nick,char *map,int lv) +{ + struct party *p; + if( (p=search_partyname(name))!=NULL){ +// printf("int_party: same name party exists [%s]\n",name); + mapif_party_created(fd,account_id,NULL); + return 0; + } + p=party_pt; + if(p==NULL){ + printf("int_party: out of memory !\n"); + mapif_party_created(fd,account_id,NULL); + return 0; + } + memset(p,0,sizeof(struct party)); + p->party_id=party_newid++; + memcpy(p->name,name,24); + p->exp=0; + p->item=0; + p->member[0].account_id=account_id; + memcpy(p->member[0].name,nick,24); + memcpy(p->member[0].map,map,16); + p->member[0].leader=1; + p->member[0].online=1; + p->member[0].lv=lv; + + inter_party_tosql(p->party_id,p); + + mapif_party_created(fd,account_id,p); + mapif_party_info(fd,p); + + return 0; +} +// パーティ情報要求 +int mapif_parse_PartyInfo(int fd,int party_id) +{ + struct party *p = party_pt; + if(p==NULL){ + printf("int_party: out of memory !\n"); + return 0; + } + inter_party_fromsql(party_id, p); + + if(p->party_id >= 0) + mapif_party_info(fd,p); + else + mapif_party_noinfo(fd,party_id); + return 0; +} +// パーティ追加要求 +int mapif_parse_PartyAddMember(int fd,int party_id,int account_id,char *nick,char *map,int lv) +{ + struct party *p; + int i; + + p = party_pt; + if(p==NULL){ + printf("int_party: out of memory !\n"); + return 0; + } + inter_party_fromsql(party_id, p); + + if(p->party_id <= 0){ + mapif_party_memberadded(fd,party_id,account_id,1); + return 0; + } + + for(i=0;i<MAX_PARTY;i++){ + if(p->member[i].account_id==0){ + int flag=0; + + p->member[i].account_id=account_id; + memcpy(p->member[i].name,nick,24); + memcpy(p->member[i].map,map,16); + p->member[i].leader=0; + p->member[i].online=1; + p->member[i].lv=lv; + mapif_party_memberadded(fd,party_id,account_id,0); + mapif_party_info(-1,p); + + if( p->exp>0 && !party_check_exp_share(p) ){ + p->exp=0; + flag=0x01; + } + if(flag) + mapif_party_optionchanged(fd,p,0,0); + + inter_party_tosql(party_id, p); + return 0; + } + } + mapif_party_memberadded(fd,party_id,account_id,1); + //inter_party_tosql(party_id, p); + return 0; +} +// パーティー設定変更要求 +int mapif_parse_PartyChangeOption(int fd,int party_id,int account_id,int exp,int item) +{ + struct party *p; + int flag=0; + + p = party_pt; + if(p==NULL){ + printf("int_party: out of memory !\n"); + return 0; + } + + inter_party_fromsql(party_id, p); + + if(p->party_id <= 0){ + return 0; + } + + p->exp=exp; + if( exp>0 && !party_check_exp_share(p) ){ + flag|=0x01; + p->exp=0; + } + + p->item=item; + + mapif_party_optionchanged(fd,p,account_id,flag); + inter_party_tosql(party_id, p); + return 0; +} +// パーティ脱退要求 +int mapif_parse_PartyLeave(int fd,int party_id,int account_id) +{ + char t_member[24]; + struct party *p = party_pt; + if(p==NULL){ + printf("int_party: out of memory !\n"); + return 0; + } + + inter_party_fromsql(party_id, p); + + if(p->party_id >= 0){ + int i,j; + for(i=0;i<MAX_PARTY;i++){ + + if(p->member[i].account_id==account_id){ + //printf("p->member[i].account_id = %d , account_id = %d \n",p->member[i].account_id,account_id); + mapif_party_leaved(party_id,account_id,p->member[i].name); + + + + // Update char information, does the name need encoding? + sprintf(tmp_sql,"UPDATE `%s` SET `party_id`='0', `online`='1' WHERE `party_id`='%d' AND `name`='%s'", + char_db, party_id, jstrescapecpy(t_member,p->member[i].name)); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (update `char`)- %s\n", mysql_error(&mysql_handle) ); + } +// printf("Delete member %s from MySQL \n", p->member[i].name); + + if (p->member[i].leader==1){ + for(j=0;j<MAX_PARTY;j++) + { + //printf("j = %d , p->member[j].account_id = %d , p->member[j].account_id = %d \n",j,p->member[j].account_id,p->member[j].account_id); + if(p->member[j].account_id>0&&j!=i){ + mapif_party_leaved(party_id,p->member[j].account_id,p->member[j].name); + // Update char information, does the name need encoding? + sprintf(tmp_sql,"UPDATE `%s` SET `party_id`='0', `online`='1' WHERE `party_id`='%d' AND `name`='%s'", + char_db, party_id, jstrescapecpy(t_member,p->member[i].name)); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (update `char`)- %s\n", mysql_error(&mysql_handle) ); + } +// printf("Delete member %s from MySQL \n", p->member[j].name); + } + } + // Delete the party, if has no member. + sprintf(tmp_sql,"DELETE FROM `%s` WHERE `party_id`='%d'",party_db, party_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle) ); + } +// printf("Leader breaks party %d \n",party_id); + memset(p, 0, sizeof(struct party)); + }else memset(&p->member[i],0,sizeof(struct party_member)); + + break; + + } + } + if( party_check_empty(p)==0 ) + mapif_party_info(-1,p);// まだ人がいるのでデータ送信 + /* + else + inter_party_tosql(party_id,p); // Break the party if no member + */ + }else{ + sprintf(tmp_sql,"UPDATE `%s` SET `party_id`='0' WHERE `party_id`='%d' AND `account_id`='%d' AND `online`='1'", + char_db, party_id, account_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (update `char`)- %s\n", mysql_error(&mysql_handle) ); + } + } + return 0; +} +// When member goes to other map +int mapif_parse_PartyChangeMap(int fd,int party_id,int account_id,char *map,int online,int lv) +{ + struct party *p; + int i; + + p = party_pt; + if(p==NULL){ + printf("int_party: out of memory !\n"); + return 0; + } + inter_party_fromsql(party_id, p); + + if(p->party_id <= 0){ + return 0; + } + for(i=0;i<MAX_PARTY;i++){ + if(p->member[i].account_id==account_id){ + int flag=0; + + memcpy(p->member[i].map,map,16); + p->member[i].online=online; + p->member[i].lv=lv; + mapif_party_membermoved(p,i); + + if( p->exp>0 && !party_check_exp_share(p) ){ + p->exp=0; + flag=1; + } + if(flag) + mapif_party_optionchanged(fd,p,0,0); + break; + } + } + inter_party_tosql(party_id, p); + return 0; +} +// パーティ解散要求 +int mapif_parse_BreakParty(int fd,int party_id) +{ + struct party *p; + + p = party_pt; + if(p==NULL){ + printf("int_party: out of memory !\n"); + return 0; + } + + inter_party_fromsql(party_id, p); + + if(p->party_id <= 0){ + return 0; + } + inter_party_tosql(party_id,p); + + mapif_party_broken(fd,party_id); + return 0; +} +// パーティメッセージ送信 +int mapif_parse_PartyMessage(int fd,int party_id,int account_id,char *mes,int len) +{ + return mapif_party_message(party_id,account_id,mes,len); +} +// パーティチェック要求 +int mapif_parse_PartyCheck(int fd,int party_id,int account_id,char *nick) +{ + return party_check_conflict(party_id,account_id,nick); +} + +// map server からの通信 +// ・1パケットのみ解析すること +// ・パケット長データはinter.cにセットしておくこと +// ・パケット長チェックや、RFIFOSKIPは呼び出し元で行われるので行ってはならない +// ・エラーなら0(false)、そうでないなら1(true)をかえさなければならない +int inter_party_parse_frommap(int fd) +{ + switch(RFIFOW(fd,0)){ + case 0x3020: mapif_parse_CreateParty(fd,RFIFOL(fd,2),RFIFOP(fd,6),RFIFOP(fd,30),RFIFOP(fd,54),RFIFOW(fd,70)); break; + case 0x3021: mapif_parse_PartyInfo(fd,RFIFOL(fd,2)); break; + case 0x3022: mapif_parse_PartyAddMember(fd,RFIFOL(fd,2),RFIFOL(fd,6),RFIFOP(fd,10),RFIFOP(fd,34),RFIFOW(fd,50)); break; + case 0x3023: mapif_parse_PartyChangeOption(fd,RFIFOL(fd,2),RFIFOL(fd,6),RFIFOW(fd,10),RFIFOW(fd,12)); break; + case 0x3024: mapif_parse_PartyLeave(fd,RFIFOL(fd,2),RFIFOL(fd,6)); break; + case 0x3025: mapif_parse_PartyChangeMap(fd,RFIFOL(fd,2),RFIFOL(fd,6),RFIFOP(fd,10),RFIFOB(fd,26),RFIFOW(fd,27)); break; + case 0x3026: mapif_parse_BreakParty(fd,RFIFOL(fd,2)); break; + case 0x3027: mapif_parse_PartyMessage(fd,RFIFOL(fd,4),RFIFOL(fd,8),RFIFOP(fd,12),RFIFOW(fd,2)-12); break; + case 0x3028: mapif_parse_PartyCheck(fd,RFIFOL(fd,2),RFIFOL(fd,6),RFIFOP(fd,10)); break; + default: + return 0; + } + return 1; +} + +// サーバーから脱退要求(キャラ削除用) +int inter_party_leave(int party_id,int account_id) +{ + return mapif_parse_PartyLeave(-1,party_id,account_id); +} + + + diff --git a/misc/src/char_sql/int_party.h b/misc/src/char_sql/int_party.h new file mode 100644 index 0000000..04f71c8 --- /dev/null +++ b/misc/src/char_sql/int_party.h @@ -0,0 +1,8 @@ +#ifndef _INT_PARTY_H_ +#define _INT_PARTY_H_ + +int inter_party_parse_frommap(int fd); +int inter_party_sql_init(); +int inter_party_leave(int party_id,int account_id); + +#endif diff --git a/misc/src/char_sql/int_pet.c b/misc/src/char_sql/int_pet.c new file mode 100644 index 0000000..9b5566d --- /dev/null +++ b/misc/src/char_sql/int_pet.c @@ -0,0 +1,324 @@ +// +// original code from athena +// SQL conversion by Jioh L. Jung +// +#include "char.h" +#include "strlib.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +struct s_pet *pet_pt; +static int pet_newid = 100; + + +//--------------------------------------------------------- +int inter_pet_tosql(int pet_id, struct s_pet *p) { + //`pet` (`pet_id`, `class`,`name`,`account_id`,`char_id`,`level`,`egg_id`,`equip`,`intimate`,`hungry`,`rename_flag`,`incuvate`) + char t_name[100]; + + printf("request save pet: %d.......\n",pet_id); + + jstrescapecpy(t_name, p->name); + + if(p->hungry < 0) + p->hungry = 0; + else if(p->hungry > 100) + p->hungry = 100; + if(p->intimate < 0) + p->intimate = 0; + else if(p->intimate > 1000) + p->intimate = 1000; + sprintf(tmp_sql,"SELECT * FROM `%s` WHERE `pet_id`='%d'",pet_db, pet_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle) ); + } + sql_res = mysql_store_result(&mysql_handle) ; + if (sql_res!=NULL && mysql_num_rows(sql_res)>0) + //row reside -> updating + sprintf(tmp_sql, "UPDATE `%s` SET `class`='%d',`name`='%s',`account_id`='%d',`char_id`='%d',`level`='%d',`egg_id`='%d',`equip`='%d',`intimate`='%d',`hungry`='%d',`rename_flag`='%d',`incuvate`='%d' WHERE `pet_id`='%d'", + pet_db, p->class, t_name, p->account_id, p->char_id, p->level, p->egg_id, + p->equip, p->intimate, p->hungry, p->rename_flag, p->incuvate, p->pet_id); + else //no row -> insert + sprintf(tmp_sql,"INSERT INTO `%s` (`pet_id`, `class`,`name`,`account_id`,`char_id`,`level`,`egg_id`,`equip`,`intimate`,`hungry`,`rename_flag`,`incuvate`) VALUES ('%d', '%d', '%s', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d')", + pet_db, pet_id, p->class, t_name, p->account_id, p->char_id, p->level, p->egg_id, + p->equip, p->intimate, p->hungry, p->rename_flag, p->incuvate); + mysql_free_result(sql_res) ; //resource free + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (inset/update `pet`)- %s\n", mysql_error(&mysql_handle) ); + } + + printf("pet save success.......\n"); + return 0; +} + +int inter_pet_fromsql(int pet_id, struct s_pet *p){ + + printf("request load pet: %d.......\n",pet_id); + + memset(p, 0, sizeof(struct s_pet)); + + //`pet` (`pet_id`, `class`,`name`,`account_id`,`char_id`,`level`,`egg_id`,`equip`,`intimate`,`hungry`,`rename_flag`,`incuvate`) + + sprintf(tmp_sql,"SELECT `pet_id`, `class`,`name`,`account_id`,`char_id`,`level`,`egg_id`,`equip`,`intimate`,`hungry`,`rename_flag`,`incuvate` FROM `%s` WHERE `pet_id`='%d'",pet_db, pet_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (select `pet`)- %s\n", mysql_error(&mysql_handle) ); + return 0; + } + sql_res = mysql_store_result(&mysql_handle) ; + if (sql_res!=NULL && mysql_num_rows(sql_res)>0) { + sql_row = mysql_fetch_row(sql_res); + + p->pet_id = pet_id; + p->class = atoi(sql_row[1]); + memcpy(p->name, sql_row[2],24); + p->account_id = atoi(sql_row[3]); + p->char_id = atoi(sql_row[4]); + p->level = atoi(sql_row[5]); + p->egg_id = atoi(sql_row[6]); + p->equip = atoi(sql_row[7]); + p->intimate = atoi(sql_row[8]); + p->hungry = atoi(sql_row[9]); + p->rename_flag = atoi(sql_row[10]); + p->incuvate = atoi(sql_row[11]); + } + if(p->hungry < 0) + p->hungry = 0; + else if(p->hungry > 100) + p->hungry = 100; + if(p->intimate < 0) + p->intimate = 0; + else if(p->intimate > 1000) + p->intimate = 1000; + + mysql_free_result(sql_res); + + printf("pet load success.......\n"); + return 0; +} +//---------------------------------------------- + +int inter_pet_sql_init(){ + int i; + + //memory alloc + printf("interserver pet memory initialize.... (%d byte)\n",sizeof(struct s_pet)); + pet_pt = calloc(sizeof(struct s_pet), 1); + + sprintf (tmp_sql , "SELECT count(*) FROM `%s`", pet_db); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle) ); + exit(0); + } + sql_res = mysql_store_result(&mysql_handle) ; + sql_row = mysql_fetch_row(sql_res); + printf("total pet data -> '%s'.......\n",sql_row[0]); + i = atoi (sql_row[0]); + mysql_free_result(sql_res); + + if (i > 0) { + //set pet_newid + sprintf (tmp_sql , "SELECT max(`pet_id`) FROM `%s`",pet_db ); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle) ); + } + + sql_res = mysql_store_result(&mysql_handle) ; + + sql_row = mysql_fetch_row(sql_res); + pet_newid = atoi (sql_row[0]); + mysql_free_result(sql_res); + } + + printf("set pet_newid: %d.......\n",pet_newid); + + return 0; +} +//---------------------------------- +int inter_pet_delete(int pet_id){ + printf("request delete pet: %d.......\n",pet_id); + + sprintf(tmp_sql,"DELETE FROM `%s` WHERE `pet_id`='%d'",pet_db, pet_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle) ); + } + return 0; +} +//------------------------------------------------------ +int mapif_pet_created(int fd, int account_id, struct s_pet *p) +{ + WFIFOW(fd, 0) =0x3880; + WFIFOL(fd, 2) =account_id; + if(p!=NULL){ + WFIFOB(fd, 6)=0; + WFIFOL(fd, 7) =p->pet_id; + printf("int_pet: created! %d %s\n", p->pet_id, p->name); + }else{ + WFIFOB(fd, 6)=1; + WFIFOL(fd, 7)=0; + } + WFIFOSET(fd, 11); + + return 0; +} + +int mapif_pet_info(int fd, int account_id, struct s_pet *p){ + WFIFOW(fd, 0) =0x3881; + WFIFOW(fd, 2) =sizeof(struct s_pet) + 9; + WFIFOL(fd, 4) =account_id; + WFIFOB(fd, 8)=0; + memcpy(WFIFOP(fd, 9), p, sizeof(struct s_pet)); + WFIFOSET(fd, WFIFOW(fd, 2)); + + return 0; +} + +int mapif_pet_noinfo(int fd, int account_id){ + WFIFOW(fd, 0) =0x3881; + WFIFOW(fd, 2) =sizeof(struct s_pet) + 9; + WFIFOL(fd, 4) =account_id; + WFIFOB(fd, 8)=1; + memset(WFIFOP(fd, 9), 0, sizeof(struct s_pet)); + WFIFOSET(fd, WFIFOW(fd, 2)); + + return 0; +} + +int mapif_save_pet_ack(int fd, int account_id, int flag){ + WFIFOW(fd, 0) =0x3882; + WFIFOL(fd, 2) =account_id; + WFIFOB(fd, 6) =flag; + WFIFOSET(fd, 7); + + return 0; +} + +int mapif_delete_pet_ack(int fd, int flag){ + WFIFOW(fd, 0) =0x3883; + WFIFOB(fd, 2) =flag; + WFIFOSET(fd, 3); + + return 0; +} + +int mapif_create_pet(int fd, int account_id, int char_id, short pet_class, short pet_lv, short pet_egg_id, + short pet_equip, short intimate, short hungry, char rename_flag, char incuvate, char *pet_name){ + + memset(pet_pt, 0, sizeof(struct s_pet)); + pet_pt->pet_id = pet_newid++; + memcpy(pet_pt->name, pet_name, 24); + if(incuvate == 1) + pet_pt->account_id = pet_pt->char_id = 0; + else { + pet_pt->account_id = account_id; + pet_pt->char_id = char_id; + } + pet_pt->class = pet_class; + pet_pt->level = pet_lv; + pet_pt->egg_id = pet_egg_id; + pet_pt->equip = pet_equip; + pet_pt->intimate = intimate; + pet_pt->hungry = hungry; + pet_pt->rename_flag = rename_flag; + pet_pt->incuvate = incuvate; + + if(pet_pt->hungry < 0) + pet_pt->hungry = 0; + else if(pet_pt->hungry > 100) + pet_pt->hungry = 100; + if(pet_pt->intimate < 0) + pet_pt->intimate = 0; + else if(pet_pt->intimate > 1000) + pet_pt->intimate = 1000; + + inter_pet_tosql(pet_pt->pet_id,pet_pt); + + mapif_pet_created(fd, account_id, pet_pt); + + return 0; +} + +int mapif_load_pet(int fd, int account_id, int char_id, int pet_id){ + memset(pet_pt, 0, sizeof(struct s_pet)); + + inter_pet_fromsql(pet_id, pet_pt); + + if(pet_pt!=NULL) { + if(pet_pt->incuvate == 1) { + pet_pt->account_id = pet_pt->char_id = 0; + mapif_pet_info(fd, account_id, pet_pt); + } + else if(account_id == pet_pt->account_id && char_id == pet_pt->char_id) + mapif_pet_info(fd, account_id, pet_pt); + else + mapif_pet_noinfo(fd, account_id); + } + else + mapif_pet_noinfo(fd, account_id); + + return 0; +} + +int mapif_save_pet(int fd, int account_id, struct s_pet *data) { + //here process pet save request. + int len=RFIFOW(fd, 2); + if(sizeof(struct s_pet)!=len-8) { + printf("inter pet: data size error %d %d\n", sizeof(struct s_pet), len-8); + } + + else{ + if(data->hungry < 0) + data->hungry = 0; + else if(data->hungry > 100) + data->hungry = 100; + if(data->intimate < 0) + data->intimate = 0; + else if(data->intimate > 1000) + data->intimate = 1000; + inter_pet_tosql(data->pet_id,data); + mapif_save_pet_ack(fd, account_id, 0); + } + + return 0; +} + +int mapif_delete_pet(int fd, int pet_id){ + mapif_delete_pet_ack(fd, inter_pet_delete(pet_id)); + + return 0; +} + +int mapif_parse_CreatePet(int fd){ + mapif_create_pet(fd, RFIFOL(fd, 2), RFIFOL(fd, 6), RFIFOW(fd, 10), RFIFOW(fd, 12), RFIFOW(fd, 14), RFIFOW(fd, 16), RFIFOL(fd, 18), + RFIFOL(fd, 20), RFIFOB(fd, 22), RFIFOB(fd, 23), RFIFOP(fd, 24)); + return 0; +} + +int mapif_parse_LoadPet(int fd){ + mapif_load_pet(fd, RFIFOL(fd, 2), RFIFOL(fd, 6), RFIFOL(fd, 10)); + return 0; +} + +int mapif_parse_SavePet(int fd){ + mapif_save_pet(fd, RFIFOL(fd, 4), (struct s_pet *) RFIFOP(fd, 8)); + return 0; +} + +int mapif_parse_DeletePet(int fd){ + mapif_delete_pet(fd, RFIFOL(fd, 2)); + return 0; +} + +int inter_pet_parse_frommap(int fd){ + switch(RFIFOW(fd, 0)){ + case 0x3080: mapif_parse_CreatePet(fd); break; + case 0x3081: mapif_parse_LoadPet(fd); break; + case 0x3082: mapif_parse_SavePet(fd); break; + case 0x3083: mapif_parse_DeletePet(fd); break; + default: + return 0; + } + return 1; +} + diff --git a/misc/src/char_sql/int_pet.h b/misc/src/char_sql/int_pet.h new file mode 100644 index 0000000..b6e3f1b --- /dev/null +++ b/misc/src/char_sql/int_pet.h @@ -0,0 +1,12 @@ +#ifndef _INT_PET_H_ +#define _INT_PET_H_ + +int inter_pet_init(); +int inter_pet_save(); +int inter_pet_delete(int pet_id); + +int inter_pet_parse_frommap(int fd); +int inter_pet_sql_init(); +//extern char pet_txt[256]; + +#endif diff --git a/misc/src/char_sql/int_storage.c b/misc/src/char_sql/int_storage.c new file mode 100644 index 0000000..fdf85ae --- /dev/null +++ b/misc/src/char_sql/int_storage.c @@ -0,0 +1,366 @@ +// +// original code from athena +// SQL conversion by Jioh L. Jung +// +#include "char.h" +#include "itemdb.h" +#include <string.h> +#include <stdlib.h> + +#define STORAGE_MEMINC 16 + +// reset by inter_config_read() +struct storage *storage_pt=NULL; +struct guild_storage *guild_storage_pt=NULL; + + +// storage data -> DB conversion +int storage_tosql(int account_id,struct storage *p){ + int i; + int eqcount=1; + int noteqcount=1; + struct itemtemp mapitem; + for(i=0;i<MAX_STORAGE;i++){ + if(p->storage[i].nameid>0){ + if(itemdb_isequip(p->storage[i].nameid)==1){ + mapitem.equip[eqcount].flag=0; + mapitem.equip[eqcount].id = p->storage[i].id; + mapitem.equip[eqcount].nameid=p->storage[i].nameid; + mapitem.equip[eqcount].amount = p->storage[i].amount; + mapitem.equip[eqcount].equip = p->storage[i].equip; + mapitem.equip[eqcount].identify = p->storage[i].identify; + mapitem.equip[eqcount].refine = p->storage[i].refine; + mapitem.equip[eqcount].attribute = p->storage[i].attribute; + mapitem.equip[eqcount].card[0] = p->storage[i].card[0]; + mapitem.equip[eqcount].card[1] = p->storage[i].card[1]; + mapitem.equip[eqcount].card[2] = p->storage[i].card[2]; + mapitem.equip[eqcount].card[3] = p->storage[i].card[3]; + mapitem.equip[eqcount].broken = p->storage[i].broken; + eqcount++; + } + else if(itemdb_isequip(p->storage[i].nameid)==0){ + mapitem.notequip[noteqcount].flag=0; + mapitem.notequip[noteqcount].id = p->storage[i].id; + mapitem.notequip[noteqcount].nameid=p->storage[i].nameid; + mapitem.notequip[noteqcount].amount = p->storage[i].amount; + mapitem.notequip[noteqcount].equip = p->storage[i].equip; + mapitem.notequip[noteqcount].identify = p->storage[i].identify; + mapitem.notequip[noteqcount].refine = p->storage[i].refine; + mapitem.notequip[noteqcount].attribute = p->storage[i].attribute; + mapitem.notequip[noteqcount].card[0] = p->storage[i].card[0]; + mapitem.notequip[noteqcount].card[1] = p->storage[i].card[1]; + mapitem.notequip[noteqcount].card[2] = p->storage[i].card[2]; + mapitem.notequip[noteqcount].card[3] = p->storage[i].card[3]; + mapitem.notequip[noteqcount].broken = p->storage[i].broken; + noteqcount++; + } + } + } + + memitemdata_to_sql(mapitem, eqcount, noteqcount, account_id,TABLE_STORAGE); + + //printf ("storage dump to DB - id: %d (total: %d)\n", account_id, j); + return 0; +} + +// DB -> storage data conversion +int storage_fromsql(int account_id, struct storage *p){ + int i=0; + + memset(p,0,sizeof(struct storage)); //clean up memory + p->storage_amount = 0; + p->account_id = account_id; + + // storage {`account_id`/`id`/`nameid`/`amount`/`equip`/`identify`/`refine`/`attribute`/`card0`/`card1`/`card2`/`card3`} + sprintf(tmp_sql,"SELECT `id`,`nameid`,`amount`,`equip`,`identify`,`refine`,`attribute`,`card0`,`card1`,`card2`,`card3`,`broken` FROM `%s` WHERE `account_id`='%d'",storage_db, account_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle) ); + } + sql_res = mysql_store_result(&mysql_handle) ; + + if (sql_res) { + while((sql_row = mysql_fetch_row(sql_res))) { //start to fetch + p->storage[i].id= atoi(sql_row[0]); + p->storage[i].nameid= atoi(sql_row[1]); + p->storage[i].amount= atoi(sql_row[2]); + p->storage[i].equip= atoi(sql_row[3]); + p->storage[i].identify= atoi(sql_row[4]); + p->storage[i].refine= atoi(sql_row[5]); + p->storage[i].attribute= atoi(sql_row[6]); + p->storage[i].card[0]= atoi(sql_row[7]); + p->storage[i].card[1]= atoi(sql_row[8]); + p->storage[i].card[2]= atoi(sql_row[9]); + p->storage[i].card[3]= atoi(sql_row[10]); + p->storage[i].broken = atoi(sql_row[11]); + p->storage_amount = ++i; + } + mysql_free_result(sql_res); + } + + printf ("storage load complete from DB - id: %d (total: %d)\n", account_id, p->storage_amount); + return 1; +} + +// Save guild_storage data to sql +int guild_storage_tosql(int guild_id, struct guild_storage *p){ + int i; + int eqcount=1; + int noteqcount=1; + struct itemtemp mapitem; + for(i=0;i<MAX_GUILD_STORAGE;i++){ + if(p->storage[i].nameid>0){ + if(itemdb_isequip(p->storage[i].nameid)==1){ + mapitem.equip[eqcount].flag=0; + mapitem.equip[eqcount].id = p->storage[i].id; + mapitem.equip[eqcount].nameid=p->storage[i].nameid; + mapitem.equip[eqcount].amount = p->storage[i].amount; + mapitem.equip[eqcount].equip = p->storage[i].equip; + mapitem.equip[eqcount].identify = p->storage[i].identify; + mapitem.equip[eqcount].refine = p->storage[i].refine; + mapitem.equip[eqcount].attribute = p->storage[i].attribute; + mapitem.equip[eqcount].card[0] = p->storage[i].card[0]; + mapitem.equip[eqcount].card[1] = p->storage[i].card[1]; + mapitem.equip[eqcount].card[2] = p->storage[i].card[2]; + mapitem.equip[eqcount].card[3] = p->storage[i].card[3]; + mapitem.equip[eqcount].broken = p->storage[i].broken; + eqcount++; + } + else if(itemdb_isequip(p->storage[i].nameid)==0){ + mapitem.notequip[noteqcount].flag=0; + mapitem.notequip[noteqcount].id = p->storage[i].id; + mapitem.notequip[noteqcount].nameid=p->storage[i].nameid; + mapitem.notequip[noteqcount].amount = p->storage[i].amount; + mapitem.notequip[noteqcount].equip = p->storage[i].equip; + mapitem.notequip[noteqcount].identify = p->storage[i].identify; + mapitem.notequip[noteqcount].refine = p->storage[i].refine; + mapitem.notequip[noteqcount].attribute = p->storage[i].attribute; + mapitem.notequip[noteqcount].card[0] = p->storage[i].card[0]; + mapitem.notequip[noteqcount].card[1] = p->storage[i].card[1]; + mapitem.notequip[noteqcount].card[2] = p->storage[i].card[2]; + mapitem.notequip[noteqcount].card[3] = p->storage[i].card[3]; + mapitem.notequip[noteqcount].broken = p->storage[i].broken; + noteqcount++; + } + } + } + + memitemdata_to_sql(mapitem, eqcount, noteqcount, guild_id,TABLE_GUILD_STORAGE); + + printf ("guild storage save to DB - id: %d (total: %d)\n", guild_id,i); + return 0; +} + +// Load guild_storage data to mem +int guild_storage_fromsql(int guild_id, struct guild_storage *p){ + int i=0; + struct guild_storage *gs=guild_storage_pt; + p=gs; + + memset(p,0,sizeof(struct guild_storage)); //clean up memory + p->storage_amount = 0; + p->guild_id = guild_id; + + // storage {`guild_id`/`id`/`nameid`/`amount`/`equip`/`identify`/`refine`/`attribute`/`card0`/`card1`/`card2`/`card3`} + sprintf(tmp_sql,"SELECT `id`,`nameid`,`amount`,`equip`,`identify`,`refine`,`attribute`,`card0`,`card1`,`card2`,`card3`,`broken` FROM `%s` WHERE `guild_id`='%d'",guild_storage_db, guild_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle) ); + } + sql_res = mysql_store_result(&mysql_handle) ; + + if (sql_res) { + while((sql_row = mysql_fetch_row(sql_res))) { //start to fetch + p->storage[i].id= atoi(sql_row[0]); + p->storage[i].nameid= atoi(sql_row[1]); + p->storage[i].amount= atoi(sql_row[2]); + p->storage[i].equip= atoi(sql_row[3]); + p->storage[i].identify= atoi(sql_row[4]); + p->storage[i].refine= atoi(sql_row[5]); + p->storage[i].attribute= atoi(sql_row[6]); + p->storage[i].card[0]= atoi(sql_row[7]); + p->storage[i].card[1]= atoi(sql_row[8]); + p->storage[i].card[2]= atoi(sql_row[9]); + p->storage[i].card[3]= atoi(sql_row[10]); + p->storage[i].broken = atoi(sql_row[11]); + p->storage_amount = ++i; + } + mysql_free_result(sql_res); + } + printf ("guild storage load complete from DB - id: %d (total: %d)\n", guild_id, p->storage_amount); + return 0; +} + +//--------------------------------------------------------- +// storage data initialize +int inter_storage_sql_init(){ + + //memory alloc + printf("interserver storage memory initialize....(%d byte)\n",sizeof(struct storage)); + storage_pt=calloc(sizeof(struct storage), 1); + guild_storage_pt=calloc(sizeof(struct guild_storage), 1); + memset(storage_pt,0,sizeof(struct storage)); + memset(guild_storage_pt,0,sizeof(struct guild_storage)); + + return 1; +} +// 倉庫データ削除 +int inter_storage_delete(int account_id) +{ + sprintf(tmp_sql, "DELETE FROM `%s` WHERE `account_id`='%d'",storage_db, account_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (delete `storage`)- %s\n", mysql_error(&mysql_handle) ); + } + return 0; +} +int inter_guild_storage_delete(int guild_id) +{ + sprintf(tmp_sql, "DELETE FROM `%s` WHERE `guild_id`='%d'",guild_storage_db, guild_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (delete `guild_storage`)- %s\n", mysql_error(&mysql_handle) ); + } + return 0; +} + +//--------------------------------------------------------- +// packet from map server + +// recive packet about storage data +int mapif_load_storage(int fd,int account_id){ + //load from DB + storage_fromsql(account_id, storage_pt); + WFIFOW(fd,0)=0x3810; + WFIFOW(fd,2)=sizeof(struct storage)+8; + WFIFOL(fd,4)=account_id; + memcpy(WFIFOP(fd,8),storage_pt,sizeof(struct storage)); + WFIFOSET(fd,WFIFOW(fd,2)); + return 0; +} +// send ack to map server which is "storage data save ok." +int mapif_save_storage_ack(int fd,int account_id){ + WFIFOW(fd,0)=0x3811; + WFIFOL(fd,2)=account_id; + WFIFOB(fd,6)=0; + WFIFOSET(fd,7); + return 0; +} + +int mapif_load_guild_storage(int fd,int account_id,int guild_id) +{ + int guild_exist=0; + WFIFOW(fd,0)=0x3818; + + // Check if guild exists, I may write a function for this later, coz I use it several times. + //printf("- Check if guild %d exists\n",g->guild_id); + sprintf(tmp_sql, "SELECT count(*) FROM `%s` WHERE `guild_id`='%d'",guild_db, guild_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (delete `guild`)- %s\n", mysql_error(&mysql_handle) ); + } + sql_res = mysql_store_result(&mysql_handle) ; + if (sql_res!=NULL && mysql_num_rows(sql_res)>0) { + sql_row = mysql_fetch_row(sql_res); + guild_exist = atoi (sql_row[0]); + //printf("- Check if guild %d exists : %s\n",g->guild_id,((guild_exist==0)?"No":"Yes")); + } + mysql_free_result(sql_res) ; //resource free + + if(guild_exist==1) { + guild_storage_fromsql(guild_id,guild_storage_pt); + WFIFOW(fd,2)=sizeof(struct guild_storage)+12; + WFIFOL(fd,4)=account_id; + WFIFOL(fd,8)=guild_id; + memcpy(WFIFOP(fd,12),guild_storage_pt,sizeof(struct guild_storage)); + } + else { + WFIFOW(fd,2)=12; + WFIFOL(fd,4)=account_id; + WFIFOL(fd,8)=0; + } + WFIFOSET(fd,WFIFOW(fd,2)); + + return 0; +} +int mapif_save_guild_storage_ack(int fd,int account_id,int guild_id,int fail) +{ + WFIFOW(fd,0)=0x3819; + WFIFOL(fd,2)=account_id; + WFIFOL(fd,6)=guild_id; + WFIFOB(fd,10)=fail; + WFIFOSET(fd,11); + return 0; +} + +//--------------------------------------------------------- +// packet from map server + +// recive request about storage data +int mapif_parse_LoadStorage(int fd){ + mapif_load_storage(fd,RFIFOL(fd,2)); + return 0; +} +// storage data recive and save +int mapif_parse_SaveStorage(int fd){ + int account_id=RFIFOL(fd,4); + int len=RFIFOW(fd,2); + + if(sizeof(struct storage)!=len-8){ + printf("inter storage: data size error %d %d\n",sizeof(struct storage),len-8); + }else{ + memcpy(&storage_pt[0],RFIFOP(fd,8),sizeof(struct storage)); + storage_tosql(account_id, storage_pt); + mapif_save_storage_ack(fd,account_id); + } + return 0; +} + +int mapif_parse_LoadGuildStorage(int fd) +{ + mapif_load_guild_storage(fd,RFIFOL(fd,2),RFIFOL(fd,6)); + return 0; +} + +int mapif_parse_SaveGuildStorage(int fd) +{ + int guild_exist=0; + int guild_id=RFIFOL(fd,8); + int len=RFIFOW(fd,2); + if(sizeof(struct guild_storage)!=len-12){ + printf("inter storage: data size error %d %d\n",sizeof(struct guild_storage),len-12); + } + else { + // Check if guild exists, I may write a function for this later, coz I use it several times. + //printf("- Check if guild %d exists\n",g->guild_id); + sprintf(tmp_sql, "SELECT count(*) FROM `%s` WHERE `guild_id`='%d'",guild_db, guild_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (delete `guild`)- %s\n", mysql_error(&mysql_handle) ); + } + sql_res = mysql_store_result(&mysql_handle) ; + if (sql_res!=NULL && mysql_num_rows(sql_res)>0) { + sql_row = mysql_fetch_row(sql_res); + guild_exist = atoi (sql_row[0]); + //printf("- Check if guild %d exists : %s\n",g->guild_id,((guild_exist==0)?"No":"Yes")); + } + mysql_free_result(sql_res) ; //resource free + + if(guild_exist==1) { + memcpy(guild_storage_pt,RFIFOP(fd,12),sizeof(struct guild_storage)); + guild_storage_tosql(guild_id,guild_storage_pt); + mapif_save_guild_storage_ack(fd,RFIFOL(fd,4),guild_id,0); + } + else + mapif_save_guild_storage_ack(fd,RFIFOL(fd,4),guild_id,1); + } + return 0; +} + + +int inter_storage_parse_frommap(int fd){ + switch(RFIFOW(fd,0)){ + case 0x3010: mapif_parse_LoadStorage(fd); break; + case 0x3011: mapif_parse_SaveStorage(fd); break; + case 0x3018: mapif_parse_LoadGuildStorage(fd); break; + case 0x3019: mapif_parse_SaveGuildStorage(fd); break; + default: + return 0; + } + return 1; +} + diff --git a/misc/src/char_sql/int_storage.h b/misc/src/char_sql/int_storage.h new file mode 100644 index 0000000..f9f37db --- /dev/null +++ b/misc/src/char_sql/int_storage.h @@ -0,0 +1,13 @@ +#ifndef _INT_STORAGE_H_ +#define _INT_STORAGE_H_ + +int inter_storage_sql_init(); +int inter_storage_delete(int account_id); +int inter_guild_storage_delete(int guild_id); + +int inter_storage_parse_frommap(int fd); + + +//extern char storage_txt[256]; + +#endif diff --git a/misc/src/char_sql/inter.c b/misc/src/char_sql/inter.c new file mode 100644 index 0000000..c8fa9b4 --- /dev/null +++ b/misc/src/char_sql/inter.c @@ -0,0 +1,573 @@ +// +// original code from athena +// SQL conversion by Jioh L. Jung +// + +#include <string.h> +#include <stdlib.h> + +#include "char.h" +#include "strlib.h" +#include "inter.h" +#include "int_party.h" +#include "int_guild.h" +#include "int_storage.h" +#include "int_pet.h" +#include "lock.h" + +#define WISDATA_TTL (60*1000) // Wisデータの生存時間(60秒) +#define WISDELLIST_MAX 256 // Wisデータ削除リストの要素数 + + +struct accreg { + int account_id,reg_num; + struct global_reg reg[ACCOUNT_REG_NUM]; +}; + +static struct accreg *accreg_pt; + + +int party_share_level = 10; +MYSQL mysql_handle; +MYSQL_RES* sql_res ; +MYSQL_ROW sql_row ; +int sql_fields, sql_cnt; +char tmp_sql[65535]; + +MYSQL lmysql_handle; +char tmp_lsql[65535]; +MYSQL_RES* lsql_res ; +MYSQL_ROW lsql_row ; + +int char_server_port = 3306; +char char_server_ip[32] = "127.0.0.1"; +char char_server_id[32] = "ragnarok"; +char char_server_pw[32] = "ragnarok"; +char char_server_db[32] = "ragnarok"; + +int login_server_port = 3306; +char login_server_ip[32] = "127.0.0.1"; +char login_server_id[32] = "ragnarok"; +char login_server_pw[32] = "ragnarok"; +char login_server_db[32] = "ragnarok"; + +// sending packet list +int inter_send_packet_length[]={ + -1,-1,27, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + -1, 7, 0, 0, 0, 0, 0, 0, -1,11, 0, 0, 0, 0, 0, 0, + 35,-1,11,15, 34,29, 7,-1, 0, 0, 0, 0, 0, 0, 0, 0, + 10,-1,15, 0, 79,19, 7,-1, 0,-1,-1,-1, 14,67,186,-1, + 9, 9,-1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 11,-1, 7, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +}; +// recv. packet list +int inter_recv_packet_length[]={ + -1,-1, 7, 0, -1, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 6,-1, 0, 0, 0, 0, 0, 0, 10,-1, 0, 0, 0, 0, 0, 0, + 72, 6,52,14, 10,29, 6,-1, 34, 0, 0, 0, 0, 0, 0, 0, + -1, 6,-1, 0, 55,19, 6,-1, 14,-1,-1,-1, 14,19,186,-1, + 5, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 48,14,-1, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +}; + +struct WisData { + int id,fd,count,len; + unsigned long tick; + unsigned char src[24],dst[24],msg[512]; +}; +static struct dbt * wis_db = NULL; +static int wis_dellist[WISDELLIST_MAX], wis_delnum; + +//-------------------------------------------------------- +// Save account_reg to sql (type=2) +int inter_accreg_tosql(int account_id,struct accreg *reg){ + + int j; + char temp_str[32]; + if (account_id<=0) return 0; + reg->account_id=account_id; + + //`global_reg_value` (`type`, `account_id`, `char_id`, `str`, `value`) + sprintf(tmp_sql,"DELETE FROM `%s` WHERE `type`=2 AND `account_id`='%d'",reg_db, account_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (delete `global_reg_value`)- %s\n", mysql_error(&mysql_handle) ); + } + + if (reg->reg_num<=0) return 0; + + for(j=0;j<reg->reg_num;j++){ + if(reg->reg[j].str != NULL){ + sprintf(tmp_sql,"INSERT INTO `%s` (`type`, `account_id`, `str`, `value`) VALUES (2,'%d', '%s','%d')", + reg_db, reg->account_id, jstrescapecpy(temp_str,reg->reg[j].str), reg->reg[j].value); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (insert `global_reg_value`)- %s\n", mysql_error(&mysql_handle) ); + } + } + } + return 0; +} + +// Load account_reg from sql (type=2) +int inter_accreg_fromsql(int account_id,struct accreg *reg) +{ + int j=0; + if (reg==NULL) return 0; + memset(reg, 0, sizeof(struct accreg)); + reg->account_id=account_id; + + //`global_reg_value` (`type`, `account_id`, `char_id`, `str`, `value`) + sprintf (tmp_sql, "SELECT `str`, `value` FROM `%s` WHERE `type`=2 AND `account_id`='%d'",reg_db, reg->account_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (select `global_reg_value`)- %s\n", mysql_error(&mysql_handle) ); + } + sql_res = mysql_store_result(&mysql_handle); + + if (sql_res) { + for(j=0;(sql_row = mysql_fetch_row(sql_res));j++){ + memcpy(reg->reg[j].str, sql_row[0],32); + reg->reg[j].value = atoi(sql_row[1]); + } + mysql_free_result(sql_res); + } + reg->reg_num=j; + return 0; +} + +// Initialize +int inter_accreg_sql_init() +{ + CREATE(accreg_pt, struct accreg, 1); + return 0; + +} + +/*========================================== + * read config file + *------------------------------------------ + */ +int inter_config_read(const char *cfgName) { + printf ("start reading interserver configuration: %s\n",cfgName); + int i; + char line[1024], w1[1024], w2[1024]; + FILE *fp; + + fp=fopen(cfgName,"r"); + if(fp==NULL){ + printf("file not found: %s\n", cfgName); + return 1; + } + while(fgets(line, 1020, fp)){ + i=sscanf(line,"%[^:]: %[^\r\n]",w1,w2); + if(i!=2) + continue; + + if(strcmpi(w1,"char_server_ip")==0){ + strcpy(char_server_ip, w2); + printf ("set char_server_ip : %s\n",w2); + } + else if(strcmpi(w1,"char_server_port")==0){ + char_server_port=atoi(w2); + printf ("set char_server_port : %s\n",w2); + } + else if(strcmpi(w1,"char_server_id")==0){ + strcpy(char_server_id, w2); + printf ("set char_server_id : %s\n",w2); + } + else if(strcmpi(w1,"char_server_pw")==0){ + strcpy(char_server_pw, w2); + printf ("set char_server_pw : %s\n",w2); + } + else if(strcmpi(w1,"char_server_db")==0){ + strcpy(char_server_db, w2); + printf ("set char_server_db : %s\n",w2); + } + //Logins information to be read from the inter_athena.conf + //for character deletion (checks email in the loginDB) + + else if(strcmpi(w1,"login_server_ip")==0){ + strcpy(login_server_ip, w2); + printf ("set login_server_ip : %s\n",w2); + } + else if(strcmpi(w1,"login_server_port")==0){ + login_server_port=atoi(w2); + printf ("set login_server_port : %s\n",w2); + } + else if(strcmpi(w1,"login_server_id")==0){ + strcpy(login_server_id, w2); + printf ("set login_server_id : %s\n",w2); + } + else if(strcmpi(w1,"login_server_pw")==0){ + strcpy(login_server_pw, w2); + printf ("set login_server_pw : %s\n",w2); + } + else if(strcmpi(w1,"login_server_db")==0){ + strcpy(login_server_db, w2); + printf ("set login_server_db : %s\n",w2); + } + else if(strcmpi(w1,"party_share_level")==0){ + party_share_level=atoi(w2); + if(party_share_level < 0) party_share_level = 0; + }else if(strcmpi(w1,"import")==0){ + inter_config_read(w2); + } + else if(strcmpi(w1,"login_server_db")==0){ + strcpy(login_server_db, w2); + printf ("set login_server_db : %s\n",w2); + } + } + fclose(fp); + + printf ("success reading interserver configuration\n"); + + return 0; +} + +// Save interlog into sql +int inter_log(char *fmt,...) +{ + char str[255]; + char temp_str[255]; + va_list ap; + va_start(ap,fmt); + + vsprintf(str,fmt,ap); + sprintf(tmp_sql,"INSERT INTO `%s` (`time`, `log`) VALUES (NOW(), '%s')",interlog_db, jstrescapecpy(temp_str,str)); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (insert `interlog`)- %s\n", mysql_error(&mysql_handle) ); + } + + va_end(ap); + return 0; +} + + +// initialize +int inter_init(const char *file) +{ + //int i; + + printf ("interserver initialize...\n"); + inter_config_read(file); + + //DB connection initialized + mysql_init(&mysql_handle); + printf("Connect Character DB server.... (Character Server)\n"); + if(!mysql_real_connect(&mysql_handle, char_server_ip, char_server_id, char_server_pw, + char_server_db ,char_server_port, (char *)NULL, 0)) { + //pointer check + printf("%s\n",mysql_error(&mysql_handle)); + exit(1); + } + else { + printf ("Connect Success! (Character Server)\n"); + } + + mysql_init(&lmysql_handle); + printf("Connect Character DB server.... (login server)\n"); + if(!mysql_real_connect(&lmysql_handle, login_server_ip, login_server_id, login_server_pw, + login_server_db ,login_server_port, (char *)NULL, 0)) { + //pointer check + printf("%s\n",mysql_error(&lmysql_handle)); + exit(1); + }else { + printf ("Connect Success! (Login Server)"); + } + wis_db = numdb_init(); + inter_guild_sql_init(); + inter_storage_sql_init(); + inter_party_sql_init(); + + inter_pet_sql_init(); + inter_accreg_sql_init(); + + //printf ("interserver timer initializing : %d sec...\n",autosave_interval); + //i=add_timer_interval(gettick()+autosave_interval,inter_save_timer,0,0,autosave_interval); + + return 0; +} + +int inter_mapif_init(int fd) { + inter_guild_mapif_init(fd); + + return 0; +} + + +//-------------------------------------------------------- + +// GM message sending +int mapif_GMmessage(unsigned char *mes, int len) { + unsigned char buf[len]; + + WBUFW(buf, 0) = 0x3800; + WBUFW(buf, 2) = len; + memcpy(WBUFP(buf, 4), mes, len-4); + mapif_sendall(buf, len); + printf("\033[1;34m inter server: GM[len:%d] - '%s' \033[0m\n", len, mes); + return 0; +} + +// Wis sending +int mapif_wis_message(struct WisData *wd) { + unsigned char buf[56 + wd->len]; + + WBUFW(buf, 0) = 0x3801; + WBUFW(buf, 2) = 56 +wd->len; + WBUFL(buf, 4) = wd->id; + memcpy(WBUFP(buf, 8), wd->src, 24); + memcpy(WBUFP(buf,32), wd->dst, 24); + memcpy(WBUFP(buf,56), wd->msg, wd->len); + wd->count = mapif_sendall(buf,WBUFW(buf,2)); + + return 0; +} +// Wis sending result +int mapif_wis_end(struct WisData *wd,int flag) +{ + unsigned char buf[27]; + + WBUFW(buf, 0)=0x3802; + memcpy(WBUFP(buf, 2),wd->src,24); + WBUFB(buf,26)=flag; + mapif_send(wd->fd,buf,27); +// printf("inter server wis_end %d\n",flag); + return 0; +} + +int mapif_account_reg(int fd,unsigned char *src) +{ + unsigned char buf[WBUFW(src,2)]; + memcpy(WBUFP(buf,0),src,WBUFW(src,2)); + WBUFW(buf, 0)=0x3804; + mapif_sendallwos(fd,buf,WBUFW(buf,2)); + return 0; +} + +// Send the requested account_reg +int mapif_account_reg_reply(int fd,int account_id) +{ + struct accreg *reg=accreg_pt; + inter_accreg_fromsql(account_id,reg); + + WFIFOW(fd,0)=0x3804; + WFIFOL(fd,4)=account_id; + if(reg->reg_num==0){ + WFIFOW(fd,2)=8; + }else{ + int j,p; + for(j=0,p=8;j<reg->reg_num;j++,p+=36){ + memcpy(WFIFOP(fd,p),reg->reg[j].str,32); + WFIFOL(fd,p+32)=reg->reg[j].value; + } + WFIFOW(fd,2)=p; + } + WFIFOSET(fd,WFIFOW(fd,2)); + return 0; +} + +//-------------------------------------------------------- + +// Existence check of WISP data +int check_ttl_wisdata_sub(void *key, void *data, va_list ap) { + unsigned long tick; + struct WisData *wd = (struct WisData *)data; + tick = va_arg(ap, unsigned long); + + if (DIFF_TICK(tick, wd->tick) > WISDATA_TTL && wis_delnum < WISDELLIST_MAX) + wis_dellist[wis_delnum++] = wd->id; + + return 0; +} + +int check_ttl_wisdata() { + unsigned long tick = gettick(); + int i; + + do { + wis_delnum = 0; + numdb_foreach(wis_db, check_ttl_wisdata_sub, tick); + for(i = 0; i < wis_delnum; i++) { + struct WisData *wd = numdb_search(wis_db, wis_dellist[i]); + printf("inter: wis data id=%d time out : from %s to %s\n", wd->id, wd->src, wd->dst); + // removed. not send information after a timeout. Just no answer for the player + //mapif_wis_end(wd, 1); // flag: 0: success to send wisper, 1: target character is not loged in?, 2: ignored by target + numdb_erase(wis_db, wd->id); + free(wd); + } + } while(wis_delnum >= WISDELLIST_MAX); + + return 0; +} + +//-------------------------------------------------------- + +// GM message sending +int mapif_parse_GMmessage(int fd) +{ + mapif_GMmessage(RFIFOP(fd, 4), RFIFOW(fd, 2)); + return 0; +} + + +// Wisp/page request to send +int mapif_parse_WisRequest(int fd) { + struct WisData* wd; + static int wisid = 0; + + if (RFIFOW(fd,2)-52 >= sizeof(wd->msg)) { + printf("inter: Wis message size too long.\n"); + return 0; + } else if (RFIFOW(fd,2)-52 <= 0) { // normaly, impossible, but who knows... + printf("inter: Wis message doesn't exist.\n"); + return 0; + } + sprintf (tmp_sql, "SELECT `name` FROM `%s` WHERE `char_id`='%d'",char_db, (int) RFIFOP(fd,28)); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle) ); + } + sql_res = mysql_store_result(&mysql_handle); + + // search if character exists before to ask all map-servers + if (!(sql_row = mysql_fetch_row(sql_res))) { + unsigned char buf[27]; + WBUFW(buf, 0) = 0x3802; + memcpy(WBUFP(buf, 2), RFIFOP(fd, 4), 24); + WBUFB(buf,26) = 1; // flag: 0: success to send wisper, 1: target character is not loged in?, 2: ignored by target + mapif_send(fd, buf, 27); + // Character exists. So, ask all map-servers + } else { + // to be sure of the correct name, rewrite it + memset(RFIFOP(fd,28), 0, 24); + strncpy(RFIFOP(fd,28), sql_row[0], 24); + // if source is destination, don't ask other servers. + if (strcmp(RFIFOP(fd,4),RFIFOP(fd,28)) == 0) { + unsigned char buf[27]; + WBUFW(buf, 0) = 0x3802; + memcpy(WBUFP(buf, 2), RFIFOP(fd, 4), 24); + WBUFB(buf,26) = 1; // flag: 0: success to send wisper, 1: target character is not loged in?, 2: ignored by target + mapif_send(fd, buf, 27); + } else { + + CREATE(wd, struct WisData, 1); + + // Whether the failure of previous wisp/page transmission (timeout) + check_ttl_wisdata(); + + wd->id = ++wisid; + wd->fd = fd; + wd->len= RFIFOW(fd,2)-52; + memcpy(wd->src, RFIFOP(fd, 4), 24); + memcpy(wd->dst, RFIFOP(fd,28), 24); + memcpy(wd->msg, RFIFOP(fd,52), wd->len); + wd->tick = gettick(); + numdb_insert(wis_db, wd->id, wd); + mapif_wis_message(wd); + } + } + + return 0; +} + + +// Wisp/page transmission result +int mapif_parse_WisReply(int fd) { + int id = RFIFOL(fd,2), flag = RFIFOB(fd,6); + struct WisData *wd = numdb_search(wis_db, id); + + if (wd == NULL) + return 0; // This wisp was probably suppress before, because it was timeout of because of target was found on another map-server + + if ((--wd->count) <= 0 || flag != 1) { + mapif_wis_end(wd, flag); // flag: 0: success to send wisper, 1: target character is not loged in?, 2: ignored by target + numdb_erase(wis_db, id); + free(wd); + } + + return 0; +} + + +// Save account_reg into sql (type=2) +int mapif_parse_AccReg(int fd) +{ + int j,p; + struct accreg *reg=accreg_pt; + int account_id = RFIFOL(fd,4); + memset(accreg_pt,0,sizeof(struct accreg)); + + for(j=0,p=8;j<ACCOUNT_REG_NUM && p<RFIFOW(fd,2);j++,p+=36){ + memcpy(reg->reg[j].str,RFIFOP(fd,p),32); + reg->reg[j].value=RFIFOL(fd,p+32); + } + reg->reg_num=j; + + inter_accreg_tosql(account_id,reg); + mapif_account_reg(fd,RFIFOP(fd,0)); // Send confirm message to map + return 0; +} + +// Request the value of account_reg +int mapif_parse_AccRegRequest(int fd) +{ +// printf("mapif: accreg request\n"); + return mapif_account_reg_reply(fd,RFIFOL(fd,2)); +} + + + +//-------------------------------------------------------- +int inter_parse_frommap(int fd) +{ + int cmd=RFIFOW(fd,0); + int len=0; + + // inter鯖管轄かを調べる + if(cmd<0x3000 || cmd>=0x3000+( sizeof(inter_recv_packet_length)/ + sizeof(inter_recv_packet_length[0]) ) ) + return 0; + + // パケット長を調べる + if( (len=inter_check_length(fd,inter_recv_packet_length[cmd-0x3000]))==0 ) + return 2; + + switch(cmd){ + case 0x3000: mapif_parse_GMmessage(fd); break; + case 0x3001: mapif_parse_WisRequest(fd); break; + case 0x3002: mapif_parse_WisReply(fd); break; + case 0x3004: mapif_parse_AccReg(fd); break; + case 0x3005: mapif_parse_AccRegRequest(fd); break; + default: + if( inter_party_parse_frommap(fd) ) + break; + if( inter_guild_parse_frommap(fd) ) + break; + if( inter_storage_parse_frommap(fd) ) + break; + if( inter_pet_parse_frommap(fd) ) + break; + return 0; + } + RFIFOSKIP(fd, len ); + return 1; +} + +// RFIFO check +int inter_check_length(int fd, int length) +{ + if(length==-1){ // v-len packet + if(RFIFOREST(fd)<4) // packet not yet + return 0; + length = RFIFOW(fd, 2); + } + + if(RFIFOREST(fd)<length) // packet not yet + return 0; + + return length; +} diff --git a/misc/src/char_sql/inter.h b/misc/src/char_sql/inter.h new file mode 100644 index 0000000..841d534 --- /dev/null +++ b/misc/src/char_sql/inter.h @@ -0,0 +1,44 @@ +#ifndef _INTER_H_ +#define _INTER_H_ + +int inter_init(const char *file); +int inter_parse_frommap(int fd); +int inter_mapif_init(int fd); + + +int inter_check_length(int fd,int length); + +int inter_log(char *fmt,...); + +#define inter_cfgName "conf/inter_athena.conf" + +extern int party_share_level; +extern char inter_log_filename[1024]; + +//add include for DBMS(mysql) +#include <mysql.h> + +extern MYSQL mysql_handle; +extern char tmp_sql[65535]; +extern MYSQL_RES* sql_res ; +extern MYSQL_ROW sql_row ; +extern int sql_cnt; + +extern MYSQL lmysql_handle; +extern char tmp_lsql[65535]; +extern MYSQL_RES* lsql_res ; +extern MYSQL_ROW lsql_row ; + +extern int char_server_port; +extern char char_server_ip[32]; +extern char char_server_id[32]; +extern char char_server_pw[32]; +extern char char_server_db[32]; + +extern int login_db_server_port; +extern char login_db_server_ip[32]; +extern char login_db_server_id[32]; +extern char login_db_server_pw[32]; +extern char login_db_server_db[32]; + +#endif diff --git a/misc/src/char_sql/itemdb.c b/misc/src/char_sql/itemdb.c new file mode 100644 index 0000000..0bed07c --- /dev/null +++ b/misc/src/char_sql/itemdb.c @@ -0,0 +1,247 @@ +// $Id: itemdb.c,v 1.1.1.1 2004/09/10 17:44:48 MagicalTux Exp $ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "itemdb.h" +#include "db.h" +#include "inter.h" +#include "char.h" +#include "utils.h" + +#ifdef MEMWATCH +#include "memwatch.h" +#endif + +#define MAX_RANDITEM 2000 + +// ** ITEMDB_OVERRIDE_NAME_VERBOSE ** +// 定義すると、itemdb.txtとgrfで名前が異なる場合、表示します. +//#define ITEMDB_OVERRIDE_NAME_VERBOSE 1 + +char item_db_db[256]="item_db"; // added to specify item_db sql table [Valaris] + +static struct dbt* item_db; + +/*========================================== + * DBの検索 + *------------------------------------------ + */ +struct item_data* itemdb_search(int nameid) +{ + struct item_data *id; + + id=numdb_search(item_db,nameid); + if(id) return id; + + CREATE(id, struct item_data, 1); + + numdb_insert(item_db,nameid,id); + + + if(nameid>500 && nameid<600) + id->type=0; //heal item + else if(nameid>600 && nameid<700) + id->type=2; //use item + else if((nameid>700 && nameid<1100) || + (nameid>7000 && nameid<8000)) + id->type=3; //correction + else if(nameid>=1750 && nameid<1771) + id->type=10; //arrow + else if(nameid>1100 && nameid<2000) + id->type=4; //weapon + else if((nameid>2100 && nameid<3000) || + (nameid>5000 && nameid<6000)) + id->type=5; //armor + else if(nameid>4000 && nameid<5000) + id->type=6; //card + else if(nameid>9000 && nameid<10000) + id->type=7; //egg + else if(nameid>10000) + id->type=8; //petequip + + return id; +} + +/*========================================== + * + *------------------------------------------ + */ +int itemdb_isequip(int nameid) +{ + int type=itemdb_type(nameid); + if(type==0 || type==2 || type==3 || type==6 || type==10) + return 0; + return 1; +} +/*========================================== + * + *------------------------------------------ + */ +int itemdb_isequip2(struct item_data *data) +{ + if(data) { + int type=data->type; + if(type==0 || type==2 || type==3 || type==6 || type==10) + return 0; + else + return 1; + } + return 0; +} + + + +/*========================================== + * アイテムデータベースの読み込み + *------------------------------------------ + */ +static int itemdb_readdb(void) +{ + FILE *fp; + char line[1024]; + int ln=0; + int nameid,j; + char *str[32],*p,*np; + struct item_data *id; + + fp=fopen("db/item_db.txt","r"); + if(fp==NULL){ + printf("can't read db/item_db.txt\n"); + exit(1); + } + while(fgets(line,1020,fp)){ + if(line[0]=='/' && line[1]=='/') + continue; + memset(str,0,sizeof(str)); + for(j=0,np=p=line;j<17 && p;j++){ + str[j]=p; + p=strchr(p,','); + if(p){ *p++=0; np=p; } + } + if(str[0]==NULL) + continue; + + nameid=atoi(str[0]); + if(nameid<=0 || nameid>=20000) + continue; + ln++; + + //ID,Name,Jname,Type,Price,Sell,Weight,ATK,DEF,Range,Slot,Job,Gender,Loc,wLV,eLV,View + id=itemdb_search(nameid); + memcpy(id->name,str[1],24); + memcpy(id->jname,str[2],24); + id->type=atoi(str[3]); + + } + fclose(fp); + printf("read db/item_db.txt done (count=%d)\n",ln); + return 0; +} + +static int itemdb_read_sqldb(void) // sql item_db read, shortened version of map-server item_db read [Valaris] +{ + unsigned int nameid; // Type should be "unsigned short int", but currently isn't for compatibility with numdb_insert() + struct item_data *id; + + // ---------- + + // Output query to retrieve all rows from the item database table + sprintf(tmp_sql, "SELECT * FROM `%s`", item_db_db); + + // Execute the query; if the query execution fails, output an error + if (mysql_query(&mysql_handle, tmp_sql)) { + printf("Database server error (executing query for %s): %s\n", item_db_db, mysql_error(&mysql_handle)); + } + + // Store the query result + sql_res = mysql_store_result(&mysql_handle); + + // If the storage of the query result succeeded + if (sql_res) { + // Parse each row in the query result into sql_row + while ((sql_row = mysql_fetch_row(sql_res))) { + nameid = atoi(sql_row[0]); + + // If the identifier is not within the valid range, process the next row + if (nameid == 0 || nameid >= 20000) { // Should ">= 20000" be "> 20000"? + continue; + } + + // ---------- + + // Insert a new row into the item database +/* + id = calloc(sizeof(struct item_data), 1); + + if (id == NULL) { + printf("out of memory : itemdb_read_sqldb\n"); + exit(1); + } + + memset(id, 0, sizeof(struct item_data)); + numdb_insert(item_db, nameid, id); + + // ---------- +*/ + id=itemdb_search(nameid); + + memcpy(id->name, sql_row[1], 24); + memcpy(id->jname, sql_row[2], 24); + + id->type = atoi(sql_row[3]); + } + + // If the retrieval failed, output an error + if (mysql_errno(&mysql_handle)) { + printf("Database server error (retrieving rows from %s): %s\n", item_db_db, mysql_error(&mysql_handle)); + } + + printf("read %s done (count = %lu)\n", item_db_db, (unsigned long) mysql_num_rows(sql_res)); + + // Free the query result + mysql_free_result(sql_res); + } else { + printf("MySQL error (storing query result for %s): %s\n", item_db_db, mysql_error(&mysql_handle)); + } + + return 0; +} + +static int itemdb_final(void *key,void *data,va_list ap) +{ + struct item_data *id; + + id=data; + if(id->use_script) + free(id->use_script); + if(id->equip_script) + free(id->equip_script); + free(id); + + return 0; +} + + +/*========================================== + * + *------------------------------------------ + */ +void do_final_itemdb(void) +{ + if(item_db){ + numdb_final(item_db,itemdb_final); + item_db=NULL; + } +} +int do_init_itemdb(void) +{ + item_db = numdb_init(); + + if (db_use_sqldbs) // it db_use_sqldbs in inter config are yes, will read from item_db for char server display [Valaris] + itemdb_read_sqldb(); + else + itemdb_readdb(); + return 0; +} + diff --git a/misc/src/char_sql/itemdb.h b/misc/src/char_sql/itemdb.h new file mode 100644 index 0000000..dea835e --- /dev/null +++ b/misc/src/char_sql/itemdb.h @@ -0,0 +1,34 @@ +#ifndef _ITEMDB_H_ +#define _ITEMDB_H_ + +struct item_data { + int nameid; + char name[24],jname[24]; + int value_buy,value_sell,value_notdc,value_notoc; + int type; + int class; + int sex; + int equip; + int weight; + int atk; + int def; + int range; + int slot; + int look; + int elv; + int wlv; + char *use_script; // 回復とかも全部この中でやろうかなと + char *equip_script; // 攻撃,防御の属性設定もこの中で可能かな? + char available; +}; + +struct item_data* itemdb_search(int nameid); +#define itemdb_type(n) itemdb_search(n)->type + +int itemdb_isequip(int); +int itemdb_isequip2(struct item_data *); + +void do_final_itemdb(void); +int do_init_itemdb(void); + +#endif diff --git a/misc/src/char_sql/make.sh b/misc/src/char_sql/make.sh new file mode 100644 index 0000000..a4ca8b5 --- /dev/null +++ b/misc/src/char_sql/make.sh @@ -0,0 +1,11 @@ +#!/bin/sh + rsqlt=`rm -rf *.o` + gcc -c char.c -I/usr/local/include/mysql/ + gcc -c int_guild.c -I/usr/local/include/mysql/ + gcc -c int_party.c -I/usr/local/include/mysql/ + gcc -c int_pet.c -I/usr/local/include/mysql/ + gcc -c int_storage.c -I/usr/local/include/mysql/ + gcc -c inter.c -I/usr/local/include/mysql/ + gcc -c strlib.c + gcc -c itemdb.c -I../common/ + gcc -o ../char-server inter.o char.o int_pet.o int_storage.o int_guild.o int_party.o strlib.o itemdb.o ../common/core.o ../common/socket.o ../common/timer.o ../common/db.o -L/usr/local/lib/mysql -lmysqlclient -lz diff --git a/misc/src/char_sql/readme.txt b/misc/src/char_sql/readme.txt new file mode 100644 index 0000000..41b1144 --- /dev/null +++ b/misc/src/char_sql/readme.txt @@ -0,0 +1,250 @@ +サソ//Encoded with UTF-8 (UNICODE) +//--------------------------------------------- +// V.018 - Aarlex +1. ADD Makefile & GNUmakefile +2. fix guild_leave. +3. fix char select windows HP & SP value error. + +// V.017 - Aarlex +1. fix guild member view job update.(For mod-0728) + inter.c + int_guild.c + +// V.016 - by Aarlex +1. Add e-mail check when you Delete char data. +2. modify storage save func like 014. +2. remove Lan_support func. + +// v.014 - by Aarlex +I rewrite save function. +besause myfriend find that Mysql will use more than 40% CPU. +And log file is too big (4 days 22G ..= =+) +(maybe he sets autosave_time less then 1 min.) +but. i still rewrite save func. +char server will delete all of user item(inventory & Cart) data then insert them again before. +so i use two struct to save item data from map & database. +then compare two struct to get different . +AND add some debug message.but message maybe too much XD. + + +1. ADDED itemdb.c itemdb.h +2. modify mmo_char_tosql(). +3. ADDED memitemdata_to_sql(). +4. ADDED some debug message in memitemdata_to_sql(). +5. modify make.sh + +// v.013 - by Aarlex +1. some SQL sentance fix in old version Mysql . + +2. in_guild.c mapif_guild_leaved() + unsigned char buf[64] -> unsigned char buf[128] + +3. in_pet.c inter_pet_tosql() + if (sql_res) - > if (mysql_num_rows(sql_res)!=0) + +4. in_char.c mmo_char_send006b() + + WFIFOW(fd, offset+(i*106)+42) = char_dat[0].hp -> WFIFOW(fd,offset+(i*106)+42) = (char_dat[j].hp > 0x7fff)? 0x7fff:char_dat[j].hp; + WFIFOW(fd, offset+(i*106)+44) = char_dat[0].max_hp -> WFIFOW(fd,offset+(i*106)+44) = (char_dat[j].max_hp > 0x7fff)? 0x7fff:char_dat[j].max_hp; + WFIFOW(fd, offset+(i*106)+46) = char_dat[0].sp -> WFIFOW(fd,offset+(i*106)+46) = (char_dat[j].sp > 0x7fff)? 0x7fff:char_dat[j].sp + WFIFOW(fd, offset+(i*106)+48) = char_dat[0].max_sp -> WFIFOW(fd,offset+(i*106)+48) = (char_dat[j].max_sp > 0x7fff)? 0x7fff:char_dat[j].max_sp; + + in_char.c parse_char() + + WFIFOW(fd, offset+(i*106)+42) = char_dat[0].hp -> WFIFOW(fd,offset+(i*106)+42) = (char_dat[j].hp > 0x7fff)? 0x7fff:char_dat[j].hp; + WFIFOW(fd, offset+(i*106)+44) = char_dat[0].max_hp -> WFIFOW(fd,offset+(i*106)+44) = (char_dat[j].max_hp > 0x7fff)? 0x7fff:char_dat[j].max_hp; + WFIFOW(fd, offset+(i*106)+46) = char_dat[0].sp -> WFIFOW(fd,offset+(i*106)+46) = (char_dat[j].sp > 0x7fff)? 0x7fff:char_dat[j].sp + WFIFOW(fd, offset+(i*106)+48) = char_dat[0].max_sp -> WFIFOW(fd,offset+(i*106)+48) = (char_dat[j].max_sp > 0x7fff)? 0x7fff:char_dat[j].max_sp; + +// v.012 - by Jazz +1. 0627 official version縺ォ蜷医o縺帙※菫ョ豁」縺励∪縺励◆. +2. no more binary files are supplied. + +//--------------------------------------------- +// v.011 - by Mark +1. Fixed a couple bugs which would cause segfaults under linux :) + +//--------------------------------------------- +// v.010 - by Jazz +1. added some debug info - for reporting. + +//--------------------------------------------- +// v.009 - by Jazz +1. code added for debug. +2. some SQL sentance fix. + +//--------------------------------------------- +// v.009 - by Jazz +1. fix crash bug. - pet db. + +//--------------------------------------------- +// v.008 - by Jazz +1. DB table fix. - char.fix-from.007.to.008.sql + +譌「蟄倥ョ table 讒矩縺ォ縺ッ遏「縺瑚」逹隗」髯、縺輔l繧句エ蜷医′縺ゅj縺セ縺. + +item.equip縺ッ 'unsigned short' 蠖「蠑上〒縺. +縺薙ョ縺溘a縺ォ SQL table繧剃ソョ豁」縺励↑縺代l縺ー縺ェ繧翫∪縺帙s. + +菫ョ豁」繧ウ繝シ繝峨ッ char.fix-from.007.to.008.sql 縺ァ縺. +MySQL縺ァ荳蠎ヲ陦後▲縺ヲ縺上l繧後ー驕ゥ逕ィ縺輔l縺セ縺. 譌「蟄倥ョ繝繝シ繧ソ縺ッ螳牙ィ縺ァ縺. + +//--------------------------------------------- +// v.007 - by Jazz +1. domain 隗」驥医↓蟇セ縺吶k蝠城。後r菫ョ豁」縺励∪縺励◆. + +//--------------------------------------------- +// v.006 - by Jazz +1. crash bug fix. - when your pet DB is empty + +//--------------------------------------------- +// v.005 - by Jazz +1. 0590 official version縺ォ蜷医o縺帙※菫ョ豁」縺励∪縺励◆. + +//--------------------------------------------- +// v.004 - by Jazz +1. 0586 official version縺ォ蜷医o縺帙※菫ョ豁」縺励∪縺励◆. + +//--------------------------------------------- +// v.003 - by Jazz +1. official縺ョ guild.c 縺ィ party.c 繝輔ぃ繧、繝ォ縺ァ縺セ縺溷堺ソョ豁」縺励∪縺励◆. + +//--------------------------------------------- +// v.002 - by Jazz +1. aphostropy 蝠城。後r隗」豎コ縺励∪縺励◆. 縺薙l縺ッ菫晏ョ峨→騾」髢「縺後≠繧句撫鬘後〒縺. +2. SQL縺ョ讒矩繧剃ソョ豁」縺励∪縺励◆. + +//--------------------------------------------- +// v.001 - by Jazz +1. 荳逡ェ逶ョ螳牙ョ version縺ァ縺. alpha version縺ォ蝠城。檎せ繧貞、壽焚菫ョ豁」縺励∪縺励◆. + +//------------------------------------------------------------------------ +//For JAPANESE USER +Athena Char-Server for MySQL. 005 + +荳蠢 guild縺ィ party縺ッ繧医¥蜻シ縺ウ蜃コ縺励&繧後k驛ィ蛻縺ェ縺ョ縺ァ譌「蟄倥ョ athena char-server縺御スソ縺」縺ヲ縺繧 file 蝓コ逶、縺ョ讒矩繧呈戟縺」縺ヲ陦後″縺セ縺. +縺薙l縺ッ, 荳蠎ヲ縺ォ繝。繝「繝ェ繝シ縺ァ逧隱ュ繧薙〒, 繧ゅ≧荳蠎ヲ縺ォ逧 file縺ァ菴ソ縺譁ケ縺碁Κ荳九′蟆代↑縺上°縺九k縺ィ諤昴>縺セ縺. +縺昴@縺ヲ char 繝繝シ繧ソ縺ォ豈斐∋縺ヲ, lost縺ォ縺ェ縺」縺ヲ繧ょ撫鬘後′繧ゅ▲縺ィ蟆代↑縺縺ィ蛻、譁ュ縺励※縺昴≧縺励∪縺励◆. + +MySQL繝舌シ繧ク繝ァ繝ウ縺ョ compile縺ッ MySQL Clients Library縺悟ソ隕√〒縺. windows(cygwin) 繝舌シ繧ク繝ァ繝ウ縺ォ繧ウ繝ウ繝代う繝ォ縺輔l縺 binary繧呈キサ莉倥@縺セ縺励◆. + +險ュ鄂ョ: +縺セ縺 text->DB縺ョ converter縺ッ縺セ縺ィ繧ゅ↓謾ッ謠エ縺励↑縺縺ァ縺. 蜀驛ィ逧縺ォ縺。繧縺」縺ィ繝舌げ縺檎匱隕九&繧後※繝舌げ繧剃ソョ豁」荳ュ縺ァ縺. +縺ァ縺阪k縺縺第掠縺 upload繧偵@縺セ縺. + +1. char.sql繧 MySQL縺ォ dump縺励∪縺. + +2. inter_athena.conf縺ォ谺。繧定ソス蜉縺励∪縺. 縺昴@縺ヲ閾ェ蛻縺ョ DB繧オ繝シ繝舌シ縺ョ諠蝣ア縺ォ蜷医o縺帙※縺上l縺セ縺. +縺薙%縺ァ windows(cygwin)縺ョ蝣エ蜷医↓縺ッ localhost繧剃スソ縺」縺ヲ縺ッ縺縺代↑縺縺ァ縺. ip縺ァ譖ク縺縺ヲ縺上l縺ェ縺代l縺ー縺ェ繧翫∪縺帙s. +localhost縺ァ菴ソ縺蝣エ蜷 UNIX domain socket縺御ス懷虚縺吶k縺九i騾」邨舌′荳榊庄閭ス縺ォ縺ェ繧翫∪縺. + +//3306 is default +db_server_port: 3306 +//DB ip +db_server_ip: 127.0.0.1 +//DB id +db_server_id: ragnarok +//DB pass +db_server_pw: ragnarok +//DB name +db_server_logindb: ragnarok + +3. MySQL 繝舌シ繧ク繝ァ繝ウ縺ッ 2蛟九ョ MySQL connect session繧呈戟縺。縺セ縺. +荳縺、縺ッ繧ュ繝」繝ゥ繧ッ繧ソ繝シ繝繝シ繧ソ繧定ェュ繧薙〒譖ク縺上ョ縺ォ菴ソ繧上l縺ヲ, 莠檎分逶ョ縺ッ inter server縺ョ縺溘a縺ォ騾」邨舌@縺セ縺. + +髢狗匱縺ィ繝繧ケ繝育腸蠅縺ッ P4 2.4a/1024MB/WinXP pro(MediaCenter Edition 2K4 KR)/Cygwin GCC 縺ァ縺. +譛ャ莠コ縺碁沒蝗ス莠コ縺縺九i髻灘嵜隱樣幕逋コ閠縺ョ騾」邨。繧よュ楢ソ弱@縺セ縺. +髻灘嵜隱槭→譌・譛ャ隱槭ョ荳頑焔縺ェ譁ケ縺ッ騾」邨。繧偵¥縺縺輔>. 譌・譛ャ隱槭ョ菴ソ逕ィ縺ォ髮」縺励&繧呈─縺倥※縺縺セ縺. + +迴セ蝨ィ DarkWeiss縺 login server縺ォ MySQL繧呈髪謠エ縺怜ァ九a縺セ縺励◆. +縺励°縺 athena縺ョ縺昴l縺ッ DarkWeiss 繧医j繧ゅ▲縺ィ繧医¥菴懷虚縺吶k縺ィ諤昴>縺セ縺. + +contact : jazz@creper.com + +//------------------------------------------------------------------------ +//For KOREAN USER +Athena Char-Server for MySQL. 005 + +攵卿 guild凰 party株 梵」シ 从カ罹据株 カカ擽攵 クー。エ攪 athena char-serverー ぎ圸葺ウ 梭株 file クーー們攪 オャ。ー・シ ーァウ ー瀧笈共. +擽 イ捩, 復イ溢乱 ゥ罷ェィヲャ。 ェィ草 攷ウ, 共亨 復イ溢乱 ェィ草 file。 堂株 ェス擽 カ葺ー イ アクヲー共ウ 晝ー鮒笈共. +キクヲャウ char 魂擽┣乱 ケ紛, lostー 据鵠攵巡 ャク懋ー 鵠 共ウ 倹卿紛 キクイ 葺慣笈共. + +MySQLイ攪 compile捩 MySQL Clients Libraryー 符囈鮒笈共. windows(cygwin) イ愍。 サエ血攵頗 binary・シ イィカ葺慣笈共. + +└ケ: +符ァ text->DB攪 converter株 罹劇。 ァ寳葺ァ 賦慣笈共. ざカ愍。 平ー イキクー ー懋イャ据牟 イキク・シ 們菩、卓桿笈共. +据巡。 ケィヲャ upload・シ 葺イ慣笈共. + +1. char.sql揆 MySQL乱 dump鮒笈共. + +2. inter_athena.conf乱 共搆揆 カ緋ー 鮒笈共. キクヲャウ 梵侠攪 DB罹イ攪 簿ウエ乱 ァ樌カ肥牟 、鷺笈共. +流クー乱 windows(cygwin)攪 イス垈乱株 localhost・シ 堂ゥエ 譜姓笈共. ip。 牟」シ牟幣 鮒笈共. +localhost。 ぎ圸葺株 イス垈 UNIX domain socket擽 梠徐 葺クー 阜ャク乱 硫イー擽 カ一ー冠紛 ァ瀧笈共. + +//3306 is default +db_server_port: 3306 +//DB ip +db_server_ip: 127.0.0.1 +//DB id +db_server_id: ragnarok +//DB pass +db_server_pw: ragnarok +//DB name +db_server_logindb: ragnarok + +3. MySQL イ捩 2ー懍攪 MySQL connect session揆 ーァ瀧笈共. +葺x株 コ尖ヲュ┣ 魂擽┣・シ 攷ウ 堂株魂 ぎ圸据ゥー, 草イ溢ァク株 inter server・シ 怱紛 硫イー鮒笈共. + +ー罹ー懋ウシ 護侃敢 劍イス捩 P4 2.4a/1024MB/WinXP pro(MediaCenter Edition 2K4 KR)/Cygwin GCC 桿笈共. +ウク攤擽 復オュ攤擽クー 阜ャク乱 復オュ牟 ー罹ー懍梵攪 硫攷巡 劍鮒笈共. +復オュ牟凰 攵ウク牟ー 冠呰復 カ捩 硫攷揆 」シ┷囈. 攵ウク牟攪 ぎ圸乱 牟、它揆 叶⊂ウ 梭慣笈共. + +椪 DarkWeissー login server乱 MySQL揆 ァ寳葺クー 亨梠毎慣笈共. +葺ァァ athena攪 キクイ捩 DarkWeiss ウエ共 鵠 椈 梠徐復共ウ 晝ー鮒笈共. + +contact : jazz@creper.com + +//------------------------------------------------------------------------ +//For ENGLISH USER +Athena Char-Server for MySQL. alpha 005 + += hehe. My English is poor. and I have no time to write. :) + +anyway this version use guild and party data on text file base system. +It accesses many times, so memory dumping is useful for less cpu consume. + +MySQL version need MySQL Clients Library to compile. This include Win32-binary compiled by cygwin-gcc. + +Install: +not yet text->DB converter. I found some bug on it, so It's under debug progress. + +1. dump char.sql to MySQL. + +2. add below on inter_athena.conf. and set your own information. +do not use 'localhost' as domain on cygwin. you must set as ip. +if you use localhost on cygwin, cygwin tries to connect MySQL as UNIX domain socket. +but, MySQL does not support UNIX domain socket on windows. + +//3306 is default +db_server_port: 3306 +//DB ip +db_server_ip: 127.0.0.1 +//DB id +db_server_id: ragnarok +//DB pass +db_server_pw: ragnarok +//DB name +db_server_logindb: ragnarok + +3. MySQL version has 2 MySQL connect session. +one is for char-server and the other one is for inter-server. + +developement enviroment) + P4 2.4a/1024MB/WinXP pro(MediaCenter Edition 2K4 KR)/Cygwin GCC + +I'm korean, so contect if U're Korean developer. +Please contact me, If U can use Korean & Japanese well. + +DarkWeiss starts to support MySQL version of login server, but Athena's one works better, I thnik. + +contact : jazz@creper.com diff --git a/misc/src/char_sql/strlib.c b/misc/src/char_sql/strlib.c new file mode 100644 index 0000000..b113d96 --- /dev/null +++ b/misc/src/char_sql/strlib.c @@ -0,0 +1,79 @@ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "strlib.h" +#include "utils.h" + +//----------------------------------------------- +// string lib. +unsigned char* jstrescape (unsigned char* pt) { + //copy from here + unsigned char * ptr; + int i =0, j=0; + + //copy string to temporary + CREATE(ptr, char, J_MAX_MALLOC_SIZE); + strcpy (ptr,pt); + + while (ptr[i] != '\0') { + switch (ptr[i]) { + case '\'': + pt[j++] = '\\'; + pt[j++] = ptr[i++]; + break; + case '\\': + pt[j++] = '\\'; + pt[j++] = ptr[i++]; + break; + default: + pt[j++] = ptr[i++]; + } + } + pt[j++] = '\0'; + free (ptr); + return (unsigned char*) &pt[0]; +} + +unsigned char* jstrescapecpy (unsigned char* pt,unsigned char* spt) { + //copy from here + int i =0, j=0; + + while (spt[i] != '\0') { + switch (spt[i]) { + case '\'': + pt[j++] = '\\'; + pt[j++] = spt[i++]; + break; + case '\\': + pt[j++] = '\\'; + pt[j++] = spt[i++]; + break; + default: + pt[j++] = spt[i++]; + } + } + pt[j++] = '\0'; + return (unsigned char*) &pt[0]; +} +int jmemescapecpy (unsigned char* pt,unsigned char* spt, int size) { + //copy from here + int i =0, j=0; + + while (i < size) { + switch (spt[i]) { + case '\'': + pt[j++] = '\\'; + pt[j++] = spt[i++]; + break; + case '\\': + pt[j++] = '\\'; + pt[j++] = spt[i++]; + break; + default: + pt[j++] = spt[i++]; + } + } + // copy size is 0 ~ (j-1) + return j; +} diff --git a/misc/src/char_sql/strlib.h b/misc/src/char_sql/strlib.h new file mode 100644 index 0000000..6b61690 --- /dev/null +++ b/misc/src/char_sql/strlib.h @@ -0,0 +1,10 @@ +#ifndef _J_STR_LIB_H_ +#define _J_STR_LIB_H_ +#define J_MAX_MALLOC_SIZE 65535 +// String function library. +// code by Jioh L. Jung (ziozzang@4wish.net) +// This code is under license "BSD" +unsigned char* jstrescape (unsigned char* pt); +unsigned char* jstrescapecpy (unsigned char* pt,unsigned char* spt); +int jmemescapecpy (unsigned char* pt,unsigned char* spt, int size); +#endif diff --git a/misc/src/common/GNUmakefile b/misc/src/common/GNUmakefile new file mode 100644 index 0000000..689ac3b --- /dev/null +++ b/misc/src/common/GNUmakefile @@ -0,0 +1,13 @@ +txt sql all: core.o socket.o timer.o grfio.o db.o lock.o nullpo.o malloc.o
+
+core.o: core.c core.h
+socket.o: socket.c socket.h mmo.h
+timer.o: timer.c timer.h
+grfio.o: grfio.c grfio.h
+db.o: db.c db.h
+lock.o: lock.h
+nullpo.o: nullpo.c nullpo.h
+malloc.o: malloc.c malloc.h
+
+clean:
+ rm -f *.o
diff --git a/misc/src/common/Makefile b/misc/src/common/Makefile new file mode 100644 index 0000000..689ac3b --- /dev/null +++ b/misc/src/common/Makefile @@ -0,0 +1,13 @@ +txt sql all: core.o socket.o timer.o grfio.o db.o lock.o nullpo.o malloc.o
+
+core.o: core.c core.h
+socket.o: socket.c socket.h mmo.h
+timer.o: timer.c timer.h
+grfio.o: grfio.c grfio.h
+db.o: db.c db.h
+lock.o: lock.h
+nullpo.o: nullpo.c nullpo.h
+malloc.o: malloc.c malloc.h
+
+clean:
+ rm -f *.o
diff --git a/misc/src/common/core.c b/misc/src/common/core.c new file mode 100644 index 0000000..62af254 --- /dev/null +++ b/misc/src/common/core.c @@ -0,0 +1,152 @@ +// $Id: core.c,v 1.1.1.1 2004/09/10 17:44:49 MagicalTux Exp $ +// original : core.c 2003/02/26 18:03:12 Rev 1.7 + +#include <stdio.h> +#include <stdlib.h> +#ifndef LCCWIN32 +#include <unistd.h> +#endif +#include <signal.h> + +#include "core.h" +#include "socket.h" +#include "timer.h" +#include "version.h" + +#ifdef MEMWATCH +#include "memwatch.h" +#endif + +static void (*term_func)(void)=NULL; + +/*====================================== + * CORE : Set function + *-------------------------------------- + */ +void set_termfunc(void (*termfunc)(void)) +{ + term_func = termfunc; +} + +/*====================================== + * CORE : Signal Sub Function + *-------------------------------------- + */ + +static void sig_proc(int sn) +{ + int i; + switch(sn){ + case SIGINT: + case SIGTERM: + if(term_func) + term_func(); + for(i=0;i<fd_max;i++){ + if(!session[i]) + continue; + close(i); + } + exit(0); + break; + } +} + +/*====================================== + * CORE : Display title + *-------------------------------------- + */ + +static void display_title(void) +{ + // for help with the console colors look here: + // http://www.edoceo.com/liberum/?doc=printf-with-color + // some code explanation (used here): + // \033[2J : clear screen and go up/left (0, 0 position) + // \033[K : clear line from actual position to end of the line + // \033[0m : reset color parameter + // \033[1m : use bold for font + printf("\033[2J"); // clear screen and go up/left (0, 0 position in text) + printf("\033[37;44m (=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=)\033[K\033[0m\n"); // white writing (37) on blue background (44), \033[K clean until end of file + printf("\033[0;44m (\033[1;33m (c)2004 eAthena Development Team presents \033[0;44m)\033[K\033[0m\n"); // yellow writing (33) + printf("\033[0;44m (\033[1m ______ __ __ \033[0;44m)\033[K\033[0m\n"); // 1: bold char, 0: normal char + printf("\033[0;44m (\033[1m /\\ _ \\/\\ \\__/\\ \\ v%2d.%02d.%02d \033[0;44m)\033[K\033[0m\n", ATHENA_MAJOR_VERSION, ATHENA_MINOR_VERSION, ATHENA_REVISION); // 1: bold char, 0: normal char + printf("\033[0;44m (\033[1m __\\ \\ \\_\\ \\ \\ ,_\\ \\ \\___ __ ___ __ \033[0;44m)\033[K\033[0m\n"); // 1: bold char, 0: normal char + printf("\033[0;44m (\033[1m /'__`\\ \\ __ \\ \\ \\/\\ \\ _ `\\ /'__`\\/' _ `\\ /'__`\\ \033[0;44m)\033[K\033[0m\n"); // 1: bold char, 0: normal char + printf("\033[0;44m (\033[1m /\\ __/\\ \\ \\/\\ \\ \\ \\_\\ \\ \\ \\ \\/\\ __//\\ \\/\\ \\/\\ \\_\\.\\_ \033[0;44m)\033[K\033[0m\n"); // 1: bold char, 0: normal char + printf("\033[0;44m (\033[1m \\ \\____\\\\ \\_\\ \\_\\ \\__\\\\ \\_\\ \\_\\ \\____\\ \\_\\ \\_\\ \\__/.\\_\\ \033[0;44m)\033[K\033[0m\n"); // 1: bold char, 0: normal char + printf("\033[0;44m (\033[1m \\/____/ \\/_/\\/_/\\/__/ \\/_/\\/_/\\/____/\\/_/\\/_/\\/__/\\/_/ \033[0;44m)\033[K\033[0m\n"); // 1: bold char, 0: normal char + printf("\033[0;44m (\033[1m _ _ _ _ _ _ _ _ _ _ _ _ _ \033[0;44m)\033[K\033[0m\n"); // 1: bold char, 0: normal char + printf("\033[0;44m (\033[1m / \\ / \\ / \\ / \\ / \\ / \\ / \\ / \\ / \\ / \\ / \\ / \\ / \\ \033[0;44m)\033[K\033[0m\n"); // 1: bold char, 0: normal char + printf("\033[0;44m (\033[1m ( e | n | g | l | i | s | h ) ( A | t | h | e | n | a ) \033[0;44m)\033[K\033[0m\n"); // 1: bold char, 0: normal char + printf("\033[0;44m (\033[1m \\_/ \\_/ \\_/ \\_/ \\_/ \\_/ \\_/ \\_/ \\_/ \\_/ \\_/ \\_/ \\_/ \033[0;44m)\033[K\033[0m\n"); // 1: bold char, 0: normal char + printf("\033[37;44m (=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=)\033[K\033[0m\n\n"); // reset color +} + +// Added by Gabuzomeu +// +// This is an implementation of signal() using sigaction() for portability. +// (sigaction() is POSIX; signal() is not.) Taken from Stevens' _Advanced +// Programming in the UNIX Environment_. +// +#ifndef SIGPIPE +#define SIGPIPE SIGINT +#endif + +#ifndef POSIX +#define compat_signal(signo, func) signal(signo, func) +#else +sigfunc *compat_signal(int signo, sigfunc *func) +{ + struct sigaction sact, oact; + + sact.sa_handler = func; + sigemptyset(&sact.sa_mask); + sact.sa_flags = 0; +#ifdef SA_INTERRUPT + sact.sa_flags |= SA_INTERRUPT; /* SunOS */ +#endif + + if (sigaction(signo, &sact, &oact) < 0) + return (SIG_ERR); + + return (oact.sa_handler); +} +#endif + + +/*====================================== + * CORE : MAINROUTINE + *-------------------------------------- + */ + +int runflag = 1; + +int main(int argc,char **argv) +{ + int next; + + Net_Init(); + do_socket(); + + compat_signal(SIGPIPE,SIG_IGN); + compat_signal(SIGTERM,sig_proc); + compat_signal(SIGINT,sig_proc); + + // Signal to create coredumps by system when necessary (crash) + compat_signal(SIGSEGV, SIG_DFL); +#ifndef LCCWIN32 + compat_signal(SIGBUS, SIG_DFL); + compat_signal(SIGTRAP, SIG_DFL); +#endif + compat_signal(SIGILL, SIG_DFL); + + display_title(); + + do_init(argc,argv); + while(runflag){ + next=do_timer(gettick_nocache()); + do_sendrecv(next); + do_parsepacket(); + } + return 0; +} diff --git a/misc/src/common/core.h b/misc/src/common/core.h new file mode 100644 index 0000000..bc2be02 --- /dev/null +++ b/misc/src/common/core.h @@ -0,0 +1,12 @@ +// original : core.h 2003/03/14 11:55:25 Rev 1.4 + +#ifndef _CORE_H_ +#define _CORE_H_ + +extern int runflag; + +int do_init(int,char**); + +void set_termfunc(void (*termfunc)(void)); + +#endif // _CORE_H_ diff --git a/misc/src/common/db.c b/misc/src/common/db.c new file mode 100644 index 0000000..a2dc695 --- /dev/null +++ b/misc/src/common/db.c @@ -0,0 +1,500 @@ +// $Id: db.c,v 1.2 2004/09/23 14:43:06 MouseJstr Exp $ +// #define MALLOC_DBN +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include "db.h" +#include "utils.h" + +#ifdef MEMWATCH +#include "memwatch.h" +#endif + +#define ROOT_SIZE 4096 +#ifdef MALLOC_DBN +static struct dbn *dbn_root[512], *dbn_free; +static int dbn_root_rest=0,dbn_root_num=0; + +static void * malloc_dbn(void) +{ + struct dbn* ret; + + if(dbn_free==NULL){ + if(dbn_root_rest<=0){ + CREATE(dbn_root[dbn_root_num], struct dbn, ROOT_SIZE); + + dbn_root_rest=ROOT_SIZE; + dbn_root_num++; + } + return &(dbn_root[dbn_root_num-1][--dbn_root_rest]); + } + ret=dbn_free; + dbn_free = dbn_free->parent; + return ret; +} + +static void free_dbn(struct dbn* add_dbn) +{ + add_dbn->parent = dbn_free; + dbn_free = add_dbn; +} +#endif + +static int strdb_cmp(struct dbt* table,void* a,void* b) +{ + if(table->maxlen) + return strncmp(a,b,table->maxlen); + return strcmp(a,b); +} + +static unsigned int strdb_hash(struct dbt* table,void* a) +{ + int i; + unsigned int h; + unsigned char *p=a; + + i=table->maxlen; + if(i==0) i=0x7fffffff; + for(h=0;*p && --i>=0;){ + h=(h*33 + *p++) ^ (h>>24); + } + return h; +} + +struct dbt* strdb_init(int maxlen) +{ + int i; + struct dbt* table; + + CREATE(table, struct dbt, 1); + + table->cmp=strdb_cmp; + table->hash=strdb_hash; + table->maxlen=maxlen; + for(i=0;i<HASH_SIZE;i++) + table->ht[i]=NULL; + return table; +} + +static int numdb_cmp(struct dbt* table,void* a,void* b) +{ + int ia,ib; + + ia=(int)a; + ib=(int)b; + + if((ia^ib) & 0x80000000) + return ia<0 ? -1 : 1; + + return ia-ib; +} + +static unsigned int numdb_hash(struct dbt* table,void* a) +{ + return (unsigned int)a; +} + +struct dbt* numdb_init(void) +{ + int i; + struct dbt* table; + + CREATE(table, struct dbt, 1); + + table->cmp=numdb_cmp; + table->hash=numdb_hash; + table->maxlen=sizeof(int); + for(i=0;i<HASH_SIZE;i++) + table->ht[i]=NULL; + return table; +} + +void* db_search(struct dbt *table,void* key) +{ + struct dbn *p; + + for(p=table->ht[table->hash(table,key) % HASH_SIZE];p;){ + int c=table->cmp(table,key,p->key); + if(c==0) + return p->data; + if(c<0) + p=p->left; + else + p=p->right; + } + return NULL; +} + +void * db_search2(struct dbt *table, const char *key) +{ + int i,sp; + struct dbn *p,*pn,*stack[64]; + int slen = strlen(key); + + for(i=0;i<HASH_SIZE;i++){ + if((p=table->ht[i])==NULL) + continue; + sp=0; + while(1){ + if (strncasecmp(key, p->key, slen) == 0) + return p->data; + if((pn=p->left)!=NULL){ + if(p->right){ + stack[sp++]=p->right; + } + p=pn; + } else { + if(p->right){ + p=p->right; + } else { + if(sp==0) + break; + p=stack[--sp]; + } + } + } + } + return 0; +} + +static void db_rotate_left(struct dbn *p,struct dbn **root) +{ + struct dbn * y = p->right; + p->right = y->left; + if (y->left !=0) + y->left->parent = p; + y->parent = p->parent; + + if (p == *root) + *root = y; + else if (p == p->parent->left) + p->parent->left = y; + else + p->parent->right = y; + y->left = p; + p->parent = y; +} + +static void db_rotate_right(struct dbn *p,struct dbn **root) +{ + struct dbn * y = p->left; + p->left = y->right; + if (y->right != 0) + y->right->parent = p; + y->parent = p->parent; + + if (p == *root) + *root = y; + else if (p == p->parent->right) + p->parent->right = y; + else + p->parent->left = y; + y->right = p; + p->parent = y; +} + +static void db_rebalance(struct dbn *p,struct dbn **root) +{ + p->color = RED; + while(p!=*root && p->parent->color==RED){ // rootは必ず黒で親は赤いので親の親は必ず存在する + if (p->parent == p->parent->parent->left) { + struct dbn *y = p->parent->parent->right; + if (y && y->color == RED) { + p->parent->color = BLACK; + y->color = BLACK; + p->parent->parent->color = RED; + p = p->parent->parent; + } else { + if (p == p->parent->right) { + p = p->parent; + db_rotate_left(p, root); + } + p->parent->color = BLACK; + p->parent->parent->color = RED; + db_rotate_right(p->parent->parent, root); + } + } else { + struct dbn* y = p->parent->parent->left; + if (y && y->color == RED) { + p->parent->color = BLACK; + y->color = BLACK; + p->parent->parent->color = RED; + p = p->parent->parent; + } else { + if (p == p->parent->left) { + p = p->parent; + db_rotate_right(p, root); + } + p->parent->color = BLACK; + p->parent->parent->color = RED; + db_rotate_left(p->parent->parent, root); + } + } + } + (*root)->color=BLACK; +} + +static void db_rebalance_erase(struct dbn *z,struct dbn **root) +{ + struct dbn *y = z, *x = NULL, *x_parent = NULL; + + if (y->left == NULL) + x = y->right; + else if (y->right == NULL) + x = y->left; + else { + y = y->right; + while (y->left != NULL) + y = y->left; + x = y->right; + } + if (y != z) { // 左右が両方埋まっていた時 yをzの位置に持ってきてzを浮かせる + z->left->parent = y; + y->left = z->left; + if (y != z->right) { + x_parent = y->parent; + if (x) x->parent = y->parent; + y->parent->left = x; + y->right = z->right; + z->right->parent = y; + } else + x_parent = y; + if (*root == z) + *root = y; + else if (z->parent->left == z) + z->parent->left = y; + else + z->parent->right = y; + y->parent = z->parent; + { int tmp=y->color; y->color=z->color; z->color=tmp; } + y = z; + } else { // どちらか空いていた場合 xをzの位置に持ってきてzを浮かせる + x_parent = y->parent; + if (x) x->parent = y->parent; + if (*root == z) + *root = x; + else if (z->parent->left == z) + z->parent->left = x; + else + z->parent->right = x; + } + // ここまで色の移動の除いて通常の2分木と同じ + if (y->color != RED) { // 赤が消える分には影響無し + while (x != *root && (x == NULL || x->color == BLACK)) + if (x == x_parent->left) { + struct dbn* w = x_parent->right; + if (w->color == RED) { + w->color = BLACK; + x_parent->color = RED; + db_rotate_left(x_parent, root); + w = x_parent->right; + } + if ((w->left == NULL || + w->left->color == BLACK) && + (w->right == NULL || + w->right->color == BLACK)) { + w->color = RED; + x = x_parent; + x_parent = x_parent->parent; + } else { + if (w->right == NULL || + w->right->color == BLACK) { + if (w->left) w->left->color = BLACK; + w->color = RED; + db_rotate_right(w, root); + w = x_parent->right; + } + w->color = x_parent->color; + x_parent->color = BLACK; + if (w->right) w->right->color = BLACK; + db_rotate_left(x_parent, root); + break; + } + } else { // same as above, with right <-> left. + struct dbn* w = x_parent->left; + if (w->color == RED) { + w->color = BLACK; + x_parent->color = RED; + db_rotate_right(x_parent, root); + w = x_parent->left; + } + if ((w->right == NULL || + w->right->color == BLACK) && + (w->left == NULL || + w->left->color == BLACK)) { + w->color = RED; + x = x_parent; + x_parent = x_parent->parent; + } else { + if (w->left == NULL || + w->left->color == BLACK) { + if (w->right) w->right->color = BLACK; + w->color = RED; + db_rotate_left(w, root); + w = x_parent->left; + } + w->color = x_parent->color; + x_parent->color = BLACK; + if (w->left) w->left->color = BLACK; + db_rotate_right(x_parent, root); + break; + } + } + if (x) x->color = BLACK; + } +} + +struct dbn* db_insert(struct dbt *table,void* key,void* data) +{ + struct dbn *p,*priv; + int c,hash; + + hash = table->hash(table,key) % HASH_SIZE; + for(c=0,priv=NULL ,p = table->ht[hash];p;){ + c=table->cmp(table,key,p->key); + if(c==0){ // replace + if (table->release) + table->release(p, 3); + p->data=data; + p->key=key; + return p; + } + priv=p; + if(c<0){ + p=p->left; + } else { + p=p->right; + } + } +#ifdef MALLOC_DBN + p=malloc_dbn(); +#else + CREATE(p, struct dbn, 1); +#endif + if(p==NULL){ + printf("out of memory : db_insert\n"); + return NULL; + } + p->parent= NULL; + p->left = NULL; + p->right = NULL; + p->key = key; + p->data = data; + p->color = RED; + if(c==0){ // hash entry is empty + table->ht[hash] = p; + p->color = BLACK; + } else { + if(c<0){ // left node + priv->left = p; + p->parent=priv; + } else { // right node + priv->right = p; + p->parent=priv; + } + if(priv->color==RED){ // must rebalance + db_rebalance(p,&table->ht[hash]); + } + } + return p; +} + +void* db_erase(struct dbt *table,void* key) +{ + void *data; + struct dbn *p; + int c,hash; + + hash = table->hash(table,key) % HASH_SIZE; + for(c=0,p = table->ht[hash];p;){ + c=table->cmp(table,key,p->key); + if(c==0) + break; + if(c<0) + p=p->left; + else + p=p->right; + } + if(!p) + return NULL; + data=p->data; + db_rebalance_erase(p,&table->ht[hash]); +#ifdef MALLOC_DBN + free_dbn(p); +#else + free(p); +#endif + return data; +} + +void db_foreach(struct dbt *table,int (*func)(void*,void*,va_list),...) +{ + int i,sp; + // red-black treeなので64個stackがあれば2^32個ノードまで大丈夫 + struct dbn *p,*pn,*stack[64]; + va_list ap; + + va_start(ap,func); + for(i=0;i<HASH_SIZE;i++){ + if((p=table->ht[i])==NULL) + continue; + sp=0; + while(1){ + func(p->key,p->data,ap); + if((pn=p->left)!=NULL){ + if(p->right){ + stack[sp++]=p->right; + } + p=pn; + } else { + if(p->right){ + p=p->right; + } else { + if(sp==0) + break; + p=stack[--sp]; + } + } + } + } + va_end(ap); +} + +void db_final(struct dbt *table,int (*func)(void*,void*,va_list),...) +{ + int i,sp; + struct dbn *p,*pn,*stack[64]; + va_list ap; + + va_start(ap,func); + for(i=0;i<HASH_SIZE;i++){ + if((p=table->ht[i])==NULL) + continue; + sp=0; + while(1){ + if(func) + func(p->key,p->data,ap); + if((pn=p->left)!=NULL){ + if(p->right){ + stack[sp++]=p->right; + } + } else { + if(p->right){ + pn=p->right; + } else { + if(sp==0) + break; + pn=stack[--sp]; + } + } +#ifdef MALLOC_DBN + free_dbn(p); +#else + free(p); +#endif + p=pn; + } + } + free(table); + va_end(ap); +} diff --git a/misc/src/common/db.h b/misc/src/common/db.h new file mode 100644 index 0000000..ea9acea --- /dev/null +++ b/misc/src/common/db.h @@ -0,0 +1,47 @@ +#ifndef _DB_H_ +#define _DB_H_ + +#include <stdarg.h> + +#define HASH_SIZE (256+27) + +#define RED 0 +#define BLACK 1 + +struct dbn { + struct dbn *parent,*left,*right; + int color; + void *key; + void *data; +}; + +struct dbt { + int (*cmp)(struct dbt*,void*,void*); + unsigned int (*hash)(struct dbt*,void*); + // which 1 - key, 2 - data, 3 - both + void (*release)(struct dbn*,int which); + int maxlen; + struct dbn *ht[HASH_SIZE]; +}; + +#define strdb_search(t,k) db_search((t),(void*)(k)) +#define strdb_insert(t,k,d) db_insert((t),(void*)(k),(void*)(d)) +#define strdb_erase(t,k) db_erase ((t),(void*)(k)) +#define strdb_foreach db_foreach +#define strdb_final db_final +#define numdb_search(t,k) db_search((t),(void*)(k)) +#define numdb_insert(t,k,d) db_insert((t),(void*)(k),(void*)(d)) +#define numdb_erase(t,k) db_erase ((t),(void*)(k)) +#define numdb_foreach db_foreach +#define numdb_final db_final + +struct dbt* strdb_init(int maxlen); +struct dbt* numdb_init(void); +void* db_search(struct dbt *table,void* key); +void* db_search2(struct dbt *table, const char *key); // [MouseJstr] +struct dbn* db_insert(struct dbt *table,void* key,void* data); +void* db_erase(struct dbt *table,void* key); +void db_foreach(struct dbt*,int(*)(void*,void*,va_list),...); +void db_final(struct dbt*,int(*)(void*,void*,va_list),...); + +#endif diff --git a/misc/src/common/grfio.c b/misc/src/common/grfio.c new file mode 100644 index 0000000..08a8b2a --- /dev/null +++ b/misc/src/common/grfio.c @@ -0,0 +1,948 @@ +/********************************************************************* + * + * Ragnarok Online Emulator : grfio.c -- grf file I/O Module + *-------------------------------------------------------------------- + * special need library : zlib + ********************************************************************* + * $Id: grfio.c,v 1.2 2004/09/29 17:31:49 kalaspuff Exp $ + * + * 2002/12/18... the original edition + * 2003/01/23 ... Code correction + * 2003/02/01 ... An addition and decryption processing are improved for LocalFile and two or more GRF(s) check processing. + * 2003/02/02 ... Even if there is no grf it does not stop -- as -- correction + * 2003/02/02... grf reading specification can be added later -- as -- correction (grfio_add function addition) + * 2003/02 / 03... at the time of grfio_resourcecheck processing the entry addition processing method -- correction + * 2003/02/05... change of the processing in grfio_init + * 2003/02/23... a local file check -- GRFIO_LOCAL -- switch (Defoe -- Function Off) + * 2003/10/21 ... The data of alpha client was read. + * 2003/11/10 ... Ready new grf format. + * 2003/11/11 ... version check fix & bug fix + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include <sys/stat.h> + +#include <zlib.h> + +#include "utils.h" +#include "grfio.h" +#include "mmo.h" + +#ifdef MEMWATCH +#include "memwatch.h" +#endif + +typedef unsigned char BYTE; +typedef unsigned short WORD; +typedef unsigned long DWORD; + +static char data_file[1024] = ""; // "data.grf"; +static char sdata_file[1024] = ""; // "sdata.grf"; +static char adata_file[1024] = ""; // "adata.grf"; +static char data_dir[1024] = ""; // "../"; + +// accessor to data_file,adata_file,sdata_file +char *grfio_setdatafile(const char *str){ strcpy(data_file,str); return data_file; } +char *grfio_setadatafile(const char *str){ strcpy(adata_file,str); return adata_file; } +char *grfio_setsdatafile(const char *str){ strcpy(sdata_file,str); return sdata_file; } + +//---------------------------- +// file entry table struct +//---------------------------- +typedef struct { + int srclen; // compressed size + int srclen_aligned; // + int declen; // original size + int srcpos; + short next; + char cycle; + char type; + char fn[128-4*5]; // file name + char gentry; // read grf file select +} FILELIST; +//gentry ... 0 : It acquires from a local file. +// It acquires from the resource file of 1>=:gentry_table[gentry-1]. +// 1<=: Check a local file. +// If it is, after re-setting to 0, it acquires from a local file. +// If there is nothing, mark reversal will be carried out, and it will re-set, and will acquire from a resource file as well as 1>=. + +//Since char defines *FILELIST.gentry, the maximum which can be added by grfio_add becomes by 127 pieces. + +#define GENTRY_LIMIT 127 +#define FILELIST_LIMIT 32768 // temporary maximum, and a theory top maximum are 2G. + +static FILELIST *filelist; +static int filelist_entrys; +static int filelist_maxentry; + +static char **gentry_table; +static int gentry_entrys; +static int gentry_maxentry; + +//---------------------------- +// file list hash table +//---------------------------- +static int filelist_hash[256]; + +//---------------------------- +// grf decode data table +//---------------------------- +static unsigned char BitMaskTable[8] = { + 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01 +}; + +static char BitSwapTable1[64] = { + 58, 50, 42, 34, 26, 18, 10, 2, 60, 52, 44, 36, 28, 20, 12, 4, + 62, 54, 46, 38, 30, 22, 14, 6, 64, 56, 48, 40, 32, 24, 16, 8, + 57, 49, 41, 33, 25, 17, 9, 1, 59, 51, 43, 35, 27, 19, 11, 3, + 61, 53, 45, 37, 29, 21, 13, 5, 63, 55, 47, 39, 31, 23, 15, 7 +}; +static char BitSwapTable2[64] = { + 40, 8, 48, 16, 56, 24, 64, 32, 39, 7, 47, 15, 55, 23, 63, 31, + 38, 6, 46, 14, 54, 22, 62, 30, 37, 5, 45, 13, 53, 21, 61, 29, + 36, 4, 44, 12, 52, 20, 60, 28, 35, 3, 43, 11, 51, 19, 59, 27, + 34, 2, 42, 10, 50, 18, 58, 26, 33, 1, 41, 9, 49, 17, 57, 25 +}; +static char BitSwapTable3[32] = { + 16, 7, 20, 21, 29, 12, 28, 17, 1, 15, 23, 26, 5, 18, 31, 10, + 2, 8, 24, 14, 32, 27, 3, 9, 19, 13, 30, 6, 22, 11, 4, 25 +}; + +static unsigned char NibbleData[4][64]={ + { + 0xef, 0x03, 0x41, 0xfd, 0xd8, 0x74, 0x1e, 0x47, 0x26, 0xef, 0xfb, 0x22, 0xb3, 0xd8, 0x84, 0x1e, + 0x39, 0xac, 0xa7, 0x60, 0x62, 0xc1, 0xcd, 0xba, 0x5c, 0x96, 0x90, 0x59, 0x05, 0x3b, 0x7a, 0x85, + 0x40, 0xfd, 0x1e, 0xc8, 0xe7, 0x8a, 0x8b, 0x21, 0xda, 0x43, 0x64, 0x9f, 0x2d, 0x14, 0xb1, 0x72, + 0xf5, 0x5b, 0xc8, 0xb6, 0x9c, 0x37, 0x76, 0xec, 0x39, 0xa0, 0xa3, 0x05, 0x52, 0x6e, 0x0f, 0xd9, + }, { + 0xa7, 0xdd, 0x0d, 0x78, 0x9e, 0x0b, 0xe3, 0x95, 0x60, 0x36, 0x36, 0x4f, 0xf9, 0x60, 0x5a, 0xa3, + 0x11, 0x24, 0xd2, 0x87, 0xc8, 0x52, 0x75, 0xec, 0xbb, 0xc1, 0x4c, 0xba, 0x24, 0xfe, 0x8f, 0x19, + 0xda, 0x13, 0x66, 0xaf, 0x49, 0xd0, 0x90, 0x06, 0x8c, 0x6a, 0xfb, 0x91, 0x37, 0x8d, 0x0d, 0x78, + 0xbf, 0x49, 0x11, 0xf4, 0x23, 0xe5, 0xce, 0x3b, 0x55, 0xbc, 0xa2, 0x57, 0xe8, 0x22, 0x74, 0xce, + }, { + 0x2c, 0xea, 0xc1, 0xbf, 0x4a, 0x24, 0x1f, 0xc2, 0x79, 0x47, 0xa2, 0x7c, 0xb6, 0xd9, 0x68, 0x15, + 0x80, 0x56, 0x5d, 0x01, 0x33, 0xfd, 0xf4, 0xae, 0xde, 0x30, 0x07, 0x9b, 0xe5, 0x83, 0x9b, 0x68, + 0x49, 0xb4, 0x2e, 0x83, 0x1f, 0xc2, 0xb5, 0x7c, 0xa2, 0x19, 0xd8, 0xe5, 0x7c, 0x2f, 0x83, 0xda, + 0xf7, 0x6b, 0x90, 0xfe, 0xc4, 0x01, 0x5a, 0x97, 0x61, 0xa6, 0x3d, 0x40, 0x0b, 0x58, 0xe6, 0x3d, + }, { + 0x4d, 0xd1, 0xb2, 0x0f, 0x28, 0xbd, 0xe4, 0x78, 0xf6, 0x4a, 0x0f, 0x93, 0x8b, 0x17, 0xd1, 0xa4, + 0x3a, 0xec, 0xc9, 0x35, 0x93, 0x56, 0x7e, 0xcb, 0x55, 0x20, 0xa0, 0xfe, 0x6c, 0x89, 0x17, 0x62, + 0x17, 0x62, 0x4b, 0xb1, 0xb4, 0xde, 0xd1, 0x87, 0xc9, 0x14, 0x3c, 0x4a, 0x7e, 0xa8, 0xe2, 0x7d, + 0xa0, 0x9f, 0xf6, 0x5c, 0x6a, 0x09, 0x8d, 0xf0, 0x0f, 0xe3, 0x53, 0x25, 0x95, 0x36, 0x28, 0xcb, + } +}; +/*----------------- + * long data get + */ +static unsigned int getlong(unsigned char *p) +{ + return *p+p[1]*256+(p[2]+p[3]*256)*65536; +} + +/*========================================== + * Grf data decode : Subs + *------------------------------------------ + */ +static void NibbleSwap(BYTE *Src, int len) +{ + for(;0<len;len--,Src++) { + *Src = (*Src>>4) | (*Src<<4); + } +} + +static void BitConvert(BYTE *Src,char *BitSwapTable) +{ + int lop,prm; + BYTE tmp[8]; + *(DWORD*)tmp=*(DWORD*)(tmp+4)=0; + for(lop=0;lop!=64;lop++) { + prm = BitSwapTable[lop]-1; + if (Src[(prm >> 3) & 7] & BitMaskTable[prm & 7]) { + tmp[(lop >> 3) & 7] |= BitMaskTable[lop & 7]; + } + } + *(DWORD*)Src = *(DWORD*)tmp; + *(DWORD*)(Src+4) = *(DWORD*)(tmp+4); +} + +static void BitConvert4(BYTE *Src) +{ + int lop,prm; + BYTE tmp[8]; + tmp[0] = ((Src[7]<<5) | (Src[4]>>3)) & 0x3f; // ..0 vutsr + tmp[1] = ((Src[4]<<1) | (Src[5]>>7)) & 0x3f; // ..srqpo n + tmp[2] = ((Src[4]<<5) | (Src[5]>>3)) & 0x3f; // ..o nmlkj + tmp[3] = ((Src[5]<<1) | (Src[6]>>7)) & 0x3f; // ..kjihg f + tmp[4] = ((Src[5]<<5) | (Src[6]>>3)) & 0x3f; // ..g fedcb + tmp[5] = ((Src[6]<<1) | (Src[7]>>7)) & 0x3f; // ..cba98 7 + tmp[6] = ((Src[6]<<5) | (Src[7]>>3)) & 0x3f; // ..8 76543 + tmp[7] = ((Src[7]<<1) | (Src[4]>>7)) & 0x3f; // ..43210 v + + for(lop=0;lop!=4;lop++) { + tmp[lop] = (NibbleData[lop][tmp[lop*2]] & 0xf0) + | (NibbleData[lop][tmp[lop*2+1]] & 0x0f); + } + + *(DWORD*)(tmp+4)=0; + for(lop=0;lop!=32;lop++) { + prm = BitSwapTable3[lop]-1; + if (tmp[prm >> 3] & BitMaskTable[prm & 7]) { + tmp[(lop >> 3) + 4] |= BitMaskTable[lop & 7]; + } + } + *(DWORD*)Src ^= *(DWORD*)(tmp+4); +} + +static void decode_des_etc(BYTE *buf,int len,int type,int cycle) +{ + int lop,cnt=0; + if(cycle<3) cycle=3; + else if(cycle<5) cycle++; + else if(cycle<7) cycle+=9; + else cycle+=15; + + for(lop=0;lop*8<len;lop++,buf+=8) { + if(lop<20 || (type==0 && lop%cycle==0)){ // des + BitConvert(buf,BitSwapTable1); + BitConvert4(buf); + BitConvert(buf,BitSwapTable2); + } else { + if(cnt==7 && type==0){ + int a; + BYTE tmp[8]; + *(DWORD*)tmp = *(DWORD*)buf; + *(DWORD*)(tmp+4) = *(DWORD*)(buf+4); + cnt=0; + buf[0]=tmp[3]; + buf[1]=tmp[4]; + buf[2]=tmp[6]; + buf[3]=tmp[0]; + buf[4]=tmp[1]; + buf[5]=tmp[2]; + buf[6]=tmp[5]; + a=tmp[7]; + if(a==0x00) a=0x2b; + else if(a==0x2b) a=0x00; + else if(a==0x01) a=0x68; + else if(a==0x68) a=0x01; + else if(a==0x48) a=0x77; + else if(a==0x77) a=0x48; + else if(a==0x60) a=0xff; + else if(a==0xff) a=0x60; + else if(a==0x6c) a=0x80; + else if(a==0x80) a=0x6c; + else if(a==0xb9) a=0xc0; + else if(a==0xc0) a=0xb9; + else if(a==0xeb) a=0xfe; + else if(a==0xfe) a=0xeb; + buf[7]=a; + } + cnt++; + } + } +} +/*========================================== + * Grf data decode sub : zip + *------------------------------------------ + */ +static int decode_zip(Bytef* dest, uLongf* destLen, const Bytef* source, uLong sourceLen) +{ + z_stream stream; + int err; + + stream.next_in = (Bytef*)source; + stream.avail_in = (uInt)sourceLen; + /* Check for source > 64K on 16-bit machine: */ + if ((uLong)stream.avail_in != sourceLen) return Z_BUF_ERROR; + + stream.next_out = dest; + stream.avail_out = (uInt)*destLen; + if ((uLong)stream.avail_out != *destLen) return Z_BUF_ERROR; + + stream.zalloc = (alloc_func)0; + stream.zfree = (free_func)0; + + err = inflateInit(&stream); + if (err != Z_OK) return err; + + err = inflate(&stream, Z_FINISH); + if (err != Z_STREAM_END) { + inflateEnd(&stream); + return err == Z_OK ? Z_BUF_ERROR : err; + } + *destLen = stream.total_out; + + err = inflateEnd(&stream); + return err; +} +/*********************************************************** + *** File List Sobroutines *** + ***********************************************************/ + +/*========================================== + * File List : Hash make + *------------------------------------------ + */ +static int filehash(unsigned char *fname) +{ + unsigned int hash=0; + while(*fname) { + hash = ((hash<<1)+(hash>>7)*9+tolower(*fname)); + fname++; + } + return hash & 255; +} + +/*========================================== + * File List : Hash initalize + *------------------------------------------ + */ +static void hashinit(void) +{ + int lop; + for(lop=0;lop<256;lop++) + filelist_hash[lop]=-1; +} + +/*========================================== + * File List : File find + *------------------------------------------ + */ +FILELIST *filelist_find(char *fname) +{ + int hash; + + for(hash=filelist_hash[filehash(fname)];hash>=0;hash=filelist[hash].next) { + if(strcasecmp(filelist[hash].fn,fname)==0) + break; + } + + return (hash>=0)? &filelist[hash] : NULL; +} + +/*========================================== + * File List : Filelist add + *------------------------------------------ + */ +#define FILELIST_ADDS 1024 // number increment of file lists ` + +static FILELIST* filelist_add(FILELIST *entry) +{ + int hash; + + if (filelist_entrys>=FILELIST_LIMIT) { + printf("filelist limit : filelist_add\n"); + exit(1); + } + + if (filelist_entrys>=filelist_maxentry) { + FILELIST *new_filelist = (FILELIST*)realloc( + (void*)filelist, (filelist_maxentry+FILELIST_ADDS)*sizeof(FILELIST) ); + if (new_filelist != NULL) { + filelist = new_filelist; + memset(filelist + filelist_maxentry, '\0', + FILELIST_ADDS * sizeof(FILELIST)); + filelist_maxentry += FILELIST_ADDS; + } else { + printf("out of memory : filelist_add\n"); + exit(1); + } + } + + memcpy( &filelist[filelist_entrys], entry, sizeof(FILELIST) ); + + hash = filehash(entry->fn); + filelist[filelist_entrys].next = filelist_hash[hash]; + filelist_hash[hash] = filelist_entrys; + + filelist_entrys++; + + return &filelist[filelist_entrys-1]; +} + +static FILELIST* filelist_modify(FILELIST *entry) +{ + FILELIST *fentry; + if ((fentry=filelist_find(entry->fn))!=NULL) { + int tmp = fentry->next; + memcpy( fentry, entry, sizeof(FILELIST) ); + fentry->next = tmp; + } else { + fentry = filelist_add(entry); + } + return fentry; +} + +/*========================================== + * File List : filelist size adjust + *------------------------------------------ + */ +static void filelist_adjust(void) +{ + if (filelist!=NULL) { + if (filelist_maxentry>filelist_entrys) { + FILELIST *new_filelist = (FILELIST*)realloc( + (void*)filelist,filelist_entrys*sizeof(FILELIST) ); + if (new_filelist != NULL) { + filelist = new_filelist; + filelist_maxentry = filelist_entrys; + } else { + printf("out of memory : filelist\n"); + exit(1); + } + } + } +} + +/*********************************************************** + *** Grfio Sobroutines *** + ***********************************************************/ +/*========================================== + * Grfio : Resnametable replace + *------------------------------------------ + */ +char* grfio_resnametable(char* fname, char *lfname) +{ + FILE *fp; + char *p; + char w1[256],w2[256],restable[256],line[512]; + + sprintf(restable,"%sdata\\resnametable.txt",data_dir); + + for(p=&restable[0];*p!=0;p++) if (*p=='\\') *p = '/'; + + fp = fopen(restable,"rb"); + if(fp==NULL) { + printf("%s not found\n",restable); + exit(1); // 1:not found error + } + + while(fgets(line,508,fp)){ + if((sscanf(line,"%[^#]#%[^#]#",w1,w2)==2) && (sscanf(fname,"%*5s%s",lfname)==1) && (!strcmpi(w1,lfname))){ + sprintf(lfname,"data\\%s",w2); + fclose(fp); + return lfname; + } + } + fclose(fp); + return fname; + +} + +/*========================================== + * Grfio : Resource file size get + *------------------------------------------ + */ +int grfio_size(char *fname) +{ + FILELIST *entry; + + entry = filelist_find(fname); + + if (entry==NULL || entry->gentry<0) { // LocalFileCheck + char lfname[256],rname[256],*p; + FILELIST lentry; + struct stat st; + + //printf("%s\t",fname); + sprintf(rname,"%s",grfio_resnametable(fname,lfname)); + //printf("%s\n",rname); + sprintf(lfname,"%s%s",data_dir,rname); + //printf("%s\n",lfname); + + for(p=&lfname[0];*p!=0;p++) if (*p=='\\') *p = '/'; // * At the time of Unix + + if (stat(lfname,&st)==0) { + strncpy(lentry.fn, fname, sizeof(lentry.fn)-1 ); + lentry.declen = st.st_size; + lentry.gentry = 0; // 0:LocalFile + entry = filelist_modify(&lentry); + } else if (entry==NULL) { + printf("%s not found\n", fname); + //exit(1); + return -1; + } + } + return entry->declen; +} + +/*========================================== + * Grfio : Resource file read & size get + *------------------------------------------ + */ +void* grfio_reads(char *fname, int *size) +{ + FILE *in = NULL; + unsigned char *buf=NULL,*buf2=NULL; + char *gfname; + FILELIST *entry; + + entry = filelist_find(fname); + + if (entry==NULL || entry->gentry<=0) { // LocalFileCheck + char lfname[256],rname[256],*p; + FILELIST lentry; + + strncpy(lfname,fname,255); + sprintf(rname,"%s",grfio_resnametable(fname,lfname)); + sprintf(lfname,"%s%s",data_dir,rname); + //printf("%s\n",lfname); + + for(p=&lfname[0];*p!=0;p++) if (*p=='\\') *p = '/'; // * At the time of Unix + + in = fopen(lfname,"rb"); + if(in!=NULL) { + if (entry!=NULL && entry->gentry==0) { + lentry.declen=entry->declen; + } else { + fseek(in,0,2); // SEEK_END + lentry.declen = ftell(in); + } + fseek(in,0,0); // SEEK_SET + buf2 = calloc(lentry.declen+1024, 1); + if (buf2==NULL) { + printf("file read memory allocate error : declen\n"); + goto errret; + } + fread(buf2,1,lentry.declen,in); + fclose(in); in = NULL; + strncpy( lentry.fn, fname, sizeof(lentry.fn)-1 ); + lentry.gentry = 0; // 0:LocalFile + entry = filelist_modify(&lentry); + } else { + if (entry!=NULL && entry->gentry<0) { + entry->gentry = -entry->gentry; // local file checked + } else { + printf("%s not found\n", fname); + //goto errret; + free(buf2); + return NULL; + } + } + } + if (entry!=NULL && entry->gentry>0) { // Archive[GRF] File Read + buf = calloc(entry->srclen_aligned+1024, 1); + if (buf==NULL) { + printf("file read memory allocate error : srclen_aligned\n"); + goto errret; + } + gfname = gentry_table[entry->gentry-1]; + in = fopen(gfname,"rb"); + if(in==NULL) { + printf("%s not found\n",gfname); + //goto errret; + free(buf); + return NULL; + } + fseek(in,entry->srcpos,0); + fread(buf,1,entry->srclen_aligned,in); + fclose(in); + buf2=calloc(entry->declen+1024, 1); + if (buf2==NULL) { + printf("file decode memory allocate error\n"); + goto errret; + } + if(entry->type==1 || entry->type==3 || entry->type==5) { + uLongf len; + if (entry->cycle>=0) { + decode_des_etc(buf,entry->srclen_aligned,entry->cycle==0,entry->cycle); + } + len=entry->declen; + decode_zip(buf2,&len,buf,entry->srclen); + if(len!=entry->declen) { + printf("decode_zip size miss match err: %d != %d\n",(int)len,entry->declen); + goto errret; + } + } else { + memcpy(buf2,buf,entry->declen); + } + free(buf); + } + if (size!=NULL && entry!=NULL) + *size = entry->declen; + return buf2; +errret: + if (buf!=NULL) free(buf); + if (buf2!=NULL) free(buf2); + if (in!=NULL) fclose(in); + exit(1); //return NULL; +} + +/*========================================== + * Grfio : Resource file read + *------------------------------------------ + */ +void* grfio_read(char *fname) +{ + return grfio_reads(fname,NULL); +} + +/*========================================== + * Resource filename decode + *------------------------------------------ + */ +static unsigned char * decode_filename(unsigned char *buf,int len) +{ + int lop; + for(lop=0;lop<len;lop+=8) { + NibbleSwap(&buf[lop],8); + BitConvert(&buf[lop],BitSwapTable1); + BitConvert4(&buf[lop]); + BitConvert(&buf[lop],BitSwapTable2); + } + return buf; +} + +/*========================================== + * Grfio : Entry table read + *------------------------------------------ + */ +static int grfio_entryread(char *gfname,int gentry) +{ + FILE *fp; + int grf_size,list_size; + unsigned char grf_header[0x2e]; + int lop,entry,entrys,ofs,grf_version; + unsigned char *fname; + unsigned char *grf_filelist; + + fp = fopen(gfname,"rb"); + if(fp==NULL) { + printf("%s not found\n",gfname); + return 1; // 1:not found error + } + + fseek(fp,0,2); // SEEK_END + grf_size = ftell(fp); + fseek(fp,0,0); // SEEK_SET + fread(grf_header,1,0x2e,fp); + if(strcmp(grf_header,"Master of Magic") || fseek(fp,getlong(grf_header+0x1e),1)){ // SEEK_CUR + fclose(fp); + printf("%s read error\n",gfname); + return 2; // 2:file format error + } + + grf_version = getlong(grf_header+0x2a) >> 8; + + if (grf_version==0x01) { //****** Grf version 01xx ****** + list_size = grf_size-ftell(fp); + grf_filelist = calloc(list_size, 1); + if(grf_filelist==NULL){ + fclose(fp); + printf("out of memory : grf_filelist\n"); + return 3; // 3:memory alloc error + } + fread(grf_filelist,1,list_size,fp); + fclose(fp); + + entrys = getlong(grf_header+0x26) - getlong(grf_header+0x22) - 7; + + // Get an entry + for(entry=0,ofs=0;entry<entrys;entry++){ + int ofs2,srclen,srccount,type; + char *period_ptr; + FILELIST aentry; + + ofs2 = ofs+getlong(grf_filelist+ofs)+4; + type = grf_filelist[ofs2+12]; + if( type!=0 ){ // Directory Index ... skip + fname = decode_filename(grf_filelist+ofs+6,grf_filelist[ofs]-6); + if(strlen(fname)>sizeof(aentry.fn)-1){ + printf("file name too long : %s\n",fname); + free(grf_filelist); + exit(1); + } + srclen=0; + if((period_ptr=rindex(fname,'.'))!=NULL){ + for(lop=0;lop<4;lop++) { + if(strcasecmp(period_ptr,".gnd\0.gat\0.act\0.str"+lop*5)==0) + break; + } + srclen=getlong(grf_filelist+ofs2)-getlong(grf_filelist+ofs2+8)-715; + if(lop==4) { + for(lop=10,srccount=1;srclen>=lop;lop=lop*10,srccount++); + } else { + srccount=0; + } + } else { + srccount=0; + } + + aentry.srclen = srclen; + aentry.srclen_aligned = getlong(grf_filelist+ofs2+4)-37579; + aentry.declen = getlong(grf_filelist+ofs2+8); + aentry.srcpos = getlong(grf_filelist+ofs2+13)+0x2e; + aentry.cycle = srccount; + aentry.type = type; + strncpy(aentry.fn,fname,sizeof(aentry.fn)-1); +#ifdef GRFIO_LOCAL + aentry.gentry = -(gentry+1); // As Flag for making it a negative number carrying out the first time LocalFileCheck +#else + aentry.gentry = gentry+1; // With no first time LocalFileCheck +#endif + filelist_modify(&aentry); + } + ofs = ofs2 + 17; + } + free(grf_filelist); + + } else if (grf_version==0x02) { //****** Grf version 02xx ****** + unsigned char eheader[8]; + unsigned char *rBuf; + uLongf rSize,eSize; + + fread(eheader,1,8,fp); + rSize = getlong(eheader); // Read Size + eSize = getlong(eheader+4); // Extend Size + + if (rSize > grf_size-ftell(fp)) { + fclose(fp); + printf("Illegal data format : grf compress entry size\n"); + return 4; + } + + rBuf = calloc( rSize , 1); // Get a Read Size + if (rBuf==NULL) { + fclose(fp); + printf("out of memory : grf compress entry table buffer\n"); + return 3; + } + grf_filelist = calloc( eSize , 1); // Get a Extend Size + if (grf_filelist==NULL) { + free(rBuf); + fclose(fp); + printf("out of memory : grf extract entry table buffer\n"); + return 3; + } + fread(rBuf,1,rSize,fp); + fclose(fp); + decode_zip(grf_filelist,&eSize,rBuf,rSize); // Decode function + list_size = eSize; + free(rBuf); + + entrys = getlong(grf_header+0x26) - 7; + + // Get an entry + for(entry=0,ofs=0;entry<entrys;entry++){ + int ofs2,srclen,srccount,type; + FILELIST aentry; + + fname = grf_filelist+ofs; + if (strlen(fname)>sizeof(aentry.fn)-1) { + printf("grf : file name too long : %s\n",fname); + free(grf_filelist); + exit(1); + } + ofs2 = ofs+strlen(grf_filelist+ofs)+1; + type = grf_filelist[ofs2+12]; + if(type==1 || type==3 || type==5) { + srclen=getlong(grf_filelist+ofs2); + if (grf_filelist[ofs2+12]==3) { + for(lop=10,srccount=1;srclen>=lop;lop=lop*10,srccount++); + } else if (grf_filelist[ofs2+12]==5) { + srccount = 0; + } else { // if (grf_filelist[ofs2+12]==1) { + srccount = -1; + } + + aentry.srclen = srclen; + aentry.srclen_aligned = getlong(grf_filelist+ofs2+4); + aentry.declen = getlong(grf_filelist+ofs2+8); + aentry.srcpos = getlong(grf_filelist+ofs2+13)+0x2e; + aentry.cycle = srccount; + aentry.type = type; + strncpy(aentry.fn,fname,sizeof(aentry.fn)-1); +#ifdef GRFIO_LOCAL + aentry.gentry = -(gentry+1); // As Flag for making it a negative number carrying out the first time LocalFileCheck +#else + aentry.gentry = gentry+1; // With no first time LocalFileCheck +#endif + filelist_modify(&aentry); + } + ofs = ofs2 + 17; + } + free(grf_filelist); + + } else { //****** Grf Other version ****** + fclose(fp); + printf("not support grf versions : %04x\n",getlong(grf_header+0x2a)); + return 4; + } + + filelist_adjust(); // Unnecessary area release of filelist + + return 0; // 0:no error +} + +/*========================================== + * Grfio : Resource file check + *------------------------------------------ + */ +static void grfio_resourcecheck() +{ + int size; + unsigned char *buf,*ptr; + char w1[256],w2[256],src[256],dst[256]; + FILELIST *entry; + + buf=grfio_reads("data\\resnametable.txt",&size); + buf[size] = 0; + + for(ptr=buf;ptr-buf<size;) { + if(sscanf(ptr,"%[^#]#%[^#]#",w1,w2)==2){ + if(strstr(w2,"bmp")){ + sprintf(src,"data\\texture\\%s",w1); + sprintf(dst,"data\\texture\\%s",w2); + } else { + sprintf(src,"data\\%s",w1); + sprintf(dst,"data\\%s",w2); + } + entry = filelist_find(dst); + if (entry!=NULL) { + FILELIST fentry; + memcpy( &fentry, entry, sizeof(FILELIST) ); + strncpy( fentry.fn ,src, sizeof(fentry.fn)-1 ); + filelist_modify(&fentry); + } else { + //printf("file not found in data.grf : %s < %s\n",dst,src); + } + } + ptr = strchr(ptr,'\n'); // Next line + if (!ptr) break; + ptr++; + } + free(buf); + filelist_adjust(); // Unnecessary area release of filelist +} + +/*========================================== + * Grfio : Resource add + *------------------------------------------ + */ +#define GENTRY_ADDS 16 // The number increment of gentry_table entries + +int grfio_add(char *fname) +{ + int len,result; + char *buf; + + if (gentry_entrys>=GENTRY_LIMIT) { + printf("gentrys limit : grfio_add\n"); + exit(1); + } + + printf("%s file reading...\n",fname); + + if (gentry_entrys>=gentry_maxentry) { + char **new_gentry = (char**)realloc( + (void*)gentry_table,(gentry_maxentry+GENTRY_ADDS)*sizeof(char*) ); + if (new_gentry!=NULL) { + int lop; + gentry_table = new_gentry; + gentry_maxentry += GENTRY_ADDS; + for(lop=gentry_entrys;lop<gentry_maxentry;lop++) + gentry_table[lop] = NULL; + } else { + printf("out of memory : grfio_add\n"); + exit(1); + } + } + len = strlen( fname ); + buf = calloc(len+1, 1); + if (buf==NULL) { + printf("out of memory : gentry\n"); + exit(1); + } + strcpy( buf, fname ); + gentry_table[gentry_entrys++] = buf; + + result = grfio_entryread(fname,gentry_entrys-1); + + if (result==0) { + // Resource check + grfio_resourcecheck(); + } + + return result; +} + +/*========================================== + * Grfio : Finalize + *------------------------------------------ + */ +void grfio_final(void) +{ + int lop; + + if (filelist!=NULL) free(filelist); + filelist = NULL; + filelist_entrys = filelist_maxentry = 0; + + if (gentry_table!=NULL) { + for(lop=0;lop<gentry_entrys;lop++) { + if (gentry_table[lop]!=NULL) { + free(gentry_table[lop]); + } + } + free(gentry_table); + } + gentry_table = NULL; + gentry_entrys = gentry_maxentry = 0; +} + +/*========================================== + * Grfio : Initialize + *------------------------------------------ + */ +void grfio_init(char *fname) +{ + FILE *data_conf; + char line[1024], w1[1024], w2[1024]; + int result = 0, result2 = 0, result3 = 0; + + data_conf = fopen(fname, "r"); + + // It will read, if there is grf-files.txt. + if (data_conf) { + while(fgets(line, 1020, data_conf)) { + if (sscanf(line, "%[^:]: %[^\r\n]", w1, w2) == 2) { + if(strcmp(w1, "data") == 0) + strcpy(data_file, w2); + else if(strcmp(w1, "sdata") == 0) + strcpy(sdata_file, w2); + else if(strcmp(w1, "adata") == 0) + strcpy(adata_file, w2); + else if(strcmp(w1,"data_dir") == 0) + strcpy(data_dir, w2); + } + } + + fclose(data_conf); + printf("read %s done\n",fname); + } // end of reading grf-files.txt + + hashinit(); // hash table initialization + + filelist = NULL; filelist_entrys = filelist_maxentry = 0; + gentry_table = NULL; gentry_entrys = gentry_maxentry = 0; + atexit(grfio_final); // End processing definition + + // Entry table reading + + if (strcmp(data_file, "") != 0) // If data directive exists in grf-files.txt (i.e. data_file is not equal to "") + result = grfio_add(data_file); // Primary data file + + if (strcmp(sdata_file, "") != 0) // If sdata directive exists in grf-files.txt (i.e. sdata_file is not equal to "") + result2 = grfio_add(sdata_file); // Sakray data file + + if (strcmp(adata_file, "") != 0) // If data directive exists in grf-files.txt (i.e. adata_file is not equal to "") + result3 = grfio_add(adata_file); // Alpha version data file + + if (result != 0 && result2 != 0 && result3 != 0) { + printf("not grf file readed exit!!\n"); + exit(1); // It ends, if a resource cannot read one. + } +} diff --git a/misc/src/common/grfio.h b/misc/src/common/grfio.h new file mode 100644 index 0000000..53b9da8 --- /dev/null +++ b/misc/src/common/grfio.h @@ -0,0 +1,16 @@ +// $Id: grfio.h,v 1.1.1.1 2004/09/10 17:44:49 MagicalTux Exp $ +#ifndef _GRFIO_H_ +#define _GRFIO_H_ + +void grfio_init(char*); // GRFIO Initialize +int grfio_add(char*); // GRFIO Resource file add +void* grfio_read(char*); // GRFIO data file read +void* grfio_reads(char*,int*); // GRFIO data file read & size get +int grfio_size(char*); // GRFIO data file size get + +// Accessor to GRF filenames +char *grfio_setdatafile(const char *str); +char *grfio_setadatafile(const char *str); +char *grfio_setsdatafile(const char *str); + +#endif // _GRFIO_H_ diff --git a/misc/src/common/lock.c b/misc/src/common/lock.c new file mode 100644 index 0000000..9a2205b --- /dev/null +++ b/misc/src/common/lock.c @@ -0,0 +1,37 @@ + +#include <stdio.h> +#include "lock.h" + +// 書き込みファイルの保護処理 +// (書き込みが終わるまで、旧ファイルを保管しておく) + +// 新しいファイルの書き込み開始 +FILE* lock_fopen(const char* filename,int *info) { + char newfile[512]; + FILE *fp; + int no = 0; + + // 安全なファイル名を得る(手抜き) + do { + sprintf(newfile,"%s_%04d.tmp",filename,++no); + } while((fp = fopen(newfile,"r")) && (fclose(fp), no<9999) ); + *info = no; + return fopen(newfile,"w"); +} + +// 旧ファイルを削除&新ファイルをリネーム +int lock_fclose(FILE *fp,const char* filename,int *info) { + int ret = 0; + char newfile[512]; + if(fp != NULL) { + ret = fclose(fp); + sprintf(newfile,"%s_%04d.tmp",filename,*info); + remove(filename); + // このタイミングで落ちると最悪。 + rename(newfile,filename); + return ret; + } else { + return 1; + } +} + diff --git a/misc/src/common/lock.h b/misc/src/common/lock.h new file mode 100644 index 0000000..795bf88 --- /dev/null +++ b/misc/src/common/lock.h @@ -0,0 +1,8 @@ +#ifndef _LOCK_H_ +#define _LOCK_H_ + +FILE* lock_fopen(const char* filename,int *info); +int lock_fclose(FILE *fp,const char* filename,int *info); + +#endif + diff --git a/misc/src/common/malloc.c b/misc/src/common/malloc.c new file mode 100644 index 0000000..eda9bc2 --- /dev/null +++ b/misc/src/common/malloc.c @@ -0,0 +1,44 @@ +#include <stdio.h> +#include <stdlib.h> +#include "malloc.h" + +void* aMalloc_( size_t size, const char *file, int line, const char *func ) +{ + void *ret; + +// printf("%s:%d: in func %s: malloc %d\n",file,line,func,size); + ret=malloc(size); + if(ret==NULL){ + printf("%s:%d: in func %s: malloc error out of memory!\n",file,line,func); + exit(1); + + } + return ret; +} +void* aCalloc_( size_t num, size_t size, const char *file, int line, const char *func ) +{ + void *ret; + +// printf("%s:%d: in func %s: calloc %d %d\n",file,line,func,num,size); + ret=calloc(num,size); + if(ret==NULL){ + printf("%s:%d: in func %s: calloc error out of memory!\n",file,line,func); + exit(1); + + } + return ret; +} + +void* aRealloc_( void *p, size_t size, const char *file, int line, const char *func ) +{ + void *ret; + +// printf("%s:%d: in func %s: realloc %p %d\n",file,line,func,p,size); + ret=realloc(p,size); + if(ret==NULL){ + printf("%s:%d: in func %s: realloc error out of memory!\n",file,line,func); + exit(1); + + } + return ret; +} diff --git a/misc/src/common/malloc.h b/misc/src/common/malloc.h new file mode 100644 index 0000000..3733a5e --- /dev/null +++ b/misc/src/common/malloc.h @@ -0,0 +1,25 @@ +#ifndef _MALLOC_H_ +#define _MALLOC_H_ + +#include <stdlib.h> + +#if __STDC_VERSION__ < 199901L +# if __GNUC__ >= 2 +# define __func__ __FUNCTION__ +# else +# define __func__ "" +# endif +#endif + +#define ALC_MARK __FILE__, __LINE__, __func__ + +void* aMalloc_( size_t size, const char *file, int line, const char *func ); +void* aCalloc_( size_t num, size_t size, const char *file, int line, const char *func ); +void* aRealloc_( void *p, size_t size, const char *file, int line, const char *func ); + +#define aMalloc(n) aMalloc_(n,ALC_MARK) +#define aCalloc(m,n) aCalloc_(m,n,ALC_MARK) +#define aRealloc(p,n) aRealloc_(p,n,ALC_MARK) + + +#endif diff --git a/misc/src/common/mmo.h b/misc/src/common/mmo.h new file mode 100644 index 0000000..4105135 --- /dev/null +++ b/misc/src/common/mmo.h @@ -0,0 +1,304 @@ +// $Id: mmo.h,v 1.3 2004/09/25 20:12:25 PoW Exp $ +// Original : mmo.h 2003/03/14 12:07:02 Rev.1.7 + +#ifndef _MMO_H_ +#define _MMO_H_ + +#include <time.h> +#include "utils.h" // LCCWIN32 + +#ifdef CYGWIN +// txtやlogなどの書き出すファイルの改行コード +#define RETCODE "\r\n" // (CR/LF:Windows系) +#else +#define RETCODE "\n" // (LF:Unix系) +#endif + +#define FIFOSIZE_SERVERLINK 128*1024 + +// set to 0 to not check IP of player between each server. +// set to another value if you want to check (1) +#define CMP_AUTHFIFO_IP 1 + +#define CMP_AUTHFIFO_LOGIN2 1 + +#define MAX_MAP_PER_SERVER 512 +#define MAX_INVENTORY 100 +#define MAX_AMOUNT 30000 +#define MAX_ZENY 1000000000 // 1G zeny +#define MAX_CART 100 +#define MAX_SKILL 450 +#define GLOBAL_REG_NUM 96 +#define ACCOUNT_REG_NUM 16 +#define ACCOUNT_REG2_NUM 16 +#define DEFAULT_WALK_SPEED 150 +#define MIN_WALK_SPEED 0 +#define MAX_WALK_SPEED 1000 +#define MAX_STORAGE 300 +#define MAX_GUILD_STORAGE 1000 +#define MAX_PARTY 12 +#define MAX_GUILD 36 // increased max guild members to accomodate for +2 increase for extension levels [Valaris] (removed) [PoW] +#define MAX_GUILDPOSITION 20 // increased max guild positions to accomodate for all members [Valaris] (removed) [PoW] +#define MAX_GUILDEXPLUSION 32 +#define MAX_GUILDALLIANCE 16 +#define MAX_GUILDSKILL 8 +#define MAX_GUILDCASTLE 24 // increased to include novice castles [Valaris] +#define MAX_GUILDLEVEL 50 + +#define MIN_HAIR_STYLE battle_config.min_hair_style +#define MAX_HAIR_STYLE battle_config.max_hair_style +#define MIN_HAIR_COLOR battle_config.min_hair_color +#define MAX_HAIR_COLOR battle_config.max_hair_color +#define MIN_CLOTH_COLOR battle_config.min_cloth_color +#define MAX_CLOTH_COLOR battle_config.max_cloth_color + +// for produce +#define MIN_ATTRIBUTE 0 +#define MAX_ATTRIBUTE 4 +#define ATTRIBUTE_NORMAL 0 +#define MIN_STAR 0 +#define MAX_STAR 3 + +#define MIN_PORTAL_MEMO 0 +#define MAX_PORTAL_MEMO 2 + +#define MAX_STATUS_TYPE 5 + +#define WEDDING_RING_M 2634 +#define WEDDING_RING_F 2635 + +#define CHAR_CONF_NAME "conf/char_athena.conf" + +struct item { + int id; + short nameid; + short amount; + unsigned short equip; + char identify; + char refine; + char attribute; + short card[4]; + short broken; +}; + +struct point{ + char map[24]; + short x,y; +}; + +struct skill { + unsigned short id,lv,flag; +}; + +struct global_reg { + char str[32]; + int value; +}; + +struct s_pet { + int account_id; + int char_id; + int pet_id; + short class; + short level; + short egg_id;//pet egg id + short equip;//pet equip name_id + short intimate;//pet friendly + short hungry;//pet hungry + char name[24]; + char rename_flag; + char incuvate; +}; + +struct mmo_charstatus { + int char_id; + int account_id; + int partner_id; + + int base_exp,job_exp,zeny; + + short class; + short status_point,skill_point; + int hp,max_hp,sp,max_sp; + short option,karma,manner; + short hair,hair_color,clothes_color; + int party_id,guild_id,pet_id; + + short weapon,shield; + short head_top,head_mid,head_bottom; + + char name[24]; + unsigned char base_level,job_level; + short str,agi,vit,int_,dex,luk; + unsigned char char_num,sex; + + unsigned long mapip; + unsigned int mapport; + + struct point last_point,save_point,memo_point[10]; + struct item inventory[MAX_INVENTORY],cart[MAX_CART]; + struct skill skill[MAX_SKILL]; + int global_reg_num; + struct global_reg global_reg[GLOBAL_REG_NUM]; + int account_reg_num; + struct global_reg account_reg[ACCOUNT_REG_NUM]; + int account_reg2_num; + struct global_reg account_reg2[ACCOUNT_REG2_NUM]; +}; + +struct storage { + int account_id; + short storage_status; + short storage_amount; + struct item storage[MAX_STORAGE]; +}; + +struct guild_storage { + int guild_id; + short storage_status; + short storage_amount; + struct item storage[MAX_GUILD_STORAGE]; +}; + +struct map_session_data; + +struct gm_account { + int account_id; + int level; +}; + +struct party_member { + int account_id; + char name[24],map[24]; + int leader,online,lv; + struct map_session_data *sd; +}; + +struct party { + int party_id; + char name[24]; + int exp; + int item; + struct party_member member[MAX_PARTY]; +}; + +struct guild_member { + int account_id, char_id; + short hair,hair_color,gender,class,lv; + int exp,exp_payper; + short online,position; + int rsv1,rsv2; + char name[24]; + struct map_session_data *sd; +}; + +struct guild_position { + char name[24]; + int mode; + int exp_mode; +}; + +struct guild_alliance { + int opposition; + int guild_id; + char name[24]; +}; + +struct guild_explusion { + char name[24]; + char mes[40]; + char acc[40]; + int account_id; + int rsv1,rsv2,rsv3; +}; + +struct guild_skill { + int id,lv; +}; + +struct guild { + int guild_id; + short guild_lv, connect_member, max_member, average_lv; + int exp,next_exp,skill_point,castle_id; + char name[24],master[24]; + struct guild_member member[MAX_GUILD]; + struct guild_position position[MAX_GUILDPOSITION]; + char mes1[60],mes2[120]; + int emblem_len,emblem_id; + char emblem_data[2048]; + struct guild_alliance alliance[MAX_GUILDALLIANCE]; + struct guild_explusion explusion[MAX_GUILDEXPLUSION]; + struct guild_skill skill[MAX_GUILDSKILL]; +}; + +struct guild_castle { + int castle_id; + char map_name[24]; + char castle_name[24]; + char castle_event[24]; + int guild_id; + int economy; + int defense; + int triggerE; + int triggerD; + int nextTime; + int payTime; + int createTime; + int visibleC; + int visibleG0; + int visibleG1; + int visibleG2; + int visibleG3; + int visibleG4; + int visibleG5; + int visibleG6; + int visibleG7; + int Ghp0; // added Guardian HP [Valaris] + int Ghp1; + int Ghp2; + int Ghp3; + int Ghp4; + int Ghp5; + int Ghp6; + int Ghp7; + int GID0; + int GID1; + int GID2; + int GID3; + int GID4; + int GID5; + int GID6; + int GID7; // end addition [Valaris] +}; +struct square { + int val1[5]; + int val2[5]; +}; + +enum { + GBI_EXP =1, // ギルドのEXP + GBI_GUILDLV =2, // ギルドのLv + GBI_SKILLPOINT =3, // ギルドのスキルポイント + GBI_SKILLLV =4, // ギルドスキルLv + + GMI_POSITION =0, // メンバーの役職変更 + GMI_EXP =1, // メンバーのEXP + +}; + +#ifndef LCCWIN32 +#ifndef strcmpi +#define strcmpi strcasecmp +#endif +#ifndef stricmp +#define stricmp strcasecmp +#endif +#ifndef strncmpi +#define strncmpi strncasecmp +#endif +#ifndef strnicmp +#define strnicmp strncasecmp +#endif +#endif + +#endif // _MMO_H_ diff --git a/misc/src/common/nullpo.c b/misc/src/common/nullpo.c new file mode 100644 index 0000000..5fbf5fc --- /dev/null +++ b/misc/src/common/nullpo.c @@ -0,0 +1,90 @@ +#include <stdio.h> +#include <stdarg.h> +#include <string.h> +#include "nullpo.h" +// #include "logs.h" // 布石してみる + +static void nullpo_info_core(const char *file, int line, const char *func, + const char *fmt, va_list ap); + +/*====================================== + * Nullチェック 及び 情報出力 + *-------------------------------------- + */ +int nullpo_chk_f(const char *file, int line, const char *func, const void *target, + const char *fmt, ...) +{ + va_list ap; + + if (target != NULL) + return 0; + + va_start(ap, fmt); + nullpo_info_core(file, line, func, fmt, ap); + va_end(ap); + return 1; +} + +int nullpo_chk(const char *file, int line, const char *func, const void *target) +{ + if (target != NULL) + return 0; + + nullpo_info_core(file, line, func, NULL, NULL); + return 1; +} + + +/*====================================== + * nullpo情報出力(外部呼出し向けラッパ) + *-------------------------------------- + */ +void nullpo_info_f(const char *file, int line, const char *func, + const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + nullpo_info_core(file, line, func, fmt, ap); + va_end(ap); +} + +void nullpo_info(const char *file, int line, const char *func) +{ + nullpo_info_core(file, line, func, NULL, NULL); +} + + +/*====================================== + * nullpo情報出力(Main) + *-------------------------------------- + */ +static void nullpo_info_core(const char *file, int line, const char *func, + const char *fmt, va_list ap) +{ + if (file == NULL) + file = "??"; + + func = + func == NULL ? "unknown": + func[0] == '\0' ? "unknown": + func; + + printf("--- nullpo info --------------------------------------------\n"); + printf("%s:%d: in func `%s'\n", file, line, func); + if (fmt != NULL) + { + if (fmt[0] != '\0') + { + vprintf(fmt, ap); + + // 最後に改行したか確認 + if (fmt[strlen(fmt)-1] != '\n') + printf("\n"); + } + } + printf("--- end nullpo info ----------------------------------------\n"); + + // ここらでnullpoログをファイルに書き出せたら + // まとめて提出できるなと思っていたり。 +} diff --git a/misc/src/common/nullpo.h b/misc/src/common/nullpo.h new file mode 100644 index 0000000..2d33500 --- /dev/null +++ b/misc/src/common/nullpo.h @@ -0,0 +1,222 @@ +#ifndef _NULLPO_H_ +#define _NULLPO_H_ + + +#define NULLPO_CHECK 1 + // 全体のスイッチを宣言しているヘッダがあれば + // そこに移動していただけると + + +#if __STDC_VERSION__ < 199901L +# if __GNUC__ >= 2 +# define __func__ __FUNCTION__ +# else +# define __func__ "" +# endif +#endif + +#ifdef LCCWIN32 +#define __attribute__(x) /* nothing */ +#endif + + +#define NLP_MARK __FILE__, __LINE__, __func__ + +/*---------------------------------------------------------------------------- + * Macros + *---------------------------------------------------------------------------- + */ +/*====================================== + * Nullチェック 及び 情報出力後 return + *・展開するとifとかreturn等が出るので + * 一行単体で使ってください。 + *・nullpo_ret(x = func()); + * のような使用法も想定しています。 + *-------------------------------------- + * nullpo_ret(t) + * 戻り値 0固定 + * [引数] + * t チェック対象 + *-------------------------------------- + * nullpo_retv(t) + * 戻り値 なし + * [引数] + * t チェック対象 + *-------------------------------------- + * nullpo_retr(ret, t) + * 戻り値 指定 + * [引数] + * ret return(ret); + * t チェック対象 + *-------------------------------------- + * nullpo_ret_f(t, fmt, ...) + * 詳細情報出力用 + * 戻り値 0 + * [引数] + * t チェック対象 + * fmt ... vprintfに渡される + * 備考や関係変数の書き出しなどに + *-------------------------------------- + * nullpo_retv_f(t, fmt, ...) + * 詳細情報出力用 + * 戻り値 なし + * [引数] + * t チェック対象 + * fmt ... vprintfに渡される + * 備考や関係変数の書き出しなどに + *-------------------------------------- + * nullpo_retr_f(ret, t, fmt, ...) + * 詳細情報出力用 + * 戻り値 指定 + * [引数] + * ret return(ret); + * t チェック対象 + * fmt ... vprintfに渡される + * 備考や関係変数の書き出しなどに + *-------------------------------------- + */ + +#if NULLPO_CHECK + +#define nullpo_ret(t) \ + if (nullpo_chk(NLP_MARK, (void *)(t))) {return(0);} + +#define nullpo_retv(t) \ + if (nullpo_chk(NLP_MARK, (void *)(t))) {return;} + +#define nullpo_retr(ret, t) \ + if (nullpo_chk(NLP_MARK, (void *)(t))) {return(ret);} + + +// 可変引数マクロに関する条件コンパイル +#if __STDC_VERSION__ >= 199901L +/* C99に対応 */ +#define nullpo_ret_f(t, fmt, ...) \ + if (nullpo_chk_f(NLP_MARK, (void *)(t), (fmt), __VA_ARGS__)) {return(0);} + +#define nullpo_retv_f(t, fmt, ...) \ + if (nullpo_chk_f(NLP_MARK, (void *)(t), (fmt), __VA_ARGS__)) {return;} + +#define nullpo_retr_f(ret, t, fmt, ...) \ + if (nullpo_chk_f(NLP_MARK, (void *)(t), (fmt), __VA_ARGS__)) {return(ret);} + +#elif __GNUC__ >= 2 +/* GCC用 */ +#define nullpo_ret_f(t, fmt, args...) \ + if (nullpo_chk_f(NLP_MARK, (void *)(t), (fmt), ## args)) {return(0);} + +#define nullpo_retv_f(t, fmt, args...) \ + if (nullpo_chk_f(NLP_MARK, (void *)(t), (fmt), ## args)) {return;} + +#define nullpo_retr_f(ret, t, fmt, args...) \ + if (nullpo_chk_f(NLP_MARK, (void *)(t), (fmt), ## args)) {return(ret);} + +#else + +/* その他の場合・・・ orz */ + +#endif + +#else /* NULLPO_CHECK */ +/* No Nullpo check */ + +// if((t)){;} +// 良い方法が思いつかなかったので・・・苦肉の策です。 +// 一応ワーニングは出ないはず + +#define nullpo_ret(t) if((t)){;} +#define nullpo_retv(t) if((t)){;} +#define nullpo_retr(ret, t) if((t)){;} + +// 可変引数マクロに関する条件コンパイル +#if __STDC_VERSION__ >= 199901L +/* C99に対応 */ +#define nullpo_ret_f(t, fmt, ...) if((t)){;} +#define nullpo_retv_f(t, fmt, ...) if((t)){;} +#define nullpo_retr_f(ret, t, fmt, ...) if((t)){;} + +#elif __GNUC__ >= 2 +/* GCC用 */ +#define nullpo_ret_f(t, fmt, args...) if((t)){;} +#define nullpo_retv_f(t, fmt, args...) if((t)){;} +#define nullpo_retr_f(ret, t, fmt, args...) if((t)){;} + +#else +/* その他の場合・・・ orz */ +#endif + +#endif /* NULLPO_CHECK */ + +/*---------------------------------------------------------------------------- + * Functions + *---------------------------------------------------------------------------- + */ +/*====================================== + * nullpo_chk + * Nullチェック 及び 情報出力 + * [引数] + * file __FILE__ + * line __LINE__ + * func __func__ (関数名) + * これらには NLP_MARK を使うとよい + * target チェック対象 + * [返り値] + * 0 OK + * 1 NULL + *-------------------------------------- + */ +int nullpo_chk(const char *file, int line, const char *func, const void *target); + + +/*====================================== + * nullpo_chk_f + * Nullチェック 及び 詳細な情報出力 + * [引数] + * file __FILE__ + * line __LINE__ + * func __func__ (関数名) + * これらには NLP_MARK を使うとよい + * target チェック対象 + * fmt ... vprintfに渡される + * 備考や関係変数の書き出しなどに + * [返り値] + * 0 OK + * 1 NULL + *-------------------------------------- + */ +int nullpo_chk_f(const char *file, int line, const char *func, const void *target, + const char *fmt, ...) + __attribute__((format(printf,5,6))); + + +/*====================================== + * nullpo_info + * nullpo情報出力 + * [引数] + * file __FILE__ + * line __LINE__ + * func __func__ (関数名) + * これらには NLP_MARK を使うとよい + *-------------------------------------- + */ +void nullpo_info(const char *file, int line, const char *func); + + +/*====================================== + * nullpo_info_f + * nullpo詳細情報出力 + * [引数] + * file __FILE__ + * line __LINE__ + * func __func__ (関数名) + * これらには NLP_MARK を使うとよい + * fmt ... vprintfに渡される + * 備考や関係変数の書き出しなどに + *-------------------------------------- + */ +void nullpo_info_f(const char *file, int line, const char *func, + const char *fmt, ...) + __attribute__((format(printf,4,5))); + + +#endif diff --git a/misc/src/common/socket.c b/misc/src/common/socket.c new file mode 100644 index 0000000..1711286 --- /dev/null +++ b/misc/src/common/socket.c @@ -0,0 +1,439 @@ +// $Id: socket.c,v 1.1.1.1 2004/09/10 17:44:49 MagicalTux Exp $ +// original : core.c 2003/02/26 18:03:12 Rev 1.7 + +#include <stdio.h> +#include <stdlib.h> +#include <sys/types.h> + +#ifdef LCCWIN32 +#define WIN32_LEAN_AND_MEAN +#include <windows.h> +#include <winsock2.h> +#else +#include <sys/socket.h> +#include <netinet/in.h> +#include <netinet/tcp.h> +#include <sys/time.h> +#include <unistd.h> +#endif + +#include <fcntl.h> +#include <string.h> + +#include "mmo.h" // [Valaris] thanks to fov +#include "socket.h" +#include "utils.h" + +#ifdef MEMWATCH +#include "memwatch.h" +#endif + +fd_set readfds; +int fd_max; + +int rfifo_size = 65536; +int wfifo_size = 65536; + +struct socket_data *session[FD_SETSIZE]; + +static int null_parse(int fd); +static int (*default_func_parse)(int) = null_parse; + +/*====================================== + * CORE : Set function + *-------------------------------------- + */ +void set_defaultparse(int (*defaultparse)(int)) +{ + default_func_parse = defaultparse; +} + +/*====================================== + * CORE : Socket Sub Function + *-------------------------------------- + */ + +static int recv_to_fifo(int fd) +{ + int len; + + //printf("recv_to_fifo : %d %d\n",fd,session[fd]->eof); + if(session[fd]->eof) + return -1; + + +#ifdef LCCWIN32 + len = recv(fd,session[fd]->rdata+session[fd]->rdata_size, RFIFOSPACE(fd), 0); +#else + len=read(fd,session[fd]->rdata+session[fd]->rdata_size,RFIFOSPACE(fd)); +#endif + +// printf (":::RECEIVE:::\n"); +// dump(session[fd]->rdata, len); printf ("\n"); + + //{ int i; printf("recv %d : ",fd); for(i=0;i<len;i++){ printf("%02x ",RFIFOB(fd,session[fd]->rdata_size+i)); } printf("\n");} + if(len>0){ + session[fd]->rdata_size+=len; + } else if(len<=0){ + // value of connection is not necessary the same +// if (fd == 4) // Removed [Yor] +// printf("Char-Server Has Disconnected.\n"); +// else if (fd == 5) // Removed [Yor] +// printf("Attempt To Log In Successful.\n"); +// else if (fd == 7) // Removed [Yor] +// printf("Char-Server Has Disconnected.\n"); +// else if (fd == 8) // Removed [Valaris] +// printf("%s has logged off your server.\n",RFIFOP(fd,6)); // Removed [Valaris] + +// else if (fd != 8) // [Valaris] + printf("set eof : connection #%d\n", fd); + session[fd]->eof=1; + } + return 0; +} + +static int send_from_fifo(int fd) +{ + int len; + + //printf("send_from_fifo : %d\n",fd); + if(session[fd]->eof) + return -1; + +#ifdef LCCWIN32 + len = send(fd, session[fd]->wdata,session[fd]->wdata_size, 0); +#else + len=write(fd,session[fd]->wdata,session[fd]->wdata_size); +#endif + +// printf (":::SEND:::\n"); +// dump(session[fd]->wdata, len); printf ("\n"); + + //{ int i; printf("send %d : ",fd); for(i=0;i<len;i++){ printf("%02x ",session[fd]->wdata[i]); } printf("\n");} + if(len>0){ + if(len<session[fd]->wdata_size){ + memmove(session[fd]->wdata,session[fd]->wdata+len,session[fd]->wdata_size-len); + session[fd]->wdata_size-=len; + } else { + session[fd]->wdata_size=0; + } + } else { + printf("set eof :%d\n",fd); + session[fd]->eof=1; + } + return 0; +} + +static int null_parse(int fd) +{ + printf("null_parse : %d\n",fd); + RFIFOSKIP(fd,RFIFOREST(fd)); + return 0; +} + +/*====================================== + * CORE : Socket Function + *-------------------------------------- + */ + +static int connect_client(int listen_fd) +{ + int fd; + struct sockaddr_in client_address; + int len; + int result; + int yes = 1; // reuse fix + + //printf("connect_client : %d\n",listen_fd); + + len=sizeof(client_address); + + fd=accept(listen_fd,(struct sockaddr*)&client_address,&len); + if(fd_max<=fd) fd_max=fd+1; + +// setsockopt(fd,SOL_SOCKET,SO_REUSEADDR,NULL,0); + setsockopt(fd,SOL_SOCKET,SO_REUSEADDR,(char *)&yes,sizeof yes); // reuse fix +#ifdef SO_REUSEPORT +// setsockopt(fd,SOL_SOCKET,SO_REUSEPORT,NULL,0); + setsockopt(fd,SOL_SOCKET,SO_REUSEPORT,(char *)&yes,sizeof yes); //reuse fix +#endif +// setsockopt(fd,IPPROTO_TCP,TCP_NODELAY,NULL,0); + setsockopt(fd,IPPROTO_TCP,TCP_NODELAY,(char *)&yes,sizeof yes); // reuse fix + + if(fd==-1){ + perror("accept"); + } else { + FD_SET(fd,&readfds); + } + +#ifdef LCCWIN32 + { + unsigned long val = 1; + ioctlsocket(fd, FIONBIO, &val); + } +#else + result = fcntl(fd, F_SETFL, O_NONBLOCK); +#endif + + CREATE(session[fd], struct socket_data, 1); + CREATE(session[fd]->rdata, char, rfifo_size); + CREATE(session[fd]->wdata, char, wfifo_size); + + session[fd]->max_rdata = rfifo_size; + session[fd]->max_wdata = wfifo_size; + session[fd]->func_recv = recv_to_fifo; + session[fd]->func_send = send_from_fifo; + session[fd]->func_parse = default_func_parse; + session[fd]->client_addr = client_address; + + //printf("new_session : %d %d\n",fd,session[fd]->eof); + return fd; +} + +int make_listen_port(int port) +{ + struct sockaddr_in server_address; + int fd; + int result; + int yes = 1; // reuse fix + + fd = socket( AF_INET, SOCK_STREAM, 0 ); + if(fd_max<=fd) fd_max=fd+1; + +#ifdef LCCWIN32 + { + unsigned long val = 1; + ioctlsocket(fd, FIONBIO, &val); + } +#else + result = fcntl(fd, F_SETFL, O_NONBLOCK); +#endif + +// setsockopt(fd,SOL_SOCKET,SO_REUSEADDR,NULL,0); + setsockopt(fd,SOL_SOCKET,SO_REUSEADDR,(char *)&yes,sizeof yes); // reuse fix +#ifdef SO_REUSEPORT +// setsockopt(fd,SOL_SOCKET,SO_REUSEPORT,NULL,0); + setsockopt(fd,SOL_SOCKET,SO_REUSEPORT,(char *)&yes,sizeof yes); //reuse fix +#endif +// setsockopt(fd,IPPROTO_TCP,TCP_NODELAY,NULL,0); + setsockopt(fd,IPPROTO_TCP,TCP_NODELAY,(char *)&yes,sizeof yes); // reuse fix + + server_address.sin_family = AF_INET; + server_address.sin_addr.s_addr = htonl( INADDR_ANY ); + server_address.sin_port = htons(port); + + result = bind(fd, (struct sockaddr*)&server_address, sizeof(server_address)); + if( result == -1 ) { + perror("bind"); + exit(1); + } + result = listen( fd, 5 ); + if( result == -1 ) { /* error */ + perror("listen"); + exit(1); + } + + FD_SET(fd, &readfds ); + + CREATE(session[fd], struct socket_data, 1); + + if(session[fd]==NULL){ + printf("out of memory : make_listen_port\n"); + exit(1); + } + memset(session[fd],0,sizeof(*session[fd])); + session[fd]->func_recv = connect_client; + + return fd; +} + +int make_connection(long ip,int port) +{ + struct sockaddr_in server_address; + int fd; + int result; + int yes = 1; // reuse fix + + fd = socket( AF_INET, SOCK_STREAM, 0 ); + if(fd_max<=fd) fd_max=fd+1; +// setsockopt(fd,SOL_SOCKET,SO_REUSEADDR,NULL,0); + setsockopt(fd,SOL_SOCKET,SO_REUSEADDR,(char *)&yes,sizeof yes); // reuse fix +#ifdef SO_REUSEPORT +// setsockopt(fd,SOL_SOCKET,SO_REUSEPORT,NULL,0); + setsockopt(fd,SOL_SOCKET,SO_REUSEPORT,(char *)&yes,sizeof yes); //reuse fix +#endif +// setsockopt(fd,IPPROTO_TCP,TCP_NODELAY,NULL,0); + setsockopt(fd,IPPROTO_TCP,TCP_NODELAY,(char *)&yes,sizeof yes); // reuse fix + + server_address.sin_family = AF_INET; + server_address.sin_addr.s_addr = ip; + server_address.sin_port = htons(port); + +#ifdef LCCWIN32 + { + unsigned long val = 1; + ioctlsocket(fd, FIONBIO, &val); + } +#else + result = fcntl(fd, F_SETFL, O_NONBLOCK); +#endif + + result = connect(fd, (struct sockaddr *)(&server_address),sizeof(struct sockaddr_in)); + + FD_SET(fd,&readfds); + + CREATE(session[fd], struct socket_data, 1); + CREATE(session[fd]->rdata, char, rfifo_size); + CREATE(session[fd]->wdata, char, wfifo_size); + + session[fd]->max_rdata = rfifo_size; + session[fd]->max_wdata = wfifo_size; + session[fd]->func_recv = recv_to_fifo; + session[fd]->func_send = send_from_fifo; + session[fd]->func_parse = default_func_parse; + + return fd; +} + +int delete_session(int fd) +{ + if(fd<0 || fd>=FD_SETSIZE) + return -1; + FD_CLR(fd,&readfds); + if(session[fd]){ + if(session[fd]->rdata) + free(session[fd]->rdata); + if(session[fd]->wdata) + free(session[fd]->wdata); + if(session[fd]->session_data) + free(session[fd]->session_data); + free(session[fd]); + } + session[fd]=NULL; + //printf("delete_session:%d\n",fd); + return 0; +} + +int realloc_fifo(int fd,int rfifo_size,int wfifo_size) +{ + struct socket_data *s=session[fd]; + if( s->max_rdata != rfifo_size && s->rdata_size < rfifo_size){ + RECREATE(s->rdata, char, rfifo_size); + s->max_rdata = rfifo_size; + } + if( s->max_wdata != wfifo_size && s->wdata_size < wfifo_size){ + RECREATE(s->wdata, char, wfifo_size); + s->max_wdata = wfifo_size; + } + return 0; +} + +int WFIFOSET(int fd,int len) +{ + struct socket_data *s=session[fd]; + if( s->wdata_size+len+16384 > s->max_wdata ){ + realloc_fifo(fd,s->max_rdata, s->max_wdata <<1 ); + printf("socket: %d wdata expanded to %d bytes.\n",fd, s->max_wdata); + } + s->wdata_size=(s->wdata_size+(len)+2048 < s->max_wdata) ? + s->wdata_size+len : (printf("socket: %d wdata lost !!\n",fd),s->wdata_size); + return 0; +} + +int do_sendrecv(int next) +{ + fd_set rfd,wfd; + struct timeval timeout; + int ret,i; + + rfd=readfds; + FD_ZERO(&wfd); + for(i=0;i<fd_max;i++){ + if(!session[i] && FD_ISSET(i,&readfds)){ + printf("force clr fds %d\n",i); + FD_CLR(i,&readfds); + continue; + } + if(!session[i]) + continue; + if(session[i]->wdata_size) + FD_SET(i,&wfd); + } + timeout.tv_sec = next/1000; + timeout.tv_usec = next%1000*1000; + ret = select(fd_max,&rfd,&wfd,NULL,&timeout); + if(ret<=0) + return 0; + for(i=0;i<fd_max;i++){ + if(!session[i]) + continue; + if(FD_ISSET(i,&wfd)){ + //printf("write:%d\n",i); + if(session[i]->func_send) + //send_from_fifo(i); + session[i]->func_send(i); + } + if(FD_ISSET(i,&rfd)){ + //printf("read:%d\n",i); + if(session[i]->func_recv) + //recv_to_fifo(i); + session[i]->func_recv(i); + } + } + return 0; +} + +int do_parsepacket(void) +{ + int i; + for(i=0;i<fd_max;i++){ + if(!session[i]) + continue; + if(session[i]->rdata_size==0 && session[i]->eof==0) + continue; + if(session[i]->func_parse){ + session[i]->func_parse(i); + if(!session[i]) + continue; + } + RFIFOFLUSH(i); + } + return 0; +} + +void do_socket(void) +{ + FD_ZERO(&readfds); +} + +int RFIFOSKIP(int fd,int len) +{ + struct socket_data *s=session[fd]; + + if (s->rdata_size-s->rdata_pos-len<0) { + fprintf(stderr,"too many skip\n"); + exit(1); + } + + s->rdata_pos = s->rdata_pos+len; + + return 0; +} + + +int Net_Init(void) +{ + #ifdef LCCWIN32 + /* Start up the windows networking */ + WORD version_wanted = MAKEWORD(1,1); + WSADATA wsaData; + + if ( WSAStartup(version_wanted, &wsaData) != 0 ) { + printf("SYSERR: WinSock not available!\n"); + exit(1); + } + #endif + + return(0); +} + diff --git a/misc/src/common/socket.h b/misc/src/common/socket.h new file mode 100644 index 0000000..fe06e40 --- /dev/null +++ b/misc/src/common/socket.h @@ -0,0 +1,96 @@ +// original : core.h 2003/03/14 11:55:25 Rev 1.4 + +#ifndef _SOCKET_H_ +#define _SOCKET_H_ + +#include <stdio.h> + +#ifdef LCCWIN32 +#include <winsock.h> +#else +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#endif + +// define declaration + +#define RFIFOP(fd,pos) (session[fd]->rdata+session[fd]->rdata_pos+(pos)) +#define RFIFOB(fd,pos) (*(unsigned char*)(session[fd]->rdata+session[fd]->rdata_pos+(pos))) +#define RFIFOW(fd,pos) (*(unsigned short*)(session[fd]->rdata+session[fd]->rdata_pos+(pos))) +#define RFIFOL(fd,pos) (*(unsigned int*)(session[fd]->rdata+session[fd]->rdata_pos+(pos))) +//#define RFIFOSKIP(fd,len) ((session[fd]->rdata_size-session[fd]->rdata_pos-(len)<0) ? (fprintf(stderr,"too many skip\n"),exit(1)) : (session[fd]->rdata_pos+=(len))) +#define RFIFOREST(fd) (session[fd]->rdata_size-session[fd]->rdata_pos) +#define RFIFOFLUSH(fd) (memmove(session[fd]->rdata,RFIFOP(fd,0),RFIFOREST(fd)),session[fd]->rdata_size=RFIFOREST(fd),session[fd]->rdata_pos=0) +#define RFIFOSPACE(fd) (session[fd]->max_rdata-session[fd]->rdata_size) +#define RBUFP(p,pos) (((unsigned char*)(p))+(pos)) +#define RBUFB(p,pos) (*(unsigned char*)RBUFP((p),(pos))) +#define RBUFW(p,pos) (*(unsigned short*)RBUFP((p),(pos))) +#define RBUFL(p,pos) (*(unsigned int*)RBUFP((p),(pos))) + +#define WFIFOSPACE(fd) (session[fd]->max_wdata-session[fd]->wdata_size) +#define WFIFOP(fd,pos) (session[fd]->wdata+session[fd]->wdata_size+(pos)) +#define WFIFOB(fd,pos) (*(unsigned char*)(session[fd]->wdata+session[fd]->wdata_size+(pos))) +#define WFIFOW(fd,pos) (*(unsigned short*)(session[fd]->wdata+session[fd]->wdata_size+(pos))) +#define WFIFOL(fd,pos) (*(unsigned int*)(session[fd]->wdata+session[fd]->wdata_size+(pos))) +// use function instead of macro. +//#define WFIFOSET(fd,len) (session[fd]->wdata_size = (session[fd]->wdata_size+(len)+2048 < session[fd]->max_wdata) ? session[fd]->wdata_size+len : session[fd]->wdata_size) +#define WBUFP(p,pos) (((unsigned char*)(p))+(pos)) +#define WBUFB(p,pos) (*(unsigned char*)WBUFP((p),(pos))) +#define WBUFW(p,pos) (*(unsigned short*)WBUFP((p),(pos))) +#define WBUFL(p,pos) (*(unsigned int*)WBUFP((p),(pos))) + +#ifdef __INTERIX +#define FD_SETSIZE 4096 +#endif // __INTERIX + + +/* Removed Cygwin FD_SETSIZE declarations, now are directly passed on to the compiler through Makefile [Valaris] */ + +// Struct declaration + +struct socket_data{ + int eof; + unsigned char *rdata,*wdata; + int max_rdata,max_wdata; + int rdata_size,wdata_size; + int rdata_pos; + struct sockaddr_in client_addr; + int (*func_recv)(int); + int (*func_send)(int); + int (*func_parse)(int); + void* session_data; +}; + +// Data prototype declaration + +#ifdef LCCWIN32 + + #undef FD_SETSIZE + #define FD_SETSIZE 4096 + +#endif + +extern struct socket_data *session[FD_SETSIZE]; + +extern int rfifo_size,wfifo_size; +extern int fd_max; + +// Function prototype declaration + +int make_listen_port(int); +int make_connection(long,int); +int delete_session(int); +int realloc_fifo(int fd,int rfifo_size,int wfifo_size); +int WFIFOSET(int fd,int len); +int RFIFOSKIP(int fd,int len); + +int do_sendrecv(int next); +int do_parsepacket(void); +void do_socket(void); + +void set_defaultparse(int (*defaultparse)(int)); + +int Net_Init(void); + +#endif // _SOCKET_H_ diff --git a/misc/src/common/timer.c b/misc/src/common/timer.c new file mode 100644 index 0000000..8193ff9 --- /dev/null +++ b/misc/src/common/timer.c @@ -0,0 +1,312 @@ +// $Id: timer.c,v 1.1.1.1 2004/09/10 17:44:49 MagicalTux Exp $ +// original : core.c 2003/02/26 18:03:12 Rev 1.7 + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> + +#ifdef LCCWIN32 +#include <winsock.h> +#else +#include <sys/socket.h> +#include <sys/time.h> +#endif + +#include "timer.h" +#include "utils.h" + +#ifdef MEMWATCH +#include "memwatch.h" +#endif + +static struct TimerData* timer_data; +static int timer_data_max,timer_data_num; +static int* free_timer_list; +static int free_timer_list_max, free_timer_list_pos; + +static int timer_heap_max; +static int* timer_heap = NULL; + +// for debug +struct timer_func_list { + int (*func)(int,unsigned int,int,int); + struct timer_func_list* next; + char* name; +}; +static struct timer_func_list* tfl_root; + +#if defined(LCCWIN32) +void gettimeofday(struct timeval *t, struct timezone *dummy) +{ + DWORD millisec = GetTickCount(); + + t->tv_sec = (int) (millisec / 1000); + t->tv_usec = (millisec % 1000) * 1000; +} + +#endif + + +// +int add_timer_func_list(int (*func)(int,unsigned int,int,int),char* name) +{ + struct timer_func_list* tfl; + + CREATE(tfl, struct timer_func_list, 1); + CREATE(tfl->name, char, strlen(name) + 1); + + tfl->next = tfl_root; + tfl->func = func; + strcpy(tfl->name,name); + tfl_root = tfl; + + return 0; +} + +char* search_timer_func_list(int (*func)(int,unsigned int,int,int)) +{ + struct timer_func_list* tfl; + for(tfl = tfl_root;tfl;tfl = tfl->next) { + if (func == tfl->func) + return tfl->name; + } + return "???"; +} + +/*---------------------------- + * Get tick time + *----------------------------*/ +static unsigned int gettick_cache; +static int gettick_count; +unsigned int gettick_nocache(void) +{ + struct timeval tval; + gettimeofday(&tval,NULL); + gettick_count = 256; + return gettick_cache = tval.tv_sec * 1000 + tval.tv_usec/1000; +} + +unsigned int gettick(void) +{ + gettick_count--; + if (gettick_count<0) + return gettick_nocache(); + return gettick_cache; +} + +/*====================================== + * CORE : Timer Heap + *-------------------------------------- + */ +static void push_timer_heap(int index) +{ + int i, h; + + if (timer_heap == NULL || timer_heap[0] + 1 >= timer_heap_max) { + int first = timer_heap == NULL; + + timer_heap_max += 256; + RECREATE(timer_heap, int, timer_heap_max); + memset(timer_heap + (timer_heap_max - 256), 0, sizeof(int) * 256); + if (first) + timer_heap[0] = 0; + } + + timer_heap[0]++; + + for (h = timer_heap[0]-1, i = (h - 1) / 2; + h > 0 && DIFF_TICK(timer_data[index].tick, + timer_data[timer_heap[i + 1]].tick) < 0; + i = (h - 1) / 2) { + timer_heap[h + 1] = timer_heap[i + 1]; + h = i; + } + timer_heap[h + 1] = index; +} + +static int top_timer_heap() +{ + if (timer_heap == NULL || timer_heap[0] <= 0) + return -1; + + return timer_heap[1]; +} + +static int pop_timer_heap() +{ + int i,h,k; + int ret,last; + + if (timer_heap == NULL || timer_heap[0] <= 0) + return -1; + ret = timer_heap[1]; + last = timer_heap[timer_heap[0]]; + timer_heap[0]--; + + for(h = 0,k = 2;k<timer_heap[0];k = k * 2 + 2) { + if (DIFF_TICK(timer_data[timer_heap[k + 1]].tick , timer_data[timer_heap[k]].tick)>0) + k--; + timer_heap[h + 1] = timer_heap[k + 1], h = k; + } + if (k == timer_heap[0]) + timer_heap[h + 1] = timer_heap[k], h = k-1; + + for(i = (h-1)/2; + h>0 && DIFF_TICK(timer_data[timer_heap[i + 1]].tick , timer_data[last].tick)>0; + i = (h-1)/2) { + timer_heap[h + 1] = timer_heap[i + 1],h = i; + } + timer_heap[h + 1] = last; + + return ret; +} + +int add_timer(unsigned int tick,int (*func)(int,unsigned int,int,int),int id,int data) +{ + struct TimerData* td; + int i; + + if (free_timer_list_pos) { + do { + i = free_timer_list[--free_timer_list_pos]; + } while(i >= timer_data_num && free_timer_list_pos > 0); + } else + i = timer_data_num; + if (i >= timer_data_num) + for (i = timer_data_num;i<timer_data_max && timer_data[i].type; i++); + if (i >= timer_data_num && i >= timer_data_max) { + int j; + if (timer_data_max == 0) { + timer_data_max = 256; + CREATE(timer_data, struct TimerData, timer_data_max); + } else { + timer_data_max += 256; + RECREATE(timer_data, struct TimerData, timer_data_max); + if (timer_data == NULL) { + printf("out of memory : add_timer timer_data\n"); + exit(1); + } + memset(timer_data + (timer_data_max - 256), 0, + sizeof(struct TimerData) * 256); + } + for(j = timer_data_max-256;j<timer_data_max; j++) + timer_data[j].type = 0; + } + td = &timer_data[i]; + td->tick = tick; + td->func = func; + td->id = id; + td->data = data; + td->type = TIMER_ONCE_AUTODEL; + td->interval = 1000; + push_timer_heap(i); + if (i >= timer_data_num) + timer_data_num = i + 1; + return i; +} + +int add_timer_interval(unsigned int tick,int (*func)(int,unsigned int,int,int),int id,int data,int interval) +{ + int tid; + tid = add_timer(tick,func,id,data); + timer_data[tid].type = TIMER_INTERVAL; + timer_data[tid].interval = interval; + return tid; +} + +int delete_timer(int id,int (*func)(int,unsigned int,int,int)) +{ + if (id <= 0 || id >= timer_data_num) { + printf("delete_timer error : no such timer %d\n", id); + return -1; + } + if (timer_data[id].func != func) { + printf("delete_timer error : function dismatch %08x(%s) != %08x(%s)\n", + (int)timer_data[id].func, + search_timer_func_list(timer_data[id].func), + (int)func, + search_timer_func_list(func)); + return -2; + } + // そのうち消えるにまかせる + timer_data[id].func = NULL; + timer_data[id].type = TIMER_ONCE_AUTODEL; + timer_data[id].tick -= 60 * 60 * 1000; + return 0; +} + +int addtick_timer(int tid,unsigned int tick) +{ + return timer_data[tid].tick += tick; +} +struct TimerData* get_timer(int tid) +{ + return &timer_data[tid]; +} + + +int do_timer(unsigned int tick) +{ + int i,nextmin = 1000; + +#if 0 + static int disp_tick = 0; + if (DIFF_TICK(disp_tick,tick)<-5000 || DIFF_TICK(disp_tick,tick)>5000) { + printf("timer %d(%d + %d)\n",timer_data_num,timer_heap[0],free_timer_list_pos); + disp_tick = tick; + } +#endif + + while((i = top_timer_heap()) >= 0) { + if (DIFF_TICK(timer_data[i].tick , tick)>0) { + nextmin = DIFF_TICK(timer_data[i].tick , tick); + break; + } + pop_timer_heap(); + timer_data[i].type |= TIMER_REMOVE_HEAP; + if (timer_data[i].func) { + if (DIFF_TICK(timer_data[i].tick , tick) < -1000) { + // 1秒以上の大幅な遅延が発生しているので、 + // timer処理タイミングを現在値とする事で + // 呼び出し時タイミング(引数のtick)相対で処理してる + // timer関数の次回処理タイミングを遅らせる + timer_data[i].func(i,tick,timer_data[i].id,timer_data[i].data); + } else { + timer_data[i].func(i,timer_data[i].tick,timer_data[i].id,timer_data[i].data); + } + } + if (timer_data[i].type&TIMER_REMOVE_HEAP) { + switch(timer_data[i].type & ~TIMER_REMOVE_HEAP) { + case TIMER_ONCE_AUTODEL: + timer_data[i].type = 0; + if (free_timer_list_pos >= free_timer_list_max) { + free_timer_list_max += 256; + RECREATE(free_timer_list, int, free_timer_list_max); + memset(free_timer_list + (free_timer_list_max - 256), 0, + 256 * sizeof(free_timer_list[0])); + } + free_timer_list[free_timer_list_pos++] = i; + break; + case TIMER_INTERVAL: + if (DIFF_TICK(timer_data[i].tick , tick) < -1000) { + timer_data[i].tick = tick + timer_data[i].interval; + } else { + timer_data[i].tick += timer_data[i].interval; + } + timer_data[i].type &= ~TIMER_REMOVE_HEAP; + push_timer_heap(i); + break; + } + } + } + + if (nextmin<10) + nextmin = 10; + return nextmin; +} + +void timer_final() +{ + free(timer_data); +} diff --git a/misc/src/common/timer.h b/misc/src/common/timer.h new file mode 100644 index 0000000..f6fc5c8 --- /dev/null +++ b/misc/src/common/timer.h @@ -0,0 +1,45 @@ +// original : core.h 2003/03/14 11:55:25 Rev 1.4 + +#ifndef _TIMER_H_ +#define _TIMER_H_ + +#define BASE_TICK 5 + +#define TIMER_ONCE_AUTODEL 1 +#define TIMER_INTERVAL 2 +#define TIMER_REMOVE_HEAP 16 + +#define DIFF_TICK(a,b) ((int)((a)-(b))) + +// Struct declaration + +struct TimerData { + unsigned int tick; + int (*func)(int,unsigned int,int,int); + int id; + int data; + int type; + int interval; + int heap_pos; +}; + +// Function prototype declaration + +unsigned int gettick_nocache(void); +unsigned int gettick(void); + +int add_timer(unsigned int,int (*)(int,unsigned int,int,int),int,int); +int add_timer_interval(unsigned int,int (*)(int,unsigned int,int,int),int,int,int); +int delete_timer(int,int (*)(int,unsigned int,int,int)); + +int addtick_timer(int tid,unsigned int tick); +struct TimerData *get_timer(int tid); + +int do_timer(unsigned int tick); + +int add_timer_func_list(int (*)(int,unsigned int,int,int),char*); +char* search_timer_func_list(int (*)(int,unsigned int,int,int)); + +extern void timer_final(); + +#endif // _TIMER_H_ diff --git a/misc/src/common/utils.c b/misc/src/common/utils.c new file mode 100644 index 0000000..b0ecd26 --- /dev/null +++ b/misc/src/common/utils.c @@ -0,0 +1,108 @@ +#include <string.h> +#include "utils.h" +#include <stdio.h> + +void dump(unsigned char *buffer, int num) +{ + int icnt,jcnt; + + printf(" Hex ASCII\n"); + printf(" ----------------------------------------------- ----------------"); + + for (icnt=0;icnt<num;icnt+=16) { + printf("\n%p ",&buffer[icnt]); + for (jcnt=icnt;jcnt<icnt+16;++jcnt) { + if (jcnt < num) { + printf("%02hX ",buffer[jcnt]); + } else + printf(" "); + } + + printf(" | "); + + for (jcnt=icnt;jcnt<icnt+16;++jcnt) { + if (jcnt < num) { + if (buffer[jcnt] > 31 && buffer[jcnt] < 127) + printf("%c",buffer[jcnt]); + else + printf("."); + } else + printf(" "); + } + } + printf("\n"); +} + + +#ifdef LCCWIN32 +char *rindex(char *str, char c) +{ + char *sptr; + + sptr = str; + while(*sptr) + ++sptr; + if (c == '\0') + return(sptr); + while(str != sptr) + if (*sptr-- == c) + return(++sptr); + return(NULL); +} + +int strcasecmp(const char *arg1, const char *arg2) +{ + int chk, i; + + if (arg1 == NULL || arg2 == NULL) { + printf("SYSERR: str_cmp() passed a NULL pointer, %p or %p.\n", arg1, arg2); + return (0); + } + + for (i = 0; arg1[i] || arg2[i]; i++) + if ((chk = LOWER(arg1[i]) - LOWER(arg2[i])) != 0) + return (chk); /* not equal */ + + return (0); +} + +int strncasecmp(const char *arg1, const char *arg2, int n) +{ + int chk, i; + + if (arg1 == NULL || arg2 == NULL) { + printf("SYSERR: strn_cmp() passed a NULL pointer, %p or %p.\n", arg1, arg2); + return (0); + } + + for (i = 0; (arg1[i] || arg2[i]) && (n > 0); i++, n--) + if ((chk = LOWER(arg1[i]) - LOWER(arg2[i])) != 0) + return (chk); /* not equal */ + + return (0); +} + +void str_upper(char *name) +{ + + int len = strlen(name); + while (len--) { + if (*name >= 'a' && *name <= 'z') + *name -= ('a' - 'A'); + name++; + } +} + +void str_lower(char *name) +{ + int len = strlen(name); + + while (len--) { + if (*name >= 'A' && *name <= 'Z') + *name += ('a' - 'A'); + name++; + } +} + +#endif + diff --git a/misc/src/common/utils.h b/misc/src/common/utils.h new file mode 100644 index 0000000..29463cf --- /dev/null +++ b/misc/src/common/utils.h @@ -0,0 +1,33 @@ + +#ifndef NULL +#define NULL (void *)0 +#endif + +#define LOWER(c) (((c)>='A' && (c) <= 'Z') ? ((c)+('a'-'A')) : (c)) +#define UPPER(c) (((c)>='a' && (c) <= 'z') ? ((c)+('A'-'a')) : (c) ) + +/* strcasecmp -> stricmp -> str_cmp */ + + +#ifdef LCCWIN32 + int strcasecmp(const char *arg1, const char *arg2); + int strncasecmp(const char *arg1, const char *arg2, int n); + void str_upper(char *name); + void str_lower(char *name); + char *rindex(char *str, char c); +#endif + + + void dump(unsigned char *buffer, int num); + + +#define CREATE(result, type, number) do {\ + if ((number) * sizeof(type) <= 0) \ + printf("SYSERR: Zero bytes or less requested at %s:%d.\n", __FILE__, __LINE__); \ + if (!((result) = (type *) calloc ((number), sizeof(type)))) \ + { perror("SYSERR: malloc failure"); abort(); } } while(0) + +#define RECREATE(result,type,number) do {\ + if (!((result) = (type *) realloc ((result), sizeof(type) * (number))))\ + { printf("SYSERR: realloc failure"); abort(); } } while(0) + diff --git a/misc/src/common/version.h b/misc/src/common/version.h new file mode 100644 index 0000000..e33e2b3 --- /dev/null +++ b/misc/src/common/version.h @@ -0,0 +1,27 @@ +// $Id: version.h,v 1.2 2004/09/22 09:49:06 PoW Exp $ +#ifndef _VERSION_H_ +#define _VERSION_H_ + +#define ATHENA_MAJOR_VERSION 1 // Major Version +#define ATHENA_MINOR_VERSION 0 // Minor Version +#define ATHENA_REVISION 0 // Revision + +#define ATHENA_RELEASE_FLAG 1 // 1=Develop,0=Stable +#define ATHENA_OFFICIAL_FLAG 1 // 1=Mod,0=Official + +#define ATHENA_SERVER_LOGIN 1 // login server +#define ATHENA_SERVER_CHAR 2 // char server +#define ATHENA_SERVER_INTER 4 // inter server +#define ATHENA_SERVER_MAP 8 // map server + +// ATHENA_MOD_VERSIONはパッチ番号です。 +// これは無理に変えなくても気が向いたら変える程度の扱いで。 +// (毎回アップロードの度に変更するのも面倒と思われるし、そもそも +// この項目を参照する人がいるかどうかで疑問だから。) +// その程度の扱いなので、サーバーに問い合わせる側も、あくまで目安程度の扱いで +// あんまり信用しないこと。 +// 鯖snapshotの時や、大きな変更があった場合は設定してほしいです。 +// C言語の仕様上、最初に0を付けると8進数になるので間違えないで下さい。 +#define ATHENA_MOD_VERSION 1052 // mod version (patch No.) + +#endif diff --git a/misc/src/ladmin/GNUmakefile b/misc/src/ladmin/GNUmakefile new file mode 100644 index 0000000..ce19d9d --- /dev/null +++ b/misc/src/ladmin/GNUmakefile @@ -0,0 +1,14 @@ +all: ladmin
+txt: ladmin
+sql: ladmin
+
+COMMON_OBJ = ../common/core.o ../common/socket.o ../common/timer.o ../common/db.o ../common/malloc.o
+COMMON_H = ../common/core.h ../common/socket.h ../common/timer.h ../common/mmo.h ../common/version.h ../common/db.h ../common/malloc.h
+
+ladmin: ladmin.o md5calc.o $(COMMON_OBJ)
+ $(CC) -o ../../$@ ladmin.o md5calc.o $(COMMON_OBJ)
+ladmin.o: ladmin.c ladmin.h md5calc.h $(COMMON_H)
+md5calc.o: md5calc.c md5calc.h
+
+clean:
+ rm -f *.o ../../ladmin
diff --git a/misc/src/ladmin/Makefile b/misc/src/ladmin/Makefile new file mode 100644 index 0000000..ce19d9d --- /dev/null +++ b/misc/src/ladmin/Makefile @@ -0,0 +1,14 @@ +all: ladmin
+txt: ladmin
+sql: ladmin
+
+COMMON_OBJ = ../common/core.o ../common/socket.o ../common/timer.o ../common/db.o ../common/malloc.o
+COMMON_H = ../common/core.h ../common/socket.h ../common/timer.h ../common/mmo.h ../common/version.h ../common/db.h ../common/malloc.h
+
+ladmin: ladmin.o md5calc.o $(COMMON_OBJ)
+ $(CC) -o ../../$@ ladmin.o md5calc.o $(COMMON_OBJ)
+ladmin.o: ladmin.c ladmin.h md5calc.h $(COMMON_H)
+md5calc.o: md5calc.c md5calc.h
+
+clean:
+ rm -f *.o ../../ladmin
diff --git a/misc/src/ladmin/ladmin.c b/misc/src/ladmin/ladmin.c new file mode 100644 index 0000000..497f3bd --- /dev/null +++ b/misc/src/ladmin/ladmin.c @@ -0,0 +1,4385 @@ +// $Id: ladmin.c,v 1.1.1.1 2004/09/10 17:26:52 MagicalTux Exp $ +/////////////////////////////////////////////////////////////////////////// +// EAthena login-server remote administration tool +// Ladamin in C by [Yor] +// if you modify this software, modify ladmin in tool too. +/////////////////////////////////////////////////////////////////////////// + +#include <sys/types.h> +#include <sys/socket.h> +#include <stdio.h> +#include <stdlib.h> +#include <netinet/in.h> +#include <sys/time.h> // gettimeofday +#include <time.h> +#include <sys/ioctl.h> +#include <unistd.h> // close +#include <signal.h> +#include <fcntl.h> +#include <string.h> // str* +#include <arpa/inet.h> // inet_addr +#include <netdb.h> // gethostbyname +#include <stdarg.h> // valist +#include <ctype.h> // tolower + +#include "core.h" +#include "socket.h" +#include "ladmin.h" +#include "version.h" +#include "mmo.h" + +#ifdef PASSWORDENC +#include "md5calc.h" +#endif + +#ifdef MEMWATCH +#include "memwatch.h" +#endif + +//-------------------------------INSTRUCTIONS------------------------------ +// Set the variables below: +// IP of the login server. +// Port where the login-server listens incoming packets. +// Password of administration (same of config_athena.conf). +// Displayed language of the sofware (if not correct, english is used). +// IMPORTANT: +// Be sure that you authorize remote administration in login-server +// (see login_athena.conf, 'admin_state' parameter) +//------------------------------------------------------------------------- +char loginserverip[16] = "127.0.0.1"; // IP of login-server +int loginserverport = 6900; // Port of login-server +char loginserveradminpassword[24] = "admin"; // Administration password +#ifdef PASSWORDENC +int passenc = 2; // Encoding type of the password +#else +int passenc = 0; // Encoding type of the password +#endif +char defaultlanguage = 'E'; // Default language (F: Fran軋is/E: English) + // (if it's not 'F', default is English) +char ladmin_log_filename[1024] = "log/ladmin.log"; +char date_format[32] = "%Y-%m-%d %H:%M:%S"; +//------------------------------------------------------------------------- +// LIST of COMMANDs that you can type at the prompt: +// To use these commands you can only type only the first letters. +// You must type a minimum of letters (you can not type 'a', +// because ladmin doesn't know if it's for 'aide' or for 'add') +// <Example> q <= quit, li <= list, pass <= passwd, etc. +// +// Note: every time you must give a account_name, you can use "" or '' (spaces can be included) +// +// aide/help/? +// Display the description of the commands +// aide/help/? [command] +// Display the description of the specified command +// +// add <account_name> <sex> <password> +// Create an account with the default email (a@a.com). +// Concerning the sex, only the first letter is used (F or M). +// The e-mail is set to a@a.com (default e-mail). It's like to have no e-mail. +// When the password is omitted, the input is done without displaying of the pressed keys. +// <example> add testname Male testpass +// +// ban/banish yyyy/mm/dd hh:mm:ss <account name> +// Changes the final date of a banishment of an account. +// Like banset, but <account name> is at end. +// +// banadd <account_name> <modifier> +// Adds or substracts time from the final date of a banishment of an account. +// Modifier is done as follows: +// Adjustment value (-1, 1, +1, etc...) +// Modified element: +// a or y: year +// m: month +// j or d: day +// h: hour +// mn: minute +// s: second +// <example> banadd testname +1m-2mn1s-6y +// this example adds 1 month and 1 second, and substracts 2 minutes and 6 years at the same time. +// NOTE: If you modify the final date of a non-banished account, +// you fix the final date to (actual time +- adjustments) +// +// banset <account_name> yyyy/mm/dd [hh:mm:ss] +// Changes the final date of a banishment of an account. +// Default time [hh:mm:ss]: 23:59:59. +// banset <account_name> 0 +// Set a non-banished account (0 = unbanished). +// +// block <account name> +// Set state 5 (You have been blocked by the GM Team) to an account. +// Like state <account name> 5. +// +// check <account_name> <password> +// Check the validity of a password for an account +// NOTE: Server will never sends back a password. +// It's the only method you have to know if a password is correct. +// The other method is to have a ('physical') access to the accounts file. +// +// create <account_name> <sex> <email> <password> +// Like the 'add' command, but with e-mail moreover. +// <example> create testname Male my@mail.com testpass +// +// del <account name> +// Remove an account. +// This order requires confirmation. After confirmation, the account is deleted. +// +// email <account_name> <email> +// Modify the e-mail of an account. +// +// getcount +// Give the number of players online on all char-servers. +// +// gm <account_name> [GM_level] +// Modify the GM level of an account. +// Default value remove GM level (GM level = 0). +// <example> gm testname 80 +// +// id <account name> +// Give the id of an account. +// +// info <account_id> +// Display complete information of an account. +// +// kami <message> +// Sends a broadcast message on all map-server (in yellow). +// kamib <message> +// Sends a broadcast message on all map-server (in blue). +// +// language <language> +// Change the language of displaying. +// +// list/ls [start_id [end_id]] +// Display a list of accounts. +// 'start_id', 'end_id': indicate end and start identifiers. +// Research by name is not possible with this command. +// <example> list 10 9999999 +// +// listBan/lsBan [start_id [end_id]] +// Like list/ls, but only for accounts with state or banished +// +// listGM/lsGM [start_id [end_id]] +// Like list/ls, but only for GM accounts +// +// listOK/lsOK [start_id [end_id]] +// Like list/ls, but only for accounts without state and not banished +// +// memo <account_name> <memo> +// Modify the memo of an account. +// 'memo': it can have until 253 characters (with spaces or not). +// +// name <account_id> +// Give the name of an account. +// +// passwd <account_name> <new_password> +// Change the password of an account. +// When new password is omitted, the input is done without displaying of the pressed keys. +// +// quit/end/exit +// End of the program of administration +// +// reloadGM +// Reload GM configuration file +// +// search <expression> +// Seek accounts. +// Displays the accounts whose names correspond. +// search -r/-e/--expr/--regex <expression> +// Seek accounts by regular expression. +// Displays the accounts whose names correspond. +// +// sex <account_name> <sex> +// Modify the sex of an account. +// <example> sex testname Male +// +// state <account_name> <new_state> <error_message_#7> +// Change the state of an account. +// 'new_state': state is the state of the packet 0x006a + 1. The possibilities are: +// 0 = Account ok 6 = Your Game's EXE file is not the latest version +// 1 = Unregistered ID 7 = You are Prohibited to log in until %s +// 2 = Incorrect Password 8 = Server is jammed due to over populated +// 3 = This ID is expired 9 = No MSG +// 4 = Rejected from Server 100 = This ID has been totally erased +// 5 = You have been blocked by the GM Team +// all other values are 'No MSG', then use state 9 please. +// 'error_message_#7': message of the code error 6 = Your are Prohibited to log in until %s (packet 0x006a) +// +// timeadd <account_name> <modifier> +// Adds or substracts time from the validity limit of an account. +// Modifier is done as follows: +// Adjustment value (-1, 1, +1, etc...) +// Modified element: +// a or y: year +// m: month +// j or d: day +// h: hour +// mn: minute +// s: second +// <example> timeadd testname +1m-2mn1s-6y +// this example adds 1 month and 1 second, and substracts 2 minutes and 6 years at the same time. +// NOTE: You can not modify a unlimited validity limit. +// If you want modify it, you want probably create a limited validity limit. +// So, at first, you must set the validity limit to a date/time. +// +// timeset <account_name> yyyy/mm/dd [hh:mm:ss] +// Changes the validity limit of an account. +// Default time [hh:mm:ss]: 23:59:59. +// timeset <account_name> 0 +// Gives an unlimited validity limit (0 = unlimited). +// +// unban/unbanish <account name> +// Unban an account. +// Like banset <account name> 0. +// +// unblock <account name> +// Set state 0 (Account ok) to an account. +// Like state <account name> 0. +// +// version +// Display the version of the login-server. +// +// who <account name> +// Displays complete information of an account. +// +//------------------------------------------------------------------------- +int login_fd; +int login_ip; +int bytes_to_read = 0; // flag to know if we waiting bytes from login-server +char command[1024]; +char parameters[1024]; +int list_first, list_last, list_type, list_count; // parameter to display a list of accounts +int already_exit_function = 0; // sometimes, the exit function is called twice... so, don't log twice the message + +//------------------------------ +// Writing function of logs file +//------------------------------ +int ladmin_log(char *fmt, ...) { + FILE *logfp; + va_list ap; + struct timeval tv; + char tmpstr[2048]; + + va_start(ap, fmt); + + logfp = fopen(ladmin_log_filename, "a"); + if (logfp) { + if (fmt[0] == '\0') // jump a line if no message + fprintf(logfp, RETCODE); + else { + gettimeofday(&tv, NULL); + strftime(tmpstr, 24, date_format, localtime(&(tv.tv_sec))); + sprintf(tmpstr + strlen(tmpstr), ".%03d: %s", (int)tv.tv_usec / 1000, fmt); + vfprintf(logfp, tmpstr, ap); + } + fclose(logfp); + } + + va_end(ap); + return 0; +} + +//----------------------------------------------------- +// Function to suppress control characters in a string. +//----------------------------------------------------- +int remove_control_chars(unsigned char *str) { + int i; + int change = 0; + + for(i = 0; str[i]; i++) { + if (str[i] < 32) { + str[i] = '_'; + change = 1; + } + } + + return change; +} + +//--------------------------------------------- +// Function to return ordonal text of a number. +//--------------------------------------------- +char* makeordinal(int number) { + if (defaultlanguage == 'F') { + if (number == 0) + return ""; + else if (number == 1) + return "er"; + else + return "鑪e"; + } else { + if ((number % 10) < 4 && (number % 10) != 0 && (number < 10 || number > 20)) { + if ((number % 10) == 1) + return "st"; + else if ((number % 10) == 2) + return "nd"; + else + return "rd"; + } else { + return "th"; + } + } + return ""; +} + +//----------------------------------------------------------------------------------------- +// Function to test of the validity of an account name (return 0 if incorrect, and 1 if ok) +//----------------------------------------------------------------------------------------- +int verify_accountname(char* account_name) { + int i; + + for(i = 0; account_name[i]; i++) { + if (account_name[i] < 32) { + if (defaultlanguage == 'F') { + printf("Caract鑽e interdit trouv dans le nom du compte (%d%s caract鑽e).\n", i+1, makeordinal(i+1)); + ladmin_log("Caract鑽e interdit trouv dans le nom du compte (%d%s caract鑽e)." RETCODE, i+1, makeordinal(i+1)); + } else { + printf("Illegal character found in the account name (%d%s character).\n", i+1, makeordinal(i+1)); + ladmin_log("Illegal character found in the account name (%d%s character)." RETCODE, i+1, makeordinal(i+1)); + } + return 0; + } + } + + if (strlen(account_name) < 4) { + if (defaultlanguage == 'F') { + printf("Nom du compte trop court. Entrez un nom de compte de 4-23 caract鑽es.\n"); + ladmin_log("Nom du compte trop court. Entrez un nom de compte de 4-23 caract鑽es." RETCODE); + } else { + printf("Account name is too short. Please input an account name of 4-23 bytes.\n"); + ladmin_log("Account name is too short. Please input an account name of 4-23 bytes." RETCODE); + } + return 0; + } + + if (strlen(account_name) > 23) { + if (defaultlanguage == 'F') { + printf("Nom du compte trop long. Entrez un nom de compte de 4-23 caract鑽es.\n"); + ladmin_log("Nom du compte trop long. Entrez un nom de compte de 4-23 caract鑽es." RETCODE); + } else { + printf("Account name is too long. Please input an account name of 4-23 bytes.\n"); + ladmin_log("Account name is too long. Please input an account name of 4-23 bytes." RETCODE); + } + return 0; + } + + return 1; +} + +//--------------------------------------------------- +// E-mail check: return 0 (not correct) or 1 (valid). +//--------------------------------------------------- +int e_mail_check(unsigned char *email) { + char ch; + unsigned char* last_arobas; + + // athena limits + if (strlen(email) < 3 || strlen(email) > 39) + return 0; + + // part of RFC limits (official reference of e-mail description) + if (strchr(email, '@') == NULL || email[strlen(email)-1] == '@') + return 0; + + if (email[strlen(email)-1] == '.') + return 0; + + last_arobas = strrchr(email, '@'); + + if (strstr(last_arobas, "@.") != NULL || + strstr(last_arobas, "..") != NULL) + return 0; + + for(ch = 1; ch < 32; ch++) { + if (strchr(last_arobas, ch) != NULL) { + return 0; + break; + } + } + + if (strchr(last_arobas, ' ') != NULL || + strchr(last_arobas, ';') != NULL) + return 0; + + // all correct + return 1; +} + +//---------------------------------- +// Sub-function: Input of a password +//---------------------------------- +int typepasswd(char * password) { + char password1[1023], password2[1023]; + int letter; + int i; + + if (defaultlanguage == 'F') { + ladmin_log("Aucun mot de passe n'a 騁 donn. Demande d'un mot de passe." RETCODE); + } else { + ladmin_log("No password was given. Request to obtain a password." RETCODE); + } + + memset(password1, '\0', sizeof(password1)); + memset(password2, '\0', sizeof(password2)); + if (defaultlanguage == 'F') + printf("\033[1;36m Entrez le mot de passe > \033[0;32;42m"); + else + printf("\033[1;36m Type the password > \033[0;32;42m"); + i = 0; + while ((letter = getchar()) != '\n') + password1[i++] = letter; + if (defaultlanguage == 'F') + printf("\033[0m\033[1;36m R-entrez le mot de passe > \033[0;32;42m"); + else + printf("\033[0m\033[1;36m Verify the password > \033[0;32;42m"); + i = 0; + while ((letter = getchar()) != '\n') + password2[i++] = letter; + + printf("\033[0m"); + fflush(stdout); + fflush(stdin); + + if (strcmp(password1, password2) != 0) { + if (defaultlanguage == 'F') { + printf("Erreur de v駻ification du mot de passe: Saisissez le m麥e mot de passe svp.\n"); + ladmin_log("Erreur de v駻ification du mot de passe: Saisissez le m麥e mot de passe svp." RETCODE); + ladmin_log(" Premier mot de passe: %s, second mot de passe: %s." RETCODE, password1, password2); + } else { + printf("Password verification failed. Please input same password.\n"); + ladmin_log("Password verification failed. Please input same password." RETCODE); + ladmin_log(" First password: %s, second password: %s." RETCODE, password1, password2); + } + return 0; + } + if (defaultlanguage == 'F') { + ladmin_log("Mot de passe saisi: %s." RETCODE, password1); + } else { + ladmin_log("Typed password: %s." RETCODE, password1); + } + strcpy(password, password1); + return 1; +} + +//------------------------------------------------------------------------------------ +// Sub-function: Test of the validity of password (return 0 if incorrect, and 1 if ok) +//------------------------------------------------------------------------------------ +int verify_password(char * password) { + int i; + + for(i = 0; password[i]; i++) { + if (password[i] < 32) { + if (defaultlanguage == 'F') { + printf("Caract鑽e interdit trouv dans le mot de passe (%d%s caract鑽e).\n", i+1, makeordinal(i+1)); + ladmin_log("Caract鑽e interdit trouv dans le nom du compte (%d%s caract鑽e)." RETCODE, i+1, makeordinal(i+1)); + } else { + printf("Illegal character found in the password (%d%s character).\n", i+1, makeordinal(i+1)); + ladmin_log("Illegal character found in the password (%d%s character)." RETCODE, i+1, makeordinal(i+1)); + } + return 0; + } + } + + if (strlen(password) < 4) { + if (defaultlanguage == 'F') { + printf("Nom du compte trop court. Entrez un nom de compte de 4-23 caract鑽es.\n"); + ladmin_log("Nom du compte trop court. Entrez un nom de compte de 4-23 caract鑽es." RETCODE); + } else { + printf("Account name is too short. Please input an account name of 4-23 bytes.\n"); + ladmin_log("Account name is too short. Please input an account name of 4-23 bytes." RETCODE); + } + return 0; + } + + if (strlen(password) > 23) { + if (defaultlanguage == 'F') { + printf("Mot de passe trop long. Entrez un mot de passe de 4-23 caract鑽es.\n"); + ladmin_log("Mot de passe trop long. Entrez un mot de passe de 4-23 caract鑽es." RETCODE); + } else { + printf("Password is too long. Please input a password of 4-23 bytes.\n"); + ladmin_log("Password is too long. Please input a password of 4-23 bytes." RETCODE); + } + return 0; + } + + return 1; +} + +//------------------------------------------------------------------ +// Sub-function: Check the name of a command (return complete name) +//----------------------------------------------------------------- +int check_command(char * command) { +// help + if (strncmp(command, "aide", 2) == 0 && strncmp(command, "aide", strlen(command)) == 0) // not 1 letter command: 'aide' or 'add'? + strcpy(command, "aide"); + else if (strncmp(command, "help", 1) == 0 && strncmp(command, "help", strlen(command)) == 0) + strcpy(command, "help"); +// general commands + else if (strncmp(command, "add", 2) == 0 && strncmp(command, "add", strlen(command)) == 0) // not 1 letter command: 'aide' or 'add'? + strcpy(command, "add"); + else if ((strncmp(command, "ban", 3) == 0 && strncmp(command, "ban", strlen(command)) == 0) || + (strncmp(command, "banish", 4) == 0 && strncmp(command, "banish", strlen(command)) == 0)) + strcpy(command, "ban"); + else if ((strncmp(command, "banadd", 4) == 0 && strncmp(command, "banadd", strlen(command)) == 0) || // not 1 letter command: 'ba' or 'bs'? 'banadd' or 'banset' ? + strcmp(command, "ba") == 0) + strcpy(command, "banadd"); + else if ((strncmp(command, "banset", 4) == 0 && strncmp(command, "banset", strlen(command)) == 0) || // not 1 letter command: 'ba' or 'bs'? 'banadd' or 'banset' ? + strcmp(command, "bs") == 0) + strcpy(command, "banset"); + else if (strncmp(command, "block", 2) == 0 && strncmp(command, "block", strlen(command)) == 0) + strcpy(command, "block"); + else if (strncmp(command, "check", 2) == 0 && strncmp(command, "check", strlen(command)) == 0) // not 1 letter command: 'check' or 'create'? + strcpy(command, "check"); + else if (strncmp(command, "create", 2) == 0 && strncmp(command, "create", strlen(command)) == 0) // not 1 letter command: 'check' or 'create'? + strcpy(command, "create"); + else if (strncmp(command, "delete", 1) == 0 && strncmp(command, "delete", strlen(command)) == 0) + strcpy(command, "delete"); + else if ((strncmp(command, "email", 2) == 0 && strncmp(command, "email", strlen(command)) == 0) || // not 1 letter command: 'email', 'end' or 'exit'? + (strncmp(command, "e-mail", 2) == 0 && strncmp(command, "e-mail", strlen(command)) == 0)) + strcpy(command, "email"); + else if (strncmp(command, "getcount", 2) == 0 && strncmp(command, "getcount", strlen(command)) == 0) // not 1 letter command: 'getcount' or 'gm'? + strcpy(command, "getcount"); +// else if (strncmp(command, "gm", 2) == 0 && strncmp(command, "gm", strlen(command)) == 0) // not 1 letter command: 'getcount' or 'gm'? +// strcpy(command, "gm"); +// else if (strncmp(command, "id", 2) == 0 && strncmp(command, "id", strlen(command)) == 0) // not 1 letter command: 'id' or 'info'? +// strcpy(command, "id"); + else if (strncmp(command, "info", 2) == 0 && strncmp(command, "info", strlen(command)) == 0) // not 1 letter command: 'id' or 'info'? + strcpy(command, "info"); +// else if (strncmp(command, "kami", 4) == 0 && strncmp(command, "kami", strlen(command)) == 0) // only all letters command: 'kami' or 'kamib'? +// strcpy(command, "kami"); +// else if (strncmp(command, "kamib", 5) == 0 && strncmp(command, "kamib", strlen(command)) == 0) // only all letters command: 'kami' or 'kamib'? +// strcpy(command, "kamib"); + else if ((strncmp(command, "language", 2) == 0 && strncmp(command, "language", strlen(command)) == 0)) // not 1 letter command: 'language' or 'list'? + strcpy(command, "language"); + else if ((strncmp(command, "list", 2) == 0 && strncmp(command, "list", strlen(command)) == 0) || // 'list' is default list command // not 1 letter command: 'language' or 'list'? + strcmp(command, "ls") == 0) + strcpy(command, "list"); + else if ((strncmp(command, "listban", 5) == 0 && strncmp(command, "listban", strlen(command)) == 0) || + (strncmp(command, "lsban", 3) == 0 && strncmp(command, "lsban", strlen(command)) == 0) || + strcmp(command, "lb") == 0) + strcpy(command, "listban"); + else if ((strncmp(command, "listgm", 5) == 0 && strncmp(command, "listgm", strlen(command)) == 0) || + (strncmp(command, "lsgm", 3) == 0 && strncmp(command, "lsgm", strlen(command)) == 0) || + strcmp(command, "lg") == 0) + strcpy(command, "listgm"); + else if ((strncmp(command, "listok", 5) == 0 && strncmp(command, "listok", strlen(command)) == 0) || + (strncmp(command, "lsok", 3) == 0 && strncmp(command, "lsok", strlen(command)) == 0) || + strcmp(command, "lo") == 0) + strcpy(command, "listok"); + else if (strncmp(command, "memo", 1) == 0 && strncmp(command, "memo", strlen(command)) == 0) + strcpy(command, "memo"); + else if (strncmp(command, "name", 1) == 0 && strncmp(command, "name", strlen(command)) == 0) + strcpy(command, "name"); + else if ((strncmp(command, "password", 1) == 0 && strncmp(command, "password", strlen(command)) == 0) || + strcmp(command, "passwd") == 0) + strcpy(command, "password"); + else if (strncmp(command, "reloadgm", 1) == 0 && strncmp(command, "reloadgm", strlen(command)) == 0) + strcpy(command, "reloadgm"); + else if (strncmp(command, "search", 3) == 0 && strncmp(command, "search", strlen(command)) == 0) // not 1 letter command: 'search', 'state' or 'sex'? + strcpy(command, "search"); // not 2 letters command: 'search' or 'sex'? +// else if (strncmp(command, "sex", 3) == 0 && strncmp(command, "sex", strlen(command)) == 0) // not 1 letter command: 'search', 'state' or 'sex'? +// strcpy(command, "sex"); // not 2 letters command: 'search' or 'sex'? + else if (strncmp(command, "state", 2) == 0 && strncmp(command, "state", strlen(command)) == 0) // not 1 letter command: 'search', 'state' or 'sex'? + strcpy(command, "state"); + else if ((strncmp(command, "timeadd", 5) == 0 && strncmp(command, "timeadd", strlen(command)) == 0) || // not 1 letter command: 'ta' or 'ts'? 'timeadd' or 'timeset'? + strcmp(command, "ta") == 0) + strcpy(command, "timeadd"); + else if ((strncmp(command, "timeset", 5) == 0 && strncmp(command, "timeset", strlen(command)) == 0) || // not 1 letter command: 'ta' or 'ts'? 'timeadd' or 'timeset'? + strcmp(command, "ts") == 0) + strcpy(command, "timeset"); + else if ((strncmp(command, "unban", 5) == 0 && strncmp(command, "unban", strlen(command)) == 0) || + (strncmp(command, "unbanish", 4) == 0 && strncmp(command, "unbanish", strlen(command)) == 0)) + strcpy(command, "unban"); + else if (strncmp(command, "unblock", 4) == 0 && strncmp(command, "unblock", strlen(command)) == 0) + strcpy(command, "unblock"); + else if (strncmp(command, "version", 1) == 0 && strncmp(command, "version", strlen(command)) == 0) + strcpy(command, "version"); + else if (strncmp(command, "who", 1) == 0 && strncmp(command, "who", strlen(command)) == 0) + strcpy(command, "who"); +// quit + else if (strncmp(command, "quit", 1) == 0 && strncmp(command, "quit", strlen(command)) == 0) + strcpy(command, "quit"); + else if (strncmp(command, "exit", 2) == 0 && strncmp(command, "exit", strlen(command)) == 0) // not 1 letter command: 'email', 'end' or 'exit'? + strcpy(command, "exit"); + else if (strncmp(command, "end", 2) == 0 && strncmp(command, "end", strlen(command)) == 0) // not 1 letter command: 'email', 'end' or 'exit'? + strcpy(command, "end"); + + return 0; +} + +//----------------------------------------- +// Sub-function: Display commands of ladmin +//----------------------------------------- +void display_help(char* param, int language) { + char command[1023]; + int i; + + memset(command, '\0', sizeof(command)); + + if (sscanf(param, "%s ", command) < 1 || strlen(command) == 0) + strcpy(command, ""); // any value that is not a command + + if (command[0] == '?') { + if (defaultlanguage == 'F') + strcpy(command, "aide"); + else + strcpy(command, "help"); + } + + // lowercase for command + for (i = 0; command[i]; i++) + command[i] = tolower(command[i]); + + // Analyse of the command + check_command(command); // give complete name to the command + + if (defaultlanguage == 'F') { + ladmin_log("Affichage des commandes ou d'une commande." RETCODE); + } else { + ladmin_log("Displaying of the commands or a command." RETCODE); + } + + if (language == 1) { + if (strcmp(command, "aide") == 0) { + printf("aide/help/?\n"); + printf(" Affiche la description des commandes\n"); + printf("aide/help/? [commande]\n"); + printf(" Affiche la description de la commande specifi馥\n"); + } else if (strcmp(command, "help") == 0 ) { + printf("aide/help/?\n"); + printf(" Display the description of the commands\n"); + printf("aide/help/? [command]\n"); + printf(" Display the description of the specified command\n"); +// general commands + } else if (strcmp(command, "add") == 0) { + printf("add <nomcompte> <sexe> <motdepasse>\n"); + printf(" Cr馥 un compte avec l'email par d馭aut (a@a.com).\n"); + printf(" Concernant le sexe, seule la premi鑽e lettre compte (F ou M).\n"); + printf(" L'e-mail est a@a.com (e-mail par d馭aut). C'est comme n'avoir aucun e-mail.\n"); + printf(" Lorsque motdepasse est omis, la saisie se fait sans que la frappe se voit.\n"); + printf(" <exemple> add testname Male testpass\n"); + } else if (strcmp(command, "ban") == 0) { + printf("ban/banish aaaa/mm/jj hh:mm:ss <nom compte>\n"); + printf(" Change la date de fin de bannissement d'un compte.\n"); + printf(" Comme banset, mais <nom compte> est la fin.\n"); + } else if (strcmp(command, "banadd") == 0) { + printf("banadd <nomcompte> <Modificateur>\n"); + printf(" Ajoute ou soustrait du temps la date de banissement d'un compte.\n"); + printf(" Les modificateurs sont construits comme suit:\n"); + printf(" Valeur d'ajustement (-1, 1, +1, etc...)\n"); + printf(" El駑ent modifi:\n"); + printf(" a ou y: ann馥\n"); + printf(" m: mois\n"); + printf(" j ou d: jour\n"); + printf(" h: heure\n"); + printf(" mn: minute\n"); + printf(" s: seconde\n"); + printf(" <exemple> banadd testname +1m-2mn1s-6a\n"); + printf(" Cette exemple ajoute 1 mois et une seconde, et soustrait 2 minutes\n"); + printf(" et 6 ans dans le m麥e temps.\n"); + printf("NOTE: Si vous modifez la date de banissement d'un compte non bani,\n"); + printf(" vous indiquez comme date (le moment actuel +- les ajustements)\n"); + } else if (strcmp(command, "banset") == 0) { + printf("banset <nomcompte> aaaa/mm/jj [hh:mm:ss]\n"); + printf(" Change la date de fin de bannissement d'un compte.\n"); + printf(" Heure par d馭aut [hh:mm:ss]: 23:59:59.\n"); + printf("banset <nomcompte> 0\n"); + printf(" D饕anni un compte (0 = de-banni).\n"); + } else if (strcmp(command, "block") == 0) { + printf("block <nom compte>\n"); + printf(" Place le status d'un compte 5 (You have been blocked by the GM Team).\n"); + printf(" La commande est l'駲uivalent de state <nom_compte> 5.\n"); + } else if (strcmp(command, "check") == 0) { + printf("check <nomcompte> <motdepasse>\n"); + printf(" V駻ifie la validit d'un mot de passe pour un compte\n"); + printf(" NOTE: Le serveur n'enverra jamais un mot de passe.\n"); + printf(" C'est la seule m騁hode que vous poss馘ez pour savoir\n"); + printf(" si un mot de passe est le bon. L'autre m騁hode est\n"); + printf(" d'avoir un acc鑚 ('physique') au fichier des comptes.\n"); + } else if (strcmp(command, "create") == 0) { + printf("create <nomcompte> <sexe> <email> <motdepasse>\n"); + printf(" Comme la commande add, mais avec l'e-mail en plus.\n"); + printf(" <exemple> create testname Male mon@mail.com testpass\n"); + } else if (strcmp(command, "delete") == 0) { + printf("del <nom compte>\n"); + printf(" Supprime un compte.\n"); + printf(" La commande demande confirmation. Apr鑚 confirmation, le compte est d騁ruit.\n"); + } else if (strcmp(command, "email") == 0) { + printf("email <nomcompte> <email>\n"); + printf(" Modifie l'e-mail d'un compte.\n"); + } else if (strcmp(command, "getcount") == 0) { + printf("getcount\n"); + printf(" Donne le nombre de joueurs en ligne par serveur de char.\n"); + } else if (strcmp(command, "gm") == 0) { + printf("gm <nomcompte> [Niveau_GM]\n"); + printf(" Modifie le niveau de GM d'un compte.\n"); + printf(" Valeur par d馭aut: 0 (suppression du niveau de GM).\n"); + printf(" <exemple> gm nomtest 80\n"); + } else if (strcmp(command, "id") == 0) { + printf("id <nom compte>\n"); + printf(" Donne l'id d'un compte.\n"); + } else if (strcmp(command, "info") == 0) { + printf("info <idcompte>\n"); + printf(" Affiche les informations sur un compte.\n"); + } else if (strcmp(command, "kami") == 0) { + printf("kami <message>\n"); + printf(" Envoi un message g駭駻al sur tous les serveurs de map (en jaune).\n"); + } else if (strcmp(command, "kamib") == 0) { + printf("kamib <message>\n"); + printf(" Envoi un message g駭駻al sur tous les serveurs de map (en bleu).\n"); + } else if (strcmp(command, "language") == 0) { + printf("language <langue>\n"); + printf(" Change la langue d'affichage.\n"); + printf(" Langues possibles: 'Fran軋is' ou 'English'.\n"); + } else if (strcmp(command, "list") == 0) { + printf("list/ls [Premier_id [Dernier_id]]\n"); + printf(" Affiche une liste de comptes.\n"); + printf(" 'Premier_id', 'Dernier_id': indique les identifiants de d駱art et de fin.\n"); + printf(" La recherche par nom n'est pas possible avec cette commande.\n"); + printf(" <example> list 10 9999999\n"); + } else if (strcmp(command, "listban") == 0) { + printf("listBan/lsBan [Premier_id [Dernier_id]]\n"); + printf(" Comme list/ls, mais seulement pour les comptes avec statut ou bannis.\n"); + } else if (strcmp(command, "listgm") == 0) { + printf("listGM/lsGM [Premier_id [Dernier_id]]\n"); + printf(" Comme list/ls, mais seulement pour les comptes GM.\n"); + } else if (strcmp(command, "listok") == 0) { + printf("listOK/lsOK [Premier_id [Dernier_id]]\n"); + printf(" Comme list/ls, mais seulement pour les comptes sans statut et non bannis.\n"); + } else if (strcmp(command, "memo") == 0) { + printf("memo <nomcompte> <memo>\n"); + printf(" Modifie le m駑o d'un compte.\n"); + printf(" 'memo': Il peut avoir jusqu' 253 caract鑽es (avec des espaces ou non).\n"); + } else if (strcmp(command, "name") == 0) { + printf("name <idcompte>\n"); + printf(" Donne le nom d'un compte.\n"); + } else if (strcmp(command, "password") == 0) { + printf("passwd <nomcompte> <nouveaumotdepasse>\n"); + printf(" Change le mot de passe d'un compte.\n"); + printf(" Lorsque nouveaumotdepasse est omis,\n"); + printf(" la saisie se fait sans que la frappe ne se voit.\n"); + } else if (strcmp(command, "reloadgm") == 0) { + printf("reloadGM\n"); + printf(" Reload GM configuration file\n"); + } else if (strcmp(command, "search") == 0) { + printf("search <expression>\n"); + printf(" Cherche des comptes.\n"); + printf(" Affiche les comptes dont les noms correspondent.\n"); +// printf("search -r/-e/--expr/--regex <expression>\n"); +// printf(" Cherche des comptes par expression reguli鑽e.\n"); +// printf(" Affiche les comptes dont les noms correspondent.\n"); + } else if (strcmp(command, "sex") == 0) { + printf("sex <nomcompte> <sexe>\n"); + printf(" Modifie le sexe d'un compte.\n"); + printf(" <exemple> sex testname Male\n"); + } else if (strcmp(command, "state") == 0) { + printf("state <nomcompte> <nouveaustatut> <message_erreur_7>\n"); + printf(" Change le statut d'un compte.\n"); + printf(" 'nouveaustatut': Le statut est le m麥e que celui du packet 0x006a + 1.\n"); + printf(" les possibilit駸 sont:\n"); + printf(" 0 = Compte ok\n"); + printf(" 1 = Unregistered ID\n"); + printf(" 2 = Incorrect Password\n"); + printf(" 3 = This ID is expired\n"); + printf(" 4 = Rejected from Server\n"); + printf(" 5 = You have been blocked by the GM Team\n"); + printf(" 6 = Your Game's EXE file is not the latest version\n"); + printf(" 7 = You are Prohibited to log in until...\n"); + printf(" 8 = Server is jammed due to over populated\n"); + printf(" 9 = No MSG\n"); + printf(" 100 = This ID has been totally erased\n"); + printf(" all other values are 'No MSG', then use state 9 please.\n"); + printf(" 'message_erreur_7': message du code erreur 6 =\n"); + printf(" = Your are Prohibited to log in until... (packet 0x006a)\n"); + } else if (strcmp(command, "timeadd") == 0) { + printf("timeadd <nomcompte> <modificateur>\n"); + printf(" Ajoute/soustrait du temps la limite de validit d'un compte.\n"); + printf(" Le modificateur est compos comme suit:\n"); + printf(" Valeur modificatrice (-1, 1, +1, etc...)\n"); + printf(" El駑ent modifi:\n"); + printf(" a ou y: ann馥\n"); + printf(" m: mois\n"); + printf(" j ou d: jour\n"); + printf(" h: heure\n"); + printf(" mn: minute\n"); + printf(" s: seconde\n"); + printf(" <exemple> timeadd testname +1m-2mn1s-6a\n"); + printf(" Cette exemple ajoute 1 mois et une seconde, et soustrait 2 minutes\n"); + printf(" et 6 ans dans le m麥e temps.\n"); + printf("NOTE: Vous ne pouvez pas modifier une limite de validit illimit馥. Si vous\n"); + printf(" d駸irez le faire, c'est que vous voulez probablement cr馥r un limite de\n"); + printf(" validit limit馥. Donc, en premier, fix une limite de valitid.\n"); + } else if (strcmp(command, "timeadd") == 0) { + printf("timeset <nomcompte> aaaa/mm/jj [hh:mm:ss]\n"); + printf(" Change la limite de validit d'un compte.\n"); + printf(" Heure par d馭aut [hh:mm:ss]: 23:59:59.\n"); + printf("timeset <nomcompte> 0\n"); + printf(" Donne une limite de validit illimit馥 (0 = illimit馥).\n"); + } else if (strcmp(command, "unban") == 0) { + printf("unban/unbanish <nom compte>\n"); + printf(" Ote le banissement d'un compte.\n"); + printf(" La commande est l'駲uivalent de banset <nom_compte> 0.\n"); + } else if (strcmp(command, "unblock") == 0) { + printf("unblock <nom compte>\n"); + printf(" Place le status d'un compte 0 (Compte ok).\n"); + printf(" La commande est l'駲uivalent de state <nom_compte> 0.\n"); + } else if (strcmp(command, "version") == 0) { + printf("version\n"); + printf(" Affiche la version du login-serveur.\n"); + } else if (strcmp(command, "who") == 0) { + printf("who <nom compte>\n"); + printf(" Affiche les informations sur un compte.\n"); +// quit + } else if (strcmp(command, "quit") == 0 || + strcmp(command, "exit") == 0 || + strcmp(command, "end") == 0) { + printf("quit/end/exit\n"); + printf(" Fin du programme d'administration.\n"); +// unknown command + } else { + if (strlen(command) > 0) + printf("Commande inconnue [%s] pour l'aide. Affichage de toutes les commandes.\n", command); + printf(" aide/help/? -- Affiche cet aide\n"); + printf(" aide/help/? [commande] -- Affiche l'aide de la commande\n"); + printf(" add <nomcompte> <sexe> <motdepasse> -- Cr馥 un compte (sans email)\n"); + printf(" ban/banish aaaa/mm/jj hh:mm:ss <nom compte> -- Fixe la date finale de banismnt\n"); + printf(" banadd/ba <nomcompte> <modificateur> -- Ajout/soustrait du temps la\n"); + printf(" exemple: ba moncompte +1m-2mn1s-2y date finale de banissement\n"); + printf(" banset/bs <nomcompte> aaaa/mm/jj [hh:mm:ss] -- Change la date fin de banisemnt\n"); + printf(" banset/bs <nomcompte> 0 -- D-banis un compte.\n"); + printf(" block <nom compte> -- Mets le status d'un compte 5 (blocked by the GM Team)\n"); + printf(" check <nomcompte> <motdepasse> -- V駻ifie un mot de passe d'un compte\n"); + printf(" create <nomcompte> <sexe> <email> <motdepasse> -- Cr馥 un compte (avec email)\n"); + printf(" del <nom compte> -- Supprime un compte\n"); + printf(" email <nomcompte> <email> -- Modifie l'e-mail d'un compte\n"); + printf(" getcount -- Donne le nb de joueurs en ligne\n"); + printf(" gm <nomcompte> [Niveau_GM] -- Modifie le niveau de GM d'un compte\n"); + printf(" id <nom compte> -- Donne l'id d'un compte\n"); + printf(" info <idcompte> -- Affiche les infos sur un compte\n"); + printf(" kami <message> -- Envoi un message g駭駻al (en jaune)\n"); + printf(" kamib <message> -- Envoi un message g駭駻al (en bleu)\n"); + printf(" language <langue> -- Change la langue d'affichage.\n"); + printf(" list/ls [Premier_id [Dernier_id] ] -- Affiche une liste de comptes\n"); + printf(" listBan/lsBan [Premier_id [Dernier_id] ] -- Affiche une liste de comptes\n"); + printf(" avec un statut ou bannis\n"); + printf(" listGM/lsGM [Premier_id [Dernier_id] ] -- Affiche une liste de comptes GM\n"); + printf(" listOK/lsOK [Premier_id [Dernier_id] ] -- Affiche une liste de comptes\n"); + printf(" sans status et non bannis\n"); + printf(" memo <nomcompte> <memo> -- Modifie le memo d'un compte\n"); + printf(" name <idcompte> -- Donne le nom d'un compte\n"); + printf(" passwd <nomcompte> <nouveaumotdepasse> -- Change le mot de passe d'un compte\n"); + printf(" quit/end/exit -- Fin du programme d'administation\n"); + printf(" reloadGM -- Recharger le fichier de config des GM\n"); + printf(" search <expression> -- Cherche des comptes\n"); +// printf(" search -e/-r/--expr/--regex <expression> -- Cherche des comptes par REGEX\n"); + printf(" sex <nomcompte> <sexe> -- Modifie le sexe d'un compte\n"); + printf(" state <nomcompte> <nouveaustatut> <messageerr7> -- Change le statut d'1 compte\n"); + printf(" timeadd/ta <nomcompte> <modificateur> -- Ajout/soustrait du temps la\n"); + printf(" exemple: ta moncompte +1m-2mn1s-2y limite de validit饅n"); + printf(" timeset/ts <nomcompte> aaaa/mm/jj [hh:mm:ss] -- Change la limite de validit饅n"); + printf(" timeset/ts <nomcompte> 0 -- limite de validit = illimit馥\n"); + printf(" unban/unbanish <nom compte> -- Ote le banissement d'un compte\n"); + printf(" unblock <nom compte> -- Mets le status d'un compte 0 (Compte ok)\n"); + printf(" version -- Donne la version du login-serveur\n"); + printf(" who <nom compte> -- Affiche les infos sur un compte\n"); + printf(" Note: Pour les noms de compte avec des espaces, tapez \"<nom compte>\" (ou ').\n"); + } + } else { + if (strcmp(command, "aide") == 0) { + printf("aide/help/?\n"); + printf(" Display the description of the commands\n"); + printf("aide/help/? [command]\n"); + printf(" Display the description of the specified command\n"); + } else if (strcmp(command, "help") == 0 ) { + printf("aide/help/?\n"); + printf(" Display the description of the commands\n"); + printf("aide/help/? [command]\n"); + printf(" Display the description of the specified command\n"); +// general commands + } else if (strcmp(command, "add") == 0) { + printf("add <account_name> <sex> <password>\n"); + printf(" Create an account with the default email (a@a.com).\n"); + printf(" Concerning the sex, only the first letter is used (F or M).\n"); + printf(" The e-mail is set to a@a.com (default e-mail). It's like to have no e-mail.\n"); + printf(" When the password is omitted,\n"); + printf(" the input is done without displaying of the pressed keys.\n"); + printf(" <example> add testname Male testpass\n"); + } else if (strcmp(command, "ban") == 0) { + printf("ban/banish yyyy/mm/dd hh:mm:ss <account name>\n"); + printf(" Changes the final date of a banishment of an account.\n"); + printf(" Like banset, but <account name> is at end.\n"); + } else if (strcmp(command, "banadd") == 0) { + printf("banadd <account_name> <modifier>\n"); + printf(" Adds or substracts time from the final date of a banishment of an account.\n"); + printf(" Modifier is done as follows:\n"); + printf(" Adjustment value (-1, 1, +1, etc...)\n"); + printf(" Modified element:\n"); + printf(" a or y: year\n"); + printf(" m: month\n"); + printf(" j or d: day\n"); + printf(" h: hour\n"); + printf(" mn: minute\n"); + printf(" s: second\n"); + printf(" <example> banadd testname +1m-2mn1s-6y\n"); + printf(" this example adds 1 month and 1 second, and substracts 2 minutes\n"); + printf(" and 6 years at the same time.\n"); + printf("NOTE: If you modify the final date of a non-banished account,\n"); + printf(" you fix the final date to (actual time +- adjustments)\n"); + } else if (strcmp(command, "banset") == 0) { + printf("banset <account_name> yyyy/mm/dd [hh:mm:ss]\n"); + printf(" Changes the final date of a banishment of an account.\n"); + printf(" Default time [hh:mm:ss]: 23:59:59.\n"); + printf("banset <account_name> 0\n"); + printf(" Set a non-banished account (0 = unbanished).\n"); + } else if (strcmp(command, "block") == 0) { + printf("block <account name>\n"); + printf(" Set state 5 (You have been blocked by the GM Team) to an account.\n"); + printf(" This command works like state <account_name> 5.\n"); + } else if (strcmp(command, "check") == 0) { + printf("check <account_name> <password>\n"); + printf(" Check the validity of a password for an account.\n"); + printf(" NOTE: Server will never sends back a password.\n"); + printf(" It's the only method you have to know if a password is correct.\n"); + printf(" The other method is to have a ('physical') access to the accounts file.\n"); + } else if (strcmp(command, "create") == 0) { + printf("create <account_name> <sex> <email> <password>\n"); + printf(" Like the 'add' command, but with e-mail moreover.\n"); + printf(" <example> create testname Male my@mail.com testpass\n"); + } else if (strcmp(command, "delete") == 0) { + printf("del <account name>\n"); + printf(" Remove an account.\n"); + printf(" This order requires confirmation. After confirmation, the account is deleted.\n"); + } else if (strcmp(command, "email") == 0) { + printf("email <account_name> <email>\n"); + printf(" Modify the e-mail of an account.\n"); + } else if (strcmp(command, "getcount") == 0) { + printf("getcount\n"); + printf(" Give the number of players online on all char-servers.\n"); + } else if (strcmp(command, "gm") == 0) { + printf("gm <account_name> [GM_level]\n"); + printf(" Modify the GM level of an account.\n"); + printf(" Default value remove GM level (GM level = 0).\n"); + printf(" <example> gm testname 80\n"); + } else if (strcmp(command, "id") == 0) { + printf("id <account name>\n"); + printf(" Give the id of an account.\n"); + } else if (strcmp(command, "info") == 0) { + printf("info <account_id>\n"); + printf(" Display complete information of an account.\n"); + } else if (strcmp(command, "kami") == 0) { + printf("kami <message>\n"); + printf(" Sends a broadcast message on all map-server (in yellow).\n"); + } else if (strcmp(command, "kamib") == 0) { + printf("kamib <message>\n"); + printf(" Sends a broadcast message on all map-server (in blue).\n"); + } else if (strcmp(command, "language") == 0) { + printf("language <language>\n"); + printf(" Change the language of displaying.\n"); + printf(" Possible languages: Fran軋is or English.\n"); + } else if (strcmp(command, "list") == 0) { + printf("list/ls [start_id [end_id]]\n"); + printf(" Display a list of accounts.\n"); + printf(" 'start_id', 'end_id': indicate end and start identifiers.\n"); + printf(" Research by name is not possible with this command.\n"); + printf(" <example> list 10 9999999\n"); + } else if (strcmp(command, "listban") == 0) { + printf("listBan/lsBan [start_id [end_id]]\n"); + printf(" Like list/ls, but only for accounts with state or banished.\n"); + } else if (strcmp(command, "listgm") == 0) { + printf("listGM/lsGM [start_id [end_id]]\n"); + printf(" Like list/ls, but only for GM accounts.\n"); + } else if (strcmp(command, "listok") == 0) { + printf("listOK/lsOK [start_id [end_id]]\n"); + printf(" Like list/ls, but only for accounts without state and not banished.\n"); + } else if (strcmp(command, "memo") == 0) { + printf("memo <account_name> <memo>\n"); + printf(" Modify the memo of an account.\n"); + printf(" 'memo': it can have until 253 characters (with spaces or not).\n"); + } else if (strcmp(command, "name") == 0) { + printf("name <account_id>\n"); + printf(" Give the name of an account.\n"); + } else if (strcmp(command, "password") == 0) { + printf("passwd <account_name> <new_password>\n"); + printf(" Change the password of an account.\n"); + printf(" When new password is omitted,\n"); + printf(" the input is done without displaying of the pressed keys.\n"); + } else if (strcmp(command, "reloadgm") == 0) { + printf("reloadGM\n"); + printf(" Reload GM configuration file\n"); + } else if (strcmp(command, "search") == 0) { + printf("search <expression>\n"); + printf(" Seek accounts.\n"); + printf(" Displays the accounts whose names correspond.\n"); +// printf("search -r/-e/--expr/--regex <expression>\n"); +// printf(" Seek accounts by regular expression.\n"); +// printf(" Displays the accounts whose names correspond.\n"); + } else if (strcmp(command, "sex") == 0) { + printf("sex <account_name> <sex>\n"); + printf(" Modify the sex of an account.\n"); + printf(" <example> sex testname Male\n"); + } else if (strcmp(command, "state") == 0) { + printf("state <account_name> <new_state> <error_message_#7>\n"); + printf(" Change the state of an account.\n"); + printf(" 'new_state': state is the state of the packet 0x006a + 1.\n"); + printf(" The possibilities are:\n"); + printf(" 0 = Account ok\n"); + printf(" 1 = Unregistered ID\n"); + printf(" 2 = Incorrect Password\n"); + printf(" 3 = This ID is expired\n"); + printf(" 4 = Rejected from Server\n"); + printf(" 5 = You have been blocked by the GM Team\n"); + printf(" 6 = Your Game's EXE file is not the latest version\n"); + printf(" 7 = You are Prohibited to log in until...\n"); + printf(" 8 = Server is jammed due to over populated\n"); + printf(" 9 = No MSG\n"); + printf(" 100 = This ID has been totally erased\n"); + printf(" all other values are 'No MSG', then use state 9 please.\n"); + printf(" 'error_message_#7': message of the code error 6\n"); + printf(" = Your are Prohibited to log in until... (packet 0x006a)\n"); + } else if (strcmp(command, "timeadd") == 0) { + printf("timeadd <account_name> <modifier>\n"); + printf(" Adds or substracts time from the validity limit of an account.\n"); + printf(" Modifier is done as follows:\n"); + printf(" Adjustment value (-1, 1, +1, etc...)\n"); + printf(" Modified element:\n"); + printf(" a or y: year\n"); + printf(" m: month\n"); + printf(" j or d: day\n"); + printf(" h: hour\n"); + printf(" mn: minute\n"); + printf(" s: second\n"); + printf(" <example> timeadd testname +1m-2mn1s-6y\n"); + printf(" this example adds 1 month and 1 second, and substracts 2 minutes\n"); + printf(" and 6 years at the same time.\n"); + printf("NOTE: You can not modify a unlimited validity limit.\n"); + printf(" If you want modify it, you want probably create a limited validity limit.\n"); + printf(" So, at first, you must set the validity limit to a date/time.\n"); + } else if (strcmp(command, "timeadd") == 0) { + printf("timeset <account_name> yyyy/mm/dd [hh:mm:ss]\n"); + printf(" Changes the validity limit of an account.\n"); + printf(" Default time [hh:mm:ss]: 23:59:59.\n"); + printf("timeset <account_name> 0\n"); + printf(" Gives an unlimited validity limit (0 = unlimited).\n"); + } else if (strcmp(command, "unban") == 0) { + printf("unban/unbanish <account name>\n"); + printf(" Remove the banishment of an account.\n"); + printf(" This command works like banset <account_name> 0.\n"); + } else if (strcmp(command, "unblock") == 0) { + printf("unblock <account name>\n"); + printf(" Set state 0 (Account ok) to an account.\n"); + printf(" This command works like state <account_name> 0.\n"); + } else if (strcmp(command, "version") == 0) { + printf("version\n"); + printf(" Display the version of the login-server.\n"); + } else if (strcmp(command, "who") == 0) { + printf("who <account name>\n"); + printf(" Displays complete information of an account.\n"); +// quit + } else if (strcmp(command, "quit") == 0 || + strcmp(command, "exit") == 0 || + strcmp(command, "end") == 0) { + printf("quit/end/exit\n"); + printf(" End of the program of administration.\n"); +// unknown command + } else { + if (strlen(command) > 0) + printf("Unknown command [%s] for help. Displaying of all commands.\n", command); + printf(" aide/help/? -- Display this help\n"); + printf(" aide/help/? [command] -- Display the help of the command\n"); + printf(" add <account_name> <sex> <password> -- Create an account with default email\n"); + printf(" ban/banish yyyy/mm/dd hh:mm:ss <account name> -- Change final date of a ban\n"); + printf(" banadd/ba <account_name> <modifier> -- Add or substract time from the final\n"); + printf(" example: ba apple +1m-2mn1s-2y date of a banishment of an account\n"); + printf(" banset/bs <account_name> yyyy/mm/dd [hh:mm:ss] -- Change final date of a ban\n"); + printf(" banset/bs <account_name> 0 -- Un-banish an account\n"); + printf(" block <account name> -- Set state 5 (blocked by the GM Team) to an account\n"); + printf(" check <account_name> <password> -- Check the validity of a password\n"); + printf(" create <account_name> <sex> <email> <passwrd> -- Create an account with email\n"); + printf(" del <account name> -- Remove an account\n"); + printf(" email <account_name> <email> -- Modify an email of an account\n"); + printf(" getcount -- Give the number of players online\n"); + printf(" gm <account_name> [GM_level] -- Modify the GM level of an account\n"); + printf(" id <account name> -- Give the id of an account\n"); + printf(" info <account_id> -- Display all information of an account\n"); + printf(" kami <message> -- Sends a broadcast message (in yellow)\n"); + printf(" kamib <message> -- Sends a broadcast message (in blue)\n"); + printf(" language <language> -- Change the language of displaying.\n"); + printf(" list/ls [First_id [Last_id]] -- Display a list of accounts\n"); + printf(" listBan/lsBan [First_id [Last_id] ] -- Display a list of accounts\n"); + printf(" with state or banished\n"); + printf(" listGM/lsGM [First_id [Last_id]] -- Display a list of GM accounts\n"); + printf(" listOK/lsOK [First_id [Last_id] ] -- Display a list of accounts\n"); + printf(" without state and not banished\n"); + printf(" memo <account_name> <memo> -- Modify the memo of an account\n"); + printf(" name <account_id> -- Give the name of an account\n"); + printf(" passwd <account_name> <new_password> -- Change the password of an account\n"); + printf(" quit/end/exit -- End of the program of administation\n"); + printf(" reloadGM -- Reload GM configuration file\n"); + printf(" search <expression> -- Seek accounts\n"); +// printf(" search -e/-r/--expr/--regex <expressn> -- Seek accounts by regular-expression\n"); + printf(" sex <nomcompte> <sexe> -- Modify the sex of an account\n"); + printf(" state <account_name> <new_state> <error_message_#7> -- Change the state\n"); + printf(" timeadd/ta <account_name> <modifier> -- Add or substract time from the\n"); + printf(" example: ta apple +1m-2mn1s-2y validity limit of an account\n"); + printf(" timeset/ts <account_name> yyyy/mm/dd [hh:mm:ss] -- Change the validify limit\n"); + printf(" timeset/ts <account_name> 0 -- Give a unlimited validity limit\n"); + printf(" unban/unbanish <account name> -- Remove the banishment of an account\n"); + printf(" unblock <account name> -- Set state 0 (Account ok) to an account\n"); + printf(" version -- Gives the version of the login-server\n"); + printf(" who <account name> -- Display all information of an account\n"); + printf(" who <account name> -- Display all information of an account\n"); + printf(" Note: To use spaces in an account name, type \"<account name>\" (or ').\n"); + } + } +} + +//----------------------------- +// Sub-function: add an account +//----------------------------- +int addaccount(char* param, int emailflag) { + char name[1023], sex[1023], email[1023], password[1023]; +// int i; + + memset(name, '\0', sizeof(name)); + memset(sex, '\0', sizeof(sex)); + memset(email, '\0', sizeof(email)); + memset(password, '\0', sizeof(password)); + + if (emailflag == 0) { // add command + if (sscanf(param, "\"%[^\"]\" %s %[^\r\n]", name, sex, password) < 2 && // password can be void + sscanf(param, "'%[^']' %s %[^\r\n]", name, sex, password) < 2 && // password can be void + sscanf(param, "%s %s %[^\r\n]", name, sex, password) < 2) { // password can be void + if (defaultlanguage == 'F') { + printf("Entrez un nom de compte, un sexe et un mot de passe svp.\n"); + printf("<exemple> add nomtest Male motdepassetest\n"); + ladmin_log("Nombre incorrect de param鑼res pour cr馥r un compte (commande 'add')." RETCODE); + } else { + printf("Please input an account name, a sex and a password.\n"); + printf("<example> add testname Male testpass\n"); + ladmin_log("Incomplete parameters to create an account ('add' command)." RETCODE); + } + return 136; + } + strcpy(email, "a@a.com"); // default email + } else { // 1: create command + if (sscanf(param, "\"%[^\"]\" %s %s %[^\r\n]", name, sex, email, password) < 3 && // password can be void + sscanf(param, "'%[^']' %s %s %[^\r\n]", name, sex, email, password) < 3 && // password can be void + sscanf(param, "%s %s %s %[^\r\n]", name, sex, email, password) < 3) { // password can be void + if (defaultlanguage == 'F') { + printf("Entrez un nom de compte, un sexe et un mot de passe svp.\n"); + printf("<exemple> create nomtest Male mo@mail.com motdepassetest\n"); + ladmin_log("Nombre incorrect de param鑼res pour cr馥r un compte (commande 'create')." RETCODE); + } else { + printf("Please input an account name, a sex and a password.\n"); + printf("<example> create testname Male my@mail.com testpass\n"); + ladmin_log("Incomplete parameters to create an account ('create' command)." RETCODE); + } + return 136; + } + } + if (verify_accountname(name) == 0) { + return 102; + } + +/* for(i = 0; name[i]; i++) { + if (strchr("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_", name[i]) == NULL) { + if (defaultlanguage == 'F') { + printf("Caract鑽e interdit (%c) trouv dans le nom du compte (%d%s caract鑽e).\n", name[i], i+1, makeordinal(i+1)); + ladmin_log("Caract鑽e interdit (%c) trouv dans le nom du compte (%d%s caract鑽e)." RETCODE, name[i], i+1, makeordinal(i+1)); + } else { + printf("Illegal character (%c) found in the account name (%d%s character).\n", name[i], i+1, makeordinal(i+1)); + ladmin_log("Illegal character (%c) found in the account name (%d%s character)." RETCODE, name[i], i+1, makeordinal(i+1)); + } + return 101; + } + }*/ + + sex[0] = toupper(sex[0]); + if (strchr("MF", sex[0]) == NULL) { + if (defaultlanguage == 'F') { + printf("Sexe incorrect [%s]. Entrez M ou F svp.\n", sex); + ladmin_log("Sexe incorrect [%s]. Entrez M ou F svp." RETCODE, sex); + } else { + printf("Illegal gender [%s]. Please input M or F.\n", sex); + ladmin_log("Illegal gender [%s]. Please input M or F." RETCODE, sex); + } + return 103; + } + + if (strlen(email) < 3) { + if (defaultlanguage == 'F') { + printf("Email trop courte [%s]. Entrez une e-mail valide svp.\n", email); + ladmin_log("Email trop courte [%s]. Entrez une e-mail valide svp." RETCODE, email); + } else { + printf("Email is too short [%s]. Please input a valid e-mail.\n", email); + ladmin_log("Email is too short [%s]. Please input a valid e-mail." RETCODE, email); + } + return 109; + } + if (strlen(email) > 39) { + if (defaultlanguage == 'F') { + printf("Email trop longue [%s]. Entrez une e-mail de 39 caract鑽es maximum svp.\n", email); + ladmin_log("Email trop longue [%s]. Entrez une e-mail de 39 caract鑽es maximum svp." RETCODE, email); + } else { + printf("Email is too long [%s]. Please input an e-mail with 39 bytes at the most.\n", email); + ladmin_log("Email is too long [%s]. Please input an e-mail with 39 bytes at the most." RETCODE, email); + } + return 109; + } + if (e_mail_check(email) == 0) { + if (defaultlanguage == 'F') { + printf("Email incorrecte [%s]. Entrez une e-mail valide svp.\n", email); + ladmin_log("Email incorrecte [%s]. Entrez une e-mail valide svp." RETCODE, email); + } else { + printf("Invalid email [%s]. Please input a valid e-mail.\n", email); + ladmin_log("Invalid email [%s]. Please input a valid e-mail." RETCODE, email); + } + return 109; + } + + if (strlen(password) == 0) { + if (typepasswd(password) == 0) + return 108; + } + if (verify_password(password) == 0) + return 104; + + if (defaultlanguage == 'F') { + ladmin_log("Envoi d'un requ黎e au serveur de logins pour cr馥r un compte." RETCODE); + } else { + ladmin_log("Request to login-server to create an account." RETCODE); + } + + WFIFOW(login_fd,0) = 0x7930; + memcpy(WFIFOP(login_fd,2), name, 24); + memcpy(WFIFOP(login_fd,26), password, 24); + WFIFOB(login_fd,50) = sex[0]; + memcpy(WFIFOP(login_fd,51), email, 40); + WFIFOSET(login_fd,91); + bytes_to_read = 1; + + return 0; +} + +//--------------------------------------------------------------------------------- +// Sub-function: Add/substract time to the final date of a banishment of an account +//--------------------------------------------------------------------------------- +int banaddaccount(char* param) { + char name[1023], modif[1023]; + int year, month, day, hour, minute, second; + char * p_modif; + int value, i; + + memset(name, '\0', sizeof(name)); + memset(modif, '\0', sizeof(modif)); + year = month = day = hour = minute = second = 0; + + if (sscanf(param, "\"%[^\"]\" %[^\r\n]", name, modif) < 2 && + sscanf(param, "'%[^']' %[^\r\n]", name, modif) < 2 && + sscanf(param, "%s %[^\r\n]", name, modif) < 2) { + if (defaultlanguage == 'F') { + printf("Entrez un nom de compte et un modificateur svp.\n"); + printf(" <exemple> banadd nomtest +1m-2mn1s-6y\n"); + printf(" Cette exemple ajoute 1 mois et 1 seconde, et soustrait 2 minutes\n"); + printf(" et 6 ans dans le m麥e temps.\n"); + ladmin_log("Nombre incorrect de param鑼res pour modifier la fin de ban d'un compte (commande 'banadd')." RETCODE); + } else { + printf("Please input an account name and a modifier.\n"); + printf(" <example>: banadd testname +1m-2mn1s-6y\n"); + printf(" this example adds 1 month and 1 second, and substracts 2 minutes\n"); + printf(" and 6 years at the same time.\n"); + ladmin_log("Incomplete parameters to modify the ban date/time of an account ('banadd' command)." RETCODE); + } + return 136; + } + if (verify_accountname(name) == 0) { + return 102; + } + + // lowercase for modif + for (i = 0; modif[i]; i++) + modif[i] = tolower(modif[i]); + p_modif = modif; + while (strlen(p_modif) > 0) { + value = atoi(p_modif); + if (value == 0) { + p_modif++; + } else { + if (p_modif[0] == '-' || p_modif[0] == '+') + p_modif++; + while (strlen(p_modif) > 0 && p_modif[0] >= '0' && p_modif[0] <= '9') { + p_modif++; + } + if (p_modif[0] == 's') { + second = value; + p_modif++; + } else if (p_modif[0] == 'm' && p_modif[1] == 'n') { + minute = value; + p_modif += 2; + } else if (p_modif[0] == 'h') { + hour = value; + p_modif++; + } else if (p_modif[0] == 'd' || p_modif[0] == 'j') { + day = value; + p_modif += 2; + } else if (p_modif[0] == 'm') { + month = value; + p_modif++; + } else if (p_modif[0] == 'y' || p_modif[0] == 'a') { + year = value; + p_modif++; + } else { + p_modif++; + } + } + } + + if (defaultlanguage == 'F') { + printf(" ann馥: %d\n", year); + printf(" mois: %d\n", month); + printf(" jour: %d\n", day); + printf(" heure: %d\n", hour); + printf(" minute: %d\n", minute); + printf(" seconde: %d\n", second); + } else { + printf(" year: %d\n", year); + printf(" month: %d\n", month); + printf(" day: %d\n", day); + printf(" hour: %d\n", hour); + printf(" minute: %d\n", minute); + printf(" second: %d\n", second); + } + + if (year == 0 && month == 0 && day == 0 && hour == 0 && minute == 0 && second == 0) { + if (defaultlanguage == 'F') { + printf("Vous devez entrer un ajustement avec cette commande, svp:\n"); + printf(" Valeur d'ajustement (-1, 1, +1, etc...)\n"); + printf(" Element modifi:\n"); + printf(" a ou y: ann馥\n"); + printf(" m: mois\n"); + printf(" j ou d: jour\n"); + printf(" h: heure\n"); + printf(" mn: minute\n"); + printf(" s: seconde\n"); + printf(" <exemple> banadd nomtest +1m-2mn1s-6y\n"); + printf(" Cette exemple ajoute 1 mois et 1 seconde, et soustrait 2 minutes\n"); + printf(" et 6 ans dans le m麥e temps.\n"); + ladmin_log("Aucun ajustement n'est pas un ajustement (commande 'banadd')." RETCODE); + } else { + printf("Please give an adjustment with this command:\n"); + printf(" Adjustment value (-1, 1, +1, etc...)\n"); + printf(" Modified element:\n"); + printf(" a or y: year\n"); + printf(" m: month\n"); + printf(" j or d: day\n"); + printf(" h: hour\n"); + printf(" mn: minute\n"); + printf(" s: second\n"); + printf(" <example> banadd testname +1m-2mn1s-6y\n"); + printf(" this example adds 1 month and 1 second, and substracts 2 minutes\n"); + printf(" and 6 years at the same time.\n"); + ladmin_log("No adjustment isn't an adjustment ('banadd' command)." RETCODE); + } + return 137; + } + if (year > 127 || year < -127) { + if (defaultlanguage == 'F') { + printf("Entrez un ajustement d'ann馥s correct (de -127 127), svp.\n"); + ladmin_log("Ajustement de l'ann馥 hors norme (commande 'banadd')." RETCODE); + } else { + printf("Please give a correct adjustment for the years (from -127 to 127).\n"); + ladmin_log("Abnormal adjustement for the year ('banadd' command)." RETCODE); + } + return 137; + } + if (month > 255 || month < -255) { + if (defaultlanguage == 'F') { + printf("Entrez un ajustement de mois correct (de -255 255), svp.\n"); + ladmin_log("Ajustement du mois hors norme (commande 'banadd')." RETCODE); + } else { + printf("Please give a correct adjustment for the months (from -255 to 255).\n"); + ladmin_log("Abnormal adjustement for the month ('banadd' command)." RETCODE); + } + return 137; + } + if (day > 32767 || day < -32767) { + if (defaultlanguage == 'F') { + printf("Entrez un ajustement de jours correct (de -32767 32767), svp.\n"); + ladmin_log("Ajustement des jours hors norme (commande 'banadd')." RETCODE); + } else { + printf("Please give a correct adjustment for the days (from -32767 to 32767).\n"); + ladmin_log("Abnormal adjustement for the days ('banadd' command)." RETCODE); + } + return 137; + } + if (hour > 32767 || hour < -32767) { + if (defaultlanguage == 'F') { + printf("Entrez un ajustement d'heures correct (de -32767 32767), svp.\n"); + ladmin_log("Ajustement des heures hors norme (commande 'banadd')." RETCODE); + } else { + printf("Please give a correct adjustment for the hours (from -32767 to 32767).\n"); + ladmin_log("Abnormal adjustement for the hours ('banadd' command)." RETCODE); + } + return 137; + } + if (minute > 32767 || minute < -32767) { + if (defaultlanguage == 'F') { + printf("Entrez un ajustement de minutes correct (de -32767 32767), svp.\n"); + ladmin_log("Ajustement des minutes hors norme (commande 'banadd')." RETCODE); + } else { + printf("Please give a correct adjustment for the minutes (from -32767 to 32767).\n"); + ladmin_log("Abnormal adjustement for the minutes ('banadd' command)." RETCODE); + } + return 137; + } + if (second > 32767 || second < -32767) { + if (defaultlanguage == 'F') { + printf("Entrez un ajustement de secondes correct (de -32767 32767), svp.\n"); + ladmin_log("Ajustement des secondes hors norme (commande 'banadd')." RETCODE); + } else { + printf("Please give a correct adjustment for the seconds (from -32767 to 32767).\n"); + ladmin_log("Abnormal adjustement for the seconds ('banadd' command)." RETCODE); + } + return 137; + } + + if (defaultlanguage == 'F') { + ladmin_log("Envoi d'un requ黎e au serveur de logins pour modifier la date d'un bannissement." RETCODE); + } else { + ladmin_log("Request to login-server to modify a ban date/time." RETCODE); + } + + WFIFOW(login_fd,0) = 0x794c; + memcpy(WFIFOP(login_fd,2), name, 24); + WFIFOW(login_fd,26) = (short)year; + WFIFOW(login_fd,28) = (short)month; + WFIFOW(login_fd,30) = (short)day; + WFIFOW(login_fd,32) = (short)hour; + WFIFOW(login_fd,34) = (short)minute; + WFIFOW(login_fd,36) = (short)second; + WFIFOSET(login_fd,38); + bytes_to_read = 1; + + return 0; +} + +//----------------------------------------------------------------------- +// Sub-function of sub-function banaccount, unbanaccount or bansetaccount +// Set the final date of a banishment of an account +//----------------------------------------------------------------------- +int bansetaccountsub(char* name, char* date, char* time) { + int year, month, day, hour, minute, second; + time_t ban_until_time; // # of seconds 1/1/1970 (timestamp): ban time limit of the account (0 = no ban) + struct tm *tmtime; + + year = month = day = hour = minute = second = 0; + ban_until_time = 0; + tmtime = localtime(&ban_until_time); // initialize + + if (verify_accountname(name) == 0) { + return 102; + } + + if (atoi(date) != 0 && + ((sscanf(date, "%d/%d/%d", &year, &month, &day) < 3 && + sscanf(date, "%d-%d-%d", &year, &month, &day) < 3 && + sscanf(date, "%d.%d.%d", &year, &month, &day) < 3) || + sscanf(time, "%d:%d:%d", &hour, &minute, &second) < 3)) { + if (defaultlanguage == 'F') { + printf("Entrez une date et une heure svp (format: aaaa/mm/jj hh:mm:ss).\n"); + printf("Vous pouvez aussi mettre 0 la place si vous utilisez la commande 'banset'.\n"); + ladmin_log("Format incorrect pour la date/heure (commande'banset' ou 'ban')." RETCODE); + } else { + printf("Please input a date and a time (format: yyyy/mm/dd hh:mm:ss).\n"); + printf("You can imput 0 instead of if you use 'banset' command.\n"); + ladmin_log("Invalid format for the date/time ('banset' or 'ban' command)." RETCODE); + } + return 102; + } + + if (atoi(date) == 0) { + ban_until_time = 0; + } else { + if (year < 70) { + year = year + 100; + } + if (year >= 1900) { + year = year - 1900; + } + if (month < 1 || month > 12) { + if (defaultlanguage == 'F') { + printf("Entrez un mois correct svp (entre 1 et 12).\n"); + ladmin_log("Mois incorrect pour la date (command 'banset' ou 'ban')." RETCODE); + } else { + printf("Please give a correct value for the month (from 1 to 12).\n"); + ladmin_log("Invalid month for the date ('banset' or 'ban' command)." RETCODE); + } + return 102; + } + month = month - 1; + if (day < 1 || day > 31) { + if (defaultlanguage == 'F') { + printf("Entrez un jour correct svp (entre 1 et 31).\n"); + ladmin_log("Jour incorrect pour la date (command 'banset' ou 'ban')." RETCODE); + } else { + printf("Please give a correct value for the day (from 1 to 31).\n"); + ladmin_log("Invalid day for the date ('banset' or 'ban' command)." RETCODE); + } + return 102; + } + if (((month == 3 || month == 5 || month == 8 || month == 10) && day > 30) || + (month == 1 && day > 29)) { + if (defaultlanguage == 'F') { + printf("Entrez un jour correct en fonction du mois (%d) svp.\n", month); + ladmin_log("Jour incorrect pour ce mois correspondant (command 'banset' ou 'ban')." RETCODE); + } else { + printf("Please give a correct value for a day of this month (%d).\n", month); + ladmin_log("Invalid day for this month ('banset' or 'ban' command)." RETCODE); + } + return 102; + } + if (hour < 0 || hour > 23) { + if (defaultlanguage == 'F') { + printf("Entrez une heure correcte svp (entre 0 et 23).\n"); + ladmin_log("Heure incorrecte pour l'heure (command 'banset' ou 'ban')." RETCODE); + } else { + printf("Please give a correct value for the hour (from 0 to 23).\n"); + ladmin_log("Invalid hour for the time ('banset' or 'ban' command)." RETCODE); + } + return 102; + } + if (minute < 0 || minute > 59) { + if (defaultlanguage == 'F') { + printf("Entrez des minutes correctes svp (entre 0 et 59).\n"); + ladmin_log("Minute incorrecte pour l'heure (command 'banset' ou 'ban')." RETCODE); + } else { + printf("Please give a correct value for the minutes (from 0 to 59).\n"); + ladmin_log("Invalid minute for the time ('banset' or 'ban' command)." RETCODE); + } + return 102; + } + if (second < 0 || second > 59) { + if (defaultlanguage == 'F') { + printf("Entrez des secondes correctes svp (entre 0 et 59).\n"); + ladmin_log("Seconde incorrecte pour l'heure (command 'banset' ou 'ban')." RETCODE); + } else { + printf("Please give a correct value for the seconds (from 0 to 59).\n"); + ladmin_log("Invalid second for the time ('banset' or 'ban' command)." RETCODE); + } + return 102; + } + tmtime->tm_year = year; + tmtime->tm_mon = month; + tmtime->tm_mday = day; + tmtime->tm_hour = hour; + tmtime->tm_min = minute; + tmtime->tm_sec = second; + tmtime->tm_isdst = -1; // -1: no winter/summer time modification + ban_until_time = mktime(tmtime); + if (ban_until_time == -1) { + if (defaultlanguage == 'F') { + printf("Date incorrecte.\n"); + printf("Entrez une date et une heure svp (format: aaaa/mm/jj hh:mm:ss).\n"); + printf("Vous pouvez aussi mettre 0 la place si vous utilisez la commande 'banset'.\n"); + ladmin_log("Date incorrecte. (command 'banset' ou 'ban')." RETCODE); + } else { + printf("Invalid date.\n"); + printf("Please input a date and a time (format: yyyy/mm/dd hh:mm:ss).\n"); + printf("You can imput 0 instead of if you use 'banset' command.\n"); + ladmin_log("Invalid date. ('banset' or 'ban' command)." RETCODE); + } + return 102; + } + } + + if (defaultlanguage == 'F') { + ladmin_log("Envoi d'un requ黎e au serveur de logins pour fixer un ban." RETCODE); + } else { + ladmin_log("Request to login-server to set a ban." RETCODE); + } + + WFIFOW(login_fd,0) = 0x794a; + memcpy(WFIFOP(login_fd,2), name, 24); + WFIFOL(login_fd,26) = (int)ban_until_time; + WFIFOSET(login_fd,30); + bytes_to_read = 1; + + return 0; +} + +//--------------------------------------------------------------------- +// Sub-function: Set the final date of a banishment of an account (ban) +//--------------------------------------------------------------------- +int banaccount(char* param) { + char name[1023], date[1023], time[1023]; + + memset(name, '\0', sizeof(name)); + memset(date, '\0', sizeof(date)); + memset(time, '\0', sizeof(time)); + + if (sscanf(param, "%s %s \"%[^\"]\"", date, time, name) < 3 && + sscanf(param, "%s %s '%[^']'", date, time, name) < 3 && + sscanf(param, "%s %s %[^\r\n]", date, time, name) < 3) { + if (defaultlanguage == 'F') { + printf("Entrez un nom de compte, une date et une heure svp.\n"); + printf("<exemple>: banset <nom_du_compte> aaaa/mm/jj [hh:mm:ss]\n"); + printf(" banset <nom_du_compte> 0 (0 = d-bani)\n"); + printf(" ban/banish aaaa/mm/jj hh:mm:ss <nom du compte>\n"); + printf(" unban/unbanish <nom du compte>\n"); + printf(" Heure par d馭aut [hh:mm:ss]: 23:59:59.\n"); + ladmin_log("Nombre incorrect de param鑼res pour fixer un ban (commande 'banset' ou 'ban')." RETCODE); + } else { + printf("Please input an account name, a date and a hour.\n"); + printf("<example>: banset <account_name> yyyy/mm/dd [hh:mm:ss]\n"); + printf(" banset <account_name> 0 (0 = un-banished)\n"); + printf(" ban/banish yyyy/mm/dd hh:mm:ss <account name>\n"); + printf(" unban/unbanish <account name>\n"); + printf(" Default time [hh:mm:ss]: 23:59:59.\n"); + ladmin_log("Incomplete parameters to set a ban ('banset' or 'ban' command)." RETCODE); + } + return 136; + } + + return bansetaccountsub(name, date, time); +} + +//------------------------------------------------------------------------ +// Sub-function: Set the final date of a banishment of an account (banset) +//------------------------------------------------------------------------ +int bansetaccount(char* param) { + char name[1023], date[1023], time[1023]; + + memset(name, '\0', sizeof(name)); + memset(date, '\0', sizeof(date)); + memset(time, '\0', sizeof(time)); + + if (sscanf(param, "\"%[^\"]\" %s %[^\r\n]", name, date, time) < 2 && // if date = 0, time can be void + sscanf(param, "'%[^']' %s %[^\r\n]", name, date, time) < 2 && // if date = 0, time can be void + sscanf(param, "%s %s %[^\r\n]", name, date, time) < 2) { // if date = 0, time can be void + if (defaultlanguage == 'F') { + printf("Entrez un nom de compte, une date et une heure svp.\n"); + printf("<exemple>: banset <nom_du_compte> aaaa/mm/jj [hh:mm:ss]\n"); + printf(" banset <nom_du_compte> 0 (0 = d-bani)\n"); + printf(" ban/banish aaaa/mm/jj hh:mm:ss <nom du compte>\n"); + printf(" unban/unbanish <nom du compte>\n"); + printf(" Heure par d馭aut [hh:mm:ss]: 23:59:59.\n"); + ladmin_log("Nombre incorrect de param鑼res pour fixer un ban (commande 'banset' ou 'ban')." RETCODE); + } else { + printf("Please input an account name, a date and a hour.\n"); + printf("<example>: banset <account_name> yyyy/mm/dd [hh:mm:ss]\n"); + printf(" banset <account_name> 0 (0 = un-banished)\n"); + printf(" ban/banish yyyy/mm/dd hh:mm:ss <account name>\n"); + printf(" unban/unbanish <account name>\n"); + printf(" Default time [hh:mm:ss]: 23:59:59.\n"); + ladmin_log("Incomplete parameters to set a ban ('banset' or 'ban' command)." RETCODE); + } + return 136; + } + + if (time[0] == '\0') + strcpy(time, "23:59:59"); + + return bansetaccountsub(name, date, time); +} + +//------------------------------------------------- +// Sub-function: unbanishment of an account (unban) +//------------------------------------------------- +int unbanaccount(char* param) { + char name[1023]; + + memset(name, '\0', sizeof(name)); + + if (strlen(param) == 0 || + (sscanf(param, "\"%[^\"]\"", name) < 1 && + sscanf(param, "'%[^']'", name) < 1 && + sscanf(param, "%[^\r\n]", name) < 1) || + strlen(name) == 0) { + if (defaultlanguage == 'F') { + printf("Entrez un nom de compte svp.\n"); + printf("<exemple>: banset <nom_du_compte> aaaa/mm/jj [hh:mm:ss]\n"); + printf(" banset <nom_du_compte> 0 (0 = d-bani)\n"); + printf(" ban/banish aaaa/mm/jj hh:mm:ss <nom du compte>\n"); + printf(" unban/unbanish <nom du compte>\n"); + printf(" Heure par d馭aut [hh:mm:ss]: 23:59:59.\n"); + ladmin_log("Nombre incorrect de param鑼res pour fixer un ban (commande 'unban')." RETCODE); + } else { + printf("Please input an account name.\n"); + printf("<example>: banset <account_name> yyyy/mm/dd [hh:mm:ss]\n"); + printf(" banset <account_name> 0 (0 = un-banished)\n"); + printf(" ban/banish yyyy/mm/dd hh:mm:ss <account name>\n"); + printf(" unban/unbanish <account name>\n"); + printf(" Default time [hh:mm:ss]: 23:59:59.\n"); + ladmin_log("Incomplete parameters to set a ban ('unban' command)." RETCODE); + } + return 136; + } + + return bansetaccountsub(name, "0", ""); +} + +//--------------------------------------------------------- +// Sub-function: Asking to check the validity of a password +// (Note: never send back a password with login-server!! security of passwords) +//--------------------------------------------------------- +int checkaccount(char* param) { + char name[1023], password[1023]; + + memset(name, '\0', sizeof(name)); + memset(password, '\0', sizeof(password)); + + if (sscanf(param, "\"%[^\"]\" %[^\r\n]", name, password) < 1 && // password can be void + sscanf(param, "'%[^']' %[^\r\n]", name, password) < 1 && // password can be void + sscanf(param, "%s %[^\r\n]", name, password) < 1) { // password can be void + if (defaultlanguage == 'F') { + printf("Entrez un nom de compte svp.\n"); + printf("<exemple> check testname motdepasse\n"); + ladmin_log("Nombre incorrect de param鑼res pour tester le mot d'un passe d'un compte (commande 'check')." RETCODE); + } else { + printf("Please input an account name.\n"); + printf("<example> check testname password\n"); + ladmin_log("Incomplete parameters to check the password of an account ('check' command)." RETCODE); + } + return 136; + } + + if (verify_accountname(name) == 0) { + return 102; + } + + if (strlen(password) == 0) { + if (typepasswd(password) == 0) + return 134; + } + if (verify_password(password) == 0) + return 131; + + if (defaultlanguage == 'F') { + ladmin_log("Envoi d'un requ黎e au serveur de logins pour test un mot de passe." RETCODE); + } else { + ladmin_log("Request to login-server to check a password." RETCODE); + } + + WFIFOW(login_fd,0) = 0x793a; + memcpy(WFIFOP(login_fd,2), name, 24); + memcpy(WFIFOP(login_fd,26), password, 24); + WFIFOSET(login_fd,50); + bytes_to_read = 1; + + return 0; +} + +//------------------------------------------------ +// Sub-function: Asking for deletion of an account +//------------------------------------------------ +int delaccount(char* param) { + char name[1023]; + char letter; + char confirm[1023]; + int i; + + memset(name, '\0', sizeof(name)); + + if (strlen(param) == 0 || + (sscanf(param, "\"%[^\"]\"", name) < 1 && + sscanf(param, "'%[^']'", name) < 1 && + sscanf(param, "%[^\r\n]", name) < 1) || + strlen(name) == 0) { + if (defaultlanguage == 'F') { + printf("Entrez un nom de compte svp.\n"); + printf("<exemple> del nomtestasupprimer\n"); + ladmin_log("Aucun nom donn pour supprimer un compte (commande 'delete')." RETCODE); + } else { + printf("Please input an account name.\n"); + printf("<example> del testnametodelete\n"); + ladmin_log("No name given to delete an account ('delete' command)." RETCODE); + } + return 136; + } + + if (verify_accountname(name) == 0) { + return 102; + } + + memset(confirm, '\0', sizeof(confirm)); + while ((confirm[0] != 'o' || defaultlanguage != 'F') && confirm[0] != 'n' && (confirm[0] != 'y' || defaultlanguage == 'F')) { + if (defaultlanguage == 'F') + printf("\033[1;36m ** Etes-vous vraiment sr de vouloir SUPPRIMER le compte [$userid]? (o/n) > \033[0m"); + else + printf("\033[1;36m ** Are you really sure to DELETE account [$userid]? (y/n) > \033[0m"); + fflush(stdout); + memset(confirm, '\0', sizeof(confirm)); + i = 0; + while ((letter = getchar()) != '\n') + confirm[i++] = letter; + } + + if (confirm[0] == 'n') { + if (defaultlanguage == 'F') { + printf("Suppression annul馥.\n"); + ladmin_log("Suppression annul馥 par l'utilisateur (commande 'delete')." RETCODE); + } else { + printf("Deletion canceled.\n"); + ladmin_log("Deletion canceled by user ('delete' command)." RETCODE); + } + return 121; + } + + if (defaultlanguage == 'F') { + ladmin_log("Envoi d'un requ黎e au serveur de logins pour d騁ruire un compte." RETCODE); + } else { + ladmin_log("Request to login-server to delete an acount." RETCODE); + } + + WFIFOW(login_fd,0) = 0x7932; + memcpy(WFIFOP(login_fd,2), name, 24); + WFIFOSET(login_fd,26); + bytes_to_read = 1; + + return 0; +} + +//---------------------------------------------------------- +// Sub-function: Asking to modification of an account e-mail +//---------------------------------------------------------- +int changeemail(char* param) { + char name[1023], email[1023]; + + memset(name, '\0', sizeof(name)); + memset(email, '\0', sizeof(email)); + + if (sscanf(param, "\"%[^\"]\" %[^\r\n]", name, email) < 2 && + sscanf(param, "'%[^']' %[^\r\n]", name, email) < 2 && + sscanf(param, "%s %[^\r\n]", name, email) < 2) { + if (defaultlanguage == 'F') { + printf("Entrez un nom de compte et une email svp.\n"); + printf("<exemple> email testname nouveauemail\n"); + ladmin_log("Nombre incorrect de param鑼res pour changer l'email d'un compte (commande 'email')." RETCODE); + } else { + printf("Please input an account name and an email.\n"); + printf("<example> email testname newemail\n"); + ladmin_log("Incomplete parameters to change the email of an account ('email' command)." RETCODE); + } + return 136; + } + + if (verify_accountname(name) == 0) { + return 102; + } + + if (strlen(email) < 3) { + if (defaultlanguage == 'F') { + printf("Email trop courte [%s]. Entrez une e-mail valide svp.\n", email); + ladmin_log("Email trop courte [%s]. Entrez une e-mail valide svp." RETCODE, email); + } else { + printf("Email is too short [%s]. Please input a valid e-mail.\n", email); + ladmin_log("Email is too short [%s]. Please input a valid e-mail." RETCODE, email); + } + return 109; + } + if (strlen(email) > 39) { + if (defaultlanguage == 'F') { + printf("Email trop longue [%s]. Entrez une e-mail de 39 caract鑽es maximum svp.\n", email); + ladmin_log("Email trop longue [%s]. Entrez une e-mail de 39 caract鑽es maximum svp." RETCODE, email); + } else { + printf("Email is too long [%s]. Please input an e-mail with 39 bytes at the most.\n", email); + ladmin_log("Email is too long [%s]. Please input an e-mail with 39 bytes at the most." RETCODE, email); + } + return 109; + } + if (e_mail_check(email) == 0) { + if (defaultlanguage == 'F') { + printf("Email incorrecte [%s]. Entrez une e-mail valide svp.\n", email); + ladmin_log("Email incorrecte [%s]. Entrez une e-mail valide svp." RETCODE, email); + } else { + printf("Invalid email [%s]. Please input a valid e-mail.\n", email); + ladmin_log("Invalid email [%s]. Please input a valid e-mail." RETCODE, email); + } + return 109; + } + + if (defaultlanguage == 'F') { + ladmin_log("Envoi d'un requ黎e au serveur de logins pour changer une email." RETCODE); + } else { + ladmin_log("Request to login-server to change an email." RETCODE); + } + + WFIFOW(login_fd,0) = 0x7940; + memcpy(WFIFOP(login_fd,2), name, 24); + memcpy(WFIFOP(login_fd,26), email, 40); + WFIFOSET(login_fd,66); + bytes_to_read = 1; + + return 0; +} + +//----------------------------------------------------- +// Sub-function: Asking of the number of online players +//----------------------------------------------------- +int getlogincount() { + if (defaultlanguage == 'F') { + ladmin_log("Envoi d'un requ黎e au serveur de logins pour obtenir le nombre de joueurs en jeu." RETCODE); + } else { + ladmin_log("Request to login-server to obtain the # of online players." RETCODE); + } + + WFIFOW(login_fd,0) = 0x7938; + WFIFOSET(login_fd,2); + bytes_to_read = 1; + + return 0; +} + +//---------------------------------------------------------- +// Sub-function: Asking to modify the GM level of an account +//---------------------------------------------------------- +int changegmlevel(char* param) { + char name[1023]; + int GM_level; + + memset(name, '\0', sizeof(name)); + GM_level = 0; + + if (sscanf(param, "\"%[^\"]\" %d", name, &GM_level) < 1 && + sscanf(param, "'%[^']' %d", name, &GM_level) < 1 && + sscanf(param, "%s %d", name, &GM_level) < 1) { + if (defaultlanguage == 'F') { + printf("Entrez un nom de compte et un niveau de GM svp.\n"); + printf("<exemple> gm nomtest 80\n"); + ladmin_log("Nombre incorrect de param鑼res pour changer le Niveau de GM d'un compte (commande 'gm')." RETCODE); + } else { + printf("Please input an account name and a GM level.\n"); + printf("<example> gm testname 80\n"); + ladmin_log("Incomplete parameters to change the GM level of an account ('gm' command)." RETCODE); + } + return 136; + } + + if (verify_accountname(name) == 0) { + return 102; + } + + if (GM_level < 0 || GM_level > 99) { + if (defaultlanguage == 'F') { + printf("Niveau de GM incorrect [%d]. Entrez une valeur de 0 99 svp.\n", GM_level); + ladmin_log("Niveau de GM incorrect [%d]. La valeur peut 黎re de 0 99." RETCODE, GM_level); + } else { + printf("Illegal GM level [%d]. Please input a value from 0 to 99.\n", GM_level); + ladmin_log("Illegal GM level [%d]. The value can be from 0 to 99." RETCODE, GM_level); + } + return 103; + } + + if (defaultlanguage == 'F') { + ladmin_log("Envoi d'un requ黎e au serveur de logins pour changer un niveau de GM." RETCODE); + } else { + ladmin_log("Request to login-server to change a GM level." RETCODE); + } + + WFIFOW(login_fd,0) = 0x793e; + memcpy(WFIFOP(login_fd,2), name, 24); + WFIFOB(login_fd,26) = GM_level; + WFIFOSET(login_fd,27); + bytes_to_read = 1; + + return 0; +} + +//--------------------------------------------- +// Sub-function: Asking to obtain an account id +//--------------------------------------------- +int idaccount(char* param) { + char name[1023]; + + memset(name, '\0', sizeof(name)); + + if (strlen(param) == 0 || + (sscanf(param, "\"%[^\"]\"", name) < 1 && + sscanf(param, "'%[^']'", name) < 1 && + sscanf(param, "%[^\r\n]", name) < 1) || + strlen(name) == 0) { + if (defaultlanguage == 'F') { + printf("Entrez un nom de compte svp.\n"); + printf("<exemple> id nomtest\n"); + ladmin_log("Aucun nom donn pour rechecher l'id d'un compte (commande 'id')." RETCODE); + } else { + printf("Please input an account name.\n"); + printf("<example> id testname\n"); + ladmin_log("No name given to search an account id ('id' command)." RETCODE); + } + return 136; + } + + if (verify_accountname(name) == 0) { + return 102; + } + + if (defaultlanguage == 'F') { + ladmin_log("Envoi d'un requ黎e au serveur de logins pour connatre l'id d'un compte." RETCODE); + } else { + ladmin_log("Request to login-server to know an account id." RETCODE); + } + + WFIFOW(login_fd,0) = 0x7944; + memcpy(WFIFOP(login_fd,2), name, 24); + WFIFOSET(login_fd,26); + bytes_to_read = 1; + + return 0; +} + +//---------------------------------------------------------------------------- +// Sub-function: Asking to displaying information about an account (by its id) +//---------------------------------------------------------------------------- +int infoaccount(int account_id) { + if (account_id < 0) { + if (defaultlanguage == 'F') { + printf("Entrez un id ayant une valeur positive svp.\n"); + ladmin_log("Une valeur n馮ative a 騁 donn pour trouver le compte." RETCODE); + } else { + printf("Please input a positive value for the id.\n"); + ladmin_log("Negative value was given to found the account." RETCODE); + } + return 136; + } + + if (defaultlanguage == 'F') { + ladmin_log("Envoi d'un requ黎e au serveur de logins pour obtenir le information d'un compte (par l'id)." RETCODE); + } else { + ladmin_log("Request to login-server to obtain information about an account (by its id)." RETCODE); + } + + WFIFOW(login_fd,0) = 0x7954; + WFIFOL(login_fd,2) = account_id; + WFIFOSET(login_fd,6); + bytes_to_read = 1; + + return 0; +} + +//--------------------------------------- +// Sub-function: Send a broadcast message +//--------------------------------------- +int sendbroadcast(short type, char* message) { + if (strlen(message) == 0) { + if (defaultlanguage == 'F') { + printf("Entrez un message svp.\n"); + if (type == 0) { + printf("<exemple> kami un message\n"); + } else { + printf("<exemple> kamib un message\n"); + } + ladmin_log("Le message est vide (commande 'kami(b)')." RETCODE); + } else { + printf("Please input a message.\n"); + if (type == 0) { + printf("<example> kami a message\n"); + } else { + printf("<example> kamib a message\n"); + } + ladmin_log("The message is void ('kami(b)' command)." RETCODE); + } + return 136; + } + + WFIFOW(login_fd,0) = 0x794e; + WFIFOW(login_fd,2) = type; + WFIFOL(login_fd,4) = strlen(message)+1; + memcpy(WFIFOP(login_fd,8), message, strlen(message)+1); + WFIFOSET(login_fd,8+strlen(message)+1); + bytes_to_read = 1; + + return 0; +} + +//-------------------------------------------- +// Sub-function: Change language of displaying +//-------------------------------------------- +int changelanguage(char* language) { + if (strlen(language) == 0) { + if (defaultlanguage == 'F') { + printf("Entrez une langue svp.\n"); + printf("<exemple> language english\n"); + printf(" language fran軋is\n"); + ladmin_log("La langue est vide (commande 'language')." RETCODE); + } else { + printf("Please input a language.\n"); + printf("<example> language english\n"); + printf(" language fran軋is\n"); + ladmin_log("The language is void ('language' command)." RETCODE); + } + return 136; + } + + language[0] = toupper(language[0]); + if (language[0] == 'F' || language[0] == 'E') { + defaultlanguage = language[0]; + if (defaultlanguage == 'F') { + printf("Changement de la langue d'affichage en Fran軋is.\n"); + ladmin_log("Changement de la langue d'affichage en Fran軋is." RETCODE); + } else { + printf("Displaying language changed to English.\n"); + ladmin_log("Displaying language changed to English." RETCODE); + } + } else { + if (defaultlanguage == 'F') { + printf("Langue non param騁r馥 (langues possibles: 'Fran軋is' ou 'English').\n"); + ladmin_log("Langue non param騁r馥 (Fran軋is ou English n馗essaire)." RETCODE); + } else { + printf("Undefined language (possible languages: Fran軋is or English).\n"); + ladmin_log("Undefined language (must be Fran軋is or English)." RETCODE); + } + } + + return 0; +} + +//-------------------------------------------------------- +// Sub-function: Asking to Displaying of the accounts list +//-------------------------------------------------------- +int listaccount(char* param, int type) { +//int list_first, list_last, list_type; // parameter to display a list of accounts + int i; + + list_type = type; + + // set default values + list_first = 0; + list_last = 0; + + if (list_type == 1) { // if listgm + // get all accounts = use default + } else if (list_type == 2) { // if search + for (i = 0; param[i]; i++) + param[i] = tolower(param[i]); + // get all accounts = use default + } else if (list_type == 3) { // if listban + // get all accounts = use default + } else if (list_type == 4) { // if listok + // get all accounts = use default + } else { // if list (list_type == 0) + switch(sscanf(param, "%d %d", &list_first, &list_last)) { + case 0: + // get all accounts = use default + break; + case 1: + list_last = 0; + // use tests of the following value + default: + if (list_first < 0) + list_first = 0; + if (list_last < list_first || list_last < 0) + list_last = 0; + break; + } + } + + if (defaultlanguage == 'F') { + ladmin_log("Envoi d'un requ黎e au serveur de logins pour obtenir la liste des comptes de %d %d." RETCODE, list_first, list_last); + } else { + ladmin_log("Request to login-server to obtain the list of accounts from %d to %d." RETCODE, list_first, list_last); + } + + WFIFOW(login_fd,0) = 0x7920; + WFIFOL(login_fd,2) = list_first; + WFIFOL(login_fd,6) = list_last; + WFIFOSET(login_fd,10); + bytes_to_read = 1; + + // 0123456789 01 01234567890123456789012301234 012345 0123456789012345678901234567 + if (defaultlanguage == 'F') { + printf(" id_compte GM nom_utilisateur sexe count statut\n"); + } else { + printf("account_id GM user_name sex count state\n"); + } + printf("-------------------------------------------------------------------------------\n"); + list_count = 0; + + return 0; +} + +//-------------------------------------------- +// Sub-function: Asking to modify a memo field +//-------------------------------------------- +int changememo(char* param) { + char name[1023], memo[1023]; + + memset(name, '\0', sizeof(name)); + memset(memo, '\0', sizeof(memo)); + + if (sscanf(param, "\"%[^\"]\" %[^\r\n]", name, memo) < 1 && // memo can be void + sscanf(param, "'%[^']' %[^\r\n]", name, memo) < 1 && // memo can be void + sscanf(param, "%s %[^\r\n]", name, memo) < 1) { // memo can be void + if (defaultlanguage == 'F') { + printf("Entrez un nom de compte et un m駑o svp.\n"); + printf("<exemple> memo nomtest nouveau memo\n"); + ladmin_log("Nombre incorrect de param鑼res pour changer le m駑o d'un compte (commande 'email')." RETCODE); + } else { + printf("Please input an account name and a memo.\n"); + printf("<example> memo testname new memo\n"); + ladmin_log("Incomplete parameters to change the memo of an account ('email' command)." RETCODE); + } + return 136; + } + + if (verify_accountname(name) == 0) { + return 102; + } + + if (strlen(memo) > 254) { + if (defaultlanguage == 'F') { + printf("M駑o trop long (%d caract鑽es).\n", strlen(memo)); + printf("Entrez un m駑o de 254 caract鑽es maximum svp.\n"); + ladmin_log("M駑o trop long (%d caract鑽es). Entrez un m駑o de 254 caract鑽es maximum svp." RETCODE, strlen(memo)); + } else { + printf("Memo is too long (%d characters).\n", strlen(memo)); + printf("Please input a memo of 254 bytes at the maximum.\n"); + ladmin_log("Email is too long (%d characters). Please input a memo of 254 bytes at the maximum." RETCODE, strlen(memo)); + } + return 102; + } + + if (defaultlanguage == 'F') { + ladmin_log("Envoi d'un requ黎e au serveur de logins pour changer un m駑o." RETCODE); + } else { + ladmin_log("Request to login-server to change a memo." RETCODE); + } + + WFIFOW(login_fd,0) = 0x7942; + memcpy(WFIFOP(login_fd,2), name, 24); + WFIFOW(login_fd,26) = strlen(memo); + if (strlen(memo) > 0) + memcpy(WFIFOP(login_fd,28), memo, strlen(memo)); + WFIFOSET(login_fd,28+strlen(memo)); + bytes_to_read = 1; + + return 0; +} + +//----------------------------------------------- +// Sub-function: Asking to obtain an account name +//----------------------------------------------- +int nameaccount(int id) { + if (id < 0) { + if (defaultlanguage == 'F') { + printf("Entrez un id ayant une valeur positive svp.\n"); + ladmin_log("Id n馮atif donn pour rechecher le nom d'un compte (commande 'name')." RETCODE); + } else { + printf("Please input a positive value for the id.\n"); + ladmin_log("Negativ id given to search an account name ('name' command)." RETCODE); + } + return 136; + } + + if (defaultlanguage == 'F') + ladmin_log("Envoi d'un requ黎e au serveur de logins pour connatre le nom d'un compte." RETCODE); + else + ladmin_log("Request to login-server to know an account name." RETCODE); + + WFIFOW(login_fd,0) = 0x7946; + WFIFOL(login_fd,2) = id; + WFIFOSET(login_fd,6); + bytes_to_read = 1; + + return 0; +} + +//------------------------------------------ +// Sub-function: Asking to modify a password +// (Note: never send back a password with login-server!! security of passwords) +//------------------------------------------ +int changepasswd(char* param) { + char name[1023], password[1023]; + + memset(name, '\0', sizeof(name)); + memset(password, '\0', sizeof(password)); + + if (sscanf(param, "\"%[^\"]\" %[^\r\n]", name, password) < 1 && + sscanf(param, "'%[^']' %[^\r\n]", name, password) < 1 && + sscanf(param, "%s %[^\r\n]", name, password) < 1) { + if (defaultlanguage == 'F') { + printf("Entrez un nom de compte svp.\n"); + printf("<exemple> passwd nomtest nouveaumotdepasse\n"); + ladmin_log("Nombre incorrect de param鑼res pour changer le mot d'un passe d'un compte (commande 'password')." RETCODE); + } else { + printf("Please input an account name.\n"); + printf("<example> passwd testname newpassword\n"); + ladmin_log("Incomplete parameters to change the password of an account ('password' command)." RETCODE); + } + return 136; + } + + if (verify_accountname(name) == 0) { + return 102; + } + + if (strlen(password) == 0) { + if (typepasswd(password) == 0) + return 134; + } + if (verify_password(password) == 0) + return 131; + + if (defaultlanguage == 'F') { + ladmin_log("Envoi d'un requ黎e au serveur de logins pour changer un mot de passe." RETCODE); + } else { + ladmin_log("Request to login-server to change a password." RETCODE); + } + + WFIFOW(login_fd,0) = 0x7934; + memcpy(WFIFOP(login_fd,2), name, 24); + memcpy(WFIFOP(login_fd,26), password, 24); + WFIFOSET(login_fd,50); + bytes_to_read = 1; + + return 0; +} + +//---------------------------------------------------------------------- +// Sub-function: Request to login-server to reload GM configuration file +// this function have no answer +//---------------------------------------------------------------------- +int reloadGM() { + WFIFOW(login_fd,0) = 0x7955; + WFIFOSET(login_fd,2); + bytes_to_read = 0; + + if (defaultlanguage == 'F') { + ladmin_log("Demande de recharger le fichier de configuration des GM envoy馥." RETCODE); + printf("Demande de recharger le fichier de configuration des GM envoy馥.\n"); + printf("V駻ifiez les comptes GM actuels (apr鑚 rechargement):\n"); + } else { + ladmin_log("Request to reload the GM configuration file sended." RETCODE); + printf("Request to reload the GM configuration file sended.\n"); + printf("Check the actual GM accounts (after reloading):\n"); + } + listaccount(parameters, 1); // 1: to list only GM + + return 180; +} + +//----------------------------------------------------- +// Sub-function: Asking to modify the sex of an account +//----------------------------------------------------- +int changesex(char* param) { + char name[1023], sex[1023]; + + memset(name, '\0', sizeof(name)); + memset(sex, '\0', sizeof(sex)); + + if (sscanf(param, "\"%[^\"]\" %[^\r\n]", name, sex) < 2 && + sscanf(param, "'%[^']' %[^\r\n]", name, sex) < 2 && + sscanf(param, "%s %[^\r\n]", name, sex) < 2) { + if (defaultlanguage == 'F') { + printf("Entrez un nom de compte et un sexe svp.\n"); + printf("<exemple> sex nomtest Male\n"); + ladmin_log("Nombre incorrect de param鑼res pour changer le sexe d'un compte (commande 'sex')." RETCODE); + } else { + printf("Please input an account name and a sex.\n"); + printf("<example> sex testname Male\n"); + ladmin_log("Incomplete parameters to change the sex of an account ('sex' command)." RETCODE); + } + return 136; + } + + if (verify_accountname(name) == 0) { + return 102; + } + + sex[0] = toupper(sex[0]); + if (strchr("MF", sex[0]) == NULL) { + if (defaultlanguage == 'F') { + printf("Sexe incorrect [%s]. Entrez M ou F svp.\n", sex); + ladmin_log("Sexe incorrect [%s]. Entrez M ou F svp." RETCODE, sex); + } else { + printf("Illegal gender [%s]. Please input M or F.\n", sex); + ladmin_log("Illegal gender [%s]. Please input M or F." RETCODE, sex); + } + return 103; + } + + if (defaultlanguage == 'F') { + ladmin_log("Envoi d'un requ黎e au serveur de logins pour changer un sexe." RETCODE); + } else { + ladmin_log("Request to login-server to change a sex." RETCODE); + } + + WFIFOW(login_fd,0) = 0x793c; + memcpy(WFIFOP(login_fd,2), name, 24); + WFIFOB(login_fd,26) = sex[0]; + WFIFOSET(login_fd,27); + bytes_to_read = 1; + + return 0; +} + +//------------------------------------------------------------------------- +// Sub-function of sub-function changestate, blockaccount or unblockaccount +// Asking to modify the state of an account +//------------------------------------------------------------------------- +int changestatesub(char* name, int state, char* error_message7) { + char error_message[1023]; // need to use, because we can modify error_message7 + + memset(error_message, '\0', sizeof(error_message)); + strncpy(error_message, error_message7, sizeof(error_message)-1); + + if ((state < 0 || state > 9) && state != 100) { // Valid values: 0: ok, or value of the 0x006a packet + 1 + if (defaultlanguage == 'F') { + printf("Entrez une des statuts suivantes svp:\n"); + printf(" 0 = Compte ok 6 = Your Game's EXE file is not the latest version\n"); + } else { + printf("Please input one of these states:\n"); + printf(" 0 = Account ok 6 = Your Game's EXE file is not the latest version\n"); + } + printf(" 1 = Unregistered ID 7 = You are Prohibited to log in until + message\n"); + printf(" 2 = Incorrect Password 8 = Server is jammed due to over populated\n"); + printf(" 3 = This ID is expired 9 = No MSG\n"); + printf(" 4 = Rejected from Server 100 = This ID has been totally erased\n"); + printf(" 5 = You have been blocked by the GM Team\n"); + if (defaultlanguage == 'F') { + printf("<exemples> state nomtest 5\n"); + printf(" state nomtest 7 fin de votre ban\n"); + printf(" block <nom compte>\n"); + printf(" unblock <nom compte>\n"); + ladmin_log("Valeur incorrecte pour le statut d'un compte (commande 'state', 'block' ou 'unblock')." RETCODE); + } else { + printf("<examples> state testname 5\n"); + printf(" state testname 7 end of your ban\n"); + printf(" block <account name>\n"); + printf(" unblock <account name>\n"); + ladmin_log("Invalid value for the state of an account ('state', 'block' or 'unblock' command)." RETCODE); + } + return 151; + } + + if (verify_accountname(name) == 0) { + return 102; + } + + if (state != 7) { + strcpy(error_message, "-"); + } else { + if (strlen(error_message) < 1) { + if (defaultlanguage == 'F') { + printf("Message d'erreur trop court. Entrez un message de 1-19 caract鑽es.\n"); + ladmin_log("Message d'erreur trop court. Entrez un message de 1-19 caract鑽es." RETCODE); + } else { + printf("Error message is too short. Please input a message of 1-19 bytes.\n"); + ladmin_log("Error message is too short. Please input a message of 1-19 bytes." RETCODE); + } + return 102; + } + if (strlen(error_message) > 19) { + if (defaultlanguage == 'F') { + printf("Message d'erreur trop long. Entrez un message de 1-19 caract鑽es.\n"); + ladmin_log("Message d'erreur trop long. Entrez un message de 1-19 caract鑽es." RETCODE); + } else { + printf("Error message is too long. Please input a message of 1-19 bytes.\n"); + ladmin_log("Error message is too long. Please input a message of 1-19 bytes." RETCODE); + } + return 102; + } + } + + if (defaultlanguage == 'F') { + ladmin_log("Envoi d'un requ黎e au serveur de logins pour changer un statut." RETCODE); + } else { + ladmin_log("Request to login-server to change a state." RETCODE); + } + + WFIFOW(login_fd,0) = 0x7936; + memcpy(WFIFOP(login_fd,2), name, 24); + WFIFOL(login_fd,26) = state; + memcpy(WFIFOP(login_fd,30), error_message, 20); + WFIFOSET(login_fd,50); + bytes_to_read = 1; + + return 0; +} + +//------------------------------------------------------- +// Sub-function: Asking to modify the state of an account +//------------------------------------------------------- +int changestate(char* param) { + char name[1023], error_message[1023]; + int state; + + memset(name, '\0', sizeof(name)); + memset(error_message, '\0', sizeof(error_message)); + + if (sscanf(param, "\"%[^\"]\" %d %[^\r\n]", name, &state, error_message) < 2 && + sscanf(param, "'%[^']' %d %[^\r\n]", name, &state, error_message) < 2 && + sscanf(param, "%s %d %[^\r\n]", name, &state, error_message) < 2) { + if (defaultlanguage == 'F') { + printf("Entrez un nom de compte et un statut svp.\n"); + printf("<exemples> state nomtest 5\n"); + printf(" state nomtest 7 fin de votre ban\n"); + printf(" block <nom compte>\n"); + printf(" unblock <nom compte>\n"); + ladmin_log("Nombre incorrect de param鑼res pour changer le statut d'un compte (commande 'state')." RETCODE); + } else { + printf("Please input an account name and a state.\n"); + printf("<examples> state testname 5\n"); + printf(" state testname 7 end of your ban\n"); + printf(" block <account name>\n"); + printf(" unblock <account name>\n"); + ladmin_log("Incomplete parameters to change the state of an account ('state' command)." RETCODE); + } + return 136; + } + + return changestatesub(name, state, error_message); +} + +//------------------------------------------- +// Sub-function: Asking to unblock an account +//------------------------------------------- +int unblockaccount(char* param) { + char name[1023]; + + memset(name, '\0', sizeof(name)); + + if (strlen(param) == 0 || + (sscanf(param, "\"%[^\"]\"", name) < 1 && + sscanf(param, "'%[^']'", name) < 1 && + sscanf(param, "%[^\r\n]", name) < 1) || + strlen(name) == 0) { + if (defaultlanguage == 'F') { + printf("Entrez un nom de compte svp.\n"); + printf("<exemples> state nomtest 5\n"); + printf(" state nomtest 7 fin de votre ban\n"); + printf(" block <nom compte>\n"); + printf(" unblock <nom compte>\n"); + ladmin_log("Nombre incorrect de param鑼res pour changer le statut d'un compte (commande 'unblock')." RETCODE); + } else { + printf("Please input an account name.\n"); + printf("<examples> state testname 5\n"); + printf(" state testname 7 end of your ban\n"); + printf(" block <account name>\n"); + printf(" unblock <account name>\n"); + ladmin_log("Incomplete parameters to change the state of an account ('unblock' command)." RETCODE); + } + return 136; + } + + return changestatesub(name, 0, "-"); // state 0, no error message +} + +//------------------------------------------- +// Sub-function: Asking to unblock an account +//------------------------------------------- +int blockaccount(char* param) { + char name[1023]; + + memset(name, '\0', sizeof(name)); + + if (strlen(param) == 0 || + (sscanf(param, "\"%[^\"]\"", name) < 1 && + sscanf(param, "'%[^']'", name) < 1 && + sscanf(param, "%[^\r\n]", name) < 1) || + strlen(name) == 0) { + if (defaultlanguage == 'F') { + printf("Entrez un nom de compte svp.\n"); + printf("<exemples> state nomtest 5\n"); + printf(" state nomtest 7 fin de votre ban\n"); + printf(" block <nom compte>\n"); + printf(" unblock <nom compte>\n"); + ladmin_log("Nombre incorrect de param鑼res pour changer le statut d'un compte (commande 'block')." RETCODE); + } else { + printf("Please input an account name.\n"); + printf("<examples> state testname 5\n"); + printf(" state testname 7 end of your ban\n"); + printf(" block <account name>\n"); + printf(" unblock <account name>\n"); + ladmin_log("Incomplete parameters to change the state of an account ('block' command)." RETCODE); + } + return 136; + } + + return changestatesub(name, 5, "-"); // state 5, no error message +} + +//--------------------------------------------------------------------- +// Sub-function: Add/substract time to the validity limit of an account +//--------------------------------------------------------------------- +int timeaddaccount(char* param) { + char name[1023], modif[1023]; + int year, month, day, hour, minute, second; + char * p_modif; + int value, i; + + memset(name, '\0', sizeof(name)); + memset(modif, '\0', sizeof(modif)); + year = month = day = hour = minute = second = 0; + + if (sscanf(param, "\"%[^\"]\" %[^\r\n]", name, modif) < 2 && + sscanf(param, "'%[^']' %[^\r\n]", name, modif) < 2 && + sscanf(param, "%s %[^\r\n]", name, modif) < 2) { + if (defaultlanguage == 'F') { + printf("Entrez un nom de compte et un modificateur svp.\n"); + printf(" <exemple> timeadd nomtest +1m-2mn1s-6y\n"); + printf(" Cette exemple ajoute 1 mois et 1 seconde, et soustrait 2 minutes\n"); + printf(" et 6 ans dans le m麥e temps.\n"); + ladmin_log("Nombre incorrect de param鑼res pour modifier une date limite d'utilisation (commande 'timeadd')." RETCODE); + } else { + printf("Please input an account name and a modifier.\n"); + printf(" <example>: timeadd testname +1m-2mn1s-6y\n"); + printf(" this example adds 1 month and 1 second, and substracts 2 minutes\n"); + printf(" and 6 years at the same time.\n"); + ladmin_log("Incomplete parameters to modify a limit time ('timeadd' command)." RETCODE); + } + return 136; + } + if (verify_accountname(name) == 0) { + return 102; + } + + // lowercase for modif + for (i = 0; modif[i]; i++) + modif[i] = tolower(modif[i]); + p_modif = modif; + while (strlen(p_modif) > 0) { + value = atoi(p_modif); + if (value == 0) { + p_modif++; + } else { + if (p_modif[0] == '-' || p_modif[0] == '+') + p_modif++; + while (strlen(p_modif) > 0 && p_modif[0] >= '0' && p_modif[0] <= '9') { + p_modif++; + } + if (p_modif[0] == 's') { + second = value; + p_modif++; + } else if (p_modif[0] == 'm' && p_modif[1] == 'n') { + minute = value; + p_modif += 2; + } else if (p_modif[0] == 'h') { + hour = value; + p_modif++; + } else if (p_modif[0] == 'd' || p_modif[0] == 'j') { + day = value; + p_modif += 2; + } else if (p_modif[0] == 'm') { + month = value; + p_modif++; + } else if (p_modif[0] == 'y' || p_modif[0] == 'a') { + year = value; + p_modif++; + } else { + p_modif++; + } + } + } + + if (defaultlanguage == 'F') { + printf(" ann馥: %d\n", year); + printf(" mois: %d\n", month); + printf(" jour: %d\n", day); + printf(" heure: %d\n", hour); + printf(" minute: %d\n", minute); + printf(" seconde: %d\n", second); + } else { + printf(" year: %d\n", year); + printf(" month: %d\n", month); + printf(" day: %d\n", day); + printf(" hour: %d\n", hour); + printf(" minute: %d\n", minute); + printf(" second: %d\n", second); + } + + if (year == 0 && month == 0 && day == 0 && hour == 0 && minute == 0 && second == 0) { + if (defaultlanguage == 'F') { + printf("Vous devez entrer un ajustement avec cette commande, svp:\n"); + printf(" Valeur d'ajustement (-1, 1, +1, etc...)\n"); + printf(" El駑ent modifi:\n"); + printf(" a ou y: ann馥\n"); + printf(" m: mois\n"); + printf(" j ou d: jour\n"); + printf(" h: heure\n"); + printf(" mn: minute\n"); + printf(" s: seconde\n"); + printf(" <exemple> timeadd nomtest +1m-2mn1s-6y\n"); + printf(" Cette exemple ajoute 1 mois et 1 seconde, et soustrait 2 minutes\n"); + printf(" et 6 ans dans le m麥e temps.\n"); + ladmin_log("Aucun ajustement n'est pas un ajustement (commande 'timeadd')." RETCODE); + } else { + printf("Please give an adjustment with this command:\n"); + printf(" Adjustment value (-1, 1, +1, etc...)\n"); + printf(" Modified element:\n"); + printf(" a or y: year\n"); + printf(" m: month\n"); + printf(" j or d: day\n"); + printf(" h: hour\n"); + printf(" mn: minute\n"); + printf(" s: second\n"); + printf(" <example> timeadd testname +1m-2mn1s-6y\n"); + printf(" this example adds 1 month and 1 second, and substracts 2 minutes\n"); + printf(" and 6 years at the same time.\n"); + ladmin_log("No adjustment isn't an adjustment ('timeadd' command)." RETCODE); + } + return 137; + } + if (year > 127 || year < -127) { + if (defaultlanguage == 'F') { + printf("Entrez un ajustement d'ann馥s correct (de -127 127), svp.\n"); + ladmin_log("Ajustement de l'ann馥 hors norme ('timeadd' command)." RETCODE); + } else { + printf("Please give a correct adjustment for the years (from -127 to 127).\n"); + ladmin_log("Abnormal adjustement for the year ('timeadd' command)." RETCODE); + } + return 137; + } + if (month > 255 || month < -255) { + if (defaultlanguage == 'F') { + printf("Entrez un ajustement de mois correct (de -255 255), svp.\n"); + ladmin_log("Ajustement du mois hors norme ('timeadd' command)." RETCODE); + } else { + printf("Please give a correct adjustment for the months (from -255 to 255).\n"); + ladmin_log("Abnormal adjustement for the month ('timeadd' command)." RETCODE); + } + return 137; + } + if (day > 32767 || day < -32767) { + if (defaultlanguage == 'F') { + printf("Entrez un ajustement de jours correct (de -32767 32767), svp.\n"); + ladmin_log("Ajustement des jours hors norme ('timeadd' command)." RETCODE); + } else { + printf("Please give a correct adjustment for the days (from -32767 to 32767).\n"); + ladmin_log("Abnormal adjustement for the days ('timeadd' command)." RETCODE); + } + return 137; + } + if (hour > 32767 || hour < -32767) { + if (defaultlanguage == 'F') { + printf("Entrez un ajustement d'heures correct (de -32767 32767), svp.\n"); + ladmin_log("Ajustement des heures hors norme ('timeadd' command)." RETCODE); + } else { + printf("Please give a correct adjustment for the hours (from -32767 to 32767).\n"); + ladmin_log("Abnormal adjustement for the hours ('timeadd' command)." RETCODE); + } + return 137; + } + if (minute > 32767 || minute < -32767) { + if (defaultlanguage == 'F') { + printf("Entrez un ajustement de minutes correct (de -32767 32767), svp.\n"); + ladmin_log("Ajustement des minutes hors norme ('timeadd' command)." RETCODE); + } else { + printf("Please give a correct adjustment for the minutes (from -32767 to 32767).\n"); + ladmin_log("Abnormal adjustement for the minutes ('timeadd' command)." RETCODE); + } + return 137; + } + if (second > 32767 || second < -32767) { + if (defaultlanguage == 'F') { + printf("Entrez un ajustement de secondes correct (de -32767 32767), svp.\n"); + ladmin_log("Ajustement des secondes hors norme ('timeadd' command)." RETCODE); + } else { + printf("Please give a correct adjustment for the seconds (from -32767 to 32767).\n"); + ladmin_log("Abnormal adjustement for the seconds ('timeadd' command)." RETCODE); + } + return 137; + } + + if (defaultlanguage == 'F') { + ladmin_log("Envoi d'un requ黎e au serveur de logins pour modifier une date limite d'utilisation." RETCODE); + } else { + ladmin_log("Request to login-server to modify a time limit." RETCODE); + } + + WFIFOW(login_fd,0) = 0x7950; + memcpy(WFIFOP(login_fd,2), name, 24); + WFIFOW(login_fd,26) = (short)year; + WFIFOW(login_fd,28) = (short)month; + WFIFOW(login_fd,30) = (short)day; + WFIFOW(login_fd,32) = (short)hour; + WFIFOW(login_fd,34) = (short)minute; + WFIFOW(login_fd,36) = (short)second; + WFIFOSET(login_fd,38); + bytes_to_read = 1; + + return 0; +} + +//------------------------------------------------- +// Sub-function: Set a validity limit of an account +//------------------------------------------------- +int timesetaccount(char* param) { + char name[1023], date[1023], time[1023]; + int year, month, day, hour, minute, second; + time_t connect_until_time; // # of seconds 1/1/1970 (timestamp): Validity limit of the account (0 = unlimited) + struct tm *tmtime; + + memset(name, '\0', sizeof(name)); + memset(date, '\0', sizeof(date)); + memset(time, '\0', sizeof(time)); + year = month = day = hour = minute = second = 0; + connect_until_time = 0; + tmtime = localtime(&connect_until_time); // initialize + + if (sscanf(param, "\"%[^\"]\" %s %[^\r\n]", name, date, time) < 2 && // if date = 0, time can be void + sscanf(param, "'%[^']' %s %[^\r\n]", name, date, time) < 2 && // if date = 0, time can be void + sscanf(param, "%s %s %[^\r\n]", name, date, time) < 2) { // if date = 0, time can be void + if (defaultlanguage == 'F') { + printf("Entrez un nom de compte, une date et une heure svp.\n"); + printf("<exemple>: timeset <nom_du_compte> aaaa/mm/jj [hh:mm:ss]\n"); + printf(" timeset <nom_du_compte> 0 (0 = illimit)\n"); + printf(" Heure par d馭aut [hh:mm:ss]: 23:59:59.\n"); + ladmin_log("Nombre incorrect de param鑼res pour fixer une date limite d'utilisation (commande 'timeset')." RETCODE); + } else { + printf("Please input an account name, a date and a hour.\n"); + printf("<example>: timeset <account_name> yyyy/mm/dd [hh:mm:ss]\n"); + printf(" timeset <account_name> 0 (0 = unlimited)\n"); + printf(" Default time [hh:mm:ss]: 23:59:59.\n"); + ladmin_log("Incomplete parameters to set a limit time ('timeset' command)." RETCODE); + } + return 136; + } + if (verify_accountname(name) == 0) { + return 102; + } + + if (time[0] == '\0') + strcpy(time, "23:59:59"); + + if (atoi(date) != 0 && + ((sscanf(date, "%d/%d/%d", &year, &month, &day) < 3 && + sscanf(date, "%d-%d-%d", &year, &month, &day) < 3 && + sscanf(date, "%d.%d.%d", &year, &month, &day) < 3 && + sscanf(date, "%d'%d'%d", &year, &month, &day) < 3) || + sscanf(time, "%d:%d:%d", &hour, &minute, &second) < 3)) { + if (defaultlanguage == 'F') { + printf("Entrez 0 ou une date et une heure svp (format: 0 ou aaaa/mm/jj hh:mm:ss).\n"); + ladmin_log("Format incorrect pour la date/heure ('timeset' command)." RETCODE); + } else { + printf("Please input 0 or a date and a time (format: 0 or yyyy/mm/dd hh:mm:ss).\n"); + ladmin_log("Invalid format for the date/time ('timeset' command)." RETCODE); + } + return 102; + } + + if (atoi(date) == 0) { + connect_until_time = 0; + } else { + if (year < 70) { + year = year + 100; + } + if (year >= 1900) { + year = year - 1900; + } + if (month < 1 || month > 12) { + if (defaultlanguage == 'F') { + printf("Entrez un mois correct svp (entre 1 et 12).\n"); + ladmin_log("Mois incorrect pour la date ('timeset' command)." RETCODE); + } else { + printf("Please give a correct value for the month (from 1 to 12).\n"); + ladmin_log("Invalid month for the date ('timeset' command)." RETCODE); + } + return 102; + } + month = month - 1; + if (day < 1 || day > 31) { + if (defaultlanguage == 'F') { + printf("Entrez un jour correct svp (entre 1 et 31).\n"); + ladmin_log("Jour incorrect pour la date ('timeset' command)." RETCODE); + } else { + printf("Please give a correct value for the day (from 1 to 31).\n"); + ladmin_log("Invalid day for the date ('timeset' command)." RETCODE); + } + return 102; + } + if (((month == 3 || month == 5 || month == 8 || month == 10) && day > 30) || + (month == 1 && day > 29)) { + if (defaultlanguage == 'F') { + printf("Entrez un jour correct en fonction du mois (%d) svp.\n", month); + ladmin_log("Jour incorrect pour ce mois correspondant ('timeset' command)." RETCODE); + } else { + printf("Please give a correct value for a day of this month (%d).\n", month); + ladmin_log("Invalid day for this month ('timeset' command)." RETCODE); + } + return 102; + } + if (hour < 0 || hour > 23) { + if (defaultlanguage == 'F') { + printf("Entrez une heure correcte svp (entre 0 et 23).\n"); + ladmin_log("Heure incorrecte pour l'heure ('timeset' command)." RETCODE); + } else { + printf("Please give a correct value for the hour (from 0 to 23).\n"); + ladmin_log("Invalid hour for the time ('timeset' command)." RETCODE); + } + return 102; + } + if (minute < 0 || minute > 59) { + if (defaultlanguage == 'F') { + printf("Entrez des minutes correctes svp (entre 0 et 59).\n"); + ladmin_log("Minute incorrecte pour l'heure ('timeset' command)." RETCODE); + } else { + printf("Please give a correct value for the minutes (from 0 to 59).\n"); + ladmin_log("Invalid minute for the time ('timeset' command)." RETCODE); + } + return 102; + } + if (second < 0 || second > 59) { + if (defaultlanguage == 'F') { + printf("Entrez des secondes correctes svp (entre 0 et 59).\n"); + ladmin_log("Seconde incorrecte pour l'heure ('timeset' command)." RETCODE); + } else { + printf("Please give a correct value for the seconds (from 0 to 59).\n"); + ladmin_log("Invalid second for the time ('timeset' command)." RETCODE); + } + return 102; + } + tmtime->tm_year = year; + tmtime->tm_mon = month; + tmtime->tm_mday = day; + tmtime->tm_hour = hour; + tmtime->tm_min = minute; + tmtime->tm_sec = second; + tmtime->tm_isdst = -1; // -1: no winter/summer time modification + connect_until_time = mktime(tmtime); + if (connect_until_time == -1) { + if (defaultlanguage == 'F') { + printf("Date incorrecte.\n"); + printf("Ajoutez 0 ou une date et une heure svp (format: 0 ou aaaa/mm/jj hh:mm:ss).\n"); + ladmin_log("Date incorrecte. ('timeset' command)." RETCODE); + } else { + printf("Invalid date.\n"); + printf("Please add 0 or a date and a time (format: 0 or yyyy/mm/dd hh:mm:ss).\n"); + ladmin_log("Invalid date. ('timeset' command)." RETCODE); + } + return 102; + } + } + + if (defaultlanguage == 'F') { + ladmin_log("Envoi d'un requ黎e au serveur de logins pour fixer une date limite d'utilisation." RETCODE); + } else { + ladmin_log("Request to login-server to set a time limit." RETCODE); + } + + WFIFOW(login_fd,0) = 0x7948; + memcpy(WFIFOP(login_fd,2), name, 24); + WFIFOL(login_fd,26) = (int)connect_until_time; + WFIFOSET(login_fd,30); + bytes_to_read = 1; + + return 0; +} + +//------------------------------------------------------------------------------ +// Sub-function: Asking to displaying information about an account (by its name) +//------------------------------------------------------------------------------ +int whoaccount(char* param) { + char name[1023]; + + memset(name, '\0', sizeof(name)); + + if (strlen(param) == 0 || + (sscanf(param, "\"%[^\"]\"", name) < 1 && + sscanf(param, "'%[^']'", name) < 1 && + sscanf(param, "%[^\r\n]", name) < 1) || + strlen(name) == 0) { + if (defaultlanguage == 'F') { + printf("Entrez un nom de compte svp.\n"); + printf("<exemple> who nomtest\n"); + ladmin_log("Aucun nom n'a 騁 donn pour trouver le compte." RETCODE); + } else { + printf("Please input an account name.\n"); + printf("<example> who testname\n"); + ladmin_log("No name was given to found the account." RETCODE); + } + return 136; + } + if (verify_accountname(name) == 0) { + return 102; + } + + if (defaultlanguage == 'F') { + ladmin_log("Envoi d'un requ黎e au serveur de logins pour obtenir le information d'un compte (par le nom)." RETCODE); + } else { + ladmin_log("Request to login-server to obtain information about an account (by its name)." RETCODE); + } + + WFIFOW(login_fd,0) = 0x7952; + memcpy(WFIFOP(login_fd,2), name, 24); + WFIFOSET(login_fd,26); + bytes_to_read = 1; + + return 0; +} + +//-------------------------------------------------------- +// Sub-function: Asking of the version of the login-server +//-------------------------------------------------------- +int checkloginversion() { + if (defaultlanguage == 'F') + ladmin_log("Envoi d'un requ黎e au serveur de logins pour obtenir sa version." RETCODE); + else + ladmin_log("Request to login-server to obtain its version." RETCODE); + + WFIFOW(login_fd,0) = 0x7530; + WFIFOSET(login_fd,2); + bytes_to_read = 1; + + return 0; +} + +//--------------------------------------------- +// Prompt function +// this function wait until user type a command +// and analyse the command. +//--------------------------------------------- +int prompt() { + int i, j; + char buf[1024]; + char *p; + + // while we don't wait new packets + while (bytes_to_read == 0) { + // for help with the console colors look here: + // http://www.edoceo.com/liberum/?doc=printf-with-color + // some code explanation (used here): + // \033[2J : clear screen and go up/left (0, 0 position) + // \033[K : clear line from actual position to end of the line + // \033[0m : reset color parameter + // \033[1m : use bold for font + printf("\n"); + if (defaultlanguage == 'F') + printf("\033[32mPour afficher les commandes, tapez 'Entr馥'.\033[0m\n"); + else + printf("\033[32mTo list the commands, type 'enter'.\033[0m\n"); + printf("\033[0;36mLadmin-> \033[0m"); + printf("\033[1m"); + fflush(stdout); + + // get command and parameter + memset(buf, '\0', sizeof(buf)); + fflush(stdin); + fgets(buf, 1023, stdin); + buf[1023] = '\0'; + + printf("\033[0m"); + fflush(stdout); + + // remove final \n + if((p = strrchr(buf, '\n')) != NULL) + p[0] = '\0'; + // remove all control char + for (i = 0; buf[i]; i++) + if (buf[i] < 32) { + // remove cursor control. + if (buf[i] == 27 && buf[i+1] == '[' && + (buf[i+2] == 'H' || // home position (cursor) + buf[i+2] == 'J' || // clear screen + buf[i+2] == 'A' || // up 1 line + buf[i+2] == 'B' || // down 1 line + buf[i+2] == 'C' || // right 1 position + buf[i+2] == 'D' || // left 1 position + buf[i+2] == 'G')) { // center cursor (windows) + for (j = i; buf[j]; j++) + buf[j] = buf[j+3]; + } else if (buf[i] == 27 && buf[i+1] == '[' && buf[i+2] == '2' && buf[i+3] == 'J') { // clear screen + for (j = i; buf[j]; j++) + buf[j] = buf[j+4]; + } else if (buf[i] == 27 && buf[i+1] == '[' && buf[i+3] == '~' && + (buf[i+2] == '1' || // home (windows) + buf[i+2] == '2' || // insert (windows) + buf[i+2] == '3' || // del (windows) + buf[i+2] == '4' || // end (windows) + buf[i+2] == '5' || // pgup (windows) + buf[i+2] == '6')) { // pgdown (windows) + for (j = i; buf[j]; j++) + buf[j] = buf[j+4]; + } else { + // remove other control char. + for (j = i; buf[j]; j++) + buf[j] = buf[j+1]; + } + i--; + } + + // extract command name and parameters + memset(command, '\0', sizeof(command)); + memset(parameters, '\0', sizeof(parameters)); + sscanf(buf, "%1023s %[^\n]", command, parameters); + command[1023] = '\0'; + parameters[1023] = '\0'; + + // lowercase for command line + for (i = 0; command[i]; i++) + command[i] = tolower(command[i]); + + if (command[0] == '?' || strlen(command) == 0) { + if (defaultlanguage == 'F') { + strcpy(buf, "aide"); + strcpy(command, "aide"); + } else { + strcpy(buf, "help"); + strcpy(command, "help"); + } + } + + // Analyse of the command + check_command(command); // give complete name to the command + + if (strlen(parameters) == 0) { + if (defaultlanguage == 'F') { + ladmin_log("Commande: '%s' (sans param鑼re)" RETCODE, command, parameters); + } else { + ladmin_log("Command: '%s' (without parameters)" RETCODE, command, parameters); + } + } else { + if (defaultlanguage == 'F') { + ladmin_log("Commande: '%s', param鑼res: '%s'" RETCODE, command, parameters); + } else { + ladmin_log("Command: '%s', parameters: '%s'" RETCODE, command, parameters); + } + } + + // Analyse of the command +// help + if (strcmp(command, "aide") == 0) { + display_help(parameters, 1); // 1: french + } else if (strcmp(command, "help") == 0 ) { + display_help(parameters, 0); // 0: english +// general commands + } else if (strcmp(command, "add") == 0) { + addaccount(parameters, 0); // 0: no email + } else if (strcmp(command, "ban") == 0) { + banaccount(parameters); + } else if (strcmp(command, "banadd") == 0) { + banaddaccount(parameters); + } else if (strcmp(command, "banset") == 0) { + bansetaccount(parameters); + } else if (strcmp(command, "block") == 0) { + blockaccount(parameters); + } else if (strcmp(command, "check") == 0) { + checkaccount(parameters); + } else if (strcmp(command, "create") == 0) { + addaccount(parameters, 1); // 1: with email + } else if (strcmp(command, "delete") == 0) { + delaccount(parameters); + } else if (strcmp(command, "email") == 0) { + changeemail(parameters); + } else if (strcmp(command, "getcount") == 0) { + getlogincount(); + } else if (strcmp(command, "gm") == 0) { + changegmlevel(parameters); + } else if (strcmp(command, "id") == 0) { + idaccount(parameters); + } else if (strcmp(command, "info") == 0) { + infoaccount(atoi(parameters)); + } else if (strcmp(command, "kami") == 0) { + sendbroadcast(0, parameters); // flag for normal + } else if (strcmp(command, "kamib") == 0) { + sendbroadcast(0x10, parameters); // flag for blue + } else if (strcmp(command, "language") == 0) { + changelanguage(parameters); + } else if (strcmp(command, "list") == 0) { + listaccount(parameters, 0); // 0: to list all + } else if (strcmp(command, "listban") == 0) { + listaccount(parameters, 3); // 3: to list only accounts with state or bannished + } else if (strcmp(command, "listgm") == 0) { + listaccount(parameters, 1); // 1: to list only GM + } else if (strcmp(command, "listok") == 0) { + listaccount(parameters, 4); // 4: to list only accounts without state and not bannished + } else if (strcmp(command, "memo") == 0) { + changememo(parameters); + } else if (strcmp(command, "name") == 0) { + nameaccount(atoi(parameters)); + } else if (strcmp(command, "password") == 0) { + changepasswd(parameters); + } else if (strcmp(command, "reloadgm") == 0) { + reloadGM(); + } else if (strcmp(command, "search") == 0) { // no regex in C version + listaccount(parameters, 2); // 2: to list with pattern + } else if (strcmp(command, "sex") == 0) { + changesex(parameters); + } else if (strcmp(command, "state") == 0) { + changestate(parameters); + } else if (strcmp(command, "timeadd") == 0) { + timeaddaccount(parameters); + } else if (strcmp(command, "timeset") == 0) { + timesetaccount(parameters); + } else if (strcmp(command, "unban") == 0) { + unbanaccount(parameters); + } else if (strcmp(command, "unblock") == 0) { + unblockaccount(parameters); + } else if (strcmp(command, "version") == 0) { + checkloginversion(); + } else if (strcmp(command, "who") == 0) { + whoaccount(parameters); +// quit + } else if (strcmp(command, "quit") == 0 || + strcmp(command, "exit") == 0 || + strcmp(command, "end") == 0) { + if (defaultlanguage == 'F') { + printf("Au revoir.\n"); + } else { + printf("Bye.\n"); + } + exit(0); +// unknown command + } else { + if (defaultlanguage == 'F') { + printf("Commande inconnue [%s].\n", buf); + ladmin_log("Commande inconnue [%s]." RETCODE, buf); + } else { + printf("Unknown command [%s].\n", buf); + ladmin_log("Unknown command [%s]." RETCODE, buf); + } + } + } + + return 0; +} + +//------------------------------------------------------------- +// Function: Parse receiving informations from the login-server +//------------------------------------------------------------- +int parse_fromlogin(int fd) { + struct char_session_data *sd; + + if (session[fd]->eof) { + if (defaultlanguage == 'F') { + printf("Impossible de se connecter au serveur de login [%s:%d] !\n", loginserverip, loginserverport); + ladmin_log("Impossible de se connecter au serveur de login [%s:%d] !" RETCODE, loginserverip, loginserverport); + } else { + printf("Impossible to have a connection with the login-server [%s:%d] !\n", loginserverip, loginserverport); + ladmin_log("Impossible to have a connection with the login-server [%s:%d] !" RETCODE, loginserverip, loginserverport); + } + close(fd); + delete_session(fd); + exit (0); + } + +// printf("parse_fromlogin : %d %d %d\n", fd, RFIFOREST(fd), RFIFOW(fd,0)); + sd = session[fd]->session_data; + + while(RFIFOREST(fd) >= 2) { + switch(RFIFOW(fd,0)) { + case 0x7919: // answer of a connection request + if (RFIFOREST(fd) < 3) + return 0; + if (RFIFOB(fd,2) != 0) { + if (defaultlanguage == 'F') { + printf("Erreur de login:\n"); + printf(" - mot de passe incorrect,\n"); + printf(" - syst鑪e d'administration non activ, ou\n"); + printf(" - IP non autoris馥.\n"); + ladmin_log("Erreur de login: mot de passe incorrect, syst鑪e d'administration non activ, ou IP non autoris馥." RETCODE); + } else { + printf("Error at login:\n"); + printf(" - incorrect password,\n"); + printf(" - administration system not activated, or\n"); + printf(" - unauthorised IP.\n"); + ladmin_log("Error at login: incorrect password, administration system not activated, or unauthorised IP." RETCODE); + } + session[fd]->eof = 1; + //bytes_to_read = 1; // not stop at prompt + } else { + if (defaultlanguage == 'F') { + printf("Connexion 騁ablie.\n"); + ladmin_log("Connexion 騁ablie." RETCODE); + printf("Lecture de la version du serveur de login...\n"); + ladmin_log("Lecture de la version du serveur de login..." RETCODE); + } else { + printf("Established connection.\n"); + ladmin_log("Established connection." RETCODE); + printf("Reading of the version of the login-server...\n"); + ladmin_log("Reading of the version of the login-server..." RETCODE); + } + //bytes_to_read = 1; // unchanged + checkloginversion(); + } + RFIFOSKIP(fd,3); + break; + +#ifdef PASSWORDENC + case 0x01dc: // answer of a coding key request + if (RFIFOREST(fd) < 4 || RFIFOREST(fd) < RFIFOW(fd,2)) + return 0; + { + char md5str[64] = "", md5bin[32], md5key[RFIFOW(fd,2) - 4 + 1]; + memcpy(md5key, RFIFOP(fd,4), RFIFOW(fd,2) - 4); + md5key[sizeof(md5key)-1] = '0'; + if (passenc == 1) { + strncpy(md5str, RFIFOP(fd,4), RFIFOW(fd,2) - 4); + strcat(md5str, loginserveradminpassword); + } else if (passenc == 2) { + strncpy(md5str, loginserveradminpassword, sizeof(loginserveradminpassword)); + strcat(md5str, RFIFOP(fd,4)); + } + MD5_String2binary(md5str, md5bin); + WFIFOW(login_fd,0) = 0x7918; // Request for administation login (encrypted password) + WFIFOW(login_fd,2) = passenc; // Encrypted type + memcpy(WFIFOP(login_fd,4), md5bin, 16); + WFIFOSET(login_fd,20); + if (defaultlanguage == 'F') { + printf("R馗eption de la clef MD5.\n"); + ladmin_log("R馗eption de la clef MD5." RETCODE); + printf("Envoi du mot de passe crypt...\n"); + ladmin_log("Envoi du mot de passe crypt..." RETCODE); + } else { + printf("Receiving of the MD5 key.\n"); + ladmin_log("Receiving of the MD5 key." RETCODE); + printf("Sending of the encrypted password...\n"); + ladmin_log("Sending of the encrypted password..." RETCODE); + } + } + bytes_to_read = 1; + RFIFOSKIP(fd,RFIFOW(fd,2)); + break; +#endif + + case 0x7531: // Displaying of the version of the login-server + if (RFIFOREST(fd) < 10) + return 0; + printf(" Login-Server [%s:%d]\n", loginserverip, loginserverport); + if (((int)RFIFOB(login_fd,5)) == 0) { + printf(" eAthena version stable-%d.%d", (int)RFIFOB(login_fd,2), (int)RFIFOB(login_fd,3)); + } else { + printf(" eAthena version dev-%d.%d", (int)RFIFOB(login_fd,2), (int)RFIFOB(login_fd,3)); + } + if (((int)RFIFOB(login_fd,4)) == 0) + printf(" revision %d", (int)RFIFOB(login_fd,4)); + if (((int)RFIFOB(login_fd,6)) == 0) + printf("%d.\n", RFIFOW(login_fd,8)); + else + printf("-mod%d.\n", RFIFOW(login_fd,8)); + bytes_to_read = 0; + RFIFOSKIP(fd,10); + break; + + case 0x7921: // Displaying of the list of accounts + if (RFIFOREST(fd) < 4 || RFIFOREST(fd) < RFIFOW(fd,2)) + return 0; + if (RFIFOW(fd,2) < 5) { + if (defaultlanguage == 'F') { + ladmin_log(" R馗eption d'une liste des comptes vide." RETCODE); + if (list_count == 0) + printf("Aucun compte trouv.\n"); + else if (list_count == 1) + printf("1 compte trouv.\n"); + else + printf("%d comptes trouv駸.\n", list_count); + } else { + ladmin_log(" Receiving of a void accounts list." RETCODE); + if (list_count == 0) + printf("No account found.\n"); + else if (list_count == 1) + printf("1 account found.\n"); + else + printf("%d accounts found.\n", list_count); + } + bytes_to_read = 0; + } else { + int i; + if (defaultlanguage == 'F') + ladmin_log(" R馗eption d'une liste des comptes." RETCODE); + else + ladmin_log(" Receiving of a accounts list." RETCODE); + for(i = 4; i < RFIFOW(fd,2); i += 38) { + int j; + char userid[24]; + char lower_userid[24]; + memcpy(userid, RFIFOP(fd,i + 5), sizeof(userid)); + userid[sizeof(userid)-1] = '\0'; + memset(lower_userid, '\0', sizeof(lower_userid)); + for (j = 0; userid[j]; j++) + lower_userid[j] = tolower(userid[j]); + list_first = RFIFOL(fd,i) + 1; + // here are checks... + if (list_type == 0 || + (list_type == 1 && RFIFOB(fd,i+4) > 0) || + (list_type == 2 && strstr(lower_userid, parameters) != NULL) || + (list_type == 3 && RFIFOL(fd,i+34) != 0) || + (list_type == 4 && RFIFOL(fd,i+34) == 0)) { + printf("%10d ", RFIFOL(fd,i)); + if (RFIFOB(fd,i+4) == 0) + printf(" "); + else + printf("%2d ", (int)RFIFOB(fd,i+4)); + printf("%-24s", userid); + if (defaultlanguage == 'F') { + if (RFIFOB(fd,i+29) == 0) + printf("%-5s ", "Femme"); + else if (RFIFOB(fd,i+29) == 1) + printf("%-5s ", "Male"); + else + printf("%-5s ", "Servr"); + } else { + if (RFIFOB(fd,i+29) == 0) + printf("%-5s ", "Femal"); + else if (RFIFOB(fd,i+29) == 1) + printf("%-5s ", "Male"); + else + printf("%-5s ", "Servr"); + } + printf("%6d ", RFIFOL(fd,i+30)); + switch(RFIFOL(fd,i+34)) { + case 0: + if (defaultlanguage == 'F') + printf("%-27s\n", "Compte Ok"); + else + printf("%-27s\n", "Account OK"); + break; + case 1: + printf("%-27s\n", "Unregistered ID"); + break; + case 2: + printf("%-27s\n", "Incorrect Password"); + break; + case 3: + printf("%-27s\n", "This ID is expired"); + break; + case 4: + printf("%-27s\n", "Rejected from Server"); + break; + case 5: + printf("%-27s\n", "Blocked by the GM Team"); // You have been blocked by the GM Team + break; + case 6: + printf("%-27s\n", "Your EXE file is too old"); // Your Game's EXE file is not the latest version + break; + case 7: + printf("%-27s\n", "Banishement or"); + printf(" Prohibited to login until...\n"); // You are Prohibited to log in until %s + break; + case 8: + printf("%-27s\n", "Server is over populated"); + break; + case 9: + printf("%-27s\n", "No MSG"); + break; + default: // 100 + printf("%-27s\n", "This ID is totally erased"); // This ID has been totally erased + break; + } + list_count++; + } + } + // asking of the following acounts + if (defaultlanguage == 'F') + ladmin_log("Envoi d'un requ黎e au serveur de logins pour obtenir la liste des comptes de %d %d (compl駑ent)." RETCODE, list_first, list_last); + else + ladmin_log("Request to login-server to obtain the list of accounts from %d to %d (complement)." RETCODE, list_first, list_last); + WFIFOW(login_fd,0) = 0x7920; + WFIFOL(login_fd,2) = list_first; + WFIFOL(login_fd,6) = list_last; + WFIFOSET(login_fd,10); + bytes_to_read = 1; + } + RFIFOSKIP(fd,RFIFOW(fd,2)); + break; + + case 0x7931: // Answer of login-server about an account creation + if (RFIFOREST(fd) < 30) + return 0; + if (RFIFOL(fd,2) == -1) { + if (defaultlanguage == 'F') { + printf("Echec la cr饌tion du compte [%s]. Un compte identique existe d駛.\n", RFIFOP(fd,6)); + ladmin_log("Echec la cr饌tion du compte [%s]. Un compte identique existe d駛." RETCODE, RFIFOP(fd,6)); + } else { + printf("Account [%s] creation failed. Same account already exists.\n", RFIFOP(fd,6)); + ladmin_log("Account [%s] creation failed. Same account already exists." RETCODE, RFIFOP(fd,6)); + } + } else { + if (defaultlanguage == 'F') { + printf("Compte [%s] cr鳬 avec succ鑚 [id: %d].\n", RFIFOP(fd,6), RFIFOL(fd,2)); + ladmin_log("Compte [%s] cr鳬 avec succ鑚 [id: %d]." RETCODE, RFIFOP(fd,6), RFIFOL(fd,2)); + } else { + printf("Account [%s] is successfully created [id: %d].\n", RFIFOP(fd,6), RFIFOL(fd,2)); + ladmin_log("Account [%s] is successfully created [id: %d]." RETCODE, RFIFOP(fd,6), RFIFOL(fd,2)); + } + } + bytes_to_read = 0; + RFIFOSKIP(fd,30); + break; + + case 0x7933: // Answer of login-server about an account deletion + if (RFIFOREST(fd) < 30) + return 0; + if (RFIFOL(fd,2) == -1) { + if (defaultlanguage == 'F') { + printf("Echec de la suppression du compte [%s]. Le compte n'existe pas.\n", RFIFOP(fd,6)); + ladmin_log("Echec de la suppression du compte [%s]. Le compte n'existe pas." RETCODE, RFIFOP(fd,6)); + } else { + printf("Account [%s] deletion failed. Account doesn't exist.\n", RFIFOP(fd,6)); + ladmin_log("Account [%s] deletion failed. Account doesn't exist." RETCODE, RFIFOP(fd,6)); + } + } else { + if (defaultlanguage == 'F') { + printf("Compte [%s][id: %d] SUPPRIME avec succ鑚.\n", RFIFOP(fd,6), RFIFOL(fd,2)); + ladmin_log("Compte [%s][id: %d] SUPPRIME avec succ鑚." RETCODE, RFIFOP(fd,6), RFIFOL(fd,2)); + } else { + printf("Account [%s][id: %d] is successfully DELETED.\n", RFIFOP(fd,6), RFIFOL(fd,2)); + ladmin_log("Account [%s][id: %d] is successfully DELETED." RETCODE, RFIFOP(fd,6), RFIFOL(fd,2)); + } + } + bytes_to_read = 0; + RFIFOSKIP(fd,30); + break; + + case 0x7935: // answer of the change of an account password + if (RFIFOREST(fd) < 30) + return 0; + if (RFIFOL(fd,2) == -1) { + if (defaultlanguage == 'F') { + printf("Echec de la modification du mot de passe du compte [%s].\n", RFIFOP(fd,6)); + printf("Le compte [%s] n'existe pas.\n", RFIFOP(fd,6)); + ladmin_log("Echec de la modification du mot de passe du compte. Le compte [%s] n'existe pas." RETCODE, RFIFOP(fd,6)); + } else { + printf("Account [%s] password changing failed.\n", RFIFOP(fd,6)); + printf("Account [%s] doesn't exist.\n", RFIFOP(fd,6)); + ladmin_log("Account password changing failed. The compte [%s] doesn't exist." RETCODE, RFIFOP(fd,6)); + } + } else { + if (defaultlanguage == 'F') { + printf("Modification du mot de passe du compte [%s][id: %d] r騏ssie.\n", RFIFOP(fd,6), RFIFOL(fd,2)); + ladmin_log("Modification du mot de passe du compte [%s][id: %d] r騏ssie." RETCODE, RFIFOP(fd,6), RFIFOL(fd,2)); + } else { + printf("Account [%s][id: %d] password successfully changed.\n", RFIFOP(fd,6), RFIFOL(fd,2)); + ladmin_log("Account [%s][id: %d] password successfully changed." RETCODE, RFIFOP(fd,6), RFIFOL(fd,2)); + } + } + bytes_to_read = 0; + RFIFOSKIP(fd,30); + break; + + case 0x7937: // answer of the change of an account state + if (RFIFOREST(fd) < 34) + return 0; + if (RFIFOL(fd,2) == -1) { + if (defaultlanguage == 'F') { + printf("Echec du changement du statut du compte [%s]. Le compte n'existe pas.\n", RFIFOP(fd,6)); + ladmin_log("Echec du changement du statut du compte [%s]. Le compte n'existe pas." RETCODE, RFIFOP(fd,6)); + } else { + printf("Account [%s] state changing failed. Account doesn't exist.\n", RFIFOP(fd,6)); + ladmin_log("Account [%s] state changing failed. Account doesn't exist." RETCODE, RFIFOP(fd,6)); + } + } else { + char tmpstr[256]; + if (defaultlanguage == 'F') { + sprintf(tmpstr, "Statut du compte [%s] chang avec succ鑚 en [", RFIFOP(fd,6)); + } else { + sprintf(tmpstr, "Account [%s] state successfully changed in [", RFIFOP(fd,6)); + } + switch(RFIFOL(fd,30)) { + case 0: + if (defaultlanguage == 'F') + strcat(tmpstr, "0: Compte Ok"); + else + strcat(tmpstr, "0: Account OK"); + break; + case 1: + strcat(tmpstr, "1: Unregistered ID"); + break; + case 2: + strcat(tmpstr, "2: Incorrect Password"); + break; + case 3: + strcat(tmpstr, "3: This ID is expired"); + break; + case 4: + strcat(tmpstr, "4: Rejected from Server"); + break; + case 5: + strcat(tmpstr, "5: You have been blocked by the GM Team"); + break; + case 6: + strcat(tmpstr, "6: [Your Game's EXE file is not the latest version"); + break; + case 7: + strcat(tmpstr, "7: You are Prohibited to log in until..."); + break; + case 8: + strcat(tmpstr, "8: Server is jammed due to over populated"); + break; + case 9: + strcat(tmpstr, "9: No MSG"); + break; + default: // 100 + strcat(tmpstr, "100: This ID is totally erased"); + break; + } + strcat(tmpstr, "]"); + printf("%s\n", tmpstr); + ladmin_log("%s%s", tmpstr, RETCODE); + } + bytes_to_read = 0; + RFIFOSKIP(fd,34); + break; + + case 0x7939: // answer of the number of online players + if (RFIFOREST(fd) < 4 || RFIFOREST(fd) < RFIFOW(fd,2)) + return 0; + { + // Get length of the received packet + int i; + char name[20]; + if (defaultlanguage == 'F') { + ladmin_log(" R馗eption du nombre de joueurs en ligne." RETCODE); + } else { + ladmin_log(" Receiving of the number of online players." RETCODE); + } + // Read information of the servers + if (RFIFOW(fd,2) < 5) { + if (defaultlanguage == 'F') { + printf(" Aucun serveur n'est connect au login serveur.\n"); + } else { + printf(" No server is connected to the login-server.\n"); + } + } else { + if (defaultlanguage == 'F') { + printf(" Nombre de joueurs en ligne (serveur: nb):\n"); + } else { + printf(" Number of online players (server: number).\n"); + } + // Displaying of result + for(i = 4; i < RFIFOW(fd,2); i += 32) { + memcpy(name, RFIFOP(fd,i+6), sizeof(name)); + name[sizeof(name) - 1] = '\0'; + printf(" %-20s : %5d\n", name, RFIFOW(fd,i+26)); + } + } + } + bytes_to_read = 0; + RFIFOSKIP(fd,RFIFOW(fd,2)); + break; + + case 0x793b: // answer of the check of a password + if (RFIFOREST(fd) < 30) + return 0; + if (RFIFOL(fd,2) == -1) { + if (defaultlanguage == 'F') { + printf("Le compte [%s] n'existe pas ou le mot de passe est incorrect.\n", RFIFOP(fd,6)); + ladmin_log("Le compte [%s] n'existe pas ou le mot de passe est incorrect." RETCODE, RFIFOP(fd,6)); + } else { + printf("The account [%s] doesn't exist or the password is incorrect.\n", RFIFOP(fd,6)); + ladmin_log("The account [%s] doesn't exist or the password is incorrect." RETCODE, RFIFOP(fd,6)); + } + } else { + if (defaultlanguage == 'F') { + printf("Le mot de passe donn correspond bien au compte [%s][id: %d].\n", RFIFOP(fd,6), RFIFOL(fd,2)); + ladmin_log("Le mot de passe donn correspond bien au compte [%s][id: %d]." RETCODE, RFIFOP(fd,6), RFIFOL(fd,2)); + } else { + printf("The proposed password is correct for the account [%s][id: %d].\n", RFIFOP(fd,6), RFIFOL(fd,2)); + ladmin_log("The proposed password is correct for the account [%s][id: %d]." RETCODE, RFIFOP(fd,6), RFIFOL(fd,2)); + } + } + bytes_to_read = 0; + RFIFOSKIP(fd,30); + break; + + case 0x793d: // answer of the change of an account sex + if (RFIFOREST(fd) < 30) + return 0; + if (RFIFOL(fd,2) == -1) { + if (defaultlanguage == 'F') { + printf("Echec de la modification du sexe du compte [%s].\n", RFIFOP(fd,6)); + printf("Le compte [%s] n'existe pas ou le sexe est d駛 celui demand.\n", RFIFOP(fd,6)); + ladmin_log("Echec de la modification du sexe du compte. Le compte [%s] n'existe pas ou le sexe est d駛 celui demand." RETCODE, RFIFOP(fd,6)); + } else { + printf("Account [%s] sex changing failed.\n", RFIFOP(fd,6)); + printf("Account [%s] doesn't exist or the sex is already the good sex.\n", RFIFOP(fd,6)); + ladmin_log("Account sex changing failed. The compte [%s] doesn't exist or the sex is already the good sex." RETCODE, RFIFOP(fd,6)); + } + } else { + if (defaultlanguage == 'F') { + printf("Sexe du compte [%s][id: %d] chang avec succ鑚.\n", RFIFOP(fd,6), RFIFOL(fd,2)); + ladmin_log("Sexe du compte [%s][id: %d] chang avec succ鑚." RETCODE, RFIFOP(fd,6), RFIFOL(fd,2)); + } else { + printf("Account [%s][id: %d] sex successfully changed.\n", RFIFOP(fd,6), RFIFOL(fd,2)); + ladmin_log("Account [%s][id: %d] sex successfully changed." RETCODE, RFIFOP(fd,6), RFIFOL(fd,2)); + } + } + bytes_to_read = 0; + RFIFOSKIP(fd,30); + break; + + case 0x793f: // answer of the change of an account GM level + if (RFIFOREST(fd) < 30) + return 0; + if (RFIFOL(fd,2) == -1) { + if (defaultlanguage == 'F') { + printf("Echec de la modification du niveau de GM du compte [%s].\n", RFIFOP(fd,6)); + printf("Le compte [%s] n'existe pas, le niveau de GM est d駛 celui demand饅n", RFIFOP(fd,6)); + printf("ou il est impossible de modifier le fichier des comptes GM.\n"); + ladmin_log("Echec de la modification du niveau de GM du compte. Le compte [%s] n'existe pas, le niveau de GM est d駛 celui demand ou il est impossible de modifier le fichier des comptes GM." RETCODE, RFIFOP(fd,6)); + } else { + printf("Account [%s] GM level changing failed.\n", RFIFOP(fd,6)); + printf("Account [%s] doesn't exist, the GM level is already the good GM level\n", RFIFOP(fd,6)); + printf("or it's impossible to modify the GM accounts file.\n"); + ladmin_log("Account GM level changing failed. The compte [%s] doesn't exist, the GM level is already the good sex or it's impossible to modify the GM accounts file." RETCODE, RFIFOP(fd,6)); + } + } else { + if (defaultlanguage == 'F') { + printf("Niveau de GM du compte [%s][id: %d] chang avec succ鑚.\n", RFIFOP(fd,6), RFIFOL(fd,2)); + ladmin_log("Niveau de GM du compte [%s][id: %d] chang avec succ鑚." RETCODE, RFIFOP(fd,6), RFIFOL(fd,2)); + } else { + printf("Account [%s][id: %d] GM level successfully changed.\n", RFIFOP(fd,6), RFIFOL(fd,2)); + ladmin_log("Account [%s][id: %d] GM level successfully changed." RETCODE, RFIFOP(fd,6), RFIFOL(fd,2)); + } + } + bytes_to_read = 0; + RFIFOSKIP(fd,30); + break; + + case 0x7941: // answer of the change of an account email + if (RFIFOREST(fd) < 30) + return 0; + if (RFIFOL(fd,2) == -1) { + if (defaultlanguage == 'F') { + printf("Echec de la modification de l'e-mail du compte [%s].\n", RFIFOP(fd,6)); + printf("Le compte [%s] n'existe pas.\n", RFIFOP(fd,6)); + ladmin_log("Echec de la modification de l'e-mail du compte. Le compte [%s] n'existe pas." RETCODE, RFIFOP(fd,6)); + } else { + printf("Account [%s] e-mail changing failed.\n", RFIFOP(fd,6)); + printf("Account [%s] doesn't exist.\n", RFIFOP(fd,6)); + ladmin_log("Account e-mail changing failed. The compte [%s] doesn't exist." RETCODE, RFIFOP(fd,6)); + } + } else { + if (defaultlanguage == 'F') { + printf("Modification de l'e-mail du compte [%s][id: %d] r騏ssie.\n", RFIFOP(fd,6), RFIFOL(fd,2)); + ladmin_log("Modification de l'e-mail du compte [%s][id: %d] r騏ssie." RETCODE, RFIFOP(fd,6), RFIFOL(fd,2)); + } else { + printf("Account [%s][id: %d] e-mail successfully changed.\n", RFIFOP(fd,6), RFIFOL(fd,2)); + ladmin_log("Account [%s][id: %d] e-mail successfully changed." RETCODE, RFIFOP(fd,6), RFIFOL(fd,2)); + } + } + bytes_to_read = 0; + RFIFOSKIP(fd,30); + break; + + case 0x7943: // answer of the change of an account memo + if (RFIFOREST(fd) < 30) + return 0; + if (RFIFOL(fd,2) == -1) { + if (defaultlanguage == 'F') { + printf("Echec du changement du m駑o du compte [%s]. Le compte n'existe pas.\n", RFIFOP(fd,6)); + ladmin_log("Echec du changement du m駑o du compte [%s]. Le compte n'existe pas." RETCODE, RFIFOP(fd,6)); + } else { + printf("Account [%s] memo changing failed. Account doesn't exist.\n", RFIFOP(fd,6)); + ladmin_log("Account [%s] memo changing failed. Account doesn't exist." RETCODE, RFIFOP(fd,6)); + } + } else { + if (defaultlanguage == 'F') { + printf("M駑o du compte [%s][id: %d] chang avec succ鑚.\n", RFIFOP(fd,6), RFIFOL(fd,2)); + ladmin_log("M駑o du compte [%s][id: %d] chang avec succ鑚." RETCODE, RFIFOP(fd,6), RFIFOL(fd,2)); + } else { + printf("Account [%s][id: %d] memo successfully changed.\n", RFIFOP(fd,6), RFIFOL(fd,2)); + ladmin_log("Account [%s][id: %d] memo successfully changed." RETCODE, RFIFOP(fd,6), RFIFOL(fd,2)); + } + } + bytes_to_read = 0; + RFIFOSKIP(fd,30); + break; + + case 0x7945: // answer of an account id search + if (RFIFOREST(fd) < 30) + return 0; + if (RFIFOL(fd,2) == -1) { + if (defaultlanguage == 'F') { + printf("Impossible de trouver l'id du compte [%s]. Le compte n'existe pas.\n", RFIFOP(fd,6)); + ladmin_log("Impossible de trouver l'id du compte [%s]. Le compte n'existe pas." RETCODE, RFIFOP(fd,6)); + } else { + printf("Unable to find the account [%s] id. Account doesn't exist.\n", RFIFOP(fd,6)); + ladmin_log("Unable to find the account [%s] id. Account doesn't exist." RETCODE, RFIFOP(fd,6)); + } + } else { + if (defaultlanguage == 'F') { + printf("Le compte [%s] a pour id: %d.\n", RFIFOP(fd,6), RFIFOL(fd,2)); + ladmin_log("Le compte [%s] a pour id: %d." RETCODE, RFIFOP(fd,6), RFIFOL(fd,2)); + } else { + printf("The account [%s] have the id: %d.\n", RFIFOP(fd,6), RFIFOL(fd,2)); + ladmin_log("The account [%s] have the id: %d." RETCODE, RFIFOP(fd,6), RFIFOL(fd,2)); + } + } + bytes_to_read = 0; + RFIFOSKIP(fd,30); + break; + + case 0x7947: // answer of an account name search + if (RFIFOREST(fd) < 30) + return 0; + if (strcmp(RFIFOP(fd,6), "") == 0) { + if (defaultlanguage == 'F') { + printf("Impossible de trouver le nom du compte [%d]. Le compte n'existe pas.\n", RFIFOL(fd,2)); + ladmin_log("Impossible de trouver le nom du compte [%d]. Le compte n'existe pas." RETCODE, RFIFOL(fd,2)); + } else { + printf("Unable to find the account [%d] name. Account doesn't exist.\n", RFIFOL(fd,2)); + ladmin_log("Unable to find the account [%d] name. Account doesn't exist." RETCODE, RFIFOL(fd,2)); + } + } else { + if (defaultlanguage == 'F') { + printf("Le compte [id: %d] a pour nom: %s.\n", RFIFOL(fd,2), RFIFOP(fd,6)); + ladmin_log("Le compte [id: %d] a pour nom: %s." RETCODE, RFIFOL(fd,2), RFIFOP(fd,6)); + } else { + printf("The account [id: %d] have the name: %s.\n", RFIFOL(fd,2), RFIFOP(fd,6)); + ladmin_log("The account [id: %d] have the name: %s." RETCODE, RFIFOL(fd,2), RFIFOP(fd,6)); + } + } + bytes_to_read = 0; + RFIFOSKIP(fd,30); + break; + + case 0x7949: // answer of an account validity limit set + if (RFIFOREST(fd) < 34) + return 0; + if (RFIFOL(fd,2) == -1) { + if (defaultlanguage == 'F') { + printf("Echec du changement de la validit du compte [%s]. Le compte n'existe pas.\n", RFIFOP(fd,6)); + ladmin_log("Echec du changement de la validit du compte [%s]. Le compte n'existe pas." RETCODE, RFIFOP(fd,6)); + } else { + printf("Account [%s] validity limit changing failed. Account doesn't exist.\n", RFIFOP(fd,6)); + ladmin_log("Account [%s] validity limit changing failed. Account doesn't exist." RETCODE, RFIFOP(fd,6)); + } + } else { + time_t timestamp = RFIFOL(fd,30); + if (timestamp == 0) { + if (defaultlanguage == 'F') { + printf("Limite de validit du compte [%s][id: %d] chang馥 avec succ鑚 en [illimit饐.\n", RFIFOP(fd,6), RFIFOL(fd,2)); + ladmin_log("Limite de validit du compte [%s][id: %d] chang馥 avec succ鑚 en [illimit饐." RETCODE, RFIFOP(fd,6), RFIFOL(fd,2)); + } else { + printf("Validity Limit of the account [%s][id: %d] successfully changed to [unlimited].\n", RFIFOP(fd,6), RFIFOL(fd,2)); + ladmin_log("Validity Limit of the account [%s][id: %d] successfully changed to [unlimited]." RETCODE, RFIFOP(fd,6), RFIFOL(fd,2)); + } + } else { + char tmpstr[128]; + strftime(tmpstr, 24, date_format, localtime(×tamp)); + if (defaultlanguage == 'F') { + printf("Limite de validit du compte [%s][id: %d] chang馥 avec succ鑚 pour 黎re jusqu'au %s.\n", RFIFOP(fd,6), RFIFOL(fd,2), tmpstr); + ladmin_log("Limite de validit du compte [%s][id: %d] chang馥 avec succ鑚 pour 黎re jusqu'au %s." RETCODE, RFIFOP(fd,6), RFIFOL(fd,2), tmpstr); + } else { + printf("Validity Limit of the account [%s][id: %d] successfully changed to be until %s.\n", RFIFOP(fd,6), RFIFOL(fd,2), tmpstr); + ladmin_log("Validity Limit of the account [%s][id: %d] successfully changed to be until %s." RETCODE, RFIFOP(fd,6), RFIFOL(fd,2), tmpstr); + } + } + } + bytes_to_read = 0; + RFIFOSKIP(fd,34); + break; + + case 0x794b: // answer of an account ban set + if (RFIFOREST(fd) < 34) + return 0; + if (RFIFOL(fd,2) == -1) { + if (defaultlanguage == 'F') { + printf("Echec du changement de la date finale de banissement du compte [%s]. Le compte n'existe pas.\n", RFIFOP(fd,6)); + ladmin_log("Echec du changement de la date finale de banissement du compte [%s]. Le compte n'existe pas." RETCODE, RFIFOP(fd,6)); + } else { + printf("Account [%s] final date of banishment changing failed. Account doesn't exist.\n", RFIFOP(fd,6)); + ladmin_log("Account [%s] final date of banishment changing failed. Account doesn't exist." RETCODE, RFIFOP(fd,6)); + } + } else { + time_t timestamp = RFIFOL(fd,30); + if (timestamp == 0) { + if (defaultlanguage == 'F') { + printf("Date finale de banissement du compte [%s][id: %d] chang馥 avec succ鑚 en [d-bannie].\n", RFIFOP(fd,6), RFIFOL(fd,2)); + ladmin_log("Date finale de banissement du compte [%s][id: %d] chang馥 avec succ鑚 en [d-bannie]." RETCODE, RFIFOP(fd,6), RFIFOL(fd,2)); + } else { + printf("Final date of banishment of the account [%s][id: %d] successfully changed to [unbanished].\n", RFIFOP(fd,6), RFIFOL(fd,2)); + ladmin_log("Final date of banishment of the account [%s][id: %d] successfully changed to [unbanished]." RETCODE, RFIFOP(fd,6), RFIFOL(fd,2)); + } + } else { + char tmpstr[128]; + strftime(tmpstr, 24, date_format, localtime(×tamp)); + if (defaultlanguage == 'F') { + printf("Date finale de banissement du compte [%s][id: %d] chang馥 avec succ鑚 pour 黎re jusqu'au %s.\n", RFIFOP(fd,6), RFIFOL(fd,2), tmpstr); + ladmin_log("Date finale de banissement du compte [%s][id: %d] chang馥 avec succ鑚 pour 黎re jusqu'au %s." RETCODE, RFIFOP(fd,6), RFIFOL(fd,2), tmpstr); + } else { + printf("Final date of banishment of the account [%s][id: %d] successfully changed to be until %s.\n", RFIFOP(fd,6), RFIFOL(fd,2), tmpstr); + ladmin_log("Final date of banishment of the account [%s][id: %d] successfully changed to be until %s." RETCODE, RFIFOP(fd,6), RFIFOL(fd,2), tmpstr); + } + } + } + bytes_to_read = 0; + RFIFOSKIP(fd,34); + break; + + case 0x794d: // answer of an account ban date/time changing + if (RFIFOREST(fd) < 34) + return 0; + if (RFIFOL(fd,2) == -1) { + if (defaultlanguage == 'F') { + printf("Echec du changement de la date finale de banissement du compte [%s]. Le compte n'existe pas.\n", RFIFOP(fd,6)); + ladmin_log("Echec du changement de la date finale de banissement du compte [%s]. Le compte n'existe pas." RETCODE, RFIFOP(fd,6)); + } else { + printf("Account [%s] final date of banishment changing failed. Account doesn't exist.\n", RFIFOP(fd,6)); + ladmin_log("Account [%s] final date of banishment changing failed. Account doesn't exist." RETCODE, RFIFOP(fd,6)); + } + } else { + time_t timestamp = RFIFOL(fd,30); + if (timestamp == 0) { + if (defaultlanguage == 'F') { + printf("Date finale de banissement du compte [%s][id: %d] chang馥 avec succ鑚 en [d-bannie].\n", RFIFOP(fd,6), RFIFOL(fd,2)); + ladmin_log("Date finale de banissement du compte [%s][id: %d] chang馥 avec succ鑚 en [d-bannie]." RETCODE, RFIFOP(fd,6), RFIFOL(fd,2)); + } else { + printf("Final date of banishment of the account [%s][id: %d] successfully changed to [unbanished].\n", RFIFOP(fd,6), RFIFOL(fd,2)); + ladmin_log("Final date of banishment of the account [%s][id: %d] successfully changed to [unbanished]." RETCODE, RFIFOP(fd,6), RFIFOL(fd,2)); + } + } else { + char tmpstr[128]; + strftime(tmpstr, 24, date_format, localtime(×tamp)); + if (defaultlanguage == 'F') { + printf("Date finale de banissement du compte [%s][id: %d] chang馥 avec succ鑚 pour 黎re jusqu'au %s.\n", RFIFOP(fd,6), RFIFOL(fd,2), tmpstr); + ladmin_log("Date finale de banissement du compte [%s][id: %d] chang馥 avec succ鑚 pour 黎re jusqu'au %s." RETCODE, RFIFOP(fd,6), RFIFOL(fd,2), tmpstr); + } else { + printf("Final date of banishment of the account [%s][id: %d] successfully changed to be until %s.\n", RFIFOP(fd,6), RFIFOL(fd,2), tmpstr); + ladmin_log("Final date of banishment of the account [%s][id: %d] successfully changed to be until %s." RETCODE, RFIFOP(fd,6), RFIFOL(fd,2), tmpstr); + } + } + } + bytes_to_read = 0; + RFIFOSKIP(fd,34); + break; + + case 0x794f: // answer of a broadcast + if (RFIFOREST(fd) < 4) + return 0; + if (RFIFOW(fd,2) == (unsigned short)-1) { + if (defaultlanguage == 'F') { + printf("Echec de l'envoi du message. Aucun server de char en ligne.\n"); + ladmin_log("Echec de l'envoi du message. Aucun server de char en ligne." RETCODE); + } else { + printf("Message sending failed. No online char-server.\n"); + ladmin_log("Message sending failed. No online char-server." RETCODE); + } + } else { + if (defaultlanguage == 'F') { + printf("Message transmis au server de logins avec succ鑚.\n"); + ladmin_log("Message transmis au server de logins avec succ鑚." RETCODE); + } else { + printf("Message successfully sended to login-server.\n"); + ladmin_log("Message successfully sended to login-server." RETCODE); + } + } + bytes_to_read = 0; + RFIFOSKIP(fd,4); + break; + + case 0x7951: // answer of an account validity limit changing + if (RFIFOREST(fd) < 34) + return 0; + if (RFIFOL(fd,2) == -1) { + if (defaultlanguage == 'F') { + printf("Echec du changement de la validit du compte [%s]. Le compte n'existe pas.\n", RFIFOP(fd,6)); + ladmin_log("Echec du changement de la validit du compte [%s]. Le compte n'existe pas." RETCODE, RFIFOP(fd,6)); + } else { + printf("Account [%s] validity limit changing failed. Account doesn't exist.\n", RFIFOP(fd,6)); + ladmin_log("Account [%s] validity limit changing failed. Account doesn't exist." RETCODE, RFIFOP(fd,6)); + } + } else { + time_t timestamp = RFIFOL(fd,30); + if (timestamp == 0) { + if (defaultlanguage == 'F') { + printf("Limite de validit du compte [%s][id: %d] inchang馥.\n", RFIFOP(fd,6), RFIFOL(fd,2)); + printf("Le compte a une validit illimit馥 ou\n"); + printf("la modification est impossible avec les ajustements demand駸.\n"); + ladmin_log("Limite de validit du compte [%s][id: %d] inchang馥. Le compte a une validit illimit馥 ou la modification est impossible avec les ajustements demand駸." RETCODE, RFIFOP(fd,6), RFIFOL(fd,2)); + } else { + printf("Validity limit of the account [%s][id: %d] unchanged.\n", RFIFOP(fd,6), RFIFOL(fd,2)); + printf("The account have an unlimited validity limit or\n"); + printf("the changing is impossible with the proposed adjustments.\n"); + ladmin_log("Validity limit of the account [%s][id: %d] unchanged. The account have an unlimited validity limit or the changing is impossible with the proposed adjustments." RETCODE, RFIFOP(fd,6), RFIFOL(fd,2)); + } + } else { + char tmpstr[128]; + strftime(tmpstr, 24, date_format, localtime(×tamp)); + if (defaultlanguage == 'F') { + printf("Limite de validit du compte [%s][id: %d] chang馥 avec succ鑚 pour 黎re jusqu'au %s.\n", RFIFOP(fd,6), RFIFOL(fd,2), tmpstr); + ladmin_log("Limite de validit du compte [%s][id: %d] chang馥 avec succ鑚 pour 黎re jusqu'au %s." RETCODE, RFIFOP(fd,6), RFIFOL(fd,2), tmpstr); + } else { + printf("Validity limit of the account [%s][id: %d] successfully changed to be until %s.\n", RFIFOP(fd,6), RFIFOL(fd,2), tmpstr); + ladmin_log("Validity limit of the account [%s][id: %d] successfully changed to be until %s." RETCODE, RFIFOP(fd,6), RFIFOL(fd,2), tmpstr); + } + } + } + bytes_to_read = 0; + RFIFOSKIP(fd,34); + break; + + case 0x7953: // answer of a request about informations of an account (by account name/id) + if (RFIFOREST(fd) < 150 || RFIFOREST(fd) < (150 + RFIFOW(fd,148))) + return 0; + { + char userid[24], error_message[20], lastlogin[24], last_ip[16], email[40], memo[255]; + time_t ban_until_time; // # of seconds 1/1/1970 (timestamp): ban time limit of the account (0 = no ban) + time_t connect_until_time; // # of seconds 1/1/1970 (timestamp): Validity limit of the account (0 = unlimited) + memcpy(userid, RFIFOP(fd,7), sizeof(userid)); + userid[sizeof(userid)-1] = '\0'; + memcpy(error_message, RFIFOP(fd,40), sizeof(error_message)); + error_message[sizeof(error_message)-1] = '\0'; + memcpy(lastlogin, RFIFOP(fd,60), sizeof(lastlogin)); + lastlogin[sizeof(lastlogin)-1] = '\0'; + memcpy(last_ip, RFIFOP(fd,84), sizeof(last_ip)); + last_ip[sizeof(last_ip)-1] = '\0'; + memcpy(email, RFIFOP(fd,100), sizeof(email)); + email[sizeof(email)-1] = '\0'; + connect_until_time = (time_t)RFIFOL(fd,140); + ban_until_time = (time_t)RFIFOL(fd,144); + memset(memo, '\0', sizeof(memo)); + strncpy(memo, RFIFOP(fd,150), RFIFOW(fd,148)); + if (RFIFOL(fd,2) == -1) { + if (defaultlanguage == 'F') { + printf("Impossible de trouver le compte [%s]. Le compte n'existe pas.\n", parameters); + ladmin_log("Impossible de trouver le compte [%s]. Le compte n'existe pas." RETCODE, parameters); + } else { + printf("Unabled to find the account [%s]. Account doesn't exist.\n", parameters); + ladmin_log("Unabled to find the account [%s]. Account doesn't exist." RETCODE, parameters); + } + } else if (strlen(userid) == 0) { + if (defaultlanguage == 'F') { + printf("Impossible de trouver le compte [id: %s]. Le compte n'existe pas.\n", parameters); + ladmin_log("Impossible de trouver le compte [id: %s]. Le compte n'existe pas." RETCODE, parameters); + } else { + printf("Unabled to find the account [id: %s]. Account doesn't exist.\n", parameters); + ladmin_log("Unabled to find the account [id: %s]. Account doesn't exist." RETCODE, parameters); + } + } else { + if (defaultlanguage == 'F') { + ladmin_log("R馗eption d'information concernant un compte." RETCODE); + printf("Le compte a les caract駻istiques suivantes:\n"); + } else { + ladmin_log("Receiving information about an account." RETCODE); + printf("The account is set with:\n"); + } + if (RFIFOB(fd,6) == 0) { + printf(" Id: %d (non-GM)\n", RFIFOL(fd,2)); + } else { + if (defaultlanguage == 'F') { + printf(" Id: %d (GM niveau %d)\n", RFIFOL(fd,2), (int)RFIFOB(fd,6)); + } else { + printf(" Id: %d (GM level %d)\n", RFIFOL(fd,2), (int)RFIFOB(fd,6)); + } + } + if (defaultlanguage == 'F') { + printf(" Nom: '%s'\n", userid); + if (RFIFOB(fd,31) == 0) + printf(" Sexe: Femme\n"); + else if (RFIFOB(fd,31) == 1) + printf(" Sexe: Male\n"); + else + printf(" Sexe: Serveur\n"); + } else { + printf(" Name: '%s'\n", userid); + if (RFIFOB(fd,31) == 0) + printf(" Sex: Female\n"); + else if (RFIFOB(fd,31) == 1) + printf(" Sex: Male\n"); + else + printf(" Sex: Server\n"); + } + printf(" E-mail: %s\n", email); + switch(RFIFOL(fd,36)) { + case 0: + if (defaultlanguage == 'F') + printf(" Statut: 0 [Compte Ok]\n"); + else + printf(" Statut: 0 [Account OK]\n"); + break; + case 1: + printf(" Statut: 1 [Unregistered ID]\n"); + break; + case 2: + printf(" Statut: 2 [Incorrect Password]\n"); + break; + case 3: + printf(" Statut: 3 [This ID is expired]\n"); + break; + case 4: + printf(" Statut: 4 [Rejected from Server]\n"); + break; + case 5: + printf(" Statut: 5 [You have been blocked by the GM Team]\n"); + break; + case 6: + printf(" Statut: 6 [Your Game's EXE file is not the latest version]\n"); + break; + case 7: + printf(" Statut: 7 [You are Prohibited to log in until %s]\n", error_message); + break; + case 8: + printf(" Statut: 8 [Server is jammed due to over populated]\n"); + break; + case 9: + printf(" Statut: 9 [No MSG]\n"); + break; + default: // 100 + printf(" Statut: %d [This ID is totally erased]\n", RFIFOL(fd,36)); + break; + } + if (defaultlanguage == 'F') { + if (ban_until_time == 0) { + printf(" Banissement: non banni.\n"); + } else { + char tmpstr[128]; + strftime(tmpstr, 24, date_format, localtime(&ban_until_time)); + printf(" Banissement: jusqu'au %s.\n", tmpstr); + } + if (RFIFOL(fd,32) > 1) + printf(" Compteur: %d connexions.\n", RFIFOL(fd,32)); + else + printf(" Compteur: %d connexion.\n", RFIFOL(fd,32)); + printf(" Derni鑽e connexion le: %s (ip: %s)\n", lastlogin, last_ip); + if (connect_until_time == 0) { + printf(" Limite de validit: illimit.\n"); + } else { + char tmpstr[128]; + strftime(tmpstr, 24, date_format, localtime(&connect_until_time)); + printf(" Limite de validit: jusqu'au %s.\n", tmpstr); + } + } else { + if (ban_until_time == 0) { + printf(" Banishment: not banished.\n"); + } else { + char tmpstr[128]; + strftime(tmpstr, 24, date_format, localtime(&ban_until_time)); + printf(" Banishment: until %s.\n", tmpstr); + } + if (RFIFOL(fd,32) > 1) + printf(" Count: %d connections.\n", RFIFOL(fd,32)); + else + printf(" Count: %d connection.\n", RFIFOL(fd,32)); + printf(" Last connection at: %s (ip: %s)\n", lastlogin, last_ip); + if (connect_until_time == 0) { + printf(" Validity limit: unlimited.\n"); + } else { + char tmpstr[128]; + strftime(tmpstr, 24, date_format, localtime(&connect_until_time)); + printf(" Validity limit: until %s.\n", tmpstr); + } + } + printf(" Memo: '%s'\n", memo); + } + } + bytes_to_read = 0; + RFIFOSKIP(fd,150 + RFIFOW(fd,148)); + break; + + default: + printf("Remote administration has been disconnected (unknown packet).\n"); + ladmin_log("'End of connection, unknown packet." RETCODE); + session[fd]->eof = 1; + return 0; + } + } + + // if we don't wait new packets, do the prompt + prompt(); + + return 0; +} + +//------------------------------------ +// Function to connect to login-server +//------------------------------------ +int Connect_login_server() { + if (defaultlanguage == 'F') { + printf("Essai de connection au server de logins...\n"); + ladmin_log("Essai de connection au server de logins..." RETCODE); + } else { + printf("Attempt to connect to login-server...\n"); + ladmin_log("Attempt to connect to login-server..." RETCODE); + } + + login_fd = make_connection(login_ip, loginserverport); + +#ifdef PASSWORDENC + if (passenc == 0) { +#endif + WFIFOW(login_fd,0) = 0x7918; // Request for administation login + WFIFOW(login_fd,2) = 0; // no encrypted + memcpy(WFIFOP(login_fd,4), loginserveradminpassword, 24); + WFIFOSET(login_fd,28); + bytes_to_read = 1; + if (defaultlanguage == 'F') { + printf("Envoi du mot de passe...\n"); + ladmin_log("Envoi du mot de passe..." RETCODE); + } else { + printf("Sending of the password...\n"); + ladmin_log("Sending of the password..." RETCODE); + } +#ifdef PASSWORDENC + } else { + WFIFOW(login_fd,0) = 0x791a; // Sending request about the coding key + WFIFOSET(login_fd,2); + bytes_to_read = 1; + if (defaultlanguage == 'F') { + printf("Demande de la clef MD5...\n"); + ladmin_log("Demande de la clef MD5..." RETCODE); + } else { + printf("Request about the MD5 key...\n"); + ladmin_log("Request about the MD5 key..." RETCODE); + } + } +#endif + + return 0; +} + +//------------------------------------------------- +// Return numerical value of a switch configuration +// on/off, english, fran軋is, deutsch, espaol +//------------------------------------------------- +int config_switch(const char *str) { + if (strcmpi(str, "on") == 0 || strcmpi(str, "yes") == 0 || strcmpi(str, "oui") == 0 || strcmpi(str, "ja") == 0 || strcmpi(str, "si") == 0) + return 1; + if (strcmpi(str, "off") == 0 || strcmpi(str, "no") == 0 || strcmpi(str, "non") == 0 || strcmpi(str, "nein") == 0) + return 0; + + return atoi(str); +} + +//----------------------------------- +// Reading general configuration file +//----------------------------------- +int ladmin_config_read(const char *cfgName) { + char line[1024], w1[1024], w2[1024]; + FILE *fp; + + fp = fopen(cfgName, "r"); + if (fp == NULL) { + if (defaultlanguage == 'F') { + printf("\033[0mFichier de configuration (%s) non trouv.\n", cfgName); + } else { + printf("\033[0mConfiguration file (%s) not found.\n", cfgName); + } + return 1; + } + + if (defaultlanguage == 'F') { + printf("\033[0m---D饕ut de lecture du fichier de configuration Ladmin (%s)\n", cfgName); + } else { + printf("\033[0m---Start reading of Ladmin configuration file (%s)\n", cfgName); + } + while(fgets(line, sizeof(line)-1, fp)) { + if (line[0] == '/' && line[1] == '/') + continue; + + line[sizeof(line)-1] = '\0'; + if (sscanf(line, "%[^:]: %[^\r\n]", w1, w2) == 2) { + remove_control_chars(w1); + remove_control_chars(w2); + + if(strcmpi(w1,"login_ip")==0){ + struct hostent *h = gethostbyname (w2); + if (h != NULL) { + if (defaultlanguage == 'F') { + printf("Adresse du serveur de logins: %s -> %d.%d.%d.%d\n", w2, (unsigned char)h->h_addr[0], (unsigned char)h->h_addr[1], (unsigned char)h->h_addr[2], (unsigned char)h->h_addr[3]); + } else { + printf("Login server IP address: %s -> %d.%d.%d.%d\n", w2, (unsigned char)h->h_addr[0], (unsigned char)h->h_addr[1], (unsigned char)h->h_addr[2], (unsigned char)h->h_addr[3]); + } + sprintf(loginserverip, "%d.%d.%d.%d", (unsigned char)h->h_addr[0], (unsigned char)h->h_addr[1], (unsigned char)h->h_addr[2], (unsigned char)h->h_addr[3]); + } else + memcpy(loginserverip, w2, 16); + } else if (strcmpi(w1, "login_port") == 0) { + loginserverport = atoi(w2); + } else if (strcmpi(w1, "admin_pass") == 0) { + strncpy(loginserveradminpassword, w2, sizeof(loginserveradminpassword)); + loginserveradminpassword[sizeof(loginserveradminpassword)-1] = '\0'; +#ifdef PASSWORDENC + } else if (strcmpi(w1, "passenc") == 0) { + passenc = atoi(w2); + if (passenc < 0 || passenc > 2) + passenc = 0; +#endif + } else if (strcmpi(w1, "defaultlanguage") == 0) { + if (w2[0] == 'F' || w2[0] == 'E') + defaultlanguage = w2[0]; + } else if (strcmpi(w1, "ladmin_log_filename") == 0) { + strncpy(ladmin_log_filename, w2, sizeof(ladmin_log_filename)); + ladmin_log_filename[sizeof(ladmin_log_filename)-1] = '\0'; + } else if (strcmpi(w1, "date_format") == 0) { // note: never have more than 19 char for the date! + switch (atoi(w2)) { + case 0: + strcpy(date_format, "%d-%m-%Y %H:%M:%S"); // 31-12-2004 23:59:59 + break; + case 1: + strcpy(date_format, "%m-%d-%Y %H:%M:%S"); // 12-31-2004 23:59:59 + break; + case 2: + strcpy(date_format, "%Y-%d-%m %H:%M:%S"); // 2004-31-12 23:59:59 + break; + case 3: + strcpy(date_format, "%Y-%m-%d %H:%M:%S"); // 2004-12-31 23:59:59 + break; + } + } else if (strcmpi(w1, "import") == 0) { + ladmin_config_read(w2); + } + } + } + fclose(fp); + + login_ip = inet_addr(loginserverip); + + if (defaultlanguage == 'F') { + printf("---Lecture du fichier de configuration Ladmin termin馥.\n"); + } else { + printf("---End reading of Ladmin configuration file.\n"); + } + + return 0; +} + +//-------------------------------------- +// Function called at exit of the server +//-------------------------------------- +void do_final(void) { + + if (already_exit_function == 0) { + delete_session(login_fd); + + if (defaultlanguage == 'F') { + printf("\033[0m----Fin de Ladmin (fin normale avec fermeture de tous les fichiers).\n"); + ladmin_log("----Fin de Ladmin (fin normale avec fermeture de tous les fichiers)." RETCODE); + } else { + printf("\033[0m----End of Ladmin (normal end with closing of all files).\n"); + ladmin_log("----End of Ladmin (normal end with closing of all files)." RETCODE); + } + + already_exit_function = 1; + } +} + +//------------------------ +// Main function of ladmin +//------------------------ +int do_init(int argc, char **argv) { + // read ladmin configuration + ladmin_config_read((argc > 1) ? argv[1] : LADMIN_CONF_NAME); + + ladmin_log(""); + if (defaultlanguage == 'F') { + ladmin_log("Fichier de configuration lu." RETCODE); + } else { + ladmin_log("Configuration file readed." RETCODE); + } + + srand(time(NULL)); + + set_termfunc(do_final); + set_defaultparse(parse_fromlogin); + + if (defaultlanguage == 'F') { + printf("Outil d'administration distance de eAthena.\n"); + printf("(pour eAthena version %d.%d.%d.)\n", ATHENA_MAJOR_VERSION, ATHENA_MINOR_VERSION, ATHENA_REVISION); + } else { + printf("EAthena login-server administration tool.\n"); + printf("(for eAthena version %d.%d.%d.)\n", ATHENA_MAJOR_VERSION, ATHENA_MINOR_VERSION, ATHENA_REVISION); + } + + if (defaultlanguage == 'F') { + ladmin_log("Ladmin est pr黎." RETCODE); + printf("Ladmin est \033[1;32mpr黎\033[0m.\n\n"); + } else { + ladmin_log("Ladmin is ready." RETCODE); + printf("Ladmin is \033[1;32mready\033[0m.\n\n"); + } + + Connect_login_server(); + + atexit(do_final); + + return 0; +} diff --git a/misc/src/ladmin/ladmin.h b/misc/src/ladmin/ladmin.h new file mode 100644 index 0000000..f76bfc2 --- /dev/null +++ b/misc/src/ladmin/ladmin.h @@ -0,0 +1,11 @@ +// $Id: ladmin.h,v 1.1.1.1 2004/09/10 17:26:52 MagicalTux Exp $ +#ifndef _LADMIN_H_ +#define _LADMIN_H_ + +#define LADMIN_CONF_NAME "conf/ladmin_athena.conf" +#define PASSWORDENC 3 // A definition is given when making an encryption password correspond. + // It is 1 at the time of passwordencrypt. + // It is made into 2 at the time of passwordencrypt2. + // When it is made 3, it corresponds to both. + +#endif diff --git a/misc/src/ladmin/md5calc.c b/misc/src/ladmin/md5calc.c new file mode 100644 index 0000000..7b9a9a2 --- /dev/null +++ b/misc/src/ladmin/md5calc.c @@ -0,0 +1,237 @@ +// $Id: md5calc.c,v 1.1.1.1 2004/09/10 17:26:53 MagicalTux Exp $ +/*********************************************************** + * md5 calculation algorithm + * + * The source code referred to the following URL. + * http://www.geocities.co.jp/SiliconValley-Oakland/8878/lab17/lab17.html + * + ***********************************************************/ + +#include "md5calc.h" +#include <string.h> +#include <stdio.h> + +#ifndef UINT_MAX +#define UINT_MAX 4294967295U +#endif + +// Global variable +static unsigned int *pX; + +// Stirng Table +static const unsigned int T[] = { + 0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, //0 + 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501, //4 + 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, //8 + 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821, //12 + 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, //16 + 0xd62f105d, 0x2441453, 0xd8a1e681, 0xe7d3fbc8, //20 + 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, //24 + 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a, //28 + 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, //32 + 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70, //36 + 0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x4881d05, //40 + 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665, //44 + 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, //48 + 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1, //52 + 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, //56 + 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391 //60 +}; + +// ROTATE_LEFT The left is made to rotate x [ n-bit ]. This is diverted as it is from RFC. +#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32-(n)))) + +// The function used for other calculation +static unsigned int F(unsigned int X, unsigned int Y, unsigned int Z) +{ + return (X & Y) | (~X & Z); +} +static unsigned int G(unsigned int X, unsigned int Y, unsigned int Z) +{ + return (X & Z) | (Y & ~Z); +} +static unsigned int H(unsigned int X, unsigned int Y, unsigned int Z) +{ + return X ^ Y ^ Z; +} +static unsigned int I(unsigned int X, unsigned int Y, unsigned int Z) +{ + return Y ^ (X | ~Z); +} + +static unsigned int Round(unsigned int a, unsigned int b, unsigned int FGHI, + unsigned int k, unsigned int s, unsigned int i) +{ + return b + ROTATE_LEFT(a + FGHI + pX[k] + T[i], s); +} + +static void Round1(unsigned int *a, unsigned int b, unsigned int c, + unsigned int d,unsigned int k, unsigned int s, unsigned int i) +{ + *a = Round(*a, b, F(b,c,d), k, s, i); +} +static void Round2(unsigned int *a, unsigned int b, unsigned int c, + unsigned int d,unsigned int k, unsigned int s, unsigned int i) +{ + *a = Round(*a, b, G(b,c,d), k, s, i); +} +static void Round3(unsigned int *a, unsigned int b, unsigned int c, + unsigned int d,unsigned int k, unsigned int s, unsigned int i) +{ + *a = Round(*a, b, H(b,c,d), k, s, i); +} +static void Round4(unsigned int *a, unsigned int b, unsigned int c, + unsigned int d,unsigned int k, unsigned int s, unsigned int i) +{ + *a = Round(*a, b, I(b,c,d), k, s, i); +} + +static void MD5_Round_Calculate(const unsigned char *block, + unsigned int *A2, unsigned int *B2, unsigned int *C2, unsigned int *D2) +{ + //create X It is since it is required. + unsigned int X[16]; //512bit 64byte + int j,k; + + //Save A as AA, B as BB, C as CC, and and D as DD (saving of A, B, C, and D) + unsigned int A=*A2, B=*B2, C=*C2, D=*D2; + unsigned int AA = A,BB = B,CC = C,DD = D; + + //It is a large region variable reluctantly because of calculation of a round. . . for Round1...4 + pX = X; + + //Copy block(padding_message) i into X + for (j=0,k=0; j<64; j+=4,k++) + X[k] = ( (unsigned int )block[j] ) // 8byte*4 -> 32byte conversion + | ( ((unsigned int )block[j+1]) << 8 ) // A function called Decode as used in the field of RFC + | ( ((unsigned int )block[j+2]) << 16 ) + | ( ((unsigned int )block[j+3]) << 24 ); + + + //Round 1 + Round1(&A,B,C,D, 0, 7, 0); Round1(&D,A,B,C, 1, 12, 1); Round1(&C,D,A,B, 2, 17, 2); Round1(&B,C,D,A, 3, 22, 3); + Round1(&A,B,C,D, 4, 7, 4); Round1(&D,A,B,C, 5, 12, 5); Round1(&C,D,A,B, 6, 17, 6); Round1(&B,C,D,A, 7, 22, 7); + Round1(&A,B,C,D, 8, 7, 8); Round1(&D,A,B,C, 9, 12, 9); Round1(&C,D,A,B, 10, 17, 10); Round1(&B,C,D,A, 11, 22, 11); + Round1(&A,B,C,D, 12, 7, 12); Round1(&D,A,B,C, 13, 12, 13); Round1(&C,D,A,B, 14, 17, 14); Round1(&B,C,D,A, 15, 22, 15); + + //Round 2 + Round2(&A,B,C,D, 1, 5, 16); Round2(&D,A,B,C, 6, 9, 17); Round2(&C,D,A,B, 11, 14, 18); Round2(&B,C,D,A, 0, 20, 19); + Round2(&A,B,C,D, 5, 5, 20); Round2(&D,A,B,C, 10, 9, 21); Round2(&C,D,A,B, 15, 14, 22); Round2(&B,C,D,A, 4, 20, 23); + Round2(&A,B,C,D, 9, 5, 24); Round2(&D,A,B,C, 14, 9, 25); Round2(&C,D,A,B, 3, 14, 26); Round2(&B,C,D,A, 8, 20, 27); + Round2(&A,B,C,D, 13, 5, 28); Round2(&D,A,B,C, 2, 9, 29); Round2(&C,D,A,B, 7, 14, 30); Round2(&B,C,D,A, 12, 20, 31); + + //Round 3 + Round3(&A,B,C,D, 5, 4, 32); Round3(&D,A,B,C, 8, 11, 33); Round3(&C,D,A,B, 11, 16, 34); Round3(&B,C,D,A, 14, 23, 35); + Round3(&A,B,C,D, 1, 4, 36); Round3(&D,A,B,C, 4, 11, 37); Round3(&C,D,A,B, 7, 16, 38); Round3(&B,C,D,A, 10, 23, 39); + Round3(&A,B,C,D, 13, 4, 40); Round3(&D,A,B,C, 0, 11, 41); Round3(&C,D,A,B, 3, 16, 42); Round3(&B,C,D,A, 6, 23, 43); + Round3(&A,B,C,D, 9, 4, 44); Round3(&D,A,B,C, 12, 11, 45); Round3(&C,D,A,B, 15, 16, 46); Round3(&B,C,D,A, 2, 23, 47); + + //Round 4 + Round4(&A,B,C,D, 0, 6, 48); Round4(&D,A,B,C, 7, 10, 49); Round4(&C,D,A,B, 14, 15, 50); Round4(&B,C,D,A, 5, 21, 51); + Round4(&A,B,C,D, 12, 6, 52); Round4(&D,A,B,C, 3, 10, 53); Round4(&C,D,A,B, 10, 15, 54); Round4(&B,C,D,A, 1, 21, 55); + Round4(&A,B,C,D, 8, 6, 56); Round4(&D,A,B,C, 15, 10, 57); Round4(&C,D,A,B, 6, 15, 58); Round4(&B,C,D,A, 13, 21, 59); + Round4(&A,B,C,D, 4, 6, 60); Round4(&D,A,B,C, 11, 10, 61); Round4(&C,D,A,B, 2, 15, 62); Round4(&B,C,D,A, 9, 21, 63); + + // Then perform the following additions. (let's add) + *A2 = A + AA; + *B2 = B + BB; + *C2 = C + CC; + *D2 = D + DD; + + //The clearance of confidential information + memset(pX, 0, sizeof(X)); +} + +//------------------------------------------------------------------- +// The function for the exteriors + +/** output is the coded binary in the character sequence which wants to code string. */ +void MD5_String2binary(const char * string, char * output) +{ +//var + /*8bit*/ + unsigned char padding_message[64]; //Extended message 512bit 64byte + unsigned char *pstring; //The position of string in the present scanning notes is held. + +// unsigned char digest[16]; + /*32bit*/ + unsigned int string_byte_len, //The byte chief of string is held. + string_bit_len, //The bit length of string is held. + copy_len, //The number of bytes which is used by 1-3 and which remained + msg_digest[4]; //Message digest 128bit 4byte + unsigned int *A = &msg_digest[0], //The message digest in accordance with RFC (reference) + *B = &msg_digest[1], + *C = &msg_digest[2], + *D = &msg_digest[3]; + int i; + +//prog + //Step 3.Initialize MD Buffer (although it is the initialization; step 3 of A, B, C, and D -- unavoidable -- a head) + *A = 0x67452301; + *B = 0xefcdab89; + *C = 0x98badcfe; + *D = 0x10325476; + + //Step 1.Append Padding Bits (extension of a mark bit) + //1-1 + string_byte_len = strlen(string); //The byte chief of a character sequence is acquired. + pstring = (unsigned char *)string; //The position of the present character sequence is set. + + //1-2 Repeat calculation until length becomes less than 64 bytes. + for (i=string_byte_len; 64<=i; i-=64,pstring+=64) + MD5_Round_Calculate(pstring, A,B,C,D); + + //1-3 + copy_len = string_byte_len % 64; //The number of bytes which remained is computed. + strncpy((char *)padding_message, (char *)pstring, copy_len); //A message is copied to an extended bit sequence. + memset(padding_message+copy_len, 0, 64 - copy_len); //It buries by 0 until it becomes extended bit length. + padding_message[copy_len] |= 0x80; //The next of a message is 1. + + //1-4 + //If 56 bytes or more (less than 64 bytes) of remainder becomes, it will calculate by extending to 64 bytes. + if (56 <= copy_len) { + MD5_Round_Calculate(padding_message, A,B,C,D); + memset(padding_message, 0, 56); //56 bytes is newly fill uped with 0. + } + + + //Step 2.Append Length (the information on length is added) + string_bit_len = string_byte_len * 8; //From the byte chief to bit length (32 bytes of low rank) + memcpy(&padding_message[56], &string_bit_len, 4); //32 bytes of low rank is set. + + //When bit length cannot be expressed in 32 bytes of low rank, it is a beam raising to a higher rank. + if (UINT_MAX / 8 < string_byte_len) { + unsigned int high = (string_byte_len - UINT_MAX / 8) * 8; + memcpy(&padding_message[60], &high, 4); + } else + memset(&padding_message[60], 0, 4); //In this case, it is good for a higher rank at 0. + + //Step 4.Process Message in 16-Word Blocks (calculation of MD5) + MD5_Round_Calculate(padding_message, A,B,C,D); + + + //Step 5.Output (output) + memcpy(output,msg_digest,16); +// memcpy (digest, msg_digest, and 16); //8 byte*4 < - 32byte conversion A function called Encode as used in the field of RFC +/* sprintf(output, + "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", + digest[ 0], digest[ 1], digest[ 2], digest[ 3], + digest[ 4], digest[ 5], digest[ 6], digest[ 7], + digest[ 8], digest[ 9], digest[10], digest[11], + digest[12], digest[13], digest[14], digest[15]);*/ +} + +/** output is the coded character sequence in the character sequence which wants to code string. */ +void MD5_String(const char * string, char * output) +{ + unsigned char digest[16]; + + MD5_String2binary(string,digest); + sprintf(output, + "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", + digest[ 0], digest[ 1], digest[ 2], digest[ 3], + digest[ 4], digest[ 5], digest[ 6], digest[ 7], + digest[ 8], digest[ 9], digest[10], digest[11], + digest[12], digest[13], digest[14], digest[15]); +} + diff --git a/misc/src/ladmin/md5calc.h b/misc/src/ladmin/md5calc.h new file mode 100644 index 0000000..b4dd614 --- /dev/null +++ b/misc/src/ladmin/md5calc.h @@ -0,0 +1,8 @@ +// $Id: md5calc.h,v 1.1.1.1 2004/09/10 17:26:53 MagicalTux Exp $ +#ifndef _MD5CALC_H_ +#define _MD5CALC_H_ + +void MD5_String(const char * string, char * output); +void MD5_String2binary(const char * string, char * output); + +#endif diff --git a/misc/src/login/GNUmakefile b/misc/src/login/GNUmakefile new file mode 100644 index 0000000..df6cb21 --- /dev/null +++ b/misc/src/login/GNUmakefile @@ -0,0 +1,13 @@ +all: login-server
+txt: login-server
+
+COMMON_OBJ = ../common/core.o ../common/socket.o ../common/timer.o ../common/db.o ../common/lock.o ../common/malloc.o
+COMMON_H = ../common/core.h ../common/socket.h ../common/timer.h ../common/mmo.h ../common/version.h ../common/db.h ../common/lock.h ../common/malloc.h
+
+login-server: login.o md5calc.o $(COMMON_OBJ)
+ $(CC) -o ../../$@ login.o md5calc.o $(COMMON_OBJ)
+login.o: login.c login.h md5calc.h $(COMMON_H)
+md5calc.o: md5calc.c md5calc.h
+
+clean:
+ rm -f *.o ../../login-server
diff --git a/misc/src/login/Makefile b/misc/src/login/Makefile new file mode 100644 index 0000000..df6cb21 --- /dev/null +++ b/misc/src/login/Makefile @@ -0,0 +1,13 @@ +all: login-server
+txt: login-server
+
+COMMON_OBJ = ../common/core.o ../common/socket.o ../common/timer.o ../common/db.o ../common/lock.o ../common/malloc.o
+COMMON_H = ../common/core.h ../common/socket.h ../common/timer.h ../common/mmo.h ../common/version.h ../common/db.h ../common/lock.h ../common/malloc.h
+
+login-server: login.o md5calc.o $(COMMON_OBJ)
+ $(CC) -o ../../$@ login.o md5calc.o $(COMMON_OBJ)
+login.o: login.c login.h md5calc.h $(COMMON_H)
+md5calc.o: md5calc.c md5calc.h
+
+clean:
+ rm -f *.o ../../login-server
diff --git a/misc/src/login/login.c b/misc/src/login/login.c new file mode 100644 index 0000000..8d9818d --- /dev/null +++ b/misc/src/login/login.c @@ -0,0 +1,3698 @@ +// $Id: login.c,v 1.1.1.1 2004/09/10 17:26:53 MagicalTux Exp $ +// new version of the login-server by [Yor] + +#include <sys/types.h> +#include <sys/socket.h> +#include <stdio.h> +#include <stdlib.h> +#include <netinet/in.h> +#include <sys/time.h> +#include <time.h> +#include <sys/ioctl.h> +#include <sys/stat.h> // for stat/lstat/fstat +#include <unistd.h> +#include <signal.h> +#include <fcntl.h> +#include <string.h> +#include <arpa/inet.h> +#include <netdb.h> + +#include "core.h" +#include "socket.h" +#include "timer.h" +#include "login.h" +#include "mmo.h" +#include "version.h" +#include "db.h" +#include "lock.h" + +#ifdef PASSWORDENC +#include "md5calc.h" +#endif + +#ifdef MEMWATCH +#include "memwatch.h" +#endif + +int account_id_count = START_ACCOUNT_NUM; +int server_num; +int new_account_flag = 0; +int login_port = 6900; +char lan_char_ip[16]; +int subneti[4]; +int subnetmaski[4]; + +char account_filename[1024] = "save/account.txt"; +char GM_account_filename[1024] = "conf/GM_account.txt"; +char login_log_filename[1024] = "log/login.log"; +char login_log_unknown_packets_filename[1024] = "log/login_unknown_packets.log"; +char date_format[32] = "%Y-%m-%d %H:%M:%S"; +int save_unknown_packets = 0; +long creation_time_GM_account_file; +int gm_account_filename_check_timer = 15; // Timer to check if GM_account file has been changed and reload GM account automaticaly (in seconds; default: 15) + +int display_parse_login = 0; // 0: no, 1: yes +int display_parse_admin = 0; // 0: no, 1: yes +int display_parse_fromchar = 0; // 0: no, 1: yes (without packet 0x2714), 2: all packets + +struct mmo_char_server server[MAX_SERVERS]; +int server_fd[MAX_SERVERS]; +int server_freezeflag[MAX_SERVERS]; // Char-server anti-freeze system. Counter. 5 ok, 4...0 freezed +int anti_freeze_enable = 0; +int ANTI_FREEZE_INTERVAL = 15; + +int login_fd; + +enum { + ACO_DENY_ALLOW = 0, + ACO_ALLOW_DENY, + ACO_MUTUAL_FAILTURE, + ACO_STRSIZE = 128, +}; + +int access_order = ACO_DENY_ALLOW; +int access_allownum = 0; +int access_denynum = 0; +char *access_allow = NULL; +char *access_deny = NULL; + +int access_ladmin_allownum = 0; +char *access_ladmin_allow = NULL; + +int min_level_to_connect = 0; // minimum level of player/GM (0: player, 1-99: gm) to connect on the server +int add_to_unlimited_account = 0; // Give possibility or not to adjust (ladmin command: timeadd) the time of an unlimited account. +int start_limited_time = -1; // Starting additional sec from now for the limited time at creation of accounts (-1: unlimited time, 0 or more: additional sec from now) +int check_ip_flag = 1; // It's to check IP of a player between login-server and char-server (part of anti-hacking system) + +struct login_session_data { + int md5keylen; + char md5key[20]; +}; + +#define AUTH_FIFO_SIZE 256 +struct { + int account_id, login_id1, login_id2; + int ip, sex, delflag; +} auth_fifo[AUTH_FIFO_SIZE]; +int auth_fifo_pos = 0; + +struct auth_dat { + int account_id, sex; + char userid[24], pass[24], lastlogin[24]; + int logincount; + int state; // packet 0x006a value + 1 (0: compte OK) + char email[40]; // e-mail (by default: a@a.com) + char error_message[20]; // Message of error code #6 = Your are Prohibited to log in until %s (packet 0x006a) + time_t ban_until_time; // # of seconds 1/1/1970 (timestamp): ban time limit of the account (0 = no ban) + time_t connect_until_time; // # of seconds 1/1/1970 (timestamp): Validity limit of the account (0 = unlimited) + char last_ip[16]; // save of last IP of connection + char memo[255]; // a memo field + int account_reg2_num; + struct global_reg account_reg2[ACCOUNT_REG2_NUM]; +} *auth_dat; + +int auth_num = 0, auth_max = 0; + +// define the number of times that some players must authentify them before to save account file. +// it's just about normal authentification. If an account is created or modified, save is immediatly done. +// An authentification just change last connected IP and date. It already save in log file. +// set minimum auth change before save: +#define AUTH_BEFORE_SAVE_FILE 10 +// set divider of auth_num to found number of change before save +#define AUTH_SAVE_FILE_DIVIDER 50 +int auth_before_save_file = 0; // Counter. First save when 1st char-server do connection. + +int admin_state = 0; +char admin_pass[24] = ""; +char gm_pass[64] = ""; +int level_new_gm = 60; + +static struct dbt *gm_account_db; + +//------------------------------ +// Writing function of logs file +//------------------------------ +int login_log(char *fmt, ...) { + FILE *logfp; + va_list ap; + struct timeval tv; + char tmpstr[2048]; + + va_start(ap, fmt); + + logfp = fopen(login_log_filename, "a"); + if (logfp) { + if (fmt[0] == '\0') // jump a line if no message + fprintf(logfp, RETCODE); + else { + gettimeofday(&tv, NULL); + strftime(tmpstr, 24, date_format, localtime(&(tv.tv_sec))); + sprintf(tmpstr + strlen(tmpstr), ".%03d: %s", (int)tv.tv_usec / 1000, fmt); + vfprintf(logfp, tmpstr, ap); + } + fclose(logfp); + } + + va_end(ap); + return 0; +} + +//---------------------------------------------------------------------- +// Determine if an account (id) is a GM account +// and returns its level (or 0 if it isn't a GM account or if not found) +//---------------------------------------------------------------------- +int isGM(int account_id) { + struct gm_account *p; + + p = numdb_search(gm_account_db, account_id); + if (p == NULL) + return 0; + return p->level; +} + +//------------------------------------------------------- +// Reading function of GM accounts file (and their level) +//------------------------------------------------------- +int read_gm_account() { + char line[512]; + struct gm_account *p; + FILE *fp; + int c = 0; + int GM_level; + struct stat file_stat; + + free(gm_account_db); + gm_account_db = numdb_init(); + + // get last modify time/date + if (stat(GM_account_filename, &file_stat)) + creation_time_GM_account_file = 0; // error + else + creation_time_GM_account_file = file_stat.st_mtime; + + if ((fp = fopen(GM_account_filename, "r")) == NULL) { + printf("read_gm_account: GM accounts file [%s] not found.\n", GM_account_filename); + printf(" Actually, there is no GM accounts on the server.\n"); + login_log("read_gm_account: GM accounts file [%s] not found." RETCODE, GM_account_filename); + login_log(" Actually, there is no GM accounts on the server." RETCODE); + return 1; + } + // limited to 4000, because we send information to char-servers (more than 4000 GM accounts???) + // int (id) + int (level) = 8 bytes * 4000 = 32k (limit of packets in windows) + while(fgets(line, sizeof(line)-1, fp) && c < 4000) { + if ((line[0] == '/' && line[1] == '/') || line[0] == '\0' || line[0] == '\n' || line[0] == '\r') + continue; + p = calloc(sizeof(struct gm_account), 1); + if (p == NULL) { + printf("read_gm_account: memory allocation failure (malloc)!\n"); + exit(0); + } + if (sscanf(line, "%d %d", &p->account_id, &p->level) != 2 && sscanf(line, "%d: %d", &p->account_id, &p->level) != 2) + printf("read_gm_account: file [%s], invalid 'id_acount level' format.\n", GM_account_filename); + else if (p->level <= 0) + printf("read_gm_account: file [%s] %dth account (invalid level [0 or negative]: %d).\n", GM_account_filename, c+1, p->level); + else { + if (p->level > 99) { + printf("read_gm_account: file [%s] %dth account (invalid level, but corrected: %d->99).\n", GM_account_filename, c+1, p->level); + p->level = 99; + } + if ((GM_level = isGM(p->account_id)) > 0) { // if it's not a new account + if (GM_level == p->level) + printf("read_gm_account: GM account %d defined twice (same level: %d).\n", p->account_id, p->level); + else + printf("read_gm_account: GM account %d defined twice (levels: %d and %d).\n", p->account_id, GM_level, p->level); + } + if (GM_level != p->level) { // if new account or new level + numdb_insert(gm_account_db, p->account_id, p); + //printf("GM account:%d, level: %d->%d\n", p->account_id, GM_level, p->level); + if (GM_level == 0) { // if new account + c++; + if (c >= 4000) { + printf("***WARNING: 4000 GM accounts found. Next GM accounts are not readed.\n"); + login_log("***WARNING: 4000 GM accounts found. Next GM accounts are not readed." RETCODE); + } + } + } + } + } + fclose(fp); + + printf("read_gm_account: file '%s' readed (%d GM accounts found).\n", GM_account_filename, c); + login_log("read_gm_account: file '%s' readed (%d GM accounts found)." RETCODE, GM_account_filename, c); + + return 0; +} + +//-------------------------------------------------------------- +// Test of the IP mask +// (ip: IP to be tested, str: mask x.x.x.x/# or x.x.x.x/y.y.y.y) +//-------------------------------------------------------------- +int check_ipmask(unsigned int ip, const unsigned char *str) { + unsigned int mask = 0, i = 0, m, ip2, a0, a1, a2, a3; + unsigned char *p = (unsigned char *)&ip2, *p2 = (unsigned char *)&mask; + + if (sscanf(str, "%d.%d.%d.%d/%n", &a0, &a1, &a2, &a3, &i) != 4 || i == 0) + return 0; + p[0] = a0; p[1] = a1; p[2] = a2; p[3] = a3; + + if (sscanf(str+i, "%d.%d.%d.%d", &a0, &a1, &a2, &a3) == 4) { + p2[0] = a0; p2[1] = a1; p2[2] = a2; p2[3] = a3; + mask = ntohl(mask); + } else if (sscanf(str+i, "%d", &m) == 1 && m >= 0 && m <= 32) { + for(i = 0; i < m && i < 32; i++) + mask = (mask >> 1) | 0x80000000; + } else { + printf("check_ipmask: invalid mask [%s].\n", str); + return 0; + } + +// printf("Tested IP: %08x, network: %08x, network mask: %08x\n", +// (unsigned int)ntohl(ip), (unsigned int)ntohl(ip2), (unsigned int)mask); + return ((ntohl(ip) & mask) == (ntohl(ip2) & mask)); +} + +//--------------------- +// Access control by IP +//--------------------- +int check_ip(unsigned int ip) { + int i; + unsigned char *p = (unsigned char *)&ip; + char buf[32]; + enum { ACF_DEF, ACF_ALLOW, ACF_DENY } flag = ACF_DEF; + + if (access_allownum == 0 && access_denynum == 0) + return 1; // When there is no restriction, all IP are authorised. + +// + 012.345.: front match form, or +// all: all IP are matched, or +// 012.345.678.901/24: network form (mask with # of bits), or +// 012.345.678.901/255.255.255.0: network form (mask with ip mask) +// + Note about the DNS resolution (like www.ne.jp, etc.): +// There is no guarantee to have an answer. +// If we have an answer, there is no guarantee to have a 100% correct value. +// And, the waiting time (to check) can be long (over 1 minute to a timeout). That can block the software. +// So, DNS notation isn't authorised for ip checking. + sprintf(buf, "%d.%d.%d.%d.", p[0], p[1], p[2], p[3]); + + for(i = 0; i < access_allownum; i++) { + const char *p = access_allow + i * ACO_STRSIZE; + if (memcmp(p, buf, strlen(p)) == 0 || check_ipmask(ip, p)) { + flag = ACF_ALLOW; + if (access_order == ACO_ALLOW_DENY) + return 1; // With 'allow, deny' (deny if not allow), allow has priority + break; + } + } + + for(i = 0; i < access_denynum; i++) { + const char *p = access_deny + i * ACO_STRSIZE; + if (memcmp(p, buf, strlen(p)) == 0 || check_ipmask(ip, p)) { + flag = ACF_DENY; + return 0; // At this point, if it's 'deny', we refuse connection. + break; + } + } + + return (flag == ACF_ALLOW || access_order == ACO_DENY_ALLOW) ? 1:0; + // With 'mutual-failture', only 'allow' and non 'deny' IP are authorised. + // A non 'allow' (even non 'deny') IP is not authorised. It's like: if allowed and not denied, it's authorised. + // So, it's disapproval if you have no description at the time of 'mutual-failture'. + // With 'deny,allow' (allow if not deny), because here it's not deny, we authorise. +} + +//-------------------------------- +// Access control by IP for ladmin +//-------------------------------- +int check_ladminip(unsigned int ip) { + int i; + unsigned char *p = (unsigned char *)&ip; + char buf[32]; + + if (access_ladmin_allownum == 0) + return 1; // When there is no restriction, all IP are authorised. + +// + 012.345.: front match form, or +// all: all IP are matched, or +// 012.345.678.901/24: network form (mask with # of bits), or +// 012.345.678.901/255.255.255.0: network form (mask with ip mask) +// + Note about the DNS resolution (like www.ne.jp, etc.): +// There is no guarantee to have an answer. +// If we have an answer, there is no guarantee to have a 100% correct value. +// And, the waiting time (to check) can be long (over 1 minute to a timeout). That can block the software. +// So, DNS notation isn't authorised for ip checking. + sprintf(buf, "%d.%d.%d.%d.", p[0], p[1], p[2], p[3]); + + for(i = 0; i < access_ladmin_allownum; i++) { + const char *p = access_ladmin_allow + i * ACO_STRSIZE; + if (memcmp(p, buf, strlen(p)) == 0 || check_ipmask(ip, p)) { + return 1; + } + } + + return 0; +} + +//----------------------------------------------------- +// Function to suppress control characters in a string. +//----------------------------------------------------- +int remove_control_chars(unsigned char *str) { + int i; + int change = 0; + + for(i = 0; str[i]; i++) { + if (str[i] < 32) { + str[i] = '_'; + change = 1; + } + } + + return change; +} + +//--------------------------------------------------- +// E-mail check: return 0 (not correct) or 1 (valid). +//--------------------------------------------------- +int e_mail_check(unsigned char *email) { + char ch; + unsigned char* last_arobas; + + // athena limits + if (strlen(email) < 3 || strlen(email) > 39) + return 0; + + // part of RFC limits (official reference of e-mail description) + if (strchr(email, '@') == NULL || email[strlen(email)-1] == '@') + return 0; + + if (email[strlen(email)-1] == '.') + return 0; + + last_arobas = strrchr(email, '@'); + + if (strstr(last_arobas, "@.") != NULL || + strstr(last_arobas, "..") != NULL) + return 0; + + for(ch = 1; ch < 32; ch++) { + if (strchr(last_arobas, ch) != NULL) { + return 0; + break; + } + } + + if (strchr(last_arobas, ' ') != NULL || + strchr(last_arobas, ';') != NULL) + return 0; + + // all correct + return 1; +} + +//----------------------------------------------- +// Search an account id +// (return account index or -1 (if not found)) +// If exact account name is not found, +// the function checks without case sensitive +// and returns index if only 1 account is found +// and similar to the searched name. +//----------------------------------------------- +int search_account_index(char* account_name) { + int i, quantity, index; + + quantity = 0; + index = -1; + for(i = 0; i < auth_num; i++) { + // Without case sensitive check (increase the number of similar account names found) + if (stricmp(auth_dat[i].userid, account_name) == 0) { + // Strict comparison (if found, we finish the function immediatly with correct value) + if (strcmp(auth_dat[i].userid, account_name) == 0) + return i; + quantity++; + index = i; + } + } + // Here, the exact account name is not found + // We return the found index of a similar account ONLY if there is 1 similar account + if (quantity == 1) + return index; + + // Exact account name is not found and 0 or more than 1 similar accounts have been found ==> we say not found + return -1; +} + +//-------------------------------------------------------- +// Create a string to save the account in the account file +//-------------------------------------------------------- +int mmo_auth_tostr(char *str, struct auth_dat *p) { + int i; + char *str_p = str; + + str_p += sprintf(str_p, "%d\t%s\t%s\t%s\t%c\t%d\t%d\t" + "%s\t%s\t%ld\t%s\t%s\t%ld\t", + p->account_id, p->userid, p->pass, p->lastlogin, + (p->sex == 2) ? 'S' : (p->sex ? 'M' : 'F'), + p->logincount, p->state, + p->email, p->error_message, + p->connect_until_time, p->last_ip, p->memo, p->ban_until_time); + + for(i = 0; i < p->account_reg2_num; i++) + if (p->account_reg2[i].str[0]) + str_p += sprintf(str_p, "%s,%d ", p->account_reg2[i].str, p->account_reg2[i].value); + + return 0; +} + +//--------------------------------- +// Reading of the accounts database +//--------------------------------- +int mmo_auth_init(void) { + FILE *fp; + int account_id, logincount, state, n, i, j, v; + char line[2048], *p, userid[2048], pass[2048], lastlogin[2048], sex, email[2048], error_message[2048], last_ip[2048], memo[2048]; + time_t ban_until_time; + time_t connect_until_time; + char str[2048]; + int GM_count = 0; + int server_count = 0; + + auth_dat = calloc(sizeof(struct auth_dat) * 256, 1); + auth_max = 256; + + fp = fopen(account_filename, "r"); + if (fp == NULL) { + // no account file -> no account -> no login, including char-server (ERROR) + printf("\033[1;31mmmo_auth_init: Accounts file [%s] not found.\033[0m\n", account_filename); + return 0; + } + + while(fgets(line, sizeof(line)-1, fp) != NULL) { + if (line[0] == '/' && line[1] == '/') + continue; + line[sizeof(line)-1] = '\0'; + p = line; + + // database version reading (v2) + if (((i = sscanf(line, "%d\t%[^\t]\t%[^\t]\t%[^\t]\t%c\t%d\t%d\t" + "%[^\t]\t%[^\t]\t%ld\t%[^\t]\t%[^\t]\t%ld%n", + &account_id, userid, pass, lastlogin, &sex, &logincount, &state, + email, error_message, &connect_until_time, last_ip, memo, &ban_until_time, &n)) == 13 && line[n] == '\t') || + ((i = sscanf(line, "%d\t%[^\t]\t%[^\t]\t%[^\t]\t%c\t%d\t%d\t" + "%[^\t]\t%[^\t]\t%ld\t%[^\t]\t%[^\t]%n", + &account_id, userid, pass, lastlogin, &sex, &logincount, &state, + email, error_message, &connect_until_time, last_ip, memo, &n)) == 12 && line[n] == '\t')) { + n = n + 1; + + // Some checks + if (account_id > END_ACCOUNT_NUM) { + printf("\033[1;31mmmo_auth_init: ******Error: an account has an id higher than %d\n", END_ACCOUNT_NUM); + printf(" account id #%d -> account not read (saved in log file).\033[0m\n", account_id); + login_log("mmmo_auth_init: ******Error: an account has an id higher than %d." RETCODE, END_ACCOUNT_NUM); + login_log(" account id #%d -> account not read (saved in next line):" RETCODE, account_id); + login_log("%s", line); + continue; + } + userid[23] = '\0'; + remove_control_chars(userid); + for(j = 0; j < auth_num; j++) { + if (auth_dat[j].account_id == account_id) { + printf("\033[1;31mmmo_auth_init: ******Error: an account has an identical id to another.\n"); + printf(" account id #%d -> new account not read (saved in log file).\033[0m\n", account_id); + login_log("mmmo_auth_init: ******Error: an account has an identical id to another." RETCODE); + login_log(" account id #%d -> new account not read (saved in next line):" RETCODE, account_id); + login_log("%s", line); + break; + } else if (strcmp(auth_dat[j].userid, userid) == 0) { + printf("\033[1;31mmmo_auth_init: ******Error: account name already exists.\n"); + printf(" account name '%s' -> new account not read.\n", userid); // 2 lines, account name can be long. + printf(" Account saved in log file.\033[0m\n"); + login_log("mmmo_auth_init: ******Error: an account has an identical id to another." RETCODE); + login_log(" account id #%d -> new account not read (saved in next line):" RETCODE, account_id); + login_log("%s", line); + break; + } + } + if (j != auth_num) + continue; + + if (auth_num >= auth_max) { + auth_max += 256; + auth_dat = realloc(auth_dat, sizeof(struct auth_dat) * auth_max); + } + + memset(&auth_dat[auth_num], '\0', sizeof(struct auth_dat)); + + auth_dat[auth_num].account_id = account_id; + + strncpy(auth_dat[auth_num].userid, userid, 24); + + pass[23] = '\0'; + remove_control_chars(pass); + strncpy(auth_dat[auth_num].pass, pass, 24); + + lastlogin[23] = '\0'; + remove_control_chars(lastlogin); + strncpy(auth_dat[auth_num].lastlogin, lastlogin, 24); + + auth_dat[auth_num].sex = (sex == 'S' || sex == 's') ? 2 : (sex == 'M' || sex == 'm'); + + if (logincount >= 0) + auth_dat[auth_num].logincount = logincount; + else + auth_dat[auth_num].logincount = 0; + + if (state > 255) + auth_dat[auth_num].state = 100; + else if (state < 0) + auth_dat[auth_num].state = 0; + else + auth_dat[auth_num].state = state; + + if (e_mail_check(email) == 0) { + printf("Account %s (%d): invalid e-mail (replaced par a@a.com).\n", auth_dat[auth_num].userid, auth_dat[auth_num].account_id); + strncpy(auth_dat[auth_num].email, "a@a.com", 40); + } else { + remove_control_chars(email); + strncpy(auth_dat[auth_num].email, email, 40); + } + + error_message[19] = '\0'; + remove_control_chars(error_message); + if (error_message[0] == '\0' || state != 7) { // 7, because state is packet 0x006a value + 1 + strncpy(auth_dat[auth_num].error_message, "-", 20); + } else { + strncpy(auth_dat[auth_num].error_message, error_message, 20); + } + + if (i == 13) + auth_dat[auth_num].ban_until_time = ban_until_time; + else + auth_dat[auth_num].ban_until_time = 0; + + auth_dat[auth_num].connect_until_time = connect_until_time; + + last_ip[15] = '\0'; + remove_control_chars(last_ip); + strncpy(auth_dat[auth_num].last_ip, last_ip, 16); + + memo[254] = '\0'; + remove_control_chars(memo); + strncpy(auth_dat[auth_num].memo, memo, 255); + + for(j = 0; j < ACCOUNT_REG2_NUM; j++) { + p += n; + if (sscanf(p, "%[^\t,],%d %n", str, &v, &n) != 2) { + // We must check if a str is void. If it's, we can continue to read other REG2. + // Account line will have something like: str2,9 ,9 str3,1 (here, ,9 is not good) + if (p[0] == ',' && sscanf(p, ",%d %n", &v, &n) == 1) { + j--; + continue; + } else + break; + } + str[31] = '\0'; + remove_control_chars(str); + strncpy(auth_dat[auth_num].account_reg2[j].str, str, 32); + auth_dat[auth_num].account_reg2[j].value = v; + } + auth_dat[auth_num].account_reg2_num = j; + + if (isGM(account_id) > 0) + GM_count++; + if (auth_dat[auth_num].sex == 2) + server_count++; + + auth_num++; + if (account_id >= account_id_count) + account_id_count = account_id + 1; + + // Old athena database version reading (v1) + } else if ((i = sscanf(line, "%d\t%[^\t]\t%[^\t]\t%[^\t]\t%c\t%d\t%d\t%n", + &account_id, userid, pass, lastlogin, &sex, &logincount, &state, &n)) >= 5) { + if (account_id > END_ACCOUNT_NUM) { + printf("\033[1;31mmmo_auth_init: ******Error: an account has an id higher than %d\n", END_ACCOUNT_NUM); + printf(" account id #%d -> account not read (saved in log file).\033[0m\n", account_id); + login_log("mmmo_auth_init: ******Error: an account has an id higher than %d." RETCODE, END_ACCOUNT_NUM); + login_log(" account id #%d -> account not read (saved in next line):" RETCODE, account_id); + login_log("%s", line); + continue; + } + userid[23] = '\0'; + remove_control_chars(userid); + for(j = 0; j < auth_num; j++) { + if (auth_dat[j].account_id == account_id) { + printf("\033[1;31mmmo_auth_init: ******Error: an account has an identical id to another.\n"); + printf(" account id #%d -> new account not read (saved in log file).\033[0m\n", account_id); + login_log("mmmo_auth_init: ******Error: an account has an identical id to another." RETCODE); + login_log(" account id #%d -> new account not read (saved in next line):" RETCODE, account_id); + login_log("%s", line); + break; + } else if (strcmp(auth_dat[j].userid, userid) == 0) { + printf("\033[1;31mmmo_auth_init: ******Error: account name already exists.\n"); + printf(" account name '%s' -> new account not read.\n", userid); // 2 lines, account name can be long. + printf(" Account saved in log file.\033[0m\n"); + login_log("mmmo_auth_init: ******Error: an account has an identical id to another." RETCODE); + login_log(" account id #%d -> new account not read (saved in next line):" RETCODE, account_id); + login_log("%s", line); + break; + } + } + if (j != auth_num) + continue; + + if (auth_num >= auth_max) { + auth_max += 256; + auth_dat = realloc(auth_dat, sizeof(struct auth_dat) * auth_max); + } + + memset(&auth_dat[auth_num], '\0', sizeof(struct auth_dat)); + + auth_dat[auth_num].account_id = account_id; + + strncpy(auth_dat[auth_num].userid, userid, 24); + + pass[23] = '\0'; + remove_control_chars(pass); + strncpy(auth_dat[auth_num].pass, pass, 24); + + lastlogin[23] = '\0'; + remove_control_chars(lastlogin); + strncpy(auth_dat[auth_num].lastlogin, lastlogin, 24); + + auth_dat[auth_num].sex = (sex == 'S' || sex == 's') ? 2 : (sex == 'M' || sex == 'm'); + + if (i >= 6) { + if (logincount >= 0) + auth_dat[auth_num].logincount = logincount; + else + auth_dat[auth_num].logincount = 0; + } else + auth_dat[auth_num].logincount = 0; + + if (i >= 7) { + if (state > 255) + auth_dat[auth_num].state = 100; + else if (state < 0) + auth_dat[auth_num].state = 0; + else + auth_dat[auth_num].state = state; + } else + auth_dat[auth_num].state = 0; + + // Initialization of new data + strncpy(auth_dat[auth_num].email, "a@a.com", 40); + strncpy(auth_dat[auth_num].error_message, "-", 20); + auth_dat[auth_num].ban_until_time = 0; + auth_dat[auth_num].connect_until_time = 0; + strncpy(auth_dat[auth_num].last_ip, "-", 16); + strncpy(auth_dat[auth_num].memo, "-", 255); + + for(j = 0; j < ACCOUNT_REG2_NUM; j++) { + p += n; + if (sscanf(p, "%[^\t,],%d %n", str, &v, &n) != 2) { + // We must check if a str is void. If it's, we can continue to read other REG2. + // Account line will have something like: str2,9 ,9 str3,1 (here, ,9 is not good) + if (p[0] == ',' && sscanf(p, ",%d %n", &v, &n) == 1) { + j--; + continue; + } else + break; + } + str[31] = '\0'; + remove_control_chars(str); + strncpy(auth_dat[auth_num].account_reg2[j].str, str, 32); + auth_dat[auth_num].account_reg2[j].value = v; + } + auth_dat[auth_num].account_reg2_num = j; + + if (isGM(account_id) > 0) + GM_count++; + if (auth_dat[auth_num].sex == 2) + server_count++; + + auth_num++; + if (account_id >= account_id_count) + account_id_count = account_id + 1; + + } else { + i = 0; + if (sscanf(line, "%d\t%%newid%%\n%n", &account_id, &i) == 1 && + i > 0 && account_id > account_id_count) + account_id_count = account_id; + } + } + fclose(fp); + + if (auth_num == 0) { + printf("mmo_auth_init: No account found in %s.\n", account_filename); + sprintf(line, "No account found in %s.", account_filename); + } else { + if (auth_num == 1) { + printf("mmo_auth_init: 1 account read in %s,\n", account_filename); + sprintf(line, "1 account read in %s,", account_filename); + } else { + printf("mmo_auth_init: %d accounts read in %s,\n", auth_num, account_filename); + sprintf(line, "%d accounts read in %s,", auth_num, account_filename); + } + if (GM_count == 0) { + printf(" of which is no GM account, and "); + sprintf(str, "%s of which is no GM account and", line); + } else if (GM_count == 1) { + printf(" of which is 1 GM account, and "); + sprintf(str, "%s of which is 1 GM account and", line); + } else { + printf(" of which is %d GM accounts, and ", GM_count); + sprintf(str, "%s of which is %d GM accounts and", line, GM_count); + } + if (server_count == 0) { + printf("no server account ('S').\n"); + sprintf(line, "%s no server account ('S').", str); + } else if (server_count == 1) { + printf("1 server account ('S').\n"); + sprintf(line, "%s 1 server account ('S').", str); + } else { + printf("%d server accounts ('S').\n", server_count); + sprintf(line, "%s %d server accounts ('S').", str, server_count); + } + } + login_log("%s" RETCODE, line); + + return 0; +} + +//------------------------------------------ +// Writing of the accounts database file +// (accounts are sorted by id before save) +//------------------------------------------ +void mmo_auth_sync(void) { + FILE *fp; + int i, j, k, lock; + int id[auth_num]; + char line[65536]; + + // Sorting before save + for(i = 0; i < auth_num; i++) { + id[i] = i; + for(j = 0; j < i; j++) { + if (auth_dat[i].account_id < auth_dat[id[j]].account_id) { + for(k = i; k > j; k--) + id[k] = id[k-1]; + id[j] = i; // id[i] + break; + } + } + } + + // Data save + fp = lock_fopen(account_filename, &lock); + if (fp == NULL) + return; + fprintf(fp, "// Accounts file: here are saved all information about the accounts.\n"); + fprintf(fp, "// Structure: ID, account name, password, last login time, sex, # of logins, state, email, error message for state 7, validity time, last (accepted) login ip, memo field, ban timestamp, repeated(register text, register value)\n"); + fprintf(fp, "// Some explanations:\n"); + fprintf(fp, "// account name : between 4 to 23 char for a normal account (standard client can't send less than 4 char).\n"); + fprintf(fp, "// account password: between 4 to 23 char\n"); + fprintf(fp, "// sex : M or F for normal accounts, S for server accounts\n"); + fprintf(fp, "// state : 0: account is ok, 1 to 256: error code of packet 0x006a + 1\n"); + fprintf(fp, "// email : between 3 to 39 char (a@a.com is like no email)\n"); + fprintf(fp, "// error message : text for the state 7: 'Your are Prohibited to login until <text>'. Max 19 char\n"); + fprintf(fp, "// valitidy time : 0: unlimited account, <other value>: date calculated by addition of 1/1/1970 + value (number of seconds since the 1/1/1970)\n"); + fprintf(fp, "// memo field : max 254 char\n"); + fprintf(fp, "// ban time : 0: no ban, <other value>: banned until the date: date calculated by addition of 1/1/1970 + value (number of seconds since the 1/1/1970)\n"); + for(i = 0; i < auth_num; i++) { + k = id[i]; // use of sorted index + if (auth_dat[k].account_id < 0) + continue; + + mmo_auth_tostr(line, &auth_dat[k]); + fprintf(fp, "%s" RETCODE, line); + } + fprintf(fp, "%d\t%%newid%%\n", account_id_count); + + lock_fclose(fp, account_filename, &lock); + + // set new counter to minimum number of auth before save + auth_before_save_file = auth_num / AUTH_SAVE_FILE_DIVIDER; // Re-initialise counter. We have save. + if (auth_before_save_file < AUTH_BEFORE_SAVE_FILE) + auth_before_save_file = AUTH_BEFORE_SAVE_FILE; + + return; +} + +//----------------------------------------------------- +// Check if we must save accounts file or not +// every minute, we check if we must save because we +// have do some authentifications without arrive to +// the minimum of authentifications for the save. +// Note: all other modification of accounts (deletion, +// change of some informations excepted lastip/ +// lastlogintime, creation) are always save +// immediatly and set the minimum of +// authentifications to its initialization value. +//----------------------------------------------------- +int check_auth_sync(int tid, unsigned int tick, int id, int data) { + // we only save if necessary: + // we have do some authentifications without do saving + if (auth_before_save_file < AUTH_BEFORE_SAVE_FILE || + auth_before_save_file < (int)(auth_num / AUTH_SAVE_FILE_DIVIDER)) + mmo_auth_sync(); + + return 0; +} + +//-------------------------------------------------------------------- +// Packet send to all char-servers, except one (wos: without our self) +//-------------------------------------------------------------------- +int charif_sendallwos(int sfd, unsigned char *buf, unsigned int len) { + int i, c; + + for(i = 0, c = 0; i < MAX_SERVERS; i++) { + int fd; + if ((fd = server_fd[i]) >= 0 && fd != sfd) { + memcpy(WFIFOP(fd,0), buf, len); + WFIFOSET(fd, len); + c++; + } + } + return c; +} + +//----------------------------------------------------- +// Send GM accounts to all char-server +//----------------------------------------------------- +void send_GM_accounts() { + int i; + char buf[32000]; + int GM_value; + int len; + + len = 4; + WBUFW(buf,0) = 0x2732; + for(i = 0; i < auth_num; i++) + // send only existing accounts. We can not create a GM account when server is online. + if ((GM_value = isGM(auth_dat[i].account_id)) > 0) { + WBUFL(buf,len) = auth_dat[i].account_id; + WBUFB(buf,len+4) = (unsigned char)GM_value; + len += 5; + } + WBUFW(buf,2) = len; + charif_sendallwos(-1, buf, len); + + return; +} + +//----------------------------------------------------- +// Check if GM file account have been changed +//----------------------------------------------------- +int check_GM_file(int tid, unsigned int tick, int id, int data) { + struct stat file_stat; + long new_time; + + // if we would not check + if (gm_account_filename_check_timer < 1) + return 0; + + // get last modify time/date + if (stat(GM_account_filename, &file_stat)) + new_time = 0; // error + else + new_time = file_stat.st_mtime; + + if (new_time != creation_time_GM_account_file) { + read_gm_account(); + send_GM_accounts(); + } + + return 0; +} + +//------------------------------------- +// Account creation (with e-mail check) +//------------------------------------- +int mmo_auth_new(struct mmo_account* account, char sex, char* email) { + time_t timestamp, timestamp_temp; + struct tm *tmtime; + int i = auth_num; + + if (auth_num >= auth_max) { + auth_max += 256; + auth_dat = realloc(auth_dat, sizeof(struct auth_dat) * auth_max); + } + + memset(&auth_dat[i], '\0', sizeof(struct auth_dat)); + + while (isGM(account_id_count) > 0) + account_id_count++; + + auth_dat[i].account_id = account_id_count++; + + strncpy(auth_dat[i].userid, account->userid, 24); + auth_dat[i].userid[23] = '\0'; + + strncpy(auth_dat[i].pass, account->passwd, 24); + auth_dat[i].pass[23] = '\0'; + + memcpy(auth_dat[i].lastlogin, "-", 2); + + auth_dat[i].sex = (sex == 'M'); + + auth_dat[i].logincount = 0; + + auth_dat[i].state = 0; + + if (e_mail_check(email) == 0) + strncpy(auth_dat[i].email, "a@a.com", 40); + else + strncpy(auth_dat[i].email, email, 40); + + strncpy(auth_dat[i].error_message, "-", 20); + + auth_dat[i].ban_until_time = 0; + + if (start_limited_time < 0) + auth_dat[i].connect_until_time = 0; // unlimited + else { // limited time + timestamp = time(NULL) + start_limited_time; + // double conversion to be sure that it is possible + tmtime = localtime(×tamp); + timestamp_temp = mktime(tmtime); + if (timestamp_temp != -1 && (timestamp_temp + 3600) >= timestamp) // check possible value and overflow (and avoid summer/winter hour) + auth_dat[i].connect_until_time = timestamp_temp; + else + auth_dat[i].connect_until_time = 0; // unlimited + } + + strncpy(auth_dat[i].last_ip, "-", 16); + + strncpy(auth_dat[i].memo, "-", 255); + + auth_dat[i].account_reg2_num = 0; + + auth_num++; + + return (account_id_count - 1); +} + +//--------------------------------------- +// Check/authentification of a connection +//--------------------------------------- +int mmo_auth(struct mmo_account* account, int fd) { + int i; + struct timeval tv; + char tmpstr[256]; + int len, newaccount = 0; + char md5str[64], md5bin[32]; + char ip[16]; + unsigned char *sin_addr = (unsigned char *)&session[fd]->client_addr.sin_addr; + + sprintf(ip, "%d.%d.%d.%d", sin_addr[0], sin_addr[1], sin_addr[2], sin_addr[3]); + + len = strlen(account->userid) - 2; + // Account creation with _M/_F + if (account->passwdenc == 0 && account->userid[len] == '_' && + (account->userid[len+1] == 'F' || account->userid[len+1] == 'M') && new_account_flag == 1 && + account_id_count <= END_ACCOUNT_NUM && len >= 4 && strlen(account->passwd) >= 4) { + if (new_account_flag == 1) + newaccount = 1; + account->userid[len] = '\0'; + } + + // Strict account search + for(i = 0; i < auth_num; i++) { + if (strcmp(account->userid, auth_dat[i].userid) == 0) + break; + } + // if there is no creation request and strict account search fails, we do a no sensitive case research for index + if (newaccount == 0 && i == auth_num) { + i = search_account_index(account->userid); + if (i == -1) + i = auth_num; + else + memcpy(account->userid, auth_dat[i].userid, 24); // for the possible tests/checks afterwards (copy correcte sensitive case). + } + + if (i != auth_num) { + int encpasswdok = 0; + struct login_session_data *ld; + if (newaccount) { + login_log("Attempt of creation of an already existant account (account: %s_%c, pass: %s, received pass: %s, ip: %s)" RETCODE, + account->userid, account->userid[len+1], auth_dat[i].pass, account->passwd, ip); + return 9; // 9 = Account already exists + } + ld = session[fd]->session_data; +#ifdef PASSWORDENC + if (account->passwdenc > 0) { + int j = account->passwdenc; + if (!ld) { + login_log("Md5 key not created (account: %s, ip: %s)" RETCODE, account->userid, ip); + return 1; // 1 = Incorrect Password + } + if (j > 2) + j = 1; + do { + if (j == 1) { + strncpy(md5str, ld->md5key, sizeof(ld->md5key)); // 20 + strcat(md5str, auth_dat[i].pass); // 24 + } else if (j == 2) { + strncpy(md5str, auth_dat[i].pass, sizeof(auth_dat[i].pass)); // 24 + strcat(md5str, ld->md5key); // 20 + } else + md5str[0] = '\0'; + md5str[sizeof(md5str)-1] = '\0'; // 64 + MD5_String2binary(md5str, md5bin); + encpasswdok = (memcmp(account->passwd, md5bin, 16) == 0); + } while (j < 2 && !encpasswdok && (j++) != account->passwdenc); +// printf("key[%s] md5 [%s] ", md5key, md5); +// printf("client [%s] accountpass [%s]\n", account->passwd, auth_dat[i].pass); + } +#endif + if ((strcmp(account->passwd, auth_dat[i].pass) && !encpasswdok)) { + if (account->passwdenc == 0) + login_log("Invalid password (account: %s, pass: %s, received pass: %s, ip: %s)" RETCODE, account->userid, auth_dat[i].pass, account->passwd, ip); +#ifdef PASSWORDENC + else { + char logbuf[512], *p = logbuf; + int j; + p += sprintf(p, "Invalid password (account: %s, received md5[", account->userid); + for(j = 0; j < 16; j++) + p += sprintf(p, "%02x", ((unsigned char *)account->passwd)[j]); + p += sprintf(p,"] calculated md5["); + for(j = 0; j < 16; j++) + p += sprintf(p, "%02x", ((unsigned char *)md5bin)[j]); + p += sprintf(p, "] md5 key["); + for(j = 0; j < ld->md5keylen; j++) + p += sprintf(p, "%02x", ((unsigned char *)ld->md5key)[j]); + p += sprintf(p, "], ip: %s)" RETCODE, ip); + login_log(logbuf); + } +#endif + return 1; // 1 = Incorrect Password + } + + if (auth_dat[i].state) { + login_log("Connection refused (account: %s, pass: %s, state: %d, ip: %s)" RETCODE, + account->userid, account->passwd, auth_dat[i].state, ip); + switch(auth_dat[i].state) { // packet 0x006a value + 1 + case 1: // 0 = Unregistered ID + case 2: // 1 = Incorrect Password + case 3: // 2 = This ID is expired + case 4: // 3 = Rejected from Server + case 5: // 4 = You have been blocked by the GM Team + case 6: // 5 = Your Game's EXE file is not the latest version + case 7: // 6 = Your are Prohibited to log in until %s + case 8: // 7 = Server is jammed due to over populated + case 9: // 8 = No MSG (actually, all states after 9 except 99 are No MSG, use only this) + case 100: // 99 = This ID has been totally erased + return auth_dat[i].state - 1; + break; + default: + return 99; // 99 = ID has been totally erased + break; + } + } + + if (auth_dat[i].ban_until_time != 0) { // if account is banned + strftime(tmpstr, 20, date_format, localtime(&auth_dat[i].ban_until_time)); + tmpstr[19] = '\0'; + if (auth_dat[i].ban_until_time > time(NULL)) { // always banned + login_log("Connection refused (account: %s, pass: %s, banned until %s, ip: %s)" RETCODE, + account->userid, account->passwd, tmpstr, ip); + return 6; // 6 = Your are Prohibited to log in until %s + } else { // ban is finished + login_log("End of ban (account: %s, pass: %s, previously banned until %s -> not more banned, ip: %s)" RETCODE, + account->userid, account->passwd, tmpstr, ip); + auth_dat[i].ban_until_time = 0; // reset the ban time + } + } + + if (auth_dat[i].connect_until_time != 0 && auth_dat[i].connect_until_time < time(NULL)) { + login_log("Connection refused (account: %s, pass: %s, expired ID, ip: %s)" RETCODE, + account->userid, account->passwd, ip); + return 2; // 2 = This ID is expired + } + + login_log("Authentification accepted (account: %s (id: %d), ip: %s)" RETCODE, account->userid, auth_dat[i].account_id, ip); + } else { + if (newaccount == 0) { + login_log("Unknown account (account: %s, received pass: %s, ip: %s)" RETCODE, + account->userid, account->passwd, ip); + return 0; // 0 = Unregistered ID + } else { + int new_id = mmo_auth_new(account, account->userid[len+1], "a@a.com"); + login_log("Account creation and authentification accepted (account %s (id: %d), pass: %s, sex: %c, connection with _F/_M, ip: %s)" RETCODE, + account->userid, new_id, account->passwd, account->userid[len+1], ip); + auth_before_save_file = 0; // Creation of an account -> save accounts file immediatly + } + } + + gettimeofday(&tv, NULL); + strftime(tmpstr, 24, date_format, localtime(&(tv.tv_sec))); + sprintf(tmpstr + strlen(tmpstr), ".%03d", (int)tv.tv_usec / 1000); + + account->account_id = auth_dat[i].account_id; + account->login_id1 = rand(); + account->login_id2 = rand(); + memcpy(account->lastlogin, auth_dat[i].lastlogin, 24); + memcpy(auth_dat[i].lastlogin, tmpstr, 24); + account->sex = auth_dat[i].sex; + strncpy(auth_dat[i].last_ip, ip, 16); + auth_dat[i].logincount++; + + // Save until for change ip/time of auth is not very useful => limited save for that + // Save there informations isnot necessary, because they are saved in log file. + if (--auth_before_save_file <= 0) // Reduce counter. 0 or less, we save + mmo_auth_sync(); + + return -1; // account OK +} + +//------------------------------- +// Char-server anti-freeze system +//------------------------------- +int char_anti_freeze_system(int tid, unsigned int tick, int id, int data) { + int i; + + //printf("Entering in char_anti_freeze_system function to check freeze of servers.\n"); + for(i = 0; i < MAX_SERVERS; i++) { + if (server_fd[i] >= 0) {// if char-server is online + //printf("char_anti_freeze_system: server #%d '%s', flag: %d.\n", i, server[i].name, server_freezeflag[i]); + if (server_freezeflag[i]-- < 1) { // Char-server anti-freeze system. Counter. 5 ok, 4...0 freezed + printf("Char-server anti-freeze system: char-server #%d '%s' is freezed -> disconnection.\n", i, server[i].name); + login_log("Char-server anti-freeze system: char-server #%d '%s' is freezed -> disconnection." RETCODE, + i, server[i].name); + session[server_fd[i]]->eof = 1; + } + } + } + + return 0; +} + +//-------------------------------- +// Packet parsing for char-servers +//-------------------------------- +int parse_fromchar(int fd) { + int i, j, id; + unsigned char *p = (unsigned char *) &session[fd]->client_addr.sin_addr; + char ip[16]; + + sprintf(ip, "%d.%d.%d.%d", p[0], p[1], p[2], p[3]); + + for(id = 0; id < MAX_SERVERS; id++) + if (server_fd[id] == fd) + break; + if (id == MAX_SERVERS || session[fd]->eof) { + if (id < MAX_SERVERS) { + printf("Char-server '%s' has disconnected.\n", server[id].name); + login_log("Char-server '%s' has disconnected (ip: %s)." RETCODE, + server[id].name, ip); + server_fd[id] = -1; + memset(&server[id], 0, sizeof(struct mmo_char_server)); + } + close(fd); + delete_session(fd); + return 0; + } + + while (RFIFOREST(fd) >= 2) { + if (display_parse_fromchar == 2 || (display_parse_fromchar == 1 && RFIFOW(fd,0) != 0x2714)) // 0x2714 is done very often (number of players) + printf("parse_fromchar: connection #%d, packet: 0x%x (with being read: %d bytes).\n", fd, RFIFOW(fd,0), RFIFOREST(fd)); + + switch (RFIFOW(fd,0)) { + // request from map-server via char-server to reload GM accounts (by Yor). + case 0x2709: + login_log("Char-server '%s': Request to re-load GM configuration file (ip: %s)." RETCODE, server[id].name, ip); + read_gm_account(); + // send GM accounts to all char-servers + send_GM_accounts(); + RFIFOSKIP(fd,2); + break; + + case 0x2712: // request from char-server to authentify an account + if (RFIFOREST(fd) < 19) + return 0; + { + int acc; + acc = RFIFOL(fd,2); // speed up + for(i = 0; i < AUTH_FIFO_SIZE; i++) { + if (auth_fifo[i].account_id == acc && + auth_fifo[i].login_id1 == RFIFOL(fd,6) && +#if CMP_AUTHFIFO_LOGIN2 != 0 + auth_fifo[i].login_id2 == RFIFOL(fd,10) && // relate to the versions higher than 18 +#endif + auth_fifo[i].sex == RFIFOB(fd,14) && + (!check_ip_flag || auth_fifo[i].ip == RFIFOL(fd,15)) && + !auth_fifo[i].delflag) { + int p, k; + auth_fifo[i].delflag = 1; + login_log("Char-server '%s': authentification of the account %d accepted (ip: %s)." RETCODE, + server[id].name, acc, ip); +// printf("%d\n", i); + for(k = 0; k < auth_num; k++) { + if (auth_dat[k].account_id == acc) { + WFIFOW(fd,0) = 0x2729; // Sending of the account_reg2 + WFIFOL(fd,4) = acc; + for(p = 8, j = 0; j < auth_dat[k].account_reg2_num; p += 36, j++) { + memcpy(WFIFOP(fd,p), auth_dat[k].account_reg2[j].str, 32); + WFIFOL(fd,p+32) = auth_dat[k].account_reg2[j].value; + } + WFIFOW(fd,2) = p; + WFIFOSET(fd,p); +// printf("parse_fromchar: Sending of account_reg2: login->char (auth fifo)\n"); + WFIFOW(fd,0) = 0x2713; + WFIFOL(fd,2) = acc; + WFIFOB(fd,6) = 0; + memcpy(WFIFOP(fd, 7), auth_dat[k].email, 40); + WFIFOL(fd,47) = (unsigned long)auth_dat[k].connect_until_time; + WFIFOSET(fd,51); + break; + } + } + break; + } + } + // authentification not found + if (i == AUTH_FIFO_SIZE) { + login_log("Char-server '%s': authentification of the account %d REFUSED (ip: %s)." RETCODE, + server[id].name, acc, ip); + WFIFOW(fd,0) = 0x2713; + WFIFOL(fd,2) = acc; + WFIFOB(fd,6) = 1; + // It is unnecessary to send email + // It is unnecessary to send validity date of the account + WFIFOSET(fd,51); + } + } + RFIFOSKIP(fd,19); + break; + + case 0x2714: + if (RFIFOREST(fd) < 6) + return 0; + //printf("parse_fromchar: Receiving of the users number of the server '%s': %d\n", server[id].name, RFIFOL(fd,2)); + server[id].users = RFIFOL(fd,2); + if(anti_freeze_enable) + server_freezeflag[id] = 5; // Char anti-freeze system. Counter. 5 ok, 4...0 freezed + RFIFOSKIP(fd,6); + break; + + // we receive a e-mail creation of an account with a default e-mail (no answer) + case 0x2715: { + int acc; + char email[40]; + if (RFIFOREST(fd) < 46) + return 0; + acc = RFIFOL(fd,2); // speed up + memcpy(email, RFIFOP(fd,6), 40); + email[39] = '\0'; + remove_control_chars(email); + //printf("parse_fromchar: an e-mail creation of an account with a default e-mail: server '%s', account: %d, e-mail: '%s'.\n", server[id].name, acc, RFIFOP(fd,6)); + if (e_mail_check(email) == 0) + login_log("Char-server '%s': Attempt to create an e-mail on an account with a default e-mail REFUSED - e-mail is invalid (account: %d, ip: %s)" RETCODE, + server[id].name, acc, ip); + else { + for(i = 0; i < auth_num; i++) { + if (auth_dat[i].account_id == acc && (strcmp(auth_dat[i].email, "a@a.com") == 0 || auth_dat[i].email[0] == '\0')) { + memcpy(auth_dat[i].email, email, 40); + login_log("Char-server '%s': Create an e-mail on an account with a default e-mail (account: %d, new e-mail: %s, ip: %s)." RETCODE, + server[id].name, acc, email, ip); + // Save + mmo_auth_sync(); + break; + } + } + if (i == auth_num) + login_log("Char-server '%s': Attempt to create an e-mail on an account with a default e-mail REFUSED - account doesn't exist or e-mail of account isn't default e-mail (account: %d, ip: %s)." RETCODE, + server[id].name, acc, ip); + } + RFIFOSKIP(fd,46); + break; + + // We receive an e-mail/limited time request, because a player comes back from a map-server to the char-server + } + case 0x2716: + if (RFIFOREST(fd) < 6) + return 0; + //printf("parse_fromchar: E-mail/limited time request from '%s' server (concerned account: %d)\n", server[id].name, RFIFOL(fd,2)); + for(i = 0; i < auth_num; i++) { + if (auth_dat[i].account_id == RFIFOL(fd,2)) { + login_log("Char-server '%s': e-mail of the account %d found (ip: %s)." RETCODE, + server[id].name, RFIFOL(fd,2), ip); + WFIFOW(fd,0) = 0x2717; + WFIFOL(fd,2) = RFIFOL(fd,2); + memcpy(WFIFOP(fd, 6), auth_dat[i].email, 40); + WFIFOL(fd,46) = (unsigned long)auth_dat[i].connect_until_time; + WFIFOSET(fd,50); + break; + } + } + if (i == auth_num) { + login_log("Char-server '%s': e-mail of the account %d NOT found (ip: %s)." RETCODE, + server[id].name, RFIFOL(fd,2), ip); + } + RFIFOSKIP(fd,6); + break; + + case 0x2720: // To become GM request + if (RFIFOREST(fd) < 4 || RFIFOREST(fd) < RFIFOW(fd,2)) + return 0; + { + int acc; + unsigned char buf[10]; + FILE *fp; + acc = RFIFOL(fd,4); + //printf("parse_fromchar: Request to become a GM acount from %d account.\n", acc); + WBUFW(buf,0) = 0x2721; + WBUFL(buf,2) = acc; + WBUFL(buf,6) = 0; + if (strcmp(RFIFOP(fd,8), gm_pass) == 0) { + // only non-GM can become GM + if (isGM(acc) == 0) { + // if we autorise creation + if (level_new_gm > 0) { + // if we can open the file to add the new GM + if ((fp = fopen(GM_account_filename, "a")) != NULL) { + char tmpstr[24]; + struct timeval tv; + gettimeofday(&tv, NULL); + strftime(tmpstr, 23, date_format, localtime(&(tv.tv_sec))); + fprintf(fp, RETCODE "// %s: @GM command on account %d" RETCODE "%d %d" RETCODE, tmpstr, acc, acc, level_new_gm); + fclose(fp); + WBUFL(buf,6) = level_new_gm; + read_gm_account(); + send_GM_accounts(); + printf("GM Change of the account %d: level 0 -> %d.\n", acc, level_new_gm); + login_log("Char-server '%s': GM Change of the account %d: level 0 -> %d (ip: %s)." RETCODE, + server[id].name, acc, level_new_gm, ip); + } else { + printf("Error of GM change (suggested account: %d, correct password, unable to add a GM account in GM accounts file)\n", acc); + login_log("Char-server '%s': Error of GM change (suggested account: %d, correct password, unable to add a GM account in GM accounts file, ip: %s)." RETCODE, + server[id].name, acc, ip); + } + } else { + printf("Error of GM change (suggested account: %d, correct password, but GM creation is disable (level_new_gm = 0))\n", acc); + login_log("Char-server '%s': Error of GM change (suggested account: %d, correct password, but GM creation is disable (level_new_gm = 0), ip: %s)." RETCODE, + server[id].name, acc, ip); + } + } else { + printf("Error of GM change (suggested account: %d (already GM), correct password).\n", acc); + login_log("Char-server '%s': Error of GM change (suggested account: %d (already GM), correct password, ip: %s)." RETCODE, + server[id].name, acc, ip); + } + } else { + printf("Error of GM change (suggested account: %d, invalid password).\n", acc); + login_log("Char-server '%s': Error of GM change (suggested account: %d, invalid password, ip: %s)." RETCODE, + server[id].name, acc, ip); + } + charif_sendallwos(-1, buf, 10); + } + RFIFOSKIP(fd, RFIFOW(fd,2)); + return 0; + + // Map server send information to change an email of an account via char-server + case 0x2722: // 0x2722 <account_id>.L <actual_e-mail>.40B <new_e-mail>.40B + if (RFIFOREST(fd) < 86) + return 0; + { + int acc; + char actual_email[40], new_email[40]; + acc = RFIFOL(fd,2); + memcpy(actual_email, RFIFOP(fd,6), 40); + actual_email[39] = '\0'; + remove_control_chars(actual_email); + memcpy(new_email, RFIFOP(fd,46), 40); + new_email[39] = '\0'; + remove_control_chars(new_email); + if (e_mail_check(actual_email) == 0) + login_log("Char-server '%s': Attempt to modify an e-mail on an account (@email GM command), but actual email is invalid (account: %d, ip: %s)" RETCODE, + server[id].name, acc, ip); + else if (e_mail_check(new_email) == 0) + login_log("Char-server '%s': Attempt to modify an e-mail on an account (@email GM command) with a invalid new e-mail (account: %d, ip: %s)" RETCODE, + server[id].name, acc, ip); + else if (strcmpi(new_email, "a@a.com") == 0) + login_log("Char-server '%s': Attempt to modify an e-mail on an account (@email GM command) with a default e-mail (account: %d, ip: %s)" RETCODE, + server[id].name, acc, ip); + else { + for(i = 0; i < auth_num; i++) { + if (auth_dat[i].account_id == acc) { + if (strcmpi(auth_dat[i].email, actual_email) == 0) { + memcpy(auth_dat[i].email, new_email, 40); + login_log("Char-server '%s': Modify an e-mail on an account (@email GM command) (account: %d (%s), new e-mail: %s, ip: %s)." RETCODE, + server[id].name, acc, auth_dat[i].userid, new_email, ip); + // Save + mmo_auth_sync(); + } else + login_log("Char-server '%s': Attempt to modify an e-mail on an account (@email GM command), but actual e-mail is incorrect (account: %d (%s), actual e-mail: %s, proposed e-mail: %s, ip: %s)." RETCODE, + server[id].name, acc, auth_dat[i].userid, auth_dat[i].email, actual_email, ip); + break; + } + } + if (i == auth_num) + login_log("Char-server '%s': Attempt to modify an e-mail on an account (@email GM command), but account doesn't exist (account: %d, ip: %s)." RETCODE, + server[id].name, acc, ip); + } + } + RFIFOSKIP(fd, 86); + break; + + // Receiving of map-server via char-server a status change resquest (by Yor) + case 0x2724: + if (RFIFOREST(fd) < 10) + return 0; + { + int acc, statut; + acc = RFIFOL(fd,2); + statut = RFIFOL(fd,6); + for(i = 0; i < auth_num; i++) { + if (auth_dat[i].account_id == acc) { + if (auth_dat[i].state != statut) { + login_log("Char-server '%s': Status change (account: %d, new status %d, ip: %s)." RETCODE, + server[id].name, acc, statut, ip); + if (statut != 0) { + unsigned char buf[16]; + WBUFW(buf,0) = 0x2731; + WBUFL(buf,2) = acc; + WBUFB(buf,6) = 0; // 0: change of statut, 1: ban + WBUFL(buf,7) = statut; // status or final date of a banishment + charif_sendallwos(-1, buf, 11); + for(j = 0; j < AUTH_FIFO_SIZE; j++) + if (auth_fifo[j].account_id == acc) + auth_fifo[j].login_id1++; // to avoid reconnection error when come back from map-server (char-server will ask again the authentification) + } + auth_dat[i].state = statut; + // Save + mmo_auth_sync(); + } else + login_log("Char-server '%s': Error of Status change - actual status is already the good status (account: %d, status %d, ip: %s)." RETCODE, + server[id].name, acc, statut, ip); + break; + } + } + if (i == auth_num) { + login_log("Char-server '%s': Error of Status change (account: %d not found, suggested status %d, ip: %s)." RETCODE, + server[id].name, acc, statut, ip); + } + RFIFOSKIP(fd,10); + } + return 0; + + case 0x2725: // Receiving of map-server via char-server a ban resquest (by Yor) + if (RFIFOREST(fd) < 18) + return 0; + { + int acc; + acc = RFIFOL(fd,2); + for(i = 0; i < auth_num; i++) { + if (auth_dat[i].account_id == acc) { + time_t timestamp; + struct tm *tmtime; + if (auth_dat[i].ban_until_time == 0 || auth_dat[i].ban_until_time < time(NULL)) + timestamp = time(NULL); + else + timestamp = auth_dat[i].ban_until_time; + tmtime = localtime(×tamp); + tmtime->tm_year = tmtime->tm_year + (short)RFIFOW(fd,6); + tmtime->tm_mon = tmtime->tm_mon + (short)RFIFOW(fd,8); + tmtime->tm_mday = tmtime->tm_mday + (short)RFIFOW(fd,10); + tmtime->tm_hour = tmtime->tm_hour + (short)RFIFOW(fd,12); + tmtime->tm_min = tmtime->tm_min + (short)RFIFOW(fd,14); + tmtime->tm_sec = tmtime->tm_sec + (short)RFIFOW(fd,16); + timestamp = mktime(tmtime); + if (timestamp != -1) { + if (timestamp <= time(NULL)) + timestamp = 0; + if (auth_dat[i].ban_until_time != timestamp) { + if (timestamp != 0) { + unsigned char buf[16]; + char tmpstr[2048]; + strftime(tmpstr, 24, date_format, localtime(×tamp)); + login_log("Char-server '%s': Ban request (account: %d, new final date of banishment: %d (%s), ip: %s)." RETCODE, + server[id].name, acc, timestamp, (timestamp == 0 ? "no banishment" : tmpstr), ip); + WBUFW(buf,0) = 0x2731; + WBUFL(buf,2) = auth_dat[i].account_id; + WBUFB(buf,6) = 1; // 0: change of statut, 1: ban + WBUFL(buf,7) = timestamp; // status or final date of a banishment + charif_sendallwos(-1, buf, 11); + for(j = 0; j < AUTH_FIFO_SIZE; j++) + if (auth_fifo[j].account_id == acc) + auth_fifo[j].login_id1++; // to avoid reconnection error when come back from map-server (char-server will ask again the authentification) + } else { + login_log("Char-server '%s': Error of ban request (account: %d, new date unbans the account, ip: %s)." RETCODE, + server[id].name, acc, ip); + } + auth_dat[i].ban_until_time = timestamp; + // Save + mmo_auth_sync(); + } else { + login_log("Char-server '%s': Error of ban request (account: %d, no change for ban date, ip: %s)." RETCODE, + server[id].name, acc, ip); + } + } else { + login_log("Char-server '%s': Error of ban request (account: %d, invalid date, ip: %s)." RETCODE, + server[id].name, acc, ip); + } + break; + } + } + if (i == auth_num) { + login_log("Char-server '%s': Error of ban request (account: %d not found, ip: %s)." RETCODE, + server[id].name, acc, ip); + } + RFIFOSKIP(fd,18); + } + return 0; + + case 0x2727: // Change of sex (sex is reversed) + if (RFIFOREST(fd) < 6) + return 0; + { + int acc, sex; + acc = RFIFOL(fd,2); + for(i = 0; i < auth_num; i++) { +// printf("%d,", auth_dat[i].account_id); + if (auth_dat[i].account_id == acc) { + if (auth_dat[i].sex == 2) + login_log("Char-server '%s': Error of sex change - Server account (suggested account: %d, actual sex %d (Server), ip: %s)." RETCODE, + server[id].name, acc, auth_dat[i].sex, ip); + else { + unsigned char buf[16]; + if (auth_dat[i].sex == 0) + sex = 1; + else + sex = 0; + login_log("Char-server '%s': Sex change (account: %d, new sex %c, ip: %s)." RETCODE, + server[id].name, acc, (sex == 2) ? 'S' : (sex ? 'M' : 'F'), ip); + for(j = 0; j < AUTH_FIFO_SIZE; j++) + if (auth_fifo[j].account_id == acc) + auth_fifo[j].login_id1++; // to avoid reconnection error when come back from map-server (char-server will ask again the authentification) + auth_dat[i].sex = sex; + WBUFW(buf,0) = 0x2723; + WBUFL(buf,2) = acc; + WBUFB(buf,6) = sex; + charif_sendallwos(-1, buf, 7); + // Save + mmo_auth_sync(); + } + break; + } + } + if (i == auth_num) { + login_log("Char-server '%s': Error of sex change (account: %d not found, sex would be reversed, ip: %s)." RETCODE, + server[id].name, acc, ip); + } + RFIFOSKIP(fd,6); + } + return 0; + + case 0x2728: // We receive account_reg2 from a char-server, and we send them to other char-servers. + if (RFIFOREST(fd) < 4 || RFIFOREST(fd) < RFIFOW(fd,2)) + return 0; + { + int acc, p; + acc = RFIFOL(fd,4); + for(i = 0; i < auth_num; i++) { + if (auth_dat[i].account_id == acc) { + unsigned char buf[RFIFOW(fd,2)+1]; + login_log("Char-server '%s': receiving (from the char-server) of account_reg2 (account: %d, ip: %s)." RETCODE, + server[id].name, acc, ip); + for(p = 8, j = 0; p < RFIFOW(fd,2) && j < ACCOUNT_REG2_NUM; p += 36, j++) { + memcpy(auth_dat[i].account_reg2[j].str, RFIFOP(fd,p), 32); + auth_dat[i].account_reg2[j].str[31] = '\0'; + remove_control_chars(auth_dat[i].account_reg2[j].str); + auth_dat[i].account_reg2[j].value = RFIFOL(fd,p+32); + } + auth_dat[i].account_reg2_num = j; + // Sending information towards the other char-servers. + memcpy(WBUFP(buf,0), RFIFOP(fd,0), RFIFOW(fd,2)); + WBUFW(buf,0) = 0x2729; + charif_sendallwos(fd, buf, WBUFW(buf,2)); + // Save + mmo_auth_sync(); +// printf("parse_fromchar: receiving (from the char-server) of account_reg2 (account id: %d).\n", acc); + break; + } + } + if (i == auth_num) { +// printf("parse_fromchar: receiving (from the char-server) of account_reg2 (unknwon account id: %d).\n", acc); + login_log("Char-server '%s': receiving (from the char-server) of account_reg2 (account: %d not found, ip: %s)." RETCODE, + server[id].name, acc, ip); + } + } + RFIFOSKIP(fd,RFIFOW(fd,2)); + break; + + case 0x272a: // Receiving of map-server via char-server a unban resquest (by Yor) + if (RFIFOREST(fd) < 6) + return 0; + { + int acc; + acc = RFIFOL(fd,2); + for(i = 0; i < auth_num; i++) { + if (auth_dat[i].account_id == acc) { + if (auth_dat[i].ban_until_time != 0) { + auth_dat[i].ban_until_time = 0; + login_log("Char-server '%s': UnBan request (account: %d, ip: %s)." RETCODE, + server[id].name, acc, ip); + } else { + login_log("Char-server '%s': Error of UnBan request (account: %d, no change for unban date, ip: %s)." RETCODE, + server[id].name, acc, ip); + } + break; + } + } + if (i == auth_num) { + login_log("Char-server '%s': Error of UnBan request (account: %d not found, ip: %s)." RETCODE, + server[id].name, acc, ip); + } + RFIFOSKIP(fd,6); + } + return 0; + + default: + { + FILE *logfp; + char tmpstr[24]; + struct timeval tv; + logfp = fopen(login_log_unknown_packets_filename, "a"); + if (logfp) { + gettimeofday(&tv, NULL); + strftime(tmpstr, 23, date_format, localtime(&(tv.tv_sec))); + fprintf(logfp, "%s.%03d: receiving of an unknown packet -> disconnection" RETCODE, tmpstr, (int)tv.tv_usec / 1000); + fprintf(logfp, "parse_fromchar: connection #%d (ip: %s), packet: 0x%x (with being read: %d)." RETCODE, fd, ip, RFIFOW(fd,0), RFIFOREST(fd)); + fprintf(logfp, "Detail (in hex):" RETCODE); + fprintf(logfp, "---- 00-01-02-03-04-05-06-07 08-09-0A-0B-0C-0D-0E-0F" RETCODE); + memset(tmpstr, '\0', sizeof(tmpstr)); + for(i = 0; i < RFIFOREST(fd); i++) { + if ((i & 15) == 0) + fprintf(logfp, "%04X ",i); + fprintf(logfp, "%02x ", RFIFOB(fd,i)); + if (RFIFOB(fd,i) > 0x1f) + tmpstr[i % 16] = RFIFOB(fd,i); + else + tmpstr[i % 16] = '.'; + if ((i - 7) % 16 == 0) // -8 + 1 + fprintf(logfp, " "); + else if ((i + 1) % 16 == 0) { + fprintf(logfp, " %s" RETCODE, tmpstr); + memset(tmpstr, '\0', sizeof(tmpstr)); + } + } + if (i % 16 != 0) { + for(j = i; j % 16 != 0; j++) { + fprintf(logfp, " "); + if ((j - 7) % 16 == 0) // -8 + 1 + fprintf(logfp, " "); + } + fprintf(logfp, " %s" RETCODE, tmpstr); + } + fprintf(logfp, RETCODE); + fclose(logfp); + } + } + printf("parse_fromchar: Unknown packet 0x%x (from a char-server)! -> disconnection.\n", RFIFOW(fd,0)); + session[fd]->eof = 1; + printf("Char-server has been disconnected (unknown packet).\n"); + return 0; + } + } + return 0; +} + +//--------------------------------------- +// Packet parsing for administation login +//--------------------------------------- +int parse_admin(int fd) { + int i, j; + unsigned char *p = (unsigned char *) &session[fd]->client_addr.sin_addr; + char* account_name; + char ip[16]; + + sprintf(ip, "%d.%d.%d.%d", p[0], p[1], p[2], p[3]); + + if (session[fd]->eof) { + close(fd); + delete_session(fd); + printf("Remote administration has disconnected (session #%d).\n", fd); + return 0; + } + + while(RFIFOREST(fd) >= 2) { + if (display_parse_admin == 1) + printf("parse_admin: connection #%d, packet: 0x%x (with being read: %d).\n", fd, RFIFOW(fd,0), RFIFOREST(fd)); + + switch(RFIFOW(fd,0)) { + case 0x7530: // Request of the server version + login_log("'ladmin': Sending of the server version (ip: %s)" RETCODE, ip); + WFIFOW(fd,0) = 0x7531; + WFIFOB(fd,2) = ATHENA_MAJOR_VERSION; + WFIFOB(fd,3) = ATHENA_MINOR_VERSION; + WFIFOB(fd,4) = ATHENA_REVISION; + WFIFOB(fd,5) = ATHENA_RELEASE_FLAG; + WFIFOB(fd,6) = ATHENA_OFFICIAL_FLAG; + WFIFOB(fd,7) = ATHENA_SERVER_LOGIN; + WFIFOW(fd,8) = ATHENA_MOD_VERSION; + WFIFOSET(fd,10); + RFIFOSKIP(fd,2); + break; + + case 0x7532: // Request of end of connection + login_log("'ladmin': End of connection (ip: %s)" RETCODE, ip); + RFIFOSKIP(fd,2); + session[fd]->eof = 1; + break; + + case 0x7920: // Request of an accounts list + if (RFIFOREST(fd) < 10) + return 0; + { + int st, ed, len; + int id[auth_num]; + st = RFIFOL(fd,2); + ed = RFIFOL(fd,6); + RFIFOSKIP(fd,10); + WFIFOW(fd,0) = 0x7921; + if (st < 0) + st = 0; + if (ed > END_ACCOUNT_NUM || ed < st || ed <= 0) + ed = END_ACCOUNT_NUM; + login_log("'ladmin': Sending an accounts list (ask: from %d to %d, ip: %s)" RETCODE, st, ed, ip); + // Sort before send + for(i = 0; i < auth_num; i++) { + int k; + id[i] = i; + for(j = 0; j < i; j++) { + if (auth_dat[id[i]].account_id < auth_dat[id[j]].account_id) { + for(k = i; k > j; k--) { + id[k] = id[k-1]; + } + id[j] = i; // id[i] + break; + } + } + } + // Sending accounts information + len = 4; + for(i = 0; i < auth_num && len < 30000; i++) { + int account_id = auth_dat[id[i]].account_id; // use sorted index + if (account_id >= st && account_id <= ed) { + j = id[i]; + WFIFOL(fd,len) = account_id; + WFIFOB(fd,len+4) = (unsigned char)isGM(account_id); + memcpy(WFIFOP(fd,len+5), auth_dat[j].userid, 24); + WFIFOB(fd,len+29) = auth_dat[j].sex; + WFIFOL(fd,len+30) = auth_dat[j].logincount; + if (auth_dat[j].state == 0 && auth_dat[j].ban_until_time != 0) // if no state and banished + WFIFOL(fd,len+34) = 7; // 6 = Your are Prohibited to log in until %s + else + WFIFOL(fd,len+34) = auth_dat[j].state; + len += 38; + } + } + WFIFOW(fd,2) = len; + WFIFOSET(fd,len); + } + break; + + case 0x7930: // Request for an account creation + if (RFIFOREST(fd) < 91) + return 0; + { + struct mmo_account ma; + ma.userid = RFIFOP(fd, 2); + ma.passwd = RFIFOP(fd, 26); + memcpy(ma.lastlogin, "-", 2); + ma.sex = RFIFOB(fd,50); + WFIFOW(fd,0) = 0x7931; + WFIFOL(fd,2) = -1; + memcpy(WFIFOP(fd,6), RFIFOP(fd,2), 24); + if (strlen(ma.userid) > 23 || strlen(ma.passwd) > 23) { + login_log("'ladmin': Attempt to create an invalid account (account or pass is too long, ip: %s)" RETCODE, + ip); + } else if (strlen(ma.userid) < 4 || strlen(ma.passwd) < 4) { + login_log("'ladmin': Attempt to create an invalid account (account or pass is too short, ip: %s)" RETCODE, + ip); + } else if (ma.sex != 'F' && ma.sex != 'M') { + login_log("'ladmin': Attempt to create an invalid account (account: %s, received pass: %s, invalid sex, ip: %s)" RETCODE, + ma.userid, ma.passwd, ip); + } else if (account_id_count > END_ACCOUNT_NUM) { + login_log("'ladmin': Attempt to create an account, but there is no more available id number (account: %s, pass: %s, sex: %c, ip: %s)" RETCODE, + ma.userid, ma.passwd, ma.sex, ip); + } else { + remove_control_chars(ma.userid); + remove_control_chars(ma.passwd); + for(i = 0; i < auth_num; i++) { + if (strncmp(auth_dat[i].userid, ma.userid, 24) == 0) { + login_log("'ladmin': Attempt to create an already existing account (account: %s, pass: %s, received pass: %s, ip: %s)" RETCODE, + auth_dat[i].userid, auth_dat[i].pass, ma.passwd, ip); + break; + } + } + if (i == auth_num) { + int new_id; + char email[40]; + memcpy(email, RFIFOP(fd,51), 40); + email[39] = '\0'; + remove_control_chars(email); + new_id = mmo_auth_new(&ma, ma.sex, email); + login_log("'ladmin': Account creation (account: %s (id: %d), pass: %s, sex: %c, email: %s, ip: %s)" RETCODE, + ma.userid, new_id, ma.passwd, ma.sex, auth_dat[i].email, ip); + WFIFOL(fd,2) = new_id; + mmo_auth_sync(); + } + } + WFIFOSET(fd,30); + RFIFOSKIP(fd,91); + } + break; + + case 0x7932: // Request for an account deletion + if (RFIFOREST(fd) < 26) + return 0; + WFIFOW(fd,0) = 0x7933; + WFIFOL(fd,2) = -1; + account_name = RFIFOP(fd,2); + account_name[23] = '\0'; + remove_control_chars(account_name); + i = search_account_index(account_name); + if (i != -1) { + // Char-server is notified of deletion (for characters deletion). + unsigned char buf[65535]; + WBUFW(buf,0) = 0x2730; + WBUFL(buf,2) = auth_dat[i].account_id; + charif_sendallwos(-1, buf, 6); + // send answer + memcpy(WFIFOP(fd,6), auth_dat[i].userid, 24); + WFIFOL(fd,2) = auth_dat[i].account_id; + // save deleted account in log file + login_log("'ladmin': Account deletion (account: %s, id: %d, ip: %s) - saved in next line:" RETCODE, + auth_dat[i].userid, auth_dat[i].account_id, ip); + mmo_auth_tostr(buf, &auth_dat[i]); + login_log("%s" RETCODE, buf); + // delete account + memset(auth_dat[i].userid, '\0', sizeof(auth_dat[i].userid)); + auth_dat[i].account_id = -1; + mmo_auth_sync(); + } else { + memcpy(WFIFOP(fd,6), account_name, 24); + login_log("'ladmin': Attempt to delete an unknown account (account: %s, ip: %s)" RETCODE, + account_name, ip); + } + WFIFOSET(fd,30); + RFIFOSKIP(fd,26); + break; + + case 0x7934: // Request to change a password + if (RFIFOREST(fd) < 50) + return 0; + WFIFOW(fd,0) = 0x7935; + WFIFOL(fd,2) = -1; + account_name = RFIFOP(fd,2); + account_name[23] = '\0'; + remove_control_chars(account_name); + i = search_account_index(account_name); + if (i != -1) { + memcpy(WFIFOP(fd,6), auth_dat[i].userid, 24); + memcpy(auth_dat[i].pass, RFIFOP(fd,26), 24); + auth_dat[i].pass[23] = '\0'; + remove_control_chars(auth_dat[i].pass); + WFIFOL(fd,2) = auth_dat[i].account_id; + login_log("'ladmin': Modification of a password (account: %s, new password: %s, ip: %s)" RETCODE, + auth_dat[i].userid, auth_dat[i].pass, ip); + mmo_auth_sync(); + } else { + memcpy(WFIFOP(fd,6), account_name, 24); + login_log("'ladmin': Attempt to modify the password of an unknown account (account: %s, ip: %s)" RETCODE, + account_name, ip); + } + WFIFOSET(fd,30); + RFIFOSKIP(fd,50); + break; + + case 0x7936: // Request to modify a state + if (RFIFOREST(fd) < 50) + return 0; + { + char error_message[20]; + int statut; + WFIFOW(fd,0) = 0x7937; + WFIFOL(fd,2) = -1; + account_name = RFIFOP(fd,2); + account_name[23] = '\0'; + remove_control_chars(account_name); + statut = RFIFOL(fd,26); + memcpy(error_message, RFIFOP(fd,30), 20); + error_message[19] = '\0'; + remove_control_chars(error_message); + if (statut != 7 || error_message[0] == '\0') { // 7: // 6 = Your are Prohibited to log in until %s + strcpy(error_message, "-"); + } + i = search_account_index(account_name); + if (i != -1) { + memcpy(WFIFOP(fd,6), auth_dat[i].userid, 24); + WFIFOL(fd,2) = auth_dat[i].account_id; + if (auth_dat[i].state == statut && strcmp(auth_dat[i].error_message, error_message) == 0) + login_log("'ladmin': Modification of a state, but the state of the account is already the good state (account: %s, received state: %d, ip: %s)" RETCODE, + account_name, statut, ip); + else { + if (statut == 7) + login_log("'ladmin': Modification of a state (account: %s, new state: %d - prohibited to login until '%s', ip: %s)" RETCODE, + auth_dat[i].userid, statut, error_message, ip); + else + login_log("'ladmin': Modification of a state (account: %s, new state: %d, ip: %s)" RETCODE, + auth_dat[i].userid, statut, ip); + if (auth_dat[i].state == 0) { + unsigned char buf[16]; + WBUFW(buf,0) = 0x2731; + WBUFL(buf,2) = auth_dat[i].account_id; + WBUFB(buf,6) = 0; // 0: change of statut, 1: ban + WBUFL(buf,7) = statut; // status or final date of a banishment + charif_sendallwos(-1, buf, 11); + for(j = 0; j < AUTH_FIFO_SIZE; j++) + if (auth_fifo[j].account_id == auth_dat[i].account_id) + auth_fifo[j].login_id1++; // to avoid reconnection error when come back from map-server (char-server will ask again the authentification) + } + auth_dat[i].state = statut; + memcpy(auth_dat[i].error_message, error_message, 20); + mmo_auth_sync(); + } + } else { + memcpy(WFIFOP(fd,6), account_name, 24); + login_log("'ladmin': Attempt to modify the state of an unknown account (account: %s, received state: %d, ip: %s)" RETCODE, + account_name, statut, ip); + } + WFIFOL(fd,30) = statut; + } + WFIFOSET(fd,34); + RFIFOSKIP(fd,50); + break; + + case 0x7938: // Request for servers list and # of online players + login_log("'ladmin': Sending of servers list (ip: %s)" RETCODE, ip); + server_num = 0; + for(i = 0; i < MAX_SERVERS; i++) { + if (server_fd[i] >= 0) { + WFIFOL(fd,4+server_num*32) = server[i].ip; + WFIFOW(fd,4+server_num*32+4) = server[i].port; + memcpy(WFIFOP(fd,4+server_num*32+6), server[i].name, 20); + WFIFOW(fd,4+server_num*32+26) = server[i].users; + WFIFOW(fd,4+server_num*32+28) = server[i].maintenance; + WFIFOW(fd,4+server_num*32+30) = server[i].new; + server_num++; + } + } + WFIFOW(fd,0) = 0x7939; + WFIFOW(fd,2) = 4 + 32 * server_num; + WFIFOSET(fd,4+32*server_num); + RFIFOSKIP(fd,2); + break; + + case 0x793a: // Request to password check + if (RFIFOREST(fd) < 50) + return 0; + WFIFOW(fd,0) = 0x793b; + WFIFOL(fd,2) = -1; + account_name = RFIFOP(fd,2); + account_name[23] = '\0'; + remove_control_chars(account_name); + i = search_account_index(account_name); + if (i != -1) { + memcpy(WFIFOP(fd,6), auth_dat[i].userid, 24); + if (strcmp(auth_dat[i].pass, RFIFOP(fd,26)) == 0) { + WFIFOL(fd,2) = auth_dat[i].account_id; + login_log("'ladmin': Check of password OK (account: %s, password: %s, ip: %s)" RETCODE, + auth_dat[i].userid, auth_dat[i].pass, ip); + } else { + char pass[24]; + memcpy(pass, RFIFOP(fd,26), 24); + pass[23] = '\0'; + remove_control_chars(pass); + login_log("'ladmin': Failure of password check (account: %s, proposed pass: %s, ip: %s)" RETCODE, + auth_dat[i].userid, pass, ip); + } + } else { + memcpy(WFIFOP(fd,6), account_name, 24); + login_log("'ladmin': Attempt to check the password of an unknown account (account: %s, ip: %s)" RETCODE, + account_name, ip); + } + WFIFOSET(fd,30); + RFIFOSKIP(fd,50); + break; + + case 0x793c: // Request to modify sex + if (RFIFOREST(fd) < 27) + return 0; + WFIFOW(fd,0) = 0x793d; + WFIFOL(fd,2) = -1; + account_name = RFIFOP(fd,2); + account_name[23] = '\0'; + remove_control_chars(account_name); + memcpy(WFIFOP(fd,6), account_name, 24); + { + char sex; + sex = RFIFOB(fd,26); + if (sex != 'F' && sex != 'M') { + if (sex > 31) + login_log("'ladmin': Attempt to give an invalid sex (account: %s, received sex: %c, ip: %s)" RETCODE, + account_name, sex, ip); + else + login_log("'ladmin': Attempt to give an invalid sex (account: %s, received sex: 'control char', ip: %s)" RETCODE, + account_name, ip); + } else { + i = search_account_index(account_name); + if (i != -1) { + memcpy(WFIFOP(fd,6), auth_dat[i].userid, 24); + if (auth_dat[i].sex != ((sex == 'S' || sex == 's') ? 2 : (sex == 'M' || sex == 'm'))) { + unsigned char buf[16]; + WFIFOL(fd,2) = auth_dat[i].account_id; + for(j = 0; j < AUTH_FIFO_SIZE; j++) + if (auth_fifo[j].account_id == auth_dat[i].account_id) + auth_fifo[j].login_id1++; // to avoid reconnection error when come back from map-server (char-server will ask again the authentification) + auth_dat[i].sex = (sex == 'S' || sex == 's') ? 2 : (sex == 'M' || sex == 'm'); + login_log("'ladmin': Modification of a sex (account: %s, new sex: %c, ip: %s)" RETCODE, + auth_dat[i].userid, sex, ip); + mmo_auth_sync(); + // send to all char-server the change + WBUFW(buf,0) = 0x2723; + WBUFL(buf,2) = auth_dat[i].account_id; + WBUFB(buf,6) = auth_dat[i].sex; + charif_sendallwos(-1, buf, 7); + } else { + login_log("'ladmin': Modification of a sex, but the sex is already the good sex (account: %s, sex: %c, ip: %s)" RETCODE, + auth_dat[i].userid, sex, ip); + } + } else { + login_log("'ladmin': Attempt to modify the sex of an unknown account (account: %s, received sex: %c, ip: %s)" RETCODE, + account_name, sex, ip); + } + } + } + WFIFOSET(fd,30); + RFIFOSKIP(fd,27); + break; + + case 0x793e: // Request to modify GM level + if (RFIFOREST(fd) < 27) + return 0; + WFIFOW(fd,0) = 0x793f; + WFIFOL(fd,2) = -1; + account_name = RFIFOP(fd,2); + account_name[23] = '\0'; + remove_control_chars(account_name); + memcpy(WFIFOP(fd,6), account_name, 24); + { + char new_gm_level; + new_gm_level = RFIFOB(fd,26); + if (new_gm_level < 0 || new_gm_level > 99) { + login_log("'ladmin': Attempt to give an invalid GM level (account: %s, received GM level: %d, ip: %s)" RETCODE, + account_name, (int)new_gm_level, ip); + } else { + i = search_account_index(account_name); + if (i != -1) { + int acc = auth_dat[i].account_id; + memcpy(WFIFOP(fd,6), auth_dat[i].userid, 24); + if (isGM(acc) != new_gm_level) { + // modification of the file + FILE *fp, *fp2; + int lock; + char line[512]; + int GM_account, GM_level; + int modify_flag; + char tmpstr[24]; + struct timeval tv; + if ((fp2 = lock_fopen(GM_account_filename, &lock)) != NULL) { + if ((fp = fopen(GM_account_filename, "r")) != NULL) { + gettimeofday(&tv, NULL); + strftime(tmpstr, 23, date_format, localtime(&(tv.tv_sec))); + modify_flag = 0; + // read/write GM file + while(fgets(line, sizeof(line)-1, fp)) { + while(line[0] != '\0' && (line[strlen(line)-1] == '\n' || line[strlen(line)-1] == '\r')) + line[strlen(line)-1] = '\0'; + if ((line[0] == '/' && line[1] == '/') || line[0] == '\0') + fprintf(fp2, "%s" RETCODE, line); + else { + if (sscanf(line, "%d %d", &GM_account, &GM_level) != 2 && sscanf(line, "%d: %d", &GM_account, &GM_level) != 2) + fprintf(fp2, "%s" RETCODE, line); + else if (GM_account != acc) + fprintf(fp2, "%s" RETCODE, line); + else if (new_gm_level < 1) { + fprintf(fp2, "// %s: 'ladmin' GM level removed on account %d '%s' (previous level: %d)" RETCODE "//%d %d" RETCODE, tmpstr, acc, auth_dat[i].userid, GM_level, acc, new_gm_level); + modify_flag = 1; + } else { + fprintf(fp2, "// %s: 'ladmin' GM level on account %d '%s' (previous level: %d)" RETCODE "%d %d" RETCODE, tmpstr, acc, auth_dat[i].userid, GM_level, acc, new_gm_level); + modify_flag = 1; + } + } + } + if (modify_flag == 0) + fprintf(fp2, "// %s: 'ladmin' GM level on account %d '%s' (previous level: 0)" RETCODE "%d %d" RETCODE, tmpstr, acc, auth_dat[i].userid, acc, new_gm_level); + fclose(fp); + } else { + login_log("'ladmin': Attempt to modify of a GM level - impossible to read GM accounts file (account: %s (%d), received GM level: %d, ip: %s)" RETCODE, + auth_dat[i].userid, acc, (int)new_gm_level, ip); + } + if (lock_fclose(fp2, GM_account_filename, &lock) == 0) { + WFIFOL(fd,2) = acc; + login_log("'ladmin': Modification of a GM level (account: %s (%d), new GM level: %d, ip: %s)" RETCODE, + auth_dat[i].userid, acc, (int)new_gm_level, ip); + // read and send new GM informations + read_gm_account(); + send_GM_accounts(); + } else { + login_log("'ladmin': Attempt to modify of a GM level - impossible to write GM accounts file (account: %s (%d), received GM level: %d, ip: %s)" RETCODE, + auth_dat[i].userid, acc, (int)new_gm_level, ip); + } + } else { + login_log("'ladmin': Attempt to modify of a GM level - impossible to write GM accounts file (account: %s (%d), received GM level: %d, ip: %s)" RETCODE, + auth_dat[i].userid, acc, (int)new_gm_level, ip); + } + } else { + login_log("'ladmin': Attempt to modify of a GM level, but the GM level is already the good GM level (account: %s (%d), GM level: %d, ip: %s)" RETCODE, + auth_dat[i].userid, acc, (int)new_gm_level, ip); + } + } else { + login_log("'ladmin': Attempt to modify the GM level of an unknown account (account: %s, received GM level: %d, ip: %s)" RETCODE, + account_name, (int)new_gm_level, ip); + } + } + } + WFIFOSET(fd,30); + RFIFOSKIP(fd,27); + break; + + case 0x7940: // Request to modify e-mail + if (RFIFOREST(fd) < 66) + return 0; + WFIFOW(fd,0) = 0x7941; + WFIFOL(fd,2) = -1; + account_name = RFIFOP(fd,2); + account_name[23] = '\0'; + remove_control_chars(account_name); + memcpy(WFIFOP(fd,6), account_name, 24); + { + char email[40]; + memcpy(email, RFIFOP(fd,26), 40); + if (e_mail_check(email) == 0) { + login_log("'ladmin': Attempt to give an invalid e-mail (account: %s, ip: %s)" RETCODE, + account_name, ip); + } else { + remove_control_chars(email); + i = search_account_index(account_name); + if (i != -1) { + memcpy(WFIFOP(fd,6), auth_dat[i].userid, 24); + memcpy(auth_dat[i].email, email, 40); + WFIFOL(fd,2) = auth_dat[i].account_id; + login_log("'ladmin': Modification of an email (account: %s, new e-mail: %s, ip: %s)" RETCODE, + auth_dat[i].userid, email, ip); + mmo_auth_sync(); + } else { + login_log("'ladmin': Attempt to modify the e-mail of an unknown account (account: %s, received e-mail: %s, ip: %s)" RETCODE, + account_name, email, ip); + } + } + } + WFIFOSET(fd,30); + RFIFOSKIP(fd,66); + break; + + case 0x7942: // Request to modify memo field + if (RFIFOREST(fd) < 28 || RFIFOREST(fd) < (28 + RFIFOW(fd,26))) + return 0; + WFIFOW(fd,0) = 0x7943; + WFIFOL(fd,2) = -1; + account_name = RFIFOP(fd,2); + account_name[23] = '\0'; + remove_control_chars(account_name); + i = search_account_index(account_name); + if (i != -1) { + int size_of_memo = sizeof(auth_dat[i].memo); + memcpy(WFIFOP(fd,6), auth_dat[i].userid, 24); + memset(auth_dat[i].memo, '\0', size_of_memo); + if (RFIFOW(fd,26) == 0) { + strncpy(auth_dat[i].memo, "-", size_of_memo); + } else if (RFIFOW(fd,26) > size_of_memo - 1) { + memcpy(auth_dat[i].memo, RFIFOP(fd,28), size_of_memo - 1); + } else { + memcpy(auth_dat[i].memo, RFIFOP(fd,28), RFIFOW(fd,26)); + } + auth_dat[i].memo[size_of_memo - 1] = '\0'; + remove_control_chars(auth_dat[i].memo); + WFIFOL(fd,2) = auth_dat[i].account_id; + login_log("'ladmin': Modification of a memo field (account: %s, new memo: %s, ip: %s)" RETCODE, + auth_dat[i].userid, auth_dat[i].memo, ip); + mmo_auth_sync(); + } else { + memcpy(WFIFOP(fd,6), account_name, 24); + login_log("'ladmin': Attempt to modify the memo field of an unknown account (account: %s, ip: %s)" RETCODE, + account_name, ip); + } + WFIFOSET(fd,30); + RFIFOSKIP(fd,28 + RFIFOW(fd,26)); + break; + + case 0x7944: // Request to found an account id + if (RFIFOREST(fd) < 26) + return 0; + WFIFOW(fd,0) = 0x7945; + WFIFOL(fd,2) = -1; + account_name = RFIFOP(fd,2); + account_name[23] = '\0'; + remove_control_chars(account_name); + i = search_account_index(account_name); + if (i != -1) { + memcpy(WFIFOP(fd,6), auth_dat[i].userid, 24); + WFIFOL(fd,2) = auth_dat[i].account_id; + login_log("'ladmin': Request (by the name) of an account id (account: %s, id: %d, ip: %s)" RETCODE, + auth_dat[i].userid, auth_dat[i].account_id, ip); + } else { + memcpy(WFIFOP(fd,6), account_name, 24); + login_log("'ladmin': ID request (by the name) of an unknown account (account: %s, ip: %s)" RETCODE, + account_name, ip); + } + WFIFOSET(fd,30); + RFIFOSKIP(fd,26); + break; + + case 0x7946: // Request to found an account name + if (RFIFOREST(fd) < 6) + return 0; + WFIFOW(fd,0) = 0x7947; + WFIFOL(fd,2) = RFIFOL(fd,2); + memset(WFIFOP(fd,6), '\0', 24); + for(i = 0; i < auth_num; i++) { + if (auth_dat[i].account_id == RFIFOL(fd,2)) { + strncpy(WFIFOP(fd,6), auth_dat[i].userid, 24); + login_log("'ladmin': Request (by id) of an account name (account: %s, id: %d, ip: %s)" RETCODE, + auth_dat[i].userid, RFIFOL(fd,2), ip); + break; + } + } + if (i == auth_num) { + login_log("'ladmin': Name request (by id) of an unknown account (id: %d, ip: %s)" RETCODE, + RFIFOL(fd,2), ip); + strncpy(WFIFOP(fd,6), "", 24); + } + WFIFOSET(fd,30); + RFIFOSKIP(fd,6); + break; + + case 0x7948: // Request to change the validity limit (timestamp) (absolute value) + if (RFIFOREST(fd) < 30) + return 0; + { + time_t timestamp; + char tmpstr[2048]; + WFIFOW(fd,0) = 0x7949; + WFIFOL(fd,2) = -1; + account_name = RFIFOP(fd,2); + account_name[23] = '\0'; + remove_control_chars(account_name); + timestamp = (time_t)RFIFOL(fd,26); + strftime(tmpstr, 24, date_format, localtime(×tamp)); + i = search_account_index(account_name); + if (i != -1) { + memcpy(WFIFOP(fd,6), auth_dat[i].userid, 24); + login_log("'ladmin': Change of a validity limit (account: %s, new validity: %d (%s), ip: %s)" RETCODE, + auth_dat[i].userid, timestamp, (timestamp == 0 ? "unlimited" : tmpstr), ip); + auth_dat[i].connect_until_time = timestamp; + WFIFOL(fd,2) = auth_dat[i].account_id; + mmo_auth_sync(); + } else { + memcpy(WFIFOP(fd,6), account_name, 24); + login_log("'ladmin': Attempt to change the validity limit of an unknown account (account: %s, received validity: %d (%s), ip: %s)" RETCODE, + account_name, timestamp, (timestamp == 0 ? "unlimited" : tmpstr), ip); + } + WFIFOL(fd,30) = timestamp; + } + WFIFOSET(fd,34); + RFIFOSKIP(fd,30); + break; + + case 0x794a: // Request to change the final date of a banishment (timestamp) (absolute value) + if (RFIFOREST(fd) < 30) + return 0; + { + time_t timestamp; + char tmpstr[2048]; + WFIFOW(fd,0) = 0x794b; + WFIFOL(fd,2) = -1; + account_name = RFIFOP(fd,2); + account_name[23] = '\0'; + remove_control_chars(account_name); + timestamp = (time_t)RFIFOL(fd,26); + if (timestamp <= time(NULL)) + timestamp = 0; + strftime(tmpstr, 24, date_format, localtime(×tamp)); + i = search_account_index(account_name); + if (i != -1) { + memcpy(WFIFOP(fd,6), auth_dat[i].userid, 24); + WFIFOL(fd,2) = auth_dat[i].account_id; + login_log("'ladmin': Change of the final date of a banishment (account: %s, new final date of banishment: %d (%s), ip: %s)" RETCODE, + auth_dat[i].userid, timestamp, (timestamp == 0 ? "no banishment" : tmpstr), ip); + if (auth_dat[i].ban_until_time != timestamp) { + if (timestamp != 0) { + unsigned char buf[16]; + WBUFW(buf,0) = 0x2731; + WBUFL(buf,2) = auth_dat[i].account_id; + WBUFB(buf,6) = 1; // 0: change of statut, 1: ban + WBUFL(buf,7) = timestamp; // status or final date of a banishment + charif_sendallwos(-1, buf, 11); + for(j = 0; j < AUTH_FIFO_SIZE; j++) + if (auth_fifo[j].account_id == auth_dat[i].account_id) + auth_fifo[j].login_id1++; // to avoid reconnection error when come back from map-server (char-server will ask again the authentification) + } + auth_dat[i].ban_until_time = timestamp; + mmo_auth_sync(); + } + } else { + memcpy(WFIFOP(fd,6), account_name, 24); + login_log("'ladmin': Attempt to change the final date of a banishment of an unknown account (account: %s, received final date of banishment: %d (%s), ip: %s)" RETCODE, + account_name, timestamp, (timestamp == 0 ? "no banishment" : tmpstr), ip); + } + WFIFOL(fd,30) = timestamp; + } + WFIFOSET(fd,34); + RFIFOSKIP(fd,30); + break; + + case 0x794c: // Request to change the final date of a banishment (timestamp) (relative change) + if (RFIFOREST(fd) < 38) + return 0; + { + time_t timestamp; + struct tm *tmtime; + char tmpstr[2048]; + WFIFOW(fd,0) = 0x794d; + WFIFOL(fd,2) = -1; + account_name = RFIFOP(fd,2); + account_name[23] = '\0'; + remove_control_chars(account_name); + i = search_account_index(account_name); + if (i != -1) { + WFIFOL(fd,2) = auth_dat[i].account_id; + memcpy(WFIFOP(fd,6), auth_dat[i].userid, 24); + if (auth_dat[i].ban_until_time == 0 || auth_dat[i].ban_until_time < time(NULL)) + timestamp = time(NULL); + else + timestamp = auth_dat[i].ban_until_time; + tmtime = localtime(×tamp); + tmtime->tm_year = tmtime->tm_year + (short)RFIFOW(fd,26); + tmtime->tm_mon = tmtime->tm_mon + (short)RFIFOW(fd,28); + tmtime->tm_mday = tmtime->tm_mday + (short)RFIFOW(fd,30); + tmtime->tm_hour = tmtime->tm_hour + (short)RFIFOW(fd,32); + tmtime->tm_min = tmtime->tm_min + (short)RFIFOW(fd,34); + tmtime->tm_sec = tmtime->tm_sec + (short)RFIFOW(fd,36); + timestamp = mktime(tmtime); + if (timestamp != -1) { + if (timestamp <= time(NULL)) + timestamp = 0; + strftime(tmpstr, 24, date_format, localtime(×tamp)); + login_log("'ladmin': Adjustment of a final date of a banishment (account: %s, (%+d y %+d m %+d d %+d h %+d mn %+d s) -> new validity: %d (%s), ip: %s)" RETCODE, + auth_dat[i].userid, (short)RFIFOW(fd,26), (short)RFIFOW(fd,28), (short)RFIFOW(fd,30), (short)RFIFOW(fd,32), (short)RFIFOW(fd,34), (short)RFIFOW(fd,36), timestamp, (timestamp == 0 ? "no banishment" : tmpstr), ip); + if (auth_dat[i].ban_until_time != timestamp) { + if (timestamp != 0) { + unsigned char buf[16]; + WBUFW(buf,0) = 0x2731; + WBUFL(buf,2) = auth_dat[i].account_id; + WBUFB(buf,6) = 1; // 0: change of statut, 1: ban + WBUFL(buf,7) = timestamp; // status or final date of a banishment + charif_sendallwos(-1, buf, 11); + for(j = 0; j < AUTH_FIFO_SIZE; j++) + if (auth_fifo[j].account_id == auth_dat[i].account_id) + auth_fifo[j].login_id1++; // to avoid reconnection error when come back from map-server (char-server will ask again the authentification) + } + auth_dat[i].ban_until_time = timestamp; + mmo_auth_sync(); + } + } else { + strftime(tmpstr, 24, date_format, localtime(&auth_dat[i].ban_until_time)); + login_log("'ladmin': Impossible to adjust the final date of a banishment (account: %s, %d (%s) + (%+d y %+d m %+d d %+d h %+d mn %+d s) -> ???, ip: %s)" RETCODE, + auth_dat[i].userid, auth_dat[i].ban_until_time, (auth_dat[i].ban_until_time == 0 ? "no banishment" : tmpstr), (short)RFIFOW(fd,26), (short)RFIFOW(fd,28), (short)RFIFOW(fd,30), (short)RFIFOW(fd,32), (short)RFIFOW(fd,34), (short)RFIFOW(fd,36), ip); + } + WFIFOL(fd,30) = (unsigned long)auth_dat[i].ban_until_time; + } else { + memcpy(WFIFOP(fd,6), account_name, 24); + login_log("'ladmin': Attempt to adjust the final date of a banishment of an unknown account (account: %s, ip: %s)" RETCODE, + account_name, ip); + WFIFOL(fd,30) = 0; + } + } + WFIFOSET(fd,34); + RFIFOSKIP(fd,38); + break; + + case 0x794e: // Request to send a broadcast message + if (RFIFOREST(fd) < 8 || RFIFOREST(fd) < (8 + RFIFOL(fd,4))) + return 0; + WFIFOW(fd,0) = 0x794f; + WFIFOW(fd,2) = -1; + if (RFIFOL(fd,4) < 1) { + login_log("'ladmin': Receiving a message for broadcast, but message is void (ip: %s)" RETCODE, + ip); + } else { + // at least 1 char-server + for(i = 0; i < MAX_SERVERS; i++) + if (server_fd[i] >= 0) + break; + if (i == MAX_SERVERS) { + login_log("'ladmin': Receiving a message for broadcast, but no char-server is online (ip: %s)" RETCODE, + ip); + } else { + char buf[32000]; + char message[32000]; + WFIFOW(fd,2) = 0; + memset(message, '\0', sizeof(message)); + memcpy(message, RFIFOP(fd,8), RFIFOL(fd,4)); + message[sizeof(message)-1] = '\0'; + remove_control_chars(message); + if (RFIFOW(fd,2) == 0) + login_log("'ladmin': Receiving a message for broadcast (message (in yellow): %s, ip: %s)" RETCODE, + message, ip); + else + login_log("'ladmin': Receiving a message for broadcast (message (in blue): %s, ip: %s)" RETCODE, + message, ip); + // send same message to all char-servers (no answer) + memcpy(WBUFP(buf,0), RFIFOP(fd,0), 8 + RFIFOL(fd,4)); + WBUFW(buf,0) = 0x2726; + charif_sendallwos(-1, buf, 8 + RFIFOL(fd,4)); + } + } + WFIFOSET(fd,4); + RFIFOSKIP(fd,8 + RFIFOL(fd,4)); + break; + + case 0x7950: // Request to change the validity limite (timestamp) (relative change) + if (RFIFOREST(fd) < 38) + return 0; + { + time_t timestamp; + struct tm *tmtime; + char tmpstr[2048]; + char tmpstr2[2048]; + WFIFOW(fd,0) = 0x7951; + WFIFOL(fd,2) = -1; + account_name = RFIFOP(fd,2); + account_name[23] = '\0'; + remove_control_chars(account_name); + i = search_account_index(account_name); + if (i != -1) { + WFIFOL(fd,2) = auth_dat[i].account_id; + memcpy(WFIFOP(fd,6), auth_dat[i].userid, 24); + timestamp = auth_dat[i].connect_until_time; + if (add_to_unlimited_account == 0 && timestamp == 0) { + login_log("'ladmin': Attempt to adjust the validity limit of an unlimited account (account: %s, ip: %s)" RETCODE, + auth_dat[i].userid, ip); + WFIFOL(fd,30) = 0; + } else { + if (timestamp == 0 || timestamp < time(NULL)) + timestamp = time(NULL); + tmtime = localtime(×tamp); + tmtime->tm_year = tmtime->tm_year + (short)RFIFOW(fd,26); + tmtime->tm_mon = tmtime->tm_mon + (short)RFIFOW(fd,28); + tmtime->tm_mday = tmtime->tm_mday + (short)RFIFOW(fd,30); + tmtime->tm_hour = tmtime->tm_hour + (short)RFIFOW(fd,32); + tmtime->tm_min = tmtime->tm_min + (short)RFIFOW(fd,34); + tmtime->tm_sec = tmtime->tm_sec + (short)RFIFOW(fd,36); + timestamp = mktime(tmtime); + if (timestamp != -1) { + strftime(tmpstr, 24, date_format, localtime(&auth_dat[i].connect_until_time)); + strftime(tmpstr2, 24, date_format, localtime(×tamp)); + login_log("'ladmin': Adjustment of a validity limit (account: %s, %d (%s) + (%+d y %+d m %+d d %+d h %+d mn %+d s) -> new validity: %d (%s), ip: %s)" RETCODE, + auth_dat[i].userid, auth_dat[i].connect_until_time, (auth_dat[i].connect_until_time == 0 ? "unlimited" : tmpstr), (short)RFIFOW(fd,26), (short)RFIFOW(fd,28), (short)RFIFOW(fd,30), (short)RFIFOW(fd,32), (short)RFIFOW(fd,34), (short)RFIFOW(fd,36), timestamp, (timestamp == 0 ? "unlimited" : tmpstr2), ip); + auth_dat[i].connect_until_time = timestamp; + mmo_auth_sync(); + WFIFOL(fd,30) = (unsigned long)auth_dat[i].connect_until_time; + } else { + strftime(tmpstr, 24, date_format, localtime(&auth_dat[i].connect_until_time)); + login_log("'ladmin': Impossible to adjust a validity limit (account: %s, %d (%s) + (%+d y %+d m %+d d %+d h %+d mn %+d s) -> ???, ip: %s)" RETCODE, + auth_dat[i].userid, auth_dat[i].connect_until_time, (auth_dat[i].connect_until_time == 0 ? "unlimited" : tmpstr), (short)RFIFOW(fd,26), (short)RFIFOW(fd,28), (short)RFIFOW(fd,30), (short)RFIFOW(fd,32), (short)RFIFOW(fd,34), (short)RFIFOW(fd,36), ip); + WFIFOL(fd,30) = 0; + } + } + } else { + memcpy(WFIFOP(fd,6), account_name, 24); + login_log("'ladmin': Attempt to adjust the validity limit of an unknown account (account: %s, ip: %s)" RETCODE, + account_name, ip); + WFIFOL(fd,30) = 0; + } + } + WFIFOSET(fd,34); + RFIFOSKIP(fd,38); + break; + + case 0x7952: // Request about informations of an account (by account name) + if (RFIFOREST(fd) < 26) + return 0; + WFIFOW(fd,0) = 0x7953; + WFIFOL(fd,2) = -1; + account_name = RFIFOP(fd,2); + account_name[23] = '\0'; + remove_control_chars(account_name); + i = search_account_index(account_name); + if (i != -1) { + WFIFOL(fd,2) = auth_dat[i].account_id; + WFIFOB(fd,6) = (unsigned char)isGM(auth_dat[i].account_id); + memcpy(WFIFOP(fd,7), auth_dat[i].userid, 24); + WFIFOB(fd,31) = auth_dat[i].sex; + WFIFOL(fd,32) = auth_dat[i].logincount; + WFIFOL(fd,36) = auth_dat[i].state; + memcpy(WFIFOP(fd,40), auth_dat[i].error_message, 20); + memcpy(WFIFOP(fd,60), auth_dat[i].lastlogin, 24); + memcpy(WFIFOP(fd,84), auth_dat[i].last_ip, 16); + memcpy(WFIFOP(fd,100), auth_dat[i].email, 40); + WFIFOL(fd,140) = (unsigned long)auth_dat[i].connect_until_time; + WFIFOL(fd,144) = (unsigned long)auth_dat[i].ban_until_time; + WFIFOW(fd,148) = strlen(auth_dat[i].memo); + if (auth_dat[i].memo[0]) { + memcpy(WFIFOP(fd,150), auth_dat[i].memo, strlen(auth_dat[i].memo)); + } + login_log("'ladmin': Sending information of an account (request by the name; account: %s, id: %d, ip: %s)" RETCODE, + auth_dat[i].userid, auth_dat[i].account_id, ip); + WFIFOSET(fd,150+strlen(auth_dat[i].memo)); + } else { + memcpy(WFIFOP(fd,7), account_name, 24); + WFIFOW(fd,148) = 0; + login_log("'ladmin': Attempt to obtain information (by the name) of an unknown account (account: %s, ip: %s)" RETCODE, + account_name, ip); + WFIFOSET(fd,150); + } + RFIFOSKIP(fd,26); + break; + + case 0x7954: // Request about information of an account (by account id) + if (RFIFOREST(fd) < 6) + return 0; + WFIFOW(fd,0) = 0x7953; + WFIFOL(fd,2) = RFIFOL(fd,2); + memset(WFIFOP(fd,7), '\0', 24); + for(i = 0; i < auth_num; i++) { + if (auth_dat[i].account_id == RFIFOL(fd,2)) { + login_log("'ladmin': Sending information of an account (request by the id; account: %s, id: %d, ip: %s)" RETCODE, + auth_dat[i].userid, RFIFOL(fd,2), ip); + WFIFOB(fd,6) = (unsigned char)isGM(auth_dat[i].account_id); + memcpy(WFIFOP(fd,7), auth_dat[i].userid, 24); + WFIFOB(fd,31) = auth_dat[i].sex; + WFIFOL(fd,32) = auth_dat[i].logincount; + WFIFOL(fd,36) = auth_dat[i].state; + memcpy(WFIFOP(fd,40), auth_dat[i].error_message, 20); + memcpy(WFIFOP(fd,60), auth_dat[i].lastlogin, 24); + memcpy(WFIFOP(fd,84), auth_dat[i].last_ip, 16); + memcpy(WFIFOP(fd,100), auth_dat[i].email, 40); + WFIFOL(fd,140) = (unsigned long)auth_dat[i].connect_until_time; + WFIFOL(fd,144) = (unsigned long)auth_dat[i].ban_until_time; + WFIFOW(fd,148) = strlen(auth_dat[i].memo); + if (auth_dat[i].memo[0]) { + memcpy(WFIFOP(fd,150), auth_dat[i].memo, strlen(auth_dat[i].memo)); + } + WFIFOSET(fd,150+strlen(auth_dat[i].memo)); + break; + } + } + if (i == auth_num) { + login_log("'ladmin': Attempt to obtain information (by the id) of an unknown account (id: %d, ip: %s)" RETCODE, + RFIFOL(fd,2), ip); + strncpy(WFIFOP(fd,7), "", 24); + WFIFOW(fd,148) = 0; + WFIFOSET(fd,150); + } + RFIFOSKIP(fd,6); + break; + + case 0x7955: // Request to reload GM file (no answer) + login_log("'ladmin': Request to re-load GM configuration file (ip: %s)." RETCODE, ip); + read_gm_account(); + // send GM accounts to all char-servers + send_GM_accounts(); + RFIFOSKIP(fd,2); + break; + + default: + { + FILE *logfp; + char tmpstr[24]; + struct timeval tv; + logfp = fopen(login_log_unknown_packets_filename, "a"); + if (logfp) { + gettimeofday(&tv, NULL); + strftime(tmpstr, 23, date_format, localtime(&(tv.tv_sec))); + fprintf(logfp, "%s.%03d: receiving of an unknown packet -> disconnection" RETCODE, tmpstr, (int)tv.tv_usec / 1000); + fprintf(logfp, "parse_admin: connection #%d (ip: %s), packet: 0x%x (with being read: %d)." RETCODE, fd, ip, RFIFOW(fd,0), RFIFOREST(fd)); + fprintf(logfp, "Detail (in hex):" RETCODE); + fprintf(logfp, "---- 00-01-02-03-04-05-06-07 08-09-0A-0B-0C-0D-0E-0F" RETCODE); + memset(tmpstr, '\0', sizeof(tmpstr)); + for(i = 0; i < RFIFOREST(fd); i++) { + if ((i & 15) == 0) + fprintf(logfp, "%04X ",i); + fprintf(logfp, "%02x ", RFIFOB(fd,i)); + if (RFIFOB(fd,i) > 0x1f) + tmpstr[i % 16] = RFIFOB(fd,i); + else + tmpstr[i % 16] = '.'; + if ((i - 7) % 16 == 0) // -8 + 1 + fprintf(logfp, " "); + else if ((i + 1) % 16 == 0) { + fprintf(logfp, " %s" RETCODE, tmpstr); + memset(tmpstr, '\0', sizeof(tmpstr)); + } + } + if (i % 16 != 0) { + for(j = i; j % 16 != 0; j++) { + fprintf(logfp, " "); + if ((j - 7) % 16 == 0) // -8 + 1 + fprintf(logfp, " "); + } + fprintf(logfp, " %s" RETCODE, tmpstr); + } + fprintf(logfp, RETCODE); + fclose(logfp); + } + } + login_log("'ladmin': End of connection, unknown packet (ip: %s)" RETCODE, ip); + session[fd]->eof = 1; + printf("Remote administration has been disconnected (unknown packet).\n"); + return 0; + } + //WFIFOW(fd,0) = 0x791f; + //WFIFOSET(fd,2); + } + return 0; +} + +//-------------------------------------------- +// Test to know if an IP come from LAN or WAN. +//-------------------------------------------- +int lan_ip_check(unsigned char *p) { + int i; + int lancheck = 1; + +// printf("lan_ip_check: to compare: %d.%d.%d.%d, network: %d.%d.%d.%d/%d.%d.%d.%d\n", +// p[0], p[1], p[2], p[3], +// subneti[0], subneti[1], subneti[2], subneti[3], +// subnetmaski[0], subnetmaski[1], subnetmaski[2], subnetmaski[3]); + for(i = 0; i < 4; i++) { + if ((subneti[i] & subnetmaski[i]) != (p[i] & subnetmaski[i])) { + lancheck = 0; + break; + } + } + printf("LAN test (result): %s source\033[0m.\n", (lancheck) ? "\033[1;36mLAN" : "\033[1;32mWAN"); + return lancheck; +} + +//---------------------------------------------------------------------------------------- +// Default packet parsing (normal players or administation/char-server connexion requests) +//---------------------------------------------------------------------------------------- +int parse_login(int fd) { + struct mmo_account account; + int result, i, j; + unsigned char *p = (unsigned char *) &session[fd]->client_addr.sin_addr; + char ip[16]; + + sprintf(ip, "%d.%d.%d.%d", p[0], p[1], p[2], p[3]); + + if (session[fd]->eof) { + close(fd); + delete_session(fd); + return 0; + } + + while(RFIFOREST(fd) >= 2) { + if (display_parse_login == 1) { + if (RFIFOW(fd,0) == 0x64 || RFIFOW(fd,0) == 0x01dd) { + if (RFIFOREST(fd) >= ((RFIFOW(fd,0) == 0x64) ? 55 : 47)) + printf("parse_login: connection #%d, packet: 0x%x (with being read: %d), account: %s.\n", fd, RFIFOW(fd,0), RFIFOREST(fd), RFIFOP(fd,6)); + } else if (RFIFOW(fd,0) == 0x2710) { + if (RFIFOREST(fd) >= 86) + printf("parse_login: connection #%d, packet: 0x%x (with being read: %d), server: %s.\n", fd, RFIFOW(fd,0), RFIFOREST(fd), RFIFOP(fd,60)); + } else + printf("parse_login: connection #%d, packet: 0x%x (with being read: %d).\n", fd, RFIFOW(fd,0), RFIFOREST(fd)); + } + + switch(RFIFOW(fd,0)) { + case 0x200: // New alive packet: structure: 0x200 <account.userid>.24B. used to verify if client is always alive. + if (RFIFOREST(fd) < 26) + return 0; + RFIFOSKIP(fd,26); + break; + + case 0x204: // New alive packet: structure: 0x204 <encrypted.account.userid>.16B. (new ragexe from 22 june 2004) + if (RFIFOREST(fd) < 18) + return 0; + RFIFOSKIP(fd,18); + break; + + case 0x64: // Ask connection of a client + case 0x01dd: // Ask connection of a client (encryption mode) + if (RFIFOREST(fd) < ((RFIFOW(fd,0) == 0x64) ? 55 : 47)) + return 0; + + account.userid = RFIFOP(fd,6); + account.userid[23] = '\0'; + remove_control_chars(account.userid); + account.passwd = RFIFOP(fd,30); + if (RFIFOW(fd,0) == 0x64) { + account.passwd[23] = '\0'; + remove_control_chars(account.passwd); + } +#ifdef PASSWORDENC + account.passwdenc = (RFIFOW(fd,0) == 0x64) ? 0 : PASSWORDENC; +#else + account.passwdenc = 0; +#endif + + if (RFIFOW(fd,0) == 0x64) { + login_log("Request for connection (non encryption mode) of %s (ip: %s)." RETCODE, account.userid, ip); + } else { + login_log("Request for connection (encryption mode) of %s (ip: %s)." RETCODE, account.userid, ip); + } + + if (!check_ip(session[fd]->client_addr.sin_addr.s_addr)) { + login_log("Connection refused: IP isn't authorised (deny/allow, ip: %s)." RETCODE, ip); + WFIFOW(fd,0) = 0x6a; + WFIFOB(fd,2) = 0x03; + WFIFOSET(fd,3); + RFIFOSKIP(fd,(RFIFOW(fd,0) == 0x64) ? 55 : 47); + break; + } + + result = mmo_auth(&account, fd); + if (result == -1) { + int gm_level = isGM(account.account_id); + if (min_level_to_connect > gm_level) { + login_log("Connection refused: the minimum GM level for connection is %d (account: %s, GM level: %d, ip: %s)." RETCODE, + min_level_to_connect, account.userid, gm_level, ip); + WFIFOW(fd,0) = 0x81; + WFIFOL(fd,2) = 1; // 01 = Server closed + WFIFOSET(fd,3); + } else { + if (gm_level) + printf("Connection of the GM (level:%d) account '%s' accepted.\n", gm_level, account.userid); + else + printf("Connection of the account '%s' accepted.\n", account.userid); + server_num = 0; + for(i = 0; i < MAX_SERVERS; i++) { + if (server_fd[i] >= 0) { + if (lan_ip_check(p)) + WFIFOL(fd,47+server_num*32) = inet_addr(lan_char_ip); + else + WFIFOL(fd,47+server_num*32) = server[i].ip; + WFIFOW(fd,47+server_num*32+4) = server[i].port; + memcpy(WFIFOP(fd,47+server_num*32+6), server[i].name, 20); + WFIFOW(fd,47+server_num*32+26) = server[i].users; + WFIFOW(fd,47+server_num*32+28) = server[i].maintenance; + WFIFOW(fd,47+server_num*32+30) = server[i].new; + server_num++; + } + } + // if at least 1 char-server + if (server_num > 0) { + WFIFOW(fd,0) = 0x69; + WFIFOW(fd,2) = 47+32*server_num; + WFIFOL(fd,4) = account.login_id1; + WFIFOL(fd,8) = account.account_id; + WFIFOL(fd,12) = account.login_id2; + WFIFOL(fd,16) = 0; // in old version, that was for ip (not more used) + memcpy(WFIFOP(fd,20), account.lastlogin, 24); // in old version, that was for name (not more used) + WFIFOB(fd,46) = account.sex; + WFIFOSET(fd,47+32*server_num); + if (auth_fifo_pos >= AUTH_FIFO_SIZE) + auth_fifo_pos = 0; + auth_fifo[auth_fifo_pos].account_id = account.account_id; + auth_fifo[auth_fifo_pos].login_id1 = account.login_id1; + auth_fifo[auth_fifo_pos].login_id2 = account.login_id2; + auth_fifo[auth_fifo_pos].sex = account.sex; + auth_fifo[auth_fifo_pos].delflag = 0; + auth_fifo[auth_fifo_pos].ip = session[fd]->client_addr.sin_addr.s_addr; + auth_fifo_pos++; + // if no char-server, don't send void list of servers, just disconnect the player with proper message + } else { + login_log("Connection refused: there is no char-server online (account: %s, ip: %s)." RETCODE, + account.userid, ip); + WFIFOW(fd,0) = 0x81; + WFIFOL(fd,2) = 1; // 01 = Server closed + WFIFOSET(fd,3); + } + } + } else { + memset(WFIFOP(fd,0), '\0', 23); + WFIFOW(fd,0) = 0x6a; + WFIFOB(fd,2) = result; + if (result == 6) { // 6 = Your are Prohibited to log in until %s + i = search_account_index(account.userid); + if (i != -1) { + if (auth_dat[i].ban_until_time != 0) { // if account is banned, we send ban timestamp + char tmpstr[256]; + strftime(tmpstr, 20, date_format, localtime(&auth_dat[i].ban_until_time)); + tmpstr[19] = '\0'; + memcpy(WFIFOP(fd,3), tmpstr, 20); + } else { // we send error message + memcpy(WFIFOP(fd,3), auth_dat[i].error_message, 20); + } + } + } + WFIFOSET(fd,23); + } + RFIFOSKIP(fd,(RFIFOW(fd,0) == 0x64) ? 55 : 47); + break; + + case 0x01db: // Sending request of the coding key + case 0x791a: // Sending request of the coding key (administration packet) + { + struct login_session_data *ld; + if (session[fd]->session_data) { + printf("login: abnormal request of MD5 key (already opened session).\n"); + session[fd]->eof = 1; + return 0; + } + ld = session[fd]->session_data = calloc(sizeof(*ld), 1); + if (!ld) { + printf("login: Request for md5 key: memory allocation failure (malloc)!\n"); + session[fd]->eof = 1; + return 0; + } + if (RFIFOW(fd,0) == 0x01db) { + login_log("Sending request of the coding key (ip: %s)" RETCODE, ip); + } else { + login_log("'ladmin': Sending request of the coding key (ip: %s)" RETCODE, ip); + } + // Creation of the coding key + memset(ld->md5key, '\0', sizeof(ld->md5key)); + ld->md5keylen = rand() % 4 + 12; + for(i = 0; i < ld->md5keylen; i++) + ld->md5key[i] = rand() % 255 + 1; + + RFIFOSKIP(fd,2); + WFIFOW(fd,0) = 0x01dc; + WFIFOW(fd,2) = 4 + ld->md5keylen; + memcpy(WFIFOP(fd,4), ld->md5key, ld->md5keylen); + WFIFOSET(fd,WFIFOW(fd,2)); + } + break; + + case 0x2710: // Connection request of a char-server + if (RFIFOREST(fd) < 86) + return 0; + { + int GM_value, len; + unsigned char* server_name; + account.userid = RFIFOP(fd,2); + account.userid[23] = '\0'; + remove_control_chars(account.userid); + account.passwd = RFIFOP(fd,26); + account.passwd[23] = '\0'; + remove_control_chars(account.passwd); + account.passwdenc = 0; + server_name = RFIFOP(fd,60); + server_name[19] = '\0'; + remove_control_chars(server_name); + login_log("Connection request of the char-server '%s' @ %d.%d.%d.%d:%d (ip: %s)" RETCODE, + server_name, RFIFOB(fd,54), RFIFOB(fd,55), RFIFOB(fd,56), RFIFOB(fd,57), RFIFOW(fd,58), ip); + result = mmo_auth(&account, fd); + if (result == -1 && account.sex == 2 && account.account_id < MAX_SERVERS && server_fd[account.account_id] == -1) { + login_log("Connection of the char-server '%s' accepted (account: %s, pass: %s, ip: %s)" RETCODE, + server_name, account.userid, account.passwd, ip); + printf("Connection of the char-server '%s' accepted.\n", server_name); + memset(&server[account.account_id], 0, sizeof(struct mmo_char_server)); + server[account.account_id].ip = RFIFOL(fd,54); + server[account.account_id].port = RFIFOW(fd,58); + memcpy(server[account.account_id].name, server_name, 20); + server[account.account_id].users = 0; + server[account.account_id].maintenance = RFIFOW(fd,82); + server[account.account_id].new = RFIFOW(fd,84); + server_fd[account.account_id] = fd; + if(anti_freeze_enable) + server_freezeflag[account.account_id] = 5; // Char-server anti-freeze system. Counter. 5 ok, 4...0 freezed + WFIFOW(fd,0) = 0x2711; + WFIFOB(fd,2) = 0; + WFIFOSET(fd,3); + session[fd]->func_parse = parse_fromchar; + realloc_fifo(fd, FIFOSIZE_SERVERLINK, FIFOSIZE_SERVERLINK); + // send GM account to char-server + len = 4; + WFIFOW(fd,0) = 0x2732; + for(i = 0; i < auth_num; i++) + // send only existing accounts. We can not create a GM account when server is online. + if ((GM_value = isGM(auth_dat[i].account_id)) > 0) { + WFIFOL(fd,len) = auth_dat[i].account_id; + WFIFOB(fd,len+4) = (unsigned char)GM_value; + len += 5; + } + WFIFOW(fd,2) = len; + WFIFOSET(fd,len); + } else { + login_log("Connexion of the char-server '%s' REFUSED (account: %s, pass: %s, ip: %s)" RETCODE, + server_name, account.userid, account.passwd, ip); + WFIFOW(fd,0) = 0x2711; + WFIFOB(fd,2) = 3; + WFIFOSET(fd,3); + } + } + RFIFOSKIP(fd,86); + return 0; + + case 0x7530: // Request of the server version + login_log("Sending of the server version (ip: %s)" RETCODE, ip); + WFIFOW(fd,0) = 0x7531; + WFIFOB(fd,2) = ATHENA_MAJOR_VERSION; + WFIFOB(fd,3) = ATHENA_MINOR_VERSION; + WFIFOB(fd,4) = ATHENA_REVISION; + WFIFOB(fd,5) = ATHENA_RELEASE_FLAG; + WFIFOB(fd,6) = ATHENA_OFFICIAL_FLAG; + WFIFOB(fd,7) = ATHENA_SERVER_LOGIN; + WFIFOW(fd,8) = ATHENA_MOD_VERSION; + WFIFOSET(fd,10); + RFIFOSKIP(fd,2); + break; + + case 0x7532: // Request to end connection + login_log("End of connection (ip: %s)" RETCODE, ip); + session[fd]->eof = 1; + return 0; + + case 0x7918: // Request for administation login + if (RFIFOREST(fd) < 4 || RFIFOREST(fd) < ((RFIFOW(fd,2) == 0) ? 28 : 20)) + return 0; + WFIFOW(fd,0) = 0x7919; + WFIFOB(fd,2) = 1; + if (!check_ladminip(session[fd]->client_addr.sin_addr.s_addr)) { + login_log("'ladmin'-login: Connection in administration mode refused: IP isn't authorised (ladmin_allow, ip: %s)." RETCODE, ip); + } else { + struct login_session_data *ld = session[fd]->session_data; + if (RFIFOW(fd,2) == 0) { // non encrypted password + unsigned char* password; + password = RFIFOP(fd,4); + password[23] = '\0'; + remove_control_chars(password); + // If remote administration is enabled and password sent by client matches password read from login server configuration file + if ((admin_state == 1) && (strcmp(password, admin_pass) == 0)) { + login_log("'ladmin'-login: Connection in administration mode accepted (non encrypted password: %s, ip: %s)" RETCODE, password, ip); + printf("Connection of a remote administration accepted (non encrypted password).\n"); + WFIFOB(fd,2) = 0; + session[fd]->func_parse = parse_admin; + } else if (admin_state != 1) + login_log("'ladmin'-login: Connection in administration mode REFUSED - remote administration is disabled (non encrypted password: %s, ip: %s)" RETCODE, password, ip); + else + login_log("'ladmin'-login: Connection in administration mode REFUSED - invalid password (non encrypted password: %s, ip: %s)" RETCODE, password, ip); + } else { // encrypted password + if (!ld) + printf("'ladmin'-login: error! MD5 key not created/requested for an administration login.\n"); + else { + char md5str[64] = "", md5bin[32]; + if (RFIFOW(fd,2) == 1) { + strncpy(md5str, ld->md5key, sizeof(ld->md5key)); // 20 + strcat(md5str, admin_pass); // 24 + } else if (RFIFOW(fd,2) == 2) { + strncpy(md5str, admin_pass, sizeof(admin_pass)); // 24 + strcat(md5str, ld->md5key); // 20 + } + MD5_String2binary(md5str, md5bin); + // If remote administration is enabled and password hash sent by client matches hash of password read from login server configuration file + if ((admin_state == 1) && (memcmp(md5bin, RFIFOP(fd,4), 16) == 0)) { + login_log("'ladmin'-login: Connection in administration mode accepted (encrypted password, ip: %s)" RETCODE, ip); + printf("Connection of a remote administration accepted (encrypted password).\n"); + WFIFOB(fd,2) = 0; + session[fd]->func_parse = parse_admin; + } else if (admin_state != 1) + login_log("'ladmin'-login: Connection in administration mode REFUSED - remote administration is disabled (encrypted password, ip: %s)" RETCODE, ip); + else + login_log("'ladmin'-login: Connection in administration mode REFUSED - invalid password (encrypted password, ip: %s)" RETCODE, ip); + } + } + } + WFIFOSET(fd,3); + RFIFOSKIP(fd, (RFIFOW(fd,2) == 0) ? 28 : 20); + break; + + default: + if (save_unknown_packets) { + FILE *logfp; + char tmpstr[24]; + struct timeval tv; + logfp = fopen(login_log_unknown_packets_filename, "a"); + if (logfp) { + gettimeofday(&tv, NULL); + strftime(tmpstr, 23, date_format, localtime(&(tv.tv_sec))); + fprintf(logfp, "%s.%03d: receiving of an unknown packet -> disconnection" RETCODE, tmpstr, (int)tv.tv_usec / 1000); + fprintf(logfp, "parse_login: connection #%d (ip: %s), packet: 0x%x (with being read: %d)." RETCODE, fd, ip, RFIFOW(fd,0), RFIFOREST(fd)); + fprintf(logfp, "Detail (in hex):" RETCODE); + fprintf(logfp, "---- 00-01-02-03-04-05-06-07 08-09-0A-0B-0C-0D-0E-0F" RETCODE); + memset(tmpstr, '\0', sizeof(tmpstr)); + for(i = 0; i < RFIFOREST(fd); i++) { + if ((i & 15) == 0) + fprintf(logfp, "%04X ",i); + fprintf(logfp, "%02x ", RFIFOB(fd,i)); + if (RFIFOB(fd,i) > 0x1f) + tmpstr[i % 16] = RFIFOB(fd,i); + else + tmpstr[i % 16] = '.'; + if ((i - 7) % 16 == 0) // -8 + 1 + fprintf(logfp, " "); + else if ((i + 1) % 16 == 0) { + fprintf(logfp, " %s" RETCODE, tmpstr); + memset(tmpstr, '\0', sizeof(tmpstr)); + } + } + if (i % 16 != 0) { + for(j = i; j % 16 != 0; j++) { + fprintf(logfp, " "); + if ((j - 7) % 16 == 0) // -8 + 1 + fprintf(logfp, " "); + } + fprintf(logfp, " %s" RETCODE, tmpstr); + } + fprintf(logfp, RETCODE); + fclose(logfp); + } + } + login_log("End of connection, unknown packet (ip: %s)" RETCODE, ip); + session[fd]->eof = 1; + return 0; + } + } + return 0; +} + + +//------------------------------------------------- +// Return numerical value of a switch configuration +// on/off, english, fran軋is, deutsch, espaol +//------------------------------------------------- +int config_switch(const char *str) { + if (strcmpi(str, "on") == 0 || strcmpi(str, "yes") == 0 || strcmpi(str, "oui") == 0 || strcmpi(str, "ja") == 0 || strcmpi(str, "si") == 0) + return 1; + if (strcmpi(str, "off") == 0 || strcmpi(str, "no") == 0 || strcmpi(str, "non") == 0 || strcmpi(str, "nein") == 0) + return 0; + + return atoi(str); +} + +//---------------------------------- +// Reading Lan Support configuration +//---------------------------------- +int login_lan_config_read(const char *lancfgName) { + int j; + struct hostent * h = NULL; + char line[1024], w1[1024], w2[1024]; + FILE *fp; + + // set default configuration + strncpy(lan_char_ip, "127.0.0.1", sizeof(lan_char_ip)); + subneti[0] = 127; + subneti[1] = 0; + subneti[2] = 0; + subneti[3] = 1; + for(j = 0; j < 4; j++) + subnetmaski[j] = 255; + + fp = fopen(lancfgName, "r"); + + if (fp == NULL) { + printf("***WARNING: LAN Support configuration file is not found: %s\n", lancfgName); + return 1; + } + + printf("---Start reading Lan Support configuration file\n"); + + while(fgets(line, sizeof(line)-1, fp)) { + if (line[0] == '/' && line[1] == '/') + continue; + + line[sizeof(line)-1] = '\0'; + if (sscanf(line,"%[^:]: %[^\r\n]", w1, w2) != 2) + continue; + + remove_control_chars(w1); + remove_control_chars(w2); + if (strcmpi(w1, "lan_char_ip") == 0) { // Read Char-Server Lan IP Address + h = gethostbyname(w2); + if (h != NULL) { + sprintf(lan_char_ip, "%d.%d.%d.%d", (unsigned char)h->h_addr[0], (unsigned char)h->h_addr[1], (unsigned char)h->h_addr[2], (unsigned char)h->h_addr[3]); + } else { + strncpy(lan_char_ip, w2, sizeof(lan_char_ip)); + lan_char_ip[sizeof(lan_char_ip)-1] = '\0'; + } + printf("LAN IP of char-server: %s.\n", lan_char_ip); + } else if (strcmpi(w1, "subnet") == 0) { // Read Subnetwork + for(j = 0; j < 4; j++) + subneti[j] = 0; + h = gethostbyname(w2); + if (h != NULL) { + for(j = 0; j < 4; j++) + subneti[j] = (unsigned char)h->h_addr[j]; + } else { + sscanf(w2, "%d.%d.%d.%d", &subneti[0], &subneti[1], &subneti[2], &subneti[3]); + } + printf("Sub-network of the char-server: %d.%d.%d.%d.\n", subneti[0], subneti[1], subneti[2], subneti[3]); + } else if (strcmpi(w1, "subnetmask") == 0) { // Read Subnetwork Mask + for(j = 0; j < 4; j++) + subnetmaski[j] = 255; + h = gethostbyname(w2); + if (h != NULL) { + for(j = 0; j < 4; j++) + subnetmaski[j] = (unsigned char)h->h_addr[j]; + } else { + sscanf(w2, "%d.%d.%d.%d", &subnetmaski[0], &subnetmaski[1], &subnetmaski[2], &subnetmaski[3]); + } + printf("Sub-network mask of the char-server: %d.%d.%d.%d.\n", subnetmaski[0], subnetmaski[1], subnetmaski[2], subnetmaski[3]); + } + } + fclose(fp); + + // log the LAN configuration + login_log("The LAN configuration of the server is set:" RETCODE); + login_log("- with LAN IP of char-server: %s." RETCODE, lan_char_ip); + login_log("- with the sub-network of the char-server: %d.%d.%d.%d/%d.%d.%d.%d." RETCODE, + subneti[0], subneti[1], subneti[2], subneti[3], subnetmaski[0], subnetmaski[1], subnetmaski[2], subnetmaski[3]); + + // sub-network check of the char-server + { + unsigned int a0, a1, a2, a3; + unsigned char p[4]; + sscanf(lan_char_ip, "%d.%d.%d.%d", &a0, &a1, &a2, &a3); + p[0] = a0; p[1] = a1; p[2] = a2; p[3] = a3; + printf("LAN test of LAN IP of the char-server: "); + if (lan_ip_check(p) == 0) { + printf("\033[1;31m***ERROR: LAN IP of the char-server doesn't belong to the specified Sub-network\033[0m\n"); + login_log("***ERROR: LAN IP of the char-server doesn't belong to the specified Sub-network." RETCODE); + } + } + + printf("---End reading of Lan Support configuration file\n"); + + return 0; +} + +//----------------------------------- +// Reading general configuration file +//----------------------------------- +int login_config_read(const char *cfgName) { + char line[1024], w1[1024], w2[1024]; + FILE *fp; + + fp = fopen(cfgName, "r"); + if (fp == NULL) { + printf("Configuration file (%s) not found.\n", cfgName); + return 1; + } + + printf("---Start reading of Login Server configuration file (%s)\n", cfgName); + while(fgets(line, sizeof(line)-1, fp)) { + if (line[0] == '/' && line[1] == '/') + continue; + + line[sizeof(line)-1] = '\0'; + if (sscanf(line, "%[^:]: %[^\r\n]", w1, w2) == 2) { + remove_control_chars(w1); + remove_control_chars(w2); + + if (strcmpi(w1, "admin_state") == 0) { + admin_state = config_switch(w2); + } else if (strcmpi(w1, "admin_pass") == 0) { + strncpy(admin_pass, w2, sizeof(admin_pass)); + admin_pass[sizeof(admin_pass)-1] = '\0'; + } else if (strcmpi(w1, "ladminallowip") == 0) { + if (strcmpi(w2, "clear") == 0) { + if (access_ladmin_allow) + free(access_ladmin_allow); + access_ladmin_allow = NULL; + access_ladmin_allownum = 0; + } else { + if (strcmpi(w2, "all") == 0) { + // reset all previous values + if (access_ladmin_allow) + free(access_ladmin_allow); + // set to all + access_ladmin_allow = calloc(ACO_STRSIZE, 1); + access_ladmin_allownum = 1; + access_ladmin_allow[0] = '\0'; + } else if (w2[0] && !(access_ladmin_allownum == 1 && access_ladmin_allow[0] == '\0')) { // don't add IP if already 'all' + if (access_ladmin_allow) + access_ladmin_allow = realloc(access_ladmin_allow, (access_ladmin_allownum+1) * ACO_STRSIZE); + else + access_ladmin_allow = calloc(ACO_STRSIZE, 1); + strncpy(access_ladmin_allow + (access_ladmin_allownum++) * ACO_STRSIZE, w2, ACO_STRSIZE); + access_ladmin_allow[access_ladmin_allownum * ACO_STRSIZE - 1] = '\0'; + } + } + } else if (strcmpi(w1, "gm_pass") == 0) { + strncpy(gm_pass, w2, sizeof(gm_pass)); + gm_pass[sizeof(gm_pass)-1] = '\0'; + } else if (strcmpi(w1, "level_new_gm") == 0) { + level_new_gm = atoi(w2); + } else if (strcmpi(w1, "new_account") == 0) { + new_account_flag = config_switch(w2); + } else if (strcmpi(w1, "login_port") == 0) { + login_port = atoi(w2); + } else if (strcmpi(w1, "account_filename") == 0) { + strncpy(account_filename, w2, sizeof(account_filename)); + account_filename[sizeof(account_filename)-1] = '\0'; + } else if (strcmpi(w1, "gm_account_filename") == 0) { + strncpy(GM_account_filename, w2, sizeof(GM_account_filename)); + GM_account_filename[sizeof(GM_account_filename)-1] = '\0'; + } else if (strcmpi(w1, "gm_account_filename_check_timer") == 0) { + gm_account_filename_check_timer = atoi(w2); + } else if (strcmpi(w1, "login_log_filename") == 0) { + strncpy(login_log_filename, w2, sizeof(login_log_filename)); + login_log_filename[sizeof(login_log_filename)-1] = '\0'; + } else if (strcmpi(w1, "login_log_unknown_packets_filename") == 0) { + strncpy(login_log_unknown_packets_filename, w2, sizeof(login_log_unknown_packets_filename)); + login_log_unknown_packets_filename[sizeof(login_log_unknown_packets_filename)-1] = '\0'; + } else if (strcmpi(w1, "save_unknown_packets") == 0) { + save_unknown_packets = config_switch(w2); + } else if (strcmpi(w1, "display_parse_login") == 0) { + display_parse_login = config_switch(w2); // 0: no, 1: yes + } else if (strcmpi(w1, "display_parse_admin") == 0) { + display_parse_admin = config_switch(w2); // 0: no, 1: yes + } else if (strcmpi(w1, "display_parse_fromchar") == 0) { + display_parse_fromchar = config_switch(w2); // 0: no, 1: yes (without packet 0x2714), 2: all packets + } else if (strcmpi(w1, "date_format") == 0) { // note: never have more than 19 char for the date! + switch (atoi(w2)) { + case 0: + strcpy(date_format, "%d-%m-%Y %H:%M:%S"); // 31-12-2004 23:59:59 + break; + case 1: + strcpy(date_format, "%m-%d-%Y %H:%M:%S"); // 12-31-2004 23:59:59 + break; + case 2: + strcpy(date_format, "%Y-%d-%m %H:%M:%S"); // 2004-31-12 23:59:59 + break; + case 3: + strcpy(date_format, "%Y-%m-%d %H:%M:%S"); // 2004-12-31 23:59:59 + break; + } + } else if (strcmpi(w1, "min_level_to_connect") == 0) { + min_level_to_connect = atoi(w2); + } else if (strcmpi(w1, "add_to_unlimited_account") == 0) { + add_to_unlimited_account = config_switch(w2); + } else if (strcmpi(w1, "start_limited_time") == 0) { + start_limited_time = atoi(w2); + } else if (strcmpi(w1, "check_ip_flag") == 0) { + check_ip_flag = config_switch(w2); + } else if (strcmpi(w1, "order") == 0) { + access_order = atoi(w2); + if (strcmpi(w2, "deny,allow") == 0 || + strcmpi(w2, "deny, allow") == 0) access_order = ACO_DENY_ALLOW; + if (strcmpi(w2, "allow,deny") == 0 || + strcmpi(w2, "allow, deny") == 0) access_order = ACO_ALLOW_DENY; + if (strcmpi(w2, "mutual-failture") == 0 || + strcmpi(w2, "mutual-failure") == 0) access_order = ACO_MUTUAL_FAILTURE; + } else if (strcmpi(w1, "allow") == 0) { + if (strcmpi(w2, "clear") == 0) { + if (access_allow) + free(access_allow); + access_allow = NULL; + access_allownum = 0; + } else { + if (strcmpi(w2, "all") == 0) { + // reset all previous values + if (access_allow) + free(access_allow); + // set to all + access_allow = calloc(ACO_STRSIZE, 1); + access_allownum = 1; + access_allow[0] = '\0'; + } else if (w2[0] && !(access_allownum == 1 && access_allow[0] == '\0')) { // don't add IP if already 'all' + if (access_allow) + access_allow = realloc(access_allow, (access_allownum+1) * ACO_STRSIZE); + else + access_allow = calloc(ACO_STRSIZE, 1); + strncpy(access_allow + (access_allownum++) * ACO_STRSIZE, w2, ACO_STRSIZE); + access_allow[access_allownum * ACO_STRSIZE - 1] = '\0'; + } + } + } else if (strcmpi(w1, "deny") == 0) { + if (strcmpi(w2, "clear") == 0) { + if (access_deny) + free(access_deny); + access_deny = NULL; + access_denynum = 0; + } else { + if (strcmpi(w2, "all") == 0) { + // reset all previous values + if (access_deny) + free(access_deny); + // set to all + access_deny = calloc(ACO_STRSIZE, 1); + access_denynum = 1; + access_deny[0] = '\0'; + } else if (w2[0] && !(access_denynum == 1 && access_deny[0] == '\0')) { // don't add IP if already 'all' + if (access_deny) + access_deny = realloc(access_deny, (access_denynum+1) * ACO_STRSIZE); + else + access_deny = calloc(ACO_STRSIZE, 1); + strncpy(access_deny + (access_denynum++) * ACO_STRSIZE, w2, ACO_STRSIZE); + access_deny[access_denynum * ACO_STRSIZE - 1] = '\0'; + } + } + } else if(strcmpi(w1,"anti_freeze_enable")==0){ + anti_freeze_enable = config_switch(w2); + } else if (strcmpi(w1, "anti_freeze_interval") == 0) { + ANTI_FREEZE_INTERVAL = atoi(w2); + if (ANTI_FREEZE_INTERVAL < 5) + ANTI_FREEZE_INTERVAL = 5; // minimum 5 seconds + } else if (strcmpi(w1, "import") == 0) { + login_config_read(w2); + } + } + } + fclose(fp); + + printf("---End reading of Login Server configuration file.\n"); + + return 0; +} + +//------------------------------------- +// Displaying of configuration warnings +//------------------------------------- +void display_conf_warnings(void) { + if (admin_state != 0 && admin_state != 1) { + printf("***WARNING: Invalid value for admin_state parameter -> set to 0 (no remote admin).\n"); + admin_state = 0; + } + + if (admin_state == 1) { + if (admin_pass[0] == '\0') { + printf("***WARNING: Administrator password is void (admin_pass).\n"); + } else if (strcmp(admin_pass, "admin") == 0) { + printf("***WARNING: You are using the default administrator password (admin_pass).\n"); + printf(" We highly recommend that you change it.\n"); + } + } + + if (gm_pass[0] == '\0') { + printf("***WARNING: 'To GM become' password is void (gm_pass).\n"); + printf(" We highly recommend that you set one password.\n"); + } else if (strcmp(gm_pass, "gm") == 0) { + printf("***WARNING: You are using the default GM password (gm_pass).\n"); + printf(" We highly recommend that you change it.\n"); + } + + if (level_new_gm < 0 || level_new_gm > 99) { + printf("***WARNING: Invalid value for level_new_gm parameter -> set to 60 (default).\n"); + level_new_gm = 60; + } + + if (new_account_flag != 0 && new_account_flag != 1) { + printf("***WARNING: Invalid value for new_account parameter -> set to 0 (no new account).\n"); + new_account_flag = 0; + } + + if (login_port < 1024 || login_port > 65535) { + printf("***WARNING: Invalid value for login_port parameter -> set to 6900 (default).\n"); + login_port = 6900; + } + + if (gm_account_filename_check_timer < 0) { + printf("***WARNING: Invalid value for gm_account_filename_check_timer parameter.\n"); + printf(" -> set to 15 sec (default).\n"); + gm_account_filename_check_timer = 15; + } else if (gm_account_filename_check_timer == 1) { + printf("***WARNING: Invalid value for gm_account_filename_check_timer parameter.\n"); + printf(" -> set to 2 sec (minimum value).\n"); + gm_account_filename_check_timer = 2; + } + + if (save_unknown_packets != 0 && save_unknown_packets != 1) { + printf("WARNING: Invalid value for save_unknown_packets parameter -> set to 0-no save.\n"); + save_unknown_packets = 0; + } + + if (display_parse_login != 0 && display_parse_login != 1) { // 0: no, 1: yes + printf("***WARNING: Invalid value for display_parse_login parameter\n"); + printf(" -> set to 0 (no display).\n"); + display_parse_login = 0; + } + + if (display_parse_admin != 0 && display_parse_admin != 1) { // 0: no, 1: yes + printf("***WARNING: Invalid value for display_parse_admin parameter\n"); + printf(" -> set to 0 (no display).\n"); + display_parse_admin = 0; + } + + if (display_parse_fromchar < 0 || display_parse_fromchar > 2) { // 0: no, 1: yes (without packet 0x2714), 2: all packets + printf("***WARNING: Invalid value for display_parse_fromchar parameter\n"); + printf(" -> set to 0 (no display).\n"); + display_parse_fromchar = 0; + } + + if (min_level_to_connect < 0) { // 0: all players, 1-99 at least gm level x + printf("***WARNING: Invalid value for min_level_to_connect (%d) parameter\n", min_level_to_connect); + printf(" -> set to 0 (any player).\n"); + min_level_to_connect = 0; + } else if (min_level_to_connect > 99) { // 0: all players, 1-99 at least gm level x + printf("***WARNING: Invalid value for min_level_to_connect (%d) parameter\n", min_level_to_connect); + printf(" -> set to 99 (only GM level 99).\n"); + min_level_to_connect = 99; + } + + if (add_to_unlimited_account != 0 && add_to_unlimited_account != 1) { // 0: no, 1: yes + printf("***WARNING: Invalid value for add_to_unlimited_account parameter\n"); + printf(" -> set to 0 (impossible to add a time to an unlimited account).\n"); + add_to_unlimited_account = 0; + } + + if (start_limited_time < -1) { // -1: create unlimited account, 0 or more: additionnal sec from now to create limited time + printf("***WARNING: Invalid value for start_limited_time parameter\n"); + printf(" -> set to -1 (new accounts are created with unlimited time).\n"); + start_limited_time = -1; + } + + if (check_ip_flag != 0 && check_ip_flag != 1) { // 0: no, 1: yes + printf("***WARNING: Invalid value for check_ip_flag parameter\n"); + printf(" -> set to 1 (check players ip between login-server & char-server).\n"); + check_ip_flag = 1; + } + + if (access_order == ACO_DENY_ALLOW) { + if (access_denynum == 1 && access_deny[0] == '\0') { + printf("***WARNING: The IP security order is 'deny,allow' (allow if not deny).\n"); + printf(" And you refuse ALL IP.\n"); + } + } else if (access_order == ACO_ALLOW_DENY) { + if (access_allownum == 0) { + printf("***WARNING: The IP security order is 'allow,deny' (deny if not allow).\n"); + printf(" But, NO IP IS AUTHORISED!\n"); + } + } else { // ACO_MUTUAL_FAILTURE + if (access_allownum == 0) { + printf("***WARNING: The IP security order is 'mutual-failture'\n"); + printf(" (allow if in the allow list and not in the deny list).\n"); + printf(" But, NO IP IS AUTHORISED!\n"); + } else if (access_denynum == 1 && access_deny[0] == '\0') { + printf("***WARNING: The IP security order is mutual-failture\n"); + printf(" (allow if in the allow list and not in the deny list).\n"); + printf(" But, you refuse ALL IP!\n"); + } + } + + return; +} + +//------------------------------- +// Save configuration in log file +//------------------------------- +void save_config_in_log(void) { + int i; + + // a newline in the log... + login_log(""); + login_log("The login-server starting..." RETCODE); + + // save configuration in log file + login_log("The configuration of the server is set:" RETCODE); + + if (admin_state != 1) + login_log("- with no remote administration." RETCODE); + else if (admin_pass[0] == '\0') + login_log("- with a remote administration with a VOID password." RETCODE); + else if (strcmp(admin_pass, "admin") == 0) + login_log("- with a remote administration with the DEFAULT password." RETCODE); + else + login_log("- with a remote administration with the password of %d character(s)." RETCODE, strlen(admin_pass)); + if (access_ladmin_allownum == 0 || (access_ladmin_allownum == 1 && access_ladmin_allow[0] == '\0')) { + login_log("- to accept any IP for remote administration" RETCODE); + } else { + login_log("- to accept following IP for remote administration:" RETCODE); + for(i = 0; i < access_ladmin_allownum; i++) + login_log(" %s" RETCODE, (char *)(access_ladmin_allow + i * ACO_STRSIZE)); + } + + if (gm_pass[0] == '\0') + login_log("- with a VOID 'To GM become' password (gm_pass)." RETCODE); + else if (strcmp(gm_pass, "gm") == 0) + login_log("- with the DEFAULT 'To GM become' password (gm_pass)." RETCODE); + else + login_log("- with a 'To GM become' password (gm_pass) of %d character(s)." RETCODE, strlen(gm_pass)); + if (level_new_gm == 0) + login_log("- to refuse any creation of GM with @gm." RETCODE); + else + login_log("- to create GM with level '%d' when @gm is used." RETCODE, level_new_gm); + + if (new_account_flag == 1) + login_log("- to ALLOW new users (with _F/_M)." RETCODE); + else + login_log("- to NOT ALLOW new users (with _F/_M)." RETCODE); + login_log("- with port: %d." RETCODE, login_port); + login_log("- with the accounts file name: '%s'." RETCODE, account_filename); + login_log("- with the GM accounts file name: '%s'." RETCODE, GM_account_filename); + if (gm_account_filename_check_timer == 0) + login_log("- to NOT check GM accounts file modifications." RETCODE); + else + login_log("- to check GM accounts file modifications every %d seconds." RETCODE, gm_account_filename_check_timer); + + // not necessary to log the 'login_log_filename', we are inside :) + + login_log("- with the unknown packets file name: '%s'." RETCODE, login_log_unknown_packets_filename); + if (save_unknown_packets) + login_log("- to SAVE all unkown packets." RETCODE); + else + login_log("- to SAVE only unkown packets sending by a char-server or a remote administration." RETCODE); + if (display_parse_login) + login_log("- to display normal parse packets on console." RETCODE); + else + login_log("- to NOT display normal parse packets on console." RETCODE); + if (display_parse_admin) + login_log("- to display administration parse packets on console." RETCODE); + else + login_log("- to NOT display administration parse packets on console." RETCODE); + if (display_parse_fromchar) + login_log("- to display char-server parse packets on console." RETCODE); + else + login_log("- to NOT display char-server parse packets on console." RETCODE); + + if (min_level_to_connect == 0) // 0: all players, 1-99 at least gm level x + login_log("- with no minimum level for connection." RETCODE); + else if (min_level_to_connect == 99) + login_log("- to accept only GM with level 99." RETCODE); + else + login_log("- to accept only GM with level %d or more." RETCODE, min_level_to_connect); + + if (add_to_unlimited_account) + login_log("- to authorize adjustment (with timeadd ladmin) on an unlimited account." RETCODE); + else + login_log("- to refuse adjustment (with timeadd ladmin) on an unlimited account. You must use timeset (ladmin command) before." RETCODE); + + if (start_limited_time < 0) + login_log("- to create new accounts with an unlimited time." RETCODE); + else if (start_limited_time == 0) + login_log("- to create new accounts with a limited time: time of creation." RETCODE); + else + login_log("- to create new accounts with a limited time: time of creation + %d second(s)." RETCODE, start_limited_time); + + if (check_ip_flag) + login_log("- with control of players IP between login-server and char-server." RETCODE); + else + login_log("- to not check players IP between login-server and char-server." RETCODE); + + if (access_order == ACO_DENY_ALLOW) { + if (access_denynum == 0) { + login_log("- with the IP security order: 'deny,allow' (allow if not deny). You refuse no IP." RETCODE); + } else if (access_denynum == 1 && access_deny[0] == '\0') { + login_log("- with the IP security order: 'deny,allow' (allow if not deny). You refuse ALL IP." RETCODE); + } else { + login_log("- with the IP security order: 'deny,allow' (allow if not deny). Refused IP are:" RETCODE); + for(i = 0; i < access_denynum; i++) + login_log(" %s" RETCODE, (char *)(access_deny + i * ACO_STRSIZE)); + } + } else if (access_order == ACO_ALLOW_DENY) { + if (access_allownum == 0) { + login_log("- with the IP security order: 'allow,deny' (deny if not allow). But, NO IP IS AUTHORISED!" RETCODE); + } else if (access_allownum == 1 && access_allow[0] == '\0') { + login_log("- with the IP security order: 'allow,deny' (deny if not allow). You authorise ALL IP." RETCODE); + } else { + login_log("- with the IP security order: 'allow,deny' (deny if not allow). Authorised IP are:" RETCODE); + for(i = 0; i < access_allownum; i++) + login_log(" %s" RETCODE, (char *)(access_allow + i * ACO_STRSIZE)); + } + } else { // ACO_MUTUAL_FAILTURE + login_log("- with the IP security order: 'mutual-failture' (allow if in the allow list and not in the deny list)." RETCODE); + if (access_allownum == 0) { + login_log(" But, NO IP IS AUTHORISED!" RETCODE); + } else if (access_denynum == 1 && access_deny[0] == '\0') { + login_log(" But, you refuse ALL IP!" RETCODE); + } else { + if (access_allownum == 1 && access_allow[0] == '\0') { + login_log(" You authorise ALL IP." RETCODE); + } else { + login_log(" Authorised IP are:" RETCODE); + for(i = 0; i < access_allownum; i++) + login_log(" %s" RETCODE, (char *)(access_allow + i * ACO_STRSIZE)); + } + login_log(" Refused IP are:" RETCODE); + for(i = 0; i < access_denynum; i++) + login_log(" %s" RETCODE, (char *)(access_deny + i * ACO_STRSIZE)); + } + } +} + +//-------------------------------------- +// Function called at exit of the server +//-------------------------------------- +void do_final(void) { + int i, fd; + + mmo_auth_sync(); + + free(auth_dat); + free(gm_account_db); + for (i = 0; i < MAX_SERVERS; i++) { + if ((fd = server_fd[i]) >= 0) + delete_session(fd); + } + delete_session(login_fd); + + login_log("----End of login-server (normal end with closing of all files)." RETCODE); +} + +//------------------------------ +// Main function of login-server +//------------------------------ +int do_init(int argc, char **argv) { + int i, j; + + // read login-server configuration + login_config_read((argc > 1) ? argv[1] : LOGIN_CONF_NAME); + display_conf_warnings(); // not in login_config_read, because we can use 'import' option, and display same message twice or more + save_config_in_log(); // not before, because log file name can be changed + login_lan_config_read((argc > 1) ? argv[1] : LAN_CONF_NAME); + + srand(time(NULL)); + + for(i = 0; i< AUTH_FIFO_SIZE; i++) + auth_fifo[i].delflag = 1; + for(i = 0; i < MAX_SERVERS; i++) + server_fd[i] = -1; + + gm_account_db = numdb_init(); + + read_gm_account(); + mmo_auth_init(); + set_termfunc(mmo_auth_sync); + set_defaultparse(parse_login); + login_fd = make_listen_port(login_port); + + add_timer_func_list(check_auth_sync, "check_auth_sync"); + + i = add_timer_interval(gettick() + 60000, check_auth_sync, 0, 0, 60000); // every 60 sec we check if we must save accounts file (only if necessary to save) + + if(anti_freeze_enable > 0) { + add_timer_func_list(char_anti_freeze_system, "char_anti_freeze_system"); + i = add_timer_interval(gettick() + 1000, char_anti_freeze_system, 0, 0, ANTI_FREEZE_INTERVAL * 1000); + } + + // add timer to check GM accounts file modification + j = gm_account_filename_check_timer; + if (j == 0) // if we would not to check, we check every 60 sec, just to have timer (if we change timer, is was not necessary to check if timer already exists) + j = 60; + add_timer_func_list(check_GM_file, "check_GM_file"); + i = add_timer_interval(gettick() + j * 1000, check_GM_file, 0, 0, j * 1000); // every x sec we check if gm file has been changed + + login_log("The login-server is ready (Server is listening on the port %d)." RETCODE, login_port); + + printf("The login-server is \033[1;32mready\033[0m (Server is listening on the port %d).\n\n", login_port); + + atexit(do_final); + return 0; +} + + diff --git a/misc/src/login/login.h b/misc/src/login/login.h new file mode 100644 index 0000000..3a8a6d5 --- /dev/null +++ b/misc/src/login/login.h @@ -0,0 +1,38 @@ +// $Id: login.h,v 1.1.1.1 2004/09/10 17:26:53 MagicalTux Exp $ +#ifndef _LOGIN_H_ +#define _LOGIN_H_ + +#define MAX_SERVERS 30 + +#define LOGIN_CONF_NAME "conf/login_athena.conf" +#define LAN_CONF_NAME "conf/lan_support.conf" +#define PASSWORDENC 3 // A definition is given when making an encryption password correspond. + // It is 1 at the time of passwordencrypt. + // It is made into 2 at the time of passwordencrypt2. + // When it is made 3, it corresponds to both. +#define START_ACCOUNT_NUM 2000000 +#define END_ACCOUNT_NUM 100000000 + +struct mmo_account { + char* userid; + char* passwd; + int passwdenc; + + long account_id; + long login_id1; + long login_id2; + long char_id; + char lastlogin[24]; + int sex; +}; + +struct mmo_char_server { + char name[20]; + long ip; + short port; + int users; + int maintenance; + int new; +}; + +#endif diff --git a/misc/src/login/md5calc.c b/misc/src/login/md5calc.c new file mode 100644 index 0000000..96bfc34 --- /dev/null +++ b/misc/src/login/md5calc.c @@ -0,0 +1,237 @@ +// $Id: md5calc.c,v 1.1.1.1 2004/09/10 17:26:54 MagicalTux Exp $ +/*********************************************************** + * md5 calculation algorithm + * + * The source code referred to the following URL. + * http://www.geocities.co.jp/SiliconValley-Oakland/8878/lab17/lab17.html + * + ***********************************************************/ + +#include "md5calc.h" +#include <string.h> +#include <stdio.h> + +#ifndef UINT_MAX +#define UINT_MAX 4294967295U +#endif + +// Global variable +static unsigned int *pX; + +// Stirng Table +static const unsigned int T[] = { + 0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, //0 + 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501, //4 + 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, //8 + 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821, //12 + 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, //16 + 0xd62f105d, 0x2441453, 0xd8a1e681, 0xe7d3fbc8, //20 + 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, //24 + 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a, //28 + 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, //32 + 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70, //36 + 0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x4881d05, //40 + 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665, //44 + 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, //48 + 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1, //52 + 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, //56 + 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391 //60 +}; + +// ROTATE_LEFT The left is made to rotate x [ n-bit ]. This is diverted as it is from RFC. +#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32-(n)))) + +// The function used for other calculation +static unsigned int F(unsigned int X, unsigned int Y, unsigned int Z) +{ + return (X & Y) | (~X & Z); +} +static unsigned int G(unsigned int X, unsigned int Y, unsigned int Z) +{ + return (X & Z) | (Y & ~Z); +} +static unsigned int H(unsigned int X, unsigned int Y, unsigned int Z) +{ + return X ^ Y ^ Z; +} +static unsigned int I(unsigned int X, unsigned int Y, unsigned int Z) +{ + return Y ^ (X | ~Z); +} + +static unsigned int Round(unsigned int a, unsigned int b, unsigned int FGHI, + unsigned int k, unsigned int s, unsigned int i) +{ + return b + ROTATE_LEFT(a + FGHI + pX[k] + T[i], s); +} + +static void Round1(unsigned int *a, unsigned int b, unsigned int c, + unsigned int d,unsigned int k, unsigned int s, unsigned int i) +{ + *a = Round(*a, b, F(b,c,d), k, s, i); +} +static void Round2(unsigned int *a, unsigned int b, unsigned int c, + unsigned int d,unsigned int k, unsigned int s, unsigned int i) +{ + *a = Round(*a, b, G(b,c,d), k, s, i); +} +static void Round3(unsigned int *a, unsigned int b, unsigned int c, + unsigned int d,unsigned int k, unsigned int s, unsigned int i) +{ + *a = Round(*a, b, H(b,c,d), k, s, i); +} +static void Round4(unsigned int *a, unsigned int b, unsigned int c, + unsigned int d,unsigned int k, unsigned int s, unsigned int i) +{ + *a = Round(*a, b, I(b,c,d), k, s, i); +} + +static void MD5_Round_Calculate(const unsigned char *block, + unsigned int *A2, unsigned int *B2, unsigned int *C2, unsigned int *D2) +{ + //create X It is since it is required. + unsigned int X[16]; //512bit 64byte + int j,k; + + //Save A as AA, B as BB, C as CC, and and D as DD (saving of A, B, C, and D) + unsigned int A=*A2, B=*B2, C=*C2, D=*D2; + unsigned int AA = A,BB = B,CC = C,DD = D; + + //It is a large region variable reluctantly because of calculation of a round. . . for Round1...4 + pX = X; + + //Copy block(padding_message) i into X + for (j=0,k=0; j<64; j+=4,k++) + X[k] = ( (unsigned int )block[j] ) // 8byte*4 -> 32byte conversion + | ( ((unsigned int )block[j+1]) << 8 ) // A function called Decode as used in the field of RFC + | ( ((unsigned int )block[j+2]) << 16 ) + | ( ((unsigned int )block[j+3]) << 24 ); + + + //Round 1 + Round1(&A,B,C,D, 0, 7, 0); Round1(&D,A,B,C, 1, 12, 1); Round1(&C,D,A,B, 2, 17, 2); Round1(&B,C,D,A, 3, 22, 3); + Round1(&A,B,C,D, 4, 7, 4); Round1(&D,A,B,C, 5, 12, 5); Round1(&C,D,A,B, 6, 17, 6); Round1(&B,C,D,A, 7, 22, 7); + Round1(&A,B,C,D, 8, 7, 8); Round1(&D,A,B,C, 9, 12, 9); Round1(&C,D,A,B, 10, 17, 10); Round1(&B,C,D,A, 11, 22, 11); + Round1(&A,B,C,D, 12, 7, 12); Round1(&D,A,B,C, 13, 12, 13); Round1(&C,D,A,B, 14, 17, 14); Round1(&B,C,D,A, 15, 22, 15); + + //Round 2 + Round2(&A,B,C,D, 1, 5, 16); Round2(&D,A,B,C, 6, 9, 17); Round2(&C,D,A,B, 11, 14, 18); Round2(&B,C,D,A, 0, 20, 19); + Round2(&A,B,C,D, 5, 5, 20); Round2(&D,A,B,C, 10, 9, 21); Round2(&C,D,A,B, 15, 14, 22); Round2(&B,C,D,A, 4, 20, 23); + Round2(&A,B,C,D, 9, 5, 24); Round2(&D,A,B,C, 14, 9, 25); Round2(&C,D,A,B, 3, 14, 26); Round2(&B,C,D,A, 8, 20, 27); + Round2(&A,B,C,D, 13, 5, 28); Round2(&D,A,B,C, 2, 9, 29); Round2(&C,D,A,B, 7, 14, 30); Round2(&B,C,D,A, 12, 20, 31); + + //Round 3 + Round3(&A,B,C,D, 5, 4, 32); Round3(&D,A,B,C, 8, 11, 33); Round3(&C,D,A,B, 11, 16, 34); Round3(&B,C,D,A, 14, 23, 35); + Round3(&A,B,C,D, 1, 4, 36); Round3(&D,A,B,C, 4, 11, 37); Round3(&C,D,A,B, 7, 16, 38); Round3(&B,C,D,A, 10, 23, 39); + Round3(&A,B,C,D, 13, 4, 40); Round3(&D,A,B,C, 0, 11, 41); Round3(&C,D,A,B, 3, 16, 42); Round3(&B,C,D,A, 6, 23, 43); + Round3(&A,B,C,D, 9, 4, 44); Round3(&D,A,B,C, 12, 11, 45); Round3(&C,D,A,B, 15, 16, 46); Round3(&B,C,D,A, 2, 23, 47); + + //Round 4 + Round4(&A,B,C,D, 0, 6, 48); Round4(&D,A,B,C, 7, 10, 49); Round4(&C,D,A,B, 14, 15, 50); Round4(&B,C,D,A, 5, 21, 51); + Round4(&A,B,C,D, 12, 6, 52); Round4(&D,A,B,C, 3, 10, 53); Round4(&C,D,A,B, 10, 15, 54); Round4(&B,C,D,A, 1, 21, 55); + Round4(&A,B,C,D, 8, 6, 56); Round4(&D,A,B,C, 15, 10, 57); Round4(&C,D,A,B, 6, 15, 58); Round4(&B,C,D,A, 13, 21, 59); + Round4(&A,B,C,D, 4, 6, 60); Round4(&D,A,B,C, 11, 10, 61); Round4(&C,D,A,B, 2, 15, 62); Round4(&B,C,D,A, 9, 21, 63); + + // Then perform the following additions. (let's add) + *A2 = A + AA; + *B2 = B + BB; + *C2 = C + CC; + *D2 = D + DD; + + //The clearance of confidential information + memset(pX, 0, sizeof(X)); +} + +//------------------------------------------------------------------- +// The function for the exteriors + +/** output is the coded binary in the character sequence which wants to code string. */ +void MD5_String2binary(const char * string, char * output) +{ +//var + /*8bit*/ + unsigned char padding_message[64]; //Extended message 512bit 64byte + unsigned char *pstring; //The position of string in the present scanning notes is held. + +// unsigned char digest[16]; + /*32bit*/ + unsigned int string_byte_len, //The byte chief of string is held. + string_bit_len, //The bit length of string is held. + copy_len, //The number of bytes which is used by 1-3 and which remained + msg_digest[4]; //Message digest 128bit 4byte + unsigned int *A = &msg_digest[0], //The message digest in accordance with RFC (reference) + *B = &msg_digest[1], + *C = &msg_digest[2], + *D = &msg_digest[3]; + int i; + +//prog + //Step 3.Initialize MD Buffer (although it is the initialization; step 3 of A, B, C, and D -- unavoidable -- a head) + *A = 0x67452301; + *B = 0xefcdab89; + *C = 0x98badcfe; + *D = 0x10325476; + + //Step 1.Append Padding Bits (extension of a mark bit) + //1-1 + string_byte_len = strlen(string); //The byte chief of a character sequence is acquired. + pstring = (unsigned char *)string; //The position of the present character sequence is set. + + //1-2 Repeat calculation until length becomes less than 64 bytes. + for (i=string_byte_len; 64<=i; i-=64,pstring+=64) + MD5_Round_Calculate(pstring, A,B,C,D); + + //1-3 + copy_len = string_byte_len % 64; //The number of bytes which remained is computed. + strncpy((char *)padding_message, (char *)pstring, copy_len); //A message is copied to an extended bit sequence. + memset(padding_message+copy_len, 0, 64 - copy_len); //It buries by 0 until it becomes extended bit length. + padding_message[copy_len] |= 0x80; //The next of a message is 1. + + //1-4 + //If 56 bytes or more (less than 64 bytes) of remainder becomes, it will calculate by extending to 64 bytes. + if (56 <= copy_len) { + MD5_Round_Calculate(padding_message, A,B,C,D); + memset(padding_message, 0, 56); //56 bytes is newly fill uped with 0. + } + + + //Step 2.Append Length (the information on length is added) + string_bit_len = string_byte_len * 8; //From the byte chief to bit length (32 bytes of low rank) + memcpy(&padding_message[56], &string_bit_len, 4); //32 bytes of low rank is set. + + //When bit length cannot be expressed in 32 bytes of low rank, it is a beam raising to a higher rank. + if (UINT_MAX / 8 < string_byte_len) { + unsigned int high = (string_byte_len - UINT_MAX / 8) * 8; + memcpy(&padding_message[60], &high, 4); + } else + memset(&padding_message[60], 0, 4); //In this case, it is good for a higher rank at 0. + + //Step 4.Process Message in 16-Word Blocks (calculation of MD5) + MD5_Round_Calculate(padding_message, A,B,C,D); + + + //Step 5.Output (output) + memcpy(output,msg_digest,16); +// memcpy (digest, msg_digest, and 16); //8 byte*4 < - 32byte conversion A function called Encode as used in the field of RFC +/* sprintf(output, + "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", + digest[ 0], digest[ 1], digest[ 2], digest[ 3], + digest[ 4], digest[ 5], digest[ 6], digest[ 7], + digest[ 8], digest[ 9], digest[10], digest[11], + digest[12], digest[13], digest[14], digest[15]);*/ +} + +/** output is the coded character sequence in the character sequence which wants to code string. */ +void MD5_String(const char * string, char * output) +{ + unsigned char digest[16]; + + MD5_String2binary(string,digest); + sprintf(output, + "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", + digest[ 0], digest[ 1], digest[ 2], digest[ 3], + digest[ 4], digest[ 5], digest[ 6], digest[ 7], + digest[ 8], digest[ 9], digest[10], digest[11], + digest[12], digest[13], digest[14], digest[15]); +} + diff --git a/misc/src/login/md5calc.h b/misc/src/login/md5calc.h new file mode 100644 index 0000000..9137b5b --- /dev/null +++ b/misc/src/login/md5calc.h @@ -0,0 +1,8 @@ +// $Id: md5calc.h,v 1.1.1.1 2004/09/10 17:26:54 MagicalTux Exp $ +#ifndef _MD5CALC_H_ +#define _MD5CALC_H_ + +void MD5_String(const char * string, char * output); +void MD5_String2binary(const char * string, char * output); + +#endif diff --git a/misc/src/login_sql/GNUmakefile b/misc/src/login_sql/GNUmakefile new file mode 100644 index 0000000..c245b79 --- /dev/null +++ b/misc/src/login_sql/GNUmakefile @@ -0,0 +1,17 @@ +all: login-server_sql +sql: login-server_sql + +shared_libs=all +COMMON_OBJ = ../common/core.o ../common/socket.o ../common/timer.o ../common/db.o ../common/malloc.o +COMMON_H = ../common/core.h ../common/socket.h ../common/timer.h ../common/mmo.h ../common/version.h ../common/db.h ../common/malloc.h + +login-server_sql: login.o md5calc.o strlib.o $(COMMON_OBJ) + $(CC) -o ../../$@ $^ $(LIB_S) + +login.o: login.c login.h md5calc.h strlib.h $(COMMON_H) +md5calc.o: md5calc.c md5calc.h +strlib.o: strlib.c strlib.h + +clean: + rm -f *.o ../../login-server_sql + diff --git a/misc/src/login_sql/Makefile b/misc/src/login_sql/Makefile new file mode 100644 index 0000000..d1f2734 --- /dev/null +++ b/misc/src/login_sql/Makefile @@ -0,0 +1,17 @@ +all: login-server_sql
+sql: login-server_sql
+
+shared_libs=all
+COMMON_OBJ = ../common/core.o ../common/socket.o ../common/timer.o ../common/db.o ../common/malloc.o
+COMMON_H = ../common/core.h ../common/socket.h ../common/timer.h ../common/mmo.h ../common/version.h ../common/db.h ../common/malloc.h
+
+login-server_sql: login.o md5calc.o strlib.o $(COMMON_OBJ)
+ $(CC) -o ../../$@ $^ $(LIB_S)
+
+login.o: login.c login.h md5calc.h strlib.h $(COMMON_H)
+md5calc.o: md5calc.c md5calc.h
+strlib.o: strlib.c strlib.h
+
+clean:
+ rm -f *.o ../../login-server_sql
+
diff --git a/misc/src/login_sql/login.c b/misc/src/login_sql/login.c new file mode 100644 index 0000000..2fab051 --- /dev/null +++ b/misc/src/login_sql/login.c @@ -0,0 +1,1682 @@ +// $Id: login.c,v 1.6 2004/09/19 21:12:07 Valaris Exp $ +// original : login2.c 2003/01/28 02:29:17 Rev.1.1.1.1 +// txt version 1.100 + +#include <sys/types.h> + +#ifdef LCCWIN32 +#include <winsock.h> +#pragma lib <libmysql.lib> +#else +#include <sys/socket.h> +#include <netinet/in.h> +#include <sys/time.h> +#include <sys/ioctl.h> +#include <arpa/inet.h> +#endif + +#include <stdio.h> +#include <stdlib.h> +#include <netinet/in.h> +#include <sys/time.h> +#include <time.h> +#include <sys/ioctl.h> +#include <sys/stat.h> // for stat/lstat/fstat +#include <unistd.h> +#include <signal.h> +#include <fcntl.h> +#include <string.h> +#include <arpa/inet.h> + +//add include for DBMS(mysql) +#include <mysql.h> + +#include "strlib.h" +#include "timer.h" +/* +#include "timer.h" +#include "core.h" +#include "socket.h" +#include "login.h" +#include "mmo.h" +#include "version.h" +#include "db.h" +*/ + +#include "../common/core.h" +#include "../common/socket.h" +#include "login.h" +#include "../common/mmo.h" +#include "../common/version.h" +#include "../common/db.h" +#include "../common/timer.h" + +#ifdef PASSWORDENC +#include "md5calc.h" +#endif + +#ifdef MEMWATCH +#include "memwatch.h" +#endif + +#define J_MAX_MALLOC_SIZE 65535 + +//----------------------------------------------------- +// global variable +//----------------------------------------------------- +int account_id_count = START_ACCOUNT_NUM; +int server_num; +int new_account_flag = 0; +int login_port = 6900; +char lan_char_ip[128]; // Lan char ip added by kashy +int subnetmaski[4]; // Subnetmask added by kashy + +struct mmo_char_server server[MAX_SERVERS]; +int server_fd[MAX_SERVERS]; +int server_freezeflag[MAX_SERVERS]; // Char-server anti-freeze system. Counter. 5 ok, 4...0 freezed +int anti_freeze_enable = 0; +int ANTI_FREEZE_INTERVAL = 15; + +int login_fd; + +char date_format[32] = "%Y-%m-%d %H:%M:%S"; +int auth_num = 0, auth_max = 0; + +int min_level_to_connect = 0; // minimum level of player/GM (0: player, 1-99: gm) to connect on the server +int check_ip_flag = 1; // It's to check IP of a player between login-server and char-server (part of anti-hacking system) + +MYSQL mysql_handle; + +int ipban = 1; +int dynamic_account_ban = 1; +int dynamic_account_ban_class = 0; +int dynamic_pass_failure_ban = 1; +int dynamic_pass_failure_ban_time = 5; +int dynamic_pass_failure_ban_how_many = 3; +int dynamic_pass_failure_ban_how_long = 60; + +int login_server_port = 3306; +char login_server_ip[32] = "127.0.0.1"; +char login_server_id[32] = "ragnarok"; +char login_server_pw[32] = "ragnarok"; +char login_server_db[32] = "ragnarok"; +int use_md5_passwds = 0; +char login_db[256] = "login"; +char loginlog_db[256] = "loginlog"; + +// added to help out custom login tables, without having to recompile +// source so options are kept in the login_athena.conf or the inter_athena.conf +char login_db_account_id[256] = "account_id"; +char login_db_userid[256] = "userid"; +char login_db_user_pass[256] = "user_pass"; +char login_db_level[256] = "level"; + +char tmpsql[65535], tmp_sql[65535]; + +//----------------------------------------------------- + +#define AUTH_FIFO_SIZE 256 +struct { + int account_id,login_id1,login_id2; + int ip,sex,delflag; +} auth_fifo[AUTH_FIFO_SIZE]; + +int auth_fifo_pos = 0; + + +//----------------------------------------------------- + +static char md5key[20], md5keylen = 16; + +//----------------------------------------------------- +// check user level +//----------------------------------------------------- + +int isGM(int account_id) { + int level; + + MYSQL_RES* sql_res; + MYSQL_ROW sql_row; + level = 0; + sprintf(tmpsql,"SELECT `%s` FROM `%s` WHERE `%s`='%d'", login_db_level, login_db, login_db_account_id, account_id); + if (mysql_query(&mysql_handle, tmpsql)) { + printf("DB server Error (select GM Level to Memory)- %s\n", mysql_error(&mysql_handle)); + } + sql_res = mysql_store_result(&mysql_handle); + if (sql_res) { + sql_row = mysql_fetch_row(sql_res); + level = atoi(sql_row[0]); + if (level > 99) + level = 99; + } + + if (level == 0) { + return 0; + //not GM + } + + mysql_free_result(sql_res); + + return level; +} + +//----------------------------------------------------- +// Function to suppress control characters in a string. +//----------------------------------------------------- +int remove_control_chars(unsigned char *str) { + int i; + int change = 0; + + for(i = 0; str[i]; i++) { + if (str[i] < 32) { + str[i] = '_'; + change = 1; + } + } + + return change; +} + +//--------------------------------------------------- +// E-mail check: return 0 (not correct) or 1 (valid). +//--------------------------------------------------- +int e_mail_check(unsigned char *email) { + char ch; + unsigned char* last_arobas; + + // athena limits + if (strlen(email) < 3 || strlen(email) > 39) + return 0; + + // part of RFC limits (official reference of e-mail description) + if (strchr(email, '@') == NULL || email[strlen(email)-1] == '@') + return 0; + + if (email[strlen(email)-1] == '.') + return 0; + + last_arobas = strrchr(email, '@'); + + if (strstr(last_arobas, "@.") != NULL || + strstr(last_arobas, "..") != NULL) + return 0; + + for(ch = 1; ch < 32; ch++) { + if (strchr(last_arobas, ch) != NULL) { + return 0; + break; + } + } + + if (strchr(last_arobas, ' ') != NULL || + strchr(last_arobas, ';') != NULL) + return 0; + + // all correct + return 1; +} + +//----------------------------------------------------- +// Read Account database - mysql db +//----------------------------------------------------- +int mmo_auth_sqldb_init(void) { + + printf("Login server init....\n"); + + // memory initialize + printf("memory initialize....\n"); + + mysql_init(&mysql_handle); + + // DB connection start + printf("Connect Login Database Server....\n"); + if (!mysql_real_connect(&mysql_handle, login_server_ip, login_server_id, login_server_pw, + login_server_db, login_server_port, (char *)NULL, 0)) { + // pointer check + printf("%s\n", mysql_error(&mysql_handle)); + exit(1); + } else { + printf("connect success!\n"); + } + + sprintf(tmpsql, "INSERT INTO `%s`(`time`,`ip`,`user`,`rcode`,`log`) VALUES (NOW(), '', 'lserver', '100','login server started')", loginlog_db); + + //query + if (mysql_query(&mysql_handle, tmpsql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + + return 0; +} + +//----------------------------------------------------- +// DB server connect check +//----------------------------------------------------- +void mmo_auth_sqldb_sync(void) { + // db connect check? or close? + // ping pong DB server -if losted? then connect try. else crash. +} + +//----------------------------------------------------- +// close DB +//----------------------------------------------------- +void mmo_db_close(void) { + + //set log. + sprintf(tmpsql,"INSERT INTO `%s`(`time`,`ip`,`user`,`rcode`,`log`) VALUES (NOW(), '', 'lserver','100', 'login server shutdown')", loginlog_db); + + //query + if (mysql_query(&mysql_handle, tmpsql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + + //delete all server status + sprintf(tmpsql,"DELETE FROM `sstatus`"); + //query + if (mysql_query(&mysql_handle, tmpsql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + + mysql_close(&mysql_handle); + printf("close DB connect....\n"); + + int i, fd; + + for (i = 0; i < MAX_SERVERS; i++) { + if ((fd = server_fd[i]) >= 0) + delete_session(fd); + } + delete_session(login_fd); +} + +//----------------------------------------------------- +// Make new account +//----------------------------------------------------- +int mmo_auth_sqldb_new(struct mmo_account* account,const char *tmpstr, char sex) { + //no need on DB version + + printf("Request new account.... - not support on this version\n"); + + return 0; +} + +//----------------------------------------------------- +// Make new account +//----------------------------------------------------- +int mmo_auth_new(struct mmo_account* account, const char *tmpstr, char sex) { + + return 0; +} + +#ifdef LCCWIN32 +extern void gettimeofday(struct timeval *t, struct timezone *dummy); +#endif + +//----------------------------------------------------- +// Auth +//----------------------------------------------------- +int mmo_auth( struct mmo_account* account , int fd){ + struct timeval tv; + time_t ban_until_time; + char tmpstr[256]; + char t_uid[256], t_pass[256]; + char user_password[256]; + + MYSQL_RES* sql_res ; + MYSQL_ROW sql_row ; + //int sql_fields, sql_cnt; + char md5str[64], md5bin[32]; + + char ip[16]; + + unsigned char *sin_addr = (unsigned char *)&session[fd]->client_addr.sin_addr; + + printf ("auth start...\n"); + + sprintf(ip, "%d.%d.%d.%d", sin_addr[0], sin_addr[1], sin_addr[2], sin_addr[3]); + + // auth start : time seed + gettimeofday(&tv, NULL); + strftime(tmpstr, 24, "%Y-%m-%d %H:%M:%S",localtime(&(tv.tv_sec))); + sprintf(tmpstr+19, ".%03d", (int)tv.tv_usec/1000); + + jstrescapecpy(t_uid,account->userid); + jstrescapecpy(t_pass, account->passwd); + + // make query + sprintf(tmpsql, "SELECT `%s`,`%s`,`%s`,`lastlogin`,`logincount`,`sex`,`connect_until`,`last_ip`,`ban_until`,`state`,`%s`" + " FROM `%s` WHERE `%s`='%s'", login_db_account_id, login_db_userid, login_db_user_pass, login_db_level, login_db, login_db_userid, t_uid); + //login {0-account_id/1-userid/2-user_pass/3-lastlogin/4-logincount/5-sex/6-connect_untl/7-last_ip/8-ban_until/9-state} + + // query + if (mysql_query(&mysql_handle, tmpsql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + sql_res = mysql_store_result(&mysql_handle) ; + if (sql_res) { + sql_row = mysql_fetch_row(sql_res); //row fetching + if (!sql_row) { + //there's no id. + printf ("auth failed no account %s %s %s\n", tmpstr, account->userid, account->passwd); + mysql_free_result(sql_res); + return 0; + } + } else { + printf("mmo_auth DB result error ! \n"); + return 0; + } + // Documented by CLOWNISIUS || LLRO || Gunstar lead this one with me + // IF changed to diferent returns~ you get diferent responses from your msgstringtable.txt + //Ireturn 2 == line 9 + //Ireturn 5 == line 311 + //Ireturn 6 == line 450 + //Ireturn 7 == line 440 + //Ireturn 8 == line 682 + //Ireturn 9 == line 704 + //Ireturn 10 == line 705 + //Ireturn 11 == line 706 + //Ireturn 12 == line 707 + //Ireturn 13 == line 708 + //Ireturn 14 == line 709 + //Ireturn 15 == line 710 + //Ireturn -1 == line 010 + // Check status + { + if (atoi(sql_row[9]) == -3) { + //id is banned + mysql_free_result(sql_res); + return -3; + } else if (atoi(sql_row[9]) == -2) { //dynamic ban + //id is banned + mysql_free_result(sql_res); + //add IP list. + return -2; + } + + if (use_md5_passwds) { + MD5_String(account->passwd,user_password); + } else { + jstrescapecpy(user_password, account->passwd); + } + printf("account id ok encval:%d\n",account->passwdenc); + int encpasswdok = 0; +#ifdef PASSWORDENC + if (account->passwdenc > 0) { + printf ("start md5calc..\n"); + int j = account->passwdenc; + if (j > 2) + j = 1; + do { + if (j == 1) { + sprintf(md5str, "%s%s", md5key,sql_row[2]); + } else if (j == 2) { + sprintf(md5str, "%s%s", sql_row[2], md5key); + } else + md5str[0] = 0; + printf("j:%d mdstr:%s\n", j, md5str); + MD5_String2binary(md5str, md5bin); + encpasswdok = (memcmp(user_password, md5bin, 16) == 0); + } while (j < 2 && !encpasswdok && (j++) != account->passwdenc); + //printf("key[%s] md5 [%s] ", md5key, md5); + printf("client [%s] accountpass [%s]\n", user_password, sql_row[2]); + printf ("end md5calc..\n"); + } +#endif + if ((strcmp(user_password, sql_row[2]) && !encpasswdok)) { + if (account->passwdenc == 0) { + printf ("auth failed pass error %s %s %s" RETCODE, tmpstr, account->userid, user_password); +#ifdef PASSWORDENC + } else { + char logbuf[1024], *p = logbuf; + int j; + p += sprintf(p, "auth failed pass error %s %s recv-md5[", tmpstr, account->userid); + for(j = 0; j < 16; j++) + p += sprintf(p, "%02x", ((unsigned char *)user_password)[j]); + p += sprintf(p, "] calc-md5["); + for(j = 0; j < 16; j++) + p += sprintf(p, "%02x", ((unsigned char *)md5bin)[j]); + p += sprintf(p, "] md5key["); + for(j = 0; j < md5keylen; j++) + p += sprintf(p, "%02x", ((unsigned char *)md5key)[j]); + p += sprintf(p, "]" RETCODE); + printf("%s\n", p); +#endif + } + return 1; + } + printf("auth ok %s %s" RETCODE, tmpstr, account->userid); + } + + if (atoi(sql_row[9])) { + switch(atoi(sql_row[9])) { // packet 0x006a value + 1 + case 1: // 0 = Unregistered ID + case 2: // 1 = Incorrect Password + case 3: // 2 = This ID is expired + case 4: // 3 = Rejected from Server + case 5: // 4 = You have been blocked by the GM Team + case 6: // 5 = Your Game's EXE file is not the latest version + case 7: // 6 = Your are Prohibited to log in until %s + case 8: // 7 = Server is jammed due to over populated + case 9: // 8 = No MSG (actually, all states after 9 except 99 are No MSG, use only this) + case 100: // 99 = This ID has been totally erased + printf("Auth Error #%d\n", atoi(sql_row[9])); + return atoi(sql_row[9]) - 1; + break; + default: + return 99; // 99 = ID has been totally erased + break; + } + } + +/* +// do not remove this section. this is meant for future, and current forums usage +// as a login manager and CP for login server. [CLOWNISIUS] + if (atoi(sql_row[10]) == 1) { + return 4; + } + + if (atoi(sql_row[10]) >= 5) { + switch(atoi(sql_row[10])) { + case 5: + return 5; + break; + case 6: + return 7; + break; + case 7: + return 9; + break; + case 8: + return 10; + break; + case 9: + return 11; + break; + default: + return 10; + break; + } + } +*/ + ban_until_time = atol(sql_row[8]); + + //login {0-account_id/1-userid/2-user_pass/3-lastlogin/4-logincount/5-sex/6-connect_untl/7-last_ip/8-ban_until/9-state} + if (ban_until_time != 0) { // if account is banned + strftime(tmpstr, 20, date_format, localtime(&ban_until_time)); + tmpstr[19] = '\0'; + if (ban_until_time > time(NULL)) { // always banned + return 6; // 6 = Your are Prohibited to log in until %s + } else { // ban is finished + // reset the ban time + sprintf(tmpsql, "UPDATE `%s` SET `ban_until`='0' WHERE `%s`='%s'", login_db, login_db_userid, t_uid); + if (mysql_query(&mysql_handle, tmpsql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + } + } + + if (atol(sql_row[6]) != 0 && atol(sql_row[6]) < time(NULL)) { + return 2; // 2 = This ID is expired + } + + account->account_id = atoi(sql_row[0]); + account->login_id1 = rand(); + account->login_id2 = rand(); + memcpy(tmpstr, sql_row[3], 19); + memcpy(account->lastlogin, tmpstr, 24); + account->sex = sql_row[5][0] == 'S' ? 2 : sql_row[5][0]=='M'; + + sprintf(tmpsql, "UPDATE `%s` SET `lastlogin` = NOW(), `logincount`=`logincount` +1, `last_ip`='%s' WHERE `%s` = '%s'", + login_db, ip, login_db_userid, sql_row[1]); + mysql_free_result(sql_res) ; //resource free + if (mysql_query(&mysql_handle, tmpsql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + + return -1; +} + +// Send to char +int charif_sendallwos(int sfd, unsigned char *buf, unsigned int len) { + int i, c; + int fd; + + c = 0; + for(i = 0; i < MAX_SERVERS; i++) { + if ((fd = server_fd[i]) > 0 && fd != sfd) { + memcpy(WFIFOP(fd,0), buf, len); + WFIFOSET(fd,len); + c++; + } + } + + return c; +} + +//-------------------------------- +// Char-server anti-freeze system +//-------------------------------- +int char_anti_freeze_system(int tid, unsigned int tick, int id, int data) { + int i; + + for(i = 0; i < MAX_SERVERS; i++) { + if (server_fd[i] >= 0) {// if char-server is online +// printf("char_anti_freeze_system: server #%d '%s', flag: %d.\n", i, server[i].name, server_freezeflag[i]); + if (server_freezeflag[i]-- < 1) {// Char-server anti-freeze system. Counter. 5 ok, 4...0 freezed + session[server_fd[i]]->eof = 1; + } + } + } + + return 0; +} + +//----------------------------------------------------- +// char-server packet parse +//----------------------------------------------------- +int parse_fromchar(int fd){ + int i, id; + MYSQL_RES* sql_res; + MYSQL_ROW sql_row = NULL; + + unsigned char *p = (unsigned char *) &session[fd]->client_addr.sin_addr; + char ip[16]; + + sprintf(ip, "%d.%d.%d.%d", p[0], p[1], p[2], p[3]); + + for(id = 0; id < MAX_SERVERS; id++) + if (server_fd[id] == fd) + break; + + if (id == MAX_SERVERS || session[fd]->eof) { + if (id < MAX_SERVERS) { + printf("Char-server '%s' has disconnected.\n", server[id].name); + server_fd[id] = -1; + memset(&server[id], 0, sizeof(struct mmo_char_server)); + // server delete + sprintf(tmpsql, "DELETE FROM `sstatus` WHERE `index`='%d'", id); + // query + if (mysql_query(&mysql_handle, tmpsql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + } + close(fd); + delete_session(fd); + return 0; + } + + while(RFIFOREST(fd) >= 2) { +// printf("char_parse: %d %d packet case=%x\n", fd, RFIFOREST(fd), RFIFOW(fd, 0)); + + switch (RFIFOW(fd,0)) { + case 0x2712: + if (RFIFOREST(fd) < 19) + return 0; + { + int account_id; + account_id = RFIFOL(fd,2); // speed up + for(i=0;i<AUTH_FIFO_SIZE;i++){ + if (auth_fifo[i].account_id == account_id && + auth_fifo[i].login_id1 == RFIFOL(fd,6) && +#if CMP_AUTHFIFO_LOGIN2 != 0 + auth_fifo[i].login_id2 == RFIFOL(fd,10) && // relate to the versions higher than 18 +#endif + auth_fifo[i].sex == RFIFOB(fd,14) && +#if CMP_AUTHFIFO_IP != 0 + auth_fifo[i].ip == RFIFOL(fd,15) && +#endif + !auth_fifo[i].delflag) { + auth_fifo[i].delflag = 1; + printf("auth -> %d\n", i); + break; + } + } + + if (i != AUTH_FIFO_SIZE) { // send account_reg + int p; + time_t connect_until_time = 0; + char email[40] = ""; + account_id=RFIFOL(fd,2); + sprintf(tmpsql, "SELECT `email`,`connect_until` FROM `%s` WHERE `%s`='%d'", login_db, login_db_account_id, account_id); + if (mysql_query(&mysql_handle, tmpsql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + sql_res = mysql_store_result(&mysql_handle) ; + if (sql_res) { + sql_row = mysql_fetch_row(sql_res); + connect_until_time = atol(sql_row[1]); + strcpy(email, sql_row[0]); + } + mysql_free_result(sql_res); + if (account_id > 0) { + sprintf(tmpsql, "SELECT `str`,`value` FROM `global_reg_value` WHERE `type`='1' AND `account_id`='%d'",account_id); + if (mysql_query(&mysql_handle, tmpsql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + sql_res = mysql_store_result(&mysql_handle) ; + if (sql_res) { + WFIFOW(fd,0) = 0x2729; + WFIFOL(fd,4) = account_id; + for(p = 8; (sql_row = mysql_fetch_row(sql_res));p+=36){ + memcpy(WFIFOP(fd,p), sql_row[0], 32); + WFIFOL(fd,p+32) = atoi(sql_row[1]); + } + WFIFOW(fd,2) = p; + WFIFOSET(fd,p); + //printf("account_reg2 send : login->char (auth fifo)\n"); + WFIFOW(fd,0) = 0x2713; + WFIFOL(fd,2) = account_id; + WFIFOB(fd,6) = 0; + memcpy(WFIFOP(fd, 7), email, 40); + WFIFOL(fd,47) = (unsigned long) connect_until_time; + WFIFOSET(fd,51); + } + mysql_free_result(sql_res); + } + } else { + WFIFOW(fd,0) = 0x2713; + WFIFOL(fd,2) = account_id; + WFIFOB(fd,6) = 1; + WFIFOSET(fd,51); + } + } + RFIFOSKIP(fd,19); + break; + + case 0x2714: + if (RFIFOREST(fd) < 6) + return 0; + // how many users on world? (update) + if (server[id].users != RFIFOL(fd,2)) + printf("set users %s : %d\n", server[id].name, RFIFOL(fd,2)); + server[id].users = RFIFOL(fd,2); + if(anti_freeze_enable) + server_freezeflag[id] = 5; // Char anti-freeze system. Counter. 5 ok, 4...0 freezed + + sprintf(tmpsql,"UPDATE `sstatus` SET `user` = '%d' WHERE `index` = '%d'", server[id].users, id); + // query + if (mysql_query(&mysql_handle, tmpsql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + RFIFOSKIP(fd,6); + break; + + // We receive an e-mail/limited time request, because a player comes back from a map-server to the char-server + case 0x2716: + if (RFIFOREST(fd) < 6) + return 0; + { + int account_id; + time_t connect_until_time = 0; + char email[40] = ""; + account_id=RFIFOL(fd,2); + sprintf(tmpsql,"SELECT `email`,`connect_until` FROM `%s` WHERE `%s`='%d'",login_db, login_db_account_id, account_id); + if(mysql_query(&mysql_handle, tmpsql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + sql_res = mysql_store_result(&mysql_handle) ; + if (sql_res) { + sql_row = mysql_fetch_row(sql_res); + connect_until_time = atol(sql_row[1]); + strcpy(email, sql_row[0]); + } + mysql_free_result(sql_res); + //printf("parse_fromchar: E-mail/limited time request from '%s' server (concerned account: %d)\n", server[id].name, RFIFOL(fd,2)); + WFIFOW(fd,0) = 0x2717; + WFIFOL(fd,2) = RFIFOL(fd,2); + memcpy(WFIFOP(fd, 6), email, 40); + WFIFOL(fd,46) = (unsigned long) connect_until_time; + WFIFOSET(fd,50); + } + RFIFOSKIP(fd,6); + break; + + case 0x2720: // GM + if (RFIFOREST(fd) < 4) + return 0; + if (RFIFOREST(fd) < RFIFOW(fd,2)) + return 0; + //oldacc = RFIFOL(fd,4); + printf("change GM isn't support in this login server version.\n"); + printf("change GM error 0 %s\n", RFIFOP(fd, 8)); + + RFIFOSKIP(fd, RFIFOW(fd, 2)); + WFIFOW(fd, 0) = 0x2721; + WFIFOL(fd, 2) = RFIFOL(fd,4); // oldacc; + WFIFOL(fd, 6) = 0; // newacc; + WFIFOSET(fd, 10); + return 0; + + // Map server send information to change an email of an account via char-server + case 0x2722: // 0x2722 <account_id>.L <actual_e-mail>.40B <new_e-mail>.40B + if (RFIFOREST(fd) < 86) + return 0; + { + int acc; + char actual_email[40], new_email[40]; + acc = RFIFOL(fd,2); + memcpy(actual_email, RFIFOP(fd,6), 40); + memcpy(new_email, RFIFOP(fd,46), 40); + if (e_mail_check(actual_email) == 0) + printf("Char-server '%s': Attempt to modify an e-mail on an account (@email GM command), but actual email is invalid (account: %d, ip: %s)" RETCODE, + server[id].name, acc, ip); + else if (e_mail_check(new_email) == 0) + printf("Char-server '%s': Attempt to modify an e-mail on an account (@email GM command) with a invalid new e-mail (account: %d, ip: %s)" RETCODE, + server[id].name, acc, ip); + else if (strcmpi(new_email, "a@a.com") == 0) + printf("Char-server '%s': Attempt to modify an e-mail on an account (@email GM command) with a default e-mail (account: %d, ip: %s)" RETCODE, + server[id].name, acc, ip); + else { + sprintf(tmpsql, "SELECT `%s`,`email` FROM `%s` WHERE `%s` = '%d'", login_db_userid, login_db, login_db_account_id, acc); + if (mysql_query(&mysql_handle, tmpsql)) + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + sql_res = mysql_store_result(&mysql_handle); + if (sql_res) { + sql_row = mysql_fetch_row(sql_res); //row fetching + + if (strcmpi(sql_row[1], actual_email) == 0) { + sprintf(tmpsql, "UPDATE `%s` SET `email` = '%s' WHERE `%s` = '%d'", login_db, new_email, login_db_account_id, acc); + // query + if (mysql_query(&mysql_handle, tmpsql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + printf("Char-server '%s': Modify an e-mail on an account (@email GM command) (account: %d (%s), new e-mail: %s, ip: %s)." RETCODE, + server[id].name, acc, sql_row[0], actual_email, ip); + } + } + + } + } + RFIFOSKIP(fd, 86); + break; + + case 0x2724: // Receiving of map-server via char-server a status change resquest (by Yor) + if (RFIFOREST(fd) < 10) + return 0; + { + int acc, statut; + acc = RFIFOL(fd,2); + statut = RFIFOL(fd,6); + sprintf(tmpsql, "SELECT `state` FROM `%s` WHERE `%s` = '%d'", login_db, login_db_account_id, acc); + if (mysql_query(&mysql_handle, tmpsql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + sql_res = mysql_store_result(&mysql_handle); + if (sql_res) { + sql_row = mysql_fetch_row(sql_res); // row fetching + } + if (atoi(sql_row[0]) != statut && statut != 0) { + unsigned char buf[16]; + WBUFW(buf,0) = 0x2731; + WBUFL(buf,2) = acc; + WBUFB(buf,6) = 0; // 0: change of statut, 1: ban + WBUFL(buf,7) = statut; // status or final date of a banishment + charif_sendallwos(-1, buf, 11); + } + sprintf(tmpsql,"UPDATE `%s` SET `state` = '%d' WHERE `%s` = '%d'", login_db, statut,login_db_account_id,acc); + //query + if(mysql_query(&mysql_handle, tmpsql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + RFIFOSKIP(fd,10); + } + return 0; + + case 0x2725: // Receiving of map-server via char-server a ban resquest (by Yor) + if (RFIFOREST(fd) < 18) + return 0; + { + int acc; + struct tm *tmtime; + time_t timestamp, tmptime; + acc = RFIFOL(fd,2); + sprintf(tmpsql, "SELECT `ban_until` FROM `%s` WHERE `%s` = '%d'",login_db,login_db_account_id,acc); + if (mysql_query(&mysql_handle, tmpsql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + sql_res = mysql_store_result(&mysql_handle); + if (sql_res) { + sql_row = mysql_fetch_row(sql_res); // row fetching + } + tmptime = atol(sql_row[0]); + if (tmptime == 0 || tmptime < time(NULL)) + timestamp = time(NULL); + else + timestamp = tmptime; + tmtime = localtime(×tamp); + tmtime->tm_year = tmtime->tm_year + (short)RFIFOW(fd,6); + tmtime->tm_mon = tmtime->tm_mon + (short)RFIFOW(fd,8); + tmtime->tm_mday = tmtime->tm_mday + (short)RFIFOW(fd,10); + tmtime->tm_hour = tmtime->tm_hour + (short)RFIFOW(fd,12); + tmtime->tm_min = tmtime->tm_min + (short)RFIFOW(fd,14); + tmtime->tm_sec = tmtime->tm_sec + (short)RFIFOW(fd,16); + timestamp = mktime(tmtime); + if (timestamp != -1) { + if (timestamp <= time(NULL)) + timestamp = 0; + if (tmptime != timestamp) { + if (timestamp != 0) { + unsigned char buf[16]; + WBUFW(buf,0) = 0x2731; + WBUFL(buf,2) = acc; + WBUFB(buf,6) = 1; // 0: change of statut, 1: ban + WBUFL(buf,7) = timestamp; // status or final date of a banishment + charif_sendallwos(-1, buf, 11); + } + printf("Account: %d Banned until: %ld\n", acc, timestamp); + sprintf(tmpsql, "UPDATE `%s` SET `ban_until` = '%ld', `state`='7' WHERE `%s` = '%d'", login_db, timestamp, login_db_account_id, acc); + // query + if (mysql_query(&mysql_handle, tmpsql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + } + } + RFIFOSKIP(fd,18); + break; + } + return 0; + + case 0x2727: + if (RFIFOREST(fd) < 6) + return 0; + { + int acc,sex; + unsigned char buf[16]; + acc=RFIFOL(fd,4); + sprintf(tmpsql,"SELECT `sex` FROM `%s` WHERE `%s` = '%d'",login_db,login_db_account_id,acc); + + if(mysql_query(&mysql_handle, tmpsql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + return 0; + } + + sql_res = mysql_store_result(&mysql_handle) ; + + if (sql_res) { + if (mysql_num_rows(sql_res) == 0) { + mysql_free_result(sql_res); + return 0; + } + sql_row = mysql_fetch_row(sql_res); //row fetching + } + + if (strcmpi(sql_row[0], "M") == 0) + sex = 1; + else + sex = 0; + sprintf(tmpsql,"UPDATE `%s` SET `sex` = '%c' WHERE `%s` = '%d'", login_db, (sex==0?'M':'F'), login_db_account_id, acc); + //query + if(mysql_query(&mysql_handle, tmpsql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + WBUFW(buf,0) = 0x2723; + WBUFL(buf,2) = acc; + WBUFB(buf,6) = sex; + charif_sendallwos(-1, buf, 7); + RFIFOSKIP(fd,6); + } + return 0; + + case 0x2728: // save account_reg + if (RFIFOREST(fd) < 4 || RFIFOREST(fd) < RFIFOW(fd,2)) + return 0; + { + int acc,p,j; + char str[32]; + char temp_str[32]; + int value; + acc=RFIFOL(fd,4); + + if (acc>0){ + unsigned char buf[RFIFOW(fd,2)+1]; + for(p=8,j=0;p<RFIFOW(fd,2) && j<ACCOUNT_REG2_NUM;p+=36,j++){ + memcpy(str,RFIFOP(fd,p),32); + value=RFIFOL(fd,p+32); + sprintf(tmpsql,"DELETE FROM `global_reg_value` WHERE `type`='1' AND `account_id`='%d' AND `str`='%s';",acc,jstrescapecpy(temp_str,str)); + if(mysql_query(&mysql_handle, tmpsql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + sprintf(tmpsql,"INSERT INTO `global_reg_value` (`type`, `account_id`, `str`, `value`) VALUES ( 1 , '%d' , '%s' , '%d');", acc, jstrescapecpy(temp_str,str), value); + if(mysql_query(&mysql_handle, tmpsql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + } + + // Send to char + memcpy(WBUFP(buf,0),RFIFOP(fd,0),RFIFOW(fd,2)); + WBUFW(buf,0)=0x2729; + charif_sendallwos(fd,buf,WBUFW(buf,2)); + } + } + RFIFOSKIP(fd,RFIFOW(fd,2)); + //printf("login: save account_reg (from char)\n"); + break; + + case 0x272a: // Receiving of map-server via char-server a unban resquest (by Yor) + if (RFIFOREST(fd) < 6) + return 0; + { + int acc; + acc = RFIFOL(fd,2); + sprintf(tmpsql,"SELECT `ban_until` FROM `%s` WHERE `%s` = '%d'",login_db,login_db_account_id,acc); + if(mysql_query(&mysql_handle, tmpsql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + sql_res = mysql_store_result(&mysql_handle) ; + if (sql_res) { + sql_row = mysql_fetch_row(sql_res); //row fetching + } + if (atol(sql_row[0]) != 0) { + sprintf(tmpsql,"UPDATE `%s` SET `ban_until` = '0', `state`='0' WHERE `%s` = '%d'", login_db,login_db_account_id,acc); + //query + if(mysql_query(&mysql_handle, tmpsql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + break; + } + RFIFOSKIP(fd,6); + } + return 0; + + default: + printf("login: unknown packet %x! (from char).\n", RFIFOW(fd,0)); + session[fd]->eof = 1; + return 0; + } + } + + return 0; +} + +//Lan ip check added by Kashy +int lan_ip_check(unsigned char *p) { + int y; + int lancheck = 1; + int lancharip[4]; + + unsigned int k0, k1, k2, k3; + sscanf(lan_char_ip, "%d.%d.%d.%d", &k0, &k1, &k2, &k3); + lancharip[0] = k0; lancharip[1] = k1; lancharip[2] = k2; lancharip[3] = k3; + + for(y = 0; y < 4; y++) { + if ((lancharip[y] & subnetmaski[y])!= (p[y])) + lancheck = 0; + break; } + + printf("LAN check: %s.\n", (lancheck) ? "\033[1;32mLAN\033[0m" : "\033[1;31mWAN\033[0m"); + return lancheck; +} + +//---------------------------------------------------------------------------------------- +// Default packet parsing (normal players or administation/char-server connection requests) +//---------------------------------------------------------------------------------------- +int parse_login(int fd) { + //int len; + + MYSQL_RES* sql_res ; + MYSQL_ROW sql_row = NULL; + + char t_uid[100]; + //int sql_fields, sql_cnt; + struct mmo_account account; + + int result, i; + unsigned char *p = (unsigned char *) &session[fd]->client_addr.sin_addr; + char ip[16]; + + sprintf(ip, "%d.%d.%d.%d", p[0], p[1], p[2], p[3]); + + if (ipban > 0) { + //ip ban + //p[0], p[1], p[2], p[3] + //request DB connection + //check + sprintf(tmpsql, "SELECT count(*) FROM `ipbanlist` WHERE `list` = '%d.*.*.*' OR `list` = '%d.%d.*.*' OR `list` = '%d.%d.%d.*' OR `list` = '%d.%d.%d.%d'", + p[0], p[0], p[1], p[0], p[1], p[2], p[0], p[1], p[2], p[3]); + if (mysql_query(&mysql_handle, tmpsql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + + sql_res = mysql_store_result(&mysql_handle) ; + sql_row = mysql_fetch_row(sql_res); //row fetching + + if (atoi(sql_row[0]) >0) { + // ip ban ok. + printf ("packet from banned ip : %d.%d.%d.%d" RETCODE, p[0], p[1], p[2], p[3]); + sprintf(tmpsql,"INSERT INTO `%s`(`time`,`ip`,`user`,`rcode`,`log`) VALUES (NOW(), '%d.%d.%d.%d', 'unknown','-3', 'ip banned')", loginlog_db, p[0], p[1], p[2], p[3]); + + // query + if(mysql_query(&mysql_handle, tmpsql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + printf ("close session connection...\n"); + + // close connection + session[fd]->eof = 1; + + } else { + printf ("packet from ip (ban check ok) : %d.%d.%d.%d" RETCODE, p[0], p[1], p[2], p[3]); + } + mysql_free_result(sql_res); + } + + if (session[fd]->eof) { + for(i = 0; i < MAX_SERVERS; i++) + if (server_fd[i] == fd) + server_fd[i] = -1; + close(fd); + delete_session(fd); + return 0; + } + + while(RFIFOREST(fd)>=2){ + printf("parse_login : %d %d packet case=%x\n", fd, RFIFOREST(fd), RFIFOW(fd,0)); + + switch(RFIFOW(fd,0)){ + case 0x200: // New alive packet: structure: 0x200 <account.userid>.24B. used to verify if client is always alive. + if (RFIFOREST(fd) < 26) + return 0; + RFIFOSKIP(fd,26); + break; + + case 0x204: // New alive packet: structure: 0x204 <encrypted.account.userid>.16B. (new ragexe from 22 june 2004) + if (RFIFOREST(fd) < 18) + return 0; + RFIFOSKIP(fd,18); + break; + + case 0x64: // request client login + case 0x01dd: // request client login with encrypt + if(RFIFOREST(fd)< ((RFIFOW(fd, 0) ==0x64)?55:47)) + return 0; + + printf("client connection request %s from %d.%d.%d.%d\n", RFIFOP(fd, 6), p[0], p[1], p[2], p[3]); + + account.userid = RFIFOP(fd, 6); + account.passwd = RFIFOP(fd, 30); +#ifdef PASSWORDENC + account.passwdenc= (RFIFOW(fd,0)==0x64)?0:PASSWORDENC; +#else + account.passwdenc=0; +#endif + result=mmo_auth(&account, fd); + + jstrescapecpy(t_uid,RFIFOP(fd, 6)); + if(result==-1){ + int gm_level = isGM(account.account_id); + if (min_level_to_connect > gm_level) { + WFIFOW(fd,0) = 0x81; + WFIFOL(fd,2) = 1; // 01 = Server closed + WFIFOSET(fd,3); + } else { + if (p[0] != 127) { + sprintf(tmpsql,"INSERT INTO `%s`(`time`,`ip`,`user`,`rcode`,`log`) VALUES (NOW(), '%d.%d.%d.%d', '%s','100', 'login ok')", loginlog_db, p[0], p[1], p[2], p[3], t_uid); + //query + if(mysql_query(&mysql_handle, tmpsql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + } + if (gm_level) + printf("Connection of the GM (level:%d) account '%s' accepted.\n", gm_level, account.userid); + else + printf("Connection of the account '%s' accepted.\n", account.userid); + server_num=0; + for(i = 0; i < MAX_SERVERS; i++) { + if (server_fd[i] >= 0) { + //Lan check added by Kashy + if (lan_ip_check(p)) + WFIFOL(fd,47+server_num*32) = inet_addr(lan_char_ip); + else + WFIFOL(fd,47+server_num*32) = server[i].ip; + WFIFOW(fd,47+server_num*32+4) = server[i].port; + memcpy(WFIFOP(fd,47+server_num*32+6), server[i].name, 20); + WFIFOW(fd,47+server_num*32+26) = server[i].users; + WFIFOW(fd,47+server_num*32+28) = server[i].maintenance; + WFIFOW(fd,47+server_num*32+30) = server[i].new; + server_num++; + } + } + // if at least 1 char-server + if (server_num > 0) { + WFIFOW(fd,0)=0x69; + WFIFOW(fd,2)=47+32*server_num; + WFIFOL(fd,4)=account.login_id1; + WFIFOL(fd,8)=account.account_id; + WFIFOL(fd,12)=account.login_id2; + WFIFOL(fd,16)=0; + memcpy(WFIFOP(fd,20),account.lastlogin,24); + WFIFOB(fd,46)=account.sex; + WFIFOSET(fd,47+32*server_num); + if(auth_fifo_pos>=AUTH_FIFO_SIZE) + auth_fifo_pos=0; + auth_fifo[auth_fifo_pos].account_id=account.account_id; + auth_fifo[auth_fifo_pos].login_id1=account.login_id1; + auth_fifo[auth_fifo_pos].login_id2=account.login_id2; + auth_fifo[auth_fifo_pos].sex=account.sex; + auth_fifo[auth_fifo_pos].delflag=0; + auth_fifo[auth_fifo_pos].ip = session[fd]->client_addr.sin_addr.s_addr; + auth_fifo_pos++; + } else { + WFIFOW(fd,0) = 0x81; + WFIFOL(fd,2) = 1; // 01 = Server closed + WFIFOSET(fd,3); + } + } + } else { + char tmp_sql[512]; + char error[64]; + sprintf(tmp_sql,"INSERT INTO `%s`(`time`,`ip`,`user`,`rcode`,`log`) VALUES (NOW(), '%d.%d.%d.%d', '%s', '%d','login failed : %%s')", loginlog_db, p[0], p[1], p[2], p[3], t_uid, result); + switch((result + 1)) { + case -2: //-3 = Account Banned + sprintf(tmpsql,tmp_sql,"Account banned."); + sprintf(error,"Account banned."); + break; + case -1: //-2 = Dynamic Ban + sprintf(tmpsql,tmp_sql,"dynamic ban (ip and account)."); + sprintf(error,"dynamic ban (ip and account)."); + break; + case 1: // 0 = Unregistered ID + sprintf(tmpsql,tmp_sql,"Unregisterd ID."); + sprintf(error,"Unregisterd ID."); + break; + case 2: // 1 = Incorrect Password + sprintf(tmpsql,tmp_sql,"Incorrect Password."); + sprintf(error,"Incorrect Password."); + break; + case 3: // 2 = This ID is expired + sprintf(tmpsql,tmp_sql,"Account Expired."); + sprintf(error,"Account Expired."); + break; + case 4: // 3 = Rejected from Server + sprintf(tmpsql,tmp_sql,"Rejected from server."); + sprintf(error,"Rejected from server."); + break; + case 5: // 4 = You have been blocked by the GM Team + sprintf(tmpsql,tmp_sql,"Blocked by GM."); + sprintf(error,"Blocked by GM."); + break; + case 6: // 5 = Your Game's EXE file is not the latest version + sprintf(tmpsql,tmp_sql,"Not latest game EXE."); + sprintf(error,"Not latest game EXE."); + break; + case 7: // 6 = Your are Prohibited to log in until %s + sprintf(tmpsql,tmp_sql,"Banned."); + sprintf(error,"Banned."); + break; + case 8: // 7 = Server is jammed due to over populated + sprintf(tmpsql,tmp_sql,"Server Over-population."); + sprintf(error,"Server Over-population."); + break; + case 9: // 8 = No MSG (actually, all states after 9 except 99 are No MSG, use only this) + sprintf(tmpsql,tmp_sql," "); + sprintf(error," "); + break; + case 100: // 99 = This ID has been totally erased + sprintf(tmpsql,tmp_sql,"Account gone."); + sprintf(error,"Account gone."); + break; + default: + sprintf(tmpsql,tmp_sql,"Uknown Error."); + sprintf(error,"Uknown Error."); + break; + } + //query + if(mysql_query(&mysql_handle, tmpsql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + if ((result == 1) && (dynamic_pass_failure_ban != 0)){ // failed password + sprintf(tmpsql,"SELECT count(*) FROM `%s` WHERE `ip` = '%d.%d.%d.%d' AND `rcode` = '1' AND `time` > NOW() - INTERVAL %d MINUTE", + loginlog_db, p[0], p[1], p[2], p[3], dynamic_pass_failure_ban_time); //how many times filed account? in one ip. + if(mysql_query(&mysql_handle, tmpsql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + //check query result + sql_res = mysql_store_result(&mysql_handle) ; + sql_row = mysql_fetch_row(sql_res); //row fetching + + if (atoi(sql_row[0]) >= dynamic_pass_failure_ban_how_many ) { + sprintf(tmpsql,"INSERT INTO `ipbanlist`(`list`,`btime`,`rtime`,`reason`) VALUES ('%d.%d.%d.*', NOW() , NOW() + INTERVAL %d MINUTE ,'Password error ban: %s')", p[0], p[1], p[2], dynamic_pass_failure_ban_how_long, t_uid); + if(mysql_query(&mysql_handle, tmpsql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + } + mysql_free_result(sql_res); + } + else if (result == -2){ //dynamic banned - add ip to ban list. + sprintf(tmpsql,"INSERT INTO `ipbanlist`(`list`,`btime`,`rtime`,`reason`) VALUES ('%d.%d.%d.*', NOW() , NOW() + INTERVAL 1 MONTH ,'Dynamic banned user id : %s')", p[0], p[1], p[2], t_uid); + if(mysql_query(&mysql_handle, tmpsql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + result = -3; + } + + sprintf(tmpsql,"SELECT `ban_until` FROM `%s` WHERE `%s` = '%s'",login_db,login_db_userid, t_uid); + if(mysql_query(&mysql_handle, tmpsql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + sql_res = mysql_store_result(&mysql_handle) ; + if (sql_res) { + sql_row = mysql_fetch_row(sql_res); //row fetching + } + //cannot connect login failed + memset(WFIFOP(fd,0),'\0',23); + WFIFOW(fd,0)=0x6a; + WFIFOB(fd,2)=result; + if (result == 6) { // 6 = Your are Prohibited to log in until %s + if (atol(sql_row[0]) != 0) { // if account is banned, we send ban timestamp + char tmpstr[256]; + time_t ban_until_time; + ban_until_time = atol(sql_row[0]); + strftime(tmpstr, 20, date_format, localtime(&ban_until_time)); + tmpstr[19] = '\0'; + memcpy(WFIFOP(fd,3), tmpstr, 20); + } else { // we send error message + memcpy(WFIFOP(fd,3), error, 20); + } + } + WFIFOSET(fd,23); + } + RFIFOSKIP(fd,(RFIFOW(fd,0)==0x64)?55:47); + break; + + case 0x01db: // request password key + if (session[fd]->session_data) { + printf("login: abnormal request of MD5 key (already opened session).\n"); + session[fd]->eof = 1; + return 0; + } + printf("Request Password key -%s\n",md5key); + RFIFOSKIP(fd,2); + WFIFOW(fd,0)=0x01dc; + WFIFOW(fd,2)=4+md5keylen; + memcpy(WFIFOP(fd,4),md5key,md5keylen); + WFIFOSET(fd,WFIFOW(fd,2)); + break; + + case 0x2710: // request Char-server connection + if(RFIFOREST(fd)<86) + return 0; + { + sprintf(tmpsql,"INSERT INTO `%s`(`time`,`ip`,`user`,`rcode`,`log`) VALUES (NOW(), '%d.%d.%d.%d', '%s@%s','100', 'charserver - %s@%d.%d.%d.%d:%d')", loginlog_db, p[0], p[1], p[2], p[3], RFIFOP(fd, 2),RFIFOP(fd, 60),RFIFOP(fd, 60), RFIFOB(fd, 54), RFIFOB(fd, 55), RFIFOB(fd, 56), RFIFOB(fd, 57), RFIFOW(fd, 58)); + + //query + if(mysql_query(&mysql_handle, tmpsql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + printf("server connection request %s @ %d.%d.%d.%d:%d (%d.%d.%d.%d)\n", + RFIFOP(fd, 60), RFIFOB(fd, 54), RFIFOB(fd, 55), RFIFOB(fd, 56), RFIFOB(fd, 57), RFIFOW(fd, 58), + p[0], p[1], p[2], p[3]); + unsigned char* server_name; + account.userid = RFIFOP(fd, 2); + account.passwd = RFIFOP(fd, 26); + account.passwdenc = 0; + server_name = RFIFOP(fd,60); + result = mmo_auth(&account, fd); + //printf("Result: %d - Sex: %d - Account ID: %d\n",result,account.sex,(int) account.account_id); + + if(result == -1 && account.sex==2 && account.account_id<MAX_SERVERS && server_fd[account.account_id]==-1){ + printf("Connection of the char-server '%s' accepted.\n", server_name); + memset(&server[account.account_id], 0, sizeof(struct mmo_char_server)); + server[account.account_id].ip=RFIFOL(fd,54); + server[account.account_id].port=RFIFOW(fd,58); + memcpy(server[account.account_id].name,RFIFOP(fd,60),20); + server[account.account_id].users=0; + server[account.account_id].maintenance=RFIFOW(fd,82); + server[account.account_id].new=RFIFOW(fd,84); + server_fd[account.account_id]=fd; + if(anti_freeze_enable) + server_freezeflag[account.account_id] = 5; // Char-server anti-freeze system. Counter. 5 ok, 4...0 freezed + sprintf(tmpsql,"DELETE FROM `sstatus` WHERE `index`='%ld'", account.account_id); + //query + if(mysql_query(&mysql_handle, tmpsql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + + jstrescapecpy(t_uid,server[account.account_id].name); + sprintf(tmpsql,"INSERT INTO `sstatus`(`index`,`name`,`user`) VALUES ( '%ld', '%s', '%d')", + account.account_id, server[account.account_id].name,0); + //query + if(mysql_query(&mysql_handle, tmpsql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + WFIFOW(fd,0)=0x2711; + WFIFOB(fd,2)=0; + WFIFOSET(fd,3); + session[fd]->func_parse=parse_fromchar; + realloc_fifo(fd,FIFOSIZE_SERVERLINK,FIFOSIZE_SERVERLINK); + } else { + WFIFOW(fd, 0) =0x2711; + WFIFOB(fd, 2)=3; + WFIFOSET(fd, 3); + } + } + RFIFOSKIP(fd, 86); + return 0; + + case 0x7530: // request Athena information + WFIFOW(fd,0)=0x7531; + WFIFOB(fd,2)=ATHENA_MAJOR_VERSION; + WFIFOB(fd,3)=ATHENA_MINOR_VERSION; + WFIFOB(fd,4)=ATHENA_REVISION; + WFIFOB(fd,5)=ATHENA_RELEASE_FLAG; + WFIFOB(fd,6)=ATHENA_OFFICIAL_FLAG; + WFIFOB(fd,7)=ATHENA_SERVER_LOGIN; + WFIFOW(fd,8)=ATHENA_MOD_VERSION; + WFIFOSET(fd,10); + RFIFOSKIP(fd,2); + printf ("Athena version check...\n"); + break; + + case 0x7532: + default: + printf ("End of connection (ip: %s)" RETCODE, ip); + session[fd]->eof = 1; + return 0; + } + } + + return 0; +} + +//------------------------------------------------- +// Return numerical value of a switch configuration +// on/off, english, fran軋is, deutsch, espaol +//------------------------------------------------- +int config_switch(const char *str) { + if (strcmpi(str, "on") == 0 || strcmpi(str, "yes") == 0 || strcmpi(str, "oui") == 0 || strcmpi(str, "ja") == 0 || strcmpi(str, "si") == 0) + return 1; + if (strcmpi(str, "off") == 0 || strcmpi(str, "no") == 0 || strcmpi(str, "non") == 0 || strcmpi(str, "nein") == 0) + return 0; + + return atoi(str); +} + + +//Lan Support conf reading added by Kashy +int login_lan_config_read(const char *lancfgName){ + int i; + char subnetmask[128]; + char line[1024], w1[1024], w2[1024]; + FILE *fp; + + fp=fopen(lancfgName, "r"); + + if (fp == NULL) { + printf("file not found: %s\n", lancfgName); + return 1; + } + printf("Start reading of Lan Support configuration file\n"); + while(fgets(line, sizeof(line)-1, fp)){ + if (line[0] == '/' && line[1] == '/') + continue; + + i = sscanf(line,"%[^:]: %[^\r\n]",w1,w2); + if(i!=2) + continue; + + else if(strcmpi(w1,"lan_char_ip")==0){ + strcpy(lan_char_ip, w2); + printf ("set Lan_Char_IP : %s\n",w2); + } + + else if(strcmpi(w1,"subnetmask")==0){ + strcpy(subnetmask, w2); + unsigned int k0, k1, k2, k3; + sscanf(subnetmask, "%d.%d.%d.%d", &k0, &k1, &k2, &k3); + subnetmaski[0] = k0; subnetmaski[1] = k1; subnetmaski[2] = k2; subnetmaski[3] = k3; + printf ("set subnetmask : %s\n",w2); + } + } + fclose(fp); + + { + unsigned int a0, a1, a2, a3; + unsigned char p[4]; + sscanf(lan_char_ip, "%d.%d.%d.%d", &a0, &a1, &a2, &a3); + p[0] = a0; p[1] = a1; p[2] = a2; p[3] = a3; + printf("LAN test of LAN IP of the char-server: "); + if (lan_ip_check(p) == 0) { + printf("\033[1;31m***ERROR: LAN IP of the char-server doesn't belong to the specified Sub-network\033[0m\n"); + } + } + + printf("End reading of Lan Support configuration file\n"); + + return 0; +} + +//----------------------------------------------------- +//BANNED IP CHECK. +//----------------------------------------------------- +int ip_ban_check(int tid, unsigned int tick, int id, int data){ + + //query + if(mysql_query(&mysql_handle, "DELETE FROM `ipbanlist` WHERE `rtime` <= NOW()")) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + + return 0; +} + +//----------------------------------------------------- +// reading configuration +//----------------------------------------------------- +int login_config_read(const char *cfgName){ + int i; + char line[1024], w1[1024], w2[1024]; + FILE *fp; + + fp=fopen(cfgName,"r"); + + if(fp==NULL){ + printf("Configuration file (%s) not found.\n", cfgName); + return 1; + } + printf ("start reading configuration...\n"); + while(fgets(line, sizeof(line)-1, fp)){ + if(line[0] == '/' && line[1] == '/') + continue; + + i=sscanf(line,"%[^:]: %[^\r\n]",w1,w2); + if(i!=2) + continue; + + else if(strcmpi(w1,"login_port")==0){ + login_port=atoi(w2); + printf ("set login_port : %s\n",w2); + } + else if(strcmpi(w1,"ipban")==0){ + ipban=atoi(w2); + printf ("set ipban : %d\n",ipban); + } + //account ban -> ip ban + else if(strcmpi(w1,"dynamic_account_ban")==0){ + dynamic_account_ban=atoi(w2); + printf ("set dynamic_account_ban : %d\n",dynamic_account_ban); + } + else if(strcmpi(w1,"dynamic_account_ban_class")==0){ + dynamic_account_ban_class=atoi(w2); + printf ("set dynamic_account_ban_class : %d\n",dynamic_account_ban_class); + } + //dynamic password error ban + else if(strcmpi(w1,"dynamic_pass_failure_ban")==0){ + dynamic_pass_failure_ban=atoi(w2); + printf ("set dynamic_pass_failure_ban : %d\n",dynamic_pass_failure_ban); + } + else if(strcmpi(w1,"dynamic_pass_failure_ban_time")==0){ + dynamic_pass_failure_ban_time=atoi(w2); + printf ("set dynamic_pass_failure_ban_time : %d\n",dynamic_pass_failure_ban_time); + } + else if(strcmpi(w1,"dynamic_pass_failure_ban_how_many")==0){ + dynamic_pass_failure_ban_how_many=atoi(w2); + printf ("set dynamic_pass_failure_ban_how_many : %d\n",dynamic_pass_failure_ban_how_many); + } + else if(strcmpi(w1,"dynamic_pass_failure_ban_how_long")==0){ + dynamic_pass_failure_ban_how_long=atoi(w2); + printf ("set dynamic_pass_failure_ban_how_long : %d\n",dynamic_pass_failure_ban_how_long); + } + else if(strcmpi(w1,"anti_freeze_enable")==0){ + anti_freeze_enable = config_switch(w2); + } + else if (strcmpi(w1, "anti_freeze_interval") == 0) { + ANTI_FREEZE_INTERVAL = atoi(w2); + if (ANTI_FREEZE_INTERVAL < 5) + ANTI_FREEZE_INTERVAL = 5; // minimum 5 seconds + } + else if (strcmpi(w1, "import") == 0) { + login_config_read(w2); + } + else if(strcmpi(w1,"use_MD5_passwords")==0){ + if (!strcmpi(w2,"yes")) { + use_md5_passwds=1; + } else if (!strcmpi(w2,"no")){ + use_md5_passwds=0; + } + printf ("Using MD5 Passwords: %s \n",w2); + } + else if (strcmpi(w1, "date_format") == 0) { // note: never have more than 19 char for the date! + switch (atoi(w2)) { + case 0: + strcpy(date_format, "%d-%m-%Y %H:%M:%S"); // 31-12-2004 23:59:59 + break; + case 1: + strcpy(date_format, "%m-%d-%Y %H:%M:%S"); // 12-31-2004 23:59:59 + break; + case 2: + strcpy(date_format, "%Y-%d-%m %H:%M:%S"); // 2004-31-12 23:59:59 + break; + case 3: + strcpy(date_format, "%Y-%m-%d %H:%M:%S"); // 2004-12-31 23:59:59 + break; + } + } + else if (strcmpi(w1, "min_level_to_connect") == 0) { + min_level_to_connect = atoi(w2); + } + else if (strcmpi(w1, "check_ip_flag") == 0) { + check_ip_flag = config_switch(w2); + } + } + fclose(fp); + printf ("End reading configuration...\n"); + return 0; +} + +void sql_config_read(const char *cfgName){ /* Kalaspuff, to get login_db */ + int i; + char line[1024], w1[1024], w2[1024]; + printf("reading configure: %s\n", cfgName); + FILE *fp=fopen(cfgName,"r"); + if(fp==NULL){ + printf("file not found: %s\n",cfgName); + exit(1); + } + while(fgets(line, sizeof(line)-1, fp)){ + if(line[0] == '/' && line[1] == '/') + continue; + i=sscanf(line,"%[^:]: %[^\r\n]",w1,w2); + if(i!=2) + continue; + if (strcmpi(w1, "login_db") == 0) { + strcpy(login_db, w2); + } + //add for DB connection + else if(strcmpi(w1,"login_server_ip")==0){ + strcpy(login_server_ip, w2); + printf ("set login_server_ip : %s\n",w2); + } + else if(strcmpi(w1,"login_server_port")==0){ + login_server_port=atoi(w2); + printf ("set login_server_port : %s\n",w2); + } + else if(strcmpi(w1,"login_server_id")==0){ + strcpy(login_server_id, w2); + printf ("set login_server_id : %s\n",w2); + } + else if(strcmpi(w1,"login_server_pw")==0){ + strcpy(login_server_pw, w2); + printf ("set login_server_pw : %s\n",w2); + } + else if(strcmpi(w1,"login_server_db")==0){ + strcpy(login_server_db, w2); + printf ("set login_server_db : %s\n",w2); + } + //added for custom column names for custom login table + else if(strcmpi(w1,"login_db_account_id")==0){ + strcpy(login_db_account_id, w2); + } + else if(strcmpi(w1,"login_db_userid")==0){ + strcpy(login_db_userid, w2); + } + else if(strcmpi(w1,"login_db_user_pass")==0){ + strcpy(login_db_user_pass, w2); + } + else if(strcmpi(w1,"login_db_level")==0){ + strcpy(login_db_level, w2); + } + //end of custom table config + else if (strcmpi(w1, "loginlog_db") == 0) { + strcpy(loginlog_db, w2); + } + } + fclose(fp); + printf("reading configure done.....\n"); +} + +int do_init(int argc,char **argv){ + //initialize login server + int i; + + //read login configue + login_config_read( (argc>1)?argv[1]:LOGIN_CONF_NAME ); + sql_config_read(SQL_CONF_NAME); + login_lan_config_read((argc > 1) ? argv[1] : LAN_CONF_NAME); + //Generate Passworded Key. + printf ("memset md5key \n"); + memset(md5key, 0, sizeof(md5key)); + printf ("memset md5key complete\n"); + printf ("memset keyleng\n"); + md5keylen=rand()%4+12; + for(i=0;i<md5keylen;i++) + md5key[i]=rand()%255+1; + printf ("memset keyleng complete\n"); + + printf ("set FIFO Size\n"); + for(i=0;i<AUTH_FIFO_SIZE;i++) + auth_fifo[i].delflag=1; + printf ("set FIFO Size complete\n"); + + printf ("set max servers\n"); + for(i=0;i<MAX_SERVERS;i++) + server_fd[i]=-1; + printf ("set max servers complete\n"); + //server port open & binding + + login_fd=make_listen_port(login_port); + + //Auth start + printf ("Running mmo_auth_sqldb_init()\n"); + mmo_auth_sqldb_init(); + printf ("finished mmo_auth_sqldb_init()\n"); + //sync account when terminating. + //but no need when you using DBMS (mysql) + set_termfunc(mmo_db_close); + + //set default parser as parse_login function + set_defaultparse(parse_login); + + if(anti_freeze_enable > 0) { + add_timer_func_list(char_anti_freeze_system, "char_anti_freeze_system"); + i = add_timer_interval(gettick()+1000, char_anti_freeze_system, 0, 0, ANTI_FREEZE_INTERVAL * 1000); + } + + // ban deleter timer - 1 minute term + printf("add interval tic (ip_ban_check)....\n"); + i=add_timer_interval(gettick()+10, ip_ban_check,0,0,60*1000); + + printf("The login-server is \033[1;32mready\033[0m (Server is listening on the port %d).\n\n", login_port); + + return 0; +} + + diff --git a/misc/src/login_sql/login.h b/misc/src/login_sql/login.h new file mode 100644 index 0000000..6335168 --- /dev/null +++ b/misc/src/login_sql/login.h @@ -0,0 +1,41 @@ +#ifndef _LOGIN_H_ +#define _LOGIN_H_ + +#define MAX_SERVERS 30 + +#define LOGIN_CONF_NAME "conf/login_athena.conf" +#define SQL_CONF_NAME "conf/inter_athena.conf" +#define LAN_CONF_NAME "conf/lan_support.conf" + +#define PASSWORDENC 3 // A definition is given when making an encryption password correspond. + // It is 1 at the time of passwordencrypt. + // It is made into 2 at the time of passwordencrypt2. + // When it is made 3, it corresponds to both. + +#define START_ACCOUNT_NUM 2000000 +#define END_ACCOUNT_NUM 100000000 + +struct mmo_account { + char* userid; + char* passwd; + int passwdenc; + + long account_id; + long login_id1; + long login_id2; + long char_id; + char lastlogin[24]; + int sex; +}; + +struct mmo_char_server { + char name[20]; + long ip; + short port; + int users; + int maintenance; + int new; +}; + + +#endif diff --git a/misc/src/login_sql/make.sh b/misc/src/login_sql/make.sh new file mode 100644 index 0000000..198d33b --- /dev/null +++ b/misc/src/login_sql/make.sh @@ -0,0 +1,6 @@ +#!/bin/sh + rsqlt=`rm -rf *.o` + gcc -c login.c -I/usr/local/include/mysql/ + gcc -c md5calc.c -I/usr/local/include/mysql/ + gcc -c strlib.c + gcc -o login-server login.o strlib.o md5calc.o ../common/core.o ../common/socket.o ../common/timer.o ../common/db.o -L/usr/local/lib/mysql/ -lmysqlclient -lz diff --git a/misc/src/login_sql/md5calc.c b/misc/src/login_sql/md5calc.c new file mode 100644 index 0000000..58cea12 --- /dev/null +++ b/misc/src/login_sql/md5calc.c @@ -0,0 +1,236 @@ +/*********************************************************** + * md5 calculation algorithm + * + * The source code referred to the following URL. + * http://www.geocities.co.jp/SiliconValley-Oakland/8878/lab17/lab17.html + * + ***********************************************************/ + +#include "md5calc.h" +#include <string.h> +#include <stdio.h> + +#ifndef UINT_MAX +#define UINT_MAX 4294967295U +#endif + +// Global variable +static unsigned int *pX; + +// Stirng Table +static const unsigned int T[] = { + 0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, //0 + 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501, //4 + 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, //8 + 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821, //12 + 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, //16 + 0xd62f105d, 0x2441453, 0xd8a1e681, 0xe7d3fbc8, //20 + 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, //24 + 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a, //28 + 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, //32 + 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70, //36 + 0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x4881d05, //40 + 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665, //44 + 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, //48 + 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1, //52 + 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, //56 + 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391 //60 +}; + +// ROTATE_LEFT The left is made to rotate x [ n-bit ]. This is diverted as it is from RFC. +#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32-(n)))) + +// The function used for other calculation +static unsigned int F(unsigned int X, unsigned int Y, unsigned int Z) +{ + return (X & Y) | (~X & Z); +} +static unsigned int G(unsigned int X, unsigned int Y, unsigned int Z) +{ + return (X & Z) | (Y & ~Z); +} +static unsigned int H(unsigned int X, unsigned int Y, unsigned int Z) +{ + return X ^ Y ^ Z; +} +static unsigned int I(unsigned int X, unsigned int Y, unsigned int Z) +{ + return Y ^ (X | ~Z); +} + +static unsigned int Round(unsigned int a, unsigned int b, unsigned int FGHI, + unsigned int k, unsigned int s, unsigned int i) +{ + return b + ROTATE_LEFT(a + FGHI + pX[k] + T[i], s); +} + +static void Round1(unsigned int *a, unsigned int b, unsigned int c, + unsigned int d,unsigned int k, unsigned int s, unsigned int i) +{ + *a = Round(*a, b, F(b,c,d), k, s, i); +} +static void Round2(unsigned int *a, unsigned int b, unsigned int c, + unsigned int d,unsigned int k, unsigned int s, unsigned int i) +{ + *a = Round(*a, b, G(b,c,d), k, s, i); +} +static void Round3(unsigned int *a, unsigned int b, unsigned int c, + unsigned int d,unsigned int k, unsigned int s, unsigned int i) +{ + *a = Round(*a, b, H(b,c,d), k, s, i); +} +static void Round4(unsigned int *a, unsigned int b, unsigned int c, + unsigned int d,unsigned int k, unsigned int s, unsigned int i) +{ + *a = Round(*a, b, I(b,c,d), k, s, i); +} + +static void MD5_Round_Calculate(const unsigned char *block, + unsigned int *A2, unsigned int *B2, unsigned int *C2, unsigned int *D2) +{ + //create X It is since it is required. + unsigned int X[16]; //512bit 64byte + int j,k; + + //Save A as AA, B as BB, C as CC, and and D as DD (saving of A, B, C, and D) + unsigned int A=*A2, B=*B2, C=*C2, D=*D2; + unsigned int AA = A,BB = B,CC = C,DD = D; + + //It is a large region variable reluctantly because of calculation of a round. . . for Round1...4 + pX = X; + + //Copy block(padding_message) i into X + for (j=0,k=0; j<64; j+=4,k++) + X[k] = ( (unsigned int )block[j] ) // 8byte*4 -> 32byte conversion + | ( ((unsigned int )block[j+1]) << 8 ) // A function called Decode as used in the field of RFC + | ( ((unsigned int )block[j+2]) << 16 ) + | ( ((unsigned int )block[j+3]) << 24 ); + + + //Round 1 + Round1(&A,B,C,D, 0, 7, 0); Round1(&D,A,B,C, 1, 12, 1); Round1(&C,D,A,B, 2, 17, 2); Round1(&B,C,D,A, 3, 22, 3); + Round1(&A,B,C,D, 4, 7, 4); Round1(&D,A,B,C, 5, 12, 5); Round1(&C,D,A,B, 6, 17, 6); Round1(&B,C,D,A, 7, 22, 7); + Round1(&A,B,C,D, 8, 7, 8); Round1(&D,A,B,C, 9, 12, 9); Round1(&C,D,A,B, 10, 17, 10); Round1(&B,C,D,A, 11, 22, 11); + Round1(&A,B,C,D, 12, 7, 12); Round1(&D,A,B,C, 13, 12, 13); Round1(&C,D,A,B, 14, 17, 14); Round1(&B,C,D,A, 15, 22, 15); + + //Round 2 + Round2(&A,B,C,D, 1, 5, 16); Round2(&D,A,B,C, 6, 9, 17); Round2(&C,D,A,B, 11, 14, 18); Round2(&B,C,D,A, 0, 20, 19); + Round2(&A,B,C,D, 5, 5, 20); Round2(&D,A,B,C, 10, 9, 21); Round2(&C,D,A,B, 15, 14, 22); Round2(&B,C,D,A, 4, 20, 23); + Round2(&A,B,C,D, 9, 5, 24); Round2(&D,A,B,C, 14, 9, 25); Round2(&C,D,A,B, 3, 14, 26); Round2(&B,C,D,A, 8, 20, 27); + Round2(&A,B,C,D, 13, 5, 28); Round2(&D,A,B,C, 2, 9, 29); Round2(&C,D,A,B, 7, 14, 30); Round2(&B,C,D,A, 12, 20, 31); + + //Round 3 + Round3(&A,B,C,D, 5, 4, 32); Round3(&D,A,B,C, 8, 11, 33); Round3(&C,D,A,B, 11, 16, 34); Round3(&B,C,D,A, 14, 23, 35); + Round3(&A,B,C,D, 1, 4, 36); Round3(&D,A,B,C, 4, 11, 37); Round3(&C,D,A,B, 7, 16, 38); Round3(&B,C,D,A, 10, 23, 39); + Round3(&A,B,C,D, 13, 4, 40); Round3(&D,A,B,C, 0, 11, 41); Round3(&C,D,A,B, 3, 16, 42); Round3(&B,C,D,A, 6, 23, 43); + Round3(&A,B,C,D, 9, 4, 44); Round3(&D,A,B,C, 12, 11, 45); Round3(&C,D,A,B, 15, 16, 46); Round3(&B,C,D,A, 2, 23, 47); + + //Round 4 + Round4(&A,B,C,D, 0, 6, 48); Round4(&D,A,B,C, 7, 10, 49); Round4(&C,D,A,B, 14, 15, 50); Round4(&B,C,D,A, 5, 21, 51); + Round4(&A,B,C,D, 12, 6, 52); Round4(&D,A,B,C, 3, 10, 53); Round4(&C,D,A,B, 10, 15, 54); Round4(&B,C,D,A, 1, 21, 55); + Round4(&A,B,C,D, 8, 6, 56); Round4(&D,A,B,C, 15, 10, 57); Round4(&C,D,A,B, 6, 15, 58); Round4(&B,C,D,A, 13, 21, 59); + Round4(&A,B,C,D, 4, 6, 60); Round4(&D,A,B,C, 11, 10, 61); Round4(&C,D,A,B, 2, 15, 62); Round4(&B,C,D,A, 9, 21, 63); + + // Then perform the following additions. (let's add) + *A2 = A + AA; + *B2 = B + BB; + *C2 = C + CC; + *D2 = D + DD; + + //The clearance of confidential information + memset(pX, 0, sizeof(X)); +} + +//------------------------------------------------------------------- +// The function for the exteriors + +/** output is the coded binary in the character sequence which wants to code string. */ +void MD5_String2binary(const char * string, char * output) +{ +//var + /*8bit*/ + unsigned char padding_message[64]; //Extended message 512bit 64byte + unsigned char *pstring; //The position of string in the present scanning notes is held. + +// unsigned char digest[16]; + /*32bit*/ + unsigned int string_byte_len, //The byte chief of string is held. + string_bit_len, //The bit length of string is held. + copy_len, //The number of bytes which is used by 1-3 and which remained + msg_digest[4]; //Message digest 128bit 4byte + unsigned int *A = &msg_digest[0], //The message digest in accordance with RFC (reference) + *B = &msg_digest[1], + *C = &msg_digest[2], + *D = &msg_digest[3]; + int i; + +//prog + //Step 3.Initialize MD Buffer (although it is the initialization; step 3 of A, B, C, and D -- unavoidable -- a head) + *A = 0x67452301; + *B = 0xefcdab89; + *C = 0x98badcfe; + *D = 0x10325476; + + //Step 1.Append Padding Bits (extension of a mark bit) + //1-1 + string_byte_len = strlen(string); //The byte chief of a character sequence is acquired. + pstring = (unsigned char *)string; //The position of the present character sequence is set. + + //1-2 Repeat calculation until length becomes less than 64 bytes. + for (i=string_byte_len; 64<=i; i-=64,pstring+=64) + MD5_Round_Calculate(pstring, A,B,C,D); + + //1-3 + copy_len = string_byte_len % 64; //The number of bytes which remained is computed. + strncpy((char *)padding_message, (char *)pstring, copy_len); //A message is copied to an extended bit sequence. + memset(padding_message+copy_len, 0, 64 - copy_len); //It buries by 0 until it becomes extended bit length. + padding_message[copy_len] |= 0x80; //The next of a message is 1. + + //1-4 + //If 56 bytes or more (less than 64 bytes) of remainder becomes, it will calculate by extending to 64 bytes. + if (56 <= copy_len) { + MD5_Round_Calculate(padding_message, A,B,C,D); + memset(padding_message, 0, 56); //56 bytes is newly fill uped with 0. + } + + + //Step 2.Append Length (the information on length is added) + string_bit_len = string_byte_len * 8; //From the byte chief to bit length (32 bytes of low rank) + memcpy(&padding_message[56], &string_bit_len, 4); //32 bytes of low rank is set. + + //When bit length cannot be expressed in 32 bytes of low rank, it is a beam raising to a higher rank. + if (UINT_MAX / 8 < string_byte_len) { + unsigned int high = (string_byte_len - UINT_MAX / 8) * 8; + memcpy(&padding_message[60], &high, 4); + } else + memset(&padding_message[60], 0, 4); //In this case, it is good for a higher rank at 0. + + //Step 4.Process Message in 16-Word Blocks (calculation of MD5) + MD5_Round_Calculate(padding_message, A,B,C,D); + + + //Step 5.Output (output) + memcpy(output,msg_digest,16); +// memcpy (digest, msg_digest, and 16); //8 byte*4 < - 32byte conversion A function called Encode as used in the field of RFC +/* sprintf(output, + "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", + digest[ 0], digest[ 1], digest[ 2], digest[ 3], + digest[ 4], digest[ 5], digest[ 6], digest[ 7], + digest[ 8], digest[ 9], digest[10], digest[11], + digest[12], digest[13], digest[14], digest[15]);*/ +} + +/** output is the coded character sequence in the character sequence which wants to code string. */ +void MD5_String(const char * string, char * output) +{ + unsigned char digest[16]; + + MD5_String2binary(string,digest); + sprintf(output, + "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", + digest[ 0], digest[ 1], digest[ 2], digest[ 3], + digest[ 4], digest[ 5], digest[ 6], digest[ 7], + digest[ 8], digest[ 9], digest[10], digest[11], + digest[12], digest[13], digest[14], digest[15]); +} + diff --git a/misc/src/login_sql/md5calc.h b/misc/src/login_sql/md5calc.h new file mode 100644 index 0000000..9bc554f --- /dev/null +++ b/misc/src/login_sql/md5calc.h @@ -0,0 +1,7 @@ +#ifndef _MD5CALC_H_ +#define _MD5CALC_H_ + +void MD5_String(const char * string, char * output); +void MD5_String2binary(const char * string, char * output); + +#endif diff --git a/misc/src/login_sql/readme.txt b/misc/src/login_sql/readme.txt new file mode 100644 index 0000000..b945af7 --- /dev/null +++ b/misc/src/login_sql/readme.txt @@ -0,0 +1,120 @@ +サソ// Encoded UTF-8 +//--------------------------------------------- +// 007 - by Jazz +1. 0590 official version縺ォ蜷医o縺帙※菫ョ豁」縺励∪縺励◆. + +//--------------------------------------------- +// 006 - by Jazz +1. 繝代せ繝ッ繝シ繝画アコ縺セ縺」縺溷屓謨ー髢馴&縺譎りェ蜍 IP驕ョ譁ュ蜈キ迴セ. +2. login_athena.conf繧剃ソョ豁」 + +//--------------------------------------------- +// 005 - by Jazz +1. 險ア螳ケ縺励↑縺 ID縺梧磁霑代ョ譎りェ蜍輔〒 IP驕ョ譁ュ讖溯ス蜈キ迴セ. +2. 閾ェ蜍輔〒驕ョ譁ュ隗」髯、讖溯ス蜈キ迴セ. + +//--------------------------------------------- +// 004 - by Jazz +1. aphostropy 蝠城。後r隗」豎コ縺励∪縺励◆. 縺薙l縺ッ菫晏ョ峨→騾」髢「縺後≠繧句撫鬘後〒縺. +2. SQL縺ョ讒矩繧剃ソョ豁」縺励∪縺励◆. +3. server 迥カ諷九r SQL縺ァ蜃コ蜉. + +//--------------------------------------------- +// 003 - by Jazz +1. 謗・邯夊ィ倬鹸繧 DB縺ァ蜃コ蜉. +2. IP蝓コ逶、縺ョ謗・邯夐ョ譁ュ蜈キ迴セ. + +//--------------------------------------------- +// 002 - by Jazz +1. 縺縺上▽縺九ョ隧ウ邏ー蝠城。檎せ菫ョ豁」. + +//--------------------------------------------- +// 001 - by Jazz +1. 荳逡ェ逶ョ螳牙ョ version縺ァ縺. + + +// notice some.. +In this program, new parts are under BSD License. +and imported parts are under GPL as athena did. + +this program is not public license. but modification and editing are +unlimit permitted. but auther don't have any responsibility on that. + +this realase does not gurantee working perfectly. and, I would +answer neither that question nor technical one. + +I use... +P4 2.4Gh/512MB/WinXP/cygwin + +athena is under GPL license. +Ragnarok is Gravity's trademark. +login-db for athena is BSD license except code from original athena. +cygwin is Redhat's trademark. + +// About compile. +You need mysqlclient library to compile this. You can get this by +compiling mysql source. + +"-lmysqlclient" option needed when you did compile. + +some patch need on mysql when you did compile under cygwin. + +// ェーァ 簿ウエ乱 劇葺流 + +擽 売。懋キク棹乱 ヨ。 ァ誤豆牟ァ カカ捩 BSD 攵擽┥侃 符椈乱 ーー小 姓笈共. +キクヲャウ import 頗 カカ捩 寳椈 athena攪 GPL 攵擽┥侃 符椈乱 梭慣笈共. + +擽 売。懋キク棹捩 public licenseー 符漁笈共. 葺ァァ ウイス, ー懍。ー, 們菩捩 +ャエ懦復 来圸据ゥー キク乱 劇復 ア桷捩 梵ー ァァ 賦慣笈共. + +椪 擽 ヲエヲャヲ壱株 刋イス梭 徐梠物揆 ウエ棗葺ァ 賦慣笈共. キクヲャウ キク乱 劇復 +メ橋 戦復 壱劇 ー幗ァ 賦慣笈共. + +クー唖攤 カカ攪 メ橋捩 ー幗ァ 賦慣笈共. + +ー罹ー 劍イス +P4 2.4Gh/512MB/WinXP/cygwin + +athena株 GPL 攵擽┥侃・シ 、 鮒笈共. +Ragnarok株 Gravity攪 trademark桿笈共. +login-db for athena株 athena乱 ーク乖 カカ揆 懍匣葺ゥエ BSD 攵擽┥侃・シ 伐ヲ笈共. +cygwin捩 Redhat攪 trademark桿笈共. + +// サエ血攵乱 劇葺流. + +擽 売。懋キク棹揆 サエ血攵葺クー 怱葺流 mysqlclient乱 劇復 libraryー 符囈鮒笈共. +擽株 mysql攪 護侃・シ サエ血攵葺ゥエ 冥揆 梭慣笈共. + +サエ血攵亨乱 -lmysqlclient 亰們擽 符囈鮒笈共. + +cygwin攪 イス垈株 ェーァ 肩ケ俾ー 符囈鮒笈共. + +// 縺縺上▽縺九ョ諠蝣ア縺ォ蟇セ縺励※ + +縺薙ョ繝励Ο繧ー繝ゥ繝縺ァ譁ー縺溘↓菴懊i繧後◆驛ィ蛻縺ッ BSD 繝ゥ繧、繧サ繝ウ繧ケ縺ョ荳九↓驟榊ク縺ォ縺ェ繧翫∪縺. +縺昴@縺ヲ import 縺ォ縺ェ縺」縺滄Κ蛻縺ッ蜈縲 athena縺ョ GPL 繝ゥ繧、繧サ繝ウ繧ケ縺ョ荳九↓縺ゅj縺セ縺. + +縺薙ョ繝励Ο繧ー繝ゥ繝縺ッ public license縺ァ縺ッ縺ェ縺縺ァ縺. 縺励°縺怜、画峩, 謾ケ騾, 菫ョ豁」縺ッ +辟。蛻カ髯占ィア螳ケ縺輔l縺ヲ縺昴l縺ォ蟇セ縺吶k雋ャ莉サ縺ッ闡苓縺瑚イ縺代↑縺縺ァ縺. + +迴セ蝨ィ縺薙ョ繝ェ繝ェ繝シ繧ケ縺ッ螳悟」√↓蜍穂ス懊☆繧九%縺ィ繧剃ソ晞囿縺励↑縺縺ァ縺. 縺昴@縺ヲ蠖シ縺ォ螟ァ髻 +逶ク隲繧らオカ蟇セ蜿励¢縺ェ縺縺ァ縺. + +謚陦鍋噪縺ェ驛ィ蛻縺ョ逶ク隲縺ッ蜿励¢縺ェ縺縺ァ縺. + +髢狗匱迺ー蠅 +P4 2.4Gh/512MB/WinXP/cygwin + +athena縺ッ GPL 繝ゥ繧、繧サ繝ウ繧ケ繧貞ョ医j縺セ縺. +Ragnarok縺ッ Gravity縺ョ trademark縺ァ縺. +login-db for athena縺ッ athena縺九i謖√▲縺ヲ譚・縺滄Κ蛻繧帝勁縺代ー BSD 繝ゥ繧、繧サ繝ウ繧ケ縺ォ莉倥″縺セ縺. +cygwin縺ッ Redhat縺ョ trademark縺ァ縺. + +// 繧ウ繝ウ繝代う繝ォ縺ォ蟇セ縺励※. + +縺薙ョ繝励Ο繧ー繝ゥ繝繧偵さ繝ウ繝代う繝ォ縺吶k縺溘a縺ォ mysqlclient縺ォ蟇セ縺吶k library縺悟ソ隕√〒縺. +縺薙l縺ッ mysql縺ョ繧ス繝シ繧ケ繧偵さ繝ウ繝代う繝ォ縺吶l縺ー蠕励k縺薙→縺後〒縺阪∪縺. + +繧ウ繝ウ繝代う繝ォ縺ョ譎ゅ↓ -lmysqlclient 繧ェ繝励す繝ァ繝ウ縺悟ソ隕√〒縺. + +cygwin縺ョ蝣エ蜷医ッ縺縺上▽縺九ョ繝代ャ繝√′蠢隕√〒縺.
\ No newline at end of file diff --git a/misc/src/login_sql/strlib.c b/misc/src/login_sql/strlib.c new file mode 100644 index 0000000..7f6e197 --- /dev/null +++ b/misc/src/login_sql/strlib.c @@ -0,0 +1,58 @@ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "strlib.h" +#include "utils.h" + +//----------------------------------------------- +// string lib. +unsigned char* jstrescape (unsigned char* pt) { + //copy from here + unsigned char * ptr; + int i =0, j=0; + + //copy string to temporary + CREATE(ptr, char, J_MAX_MALLOC_SIZE); + strcpy (ptr,pt); + + while (ptr[i] != '\0') { + switch (ptr[i]) { + case '\'': + pt[j++] = '\\'; + pt[j++] = ptr[i++]; + break; + case '\\': + pt[j++] = '\\'; + pt[j++] = ptr[i++]; + break; + default: + pt[j++] = ptr[i++]; + } + } + pt[j++] = '\0'; + free (ptr); + return (unsigned char*) &pt[0]; +} + +unsigned char* jstrescapecpy (unsigned char* pt,unsigned char* spt) { + //copy from here + int i =0, j=0; + + while (spt[i] != '\0') { + switch (spt[i]) { + case '\'': + pt[j++] = '\\'; + pt[j++] = spt[i++]; + break; + case '\\': + pt[j++] = '\\'; + pt[j++] = spt[i++]; + break; + default: + pt[j++] = spt[i++]; + } + } + pt[j++] = '\0'; + return (unsigned char*) &pt[0]; +} diff --git a/misc/src/login_sql/strlib.h b/misc/src/login_sql/strlib.h new file mode 100644 index 0000000..ddf29ab --- /dev/null +++ b/misc/src/login_sql/strlib.h @@ -0,0 +1,9 @@ +#ifndef _J_STR_LIB_H_ +#define _J_STR_LIB_H_ +#define J_MAX_MALLOC_SIZE 65535 +// String function library. +// code by Jioh L. Jung (ziozzang@4wish.net) +// This code is under license "BSD" +unsigned char* jstrescape (unsigned char* pt); +unsigned char* jstrescapecpy (unsigned char* pt,unsigned char* spt); +#endif diff --git a/misc/src/login_sql/timer.h b/misc/src/login_sql/timer.h new file mode 100644 index 0000000..be9706a --- /dev/null +++ b/misc/src/login_sql/timer.h @@ -0,0 +1,43 @@ +// original : core.h 2003/03/14 11:55:25 Rev 1.4 + +#ifndef _TIMER_H_ +#define _TIMER_H_ + +#define BASE_TICK 5 + +#define TIMER_ONCE_AUTODEL 1 +#define TIMER_INTERVAL 2 +#define TIMER_REMOVE_HEAP 16 + +#define DIFF_TICK(a,b) ((int)((a)-(b))) + +// Struct declaration + +struct TimerData { + unsigned int tick; + int (*func)(int,unsigned int,int,int); + int id; + int data; + int type; + int interval; + int heap_pos; +}; + +// Function prototype declaration + +unsigned int gettick_nocache(void); +unsigned int gettick(void); + +int add_timer(unsigned int,int (*)(int,unsigned int,int,int),int,int); +int add_timer_interval(unsigned int,int (*)(int,unsigned int,int,int),int,int,int); +int delete_timer(int,int (*)(int,unsigned int,int,int)); + +int addtick_timer(int tid,unsigned int tick); +struct TimerData *get_timer(int tid); + +int do_timer(unsigned int tick); + +int add_timer_func_list(int (*)(int,unsigned int,int,int),char*); +char* search_timer_func_list(int (*)(int,unsigned int,int,int)); + +#endif // _TIMER_H_ diff --git a/misc/src/map/GNUmakefile b/misc/src/map/GNUmakefile new file mode 100644 index 0000000..852bbb9 --- /dev/null +++ b/misc/src/map/GNUmakefile @@ -0,0 +1,72 @@ +all: txt sql
+
+txt: txtobj map-server
+
+sql: sqlobj map-server_sql
+
+txtobj:
+ mkdir txtobj
+
+sqlobj:
+ mkdir sqlobj
+
+COMMON_OBJ = ../common/core.o ../common/socket.o ../common/timer.o ../common/grfio.o ../common/db.o ../common/lock.o ../common/nullpo.o ../common/malloc.o
+LIBS = -lz -lm
+
+map-server: txtobj/map.o txtobj/chrif.o txtobj/clif.o txtobj/pc.o txtobj/npc.o txtobj/chat.o txtobj/path.o txtobj/itemdb.o txtobj/mob.o txtobj/script.o txtobj/storage.o txtobj/skill.o txtobj/atcommand.o txtobj/battle.o txtobj/intif.o txtobj/trade.o txtobj/party.o txtobj/vending.o txtobj/guild.o txtobj/pet.o $(COMMON_OBJ)
+ $(CC) -o ../../$@ $^ $(LIBS)
+
+map-server_sql: sqlobj/map.o sqlobj/chrif.o sqlobj/clif.o sqlobj/pc.o sqlobj/npc.o sqlobj/chat.o sqlobj/path.o sqlobj/itemdb.o sqlobj/mob.o sqlobj/script.o sqlobj/storage.o sqlobj/skill.o sqlobj/atcommand.o sqlobj/battle.o sqlobj/intif.o sqlobj/trade.o sqlobj/party.o sqlobj/vending.o sqlobj/guild.o sqlobj/pet.o sqlobj/mail.o $(COMMON_OBJ)
+ $(CC) -o ../../$@ $^ $(LIB_S)
+
+txtobj/%.o: %.c
+ $(COMPILE.c) -DTXT_ONLY $(OUTPUT_OPTION) $<
+
+sqlobj/%.o: %.c
+ $(COMPILE.c) $(OUTPUT_OPTION) $<
+
+txtobj/map.o: map.c map.h chrif.h clif.h npc.h pc.h mob.h chat.h skill.h itemdb.h storage.h party.h pet.h atcommand.h ../common/core.h ../common/timer.h ../common/db.h ../common/grfio.h ../common/mmo.h
+txtobj/chrif.o: chrif.c map.h battle.h chrif.h clif.h intif.h pc.h npc.h ../common/socket.h ../common/timer.h ../common/mmo.h
+txtobj/clif.o: clif.c map.h chrif.h clif.h mob.h intif.h pc.h npc.h itemdb.h chat.h script.h storage.h party.h guild.h atcommand.h pet.h atcommand.h ../common/socket.h ../common/timer.h ../common/mmo.h ../common/version.h
+txtobj/pc.o: pc.c map.h clif.h intif.h pc.h npc.h mob.h itemdb.h battle.h skill.h script.h party.h guild.h pet.h trade.h storage.h chat.h vending.h ../common/timer.h ../common/mmo.h ../common/db.h
+txtobj/npc.o: npc.c map.h npc.h clif.h pc.h script.h mob.h itemdb.h battle.h ../common/db.h ../common/timer.h ../common/mmo.h
+txtobj/chat.o: chat.c map.h clif.h pc.h chat.h ../common/db.h ../common/mmo.h
+txtobj/path.o: path.c map.h battle.h ../common/mmo.h
+txtobj/itemdb.o: itemdb.c map.h battle.h itemdb.h ../common/db.h ../common/grfio.h ../common/mmo.h
+txtobj/mob.o: mob.c map.h clif.h intif.h pc.h mob.h skill.h battle.h npc.h itemdb.h ../common/timer.h ../common/socket.h ../common/mmo.h
+txtobj/script.o: script.c itemdb.h map.h pc.h mob.h clif.h intif.h npc.h script.h storage.h skill.h pet.h battle.h ../common/timer.h ../common/socket.h ../common/db.h ../common/mmo.h ../common/lock.h
+txtobj/storage.o: storage.c itemdb.h pc.h clif.h intif.h storage.h guild.h ../common/mmo.h ../common/db.h
+txtobj/skill.o: skill.c skill.h map.h clif.h pc.h mob.h battle.h itemdb.h script.h ../common/timer.h ../common/mmo.h
+txtobj/atcommand.o: atcommand.c atcommand.h itemdb.h pc.h map.h skill.h clif.h mob.h intif.h battle.h storage.h guild.h pet.h ../common/socket.h ../common/timer.h ../common/mmo.h
+txtobj/battle.o: battle.c battle.h skill.h map.h mob.h pc.h pet.h guild.h ../common/timer.h ../common/mmo.h
+txtobj/intif.o: intif.c intif.h chrif.h clif.h party.h guild.h storage.h map.h battle.h pet.h ../common/socket.h ../common/mmo.h
+txtobj/trade.o: trade.c trade.h clif.h itemdb.h map.h pc.h npc.h ../common/mmo.h
+txtobj/party.o: party.c party.h clif.h intif.h pc.h map.h battle.h ../common/db.h ../common/socket.h ../common/timer.h ../common/mmo.h
+txtobj/vending.o: vending.c vending.h clif.h itemdb.h map.h pc.h ../common/mmo.h
+txtobj/guild.o: guild.c guild.h storage.h battle.h clif.h intif.h pc.h npc.h map.h ../common/db.h ../common/socket.h ../common/timer.h ../common/mmo.h
+txtobj/pet.o: pet.c pet.h map.h clif.h chrif.h intif.h pc.h itemdb.h battle.h mob.h npc.h script.h ../common/db.h ../common/socket.h ../common/timer.h ../common/mmo.h
+
+sqlobj/map.o: map.c map.h chrif.h clif.h npc.h pc.h mob.h chat.h skill.h itemdb.h storage.h party.h pet.h atcommand.h ../common/core.h ../common/timer.h ../common/db.h ../common/grfio.h ../common/mmo.h
+sqlobj/chrif.o: chrif.c map.h battle.h chrif.h clif.h intif.h pc.h npc.h ../common/socket.h ../common/timer.h ../common/mmo.h
+sqlobj/clif.o: clif.c map.h chrif.h clif.h mob.h intif.h pc.h npc.h itemdb.h chat.h script.h storage.h party.h guild.h atcommand.h pet.h atcommand.h ../common/socket.h ../common/timer.h ../common/mmo.h ../common/version.h
+sqlobj/pc.o: pc.c map.h clif.h intif.h pc.h npc.h mob.h itemdb.h battle.h skill.h script.h party.h guild.h pet.h trade.h storage.h chat.h vending.h ../common/timer.h ../common/mmo.h ../common/db.h
+sqlobj/npc.o: npc.c map.h npc.h clif.h pc.h script.h mob.h itemdb.h battle.h ../common/db.h ../common/timer.h ../common/mmo.h
+sqlobj/chat.o: chat.c map.h clif.h pc.h chat.h ../common/db.h ../common/mmo.h
+sqlobj/path.o: path.c map.h battle.h ../common/mmo.h
+sqlobj/itemdb.o: itemdb.c map.h battle.h itemdb.h ../common/db.h ../common/grfio.h ../common/mmo.h
+sqlobj/mob.o: mob.c map.h clif.h intif.h pc.h mob.h skill.h battle.h npc.h itemdb.h ../common/timer.h ../common/socket.h ../common/mmo.h
+sqlobj/script.o: script.c itemdb.h map.h pc.h mob.h clif.h intif.h npc.h script.h storage.h skill.h pet.h battle.h ../common/timer.h ../common/socket.h ../common/db.h ../common/mmo.h ../common/lock.h
+sqlobj/storage.o: storage.c itemdb.h pc.h clif.h intif.h storage.h guild.h ../common/mmo.h ../common/db.h
+sqlobj/skill.o: skill.c skill.h map.h clif.h pc.h mob.h battle.h itemdb.h script.h ../common/timer.h ../common/mmo.h
+sqlobj/atcommand.o: atcommand.c atcommand.h itemdb.h pc.h map.h skill.h clif.h mob.h intif.h battle.h storage.h guild.h pet.h ../common/socket.h ../common/timer.h ../common/mmo.h
+sqlobj/battle.o: battle.c battle.h skill.h map.h mob.h pc.h pet.h guild.h ../common/timer.h ../common/mmo.h
+sqlobj/intif.o: intif.c intif.h chrif.h clif.h party.h guild.h storage.h map.h battle.h pet.h ../common/socket.h ../common/mmo.h
+sqlobj/trade.o: trade.c trade.h clif.h itemdb.h map.h pc.h npc.h ../common/mmo.h
+sqlobj/party.o: party.c party.h clif.h intif.h pc.h map.h battle.h ../common/db.h ../common/socket.h ../common/timer.h ../common/mmo.h
+sqlobj/vending.o: vending.c vending.h clif.h itemdb.h map.h pc.h ../common/mmo.h
+sqlobj/guild.o: guild.c guild.h storage.h battle.h clif.h intif.h pc.h npc.h map.h ../common/db.h ../common/socket.h ../common/timer.h ../common/mmo.h
+sqlobj/pet.o: pet.c pet.h map.h clif.h chrif.h intif.h pc.h itemdb.h battle.h mob.h npc.h script.h ../common/db.h ../common/socket.h ../common/timer.h ../common/mmo.h
+sqlobj/mail.o: mail.c mail.h
+
+clean:
+ rm -rf *.o ../../map-server ../../map-server_sql sqlobj txtobj
diff --git a/misc/src/map/Makefile b/misc/src/map/Makefile new file mode 100644 index 0000000..3f5396e --- /dev/null +++ b/misc/src/map/Makefile @@ -0,0 +1,72 @@ +all: txt sql
+
+txt: txtobj map-server
+
+sql: sqlobj map-server_sql
+
+txtobj:
+ mkdir txtobj
+
+sqlobj:
+ mkdir sqlobj
+
+COMMON_OBJ = ../common/core.o ../common/socket.o ../common/timer.o ../common/grfio.o ../common/db.o ../common/lock.o ../common/nullpo.o ../common/malloc.o
+LIBS = -lz -lm
+
+map-server: txtobj/map.o txtobj/chrif.o txtobj/clif.o txtobj/pc.o txtobj/npc.o txtobj/chat.o txtobj/path.o txtobj/itemdb.o txtobj/mob.o txtobj/script.o txtobj/storage.o txtobj/skill.o txtobj/atcommand.o txtobj/battle.o txtobj/intif.o txtobj/trade.o txtobj/party.o txtobj/vending.o txtobj/guild.o txtobj/pet.o $(COMMON_OBJ)
+ $(CC) -o ../../$@ $> $(LIBS)
+
+map-server_sql: sqlobj/map.o sqlobj/chrif.o sqlobj/clif.o sqlobj/pc.o sqlobj/npc.o sqlobj/chat.o sqlobj/path.o sqlobj/itemdb.o sqlobj/mob.o sqlobj/script.o sqlobj/storage.o sqlobj/skill.o sqlobj/atcommand.o sqlobj/battle.o sqlobj/intif.o sqlobj/trade.o sqlobj/party.o sqlobj/vending.o sqlobj/guild.o sqlobj/pet.o sqlobj/mail.o $(COMMON_OBJ)
+ $(CC) -o ../../$@ $> $(LIB_S)
+
+txtobj/%.o: %.c
+ $(COMPILE.c) -DTXT_ONLY $(OUTPUT_OPTION) $<
+
+sqlobj/%.o: %.c
+ $(COMPILE.c) $(OUTPUT_OPTION) $<
+
+txtobj/map.o: map.c map.h chrif.h clif.h npc.h pc.h mob.h chat.h skill.h itemdb.h storage.h party.h pet.h atcommand.h ../common/core.h ../common/timer.h ../common/db.h ../common/grfio.h ../common/mmo.h
+txtobj/chrif.o: chrif.c map.h battle.h chrif.h clif.h intif.h pc.h npc.h ../common/socket.h ../common/timer.h ../common/mmo.h
+txtobj/clif.o: clif.c map.h chrif.h clif.h mob.h intif.h pc.h npc.h itemdb.h chat.h script.h storage.h party.h guild.h atcommand.h pet.h atcommand.h ../common/socket.h ../common/timer.h ../common/mmo.h ../common/version.h
+txtobj/pc.o: pc.c map.h clif.h intif.h pc.h npc.h mob.h itemdb.h battle.h skill.h script.h party.h guild.h pet.h trade.h storage.h chat.h vending.h ../common/timer.h ../common/mmo.h ../common/db.h
+txtobj/npc.o: npc.c map.h npc.h clif.h pc.h script.h mob.h itemdb.h battle.h ../common/db.h ../common/timer.h ../common/mmo.h
+txtobj/chat.o: chat.c map.h clif.h pc.h chat.h ../common/db.h ../common/mmo.h
+txtobj/path.o: path.c map.h battle.h ../common/mmo.h
+txtobj/itemdb.o: itemdb.c map.h battle.h itemdb.h ../common/db.h ../common/grfio.h ../common/mmo.h
+txtobj/mob.o: mob.c map.h clif.h intif.h pc.h mob.h skill.h battle.h npc.h itemdb.h ../common/timer.h ../common/socket.h ../common/mmo.h
+txtobj/script.o: script.c itemdb.h map.h pc.h mob.h clif.h intif.h npc.h script.h storage.h skill.h pet.h battle.h ../common/timer.h ../common/socket.h ../common/db.h ../common/mmo.h ../common/lock.h
+txtobj/storage.o: storage.c itemdb.h pc.h clif.h intif.h storage.h guild.h ../common/mmo.h ../common/db.h
+txtobj/skill.o: skill.c skill.h map.h clif.h pc.h mob.h battle.h itemdb.h script.h ../common/timer.h ../common/mmo.h
+txtobj/atcommand.o: atcommand.c atcommand.h itemdb.h pc.h map.h skill.h clif.h mob.h intif.h battle.h storage.h guild.h pet.h ../common/socket.h ../common/timer.h ../common/mmo.h
+txtobj/battle.o: battle.c battle.h skill.h map.h mob.h pc.h pet.h guild.h ../common/timer.h ../common/mmo.h
+txtobj/intif.o: intif.c intif.h chrif.h clif.h party.h guild.h storage.h map.h battle.h pet.h ../common/socket.h ../common/mmo.h
+txtobj/trade.o: trade.c trade.h clif.h itemdb.h map.h pc.h npc.h ../common/mmo.h
+txtobj/party.o: party.c party.h clif.h intif.h pc.h map.h battle.h ../common/db.h ../common/socket.h ../common/timer.h ../common/mmo.h
+txtobj/vending.o: vending.c vending.h clif.h itemdb.h map.h pc.h ../common/mmo.h
+txtobj/guild.o: guild.c guild.h storage.h battle.h clif.h intif.h pc.h npc.h map.h ../common/db.h ../common/socket.h ../common/timer.h ../common/mmo.h
+txtobj/pet.o: pet.c pet.h map.h clif.h chrif.h intif.h pc.h itemdb.h battle.h mob.h npc.h script.h ../common/db.h ../common/socket.h ../common/timer.h ../common/mmo.h
+
+sqlobj/map.o: map.c map.h chrif.h clif.h npc.h pc.h mob.h chat.h skill.h itemdb.h storage.h party.h pet.h atcommand.h ../common/core.h ../common/timer.h ../common/db.h ../common/grfio.h ../common/mmo.h
+sqlobj/chrif.o: chrif.c map.h battle.h chrif.h clif.h intif.h pc.h npc.h ../common/socket.h ../common/timer.h ../common/mmo.h
+sqlobj/clif.o: clif.c map.h chrif.h clif.h mob.h intif.h pc.h npc.h itemdb.h chat.h script.h storage.h party.h guild.h atcommand.h pet.h atcommand.h ../common/socket.h ../common/timer.h ../common/mmo.h ../common/version.h
+sqlobj/pc.o: pc.c map.h clif.h intif.h pc.h npc.h mob.h itemdb.h battle.h skill.h script.h party.h guild.h pet.h trade.h storage.h chat.h vending.h ../common/timer.h ../common/mmo.h ../common/db.h
+sqlobj/npc.o: npc.c map.h npc.h clif.h pc.h script.h mob.h itemdb.h battle.h ../common/db.h ../common/timer.h ../common/mmo.h
+sqlobj/chat.o: chat.c map.h clif.h pc.h chat.h ../common/db.h ../common/mmo.h
+sqlobj/path.o: path.c map.h battle.h ../common/mmo.h
+sqlobj/itemdb.o: itemdb.c map.h battle.h itemdb.h ../common/db.h ../common/grfio.h ../common/mmo.h
+sqlobj/mob.o: mob.c map.h clif.h intif.h pc.h mob.h skill.h battle.h npc.h itemdb.h ../common/timer.h ../common/socket.h ../common/mmo.h
+sqlobj/script.o: script.c itemdb.h map.h pc.h mob.h clif.h intif.h npc.h script.h storage.h skill.h pet.h battle.h ../common/timer.h ../common/socket.h ../common/db.h ../common/mmo.h ../common/lock.h
+sqlobj/storage.o: storage.c itemdb.h pc.h clif.h intif.h storage.h guild.h ../common/mmo.h ../common/db.h
+sqlobj/skill.o: skill.c skill.h map.h clif.h pc.h mob.h battle.h itemdb.h script.h ../common/timer.h ../common/mmo.h
+sqlobj/atcommand.o: atcommand.c atcommand.h itemdb.h pc.h map.h skill.h clif.h mob.h intif.h battle.h storage.h guild.h pet.h ../common/socket.h ../common/timer.h ../common/mmo.h
+sqlobj/battle.o: battle.c battle.h skill.h map.h mob.h pc.h pet.h guild.h ../common/timer.h ../common/mmo.h
+sqlobj/intif.o: intif.c intif.h chrif.h clif.h party.h guild.h storage.h map.h battle.h pet.h ../common/socket.h ../common/mmo.h
+sqlobj/trade.o: trade.c trade.h clif.h itemdb.h map.h pc.h npc.h ../common/mmo.h
+sqlobj/party.o: party.c party.h clif.h intif.h pc.h map.h battle.h ../common/db.h ../common/socket.h ../common/timer.h ../common/mmo.h
+sqlobj/vending.o: vending.c vending.h clif.h itemdb.h map.h pc.h ../common/mmo.h
+sqlobj/guild.o: guild.c guild.h storage.h battle.h clif.h intif.h pc.h npc.h map.h ../common/db.h ../common/socket.h ../common/timer.h ../common/mmo.h
+sqlobj/pet.o: pet.c pet.h map.h clif.h chrif.h intif.h pc.h itemdb.h battle.h mob.h npc.h script.h ../common/db.h ../common/socket.h ../common/timer.h ../common/mmo.h
+sqlobj/mail.o: mail.c mail.h
+
+clean:
+ rm -rf *.o ../../map-server ../../map-server_sql sqlobj txtobj
diff --git a/misc/src/map/atcommand.c b/misc/src/map/atcommand.c new file mode 100644 index 0000000..8dc39ba --- /dev/null +++ b/misc/src/map/atcommand.c @@ -0,0 +1,7753 @@ +// $Id: atcommand.c 148 2004-09-30 14:05:37Z MouseJstr $ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include <math.h> + +#include "socket.h" +#include "timer.h" +#include "nullpo.h" + +#include "clif.h" +#include "chrif.h" +#include "intif.h" +#include "itemdb.h" +#include "map.h" +#include "pc.h" +#include "skill.h" +#include "mob.h" +#include "pet.h" +#include "battle.h" +#include "party.h" +#include "guild.h" +#include "atcommand.h" +#include "script.h" +#include "npc.h" +#include "trade.h" +#include "core.h" + +#ifndef TXT_ONLY +#include "mail.h" +#endif + +#define STATE_BLIND 0x10 + +static char command_symbol = '@'; // first char of the commands (by [Yor]) + +static char msg_table[1000][1024]; // Server messages (0-499 reserved for GM commands, 500-999 reserved for others) + +#define ATCOMMAND_FUNC(x) int atcommand_ ## x (const int fd, struct map_session_data* sd, const char* command, const char* message) +ATCOMMAND_FUNC(broadcast); +ATCOMMAND_FUNC(localbroadcast); +ATCOMMAND_FUNC(rurap); +ATCOMMAND_FUNC(rura); +ATCOMMAND_FUNC(where); +ATCOMMAND_FUNC(jumpto); +ATCOMMAND_FUNC(jump); +ATCOMMAND_FUNC(who); +ATCOMMAND_FUNC(who2); +ATCOMMAND_FUNC(who3); +ATCOMMAND_FUNC(whomap); +ATCOMMAND_FUNC(whomap2); +ATCOMMAND_FUNC(whomap3); +ATCOMMAND_FUNC(whogm); // by Yor +ATCOMMAND_FUNC(save); +ATCOMMAND_FUNC(load); +ATCOMMAND_FUNC(speed); +ATCOMMAND_FUNC(storage); +ATCOMMAND_FUNC(guildstorage); +ATCOMMAND_FUNC(option); +ATCOMMAND_FUNC(hide); +ATCOMMAND_FUNC(jobchange); +ATCOMMAND_FUNC(die); +ATCOMMAND_FUNC(kill); +ATCOMMAND_FUNC(alive); +ATCOMMAND_FUNC(kami); +ATCOMMAND_FUNC(heal); +ATCOMMAND_FUNC(item); +ATCOMMAND_FUNC(item2); +ATCOMMAND_FUNC(itemreset); +ATCOMMAND_FUNC(itemcheck); +ATCOMMAND_FUNC(baselevelup); +ATCOMMAND_FUNC(joblevelup); +ATCOMMAND_FUNC(help); +ATCOMMAND_FUNC(gm); +ATCOMMAND_FUNC(pvpoff); +ATCOMMAND_FUNC(pvpon); +ATCOMMAND_FUNC(gvgoff); +ATCOMMAND_FUNC(gvgon); +ATCOMMAND_FUNC(model); +ATCOMMAND_FUNC(go); +ATCOMMAND_FUNC(monster); +ATCOMMAND_FUNC(spawn); +ATCOMMAND_FUNC(killmonster); +ATCOMMAND_FUNC(killmonster2); +ATCOMMAND_FUNC(refine); +ATCOMMAND_FUNC(produce); +ATCOMMAND_FUNC(memo); +ATCOMMAND_FUNC(gat); +ATCOMMAND_FUNC(packet); +ATCOMMAND_FUNC(statuspoint); +ATCOMMAND_FUNC(skillpoint); +ATCOMMAND_FUNC(zeny); +ATCOMMAND_FUNC(param); +ATCOMMAND_FUNC(guildlevelup); +ATCOMMAND_FUNC(makeegg); +ATCOMMAND_FUNC(hatch); +ATCOMMAND_FUNC(petfriendly); +ATCOMMAND_FUNC(pethungry); +ATCOMMAND_FUNC(petrename); +ATCOMMAND_FUNC(charpetrename); // by Yor +ATCOMMAND_FUNC(recall); +ATCOMMAND_FUNC(recallall); +ATCOMMAND_FUNC(character_job); +ATCOMMAND_FUNC(revive); +ATCOMMAND_FUNC(character_stats); +ATCOMMAND_FUNC(character_stats_all); +ATCOMMAND_FUNC(character_option); +ATCOMMAND_FUNC(character_save); +ATCOMMAND_FUNC(night); +ATCOMMAND_FUNC(day); +ATCOMMAND_FUNC(doom); +ATCOMMAND_FUNC(doommap); +ATCOMMAND_FUNC(raise); +ATCOMMAND_FUNC(raisemap); +ATCOMMAND_FUNC(character_baselevel); +ATCOMMAND_FUNC(character_joblevel); +ATCOMMAND_FUNC(kick); +ATCOMMAND_FUNC(kickall); +ATCOMMAND_FUNC(allskill); +ATCOMMAND_FUNC(questskill); +ATCOMMAND_FUNC(charquestskill); +ATCOMMAND_FUNC(lostskill); +ATCOMMAND_FUNC(charlostskill); +ATCOMMAND_FUNC(spiritball); +ATCOMMAND_FUNC(party); +ATCOMMAND_FUNC(guild); +ATCOMMAND_FUNC(charskreset); +ATCOMMAND_FUNC(charstreset); +ATCOMMAND_FUNC(charreset); +ATCOMMAND_FUNC(charstpoint); +ATCOMMAND_FUNC(charmodel); +ATCOMMAND_FUNC(charskpoint); +ATCOMMAND_FUNC(charzeny); +ATCOMMAND_FUNC(agitstart); +ATCOMMAND_FUNC(agitend); +ATCOMMAND_FUNC(reloaditemdb); +ATCOMMAND_FUNC(reloadmobdb); +ATCOMMAND_FUNC(reloadskilldb); +#ifndef TXT_ONLY +ATCOMMAND_FUNC(rehash);// by Fr3DBr +#else /* TXT_ONLY */ +ATCOMMAND_FUNC(reloadscript); +#endif /* TXT_ONLY */ +ATCOMMAND_FUNC(reloadgmdb); // by Yor +ATCOMMAND_FUNC(mapexit); +ATCOMMAND_FUNC(idsearch); +ATCOMMAND_FUNC(mapinfo); +ATCOMMAND_FUNC(dye); //** by fritz +ATCOMMAND_FUNC(hair_style); //** by fritz +ATCOMMAND_FUNC(hair_color); //** by fritz +ATCOMMAND_FUNC(stat_all); //** by fritz +ATCOMMAND_FUNC(char_change_sex); // by Yor +ATCOMMAND_FUNC(char_block); // by Yor +ATCOMMAND_FUNC(char_ban); // by Yor +ATCOMMAND_FUNC(char_unblock); // by Yor +ATCOMMAND_FUNC(char_unban); // by Yor +ATCOMMAND_FUNC(mount_peco); // by Valaris +ATCOMMAND_FUNC(char_mount_peco); // by Yor +ATCOMMAND_FUNC(guildspy); // [Syrus22] +ATCOMMAND_FUNC(partyspy); // [Syrus22] +ATCOMMAND_FUNC(repairall); // [Valaris] +ATCOMMAND_FUNC(guildrecall); // by Yor +ATCOMMAND_FUNC(partyrecall); // by Yor +//ATCOMMAND_FUNC(nuke); // [Valaris] +ATCOMMAND_FUNC(enablenpc); +ATCOMMAND_FUNC(disablenpc); +ATCOMMAND_FUNC(servertime); // by Yor +ATCOMMAND_FUNC(chardelitem); // by Yor +ATCOMMAND_FUNC(jail); // by Yor +ATCOMMAND_FUNC(unjail); // by Yor +ATCOMMAND_FUNC(disguise); // [Valaris] +ATCOMMAND_FUNC(undisguise); // by Yor +ATCOMMAND_FUNC(ignorelist); // by Yor +ATCOMMAND_FUNC(charignorelist); // by Yor +ATCOMMAND_FUNC(inall); // by Yor +ATCOMMAND_FUNC(exall); // by Yor +ATCOMMAND_FUNC(chardisguise); // Kalaspuff +ATCOMMAND_FUNC(charundisguise); // Kalaspuff +ATCOMMAND_FUNC(email); // by Yor +ATCOMMAND_FUNC(effect);//by Apple +ATCOMMAND_FUNC(character_item_list); // by Yor +ATCOMMAND_FUNC(character_storage_list); // by Yor +ATCOMMAND_FUNC(character_cart_list); // by Yor +ATCOMMAND_FUNC(addwarp); // by MouseJstr +ATCOMMAND_FUNC(follow); // by MouseJstr +ATCOMMAND_FUNC(skillon); // by MouseJstr +ATCOMMAND_FUNC(skilloff); // by MouseJstr +ATCOMMAND_FUNC(killer); // by MouseJstr +ATCOMMAND_FUNC(npcmove); // by MouseJstr +ATCOMMAND_FUNC(killable); // by MouseJstr +ATCOMMAND_FUNC(charkillable); // by MouseJstr +ATCOMMAND_FUNC(chareffect); // by MouseJstr +ATCOMMAND_FUNC(chardye); // by MouseJstr +ATCOMMAND_FUNC(charhairstyle); // by MouseJstr +ATCOMMAND_FUNC(charhaircolor); // by MouseJstr +ATCOMMAND_FUNC(dropall); // by MouseJstr +ATCOMMAND_FUNC(chardropall); // by MouseJstr +ATCOMMAND_FUNC(storeall); // by MouseJstr +ATCOMMAND_FUNC(charstoreall); // by MouseJstr +ATCOMMAND_FUNC(skillid); // by MouseJstr +ATCOMMAND_FUNC(useskill); // by MouseJstr +ATCOMMAND_FUNC(summon); +ATCOMMAND_FUNC(rain); +ATCOMMAND_FUNC(snow); +ATCOMMAND_FUNC(sakura); +ATCOMMAND_FUNC(fog); +ATCOMMAND_FUNC(leaves); +ATCOMMAND_FUNC(adjgmlvl); // by MouseJstr +ATCOMMAND_FUNC(adjcmdlvl); // by MouseJstr +ATCOMMAND_FUNC(trade); // by MouseJstr +ATCOMMAND_FUNC(unmute); // [Valaris] + +#ifndef TXT_ONLY +ATCOMMAND_FUNC(checkmail); // [Valaris] +ATCOMMAND_FUNC(listmail); // [Valaris] +ATCOMMAND_FUNC(listnewmail); // [Valaris] +ATCOMMAND_FUNC(readmail); // [Valaris] +ATCOMMAND_FUNC(sendmail); // [Valaris] +ATCOMMAND_FUNC(sendprioritymail); // [Valaris] +ATCOMMAND_FUNC(deletemail); // [Valaris] +ATCOMMAND_FUNC(sound); // [Valaris] +ATCOMMAND_FUNC(refreshonline); // [Valaris] +#endif /* TXT_ONLY */ + +/*========================================== + *AtCommandInfo atcommand_info[]構造体の定義 + *------------------------------------------ + */ + +// First char of commands is configured in atcommand_athena.conf. Leave @ in this list for default value. +// to set default level, read atcommand_athena.conf first please. +static AtCommandInfo atcommand_info[] = { + { AtCommand_RuraP, "@rura+", 60, atcommand_rurap }, + { AtCommand_RuraP, "@charwarp", 60, atcommand_rurap }, + { AtCommand_Rura, "@rura", 40, atcommand_rura }, + { AtCommand_Warp, "@warp", 40, atcommand_rura }, + { AtCommand_Where, "@where", 1, atcommand_where }, + { AtCommand_JumpTo, "@jumpto", 20, atcommand_jumpto }, // + /shift + { AtCommand_JumpTo, "@warpto", 20, atcommand_jumpto }, + { AtCommand_JumpTo, "@goto", 20, atcommand_jumpto }, + { AtCommand_Jump, "@jump", 40, atcommand_jump }, + { AtCommand_Who, "@who", 20, atcommand_who }, + { AtCommand_Who, "@whois", 20, atcommand_who }, + { AtCommand_Who2, "@who2", 20, atcommand_who2 }, + { AtCommand_Who3, "@who3", 20, atcommand_who3 }, + { AtCommand_WhoMap, "@whomap", 20, atcommand_whomap }, + { AtCommand_WhoMap2, "@whomap2", 20, atcommand_whomap2 }, + { AtCommand_WhoMap3, "@whomap3", 20, atcommand_whomap3 }, + { AtCommand_WhoGM, "@whogm", 20, atcommand_whogm }, // by Yor + { AtCommand_Save, "@save", 40, atcommand_save }, + { AtCommand_Load, "@return", 40, atcommand_load }, + { AtCommand_Load, "@load", 40, atcommand_load }, + { AtCommand_Speed, "@speed", 40, atcommand_speed }, + { AtCommand_Storage, "@storage", 1, atcommand_storage }, + { AtCommand_GuildStorage, "@gstorage", 50, atcommand_guildstorage }, + { AtCommand_Option, "@option", 40, atcommand_option }, + { AtCommand_Hide, "@hide", 40, atcommand_hide }, // + /hide + { AtCommand_JobChange, "@jobchange", 40, atcommand_jobchange }, + { AtCommand_JobChange, "@job", 40, atcommand_jobchange }, + { AtCommand_Die, "@die", 1, atcommand_die }, + { AtCommand_Kill, "@kill", 60, atcommand_kill }, + { AtCommand_Alive, "@alive", 60, atcommand_alive }, + { AtCommand_Kami, "@kami", 40, atcommand_kami }, + { AtCommand_KamiB, "@kamib", 40, atcommand_kami }, + { AtCommand_Heal, "@heal", 40, atcommand_heal }, + { AtCommand_Item, "@item", 60, atcommand_item }, + { AtCommand_Item2, "@item2", 60, atcommand_item2 }, + { AtCommand_ItemReset, "@itemreset", 40, atcommand_itemreset }, + { AtCommand_ItemCheck, "@itemcheck", 60, atcommand_itemcheck }, + { AtCommand_BaseLevelUp, "@lvup", 60, atcommand_baselevelup }, + { AtCommand_BaseLevelUp, "@blevel", 60, atcommand_baselevelup }, + { AtCommand_BaseLevelUp, "@baselvlup", 60, atcommand_baselevelup }, + { AtCommand_JobLevelUp, "@jlevel", 60, atcommand_joblevelup }, + { AtCommand_JobLevelUp, "@joblvup", 60, atcommand_joblevelup }, + { AtCommand_JobLevelUp, "@joblvlup", 60, atcommand_joblevelup }, + { AtCommand_H, "@h", 20, atcommand_help }, + { AtCommand_Help, "@help", 20, atcommand_help }, + { AtCommand_GM, "@gm", 100, atcommand_gm }, + { AtCommand_PvPOff, "@pvpoff", 40, atcommand_pvpoff }, + { AtCommand_PvPOn, "@pvpon", 40, atcommand_pvpon }, + { AtCommand_GvGOff, "@gvgoff", 40, atcommand_gvgoff }, + { AtCommand_GvGOff, "@gpvpoff", 40, atcommand_gvgoff }, + { AtCommand_GvGOn, "@gvgon", 40, atcommand_gvgon }, + { AtCommand_GvGOn, "@gpvpon", 40, atcommand_gvgon }, + { AtCommand_Model, "@model", 20, atcommand_model }, + { AtCommand_Go, "@go", 10, atcommand_go }, + { AtCommand_Spawn, "@monster", 50, atcommand_spawn }, + { AtCommand_Spawn, "@spawn", 50, atcommand_spawn }, + //{ AtCommand_Spawn, "@summon", 50, atcommand_spawn }, + { AtCommand_Monster, "@monster2", 50, atcommand_monster }, + { AtCommand_KillMonster, "@killmonster", 60, atcommand_killmonster }, + { AtCommand_KillMonster2, "@killmonster2", 40, atcommand_killmonster2 }, + { AtCommand_Refine, "@refine", 60, atcommand_refine }, + { AtCommand_Produce, "@produce", 60, atcommand_produce }, + { AtCommand_Memo, "@memo", 40, atcommand_memo }, + { AtCommand_GAT, "@gat", 99, atcommand_gat }, // debug function + { AtCommand_Packet, "@packet", 99, atcommand_packet }, // debug function + { AtCommand_StatusPoint, "@stpoint", 60, atcommand_statuspoint }, + { AtCommand_SkillPoint, "@skpoint", 60, atcommand_skillpoint }, + { AtCommand_Zeny, "@zeny", 60, atcommand_zeny }, + { AtCommand_Strength, "@str", 60, atcommand_param }, + { AtCommand_Agility, "@agi", 60, atcommand_param }, + { AtCommand_Vitality, "@vit", 60, atcommand_param }, + { AtCommand_Intelligence, "@int", 60, atcommand_param }, + { AtCommand_Dexterity, "@dex", 60, atcommand_param }, + { AtCommand_Luck, "@luk", 60, atcommand_param }, + { AtCommand_GuildLevelUp, "@guildlvup", 60, atcommand_guildlevelup }, + { AtCommand_GuildLevelUp, "@guildlvlup", 60, atcommand_guildlevelup }, + { AtCommand_MakeEgg, "@makeegg", 60, atcommand_makeegg }, + { AtCommand_Hatch, "@hatch", 60, atcommand_hatch }, + { AtCommand_PetFriendly, "@petfriendly", 40, atcommand_petfriendly }, + { AtCommand_PetHungry, "@pethungry", 40, atcommand_pethungry }, + { AtCommand_PetRename, "@petrename", 1, atcommand_petrename }, + { AtCommand_CharPetRename, "@charpetrename", 50, atcommand_charpetrename }, // by Yor + { AtCommand_Recall, "@recall", 60, atcommand_recall }, // + /recall + { AtCommand_CharacterJob, "@charjob", 60, atcommand_character_job }, + { AtCommand_CharacterJob, "@charjobchange", 60, atcommand_character_job }, + { AtCommand_Revive, "@revive", 60, atcommand_revive }, + { AtCommand_CharacterStats, "@charstats", 40, atcommand_character_stats }, + { AtCommand_CharacterStatsAll, "@charstatsall", 40, atcommand_character_stats_all }, + { AtCommand_CharacterOption, "@charoption", 60, atcommand_character_option }, + { AtCommand_CharacterSave, "@charsave", 60, atcommand_character_save }, + { AtCommand_Night, "@night", 80, atcommand_night }, + { AtCommand_Day, "@day", 80, atcommand_day }, + { AtCommand_Doom, "@doom", 80, atcommand_doom }, + { AtCommand_DoomMap, "@doommap", 80, atcommand_doommap }, + { AtCommand_Raise, "@raise", 80, atcommand_raise }, + { AtCommand_RaiseMap, "@raisemap", 80, atcommand_raisemap }, + { AtCommand_CharacterBaseLevel, "@charbaselvl", 60, atcommand_character_baselevel }, + { AtCommand_CharacterJobLevel, "@charjlvl", 60, atcommand_character_joblevel }, + { AtCommand_Kick, "@kick", 20, atcommand_kick }, // + right click menu for GM "(name) force to quit" + { AtCommand_KickAll, "@kickall", 99, atcommand_kickall }, + { AtCommand_AllSkill, "@allskill", 60, atcommand_allskill }, + { AtCommand_AllSkill, "@allskills", 60, atcommand_allskill }, + { AtCommand_AllSkill, "@skillall", 60, atcommand_allskill }, + { AtCommand_AllSkill, "@skillsall", 60, atcommand_allskill }, + { AtCommand_QuestSkill, "@questskill", 40, atcommand_questskill }, + { AtCommand_CharQuestSkill, "@charquestskill", 60, atcommand_charquestskill }, + { AtCommand_LostSkill, "@lostskill", 40, atcommand_lostskill }, + { AtCommand_CharLostSkill, "@charlostskill", 60, atcommand_charlostskill }, + { AtCommand_SpiritBall, "@spiritball", 40, atcommand_spiritball }, + { AtCommand_Party, "@party", 1, atcommand_party }, + { AtCommand_Guild, "@guild", 50, atcommand_guild }, + { AtCommand_AgitStart, "@agitstart", 60, atcommand_agitstart }, + { AtCommand_AgitEnd, "@agitend", 60, atcommand_agitend }, + { AtCommand_MapExit, "@mapexit", 99, atcommand_mapexit }, + { AtCommand_IDSearch, "@idsearch", 60, atcommand_idsearch }, + { AtCommand_MapMove, "@mapmove", 40, atcommand_rura }, // /mm command + { AtCommand_Broadcast, "@broadcast", 40, atcommand_broadcast }, // /b and /nb command + { AtCommand_LocalBroadcast, "@localbroadcast", 40, atcommand_localbroadcast }, // /lb and /nlb command + { AtCommand_RecallAll, "@recallall", 80, atcommand_recallall }, + { AtCommand_CharSkReset, "@charskreset", 60, atcommand_charskreset }, + { AtCommand_CharStReset, "@charstreset", 60, atcommand_charstreset }, + { AtCommand_ReloadItemDB, "@reloaditemdb", 99, atcommand_reloaditemdb }, // admin command + { AtCommand_ReloadMobDB, "@reloadmobdb", 99, atcommand_reloadmobdb }, // admin command + { AtCommand_ReloadSkillDB, "@reloadskilldb", 99, atcommand_reloadskilldb }, // admin command +#ifndef TXT_ONLY + { AtCommand_Rehash, "@rehash", 99, atcommand_rehash }, // admin command +#else /* TXT_ONLY */ + { AtCommand_ReloadScript, "@reloadscript", 99, atcommand_reloadscript }, // admin command +#endif /* TXT_ONLY */ + { AtCommand_ReloadGMDB, "@reloadgmdb", 99, atcommand_reloadgmdb }, // admin command + { AtCommand_CharReset, "@charreset", 60, atcommand_charreset }, + { AtCommand_CharModel, "@charmodel", 50, atcommand_charmodel }, + { AtCommand_CharSKPoint, "@charskpoint", 60, atcommand_charskpoint }, + { AtCommand_CharSTPoint, "@charstpoint", 60, atcommand_charstpoint }, + { AtCommand_CharZeny, "@charzeny", 60, atcommand_charzeny }, + { AtCommand_MapInfo, "@mapinfo", 99, atcommand_mapinfo }, + { AtCommand_Dye, "@dye", 40, atcommand_dye }, // by fritz + { AtCommand_Dye, "@ccolor", 40, atcommand_dye }, // by fritz + { AtCommand_Hstyle, "@hairstyle", 40, atcommand_hair_style }, // by fritz + { AtCommand_Hstyle, "@hstyle", 40, atcommand_hair_style }, // by fritz + { AtCommand_Hcolor, "@haircolor", 40, atcommand_hair_color }, // by fritz + { AtCommand_Hcolor, "@hcolor", 40, atcommand_hair_color }, // by fritz + { AtCommand_StatAll, "@statall", 60, atcommand_stat_all }, // by fritz + { AtCommand_StatAll, "@statsall", 60, atcommand_stat_all }, + { AtCommand_StatAll, "@allstats", 60, atcommand_stat_all }, // by fritz + { AtCommand_StatAll, "@allstat", 60, atcommand_stat_all }, // by fritz + { AtCommand_CharChangeSex, "@charchangesex", 60, atcommand_char_change_sex }, // by Yor + { AtCommand_CharBlock, "@block", 60, atcommand_char_block }, // by Yor + { AtCommand_CharBlock, "@charblock", 60, atcommand_char_block }, // by Yor + { AtCommand_CharBan, "@ban", 60, atcommand_char_ban }, // by Yor + { AtCommand_CharBan, "@banish", 60, atcommand_char_ban }, // by Yor + { AtCommand_CharBan, "@charban", 60, atcommand_char_ban }, // by Yor + { AtCommand_CharBan, "@charbanish", 60, atcommand_char_ban }, // by Yor + { AtCommand_CharUnBlock, "@unblock", 60, atcommand_char_unblock }, // by Yor + { AtCommand_CharUnBlock, "@charunblock", 60, atcommand_char_unblock }, // by Yor + { AtCommand_CharUnBan, "@unban", 60, atcommand_char_unban }, // by Yor + { AtCommand_CharUnBan, "@unbanish", 60, atcommand_char_unban }, // by Yor + { AtCommand_CharUnBan, "@charunban", 60, atcommand_char_unban }, // by Yor + { AtCommand_CharUnBan, "@charunbanish", 60, atcommand_char_unban }, // by Yor + { AtCommand_MountPeco, "@mountpeco", 20, atcommand_mount_peco }, // by Valaris + { AtCommand_CharMountPeco, "@charmountpeco", 50, atcommand_char_mount_peco }, // by Yor + { AtCommand_GuildSpy, "@guildspy", 60, atcommand_guildspy }, // [Syrus22] + { AtCommand_PartySpy, "@partyspy", 60, atcommand_partyspy }, // [Syrus22] + { AtCommand_RepairAll, "@repairall", 60, atcommand_repairall }, // [Valaris] + { AtCommand_GuildRecall, "@guildrecall", 60, atcommand_guildrecall }, // by Yor + { AtCommand_PartyRecall, "@partyrecall", 60, atcommand_partyrecall }, // by Yor +// { AtCommand_Nuke, "@nuke", 60, atcommand_nuke }, // [Valaris] + { AtCommand_Enablenpc, "@enablenpc", 80, atcommand_enablenpc }, // [] + { AtCommand_Disablenpc, "@disablenpc", 80, atcommand_disablenpc }, // [] + { AtCommand_ServerTime, "@time", 0, atcommand_servertime }, // by Yor + { AtCommand_ServerTime, "@date", 0, atcommand_servertime }, // by Yor + { AtCommand_ServerTime, "@server_date", 0, atcommand_servertime }, // by Yor + { AtCommand_ServerTime, "@serverdate", 0, atcommand_servertime }, // by Yor + { AtCommand_ServerTime, "@server_time", 0, atcommand_servertime }, // by Yor + { AtCommand_ServerTime, "@servertime", 0, atcommand_servertime }, // by Yor + { AtCommand_CharDelItem, "@chardelitem", 60, atcommand_chardelitem }, // by Yor + { AtCommand_Jail, "@jail", 60, atcommand_jail }, // by Yor + { AtCommand_UnJail, "@unjail", 60, atcommand_unjail }, // by Yor + { AtCommand_UnJail, "@discharge", 60, atcommand_unjail }, // by Yor + { AtCommand_Disguise, "@disguise", 20, atcommand_disguise }, // [Valaris] + { AtCommand_UnDisguise, "@undisguise", 20, atcommand_undisguise }, // by Yor + { AtCommand_IgnoreList, "@ignorelist", 0, atcommand_ignorelist }, // by Yor + { AtCommand_CharIgnoreList, "@charignorelist", 20, atcommand_charignorelist }, // by Yor + { AtCommand_IgnoreList, "@inall", 20, atcommand_inall }, // by Yor + { AtCommand_ExAll, "@exall", 20, atcommand_exall }, // by Yor + { AtCommand_CharDisguise, "@chardisguise", 60, atcommand_chardisguise }, // Kalaspuff + { AtCommand_CharUnDisguise, "@charundisguise", 60, atcommand_charundisguise }, // Kalaspuff + { AtCommand_EMail, "@email", 0, atcommand_email }, // by Yor + { AtCommand_Effect, "@effect", 40, atcommand_effect }, // by Apple + { AtCommand_Char_Item_List, "@charitemlist", 40, atcommand_character_item_list }, // by Yor + { AtCommand_Char_Storage_List, "@charstoragelist", 40, atcommand_character_storage_list }, // by Yor + { AtCommand_Char_Cart_List, "@charcartlist", 40, atcommand_character_cart_list }, // by Yor + { AtCommand_Follow, "@follow", 10, atcommand_follow }, // by MouseJstr + { AtCommand_AddWarp, "@addwarp", 20, atcommand_addwarp }, // by MouseJstr + { AtCommand_SkillOn, "@skillon", 20, atcommand_skillon }, // by MouseJstr + { AtCommand_SkillOff, "@skilloff", 20, atcommand_skilloff }, // by MouseJstr + { AtCommand_Killer, "@killer", 60, atcommand_killer }, // by MouseJstr + { AtCommand_NpcMove, "@npcmove", 20, atcommand_npcmove }, // by MouseJstr + { AtCommand_Killable, "@killable", 40, atcommand_killable }, // by MouseJstr + { AtCommand_CharKillable, "@charkillable", 40, atcommand_charkillable }, // by MouseJstr + { AtCommand_Chareffect, "@chareffect", 40, atcommand_chareffect }, // MouseJstr + //{ AtCommand_Chardye, "@chardye", 40, atcommand_chardye }, // MouseJstr + //{ AtCommand_Charhairstyle, "@charhairstyle", 40, atcommand_charhairstyle }, // MouseJstr + //{ AtCommand_Charhaircolor, "@charhaircolor", 40, atcommand_charhaircolor }, // MouseJstr + { AtCommand_Dropall, "@dropall", 40, atcommand_dropall }, // MouseJstr + { AtCommand_Chardropall, "@chardropall", 40, atcommand_chardropall }, // MouseJstr + { AtCommand_Storeall, "@storeall", 40, atcommand_storeall }, // MouseJstr + { AtCommand_Charstoreall, "@charstoreall", 40, atcommand_charstoreall }, // MouseJstr + { AtCommand_Skillid, "@skillid", 40, atcommand_skillid }, // MouseJstr + { AtCommand_Useskill, "@useskill", 40, atcommand_useskill }, // MouseJstr + { AtCommand_Rain, "@rain", 99, atcommand_rain }, + { AtCommand_Snow, "@snow", 99, atcommand_snow }, + { AtCommand_Sakura, "@sakura", 99, atcommand_sakura }, + { AtCommand_Fog, "@fog", 99, atcommand_fog }, + { AtCommand_Leaves, "@leaves", 99, atcommand_leaves }, + //{ AtCommand_Shuffle, "@shuffle", 99, atcommand_shuffle }, + //{ AtCommand_Maintenance, "@maintenance", 99, atcommand_maintenance }, + //{ AtCommand_Misceffect, "@misceffect", 60, atcommand_misceffect }, + { AtCommand_Summon, "@summon", 60, atcommand_summon }, + { AtCommand_AdjGmLvl, "@adjgmlvl", 99, atcommand_adjgmlvl }, + { AtCommand_AdjCmdLvl, "@adjcmdlvl", 99, atcommand_adjcmdlvl }, + { AtCommand_Trade, "@trade", 60, atcommand_trade }, + { AtCommand_UnMute, "@unmute", 60, atcommand_unmute }, // [Valaris] + +#ifndef TXT_ONLY // sql-only commands + { AtCommand_CheckMail, "@checkmail", 1, atcommand_listmail }, // [Valaris] + { AtCommand_ListMail, "@listmail", 1, atcommand_listmail }, // [Valaris] + { AtCommand_ListNewMail, "@listnewmail", 1, atcommand_listmail }, // [Valaris] + { AtCommand_ReadMail, "@readmail", 1, atcommand_readmail }, // [Valaris] + { AtCommand_DeleteMail, "@deletemail", 1, atcommand_readmail }, // [Valaris] + { AtCommand_SendMail, "@sendmail", 1, atcommand_sendmail }, // [Valaris] + { AtCommand_SendPriorityMail, "@sendprioritymail",80, atcommand_sendmail }, // [Valaris] + { AtCommand_RefreshOnline, "@refreshonline", 99, atcommand_refreshonline }, // [Valaris] +#endif /* TXT_ONLY */ + +// add new commands before this line + { AtCommand_Unknown, NULL, 1, NULL } +}; + +/*==================================================== + * This function return the name of the job (by [Yor]) + *---------------------------------------------------- + */ +char * job_name(int class) { + switch (class) { + case 0: return "Novice"; + case 1: return "Swordsman"; + case 2: return "Mage"; + case 3: return "Archer"; + case 4: return "Acolyte"; + case 5: return "Merchant"; + case 6: return "Thief"; + case 7: return "Knight"; + case 8: return "Priest"; + case 9: return "Wizard"; + case 10: return "Blacksmith"; + case 11: return "Hunter"; + case 12: return "Assassin"; + case 13: return "Knight 2"; + case 14: return "Crusader"; + case 15: return "Monk"; + case 16: return "Sage"; + case 17: return "Rogue"; + case 18: return "Alchemist"; + case 19: return "Bard"; + case 20: return "Dancer"; + case 21: return "Crusader 2"; + case 22: return "Wedding"; + case 23: return "Super Novice"; + case 4001: return "Novice High"; + case 4002: return "Swordsman High"; + case 4003: return "Mage High"; + case 4004: return "Archer High"; + case 4005: return "Acolyte High"; + case 4006: return "Merchant High"; + case 4007: return "Thief High"; + case 4008: return "Lord Knight"; + case 4009: return "High Priest"; + case 4010: return "High Wizard"; + case 4011: return "Whitesmith"; + case 4012: return "Sniper"; + case 4013: return "Assassin Cross"; + case 4014: return "Peko Knight"; + case 4015: return "Paladin"; + case 4016: return "Champion"; + case 4017: return "Professor"; + case 4018: return "Stalker"; + case 4019: return "Creator"; + case 4020: return "Clown"; + case 4021: return "Gypsy"; + case 4022: return "Peko Paladin"; + case 4023: return "Baby Novice"; + case 4024: return "Baby Swordsman"; + case 4025: return "Baby Mage"; + case 4026: return "Baby Archer"; + case 4027: return "Baby Acolyte"; + case 4028: return "Baby Merchant"; + case 4029: return "Baby Thief"; + case 4030: return "Baby Knight"; + case 4031: return "Baby Priest"; + case 4032: return "Baby Wizard"; + case 4033: return "Baby Blacksmith"; + case 4034: return "Baby Hunter"; + case 4035: return "Baby Assassin"; + case 4036: return "Baby Peco Knight"; + case 4037: return "Baby Crusader"; + case 4038: return "Baby Monk"; + case 4039: return "Baby Sage"; + case 4040: return "Baby Rogue"; + case 4041: return "Baby Alchemist"; + case 4042: return "Baby Bard"; + case 4043: return "Baby Dancer"; + case 4044: return "Baby Peco Crusader"; + case 4045: return "Super Baby"; + } + return "Unknown Job"; +} + +//----------------------------------------------------------- +// Return the message string of the specified number by [Yor] +//----------------------------------------------------------- +char * msg_txt(int msg_number) { + if (msg_number >= 0 && msg_number < (int)(sizeof(msg_table) / sizeof(msg_table[0])) && + msg_table[msg_number] != NULL && msg_table[msg_number][0] != '\0') + return msg_table[msg_number]; + + return "??"; +} + +//------------------------------------------------------------ +// E-mail check: return 0 (not correct) or 1 (valid). by [Yor] +//------------------------------------------------------------ +int e_mail_check(unsigned char *email) { + char ch; + unsigned char* last_arobas; + + // athena limits + if (strlen(email) < 3 || strlen(email) > 39) + return 0; + + // part of RFC limits (official reference of e-mail description) + if (strchr(email, '@') == NULL || email[strlen(email)-1] == '@') + return 0; + + if (email[strlen(email)-1] == '.') + return 0; + + last_arobas = strrchr(email, '@'); + + if (strstr(last_arobas, "@.") != NULL || + strstr(last_arobas, "..") != NULL) + return 0; + + for(ch = 1; ch < 32; ch++) { + if (strchr(last_arobas, ch) != NULL) { + return 0; + break; + } + } + + if (strchr(last_arobas, ' ') != NULL || + strchr(last_arobas, ';') != NULL) + return 0; + + // all correct + return 1; +} + +/*========================================== + * get_atcommand_level @コマンドの必要レベルを取得 + *------------------------------------------ + */ +int get_atcommand_level(const AtCommandType type) { + int i; + + for (i = 0; atcommand_info[i].type != AtCommand_None; i++) + if (atcommand_info[i].type == type) + return atcommand_info[i].level; + + return 100; // 100: command can not be used +} + +/*========================================== + *is_atcommand @コマンドに存在するかどうか確認する + *------------------------------------------ + */ +AtCommandType +is_atcommand(const int fd, struct map_session_data* sd, const char* message, int gmlvl) { + const char* str = message; + int s_flag = 0; + AtCommandInfo info; + AtCommandType type; + + nullpo_retr(AtCommand_None, sd); + + if (!message || !*message) + return AtCommand_None; + + memset(&info, 0, sizeof(info)); + str += strlen(sd->status.name); + while (*str && (isspace(*str) || (s_flag == 0 && *str == ':'))) { + if (*str == ':') + s_flag = 1; + str++; + } + if (!*str) + return AtCommand_None; + + type = atcommand(gmlvl > 0 ? gmlvl : pc_isGM(sd), str, &info); + if (type != AtCommand_None) { + char command[100]; + char output[200]; + const char* p = str; + memset(command, '\0', sizeof(command)); + memset(output, '\0', sizeof(output)); + while (*p && !isspace(*p)) + p++; + if (p - str >= sizeof(command)) // too long + return AtCommand_Unknown; + strncpy(command, str, p - str); + while (isspace(*p)) + p++; + + if (type == AtCommand_Unknown || info.proc == NULL) { + sprintf(output, msg_table[153], command); // %s is Unknown Command. + clif_displaymessage(fd, output); + } else { + if (info.proc(fd, sd, command, p) != 0) { + // Command can not be executed + sprintf(output, msg_table[154], command); // %s failed. + clif_displaymessage(fd, output); + } + } + + return info.type; + } + + return AtCommand_None; +} + +/*========================================== + * + *------------------------------------------ + */ +AtCommandType atcommand(const int level, const char* message, struct AtCommandInfo* info) { + char* p = (char *)message; // it's 'char' and not 'const char' to have possibility to modify the first character if necessary + + if (!info) + return AtCommand_None; + if (battle_config.atc_gmonly != 0 && !level) // level = pc_isGM(sd) + return AtCommand_None; + if (!p || !*p) { + fprintf(stderr, "at command message is empty\n"); + return AtCommand_None; + } + + if (*p == command_symbol) { // check first char. + char command[101]; + int i = 0; + memset(info, 0, sizeof(AtCommandInfo)); + sscanf(p, "%100s", command); + command[sizeof(command)-1] = '\0'; + + while (atcommand_info[i].type != AtCommand_Unknown) { + if (strcmpi(command+1, atcommand_info[i].command+1) == 0 && level >= atcommand_info[i].level) { + p[0] = atcommand_info[i].command[0]; // set correct first symbol for after. + break; + } + i++; + } + + if (atcommand_info[i].type == AtCommand_Unknown) { + // doesn't return Unknown if player is normal player (display the text, not display: unknown command) + if (level == 0) + return AtCommand_None; + else + return AtCommand_Unknown; + } + memcpy(info, &atcommand_info[i], sizeof atcommand_info[i]); + } else { + return AtCommand_None; + } + + return info->type; +} + +/*========================================== + * + *------------------------------------------ + */ +static int atkillmonster_sub(struct block_list *bl, va_list ap) { + int flag = va_arg(ap, int); + + nullpo_retr(0, bl); + + if (flag) + mob_damage(NULL, (struct mob_data *)bl, ((struct mob_data *)bl)->hp, 2); + else + mob_delete((struct mob_data *)bl); + + return 0; +} + +#ifndef TXT_ONLY +static int atkillnpc_sub(struct block_list *bl, va_list ap) +{ + int flag = va_arg(ap,int); + + nullpo_retr(0, bl); + + npc_delete((struct npc_data *)bl); + + flag = 0; + + return 0; +} + +void rehash( const int fd, struct map_session_data* sd ) +{ + int map_id = 0; + + int LOADED_MAPS = map_num; + + for (map_id = 0; map_id < LOADED_MAPS;map_id++) { + + if (map_id > LOADED_MAPS) + break; + + map_foreachinarea(atkillmonster_sub, map_id, 0, 0, map[map_id].xs, map[map_id].ys, BL_MOB, 0); + map_foreachinarea(atkillnpc_sub, map_id, 0, 0, map[map_id].xs, map[map_id].ys, BL_NPC, 0); + } +} + +#endif /* not TXT_ONLY */ +/*========================================== + * Read Message Data + *------------------------------------------ + */ +int msg_config_read(const char *cfgName) { + int msg_number; + char line[1024], w1[1024], w2[1024]; + FILE *fp; + + if ((fp = fopen(cfgName, "r")) == NULL) { + printf("Messages file not found: %s\n", cfgName); + return 1; + } + + while(fgets(line, sizeof(line)-1, fp)) { + if (line[0] == '/' && line[1] == '/') + continue; + if (sscanf(line, "%[^:]: %[^\r\n]", w1, w2) == 2) { + if (strcmpi(w1, "import") == 0) { + msg_config_read(w2); + } else { + msg_number = atoi(w1); + if (msg_number >= 0 && msg_number < (int)(sizeof(msg_table) / sizeof(msg_table[0]))) + strcpy(msg_table[msg_number], w2); + // printf("message #%d: '%s'.\n", msg_number, msg_table[msg_number]); + } + } + } + fclose(fp); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +static AtCommandInfo* get_atcommandinfo_byname(const char* name) { + int i; + + for (i = 0; atcommand_info[i].type != AtCommand_Unknown; i++) + if (strcmpi(atcommand_info[i].command + 1, name) == 0) + return &atcommand_info[i]; + + return NULL; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_config_read(const char *cfgName) { + char line[1024], w1[1024], w2[1024]; + AtCommandInfo* p; + FILE* fp; + + if ((fp = fopen(cfgName, "r")) == NULL) { + printf("At commands configuration file not found: %s\n", cfgName); + return 1; + } + + while (fgets(line, sizeof(line)-1, fp)) { + if (line[0] == '/' && line[1] == '/') + continue; + + if (sscanf(line, "%1023[^:]:%1023s", w1, w2) != 2) + continue; + p = get_atcommandinfo_byname(w1); + if (p != NULL) { + p->level = atoi(w2); + if (p->level > 100) + p->level = 100; + else if (p->level < 0) + p->level = 0; + } + + if (strcmpi(w1, "import") == 0) + atcommand_config_read(w2); + else if (strcmpi(w1, "command_symbol") == 0 && w2[0] > 31 && + w2[0] != '/' && // symbol of standard ragnarok GM commands + w2[0] != '%') // symbol of party chat speaking + command_symbol = w2[0]; + } + fclose(fp); + + return 0; +} + +/*========================================== +// @ command processing functions + *------------------------------------------ + */ + +/*========================================== + * @rura+ + *------------------------------------------ + */ +int atcommand_rurap( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char map_name[100]; + char character[100]; + int x = 0, y = 0; + struct map_session_data *pl_sd; + int m; + + memset(map_name, '\0', sizeof(map_name)); + memset(character, '\0', sizeof(character)); + + if (!message || !*message || sscanf(message, "%99s %d %d %99[^\n]", map_name, &x, &y, character) < 4) { + clif_displaymessage(fd, "Usage: @charwarp/@rura+ <mapname> <x> <y> <char name>"); + return -1; + } + + if (x <= 0) + x = rand() % 399 + 1; + if (y <= 0) + y = rand() % 399 + 1; + if (strstr(map_name, ".gat") == NULL && strstr(map_name, ".afm") == NULL && strlen(map_name) < 13) // 16 - 4 (.gat) + strcat(map_name, ".gat"); + + if ((pl_sd = map_nick2sd(character)) != NULL) { + if (pc_isGM(sd) >= pc_isGM(pl_sd)) { // you can rura+ only lower or same GM level + if (x > 0 && x < 400 && y > 0 && y < 400) { + m = map_mapname2mapid(map_name); + if (m >= 0 && map[m].flag.nowarpto && battle_config.any_warp_GM_min_level > pc_isGM(sd)) { + clif_displaymessage(fd, "You are not authorised to warp someone to this map."); + return -1; + } + if (pl_sd->bl.m >= 0 && map[pl_sd->bl.m].flag.nowarp && battle_config.any_warp_GM_min_level > pc_isGM(sd)) { + clif_displaymessage(fd, "You are not authorised to warp this player from its actual map."); + return -1; + } + if (pc_setpos(pl_sd, map_name, x, y, 3) == 0) { + clif_displaymessage(pl_sd->fd, msg_table[0]); // Warped. + clif_displaymessage(fd, msg_table[15]); // Player warped (message sends to player too). + } else { + clif_displaymessage(fd, msg_table[1]); // Map not found. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[2]); // Coordinates out of range. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[81]); // Your GM level don't authorise you to do this action on this player. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[3]); // Character not found. + return -1; + } + + return 0; +} + +// @rura +/*========================================== + * + *------------------------------------------ + */ +int atcommand_rura( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char map_name[100]; + int x = 0, y = 0; + int m; + + memset(map_name, '\0', sizeof(map_name)); + + if (!message || !*message || sscanf(message, "%99s %d %d", map_name, &x, &y) < 1) { + clif_displaymessage(fd, "Please, enter a map (usage: @warp/@rura/@mapmove <mapname> <x> <y>)."); + return -1; + } + + if (x <= 0) + x = rand() % 399 + 1; + if (y <= 0) + y = rand() % 399 + 1; + + if (strstr(map_name, ".gat") == NULL && strstr(map_name, ".afm") == NULL && strlen(map_name) < 13) // 16 - 4 (.gat) + strcat(map_name, ".gat"); + + if (x > 0 && x < 400 && y > 0 && y < 400) { + m = map_mapname2mapid(map_name); + if (m >= 0 && map[m].flag.nowarpto && battle_config.any_warp_GM_min_level > pc_isGM(sd)) { + clif_displaymessage(fd, "You are not authorised to warp you to this map."); + return -1; + } + if (sd->bl.m >= 0 && map[sd->bl.m].flag.nowarp && battle_config.any_warp_GM_min_level > pc_isGM(sd)) { + clif_displaymessage(fd, "You are not authorised to warp you from your actual map."); + return -1; + } + if (pc_setpos(sd, map_name, x, y, 3) == 0) + clif_displaymessage(fd, msg_table[0]); // Warped. + else { + clif_displaymessage(fd, msg_table[1]); // Map not found. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[2]); // Coordinates out of range. + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_where( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char character[100]; + char output[200]; + struct map_session_data *pl_sd; + + memset(character, '\0', sizeof(character)); + memset(output, '\0', sizeof(output)); + + if (sscanf(message, "%99[^\n]", character) < 1) + strcpy(character, sd->status.name); + + if ((pl_sd = map_nick2sd(character)) != NULL && + !((battle_config.hide_GM_session || (pl_sd->status.option & OPTION_HIDE)) && (pc_isGM(pl_sd) > pc_isGM(sd)))) { // you can look only lower or same level + sprintf(output, "%s: %s (%d,%d)", pl_sd->status.name, pl_sd->mapname, pl_sd->bl.x, pl_sd->bl.y); + clif_displaymessage(fd, output); + } else { + clif_displaymessage(fd, msg_table[3]); // Character not found. + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_jumpto( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char character[100]; + char output[200]; + struct map_session_data *pl_sd; + + memset(character, '\0', sizeof(character)); + memset(output, '\0', sizeof(output)); + + if (!message || !*message || sscanf(message, "%99[^\n]", character) < 1) { + clif_displaymessage(fd, "Please, enter a player name (usage: @jumpto/@warpto/@goto <char name>)."); + return -1; + } + + if ((pl_sd = map_nick2sd(character)) != NULL) { + if (pl_sd->bl.m >= 0 && map[pl_sd->bl.m].flag.nowarpto && battle_config.any_warp_GM_min_level > pc_isGM(sd)) { + clif_displaymessage(fd, "You are not authorised to warp you to the map of this player."); + return -1; + } + if (sd->bl.m >= 0 && map[sd->bl.m].flag.nowarp && battle_config.any_warp_GM_min_level > pc_isGM(sd)) { + clif_displaymessage(fd, "You are not authorised to warp you from your actual map."); + return -1; + } + pc_setpos(sd, pl_sd->mapname, pl_sd->bl.x, pl_sd->bl.y, 3); + sprintf(output, msg_table[4], character); // Jump to %s + clif_displaymessage(fd, output); + } else { + clif_displaymessage(fd, msg_table[3]); // Character not found. + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_jump( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char output[200]; + int x = 0, y = 0; + + memset(output, '\0', sizeof(output)); + + sscanf(message, "%d %d", &x, &y); + + if (x <= 0) + x = rand() % 399 + 1; + if (y <= 0) + y = rand() % 399 + 1; + if (x > 0 && x < 400 && y > 0 && y < 400) { + if (sd->bl.m >= 0 && map[sd->bl.m].flag.nowarpto && battle_config.any_warp_GM_min_level > pc_isGM(sd)) { + clif_displaymessage(fd, "You are not authorised to warp you to your actual map."); + return -1; + } + if (sd->bl.m >= 0 && map[sd->bl.m].flag.nowarp && battle_config.any_warp_GM_min_level > pc_isGM(sd)) { + clif_displaymessage(fd, "You are not authorised to warp you from your actual map."); + return -1; + } + pc_setpos(sd, sd->mapname, x, y, 3); + sprintf(output, msg_table[5], x, y); // Jump to %d %d + clif_displaymessage(fd, output); + } else { + clif_displaymessage(fd, msg_table[2]); // Coordinates out of range. + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_who( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char output[200]; + struct map_session_data *pl_sd; + int i, j, count; + int pl_GM_level, GM_level; + char match_text[100]; + char player_name[24]; + + memset(output, '\0', sizeof(output)); + memset(match_text, '\0', sizeof(match_text)); + memset(player_name, '\0', sizeof(player_name)); + + if (sscanf(message, "%99[^\n]", match_text) < 1) + strcpy(match_text, ""); + for (j = 0; match_text[j]; j++) + match_text[j] = tolower(match_text[j]); + + count = 0; + GM_level = pc_isGM(sd); + for (i = 0; i < fd_max; i++) { + if (session[i] && (pl_sd = session[i]->session_data) && pl_sd->state.auth) { + pl_GM_level = pc_isGM(pl_sd); + if (!((battle_config.hide_GM_session || (pl_sd->status.option & OPTION_HIDE)) && (pl_GM_level > GM_level))) { // you can look only lower or same level + memcpy(player_name, pl_sd->status.name, 24); + for (j = 0; player_name[j]; j++) + player_name[j] = tolower(player_name[j]); + if (strstr(player_name, match_text) != NULL) { // search with no case sensitive + if (pl_GM_level > 0) + sprintf(output, "Name: %s (GM:%d) | Location: %s %d %d", pl_sd->status.name, pl_GM_level, pl_sd->mapname, pl_sd->bl.x, pl_sd->bl.y); + else + sprintf(output, "Name: %s | Location: %s %d %d", pl_sd->status.name, pl_sd->mapname, pl_sd->bl.x, pl_sd->bl.y); + clif_displaymessage(fd, output); + count++; + } + } + } + } + + if (count == 0) + clif_displaymessage(fd, msg_table[28]); // No player found. + else if (count == 1) + clif_displaymessage(fd, msg_table[29]); // 1 player found. + else { + sprintf(output, msg_table[30], count); // %d players found. + clif_displaymessage(fd, output); + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_who2( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char output[200]; + struct map_session_data *pl_sd; + int i, j, count; + int pl_GM_level, GM_level; + char match_text[100]; + char player_name[24]; + + memset(output, '\0', sizeof(output)); + memset(match_text, '\0', sizeof(match_text)); + memset(player_name, '\0', sizeof(player_name)); + + if (sscanf(message, "%99[^\n]", match_text) < 1) + strcpy(match_text, ""); + for (j = 0; match_text[j]; j++) + match_text[j] = tolower(match_text[j]); + + count = 0; + GM_level = pc_isGM(sd); + for (i = 0; i < fd_max; i++) { + if (session[i] && (pl_sd = session[i]->session_data) && pl_sd->state.auth) { + pl_GM_level = pc_isGM(pl_sd); + if (!((battle_config.hide_GM_session || (pl_sd->status.option & OPTION_HIDE)) && (pl_GM_level > GM_level))) { // you can look only lower or same level + memcpy(player_name, pl_sd->status.name, 24); + for (j = 0; player_name[j]; j++) + player_name[j] = tolower(player_name[j]); + if (strstr(player_name, match_text) != NULL) { // search with no case sensitive + if (pl_GM_level > 0) + sprintf(output, "Name: %s (GM:%d) | BLvl: %d | Job: %s (Lvl: %d)", pl_sd->status.name, pl_GM_level, pl_sd->status.base_level, job_name(pl_sd->status.class), pl_sd->status.job_level); + else + sprintf(output, "Name: %s | BLvl: %d | Job: %s (Lvl: %d)", pl_sd->status.name, pl_sd->status.base_level, job_name(pl_sd->status.class), pl_sd->status.job_level); + clif_displaymessage(fd, output); + count++; + } + } + } + } + + if (count == 0) + clif_displaymessage(fd, msg_table[28]); // No player found. + else if (count == 1) + clif_displaymessage(fd, msg_table[29]); // 1 player found. + else { + sprintf(output, msg_table[30], count); // %d players found. + clif_displaymessage(fd, output); + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_who3( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char temp0[100]; + char temp1[100]; + char output[200]; + struct map_session_data *pl_sd; + int i, j, count; + int pl_GM_level, GM_level; + char match_text[100]; + char player_name[24]; + struct guild *g; + struct party *p; + + memset(temp0, '\0', sizeof(temp0)); + memset(temp1, '\0', sizeof(temp1)); + memset(output, '\0', sizeof(output)); + memset(match_text, '\0', sizeof(match_text)); + memset(player_name, '\0', sizeof(player_name)); + + if (sscanf(message, "%99[^\n]", match_text) < 1) + strcpy(match_text, ""); + for (j = 0; match_text[j]; j++) + match_text[j] = tolower(match_text[j]); + + count = 0; + GM_level = pc_isGM(sd); + for (i = 0; i < fd_max; i++) { + if (session[i] && (pl_sd = session[i]->session_data) && pl_sd->state.auth) { + pl_GM_level = pc_isGM(pl_sd); + if (!((battle_config.hide_GM_session || (pl_sd->status.option & OPTION_HIDE)) && (pl_GM_level > GM_level))) { // you can look only lower or same level + memcpy(player_name, pl_sd->status.name, 24); + for (j = 0; player_name[j]; j++) + player_name[j] = tolower(player_name[j]); + if (strstr(player_name, match_text) != NULL) { // search with no case sensitive + g = guild_search(pl_sd->status.guild_id); + if (g == NULL) + sprintf(temp1, "None"); + else + sprintf(temp1, "%s", g->name); + p = party_search(pl_sd->status.party_id); + if (p == NULL) + sprintf(temp0, "None"); + else + sprintf(temp0, "%s", p->name); + if (pl_GM_level > 0) + sprintf(output, "Name: %s (GM:%d) | Party: '%s' | Guild: '%s'", pl_sd->status.name, pl_GM_level, temp0, temp1); + else + sprintf(output, "Name: %s | Party: '%s' | Guild: '%s'", pl_sd->status.name, temp0, temp1); + clif_displaymessage(fd, output); + count++; + } + } + } + } + + if (count == 0) + clif_displaymessage(fd, msg_table[28]); // No player found. + else if (count == 1) + clif_displaymessage(fd, msg_table[29]); // 1 player found. + else { + sprintf(output, msg_table[30], count); // %d players found. + clif_displaymessage(fd, output); + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_whomap( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char output[200]; + struct map_session_data *pl_sd; + int i, count; + int pl_GM_level, GM_level; + int map_id; + char map_name[100]; + + memset(output, '\0', sizeof(output)); + memset(map_name, '\0', sizeof(map_name)); + + if (!message || !*message) + map_id = sd->bl.m; + else { + sscanf(message, "%99s", map_name); + if (strstr(map_name, ".gat") == NULL && strstr(map_name, ".afm") == NULL && strlen(map_name) < 13) // 16 - 4 (.gat) + strcat(map_name, ".gat"); + if ((map_id = map_mapname2mapid(map_name)) < 0) + map_id = sd->bl.m; + } + + count = 0; + GM_level = pc_isGM(sd); + for (i = 0; i < fd_max; i++) { + if (session[i] && (pl_sd = session[i]->session_data) && pl_sd->state.auth) { + pl_GM_level = pc_isGM(pl_sd); + if (!((battle_config.hide_GM_session || (pl_sd->status.option & OPTION_HIDE)) && (pl_GM_level > GM_level))) { // you can look only lower or same level + if (pl_sd->bl.m == map_id) { + if (pl_GM_level > 0) + sprintf(output, "Name: %s (GM:%d) | Location: %s %d %d", pl_sd->status.name, pl_GM_level, pl_sd->mapname, pl_sd->bl.x, pl_sd->bl.y); + else + sprintf(output, "Name: %s | Location: %s %d %d", pl_sd->status.name, pl_sd->mapname, pl_sd->bl.x, pl_sd->bl.y); + clif_displaymessage(fd, output); + count++; + } + } + } + } + + if (count == 0) + sprintf(output, msg_table[54], map[map_id].name); // No player found in map '%s'. + else if (count == 1) + sprintf(output, msg_table[55], map[map_id].name); // 1 player found in map '%s'. + else { + sprintf(output, msg_table[56], count, map[map_id].name); // %d players found in map '%s'. + } + clif_displaymessage(fd, output); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_whomap2( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char output[200]; + struct map_session_data *pl_sd; + int i, count; + int pl_GM_level, GM_level; + int map_id = 0; + char map_name[100]; + + memset(output, '\0', sizeof(output)); + memset(map_name, '\0', sizeof(map_name)); + + if (!message || !*message) + map_id = sd->bl.m; + else { + sscanf(message, "%99s", map_name); + if (strstr(map_name, ".gat") == NULL && strstr(map_name, ".afm") == NULL && strlen(map_name) < 13) // 16 - 4 (.gat) + strcat(map_name, ".gat"); + if ((map_id = map_mapname2mapid(map_name)) < 0) + map_id = sd->bl.m; + } + + count = 0; + GM_level = pc_isGM(sd); + for (i = 0; i < fd_max; i++) { + if (session[i] && (pl_sd = session[i]->session_data) && pl_sd->state.auth) { + pl_GM_level = pc_isGM(pl_sd); + if (!((battle_config.hide_GM_session || (pl_sd->status.option & OPTION_HIDE)) && (pl_GM_level > GM_level))) { // you can look only lower or same level + if (pl_sd->bl.m == map_id) { + if (pl_GM_level > 0) + sprintf(output, "Name: %s (GM:%d) | BLvl: %d | Job: %s (Lvl: %d)", pl_sd->status.name, pl_GM_level, pl_sd->status.base_level, job_name(pl_sd->status.class), pl_sd->status.job_level); + else + sprintf(output, "Name: %s | BLvl: %d | Job: %s (Lvl: %d)", pl_sd->status.name, pl_sd->status.base_level, job_name(pl_sd->status.class), pl_sd->status.job_level); + clif_displaymessage(fd, output); + count++; + } + } + } + } + + if (count == 0) + sprintf(output, msg_table[54], map[map_id].name); // No player found in map '%s'. + else if (count == 1) + sprintf(output, msg_table[55], map[map_id].name); // 1 player found in map '%s'. + else { + sprintf(output, msg_table[56], count, map[map_id].name); // %d players found in map '%s'. + } + clif_displaymessage(fd, output); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_whomap3( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char temp0[100]; + char temp1[100]; + char output[200]; + struct map_session_data *pl_sd; + int i, count; + int pl_GM_level, GM_level; + int map_id = 0; + char map_name[100]; + struct guild *g; + struct party *p; + + memset(temp0, '\0', sizeof(temp0)); + memset(temp1, '\0', sizeof(temp1)); + memset(output, '\0', sizeof(output)); + memset(map_name, '\0', sizeof(map_name)); + + if (!message || !*message) + map_id = sd->bl.m; + else { + sscanf(message, "%99s", map_name); + if (strstr(map_name, ".gat") == NULL && strstr(map_name, ".afm") == NULL && strlen(map_name) < 13) // 16 - 4 (.gat) + strcat(map_name, ".gat"); + if ((map_id = map_mapname2mapid(map_name)) < 0) + map_id = sd->bl.m; + } + + count = 0; + GM_level = pc_isGM(sd); + for (i = 0; i < fd_max; i++) { + if (session[i] && (pl_sd = session[i]->session_data) && pl_sd->state.auth) { + pl_GM_level = pc_isGM(pl_sd); + if (!((battle_config.hide_GM_session || (pl_sd->status.option & OPTION_HIDE)) && (pl_GM_level > GM_level))) { // you can look only lower or same level + if (pl_sd->bl.m == map_id) { + g = guild_search(pl_sd->status.guild_id); + if (g == NULL) + sprintf(temp1, "None"); + else + sprintf(temp1, "%s", g->name); + p = party_search(pl_sd->status.party_id); + if (p == NULL) + sprintf(temp0, "None"); + else + sprintf(temp0, "%s", p->name); + if (pl_GM_level > 0) + sprintf(output, "Name: %s (GM:%d) | Party: '%s' | Guild: '%s'", pl_sd->status.name, pl_GM_level, temp0, temp1); + else + sprintf(output, "Name: %s | Party: '%s' | Guild: '%s'", pl_sd->status.name, temp0, temp1); + clif_displaymessage(fd, output); + count++; + } + } + } + } + + if (count == 0) + sprintf(output, msg_table[54], map[map_id].name); // No player found in map '%s'. + else if (count == 1) + sprintf(output, msg_table[55], map[map_id].name); // 1 player found in map '%s'. + else { + sprintf(output, msg_table[56], count, map[map_id].name); // %d players found in map '%s'. + } + clif_displaymessage(fd, output); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_whogm( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char temp0[100]; + char temp1[100]; + char output[200]; + struct map_session_data *pl_sd; + int i, j, count; + int pl_GM_level, GM_level; + char match_text[100]; + char player_name[24]; + struct guild *g; + struct party *p; + + memset(temp0, '\0', sizeof(temp0)); + memset(temp1, '\0', sizeof(temp1)); + memset(output, '\0', sizeof(output)); + memset(match_text, '\0', sizeof(match_text)); + memset(player_name, '\0', sizeof(player_name)); + + if (sscanf(message, "%99[^\n]", match_text) < 1) + strcpy(match_text, ""); + for (j = 0; match_text[j]; j++) + match_text[j] = tolower(match_text[j]); + + count = 0; + GM_level = pc_isGM(sd); + for (i = 0; i < fd_max; i++) { + if (session[i] && (pl_sd = session[i]->session_data) && pl_sd->state.auth) { + pl_GM_level = pc_isGM(pl_sd); + if (pl_GM_level > 0) { + if (!((battle_config.hide_GM_session || (pl_sd->status.option & OPTION_HIDE)) && (pl_GM_level > GM_level))) { // you can look only lower or same level + memcpy(player_name, pl_sd->status.name, 24); + for (j = 0; player_name[j]; j++) + player_name[j] = tolower(player_name[j]); + if (strstr(player_name, match_text) != NULL) { // search with no case sensitive + sprintf(output, "Name: %s (GM:%d) | Location: %s %d %d", pl_sd->status.name, pl_GM_level, pl_sd->mapname, pl_sd->bl.x, pl_sd->bl.y); + clif_displaymessage(fd, output); + sprintf(output, " BLvl: %d | Job: %s (Lvl: %d)", pl_sd->status.base_level, job_name(pl_sd->status.class), pl_sd->status.job_level); + clif_displaymessage(fd, output); + g = guild_search(pl_sd->status.guild_id); + if (g == NULL) + sprintf(temp1, "None"); + else + sprintf(temp1, "%s", g->name); + p = party_search(pl_sd->status.party_id); + if (p == NULL) + sprintf(temp0, "None"); + else + sprintf(temp0, "%s", p->name); + sprintf(output, " Party: '%s' | Guild: '%s'", temp0, temp1); + clif_displaymessage(fd, output); + count++; + } + } + } + } + } + + if (count == 0) + clif_displaymessage(fd, msg_table[150]); // No GM found. + else if (count == 1) + clif_displaymessage(fd, msg_table[151]); // 1 GM found. + else { + sprintf(output, msg_table[152], count); // %d GMs found. + clif_displaymessage(fd, output); + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_save( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + pc_setsavepoint(sd, sd->mapname, sd->bl.x, sd->bl.y); + if (sd->status.pet_id > 0 && sd->pd) + intif_save_petdata(sd->status.account_id, &sd->pet); + pc_makesavestatus(sd); + chrif_save(sd); + storage_storage_save(sd); + clif_displaymessage(fd, msg_table[6]); // Character data respawn point saved. + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_load( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + int m; + + m = map_mapname2mapid(sd->status.save_point.map); + if (m >= 0 && map[m].flag.nowarpto && battle_config.any_warp_GM_min_level > pc_isGM(sd)) { + clif_displaymessage(fd, "You are not authorised to warp you to your save map."); + return -1; + } + if (sd->bl.m >= 0 && map[sd->bl.m].flag.nowarp && battle_config.any_warp_GM_min_level > pc_isGM(sd)) { + clif_displaymessage(fd, "You are not authorised to warp you from your actual map."); + return -1; + } + + pc_setpos(sd, sd->status.save_point.map, sd->status.save_point.x, sd->status.save_point.y, 0); + clif_displaymessage(fd, msg_table[7]); // Warping to respawn point. + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_speed( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char output[200]; + int speed; + + memset(output, '\0', sizeof(output)); + + if (!message || !*message) { + sprintf(output, "Please, enter a speed value (usage: @speed <%d-%d>).", MIN_WALK_SPEED, MAX_WALK_SPEED); + clif_displaymessage(fd, output); + return -1; + } + + speed = atoi(message); + if (speed >= MIN_WALK_SPEED && speed <= MAX_WALK_SPEED) { + sd->speed = speed; + //sd->walktimer = x; + //この文を追加 by れ + clif_updatestatus(sd, SP_SPEED); + clif_displaymessage(fd, msg_table[8]); // Speed changed. + } else { + sprintf(output, "Please, enter a valid speed value (usage: @speed <%d-%d>).", MIN_WALK_SPEED, MAX_WALK_SPEED); + clif_displaymessage(fd, output); + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_storage( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + storage_storageopen(sd); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_guildstorage( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + if (sd->status.guild_id > 0) + storage_guild_storageopen(sd); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_option( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + int param1 = 0, param2 = 0, param3 = 0; + + if (!message || !*message || sscanf(message, "%d %d %d", ¶m1, ¶m2, ¶m3) < 1 || param1 < 0 || param2 < 0 || param3 < 0) { + clif_displaymessage(fd, "Please, enter at least a option (usage: @option <param1:0+> <param2:0+> <param3:0+>)."); + return -1; + } + + sd->opt1 = param1; + sd->opt2 = param2; + if (!(sd->status.option & CART_MASK) && param3 & CART_MASK) { + clif_cart_itemlist(sd); + clif_cart_equiplist(sd); + clif_updatestatus(sd, SP_CARTINFO); + } + sd->status.option = param3; + // fix pecopeco display + if (sd->status.class == 13 || sd->status.class == 21 || sd->status.class == 4014 || sd->status.class == 4022) { + if (!pc_isriding(sd)) { // sd have the new value... + if (sd->status.class == 13) + sd->status.class = sd->view_class = 7; + else if (sd->status.class == 21) + sd->status.class = sd->view_class = 14; + else if (sd->status.class == 4014) + sd->status.class = sd->view_class = 4008; + else if (sd->status.class == 4022) + sd->status.class = sd->view_class = 4015; + } + } else { + if (pc_isriding(sd)) { // sd have the new value... + if (sd->disguise > 0) { // temporary prevention of crash caused by peco + disguise, will look into a better solution [Valaris] (code added by [Yor]) + sd->status.option &= ~0x0020; + } else { + if (sd->status.class == 7) + sd->status.class = sd->view_class = 13; + else if (sd->status.class == 14) + sd->status.class = sd->view_class = 21; + else if (sd->status.class == 4008) + sd->status.class = sd->view_class = 4014; + else if (sd->status.class == 4015) + sd->status.class = sd->view_class = 4022; + else + sd->status.option &= ~0x0020; + } + } + } + + clif_changeoption(&sd->bl); + pc_calcstatus(sd, 0); + clif_displaymessage(fd, msg_table[9]); // Options changed. + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_hide( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + if (sd->status.option & OPTION_HIDE) { + sd->status.option &= ~OPTION_HIDE; + clif_displaymessage(fd, msg_table[10]); // Invisible: Off + } else { + sd->status.option |= OPTION_HIDE; + clif_displaymessage(fd, msg_table[11]); // Invisible: On + } + clif_changeoption(&sd->bl); + + return 0; +} + +/*========================================== + * 転職する upperを指定すると転生や養子にもなれる + *------------------------------------------ + */ +int atcommand_jobchange( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + int job = 0, upper = -1; + + if (!message || !*message || sscanf(message, "%d %d", &job, &upper) < 1) { + clif_displaymessage(fd, "Please, enter job ID (usage: @job/@jobchange <job ID>)."); + return -1; + } + + if (job == 37 ||job == 45) + return 0; + + if ((job >= 0 && job < MAX_PC_CLASS)) { + + // fix pecopeco display + if ((job != 13 && job != 21 && job != 4014 && job != 4022)) { + if (pc_isriding(sd)) { + if (sd->status.class == 13) + sd->status.class = sd->view_class = 7; + if (sd->status.class == 21) + sd->status.class = sd->view_class = 14; + if (sd->status.class == 4014) + sd->status.class = sd->view_class = 4008; + if (sd->status.class == 4022) + sd->status.class = sd->view_class = 4015; + sd->status.option &= ~0x0020; + clif_changeoption(&sd->bl); + pc_calcstatus(sd, 0); + } + } else { + if (!pc_isriding(sd)) { + if (job == 13) + job = 7; + if (job == 21) + job = 14; + if (job == 4014) + job = 4008; + if (job == 4022) + job = 4015; + } + } + + if (pc_jobchange(sd, job, upper) == 0) + clif_displaymessage(fd, msg_table[12]); // Your job has been changed. + else { + clif_displaymessage(fd, msg_table[155]); // Impossible to change your job. + return -1; + } + } else { + clif_displaymessage(fd, "Please, enter a valid job ID (usage: @job/@jobchange <job ID>)."); + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_die( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + pc_damage(NULL, sd, sd->status.hp + 1); + clif_displaymessage(fd, msg_table[13]); // A pity! You've died. + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_kill( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char character[100]; + struct map_session_data *pl_sd; + + memset(character, '\0', sizeof(character)); + + if (!message || !*message || sscanf(message, "%99[^\n]", character) < 1) { + clif_displaymessage(fd, "Please, enter a player name (usage: @kill <char name>)."); + return -1; + } + + if ((pl_sd = map_nick2sd(character)) != NULL) { + if (pc_isGM(sd) >= pc_isGM(pl_sd)) { // you can kill only lower or same level + pc_damage(NULL, pl_sd, pl_sd->status.hp + 1); + clif_displaymessage(fd, msg_table[14]); // Character killed. + } else { + clif_displaymessage(fd, msg_table[81]); // Your GM level don't authorise you to do this action on this player. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[3]); // Character not found. + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_alive( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + sd->status.hp = sd->status.max_hp; + sd->status.sp = sd->status.max_sp; + pc_setstand(sd); + if (battle_config.pc_invincible_time > 0) + pc_setinvincibletimer(sd, battle_config.pc_invincible_time); + clif_updatestatus(sd, SP_HP); + clif_updatestatus(sd, SP_SP); + clif_resurrection(&sd->bl, 1); + clif_displaymessage(fd, msg_table[16]); // You've been revived! It's a miracle! + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_kami( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char output[200]; + + memset(output, '\0', sizeof(output)); + + if (!message || !*message) { + clif_displaymessage(fd, "Please, enter a message (usage: @kami <message>)."); + return -1; + } + + sscanf(message, "%199[^\n]", output); + intif_GMmessage(output, strlen(output) + 1, (*(command + 5) == 'b') ? 0x10 : 0); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_heal( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + int hp = 0, sp = 0; // [Valaris] thanks to fov + + sscanf(message, "%d %d", &hp, &sp); + + if (hp == 0 && sp == 0) { + hp = sd->status.max_hp - sd->status.hp; + sp = sd->status.max_sp - sd->status.sp; + } else { + if (hp > 0 && (hp > sd->status.max_hp || hp > (sd->status.max_hp - sd->status.hp))) // fix positiv overflow + hp = sd->status.max_hp - sd->status.hp; + else if (hp < 0 && (hp < -sd->status.max_hp || hp < (1 - sd->status.hp))) // fix negativ overflow + hp = 1 - sd->status.hp; + if (sp > 0 && (sp > sd->status.max_sp || sp > (sd->status.max_sp - sd->status.sp))) // fix positiv overflow + sp = sd->status.max_sp - sd->status.sp; + else if (sp < 0 && (sp < -sd->status.max_sp || sp < (1 - sd->status.sp))) // fix negativ overflow + sp = 1 - sd->status.sp; + } + + if (hp > 0) // display like heal + clif_heal(fd, SP_HP, hp); + else if (hp < 0) // display like damage + clif_damage(&sd->bl,&sd->bl, gettick(), 0, 0, -hp, 0 , 4, 0); + if (sp > 0) // no display when we lost SP + clif_heal(fd, SP_SP, sp); + + if (hp != 0 || sp != 0) { + pc_heal(sd, hp, sp); + if (hp >= 0 && sp >= 0) + clif_displaymessage(fd, msg_table[17]); // HP, SP recovered. + else + clif_displaymessage(fd, msg_table[156]); // HP or/and SP modified. + } else { + clif_displaymessage(fd, msg_table[157]); // HP and SP are already with the good value. + return -1; + } + + return 0; +} + +/*========================================== + * @item command (usage: @item <name/id_of_item> <quantity>) (modified by [Yor] for pet_egg) + *------------------------------------------ + */ +int atcommand_item( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char item_name[100]; + int number = 0, item_id, flag; + struct item item_tmp; + struct item_data *item_data; + int get_count, i, pet_id; + + memset(item_name, '\0', sizeof(item_name)); + + if (!message || !*message || sscanf(message, "%99s %d", item_name, &number) < 1) { + clif_displaymessage(fd, "Please, enter an item name/id (usage: @item <item name or ID> [quantity])."); + return -1; + } + + if (number <= 0) + number = 1; + + item_id = 0; + if ((item_data = itemdb_searchname(item_name)) != NULL || + (item_data = itemdb_exists(atoi(item_name))) != NULL) + item_id = item_data->nameid; + + if (item_id >= 500) { + get_count = number; + // check pet egg + pet_id = search_petDB_index(item_id, PET_EGG); + if (item_data->type == 4 || item_data->type == 5 || + item_data->type == 7 || item_data->type == 8) { + get_count = 1; + } + for (i = 0; i < number; i += get_count) { + // if pet egg + if (pet_id >= 0) { + sd->catch_target_class = pet_db[pet_id].class; + intif_create_pet(sd->status.account_id, sd->status.char_id, + pet_db[pet_id].class, mob_db[pet_db[pet_id].class].lv, + pet_db[pet_id].EggID, 0, pet_db[pet_id].intimate, + 100, 0, 1, pet_db[pet_id].jname); + // if not pet egg + } else { + memset(&item_tmp, 0, sizeof(item_tmp)); + item_tmp.nameid = item_id; + item_tmp.identify = 1; + if ((flag = pc_additem((struct map_session_data*)sd, &item_tmp, get_count))) + clif_additem((struct map_session_data*)sd, 0, 0, flag); + } + } + clif_displaymessage(fd, msg_table[18]); // Item created. + } else { + clif_displaymessage(fd, msg_table[19]); // Invalid item ID or name. + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_item2( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + struct item item_tmp; + struct item_data *item_data; + char item_name[100]; + int item_id, number = 0; + int identify = 0, refine = 0, attr = 0; + int c1 = 0, c2 = 0, c3 = 0, c4 = 0; + int flag; + int loop, get_count, i; + + memset(item_name, '\0', sizeof(item_name)); + + if (!message || !*message || sscanf(message, "%99s %d %d %d %d %d %d %d %d", item_name, &number, &identify, &refine, &attr, &c1, &c2, &c3, &c4) < 9) { + clif_displaymessage(fd, "Please, enter all informations (usage: @item2 <item name or ID> <quantity>"); + clif_displaymessage(fd, " <Identify_flag> <refine> <attribut> <Card1> <Card2> <Card3> <Card4>)."); + return -1; + } + + if (number <= 0) + number = 1; + + item_id = 0; + if ((item_data = itemdb_searchname(item_name)) != NULL || + (item_data = itemdb_exists(atoi(item_name))) != NULL) + item_id = item_data->nameid; + + if (item_id > 500) { + loop = 1; + get_count = number; + if (item_data->type == 4 || item_data->type == 5 || + item_data->type == 7 || item_data->type == 8) { + loop = number; + get_count = 1; + if (item_data->type == 7) { + identify = 1; + refine = 0; + } + if (item_data->type == 8) + refine = 0; + if (refine > 10) + refine = 10; + } else { + identify = 1; + refine = attr = 0; + } + for (i = 0; i < loop; i++) { + memset(&item_tmp, 0, sizeof(item_tmp)); + item_tmp.nameid = item_id; + item_tmp.identify = identify; + item_tmp.refine = refine; + item_tmp.attribute = attr; + item_tmp.card[0] = c1; + item_tmp.card[1] = c2; + item_tmp.card[2] = c3; + item_tmp.card[3] = c4; + if ((flag = pc_additem(sd, &item_tmp, get_count))) + clif_additem(sd, 0, 0, flag); + } + clif_displaymessage(fd, msg_table[18]); // Item created. + } else { + clif_displaymessage(fd, msg_table[19]); // Invalid item ID or name. + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_itemreset( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + int i; + + for (i = 0; i < MAX_INVENTORY; i++) { + if (sd->status.inventory[i].amount && sd->status.inventory[i].equip == 0) + pc_delitem(sd, i, sd->status.inventory[i].amount, 0); + } + clif_displaymessage(fd, msg_table[20]); // All of your items have been removed. + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_itemcheck( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + pc_checkitem(sd); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_baselevelup( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + int level, i; + + if (!message || !*message || (level = atoi(message)) == 0) { + clif_displaymessage(fd, "Please, enter a level adjustement (usage: @lvup/@blevel/@baselvlup <number of levels>)."); + return -1; + } + + if (level > 0) { + if (sd->status.base_level == battle_config.maximum_level) { // check for max level by Valaris + clif_displaymessage(fd, msg_table[47]); // Base level can't go any higher. + return -1; + } // End Addition + if (level > battle_config.maximum_level || level > (battle_config.maximum_level - sd->status.base_level)) // fix positiv overflow + level = battle_config.maximum_level - sd->status.base_level; + for (i = 1; i <= level; i++) + sd->status.status_point += (sd->status.base_level + i + 14) / 5; + sd->status.base_level += level; + clif_updatestatus(sd, SP_BASELEVEL); + clif_updatestatus(sd, SP_NEXTBASEEXP); + clif_updatestatus(sd, SP_STATUSPOINT); + pc_calcstatus(sd, 0); + pc_heal(sd, sd->status.max_hp, sd->status.max_sp); + clif_misceffect(&sd->bl, 0); + clif_displaymessage(fd, msg_table[21]); // Base level raised. + } else { + if (sd->status.base_level == 1) { + clif_displaymessage(fd, msg_table[158]); // Base level can't go any lower. + return -1; + } + if (level < -battle_config.maximum_level || level < (1 - sd->status.base_level)) // fix negativ overflow + level = 1 - sd->status.base_level; + if (sd->status.status_point > 0) { + for (i = 0; i > level; i--) + sd->status.status_point -= (sd->status.base_level + i + 14) / 5; + if (sd->status.status_point < 0) + sd->status.status_point = 0; + clif_updatestatus(sd, SP_STATUSPOINT); + } // to add: remove status points from stats + sd->status.base_level += level; + clif_updatestatus(sd, SP_BASELEVEL); + clif_updatestatus(sd, SP_NEXTBASEEXP); + pc_calcstatus(sd, 0); + clif_displaymessage(fd, msg_table[22]); // Base level lowered. + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_joblevelup( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + int up_level = 50, level; + + if (!message || !*message || (level = atoi(message)) == 0) { + clif_displaymessage(fd, "Please, enter a level adjustement (usage: @joblvup/@jlevel/@joblvlup <number of levels>)."); + return -1; + } + + if (sd->status.class == 0 || sd->status.class == 4001) + up_level -= 40; + else if ((sd->status.class > 4007 && sd->status.class < 4024) || sd->status.class == 23) + up_level += 20; + + if (level > 0) { + if (sd->status.job_level == up_level) { + clif_displaymessage(fd, msg_table[23]); // Job level can't go any higher. + return -1; + } + if (level > up_level || level > (up_level - sd->status.job_level)) // fix positiv overflow + level = up_level - sd->status.job_level; + sd->status.job_level += level; + clif_updatestatus(sd, SP_JOBLEVEL); + clif_updatestatus(sd, SP_NEXTJOBEXP); + sd->status.skill_point += level; + clif_updatestatus(sd, SP_SKILLPOINT); + pc_calcstatus(sd, 0); + clif_misceffect(&sd->bl, 1); + clif_displaymessage(fd, msg_table[24]); // Job level raised. + } else { + if (sd->status.job_level == 1) { + clif_displaymessage(fd, msg_table[159]); // Job level can't go any lower. + return -1; + } + if (level < -up_level || level < (1 - sd->status.job_level)) // fix negativ overflow + level = 1 - sd->status.job_level; + sd->status.job_level += level; + clif_updatestatus(sd, SP_JOBLEVEL); + clif_updatestatus(sd, SP_NEXTJOBEXP); + if (sd->status.skill_point > 0) { + sd->status.skill_point += level; + if (sd->status.skill_point < 0) + sd->status.skill_point = 0; + clif_updatestatus(sd, SP_SKILLPOINT); + } // to add: remove status points from skills + pc_calcstatus(sd, 0); + clif_displaymessage(fd, msg_table[25]); // Job level lowered. + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_help( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char buf[2048], w1[2048], w2[2048]; + int i, gm_level; + FILE* fp; + + memset(buf, '\0', sizeof(buf)); + + if ((fp = fopen(help_txt, "r")) != NULL) { + clif_displaymessage(fd, msg_table[26]); // Help commands: + gm_level = pc_isGM(sd); + while(fgets(buf, sizeof(buf) - 1, fp) != NULL) { + if (buf[0] == '/' && buf[1] == '/') + continue; + for (i = 0; buf[i] != '\0'; i++) { + if (buf[i] == '\r' || buf[i] == '\n') { + buf[i] = '\0'; + break; + } + } + if (sscanf(buf, "%2047[^:]:%2047[^\n]", w1, w2) < 2) + clif_displaymessage(fd, buf); + else if (gm_level >= atoi(w1)) + clif_displaymessage(fd, w2); + } + fclose(fp); + } else { + clif_displaymessage(fd, msg_table[27]); // File help.txt not found. + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_gm( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char password[100]; + + memset(password, '\0', sizeof(password)); + + if (!message || !*message || sscanf(message, "%99[^\n]", password) < 1) { + clif_displaymessage(fd, "Please, enter a password (usage: @gm <password>)."); + return -1; + } + + if (pc_isGM(sd)) { // a GM can not use this function. only a normal player (become gm is not for gm!) + clif_displaymessage(fd, msg_table[50]); // You already have some GM powers. + return -1; + } else + chrif_changegm(sd->status.account_id, password, strlen(password) + 1); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_pvpoff( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + struct map_session_data *pl_sd; + int i; + + if (battle_config.pk_mode) { //disable command if server is in PK mode [Valaris] + clif_displaymessage(fd, msg_table[52]); // This option cannot be used in PK Mode. + return -1; + } + + if (map[sd->bl.m].flag.pvp) { + map[sd->bl.m].flag.pvp = 0; + clif_send0199(sd->bl.m, 0); + for (i = 0; i < fd_max; i++) { //人数分ループ + if (session[i] && (pl_sd = session[i]->session_data) && pl_sd->state.auth) { + if (sd->bl.m == pl_sd->bl.m) { + clif_pvpset(pl_sd, 0, 0, 2); + if (pl_sd->pvp_timer != -1) { + delete_timer(pl_sd->pvp_timer, pc_calc_pvprank_timer); + pl_sd->pvp_timer = -1; + } + } + } + } + clif_displaymessage(fd, msg_table[31]); // PvP: Off. + } else { + clif_displaymessage(fd, msg_table[160]); // PvP is already Off. + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_pvpon( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + struct map_session_data *pl_sd; + int i; + + if (battle_config.pk_mode) { //disable command if server is in PK mode [Valaris] + clif_displaymessage(fd, msg_table[52]); // This option cannot be used in PK Mode. + return -1; + } + + if (!map[sd->bl.m].flag.pvp && !map[sd->bl.m].flag.nopvp) { + map[sd->bl.m].flag.pvp = 1; + clif_send0199(sd->bl.m, 1); + for (i = 0; i < fd_max; i++) { + if (session[i] && (pl_sd = session[i]->session_data) && pl_sd->state.auth) { + if (sd->bl.m == pl_sd->bl.m && pl_sd->pvp_timer == -1) { + pl_sd->pvp_timer = add_timer(gettick() + 200, + pc_calc_pvprank_timer, pl_sd->bl.id, 0); + pl_sd->pvp_rank = 0; + pl_sd->pvp_lastusers = 0; + pl_sd->pvp_point = 5; + } + } + } + clif_displaymessage(fd, msg_table[32]); // PvP: On. + } else { + clif_displaymessage(fd, msg_table[161]); // PvP is already On. + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_gvgoff( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + if (map[sd->bl.m].flag.gvg) { + map[sd->bl.m].flag.gvg = 0; + clif_send0199(sd->bl.m, 0); + clif_displaymessage(fd, msg_table[33]); // GvG: Off. + } else { + clif_displaymessage(fd, msg_table[162]); // GvG is already Off. + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_gvgon( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + if (!map[sd->bl.m].flag.gvg) { + map[sd->bl.m].flag.gvg = 1; + clif_send0199(sd->bl.m, 3); + clif_displaymessage(fd, msg_table[34]); // GvG: On. + } else { + clif_displaymessage(fd, msg_table[163]); // GvG is already On. + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_model( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + int hair_style = 0, hair_color = 0, cloth_color = 0; + char output[200]; + + memset(output, '\0', sizeof(output)); + + if (!message || !*message || sscanf(message, "%d %d %d", &hair_style, &hair_color, &cloth_color) < 1) { + sprintf(output, "Please, enter at least a value (usage: @model <hair ID: %d-%d> <hair color: %d-%d> <clothes color: %d-%d>).", + MIN_HAIR_STYLE, MAX_HAIR_STYLE, MIN_HAIR_COLOR, MAX_HAIR_COLOR, MIN_CLOTH_COLOR, MAX_CLOTH_COLOR); + clif_displaymessage(fd, output); + return -1; + } + + if (hair_style >= MIN_HAIR_STYLE && hair_style <= MAX_HAIR_STYLE && + hair_color >= MIN_HAIR_COLOR && hair_color <= MAX_HAIR_COLOR && + cloth_color >= MIN_CLOTH_COLOR && cloth_color <= MAX_CLOTH_COLOR) { + //服の色変更 + if (cloth_color != 0 && sd->status.sex == 1 && (sd->status.class == 12 || sd->status.class == 17)) { + //服の色未実装職の判定 + clif_displaymessage(fd, msg_table[35]); // You can't use this command with this class. + return -1; + } else { + pc_changelook(sd, LOOK_HAIR, hair_style); + pc_changelook(sd, LOOK_HAIR_COLOR, hair_color); + pc_changelook(sd, LOOK_CLOTHES_COLOR, cloth_color); + clif_displaymessage(fd, msg_table[36]); // Appearence changed. + } + } else { + clif_displaymessage(fd, msg_table[37]); // An invalid number was specified. + return -1; + } + + return 0; +} + +/*========================================== + * @dye && @ccolor + *------------------------------------------ + */ +int atcommand_dye(const int fd, struct map_session_data* sd, const char* command, const char* message) +{ + int cloth_color = 0; + char output[200]; + + memset(output, '\0', sizeof(output)); + + if (!message || !*message || sscanf(message, "%d", &cloth_color) < 1) { + sprintf(output, "Please, enter a clothes color (usage: @dye/@ccolor <clothes color: %d-%d>).", MIN_CLOTH_COLOR, MAX_CLOTH_COLOR); + clif_displaymessage(fd, output); + return -1; + } + + if (cloth_color >= MIN_CLOTH_COLOR && cloth_color <= MAX_CLOTH_COLOR) { + if (cloth_color != 0 && sd->status.sex == 1 && (sd->status.class == 12 || sd->status.class == 17)) { + clif_displaymessage(fd, msg_table[35]); // You can't use this command with this class. + return -1; + } else { + pc_changelook(sd, LOOK_CLOTHES_COLOR, cloth_color); + clif_displaymessage(fd, msg_table[36]); // Appearence changed. + } + } else { + clif_displaymessage(fd, msg_table[37]); // An invalid number was specified. + return -1; + } + + return 0; +} + +/*========================================== + * @chardye by [MouseJstr] + *------------------------------------------ + */ +int +atcommand_chardye(const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + return 0; +} + +/*========================================== + * @hairstyle && @hstyle + *------------------------------------------ + */ +int atcommand_hair_style(const int fd, struct map_session_data* sd, const char* command, const char* message) +{ + int hair_style = 0; + char output[200]; + + memset(output, '\0', sizeof(output)); + + if (!message || !*message || sscanf(message, "%d", &hair_style) < 1) { + sprintf(output, "Please, enter a hair style (usage: @hairstyle/@hstyle <hair ID: %d-%d>).", MIN_HAIR_STYLE, MAX_HAIR_STYLE); + clif_displaymessage(fd, output); + return -1; + } + + if (hair_style >= MIN_HAIR_STYLE && hair_style <= MAX_HAIR_STYLE) { + if (hair_style != 0 && sd->status.sex == 1 && (sd->status.class == 12 || sd->status.class == 17)) { + clif_displaymessage(fd, msg_table[35]); // You can't use this command with this class. + return -1; + } else { + pc_changelook(sd, LOOK_HAIR, hair_style); + clif_displaymessage(fd, msg_table[36]); // Appearence changed. + } + } else { + clif_displaymessage(fd, msg_table[37]); // An invalid number was specified. + return -1; + } + + return 0; +} + +/*========================================== + * @charhairstyle by [MouseJstr] + *------------------------------------------ + */ +int +atcommand_charhairstyle(const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + return 0; +} + +/*========================================== + * @haircolor && @hcolor + *------------------------------------------ + */ +int atcommand_hair_color(const int fd, struct map_session_data* sd, const char* command, const char* message) +{ + int hair_color = 0; + char output[200]; + + memset(output, '\0', sizeof(output)); + + if (!message || !*message || sscanf(message, "%d", &hair_color) < 1) { + sprintf(output, "Please, enter a hair color (usage: @haircolor/@hcolor <hair color: %d-%d>).", MIN_HAIR_COLOR, MAX_HAIR_COLOR); + clif_displaymessage(fd, output); + return -1; + } + + if (hair_color >= MIN_HAIR_COLOR && hair_color <= MAX_HAIR_COLOR) { + if (hair_color != 0 && sd->status.sex == 1 && (sd->status.class == 12 || sd->status.class == 17)) { + clif_displaymessage(fd, msg_table[35]); // You can't use this command with this class. + return -1; + } else { + pc_changelook(sd, LOOK_HAIR_COLOR, hair_color); + clif_displaymessage(fd, msg_table[36]); // Appearence changed. + } + } else { + clif_displaymessage(fd, msg_table[37]); // An invalid number was specified. + return -1; + } + + return 0; +} + +/*========================================== + * @charhaircolor by [MouseJstr] + *------------------------------------------ + */ +int +atcommand_charhaircolor(const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + return 0; +} + +/*========================================== + * @go [city_number/city_name]: improved by [yor] to add city names and help + *------------------------------------------ + */ +int atcommand_go( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + int i; + int town; + char map_name[100]; + char output[200]; + int m; + + struct { char map[16]; int x, y; } data[] = { + { "prontera.gat", 156, 191 }, // 0=Prontera + { "morocc.gat", 156, 93 }, // 1=Morroc + { "geffen.gat", 119, 59 }, // 2=Geffen + { "payon.gat", 162, 233 }, // 3=Payon + { "alberta.gat", 192, 147 }, // 4=Alberta + { "izlude.gat", 128, 114 }, // 5=Izlude + { "aldebaran.gat",140, 131 }, // 6=Al de Baran + { "xmas.gat", 147, 134 }, // 7=Lutie + { "comodo.gat", 209, 143 }, // 8=Comodo + { "yuno.gat", 157, 51 }, // 9=Yuno + { "amatsu.gat", 198, 84 }, // 10=Amatsu + { "gonryun.gat", 160, 120 }, // 11=Gon Ryun + { "umbala.gat", 89, 157 }, // 12=Umbala + { "niflheim.gat", 21, 153 }, // 13=Niflheim + { "louyang.gat", 217, 40 }, // 14=Lou Yang + { "new_1-1.gat", 53, 111 }, // 15=Start point + { "sec_pri.gat", 23, 61 }, // 16=Prison + }; + + memset(map_name, '\0', sizeof(map_name)); + memset(output, '\0', sizeof(output)); + + // get the number + town = atoi(message); + + // if no value, display all value + if (!message || !*message || sscanf(message, "%99s", map_name) < 1 || town < -3 || town >= (int)(sizeof(data) / sizeof(data[0]))) { + clif_displaymessage(fd, msg_table[38]); // Invalid location number or name. + clif_displaymessage(fd, msg_table[82]); // Please, use one of this number/name: + clif_displaymessage(fd, "-3=(Memo point 2) 4=Alberta 11=Gon Ryun"); + clif_displaymessage(fd, "-2=(Memo point 1) 5=Izlude 12=Umbala"); + clif_displaymessage(fd, "-1=(Memo point 0) 6=Al de Baran 13=Niflheim"); + clif_displaymessage(fd, " 0=Prontera 7=Lutie 14=Lou Yang"); + clif_displaymessage(fd, " 1=Morroc 8=Comodo 15=Start point"); + clif_displaymessage(fd, " 2=Geffen 9=Yuno 16=Prison"); + clif_displaymessage(fd, " 3=Payon 10=Amatsu"); + return -1; + } else { + // get possible name of the city and add .gat if not in the name + map_name[sizeof(map_name)-1] = '\0'; + for (i = 0; map_name[i]; i++) + map_name[i] = tolower(map_name[i]); + if (strstr(map_name, ".gat") == NULL && strstr(map_name, ".afm") == NULL && strlen(map_name) < 13) // 16 - 4 (.gat) + strcat(map_name, ".gat"); + // try to see if it's a name, and not a number (try a lot of possibilities, write errors and abbreviations too) + if (strncmp(map_name, "prontera.gat", 3) == 0) { // 3 first characters + town = 0; + } else if (strncmp(map_name, "morocc.gat", 3) == 0) { // 3 first characters + town = 1; + } else if (strncmp(map_name, "geffen.gat", 3) == 0) { // 3 first characters + town = 2; + } else if (strncmp(map_name, "payon.gat", 3) == 0 || // 3 first characters + strncmp(map_name, "paion.gat", 3) == 0) { // writing error (3 first characters) + town = 3; + } else if (strncmp(map_name, "alberta.gat", 3) == 0) { // 3 first characters + town = 4; + } else if (strncmp(map_name, "izlude.gat", 3) == 0 || // 3 first characters + strncmp(map_name, "islude.gat", 3) == 0) { // writing error (3 first characters) + town = 5; + } else if (strncmp(map_name, "aldebaran.gat", 3) == 0 || // 3 first characters + strcmp(map_name, "al.gat") == 0) { // al (de baran) + town = 6; + } else if (strncmp(map_name, "lutie.gat", 3) == 0 || // name of the city, not name of the map (3 first characters) + strcmp(map_name, "christmas.gat") == 0 || // name of the symbol + strncmp(map_name, "xmas.gat", 3) == 0 || // 3 first characters + strncmp(map_name, "x-mas.gat", 3) == 0) { // writing error (3 first characters) + town = 7; + } else if (strncmp(map_name, "comodo.gat", 3) == 0) { // 3 first characters + town = 8; + } else if (strncmp(map_name, "yuno.gat", 3) == 0) { // 3 first characters + town = 9; + } else if (strncmp(map_name, "amatsu.gat", 3) == 0 || // 3 first characters + strncmp(map_name, "ammatsu.gat", 3) == 0) { // writing error (3 first characters) + town = 10; + } else if (strncmp(map_name, "gonryun.gat", 3) == 0) { // 3 first characters + town = 11; + } else if (strncmp(map_name, "umbala.gat", 3) == 0) { // 3 first characters + town = 12; + } else if (strncmp(map_name, "niflheim.gat", 3) == 0) { // 3 first characters + town = 13; + } else if (strncmp(map_name, "louyang.gat", 3) == 0) { // 3 first characters + town = 14; + } else if (strncmp(map_name, "new_1-1.gat", 3) == 0 || // 3 first characters (or "newbies") + strncmp(map_name, "startpoint.gat", 3) == 0 || // name of the position (3 first characters) + strncmp(map_name, "begining.gat", 3) == 0) { // name of the position (3 first characters) + town = 15; + } else if (strncmp(map_name, "sec_pri.gat", 3) == 0 || // 3 first characters + strncmp(map_name, "prison.gat", 3) == 0 || // name of the position (3 first characters) + strncmp(map_name, "jails.gat", 3) == 0) { // name of the position + town = 16; + } + + if (town >= -3 && town <= -1) { + if (sd->status.memo_point[-town-1].map[0]) { + m = map_mapname2mapid(sd->status.memo_point[-town-1].map); + if (m >= 0 && map[m].flag.nowarpto && battle_config.any_warp_GM_min_level > pc_isGM(sd)) { + clif_displaymessage(fd, "You are not authorised to warp you to this memo map."); + return -1; + } + if (sd->bl.m >= 0 && map[sd->bl.m].flag.nowarp && battle_config.any_warp_GM_min_level > pc_isGM(sd)) { + clif_displaymessage(fd, "You are not authorised to warp you from your actual map."); + return -1; + } + if (pc_setpos(sd, sd->status.memo_point[-town-1].map, sd->status.memo_point[-town-1].x, sd->status.memo_point[-town-1].y, 3) == 0) { + clif_displaymessage(fd, msg_table[0]); // Warped. + } else { + clif_displaymessage(fd, msg_table[1]); // Map not found. + return -1; + } + } else { + sprintf(output, msg_table[164], -town-1); // Your memo point #%d doesn't exist. + clif_displaymessage(fd, output); + return -1; + } + } else if (town >= 0 && town < (int)(sizeof(data) / sizeof(data[0]))) { + m = map_mapname2mapid(data[town].map); + if (m >= 0 && map[m].flag.nowarpto && battle_config.any_warp_GM_min_level > pc_isGM(sd)) { + clif_displaymessage(fd, "You are not authorised to warp you to this destination map."); + return -1; + } + if (sd->bl.m >= 0 && map[sd->bl.m].flag.nowarp && battle_config.any_warp_GM_min_level > pc_isGM(sd)) { + clif_displaymessage(fd, "You are not authorised to warp you from your actual map."); + return -1; + } + if (pc_setpos(sd, data[town].map, data[town].x, data[town].y, 3) == 0) { + clif_displaymessage(fd, msg_table[0]); // Warped. + } else { + clif_displaymessage(fd, msg_table[1]); // Map not found. + return -1; + } + } else { // if you arrive here, you have an error in town variable when reading of names + clif_displaymessage(fd, msg_table[38]); // Invalid location number or name. + return -1; + } + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_monster( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char name[100]; + char monster[100]; + char output[200]; + int mob_id; + int number = 0; + int x = 0, y = 0; + int count; + int i, j, k; + int mx, my, range; + + memset(name, '\0', sizeof(name)); + memset(monster, '\0', sizeof(monster)); + memset(output, '\0', sizeof(output)); + + if (!message || !*message || + (sscanf(message, "\"%[^\"]\" %99s %d %d %d", name, monster, &number, &x, &y) < 2 && + sscanf(message, "%99s \"%[^\"]\" %d %d %d", monster, name, &number, &x, &y) < 2 && + sscanf(message, "%99s %99s %d %d %d", name, monster, &number, &x, &y) < 2)) { + clif_displaymessage(fd, msg_table[80]); // Give a display name and monster name/id please. + return -1; + } + + if ((mob_id = mobdb_searchname(monster)) == 0) // check name first (to avoid possible name begining by a number) + mob_id = mobdb_checkid(atoi(monster)); + + if (mob_id == 0) { + clif_displaymessage(fd, msg_table[40]); // Invalid monster ID or name. + return -1; + } + + if (mob_id == 1288) { + clif_displaymessage(fd, msg_table[83]); // Cannot spawn emperium. + return -1; + } + + if (number <= 0) + number = 1; + + if (strlen(name) < 1) + strcpy(name, "--ja--"); + + // If value of atcommand_spawn_quantity_limit directive is greater than or equal to 1 and quantity of monsters is greater than value of the directive + if (battle_config.atc_spawn_quantity_limit >= 1 && number > battle_config.atc_spawn_quantity_limit) + number = battle_config.atc_spawn_quantity_limit; + + if (battle_config.etc_log) + printf("%s monster='%s' name='%s' id=%d count=%d (%d,%d)\n", command, monster, name, mob_id, number, x, y); + + count = 0; + range = sqrt(number) / 2; + range = range * 2 + 5; // calculation of an odd number (+ 4 area around) + for (i = 0; i < number; i++) { + j = 0; + k = 0; + while(j++ < 8 && k == 0) { // try 8 times to spawn the monster (needed for close area) + if (x <= 0) + mx = sd->bl.x + (rand() % range - (range / 2)); + else + mx = x; + if (y <= 0) + my = sd->bl.y + (rand() % range - (range / 2)); + else + my = y; + k = mob_once_spawn((struct map_session_data*)sd, "this", mx, my, name, mob_id, 1, ""); + } + count += (k != 0) ? 1 : 0; + } + + if (count != 0) + if (number == count) + clif_displaymessage(fd, msg_table[39]); // All monster summoned! + else { + sprintf(output, msg_table[240], count); // %d monster(s) summoned! + clif_displaymessage(fd, output); + } + else { + clif_displaymessage(fd, msg_table[40]); // Invalid monster ID or name. + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_spawn( + const int fd, struct map_session_data* sd, + const char* command, const char* message) { + char name[100]; + char monster[100]; + char output[200]; + int mob_id; + int number = 0; + int x = 0, y = 0; + int count; + int i, j, k; + int mx, my, range; + + memset(name, '\0', sizeof(name)); + memset(monster, '\0', sizeof(monster)); + memset(output, '\0', sizeof(output)); + + if (!message || !*message || + (sscanf(message, "\"%[^\"]\" %99s %d %d %d", name, monster, &number, &x, &y) < 2 && + sscanf(message, "%99s \"%[^\"]\" %d %d %d", monster, name, &number, &x, &y) < 2 && + sscanf(message, "%99s %d %99s %d %d", monster, &number, name, &x, &y) < 1)) { + clif_displaymessage(fd, msg_table[143]); // Give a monster name/id please. + return -1; + } + + // If monster identifier/name argument is a name + if ((mob_id = mobdb_searchname(monster)) == 0) // check name first (to avoid possible name begining by a number) + mob_id = mobdb_checkid(atoi(monster)); + + if (mob_id == 0) { + clif_displaymessage(fd, msg_table[40]); // Invalid monster ID or name. + return -1; + } + + if (mob_id == 1288) { + clif_displaymessage(fd, msg_table[83]); // Cannot spawn emperium. + return -1; + } + + if (number <= 0) + number = 1; + + if (strlen(name) < 1) + strcpy(name, "--ja--"); + + // If value of atcommand_spawn_quantity_limit directive is greater than or equal to 1 and quantity of monsters is greater than value of the directive + if (battle_config.atc_spawn_quantity_limit >= 1 && number > battle_config.atc_spawn_quantity_limit) + number = battle_config.atc_spawn_quantity_limit; + + if (battle_config.etc_log) + printf("%s monster='%s' name='%s' id=%d count=%d (%d,%d)\n", command, monster, name, mob_id, number, x, y); + + count = 0; + range = sqrt(number) / 2; + range = range * 2 + 5; // calculation of an odd number (+ 4 area around) + for (i = 0; i < number; i++) { + j = 0; + k = 0; + while(j++ < 8 && k == 0) { // try 8 times to spawn the monster (needed for close area) + if (x <= 0) + mx = sd->bl.x + (rand() % range - (range / 2)); + else + mx = x; + if (y <= 0) + my = sd->bl.y + (rand() % range - (range / 2)); + else + my = y; + k = mob_once_spawn((struct map_session_data*)sd, "this", mx, my, name, mob_id, 1, ""); + } + count += (k != 0) ? 1 : 0; + } + + if (count != 0) + if (number == count) + clif_displaymessage(fd, msg_table[39]); // All monster summoned! + else { + sprintf(output, msg_table[240], count); // %d monster(s) summoned! + clif_displaymessage(fd, output); + } + else { + clif_displaymessage(fd, msg_table[40]); // Invalid monster ID or name. + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +void atcommand_killmonster_sub( + const int fd, struct map_session_data* sd, const char* message, + const int drop) +{ + int map_id; + char map_name[100]; + + memset(map_name, '\0', sizeof(map_name)); + + if (!message || !*message || sscanf(message, "%99s", map_name) < 1) + map_id = sd->bl.m; + else { + if (strstr(map_name, ".gat") == NULL && strstr(map_name, ".afm") == NULL && strlen(map_name) < 13) // 16 - 4 (.gat) + strcat(map_name, ".gat"); + if ((map_id = map_mapname2mapid(map_name)) < 0) + map_id = sd->bl.m; + } + + map_foreachinarea(atkillmonster_sub, map_id, 0, 0, map[map_id].xs, map[map_id].ys, BL_MOB, drop); + + clif_displaymessage(fd, msg_table[165]); // All monsters killed! + + return; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_killmonster( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + atcommand_killmonster_sub(fd, sd, message, 1); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_killmonster2( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + atcommand_killmonster_sub(fd, sd, message, 0); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_refine( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + int i, position = 0, refine = 0, current_position, final_refine; + int count; + char output[200]; + + memset(output, '\0', sizeof(output)); + + if (!message || !*message || sscanf(message, "%d %d", &position, &refine) < 2) { + clif_displaymessage(fd, "Please, enter a position and a amount (usage: @refine <equip position> <+/- amount>)."); + return -1; + } + + if (refine < -10) + refine = -10; + else if (refine > 10) + refine = 10; + else if (refine == 0) + refine = 1; + + count = 0; + for (i = 0; i < MAX_INVENTORY; i++) { + if (sd->status.inventory[i].nameid && // 該当個所の装備を精錬する + (sd->status.inventory[i].equip & position || + (sd->status.inventory[i].equip && !position))) { + final_refine = sd->status.inventory[i].refine + refine; + if (final_refine > 10) + final_refine = 10; + else if (final_refine < 0) + final_refine = 0; + if (sd->status.inventory[i].refine != final_refine) { + sd->status.inventory[i].refine = final_refine; + current_position = sd->status.inventory[i].equip; + pc_unequipitem(sd, i, 0); + clif_refine(fd, sd, 0, i, sd->status.inventory[i].refine); + clif_delitem(sd, i, 1); + clif_additem(sd, i, 1, 0); + pc_equipitem(sd, i, current_position); + clif_misceffect((struct block_list*)&sd->bl, 3); + count++; + } + } + } + + if (count == 0) + clif_displaymessage(fd, msg_table[166]); // No item has been refined! + else if (count == 1) + clif_displaymessage(fd, msg_table[167]); // 1 item has been refined! + else { + sprintf(output, msg_table[168], count); // %d items have been refined! + clif_displaymessage(fd, output); + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_produce( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char item_name[100]; + int item_id, attribute = 0, star = 0; + int flag = 0; + struct item_data *item_data; + struct item tmp_item; + char output[200]; + + memset(output, '\0', sizeof(output)); + memset(item_name, '\0', sizeof(item_name)); + + if (!message || !*message || sscanf(message, "%99s %d %d", item_name, &attribute, &star) < 1) { + clif_displaymessage(fd, "Please, enter at least an item name/id (usage: @produce <equip name or equip ID> <element> <# of very's>)."); + return -1; + } + + item_id = 0; + if ((item_data = itemdb_searchname(item_name)) != NULL || + (item_data = itemdb_exists(atoi(item_name))) != NULL) + item_id = item_data->nameid; + + if (itemdb_exists(item_id) && + (item_id <= 500 || item_id > 1099) && + (item_id < 4001 || item_id > 4148) && + (item_id < 7001 || item_id > 10019) && + itemdb_isequip(item_id)) { + if (attribute < MIN_ATTRIBUTE || attribute > MAX_ATTRIBUTE) + attribute = ATTRIBUTE_NORMAL; + if (star < MIN_STAR || star > MAX_STAR) + star = 0; + memset(&tmp_item, 0, sizeof tmp_item); + tmp_item.nameid = item_id; + tmp_item.amount = 1; + tmp_item.identify = 1; + tmp_item.card[0] = 0x00ff; + tmp_item.card[1] = ((star * 5) << 8) + attribute; + *((unsigned long *)(&tmp_item.card[2])) = sd->char_id; + clif_produceeffect(sd, 0, item_id); // 製造エフェクトパケット + clif_misceffect(&sd->bl, 3); // 他人にも成功を通知 + if ((flag = pc_additem(sd, &tmp_item, 1))) + clif_additem(sd, 0, 0, flag); + } else { + if (battle_config.error_log) + printf("@produce NOT WEAPON [%d]\n", item_id); + if (item_id != 0 && itemdb_exists(item_id)) + sprintf(output, msg_table[169], item_id, item_data->name); // This item (%d: '%s') is not an equipment. + else + sprintf(output, msg_table[170]); // This item is not an equipment. + clif_displaymessage(fd, output); + return -1; + } + + return 0; +} + +/*========================================== + * Sub-function to display actual memo points + *------------------------------------------ + */ +void atcommand_memo_sub(struct map_session_data* sd) { + int i; + char output[200]; + + memset(output, '\0', sizeof(output)); + + clif_displaymessage(sd->fd, "Your actual memo positions are (except respawn point):"); + for (i = MIN_PORTAL_MEMO; i <= MAX_PORTAL_MEMO; i++) { + if (sd->status.memo_point[i].map[0]) + sprintf(output, "%d - %s (%d,%d)", i, sd->status.memo_point[i].map, sd->status.memo_point[i].x, sd->status.memo_point[i].y); + else + sprintf(output, msg_table[171], i); // %d - void + clif_displaymessage(sd->fd, output); + } + + return; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_memo( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + int position = 0; + char output[200]; + + memset(output, '\0', sizeof(output)); + + if (!message || !*message || sscanf(message, "%d", &position) < 1) + atcommand_memo_sub(sd); + else { + if (position >= MIN_PORTAL_MEMO && position <= MAX_PORTAL_MEMO) { + if (sd->bl.m >= 0 && map[sd->bl.m].flag.nowarpto && battle_config.any_warp_GM_min_level > pc_isGM(sd)) { + clif_displaymessage(fd, "You are not authorised to memo this map."); + return -1; + } + if (sd->status.memo_point[position].map[0]) { + sprintf(output, msg_table[172], position, sd->status.memo_point[position].map, sd->status.memo_point[position].x, sd->status.memo_point[position].y); // You replace previous memo position %d - %s (%d,%d). + clif_displaymessage(fd, output); + } + memcpy(sd->status.memo_point[position].map, map[sd->bl.m].name, 24); + sd->status.memo_point[position].x = sd->bl.x; + sd->status.memo_point[position].y = sd->bl.y; + clif_skill_memo(sd, 0); + if (pc_checkskill(sd, AL_WARP) <= (position + 1)) + clif_displaymessage(fd, msg_table[173]); // Note: you don't have the 'Warp' skill level to use it. + atcommand_memo_sub(sd); + } else { + sprintf(output, "Please, enter a valid position (usage: @memo <memo_position:%d-%d>).", MIN_PORTAL_MEMO, MAX_PORTAL_MEMO); + clif_displaymessage(fd, output); + atcommand_memo_sub(sd); + return -1; + } + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_gat( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char output[200]; + int y; + + memset(output, '\0', sizeof(output)); + + for (y = 2; y >= -2; y--) { + sprintf(output, "%s (x= %d, y= %d) %02X %02X %02X %02X %02X", + map[sd->bl.m].name, sd->bl.x - 2, sd->bl.y + y, + map_getcell(sd->bl.m, sd->bl.x - 2, sd->bl.y + y), + map_getcell(sd->bl.m, sd->bl.x - 1, sd->bl.y + y), + map_getcell(sd->bl.m, sd->bl.x, sd->bl.y + y), + map_getcell(sd->bl.m, sd->bl.x + 1, sd->bl.y + y), + map_getcell(sd->bl.m, sd->bl.x + 2, sd->bl.y + y)); + clif_displaymessage(fd, output); + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_packet( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + int x = 0, y = 0; + + if (!message || !*message || sscanf(message, "%d %d", &x, &y) < 2) { + clif_displaymessage(fd, "Please, enter a status type/flag (usage: @packet <status type> <flag>)."); + return -1; + } + + clif_status_change(&sd->bl, x, y); + + return 0; +} + +/*========================================== + * @stpoint (Rewritten by [Yor]) + *------------------------------------------ + */ +int atcommand_statuspoint( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + int point, new_status_point; + + if (!message || !*message || (point = atoi(message)) == 0) { + clif_displaymessage(fd, "Please, enter a number (usage: @stpoint <number of points>)."); + return -1; + } + + new_status_point = (int)sd->status.status_point + point; + if (point > 0 && (point > 0x7FFF || new_status_point > 0x7FFF)) // fix positiv overflow + new_status_point = 0x7FFF; + else if (point < 0 && (point < -0x7FFF || new_status_point < 0)) // fix negativ overflow + new_status_point = 0; + + if (new_status_point != (int)sd->status.status_point) { + sd->status.status_point = (short)new_status_point; + clif_updatestatus(sd, SP_STATUSPOINT); + clif_displaymessage(fd, msg_table[174]); // Number of status points changed! + } else { + if (point < 0) + clif_displaymessage(fd, msg_table[41]); // Impossible to decrease the number/value. + else + clif_displaymessage(fd, msg_table[149]); // Impossible to increase the number/value. + return -1; + } + + return 0; +} + +/*========================================== + * @skpoint (Rewritten by [Yor]) + *------------------------------------------ + */ +int atcommand_skillpoint( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + int point, new_skill_point; + + if (!message || !*message || (point = atoi(message)) == 0) { + clif_displaymessage(fd, "Please, enter a number (usage: @skpoint <number of points>)."); + return -1; + } + + new_skill_point = (int)sd->status.skill_point + point; + if (point > 0 && (point > 0x7FFF || new_skill_point > 0x7FFF)) // fix positiv overflow + new_skill_point = 0x7FFF; + else if (point < 0 && (point < -0x7FFF || new_skill_point < 0)) // fix negativ overflow + new_skill_point = 0; + + if (new_skill_point != (int)sd->status.skill_point) { + sd->status.skill_point = (short)new_skill_point; + clif_updatestatus(sd, SP_SKILLPOINT); + clif_displaymessage(fd, msg_table[175]); // Number of skill points changed! + } else { + if (point < 0) + clif_displaymessage(fd, msg_table[41]); // Impossible to decrease the number/value. + else + clif_displaymessage(fd, msg_table[149]); // Impossible to increase the number/value. + return -1; + } + + return 0; +} + +/*========================================== + * @zeny (Rewritten by [Yor]) + *------------------------------------------ + */ +int atcommand_zeny( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + int zeny, new_zeny; + + if (!message || !*message || (zeny = atoi(message)) == 0) { + clif_displaymessage(fd, "Please, enter an amount (usage: @zeny <amount>)."); + return -1; + } + + new_zeny = sd->status.zeny + zeny; + if (zeny > 0 && (zeny > MAX_ZENY || new_zeny > MAX_ZENY)) // fix positiv overflow + new_zeny = MAX_ZENY; + else if (zeny < 0 && (zeny < -MAX_ZENY || new_zeny < 0)) // fix negativ overflow + new_zeny = 0; + + if (new_zeny != sd->status.zeny) { + sd->status.zeny = new_zeny; + clif_updatestatus(sd, SP_ZENY); + clif_displaymessage(fd, msg_table[176]); // Number of zenys changed! + } else { + if (zeny < 0) + clif_displaymessage(fd, msg_table[41]); // Impossible to decrease the number/value. + else + clif_displaymessage(fd, msg_table[149]); // Impossible to increase the number/value. + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_param( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + int i, index, value = 0, new_value; + const char* param[] = { "@str", "@agi", "@vit", "@int", "@dex", "@luk", NULL }; + short* status[] = { + &sd->status.str, &sd->status.agi, &sd->status.vit, + &sd->status.int_, &sd->status.dex, &sd->status.luk + }; + char output[200]; + + memset(output, '\0', sizeof(output)); + + if (!message || !*message || sscanf(message, "%d", &value) < 1 || value == 0) { + sprintf(output, "Please, enter a valid value (usage: @str,@agi,@vit,@int,@dex,@luk <+/-adjustement>)."); + clif_displaymessage(fd, output); + return -1; + } + + index = -1; + for (i = 0; param[i] != NULL; i++) { + if (strcmpi(command, param[i]) == 0) { + index = i; + break; + } + } + if (index < 0 || index > MAX_STATUS_TYPE) { // normaly impossible... + sprintf(output, "Please, enter a valid value (usage: @str,@agi,@vit,@int,@dex,@luk <+/-adjustement>)."); + clif_displaymessage(fd, output); + return -1; + } + + new_value = (int)*status[index] + value; + if (value > 0 && (value > battle_config.max_parameter || new_value > battle_config.max_parameter)) // fix positiv overflow + new_value = battle_config.max_parameter; + else if (value < 0 && (value < -battle_config.max_parameter || new_value < 1)) // fix negativ overflow + new_value = 1; + + if (new_value != (int)*status[index]) { + *status[index] = new_value; + clif_updatestatus(sd, SP_STR + index); + clif_updatestatus(sd, SP_USTR + index); + pc_calcstatus(sd, 0); + clif_displaymessage(fd, msg_table[42]); // Stat changed. + } else { + if (value < 0) + clif_displaymessage(fd, msg_table[41]); // Impossible to decrease the number/value. + else + clif_displaymessage(fd, msg_table[149]); // Impossible to increase the number/value. + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +//** Stat all by fritz (rewritten by [Yor]) +int atcommand_stat_all( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + int index, count, value = 0, new_value; + short* status[] = { + &sd->status.str, &sd->status.agi, &sd->status.vit, + &sd->status.int_, &sd->status.dex, &sd->status.luk + }; + + if (!message || !*message || sscanf(message, "%d", &value) < 1 || value == 0) + value = battle_config.max_parameter; + + count = 0; + for (index = 0; index < (int)(sizeof(status) / sizeof(status[0])); index++) { + + new_value = (int)*status[index] + value; + if (value > 0 && (value > battle_config.max_parameter || new_value > battle_config.max_parameter)) // fix positiv overflow + new_value = battle_config.max_parameter; + else if (value < 0 && (value < -battle_config.max_parameter || new_value < 1)) // fix negativ overflow + new_value = 1; + + if (new_value != (int)*status[index]) { + *status[index] = new_value; + clif_updatestatus(sd, SP_STR + index); + clif_updatestatus(sd, SP_USTR + index); + pc_calcstatus(sd, 0); + count++; + } + } + + if (count > 0) // if at least 1 stat modified + clif_displaymessage(fd, msg_table[84]); // All stats changed! + else { + if (value < 0) + clif_displaymessage(fd, msg_table[177]); // Impossible to decrease a stat. + else + clif_displaymessage(fd, msg_table[178]); // Impossible to increase a stat. + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_guildlevelup( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + int level = 0; + short added_level; + struct guild *guild_info; + + if (!message || !*message || sscanf(message, "%d", &level) < 1 || level == 0) { + clif_displaymessage(fd, "Please, enter a valid level (usage: @guildlvup/@guildlvlup <# of levels>)."); + return -1; + } + + if (sd->status.guild_id <= 0 || (guild_info = guild_search(sd->status.guild_id)) == NULL) { + clif_displaymessage(fd, msg_table[43]); // You're not in a guild. + return -1; + } + if (strcmp(sd->status.name, guild_info->master) != 0) { + clif_displaymessage(fd, msg_table[44]); // You're not the master of your guild. + return -1; + } + + added_level = (short)level; + if (level > 0 && (level > MAX_GUILDLEVEL || added_level > ((short)MAX_GUILDLEVEL - guild_info->guild_lv))) // fix positiv overflow + added_level = (short)MAX_GUILDLEVEL - guild_info->guild_lv; + else if (level < 0 && (level < -MAX_GUILDLEVEL || added_level < (1 - guild_info->guild_lv))) // fix negativ overflow + added_level = 1 - guild_info->guild_lv; + + if (added_level != 0) { + intif_guild_change_basicinfo(guild_info->guild_id, GBI_GUILDLV, &added_level, 2); + clif_displaymessage(fd, msg_table[179]); // Guild level changed. + } else { + clif_displaymessage(fd, msg_table[45]); // Guild level change failed. + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_makeegg( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + struct item_data *item_data; + int id, pet_id; + + if (!message || !*message) { + clif_displaymessage(fd, "Please, enter a monter/egg name/id (usage: @makeegg <pet_id>)."); + return -1; + } + + if ((item_data = itemdb_searchname(message)) != NULL) // for egg name + id = item_data->nameid; + else if ((id = mobdb_searchname(message)) == 0) // for monster name + id = atoi(message); + + pet_id = search_petDB_index(id, PET_CLASS); + if (pet_id < 0) + pet_id = search_petDB_index(id, PET_EGG); + if (pet_id >= 0) { + sd->catch_target_class = pet_db[pet_id].class; + intif_create_pet( + sd->status.account_id, sd->status.char_id, + pet_db[pet_id].class, mob_db[pet_db[pet_id].class].lv, + pet_db[pet_id].EggID, 0, pet_db[pet_id].intimate, + 100, 0, 1, pet_db[pet_id].jname); + } else { + clif_displaymessage(fd, msg_table[180]); // The monter/egg name/id doesn't exist. + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_hatch( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + if (sd->status.pet_id <= 0) + clif_sendegg(sd); + else { + clif_displaymessage(fd, msg_table[181]); // You already have a pet. + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_petfriendly( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + int friendly; + int t; + + if (!message || !*message || (friendly = atoi(message)) < 0) { + clif_displaymessage(fd, "Please, enter a valid value (usage: @petfriendly <0-1000>)."); + return -1; + } + + if (sd->status.pet_id > 0 && sd->pd) { + if (friendly >= 0 && friendly <= 1000) { + if (friendly != sd->pet.intimate) { + t = sd->pet.intimate; + sd->pet.intimate = friendly; + clif_send_petstatus(sd); + if (battle_config.pet_status_support) { + if ((sd->pet.intimate > 0 && t <= 0) || + (sd->pet.intimate <= 0 && t > 0)) { + if (sd->bl.prev != NULL) + pc_calcstatus(sd, 0); + else + pc_calcstatus(sd, 2); + } + } + clif_displaymessage(fd, msg_table[182]); // Pet friendly value changed! + } else { + clif_displaymessage(fd, msg_table[183]); // Pet friendly is already the good value. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[37]); // An invalid number was specified. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[184]); // Sorry, but you have no pet. + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_pethungry( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + int hungry; + + if (!message || !*message || (hungry = atoi(message)) < 0) { + clif_displaymessage(fd, "Please, enter a valid number (usage: @pethungry <0-100>)."); + return -1; + } + + if (sd->status.pet_id > 0 && sd->pd) { + if (hungry >= 0 && hungry <= 100) { + if (hungry != sd->pet.hungry) { + sd->pet.hungry = hungry; + clif_send_petstatus(sd); + clif_displaymessage(fd, msg_table[185]); // Pet hungry value changed! + } else { + clif_displaymessage(fd, msg_table[186]); // Pet hungry is already the good value. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[37]); // An invalid number was specified. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[184]); // Sorry, but you have no pet. + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_petrename( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + if (sd->status.pet_id > 0 && sd->pd) { + if (sd->pet.rename_flag != 0) { + sd->pet.rename_flag = 0; + intif_save_petdata(sd->status.account_id, &sd->pet); + clif_send_petstatus(sd); + clif_displaymessage(fd, msg_table[187]); // You can now rename your pet. + } else { + clif_displaymessage(fd, msg_table[188]); // You can already rename your pet. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[184]); // Sorry, but you have no pet. + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_charpetrename( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char character[100]; + struct map_session_data *pl_sd; + + memset(character, '\0', sizeof(character)); + + if (!message || !*message || sscanf(message, "%99[^\n]", character) < 1) { + clif_displaymessage(fd, "Please, enter a player name (usage: @charpetrename <char name>)."); + return -1; + } + + if ((pl_sd = map_nick2sd(character)) != NULL) { + if (pl_sd->status.pet_id > 0 && pl_sd->pd) { + if (pl_sd->pet.rename_flag != 0) { + pl_sd->pet.rename_flag = 0; + intif_save_petdata(pl_sd->status.account_id, &pl_sd->pet); + clif_send_petstatus(pl_sd); + clif_displaymessage(fd, msg_table[189]); // This player can now rename his/her pet. + } else { + clif_displaymessage(fd, msg_table[190]); // This player can already rename his/her pet. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[191]); // Sorry, but this player has no pet. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[3]); // Character not found. + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_recall( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char character[100]; + char output[200]; + struct map_session_data *pl_sd; + + memset(character, '\0', sizeof(character)); + memset(output, '\0', sizeof(output)); + + if (!message || !*message || sscanf(message, "%99[^\n]", character) < 1) { + clif_displaymessage(fd, "Please, enter a player name (usage: @recall <char name>)."); + return -1; + } + + if ((pl_sd = map_nick2sd(character)) != NULL) { + if (pc_isGM(sd) >= pc_isGM(pl_sd)) { // you can recall only lower or same level + if (sd->bl.m >= 0 && map[sd->bl.m].flag.nowarpto && battle_config.any_warp_GM_min_level > pc_isGM(sd)) { + clif_displaymessage(fd, "You are not authorised to warp somenone to your actual map."); + return -1; + } + if (pl_sd->bl.m >= 0 && map[pl_sd->bl.m].flag.nowarp && battle_config.any_warp_GM_min_level > pc_isGM(sd)) { + clif_displaymessage(fd, "You are not authorised to warp this player from its actual map."); + return -1; + } + pc_setpos(pl_sd, sd->mapname, sd->bl.x, sd->bl.y, 2); + sprintf(output, msg_table[46], character); // %s recalled! + clif_displaymessage(fd, output); + } else { + clif_displaymessage(fd, msg_table[81]); // Your GM level don't authorise you to do this action on this player. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[3]); // Character not found. + return -1; + } + + return 0; +} + +/*========================================== + * 対象キャラクターを転職させる upper指定で転生や養子も可能 + *------------------------------------------ + */ +int atcommand_character_job( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char character[100]; + struct map_session_data* pl_sd; + int job = 0, upper = -1; + + memset(character, '\0', sizeof(character)); + + if (!message || !*message) { + clif_displaymessage(fd, "Please, enter a job and a player name (usage: @charjob/@charjobchange <job ID> <char name>)."); + return -1; + } + + if (sscanf(message, "%d %d %99[^\n]", &job, &upper, character) < 3) { //upper指定してある + upper = -1; + if (sscanf(message, "%d %99[^\n]", &job, character) < 2) { //upper指定してない上に何か足りない + clif_displaymessage(fd, "Please, enter a job and a player name (usage: @charjob/@charjobchange <job ID> <char name>)."); + return -1; + } + } + + if ((pl_sd = map_nick2sd(character)) != NULL) { + if (pc_isGM(sd) >= pc_isGM(pl_sd)) { // you can change job only to lower or same level + if ((job >= 0 && job < MAX_PC_CLASS)) { + + // fix pecopeco display + if ((job != 13 && job != 21 && job != 4014 && job != 4022)) { + if (pc_isriding(sd)) { + if (pl_sd->status.class == 13) + pl_sd->status.class = pl_sd->view_class = 7; + if (pl_sd->status.class == 21) + pl_sd->status.class = pl_sd->view_class = 14; + if (pl_sd->status.class == 4014) + pl_sd->status.class = pl_sd->view_class = 4008; + if (pl_sd->status.class == 4022) + pl_sd->status.class = pl_sd->view_class = 4015; + pl_sd->status.option &= ~0x0020; + clif_changeoption(&pl_sd->bl); + pc_calcstatus(pl_sd, 0); + } + } else { + if (!pc_isriding(sd)) { + if (job == 13) + job = 7; + if (job == 21) + job = 14; + if (job == 4014) + job = 4008; + if (job == 4022) + job = 4015; + } + } + + if (pc_jobchange(pl_sd, job, upper) == 0) + clif_displaymessage(fd, msg_table[48]); // Character's job changed. + else { + clif_displaymessage(fd, msg_table[192]); // Impossible to change the character's job. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[49]); // Invalid job ID. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[81]); // Your GM level don't authorise you to do this action on this player. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[3]); // Character not found. + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_revive( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char character[100]; + struct map_session_data *pl_sd; + + memset(character, '\0', sizeof(character)); + + if (!message || !*message || sscanf(message, "%99[^\n]", character) < 1) { + clif_displaymessage(fd, "Please, enter a player name (usage: @revive <char name>)."); + return -1; + } + + if ((pl_sd = map_nick2sd(character)) != NULL) { + pl_sd->status.hp = pl_sd->status.max_hp; + pc_setstand(pl_sd); + if (battle_config.pc_invincible_time > 0) + pc_setinvincibletimer(sd, battle_config.pc_invincible_time); + clif_updatestatus(pl_sd, SP_HP); + clif_updatestatus(pl_sd, SP_SP); + clif_resurrection(&pl_sd->bl, 1); + clif_displaymessage(fd, msg_table[51]); // Character revived. + } else { + clif_displaymessage(fd, msg_table[3]); // Character not found. + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_character_stats( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char character[100]; + char job_jobname[100]; + char output[200]; + struct map_session_data *pl_sd; + int i; + + memset(character, '\0', sizeof(character)); + memset(job_jobname, '\0', sizeof(job_jobname)); + memset(output, '\0', sizeof(output)); + + if (!message || !*message || sscanf(message, "%99[^\n]", character) < 1) { + clif_displaymessage(fd, "Please, enter a player name (usage: @charstats <char name>)."); + return -1; + } + + if ((pl_sd = map_nick2sd(character)) != NULL) { + struct { + const char* format; + int value; + } output_table[] = { + { "Base Level - %d", pl_sd->status.base_level }, + { job_jobname, pl_sd->status.job_level }, + { "Hp - %d", pl_sd->status.hp }, + { "MaxHp - %d", pl_sd->status.max_hp }, + { "Sp - %d", pl_sd->status.sp }, + { "MaxSp - %d", pl_sd->status.max_sp }, + { "Str - %3d", pl_sd->status.str }, + { "Agi - %3d", pl_sd->status.agi }, + { "Vit - %3d", pl_sd->status.vit }, + { "Int - %3d", pl_sd->status.int_ }, + { "Dex - %3d", pl_sd->status.dex }, + { "Luk - %3d", pl_sd->status.luk }, + { "Zeny - %d", pl_sd->status.zeny }, + { NULL, 0 } + }; + sprintf(job_jobname, "Job - %s %s", job_name(pl_sd->status.class), "(level %d)"); + sprintf(output, msg_table[53], pl_sd->status.name); // '%s' stats: + clif_displaymessage(fd, output); + for (i = 0; output_table[i].format != NULL; i++) { + sprintf(output, output_table[i].format, output_table[i].value); + clif_displaymessage(fd, output); + } + } else { + clif_displaymessage(fd, msg_table[3]); // Character not found. + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +//** Character Stats All by fritz +int atcommand_character_stats_all(const int fd, struct map_session_data* sd, const char* command, const char* message) +{ + char output[1024], gmlevel[1024]; + int i; + int count; + struct map_session_data *pl_sd; + + memset(output, '\0', sizeof(output)); + memset(gmlevel, '\0', sizeof(gmlevel)); + + count = 0; + for(i = 0; i < fd_max; i++) { + if (session[i] && (pl_sd = session[i]->session_data) && pl_sd->state.auth) { + + if (pc_isGM(pl_sd) > 0) + sprintf(gmlevel, "| GM Lvl: %d", pc_isGM(pl_sd)); + else + sprintf(gmlevel, " "); + + sprintf(output, "Name: %s | BLvl: %d | Job: %s (Lvl: %d) | HP: %d/%d | SP: %d/%d", pl_sd->status.name, pl_sd->status.base_level, job_name(pl_sd->status.class), pl_sd->status.job_level, pl_sd->status.hp, pl_sd->status.max_hp, pl_sd->status.sp, pl_sd->status.max_sp); + clif_displaymessage(fd, output); + sprintf(output, "STR: %d | AGI: %d | VIT: %d | INT: %d | DEX: %d | LUK: %d | Zeny: %d %s", pl_sd->status.str, pl_sd->status.agi, pl_sd->status.vit, pl_sd->status.int_, pl_sd->status.dex, pl_sd->status.luk, pl_sd->status.zeny, gmlevel); + clif_displaymessage(fd, output); + clif_displaymessage(fd, "--------"); + count++; + } + } + + if (count == 0) + clif_displaymessage(fd, msg_table[28]); // No player found. + else if (count == 1) + clif_displaymessage(fd, msg_table[29]); // 1 player found. + else { + sprintf(output, msg_table[30], count); // %d players found. + clif_displaymessage(fd, output); + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_character_option( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char character[100]; + int opt1 = 0, opt2 = 0, opt3 = 0; + struct map_session_data* pl_sd; + + memset(character, '\0', sizeof(character)); + + if (!message || !*message || sscanf(message, "%d %d %d %99[^\n]", &opt1, &opt2, &opt3, character) < 4 || opt1 < 0 || opt2 < 0 || opt3 < 0) { + clif_displaymessage(fd, "Please, enter valid options and a player name (usage: @charoption <param1> <param2> <param3> <charname>)."); + return -1; + } + + if ((pl_sd = map_nick2sd(character)) != NULL) { + if (pc_isGM(sd) >= pc_isGM(pl_sd)) { // you can change option only to lower or same level + pl_sd->opt1 = opt1; + pl_sd->opt2 = opt2; + pl_sd->status.option = opt3; + // fix pecopeco display + if (pl_sd->status.class == 13 || pl_sd->status.class == 21 || pl_sd->status.class == 4014 || pl_sd->status.class == 4022) { + if (!pc_isriding(pl_sd)) { // pl_sd have the new value... + if (pl_sd->status.class == 13) + pl_sd->status.class = pl_sd->view_class = 7; + else if (pl_sd->status.class == 21) + pl_sd->status.class = pl_sd->view_class = 14; + else if (pl_sd->status.class == 4014) + pl_sd->status.class = pl_sd->view_class = 4008; + else if (pl_sd->status.class == 4022) + pl_sd->status.class = pl_sd->view_class = 4015; + } + } else { + if (pc_isriding(pl_sd)) { // pl_sd have the new value... + if (pl_sd->disguise > 0) { // temporary prevention of crash caused by peco + disguise, will look into a better solution [Valaris] (code added by [Yor]) + pl_sd->status.option &= ~0x0020; + } else { + if (pl_sd->status.class == 7) + pl_sd->status.class = pl_sd->view_class = 13; + else if (pl_sd->status.class == 14) + pl_sd->status.class = pl_sd->view_class = 21; + else if (pl_sd->status.class == 4008) + pl_sd->status.class = pl_sd->view_class = 4014; + else if (pl_sd->status.class == 4015) + pl_sd->status.class = pl_sd->view_class = 4022; + else + pl_sd->status.option &= ~0x0020; + } + } + } + clif_changeoption(&pl_sd->bl); + pc_calcstatus(pl_sd, 0); + clif_displaymessage(fd, msg_table[58]); // Character's options changed. + } else { + clif_displaymessage(fd, msg_table[81]); // Your GM level don't authorise you to do this action on this player. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[3]); // Character not found. + return -1; + } + + return 0; +} + +/*========================================== + * charchangesex command (usage: charchangesex <player_name>) + *------------------------------------------ + */ +int atcommand_char_change_sex( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char character[100]; + + memset(character, '\0', sizeof(character)); + + if (!message || !*message || sscanf(message, "%99[^\n]", character) < 1) { + clif_displaymessage(fd, "Please, enter a player name (usage: @charchangesex <name>)."); + return -1; + } + + // check player name + if (strlen(character) < 4) { + clif_displaymessage(fd, msg_table[86]); // Sorry, but a player name have at least 4 characters. + return -1; + } else if (strlen(character) > 23) { + clif_displaymessage(fd, msg_table[87]); // Sorry, but a player name have 23 characters maximum. + return -1; + } else { + chrif_char_ask_name(sd->status.account_id, character, 5, 0, 0, 0, 0, 0, 0); // type: 5 - changesex + clif_displaymessage(fd, msg_table[88]); // Character name sends to char-server to ask it. + } + + return 0; +} + +/*========================================== + * charblock command (usage: charblock <player_name>) + * This command do a definitiv ban on a player + *------------------------------------------ + */ +int atcommand_char_block( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char character[100]; + + memset(character, '\0', sizeof(character)); + + if (!message || !*message || sscanf(message, "%99[^\n]", character) < 1) { + clif_displaymessage(fd, "Please, enter a player name (usage: @charblock/@block <name>)."); + return -1; + } + + // check player name + if (strlen(character) < 4) { + clif_displaymessage(fd, msg_table[86]); // Sorry, but a player name have at least 4 characters. + return -1; + } else if (strlen(character) > 23) { + clif_displaymessage(fd, msg_table[87]); // Sorry, but a player name have 23 characters maximum. + return -1; + } else { + chrif_char_ask_name(sd->status.account_id, character, 1, 0, 0, 0, 0, 0, 0); // type: 1 - block + clif_displaymessage(fd, msg_table[88]); // Character name sends to char-server to ask it. + } + + return 0; +} + +/*========================================== + * charban command (usage: charban <time> <player_name>) + * This command do a limited ban on a player + * Time is done as follows: + * Adjustment value (-1, 1, +1, etc...) + * Modified element: + * a or y: year + * m: month + * j or d: day + * h: hour + * mn: minute + * s: second + * <example> @ban +1m-2mn1s-6y test_player + * this example adds 1 month and 1 second, and substracts 2 minutes and 6 years at the same time. + *------------------------------------------ + */ +int atcommand_char_ban( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char modif[100], character[100]; + char * modif_p; + int year, month, day, hour, minute, second, value; + + memset(modif, '\0', sizeof(modif)); + memset(character, '\0', sizeof(character)); + + if (!message || !*message || sscanf(message, "%s %99[^\n]", modif, character) < 2) { + clif_displaymessage(fd, "Please, enter ban time and a player name (usage: @charban/@ban/@banish/@charbanish <time> <name>)."); + return -1; + } + + modif[sizeof(modif)-1] = '\0'; + character[sizeof(character)-1] = '\0'; + + modif_p = modif; + year = month = day = hour = minute = second = 0; + while (modif_p[0] != '\0') { + value = atoi(modif_p); + if (value == 0) + modif_p++; + else { + if (modif_p[0] == '-' || modif_p[0] == '+') + modif_p++; + while (modif_p[0] >= '0' && modif_p[0] <= '9') + modif_p++; + if (modif_p[0] == 's') { + second = value; + modif_p++; + } else if (modif_p[0] == 'm' && modif_p[1] == 'n') { + minute = value; + modif_p = modif_p + 2; + } else if (modif_p[0] == 'h') { + hour = value; + modif_p++; + } else if (modif_p[0] == 'd' || modif_p[0] == 'j') { + day = value; + modif_p++; + } else if (modif_p[0] == 'm') { + month = value; + modif_p++; + } else if (modif_p[0] == 'y' || modif_p[0] == 'a') { + year = value; + modif_p++; + } else if (modif_p[0] != '\0') { + modif_p++; + } + } + } + if (year == 0 && month == 0 && day == 0 && hour == 0 && minute == 0 && second == 0) { + clif_displaymessage(fd, msg_table[85]); // Invalid time for ban command. + return -1; + } + + // check player name + if (strlen(character) < 4) { + clif_displaymessage(fd, msg_table[86]); // Sorry, but a player name have at least 4 characters. + return -1; + } else if (strlen(character) > 23) { + clif_displaymessage(fd, msg_table[87]); // Sorry, but a player name have 23 characters maximum. + return -1; + } else { + chrif_char_ask_name(sd->status.account_id, character, 2, year, month, day, hour, minute, second); // type: 2 - ban + clif_displaymessage(fd, msg_table[88]); // Character name sends to char-server to ask it. + } + + return 0; +} + +/*========================================== + * charunblock command (usage: charunblock <player_name>) + *------------------------------------------ + */ +int atcommand_char_unblock( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char character[100]; + + memset(character, '\0', sizeof(character)); + + if (!message || !*message || sscanf(message, "%99[^\n]", character) < 1) { + clif_displaymessage(fd, "Please, enter a player name (usage: @charunblock <player_name>)."); + return -1; + } + + // check player name + if (strlen(character) < 4) { + clif_displaymessage(fd, msg_table[86]); // Sorry, but a player name have at least 4 characters. + return -1; + } else if (strlen(character) > 23) { + clif_displaymessage(fd, msg_table[87]); // Sorry, but a player name have 23 characters maximum. + return -1; + } else { + // send answer to login server via char-server + chrif_char_ask_name(sd->status.account_id, character, 3, 0, 0, 0, 0, 0, 0); // type: 3 - unblock + clif_displaymessage(fd, msg_table[88]); // Character name sends to char-server to ask it. + } + + return 0; +} + +/*========================================== + * charunban command (usage: charunban <player_name>) + *------------------------------------------ + */ +int atcommand_char_unban( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char character[100]; + + memset(character, '\0', sizeof(character)); + + if (!message || !*message || sscanf(message, "%99[^\n]", character) < 1) { + clif_displaymessage(fd, "Please, enter a player name (usage: @charunban <player_name>)."); + return -1; + } + + // check player name + if (strlen(character) < 4) { + clif_displaymessage(fd, msg_table[86]); // Sorry, but a player name have at least 4 characters. + return -1; + } else if (strlen(character) > 23) { + clif_displaymessage(fd, msg_table[87]); // Sorry, but a player name have 23 characters maximum. + return -1; + } else { + // send answer to login server via char-server + chrif_char_ask_name(sd->status.account_id, character, 4, 0, 0, 0, 0, 0, 0); // type: 4 - unban + clif_displaymessage(fd, msg_table[88]); // Character name sends to char-server to ask it. + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_character_save( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char map_name[100]; + char character[100]; + struct map_session_data* pl_sd; + int x = 0, y = 0; + int m; + + memset(map_name, '\0', sizeof(map_name)); + memset(character, '\0', sizeof(character)); + + if (!message || !*message || sscanf(message, "%99s %d %d %99[^\n]", map_name, &x, &y, character) < 4 || x < 0 || y < 0) { + clif_displaymessage(fd, "Please, enter a valid save point and a player name (usage: @charsave <map> <x> <y> <charname>)."); + return -1; + } + + if (strstr(map_name, ".gat") == NULL && strstr(map_name, ".afm") == NULL && strlen(map_name) < 13) // 16 - 4 (.gat) + strcat(map_name, ".gat"); + + if ((pl_sd = map_nick2sd(character)) != NULL) { + if (pc_isGM(sd) >= pc_isGM(pl_sd)) { // you can change save point only to lower or same gm level + m = map_mapname2mapid(map_name); + if (m < 0) { + clif_displaymessage(fd, msg_table[1]); // Map not found. + return -1; + } else { + if (m >= 0 && map[m].flag.nowarpto && battle_config.any_warp_GM_min_level > pc_isGM(sd)) { + clif_displaymessage(fd, "You are not authorised to set this map as a save map."); + return -1; + } + pc_setsavepoint(pl_sd, map_name, x, y); + clif_displaymessage(fd, msg_table[57]); // Character's respawn point changed. + } + } else { + clif_displaymessage(fd, msg_table[81]); // Your GM level don't authorise you to do this action on this player. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[3]); // Character not found. + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_night( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + struct map_session_data *pl_sd; + int i; + + if (night_flag != 1) { + night_flag = 1; // 0=day, 1=night [Yor] + for(i = 0; i < fd_max; i++) { + if (session[i] && (pl_sd = session[i]->session_data) && pl_sd->state.auth) { + pl_sd->opt2 |= STATE_BLIND; + clif_changeoption(&pl_sd->bl); + clif_displaymessage(pl_sd->fd, msg_table[59]); // Night has fallen. + } + } + } else { + clif_displaymessage(fd, msg_table[89]); // Sorry, it's already the night. Impossible to execute the command. + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_day( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + struct map_session_data *pl_sd; + int i; + + if (night_flag != 0) { + night_flag = 0; // 0=day, 1=night [Yor] + for(i = 0; i < fd_max; i++) { + if (session[i] && (pl_sd = session[i]->session_data) && pl_sd->state.auth) { + pl_sd->opt2 &= ~STATE_BLIND; + clif_changeoption(&pl_sd->bl); + clif_displaymessage(pl_sd->fd, msg_table[60]); // Day has arrived. + } + } + } else { + clif_displaymessage(fd, msg_table[90]); // Sorry, it's already the day. Impossible to execute the command. + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_doom( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + struct map_session_data *pl_sd; + int i; + + for(i = 0; i < fd_max; i++) { + if (session[i] && (pl_sd = session[i]->session_data) && pl_sd->state.auth && i != fd && + pc_isGM(sd) >= pc_isGM(pl_sd)) { // you can doom only lower or same gm level + pc_damage(NULL, pl_sd, pl_sd->status.hp + 1); + clif_displaymessage(pl_sd->fd, msg_table[61]); // The holy messenger has given judgement. + } + } + clif_displaymessage(fd, msg_table[62]); // Judgement was made. + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_doommap( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + struct map_session_data *pl_sd; + int i; + + for (i = 0; i < fd_max; i++) { + if (session[i] && (pl_sd = session[i]->session_data) && pl_sd->state.auth && i != fd && sd->bl.m == pl_sd->bl.m && + pc_isGM(sd) >= pc_isGM(pl_sd)) { // you can doom only lower or same gm level + pc_damage(NULL, pl_sd, pl_sd->status.hp + 1); + clif_displaymessage(pl_sd->fd, msg_table[61]); // The holy messenger has given judgement. + } + } + clif_displaymessage(fd, msg_table[62]); // Judgement was made. + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +static void atcommand_raise_sub(struct map_session_data* sd) +{ + if (sd && sd->state.auth && pc_isdead(sd)) { + sd->status.hp = sd->status.max_hp; + sd->status.sp = sd->status.max_sp; + pc_setstand(sd); + clif_updatestatus(sd, SP_HP); + clif_updatestatus(sd, SP_SP); + clif_resurrection(&sd->bl, 1); + clif_displaymessage(sd->fd, msg_table[63]); // Mercy has been shown. + } +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_raise( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + int i; + + for (i = 0; i < fd_max; i++) { + if (session[i]) + atcommand_raise_sub(session[i]->session_data); + } + clif_displaymessage(fd, msg_table[64]); // Mercy has been granted. + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_raisemap( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + struct map_session_data *pl_sd; + int i; + + for (i = 0; i < fd_max; i++) { + if (session[i] && (pl_sd = session[i]->session_data) && pl_sd->state.auth && sd->bl.m == pl_sd->bl.m) + atcommand_raise_sub(pl_sd); + } + clif_displaymessage(fd, msg_table[64]); // Mercy has been granted. + + return 0; +} + +/*========================================== + * atcommand_character_baselevel @charbaselvlで対象キャラのレベルを上げる + *------------------------------------------ +*/ +int atcommand_character_baselevel( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + struct map_session_data *pl_sd; + char character[100]; + int level = 0, i; + + memset(character, '\0', sizeof(character)); + + if (!message || !*message || sscanf(message, "%d %99[^\n]", &level, character) < 2 || level == 0) { + clif_displaymessage(fd, "Please, enter a level adjustement and a player name (usage: @charbaselvl <#> <nickname>)."); + return -1; + } + + if ((pl_sd = map_nick2sd(character)) != NULL) { + if (pc_isGM(sd) >= pc_isGM(pl_sd)) { // you can change base level only lower or same gm level + + if (level > 0) { + if (pl_sd->status.base_level == battle_config.maximum_level) { // check for max level by Valaris + clif_displaymessage(fd, msg_table[91]); // Character's base level can't go any higher. + return 0; + } // End Addition + if (level > battle_config.maximum_level || level > (battle_config.maximum_level - pl_sd->status.base_level)) // fix positiv overflow + level = battle_config.maximum_level - pl_sd->status.base_level; + for (i = 1; i <= level; i++) + pl_sd->status.status_point += (pl_sd->status.base_level + i + 14) / 5; + pl_sd->status.base_level += level; + clif_updatestatus(pl_sd, SP_BASELEVEL); + clif_updatestatus(pl_sd, SP_NEXTBASEEXP); + clif_updatestatus(pl_sd, SP_STATUSPOINT); + pc_calcstatus(pl_sd, 0); + pc_heal(pl_sd, pl_sd->status.max_hp, pl_sd->status.max_sp); + clif_misceffect(&pl_sd->bl, 0); + clif_displaymessage(fd, msg_table[65]); // Character's base level raised. + } else { + if (pl_sd->status.base_level == 1) { + clif_displaymessage(fd, msg_table[193]); // Character's base level can't go any lower. + return -1; + } + if (level < -battle_config.maximum_level || level < (1 - pl_sd->status.base_level)) // fix negativ overflow + level = 1 - pl_sd->status.base_level; + if (pl_sd->status.status_point > 0) { + for (i = 0; i > level; i--) + pl_sd->status.status_point -= (pl_sd->status.base_level + i + 14) / 5; + if (pl_sd->status.status_point < 0) + pl_sd->status.status_point = 0; + clif_updatestatus(pl_sd, SP_STATUSPOINT); + } // to add: remove status points from stats + pl_sd->status.base_level += level; + clif_updatestatus(pl_sd, SP_BASELEVEL); + clif_updatestatus(pl_sd, SP_NEXTBASEEXP); + pc_calcstatus(pl_sd, 0); + clif_displaymessage(fd, msg_table[66]); // Character's base level lowered. + } + } else { + clif_displaymessage(fd, msg_table[81]); // Your GM level don't authorise you to do this action on this player. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[3]); // Character not found. + return -1; + } + + return 0; //正常終了 +} + +/*========================================== + * atcommand_character_joblevel @charjoblvlで対象キャラのJobレベルを上げる + *------------------------------------------ + */ +int atcommand_character_joblevel( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + struct map_session_data *pl_sd; + char character[100]; + int max_level = 50, level = 0; + //転生や養子の場合の元の職業を算出する + struct pc_base_job pl_s_class; + + memset(character, '\0', sizeof(character)); + + if (!message || !*message || sscanf(message, "%d %99[^\n]", &level, character) < 2 || level == 0) { + clif_displaymessage(fd, "Please, enter a level adjustement and a player name (usage: @charjlvl <#> <nickname>)."); + return -1; + } + + if ((pl_sd = map_nick2sd(character)) != NULL) { + pl_s_class = pc_calc_base_job(pl_sd->status.class); + if (pc_isGM(sd) >= pc_isGM(pl_sd)) { // you can change job level only lower or same gm level + if (pl_s_class.job == 0) + max_level -= 40; + if ((pl_s_class.job == 23) || (pl_s_class.upper == 1 && pl_s_class.type == 2)) //スパノビと転生職はJobレベルの最高が70 + max_level += 20; + + if (level > 0) { + if (pl_sd->status.job_level == max_level) { + clif_displaymessage(fd, msg_table[67]); // Character's job level can't go any higher. + return -1; + } + if (pl_sd->status.job_level + level > max_level) + level = max_level - pl_sd->status.job_level; + pl_sd->status.job_level += level; + clif_updatestatus(pl_sd, SP_JOBLEVEL); + clif_updatestatus(pl_sd, SP_NEXTJOBEXP); + pl_sd->status.skill_point += level; + clif_updatestatus(pl_sd, SP_SKILLPOINT); + pc_calcstatus(pl_sd, 0); + clif_misceffect(&pl_sd->bl, 1); + clif_displaymessage(fd, msg_table[68]); // character's job level raised. + } else { + if (pl_sd->status.job_level == 1) { + clif_displaymessage(fd, msg_table[194]); // Character's job level can't go any lower. + return -1; + } + if (pl_sd->status.job_level + level < 1) + level = 1 - pl_sd->status.job_level; + pl_sd->status.job_level += level; + clif_updatestatus(pl_sd, SP_JOBLEVEL); + clif_updatestatus(pl_sd, SP_NEXTJOBEXP); + if (pl_sd->status.skill_point > 0) { + pl_sd->status.skill_point += level; + if (pl_sd->status.skill_point < 0) + pl_sd->status.skill_point = 0; + clif_updatestatus(pl_sd, SP_SKILLPOINT); + } // to add: remove status points from skills + pc_calcstatus(pl_sd, 0); + clif_displaymessage(fd, msg_table[69]); // Character's job level lowered. + } + } else { + clif_displaymessage(fd, msg_table[81]); // Your GM level don't authorise you to do this action on this player. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[3]); // Character not found. + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_kick( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + struct map_session_data *pl_sd; + char character[100]; + + memset(character, '\0', sizeof(character)); + + if (!message || !*message || sscanf(message, "%99[^\n]", character) < 1) { + clif_displaymessage(fd, "Please, enter a player name (usage: @kick <charname>)."); + return -1; + } + + if ((pl_sd = map_nick2sd(character)) != NULL) { + if (pc_isGM(sd) >= pc_isGM(pl_sd)) // you can kick only lower or same gm level + clif_GM_kick(sd, pl_sd, 1); + else { + clif_displaymessage(fd, msg_table[81]); // Your GM level don't authorise you to do this action on this player. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[3]); // Character not found. + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_kickall( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + struct map_session_data *pl_sd; + int i; + + for (i = 0; i < fd_max; i++) { + if (session[i] && (pl_sd = session[i]->session_data) && pl_sd->state.auth && + pc_isGM(sd) >= pc_isGM(pl_sd)) { // you can kick only lower or same gm level + if (sd->status.account_id != pl_sd->status.account_id) + clif_GM_kick(sd, pl_sd, 0); + } + } + + clif_displaymessage(fd, msg_table[195]); // All players have been kicked! + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_allskill( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + pc_allskillup(sd); // all skills + sd->status.skill_point = 0; // 0 skill points + clif_updatestatus(sd, SP_SKILLPOINT); // update + clif_displaymessage(fd, msg_table[76]); // You have received all skills. + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_questskill( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + int skill_id; + + if (!message || !*message || (skill_id = atoi(message)) < 0) { + clif_displaymessage(fd, "Please, enter a quest skill number (usage: @questskill <#:0+>)."); + return -1; + } + + if (skill_id >= 0 && skill_id < MAX_SKILL_DB) { + if (skill_get_inf2(skill_id) & 0x01) { + if (pc_checkskill(sd, skill_id) == 0) { + pc_skill(sd, skill_id, 1, 0); + clif_displaymessage(fd, msg_table[70]); // You have learned the skill. + } else { + clif_displaymessage(fd, msg_table[196]); // You already have this quest skill. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[197]); // This skill number doesn't exist or isn't a quest skill. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[198]); // This skill number doesn't exist. + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_charquestskill( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char character[100]; + struct map_session_data *pl_sd; + int skill_id = 0; + + memset(character, '\0', sizeof(character)); + + if (!message || !*message || sscanf(message, "%d %99[^\n]", &skill_id, character) < 2 || skill_id < 0) { + clif_displaymessage(fd, "Please, enter a quest skill number and a player name (usage: @charquestskill <#:0+> <char_name>)."); + return -1; + } + + if (skill_id >= 0 && skill_id < MAX_SKILL_DB) { + if (skill_get_inf2(skill_id) & 0x01) { + if ((pl_sd = map_nick2sd(character)) != NULL) { + if (pc_checkskill(pl_sd, skill_id) == 0) { + pc_skill(pl_sd, skill_id, 1, 0); + clif_displaymessage(fd, msg_table[199]); // This player has learned the skill. + } else { + clif_displaymessage(fd, msg_table[200]); // This player already has this quest skill. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[3]); // Character not found. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[197]); // This skill number doesn't exist or isn't a quest skill. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[198]); // This skill number doesn't exist. + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_lostskill( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + int skill_id; + + if (!message || !*message || (skill_id = atoi(message)) < 0) { + clif_displaymessage(fd, "Please, enter a quest skill number (usage: @lostskill <#:0+>)."); + return -1; + } + + if (skill_id >= 0 && skill_id < MAX_SKILL) { + if (skill_get_inf2(skill_id) & 0x01) { + if (pc_checkskill(sd, skill_id) > 0) { + sd->status.skill[skill_id].lv = 0; + sd->status.skill[skill_id].flag = 0; + clif_skillinfoblock(sd); + clif_displaymessage(fd, msg_table[71]); // You have forgotten the skill. + } else { + clif_displaymessage(fd, msg_table[201]); // You don't have this quest skill. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[197]); // This skill number doesn't exist or isn't a quest skill. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[198]); // This skill number doesn't exist. + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_charlostskill( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char character[100]; + struct map_session_data *pl_sd; + int skill_id = 0; + + memset(character, '\0', sizeof(character)); + + if (!message || !*message || sscanf(message, "%d %99[^\n]", &skill_id, character) < 2 || skill_id < 0) { + clif_displaymessage(fd, "Please, enter a quest skill number and a player name (usage: @charlostskill <#:0+> <char_name>)."); + return -1; + } + + if (skill_id >= 0 && skill_id < MAX_SKILL) { + if (skill_get_inf2(skill_id) & 0x01) { + if ((pl_sd = map_nick2sd(character)) != NULL) { + if (pc_checkskill(pl_sd, skill_id) > 0) { + pl_sd->status.skill[skill_id].lv = 0; + pl_sd->status.skill[skill_id].flag = 0; + clif_skillinfoblock(pl_sd); + clif_displaymessage(fd, msg_table[202]); // This player has forgotten the skill. + } else { + clif_displaymessage(fd, msg_table[203]); // This player doesn't have this quest skill. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[3]); // Character not found. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[197]); // This skill number doesn't exist or isn't a quest skill. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[198]); // This skill number doesn't exist. + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_spiritball( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + int number; + + if (!message || !*message || (number = atoi(message)) < 0) { + clif_displaymessage(fd, "Please, enter a spirit ball number (usage: @spiritball <number: 0-1000>)."); + return -1; + } + + // set max number to avoid server/client crash (500 create big balls of several balls: no visial difference with more) + if (number > 500) + number = 500; + + if (number >= 0 && number <= 0x7FFF) { + if (sd->spiritball != number || number > 499) { + if (sd->spiritball > 0) + pc_delspiritball(sd, sd->spiritball, 1); + sd->spiritball = number; + clif_spiritball(sd); + // no message, player can look the difference + if (number > 1000) + clif_displaymessage(fd, msg_table[204]); // WARNING: more than 1000 spiritballs can CRASH your server and/or client! + } else { + clif_displaymessage(fd, msg_table[205]); // You already have this number of spiritballs. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[37]); // An invalid number was specified. + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_party( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char party[100]; + + memset(party, '\0', sizeof(party)); + + if (!message || !*message || sscanf(message, "%99[^\n]", party) < 1) { + clif_displaymessage(fd, "Please, enter a party name (usage: @party <party_name>)."); + return -1; + } + + party_create(sd, party); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_guild( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char guild[100]; + int prev; + + memset(guild, '\0', sizeof(guild)); + + if (!message || !*message || sscanf(message, "%99[^\n]", guild) < 1) { + clif_displaymessage(fd, "Please, enter a guild name (usage: @guild <guild_name>)."); + return -1; + } + + prev = battle_config.guild_emperium_check; + battle_config.guild_emperium_check = 0; + guild_create(sd, guild); + battle_config.guild_emperium_check = prev; + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_agitstart( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + if (agit_flag == 1) { + clif_displaymessage(fd, msg_table[73]); // Already it has started siege warfare. + return -1; + } + + agit_flag = 1; + guild_agit_start(); + clif_displaymessage(fd, msg_table[72]); // Guild siege warfare start! + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_agitend( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + if (agit_flag == 0) { + clif_displaymessage(fd, msg_table[75]); // Siege warfare hasn't started yet. + return -1; + } + + agit_flag = 0; + guild_agit_end(); + clif_displaymessage(fd, msg_table[74]); // Guild siege warfare end! + + return 0; +} + +/*========================================== + * @mapexitでマップサーバーを終了させる + *------------------------------------------ + */ +int atcommand_mapexit( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + struct map_session_data *pl_sd; + int i; + + for (i = 0; i < fd_max; i++) { + if (session[i] && (pl_sd = session[i]->session_data) && pl_sd->state.auth) { + if (sd->status.account_id != pl_sd->status.account_id) + clif_GM_kick(sd, pl_sd, 0); + } + } + clif_GM_kick(sd, sd, 0); + + runflag = 0; + + return 0; +} + +/*========================================== + * idsearch <part_of_name>: revrited by [Yor] + *------------------------------------------ + */ +int atcommand_idsearch( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char item_name[100]; + char output[200]; + int i, match; + struct item_data *item; + + memset(item_name, '\0', sizeof(item_name)); + memset(output, '\0', sizeof(output)); + + if (!message || !*message || sscanf(message, "%99s", item_name) < 0) { + clif_displaymessage(fd, "Please, enter a part of item name (usage: @idsearch <part_of_item_name>)."); + return -1; + } + + sprintf(output, msg_table[77], item_name); // The reference result of '%s' (name: id): + clif_displaymessage(fd, output); + match = 0; + for(i = 0; i < 20000; i++) { + if ((item = itemdb_exists(i)) != NULL && strstr(item->jname, item_name) != NULL) { + match++; + sprintf(output, msg_table[78], item->jname, item->nameid); // %s: %d + clif_displaymessage(fd, output); + } + } + sprintf(output, msg_table[79], match); // It is %d affair above. + clif_displaymessage(fd, output); + + return 0; +} + +/*========================================== + * Character Skill Reset + *------------------------------------------ + */ +int atcommand_charskreset( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char character[100]; + char output[200]; + struct map_session_data *pl_sd; + + memset(character, '\0', sizeof(character)); + memset(output, '\0', sizeof(output)); + + if (!message || !*message || sscanf(message, "%99[^\n]", character) < 1) { + clif_displaymessage(fd, "Please, enter a player name (usage: @charskreset <charname>)."); + return -1; + } + + if ((pl_sd = map_nick2sd(character)) != NULL) { + if (pc_isGM(sd) >= pc_isGM(pl_sd)) { // you can reset skill points only lower or same gm level + pc_resetskill(pl_sd); + sprintf(output, msg_table[206], character); // '%s' skill points reseted! + clif_displaymessage(fd, output); + } else { + clif_displaymessage(fd, msg_table[81]); // Your GM level don't authorise you to do this action on this player. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[3]); // Character not found. + return -1; + } + + return 0; +} + +/*========================================== + * Character Stat Reset + *------------------------------------------ + */ +int atcommand_charstreset( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char character[100]; + char output[200]; + struct map_session_data *pl_sd; + + memset(character, '\0', sizeof(character)); + memset(output, '\0', sizeof(output)); + + if (!message || !*message || sscanf(message, "%99[^\n]", character) < 1) { + clif_displaymessage(fd, "Please, enter a player name (usage: @charstreset <charname>)."); + return -1; + } + + if ((pl_sd = map_nick2sd(character)) != NULL) { + if (pc_isGM(sd) >= pc_isGM(pl_sd)) { // you can reset stats points only lower or same gm level + pc_resetstate(pl_sd); + sprintf(output, msg_table[207], character); // '%s' stats points reseted! + clif_displaymessage(fd, output); + } else { + clif_displaymessage(fd, msg_table[81]); // Your GM level don't authorise you to do this action on this player. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[3]); // Character not found. + return -1; + } + + return 0; +} + +/*========================================== + * Character Reset + *------------------------------------------ + */ +int atcommand_charreset( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char character[100]; + char output[200]; + struct map_session_data *pl_sd; + + memset(character, '\0', sizeof(character)); + memset(output, '\0', sizeof(output)); + + if (!message || !*message || sscanf(message, "%99[^\n]", character) < 1) { + clif_displaymessage(fd, "Please, enter a player name (usage: @charreset <charname>)."); + return -1; + } + + if ((pl_sd = map_nick2sd(character)) != NULL) { + if (pc_isGM(sd) >= pc_isGM(pl_sd)) { // you can reset a character only for lower or same GM level + pc_resetstate(pl_sd); + pc_resetskill(pl_sd); + sprintf(output, msg_table[208], character); // '%s' skill and stats points reseted! + clif_displaymessage(fd, output); + } else { + clif_displaymessage(fd, msg_table[81]); // Your GM level don't authorise you to do this action on this player. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[3]); // Character not found. + return -1; + } + + return 0; +} + +/*========================================== + * Character Model by chbrules + *------------------------------------------ + */ +int atcommand_charmodel( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + int hair_style = 0, hair_color = 0, cloth_color = 0; + struct map_session_data *pl_sd; + char character[100]; + char output[200]; + + memset(character, '\0', sizeof(character)); + memset(output, '\0', sizeof(output)); + + if (!message || !*message || sscanf(message, "%d %d %d %99[^\n]", &hair_style, &hair_color, &cloth_color, character) < 4 || hair_style < 0 || hair_color < 0 || cloth_color < 0) { + sprintf(output, "Please, enter a valid model and a player name (usage: @charmodel <hair ID: %d-%d> <hair color: %d-%d> <clothes color: %d-%d> <name>).", + MIN_HAIR_STYLE, MAX_HAIR_STYLE, MIN_HAIR_COLOR, MAX_HAIR_COLOR, MIN_CLOTH_COLOR, MAX_CLOTH_COLOR); + clif_displaymessage(fd, output); + return -1; + } + + if ((pl_sd = map_nick2sd(character)) != NULL) { + if (hair_style >= MIN_HAIR_STYLE && hair_style <= MAX_HAIR_STYLE && + hair_color >= MIN_HAIR_COLOR && hair_color <= MAX_HAIR_COLOR && + cloth_color >= MIN_CLOTH_COLOR && cloth_color <= MAX_CLOTH_COLOR) { + + if (cloth_color != 0 && + pl_sd->status.sex == 1 && + (pl_sd->status.class == 12 || pl_sd->status.class == 17)) { + clif_displaymessage(fd, msg_table[35]); // You can't use this command with this class. + return -1; + } else { + pc_changelook(pl_sd, LOOK_HAIR, hair_style); + pc_changelook(pl_sd, LOOK_HAIR_COLOR, hair_color); + pc_changelook(pl_sd, LOOK_CLOTHES_COLOR, cloth_color); + clif_displaymessage(fd, msg_table[36]); // Appearence changed. + } + } else { + clif_displaymessage(fd, msg_table[37]); // An invalid number was specified. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[3]); // Character not found. + return -1; + } + + return 0; +} + +/*========================================== + * Character Skill Point (Rewritten by [Yor]) + *------------------------------------------ + */ +int atcommand_charskpoint( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + struct map_session_data *pl_sd; + char character[100]; + int new_skill_point; + int point = 0; + + memset(character, '\0', sizeof(character)); + + if (!message || !*message || sscanf(message, "%d %99[^\n]", &point, character) < 2 || point == 0) { + clif_displaymessage(fd, "Please, enter a number and a player name (usage: @charskpoint <amount> <name>)."); + return -1; + } + + if ((pl_sd = map_nick2sd(character)) != NULL) { + new_skill_point = (int)pl_sd->status.skill_point + point; + if (point > 0 && (point > 0x7FFF || new_skill_point > 0x7FFF)) // fix positiv overflow + new_skill_point = 0x7FFF; + else if (point < 0 && (point < -0x7FFF || new_skill_point < 0)) // fix negativ overflow + new_skill_point = 0; + if (new_skill_point != (int)pl_sd->status.skill_point) { + pl_sd->status.skill_point = new_skill_point; + clif_updatestatus(pl_sd, SP_SKILLPOINT); + clif_displaymessage(fd, msg_table[209]); // Character's number of skill points changed! + } else { + if (point < 0) + clif_displaymessage(fd, msg_table[41]); // Impossible to decrease the number/value. + else + clif_displaymessage(fd, msg_table[149]); // Impossible to increase the number/value. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[3]); // Character not found. + return -1; + } + + return 0; +} + +/*========================================== + * Character Status Point (rewritten by [Yor]) + *------------------------------------------ + */ +int atcommand_charstpoint( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + struct map_session_data *pl_sd; + char character[100]; + int new_status_point; + int point = 0; + + memset(character, '\0', sizeof(character)); + + if (!message || !*message || sscanf(message, "%d %99[^\n]", &point, character) < 2 || point == 0) { + clif_displaymessage(fd, "Please, enter a number and a player name (usage: @charstpoint <amount> <name>)."); + return -1; + } + + if ((pl_sd = map_nick2sd(character)) != NULL) { + new_status_point = (int)pl_sd->status.status_point + point; + if (point > 0 && (point > 0x7FFF || new_status_point > 0x7FFF)) // fix positiv overflow + new_status_point = 0x7FFF; + else if (point < 0 && (point < -0x7FFF || new_status_point < 0)) // fix negativ overflow + new_status_point = 0; + if (new_status_point != (int)pl_sd->status.status_point) { + pl_sd->status.status_point = new_status_point; + clif_updatestatus(pl_sd, SP_STATUSPOINT); + clif_displaymessage(fd, msg_table[210]); // Character's number of status points changed! + } else { + if (point < 0) + clif_displaymessage(fd, msg_table[41]); // Impossible to decrease the number/value. + else + clif_displaymessage(fd, msg_table[149]); // Impossible to increase the number/value. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[3]); // Character not found. + return -1; + } + + return 0; +} + +/*========================================== + * Character Zeny Point (Rewritten by [Yor]) + *------------------------------------------ + */ +int atcommand_charzeny( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + struct map_session_data *pl_sd; + char character[100]; + int zeny = 0, new_zeny; + + memset(character, '\0', sizeof(character)); + + if (!message || !*message || sscanf(message, "%d %99[^\n]", &zeny, character) < 2 || zeny == 0) { + clif_displaymessage(fd, "Please, enter a number and a player name (usage: @charzeny <zeny> <name>)."); + return -1; + } + + if ((pl_sd = map_nick2sd(character)) != NULL) { + new_zeny = pl_sd->status.zeny + zeny; + if (zeny > 0 && (zeny > MAX_ZENY || new_zeny > MAX_ZENY)) // fix positiv overflow + new_zeny = MAX_ZENY; + else if (zeny < 0 && (zeny < -MAX_ZENY || new_zeny < 0)) // fix negativ overflow + new_zeny = 0; + if (new_zeny != pl_sd->status.zeny) { + pl_sd->status.zeny = new_zeny; + clif_updatestatus(pl_sd, SP_ZENY); + clif_displaymessage(fd, msg_table[211]); // Character's number of zenys changed! + } else { + if (zeny < 0) + clif_displaymessage(fd, msg_table[41]); // Impossible to decrease the number/value. + else + clif_displaymessage(fd, msg_table[149]); // Impossible to increase the number/value. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[3]); // Character not found. + return -1; + } + + return 0; +} + +/*========================================== + * Recall All Characters Online To Your Location + *------------------------------------------ + */ +int atcommand_recallall( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + struct map_session_data *pl_sd; + int i; + int count; + char output[200]; + + memset(output, '\0', sizeof(output)); + + if (sd->bl.m >= 0 && map[sd->bl.m].flag.nowarpto && battle_config.any_warp_GM_min_level > pc_isGM(sd)) { + clif_displaymessage(fd, "You are not authorised to warp somenone to your actual map."); + return -1; + } + + count = 0; + for (i = 0; i < fd_max; i++) { + if (session[i] && (pl_sd = session[i]->session_data) && pl_sd->state.auth && sd->status.account_id != pl_sd->status.account_id && + pc_isGM(sd) >= pc_isGM(pl_sd)) { // you can recall only lower or same level + if (pl_sd->bl.m >= 0 && map[pl_sd->bl.m].flag.nowarp && battle_config.any_warp_GM_min_level > pc_isGM(sd)) + count++; + else + pc_setpos(pl_sd, sd->mapname, sd->bl.x, sd->bl.y, 2); + } + } + + clif_displaymessage(fd, msg_table[92]); // All characters recalled! + if (count) { + sprintf(output, "Because you are not authorised to warp from some maps, %d player(s) have not been recalled.", count); + clif_displaymessage(fd, output); + } + + return 0; +} + +/*========================================== + * Recall online characters of a guild to your location + *------------------------------------------ + */ +int atcommand_guildrecall( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + struct map_session_data *pl_sd; + int i; + char guild_name[100]; + char output[200]; + struct guild *g; + int count; + + memset(guild_name, '\0', sizeof(guild_name)); + memset(output, '\0', sizeof(output)); + + if (!message || !*message || sscanf(message, "%99[^\n]", guild_name) < 1) { + clif_displaymessage(fd, "Please, enter a guild name/id (usage: @guildrecall <guild_name/id>)."); + return -1; + } + + if (sd->bl.m >= 0 && map[sd->bl.m].flag.nowarpto && battle_config.any_warp_GM_min_level > pc_isGM(sd)) { + clif_displaymessage(fd, "You are not authorised to warp somenone to your actual map."); + return -1; + } + + if ((g = guild_searchname(guild_name)) != NULL || // name first to avoid error when name begin with a number + (g = guild_search(atoi(message))) != NULL) { + count = 0; + for (i = 0; i < fd_max; i++) { + if (session[i] && (pl_sd = session[i]->session_data) && pl_sd->state.auth && + sd->status.account_id != pl_sd->status.account_id && + pl_sd->status.guild_id == g->guild_id) { + if (pl_sd->bl.m >= 0 && map[pl_sd->bl.m].flag.nowarp && battle_config.any_warp_GM_min_level > pc_isGM(sd)) + count++; + else + pc_setpos(pl_sd, sd->mapname, sd->bl.x, sd->bl.y, 2); + } + } + sprintf(output, msg_table[93], g->name); // All online characters of the %s guild are near you. + clif_displaymessage(fd, output); + if (count) { + sprintf(output, "Because you are not authorised to warp from some maps, %d player(s) have not been recalled.", count); + clif_displaymessage(fd, output); + } + } else { + clif_displaymessage(fd, msg_table[94]); // Incorrect name/ID, or no one from the guild is online. + return -1; + } + + return 0; +} + +/*========================================== + * Recall online characters of a party to your location + *------------------------------------------ + */ +int atcommand_partyrecall( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + int i; + struct map_session_data *pl_sd; + char party_name[100]; + char output[200]; + struct party *p; + int count; + + memset(party_name, '\0', sizeof(party_name)); + memset(output, '\0', sizeof(output)); + + if (!message || !*message || sscanf(message, "%99[^\n]", party_name) < 1) { + clif_displaymessage(fd, "Please, enter a party name/id (usage: @partyrecall <party_name/id>)."); + return -1; + } + + if (sd->bl.m >= 0 && map[sd->bl.m].flag.nowarpto && battle_config.any_warp_GM_min_level > pc_isGM(sd)) { + clif_displaymessage(fd, "You are not authorised to warp somenone to your actual map."); + return -1; + } + + if ((p = party_searchname(party_name)) != NULL || // name first to avoid error when name begin with a number + (p = party_search(atoi(message))) != NULL) { + count = 0; + for (i = 0; i < fd_max; i++) { + if (session[i] && (pl_sd = session[i]->session_data) && pl_sd->state.auth && + sd->status.account_id != pl_sd->status.account_id && + pl_sd->status.party_id == p->party_id) { + if (pl_sd->bl.m >= 0 && map[pl_sd->bl.m].flag.nowarp && battle_config.any_warp_GM_min_level > pc_isGM(sd)) + count++; + else + pc_setpos(pl_sd, sd->mapname, sd->bl.x, sd->bl.y, 2); + } + } + sprintf(output, msg_table[95], p->name); // All online characters of the %s party are near you. + clif_displaymessage(fd, output); + if (count) { + sprintf(output, "Because you are not authorised to warp from some maps, %d player(s) have not been recalled.", count); + clif_displaymessage(fd, output); + } + } else { + clif_displaymessage(fd, msg_table[96]); // Incorrect name or ID, or no one from the party is online. + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_reloaditemdb( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + itemdb_reload(); + clif_displaymessage(fd, msg_table[97]); // Item database reloaded. + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_reloadmobdb( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + mob_reload(); + clif_displaymessage(fd, msg_table[98]); // Monster database reloaded. + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_reloadskilldb( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + skill_reload(); + clif_displaymessage(fd, msg_table[99]); // Skill database reloaded. + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +#ifndef TXT_ONLY +int atcommand_rehash( +#else /* TXT_ONLY */ +int atcommand_reloadscript( +#endif /* TXT_ONLY */ + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ +#ifndef TXT_ONLY + atcommand_broadcast( fd, sd, "@broadcast", "eAthena SQL Server is Rehashing..." ); + atcommand_broadcast( fd, sd, "@broadcast", "You will feel a bit of lag at this point !" ); + + rehash( fd, sd ); + + atcommand_broadcast( fd, sd, "@broadcast", "Reloading NPCs..." ); +#endif /* not TXT_ONLY */ + do_init_npc(); + do_init_script(); + + npc_event_do_oninit(); + + clif_displaymessage(fd, msg_table[100]); // Scripts reloaded. + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_reloadgmdb( // by [Yor] + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + chrif_reloadGMdb(); + + clif_displaymessage(fd, msg_table[101]); // Login-server asked to reload GM accounts and their level. + + return 0; +} + +/*========================================== + * @mapinfo <map name> [0-3] by MC_Cameri + * => Shows information about the map [map name] + * 0 = no additional information + * 1 = Show users in that map and their location + * 2 = Shows NPCs in that map + * 3 = Shows the shops/chats in that map (not implemented) + *------------------------------------------ + */ +int atcommand_mapinfo( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + struct map_session_data *pl_sd; + struct npc_data *nd = NULL; + struct chat_data *cd = NULL; + char output[200], map_name[100]; + char direction[12]; + int m_id, i, chat_num, list = 0; + + memset(output, '\0', sizeof(output)); + memset(map_name, '\0', sizeof(map_name)); + memset(direction, '\0', sizeof(direction)); + + sscanf(message, "%d %99[^\n]", &list, map_name); + + if (list < 0 || list > 3) { + clif_displaymessage(fd, "Please, enter at least a valid list number (usage: @mapinfo <0-3> [map])."); + return -1; + } + + if (map_name[0] == '\0') + strcpy(map_name, sd->mapname); + if (strstr(map_name, ".gat") == NULL && strstr(map_name, ".afm") == NULL && strlen(map_name) < 13) // 16 - 4 (.gat) + strcat(map_name, ".gat"); + + if ((m_id = map_mapname2mapid(map_name)) < 0) { + clif_displaymessage(fd, msg_table[1]); // Map not found. + return -1; + } + + clif_displaymessage(fd, "------ Map Info ------"); + sprintf(output, "Map Name: %s", map_name); + clif_displaymessage(fd, output); + sprintf(output, "Players In Map: %d", map[m_id].users); + clif_displaymessage(fd, output); + sprintf(output, "NPCs In Map: %d", map[m_id].npc_num); + clif_displaymessage(fd, output); + chat_num = 0; + for (i = 0; i < fd_max; i++) { + if (session[i] && (pl_sd = session[i]->session_data) && pl_sd->state.auth && + (cd = (struct chat_data*)map_id2bl(pl_sd->chatID))) { + chat_num++; + } + } + sprintf(output, "Chats In Map: %d", chat_num); + clif_displaymessage(fd, output); + clif_displaymessage(fd, "------ Map Flags ------"); + sprintf(output, "Player vs Player: %s | No Guild: %s | No Party: %s", + (map[m_id].flag.pvp) ? "True" : "False", + (map[m_id].flag.pvp_noguild) ? "True" : "False", + (map[m_id].flag.pvp_noparty) ? "True" : "False"); + clif_displaymessage(fd, output); + sprintf(output, "Guild vs Guild: %s | No Party: %s", (map[m_id].flag.gvg) ? "True" : "False", (map[m_id].flag.gvg_noparty) ? "True" : "False"); + clif_displaymessage(fd, output); + sprintf(output, "No Dead Branch: %s", (map[m_id].flag.nobranch) ? "True" : "False"); + clif_displaymessage(fd, output); + sprintf(output, "No Memo: %s", (map[m_id].flag.nomemo) ? "True" : "False"); + clif_displaymessage(fd, output); + sprintf(output, "No Penalty: %s", (map[m_id].flag.nopenalty) ? "True" : "False"); + clif_displaymessage(fd, output); + sprintf(output, "No Return: %s", (map[m_id].flag.noreturn) ? "True" : "False"); + clif_displaymessage(fd, output); + sprintf(output, "No Save: %s", (map[m_id].flag.nosave) ? "True" : "False"); + clif_displaymessage(fd, output); + sprintf(output, "No Teleport: %s", (map[m_id].flag.noteleport) ? "True" : "False"); + clif_displaymessage(fd, output); + sprintf(output, "No Monster Teleport: %s", (map[m_id].flag.monster_noteleport) ? "True" : "False"); + clif_displaymessage(fd, output); + sprintf(output, "No Zeny Penalty: %s", (map[m_id].flag.nozenypenalty) ? "True" : "False"); + clif_displaymessage(fd, output); + + switch (list) { + case 0: + // Do nothing. It's list 0, no additional display. + break; + case 1: + clif_displaymessage(fd, "----- Players in Map -----"); + for (i = 0; i < fd_max; i++) { + if (session[i] && (pl_sd = session[i]->session_data) && pl_sd->state.auth && strcmp(pl_sd->mapname, map_name) == 0) { + sprintf(output, "Player '%s' (session #%d) | Location: %d,%d", + pl_sd->status.name, i, pl_sd->bl.x, pl_sd->bl.y); + clif_displaymessage(fd, output); + } + } + break; + case 2: + clif_displaymessage(fd, "----- NPCs in Map -----"); + for (i = 0; i < map[m_id].npc_num;) { + nd = map[m_id].npc[i]; + switch(nd->dir) { + case 0: strcpy(direction, "North"); break; + case 1: strcpy(direction, "North West"); break; + case 2: strcpy(direction, "West"); break; + case 3: strcpy(direction, "South West"); break; + case 4: strcpy(direction, "South"); break; + case 5: strcpy(direction, "South East"); break; + case 6: strcpy(direction, "East"); break; + case 7: strcpy(direction, "North East"); break; + case 9: strcpy(direction, "North"); break; + default: strcpy(direction, "Unknown"); break; + } + sprintf(output, "NPC %d: %s | Direction: %s | Sprite: %d | Location: %d %d", + ++i, nd->name, direction, nd->class, nd->bl.x, nd->bl.y); + clif_displaymessage(fd, output); + } + break; + case 3: + clif_displaymessage(fd, "----- Chats in Map -----"); + for (i = 0; i < fd_max; i++) { + if (session[i] && (pl_sd = session[i]->session_data) && pl_sd->state.auth && + (cd = (struct chat_data*)map_id2bl(pl_sd->chatID)) && + strcmp(pl_sd->mapname, map_name) == 0 && + cd->usersd[0] == pl_sd) { + sprintf(output, "Chat %d: %s | Player: %s | Location: %d %d", + i, cd->title, pl_sd->status.name, cd->bl.x, cd->bl.y); + clif_displaymessage(fd, output); + sprintf(output, " Users: %d/%d | Password: %s | Public: %s", + cd->users, cd->limit, cd->pass, (cd->pub) ? "Yes" : "No"); + clif_displaymessage(fd, output); + } + } + break; + default: // normally impossible to arrive here + clif_displaymessage(fd, "Please, enter at least a valid list number (usage: @mapinfo <0-3> [map])."); + return -1; + break; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_mount_peco( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + if (sd->disguise > 0) { // temporary prevention of crash caused by peco + disguise, will look into a better solution [Valaris] + clif_displaymessage(fd, msg_table[212]); // Cannot mount a Peco while in disguise. + return -1; + } + + if (!pc_isriding(sd)) { // if actually no peco + if (sd->status.class == 7 || sd->status.class == 14 || sd->status.class == 4008 || sd->status.class == 4015) { + if (sd->status.class == 7) + sd->status.class = sd->view_class = 13; + else if (sd->status.class == 14) + sd->status.class = sd->view_class = 21; + else if (sd->status.class == 4008) + sd->status.class = sd->view_class = 4014; + else if (sd->status.class == 4015) + sd->status.class = sd->view_class = 4022; + pc_setoption(sd, sd->status.option | 0x0020); + clif_displaymessage(fd, msg_table[102]); // Mounted Peco. + } else { + clif_displaymessage(fd, msg_table[213]); // You can not mount a peco with your job. + return -1; + } + } else { + if (sd->status.class == 13) + sd->status.class = sd->view_class = 7; + else if (sd->status.class == 21) + sd->status.class = sd->view_class = 14; + else if (sd->status.class == 4014) + sd->status.class = sd->view_class = 4008; + else if (sd->status.class == 4022) + sd->status.class = sd->view_class = 4015; + pc_setoption(sd, sd->status.option & ~0x0020); + clif_displaymessage(fd, msg_table[214]); // Unmounted Peco. + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_char_mount_peco( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char character[100]; + struct map_session_data *pl_sd; + + memset(character, '\0', sizeof(character)); + + if (!message || !*message || sscanf(message, "%99[^\n]", character) < 1) { + clif_displaymessage(fd, "Please, enter a player name (usage: @charmountpeco <char_name>)."); + return -1; + } + + if ((pl_sd = map_nick2sd(character)) != NULL) { + if (pl_sd->disguise > 0) { // temporary prevention of crash caused by peco + disguise, will look into a better solution [Valaris] + clif_displaymessage(fd, msg_table[215]); // This player cannot mount a Peco while in disguise. + return -1; + } + + if (!pc_isriding(pl_sd)) { // if actually no peco + if (pl_sd->status.class == 7 || pl_sd->status.class == 14 || pl_sd->status.class == 4008 || pl_sd->status.class == 4015) { + if (pl_sd->status.class == 7) + pl_sd->status.class = pl_sd->view_class = 13; + else if (pl_sd->status.class == 14) + pl_sd->status.class = pl_sd->view_class = 21; + else if (pl_sd->status.class == 4008) + pl_sd->status.class = pl_sd->view_class = 4014; + else if (pl_sd->status.class == 4015) + pl_sd->status.class = pl_sd->view_class = 4022; + pc_setoption(pl_sd, pl_sd->status.option | 0x0020); + clif_displaymessage(fd, msg_table[216]); // Now, this player mounts a peco. + } else { + clif_displaymessage(fd, msg_table[217]); // This player can not mount a peco with his/her job. + return -1; + } + } else { + if (pl_sd->status.class == 13) + pl_sd->status.class = pl_sd->view_class = 7; + else if (pl_sd->status.class == 21) + pl_sd->status.class = pl_sd->view_class = 14; + else if (pl_sd->status.class == 4014) + pl_sd->status.class = pl_sd->view_class = 4008; + else if (pl_sd->status.class == 4022) + pl_sd->status.class = pl_sd->view_class = 4015; + pc_setoption(pl_sd, pl_sd->status.option & ~0x0020); + clif_displaymessage(fd, msg_table[218]); // Now, this player has not more peco. + } + } else { + clif_displaymessage(fd, msg_table[3]); // Character not found. + return -1; + } + + return 0; +} + +/*========================================== + *Spy Commands by Syrus22 + *------------------------------------------ + */ +int atcommand_guildspy( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char guild_name[100]; + char output[200]; + struct guild *g; + + memset(guild_name, '\0', sizeof(guild_name)); + memset(output, '\0', sizeof(output)); + + if (!message || !*message || sscanf(message, "%99[^\n]", guild_name) < 1) { + clif_displaymessage(fd, "Please, enter a guild name/id (usage: @guildspy <guild_name/id>)."); + return -1; + } + + if ((g = guild_searchname(guild_name)) != NULL || // name first to avoid error when name begin with a number + (g = guild_search(atoi(message))) != NULL) { + if (sd->guildspy == g->guild_id) { + sd->guildspy = 0; + sprintf(output, msg_table[103], g->name); // No longer spying on the %s guild. + clif_displaymessage(fd, output); + } else { + sd->guildspy = g->guild_id; + sprintf(output, msg_table[104], g->name); // Spying on the %s guild. + clif_displaymessage(fd, output); + } + } else { + clif_displaymessage(fd, msg_table[94]); // Incorrect name/ID, or no one from the guild is online. + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_partyspy( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char party_name[100]; + char output[200]; + struct party *p; + + memset(party_name, '\0', sizeof(party_name)); + memset(output, '\0', sizeof(output)); + + if (!message || !*message || sscanf(message, "%99[^\n]", party_name) < 1) { + clif_displaymessage(fd, "Please, enter a party name/id (usage: @partyspy <party_name/id>)."); + return -1; + } + + if ((p = party_searchname(party_name)) != NULL || // name first to avoid error when name begin with a number + (p = party_search(atoi(message))) != NULL) { + if (sd->partyspy == p->party_id) { + sd->partyspy = 0; + sprintf(output, msg_table[105], p->name); // No longer spying on the %s party. + clif_displaymessage(fd, output); + } else { + sd->partyspy = p->party_id; + sprintf(output, msg_table[106], p->name); // Spying on the %s party. + clif_displaymessage(fd, output); + } + } else { + clif_displaymessage(fd, msg_table[96]); // Incorrect name or ID, or no one from the party is online. + return -1; + } + + return 0; +} + +/*========================================== + * @repairall [Valaris] + *------------------------------------------ + */ +int atcommand_repairall( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + int count, i; + + count = 0; + for (i = 0; i < MAX_INVENTORY; i++) { + if (sd->status.inventory[i].nameid && sd->status.inventory[i].broken == 1) { + sd->status.inventory[i].broken = 0; + clif_produceeffect(sd, 0, sd->status.inventory[i].nameid); + count++; + } + } + + if (count > 0) { + clif_misceffect(&sd->bl, 3); + clif_equiplist(sd); + clif_displaymessage(fd, msg_table[107]); // All items have been repaired. + } else { + clif_displaymessage(fd, msg_table[108]); // No item need to be repaired. + return -1; + } + + return 0; +} + +/* Removed @nuke for now in favor of alchemist marine sphere skill [Valaris] +int atcommand_nuke( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char character[100]; + struct map_session_data *pl_sd; + + memset(character, '\0', sizeof(character)); + + if (!message || !*message || sscanf(message, "%99[^\n]", character) < 1) { + clif_displaymessage(fd, "Please, enter a player name (usage: @nuke <char name>)."); + return -1; + } + + if ((pl_sd = map_nick2sd(character)) != NULL) { + if (pc_isGM(sd) >= pc_isGM(pl_sd)) { // you can kill only lower or same GM level + skill_castend_damage_id(&pl_sd->bl, &pl_sd->bl, NPC_SELFDESTRUCTION, 99, gettick(), 0); + clif_displaymessage(fd, msg_table[109]); // Player has been nuked! + } else { + clif_displaymessage(fd, msg_table[81]); // Your GM level don't authorise you to do this action on this player. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[3]); // Character not found. + return -1; + } + + return 0; +} +*/ + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_enablenpc(const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char NPCname[100]; + + memset(NPCname, '\0', sizeof(NPCname)); + + if (!message || !*message || sscanf(message, "%99[^\n]", NPCname) < 1) { + clif_displaymessage(fd, "Please, enter a NPC name (usage: @npcon <NPC_name>)."); + return -1; + } + + if (npc_name2id(NPCname) != NULL) { + npc_enable(NPCname, 1); + clif_displaymessage(fd, msg_table[110]); // Npc Enabled. + } else { + clif_displaymessage(fd, msg_table[111]); // This NPC doesn't exist. + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_disablenpc(const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char NPCname[100]; + + memset(NPCname, '\0', sizeof(NPCname)); + + if (!message || !*message || sscanf(message, "%99[^\n]", NPCname) < 1) { + clif_displaymessage(fd, "Please, enter a NPC name (usage: @npcoff <NPC_name>)."); + return -1; + } + + if (npc_name2id(NPCname) != NULL) { + npc_enable(NPCname, 0); + clif_displaymessage(fd, msg_table[112]); // Npc Disabled. + } else { + clif_displaymessage(fd, msg_table[111]); // This NPC doesn't exist. + return -1; + } + + return 0; +} + +/*========================================== + * time in txt for time command (by [Yor]) + *------------------------------------------ + */ +char * txt_time(unsigned int duration) { + int days, hours, minutes, seconds; + char temp[256]; + static char temp1[256]; + + memset(temp, '\0', sizeof(temp)); + memset(temp1, '\0', sizeof(temp1)); + + if (duration < 0) + duration = 0; + + days = duration / (60 * 60 * 24); + duration = duration - (60 * 60 * 24 * days); + hours = duration / (60 * 60); + duration = duration - (60 * 60 * hours); + minutes = duration / 60; + seconds = duration - (60 * minutes); + + if (days < 2) + sprintf(temp, msg_table[219], days); // %d day + else + sprintf(temp, msg_table[220], days); // %d days + if (hours < 2) + sprintf(temp1, msg_table[221], temp, hours); // %s %d hour + else + sprintf(temp1, msg_table[222], temp, hours); // %s %d hours + if (minutes < 2) + sprintf(temp, msg_table[223], temp1, minutes); // %s %d minute + else + sprintf(temp, msg_table[224], temp1, minutes); // %s %d minutes + if (seconds < 2) + sprintf(temp1, msg_table[225], temp, seconds); // %s and %d second + else + sprintf(temp1, msg_table[226], temp, seconds); // %s and %d seconds + + return temp1; +} + +/*========================================== + * @time/@date/@server_date/@serverdate/@server_time/@servertime: Display the date/time of the server (by [Yor] + * Calculation management of GM modification (@day/@night GM commands) is done + *------------------------------------------ + */ +int atcommand_servertime(const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + struct TimerData * timer_data; + struct TimerData * timer_data2; + time_t time_server; // variable for number of seconds (used with time() function) + struct tm *datetime; // variable for time in structure ->tm_mday, ->tm_sec, ... + char temp[256]; + + memset(temp, '\0', sizeof(temp)); + + time(&time_server); // get time in seconds since 1/1/1970 + datetime = localtime(&time_server); // convert seconds in structure + // like sprintf, but only for date/time (Sunday, November 02 2003 15:12:52) + strftime(temp, sizeof(temp)-1, msg_table[230], datetime); // Server time (normal time): %A, %B %d %Y %X. + clif_displaymessage(fd, temp); + + if (battle_config.night_duration == 0 && battle_config.day_duration == 0) { + if (night_flag == 0) + clif_displaymessage(fd, msg_table[231]); // Game time: The game is in permanent daylight. + else + clif_displaymessage(fd, msg_table[232]); // Game time: The game is in permanent night. + } else if (battle_config.night_duration == 0) + if (night_flag == 1) { // we start with night + timer_data = get_timer(day_timer_tid); + sprintf(temp, msg_table[233], txt_time((timer_data->tick - gettick()) / 1000)); // Game time: The game is actualy in night for %s. + clif_displaymessage(fd, temp); + clif_displaymessage(fd, msg_table[234]); // Game time: After, the game will be in permanent daylight. + } else + clif_displaymessage(fd, msg_table[231]); // Game time: The game is in permanent daylight. + else if (battle_config.day_duration == 0) + if (night_flag == 0) { // we start with day + timer_data = get_timer(night_timer_tid); + sprintf(temp, msg_table[235], txt_time((timer_data->tick - gettick()) / 1000)); // Game time: The game is actualy in daylight for %s. + clif_displaymessage(fd, temp); + clif_displaymessage(fd, msg_table[236]); // Game time: After, the game will be in permanent night. + } else + clif_displaymessage(fd, msg_table[232]); // Game time: The game is in permanent night. + else { + if (night_flag == 0) { + timer_data = get_timer(night_timer_tid); + timer_data2 = get_timer(day_timer_tid); + sprintf(temp, msg_table[235], txt_time((timer_data->tick - gettick()) / 1000)); // Game time: The game is actualy in daylight for %s. + clif_displaymessage(fd, temp); + if (timer_data->tick > timer_data2->tick) + sprintf(temp, msg_table[237], txt_time((timer_data->interval - abs(timer_data->tick - timer_data2->tick)) / 1000)); // Game time: After, the game will be in night for %s. + else + sprintf(temp, msg_table[237], txt_time(abs(timer_data->tick - timer_data2->tick) / 1000)); // Game time: After, the game will be in night for %s. + clif_displaymessage(fd, temp); + sprintf(temp, msg_table[238], txt_time(timer_data->interval / 1000)); // Game time: A day cycle has a normal duration of %s. + clif_displaymessage(fd, temp); + } else { + timer_data = get_timer(day_timer_tid); + timer_data2 = get_timer(night_timer_tid); + sprintf(temp, msg_table[233], txt_time((timer_data->tick - gettick()) / 1000)); // Game time: The game is actualy in night for %s. + clif_displaymessage(fd, temp); + if (timer_data->tick > timer_data2->tick) + sprintf(temp, msg_table[239], txt_time((timer_data->interval - abs(timer_data->tick - timer_data2->tick)) / 1000)); // Game time: After, the game will be in daylight for %s. + else + sprintf(temp, msg_table[239], txt_time(abs(timer_data->tick - timer_data2->tick) / 1000)); // Game time: After, the game will be in daylight for %s. + clif_displaymessage(fd, temp); + sprintf(temp, msg_table[238], txt_time(timer_data->interval / 1000)); // Game time: A day cycle has a normal duration of %s. + clif_displaymessage(fd, temp); + } + } + + return 0; +} + +/*========================================== + * @chardelitem <item_name_or_ID> <quantity> <player> (by [Yor] + * removes <quantity> item from a character + * item can be equiped or not. + * Inspired from a old command created by RoVeRT + *------------------------------------------ + */ +int atcommand_chardelitem(const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + struct map_session_data *pl_sd; + char character[100]; + char item_name[100]; + int i, number = 0, item_id, item_position, count; + char output[200]; + struct item_data *item_data; + + memset(character, '\0', sizeof(character)); + memset(item_name, '\0', sizeof(item_name)); + memset(output, '\0', sizeof(output)); + + if (!message || !*message || sscanf(message, "%s %d %99[^\n]", item_name, &number, character) < 3 || number < 1) { + clif_displaymessage(fd, "Please, enter an item name/id, a quantity and a player name (usage: @chardelitem <item_name_or_ID> <quantity> <player>)."); + return -1; + } + + item_id = 0; + if ((item_data = itemdb_searchname(item_name)) != NULL || + (item_data = itemdb_exists(atoi(item_name))) != NULL) + item_id = item_data->nameid; + + if (item_id > 500) { + if ((pl_sd = map_nick2sd(character)) != NULL) { + if (pc_isGM(sd) >= pc_isGM(pl_sd)) { // you can kill only lower or same level + item_position = pc_search_inventory(pl_sd, item_id); + if (item_position >= 0) { + count = 0; + for(i = 0; i < number && item_position >= 0; i++) { + pc_delitem(pl_sd, item_position, 1, 0); + count++; + item_position = pc_search_inventory(pl_sd, item_id); // for next loop + } + sprintf(output, msg_table[113], count); // %d item(s) removed by a GM. + clif_displaymessage(pl_sd->fd, output); + if (number == count) + sprintf(output, msg_table[114], count); // %d item(s) removed from the player. + else + sprintf(output, msg_table[115], count, count, number); // %d item(s) removed. Player had only %d on %d items. + clif_displaymessage(fd, output); + } else { + clif_displaymessage(fd, msg_table[116]); // Character does not have the item. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[81]); // Your GM level don't authorise you to do this action on this player. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[3]); // Character not found. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[19]); // Invalid item ID or name. + return -1; + } + + return 0; +} + +/*========================================== + * @jail <char_name> by [Yor] + * Special warp! No check with nowarp and nowarpto flag + *------------------------------------------ + */ +int atcommand_jail( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char character[100]; + struct map_session_data *pl_sd; + int x, y; + + memset(character, '\0', sizeof(character)); + + if (!message || !*message || sscanf(message, "%99[^\n]", character) < 1) { + clif_displaymessage(fd, "Please, enter a player name (usage: @jail <char_name>)."); + return -1; + } + + if ((pl_sd = map_nick2sd(character)) != NULL) { + if (pc_isGM(sd) >= pc_isGM(pl_sd)) { // you can jail only lower or same GM + switch(rand() % 2) { + case 0: + x = 24; + y = 75; + break; + default: + x = 49; + y = 75; + break; + } + if (pc_setpos(pl_sd, "sec_pri.gat", x, y, 3) == 0) { + pc_setsavepoint(pl_sd, "sec_pri.gat", x, y); // Save Char Respawn Point in the jail room [Lupus] + clif_displaymessage(pl_sd->fd, msg_table[117]); // GM has send you in jails. + clif_displaymessage(fd, msg_table[118]); // Player warped in jails. + } else { + clif_displaymessage(fd, msg_table[1]); // Map not found. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[81]); // Your GM level don't authorise you to do this action on this player. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[3]); // Character not found. + return -1; + } + + return 0; +} + +/*========================================== + * @unjail/@discharge <char_name> by [Yor] + * Special warp! No check with nowarp and nowarpto flag + *------------------------------------------ + */ +int atcommand_unjail( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char character[100]; + struct map_session_data *pl_sd; + + memset(character, '\0', sizeof(character)); + + if (!message || !*message || sscanf(message, "%99[^\n]", character) < 1) { + clif_displaymessage(fd, "Please, enter a player name (usage: @unjail/@discharge <char_name>)."); + return -1; + } + + if ((pl_sd = map_nick2sd(character)) != NULL) { + if (pc_isGM(sd) >= pc_isGM(pl_sd)) { // you can jail only lower or same GM + if (pl_sd->bl.m != map_mapname2mapid("sec_pri.gat")) { + clif_displaymessage(fd, msg_table[119]); // This player is not in jails. + return -1; + } else if (pc_setpos(pl_sd, "prontera.gat", 156, 191, 3) == 0) { + pc_setsavepoint(pl_sd, "prontera.gat", 156, 191); // Save char respawn point in Prontera + clif_displaymessage(pl_sd->fd, msg_table[120]); // GM has discharge you. + clif_displaymessage(fd, msg_table[121]); // Player warped to Prontera. + } else { + clif_displaymessage(fd, msg_table[1]); // Map not found. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[81]); // Your GM level don't authorise you to do this action on this player. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[3]); // Character not found. + return -1; + } + + return 0; +} + +/*========================================== + * @disguise <mob_id> by [Valaris] (simplified by [Yor]) + *------------------------------------------ + */ +int atcommand_disguise( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + int mob_id; + + if (!message || !*message) { + clif_displaymessage(fd, "Please, enter a Monster/NPC name/id (usage: @disguise <monster_name_or_monster_ID>)."); + return -1; + } + + if ((mob_id = mobdb_searchname(message)) == 0) // check name first (to avoid possible name begining by a number) + mob_id = atoi(message); + + if ((mob_id >= 46 && mob_id <= 125) || (mob_id >= 700 && mob_id <= 718) || // NPC + (mob_id >= 721 && mob_id <= 755) || (mob_id >= 757 && mob_id <= 811) || // NPC + (mob_id >= 813 && mob_id <= 834) || // NPC + (mob_id > 1000 && mob_id < 1521)) { // monsters + if (pc_isriding(sd)) { // temporary prevention of crash caused by peco + disguise, will look into a better solution [Valaris] + clif_displaymessage(fd, msg_table[227]); // Cannot wear disguise while riding a Peco. + return -1; + } + sd->disguiseflag = 1; // set to override items with disguise script [Valaris] + sd->disguise = mob_id; + pc_setpos(sd, sd->mapname, sd->bl.x, sd->bl.y, 3); + clif_displaymessage(fd, msg_table[122]); // Disguise applied. + } else { + clif_displaymessage(fd, msg_table[123]); // Monster/NPC name/id hasn't been found. + return -1; + } + + return 0; +} + +/*========================================== + * @undisguise by [Yor] + *------------------------------------------ + */ +int atcommand_undisguise( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + if (sd->disguise) { + clif_clearchar(&sd->bl, 9); + sd->disguise = 0; + pc_setpos(sd, sd->mapname, sd->bl.x, sd->bl.y, 3); + clif_displaymessage(fd, msg_table[124]); // Undisguise applied. + } else { + clif_displaymessage(fd, msg_table[125]); // You're not disguised. + return -1; + } + + return 0; +} + +/*========================================== + * @broadcast by [Valaris] + *------------------------------------------ + */ +int atcommand_broadcast( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char output[200]; + + memset(output, '\0', sizeof(output)); + + if (!message || !*message) { + clif_displaymessage(fd, "Please, enter a message (usage: @broadcast <message>)."); + return -1; + } + + sprintf(output, "%s : %s", sd->status.name, message); + intif_GMmessage(output, strlen(output) + 1, 0); + + return 0; +} + +/*========================================== + * @localbroadcast by [Valaris] + *------------------------------------------ + */ +int atcommand_localbroadcast( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char output[200]; + + memset(output, '\0', sizeof(output)); + + if (!message || !*message) { + clif_displaymessage(fd, "Please, enter a message (usage: @localbroadcast <message>)."); + return -1; + } + + sprintf(output, "%s : %s", sd->status.name, message); + + clif_GMmessage(&sd->bl, output, strlen(output) + 1, 1); // 1: ALL_SAMEMAP + + return 0; +} + +/*========================================== + * @ignorelist by [Yor] + *------------------------------------------ + */ +int atcommand_ignorelist( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char output[200]; + int count; + int i; + + memset(output, '\0', sizeof(output)); + + count = 0; + for(i = 0; i < (int)(sizeof(sd->ignore) / sizeof(sd->ignore[0])); i++) + if (sd->ignore[i].name[0]) + count++; + + if (sd->ignoreAll == 0) + if (count == 0) + clif_displaymessage(fd, msg_table[126]); // You accept any wisp (no wisper is refused). + else { + sprintf(output, msg_table[127], count); // You accept any wisp, except thoses from %d player(s): + clif_displaymessage(fd, output); + } + else + if (count == 0) + clif_displaymessage(fd, msg_table[128]); // You refuse all wisps (no specifical wisper is refused). + else { + sprintf(output, msg_table[129], count); // You refuse all wisps, AND refuse wisps from %d player(s): + clif_displaymessage(fd, output); + } + + if (count > 0) + for(i = 0; i < (int)(sizeof(sd->ignore) / sizeof(sd->ignore[0])); i++) + if (sd->ignore[i].name[0]) + clif_displaymessage(fd, sd->ignore[i].name); + + return 0; +} + +/*========================================== + * @charignorelist <player_name> by [Yor] + *------------------------------------------ + */ +int atcommand_charignorelist( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char character[100]; + struct map_session_data *pl_sd; + char output[200]; + int count; + int i; + + memset(character, '\0', sizeof(character)); + memset(output, '\0', sizeof(output)); + + if (!message || !*message || sscanf(message, "%99[^\n]", character) < 1) { + clif_displaymessage(fd, "Please, enter a player name (usage: @charignorelist <char name>)."); + return -1; + } + + if ((pl_sd = map_nick2sd(character)) != NULL) { + count = 0; + for(i = 0; i < (int)(sizeof(pl_sd->ignore) / sizeof(pl_sd->ignore[0])); i++) + if (pl_sd->ignore[i].name[0]) + count++; + + if (pl_sd->ignoreAll == 0) + if (count == 0) { + sprintf(output, msg_table[130], pl_sd->status.name); // '%s' accept any wisp (no wisper is refused). + clif_displaymessage(fd, output); + } else { + sprintf(output, msg_table[131], pl_sd->status.name, count); // '%s' accept any wisp, except thoses from %d player(s): + clif_displaymessage(fd, output); + } + else + if (count == 0) { + sprintf(output, msg_table[132], pl_sd->status.name); // '%s' refuse all wisps (no specifical wisper is refused). + clif_displaymessage(fd, output); + } else { + sprintf(output, msg_table[133], pl_sd->status.name, count); // '%s' refuse all wisps, AND refuse wisps from %d player(s): + clif_displaymessage(fd, output); + } + + if (count > 0) + for(i = 0; i < (int)(sizeof(pl_sd->ignore) / sizeof(pl_sd->ignore[0])); i++) + if (pl_sd->ignore[i].name[0]) + clif_displaymessage(fd, pl_sd->ignore[i].name); + + } else { + clif_displaymessage(fd, msg_table[3]); // Character not found. + return -1; + } + + return 0; +} + +/*========================================== + * @inall <player_name> by [Yor] + *------------------------------------------ + */ +int atcommand_inall( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char character[100]; + char output[200]; + struct map_session_data *pl_sd; + + memset(character, '\0', sizeof(character)); + memset(output, '\0', sizeof(output)); + + if (!message || !*message || sscanf(message, "%99[^\n]", character) < 1) { + clif_displaymessage(fd, "Please, enter a player name (usage: @inall <char name>)."); + return -1; + } + + if ((pl_sd = map_nick2sd(character)) != NULL) { + if (pc_isGM(sd) >= pc_isGM(pl_sd)) { // you can change wisp option only to lower or same level + if (pl_sd->ignoreAll == 0) { + sprintf(output, msg_table[134], pl_sd->status.name); // '%s' already accepts all wispers. + clif_displaymessage(fd, output); + return -1; + } else { + pl_sd->ignoreAll = 0; + sprintf(output, msg_table[135], pl_sd->status.name); // '%s' now accepts all wispers. + clif_displaymessage(fd, output); + // message to player + clif_displaymessage(pl_sd->fd, msg_table[136]); // A GM has authorised all wispers for you. + WFIFOW(pl_sd->fd,0) = 0x0d2; // R 00d2 <type>.B <fail>.B: type: 0: deny, 1: allow, fail: 0: success, 1: fail + WFIFOB(pl_sd->fd,2) = 1; + WFIFOB(pl_sd->fd,3) = 0; // success + WFIFOSET(pl_sd->fd, 4); // packet_len_table[0x0d2] + } + } else { + clif_displaymessage(fd, msg_table[81]); // Your GM level don't authorise you to do this action on this player. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[3]); // Character not found. + return -1; + } + + return 0; +} + +/*========================================== + * @exall <player_name> by [Yor] + *------------------------------------------ + */ +int atcommand_exall( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char character[100]; + char output[200]; + struct map_session_data *pl_sd; + + memset(character, '\0', sizeof(character)); + memset(output, '\0', sizeof(output)); + + if (!message || !*message || sscanf(message, "%99[^\n]", character) < 1) { + clif_displaymessage(fd, "Please, enter a player name (usage: @exall <char name>)."); + return -1; + } + + if ((pl_sd = map_nick2sd(character)) != NULL) { + if (pc_isGM(sd) >= pc_isGM(pl_sd)) { // you can change wisp option only to lower or same level + if (pl_sd->ignoreAll == 1) { + sprintf(output, msg_table[137], pl_sd->status.name); // '%s' already blocks all wispers. + clif_displaymessage(fd, output); + return -1; + } else { + pl_sd->ignoreAll = 1; + sprintf(output, msg_table[138], pl_sd->status.name); // '%s' blocks now all wispers. + clif_displaymessage(fd, output); + // message to player + clif_displaymessage(pl_sd->fd, msg_table[139]); // A GM has blocked all wispers for you. + WFIFOW(pl_sd->fd,0) = 0x0d2; // R 00d2 <type>.B <fail>.B: type: 0: deny, 1: allow, fail: 0: success, 1: fail + WFIFOB(pl_sd->fd,2) = 0; + WFIFOB(pl_sd->fd,3) = 0; // success + WFIFOSET(pl_sd->fd,4); // packet_len_table[0x0d2] + } + } else { + clif_displaymessage(fd, msg_table[81]); // Your GM level don't authorise you to do this action on this player. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[3]); // Character not found. + return -1; + } + + return 0; +} + +/*========================================== + * @chardisguise <mob_id> <character> by Kalaspuff (based off Valaris' and Yor's work) + *------------------------------------------ + */ +int atcommand_chardisguise( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + int mob_id; + char character[100]; + char mob_name[100]; + struct map_session_data* pl_sd; + + memset(character, '\0', sizeof(character)); + memset(mob_name, '\0', sizeof(mob_name)); + + if (!message || !*message || sscanf(message, "%s %99[^\n]", mob_name, character) < 2) { + clif_displaymessage(fd, "Please, enter a Monster/NPC name/id and a player name (usage: @chardisguise <monster_name_or_monster_ID> <char name>)."); + return -1; + } + + if ((mob_id = mobdb_searchname(mob_name)) == 0) // check name first (to avoid possible name begining by a number) + mob_id = atoi(mob_name); + + if ((pl_sd = map_nick2sd(character)) != NULL) { + if (pc_isGM(sd) >= pc_isGM(pl_sd)) { // you can disguise only lower or same level + if ((mob_id >= 46 && mob_id <= 125) || (mob_id >= 700 && mob_id <= 718) || // NPC + (mob_id >= 721 && mob_id <= 755) || (mob_id >= 757 && mob_id <= 811) || // NPC + (mob_id >= 813 && mob_id <= 834) || // NPC + (mob_id > 1000 && mob_id < 1521)) { // monsters + if (pc_isriding(pl_sd)) { // temporary prevention of crash caused by peco + disguise, will look into a better solution [Valaris] + clif_displaymessage(fd, msg_table[228]); // Character cannot wear disguise while riding a Peco. + return -1; + } + pl_sd->disguiseflag = 1; // set to override items with disguise script [Valaris] + pl_sd->disguise = mob_id; + pc_setpos(pl_sd, pl_sd->mapname, pl_sd->bl.x, pl_sd->bl.y, 3); + clif_displaymessage(fd, msg_table[140]); // Character's disguise applied. + } else { + clif_displaymessage(fd, msg_table[123]); // Monster/NPC name/id hasn't been found. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[81]); // Your GM level don't authorise you to do this action on this player. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[3]); // Character not found. + return -1; + } + + return 0; +} + +/*========================================== + * @charundisguise <character> by Kalaspuff (based off Yor's work) + *------------------------------------------ + */ +int atcommand_charundisguise( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char character[100]; + struct map_session_data* pl_sd; + + memset(character, '\0', sizeof(character)); + + if (!message || !*message || sscanf(message, "%99[^\n]", character) < 1) { + clif_displaymessage(fd, "Please, enter a player name (usage: @charundisguise <char name>)."); + return -1; + } + + if ((pl_sd = map_nick2sd(character)) != NULL) { + if (pc_isGM(sd) >= pc_isGM(pl_sd)) { // you can undisguise only lower or same level + if (pl_sd->disguise) { + clif_clearchar(&pl_sd->bl, 9); + pl_sd->disguise = 0; + pc_setpos(pl_sd, pl_sd->mapname, pl_sd->bl.x, pl_sd->bl.y, 3); + clif_displaymessage(fd, msg_table[141]); // Character's undisguise applied. + } else { + clif_displaymessage(fd, msg_table[142]); // Character is not disguised. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[81]); // Your GM level don't authorise you to do this action on this player. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[3]); // Character not found. + return -1; + } + + return 0; +} + +/*========================================== + * @email <actual@email> <new@email> by [Yor] + *------------------------------------------ + */ +int atcommand_email( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char actual_email[100]; + char new_email[100]; + + memset(actual_email, '\0', sizeof(actual_email)); + memset(new_email, '\0', sizeof(new_email)); + + if (!message || !*message || sscanf(message, "%99s %99s", actual_email, new_email) < 2) { + clif_displaymessage(fd, "Please enter 2 emails (usage: @email <actual@email> <new@email>)."); + return -1; + } + + if (e_mail_check(actual_email) == 0) { + clif_displaymessage(fd, msg_table[144]); // Invalid actual email. If you have default e-mail, give a@a.com. + return -1; + } else if (e_mail_check(new_email) == 0) { + clif_displaymessage(fd, msg_table[145]); // Invalid new email. Please enter a real e-mail. + return -1; + } else if (strcmpi(new_email, "a@a.com") == 0) { + clif_displaymessage(fd, msg_table[146]); // New email must be a real e-mail. + return -1; + } else if (strcmpi(actual_email, new_email) == 0) { + clif_displaymessage(fd, msg_table[147]); // New email must be different of the actual e-mail. + return -1; + } else { + chrif_changeemail(sd->status.account_id, actual_email, new_email); + clif_displaymessage(fd, msg_table[148]); // Information sended to login-server via char-server. + } + + return 0; +} + +/*========================================== + *@effect + *------------------------------------------ + */ +int atcommand_effect( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + struct map_session_data *pl_sd; + int type = 0, flag = 0, i; + + if (!message || !*message || sscanf(message, "%d %d", &type,&flag) < 2) { + clif_displaymessage(fd, "Please, enter at least a option (usage: @effect <type+>)."); + return -1; + } + if(flag <=0){ + clif_specialeffect(&sd->bl, type, flag); + clif_displaymessage(fd, msg_table[229]); // Your effect has changed. + } + else{ + for (i = 0; i < fd_max; i++) { + if (session[i] && (pl_sd = session[i]->session_data) && pl_sd->state.auth) { + clif_specialeffect(&pl_sd->bl, type, flag); + clif_displaymessage(pl_sd->fd, msg_table[229]); // Your effect has changed. + } + } + } + + return 0; +} + +/*========================================== + * @charitemlist <character>: Displays the list of a player's items. + *------------------------------------------ + */ +int +atcommand_character_item_list( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + struct map_session_data *pl_sd; + struct item_data *item_data, *item_temp; + int i, j, equip, count, counter, counter2; + char character[100], output[200], equipstr[100], outputtmp[200]; + + memset(character, '\0', sizeof(character)); + memset(output, '\0', sizeof(output)); + memset(equipstr, '\0', sizeof(equipstr)); + memset(outputtmp, '\0', sizeof(outputtmp)); + + if (!message || !*message || sscanf(message, "%99[^\n]", character) < 1) { + clif_displaymessage(fd, "Please, enter a player name (usage: @charitemlist <char name>)."); + return -1; + } + + if ((pl_sd = map_nick2sd(character)) != NULL) { + if (pc_isGM(sd) >= pc_isGM(pl_sd)) { // you can look items only lower or same level + counter = 0; + count = 0; + for (i = 0; i < MAX_INVENTORY; i++) { + if (pl_sd->status.inventory[i].nameid > 0 && (item_data = itemdb_search(pl_sd->status.inventory[i].nameid)) != NULL) { + counter = counter + pl_sd->status.inventory[i].amount; + count++; + if (count == 1) { + sprintf(output, "------ Items list of '%s' ------", pl_sd->status.name); + clif_displaymessage(fd, output); + } + if ((equip = pl_sd->status.inventory[i].equip)) { + strcpy(equipstr, "| equiped: "); + if (equip & 4) + strcat(equipstr, "robe/gargment, "); + if (equip & 8) + strcat(equipstr, "left accessory, "); + if (equip & 16) + strcat(equipstr, "body/armor, "); + if ((equip & 34) == 2) + strcat(equipstr, "right hand, "); + if ((equip & 34) == 32) + strcat(equipstr, "left hand, "); + if ((equip & 34) == 34) + strcat(equipstr, "both hands, "); + if (equip & 64) + strcat(equipstr, "feet, "); + if (equip & 128) + strcat(equipstr, "right accessory, "); + if ((equip & 769) == 1) + strcat(equipstr, "lower head, "); + if ((equip & 769) == 256) + strcat(equipstr, "top head, "); + if ((equip & 769) == 257) + strcat(equipstr, "lower/top head, "); + if ((equip & 769) == 512) + strcat(equipstr, "mid head, "); + if ((equip & 769) == 512) + strcat(equipstr, "lower/mid head, "); + if ((equip & 769) == 769) + strcat(equipstr, "lower/mid/top head, "); + // remove final ', ' + equipstr[strlen(equipstr) - 2] = '\0'; + } else + memset(equipstr, '\0', sizeof(equipstr)); + if (sd->status.inventory[i].refine) + sprintf(output, "%d %s %+d (%s %+d, id: %d) %s", pl_sd->status.inventory[i].amount, item_data->name, pl_sd->status.inventory[i].refine, item_data->jname, pl_sd->status.inventory[i].refine, pl_sd->status.inventory[i].nameid, equipstr); + else + sprintf(output, "%d %s (%s, id: %d) %s", pl_sd->status.inventory[i].amount, item_data->name, item_data->jname, pl_sd->status.inventory[i].nameid, equipstr); + clif_displaymessage(fd, output); + memset(output, '\0', sizeof(output)); + counter2 = 0; + for (j = 0; j < item_data->slot; j++) { + if (pl_sd->status.inventory[i].card[j]) { + if ((item_temp = itemdb_search(pl_sd->status.inventory[i].card[j])) != NULL) { + if (output[0] == '\0') + sprintf(outputtmp, " -> (card(s): #%d %s (%s), ", ++counter2, item_temp->name, item_temp->jname); + else + sprintf(outputtmp, "#%d %s (%s), ", ++counter2, item_temp->name, item_temp->jname); + strcat(output, outputtmp); + } + } + } + if (output[0] != '\0') { + output[strlen(output) - 2] = ')'; + output[strlen(output) - 1] = '\0'; + clif_displaymessage(fd, output); + } + } + } + if (count == 0) + clif_displaymessage(fd, "No item found on this player."); + else { + sprintf(output, "%d item(s) found in %d kind(s) of items.", counter, count); + clif_displaymessage(fd, output); + } + } else { + clif_displaymessage(fd, msg_table[81]); // Your GM level don't authorise you to do this action on this player. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[3]); // Character not found. + return -1; + } + + return 0; +} + +/*========================================== + * @charstoragelist <character>: Displays the items list of a player's storage. + *------------------------------------------ + */ +int +atcommand_character_storage_list( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + struct storage *stor; + struct map_session_data *pl_sd; + struct item_data *item_data, *item_temp; + int i, j, count, counter, counter2; + char character[100], output[200], outputtmp[200]; + + memset(character, '\0', sizeof(character)); + memset(output, '\0', sizeof(output)); + memset(outputtmp, '\0', sizeof(outputtmp)); + + if (!message || !*message || sscanf(message, "%99[^\n]", character) < 1) { + clif_displaymessage(fd, "Please, enter a player name (usage: @charitemlist <char name>)."); + return -1; + } + + if ((pl_sd = map_nick2sd(character)) != NULL) { + if (pc_isGM(sd) >= pc_isGM(pl_sd)) { // you can look items only lower or same level + if((stor = account2storage2(pl_sd->status.account_id)) != NULL) { + counter = 0; + count = 0; + for (i = 0; i < MAX_STORAGE; i++) { + if (stor->storage[i].nameid > 0 && (item_data = itemdb_search(stor->storage[i].nameid)) != NULL) { + counter = counter + stor->storage[i].amount; + count++; + if (count == 1) { + sprintf(output, "------ Storage items list of '%s' ------", pl_sd->status.name); + clif_displaymessage(fd, output); + } + if (stor->storage[i].refine) + sprintf(output, "%d %s %+d (%s %+d, id: %d)", stor->storage[i].amount, item_data->name, stor->storage[i].refine, item_data->jname, stor->storage[i].refine, stor->storage[i].nameid); + else + sprintf(output, "%d %s (%s, id: %d)", stor->storage[i].amount, item_data->name, item_data->jname, stor->storage[i].nameid); + clif_displaymessage(fd, output); + memset(output, '\0', sizeof(output)); + counter2 = 0; + for (j = 0; j < item_data->slot; j++) { + if (stor->storage[i].card[j]) { + if ((item_temp = itemdb_search(stor->storage[i].card[j])) != NULL) { + if (output[0] == '\0') + sprintf(outputtmp, " -> (card(s): #%d %s (%s), ", ++counter2, item_temp->name, item_temp->jname); + else + sprintf(outputtmp, "#%d %s (%s), ", ++counter2, item_temp->name, item_temp->jname); + strcat(output, outputtmp); + } + } + } + if (output[0] != '\0') { + output[strlen(output) - 2] = ')'; + output[strlen(output) - 1] = '\0'; + clif_displaymessage(fd, output); + } + } + } + if (count == 0) + clif_displaymessage(fd, "No item found in the storage of this player."); + else { + sprintf(output, "%d item(s) found in %d kind(s) of items.", counter, count); + clif_displaymessage(fd, output); + } + } else { + clif_displaymessage(fd, "This player has no storage."); + return -1; + } + } else { + clif_displaymessage(fd, msg_table[81]); // Your GM level don't authorise you to do this action on this player. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[3]); // Character not found. + return -1; + } + + return 0; +} + +/*========================================== + * @charcartlist <character>: Displays the items list of a player's cart. + *------------------------------------------ + */ +int +atcommand_character_cart_list( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + struct map_session_data *pl_sd; + struct item_data *item_data, *item_temp; + int i, j, count, counter, counter2; + char character[100], output[200], outputtmp[200]; + + memset(character, '\0', sizeof(character)); + memset(output, '\0', sizeof(output)); + memset(outputtmp, '\0', sizeof(outputtmp)); + + if (!message || !*message || sscanf(message, "%99[^\n]", character) < 1) { + clif_displaymessage(fd, "Please, enter a player name (usage: @charitemlist <char name>)."); + return -1; + } + + if ((pl_sd = map_nick2sd(character)) != NULL) { + if (pc_isGM(sd) >= pc_isGM(pl_sd)) { // you can look items only lower or same level + counter = 0; + count = 0; + for (i = 0; i < MAX_CART; i++) { + if (pl_sd->status.cart[i].nameid > 0 && (item_data = itemdb_search(pl_sd->status.cart[i].nameid)) != NULL) { + counter = counter + pl_sd->status.cart[i].amount; + count++; + if (count == 1) { + sprintf(output, "------ Cart items list of '%s' ------", pl_sd->status.name); + clif_displaymessage(fd, output); + } + if (pl_sd->status.cart[i].refine) + sprintf(output, "%d %s %+d (%s %+d, id: %d)", pl_sd->status.cart[i].amount, item_data->name, pl_sd->status.cart[i].refine, item_data->jname, pl_sd->status.cart[i].refine, pl_sd->status.cart[i].nameid); + else + sprintf(output, "%d %s (%s, id: %d)", pl_sd->status.cart[i].amount, item_data->name, item_data->jname, pl_sd->status.cart[i].nameid); + clif_displaymessage(fd, output); + memset(output, '\0', sizeof(output)); + counter2 = 0; + for (j = 0; j < item_data->slot; j++) { + if (pl_sd->status.cart[i].card[j]) { + if ((item_temp = itemdb_search(pl_sd->status.cart[i].card[j])) != NULL) { + if (output[0] == '\0') + sprintf(outputtmp, " -> (card(s): #%d %s (%s), ", ++counter2, item_temp->name, item_temp->jname); + else + sprintf(outputtmp, "#%d %s (%s), ", ++counter2, item_temp->name, item_temp->jname); + strcat(output, outputtmp); + } + } + } + if (output[0] != '\0') { + output[strlen(output) - 2] = ')'; + output[strlen(output) - 1] = '\0'; + clif_displaymessage(fd, output); + } + } + } + if (count == 0) + clif_displaymessage(fd, "No item found in the cart of this player."); + else { + sprintf(output, "%d item(s) found in %d kind(s) of items.", counter, count); + clif_displaymessage(fd, output); + } + } else { + clif_displaymessage(fd, msg_table[81]); // Your GM level don't authorise you to do this action on this player. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[3]); // Character not found. + return -1; + } + + return 0; +} + +/*========================================== + * @killer by MouseJstr + * enable killing players even when not in pvp + *------------------------------------------ + */ +int +atcommand_killer( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + sd->special_state.killer = !sd->special_state.killer; + + if(sd->special_state.killer) + clif_displaymessage(fd, msg_table[241]); + else + clif_displaymessage(fd, msg_table[242]); + + return 0; +} + +/*========================================== + * @killable by MouseJstr + * enable other people killing you + *------------------------------------------ + */ +int +atcommand_killable( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + sd->special_state.killable = !sd->special_state.killable; + + if(sd->special_state.killable) + clif_displaymessage(fd, msg_table[242]); + else + clif_displaymessage(fd, msg_table[241]); + + return 0; +} + +/*========================================== + * @charkillable by MouseJstr + * enable another player to be killed + *------------------------------------------ + */ +int +atcommand_charkillable( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + struct map_session_data *pl_sd = NULL; + + if (!message || !*message) + return -1; + + if((pl_sd=map_nick2sd((char *) message)) == NULL) + return -1; + + pl_sd->special_state.killable = !pl_sd->special_state.killable; + + if(pl_sd->special_state.killable) + clif_displaymessage(fd, "The player is now killable"); + else + clif_displaymessage(fd, "The player is no longer killable"); + + return 0; +} + + +/*========================================== + * @skillon by MouseJstr + * turn skills on for the map + *------------------------------------------ + */ +int +atcommand_skillon( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + map[sd->bl.m].flag.noskill = 0; + clif_displaymessage(fd, msg_table[244]); + return 0; +} + +/*========================================== + * @skilloff by MouseJstr + * Turn skills off on the map + *------------------------------------------ + */ +int +atcommand_skilloff( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + map[sd->bl.m].flag.noskill = 1; + clif_displaymessage(fd, msg_table[243]); + return 0; +} + +/*========================================== + * @npcmove by MouseJstr + * + * move a npc + *------------------------------------------ + */ +int +atcommand_npcmove(const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char character[100]; + int x = 0, y = 0; + struct npc_data *nd = 0; + + if( sd == NULL ) + return -1; + + if (!message || !*message) + return -1; + + memset(character, '\0', sizeof character); + + if (sscanf(message, "%d %d %99[^\n]", &x, &y, character) < 4) + return -1; + + nd=npc_name2id(character); + if (nd==NULL) + return -1; + + npc_enable(character, 0); + nd->bl.x = x; + nd->bl.y = y; + npc_enable(character, 1); + + return 0; +} + +/*========================================== + * @addwarp by MouseJstr + * + * Create a new static warp point. + *------------------------------------------ + */ +int +atcommand_addwarp(const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char w1[64], w3[64], w4[64]; + char map[30], output[200]; + int x,y,ret; + + if (!message || !*message) + return -1; + + if (sscanf(message, "%99s %d %d[^\n]", map, &x, &y ) < 3) + return -1; + + sprintf(w1,"%s,%d,%d", sd->mapname, sd->bl.x, sd->bl.y); + sprintf(w3,"%s%d%d%d%d", map,sd->bl.x, sd->bl.y, x, y); + sprintf(w4,"1,1,%s.gat,%d,%d", map, x, y); + + ret = npc_parse_warp(w1, "warp", w3, w4); + + sprintf(output, "New warp NPC => %s",w3); + + clif_displaymessage(fd, output); + + return ret; +} + +/*========================================== + * @follow by [MouseJstr] + * + * Follow a player .. staying no more then 5 spaces away + *------------------------------------------ + */ +int +atcommand_follow(const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + struct map_session_data *pl_sd = NULL; + + if (!message || !*message) + return -1; + if((pl_sd=map_nick2sd((char *) message)) != NULL) + pc_follow(sd, pl_sd->bl.id); + else + return 1; + return 0; +} + + +/*========================================== + * @chareffect by [MouseJstr] + * + * Create a effect localized on another character + *------------------------------------------ + */ +int +atcommand_chareffect(const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + struct map_session_data *pl_sd = NULL; + char target[255]; + int type = 0; + + if (!message || !*message || sscanf(message, "%d %s", &type, target) != 2) { + clif_displaymessage(fd, "usage: @chareffect <type+> <target>."); + return -1; + } + + if((pl_sd=map_nick2sd((char *) target)) == NULL) + return -1; + + clif_specialeffect(&pl_sd->bl, type, 0); + clif_displaymessage(fd, msg_table[229]); // Your effect has changed. + + return 0; +} +/*========================================== + * @dropall by [MouseJstr] + * + * Drop all your possession on the ground + *------------------------------------------ + */ +int +atcommand_dropall(const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + int i; + for (i = 0; i < MAX_INVENTORY; i++) { + if (sd->status.inventory[i].amount) { + if(sd->status.inventory[i].equip != 0) + pc_unequipitem(sd, i, 0); + pc_dropitem(sd, i, sd->status.inventory[i].amount); + } + } + return 0; +} +/*========================================== + * @chardropall by [MouseJstr] + * + * Throw all the characters possessions on the ground. Normally + * done in response to them being disrespectful of a GM + *------------------------------------------ + */ +int +atcommand_chardropall(const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + int i; + struct map_session_data *pl_sd = NULL; + + if (!message || !*message) + return -1; + if((pl_sd=map_nick2sd((char *) message)) == NULL) + return -1; + for (i = 0; i < MAX_INVENTORY; i++) { + if (pl_sd->status.inventory[i].amount) { + if(pl_sd->status.inventory[i].equip != 0) + pc_unequipitem(pl_sd, i, 0); + pc_dropitem(pl_sd, i, pl_sd->status.inventory[i].amount); + } + } + + clif_displaymessage(pl_sd->fd, "Ever play 52 card pickup?"); + clif_displaymessage(fd, "It is done"); + //clif_displaymessage(fd, "It is offical.. your a jerk"); + + return 0; +} +/*========================================== + * @storeall by [MouseJstr] + * + * Put everything into storage to simplify your inventory to make + * debugging easie + *------------------------------------------ + */ +int +atcommand_storeall(const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + int i; + if (storage_storageopen(sd) == 1) { + clif_displaymessage(fd, "run this command again.."); + return 0; + } + for (i = 0; i < MAX_INVENTORY; i++) { + if (sd->status.inventory[i].amount) { + if(sd->status.inventory[i].equip != 0) + pc_unequipitem(sd, i, 0); + storage_storageadd(sd, i, sd->status.inventory[i].amount); + } + } + storage_storageclose(sd); + + clif_displaymessage(fd, "It is done"); + return 0; +} +/*========================================== + * @charstoreall by [MouseJstr] + * + * A way to screw with players who piss you off + *------------------------------------------ + */ +int +atcommand_charstoreall(const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + int i; + struct map_session_data *pl_sd = NULL; + + if (!message || !*message) + return -1; + if((pl_sd=map_nick2sd((char *) message)) == NULL) + return -1; + + if (storage_storageopen(pl_sd) == 1) { + clif_displaymessage(fd, "Had to open the characters storage window..."); + clif_displaymessage(fd, "run this command again.."); + return 0; + } + for (i = 0; i < MAX_INVENTORY; i++) { + if (pl_sd->status.inventory[i].amount) { + if(pl_sd->status.inventory[i].equip != 0) + pc_unequipitem(pl_sd, i, 0); + storage_storageadd(pl_sd, i, sd->status.inventory[i].amount); + } + } + storage_storageclose(pl_sd); + + clif_displaymessage(pl_sd->fd, "Everything you own has been put away for safe keeping."); + clif_displaymessage(pl_sd->fd, "go to the nearest kafka to retrieve it.."); + clif_displaymessage(pl_sd->fd, " -- the management"); + + clif_displaymessage(fd, "It is done"); + + return 0; +} +/*========================================== + * @skillid by [MouseJstr] + * + * lookup a skill by name + *------------------------------------------ + */ +int +atcommand_skillid(const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + int skillen = 0, idx = 0; + if (!message || !*message) + return -1; + skillen = strlen(message); + while (skill_names[idx].id != 0) { + if ((strnicmp(skill_names[idx].name, message, skillen) == 0) || + (strnicmp(skill_names[idx].desc, message, skillen) == 0)) { + char output[255]; + sprintf(output, "skill %d: %s", skill_names[idx].id, skill_names[idx].desc); + clif_displaymessage(fd, output); + } + idx++; + } + return 0; +} +/*========================================== + * @useskill by [MouseJstr] + * + * A way of using skills without having to find them in the skills menu + *------------------------------------------ + */ +int +atcommand_useskill(const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + struct map_session_data *pl_sd = NULL; + int skillnum; + int skilllv; + int inf; + char target[255]; + + if (!message || !*message) + return -1; + if(sscanf(message, "%d %d %s", &skillnum, &skilllv, target) != 3) { + clif_displaymessage(fd, "Usage: @useskill <skillnum> <skillv> <target>"); + return -1; + } + if((pl_sd=map_nick2sd(target)) == NULL) { + return -1; + } + + inf = skill_get_inf(skillnum); + + if ((inf == 2) || (inf == 1)) + skill_use_pos(sd, pl_sd->bl.x, pl_sd->bl.y, skillnum, skilllv); + else + skill_use_id(sd, pl_sd->bl.id, skillnum, skilllv); + + return 0; +} +/*========================================== + * It is made to rain. + *------------------------------------------ + */ +int +atcommand_rain( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + int effno = 0; + effno = 161; + nullpo_retr(-1, sd); + if (effno < 0 || map[sd->bl.m].flag.rain) + return -1; + + map[sd->bl.m].flag.rain=1; + clif_specialeffect(&sd->bl,effno,2); + return 0; +} +/*========================================== + * It is made to snow. + *------------------------------------------ + */ +int +atcommand_snow( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + int effno = 0; + effno = 162; + nullpo_retr(-1, sd); + if (effno < 0 || map[sd->bl.m].flag.snow) + return -1; + + map[sd->bl.m].flag.snow=1; + clif_specialeffect(&sd->bl,effno,2); + return 0; +} + +/*========================================== + * Cherry tree snowstorm is made to fall. (Sakura) + *------------------------------------------ + */ +int +atcommand_sakura( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + int effno = 0; + effno = 163; + nullpo_retr(-1, sd); + if (effno < 0 || map[sd->bl.m].flag.sakura) + return -1; + + map[sd->bl.m].flag.sakura=1; + clif_specialeffect(&sd->bl,effno,2); + return 0; +} + +/*========================================== + * Fog hangs over. + *------------------------------------------ + */ +int +atcommand_fog( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + int effno = 0; + effno = 233; + nullpo_retr(-1, sd); + if (effno < 0 || map[sd->bl.m].flag.fog) + return -1; + + map[sd->bl.m].flag.fog=1; + clif_specialeffect(&sd->bl,effno,2); + + return 0; +} + +/*========================================== + * Fallen leaves fall. + *------------------------------------------ + */ +int +atcommand_leaves( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + int effno = 0; + effno = 333; + nullpo_retr(-1, sd); + if (effno < 0 || map[sd->bl.m].flag.leaves) + return -1; + + map[sd->bl.m].flag.leaves=1; + clif_specialeffect(&sd->bl,effno,2); + return 0; +} +/*========================================== + * + *------------------------------------------ + */ +int +atcommand_summon( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char name[100]; + int mob_id = 0; + int x = 0; + int y = 0; + int id = 0; + struct mob_data *md; + unsigned int tick=gettick(); + + nullpo_retr(-1, sd); + + if (!message || !*message) + return -1; + if (sscanf(message, "%99s", name) < 1) + return -1; + + if ((mob_id = atoi(name)) == 0) + mob_id = mobdb_searchname(name); + if(mob_id == 0) + return -1; + + x = sd->bl.x + (rand() % 10 - 5); + y = sd->bl.y + (rand() % 10 - 5); + + id = mob_once_spawn(sd,"this", x, y, "--ja--", mob_id, 1, ""); + if((md=(struct mob_data *)map_id2bl(id))){ + md->master_id=sd->bl.id; + md->state.special_mob_ai=1; + md->mode=mob_db[md->class].mode|0x04; + md->deletetimer=add_timer(tick+60000,mob_timer_delete,id,0); + clif_misceffect2(&md->bl,344); + } + clif_skill_poseffect(&sd->bl,AM_CALLHOMUN,1,x,y,tick); + + return 0; +} + + +/*========================================== + * @adjcmdlvl by [MouseJstr] + * + * Temp adjust the GM level required to use a GM command + * + * Used during beta testing to allow players to use GM commands + * for short periods of time + *------------------------------------------ + */ +int +atcommand_adjcmdlvl( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + int i, newlev; + char cmd[100]; + + if (!message || !*message || sscanf(message, "%d %s", &newlev, cmd) != 2) { + clif_displaymessage(fd, "usage: @adjcmdlvl <lvl> <command>."); + return -1; + } + + for (i = 0; atcommand_info[i].type != AtCommand_None; i++) + if (strcmpi(cmd, atcommand_info[i].command+1) == 0) { + atcommand_info[i].level = newlev; + clif_displaymessage(fd, "@command level changed."); + return 0; + } + + clif_displaymessage(fd, "@command not found."); + return -1; +} + +/*========================================== + * @adjgmlvl by [MouseJstr] + * + * Create a temp GM + * + * Used during beta testing to allow players to use GM commands + * for short periods of time + *------------------------------------------ + */ +int +atcommand_adjgmlvl( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + int newlev; + char user[100]; + struct map_session_data *pl_sd; + + if (!message || !*message || sscanf(message, "%d %s", &newlev, user) != 2) { + clif_displaymessage(fd, "usage: @adjgmlvl <lvl> <user>."); + return -1; + } + + if((pl_sd=map_nick2sd((char *) user)) == NULL) + return -1; + + pc_set_gm_level(pl_sd->status.account_id, newlev); + + return 0; +} + + +/*========================================== + * @trade by [MouseJstr] + * + * Open a trade window with a remote player + * + * If I have to jump to a remote player one more time, I am + * gonna scream! + *------------------------------------------ + */ +int +atcommand_trade( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + struct map_session_data *pl_sd = NULL; + + if (!message || !*message) + return -1; + if((pl_sd=map_nick2sd((char *) message)) != NULL) { + trade_traderequest(sd, pl_sd->bl.id); + return 0; + } + return -1; +} + + +/*=========================== + * @unmute [Valaris] + *=========================== +*/ +int atcommand_unmute( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + struct map_session_data *pl_sd = NULL; + if (!message || !*message) + return -1; + + if((pl_sd=map_nick2sd((char *) message)) != NULL) { + if(pl_sd->sc_data[SC_NOCHAT].timer!=-1) { + skill_status_change_end(&pl_sd->bl,SC_NOCHAT,-1); + clif_displaymessage(sd->fd,"Player unmuted"); + } + else + clif_displaymessage(sd->fd,"Player is not muted"); + } + + return 0; +} + +#ifndef TXT_ONLY /* Begin SQL-Only commands */ + +/*========================================== + * Mail System commands by [Valaris] + *------------------------------------------ + */ +int atcommand_listmail( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + if(!battle_config.mail_system) + return 0; + + nullpo_retr(-1, sd); + + if(strlen(command)==12) + mail_check(sd,3); + else if(strlen(command)==9) + mail_check(sd,2); + else + mail_check(sd,1); + return 0; +} + +int atcommand_readmail( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + if(!battle_config.mail_system) + return 0; + + nullpo_retr(-1, sd); + + if (!message || !*message) { + clif_displaymessage(sd->fd,"You must specify a message number."); + return 0; + } + + if(strlen(command)==11) + mail_delete(sd,atoi(message)); + else + mail_read(sd,atoi(message)); + + return 0; +} + +int atcommand_sendmail( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + if(!battle_config.mail_system) + return 0; + + char name[24],text[80]; + + nullpo_retr(-1, sd); + + if (!message || !*message) { + clif_displaymessage(sd->fd,"You must specify a recipient and a message."); + return 0; + } + + if ((sscanf(message, "\"%[^\"]\" %79[^\n]", name, text) < 2) && + (sscanf(message, "%23s %79[^\n]", name, text) < 2)) { + clif_displaymessage(sd->fd,"You must specify a recipient and a message."); + return 0; + } + + if(strlen(command)==17) + mail_send(sd,name,text,1); + else + mail_send(sd,name,text,0); + + return 0; +} + +/*========================================== + * Refresh online command for SQL [Valaris] + * Will refresh and check online column of + * players and set correctly. + *------------------------------------------ + */ +int atcommand_refreshonline( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + nullpo_retr(-1, sd); + + char_online_check(); + + return 0; +} + +#endif /* end sql only */ diff --git a/misc/src/map/atcommand.h b/misc/src/map/atcommand.h new file mode 100644 index 0000000..06029a5 --- /dev/null +++ b/misc/src/map/atcommand.h @@ -0,0 +1,245 @@ +// $Id: atcommand.h 148 2004-09-30 14:05:37Z MouseJstr $ +#ifndef _ATCOMMAND_H_ +#define _ATCOMMAND_H_ + +enum AtCommandType { + AtCommand_None = -1, + AtCommand_Broadcast = 0, + AtCommand_LocalBroadcast, + AtCommand_MapMove, + AtCommand_ResetState, + AtCommand_RuraP, + AtCommand_Rura, + AtCommand_Warp, + AtCommand_Where, + AtCommand_JumpTo, + AtCommand_Jump, + AtCommand_Who, + AtCommand_Who2, + AtCommand_Who3, + AtCommand_WhoMap, + AtCommand_WhoMap2, + AtCommand_WhoMap3, + AtCommand_WhoGM, + AtCommand_Save, + AtCommand_Load, + AtCommand_Speed, + AtCommand_Storage, + AtCommand_GuildStorage, + AtCommand_Option, + AtCommand_Hide, + AtCommand_JobChange, + AtCommand_JobChange2, + AtCommand_JobChange3, + AtCommand_Die, + AtCommand_Kill, + AtCommand_Alive, + AtCommand_Kami, + AtCommand_KamiB, + AtCommand_Heal, + AtCommand_Item, + AtCommand_Item2, + AtCommand_ItemReset, + AtCommand_ItemCheck, + AtCommand_BaseLevelUp, + AtCommand_JobLevelUp, + AtCommand_H, + AtCommand_Help, + AtCommand_GM, + AtCommand_PvPOff, + AtCommand_PvPOn, + AtCommand_GvGOff, + AtCommand_GvGOn, + AtCommand_Model, + AtCommand_Go, + AtCommand_Spawn, + AtCommand_Monster, + AtCommand_KillMonster, + AtCommand_KillMonster2, + AtCommand_Refine, + AtCommand_Produce, + AtCommand_Memo, + AtCommand_GAT, + AtCommand_Packet, + AtCommand_StatusPoint, + AtCommand_SkillPoint, + AtCommand_Zeny, + AtCommand_Param, + AtCommand_Strength, + AtCommand_Agility, + AtCommand_Vitality, + AtCommand_Intelligence, + AtCommand_Dexterity, + AtCommand_Luck, + AtCommand_GuildLevelUp, + AtCommand_MakeEgg, + AtCommand_PetFriendly, + AtCommand_PetHungry, + AtCommand_PetRename, + AtCommand_CharPetRename, // by Yor + AtCommand_Recall, + AtCommand_CharacterJob, + AtCommand_CharacterJob2, + AtCommand_CharacterJob3, + AtCommand_Revive, + AtCommand_CharacterStats, + AtCommand_CharacterStatsAll, + AtCommand_CharacterOption, + AtCommand_CharacterSave, + AtCommand_CharacterLoad, + AtCommand_Night, + AtCommand_Day, + AtCommand_Doom, + AtCommand_DoomMap, + AtCommand_Raise, + AtCommand_RaiseMap, + AtCommand_CharacterBaseLevel, + AtCommand_CharacterJobLevel, + AtCommand_Kick, + AtCommand_KickAll, + AtCommand_AllSkill, + AtCommand_QuestSkill, + AtCommand_CharQuestSkill, + AtCommand_LostSkill, + AtCommand_CharLostSkill, + AtCommand_SpiritBall, + AtCommand_Party, + AtCommand_Guild, + AtCommand_AgitStart, + AtCommand_AgitEnd, + AtCommand_MapExit, + AtCommand_IDSearch, + AtCommand_CharSkReset, + AtCommand_CharStReset, + AtCommand_CharReset, + //by chbrules + AtCommand_CharModel, + AtCommand_CharSKPoint, + AtCommand_CharSTPoint, + AtCommand_CharZeny, + AtCommand_RecallAll, + AtCommand_ReloadItemDB, + AtCommand_ReloadMobDB, + AtCommand_ReloadSkillDB, +#ifndef TXT_ONLY + AtCommand_Rehash, +#else /* TXT_ONLY */ + AtCommand_ReloadScript, +#endif /* TXT_ONLY */ + AtCommand_ReloadGMDB, + AtCommand_MapInfo, + AtCommand_Dye, + AtCommand_Hstyle, + AtCommand_Hcolor, + AtCommand_StatAll, + AtCommand_CharChangeSex, // by Yor + AtCommand_CharBlock, // by Yor + AtCommand_CharBan, // by Yor + AtCommand_CharUnBlock, // by Yor + AtCommand_CharUnBan, // by Yor + AtCommand_MountPeco, // by Valaris + AtCommand_CharMountPeco, // by Yor + AtCommand_GuildSpy, // [Syrus22] + AtCommand_PartySpy, // [Syrus22] + AtCommand_RepairAll, // [Valaris] + AtCommand_GuildRecall, // by Yor + AtCommand_PartyRecall, // by Yor +// AtCommand_Nuke, // [Valaris] + AtCommand_Enablenpc, + AtCommand_Disablenpc, + AtCommand_ServerTime, // by Yor + AtCommand_CharDelItem, // by Yor + AtCommand_Jail, // by Yor + AtCommand_UnJail, // by Yor + AtCommand_Disguise, // [Valaris] + AtCommand_UnDisguise, // by Yor + AtCommand_IgnoreList, // by Yor + AtCommand_CharIgnoreList, // by Yor + AtCommand_InAll, // by Yor + AtCommand_ExAll, // by Yor + AtCommand_CharDisguise, // Kalaspuff + AtCommand_CharUnDisguise, // Kalaspuff + AtCommand_EMail, // by Yor + AtCommand_Hatch, + AtCommand_Effect, // by Apple + AtCommand_Char_Item_List, // by Yor + AtCommand_Char_Storage_List, // by Yor + AtCommand_Char_Cart_List, // by Yor + AtCommand_AddWarp, // by MouseJstr + AtCommand_Follow, // by MouseJstr + AtCommand_SkillOn, // by MouseJstr + AtCommand_SkillOff, // by MouseJstr + AtCommand_Killer, // by MouseJstr + AtCommand_NpcMove, // by MouseJstr + AtCommand_Killable, // by MouseJstr + AtCommand_CharKillable, // by MouseJstr + AtCommand_Chareffect, // by MouseJstr + AtCommand_Chardye, // by MouseJstr + AtCommand_Charhairstyle, // by MouseJstr + AtCommand_Charhaircolor, // by MouseJstr + AtCommand_Dropall, // by MouseJstr + AtCommand_Chardropall, // by MouseJstr + AtCommand_Storeall, // by MouseJstr + AtCommand_Charstoreall, // by MouseJstr + AtCommand_Skillid, // by MouseJstr + AtCommand_Useskill, // by MouseJstr + AtCommand_Summon, + AtCommand_Rain, + AtCommand_Snow, + AtCommand_Sakura, + AtCommand_Fog, + AtCommand_Leaves, + AtCommand_AdjGmLvl, + AtCommand_AdjCmdLvl, + AtCommand_Trade, + AtCommand_UnMute, + + // SQL-only commands start +#ifndef TXT_ONLY + AtCommand_CheckMail, // [Valaris] + AtCommand_ListMail, // [Valaris] + AtCommand_ListNewMail, // [Valaris] + AtCommand_ReadMail, // [Valaris] + AtCommand_SendMail, // [Valaris] + AtCommand_DeleteMail, // [Valaris] + AtCommand_SendPriorityMail, // [Valaris] + AtCommand_Sound, // [Valaris] + AtCommand_RefreshOnline, // [Valaris] + // SQL-only commands end +#endif + + // end + AtCommand_Unknown, + AtCommand_MAX +}; + +typedef enum AtCommandType AtCommandType; + +typedef struct AtCommandInfo { + AtCommandType type; + const char* command; + int level; + int (*proc)(const int, struct map_session_data*, + const char* command, const char* message); +} AtCommandInfo; + +AtCommandType +is_atcommand(const int fd, struct map_session_data* sd, const char* message, int gmlvl); + +AtCommandType atcommand( + const int level, const char* message, AtCommandInfo* info); +int get_atcommand_level(const AtCommandType type); + +char * msg_txt(int msg_number); // [Yor] + +int atcommand_item(const int fd, struct map_session_data* sd,const char* command, const char* message); // [Valaris] +int atcommand_rura(const int fd, struct map_session_data* sd,const char* command, const char* message); // [Yor] +int atcommand_spawn(const int fd, struct map_session_data* sd, const char* command, const char* message); // [Valaris] +int atcommand_jumpto(const int fd, struct map_session_data* sd, const char* command, const char* message); // [Yor] +int atcommand_recall(const int fd, struct map_session_data* sd, const char* command, const char* message); // [Yor] + +int atcommand_config_read(const char *cfgName); +int msg_config_read(const char *cfgName); + +#endif + diff --git a/misc/src/map/battle.c b/misc/src/map/battle.c new file mode 100644 index 0000000..7a6dd47 --- /dev/null +++ b/misc/src/map/battle.c @@ -0,0 +1,5374 @@ +// $Id: battle.c,v 1.10 2004/09/29 21:08:17 Akitasha Exp $ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <math.h> + +#include "battle.h" + +#include "timer.h" +#include "nullpo.h" +#include "malloc.h" + +#include "map.h" +#include "pc.h" +#include "skill.h" +#include "mob.h" +#include "itemdb.h" +#include "clif.h" +#include "pet.h" +#include "guild.h" + +#ifdef MEMWATCH +#include "memwatch.h" +#endif + +int attr_fix_table[4][10][10]; + +struct Battle_Config battle_config; + +/*========================================== + * 二点間の距離を返す + * 戻りは整数で0以上 + *------------------------------------------ + */ +static int distance(int x0,int y0,int x1,int y1) +{ + int dx,dy; + + dx=abs(x0-x1); + dy=abs(y0-y1); + return dx>dy ? dx : dy; +} + +/*========================================== + * 自分をロックしている対象の数を返す(汎用) + * 戻りは整数で0以上 + *------------------------------------------ + */ +int battle_counttargeted(struct block_list *bl,struct block_list *src,int target_lv) +{ + nullpo_retr(0, bl); + if(bl->type == BL_PC) + return pc_counttargeted((struct map_session_data *)bl,src,target_lv); + else if(bl->type == BL_MOB) + return mob_counttargeted((struct mob_data *)bl,src,target_lv); + return 0; +} +/*========================================== + * 対象のClassを返す(汎用) + * 戻りは整数で0以上 + *------------------------------------------ + */ +int battle_get_class(struct block_list *bl) +{ + nullpo_retr(0, bl); + if(bl->type==BL_MOB && (struct mob_data *)bl) + return ((struct mob_data *)bl)->class; + else if(bl->type==BL_PC && (struct map_session_data *)bl) + return ((struct map_session_data *)bl)->status.class; + else if(bl->type==BL_PET && (struct pet_data *)bl) + return ((struct pet_data *)bl)->class; + else + return 0; +} +/*========================================== + * 対象の方向を返す(汎用) + * 戻りは整数で0以上 + *------------------------------------------ + */ +int battle_get_dir(struct block_list *bl) +{ + nullpo_retr(0, bl); + if(bl->type==BL_MOB && (struct mob_data *)bl) + return ((struct mob_data *)bl)->dir; + else if(bl->type==BL_PC && (struct map_session_data *)bl) + return ((struct map_session_data *)bl)->dir; + else if(bl->type==BL_PET && (struct pet_data *)bl) + return ((struct pet_data *)bl)->dir; + else + return 0; +} +/*========================================== + * 対象のレベルを返す(汎用) + * 戻りは整数で0以上 + *------------------------------------------ + */ +int battle_get_lv(struct block_list *bl) +{ + nullpo_retr(0, bl); + if(bl->type==BL_MOB && (struct mob_data *)bl) + return mob_db[((struct mob_data *)bl)->class].lv; + else if(bl->type==BL_PC && (struct map_session_data *)bl) + return ((struct map_session_data *)bl)->status.base_level; + else if(bl->type==BL_PET && (struct pet_data *)bl) + return ((struct pet_data *)bl)->msd->pet.level; + else + return 0; +} +/*========================================== + * 対象の射程を返す(汎用) + * 戻りは整数で0以上 + *------------------------------------------ + */ +int battle_get_range(struct block_list *bl) +{ + nullpo_retr(0, bl); + if(bl->type==BL_MOB && (struct mob_data *)bl) + return mob_db[((struct mob_data *)bl)->class].range; + else if(bl->type==BL_PC && (struct map_session_data *)bl) + return ((struct map_session_data *)bl)->attackrange; + else if(bl->type==BL_PET && (struct pet_data *)bl) + return mob_db[((struct pet_data *)bl)->class].range; + else + return 0; +} +/*========================================== + * 対象のHPを返す(汎用) + * 戻りは整数で0以上 + *------------------------------------------ + */ +int battle_get_hp(struct block_list *bl) +{ + nullpo_retr(1, bl); + if(bl->type==BL_MOB && (struct mob_data *)bl) + return ((struct mob_data *)bl)->hp; + else if(bl->type==BL_PC && (struct map_session_data *)bl) + return ((struct map_session_data *)bl)->status.hp; + else + return 1; +} +/*========================================== + * 対象のMHPを返す(汎用) + * 戻りは整数で0以上 + *------------------------------------------ + */ +int battle_get_max_hp(struct block_list *bl) +{ + nullpo_retr(1, bl); + if(bl->type==BL_PC && ((struct map_session_data *)bl)) + return ((struct map_session_data *)bl)->status.max_hp; + else { + struct status_change *sc_data=battle_get_sc_data(bl); + int max_hp=1; + if(bl->type==BL_MOB && ((struct mob_data*)bl)) { + max_hp = mob_db[((struct mob_data*)bl)->class].max_hp; + if(mob_db[((struct mob_data*)bl)->class].mexp > 0) { + if(battle_config.mvp_hp_rate != 100) + max_hp = (max_hp * battle_config.mvp_hp_rate)/100; + } + else { + if(battle_config.monster_hp_rate != 100) + max_hp = (max_hp * battle_config.monster_hp_rate)/100; + } + } + else if(bl->type==BL_PET && ((struct pet_data*)bl)) { + max_hp = mob_db[((struct pet_data*)bl)->class].max_hp; + if(mob_db[((struct pet_data*)bl)->class].mexp > 0) { + if(battle_config.mvp_hp_rate != 100) + max_hp = (max_hp * battle_config.mvp_hp_rate)/100; + } + else { + if(battle_config.monster_hp_rate != 100) + max_hp = (max_hp * battle_config.monster_hp_rate)/100; + } + } + if(sc_data) { + if(sc_data[SC_APPLEIDUN].timer!=-1) + max_hp += ((5+sc_data[SC_APPLEIDUN].val1*2+((sc_data[SC_APPLEIDUN].val2+1)>>1) + +sc_data[SC_APPLEIDUN].val3/10) * max_hp)/100; + } + if(max_hp < 1) max_hp = 1; + return max_hp; + } + return 1; +} +/*========================================== + * 対象のStrを返す(汎用) + * 戻りは整数で0以上 + *------------------------------------------ + */ +int battle_get_str(struct block_list *bl) +{ + int str=0; + struct status_change *sc_data; + + nullpo_retr(0, bl); + sc_data=battle_get_sc_data(bl); + if(bl->type==BL_MOB && ((struct mob_data *)bl)) + str = mob_db[((struct mob_data *)bl)->class].str; + else if(bl->type==BL_PC && ((struct map_session_data *)bl)) + return ((struct map_session_data *)bl)->paramc[0]; + else if(bl->type==BL_PET && ((struct pet_data *)bl)) + str = mob_db[((struct pet_data *)bl)->class].str; + + if(sc_data) { + if(sc_data[SC_LOUD].timer!=-1 && sc_data[SC_QUAGMIRE].timer == -1 && bl->type != BL_PC) + str += 4; + if( sc_data[SC_BLESSING].timer != -1 && bl->type != BL_PC){ // ブレッシング + int race=battle_get_race(bl); + if(battle_check_undead(race,battle_get_elem_type(bl)) || race==6 ) str >>= 1; // 悪 魔/不死 + else str += sc_data[SC_BLESSING].val1; // その他 + } + if(sc_data[SC_TRUESIGHT].timer!=-1 && bl->type != BL_PC) // トゥルーサイト + str += 5; + } + if(str < 0) str = 0; + return str; +} +/*========================================== + * 対象のAgiを返す(汎用) + * 戻りは整数で0以上 + *------------------------------------------ + */ + +int battle_get_agi(struct block_list *bl) +{ + int agi=0; + struct status_change *sc_data; + + nullpo_retr(0, bl); + sc_data=battle_get_sc_data(bl); + if(bl->type==BL_MOB && (struct mob_data *)bl) + agi=mob_db[((struct mob_data *)bl)->class].agi; + else if(bl->type==BL_PC && (struct map_session_data *)bl) + agi=((struct map_session_data *)bl)->paramc[1]; + else if(bl->type==BL_PET && (struct pet_data *)bl) + agi=mob_db[((struct pet_data *)bl)->class].agi; + + if(sc_data) { + if( sc_data[SC_INCREASEAGI].timer!=-1 && sc_data[SC_QUAGMIRE].timer == -1 && sc_data[SC_DONTFORGETME].timer == -1 && + bl->type != BL_PC) // 速度増加(PCはpc.cで) + agi += 2+sc_data[SC_INCREASEAGI].val1; + + if(sc_data[SC_CONCENTRATE].timer!=-1 && sc_data[SC_QUAGMIRE].timer == -1 && bl->type != BL_PC) + agi += agi*(2+sc_data[SC_CONCENTRATE].val1)/100; + + if(sc_data[SC_DECREASEAGI].timer!=-1) // 速度減少 + agi -= 2+sc_data[SC_DECREASEAGI].val1; + + if(sc_data[SC_QUAGMIRE].timer!=-1 ) // クァグマイア + agi >>= 1; + if(sc_data[SC_TRUESIGHT].timer!=-1 && bl->type != BL_PC) // トゥルーサイト + agi += 5; + } + if(agi < 0) agi = 0; + return agi; +} +/*========================================== + * 対象のVitを返す(汎用) + * 戻りは整数で0以上 + *------------------------------------------ + */ +int battle_get_vit(struct block_list *bl) +{ + int vit=0; + struct status_change *sc_data; + + nullpo_retr(0, bl); + sc_data=battle_get_sc_data(bl); + if(bl->type==BL_MOB && (struct mob_data *)bl) + vit=mob_db[((struct mob_data *)bl)->class].vit; + else if(bl->type==BL_PC && (struct map_session_data *)bl) + vit=((struct map_session_data *)bl)->paramc[2]; + else if(bl->type==BL_PET && (struct pet_data *)bl) + vit=mob_db[((struct pet_data *)bl)->class].vit; + if(sc_data) { + if(sc_data[SC_STRIPARMOR].timer != -1 && bl->type!=BL_PC) + vit = vit*60/100; + if(sc_data[SC_TRUESIGHT].timer!=-1 && bl->type != BL_PC) // トゥルーサイト + vit += 5; + } + + if(vit < 0) vit = 0; + return vit; +} +/*========================================== + * 対象のIntを返す(汎用) + * 戻りは整数で0以上 + *------------------------------------------ + */ +int battle_get_int(struct block_list *bl) +{ + int int_=0; + struct status_change *sc_data; + + nullpo_retr(0, bl); + sc_data=battle_get_sc_data(bl); + if(bl->type==BL_MOB && (struct mob_data *)bl) + int_=mob_db[((struct mob_data *)bl)->class].int_; + else if(bl->type==BL_PC && (struct map_session_data *)bl) + int_=((struct map_session_data *)bl)->paramc[3]; + else if(bl->type==BL_PET && (struct pet_data *)bl) + int_=mob_db[((struct pet_data *)bl)->class].int_; + + if(sc_data) { + if( sc_data[SC_BLESSING].timer != -1 && bl->type != BL_PC){ // ブレッシング + int race=battle_get_race(bl); + if(battle_check_undead(race,battle_get_elem_type(bl)) || race==6 ) int_ >>= 1; // 悪 魔/不死 + else int_ += sc_data[SC_BLESSING].val1; // その他 + } + if( sc_data[SC_STRIPHELM].timer != -1 && bl->type != BL_PC) + int_ = int_*60/100; + if(sc_data[SC_TRUESIGHT].timer!=-1 && bl->type != BL_PC) // トゥルーサイト + int_ += 5; + } + if(int_ < 0) int_ = 0; + return int_; +} +/*========================================== + * 対象のDexを返す(汎用) + * 戻りは整数で0以上 + *------------------------------------------ + */ +int battle_get_dex(struct block_list *bl) +{ + int dex=0; + struct status_change *sc_data; + + nullpo_retr(0, bl); + sc_data=battle_get_sc_data(bl); + if(bl->type==BL_MOB && (struct mob_data *)bl) + dex=mob_db[((struct mob_data *)bl)->class].dex; + else if(bl->type==BL_PC && (struct map_session_data *)bl) + dex=((struct map_session_data *)bl)->paramc[4]; + else if(bl->type==BL_PET && (struct pet_data *)bl) + dex=mob_db[((struct pet_data *)bl)->class].dex; + + if(sc_data) { + if(sc_data[SC_CONCENTRATE].timer!=-1 && sc_data[SC_QUAGMIRE].timer == -1 && bl->type != BL_PC) + dex += dex*(2+sc_data[SC_CONCENTRATE].val1)/100; + + if( sc_data[SC_BLESSING].timer != -1 && bl->type != BL_PC){ // ブレッシング + int race=battle_get_race(bl); + if(battle_check_undead(race,battle_get_elem_type(bl)) || race==6 ) dex >>= 1; // 悪 魔/不死 + else dex += sc_data[SC_BLESSING].val1; // その他 + } + + if(sc_data[SC_QUAGMIRE].timer!=-1 ) // クァグマイア + dex >>= 1; + if(sc_data[SC_TRUESIGHT].timer!=-1 && bl->type != BL_PC) // トゥルーサイト + dex += 5; + } + if(dex < 0) dex = 0; + return dex; +} +/*========================================== + * 対象のLukを返す(汎用) + * 戻りは整数で0以上 + *------------------------------------------ + */ +int battle_get_luk(struct block_list *bl) +{ + int luk=0; + struct status_change *sc_data; + + nullpo_retr(0, bl); + sc_data=battle_get_sc_data(bl); + if(bl->type==BL_MOB && (struct mob_data *)bl) + luk=mob_db[((struct mob_data *)bl)->class].luk; + else if(bl->type==BL_PC && (struct map_session_data *)bl) + luk=((struct map_session_data *)bl)->paramc[5]; + else if(bl->type==BL_PET && (struct pet_data *)bl) + luk=mob_db[((struct pet_data *)bl)->class].luk; + + if(sc_data) { + if(sc_data[SC_GLORIA].timer!=-1 && bl->type != BL_PC) // グロリア(PCはpc.cで) + luk += 30; + if(sc_data[SC_CURSE].timer!=-1 ) // 呪い + luk=0; + if(sc_data[SC_TRUESIGHT].timer!=-1 && bl->type != BL_PC) // トゥルーサイト + luk += 5; + } + if(luk < 0) luk = 0; + return luk; +} + +/*========================================== + * 対象のFleeを返す(汎用) + * 戻りは整数で1以上 + *------------------------------------------ + */ +int battle_get_flee(struct block_list *bl) +{ + int flee=1; + struct status_change *sc_data; + + nullpo_retr(1, bl); + sc_data=battle_get_sc_data(bl); + if(bl->type==BL_PC && (struct map_session_data *)bl) + flee=((struct map_session_data *)bl)->flee; + else + flee=battle_get_agi(bl) + battle_get_lv(bl); + + if(sc_data) { + if(sc_data[SC_WHISTLE].timer!=-1 && bl->type != BL_PC) + flee += flee*(sc_data[SC_WHISTLE].val1+sc_data[SC_WHISTLE].val2 + +(sc_data[SC_WHISTLE].val3>>16))/100; + if(sc_data[SC_BLIND].timer!=-1 && bl->type != BL_PC) + flee -= flee*25/100; + if(sc_data[SC_WINDWALK].timer!=-1 && bl->type != BL_PC) // ウィンドウォーク + flee += flee*(sc_data[SC_WINDWALK].val2)/100; + if(sc_data[SC_SPIDERWEB].timer!=-1 && bl->type != BL_PC) //スパイダーウェブ + flee -= flee*50/100; + } + if(flee < 1) flee = 1; + return flee; +} +/*========================================== + * 対象のHitを返す(汎用) + * 戻りは整数で1以上 + *------------------------------------------ + */ +int battle_get_hit(struct block_list *bl) +{ + int hit=1; + struct status_change *sc_data; + + nullpo_retr(1, bl); + sc_data=battle_get_sc_data(bl); + if(bl->type==BL_PC && (struct map_session_data *)bl) + hit=((struct map_session_data *)bl)->hit; + else + hit=battle_get_dex(bl) + battle_get_lv(bl); + + if(sc_data) { + if(sc_data[SC_HUMMING].timer!=-1 && bl->type != BL_PC) // + hit += hit*(sc_data[SC_HUMMING].val1*2+sc_data[SC_HUMMING].val2 + +sc_data[SC_HUMMING].val3)/100; + if(sc_data[SC_BLIND].timer!=-1 && bl->type != BL_PC) // 呪い + hit -= hit*25/100; + if(sc_data[SC_TRUESIGHT].timer!=-1 && bl->type != BL_PC) // トゥルーサイト + hit += 3*(sc_data[SC_TRUESIGHT].val1); + if(sc_data[SC_CONCENTRATION].timer!=-1 && bl->type != BL_PC) //コンセントレーション + hit += (hit*(10*(sc_data[SC_CONCENTRATION].val1)))/100; + } + if(hit < 1) hit = 1; + return hit; +} +/*========================================== + * 対象の完全回避を返す(汎用) + * 戻りは整数で1以上 + *------------------------------------------ + */ +int battle_get_flee2(struct block_list *bl) +{ + int flee2=1; + struct status_change *sc_data; + + nullpo_retr(1, bl); + sc_data=battle_get_sc_data(bl); + if(bl->type==BL_PC && (struct map_session_data *)bl){ + flee2 = battle_get_luk(bl) + 10; + flee2 += ((struct map_session_data *)bl)->flee2 - (((struct map_session_data *)bl)->paramc[5] + 10); + } + else + flee2=battle_get_luk(bl)+1; + + if(sc_data) { + if(sc_data[SC_WHISTLE].timer!=-1 && bl->type != BL_PC) + flee2 += (sc_data[SC_WHISTLE].val1+sc_data[SC_WHISTLE].val2 + +(sc_data[SC_WHISTLE].val3&0xffff))*10; + } + if(flee2 < 1) flee2 = 1; + return flee2; +} +/*========================================== + * 対象のクリティカルを返す(汎用) + * 戻りは整数で1以上 + *------------------------------------------ + */ +int battle_get_critical(struct block_list *bl) +{ + int critical=1; + struct status_change *sc_data; + + nullpo_retr(1, bl); + sc_data=battle_get_sc_data(bl); + if(bl->type==BL_PC && (struct map_session_data *)bl){ + critical = battle_get_luk(bl)*3 + 10; + critical += ((struct map_session_data *)bl)->critical - ((((struct map_session_data *)bl)->paramc[5]*3) + 10); + } + else + critical=battle_get_luk(bl)*3 + 1; + + if(sc_data) { + if(sc_data[SC_FORTUNE].timer!=-1 && bl->type != BL_PC) + critical += (10+sc_data[SC_FORTUNE].val1+sc_data[SC_FORTUNE].val2 + +sc_data[SC_FORTUNE].val3)*10; + if(sc_data[SC_EXPLOSIONSPIRITS].timer!=-1 && bl->type != BL_PC) + critical += sc_data[SC_EXPLOSIONSPIRITS].val2; + if(sc_data[SC_TRUESIGHT].timer!=-1 && bl->type != BL_PC) //トゥルーサイト + critical += critical*sc_data[SC_TRUESIGHT].val1/100; + } + if(critical < 1) critical = 1; + return critical; +} +/*========================================== + * base_atkの取得 + * 戻りは整数で1以上 + *------------------------------------------ + */ +int battle_get_baseatk(struct block_list *bl) +{ + struct status_change *sc_data; + int batk=1; + + nullpo_retr(1, bl); + sc_data=battle_get_sc_data(bl); + if(bl->type==BL_PC && (struct map_session_data *)bl) + batk = ((struct map_session_data *)bl)->base_atk; //設定されているbase_atk + else { //それ以外なら + int str,dstr; + str = battle_get_str(bl); //STR + dstr = str/10; + batk = dstr*dstr + str; //base_atkを計算する + } + if(sc_data) { //状態異常あり + if(sc_data[SC_PROVOKE].timer!=-1 && bl->type != BL_PC) //PCでプロボック(SM_PROVOKE)状態 + batk = batk*(100+2*sc_data[SC_PROVOKE].val1)/100; //base_atk増加 + if(sc_data[SC_CURSE].timer!=-1 ) //呪われていたら + batk -= batk*25/100; //base_atkが25%減少 + if(sc_data[SC_CONCENTRATION].timer!=-1 && bl->type != BL_PC) //コンセントレーション + batk += batk*(5*sc_data[SC_CONCENTRATION].val1)/100; + } + if(batk < 1) batk = 1; //base_atkは最低でも1 + return batk; +} +/*========================================== + * 対象のAtkを返す(汎用) + * 戻りは整数で0以上 + *------------------------------------------ + */ +int battle_get_atk(struct block_list *bl) +{ + struct status_change *sc_data; + int atk=0; + + nullpo_retr(0, bl); + sc_data=battle_get_sc_data(bl); + if(bl->type==BL_PC && (struct map_session_data *)bl) + atk = ((struct map_session_data*)bl)->watk; + else if(bl->type==BL_MOB && (struct mob_data *)bl) + atk = mob_db[((struct mob_data*)bl)->class].atk1; + else if(bl->type==BL_PET && (struct pet_data *)bl) + atk = mob_db[((struct pet_data*)bl)->class].atk1; + + if(sc_data) { + if(sc_data[SC_PROVOKE].timer!=-1 && bl->type != BL_PC) + atk = atk*(100+2*sc_data[SC_PROVOKE].val1)/100; + if(sc_data[SC_CURSE].timer!=-1 ) + atk -= atk*25/100; + if(sc_data[SC_CONCENTRATION].timer!=-1 && bl->type != BL_PC) //コンセントレーション + atk += atk*(5*sc_data[SC_CONCENTRATION].val1)/100; + } + if(atk < 0) atk = 0; + return atk; +} +/*========================================== + * 対象の左手Atkを返す(汎用) + * 戻りは整数で0以上 + *------------------------------------------ + */ +int battle_get_atk_(struct block_list *bl) +{ + nullpo_retr(0, bl); + if(bl->type==BL_PC && (struct map_session_data *)bl){ + int atk=((struct map_session_data*)bl)->watk_; + + if(((struct map_session_data *)bl)->sc_data[SC_CURSE].timer!=-1 ) + atk -= atk*25/100; + return atk; + } + else + return 0; +} +/*========================================== + * 対象のAtk2を返す(汎用) + * 戻りは整数で0以上 + *------------------------------------------ + */ +int battle_get_atk2(struct block_list *bl) +{ + nullpo_retr(0, bl); + if(bl->type==BL_PC && (struct map_session_data *)bl) + return ((struct map_session_data*)bl)->watk2; + else { + struct status_change *sc_data=battle_get_sc_data(bl); + int atk2=0; + if(bl->type==BL_MOB && (struct mob_data *)bl) + atk2 = mob_db[((struct mob_data*)bl)->class].atk2; + else if(bl->type==BL_PET && (struct pet_data *)bl) + atk2 = mob_db[((struct pet_data*)bl)->class].atk2; + if(sc_data) { + if( sc_data[SC_IMPOSITIO].timer!=-1) + atk2 += sc_data[SC_IMPOSITIO].val1*5; + if( sc_data[SC_PROVOKE].timer!=-1 ) + atk2 = atk2*(100+2*sc_data[SC_PROVOKE].val1)/100; + if( sc_data[SC_CURSE].timer!=-1 ) + atk2 -= atk2*25/100; + if(sc_data[SC_DRUMBATTLE].timer!=-1) + atk2 += sc_data[SC_DRUMBATTLE].val2; + if(sc_data[SC_NIBELUNGEN].timer!=-1 && (battle_get_element(bl)/10) >= 8 ) + atk2 += sc_data[SC_NIBELUNGEN].val2; + if(sc_data[SC_STRIPWEAPON].timer!=-1) + atk2 = atk2*90/100; + if(sc_data[SC_CONCENTRATION].timer!=-1) //コンセントレーション + atk2 += atk2*(5*sc_data[SC_CONCENTRATION].val1)/100; + } + if(atk2 < 0) atk2 = 0; + return atk2; + } + return 0; +} +/*========================================== + * 対象の左手Atk2を返す(汎用) + * 戻りは整数で0以上 + *------------------------------------------ + */ +int battle_get_atk_2(struct block_list *bl) +{ + nullpo_retr(0, bl); + if(bl->type==BL_PC && (struct map_session_data *)bl) + return ((struct map_session_data*)bl)->watk_2; + else + return 0; +} +/*========================================== + * 対象のMAtk1を返す(汎用) + * 戻りは整数で0以上 + *------------------------------------------ + */ +int battle_get_matk1(struct block_list *bl) +{ + struct status_change *sc_data; + nullpo_retr(0, bl); + sc_data=battle_get_sc_data(bl); + if(bl->type==BL_MOB){ + int matk,int_=battle_get_int(bl); + matk = int_+(int_/5)*(int_/5); + + if(sc_data) + if(sc_data[SC_MINDBREAKER].timer!=-1 && bl->type != BL_PC) + matk = matk*(100+2*sc_data[SC_MINDBREAKER].val1)/100; + return matk; + } + else if(bl->type==BL_PC && (struct map_session_data *)bl) + return ((struct map_session_data *)bl)->matk1; + else if(bl->type==BL_PET){ + int matk,int_=battle_get_int(bl); + matk = int_+(int_/5)*(int_/5); + + if(sc_data) + if(sc_data[SC_MINDBREAKER].timer!=-1 && bl->type != BL_PC) + matk = matk*(100+2*sc_data[SC_MINDBREAKER].val1)/100; + return matk; + } + else + return 0; +} +/*========================================== + * 対象のMAtk2を返す(汎用) + * 戻りは整数で0以上 + *------------------------------------------ + */ +int battle_get_matk2(struct block_list *bl) +{ + struct status_change *sc_data=battle_get_sc_data(bl); + nullpo_retr(0, bl); + if(bl->type==BL_MOB){ + int matk,int_=battle_get_int(bl); + matk = int_+(int_/7)*(int_/7); + + if(sc_data) + if(sc_data[SC_MINDBREAKER].timer!=-1 && bl->type != BL_PC) + matk = matk*(100+2*sc_data[SC_MINDBREAKER].val1)/100; + return matk; + } + else if(bl->type==BL_PC && (struct map_session_data *)bl) + return ((struct map_session_data *)bl)->matk2; + else if(bl->type==BL_PET){ + int matk,int_=battle_get_int(bl); + matk = int_+(int_/7)*(int_/7); + if(sc_data) + if(sc_data[SC_MINDBREAKER].timer!=-1 && bl->type != BL_PC) + matk = matk*(100+2*sc_data[SC_MINDBREAKER].val1)/100; + return matk; + } + else + return 0; +} +/*========================================== + * 対象のDefを返す(汎用) + * 戻りは整数で0以上 + *------------------------------------------ + */ +int battle_get_def(struct block_list *bl) +{ + struct status_change *sc_data; + int def=0,skilltimer=-1,skillid=0; + + nullpo_retr(0, bl); + sc_data=battle_get_sc_data(bl); + if(bl->type==BL_PC && (struct map_session_data *)bl){ + def = ((struct map_session_data *)bl)->def; + skilltimer = ((struct map_session_data *)bl)->skilltimer; + skillid = ((struct map_session_data *)bl)->skillid; + } + else if(bl->type==BL_MOB && (struct mob_data *)bl) { + def = mob_db[((struct mob_data *)bl)->class].def; + skilltimer = ((struct mob_data *)bl)->skilltimer; + skillid = ((struct mob_data *)bl)->skillid; + } + else if(bl->type==BL_PET && (struct pet_data *)bl) + def = mob_db[((struct pet_data *)bl)->class].def; + + if(def < 1000000) { + if(sc_data) { + //キーピング時はDEF100 + if( sc_data[SC_KEEPING].timer!=-1) + def = 100; + //プロボック時は減算 + if( sc_data[SC_PROVOKE].timer!=-1 && bl->type != BL_PC) + def = (def*(100 - 6*sc_data[SC_PROVOKE].val1)+50)/100; + //戦太鼓の響き時は加算 + if( sc_data[SC_DRUMBATTLE].timer!=-1 && bl->type != BL_PC) + def += sc_data[SC_DRUMBATTLE].val3; + //毒にかかっている時は減算 + if(sc_data[SC_POISON].timer!=-1 && bl->type != BL_PC) + def = def*75/100; + //ストリップシールド時は減算 + if(sc_data[SC_STRIPSHIELD].timer!=-1 && bl->type != BL_PC) + def = def*85/100; + //シグナムクルシス時は減算 + if(sc_data[SC_SIGNUMCRUCIS].timer!=-1 && bl->type != BL_PC) + def = def * (100 - sc_data[SC_SIGNUMCRUCIS].val2)/100; + //永遠の混沌時はDEF0になる + if(sc_data[SC_ETERNALCHAOS].timer!=-1 && bl->type != BL_PC) + def = 0; + //凍結、石化時は右シフト + if(sc_data[SC_FREEZE].timer != -1 || (sc_data[SC_STONE].timer != -1 && sc_data[SC_STONE].val2 == 0)) + def >>= 1; + //コンセントレーション時は減算 + if( sc_data[SC_CONCENTRATION].timer!=-1 && bl->type != BL_PC) + def = (def*(100 - 5*sc_data[SC_CONCENTRATION].val1))/100; + } + //詠唱中は詠唱時減算率に基づいて減算 + if(skilltimer != -1) { + int def_rate = skill_get_castdef(skillid); + if(def_rate != 0) + def = (def * (100 - def_rate))/100; + } + } + if(def < 0) def = 0; + return def; +} +/*========================================== + * 対象のMDefを返す(汎用) + * 戻りは整数で0以上 + *------------------------------------------ + */ +int battle_get_mdef(struct block_list *bl) +{ + struct status_change *sc_data; + int mdef=0; + + nullpo_retr(0, bl); + sc_data=battle_get_sc_data(bl); + if(bl->type==BL_PC && (struct map_session_data *)bl) + mdef = ((struct map_session_data *)bl)->mdef; + else if(bl->type==BL_MOB && (struct mob_data *)bl) + mdef = mob_db[((struct mob_data *)bl)->class].mdef; + else if(bl->type==BL_PET && (struct pet_data *)bl) + mdef = mob_db[((struct pet_data *)bl)->class].mdef; + + if(mdef < 1000000) { + if(sc_data) { + //バリアー状態時はMDEF100 + if(sc_data[SC_BARRIER].timer != -1) + mdef = 100; + //凍結、石化時は1.25倍 + if(sc_data[SC_FREEZE].timer != -1 || (sc_data[SC_STONE].timer != -1 && sc_data[SC_STONE].val2 == 0)) + mdef = mdef*125/100; + if( sc_data[SC_MINDBREAKER].timer!=-1 && bl->type != BL_PC) + mdef -= (mdef*6*sc_data[SC_MINDBREAKER].val1)/100; + } + } + if(mdef < 0) mdef = 0; + return mdef; +} +/*========================================== + * 対象のDef2を返す(汎用) + * 戻りは整数で1以上 + *------------------------------------------ + */ +int battle_get_def2(struct block_list *bl) +{ + struct status_change *sc_data; + int def2=1; + + nullpo_retr(1, bl); + sc_data=battle_get_sc_data(bl); + if(bl->type==BL_PC) + def2 = ((struct map_session_data *)bl)->def2; + else if(bl->type==BL_MOB) + def2 = mob_db[((struct mob_data *)bl)->class].vit; + else if(bl->type==BL_PET) + def2 = mob_db[((struct pet_data *)bl)->class].vit; + + if(sc_data) { + if( sc_data[SC_ANGELUS].timer!=-1 && bl->type != BL_PC) + def2 = def2*(110+5*sc_data[SC_ANGELUS].val1)/100; + if( sc_data[SC_PROVOKE].timer!=-1 && bl->type != BL_PC) + def2 = (def2*(100 - 6*sc_data[SC_PROVOKE].val1)+50)/100; + if(sc_data[SC_POISON].timer!=-1 && bl->type != BL_PC) + def2 = def2*75/100; + //コンセントレーション時は減算 + if( sc_data[SC_CONCENTRATION].timer!=-1 && bl->type != BL_PC) + def2 = def2*(100 - 5*sc_data[SC_CONCENTRATION].val1)/100; + } + if(def2 < 1) def2 = 1; + return def2; +} +/*========================================== + * 対象のMDef2を返す(汎用) + * 戻りは整数で0以上 + *------------------------------------------ + */ +int battle_get_mdef2(struct block_list *bl) +{ + int mdef2=0; + struct status_change *sc_data=battle_get_sc_data(bl); + + nullpo_retr(0, bl); + if(bl->type==BL_MOB) + mdef2 = mob_db[((struct mob_data *)bl)->class].int_ + (mob_db[((struct mob_data *)bl)->class].vit>>1); + else if(bl->type==BL_PC) + mdef2 = ((struct map_session_data *)bl)->mdef2 + (((struct map_session_data *)bl)->paramc[2]>>1); + else if(bl->type==BL_PET) + mdef2 = mob_db[((struct pet_data *)bl)->class].int_ + (mob_db[((struct pet_data *)bl)->class].vit>>1); + if(sc_data) { + if( sc_data[SC_MINDBREAKER].timer!=-1 && bl->type != BL_PC) + mdef2 -= (mdef2*6*sc_data[SC_MINDBREAKER].val1)/100; + } + if(mdef2 < 0) mdef2 = 0; + return mdef2; +} +/*========================================== + * 対象のSpeed(移動速度)を返す(汎用) + * 戻りは整数で1以上 + * Speedは小さいほうが移動速度が速い + *------------------------------------------ + */ +int battle_get_speed(struct block_list *bl) +{ + nullpo_retr(1000, bl); + if(bl->type==BL_PC && (struct map_session_data *)bl) + return ((struct map_session_data *)bl)->speed; + else { + struct status_change *sc_data=battle_get_sc_data(bl); + int speed = 1000; + if(bl->type==BL_MOB && (struct mob_data *)bl) +// speed = mob_db[((struct mob_data *)bl)->class].speed; + speed = ((struct mob_data *)bl)->speed; + else if(bl->type==BL_PET && (struct pet_data *)bl) + speed = ((struct pet_data *)bl)->msd->petDB->speed; + + if(sc_data) { + //速度増加時は25%減算 + if(sc_data[SC_INCREASEAGI].timer!=-1 && sc_data[SC_DONTFORGETME].timer == -1) + speed -= speed*25/100; + //速度減少時は25%加算 + if(sc_data[SC_DECREASEAGI].timer!=-1) + speed = speed*125/100; + //クァグマイア時は50%加算 + if(sc_data[SC_QUAGMIRE].timer!=-1) + speed = speed*3/2; + //私を忘れないで…時は加算 + if(sc_data[SC_DONTFORGETME].timer!=-1) + speed = speed*(100+sc_data[SC_DONTFORGETME].val1*2 + sc_data[SC_DONTFORGETME].val2 + (sc_data[SC_DONTFORGETME].val3&0xffff))/100; + //金剛時は25%加算 + if(sc_data[SC_STEELBODY].timer!=-1) + speed = speed*125/100; + //ディフェンダー時は加算 + if(sc_data[SC_DEFENDER].timer!=-1) + speed = (speed * (155 - sc_data[SC_DEFENDER].val1*5)) / 100; + //踊り状態は4倍遅い + if(sc_data[SC_DANCING].timer!=-1 ) + speed*=4; + //呪い時は450加算 + if(sc_data[SC_CURSE].timer!=-1) + speed = speed + 450; + //ウィンドウォーク時はLv*2%減算 + if(sc_data[SC_WINDWALK].timer!=-1) + speed -= (speed*(sc_data[SC_WINDWALK].val1*2))/100; + } + if(speed < 1) speed = 1; + return speed; + } + + return 1000; +} +/*========================================== + * 対象のaDelay(攻撃時ディレイ)を返す(汎用) + * aDelayは小さいほうが攻撃速度が速い + *------------------------------------------ + */ +int battle_get_adelay(struct block_list *bl) +{ + nullpo_retr(4000, bl); + if(bl->type==BL_PC && (struct map_session_data *)bl) + return (((struct map_session_data *)bl)->aspd<<1); + else { + struct status_change *sc_data=battle_get_sc_data(bl); + int adelay=4000,aspd_rate = 100,i; + if(bl->type==BL_MOB && (struct mob_data *)bl) + adelay = mob_db[((struct mob_data *)bl)->class].adelay; + else if(bl->type==BL_PET && (struct pet_data *)bl) + adelay = mob_db[((struct pet_data *)bl)->class].adelay; + + if(sc_data) { + //ツーハンドクイッケン使用時でクァグマイアでも私を忘れないで…でもない時は3割減算 + if(sc_data[SC_TWOHANDQUICKEN].timer != -1 && sc_data[SC_QUAGMIRE].timer == -1 && sc_data[SC_DONTFORGETME].timer == -1) // 2HQ + aspd_rate -= 30; + //アドレナリンラッシュ使用時でツーハンドクイッケンでもクァグマイアでも私を忘れないで…でもない時は + if(sc_data[SC_ADRENALINE].timer != -1 && sc_data[SC_TWOHANDQUICKEN].timer == -1 && + sc_data[SC_QUAGMIRE].timer == -1 && sc_data[SC_DONTFORGETME].timer == -1) { // アドレナリンラッシュ + //使用者とパーティメンバーで格差が出る設定でなければ3割減算 + if(sc_data[SC_ADRENALINE].val2 || !battle_config.party_skill_penaly) + aspd_rate -= 30; + //そうでなければ2.5割減算 + else + aspd_rate -= 25; + } + //スピアクィッケン時は減算 + if(sc_data[SC_SPEARSQUICKEN].timer != -1 && sc_data[SC_ADRENALINE].timer == -1 && + sc_data[SC_TWOHANDQUICKEN].timer == -1 && sc_data[SC_QUAGMIRE].timer == -1 && sc_data[SC_DONTFORGETME].timer == -1) // スピアクィッケン + aspd_rate -= sc_data[SC_SPEARSQUICKEN].val2; + //夕日のアサシンクロス時は減算 + if(sc_data[SC_ASSNCROS].timer!=-1 && // 夕陽のアサシンクロス + sc_data[SC_TWOHANDQUICKEN].timer==-1 && sc_data[SC_ADRENALINE].timer==-1 && sc_data[SC_SPEARSQUICKEN].timer==-1 && + sc_data[SC_DONTFORGETME].timer == -1) + aspd_rate -= 5+sc_data[SC_ASSNCROS].val1+sc_data[SC_ASSNCROS].val2+sc_data[SC_ASSNCROS].val3; + //私を忘れないで…時は加算 + if(sc_data[SC_DONTFORGETME].timer!=-1) // 私を忘れないで + aspd_rate += sc_data[SC_DONTFORGETME].val1*3 + sc_data[SC_DONTFORGETME].val2 + (sc_data[SC_DONTFORGETME].val3>>16); + //金剛時25%加算 + if(sc_data[SC_STEELBODY].timer!=-1) // 金剛 + aspd_rate += 25; + //増速ポーション使用時は減算 + if( sc_data[i=SC_SPEEDPOTION2].timer!=-1 || sc_data[i=SC_SPEEDPOTION1].timer!=-1 || sc_data[i=SC_SPEEDPOTION0].timer!=-1) + aspd_rate -= sc_data[i].val2; + //ディフェンダー時は加算 + if(sc_data[SC_DEFENDER].timer != -1) + adelay += (1100 - sc_data[SC_DEFENDER].val1*100); + } + if(aspd_rate != 100) + adelay = adelay*aspd_rate/100; + if(adelay < battle_config.monster_max_aspd<<1) adelay = battle_config.monster_max_aspd<<1; + return adelay; + } + return 4000; +} +int battle_get_amotion(struct block_list *bl) +{ + nullpo_retr(2000, bl); + if(bl->type==BL_PC && (struct map_session_data *)bl) + return ((struct map_session_data *)bl)->amotion; + else { + struct status_change *sc_data=battle_get_sc_data(bl); + int amotion=2000,aspd_rate = 100,i; + if(bl->type==BL_MOB && (struct mob_data *)bl) + amotion = mob_db[((struct mob_data *)bl)->class].amotion; + else if(bl->type==BL_PET && (struct pet_data *)bl) + amotion = mob_db[((struct pet_data *)bl)->class].amotion; + + if(sc_data) { + if(sc_data[SC_TWOHANDQUICKEN].timer != -1 && sc_data[SC_QUAGMIRE].timer == -1 && sc_data[SC_DONTFORGETME].timer == -1) // 2HQ + aspd_rate -= 30; + if(sc_data[SC_ADRENALINE].timer != -1 && sc_data[SC_TWOHANDQUICKEN].timer == -1 && + sc_data[SC_QUAGMIRE].timer == -1 && sc_data[SC_DONTFORGETME].timer == -1) { // アドレナリンラッシュ + if(sc_data[SC_ADRENALINE].val2 || !battle_config.party_skill_penaly) + aspd_rate -= 30; + else + aspd_rate -= 25; + } + if(sc_data[SC_SPEARSQUICKEN].timer != -1 && sc_data[SC_ADRENALINE].timer == -1 && + sc_data[SC_TWOHANDQUICKEN].timer == -1 && sc_data[SC_QUAGMIRE].timer == -1 && sc_data[SC_DONTFORGETME].timer == -1) // スピアクィッケン + aspd_rate -= sc_data[SC_SPEARSQUICKEN].val2; + if(sc_data[SC_ASSNCROS].timer!=-1 && // 夕陽のアサシンクロス + sc_data[SC_TWOHANDQUICKEN].timer==-1 && sc_data[SC_ADRENALINE].timer==-1 && sc_data[SC_SPEARSQUICKEN].timer==-1 && + sc_data[SC_DONTFORGETME].timer == -1) + aspd_rate -= 5+sc_data[SC_ASSNCROS].val1+sc_data[SC_ASSNCROS].val2+sc_data[SC_ASSNCROS].val3; + if(sc_data[SC_DONTFORGETME].timer!=-1) // 私を忘れないで + aspd_rate += sc_data[SC_DONTFORGETME].val1*3 + sc_data[SC_DONTFORGETME].val2 + (sc_data[SC_DONTFORGETME].val3>>16); + if(sc_data[SC_STEELBODY].timer!=-1) // 金剛 + aspd_rate += 25; + if( sc_data[i=SC_SPEEDPOTION2].timer!=-1 || sc_data[i=SC_SPEEDPOTION1].timer!=-1 || sc_data[i=SC_SPEEDPOTION0].timer!=-1) + aspd_rate -= sc_data[i].val2; + if(sc_data[SC_DEFENDER].timer != -1) + amotion += (550 - sc_data[SC_DEFENDER].val1*50); + } + if(aspd_rate != 100) + amotion = amotion*aspd_rate/100; + if(amotion < battle_config.monster_max_aspd) amotion = battle_config.monster_max_aspd; + return amotion; + } + return 2000; +} +int battle_get_dmotion(struct block_list *bl) +{ + int ret; + struct status_change *sc_data; + + nullpo_retr(0, bl); + sc_data = battle_get_sc_data(bl); + if(bl->type==BL_MOB && (struct mob_data *)bl){ + ret=mob_db[((struct mob_data *)bl)->class].dmotion; + if(battle_config.monster_damage_delay_rate != 100) + ret = ret*battle_config.monster_damage_delay_rate/400; + } + else if(bl->type==BL_PC && (struct map_session_data *)bl){ + ret=((struct map_session_data *)bl)->dmotion; + if(battle_config.pc_damage_delay_rate != 100) + ret = ret*battle_config.pc_damage_delay_rate/400; + } + else if(bl->type==BL_PET && (struct pet_data *)bl) + ret=mob_db[((struct pet_data *)bl)->class].dmotion; + else + return 2000; + + if((sc_data && sc_data[SC_ENDURE].timer!=-1) || + (bl->type == BL_PC && ((struct map_session_data *)bl)->special_state.infinite_endure)) + ret=0; + + return ret; +} +int battle_get_element(struct block_list *bl) +{ + int ret = 20; + struct status_change *sc_data; + + nullpo_retr(ret, bl); + sc_data = battle_get_sc_data(bl); + if(bl->type==BL_MOB && (struct mob_data *)bl) // 10の位=Lv*2、1の位=属性 + ret=((struct mob_data *)bl)->def_ele; + else if(bl->type==BL_PC && (struct map_session_data *)bl) + ret=20+((struct map_session_data *)bl)->def_ele; // 防御属性Lv1 + else if(bl->type==BL_PET && (struct pet_data *)bl) + ret = mob_db[((struct pet_data *)bl)->class].element; + + if(sc_data) { + if( sc_data[SC_BENEDICTIO].timer!=-1 ) // 聖体降福 + ret=26; + if( sc_data[SC_FREEZE].timer!=-1 ) // 凍結 + ret=21; + if( sc_data[SC_STONE].timer!=-1 && sc_data[SC_STONE].val2==0) + ret=22; + } + + return ret; +} +int battle_get_attack_element(struct block_list *bl) +{ + int ret = 0; + struct status_change *sc_data=battle_get_sc_data(bl); + + nullpo_retr(0, bl); + if(bl->type==BL_MOB && (struct mob_data *)bl) + ret=0; + else if(bl->type==BL_PC && (struct map_session_data *)bl) + ret=((struct map_session_data *)bl)->atk_ele; + else if(bl->type==BL_PET && (struct pet_data *)bl) + ret=0; + + if(sc_data) { + if( sc_data[SC_FROSTWEAPON].timer!=-1) // フロストウェポン + ret=1; + if( sc_data[SC_SEISMICWEAPON].timer!=-1) // サイズミックウェポン + ret=2; + if( sc_data[SC_FLAMELAUNCHER].timer!=-1) // フレームランチャー + ret=3; + if( sc_data[SC_LIGHTNINGLOADER].timer!=-1) // ライトニングローダー + ret=4; + if( sc_data[SC_ENCPOISON].timer!=-1) // エンチャントポイズン + ret=5; + if( sc_data[SC_ASPERSIO].timer!=-1) // アスペルシオ + ret=6; + } + + return ret; +} +int battle_get_attack_element2(struct block_list *bl) +{ + nullpo_retr(0, bl); + if(bl->type==BL_PC && (struct map_session_data *)bl) { + int ret = ((struct map_session_data *)bl)->atk_ele_; + struct status_change *sc_data = ((struct map_session_data *)bl)->sc_data; + + if(sc_data) { + if( sc_data[SC_FROSTWEAPON].timer!=-1) // フロストウェポン + ret=1; + if( sc_data[SC_SEISMICWEAPON].timer!=-1) // サイズミックウェポン + ret=2; + if( sc_data[SC_FLAMELAUNCHER].timer!=-1) // フレームランチャー + ret=3; + if( sc_data[SC_LIGHTNINGLOADER].timer!=-1) // ライトニングローダー + ret=4; + if( sc_data[SC_ENCPOISON].timer!=-1) // エンチャントポイズン + ret=5; + if( sc_data[SC_ASPERSIO].timer!=-1) // アスペルシオ + ret=6; + } + return ret; + } + return 0; +} +int battle_get_party_id(struct block_list *bl) +{ + nullpo_retr(0, bl); + if(bl->type==BL_PC && (struct map_session_data *)bl) + return ((struct map_session_data *)bl)->status.party_id; + else if(bl->type==BL_MOB && (struct mob_data *)bl){ + struct mob_data *md=(struct mob_data *)bl; + if( md->master_id>0 ) + return -md->master_id; + return -md->bl.id; + } + else if(bl->type==BL_SKILL && (struct skill_unit *)bl) + return ((struct skill_unit *)bl)->group->party_id; + else + return 0; +} +int battle_get_guild_id(struct block_list *bl) +{ + nullpo_retr(0, bl); + if(bl->type==BL_PC && (struct map_session_data *)bl) + return ((struct map_session_data *)bl)->status.guild_id; + else if(bl->type==BL_MOB && (struct mob_data *)bl) + return ((struct mob_data *)bl)->class; + else if(bl->type==BL_SKILL && (struct skill_unit *)bl) + return ((struct skill_unit *)bl)->group->guild_id; + else + return 0; +} +int battle_get_race(struct block_list *bl) +{ + nullpo_retr(0, bl); + if(bl->type==BL_MOB && (struct mob_data *)bl) + return mob_db[((struct mob_data *)bl)->class].race; + else if(bl->type==BL_PC && (struct map_session_data *)bl) + return 7; + else if(bl->type==BL_PET && (struct pet_data *)bl) + return mob_db[((struct pet_data *)bl)->class].race; + else + return 0; +} +int battle_get_size(struct block_list *bl) +{ + nullpo_retr(1, bl); + if(bl->type==BL_MOB && (struct mob_data *)bl) + return mob_db[((struct mob_data *)bl)->class].size; + else if(bl->type==BL_PC && (struct map_session_data *)bl) + return 1; + else if(bl->type==BL_PET && (struct pet_data *)bl) + return mob_db[((struct pet_data *)bl)->class].size; + else + return 1; +} +int battle_get_mode(struct block_list *bl) +{ + nullpo_retr(0x01, bl); + if(bl->type==BL_MOB && (struct mob_data *)bl) + return mob_db[((struct mob_data *)bl)->class].mode; + else if(bl->type==BL_PET && (struct pet_data *)bl) + return mob_db[((struct pet_data *)bl)->class].mode; + else + return 0x01; // とりあえず動くということで1 +} + +int battle_get_mexp(struct block_list *bl) +{ + nullpo_retr(0, bl); + if(bl->type==BL_MOB && (struct mob_data *)bl) + return mob_db[((struct mob_data *)bl)->class].mexp; + else if(bl->type==BL_PET && (struct pet_data *)bl) + return mob_db[((struct pet_data *)bl)->class].mexp; + else + return 0; +} + +// StatusChange系の所得 +struct status_change *battle_get_sc_data(struct block_list *bl) +{ + nullpo_retr(NULL, bl); + if(bl->type==BL_MOB && (struct mob_data *)bl) + return ((struct mob_data*)bl)->sc_data; + else if(bl->type==BL_PC && (struct map_session_data *)bl) + return ((struct map_session_data*)bl)->sc_data; + return NULL; +} +short *battle_get_sc_count(struct block_list *bl) +{ + nullpo_retr(NULL, bl); + if(bl->type==BL_MOB && (struct mob_data *)bl) + return &((struct mob_data*)bl)->sc_count; + else if(bl->type==BL_PC && (struct map_session_data *)bl) + return &((struct map_session_data*)bl)->sc_count; + return NULL; +} +short *battle_get_opt1(struct block_list *bl) +{ + nullpo_retr(0, bl); + if(bl->type==BL_MOB && (struct mob_data *)bl) + return &((struct mob_data*)bl)->opt1; + else if(bl->type==BL_PC && (struct map_session_data *)bl) + return &((struct map_session_data*)bl)->opt1; + else if(bl->type==BL_NPC && (struct npc_data *)bl) + return &((struct npc_data*)bl)->opt1; + return 0; +} +short *battle_get_opt2(struct block_list *bl) +{ + nullpo_retr(0, bl); + if(bl->type==BL_MOB && (struct mob_data *)bl) + return &((struct mob_data*)bl)->opt2; + else if(bl->type==BL_PC && (struct map_session_data *)bl) + return &((struct map_session_data*)bl)->opt2; + else if(bl->type==BL_NPC && (struct npc_data *)bl) + return &((struct npc_data*)bl)->opt2; + return 0; +} +short *battle_get_opt3(struct block_list *bl) +{ + nullpo_retr(0, bl); + if(bl->type==BL_MOB && (struct mob_data *)bl) + return &((struct mob_data*)bl)->opt3; + else if(bl->type==BL_PC && (struct map_session_data *)bl) + return &((struct map_session_data*)bl)->opt3; + else if(bl->type==BL_NPC && (struct npc_data *)bl) + return &((struct npc_data*)bl)->opt3; + return 0; +} +short *battle_get_option(struct block_list *bl) +{ + nullpo_retr(0, bl); + if(bl->type==BL_MOB && (struct mob_data *)bl) + return &((struct mob_data*)bl)->option; + else if(bl->type==BL_PC && (struct map_session_data *)bl) + return &((struct map_session_data*)bl)->status.option; + else if(bl->type==BL_NPC && (struct npc_data *)bl) + return &((struct npc_data*)bl)->option; + return 0; +} + +//------------------------------------------------------------------- + +// ダメージの遅延 +struct battle_delay_damage_ { + struct block_list *src,*target; + int damage; + int flag; +}; +int battle_delay_damage_sub(int tid,unsigned int tick,int id,int data) +{ + struct battle_delay_damage_ *dat=(struct battle_delay_damage_ *)data; + if( dat && map_id2bl(id)==dat->src && dat->target->prev!=NULL) + battle_damage(dat->src,dat->target,dat->damage,dat->flag); + free(dat); + return 0; +} +int battle_delay_damage(unsigned int tick,struct block_list *src,struct block_list *target,int damage,int flag) +{ + struct battle_delay_damage_ *dat = (struct battle_delay_damage_*)aCalloc(1,sizeof(struct battle_delay_damage_)); + + nullpo_retr(0, src); + nullpo_retr(0, target); + + + dat->src=src; + dat->target=target; + dat->damage=damage; + dat->flag=flag; + add_timer(tick,battle_delay_damage_sub,src->id,(int)dat); + return 0; +} + +// 実際にHPを操作 +int battle_damage(struct block_list *bl,struct block_list *target,int damage,int flag) +{ + struct map_session_data *sd=NULL; + struct status_change *sc_data=battle_get_sc_data(target); + short *sc_count; + int i; + + nullpo_retr(0, target); //blはNULLで呼ばれることがあるので他でチェック + + if(damage==0 || target->type == BL_PET) + return 0; + + if(target->prev == NULL) + return 0; + + if(bl) { + if(bl->prev==NULL) + return 0; + + if(bl->type==BL_PC) + sd=(struct map_session_data *)bl; + } + + if(damage<0) + return battle_heal(bl,target,-damage,0,flag); + + if(!flag && (sc_count=battle_get_sc_count(target))!=NULL && *sc_count>0){ + // 凍結、石化、睡眠を消去 + if(sc_data[SC_FREEZE].timer!=-1) + skill_status_change_end(target,SC_FREEZE,-1); + if(sc_data[SC_STONE].timer!=-1 && sc_data[SC_STONE].val2==0) + skill_status_change_end(target,SC_STONE,-1); + if(sc_data[SC_SLEEP].timer!=-1) + skill_status_change_end(target,SC_SLEEP,-1); + } + + if(target->type==BL_MOB){ // MOB + struct mob_data *md=(struct mob_data *)target; + if(md && md->skilltimer!=-1 && md->state.skillcastcancel) // 詠唱妨害 + skill_castcancel(target,0); + return mob_damage(bl,md,damage,0); + } + else if(target->type==BL_PC){ // PC + + struct map_session_data *tsd=(struct map_session_data *)target; + + if(tsd && tsd->sc_data && tsd->sc_data[SC_DEVOTION].val1){ // ディボーションをかけられている + struct map_session_data *md = map_id2sd(tsd->sc_data[SC_DEVOTION].val1); + if(md && skill_devotion3(&md->bl,target->id)){ + skill_devotion(md,target->id); + } + else if(md && bl) + for(i=0;i<5;i++) + if(md->dev.val1[i] == target->id){ + clif_damage(bl,&md->bl, gettick(), 0, 0, + damage, 0 , 0, 0); + pc_damage(&md->bl,md,damage); + + return 0; + } + } + + if(tsd && tsd->skilltimer!=-1){ // 詠唱妨害 + // フェンカードや妨害されないスキルかの検査 + if( (!tsd->special_state.no_castcancel || map[bl->m].flag.gvg) && tsd->state.skillcastcancel && + !tsd->special_state.no_castcancel2) + skill_castcancel(target,0); + } + + return pc_damage(bl,tsd,damage); + + } + else if(target->type==BL_SKILL) + return skill_unit_ondamaged((struct skill_unit *)target,bl,damage,gettick()); + return 0; +} +int battle_heal(struct block_list *bl,struct block_list *target,int hp,int sp,int flag) +{ + nullpo_retr(0, target); //blはNULLで呼ばれることがあるので他でチェック + + if(target->type == BL_PET) + return 0; + if( target->type ==BL_PC && pc_isdead((struct map_session_data *)target) ) + return 0; + if(hp==0 && sp==0) + return 0; + + if(hp<0) + return battle_damage(bl,target,-hp,flag); + + if(target->type==BL_MOB) + return mob_heal((struct mob_data *)target,hp); + else if(target->type==BL_PC) + return pc_heal((struct map_session_data *)target,hp,sp); + return 0; +} + +// 攻撃停止 +int battle_stopattack(struct block_list *bl) +{ + nullpo_retr(0, bl); + if(bl->type==BL_MOB) + return mob_stopattack((struct mob_data*)bl); + else if(bl->type==BL_PC) + return pc_stopattack((struct map_session_data*)bl); + else if(bl->type==BL_PET) + return pet_stopattack((struct pet_data*)bl); + return 0; +} +// 移動停止 +int battle_stopwalking(struct block_list *bl,int type) +{ + nullpo_retr(0, bl); + if(bl->type==BL_MOB) + return mob_stop_walking((struct mob_data*)bl,type); + else if(bl->type==BL_PC) + return pc_stop_walking((struct map_session_data*)bl,type); + else if(bl->type==BL_PET) + return pet_stop_walking((struct pet_data*)bl,type); + return 0; +} + + +/*========================================== + * ダメージの属性修正 + *------------------------------------------ + */ +int battle_attr_fix(int damage,int atk_elem,int def_elem) +{ + int def_type= def_elem%10, def_lv=def_elem/10/2; + + if( atk_elem<0 || atk_elem>9 || def_type<0 || def_type>9 || + def_lv<1 || def_lv>4){ // 属 性値がおかしいのでとりあえずそのまま返す + if(battle_config.error_log) + printf("battle_attr_fix: unknown attr type: atk=%d def_type=%d def_lv=%d\n",atk_elem,def_type,def_lv); + return damage; + } + + return damage*attr_fix_table[def_lv-1][atk_elem][def_type]/100; +} + + +/*========================================== + * ダメージ最終計算 + *------------------------------------------ + */ +int battle_calc_damage(struct block_list *src,struct block_list *bl,int damage,int div_,int skill_num,int skill_lv,int flag) +{ + struct map_session_data *sd=NULL; + struct mob_data *md=NULL; + struct status_change *sc_data,*sc; + short *sc_count; + int class; + + nullpo_retr(0, bl); + + class = battle_get_class(bl); + if(bl->type==BL_MOB) md=(struct mob_data *)bl; + else sd=(struct map_session_data *)bl; + + sc_data=battle_get_sc_data(bl); + sc_count=battle_get_sc_count(bl); + + if(sc_count!=NULL && *sc_count>0){ + + if(sc_data[SC_SAFETYWALL].timer!=-1 && damage>0 && flag&BF_WEAPON && flag&BF_SHORT && skill_num != NPC_GUIDEDATTACK){ + // セーフティウォール + struct skill_unit *unit=(struct skill_unit*)sc_data[SC_SAFETYWALL].val2; + if( unit && unit->alive && (--unit->group->val2)<=0 ) + skill_delunit(unit); + skill_unit_move(bl,gettick(),1); // 重ね掛けチェック + damage=0; + } + if(sc_data[SC_PNEUMA].timer!=-1 && damage>0 && flag&BF_WEAPON && flag&BF_LONG && skill_num != NPC_GUIDEDATTACK){ + // ニューマ + damage=0; + } + + if(sc_data[SC_ROKISWEIL].timer!=-1 && damage>0 && + flag&BF_MAGIC ){ + // ニューマ + damage=0; + } + + if(sc_data[SC_AETERNA].timer!=-1 && damage>0){ // レックスエーテルナ + damage<<=1; + skill_status_change_end( bl,SC_AETERNA,-1 ); + } + + //属性場のダメージ増加 + if(sc_data[SC_VOLCANO].timer!=-1){ // ボルケーノ + if(flag&BF_SKILL && skill_get_pl(skill_num)==3) + damage += damage*sc_data[SC_VOLCANO].val4/100; + else if(!flag&BF_SKILL && battle_get_attack_element(bl)==3) + damage += damage*sc_data[SC_VOLCANO].val4/100; + } + + if(sc_data[SC_VIOLENTGALE].timer!=-1){ // バイオレントゲイル + if(flag&BF_SKILL && skill_get_pl(skill_num)==4) + damage += damage*sc_data[SC_VIOLENTGALE].val4/100; + else if(!flag&BF_SKILL && battle_get_attack_element(bl)==4) + damage += damage*sc_data[SC_VIOLENTGALE].val4/100; + } + + if(sc_data[SC_DELUGE].timer!=-1){ // デリュージ + if(flag&BF_SKILL && skill_get_pl(skill_num)==1) + damage += damage*sc_data[SC_DELUGE].val4/100; + else if(!flag&BF_SKILL && battle_get_attack_element(bl)==1) + damage += damage*sc_data[SC_DELUGE].val4/100; + } + + if(sc_data[SC_ENERGYCOAT].timer!=-1 && damage>0 && flag&BF_WEAPON){ // エナジーコート + if(sd){ + if(sd->status.sp>0){ + int per = sd->status.sp * 5 / (sd->status.max_sp + 1); + sd->status.sp -= sd->status.sp * (per * 5 + 10) / 1000; + if( sd->status.sp < 0 ) sd->status.sp = 0; + damage -= damage * ((per+1) * 6) / 100; + clif_updatestatus(sd,SP_SP); + } + if(sd->status.sp<=0) + skill_status_change_end( bl,SC_ENERGYCOAT,-1 ); + } + else + damage -= damage * (sc_data[SC_ENERGYCOAT].val1 * 6) / 100; + } + + if(sc_data[SC_KYRIE].timer!=-1 && damage > 0){ // キリエエレイソン + sc=&sc_data[SC_KYRIE]; + sc->val2-=damage; + if(flag&BF_WEAPON){ + if(sc->val2>=0) damage=0; + else damage=-sc->val2; + } + if((--sc->val3)<=0 || (sc->val2<=0) || skill_num == AL_HOLYLIGHT) + skill_status_change_end(bl, SC_KYRIE, -1); + } + + if(sc_data[SC_BASILICA].timer!=-1 && damage > 0){ + // ニューマ + damage=0; + } + if(sc_data[SC_LANDPROTECTOR].timer!=-1 && damage>0 && flag&BF_MAGIC){ + // ニューマ + damage=0; + } + + if(sc_data[SC_AUTOGUARD].timer != -1 && damage > 0 && flag&BF_WEAPON) { + if(rand()%100 < sc_data[SC_AUTOGUARD].val2) { + damage = 0; + clif_skill_nodamage(bl,bl,CR_AUTOGUARD,sc_data[SC_AUTOGUARD].val1,1); + if(sd) + sd->canmove_tick = gettick() + 300; + else if(md) + md->canmove_tick = gettick() + 300; + } + } +// -- moonsoul (chance to block attacks with new Lord Knight skill parrying) +// + if(sc_data[SC_PARRYING].timer != -1 && damage > 0 && flag&BF_WEAPON) { + if(rand()%100 < sc_data[SC_PARRYING].val2) { + damage = 0; + clif_skill_nodamage(bl,bl,LK_PARRYING,sc_data[SC_PARRYING].val1,1); + } + } + // リジェクトソード + if(sc_data[SC_REJECTSWORD].timer!=-1 && damage > 0 && flag&BF_WEAPON && + ((src->type==BL_PC && ((struct map_session_data *)src)->status.weapon == (1 || 2 || 3)) || src->type==BL_MOB )){ + if(rand()%100 < (10+5*sc_data[SC_REJECTSWORD].val1)){ //反射確率は10+5*Lv + damage = damage*50/100; + battle_damage(bl,src,damage,0); + //ダメージを与えたのは良いんだが、ここからどうして表示するんだかわかんねぇ + //エフェクトもこれでいいのかわかんねぇ + clif_skill_nodamage(bl,bl,ST_REJECTSWORD,sc_data[SC_REJECTSWORD].val1,1); + if((--sc_data[SC_REJECTSWORD].val2)<=0) + skill_status_change_end(bl, SC_REJECTSWORD, -1); + } + } + } + + if(class == 1288 || class == 1287 || class == 1286 || class == 1285) { +// if(class == 1288) { + if(class == 1288 && flag&BF_SKILL) + damage=0; + if(src->type == BL_PC) { + struct guild *g=guild_search(((struct map_session_data *)src)->status.guild_id); + struct guild_castle *gc=guild_mapname2gc(map[bl->m].name); + if(!((struct map_session_data *)src)->status.guild_id) + damage=0; + if(gc && agit_flag==0 && class != 1288) // guardians cannot be damaged during non-woe [Valaris] + damage=0; // end woe check [Valaris] + if(g == NULL) + damage=0;//ギルド未加入ならダメージ無し + else if((gc != NULL) && guild_isallied(g, gc)) + damage=0;//自占領ギルドのエンペならダメージ無し + else if(g && guild_checkskill(g,GD_APPROVAL) <= 0) + damage=0;//正規ギルド承認がないとダメージ無し + else if (battle_config.guild_max_castles != 0 && guild_checkcastles(g)>=battle_config.guild_max_castles) + damage = 0; // [MouseJstr] + } + else damage = 0; + } + + if(map[bl->m].flag.gvg && damage > 0) { //GvG + if(flag&BF_WEAPON) { + if(flag&BF_SHORT) + damage=damage*battle_config.gvg_short_damage_rate/100; + if(flag&BF_LONG) + damage=damage*battle_config.gvg_long_damage_rate/100; + } + if(flag&BF_MAGIC) + damage = damage*battle_config.gvg_magic_damage_rate/100; + if(flag&BF_MISC) + damage=damage*battle_config.gvg_misc_damage_rate/100; + if(damage < 1) damage = 1; + } + + if(battle_config.skill_min_damage || flag&BF_MISC) { + if(div_ < 255) { + if(damage > 0 && damage < div_) + damage = div_; + } + else if(damage > 0 && damage < 3) + damage = 3; + } + + if( md!=NULL && md->hp>0 && damage > 0 ) // 反撃などのMOBスキル判定 + mobskill_event(md,flag); + + return damage; +} + +/*========================================== + * 修練ダメージ + *------------------------------------------ + */ +int battle_addmastery(struct map_session_data *sd,struct block_list *target,int dmg,int type) +{ + int damage,skill; + int race=battle_get_race(target); + int weapon; + damage = 0; + + nullpo_retr(0, sd); + + // デーモンベイン(+3 〜 +30) vs 不死 or 悪魔 (死人は含めない?) + if((skill = pc_checkskill(sd,AL_DEMONBANE)) > 0 && (battle_check_undead(race,battle_get_elem_type(target)) || race==6) ) + damage += (skill * 3); + + // ビーストベイン(+4 〜 +40) vs 動物 or 昆虫 + if((skill = pc_checkskill(sd,HT_BEASTBANE)) > 0 && (race==2 || race==4) ) + damage += (skill * 4); + + if(type == 0) + weapon = sd->weapontype1; + else + weapon = sd->weapontype2; + switch(weapon) + { + case 0x01: // 短剣 (Updated By AppleGirl) + case 0x02: // 1HS + { + // 剣修練(+4 〜 +40) 片手剣 短剣含む + if((skill = pc_checkskill(sd,SM_SWORD)) > 0) { + damage += (skill * 4); + } + break; + } + case 0x03: // 2HS + { + // 両手剣修練(+4 〜 +40) 両手剣 + if((skill = pc_checkskill(sd,SM_TWOHAND)) > 0) { + damage += (skill * 4); + } + break; + } + case 0x04: // 1HL + { + // 槍修練(+4 〜 +40,+5 〜 +50) 槍 + if((skill = pc_checkskill(sd,KN_SPEARMASTERY)) > 0) { + if(!pc_isriding(sd)) + damage += (skill * 4); // ペコに乗ってない + else + damage += (skill * 5); // ペコに乗ってる + } + break; + } + case 0x05: // 2HL + { + // 槍修練(+4 〜 +40,+5 〜 +50) 槍 + if((skill = pc_checkskill(sd,KN_SPEARMASTERY)) > 0) { + if(!pc_isriding(sd)) + damage += (skill * 4); // ペコに乗ってない + else + damage += (skill * 5); // ペコに乗ってる + } + break; + } + case 0x06: // 片手斧 + { + if((skill = pc_checkskill(sd,AM_AXEMASTERY)) > 0) { + damage += (skill * 3); + } + break; + } + case 0x07: // Axe by Tato + { + if((skill = pc_checkskill(sd,AM_AXEMASTERY)) > 0) { + damage += (skill * 3); + } + break; + } + case 0x08: // メイス + { + // メイス修練(+3 〜 +30) メイス + if((skill = pc_checkskill(sd,PR_MACEMASTERY)) > 0) { + damage += (skill * 3); + } + break; + } + case 0x09: // なし? + break; + case 0x0a: // 杖 + break; + case 0x0b: // 弓 + break; + case 0x00: // 素手 + case 0x0c: // Knuckles + { + // 鉄拳(+3 〜 +30) 素手,ナックル + if((skill = pc_checkskill(sd,MO_IRONHAND)) > 0) { + damage += (skill * 3); + } + break; + } + case 0x0d: // Musical Instrument + { + // 楽器の練習(+3 〜 +30) 楽器 + if((skill = pc_checkskill(sd,BA_MUSICALLESSON)) > 0) { + damage += (skill * 3); + } + break; + } + case 0x0e: // Dance Mastery + { + // Dance Lesson Skill Effect(+3 damage for every lvl = +30) 鞭 + if((skill = pc_checkskill(sd,DC_DANCINGLESSON)) > 0) { + damage += (skill * 3); + } + break; + } + case 0x0f: // Book + { + // Advance Book Skill Effect(+3 damage for every lvl = +30) { + if((skill = pc_checkskill(sd,SA_ADVANCEDBOOK)) > 0) { + damage += (skill * 3); + } + break; + } + case 0x10: // Katars + { + // カタール修練(+3 〜 +30) カタール + if((skill = pc_checkskill(sd,AS_KATAR)) > 0) { + //ソニックブロー時は別処理(1撃に付き1/8適応) + damage += (skill * 3); + } + break; + } + } + damage = dmg + damage; + return (damage); +} + +static struct Damage battle_calc_pet_weapon_attack( + struct block_list *src,struct block_list *target,int skill_num,int skill_lv,int wflag) +{ + struct pet_data *pd = (struct pet_data *)src; + struct mob_data *tmd=NULL; + int hitrate,flee,cri = 0,atkmin,atkmax; + int luk,target_count = 1; + int def1 = battle_get_def(target); + int def2 = battle_get_def2(target); + int t_vit = battle_get_vit(target); + struct Damage wd; + int damage,damage2=0,type,div_,blewcount=skill_get_blewcount(skill_num,skill_lv); + int flag,dmg_lv=0; + int t_mode=0,t_race=0,t_size=1,s_race=0,s_ele=0; + struct status_change *t_sc_data; + + //return前の処理があるので情報出力部のみ変更 + if( target == NULL || pd == NULL ){ //srcは内容に直接触れていないのでスルーしてみる + nullpo_info(NLP_MARK); + memset(&wd,0,sizeof(wd)); + return wd; + } + + s_race=battle_get_race(src); + s_ele=battle_get_attack_element(src); + + // ターゲット + if(target->type == BL_MOB) + tmd=(struct mob_data *)target; + else { + memset(&wd,0,sizeof(wd)); + return wd; + } + t_race=battle_get_race( target ); + t_size=battle_get_size( target ); + t_mode=battle_get_mode( target ); + t_sc_data=battle_get_sc_data( target ); + + flag=BF_SHORT|BF_WEAPON|BF_NORMAL; // 攻撃の種類の設定 + + // 回避率計算、回避判定は後で + flee = battle_get_flee(target); + if(battle_config.agi_penaly_type > 0 || battle_config.vit_penaly_type > 0) + target_count += battle_counttargeted(target,src,battle_config.agi_penaly_count_lv); + if(battle_config.agi_penaly_type > 0) { + if(target_count >= battle_config.agi_penaly_count) { + if(battle_config.agi_penaly_type == 1) + flee = (flee * (100 - (target_count - (battle_config.agi_penaly_count - 1))*battle_config.agi_penaly_num))/100; + else if(battle_config.agi_penaly_type == 2) + flee -= (target_count - (battle_config.agi_penaly_count - 1))*battle_config.agi_penaly_num; + if(flee < 1) flee = 1; + } + } + hitrate=battle_get_hit(src) - flee + 80; + + type=0; // normal + div_ = 1; // single attack + + luk=battle_get_luk(src); + + if(battle_config.pet_str) + damage = battle_get_baseatk(src); + else + damage = 0; + + if(skill_num==HW_MAGICCRASHER){ /* マジッククラッシャーはMATKで殴る */ + atkmin = battle_get_matk1(src); + atkmax = battle_get_matk2(src); + }else{ + atkmin = battle_get_atk(src); + atkmax = battle_get_atk2(src); + } + if(mob_db[pd->class].range>3 ) + flag=(flag&~BF_RANGEMASK)|BF_LONG; + + if(atkmin > atkmax) atkmin = atkmax; + + cri = battle_get_critical(src); + cri -= battle_get_luk(target) * 2; // luk/5*10 => target_luk*2 not target_luk*3 + if(battle_config.enemy_critical_rate != 100) { + cri = cri*battle_config.enemy_critical_rate/100; + if(cri < 1) + cri = 1; + } + if(t_sc_data != NULL && t_sc_data[SC_SLEEP].timer!=-1 ) + cri <<=1; + + if(skill_num == 0 && skill_lv >= 0 && battle_config.enemy_critical && (rand() % 1000) < cri) + { + damage += atkmax; + type = 0x0a; + } + else { + int vitbonusmax; + + if(atkmax > atkmin) + damage += atkmin + rand() % (atkmax-atkmin + 1); + else + damage += atkmin ; + // スキル修正1(攻撃力倍化系) + // オーバートラスト(+5% 〜 +25%),他攻撃系スキルの場合ここで補正 + // バッシュ,マグナムブレイク, + // ボーリングバッシュ,スピアブーメラン,ブランディッシュスピア,スピアスタッブ, + // メマーナイト,カートレボリューション + // ダブルストレイフィング,アローシャワー,チャージアロー, + // ソニックブロー + if(skill_num>0){ + int i; + if( (i=skill_get_pl(skill_num))>0 ) + s_ele=i; + + flag=(flag&~BF_SKILLMASK)|BF_SKILL; + switch( skill_num ){ + case SM_BASH: // バッシュ + damage = damage*(100+ 30*skill_lv)/100; + hitrate = (hitrate*(100+5*skill_lv))/100; + break; + case SM_MAGNUM: // マグナムブレイク + damage = damage*(5*skill_lv +(wflag)?65:115 )/100; + break; + case MC_MAMMONITE: // メマーナイト + damage = damage*(100+ 50*skill_lv)/100; + break; + case AC_DOUBLE: // ダブルストレイフィング + damage = damage*(180+ 20*skill_lv)/100; + div_=2; + flag=(flag&~BF_RANGEMASK)|BF_LONG; + break; + case AC_SHOWER: // アローシャワー + damage = damage*(75 + 5*skill_lv)/100; + flag=(flag&~BF_RANGEMASK)|BF_LONG; + break; + case AC_CHARGEARROW: // チャージアロー + damage = damage*150/100; + flag=(flag&~BF_RANGEMASK)|BF_LONG; + break; + case KN_PIERCE: // ピアース + damage = damage*(100+ 10*skill_lv)/100; + hitrate = hitrate*(100+5*skill_lv)/100; + div_=t_size+1; + damage*=div_; + break; + case KN_SPEARSTAB: // スピアスタブ + damage = damage*(100+ 15*skill_lv)/100; + break; + case KN_SPEARBOOMERANG: // スピアブーメラン + damage = damage*(100+ 50*skill_lv)/100; + flag=(flag&~BF_RANGEMASK)|BF_LONG; + break; + case KN_BRANDISHSPEAR: // ブランディッシュスピア + damage = damage*(100+ 20*skill_lv)/100; + if(skill_lv>3 && wflag==1) damage2+=damage/2; + if(skill_lv>6 && wflag==1) damage2+=damage/4; + if(skill_lv>9 && wflag==1) damage2+=damage/8; + if(skill_lv>6 && wflag==2) damage2+=damage/2; + if(skill_lv>9 && wflag==2) damage2+=damage/4; + if(skill_lv>9 && wflag==3) damage2+=damage/2; + damage +=damage2; + blewcount=0; + break; + case KN_BOWLINGBASH: // ボウリングバッシュ + damage = damage*(100+ 50*skill_lv)/100; + blewcount=0; + break; + case AS_SONICBLOW: // ソニックブロウ + damage = damage*(300+ 50*skill_lv)/100; + div_=8; + break; + case TF_SPRINKLESAND: // 砂まき + damage = damage*125/100; + break; + case MC_CARTREVOLUTION: // カートレボリューション + damage = (damage*150)/100; + break; + // 以下MOB + case NPC_COMBOATTACK: // 多段攻撃 + div_=skill_get_num(skill_num,skill_lv); + damage *= div_; + break; + case NPC_RANDOMATTACK: // ランダムATK攻撃 + damage = damage*(50+rand()%150)/100; + break; + // 属性攻撃(適当) + case NPC_WATERATTACK: + case NPC_GROUNDATTACK: + case NPC_FIREATTACK: + case NPC_WINDATTACK: + case NPC_POISONATTACK: + case NPC_HOLYATTACK: + case NPC_DARKNESSATTACK: + case NPC_TELEKINESISATTACK: + div_= pd->skillduration; // [Valaris] + break; + case NPC_GUIDEDATTACK: + hitrate = 1000000; + break; + case NPC_RANGEATTACK: + flag=(flag&~BF_RANGEMASK)|BF_LONG; + break; + case NPC_PIERCINGATT: + flag=(flag&~BF_RANGEMASK)|BF_SHORT; + break; + case RG_BACKSTAP: // バックスタブ + damage = damage*(300+ 40*skill_lv)/100; + hitrate = 1000000; + break; + case RG_RAID: // サプライズアタック + damage = damage*(100+ 40*skill_lv)/100; + break; + case RG_INTIMIDATE: // インティミデイト + damage = damage*(100+ 30*skill_lv)/100; + break; + case CR_SHIELDCHARGE: // シールドチャージ + damage = damage*(100+ 20*skill_lv)/100; + flag=(flag&~BF_RANGEMASK)|BF_SHORT; + s_ele = 0; + break; + case CR_SHIELDBOOMERANG: // シールドブーメラン + damage = damage*(100+ 30*skill_lv)/100; + flag=(flag&~BF_RANGEMASK)|BF_LONG; + s_ele = 0; + break; + case CR_HOLYCROSS: // ホーリークロス + damage = damage*(100+ 35*skill_lv)/100; + div_=2; + break; + case CR_GRANDCROSS: + hitrate= 1000000; + break; + case AM_DEMONSTRATION: // デモンストレーション + damage = damage*(100+ 20*skill_lv)/100; + damage2 = damage2*(100+ 20*skill_lv)/100; + break; + case AM_ACIDTERROR: // アシッドテラー + damage = damage*(100+ 40*skill_lv)/100; + damage2 = damage2*(100+ 40*skill_lv)/100; + break; + case MO_FINGEROFFENSIVE: //指弾 + damage = damage * (100 + 50 * skill_lv) / 100; + div_ = 1; + break; + case MO_INVESTIGATE: // 発 勁 + if(def1 < 1000000) + damage = damage*(100+ 75*skill_lv)/100 * (def1 + def2)/100; + hitrate = 1000000; + s_ele = 0; + break; + case MO_EXTREMITYFIST: // 阿修羅覇鳳拳 + damage = damage * 8 + 250 + (skill_lv * 150); + hitrate = 1000000; + s_ele = 0; + break; + case MO_CHAINCOMBO: // 連打掌 + damage = damage*(150+ 50*skill_lv)/100; + div_=4; + break; + case MO_COMBOFINISH: // 猛龍拳 + damage = damage*(240+ 60*skill_lv)/100; + break; + case DC_THROWARROW: // 矢撃ち + damage = damage*(100+ 50 * skill_lv)/100; + flag=(flag&~BF_RANGEMASK)|BF_LONG; + break; + case BA_MUSICALSTRIKE: // ミュージカルストライク + damage = damage*(100+ 50 * skill_lv)/100; + flag=(flag&~BF_RANGEMASK)|BF_LONG; + break; + case CH_TIGERFIST: // 伏虎拳 + damage = damage*(100+ 20*skill_lv)/100; + break; + case CH_CHAINCRUSH: // 連柱崩撃 + damage = damage*(100+ 20*skill_lv)/100; + div_=skill_get_num(skill_num,skill_lv); + break; + case CH_PALMSTRIKE: // 猛虎硬派山 + damage = damage*(50+ 100*skill_lv)/100; + break; + case LK_SPIRALPIERCE: /* スパイラルピアース */ + damage = damage*(100+ 50*skill_lv)/100; //増加量が分からないので適当に + div_=5; + if(target->type == BL_PC) + ((struct map_session_data *)target)->canmove_tick = gettick() + 1000; + else if(target->type == BL_MOB) + ((struct mob_data *)target)->canmove_tick = gettick() + 1000; + break; + case LK_HEADCRUSH: /* ヘッドクラッシュ */ + damage = damage*(100+ 20*skill_lv)/100; + break; + case LK_JOINTBEAT: /* ジョイントビート */ + damage = damage*(50+ 10*skill_lv)/100; + break; + case ASC_METEORASSAULT: /* メテオアサルト */ + damage = damage*(40+ 40*skill_lv)/100; + break; + case SN_SHARPSHOOTING: /* シャープシューティング */ + damage += damage*(30*skill_lv)/100; + break; + case CG_ARROWVULCAN: /* アローバルカン */ + damage = damage*(160+40*skill_lv)/100; + div_=9; + break; + case AS_SPLASHER: /* ベナムスプラッシャー */ + damage = damage*(200+20*skill_lv)/100; + break; + } + } + + if( skill_num!=NPC_CRITICALSLASH ){ + // 対 象の防御力によるダメージの減少 + // ディバインプロテクション(ここでいいのかな?) + if ( skill_num != MO_INVESTIGATE && skill_num != MO_EXTREMITYFIST && skill_num != KN_AUTOCOUNTER && def1 < 1000000 ) { //DEF, VIT無視 + int t_def; + target_count = 1 + battle_counttargeted(target,src,battle_config.vit_penaly_count_lv); + if(battle_config.vit_penaly_type > 0) { + if(target_count >= battle_config.vit_penaly_count) { + if(battle_config.vit_penaly_type == 1) { + def1 = (def1 * (100 - (target_count - (battle_config.vit_penaly_count - 1))*battle_config.vit_penaly_num))/100; + def2 = (def2 * (100 - (target_count - (battle_config.vit_penaly_count - 1))*battle_config.vit_penaly_num))/100; + t_vit = (t_vit * (100 - (target_count - (battle_config.vit_penaly_count - 1))*battle_config.vit_penaly_num))/100; + } + else if(battle_config.vit_penaly_type == 2) { + def1 -= (target_count - (battle_config.vit_penaly_count - 1))*battle_config.vit_penaly_num; + def2 -= (target_count - (battle_config.vit_penaly_count - 1))*battle_config.vit_penaly_num; + t_vit -= (target_count - (battle_config.vit_penaly_count - 1))*battle_config.vit_penaly_num; + } + if(def1 < 0) def1 = 0; + if(def2 < 1) def2 = 1; + if(t_vit < 1) t_vit = 1; + } + } + t_def = def2*8/10; + vitbonusmax = (t_vit/20)*(t_vit/20)-1; + if(battle_config.pet_defense_type) { + damage = damage - (def1 * battle_config.pet_defense_type) - t_def - ((vitbonusmax < 1)?0: rand()%(vitbonusmax+1) ); + } + else{ + damage = damage * (100 - def1) /100 - t_def - ((vitbonusmax < 1)?0: rand()%(vitbonusmax+1) ); + } + } + } + } + + // 0未満だった場合1に補正 + if(damage<1) damage=1; + + // 回避修正 + if(hitrate < 1000000) + hitrate = ( (hitrate>95)?95: ((hitrate<5)?5:hitrate) ); + if( hitrate < 1000000 && // 必中攻撃 + (t_sc_data != NULL && (t_sc_data[SC_SLEEP].timer!=-1 || // 睡眠は必中 + t_sc_data[SC_STAN].timer!=-1 || // スタンは必中 + t_sc_data[SC_FREEZE].timer!=-1 || (t_sc_data[SC_STONE].timer!=-1 && t_sc_data[SC_STONE].val2==0) ) ) ) // 凍結は必中 + hitrate = 1000000; + if(type == 0 && rand()%100 >= hitrate) { + damage = damage2 = 0; + dmg_lv = ATK_FLEE; + } else { + dmg_lv = ATK_DEF; + } + + + if(t_sc_data) { + int cardfix=100; + if(t_sc_data[SC_DEFENDER].timer != -1 && flag&BF_LONG) + cardfix=cardfix*(100-t_sc_data[SC_DEFENDER].val2)/100; + if(cardfix != 100) + damage=damage*cardfix/100; + } + if(damage < 0) damage = 0; + + // 属 性の適用 + if(skill_num != 0 || s_ele != 0 || !battle_config.pet_attack_attr_none) + damage=battle_attr_fix(damage, s_ele, battle_get_element(target) ); + + if(skill_num==PA_PRESSURE) /* プレッシャー 必中? */ + damage = 700+100*skill_lv; + + // インベナム修正 + if(skill_num==TF_POISON){ + damage = battle_attr_fix(damage + 15*skill_lv, s_ele, battle_get_element(target) ); + } + if(skill_num==MC_CARTREVOLUTION){ + damage = battle_attr_fix(damage, 0, battle_get_element(target) ); + } + + // 完全回避の判定 + if(battle_config.enemy_perfect_flee) { + if(skill_num == 0 && skill_lv >= 0 && tmd!=NULL && rand()%1000 < battle_get_flee2(target) ){ + damage=0; + type=0x0b; + dmg_lv = ATK_LUCKY; + } + } + +// if(def1 >= 1000000 && damage > 0) + if(t_mode&0x40 && damage > 0) + damage = 1; + + if(skill_num != CR_GRANDCROSS) + damage=battle_calc_damage(src,target,damage,div_,skill_num,skill_lv,flag); + + wd.damage=damage; + wd.damage2=0; + wd.type=type; + wd.div_=div_; + wd.amotion=battle_get_amotion(src); + if(skill_num == KN_AUTOCOUNTER) + wd.amotion >>= 1; + wd.dmotion=battle_get_dmotion(target); + wd.blewcount=blewcount; + wd.flag=flag; + wd.dmg_lv=dmg_lv; + + return wd; +} + +static struct Damage battle_calc_mob_weapon_attack( + struct block_list *src,struct block_list *target,int skill_num,int skill_lv,int wflag) +{ + struct map_session_data *tsd=NULL; + struct mob_data* md=(struct mob_data *)src,*tmd=NULL; + int hitrate,flee,cri = 0,atkmin,atkmax; + int luk,target_count = 1; + int def1 = battle_get_def(target); + int def2 = battle_get_def2(target); + int t_vit = battle_get_vit(target); + struct Damage wd; + int damage,damage2=0,type,div_,blewcount=skill_get_blewcount(skill_num,skill_lv); + int flag,skill,ac_flag = 0,dmg_lv = 0; + int t_mode=0,t_race=0,t_size=1,s_race=0,s_ele=0; + struct status_change *sc_data,*t_sc_data; + short *sc_count; + short *option, *opt1, *opt2; + + //return前の処理があるので情報出力部のみ変更 + if( src == NULL || target == NULL || md == NULL ){ + nullpo_info(NLP_MARK); + memset(&wd,0,sizeof(wd)); + return wd; + } + + s_race=battle_get_race(src); + s_ele=battle_get_attack_element(src); + sc_data=battle_get_sc_data(src); + sc_count=battle_get_sc_count(src); + option=battle_get_option(src); + opt1=battle_get_opt1(src); + opt2=battle_get_opt2(src); + + // ターゲット + if(target->type==BL_PC) + tsd=(struct map_session_data *)target; + else if(target->type==BL_MOB) + tmd=(struct mob_data *)target; + t_race=battle_get_race( target ); + t_size=battle_get_size( target ); + t_mode=battle_get_mode( target ); + t_sc_data=battle_get_sc_data( target ); + + if((skill_num == 0 || (target->type == BL_PC && battle_config.pc_auto_counter_type&2) || + (target->type == BL_MOB && battle_config.monster_auto_counter_type&2)) && skill_lv >= 0) { + if(skill_num != CR_GRANDCROSS && t_sc_data && t_sc_data[SC_AUTOCOUNTER].timer != -1) { + int dir = map_calc_dir(src,target->x,target->y),t_dir = battle_get_dir(target); + int dist = distance(src->x,src->y,target->x,target->y); + if(dist <= 0 || map_check_dir(dir,t_dir) ) { + memset(&wd,0,sizeof(wd)); + t_sc_data[SC_AUTOCOUNTER].val3 = 0; + t_sc_data[SC_AUTOCOUNTER].val4 = 1; + if(sc_data && sc_data[SC_AUTOCOUNTER].timer == -1) { + int range = battle_get_range(target); + if((target->type == BL_PC && ((struct map_session_data *)target)->status.weapon != 11 && dist <= range+1) || + (target->type == BL_MOB && range <= 3 && dist <= range+1) ) + t_sc_data[SC_AUTOCOUNTER].val3 = src->id; + } + return wd; + } + else ac_flag = 1; + } + } + flag=BF_SHORT|BF_WEAPON|BF_NORMAL; // 攻撃の種類の設定 + + // 回避率計算、回避判定は後で + flee = battle_get_flee(target); + if(battle_config.agi_penaly_type > 0 || battle_config.vit_penaly_type > 0) + target_count += battle_counttargeted(target,src,battle_config.agi_penaly_count_lv); + if(battle_config.agi_penaly_type > 0) { + if(target_count >= battle_config.agi_penaly_count) { + if(battle_config.agi_penaly_type == 1) + flee = (flee * (100 - (target_count - (battle_config.agi_penaly_count - 1))*battle_config.agi_penaly_num))/100; + else if(battle_config.agi_penaly_type == 2) + flee -= (target_count - (battle_config.agi_penaly_count - 1))*battle_config.agi_penaly_num; + if(flee < 1) flee = 1; + } + } + hitrate=battle_get_hit(src) - flee + 80; + + type=0; // normal + div_ = 1; // single attack + + luk=battle_get_luk(src); + + if(battle_config.enemy_str) + damage = battle_get_baseatk(src); + else + damage = 0; + if(skill_num==HW_MAGICCRASHER){ /* マジッククラッシャーはMATKで殴る */ + atkmin = battle_get_matk1(src); + atkmax = battle_get_matk2(src); + }else{ + atkmin = battle_get_atk(src); + atkmax = battle_get_atk2(src); + } + if(mob_db[md->class].range>3 ) + flag=(flag&~BF_RANGEMASK)|BF_LONG; + + if(atkmin > atkmax) atkmin = atkmax; + + if(sc_data != NULL && sc_data[SC_MAXIMIZEPOWER].timer!=-1 ){ // マキシマイズパワー + atkmin=atkmax; + } + + cri = battle_get_critical(src); + cri -= battle_get_luk(target) * 3; + if(battle_config.enemy_critical_rate != 100) { + cri = cri*battle_config.enemy_critical_rate/100; + if(cri < 1) + cri = 1; + } + if(t_sc_data != NULL && t_sc_data[SC_SLEEP].timer!=-1 ) // 睡眠中はクリティカルが倍に + cri <<=1; + + if(ac_flag) cri = 1000; + + if(skill_num == KN_AUTOCOUNTER) { + if(!(battle_config.monster_auto_counter_type&1)) + cri = 1000; + else + cri <<= 1; + } + + if(tsd && tsd->critical_def) + cri = cri * (100 - tsd->critical_def) / 100; + + if((skill_num == 0 || skill_num == KN_AUTOCOUNTER) && skill_lv >= 0 && battle_config.enemy_critical && (rand() % 1000) < cri) // 判定(スキルの場合は無視) + // 敵の判定 + { + damage += atkmax; + type = 0x0a; + } + else { + int vitbonusmax; + + if(atkmax > atkmin) + damage += atkmin + rand() % (atkmax-atkmin + 1); + else + damage += atkmin ; + // スキル修正1(攻撃力倍化系) + // オーバートラスト(+5% 〜 +25%),他攻撃系スキルの場合ここで補正 + // バッシュ,マグナムブレイク, + // ボーリングバッシュ,スピアブーメラン,ブランディッシュスピア,スピアスタッブ, + // メマーナイト,カートレボリューション + // ダブルストレイフィング,アローシャワー,チャージアロー, + // ソニックブロー + if(sc_data){ //状態異常中のダメージ追加 + if(sc_data[SC_OVERTHRUST].timer!=-1) // オーバートラスト + damage += damage*(5*sc_data[SC_OVERTHRUST].val1)/100; + if(sc_data[SC_TRUESIGHT].timer!=-1) // トゥルーサイト + damage += damage*(2*sc_data[SC_TRUESIGHT].val1)/100; + if(sc_data[SC_BERSERK].timer!=-1) // バーサーク + damage += damage*50/100; + } + + if(skill_num>0){ + int i; + if( (i=skill_get_pl(skill_num))>0 ) + s_ele=i; + + flag=(flag&~BF_SKILLMASK)|BF_SKILL; + switch( skill_num ){ + case SM_BASH: // バッシュ + damage = damage*(100+ 30*skill_lv)/100; + hitrate = (hitrate*(100+5*skill_lv))/100; + break; + case SM_MAGNUM: // マグナムブレイク + damage = damage*(5*skill_lv +(wflag)?65:115 )/100; + break; + case MC_MAMMONITE: // メマーナイト + damage = damage*(100+ 50*skill_lv)/100; + break; + case AC_DOUBLE: // ダブルストレイフィング + damage = damage*(180+ 20*skill_lv)/100; + div_=2; + flag=(flag&~BF_RANGEMASK)|BF_LONG; + break; + case AC_SHOWER: // アローシャワー + damage = damage*(75 + 5*skill_lv)/100; + flag=(flag&~BF_RANGEMASK)|BF_LONG; + break; + case AC_CHARGEARROW: // チャージアロー + damage = damage*150/100; + flag=(flag&~BF_RANGEMASK)|BF_LONG; + break; + case KN_PIERCE: // ピアース + damage = damage*(100+ 10*skill_lv)/100; + hitrate=hitrate*(100+5*skill_lv)/100; + div_=t_size+1; + damage*=div_; + break; + case KN_SPEARSTAB: // スピアスタブ + damage = damage*(100+ 15*skill_lv)/100; + break; + case KN_SPEARBOOMERANG: // スピアブーメラン + damage = damage*(100+ 50*skill_lv)/100; + flag=(flag&~BF_RANGEMASK)|BF_LONG; + break; + case KN_BRANDISHSPEAR: // ブランディッシュスピア + damage = damage*(100+ 20*skill_lv)/100; + if(skill_lv>3 && wflag==1) damage2+=damage/2; + if(skill_lv>6 && wflag==1) damage2+=damage/4; + if(skill_lv>9 && wflag==1) damage2+=damage/8; + if(skill_lv>6 && wflag==2) damage2+=damage/2; + if(skill_lv>9 && wflag==2) damage2+=damage/4; + if(skill_lv>9 && wflag==3) damage2+=damage/2; + damage +=damage2; + blewcount=0; + break; + case KN_BOWLINGBASH: // ボウリングバッシュ + damage = damage*(100+ 50*skill_lv)/100; + blewcount=0; + break; + case KN_AUTOCOUNTER: + if(battle_config.monster_auto_counter_type&1) + hitrate += 20; + else + hitrate = 1000000; + flag=(flag&~BF_SKILLMASK)|BF_NORMAL; + break; + case AS_SONICBLOW: // ソニックブロウ + damage = damage*(300+ 50*skill_lv)/100; + div_=8; + break; + case TF_SPRINKLESAND: // 砂まき + damage = damage*125/100; + break; + case MC_CARTREVOLUTION: // カートレボリューション + damage = (damage*150)/100; + break; + // 以下MOB + case NPC_COMBOATTACK: // 多段攻撃 + div_=skill_get_num(skill_num,skill_lv); + damage *= div_; + break; + case NPC_RANDOMATTACK: // ランダムATK攻撃 + damage = damage*(50+rand()%150)/100; + break; + // 属性攻撃(適当) + case NPC_WATERATTACK: + case NPC_GROUNDATTACK: + case NPC_FIREATTACK: + case NPC_WINDATTACK: + case NPC_POISONATTACK: + case NPC_HOLYATTACK: + case NPC_DARKNESSATTACK: + case NPC_TELEKINESISATTACK: + damage = damage*(100+25*(skill_lv-1))/100; + break; + case NPC_GUIDEDATTACK: + hitrate = 1000000; + break; + case NPC_RANGEATTACK: + flag=(flag&~BF_RANGEMASK)|BF_LONG; + break; + case NPC_PIERCINGATT: + flag=(flag&~BF_RANGEMASK)|BF_SHORT; + break; + case RG_BACKSTAP: // バックスタブ + damage = damage*(300+ 40*skill_lv)/100; + hitrate = 1000000; + break; + case RG_RAID: // サプライズアタック + damage = damage*(100+ 40*skill_lv)/100; + break; + case RG_INTIMIDATE: // インティミデイト + damage = damage*(100+ 30*skill_lv)/100; + break; + case CR_SHIELDCHARGE: // シールドチャージ + damage = damage*(100+ 20*skill_lv)/100; + flag=(flag&~BF_RANGEMASK)|BF_SHORT; + s_ele = 0; + break; + case CR_SHIELDBOOMERANG: // シールドブーメラン + damage = damage*(100+ 30*skill_lv)/100; + flag=(flag&~BF_RANGEMASK)|BF_LONG; + s_ele = 0; + break; + case CR_HOLYCROSS: // ホーリークロス + damage = damage*(100+ 35*skill_lv)/100; + div_=2; + break; + case CR_GRANDCROSS: + hitrate= 1000000; + break; + case AM_DEMONSTRATION: // デモンストレーション + damage = damage*(100+ 20*skill_lv)/100; + damage2 = damage2*(100+ 20*skill_lv)/100; + break; + case AM_ACIDTERROR: // アシッドテラー + damage = damage*(100+ 40*skill_lv)/100; + damage2 = damage2*(100+ 40*skill_lv)/100; + break; + case MO_FINGEROFFENSIVE: //指弾 + damage = damage * (100 + 50 * skill_lv) / 100; + div_ = 1; + break; + case MO_INVESTIGATE: // 発 勁 + if(def1 < 1000000) + damage = damage*(100+ 75*skill_lv)/100 * (def1 + def2)/100; + hitrate = 1000000; + s_ele = 0; + break; + case MO_EXTREMITYFIST: // 阿修羅覇鳳拳 + damage = damage * 8 + 250 + (skill_lv * 150); + hitrate = 1000000; + s_ele = 0; + break; + case MO_CHAINCOMBO: // 連打掌 + damage = damage*(150+ 50*skill_lv)/100; + div_=4; + break; + case BA_MUSICALSTRIKE: // ミュージカルストライク + damage = damage*(100+ 50 * skill_lv)/100; + flag=(flag&~BF_RANGEMASK)|BF_LONG; + break; + case DC_THROWARROW: // 矢撃ち + damage = damage*(100+ 50 * skill_lv)/100; + flag=(flag&~BF_RANGEMASK)|BF_LONG; + break; + case MO_COMBOFINISH: // 猛龍拳 + damage = damage*(240+ 60*skill_lv)/100; + break; + case CH_TIGERFIST: // 伏虎拳 + damage = damage*(100+ 20*skill_lv)/100; + break; + case CH_CHAINCRUSH: // 連柱崩撃 + damage = damage*(100+ 20*skill_lv)/100; + div_=skill_get_num(skill_num,skill_lv); + break; + case CH_PALMSTRIKE: // 猛虎硬派山 + damage = damage*(50+ 100*skill_lv)/100; + break; + case LK_SPIRALPIERCE: /* スパイラルピアース */ + damage = damage*(100+ 50*skill_lv)/100; //増加量が分からないので適当に + div_=5; + if(tsd) + tsd->canmove_tick = gettick() + 1000; + else if(tmd) + tmd->canmove_tick = gettick() + 1000; + break; + case LK_HEADCRUSH: /* ヘッドクラッシュ */ + damage = damage*(100+ 20*skill_lv)/100; + break; + case LK_JOINTBEAT: /* ジョイントビート */ + damage = damage*(50+ 10*skill_lv)/100; + break; + case ASC_METEORASSAULT: /* メテオアサルト */ + damage = damage*(40+ 40*skill_lv)/100; + break; + case SN_SHARPSHOOTING: /* シャープシューティング */ + damage += damage*(30*skill_lv)/100; + break; + case CG_ARROWVULCAN: /* アローバルカン */ + damage = damage*(160+40*skill_lv)/100; + div_=9; + break; + case AS_SPLASHER: /* ベナムスプラッシャー */ + damage = damage*(200+20*skill_lv)/100; + break; + } + } + + if( skill_num!=NPC_CRITICALSLASH ){ + // 対 象の防御力によるダメージの減少 + // ディバインプロテクション(ここでいいのかな?) + if ( skill_num != MO_INVESTIGATE && skill_num != MO_EXTREMITYFIST && skill_num != KN_AUTOCOUNTER && def1 < 1000000) { //DEF, VIT無視 + int t_def; + target_count = 1 + battle_counttargeted(target,src,battle_config.vit_penaly_count_lv); + if(battle_config.vit_penaly_type > 0) { + if(target_count >= battle_config.vit_penaly_count) { + if(battle_config.vit_penaly_type == 1) { + def1 = (def1 * (100 - (target_count - (battle_config.vit_penaly_count - 1))*battle_config.vit_penaly_num))/100; + def2 = (def2 * (100 - (target_count - (battle_config.vit_penaly_count - 1))*battle_config.vit_penaly_num))/100; + t_vit = (t_vit * (100 - (target_count - (battle_config.vit_penaly_count - 1))*battle_config.vit_penaly_num))/100; + } + else if(battle_config.vit_penaly_type == 2) { + def1 -= (target_count - (battle_config.vit_penaly_count - 1))*battle_config.vit_penaly_num; + def2 -= (target_count - (battle_config.vit_penaly_count - 1))*battle_config.vit_penaly_num; + t_vit -= (target_count - (battle_config.vit_penaly_count - 1))*battle_config.vit_penaly_num; + } + if(def1 < 0) def1 = 0; + if(def2 < 1) def2 = 1; + if(t_vit < 1) t_vit = 1; + } + } + t_def = def2*8/10; + if(battle_check_undead(s_race,battle_get_elem_type(src)) || s_race==6) + if(tsd && (skill=pc_checkskill(tsd,AL_DP)) > 0 ) + t_def += skill*3; + + vitbonusmax = (t_vit/20)*(t_vit/20)-1; + if(battle_config.monster_defense_type) { + damage = damage - (def1 * battle_config.monster_defense_type) - t_def - ((vitbonusmax < 1)?0: rand()%(vitbonusmax+1) ); + } + else{ + damage = damage * (100 - def1) /100 - t_def - ((vitbonusmax < 1)?0: rand()%(vitbonusmax+1) ); + } + } + } + } + + // 0未満だった場合1に補正 + if(damage<1) damage=1; + + // 回避修正 + if(hitrate < 1000000) + hitrate = ( (hitrate>95)?95: ((hitrate<5)?5:hitrate) ); + if( hitrate < 1000000 && // 必中攻撃 + (t_sc_data != NULL && (t_sc_data[SC_SLEEP].timer!=-1 || // 睡眠は必中 + t_sc_data[SC_STAN].timer!=-1 || // スタンは必中 + t_sc_data[SC_FREEZE].timer!=-1 || (t_sc_data[SC_STONE].timer!=-1 && t_sc_data[SC_STONE].val2==0) ) ) ) // 凍結は必中 + hitrate = 1000000; + if(type == 0 && rand()%100 >= hitrate) { + damage = damage2 = 0; + dmg_lv = ATK_FLEE; + } else { + dmg_lv = ATK_DEF; + } + + if(tsd){ + int cardfix=100,i; + cardfix=cardfix*(100-tsd->subele[s_ele])/100; // 属 性によるダメージ耐性 + cardfix=cardfix*(100-tsd->subrace[s_race])/100; // 種族によるダメージ耐性 + if(mob_db[md->class].mode & 0x20) + cardfix=cardfix*(100-tsd->subrace[10])/100; + else + cardfix=cardfix*(100-tsd->subrace[11])/100; + for(i=0;i<tsd->add_def_class_count;i++) { + if(tsd->add_def_classid[i] == md->class) { + cardfix=cardfix*(100-tsd->add_def_classrate[i])/100; + break; + } + } + if(flag&BF_LONG) + cardfix=cardfix*(100-tsd->long_attack_def_rate)/100; + if(flag&BF_SHORT) + cardfix=cardfix*(100-tsd->near_attack_def_rate)/100; + damage=damage*cardfix/100; + } + if(t_sc_data) { + int cardfix=100; + if(t_sc_data[SC_DEFENDER].timer != -1 && flag&BF_LONG) + cardfix=cardfix*(100-t_sc_data[SC_DEFENDER].val2)/100; + if(cardfix != 100) + damage=damage*cardfix/100; + } + if(t_sc_data && t_sc_data[SC_ASSUMPTIO].timer != -1){ //アシャンプティオ + if(!map[target->m].flag.pvp) + damage=damage/3; + else + damage=damage/2; + } + + if(damage < 0) damage = 0; + + // 属 性の適用 + if (!((battle_config.mob_ghostring_fix == 1) && + (battle_get_element(target) == 8) && + (target->type==BL_PC))) // [MouseJstr] + if(skill_num != 0 || s_ele != 0 || !battle_config.mob_attack_attr_none) + damage=battle_attr_fix(damage, s_ele, battle_get_element(target) ); + + if(sc_data && sc_data[SC_AURABLADE].timer!=-1) /* オーラブレード 必中 */ + damage += sc_data[SC_AURABLADE].val1 * 10; + if(skill_num==PA_PRESSURE) /* プレッシャー 必中? */ + damage = 700+100*skill_lv; + + // インベナム修正 + if(skill_num==TF_POISON){ + damage = battle_attr_fix(damage + 15*skill_lv, s_ele, battle_get_element(target) ); + } + if(skill_num==MC_CARTREVOLUTION){ + damage = battle_attr_fix(damage, 0, battle_get_element(target) ); + } + + // 完全回避の判定 + if(skill_num == 0 && skill_lv >= 0 && tsd!=NULL && rand()%1000 < battle_get_flee2(target) ){ + damage=0; + type=0x0b; + dmg_lv = ATK_LUCKY; + } + + if(battle_config.enemy_perfect_flee) { + if(skill_num == 0 && skill_lv >= 0 && tmd!=NULL && rand()%1000 < battle_get_flee2(target) ){ + damage=0; + type=0x0b; + dmg_lv = ATK_LUCKY; + } + } + +// if(def1 >= 1000000 && damage > 0) + if(t_mode&0x40 && damage > 0) + damage = 1; + + if( tsd && tsd->special_state.no_weapon_damage) + damage = 0; + + if(skill_num != CR_GRANDCROSS) + damage=battle_calc_damage(src,target,damage,div_,skill_num,skill_lv,flag); + + wd.damage=damage; + wd.damage2=0; + wd.type=type; + wd.div_=div_; + wd.amotion=battle_get_amotion(src); + if(skill_num == KN_AUTOCOUNTER) + wd.amotion >>= 1; + wd.dmotion=battle_get_dmotion(target); + wd.blewcount=blewcount; + wd.flag=flag; + wd.dmg_lv=dmg_lv; + return wd; +} +/* + * ========================================================================= + * PCの武器による攻撃 + *------------------------------------------------------------------------- + */ +static struct Damage battle_calc_pc_weapon_attack( + struct block_list *src,struct block_list *target,int skill_num,int skill_lv,int wflag) +{ + struct map_session_data *sd=(struct map_session_data *)src,*tsd=NULL; + struct mob_data *tmd=NULL; + int hitrate,flee,cri = 0,atkmin,atkmax; + int dex,luk,target_count = 1; + int def1 = battle_get_def(target); + int def2 = battle_get_def2(target); + int t_vit = battle_get_vit(target); + struct Damage wd; + int damage,damage2,damage3=0,damage4=0,type,div_,blewcount=skill_get_blewcount(skill_num,skill_lv); + int flag,skill,dmg_lv = 0; + int t_mode=0,t_race=0,t_size=1,s_race=7,s_ele=0; + struct status_change *sc_data,*t_sc_data; + short *sc_count; + short *option, *opt1, *opt2; + int atkmax_=0, atkmin_=0, s_ele_; //二刀流用 + int watk,watk_,cardfix,t_ele; + int da=0,i,t_class,ac_flag = 0; + int idef_flag=0,idef_flag_=0; + + //return前の処理があるので情報出力部のみ変更 + if( src == NULL || target == NULL || sd == NULL ){ + nullpo_info(NLP_MARK); + memset(&wd,0,sizeof(wd)); + return wd; + } + + + // アタッカー + s_race=battle_get_race(src); //種族 + s_ele=battle_get_attack_element(src); //属性 + s_ele_=battle_get_attack_element2(src); //左手属性 + sc_data=battle_get_sc_data(src); //ステータス異常 + sc_count=battle_get_sc_count(src); //ステータス異常の数 + option=battle_get_option(src); //鷹とかペコとかカートとか + opt1=battle_get_opt1(src); //石化、凍結、スタン、睡眠、暗闇 + opt2=battle_get_opt2(src); //毒、呪い、沈黙、暗闇? + + if(skill_num != CR_GRANDCROSS) //グランドクロスでないなら + sd->state.attack_type = BF_WEAPON; //攻撃タイプは武器攻撃 + + // ターゲット + if(target->type==BL_PC) //対象がPCなら + tsd=(struct map_session_data *)target; //tsdに代入(tmdはNULL) + else if(target->type==BL_MOB) //対象がMobなら + tmd=(struct mob_data *)target; //tmdに代入(tsdはNULL) + t_race=battle_get_race( target ); //対象の種族 + t_ele=battle_get_elem_type(target); //対象の属性 + t_size=battle_get_size( target ); //対象のサイズ + t_mode=battle_get_mode( target ); //対象のMode + t_sc_data=battle_get_sc_data( target ); //対象のステータス異常 + +//オートカウンター処理ここから + if((skill_num == 0 || (target->type == BL_PC && battle_config.pc_auto_counter_type&2) || + (target->type == BL_MOB && battle_config.monster_auto_counter_type&2)) && skill_lv >= 0) { + if(skill_num != CR_GRANDCROSS && t_sc_data && t_sc_data[SC_AUTOCOUNTER].timer != -1) { //グランドクロスでなく、対象がオートカウンター状態の場合 + int dir = map_calc_dir(src,target->x,target->y),t_dir = battle_get_dir(target); + int dist = distance(src->x,src->y,target->x,target->y); + if(dist <= 0 || map_check_dir(dir,t_dir) ) { //対象との距離が0以下、または対象の正面? + memset(&wd,0,sizeof(wd)); + t_sc_data[SC_AUTOCOUNTER].val3 = 0; + t_sc_data[SC_AUTOCOUNTER].val4 = 1; + if(sc_data && sc_data[SC_AUTOCOUNTER].timer == -1) { //自分がオートカウンター状態 + int range = battle_get_range(target); + if((target->type == BL_PC && ((struct map_session_data *)target)->status.weapon != 11 && dist <= range+1) || //対象がPCで武器が弓矢でなく射程内 + (target->type == BL_MOB && range <= 3 && dist <= range+1) ) //または対象がMobで射程が3以下で射程内 + t_sc_data[SC_AUTOCOUNTER].val3 = src->id; + } + return wd; //ダメージ構造体を返して終了 + } + else ac_flag = 1; + } + } +//オートカウンター処理ここまで + + flag=BF_SHORT|BF_WEAPON|BF_NORMAL; // 攻撃の種類の設定 + + // 回避率計算、回避判定は後で + flee = battle_get_flee(target); + if(battle_config.agi_penaly_type > 0 || battle_config.vit_penaly_type > 0) //AGI、VITペナルティ設定が有効 + target_count += battle_counttargeted(target,src,battle_config.agi_penaly_count_lv); //対象の数を算出 + if(battle_config.agi_penaly_type > 0) { + if(target_count >= battle_config.agi_penaly_count) { //ペナルティ設定より対象が多い + if(battle_config.agi_penaly_type == 1) //回避率がagi_penaly_num%ずつ減少 + flee = (flee * (100 - (target_count - (battle_config.agi_penaly_count - 1))*battle_config.agi_penaly_num))/100; + else if(battle_config.agi_penaly_type == 2) //回避率がagi_penaly_num分減少 + flee -= (target_count - (battle_config.agi_penaly_count - 1))*battle_config.agi_penaly_num; + if(flee < 1) flee = 1; //回避率は最低でも1 + } + } + hitrate=battle_get_hit(src) - flee + 80; //命中率計算 + + type=0; // normal + div_ = 1; // single attack + + dex=battle_get_dex(src); //DEX + luk=battle_get_luk(src); //LUK + watk = battle_get_atk(src); //ATK + watk_ = battle_get_atk_(src); //ATK左手 + + if(skill_num==HW_MAGICCRASHER){ /* マジッククラッシャーはMATKで殴る */ + damage = damage2 = battle_get_matk1(src); //damega,damega2初登場、base_atkの取得 + }else{ + damage = damage2 = battle_get_baseatk(&sd->bl); //damega,damega2初登場、base_atkの取得 + } + atkmin = atkmin_ = dex; //最低ATKはDEXで初期化? + sd->state.arrow_atk = 0; //arrow_atk初期化 + if(sd->equip_index[9] >= 0 && sd->inventory_data[sd->equip_index[9]]) + atkmin = atkmin*(80 + sd->inventory_data[sd->equip_index[9]]->wlv*20)/100; + if(sd->equip_index[8] >= 0 && sd->inventory_data[sd->equip_index[8]]) + atkmin_ = atkmin_*(80 + sd->inventory_data[sd->equip_index[8]]->wlv*20)/100; + if(sd->status.weapon == 11) { //武器が弓矢の場合 + atkmin = watk * ((atkmin<watk)? atkmin:watk)/100; //弓用最低ATK計算 + flag=(flag&~BF_RANGEMASK)|BF_LONG; //遠距離攻撃フラグを有効 + if(sd->arrow_ele > 0) //属性矢なら属性を矢の属性に変更 + s_ele = sd->arrow_ele; + sd->state.arrow_atk = 1; //arrow_atk有効化 + } + + // サイズ修正 + // ペコ騎乗していて、槍で攻撃した場合は中型のサイズ修正を100にする + // ウェポンパーフェクション,ドレイクC + if(((sd->special_state.no_sizefix) || (pc_isriding(sd) && (sd->status.weapon==4 || sd->status.weapon==5) && t_size==1) || skill_num == MO_EXTREMITYFIST)){ //ペコ騎乗していて、槍で中型を攻撃 + atkmax = watk; + atkmax_ = watk_; + } else { + atkmax = (watk * sd->atkmods[ t_size ]) / 100; + atkmin = (atkmin * sd->atkmods[ t_size ]) / 100; + atkmax_ = (watk_ * sd->atkmods_[ t_size ]) / 100; + atkmin_ = (atkmin_ * sd->atkmods[ t_size ]) / 100; + } + if( (sc_data != NULL && sc_data[SC_WEAPONPERFECTION].timer!=-1) || (sd->special_state.no_sizefix)) { // ウェポンパーフェクション || ドレイクカード + atkmax = watk; + atkmax_ = watk_; + } + + if(atkmin > atkmax && !(sd->state.arrow_atk)) atkmin = atkmax; //弓は最低が上回る場合あり + if(atkmin_ > atkmax_) atkmin_ = atkmax_; + + if(sc_data != NULL && sc_data[SC_MAXIMIZEPOWER].timer!=-1 ){ // マキシマイズパワー + atkmin=atkmax; + atkmin_=atkmax_; + } + + //ダブルアタック判定 + if(sd->weapontype1 == 0x01) { + if(skill_num == 0 && skill_lv >= 0 && (skill = pc_checkskill(sd,TF_DOUBLE)) > 0) + da = (rand()%100 < (skill*5)) ? 1:0; + } + + //三段掌 + if(skill_num == 0 && skill_lv >= 0 && (skill = pc_checkskill(sd,MO_TRIPLEATTACK)) > 0 && sd->status.weapon <= 16 && !sd->state.arrow_atk) { + da = (rand()%100 < (30 - skill)) ? 2:0; + } + + if(sd->double_rate > 0 && da == 0 && skill_num == 0 && skill_lv >= 0) + da = (rand()%100 < sd->double_rate) ? 1:0; + + // 過剰精錬ボーナス + if(sd->overrefine>0 ) + damage+=(rand()%sd->overrefine)+1; + if(sd->overrefine_>0 ) + damage2+=(rand()%sd->overrefine_)+1; + + if(da == 0){ //ダブルアタックが発動していない + // クリティカル計算 + cri = battle_get_critical(src); + + if(sd->state.arrow_atk) + cri += sd->arrow_cri; + if(sd->status.weapon == 16) + // カタールの場合、クリティカルを倍に + cri <<=1; + cri -= battle_get_luk(target) * 3; + if(t_sc_data != NULL && t_sc_data[SC_SLEEP].timer!=-1 ) // 睡眠中はクリティカルが倍に + cri <<=1; + if(ac_flag) cri = 1000; + + if(skill_num == KN_AUTOCOUNTER) { + if(!(battle_config.pc_auto_counter_type&1)) + cri = 1000; + else + cri <<= 1; + } + + if(skill_num == SN_SHARPSHOOTING && rand()%100 < 50) + cri = 1000; + } + + if(tsd && tsd->critical_def) + cri = cri * (100-tsd->critical_def) / 100; + + if(da == 0 && (skill_num==0 || skill_num == KN_AUTOCOUNTER || skill_num == SN_SHARPSHOOTING) && skill_lv >= 0 && //ダブルアタックが発動していない + (rand() % 1000) < cri) // 判定(スキルの場合は無視) + { + damage += atkmax; + damage2 += atkmax_; + if(sd->atk_rate != 100) { + damage = (damage * sd->atk_rate)/100; + damage2 = (damage2 * sd->atk_rate)/100; + } + if(sd->state.arrow_atk) + damage += sd->arrow_atk; + type = 0x0a; + +/* if(def1 < 1000000) { + if(sd->def_ratio_atk_ele & (1<<t_ele) || sd->def_ratio_atk_race & (1<<t_race)) { + damage = (damage * (def1 + def2))/100; + idef_flag = 1; + } + if(sd->def_ratio_atk_ele_ & (1<<t_ele) || sd->def_ratio_atk_race_ & (1<<t_race)) { + damage2 = (damage2 * (def1 + def2))/100; + idef_flag_ = 1; + } + if(t_mode & 0x20) { + if(!idef_flag && sd->def_ratio_atk_race & (1<<10)) { + damage = (damage * (def1 + def2))/100; + idef_flag = 1; + } + if(!idef_flag_ && sd->def_ratio_atk_race_ & (1<<10)) { + damage2 = (damage2 * (def1 + def2))/100; + idef_flag_ = 1; + } + } + else { + if(!idef_flag && sd->def_ratio_atk_race & (1<<11)) { + damage = (damage * (def1 + def2))/100; + idef_flag = 1; + } + if(!idef_flag_ && sd->def_ratio_atk_race_ & (1<<11)) { + damage2 = (damage2 * (def1 + def2))/100; + idef_flag_ = 1; + } + } + }*/ + } + else { + int vitbonusmax; + + if(atkmax > atkmin) + damage += atkmin + rand() % (atkmax-atkmin + 1); + else + damage += atkmin ; + if(atkmax_ > atkmin_) + damage2 += atkmin_ + rand() % (atkmax_-atkmin_ + 1); + else + damage2 += atkmin_ ; + if(sd->atk_rate != 100) { + damage = (damage * sd->atk_rate)/100; + damage2 = (damage2 * sd->atk_rate)/100; + } + + if(sd->state.arrow_atk) { + if(sd->arrow_atk > 0) + damage += rand()%(sd->arrow_atk+1); + hitrate += sd->arrow_hit; + } + + if(skill_num != MO_INVESTIGATE && def1 < 1000000) { + if(sd->def_ratio_atk_ele & (1<<t_ele) || sd->def_ratio_atk_race & (1<<t_race)) { + damage = (damage * (def1 + def2))/100; + idef_flag = 1; + } + if(sd->def_ratio_atk_ele_ & (1<<t_ele) || sd->def_ratio_atk_race_ & (1<<t_race)) { + damage2 = (damage2 * (def1 + def2))/100; + idef_flag_ = 1; + } + if(t_mode & 0x20) { + if(!idef_flag && sd->def_ratio_atk_race & (1<<10)) { + damage = (damage * (def1 + def2))/100; + idef_flag = 1; + } + if(!idef_flag_ && sd->def_ratio_atk_race_ & (1<<10)) { + damage2 = (damage2 * (def1 + def2))/100; + idef_flag_ = 1; + } + } + else { + if(!idef_flag && sd->def_ratio_atk_race & (1<<11)) { + damage = (damage * (def1 + def2))/100; + idef_flag = 1; + } + if(!idef_flag_ && sd->def_ratio_atk_race_ & (1<<11)) { + damage2 = (damage2 * (def1 + def2))/100; + idef_flag_ = 1; + } + } + } + + // スキル修正1(攻撃力倍化系) + // オーバートラスト(+5% 〜 +25%),他攻撃系スキルの場合ここで補正 + // バッシュ,マグナムブレイク, + // ボーリングバッシュ,スピアブーメラン,ブランディッシュスピア,スピアスタッブ, + // メマーナイト,カートレボリューション + // ダブルストレイフィング,アローシャワー,チャージアロー, + // ソニックブロー + if(sc_data){ //状態異常中のダメージ追加 + if(sc_data[SC_OVERTHRUST].timer!=-1){ // オーバートラスト + damage += damage*(5*sc_data[SC_OVERTHRUST].val1)/100; + damage2 += damage2*(5*sc_data[SC_OVERTHRUST].val1)/100; + } + if(sc_data[SC_TRUESIGHT].timer!=-1){ // トゥルーサイト + damage += damage*(2*sc_data[SC_TRUESIGHT].val1)/100; + damage2 += damage2*(2*sc_data[SC_TRUESIGHT].val1)/100; + } + if(sc_data[SC_BERSERK].timer!=-1){ // バーサーク + damage += damage*50/100; + damage2 += damage2*50/100; + } + } + + if(skill_num>0){ + int i; + if( (i=skill_get_pl(skill_num))>0 ) + s_ele=s_ele_=i; + + flag=(flag&~BF_SKILLMASK)|BF_SKILL; + switch( skill_num ){ + case SM_BASH: // バッシュ + damage = damage*(100+ 30*skill_lv)/100; + damage2 = damage2*(100+ 30*skill_lv)/100; + hitrate = (hitrate*(100+5*skill_lv))/100; + break; + case SM_MAGNUM: // マグナムブレイク + damage = damage*(5*skill_lv +(wflag)?65:115 )/100; + damage2 = damage2*(5*skill_lv +(wflag)?65:115 )/100; + break; + case MC_MAMMONITE: // メマーナイト + damage = damage*(100+ 50*skill_lv)/100; + damage2 = damage2*(100+ 50*skill_lv)/100; + break; + case AC_DOUBLE: // ダブルストレイフィング + if(!sd->state.arrow_atk && sd->arrow_atk > 0) { + int arr = rand()%(sd->arrow_atk+1); + damage += arr; + damage2 += arr; + } + damage = damage*(180+ 20*skill_lv)/100; + damage2 = damage2*(180+ 20*skill_lv)/100; + div_=2; + if(sd->arrow_ele > 0) { + s_ele = sd->arrow_ele; + s_ele_ = sd->arrow_ele; + } + flag=(flag&~BF_RANGEMASK)|BF_LONG; + sd->state.arrow_atk = 1; + break; + case AC_SHOWER: // アローシャワー + if(!sd->state.arrow_atk && sd->arrow_atk > 0) { + int arr = rand()%(sd->arrow_atk+1); + damage += arr; + damage2 += arr; + } + damage = damage*(75 + 5*skill_lv)/100; + damage2 = damage2*(75 + 5*skill_lv)/100; + if(sd->arrow_ele > 0) { + s_ele = sd->arrow_ele; + s_ele_ = sd->arrow_ele; + } + flag=(flag&~BF_RANGEMASK)|BF_LONG; + sd->state.arrow_atk = 1; + break; + case AC_CHARGEARROW: // チャージアロー + if(!sd->state.arrow_atk && sd->arrow_atk > 0) { + int arr = rand()%(sd->arrow_atk+1); + damage += arr; + damage2 += arr; + } + damage = damage*150/100; + damage2 = damage2*150/100; + if(sd->arrow_ele > 0) { + s_ele = sd->arrow_ele; + s_ele_ = sd->arrow_ele; + } + flag=(flag&~BF_RANGEMASK)|BF_LONG; + sd->state.arrow_atk = 1; + break; + case KN_PIERCE: // ピアース + damage = damage*(100+ 10*skill_lv)/100; + damage2 = damage2*(100+ 10*skill_lv)/100; + hitrate=hitrate*(100+5*skill_lv)/100; + div_=t_size+1; + damage*=div_; + damage2*=div_; + break; + case KN_SPEARSTAB: // スピアスタブ + damage = damage*(100+ 15*skill_lv)/100; + damage2 = damage2*(100+ 15*skill_lv)/100; + break; + case KN_SPEARBOOMERANG: // スピアブーメラン + damage = damage*(100+ 50*skill_lv)/100; + damage2 = damage2*(100+ 50*skill_lv)/100; + flag=(flag&~BF_RANGEMASK)|BF_LONG; + break; + case KN_BRANDISHSPEAR: // ブランディッシュスピア + damage = damage*(100+ 20*skill_lv)/100; + damage2 = damage2*(100+ 20*skill_lv)/100; + if(skill_lv>3 && wflag==1) damage3+=damage/2; + if(skill_lv>6 && wflag==1) damage3+=damage/4; + if(skill_lv>9 && wflag==1) damage3+=damage/8; + if(skill_lv>6 && wflag==2) damage3+=damage/2; + if(skill_lv>9 && wflag==2) damage3+=damage/4; + if(skill_lv>9 && wflag==3) damage3+=damage/2; + damage +=damage3; + if(skill_lv>3 && wflag==1) damage4+=damage2/2; + if(skill_lv>6 && wflag==1) damage4+=damage2/4; + if(skill_lv>9 && wflag==1) damage4+=damage2/8; + if(skill_lv>6 && wflag==2) damage4+=damage2/2; + if(skill_lv>9 && wflag==2) damage4+=damage2/4; + if(skill_lv>9 && wflag==3) damage4+=damage2/2; + damage2 +=damage4; + blewcount=0; + break; + case KN_BOWLINGBASH: // ボウリングバッシュ + damage = damage*(100+ 50*skill_lv)/100; + damage2 = damage2*(100+ 50*skill_lv)/100; + blewcount=0; + break; + case KN_AUTOCOUNTER: + if(battle_config.pc_auto_counter_type&1) + hitrate += 20; + else + hitrate = 1000000; + flag=(flag&~BF_SKILLMASK)|BF_NORMAL; + break; + case AS_SONICBLOW: // ソニックブロウ + hitrate+=30; // hitrate +30, thanks to midas + damage = damage*(300+ 50*skill_lv)/100; + damage2 = damage2*(300+ 50*skill_lv)/100; + div_=8; + break; + case TF_SPRINKLESAND: // 砂まき + damage = damage*125/100; + damage2 = damage2*125/100; + break; + case MC_CARTREVOLUTION: // カートレボリューション + if(sd->cart_max_weight > 0 && sd->cart_weight > 0) { + damage = (damage*(150 + pc_checkskill(sd,BS_WEAPONRESEARCH) + (sd->cart_weight*100/sd->cart_max_weight) ) )/100; + damage2 = (damage2*(150 + pc_checkskill(sd,BS_WEAPONRESEARCH) + (sd->cart_weight*100/sd->cart_max_weight) ) )/100; + } + else { + damage = (damage*150)/100; + damage2 = (damage2*150)/100; + } + break; + // 以下MOB + case NPC_COMBOATTACK: // 多段攻撃 + div_=skill_get_num(skill_num,skill_lv); + damage *= div_; + damage2 *= div_; + break; + case NPC_RANDOMATTACK: // ランダムATK攻撃 + damage = damage*(50+rand()%150)/100; + damage2 = damage2*(50+rand()%150)/100; + break; + // 属性攻撃(適当) + case NPC_WATERATTACK: + case NPC_GROUNDATTACK: + case NPC_FIREATTACK: + case NPC_WINDATTACK: + case NPC_POISONATTACK: + case NPC_HOLYATTACK: + case NPC_DARKNESSATTACK: + case NPC_TELEKINESISATTACK: + damage = damage*(100+25*skill_lv)/100; + damage2 = damage2*(100+25*skill_lv)/100; + break; + case NPC_GUIDEDATTACK: + hitrate = 1000000; + break; + case NPC_RANGEATTACK: + flag=(flag&~BF_RANGEMASK)|BF_LONG; + break; + case NPC_PIERCINGATT: + flag=(flag&~BF_RANGEMASK)|BF_SHORT; + break; + case RG_BACKSTAP: // バックスタブ + if(battle_config.backstab_bow_penalty == 1 && sd->status.weapon == 11){ + damage = (damage*(300+ 40*skill_lv)/100)/2; + damage2 = (damage2*(300+ 40*skill_lv)/100)/2; + }else{ + damage = damage*(300+ 40*skill_lv)/100; + damage2 = damage2*(300+ 40*skill_lv)/100; + } + hitrate = 1000000; + break; + case RG_RAID: // サプライズアタック + damage = damage*(100+ 40*skill_lv)/100; + damage2 = damage2*(100+ 40*skill_lv)/100; + break; + case RG_INTIMIDATE: // インティミデイト + damage = damage*(100+ 30*skill_lv)/100; + damage2 = damage2*(100+ 30*skill_lv)/100; + break; + case CR_SHIELDCHARGE: // シールドチャージ + damage = damage*(100+ 20*skill_lv)/100; + damage2 = damage2*(100+ 20*skill_lv)/100; + flag=(flag&~BF_RANGEMASK)|BF_SHORT; + s_ele = 0; + break; + case CR_SHIELDBOOMERANG: // シールドブーメラン + damage = damage*(100+ 30*skill_lv)/100; + damage2 = damage2*(100+ 30*skill_lv)/100; + flag=(flag&~BF_RANGEMASK)|BF_LONG; + s_ele = 0; + break; + case CR_HOLYCROSS: // ホーリークロス + damage = damage*(100+ 35*skill_lv)/100; + damage2 = damage2*(100+ 35*skill_lv)/100; + div_=2; + break; + case CR_GRANDCROSS: + hitrate= 1000000; + break; + case AM_DEMONSTRATION: // デモンストレーション + damage = damage*(100+ 20*skill_lv)/100; + damage2 = damage2*(100+ 20*skill_lv)/100; + break; + case AM_ACIDTERROR: // アシッドテラー + damage = damage*(100+ 40*skill_lv)/100; + damage2 = damage2*(100+ 40*skill_lv)/100; + break; + case MO_FINGEROFFENSIVE: //指弾 + if(battle_config.finger_offensive_type == 0) { + damage = damage * (100 + 50 * skill_lv) / 100 * sd->spiritball_old; + damage2 = damage2 * (100 + 50 * skill_lv) / 100 * sd->spiritball_old; + div_ = sd->spiritball_old; + } + else { + damage = damage * (100 + 50 * skill_lv) / 100; + damage2 = damage2 * (100 + 50 * skill_lv) / 100; + div_ = 1; + } + break; + case MO_INVESTIGATE: // 発 勁 + if(def1 < 1000000) { + damage = damage*(100+ 75*skill_lv)/100 * (def1 + def2)/100; + damage2 = damage2*(100+ 75*skill_lv)/100 * (def1 + def2)/100; + } + hitrate = 1000000; + s_ele = 0; + s_ele_ = 0; + break; + case MO_EXTREMITYFIST: // 阿修羅覇鳳拳 + damage = damage * (8 + ((sd->status.sp)/10)) + 250 + (skill_lv * 150); + damage2 = damage2 * (8 + ((sd->status.sp)/10)) + 250 + (skill_lv * 150); + sd->status.sp = 0; + clif_updatestatus(sd,SP_SP); + hitrate = 1000000; + s_ele = 0; + s_ele_ = 0; + break; + case MO_CHAINCOMBO: // 連打掌 + damage = damage*(150+ 50*skill_lv)/100; + damage2 = damage2*(150+ 50*skill_lv)/100; + div_=4; + break; + case MO_COMBOFINISH: // 猛龍拳 + damage = damage*(240+ 60*skill_lv)/100; + damage2 = damage2*(240+ 60*skill_lv)/100; + break; + case BA_MUSICALSTRIKE: // ミュージカルストライク + if(!sd->state.arrow_atk && sd->arrow_atk > 0) { + int arr = rand()%(sd->arrow_atk+1); + damage += arr; + damage2 += arr; + } + damage = damage*(100+ 50 * skill_lv)/100; + damage2 = damage2*(100+ 50 * skill_lv)/100; + if(sd->arrow_ele > 0) { + s_ele = sd->arrow_ele; + s_ele_ = sd->arrow_ele; + } + flag=(flag&~BF_RANGEMASK)|BF_LONG; + sd->state.arrow_atk = 1; + break; + case DC_THROWARROW: // 矢撃ち + if(!sd->state.arrow_atk && sd->arrow_atk > 0) { + int arr = rand()%(sd->arrow_atk+1); + damage += arr; + damage2 += arr; + } + damage = damage*(100+ 50 * skill_lv)/100; + damage2 = damage2*(100+ 50 * skill_lv)/100; + if(sd->arrow_ele > 0) { + s_ele = sd->arrow_ele; + s_ele_ = sd->arrow_ele; + } + flag=(flag&~BF_RANGEMASK)|BF_LONG; + sd->state.arrow_atk = 1; + break; + case CH_TIGERFIST: // 伏虎拳 + damage = damage*(100+ 20*skill_lv)/100; + damage2 = damage2*(100+ 20*skill_lv)/100; + break; + case CH_CHAINCRUSH: // 連柱崩撃 + damage = damage*(100+ 20*skill_lv)/100; + damage2 = damage2*(100+ 20*skill_lv)/100; + div_=skill_get_num(skill_num,skill_lv); + break; + case CH_PALMSTRIKE: // 猛虎硬派山 + damage = damage*(50+ 100*skill_lv)/100; + damage2 = damage2*(50+ 100*skill_lv)/100; + break; + case LK_SPIRALPIERCE: /* スパイラルピアース */ + damage = damage*(100+ 50*skill_lv)/100; //増加量が分からないので適当に + damage2 = damage2*(100+ 50*skill_lv)/100; //増加量が分からないので適当に + div_=5; + if(tsd) + tsd->canmove_tick = gettick() + 1000; + else if(tmd) + tmd->canmove_tick = gettick() + 1000; + break; + case LK_HEADCRUSH: /* ヘッドクラッシュ */ + damage = damage*(100+ 20*skill_lv)/100; + damage2 = damage2*(100+ 20*skill_lv)/100; + break; + case LK_JOINTBEAT: /* ジョイントビート */ + damage = damage*(50+ 10*skill_lv)/100; + damage2 = damage2*(50+ 10*skill_lv)/100; + break; + case ASC_METEORASSAULT: /* メテオアサルト */ + damage = damage*(40+ 40*skill_lv)/100; + damage2 = damage2*(40+ 40*skill_lv)/100; + break; + case SN_SHARPSHOOTING: /* シャープシューティング */ + damage += damage*(30*skill_lv)/100; + damage2 += damage2*(30*skill_lv)/100; + break; + case CG_ARROWVULCAN: /* アローバルカン */ + damage = damage*(160+40*skill_lv)/100; + damage2 = damage2*(160+40*skill_lv)/100; + div_=9; + break; + case AS_SPLASHER: /* ベナムスプラッシャー */ + damage = damage*(200+20*skill_lv+20*pc_checkskill(sd,AS_POISONREACT))/100; + damage2 = damage2*(200+20*skill_lv+20*pc_checkskill(sd,AS_POISONREACT))/100; + break; + case PA_SACRIFICE: + if(sd){ + int hp, mhp, damage3; + hp = battle_get_hp(src); + mhp = battle_get_max_hp(src); + damage3 = mhp*((skill_lv/2)+(50/100))/100; + damage = (((skill_lv*15)+90)/100)*damage3/100; + damage2 = (((skill_lv*15)+90)/100)*damage3/100; + } + break; + case ASC_BREAKER: // -- moonsoul (special damage for ASC_BREAKER skill) + if(sd){ + int damage3; + int mdef1=battle_get_mdef(target); + int mdef2=battle_get_mdef2(target); + int imdef_flag=0; + + damage = ((damage * 5) + (skill_lv * battle_get_int(src) * 5) + rand()%500 + 500) /2; + damage2 = ((damage2 * 5) + (skill_lv * battle_get_int(src) * 5) + rand()%500 + 500) /2; + damage3 = damage; + hitrate = 1000000; + + if(sd->ignore_mdef_ele & (1<<t_ele) || sd->ignore_mdef_race & (1<<t_race)) + imdef_flag = 1; + if(t_mode & 0x20) { + if(sd->ignore_mdef_race & (1<<10)) + imdef_flag = 1; + } + else { + if(sd->ignore_mdef_race & (1<<11)) + imdef_flag = 1; + } + if(!imdef_flag){ + if(battle_config.magic_defense_type) { + damage3 = damage3 - (mdef1 * battle_config.magic_defense_type) - mdef2; + } + else{ + damage3 = (damage3*(100-mdef1))/100 - mdef2; + } + } + + if(damage3<1) + damage3=1; + + damage3=battle_attr_fix(damage2,s_ele_, battle_get_element(target) ); + } + break; + } + } + if(da == 2) { //三段掌が発動しているか + type = 0x08; + div_ = 255; //三段掌用に… + damage = damage * (100 + 20 * pc_checkskill(sd, MO_TRIPLEATTACK)) / 100; + } + + if( skill_num!=NPC_CRITICALSLASH ){ + // 対 象の防御力によるダメージの減少 + // ディバインプロテクション(ここでいいのかな?) + if ( skill_num != MO_INVESTIGATE && skill_num != MO_EXTREMITYFIST && skill_num != KN_AUTOCOUNTER && def1 < 1000000) { //DEF, VIT無視 + int t_def; + target_count = 1 + battle_counttargeted(target,src,battle_config.vit_penaly_count_lv); + if(battle_config.vit_penaly_type > 0) { + if(target_count >= battle_config.vit_penaly_count) { + if(battle_config.vit_penaly_type == 1) { + def1 = (def1 * (100 - (target_count - (battle_config.vit_penaly_count - 1))*battle_config.vit_penaly_num))/100; + def2 = (def2 * (100 - (target_count - (battle_config.vit_penaly_count - 1))*battle_config.vit_penaly_num))/100; + t_vit = (t_vit * (100 - (target_count - (battle_config.vit_penaly_count - 1))*battle_config.vit_penaly_num))/100; + } + else if(battle_config.vit_penaly_type == 2) { + def1 -= (target_count - (battle_config.vit_penaly_count - 1))*battle_config.vit_penaly_num; + def2 -= (target_count - (battle_config.vit_penaly_count - 1))*battle_config.vit_penaly_num; + t_vit -= (target_count - (battle_config.vit_penaly_count - 1))*battle_config.vit_penaly_num; + } + if(def1 < 0) def1 = 0; + if(def2 < 1) def2 = 1; + if(t_vit < 1) t_vit = 1; + } + } + t_def = def2*8/10; + vitbonusmax = (t_vit/20)*(t_vit/20)-1; + if(sd->ignore_def_ele & (1<<t_ele) || sd->ignore_def_race & (1<<t_race)) + idef_flag = 1; + if(sd->ignore_def_ele_ & (1<<t_ele) || sd->ignore_def_race_ & (1<<t_race)) + idef_flag_ = 1; + if(t_mode & 0x20) { + if(sd->ignore_def_race & (1<<10)) + idef_flag = 1; + if(sd->ignore_def_race_ & (1<<10)) + idef_flag_ = 1; + } + else { + if(sd->ignore_def_race & (1<<11)) + idef_flag = 1; + if(sd->ignore_def_race_ & (1<<11)) + idef_flag_ = 1; + } + + if(!idef_flag){ + if(battle_config.player_defense_type) { + damage = damage - (def1 * battle_config.player_defense_type) - t_def - ((vitbonusmax < 1)?0: rand()%(vitbonusmax+1) ); + } + else{ + damage = damage * (100 - def1) /100 - t_def - ((vitbonusmax < 1)?0: rand()%(vitbonusmax+1) ); + } + } + if(!idef_flag_){ + if(battle_config.player_defense_type) { + damage2 = damage2 - (def1 * battle_config.player_defense_type) - t_def - ((vitbonusmax < 1)?0: rand()%(vitbonusmax+1) ); + } + else{ + damage2 = damage2 * (100 - def1) /100 - t_def - ((vitbonusmax < 1)?0: rand()%(vitbonusmax+1) ); + } + } + } + } + } + // 精錬ダメージの追加 + if( skill_num != MO_INVESTIGATE && skill_num != MO_EXTREMITYFIST) { //DEF, VIT無視 + damage += battle_get_atk2(src); + damage2 += battle_get_atk_2(src); + } + if(skill_num == CR_SHIELDBOOMERANG) { + if(sd->equip_index[8] >= 0) { + int index = sd->equip_index[8]; + if(sd->inventory_data[index] && sd->inventory_data[index]->type == 5) { + damage += sd->inventory_data[index]->weight/10; + damage += sd->status.inventory[index].refine * pc_getrefinebonus(0,1); + } + } + } + if(skill_num == LK_SPIRALPIERCE) { /* スパイラルピアース */ + if(sd->equip_index[9] >= 0) { //重量で追加ダメージらしいのでシールドブーメランを参考に追加 + int index = sd->equip_index[9]; + if(sd->inventory_data[index] && sd->inventory_data[index]->type == 4) { + damage += (int)(double)(sd->inventory_data[index]->weight*(0.8*skill_lv*4/10)); + damage += sd->status.inventory[index].refine * pc_getrefinebonus(0,1); + } + } + } + + // 0未満だった場合1に補正 + if(damage<1) damage=1; + if(damage2<1) damage2=1; + + // スキル修正2(修練系) + // 修練ダメージ(右手のみ) ソニックブロー時は別処理(1撃に付き1/8適応) + if( skill_num != MO_INVESTIGATE && skill_num != MO_EXTREMITYFIST && skill_num != CR_GRANDCROSS) { //修練ダメージ無視 + damage = battle_addmastery(sd,target,damage,0); + damage2 = battle_addmastery(sd,target,damage2,1); + } + + if(sd->perfect_hit > 0) { + if(rand()%100 < sd->perfect_hit) + hitrate = 1000000; + } + + // 回避修正 + hitrate = (hitrate<5)?5:hitrate; + if( hitrate < 1000000 && // 必中攻撃 + (t_sc_data != NULL && (t_sc_data[SC_SLEEP].timer!=-1 || // 睡眠は必中 + t_sc_data[SC_STAN].timer!=-1 || // スタンは必中 + t_sc_data[SC_FREEZE].timer!=-1 || (t_sc_data[SC_STONE].timer!=-1 && t_sc_data[SC_STONE].val2==0) ) ) ) // 凍結は必中 + hitrate = 1000000; + if(type == 0 && rand()%100 >= hitrate) { + damage = damage2 = 0; + dmg_lv = ATK_FLEE; + } else { + dmg_lv = ATK_DEF; + } + // スキル修正3(武器研究) + if( (skill=pc_checkskill(sd,BS_WEAPONRESEARCH)) > 0) { + damage+= skill*2; + damage2+= skill*2; + } + //Advanced Katar Research by zanetheinsane + if(sd->weapontype1 == 0x10 || sd->weapontype2 == 0x10){ + if((skill = pc_checkskill(sd,ASC_KATAR)) > 0) { + damage += (damage*((skill*2)+10)) / 100 ; + } + } + +//スキルによるダメージ補正ここまで + +//カードによるダメージ追加処理ここから + cardfix=100; + if(!sd->state.arrow_atk) { //弓矢以外 + if(!battle_config.left_cardfix_to_right) { //左手カード補正設定無し + cardfix=cardfix*(100+sd->addrace[t_race])/100; // 種族によるダメージ修正 + cardfix=cardfix*(100+sd->addele[t_ele])/100; // 属性によるダメージ修正 + cardfix=cardfix*(100+sd->addsize[t_size])/100; // サイズによるダメージ修正 + } + else { + cardfix=cardfix*(100+sd->addrace[t_race]+sd->addrace_[t_race])/100; // 種族によるダメージ修正(左手による追加あり) + cardfix=cardfix*(100+sd->addele[t_ele]+sd->addele_[t_ele])/100; // 属性によるダメージ修正(左手による追加あり) + cardfix=cardfix*(100+sd->addsize[t_size]+sd->addsize_[t_size])/100; // サイズによるダメージ修正(左手による追加あり) + } + } + else { //弓矢 + cardfix=cardfix*(100+sd->addrace[t_race]+sd->arrow_addrace[t_race])/100; // 種族によるダメージ修正(弓矢による追加あり) + cardfix=cardfix*(100+sd->addele[t_ele]+sd->arrow_addele[t_ele])/100; // 属性によるダメージ修正(弓矢による追加あり) + cardfix=cardfix*(100+sd->addsize[t_size]+sd->arrow_addsize[t_size])/100; // サイズによるダメージ修正(弓矢による追加あり) + } + if(t_mode & 0x20) { //ボス + if(!sd->state.arrow_atk) { //弓矢攻撃以外なら + if(!battle_config.left_cardfix_to_right) //左手カード補正設定無し + cardfix=cardfix*(100+sd->addrace[10])/100; //ボスモンスターに追加ダメージ + else //左手カード補正設定あり + cardfix=cardfix*(100+sd->addrace[10]+sd->addrace_[10])/100; //ボスモンスターに追加ダメージ(左手による追加あり) + } + else //弓矢攻撃 + cardfix=cardfix*(100+sd->addrace[10]+sd->arrow_addrace[10])/100; //ボスモンスターに追加ダメージ(弓矢による追加あり) + } + else { //ボスじゃない + if(!sd->state.arrow_atk) { //弓矢攻撃以外 + if(!battle_config.left_cardfix_to_right) //左手カード補正設定無し + cardfix=cardfix*(100+sd->addrace[11])/100; //ボス以外モンスターに追加ダメージ + else //左手カード補正設定あり + cardfix=cardfix*(100+sd->addrace[11]+sd->addrace_[11])/100; //ボス以外モンスターに追加ダメージ(左手による追加あり) + } + else + cardfix=cardfix*(100+sd->addrace[11]+sd->arrow_addrace[11])/100; //ボス以外モンスターに追加ダメージ(弓矢による追加あり) + } + //特定Class用補正処理(少女の日記→ボンゴン用?) + t_class = battle_get_class(target); + for(i=0;i<sd->add_damage_class_count;i++) { + if(sd->add_damage_classid[i] == t_class) { + cardfix=cardfix*(100+sd->add_damage_classrate[i])/100; + break; + } + } + if(skill_num != CR_GRANDCROSS || !battle_config.gx_cardfix) + damage=damage*cardfix/100; //カード補正によるダメージ増加 +//カードによるダメージ増加処理ここまで + +//カードによるダメージ追加処理(左手)ここから + cardfix=100; + if(!battle_config.left_cardfix_to_right) { //左手カード補正設定無し + cardfix=cardfix*(100+sd->addrace_[t_race])/100; // 種族によるダメージ修正左手 + cardfix=cardfix*(100+sd->addele_[t_ele])/100; // 属 性によるダメージ修正左手 + cardfix=cardfix*(100+sd->addsize_[t_size])/100; // サイズによるダメージ修正左手 + if(t_mode & 0x20) //ボス + cardfix=cardfix*(100+sd->addrace_[10])/100; //ボスモンスターに追加ダメージ左手 + else + cardfix=cardfix*(100+sd->addrace_[11])/100; //ボス以外モンスターに追加ダメージ左手 + } + //特定Class用補正処理左手(少女の日記→ボンゴン用?) + for(i=0;i<sd->add_damage_class_count_;i++) { + if(sd->add_damage_classid_[i] == t_class) { + cardfix=cardfix*(100+sd->add_damage_classrate_[i])/100; + break; + } + } + if(skill_num != CR_GRANDCROSS) damage2=damage2*cardfix/100; //カード補正による左手ダメージ増加 +//カードによるダメージ増加処理(左手)ここまで + +// -- moonsoul (cardfix for magic damage portion of ASC_BREAKER) + if(skill_num == ASC_BREAKER) + damage3 = damage3 * cardfix / 100; + +//カードによるダメージ減衰処理ここから + if(tsd){ //対象がPCの場合 + cardfix=100; + cardfix=cardfix*(100-tsd->subrace[s_race])/100; // 種族によるダメージ耐性 + cardfix=cardfix*(100-tsd->subele[s_ele])/100; // 属性によるダメージ耐性 + if(battle_get_mode(src) & 0x20) + cardfix=cardfix*(100-tsd->subrace[10])/100; //ボスからの攻撃はダメージ減少 + else + cardfix=cardfix*(100-tsd->subrace[11])/100; //ボス以外からの攻撃はダメージ減少 + //特定Class用補正処理左手(少女の日記→ボンゴン用?) + for(i=0;i<tsd->add_def_class_count;i++) { + if(tsd->add_def_classid[i] == sd->status.class) { + cardfix=cardfix*(100-tsd->add_def_classrate[i])/100; + break; + } + } + if(flag&BF_LONG) + cardfix=cardfix*(100-tsd->long_attack_def_rate)/100; //遠距離攻撃はダメージ減少(ホルンCとか) + if(flag&BF_SHORT) + cardfix=cardfix*(100-tsd->near_attack_def_rate)/100; //近距離攻撃はダメージ減少(該当無し?) + damage=damage*cardfix/100; //カード補正によるダメージ減少 + damage2=damage2*cardfix/100; //カード補正による左手ダメージ減少 + } +//カードによるダメージ減衰処理ここまで + +//対象にステータス異常がある場合のダメージ減算処理ここから + if(t_sc_data) { + cardfix=100; + if(t_sc_data[SC_DEFENDER].timer != -1 && flag&BF_LONG) //ディフェンダー状態で遠距離攻撃 + cardfix=cardfix*(100-t_sc_data[SC_DEFENDER].val2)/100; //ディフェンダーによる減衰 + if(cardfix != 100) { + damage=damage*cardfix/100; //ディフェンダー補正によるダメージ減少 + damage2=damage2*cardfix/100; //ディフェンダー補正による左手ダメージ減少 + } + if(t_sc_data[SC_ASSUMPTIO].timer != -1){ //アスムプティオ + if(!map[target->m].flag.pvp){ + damage=damage/3; + damage2=damage2/3; + }else{ + damage=damage/2; + damage2=damage2/2; + } + } + } +//対象にステータス異常がある場合のダメージ減算処理ここまで + + if(damage < 0) damage = 0; + if(damage2 < 0) damage2 = 0; + + // 属 性の適用 + damage=battle_attr_fix(damage,s_ele, battle_get_element(target) ); + damage2=battle_attr_fix(damage2,s_ele_, battle_get_element(target) ); + + // 星のかけら、気球の適用 + damage += sd->star; + damage2 += sd->star_; + damage += sd->spiritball*3; + damage2 += sd->spiritball*3; + + if(sc_data && sc_data[SC_AURABLADE].timer!=-1){ /* オーラブレード 必中 */ + damage += sc_data[SC_AURABLADE].val1 * 10; + damage2 += sc_data[SC_AURABLADE].val1 * 10; + } + if(skill_num==PA_PRESSURE){ /* プレッシャー 必中? */ + damage = 700+100*skill_lv; + damage2 = 700+100*skill_lv; + } + + // >二刀流の左右ダメージ計算誰かやってくれぇぇぇぇえええ! + // >map_session_data に左手ダメージ(atk,atk2)追加して + // >pc_calcstatus()でやるべきかな? + // map_session_data に左手武器(atk,atk2,ele,star,atkmods)追加して + // pc_calcstatus()でデータを入力しています + + //左手のみ武器装備 + if(sd->weapontype1 == 0 && sd->weapontype2 > 0) { + damage = damage2; + damage2 = 0; + } + // 右手、左手修練の適用 + if(sd->status.weapon > 16) {// 二刀流か? + int dmg = damage, dmg2 = damage2; + // 右手修練(60% 〜 100%) 右手全般 + skill = pc_checkskill(sd,AS_RIGHT); + damage = damage * (50 + (skill * 10))/100; + if(dmg > 0 && damage < 1) damage = 1; + // 左手修練(40% 〜 80%) 左手全般 + skill = pc_checkskill(sd,AS_LEFT); + damage2 = damage2 * (30 + (skill * 10))/100; + if(dmg2 > 0 && damage2 < 1) damage2 = 1; + } + else //二刀流でなければ左手ダメージは0 + damage2 = 0; + + // 右手,短剣のみ + if(da == 1) { //ダブルアタックが発動しているか + div_ = 2; + damage += damage; + type = 0x08; + } + + if(sd->status.weapon == 16) { + // カタール追撃ダメージ + skill = pc_checkskill(sd,TF_DOUBLE); + damage2 = damage * (1 + (skill * 2))/100; + if(damage > 0 && damage2 < 1) damage2 = 1; + } + + // インベナム修正 + if(skill_num==TF_POISON){ + damage = battle_attr_fix(damage + 15*skill_lv, s_ele, battle_get_element(target) ); + } + if(skill_num==MC_CARTREVOLUTION){ + damage = battle_attr_fix(damage, 0, battle_get_element(target) ); + } + + // 完全回避の判定 + if(skill_num == 0 && skill_lv >= 0 && tsd!=NULL && div_ < 255 && rand()%1000 < battle_get_flee2(target) ){ + damage=damage2=0; + type=0x0b; + dmg_lv = ATK_LUCKY; + } + + // 対象が完全回避をする設定がONなら + if(battle_config.enemy_perfect_flee) { + if(skill_num == 0 && skill_lv >= 0 && tmd!=NULL && div_ < 255 && rand()%1000 < battle_get_flee2(target) ) { + damage=damage2=0; + type=0x0b; + dmg_lv = ATK_LUCKY; + } + } + + //MobのModeに頑強フラグが立っているときの処理 + if(t_mode&0x40){ + if(damage > 0) + damage = 1; + if(damage2 > 0) + damage2 = 1; + } + + //bNoWeaponDamage(設定アイテム無し?)でグランドクロスじゃない場合はダメージが0 + if( tsd && tsd->special_state.no_weapon_damage && skill_num != CR_GRANDCROSS) + damage = damage2 = 0; + + if(skill_num != CR_GRANDCROSS && (damage > 0 || damage2 > 0) ) { + if(damage2<1) // ダメージ最終修正 + damage=battle_calc_damage(src,target,damage,div_,skill_num,skill_lv,flag); + else if(damage<1) // 右手がミス? + damage2=battle_calc_damage(src,target,damage2,div_,skill_num,skill_lv,flag); + else { // 両 手/カタールの場合はちょっと計算ややこしい + int d1=damage+damage2,d2=damage2; + damage=battle_calc_damage(src,target,damage+damage2,div_,skill_num,skill_lv,flag); + damage2=(d2*100/d1)*damage/100; + if(damage > 1 && damage2 < 1) damage2=1; + damage-=damage2; + } + } + + /* For executioner card [Valaris] */ + if(src->type == BL_PC && sd->random_attack_increase_add > 0 && sd->random_attack_increase_per > 0 && skill_num == 0 ){ + if(rand()%100 < sd->random_attack_increase_per){ + if(damage >0) damage*=sd->random_attack_increase_add/100; + if(damage2 >0) damage2*=sd->random_attack_increase_add/100; + } + } + /* End addition */ + +// -- moonsoul (final combination of phys, mag damage for ASC_BREAKER) + if(skill_num == ASC_BREAKER) { + damage += damage3; + damage2 += damage3; + } + + wd.damage=damage; + wd.damage2=damage2; + wd.type=type; + wd.div_=div_; + wd.amotion=battle_get_amotion(src); + if(skill_num == KN_AUTOCOUNTER) + wd.amotion >>= 1; + wd.dmotion=battle_get_dmotion(target); + wd.blewcount=blewcount; + wd.flag=flag; + wd.dmg_lv=dmg_lv; + + return wd; +} + +/*========================================== + * 武器ダメージ計算 + *------------------------------------------ + */ +struct Damage battle_calc_weapon_attack( + struct block_list *src,struct block_list *target,int skill_num,int skill_lv,int wflag) +{ + struct Damage wd; + + //return前の処理があるので情報出力部のみ変更 + if (src == NULL || target == NULL) { + nullpo_info(NLP_MARK); + memset(&wd,0,sizeof(wd)); + return wd; + } + + if(target->type == BL_PET) + memset(&wd,0,sizeof(wd)); + + else if(src->type == BL_PC) + wd = battle_calc_pc_weapon_attack(src,target,skill_num,skill_lv,wflag); // weapon breaking [Valaris] + else if(src->type == BL_MOB) + wd = battle_calc_mob_weapon_attack(src,target,skill_num,skill_lv,wflag); + else if(src->type == BL_PET) + wd = battle_calc_pet_weapon_attack(src,target,skill_num,skill_lv,wflag); + else + memset(&wd,0,sizeof(wd)); + + if(battle_config.equipment_breaking && src->type==BL_PC && (wd.damage > 0 || wd.damage2 > 0)) { + struct map_session_data *sd=(struct map_session_data *)src; + if(sd->status.weapon && sd->status.weapon!=11) { + int breakrate=1; + if(target->type == BL_PC && sd->sc_data[SC_MELTDOWN].timer!=-1){ + breakrate+=100*sd->sc_data[SC_MELTDOWN].val1; + if(rand()%10000 < breakrate*battle_config.equipment_break_rate/100 || breakrate >= 10000) + pc_breakweapon((struct map_session_data *)target); + } + if(sd->sc_data[SC_OVERTHRUST].timer!=-1) + breakrate+=20*sd->sc_data[SC_OVERTHRUST].val1; + if(wd.type==0x0a) + breakrate*=2; + if(rand()%10000 < breakrate*battle_config.equipment_break_rate/100 || breakrate >= 10000) { + pc_breakweapon(sd); + memset(&wd,0,sizeof(wd)); + } + } + } + + if (battle_config.equipment_breaking && target->type == BL_PC && (wd.damage > 0 || wd.damage2 > 0)) { + int breakrate=1; + if(src->type==BL_PC && ((struct map_session_data *)src)->sc_data[SC_MELTDOWN].timer!=-1) breakrate+=70*((struct map_session_data *)src)->sc_data[SC_MELTDOWN].val1; + if (wd.type==0x0a) + breakrate*=2; + if (rand()%10000 < breakrate*battle_config.equipment_break_rate/100 || breakrate >= 10000) { + pc_breakarmor((struct map_session_data *)target); + } + } + + return wd; +} + +/*========================================== + * 魔法ダメージ計算 + *------------------------------------------ + */ +struct Damage battle_calc_magic_attack( + struct block_list *bl,struct block_list *target,int skill_num,int skill_lv,int flag) + { + int mdef1=battle_get_mdef(target); + int mdef2=battle_get_mdef2(target); + int matk1,matk2,damage=0,div_=1,blewcount=skill_get_blewcount(skill_num,skill_lv),rdamage = 0; + struct Damage md; + int aflag; + int normalmagic_flag=1; + int ele=0,race=7,t_ele=0,t_race=7,t_mode = 0,cardfix,t_class,i; + struct map_session_data *sd=NULL,*tsd=NULL; + struct mob_data *tmd = NULL; + + + //return前の処理があるので情報出力部のみ変更 + if( bl == NULL || target == NULL ){ + nullpo_info(NLP_MARK); + memset(&md,0,sizeof(md)); + return md; + } + + if(target->type == BL_PET) { + memset(&md,0,sizeof(md)); + return md; + } + + matk1=battle_get_matk1(bl); + matk2=battle_get_matk2(bl); + ele = skill_get_pl(skill_num); + race = battle_get_race(bl); + t_ele = battle_get_elem_type(target); + t_race = battle_get_race(target); + t_mode = battle_get_mode(target); + +#define MATK_FIX( a,b ) { matk1=matk1*(a)/(b); matk2=matk2*(a)/(b); } + + if( bl->type==BL_PC && (sd=(struct map_session_data *)bl) ){ + sd->state.attack_type = BF_MAGIC; + if(sd->matk_rate != 100) + MATK_FIX(sd->matk_rate,100); + sd->state.arrow_atk = 0; + } + if( target->type==BL_PC ) + tsd=(struct map_session_data *)target; + else if( target->type==BL_MOB ) + tmd=(struct mob_data *)target; + + aflag=BF_MAGIC|BF_LONG|BF_SKILL; + + if(skill_num > 0){ + switch(skill_num){ // 基本ダメージ計算(スキルごとに処理) + // ヒールor聖体 + case AL_HEAL: + case PR_BENEDICTIO: + damage = skill_calc_heal(bl,skill_lv)/2; + normalmagic_flag=0; + break; + case PR_ASPERSIO: /* アスペルシオ */ + damage = 40; //固定ダメージ + normalmagic_flag=0; + break; + case PR_SANCTUARY: // サンクチュアリ + damage = (skill_lv>6)?388:skill_lv*50; + normalmagic_flag=0; + blewcount|=0x10000; + break; + case ALL_RESURRECTION: + case PR_TURNUNDEAD: // 攻撃リザレクションとターンアンデッド + if(target->type != BL_PC && battle_check_undead(t_race,t_ele)){ + int hp, mhp, thres; + hp = battle_get_hp(target); + mhp = battle_get_max_hp(target); + thres = (skill_lv * 20) + battle_get_luk(bl)+ + battle_get_int(bl) + battle_get_lv(bl)+ + ((200 - hp * 200 / mhp)); + if(thres > 700) thres = 700; +// if(battle_config.battle_log) +// printf("ターンアンデッド! 確率%d ‰(千分率)\n", thres); + if(rand()%1000 < thres && !(t_mode&0x20)) // 成功 + damage = hp; + else // 失敗 + damage = battle_get_lv(bl) + battle_get_int(bl) + skill_lv * 10; + } + normalmagic_flag=0; + break; + + case MG_NAPALMBEAT: // ナパームビート(分散計算込み) + MATK_FIX(70+ skill_lv*10,100); + if(flag>0){ + MATK_FIX(1,flag); + }else { + if(battle_config.error_log) + printf("battle_calc_magic_attack(): napam enemy count=0 !\n"); + } + break; + case MG_FIREBALL: // ファイヤーボール + { + const int drate[]={100,90,70}; + if(flag>2) + matk1=matk2=0; + else + MATK_FIX( (95+skill_lv*5)*drate[flag] ,10000 ); + } + break; + case MG_FIREWALL: // ファイヤーウォール +/* + if( (t_ele!=3 && !battle_check_undead(t_race,t_ele)) || target->type==BL_PC ) //PCは火属性でも飛ぶ?そもそもダメージ受ける? + blewcount |= 0x10000; + else + blewcount = 0; +*/ + if((t_ele==3 || battle_check_undead(t_race,t_ele)) && target->type!=BL_PC) + blewcount = 0; + else + blewcount |= 0x10000; + MATK_FIX( 1,2 ); + break; + case MG_THUNDERSTORM: // サンダーストーム + MATK_FIX( 80,100 ); + break; + case MG_FROSTDIVER: // フロストダイバ + MATK_FIX( 100+skill_lv*10, 100); + break; + case WZ_FROSTNOVA: // フロストダイバ + MATK_FIX( ((100+skill_lv*10)*(2/3)), 100); + break; + case WZ_FIREPILLAR: // ファイヤーピラー + if(mdef1 < 1000000) + mdef1=mdef2=0; // MDEF無視 + MATK_FIX( 1,5 ); + matk1+=50; + matk2+=50; + break; + case WZ_SIGHTRASHER: + MATK_FIX( 100+skill_lv*20, 100); + break; + case WZ_METEOR: + case WZ_JUPITEL: // ユピテルサンダー + break; + case WZ_VERMILION: // ロードオブバーミリオン + MATK_FIX( skill_lv*20+80, 100 ); + break; + case WZ_WATERBALL: // ウォーターボール + matk1+= skill_lv*30; + matk2+= skill_lv*30; + break; + case WZ_STORMGUST: // ストームガスト + MATK_FIX( skill_lv*40+100 ,100 ); + blewcount|=0x10000; + break; + case AL_HOLYLIGHT: // ホーリーライト + MATK_FIX( 125,100 ); + break; + case AL_RUWACH: + MATK_FIX( 145,100 ); + break; + case HW_NAPALMVULCAN: // ナパームビート(分散計算込み) + MATK_FIX(70+ skill_lv*10,100); + if(flag>0){ + MATK_FIX(1,flag); + }else { + if(battle_config.error_log) + printf("battle_calc_magic_attack(): napalmvulcan enemy count=0 !\n"); + } + break; + } + } + + if(normalmagic_flag){ // 一般魔法ダメージ計算 + int imdef_flag=0; + if(matk1>matk2) + damage= matk2+rand()%(matk1-matk2+1); + else + damage= matk2; + if(sd) { + if(sd->ignore_mdef_ele & (1<<t_ele) || sd->ignore_mdef_race & (1<<t_race)) + imdef_flag = 1; + if(t_mode & 0x20) { + if(sd->ignore_mdef_race & (1<<10)) + imdef_flag = 1; + } + else { + if(sd->ignore_mdef_race & (1<<11)) + imdef_flag = 1; + } + } + if(!imdef_flag){ + if(battle_config.magic_defense_type) { + damage = damage - (mdef1 * battle_config.magic_defense_type) - mdef2; + } + else{ + damage = (damage*(100-mdef1))/100 - mdef2; + } + } + + if(damage<1) + damage=1; + } + + if(sd) { + cardfix=100; + cardfix=cardfix*(100+sd->magic_addrace[t_race])/100; + cardfix=cardfix*(100+sd->magic_addele[t_ele])/100; + if(t_mode & 0x20) + cardfix=cardfix*(100+sd->magic_addrace[10])/100; + else + cardfix=cardfix*(100+sd->magic_addrace[11])/100; + t_class = battle_get_class(target); + for(i=0;i<sd->add_magic_damage_class_count;i++) { + if(sd->add_magic_damage_classid[i] == t_class) { + cardfix=cardfix*(100+sd->add_magic_damage_classrate[i])/100; + break; + } + } + damage=damage*cardfix/100; + } + + if( tsd ){ + int s_class = battle_get_class(bl); + cardfix=100; + cardfix=cardfix*(100-tsd->subele[ele])/100; // 属 性によるダメージ耐性 + cardfix=cardfix*(100-tsd->subrace[race])/100; // 種族によるダメージ耐性 + cardfix=cardfix*(100-tsd->magic_subrace[race])/100; + if(battle_get_mode(bl) & 0x20) + cardfix=cardfix*(100-tsd->magic_subrace[10])/100; + else + cardfix=cardfix*(100-tsd->magic_subrace[11])/100; + for(i=0;i<tsd->add_mdef_class_count;i++) { + if(tsd->add_mdef_classid[i] == s_class) { + cardfix=cardfix*(100-tsd->add_mdef_classrate[i])/100; + break; + } + } + cardfix=cardfix*(100-tsd->magic_def_rate)/100; + damage=damage*cardfix/100; + } + if(damage < 0) damage = 0; + + damage=battle_attr_fix(damage, ele, battle_get_element(target) ); // 属 性修正 + + if(skill_num == CR_GRANDCROSS) { // グランドクロス + struct Damage wd; + wd=battle_calc_weapon_attack(bl,target,skill_num,skill_lv,flag); + damage = (damage + wd.damage) * (100 + 40*skill_lv)/100; + if(battle_config.gx_dupele) damage=battle_attr_fix(damage, ele, battle_get_element(target) ); //属性2回かかる + if(bl==target) damage=damage/2; //反動は半分 + } + + div_=skill_get_num( skill_num,skill_lv ); + + if(div_>1 && skill_num != WZ_VERMILION) + damage*=div_; + +// if(mdef1 >= 1000000 && damage > 0) + if(t_mode&0x40 && damage > 0) + damage = 1; + + if( tsd && tsd->special_state.no_magic_damage) { + if (battle_config.gtb_pvp_only != 0) { // [MouseJstr] + if ((map[target->m].flag.pvp || map[target->m].flag.gvg) && target->type==BL_PC) + damage = (damage * (100 - battle_config.gtb_pvp_only)) / 100; + } else + damage=0; // 黄 金蟲カード(魔法ダメージ0) + } + + damage=battle_calc_damage(bl,target,damage,div_,skill_num,skill_lv,aflag); // 最終修正 + + /* magic_damage_return by [AppleGirl] and [Valaris] */ + if( target->type==BL_PC && tsd && tsd->magic_damage_return > 0 ){ + rdamage += damage * tsd->magic_damage_return / 100; + if(rdamage < 1) rdamage = 1; + clif_damage(target,bl,gettick(),0,0,rdamage,0,0,0); + battle_damage(target,bl,rdamage,0); + } + /* end magic_damage_return */ + + md.damage=damage; + md.div_=div_; + md.amotion=battle_get_amotion(bl); + md.dmotion=battle_get_dmotion(target); + md.damage2=0; + md.type=0; + md.blewcount=blewcount; + md.flag=aflag; + + return md; +} + +/*========================================== + * その他ダメージ計算 + *------------------------------------------ + */ +struct Damage battle_calc_misc_attack( + struct block_list *bl,struct block_list *target,int skill_num,int skill_lv,int flag) +{ + int int_=battle_get_int(bl); +// int luk=battle_get_luk(bl); + int dex=battle_get_dex(bl); + int skill,ele,race,cardfix; + struct map_session_data *sd=NULL,*tsd=NULL; + int damage=0,div_=1,blewcount=skill_get_blewcount(skill_num,skill_lv); + struct Damage md; + int damagefix=1; + + int aflag=BF_MISC|BF_LONG|BF_SKILL; + + //return前の処理があるので情報出力部のみ変更 + if( bl == NULL || target == NULL ){ + nullpo_info(NLP_MARK); + memset(&md,0,sizeof(md)); + return md; + } + + if(target->type == BL_PET) { + memset(&md,0,sizeof(md)); + return md; + } + + if( bl->type == BL_PC && (sd=(struct map_session_data *)bl) ) { + sd->state.attack_type = BF_MISC; + sd->state.arrow_atk = 0; + } + + if( target->type==BL_PC ) + tsd=(struct map_session_data *)target; + + switch(skill_num){ + + case HT_LANDMINE: // ランドマイン + damage=skill_lv*(dex+75)*(100+int_)/100; + break; + + case HT_BLASTMINE: // ブラストマイン + damage=skill_lv*(dex/2+50)*(100+int_)/100; + break; + + case HT_CLAYMORETRAP: // クレイモアートラップ + damage=skill_lv*(dex/2+75)*(100+int_)/100; + break; + + case HT_BLITZBEAT: // ブリッツビート + if( sd==NULL || (skill = pc_checkskill(sd,HT_STEELCROW)) <= 0) + skill=0; + damage=(dex/10+int_/2+skill*3+40)*2; + if(flag > 1) + damage /= flag; + break; + + case TF_THROWSTONE: // 石投げ + damage=30; + damagefix=0; + break; + + case BA_DISSONANCE: // 不協和音 + damage=(skill_lv)*20+pc_checkskill(sd,BA_MUSICALLESSON)*3; + break; + + case NPC_SELFDESTRUCTION: // 自爆 + damage=battle_get_hp(bl)-(bl==target?1:0); + damagefix=0; + break; + + case NPC_SMOKING: // タバコを吸う + damage=3; + damagefix=0; + break; + + case NPC_DARKBREATH: + { + struct status_change *sc_data = battle_get_sc_data(target); + int hitrate=battle_get_hit(bl) - battle_get_flee(target) + 80; + hitrate = ( (hitrate>95)?95: ((hitrate<5)?5:hitrate) ); + if(sc_data && (sc_data[SC_SLEEP].timer!=-1 || sc_data[SC_STAN].timer!=-1 || + sc_data[SC_FREEZE].timer!=-1 || (sc_data[SC_STONE].timer!=-1 && sc_data[SC_STONE].val2==0) ) ) + hitrate = 1000000; + if(rand()%100 < hitrate) { + damage = 500 + (skill_lv-1)*1000 + rand()%1000; + if(damage > 9999) damage = 9999; + } + } + break; + case SN_FALCONASSAULT: /* ファルコンアサルト */ + skill = pc_checkskill(sd,HT_BLITZBEAT); + damage=(100+50*skill_lv+(dex/10+int_/2+skill*3+40)*2); + break; + } + + ele = skill_get_pl(skill_num); + race = battle_get_race(bl); + + if(damagefix){ + if(damage<1 && skill_num != NPC_DARKBREATH) + damage=1; + + if( tsd ){ + cardfix=100; + cardfix=cardfix*(100-tsd->subele[ele])/100; // 属性によるダメージ耐性 + cardfix=cardfix*(100-tsd->subrace[race])/100; // 種族によるダメージ耐性 + cardfix=cardfix*(100-tsd->misc_def_rate)/100; + damage=damage*cardfix/100; + } + if(damage < 0) damage = 0; + damage=battle_attr_fix(damage, ele, battle_get_element(target) ); // 属性修正 + } + + div_=skill_get_num( skill_num,skill_lv ); + if(div_>1) + damage*=div_; + + if(damage > 0 && (damage < div_ || (battle_get_def(target) >= 1000000 && battle_get_mdef(target) >= 1000000) ) ) { + damage = div_; + } + + damage=battle_calc_damage(bl,target,damage,div_,skill_num,skill_lv,aflag); // 最終修正 + + md.damage=damage; + md.div_=div_; + md.amotion=battle_get_amotion(bl); + md.dmotion=battle_get_dmotion(target); + md.damage2=0; + md.type=0; + md.blewcount=blewcount; + md.flag=aflag; + return md; + +} +/*========================================== + * ダメージ計算一括処理用 + *------------------------------------------ + */ +struct Damage battle_calc_attack( int attack_type, + struct block_list *bl,struct block_list *target,int skill_num,int skill_lv,int flag) +{ + struct Damage d; + switch(attack_type){ + case BF_WEAPON: + return battle_calc_weapon_attack(bl,target,skill_num,skill_lv,flag); + case BF_MAGIC: + return battle_calc_magic_attack(bl,target,skill_num,skill_lv,flag); + case BF_MISC: + return battle_calc_misc_attack(bl,target,skill_num,skill_lv,flag); + default: + if(battle_config.error_log) + printf("battle_calc_attack: unknwon attack type ! %d\n",attack_type); + break; + } + return d; +} +/*========================================== + * 通常攻撃処理まとめ + *------------------------------------------ + */ +int battle_weapon_attack( struct block_list *src,struct block_list *target, + unsigned int tick,int flag) +{ + struct map_session_data *sd=NULL; + struct status_change *sc_data = battle_get_sc_data(src),*t_sc_data=battle_get_sc_data(target); + short *opt1; + int race = 7, ele = 0; + int damage,rdamage = 0; + struct Damage wd; + + nullpo_retr(0, src); + nullpo_retr(0, target); + + if(src->type == BL_PC) + sd = (struct map_session_data *)src; + + if(src->prev == NULL || target->prev == NULL) + return 0; + if(src->type == BL_PC && pc_isdead(sd)) + return 0; + if(target->type == BL_PC && pc_isdead((struct map_session_data *)target)) + return 0; + + opt1=battle_get_opt1(src); + if(opt1 && *opt1 > 0) { + battle_stopattack(src); + return 0; + } + if(sc_data && sc_data[SC_BLADESTOP].timer!=-1){ + battle_stopattack(src); + return 0; + } + + race = battle_get_race(target); + ele = battle_get_elem_type(target); + if(battle_check_target(src,target,BCT_ENEMY) > 0 && + battle_check_range(src,target,0)){ + // 攻撃対象となりうるので攻撃 + if(sd && sd->status.weapon == 11) { + if(sd->equip_index[10] >= 0) { + if(battle_config.arrow_decrement) + pc_delitem(sd,sd->equip_index[10],1,0); + } + else { + clif_arrow_fail(sd,0); + return 0; + } + } + if(flag&0x8000) { + if(sd && battle_config.pc_attack_direction_change) + sd->dir = sd->head_dir = map_calc_dir(src, target->x,target->y ); + else if(src->type == BL_MOB && battle_config.monster_attack_direction_change) + ((struct mob_data *)src)->dir = map_calc_dir(src, target->x,target->y ); + wd=battle_calc_weapon_attack(src,target,KN_AUTOCOUNTER,flag&0xff,0); + } + else + wd=battle_calc_weapon_attack(src,target,0,0,0); + if((damage = wd.damage + wd.damage2) > 0 && src != target) { + if(wd.flag&BF_SHORT) { + if(target->type == BL_PC) { + struct map_session_data *tsd = (struct map_session_data *)target; + if(tsd && tsd->short_weapon_damage_return > 0) { + rdamage += damage * tsd->short_weapon_damage_return / 100; + if(rdamage < 1) rdamage = 1; + } + } + if(t_sc_data && t_sc_data[SC_REFLECTSHIELD].timer != -1) { + rdamage += damage * t_sc_data[SC_REFLECTSHIELD].val2 / 100; + if(rdamage < 1) rdamage = 1; + } + } + else if(wd.flag&BF_LONG) { + if(target->type == BL_PC) { + struct map_session_data *tsd = (struct map_session_data *)target; + if(tsd && tsd->long_weapon_damage_return > 0) { + rdamage += damage * tsd->long_weapon_damage_return / 100; + if(rdamage < 1) rdamage = 1; + } + } + } + if(rdamage > 0) + clif_damage(src,src,tick, wd.amotion,0,rdamage,1,4,0); + } + + if (wd.div_ == 255 && sd) { //三段掌 + int delay = 1000 - 4 * battle_get_agi(src) - 2 * battle_get_dex(src); + int skilllv; + if(wd.damage+wd.damage2 < battle_get_hp(target)) { + if((skilllv = pc_checkskill(sd, MO_CHAINCOMBO)) > 0) + delay += 300 * battle_config.combo_delay_rate /100; //追加ディレイをconfにより調整 + + skill_status_change_start(src,SC_COMBO,MO_TRIPLEATTACK,skilllv,0,0,delay,0); + } + sd->attackabletime = sd->canmove_tick = tick + delay; + clif_combo_delay(src,delay); + clif_skill_damage(src , target , tick , wd.amotion , wd.dmotion , + wd.damage , 3 , MO_TRIPLEATTACK, pc_checkskill(sd,MO_TRIPLEATTACK) , -1 ); + } + else { + clif_damage(src,target,tick, wd.amotion, wd.dmotion, + wd.damage, wd.div_ , wd.type, wd.damage2); + //二刀流左手とカタール追撃のミス表示(無理やり〜) + if(sd && sd->status.weapon >= 16 && wd.damage2 == 0) + clif_damage(src,target,tick+10, wd.amotion, wd.dmotion,0, 1, 0, 0); + } + if(sd && sd->splash_range > 0 && (wd.damage > 0 || wd.damage2 > 0) ) + skill_castend_damage_id(src,target,0,-1,tick,0); + map_freeblock_lock(); + battle_damage(src,target,(wd.damage+wd.damage2),0); + if(target->prev != NULL && + (target->type != BL_PC || (target->type == BL_PC && !pc_isdead((struct map_session_data *)target) ) ) ) { + if(wd.damage > 0 || wd.damage2 > 0) { + skill_additional_effect(src,target,0,0,BF_WEAPON,tick); + if(sd) { + if(sd->weapon_coma_ele[ele] > 0 && rand()%10000 < sd->weapon_coma_ele[ele]) + battle_damage(src,target,battle_get_max_hp(target),1); + if(sd->weapon_coma_race[race] > 0 && rand()%10000 < sd->weapon_coma_race[race]) + battle_damage(src,target,battle_get_max_hp(target),1); + if(battle_get_mode(target) & 0x20) { + if(sd->weapon_coma_race[10] > 0 && rand()%10000 < sd->weapon_coma_race[10]) + battle_damage(src,target,battle_get_max_hp(target),1); + } + else { + if(sd->weapon_coma_race[11] > 0 && rand()%10000 < sd->weapon_coma_race[11]) + battle_damage(src,target,battle_get_max_hp(target),1); + } + } + } + } + if(sc_data && sc_data[SC_AUTOSPELL].timer != -1 && rand()%100 < sc_data[SC_AUTOSPELL].val4) { + int skilllv=sc_data[SC_AUTOSPELL].val3,i,f=0; + i = rand()%100; + if(i >= 50) skilllv -= 2; + else if(i >= 15) skilllv--; + if(skilllv < 1) skilllv = 1; + if(sd) { + int sp = skill_get_sp(sc_data[SC_AUTOSPELL].val2,skilllv)*2/3; + if(sd->status.sp >= sp) { + if((i=skill_get_inf(sc_data[SC_AUTOSPELL].val2) == 2) || i == 32) + f = skill_castend_pos2(src,target->x,target->y,sc_data[SC_AUTOSPELL].val2,skilllv,tick,flag); + else { + switch( skill_get_nk(sc_data[SC_AUTOSPELL].val2) ) { + case 0: case 2: + f = skill_castend_damage_id(src,target,sc_data[SC_AUTOSPELL].val2,skilllv,tick,flag); + break; + case 1:/* 支援系 */ + if((sc_data[SC_AUTOSPELL].val2==AL_HEAL || (sc_data[SC_AUTOSPELL].val2==ALL_RESURRECTION && target->type != BL_PC)) && battle_check_undead(race,ele)) + f = skill_castend_damage_id(src,target,sc_data[SC_AUTOSPELL].val2,skilllv,tick,flag); + else + f = skill_castend_nodamage_id(src,target,sc_data[SC_AUTOSPELL].val2,skilllv,tick,flag); + break; + } + } + if(!f) pc_heal(sd,0,-sp); + } + } + else { + if((i=skill_get_inf(sc_data[SC_AUTOSPELL].val2) == 2) || i == 32) + skill_castend_pos2(src,target->x,target->y,sc_data[SC_AUTOSPELL].val2,skilllv,tick,flag); + else { + switch( skill_get_nk(sc_data[SC_AUTOSPELL].val2) ) { + case 0: case 2: + skill_castend_damage_id(src,target,sc_data[SC_AUTOSPELL].val2,skilllv,tick,flag); + break; + case 1:/* 支援系 */ + if((sc_data[SC_AUTOSPELL].val2==AL_HEAL || (sc_data[SC_AUTOSPELL].val2==ALL_RESURRECTION && target->type != BL_PC)) && battle_check_undead(race,ele)) + skill_castend_damage_id(src,target,sc_data[SC_AUTOSPELL].val2,skilllv,tick,flag); + else + skill_castend_nodamage_id(src,target,sc_data[SC_AUTOSPELL].val2,skilllv,tick,flag); + break; + } + } + } + } + if(sd) { + if(sd->autospell_id > 0 && sd->autospell_lv > 0 && rand()%100 < sd->autospell_rate) { + int skilllv=sd->autospell_lv,i,f=0,sp; + i = rand()%100; + if(i >= 50) skilllv -= 2; + else if(i >= 15) skilllv--; + if(skilllv < 1) skilllv = 1; + sp = skill_get_sp(sd->autospell_id,skilllv)*2/3; + if(sd->status.sp >= sp) { + if((i=skill_get_inf(sd->autospell_id) == 2) || i == 32) + f = skill_castend_pos2(src,target->x,target->y,sd->autospell_id,skilllv,tick,flag); + else { + switch( skill_get_nk(sd->autospell_id) ) { + case 0: case 2: + f = skill_castend_damage_id(src,target,sd->autospell_id,skilllv,tick,flag); + break; + case 1:/* 支援系 */ + if((sd->autospell_id==AL_HEAL || (sd->autospell_id==ALL_RESURRECTION && target->type != BL_PC)) && battle_check_undead(race,ele)) + f = skill_castend_damage_id(src,target,sd->autospell_id,skilllv,tick,flag); + else + f = skill_castend_nodamage_id(src,target,sd->autospell_id,skilllv,tick,flag); + break; + } + } + if(!f) pc_heal(sd,0,-sp); + } + } + if(wd.flag&BF_WEAPON && src != target && (wd.damage > 0 || wd.damage2 > 0)) { + int hp = 0,sp = 0; + if(sd->hp_drain_rate && sd->hp_drain_per > 0 && wd.damage > 0 && rand()%100 < sd->hp_drain_rate) { + hp += (wd.damage * sd->hp_drain_per)/100; + if(sd->hp_drain_rate > 0 && hp < 1) hp = 1; + else if(sd->hp_drain_rate < 0 && hp > -1) hp = -1; + } + if(sd->hp_drain_rate_ && sd->hp_drain_per_ > 0 && wd.damage2 > 0 && rand()%100 < sd->hp_drain_rate_) { + hp += (wd.damage2 * sd->hp_drain_per_)/100; + if(sd->hp_drain_rate_ > 0 && hp < 1) hp = 1; + else if(sd->hp_drain_rate_ < 0 && hp > -1) hp = -1; + } + if(sd->sp_drain_rate && sd->sp_drain_per > 0 && wd.damage > 0 && rand()%100 < sd->sp_drain_rate) { + sp += (wd.damage * sd->sp_drain_per)/100; + if(sd->sp_drain_rate > 0 && sp < 1) sp = 1; + else if(sd->sp_drain_rate < 0 && sp > -1) sp = -1; + } + if(sd->sp_drain_rate_ && sd->sp_drain_per_ > 0 && wd.damage2 > 0 && rand()%100 < sd->sp_drain_rate_) { + sp += (wd.damage2 * sd->sp_drain_per_)/100; + if(sd->sp_drain_rate_ > 0 && sp < 1) sp = 1; + else if(sd->sp_drain_rate_ < 0 && sp > -1) sp = -1; + } + if(hp || sp) pc_heal(sd,hp,sp); + } + } + + if(rdamage > 0) + battle_damage(target,src,rdamage,0); + if(t_sc_data && t_sc_data[SC_AUTOCOUNTER].timer != -1 && t_sc_data[SC_AUTOCOUNTER].val4 > 0) { + if(t_sc_data[SC_AUTOCOUNTER].val3 == src->id) + battle_weapon_attack(target,src,tick,0x8000|t_sc_data[SC_AUTOCOUNTER].val1); + skill_status_change_end(target,SC_AUTOCOUNTER,-1); + } + if(t_sc_data && t_sc_data[SC_BLADESTOP_WAIT].timer != -1){ + int lv = t_sc_data[SC_BLADESTOP_WAIT].val1; + skill_status_change_end(target,SC_BLADESTOP_WAIT,-1); + skill_status_change_start(src,SC_BLADESTOP,lv,1,(int)src,(int)target,skill_get_time2(MO_BLADESTOP,lv),0); + skill_status_change_start(target,SC_BLADESTOP,lv,2,(int)target,(int)src,skill_get_time2(MO_BLADESTOP,lv),0); + } + if(t_sc_data && t_sc_data[SC_SPLASHER].timer!=-1) //殴ったので対象のベナムスプラッシャー状態を解除 + skill_status_change_end(target,SC_SPLASHER,-1); + + map_freeblock_unlock(); + } + return wd.dmg_lv; +} + +int battle_check_undead(int race,int element) +{ + if(battle_config.undead_detect_type == 0) { + if(element == 9) + return 1; + } + else if(battle_config.undead_detect_type == 1) { + if(race == 1) + return 1; + } + else { + if(element == 9 || race == 1) + return 1; + } + return 0; +} + +/*========================================== + * 敵味方判定(1=肯定,0=否定,-1=エラー) + * flag&0xf0000 = 0x00000:敵じゃないか判定(ret:1=敵ではない) + * = 0x10000:パーティー判定(ret:1=パーティーメンバ) + * = 0x20000:全て(ret:1=敵味方両方) + * = 0x40000:敵か判定(ret:1=敵) + * = 0x50000:パーティーじゃないか判定(ret:1=パーティでない) + *------------------------------------------ + */ +int battle_check_target( struct block_list *src, struct block_list *target,int flag) +{ + int s_p,s_g,t_p,t_g; + struct block_list *ss=src; + + nullpo_retr(0, src); + nullpo_retr(0, target); + + if( flag&0x40000 ){ // 反転フラグ + int ret=battle_check_target(src,target,flag&0x30000); + if(ret!=-1) + return !ret; + return -1; + } + + if( flag&0x20000 ){ + if( target->type==BL_MOB || target->type==BL_PC ) + return 1; + else + return -1; + } + + if(src->type == BL_SKILL && target->type == BL_SKILL) // 対象がスキルユニットなら無条件肯定 + return -1; + + if(target->type == BL_PC && ((struct map_session_data *)target)->invincible_timer != -1) + return -1; + + if(target->type == BL_SKILL) { + switch(((struct skill_unit *)target)->group->unit_id){ + case 0x8d: + case 0x8f: + case 0x98: + return 0; + break; + } + } + + if(target->type == BL_PET) + return -1; + + // スキルユニットの場合、親を求める + if( src->type==BL_SKILL) { + int inf2 = skill_get_inf2(((struct skill_unit *)src)->group->skill_id); + if( (ss=map_id2bl( ((struct skill_unit *)src)->group->src_id))==NULL ) + return -1; + if(ss->prev == NULL) + return -1; + if(inf2&0x80 && + (map[src->m].flag.pvp || pc_iskiller((struct map_session_data *)src, (struct map_session_data *)target)) && // [MouseJstr] + !(target->type == BL_PC && pc_isinvisible((struct map_session_data *)target))) + return 0; + if(ss == target) { + if(inf2&0x100) + return 0; + if(inf2&0x200) + return -1; + } + } + // Mobでmaster_idがあってspecial_mob_aiなら、召喚主を求める + if( src->type==BL_MOB ){ + struct mob_data *md=(struct mob_data *)src; + if(md && md->master_id>0){ + if(md->master_id==target->id) // 主なら肯定 + return 1; + if(md->state.special_mob_ai){ + if(target->type==BL_MOB){ //special_mob_aiで対象がMob + struct mob_data *tmd=(struct mob_data *)target; + if(tmd){ + if(tmd->master_id != md->master_id) //召喚主が一緒でなければ否定 + return 0; + else{ //召喚主が一緒なので肯定したいけど自爆は否定 + if(md->state.special_mob_ai>2) + return 0; + else + return 1; + } + } + } + } + if((ss=map_id2bl(md->master_id))==NULL) + return -1; + } + } + + if( src==target || ss==target ) // 同じなら肯定 + return 1; + + if(target->type == BL_PC && pc_isinvisible((struct map_session_data *)target)) + return -1; + + if( src->prev==NULL || // 死んでるならエラー + (src->type==BL_PC && pc_isdead((struct map_session_data *)src) ) ) + return -1; + + if( (ss->type == BL_PC && target->type==BL_MOB) || + (ss->type == BL_MOB && target->type==BL_PC) ) + return 0; // PCvsMOBなら否定 + + if(ss->type == BL_PET && target->type==BL_MOB) + return 0; + + s_p=battle_get_party_id(ss); + s_g=battle_get_guild_id(ss); + + t_p=battle_get_party_id(target); + t_g=battle_get_guild_id(target); + + if(flag&0x10000) { + if(s_p && t_p && s_p == t_p) // 同じパーティなら肯定(味方) + return 1; + else // パーティ検索なら同じパーティじゃない時点で否定 + return 0; + } + + if(ss->type == BL_MOB && s_g > 0 && t_g > 0 && s_g == t_g ) // 同じギルド/mobクラスなら肯定(味方) + return 1; + +//printf("ss:%d src:%d target:%d flag:0x%x %d %d ",ss->id,src->id,target->id,flag,src->type,target->type); +//printf("p:%d %d g:%d %d\n",s_p,t_p,s_g,t_g); + + if( ss->type==BL_PC && target->type==BL_PC) { // 両方PVPモードなら否定(敵) + struct skill_unit *su=NULL; + if(src->type==BL_SKILL) + su=(struct skill_unit *)src; + if(map[ss->m].flag.pvp || pc_iskiller((struct map_session_data *)ss, (struct map_session_data*)target)) { // [MouseJstr] + if(su && su->group->target_flag==BCT_NOENEMY) + return 1; + else if(battle_config.pk_mode && (((struct map_session_data*)ss)->status.class==0 || ((struct map_session_data*)target)->status.class==0)) + return 1; // prevent novice engagement in pk_mode [Valaris] + else if(map[ss->m].flag.pvp_noparty && s_p > 0 && t_p > 0 && s_p == t_p) + return 1; + else if(map[ss->m].flag.pvp_noguild && s_g > 0 && t_g > 0 && s_g == t_g) + return 1; + return 0; + } + if(map[src->m].flag.gvg) { + struct guild *g=NULL; + if(su && su->group->target_flag==BCT_NOENEMY) + return 1; + if( s_g > 0 && s_g == t_g) + return 1; + if(map[src->m].flag.gvg_noparty && s_p > 0 && t_p > 0 && s_p == t_p) + return 1; + if((g = guild_search(s_g))) { + int i; + for(i=0;i<MAX_GUILDALLIANCE;i++){ + if(g->alliance[i].guild_id > 0 && g->alliance[i].guild_id == t_g) { + if(g->alliance[i].opposition) + return 0;//敵対ギルドなら無条件に敵 + else + return 1;//同盟ギルドなら無条件に味方 + } + } + } + return 0; + } + } + + return 1; // 該当しないので無関係人物(まあ敵じゃないので味方) +} +/*========================================== + * 射程判定 + *------------------------------------------ + */ +int battle_check_range(struct block_list *src,struct block_list *bl,int range) +{ + + int dx,dy; + struct walkpath_data wpd; + int arange; + + nullpo_retr(0, src); + nullpo_retr(0, bl); + + dx=abs(bl->x-src->x); + dy=abs(bl->y-src->y); + arange=((dx>dy)?dx:dy); + + if(src->m != bl->m) // 違うマップ + return 0; + + if( range>0 && range < arange ) // 遠すぎる + return 0; + + if( arange<2 ) // 同じマスか隣接 + return 1; + +// if(bl->type == BL_SKILL && ((struct skill_unit *)bl)->group->unit_id == 0x8d) +// return 1; + + // 障害物判定 + wpd.path_len=0; + wpd.path_pos=0; + wpd.path_half=0; + if(path_search(&wpd,src->m,src->x,src->y,bl->x,bl->y,0x10001)!=-1) + return 1; + + dx=(dx>0)?1:((dx<0)?-1:0); + dy=(dy>0)?1:((dy<0)?-1:0); + return (path_search(&wpd,src->m,src->x+dx,src->y+dy, + bl->x-dx,bl->y-dy,0x10001)!=-1)?1:0; +} + +/*========================================== + * Return numerical value of a switch configuration (modified by [Yor]) + * on/off, english, fran軋is, deutsch, espaol + *------------------------------------------ + */ +int battle_config_switch(const char *str) { + if (strcmpi(str, "on") == 0 || strcmpi(str, "yes") == 0 || strcmpi(str, "oui") == 0 || strcmpi(str, "ja") == 0 || strcmpi(str, "si") == 0) + return 1; + if (strcmpi(str, "off") == 0 || strcmpi(str, "no") == 0 || strcmpi(str, "non") == 0 || strcmpi(str, "nein") == 0) + return 0; + return atoi(str); +} +/*========================================== + * 設定ファイルを読み込む + *------------------------------------------ + */ +int battle_config_read(const char *cfgName) +{ + int i; + char line[1024], w1[1024], w2[1024]; + FILE *fp; + static int count = 0; + + if ((count++) == 0) { + battle_config.warp_point_debug=0; + battle_config.enemy_critical=0; + battle_config.enemy_critical_rate=100; + battle_config.enemy_str=1; + battle_config.enemy_perfect_flee=0; + battle_config.cast_rate=100; + battle_config.delay_rate=100; + battle_config.delay_dependon_dex=0; + battle_config.sdelay_attack_enable=0; + battle_config.left_cardfix_to_right=0; + battle_config.pc_skill_add_range=0; + battle_config.skill_out_range_consume=1; + battle_config.mob_skill_add_range=0; + battle_config.pc_damage_delay=1; + battle_config.pc_damage_delay_rate=100; + battle_config.defnotenemy=1; + battle_config.random_monster_checklv=1; + battle_config.attr_recover=1; + battle_config.flooritem_lifetime=LIFETIME_FLOORITEM*1000; + battle_config.item_auto_get=0; + battle_config.item_first_get_time=3000; + battle_config.item_second_get_time=1000; + battle_config.item_third_get_time=1000; + battle_config.mvp_item_first_get_time=10000; + battle_config.mvp_item_second_get_time=10000; + battle_config.mvp_item_third_get_time=2000; + + battle_config.drop_rate0item=0; + battle_config.base_exp_rate=100; + battle_config.job_exp_rate=100; + battle_config.pvp_exp=1; + battle_config.gtb_pvp_only=0; + battle_config.death_penalty_type=0; + battle_config.death_penalty_base=0; + battle_config.death_penalty_job=0; + battle_config.zeny_penalty=0; + battle_config.restart_hp_rate=0; + battle_config.restart_sp_rate=0; + battle_config.mvp_item_rate=100; + battle_config.mvp_exp_rate=100; + battle_config.mvp_hp_rate=100; + battle_config.monster_hp_rate=100; + battle_config.monster_max_aspd=199; + battle_config.atc_gmonly=0; + battle_config.gm_allskill=0; + battle_config.gm_allequip=0; + battle_config.gm_skilluncond=0; + battle_config.guild_max_castles=0; + battle_config.skillfree = 0; + battle_config.skillup_limit = 0; + battle_config.wp_rate=100; + battle_config.pp_rate=100; + battle_config.monster_active_enable=1; + battle_config.monster_damage_delay_rate=100; + battle_config.monster_loot_type=0; + battle_config.mob_skill_use=1; + battle_config.mob_count_rate=100; + battle_config.quest_skill_learn=0; + battle_config.quest_skill_reset=1; + battle_config.basic_skill_check=1; + battle_config.guild_emperium_check=1; + battle_config.guild_exp_limit=50; + battle_config.pc_invincible_time = 5000; + battle_config.pet_catch_rate=100; + battle_config.pet_rename=0; + battle_config.pet_friendly_rate=100; + battle_config.pet_hungry_delay_rate=100; + battle_config.pet_hungry_friendly_decrease=5; + battle_config.pet_str=1; + battle_config.pet_status_support=0; + battle_config.pet_attack_support=0; + battle_config.pet_damage_support=0; + battle_config.pet_support_rate=100; + battle_config.pet_attack_exp_to_master=0; + battle_config.pet_attack_exp_rate=100; + battle_config.skill_min_damage=0; + battle_config.finger_offensive_type=0; + battle_config.heal_exp=0; + battle_config.resurrection_exp=0; + battle_config.shop_exp=0; + battle_config.combo_delay_rate=100; + battle_config.item_check=1; + battle_config.wedding_modifydisplay=0; + battle_config.natural_healhp_interval=6000; + battle_config.natural_healsp_interval=8000; + battle_config.natural_heal_skill_interval=10000; + battle_config.natural_heal_weight_rate=50; + battle_config.item_name_override_grffile=1; + battle_config.arrow_decrement=1; + battle_config.max_aspd = 199; + battle_config.max_hp = 32500; + battle_config.max_sp = 32500; + battle_config.max_lv = 99; // [MouseJstr] + battle_config.max_parameter = 99; + battle_config.max_cart_weight = 8000; + battle_config.pc_skill_log = 0; + battle_config.mob_skill_log = 0; + battle_config.battle_log = 0; + battle_config.save_log = 0; + battle_config.error_log = 1; + battle_config.etc_log = 1; + battle_config.save_clothcolor = 0; + battle_config.undead_detect_type = 0; + battle_config.pc_auto_counter_type = 1; + battle_config.monster_auto_counter_type = 1; + battle_config.agi_penaly_type = 0; + battle_config.agi_penaly_count = 3; + battle_config.agi_penaly_num = 0; + battle_config.agi_penaly_count_lv = ATK_FLEE; + battle_config.vit_penaly_type = 0; + battle_config.vit_penaly_count = 3; + battle_config.vit_penaly_num = 0; + battle_config.vit_penaly_count_lv = ATK_DEF; + battle_config.player_defense_type = 0; + battle_config.monster_defense_type = 0; + battle_config.pet_defense_type = 0; + battle_config.magic_defense_type = 0; + battle_config.pc_skill_reiteration = 0; + battle_config.monster_skill_reiteration = 0; + battle_config.pc_skill_nofootset = 0; + battle_config.monster_skill_nofootset = 0; + battle_config.pc_cloak_check_type = 0; + battle_config.monster_cloak_check_type = 0; + battle_config.gvg_short_damage_rate = 100; + battle_config.gvg_long_damage_rate = 100; + battle_config.gvg_magic_damage_rate = 100; + battle_config.gvg_misc_damage_rate = 100; + battle_config.gvg_eliminate_time = 7000; + battle_config.mob_changetarget_byskill = 0; + battle_config.pc_attack_direction_change = 1; + battle_config.monster_attack_direction_change = 1; + battle_config.pc_undead_nofreeze = 0; + battle_config.pc_land_skill_limit = 1; + battle_config.monster_land_skill_limit = 1; + battle_config.party_skill_penaly = 1; + battle_config.monster_class_change_full_recover = 0; + battle_config.produce_item_name_input = 1; + battle_config.produce_potion_name_input = 1; + battle_config.making_arrow_name_input = 1; + battle_config.holywater_name_input = 1; + battle_config.display_delay_skill_fail = 1; + battle_config.chat_warpportal = 0; + battle_config.mob_warpportal = 0; + battle_config.dead_branch_active = 0; + battle_config.vending_max_value = 10000000; + battle_config.show_steal_in_same_party = 0; + battle_config.enable_upper_class = 0; + battle_config.pet_attack_attr_none = 0; + battle_config.pc_attack_attr_none = 0; + battle_config.mob_attack_attr_none = 1; + battle_config.mob_ghostring_fix = 0; + battle_config.gx_allhit = 0; + battle_config.gx_cardfix = 0; + battle_config.gx_dupele = 1; + battle_config.gx_disptype = 1; + battle_config.player_skill_partner_check = 1; + battle_config.hide_GM_session = 0; + battle_config.unit_movement_type = 0; + battle_config.invite_request_check = 1; + battle_config.skill_removetrap_type = 0; + battle_config.disp_experience = 0; + battle_config.item_rate_common = 100; + battle_config.item_rate_equip = 100; + battle_config.item_rate_card = 100; + battle_config.item_rate_heal = 100; // Added by Valaris + battle_config.item_rate_use = 100; // End + battle_config.item_drop_common_min=1; // Added by TyrNemesis^ + battle_config.item_drop_common_max=10000; + battle_config.item_drop_equip_min=1; + battle_config.item_drop_equip_max=10000; + battle_config.item_drop_card_min=1; + battle_config.item_drop_card_max=10000; + battle_config.item_drop_mvp_min=1; + battle_config.item_drop_mvp_max=10000; // End Addition + battle_config.item_drop_heal_min=1; // Added by Valaris + battle_config.item_drop_heal_max=10000; + battle_config.item_drop_use_min=1; + battle_config.item_drop_use_max=10000; // End + battle_config.prevent_logout = 1; // Added by RoVeRT + battle_config.maximum_level = 255; // Added by Valaris + battle_config.drops_by_luk = 0; // [Valaris] + battle_config.equipment_breaking = 0; // [Valaris] + battle_config.equipment_break_rate = 100; // [Valaris] + battle_config.pk_mode = 0; // [Valaris] + battle_config.pet_equip_required = 0; // [Valaris] + battle_config.multi_level_up = 0; // [Valaris] + battle_config.backstab_bow_penalty = 0; // Akaru + battle_config.night_at_start = 0; // added by [Yor] + battle_config.day_duration = 2*60*60*1000; // added by [Yor] (2 hours) + battle_config.night_duration = 30*60*1000; // added by [Yor] (30 minutes) + battle_config.show_mob_hp = 0; // [Valaris] + battle_config.ban_spoof_namer = 5; // added by [Yor] (default: 5 minutes) + battle_config.hack_info_GM_level = 60; // added by [Yor] (default: 60, GM level) + battle_config.any_warp_GM_min_level = 20; // added by [Yor] + battle_config.packet_ver_flag = 63; // added by [Yor] + battle_config.min_hair_style = 0; + battle_config.max_hair_style = 20; + battle_config.min_hair_color = 0; + battle_config.max_hair_color = 9; + battle_config.min_cloth_color = 0; + battle_config.max_cloth_color = 4; + + battle_config.castrate_dex_scale = 150; + + battle_config.area_size = 14; + +//SQL-only options start +#ifndef TXT_ONLY + battle_config.mail_system = 0; +//SQL-only options end +#endif + +} + + fp = fopen(cfgName,"r"); + if (fp == NULL) { + printf("file not found: %s\n", cfgName); + return 1; + } + while(fgets(line,1020,fp)){ + const struct { + char str[128]; + int *val; + } data[] = { + { "warp_point_debug", &battle_config.warp_point_debug }, + { "enemy_critical", &battle_config.enemy_critical }, + { "enemy_critical_rate", &battle_config.enemy_critical_rate }, + { "enemy_str", &battle_config.enemy_str }, + { "enemy_perfect_flee", &battle_config.enemy_perfect_flee }, + { "casting_rate", &battle_config.cast_rate }, + { "delay_rate", &battle_config.delay_rate }, + { "delay_dependon_dex", &battle_config.delay_dependon_dex }, + { "skill_delay_attack_enable", &battle_config.sdelay_attack_enable }, + { "left_cardfix_to_right", &battle_config.left_cardfix_to_right }, + { "player_skill_add_range", &battle_config.pc_skill_add_range }, + { "skill_out_range_consume", &battle_config.skill_out_range_consume }, + { "monster_skill_add_range", &battle_config.mob_skill_add_range }, + { "player_damage_delay", &battle_config.pc_damage_delay }, + { "player_damage_delay_rate", &battle_config.pc_damage_delay_rate }, + { "defunit_not_enemy", &battle_config.defnotenemy }, + { "random_monster_checklv", &battle_config.random_monster_checklv }, + { "attribute_recover", &battle_config.attr_recover }, + { "flooritem_lifetime", &battle_config.flooritem_lifetime }, + { "item_auto_get", &battle_config.item_auto_get }, + { "item_first_get_time", &battle_config.item_first_get_time }, + { "item_second_get_time", &battle_config.item_second_get_time }, + { "item_third_get_time", &battle_config.item_third_get_time }, + { "mvp_item_first_get_time", &battle_config.mvp_item_first_get_time }, + { "mvp_item_second_get_time", &battle_config.mvp_item_second_get_time }, + { "mvp_item_third_get_time", &battle_config.mvp_item_third_get_time }, + { "item_rate", &battle_config.item_rate }, + { "drop_rate0item", &battle_config.drop_rate0item }, + { "base_exp_rate", &battle_config.base_exp_rate }, + { "job_exp_rate", &battle_config.job_exp_rate }, + { "pvp_exp", &battle_config.pvp_exp }, + { "gtb_pvp_only", &battle_config.gtb_pvp_only }, + { "guild_max_castles", &battle_config.guild_max_castles }, + { "death_penalty_type", &battle_config.death_penalty_type }, + { "death_penalty_base", &battle_config.death_penalty_base }, + { "death_penalty_job", &battle_config.death_penalty_job }, + { "zeny_penalty", &battle_config.zeny_penalty }, + { "restart_hp_rate", &battle_config.restart_hp_rate }, + { "restart_sp_rate", &battle_config.restart_sp_rate }, + { "mvp_hp_rate", &battle_config.mvp_hp_rate }, + { "mvp_item_rate", &battle_config.mvp_item_rate }, + { "mvp_exp_rate", &battle_config.mvp_exp_rate }, + { "monster_hp_rate", &battle_config.monster_hp_rate }, + { "monster_max_aspd", &battle_config.monster_max_aspd }, + { "atcommand_gm_only", &battle_config.atc_gmonly }, + { "atcommand_spawn_quantity_limit", &battle_config.atc_spawn_quantity_limit }, + { "gm_all_skill", &battle_config.gm_allskill }, + { "gm_all_skill_add_abra", &battle_config.gm_allskill_addabra }, + { "gm_all_equipment", &battle_config.gm_allequip }, + { "gm_skill_unconditional", &battle_config.gm_skilluncond }, + { "player_skillfree", &battle_config.skillfree }, + { "player_skillup_limit", &battle_config.skillup_limit }, + { "weapon_produce_rate", &battle_config.wp_rate }, + { "potion_produce_rate", &battle_config.pp_rate }, + { "monster_active_enable", &battle_config.monster_active_enable }, + { "monster_damage_delay_rate", &battle_config.monster_damage_delay_rate}, + { "monster_loot_type", &battle_config.monster_loot_type }, + { "mob_skill_use", &battle_config.mob_skill_use }, + { "mob_count_rate", &battle_config.mob_count_rate }, + { "quest_skill_learn", &battle_config.quest_skill_learn }, + { "quest_skill_reset", &battle_config.quest_skill_reset }, + { "basic_skill_check", &battle_config.basic_skill_check }, + { "guild_emperium_check", &battle_config.guild_emperium_check }, + { "guild_exp_limit", &battle_config.guild_exp_limit }, + { "player_invincible_time", &battle_config.pc_invincible_time }, + { "pet_catch_rate", &battle_config.pet_catch_rate }, + { "pet_rename", &battle_config.pet_rename }, + { "pet_friendly_rate", &battle_config.pet_friendly_rate }, + { "pet_hungry_delay_rate", &battle_config.pet_hungry_delay_rate }, + { "pet_hungry_friendly_decrease", &battle_config.pet_hungry_friendly_decrease}, + { "pet_str", &battle_config.pet_str }, + { "pet_status_support", &battle_config.pet_status_support }, + { "pet_attack_support", &battle_config.pet_attack_support }, + { "pet_damage_support", &battle_config.pet_damage_support }, + { "pet_support_rate", &battle_config.pet_support_rate }, + { "pet_attack_exp_to_master", &battle_config.pet_attack_exp_to_master }, + { "pet_attack_exp_rate", &battle_config.pet_attack_exp_rate }, + { "skill_min_damage", &battle_config.skill_min_damage }, + { "finger_offensive_type", &battle_config.finger_offensive_type }, + { "heal_exp", &battle_config.heal_exp }, + { "resurrection_exp", &battle_config.resurrection_exp }, + { "shop_exp", &battle_config.shop_exp }, + { "combo_delay_rate", &battle_config.combo_delay_rate }, + { "item_check", &battle_config.item_check }, + { "wedding_modifydisplay", &battle_config.wedding_modifydisplay }, + { "natural_healhp_interval", &battle_config.natural_healhp_interval }, + { "natural_healsp_interval", &battle_config.natural_healsp_interval }, + { "natural_heal_skill_interval", &battle_config.natural_heal_skill_interval}, + { "natural_heal_weight_rate", &battle_config.natural_heal_weight_rate }, + { "item_name_override_grffile", &battle_config.item_name_override_grffile}, + { "arrow_decrement", &battle_config.arrow_decrement }, + { "max_aspd", &battle_config.max_aspd }, + { "max_hp", &battle_config.max_hp }, + { "max_sp", &battle_config.max_sp }, + { "max_lv", &battle_config.max_lv }, + { "max_parameter", &battle_config.max_parameter }, + { "max_cart_weight", &battle_config.max_cart_weight }, + { "player_skill_log", &battle_config.pc_skill_log }, + { "monster_skill_log", &battle_config.mob_skill_log }, + { "battle_log", &battle_config.battle_log }, + { "save_log", &battle_config.save_log }, + { "error_log", &battle_config.error_log }, + { "etc_log", &battle_config.etc_log }, + { "save_clothcolor", &battle_config.save_clothcolor }, + { "undead_detect_type", &battle_config.undead_detect_type }, + { "player_auto_counter_type", &battle_config.pc_auto_counter_type }, + { "monster_auto_counter_type", &battle_config.monster_auto_counter_type}, + { "agi_penaly_type", &battle_config.agi_penaly_type }, + { "agi_penaly_count", &battle_config.agi_penaly_count }, + { "agi_penaly_num", &battle_config.agi_penaly_num }, + { "agi_penaly_count_lv", &battle_config.agi_penaly_count_lv }, + { "vit_penaly_type", &battle_config.vit_penaly_type }, + { "vit_penaly_count", &battle_config.vit_penaly_count }, + { "vit_penaly_num", &battle_config.vit_penaly_num }, + { "vit_penaly_count_lv", &battle_config.vit_penaly_count_lv }, + { "player_defense_type", &battle_config.player_defense_type }, + { "monster_defense_type", &battle_config.monster_defense_type }, + { "pet_defense_type", &battle_config.pet_defense_type }, + { "magic_defense_type", &battle_config.magic_defense_type }, + { "player_skill_reiteration", &battle_config.pc_skill_reiteration }, + { "monster_skill_reiteration", &battle_config.monster_skill_reiteration}, + { "player_skill_nofootset", &battle_config.pc_skill_nofootset }, + { "monster_skill_nofootset", &battle_config.monster_skill_nofootset }, + { "player_cloak_check_type", &battle_config.pc_cloak_check_type }, + { "monster_cloak_check_type", &battle_config.monster_cloak_check_type }, + { "gvg_short_attack_damage_rate", &battle_config.gvg_short_damage_rate }, + { "gvg_long_attack_damage_rate", &battle_config.gvg_long_damage_rate }, + { "gvg_magic_attack_damage_rate", &battle_config.gvg_magic_damage_rate }, + { "gvg_misc_attack_damage_rate", &battle_config.gvg_misc_damage_rate }, + { "gvg_eliminate_time", &battle_config.gvg_eliminate_time }, + { "mob_changetarget_byskill", &battle_config.mob_changetarget_byskill}, + { "player_attack_direction_change", &battle_config.pc_attack_direction_change }, + { "monster_attack_direction_change", &battle_config.monster_attack_direction_change }, + { "player_land_skill_limit", &battle_config.pc_land_skill_limit }, + { "monster_land_skill_limit", &battle_config.monster_land_skill_limit}, + { "party_skill_penaly", &battle_config.party_skill_penaly }, + { "monster_class_change_full_recover", &battle_config.monster_class_change_full_recover }, + { "produce_item_name_input", &battle_config.produce_item_name_input }, + { "produce_potion_name_input", &battle_config.produce_potion_name_input}, + { "making_arrow_name_input", &battle_config.making_arrow_name_input }, + { "holywater_name_input", &battle_config.holywater_name_input }, + { "display_delay_skill_fail", &battle_config.display_delay_skill_fail }, + { "chat_warpportal", &battle_config.chat_warpportal }, + { "mob_warpportal", &battle_config.mob_warpportal }, + { "dead_branch_active", &battle_config.dead_branch_active }, + { "vending_max_value", &battle_config.vending_max_value }, + { "show_steal_in_same_party", &battle_config.show_steal_in_same_party }, + { "enable_upper_class", &battle_config.enable_upper_class }, + { "pet_attack_attr_none", &battle_config.pet_attack_attr_none }, + { "mob_attack_attr_none", &battle_config.mob_attack_attr_none }, + { "mob_ghostring_fix", &battle_config.mob_ghostring_fix }, + { "pc_attack_attr_none", &battle_config.pc_attack_attr_none }, + { "gx_allhit", &battle_config.gx_allhit }, + { "gx_cardfix", &battle_config.gx_cardfix }, + { "gx_dupele", &battle_config.gx_dupele }, + { "gx_disptype", &battle_config.gx_disptype }, + { "player_skill_partner_check", &battle_config.player_skill_partner_check}, + { "hide_GM_session", &battle_config.hide_GM_session }, + { "unit_movement_type", &battle_config.unit_movement_type }, + { "invite_request_check", &battle_config.invite_request_check }, + { "skill_removetrap_type", &battle_config.skill_removetrap_type }, + { "disp_experience", &battle_config.disp_experience }, + { "castle_defense_rate", &battle_config.castle_defense_rate }, + { "riding_weight", &battle_config.riding_weight }, + { "item_rate_common", &battle_config.item_rate_common }, // Added by RoVeRT + { "item_rate_equip", &battle_config.item_rate_equip }, + { "item_rate_card", &battle_config.item_rate_card }, // End Addition + { "item_rate_heal", &battle_config.item_rate_heal }, // Added by Valaris + { "item_rate_use", &battle_config.item_rate_use }, // End + { "item_drop_common_min", &battle_config.item_drop_common_min }, // Added by TyrNemesis^ + { "item_drop_common_max", &battle_config.item_drop_common_max }, + { "item_drop_equip_min", &battle_config.item_drop_equip_min }, + { "item_drop_equip_max", &battle_config.item_drop_equip_max }, + { "item_drop_card_min", &battle_config.item_drop_card_min }, + { "item_drop_card_max", &battle_config.item_drop_card_max }, + { "item_drop_mvp_min", &battle_config.item_drop_mvp_min }, + { "item_drop_mvp_max", &battle_config.item_drop_mvp_max }, // End Addition + { "prevent_logout", &battle_config.prevent_logout }, // Added by RoVeRT + { "alchemist_summon_reward", &battle_config.alchemist_summon_reward }, // [Valaris] + { "maximum_level", &battle_config.maximum_level }, // [Valaris] + { "drops_by_luk", &battle_config.drops_by_luk }, // [Valaris] + { "monsters_ignore_gm", &battle_config.monsters_ignore_gm }, // [Valaris] + { "equipment_breaking", &battle_config.equipment_breaking }, // [Valaris] + { "equipment_break_rate", &battle_config.equipment_break_rate }, // [Valaris] + { "pk_mode", &battle_config.pk_mode }, // [Valaris] + { "pet_equip_required", &battle_config.pet_equip_required }, // [Valaris] + { "multi_level_up", &battle_config.multi_level_up }, // [Valaris] + { "backstab_bow_penalty", &battle_config.backstab_bow_penalty }, + { "night_at_start", &battle_config.night_at_start }, // added by [Yor] + { "day_duration", &battle_config.day_duration }, // added by [Yor] + { "night_duration", &battle_config.night_duration }, // added by [Yor] + { "show_mob_hp", &battle_config.show_mob_hp }, // [Valaris] + { "ban_spoof_namer", &battle_config.ban_spoof_namer }, // added by [Yor] + { "hack_info_GM_level", &battle_config.hack_info_GM_level }, // added by [Yor] + { "any_warp_GM_min_level", &battle_config.any_warp_GM_min_level }, // added by [Yor] + { "packet_ver_flag", &battle_config.packet_ver_flag }, // added by [Yor] + { "min_hair_style", &battle_config.min_hair_style }, // added by [MouseJstr] + { "max_hair_style", &battle_config.max_hair_style }, // added by [MouseJstr] + { "min_hair_color", &battle_config.min_hair_color }, // added by [MouseJstr] + { "max_hair_color", &battle_config.max_hair_color }, // added by [MouseJstr] + { "min_cloth_color", &battle_config.min_cloth_color }, // added by [MouseJstr] + { "max_cloth_color", &battle_config.max_cloth_color }, // added by [MouseJstr] + { "castrate_dex_scale", &battle_config.castrate_dex_scale }, // added by [MouseJstr] + { "area_size", &battle_config.area_size }, // added by [MouseJstr] + { "muting_players", &battle_config.muting_players}, // added by [Apple] +//SQL-only options start +#ifndef TXT_ONLY + { "mail_system", &battle_config.mail_system }, // added by [Valaris] +//SQL-only options end +#endif + }; + + if (line[0] == '/' && line[1] == '/') + continue; + if (sscanf(line, "%[^:]:%s", w1, w2) != 2) + continue; + for(i = 0; i < sizeof(data) / (sizeof(data[0])); i++) + if (strcmpi(w1, data[i].str) == 0) + *data[i].val = battle_config_switch(w2); + + if (strcmpi(w1, "import") == 0) + battle_config_read(w2); + } + fclose(fp); + + if (--count == 0) { + if(battle_config.flooritem_lifetime < 1000) + battle_config.flooritem_lifetime = LIFETIME_FLOORITEM*1000; + if(battle_config.restart_hp_rate < 0) + battle_config.restart_hp_rate = 0; + else if(battle_config.restart_hp_rate > 100) + battle_config.restart_hp_rate = 100; + if(battle_config.restart_sp_rate < 0) + battle_config.restart_sp_rate = 0; + else if(battle_config.restart_sp_rate > 100) + battle_config.restart_sp_rate = 100; + if(battle_config.natural_healhp_interval < NATURAL_HEAL_INTERVAL) + battle_config.natural_healhp_interval=NATURAL_HEAL_INTERVAL; + if(battle_config.natural_healsp_interval < NATURAL_HEAL_INTERVAL) + battle_config.natural_healsp_interval=NATURAL_HEAL_INTERVAL; + if(battle_config.natural_heal_skill_interval < NATURAL_HEAL_INTERVAL) + battle_config.natural_heal_skill_interval=NATURAL_HEAL_INTERVAL; + if(battle_config.natural_heal_weight_rate < 50) + battle_config.natural_heal_weight_rate = 50; + if(battle_config.natural_heal_weight_rate > 101) + battle_config.natural_heal_weight_rate = 101; + battle_config.monster_max_aspd = 2000 - battle_config.monster_max_aspd*10; + if(battle_config.monster_max_aspd < 10) + battle_config.monster_max_aspd = 10; + if(battle_config.monster_max_aspd > 1000) + battle_config.monster_max_aspd = 1000; + battle_config.max_aspd = 2000 - battle_config.max_aspd*10; + if(battle_config.max_aspd < 10) + battle_config.max_aspd = 10; + if(battle_config.max_aspd > 1000) + battle_config.max_aspd = 1000; + if(battle_config.max_hp > 1000000) + battle_config.max_hp = 1000000; + if(battle_config.max_hp < 100) + battle_config.max_hp = 100; + if(battle_config.max_sp > 1000000) + battle_config.max_sp = 1000000; + if(battle_config.max_sp < 100) + battle_config.max_sp = 100; + if(battle_config.max_parameter < 10) + battle_config.max_parameter = 10; + if(battle_config.max_parameter > 10000) + battle_config.max_parameter = 10000; + if(battle_config.max_cart_weight > 1000000) + battle_config.max_cart_weight = 1000000; + if(battle_config.max_cart_weight < 100) + battle_config.max_cart_weight = 100; + battle_config.max_cart_weight *= 10; + + if(battle_config.agi_penaly_count < 2) + battle_config.agi_penaly_count = 2; + if(battle_config.vit_penaly_count < 2) + battle_config.vit_penaly_count = 2; + + if(battle_config.guild_exp_limit > 99) + battle_config.guild_exp_limit = 99; + if(battle_config.guild_exp_limit < 0) + battle_config.guild_exp_limit = 0; + + if(battle_config.castle_defense_rate < 0) + battle_config.castle_defense_rate = 0; + if(battle_config.castle_defense_rate > 100) + battle_config.castle_defense_rate = 100; + if(battle_config.item_drop_common_min < 1) // Added by TyrNemesis^ + battle_config.item_drop_common_min = 1; + if(battle_config.item_drop_common_max > 10000) + battle_config.item_drop_common_max = 10000; + if(battle_config.item_drop_equip_min < 1) + battle_config.item_drop_equip_min = 1; + if(battle_config.item_drop_equip_max > 10000) + battle_config.item_drop_equip_max = 10000; + if(battle_config.item_drop_card_min < 1) + battle_config.item_drop_card_min = 1; + if(battle_config.item_drop_card_max > 10000) + battle_config.item_drop_card_max = 10000; + if(battle_config.item_drop_mvp_min < 1) + battle_config.item_drop_mvp_min = 1; + if(battle_config.item_drop_mvp_max > 10000) + battle_config.item_drop_mvp_max = 10000; // End Addition + + if (battle_config.night_at_start < 0) // added by [Yor] + battle_config.night_at_start = 0; + else if (battle_config.night_at_start > 1) // added by [Yor] + battle_config.night_at_start = 1; + if (battle_config.day_duration < 0) // added by [Yor] + battle_config.day_duration = 0; + if (battle_config.night_duration < 0) // added by [Yor] + battle_config.night_duration = 0; + + if (battle_config.ban_spoof_namer < 0) // added by [Yor] + battle_config.ban_spoof_namer = 0; + else if (battle_config.ban_spoof_namer > 32767) + battle_config.ban_spoof_namer = 32767; + + if (battle_config.hack_info_GM_level < 0) // added by [Yor] + battle_config.hack_info_GM_level = 0; + else if (battle_config.hack_info_GM_level > 100) + battle_config.hack_info_GM_level = 100; + + if (battle_config.any_warp_GM_min_level < 0) // added by [Yor] + battle_config.any_warp_GM_min_level = 0; + else if (battle_config.any_warp_GM_min_level > 100) + battle_config.any_warp_GM_min_level = 100; + + // at least 1 client must be accepted + if ((battle_config.packet_ver_flag & 63) == 0) // added by [Yor] + battle_config.packet_ver_flag = 63; // accept all clients + + add_timer_func_list(battle_delay_damage_sub, "battle_delay_damage_sub"); + } + + return 0; +} diff --git a/misc/src/map/battle.h b/misc/src/map/battle.h new file mode 100644 index 0000000..8f09d22 --- /dev/null +++ b/misc/src/map/battle.h @@ -0,0 +1,342 @@ +// $Id: battle.h,v 1.6 2004/09/29 21:08:17 Akitasha Exp $ +#ifndef _BATTLE_H_ +#define _BATTLE_H_ + +// ダメージ +struct Damage { + int damage,damage2; + int type,div_; + int amotion,dmotion; + int blewcount; + int flag; + int dmg_lv; //囲まれ減算計算用 0:スキル攻撃 ATK_LUCKY,ATK_FLEE,ATK_DEF +}; + +// 属性表(読み込みはpc.c、battle_attr_fixで使用) +extern int attr_fix_table[4][10][10]; + +struct map_session_data; +struct mob_data; +struct block_list; + +// ダメージ計算 + +struct Damage battle_calc_attack( int attack_type, + struct block_list *bl,struct block_list *target,int skill_num,int skill_lv,int flag); +struct Damage battle_calc_weapon_attack( + struct block_list *bl,struct block_list *target,int skill_num,int skill_lv,int flag); +struct Damage battle_calc_magic_attack( + struct block_list *bl,struct block_list *target,int skill_num,int skill_lv,int flag); +struct Damage battle_calc_misc_attack( + struct block_list *bl,struct block_list *target,int skill_num,int skill_lv,int flag); + +// 属性修正計算 +int battle_attr_fix(int damage,int atk_elem,int def_elem); + +// ダメージ最終計算 +int battle_calc_damage(struct block_list *src,struct block_list *bl,int damage,int div_,int skill_num,int skill_lv,int flag); +enum { // 最終計算のフラグ + BF_WEAPON = 0x0001, + BF_MAGIC = 0x0002, + BF_MISC = 0x0004, + BF_SHORT = 0x0010, + BF_LONG = 0x0040, + BF_SKILL = 0x0100, + BF_NORMAL = 0x0200, + BF_WEAPONMASK=0x000f, + BF_RANGEMASK= 0x00f0, + BF_SKILLMASK= 0x0f00, +}; + +// 実際にHPを増減 +int battle_delay_damage(unsigned int tick,struct block_list *src,struct block_list *target,int damage,int flag); +int battle_damage(struct block_list *bl,struct block_list *target,int damage,int flag); +int battle_heal(struct block_list *bl,struct block_list *target,int hp,int sp,int flag); + +// 攻撃や移動を止める +int battle_stopattack(struct block_list *bl); +int battle_stopwalking(struct block_list *bl,int type); + +// 通常攻撃処理まとめ +int battle_weapon_attack( struct block_list *bl,struct block_list *target, + unsigned int tick,int flag); + +// 各種パラメータを得る +int battle_counttargeted(struct block_list *bl,struct block_list *src,int target_lv); +int battle_get_class(struct block_list *bl); +int battle_get_dir(struct block_list *bl); +int battle_get_lv(struct block_list *bl); +int battle_get_range(struct block_list *bl); +int battle_get_hp(struct block_list *bl); +int battle_get_max_hp(struct block_list *bl); +int battle_get_str(struct block_list *bl); +int battle_get_agi(struct block_list *bl); +int battle_get_vit(struct block_list *bl); +int battle_get_int(struct block_list *bl); +int battle_get_dex(struct block_list *bl); +int battle_get_luk(struct block_list *bl); +int battle_get_hit(struct block_list *bl); +int battle_get_flee(struct block_list *bl); +int battle_get_def(struct block_list *bl); +int battle_get_mdef(struct block_list *bl); +int battle_get_flee2(struct block_list *bl); +int battle_get_def2(struct block_list *bl); +int battle_get_mdef2(struct block_list *bl); +int battle_get_baseatk(struct block_list *bl); +int battle_get_atk(struct block_list *bl); +int battle_get_atk2(struct block_list *bl); +int battle_get_speed(struct block_list *bl); +int battle_get_adelay(struct block_list *bl); +int battle_get_amotion(struct block_list *bl); +int battle_get_dmotion(struct block_list *bl); +int battle_get_element(struct block_list *bl); +int battle_get_attack_element(struct block_list *bl); +int battle_get_attack_element2(struct block_list *bl); //左手武器属性取得 +#define battle_get_elem_type(bl) (battle_get_element(bl)%10) +#define battle_get_elem_level(bl) (battle_get_element(bl)/10/2) +int battle_get_party_id(struct block_list *bl); +int battle_get_guild_id(struct block_list *bl); +int battle_get_race(struct block_list *bl); +int battle_get_size(struct block_list *bl); +int battle_get_mode(struct block_list *bl); +int battle_get_mexp(struct block_list *bl); + +struct status_change *battle_get_sc_data(struct block_list *bl); +short *battle_get_sc_count(struct block_list *bl); +short *battle_get_opt1(struct block_list *bl); +short *battle_get_opt2(struct block_list *bl); +short *battle_get_opt3(struct block_list *bl); +short *battle_get_option(struct block_list *bl); + +enum { + BCT_NOENEMY =0x00000, + BCT_PARTY =0x10000, + BCT_ENEMY =0x40000, + BCT_NOPARTY =0x50000, + BCT_ALL =0x20000, + BCT_NOONE =0x60000, +}; + +int battle_check_undead(int race,int element); +int battle_check_target( struct block_list *src, struct block_list *target,int flag); +int battle_check_range(struct block_list *src,struct block_list *bl,int range); + + +// 設定 + +int battle_config_switch(const char *str); // [Valaris] + +extern struct Battle_Config { + int warp_point_debug; + int enemy_critical; + int enemy_critical_rate; + int enemy_str; + int enemy_perfect_flee; + int cast_rate,delay_rate,delay_dependon_dex; + int sdelay_attack_enable; + int left_cardfix_to_right; + int pc_skill_add_range; + int skill_out_range_consume; + int mob_skill_add_range; + int pc_damage_delay; + int pc_damage_delay_rate; + int defnotenemy; + int random_monster_checklv; + int attr_recover; + int flooritem_lifetime; + int item_auto_get; + int item_first_get_time; + int item_second_get_time; + int item_third_get_time; + int mvp_item_first_get_time; + int mvp_item_second_get_time; + int mvp_item_third_get_time; + int item_rate,base_exp_rate,job_exp_rate; // removed item rate, depreciated + int drop_rate0item; + int death_penalty_type; + int death_penalty_base,death_penalty_job; + int pvp_exp; // [MouseJstr] + int gtb_pvp_only; // [MouseJstr] + int zeny_penalty; + int restart_hp_rate; + int restart_sp_rate; + int mvp_item_rate,mvp_exp_rate; + int mvp_hp_rate; + int monster_hp_rate; + int monster_max_aspd; + int atc_gmonly; + int atc_spawn_quantity_limit; + int gm_allskill; + int gm_allskill_addabra; + int gm_allequip; + int gm_skilluncond; + int skillfree; + int skillup_limit; + int wp_rate; + int pp_rate; + int monster_active_enable; + int monster_damage_delay_rate; + int monster_loot_type; + int mob_skill_use; + int mob_count_rate; + int quest_skill_learn; + int quest_skill_reset; + int basic_skill_check; + int guild_emperium_check; + int guild_exp_limit; + int guild_max_castles; + int pc_invincible_time; + int pet_catch_rate; + int pet_rename; + int pet_friendly_rate; + int pet_hungry_delay_rate; + int pet_hungry_friendly_decrease; + int pet_str; + int pet_status_support; + int pet_attack_support; + int pet_damage_support; + int pet_support_rate; + int pet_attack_exp_to_master; + int pet_attack_exp_rate; + int skill_min_damage; + int finger_offensive_type; + int heal_exp; + int resurrection_exp; + int shop_exp; + int combo_delay_rate; + int item_check; + int wedding_modifydisplay; + int natural_healhp_interval; + int natural_healsp_interval; + int natural_heal_skill_interval; + int natural_heal_weight_rate; + int item_name_override_grffile; + int arrow_decrement; + int max_aspd; + int max_hp; + int max_sp; + int max_lv; + int max_parameter; + int max_cart_weight; + int pc_skill_log; + int mob_skill_log; + int battle_log; + int save_log; + int error_log; + int etc_log; + int save_clothcolor; + int undead_detect_type; + int pc_auto_counter_type; + int monster_auto_counter_type; + int agi_penaly_type; + int agi_penaly_count; + int agi_penaly_num; + int vit_penaly_type; + int vit_penaly_count; + int vit_penaly_num; + int player_defense_type; + int monster_defense_type; + int pet_defense_type; + int magic_defense_type; + int pc_skill_reiteration; + int monster_skill_reiteration; + int pc_skill_nofootset; + int monster_skill_nofootset; + int pc_cloak_check_type; + int monster_cloak_check_type; + int gvg_short_damage_rate; + int gvg_long_damage_rate; + int gvg_magic_damage_rate; + int gvg_misc_damage_rate; + int gvg_eliminate_time; + int mob_changetarget_byskill; + int pc_attack_direction_change; + int monster_attack_direction_change; + int pc_undead_nofreeze; + int pc_land_skill_limit; + int monster_land_skill_limit; + int party_skill_penaly; + int monster_class_change_full_recover; + int produce_item_name_input; + int produce_potion_name_input; + int making_arrow_name_input; + int holywater_name_input; + int display_delay_skill_fail; + int chat_warpportal; + int mob_warpportal; + int dead_branch_active; + int vending_max_value; +// int pet_lootitem; // removed [Valaris] +// int pet_weight; // removed [Valaris] + int show_steal_in_same_party; + int enable_upper_class; + int pet_attack_attr_none; + int mob_attack_attr_none; + int mob_ghostring_fix; + int pc_attack_attr_none; + int item_rate_common,item_rate_card,item_rate_equip,item_rate_heal,item_rate_use; // Added by RoVeRT, Additional Heal and Usable item rate by Val + int item_drop_common_min,item_drop_common_max; // Added by TyrNemesis^ + int item_drop_card_min,item_drop_card_max; + int item_drop_equip_min,item_drop_equip_max; + int item_drop_mvp_min,item_drop_mvp_max; // End Addition + int item_drop_heal_min,item_drop_heal_max; // Added by Valatris + int item_drop_use_min,item_drop_use_max; //End + + int prevent_logout; // Added by RoVeRT + + int alchemist_summon_reward; // [Valaris] + int maximum_level; + int drops_by_luk; + int monsters_ignore_gm; + int equipment_breaking; + int equipment_break_rate; + int pet_equip_required; + int multi_level_up; + int pk_mode; + int show_mob_hp; // end additions [Valaris] + + int agi_penaly_count_lv; + int vit_penaly_count_lv; + + int gx_allhit; + int gx_cardfix; + int gx_dupele; + int gx_disptype; + int player_skill_partner_check; + int hide_GM_session; + int unit_movement_type; + int invite_request_check; + int skill_removetrap_type; + int disp_experience; + int castle_defense_rate; + int riding_weight; + int backstab_bow_penalty; + + int night_at_start; // added by [Yor] + int day_duration; // added by [Yor] + int night_duration; // added by [Yor] + int ban_spoof_namer; // added by [Yor] + int hack_info_GM_level; // added by [Yor] + int any_warp_GM_min_level; // added by [Yor] + int packet_ver_flag; // added by [Yor] + int muting_players; // added by [Apple] + + int min_hair_style; // added by [MouseJstr] + int max_hair_style; // added by [MouseJstr] + int min_hair_color; // added by [MouseJstr] + int max_hair_color; // added by [MouseJstr] + int min_cloth_color; // added by [MouseJstr] + int max_cloth_color; // added by [MouseJstr] + + int castrate_dex_scale; // added by [MouseJstr] + int area_size; // added by [MouseJstr] + +#ifndef TXT_ONLY /* SQL-only options */ + int mail_system; // [Valaris] +#endif + +} battle_config; + +int battle_config_read(const char *cfgName); + +#endif diff --git a/misc/src/map/chat.c b/misc/src/map/chat.c new file mode 100644 index 0000000..ade4dcb --- /dev/null +++ b/misc/src/map/chat.c @@ -0,0 +1,373 @@ +// $Id: chat.c,v 1.2 2004/09/22 02:59:47 Akitasha Exp $ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "db.h" +#include "nullpo.h" +#include "malloc.h" +#include "map.h" +#include "clif.h" +#include "pc.h" +#include "chat.h" +#include "npc.h" + +#ifdef MEMWATCH +#include "memwatch.h" +#endif + +int chat_triggerevent(struct chat_data *cd); + + +/*========================================== + * チャットルーム作成 + *------------------------------------------ + */ +int chat_createchat(struct map_session_data *sd,int limit,int pub,char* pass,char* title,int titlelen) +{ + struct chat_data *cd; + + nullpo_retr(0, sd); + + cd = aCalloc(1,sizeof(struct chat_data)); + + cd->limit = limit; + cd->pub = pub; + cd->users = 1; + memcpy(cd->pass,pass,8); + if(titlelen>=sizeof(cd->title)-1) titlelen=sizeof(cd->title)-1; + memcpy(cd->title,title,titlelen); + cd->title[titlelen]=0; + + cd->owner = (struct block_list **)(&cd->usersd[0]); + cd->usersd[0] = sd; + cd->bl.m = sd->bl.m; + cd->bl.x = sd->bl.x; + cd->bl.y = sd->bl.y; + cd->bl.type = BL_CHAT; + + cd->bl.id = map_addobject(&cd->bl); + if(cd->bl.id==0){ + clif_createchat(sd,1); + free(cd); + return 0; + } + pc_setchatid(sd,cd->bl.id); + + clif_createchat(sd,0); + clif_dispchat(cd,0); + + return 0; +} + +/*========================================== + * 既存チャットルームに参加 + *------------------------------------------ + */ +int chat_joinchat(struct map_session_data *sd,int chatid,char* pass) +{ + struct chat_data *cd; + + nullpo_retr(0, sd); + + cd=(struct chat_data*)map_id2bl(chatid); + if(cd==NULL) + return 1; + + if(cd->bl.m != sd->bl.m || cd->limit <= cd->users){ + clif_joinchatfail(sd,0); + return 0; + } + if(cd->pub==0 && strncmp(pass,cd->pass,8)){ + clif_joinchatfail(sd,1); + return 0; + } + + cd->usersd[cd->users] = sd; + cd->users++; + + pc_setchatid(sd,cd->bl.id); + + + clif_joinchatok(sd,cd); // 新たに参加した人には全員のリスト + clif_addchat(cd,sd); // 既に中に居た人には追加した人の報告 + clif_dispchat(cd,0); // 周囲の人には人数変化報告 + + chat_triggerevent(cd); // イベント + + return 0; +} + +/*========================================== + * チャットルームから抜ける + *------------------------------------------ + */ +int chat_leavechat(struct map_session_data *sd) +{ + struct chat_data *cd; + int i,leavechar; + + nullpo_retr(1, sd); + + cd=(struct chat_data*)map_id2bl(sd->chatID); + if(cd==NULL) + return 1; + + for(i = 0,leavechar=-1;i < cd->users;i++){ + if(cd->usersd[i] == sd){ + leavechar=i; + break; + } + } + if(leavechar<0) // そのchatに所属していないらしい (バグ時のみ) + return -1; + + if(leavechar==0 && cd->users>1 && (*cd->owner)->type==BL_PC){ + // 所有者だった&他に人が居る&PCのチャット + clif_changechatowner(cd,cd->usersd[1]); + clif_clearchat(cd,0); + } + + // 抜けるPCにも送るのでusersを減らす前に実行 + clif_leavechat(cd,sd); + + cd->users--; + pc_setchatid(sd,0); + + if(cd->users == 0 && (*cd->owner)->type==BL_PC){ + // 全員居なくなった&PCのチャットなので消す + clif_clearchat(cd,0); + map_delobject(cd->bl.id); // freeまでしてくれる + } else { + for(i=leavechar;i < cd->users;i++) + cd->usersd[i] = cd->usersd[i+1]; + if(leavechar==0 && (*cd->owner)->type==BL_PC){ + // PCのチャットなので所有者が抜けたので位置変更 + cd->bl.x=cd->usersd[0]->bl.x; + cd->bl.y=cd->usersd[0]->bl.y; + } + clif_dispchat(cd,0); + } + + return 0; +} + +/*========================================== + * チャットルームの持ち主を譲る + *------------------------------------------ + */ +int chat_changechatowner(struct map_session_data *sd,char *nextownername) +{ + struct chat_data *cd; + struct map_session_data *tmp_sd; + int i,nextowner; + + nullpo_retr(1, sd); + + cd=(struct chat_data*)map_id2bl(sd->chatID); + if(cd==NULL || (struct block_list *)sd!=(*cd->owner)) + return 1; + + for(i = 1,nextowner=-1;i < cd->users;i++){ + if(strcmp(cd->usersd[i]->status.name,nextownername)==0){ + nextowner=i; + break; + } + } + if(nextowner<0) // そんな人は居ない + return -1; + + clif_changechatowner(cd,cd->usersd[nextowner]); + // 一旦消す + clif_clearchat(cd,0); + + // userlistの順番変更 (0が所有者なので) + if( (tmp_sd = cd->usersd[0]) == NULL ) + return 1; //ありえるのかな? + cd->usersd[0] = cd->usersd[nextowner]; + cd->usersd[nextowner] = tmp_sd; + + // 新しい所有者の位置へ変更 + cd->bl.x=cd->usersd[0]->bl.x; + cd->bl.y=cd->usersd[0]->bl.y; + + // 再度表示 + clif_dispchat(cd,0); + + return 0; +} + +/*========================================== + * チャットの状態(タイトル等)を変更 + *------------------------------------------ + */ +int chat_changechatstatus(struct map_session_data *sd,int limit,int pub,char* pass,char* title,int titlelen) +{ + struct chat_data *cd; + + nullpo_retr(1, sd); + + cd=(struct chat_data*)map_id2bl(sd->chatID); + if(cd==NULL || (struct block_list *)sd!=(*cd->owner)) + return 1; + + cd->limit = limit; + cd->pub = pub; + memcpy(cd->pass,pass,8); + if(titlelen>=sizeof(cd->title)-1) titlelen=sizeof(cd->title)-1; + memcpy(cd->title,title,titlelen); + cd->title[titlelen]=0; + + clif_changechatstatus(cd); + clif_dispchat(cd,0); + + return 0; +} + +/*========================================== + * チャットルームから蹴り出す + *------------------------------------------ + */ +int chat_kickchat(struct map_session_data *sd,char *kickusername) +{ + struct chat_data *cd; + int i,kickuser; + + nullpo_retr(1, sd); + + cd=(struct chat_data*)map_id2bl(sd->chatID); + if(cd==NULL || (struct block_list *)sd!=(*cd->owner)) + return 1; + + for(i = 0,kickuser=-1;i < cd->users;i++){ + if(strcmp(cd->usersd[i]->status.name,kickusername)==0){ + kickuser=i; + break; + } + } + if(kickuser<0) // そんな人は居ない + return -1; + + chat_leavechat(cd->usersd[kickuser]); + + return 0; +} + +/*========================================== + * npcチャットルーム作成 + *------------------------------------------ + */ +int chat_createnpcchat(struct npc_data *nd,int limit,int pub,int trigger,char* title,int titlelen,const char *ev) +{ + struct chat_data *cd; + + nullpo_retr(1, nd); + + cd = aCalloc(1,sizeof(struct chat_data)); + + cd->limit = cd->trigger = limit; + if(trigger>0) + cd->trigger = trigger; + cd->pub = pub; + cd->users = 0; + memcpy(cd->pass,"",8); + if(titlelen>=sizeof(cd->title)-1) titlelen=sizeof(cd->title)-1; + memcpy(cd->title,title,titlelen); + cd->title[titlelen]=0; + + cd->bl.m = nd->bl.m; + cd->bl.x = nd->bl.x; + cd->bl.y = nd->bl.y; + cd->bl.type = BL_CHAT; + cd->owner_ = (struct block_list *)nd; + cd->owner = &cd->owner_; + memcpy(cd->npc_event,ev,sizeof(cd->npc_event)); + + cd->bl.id = map_addobject(&cd->bl); + if(cd->bl.id==0){ + free(cd); + return 0; + } + nd->chat_id=cd->bl.id; + + clif_dispchat(cd,0); + + return 0; +} +/*========================================== + * npcチャットルーム削除 + *------------------------------------------ + */ +int chat_deletenpcchat(struct npc_data *nd) +{ + struct chat_data *cd; + + nullpo_retr(0, nd); + nullpo_retr(0, cd=(struct chat_data*)map_id2bl(nd->chat_id)); + + chat_npckickall(cd); + clif_clearchat(cd,0); + map_delobject(cd->bl.id); // freeまでしてくれる + nd->chat_id=0; + + return 0; +} + +/*========================================== + * 規定人数以上でイベントが定義されてるなら実行 + *------------------------------------------ + */ +int chat_triggerevent(struct chat_data *cd) +{ + nullpo_retr(0, cd); + + if(cd->users>=cd->trigger && cd->npc_event[0]) + npc_event_do(cd->npc_event); + return 0; +} + +/*========================================== + * イベントの有効化 + *------------------------------------------ + */ +int chat_enableevent(struct chat_data *cd) +{ + nullpo_retr(0, cd); + + cd->trigger&=0x7f; + chat_triggerevent(cd); + return 0; +} +/*========================================== + * イベントの無効化 + *------------------------------------------ + */ +int chat_disableevent(struct chat_data *cd) +{ + nullpo_retr(0, cd); + + cd->trigger|=0x80; + return 0; +} +/*========================================== + * チャットルームから全員蹴り出す + *------------------------------------------ + */ +int chat_npckickall(struct chat_data *cd) +{ + nullpo_retr(0, cd); + + while(cd->users>0){ + chat_leavechat(cd->usersd[cd->users-1]); + } + return 0; +} + +/*========================================== + * 終了 + *------------------------------------------ + */ +int do_final_chat(void) +{ + return 0; +} diff --git a/misc/src/map/chat.h b/misc/src/map/chat.h new file mode 100644 index 0000000..2761602 --- /dev/null +++ b/misc/src/map/chat.h @@ -0,0 +1,22 @@ +// $Id: chat.h,v 1.3 2004/09/25 05:32:18 MouseJstr Exp $ +#ifndef _CHAT_H_ +#define _CHAT_H_ + +#include "map.h" + +int chat_createchat(struct map_session_data *,int,int,char*,char*,int); +int chat_joinchat(struct map_session_data *,int,char*); +int chat_leavechat(struct map_session_data* ); +int chat_changechatowner(struct map_session_data *,char *); +int chat_changechatstatus(struct map_session_data *,int,int,char*,char*,int); +int chat_kickchat(struct map_session_data *,char *); + +int chat_createnpcchat(struct npc_data *nd,int limit,int pub,int trigger,char* title,int titlelen,const char *ev); +int chat_deletenpcchat(struct npc_data *nd); +int chat_enableevent(struct chat_data *cd); +int chat_disableevent(struct chat_data *cd); +int chat_npckickall(struct chat_data *cd); + +int do_final_chat(void); + +#endif diff --git a/misc/src/map/chrif.c b/misc/src/map/chrif.c new file mode 100644 index 0000000..dc401b6 --- /dev/null +++ b/misc/src/map/chrif.c @@ -0,0 +1,1016 @@ +// $Id: chrif.c,v 1.6 2004/09/25 11:39:17 MouseJstr Exp $ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#ifdef LCCWIN32 +#include <winsock.h> +#else +#include <unistd.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#endif +#include <sys/types.h> +#include <time.h> + +#include "socket.h" +#include "timer.h" +#include "map.h" +#include "battle.h" +#include "chrif.h" +#include "clif.h" +#include "intif.h" +#include "npc.h" +#include "pc.h" +#include "nullpo.h" + +#ifdef MEMWATCH +#include "memwatch.h" +#endif + +static const int packet_len_table[0x20] = { + 60, 3,-1,27,22,-1, 6,-1, // 2af8-2aff + 6,-1,18, 7,-1,49,44, 0, // 2b00-2b07 + 6,30,-1,10,86, 7,44,34, // 2b08-2b0f + -1,-1,10, 6,11,-1, 0, 0, // 2b10-2b17 +}; + +int char_fd; +int srvinfo; +static char char_ip_str[16]; +static int char_ip; +static int char_port = 6121; +static char userid[24], passwd[24]; +static int chrif_state; + +// 設定ファイル読み込み関係 +/*========================================== + * + *------------------------------------------ + */ +void chrif_setuserid(char *id) +{ + strncpy(userid, id, 24); +} + +/*========================================== + * + *------------------------------------------ + */ +void chrif_setpasswd(char *pwd) +{ + strncpy(passwd, pwd, 24); +} + +/*========================================== + * + *------------------------------------------ + */ +void chrif_setip(char *ip) +{ + strncpy(char_ip_str, ip, 16); + char_ip = inet_addr(char_ip_str); +} + +/*========================================== + * + *------------------------------------------ + */ +void chrif_setport(int port) +{ + char_port = port; +} + +/*========================================== + * + *------------------------------------------ + */ +int chrif_isconnect(void) +{ + return chrif_state == 2; +} + +/*========================================== + * + *------------------------------------------ + */ +int chrif_save(struct map_session_data *sd) +{ + nullpo_retr(-1, sd); + + if (char_fd < 0) + return -1; + + pc_makesavestatus(sd); + + WFIFOW(char_fd,0) = 0x2b01; + WFIFOW(char_fd,2) = sizeof(sd->status) + 12; + WFIFOL(char_fd,4) = sd->bl.id; + WFIFOL(char_fd,8) = sd->char_id; + memcpy(WFIFOP(char_fd,12), &sd->status, sizeof(sd->status)); + WFIFOSET(char_fd, WFIFOW(char_fd,2)); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int chrif_connect(int fd) +{ + WFIFOW(fd,0) = 0x2af8; + memcpy(WFIFOP(fd,2), userid, 24); + memcpy(WFIFOP(fd,26), passwd, 24); + WFIFOL(fd,50) = 0; + WFIFOL(fd,54) = clif_getip(); + WFIFOW(fd,58) = clif_getport(); // [Valaris] thanks to fov + WFIFOSET(fd,60); + + return 0; +} + +/*========================================== + * マップ送信 + *------------------------------------------ + */ +int chrif_sendmap(int fd) +{ + int i; + + WFIFOW(fd,0) = 0x2afa; + for(i = 0; i < map_num; i++) + if (map[i].alias[0] != '\0') // [MouseJstr] map aliasing + memcpy(WFIFOP(fd,4+i*16), map[i].alias, 16); + else + memcpy(WFIFOP(fd,4+i*16), map[i].name, 16); + WFIFOW(fd,2) = 4 + i * 16; + WFIFOSET(fd,WFIFOW(fd,2)); + + return 0; +} + +/*========================================== + * マップ受信 + *------------------------------------------ + */ +int chrif_recvmap(int fd) +{ + int i, j, ip, port; + unsigned char *p = (unsigned char *)&ip; + + if (chrif_state < 2) // まだ準備中 + return -1; + + ip = RFIFOL(fd,4); + port = RFIFOW(fd,8); + for(i = 10, j = 0; i < RFIFOW(fd,2); i += 16, j++) { + map_setipport(RFIFOP(fd,i), ip, port); +// if (battle_config.etc_log) +// printf("recv map %d %s\n", j, RFIFOP(fd,i)); + } + if (battle_config.etc_log) + printf("recv map on %d.%d.%d.%d:%d (%d maps)\n", p[0], p[1], p[2], p[3], port, j); + + return 0; +} + +/*========================================== + * マップ鯖間移動のためのデータ準備要求 + *------------------------------------------ + */ +int chrif_changemapserver(struct map_session_data *sd, char *name, int x, int y, int ip, short port) +{ + int i, s_ip; + + nullpo_retr(-1, sd); + + s_ip = 0; + for(i = 0; i < fd_max; i++) + if (session[i] && session[i]->session_data == sd) { + s_ip = session[i]->client_addr.sin_addr.s_addr; + break; + } + + WFIFOW(char_fd, 0) = 0x2b05; + WFIFOL(char_fd, 2) = sd->bl.id; + WFIFOL(char_fd, 6) = sd->login_id1; + WFIFOL(char_fd,10) = sd->login_id2; + WFIFOL(char_fd,14) = sd->status.char_id; + memcpy(WFIFOP(char_fd,18), name, 16); + WFIFOW(char_fd,34) = x; + WFIFOW(char_fd,36) = y; + WFIFOL(char_fd,38) = ip; + WFIFOL(char_fd,42) = port; + WFIFOB(char_fd,44) = sd->status.sex; + WFIFOL(char_fd,45) = s_ip; + WFIFOSET(char_fd,49); + + return 0; +} + +/*========================================== + * マップ鯖間移動ack + *------------------------------------------ + */ +int chrif_changemapserverack(int fd) +{ + struct map_session_data *sd = map_id2sd(RFIFOL(fd,2)); + + if (sd == NULL || sd->status.char_id != RFIFOL(fd,14)) + return -1; + + if (RFIFOL(fd,6) == 1) { + if (battle_config.error_log) + printf("map server change failed.\n"); + pc_authfail(sd->fd); + return 0; + } + clif_changemapserver(sd, RFIFOP(fd,18), RFIFOW(fd,34), RFIFOW(fd,36), RFIFOL(fd,38), RFIFOW(fd,42)); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int chrif_connectack(int fd) +{ + if (RFIFOB(fd,2)) { + printf("Connected to char-server failed %d.\n", RFIFOB(fd,2)); + exit(1); + } + printf("Connected to char-server (connection #%d).\n", fd); + chrif_state = 1; + + chrif_sendmap(fd); + + printf("chrif: OnCharIfInit event done. (%d events)\n", npc_event_doall("OnCharIfInit")); + printf("chrif: OnInterIfInit event done. (%d events)\n", npc_event_doall("OnInterIfInit")); + + // <Agit> Run Event [AgitInit] +// printf("NPC_Event:[OnAgitInit] do (%d) events (Agit Initialize).\n", npc_event_doall("OnAgitInit")); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int chrif_sendmapack(int fd) +{ + if (RFIFOB(fd,2)) { + printf("chrif : send map list to char server failed %d\n", RFIFOB(fd,2)); + exit(1); + } + + memcpy(wisp_server_name, RFIFOP(fd,3), 24); + + chrif_state = 2; + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int chrif_authreq(struct map_session_data *sd) +{ + int i; + + nullpo_retr(-1, sd); + + if (!sd || !char_fd || !sd->bl.id || !sd->login_id1) + return -1; + + for(i = 0; i < fd_max; i++) + if (session[i] && session[i]->session_data == sd) { + WFIFOW(char_fd, 0) = 0x2afc; + WFIFOL(char_fd, 2) = sd->bl.id; + WFIFOL(char_fd, 6) = sd->char_id; + WFIFOL(char_fd,10) = sd->login_id1; + WFIFOL(char_fd,14) = sd->login_id2; + WFIFOL(char_fd,18) = session[i]->client_addr.sin_addr.s_addr; + WFIFOSET(char_fd,22); + break; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int chrif_charselectreq(struct map_session_data *sd) +{ + int i, s_ip; + + nullpo_retr(-1, sd); + + if(!sd || !char_fd || !sd->bl.id || !sd->login_id1) + return -1; + + s_ip = 0; + for(i = 0; i < fd_max; i++) + if (session[i] && session[i]->session_data == sd) { + s_ip = session[i]->client_addr.sin_addr.s_addr; + break; + } + + WFIFOW(char_fd, 0) = 0x2b02; + WFIFOL(char_fd, 2) = sd->bl.id; + WFIFOL(char_fd, 6) = sd->login_id1; + WFIFOL(char_fd,10) = sd->login_id2; + WFIFOL(char_fd,14) = s_ip; + WFIFOSET(char_fd,18); + + return 0; +} + +/*========================================== + * キャラ名問い合わせ + *------------------------------------------ + */ +int chrif_searchcharid(int char_id) +{ + if (!char_id) + return -1; + + WFIFOW(char_fd,0) = 0x2b08; + WFIFOL(char_fd,2) = char_id; + WFIFOSET(char_fd,6); + + return 0; +} + +/*========================================== + * GMに変化要求 + *------------------------------------------ + */ +int chrif_changegm(int id, const char *pass, int len) +{ + if (battle_config.etc_log) + printf("chrif_changegm: account: %d, password: '%s'.\n", id, pass); + + WFIFOW(char_fd,0) = 0x2b0a; + WFIFOW(char_fd,2) = len + 8; + WFIFOL(char_fd,4) = id; + memcpy(WFIFOP(char_fd,8), pass, len); + WFIFOSET(char_fd, len + 8); + + return 0; +} + +/*========================================== + * Change Email + *------------------------------------------ + */ +int chrif_changeemail(int id, const char *actual_email, const char *new_email) +{ + if (battle_config.etc_log) + printf("chrif_changeemail: account: %d, actual_email: '%s', new_email: '%s'.\n", id, actual_email, new_email); + + WFIFOW(char_fd,0) = 0x2b0c; + WFIFOL(char_fd,2) = id; + memcpy(WFIFOP(char_fd,6), actual_email, 40); + memcpy(WFIFOP(char_fd,46), new_email, 40); + WFIFOSET(char_fd,86); + + return 0; +} + +/*========================================== + * Send message to char-server with a character name to do some operations (by Yor) + * Used to ask Char-server about a character name to have the account number to modify account file in login-server. + * type of operation: + * 1: block + * 2: ban + * 3: unblock + * 4: unban + * 5: changesex + *------------------------------------------ + */ +int chrif_char_ask_name(int id, char * character_name, short operation_type, int year, int month, int day, int hour, int minute, int second) +{ + WFIFOW(char_fd, 0) = 0x2b0e; + WFIFOL(char_fd, 2) = id; // account_id of who ask (for answer) -1 if nobody + memcpy(WFIFOP(char_fd,6), character_name, 24); + WFIFOW(char_fd, 30) = operation_type; // type of operation + if (operation_type == 2) { + WFIFOW(char_fd, 32) = year; + WFIFOW(char_fd, 34) = month; + WFIFOW(char_fd, 36) = day; + WFIFOW(char_fd, 38) = hour; + WFIFOW(char_fd, 40) = minute; + WFIFOW(char_fd, 42) = second; + } + printf("chrif : sended 0x2b0e\n"); + WFIFOSET(char_fd,44); + + return 0; +} + +/*========================================== + * Answer after a request about a character name to do some operations (by Yor) + * Used to answer of chrif_char_ask_name. + * type of operation: + * 1: block + * 2: ban + * 3: unblock + * 4: unban + * 5: changesex + * type of answer: + * 0: login-server resquest done + * 1: player not found + * 2: gm level too low + * 3: login-server offline + *------------------------------------------ + */ +int chrif_char_ask_name_answer(int fd) +{ + int acc; + struct map_session_data *sd; + char output[256]; + char player_name[24]; + + acc = RFIFOL(fd,2); // account_id of who has asked (-1 if nobody) + memcpy(player_name, RFIFOP(fd,6), sizeof(player_name)); + player_name[sizeof(player_name)-1] = '\0'; + + sd = map_id2sd(acc); + if (acc >= 0 && sd != NULL) { + if (RFIFOW(fd, 32) == 1) // player not found + sprintf(output, "The player '%s' doesn't exist.", player_name); + else { + switch(RFIFOW(fd, 30)) { + case 1: // block + switch(RFIFOW(fd, 32)) { + case 0: // login-server resquest done + sprintf(output, "Login-server has been asked to block the player '%s'.", player_name); + break; + //case 1: // player not found + case 2: // gm level too low + sprintf(output, "Your GM level don't authorise you to block the player '%s'.", player_name); + break; + case 3: // login-server offline + sprintf(output, "Login-server is offline. Impossible to block the the player '%s'.", player_name); + break; + } + break; + case 2: // ban + switch(RFIFOW(fd, 32)) { + case 0: // login-server resquest done + sprintf(output, "Login-server has been asked to ban the player '%s'.", player_name); + break; + //case 1: // player not found + case 2: // gm level too low + sprintf(output, "Your GM level don't authorise you to ban the player '%s'.", player_name); + break; + case 3: // login-server offline + sprintf(output, "Login-server is offline. Impossible to ban the the player '%s'.", player_name); + break; + } + break; + case 3: // unblock + switch(RFIFOW(fd, 32)) { + case 0: // login-server resquest done + sprintf(output, "Login-server has been asked to unblock the player '%s'.", player_name); + break; + //case 1: // player not found + case 2: // gm level too low + sprintf(output, "Your GM level don't authorise you to unblock the player '%s'.", player_name); + break; + case 3: // login-server offline + sprintf(output, "Login-server is offline. Impossible to unblock the the player '%s'.", player_name); + break; + } + break; + case 4: // unban + switch(RFIFOW(fd, 32)) { + case 0: // login-server resquest done + sprintf(output, "Login-server has been asked to unban the player '%s'.", player_name); + break; + //case 1: // player not found + case 2: // gm level too low + sprintf(output, "Your GM level don't authorise you to unban the player '%s'.", player_name); + break; + case 3: // login-server offline + sprintf(output, "Login-server is offline. Impossible to unban the the player '%s'.", player_name); + break; + } + break; + case 5: // changesex + switch(RFIFOW(fd, 32)) { + case 0: // login-server resquest done + sprintf(output, "Login-server has been asked to change the sex of the player '%s'.", player_name); + break; + //case 1: // player not found + case 2: // gm level too low + sprintf(output, "Your GM level don't authorise you to change the sex of the player '%s'.", player_name); + break; + case 3: // login-server offline + sprintf(output, "Login-server is offline. Impossible to change the sex of the the player '%s'.", player_name); + break; + } + break; + } + } + if (output[0] != '\0') { + output[sizeof(output)-1] = '\0'; + clif_displaymessage(sd->fd, output); + } + } else + printf("chrif_char_ask_name_answer failed - player not online.\n"); + + return 0; +} + +/*========================================== + * End of GM change (@GM) (modified by Yor) + *------------------------------------------ + */ +int chrif_changedgm(int fd) +{ + int acc, level; + struct map_session_data *sd = NULL; + + acc = RFIFOL(fd,2); + level = RFIFOL(fd,6); + + sd = map_id2sd(acc); + + if (battle_config.etc_log) + printf("chrif_changedgm: account: %d, GM level 0 -> %d.\n", acc, level); + if (sd != NULL) { + if (level > 0) + clif_displaymessage(sd->fd, "GM modification success."); + else + clif_displaymessage(sd->fd, "Failure of GM modification."); + } + + return 0; +} + +/*========================================== + * 性別変化終了 (modified by Yor) + *------------------------------------------ + */ +int chrif_changedsex(int fd) +{ + int acc, sex, i; + struct map_session_data *sd; + struct pc_base_job s_class; + + acc = RFIFOL(fd,2); + sex = RFIFOL(fd,6); + if (battle_config.etc_log) + printf("chrif_changedsex %d.\n", acc); + sd = map_id2sd(acc); + if (acc > 0) { + if (sd != NULL && sd->status.sex != sex) { + s_class = pc_calc_base_job(sd->status.class); + if (sd->status.sex == 0) { + sd->status.sex = 1; + sd->sex = 1; + } else if (sd->status.sex == 1) { + sd->status.sex = 0; + sd->sex = 0; + } + // to avoid any problem with equipment and invalid sex, equipment is unequiped. + for (i = 0; i < MAX_INVENTORY; i++) { + if (sd->status.inventory[i].nameid && sd->status.inventory[i].equip) + pc_unequipitem((struct map_session_data*)sd, i, 0); + } + // reset skill of some job + if (s_class.job == 19 || s_class.job == 4020 || s_class.job == 4042 || + s_class.job == 20 || s_class.job == 4021 || s_class.job == 4043) { + // remove specifical skills of classes 19, 4020 and 4042 + for(i = 315; i <= 322; i++) { + if (sd->status.skill[i].id > 0 && !sd->status.skill[i].flag) { + sd->status.skill_point += sd->status.skill[i].lv; + sd->status.skill[i].id = 0; + sd->status.skill[i].lv = 0; + } + } + // remove specifical skills of classes 20, 4021 and 4043 + for(i = 323; i <= 330; i++) { + if (sd->status.skill[i].id > 0 && !sd->status.skill[i].flag) { + sd->status.skill_point += sd->status.skill[i].lv; + sd->status.skill[i].id = 0; + sd->status.skill[i].lv = 0; + } + } + clif_updatestatus(sd, SP_SKILLPOINT); + // change job if necessary + if (s_class.job == 20 || s_class.job == 4021 || s_class.job == 4043) + sd->status.class -= 1; + else if (s_class.job == 19 || s_class.job == 4020 || s_class.job == 4042) + sd->status.class += 1; + } + // save character + chrif_save(sd); + sd->login_id1++; // change identify, because if player come back in char within the 5 seconds, he can change its characters + // do same modify in login-server for the account, but no in char-server (it ask again login_id1 to login, and don't remember it) + clif_displaymessage(sd->fd, "Your sex has been changed (need disconexion by the server)..."); + clif_setwaitclose(sd->fd); // forced to disconnect for the change + } + } else { + if (sd != NULL) { + printf("chrif_changedsex failed.\n"); + } + } + + return 0; +} + +/*========================================== + * アカウント変数保存要求 + *------------------------------------------ + */ +int chrif_saveaccountreg2(struct map_session_data *sd) +{ + int p, j; + nullpo_retr(-1, sd); + + p = 8; + for(j = 0; j < sd->status.account_reg2_num; j++) { + struct global_reg *reg = &sd->status.account_reg2[j]; + if (reg->str[0] && reg->value != 0) { + memcpy(WFIFOP(char_fd,p), reg->str, 32); + WFIFOL(char_fd,p+32) = reg->value; + p += 36; + } + } + WFIFOW(char_fd,0) = 0x2b10; + WFIFOW(char_fd,2) = p; + WFIFOL(char_fd,4) = sd->bl.id; + WFIFOSET(char_fd,p); + + return 0; +} + +/*========================================== + * アカウント変数通知 + *------------------------------------------ + */ +int chrif_accountreg2(int fd) +{ + int j, p; + struct map_session_data *sd; + + if ((sd = map_id2sd(RFIFOL(fd,4))) == NULL) + return 1; + + for(p = 8, j = 0; p < RFIFOW(fd,2) && j < ACCOUNT_REG2_NUM; p += 36, j++) { + memcpy(sd->status.account_reg2[j].str, RFIFOP(fd,p), 32); + sd->status.account_reg2[j].value = RFIFOL(fd, p + 32); + } + sd->status.account_reg2_num = j; +// printf("chrif: accountreg2\n"); + + return 0; +} + +/*========================================== + * 離婚情報同期要求 + *------------------------------------------ + */ +int chrif_divorce(int char_id, int partner_id) +{ + struct map_session_data *sd = NULL; + + if (!char_id || !partner_id) + return 0; + + nullpo_retr(0, sd = map_nick2sd(map_charid2nick(partner_id))); + if (sd->status.partner_id == char_id) { + int i; + //離婚(相方は既にキャラが消えている筈なので) + sd->status.partner_id = 0; + + //相方の結婚指輪を剥奪 + for(i = 0; i < MAX_INVENTORY; i++) + if (sd->status.inventory[i].nameid == WEDDING_RING_M || sd->status.inventory[i].nameid == WEDDING_RING_F) + pc_delitem(sd, i, 1, 0); + } + + return 0; +} + +/*========================================== + * Disconnection of a player (account has been deleted in login-server) by [Yor] + *------------------------------------------ + */ +int chrif_accountdeletion(int fd) +{ + int acc; + struct map_session_data *sd; + + acc = RFIFOL(fd,2); + if (battle_config.etc_log) + printf("chrif_accountdeletion %d.\n", acc); + sd = map_id2sd(acc); + if (acc > 0) { + if (sd != NULL) { + sd->login_id1++; // change identify, because if player come back in char within the 5 seconds, he can change its characters + clif_displaymessage(sd->fd, "Your account has been deleted (disconnexion)..."); + clif_setwaitclose(sd->fd); // forced to disconnect for the change + } + } else { + if (sd != NULL) + printf("chrif_accountdeletion failed - player not online.\n"); + } + + return 0; +} + +/*========================================== + * Disconnection of a player (account has been banned of has a status, from login-server) by [Yor] + *------------------------------------------ + */ +int chrif_accountban(int fd) +{ + int acc; + struct map_session_data *sd; + + acc = RFIFOL(fd,2); + if (battle_config.etc_log) + printf("chrif_accountban %d.\n", acc); + sd = map_id2sd(acc); + if (acc > 0) { + if (sd != NULL) { + sd->login_id1++; // change identify, because if player come back in char within the 5 seconds, he can change its characters + if (RFIFOB(fd,6) == 0) { // 0: change of statut, 1: ban + switch (RFIFOL(fd,7)) { // status or final date of a banishment + case 1: // 0 = Unregistered ID + clif_displaymessage(sd->fd, "Your account has 'Unregistered'."); + break; + case 2: // 1 = Incorrect Password + clif_displaymessage(sd->fd, "Your account has an 'Incorrect Password'..."); + break; + case 3: // 2 = This ID is expired + clif_displaymessage(sd->fd, "Your account has expired."); + break; + case 4: // 3 = Rejected from Server + clif_displaymessage(sd->fd, "Your account has been rejected from server."); + break; + case 5: // 4 = You have been blocked by the GM Team + clif_displaymessage(sd->fd, "Your account has been blocked by the GM Team."); + break; + case 6: // 5 = Your Game's EXE file is not the latest version + clif_displaymessage(sd->fd, "Your Game's EXE file is not the latest version."); + break; + case 7: // 6 = Your are Prohibited to log in until %s + clif_displaymessage(sd->fd, "Your account has been prohibited to log in."); + break; + case 8: // 7 = Server is jammed due to over populated + clif_displaymessage(sd->fd, "Server is jammed due to over populated."); + break; + case 9: // 8 = No MSG (actually, all states after 9 except 99 are No MSG, use only this) + clif_displaymessage(sd->fd, "Your account has not more authorised."); + break; + case 100: // 99 = This ID has been totally erased + clif_displaymessage(sd->fd, "Your account has been totally erased."); + break; + default: + clif_displaymessage(sd->fd, "Your account has not more authorised."); + break; + } + } else if (RFIFOB(fd,6) == 1) { // 0: change of statut, 1: ban + time_t timestamp; + char tmpstr[2048]; + timestamp = (time_t)RFIFOL(fd,7); // status or final date of a banishment + strcpy(tmpstr, "Your account has been banished until "); + strftime(tmpstr + strlen(tmpstr), 24, "%d-%m-%Y %H:%M:%S", localtime(×tamp)); + clif_displaymessage(sd->fd, tmpstr); + } + clif_setwaitclose(sd->fd); // forced to disconnect for the change + } + } else { + if (sd != NULL) + printf("chrif_accountban failed - player not online.\n"); + } + + return 0; +} + +/*========================================== + * Receiving GM accounts and their levels from char-server by [Yor] + *------------------------------------------ + */ +int chrif_recvgmaccounts(int fd) +{ + printf("From login-server: receiving of %d GM accounts information.\n", pc_read_gm_account(fd)); + + return 0; +} + +/*========================================== + * Request to reload GM accounts and their levels: send to char-server by [Yor] + *------------------------------------------ + */ +int chrif_reloadGMdb(void) +{ + + WFIFOW(char_fd,0) = 0x2af7; + WFIFOSET(char_fd, 2); + + return 0; +} + +/*========================================== + * Send rates and motd to char server [Wizputer] + *------------------------------------------ + */ + int chrif_ragsrvinfo(int base_rate, int job_rate, int drop_rate) +{ + char buf[256]; + FILE *fp; + int i; + + WFIFOW(char_fd,0) = 0x2b16; + WFIFOW(char_fd,2) = base_rate; + WFIFOW(char_fd,4) = job_rate; + WFIFOW(char_fd,6) = drop_rate; + + if ((fp = fopen(motd_txt, "r")) != NULL) { + if (fgets(buf, 250, fp) != NULL) { + for(i = 0; buf[i]; i++) { + if (buf[i] == '\r' || buf[i] == '\n') { + buf[i] = 0; + break; + } + } + WFIFOW(char_fd,8) = sizeof(buf) + 10; + memcpy(WFIFOP(char_fd,10), buf, sizeof(buf)); + } + fclose(fp); + } else { + WFIFOW(char_fd,8) = sizeof(buf) + 10; + memcpy(WFIFOP(char_fd,10), buf, sizeof(buf)); + } + WFIFOSET(char_fd,WFIFOW(char_fd,8)); + + return 0; +} + +/*========================================= + * Tell char-server charcter disconnected [Wizputer] + *----------------------------------------- + */ + +int chrif_char_offline(struct map_session_data *sd) +{ + if (char_fd < 0) + return -1; + + WFIFOW(char_fd,0) = 0x2b17; + WFIFOL(char_fd,2) = sd->status.char_id; + WFIFOSET(char_fd,6); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int chrif_parse(int fd) +{ + int packet_len, cmd; + + // only char-server can have an access to here. + // so, if it isn't the char-server, we disconnect the session (fd != char_fd). + if (fd != char_fd || session[fd]->eof) { + if (fd == char_fd) { + printf("Map-server can't connect to char-server (connection #%d).\n", fd); + char_fd = -1; + } + close(fd); + delete_session(fd); + return 0; + } + + while (RFIFOREST(fd) >= 2) { + cmd = RFIFOW(fd,0); + if (cmd < 0x2af8 || cmd >= 0x2af8 + (sizeof(packet_len_table) / sizeof(packet_len_table[0])) || + packet_len_table[cmd-0x2af8] == 0) { + + int r = intif_parse(fd); // intifに渡す + + if (r == 1) continue; // intifで処理した + if (r == 2) return 0; // intifで処理したが、データが足りない + + session[fd]->eof = 1; + return 0; + } + packet_len = packet_len_table[cmd-0x2af8]; + if (packet_len == -1) { + if (RFIFOREST(fd) < 4) + return 0; + packet_len = RFIFOW(fd,2); + } + if (RFIFOREST(fd) < packet_len) + return 0; + + switch(cmd) { + case 0x2af9: chrif_connectack(fd); break; + case 0x2afb: chrif_sendmapack(fd); break; + case 0x2afd: pc_authok(RFIFOL(fd,4), RFIFOL(fd,8), (time_t)RFIFOL(fd,12), (struct mmo_charstatus*)RFIFOP(fd,16)); break; + case 0x2afe: pc_authfail(RFIFOL(fd,2)); break; + case 0x2b00: map_setusers(RFIFOL(fd,2)); break; + case 0x2b03: clif_charselectok(RFIFOL(fd,2)); break; + case 0x2b04: chrif_recvmap(fd); break; + case 0x2b06: chrif_changemapserverack(fd); break; + case 0x2b09: map_addchariddb(RFIFOL(fd,2), RFIFOP(fd,6)); break; + case 0x2b0b: chrif_changedgm(fd); break; + case 0x2b0d: chrif_changedsex(fd); break; + case 0x2b0f: chrif_char_ask_name_answer(fd); break; + case 0x2b11: chrif_accountreg2(fd); break; + case 0x2b12: chrif_divorce(RFIFOL(fd,2), RFIFOL(fd,6)); break; + case 0x2b13: chrif_accountdeletion(fd); break; + case 0x2b14: chrif_accountban(fd); break; + case 0x2b15: chrif_recvgmaccounts(fd); break; + + default: + if (battle_config.error_log) + printf("chrif_parse : unknown packet %d %d\n", fd, RFIFOW(fd,0)); + session[fd]->eof = 1; + return 0; + } + RFIFOSKIP(fd, packet_len); + } + + return 0; +} + +/*========================================== + * timer関数 + * 今このmap鯖に繋がっているクライアント人数をchar鯖へ送る + *------------------------------------------ + */ +int send_users_tochar(int tid, unsigned int tick, int id, int data) { + int users = 0, i; + struct map_session_data *sd; + + if (char_fd <= 0 || session[char_fd] == NULL) + return 0; + + WFIFOW(char_fd,0) = 0x2aff; + for (i = 0; i < fd_max; i++) { + if (session[i] && (sd = session[i]->session_data) && sd->state.auth && + !((battle_config.hide_GM_session || (sd->status.option & OPTION_HIDE)) && pc_isGM(sd))) { + WFIFOL(char_fd,6+4*users) = sd->status.char_id; + users++; + } + } + WFIFOW(char_fd,2) = 6 + 4 * users; + WFIFOW(char_fd,4) = users; + WFIFOSET(char_fd,6+4*users); + + return 0; +} + +/*========================================== + * timer関数 + * char鯖との接続を確認し、もし切れていたら再度接続する + *------------------------------------------ + */ +int check_connect_char_server(int tid, unsigned int tick, int id, int data) { + if (char_fd <= 0 || session[char_fd] == NULL) { + printf("Attempt to connect to char-server...\n"); + chrif_state = 0; + char_fd = make_connection(char_ip, char_port); + session[char_fd]->func_parse = chrif_parse; + realloc_fifo(char_fd, FIFOSIZE_SERVERLINK, FIFOSIZE_SERVERLINK); + + chrif_connect(char_fd); +#ifndef TXT_ONLY + srvinfo = 0; + } else { + if (srvinfo == 0) { + chrif_ragsrvinfo(battle_config.base_exp_rate, battle_config.job_exp_rate, battle_config.item_rate_common); + srvinfo = 1; + } +#endif /* not TXT_ONLY */ + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int do_init_chrif(void) { + add_timer_func_list(check_connect_char_server, "check_connect_char_server"); + add_timer_func_list(send_users_tochar, "send_users_tochar"); + add_timer_interval(gettick() + 1000, check_connect_char_server, 0, 0, 10 * 1000); + add_timer_interval(gettick() + 1000, send_users_tochar, 0, 0, 5 * 1000); + + return 0; +} diff --git a/misc/src/map/chrif.h b/misc/src/map/chrif.h new file mode 100644 index 0000000..19d725d --- /dev/null +++ b/misc/src/map/chrif.h @@ -0,0 +1,29 @@ +// $Id: chrif.h,v 1.3 2004/09/25 11:39:17 MouseJstr Exp $ +#ifndef _CHRIF_H_ +#define _CHRIF_H_ + +void chrif_setuserid(char*); +void chrif_setpasswd(char*); +void chrif_setip(char*); +void chrif_setport(int); + +int chrif_isconnect(void); + +int chrif_authreq(struct map_session_data *); +int chrif_save(struct map_session_data*); +int chrif_charselectreq(struct map_session_data *); + +int chrif_changemapserver(struct map_session_data *sd,char *name,int x,int y,int ip,short port); + +int chrif_searchcharid(int char_id); +int chrif_changegm(int id,const char *pass,int len); +int chrif_changeemail(int id, const char *actual_email, const char *new_email); +int chrif_char_ask_name(int id, char * character_name, short operation_type, int year, int month, int day, int hour, int minute, int second); +int chrif_saveaccountreg2(struct map_session_data *sd); +int chrif_reloadGMdb(void); +int chrif_ragsrvinfo(int base_rate,int job_rate, int drop_rate); +int chrif_char_offline(struct map_session_data *sd); + +int do_init_chrif(void); + +#endif diff --git a/misc/src/map/clif.c b/misc/src/map/clif.c new file mode 100644 index 0000000..1f320b2 --- /dev/null +++ b/misc/src/map/clif.c @@ -0,0 +1,9826 @@ +// $Id: clif.c 164 2004-10-01 16:46:58Z $ + +#define DUMP_UNKNOWN_PACKET 1 + +#include <stdio.h> +#include <ctype.h> +#include <stdlib.h> +#include <string.h> +#include <stdarg.h> +#include <sys/types.h> +#ifdef LCCWIN32 +#include <winsock.h> +#else +#include <unistd.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#endif +#include <time.h> + +#include "socket.h" +#include "timer.h" +#include "malloc.h" +#include "version.h" +#include "nullpo.h" + +#include "map.h" +#include "chrif.h" +#include "clif.h" +#include "pc.h" +#include "npc.h" +#include "itemdb.h" +#include "chat.h" +#include "trade.h" +#include "storage.h" +#include "script.h" +#include "skill.h" +#include "atcommand.h" +#include "intif.h" +#include "battle.h" +#include "mob.h" +#include "party.h" +#include "guild.h" +#include "vending.h" +#include "pet.h" + +#ifdef MEMWATCH +#include "memwatch.h" +#endif + +#define STATE_BLIND 0x10 + +static const int packet_len_table[0x220] = { + 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +//#0x0040 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 55, 17, 3, 37, 46, -1, 23, -1, 3,108, 3, 2, +#if PACKETVER < 2 + 3, 28, 19, 11, 3, -1, 9, 5, 52, 51, 56, 58, 41, 2, 6, 6, +#else // 78-7b 亀島以降 lv99エフェクト用 + 3, 28, 19, 11, 3, -1, 9, 5, 54, 53, 58, 60, 41, 2, 6, 6, +#endif +//#0x0080 + 7, 3, 2, 2, 2, 5, 16, 12, 10, 7, 29, 23, -1, -1, -1, 0, // 0x8b unknown... size 2 or 23? + 7, 22, 28, 2, 6, 30, -1, -1, 3, -1, -1, 5, 9, 17, 17, 6, + 23, 6, 6, -1, -1, -1, -1, 8, 7, 6, 7, 4, 7, 0, -1, 6, + 8, 8, 3, 3, -1, 6, 6, -1, 7, 6, 2, 5, 6, 44, 5, 3, +//#0x00C0 + 7, 2, 6, 8, 6, 7, -1, -1, -1, -1, 3, 3, 6, 6, 2, 27, + 3, 4, 4, 2, -1, -1, 3, -1, 6, 14, 3, -1, 28, 29, -1, -1, + 30, 30, 26, 2, 6, 26, 3, 3, 8, 19, 5, 2, 3, 2, 2, 2, + 3, 2, 6, 8, 21, 8, 8, 2, 2, 26, 3, -1, 6, 27, 30, 10, + +//#0x0100 + 2, 6, 6, 30, 79, 31, 10, 10, -1, -1, 4, 6, 6, 2, 11, -1, + 10, 39, 4, 10, 31, 35, 10, 18, 2, 13, 15, 20, 68, 2, 3, 16, + 6, 14, -1, -1, 21, 8, 8, 8, 8, 8, 2, 2, 3, 4, 2, -1, + 6, 86, 6, -1, -1, 7, -1, 6, 3, 16, 4, 4, 4, 6, 24, 26, +//#0x0140 + 22, 14, 6, 10, 23, 19, 6, 39, 8, 9, 6, 27, -1, 2, 6, 6, + 110, 6, -1, -1, -1, -1, -1, 6, -1, 54, 66, 54, 90, 42, 6, 42, + -1, -1, -1, -1, -1, 30, -1, 3, 14, 3, 30, 10, 43, 14,186,182, + 14, 30, 10, 3, -1, 6,106, -1, 4, 5, 4, -1, 6, 7, -1, -1, +//#0x0180 + 6, 3,106, 10, 10, 34, 0, 6, 8, 4, 4, 4, 29, -1, 10, 6, +#if PACKETVER < 1 + 90, 86, 24, 6, 30,102, 8, 4, 8, 4, 14, 10, -1, 6, 2, 6, +#else // 196 comodo以降 状態表示アイコン用 + 90, 86, 24, 6, 30,102, 9, 4, 8, 4, 14, 10, -1, 6, 2, 6, +#endif + 3, 3, 35, 5, 11, 26, -1, 4, 4, 6, 10, 12, 6, -1, 4, 4, + 11, 7, -1, 67, 12, 18,114, 6, 3, 6, 26, 26, 26, 26, 2, 3, +//#0x01C0, Set 0x1d5=-1 + 2, 14, 10, -1, 22, 22, 4, 2, 13, 97, 0, 9, 9, 30, 6, 28, + 8, 14, 10, 35, 6, -1, 4, 11, 54, 53, 60, 2, -1, 47, 33, 6, + 30, 8, 34, 14, 2, 6, 26, 2, 28, 81, 6, 10, 26, 2, -1, -1, + -1, -1, 20, 10, 32, 9, 34, 14, 2, 6, 48, 56, -1, 4, 5, 10, +//#0x200 + 26, -1, 26, 10, 18, 26, 11, 34, 14, 36, 10, 19, 0, -1, 24, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +}; + +// size list for each packet version after packet version 4. +static int packet_size_table[6][0x220]; + +// local define +enum { + ALL_CLIENT, + ALL_SAMEMAP, + AREA, + AREA_WOS, + AREA_WOC, + AREA_WOSC, + AREA_CHAT_WOC, + CHAT, + CHAT_WOS, + PARTY, + PARTY_WOS, + PARTY_SAMEMAP, + PARTY_SAMEMAP_WOS, + PARTY_AREA, + PARTY_AREA_WOS, + GUILD, + GUILD_WOS, + GUILD_SAMEMAP, // [Valaris] + GUILD_SAMEMAP_WOS, + GUILD_AREA, + GUILD_AREA_WOS, // end additions [Valaris] + SELF +}; + +#define WBUFPOS(p,pos,x,y) { unsigned char *__p = (p); __p+=(pos); __p[0] = (x)>>2; __p[1] = ((x)<<6) | (((y)>>4)&0x3f); __p[2] = (y)<<4; } +#define WBUFPOS2(p,pos,x0,y0,x1,y1) { unsigned char *__p = (p); __p+=(pos); __p[0] = (x0)>>2; __p[1] = ((x0)<<6) | (((y0)>>4)&0x3f); __p[2] = ((y0)<<4) | (((x1)>>6)&0x0f); __p[3]=((x1)<<2) | (((y1)>>8)&0x03); __p[4]=(y1); } + +#define WFIFOPOS(fd,pos,x,y) { WBUFPOS (WFIFOP(fd,pos),0,x,y); } +#define WFIFOPOS2(fd,pos,x0,y0,x1,y1) { WBUFPOS2(WFIFOP(fd,pos),0,x0,y0,x1,y1); } + +static char map_ip_str[16]; +static in_addr_t map_ip; +static int map_port = 5121; +int map_fd; +char talkie_mes[80]; + +/*========================================== + * map鯖のip設定 + *------------------------------------------ + */ +void clif_setip(char *ip) +{ + memcpy(map_ip_str, ip, 16); + map_ip = inet_addr(map_ip_str); +} + +/*========================================== + * map鯖のport設定 + *------------------------------------------ + */ +void clif_setport(int port) +{ + map_port = port; +} + +/*========================================== + * map鯖のip読み出し + *------------------------------------------ + */ +in_addr_t clif_getip(void) +{ + return map_ip; +} + +/*========================================== + * map鯖のport読み出し + *------------------------------------------ + */ +int clif_getport(void) +{ + return map_port; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_countusers(void) +{ + int users = 0, i; + struct map_session_data *sd; + + for(i = 0; i < fd_max; i++) { + if (session[i] && (sd = session[i]->session_data) && sd && sd->state.auth && + !(battle_config.hide_GM_session && pc_isGM(sd))) + users++; + } + return users; +} + +/*========================================== + * 全てのclientに対してfunc()実行 + *------------------------------------------ + */ +int clif_foreachclient(int (*func)(struct map_session_data*, va_list),...) +{ + int i; + va_list ap; + struct map_session_data *sd; + + va_start(ap,func); + for(i = 0; i < fd_max; i++) { + if (session[i] && (sd = session[i]->session_data) && sd && sd->state.auth) + func(sd, ap); + } + va_end(ap); + return 0; +} + +/*========================================== + * clif_sendでAREA*指定時用 + *------------------------------------------ + */ +int clif_send_sub(struct block_list *bl, va_list ap) +{ + unsigned char *buf; + int len; + struct block_list *src_bl; + int type; + struct map_session_data *sd; + + nullpo_retr(0, bl); + nullpo_retr(0, ap); + nullpo_retr(0, sd = (struct map_session_data *)bl); + + buf = va_arg(ap,unsigned char*); + len = va_arg(ap,int); + nullpo_retr(0, src_bl = va_arg(ap,struct block_list*)); + type = va_arg(ap,int); + + switch(type) { + case AREA_WOS: + if (bl && bl == src_bl) + return 0; + break; + case AREA_WOC: + if ((sd && sd->chatID) || (bl && bl == src_bl)) + return 0; + break; + case AREA_WOSC: + if ((sd) && sd->chatID && sd->chatID == ((struct map_session_data*)src_bl)->chatID) + return 0; + break; + } + + if (sd) { + if (WFIFOP(sd->fd,0) == buf) { + printf("WARNING: Invalid use of clif_send function\n"); + printf(" Packet x%4x use a WFIFO of a player instead of to use a buffer.\n", WBUFW(buf,0)); + printf(" Please correct your code.\n"); + // don't send to not move the pointer of the packet for next sessions in the loop + } else { + if (packet_size_table[sd->packet_ver-5][RBUFW(buf,0)]) { // packet must exist for the client version + memcpy(WFIFOP(sd->fd,0), buf, len); + WFIFOSET(sd->fd,len); + } + } + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_send(unsigned char *buf, int len, struct block_list *bl, int type) { + int i; + struct map_session_data *sd; + struct chat_data *cd; + struct party *p = NULL; + struct guild *g = NULL; + int x0 = 0, x1 = 0, y0 = 0, y1 = 0; + + if (type != ALL_CLIENT) { + nullpo_retr(0, bl); + } + + switch(type) { + case ALL_CLIENT: // 全クライアントに送信 + for(i = 0; i < fd_max; i++) { + if (session[i] && (sd = session[i]->session_data) != NULL && sd->state.auth) { + if (packet_size_table[sd->packet_ver-5][RBUFW(buf,0)]) { // packet must exist for the client version + memcpy(WFIFOP(i,0), buf, len); + WFIFOSET(i,len); + } + } + } + break; + case ALL_SAMEMAP: // 同じマップの全クライアントに送信 + for(i = 0; i < fd_max; i++) { + if (session[i] && (sd = session[i]->session_data) != NULL && sd->state.auth && sd->bl.m == bl->m) { + if (packet_size_table[sd->packet_ver-5][RBUFW(buf,0)]) { // packet must exist for the client version + memcpy(WFIFOP(i,0), buf, len); + WFIFOSET(i,len); + } + } + } + break; + case AREA: + case AREA_WOS: + case AREA_WOC: + case AREA_WOSC: + map_foreachinarea(clif_send_sub, bl->m, bl->x-AREA_SIZE, bl->y-AREA_SIZE, bl->x+AREA_SIZE, bl->y+AREA_SIZE, BL_PC, buf, len, bl, type); + break; + case AREA_CHAT_WOC: + map_foreachinarea(clif_send_sub, bl->m, bl->x-(AREA_SIZE-5), bl->y-(AREA_SIZE-5), bl->x+(AREA_SIZE-5), bl->y+(AREA_SIZE-5), BL_PC, buf, len, bl, AREA_WOC); + break; + case CHAT: + case CHAT_WOS: + cd = (struct chat_data*)bl; + if (bl->type == BL_PC) { + sd = (struct map_session_data*)bl; + cd = (struct chat_data*)map_id2bl(sd->chatID); + } else if (bl->type != BL_CHAT) + break; + if (cd == NULL) + break; + for(i = 0; i < cd->users; i++) { + if (type == CHAT_WOS && cd->usersd[i] == (struct map_session_data*)bl) + continue; + if (packet_size_table[cd->usersd[i]->packet_ver-5][RBUFW(buf,0)]) { // packet must exist for the client version + memcpy(WFIFOP(cd->usersd[i]->fd,0), buf, len); + WFIFOSET(cd->usersd[i]->fd,len); + } + } + break; + + case PARTY_AREA: // 同じ画面内の全パーティーメンバに送信 + case PARTY_AREA_WOS: // 自分以外の同じ画面内の全パーティーメンバに送信 + x0 = bl->x - AREA_SIZE; + y0 = bl->y - AREA_SIZE; + x1 = bl->x + AREA_SIZE; + y1 = bl->y + AREA_SIZE; + case PARTY: // 全パーティーメンバに送信 + case PARTY_WOS: // 自分以外の全パーティーメンバに送信 + case PARTY_SAMEMAP: // 同じマップの全パーティーメンバに送信 + case PARTY_SAMEMAP_WOS: // 自分以外の同じマップの全パーティーメンバに送信 + if (bl->type == BL_PC) { + sd = (struct map_session_data *)bl; + if (sd->partyspy > 0) { + p = party_search(sd->partyspy); + } else { + if (sd->status.party_id > 0) + p = party_search(sd->status.party_id); + } + } + if (p) { + for(i=0;i<MAX_PARTY;i++){ + if ((sd = p->member[i].sd) != NULL) { + if (sd->bl.id == bl->id && (type == PARTY_WOS || + type == PARTY_SAMEMAP_WOS || type == PARTY_AREA_WOS)) + continue; + if (type != PARTY && type != PARTY_WOS && bl->m != sd->bl.m) // マップチェック + continue; + if ((type == PARTY_AREA || type == PARTY_AREA_WOS) && + (sd->bl.x < x0 || sd->bl.y < y0 || + sd->bl.x > x1 || sd->bl.y > y1)) + continue; + if (packet_size_table[sd->packet_ver-5][RBUFW(buf,0)]) { // packet must exist for the client version + memcpy(WFIFOP(sd->fd,0), buf, len); + WFIFOSET(sd->fd,len); + } +// if(battle_config.etc_log) +// printf("send party %d %d %d\n",p->party_id,i,flag) + + } + } + for (i = 0; i < fd_max; i++){ + if (session[i] && (sd = session[i]->session_data) != NULL && sd->state.auth) { + if (sd->partyspy == p->party_id) { + if (packet_size_table[sd->packet_ver-5][RBUFW(buf,0)]) { // packet must exist for the client version + memcpy(WFIFOP(sd->fd,0), buf, len); + WFIFOSET(sd->fd,len); + } + } + } + } + } + break; + case SELF: + sd = (struct map_session_data *)bl; + if (packet_size_table[sd->packet_ver-5][RBUFW(buf,0)]) { // packet must exist for the client version + memcpy(WFIFOP(sd->fd,0), buf, len); + WFIFOSET(sd->fd,len); + } + break; + +/* New definitions for guilds [Valaris] */ + + case GUILD_AREA: + case GUILD_AREA_WOS: + x0 = bl->x - AREA_SIZE; + y0 = bl->y - AREA_SIZE; + x1 = bl->x + AREA_SIZE; + y1 = bl->y + AREA_SIZE; + case GUILD: + case GUILD_WOS: + if (bl && bl->type == BL_PC) { // guildspy [Syrus22] + sd = (struct map_session_data *)bl; + if (sd->guildspy > 0) { + g = guild_search(sd->guildspy); + } else { + if (sd->status.guild_id > 0) + g = guild_search(sd->status.guild_id); + } + } + if (g) { + for(i = 0; i < g->max_member; i++) { + if ((sd = g->member[i].sd) != NULL) { + if (type == GUILD_WOS && sd->bl.id == bl->id) + continue; + if (packet_size_table[sd->packet_ver-5][RBUFW(buf,0)]) { // packet must exist for the client version + memcpy(WFIFOP(sd->fd,0), buf, len); + WFIFOSET(sd->fd,len); + } + } + } + for (i = 0; i < fd_max; i++){ + if (session[i] && (sd = session[i]->session_data) != NULL && sd->state.auth) { + if (sd->guildspy == g->guild_id) { + if (packet_size_table[sd->packet_ver-5][RBUFW(buf,0)]) { // packet must exist for the client version + memcpy(WFIFOP(sd->fd,0), buf, len); + WFIFOSET(sd->fd,len); + } + } + } + } + } + break; + case GUILD_SAMEMAP: + case GUILD_SAMEMAP_WOS: + if (bl->type == BL_PC) { + sd = (struct map_session_data *)bl; + if (sd->status.guild_id > 0) + g = guild_search(sd->status.guild_id); + } + if (g) { + for(i = 0; i < g->max_member; i++) { + if ((sd = g->member[i].sd) != NULL) { + if (sd->bl.id == bl->id && (type == GUILD_WOS || + type == GUILD_SAMEMAP_WOS || type == GUILD_AREA_WOS)) + continue; + if (type != GUILD && type != GUILD_WOS && bl->m != sd->bl.m) // マップチェック + continue; + if ((type == GUILD_AREA || type == GUILD_AREA_WOS) && + (sd->bl.x < x0 || sd->bl.y < y0 || + sd->bl.x > x1 || sd->bl.y > y1)) + continue; + if (packet_size_table[sd->packet_ver-5][RBUFW(buf,0)]) { // packet must exist for the client version + memcpy(WFIFOP(sd->fd,0), buf, len); + WFIFOSET(sd->fd,len); + } + } + } + } + break; +/* End [Valaris] */ + + default: + if (battle_config.error_log) + printf("clif_send まだ作ってないよー\n"); + return -1; + } + + return 0; +} + +// +// パケット作って送信 +// +/*========================================== + * + *------------------------------------------ + */ +int clif_authok(struct map_session_data *sd) { + int fd; + + nullpo_retr(0, sd); + + if (!sd) + return 0; + + if (!sd->fd) + return 0; + + fd = sd->fd; + + WFIFOW(fd, 0) = 0x73; + WFIFOL(fd, 2) = gettick(); + WFIFOPOS(fd, 6, sd->bl.x, sd->bl.y); + WFIFOB(fd, 9) = 5; + WFIFOB(fd,10) = 5; + WFIFOSET(fd,packet_len_table[0x73]); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_authfail_fd(int fd, int type) { + if (!fd || !session[fd]) + return 0; + + WFIFOW(fd,0) = 0x81; + WFIFOL(fd,2) = type; + WFIFOSET(fd,packet_len_table[0x81]); + + clif_setwaitclose(fd); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_charselectok(int id) { + struct map_session_data *sd; + int fd; + + if ((sd = map_id2sd(id)) == NULL) + return 1; + + if (!sd->fd) + return 1; + + fd = sd->fd; + WFIFOW(fd,0) = 0xb3; + WFIFOB(fd,2) = 1; + WFIFOSET(fd,packet_len_table[0xb3]); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +static int clif_set009e(struct flooritem_data *fitem,unsigned char *buf) { + int view; + + nullpo_retr(0, fitem); + + //009e <ID>.l <name ID>.w <identify flag>.B <X>.w <Y>.w <subX>.B <subY>.B <amount>.w + WBUFW(buf, 0) = 0x9e; + WBUFL(buf, 2) = fitem->bl.id; + if ((view = itemdb_viewid(fitem->item_data.nameid)) > 0) + WBUFW(buf, 6) = view; + else + WBUFW(buf, 6) = fitem->item_data.nameid; + WBUFB(buf, 8) = fitem->item_data.identify; + WBUFW(buf, 9) = fitem->bl.x; + WBUFW(buf,11) = fitem->bl.y; + WBUFB(buf,13) = fitem->subx; + WBUFB(buf,14) = fitem->suby; + WBUFW(buf,15) = fitem->item_data.amount; + + return packet_len_table[0x9e]; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_dropflooritem(struct flooritem_data *fitem) { + char buf[64]; + + nullpo_retr(0, fitem); + + if (fitem->item_data.nameid <= 0) + return 0; + clif_set009e(fitem, buf); + clif_send(buf, packet_len_table[0x9e], &fitem->bl, AREA); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_clearflooritem(struct flooritem_data *fitem, int fd) { + unsigned char buf[16]; + + nullpo_retr(0, fitem); + + WBUFW(buf,0) = 0xa1; + WBUFL(buf,2) = fitem->bl.id; + + if (fd == 0) { + clif_send(buf, packet_len_table[0xa1], &fitem->bl, AREA); + } else { + memcpy(WFIFOP(fd,0), buf, 6); + WFIFOSET(fd,packet_len_table[0xa1]); + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_clearchar(struct block_list *bl, int type) { + unsigned char buf[16]; + + nullpo_retr(0, bl); + + WBUFW(buf,0) = 0x80; + WBUFL(buf,2) = bl->id; + if (type == 9) { + WBUFB(buf,6) = 0; + clif_send(buf, packet_len_table[0x80], bl, AREA); + } else { + WBUFB(buf,6) = type; + clif_send(buf, packet_len_table[0x80], bl, type == 1 ? AREA : AREA_WOS); + } + + return 0; +} + +static int clif_clearchar_delay_sub(int tid, unsigned int tick, int id, int data) { + struct block_list *bl = (struct block_list *)id; + + clif_clearchar(bl,data); + map_freeblock(bl); + + return 0; +} + +int clif_clearchar_delay(unsigned int tick, struct block_list *bl, int type) { + struct block_list *tmpbl = calloc(sizeof(struct block_list), 1); + if (tmpbl == NULL) { + printf("clif_clearchar_delay: out of memory !\n"); + exit(1); + } + memcpy(tmpbl, bl, sizeof(struct block_list)); + add_timer(tick, clif_clearchar_delay_sub, (int)tmpbl, type); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_clearchar_id(int id, int type, int fd) { + unsigned char buf[16]; + + WBUFW(buf,0) = 0x80; + WBUFL(buf,2) = id; + WBUFB(buf,6) = type; + memcpy(WFIFOP(fd,0), buf, 7); + WFIFOSET(fd, packet_len_table[0x80]); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +static int clif_set0078(struct map_session_data *sd, unsigned char *buf) { + int level=0; + + nullpo_retr(0, sd); + + if (sd->disguise > 23 && sd->disguise < 4001) { // mob disguises [Valaris] + WBUFW(buf,0) = 0x78; + WBUFL(buf,2) = sd->bl.id; + WBUFW(buf,6) = battle_get_speed(&sd->bl); + WBUFW(buf,8) = sd->opt1; + WBUFW(buf,10) = sd->opt2; + WBUFW(buf,12) = sd->status.option; + WBUFW(buf,14) = sd->disguise; + WBUFW(buf,42) = 0; + WBUFB(buf,44) = 0; + WBUFPOS(buf, 46, sd->bl.x, sd->bl.y); + WBUFB(buf,48) |= sd->dir & 0x0f; + WBUFB(buf,49) = 5; + WBUFB(buf,50) = 5; + WBUFB(buf,51) = 0; + WBUFW(buf,52) = ((level = battle_get_lv(&sd->bl)) > battle_config.max_lv) ? battle_config.max_lv : level; + + return packet_len_table[0x78]; + } + +#if PACKETVER < 4 + WBUFW(buf,0)= 0x78; + WBUFL(buf,2)= sd->bl.id; + WBUFW(buf,6)= sd->speed; + WBUFW(buf,8)= sd->opt1; + WBUFW(buf,10)= sd->opt2; + WBUFW(buf,12)= sd->status.option; + WBUFW(buf,14)= sd->view_class; + WBUFW(buf,16)= sd->status.hair; + if (sd->view_class != 22) + WBUFW(buf,18) = sd->status.weapon; + else + WBUFW(buf,18)=0; + WBUFW(buf,20)=sd->status.head_bottom; + WBUFW(buf,22)=sd->status.shield; + WBUFW(buf,24)=sd->status.head_top; + WBUFW(buf,26)=sd->status.head_mid; + WBUFW(buf,28)=sd->status.hair_color; + WBUFW(buf,30)=sd->status.clothes_color; + WBUFW(buf,32)=sd->head_dir; + WBUFL(buf,34)=sd->status.guild_id; + WBUFL(buf,38)=sd->guild_emblem_id; + WBUFW(buf,42)=sd->status.manner; + WBUFB(buf,44)=sd->status.karma; + WBUFB(buf,45)=sd->sex; + WBUFPOS(buf,46,sd->bl.x,sd->bl.y); + WBUFB(buf,48)|=sd->dir&0x0f; + WBUFB(buf,49)=5; + WBUFB(buf,50)=5; + WBUFB(buf,51)=sd->state.dead_sit; + WBUFW(buf,52)=(sd->status.base_level>battle_config.max_lv)?battle_config.max_lv:sd->status.base_level; + + return packet_len_table[0x78]; +#else + WBUFW(buf,0) = 0x1d8; + WBUFL(buf,2) = sd->bl.id; + WBUFW(buf,6) = sd->speed; + WBUFW(buf,8) = sd->opt1; + WBUFW(buf,10) = sd->opt2; + WBUFW(buf,12) = sd->status.option; + WBUFW(buf,14) = sd->view_class; + WBUFW(buf,16) = sd->status.hair; + if (sd->equip_index[9] >= 0 && sd->inventory_data[sd->equip_index[9]] && sd->view_class != 22) { + if (sd->inventory_data[sd->equip_index[9]]->view_id > 0) + WBUFW(buf,18) = sd->inventory_data[sd->equip_index[9]]->view_id; + else + WBUFW(buf,18) = sd->status.inventory[sd->equip_index[9]].nameid; + } else + WBUFW(buf,18) = 0; + if (sd->equip_index[8] >= 0 && sd->equip_index[8] != sd->equip_index[9] && sd->inventory_data[sd->equip_index[8]] && sd->view_class != 22) { + if (sd->inventory_data[sd->equip_index[8]]->view_id > 0) + WBUFW(buf,20) = sd->inventory_data[sd->equip_index[8]]->view_id; + else + WBUFW(buf,20) = sd->status.inventory[sd->equip_index[8]].nameid; + } else + WBUFW(buf,20) = 0; + WBUFW(buf,22)=sd->status.head_bottom; + WBUFW(buf,24)=sd->status.head_top; + WBUFW(buf,26)=sd->status.head_mid; + WBUFW(buf,28)=sd->status.hair_color; + WBUFW(buf,30)=sd->status.clothes_color; + WBUFW(buf,32)=sd->head_dir; + WBUFL(buf,34)=sd->status.guild_id; + WBUFW(buf,38)=sd->guild_emblem_id; + WBUFW(buf,40)=sd->status.manner; + WBUFW(buf,42)=sd->opt3; + WBUFB(buf,44)=sd->status.karma; + WBUFB(buf,45)=sd->sex; + WBUFPOS(buf,46,sd->bl.x,sd->bl.y); + WBUFB(buf,48)|=sd->dir&0x0f; + WBUFB(buf,49)=5; + WBUFB(buf,50)=5; + WBUFB(buf,51)=sd->state.dead_sit; + WBUFW(buf,52)=((level = battle_get_lv(&sd->bl)) > battle_config.max_lv) ? battle_config.max_lv : level; + + return packet_len_table[0x1d8]; +#endif +} + +/*========================================== + * + *------------------------------------------ + */ +static int clif_set007b(struct map_session_data *sd,unsigned char *buf) { + int level=0; + + nullpo_retr(0, sd); + + if (sd->disguise > 23 && sd->disguise < 4001) { // mob disguises [Valaris] + WBUFW(buf,0)=0x7b; + WBUFL(buf,2)=sd->bl.id; + WBUFW(buf,6)=battle_get_speed(&sd->bl); + WBUFW(buf,8)=sd->opt1; + WBUFW(buf,10)=sd->opt2; + WBUFW(buf,12)=sd->status.option; + WBUFW(buf,14)=sd->disguise; + WBUFL(buf,22)=gettick(); + WBUFW(buf,46)=0; + WBUFB(buf,48)=0; + WBUFPOS2(buf,50,sd->bl.x,sd->bl.y,sd->to_x,sd->to_y); + WBUFB(buf,55)=0; + WBUFB(buf,56)=5; + WBUFB(buf,57)=5; + WBUFW(buf,58)=((level = battle_get_lv(&sd->bl))>battle_config.max_lv)? battle_config.max_lv:level; + + return packet_len_table[0x7b]; + } + +#if PACKETVER < 4 + WBUFW(buf,0)=0x7b; + WBUFL(buf,2)=sd->bl.id; + WBUFW(buf,6)=sd->speed; + WBUFW(buf,8)=sd->opt1; + WBUFW(buf,10)=sd->opt2; + WBUFW(buf,12)=sd->status.option; + WBUFW(buf,14)=sd->view_class; + WBUFW(buf,16)=sd->status.hair; + if(sd->view_class != 22) + WBUFW(buf,18)=sd->status.weapon; + else + WBUFW(buf,18)=0; + WBUFW(buf,20)=sd->status.head_bottom; + WBUFL(buf,22)=gettick(); + WBUFW(buf,26)=sd->status.shield; + WBUFW(buf,28)=sd->status.head_top; + WBUFW(buf,30)=sd->status.head_mid; + WBUFW(buf,32)=sd->status.hair_color; + WBUFW(buf,34)=sd->status.clothes_color; + WBUFW(buf,36)=sd->head_dir; + WBUFL(buf,38)=sd->status.guild_id; + WBUFL(buf,42)=sd->guild_emblem_id; + WBUFW(buf,46)=sd->status.manner; + WBUFB(buf,48)=sd->status.karma; + WBUFB(buf,49)=sd->sex; + WBUFPOS2(buf,50,sd->bl.x,sd->bl.y,sd->to_x,sd->to_y); + WBUFB(buf,55)=0; + WBUFB(buf,56)=5; + WBUFB(buf,57)=5; + WBUFW(buf,58)=(sd->status.base_level>battle_config.max_lv)?battle_config.max_lv:sd->status.base_level; + + return packet_len_table[0x7b]; +#else + WBUFW(buf,0)=0x1da; + WBUFL(buf,2)=sd->bl.id; + WBUFW(buf,6)=sd->speed; + WBUFW(buf,8)=sd->opt1; + WBUFW(buf,10)=sd->opt2; + WBUFW(buf,12)=sd->status.option; + WBUFW(buf,14)=sd->view_class; + WBUFW(buf,16)=sd->status.hair; + if(sd->equip_index[9] >= 0 && sd->inventory_data[sd->equip_index[9]] && sd->view_class != 22) { + if(sd->inventory_data[sd->equip_index[9]]->view_id > 0) + WBUFW(buf,18)=sd->inventory_data[sd->equip_index[9]]->view_id; + else + WBUFW(buf,18)=sd->status.inventory[sd->equip_index[9]].nameid; + } + else + WBUFW(buf,18)=0; + if(sd->equip_index[8] >= 0 && sd->equip_index[8] != sd->equip_index[9] && sd->inventory_data[sd->equip_index[8]] && sd->view_class != 22) { + if(sd->inventory_data[sd->equip_index[8]]->view_id > 0) + WBUFW(buf,20)=sd->inventory_data[sd->equip_index[8]]->view_id; + else + WBUFW(buf,20)=sd->status.inventory[sd->equip_index[8]].nameid; + } + else + WBUFW(buf,20)=0; + WBUFW(buf,22)=sd->status.head_bottom; + WBUFL(buf,24)=gettick(); + WBUFW(buf,28)=sd->status.head_top; + WBUFW(buf,30)=sd->status.head_mid; + WBUFW(buf,32)=sd->status.hair_color; + WBUFW(buf,34)=sd->status.clothes_color; + WBUFW(buf,36)=sd->head_dir; + WBUFL(buf,38)=sd->status.guild_id; + WBUFW(buf,42)=sd->guild_emblem_id; + WBUFW(buf,44)=sd->status.manner; + WBUFW(buf,46)=sd->opt3; + WBUFB(buf,48)=sd->status.karma; + WBUFB(buf,49)=sd->sex; + WBUFPOS2(buf,50,sd->bl.x,sd->bl.y,sd->to_x,sd->to_y); + WBUFB(buf,55)=0; + WBUFB(buf,56)=5; + WBUFB(buf,57)=5; + WBUFW(buf,58)=(sd->status.base_level>battle_config.max_lv)?battle_config.max_lv:sd->status.base_level; + + return packet_len_table[0x1da]; +#endif +} + +/*========================================== + * クラスチェンジ typeはMobの場合は1で他は0? + *------------------------------------------ + */ +int clif_class_change(struct block_list *bl,int class,int type) +{ + char buf[16]; + + nullpo_retr(0, bl); + + if(class >= MAX_PC_CLASS) { + WBUFW(buf,0)=0x1b0; + WBUFL(buf,2)=bl->id; + WBUFB(buf,6)=type; + WBUFL(buf,7)=class; + + clif_send(buf,packet_len_table[0x1b0],bl,AREA); + } + return 0; +} +/*========================================== + * + *------------------------------------------ + */ +int clif_mob_class_change(struct mob_data *md, int class) { + char buf[16]; + int view = mob_get_viewclass(class); + + nullpo_retr(0, md); + + if(view >= MAX_PC_CLASS) { + WBUFW(buf,0)=0x1b0; + WBUFL(buf,2)=md->bl.id; + WBUFB(buf,6)=1; + WBUFL(buf,7)=view; + + clif_send(buf,packet_len_table[0x1b0],&md->bl,AREA); + } + return 0; +} +// mob equipment [Valaris] + +int clif_mob_equip(struct mob_data *md, int nameid) { + unsigned char buf[16]; + + nullpo_retr(0, md); + + memset(buf,0,packet_len_table[0x1a4]); + + WBUFW(buf,0)=0x1a4; + WBUFB(buf,2)=3; + WBUFL(buf,3)=md->bl.id; + WBUFL(buf,7)=nameid; + + clif_send(buf,packet_len_table[0x1a4],&md->bl,AREA); + + return 0; +} + +/*========================================== + * MOB表示1 + *------------------------------------------ + */ +static int clif_mob0078(struct mob_data *md, unsigned char *buf) +{ + int level; + + memset(buf,0,packet_len_table[0x78]); + + nullpo_retr(0, md); + + WBUFW(buf,0)=0x78; + WBUFL(buf,2)=md->bl.id; + WBUFW(buf,6)=battle_get_speed(&md->bl); + WBUFW(buf,8)=md->opt1; + WBUFW(buf,10)=md->opt2; + WBUFW(buf,12)=md->option; + WBUFW(buf,14)=mob_get_viewclass(md->class); + if((mob_get_viewclass(md->class) <= 23) || (mob_get_viewclass(md->class) == 812) || (mob_get_viewclass(md->class) >= 4001)) { + WBUFW(buf,12)|=mob_db[md->class].option; + WBUFW(buf,16)=mob_get_hair(md->class); + WBUFW(buf,18)=mob_get_weapon(md->class); + WBUFW(buf,20)=mob_get_head_buttom(md->class); + WBUFW(buf,22)=mob_get_shield(md->class); + WBUFW(buf,24)=mob_get_head_top(md->class); + WBUFW(buf,26)=mob_get_head_mid(md->class); + WBUFW(buf,28)=mob_get_hair_color(md->class); + WBUFW(buf,30)=mob_get_clothes_color(md->class); //Add for player monster dye - Valaris + WBUFB(buf,45)=mob_get_sex(md->class); + } + + if (md->class >= 1285 && md->class <= 1287) { // Added guardian emblems [Valaris] + struct guild *g; + struct guild_castle *gc=guild_mapname2gc(map[md->bl.m].name); + if (gc && gc->guild_id > 0) { + g=guild_search(gc->guild_id); + if (g) { + WBUFL(buf,26)=gc->guild_id; + WBUFL(buf,22)=g->emblem_id; + } + } + } // End addition + + WBUFPOS(buf,46,md->bl.x,md->bl.y); + WBUFB(buf,48)|=md->dir&0x0f; + WBUFB(buf,49)=5; + WBUFB(buf,50)=5; + WBUFW(buf,52)=((level = battle_get_lv(&md->bl))>battle_config.max_lv)? battle_config.max_lv:level; + + return packet_len_table[0x78]; +} + +/*========================================== + * MOB表示2 + *------------------------------------------ + */ +static int clif_mob007b(struct mob_data *md, unsigned char *buf) { + int level; + + memset(buf,0,packet_len_table[0x7b]); + + nullpo_retr(0, md); + + WBUFW(buf,0)=0x7b; + WBUFL(buf,2)=md->bl.id; + WBUFW(buf,6)=battle_get_speed(&md->bl); + WBUFW(buf,8)=md->opt1; + WBUFW(buf,10)=md->opt2; + WBUFW(buf,12)=md->option; + WBUFW(buf,14)=mob_get_viewclass(md->class); + if ((mob_get_viewclass(md->class) < 24) || (mob_get_viewclass(md->class) > 4000)) { + WBUFW(buf,12)|=mob_db[md->class].option; + WBUFW(buf,16)=mob_get_hair(md->class); + WBUFW(buf,18)=mob_get_weapon(md->class); + WBUFW(buf,20)=mob_get_head_buttom(md->class); + WBUFL(buf,22)=gettick(); + WBUFW(buf,26)=mob_get_shield(md->class); + WBUFW(buf,28)=mob_get_head_top(md->class); + WBUFW(buf,30)=mob_get_head_mid(md->class); + WBUFW(buf,32)=mob_get_hair_color(md->class); + WBUFW(buf,34)=mob_get_clothes_color(md->class); //Add for player monster dye - Valaris + WBUFB(buf,49)=mob_get_sex(md->class); + } else + WBUFL(buf,22)=gettick(); + + if(md->class >= 1285 && md->class <= 1287) { // Added guardian emblems [Valaris] + struct guild *g; + struct guild_castle *gc=guild_mapname2gc(map[md->bl.m].name); + if(gc && gc->guild_id > 0){ + g=guild_search(gc->guild_id); + if(g) { + WBUFL(buf,28)=gc->guild_id; + WBUFL(buf,24)=g->emblem_id; + } + } + } // End addition + + WBUFPOS2(buf,50,md->bl.x,md->bl.y,md->to_x,md->to_y); + WBUFB(buf,56)=5; + WBUFB(buf,57)=5; + WBUFW(buf,58)=((level = battle_get_lv(&md->bl))>battle_config.max_lv)? battle_config.max_lv:level; + + return packet_len_table[0x7b]; +} + +/*========================================== + * + *------------------------------------------ + */ +static int clif_npc0078(struct npc_data *nd, unsigned char *buf) { + struct guild *g; + + nullpo_retr(0, nd); + + memset(buf,0,packet_len_table[0x78]); + + WBUFW(buf,0)=0x78; + WBUFL(buf,2)=nd->bl.id; + WBUFW(buf,6)=nd->speed; + WBUFW(buf,14)=nd->class; + if ((nd->class == 722) && (nd->u.scr.guild_id > 0) && ((g=guild_search(nd->u.scr.guild_id)) != NULL)) { + WBUFL(buf,22)=g->emblem_id; + WBUFL(buf,26)=g->guild_id; + } + WBUFPOS(buf,46,nd->bl.x,nd->bl.y); + WBUFB(buf,48)|=nd->dir&0x0f; + WBUFB(buf,49)=5; + WBUFB(buf,50)=5; + + return packet_len_table[0x78]; +} + +/*========================================== + * + *------------------------------------------ + */ +static int clif_pet0078(struct pet_data *pd, unsigned char *buf) { + int view,level; + + nullpo_retr(0, pd); + + memset(buf,0,packet_len_table[0x78]); + + WBUFW(buf,0)=0x78; + WBUFL(buf,2)=pd->bl.id; + WBUFW(buf,6)=pd->speed; + WBUFW(buf,14)=mob_get_viewclass(pd->class); + if((mob_get_viewclass(pd->class) < 24) || (mob_get_viewclass(pd->class) > 4000)) { + WBUFW(buf,12)=mob_db[pd->class].option; + WBUFW(buf,16)=mob_get_hair(pd->class); + WBUFW(buf,18)=mob_get_weapon(pd->class); + WBUFW(buf,20)=mob_get_head_buttom(pd->class); + WBUFW(buf,22)=mob_get_shield(pd->class); + WBUFW(buf,24)=mob_get_head_top(pd->class); + WBUFW(buf,26)=mob_get_head_mid(pd->class); + WBUFW(buf,28)=mob_get_hair_color(pd->class); + WBUFW(buf,30)=mob_get_clothes_color(pd->class); //Add for player pet dye - Valaris + WBUFB(buf,45)=mob_get_sex(pd->class); + } else { + WBUFW(buf,16)=0x14; + if((view = itemdb_viewid(pd->equip)) > 0) + WBUFW(buf,20)=view; + else + WBUFW(buf,20)=pd->equip; + } + WBUFPOS(buf,46,pd->bl.x,pd->bl.y); + WBUFB(buf,48)|=pd->dir&0x0f; + WBUFB(buf,49)=0; + WBUFB(buf,50)=0; + WBUFW(buf,52)=((level = battle_get_lv(&pd->bl))>battle_config.max_lv)? battle_config.max_lv:level; + + return packet_len_table[0x78]; +} + +/*========================================== + * + *------------------------------------------ + */ +static int clif_pet007b(struct pet_data *pd, unsigned char *buf) { + int view,level; + + nullpo_retr(0, pd); + + memset(buf,0,packet_len_table[0x7b]); + + WBUFW(buf,0)=0x7b; + WBUFL(buf,2)=pd->bl.id; + WBUFW(buf,6)=pd->speed; + WBUFW(buf,14)=mob_get_viewclass(pd->class); + if((mob_get_viewclass(pd->class) < 24) || (mob_get_viewclass(pd->class) > 4000)) { + WBUFW(buf,12)=mob_db[pd->class].option; + WBUFW(buf,16)=mob_get_hair(pd->class); + WBUFW(buf,18)=mob_get_weapon(pd->class); + WBUFW(buf,20)=mob_get_head_buttom(pd->class); + WBUFL(buf,22)=gettick(); + WBUFW(buf,26)=mob_get_shield(pd->class); + WBUFW(buf,28)=mob_get_head_top(pd->class); + WBUFW(buf,30)=mob_get_head_mid(pd->class); + WBUFW(buf,32)=mob_get_hair_color(pd->class); + WBUFW(buf,34)=mob_get_clothes_color(pd->class); //Add for player pet dye - Valaris + WBUFB(buf,49)=mob_get_sex(pd->class); + } else { + WBUFW(buf,16)=0x14; + if ((view = itemdb_viewid(pd->equip)) > 0) + WBUFW(buf,20)=view; + else + WBUFW(buf,20)=pd->equip; + WBUFL(buf,22)=gettick(); + } + WBUFPOS2(buf,50,pd->bl.x,pd->bl.y,pd->to_x,pd->to_y); + WBUFB(buf,56)=0; + WBUFB(buf,57)=0; + WBUFW(buf,58)=((level = battle_get_lv(&pd->bl))>battle_config.max_lv)? battle_config.max_lv:level; + + return packet_len_table[0x7b]; +} + +/*========================================== + * + *------------------------------------------ + */ +static int clif_set01e1(struct map_session_data *sd, unsigned char *buf) { + nullpo_retr(0, sd); + + WBUFW(buf,0)=0x1e1; + WBUFL(buf,2)=sd->bl.id; + WBUFW(buf,6)=sd->spiritball; + + return packet_len_table[0x1e1]; +} + +/*========================================== + * + *------------------------------------------ + */ +static int clif_set0192(int fd, int m, int x, int y, int type) { + WFIFOW(fd,0) = 0x192; + WFIFOW(fd,2) = x; + WFIFOW(fd,4) = y; + WFIFOW(fd,6) = type; + memcpy(WFIFOP(fd,8),map[m].name,16); + WFIFOSET(fd,packet_len_table[0x192]); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_spawnpc(struct map_session_data *sd) { + unsigned char buf[128]; + + nullpo_retr(0, sd); + + if (sd->disguise > 23 && sd->disguise < 4001) { // mob disguises [Valaris] + clif_clearchar(&sd->bl, 9); + + memset(buf, 0, packet_len_table[0x119]); + + WBUFW(buf, 0) = 0x119; + WBUFL(buf, 2) = sd->bl.id; + WBUFW(buf, 6) = 0; + WBUFW(buf, 8) = 0; + WBUFW(buf,10) = 0x40; + WBUFB(buf,12) = 0; + + clif_send(buf, packet_len_table[0x119], &sd->bl, SELF); + + memset(buf, 0, packet_len_table[0x7c]); + + WBUFW(buf, 0) = 0x7c; + WBUFL(buf, 2) = sd->bl.id; + WBUFW(buf, 6) = sd->speed; + WBUFW(buf, 8) = sd->opt1; + WBUFW(buf,10) = sd->opt2; + WBUFW(buf,12) = sd->status.option; + WBUFW(buf,20) = sd->disguise; + WBUFPOS(buf, 36, sd->bl.x, sd->bl.y); + clif_send(buf, packet_len_table[0x7c], &sd->bl, AREA); + } + + clif_set0078(sd, buf); + +#if PACKETVER < 4 + WBUFW(buf, 0) = 0x79; + WBUFW(buf,51) = (sd->status.base_level > battle_config.max_lv) ? battle_config.max_lv : sd->status.base_level; + clif_send(buf, packet_len_table[0x79], &sd->bl, AREA_WOS); +#else + WBUFW(buf, 0) = 0x1d9; + WBUFW(buf,51) = (sd->status.base_level > battle_config.max_lv) ? battle_config.max_lv : sd->status.base_level; + clif_send(buf, packet_len_table[0x1d9], &sd->bl, AREA_WOS); +#endif + + + if (sd->spiritball > 0) + clif_spiritball(sd); + + if (sd->status.guild_id > 0) { // force display of guild emblem [Valaris] + struct guild *g = guild_search(sd->status.guild_id); + if (g) + clif_guild_emblem(sd,g); + } // end addition [Valaris] + + if (sd->status.class==13 || sd->status.class==21 || sd->status.class==4014 || sd->status.class==4022) + pc_setoption(sd,sd->status.option|0x0020); // [Valaris] + + if ((pc_isriding(sd) && pc_checkskill(sd,KN_RIDING)>0) && (sd->status.class==7 || + sd->status.class==14 || sd->status.class==4008 || sd->status.class==4015)) + pc_setriding(sd); // update peco riders for people upgrading athena [Valaris] + + + if (map[sd->bl.m].flag.snow) + clif_specialeffect(&sd->bl, 162, 1); + if (map[sd->bl.m].flag.fog) + clif_specialeffect(&sd->bl, 233, 1); + if (map[sd->bl.m].flag.sakura) + clif_specialeffect(&sd->bl, 163, 1); + if (map[sd->bl.m].flag.leaves) + clif_specialeffect(&sd->bl, 333, 1); + if (map[sd->bl.m].flag.rain) + clif_specialeffect(&sd->bl, 161, 1); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_spawnnpc(struct npc_data *nd) +{ + unsigned char buf[64]; + int len; + + nullpo_retr(0, nd); + + if(nd->class < 0 || nd->flag&1 || nd->class == INVISIBLE_CLASS) + return 0; + + memset(buf,0,packet_len_table[0x7c]); + + WBUFW(buf,0)=0x7c; + WBUFL(buf,2)=nd->bl.id; + WBUFW(buf,6)=nd->speed; + WBUFW(buf,20)=nd->class; + WBUFPOS(buf,36,nd->bl.x,nd->bl.y); + + clif_send(buf,packet_len_table[0x7c],&nd->bl,AREA); + + len = clif_npc0078(nd,buf); + clif_send(buf,len,&nd->bl,AREA); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_spawnmob(struct mob_data *md) +{ + unsigned char buf[64]; + int len; + + nullpo_retr(0, md); + + if (mob_get_viewclass(md->class) > 23 ) { + memset(buf,0,packet_len_table[0x7c]); + + WBUFW(buf,0)=0x7c; + WBUFL(buf,2)=md->bl.id; + WBUFW(buf,6)=md->speed; + WBUFW(buf,8)=md->opt1; + WBUFW(buf,10)=md->opt2; + WBUFW(buf,12)=md->option; + WBUFW(buf,20)=mob_get_viewclass(md->class); + WBUFPOS(buf,36,md->bl.x,md->bl.y); + clif_send(buf,packet_len_table[0x7c],&md->bl,AREA); + } + + len = clif_mob0078(md,buf); + clif_send(buf,len,&md->bl,AREA); + + if (mob_get_equip(md->class) > 0) // mob equipment [Valaris] + clif_mob_equip(md,mob_get_equip(md->class)); + + return 0; +} + +// pet + +/*========================================== + * + *------------------------------------------ + */ +int clif_spawnpet(struct pet_data *pd) +{ + unsigned char buf[64]; + int len; + + nullpo_retr(0, pd); + + if (mob_get_viewclass(pd->class) >= MAX_PC_CLASS) { + memset(buf,0,packet_len_table[0x7c]); + + WBUFW(buf,0)=0x7c; + WBUFL(buf,2)=pd->bl.id; + WBUFW(buf,6)=pd->speed; + WBUFW(buf,20)=mob_get_viewclass(pd->class); + WBUFPOS(buf,36,pd->bl.x,pd->bl.y); + + clif_send(buf,packet_len_table[0x7c],&pd->bl,AREA); + } + + len = clif_pet0078(pd,buf); + clif_send(buf,len,&pd->bl,AREA); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_movepet(struct pet_data *pd) { + unsigned char buf[256]; + int len; + + nullpo_retr(0, pd); + + len = clif_pet007b(pd,buf); + clif_send(buf,len,&pd->bl,AREA); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_servertick(struct map_session_data *sd) +{ + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd,0)=0x7f; + WFIFOL(fd,2)=sd->server_tick; + WFIFOSET(fd,packet_len_table[0x7f]); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_walkok(struct map_session_data *sd) +{ + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd,0)=0x87; + WFIFOL(fd,2)=gettick();; + WFIFOPOS2(fd,6,sd->bl.x,sd->bl.y,sd->to_x,sd->to_y); + WFIFOB(fd,11)=0; + WFIFOSET(fd,packet_len_table[0x87]); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_movechar(struct map_session_data *sd) { + int fd; + int len; + unsigned char buf[256]; + + nullpo_retr(0, sd); + + fd = sd->fd; + + len = clif_set007b(sd, buf); + + if (sd->disguise > 23 && sd->disguise < 4001) { + clif_send(buf, len, &sd->bl, AREA); + return 0; + } else + clif_send(buf, len, &sd->bl, AREA_WOS); + + if (battle_config.save_clothcolor == 1 && sd->status.clothes_color > 0) + clif_changelook(&sd->bl, LOOK_CLOTHES_COLOR, sd->status.clothes_color); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +void clif_quitsave(int fd,struct map_session_data *sd) +{ + map_quit(sd); +} + +/*========================================== + * + *------------------------------------------ + */ +static int clif_waitclose(int tid, unsigned int tick, int id, int data) { + if (session[id]) + session[id]->eof = 1; + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +void clif_setwaitclose(int fd) { + add_timer(gettick() + 5000, clif_waitclose, fd, 0); +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_changemap(struct map_session_data *sd, char *mapname, int x, int y) { + int fd; + + nullpo_retr(0, sd); + + fd = sd->fd; + + WFIFOW(fd,0) = 0x91; + memcpy(WFIFOP(fd,2), mapname, 16); + WFIFOW(fd,18) = x; + WFIFOW(fd,20) = y; + WFIFOSET(fd, packet_len_table[0x91]); + + if (sd->disguise > 23 && sd->disguise < 4001) // mob disguises [Valaris] + clif_spawnpc(sd); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_changemapserver(struct map_session_data *sd, char *mapname, int x, int y, int ip, int port) { + int fd; + + nullpo_retr(0, sd); + + fd = sd->fd; + WFIFOW(fd,0) = 0x92; + memcpy(WFIFOP(fd,2), mapname, 16); + WFIFOW(fd,18) = x; + WFIFOW(fd,20) = y; + WFIFOL(fd,22) = ip; + WFIFOW(fd,26) = port; + WFIFOSET(fd, packet_len_table[0x92]); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_fixpos(struct block_list *bl) { + char buf[16]; + + nullpo_retr(0, bl); + + WBUFW(buf,0)=0x88; + WBUFL(buf,2)=bl->id; + WBUFW(buf,6)=bl->x; + WBUFW(buf,8)=bl->y; + + clif_send(buf, packet_len_table[0x88], bl, AREA); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_npcbuysell(struct map_session_data* sd, int id) { + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd,0)=0xc4; + WFIFOL(fd,2)=id; + WFIFOSET(fd,packet_len_table[0xc4]); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_buylist(struct map_session_data *sd, struct npc_data *nd) { + struct item_data *id; + int fd,i,val; + + nullpo_retr(0, sd); + nullpo_retr(0, nd); + + fd=sd->fd; + WFIFOW(fd,0)=0xc6; + for(i=0;nd->u.shop_item[i].nameid > 0;i++){ + id = itemdb_search(nd->u.shop_item[i].nameid); + val=nd->u.shop_item[i].value; + WFIFOL(fd,4+i*11)=val; + if (!id->flag.value_notdc) + val=pc_modifybuyvalue(sd,val); + WFIFOL(fd,8+i*11)=val; + WFIFOB(fd,12+i*11)=id->type; + if (id->view_id > 0) + WFIFOW(fd,13+i*11)=id->view_id; + else + WFIFOW(fd,13+i*11)=nd->u.shop_item[i].nameid; + } + WFIFOW(fd,2)=i*11+4; + WFIFOSET(fd,WFIFOW(fd,2)); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_selllist(struct map_session_data *sd) { + int fd,i,c=0,val; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd,0)=0xc7; + for(i=0;i<MAX_INVENTORY;i++) { + if(sd->status.inventory[i].nameid > 0 && sd->inventory_data[i]) { + val=sd->inventory_data[i]->value_sell; + if (val < 0) + continue; + WFIFOW(fd,4+c*10)=i+2; + WFIFOL(fd,6+c*10)=val; + if (!sd->inventory_data[i]->flag.value_notoc) + val=pc_modifysellvalue(sd,val); + WFIFOL(fd,10+c*10)=val; + c++; + } + } + WFIFOW(fd,2)=c*10+4; + WFIFOSET(fd,WFIFOW(fd,2)); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_scriptmes(struct map_session_data *sd, int npcid, char *mes) { + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd,0)=0xb4; + WFIFOW(fd,2)=strlen(mes)+9; + WFIFOL(fd,4)=npcid; + strcpy(WFIFOP(fd,8),mes); + WFIFOSET(fd,WFIFOW(fd,2)); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_scriptnext(struct map_session_data *sd,int npcid) { + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd,0)=0xb5; + WFIFOL(fd,2)=npcid; + WFIFOSET(fd,packet_len_table[0xb5]); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_scriptclose(struct map_session_data *sd, int npcid) { + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd,0)=0xb6; + WFIFOL(fd,2)=npcid; + WFIFOSET(fd,packet_len_table[0xb6]); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_scriptmenu(struct map_session_data *sd, int npcid, char *mes) { + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd,0)=0xb7; + WFIFOW(fd,2)=strlen(mes)+8; + WFIFOL(fd,4)=npcid; + strcpy(WFIFOP(fd,8),mes); + WFIFOSET(fd,WFIFOW(fd,2)); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_scriptinput(struct map_session_data *sd, int npcid) { + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd,0)=0x142; + WFIFOL(fd,2)=npcid; + WFIFOSET(fd,packet_len_table[0x142]); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_scriptinputstr(struct map_session_data *sd, int npcid) { + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd,0)=0x1d4; + WFIFOL(fd,2)=npcid; + WFIFOSET(fd,packet_len_table[0x1d4]); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_viewpoint(struct map_session_data *sd, int npc_id, int type, int x, int y, int id, int color) { + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd,0)=0x144; + WFIFOL(fd,2)=npc_id; + WFIFOL(fd,6)=type; + WFIFOL(fd,10)=x; + WFIFOL(fd,14)=y; + WFIFOB(fd,18)=id; + WFIFOL(fd,19)=color; + WFIFOSET(fd,packet_len_table[0x144]); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_cutin(struct map_session_data *sd, char *image, int type) { + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd,0)=0x1b3; + memcpy(WFIFOP(fd,2),image,64); + WFIFOB(fd,66)=type; + WFIFOSET(fd,packet_len_table[0x1b3]); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_additem(struct map_session_data *sd, int n, int amount, int fail) { + int fd,j; + unsigned char *buf; + + nullpo_retr(0, sd); + + fd=sd->fd; + buf=WFIFOP(fd,0); + if(fail) { + WBUFW(buf,0)=0xa0; + WBUFW(buf,2)=n+2; + WBUFW(buf,4)=amount; + WBUFW(buf,6)=0; + WBUFB(buf,8)=0; + WBUFB(buf,9)=0; + WBUFB(buf,10)=0; + WBUFW(buf,11)=0; + WBUFW(buf,13)=0; + WBUFW(buf,15)=0; + WBUFW(buf,17)=0; + WBUFW(buf,19)=0; + WBUFB(buf,21)=0; + WBUFB(buf,22)=fail; + } else { + if (n<0 || n>=MAX_INVENTORY || sd->status.inventory[n].nameid <=0 || sd->inventory_data[n] == NULL) + return 1; + + WBUFW(buf,0)=0xa0; + WBUFW(buf,2)=n+2; + WBUFW(buf,4)=amount; + if (sd->inventory_data[n]->view_id > 0) + WBUFW(buf,6)=sd->inventory_data[n]->view_id; + else + WBUFW(buf,6)=sd->status.inventory[n].nameid; + WBUFB(buf,8)=sd->status.inventory[n].identify; + if (sd->status.inventory[n].broken==1) + WBUFB(buf,9)=1; // is weapon broken [Valaris] + else + WBUFB(buf,9)=sd->status.inventory[n].attribute; + WBUFB(buf,10)=sd->status.inventory[n].refine; + if(sd->status.inventory[n].card[0]==0x00ff || sd->status.inventory[n].card[0]==0x00fe || sd->status.inventory[n].card[0]==(short)0xff00) { + WBUFW(buf,11)=sd->status.inventory[n].card[0]; + WBUFW(buf,13)=sd->status.inventory[n].card[1]; + WBUFW(buf,15)=sd->status.inventory[n].card[2]; + WBUFW(buf,17)=sd->status.inventory[n].card[3]; + } else { + if (sd->status.inventory[n].card[0] > 0 && (j=itemdb_viewid(sd->status.inventory[n].card[0])) > 0) + WBUFW(buf,11)=j; + else + WBUFW(buf,11)=sd->status.inventory[n].card[0]; + if (sd->status.inventory[n].card[1] > 0 && (j=itemdb_viewid(sd->status.inventory[n].card[1])) > 0) + WBUFW(buf,13)=j; + else + WBUFW(buf,13)=sd->status.inventory[n].card[1]; + if (sd->status.inventory[n].card[2] > 0 && (j=itemdb_viewid(sd->status.inventory[n].card[2])) > 0) + WBUFW(buf,15)=j; + else + WBUFW(buf,15)=sd->status.inventory[n].card[2]; + if (sd->status.inventory[n].card[3] > 0 && (j=itemdb_viewid(sd->status.inventory[n].card[3])) > 0) + WBUFW(buf,17)=j; + else + WBUFW(buf,17)=sd->status.inventory[n].card[3]; + } + WBUFW(buf,19)=pc_equippoint(sd,n); + WBUFB(buf,21)=(sd->inventory_data[n]->type == 7)? 4:sd->inventory_data[n]->type; + WBUFB(buf,22)=fail; + } + + WFIFOSET(fd,packet_len_table[0xa0]); + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_delitem(struct map_session_data *sd,int n,int amount) +{ + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd,0)=0xaf; + WFIFOW(fd,2)=n+2; + WFIFOW(fd,4)=amount; + + WFIFOSET(fd,packet_len_table[0xaf]); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_itemlist(struct map_session_data *sd) +{ + int i,n,fd,arrow=-1; + unsigned char *buf; + + nullpo_retr(0, sd); + + fd=sd->fd; + buf = WFIFOP(fd,0); +#if PACKETVER < 5 + WBUFW(buf,0)=0xa3; + for(i=0,n=0;i<MAX_INVENTORY;i++){ + if (sd->status.inventory[i].nameid <=0 || sd->inventory_data[i] == NULL || itemdb_isequip2(sd->inventory_data[i])) + continue; + WBUFW(buf,n*10+4)=i+2; + if (sd->inventory_data[i]->view_id > 0) + WBUFW(buf,n*10+6)=sd->inventory_data[i]->view_id; + else + WBUFW(buf,n*10+6)=sd->status.inventory[i].nameid; + WBUFB(buf,n*10+8)=sd->inventory_data[i]->type; + WBUFB(buf,n*10+9)=sd->status.inventory[i].identify; + WBUFW(buf,n*10+10)=sd->status.inventory[i].amount; + if (sd->inventory_data[i]->equip == 0x8000) { + WBUFW(buf,n*10+12)=0x8000; + if (sd->status.inventory[i].equip) + arrow=i; // ついでに矢装備チェック + } else + WBUFW(buf,n*10+12)=0; + n++; + } + if (n) { + WBUFW(buf,2)=4+n*10; + WFIFOSET(fd,WFIFOW(fd,2)); + } +#else + WBUFW(buf,0)=0x1ee; + for(i=0,n=0;i<MAX_INVENTORY;i++){ + if(sd->status.inventory[i].nameid <=0 || sd->inventory_data[i] == NULL || itemdb_isequip2(sd->inventory_data[i])) + continue; + WBUFW(buf,n*18+4)=i+2; + if(sd->inventory_data[i]->view_id > 0) + WBUFW(buf,n*18+6)=sd->inventory_data[i]->view_id; + else + WBUFW(buf,n*18+6)=sd->status.inventory[i].nameid; + WBUFB(buf,n*18+8)=sd->inventory_data[i]->type; + WBUFB(buf,n*18+9)=sd->status.inventory[i].identify; + WBUFW(buf,n*18+10)=sd->status.inventory[i].amount; + if (sd->inventory_data[i]->equip == 0x8000) { + WBUFW(buf,n*18+12)=0x8000; + if(sd->status.inventory[i].equip) + arrow=i; // ついでに矢装備チェック + } else + WBUFW(buf,n*18+12)=0; + WBUFW(buf,n*18+14)=sd->status.inventory[i].card[0]; + WBUFW(buf,n*18+16)=sd->status.inventory[i].card[1]; + WBUFW(buf,n*18+18)=sd->status.inventory[i].card[2]; + WBUFW(buf,n*18+20)=sd->status.inventory[i].card[3]; + n++; + } + if (n) { + WBUFW(buf,2)=4+n*18; + WFIFOSET(fd,WFIFOW(fd,2)); + } +#endif + if(arrow >= 0) + clif_arrowequip(sd,arrow); + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_equiplist(struct map_session_data *sd) +{ + int i,j,n,fd; + unsigned char *buf; + + nullpo_retr(0, sd); + + fd=sd->fd; + buf = WFIFOP(fd,0); + WBUFW(buf,0)=0xa4; + for(i=0,n=0;i<MAX_INVENTORY;i++){ + if(sd->status.inventory[i].nameid<=0 || sd->inventory_data[i] == NULL || !itemdb_isequip2(sd->inventory_data[i])) + continue; + WBUFW(buf,n*20+4)=i+2; + if(sd->inventory_data[i]->view_id > 0) + WBUFW(buf,n*20+6)=sd->inventory_data[i]->view_id; + else + WBUFW(buf,n*20+6)=sd->status.inventory[i].nameid; + WBUFB(buf,n*20+8)=(sd->inventory_data[i]->type == 7)? 4:sd->inventory_data[i]->type; + WBUFB(buf,n*20+9)=sd->status.inventory[i].identify; + WBUFW(buf,n*20+10)=pc_equippoint(sd,i); + WBUFW(buf,n*20+12)=sd->status.inventory[i].equip; + if(sd->status.inventory[i].broken==1) + WBUFB(buf,n*20+14)=1; // is weapon broken [Valaris] + else + WBUFB(buf,n*20+14)=sd->status.inventory[i].attribute; + WBUFB(buf,n*20+15)=sd->status.inventory[i].refine; + if(sd->status.inventory[i].card[0]==0x00ff || sd->status.inventory[i].card[0]==0x00fe || sd->status.inventory[i].card[0]==(short)0xff00) { + WBUFW(buf,n*20+16)=sd->status.inventory[i].card[0]; + WBUFW(buf,n*20+18)=sd->status.inventory[i].card[1]; + WBUFW(buf,n*20+20)=sd->status.inventory[i].card[2]; + WBUFW(buf,n*20+22)=sd->status.inventory[i].card[3]; + } else { + if(sd->status.inventory[i].card[0] > 0 && (j=itemdb_viewid(sd->status.inventory[i].card[0])) > 0) + WBUFW(buf,n*20+16)=j; + else + WBUFW(buf,n*20+16)=sd->status.inventory[i].card[0]; + if(sd->status.inventory[i].card[1] > 0 && (j=itemdb_viewid(sd->status.inventory[i].card[1])) > 0) + WBUFW(buf,n*20+18)=j; + else + WBUFW(buf,n*20+18)=sd->status.inventory[i].card[1]; + if(sd->status.inventory[i].card[2] > 0 && (j=itemdb_viewid(sd->status.inventory[i].card[2])) > 0) + WBUFW(buf,n*20+20)=j; + else + WBUFW(buf,n*20+20)=sd->status.inventory[i].card[2]; + if(sd->status.inventory[i].card[3] > 0 && (j=itemdb_viewid(sd->status.inventory[i].card[3])) > 0) + WBUFW(buf,n*20+22)=j; + else + WBUFW(buf,n*20+22)=sd->status.inventory[i].card[3]; + } + n++; + } + if(n){ + WBUFW(buf,2)=4+n*20; + WFIFOSET(fd,WFIFOW(fd,2)); + } + return 0; +} + +/*========================================== + * カプラさんに預けてある消耗品&収集品リスト + *------------------------------------------ + */ +int clif_storageitemlist(struct map_session_data *sd,struct storage *stor) +{ + struct item_data *id; + int i,n,fd; + unsigned char *buf; + + nullpo_retr(0, sd); + nullpo_retr(0, stor); + + fd=sd->fd; + buf = WFIFOP(fd,0); +#if PACKETVER < 5 + WBUFW(buf,0)=0xa5; + for(i=0,n=0;i<MAX_STORAGE;i++){ + if(stor->storage[i].nameid<=0) + continue; + nullpo_retr(0, id = itemdb_search(stor->storage[i].nameid)); + if(itemdb_isequip2(id)) + continue; + + WBUFW(buf,n*10+4)=i+1; + if(id->view_id > 0) + WBUFW(buf,n*10+6)=id->view_id; + else + WBUFW(buf,n*10+6)=stor->storage[i].nameid; + WBUFB(buf,n*10+8)=id->type;; + WBUFB(buf,n*10+9)=stor->storage[i].identify; + WBUFW(buf,n*10+10)=stor->storage[i].amount; + WBUFW(buf,n*10+12)=0; + n++; + } + if(n){ + WBUFW(buf,2)=4+n*10; + WFIFOSET(fd,WFIFOW(fd,2)); + } +#else + WBUFW(buf,0)=0x1f0; + for(i=0,n=0;i<MAX_STORAGE;i++){ + if(stor->storage[i].nameid<=0) + continue; + nullpo_retr(0, id = itemdb_search(stor->storage[i].nameid)); + if(itemdb_isequip2(id)) + continue; + + WBUFW(buf,n*18+4)=i+1; + if(id->view_id > 0) + WBUFW(buf,n*18+6)=id->view_id; + else + WBUFW(buf,n*18+6)=stor->storage[i].nameid; + WBUFB(buf,n*18+8)=id->type;; + WBUFB(buf,n*18+9)=stor->storage[i].identify; + WBUFW(buf,n*18+10)=stor->storage[i].amount; + WBUFW(buf,n*18+12)=0; + WBUFW(buf,n*18+14)=stor->storage[i].card[0]; + WBUFW(buf,n*18+16)=stor->storage[i].card[1]; + WBUFW(buf,n*18+18)=stor->storage[i].card[2]; + WBUFW(buf,n*18+20)=stor->storage[i].card[3]; + n++; + } + if(n){ + WBUFW(buf,2)=4+n*18; + WFIFOSET(fd,WFIFOW(fd,2)); + } +#endif + return 0; +} + +/*========================================== + * カプラさんに預けてある装備リスト + *------------------------------------------ + */ +int clif_storageequiplist(struct map_session_data *sd,struct storage *stor) +{ + struct item_data *id; + int i,j,n,fd; + unsigned char *buf; + + nullpo_retr(0, sd); + nullpo_retr(0, stor); + + fd=sd->fd; + buf = WFIFOP(fd,0); + WBUFW(buf,0)=0xa6; + for(i=0,n=0;i<MAX_STORAGE;i++){ + if(stor->storage[i].nameid<=0) + continue; + nullpo_retr(0, id = itemdb_search(stor->storage[i].nameid)); + if(!itemdb_isequip2(id)) + continue; + WBUFW(buf,n*20+4)=i+1; + if(id->view_id > 0) + WBUFW(buf,n*20+6)=id->view_id; + else + WBUFW(buf,n*20+6)=stor->storage[i].nameid; + WBUFB(buf,n*20+8)=id->type; + WBUFB(buf,n*20+9)=stor->storage[i].identify; + WBUFW(buf,n*20+10)=id->equip; + WBUFW(buf,n*20+12)=stor->storage[i].equip; + if(stor->storage[i].broken==1) + WBUFB(buf,n*20+14)=1; //is weapon broken [Valaris] + else + WBUFB(buf,n*20+14)=stor->storage[i].attribute; + WBUFB(buf,n*20+15)=stor->storage[i].refine; + if(stor->storage[i].card[0]==0x00ff || stor->storage[i].card[0]==0x00fe || stor->storage[i].card[0]==(short)0xff00) { + WBUFW(buf,n*20+16)=stor->storage[i].card[0]; + WBUFW(buf,n*20+18)=stor->storage[i].card[1]; + WBUFW(buf,n*20+20)=stor->storage[i].card[2]; + WBUFW(buf,n*20+22)=stor->storage[i].card[3]; + } else { + if(stor->storage[i].card[0] > 0 && (j=itemdb_viewid(stor->storage[i].card[0])) > 0) + WBUFW(buf,n*20+16)=j; + else + WBUFW(buf,n*20+16)=stor->storage[i].card[0]; + if(stor->storage[i].card[1] > 0 && (j=itemdb_viewid(stor->storage[i].card[1])) > 0) + WBUFW(buf,n*20+18)=j; + else + WBUFW(buf,n*20+18)=stor->storage[i].card[1]; + if(stor->storage[i].card[2] > 0 && (j=itemdb_viewid(stor->storage[i].card[2])) > 0) + WBUFW(buf,n*20+20)=j; + else + WBUFW(buf,n*20+20)=stor->storage[i].card[2]; + if(stor->storage[i].card[3] > 0 && (j=itemdb_viewid(stor->storage[i].card[3])) > 0) + WBUFW(buf,n*20+22)=j; + else + WBUFW(buf,n*20+22)=stor->storage[i].card[3]; + } + n++; + } + if(n){ + WBUFW(buf,2)=4+n*20; + WFIFOSET(fd,WFIFOW(fd,2)); + } + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_guildstorageitemlist(struct map_session_data *sd,struct guild_storage *stor) +{ + struct item_data *id; + int i,n,fd; + unsigned char *buf; + + nullpo_retr(0, sd); + nullpo_retr(0, stor); + + fd=sd->fd; + buf=WFIFOP(fd,0); + +#if PACKETVER < 5 + WBUFW(buf,0)=0xa5; + for(i=0,n=0;i<MAX_GUILD_STORAGE;i++){ + if(stor->storage[i].nameid<=0) + continue; + nullpo_retr(0, id = itemdb_search(stor->storage[i].nameid)); + if(itemdb_isequip2(id)) + continue; + + WBUFW(buf,n*10+4)=i+1; + if(id->view_id > 0) + WBUFW(buf,n*10+6)=id->view_id; + else + WBUFW(buf,n*10+6)=stor->storage[i].nameid; + WBUFB(buf,n*10+8)=id->type;; + WBUFB(buf,n*10+9)=stor->storage[i].identify; + WBUFW(buf,n*10+10)=stor->storage[i].amount; + WBUFW(buf,n*10+12)=0; + n++; + } + if(n){ + WBUFW(buf,2)=4+n*10; + WFIFOSET(fd,WFIFOW(fd,2)); + } +#else + WBUFW(buf,0)=0x1f0; + for(i=0,n=0;i<MAX_GUILD_STORAGE;i++){ + if(stor->storage[i].nameid<=0) + continue; + nullpo_retr(0, id = itemdb_search(stor->storage[i].nameid)); + if(itemdb_isequip2(id)) + continue; + + WBUFW(buf,n*18+4)=i+1; + if(id->view_id > 0) + WBUFW(buf,n*18+6)=id->view_id; + else + WBUFW(buf,n*18+6)=stor->storage[i].nameid; + WBUFB(buf,n*18+8)=id->type;; + WBUFB(buf,n*18+9)=stor->storage[i].identify; + WBUFW(buf,n*18+10)=stor->storage[i].amount; + WBUFW(buf,n*18+12)=0; + WBUFW(buf,n*18+14)=stor->storage[i].card[0]; + WBUFW(buf,n*18+16)=stor->storage[i].card[1]; + WBUFW(buf,n*18+18)=stor->storage[i].card[2]; + WBUFW(buf,n*18+20)=stor->storage[i].card[3]; + n++; + } + if(n){ + WBUFW(buf,2)=4+n*18; + WFIFOSET(fd,WFIFOW(fd,2)); + } +#endif + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_guildstorageequiplist(struct map_session_data *sd,struct guild_storage *stor) +{ + struct item_data *id; + int i,j,n,fd; + unsigned char *buf; + + nullpo_retr(0, sd); + + fd=sd->fd; + buf=WFIFOP(fd,0); + + WBUFW(buf,0)=0xa6; + for(i=0,n=0;i<MAX_GUILD_STORAGE;i++){ + if(stor->storage[i].nameid<=0) + continue; + nullpo_retr(0, id = itemdb_search(stor->storage[i].nameid)); + if(!itemdb_isequip2(id)) + continue; + WBUFW(buf,n*20+4)=i+1; + if(id->view_id > 0) + WBUFW(buf,n*20+6)=id->view_id; + else + WBUFW(buf,n*20+6)=stor->storage[i].nameid; + WBUFB(buf,n*20+8)=id->type; + WBUFB(buf,n*20+9)=stor->storage[i].identify; + WBUFW(buf,n*20+10)=id->equip; + WBUFW(buf,n*20+12)=stor->storage[i].equip; + if(stor->storage[i].broken==1) + WBUFB(buf,n*20+14)=1; // is weapon broken [Valaris] + else + WBUFB(buf,n*20+14)=stor->storage[i].attribute; + WBUFB(buf,n*20+15)=stor->storage[i].refine; + if(stor->storage[i].card[0]==0x00ff || stor->storage[i].card[0]==0x00fe || stor->storage[i].card[0]==(short)0xff00) { + WBUFW(buf,n*20+16)=stor->storage[i].card[0]; + WBUFW(buf,n*20+18)=stor->storage[i].card[1]; + WBUFW(buf,n*20+20)=stor->storage[i].card[2]; + WBUFW(buf,n*20+22)=stor->storage[i].card[3]; + } else { + if(stor->storage[i].card[0] > 0 && (j=itemdb_viewid(stor->storage[i].card[0])) > 0) + WBUFW(buf,n*20+16)=j; + else + WBUFW(buf,n*20+16)=stor->storage[i].card[0]; + if(stor->storage[i].card[1] > 0 && (j=itemdb_viewid(stor->storage[i].card[1])) > 0) + WBUFW(buf,n*20+18)=j; + else + WBUFW(buf,n*20+18)=stor->storage[i].card[1]; + if(stor->storage[i].card[2] > 0 && (j=itemdb_viewid(stor->storage[i].card[2])) > 0) + WBUFW(buf,n*20+20)=j; + else + WBUFW(buf,n*20+20)=stor->storage[i].card[2]; + if(stor->storage[i].card[3] > 0 && (j=itemdb_viewid(stor->storage[i].card[3])) > 0) + WBUFW(buf,n*20+22)=j; + else + WBUFW(buf,n*20+22)=stor->storage[i].card[3]; + } + n++; + } + if(n){ + WBUFW(buf,2)=4+n*20; + WFIFOSET(fd,WFIFOW(fd,2)); + } + return 0; +} + +/*========================================== + * ステータスを送りつける + * 表示専用数字はこの中で計算して送る + *------------------------------------------ + */ +int clif_updatestatus(struct map_session_data *sd,int type) +{ + int fd,len=8; + + nullpo_retr(0, sd); + + fd=sd->fd; + + WFIFOW(fd,0)=0xb0; + WFIFOW(fd,2)=type; + switch(type){ + // 00b0 + case SP_WEIGHT: + pc_checkweighticon(sd); + WFIFOW(fd,0)=0xb0; + WFIFOW(fd,2)=type; + WFIFOL(fd,4)=sd->weight; + break; + case SP_MAXWEIGHT: + WFIFOL(fd,4)=sd->max_weight; + break; + case SP_SPEED: + WFIFOL(fd,4)=sd->speed; + break; + case SP_BASELEVEL: + WFIFOL(fd,4)=sd->status.base_level; + break; + case SP_JOBLEVEL: + WFIFOL(fd,4)=sd->status.job_level; + break; + case SP_MANNER: + WFIFOL(fd,4)=sd->status.manner; + clif_changestatus(&sd->bl,SP_MANNER,sd->status.manner); + break; + case SP_STATUSPOINT: + WFIFOL(fd,4)=sd->status.status_point; + break; + case SP_SKILLPOINT: + WFIFOL(fd,4)=sd->status.skill_point; + break; + case SP_HIT: + WFIFOL(fd,4)=sd->hit; + break; + case SP_FLEE1: + WFIFOL(fd,4)=sd->flee; + break; + case SP_FLEE2: + WFIFOL(fd,4)=sd->flee2/10; + break; + case SP_MAXHP: + WFIFOL(fd,4)=sd->status.max_hp; + break; + case SP_MAXSP: + WFIFOL(fd,4)=sd->status.max_sp; + break; + case SP_HP: + WFIFOL(fd,4)=sd->status.hp; + break; + case SP_SP: + WFIFOL(fd,4)=sd->status.sp; + break; + case SP_ASPD: + WFIFOL(fd,4)=sd->aspd; + break; + case SP_ATK1: + WFIFOL(fd,4)=sd->base_atk+sd->watk; + break; + case SP_DEF1: + WFIFOL(fd,4)=sd->def; + break; + case SP_MDEF1: + WFIFOL(fd,4)=sd->mdef; + break; + case SP_ATK2: + WFIFOL(fd,4)=sd->watk2; + break; + case SP_DEF2: + WFIFOL(fd,4)=sd->def2; + break; + case SP_MDEF2: + WFIFOL(fd,4)=sd->mdef2; + break; + case SP_CRITICAL: + WFIFOL(fd,4)=sd->critical/10; + break; + case SP_MATK1: + WFIFOL(fd,4)=sd->matk1; + break; + case SP_MATK2: + WFIFOL(fd,4)=sd->matk2; + break; + + + case SP_ZENY: + WFIFOW(fd,0)=0xb1; + if(sd->status.zeny < 0) + sd->status.zeny = 0; + WFIFOL(fd,4)=sd->status.zeny; + break; + case SP_BASEEXP: + WFIFOW(fd,0)=0xb1; + WFIFOL(fd,4)=sd->status.base_exp; + break; + case SP_JOBEXP: + WFIFOW(fd,0)=0xb1; + WFIFOL(fd,4)=sd->status.job_exp; + break; + case SP_NEXTBASEEXP: + WFIFOW(fd,0)=0xb1; + WFIFOL(fd,4)=pc_nextbaseexp(sd); + break; + case SP_NEXTJOBEXP: + WFIFOW(fd,0)=0xb1; + WFIFOL(fd,4)=pc_nextjobexp(sd); + break; + + // 00be 終了 + case SP_USTR: + case SP_UAGI: + case SP_UVIT: + case SP_UINT: + case SP_UDEX: + case SP_ULUK: + WFIFOW(fd,0)=0xbe; + WFIFOB(fd,4)=pc_need_status_point(sd,type-SP_USTR+SP_STR); + len=5; + break; + + // 013a 終了 + case SP_ATTACKRANGE: + WFIFOW(fd,0)=0x13a; + WFIFOW(fd,2)=sd->attackrange; + len=4; + break; + + // 0141 終了 + case SP_STR: + WFIFOW(fd,0)=0x141; + WFIFOL(fd,2)=type; + WFIFOL(fd,6)=sd->status.str; + WFIFOL(fd,10)=sd->paramb[0] + sd->parame[0]; + len=14; + break; + case SP_AGI: + WFIFOW(fd,0)=0x141; + WFIFOL(fd,2)=type; + WFIFOL(fd,6)=sd->status.agi; + WFIFOL(fd,10)=sd->paramb[1] + sd->parame[1]; + len=14; + break; + case SP_VIT: + WFIFOW(fd,0)=0x141; + WFIFOL(fd,2)=type; + WFIFOL(fd,6)=sd->status.vit; + WFIFOL(fd,10)=sd->paramb[2] + sd->parame[2]; + len=14; + break; + case SP_INT: + WFIFOW(fd,0)=0x141; + WFIFOL(fd,2)=type; + WFIFOL(fd,6)=sd->status.int_; + WFIFOL(fd,10)=sd->paramb[3] + sd->parame[3]; + len=14; + break; + case SP_DEX: + WFIFOW(fd,0)=0x141; + WFIFOL(fd,2)=type; + WFIFOL(fd,6)=sd->status.dex; + WFIFOL(fd,10)=sd->paramb[4] + sd->parame[4]; + len=14; + break; + case SP_LUK: + WFIFOW(fd,0)=0x141; + WFIFOL(fd,2)=type; + WFIFOL(fd,6)=sd->status.luk; + WFIFOL(fd,10)=sd->paramb[5] + sd->parame[5]; + len=14; + break; + + case SP_CARTINFO: + WFIFOW(fd,0)=0x121; + WFIFOW(fd,2)=sd->cart_num; + WFIFOW(fd,4)=sd->cart_max_num; + WFIFOL(fd,6)=sd->cart_weight; + WFIFOL(fd,10)=sd->cart_max_weight; + len=14; + break; + + default: + if(battle_config.error_log) + printf("clif_updatestatus : make %d routine\n",type); + return 1; + } + WFIFOSET(fd,len); + + return 0; +} +int clif_changestatus(struct block_list *bl,int type,int val) +{ + unsigned char buf[12]; + struct map_session_data *sd = NULL; + + nullpo_retr(0, bl); + + if(bl->type == BL_PC) + sd = (struct map_session_data *)bl; + +//printf("clif_changestatus id:%d type:%d val:%d\n",bl->id,type,val); + if(sd){ + WBUFW(buf,0)=0x1ab; + WBUFL(buf,2)=bl->id; + WBUFW(buf,6)=type; + switch(type){ + case SP_MANNER: + WBUFL(buf,8)=val; + break; + default: + if(battle_config.error_log) + printf("clif_changestatus : make %d routine\n",type); + return 1; + } + clif_send(buf,packet_len_table[0x1ab],bl,AREA_WOS); + } + return 0; +} +/*========================================== + * + *------------------------------------------ + */ +int clif_changelook(struct block_list *bl,int type,int val) +{ + + unsigned char buf[32]; + struct map_session_data *sd = NULL; + + nullpo_retr(0, bl); + + if(bl->type == BL_PC) + sd = (struct map_session_data *)bl; + + if(sd && sd->disguise > 23 && sd->disguise < 4001) // mob disguises [Valaris] + return 0; + +#if PACKETVER < 4 + if(sd && (type == LOOK_WEAPON || type == LOOK_SHIELD) && sd->view_class == 22) + val =0; + WBUFW(buf,0)=0xc3; + WBUFL(buf,2)=bl->id; + WBUFB(buf,6)=type; + WBUFB(buf,7)=val; + clif_send(buf,packet_len_table[0xc3],bl,AREA); +#else + if(sd && (type == LOOK_WEAPON || type == LOOK_SHIELD || type == LOOK_SHOES)) { + WBUFW(buf,0)=0x1d7; + WBUFL(buf,2)=bl->id; + if(type == LOOK_SHOES) { + WBUFB(buf,6)=9; + if(sd->equip_index[2] >= 0 && sd->inventory_data[sd->equip_index[2]]) { + if(sd->inventory_data[sd->equip_index[2]]->view_id > 0) + WBUFW(buf,7)=sd->inventory_data[sd->equip_index[2]]->view_id; + else + WBUFW(buf,7)=sd->status.inventory[sd->equip_index[2]].nameid; + } else + WBUFW(buf,7)=0; + WBUFW(buf,9)=0; + } + else { + WBUFB(buf,6)=2; + if(sd->equip_index[9] >= 0 && sd->inventory_data[sd->equip_index[9]] && sd->view_class != 22) { + if(sd->inventory_data[sd->equip_index[9]]->view_id > 0) + WBUFW(buf,7)=sd->inventory_data[sd->equip_index[9]]->view_id; + else + WBUFW(buf,7)=sd->status.inventory[sd->equip_index[9]].nameid; + } else + WBUFW(buf,7)=0; + if(sd->equip_index[8] >= 0 && sd->equip_index[8] != sd->equip_index[9] && sd->inventory_data[sd->equip_index[8]] && + sd->view_class != 22) { + if(sd->inventory_data[sd->equip_index[8]]->view_id > 0) + WBUFW(buf,9)=sd->inventory_data[sd->equip_index[8]]->view_id; + else + WBUFW(buf,9)=sd->status.inventory[sd->equip_index[8]].nameid; + } else + WBUFW(buf,9)=0; + } + clif_send(buf,packet_len_table[0x1d7],bl,AREA); + } + else if(sd && (type == LOOK_BASE) && (val > 255)) + { + WBUFW(buf,0)=0x1d7; + WBUFL(buf,2)=bl->id; + WBUFB(buf,6)=type; + WBUFW(buf,7)=val; + WBUFW(buf,9)=0; + clif_send(buf,packet_len_table[0x1d7],bl,AREA); + } else { + WBUFW(buf,0)=0xc3; + WBUFL(buf,2)=bl->id; + WBUFB(buf,6)=type; + WBUFB(buf,7)=val; + clif_send(buf,packet_len_table[0xc3],bl,AREA); + } +#endif + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_initialstatus(struct map_session_data *sd) +{ + int fd; + unsigned char *buf; + + nullpo_retr(0, sd); + + fd=sd->fd; + buf=WFIFOP(fd,0); + + WBUFW(buf,0)=0xbd; + WBUFW(buf,2)=sd->status.status_point; + WBUFB(buf,4)=(sd->status.str > 255)? 255:sd->status.str; + WBUFB(buf,5)=pc_need_status_point(sd,SP_STR); + WBUFB(buf,6)=(sd->status.agi > 255)? 255:sd->status.agi; + WBUFB(buf,7)=pc_need_status_point(sd,SP_AGI); + WBUFB(buf,8)=(sd->status.vit > 255)? 255:sd->status.vit; + WBUFB(buf,9)=pc_need_status_point(sd,SP_VIT); + WBUFB(buf,10)=(sd->status.int_ > 255)? 255:sd->status.int_; + WBUFB(buf,11)=pc_need_status_point(sd,SP_INT); + WBUFB(buf,12)=(sd->status.dex > 255)? 255:sd->status.dex; + WBUFB(buf,13)=pc_need_status_point(sd,SP_DEX); + WBUFB(buf,14)=(sd->status.luk > 255)? 255:sd->status.luk; + WBUFB(buf,15)=pc_need_status_point(sd,SP_LUK); + + WBUFW(buf,16) = sd->base_atk + sd->watk; + WBUFW(buf,18) = sd->watk2; //atk bonus + WBUFW(buf,20) = sd->matk1; + WBUFW(buf,22) = sd->matk2; + WBUFW(buf,24) = sd->def; // def + WBUFW(buf,26) = sd->def2; + WBUFW(buf,28) = sd->mdef; // mdef + WBUFW(buf,30) = sd->mdef2; + WBUFW(buf,32) = sd->hit; + WBUFW(buf,34) = sd->flee; + WBUFW(buf,36) = sd->flee2/10; + WBUFW(buf,38) = sd->critical/10; + WBUFW(buf,40) = sd->status.karma; + WBUFW(buf,42) = sd->status.manner; + + WFIFOSET(fd,packet_len_table[0xbd]); + + clif_updatestatus(sd,SP_STR); + clif_updatestatus(sd,SP_AGI); + clif_updatestatus(sd,SP_VIT); + clif_updatestatus(sd,SP_INT); + clif_updatestatus(sd,SP_DEX); + clif_updatestatus(sd,SP_LUK); + + clif_updatestatus(sd,SP_ATTACKRANGE); + clif_updatestatus(sd,SP_ASPD); + + return 0; +} + +/*========================================== + *矢装備 + *------------------------------------------ + */ +int clif_arrowequip(struct map_session_data *sd,int val) +{ + int fd; + + nullpo_retr(0, sd); + + if(sd->attacktarget && sd->attacktarget > 0) // [Valaris] + sd->attacktarget = 0; + + fd=sd->fd; + WFIFOW(fd,0)=0x013c; + WFIFOW(fd,2)=val+2;//矢のアイテムID + + WFIFOSET(fd,packet_len_table[0x013c]); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_arrow_fail(struct map_session_data *sd,int type) +{ + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd,0)=0x013b; + WFIFOW(fd,2)=type; + + WFIFOSET(fd,packet_len_table[0x013b]); + + return 0; +} + +/*========================================== + * 作成可能 矢リスト送信 + *------------------------------------------ + */ +int clif_arrow_create_list(struct map_session_data *sd) +{ + int i,c,view; + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd,0)=0x1ad; + + for(i=0,c=0;i<MAX_SKILL_ARROW_DB;i++){ + if(skill_arrow_db[i].nameid > 0 && pc_search_inventory(sd,skill_arrow_db[i].nameid)>=0){ + if((view = itemdb_viewid(skill_arrow_db[i].nameid)) > 0) + WFIFOW(fd,c*2+4) = view; + else + WFIFOW(fd,c*2+4) = skill_arrow_db[i].nameid; + c++; + } + } + WFIFOW(fd,2)=c*2+4; + WFIFOSET(fd,WFIFOW(fd,2)); + if(c > 0) sd->state.make_arrow_flag = 1; + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_statusupack(struct map_session_data *sd,int type,int ok,int val) +{ + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd,0)=0xbc; + WFIFOW(fd,2)=type; + WFIFOB(fd,4)=ok; + WFIFOB(fd,5)=val; + WFIFOSET(fd,packet_len_table[0xbc]); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_equipitemack(struct map_session_data *sd,int n,int pos,int ok) +{ + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd,0)=0xaa; + WFIFOW(fd,2)=n+2; + WFIFOW(fd,4)=pos; + WFIFOB(fd,6)=ok; + WFIFOSET(fd,packet_len_table[0xaa]); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_unequipitemack(struct map_session_data *sd,int n,int pos,int ok) +{ + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd,0)=0xac; + WFIFOW(fd,2)=n+2; + WFIFOW(fd,4)=pos; + WFIFOB(fd,6)=ok; + WFIFOSET(fd,packet_len_table[0xac]); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_misceffect(struct block_list* bl,int type) +{ + char buf[32]; + + nullpo_retr(0, bl); + + WBUFW(buf,0) = 0x19b; + WBUFL(buf,2) = bl->id; + WBUFL(buf,6) = type; + + clif_send(buf,packet_len_table[0x19b],bl,AREA); + + return 0; +} +int clif_misceffect2(struct block_list *bl, int type) { + unsigned char buf[24]; + + nullpo_retr(0, bl); + + memset(buf, 0, packet_len_table[0x1f3]); + + WBUFW(buf,0) = 0x1f3; + WBUFL(buf,2) = bl->id; + WBUFL(buf,6) = type; + + clif_send(buf, packet_len_table[0x1f3], bl, AREA); + + return 0; + +} +/*========================================== + * 表示オプション変更 + *------------------------------------------ + */ +int clif_changeoption(struct block_list* bl) +{ + char buf[32]; + short option; + struct status_change *sc_data; + static const int omask[]={ 0x10,0x20 }; + static const int scnum[]={ SC_FALCON, SC_RIDING }; + int i; + + nullpo_retr(0, bl); + + option = *battle_get_option(bl); + sc_data = battle_get_sc_data(bl); + + WBUFW(buf,0) = 0x119; + WBUFL(buf,2) = bl->id; + WBUFW(buf,6) = *battle_get_opt1(bl); + WBUFW(buf,8) = *battle_get_opt2(bl); + WBUFW(buf,10) = option; + WBUFB(buf,12) = 0; // ?? + + if(bl->type==BL_PC) { // disguises [Valaris] + struct map_session_data *sd=((struct map_session_data *)bl); + if(sd && sd->disguise > 23 && sd->disguise < 4001) { + clif_send(buf,packet_len_table[0x119],bl,AREA_WOS); + clif_spawnpc(sd); + } else + clif_send(buf,packet_len_table[0x119],bl,AREA); + } else + clif_send(buf,packet_len_table[0x119],bl,AREA); + + // アイコンの表示 + for(i=0;i<sizeof(omask)/sizeof(omask[0]);i++){ + if( option&omask[i] ){ + if( sc_data[scnum[i]].timer==-1) + skill_status_change_start(bl,scnum[i],0,0,0,0,0,0); + } else { + skill_status_change_end(bl,scnum[i],-1); + } + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_useitemack(struct map_session_data *sd,int index,int amount,int ok) +{ + nullpo_retr(0, sd); + + if(!ok) { + int fd=sd->fd; + WFIFOW(fd,0)=0xa8; + WFIFOW(fd,2)=index+2; + WFIFOW(fd,4)=amount; + WFIFOB(fd,6)=ok; + WFIFOSET(fd,packet_len_table[0xa8]); + } + else { +#if PACKETVER < 3 + int fd=sd->fd; + WFIFOW(fd,0)=0xa8; + WFIFOW(fd,2)=index+2; + WFIFOW(fd,4)=amount; + WFIFOB(fd,6)=ok; + WFIFOSET(fd,packet_len_table[0xa8]); +#else + char buf[32]; + + WBUFW(buf,0)=0x1c8; + WBUFW(buf,2)=index+2; + if(sd->inventory_data[index] && sd->inventory_data[index]->view_id > 0) + WBUFW(buf,4)=sd->inventory_data[index]->view_id; + else + WBUFW(buf,4)=sd->status.inventory[index].nameid; + WBUFL(buf,6)=sd->bl.id; + WBUFW(buf,10)=amount; + WBUFB(buf,12)=ok; + clif_send(buf,packet_len_table[0x1c8],&sd->bl,AREA); +#endif + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_createchat(struct map_session_data *sd,int fail) +{ + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd,0)=0xd6; + WFIFOB(fd,2)=fail; + WFIFOSET(fd,packet_len_table[0xd6]); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_dispchat(struct chat_data *cd,int fd) +{ + char buf[128]; // 最大title(60バイト)+17 + + if(cd==NULL || *cd->owner==NULL) + return 1; + + WBUFW(buf,0)=0xd7; + WBUFW(buf,2)=strlen(cd->title)+17; + WBUFL(buf,4)=(*cd->owner)->id; + WBUFL(buf,8)=cd->bl.id; + WBUFW(buf,12)=cd->limit; + WBUFW(buf,14)=cd->users; + WBUFB(buf,16)=cd->pub; + strcpy(WBUFP(buf,17),cd->title); + if(fd){ + memcpy(WFIFOP(fd,0),buf,WBUFW(buf,2)); + WFIFOSET(fd,WBUFW(buf,2)); + } else { + clif_send(buf,WBUFW(buf,2),*cd->owner,AREA_WOSC); + } + + return 0; +} + +/*========================================== + * chatの状態変更成功 + * 外部の人用と命令コード(d7->df)が違うだけ + *------------------------------------------ + */ +int clif_changechatstatus(struct chat_data *cd) +{ + char buf[128]; // 最大title(60バイト)+17 + + if(cd==NULL || cd->usersd[0]==NULL) + return 1; + + WBUFW(buf,0)=0xdf; + WBUFW(buf,2)=strlen(cd->title)+17; + WBUFL(buf,4)=cd->usersd[0]->bl.id; + WBUFL(buf,8)=cd->bl.id; + WBUFW(buf,12)=cd->limit; + WBUFW(buf,14)=cd->users; + WBUFB(buf,16)=cd->pub; + strcpy(WBUFP(buf,17),cd->title); + clif_send(buf,WBUFW(buf,2),&cd->usersd[0]->bl,CHAT); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_clearchat(struct chat_data *cd,int fd) +{ + char buf[32]; + + nullpo_retr(0, cd); + + WBUFW(buf,0)=0xd8; + WBUFL(buf,2)=cd->bl.id; + if(fd){ + memcpy(WFIFOP(fd,0),buf,packet_len_table[0xd8]); + WFIFOSET(fd,packet_len_table[0xd8]); + } else { + clif_send(buf,packet_len_table[0xd8],*cd->owner,AREA_WOSC); + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_joinchatfail(struct map_session_data *sd,int fail) +{ + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + + WFIFOW(fd,0)=0xda; + WFIFOB(fd,2)=fail; + WFIFOSET(fd,packet_len_table[0xda]); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_joinchatok(struct map_session_data *sd,struct chat_data* cd) +{ + int fd; + int i; + + nullpo_retr(0, sd); + nullpo_retr(0, cd); + + fd=sd->fd; + WFIFOW(fd,0)=0xdb; + WFIFOW(fd,2)=8+(28*cd->users); + WFIFOL(fd,4)=cd->bl.id; + for(i = 0;i < cd->users;i++){ + WFIFOL(fd,8+i*28) = (i!=0)||((*cd->owner)->type==BL_NPC); + memcpy(WFIFOP(fd,8+i*28+4),cd->usersd[i]->status.name,24); + } + WFIFOSET(fd,WFIFOW(fd,2)); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_addchat(struct chat_data* cd,struct map_session_data *sd) +{ + char buf[32]; + + nullpo_retr(0, sd); + nullpo_retr(0, cd); + + WBUFW(buf, 0) = 0x0dc; + WBUFW(buf, 2) = cd->users; + memcpy(WBUFP(buf, 4),sd->status.name,24); + clif_send(buf,packet_len_table[0xdc],&sd->bl,CHAT_WOS); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_changechatowner(struct chat_data* cd,struct map_session_data *sd) +{ + char buf[64]; + + nullpo_retr(0, sd); + nullpo_retr(0, cd); + + WBUFW(buf, 0) = 0xe1; + WBUFL(buf, 2) = 1; + memcpy(WBUFP(buf,6),cd->usersd[0]->status.name,24); + WBUFW(buf,30) = 0xe1; + WBUFL(buf,32) = 0; + memcpy(WBUFP(buf,36),sd->status.name,24); + + clif_send(buf,packet_len_table[0xe1]*2,&sd->bl,CHAT); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_leavechat(struct chat_data* cd,struct map_session_data *sd) +{ + char buf[32]; + + nullpo_retr(0, sd); + nullpo_retr(0, cd); + + WBUFW(buf, 0) = 0xdd; + WBUFW(buf, 2) = cd->users-1; + memcpy(WBUFP(buf,4),sd->status.name,24); + WBUFB(buf,28) = 0; + + clif_send(buf,packet_len_table[0xdd],&sd->bl,CHAT); + + return 0; +} + +/*========================================== + * 取り引き要請受け + *------------------------------------------ + */ +int clif_traderequest(struct map_session_data *sd,char *name) +{ + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd,0)=0xe5; + strcpy(WFIFOP(fd,2),name); + WFIFOSET(fd,packet_len_table[0xe5]); + + return 0; +} + +/*========================================== + * 取り引き要求応答 + *------------------------------------------ + */ +int clif_tradestart(struct map_session_data *sd,int type) +{ + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd,0)=0xe7; + WFIFOB(fd,2)=type; + WFIFOSET(fd,packet_len_table[0xe7]); + + return 0; +} + +/*========================================== + * 相手方からのアイテム追加 + *------------------------------------------ + */ +int clif_tradeadditem(struct map_session_data *sd,struct map_session_data *tsd,int index,int amount) +{ + int fd,j; + + nullpo_retr(0, sd); + nullpo_retr(0, tsd); + + fd=tsd->fd; + WFIFOW(fd,0)=0xe9; + WFIFOL(fd,2)=amount; + if(index==0){ + WFIFOW(fd,6) = 0; // type id + WFIFOB(fd,8) = 0; //identify flag + WFIFOB(fd,9) = 0; // attribute + WFIFOB(fd,10)= 0; //refine + WFIFOW(fd,11)= 0; //card (4w) + WFIFOW(fd,13)= 0; //card (4w) + WFIFOW(fd,15)= 0; //card (4w) + WFIFOW(fd,17)= 0; //card (4w) + } + else{ + index -= 2; + if(sd->inventory_data[index] && sd->inventory_data[index]->view_id > 0) + WFIFOW(fd,6) = sd->inventory_data[index]->view_id; + else + WFIFOW(fd,6) = sd->status.inventory[index].nameid; // type id + WFIFOB(fd,8) = sd->status.inventory[index].identify; //identify flag + if(sd->status.inventory[index].broken==1) + WFIFOB(fd,9) = 1; // is broke weapon [Valaris] + else + WFIFOB(fd,9) = sd->status.inventory[index].attribute; // attribute + WFIFOB(fd,10)= sd->status.inventory[index].refine; //refine + if(sd->status.inventory[index].card[0]==0x00ff || sd->status.inventory[index].card[0]==0x00fe || sd->status.inventory[index].card[0]==(short)0xff00) { + WFIFOW(fd,11)= sd->status.inventory[index].card[0]; //card (4w) + WFIFOW(fd,13)= sd->status.inventory[index].card[1]; //card (4w) + WFIFOW(fd,15)= sd->status.inventory[index].card[2]; //card (4w) + WFIFOW(fd,17)= sd->status.inventory[index].card[3]; //card (4w) + } else { + if(sd->status.inventory[index].card[0] > 0 && (j=itemdb_viewid(sd->status.inventory[index].card[0])) > 0) + WFIFOW(fd,11)= j; + else + WFIFOW(fd,11)= sd->status.inventory[index].card[0]; + if(sd->status.inventory[index].card[1] > 0 && (j=itemdb_viewid(sd->status.inventory[index].card[1])) > 0) + WFIFOW(fd,13)= j; + else + WFIFOW(fd,13)= sd->status.inventory[index].card[1]; + if(sd->status.inventory[index].card[2] > 0 && (j=itemdb_viewid(sd->status.inventory[index].card[2])) > 0) + WFIFOW(fd,15)= j; + else + WFIFOW(fd,15)= sd->status.inventory[index].card[2]; + if(sd->status.inventory[index].card[3] > 0 && (j=itemdb_viewid(sd->status.inventory[index].card[3])) > 0) + WFIFOW(fd,17)= j; + else + WFIFOW(fd,17)= sd->status.inventory[index].card[3]; + } + } + WFIFOSET(fd,packet_len_table[0xe9]); + + return 0; +} + +/*========================================== + * アイテム追加成功/失敗 + *------------------------------------------ + */ +int clif_tradeitemok(struct map_session_data *sd,int index,int amount,int fail) +{ + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd,0)=0x1b1; + //WFIFOW(fd,0)=0xea; + WFIFOW(fd,2)=index; + WFIFOW(fd,4)=amount; + WFIFOB(fd,6)=fail; + WFIFOSET(fd,packet_len_table[0x1b1]); + + return 0; +} + +/*========================================== + * 取り引きok押し + *------------------------------------------ + */ +int clif_tradedeal_lock(struct map_session_data *sd,int fail) +{ + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd,0)=0xec; + WFIFOB(fd,2)=fail; // 0=you 1=the other person + WFIFOSET(fd,packet_len_table[0xec]); + + return 0; +} + +/*========================================== + * 取り引きがキャンセルされました + *------------------------------------------ + */ +int clif_tradecancelled(struct map_session_data *sd) +{ + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd,0)=0xee; + WFIFOSET(fd,packet_len_table[0xee]); + + return 0; +} + +/*========================================== + * 取り引き完了 + *------------------------------------------ + */ +int clif_tradecompleted(struct map_session_data *sd,int fail) +{ + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd,0)=0xf0; + WFIFOB(fd,2)=fail; + WFIFOSET(fd,packet_len_table[0xf0]); + + return 0; +} + +/*========================================== + * カプラ倉庫のアイテム数を更新 + *------------------------------------------ + */ +int clif_updatestorageamount(struct map_session_data *sd,struct storage *stor) +{ + int fd; + + nullpo_retr(0, sd); + nullpo_retr(0, stor); + + fd=sd->fd; + WFIFOW(fd,0) = 0xf2; // update storage amount + WFIFOW(fd,2) = stor->storage_amount; //items + WFIFOW(fd,4) = MAX_STORAGE; //items max + WFIFOSET(fd,packet_len_table[0xf2]); + + return 0; +} + +/*========================================== + * カプラ倉庫にアイテムを追加する + *------------------------------------------ + */ +int clif_storageitemadded(struct map_session_data *sd,struct storage *stor,int index,int amount) +{ + int view,fd,j; + + nullpo_retr(0, sd); + nullpo_retr(0, stor); + + fd=sd->fd; + WFIFOW(fd,0) =0xf4; // Storage item added + WFIFOW(fd,2) =index+1; // index + WFIFOL(fd,4) =amount; // amount + if((view = itemdb_viewid(stor->storage[index].nameid)) > 0) + WFIFOW(fd,8) =view; + else + WFIFOW(fd,8) =stor->storage[index].nameid; // id + WFIFOB(fd,10)=stor->storage[index].identify; //identify flag + if(stor->storage[index].broken==1) + WFIFOB(fd,11)=1; // is weapon broken [Valaris] + else + WFIFOB(fd,11)=stor->storage[index].attribute; // attribute + WFIFOB(fd,12)=stor->storage[index].refine; //refine + if(stor->storage[index].card[0]==0x00ff || stor->storage[index].card[0]==0x00fe || stor->storage[index].card[0]==(short)0xff00) { + WFIFOW(fd,13)=stor->storage[index].card[0]; //card (4w) + WFIFOW(fd,15)=stor->storage[index].card[1]; //card (4w) + WFIFOW(fd,17)=stor->storage[index].card[2]; //card (4w) + WFIFOW(fd,19)=stor->storage[index].card[3]; //card (4w) + } else { + if(stor->storage[index].card[0] > 0 && (j=itemdb_viewid(stor->storage[index].card[0])) > 0) + WFIFOW(fd,13)= j; + else + WFIFOW(fd,13)= stor->storage[index].card[0]; + if(stor->storage[index].card[1] > 0 && (j=itemdb_viewid(stor->storage[index].card[1])) > 0) + WFIFOW(fd,15)= j; + else + WFIFOW(fd,15)= stor->storage[index].card[1]; + if(stor->storage[index].card[2] > 0 && (j=itemdb_viewid(stor->storage[index].card[2])) > 0) + WFIFOW(fd,17)= j; + else + WFIFOW(fd,17)= stor->storage[index].card[2]; + if(stor->storage[index].card[3] > 0 && (j=itemdb_viewid(stor->storage[index].card[3])) > 0) + WFIFOW(fd,19)= j; + else + WFIFOW(fd,19)= stor->storage[index].card[3]; + } + WFIFOSET(fd,packet_len_table[0xf4]); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_updateguildstorageamount(struct map_session_data *sd,struct guild_storage *stor) +{ + int fd; + + nullpo_retr(0, sd); + nullpo_retr(0, stor); + + fd=sd->fd; + WFIFOW(fd,0) = 0xf2; // update storage amount + WFIFOW(fd,2) = stor->storage_amount; //items + WFIFOW(fd,4) = MAX_GUILD_STORAGE; //items max + WFIFOSET(fd,packet_len_table[0xf2]); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_guildstorageitemadded(struct map_session_data *sd,struct guild_storage *stor,int index,int amount) +{ + int view,fd,j; + + nullpo_retr(0, sd); + nullpo_retr(0, stor); + + fd=sd->fd; + WFIFOW(fd,0) =0xf4; // Storage item added + WFIFOW(fd,2) =index+1; // index + WFIFOL(fd,4) =amount; // amount + if((view = itemdb_viewid(stor->storage[index].nameid)) > 0) + WFIFOW(fd,8) =view; + else + WFIFOW(fd,8) =stor->storage[index].nameid; // id + WFIFOB(fd,10)=stor->storage[index].identify; //identify flag + if(stor->storage[index].broken==1) + WFIFOB(fd,11)=1; // is weapon broken [Valaris] + else + WFIFOB(fd,11)=stor->storage[index].attribute; // attribute + WFIFOB(fd,12)=stor->storage[index].refine; //refine + if(stor->storage[index].card[0]==0x00ff || stor->storage[index].card[0]==0x00fe || stor->storage[index].card[0]==(short)0xff00) { + WFIFOW(fd,13)=stor->storage[index].card[0]; //card (4w) + WFIFOW(fd,15)=stor->storage[index].card[1]; //card (4w) + WFIFOW(fd,17)=stor->storage[index].card[2]; //card (4w) + WFIFOW(fd,19)=stor->storage[index].card[3]; //card (4w) + } else { + if(stor->storage[index].card[0] > 0 && (j=itemdb_viewid(stor->storage[index].card[0])) > 0) + WFIFOW(fd,13)= j; + else + WFIFOW(fd,13)= stor->storage[index].card[0]; + if(stor->storage[index].card[1] > 0 && (j=itemdb_viewid(stor->storage[index].card[1])) > 0) + WFIFOW(fd,15)= j; + else + WFIFOW(fd,15)= stor->storage[index].card[1]; + if(stor->storage[index].card[2] > 0 && (j=itemdb_viewid(stor->storage[index].card[2])) > 0) + WFIFOW(fd,17)= j; + else + WFIFOW(fd,17)= stor->storage[index].card[2]; + if(stor->storage[index].card[3] > 0 && (j=itemdb_viewid(stor->storage[index].card[3])) > 0) + WFIFOW(fd,19)= j; + else + WFIFOW(fd,19)= stor->storage[index].card[3]; + } + WFIFOSET(fd,packet_len_table[0xf4]); + + return 0; +} + +/*========================================== + * カプラ倉庫からアイテムを取り去る + *------------------------------------------ + */ +int clif_storageitemremoved(struct map_session_data *sd,int index,int amount) +{ + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd,0)=0xf6; // Storage item removed + WFIFOW(fd,2)=index+1; + WFIFOL(fd,4)=amount; + WFIFOSET(fd,packet_len_table[0xf6]); + + return 0; +} + +/*========================================== + * カプラ倉庫を閉じる + *------------------------------------------ + */ +int clif_storageclose(struct map_session_data *sd) +{ + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd,0)=0xf8; // Storage Closed + WFIFOSET(fd,packet_len_table[0xf8]); + + return 0; +} + +// +// callback系 ? +// +/*========================================== + * PC表示 + *------------------------------------------ + */ +void clif_getareachar_pc(struct map_session_data* sd,struct map_session_data* dstsd) +{ + int len; + + nullpo_retv(sd); + nullpo_retv(dstsd); + + if(dstsd->walktimer != -1){ + len = clif_set007b(dstsd,WFIFOP(sd->fd,0)); + WFIFOSET(sd->fd,len); + } else { + len = clif_set0078(dstsd,WFIFOP(sd->fd,0)); + WFIFOSET(sd->fd,len); + } + + if(dstsd->chatID){ + struct chat_data *cd; + cd=(struct chat_data*)map_id2bl(dstsd->chatID); + if(cd->usersd[0]==dstsd) + clif_dispchat(cd,sd->fd); + } + if(dstsd->vender_id){ + clif_showvendingboard(&dstsd->bl,dstsd->message,sd->fd); + } + if(dstsd->spiritball > 0) { + clif_set01e1(dstsd,WFIFOP(sd->fd,0)); + WFIFOSET(sd->fd,packet_len_table[0x1e1]); + } + if(battle_config.save_clothcolor==1 && dstsd->status.clothes_color > 0) + clif_changelook(&dstsd->bl,LOOK_CLOTHES_COLOR,dstsd->status.clothes_color); + + if(sd->status.manner < 0) + clif_changestatus(&sd->bl,SP_MANNER,sd->status.manner); + +} + +/*========================================== + * NPC表示 + *------------------------------------------ + */ +void clif_getareachar_npc(struct map_session_data* sd,struct npc_data* nd) +{ + int len; + + nullpo_retv(sd); + nullpo_retv(nd); + + if(nd->class < 0 || nd->flag&1 || nd->class == INVISIBLE_CLASS) + return; + + len = clif_npc0078(nd,WFIFOP(sd->fd,0)); + WFIFOSET(sd->fd,len); + + if(nd->chat_id){ + clif_dispchat((struct chat_data*)map_id2bl(nd->chat_id),sd->fd); + } + +} + +/*========================================== + * 移動停止 + *------------------------------------------ + */ +int clif_movemob(struct mob_data *md) +{ + unsigned char buf[256]; + int len; + + nullpo_retr(0, md); + + len = clif_mob007b(md,buf); + clif_send(buf,len,&md->bl,AREA); + + if(mob_get_equip(md->class) > 0) // mob equipment [Valaris] + clif_mob_equip(md,mob_get_equip(md->class)); + + return 0; +} + +/*========================================== + * モンスターの位置修正 + *------------------------------------------ + */ +int clif_fixmobpos(struct mob_data *md) +{ + unsigned char buf[256]; + int len; + + nullpo_retr(0, md); + + if(md->state.state == MS_WALK){ + len = clif_mob007b(md,buf); + clif_send(buf,len,&md->bl,AREA); + } else { + len = clif_mob0078(md,buf); + clif_send(buf,len,&md->bl,AREA); + } + + return 0; +} + +/*========================================== + * PCの位置修正 + *------------------------------------------ + */ +int clif_fixpcpos(struct map_session_data *sd) +{ + unsigned char buf[256]; + int len; + + nullpo_retr(0, sd); + + if(sd->walktimer != -1){ + len = clif_set007b(sd,buf); + clif_send(buf,len,&sd->bl,AREA); + } else { + len = clif_set0078(sd,buf); + clif_send(buf,len,&sd->bl,AREA); + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_fixpetpos(struct pet_data *pd) +{ + unsigned char buf[256]; + int len; + + nullpo_retr(0, pd); + + if(pd->state.state == MS_WALK){ + len = clif_pet007b(pd,buf); + clif_send(buf,len,&pd->bl,AREA); + } else { + len = clif_pet0078(pd,buf); + clif_send(buf,len,&pd->bl,AREA); + } + + return 0; +} + +/*========================================== + * 通常攻撃エフェクト&ダメージ + *------------------------------------------ + */ +int clif_damage(struct block_list *src,struct block_list *dst,unsigned int tick,int sdelay,int ddelay,int damage,int div,int type,int damage2) +{ + unsigned char buf[256]; + struct status_change *sc_data; + + nullpo_retr(0, src); + nullpo_retr(0, dst); + + sc_data = battle_get_sc_data(dst); + + if(type != 4 && dst->type == BL_PC && ((struct map_session_data *)dst)->special_state.infinite_endure) + type = 9; + if(sc_data) { + if(type != 4 && sc_data[SC_ENDURE].timer != -1) + type = 9; + if(sc_data[SC_HALLUCINATION].timer != -1) { + if(damage > 0) + damage = damage*(5+sc_data[SC_HALLUCINATION].val1) + rand()%100; + if(damage2 > 0) + damage2 = damage2*(5+sc_data[SC_HALLUCINATION].val1) + rand()%100; + } + } + + WBUFW(buf,0)=0x8a; + WBUFL(buf,2)=src->id; + WBUFL(buf,6)=dst->id; + WBUFL(buf,10)=tick; + WBUFL(buf,14)=sdelay; + WBUFL(buf,18)=ddelay; + WBUFW(buf,22)=(damage > 0x7fff)? 0x7fff:damage; + WBUFW(buf,24)=div; + WBUFB(buf,26)=type; + WBUFW(buf,27)=damage2; + clif_send(buf,packet_len_table[0x8a],src,AREA); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +void clif_getareachar_mob(struct map_session_data* sd,struct mob_data* md) +{ + int len; + nullpo_retv(sd); + nullpo_retv(md); + + if(md->state.state == MS_WALK){ + len = clif_mob007b(md,WFIFOP(sd->fd,0)); + WFIFOSET(sd->fd,len); + } else { + len = clif_mob0078(md,WFIFOP(sd->fd,0)); + WFIFOSET(sd->fd,len); + } + + if(mob_get_equip(md->class) > 0) // mob equipment [Valaris] + clif_mob_equip(md,mob_get_equip(md->class)); +} + +/*========================================== + * + *------------------------------------------ + */ +void clif_getareachar_pet(struct map_session_data* sd,struct pet_data* pd) +{ + int len; + + nullpo_retv(sd); + nullpo_retv(pd); + + if(pd->state.state == MS_WALK){ + len = clif_pet007b(pd,WFIFOP(sd->fd,0)); + WFIFOSET(sd->fd,len); + } else { + len = clif_pet0078(pd,WFIFOP(sd->fd,0)); + WFIFOSET(sd->fd,len); + } +} + +/*========================================== + * + *------------------------------------------ + */ +void clif_getareachar_item(struct map_session_data* sd,struct flooritem_data* fitem) +{ + int view,fd; + + nullpo_retv(sd); + nullpo_retv(fitem); + + fd=sd->fd; + //009d <ID>.l <item ID>.w <identify flag>.B <X>.w <Y>.w <amount>.w <subX>.B <subY>.B + WFIFOW(fd,0)=0x9d; + WFIFOL(fd,2)=fitem->bl.id; + if((view = itemdb_viewid(fitem->item_data.nameid)) > 0) + WFIFOW(fd,6)=view; + else + WFIFOW(fd,6)=fitem->item_data.nameid; + WFIFOB(fd,8)=fitem->item_data.identify; + WFIFOW(fd,9)=fitem->bl.x; + WFIFOW(fd,11)=fitem->bl.y; + WFIFOW(fd,13)=fitem->item_data.amount; + WFIFOB(fd,15)=fitem->subx; + WFIFOB(fd,16)=fitem->suby; + + WFIFOSET(fd,packet_len_table[0x9d]); +} +/*========================================== + * 場所スキルエフェクトが視界に入る + *------------------------------------------ + */ +int clif_getareachar_skillunit(struct map_session_data *sd,struct skill_unit *unit) +{ + int fd; + struct block_list *bl; + + nullpo_retr(0, unit); + + fd=sd->fd; + bl=map_id2bl(unit->group->src_id); +#if PACKETVER < 3 + memset(WFIFOP(fd,0),0,packet_len_table[0x11f]); + WFIFOW(fd, 0)=0x11f; + WFIFOL(fd, 2)=unit->bl.id; + WFIFOL(fd, 6)=unit->group->src_id; + WFIFOW(fd,10)=unit->bl.x; + WFIFOW(fd,12)=unit->bl.y; + WFIFOB(fd,14)=unit->group->unit_id; + WFIFOB(fd,15)=0; + WFIFOSET(fd,packet_len_table[0x11f]); +#else + memset(WFIFOP(fd,0),0,packet_len_table[0x1c9]); + WFIFOW(fd, 0)=0x1c9; + WFIFOL(fd, 2)=unit->bl.id; + WFIFOL(fd, 6)=unit->group->src_id; + WFIFOW(fd,10)=unit->bl.x; + WFIFOW(fd,12)=unit->bl.y; + WFIFOB(fd,14)=unit->group->unit_id; + WFIFOB(fd,15)=1; + WFIFOL(fd,15+1)=0; //1-4調べた限り固定 + WFIFOL(fd,15+5)=0; //5-8調べた限り固定 + //9-12マップごとで一定の77-80とはまた違う4バイトのかなり大きな数字 + WFIFOL(fd,15+13)=unit->bl.y - 0x12; //13-16ユニットのY座標-18っぽい(Y:17でFF FF FF FF) + WFIFOL(fd,15+17)=0x004f37dd; //17-20調べた限り固定 + WFIFOL(fd,15+21)=0x0012f674; //21-24調べた限り固定 + WFIFOL(fd,15+25)=0x0012f664; //25-28調べた限り固定 + WFIFOL(fd,15+29)=0x0012f654; //29-32調べた限り固定 + WFIFOL(fd,15+33)=0x77527bbc; //33-36調べた限り固定 + //37-39 + WFIFOB(fd,15+40)=0x2d; //40調べた限り固定 + WFIFOL(fd,15+41)=0; //41-44調べた限り0固定 + WFIFOL(fd,15+45)=0; //45-48調べた限り0固定 + WFIFOL(fd,15+49)=0; //49-52調べた限り0固定 + WFIFOL(fd,15+53)=0x0048d919; //53-56調べた限り固定 + WFIFOL(fd,15+57)=0x0000003e; //57-60調べた限り固定 + WFIFOL(fd,15+61)=0x0012f66c; //61-64調べた限り固定 + //65-68 + //69-72 + if(bl) WFIFOL(fd,15+73)=bl->y; //73-76術者のY座標 + WFIFOL(fd,15+77)=unit->bl.m; //77-80マップIDかなぁ?かなり2バイトで足りそうな数字 + WFIFOB(fd,15+81)=0xaa; //81終端文字0xaa + + /* Graffiti [Valaris] */ + if(unit->group->unit_id==0xb0) { + WFIFOL(fd,15)=1; + WFIFOL(fd,16)=1; + memcpy(WFIFOP(fd,17),unit->group->valstr,80); + } + + WFIFOSET(fd,packet_len_table[0x1c9]); +#endif + if(unit->group->skill_id == WZ_ICEWALL) + clif_set0192(fd,unit->bl.m,unit->bl.x,unit->bl.y,5); + + return 0; +} +/*========================================== + * 場所スキルエフェクトが視界から消える + *------------------------------------------ + */ +int clif_clearchar_skillunit(struct skill_unit *unit,int fd) +{ + nullpo_retr(0, unit); + + WFIFOW(fd, 0)=0x120; + WFIFOL(fd, 2)=unit->bl.id; + WFIFOSET(fd,packet_len_table[0x120]); + if(unit->group->skill_id == WZ_ICEWALL) + clif_set0192(fd,unit->bl.m,unit->bl.x,unit->bl.y,unit->val2); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_01ac(struct block_list *bl) +{ + char buf[32]; + + nullpo_retr(0, bl); + + WBUFW(buf, 0) = 0x1ac; + WBUFL(buf, 2) = bl->id; + + clif_send(buf,packet_len_table[0x1ac],bl,AREA); + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ + int clif_getareachar(struct block_list* bl,va_list ap) +{ + struct map_session_data *sd; + + nullpo_retr(0, bl); + nullpo_retr(0, ap); + + sd=va_arg(ap,struct map_session_data*); + + switch(bl->type){ + case BL_PC: + if(sd==(struct map_session_data*)bl) + break; + clif_getareachar_pc(sd,(struct map_session_data*) bl); + break; + case BL_NPC: + clif_getareachar_npc(sd,(struct npc_data*) bl); + break; + case BL_MOB: + clif_getareachar_mob(sd,(struct mob_data*) bl); + break; + case BL_PET: + clif_getareachar_pet(sd,(struct pet_data*) bl); + break; + case BL_ITEM: + clif_getareachar_item(sd,(struct flooritem_data*) bl); + break; + case BL_SKILL: + clif_getareachar_skillunit(sd,(struct skill_unit *)bl); + break; + default: + if(battle_config.error_log) + printf("get area char ??? %d\n",bl->type); + break; + } + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_pcoutsight(struct block_list *bl,va_list ap) +{ + struct map_session_data *sd,*dstsd; + + nullpo_retr(0, bl); + nullpo_retr(0, ap); + nullpo_retr(0, sd=va_arg(ap,struct map_session_data*)); + + switch(bl->type){ + case BL_PC: + dstsd = (struct map_session_data*) bl; + if(sd != dstsd) { + clif_clearchar_id(dstsd->bl.id,0,sd->fd); + clif_clearchar_id(sd->bl.id,0,dstsd->fd); + if(dstsd->chatID){ + struct chat_data *cd; + cd=(struct chat_data*)map_id2bl(dstsd->chatID); + if(cd->usersd[0]==dstsd) + clif_dispchat(cd,sd->fd); + } + if(dstsd->vender_id){ + clif_closevendingboard(&dstsd->bl,sd->fd); + } + } + break; + case BL_NPC: + if( ((struct npc_data *)bl)->class != INVISIBLE_CLASS ) + clif_clearchar_id(bl->id,0,sd->fd); + break; + case BL_MOB: + case BL_PET: + clif_clearchar_id(bl->id,0,sd->fd); + break; + case BL_ITEM: + clif_clearflooritem((struct flooritem_data*)bl,sd->fd); + break; + case BL_SKILL: + clif_clearchar_skillunit((struct skill_unit *)bl,sd->fd); + break; + } + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_pcinsight(struct block_list *bl,va_list ap) +{ + struct map_session_data *sd,*dstsd; + + nullpo_retr(0, bl); + nullpo_retr(0, ap); + nullpo_retr(0, sd=va_arg(ap,struct map_session_data*)); + + switch(bl->type){ + case BL_PC: + dstsd = (struct map_session_data *)bl; + if(sd != dstsd) { + clif_getareachar_pc(sd,dstsd); + clif_getareachar_pc(dstsd,sd); + } + break; + case BL_NPC: + clif_getareachar_npc(sd,(struct npc_data*)bl); + break; + case BL_MOB: + clif_getareachar_mob(sd,(struct mob_data*)bl); + break; + case BL_PET: + clif_getareachar_pet(sd,(struct pet_data*)bl); + break; + case BL_ITEM: + clif_getareachar_item(sd,(struct flooritem_data*)bl); + break; + case BL_SKILL: + clif_getareachar_skillunit(sd,(struct skill_unit *)bl); + break; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_moboutsight(struct block_list *bl,va_list ap) +{ + struct map_session_data *sd; + struct mob_data *md; + + nullpo_retr(0, bl); + nullpo_retr(0, ap); + nullpo_retr(0, md=va_arg(ap,struct mob_data*)); + + if(bl->type==BL_PC && (sd = (struct map_session_data*) bl)){ + clif_clearchar_id(md->bl.id,0,sd->fd); + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_mobinsight(struct block_list *bl,va_list ap) +{ + struct map_session_data *sd; + struct mob_data *md; + + nullpo_retr(0, bl); + nullpo_retr(0, ap); + + md=va_arg(ap,struct mob_data*); + if(bl->type==BL_PC && (sd = (struct map_session_data *)bl)){ + clif_getareachar_mob(sd,md); + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_petoutsight(struct block_list *bl,va_list ap) +{ + struct map_session_data *sd; + struct pet_data *pd; + + nullpo_retr(0, bl); + nullpo_retr(0, ap); + nullpo_retr(0, pd=va_arg(ap,struct pet_data*)); + + if(bl->type==BL_PC && (sd = (struct map_session_data*) bl)){ + clif_clearchar_id(pd->bl.id,0,sd->fd); + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_petinsight(struct block_list *bl,va_list ap) +{ + struct map_session_data *sd; + struct pet_data *pd; + + nullpo_retr(0, bl); + nullpo_retr(0, ap); + + pd=va_arg(ap,struct pet_data*); + if(bl->type==BL_PC && (sd = (struct map_session_data *)bl)){ + clif_getareachar_pet(sd,pd); + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_skillinfo(struct map_session_data *sd,int skillid,int type,int range) +{ + int fd,id; + + nullpo_retr(0, sd); + + fd=sd->fd; + if( (id=sd->status.skill[skillid].id) <= 0 ) + return 0; + WFIFOW(fd,0)=0x147; + WFIFOW(fd,2) = id; + if(type < 0) + WFIFOW(fd,4) = skill_get_inf(id); + else + WFIFOW(fd,4) = type; + WFIFOW(fd,6) = 0; + WFIFOW(fd,8) = sd->status.skill[skillid].lv; + WFIFOW(fd,10) = skill_get_sp(id,sd->status.skill[skillid].lv); + if(range < 0) { + range = skill_get_range(id,sd->status.skill[skillid].lv); + if(range < 0) + range = battle_get_range(&sd->bl) - (range + 1); + WFIFOW(fd,12)= range; + } else + WFIFOW(fd,12)= range; + memset(WFIFOP(fd,14),0,24); + if(!(skill_get_inf2(id)&0x01) || battle_config.quest_skill_learn == 1 || (battle_config.gm_allskill > 0 && pc_isGM(sd) >= battle_config.gm_allskill) ) + WFIFOB(fd,38)= (sd->status.skill[skillid].lv < skill_get_max(id) && sd->status.skill[skillid].flag ==0 )? 1:0; + else + WFIFOB(fd,38) = 0; + WFIFOSET(fd,packet_len_table[0x147]); + + return 0; +} + +/*========================================== + * スキルリストを送信する + *------------------------------------------ + */ +int clif_skillinfoblock(struct map_session_data *sd) +{ + int fd; + int i,c,len=4,id,range; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd,0)=0x10f; + for ( i = c = 0; i < MAX_SKILL; i++){ + if( (id=sd->status.skill[i].id)!=0 ){ + WFIFOW(fd,len ) = id; + WFIFOW(fd,len+2) = skill_get_inf(id); + WFIFOW(fd,len+4) = 0; + WFIFOW(fd,len+6) = sd->status.skill[i].lv; + WFIFOW(fd,len+8) = skill_get_sp(id,sd->status.skill[i].lv); + range = skill_get_range(id,sd->status.skill[i].lv); + if(range < 0) + range = battle_get_range(&sd->bl) - (range + 1); + WFIFOW(fd,len+10)= range; + memset(WFIFOP(fd,len+12),0,24); + if(!(skill_get_inf2(id)&0x01) || battle_config.quest_skill_learn == 1 || (battle_config.gm_allskill > 0 && pc_isGM(sd) >= battle_config.gm_allskill) ) + WFIFOB(fd,len+36)= (sd->status.skill[i].lv < skill_get_max(id) && sd->status.skill[i].flag ==0 )? 1:0; + else + WFIFOB(fd,len+36) = 0; + len+=37; + c++; + } + } + WFIFOW(fd,2)=len; + WFIFOSET(fd,len); + + return 0; +} + +/*========================================== + * スキル割り振り通知 + *------------------------------------------ + */ +int clif_skillup(struct map_session_data *sd,int skill_num) +{ + int range,fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd,0) = 0x10e; + WFIFOW(fd,2) = skill_num; + WFIFOW(fd,4) = sd->status.skill[skill_num].lv; + WFIFOW(fd,6) = skill_get_sp(skill_num,sd->status.skill[skill_num].lv); + range = skill_get_range(skill_num,sd->status.skill[skill_num].lv); + if(range < 0) + range = battle_get_range(&sd->bl) - (range + 1); + WFIFOW(fd,8) = range; + WFIFOB(fd,10) = (sd->status.skill[skill_num].lv < skill_get_max(sd->status.skill[skill_num].id)) ? 1 : 0; + WFIFOSET(fd,packet_len_table[0x10e]); + + return 0; +} + +/*========================================== + * スキル詠唱エフェクトを送信する + *------------------------------------------ + */ +int clif_skillcasting(struct block_list* bl, + int src_id,int dst_id,int dst_x,int dst_y,int skill_num,int casttime) +{ + unsigned char buf[32]; + WBUFW(buf,0) = 0x13e; + WBUFL(buf,2) = src_id; + WBUFL(buf,6) = dst_id; + WBUFW(buf,10) = dst_x; + WBUFW(buf,12) = dst_y; + WBUFW(buf,14) = skill_num;//魔法詠唱スキル + WBUFL(buf,16) = skill_get_pl(skill_num);//属性 + WBUFL(buf,20) = casttime;//skill詠唱時間 + clif_send(buf,packet_len_table[0x13e], bl, AREA); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_skillcastcancel(struct block_list* bl) +{ + unsigned char buf[16]; + + nullpo_retr(0, bl); + + WBUFW(buf,0) = 0x1b9; + WBUFL(buf,2) = bl->id; + clif_send(buf,packet_len_table[0x1b9], bl, AREA); + + return 0; +} + +/*========================================== + * スキル詠唱失敗 + *------------------------------------------ + */ +int clif_skill_fail(struct map_session_data *sd,int skill_id,int type,int btype) +{ + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + + if(type==0x4 && battle_config.display_delay_skill_fail==0){ + return 0; + } + + WFIFOW(fd,0) = 0x110; + WFIFOW(fd,2) = skill_id; + WFIFOW(fd,4) = btype; + WFIFOW(fd,6) = 0; + WFIFOB(fd,8) = 0; + WFIFOB(fd,9) = type; + WFIFOSET(fd,packet_len_table[0x110]); + + return 0; +} + +/*========================================== + * スキル攻撃エフェクト&ダメージ + *------------------------------------------ + */ +int clif_skill_damage(struct block_list *src,struct block_list *dst, + unsigned int tick,int sdelay,int ddelay,int damage,int div,int skill_id,int skill_lv,int type) +{ + unsigned char buf[64]; + struct status_change *sc_data; + + nullpo_retr(0, src); + nullpo_retr(0, dst); + + sc_data = battle_get_sc_data(dst); + + if(type != 5 && dst->type == BL_PC && ((struct map_session_data *)dst)->special_state.infinite_endure) + type = 9; + if(sc_data) { + if(type != 5 && sc_data[SC_ENDURE].timer != -1) + type = 9; + if(sc_data[SC_HALLUCINATION].timer != -1 && damage > 0) + damage = damage*(5+sc_data[SC_HALLUCINATION].val1) + rand()%100; + } + +#if PACKETVER < 3 + WBUFW(buf,0)=0x114; + WBUFW(buf,2)=skill_id; + WBUFL(buf,4)=src->id; + WBUFL(buf,8)=dst->id; + WBUFL(buf,12)=tick; + WBUFL(buf,16)=sdelay; + WBUFL(buf,20)=ddelay; + WBUFW(buf,24)=damage; + WBUFW(buf,26)=skill_lv; + WBUFW(buf,28)=div; + WBUFB(buf,30)=(type>0)?type:skill_get_hit(skill_id); + clif_send(buf,packet_len_table[0x114],src,AREA); +#else + WBUFW(buf,0)=0x1de; + WBUFW(buf,2)=skill_id; + WBUFL(buf,4)=src->id; + WBUFL(buf,8)=dst->id; + WBUFL(buf,12)=tick; + WBUFL(buf,16)=sdelay; + WBUFL(buf,20)=ddelay; + WBUFL(buf,24)=damage; + WBUFW(buf,28)=skill_lv; + WBUFW(buf,30)=div; + WBUFB(buf,32)=(type>0)?type:skill_get_hit(skill_id); + clif_send(buf,packet_len_table[0x1de],src,AREA); +#endif + + return 0; +} + +/*========================================== + * 吹き飛ばしスキル攻撃エフェクト&ダメージ + *------------------------------------------ + */ +int clif_skill_damage2(struct block_list *src,struct block_list *dst, + unsigned int tick,int sdelay,int ddelay,int damage,int div,int skill_id,int skill_lv,int type) +{ + unsigned char buf[64]; + struct status_change *sc_data; + + nullpo_retr(0, src); + nullpo_retr(0, dst); + + sc_data = battle_get_sc_data(dst); + + if(type != 5 && dst->type == BL_PC && ((struct map_session_data *)dst)->special_state.infinite_endure) + type = 9; + if(sc_data) { + if(type != 5 && sc_data[SC_ENDURE].timer != -1) + type = 9; + if(sc_data[SC_HALLUCINATION].timer != -1 && damage > 0) + damage = damage*(5+sc_data[SC_HALLUCINATION].val1) + rand()%100; + } + + WBUFW(buf,0)=0x115; + WBUFW(buf,2)=skill_id; + WBUFL(buf,4)=src->id; + WBUFL(buf,8)=dst->id; + WBUFL(buf,12)=tick; + WBUFL(buf,16)=sdelay; + WBUFL(buf,20)=ddelay; + WBUFW(buf,24)=dst->x; + WBUFW(buf,26)=dst->y; + WBUFW(buf,28)=damage; + WBUFW(buf,30)=skill_lv; + WBUFW(buf,32)=div; + WBUFB(buf,34)=(type>0)?type:skill_get_hit(skill_id); + clif_send(buf,packet_len_table[0x115],src,AREA); + + return 0; +} + +/*========================================== + * 支援/回復スキルエフェクト + *------------------------------------------ + */ +int clif_skill_nodamage(struct block_list *src,struct block_list *dst, + int skill_id,int heal,int fail) +{ + unsigned char buf[32]; + + nullpo_retr(0, src); + nullpo_retr(0, dst); + + WBUFW(buf,0)=0x11a; + WBUFW(buf,2)=skill_id; + WBUFW(buf,4)=(heal > 0x7fff)? 0x7fff:heal; + WBUFL(buf,6)=dst->id; + WBUFL(buf,10)=src->id; + WBUFB(buf,14)=fail; + clif_send(buf,packet_len_table[0x11a],src,AREA); + + return 0; +} + +/*========================================== + * 場所スキルエフェクト + *------------------------------------------ + */ +int clif_skill_poseffect(struct block_list *src,int skill_id,int val,int x,int y,int tick) +{ + unsigned char buf[32]; + + nullpo_retr(0, src); + + WBUFW(buf,0)=0x117; + WBUFW(buf,2)=skill_id; + WBUFL(buf,4)=src->id; + WBUFW(buf,8)=val; + WBUFW(buf,10)=x; + WBUFW(buf,12)=y; + WBUFL(buf,14)=tick; + clif_send(buf,packet_len_table[0x117],src,AREA); + + return 0; +} + +/*========================================== + * 場所スキルエフェクト表示 + *------------------------------------------ + */ +int clif_skill_setunit(struct skill_unit *unit) +{ + unsigned char buf[128]; + struct block_list *bl; + + nullpo_retr(0, unit); + + bl=map_id2bl(unit->group->src_id); + +#if PACKETVER < 3 + memset(WBUFP(buf, 0),0,packet_len_table[0x11f]); + WBUFW(buf, 0)=0x11f; + WBUFL(buf, 2)=unit->bl.id; + WBUFL(buf, 6)=unit->group->src_id; + WBUFW(buf,10)=unit->bl.x; + WBUFW(buf,12)=unit->bl.y; + WBUFB(buf,14)=unit->group->unit_id; + WBUFB(buf,15)=0; + clif_send(buf,packet_len_table[0x11f],&unit->bl,AREA); +#else + memset(WBUFP(buf, 0),0,packet_len_table[0x1c9]); + WBUFW(buf, 0)=0x1c9; + WBUFL(buf, 2)=unit->bl.id; + WBUFL(buf, 6)=unit->group->src_id; + WBUFW(buf,10)=unit->bl.x; + WBUFW(buf,12)=unit->bl.y; + WBUFB(buf,14)=unit->group->unit_id; + WBUFB(buf,15)=1; + WBUFL(buf,15+1)=0; //1-4調べた限り固定 + WBUFL(buf,15+5)=0; //5-8調べた限り固定 + //9-12マップごとで一定の77-80とはまた違う4バイトのかなり大きな数字 + WBUFL(buf,15+13)=unit->bl.y - 0x12; //13-16ユニットのY座標-18っぽい(Y:17でFF FF FF FF) + WBUFL(buf,15+17)=0x004f37dd; //17-20調べた限り固定(0x1b2で0x004fdbddだった) + WBUFL(buf,15+21)=0x0012f674; //21-24調べた限り固定 + WBUFL(buf,15+25)=0x0012f664; //25-28調べた限り固定 + WBUFL(buf,15+29)=0x0012f654; //29-32調べた限り固定 + WBUFL(buf,15+33)=0x77527bbc; //33-36調べた限り固定 + //37-39 + WBUFB(buf,15+40)=0x2d; //40調べた限り固定 + WBUFL(buf,15+41)=0; //41-44調べた限り0固定 + WBUFL(buf,15+45)=0; //45-48調べた限り0固定 + WBUFL(buf,15+49)=0; //49-52調べた限り0固定 + WBUFL(buf,15+53)=0x0048d919; //53-56調べた限り固定(0x01b2で0x00495119だった) + WBUFL(buf,15+57)=0x0000003e; //57-60調べた限り固定 + WBUFL(buf,15+61)=0x0012f66c; //61-64調べた限り固定 + //65-68 + //69-72 + if(bl) WBUFL(buf,15+73)=bl->y; //73-76術者のY座標 + WBUFL(buf,15+77)=unit->bl.m; //77-80マップIDかなぁ?かなり2バイトで足りそうな数字 + WBUFB(buf,15+81)=0xaa; //81終端文字0xaa + + /* Graffiti [Valaris] */ + if(unit->group->unit_id==0xb0) { + WBUFL(buf,15)=1; + WBUFL(buf,16)=1; + memcpy(WBUFP(buf,17),unit->group->valstr,80); + } + + clif_send(buf,packet_len_table[0x1c9],&unit->bl,AREA); +#endif + return 0; +} +/*========================================== + * 場所スキルエフェクト削除 + *------------------------------------------ + */ +int clif_skill_delunit(struct skill_unit *unit) +{ + unsigned char buf[16]; + + nullpo_retr(0, unit); + + WBUFW(buf, 0)=0x120; + WBUFL(buf, 2)=unit->bl.id; + clif_send(buf,packet_len_table[0x120],&unit->bl,AREA); + return 0; +} +/*========================================== + * ワープ場所選択 + *------------------------------------------ + */ +int clif_skill_warppoint(struct map_session_data *sd,int skill_num, + const char *map1,const char *map2,const char *map3,const char *map4) +{ + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd,0)=0x11c; + WFIFOW(fd,2)=skill_num; + memcpy(WFIFOP(fd, 4),map1,16); + memcpy(WFIFOP(fd,20),map2,16); + memcpy(WFIFOP(fd,36),map3,16); + memcpy(WFIFOP(fd,52),map4,16); + WFIFOSET(fd,packet_len_table[0x11c]); + return 0; +} +/*========================================== + * メモ応答 + *------------------------------------------ + */ +int clif_skill_memo(struct map_session_data *sd,int flag) +{ + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + + WFIFOW(fd,0)=0x11e; + WFIFOB(fd,2)=flag; + WFIFOSET(fd,packet_len_table[0x11e]); + return 0; +} +int clif_skill_teleportmessage(struct map_session_data *sd,int flag) +{ + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd,0)=0x189; + WFIFOW(fd,2)=flag; + WFIFOSET(fd,packet_len_table[0x189]); + return 0; +} + +/*========================================== + * モンスター情報 + *------------------------------------------ + */ +int clif_skill_estimation(struct map_session_data *sd,struct block_list *dst) +{ + struct mob_data *md; + unsigned char buf[64]; + int i; + + nullpo_retr(0, sd); + nullpo_retr(0, dst); + + if(dst->type!=BL_MOB ) + return 0; + if((md=(struct mob_data *)dst) == NULL) + return 0; + + WBUFW(buf, 0)=0x18c; + WBUFW(buf, 2)=mob_get_viewclass(md->class); + WBUFW(buf, 4)=mob_db[md->class].lv; + WBUFW(buf, 6)=mob_db[md->class].size; + WBUFL(buf, 8)=md->hp; + WBUFW(buf,12)=battle_get_def2(&md->bl); + WBUFW(buf,14)=mob_db[md->class].race; + WBUFW(buf,16)=battle_get_mdef2(&md->bl) - (mob_db[md->class].vit>>1); + WBUFW(buf,18)=battle_get_elem_type(&md->bl); + for(i=0;i<9;i++) + WBUFB(buf,20+i)= battle_attr_fix(100,i+1,md->def_ele); + + if(sd->status.party_id>0) + clif_send(buf,packet_len_table[0x18c],&sd->bl,PARTY_AREA); + else{ + memcpy(WFIFOP(sd->fd,0),buf,packet_len_table[0x18c]); + WFIFOSET(sd->fd,packet_len_table[0x18c]); + } + return 0; +} +/*========================================== + * アイテム合成可能リスト + *------------------------------------------ + */ +int clif_skill_produce_mix_list(struct map_session_data *sd,int trigger) +{ + int i,c,view,fd; + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd, 0)=0x18d; + + for(i=0,c=0;i<MAX_SKILL_PRODUCE_DB;i++){ + if( skill_can_produce_mix(sd,skill_produce_db[i].nameid,trigger) ){ + if((view = itemdb_viewid(skill_produce_db[i].nameid)) > 0) + WFIFOW(fd,c*8+ 4)= view; + else + WFIFOW(fd,c*8+ 4)= skill_produce_db[i].nameid; + WFIFOW(fd,c*8+ 6)= 0x0012; + WFIFOL(fd,c*8+ 8)= sd->status.char_id; + c++; + } + } + WFIFOW(fd, 2)=c*8+8; + WFIFOSET(fd,WFIFOW(fd,2)); + if(c > 0) sd->state.produce_flag = 1; + return 0; +} + +/*========================================== + * 状態異常アイコン/メッセージ表示 + *------------------------------------------ + */ +int clif_status_change(struct block_list *bl,int type,int flag) +{ + unsigned char buf[16]; + + nullpo_retr(0, bl); + + WBUFW(buf,0)=0x0196; + WBUFW(buf,2)=type; + WBUFL(buf,4)=bl->id; + WBUFB(buf,8)=flag; + clif_send(buf,packet_len_table[0x196],bl,AREA); + return 0; +} + +/*========================================== + * Send message (modified by [Yor]) + *------------------------------------------ + */ +int clif_displaymessage(const int fd, char* mes) +{ + int len_mes = strlen(mes); + + if (len_mes > 0) { // don't send a void message (it's not displaying on the client chat). @help can send void line. + WFIFOW(fd,0) = 0x8e; + WFIFOW(fd,2) = 5 + len_mes; // 4 + len + NULL teminate + memcpy(WFIFOP(fd,4), mes, len_mes + 1); + WFIFOSET(fd, 5 + len_mes); + } + + return 0; +} + +/*========================================== + * 天の声を送信する + *------------------------------------------ + */ +int clif_GMmessage(struct block_list *bl, char* mes, int len, int flag) +{ + unsigned char lbuf[255]; + unsigned char *buf = ((len + 16) >= sizeof(lbuf)) ? malloc(len+16) : lbuf; + int lp = (flag&0x10) ? 8 : 4; + + WBUFW(buf,0) = 0x9a; + WBUFW(buf,2) = len + lp; + WBUFL(buf,4) = 0x65756c62; + memcpy(WBUFP(buf,lp), mes, len); + flag &= 0x07; + clif_send(buf, WBUFW(buf,2), bl, + (flag==1) ? ALL_SAMEMAP: + (flag==2) ? AREA: + (flag==3) ? SELF: + ALL_CLIENT); + if (buf != lbuf) + free(buf); + return 0; +} + +/*========================================== + * HPSP回復エフェクトを送信する + *------------------------------------------ + */ +int clif_heal(int fd,int type,int val) +{ + WFIFOW(fd,0)=0x13d; + WFIFOW(fd,2)=type; + WFIFOW(fd,4)=val; + WFIFOSET(fd,packet_len_table[0x13d]); + + return 0; +} + +/*========================================== + * 復活する + *------------------------------------------ + */ +int clif_resurrection(struct block_list *bl,int type) +{ + unsigned char buf[16]; + + nullpo_retr(0, bl); + + if(bl->type==BL_PC) { // disguises [Valaris] + struct map_session_data *sd=((struct map_session_data *)bl); + if(sd && sd->disguise > 23 && sd->disguise < 4001) + clif_spawnpc(sd); + } + + WBUFW(buf,0)=0x148; + WBUFL(buf,2)=bl->id; + WBUFW(buf,6)=type; + + clif_send(buf,packet_len_table[0x148],bl,type==1 ? AREA : AREA_WOS); + + return 0; +} + +/*========================================== + * PVP実装?(仮) + *------------------------------------------ + */ +int clif_set0199(int fd,int type) +{ + WFIFOW(fd,0)=0x199; + WFIFOW(fd,2)=type; + WFIFOSET(fd,packet_len_table[0x199]); + + return 0; +} + +/*========================================== + * PVP実装?(仮) + *------------------------------------------ + */ +int clif_pvpset(struct map_session_data *sd,int pvprank,int pvpnum,int type) +{ + nullpo_retr(0, sd); + + if(map[sd->bl.m].flag.nopvp) + return 0; + + if(type == 2) { + WFIFOW(sd->fd,0) = 0x19a; + WFIFOL(sd->fd,2) = sd->bl.id; + if(pvprank<=0) + pc_calc_pvprank(sd); + WFIFOL(sd->fd,6) = pvprank; + WFIFOL(sd->fd,10) = pvpnum; + WFIFOSET(sd->fd,packet_len_table[0x19a]); + } else { + char buf[32]; + + WBUFW(buf,0) = 0x19a; + WBUFL(buf,2) = sd->bl.id; + if(sd->status.option&0x46) + WBUFL(buf,6) = -1; + else + if(pvprank<=0) + pc_calc_pvprank(sd); + WBUFL(buf,6) = pvprank; + WBUFL(buf,10) = pvpnum; + if(!type) + clif_send(buf,packet_len_table[0x19a],&sd->bl,AREA); + else + clif_send(buf,packet_len_table[0x19a],&sd->bl,ALL_SAMEMAP); + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_send0199(int map,int type) +{ + struct block_list bl; + char buf[16]; + + bl.m = map; + WBUFW(buf,0)=0x199; + WBUFW(buf,2)=type; + clif_send(buf,packet_len_table[0x199],&bl,ALL_SAMEMAP); + + return 0; +} + +/*========================================== + * 精錬エフェクトを送信する + *------------------------------------------ + */ +int clif_refine(int fd,struct map_session_data *sd,int fail,int index,int val) +{ + WFIFOW(fd,0)=0x188; + WFIFOW(fd,2)=fail; + WFIFOW(fd,4)=index+2; + WFIFOW(fd,6)=val; + WFIFOSET(fd,packet_len_table[0x188]); + + return 0; +} + +/*========================================== + * Wisp/page is transmitted to the destination player + *------------------------------------------ + */ +int clif_wis_message(int fd, char *nick, char *mes, int mes_len) // R 0097 <len>.w <nick>.24B <message>.?B +{ + WFIFOW(fd,0) = 0x97; + WFIFOW(fd,2) = mes_len + 24 + 4; + memcpy(WFIFOP(fd,4), nick, 24); + memcpy(WFIFOP(fd,28), mes, mes_len); + WFIFOSET(fd,WFIFOW(fd,2)); + return 0; +} + +/*========================================== + * The transmission result of Wisp/page is transmitted to the source player + *------------------------------------------ + */ +int clif_wis_end(int fd, int flag) // R 0098 <type>.B: 0: success to send wisper, 1: target character is not loged in?, 2: ignored by target +{ + WFIFOW(fd,0) = 0x98; + WFIFOW(fd,2) = flag; + WFIFOSET(fd,packet_len_table[0x98]); + return 0; +} + +/*========================================== + * キャラID名前引き結果を送信する + *------------------------------------------ + */ +int clif_solved_charname(struct map_session_data *sd,int char_id) +{ + char *p= map_charid2nick(char_id); + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + if(p!=NULL){ + WFIFOW(fd,0)=0x194; + WFIFOL(fd,2)=char_id; + memcpy(WFIFOP(fd,6), p,24 ); + WFIFOSET(fd,packet_len_table[0x194]); + }else{ + map_reqchariddb(sd,char_id); + chrif_searchcharid(char_id); + } + return 0; +} + +/*========================================== + * カードの挿入可能リストを返す + *------------------------------------------ + */ +int clif_use_card(struct map_session_data *sd,int idx) +{ + nullpo_retr(0, sd); + + if(sd->inventory_data[idx]) { + int i,c; + int ep=sd->inventory_data[idx]->equip; + int fd=sd->fd; + WFIFOW(fd,0)=0x017b; + + for(i=c=0;i<MAX_INVENTORY;i++){ + int j; + + if(sd->inventory_data[i] == NULL) + continue; + if(sd->inventory_data[i]->type!=4 && sd->inventory_data[i]->type!=5) // 武器防具じゃない + continue; + if(sd->status.inventory[i].card[0]==0x00ff) // 製造武器 + continue; + if(sd->status.inventory[i].card[0]==(short)0xff00 || sd->status.inventory[i].card[0]==0x00fe) + continue; + if(sd->status.inventory[i].identify==0 ) // 未鑑定 + continue; + + if((sd->inventory_data[i]->equip&ep)==0) // 装備個所が違う + continue; + if(sd->inventory_data[i]->type==4 && ep==32) // 盾カードと両手武器 + continue; + + for(j=0;j<sd->inventory_data[i]->slot;j++){ + if( sd->status.inventory[i].card[j]==0 ) + break; + } + if(j==sd->inventory_data[i]->slot) // すでにカードが一杯 + continue; + + WFIFOW(fd,4+c*2)=i+2; + c++; + } + WFIFOW(fd,2)=4+c*2; + WFIFOSET(fd,WFIFOW(fd,2)); + } + + return 0; +} +/*========================================== + * カードの挿入終了 + *------------------------------------------ + */ +int clif_insert_card(struct map_session_data *sd,int idx_equip,int idx_card,int flag) +{ + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd,0)=0x17d; + WFIFOW(fd,2)=idx_equip+2; + WFIFOW(fd,4)=idx_card+2; + WFIFOB(fd,6)=flag; + WFIFOSET(fd,packet_len_table[0x17d]); + return 0; +} + +/*========================================== + * 鑑定可能アイテムリスト送信 + *------------------------------------------ + */ +int clif_item_identify_list(struct map_session_data *sd) +{ + int i,c; + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + + WFIFOW(fd,0)=0x177; + for(i=c=0;i<MAX_INVENTORY;i++){ + if(sd->status.inventory[i].nameid > 0 && sd->status.inventory[i].identify!=1){ + WFIFOW(fd,c*2+4)=i+2; + c++; + } + } + if(c > 0) { + WFIFOW(fd,2)=c*2+4; + WFIFOSET(fd,WFIFOW(fd,2)); + } + return 0; +} + +/*========================================== + * 鑑定結果 + *------------------------------------------ + */ +int clif_item_identified(struct map_session_data *sd,int idx,int flag) +{ + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd, 0)=0x179; + WFIFOW(fd, 2)=idx+2; + WFIFOB(fd, 4)=flag; + WFIFOSET(fd,packet_len_table[0x179]); + return 0; +} + +/*========================================== + * 修理可能アイテムリスト送信 + * ※実際のパケットがわからないので動作しません + *------------------------------------------ + */ +int clif_item_repair_list(struct map_session_data *sd) +{ + int i,c; + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + + WFIFOW(fd,0)=0x0; + for(i=c=0;i<MAX_INVENTORY;i++){ + if(sd->status.inventory[i].nameid > 0 && sd->status.inventory[i].broken!=0){ + WFIFOW(fd,c*2+4)=i+2; + c++; + } + } + if(c > 0) { + WFIFOW(fd,2)=c*2+4; + WFIFOSET(fd,WFIFOW(fd,2)); + } + return 0; +} + +/*========================================== + * アイテムによる一時的なスキル効果 + *------------------------------------------ + */ +int clif_item_skill(struct map_session_data *sd,int skillid,int skilllv,const char *name) +{ + int range,fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd, 0)=0x147; + WFIFOW(fd, 2)=skillid; + WFIFOW(fd, 4)=skill_get_inf(skillid); + WFIFOW(fd, 6)=0; + WFIFOW(fd, 8)=skilllv; + WFIFOW(fd,10)=skill_get_sp(skillid,skilllv); + range = skill_get_range(skillid,skilllv); + if(range < 0) + range = battle_get_range(&sd->bl) - (range + 1); + WFIFOW(fd,12)=range; + memcpy(WFIFOP(fd,14),name,24); + WFIFOB(fd,38)=0; + WFIFOSET(fd,packet_len_table[0x147]); + return 0; +} + +/*========================================== + * カートにアイテム追加 + *------------------------------------------ + */ +int clif_cart_additem(struct map_session_data *sd,int n,int amount,int fail) +{ + int view,j,fd; + unsigned char *buf; + + nullpo_retr(0, sd); + + fd=sd->fd; + buf=WFIFOP(fd,0); + if(n<0 || n>=MAX_CART || sd->status.cart[n].nameid<=0) + return 1; + + WBUFW(buf,0)=0x124; + WBUFW(buf,2)=n+2; + WBUFL(buf,4)=amount; + if((view = itemdb_viewid(sd->status.cart[n].nameid)) > 0) + WBUFW(buf,8)=view; + else + WBUFW(buf,8)=sd->status.cart[n].nameid; + WBUFB(buf,10)=sd->status.cart[n].identify; + if(sd->status.cart[n].broken==1) //is weapon broken [Valaris] + WBUFB(buf,11)=1; + else + WBUFB(buf,11)=sd->status.cart[n].attribute; + WBUFB(buf,12)=sd->status.cart[n].refine; + if(sd->status.cart[n].card[0]==0x00ff || sd->status.cart[n].card[0]==0x00fe || sd->status.cart[n].card[0]==(short)0xff00) { + WBUFW(buf,13)=sd->status.cart[n].card[0]; + WBUFW(buf,15)=sd->status.cart[n].card[1]; + WBUFW(buf,17)=sd->status.cart[n].card[2]; + WBUFW(buf,19)=sd->status.cart[n].card[3]; + } else { + if(sd->status.cart[n].card[0] > 0 && (j=itemdb_viewid(sd->status.cart[n].card[0])) > 0) + WBUFW(buf,13)= j; + else + WBUFW(buf,13)= sd->status.cart[n].card[0]; + if(sd->status.cart[n].card[1] > 0 && (j=itemdb_viewid(sd->status.cart[n].card[1])) > 0) + WBUFW(buf,15)= j; + else + WBUFW(buf,15)= sd->status.cart[n].card[1]; + if(sd->status.cart[n].card[2] > 0 && (j=itemdb_viewid(sd->status.cart[n].card[2])) > 0) + WBUFW(buf,17)= j; + else + WBUFW(buf,17)= sd->status.cart[n].card[2]; + if(sd->status.cart[n].card[3] > 0 && (j=itemdb_viewid(sd->status.cart[n].card[3])) > 0) + WBUFW(buf,19)= j; + else + WBUFW(buf,19)= sd->status.cart[n].card[3]; + } + WFIFOSET(fd,packet_len_table[0x124]); + return 0; +} + +/*========================================== + * カートからアイテム削除 + *------------------------------------------ + */ +int clif_cart_delitem(struct map_session_data *sd,int n,int amount) +{ + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + + WFIFOW(fd,0)=0x125; + WFIFOW(fd,2)=n+2; + WFIFOL(fd,4)=amount; + + WFIFOSET(fd,packet_len_table[0x125]); + + return 0; +} + +/*========================================== + * カートのアイテムリスト + *------------------------------------------ + */ +int clif_cart_itemlist(struct map_session_data *sd) +{ + struct item_data *id; + int i,n,fd; + unsigned char *buf; + + nullpo_retr(0, sd); + + fd=sd->fd; + buf = WFIFOP(fd,0); +#if PACKETVER < 5 + WBUFW(buf,0)=0x123; + for(i=0,n=0;i<MAX_CART;i++){ + if(sd->status.cart[i].nameid<=0) + continue; + id = itemdb_search(sd->status.cart[i].nameid); + if(itemdb_isequip2(id)) + continue; + WBUFW(buf,n*10+4)=i+2; + if(id->view_id > 0) + WBUFW(buf,n*10+6)=id->view_id; + else + WBUFW(buf,n*10+6)=sd->status.cart[i].nameid; + WBUFB(buf,n*10+8)=id->type; + WBUFB(buf,n*10+9)=sd->status.cart[i].identify; + WBUFW(buf,n*10+10)=sd->status.cart[i].amount; + WBUFW(buf,n*10+12)=0; + n++; + } + if(n){ + WBUFW(buf,2)=4+n*10; + WFIFOSET(fd,WFIFOW(fd,2)); + } +#else + WBUFW(buf,0)=0x1ef; + for(i=0,n=0;i<MAX_CART;i++){ + if(sd->status.cart[i].nameid<=0) + continue; + id = itemdb_search(sd->status.cart[i].nameid); + if(itemdb_isequip2(id)) + continue; + WBUFW(buf,n*18+4)=i+2; + if(id->view_id > 0) + WBUFW(buf,n*18+6)=id->view_id; + else + WBUFW(buf,n*18+6)=sd->status.cart[i].nameid; + WBUFB(buf,n*18+8)=id->type; + WBUFB(buf,n*18+9)=sd->status.cart[i].identify; + WBUFW(buf,n*18+10)=sd->status.cart[i].amount; + WBUFW(buf,n*18+12)=0; + WBUFW(buf,n*18+14)=sd->status.cart[i].card[0]; + WBUFW(buf,n*18+16)=sd->status.cart[i].card[1]; + WBUFW(buf,n*18+18)=sd->status.cart[i].card[2]; + WBUFW(buf,n*18+20)=sd->status.cart[i].card[3]; + n++; + } + if(n){ + WBUFW(buf,2)=4+n*18; + WFIFOSET(fd,WFIFOW(fd,2)); + } +#endif + return 0; +} + +/*========================================== + * カートの装備品リスト + *------------------------------------------ + */ +int clif_cart_equiplist(struct map_session_data *sd) +{ + struct item_data *id; + int i,j,n,fd; + unsigned char *buf; + + nullpo_retr(0, sd); + + fd=sd->fd; + buf = WFIFOP(fd,0); + + WBUFW(buf,0)=0x122; + for(i=0,n=0;i<MAX_INVENTORY;i++){ + if(sd->status.cart[i].nameid<=0) + continue; + id = itemdb_search(sd->status.cart[i].nameid); + if(!itemdb_isequip2(id)) + continue; + WBUFW(buf,n*20+4)=i+2; + if(id->view_id > 0) + WBUFW(buf,n*20+6)=id->view_id; + else + WBUFW(buf,n*20+6)=sd->status.cart[i].nameid; + WBUFB(buf,n*20+8)=id->type; + WBUFB(buf,n*20+9)=sd->status.cart[i].identify; + WBUFW(buf,n*20+10)=id->equip; + WBUFW(buf,n*20+12)=sd->status.cart[i].equip; + if(sd->status.cart[i].broken==1) + WBUFB(buf,n*20+14)=1; //is weapon broken [Valaris] + else + WBUFB(buf,n*20+14)=sd->status.cart[i].attribute; + WBUFB(buf,n*20+15)=sd->status.cart[i].refine; + if(sd->status.cart[i].card[0]==0x00ff || sd->status.cart[i].card[0]==0x00fe || sd->status.cart[i].card[0]==(short)0xff00) { + WBUFW(buf,n*20+16)=sd->status.cart[i].card[0]; + WBUFW(buf,n*20+18)=sd->status.cart[i].card[1]; + WBUFW(buf,n*20+20)=sd->status.cart[i].card[2]; + WBUFW(buf,n*20+22)=sd->status.cart[i].card[3]; + } else { + if(sd->status.cart[i].card[0] > 0 && (j=itemdb_viewid(sd->status.cart[i].card[0])) > 0) + WBUFW(buf,n*20+16)= j; + else + WBUFW(buf,n*20+16)= sd->status.cart[i].card[0]; + if(sd->status.cart[i].card[1] > 0 && (j=itemdb_viewid(sd->status.cart[i].card[1])) > 0) + WBUFW(buf,n*20+18)= j; + else + WBUFW(buf,n*20+18)= sd->status.cart[i].card[1]; + if(sd->status.cart[i].card[2] > 0 && (j=itemdb_viewid(sd->status.cart[i].card[2])) > 0) + WBUFW(buf,n*20+20)= j; + else + WBUFW(buf,n*20+20)= sd->status.cart[i].card[2]; + if(sd->status.cart[i].card[3] > 0 && (j=itemdb_viewid(sd->status.cart[i].card[3])) > 0) + WBUFW(buf,n*20+22)= j; + else + WBUFW(buf,n*20+22)= sd->status.cart[i].card[3]; + } + n++; + } + if(n){ + WBUFW(buf,2)=4+n*20; + WFIFOSET(fd,WFIFOW(fd,2)); + } + return 0; +} + +/*========================================== + * 露店開設 + *------------------------------------------ + */ +int clif_openvendingreq(struct map_session_data *sd,int num) +{ + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd,0)=0x12d; + WFIFOW(fd,2)=num; + WFIFOSET(fd,packet_len_table[0x12d]); + + return 0; +} + +/*========================================== + * 露店看板表示 + *------------------------------------------ + */ +int clif_showvendingboard(struct block_list* bl,char *message,int fd) +{ + unsigned char buf[128]; + + nullpo_retr(0, bl); + + WBUFW(buf,0)=0x131; + WBUFL(buf,2)=bl->id; + strncpy(WBUFP(buf,6),message,80); + if(fd){ + memcpy(WFIFOP(fd,0),buf,packet_len_table[0x131]); + WFIFOSET(fd,packet_len_table[0x131]); + }else{ + clif_send(buf,packet_len_table[0x131],bl,AREA_WOS); + } + return 0; +} + +/*========================================== + * 露店看板消去 + *------------------------------------------ + */ +int clif_closevendingboard(struct block_list* bl,int fd) +{ + unsigned char buf[16]; + + nullpo_retr(0, bl); + + WBUFW(buf,0)=0x132; + WBUFL(buf,2)=bl->id; + if(fd){ + memcpy(WFIFOP(fd,0),buf,packet_len_table[0x132]); + WFIFOSET(fd,packet_len_table[0x132]); + }else{ + clif_send(buf,packet_len_table[0x132],bl,AREA_WOS); + } + + return 0; +} +/*========================================== + * 露店アイテムリスト + *------------------------------------------ + */ +int clif_vendinglist(struct map_session_data *sd,int id,struct vending *vending) +{ + struct item_data *data; + int i,j,n,index,fd; + struct map_session_data *vsd; + unsigned char *buf; + + nullpo_retr(0, sd); + nullpo_retr(0, vending); + nullpo_retr(0, vsd=map_id2sd(id)); + + fd=sd->fd; + buf = WFIFOP(fd,0); + WBUFW(buf,0)=0x133; + WBUFL(buf,4)=id; + for(i=0,n=0;i<vsd->vend_num;i++){ + if(vending[i].amount<=0) + continue; + WBUFL(buf,8+n*22)=vending[i].value; + WBUFW(buf,12+n*22)=vending[i].amount; + WBUFW(buf,14+n*22)=(index=vending[i].index)+2; + if(vsd->status.cart[index].nameid <= 0 || vsd->status.cart[index].amount <= 0) + continue; + data = itemdb_search(vsd->status.cart[index].nameid); + WBUFB(buf,16+n*22)=data->type; + if(data->view_id > 0) + WBUFW(buf,17+n*22)=data->view_id; + else + WBUFW(buf,17+n*22)=vsd->status.cart[index].nameid; + WBUFB(buf,19+n*22)=vsd->status.cart[index].identify; + if(vsd->status.cart[index].broken==1) + WBUFB(buf,20+n*22)=1; //is weapon broken [Valaris] + else + WBUFB(buf,20+n*22)=vsd->status.cart[index].attribute; + WBUFB(buf,21+n*22)=vsd->status.cart[index].refine; + if(vsd->status.cart[index].card[0]==0x00ff || vsd->status.cart[index].card[0]==0x00fe || vsd->status.cart[index].card[0]==(short)0xff00) { + WBUFW(buf,22+n*22)=vsd->status.cart[index].card[0]; + WBUFW(buf,24+n*22)=vsd->status.cart[index].card[1]; + WBUFW(buf,26+n*22)=vsd->status.cart[index].card[2]; + WBUFW(buf,28+n*22)=vsd->status.cart[index].card[3]; + } else { + if(vsd->status.cart[index].card[0] > 0 && (j=itemdb_viewid(vsd->status.cart[index].card[0])) > 0) + WBUFW(buf,22+n*22)= j; + else + WBUFW(buf,22+n*22)= vsd->status.cart[index].card[0]; + if(vsd->status.cart[index].card[1] > 0 && (j=itemdb_viewid(vsd->status.cart[index].card[1])) > 0) + WBUFW(buf,24+n*22)= j; + else + WBUFW(buf,24+n*22)= vsd->status.cart[index].card[1]; + if(vsd->status.cart[index].card[2] > 0 && (j=itemdb_viewid(vsd->status.cart[index].card[2])) > 0) + WBUFW(buf,26+n*22)= j; + else + WBUFW(buf,26+n*22)= vsd->status.cart[index].card[2]; + if(vsd->status.cart[index].card[3] > 0 && (j=itemdb_viewid(vsd->status.cart[index].card[3])) > 0) + WBUFW(buf,28+n*22)= j; + else + WBUFW(buf,28+n*22)= vsd->status.cart[index].card[3]; + } + n++; + } + if(n > 0){ + WBUFW(buf,2)=8+n*22; + WFIFOSET(fd,WFIFOW(fd,2)); + } + + return 0; +} + +/*========================================== + * 露店アイテム購入失敗 + *------------------------------------------ +*/ +int clif_buyvending(struct map_session_data *sd,int index,int amount,int fail) +{ + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd,0)=0x135; + WFIFOW(fd,2)=index+2; + WFIFOW(fd,4)=amount; + WFIFOB(fd,6)=fail; + WFIFOSET(fd,packet_len_table[0x135]); + + return 0; +} + +/*========================================== + * 露店開設成功 + *------------------------------------------ +*/ +int clif_openvending(struct map_session_data *sd,int id,struct vending *vending) +{ + struct item_data *data; + int i,j,n,index,fd; + unsigned char *buf; + + nullpo_retr(0, sd); + + fd=sd->fd; + buf = WFIFOP(fd,0); + + WBUFW(buf,0)=0x136; + WBUFL(buf,4)=id; + for(i=0,n=0;i<sd->vend_num;i++){ + if (sd->vend_num > 2+pc_checkskill(sd,MC_VENDING)) return 0; + WBUFL(buf,8+n*22)=vending[i].value; + WBUFW(buf,12+n*22)=(index=vending[i].index)+2; + WBUFW(buf,14+n*22)=vending[i].amount; + if(sd->status.cart[index].nameid <= 0 || sd->status.cart[index].amount <= 0 || sd->status.cart[index].identify==0 || + sd->status.cart[index].broken==1) // Prevent unidentified and broken items from being sold [Valaris] + continue; + data = itemdb_search(sd->status.cart[index].nameid); + WBUFB(buf,16+n*22)=data->type; + if(data->view_id > 0) + WBUFW(buf,17+n*22)=data->view_id; + else + WBUFW(buf,17+n*22)=sd->status.cart[index].nameid; + WBUFB(buf,19+n*22)=sd->status.cart[index].identify; + if(sd->status.cart[index].broken==1) + WBUFB(buf,20+n*22)=1; // is weapon broken [Valaris] + else + WBUFB(buf,20+n*22)=sd->status.cart[index].attribute; + WBUFB(buf,21+n*22)=sd->status.cart[index].refine; + if(sd->status.cart[index].card[0]==0x00ff || sd->status.cart[index].card[0]==0x00fe || sd->status.cart[index].card[0]==(short)0xff00) { + WBUFW(buf,22+n*22)=sd->status.cart[index].card[0]; + WBUFW(buf,24+n*22)=sd->status.cart[index].card[1]; + WBUFW(buf,26+n*22)=sd->status.cart[index].card[2]; + WBUFW(buf,28+n*22)=sd->status.cart[index].card[3]; + } else { + if(sd->status.cart[index].card[0] > 0 && (j=itemdb_viewid(sd->status.cart[index].card[0])) > 0) + WBUFW(buf,22+n*22)= j; + else + WBUFW(buf,22+n*22)= sd->status.cart[index].card[0]; + if(sd->status.cart[index].card[1] > 0 && (j=itemdb_viewid(sd->status.cart[index].card[1])) > 0) + WBUFW(buf,24+n*22)= j; + else + WBUFW(buf,24+n*22)= sd->status.cart[index].card[1]; + if(sd->status.cart[index].card[2] > 0 && (j=itemdb_viewid(sd->status.cart[index].card[2])) > 0) + WBUFW(buf,26+n*22)= j; + else + WBUFW(buf,26+n*22)= sd->status.cart[index].card[2]; + if(sd->status.cart[index].card[3] > 0 && (j=itemdb_viewid(sd->status.cart[index].card[3])) > 0) + WBUFW(buf,28+n*22)= j; + else + WBUFW(buf,28+n*22)= sd->status.cart[index].card[3]; + } + n++; + } + if(n > 0){ + WBUFW(buf,2)=8+n*22; + WFIFOSET(fd,WFIFOW(fd,2)); + } + + return n; +} + +/*========================================== + * 露店アイテム販売報告 + *------------------------------------------ +*/ +int clif_vendingreport(struct map_session_data *sd,int index,int amount) +{ + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd,0)=0x137; + WFIFOW(fd,2)=index+2; + WFIFOW(fd,4)=amount; + WFIFOSET(fd,packet_len_table[0x137]); + + return 0; +} + +/*========================================== + * パーティ作成完了 + *------------------------------------------ + */ +int clif_party_created(struct map_session_data *sd,int flag) +{ + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd,0)=0xfa; + WFIFOB(fd,2)=flag; + WFIFOSET(fd,packet_len_table[0xfa]); + return 0; +} +/*========================================== + * パーティ情報送信 + *------------------------------------------ + */ +int clif_party_info(struct party *p,int fd) +{ + unsigned char buf[1024]; + int i,c; + struct map_session_data *sd=NULL; + + nullpo_retr(0, p); + + WBUFW(buf,0)=0xfb; + memcpy(WBUFP(buf,4),p->name,24); + for(i=c=0;i<MAX_PARTY;i++){ + struct party_member *m=&p->member[i]; + if(m->account_id>0){ + if(sd==NULL) sd=m->sd; + WBUFL(buf,28+c*46)=m->account_id; + memcpy(WBUFP(buf,28+c*46+ 4),m->name,24); + memcpy(WBUFP(buf,28+c*46+28),m->map,16); + WBUFB(buf,28+c*46+44)=(m->leader)?0:1; + WBUFB(buf,28+c*46+45)=(m->online)?0:1; + c++; + } + } + WBUFW(buf,2)=28+c*46; + if(fd>=0){ // fdが設定されてるならそれに送る + memcpy(WFIFOP(fd,0),buf,WBUFW(buf,2)); + WFIFOSET(fd,WFIFOW(fd,2)); + return 9; + } + if(sd!=NULL) + clif_send(buf,WBUFW(buf,2),&sd->bl,PARTY); + return 0; +} +/*========================================== + * パーティ勧誘 + *------------------------------------------ + */ +int clif_party_invite(struct map_session_data *sd,struct map_session_data *tsd) +{ + int fd; + struct party *p; + + nullpo_retr(0, sd); + nullpo_retr(0, tsd); + + fd=tsd->fd; + + if( (p=party_search(sd->status.party_id))==NULL ) + return 0; + + WFIFOW(fd,0)=0xfe; + WFIFOL(fd,2)=sd->status.account_id; + memcpy(WFIFOP(fd,6),p->name,24); + WFIFOSET(fd,packet_len_table[0xfe]); + return 0; +} + +/*========================================== + * パーティ勧誘結果 + *------------------------------------------ + */ +int clif_party_inviteack(struct map_session_data *sd,char *nick,int flag) +{ + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd,0)=0xfd; + memcpy(WFIFOP(fd,2),nick,24); + WFIFOB(fd,26)=flag; + WFIFOSET(fd,packet_len_table[0xfd]); + return 0; +} + +/*========================================== + * パーティ設定送信 + * flag & 0x001=exp変更ミス + * 0x010=item変更ミス + * 0x100=一人にのみ送信 + *------------------------------------------ + */ +int clif_party_option(struct party *p,struct map_session_data *sd,int flag) +{ + unsigned char buf[16]; + + nullpo_retr(0, p); + +// if(battle_config.etc_log) +// printf("clif_party_option: %d %d %d\n",p->exp,p->item,flag); + if(sd==NULL && flag==0){ + int i; + for(i=0;i<MAX_PARTY;i++) + if((sd=map_id2sd(p->member[i].account_id))!=NULL) + break; + } + if(sd==NULL) + return 0; + WBUFW(buf,0)=0x101; + WBUFW(buf,2)=((flag&0x01)?2:p->exp); + WBUFW(buf,4)=((flag&0x10)?2:p->item); + if(flag==0) + clif_send(buf,packet_len_table[0x101],&sd->bl,PARTY); + else { + memcpy(WFIFOP(sd->fd,0),buf,packet_len_table[0x101]); + WFIFOSET(sd->fd,packet_len_table[0x101]); + } + return 0; +} +/*========================================== + * パーティ脱退(脱退前に呼ぶこと) + *------------------------------------------ + */ +int clif_party_leaved(struct party *p,struct map_session_data *sd,int account_id,char *name,int flag) +{ + unsigned char buf[64]; + int i; + + nullpo_retr(0, p); + + WBUFW(buf,0)=0x105; + WBUFL(buf,2)=account_id; + memcpy(WBUFP(buf,6),name,24); + WBUFB(buf,30)=flag&0x0f; + + if((flag&0xf0)==0){ + if(sd==NULL) + for(i=0;i<MAX_PARTY;i++) + if((sd=p->member[i].sd)!=NULL) + break; + if (sd!=NULL) + clif_send(buf,packet_len_table[0x105],&sd->bl,PARTY); + } else if (sd!=NULL) { + memcpy(WFIFOP(sd->fd,0),buf,packet_len_table[0x105]); + WFIFOSET(sd->fd,packet_len_table[0x105]); + } + return 0; +} +/*========================================== + * パーティメッセージ送信 + *------------------------------------------ + */ +int clif_party_message(struct party *p,int account_id,char *mes,int len) +{ + struct map_session_data *sd; + int i; + + nullpo_retr(0, p); + + for(i=0;i<MAX_PARTY;i++){ + if((sd=p->member[i].sd)!=NULL) + break; + } + if(sd!=NULL){ + unsigned char buf[1024]; + WBUFW(buf,0)=0x109; + WBUFW(buf,2)=len+8; + WBUFL(buf,4)=account_id; + memcpy(WBUFP(buf,8),mes,len); + clif_send(buf,len+8,&sd->bl,PARTY); + } + return 0; +} +/*========================================== + * パーティ座標通知 + *------------------------------------------ + */ +int clif_party_xy(struct party *p,struct map_session_data *sd) +{ + unsigned char buf[16]; + + nullpo_retr(0, sd); + + WBUFW(buf,0)=0x107; + WBUFL(buf,2)=sd->status.account_id; + WBUFW(buf,6)=sd->bl.x; + WBUFW(buf,8)=sd->bl.y; + clif_send(buf,packet_len_table[0x107],&sd->bl,PARTY_SAMEMAP_WOS); +// if(battle_config.etc_log) +// printf("clif_party_xy %d\n",sd->status.account_id); + return 0; +} +/*========================================== + * パーティHP通知 + *------------------------------------------ + */ +int clif_party_hp(struct party *p,struct map_session_data *sd) +{ + unsigned char buf[16]; + + nullpo_retr(0, sd); + + WBUFW(buf,0)=0x106; + WBUFL(buf,2)=sd->status.account_id; + WBUFW(buf,6)=(sd->status.hp > 0x7fff)? 0x7fff:sd->status.hp; + WBUFW(buf,8)=(sd->status.max_hp > 0x7fff)? 0x7fff:sd->status.max_hp; + clif_send(buf,packet_len_table[0x106],&sd->bl,PARTY_AREA_WOS); +// if(battle_config.etc_log) +// printf("clif_party_hp %d\n",sd->status.account_id); + return 0; +} +/*========================================== + * パーティ場所移動(未使用) + *------------------------------------------ + */ +int clif_party_move(struct party *p,struct map_session_data *sd,int online) +{ + unsigned char buf[128]; + + nullpo_retr(0, sd); + nullpo_retr(0, p); + + WBUFW(buf, 0)=0x104; + WBUFL(buf, 2)=sd->status.account_id; + WBUFL(buf, 6)=0; + WBUFW(buf,10)=sd->bl.x; + WBUFW(buf,12)=sd->bl.y; + WBUFB(buf,14)=!online; + memcpy(WBUFP(buf,15),p->name,24); + memcpy(WBUFP(buf,39),sd->status.name,24); + memcpy(WBUFP(buf,63),map[sd->bl.m].name,16); + clif_send(buf,packet_len_table[0x104],&sd->bl,PARTY); + return 0; +} +/*========================================== + * 攻撃するために移動が必要 + *------------------------------------------ + */ +int clif_movetoattack(struct map_session_data *sd,struct block_list *bl) +{ + int fd; + + nullpo_retr(0, sd); + nullpo_retr(0, bl); + + fd=sd->fd; + WFIFOW(fd, 0)=0x139; + WFIFOL(fd, 2)=bl->id; + WFIFOW(fd, 6)=bl->x; + WFIFOW(fd, 8)=bl->y; + WFIFOW(fd,10)=sd->bl.x; + WFIFOW(fd,12)=sd->bl.y; + WFIFOW(fd,14)=sd->attackrange; + WFIFOSET(fd,packet_len_table[0x139]); + return 0; +} +/*========================================== + * 製造エフェクト + *------------------------------------------ + */ +int clif_produceeffect(struct map_session_data *sd,int flag,int nameid) +{ + int view,fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + // 名前の登録と送信を先にしておく + if( map_charid2nick(sd->status.char_id)==NULL ) + map_addchariddb(sd->status.char_id,sd->status.name); + clif_solved_charname(sd,sd->status.char_id); + + WFIFOW(fd, 0)=0x18f; + WFIFOW(fd, 2)=flag; + if((view = itemdb_viewid(nameid)) > 0) + WFIFOW(fd, 4)=view; + else + WFIFOW(fd, 4)=nameid; + WFIFOSET(fd,packet_len_table[0x18f]); + return 0; +} + +// pet +int clif_catch_process(struct map_session_data *sd) +{ + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd,0)=0x19e; + WFIFOSET(fd,packet_len_table[0x19e]); + + return 0; +} + +int clif_pet_rulet(struct map_session_data *sd,int data) +{ + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd,0)=0x1a0; + WFIFOB(fd,2)=data; + WFIFOSET(fd,packet_len_table[0x1a0]); + + return 0; +} + +/*========================================== + * pet卵リスト作成 + *------------------------------------------ + */ +int clif_sendegg(struct map_session_data *sd) +{ + //R 01a6 <len>.w <index>.w* + int i,n=0,fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd,0)=0x1a6; + if(sd->status.pet_id <= 0) { + for(i=0,n=0;i<MAX_INVENTORY;i++){ + if(sd->status.inventory[i].nameid<=0 || sd->inventory_data[i] == NULL || + sd->inventory_data[i]->type!=7 || + sd->status.inventory[i].amount<=0) + continue; + WFIFOW(fd,n*2+4)=i+2; + n++; + } + } + WFIFOW(fd,2)=4+n*2; + WFIFOSET(fd,WFIFOW(fd,2)); + + return 0; +} + +int clif_send_petdata(struct map_session_data *sd,int type,int param) +{ + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd,0)=0x1a4; + WFIFOB(fd,2)=type; + WFIFOL(fd,3)=sd->pd->bl.id; + WFIFOL(fd,7)=param; + WFIFOSET(fd,packet_len_table[0x1a4]); + + return 0; +} + +int clif_send_petstatus(struct map_session_data *sd) +{ + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd,0)=0x1a2; + memcpy(WFIFOP(fd,2),sd->pet.name,24); + WFIFOB(fd,26)=(battle_config.pet_rename == 1)? 0:sd->pet.rename_flag; + WFIFOW(fd,27)=sd->pet.level; + WFIFOW(fd,29)=sd->pet.hungry; + WFIFOW(fd,31)=sd->pet.intimate; + WFIFOW(fd,33)=sd->pet.equip; + WFIFOSET(fd,packet_len_table[0x1a2]); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_pet_emotion(struct pet_data *pd,int param) +{ + unsigned char buf[16]; + struct map_session_data *sd; + + nullpo_retr(0, pd); + nullpo_retr(0, sd = pd->msd); + + memset(buf,0,packet_len_table[0x1aa]); + + WBUFW(buf,0)=0x1aa; + WBUFL(buf,2)=pd->bl.id; + if(param >= 100 && sd->petDB->talk_convert_class) { + if(sd->petDB->talk_convert_class < 0) + return 0; + else if(sd->petDB->talk_convert_class > 0) { + param -= (pd->class - 100)*100; + param += (sd->petDB->talk_convert_class - 100)*100; + } + } + WBUFL(buf,6)=param; + + clif_send(buf,packet_len_table[0x1aa],&pd->bl,AREA); + + return 0; +} + +int clif_pet_performance(struct block_list *bl,int param) +{ + unsigned char buf[16]; + + nullpo_retr(0, bl); + + memset(buf,0,packet_len_table[0x1a4]); + + WBUFW(buf,0)=0x1a4; + WBUFB(buf,2)=4; + WBUFL(buf,3)=bl->id; + WBUFL(buf,7)=param; + + clif_send(buf,packet_len_table[0x1a4],bl,AREA); + + return 0; +} + +int clif_pet_equip(struct pet_data *pd,int nameid) +{ + unsigned char buf[16]; + int view; + + nullpo_retr(0, pd); + + memset(buf,0,packet_len_table[0x1a4]); + + WBUFW(buf,0)=0x1a4; + WBUFB(buf,2)=3; + WBUFL(buf,3)=pd->bl.id; + if((view = itemdb_viewid(nameid)) > 0) + WBUFL(buf,7)=view; + else + WBUFL(buf,7)=nameid; + + clif_send(buf,packet_len_table[0x1a4],&pd->bl,AREA); + + return 0; +} + +int clif_pet_food(struct map_session_data *sd,int foodid,int fail) +{ + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd,0)=0x1a3; + WFIFOB(fd,2)=fail; + WFIFOW(fd,3)=foodid; + WFIFOSET(fd,packet_len_table[0x1a3]); + + return 0; +} + +/*========================================== + * オートスペル リスト送信 + *------------------------------------------ + */ +int clif_autospell(struct map_session_data *sd,int skilllv) +{ + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd, 0)=0x1cd; + + if(skilllv>0 && pc_checkskill(sd,MG_NAPALMBEAT)>0) + WFIFOL(fd,2)= MG_NAPALMBEAT; + else + WFIFOL(fd,2)= 0x00000000; + if(skilllv>1 && pc_checkskill(sd,MG_COLDBOLT)>0) + WFIFOL(fd,6)= MG_COLDBOLT; + else + WFIFOL(fd,6)= 0x00000000; + if(skilllv>1 && pc_checkskill(sd,MG_FIREBOLT)>0) + WFIFOL(fd,10)= MG_FIREBOLT; + else + WFIFOL(fd,10)= 0x00000000; + if(skilllv>1 && pc_checkskill(sd,MG_LIGHTNINGBOLT)>0) + WFIFOL(fd,14)= MG_LIGHTNINGBOLT; + else + WFIFOL(fd,14)= 0x00000000; + if(skilllv>4 && pc_checkskill(sd,MG_SOULSTRIKE)>0) + WFIFOL(fd,18)= MG_SOULSTRIKE; + else + WFIFOL(fd,18)= 0x00000000; + if(skilllv>7 && pc_checkskill(sd,MG_FIREBALL)>0) + WFIFOL(fd,22)= MG_FIREBALL; + else + WFIFOL(fd,22)= 0x00000000; + if(skilllv>9 && pc_checkskill(sd,MG_FROSTDIVER)>0) + WFIFOL(fd,26)= MG_FROSTDIVER; + else + WFIFOL(fd,26)= 0x00000000; + + WFIFOSET(fd,packet_len_table[0x1cd]); + return 0; +} + +/*========================================== + * ディボーションの青い糸 + *------------------------------------------ + */ +int clif_devotion(struct map_session_data *sd,int target) +{ + unsigned char buf[56]; + int n; + + nullpo_retr(0, sd); + + WBUFW(buf,0)=0x1cf; + WBUFL(buf,2)=sd->bl.id; +// WBUFL(buf,6)=target; + for(n=0;n<5;n++) + WBUFL(buf,6+4*n)=sd->dev.val2[n]; +// WBUFL(buf,10+4*n)=0; + WBUFB(buf,26)=8; + WBUFB(buf,27)=0; + + clif_send(buf,packet_len_table[0x1cf],&sd->bl,AREA); + return 0; +} + +/*========================================== + * 氣球 + *------------------------------------------ + */ +int clif_spiritball(struct map_session_data *sd) +{ + unsigned char buf[16]; + + nullpo_retr(0, sd); + + WBUFW(buf,0)=0x1d0; + WBUFL(buf,2)=sd->bl.id; + WBUFW(buf,6)=sd->spiritball; + clif_send(buf,packet_len_table[0x1d0],&sd->bl,AREA); + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_combo_delay(struct block_list *bl,int wait) +{ + unsigned char buf[32]; + + nullpo_retr(0, bl); + + WBUFW(buf,0)=0x1d2; + WBUFL(buf,2)=bl->id; + WBUFL(buf,6)=wait; + clif_send(buf,packet_len_table[0x1d2],bl,AREA); + + return 0; +} +/*========================================== + *白刃取り + *------------------------------------------ + */ +int clif_bladestop(struct block_list *src,struct block_list *dst, + int bool) +{ + unsigned char buf[32]; + + nullpo_retr(0, src); + nullpo_retr(0, dst); + + WBUFW(buf,0)=0x1d1; + WBUFL(buf,2)=src->id; + WBUFL(buf,6)=dst->id; + WBUFL(buf,10)=bool; + + clif_send(buf,packet_len_table[0x1d1],src,AREA); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_changemapcell(int m,int x,int y,int cell_type,int type) +{ + struct block_list bl; + char buf[32]; + + bl.m = m; + bl.x = x; + bl.y = y; + WBUFW(buf,0) = 0x192; + WBUFW(buf,2) = x; + WBUFW(buf,4) = y; + WBUFW(buf,6) = cell_type; + memcpy(WBUFP(buf,8),map[m].name,16); + if(!type) + clif_send(buf,packet_len_table[0x192],&bl,AREA); + else + clif_send(buf,packet_len_table[0x192],&bl,ALL_SAMEMAP); + + return 0; +} + +/*========================================== + * MVPエフェクト + *------------------------------------------ + */ +int clif_mvp_effect(struct map_session_data *sd) +{ + unsigned char buf[16]; + + nullpo_retr(0, sd); + + WBUFW(buf,0)=0x10c; + WBUFL(buf,2)=sd->bl.id; + clif_send(buf,packet_len_table[0x10c],&sd->bl,AREA); + return 0; +} +/*========================================== + * MVPアイテム所得 + *------------------------------------------ + */ +int clif_mvp_item(struct map_session_data *sd,int nameid) +{ + int view,fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd,0)=0x10a; + if((view = itemdb_viewid(nameid)) > 0) + WFIFOW(fd,2)=view; + else + WFIFOW(fd,2)=nameid; + WFIFOSET(fd,packet_len_table[0x10a]); + return 0; +} +/*========================================== + * MVP経験値所得 + *------------------------------------------ + */ +int clif_mvp_exp(struct map_session_data *sd,int exp) +{ + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd,0)=0x10b; + WFIFOL(fd,2)=exp; + WFIFOSET(fd,packet_len_table[0x10b]); + return 0; +} + +/*========================================== + * ギルド作成可否通知 + *------------------------------------------ + */ +int clif_guild_created(struct map_session_data *sd,int flag) +{ + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd,0)=0x167; + WFIFOB(fd,2)=flag; + WFIFOSET(fd,packet_len_table[0x167]); + return 0; +} +/*========================================== + * ギルド所属通知 + *------------------------------------------ + */ +int clif_guild_belonginfo(struct map_session_data *sd,struct guild *g) +{ + int ps,fd; + + nullpo_retr(0, sd); + nullpo_retr(0, g); + + fd=sd->fd; + ps=guild_getposition(sd,g); + + memset(WFIFOP(fd,0),0,packet_len_table[0x16c]); + WFIFOW(fd,0)=0x16c; + WFIFOL(fd,2)=g->guild_id; + WFIFOL(fd,6)=g->emblem_id; + WFIFOL(fd,10)=g->position[ps].mode; + memcpy(WFIFOP(fd,19),g->name,24); + WFIFOSET(fd,packet_len_table[0x16c]); + return 0; +} +/*========================================== + * ギルドメンバログイン通知 + *------------------------------------------ + */ +int clif_guild_memberlogin_notice(struct guild *g,int idx,int flag) +{ + unsigned char buf[64]; + + nullpo_retr(0, g); + + WBUFW(buf, 0)=0x16d; + WBUFL(buf, 2)=g->member[idx].account_id; + WBUFL(buf, 6)=g->member[idx].char_id; + WBUFL(buf,10)=flag; + if(g->member[idx].sd==NULL){ + struct map_session_data *sd=guild_getavailablesd(g); + if(sd!=NULL) + clif_send(buf,packet_len_table[0x16d],&sd->bl,GUILD); + }else + clif_send(buf,packet_len_table[0x16d],&g->member[idx].sd->bl,GUILD_WOS); + return 0; +} +/*========================================== + * ギルドマスター通知(14dへの応答) + *------------------------------------------ + */ +int clif_guild_masterormember(struct map_session_data *sd) +{ + int type=0x57,fd; + struct guild *g; + + nullpo_retr(0, sd); + + fd=sd->fd; + g=guild_search(sd->status.guild_id); + if(g!=NULL && strcmp(g->master,sd->status.name)==0) + type=0xd7; + WFIFOW(fd,0)=0x14e; + WFIFOL(fd,2)=type; + WFIFOSET(fd,packet_len_table[0x14e]); + return 0; +} +/*========================================== + * Basic Info (Territories [Valaris]) + *------------------------------------------ + */ +int clif_guild_basicinfo(struct map_session_data *sd) +{ + int fd,i,t=0; + struct guild *g; + struct guild_castle *gc=NULL; + + nullpo_retr(0, sd); + + fd=sd->fd; + g=guild_search(sd->status.guild_id); + if(g==NULL) + return 0; + + WFIFOW(fd, 0)=0x1b6;//0x150; + WFIFOL(fd, 2)=g->guild_id; + WFIFOL(fd, 6)=g->guild_lv; + WFIFOL(fd,10)=g->connect_member; + WFIFOL(fd,14)=g->max_member; + WFIFOL(fd,18)=g->average_lv; + WFIFOL(fd,22)=g->exp; + WFIFOL(fd,26)=g->next_exp; + WFIFOL(fd,30)=0; // 上納 + WFIFOL(fd,34)=0; // VW(性格の悪さ?:性向グラフ左右) + WFIFOL(fd,38)=0; // RF(正義の度合い?:性向グラフ上下) + WFIFOL(fd,42)=0; // 人数? + memcpy(WFIFOP(fd,46),g->name,24); + memcpy(WFIFOP(fd,70),g->master,24); + + for(i=0;i<MAX_GUILDCASTLE;i++){ + gc=guild_castle_search(i); + if(!gc) continue; + if(g->guild_id == gc->guild_id) t++; + } + + if (t==1) memcpy(WFIFOP(fd,94),"One Castle",20); + else if (t==2) memcpy(WFIFOP(fd,94),"Two Castles",20); + else if (t==3) memcpy(WFIFOP(fd,94),"Three Castles",20); + else if (t==4) memcpy(WFIFOP(fd,94),"Four Castles",20); + else if (t==5) memcpy(WFIFOP(fd,94),"Five Castles",20); + else if (t==6) memcpy(WFIFOP(fd,94),"Six Castles",20); + else if (t==7) memcpy(WFIFOP(fd,94),"Seven Castles",20); + else if (t==8) memcpy(WFIFOP(fd,94),"Eight Castles",20); + else if (t==9) memcpy(WFIFOP(fd,94),"Nine Castles",20); + else if (t==10) memcpy(WFIFOP(fd,94),"Ten Castles",20); + else if (t==11) memcpy(WFIFOP(fd,94),"Eleven Castles",20); + else if (t==12) memcpy(WFIFOP(fd,94),"Twelve Castles",20); + else if (t==13) memcpy(WFIFOP(fd,94),"Thirteen Castles",20); + else if (t==14) memcpy(WFIFOP(fd,94),"Fourteen Castles",20); + else if (t==15) memcpy(WFIFOP(fd,94),"Fifteen Castles",20); + else if (t==16) memcpy(WFIFOP(fd,94),"Sixteen Castles",20); + else if (t==17) memcpy(WFIFOP(fd,94),"Seventeen Castles",20); + else if (t==18) memcpy(WFIFOP(fd,94),"Eighteen Castles",20); + else if (t==19) memcpy(WFIFOP(fd,94),"Nineteen Castles",20); + else if (t==20) memcpy(WFIFOP(fd,94),"Twenty Castles",20); + else if (t==21) memcpy(WFIFOP(fd,94),"Twenty One Castles",20); + else if (t==22) memcpy(WFIFOP(fd,94),"Twenty Two Castles",20); + else if (t==23) memcpy(WFIFOP(fd,94),"Twenty Three Castles",20); + else if (t==24) memcpy(WFIFOP(fd,94),"Twenty Four Castles",20); + else if (t==MAX_GUILDCASTLE) memcpy(WFIFOP(fd,94),"Total Domination",20); + else memcpy(WFIFOP(fd,94),"None Taken",20); + + WFIFOSET(fd,packet_len_table[WFIFOW(fd,0)]); + clif_guild_emblem(sd,g); // Guild emblem vanish fix [Valaris] + return 0; +} + +/*========================================== + * ギルド同盟/敵対情報 + *------------------------------------------ + */ +int clif_guild_allianceinfo(struct map_session_data *sd) +{ + int fd,i,c; + struct guild *g; + + nullpo_retr(0, sd); + + fd=sd->fd; + g=guild_search(sd->status.guild_id); + if(g==NULL) + return 0; + WFIFOW(fd, 0)=0x14c; + for(i=c=0;i<MAX_GUILDALLIANCE;i++){ + struct guild_alliance *a=&g->alliance[i]; + if(a->guild_id>0){ + WFIFOL(fd,c*32+4)=a->opposition; + WFIFOL(fd,c*32+8)=a->guild_id; + memcpy(WFIFOP(fd,c*32+12),a->name,24); + c++; + } + } + WFIFOW(fd, 2)=c*32+4; + WFIFOSET(fd,WFIFOW(fd,2)); + return 0; +} + +/*========================================== + * ギルドメンバーリスト + *------------------------------------------ + */ +int clif_guild_memberlist(struct map_session_data *sd) +{ + int fd; + int i,c; + struct guild *g; + + nullpo_retr(0, sd); + + fd=sd->fd; + g=guild_search(sd->status.guild_id); + if(g==NULL) + return 0; + + WFIFOW(fd, 0)=0x154; + for(i=0,c=0;i<g->max_member;i++){ + struct guild_member *m=&g->member[i]; + if(m->account_id==0) + continue; + WFIFOL(fd,c*104+ 4)=m->account_id; + WFIFOL(fd,c*104+ 8)=m->char_id; + WFIFOW(fd,c*104+12)=m->hair; + WFIFOW(fd,c*104+14)=m->hair_color; + WFIFOW(fd,c*104+16)=m->gender; + WFIFOW(fd,c*104+18)=m->class; + WFIFOW(fd,c*104+20)=m->lv; + WFIFOL(fd,c*104+22)=m->exp; + WFIFOL(fd,c*104+26)=m->online; + WFIFOL(fd,c*104+30)=m->position; + memset(WFIFOP(fd,c*104+34),0,50); // メモ? + memcpy(WFIFOP(fd,c*104+84),m->name,24); + c++; + } + WFIFOW(fd, 2)=c*104+4; + WFIFOSET(fd,WFIFOW(fd,2)); + return 0; +} +/*========================================== + * ギルド役職名リスト + *------------------------------------------ + */ +int clif_guild_positionnamelist(struct map_session_data *sd) +{ + int i,fd; + struct guild *g; + + nullpo_retr(0, sd); + + fd=sd->fd; + g=guild_search(sd->status.guild_id); + if(g==NULL) + return 0; + WFIFOW(fd, 0)=0x166; + for(i=0;i<MAX_GUILDPOSITION;i++){ + WFIFOL(fd,i*28+4)=i; + memcpy(WFIFOP(fd,i*28+8),g->position[i].name,24); + } + WFIFOW(fd,2)=i*28+4; + WFIFOSET(fd,WFIFOW(fd,2)); + return 0; +} +/*========================================== + * ギルド役職情報リスト + *------------------------------------------ + */ +int clif_guild_positioninfolist(struct map_session_data *sd) +{ + int i,fd; + struct guild *g; + + nullpo_retr(0, sd); + + fd=sd->fd; + g=guild_search(sd->status.guild_id); + if(g==NULL) + return 0; + WFIFOW(fd, 0)=0x160; + for(i=0;i<MAX_GUILDPOSITION;i++){ + struct guild_position *p=&g->position[i]; + WFIFOL(fd,i*16+ 4)=i; + WFIFOL(fd,i*16+ 8)=p->mode; + WFIFOL(fd,i*16+12)=i; + WFIFOL(fd,i*16+16)=p->exp_mode; + } + WFIFOW(fd, 2)=i*16+4; + WFIFOSET(fd,WFIFOW(fd,2)); + return 0; +} +/*========================================== + * ギルド役職変更通知 + *------------------------------------------ + */ +int clif_guild_positionchanged(struct guild *g,int idx) +{ + struct map_session_data *sd; + unsigned char buf[128]; + + nullpo_retr(0, g); + + WBUFW(buf, 0)=0x174; + WBUFW(buf, 2)=44; + WBUFL(buf, 4)=idx; + WBUFL(buf, 8)=g->position[idx].mode; + WBUFL(buf,12)=idx; + WBUFL(buf,16)=g->position[idx].exp_mode; + memcpy(WBUFP(buf,20),g->position[idx].name,24); + if( (sd=guild_getavailablesd(g))!=NULL ) + clif_send(buf,WBUFW(buf,2),&sd->bl,GUILD); + return 0; +} +/*========================================== + * ギルドメンバ変更通知 + *------------------------------------------ + */ +int clif_guild_memberpositionchanged(struct guild *g,int idx) +{ + struct map_session_data *sd; + unsigned char buf[64]; + + nullpo_retr(0, g); + + WBUFW(buf, 0)=0x156; + WBUFW(buf, 2)=16; + WBUFL(buf, 4)=g->member[idx].account_id; + WBUFL(buf, 8)=g->member[idx].char_id; + WBUFL(buf,12)=g->member[idx].position; + if( (sd=guild_getavailablesd(g))!=NULL ) + clif_send(buf,WBUFW(buf,2),&sd->bl,GUILD); + return 0; +} +/*========================================== + * ギルドエンブレム送信 + *------------------------------------------ + */ +int clif_guild_emblem(struct map_session_data *sd,struct guild *g) +{ + int fd; + + nullpo_retr(0, sd); + nullpo_retr(0, g); + + fd=sd->fd; + + if(g->emblem_len<=0) + return 0; + WFIFOW(fd,0)=0x152; + WFIFOW(fd,2)=g->emblem_len+12; + WFIFOL(fd,4)=g->guild_id; + WFIFOL(fd,8)=g->emblem_id; + memcpy(WFIFOP(fd,12),g->emblem_data,g->emblem_len); + WFIFOSET(fd,WFIFOW(fd,2)); + return 0; +} +/*========================================== + * ギルドスキル送信 + *------------------------------------------ + */ +int clif_guild_skillinfo(struct map_session_data *sd) +{ + int fd; + int i,id,c; + struct guild *g; + + nullpo_retr(0, sd); + + fd=sd->fd; + g=guild_search(sd->status.guild_id); + if(g==NULL) + return 0; + WFIFOW(fd,0)=0x0162; + WFIFOW(fd,4)=g->skill_point; + for(i=c=0;i<MAX_GUILDSKILL;i++){ + if(g->skill[i].id>0){ + WFIFOW(fd,c*37+ 6) = id = g->skill[i].id; + WFIFOW(fd,c*37+ 8) = guild_skill_get_inf(id); + WFIFOW(fd,c*37+10) = 0; + WFIFOW(fd,c*37+12) = g->skill[i].lv; + WFIFOW(fd,c*37+14) = guild_skill_get_sp(id,g->skill[i].lv); + WFIFOW(fd,c*37+16) = guild_skill_get_range(id); + memset(WFIFOP(fd,c*37+18),0,24); + WFIFOB(fd,c*37+42)= //up; + (g->skill[i].lv < guild_skill_get_max(id))? 1:0; + c++; + } + } + WFIFOW(fd,2)=c*37+6; + WFIFOSET(fd,WFIFOW(fd,2)); + return 0; +} +/*========================================== + * ギルド告知送信 + *------------------------------------------ + */ +int clif_guild_notice(struct map_session_data *sd,struct guild *g) +{ + int fd; + + nullpo_retr(0, sd); + nullpo_retr(0, g); + + fd=sd->fd; + if(*g->mes1==0 && *g->mes2==0) + return 0; + WFIFOW(fd,0)=0x16f; + memcpy(WFIFOP(fd,2),g->mes1,60); + memcpy(WFIFOP(fd,62),g->mes2,120); + WFIFOSET(fd,packet_len_table[0x16f]); + return 0; +} + +/*========================================== + * ギルドメンバ勧誘 + *------------------------------------------ + */ +int clif_guild_invite(struct map_session_data *sd,struct guild *g) +{ + int fd; + + nullpo_retr(0, sd); + nullpo_retr(0, g); + + fd=sd->fd; + WFIFOW(fd,0)=0x16a; + WFIFOL(fd,2)=g->guild_id; + memcpy(WFIFOP(fd,6),g->name,24); + WFIFOSET(fd,packet_len_table[0x16a]); + return 0; +} +/*========================================== + * ギルドメンバ勧誘結果 + *------------------------------------------ + */ +int clif_guild_inviteack(struct map_session_data *sd,int flag) +{ + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd,0)=0x169; + WFIFOB(fd,2)=flag; + WFIFOSET(fd,packet_len_table[0x169]); + return 0; +} +/*========================================== + * ギルドメンバ脱退通知 + *------------------------------------------ + */ +int clif_guild_leave(struct map_session_data *sd,const char *name,const char *mes) +{ + unsigned char buf[128]; + + nullpo_retr(0, sd); + + WBUFW(buf, 0)=0x15a; + memcpy(WBUFP(buf, 2),name,24); + memcpy(WBUFP(buf,26),mes,40); + clif_send(buf,packet_len_table[0x15a],&sd->bl,GUILD); + return 0; +} +/*========================================== + * ギルドメンバ追放通知 + *------------------------------------------ + */ +int clif_guild_explusion(struct map_session_data *sd,const char *name,const char *mes, + int account_id) +{ + unsigned char buf[128]; + + nullpo_retr(0, sd); + + WBUFW(buf, 0)=0x15c; + memcpy(WBUFP(buf, 2),name,24); + memcpy(WBUFP(buf,26),mes,40); + memcpy(WBUFP(buf,66),"dummy",24); + clif_send(buf,packet_len_table[0x15c],&sd->bl,GUILD); + return 0; +} +/*========================================== + * ギルド追放メンバリスト + *------------------------------------------ + */ +int clif_guild_explusionlist(struct map_session_data *sd) +{ + int fd; + int i,c; + struct guild *g; + + nullpo_retr(0, sd); + + fd=sd->fd; + g=guild_search(sd->status.guild_id); + if(g==NULL) + return 0; + WFIFOW(fd,0)=0x163; + for(i=c=0;i<MAX_GUILDEXPLUSION;i++){ + struct guild_explusion *e=&g->explusion[i]; + if(e->account_id>0){ + memcpy(WFIFOP(fd,c*88+ 4),e->name,24); + memcpy(WFIFOP(fd,c*88+28),e->acc,24); + memcpy(WFIFOP(fd,c*88+52),e->mes,44); + c++; + } + } + WFIFOW(fd,2)=c*88+4; + WFIFOSET(fd,WFIFOW(fd,2)); + return 0; +} + +/*========================================== + * ギルド会話 + *------------------------------------------ + */ +int clif_guild_message(struct guild *g,int account_id,const char *mes,int len) +{ + struct map_session_data *sd; + unsigned char lbuf[255]; + unsigned char *buf = lbuf; + if (len + 32 >= sizeof(lbuf)) + buf = malloc(len + 32); + WBUFW(buf, 0)=0x17f; + WBUFW(buf, 2)=len+4; + memcpy(WBUFP(buf,4),mes,len); + + if( (sd=guild_getavailablesd(g))!=NULL ) + clif_send(buf,WBUFW(buf,2),&sd->bl,GUILD); + if ( buf != lbuf) + free(buf); + return 0; +} +/*========================================== + * ギルドスキル割り振り通知 + *------------------------------------------ + */ +int clif_guild_skillup(struct map_session_data *sd,int skill_num,int lv) +{ + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd,0) = 0x10e; + WFIFOW(fd,2) = skill_num; + WFIFOW(fd,4) = lv; + WFIFOW(fd,6) = guild_skill_get_sp(skill_num,lv); + WFIFOW(fd,8) = guild_skill_get_range(skill_num); + WFIFOB(fd,10) = 1; + WFIFOSET(fd,11); + return 0; +} +/*========================================== + * ギルド同盟要請 + *------------------------------------------ + */ +int clif_guild_reqalliance(struct map_session_data *sd,int account_id,const char *name) +{ + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd,0)=0x171; + WFIFOL(fd,2)=account_id; + memcpy(WFIFOP(fd,6),name,24); + WFIFOSET(fd,packet_len_table[0x171]); + return 0; +} +/*========================================== + * ギルド同盟結果 + *------------------------------------------ + */ +int clif_guild_allianceack(struct map_session_data *sd,int flag) +{ + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd,0)=0x173; + WFIFOL(fd,2)=flag; + WFIFOSET(fd,packet_len_table[0x173]); + return 0; +} +/*========================================== + * ギルド関係解消通知 + *------------------------------------------ + */ +int clif_guild_delalliance(struct map_session_data *sd,int guild_id,int flag) +{ + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd,0)=0x184; + WFIFOL(fd,2)=guild_id; + WFIFOL(fd,6)=flag; + WFIFOSET(fd,packet_len_table[0x184]); + return 0; +} +/*========================================== + * ギルド敵対結果 + *------------------------------------------ + */ +int clif_guild_oppositionack(struct map_session_data *sd,int flag) +{ + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd,0)=0x181; + WFIFOB(fd,2)=flag; + WFIFOSET(fd,packet_len_table[0x181]); + return 0; +} +/*========================================== + * ギルド関係追加 + *------------------------------------------ + */ +/*int clif_guild_allianceadded(struct guild *g,int idx) +{ + unsigned char buf[64]; + WBUFW(fd,0)=0x185; + WBUFL(fd,2)=g->alliance[idx].opposition; + WBUFL(fd,6)=g->alliance[idx].guild_id; + memcpy(WBUFP(fd,10),g->alliance[idx].name,24); + clif_send(buf,packet_len_table[0x185],guild_getavailablesd(g),GUILD); + return 0; +}*/ + +/*========================================== + * ギルド解散通知 + *------------------------------------------ + */ +int clif_guild_broken(struct map_session_data *sd,int flag) +{ + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd,0)=0x15e; + WFIFOL(fd,2)=flag; + WFIFOSET(fd,packet_len_table[0x15e]); + return 0; +} + +/*========================================== + * エモーション + *------------------------------------------ + */ +void clif_emotion(struct block_list *bl,int type) +{ + unsigned char buf[8]; + + nullpo_retv(bl); + + WBUFW(buf,0)=0xc0; + WBUFL(buf,2)=bl->id; + WBUFB(buf,6)=type; + clif_send(buf,packet_len_table[0xc0],bl,AREA); +} + +/*========================================== + * トーキーボックス + *------------------------------------------ + */ +void clif_talkiebox(struct block_list *bl,char* talkie) +{ + unsigned char buf[86]; + + nullpo_retv(bl); + + WBUFW(buf,0)=0x191; + WBUFL(buf,2)=bl->id; + memcpy(WBUFP(buf,6),talkie,80); + clif_send(buf,packet_len_table[0x191],bl,AREA); +} + +/*========================================== + * 結婚エフェクト + *------------------------------------------ + */ +void clif_wedding_effect(struct block_list *bl) { + unsigned char buf[6]; + + nullpo_retv(bl); + + WBUFW(buf,0) = 0x1ea; + WBUFL(buf,2) = bl->id; + clif_send(buf, packet_len_table[0x1ea], bl, AREA); +} +/*========================================== + * あなたに逢いたい使用時名前叫び + *------------------------------------------ + +void clif_callpartner(struct map_session_data *sd) +{ + unsigned char buf[26]; + char *p; + + nullpo_retv(sd); + + if(sd->status.partner_id){ + WBUFW(buf,0)=0x1e6; + p = map_charid2nick(sd->status.partner_id); + if(p){ + memcpy(WBUFP(buf,2),p,24); + }else{ + map_reqchariddb(sd,sd->status.partner_id); + chrif_searchcharid(sd->status.partner_id); + WBUFB(buf,2) = 0; + } + clif_send(buf,packet_len_table[0x1e6]&sd->bl,AREA); + } + return; +} +*/ +/*========================================== + * 座る + *------------------------------------------ + */ +void clif_sitting(int fd, struct map_session_data *sd) +{ + unsigned char buf[64]; + + nullpo_retv(sd); + + WBUFW(buf, 0) = 0x8a; + WBUFL(buf, 2) = sd->bl.id; + WBUFB(buf,26) = 2; + clif_send(buf, packet_len_table[0x8a], &sd->bl, AREA); +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_disp_onlyself(struct map_session_data *sd, char *mes, int len) +{ + unsigned char lbuf[255]; + unsigned char *buf = (len + 32 >= sizeof(lbuf)) ? malloc(len + 32) : lbuf; + + nullpo_retr(0, sd); + + WBUFW(buf, 0) = 0x17f; + WBUFW(buf, 2) = len + 8; + memcpy(WBUFP(buf,4), mes, len + 4); + + clif_send(buf, WBUFW(buf,2), &sd->bl, SELF); + + if (buf != lbuf) + free(buf); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ + +int clif_GM_kickack(struct map_session_data *sd, int id) +{ + int fd; + + nullpo_retr(0, sd); + + fd = sd->fd; + WFIFOW(fd,0) = 0xcd; + WFIFOL(fd,2) = id; + WFIFOSET(fd, packet_len_table[0xcd]); + return 0; +} + +void clif_parse_QuitGame(int fd,struct map_session_data *sd); + +int clif_GM_kick(struct map_session_data *sd,struct map_session_data *tsd,int type) +{ + nullpo_retr(0, tsd); + + if(type) + clif_GM_kickack(sd,tsd->status.account_id); + tsd->opt1 = tsd->opt2 = 0; + clif_parse_QuitGame(tsd->fd,tsd); + + return 0; +} +/*========================================== + * Wis拒否許可応答 + *------------------------------------------ + */ +int clif_wisexin(struct map_session_data *sd,int type,int flag) +{ + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd,0)=0xd1; + WFIFOB(fd,2)=type; + WFIFOB(fd,3)=flag; + WFIFOSET(fd,packet_len_table[0xd1]); + + return 0; +} +/*========================================== + * Wis全拒否許可応答 + *------------------------------------------ + */ +int clif_wisall(struct map_session_data *sd,int type,int flag) +{ + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd,0)=0xd2; + WFIFOB(fd,2)=type; + WFIFOB(fd,3)=flag; + WFIFOSET(fd,packet_len_table[0xd2]); + + return 0; +} +/*========================================== + * サウンドエフェクト + *------------------------------------------ + */ +void clif_soundeffect(struct map_session_data *sd,struct block_list *bl,char *name,int type) +{ + int fd; + + nullpo_retv(sd); + nullpo_retv(bl); + + fd=sd->fd; + WFIFOW(fd,0)=0x1d3; + memcpy(WFIFOP(fd,2),name,24); + WFIFOB(fd,26)=type; + WFIFOL(fd,27)=0; + WFIFOL(fd,31)=bl->id; + WFIFOSET(fd,packet_len_table[0x1d3]); + + return; +} +// displaying special effects (npcs, weather, etc) [Valaris] +int clif_specialeffect(struct block_list *bl, int type, int flag) { + unsigned char buf[24]; + + nullpo_retr(0, bl); + + memset(buf, 0, packet_len_table[0x1f3]); + + WBUFW(buf,0) = 0x1f3; + WBUFL(buf,2) = bl->id; + WBUFL(buf,6) = type; + + if (flag==2) { + struct map_session_data *sd=NULL; + int i; + for(i=0; i < fd_max; i++) { + if (session[i] && (sd = session[i]->session_data) != NULL && sd->state.auth && sd->bl.m == bl->m) + clif_specialeffect(&sd->bl,type,1); + } + } + + else if (flag==1) + clif_send(buf, packet_len_table[0x1f3], bl, SELF); + else if (!flag) + clif_send(buf, packet_len_table[0x1f3], bl, AREA); + + return 0; + +} +// ------------ +// clif_parse_* +// ------------ +// パケット読み取って色々操作 +/*========================================== + * + *------------------------------------------ + */ +void clif_parse_WantToConnection(int fd, struct map_session_data *sd) +{ + struct map_session_data *old_sd; + int account_id; // account_id in the packet 0x72 or 0x7E + + if (sd) { + if (battle_config.error_log) + printf("clif_parse_WantToConnection : invalid request?\n"); + return; + } + + // 0x72 + if (RFIFOW(fd,0) == 0x72) { + //printf("Received bytes %d with packet 0x72.\n", RFIFOREST(fd)); + if (RFIFOREST(fd) >= 39 && (RFIFOB(fd,38) == 0 || RFIFOB(fd,38) == 1)) // 00 = Female, 01 = Male + account_id = RFIFOL(fd,12); + else if (RFIFOREST(fd) >= 22 && (RFIFOB(fd,21) == 0 || RFIFOB(fd,21) == 1)) // 00 = Female, 01 = Male + account_id = RFIFOL(fd,5); + else // old packet version + account_id = RFIFOL(fd,2); + // 0x7E + } else if (RFIFOW(fd,0) == 0x7E) { + //printf("Received bytes %d with packet 0x7E.\n", RFIFOREST(fd)); + if (RFIFOREST(fd) >= 37 && (RFIFOB(fd,36) == 0 || RFIFOB(fd,36) == 1)) // 00 = Female, 01 = Male + account_id = RFIFOL(fd,9); + else + account_id = RFIFOL(fd,12); + // 0xF5 + } else { + //printf("Received bytes %d with packet 0xF5.\n", RFIFOREST(fd)); + account_id = RFIFOL(fd,7); + } + + // if same account already connected, we disconnect the 2 sessions + if ((old_sd = map_id2sd(account_id)) != NULL) { + clif_authfail_fd(fd, 2); // same id + clif_authfail_fd(old_sd->fd, 2); // same id + printf("clif_parse_WantToConnection: Double connection for account %d (sessions: #%d (new) and #%d (old)).\n", account_id, fd, old_sd->fd); + } else { + sd = session[fd]->session_data = calloc(sizeof(*sd), 1); + if (sd == NULL) { + printf("out of memory : clif_parse_WantToConnection\n"); + exit(1); + } + sd->fd = fd; + + // 0x72 + if (RFIFOW(fd,0) == 0x72) { + if (RFIFOREST(fd) >= 39 && (RFIFOB(fd,38) == 0 || RFIFOB(fd,38) == 1)) { // 00 = Female, 01 = Male + sd->packet_ver = 7; // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + pc_setnewpc(sd, account_id, RFIFOL(fd,22), RFIFOL(fd,30), RFIFOL(fd,34), RFIFOB(fd,38), fd); + } else if (RFIFOREST(fd) >= 22 && (RFIFOB(fd,21) == 0 || RFIFOB(fd,21) == 1)) { // 00 = Female, 01 = Male + sd->packet_ver = 6; // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + pc_setnewpc(sd, account_id, RFIFOL(fd,9), RFIFOL(fd,13), RFIFOL(fd,17), RFIFOB(fd,21), fd); + } else { // old packet version + sd->packet_ver = 5; // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + pc_setnewpc(sd, account_id, RFIFOL(fd,6), RFIFOL(fd,10), RFIFOL(fd,14), RFIFOB(fd,18), fd); + } + // 0x7E + } else if (RFIFOW(fd,0) == 0x7E) { + if (RFIFOREST(fd) >= 37 && (RFIFOB(fd,36) == 0 || RFIFOB(fd,36) == 1)) { // 00 = Female, 01 = Male + sd->packet_ver = 9; // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + pc_setnewpc(sd, account_id, RFIFOL(fd,21), RFIFOL(fd,28), RFIFOL(fd,32), RFIFOB(fd,36), fd); + } else { + sd->packet_ver = 8; // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + pc_setnewpc(sd, account_id, RFIFOL(fd,18), RFIFOL(fd,24), RFIFOL(fd,28), RFIFOB(fd,32), fd); + } + // 0xF5 + } else { + sd->packet_ver = 10; // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + pc_setnewpc(sd, account_id, RFIFOL(fd,15), RFIFOL(fd,25), RFIFOL(fd,29), RFIFOB(fd,33), fd); + } + + WFIFOL(fd,0) = sd->bl.id; + WFIFOSET(fd,4); + + map_addiddb(&sd->bl); + + chrif_authreq(sd); + } + + return; +} + +/*========================================== + * 007d クライアント側マップ読み込み完了 + * map侵入時に必要なデータを全て送りつける + *------------------------------------------ + */ +void clif_parse_LoadEndAck(int fd,struct map_session_data *sd) +{ +// struct item_data* item; + int i; + nullpo_retv(sd); + + if(sd->bl.prev != NULL) + return; + + // 接続ok時 + //clif_authok(); + if(sd->npc_id) npc_event_dequeue(sd); + clif_skillinfoblock(sd); + pc_checkitem(sd); + //guild_info(); + + // loadendack時 + // next exp + clif_updatestatus(sd,SP_NEXTBASEEXP); + clif_updatestatus(sd,SP_NEXTJOBEXP); + // skill point + clif_updatestatus(sd,SP_SKILLPOINT); + // item + clif_itemlist(sd); + clif_equiplist(sd); + // cart + if(pc_iscarton(sd)){ + clif_cart_itemlist(sd); + clif_cart_equiplist(sd); + clif_updatestatus(sd,SP_CARTINFO); + } + // param all + clif_initialstatus(sd); + // party + party_send_movemap(sd); + // guild + guild_send_memberinfoshort(sd,1); + // 119 + // 78 + + if(battle_config.pc_invincible_time > 0) { + if(map[sd->bl.m].flag.gvg) + pc_setinvincibletimer(sd,battle_config.pc_invincible_time<<1); + else + pc_setinvincibletimer(sd,battle_config.pc_invincible_time); + } + + map_addblock(&sd->bl); // ブロック登録 + clif_spawnpc(sd); // spawn + + // weight max , now + clif_updatestatus(sd,SP_MAXWEIGHT); + clif_updatestatus(sd,SP_WEIGHT); + + // pvp + if(sd->pvp_timer!=-1 && !battle_config.pk_mode) + delete_timer(sd->pvp_timer,pc_calc_pvprank_timer); + if(map[sd->bl.m].flag.pvp){ + if(!battle_config.pk_mode) { // remove pvp stuff for pk_mode [Valaris] + sd->pvp_timer=add_timer(gettick()+200,pc_calc_pvprank_timer,sd->bl.id,0); + sd->pvp_rank=0; + sd->pvp_lastusers=0; + sd->pvp_point=5; + } + clif_set0199(sd->fd,1); + } else { + sd->pvp_timer=-1; + } + if(map[sd->bl.m].flag.gvg) { + clif_set0199(sd->fd,3); + } + + // pet + if(sd->status.pet_id > 0 && sd->pd && sd->pet.intimate > 0) { + map_addblock(&sd->pd->bl); + clif_spawnpet(sd->pd); + clif_send_petdata(sd,0,0); + clif_send_petdata(sd,5,0x14); + clif_send_petstatus(sd); + } + + if(sd->state.connect_new) { + sd->state.connect_new = 0; + if(sd->status.class != sd->view_class) + clif_changelook(&sd->bl,LOOK_BASE,sd->view_class); + if(sd->status.pet_id > 0 && sd->pd && sd->pet.intimate > 900) + clif_pet_emotion(sd->pd,(sd->pd->class - 100)*100 + 50 + pet_hungry_val(sd)); + +/* Stop players from spawning inside castles [Valaris] */ + + { + struct guild_castle *gc=guild_mapname2gc(map[sd->bl.m].name); + if (gc) + pc_setpos(sd,sd->status.save_point.map,sd->status.save_point.x,sd->status.save_point.y,2); + } + +/* End Addition [Valaris] */ + + } + + // view equipment item +#if PACKETVER < 4 + clif_changelook(&sd->bl,LOOK_WEAPON,sd->status.weapon); + clif_changelook(&sd->bl,LOOK_SHIELD,sd->status.shield); +#else + clif_changelook(&sd->bl,LOOK_WEAPON,0); +#endif + if(battle_config.save_clothcolor==1 && sd->status.clothes_color > 0) + clif_changelook(&sd->bl,LOOK_CLOTHES_COLOR,sd->status.clothes_color); + + if(sd->status.hp<sd->status.max_hp>>2 && pc_checkskill(sd,SM_AUTOBERSERK)>0 && + (sd->sc_data[SC_PROVOKE].timer==-1 || sd->sc_data[SC_PROVOKE].val2==0 )) + // オートバーサーク発動 + skill_status_change_start(&sd->bl,SC_PROVOKE,10,1,0,0,0,0); + +// if(time(&timer) < ((weddingtime=pc_readglobalreg(sd,"PC_WEDDING_TIME")) + 3600)) +// skill_status_change_start(&sd->bl,SC_WEDDING,0,weddingtime,0,0,36000,0); + + if(battle_config.muting_players && sd->status.manner < 0) + skill_status_change_start(&sd->bl,SC_NOCHAT,0,0,0,0,0,0); + + // option + clif_changeoption(&sd->bl); + if(sd->sc_data[SC_TRICKDEAD].timer != -1) + skill_status_change_end(&sd->bl,SC_TRICKDEAD,-1); + if(sd->sc_data[SC_SIGNUMCRUCIS].timer != -1 && !battle_check_undead(7,sd->def_ele)) + skill_status_change_end(&sd->bl,SC_SIGNUMCRUCIS,-1); + if(sd->special_state.infinite_endure && sd->sc_data[SC_ENDURE].timer == -1) + skill_status_change_start(&sd->bl,SC_ENDURE,10,1,0,0,0,0); + for(i=0;i<MAX_INVENTORY;i++){ + if(sd->status.inventory[i].equip && sd->status.inventory[i].equip & 0x0002 && sd->status.inventory[i].broken==1) + skill_status_change_start(&sd->bl,SC_BROKNWEAPON,0,0,0,0,0,0); + if(sd->status.inventory[i].equip && sd->status.inventory[i].equip & 0x0010 && sd->status.inventory[i].broken==1) + skill_status_change_start(&sd->bl,SC_BROKNARMOR,0,0,0,0,0,0); + } + + map_foreachinarea(clif_getareachar,sd->bl.m,sd->bl.x-AREA_SIZE,sd->bl.y-AREA_SIZE,sd->bl.x+AREA_SIZE,sd->bl.y+AREA_SIZE,0,sd); +} + +/*========================================== + * + *------------------------------------------ + */ +void clif_parse_TickSend(int fd, struct map_session_data *sd) { + nullpo_retv(sd); + + if (sd->packet_ver == 8) // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + sd->client_tick = RFIFOL(fd,6); + else if (sd->packet_ver == 9) // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + sd->client_tick = RFIFOL(fd,9); + else if (sd->packet_ver == 10) // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + sd->client_tick = RFIFOL(fd,7); + else // old version by default (and version 6 + 7) + sd->client_tick = RFIFOL(fd,2); + sd->server_tick = gettick(); + clif_servertick(sd); +} + +/*========================================== + * + *------------------------------------------ + */ +void clif_parse_WalkToXY(int fd, struct map_session_data *sd) { + int x, y; + + nullpo_retv(sd); + + if (pc_isdead(sd)) { + clif_clearchar_area(&sd->bl, 1); + return; + } + + if (sd->npc_id != 0 || sd->vender_id != 0) + return; + + if (sd->skilltimer != -1 && pc_checkskill(sd, SA_FREECAST) <= 0) // フリーキャスト + return; + + if (sd->chatID) + return; + + if (sd->canmove_tick > gettick()) + return; + + // ステータス異常やハイディング中(トンネルドライブ無)で動けない + if ((sd->opt1 > 0 && sd->opt1 != 6) || + sd->sc_data[SC_ANKLE].timer !=-1 || //アンクルスネア + sd->sc_data[SC_AUTOCOUNTER].timer !=-1 || //オートカウンター + sd->sc_data[SC_TRICKDEAD].timer !=-1 || //死んだふり + sd->sc_data[SC_BLADESTOP].timer !=-1 || //白刃取り + sd->sc_data[SC_SPIDERWEB].timer !=-1 || //スパイダーウェッブ + (sd->sc_data[SC_DANCING].timer !=-1 && sd->sc_data[SC_DANCING].val4)) //合奏スキル演奏中は動けない + return; + if ((sd->status.option & 2) && pc_checkskill(sd, RG_TUNNELDRIVE) <= 0) + return; + + if (sd->invincible_timer != -1) + pc_delinvincibletimer(sd); + + pc_stopattack(sd); + + if (sd->packet_ver == 6) { // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + x = RFIFOB(fd,5) * 4 + (RFIFOB(fd,6) >> 6); + y = ((RFIFOB(fd,6) & 0x3f) << 4) + (RFIFOB(fd,7) >> 4); + } else if (sd->packet_ver == 7) { // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + x = RFIFOB(fd,6) * 4 + (RFIFOB(fd,7) >> 6); + y = ((RFIFOB(fd,7) & 0x3f) << 4) + (RFIFOB(fd,8) >> 4); + } else if (sd->packet_ver == 8) { // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + x = RFIFOB(fd,3) * 4 + (RFIFOB(fd,4) >> 6); + y = ((RFIFOB(fd,4) & 0x3f) << 4) + (RFIFOB(fd,5) >> 4); + } else if (sd->packet_ver == 9) { // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + x = RFIFOB(fd,12) * 4 + (RFIFOB(fd,13) >> 6); + y = ((RFIFOB(fd,13) & 0x3f) << 4) + (RFIFOB(fd,14) >> 4); + } else if (sd->packet_ver == 10) { // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + x = RFIFOB(fd,6) * 4 + (RFIFOB(fd,7) >> 6); + y = ((RFIFOB(fd,7) & 0x3f) << 4) + (RFIFOB(fd,8) >> 4); + } else { // old version by default + x = RFIFOB(fd,2) * 4 + (RFIFOB(fd,3) >> 6); + y = ((RFIFOB(fd,3) & 0x3f) << 4) + (RFIFOB(fd,4) >> 4); + } + pc_walktoxy(sd, x, y); + +} + +/*========================================== + * + *------------------------------------------ + */ +void clif_parse_QuitGame(int fd, struct map_session_data *sd) { + unsigned int tick=gettick(); + struct skill_unit_group* sg; + + nullpo_retv(sd); + + WFIFOW(fd,0) = 0x18b; + if ((!pc_isdead(sd) && (sd->opt1 || (sd->opt2 && !(night_flag == 1 && sd->opt2 == STATE_BLIND)))) || + sd->skilltimer != -1 || + (DIFF_TICK(tick , sd->canact_tick) < 0) || + (sd->sc_data && sd->sc_data[SC_DANCING].timer!=-1 && sd->sc_data[SC_DANCING].val4 && (sg=(struct skill_unit_group *)sd->sc_data[SC_DANCING].val2) && sg->src_id == sd->bl.id)) { + WFIFOW(fd,2)=1; + WFIFOSET(fd,packet_len_table[0x18b]); + return; + } + + /* Rovert's prevent logout option fixed [Valaris] */ + if ((battle_config.prevent_logout && (gettick() - sd->canlog_tick) >= 10000) || (!battle_config.prevent_logout)) { + clif_setwaitclose(fd); + WFIFOW(fd,2)=0; + } else { + WFIFOW(fd,2)=1; + } + WFIFOSET(fd,packet_len_table[0x18b]); + +} + +/*========================================== + * + *------------------------------------------ + */ +void clif_parse_GetCharNameRequest(int fd, struct map_session_data *sd) { + struct block_list *bl; + int account_id; + + if (sd->packet_ver == 8) { // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + account_id = RFIFOL(fd,11); + } else if (sd->packet_ver == 9) { // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + account_id = RFIFOL(fd,8); + } else if (sd->packet_ver == 10) { // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + account_id = RFIFOL(fd,10); + } else { // old version by default (+ packet version 6 and 7) + account_id = RFIFOL(fd,2); + } + bl = map_id2bl(account_id); + if (bl == NULL) + return; + + WFIFOW(fd,0) = 0x95; + WFIFOL(fd,2) = account_id; + + switch(bl->type) { + case BL_PC: + { + struct map_session_data *ssd = (struct map_session_data *)bl; + struct party *p = NULL; + struct guild *g = NULL; + + nullpo_retv(ssd); + + memcpy(WFIFOP(fd,6), ssd->status.name, 24); + if (ssd->status.guild_id > 0 && (g = guild_search(ssd->status.guild_id)) != NULL && + (ssd->status.party_id == 0 || (p = party_search(ssd->status.party_id)) != NULL)) { + // ギルド所属ならパケット0195を返す + int i, ps = -1; + for(i = 0; i < g->max_member; i++) { + if (g->member[i].account_id == ssd->status.account_id && + g->member[i].char_id == ssd->status.char_id ) + ps = g->member[i].position; + } + if (ps >= 0 && ps < MAX_GUILDPOSITION) { + WFIFOW(fd, 0) = 0x195; + if (p) + memcpy(WFIFOP(fd,30), p->name, 24); + else + WFIFOB(fd,30) = 0; + memcpy(WFIFOP(fd,54), g->name,24); + memcpy(WFIFOP(fd,78), g->position[ps].name, 24); + WFIFOSET(fd,packet_len_table[0x195]); + break; + } + } + WFIFOSET(fd,packet_len_table[0x95]); + } + break; + case BL_PET: + memcpy(WFIFOP(fd,6), ((struct pet_data*)bl)->name, 24); + WFIFOSET(fd,packet_len_table[0x95]); + break; + case BL_NPC: + memcpy(WFIFOP(fd,6), ((struct npc_data*)bl)->name, 24); + WFIFOSET(fd,packet_len_table[0x95]); + break; + case BL_MOB: + { + struct mob_data *md = (struct mob_data *)bl; + + nullpo_retv(md); + + memcpy(WFIFOP(fd,6), md->name, 24); + if (md->class >= 1285 && md->class <= 1288) { + struct guild *g; + struct guild_castle *gc = guild_mapname2gc(map[md->bl.m].name); + if (gc && gc->guild_id > 0 && (g = guild_search(gc->guild_id)) != NULL) { + WFIFOW(fd, 0) = 0x195; + WFIFOB(fd,30) = 0; + memcpy(WFIFOP(fd,54), g->name, 24); + memcpy(WFIFOP(fd,78), gc->castle_name, 24); + WFIFOSET(fd,packet_len_table[0x195]); + } else { + WFIFOSET(fd,packet_len_table[0x95]); + } + } else if (battle_config.show_mob_hp == 1) { + char mobhp[50]; + sprintf(mobhp, "hp: %d/%d", md->hp, mob_db[md->class].max_hp); + WFIFOW(fd, 0) = 0x195; + memcpy(WFIFOP(fd,30), mobhp, 24); + WFIFOB(fd,54) = 0; + WFIFOB(fd,78) = 0; + WFIFOSET(fd,packet_len_table[0x195]); + } else { + WFIFOSET(fd,packet_len_table[0x95]); + } + } + break; + default: + if (battle_config.error_log) + printf("clif_parse_GetCharNameRequest : bad type %d(%d)\n", bl->type, account_id); + break; + } +} + +/*========================================== + * + *------------------------------------------ + */ +void clif_parse_GlobalMessage(int fd, struct map_session_data *sd) { // S 008c <len>.w <str>.?B + char *message = (char *) malloc(RFIFOW(fd,2) + 128); + char *buf = (char *) malloc(RFIFOW(fd,2) + 4); + + nullpo_retv(sd); + + memset(message, '\0', RFIFOW(fd,2) + 128); + memset(buf, '\0', RFIFOW(fd,2) + 4); + + if ((is_atcommand(fd, sd, RFIFOP(fd,4), 0) != AtCommand_None) || + ( sd->sc_data && + (sd->sc_data[SC_BERSERK].timer!=-1 || //バーサーク時は会話も不可 + sd->sc_data[SC_NOCHAT].timer!=-1 ) )) //チャット禁止 + { + free(message); + free(buf); + return; + } + + //printf("clif_parse_GlobalMessage: message: '%s'.\n", RFIFOP(fd,4)); + if (strncmp(RFIFOP(fd,4), sd->status.name, strlen(sd->status.name)) != 0) { + printf("Hack on global message: character '%s' (account: %d), use an other name to send a (normal) message.\n", sd->status.name, sd->status.account_id); + + // information is sended to all online GM + sprintf(message, "Hack on global message (normal message): character '%s' (account: %d) uses an other name.", sd->status.name, sd->status.account_id); + intif_wis_message_to_gm(wisp_server_name, battle_config.hack_info_GM_level, message, strlen(message) + 1); + if (strlen(RFIFOP(fd,4)) == 0) + strcpy(message, " This player sends a void name and a void message."); + else + sprintf(message, " This player sends (name:message): '%s'.", RFIFOP(fd,4)); + intif_wis_message_to_gm(wisp_server_name, battle_config.hack_info_GM_level, message, strlen(message) + 1); + // message about the ban + if (battle_config.ban_spoof_namer > 0) + sprintf(message, " This player has been banned for %d minute(s).", battle_config.ban_spoof_namer); + else + sprintf(message, " This player hasn't been banned (Ban option is disabled)."); + intif_wis_message_to_gm(wisp_server_name, battle_config.hack_info_GM_level, message, strlen(message) + 1); + + // if we ban people + if (battle_config.ban_spoof_namer > 0) { + chrif_char_ask_name(-1, sd->status.name, 2, 0, 0, 0, 0, battle_config.ban_spoof_namer, 0); // type: 2 - ban (year, month, day, hour, minute, second) + clif_setwaitclose(fd); // forced to disconnect because of the hack + } + // but for the hacker, we display on his screen (he see/look no difference). + } else { + // send message to others + WBUFW(buf,0) = 0x8d; + WBUFW(buf,2) = RFIFOW(fd,2) + 4; // len of message - 4 + 8 + WBUFL(buf,4) = sd->bl.id; + memcpy(WBUFP(buf,8), RFIFOP(fd,4), RFIFOW(fd,2) - 4); + clif_send(buf, WBUFW(buf,2), &sd->bl, sd->chatID ? CHAT_WOS : AREA_CHAT_WOC); + } + + // send back message to the speaker + memcpy(WFIFOP(fd,0), RFIFOP(fd,0), RFIFOW(fd,2)); + WFIFOW(fd,0) = 0x8e; + WFIFOSET(fd, WFIFOW(fd,2)); + + free(message); + free(buf); + + return; +} + +int clif_message(struct block_list *bl, char* msg) +{ + unsigned short msg_len = strlen(msg) + 1; + unsigned char buf[256]; + + nullpo_retr(0, bl); + + WBUFW(buf, 0) = 0x8d; + WBUFW(buf, 2) = msg_len + 8; + WBUFL(buf, 4) = bl->id; + memcpy(WBUFP(buf, 8), msg, msg_len); + + clif_send(buf, WBUFW(buf,2), bl, AREA); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +void clif_parse_MapMove(int fd, struct map_session_data *sd) { +// /m /mapmove (as @rura GM command) + char output[100]; + char map_name[17]; + + nullpo_retv(sd); + + memset(output, '\0', sizeof(output)); + memset(map_name, '\0', sizeof(map_name)); + + if ((battle_config.atc_gmonly == 0 || pc_isGM(sd)) && + (pc_isGM(sd) >= get_atcommand_level(AtCommand_MapMove))) { + memcpy(map_name, RFIFOP(fd,2), 16); + sprintf(output, "%s %d %d", map_name, RFIFOW(fd,18), RFIFOW(fd,20)); + atcommand_rura(fd, sd, "@rura", output); + } + + return; +} + +/*========================================== + * + *------------------------------------------ + */ +void clif_parse_ChangeDir(int fd, struct map_session_data *sd) { + unsigned char buf[64]; + short headdir, dir; + + nullpo_retv(sd); + + if (sd->packet_ver == 7) { // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + headdir = RFIFOW(fd,5); + dir = RFIFOB(fd,12); + } else if (sd->packet_ver == 8) { // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + headdir = RFIFOW(fd,5); + dir = RFIFOB(fd,12); + } else if (sd->packet_ver == 9) { // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + headdir = RFIFOW(fd,7); + dir = RFIFOB(fd,11); + } else if (sd->packet_ver == 10) { // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + headdir = RFIFOW(fd,4); + dir = RFIFOB(fd,9); + } else { // old version by default (and packet version 6) + headdir = RFIFOW(fd,2); + dir = RFIFOB(fd,4); + } + + pc_setdir(sd, dir, headdir); + + WBUFW(buf,0) = 0x9c; + WBUFL(buf,2) = sd->bl.id; + WBUFW(buf,6) = headdir; + WBUFB(buf,8) = dir; + if (sd->disguise > 23 && sd->disguise < 4001) // mob disguises [Valaris] + clif_send(buf, packet_len_table[0x9c], &sd->bl, AREA); + else + clif_send(buf, packet_len_table[0x9c], &sd->bl, AREA_WOS); + +} + +/*========================================== + * + *------------------------------------------ + */ +void clif_parse_Emotion(int fd, struct map_session_data *sd) { + unsigned char buf[64]; + + nullpo_retv(sd); + + if (battle_config.basic_skill_check == 0 || pc_checkskill(sd, NV_BASIC) >= 2) { + WBUFW(buf,0) = 0xc0; + WBUFL(buf,2) = sd->bl.id; + WBUFB(buf,6) = RFIFOB(fd,2); + clif_send(buf, packet_len_table[0xc0], &sd->bl, AREA); + } else + clif_skill_fail(sd, 1, 0, 1); +} + +/*========================================== + * + *------------------------------------------ + */ +void clif_parse_HowManyConnections(int fd, struct map_session_data *sd) { + WFIFOW(fd,0) = 0xc2; + WFIFOL(fd,2) = map_getusers(); + WFIFOSET(fd,packet_len_table[0xc2]); +} + +/*========================================== + * + *------------------------------------------ + */ +void clif_parse_ActionRequest(int fd, struct map_session_data *sd) { + unsigned int tick; + unsigned char buf[64]; + int action_type, target_id; + + nullpo_retv(sd); + + if (pc_isdead(sd)) { + clif_clearchar_area(&sd->bl, 1); + return; + } + if (sd->npc_id != 0 || sd->opt1 > 0 || sd->status.option & 2 || + (sd->sc_data && + (sd->sc_data[SC_AUTOCOUNTER].timer != -1 || //オートカウンター + sd->sc_data[SC_BLADESTOP].timer != -1 || //白刃取り + sd->sc_data[SC_DANCING].timer != -1))) + return; + + tick = gettick(); + + pc_stop_walking(sd, 0); + pc_stopattack(sd); + + if (sd->packet_ver == 8) { // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + target_id = RFIFOL(fd,3); + action_type = RFIFOB(fd,8); + } else if (sd->packet_ver == 9) { // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + target_id = RFIFOL(fd,7); + action_type = RFIFOB(fd,17); + } else if (sd->packet_ver == 10) { // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + target_id = RFIFOL(fd,9); + action_type = RFIFOB(fd,22); + } else { // old version by default (and packet version 6 and 7) + target_id = RFIFOL(fd,2); + action_type = RFIFOB(fd,6); + } + + switch(action_type) { + case 0x00: // once attack + case 0x07: // continuous attack + if(sd->sc_data[SC_WEDDING].timer != -1 || sd->view_class==22) + return; + if (sd->vender_id != 0) + return; + if (!battle_config.sdelay_attack_enable && pc_checkskill(sd, SA_FREECAST) <= 0) { + if (DIFF_TICK(tick, sd->canact_tick) < 0) { + clif_skill_fail(sd, 1, 4, 0); + return; + } + } + if (sd->invincible_timer != -1) + pc_delinvincibletimer(sd); + if (sd->attacktarget > 0) // [Valaris] + sd->attacktarget = 0; + pc_attack(sd, target_id, action_type != 0); + break; + case 0x02: // sitdown + if (battle_config.basic_skill_check == 0 || pc_checkskill(sd, NV_BASIC) >= 3) { + pc_stop_walking(sd, 1); + skill_gangsterparadise(sd, 1); // ギャングスターパラダイス設定 + pc_setsit(sd); + clif_sitting(fd, sd); + } else + clif_skill_fail(sd, 1, 0, 2); + break; + case 0x03: // standup + skill_gangsterparadise(sd, 0); // ギャングスターパラダイス解除 + pc_setstand(sd); + WBUFW(buf, 0) = 0x8a; + WBUFL(buf, 2) = sd->bl.id; + WBUFB(buf,26) = 3; + clif_send(buf, packet_len_table[0x8a], &sd->bl, AREA); + break; + } +} + +/*========================================== + * + *------------------------------------------ + */ +void clif_parse_Restart(int fd, struct map_session_data *sd) { + nullpo_retv(sd); + + switch(RFIFOB(fd,2)) { + case 0x00: + if (pc_isdead(sd)) { + pc_setstand(sd); + pc_setrestartvalue(sd, 3); + pc_setpos(sd, sd->status.save_point.map, sd->status.save_point.x, sd->status.save_point.y, 2); + } + break; + case 0x01: + if(!pc_isdead(sd) && (sd->opt1 || (sd->opt2 && !(night_flag == 1 && sd->opt2 == STATE_BLIND)))) + return; + + /* Rovert's Prevent logout option - Fixed [Valaris] */ + if ((battle_config.prevent_logout && (gettick() - sd->canlog_tick) >= 10000) || (!battle_config.prevent_logout)) { + chrif_charselectreq(sd); + } else { + WFIFOW(fd,0)=0x18b; + WFIFOW(fd,2)=1; + + WFIFOSET(fd,packet_len_table[0x018b]); + } + break; + } +} + +/*========================================== + * Transmission of a wisp (S 0096 <len>.w <nick>.24B <message>.?B) + *------------------------------------------ + */ +void clif_parse_Wis(int fd, struct map_session_data *sd) { // S 0096 <len>.w <nick>.24B <message>.?B // rewritten by [Yor] + struct map_session_data *dstsd; + int i; + int gmlen = strlen(RFIFOP(fd,28)); + char gmbuf[512]; + char *gm_command = ((gmlen+28) > sizeof(gmbuf)) ? (char *) malloc(gmlen + 28) : gmbuf; + // 24+3+(RFIFOW(fd,2)-28)+1 or 24+3+(strlen(RFIFOP(fd,28))+1 (size can be wrong with hacker) + + //printf("clif_parse_Wis: message: '%s'.\n", RFIFOP(fd,28)); + memset(gm_command, 0, gmlen); + sprintf(gm_command, "%s : %s", sd->status.name, RFIFOP(fd,28)); + if ((is_atcommand(fd, sd, gm_command, 0) != AtCommand_None) || + ( sd && sd->sc_data && + (sd->sc_data[SC_BERSERK].timer!=-1 || //バーサーク時は会話も不可 + sd->sc_data[SC_NOCHAT].timer!=-1 ) )) //チャット禁止 + { + if (gm_command != gmbuf) + free(gm_command); + return; + } + + // searching destination character + dstsd = map_nick2sd(RFIFOP(fd,4)); + // player is not on this map-server + if (dstsd == NULL || + // At this point, don't send wisp/page if it's not exactly the same name, because (example) + // if there are 'Test' player on an other map-server and 'test' player on this map-server, + // and if we ask for 'Test', we must not contact 'test' player + // so, we send information to inter-server, which is the only one which decide (and copy correct name). + strcmp(dstsd->status.name, RFIFOP(fd,4)) != 0) // not exactly same name + // send message to inter-server + intif_wis_message(sd, RFIFOP(fd,4), RFIFOP(fd,28), RFIFOW(fd,2)-28); + // player is on this map-server + else { + // if you send to your self, don't send anything to others + if (dstsd->fd == fd) // but, normaly, it's impossible! + clif_wis_message(fd, wisp_server_name, "You can not page yourself. Sorry.", strlen("You can not page yourself. Sorry.") + 1); + // otherwise, send message and answer immediatly + else { + if (dstsd->ignoreAll == 1) + clif_wis_end(fd, 2); // type: 0: success to send wisper, 1: target character is not loged in?, 2: ignored by target + else { + // if player ignore the source character + for(i = 0; i < (sizeof(dstsd->ignore) / sizeof(dstsd->ignore[0])); i++) + if (strcmp(dstsd->ignore[i].name, sd->status.name) == 0) { + clif_wis_end(fd, 2); // flag: 0: success to send wisper, 1: target character is not loged in?, 2: ignored by target + break; + } + // if source player not found in ignore list + if (i == (sizeof(sd->ignore) / sizeof(sd->ignore[0]))) { + clif_wis_message(dstsd->fd, sd->status.name, RFIFOP(fd,28), RFIFOW(fd,2) - 28); + clif_wis_end(fd, 0); // type: 0: success to send wisper, 1: target character is not loged in?, 2: ignored by target + } + } + } + } + + if (gm_command != gmbuf) + free(gm_command); + + return; +} + +/*========================================== + * + *------------------------------------------ + */ +void clif_parse_GMmessage(int fd, struct map_session_data *sd) { +// /b + nullpo_retv(sd); + + if ((battle_config.atc_gmonly == 0 || pc_isGM(sd)) && + (pc_isGM(sd) >= get_atcommand_level(AtCommand_Broadcast))) + intif_GMmessage(RFIFOP(fd,4), RFIFOW(fd,2)-4, 0); +} + +/*========================================== + * + *------------------------------------------ + */ +void clif_parse_TakeItem(int fd, struct map_session_data *sd) { + struct flooritem_data *fitem; + int map_object_id; + + nullpo_retv(sd); + + if (sd->packet_ver == 7) // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + map_object_id = RFIFOL(fd,6); + else if (sd->packet_ver == 8) // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + map_object_id = RFIFOL(fd,6); + else if (sd->packet_ver == 9) // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + map_object_id = RFIFOL(fd,9); + else if (sd->packet_ver == 10) // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + map_object_id = RFIFOL(fd,7); + else // old version by default (and parcket version 6) + map_object_id = RFIFOL(fd,2); + fitem = (struct flooritem_data*)map_id2bl(map_object_id); + + if (pc_isdead(sd)) { + clif_clearchar_area(&sd->bl, 1); + return; + } + + if( sd->npc_id!=0 || sd->vender_id != 0 || sd->opt1 > 0 || + (sd->sc_data && (sd->sc_data[SC_TRICKDEAD].timer != -1 || //死んだふり + sd->sc_data[SC_BLADESTOP].timer != -1 || //白刃取り + sd->sc_data[SC_BERSERK].timer!=-1 || //バーサーク + sd->sc_data[SC_NOCHAT].timer!=-1 )) ) //会話禁止 + return; + + if (fitem == NULL || fitem->bl.m != sd->bl.m) + return; + + pc_takeitem(sd, fitem); +} + +/*========================================== + * + *------------------------------------------ + */ +void clif_parse_DropItem(int fd, struct map_session_data *sd) { + int item_index, item_amount; + + nullpo_retv(sd); + + if (pc_isdead(sd)) { + clif_clearchar_area(&sd->bl, 1); + return; + } + if (sd->npc_id != 0 || sd->vender_id != 0 || sd->opt1 > 0 || + (sd->sc_data && (sd->sc_data[SC_AUTOCOUNTER].timer != -1 || //オートカウンター + sd->sc_data[SC_BLADESTOP].timer != -1 || //白刃取り + sd->sc_data[SC_BERSERK].timer != -1)) ) //バーサーク + return; + + if (sd->packet_ver == 8) { // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + item_index = RFIFOW(fd,5) - 2; + item_amount = RFIFOW(fd,12); + } else if (sd->packet_ver == 9) { // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + item_index = RFIFOW(fd,8) - 2; + item_amount = RFIFOW(fd,15); + } else if (sd->packet_ver == 10) { // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + item_index = RFIFOW(fd,6) - 2; + item_amount = RFIFOW(fd,15); + } else { // old version by default (+ packet version 6 and 7) + item_index = RFIFOW(fd,2) - 2; + item_amount = RFIFOW(fd,4); + } + + pc_dropitem(sd, item_index, item_amount); +} + +/*========================================== + * + *------------------------------------------ + */ +void clif_parse_UseItem(int fd, struct map_session_data *sd) { + nullpo_retv(sd); + + if (pc_isdead(sd)) { + clif_clearchar_area(&sd->bl, 1); + return; + } + if (sd->npc_id!=0 || sd->vender_id != 0 || sd->opt1 > 0 || + (sd->sc_data && (sd->sc_data[SC_TRICKDEAD].timer != -1 || //死んだふり + sd->sc_data[SC_BLADESTOP].timer != -1 || //白刃取り + sd->sc_data[SC_BERSERK].timer!=-1 || //バーサーク + sd->sc_data[SC_NOCHAT].timer!=-1 )) ) //会話禁止 + return; + + if (sd->invincible_timer != -1) + pc_delinvincibletimer(sd); + + if (sd->packet_ver == 6) // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + pc_useitem(sd,RFIFOW(fd,5)-2); + else if (sd->packet_ver == 7) // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + pc_useitem(sd,RFIFOW(fd,6)-2); + else if (sd->packet_ver == 8) // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + pc_useitem(sd,RFIFOW(fd,6)-2); + else if (sd->packet_ver == 9) // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + pc_useitem(sd,RFIFOW(fd,9)-2); + else if (sd->packet_ver == 10) // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + pc_useitem(sd,RFIFOW(fd,7)-2); + else // old version by default + pc_useitem(sd,RFIFOW(fd,2)-2); +} + +/*========================================== + * + *------------------------------------------ + */ +void clif_parse_EquipItem(int fd,struct map_session_data *sd) +{ + int index; + + nullpo_retv(sd); + + if(pc_isdead(sd)) { + clif_clearchar_area(&sd->bl,1); + return; + } + index = RFIFOW(fd,2)-2; + if(sd->npc_id!=0 || sd->vender_id != 0) return; + if(sd->sc_data && ( sd->sc_data[SC_BLADESTOP].timer!=-1 || sd->sc_data[SC_BERSERK].timer!=-1 )) return; + + if(sd->status.inventory[index].identify != 1) { // 未鑑定 + // Bjorn: Auto-identify items when equipping them as there + // is no nice way to do this in the client yet. + sd->status.inventory[index].identify = 1; + //clif_equipitemack(sd,index,0,0); // fail + //return; + } + //ペット用装備であるかないか + if(sd->inventory_data[index]) { + if(sd->inventory_data[index]->type != 8){ + if(sd->inventory_data[index]->type == 10) + RFIFOW(fd,4)=0x8000; // 矢を無理やり装備できるように(−−; + pc_equipitem(sd,index,RFIFOW(fd,4)); + } else + pet_equipitem(sd,index); + } +} + +/*========================================== + * + *------------------------------------------ + */ +void clif_parse_UnequipItem(int fd,struct map_session_data *sd) +{ + int index; + + nullpo_retv(sd); + + if(pc_isdead(sd)) { + clif_clearchar_area(&sd->bl,1); + return; + } + index = RFIFOW(fd,2)-2; + if(sd->status.inventory[index].broken == 1 && sd->sc_data && sd->sc_data[SC_BROKNWEAPON].timer!=-1) + skill_status_change_end(&sd->bl,SC_BROKNWEAPON,-1); + if(sd->status.inventory[index].broken == 1 && sd->sc_data && sd->sc_data[SC_BROKNARMOR].timer!=-1) + skill_status_change_end(&sd->bl,SC_BROKNARMOR,-1); + if(sd->sc_data && ( sd->sc_data[SC_BLADESTOP].timer!=-1 || sd->sc_data[SC_BERSERK].timer!=-1 )) + return; + + if(sd->npc_id!=0 || sd->vender_id != 0 || sd->opt1 > 0) + return; + pc_unequipitem(sd,index,0); +} + +/*========================================== + * + *------------------------------------------ + */ +void clif_parse_NpcClicked(int fd,struct map_session_data *sd) +{ + nullpo_retv(sd); + + if(pc_isdead(sd)) { + clif_clearchar_area(&sd->bl,1); + return; + } + if(sd->npc_id!=0 || sd->vender_id != 0) + return; + npc_click(sd,RFIFOL(fd,2)); +} + +/*========================================== + * + *------------------------------------------ + */ +void clif_parse_NpcBuySellSelected(int fd,struct map_session_data *sd) +{ + npc_buysellsel(sd,RFIFOL(fd,2),RFIFOB(fd,6)); +} + +/*========================================== + * + *------------------------------------------ + */ +void clif_parse_NpcBuyListSend(int fd,struct map_session_data *sd) +{ + int fail=0,n; + unsigned short *item_list; + + n = (RFIFOW(fd,2)-4) /4; + item_list = (unsigned short*)RFIFOP(fd,4); + + fail = npc_buylist(sd,n,item_list); + + WFIFOW(fd,0)=0xca; + WFIFOB(fd,2)=fail; + WFIFOSET(fd,packet_len_table[0xca]); +} + +/*========================================== + * + *------------------------------------------ + */ +void clif_parse_NpcSellListSend(int fd,struct map_session_data *sd) +{ + int fail=0,n; + unsigned short *item_list; + + n = (RFIFOW(fd,2)-4) /4; + item_list = (unsigned short*)RFIFOP(fd,4); + + fail = npc_selllist(sd,n,item_list); + + WFIFOW(fd,0)=0xcb; + WFIFOB(fd,2)=fail; + WFIFOSET(fd,packet_len_table[0xcb]); +} + +/*========================================== + * + *------------------------------------------ + */ +void clif_parse_CreateChatRoom(int fd,struct map_session_data *sd) +{ + if(battle_config.basic_skill_check == 0 || pc_checkskill(sd,NV_BASIC) >= 4){ + chat_createchat(sd,RFIFOW(fd,4),RFIFOB(fd,6),RFIFOP(fd,7),RFIFOP(fd,15),RFIFOW(fd,2)-15); + } else + clif_skill_fail(sd,1,0,3); +} + +/*========================================== + * + *------------------------------------------ + */ +void clif_parse_ChatAddMember(int fd,struct map_session_data *sd) +{ + chat_joinchat(sd,RFIFOL(fd,2),RFIFOP(fd,6)); +} + +/*========================================== + * + *------------------------------------------ + */ +void clif_parse_ChatRoomStatusChange(int fd,struct map_session_data *sd) +{ + chat_changechatstatus(sd,RFIFOW(fd,4),RFIFOB(fd,6),RFIFOP(fd,7),RFIFOP(fd,15),RFIFOW(fd,2)-15); +} + +/*========================================== + * + *------------------------------------------ + */ +void clif_parse_ChangeChatOwner(int fd,struct map_session_data *sd) +{ + chat_changechatowner(sd,RFIFOP(fd,6)); +} + +/*========================================== + * + *------------------------------------------ + */ +void clif_parse_KickFromChat(int fd,struct map_session_data *sd) +{ + chat_kickchat(sd,RFIFOP(fd,2)); +} + +/*========================================== + * + *------------------------------------------ + */ +void clif_parse_ChatLeave(int fd,struct map_session_data *sd) +{ + chat_leavechat(sd); +} + +/*========================================== + * 取引要請を相手に送る + *------------------------------------------ + */ +void clif_parse_TradeRequest(int fd,struct map_session_data *sd) +{ + nullpo_retv(sd); + + if(battle_config.basic_skill_check == 0 || pc_checkskill(sd,NV_BASIC) >= 1){ + trade_traderequest(sd,RFIFOL(sd->fd,2)); + } else + clif_skill_fail(sd,1,0,0); +} + +/*========================================== + * 取引要請 + *------------------------------------------ + */ +void clif_parse_TradeAck(int fd,struct map_session_data *sd) +{ + nullpo_retv(sd); + + trade_tradeack(sd,RFIFOB(sd->fd,2)); +} + +/*========================================== + * アイテム追加 + *------------------------------------------ + */ +void clif_parse_TradeAddItem(int fd,struct map_session_data *sd) +{ + nullpo_retv(sd); + + trade_tradeadditem(sd,RFIFOW(sd->fd,2),RFIFOL(sd->fd,4)); +} + +/*========================================== + * アイテム追加完了(ok押し) + *------------------------------------------ + */ +void clif_parse_TradeOk(int fd,struct map_session_data *sd) +{ + trade_tradeok(sd); +} + +/*========================================== + * 取引キャンセル + *------------------------------------------ + */ +void clif_parse_TradeCansel(int fd,struct map_session_data *sd) +{ + trade_tradecancel(sd); +} + +/*========================================== + * 取引許諾(trade押し) + *------------------------------------------ + */ +void clif_parse_TradeCommit(int fd,struct map_session_data *sd) +{ + trade_tradecommit(sd); +} + +/*========================================== + * + *------------------------------------------ + */ +void clif_parse_StopAttack(int fd,struct map_session_data *sd) +{ + pc_stopattack(sd); +} + +/*========================================== + * カートへアイテムを移す + *------------------------------------------ + */ +void clif_parse_PutItemToCart(int fd,struct map_session_data *sd) +{ + nullpo_retv(sd); + + if(sd->npc_id!=0 || sd->vender_id != 0) + return; + pc_putitemtocart(sd,RFIFOW(fd,2)-2,RFIFOL(fd,4)); +} +/*========================================== + * カートからアイテムを出す + *------------------------------------------ + */ +void clif_parse_GetItemFromCart(int fd,struct map_session_data *sd) +{ + nullpo_retv(sd); + + if(sd->npc_id!=0 || sd->vender_id != 0) return; + pc_getitemfromcart(sd,RFIFOW(fd,2)-2,RFIFOL(fd,4)); +} + +/*========================================== + * 付属品(鷹,ペコ,カート)をはずす + *------------------------------------------ + */ +void clif_parse_RemoveOption(int fd,struct map_session_data *sd) +{ + if(pc_isriding(sd)) { // jobchange when removing peco [Valaris] + if(sd->status.class==13) + sd->status.class=sd->view_class=7; + + if(sd->status.class==21) + sd->status.class=sd->view_class=14; + + if(sd->status.class==4014) + sd->status.class=sd->view_class=4008; + + if(sd->status.class==4022) + sd->status.class=sd->view_class=4015; + } + + pc_setoption(sd,0); +} + +/*========================================== + * チェンジカート + *------------------------------------------ + */ +void clif_parse_ChangeCart(int fd,struct map_session_data *sd) +{ + pc_setcart(sd,RFIFOW(fd,2)); +} + +/*========================================== + * ステータスアップ + *------------------------------------------ + */ +void clif_parse_StatusUp(int fd,struct map_session_data *sd) +{ + pc_statusup(sd,RFIFOW(fd,2)); +} + +/*========================================== + * スキルレベルアップ + *------------------------------------------ + */ +void clif_parse_SkillUp(int fd,struct map_session_data *sd) +{ + pc_skillup(sd,RFIFOW(fd,2)); +} + +/*========================================== + * スキル使用(ID指定) + *------------------------------------------ + */ +void clif_parse_UseSkillToId(int fd, struct map_session_data *sd) { + int skillnum, skilllv, lv, target_id; + unsigned int tick = gettick(); + + nullpo_retv(sd); + + if(map[sd->bl.m].flag.noskill) return; + if (sd->chatID || sd->npc_id != 0 || sd->vender_id != 0) + return; + + if (sd->packet_ver == 6) { // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + skilllv = RFIFOW(fd,4); + skillnum = RFIFOW(fd,9); + target_id = RFIFOL(fd,11); + } else if (sd->packet_ver == 7) { // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + skilllv = RFIFOW(fd,7); + skillnum = RFIFOW(fd,9); + target_id = RFIFOL(fd,15); + } else if (sd->packet_ver == 8) { // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + skilllv = RFIFOW(fd,7); + skillnum = RFIFOW(fd,12); + target_id = RFIFOL(fd,16); + } else if (sd->packet_ver == 9) { // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + skilllv = RFIFOW(fd,11); + skillnum = RFIFOW(fd,18); + target_id = RFIFOL(fd,22); + } else if (sd->packet_ver == 10) { // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + skilllv = RFIFOW(fd,9); + skillnum = RFIFOW(fd,15); + target_id = RFIFOL(fd,18); + } else { // old version by default + skilllv = RFIFOW(fd,2); + skillnum = RFIFOW(fd,4); + target_id = RFIFOL(fd,6); + } + + if (sd->skilltimer != -1) { + if (skillnum != SA_CASTCANCEL) + return; + } else if (DIFF_TICK(tick, sd->canact_tick) < 0) { + clif_skill_fail(sd, skillnum, 4, 0); + return; + } + + if ((sd->sc_data[SC_TRICKDEAD].timer != -1 && skillnum != NV_TRICKDEAD) || + sd->sc_data[SC_BERSERK].timer != -1 || sd->sc_data[SC_NOCHAT].timer!=-1 || + sd->sc_data[SC_WEDDING].timer!=-1 || sd->view_class==22) + return; + if (sd->invincible_timer != -1) + pc_delinvincibletimer(sd); + if (sd->skillitem >= 0 && sd->skillitem == skillnum) { + if (skilllv != sd->skillitemlv) + skilllv = sd->skillitemlv; + skill_use_id(sd, target_id, skillnum, skilllv); + } else { + sd->skillitem = sd->skillitemlv = -1; + if (skillnum == MO_EXTREMITYFIST) { + if ((sd->sc_data[SC_COMBO].timer == -1 || (sd->sc_data[SC_COMBO].val1 != MO_COMBOFINISH && sd->sc_data[SC_COMBO].val1 != CH_CHAINCRUSH))) { + if (!sd->state.skill_flag ) { + sd->state.skill_flag = 1; + clif_skillinfo(sd, MO_EXTREMITYFIST, 1, -1); + return; + } else if (sd->bl.id == target_id) { + clif_skillinfo(sd, MO_EXTREMITYFIST, 1, -1); + return; + } + } + } + if ((lv = pc_checkskill(sd, skillnum)) > 0) { + if (skilllv > lv) + skilllv = lv; + skill_use_id(sd, target_id, skillnum, skilllv); + if (sd->state.skill_flag) + sd->state.skill_flag = 0; + } + } +} + +/*========================================== + * スキル使用(場所指定) + *------------------------------------------ + */ +void clif_parse_UseSkillToPos(int fd, struct map_session_data *sd) { + int skillnum, skilllv, lv, x, y; + unsigned int tick = gettick(); + int skillmoreinfo; + + nullpo_retv(sd); + + if(map[sd->bl.m].flag.noskill) return; + if (sd->npc_id != 0 || sd->vender_id != 0) return; + if(sd->chatID) return; + + skillmoreinfo = -1; + if (sd->packet_ver == 6) { // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + skilllv = RFIFOW(fd,4); + skillnum = RFIFOW(fd,9); + x = RFIFOW(fd,11); + y = RFIFOW(fd,13); + if (RFIFOW(fd,0) == 0x190) + skillmoreinfo = 15; + } else if (sd->packet_ver == 7) { // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + skilllv = RFIFOW(fd,7); + skillnum = RFIFOW(fd,9); + x = RFIFOW(fd,15); + y = RFIFOW(fd,17); + if (RFIFOW(fd,0) == 0x190) + skillmoreinfo = 19; + } else if (sd->packet_ver == 8) { // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + skilllv = RFIFOW(fd,3); + skillnum = RFIFOW(fd,6); + x = RFIFOW(fd,17); + y = RFIFOW(fd,21); + if (RFIFOW(fd,0) == 0x0a2) + skillmoreinfo = 23; + } else if (sd->packet_ver == 9) { // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + skilllv = RFIFOW(fd,5); + skillnum = RFIFOW(fd,15); + x = RFIFOW(fd,29); + y = RFIFOW(fd,38); + if (RFIFOW(fd,0) == 0x0a2) + skillmoreinfo = 40; + } else if (sd->packet_ver == 10) { // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + skilllv = RFIFOW(fd,10); + skillnum = RFIFOW(fd,14); + x = RFIFOW(fd,18); + y = RFIFOW(fd,23); + if (RFIFOW(fd,0) == 0x0a7) + skillmoreinfo = 25; + } else { // old version by default + skilllv = RFIFOW(fd,2); + skillnum = RFIFOW(fd,4); + x = RFIFOW(fd,6); + y = RFIFOW(fd,8); + if (RFIFOW(fd,0) == 0x190) + skillmoreinfo = 10; + } + + if (skillmoreinfo != -1) { + if (pc_issit(sd)) { + clif_skill_fail(sd, skillnum, 0, 0); + return; + } + memcpy(talkie_mes, RFIFOP(fd,skillmoreinfo), 80); + } + + if (sd->skilltimer != -1) + return; + else if (DIFF_TICK(tick, sd->canact_tick) < 0) { + clif_skill_fail(sd, skillnum, 4, 0); + return; + } + + if((sd->sc_data[SC_TRICKDEAD].timer != -1 && skillnum != NV_TRICKDEAD) || + sd->sc_data[SC_BERSERK].timer!=-1 || sd->sc_data[SC_NOCHAT].timer!=-1 || + sd->sc_data[SC_WEDDING].timer!=-1 || sd->view_class==22) + return; + if (sd->invincible_timer != -1) + pc_delinvincibletimer(sd); + if (sd->skillitem >= 0 && sd->skillitem == skillnum) { + if (skilllv != sd->skillitemlv) + skilllv = sd->skillitemlv; + skill_use_pos(sd, x, y, skillnum, skilllv); + } else { + sd->skillitem = sd->skillitemlv = -1; + if ((lv = pc_checkskill(sd, skillnum)) > 0) { + if (skilllv > lv) + skilllv = lv; + skill_use_pos(sd, x, y, skillnum,skilllv); + } + } +} + +/*========================================== + * スキル使用(map指定) + *------------------------------------------ + */ +void clif_parse_UseSkillMap(int fd,struct map_session_data *sd) +{ + nullpo_retv(sd); + + if(map[sd->bl.m].flag.noskill) return; + if(sd->chatID) return; + + if(sd->npc_id!=0 || sd->vender_id != 0 || (sd->sc_data && + (sd->sc_data[SC_TRICKDEAD].timer != -1 || + sd->sc_data[SC_BERSERK].timer!=-1 || + sd->sc_data[SC_NOCHAT].timer!=-1 || + sd->sc_data[SC_WEDDING].timer!=-1 || + sd->view_class==22))) + return; + + if(sd->invincible_timer != -1) + pc_delinvincibletimer(sd); + + skill_castend_map(sd,RFIFOW(fd,2),RFIFOP(fd,4)); +} +/*========================================== + * メモ要求 + *------------------------------------------ + */ +void clif_parse_RequestMemo(int fd,struct map_session_data *sd) +{ + pc_memo(sd,-1); +} +/*========================================== + * アイテム合成 + *------------------------------------------ + */ +void clif_parse_ProduceMix(int fd,struct map_session_data *sd) +{ + nullpo_retv(sd); + + sd->state.produce_flag = 0; + skill_produce_mix(sd,RFIFOW(fd,2),RFIFOW(fd,4),RFIFOW(fd,6),RFIFOW(fd,8)); +} + +/*========================================== + * + *------------------------------------------ + */ +void clif_parse_NpcSelectMenu(int fd,struct map_session_data *sd) +{ + nullpo_retv(sd); + + sd->npc_menu=RFIFOB(fd,6); + npc_scriptcont(sd,RFIFOL(fd,2)); +} + +/*========================================== + * + *------------------------------------------ + */ +void clif_parse_NpcNextClicked(int fd,struct map_session_data *sd) +{ + npc_scriptcont(sd,RFIFOL(fd,2)); +} + +/*========================================== + * + *------------------------------------------ + */ +void clif_parse_NpcAmountInput(int fd,struct map_session_data *sd) +{ + nullpo_retv(sd); + +#define RFIFOL_(fd,pos) (*(int*)(session[fd]->rdata+session[fd]->rdata_pos+(pos))) + //Input Value overflow Exploit FIX + sd->npc_amount=RFIFOL_(fd,6); //fixed by Lupus. npc_amount is (int) but was RFIFOL changing it to (unsigned int) + +#undef RFIFOL_ + + npc_scriptcont(sd,RFIFOL(fd,2)); +} + +/*========================================== + * + *------------------------------------------ + */ +void clif_parse_NpcStringInput(int fd,struct map_session_data *sd) +{ + nullpo_retv(sd); + + if(RFIFOW(fd,2)-7 >= sizeof(sd->npc_str)){ + printf("clif: input string too long !\n"); + memcpy(sd->npc_str,RFIFOP(fd,8),sizeof(sd->npc_str)); + sd->npc_str[sizeof(sd->npc_str)-1]=0; + } else + strcpy(sd->npc_str,RFIFOP(fd,8)); + npc_scriptcont(sd,RFIFOL(fd,4)); +} + +/*========================================== + * + *------------------------------------------ + */ +void clif_parse_NpcCloseClicked(int fd,struct map_session_data *sd) +{ + npc_scriptcont(sd,RFIFOL(fd,2)); +} + +/*========================================== + * アイテム鑑定 + *------------------------------------------ + */ +void clif_parse_ItemIdentify(int fd,struct map_session_data *sd) +{ + pc_item_identify(sd,RFIFOW(fd,2)-2); +} +/*========================================== + * 矢作成 + *------------------------------------------ + */ +void clif_parse_SelectArrow(int fd,struct map_session_data *sd) +{ + nullpo_retv(sd); + + sd->state.make_arrow_flag = 0; + skill_arrow_create(sd,RFIFOW(fd,2)); +} +/*========================================== + * オートスペル受信 + *------------------------------------------ + */ +void clif_parse_AutoSpell(int fd,struct map_session_data *sd) +{ + skill_autospell(sd,RFIFOW(fd,2)); +} +/*========================================== + * カード使用 + *------------------------------------------ + */ +void clif_parse_UseCard(int fd,struct map_session_data *sd) +{ + clif_use_card(sd,RFIFOW(fd,2)-2); +} +/*========================================== + * カード挿入装備選択 + *------------------------------------------ + */ +void clif_parse_InsertCard(int fd,struct map_session_data *sd) +{ + pc_insert_card(sd,RFIFOW(fd,2)-2,RFIFOW(fd,4)-2); +} + +/*========================================== + * 0193 キャラID名前引き + *------------------------------------------ + */ +void clif_parse_SolveCharName(int fd, struct map_session_data *sd) { + int char_id; + + if (sd->packet_ver == 8) { // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + char_id = RFIFOL(fd,8); + } else if (sd->packet_ver == 9) { // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + char_id = RFIFOL(fd,7); + } else if (sd->packet_ver == 10) { // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + char_id = RFIFOL(fd,10); + } else { // old version by default (+ packet version 6 and 7) + char_id = RFIFOL(fd,2); + } + clif_solved_charname(sd, char_id); +} + +/*========================================== + * 0197 /resetskill /resetstate + *------------------------------------------ + */ +void clif_parse_ResetChar(int fd, struct map_session_data *sd) { + nullpo_retv(sd); + + if (battle_config.atc_gmonly == 0 || pc_isGM(sd)) { + switch(RFIFOW(fd,2)){ + case 0: + if (pc_isGM(sd) >= get_atcommand_level(AtCommand_ResetState)) + pc_resetstate(sd); + break; + case 1: + if (pc_isGM(sd) >= get_atcommand_level(AtCommand_ResetState)) + pc_resetskill(sd); + break; + } + } +} + +/*========================================== + * 019c /lb等 + *------------------------------------------ + */ +void clif_parse_LGMmessage(int fd, struct map_session_data *sd) { + unsigned char buf[64]; + + nullpo_retv(sd); + + if ((battle_config.atc_gmonly == 0 || pc_isGM(sd)) && + (pc_isGM(sd) >= get_atcommand_level(AtCommand_LocalBroadcast))) { + WBUFW(buf,0) = 0x9a; + WBUFW(buf,2) = RFIFOW(fd,2); + memcpy(WBUFP(buf,4), RFIFOP(fd,4), RFIFOW(fd,2) - 4); + clif_send(buf, RFIFOW(fd,2), &sd->bl, ALL_SAMEMAP); + } +} + +/*========================================== + * カプラ倉庫へ入れる + *------------------------------------------ + */ +void clif_parse_MoveToKafra(int fd, struct map_session_data *sd) { + int item_index, item_amount; + + nullpo_retv(sd); + + if (sd->packet_ver == 8) { // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + item_index = RFIFOW(fd,5) - 2; + item_amount = RFIFOL(fd,12); + } else if (sd->packet_ver == 9) { // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + item_index = RFIFOW(fd,5) - 2; + item_amount = RFIFOL(fd,19); + } else if (sd->packet_ver == 10) { // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + item_index = RFIFOW(fd,3) - 2; + item_amount = RFIFOL(fd,15); + } else { // old version by default (+ packet version 6 and 7) + item_index = RFIFOW(fd,2) - 2; + item_amount = RFIFOL(fd,4); + } + + if (sd->npc_id != 0 || sd->vender_id != 0) + return; + + if (sd->state.storage_flag) + storage_guild_storageadd(sd, item_index, item_amount); + else + storage_storageadd(sd, item_index, item_amount); +} + +/*========================================== + * カプラ倉庫から出す + *------------------------------------------ + */ +void clif_parse_MoveFromKafra(int fd,struct map_session_data *sd) { + int item_index, item_amount; + + nullpo_retv(sd); + + if (sd->packet_ver == 8) { // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + item_index = RFIFOW(fd,10) - 1; + item_amount = RFIFOL(fd,22); + } else if (sd->packet_ver == 9) { // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + item_index = RFIFOW(fd,11) - 1; + item_amount = RFIFOL(fd,22); + } else if (sd->packet_ver == 10) { // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + item_index = RFIFOW(fd,3) - 1; + item_amount = RFIFOL(fd,13); + } else { // old version by default (+ packet version 6 and 7) + item_index = RFIFOW(fd,2) - 1; + item_amount = RFIFOL(fd,4); + } + + if (sd->npc_id != 0 || sd->vender_id != 0) + return; + + if (sd->state.storage_flag) + storage_guild_storageget(sd, item_index, item_amount); + else + storage_storageget(sd, item_index, item_amount); +} + +/*========================================== + * カプラ倉庫へカートから入れる + *------------------------------------------ + */ +void clif_parse_MoveToKafraFromCart(int fd, struct map_session_data *sd) { + nullpo_retv(sd); + + if (sd->npc_id != 0 || sd->vender_id != 0) + return; + if (sd->state.storage_flag) + storage_guild_storageaddfromcart(sd, RFIFOW(fd,2) - 2, RFIFOL(fd,4)); + else + storage_storageaddfromcart(sd, RFIFOW(fd,2) - 2, RFIFOL(fd,4)); +} + +/*========================================== + * カプラ倉庫から出す + *------------------------------------------ + */ +void clif_parse_MoveFromKafraToCart(int fd, struct map_session_data *sd) { + nullpo_retv(sd); + + if (sd->npc_id != 0 || sd->vender_id != 0) + return; + if (sd->state.storage_flag) + storage_guild_storagegettocart(sd, RFIFOW(fd,2)-1, RFIFOL(fd,4)); + else + storage_storagegettocart(sd, RFIFOW(fd,2)-1, RFIFOL(fd,4)); +} + +/*========================================== + * カプラ倉庫を閉じる + *------------------------------------------ + */ +void clif_parse_CloseKafra(int fd, struct map_session_data *sd) { + nullpo_retv(sd); + + if (sd->state.storage_flag) + storage_guild_storageclose(sd); + else + storage_storageclose(sd); +} + +/*========================================== + * パーティを作る + *------------------------------------------ + */ +void clif_parse_CreateParty(int fd, struct map_session_data *sd) { + if (battle_config.basic_skill_check == 0 || pc_checkskill(sd,NV_BASIC) >= 7) { + party_create(sd,RFIFOP(fd,2)); + } else + clif_skill_fail(sd,1,0,4); +} + +/*========================================== + * パーティを作る + *------------------------------------------ + */ +void clif_parse_CreateParty2(int fd, struct map_session_data *sd) { + if (battle_config.basic_skill_check == 0 || pc_checkskill(sd,NV_BASIC) >= 7){ + party_create(sd, RFIFOP(fd,2)); + } else + clif_skill_fail(sd, 1, 0, 4); +} + +/*========================================== + * パーティに勧誘 + *------------------------------------------ + */ +void clif_parse_PartyInvite(int fd, struct map_session_data *sd) { + party_invite(sd, RFIFOL(fd,2)); +} + +/*========================================== + * パーティ勧誘返答 + *------------------------------------------ + */ +void clif_parse_ReplyPartyInvite(int fd,struct map_session_data *sd) { + if(battle_config.basic_skill_check == 0 || pc_checkskill(sd,NV_BASIC) >= 5){ + party_reply_invite(sd,RFIFOL(fd,2),RFIFOL(fd,6)); + } else { + party_reply_invite(sd,RFIFOL(fd,2),-1); + clif_skill_fail(sd,1,0,4); + } +} + +/*========================================== + * パーティ脱退要求 + *------------------------------------------ + */ +void clif_parse_LeaveParty(int fd, struct map_session_data *sd) { + party_leave(sd); +} + +/*========================================== + * パーティ除名要求 + *------------------------------------------ + */ +void clif_parse_RemovePartyMember(int fd, struct map_session_data *sd) { + party_removemember(sd,RFIFOL(fd,2),RFIFOP(fd,6)); +} + +/*========================================== + * パーティ設定変更要求 + *------------------------------------------ + */ +void clif_parse_PartyChangeOption(int fd, struct map_session_data *sd) { + party_changeoption(sd, RFIFOW(fd,2), RFIFOW(fd,4)); +} + +/*========================================== + * パーティメッセージ送信要求 + *------------------------------------------ + */ +void clif_parse_PartyMessage(int fd, struct map_session_data *sd) { + nullpo_retv(sd); + + if (is_atcommand(fd, sd, RFIFOP(fd,4), 0) != AtCommand_None) + return; + if(sd->sc_data && + (sd->sc_data[SC_BERSERK].timer!=-1 || //バーサーク時は会話も不可 + sd->sc_data[SC_NOCHAT].timer!=-1)) //チャット禁止 + return; + + party_send_message(sd, RFIFOP(fd,4), RFIFOW(fd,2)-4); +} + +/*========================================== + * 露店閉鎖 + *------------------------------------------ + */ +void clif_parse_CloseVending(int fd, struct map_session_data *sd) { + vending_closevending(sd); +} + +/*========================================== + * 露店アイテムリスト要求 + *------------------------------------------ + */ +void clif_parse_VendingListReq(int fd, struct map_session_data *sd) { + nullpo_retv(sd); + + vending_vendinglistreq(sd,RFIFOL(fd,2)); + if(sd->npc_id) + npc_event_dequeue(sd); +} + +/*========================================== + * 露店アイテム購入 + *------------------------------------------ + */ +void clif_parse_PurchaseReq(int fd, struct map_session_data *sd) { + vending_purchasereq(sd, RFIFOW(fd,2), RFIFOL(fd,4), RFIFOP(fd,8)); +} + +/*========================================== + * 露店開設 + *------------------------------------------ + */ +void clif_parse_OpenVending(int fd,struct map_session_data *sd) { + vending_openvending(sd, RFIFOW(fd,2), RFIFOP(fd,4), RFIFOB(fd,84), RFIFOP(fd,85)); +} + +/*========================================== + * /monster /item rewriten by [Yor] + *------------------------------------------ + */ +void clif_parse_GM_Monster_Item(int fd, struct map_session_data *sd) { + char monster_item_name[25]; + + nullpo_retv(sd); + + memset(monster_item_name, '\0', sizeof(monster_item_name)); + + if (battle_config.atc_gmonly == 0 || pc_isGM(sd)) { + memcpy(monster_item_name, RFIFOP(fd,2), 24); + + if (mobdb_searchname(monster_item_name) != 0) { + if (pc_isGM(sd) >= get_atcommand_level(AtCommand_Monster)) + atcommand_spawn(fd, sd, "@spawn", monster_item_name); // as @spawn + } else if (itemdb_searchname(monster_item_name) != NULL) { + if (pc_isGM(sd) >= get_atcommand_level(AtCommand_Item)) + atcommand_item(fd, sd, "@item", monster_item_name); // as @item + } + + } +} + +/*========================================== + * ギルドを作る + *------------------------------------------ + */ +void clif_parse_CreateGuild(int fd,struct map_session_data *sd) { + guild_create(sd, RFIFOP(fd,6)); +} + +/*========================================== + * ギルドマスターかどうか確認 + *------------------------------------------ + */ +void clif_parse_GuildCheckMaster(int fd, struct map_session_data *sd) { + clif_guild_masterormember(sd); +} + +/*========================================== + * ギルド情報要求 + *------------------------------------------ + */ +void clif_parse_GuildReqeustInfo(int fd, struct map_session_data *sd) { + switch(RFIFOL(fd,2)){ + case 0: // ギルド基本情報、同盟敵対情報 + clif_guild_basicinfo(sd); + clif_guild_allianceinfo(sd); + break; + case 1: // メンバーリスト、役職名リスト + clif_guild_positionnamelist(sd); + clif_guild_memberlist(sd); + break; + case 2: // 役職名リスト、役職情報リスト + clif_guild_positionnamelist(sd); + clif_guild_positioninfolist(sd); + break; + case 3: // スキルリスト + clif_guild_skillinfo(sd); + break; + case 4: // 追放リスト + clif_guild_explusionlist(sd); + break; + default: + if (battle_config.error_log) + printf("clif: guild request info: unknown type %d\n", RFIFOL(fd,2)); + break; + } +} + +/*========================================== + * ギルド役職変更 + *------------------------------------------ + */ +void clif_parse_GuildChangePositionInfo(int fd, struct map_session_data *sd) { + int i; + + for(i = 4; i < RFIFOW(fd,2); i += 40 ){ + guild_change_position(sd, RFIFOL(fd,i), RFIFOL(fd,i+4), RFIFOL(fd,i+12), RFIFOP(fd,i+16)); + } +} + +/*========================================== + * ギルドメンバ役職変更 + *------------------------------------------ + */ +void clif_parse_GuildChangeMemberPosition(int fd, struct map_session_data *sd) { + int i; + + nullpo_retv(sd); + + for(i=4;i<RFIFOW(fd,2);i+=12){ + guild_change_memberposition(sd->status.guild_id, + RFIFOL(fd,i),RFIFOL(fd,i+4),RFIFOL(fd,i+8)); + } +} + +/*========================================== + * ギルドエンブレム要求 + *------------------------------------------ + */ +void clif_parse_GuildRequestEmblem(int fd,struct map_session_data *sd) { + struct guild *g=guild_search(RFIFOL(fd,2)); + if(g!=NULL) + clif_guild_emblem(sd,g); +} + +/*========================================== + * ギルドエンブレム変更 + *------------------------------------------ + */ +void clif_parse_GuildChangeEmblem(int fd,struct map_session_data *sd) { + guild_change_emblem(sd,RFIFOW(fd,2)-4,RFIFOP(fd,4)); +} + +/*========================================== + * ギルド告知変更 + *------------------------------------------ + */ +void clif_parse_GuildChangeNotice(int fd,struct map_session_data *sd) { + guild_change_notice(sd,RFIFOL(fd,2),RFIFOP(fd,6),RFIFOP(fd,66)); +} + +/*========================================== + * ギルド勧誘 + *------------------------------------------ + */ +void clif_parse_GuildInvite(int fd,struct map_session_data *sd) { + guild_invite(sd,RFIFOL(fd,2)); +} + +/*========================================== + * ギルド勧誘返信 + *------------------------------------------ + */ +void clif_parse_GuildReplyInvite(int fd,struct map_session_data *sd) { + guild_reply_invite(sd,RFIFOL(fd,2),RFIFOB(fd,6)); +} + +/*========================================== + * ギルド脱退 + *------------------------------------------ + */ +void clif_parse_GuildLeave(int fd,struct map_session_data *sd) { + guild_leave(sd,RFIFOL(fd,2),RFIFOL(fd,6),RFIFOL(fd,10),RFIFOP(fd,14)); +} + +/*========================================== + * ギルド追放 + *------------------------------------------ + */ +void clif_parse_GuildExplusion(int fd,struct map_session_data *sd) { + guild_explusion(sd,RFIFOL(fd,2),RFIFOL(fd,6),RFIFOL(fd,10),RFIFOP(fd,14)); +} + +/*========================================== + * ギルド会話 + *------------------------------------------ + */ +void clif_parse_GuildMessage(int fd,struct map_session_data *sd) { + nullpo_retv(sd); + + if (is_atcommand(fd, sd, RFIFOP(fd, 4), 0) != AtCommand_None) + return; + if(sd->sc_data && + (sd->sc_data[SC_BERSERK].timer!=-1 || //バーサーク時は会話も不可 + sd->sc_data[SC_NOCHAT].timer!=-1)) //チャット禁止 + return; + + guild_send_message(sd, RFIFOP(fd,4), RFIFOW(fd,2)-4); +} + +/*========================================== + * ギルド同盟要求 + *------------------------------------------ + */ +void clif_parse_GuildRequestAlliance(int fd, struct map_session_data *sd) { + guild_reqalliance(sd,RFIFOL(fd,2)); +} + +/*========================================== + * ギルド同盟要求返信 + *------------------------------------------ + */ +void clif_parse_GuildReplyAlliance(int fd, struct map_session_data *sd) { + guild_reply_reqalliance(sd,RFIFOL(fd,2),RFIFOL(fd,6)); +} + +/*========================================== + * ギルド関係解消 + *------------------------------------------ + */ +void clif_parse_GuildDelAlliance(int fd, struct map_session_data *sd) { + guild_delalliance(sd,RFIFOL(fd,2),RFIFOL(fd,6)); +} + +/*========================================== + * ギルド敵対 + *------------------------------------------ + */ +void clif_parse_GuildOpposition(int fd, struct map_session_data *sd) { + guild_opposition(sd,RFIFOL(fd,2)); +} + +/*========================================== + * ギルド解散 + *------------------------------------------ + */ +void clif_parse_GuildBreak(int fd, struct map_session_data *sd) { + guild_break(sd,RFIFOP(fd,2)); +} + +// pet +void clif_parse_PetMenu(int fd, struct map_session_data *sd) { + pet_menu(sd,RFIFOB(fd,2)); +} + +void clif_parse_CatchPet(int fd, struct map_session_data *sd) { + pet_catch_process2(sd,RFIFOL(fd,2)); +} + +void clif_parse_SelectEgg(int fd, struct map_session_data *sd) { + pet_select_egg(sd,RFIFOW(fd,2)-2); +} + +void clif_parse_SendEmotion(int fd, struct map_session_data *sd) { + nullpo_retv(sd); + + if(sd->pd) + clif_pet_emotion(sd->pd,RFIFOL(fd,2)); +} + +void clif_parse_ChangePetName(int fd, struct map_session_data *sd) { + pet_change_name(sd,RFIFOP(fd,2)); +} + +// Kick (right click menu for GM "(name) force to quit") +void clif_parse_GMKick(int fd, struct map_session_data *sd) { + struct block_list *target; + int tid = RFIFOL(fd,2); + + nullpo_retv(sd); + + if ((battle_config.atc_gmonly == 0 || pc_isGM(sd)) && + (pc_isGM(sd) >= get_atcommand_level(AtCommand_Kick))) { + target = map_id2bl(tid); + if (target) { + if (target->type == BL_PC) { + struct map_session_data *tsd = (struct map_session_data *)target; + if (pc_isGM(sd) > pc_isGM(tsd)) + clif_GM_kick(sd, tsd, 1); + else + clif_GM_kickack(sd, 0); + } else if (target->type == BL_MOB) { + struct mob_data *md = (struct mob_data *)target; + sd->state.attack_type = 0; + mob_damage(&sd->bl, md, md->hp, 2); + } else + clif_GM_kickack(sd, 0); + } else + clif_GM_kickack(sd, 0); + } +} + +/*========================================== + * /shift + *------------------------------------------ + */ +void clif_parse_Shift(int fd, struct map_session_data *sd) { // Rewriten by [Yor] + char player_name[25]; + + nullpo_retv(sd); + + memset(player_name, '\0', sizeof(player_name)); + + if ((battle_config.atc_gmonly == 0 || pc_isGM(sd)) && + (pc_isGM(sd) >= get_atcommand_level(AtCommand_JumpTo))) { + memcpy(player_name, RFIFOP(fd,2), 24); + atcommand_jumpto(fd, sd, "@jumpto", player_name); // as @jumpto + } + + return; +} + +/*========================================== + * /recall + *------------------------------------------ + */ +void clif_parse_Recall(int fd, struct map_session_data *sd) { // Added by RoVeRT + char player_name[25]; + + nullpo_retv(sd); + + memset(player_name, '\0', sizeof(player_name)); + + if ((battle_config.atc_gmonly == 0 || pc_isGM(sd)) && + (pc_isGM(sd) >= get_atcommand_level(AtCommand_Recall))) { + memcpy(player_name, RFIFOP(fd,2), 24); + atcommand_recall(fd, sd, "@recall", player_name); // as @recall + } + + return; +} + +void clif_parse_GMHide(int fd, struct map_session_data *sd) { // Modified by [Yor] + nullpo_retv(sd); + + //printf("%2x %2x %2x\n", RFIFOW(fd,0), RFIFOW(fd,2), RFIFOW(fd,4)); // R 019d <Option_value>.2B <flag>.2B + if ((battle_config.atc_gmonly == 0 || pc_isGM(sd)) && + (pc_isGM(sd) >= get_atcommand_level(AtCommand_Hide))) { + if (sd->status.option & OPTION_HIDE) { // OPTION_HIDE = 0x40 + sd->status.option &= ~OPTION_HIDE; // OPTION_HIDE = 0x40 + clif_displaymessage(fd, "Invisible: Off."); + } else { + sd->status.option |= OPTION_HIDE; // OPTION_HIDE = 0x40 + clif_displaymessage(fd, "Invisible: On."); + } + clif_changeoption(&sd->bl); + } +} + +/*========================================== + * GMによるチャット禁止時間付与 + *------------------------------------------ + */ +void clif_parse_GMReqNoChat(int fd,struct map_session_data *sd) +{ + int tid = RFIFOL(fd,2); + int type = RFIFOB(fd,6); + int limit = RFIFOW(fd,7); + struct block_list *bl = map_id2bl(tid); + struct map_session_data *dstsd; + int dstfd; + + nullpo_retv(sd); + + if(!battle_config.muting_players) { + clif_displaymessage(fd, "Muting is disabled."); + return; + } + + if(type == 0) + limit = 0 - limit; + if(bl->type == BL_PC && (dstsd =(struct map_session_data *)bl)){ + if((tid == bl->id && type == 2 && !pc_isGM(sd)) || (pc_isGM(sd) > pc_isGM(dstsd)) ){ + dstfd = dstsd->fd; + WFIFOW(dstfd,0)=0x14b; + WFIFOB(dstfd,2)=(type==2)?1:type; + memcpy(WFIFOP(dstfd,3),sd->status.name,24); + WFIFOSET(dstfd,packet_len_table[0x14b]); + dstsd->status.manner -= limit; + if(dstsd->status.manner < 0) + skill_status_change_start(bl,SC_NOCHAT,0,0,0,0,0,0); + else{ + dstsd->status.manner = 0; + skill_status_change_end(bl,SC_NOCHAT,-1); + } + printf("name:%s type:%d limit:%d manner:%d\n",dstsd->status.name,type,limit,dstsd->status.manner); + } + } + + return; +} +/*========================================== + * GMによるチャット禁止時間参照(?) + *------------------------------------------ + */ +void clif_parse_GMReqNoChatCount(int fd,struct map_session_data *sd) +{ + int tid = RFIFOL(fd,2); + + WFIFOW(fd,0)=0x1e0; + WFIFOL(fd,2)=tid; + sprintf(WFIFOP(fd,6),"%d",tid); +// memcpy(WFIFOP(fd,6),"TESTNAME",24); + WFIFOSET(fd,packet_len_table[0x1e0]); + + return; +} + +void clif_parse_PMIgnore(int fd, struct map_session_data *sd) { // Rewritten by [Yor] + char output[1024]; + char *nick; // S 00cf <nick>.24B <type>.B: 00 (/ex nick) deny speech from nick, 01 (/in nick) allow speech from nick + int i; + int pos; + + memset(output, '\0', sizeof(output)); + + nick = RFIFOP(fd,2); // speed up + //printf("Ignore: char '%s' state: %d\n", nick, RFIFOB(fd,26)); + // we ask for deny (we add nick only if it's not already exist + if (RFIFOB(fd,26) == 0) { // type + if (strlen(nick) >= 4 && strlen(nick) < 24) { // do something only if nick can be exist + pos = -1; + for(i = 0; i < (sizeof(sd->ignore) / sizeof(sd->ignore[0])); i++) { + if (strcmp(sd->ignore[i].name, nick) == 0) + break; + else if (pos == -1 && sd->ignore[i].name[0] == '\0') + pos = i; + } + WFIFOW(fd,0) = 0x0d1; // R 00d1 <type>.B <fail>.B: type: 0: deny, 1: allow, fail: 0: success, 1: fail + WFIFOB(fd,2) = 0; + // if a position is found and name not found, we add it in the list + if (pos != -1 && i == (sizeof(sd->ignore) / sizeof(sd->ignore[0]))) { + memcpy(sd->ignore[pos].name, nick, 24); + WFIFOB(fd,3) = 0; // success + WFIFOSET(fd, packet_len_table[0x0d1]); + if (strcmp(wisp_server_name, nick) == 0) { // to found possible bot users that automaticaly ignores people. + sprintf(output, "Character '%s' (account: %d) has tried to block wisps from '%s' (wisp name of the server). Bot user?", sd->status.name, sd->status.account_id, wisp_server_name); + intif_wis_message_to_gm(wisp_server_name, battle_config.hack_info_GM_level, output, strlen(output) + 1); + // send something to be inform and force bot to ignore twice... If GM receiving block + block again, it's a bot :) + clif_wis_message(fd, wisp_server_name, "Add me in your ignore list, doesn't block my wisps.", strlen("Add me in your ignore list, doesn't block my wisps.") + 1); + } + } else { + WFIFOB(fd,3) = 1; // fail + if (i == (sizeof(sd->ignore) / sizeof(sd->ignore[0]))) { + clif_wis_message(fd, wisp_server_name, "You can not block more people.", strlen("You can not block more people.") + 1); + if (strcmp(wisp_server_name, nick) == 0) { // to found possible bot users that automaticaly ignores people. + sprintf(output, "Character '%s' (account: %d) has tried to block wisps from '%s' (wisp name of the server). Bot user?", sd->status.name, sd->status.account_id, wisp_server_name); + intif_wis_message_to_gm(wisp_server_name, battle_config.hack_info_GM_level, output, strlen(output) + 1); + } + } else { + clif_wis_message(fd, wisp_server_name, "This player is already blocked.", strlen("This player is already blocked.") + 1); + if (strcmp(wisp_server_name, nick) == 0) { // to found possible bot users that automaticaly ignores people. + sprintf(output, "Character '%s' (account: %d) has tried AGAIN to block wisps from '%s' (wisp name of the server). Bot user?", sd->status.name, sd->status.account_id, wisp_server_name); + intif_wis_message_to_gm(wisp_server_name, battle_config.hack_info_GM_level, output, strlen(output) + 1); + } + } + } + } else + clif_wis_message(fd, wisp_server_name, "It's impossible to block this player.", strlen("It's impossible to block this player.") + 1); + // we ask for allow (we remove all same nick if exist) + } else { + if (strlen(nick) >= 4 && strlen(nick) < 24) { // do something only if nick can be exist + WFIFOW(fd,0) = 0x0d1; // R 00d1 <type>.B <fail>.B: type: 0: deny, 1: allow, fail: 0: success, 1: fail + WFIFOB(fd,2) = 1; + for(i = 0; i < (sizeof(sd->ignore) / sizeof(sd->ignore[0])); i++) + if (strcmp(sd->ignore[i].name, nick) == 0) { + memset(sd->ignore[i].name, 0, sizeof(sd->ignore[i].name)); + WFIFOB(fd,3) = 0; // success + WFIFOSET(fd, packet_len_table[0x0d1]); + break; + } + if (i == (sizeof(sd->ignore) / sizeof(sd->ignore[0]))) { + WFIFOB(fd,3) = 1; // fail + WFIFOSET(fd, packet_len_table[0x0d1]); + clif_wis_message(fd, wisp_server_name, "This player is not blocked by you.", strlen("This player is not blocked by you.") + 1); + } + } else + clif_wis_message(fd, wisp_server_name, "It's impossible to unblock this player.", strlen("It's impossible to unblock this player.") + 1); + } + +// for(i = 0; i < (sizeof(sd->ignore) / sizeof(sd->ignore[0])); i++) // for debug only +// if (sd->ignore[i].name[0] != '\0') +// printf("Ignored player: '%s'\n", sd->ignore[i].name); + + return; +} + +void clif_parse_PMIgnoreAll(int fd, struct map_session_data *sd) { // Rewritten by [Yor] + //printf("Ignore all: state: %d\n", RFIFOB(fd,2)); + if (RFIFOB(fd,2) == 0) {// S 00d0 <type>len.B: 00 (/exall) deny all speech, 01 (/inall) allow all speech + WFIFOW(fd,0) = 0x0d2; // R 00d2 <type>.B <fail>.B: type: 0: deny, 1: allow, fail: 0: success, 1: fail + WFIFOB(fd,2) = 0; + if (sd->ignoreAll == 0) { + sd->ignoreAll = 1; + WFIFOB(fd,3) = 0; // success + WFIFOSET(fd, packet_len_table[0x0d2]); + } else { + WFIFOB(fd,3) = 1; // fail + WFIFOSET(fd, packet_len_table[0x0d2]); + clif_wis_message(fd, wisp_server_name, "You already block everyone.", strlen("You already block everyone.") + 1); + } + } else { + WFIFOW(fd,0) = 0x0d2; // R 00d2 <type>.B <fail>.B: type: 0: deny, 1: allow, fail: 0: success, 1: fail + WFIFOB(fd,2) = 1; + if (sd->ignoreAll == 1) { + sd->ignoreAll = 0; + WFIFOB(fd,3) = 0; // success + WFIFOSET(fd, packet_len_table[0x0d2]); + } else { + WFIFOB(fd,3) = 1; // fail + WFIFOSET(fd, packet_len_table[0x0d2]); + clif_wis_message(fd, wisp_server_name, "You already allow everyone.", strlen("You already allow everyone.") + 1); + } + } + + return; +} + +void clif_parse_skillMessage(int fd, struct map_session_data *sd) { // Added by RoVeRT + int skillid,skilllv, x, y; + char *mes; + + skilllv = RFIFOW(fd,2); + skillid = RFIFOW(fd,4); + + y = RFIFOB(fd,6); + x = RFIFOB(fd,8); + + mes = RFIFOP(fd,10); + + // skill 220 = graffiti +// printf("skill: %d %d location: %3d %3d message: %s\n", skillid, skilllv, x, y, (char*)mes); +} + +int monk(struct map_session_data *sd, struct block_list *target, int type) { +//R 01d1 <Monk id>L <Target monster id>L <Bool>L + int fd=sd->fd; + WFIFOW(fd,0)=0x1d1; + WFIFOL(fd,2)=sd->bl.id; + WFIFOL(fd,6)=target->id; + WFIFOL(fd,10)=type; + WFIFOSET(fd,packet_len_table[0x1d1]); + + return 0; +} + +/*========================================== + * スパノビの/doridoriによるSPR2倍 + *------------------------------------------ + */ +void clif_parse_sn_doridori(int fd, struct map_session_data *sd) { + if (sd) + sd->doridori_counter = 1; + + return; +} +/*========================================== + * スパノビの爆裂波動 + *------------------------------------------ + */ +void clif_parse_sn_explosionspirits(int fd, struct map_session_data *sd) +{ + if(sd){ + int nextbaseexp=pc_nextbaseexp(sd); + struct pc_base_job s_class = pc_calc_base_job(sd->status.class); + if (battle_config.etc_log){ + if(nextbaseexp != 0) + printf("SuperNovice explosionspirits!! %d %d %d %d\n",sd->bl.id,s_class.job,sd->status.base_exp,(int)((double)1000*sd->status.base_exp/nextbaseexp)); + else + printf("SuperNovice explosionspirits!! %d %d %d 000\n",sd->bl.id,s_class.job,sd->status.base_exp); + } + if(s_class.job == 23 && sd->status.base_exp > 0 && nextbaseexp > 0 && (int)((double)1000*sd->status.base_exp/nextbaseexp)%100==0){ + clif_skill_nodamage(&sd->bl,&sd->bl,MO_EXPLOSIONSPIRITS,5,1); + skill_status_change_start(&sd->bl,SkillStatusChangeTable[MO_EXPLOSIONSPIRITS],5,0,0,0,skill_get_time(MO_EXPLOSIONSPIRITS,5),0 ); + } + } + return; +} + +// functions list +static void (*clif_parse_func_table[4][0x220])() = { + { + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + + // 40 + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + + // 70 + NULL, NULL, clif_parse_WantToConnection, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, clif_parse_LoadEndAck, clif_parse_TickSend, NULL, + + // 80 + NULL, NULL, NULL, NULL, NULL, clif_parse_WalkToXY, NULL, NULL, + NULL, clif_parse_ActionRequest, NULL, NULL, clif_parse_GlobalMessage, NULL, NULL, NULL, + // 90 + clif_parse_NpcClicked, NULL, NULL, NULL, clif_parse_GetCharNameRequest, NULL, clif_parse_Wis, NULL, + NULL, clif_parse_GMmessage, NULL, clif_parse_ChangeDir, NULL, NULL, NULL, clif_parse_TakeItem, + // a0 + NULL, NULL, clif_parse_DropItem, NULL, NULL, NULL, NULL, clif_parse_UseItem, + NULL, clif_parse_EquipItem, NULL, clif_parse_UnequipItem, NULL, NULL, NULL, NULL, + // b0 + NULL, NULL, clif_parse_Restart, NULL, NULL, NULL, NULL, NULL, + clif_parse_NpcSelectMenu, clif_parse_NpcNextClicked, NULL, clif_parse_StatusUp, NULL, NULL, NULL, clif_parse_Emotion, + + // c0 + NULL, clif_parse_HowManyConnections, NULL, NULL, NULL, clif_parse_NpcBuySellSelected, NULL, NULL, + clif_parse_NpcBuyListSend, clif_parse_NpcSellListSend, NULL, NULL, clif_parse_GMKick, NULL, NULL, clif_parse_PMIgnore, + // d0 + clif_parse_PMIgnoreAll, NULL, NULL, NULL, NULL, clif_parse_CreateChatRoom, NULL, NULL, + NULL, clif_parse_ChatAddMember, NULL, NULL, NULL, NULL, clif_parse_ChatRoomStatusChange, NULL, + // e0 + clif_parse_ChangeChatOwner, NULL, clif_parse_KickFromChat, clif_parse_ChatLeave, clif_parse_TradeRequest, NULL, clif_parse_TradeAck, NULL, + clif_parse_TradeAddItem, NULL, NULL, clif_parse_TradeOk, NULL, clif_parse_TradeCansel, NULL, clif_parse_TradeCommit, + // f0 + NULL, NULL, NULL, clif_parse_MoveToKafra, NULL, clif_parse_MoveFromKafra, NULL, clif_parse_CloseKafra, + NULL, clif_parse_CreateParty, NULL, NULL, clif_parse_PartyInvite, NULL, NULL, clif_parse_ReplyPartyInvite, + + // 100 + clif_parse_LeaveParty, NULL, clif_parse_PartyChangeOption, clif_parse_RemovePartyMember, NULL, NULL, NULL, NULL, + clif_parse_PartyMessage, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + // 110 + NULL, NULL, clif_parse_SkillUp, clif_parse_UseSkillToId, NULL, NULL, clif_parse_UseSkillToPos, NULL, + clif_parse_StopAttack, NULL, NULL, clif_parse_UseSkillMap, NULL, clif_parse_RequestMemo, NULL, NULL, + // 120 + NULL, NULL, NULL, NULL, NULL, NULL, clif_parse_PutItemToCart, clif_parse_GetItemFromCart, + clif_parse_MoveFromKafraToCart, clif_parse_MoveToKafraFromCart, clif_parse_RemoveOption, NULL, NULL, NULL, clif_parse_CloseVending, NULL, + // 130 + clif_parse_VendingListReq, NULL, NULL, NULL, clif_parse_PurchaseReq, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, clif_parse_GM_Monster_Item, + + // 140 + clif_parse_MapMove, NULL, NULL, clif_parse_NpcAmountInput, NULL, NULL, clif_parse_NpcCloseClicked, NULL, + NULL, clif_parse_GMReqNoChat, NULL, NULL, NULL, clif_parse_GuildCheckMaster, NULL, clif_parse_GuildReqeustInfo, + // 150 + NULL, clif_parse_GuildRequestEmblem, NULL, clif_parse_GuildChangeEmblem, NULL, clif_parse_GuildChangeMemberPosition, NULL, NULL, + NULL, clif_parse_GuildLeave, NULL, clif_parse_GuildExplusion, NULL, clif_parse_GuildBreak, NULL, NULL, + // 160 + NULL, clif_parse_GuildChangePositionInfo, NULL, NULL, NULL, clif_parse_CreateGuild, NULL, NULL, + clif_parse_GuildInvite, NULL, NULL, clif_parse_GuildReplyInvite, NULL, NULL, clif_parse_GuildChangeNotice, NULL, + // 170 + clif_parse_GuildRequestAlliance, NULL, clif_parse_GuildReplyAlliance, NULL, NULL, NULL, NULL, NULL, + clif_parse_ItemIdentify, NULL, clif_parse_UseCard, NULL, clif_parse_InsertCard, NULL, clif_parse_GuildMessage, NULL, + + // 180 + clif_parse_GuildOpposition, NULL, NULL, clif_parse_GuildDelAlliance, NULL, NULL, NULL, NULL, + NULL, NULL, clif_parse_QuitGame, NULL, NULL, NULL, clif_parse_ProduceMix, NULL, + // 190 + clif_parse_UseSkillToPos, NULL, NULL, clif_parse_SolveCharName, NULL, NULL, NULL, clif_parse_ResetChar, + NULL, NULL, NULL, NULL, clif_parse_LGMmessage, clif_parse_GMHide, NULL, clif_parse_CatchPet, + // 1a0 + NULL, clif_parse_PetMenu, NULL, NULL, NULL, clif_parse_ChangePetName, NULL, clif_parse_SelectEgg, + NULL, clif_parse_SendEmotion, NULL, NULL, NULL, NULL, clif_parse_SelectArrow, clif_parse_ChangeCart, + // 1b0 + NULL, NULL, clif_parse_OpenVending, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, clif_parse_Shift, clif_parse_Shift, clif_parse_Recall, clif_parse_Recall, NULL, NULL, + + // 1c0 + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, clif_parse_AutoSpell, + NULL, + // 1d0 + NULL, NULL, NULL, NULL, NULL, clif_parse_NpcStringInput, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, clif_parse_GMReqNoChatCount, + // 1e0 + NULL, NULL, NULL, NULL, NULL, NULL, NULL, clif_parse_sn_doridori, + clif_parse_CreateParty2, NULL, NULL, NULL, NULL, clif_parse_sn_explosionspirits, NULL, NULL, + // 1f0 + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + + // 200 + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + // 210 + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, +#if 0 + case 0xce: clif_parse_GMkillall + case 0xd3: clif_parse_IgnoreList +#endif + }, + {NULL}, + {NULL}, + {NULL} +}; + +/*========================================== + * クライアントからのパケット解析 + * socket.cのdo_parsepacketから呼び出される + *------------------------------------------ + */ +static int clif_parse(int fd) { + int packet_len = 0, cmd=0, packet_ver =0; + struct map_session_data *sd=NULL; + + sd = session[fd]->session_data; + + // 接続が切れてるので後始末 + if (!chrif_isconnect() || session[fd]->eof) { // char鯖に繋がってない間は接続禁止 (!chrif_isconnect()) + if (sd && sd->state.auth) { + clif_quitsave(fd, sd); + if (sd->status.name != NULL) + printf("Player [%s] has logged off your server.\n", sd->status.name); // Player logout display [Valaris] + else + printf("Player with account [%d] has logged off your server.\n", sd->bl.id); // Player logout display [Yor] + } else if (sd) { // not authentified! (refused by char-server or disconnect before to be authentified) + printf("Player with account [%d] has logged off your server (not auth account).\n", sd->bl.id); // Player logout display [Yor] + map_deliddb(&sd->bl); // account_id has been included in the DB before auth answer + } + if(fd) close(fd); + if(fd) delete_session(fd); + return 0; + } + + if (RFIFOREST(fd) < 2) + return 0; + + cmd = RFIFOW(fd,0); + + // 管理用パケット処理 + if (cmd >= 30000) { + switch(cmd) { + case 0x7530: // Athena情報所得 + WFIFOW(fd,0) = 0x7531; + WFIFOB(fd,2) = ATHENA_MAJOR_VERSION; + WFIFOB(fd,3) = ATHENA_MINOR_VERSION; + WFIFOB(fd,4) = ATHENA_REVISION; + WFIFOB(fd,5) = ATHENA_RELEASE_FLAG; + WFIFOB(fd,6) = ATHENA_OFFICIAL_FLAG; + WFIFOB(fd,7) = ATHENA_SERVER_MAP; + WFIFOW(fd,8) = ATHENA_MOD_VERSION; + WFIFOSET(fd,10); + RFIFOSKIP(fd,2); + break; + case 0x7532: // 接続の切断 + session[fd]->eof = 1; + break; + } + return 0; + } + + // get packet version before to parse + packet_ver = 0; + if (sd) + packet_ver = sd->packet_ver; + // check authentification packet to know packet version + else { + // 0x72 + if (cmd == 0x72) { + if (RFIFOREST(fd) >= 39 && (RFIFOB(fd,38) == 0 || RFIFOB(fd,38) == 1)) // 00 = Female, 01 = Male + packet_ver = 7; // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + else if (RFIFOREST(fd) >= 22 && (RFIFOB(fd,21) == 0 || RFIFOB(fd,21) == 1)) // 00 = Female, 01 = Male + packet_ver = 6; // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + else if (RFIFOREST(fd) >= 19 && (RFIFOB(fd,18) == 0 || RFIFOB(fd,18) == 1)) // 00 = Female, 01 = Male + packet_ver = 5; // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + // else probably incomplete packet + else if (RFIFOREST(fd) < 19) + return 0; + // 0x7E + } else if (cmd == 0x7E) { + if (RFIFOREST(fd) >= 37 && (RFIFOB(fd,36) == 0 || RFIFOB(fd,36) == 1)) // 00 = Female, 01 = Male + packet_ver = 9; // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + else if (RFIFOREST(fd) >= 33 && (RFIFOB(fd,32) == 0 || RFIFOB(fd,32) == 1)) // 00 = Female, 01 = Male + packet_ver = 8; // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + // else probably incomplete packet + else if (RFIFOREST(fd) < 33) + return 0; + // 0xF5 + } else { + if (RFIFOREST(fd) >= 34 && (RFIFOB(fd,33) == 0 || RFIFOB(fd,33) == 1)) // 00 = Female, 01 = Male + packet_ver = 10; // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + // else probably incomplete packet + else if (RFIFOREST(fd) < 34) + return 0; + } + // check if version is accepted + if ((packet_ver == 5 && (battle_config.packet_ver_flag & 1) == 0) || + (packet_ver == 6 && (battle_config.packet_ver_flag & 2) == 0) || + (packet_ver == 7 && (battle_config.packet_ver_flag & 4) == 0) || + (packet_ver == 8 && (battle_config.packet_ver_flag & 8) == 0) || + (packet_ver == 9 && (battle_config.packet_ver_flag & 16) == 0) || + (packet_ver == 10 && (battle_config.packet_ver_flag & 32) == 0)) { + WFIFOW(fd,0) = 0x6a; + WFIFOB(fd,2) = 5; // 05 = Game's EXE is not the latest version + WFIFOSET(fd,23); + session[fd]->eof = 1; + return 0; + } + } + + // ゲーム用以外パケットか、認証を終える前に0072以外が来たら、切断する + if (packet_ver < 5 || packet_ver > 10 || // if packet is not inside these values: session is incorrect?? or auth packet is unknown + cmd >= 0x220 || packet_size_table[packet_ver-5][cmd] == 0) { + if (!fd) + return 0; + session[fd]->eof = 1; + printf("clif_parse: session #%d, packet 0x%x (%d bytes received) -> disconnected.\n", fd, cmd, RFIFOREST(fd)); + return 0; + } + + // パケット長を計算 + packet_len = packet_size_table[packet_ver-5][cmd]; + if (packet_len == -1) { + if (RFIFOREST(fd) < 4) + return 0; // 可変長パケットで長さの所までデータが来てない + packet_len = RFIFOW(fd,2); + if (packet_len < 4 || packet_len > 32768) { + session[fd]->eof = 1; + return 0; + } + } + if (RFIFOREST(fd) < packet_len) + return 0; // まだ1パケット分データが揃ってない + + if (sd && sd->state.auth == 1 && sd->state.waitingdisconnect == 1) { // 切断待ちの場合パケットを処理しない + + } else if (packet_ver < 8 && clif_parse_func_table[0][cmd]) { // packet version 5-6-7 use same functions, but size are different + // パケット処理 + clif_parse_func_table[0][cmd](fd, sd); + } else if (packet_ver >= 8 && clif_parse_func_table[packet_ver - 7][cmd]) { + // パケット処理 + clif_parse_func_table[packet_ver - 7][cmd](fd, sd); + } else { + // 不明なパケット + if (battle_config.error_log) { + if (fd) + printf("\nclif_parse: session #%d, packet 0x%x, lenght %d\n", fd, cmd, packet_len); +#ifdef DUMP_UNKNOWN_PACKET + { + int i; + FILE *fp; + char packet_txt[256] = "save/packet.txt"; + time_t now; + printf("---- 00-01-02-03-04-05-06-07-08-09-0A-0B-0C-0D-0E-0F"); + for(i = 0; i < packet_len; i++) { + if ((i & 15) == 0) + printf("\n%04X ",i); + printf("%02X ", RFIFOB(fd,i)); + } + if (sd && sd->state.auth) { + if (sd->status.name != NULL) + printf("\nAccount ID %d, character ID %d, player name %s.\n", + sd->status.account_id, sd->status.char_id, sd->status.name); + else + printf("\nAccount ID %d.\n", sd->bl.id); + } else if (sd) // not authentified! (refused by char-server or disconnect before to be authentified) + printf("\nAccount ID %d.\n", sd->bl.id); + + if ((fp = fopen(packet_txt, "a")) == NULL) { + printf("clif.c: cant write [%s] !!! data is lost !!!\n", packet_txt); + return 1; + } else { + time(&now); + if (sd && sd->state.auth) { + if (sd->status.name != NULL) + fprintf(fp, "%sPlayer with account ID %d (character ID %d, player name %s) sent wrong packet:\n", + asctime(localtime(&now)), sd->status.account_id, sd->status.char_id, sd->status.name); + else + fprintf(fp, "%sPlayer with account ID %d sent wrong packet:\n", asctime(localtime(&now)), sd->bl.id); + } else if (sd) // not authentified! (refused by char-server or disconnect before to be authentified) + fprintf(fp, "%sPlayer with account ID %d sent wrong packet:\n", asctime(localtime(&now)), sd->bl.id); + + fprintf(fp, "\t---- 00-01-02-03-04-05-06-07-08-09-0A-0B-0C-0D-0E-0F"); + for(i = 0; i < packet_len; i++) { + if ((i & 15) == 0) + fprintf(fp, "\n\t%04X ", i); + fprintf(fp, "%02X ", RFIFOB(fd,i)); + } + fprintf(fp, "\n\n"); + fclose(fp); + } + } +#endif + } + } + RFIFOSKIP(fd, packet_len); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int do_init_clif(void) { + int i; + + // functions of packet version 5-6-7 are same, but size are different + // init packet function calls for packet ver 8 + memcpy(clif_parse_func_table[1], clif_parse_func_table[0], sizeof(clif_parse_func_table[0])); + clif_parse_func_table[1][0x072] = clif_parse_DropItem; + clif_parse_func_table[1][0x07e] = clif_parse_WantToConnection; + clif_parse_func_table[1][0x085] = clif_parse_UseSkillToId; + clif_parse_func_table[1][0x089] = clif_parse_GetCharNameRequest; + clif_parse_func_table[1][0x08c] = clif_parse_UseSkillToPos; + clif_parse_func_table[1][0x094] = clif_parse_TakeItem; + clif_parse_func_table[1][0x09b] = clif_parse_WalkToXY; + clif_parse_func_table[1][0x09f] = clif_parse_ChangeDir; + clif_parse_func_table[1][0x0a2] = clif_parse_UseSkillToPos; + clif_parse_func_table[1][0x0a7] = clif_parse_SolveCharName; + clif_parse_func_table[1][0x0f3] = clif_parse_GlobalMessage; + clif_parse_func_table[1][0x0f5] = clif_parse_UseItem; + clif_parse_func_table[1][0x0f7] = clif_parse_TickSend; + clif_parse_func_table[1][0x113] = clif_parse_MoveToKafra; + clif_parse_func_table[1][0x116] = clif_parse_CloseKafra; + clif_parse_func_table[1][0x190] = clif_parse_MoveFromKafra; + clif_parse_func_table[1][0x193] = clif_parse_ActionRequest; + // init packet function calls for packet ver 9 (same function of packet version 8, but size are different) + memcpy(clif_parse_func_table[2], clif_parse_func_table[1], sizeof(clif_parse_func_table[0])); + // init packet function calls for packet ver 10 + memcpy(clif_parse_func_table[3], clif_parse_func_table[2], sizeof(clif_parse_func_table[0])); + clif_parse_func_table[3][0x072] = clif_parse_UseItem; + clif_parse_func_table[3][0x07e] = clif_parse_MoveToKafra; + clif_parse_func_table[3][0x085] = clif_parse_ActionRequest; + clif_parse_func_table[3][0x089] = clif_parse_WalkToXY; + clif_parse_func_table[3][0x08c] = clif_parse_UseSkillToPos; + clif_parse_func_table[3][0x094] = clif_parse_DropItem; + clif_parse_func_table[3][0x09b] = clif_parse_GetCharNameRequest; + clif_parse_func_table[3][0x09f] = clif_parse_GlobalMessage; + clif_parse_func_table[3][0x0a2] = clif_parse_SolveCharName; + clif_parse_func_table[3][0x0a7] = clif_parse_UseSkillToPos; + clif_parse_func_table[3][0x0f3] = clif_parse_ChangeDir; + clif_parse_func_table[3][0x0f5] = clif_parse_WantToConnection; + clif_parse_func_table[3][0x0f7] = clif_parse_CloseKafra; + clif_parse_func_table[3][0x113] = clif_parse_TakeItem; + clif_parse_func_table[3][0x116] = clif_parse_TickSend; + clif_parse_func_table[3][0x190] = clif_parse_UseSkillToId; + clif_parse_func_table[3][0x193] = clif_parse_MoveFromKafra; + + // size of packet version 5 + memcpy(&packet_size_table[0], &packet_len_table, sizeof(packet_len_table)); + // size of packet version 6 + memcpy(&packet_size_table[1], &packet_size_table[0], sizeof(packet_len_table)); + packet_size_table[1][0x072] = 22; + packet_size_table[1][0x085] = 8; + packet_size_table[1][0x0a7] = 13; + packet_size_table[1][0x113] = 15; + packet_size_table[1][0x116] = 15; + packet_size_table[1][0x190] = 95; + // size of packet version 7 + memcpy(&packet_size_table[2], &packet_size_table[1], sizeof(packet_len_table)); + packet_size_table[2][0x072] = 39; + packet_size_table[2][0x085] = 9; + packet_size_table[2][0x09b] = 13; + packet_size_table[2][0x09f] = 10; + packet_size_table[2][0x0a7] = 17; + packet_size_table[2][0x113] = 19; + packet_size_table[2][0x116] = 19; + packet_size_table[2][0x190] = 99; + // size of packet version 8 + memcpy(&packet_size_table[3], &packet_size_table[2], sizeof(packet_len_table)); + packet_size_table[3][0x072] = 14; + packet_size_table[3][0x07e] = 33; + packet_size_table[3][0x085] = 20; + packet_size_table[3][0x089] = 15; + packet_size_table[3][0x08c] = 23; + packet_size_table[3][0x094] = 10; + packet_size_table[3][0x09b] = 6; + packet_size_table[3][0x09f] = 13; + packet_size_table[3][0x0a2] = 103; + packet_size_table[3][0x0a7] = 12; + packet_size_table[3][0x0f3] = -1; + packet_size_table[3][0x0f5] = 17; + packet_size_table[3][0x0f7] = 10; + packet_size_table[3][0x113] = 16; + packet_size_table[3][0x116] = 2; + packet_size_table[3][0x190] = 26; + packet_size_table[3][0x193] = 9; + // size of packet version 9 + memcpy(&packet_size_table[4], &packet_size_table[3], sizeof(packet_len_table)); + packet_size_table[4][0x072] = 17; + packet_size_table[4][0x07e] = 37; + packet_size_table[4][0x085] = 26; + packet_size_table[4][0x089] = 12; + packet_size_table[4][0x08c] = 40; + packet_size_table[4][0x094] = 13; + packet_size_table[4][0x09b] = 15; + packet_size_table[4][0x09f] = 12; + packet_size_table[4][0x0a2] = 120; + packet_size_table[4][0x0a7] = 11; +// packet_size_table[4][0x0f3] = -1; + packet_size_table[4][0x0f5] = 24; + packet_size_table[4][0x0f7] = 13; + packet_size_table[4][0x113] = 23; +// packet_size_table[4][0x116] = 2; + packet_size_table[4][0x190] = 26; + packet_size_table[4][0x193] = 18; + // new packet + packet_size_table[4][0x20f] = 10; + packet_size_table[4][0x210] = 22; + packet_size_table[4][0x212] = 26; + packet_size_table[4][0x213] = 26; + packet_size_table[4][0x214] = 42; + // size of packet version 9 + memcpy(&packet_size_table[5], &packet_size_table[4], sizeof(packet_len_table)); + packet_size_table[5][0x072] = 20; + packet_size_table[5][0x07e] = 19; + packet_size_table[5][0x085] = 23; + packet_size_table[5][0x089] = 9; + packet_size_table[5][0x08c] = 105; + packet_size_table[5][0x094] = 17; + packet_size_table[5][0x09b] = 14; + packet_size_table[5][0x09f] = -1; + packet_size_table[5][0x0a2] = 14; + packet_size_table[5][0x0a7] = 25; + packet_size_table[5][0x0f3] = 10; + packet_size_table[5][0x0f5] = 34; + packet_size_table[5][0x0f7] = 2; + packet_size_table[5][0x113] = 11; + packet_size_table[5][0x116] = 11; + packet_size_table[5][0x190] = 22; + packet_size_table[5][0x193] = 17; + + set_defaultparse(clif_parse); + for(i = 0; i < 10; i++) { + if (make_listen_port(map_port)) + break; +#ifdef LCCWIN32 + Sleep(20000); +#else + sleep(20); +#endif + } + if (i == 10) { + printf("cant bind game port\n"); + exit(1); + } + + add_timer_func_list(clif_waitclose, "clif_waitclose"); + add_timer_func_list(clif_clearchar_delay_sub, "clif_clearchar_delay_sub"); + + return 0; +} + diff --git a/misc/src/map/clif.h b/misc/src/map/clif.h new file mode 100644 index 0000000..cdddd8b --- /dev/null +++ b/misc/src/map/clif.h @@ -0,0 +1,280 @@ +// $Id: clif.h,v 1.4 2004/09/25 05:32:18 MouseJstr Exp $ +#ifndef _CLIF_H_ +#define _CLIF_H_ + +#include <sys/types.h> + +#ifdef LCCWIN32 +#include <winsock.h> +typedef unsigned int in_addr_t; +#else +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#endif + +#include "map.h" + +void clif_setip(char*); +void clif_setport(int); + +in_addr_t clif_getip(void); +int clif_getport(void); +int clif_countusers(void); +void clif_setwaitclose(int); + +int clif_authok(struct map_session_data *); +int clif_authfail_fd(int,int); +int clif_charselectok(int); +int clif_dropflooritem(struct flooritem_data *); +int clif_clearflooritem(struct flooritem_data *,int); +int clif_clearchar(struct block_list*,int); // area or fd +int clif_clearchar_delay(unsigned int,struct block_list *,int); +#define clif_clearchar_area(bl,type) clif_clearchar(bl,type) +int clif_clearchar_id(int,int,int); +int clif_spawnpc(struct map_session_data*); //area +int clif_spawnnpc(struct npc_data*); // area +int clif_spawnmob(struct mob_data*); // area +int clif_spawnpet(struct pet_data*); // area +int clif_walkok(struct map_session_data*); // self +int clif_movechar(struct map_session_data*); // area +int clif_movemob(struct mob_data*); //area +int clif_movepet(struct pet_data *pd); //area +int clif_changemap(struct map_session_data*,char*,int,int); //self +int clif_changemapserver(struct map_session_data*,char*,int,int,int,int); //self +int clif_fixpos(struct block_list *); // area +int clif_fixmobpos(struct mob_data *md); +int clif_fixpcpos(struct map_session_data *sd); +int clif_fixpetpos(struct pet_data *pd); +int clif_npcbuysell(struct map_session_data*,int); //self +int clif_buylist(struct map_session_data*,struct npc_data*); //self +int clif_selllist(struct map_session_data*); //self +int clif_scriptmes(struct map_session_data*,int,char*); //self +int clif_scriptnext(struct map_session_data*,int); //self +int clif_scriptclose(struct map_session_data*,int); //self +int clif_scriptmenu(struct map_session_data*,int,char*); //self +int clif_scriptinput(struct map_session_data*,int); //self +int clif_scriptinputstr(struct map_session_data *sd,int npcid); // self +int clif_cutin(struct map_session_data*,char*,int); //self +int clif_viewpoint(struct map_session_data*,int,int,int,int,int,int); //self +int clif_additem(struct map_session_data*,int,int,int); //self +int clif_delitem(struct map_session_data*,int,int); //self +int clif_updatestatus(struct map_session_data*,int); //self +int clif_changestatus(struct block_list*,int,int); //area +int clif_damage(struct block_list *,struct block_list *,unsigned int,int,int,int,int,int,int); // area +#define clif_takeitem(src,dst) clif_damage(src,dst,0,0,0,0,0,1,0) +int clif_changelook(struct block_list *,int,int); // area +int clif_arrowequip(struct map_session_data *sd,int val); //self +int clif_arrow_fail(struct map_session_data *sd,int type); //self +int clif_arrow_create_list(struct map_session_data *sd); //self +int clif_statusupack(struct map_session_data *,int,int,int); // self +int clif_equipitemack(struct map_session_data *,int,int,int); // self +int clif_unequipitemack(struct map_session_data *,int,int,int); // self +int clif_misceffect(struct block_list*,int); // area +int clif_misceffect2(struct block_list *bl,int type); +int clif_changeoption(struct block_list*); // area +int clif_useitemack(struct map_session_data*,int,int,int); // self + +int clif_createchat(struct map_session_data*,int); // self +int clif_dispchat(struct chat_data*,int); // area or fd +int clif_joinchatfail(struct map_session_data*,int); // self +int clif_joinchatok(struct map_session_data*,struct chat_data*); // self +int clif_addchat(struct chat_data*,struct map_session_data*); // chat +int clif_changechatowner(struct chat_data*,struct map_session_data*); // chat +int clif_clearchat(struct chat_data*,int); // area or fd +int clif_leavechat(struct chat_data*,struct map_session_data*); // chat +int clif_changechatstatus(struct chat_data*); // chat + +void clif_emotion(struct block_list *bl,int type); +void clif_talkiebox(struct block_list *bl,char* talkie); +void clif_wedding_effect(struct block_list *bl); +void clif_sitting(int fd, struct map_session_data *sd); +//void clif_callpartner(struct map_session_data *sd); +//void clif_sitting(struct map_session_data *sd); +void clif_soundeffect(struct map_session_data *sd,struct block_list *bl,char *name,int type); + +// trade +int clif_traderequest(struct map_session_data *sd,char *name); +int clif_tradestart(struct map_session_data *sd,int type); +int clif_tradeadditem(struct map_session_data *sd,struct map_session_data *tsd,int index,int amount); +int clif_tradeitemok(struct map_session_data *sd,int index,int amount,int fail); +int clif_tradedeal_lock(struct map_session_data *sd,int fail); +int clif_tradecancelled(struct map_session_data *sd); +int clif_tradecompleted(struct map_session_data *sd,int fail); + +// storage +#include "storage.h" +int clif_storageitemlist(struct map_session_data *sd,struct storage *stor); +int clif_storageequiplist(struct map_session_data *sd,struct storage *stor); +int clif_updatestorageamount(struct map_session_data *sd,struct storage *stor); +int clif_storageitemadded(struct map_session_data *sd,struct storage *stor,int index,int amount); +int clif_storageitemremoved(struct map_session_data *sd,int index,int amount); +int clif_storageclose(struct map_session_data *sd); +int clif_guildstorageitemlist(struct map_session_data *sd,struct guild_storage *stor); +int clif_guildstorageequiplist(struct map_session_data *sd,struct guild_storage *stor); +int clif_updateguildstorageamount(struct map_session_data *sd,struct guild_storage *stor); +int clif_guildstorageitemadded(struct map_session_data *sd,struct guild_storage *stor,int index,int amount); + +int clif_pcinsight(struct block_list *,va_list); // map_forallinmovearea callback +int clif_pcoutsight(struct block_list *,va_list); // map_forallinmovearea callback +int clif_mobinsight(struct block_list *,va_list); // map_forallinmovearea callback +int clif_moboutsight(struct block_list *,va_list); // map_forallinmovearea callback +int clif_petoutsight(struct block_list *bl,va_list ap); +int clif_petinsight(struct block_list *bl,va_list ap); + +int clif_class_change(struct block_list *bl,int class,int type); +int clif_mob_class_change(struct mob_data *md,int class); +int clif_mob_equip(struct mob_data *md,int nameid); // [Valaris] + +int clif_skillinfo(struct map_session_data *sd,int skillid,int type,int range); +int clif_skillinfoblock(struct map_session_data *sd); +int clif_skillup(struct map_session_data *sd,int skill_num); + +int clif_skillcasting(struct block_list* bl, + int src_id,int dst_id,int dst_x,int dst_y,int skill_num,int casttime); +int clif_skillcastcancel(struct block_list* bl); +int clif_skill_fail(struct map_session_data *sd,int skill_id,int type,int btype); +int clif_skill_damage(struct block_list *src,struct block_list *dst, + unsigned int tick,int sdelay,int ddelay,int damage,int div, + int skill_id,int skill_lv,int type); +int clif_skill_damage2(struct block_list *src,struct block_list *dst, + unsigned int tick,int sdelay,int ddelay,int damage,int div, + int skill_id,int skill_lv,int type); +int clif_skill_nodamage(struct block_list *src,struct block_list *dst, + int skill_id,int heal,int fail); +int clif_skill_poseffect(struct block_list *src,int skill_id, + int val,int x,int y,int tick); +int clif_skill_estimation(struct map_session_data *sd,struct block_list *dst); +int clif_skill_warppoint(struct map_session_data *sd,int skill_num, + const char *map1,const char *map2,const char *map3,const char *map4); +int clif_skill_memo(struct map_session_data *sd,int flag); +int clif_skill_teleportmessage(struct map_session_data *sd,int flag); +int clif_skill_produce_mix_list(struct map_session_data *sd,int trigger); + +int clif_produceeffect(struct map_session_data *sd,int flag,int nameid); + +int clif_skill_setunit(struct skill_unit *unit); +int clif_skill_delunit(struct skill_unit *unit); + +int clif_01ac(struct block_list *bl); + +int clif_autospell(struct map_session_data *sd,int skilllv); +int clif_devotion(struct map_session_data *sd,int target); +int clif_spiritball(struct map_session_data *sd); +int clif_combo_delay(struct block_list *src,int wait); +int clif_bladestop(struct block_list *src,struct block_list *dst,int bool); +int clif_changemapcell(int m,int x,int y,int cell_type,int type); + +int clif_status_change(struct block_list *bl,int type,int flag); + +int clif_wis_message(int fd,char *nick,char *mes,int mes_len); +int clif_wis_end(int fd,int flag); + +int clif_solved_charname(struct map_session_data *sd,int char_id); + +int clif_use_card(struct map_session_data *sd,int idx); +int clif_insert_card(struct map_session_data *sd,int idx_equip,int idx_card,int flag); + +int clif_itemlist(struct map_session_data *sd); +int clif_equiplist(struct map_session_data *sd); + +int clif_cart_additem(struct map_session_data*,int,int,int); +int clif_cart_delitem(struct map_session_data*,int,int); +int clif_cart_itemlist(struct map_session_data *sd); +int clif_cart_equiplist(struct map_session_data *sd); + +int clif_item_identify_list(struct map_session_data *sd); +int clif_item_identified(struct map_session_data *sd,int idx,int flag); +int clif_item_repair_list(struct map_session_data *sd); + +int clif_item_skill(struct map_session_data *sd,int skillid,int skilllv,const char *name); + +int clif_mvp_effect(struct map_session_data *sd); +int clif_mvp_item(struct map_session_data *sd,int nameid); +int clif_mvp_exp(struct map_session_data *sd,int exp); + +// vending +int clif_openvendingreq(struct map_session_data *sd,int num); +int clif_showvendingboard(struct block_list* bl,char *message,int fd); +int clif_closevendingboard(struct block_list* bl,int fd); +int clif_vendinglist(struct map_session_data *sd,int id,struct vending *vending); +int clif_buyvending(struct map_session_data *sd,int index,int amount,int fail); +int clif_openvending(struct map_session_data *sd,int id,struct vending *vending); +int clif_vendingreport(struct map_session_data *sd,int index,int amount); + +int clif_movetoattack(struct map_session_data *sd,struct block_list *bl); + +// party +int clif_party_created(struct map_session_data *sd,int flag); +int clif_party_info(struct party *p,int fd); +int clif_party_invite(struct map_session_data *sd,struct map_session_data *tsd); +int clif_party_inviteack(struct map_session_data *sd,char *nick,int flag); +int clif_party_option(struct party *p,struct map_session_data *sd,int flag); +int clif_party_leaved(struct party *p,struct map_session_data *sd,int account_id,char *name,int flag); +int clif_party_message(struct party *p,int account_id,char *mes,int len); +int clif_party_move(struct party *p,struct map_session_data *sd,int online); +int clif_party_xy(struct party *p,struct map_session_data *sd); +int clif_party_hp(struct party *p,struct map_session_data *sd); + +// guild +int clif_guild_created(struct map_session_data *sd,int flag); +int clif_guild_belonginfo(struct map_session_data *sd,struct guild *g); +int clif_guild_basicinfo(struct map_session_data *sd); +int clif_guild_allianceinfo(struct map_session_data *sd); +int clif_guild_memberlist(struct map_session_data *sd); +int clif_guild_skillinfo(struct map_session_data *sd); +int clif_guild_memberlogin_notice(struct guild *g,int idx,int flag); +int clif_guild_invite(struct map_session_data *sd,struct guild *g); +int clif_guild_inviteack(struct map_session_data *sd,int flag); +int clif_guild_leave(struct map_session_data *sd,const char *name,const char *mes); +int clif_guild_explusion(struct map_session_data *sd,const char *name,const char *mes, + int account_id); +int clif_guild_positionchanged(struct guild *g,int idx); +int clif_guild_memberpositionchanged(struct guild *g,int idx); +int clif_guild_emblem(struct map_session_data *sd,struct guild *g); +int clif_guild_notice(struct map_session_data *sd,struct guild *g); +int clif_guild_message(struct guild *g,int account_id,const char *mes,int len); +int clif_guild_skillup(struct map_session_data *sd,int skill_num,int lv); +int clif_guild_reqalliance(struct map_session_data *sd,int account_id,const char *name); +int clif_guild_allianceack(struct map_session_data *sd,int flag); +int clif_guild_delalliance(struct map_session_data *sd,int guild_id,int flag); +int clif_guild_oppositionack(struct map_session_data *sd,int flag); +int clif_guild_broken(struct map_session_data *sd,int flag); + + +// atcommand +int clif_displaymessage(const int fd,char* mes); +int clif_disp_onlyself(struct map_session_data *sd,char *mes,int len); +int clif_GMmessage(struct block_list *bl,char* mes,int len,int flag); +int clif_heal(int fd,int type,int val); +int clif_resurrection(struct block_list *bl,int type); +int clif_set0199(int fd,int type); +int clif_pvpset(struct map_session_data *sd, int pvprank, int pvpnum,int type); +int clif_send0199(int map,int type); +int clif_refine(int fd,struct map_session_data *sd,int fail,int index,int val); + +//petsystem +int clif_catch_process(struct map_session_data *sd); +int clif_pet_rulet(struct map_session_data *sd,int data); +int clif_sendegg(struct map_session_data *sd); +int clif_send_petdata(struct map_session_data *sd,int type,int param); +int clif_send_petstatus(struct map_session_data *sd); +int clif_pet_emotion(struct pet_data *pd,int param); +int clif_pet_performance(struct block_list *bl,int param); +int clif_pet_equip(struct pet_data *pd,int nameid); +int clif_pet_food(struct map_session_data *sd,int foodid,int fail); + +int clif_specialeffect(struct block_list *bl,int type, int flag); // special effects [Valaris] +int clif_message(struct block_list *bl, char* msg); // messages (from mobs/npcs) [Valaris] + +int clif_GM_kickack(struct map_session_data *sd,int id); +int clif_GM_kick(struct map_session_data *sd,struct map_session_data *tsd,int type); + +int clif_foreachclient(int (*)(struct map_session_data*,va_list),...); + +int do_final_clif(void); +int do_init_clif(void); + +#endif + + diff --git a/misc/src/map/guild.c b/misc/src/map/guild.c new file mode 100644 index 0000000..c32ebb4 --- /dev/null +++ b/misc/src/map/guild.c @@ -0,0 +1,1543 @@ +// $Id: guild.c,v 1.5 2004/09/25 05:32:18 MouseJstr Exp $ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "guild.h" +#include "storage.h" +#include "db.h" +#include "timer.h" +#include "socket.h" +#include "nullpo.h" +#include "malloc.h" +#include "battle.h" +#include "npc.h" +#include "pc.h" +#include "map.h" +#include "mob.h" +#include "intif.h" +#include "clif.h" + +#ifdef MEMWATCH +#include "memwatch.h" +#endif + +static struct dbt *guild_db; +static struct dbt *castle_db; +static struct dbt *guild_expcache_db; +static struct dbt *guild_infoevent_db; +static struct dbt *guild_castleinfoevent_db; + +struct eventlist { + char name[50]; + struct eventlist *next; +}; + +// ギルドのEXPキャッシュのフラッシュに関連する定数 +#define GUILD_PAYEXP_INVERVAL 10000 // 間隔(キャッシュの最大生存時間、ミリ秒) +#define GUILD_PAYEXP_LIST 8192 // キャッシュの最大数 + +// ギルドのEXPキャッシュ +struct guild_expcache { + int guild_id, account_id, char_id, exp; +}; + +// ギルドスキルdbのアクセサ(今は直打ちで代用) +int guild_skill_get_inf(int id){ return 0; } +int guild_skill_get_sp(int id,int lv){ return 0; } +int guild_skill_get_range(int id){ return 0; } +int guild_skill_get_max(int id){ return (id==10004)?10:1; } + +// ギルドスキルがあるか確認 +int guild_checkskill(struct guild *g,int id){ return g->skill[id-10000].lv; } + + +int guild_payexp_timer(int tid,unsigned int tick,int id,int data); +int guild_gvg_eliminate_timer(int tid,unsigned int tick,int id,int data); + + +static int guild_read_castledb(void) +{ + FILE *fp; + char line[1024]; + int j,ln=0; + char *str[32],*p; + struct guild_castle *gc; + + if( (fp=fopen("db/castle_db.txt","r"))==NULL){ + printf("can't read db/castle_db.txt\n"); + return -1; + } + + while(fgets(line,1020,fp)){ + if(line[0]=='/' && line[1]=='/') + continue; + memset(str,0,sizeof(str)); + gc=(struct guild_castle *)aCalloc(1,sizeof(struct guild_castle)); + for(j=0,p=line;j<6 && p;j++){ + str[j]=p; + p=strchr(p,','); + if(p) *p++=0; + } + + gc->guild_id=0; // <Agit> Clear Data for Initialize + gc->economy=0; gc->defense=0; gc->triggerE=0; gc->triggerD=0; gc->nextTime=0; gc->payTime=0; + gc->createTime=0; gc->visibleC=0; gc->visibleG0=0; gc->visibleG1=0; gc->visibleG2=0; + gc->visibleG3=0; gc->visibleG4=0; gc->visibleG5=0; gc->visibleG6=0; gc->visibleG7=0; + gc->Ghp0=0; gc->Ghp1=0; gc->Ghp2=0; gc->Ghp3=0; gc->Ghp4=0; gc->Ghp5=0; gc->Ghp6=0; gc->Ghp7=0; // guardian HP [Valaris] + + gc->castle_id=atoi(str[0]); + memcpy(gc->map_name,str[1],24); + memcpy(gc->castle_name,str[2],24); + memcpy(gc->castle_event,str[3],24); + + numdb_insert(castle_db,gc->castle_id,gc); + + //intif_guild_castle_info(gc->castle_id); + + ln++; + } + fclose(fp); + printf("read db/castle_db.txt done (count=%d)\n",ln); + return 0; +} + +// 初期化 +void do_init_guild(void) +{ + guild_db=numdb_init(); + castle_db=numdb_init(); + guild_expcache_db=numdb_init(); + guild_infoevent_db=numdb_init(); + guild_castleinfoevent_db=numdb_init(); + + guild_read_castledb(); + + add_timer_func_list(guild_gvg_eliminate_timer,"guild_gvg_eliminate_timer"); + add_timer_func_list(guild_payexp_timer,"guild_payexp_timer"); + add_timer_interval(gettick()+GUILD_PAYEXP_INVERVAL,guild_payexp_timer,0,0,GUILD_PAYEXP_INVERVAL); +} + + +// 検索 +struct guild *guild_search(int guild_id) +{ + return numdb_search(guild_db,guild_id); +} +int guild_searchname_sub(void *key,void *data,va_list ap) +{ + struct guild *g=(struct guild *)data,**dst; + char *str; + str=va_arg(ap,char *); + dst=va_arg(ap,struct guild **); + if(strcmpi(g->name,str)==0) + *dst=g; + return 0; +} +// ギルド名検索 +struct guild* guild_searchname(char *str) +{ + struct guild *g=NULL; + numdb_foreach(guild_db,guild_searchname_sub,str,&g); + return g; +} +struct guild_castle *guild_castle_search(int gcid) +{ + return numdb_search(castle_db,gcid); +} + +// mapnameに対応したアジトのgcを返す +struct guild_castle *guild_mapname2gc(char *mapname) +{ + int i; + struct guild_castle *gc=NULL; + for(i=0;i<MAX_GUILDCASTLE;i++){ + gc=guild_castle_search(i); + if(!gc) continue; + if(strcmp(gc->map_name,mapname)==0) return gc; + } + return NULL; +} + +// ログイン中のギルドメンバーの1人のsdを返す +struct map_session_data *guild_getavailablesd(struct guild *g) +{ + int i; + + nullpo_retr(NULL, g); + + for(i=0;i<g->max_member;i++) + if(g->member[i].sd!=NULL) + return g->member[i].sd; + return NULL; +} + +// ギルドメンバーのインデックスを返す +int guild_getindex(struct guild *g,int account_id,int char_id) +{ + int i; + if(g==NULL) + return -1; + for(i=0;i<g->max_member;i++) + if( g->member[i].account_id==account_id && + g->member[i].char_id==char_id ) + return i; + return -1; +} +// ギルドメンバーの役職を返す +int guild_getposition(struct map_session_data *sd,struct guild *g) +{ + int i; + + nullpo_retr(-1, sd); + + if(g==NULL && (g=guild_search(sd->status.guild_id))==NULL) + return -1; + for(i=0;i<g->max_member;i++) + if( g->member[i].account_id==sd->status.account_id && + g->member[i].char_id==sd->status.char_id ) + return g->member[i].position; + return -1; +} + +// メンバー情報の作成 +void guild_makemember(struct guild_member *m,struct map_session_data *sd) +{ + nullpo_retv(sd); + + memset(m,0,sizeof(struct guild_member)); + m->account_id =sd->status.account_id; + m->char_id =sd->status.char_id; + m->hair =sd->status.hair; + m->hair_color =sd->status.hair_color; + m->gender =sd->sex; + m->class =sd->status.class; + m->lv =sd->status.base_level; + m->exp =0; + m->exp_payper =0; + m->online =1; + m->position =MAX_GUILDPOSITION-1; + memcpy(m->name,sd->status.name,24); + return; +} +// ギルド競合確認 +int guild_check_conflict(struct map_session_data *sd) +{ + nullpo_retr(0, sd); + + intif_guild_checkconflict(sd->status.guild_id, + sd->status.account_id,sd->status.char_id); + return 0; +} + +// ギルドのEXPキャッシュをinter鯖にフラッシュする +int guild_payexp_timer_sub(void *key,void *data,va_list ap) +{ + int i, *dellist,*delp, dataid=(int)key; + struct guild_expcache *c; + struct guild *g; + + nullpo_retr(0, ap); + nullpo_retr(0, c=(struct guild_expcache *)data); + nullpo_retr(0, dellist=va_arg(ap,int *)); + nullpo_retr(0, delp=va_arg(ap,int *)); + + if( *delp>=GUILD_PAYEXP_LIST || (g=guild_search(c->guild_id))==NULL ) + return 0; + if( ( i=guild_getindex(g,c->account_id,c->char_id) )<0 ) + return 0; + + g->member[i].exp+=c->exp; + intif_guild_change_memberinfo(g->guild_id,c->account_id,c->char_id, + GMI_EXP,&g->member[i].exp,sizeof(g->member[i].exp)); + c->exp=0; + + dellist[(*delp)++]=dataid; + free(c); + return 0; +} +int guild_payexp_timer(int tid,unsigned int tick,int id,int data) +{ + int dellist[GUILD_PAYEXP_LIST],delp=0,i; + numdb_foreach(guild_expcache_db,guild_payexp_timer_sub, + dellist,&delp); + for(i=0;i<delp;i++) + numdb_erase(guild_expcache_db,dellist[i]); +// if(battle_config.etc_log) +// printf("guild exp %d charactor's exp flushed !\n",delp); + return 0; +} + +//------------------------------------------------------------------------ + +// 作成要求 +int guild_create(struct map_session_data *sd,char *name) +{ + nullpo_retr(0, sd); + + if(sd->status.guild_id==0){ + if(!battle_config.guild_emperium_check || pc_search_inventory(sd,714) >= 0) { + struct guild_member m; + guild_makemember(&m,sd); + m.position=0; + intif_guild_create(name,&m); + } else + clif_guild_created(sd,3); // エンペリウムがいない + }else + clif_guild_created(sd,1); // すでに所属している + + return 0; +} + +// 作成可否 +int guild_created(int account_id,int guild_id) +{ + struct map_session_data *sd=map_id2sd(account_id); + + if(sd==NULL) + return 0; + if(guild_id>0) { + struct guild *g; + sd->status.guild_id=guild_id; + sd->guild_sended=0; + if((g=numdb_search(guild_db,guild_id))!=NULL){ + printf("guild: id already exists!\n"); + exit(1); + } + clif_guild_created(sd,0); + if(battle_config.guild_emperium_check) + pc_delitem(sd,pc_search_inventory(sd,714),1,0); // エンペリウム消耗 + } else { + clif_guild_created(sd,2); // 作成失敗(同名ギルド存在) + } + return 0; +} + +// 情報要求 +int guild_request_info(int guild_id) +{ +// if(battle_config.etc_log) +// printf("guild_request_info\n"); + return intif_guild_request_info(guild_id); +} +// イベント付き情報要求 +int guild_npc_request_info(int guild_id,const char *event) +{ + struct eventlist *ev; + + if( guild_search(guild_id) ){ + if(event && *event) + npc_event_do(event); + return 0; + } + + if(event==NULL || *event==0) + return guild_request_info(guild_id); + + ev=(struct eventlist *)aCalloc(1,sizeof(struct eventlist)); + memcpy(ev->name,event,sizeof(ev->name)); + ev->next=(struct eventlist *)numdb_search(guild_infoevent_db,guild_id); + numdb_insert(guild_infoevent_db,guild_id,ev); + return guild_request_info(guild_id); +} + +// 所属キャラの確認 +int guild_check_member(const struct guild *g) +{ + int i; + struct map_session_data *sd; + + nullpo_retr(0, g); + + for(i=0;i<fd_max;i++){ + if(session[i] && (sd=session[i]->session_data) && sd->state.auth){ + if(sd->status.guild_id==g->guild_id){ + int j,f=1; + for(j=0;j<MAX_GUILD;j++){ // データがあるか + if( g->member[j].account_id==sd->status.account_id && + g->member[j].char_id==sd->status.char_id) + f=0; + } + if(f){ + sd->status.guild_id=0; + sd->guild_sended=0; + sd->guild_emblem_id=0; + if(battle_config.error_log) + printf("guild: check_member %d[%s] is not member\n",sd->status.account_id,sd->status.name); + } + } + } + } + return 0; +} +// 情報所得失敗(そのIDのキャラを全部未所属にする) +int guild_recv_noinfo(int guild_id) +{ + int i; + struct map_session_data *sd; + for(i=0;i<fd_max;i++){ + if(session[i] && (sd=session[i]->session_data) && sd->state.auth){ + if(sd->status.guild_id==guild_id) + sd->status.guild_id=0; + } + } + return 0; +} +// 情報所得 +int guild_recv_info(struct guild *sg) +{ + struct guild *g,before; + int i,bm,m; + struct eventlist *ev,*ev2; + + nullpo_retr(0, sg); + + if((g=numdb_search(guild_db,sg->guild_id))==NULL){ + g=(struct guild *)aCalloc(1,sizeof(struct guild)); + numdb_insert(guild_db,sg->guild_id,g); + before=*sg; + + // 最初のロードなのでユーザーのチェックを行う + guild_check_member(sg); + }else + before=*g; + memcpy(g,sg,sizeof(struct guild)); + + for(i=bm=m=0;i<g->max_member;i++){ // sdの設定と人数の確認 + if(g->member[i].account_id>0){ + struct map_session_data *sd = map_id2sd(g->member[i].account_id); + g->member[i].sd=(sd!=NULL && + sd->status.char_id==g->member[i].char_id && + sd->status.guild_id==g->guild_id)? sd:NULL; + m++; + }else + g->member[i].sd=NULL; + if(before.member[i].account_id>0) + bm++; + } + + for(i=0;i<g->max_member;i++){ // 情報の送信 + struct map_session_data *sd = g->member[i].sd; + if( sd==NULL ) + continue; + + if( before.guild_lv!=g->guild_lv || bm!=m || + before.max_member!=g->max_member ){ + clif_guild_basicinfo(sd); // 基本情報送信 + clif_guild_emblem(sd,g); // エンブレム送信 + } + + if(bm!=m){ // メンバー情報送信 + clif_guild_memberlist(g->member[i].sd); + } + + if( before.skill_point!=g->skill_point) + clif_guild_skillinfo(sd); // スキル情報送信 + + if( sd->guild_sended==0){ // 未送信なら所属情報も送る + clif_guild_belonginfo(sd,g); + clif_guild_notice(sd,g); + sd->guild_emblem_id=g->emblem_id; + sd->guild_sended=1; + } + } + + // イベントの発生 + if( (ev=numdb_search(guild_infoevent_db,sg->guild_id))!=NULL ){ + numdb_erase(guild_infoevent_db,sg->guild_id); + for(;ev;ev2=ev->next,free(ev),ev=ev2){ + npc_event_do(ev->name); + } + } + + return 0; +} + + +// ギルドへの勧誘 +int guild_invite(struct map_session_data *sd,int account_id) +{ + struct map_session_data *tsd; + struct guild *g; + int i; + + nullpo_retr(0, sd); + + tsd= map_id2sd(account_id); + g=guild_search(sd->status.guild_id); + + if(tsd==NULL || g==NULL) + return 0; + if(!battle_config.invite_request_check) { + if (tsd->party_invite>0 || tsd->trade_partner) { // 相手が取引中かどうか + clif_guild_inviteack(sd,0); + return 0; + } + } + if( tsd->status.guild_id>0 || tsd->guild_invite>0 ){ // 相手の所属確認 + clif_guild_inviteack(sd,0); + return 0; + } + + // 定員確認 + for(i=0;i<g->max_member;i++) + if(g->member[i].account_id==0) + break; + if(i==g->max_member){ + clif_guild_inviteack(sd,3); + return 0; + } + + tsd->guild_invite=sd->status.guild_id; + tsd->guild_invite_account=sd->status.account_id; + + clif_guild_invite(tsd,g); + return 0; +} +// ギルド勧誘への返答 +int guild_reply_invite(struct map_session_data *sd,int guild_id,int flag) +{ + struct map_session_data *tsd; + + nullpo_retr(0, sd); + nullpo_retr(0, tsd= map_id2sd( sd->guild_invite_account )); + + if(sd->guild_invite!=guild_id) // 勧誘とギルドIDが違う + return 0; + + if(flag==1){ // 承諾 + struct guild_member m; + struct guild *g; + int i; + + // 定員確認 + if( (g=guild_search(tsd->status.guild_id))==NULL ){ + sd->guild_invite=0; + sd->guild_invite_account=0; + return 0; + } + for(i=0;i<g->max_member;i++) + if(g->member[i].account_id==0) + break; + if(i==g->max_member){ + sd->guild_invite=0; + sd->guild_invite_account=0; + clif_guild_inviteack(tsd,3); + return 0; + } + + + //inter鯖へ追加要求 + guild_makemember(&m,sd); + intif_guild_addmember( sd->guild_invite, &m ); + return 0; + }else{ // 拒否 + sd->guild_invite=0; + sd->guild_invite_account=0; + if(tsd==NULL) + return 0; + clif_guild_inviteack(tsd,1); + } + return 0; +} +// ギルドメンバが追加された +int guild_member_added(int guild_id,int account_id,int char_id,int flag) +{ + struct map_session_data *sd= map_id2sd(account_id),*sd2; + struct guild *g; + + if( (g=guild_search(guild_id))==NULL ) + return 0; + + if((sd==NULL || sd->guild_invite==0) && flag==0){ + // キャラ側に登録できなかったため脱退要求を出す + if(battle_config.error_log) + printf("guild: member added error %d is not online\n",account_id); + intif_guild_leave(guild_id,account_id,char_id,0,"**登録失敗**"); + return 0; + } + sd->guild_invite=0; + sd->guild_invite_account=0; + + sd2=map_id2sd(sd->guild_invite_account); + + if(flag==1){ // 失敗 + if( sd2!=NULL ) + clif_guild_inviteack(sd2,3); + return 0; + } + + // 成功 + sd->guild_sended=0; + sd->status.guild_id=guild_id; + + if( sd2!=NULL ) + clif_guild_inviteack(sd2,2); + + // いちおう競合確認 + guild_check_conflict(sd); + + return 0; +} + +// ギルド脱退要求 +int guild_leave(struct map_session_data *sd,int guild_id, + int account_id,int char_id,const char *mes) +{ + struct guild *g; + int i; + + nullpo_retr(0, sd); + + g = guild_search(sd->status.guild_id); + + if(g==NULL) + return 0; + + if( sd->status.account_id!=account_id || + sd->status.char_id!=char_id || sd->status.guild_id!=guild_id) + return 0; + + for(i=0;i<g->max_member;i++){ // 所属しているか + if( g->member[i].account_id==sd->status.account_id && + g->member[i].char_id==sd->status.char_id ){ + intif_guild_leave(g->guild_id,sd->status.account_id,sd->status.char_id,0,mes); + return 0; + } + } + return 0; +} +// ギルド追放要求 +int guild_explusion(struct map_session_data *sd,int guild_id, + int account_id,int char_id,const char *mes) +{ + struct guild *g; + int i,ps; + + nullpo_retr(0, sd); + + g = guild_search(sd->status.guild_id); + + if(g==NULL) + return 0; + + if( sd->status.guild_id!=guild_id) + return 0; + + if( (ps=guild_getposition(sd,g))<0 || !(g->position[ps].mode&0x0010) ) + return 0; // 処罰権限無し + + for(i=0;i<g->max_member;i++){ // 所属しているか + if( g->member[i].account_id==account_id && + g->member[i].char_id==char_id ){ + intif_guild_leave(g->guild_id,account_id,char_id,1,mes); + return 0; + } + } + return 0; +} +// ギルドメンバが脱退した +int guild_member_leaved(int guild_id,int account_id,int char_id,int flag, + const char *name,const char *mes) +{ + struct map_session_data *sd=map_id2sd(account_id); + struct guild *g=guild_search(guild_id); + int i; + + if(g!=NULL){ + int i; + for(i=0;i<g->max_member;i++) + if( g->member[i].account_id==account_id && + g->member[i].char_id==char_id ){ + struct map_session_data *sd2=sd; + if(sd2==NULL) + sd2=guild_getavailablesd(g); + else + { + if(flag==0) + clif_guild_leave(sd2,name,mes); + else + clif_guild_explusion(sd2,name,mes,account_id); + } + g->member[i].account_id=0; + g->member[i].sd=NULL; + } + } + if(sd!=NULL && sd->status.guild_id==guild_id){ + sd->status.guild_id=0; + sd->guild_emblem_id=0; + sd->guild_sended=0; + } + + // メンバーリストを全員に再通知 + for(i=0;i<g->max_member;i++){ + if( g->member[i].sd!=NULL ) + clif_guild_memberlist(g->member[i].sd); + } + + return 0; +} +// ギルドメンバのオンライン状態/Lv更新送信 +int guild_send_memberinfoshort(struct map_session_data *sd,int online) +{ + struct guild *g; + + nullpo_retr(0, sd); + + if(sd->status.guild_id<=0) + return 0; + g=guild_search(sd->status.guild_id); + if(g==NULL) + return 0; + + intif_guild_memberinfoshort(g->guild_id, + sd->status.account_id,sd->status.char_id,online,sd->status.base_level,sd->status.class); + + if( !online ){ // ログアウトするならsdをクリアして終了 + int i=guild_getindex(g,sd->status.account_id,sd->status.char_id); + if(i>=0) + g->member[i].sd=NULL; + return 0; + } + + if( sd->guild_sended!=0 ) // ギルド初期送信データは送信済み + return 0; + + // 競合確認 + guild_check_conflict(sd); + + // あるならギルド初期送信データ送信 + if( (g=guild_search(sd->status.guild_id))!=NULL ){ + guild_check_member(g); // 所属を確認する + if(sd->status.guild_id==g->guild_id){ + clif_guild_belonginfo(sd,g); + clif_guild_notice(sd,g); + sd->guild_sended=1; + sd->guild_emblem_id=g->emblem_id; + } + } + return 0; +} +// ギルドメンバのオンライン状態/Lv更新通知 +int guild_recv_memberinfoshort(int guild_id,int account_id,int char_id,int online,int lv,int class) +{ + int i,alv,c,idx=0,om=0,oldonline=-1; + struct guild *g=guild_search(guild_id); + if(g==NULL) + return 0; + for(i=0,alv=0,c=0,om=0;i<g->max_member;i++){ + struct guild_member *m=&g->member[i]; + if(m->account_id==account_id && m->char_id==char_id ){ + oldonline=m->online; + m->online=online; + m->lv=lv; + m->class=class; + idx=i; + } + if(m->account_id>0){ + alv+=m->lv; + c++; + } + if(m->online) + om++; + } + if(idx==g->max_member){ + if(battle_config.error_log) + printf("guild: not found member %d,%d on %d[%s]\n", account_id,char_id,guild_id,g->name); + return 0; + } + g->average_lv=alv/c; + g->connect_member=om; + + if(oldonline!=online) // オンライン状態が変わったので通知 + clif_guild_memberlogin_notice(g,idx,online); + + for(i=0;i<g->max_member;i++){ // sd再設定 + struct map_session_data *sd= map_id2sd(g->member[i].account_id); + g->member[i].sd=(sd!=NULL && + sd->status.char_id==g->member[i].char_id && + sd->status.guild_id==guild_id)?sd:NULL; + } + + // ここにクライアントに送信処理が必要 + + return 0; +} +// ギルド会話送信 +int guild_send_message(struct map_session_data *sd,char *mes,int len) +{ + nullpo_retr(0, sd); + + if(sd->status.guild_id==0) + return 0; + intif_guild_message(sd->status.guild_id,sd->status.account_id,mes,len); + return 0; +} +// ギルド会話受信 +int guild_recv_message(int guild_id,int account_id,char *mes,int len) +{ + struct guild *g; + if( (g=guild_search(guild_id))==NULL) + return 0; + clif_guild_message(g,account_id,mes,len); + return 0; +} +// ギルドメンバの役職変更 +int guild_change_memberposition(int guild_id,int account_id,int char_id,int idx) +{ + return intif_guild_change_memberinfo( + guild_id,account_id,char_id,GMI_POSITION,&idx,sizeof(idx)); +} +// ギルドメンバの役職変更通知 +int guild_memberposition_changed(struct guild *g,int idx,int pos) +{ + nullpo_retr(0, g); + + g->member[idx].position=pos; + clif_guild_memberpositionchanged(g,idx); + return 0; +} +// ギルド役職変更 +int guild_change_position(struct map_session_data *sd,int idx, + int mode,int exp_mode,const char *name) +{ + struct guild_position p; + + nullpo_retr(0, sd); + + if(exp_mode>battle_config.guild_exp_limit) + exp_mode=battle_config.guild_exp_limit; + if(exp_mode<0)exp_mode=0; + p.mode=mode; + p.exp_mode=exp_mode; + memcpy(p.name,name,24); + return intif_guild_position(sd->status.guild_id,idx,&p); +} +// ギルド役職変更通知 +int guild_position_changed(int guild_id,int idx,struct guild_position *p) +{ + struct guild *g=guild_search(guild_id); + if(g==NULL) + return 0; + memcpy(&g->position[idx],p,sizeof(struct guild_position)); + clif_guild_positionchanged(g,idx); + return 0; +} +// ギルド告知変更 +int guild_change_notice(struct map_session_data *sd,int guild_id,const char *mes1,const char *mes2) +{ + nullpo_retr(0, sd); + + if(guild_id!=sd->status.guild_id) + return 0; + return intif_guild_notice(guild_id,mes1,mes2); +} +// ギルド告知変更通知 +int guild_notice_changed(int guild_id,const char *mes1,const char *mes2) +{ + int i; + struct map_session_data *sd; + struct guild *g=guild_search(guild_id); + if(g==NULL) + return 0; + + memcpy(g->mes1,mes1,60); + memcpy(g->mes2,mes2,120); + + for(i=0;i<g->max_member;i++){ + if((sd=g->member[i].sd)!=NULL) + clif_guild_notice(sd,g); + } + return 0; +} +// ギルドエンブレム変更 +int guild_change_emblem(struct map_session_data *sd,int len,const char *data) +{ + nullpo_retr(0, sd); + + return intif_guild_emblem(sd->status.guild_id,len,data); +} +// ギルドエンブレム変更通知 +int guild_emblem_changed(int len,int guild_id,int emblem_id,const char *data) +{ + int i; + struct map_session_data *sd; + struct guild *g=guild_search(guild_id); + if(g==NULL) + return 0; + + memcpy(g->emblem_data,data,len); + g->emblem_len=len; + g->emblem_id=emblem_id; + + for(i=0;i<g->max_member;i++){ + if((sd=g->member[i].sd)!=NULL){ + sd->guild_emblem_id=emblem_id; + clif_guild_belonginfo(sd,g); + clif_guild_emblem(sd,g); + } + } + return 0; +} + +// ギルドのEXP上納 +int guild_payexp(struct map_session_data *sd,int exp) +{ + struct guild *g; + struct guild_expcache *c; + int per,exp2; + + nullpo_retr(0, sd); + + if(sd->status.guild_id==0 || (g=guild_search(sd->status.guild_id))==NULL ) + return 0; + if( (per=g->position[guild_getposition(sd,g)].exp_mode)<=0 ) + return 0; + if( per>100 )per=100; + + if( (exp2=exp*per/100)<=0 ) + return 0; + + if( (c=numdb_search(guild_expcache_db,sd->status.char_id))==NULL ){ + c=(struct guild_expcache *)aCalloc(1,sizeof(struct guild_expcache)); + c->guild_id=sd->status.guild_id; + c->account_id=sd->status.account_id; + c->char_id=sd->status.char_id; + c->exp=exp2; + numdb_insert(guild_expcache_db,c->char_id,c); + }else{ + c->exp+=exp2; + } + return exp2; +} + +// スキルポイント割り振り +int guild_skillup(struct map_session_data *sd,int skill_num) +{ + struct guild *g; + int idx; + + nullpo_retr(0, sd); + + if(sd->status.guild_id==0 || (g=guild_search(sd->status.guild_id))==NULL) + return 0; + if(strcmp(sd->status.name,g->master)) + return 0; + + if( g->skill_point>0 && + g->skill[(idx=skill_num-10000)].id!=0 && + g->skill[idx].lv < guild_skill_get_max(skill_num) ){ + intif_guild_skillup(g->guild_id,skill_num,sd->status.account_id); + } + return 0; +} +// スキルポイント割り振り通知 +int guild_skillupack(int guild_id,int skill_num,int account_id) +{ + struct map_session_data *sd=map_id2sd(account_id); + struct guild *g=guild_search(guild_id); + int i; + if(g==NULL) + return 0; + if(sd!=NULL) + clif_guild_skillup(sd,skill_num,g->skill[skill_num-10000].lv); + // 全員に通知 + for(i=0;i<g->max_member;i++) + if((sd=g->member[i].sd)!=NULL) + clif_guild_skillinfo(sd); + return 0; +} + +// ギルド同盟数所得 +int guild_get_alliance_count(struct guild *g,int flag) +{ + int i,c; + + nullpo_retr(0, g); + + for(i=c=0;i<MAX_GUILDALLIANCE;i++){ + if( g->alliance[i].guild_id>0 && + g->alliance[i].opposition==flag ) + c++; + } + return c; +} +// ギルド同盟要求 +int guild_reqalliance(struct map_session_data *sd,int account_id) +{ + struct map_session_data *tsd= map_id2sd(account_id); + struct guild *g[2]; + int i; + + if(agit_flag) { // Disable alliance creation during woe [Valaris] + clif_displaymessage(sd->fd,"Alliances cannot be made during Guild Wars!"); + return 0; + } // end addition [Valaris] + + + nullpo_retr(0, sd); + + if(tsd==NULL || tsd->status.guild_id<=0) + return 0; + + g[0]=guild_search(sd->status.guild_id); + g[1]=guild_search(tsd->status.guild_id); + + if(g[0]==NULL || g[1]==NULL) + return 0; + + if( guild_get_alliance_count(g[0],0)>3 ) // 同盟数確認 + clif_guild_allianceack(sd,4); + if( guild_get_alliance_count(g[1],0)>3 ) + clif_guild_allianceack(sd,3); + + if( tsd->guild_alliance>0 ){ // 相手が同盟要請状態かどうか確認 + clif_guild_allianceack(sd,1); + return 0; + } + + for(i=0;i<MAX_GUILDALLIANCE;i++){ // すでに同盟状態か確認 + if( g[0]->alliance[i].guild_id==tsd->status.guild_id && + g[0]->alliance[i].opposition==0){ + clif_guild_allianceack(sd,0); + return 0; + } + } + + tsd->guild_alliance=sd->status.guild_id; + tsd->guild_alliance_account=sd->status.account_id; + + clif_guild_reqalliance(tsd,sd->status.account_id,g[0]->name); + return 0; +} +// ギルド勧誘への返答 +int guild_reply_reqalliance(struct map_session_data *sd,int account_id,int flag) +{ + struct map_session_data *tsd; + + nullpo_retr(0, sd); + nullpo_retr(0, tsd= map_id2sd( account_id )); + + if(sd->guild_alliance!=tsd->status.guild_id) // 勧誘とギルドIDが違う + return 0; + + if(flag==1){ // 承諾 + int i; + + struct guild *g; // 同盟数再確認 + if( (g=guild_search(sd->status.guild_id))==NULL || + guild_get_alliance_count(g,0)>3 ){ + clif_guild_allianceack(sd,4); + clif_guild_allianceack(tsd,3); + return 0; + } + if( (g=guild_search(tsd->status.guild_id))==NULL || + guild_get_alliance_count(g,0)>3 ){ + clif_guild_allianceack(sd,3); + clif_guild_allianceack(tsd,4); + return 0; + } + + // 敵対関係なら敵対を止める + if((g=guild_search(sd->status.guild_id)) == NULL) + return 0; + for(i=0;i<MAX_GUILDALLIANCE;i++){ + if( g->alliance[i].guild_id==tsd->status.guild_id && + g->alliance[i].opposition==1) + intif_guild_alliance( sd->status.guild_id,tsd->status.guild_id, + sd->status.account_id,tsd->status.account_id,9 ); + } + if((g=guild_search(tsd->status.guild_id)) == NULL) + return 0; + for(i=0;i<MAX_GUILDALLIANCE;i++){ + if( g->alliance[i].guild_id==sd->status.guild_id && + g->alliance[i].opposition==1) + intif_guild_alliance( tsd->status.guild_id,sd->status.guild_id, + tsd->status.account_id,sd->status.account_id,9 ); + } + + // inter鯖へ同盟要請 + intif_guild_alliance( sd->status.guild_id,tsd->status.guild_id, + sd->status.account_id,tsd->status.account_id,0 ); + return 0; + }else{ // 拒否 + sd->guild_alliance=0; + sd->guild_alliance_account=0; + if(tsd!=NULL) + clif_guild_allianceack(tsd,3); + } + return 0; +} +// ギルド関係解消 +int guild_delalliance(struct map_session_data *sd,int guild_id,int flag) +{ + if(agit_flag) { // Disable alliance breaking during woe [Valaris] + clif_displaymessage(sd->fd,"Alliances cannot be broken during Guild Wars!"); + return 0; + } // end addition [Valaris] + + nullpo_retr(0, sd); + + intif_guild_alliance( sd->status.guild_id,guild_id, + sd->status.account_id,0,flag|8 ); + return 0; +} +// ギルド敵対 +int guild_opposition(struct map_session_data *sd,int char_id) +{ + struct map_session_data *tsd=map_id2sd(char_id); + struct guild *g; + int i; + + nullpo_retr(0, sd); + + g=guild_search(sd->status.guild_id); + if(g==NULL || tsd==NULL) + return 0; + + if( guild_get_alliance_count(g,1)>3 ) // 敵対数確認 + clif_guild_oppositionack(sd,1); + + for(i=0;i<MAX_GUILDALLIANCE;i++){ // すでに関係を持っているか確認 + if(g->alliance[i].guild_id==tsd->status.guild_id){ + if(g->alliance[i].opposition==1){ // すでに敵対 + clif_guild_oppositionack(sd,2); + return 0; + }else // 同盟破棄 + intif_guild_alliance( sd->status.guild_id,tsd->status.guild_id, + sd->status.account_id,tsd->status.account_id,8 ); + } + } + + // inter鯖に敵対要請 + intif_guild_alliance( sd->status.guild_id,tsd->status.guild_id, + sd->status.account_id,tsd->status.account_id,1 ); + return 0; +} +// ギルド同盟/敵対通知 +int guild_allianceack(int guild_id1,int guild_id2,int account_id1,int account_id2, + int flag,const char *name1,const char *name2) +{ + struct guild *g[2]; + int guild_id[2]={guild_id1,guild_id2}; + const char *guild_name[2]={name1,name2}; + struct map_session_data *sd[2]={map_id2sd(account_id1),map_id2sd(account_id2)}; + int j,i; + + g[0]=guild_search(guild_id1); + g[1]=guild_search(guild_id2); + + if(sd[0]!=NULL && (flag&0x0f)==0){ + sd[0]->guild_alliance=0; + sd[0]->guild_alliance_account=0; + } + + if(flag&0x70){ // 失敗 + for(i=0;i<2-(flag&1);i++) + if( sd[i]!=NULL ) + clif_guild_allianceack(sd[i],((flag>>4)==i+1)?3:4); + return 0; + } +// if(battle_config.etc_log) +// printf("guild alliance_ack %d %d %d %d %d %s %s\n",guild_id1,guild_id2,account_id1,account_id2,flag,name1,name2); + + if(!(flag&0x08)){ // 関係追加 + for(i=0;i<2-(flag&1);i++) + if(g[i]!=NULL) + for(j=0;j<MAX_GUILDALLIANCE;j++) + if(g[i]->alliance[j].guild_id==0){ + g[i]->alliance[j].guild_id=guild_id[1-i]; + memcpy(g[i]->alliance[j].name,guild_name[1-i],24); + g[i]->alliance[j].opposition=flag&1; + break; + } + }else{ // 関係解消 + for(i=0;i<2-(flag&1);i++){ + if(g[i]!=NULL) + for(j=0;j<MAX_GUILDALLIANCE;j++) + if( g[i]->alliance[j].guild_id==guild_id[1-i] && + g[i]->alliance[j].opposition==(flag&1)){ + g[i]->alliance[j].guild_id=0; + break; + } + if( sd[i]!=NULL ) // 解消通知 + clif_guild_delalliance(sd[i],guild_id[1-i],(flag&1)); + } + } + + if((flag&0x0f)==0){ // 同盟通知 + if( sd[1]!=NULL ) + clif_guild_allianceack(sd[1],2); + }else if((flag&0x0f)==1){ // 敵対通知 + if( sd[0]!=NULL ) + clif_guild_oppositionack(sd[0],0); + } + + + for(i=0;i<2-(flag&1);i++){ // 同盟/敵対リストの再送信 + struct map_session_data *sd; + if(g[i]!=NULL) + for(j=0;j<g[i]->max_member;j++) + if((sd=g[i]->member[j].sd)!=NULL) + clif_guild_allianceinfo(sd); + } + return 0; +} +// ギルド解散通知用 +int guild_broken_sub(void *key,void *data,va_list ap) +{ + struct guild *g=(struct guild *)data; + int guild_id=va_arg(ap,int); + int i,j; + struct map_session_data *sd=NULL; + + nullpo_retr(0, g); + + for(i=0;i<MAX_GUILDALLIANCE;i++){ // 関係を破棄 + if(g->alliance[i].guild_id==guild_id){ + for(j=0;j<g->max_member;j++) + if( (sd=g->member[j].sd)!=NULL ) + clif_guild_delalliance(sd,guild_id,g->alliance[i].opposition); + g->alliance[i].guild_id=0; + } + } + return 0; +} +// ギルド解散通知 +int guild_broken(int guild_id,int flag) +{ + struct guild *g=guild_search(guild_id); + struct map_session_data *sd; + int i; + if(flag!=0 || g==NULL) + return 0; + + for(i=0;i<g->max_member;i++){ // ギルド解散を通知 + if((sd=g->member[i].sd)!=NULL){ + if(sd->state.storage_flag) + storage_guild_storage_quit(sd,1); + sd->status.guild_id=0; + sd->guild_sended=0; + clif_guild_broken(g->member[i].sd,0); + } + } + + numdb_foreach(guild_db,guild_broken_sub,guild_id); + numdb_erase(guild_db,guild_id); + guild_storage_delete(guild_id); + free(g); + return 0; +} + +// ギルド解散 +int guild_break(struct map_session_data *sd,char *name) +{ + struct guild *g; + int i; + + nullpo_retr(0, sd); + + if( (g=guild_search(sd->status.guild_id))==NULL ) + return 0; + if(strcmp(g->name,name)!=0) + return 0; + if(strcmp(sd->status.name,g->master)!=0) + return 0; + for(i=0;i<g->max_member;i++){ + if( g->member[i].account_id>0 && ( + g->member[i].account_id!=sd->status.account_id || + g->member[i].char_id!=sd->status.char_id )) + break; + } + if(i<g->max_member){ + clif_guild_broken(sd,2); + return 0; + } + + intif_guild_break(g->guild_id); + return 0; +} + +// ギルド城データ要求 +int guild_castledataload(int castle_id,int index) +{ + return intif_guild_castle_dataload(castle_id,index); +} +// ギルド城情報所得時イベント追加 +int guild_addcastleinfoevent(int castle_id,int index,const char *name) +{ + struct eventlist *ev; + int code=castle_id|(index<<16); + + if( name==NULL || *name==0 ) + return 0; + + ev=(struct eventlist *)aCalloc(1,sizeof(struct eventlist)); + memcpy(ev->name,name,sizeof(ev->name)); + ev->next=numdb_search(guild_castleinfoevent_db,code); + numdb_insert(guild_castleinfoevent_db,code,ev); + return 0; +} + +// ギルド城データ要求返信 +int guild_castledataloadack(int castle_id,int index,int value) +{ + struct guild_castle *gc=guild_castle_search(castle_id); + int code=castle_id|(index<<16); + struct eventlist *ev,*ev2; + + if(gc==NULL){ + return 0; + } + switch(index){ + case 1: gc->guild_id = value; break; + case 2: gc->economy = value; break; + case 3: gc->defense = value; break; + case 4: gc->triggerE = value; break; + case 5: gc->triggerD = value; break; + case 6: gc->nextTime = value; break; + case 7: gc->payTime = value; break; + case 8: gc->createTime = value; break; + case 9: gc->visibleC = value; break; + case 10: gc->visibleG0 = value; break; + case 11: gc->visibleG1 = value; break; + case 12: gc->visibleG2 = value; break; + case 13: gc->visibleG3 = value; break; + case 14: gc->visibleG4 = value; break; + case 15: gc->visibleG5 = value; break; + case 16: gc->visibleG6 = value; break; + case 17: gc->visibleG7 = value; break; + case 18: gc->Ghp0 = value; break; // guardian HP [Valaris] + case 19: gc->Ghp1 = value; break; + case 20: gc->Ghp2 = value; break; + case 21: gc->Ghp3 = value; break; + case 22: gc->Ghp4 = value; break; + case 23: gc->Ghp5 = value; break; + case 24: gc->Ghp6 = value; break; + case 25: gc->Ghp7 = value; break; // end additions [Valaris] + default: + printf("guild_castledataloadack ERROR!! (Not found index=%d)\n", index); + return 0; + } + if( (ev=numdb_search(guild_castleinfoevent_db,code))!=NULL ){ + numdb_erase(guild_castleinfoevent_db,code); + for(;ev;ev2=ev->next,free(ev),ev=ev2){ + npc_event_do(ev->name); + } + } + return 1; +} +// ギルド城データ変更要求 +int guild_castledatasave(int castle_id,int index,int value) +{ + return intif_guild_castle_datasave(castle_id,index,value); +} + +// ギルド城データ変更通知 +int guild_castledatasaveack(int castle_id,int index,int value) +{ + struct guild_castle *gc=guild_castle_search(castle_id); + if(gc==NULL){ + return 0; + } + switch(index){ + case 1: gc->guild_id = value; break; + case 2: gc->economy = value; break; + case 3: gc->defense = value; break; + case 4: gc->triggerE = value; break; + case 5: gc->triggerD = value; break; + case 6: gc->nextTime = value; break; + case 7: gc->payTime = value; break; + case 8: gc->createTime = value; break; + case 9: gc->visibleC = value; break; + case 10: gc->visibleG0 = value; break; + case 11: gc->visibleG1 = value; break; + case 12: gc->visibleG2 = value; break; + case 13: gc->visibleG3 = value; break; + case 14: gc->visibleG4 = value; break; + case 15: gc->visibleG5 = value; break; + case 16: gc->visibleG6 = value; break; + case 17: gc->visibleG7 = value; break; + case 18: gc->Ghp0 = value; break; // guardian HP [Valaris] + case 19: gc->Ghp1 = value; break; + case 20: gc->Ghp2 = value; break; + case 21: gc->Ghp3 = value; break; + case 22: gc->Ghp4 = value; break; + case 23: gc->Ghp5 = value; break; + case 24: gc->Ghp6 = value; break; + case 25: gc->Ghp7 = value; break; // end additions [Valaris] + default: + printf("guild_castledatasaveack ERROR!! (Not found index=%d)\n", index); + return 0; + } + return 1; +} + +// ギルドデータ一括受信(初期化時) +int guild_castlealldataload(int len,struct guild_castle *gc) +{ + int i; + int n = (len-4) / sizeof(struct guild_castle), ev = -1; + + nullpo_retr(0, gc); + + // イベント付きで要求するデータ位置を探す(最後の占拠データ) + for(i = 0; i < n; i++) { + if ((gc + i)->guild_id) + ev = i; + } + + // 城データ格納とギルド情報要求 + for(i = 0; i < n; i++, gc++) { + struct guild_castle *c = guild_castle_search(gc->castle_id); + if (!c) { + printf("guild_castlealldataload ??\n"); + continue; + } + memcpy(&c->guild_id,&gc->guild_id, + sizeof(struct guild_castle) - ((int)&c->guild_id - (int)c) ); + if( c->guild_id ){ + if(i!=ev) + guild_request_info(c->guild_id); + else + guild_npc_request_info(c->guild_id, "::OnAgitInit"); + } + } + if (ev == -1) + npc_event_doall("OnAgitInit"); + return 0; +} + +int guild_agit_start(void) +{ // Run All NPC_Event[OnAgitStart] + int c = npc_event_doall("OnAgitStart"); + printf("NPC_Event:[OnAgitStart] Run (%d) Events by @AgitStart.\n",c); + return 0; +} + +int guild_agit_end(void) +{ // Run All NPC_Event[OnAgitEnd] + int c = npc_event_doall("OnAgitEnd"); + printf("NPC_Event:[OnAgitEnd] Run (%d) Events by @AgitEnd.\n",c); + return 0; +} + +int guild_gvg_eliminate_timer(int tid,unsigned int tick,int id,int data) +{ // Run One NPC_Event[OnAgitEliminate] + size_t len = strlen((const char*)data); + char *evname=(char*)aCalloc(len + 4,sizeof(char)); + int c=0; + + if(!agit_flag) return 0; // Agit already End + memcpy(evname,(const char *)data,len - 5); + strcpy(evname + len - 5,"Eliminate"); + c = npc_event_do(evname); + printf("NPC_Event:[%s] Run (%d) Events.\n",evname,c); + return 0; +} + +int guild_agit_break(struct mob_data *md) +{ // Run One NPC_Event[OnAgitBreak] + char *evname; + + nullpo_retr(0, md); + + evname=(char *)aCalloc(strlen(md->npc_event) + 1, sizeof(char)); + + strcpy(evname,md->npc_event); +// Now By User to Run [OnAgitBreak] NPC Event... +// It's a little impossible to null point with player disconnect in this! +// But Script will be stop, so nothing... +// Maybe will be changed in the futher.. +// int c = npc_event_do(evname); + if(!agit_flag) return 0; // Agit already End + add_timer(gettick()+battle_config.gvg_eliminate_time,guild_gvg_eliminate_timer,md->bl.m,(int)evname); + return 0; +} + +// [MouseJstr] +// How many castles does this guild have? +int guild_checkcastles(struct guild *g) { + int i,nb_cas=0, id,cas_id=0; + struct guild_castle *gc; + id=g->guild_id; + for(i=0;i<MAX_GUILDCASTLE;i++){ + gc=guild_castle_search(i); + cas_id=gc->guild_id; + if(g->guild_id==cas_id) + nb_cas=nb_cas+1; + } //end for + return nb_cas; +} + +// [MouseJstr] +// is this guild allied with this castle? +int guild_isallied(struct guild *g, struct guild_castle *gc) +{ + int i; + + nullpo_retr(0, g); + + if(g->guild_id == gc->guild_id) + return 1; + + if (gc->guild_id == 0) + return 0; + + + for(i=0;i<MAX_GUILDALLIANCE;i++) + if(g->alliance[i].guild_id == gc->guild_id) { + if(g->alliance[i].opposition == 0) + return 1; + else + return 0; + } + + return 0; +} + +static int guild_db_final(void *key,void *data,va_list ap) +{ + struct guild *g=data; + + free(g); + + return 0; +} +static int castle_db_final(void *key,void *data,va_list ap) +{ + struct guild_castle *gc=data; + + free(gc); + + return 0; +} +static int guild_expcache_db_final(void *key,void *data,va_list ap) +{ + struct guild_expcache *c=data; + + free(c); + + return 0; +} +static int guild_infoevent_db_final(void *key,void *data,va_list ap) +{ + struct eventlist *ev=data; + + free(ev); + + return 0; +} +void do_final_guild(void) +{ + if(guild_db) + numdb_final(guild_db,guild_db_final); + if(castle_db) + numdb_final(castle_db,castle_db_final); + if(guild_expcache_db) + numdb_final(guild_expcache_db,guild_expcache_db_final); + if(guild_infoevent_db) + numdb_final(guild_infoevent_db,guild_infoevent_db_final); + if(guild_castleinfoevent_db) + numdb_final(guild_castleinfoevent_db,guild_infoevent_db_final); +} diff --git a/misc/src/map/guild.h b/misc/src/map/guild.h new file mode 100644 index 0000000..53e91f0 --- /dev/null +++ b/misc/src/map/guild.h @@ -0,0 +1,87 @@ +// $Id: guild.h,v 1.4 2004/09/25 05:32:18 MouseJstr Exp $ +#ifndef _GUILD_H_ +#define _GUILD_H_ + +struct map_session_data; +struct mob_data; +struct guild; +struct guild_member; +struct guild_position; +struct guild_castle; + +int guild_skill_get_inf(int id); +int guild_skill_get_sp(int id,int lv); +int guild_skill_get_range(int id); +int guild_skill_get_max(int id); + +int guild_checkskill(struct guild *g,int id); +int guild_checkcastles(struct guild *g); // [MouseJstr] +int guild_isallied(struct guild *g, struct guild_castle *gc); + +void do_init_guild(void); +struct guild *guild_search(int guild_id); +struct guild *guild_searchname(char *str); +struct guild_castle *guild_castle_search(int gcid); + +struct guild_castle *guild_mapname2gc(char *mapname); + +struct map_session_data *guild_getavailablesd(struct guild *g); +int guild_getindex(struct guild *g,int account_id,int char_id); +int guild_getposition(struct map_session_data *sd,struct guild *g); +int guild_payexp(struct map_session_data *sd,int exp); + +int guild_create(struct map_session_data *sd,char *name); +int guild_created(int account_id,int guild_id); +int guild_request_info(int guild_id); +int guild_recv_noinfo(int guild_id); +int guild_recv_info(struct guild *sg); +int guild_npc_request_info(int guild_id,const char *ev); +int guild_invite(struct map_session_data *sd,int account_id); +int guild_reply_invite(struct map_session_data *sd,int guild_id,int flag); +int guild_member_added(int guild_id,int account_id,int char_id,int flag); +int guild_leave(struct map_session_data *sd,int guild_id, + int account_id,int char_id,const char *mes); +int guild_member_leaved(int guild_id,int account_id,int char_id,int flag, + const char *name,const char *mes); +int guild_explusion(struct map_session_data *sd,int guild_id, + int account_id,int char_id,const char *mes); +int guild_skillup(struct map_session_data *sd,int skill_num); +int guild_reqalliance(struct map_session_data *sd,int account_id); +int guild_reply_reqalliance(struct map_session_data *sd,int account_id,int flag); +int guild_alliance(int guild_id1,int guild_id2,int account_id1,int account_id2); +int guild_allianceack(int guild_id1,int guild_id2,int account_id1,int account_id2, + int flag,const char *name1,const char *name2); +int guild_delalliance(struct map_session_data *sd,int guild_id,int flag); +int guild_opposition(struct map_session_data *sd,int char_id); + +int guild_send_memberinfoshort(struct map_session_data *sd,int online); +int guild_recv_memberinfoshort(int guild_id,int account_id,int char_id,int online,int lv,int class); +int guild_change_memberposition(int guild_id,int account_id,int char_id,int idx); +int guild_memberposition_changed(struct guild *g,int idx,int pos); +int guild_change_position(struct map_session_data *sd,int idx, + int mode,int exp_mode,const char *name); +int guild_position_changed(int guild_id,int idx,struct guild_position *p); +int guild_change_notice(struct map_session_data *sd,int guild_id,const char *mes1,const char *mes2); +int guild_notice_changed(int guild_id,const char *mes1,const char *mes2); +int guild_change_emblem(struct map_session_data *sd,int len,const char *data); +int guild_emblem_changed(int len,int guild_id,int emblem_id,const char *data); +int guild_send_message(struct map_session_data *sd,char *mes,int len); +int guild_recv_message(int guild_id,int account_id,char *mes,int len); +int guild_skillupack(int guild_id,int skill_num,int account_id); +int guild_break(struct map_session_data *sd,char *name); +int guild_broken(int guild_id,int flag); + +int guild_addcastleinfoevent(int castle_id,int index,const char *name); +int guild_castledataload(int castle_id,int index); +int guild_castledataloadack(int castle_id,int index,int value); +int guild_castledatasave(int castle_id,int index,int value); +int guild_castledatasaveack(int castle_id,int index,int value); +int guild_castlealldataload(int len,struct guild_castle *gc); + +int guild_agit_start(void); +int guild_agit_end(void); +int guild_agit_break(struct mob_data *md); + +void do_final_guild(void); + +#endif diff --git a/misc/src/map/intif.c b/misc/src/map/intif.c new file mode 100644 index 0000000..ace0187 --- /dev/null +++ b/misc/src/map/intif.c @@ -0,0 +1,1042 @@ +// $Id: intif.c,v 1.2 2004/09/25 05:32:18 MouseJstr Exp $ +#include <sys/types.h> +#ifdef LCCWIN32 +#include <winsock.h> +#else +#include <sys/socket.h> +#include <netinet/in.h> +#endif +#include <stdio.h> +#include <stdlib.h> +#ifndef LCCWIN32 +#include <sys/time.h> +#include <sys/ioctl.h> +#include <unistd.h> +#include <arpa/inet.h> +#endif +#include <signal.h> +#include <fcntl.h> +#include <string.h> + +#include "socket.h" +#include "timer.h" +#include "map.h" +#include "battle.h" +#include "chrif.h" +#include "clif.h" +#include "pc.h" +#include "intif.h" +#include "storage.h" +#include "party.h" +#include "guild.h" +#include "pet.h" +#include "nullpo.h" + +#ifdef MEMWATCH +#include "memwatch.h" +#endif + +static const int packet_len_table[]={ + -1,-1,27,-1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + -1, 7, 0, 0, 0, 0, 0, 0, -1,11, 0, 0, 0, 0, 0, 0, + 35,-1,11,15, 34,29, 7,-1, 0, 0, 0, 0, 0, 0, 0, 0, + 10,-1,15, 0, 79,19, 7,-1, 0,-1,-1,-1, 14,67,186,-1, + 9, 9,-1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 11,-1, 7, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +}; + +extern int char_fd; // inter serverのfdはchar_fdを使う +#define inter_fd (char_fd) // エイリアス + +//----------------------------------------------------------------- +// inter serverへの送信 + +// pet +int intif_create_pet(int account_id,int char_id,short pet_class,short pet_lv,short pet_egg_id, + short pet_equip,short intimate,short hungry,char rename_flag,char incuvate,char *pet_name) +{ + WFIFOW(inter_fd,0) = 0x3080; + WFIFOL(inter_fd,2) = account_id; + WFIFOL(inter_fd,6) = char_id; + WFIFOW(inter_fd,10) = pet_class; + WFIFOW(inter_fd,12) = pet_lv; + WFIFOW(inter_fd,14) = pet_egg_id; + WFIFOW(inter_fd,16) = pet_equip; + WFIFOW(inter_fd,18) = intimate; + WFIFOW(inter_fd,20) = hungry; + WFIFOB(inter_fd,22) = rename_flag; + WFIFOB(inter_fd,23) = incuvate; + memcpy(WFIFOP(inter_fd,24),pet_name,24); + WFIFOSET(inter_fd,48); + + return 0; +} + +int intif_request_petdata(int account_id,int char_id,int pet_id) +{ + WFIFOW(inter_fd,0) = 0x3081; + WFIFOL(inter_fd,2) = account_id; + WFIFOL(inter_fd,6) = char_id; + WFIFOL(inter_fd,10) = pet_id; + WFIFOSET(inter_fd,14); + + return 0; +} + +int intif_save_petdata(int account_id,struct s_pet *p) +{ + WFIFOW(inter_fd,0) = 0x3082; + WFIFOW(inter_fd,2) = sizeof(struct s_pet) + 8; + WFIFOL(inter_fd,4) = account_id; + memcpy(WFIFOP(inter_fd,8),p,sizeof(struct s_pet)); + WFIFOSET(inter_fd,WFIFOW(inter_fd,2)); + + return 0; +} + +int intif_delete_petdata(int pet_id) +{ + WFIFOW(inter_fd,0) = 0x3083; + WFIFOL(inter_fd,2) = pet_id; + WFIFOSET(inter_fd,6); + + return 0; +} + +// GMメッセージを送信 +int intif_GMmessage(char* mes,int len,int flag) +{ + int lp = (flag&0x10) ? 8 : 4; + WFIFOW(inter_fd,0) = 0x3000; + WFIFOW(inter_fd,2) = lp + len; + WFIFOL(inter_fd,4) = 0x65756c62; + memcpy(WFIFOP(inter_fd,lp), mes, len); + WFIFOSET(inter_fd, WFIFOW(inter_fd,2)); + + return 0; +} + +// The transmission of Wisp/Page to inter-server (player not found on this server) +int intif_wis_message(struct map_session_data *sd, char *nick, char *mes, int mes_len) { + nullpo_retr(0, sd); + + WFIFOW(inter_fd,0) = 0x3001; + WFIFOW(inter_fd,2) = mes_len + 52; + memcpy(WFIFOP(inter_fd,4), sd->status.name, 24); + memcpy(WFIFOP(inter_fd,28), nick, 24); + memcpy(WFIFOP(inter_fd,52), mes, mes_len); + WFIFOSET(inter_fd, WFIFOW(inter_fd,2)); + + if (battle_config.etc_log) + printf("intif_wis_message from %s to %s (message: '%s')\n", sd->status.name, nick, mes); + + return 0; +} + +// The reply of Wisp/page +int intif_wis_replay(int id, int flag) { + WFIFOW(inter_fd,0) = 0x3002; + WFIFOL(inter_fd,2) = id; + WFIFOB(inter_fd,6) = flag; // flag: 0: success to send wisper, 1: target character is not loged in?, 2: ignored by target + WFIFOSET(inter_fd,7); + + if (battle_config.etc_log) + printf("intif_wis_replay: id: %d, flag:%d\n", id, flag); + + return 0; +} + +// The transmission of GM only Wisp/Page from server to inter-server +int intif_wis_message_to_gm(char *Wisp_name, int min_gm_level, char *mes, int mes_len) { + WFIFOW(inter_fd,0) = 0x3003; + WFIFOW(inter_fd,2) = mes_len + 30; + memcpy(WFIFOP(inter_fd,4), Wisp_name, 24); + WFIFOW(inter_fd,28) = (short)min_gm_level; + memcpy(WFIFOP(inter_fd,30), mes, mes_len); + WFIFOSET(inter_fd, WFIFOW(inter_fd,2)); + + if (battle_config.etc_log) + printf("intif_wis_message_to_gm: from: '%s', min level: %d, message: '%s'.\n", Wisp_name, min_gm_level, mes); + + return 0; +} + +// アカウント変数送信 +int intif_saveaccountreg(struct map_session_data *sd) { + int j,p; + + nullpo_retr(0, sd); + + WFIFOW(inter_fd,0) = 0x3004; + WFIFOL(inter_fd,4) = sd->bl.id; + for(j=0,p=8;j<sd->status.account_reg_num;j++,p+=36){ + memcpy(WFIFOP(inter_fd,p),sd->status.account_reg[j].str,32); + WFIFOL(inter_fd,p+32)=sd->status.account_reg[j].value; + } + WFIFOW(inter_fd,2)=p; + WFIFOSET(inter_fd,p); + return 0; +} +// アカウント変数要求 +int intif_request_accountreg(struct map_session_data *sd) +{ + nullpo_retr(0, sd); + + WFIFOW(inter_fd,0) = 0x3005; + WFIFOL(inter_fd,2) = sd->bl.id; + WFIFOSET(inter_fd,6); + return 0; +} + +// 倉庫データ要求 +int intif_request_storage(int account_id) +{ + WFIFOW(inter_fd,0) = 0x3010; + WFIFOL(inter_fd,2) = account_id; + WFIFOSET(inter_fd,6); + return 0; +} +// 倉庫データ送信 +int intif_send_storage(struct storage *stor) +{ + nullpo_retr(0, stor); + WFIFOW(inter_fd,0) = 0x3011; + WFIFOW(inter_fd,2) = sizeof(struct storage)+8; + WFIFOL(inter_fd,4) = stor->account_id; + memcpy( WFIFOP(inter_fd,8),stor, sizeof(struct storage) ); + WFIFOSET(inter_fd,WFIFOW(inter_fd,2)); + return 0; +} + +int intif_request_guild_storage(int account_id,int guild_id) +{ + WFIFOW(inter_fd,0) = 0x3018; + WFIFOL(inter_fd,2) = account_id; + WFIFOL(inter_fd,6) = guild_id; + WFIFOSET(inter_fd,10); + return 0; +} +int intif_send_guild_storage(int account_id,struct guild_storage *gstor) +{ + WFIFOW(inter_fd,0) = 0x3019; + WFIFOW(inter_fd,2) = sizeof(struct guild_storage)+12; + WFIFOL(inter_fd,4) = account_id; + WFIFOL(inter_fd,8) = gstor->guild_id; + memcpy( WFIFOP(inter_fd,12),gstor, sizeof(struct guild_storage) ); + WFIFOSET(inter_fd,WFIFOW(inter_fd,2)); + return 0; +} + +// パーティ作成要求 +int intif_create_party(struct map_session_data *sd,char *name) +{ + nullpo_retr(0, sd); + + WFIFOW(inter_fd,0) = 0x3020; + WFIFOL(inter_fd,2) = sd->status.account_id; + memcpy(WFIFOP(inter_fd, 6),name,24); + memcpy(WFIFOP(inter_fd,30),sd->status.name,24); + memcpy(WFIFOP(inter_fd,54),map[sd->bl.m].name,16); + WFIFOW(inter_fd,70)= sd->status.base_level; + WFIFOSET(inter_fd,72); +// if(battle_config.etc_log) +// printf("intif: create party\n"); + return 0; +} +// パーティ情報要求 +int intif_request_partyinfo(int party_id) +{ + WFIFOW(inter_fd,0) = 0x3021; + WFIFOL(inter_fd,2) = party_id; + WFIFOSET(inter_fd,6); +// if(battle_config.etc_log) +// printf("intif: request party info\n"); + return 0; +} +// パーティ追加要求 +int intif_party_addmember(int party_id,int account_id) +{ + struct map_session_data *sd; + sd=map_id2sd(account_id); +// if(battle_config.etc_log) +// printf("intif: party add member %d %d\n",party_id,account_id); + if(sd!=NULL){ + WFIFOW(inter_fd,0)=0x3022; + WFIFOL(inter_fd,2)=party_id; + WFIFOL(inter_fd,6)=account_id; + memcpy(WFIFOP(inter_fd,10),sd->status.name,24); + memcpy(WFIFOP(inter_fd,34),map[sd->bl.m].name,16); + WFIFOW(inter_fd,50)=sd->status.base_level; + WFIFOSET(inter_fd,52); + } + return 0; +} +// パーティ設定変更 +int intif_party_changeoption(int party_id,int account_id,int exp,int item) +{ + WFIFOW(inter_fd,0)=0x3023; + WFIFOL(inter_fd,2)=party_id; + WFIFOL(inter_fd,6)=account_id; + WFIFOW(inter_fd,10)=exp; + WFIFOW(inter_fd,12)=item; + WFIFOSET(inter_fd,14); + return 0; +} +// パーティ脱退要求 +int intif_party_leave(int party_id,int account_id) +{ +// if(battle_config.etc_log) +// printf("intif: party leave %d %d\n",party_id,account_id); + WFIFOW(inter_fd,0)=0x3024; + WFIFOL(inter_fd,2)=party_id; + WFIFOL(inter_fd,6)=account_id; + WFIFOSET(inter_fd,10); + return 0; +} +// パーティ移動要求 +int intif_party_changemap(struct map_session_data *sd,int online) +{ + if(sd!=NULL){ + WFIFOW(inter_fd,0)=0x3025; + WFIFOL(inter_fd,2)=sd->status.party_id; + WFIFOL(inter_fd,6)=sd->status.account_id; + memcpy(WFIFOP(inter_fd,10),map[sd->bl.m].name,16); + WFIFOB(inter_fd,26)=online; + WFIFOW(inter_fd,27)=sd->status.base_level; + WFIFOSET(inter_fd,29); + } +// if(battle_config.etc_log) +// printf("party: change map\n"); + return 0; +} +// パーティー解散要求 +int intif_break_party(int party_id) +{ + WFIFOW(inter_fd,0)=0x3026; + WFIFOL(inter_fd,2)=party_id; + WFIFOSET(inter_fd,6); + return 0; +} +// パーティ会話送信 +int intif_party_message(int party_id,int account_id,char *mes,int len) +{ +// if(battle_config.etc_log) +// printf("intif_party_message: %s\n",mes); + WFIFOW(inter_fd,0)=0x3027; + WFIFOW(inter_fd,2)=len+12; + WFIFOL(inter_fd,4)=party_id; + WFIFOL(inter_fd,8)=account_id; + memcpy(WFIFOP(inter_fd,12),mes,len); + WFIFOSET(inter_fd,len+12); + return 0; +} +// パーティ競合チェック要求 +int intif_party_checkconflict(int party_id,int account_id,char *nick) +{ + WFIFOW(inter_fd,0)=0x3028; + WFIFOL(inter_fd,2)=party_id; + WFIFOL(inter_fd,6)=account_id; + memcpy(WFIFOP(inter_fd,10),nick,24); + WFIFOSET(inter_fd,34); + return 0; +} + +// ギルド作成要求 +int intif_guild_create(const char *name,const struct guild_member *master) +{ + nullpo_retr(0, master); + + WFIFOW(inter_fd,0)=0x3030; + WFIFOW(inter_fd,2)=sizeof(struct guild_member)+32; + WFIFOL(inter_fd,4)=master->account_id; + memcpy(WFIFOP(inter_fd,8),name,24); + memcpy(WFIFOP(inter_fd,32),master,sizeof(struct guild_member)); + WFIFOSET(inter_fd,WFIFOW(inter_fd,2)); + return 0; +} +// ギルド情報要求 +int intif_guild_request_info(int guild_id) +{ + WFIFOW(inter_fd,0) = 0x3031; + WFIFOL(inter_fd,2) = guild_id; + WFIFOSET(inter_fd,6); + return 0; +} +// ギルドメンバ追加要求 +int intif_guild_addmember(int guild_id,struct guild_member *m) +{ + WFIFOW(inter_fd,0) = 0x3032; + WFIFOW(inter_fd,2) = sizeof(struct guild_member)+8; + WFIFOL(inter_fd,4) = guild_id; + memcpy(WFIFOP(inter_fd,8),m,sizeof(struct guild_member)); + WFIFOSET(inter_fd,WFIFOW(inter_fd,2)); + return 0; +} +// ギルドメンバ脱退/追放要求 +int intif_guild_leave(int guild_id,int account_id,int char_id,int flag,const char *mes) +{ + WFIFOW(inter_fd, 0) = 0x3034; + WFIFOL(inter_fd, 2) = guild_id; + WFIFOL(inter_fd, 6) = account_id; + WFIFOL(inter_fd,10) = char_id; + WFIFOB(inter_fd,14) = flag; + memcpy(WFIFOP(inter_fd,15),mes,40); + WFIFOSET(inter_fd,55); + return 0; +} +// ギルドメンバのオンライン状況/Lv更新要求 +int intif_guild_memberinfoshort(int guild_id, + int account_id,int char_id,int online,int lv,int class) +{ + WFIFOW(inter_fd, 0) = 0x3035; + WFIFOL(inter_fd, 2) = guild_id; + WFIFOL(inter_fd, 6) = account_id; + WFIFOL(inter_fd,10) = char_id; + WFIFOB(inter_fd,14) = online; + WFIFOW(inter_fd,15) = lv; + WFIFOW(inter_fd,17) = class; + WFIFOSET(inter_fd,19); + return 0; +} +// ギルド解散通知 +int intif_guild_break(int guild_id) +{ + WFIFOW(inter_fd, 0) = 0x3036; + WFIFOL(inter_fd, 2) = guild_id; + WFIFOSET(inter_fd,6); + return 0; +} +// ギルド会話送信 +int intif_guild_message(int guild_id,int account_id,char *mes,int len) +{ + WFIFOW(inter_fd,0)=0x3037; + WFIFOW(inter_fd,2)=len+12; + WFIFOL(inter_fd,4)=guild_id; + WFIFOL(inter_fd,8)=account_id; + memcpy(WFIFOP(inter_fd,12),mes,len); + WFIFOSET(inter_fd,len+12); + return 0; +} +// ギルド競合チェック要求 +int intif_guild_checkconflict(int guild_id,int account_id,int char_id) +{ + WFIFOW(inter_fd, 0)=0x3038; + WFIFOL(inter_fd, 2)=guild_id; + WFIFOL(inter_fd, 6)=account_id; + WFIFOL(inter_fd,10)=char_id; + WFIFOSET(inter_fd,14); + return 0; +} +// ギルド基本情報変更要求 +int intif_guild_change_basicinfo(int guild_id,int type,const void *data,int len) +{ + WFIFOW(inter_fd,0)=0x3039; + WFIFOW(inter_fd,2)=len+10; + WFIFOL(inter_fd,4)=guild_id; + WFIFOW(inter_fd,8)=type; + memcpy(WFIFOP(inter_fd,10),data,len); + WFIFOSET(inter_fd,len+10); + return 0; +} +// ギルドメンバ情報変更要求 +int intif_guild_change_memberinfo(int guild_id,int account_id,int char_id, + int type,const void *data,int len) +{ + WFIFOW(inter_fd, 0)=0x303a; + WFIFOW(inter_fd, 2)=len+18; + WFIFOL(inter_fd, 4)=guild_id; + WFIFOL(inter_fd, 8)=account_id; + WFIFOL(inter_fd,12)=char_id; + WFIFOW(inter_fd,16)=type; + memcpy(WFIFOP(inter_fd,18),data,len); + WFIFOSET(inter_fd,len+18); + return 0; +} +// ギルド役職変更要求 +int intif_guild_position(int guild_id,int idx,struct guild_position *p) +{ + WFIFOW(inter_fd,0)=0x303b; + WFIFOW(inter_fd,2)=sizeof(struct guild_position)+12; + WFIFOL(inter_fd,4)=guild_id; + WFIFOL(inter_fd,8)=idx; + memcpy(WFIFOP(inter_fd,12),p,sizeof(struct guild_position)); + WFIFOSET(inter_fd,WFIFOW(inter_fd,2)); + return 0; +} +// ギルドスキルアップ要求 +int intif_guild_skillup(int guild_id,int skill_num,int account_id) +{ + WFIFOW(inter_fd, 0)=0x303c; + WFIFOL(inter_fd, 2)=guild_id; + WFIFOL(inter_fd, 6)=skill_num; + WFIFOL(inter_fd,10)=account_id; + WFIFOSET(inter_fd,14); + return 0; +} +// ギルド同盟/敵対要求 +int intif_guild_alliance(int guild_id1,int guild_id2,int account_id1,int account_id2,int flag) +{ + WFIFOW(inter_fd, 0)=0x303d; + WFIFOL(inter_fd, 2)=guild_id1; + WFIFOL(inter_fd, 6)=guild_id2; + WFIFOL(inter_fd,10)=account_id1; + WFIFOL(inter_fd,14)=account_id2; + WFIFOB(inter_fd,18)=flag; + WFIFOSET(inter_fd,19); + return 0; +} +// ギルド告知変更要求 +int intif_guild_notice(int guild_id,const char *mes1,const char *mes2) +{ + WFIFOW(inter_fd,0)=0x303e; + WFIFOL(inter_fd,2)=guild_id; + memcpy(WFIFOP(inter_fd,6),mes1,60); + memcpy(WFIFOP(inter_fd,66),mes2,120); + WFIFOSET(inter_fd,186); + return 0; +} +// ギルドエンブレム変更要求 +int intif_guild_emblem(int guild_id,int len,const char *data) +{ + if(guild_id<=0 || len<0 || len>2000) + return 0; + WFIFOW(inter_fd,0)=0x303f; + WFIFOW(inter_fd,2)=len+12; + WFIFOL(inter_fd,4)=guild_id; + WFIFOL(inter_fd,8)=0; + memcpy(WFIFOP(inter_fd,12),data,len); + WFIFOSET(inter_fd,len+12); + return 0; +} +//現在のギルド城占領ギルドを調べる +int intif_guild_castle_dataload(int castle_id,int index) +{ + WFIFOW(inter_fd,0)=0x3040; + WFIFOW(inter_fd,2)=castle_id; + WFIFOB(inter_fd,4)=index; + WFIFOSET(inter_fd,5); + return 0; +} + +//ギルド城占領ギルド変更要求 +int intif_guild_castle_datasave(int castle_id,int index, int value) +{ + WFIFOW(inter_fd,0)=0x3041; + WFIFOW(inter_fd,2)=castle_id; + WFIFOB(inter_fd,4)=index; + WFIFOL(inter_fd,5)=value; + WFIFOSET(inter_fd,9); + return 0; +} +//----------------------------------------------------------------- +// Packets receive from inter server + +// Wisp/Page reception +int intif_parse_WisMessage(int fd) { // rewritten by [Yor] + struct map_session_data* sd; + int i; + char *wisp_source; + + if (battle_config.etc_log) + printf("intif_parse_wismessage: id: %d, from: %s, to: %s, message: '%s'\n", RFIFOL(fd,4), RFIFOP(fd,8), RFIFOP(fd,32), RFIFOP(fd,56)); + sd = map_nick2sd(RFIFOP(fd,32)); // Searching destination player + if (sd != NULL && strcmp(sd->status.name, RFIFOP(fd,32)) == 0) { // exactly same name (inter-server have checked the name before) + // if player ignore all + if (sd->ignoreAll == 1) + intif_wis_replay(RFIFOL(fd,4), 2); // flag: 0: success to send wisper, 1: target character is not loged in?, 2: ignored by target + else { + wisp_source = RFIFOP(fd,8); // speed up + // if player ignore the source character + for(i = 0; i < (sizeof(sd->ignore) / sizeof(sd->ignore[0])); i++) + if (strcmp(sd->ignore[i].name, wisp_source) == 0) { + intif_wis_replay(RFIFOL(fd,4), 2); // flag: 0: success to send wisper, 1: target character is not loged in?, 2: ignored by target + break; + } + // if source player not found in ignore list + if (i == (sizeof(sd->ignore) / sizeof(sd->ignore[0]))) { + clif_wis_message(sd->fd,RFIFOP(fd,8),RFIFOP(fd,56),RFIFOW(fd,2)-56); + intif_wis_replay(RFIFOL(fd,4), 0); // flag: 0: success to send wisper, 1: target character is not loged in?, 2: ignored by target + } + } + } else + intif_wis_replay(RFIFOL(fd,4), 1); // flag: 0: success to send wisper, 1: target character is not loged in?, 2: ignored by target + return 0; +} + +// Wisp/page transmission result reception +int intif_parse_WisEnd(int fd) { + struct map_session_data* sd; + + if (battle_config.etc_log) + printf("intif_parse_wisend: player: %s, flag: %d\n", RFIFOP(fd,2), RFIFOB(fd,26)); // flag: 0: success to send wisper, 1: target character is not loged in?, 2: ignored by target + sd = map_nick2sd(RFIFOP(fd,2)); + if (sd != NULL) + clif_wis_end(sd->fd, RFIFOB(fd,26)); + + return 0; +} + +// Received wisp message from map-server via char-server for ALL gm +int mapif_parse_WisToGM(int fd) { // 0x3003/0x3803 <packet_len>.w <wispname>.24B <min_gm_level>.w <message>.?B + int i, min_gm_level; + struct map_session_data *pl_sd; + char Wisp_name[24]; + char mbuf[255]; + char *message = ((RFIFOW(fd,2) - 30) >= sizeof(mbuf)) ? (char *) malloc((RFIFOW(fd,2) - 30)) : mbuf; + + min_gm_level = (int)RFIFOW(fd,28); + memcpy(Wisp_name, RFIFOP(fd,4), 24); + Wisp_name[23] = '\0'; + memcpy(message, RFIFOP(fd,30), RFIFOW(fd,2) - 30); + message[sizeof(message) - 1] = '\0'; + // information is sended to all online GM + for (i = 0; i < fd_max; i++) + if (session[i] && (pl_sd = session[i]->session_data) && pl_sd->state.auth) + if (pc_isGM(pl_sd) >= min_gm_level) + clif_wis_message(i, Wisp_name, message, strlen(message) + 1); + + if (message != mbuf) + free(message); + + return 0; +} + +// アカウント変数通知 +int intif_parse_AccountReg(int fd) { + int j,p; + struct map_session_data *sd; + + if( (sd=map_id2sd(RFIFOL(fd,4)))==NULL ) + return 1; + for(p=8,j=0;p<RFIFOW(fd,2) && j<ACCOUNT_REG_NUM;p+=36,j++){ + memcpy(sd->status.account_reg[j].str,RFIFOP(fd,p),32); + sd->status.account_reg[j].value=RFIFOL(fd,p+32); + } + sd->status.account_reg_num = j; +// printf("intif: accountreg\n"); + + return 0; +} + +// 倉庫データ受信 +int intif_parse_LoadStorage(int fd) { + struct storage *stor; + struct map_session_data *sd; + + stor = account2storage( RFIFOL(fd,4)); + if (RFIFOW(fd,2)-8 != sizeof(struct storage)) { + if (battle_config.error_log) + printf("intif_parse_LoadStorage: data size error %d %d\n", RFIFOW(fd,2)-8, sizeof(struct storage)); + return 1; + } + sd=map_id2sd( RFIFOL(fd,4) ); + if(sd==NULL){ + if(battle_config.error_log) + printf("intif_parse_LoadStorage: user not found %d\n",RFIFOL(fd,4)); + return 1; + } + if(battle_config.save_log) + printf("intif_openstorage: %d\n",RFIFOL(fd,4) ); + memcpy(stor,RFIFOP(fd,8),sizeof(struct storage)); + stor->storage_status=1; + sd->state.storage_flag = 0; + clif_storageitemlist(sd,stor); + clif_storageequiplist(sd,stor); + clif_updatestorageamount(sd,stor); + + return 0; +} + +// 倉庫データ送信成功 +int intif_parse_SaveStorage(int fd) +{ + if(battle_config.save_log) + printf("intif_savestorage: done %d %d\n",RFIFOL(fd,2),RFIFOB(fd,6) ); + return 0; +} + +int intif_parse_LoadGuildStorage(int fd) +{ + struct guild_storage *gstor; + struct map_session_data *sd; + int guild_id = RFIFOL(fd,8); + if(guild_id > 0) { + gstor=guild2storage(guild_id); + if(!gstor) { + if(battle_config.error_log) + printf("intif_parse_LoadGuildStorage: error guild_id %d not exist\n",guild_id); + return 1; + } + if( RFIFOW(fd,2)-12 != sizeof(struct guild_storage) ){ + gstor->storage_status = 0; + if(battle_config.error_log) + printf("intif_parse_LoadGuildStorage: data size error %d %d\n",RFIFOW(fd,2)-12 , sizeof(struct guild_storage)); + return 1; + } + sd=map_id2sd( RFIFOL(fd,4) ); + if(sd==NULL){ + if(battle_config.error_log) + printf("intif_parse_LoadGuildStorage: user not found %d\n",RFIFOL(fd,4)); + return 1; + } + if(battle_config.save_log) + printf("intif_open_guild_storage: %d\n",RFIFOL(fd,4) ); + memcpy(gstor,RFIFOP(fd,12),sizeof(struct guild_storage)); + gstor->storage_status = 1; + sd->state.storage_flag = 1; + clif_guildstorageitemlist(sd,gstor); + clif_guildstorageequiplist(sd,gstor); + clif_updateguildstorageamount(sd,gstor); + } + return 0; +} +int intif_parse_SaveGuildStorage(int fd) +{ + if(battle_config.save_log) { + printf("intif_save_guild_storage: done %d %d %d\n",RFIFOL(fd,2),RFIFOL(fd,6),RFIFOB(fd,10) ); + } + return 0; +} + +// パーティ作成可否 +int intif_parse_PartyCreated(int fd) +{ + if(battle_config.etc_log) + printf("intif: party created\n"); + party_created(RFIFOL(fd,2),RFIFOB(fd,6),RFIFOL(fd,7),RFIFOP(fd,11)); + return 0; +} +// パーティ情報 +int intif_parse_PartyInfo(int fd) +{ + if( RFIFOW(fd,2)==8){ + if(battle_config.error_log) + printf("intif: party noinfo %d\n",RFIFOL(fd,4)); + party_recv_noinfo(RFIFOL(fd,4)); + return 0; + } + +// printf("intif: party info %d\n",RFIFOL(fd,4)); + if( RFIFOW(fd,2)!=sizeof(struct party)+4 ){ + if(battle_config.error_log) + printf("intif: party info : data size error %d %d %d\n",RFIFOL(fd,4),RFIFOW(fd,2),sizeof(struct party)+4); + } + party_recv_info((struct party *)RFIFOP(fd,4)); + return 0; +} +// パーティ追加通知 +int intif_parse_PartyMemberAdded(int fd) +{ + if(battle_config.etc_log) + printf("intif: party member added %d %d %d\n",RFIFOL(fd,2),RFIFOL(fd,6),RFIFOB(fd,10)); + party_member_added(RFIFOL(fd,2),RFIFOL(fd,6),RFIFOB(fd,10)); + return 0; +} +// パーティ設定変更通知 +int intif_parse_PartyOptionChanged(int fd) +{ + party_optionchanged(RFIFOL(fd,2),RFIFOL(fd,6),RFIFOW(fd,10),RFIFOW(fd,12),RFIFOB(fd,14)); + return 0; +} +// パーティ脱退通知 +int intif_parse_PartyMemberLeaved(int fd) +{ + if(battle_config.etc_log) + printf("intif: party member leaved %d %d %s\n",RFIFOL(fd,2),RFIFOL(fd,6),RFIFOP(fd,10)); + party_member_leaved(RFIFOL(fd,2),RFIFOL(fd,6),RFIFOP(fd,10)); + return 0; +} +// パーティ解散通知 +int intif_parse_PartyBroken(int fd) +{ + party_broken(RFIFOL(fd,2)); + return 0; +} +// パーティ移動通知 +int intif_parse_PartyMove(int fd) +{ +// if(battle_config.etc_log) +// printf("intif: party move %d %d %s %d %d\n",RFIFOL(fd,2),RFIFOL(fd,6),RFIFOP(fd,10),RFIFOB(fd,26),RFIFOW(fd,27)); + party_recv_movemap(RFIFOL(fd,2),RFIFOL(fd,6),RFIFOP(fd,10),RFIFOB(fd,26),RFIFOW(fd,27)); + return 0; +} +// パーティメッセージ +int intif_parse_PartyMessage(int fd) +{ +// if(battle_config.etc_log) +// printf("intif_parse_PartyMessage: %s\n",RFIFOP(fd,12)); + party_recv_message(RFIFOL(fd,4),RFIFOL(fd,8),RFIFOP(fd,12),RFIFOW(fd,2)-12); + return 0; +} + +// ギルド作成可否 +int intif_parse_GuildCreated(int fd) +{ + guild_created(RFIFOL(fd,2),RFIFOL(fd,6)); + return 0; +} +// ギルド情報 +int intif_parse_GuildInfo(int fd) +{ + if( RFIFOW(fd,2)==8){ + if(battle_config.error_log) + printf("intif: guild noinfo %d\n",RFIFOL(fd,4)); + guild_recv_noinfo(RFIFOL(fd,4)); + return 0; + } + +// if(battle_config.etc_log) +// printf("intif: guild info %d\n",RFIFOL(fd,4)); + if( RFIFOW(fd,2)!=sizeof(struct guild)+4 ){ + if(battle_config.error_log) + printf("intif: guild info : data size error\n %d %d %d",RFIFOL(fd,4),RFIFOW(fd,2),sizeof(struct guild)+4); + } + guild_recv_info((struct guild *)RFIFOP(fd,4)); + return 0; +} +// ギルドメンバ追加通知 +int intif_parse_GuildMemberAdded(int fd) +{ + if(battle_config.etc_log) + printf("intif: guild member added %d %d %d %d\n",RFIFOL(fd,2),RFIFOL(fd,6),RFIFOL(fd,10),RFIFOB(fd,14)); + guild_member_added(RFIFOL(fd,2),RFIFOL(fd,6),RFIFOL(fd,10),RFIFOB(fd,14)); + return 0; +} +// ギルドメンバ脱退/追放通知 +int intif_parse_GuildMemberLeaved(int fd) +{ + guild_member_leaved(RFIFOL(fd,2),RFIFOL(fd,6),RFIFOL(fd,10),RFIFOB(fd,14), + RFIFOP(fd,55),RFIFOP(fd,15)); + return 0; +} + +// ギルドメンバオンライン状態/Lv変更通知 +int intif_parse_GuildMemberInfoShort(int fd) +{ + guild_recv_memberinfoshort(RFIFOL(fd,2),RFIFOL(fd,6),RFIFOL(fd,10),RFIFOB(fd,14),RFIFOW(fd,15),RFIFOW(fd,17)); + return 0; +} +// ギルド解散通知 +int intif_parse_GuildBroken(int fd) +{ + guild_broken(RFIFOL(fd,2),RFIFOB(fd,6)); + return 0; +} + +// ギルド基本情報変更通知 +int intif_parse_GuildBasicInfoChanged(int fd) +{ + int type=RFIFOW(fd,8),guild_id=RFIFOL(fd,4); + void *data=RFIFOP(fd,10); + struct guild *g=guild_search(guild_id); + short dw=*((short *)data); + int dd=*((int *)data); + if( g==NULL ) + return 0; + switch(type){ + case GBI_EXP: g->exp=dd; break; + case GBI_GUILDLV: g->guild_lv=dw; break; + case GBI_SKILLPOINT: g->skill_point=dd; break; + } + return 0; +} +// ギルドメンバ情報変更通知 +int intif_parse_GuildMemberInfoChanged(int fd) +{ + int type=RFIFOW(fd,16),guild_id=RFIFOL(fd,4); + int account_id=RFIFOL(fd,8),char_id=RFIFOL(fd,12); + void *data=RFIFOP(fd,18); + struct guild *g=guild_search(guild_id); + int idx,dd=*((int *)data); + if( g==NULL ) + return 0; + idx=guild_getindex(g,account_id,char_id); + switch(type){ + case GMI_POSITION: + g->member[idx].position=dd; + guild_memberposition_changed(g,idx,dd); + break; + case GMI_EXP: + g->member[idx].exp=dd; + break; + } + return 0; +} + +// ギルド役職変更通知 +int intif_parse_GuildPosition(int fd) +{ + if( RFIFOW(fd,2)!=sizeof(struct guild_position)+12 ){ + if(battle_config.error_log) + printf("intif: guild info : data size error\n %d %d %d",RFIFOL(fd,4),RFIFOW(fd,2),sizeof(struct guild_position)+12); + } + guild_position_changed(RFIFOL(fd,4),RFIFOL(fd,8),(struct guild_position *)RFIFOP(fd,12)); + return 0; +} +// ギルドスキル割り振り通知 +int intif_parse_GuildSkillUp(int fd) +{ + guild_skillupack(RFIFOL(fd,2),RFIFOL(fd,6),RFIFOL(fd,10)); + return 0; +} +// ギルド同盟/敵対通知 +int intif_parse_GuildAlliance(int fd) +{ + guild_allianceack(RFIFOL(fd,2),RFIFOL(fd,6),RFIFOL(fd,10),RFIFOL(fd,14), + RFIFOB(fd,18),RFIFOP(fd,19),RFIFOP(fd,43)); + return 0; +} +// ギルド告知変更通知 +int intif_parse_GuildNotice(int fd) +{ + guild_notice_changed(RFIFOL(fd,2),RFIFOP(fd,6),RFIFOP(fd,66)); + return 0; +} +// ギルドエンブレム変更通知 +int intif_parse_GuildEmblem(int fd) +{ + guild_emblem_changed(RFIFOW(fd,2)-12,RFIFOL(fd,4),RFIFOL(fd,8),RFIFOP(fd,12)); + return 0; +} +// ギルド会話受信 +int intif_parse_GuildMessage(int fd) +{ + guild_recv_message(RFIFOL(fd,4),RFIFOL(fd,8),RFIFOP(fd,12),RFIFOW(fd,2)-12); + return 0; +} +// ギルド城データ要求返信 +int intif_parse_GuildCastleDataLoad(int fd) +{ + return guild_castledataloadack(RFIFOW(fd,2),RFIFOB(fd,4),RFIFOL(fd,5)); +} +// ギルド城データ変更通知 +int intif_parse_GuildCastleDataSave(int fd) +{ + return guild_castledatasaveack(RFIFOW(fd,2),RFIFOB(fd,4),RFIFOL(fd,5)); +} + +// ギルド城データ一括受信(初期化時) +int intif_parse_GuildCastleAllDataLoad(int fd) +{ + return guild_castlealldataload(RFIFOW(fd,2),(struct guild_castle *)RFIFOP(fd,4)); +} + +// pet +int intif_parse_CreatePet(int fd) +{ + pet_get_egg(RFIFOL(fd,2),RFIFOL(fd,7),RFIFOB(fd,6)); + + return 0; +} + +int intif_parse_RecvPetData(int fd) +{ + struct s_pet p; + int len=RFIFOW(fd,2); + if(sizeof(struct s_pet)!=len-9) { + if(battle_config.etc_log) + printf("intif: pet data: data size error %d %d\n",sizeof(struct s_pet),len-9); + } + else{ + memcpy(&p,RFIFOP(fd,9),sizeof(struct s_pet)); + pet_recv_petdata(RFIFOL(fd,4),&p,RFIFOB(fd,8)); + } + + return 0; +} +int intif_parse_SavePetOk(int fd) +{ + if(RFIFOB(fd,6) == 1) { + if(battle_config.error_log) + printf("pet data save failure\n"); + } + + return 0; +} + +int intif_parse_DeletePetOk(int fd) +{ + if(RFIFOB(fd,2) == 1) { + if(battle_config.error_log) + printf("pet data delete failure\n"); + } + + return 0; +} +//----------------------------------------------------------------- +// inter serverからの通信 +// エラーがあれば0(false)を返すこと +// パケットが処理できれば1,パケット長が足りなければ2を返すこと +int intif_parse(int fd) +{ + int packet_len; + int cmd = RFIFOW(fd,0); + // パケットのID確認 + if(cmd<0x3800 || cmd>=0x3800+(sizeof(packet_len_table)/sizeof(packet_len_table[0])) || + packet_len_table[cmd-0x3800]==0){ + return 0; + } + // パケットの長さ確認 + packet_len = packet_len_table[cmd-0x3800]; + if(packet_len==-1){ + if(RFIFOREST(fd)<4) + return 2; + packet_len = RFIFOW(fd,2); + } +// if(battle_config.etc_log) +// printf("intif_parse %d %x %d %d\n",fd,cmd,packet_len,RFIFOREST(fd)); + if(RFIFOREST(fd)<packet_len){ + return 2; + } + // 処理分岐 + switch(cmd){ + case 0x3800: clif_GMmessage(NULL,RFIFOP(fd,4),packet_len-4,0); break; + case 0x3801: intif_parse_WisMessage(fd); break; + case 0x3802: intif_parse_WisEnd(fd); break; + case 0x3803: mapif_parse_WisToGM(fd); break; + case 0x3804: intif_parse_AccountReg(fd); break; + case 0x3810: intif_parse_LoadStorage(fd); break; + case 0x3811: intif_parse_SaveStorage(fd); break; + case 0x3818: intif_parse_LoadGuildStorage(fd); break; + case 0x3819: intif_parse_SaveGuildStorage(fd); break; + case 0x3820: intif_parse_PartyCreated(fd); break; + case 0x3821: intif_parse_PartyInfo(fd); break; + case 0x3822: intif_parse_PartyMemberAdded(fd); break; + case 0x3823: intif_parse_PartyOptionChanged(fd); break; + case 0x3824: intif_parse_PartyMemberLeaved(fd); break; + case 0x3825: intif_parse_PartyMove(fd); break; + case 0x3826: intif_parse_PartyBroken(fd); break; + case 0x3827: intif_parse_PartyMessage(fd); break; + case 0x3830: intif_parse_GuildCreated(fd); break; + case 0x3831: intif_parse_GuildInfo(fd); break; + case 0x3832: intif_parse_GuildMemberAdded(fd); break; + case 0x3834: intif_parse_GuildMemberLeaved(fd); break; + case 0x3835: intif_parse_GuildMemberInfoShort(fd); break; + case 0x3836: intif_parse_GuildBroken(fd); break; + case 0x3837: intif_parse_GuildMessage(fd); break; + case 0x3839: intif_parse_GuildBasicInfoChanged(fd); break; + case 0x383a: intif_parse_GuildMemberInfoChanged(fd); break; + case 0x383b: intif_parse_GuildPosition(fd); break; + case 0x383c: intif_parse_GuildSkillUp(fd); break; + case 0x383d: intif_parse_GuildAlliance(fd); break; + case 0x383e: intif_parse_GuildNotice(fd); break; + case 0x383f: intif_parse_GuildEmblem(fd); break; + case 0x3840: intif_parse_GuildCastleDataLoad(fd); break; + case 0x3841: intif_parse_GuildCastleDataSave(fd); break; + case 0x3842: intif_parse_GuildCastleAllDataLoad(fd); break; + case 0x3880: intif_parse_CreatePet(fd); break; + case 0x3881: intif_parse_RecvPetData(fd); break; + case 0x3882: intif_parse_SavePetOk(fd); break; + case 0x3883: intif_parse_DeletePetOk(fd); break; + default: + if(battle_config.error_log) + printf("intif_parse : unknown packet %d %x\n",fd,RFIFOW(fd,0)); + return 0; + } + // パケット読み飛ばし + RFIFOSKIP(fd,packet_len); + return 1; +} diff --git a/misc/src/map/intif.h b/misc/src/map/intif.h new file mode 100644 index 0000000..85e1914 --- /dev/null +++ b/misc/src/map/intif.h @@ -0,0 +1,56 @@ +// $Id: intif.h,v 1.2 2004/09/25 05:32:18 MouseJstr Exp $ +#ifndef _INTIF_H_ +#define _INFIF_H_ + +int intif_parse(int fd); + +int intif_GMmessage(char* mes,int len,int flag); + +int intif_wis_message(struct map_session_data *sd,char *nick,char *mes,int mes_len); +int intif_wis_message_to_gm(char *Wisp_name, int min_gm_level, char *mes, int mes_len); + +int intif_saveaccountreg(struct map_session_data *sd); +int intif_request_accountreg(struct map_session_data *sd); + +int intif_request_storage(int account_id); +int intif_send_storage(struct storage *stor); +int intif_request_guild_storage(int account_id, int guild_id); +int intif_send_guild_storage(int account_id, struct guild_storage *gstor); + + +int intif_create_party(struct map_session_data *sd,char *name); +int intif_request_partyinfo(int party_id); +int intif_party_addmember(int party_id, int account_id); +int intif_party_changeoption(int party_id, int account_id, int exp, int item); +int intif_party_leave(int party_id, int accound_id); +int intif_party_changemap(struct map_session_data *sd, int online); +int intif_break_party(int party_id); +int intif_party_message(int party_id, int account_id, char *mes,int len); +int intif_party_checkconflict(int party_id, int account_id, char *nick); + + +int intif_guild_create(const char *name, const struct guild_member *master); +int intif_guild_request_info(int guild_id); +int intif_guild_addmember(int guild_id, struct guild_member *m); +int intif_guild_leave(int guild_id, int account_id, int char_id, int flag, const char *mes); +int intif_guild_memberinfoshort(int guild_id, int account_id, int char_id, int online, int lv, int class); +int intif_guild_break(int guild_id); +int intif_guild_message(int guild_id, int account_id, char *mes, int len); +int intif_guild_checkconflict(int guild_id, int account_id, int char_id); +int intif_guild_change_basicinfo(int guild_id, int type, const void *data, int len); +int intif_guild_change_memberinfo(int guild_id, int account_id, int char_id, int type, const void *data, int len); +int intif_guild_position(int guild_id, int idx, struct guild_position *p); +int intif_guild_skillup(int guild_id, int skill_num, int account_id); +int intif_guild_alliance(int guild_id1, int guild_id2, int account_id1, int account_id2, int flag); +int intif_guild_notice(int guild_id, const char *mes1, const char *mes2); +int intif_guild_emblem(int guild_id, int len, const char *data); +int intif_guild_castle_dataload(int castle_id, int index); +int intif_guild_castle_datasave(int castle_id, int index, int value); + +int intif_create_pet(int account_id, int char_id, short pet_type, short pet_lv, short pet_egg_id, + short pet_equip, short intimate, short hungry, char rename_flag, char incuvate, char *pet_name); +int intif_request_petdata(int account_id, int char_id, int pet_id); +int intif_save_petdata(int account_id, struct s_pet *p); +int intif_delete_petdata(int pet_id); + +#endif diff --git a/misc/src/map/itemdb.c b/misc/src/map/itemdb.c new file mode 100644 index 0000000..a225cff --- /dev/null +++ b/misc/src/map/itemdb.c @@ -0,0 +1,882 @@ +// $Id: itemdb.c,v 1.3 2004/09/25 05:32:18 MouseJstr Exp $ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "db.h" +#include "grfio.h" +#include "nullpo.h" +#include "malloc.h" +#include "map.h" +#include "battle.h" +#include "itemdb.h" +#include "script.h" +#include "pc.h" + +#ifdef MEMWATCH +#include "memwatch.h" +#endif + +#define MAX_RANDITEM 2000 + +// ** ITEMDB_OVERRIDE_NAME_VERBOSE ** +// 定義すると、itemdb.txtとgrfで名前が異なる場合、表示します. +//#define ITEMDB_OVERRIDE_NAME_VERBOSE 1 + +static struct dbt* item_db; + +static struct random_item_data blue_box[MAX_RANDITEM],violet_box[MAX_RANDITEM],card_album[MAX_RANDITEM],gift_box[MAX_RANDITEM],scroll[MAX_RANDITEM]; +static int blue_box_count=0,violet_box_count=0,card_album_count=0,gift_box_count=0,scroll_count=0; +static int blue_box_default=0,violet_box_default=0,card_album_default=0,gift_box_default=0,scroll_default=0; + +// Function declarations + +static void itemdb_read(void); +static int itemdb_readdb(void); +#ifndef TXT_ONLY +static int itemdb_read_sqldb(void); +#endif /* not TXT_ONLY */ +static int itemdb_read_randomitem(); +static int itemdb_read_itemavail(void); +static int itemdb_read_itemnametable(void); +static int itemdb_read_noequip(void); +void itemdb_reload(void); + +/*========================================== + * 名前で検索用 + *------------------------------------------ + */ +// name = item alias, so we should find items aliases first. if not found then look for "jname" (full name) +int itemdb_searchname_sub(void *key,void *data,va_list ap) +{ + struct item_data *item=(struct item_data *)data,**dst; + char *str; + str=va_arg(ap,char *); + dst=va_arg(ap,struct item_data **); +// if( strcmpi(item->name,str)==0 || strcmp(item->jname,str)==0 || +// memcmp(item->name,str,24)==0 || memcmp(item->jname,str,24)==0 ) + if( strcmpi(item->name,str)==0 ) //by lupus + *dst=item; + return 0; +} + +/*========================================== + * 名前で検索用 + *------------------------------------------ + */ +int itemdb_searchjname_sub(void *key,void *data,va_list ap) +{ + struct item_data *item=(struct item_data *)data,**dst; + char *str; + str=va_arg(ap,char *); + dst=va_arg(ap,struct item_data **); + if( strcmpi(item->jname,str)==0 ) + *dst=item; + return 0; +} +/*========================================== + * 名前で検索 + *------------------------------------------ + */ +struct item_data* itemdb_searchname(const char *str) +{ + struct item_data *item=NULL; + numdb_foreach(item_db,itemdb_searchname_sub,str,&item); + return item; +} + +/*========================================== + * 箱系アイテム検索 + *------------------------------------------ + */ +int itemdb_searchrandomid(int flags) +{ + int nameid=0,i,index,count; + struct random_item_data *list=NULL; + + struct { + int nameid,count; + struct random_item_data *list; + } data[] ={ + { 0,0,NULL }, + { blue_box_default ,blue_box_count ,blue_box }, + { violet_box_default,violet_box_count ,violet_box }, + { card_album_default,card_album_count ,card_album }, + { gift_box_default ,gift_box_count ,gift_box }, + { scroll_default ,scroll_count ,scroll }, + }; + + if(flags>=1 && flags<=5){ + nameid=data[flags].nameid; + count=data[flags].count; + list=data[flags].list; + + if(count > 0) { + for(i=0;i<1000;i++) { + index = rand()%count; + if( rand()%1000000 < list[index].per) { + nameid = list[index].nameid; + break; + } + } + } + } + return nameid; +} + +/*========================================== + * DBの存在確認 + *------------------------------------------ + */ +struct item_data* itemdb_exists(int nameid) +{ + return numdb_search(item_db,nameid); +} +/*========================================== + * DBの検索 + *------------------------------------------ + */ +struct item_data* itemdb_search(int nameid) +{ + struct item_data *id; + + id=numdb_search(item_db,nameid); + if(id) return id; + + id=(struct item_data *)aCalloc(1,sizeof(struct item_data)); + numdb_insert(item_db,nameid,id); + + id->nameid=nameid; + id->value_buy=10; + id->value_sell=id->value_buy/2; + id->weight=10; + id->sex=2; + id->elv=0; + id->class=0xffffffff; + id->flag.available=0; + id->flag.value_notdc=0; //一応・・・ + id->flag.value_notoc=0; + id->flag.no_equip=0; + id->view_id=0; + + if(nameid>500 && nameid<600) + id->type=0; //heal item + else if(nameid>600 && nameid<700) + id->type=2; //use item + else if((nameid>700 && nameid<1100) || + (nameid>7000 && nameid<8000)) + id->type=3; //correction + else if(nameid>=1750 && nameid<1771) + id->type=10; //arrow + else if(nameid>1100 && nameid<2000) + id->type=4; //weapon + else if((nameid>2100 && nameid<3000) || + (nameid>5000 && nameid<6000)) + id->type=5; //armor + else if(nameid>4000 && nameid<5000) + id->type=6; //card + else if(nameid>9000 && nameid<10000) + id->type=7; //egg + else if(nameid>10000) + id->type=8; //petequip + + return id; +} + +/*========================================== + * + *------------------------------------------ + */ +int itemdb_isequip(int nameid) +{ + int type=itemdb_type(nameid); + if(type==0 || type==2 || type==3 || type==6 || type==10) + return 0; + return 1; +} +/*========================================== + * + *------------------------------------------ + */ +int itemdb_isequip2(struct item_data *data) +{ + if(data) { + int type=data->type; + if(type==0 || type==2 || type==3 || type==6 || type==10) + return 0; + else + return 1; + } + return 0; +} +/*========================================== + * + *------------------------------------------ + */ +int itemdb_isequip3(int nameid) +{ + int type=itemdb_type(nameid); + if(type==4 || type==5 || type == 8) + return 1; + return 0; +} + +/*========================================== + * 捨てられるアイテムは1、そうでないアイテムは0 + *------------------------------------------ + */ +int itemdb_isdropable(int nameid) +{ + //結婚指輪は捨てられない + switch(nameid){ + case 2634: //結婚指輪 + case 2635: //結婚指輪 + return 0; + } + + return 1; +} + +// +// 初期化 +// +/*========================================== + * + *------------------------------------------ + */ +static int itemdb_read_itemslottable(void) +{ + char *buf,*p; + int s; + + buf=grfio_read("data\\itemslottable.txt"); + if(buf==NULL) + return -1; + s=grfio_size("data\\itemslottable.txt"); + buf[s]=0; + for(p=buf;p-buf<s;){ + int nameid,equip; + sscanf(p,"%d#%d#",&nameid,&equip); + itemdb_search(nameid)->equip=equip; + p=strchr(p,10); + if(!p) break; + p++; + p=strchr(p,10); + if(!p) break; + p++; + } + free(buf); + + return 0; +} + +#ifndef TXT_ONLY +/*==================================== + * Removed item_value_db, don't re-add + *------------------------------------ + */ +static void itemdb_read(void) +{ + itemdb_read_itemslottable(); + + if (db_use_sqldbs) + { + itemdb_read_sqldb(); + } + else + { + itemdb_readdb(); + } + + itemdb_read_randomitem(); + itemdb_read_itemavail(); + itemdb_read_noequip(); + + if (!battle_config.item_name_override_grffile) + itemdb_read_itemnametable(); +} + +#endif /* not TXT_ONLY */ +/*========================================== + * アイテムデータベースの読み込み + *------------------------------------------ + */ +static int itemdb_readdb(void) +{ + FILE *fp; + char line[1024]; + int ln=0,lines=0; + int nameid,j; + char *str[32],*p,*np; + struct item_data *id; + int i=0; + char *filename[]={ "db/item_db.txt","db/item_db2.txt" }; + + for(i=0;i<2;i++){ + + fp=fopen(filename[i],"r"); + if(fp==NULL){ + if(i>0) + continue; + printf("can't read %s\n",filename[i]); + exit(1); + } + + lines=0; + while(fgets(line,1020,fp)){ + lines++; + if(line[0]=='/' && line[1]=='/') + continue; + memset(str,0,sizeof(str)); + for(j=0,np=p=line;j<17 && p;j++){ + str[j]=p; + p=strchr(p,','); + if(p){ *p++=0; np=p; } + } + if(str[0]==NULL) + continue; + + nameid=atoi(str[0]); + if(nameid<=0 || nameid>=20000) + continue; + ln++; + + //ID,Name,Jname,Type,Price,Sell,Weight,ATK,DEF,Range,Slot,Job,Gender,Loc,wLV,eLV,View + id=itemdb_search(nameid); + memcpy(id->name,str[1],24); + memcpy(id->jname,str[2],24); + id->type=atoi(str[3]); + // buy≠sell*2 は item_value_db.txt で指定してください。 + if (atoi(str[5])) { // sell値を優先とする + id->value_buy=atoi(str[5])*2; + id->value_sell=atoi(str[5]); + } else { + id->value_buy=atoi(str[4]); + id->value_sell=atoi(str[4])/2; + } + id->weight=atoi(str[6]); + id->atk=atoi(str[7]); + id->def=atoi(str[8]); + id->range=atoi(str[9]); + id->slot=atoi(str[10]); + id->class=atoi(str[11]); + id->sex=atoi(str[12]); + if(id->equip != atoi(str[13])){ + id->equip=atoi(str[13]); + } + id->wlv=atoi(str[14]); + id->elv=atoi(str[15]); + id->look=atoi(str[16]); + id->flag.available=1; + id->flag.value_notdc=0; + id->flag.value_notoc=0; + id->view_id=0; + + id->use_script=NULL; + id->equip_script=NULL; + + if((p=strchr(np,'{'))==NULL) + continue; + id->use_script = parse_script(p,lines); + if((p=strchr(p+1,'{'))==NULL) + continue; + id->equip_script = parse_script(p,lines); + } + fclose(fp); + printf("read %s done (count=%d)\n",filename[i],ln); + } + return 0; +} + +// Removed item_value_db, don't re-add! + +/*========================================== + * ランダムアイテム出現データの読み込み + *------------------------------------------ + */ +static int itemdb_read_randomitem() +{ + FILE *fp; + char line[1024]; + int ln=0; + int nameid,i,j; + char *str[10],*p; + + const struct { + char filename[64]; + struct random_item_data *pdata; + int *pcount,*pdefault; + } data[] = { + {"db/item_bluebox.txt", blue_box, &blue_box_count, &blue_box_default }, + {"db/item_violetbox.txt", violet_box, &violet_box_count, &violet_box_default }, + {"db/item_cardalbum.txt", card_album, &card_album_count, &card_album_default }, + {"db/item_giftbox.txt", gift_box, &gift_box_count, &gift_box_default }, + {"db/item_scroll.txt", scroll, &scroll_count, &scroll_default }, + }; + + for(i=0;i<sizeof(data)/sizeof(data[0]);i++){ + struct random_item_data *pd=data[i].pdata; + int *pc=data[i].pcount; + int *pdefault=data[i].pdefault; + char *fn=data[i].filename; + + *pdefault = 0; + if( (fp=fopen(fn,"r"))==NULL ){ + printf("can't read %s\n",fn); + continue; + } + + while(fgets(line,1020,fp)){ + if(line[0]=='/' && line[1]=='/') + continue; + memset(str,0,sizeof(str)); + for(j=0,p=line;j<3 && p;j++){ + str[j]=p; + p=strchr(p,','); + if(p) *p++=0; + } + + if(str[0]==NULL) + continue; + + nameid=atoi(str[0]); + if(nameid<0 || nameid>=20000) + continue; + if(nameid == 0) { + if(str[2]) + *pdefault = atoi(str[2]); + continue; + } + + if(str[2]){ + pd[ *pc ].nameid = nameid; + pd[(*pc)++].per = atoi(str[2]); + } + + if(ln >= MAX_RANDITEM) + break; + ln++; + } + fclose(fp); + printf("read %s done (count=%d)\n",fn,*pc); + } + + return 0; +} +/*========================================== + * アイテム使用可能フラグのオーバーライド + *------------------------------------------ + */ +static int itemdb_read_itemavail(void) +{ + FILE *fp; + char line[1024]; + int ln=0; + int nameid,j,k; + char *str[10],*p; + + if( (fp=fopen("db/item_avail.txt","r"))==NULL ){ + printf("can't read db/item_avail.txt\n"); + return -1; + } + + while(fgets(line,1020,fp)){ + struct item_data *id; + if(line[0]=='/' && line[1]=='/') + continue; + memset(str,0,sizeof(str)); + for(j=0,p=line;j<2 && p;j++){ + str[j]=p; + p=strchr(p,','); + if(p) *p++=0; + } + + if(str[0]==NULL) + continue; + + nameid=atoi(str[0]); + if(nameid<0 || nameid>=20000 || !(id=itemdb_exists(nameid)) ) + continue; + k=atoi(str[1]); + if(k > 0) { + id->flag.available = 1; + id->view_id = k; + } + else + id->flag.available = 0; + ln++; + } + fclose(fp); + printf("read db/item_avail.txt done (count=%d)\n",ln); + return 0; +} + +/*========================================== + * アイテムの名前テーブルを読み込む + *------------------------------------------ + */ +static int itemdb_read_itemnametable(void) +{ + char *buf,*p; + int s; + + buf=grfio_reads("data\\idnum2itemdisplaynametable.txt",&s); + + if(buf==NULL) + return -1; + + buf[s]=0; + for(p=buf;p-buf<s;){ + int nameid; + char buf2[64]; + + if( sscanf(p,"%d#%[^#]#",&nameid,buf2)==2 ){ + +#ifdef ITEMDB_OVERRIDE_NAME_VERBOSE + if( itemdb_exists(nameid) && + strncmp(itemdb_search(nameid)->jname,buf2,24)!=0 ){ + printf("[override] %d %s => %s\n",nameid + ,itemdb_search(nameid)->jname,buf2); + } +#endif + + memcpy(itemdb_search(nameid)->jname,buf2,24); + } + + p=strchr(p,10); + if(!p) break; + p++; + } + free(buf); + printf("read data\\idnum2itemdisplaynametable.txt done.\n"); + + return 0; +} +#ifdef TXT_ONLY +/*========================================== + * カードイラストのリソース名前テーブルを読み込む + *------------------------------------------ + */ +static int itemdb_read_cardillustnametable(void) +{ + char *buf,*p; + int s; + + buf=grfio_reads("data\\num2cardillustnametable.txt",&s); + + if(buf==NULL) + return -1; + + buf[s]=0; + for(p=buf;p-buf<s;){ + int nameid; + char buf2[64]; + + if( sscanf(p,"%d#%[^#]#",&nameid,buf2)==2 ){ + strcat(buf2,".bmp"); + memcpy(itemdb_search(nameid)->cardillustname,buf2,64); +// printf("%d %s\n",nameid,itemdb_search(nameid)->cardillustname); + } + + p=strchr(p,10); + if(!p) break; + p++; + } + free(buf); + printf("read data\\num2cardillustnametable.txt done.\n"); + + return 0; +} +#endif /* TXT_ONLY */ +/*========================================== + * 装備制限ファイル読み出し + *------------------------------------------ + */ +static int itemdb_read_noequip(void) +{ + FILE *fp; + char line[1024]; + int ln=0; + int nameid,j; + char *str[32],*p; + struct item_data *id; + + if( (fp=fopen("db/item_noequip.txt","r"))==NULL ){ + printf("can't read db/item_noequip.txt\n"); + return -1; + } + while(fgets(line,1020,fp)){ + if(line[0]=='/' && line[1]=='/') + continue; + memset(str,0,sizeof(str)); + for(j=0,p=line;j<2 && p;j++){ + str[j]=p; + p=strchr(p,','); + if(p) *p++=0; + } + if(str[0]==NULL) + continue; + + nameid=atoi(str[0]); + if(nameid<=0 || nameid>=20000 || !(id=itemdb_exists(nameid))) + continue; + + id->flag.no_equip=atoi(str[1]); + + ln++; + + } + fclose(fp); + printf("read db/item_noequip.txt done (count=%d)\n",ln); + return 0; +} +#ifndef TXT_ONLY + +/*====================================== +* SQL +*=================================== +*/ +static int itemdb_read_sqldb(void) +{ + unsigned short nameid; + struct item_data *id; + char script[65535 + 2 + 1]; // Maximum length of MySQL TEXT type (65535) + 2 bytes for curly brackets + 1 byte for terminator + + // ---------- + + sprintf(tmp_sql, "SELECT * FROM `%s`", item_db_db); + + // Execute the query; if the query execution succeeded... + if (mysql_query(&mmysql_handle, tmp_sql) == 0) + { + sql_res = mysql_store_result(&mmysql_handle); + + // If the storage of the query result succeeded... + if (sql_res) + { + // Parse each row in the query result into sql_row + while ((sql_row = mysql_fetch_row(sql_res))) + { + /* +----+--------------+---------------+------+-----------+------------+--------+--------+---------+-------+-------+------------+---------------+-----------------+--------------+-------------+------+------------+--------------+ + | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | + +----+--------------+---------------+------+-----------+------------+--------+--------+---------+-------+-------+------------+---------------+-----------------+--------------+-------------+------+------------+--------------+ + | id | name_english | name_japanese | type | price_buy | price_sell | weight | attack | defence | range | slots | equip_jobs | equip_genders | equip_locations | weapon_level | equip_level | view | script_use | script_equip | + +----+--------------+---------------+------+-----------+------------+--------+--------+---------+-------+-------+------------+---------------+-----------------+--------------+-------------+------+------------+--------------+ */ + + nameid = atoi(sql_row[0]); + + // If the identifier is not within the valid range, process the next row + if (nameid == 0 || nameid >= 20000) + { + continue; + } + + // Insert a new row into the item database + + /*id = calloc(sizeof(struct item_data), 1); + + if (id == NULL) + { + printf("out of memory : itemdb_read_sqldb\n"); + exit(1); + } + + memset(id, 0, sizeof(struct item_data)); + numdb_insert(item_db, (int) nameid, id);*/ + + // ---------- + id=itemdb_search(nameid); + + memcpy(id->name, sql_row[1], 25); + memcpy(id->jname, sql_row[2], 25); + + id->type = atoi(sql_row[3]); + + // If price_buy is not NULL and price_sell is not NULL... + if ((sql_row[4] != NULL) && (sql_row[5] != NULL)) + { + id->value_buy = atoi(sql_row[4]); + id->value_sell = atoi(sql_row[5]); + } + // If price_buy is not NULL and price_sell is NULL... + else if ((sql_row[4] != NULL) && (sql_row[5] == NULL)) + { + id->value_buy = atoi(sql_row[4]); + id->value_sell = atoi(sql_row[4]) / 2; + } + // If price_buy is NULL and price_sell is not NULL... + else if ((sql_row[4] == NULL) && (sql_row[5] != NULL)) + { + id->value_buy = atoi(sql_row[5]) * 2; + id->value_sell = atoi(sql_row[5]); + } + // If price_buy is NULL and price_sell is NULL... + if ((sql_row[4] == NULL) && (sql_row[5] == NULL)) + { + id->value_buy = 0; + id->value_sell = 0; + } + + id->weight = atoi(sql_row[6]); + + id->atk = (sql_row[7] != NULL) ? atoi(sql_row[7]) : 0; + id->def = (sql_row[8] != NULL) ? atoi(sql_row[8]) : 0; + id->range = (sql_row[9] != NULL) ? atoi(sql_row[9]) : 0; + id->slot = (sql_row[10] != NULL) ? atoi(sql_row[10]) : 0; + id->class = (sql_row[11] != NULL) ? atoi(sql_row[11]) : 0; + id->sex = (sql_row[12] != NULL) ? atoi(sql_row[12]) : 0; + id->equip = (sql_row[13] != NULL) ? atoi(sql_row[13]) : 0; + id->wlv = (sql_row[14] != NULL) ? atoi(sql_row[14]) : 0; + id->elv = (sql_row[15] != NULL) ? atoi(sql_row[15]) : 0; + id->look = (sql_row[16] != NULL) ? atoi(sql_row[16]) : 0; + + id->view_id = 0; + + // ---------- + + if (sql_row[17] != NULL) + { + if (sql_row[17][0] == '{') + id->use_script = parse_script(sql_row[17], 0); + else { + sprintf(script, "{%s}", sql_row[17]); + id->use_script = parse_script(script, 0); + } + } + else + { + id->use_script = NULL; + } + + if (sql_row[18] != NULL) + { + if (sql_row[18][0] == '{') + id->equip_script = parse_script(sql_row[18], 0); + else { + sprintf(script, "{%s}", sql_row[18]); + id->equip_script = parse_script(script, 0); + } + } + else + { + id->equip_script = NULL; + } + + // ---------- + + id->flag.available = 1; + id->flag.value_notdc = 0; + id->flag.value_notoc = 0; + } + + // If the retrieval failed, output an error + if (mysql_errno(&mmysql_handle)) + { + printf("Database server error (retrieving rows from %s): %s\n", item_db_db, mysql_error(&mmysql_handle)); + } + + printf("read %s done (count = %lu)\n", item_db_db, (unsigned long) mysql_num_rows(sql_res)); + } + else + { + printf("MySQL error (storing query result for %s): %s\n", item_db_db, mysql_error(&mmysql_handle)); + } + + // Free the query result + mysql_free_result(sql_res); + } + else + { + printf("Database server error (executing query for %s): %s\n", item_db_db, mysql_error(&mmysql_handle)); + } + + return 0; +} + +#endif /* not TXT_ONLY */ +/*========================================== + * + *------------------------------------------ + */ +static int itemdb_final(void *key,void *data,va_list ap) +{ + struct item_data *id; + + nullpo_retr(0, id=data); + + if(id->use_script) + free(id->use_script); + if(id->equip_script) + free(id->equip_script); + free(id); + + return 0; +} + +void itemdb_reload(void) +{ + /* + + <empty item databases> + itemdb_read(); + + */ + + do_init_itemdb(); +} + +/*========================================== + * + *------------------------------------------ + */ +void do_final_itemdb(void) +{ + if(item_db){ + numdb_final(item_db,itemdb_final); + item_db=NULL; + } +} + +/* +static FILE *dfp; +static int itemdebug(void *key,void *data,va_list ap){ +// struct item_data *id=(struct item_data *)data; + fprintf(dfp,"%6d",(int)key); + return 0; +} +void itemdebugtxt() +{ + dfp=fopen("itemdebug.txt","wt"); + numdb_foreach(item_db,itemdebug); + fclose(dfp); +} +*/ +#ifdef TXT_ONLY +/*==================================== + * Removed item_value_db, don't re-add + *------------------------------------ + */ +static void itemdb_read(void) +{ + itemdb_read_itemslottable(); + itemdb_readdb(); + itemdb_read_randomitem(); + itemdb_read_itemavail(); + itemdb_read_noequip(); + itemdb_read_cardillustnametable(); + if (!battle_config.item_name_override_grffile) + itemdb_read_itemnametable(); +} +#endif /* TXT_ONLY */ +/*========================================== + * + *------------------------------------------ + */ +int do_init_itemdb(void) +{ + item_db = numdb_init(); + + itemdb_read(); + + return 0; +} diff --git a/misc/src/map/itemdb.h b/misc/src/map/itemdb.h new file mode 100644 index 0000000..0edfad2 --- /dev/null +++ b/misc/src/map/itemdb.h @@ -0,0 +1,84 @@ +// $Id: itemdb.h,v 1.3 2004/09/25 05:32:18 MouseJstr Exp $ +#ifndef _ITEMDB_H_ +#define _ITEMDB_H_ + +#include "map.h" + +struct item_data { + int nameid; + char name[24],jname[24]; + char prefix[24],suffix[24]; + char cardillustname[64]; + int value_buy; + int value_sell; + int type; + int class; + int sex; + int equip; + int weight; + int atk; + int def; + int range; + int slot; + int look; + int elv; + int wlv; + int refine; + char *use_script; // 回復とかも全部この中でやろうかなと + char *equip_script; // 攻撃,防御の属性設定もこの中で可能かな? + struct { + unsigned available : 1; + unsigned value_notdc : 1; + unsigned value_notoc : 1; + unsigned no_equip : 3; + unsigned no_drop : 1; + unsigned no_use : 1; + } flag; + int view_id; +}; + +struct random_item_data { + int nameid; + int per; +}; + +struct item_data* itemdb_searchname(const char *name); +struct item_data* itemdb_search(int nameid); +struct item_data* itemdb_exists(int nameid); +#define itemdb_type(n) itemdb_search(n)->type +#define itemdb_atk(n) itemdb_search(n)->atk +#define itemdb_def(n) itemdb_search(n)->def +#define itemdb_look(n) itemdb_search(n)->look +#define itemdb_weight(n) itemdb_search(n)->weight +#define itemdb_equip(n) itemdb_search(n)->equip +#define itemdb_usescript(n) itemdb_search(n)->use_script +#define itemdb_equipscript(n) itemdb_search(n)->equip_script +#define itemdb_wlv(n) itemdb_search(n)->wlv +#define itemdb_range(n) itemdb_search(n)->range +#define itemdb_slot(n) itemdb_search(n)->slot +#define itemdb_available(n) (itemdb_exists(n) && itemdb_search(n)->flag.available) +#define itemdb_viewid(n) (itemdb_search(n)->view_id) + +int itemdb_searchrandomid(int flags); + +#define itemdb_value_buy(n) itemdb_search(n)->value_buy +#define itemdb_value_sell(n) itemdb_search(n)->value_sell +#define itemdb_value_notdc(n) itemdb_search(n)->flag.value_notdc +#define itemdb_value_notoc(n) itemdb_search(n)->flag.value_notoc + +int itemdb_isequip(int); +int itemdb_isequip2(struct item_data *); +int itemdb_isequip3(int); +int itemdb_isdropable(int nameid); + +// itemdb_equipマクロとitemdb_equippointとの違いは +// 前者が鯖側dbで定義された値そのものを返すのに対し +// 後者はsessiondataを考慮した鞍側での装備可能場所 +// すべての組み合わせを返す + +void itemdb_reload(void); + +void do_final_itemdb(void); +int do_init_itemdb(void); + +#endif diff --git a/misc/src/map/mail.c b/misc/src/map/mail.c new file mode 100644 index 0000000..e50b1ba --- /dev/null +++ b/misc/src/map/mail.c @@ -0,0 +1,324 @@ +// Mail System for eAthena SQL +// Created by Valaris + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "socket.h" +#include "timer.h" +#include "nullpo.h" + +#include "map.h" +#include "clif.h" +#include "chrif.h" +#include "intif.h" +#include "pc.h" +#include "mail.h" + +char mail_db[32] = "mail"; + +int MAIL_CHECK_TIME = 120000; +int mail_timer; + +#ifdef MEMWATCH +#include "memwatch.h" +#endif + +int mail_check(struct map_session_data *sd,int type) +{ + int i=0,new=0,priority=0; + char message[50]; + + if(sd==NULL) + return 0; + + sprintf(tmp_msql,"SELECT `message_id`,`to_account_id`,`from_char_name`,`read_flag`,`priority`,`check_flag` FROM `%s` WHERE `to_account_id` = \"%d\" ORDER by `message_id`", mail_db, sd->status.account_id); + + if (mysql_query(&mail_handle, tmp_msql)) { + printf("Database server error (executing query for %s): %s\n", mail_db, mysql_error(&mail_handle)); + return 0; + } + + mail_res = mysql_store_result(&mail_handle); + if(mail_res) { + if (mysql_num_rows(mail_res) == 0) { + clif_displaymessage(sd->fd,"You have no messages."); + mysql_free_result(mail_res); + return 0; + } + + while ((mail_row = mysql_fetch_row(mail_res))) { + i++; + + if(!atoi(mail_row[5])) { + sprintf(tmp_msql,"UPDATE `%s` SET `check_flag`='1' WHERE `message_id`= \"%d\"", mail_db, atoi(mail_row[0])); + if(mysql_query(&mail_handle, tmp_msql) ) { + printf("DB server Error (update Read `%s`)- %s\n", mail_db, mysql_error(&mail_handle) ); + } + } + + if(!atoi(mail_row[3])) { + new++; + if(atoi(mail_row[4])) + priority++; + if(type==2 || type==3) { + if(atoi(mail_row[4])) { + sprintf(message, "%d - From : %s (New - Priority)", i, mail_row[2]); + clif_displaymessage(sd->fd, message); + } + + else { + sprintf(message, "%d - From : %s (New)", i, mail_row[2]); + clif_displaymessage(sd->fd, message); + } + } + } + + else if(type==2){ + sprintf(message, "%d - From : %s", i, mail_row[2]); + clif_displaymessage(sd->fd, message); + } + + } + + mysql_free_result(mail_res); + + } else { + printf("MySQL error (storing query result for %s): %s\n", mail_db, mysql_error(&mail_handle)); + return 0; + } + + if(i>0 && new>0 && type==1) { + sprintf(message, "You have %d new messages.", new); + clif_displaymessage(sd->fd, message); + } + if(i>0 && new>0 && priority>0 && type==1) { + sprintf(message, "You have %d unread priority messages.", priority); + clif_displaymessage(sd->fd, message); + } + if(!new) { + clif_displaymessage(sd->fd, "You have no new messages."); + } + + return 0; +} + +int mail_read(struct map_session_data *sd, int message_id) +{ + + char message[80]; + + if(sd==NULL) + return 0; + + sprintf(tmp_msql,"SELECT `message_id`,`to_account_id`,`from_char_name`,`message`,`read_flag`,`priority`,`check_flag` from `%s` WHERE `to_account_id` = \"%d\" ORDER by `message_id` LIMIT %d, 1",mail_db,sd->status.account_id,message_id-1); + + if (mysql_query(&mail_handle, tmp_msql)) { + printf("Database server error (executing query for %s): %s\n", mail_db, mysql_error(&mail_handle)); + return 0; + } + + mail_res = mysql_store_result(&mail_handle); + if(mail_res) { + if (mysql_num_rows(mail_res) == 0) { + mysql_free_result(mail_res); + clif_displaymessage(sd->fd, "Message not found."); + return 0; + } + + if ((mail_row = mysql_fetch_row(mail_res))) { + + if(!atoi(mail_row[6])) { + sprintf(tmp_msql,"UPDATE `%s` SET `check_flag`='1' WHERE `message_id`= \"%d\"", mail_db, atoi(mail_row[0])); + if(mysql_query(&mail_handle, tmp_msql) ) { + printf("DB server Error (update Read `%s`)- %s\n", mail_db, mysql_error(&mail_handle) ); + } + } + + sprintf(message, "Reading message from %s", mail_row[2]); + clif_displaymessage(sd->fd, message); + + sprintf(message, "%s", mail_row[3]); + clif_displaymessage(sd->fd, message); + + sprintf(tmp_msql,"UPDATE `%s` SET `read_flag`='1' WHERE `message_id`= \"%d\"", mail_db, atoi(mail_row[0])); + if(mysql_query(&mail_handle, tmp_msql) ) { + printf("DB server Error (update Read `%s`)- %s\n", mail_db, mysql_error(&mail_handle) ); + } + } + + mysql_free_result(mail_res); + + } else { + printf("MySQL error (storing query result for %s): %s\n", mail_db, mysql_error(&mail_handle)); + return 0; + } + + return 0; +} + +int mail_delete(struct map_session_data *sd, int message_id) +{ + if(sd==NULL) + return 0; + + sprintf(tmp_msql,"SELECT `message_id`,`to_account_id`,`read_flag`,`priority`,`check_flag` from `%s` WHERE `to_account_id` = \"%d\" ORDER by `message_id` LIMIT %d, 1",mail_db,sd->status.account_id,message_id-1); + + if (mysql_query(&mail_handle, tmp_msql)) { + printf("Database server error (executing query for %s): %s\n", mail_db, mysql_error(&mail_handle)); + return 0; + } + + mail_res = mysql_store_result(&mail_handle); + if(mail_res) { + if (mysql_num_rows(mail_res) == 0) { + mysql_free_result(mail_res); + clif_displaymessage(sd->fd, "Message not found."); + return 0; + } + + if ((mail_row = mysql_fetch_row(mail_res))) { + if(!atoi(mail_row[2]) && atoi(mail_row[3])) { + mysql_free_result(mail_res); + clif_displaymessage(sd->fd,"Cannot delete unread priority mail."); + return 0; + } + if(!atoi(mail_row[4])) { + mysql_free_result(mail_res); + clif_displaymessage(sd->fd,"You have recieved new mail, use @listmail before deleting."); + return 0; + } + sprintf(tmp_msql,"DELETE FROM `%s` WHERE `message_id` = \"%d\"", mail_db, atoi(mail_row[0])); + if(mysql_query(&mail_handle, tmp_msql) ) { + mysql_free_result(mail_res); + printf("DB server Error (update Read `%s`)- %s\n", mail_db, mysql_error(&mail_handle) ); + return 0; + } + else clif_displaymessage(sd->fd,"Message deleted."); + } + + mysql_free_result(mail_res); + + } else { + printf("MySQL error (delete query result for %s): %s\n", mail_db, mysql_error(&mail_handle)); + return 0; + } + + return 0; +} + +int mail_send(struct map_session_data *sd, char *name, char *message, int flag) +{ + if(sd==NULL) + return 0; + + if(pc_isGM(sd) < 80 && sd->mail_counter > 0) { + clif_displaymessage(sd->fd,"You must wait 10 minutes before sending another message"); + return 0; + } + + if(strcmp(name,"*")==0) { + if(pc_isGM(sd) < 80) { + clif_displaymessage(sd->fd, "Access Denied."); + return 0; + } + else + sprintf(tmp_msql,"SELECT DISTINCT `account_id` FROM `%s` WHERE `account_id` <> '%d' ORDER BY `account_id`", char_db, sd->status.account_id); + } + else + sprintf(tmp_msql,"SELECT `account_id`,`name` FROM `%s` WHERE `name` = \"%s\"", char_db, name); + + if (mysql_query(&mail_handle, tmp_msql)) { + printf("Database server error (executing query for %s): %s\n", char_db, mysql_error(&mail_handle)); + return 0; + } + + mail_res = mysql_store_result(&mail_handle); + if(mail_res) { + if (mysql_num_rows(mail_res) == 0) { + mysql_free_result(mail_res); + clif_displaymessage(sd->fd,"Character does not exist."); + return 0; + } + + while ((mail_row = mysql_fetch_row(mail_res))) { + if(strcmp(name,"*")==0) { + sprintf(tmp_msql, "INSERT INTO `%s` (`to_account_id`,`from_account_id`,`from_char_name`,`message`,`priority`)" + " VALUES ('%d', '%d', '%s', '%s', '%d')",mail_db, atoi(mail_row[0]), sd->status.account_id, sd->status.name, message, flag); + } + else { + sprintf(tmp_msql, "INSERT INTO `%s` (`to_account_id`,`to_char_name`,`from_account_id`,`from_char_name`,`message`,`priority`)" + " VALUES ('%d', '%s', '%d', '%s', '%s', '%d')",mail_db, atoi(mail_row[0]), mail_row[1], sd->status.account_id, sd->status.name, message, flag); + if(pc_isGM(sd) < 80) + sd->mail_counter=5; + } + + if(mysql_query(&mail_handle, tmp_msql) ) { + mysql_free_result(mail_res); + printf("DB server Error (insert `mail_db`)- %s\n", mysql_error(&mail_handle) ); + return 0; + } + + } + } + + clif_displaymessage(sd->fd,"Mail has been sent."); + + return 0; +} + +int mail_check_timer(int tid,unsigned int tick,int id,int data) +{ + if(mail_timer != tid) + return 0; + + sprintf(tmp_msql,"SELECT DISTINCT `to_account_id` FROM `%s` WHERE `read_flag` = '0' AND `check_flag` = '0'", mail_db); + + if (mysql_query(&mail_handle, tmp_msql)) { + printf("Database server error (executing query for %s): %s\n", char_db, mysql_error(&mail_handle)); + mail_timer=add_timer(gettick()+MAIL_CHECK_TIME,mail_check_timer,0,0); + return 0; + } + + struct map_session_data *sd = NULL; + int i; + + mail_res = mysql_store_result(&mail_handle); + + if (mail_res) { + + if (mysql_num_rows(mail_res) == 0) { + mysql_free_result(mail_res); + mail_timer=add_timer(gettick()+MAIL_CHECK_TIME,mail_check_timer,0,0); + return 0; + } + + while ((mail_row = mysql_fetch_row(mail_res))) { + for (i = 0; i < fd_max; i++) { + if (session[i] && (sd = session[i]->session_data) && sd->state.auth){ + if(pc_isGM(sd) < 80 && sd->mail_counter > 0) + sd->mail_counter--; + if(sd->status.account_id==atoi(mail_row[0])) + clif_displaymessage(sd->fd, "You have new mail."); + } + } + } + } + + sprintf(tmp_msql,"UPDATE `%s` SET `check_flag`='1' WHERE `check_flag`= '0' ", mail_db); + if(mysql_query(&mail_handle, tmp_msql) ) { + printf("DB server Error (update Read `%s`)- %s\n", mail_db, mysql_error(&mail_handle) ); + } + + mail_timer=add_timer(gettick()+MAIL_CHECK_TIME,mail_check_timer,0,0); + return 0; +} + +int do_init_mail(void) +{ + add_timer_func_list(mail_check_timer,"mail_check_timer"); + mail_timer=add_timer(gettick()+MAIL_CHECK_TIME,mail_check_timer,0,0); + return 0; +} + diff --git a/misc/src/map/mail.h b/misc/src/map/mail.h new file mode 100644 index 0000000..6bd8e51 --- /dev/null +++ b/misc/src/map/mail.h @@ -0,0 +1,9 @@ +// Mail System for eAthena +// Created by Valaris + +int mail_check(struct map_session_data *sd, int type); +int mail_read(struct map_session_data *sd, int message_id); +int mail_delete(struct map_session_data *sd, int message_id); +int mail_send(struct map_session_data *sd, char *name, char *message, int flag); + +int do_init_mail(void); diff --git a/misc/src/map/map.c b/misc/src/map/map.c new file mode 100644 index 0000000..040e180 --- /dev/null +++ b/misc/src/map/map.c @@ -0,0 +1,2009 @@ +// $Id: map.c,v 1.6 2004/09/25 17:37:01 MouseJstr Exp $ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdarg.h> +#ifdef LCCWIN32 +#include <winsock.h> +#else +#include <netdb.h> +#endif + +#include "core.h" +#include "timer.h" +#include "db.h" +#include "grfio.h" +#include "malloc.h" + +#include "map.h" +#include "chrif.h" +#include "clif.h" +#include "intif.h" +#include "npc.h" +#include "pc.h" +#include "mob.h" +#include "chat.h" +#include "itemdb.h" +#include "storage.h" +#include "skill.h" +#include "trade.h" +#include "party.h" +#include "battle.h" +#include "script.h" +#include "guild.h" +#include "pet.h" +#include "atcommand.h" +#include "nullpo.h" +#include "socket.h" + +#ifdef MEMWATCH +#include "memwatch.h" +#endif + +#ifndef TXT_ONLY + +#include "mail.h" // mail system [Valaris] + +MYSQL mmysql_handle; +MYSQL_RES* sql_res ; +MYSQL_ROW sql_row ; +char tmp_sql[65535]=""; + +MYSQL lmysql_handle; +MYSQL_RES* lsql_res ; +MYSQL_ROW lsql_row ; +char tmp_lsql[65535]=""; + +MYSQL mail_handle; // mail system [Valaris] +MYSQL_RES* mail_res ; +MYSQL_ROW mail_row ; +char tmp_msql[65535]=""; + +int map_server_port = 3306; +char map_server_ip[16] = "127.0.0.1"; +char map_server_id[32] = "ragnarok"; +char map_server_pw[32] = "ragnarok"; +char map_server_db[32] = "ragnarok"; +int db_use_sqldbs = 0; + +int login_server_port = 3306; +char login_server_ip[16] = "127.0.0.1"; +char login_server_id[32] = "ragnarok"; +char login_server_pw[32] = "ragnarok"; +char login_server_db[32] = "ragnarok"; + +char item_db_db[32] = "item_db"; +char mob_db_db[32] = "mob_db"; +char login_db[32] = "login"; +char login_db_level[32] = "level"; +char login_db_account_id[32] = "account_id"; + +int lowest_gm_level = 1; +int read_gm_interval = 600000; + +char char_db[32] = "char"; + +static int online_timer(int,unsigned int,int,int); + +int CHECK_INTERVAL = 3600000; // [Valaris] +int check_online_timer=0; // [Valaris] + +#endif /* not TXT_ONLY */ +// 極力 staticでローカルに収める +static struct dbt * id_db=NULL; +static struct dbt * map_db=NULL; +static struct dbt * nick_db=NULL; +static struct dbt * charid_db=NULL; + +static int users=0; +static struct block_list *object[MAX_FLOORITEM]; +static int first_free_object_id=0,last_object_id=0; + +#define block_free_max 1048576 +static void *block_free[block_free_max]; +static int block_free_count = 0, block_free_lock = 0; + +#define BL_LIST_MAX 1048576 +static struct block_list *bl_list[BL_LIST_MAX]; +static int bl_list_count = 0; + +struct map_data map[MAX_MAP_PER_SERVER]; +int map_num = 0; + +int map_port=0; + +int autosave_interval = DEFAULT_AUTOSAVE_INTERVAL; +int agit_flag = 0; +int night_flag = 0; // 0=day, 1=night [Yor] + +struct charid2nick { + char nick[24]; + int req_id; +}; + +char motd_txt[256] = "conf/motd.txt"; +char help_txt[256] = "conf/help.txt"; + +char wisp_server_name[24] = "Server"; // can be modified in char-server configuration file + + +/*========================================== + * 全map鯖総計での接続数設定 + * (char鯖から送られてくる) + *------------------------------------------ + */ +void map_setusers(int n) { + users = n; +} + +/*========================================== + * 全map鯖総計での接続数取得 (/wへの応答用) + *------------------------------------------ + */ +int map_getusers(void) { + return users; +} + +// +// block削除の安全性確保処理 +// + +/*========================================== + * blockをfreeするときfreeの変わりに呼ぶ + * ロックされているときはバッファにためる + *------------------------------------------ + */ +int map_freeblock( void *bl ) +{ + if(block_free_lock==0){ + free(bl); + bl = NULL; + } + else{ + if( block_free_count>=block_free_max ) { + if(battle_config.error_log) + printf("map_freeblock: *WARNING* too many free block! %d %d\n", + block_free_count,block_free_lock); + } + else + block_free[block_free_count++]=bl; + } + return block_free_lock; +} +/*========================================== + * blockのfreeを一時的に禁止する + *------------------------------------------ + */ +int map_freeblock_lock(void) { + return ++block_free_lock; +} + +/*========================================== + * blockのfreeのロックを解除する + * このとき、ロックが完全になくなると + * バッファにたまっていたblockを全部削除 + *------------------------------------------ + */ +int map_freeblock_unlock(void) { + if ((--block_free_lock) == 0) { + int i; +// if(block_free_count>0) { +// if(battle_config.error_log) +// printf("map_freeblock_unlock: free %d object\n",block_free_count); +// } + for(i=0;i<block_free_count;i++){ + free(block_free[i]); + block_free[i] = NULL; + } + block_free_count=0; + }else if(block_free_lock<0){ + if(battle_config.error_log) + printf("map_freeblock_unlock: lock count < 0 !\n"); + } + return block_free_lock; +} + + +// +// block化処理 +// +/*========================================== + * map[]のblock_listから繋がっている場合に + * bl->prevにbl_headのアドレスを入れておく + *------------------------------------------ + */ +static struct block_list bl_head; + +/*========================================== + * map[]のblock_listに追加 + * mobは数が多いので別リスト + * + * 既にlink済みかの確認が無い。危険かも + *------------------------------------------ + */ +int map_addblock(struct block_list *bl) +{ + int m,x,y; + + nullpo_retr(0, bl); + + if(bl->prev != NULL){ + if(battle_config.error_log) + printf("map_addblock error : bl->prev!=NULL\n"); + return 0; + } + + m=bl->m; + x=bl->x; + y=bl->y; + if(m<0 || m>=map_num || + x<0 || x>=map[m].xs || + y<0 || y>=map[m].ys) + return 1; + + if(bl->type==BL_MOB){ + bl->next = map[m].block_mob[x/BLOCK_SIZE+(y/BLOCK_SIZE)*map[m].bxs]; + bl->prev = &bl_head; + if(bl->next) bl->next->prev = bl; + map[m].block_mob[x/BLOCK_SIZE+(y/BLOCK_SIZE)*map[m].bxs] = bl; + map[m].block_mob_count[x/BLOCK_SIZE+(y/BLOCK_SIZE)*map[m].bxs]++; + } else { + bl->next = map[m].block[x/BLOCK_SIZE+(y/BLOCK_SIZE)*map[m].bxs]; + bl->prev = &bl_head; + if(bl->next) bl->next->prev = bl; + map[m].block[x/BLOCK_SIZE+(y/BLOCK_SIZE)*map[m].bxs] = bl; + map[m].block_count[x/BLOCK_SIZE+(y/BLOCK_SIZE)*map[m].bxs]++; + if(bl->type==BL_PC) + map[m].users++; + } + + return 0; +} + +/*========================================== + * map[]のblock_listから外す + * prevがNULLの場合listに繋がってない + *------------------------------------------ + */ +int map_delblock(struct block_list *bl) +{ + int b; + nullpo_retr(0, bl); + + // 既にblocklistから抜けている + if(bl->prev==NULL){ + if(bl->next!=NULL){ + // prevがNULLでnextがNULLでないのは有ってはならない + if(battle_config.error_log) + printf("map_delblock error : bl->next!=NULL\n"); + } + return 0; + } + + b = bl->x/BLOCK_SIZE+(bl->y/BLOCK_SIZE)*map[bl->m].bxs; + + if(bl->type==BL_PC) + map[bl->m].users--; + if(bl->next) bl->next->prev = bl->prev; + if(bl->prev==&bl_head){ + // リストの頭なので、map[]のblock_listを更新する + if(bl->type==BL_MOB){ + map[bl->m].block_mob[b] = bl->next; + if((map[bl->m].block_mob_count[b]--) < 0) + map[bl->m].block_mob_count[b] = 0; + } else { + map[bl->m].block[b] = bl->next; + if((map[bl->m].block_count[b]--) < 0) + map[bl->m].block_count[b] = 0; + } + } else { + bl->prev->next = bl->next; + } + bl->next = NULL; + bl->prev = NULL; + + return 0; +} + +/*========================================== + * 周囲のPC人数を数える (現在未使用) + *------------------------------------------ + */ +int map_countnearpc(int m, int x, int y) { + int bx,by,c=0; + struct block_list *bl=NULL; + + if(map[m].users==0) + return 0; + for(by=y/BLOCK_SIZE-AREA_SIZE/BLOCK_SIZE-1;by<=y/BLOCK_SIZE+AREA_SIZE/BLOCK_SIZE+1;by++){ + if(by<0 || by>=map[m].bys) + continue; + for(bx=x/BLOCK_SIZE-AREA_SIZE/BLOCK_SIZE-1;bx<=x/BLOCK_SIZE+AREA_SIZE/BLOCK_SIZE+1;bx++){ + if(bx<0 || bx>=map[m].bxs) + continue; + bl = map[m].block[bx+by*map[m].bxs]; + for(;bl;bl=bl->next){ + if(bl->type==BL_PC) + c++; + } + } + } + return c; +} + +/*========================================== + * セル上のPCとMOBの数を数える (グランドクロス用) + *------------------------------------------ + */ +int map_count_oncell(int m, int x, int y) { + int bx,by; + struct block_list *bl=NULL; + int i,c; + int count = 0; + + if (x < 0 || y < 0 || (x >= map[m].xs) || (y >= map[m].ys)) + return 1; + bx = x/BLOCK_SIZE; + by = y/BLOCK_SIZE; + + bl = map[m].block[bx+by*map[m].bxs]; + c = map[m].block_count[bx+by*map[m].bxs]; + for(i=0;i<c && bl;i++,bl=bl->next){ + if(bl->x == x && bl->y == y && bl->type == BL_PC) count++; + } + bl = map[m].block_mob[bx+by*map[m].bxs]; + c = map[m].block_mob_count[bx+by*map[m].bxs]; + for(i=0;i<c && bl;i++,bl=bl->next){ + if(bl->x == x && bl->y == y) count++; + } + if(!count) count = 1; + return count; +} + + +/*========================================== + * map m (x0,y0)-(x1,y1)内の全objに対して + * funcを呼ぶ + * type!=0 ならその種類のみ + *------------------------------------------ + */ +void map_foreachinarea(int (*func)(struct block_list*,va_list),int m,int x0,int y0,int x1,int y1,int type,...) { + int bx,by; + struct block_list *bl=NULL; + va_list ap=NULL; + int blockcount=bl_list_count,i,c; + + if(m < 0) + return; + va_start(ap,type); + if (x0 < 0) x0 = 0; + if (y0 < 0) y0 = 0; + if (x1 >= map[m].xs) x1 = map[m].xs-1; + if (y1 >= map[m].ys) y1 = map[m].ys-1; + if (type == 0 || type != BL_MOB) + for (by = y0 / BLOCK_SIZE; by <= y1 / BLOCK_SIZE; by++) { + for(bx=x0/BLOCK_SIZE;bx<=x1/BLOCK_SIZE;bx++){ + bl = map[m].block[bx+by*map[m].bxs]; + c = map[m].block_count[bx+by*map[m].bxs]; + for(i=0;i<c && bl;i++,bl=bl->next){ + if(bl && type && bl->type!=type) + continue; + if(bl && bl->x>=x0 && bl->x<=x1 && bl->y>=y0 && bl->y<=y1 && bl_list_count<BL_LIST_MAX) + bl_list[bl_list_count++]=bl; + } + } + } + if(type==0 || type==BL_MOB) + for(by=y0/BLOCK_SIZE;by<=y1/BLOCK_SIZE;by++){ + for(bx=x0/BLOCK_SIZE;bx<=x1/BLOCK_SIZE;bx++){ + bl = map[m].block_mob[bx+by*map[m].bxs]; + c = map[m].block_mob_count[bx+by*map[m].bxs]; + for(i=0;i<c && bl;i++,bl=bl->next){ + if(bl && bl->x>=x0 && bl->x<=x1 && bl->y>=y0 && bl->y<=y1 && bl_list_count<BL_LIST_MAX) + bl_list[bl_list_count++]=bl; + } + } + } + + if(bl_list_count>=BL_LIST_MAX) { + if(battle_config.error_log) + printf("map_foreachinarea: *WARNING* block count too many!\n"); + } + + map_freeblock_lock(); // メモリからの解放を禁止する + + for(i=blockcount;i<bl_list_count;i++) + if(bl_list[i]->prev) // 有効かどうかチェック + func(bl_list[i],ap); + + map_freeblock_unlock(); // 解放を許可する + + va_end(ap); + bl_list_count = blockcount; +} + +/*========================================== + * 矩形(x0,y0)-(x1,y1)が(dx,dy)移動した時の + * 領域外になる領域(矩形かL字形)内のobjに + * 対してfuncを呼ぶ + * + * dx,dyは-1,0,1のみとする(どんな値でもいいっぽい?) + *------------------------------------------ + */ +void map_foreachinmovearea(int (*func)(struct block_list*,va_list),int m,int x0,int y0,int x1,int y1,int dx,int dy,int type,...) { + int bx,by; + struct block_list *bl=NULL; + va_list ap=NULL; + int blockcount=bl_list_count,i,c; + + va_start(ap,type); + if(dx==0 || dy==0){ + // 矩形領域の場合 + if(dx==0){ + if(dy<0){ + y0=y1+dy+1; + } else { + y1=y0+dy-1; + } + } else if(dy==0){ + if(dx<0){ + x0=x1+dx+1; + } else { + x1=x0+dx-1; + } + } + if(x0<0) x0=0; + if(y0<0) y0=0; + if(x1>=map[m].xs) x1=map[m].xs-1; + if(y1>=map[m].ys) y1=map[m].ys-1; + for(by=y0/BLOCK_SIZE;by<=y1/BLOCK_SIZE;by++){ + for(bx=x0/BLOCK_SIZE;bx<=x1/BLOCK_SIZE;bx++){ + bl = map[m].block[bx+by*map[m].bxs]; + c = map[m].block_count[bx+by*map[m].bxs]; + for(i=0;i<c && bl;i++,bl=bl->next){ + if(bl && type && bl->type!=type) + continue; + if(bl && bl->x>=x0 && bl->x<=x1 && bl->y>=y0 && bl->y<=y1 && bl_list_count<BL_LIST_MAX) + bl_list[bl_list_count++]=bl; + } + bl = map[m].block_mob[bx+by*map[m].bxs]; + c = map[m].block_mob_count[bx+by*map[m].bxs]; + for(i=0;i<c && bl;i++,bl=bl->next){ + if(bl && type && bl->type!=type) + continue; + if(bl && bl->x>=x0 && bl->x<=x1 && bl->y>=y0 && bl->y<=y1 && bl_list_count<BL_LIST_MAX) + bl_list[bl_list_count++]=bl; + } + } + } + }else{ + // L字領域の場合 + + if(x0<0) x0=0; + if(y0<0) y0=0; + if(x1>=map[m].xs) x1=map[m].xs-1; + if(y1>=map[m].ys) y1=map[m].ys-1; + for(by=y0/BLOCK_SIZE;by<=y1/BLOCK_SIZE;by++){ + for(bx=x0/BLOCK_SIZE;bx<=x1/BLOCK_SIZE;bx++){ + bl = map[m].block[bx+by*map[m].bxs]; + c = map[m].block_count[bx+by*map[m].bxs]; + for(i=0;i<c && bl;i++,bl=bl->next){ + if(bl && type && bl->type!=type) + continue; + if((bl) && !(bl->x>=x0 && bl->x<=x1 && bl->y>=y0 && bl->y<=y1)) + continue; + if((bl) && ((dx>0 && bl->x<x0+dx) || (dx<0 && bl->x>x1+dx) || + (dy>0 && bl->y<y0+dy) || (dy<0 && bl->y>y1+dy)) && + bl_list_count<BL_LIST_MAX) + bl_list[bl_list_count++]=bl; + } + bl = map[m].block_mob[bx+by*map[m].bxs]; + c = map[m].block_mob_count[bx+by*map[m].bxs]; + for(i=0;i<c && bl;i++,bl=bl->next){ + if(bl && type && bl->type!=type) + continue; + if((bl) && !(bl->x>=x0 && bl->x<=x1 && bl->y>=y0 && bl->y<=y1)) + continue; + if((bl) && ((dx>0 && bl->x<x0+dx) || (dx<0 && bl->x>x1+dx) || + (dy>0 && bl->y<y0+dy) || (dy<0 && bl->y>y1+dy)) && + bl_list_count<BL_LIST_MAX) + bl_list[bl_list_count++]=bl; + } + } + } + + } + + if(bl_list_count>=BL_LIST_MAX) { + if(battle_config.error_log) + printf("map_foreachinarea: *WARNING* block count too many!\n"); + } + + map_freeblock_lock(); // メモリからの解放を禁止する + + for(i=blockcount;i<bl_list_count;i++) + if(bl_list[i]->prev) // 有効かどうかチェック + func(bl_list[i],ap); + + map_freeblock_unlock(); // 解放を許可する + + va_end(ap); + bl_list_count = blockcount; +} + +// -- moonsoul (added map_foreachincell which is a rework of map_foreachinarea but +// which only checks the exact single x/y passed to it rather than an +// area radius - may be more useful in some instances) +// +void map_foreachincell(int (*func)(struct block_list*,va_list),int m,int x,int y,int type,...) { + int bx,by; + struct block_list *bl=NULL; + va_list ap=NULL; + int blockcount=bl_list_count,i,c; + + va_start(ap,type); + + by=y/BLOCK_SIZE; + bx=x/BLOCK_SIZE; + + if(type==0 || type!=BL_MOB) + { + bl = map[m].block[bx+by*map[m].bxs]; + c = map[m].block_count[bx+by*map[m].bxs]; + for(i=0;i<c && bl;i++,bl=bl->next) + { + if(type && bl && bl->type!=type) + continue; + if(bl && bl->x==x && bl->y==y && bl_list_count<BL_LIST_MAX) + bl_list[bl_list_count++]=bl; + } + } + + if(type==0 || type==BL_MOB) + { + bl = map[m].block_mob[bx+by*map[m].bxs]; + c = map[m].block_mob_count[bx+by*map[m].bxs]; + for(i=0;i<c && bl;i++,bl=bl->next) + { + if(bl && bl->x==x && bl->y==y && bl_list_count<BL_LIST_MAX) + bl_list[bl_list_count++]=bl; + } + } + + if(bl_list_count>=BL_LIST_MAX) { + if(battle_config.error_log) + printf("map_foreachincell: *WARNING* block count too many!\n"); + } + + map_freeblock_lock(); // メモリからの解放を禁止する + + for(i=blockcount;i<bl_list_count;i++) + if(bl_list[i]->prev) // 有効かどうかチェック + func(bl_list[i],ap); + + map_freeblock_unlock(); // 解放を許可する + + va_end(ap); + bl_list_count = blockcount; +} + +/*========================================== + * 床アイテムやエフェクト用の一時obj割り当て + * object[]への保存とid_db登録まで + * + * bl->idもこの中で設定して問題無い? + *------------------------------------------ + */ +int map_addobject(struct block_list *bl) { + int i; + if( bl == NULL ){ + printf("map_addobject nullpo?\n"); + return 0; + } + if(first_free_object_id<2 || first_free_object_id>=MAX_FLOORITEM) + first_free_object_id=2; + for(i=first_free_object_id;i<MAX_FLOORITEM;i++) + if(object[i]==NULL) + break; + if(i>=MAX_FLOORITEM){ + if(battle_config.error_log) + printf("no free object id\n"); + return 0; + } + first_free_object_id=i; + if(last_object_id<i) + last_object_id=i; + object[i]=bl; + numdb_insert(id_db,i,bl); + return i; +} + +/*========================================== + * 一時objectの解放 + * map_delobjectのfreeしないバージョン + *------------------------------------------ + */ +int map_delobjectnofree(int id) { + if(object[id]==NULL) + return 0; + + map_delblock(object[id]); + numdb_erase(id_db,id); +// map_freeblock(object[id]); + object[id]=NULL; + + if(first_free_object_id>id) + first_free_object_id=id; + + while(last_object_id>2 && object[last_object_id]==NULL) + last_object_id--; + + return 0; +} + +/*========================================== + * 一時objectの解放 + * block_listからの削除、id_dbからの削除 + * object dataのfree、object[]へのNULL代入 + * + * addとの対称性が無いのが気になる + *------------------------------------------ + */ +int map_delobject(int id) { + struct block_list *obj = object[id]; + + if(obj==NULL) + return 0; + + map_delobjectnofree(id); + map_freeblock(obj); + + return 0; +} + +/*========================================== + * 全一時obj相手にfuncを呼ぶ + * + *------------------------------------------ + */ +void map_foreachobject(int (*func)(struct block_list*,va_list),int type,...) { + int i; + int blockcount=bl_list_count; + va_list ap=NULL; + + va_start(ap,type); + + for(i=2;i<=last_object_id;i++){ + if(object[i]){ + if(type && object[i]->type!=type) + continue; + if(bl_list_count>=BL_LIST_MAX) { + if(battle_config.error_log) + printf("map_foreachobject: too many block !\n"); + } + else + bl_list[bl_list_count++]=object[i]; + } + } + + map_freeblock_lock(); + + for(i=blockcount;i<bl_list_count;i++) + if( bl_list[i]->prev || bl_list[i]->next ) + func(bl_list[i],ap); + + map_freeblock_unlock(); + + va_end(ap); + bl_list_count = blockcount; +} + +/*========================================== + * 床アイテムを消す + * + * data==0の時はtimerで消えた時 + * data!=0の時は拾う等で消えた時として動作 + * + * 後者は、map_clearflooritem(id)へ + * map.h内で#defineしてある + *------------------------------------------ + */ +int map_clearflooritem_timer(int tid,unsigned int tick,int id,int data) { + struct flooritem_data *fitem=NULL; + + fitem = (struct flooritem_data *)object[id]; + if(fitem==NULL || fitem->bl.type!=BL_ITEM || (!data && fitem->cleartimer != tid)){ + if(battle_config.error_log) + printf("map_clearflooritem_timer : error\n"); + return 1; + } + if(data) + delete_timer(fitem->cleartimer,map_clearflooritem_timer); + else if(fitem->item_data.card[0] == (short)0xff00) + intif_delete_petdata(*((long *)(&fitem->item_data.card[1]))); + clif_clearflooritem(fitem,0); + map_delobject(fitem->bl.id); + + return 0; +} + +/*========================================== + * (m,x,y)の周囲rangeマス内の空き(=侵入可能)cellの + * 内から適当なマス目の座標をx+(y<<16)で返す + * + * 現状range=1でアイテムドロップ用途のみ + *------------------------------------------ + */ +int map_searchrandfreecell(int m,int x,int y,int range) { + int free_cell,i,j,c; + + for(free_cell=0,i=-range;i<=range;i++){ + if(i+y<0 || i+y>=map[m].ys) + continue; + for(j=-range;j<=range;j++){ + if(j+x<0 || j+x>=map[m].xs) + continue; + if((c=read_gat(m,j+x,i+y))==1 || c==5) + continue; + free_cell++; + } + } + if(free_cell==0) + return -1; + free_cell=rand()%free_cell; + for(i=-range;i<=range;i++){ + if(i+y<0 || i+y>=map[m].ys) + continue; + for(j=-range;j<=range;j++){ + if(j+x<0 || j+x>=map[m].xs) + continue; + if((c=read_gat(m,j+x,i+y))==1 || c==5) + continue; + if(free_cell==0){ + x+=j; + y+=i; + i=range+1; + break; + } + free_cell--; + } + } + + return x+(y<<16); +} + +/*========================================== + * (m,x,y)を中心に3x3以内に床アイテム設置 + * + * item_dataはamount以外をcopyする + *------------------------------------------ + */ +int map_addflooritem(struct item *item_data,int amount,int m,int x,int y,struct map_session_data *first_sd, + struct map_session_data *second_sd,struct map_session_data *third_sd,int type) { + int xy,r; + unsigned int tick; + struct flooritem_data *fitem=NULL; + + nullpo_retr(0, item_data); + + if((xy=map_searchrandfreecell(m,x,y,1))<0) + return 0; + r=rand(); + + fitem = (struct flooritem_data *)aCalloc(1,sizeof(*fitem)); + fitem->bl.type=BL_ITEM; + fitem->bl.prev = fitem->bl.next = NULL; + fitem->bl.m=m; + fitem->bl.x=xy&0xffff; + fitem->bl.y=(xy>>16)&0xffff; + fitem->first_get_id = 0; + fitem->first_get_tick = 0; + fitem->second_get_id = 0; + fitem->second_get_tick = 0; + fitem->third_get_id = 0; + fitem->third_get_tick = 0; + + fitem->bl.id = map_addobject(&fitem->bl); + if(fitem->bl.id==0){ + free(fitem); + return 0; + } + + tick = gettick(); + if(first_sd) { + fitem->first_get_id = first_sd->bl.id; + if(type) + fitem->first_get_tick = tick + battle_config.mvp_item_first_get_time; + else + fitem->first_get_tick = tick + battle_config.item_first_get_time; + } + if(second_sd) { + fitem->second_get_id = second_sd->bl.id; + if(type) + fitem->second_get_tick = tick + battle_config.mvp_item_first_get_time + battle_config.mvp_item_second_get_time; + else + fitem->second_get_tick = tick + battle_config.item_first_get_time + battle_config.item_second_get_time; + } + if(third_sd) { + fitem->third_get_id = third_sd->bl.id; + if(type) + fitem->third_get_tick = tick + battle_config.mvp_item_first_get_time + battle_config.mvp_item_second_get_time + battle_config.mvp_item_third_get_time; + else + fitem->third_get_tick = tick + battle_config.item_first_get_time + battle_config.item_second_get_time + battle_config.item_third_get_time; + } + + memcpy(&fitem->item_data,item_data,sizeof(*item_data)); + fitem->item_data.amount=amount; + fitem->subx=(r&3)*3+3; + fitem->suby=((r>>2)&3)*3+3; + fitem->cleartimer=add_timer(gettick()+battle_config.flooritem_lifetime,map_clearflooritem_timer,fitem->bl.id,0); + + map_addblock(&fitem->bl); + clif_dropflooritem(fitem); + + return fitem->bl.id; +} + +/*========================================== + * charid_dbへ追加(返信待ちがあれば返信) + *------------------------------------------ + */ +void map_addchariddb(int charid, char *name) { + struct charid2nick *p=NULL; + int req=0; + + p=numdb_search(charid_db,charid); + if(p==NULL){ // データベースにない + p = (struct charid2nick *)aCalloc(1,sizeof(struct charid2nick)); + p->req_id=0; + }else + numdb_erase(charid_db,charid); + + req=p->req_id; + memcpy(p->nick,name,24); + p->req_id=0; + numdb_insert(charid_db,charid,p); + if(req){ // 返信待ちがあれば返信 + struct map_session_data *sd = map_id2sd(req); + if(sd!=NULL) + clif_solved_charname(sd,charid); + } +} + +/*========================================== + * charid_dbへ追加(返信要求のみ) + *------------------------------------------ + */ +int map_reqchariddb(struct map_session_data * sd,int charid) { + struct charid2nick *p=NULL; + + nullpo_retr(0, sd); + + p=numdb_search(charid_db,charid); + if(p!=NULL) // データベースにすでにある + return 0; + p = (struct charid2nick *)aCalloc(1,sizeof(struct charid2nick)); + p->req_id=sd->bl.id; + numdb_insert(charid_db,charid,p); + return 0; +} + +/*========================================== + * id_dbへblを追加 + *------------------------------------------ + */ +void map_addiddb(struct block_list *bl) { + nullpo_retv(bl); + + numdb_insert(id_db,bl->id,bl); +} + +/*========================================== + * id_dbからblを削除 + *------------------------------------------ + */ +void map_deliddb(struct block_list *bl) { + nullpo_retv(bl); + + numdb_erase(id_db,bl->id); +} + +/*========================================== + * nick_dbへsdを追加 + *------------------------------------------ + */ +void map_addnickdb(struct map_session_data *sd) { + nullpo_retv(sd); + + strdb_insert(nick_db,sd->status.name,sd); +} + +/*========================================== + * PCのquit処理 map.c内分 + * + * quit処理の主体が違うような気もしてきた + *------------------------------------------ + */ +int map_quit(struct map_session_data *sd) { + int i; + + nullpo_retr(0, sd); + + if(sd->chatID) // チャットから出る + chat_leavechat(sd); + + if(sd->trade_partner) // 取引を中断する + trade_tradecancel(sd); + + if(sd->party_invite>0) // パーティ勧誘を拒否する + party_reply_invite(sd,sd->party_invite_account,0); + + if(sd->guild_invite>0) // ギルド勧誘を拒否する + guild_reply_invite(sd,sd->guild_invite,0); + if(sd->guild_alliance>0) // ギルド同盟勧誘を拒否する + guild_reply_reqalliance(sd,sd->guild_alliance_account,0); + + party_send_logout(sd); // パーティのログアウトメッセージ送信 + + guild_send_memberinfoshort(sd,0); // ギルドのログアウトメッセージ送信 + + pc_cleareventtimer(sd); // イベントタイマを破棄する + + if(sd->state.storage_flag) + storage_guild_storage_quit(sd,0); + else + storage_storage_quit(sd); // 倉庫を開いてるなら保存する + + skill_castcancel(&sd->bl,0); // 詠唱を中断する + skill_stop_dancing(&sd->bl,1);// ダンス/演奏中断 + + if(sd->sc_data && sd->sc_data[SC_BERSERK].timer!=-1) //バーサーク中の終了はHPを100に + sd->status.hp = 100; + + skill_status_change_clear(&sd->bl,1); // ステータス異常を解除する + skill_clear_unitgroup(&sd->bl); // スキルユニットグループの削除 + skill_cleartimerskill(&sd->bl); + pc_stop_walking(sd,0); + pc_stopattack(sd); + pc_delinvincibletimer(sd); + pc_delspiritball(sd,sd->spiritball,1); + skill_gangsterparadise(sd,0); + + pc_calcstatus(sd,4); + + clif_clearchar_area(&sd->bl,2); + + if(sd->status.pet_id && sd->pd) { + pet_lootitem_drop(sd->pd,sd); + pet_remove_map(sd); + if(sd->pet.intimate <= 0) { + intif_delete_petdata(sd->status.pet_id); + sd->status.pet_id = 0; + sd->pd = NULL; + sd->petDB = NULL; + } + else + intif_save_petdata(sd->status.account_id,&sd->pet); + } + + if(pc_isdead(sd)) + pc_setrestartvalue(sd,2); + pc_makesavestatus(sd); + //クローンスキルで覚えたスキルは消す + for(i=0;i<MAX_SKILL;i++){ + if(sd->status.skill[i].flag == 13){ + sd->status.skill[i].id=0; + sd->status.skill[i].lv=0; + sd->status.skill[i].flag=0; + } + } + chrif_save(sd); + storage_storage_save(sd); + + if( sd->npc_stackbuf && sd->npc_stackbuf != NULL) + free( sd->npc_stackbuf ); + + map_delblock(&sd->bl); + +#ifndef TXT_ONLY + chrif_char_offline(sd); +#endif + + numdb_erase(id_db,sd->bl.id); + strdb_erase(nick_db,sd->status.name); + numdb_erase(charid_db,sd->status.char_id); + + return 0; +} + +/*========================================== + * id番号のPCを探す。居なければNULL + *------------------------------------------ + */ +struct map_session_data * map_id2sd(int id) { +// remove search from db, because: +// 1 - all players, npc, items and mob are in this db (to search, it's not speed, and search in session is more sure) +// 2 - DB seems not always correct. Sometimes, when a player disconnects, its id (account value) is not removed and structure +// point to a memory area that is not more a session_data and value are incorrect (or out of available memory) -> crash +// replaced by searching in all session. +// by searching in session, we are sure that fd, session, and account exist. +/* + struct block_list *bl; + + bl=numdb_search(id_db,id); + if(bl && bl->type==BL_PC) + return (struct map_session_data*)bl; + return NULL; +*/ + int i; + struct map_session_data *sd=NULL; + + for(i = 0; i < fd_max; i++) + if (session[i] && (sd = session[i]->session_data) && sd->bl.id == id) + return sd; + + return NULL; +} + +/*========================================== + * char_id番号の名前を探す + *------------------------------------------ + */ +char * map_charid2nick(int id) { + struct charid2nick *p=numdb_search(charid_db,id); + + if(p==NULL) + return NULL; + if(p->req_id!=0) + return NULL; + return p->nick; +} + + +/*========================================== + * Search session data from a nick name + * (without sensitive case if necessary) + * return map_session_data pointer or NULL + *------------------------------------------ + */ +struct map_session_data * map_nick2sd(char *nick) { + int i, quantity=0, nicklen; + struct map_session_data *sd = NULL; + struct map_session_data *pl_sd = NULL; + + if (nick == NULL) + return NULL; + + nicklen = strlen(nick); + + for (i = 0; i < fd_max; i++) { + if (session[i] && (pl_sd = session[i]->session_data) && pl_sd->state.auth) + // Without case sensitive check (increase the number of similar character names found) + if (strnicmp(pl_sd->status.name, nick, nicklen) == 0) { + // Strict comparison (if found, we finish the function immediatly with correct value) + if (strcmp(pl_sd->status.name, nick) == 0) + return pl_sd; + quantity++; + sd = pl_sd; + } + } + // Here, the exact character name is not found + // We return the found index of a similar account ONLY if there is 1 similar character + if (quantity == 1) + return sd; + + // Exact character name is not found and 0 or more than 1 similar characters have been found ==> we say not found + return NULL; +} + +/*========================================== + * id番号の物を探す + * 一時objectの場合は配列を引くのみ + *------------------------------------------ + */ +struct block_list * map_id2bl(int id) +{ + struct block_list *bl=NULL; + if(id<sizeof(object)/sizeof(object[0])) + bl = object[id]; + else + bl = numdb_search(id_db,id); + + return bl; +} + +/*========================================== + * id_db内の全てにfuncを実行 + *------------------------------------------ + */ +int map_foreachiddb(int (*func)(void*,void*,va_list),...) { + va_list ap=NULL; + + va_start(ap,func); + numdb_foreach(id_db,func,ap); + va_end(ap); + return 0; +} + +/*========================================== + * map.npcへ追加 (warp等の領域持ちのみ) + *------------------------------------------ + */ +int map_addnpc(int m,struct npc_data *nd) { + int i; + if(m<0 || m>=map_num) + return -1; + for(i=0;i<map[m].npc_num && i<MAX_NPC_PER_MAP;i++) + if(map[m].npc[i]==NULL) + break; + if(i==MAX_NPC_PER_MAP){ + if(battle_config.error_log) + printf("too many NPCs in one map %s\n",map[m].name); + return -1; + } + if(i==map[m].npc_num){ + map[m].npc_num++; + } + + nullpo_retr(0, nd); + + map[m].npc[i]=nd; + nd->n = i; + numdb_insert(id_db,nd->bl.id,nd); + + return i; +} + +void map_removenpc(void) { + int i,m,n=0; + + for(m=0;m<map_num;m++) { + for(i=0;i<map[m].npc_num && i<MAX_NPC_PER_MAP;i++) { + if(map[m].npc[i]!=NULL) { + clif_clearchar_area(&map[m].npc[i]->bl,2); + map_delblock(&map[m].npc[i]->bl); + numdb_erase(id_db,map[m].npc[i]->bl.id); + if(map[m].npc[i]->bl.subtype==SCRIPT) { +// free(map[m].npc[i]->u.scr.script); +// free(map[m].npc[i]->u.scr.label_list); + } + free(map[m].npc[i]); + map[m].npc[i] = NULL; + n++; + } + } + } + printf("%d NPCs removed.\n",n); +} + +/*========================================== + * map名からmap番号へ変換 + *------------------------------------------ + */ +int map_mapname2mapid(char *name) { + struct map_data *md=NULL; + + md=strdb_search(map_db,name); + if(md==NULL || md->gat==NULL) + return -1; + return md->m; +} + +/*========================================== + * 他鯖map名からip,port変換 + *------------------------------------------ + */ +int map_mapname2ipport(char *name,int *ip,int *port) { + struct map_data_other_server *mdos=NULL; + + mdos=strdb_search(map_db,name); + if(mdos==NULL || mdos->gat) + return -1; + *ip=mdos->ip; + *port=mdos->port; + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int map_check_dir(int s_dir,int t_dir) { + if(s_dir == t_dir) + return 0; + switch(s_dir) { + case 0: + if(t_dir == 7 || t_dir == 1 || t_dir == 0) + return 0; + break; + case 1: + if(t_dir == 0 || t_dir == 2 || t_dir == 1) + return 0; + break; + case 2: + if(t_dir == 1 || t_dir == 3 || t_dir == 2) + return 0; + break; + case 3: + if(t_dir == 2 || t_dir == 4 || t_dir == 3) + return 0; + break; + case 4: + if(t_dir == 3 || t_dir == 5 || t_dir == 4) + return 0; + break; + case 5: + if(t_dir == 4 || t_dir == 6 || t_dir == 5) + return 0; + break; + case 6: + if(t_dir == 5 || t_dir == 7 || t_dir == 6) + return 0; + break; + case 7: + if(t_dir == 6 || t_dir == 0 || t_dir == 7) + return 0; + break; + } + return 1; +} + +/*========================================== + * 彼我の方向を計算 + *------------------------------------------ + */ +int map_calc_dir( struct block_list *src,int x,int y) { + int dir=0; + int dx,dy; + + nullpo_retr(0, src); + + dx=x-src->x; + dy=y-src->y; + if( dx==0 && dy==0 ){ // 彼我の場所一致 + dir=0; // 上 + }else if( dx>=0 && dy>=0 ){ // 方向的に右上 + dir=7; // 右上 + if( dx*3-1<dy ) dir=0; // 上 + if( dx>dy*3 ) dir=6; // 右 + }else if( dx>=0 && dy<=0 ){ // 方向的に右下 + dir=5; // 右下 + if( dx*3-1<-dy ) dir=4; // 下 + if( dx>-dy*3 ) dir=6; // 右 + }else if( dx<=0 && dy<=0 ){ // 方向的に左下 + dir=3; // 左下 + if( dx*3+1>dy ) dir=4; // 下 + if( dx<dy*3 ) dir=2; // 左 + }else{ // 方向的に左上 + dir=1; // 左上 + if( -dx*3-1<dy ) dir=0; // 上 + if( -dx>dy*3 ) dir=2; // 左 + } + return dir; +} + +// gat系 +/*========================================== + * (m,x,y)の状態を調べる + *------------------------------------------ + */ +int map_getcell(int m,int x,int y) { + if(x<0 || x>=map[m].xs-1 || y<0 || y>=map[m].ys-1) + return 1; + return map[m].gat[x+y*map[m].xs]; +} + +/*========================================== + * (m,x,y)の状態をtにする + *------------------------------------------ + */ +int map_setcell(int m,int x,int y,int t) { + if(x<0 || x>=map[m].xs || y<0 || y>=map[m].ys) + return t; + return map[m].gat[x+y*map[m].xs]=t; +} + +/*========================================== + * 他鯖管理のマップをdbに追加 + *------------------------------------------ + */ +int map_setipport(char *name,unsigned long ip,int port) { + struct map_data *md=NULL; + struct map_data_other_server *mdos=NULL; + + md=strdb_search(map_db,name); + if(md==NULL){ // not exist -> add new data + mdos=(struct map_data_other_server *)aCalloc(1,sizeof(struct map_data_other_server)); + memcpy(mdos->name,name,24); + mdos->gat = NULL; + mdos->ip = ip; + mdos->port = port; + strdb_insert(map_db,mdos->name,mdos); + } else { + if(md->gat){ // local -> check data + if(ip!=clif_getip() || port!=clif_getport()){ + printf("from char server : %s -> %08lx:%d\n",name,ip,port); + return 1; + } + } else { // update + mdos=(struct map_data_other_server *)md; + mdos->ip = ip; + mdos->port = port; + } + } + return 0; +} + +// 初期化周り +/*========================================== + * 水場高さ設定 + *------------------------------------------ + */ +static struct { + char mapname[24]; + int waterheight; +} *waterlist=NULL; + +#define NO_WATER 1000000 + +static int map_waterheight(char *mapname) { + if(waterlist){ + int i; + for(i=0;waterlist[i].mapname[0] && i < MAX_MAP_PER_SERVER;i++) + if(strcmp(waterlist[i].mapname,mapname)==0) + return waterlist[i].waterheight; + } + return NO_WATER; +} + +static void map_readwater(char *watertxt) { + char line[1024],w1[1024]; + FILE *fp=NULL; + int n=0; + + fp=fopen(watertxt,"r"); + if(fp==NULL){ + printf("file not found: %s\n",watertxt); + return; + } + if(waterlist==NULL) + waterlist=aCalloc(MAX_MAP_PER_SERVER,sizeof(*waterlist)); + while(fgets(line,1020,fp) && n < MAX_MAP_PER_SERVER){ + int wh,count; + if(line[0] == '/' && line[1] == '/') + continue; + if((count=sscanf(line,"%s%d",w1,&wh)) < 1){ + continue; + } + strcpy(waterlist[n].mapname,w1); + if(count >= 2) + waterlist[n].waterheight = wh; + else + waterlist[n].waterheight = 3; + n++; + } + fclose(fp); +} + +/*========================================== + * マップ1枚読み込み + *------------------------------------------ + */ +static int map_readmap(int m,char *fn, char *alias) { + unsigned char *gat = ""; + int s; + int x,y,xs,ys; + struct gat_1cell {char type;} *p; + int wh; + size_t size; + + // read & convert fn + gat=grfio_read(fn); + if(gat==NULL) + return -1; + + printf("\rLoading Maps [%d/%d]: %-50s ",m,map_num,fn); + fflush(stdout); + + map[m].m=m; + xs=map[m].xs=*(short*)(gat); + ys=map[m].ys=*(short*)(gat+2); + printf("\n%i %i\n", xs, ys); + map[m].gat = calloc(s = map[m].xs * map[m].ys, 1); + if(map[m].gat==NULL){ + printf("out of memory : map_readmap gat\n"); + exit(1); + } + + map[m].npc_num=0; + map[m].users=0; + memset(&map[m].flag,0,sizeof(map[m].flag)); + if(battle_config.pk_mode) map[m].flag.pvp = 1; // make all maps pvp for pk_mode [Valaris] + wh=map_waterheight(map[m].name); + for(y=0;y<ys;y++){ + p=(struct gat_1cell*)(gat+y*xs+4); + for(x=0;x<xs;x++){ + /*if(wh!=NO_WATER && p->type==0){ + // 水場判定 + map[m].gat[x+y*xs]=(p->high[0]>wh || p->high[1]>wh || p->high[2]>wh || p->high[3]>wh) ? 3 : 0; + } else {*/ + map[m].gat[x+y*xs]=p->type; + //} + p++; + } + } + free(gat); + + map[m].bxs=(xs+BLOCK_SIZE-1)/BLOCK_SIZE; + map[m].bys=(ys+BLOCK_SIZE-1)/BLOCK_SIZE; + size = map[m].bxs * map[m].bys * sizeof(struct block_list*); + + map[m].block = calloc(size, 1); + if(map[m].block == NULL){ + printf("out of memory : map_readmap block\n"); + exit(1); + } + + map[m].block_mob = calloc(size, 1); + if (map[m].block_mob == NULL) { + printf("out of memory : map_readmap block_mob\n"); + exit(1); + } + + size = map[m].bxs*map[m].bys*sizeof(int); + + map[m].block_count = calloc(size, 1); + if(map[m].block_count==NULL){ + printf("out of memory : map_readmap block\n"); + exit(1); + } + memset(map[m].block_count,0,size); + + map[m].block_mob_count=calloc(size, 1); + if(map[m].block_mob_count==NULL){ + printf("out of memory : map_readmap block_mob\n"); + exit(1); + } + memset(map[m].block_mob_count,0,size); + + strdb_insert(map_db,map[m].name,&map[m]); + +// printf("%s read done\n",fn); + + return 0; +} + +/*========================================== + * 全てのmapデータを読み込む + *------------------------------------------ + */ +int map_readallmap(void) { + int i,maps_removed=0; + char fn[256]=""; + + // 先に全部のャbプの存在を確認 + for(i=0;i<map_num;i++){ + if(strstr(map[i].name,".gat")==NULL) + continue; + sprintf(fn,"data\\%s",map[i].name); + if(grfio_size(fn) == -1) { + map_delmap(map[i].name); + maps_removed++; + } + } + for(i=0;i<map_num;i++){ + if(strstr(map[i].name,".gat")!=NULL) { + char *p = strstr(map[i].name, ">"); // [MouseJstr] + if (p != NULL) { + char alias[64]; + *p = '\0'; + strcpy(alias, map[i].name); + strcpy(map[i].name, p + 1); + sprintf(fn,"data\\%s",map[i].name); + if(map_readmap(i,fn, alias) == -1) { + map_delmap(map[i].name); + maps_removed++; + } + } else { + sprintf(fn,"data\\%s",map[i].name); + if(map_readmap(i,fn, NULL) == -1) { + map_delmap(map[i].name); + maps_removed++; + } + } + } + } + + free(waterlist); + printf("\rMaps Loaded: %d %60s\n",map_num,""); + printf("\rMaps Removed: %d \n",maps_removed); + return 0; +} + +/*========================================== + * 読み込むmapを追加する + *------------------------------------------ + */ +int map_addmap(char *mapname) { + if (strcmpi(mapname,"clear")==0) { + map_num=0; + return 0; + } + + if (map_num >= MAX_MAP_PER_SERVER - 1) { + printf("too many map\n"); + return 1; + } + memcpy(map[map_num].name, mapname, 24); + map_num++; + return 0; +} + +/*========================================== + * 読み込むmapを削除する + *------------------------------------------ + */ +int map_delmap(char *mapname) { + int i; + + if (strcmpi(mapname, "all") == 0) { + map_num = 0; + return 0; + } + + for(i = 0; i < map_num; i++) { + if (strcmp(map[i].name, mapname) == 0) { + printf("Removing map [ %s ] from maplist\n",map[i].name); + memmove(map+i, map+i+1, sizeof(map[0])*(map_num-i-1)); + map_num--; + } + } + return 0; +} + +/*========================================== + * 設定ファイルを読み込む + *------------------------------------------ + */ +int map_config_read(char *cfgName) { + char line[1024], w1[1024], w2[1024]; + FILE *fp; + struct hostent *h = NULL; + + fp = fopen(cfgName,"r"); + if (fp == NULL) { + printf("Map configuration file not found at: %s\n", cfgName); + exit(1); + } + while(fgets(line, sizeof(line) -1, fp)) { + if (line[0] == '/' && line[1] == '/') + continue; + if (sscanf(line, "%[^:]: %[^\r\n]", w1, w2) == 2) { + if (strcmpi(w1, "userid")==0){ + chrif_setuserid(w2); + } else if (strcmpi(w1, "passwd") == 0) { + chrif_setpasswd(w2); + } else if (strcmpi(w1, "char_ip") == 0) { + h = gethostbyname (w2); + if(h != NULL) { + printf("Character server IP address : %s -> %d.%d.%d.%d\n", w2, (unsigned char)h->h_addr[0], (unsigned char)h->h_addr[1], (unsigned char)h->h_addr[2], (unsigned char)h->h_addr[3]); + sprintf(w2,"%d.%d.%d.%d", (unsigned char)h->h_addr[0], (unsigned char)h->h_addr[1], (unsigned char)h->h_addr[2], (unsigned char)h->h_addr[3]); + } + chrif_setip(w2); + } else if (strcmpi(w1, "char_port") == 0) { + chrif_setport(atoi(w2)); + } else if (strcmpi(w1, "map_ip") == 0) { + h = gethostbyname (w2); + if (h != NULL) { + printf("Map server IP address : %s -> %d.%d.%d.%d\n", w2, (unsigned char)h->h_addr[0], (unsigned char)h->h_addr[1], (unsigned char)h->h_addr[2], (unsigned char)h->h_addr[3]); + sprintf(w2, "%d.%d.%d.%d", (unsigned char)h->h_addr[0], (unsigned char)h->h_addr[1], (unsigned char)h->h_addr[2], (unsigned char)h->h_addr[3]); + } + clif_setip(w2); + } else if (strcmpi(w1, "map_port") == 0) { + clif_setport(atoi(w2)); + map_port = (atoi(w2)); + } else if (strcmpi(w1, "water_height") == 0) { + map_readwater(w2); + } else if (strcmpi(w1, "map") == 0) { + map_addmap(w2); + } else if (strcmpi(w1, "delmap") == 0) { + map_delmap(w2); + } else if (strcmpi(w1, "npc") == 0) { + npc_addsrcfile(w2); + } else if (strcmpi(w1, "delnpc") == 0) { + npc_delsrcfile(w2); + } else if (strcmpi(w1, "data_grf") == 0) { + grfio_setdatafile(w2); + } else if (strcmpi(w1, "sdata_grf") == 0) { + grfio_setsdatafile(w2); + } else if (strcmpi(w1, "adata_grf") == 0) { + grfio_setadatafile(w2); + } else if (strcmpi(w1, "autosave_time") == 0) { + autosave_interval = atoi(w2) * 1000; + if (autosave_interval <= 0) + autosave_interval = DEFAULT_AUTOSAVE_INTERVAL; + } else if (strcmpi(w1, "motd_txt") == 0) { + strcpy(motd_txt, w2); + } else if (strcmpi(w1, "help_txt") == 0) { + strcpy(help_txt, w2); + } else if (strcmpi(w1, "mapreg_txt") == 0) { + strcpy(mapreg_txt, w2); + } else if (strcmpi(w1, "import") == 0) { + map_config_read(w2); + } + } + } + fclose(fp); + + return 0; +} + +#ifndef TXT_ONLY +/*======================================= + * MySQL Init + *--------------------------------------- + */ + +int map_sql_init(void){ + + mysql_init(&mmysql_handle); + + //DB connection start + printf("Connect Map DB Server....\n"); + if(!mysql_real_connect(&mmysql_handle, map_server_ip, map_server_id, map_server_pw, + map_server_db ,map_server_port, (char *)NULL, 0)) { + //pointer check + printf("%s\n",mysql_error(&mmysql_handle)); + exit(1); + } + else { + printf ("connect success! (Map Server Connection)\n"); + } + + mysql_init(&lmysql_handle); + + //DB connection start + printf("Connect Login DB Server....\n"); + if(!mysql_real_connect(&lmysql_handle, login_server_ip, login_server_id, login_server_pw, + login_server_db ,login_server_port, (char *)NULL, 0)) { + //pointer check + printf("%s\n",mysql_error(&lmysql_handle)); + exit(1); + } + else { + printf ("connect success! (Login Server Connection)\n"); + } + + if(battle_config.mail_system) { // mail system [Valaris] + mysql_init(&mail_handle); + if(!mysql_real_connect(&mail_handle, map_server_ip, map_server_id, map_server_pw, + map_server_db ,map_server_port, (char *)NULL, 0)) { + printf("%s\n",mysql_error(&mail_handle)); + exit(1); + } + } + + return 0; +} + +int map_sql_close(void){ + mysql_close(&mmysql_handle); + printf("Close Map DB Connection....\n"); + + mysql_close(&lmysql_handle); + printf("Close Login DB Connection....\n"); + return 0; +} + +int sql_config_read(char *cfgName) +{ + int i; + char line[1024],w1[1024],w2[1024]; + FILE *fp; + + fp=fopen(cfgName,"r"); + if(fp==NULL){ + printf("file not found: %s\n",cfgName); + return 1; + } + while(fgets(line,1020,fp)){ + if(line[0] == '/' && line[1] == '/') + continue; + i=sscanf(line,"%[^:]: %[^\r\n]",w1,w2); + if(i!=2) + continue; + if(strcmpi(w1,"item_db_db")==0){ + strcpy(item_db_db,w2); + } else if(strcmpi(w1,"mob_db_db")==0){ + strcpy(mob_db_db,w2); + } else if(strcmpi(w1,"login_db_level")==0){ + strcpy(login_db_level,w2); + } else if(strcmpi(w1,"login_db_account_id")==0){ + strcpy(login_db_account_id,w2); + } else if(strcmpi(w1,"login_db")==0){ + strcpy(login_db,w2); + } else if (strcmpi(w1, "char_db") == 0) { + strcpy(char_db, w2); + //Map Server SQL DB + } else if(strcmpi(w1,"map_server_ip")==0){ + strcpy(map_server_ip, w2); + printf ("set map_server_ip : %s\n",w2); + } else if(strcmpi(w1,"map_server_port")==0){ + map_server_port=atoi(w2); + printf ("set map_server_port : %s\n",w2); + } else if(strcmpi(w1,"map_server_id")==0){ + strcpy(map_server_id, w2); + printf ("set map_server_id : %s\n",w2); + } else if(strcmpi(w1,"map_server_pw")==0){ + strcpy(map_server_pw, w2); + printf ("set map_server_pw : %s\n",w2); + } else if(strcmpi(w1,"map_server_db")==0){ + strcpy(map_server_db, w2); + printf ("set map_server_db : %s\n",w2); + //Map server option to use SQL db or not + } else if(strcmpi(w1,"use_sql_db")==0){ + if (strcmpi(w2,"yes")){db_use_sqldbs=0;} else if (strcmpi(w2,"no")){db_use_sqldbs=1;} + printf ("Using SQL dbs: %s\n",w2); + //Login Server SQL DB + } else if(strcmpi(w1,"login_server_ip")==0){ + strcpy(login_server_ip, w2); + printf ("set login_server_ip : %s\n",w2); + } else if(strcmpi(w1,"login_server_port")==0){ + login_server_port = atoi(w2); + printf ("set login_server_port : %s\n",w2); + } else if(strcmpi(w1,"login_server_id")==0){ + strcpy(login_server_id, w2); + printf ("set login_server_id : %s\n",w2); + } else if(strcmpi(w1,"login_server_pw")==0){ + strcpy(login_server_pw, w2); + printf ("set login_server_pw : %s\n",w2); + } else if(strcmpi(w1,"login_server_db")==0){ + strcpy(login_server_db, w2); + printf ("set login_server_db : %s\n",w2); + } else if(strcmpi(w1,"lowest_gm_level")==0){ + lowest_gm_level = atoi(w2); + printf ("set lowest_gm_level : %s\n",w2); + } else if(strcmpi(w1,"read_gm_interval")==0){ + read_gm_interval = ( atoi(w2) * 60 * 1000 ); // Minutes multiplied by 60 secs per min by 1000 milliseconds per second + printf ("set read_gm_interval : %s\n",w2); + } + } + fclose(fp); + + return 0; +} + +// sql online status checking [Valaris] +void char_offline(struct map_session_data *sd) +{ + if(sd && sd->status.char_id) { + sprintf(tmp_sql,"UPDATE `%s` SET `online`='0' WHERE `char_id`='%d'", char_db, sd->status.char_id); + if(mysql_query(&mmysql_handle, tmp_sql) ) { + printf("DB server Error (update online `%s`)- %s\n", char_db, mysql_error(&mmysql_handle) ); + } + } +} + +void do_reset_online(void) +{ + sprintf(tmp_sql,"UPDATE `%s` SET `online`='0' WHERE `online`='1'", char_db); + if(mysql_query(&mmysql_handle, tmp_sql) ) { + printf("DB server Error (reset_online `%s`)- %s\n", char_db, mysql_error(&mmysql_handle) ); + } +} + +int online_timer(int tid,unsigned int tick,int id,int data) +{ + if(check_online_timer != tid) + return 0; + + char_online_check(); + + check_online_timer=add_timer(gettick()+CHECK_INTERVAL,online_timer,0,0); + + return 0; +} + +void char_online_check(void) +{ + int i; + struct map_session_data *sd=NULL; + + do_reset_online(); + + for(i=0;i<fd_max;i++){ + if (session[i] && (sd = session[i]->session_data) && sd && sd->state.auth && + !(battle_config.hide_GM_session && pc_isGM(sd))) + if(sd->status.char_id) { + sprintf(tmp_sql,"UPDATE `%s` SET `online`='1' WHERE `char_id`='%d'", char_db, sd->status.char_id); + if(mysql_query(&mmysql_handle, tmp_sql) ) { + printf("DB server Error (update online `%s`)- %s\n", char_db, mysql_error(&mmysql_handle) ); + } + } + } + + + if(check_online_timer && check_online_timer != -1) { + delete_timer(check_online_timer,online_timer); + add_timer(gettick()+CHECK_INTERVAL,online_timer,0,0); + } + +} + +#endif /* not TXT_ONLY */ + +int id_db_final(void *k,void *d,va_list ap){ return 0; } +int map_db_final(void *k,void *d,va_list ap){ return 0; } +int nick_db_final(void *k,void *d,va_list ap){ return 0; } +int charid_db_final(void *k,void *d,va_list ap){ return 0; } + +static int cleanup_sub(struct block_list *bl, va_list ap) { + nullpo_retr(0, bl); + + switch(bl->type) { + case BL_PC: + map_delblock(bl); // There is something better... + break; + case BL_NPC: + npc_delete((struct npc_data *)bl); + break; + case BL_MOB: + mob_delete((struct mob_data *)bl); + break; + case BL_PET: + pet_remove_map((struct map_session_data *)bl); + break; + case BL_ITEM: + map_clearflooritem(bl->id); + break; + case BL_SKILL: + skill_delunit((struct skill_unit *) bl); + break; + } + + return 0; +} + +/*========================================== + * map鯖終了時処理 + *------------------------------------------ + */ +void do_final(void) { + int map_id, i; + + for (map_id = 0; map_id < map_num;map_id++) { + if(map[map_id].m) + map_foreachinarea(cleanup_sub, map_id, 0, 0, map[map_id].xs, map[map_id].ys, 0, 0); + } + + for (i = 0; i < fd_max; i++) + delete_session(i); + + map_removenpc(); + timer_final(); + + numdb_final(id_db, id_db_final); + strdb_final(map_db, map_db_final); + strdb_final(nick_db, nick_db_final); + numdb_final(charid_db, charid_db_final); + + for(i=0;i<=map_num;i++){ + if(map[i].gat) free(map[i].gat); + if(map[i].block) free(map[i].block); + if(map[i].block_mob) free(map[i].block_mob); + if(map[i].block_count) free(map[i].block_count); + if(map[i].block_mob_count) free(map[i].block_mob_count); + } + do_final_script(); + do_final_itemdb(); + do_final_storage(); + do_final_guild(); +#ifndef TXT_ONLY + do_reset_online(); + map_sql_close(); +#endif /* not TXT_ONLY */ +} + +void map_helpscreen() { + exit(1); +} + +/*====================================================== + * Map-Server Init and Command-line Arguments [Valaris] + *------------------------------------------------------ + */ +int do_init(int argc, char *argv[]) { + int i; + +#ifndef TXT_ONLY + unsigned char *SQL_CONF_NAME="conf/inter_athena.conf"; +#endif + unsigned char *MAP_CONF_NAME = "conf/map_athena.conf"; + unsigned char *BATTLE_CONF_FILENAME = "conf/battle_athena.conf"; + unsigned char *ATCOMMAND_CONF_FILENAME = "conf/atcommand_athena.conf"; + unsigned char *SCRIPT_CONF_NAME = "conf/script_athena.conf"; + unsigned char *MSG_CONF_NAME = "conf/msg_athena.conf"; + unsigned char *GRF_PATH_FILENAME = "conf/grf-files.txt"; + + srand(gettick()); + + for (i = 1; i < argc ; i++) { + + if (strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "--h") == 0 || strcmp(argv[i], "--?") == 0 || strcmp(argv[i], "/?") == 0) + map_helpscreen(); + else if (strcmp(argv[i], "--map_config") == 0) + MAP_CONF_NAME=argv[i+1]; + else if (strcmp(argv[i],"--battle_config") == 0) + BATTLE_CONF_FILENAME = argv[i+1]; + else if (strcmp(argv[i],"--atcommand_config") == 0) + ATCOMMAND_CONF_FILENAME = argv[i+1]; + else if (strcmp(argv[i],"--script_config") == 0) + SCRIPT_CONF_NAME = argv[i+1]; + else if (strcmp(argv[i],"--msg_config") == 0) + MSG_CONF_NAME = argv[i+1]; + else if (strcmp(argv[i],"--grf_path_file") == 0) + GRF_PATH_FILENAME = argv[i+1]; +#ifndef TXT_ONLY + else if (strcmp(argv[i],"--sql_config") == 0) + SQL_CONF_NAME = argv[i+1]; +#endif /* not TXT_ONLY */ + } + + map_config_read(MAP_CONF_NAME); + battle_config_read(BATTLE_CONF_FILENAME); + atcommand_config_read(ATCOMMAND_CONF_FILENAME); + script_config_read(SCRIPT_CONF_NAME); + msg_config_read(MSG_CONF_NAME); +#ifndef TXT_ONLY + sql_config_read(SQL_CONF_NAME); +#endif /* not TXT_ONLY */ + + atexit(do_final); + + id_db = numdb_init(); + map_db = strdb_init(16); + nick_db = strdb_init(24); + charid_db = numdb_init(); +#ifndef TXT_ONLY + map_sql_init(); +#endif /* not TXT_ONLY */ + + grfio_init(GRF_PATH_FILENAME); + + map_readallmap(); + + add_timer_func_list(map_clearflooritem_timer, "map_clearflooritem_timer"); + +#ifndef TXT_ONLY // online status timer, checks every hour [Valaris] + add_timer_func_list(online_timer, "online_timer"); + check_online_timer=add_timer(gettick()+CHECK_INTERVAL,online_timer,0,0); +#endif /* not TXT_ONLY */ + + do_init_chrif(); + do_init_clif(); + do_init_itemdb(); + do_init_mob(); // npcの初期化時内でmob_spawnして、mob_dbを参照するのでinit_npcより先 + do_init_script(); + do_init_npc(); + do_init_pc(); + do_init_party(); + do_init_guild(); + do_init_storage(); + do_init_skill(); + do_init_pet(); + +#ifndef TXT_ONLY /* mail system [Valaris] */ + if(battle_config.mail_system) + do_init_mail(); +#endif /* not TXT_ONLY */ + + npc_event_do_oninit(); // npcのOnInitイベント実行 + + if (battle_config.pk_mode == 1) + printf("The server is running in \033[1;31mPK Mode\033[0m.\n"); + + printf("The map-server is \033[1;32mready\033[0m (Server is listening on the port %d).\n\n", map_port); + + return 0; +} + diff --git a/misc/src/map/map.h b/misc/src/map/map.h new file mode 100644 index 0000000..703bbb6 --- /dev/null +++ b/misc/src/map/map.h @@ -0,0 +1,705 @@ +// $Id: map.h,v 1.8 2004/09/25 11:39:17 MouseJstr Exp $ +#ifndef _MAP_H_ +#define _MAP_H_ + +#include <stdarg.h> +#include "mmo.h" + +#define MAX_PC_CLASS (1+6+6+1+6+1+1+1+1+4023) +#define PC_CLASS_BASE 0 +#define PC_CLASS_BASE2 (PC_CLASS_BASE + 4001) +#define PC_CLASS_BASE3 (PC_CLASS_BASE2 + 22) +#define MAX_NPC_PER_MAP 512 +#define BLOCK_SIZE 8 +#define AREA_SIZE battle_config.area_size +#define LOCAL_REG_NUM 16 +#define LIFETIME_FLOORITEM 60 +#define DAMAGELOG_SIZE 30 +#define LOOTITEM_SIZE 10 +#define MAX_SKILL_LEVEL 100 +#define MAX_STATUSCHANGE 200 +#define MAX_SKILLUNITGROUP 32 +#define MAX_MOBSKILLUNITGROUP 8 +#define MAX_SKILLUNITGROUPTICKSET 128 +#define MAX_SKILLTIMERSKILL 32 +#define MAX_MOBSKILLTIMERSKILL 10 +#define MAX_MOBSKILL 32 +#define MAX_EVENTQUEUE 2 +#define MAX_EVENTTIMER 32 +#define NATURAL_HEAL_INTERVAL 500 +#define MAX_FLOORITEM 500000 +#define MAX_LEVEL 255 +#define MAX_WALKPATH 48 +#define MAX_DROP_PER_MAP 48 + +#define DEFAULT_AUTOSAVE_INTERVAL 60*1000 + +#define OPTION_HIDE 0x40 + +enum { BL_NUL, BL_PC, BL_NPC, BL_MOB, BL_ITEM, BL_CHAT, BL_SKILL , BL_PET }; +enum { WARP, SHOP, SCRIPT, MONS }; +struct block_list { + struct block_list *next,*prev; + int id; + short m,x,y; + unsigned char type; + unsigned char subtype; +}; + +struct walkpath_data { + unsigned char path_len,path_pos,path_half; + unsigned char path[MAX_WALKPATH]; +}; +struct script_reg { + int index; + int data; +}; +struct script_regstr { + int index; + char data[256]; +}; +struct status_change { + int timer; + int val1,val2,val3,val4; +}; +struct vending { + short index; + short amount; + int value; +}; + +struct skill_unit_group; +struct skill_unit { + struct block_list bl; + + struct skill_unit_group *group; + + int limit; + int val1,val2; + short alive,range; +}; +struct skill_unit_group { + int src_id; + int party_id; + int guild_id; + int map,range; + int target_flag; + unsigned int tick; + int limit,interval; + + int skill_id,skill_lv; + int val1,val2; + char *valstr; + int unit_id; + int group_id; + int unit_count,alive_count; + struct skill_unit *unit; +}; +struct skill_unit_group_tickset { + unsigned int tick; + int group_id; +}; +struct skill_timerskill { + int timer; + int src_id; + int target_id; + int map; + short x,y; + short skill_id,skill_lv; + int type; + int flag; +}; + +struct npc_data; +struct pet_db; +struct item_data; +struct square; + +struct map_session_data { + struct block_list bl; + struct { + unsigned auth : 1; + unsigned change_walk_target : 1; + unsigned attack_continue : 1; + unsigned menu_or_input : 1; + unsigned dead_sit : 2; + unsigned skillcastcancel : 1; + unsigned waitingdisconnect : 1; + unsigned lr_flag : 2; + unsigned connect_new : 1; + unsigned arrow_atk : 1; + unsigned attack_type : 3; + unsigned skill_flag : 1; + unsigned gangsterparadise : 1; + unsigned produce_flag : 1; + unsigned make_arrow_flag : 1; + unsigned potionpitcher_flag : 1; + unsigned storage_flag : 1; + } state; + struct { + unsigned killer : 1; + unsigned killable : 1; + unsigned restart_full_recover : 1; + unsigned no_castcancel : 1; + unsigned no_castcancel2 : 1; + unsigned no_sizefix : 1; + unsigned no_magic_damage : 1; + unsigned no_weapon_damage : 1; + unsigned no_gemstone : 1; + unsigned infinite_endure : 1; + unsigned unbreakable_weapon : 1; + unsigned unbreakable_armor : 1; + unsigned infinite_autospell : 1; + } special_state; + int char_id, login_id1, login_id2, sex; + int packet_ver; // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + struct mmo_charstatus status; + struct item_data *inventory_data[MAX_INVENTORY]; + short equip_index[11]; + int weight,max_weight; + int cart_weight,cart_max_weight,cart_num,cart_max_num; + char mapname[24]; + int fd,new_fd; + short to_x,to_y; + short speed,prev_speed; + short opt1,opt2,opt3; + char dir,head_dir; + unsigned int client_tick,server_tick; + struct walkpath_data walkpath; + int walktimer; + int npc_id,areanpc_id,npc_shopid; + int npc_pos; + int npc_menu; + int npc_amount; + int npc_stack,npc_stackmax; + char *npc_script,*npc_scriptroot; + char *npc_stackbuf; + char npc_str[256]; + unsigned int chatID; + + int attacktimer; + int attacktarget; + short attacktarget_lv; + unsigned int attackabletime; + + int followtimer; // [MouseJstr] + int followtarget; + + short attackrange,attackrange_; + int skilltimer; + int skilltarget; + short skillx,skilly; + short skillid,skilllv; + short skillitem,skillitemlv; + short skillid_old,skilllv_old; + short skillid_dance,skilllv_dance; + struct skill_unit_group skillunit[MAX_SKILLUNITGROUP]; + struct skill_unit_group_tickset skillunittick[MAX_SKILLUNITGROUPTICKSET]; + struct skill_timerskill skilltimerskill[MAX_SKILLTIMERSKILL]; + int cloneskill_id,cloneskill_lv; + int potion_hp,potion_sp,potion_per_hp,potion_per_sp; + + int invincible_timer; + unsigned int canact_tick; + unsigned int canmove_tick; + unsigned int canlog_tick; + int hp_sub,sp_sub; + int inchealhptick,inchealsptick,inchealspirithptick,inchealspiritsptick; +// -- moonsoul (new tick for berserk self-damage) + int berserkdamagetick; + int fame; + + short view_class; + short weapontype1,weapontype2; + short disguiseflag,disguise; // [Valaris] + int paramb[6],paramc[6],parame[6],paramcard[6]; + int hit,flee,flee2,aspd,amotion,dmotion; + int watk,watk2,atkmods[3]; + int def,def2,mdef,mdef2,critical,matk1,matk2; + int atk_ele,def_ele,star,overrefine; + int castrate,hprate,sprate,dsprate; + int addele[10],addrace[12],addsize[3],subele[10],subrace[12]; + int addeff[10],addeff2[10],reseff[10]; + int watk_,watk_2,atkmods_[3],addele_[10],addrace_[12],addsize_[3]; //二刀流のために追加 + int atk_ele_,star_,overrefine_; //二刀流のために追加 + int base_atk,atk_rate; + int arrow_atk,arrow_ele,arrow_cri,arrow_hit,arrow_range; + int arrow_addele[10],arrow_addrace[12],arrow_addsize[3],arrow_addeff[10],arrow_addeff2[10]; + int nhealhp,nhealsp,nshealhp,nshealsp,nsshealhp,nsshealsp; + int aspd_rate,speed_rate,hprecov_rate,sprecov_rate,critical_def,double_rate; + int near_attack_def_rate,long_attack_def_rate,magic_def_rate,misc_def_rate; + int matk_rate,ignore_def_ele,ignore_def_race,ignore_def_ele_,ignore_def_race_; + int ignore_mdef_ele,ignore_mdef_race; + int magic_addele[10],magic_addrace[12],magic_subrace[12]; + int perfect_hit,get_zeny_num; + int critical_rate,hit_rate,flee_rate,flee2_rate,def_rate,def2_rate,mdef_rate,mdef2_rate; + int def_ratio_atk_ele,def_ratio_atk_ele_,def_ratio_atk_race,def_ratio_atk_race_; + int add_damage_class_count,add_damage_class_count_,add_magic_damage_class_count; + short add_damage_classid[10],add_damage_classid_[10],add_magic_damage_classid[10]; + int add_damage_classrate[10],add_damage_classrate_[10],add_magic_damage_classrate[10]; + short add_def_class_count,add_mdef_class_count; + short add_def_classid[10],add_mdef_classid[10]; + int add_def_classrate[10],add_mdef_classrate[10]; + short monster_drop_item_count; + short monster_drop_itemid[10]; + int monster_drop_race[10],monster_drop_itemrate[10]; + int double_add_rate,speed_add_rate,aspd_add_rate,perfect_hit_add, get_zeny_add_num; + short splash_range,splash_add_range; + short autospell_id,autospell_lv,autospell_rate; + short hp_drain_rate,hp_drain_per,sp_drain_rate,sp_drain_per; + short hp_drain_rate_,hp_drain_per_,sp_drain_rate_,sp_drain_per_; + int short_weapon_damage_return,long_weapon_damage_return; + int weapon_coma_ele[10],weapon_coma_race[12]; + short break_weapon_rate,break_armor_rate; + short add_steal_rate; + + short spiritball, spiritball_old; + int spirit_timer[MAX_SKILL_LEVEL]; + int magic_damage_return; // AppleGirl Was Here + int random_attack_increase_add,random_attack_increase_per; // [Valaris] + int perfect_hiding; // [Valaris] + int unbreakable; + + int die_counter; + short doridori_counter; + + int reg_num; + struct script_reg *reg; + int regstr_num; + struct script_regstr *regstr; + + struct status_change sc_data[MAX_STATUSCHANGE]; + short sc_count; + struct square dev; + + int trade_partner; + int deal_item_index[10]; + int deal_item_amount[10]; + int deal_zeny; + short deal_locked; + + int party_sended,party_invite,party_invite_account; + int party_hp,party_x,party_y; + + int guild_sended,guild_invite,guild_invite_account; + int guild_emblem_id,guild_alliance,guild_alliance_account; + int guildspy; // [Syrus22] + int partyspy; // [Syrus22] + + int vender_id; + int vend_num; + char message[80]; + struct vending vending[12]; + + int catch_target_class; + struct s_pet pet; + struct pet_db *petDB; + struct pet_data *pd; + int pet_hungry_timer; + + int pvp_point,pvp_rank,pvp_timer,pvp_lastusers; + + char eventqueue[MAX_EVENTQUEUE][50]; + int eventtimer[MAX_EVENTTIMER]; + + int last_skillid,last_skilllv; // Added by RoVeRT + struct{ + char name[24]; + } ignore[80]; + int ignoreAll; + short sg_count; + +#ifndef TXT_ONLY + int mail_counter; // mail counter for mail system [Valaris] +#endif + +}; + +struct npc_timerevent_list { + int timer,pos; +}; +struct npc_label_list { + char name[24]; + int pos; +}; +struct npc_item_list { + int nameid,value; +}; +struct npc_data { + struct block_list bl; + short n; + short class,dir; + short speed; + char name[24]; + char exname[24]; + int chat_id; + short opt1,opt2,opt3,option; + short flag; + union { + struct { + char *script; + short xs,ys; + int guild_id; + int timer,timerid,timeramount,nexttimer; + unsigned int timertick; + struct npc_timerevent_list *timer_event; + int label_list_num; + struct npc_label_list *label_list; + int src_id; + } scr; + struct npc_item_list shop_item[1]; + struct { + short xs,ys; + short x,y; + char name[16]; + } warp; + } u; + // ここにメンバを追加してはならない(shop_itemが可変長の為) + + char eventqueue[MAX_EVENTQUEUE][50]; + int eventtimer[MAX_EVENTTIMER]; + short arenaflag; +}; +struct mob_data { + struct block_list bl; + short n; + short base_class,class,dir,mode; + short m,x0,y0,xs,ys; + char name[24]; + int spawndelay1,spawndelay2; + struct { + unsigned state : 8; + unsigned skillstate : 8; + unsigned targettype : 1; + unsigned steal_flag : 1; + unsigned steal_coin_flag : 1; + unsigned skillcastcancel : 1; + unsigned master_check : 1; + unsigned change_walk_target : 1; + unsigned walk_easy : 1; + unsigned special_mob_ai : 3; + } state; + int timer; + short to_x,to_y; + short speed; + int hp; + int target_id,attacked_id; + short target_lv; + struct walkpath_data walkpath; + unsigned int next_walktime; + unsigned int attackabletime; + unsigned int last_deadtime,last_spawntime,last_thinktime; + unsigned int canmove_tick; + short move_fail_count; + struct { + int id; + int dmg; + } dmglog[DAMAGELOG_SIZE]; + struct item *lootitem; + short lootitem_count; + + struct status_change sc_data[MAX_STATUSCHANGE]; + short sc_count; + short opt1,opt2,opt3,option; + short min_chase; + short sg_count; + int guild_id; + int deletetimer; + + int skilltimer; + int skilltarget; + short skillx,skilly; + short skillid,skilllv,skillidx; + unsigned int skilldelay[MAX_MOBSKILL]; + int def_ele; + int master_id,master_dist; + int exclusion_src,exclusion_party,exclusion_guild; + struct skill_timerskill skilltimerskill[MAX_MOBSKILLTIMERSKILL]; + struct skill_unit_group skillunit[MAX_MOBSKILLUNITGROUP]; + struct skill_unit_group_tickset skillunittick[MAX_SKILLUNITGROUPTICKSET]; + char npc_event[50]; + short size; +}; +struct pet_data { + struct block_list bl; + short n; + short class,dir; + short speed; + char name[24]; + struct { + unsigned state : 8 ; + unsigned skillstate : 8 ; + unsigned change_walk_target : 1 ; + } state; + int timer; + short to_x,to_y; + short equip; + struct walkpath_data walkpath; + int target_id; + short target_lv; + int move_fail_count; + unsigned int attackabletime,next_walktime,last_thinktime; + int skilltype,skillval,skilltimer,skillduration; // [Valaris] + int skillbonustype,skillbonusval,skillbonustimer,skillbonusduration; // [Valaris] + struct item *lootitem; + short loot; // [Valaris] + short lootmax; // [Valaris] + short lootitem_count; + short lootitem_weight; + int lootitem_timer; + struct skill_timerskill skilltimerskill[MAX_MOBSKILLTIMERSKILL]; // [Valaris] + struct skill_unit_group skillunit[MAX_MOBSKILLUNITGROUP]; // [Valaris] + struct skill_unit_group_tickset skillunittick[MAX_SKILLUNITGROUPTICKSET]; // [Valaris] + struct map_session_data *msd; +}; + +enum { MS_IDLE,MS_WALK,MS_ATTACK,MS_DEAD,MS_DELAY }; + +enum { NONE_ATTACKABLE,ATTACKABLE }; + +enum { ATK_LUCKY=1,ATK_FLEE,ATK_DEF}; // 囲まれペナルティ計算用 + +struct map_data { + char name[24]; + char alias[24]; // [MouseJstr] + unsigned char *gat; // NULLなら下のmap_data_other_serverとして扱う + struct block_list **block; + struct block_list **block_mob; + int *block_count,*block_mob_count; + int m; + short xs,ys; + short bxs,bys; + int npc_num; + int users; + struct { + unsigned alias : 1; + unsigned nomemo : 1; + unsigned noteleport : 1; + unsigned noreturn : 1; + unsigned monster_noteleport : 1; + unsigned nosave : 1; + unsigned nobranch : 1; + unsigned nopenalty : 1; + unsigned pvp : 1; + unsigned pvp_noparty : 1; + unsigned pvp_noguild : 1; + unsigned pvp_nightmaredrop :1; + unsigned pvp_nocalcrank : 1; + unsigned gvg : 1; + unsigned gvg_noparty : 1; + unsigned nozenypenalty : 1; + unsigned notrade : 1; + unsigned noskill : 1; + unsigned nowarp : 1; + unsigned nowarpto : 1; + unsigned nopvp : 1; // [Valaris] + unsigned noicewall : 1; // [Valaris] + unsigned snow : 1; // [Valaris] + unsigned fog : 1; // [Valaris] + unsigned sakura : 1; // [Valaris] + unsigned leaves : 1; // [Valaris] + unsigned rain : 1; // [Valaris] + } flag; + struct point save; + struct npc_data *npc[MAX_NPC_PER_MAP]; + struct { + int drop_id; + int drop_type; + int drop_per; + } drop_list[MAX_DROP_PER_MAP]; +}; +struct map_data_other_server { + char name[24]; + unsigned char *gat; // NULL固定にして判断 + unsigned long ip; + unsigned int port; +}; +#define read_gat(m,x,y) (map[m].gat[(x)+(y)*map[m].xs]) +#define read_gatp(m,x,y) (m->gat[(x)+(y)*m->xs]) + +struct flooritem_data { + struct block_list bl; + short subx,suby; + int cleartimer; + int first_get_id,second_get_id,third_get_id; + unsigned int first_get_tick,second_get_tick,third_get_tick; + struct item item_data; +}; + +enum { + SP_SPEED,SP_BASEEXP,SP_JOBEXP,SP_KARMA,SP_MANNER,SP_HP,SP_MAXHP,SP_SP, // 0-7 + SP_MAXSP,SP_STATUSPOINT,SP_0a,SP_BASELEVEL,SP_SKILLPOINT,SP_STR,SP_AGI,SP_VIT, // 8-15 + SP_INT,SP_DEX,SP_LUK,SP_CLASS,SP_ZENY,SP_SEX,SP_NEXTBASEEXP,SP_NEXTJOBEXP, // 16-23 + SP_WEIGHT,SP_MAXWEIGHT,SP_1a,SP_1b,SP_1c,SP_1d,SP_1e,SP_1f, // 24-31 + SP_USTR,SP_UAGI,SP_UVIT,SP_UINT,SP_UDEX,SP_ULUK,SP_26,SP_27, // 32-39 + SP_28,SP_ATK1,SP_ATK2,SP_MATK1,SP_MATK2,SP_DEF1,SP_DEF2,SP_MDEF1, // 40-47 + SP_MDEF2,SP_HIT,SP_FLEE1,SP_FLEE2,SP_CRITICAL,SP_ASPD,SP_36,SP_JOBLEVEL, // 48-55 + SP_UPPER,SP_PARTNER,SP_CART,SP_FAME,SP_UNBREAKABLE, //56-58 + SP_CARTINFO=99, // 99 + + // original 1000- + SP_ATTACKRANGE=1000, SP_ATKELE,SP_DEFELE, // 1000-1002 + SP_CASTRATE, SP_MAXHPRATE, SP_MAXSPRATE, SP_SPRATE, // 1003-1006 + SP_ADDELE, SP_ADDRACE, SP_ADDSIZE, SP_SUBELE, SP_SUBRACE, // 1007-1011 + SP_ADDEFF, SP_RESEFF, // 1012-1013 + SP_BASE_ATK,SP_ASPD_RATE,SP_HP_RECOV_RATE,SP_SP_RECOV_RATE,SP_SPEED_RATE, // 1014-1018 + SP_CRITICAL_DEF,SP_NEAR_ATK_DEF,SP_LONG_ATK_DEF, // 1019-1021 + SP_DOUBLE_RATE, SP_DOUBLE_ADD_RATE, SP_MATK, SP_MATK_RATE, // 1022-1025 + SP_IGNORE_DEF_ELE,SP_IGNORE_DEF_RACE, // 1026-1027 + SP_ATK_RATE,SP_SPEED_ADDRATE,SP_ASPD_ADDRATE, // 1028-1030 + SP_MAGIC_ATK_DEF,SP_MISC_ATK_DEF, // 1031-1032 + SP_IGNORE_MDEF_ELE,SP_IGNORE_MDEF_RACE, // 1033-1034 + SP_MAGIC_ADDELE,SP_MAGIC_ADDRACE,SP_MAGIC_SUBRACE, // 1035-1037 + SP_PERFECT_HIT_RATE,SP_PERFECT_HIT_ADD_RATE,SP_CRITICAL_RATE,SP_GET_ZENY_NUM,SP_ADD_GET_ZENY_NUM, // 1038-1042 + SP_ADD_DAMAGE_CLASS,SP_ADD_MAGIC_DAMAGE_CLASS,SP_ADD_DEF_CLASS,SP_ADD_MDEF_CLASS, // 1043-1046 + SP_ADD_MONSTER_DROP_ITEM,SP_DEF_RATIO_ATK_ELE,SP_DEF_RATIO_ATK_RACE,SP_ADD_SPEED, // 1047-1050 + SP_HIT_RATE,SP_FLEE_RATE,SP_FLEE2_RATE,SP_DEF_RATE,SP_DEF2_RATE,SP_MDEF_RATE,SP_MDEF2_RATE, // 1051-1057 + SP_SPLASH_RANGE,SP_SPLASH_ADD_RANGE,SP_AUTOSPELL,SP_HP_DRAIN_RATE,SP_SP_DRAIN_RATE, // 1058-1062 + SP_SHORT_WEAPON_DAMAGE_RETURN,SP_LONG_WEAPON_DAMAGE_RETURN,SP_WEAPON_COMA_ELE,SP_WEAPON_COMA_RACE, // 1063-1066 + SP_ADDEFF2,SP_BREAK_WEAPON_RATE,SP_BREAK_ARMOR_RATE,SP_ADD_STEAL_RATE, // 1067-1070 + SP_MAGIC_DAMAGE_RETURN,SP_RANDOM_ATTACK_INCREASE,SP_ALL_STATS,SP_AGI_VIT,SP_AGI_DEX_STR,SP_PERFECT_HIDE, // 1071-1077 + SP_DISGUISE, // 1077 + + SP_RESTART_FULL_RECORVER=2000,SP_NO_CASTCANCEL,SP_NO_SIZEFIX,SP_NO_MAGIC_DAMAGE,SP_NO_WEAPON_DAMAGE,SP_NO_GEMSTONE, // 2000-2005 + SP_NO_CASTCANCEL2,SP_INFINITE_ENDURE,SP_UNBREAKABLE_WEAPON,SP_UNBREAKABLE_ARMOR // 2006-2009 +}; + +enum { + LOOK_BASE,LOOK_HAIR,LOOK_WEAPON,LOOK_HEAD_BOTTOM,LOOK_HEAD_TOP,LOOK_HEAD_MID,LOOK_HAIR_COLOR,LOOK_CLOTHES_COLOR,LOOK_SHIELD,LOOK_SHOES +}; + +struct chat_data { + struct block_list bl; + + unsigned char pass[8]; /* password */ + unsigned char title[61]; /* room title MAX 60 */ + unsigned char limit; /* join limit */ + unsigned char trigger; + unsigned char users; /* current users */ + unsigned char pub; /* room attribute */ + struct map_session_data *usersd[20]; + struct block_list *owner_; + struct block_list **owner; + char npc_event[50]; +}; + +extern struct map_data map[]; +extern int map_num; +extern int autosave_interval; +extern int agit_flag; +extern int night_flag; // 0=day, 1=night [Yor] + +extern char motd_txt[]; +extern char help_txt[]; + +extern char talkie_mes[]; + +extern char wisp_server_name[]; + +// 鯖全体情報 +void map_setusers(int); +int map_getusers(void); +// block削除関連 +int map_freeblock( void *bl ); +int map_freeblock_lock(void); +int map_freeblock_unlock(void); +// block関連 +int map_addblock(struct block_list *); +int map_delblock(struct block_list *); +void map_foreachinarea(int (*)(struct block_list*,va_list),int,int,int,int,int,int,...); +// -- moonsoul (added map_foreachincell) +void map_foreachincell(int (*)(struct block_list*,va_list),int,int,int,int,...); +void map_foreachinmovearea(int (*)(struct block_list*,va_list),int,int,int,int,int,int,int,int,...); +int map_countnearpc(int,int,int); +//block関連に追加 +int map_count_oncell(int m,int x,int y); +// 一時的object関連 +int map_addobject(struct block_list *); +int map_delobject(int); +int map_delobjectnofree(int id); +void map_foreachobject(int (*)(struct block_list*,va_list),int,...); +// +int map_quit(struct map_session_data *); +// npc +int map_addnpc(int,struct npc_data *); + +// 床アイテム関連 +int map_clearflooritem_timer(int,unsigned int,int,int); +#define map_clearflooritem(id) map_clearflooritem_timer(0,0,id,1) +int map_addflooritem(struct item *,int,int,int,int,struct map_session_data *,struct map_session_data *,struct map_session_data *,int); +int map_searchrandfreecell(int,int,int,int); + +// キャラid=>キャラ名 変換関連 +void map_addchariddb(int charid,char *name); +void map_delchariddb(int charid); +int map_reqchariddb(struct map_session_data * sd,int charid); +char * map_charid2nick(int); + +struct map_session_data * map_id2sd(int); +struct block_list * map_id2bl(int); +int map_mapname2mapid(char*); +int map_mapname2ipport(char*,int*,int*); +int map_setipport(char *name,unsigned long ip,int port); +int map_eraseipport(char *name,unsigned long ip,int port); +void map_addiddb(struct block_list *); +void map_deliddb(struct block_list *bl); +int map_foreachiddb(int (*)(void*,void*,va_list),...); +void map_addnickdb(struct map_session_data *); +struct map_session_data * map_nick2sd(char*); + +// gat関連 +int map_getcell(int,int,int); +int map_setcell(int,int,int,int); + +// その他 +int map_check_dir(int s_dir,int t_dir); +int map_calc_dir( struct block_list *src,int x,int y); + +// path.cより +int path_search(struct walkpath_data*,int,int,int,int,int,int); +int path_blownpos(int m,int x0,int y0,int dx,int dy,int count); + +int map_who(int fd); + +void map_helpscreen(); // [Valaris] +int map_delmap(char *mapname); + +#ifndef TXT_ONLY + +// MySQL +#include <mysql.h> + +void char_online_check(void); // [Valaris] +void char_offline(struct map_session_data *sd); + +extern MYSQL mmysql_handle; +extern char tmp_sql[65535]; +extern MYSQL_RES* sql_res ; +extern MYSQL_ROW sql_row ; + +extern MYSQL lmysql_handle; +extern char tmp_lsql[65535]; +extern MYSQL_RES* lsql_res ; +extern MYSQL_ROW lsql_row ; + +extern MYSQL mail_handle; +extern MYSQL_RES* mail_res ; +extern MYSQL_ROW mail_row ; +extern char tmp_msql[65535]; + +extern int db_use_sqldbs; + +extern char item_db_db[32]; +extern char mob_db_db[32]; +extern char login_db[32]; + +extern char login_db_level[32]; +extern char login_db_account_id[32]; + +extern int lowest_gm_level; +extern int read_gm_interval; + +extern char char_db[32]; +#endif /* not TXT_ONLY */ + +#endif diff --git a/misc/src/map/mob.c b/misc/src/map/mob.c new file mode 100644 index 0000000..969eed2 --- /dev/null +++ b/misc/src/map/mob.c @@ -0,0 +1,4216 @@ +// $Id: mob.c,v 1.7 2004/09/25 05:32:18 MouseJstr Exp $ +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <string.h> + +#include "timer.h" +#include "socket.h" +#include "db.h" +#include "nullpo.h" +#include "malloc.h" +#include "map.h" +#include "clif.h" +#include "intif.h" +#include "pc.h" +#include "mob.h" +#include "guild.h" +#include "itemdb.h" +#include "skill.h" +#include "battle.h" +#include "party.h" +#include "npc.h" + +#ifdef MEMWATCH +#include "memwatch.h" +#endif + +#define MIN_MOBTHINKTIME 100 + +#define MOB_LAZYMOVEPERC 50 // Move probability in the negligent mode MOB (rate of 1000 minute) +#define MOB_LAZYWARPPERC 20 // Warp probability in the negligent mode MOB (rate of 1000 minute) + +struct mob_db mob_db[2001]; + +/*========================================== + * Local prototype declaration (only required thing) + *------------------------------------------ + */ +static int distance(int,int,int,int); +static int mob_makedummymobdb(int); +static int mob_timer(int,unsigned int,int,int); +int mobskill_use(struct mob_data *md,unsigned int tick,int event); +int mobskill_deltimer(struct mob_data *md ); +int mob_skillid2skillidx(int class,int skillid); +int mobskill_use_id(struct mob_data *md,struct block_list *target,int skill_idx); +static int mob_unlocktarget(struct mob_data *md,int tick); + +/*========================================== + * Mob is searched with a name. + *------------------------------------------ + */ +int mobdb_searchname(const char *str) +{ + int i; + + for(i = 0; i < sizeof(mob_db) / sizeof(mob_db[0]); i++) { + if (strcmpi(mob_db[i].name, str) == 0 || strcmp(mob_db[i].jname, str) == 0 || + memcmp(mob_db[i].name, str, 24) == 0 || memcmp(mob_db[i].jname, str, 24) == 0) + return i; + } + + return 0; +} + +/*========================================== + * Id Mob is checked. + *------------------------------------------ + */ +int mobdb_checkid(const int id) +{ + if (id <= 0 || id >= (sizeof(mob_db) / sizeof(mob_db[0])) || mob_db[id].name[0] == '\0') + return 0; + + return id; +} + +/*========================================== + * The minimum data set for MOB spawning + *------------------------------------------ + */ +int mob_spawn_dataset(struct mob_data *md,const char *mobname,int class) +{ + nullpo_retr(0, md); + + md->bl.prev=NULL; + md->bl.next=NULL; + if(strcmp(mobname,"--en--")==0) + memcpy(md->name,mob_db[class].name,24); + else if(strcmp(mobname,"--ja--")==0) + memcpy(md->name,mob_db[class].jname,24); + else + memcpy(md->name,mobname,24); + + md->n = 0; + md->base_class = md->class = class; + md->bl.id= npc_get_new_npc_id(); + + memset(&md->state,0,sizeof(md->state)); + md->timer = -1; + md->target_id=0; + md->attacked_id=0; + md->speed=mob_db[class].speed; + + return 0; +} + + +/*========================================== + * The MOB appearance for one time (for scripts) + *------------------------------------------ + */ +int mob_once_spawn(struct map_session_data *sd,char *mapname, + int x,int y,const char *mobname,int class,int amount,const char *event) +{ + struct mob_data *md=NULL; + int m,count,lv=255,r=class; + + if( sd ) + lv=sd->status.base_level; + + if( sd && strcmp(mapname,"this")==0) + m=sd->bl.m; + else + m=map_mapname2mapid(mapname); + + if(m<0 || amount<=0 || (class>=0 && class<=1000) || class>2000) // 値が異常なら召喚を止める + return 0; + + if(class<0){ // ランダムに召喚 + int i=0; + int j=-class-1; + int k; + if(j>=0 && j<MAX_RANDOMMONSTER){ + do{ + class=rand()%1000+1001; + k=rand()%1000000; + }while((mob_db[class].max_hp <= 0 || mob_db[class].summonper[j] <= k || + (lv<mob_db[class].lv && battle_config.random_monster_checklv==1)) && (i++) < 2000); + if(i>=2000){ + class=mob_db[0].summonper[j]; + } + }else{ + return 0; + } +// if(battle_config.etc_log==1) +// printf("mobclass=%d try=%d\n",class,i); + } + if(sd){ + if(x<=0) x=sd->bl.x; + if(y<=0) y=sd->bl.y; + }else if(x<=0 || y<=0){ + printf("mob_once_spawn: ??\n"); + } + + for(count=0;count<amount;count++){ + md=(struct mob_data *)aCalloc(1,sizeof(struct mob_data)); + memset(md, '\0', sizeof *md); + if(mob_db[class].mode&0x02) + md->lootitem=(struct item *)aCalloc(LOOTITEM_SIZE,sizeof(struct item)); + else + md->lootitem=NULL; + + mob_spawn_dataset(md,mobname,class); + md->bl.m=m; + md->bl.x=x; + md->bl.y=y; + if(r<0&&battle_config.dead_branch_active==1) md->mode=0x1+0x4+0x80; //移動してアクティブで反撃する + md->m =m; + md->x0=x; + md->y0=y; + md->xs=0; + md->ys=0; + md->spawndelay1=-1; // Only once is a flag. + md->spawndelay2=-1; // Only once is a flag. + + memcpy(md->npc_event,event,sizeof(md->npc_event)); + + md->bl.type=BL_MOB; + map_addiddb(&md->bl); + mob_spawn(md->bl.id); + + if(class==1288) { // emperium hp based on defense level [Valaris] + struct guild_castle *gc=guild_mapname2gc(map[md->bl.m].name); + if(gc) { + mob_db[class].max_hp+=2000*gc->defense; + md->hp=mob_db[class].max_hp; + } + } // end addition [Valaris] + + + } + return (amount>0)?md->bl.id:0; +} +/*========================================== + * The MOB appearance for one time (& area specification for scripts) + *------------------------------------------ + */ +int mob_once_spawn_area(struct map_session_data *sd,char *mapname, + int x0,int y0,int x1,int y1, + const char *mobname,int class,int amount,const char *event) +{ + int x,y,i,c,max,lx=-1,ly=-1,id=0; + int m; + + if(strcmp(mapname,"this")==0) + m=sd->bl.m; + else + m=map_mapname2mapid(mapname); + + max=(y1-y0+1)*(x1-x0+1)*3; + if(max>1000)max=1000; + + if(m<0 || amount<=0 || (class>=0 && class<=1000) || class>2000) // A summon is stopped if a value is unusual + return 0; + + for(i=0;i<amount;i++){ + int j=0; + do{ + x=rand()%(x1-x0+1)+x0; + y=rand()%(y1-y0+1)+y0; + }while( ( (c=map_getcell(m,x,y))==1 || c==5)&& (++j)<max ); + if(j>=max){ + if(lx>=0){ // Since reference went wrong, the place which boiled before is used. + x=lx; + y=ly; + }else + return 0; // Since reference of the place which boils first went wrong, it stops. + } + id=mob_once_spawn(sd,mapname,x,y,mobname,class,1,event); + lx=x; + ly=y; + } + return id; +} + +/*========================================== + * Summoning Guardians [Valaris] + *------------------------------------------ + */ +int mob_spawn_guardian(struct map_session_data *sd,char *mapname, + int x,int y,const char *mobname,int class,int amount,const char *event,int guardian) +{ + struct mob_data *md=NULL; + int m,count=1,lv=255; + + if( sd ) + lv=sd->status.base_level; + + if( sd && strcmp(mapname,"this")==0) + m=sd->bl.m; + else + m=map_mapname2mapid(mapname); + + if(m<0 || amount<=0 || (class>=0 && class<=1000) || class>2000) // 値が異常なら召喚を止める + return 0; + + if(class<0) + return 0; + + if(sd){ + if(x<=0) x=sd->bl.x; + if(y<=0) y=sd->bl.y; + } + + else if(x<=0 || y<=0) + printf("mob_spawn_guardian: ??\n"); + + + for(count=0;count<amount;count++){ + struct guild_castle *gc; + md=calloc(sizeof(struct mob_data), 1); + if(md==NULL){ + printf("mob_spawn_guardian: out of memory !\n"); + exit(1); + } + memset(md, '\0', sizeof *md); + + + + mob_spawn_dataset(md,mobname,class); + md->bl.m=m; + md->bl.x=x; + md->bl.y=y; + md->m =m; + md->x0=x; + md->y0=y; + md->xs=0; + md->ys=0; + md->spawndelay1=-1; // Only once is a flag. + md->spawndelay2=-1; // Only once is a flag. + + memcpy(md->npc_event,event,sizeof(md->npc_event)); + + md->bl.type=BL_MOB; + map_addiddb(&md->bl); + mob_spawn(md->bl.id); + + gc=guild_mapname2gc(map[md->bl.m].name); + if(gc) { + mob_db[class].max_hp+=2000*gc->defense; + if(guardian==0) { md->hp=gc->Ghp0; gc->GID0=md->bl.id; } + if(guardian==1) { md->hp=gc->Ghp1; gc->GID1=md->bl.id; } + if(guardian==2) { md->hp=gc->Ghp2; gc->GID2=md->bl.id; } + if(guardian==3) { md->hp=gc->Ghp3; gc->GID3=md->bl.id; } + if(guardian==4) { md->hp=gc->Ghp4; gc->GID4=md->bl.id; } + if(guardian==5) { md->hp=gc->Ghp5; gc->GID5=md->bl.id; } + if(guardian==6) { md->hp=gc->Ghp6; gc->GID6=md->bl.id; } + if(guardian==7) { md->hp=gc->Ghp7; gc->GID7=md->bl.id; } + + } + } + + return (amount>0)?md->bl.id:0; +} + + +/*========================================== + * Appearance income of mob + *------------------------------------------ + */ +int mob_get_viewclass(int class) +{ + return mob_db[class].view_class; +} +int mob_get_sex(int class) +{ + return mob_db[class].sex; +} +short mob_get_hair(int class) +{ + return mob_db[class].hair; +} +short mob_get_hair_color(int class) +{ + return mob_db[class].hair_color; +} +short mob_get_weapon(int class) +{ + return mob_db[class].weapon; +} +short mob_get_shield(int class) +{ + return mob_db[class].shield; +} +short mob_get_head_top(int class) +{ + return mob_db[class].head_top; +} +short mob_get_head_mid(int class) +{ + return mob_db[class].head_mid; +} +short mob_get_head_buttom(int class) +{ + return mob_db[class].head_buttom; +} +short mob_get_clothes_color(int class) // Add for player monster dye - Valaris +{ + return mob_db[class].clothes_color; // End +} +int mob_get_equip(int class) // mob equip [Valaris] +{ + return mob_db[class].equip; +} +/*========================================== + * Is MOB in the state in which the present movement is possible or not? + *------------------------------------------ + */ +int mob_can_move(struct mob_data *md) +{ + nullpo_retr(0, md); + + if(md->canmove_tick > gettick() || (md->opt1 > 0 && md->opt1 != 6) || md->option&2) + return 0; + // アンクル中で動けないとか + if( md->sc_data[SC_ANKLE].timer != -1 || //アンクルスネア + md->sc_data[SC_AUTOCOUNTER].timer != -1 || //オートカウンター + md->sc_data[SC_BLADESTOP].timer != -1 || //白刃取り + md->sc_data[SC_SPIDERWEB].timer != -1 //スパイダーウェッブ + ) + return 0; + + return 1; +} + +/*========================================== + * Time calculation concerning one step next to mob + *------------------------------------------ + */ +static int calc_next_walk_step(struct mob_data *md) +{ + nullpo_retr(0, md); + + if(md->walkpath.path_pos>=md->walkpath.path_len) + return -1; + if(md->walkpath.path[md->walkpath.path_pos]&1) + return battle_get_speed(&md->bl)*14/10; + return battle_get_speed(&md->bl); +} + +static int mob_walktoxy_sub(struct mob_data *md); + +/*========================================== + * Mob Walk processing + *------------------------------------------ + */ +static int mob_walk(struct mob_data *md,unsigned int tick,int data) +{ + int moveblock; + int i,ctype; + static int dirx[8]={0,-1,-1,-1,0,1,1,1}; + static int diry[8]={1,1,0,-1,-1,-1,0,1}; + int x,y,dx,dy; + + nullpo_retr(0, md); + + md->state.state=MS_IDLE; + if(md->walkpath.path_pos>=md->walkpath.path_len || md->walkpath.path_pos!=data) + return 0; + + md->walkpath.path_half ^= 1; + if(md->walkpath.path_half==0){ + md->walkpath.path_pos++; + if(md->state.change_walk_target){ + mob_walktoxy_sub(md); + return 0; + } + } + else { + if(md->walkpath.path[md->walkpath.path_pos]>=8) + return 1; + + x = md->bl.x; + y = md->bl.y; + ctype = map_getcell(md->bl.m,x,y); + if(ctype == 1 || ctype == 5) { + mob_stop_walking(md,1); + return 0; + } + md->dir=md->walkpath.path[md->walkpath.path_pos]; + dx = dirx[md->dir]; + dy = diry[md->dir]; + + ctype = map_getcell(md->bl.m,x+dx,y+dy); + if(ctype == 1 || ctype == 5) { + mob_walktoxy_sub(md); + return 0; + } + + moveblock = ( x/BLOCK_SIZE != (x+dx)/BLOCK_SIZE || y/BLOCK_SIZE != (y+dy)/BLOCK_SIZE); + + md->state.state=MS_WALK; + map_foreachinmovearea(clif_moboutsight,md->bl.m,x-AREA_SIZE,y-AREA_SIZE,x+AREA_SIZE,y+AREA_SIZE,dx,dy,BL_PC,md); + + x += dx; + y += dy; + if(md->min_chase>13) + md->min_chase--; + + if(moveblock) map_delblock(&md->bl); + md->bl.x = x; + md->bl.y = y; + if(moveblock) map_addblock(&md->bl); + + map_foreachinmovearea(clif_mobinsight,md->bl.m,x-AREA_SIZE,y-AREA_SIZE,x+AREA_SIZE,y+AREA_SIZE,-dx,-dy,BL_PC,md); + md->state.state=MS_IDLE; + + if(md->option&4) + skill_check_cloaking(&md->bl); + + skill_unit_move(&md->bl,tick,1); // Inspection of a skill unit + } + if((i=calc_next_walk_step(md))>0){ + i = i>>1; + if(i < 1 && md->walkpath.path_half == 0) + i = 1; + md->timer=add_timer(tick+i,mob_timer,md->bl.id,md->walkpath.path_pos); + md->state.state=MS_WALK; + + if(md->walkpath.path_pos>=md->walkpath.path_len) + clif_fixmobpos(md); // When mob stops, retransmission current of a position. + } + return 0; +} + +/*========================================== + * Attack processing of mob + *------------------------------------------ + */ +static int mob_attack(struct mob_data *md,unsigned int tick,int data) +{ + struct block_list *tbl=NULL; + struct map_session_data *tsd=NULL; + struct mob_data *tmd=NULL; + + int mode,race,range; + + nullpo_retr(0, md); + + md->min_chase=13; + md->state.state=MS_IDLE; + md->state.skillstate=MSS_IDLE; + + if( md->skilltimer!=-1 ) // スキル使用中 + return 0; + + if(md->opt1>0 || md->option&2) + return 0; + + if(md->sc_data[SC_AUTOCOUNTER].timer != -1) + return 0; + + if(md->sc_data[SC_BLADESTOP].timer != -1) + return 0; + + if((tbl=map_id2bl(md->target_id))==NULL){ + md->target_id=0; + md->state.targettype = NONE_ATTACKABLE; + return 0; + } + + if(tbl->type==BL_PC) + tsd=(struct map_session_data *)tbl; + else if(tbl->type==BL_MOB) + tmd=(struct mob_data *)tbl; + else + return 0; + + if(tsd){ + if( pc_isdead(tsd) || tsd->invincible_timer != -1 || pc_isinvisible(tsd) || md->bl.m != tbl->m || tbl->prev == NULL || distance(md->bl.x,md->bl.y,tbl->x,tbl->y)>=13 ){ + md->target_id=0; + md->state.targettype = NONE_ATTACKABLE; + return 0; + } + } + if(tmd){ + if(md->bl.m != tbl->m || tbl->prev == NULL || distance(md->bl.x,md->bl.y,tbl->x,tbl->y)>=13){ + md->target_id=0; + md->state.targettype = NONE_ATTACKABLE; + return 0; + } + } + + + if(!md->mode) + mode=mob_db[md->class].mode; + else + mode=md->mode; + + race=mob_db[md->class].race; + if(!(mode&0x80)){ + md->target_id=0; + md->state.targettype = NONE_ATTACKABLE; + return 0; + } + if(tsd && !(mode&0x20) && (tsd->sc_data[SC_TRICKDEAD].timer != -1 || + ((pc_ishiding(tsd) || tsd->state.gangsterparadise) && race!=4 && race!=6) ) ) { + md->target_id=0; + md->state.targettype = NONE_ATTACKABLE; + return 0; + } + + range = mob_db[md->class].range; + if(mode&1) + range++; + if(distance(md->bl.x,md->bl.y,tbl->x,tbl->y) > range) + return 0; + if(battle_config.monster_attack_direction_change) + md->dir=map_calc_dir(&md->bl, tbl->x,tbl->y ); // 向き設定 + + //clif_fixmobpos(md); + + md->state.skillstate=MSS_ATTACK; + if( mobskill_use(md,tick,-2) ) // スキル使用 + return 0; + + md->target_lv = battle_weapon_attack(&md->bl,tbl,tick,0); + + if(!(battle_config.monster_cloak_check_type&2) && md->sc_data[SC_CLOAKING].timer != -1) + skill_status_change_end(&md->bl,SC_CLOAKING,-1); + + md->attackabletime = tick + battle_get_adelay(&md->bl); + + md->timer=add_timer(md->attackabletime,mob_timer,md->bl.id,0); + md->state.state=MS_ATTACK; + + return 0; +} + + +/*========================================== + * The attack of PC which is attacking id is stopped. + * The callback function of clif_foreachclient + *------------------------------------------ + */ +int mob_stopattacked(struct map_session_data *sd,va_list ap) +{ + int id; + + nullpo_retr(0, sd); + nullpo_retr(0, ap); + + id=va_arg(ap,int); + if(sd->attacktarget==id) + pc_stopattack(sd); + return 0; +} +/*========================================== + * The timer in which the mob's states changes + *------------------------------------------ + */ +int mob_changestate(struct mob_data *md,int state,int type) +{ + unsigned int tick; + int i; + + nullpo_retr(0, md); + + if(md->timer != -1) + delete_timer(md->timer,mob_timer); + md->timer=-1; + md->state.state=state; + + switch(state){ + case MS_WALK: + if((i=calc_next_walk_step(md))>0){ + i = i>>2; + md->timer=add_timer(gettick()+i,mob_timer,md->bl.id,0); + } + else + md->state.state=MS_IDLE; + break; + case MS_ATTACK: + tick = gettick(); + i=DIFF_TICK(md->attackabletime,tick); + if(i>0 && i<2000) + md->timer=add_timer(md->attackabletime,mob_timer,md->bl.id,0); + else if(type) { + md->attackabletime = tick + battle_get_amotion(&md->bl); + md->timer=add_timer(md->attackabletime,mob_timer,md->bl.id,0); + } + else { + md->attackabletime = tick + 1; + md->timer=add_timer(md->attackabletime,mob_timer,md->bl.id,0); + } + break; + case MS_DELAY: + md->timer=add_timer(gettick()+type,mob_timer,md->bl.id,0); + break; + case MS_DEAD: + skill_castcancel(&md->bl,0); +// mobskill_deltimer(md); + md->state.skillstate=MSS_DEAD; + md->last_deadtime=gettick(); + // Since it died, all aggressors' attack to this mob is stopped. + clif_foreachclient(mob_stopattacked,md->bl.id); + skill_unit_out_all(&md->bl,gettick(),1); + skill_status_change_clear(&md->bl,2); // The abnormalities in status are canceled. + skill_clear_unitgroup(&md->bl); // All skill unit groups are deleted. + skill_cleartimerskill(&md->bl); + if(md->deletetimer!=-1) + delete_timer(md->deletetimer,mob_timer_delete); + md->deletetimer=-1; + md->hp=md->target_id=md->attacked_id=0; + md->state.targettype = NONE_ATTACKABLE; + break; + } + + return 0; +} + +/*========================================== + * timer processing of mob (timer function) + * It branches to a walk and an attack. + *------------------------------------------ + */ +static int mob_timer(int tid,unsigned int tick,int id,int data) +{ + struct mob_data *md; + struct block_list *bl; + + if( (bl=map_id2bl(id)) == NULL ){ //攻撃してきた敵がもういないのは正常のようだ + return 1; + } + + if(!bl || !bl->type || bl->type!=BL_MOB) + return 1; + + nullpo_retr(1, md=(struct mob_data*)bl); + + if(!md->bl.type || md->bl.type!=BL_MOB) + return 1; + + if(md->timer != tid){ + if(battle_config.error_log==1) + printf("mob_timer %d != %d\n",md->timer,tid); + return 0; + } + md->timer=-1; + if(md->bl.prev == NULL || md->state.state == MS_DEAD) + return 1; + + map_freeblock_lock(); + switch(md->state.state){ + case MS_WALK: + mob_walk(md,tick,data); + break; + case MS_ATTACK: + mob_attack(md,tick,data); + break; + case MS_DELAY: + mob_changestate(md,MS_IDLE,0); + break; + default: + if(battle_config.error_log==1) + printf("mob_timer : %d ?\n",md->state.state); + break; + } + map_freeblock_unlock(); + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +static int mob_walktoxy_sub(struct mob_data *md) +{ + struct walkpath_data wpd; + + nullpo_retr(0, md); + + if(path_search(&wpd,md->bl.m,md->bl.x,md->bl.y,md->to_x,md->to_y,md->state.walk_easy)) + return 1; + memcpy(&md->walkpath,&wpd,sizeof(wpd)); + + md->state.change_walk_target=0; + mob_changestate(md,MS_WALK,0); + clif_movemob(md); + + return 0; +} + +/*========================================== + * mob move start + *------------------------------------------ + */ +int mob_walktoxy(struct mob_data *md,int x,int y,int easy) +{ + struct walkpath_data wpd; + + nullpo_retr(0, md); + + if(md->state.state == MS_WALK && path_search(&wpd,md->bl.m,md->bl.x,md->bl.y,x,y,easy) ) + return 1; + + md->state.walk_easy = easy; + md->to_x=x; + md->to_y=y; + if(md->state.state == MS_WALK) { + md->state.change_walk_target=1; + } else { + return mob_walktoxy_sub(md); + } + + return 0; +} + +/*========================================== + * mob spawn with delay (timer function) + *------------------------------------------ + */ +static int mob_delayspawn(int tid,unsigned int tick,int m,int n) +{ + mob_spawn(m); + return 0; +} + +/*========================================== + * spawn timing calculation + *------------------------------------------ + */ +int mob_setdelayspawn(int id) +{ + unsigned int spawntime,spawntime1,spawntime2,spawntime3; + struct mob_data *md; + struct block_list *bl; + + if((bl=map_id2bl(id)) == NULL) + return -1; + + if(!bl || !bl->type || bl->type!=BL_MOB) + return -1; + + nullpo_retr(-1, md=(struct mob_data*)bl); + + if(!md || md->bl.type!=BL_MOB) + return -1; + + // Processing of MOB which is not revitalized + if(md->spawndelay1==-1 && md->spawndelay2==-1 && md->n==0){ + map_deliddb(&md->bl); + if(md->lootitem) { + map_freeblock(md->lootitem); + md->lootitem=NULL; + } + map_freeblock(md); // Instead of [ of free ] + return 0; + } + + spawntime1=md->last_spawntime+md->spawndelay1; + spawntime2=md->last_deadtime+md->spawndelay2; + spawntime3=gettick()+5000; + // spawntime = max(spawntime1,spawntime2,spawntime3); + if(DIFF_TICK(spawntime1,spawntime2)>0){ + spawntime=spawntime1; + } else { + spawntime=spawntime2; + } + if(DIFF_TICK(spawntime3,spawntime)>0){ + spawntime=spawntime3; + } + + add_timer(spawntime,mob_delayspawn,id,0); + return 0; +} + +/*========================================== + * Mob spawning. Initialization is also variously here. + *------------------------------------------ + */ +int mob_spawn(int id) +{ + int x=0,y=0,i=0,c; + unsigned int tick = gettick(); + struct mob_data *md; + struct block_list *bl; + + nullpo_retr(-1, bl=map_id2bl(id)); + + if(!bl || !bl->type || bl->type!=BL_MOB) + return -1; + + nullpo_retr(-1, md=(struct mob_data*)bl); + + if(!md || !md->bl.type || md->bl.type!=BL_MOB) + return -1; + + md->last_spawntime=tick; + if( md->bl.prev!=NULL ){ +// clif_clearchar_area(&md->bl,3); + skill_unit_out_all(&md->bl,gettick(),1); + map_delblock(&md->bl); + } + else + md->class = md->base_class; + + md->bl.m =md->m; + do { + if(md->x0==0 && md->y0==0){ + x=rand()%(map[md->bl.m].xs-2)+1; + y=rand()%(map[md->bl.m].ys-2)+1; + } else { + x=md->x0+rand()%(md->xs+1)-md->xs/2; + y=md->y0+rand()%(md->ys+1)-md->ys/2; + } + i++; + } while(((c=map_getcell(md->bl.m,x,y))==1 || c==5) && i<50); + + if(i>=50){ +// if(battle_config.error_log==1) +// printf("MOB spawn error %d @ %s\n",id,map[md->bl.m].name); + add_timer(tick+5000,mob_delayspawn,id,0); + return 1; + } + + md->to_x=md->bl.x=x; + md->to_y=md->bl.y=y; + md->dir=0; + + map_addblock(&md->bl); + + memset(&md->state,0,sizeof(md->state)); + md->attacked_id = 0; + md->target_id = 0; + md->move_fail_count = 0; + + if(!md->speed) + md->speed = mob_db[md->class].speed; + md->def_ele = mob_db[md->class].element; + md->master_id=0; + md->master_dist=0; + + md->state.state = MS_IDLE; + md->state.skillstate = MSS_IDLE; + md->timer = -1; + md->last_thinktime = tick; + md->next_walktime = tick+rand()%50+5000; + md->attackabletime = tick; + md->canmove_tick = tick; + + md->sg_count=0; + md->deletetimer=-1; + + md->skilltimer=-1; + for(i=0,c=tick-1000*3600*10;i<MAX_MOBSKILL;i++) + md->skilldelay[i] = c; + md->skillid=0; + md->skilllv=0; + + memset(md->dmglog,0,sizeof(md->dmglog)); + if(md->lootitem) + memset(md->lootitem,0,sizeof(md->lootitem)); + md->lootitem_count = 0; + + for(i=0;i<MAX_MOBSKILLTIMERSKILL;i++) + md->skilltimerskill[i].timer = -1; + + for(i=0;i<MAX_STATUSCHANGE;i++) { + md->sc_data[i].timer=-1; + md->sc_data[i].val1 = md->sc_data[i].val2 = md->sc_data[i].val3 = md->sc_data[i].val4 =0; + } + md->sc_count=0; + md->opt1=md->opt2=md->opt3=md->option=0; + + memset(md->skillunit,0,sizeof(md->skillunit)); + memset(md->skillunittick,0,sizeof(md->skillunittick)); + + md->hp = battle_get_max_hp(&md->bl); + if(md->hp<=0){ + mob_makedummymobdb(md->class); + md->hp = battle_get_max_hp(&md->bl); + } + + clif_spawnmob(md); + + return 0; +} + +/*========================================== + * Distance calculation between two points + *------------------------------------------ + */ +static int distance(int x0,int y0,int x1,int y1) +{ + int dx,dy; + + dx=abs(x0-x1); + dy=abs(y0-y1); + return dx>dy ? dx : dy; +} + +/*========================================== + * The stop of MOB's attack + *------------------------------------------ + */ +int mob_stopattack(struct mob_data *md) +{ + md->target_id=0; + md->state.targettype = NONE_ATTACKABLE; + md->attacked_id=0; + return 0; +} +/*========================================== + * The stop of MOB's walking + *------------------------------------------ + */ +int mob_stop_walking(struct mob_data *md,int type) +{ + nullpo_retr(0, md); + + + if(md->state.state == MS_WALK || md->state.state == MS_IDLE) { + int dx=0,dy=0; + + md->walkpath.path_len=0; + if(type&4){ + dx=md->to_x-md->bl.x; + if(dx<0) + dx=-1; + else if(dx>0) + dx=1; + dy=md->to_y-md->bl.y; + if(dy<0) + dy=-1; + else if(dy>0) + dy=1; + } + md->to_x=md->bl.x+dx; + md->to_y=md->bl.y+dy; + if(dx!=0 || dy!=0){ + mob_walktoxy_sub(md); + return 0; + } + mob_changestate(md,MS_IDLE,0); + } + if(type&0x01) + clif_fixmobpos(md); + if(type&0x02) { + int delay=battle_get_dmotion(&md->bl); + unsigned int tick = gettick(); + if(md->canmove_tick < tick) + md->canmove_tick = tick + delay; + } + + return 0; +} + +/*========================================== + * Reachability to a Specification ID existence place + *------------------------------------------ + */ +int mob_can_reach(struct mob_data *md,struct block_list *bl,int range) +{ + int dx,dy; + struct walkpath_data wpd; + int i; + + nullpo_retr(0, md); + nullpo_retr(0, bl); + + dx=abs(bl->x - md->bl.x); + dy=abs(bl->y - md->bl.y); + + //=========== guildcastle guardian no search start=========== + //when players are the guild castle member not attack them ! + if(md->class == 1285 || md->class == 1286 || md->class == 1287){ + struct map_session_data *sd; + struct guild *g=NULL; + struct guild_castle *gc=guild_mapname2gc(map[bl->m].name); + + if(gc && agit_flag==0) // Guardians will not attack during non-woe time [Valaris] + return 0; // end addition [Valaris] + + if(bl && bl->type == BL_PC){ + if((sd=(struct map_session_data *)bl) == NULL){ + printf("mob_can_reach nullpo\n"); + return 0; + } + + if(gc && sd && sd->status.guild_id && sd->status.guild_id>0) { + g=guild_search(sd->status.guild_id); // don't attack guild members [Valaris] + if(g && g->guild_id > 0 && g->guild_id == gc->guild_id) + return 0; + if(g && gc && guild_isallied(g,gc)) + return 0; + + } + } + } + //========== guildcastle guardian no search eof============== + + if(bl && bl->type == BL_PC && battle_config.monsters_ignore_gm==1) { // option to have monsters ignore GMs [Valaris] + struct map_session_data *sd; + if((sd=(struct map_session_data *)bl) != NULL && pc_isGM(sd)) + return 0; + } + + if( md->bl.m != bl->m) // 違うャbプ + return 0; + + if( range>0 && range < ((dx>dy)?dx:dy) ) // 遠すぎる + return 0; + + if( md->bl.x==bl->x && md->bl.y==bl->y ) // 同じャX + return 1; + + // Obstacle judging + wpd.path_len=0; + wpd.path_pos=0; + wpd.path_half=0; + if(path_search(&wpd,md->bl.m,md->bl.x,md->bl.y,bl->x,bl->y,0)!=-1) + return 1; + + if(bl->type!=BL_PC && bl->type!=BL_MOB) + return 0; + + // It judges whether it can adjoin or not. + dx=(dx>0)?1:((dx<0)?-1:0); + dy=(dy>0)?1:((dy<0)?-1:0); + if(path_search(&wpd,md->bl.m,md->bl.x,md->bl.y,bl->x-dx,bl->y-dy,0)!=-1) + return 1; + for(i=0;i<9;i++){ + if(path_search(&wpd,md->bl.m,md->bl.x,md->bl.y,bl->x-1+i/3,bl->y-1+i%3,0)!=-1) + return 1; + } + return 0; +} + +/*========================================== + * Determination for an attack of a monster + *------------------------------------------ + */ +int mob_target(struct mob_data *md,struct block_list *bl,int dist) +{ + struct map_session_data *sd; + struct status_change *sc_data; + short *option; + int mode,race; + + nullpo_retr(0, md); + nullpo_retr(0, bl); + + sc_data = battle_get_sc_data(bl); + option = battle_get_option(bl); + race=mob_db[md->class].race; + + if(!md->mode){ + mode=mob_db[md->class].mode; + }else{ + mode=md->mode; + } + if(!(mode&0x80)) { + md->target_id = 0; + return 0; + } + // Nothing will be carried out if there is no mind of changing TAGE by TAGE ending. + if( (md->target_id > 0 && md->state.targettype == ATTACKABLE) && ( !(mode&0x04) || rand()%100>25) ) + return 0; + + if(mode&0x20 || // Coercion is exerted if it is MVPMOB. + (sc_data && sc_data[SC_TRICKDEAD].timer == -1 && + ( (option && !(*option&0x06) ) || race==4 || race==6) ) ){ + if(bl->type == BL_PC) { + nullpo_retr(0, sd = (struct map_session_data *)bl); + if(sd->invincible_timer != -1 || pc_isinvisible(sd)) + return 0; + if(!(mode&0x20) && race!=4 && race!=6 && sd->state.gangsterparadise) + return 0; + } + + md->target_id=bl->id; // Since there was no disturbance, it locks on to target. + if(bl->type == BL_PC || bl->type == BL_MOB) + md->state.targettype = ATTACKABLE; + else + md->state.targettype = NONE_ATTACKABLE; + md->min_chase=dist+13; + if(md->min_chase>26) + md->min_chase=26; + } + return 0; +} + +/*========================================== + * The ?? routine of an active monster + *------------------------------------------ + */ +static int mob_ai_sub_hard_activesearch(struct block_list *bl,va_list ap) +{ + struct map_session_data *tsd=NULL; + struct mob_data *smd,*tmd=NULL; + int mode,race,dist,*pcc; + + nullpo_retr(0, bl); + nullpo_retr(0, ap); + nullpo_retr(0, smd=va_arg(ap,struct mob_data *)); + nullpo_retr(0, pcc=va_arg(ap,int *)); + + if(bl->type==BL_PC) + tsd=(struct map_session_data *)bl; + else if(bl->type==BL_MOB) + tmd=(struct mob_data *)bl; + else + return 0; + + //敵味方判定 + if(battle_check_target(&smd->bl,bl,BCT_ENEMY)==0) + return 0; + + if(!smd->mode) + mode=mob_db[smd->class].mode; + else + mode=smd->mode; + + // アクティブでターゲット射程内にいるなら、ロックする + if( mode&0x04 ){ + race=mob_db[smd->class].race; + //対象がPCの場合 + if(tsd && + !pc_isdead(tsd) && + tsd->bl.m == smd->bl.m && + tsd->invincible_timer == -1 && + !pc_isinvisible(tsd) && + (dist=distance(smd->bl.x,smd->bl.y,tsd->bl.x,tsd->bl.y))<9 + ) + { + if(mode&0x20 || + (tsd->sc_data[SC_TRICKDEAD].timer == -1 && + ((!pc_ishiding(tsd) && !tsd->state.gangsterparadise) || race==4 || race==6))){ // 妨害がないか判定 + if( mob_can_reach(smd,bl,12) && // 到達可能性判定 + rand()%1000<1000/(++(*pcc)) ){ // 範囲内PCで等確率にする + smd->target_id=tsd->bl.id; + smd->state.targettype = ATTACKABLE; + smd->min_chase=13; + } + } + } + //対象がMobの場合 + else if(tmd && + tmd->bl.m == smd->bl.m && + (dist=distance(smd->bl.x,smd->bl.y,tmd->bl.x,tmd->bl.y))<9 + ) + { + if( mob_can_reach(smd,bl,12) && // 到達可能性判定 + rand()%1000<1000/(++(*pcc)) ){ // 範囲内で等確率にする + smd->target_id=bl->id; + smd->state.targettype = ATTACKABLE; + smd->min_chase=13; + } + } + } + return 0; +} + +/*========================================== + * loot monster item search + *------------------------------------------ + */ +static int mob_ai_sub_hard_lootsearch(struct block_list *bl,va_list ap) +{ + struct mob_data* md; + int mode,dist,*itc; + + nullpo_retr(0, bl); + nullpo_retr(0, ap); + nullpo_retr(0, md=va_arg(ap,struct mob_data *)); + nullpo_retr(0, itc=va_arg(ap,int *)); + + if(!md->mode){ + mode=mob_db[md->class].mode; + }else{ + mode=md->mode; + } + + if( !md->target_id && mode&0x02){ + if(!md->lootitem || (battle_config.monster_loot_type == 1 && md->lootitem_count >= LOOTITEM_SIZE) ) + return 0; + if(bl->m == md->bl.m && (dist=distance(md->bl.x,md->bl.y,bl->x,bl->y))<9){ + if( mob_can_reach(md,bl,12) && // Reachability judging + rand()%1000<1000/(++(*itc)) ){ // It is made a probability, such as within the limits PC. + md->target_id=bl->id; + md->state.targettype = NONE_ATTACKABLE; + md->min_chase=13; + } + } + } + return 0; +} + +/*========================================== + * The ?? routine of a link monster + *------------------------------------------ + */ +static int mob_ai_sub_hard_linksearch(struct block_list *bl,va_list ap) +{ + struct mob_data *tmd; + struct mob_data* md; + struct block_list *target; + + nullpo_retr(0, bl); + nullpo_retr(0, ap); + nullpo_retr(0, tmd=(struct mob_data *)bl); + nullpo_retr(0, md=va_arg(ap,struct mob_data *)); + nullpo_retr(0, target=va_arg(ap,struct block_list *)); + + // same family free in a range at a link monster -- it will be made to lock if MOB is +/* if( (md->target_id > 0 && md->state.targettype == ATTACKABLE) && mob_db[md->class].mode&0x08){ + if( tmd->class==md->class && (!tmd->target_id || md->state.targettype == NONE_ATTACKABLE) && tmd->bl.m == md->bl.m){ + if( mob_can_reach(tmd,target,12) ){ // Reachability judging + tmd->target_id=md->target_id; + tmd->state.targettype = ATTACKABLE; + tmd->min_chase=13; + } + } + }*/ + if( md->attacked_id > 0 && mob_db[md->class].mode&0x08){ + if( tmd->class==md->class && tmd->bl.m == md->bl.m && (!tmd->target_id || md->state.targettype == NONE_ATTACKABLE)){ + if( mob_can_reach(tmd,target,12) ){ // Reachability judging + tmd->target_id=md->attacked_id; + tmd->state.targettype = ATTACKABLE; + tmd->min_chase=13; + } + } + } + + return 0; +} +/*========================================== + * Processing of slave monsters + *------------------------------------------ + */ +static int mob_ai_sub_hard_slavemob(struct mob_data *md,unsigned int tick) +{ + struct mob_data *mmd=NULL; + struct block_list *bl; + int mode,race,old_dist; + + nullpo_retr(0, md); + + if((bl=map_id2bl(md->master_id)) != NULL ) + mmd=(struct mob_data *)bl; + + mode=mob_db[md->class].mode; + + // It is not main monster/leader. + if(!mmd || mmd->bl.type!=BL_MOB || mmd->bl.id!=md->master_id) + return 0; + + // Since it is in the map on which the master is not, teleport is carried out and it pursues. + if( mmd->bl.m != md->bl.m ){ + mob_warp(md,mmd->bl.m,mmd->bl.x,mmd->bl.y,3); + md->state.master_check = 1; + return 0; + } + + // Distance with between slave and master is measured. + old_dist=md->master_dist; + md->master_dist=distance(md->bl.x,md->bl.y,mmd->bl.x,mmd->bl.y); + + // Since the master was in near immediately before, teleport is carried out and it pursues. + if( old_dist<10 && md->master_dist>18){ + mob_warp(md,-1,mmd->bl.x,mmd->bl.y,3); + md->state.master_check = 1; + return 0; + } + + // Although there is the master, since it is somewhat far, it approaches. + if((!md->target_id || md->state.targettype == NONE_ATTACKABLE) && mob_can_move(md) && + (md->walkpath.path_pos>=md->walkpath.path_len || md->walkpath.path_len==0) && md->master_dist<15){ + int i=0,dx,dy,ret; + if(md->master_dist>4) { + do { + if(i<=5){ + dx=mmd->bl.x - md->bl.x; + dy=mmd->bl.y - md->bl.y; + if(dx<0) dx+=(rand()%( (dx<-3)?3:-dx )+1); + else if(dx>0) dx-=(rand()%( (dx>3)?3:dx )+1); + if(dy<0) dy+=(rand()%( (dy<-3)?3:-dy )+1); + else if(dy>0) dy-=(rand()%( (dy>3)?3:dy )+1); + }else{ + dx=mmd->bl.x - md->bl.x + rand()%7 - 3; + dy=mmd->bl.y - md->bl.y + rand()%7 - 3; + } + + ret=mob_walktoxy(md,md->bl.x+dx,md->bl.y+dy,0); + i++; + } while(ret && i<10); + } + else { + do { + dx = rand()%9 - 5; + dy = rand()%9 - 5; + if( dx == 0 && dy == 0) { + dx = (rand()%1)? 1:-1; + dy = (rand()%1)? 1:-1; + } + dx += mmd->bl.x; + dy += mmd->bl.y; + + ret=mob_walktoxy(md,mmd->bl.x+dx,mmd->bl.y+dy,0); + i++; + } while(ret && i<10); + } + + md->next_walktime=tick+500; + md->state.master_check = 1; + } + + // There is the master, the master locks a target and he does not lock. + if( (mmd->target_id>0 && mmd->state.targettype == ATTACKABLE) && (!md->target_id || md->state.targettype == NONE_ATTACKABLE) ){ + struct map_session_data *sd=map_id2sd(mmd->target_id); + if(sd!=NULL && !pc_isdead(sd) && sd->invincible_timer == -1 && !pc_isinvisible(sd)){ + + race=mob_db[md->class].race; + if(mode&0x20 || + (sd->sc_data[SC_TRICKDEAD].timer == -1 && + ( (!pc_ishiding(sd) && !sd->state.gangsterparadise) || race==4 || race==6) ) ){ // 妨害がないか判定 + + md->target_id=sd->bl.id; + md->state.targettype = ATTACKABLE; + md->min_chase=5+distance(md->bl.x,md->bl.y,sd->bl.x,sd->bl.y); + md->state.master_check = 1; + } + } + } + + // There is the master, the master locks a target and he does not lock. +/* if( (md->target_id>0 && mmd->state.targettype == ATTACKABLE) && (!mmd->target_id || mmd->state.targettype == NONE_ATTACKABLE) ){ + struct map_session_data *sd=map_id2sd(md->target_id); + if(sd!=NULL && !pc_isdead(sd) && sd->invincible_timer == -1 && !pc_isinvisible(sd)){ + + race=mob_db[mmd->class].race; + if(mode&0x20 || + (sd->sc_data[SC_TRICKDEAD].timer == -1 && + (!(sd->status.option&0x06) || race==4 || race==6) + ) ){ // It judges whether there is any disturbance. + + mmd->target_id=sd->bl.id; + mmd->state.targettype = ATTACKABLE; + mmd->min_chase=5+distance(mmd->bl.x,mmd->bl.y,sd->bl.x,sd->bl.y); + } + } + }*/ + + return 0; +} + +/*========================================== + * A lock of target is stopped and mob moves to a standby state. + *------------------------------------------ + */ +static int mob_unlocktarget(struct mob_data *md,int tick) +{ + nullpo_retr(0, md); + + md->target_id=0; + md->state.targettype = NONE_ATTACKABLE; + md->state.skillstate=MSS_IDLE; + md->next_walktime=tick+rand()%3000+3000; + return 0; +} +/*========================================== + * Random walk + *------------------------------------------ + */ +static int mob_randomwalk(struct mob_data *md,int tick) +{ + const int retrycount=20; + int speed; + + nullpo_retr(0, md); + + speed=battle_get_speed(&md->bl); + if(DIFF_TICK(md->next_walktime,tick)<0){ + int i,x,y,c,d=12-md->move_fail_count; + if(d<5) d=5; + for(i=0;i<retrycount;i++){ // Search of a movable place + int r=rand(); + x=md->bl.x+r%(d*2+1)-d; + y=md->bl.y+r/(d*2+1)%(d*2+1)-d; + if((c=map_getcell(md->bl.m,x,y))!=1 && c!=5 && mob_walktoxy(md,x,y,1)==0){ + md->move_fail_count=0; + break; + } + if(i+1>=retrycount){ + md->move_fail_count++; + if(md->move_fail_count>1000){ + if(battle_config.error_log==1) + printf("MOB cant move. random spawn %d, class = %d\n",md->bl.id,md->class); + md->move_fail_count=0; + mob_spawn(md->bl.id); + } + } + } + for(i=c=0;i<md->walkpath.path_len;i++){ // The next walk start time is calculated. + if(md->walkpath.path[i]&1) + c+=speed*14/10; + else + c+=speed; + } + md->next_walktime = tick+rand()%3000+3000+c; + md->state.skillstate=MSS_WALK; + return 1; + } + return 0; +} + +/*========================================== + * AI of MOB whose is near a Player + *------------------------------------------ + */ +static int mob_ai_sub_hard(struct block_list *bl,va_list ap) +{ + struct mob_data *md,*tmd=NULL; + struct map_session_data *tsd=NULL; + struct block_list *tbl=NULL; + struct flooritem_data *fitem; + unsigned int tick; + int i,dx,dy,ret,dist; + int attack_type=0; + int mode,race; + + nullpo_retr(0, bl); + nullpo_retr(0, ap); + nullpo_retr(0, md=(struct mob_data*)bl); + + tick=va_arg(ap,unsigned int); + + + if(DIFF_TICK(tick,md->last_thinktime)<MIN_MOBTHINKTIME) + return 0; + md->last_thinktime=tick; + + if( md->skilltimer!=-1 || md->bl.prev==NULL ){ // Under a skill aria and death + if(DIFF_TICK(tick,md->next_walktime)>MIN_MOBTHINKTIME) + md->next_walktime=tick; + return 0; + } + + if(!md->mode) + mode=mob_db[md->class].mode; + else + mode=md->mode; + + race=mob_db[md->class].race; + + // Abnormalities + if((md->opt1 > 0 && md->opt1 != 6) || md->state.state==MS_DELAY || md->sc_data[SC_BLADESTOP].timer != -1) + return 0; + + if(!(mode&0x80) && md->target_id > 0) + md->target_id = 0; + + if(md->attacked_id > 0 && mode&0x08){ // Link monster + struct map_session_data *asd=map_id2sd(md->attacked_id); + if(asd){ + if(asd->invincible_timer == -1 && !pc_isinvisible(asd)){ + map_foreachinarea(mob_ai_sub_hard_linksearch,md->bl.m, + md->bl.x-13,md->bl.y-13, + md->bl.x+13,md->bl.y+13, + BL_MOB,md,&asd->bl); + } + } + } + + // It checks to see it was attacked first (if active, it is target change at 25% of probability). + if( mode>0 && md->attacked_id>0 && (!md->target_id || md->state.targettype == NONE_ATTACKABLE + || (mode&0x04 && rand()%100<25 ) ) ){ + struct block_list *abl=map_id2bl(md->attacked_id); + struct map_session_data *asd=NULL; + if(abl){ + if(abl->type==BL_PC) + asd=(struct map_session_data *)abl; + if(asd==NULL || md->bl.m != abl->m || abl->prev == NULL || asd->invincible_timer != -1 || pc_isinvisible(asd) || + (dist=distance(md->bl.x,md->bl.y,abl->x,abl->y))>=32 || battle_check_target(bl,abl,BCT_ENEMY)==0) + md->attacked_id=0; + else { + md->target_id=md->attacked_id; // set target + md->state.targettype = ATTACKABLE; + attack_type = 1; + md->attacked_id=0; + md->min_chase=dist+13; + if(md->min_chase>26) + md->min_chase=26; + } + } + } + + md->state.master_check = 0; + // Processing of slave monster + if( md->master_id > 0 && md->state.special_mob_ai==0) + mob_ai_sub_hard_slavemob(md,tick); + + // アクティヴモンスターの策敵 (?? of a bitter taste TIVU monster) + if( (!md->target_id || md->state.targettype == NONE_ATTACKABLE) && mode&0x04 && !md->state.master_check && + battle_config.monster_active_enable==1){ + i=0; + if(md->state.special_mob_ai){ + map_foreachinarea(mob_ai_sub_hard_activesearch,md->bl.m, + md->bl.x-AREA_SIZE*2,md->bl.y-AREA_SIZE*2, + md->bl.x+AREA_SIZE*2,md->bl.y+AREA_SIZE*2, + 0,md,&i); + }else{ + map_foreachinarea(mob_ai_sub_hard_activesearch,md->bl.m, + md->bl.x-AREA_SIZE*2,md->bl.y-AREA_SIZE*2, + md->bl.x+AREA_SIZE*2,md->bl.y+AREA_SIZE*2, + BL_PC,md,&i); + } + } + + // The item search of a route monster + if( !md->target_id && mode&0x02 && !md->state.master_check){ + i=0; + map_foreachinarea(mob_ai_sub_hard_lootsearch,md->bl.m, + md->bl.x-AREA_SIZE*2,md->bl.y-AREA_SIZE*2, + md->bl.x+AREA_SIZE*2,md->bl.y+AREA_SIZE*2, + BL_ITEM,md,&i); + } + + // It will attack, if the candidate for an attack is. + if(md->target_id > 0){ + if((tbl=map_id2bl(md->target_id))){ + if(tbl->type==BL_PC) + tsd=(struct map_session_data *)tbl; + else if(tbl->type==BL_MOB) + tmd=(struct mob_data *)tbl; + if(tsd || tmd) { + if(tbl->m != md->bl.m || tbl->prev == NULL || (dist=distance(md->bl.x,md->bl.y,tbl->x,tbl->y))>=md->min_chase) + mob_unlocktarget(md,tick); // 別マップか、視界外 + else if( tsd && !(mode&0x20) && (tsd->sc_data[SC_TRICKDEAD].timer != -1 || ((pc_ishiding(tsd) || tsd->state.gangsterparadise) && race!=4 && race!=6)) ) + mob_unlocktarget(md,tick); // スキルなどによる策敵妨害 + else if(!battle_check_range(&md->bl,tbl,mob_db[md->class].range)){ + // 攻撃範囲外なので移動 + if(!(mode&1)){ // 移動しないモード + mob_unlocktarget(md,tick); + return 0; + } + if( !mob_can_move(md) ) // 動けない状態にある + return 0; + md->state.skillstate=MSS_CHASE; // 突撃時スキル + mobskill_use(md,tick,-1); +// if(md->timer != -1 && (DIFF_TICK(md->next_walktime,tick)<0 || distance(md->to_x,md->to_y,tsd->bl.x,tsd->bl.y)<2) ) + if(md->timer != -1 && md->state.state!=MS_ATTACK && (DIFF_TICK(md->next_walktime,tick)<0 || distance(md->to_x,md->to_y,tbl->x,tbl->y)<2) ) + return 0; // 既に移動中 + if( !mob_can_reach(md,tbl,(md->min_chase>13)?md->min_chase:13) ) + mob_unlocktarget(md,tick); // 移動できないのでタゲ解除(IWとか?) + else{ + // 追跡 + md->next_walktime=tick+500; + i=0; + do { + if(i==0){ // 最初はAEGISと同じ方法で検索 + dx=tbl->x - md->bl.x; + dy=tbl->y - md->bl.y; + if(dx<0) dx++; + else if(dx>0) dx--; + if(dy<0) dy++; + else if(dy>0) dy--; + }else{ // だめならAthena式(ランダム) + dx=tbl->x - md->bl.x + rand()%3 - 1; + dy=tbl->y - md->bl.y + rand()%3 - 1; + } + /* if(path_search(&md->walkpath,md->bl.m,md->bl.x,md->bl.y,md->bl.x+dx,md->bl.y+dy,0)){ + dx=tsd->bl.x - md->bl.x; + dy=tsd->bl.y - md->bl.y; + if(dx<0) dx--; + else if(dx>0) dx++; + if(dy<0) dy--; + else if(dy>0) dy++; + }*/ + ret=mob_walktoxy(md,md->bl.x+dx,md->bl.y+dy,0); + i++; + } while(ret && i<5); + + if(ret){ // 移動不可能な所からの攻撃なら2歩下る + if(dx<0) dx=2; + else if(dx>0) dx=-2; + if(dy<0) dy=2; + else if(dy>0) dy=-2; + mob_walktoxy(md,md->bl.x+dx,md->bl.y+dy,0); + } + } + } else { // 攻撃射程範囲内 + md->state.skillstate=MSS_ATTACK; + if(md->state.state==MS_WALK) + mob_stop_walking(md,1); // 歩行中なら停止 + if(md->state.state==MS_ATTACK) + return 0; // 既に攻撃中 + mob_changestate(md,MS_ATTACK,attack_type); + +/* if(mode&0x08){ // リンクモンスター + map_foreachinarea(mob_ai_sub_hard_linksearch,md->bl.m, + md->bl.x-13,md->bl.y-13, + md->bl.x+13,md->bl.y+13, + BL_MOB,md,&tsd->bl); + }*/ + } + return 0; + }else{ // ルートモンスター処理 + if(tbl == NULL || tbl->type != BL_ITEM ||tbl->m != md->bl.m || + (dist=distance(md->bl.x,md->bl.y,tbl->x,tbl->y))>=md->min_chase || !md->lootitem){ + // 遠すぎるかアイテムがなくなった + mob_unlocktarget(md,tick); + if(md->state.state==MS_WALK) + mob_stop_walking(md,1); // 歩行中なら停止 + }else if(dist){ + if(!(mode&1)){ // 移動しないモード + mob_unlocktarget(md,tick); + return 0; + } + if( !mob_can_move(md) ) // 動けない状態にある + return 0; + md->state.skillstate=MSS_LOOT; // ルート時スキル使用 + mobskill_use(md,tick,-1); +// if(md->timer != -1 && (DIFF_TICK(md->next_walktime,tick)<0 || distance(md->to_x,md->to_y,tbl->x,tbl->y)<2) ) + if(md->timer != -1 && md->state.state!=MS_ATTACK && (DIFF_TICK(md->next_walktime,tick)<0 || distance(md->to_x,md->to_y,tbl->x,tbl->y) <= 0)) + return 0; // 既に移動中 + md->next_walktime=tick+500; + dx=tbl->x - md->bl.x; + dy=tbl->y - md->bl.y; +/* if(path_search(&md->walkpath,md->bl.m,md->bl.x,md->bl.y,md->bl.x+dx,md->bl.y+dy,0)){ + dx=tbl->x - md->bl.x; + dy=tbl->y - md->bl.y; + }*/ + ret=mob_walktoxy(md,md->bl.x+dx,md->bl.y+dy,0); + if(ret) + mob_unlocktarget(md,tick);// 移動できないのでタゲ解除(IWとか?) + }else{ // アイテムまでたどり着いた + if(md->state.state==MS_ATTACK) + return 0; // 攻撃中 + if(md->state.state==MS_WALK) + mob_stop_walking(md,1); // 歩行中なら停止 + fitem = (struct flooritem_data *)tbl; + if(md->lootitem_count < LOOTITEM_SIZE) + memcpy(&md->lootitem[md->lootitem_count++],&fitem->item_data,sizeof(md->lootitem[0])); + else if(battle_config.monster_loot_type == 1 && md->lootitem_count >= LOOTITEM_SIZE) { + mob_unlocktarget(md,tick); + return 0; + } + else { + if(md->lootitem[0].card[0] == (short)0xff00) + intif_delete_petdata(*((long *)(&md->lootitem[0].card[1]))); + for(i=0;i<LOOTITEM_SIZE-1;i++) + memcpy(&md->lootitem[i],&md->lootitem[i+1],sizeof(md->lootitem[0])); + memcpy(&md->lootitem[LOOTITEM_SIZE-1],&fitem->item_data,sizeof(md->lootitem[0])); + } + map_clearflooritem(tbl->id); + mob_unlocktarget(md,tick); + } + return 0; + } + }else{ + mob_unlocktarget(md,tick); + if(md->state.state==MS_WALK) + mob_stop_walking(md,4); // 歩行中なら停止 + return 0; + } + } + + // It is skill use at the time of /standby at the time of a walk. + if( mobskill_use(md,tick,-1) ) + return 0; + + // 歩行処理 + if( mode&1 && mob_can_move(md) && // 移動可能MOB&動ける状態にある + (md->master_id==0 || md->state.special_mob_ai || md->master_dist>10) ){ //取り巻きMOBじゃない + + if( DIFF_TICK(md->next_walktime,tick) > + 7000 && + (md->walkpath.path_len==0 || md->walkpath.path_pos>=md->walkpath.path_len) ){ + md->next_walktime = tick + 3000*rand()%2000; + } + + // Random movement + if( mob_randomwalk(md,tick) ) + return 0; + } + + // Since he has finished walking, it stands by. + if( md->walkpath.path_len==0 || md->walkpath.path_pos>=md->walkpath.path_len ) + md->state.skillstate=MSS_IDLE; + return 0; +} + +/*========================================== + * Serious processing for mob in PC field of view (foreachclient) + *------------------------------------------ + */ +static int mob_ai_sub_foreachclient(struct map_session_data *sd,va_list ap) +{ + unsigned int tick; + nullpo_retr(0, sd); + nullpo_retr(0, ap); + + tick=va_arg(ap,unsigned int); + map_foreachinarea(mob_ai_sub_hard,sd->bl.m, + sd->bl.x-AREA_SIZE*2,sd->bl.y-AREA_SIZE*2, + sd->bl.x+AREA_SIZE*2,sd->bl.y+AREA_SIZE*2, + BL_MOB,tick); + + return 0; +} + +/*========================================== + * Serious processing for mob in PC field of view (interval timer function) + *------------------------------------------ + */ +static int mob_ai_hard(int tid,unsigned int tick,int id,int data) +{ + clif_foreachclient(mob_ai_sub_foreachclient,tick); + + return 0; +} + +/*========================================== + * Negligent mode MOB AI (PC is not in near) + *------------------------------------------ + */ +static int mob_ai_sub_lazy(void * key,void * data,va_list app) +{ + struct mob_data *md=data; + unsigned int tick; + va_list ap; + + nullpo_retr(0, md); + nullpo_retr(0, app); + nullpo_retr(0, ap=va_arg(app,va_list)); + + if(md==NULL) + return 0; + + if(!md->bl.type || md->bl.type!=BL_MOB) + return 0; + + tick=va_arg(ap,unsigned int); + + if(DIFF_TICK(tick,md->last_thinktime)<MIN_MOBTHINKTIME*10) + return 0; + md->last_thinktime=tick; + + if(md->bl.prev==NULL || md->skilltimer!=-1){ + if(DIFF_TICK(tick,md->next_walktime)>MIN_MOBTHINKTIME*10) + md->next_walktime=tick; + return 0; + } + + if(DIFF_TICK(md->next_walktime,tick)<0 && + (mob_db[md->class].mode&1) && mob_can_move(md) ){ + + if( map[md->bl.m].users>0 ){ + // Since PC is in the same map, somewhat better negligent processing is carried out. + + // It sometimes moves. + if(rand()%1000<MOB_LAZYMOVEPERC) + mob_randomwalk(md,tick); + + // MOB which is not not the summons MOB but BOSS, either sometimes reboils. + else if( rand()%1000<MOB_LAZYWARPPERC && md->x0<=0 && md->master_id!=0 && + mob_db[md->class].mexp <= 0 && !(mob_db[md->class].mode & 0x20)) + mob_spawn(md->bl.id); + + }else{ + // Since PC is not even in the same map, suitable processing is carried out even if it takes. + + // MOB which is not BOSS which is not Summons MOB, either -- a case -- sometimes -- leaping + if( rand()%1000<MOB_LAZYWARPPERC && md->x0<=0 && md->master_id!=0 && + mob_db[md->class].mexp <= 0 && !(mob_db[md->class].mode & 0x20)) + mob_warp(md,-1,-1,-1,-1); + } + + md->next_walktime = tick+rand()%10000+5000; + } + return 0; +} + +/*========================================== + * Negligent processing for mob outside PC field of view (interval timer function) + *------------------------------------------ + */ +static int mob_ai_lazy(int tid,unsigned int tick,int id,int data) +{ + map_foreachiddb(mob_ai_sub_lazy,tick); + + return 0; +} + + +/*========================================== + * The structure object for item drop with delay + * Since it is only two being able to pass [ int ] a timer function + * Data is put in and passed to this structure object. + *------------------------------------------ + */ +struct delay_item_drop { + int m,x,y; + int nameid,amount; + struct map_session_data *first_sd,*second_sd,*third_sd; +}; + +struct delay_item_drop2 { + int m,x,y; + struct item item_data; + struct map_session_data *first_sd,*second_sd,*third_sd; +}; + +/*========================================== + * item drop with delay (timer function) + *------------------------------------------ + */ +static int mob_delay_item_drop(int tid,unsigned int tick,int id,int data) +{ + struct delay_item_drop *ditem; + struct item temp_item; + int flag; + + nullpo_retr(0, ditem=(struct delay_item_drop *)id); + + memset(&temp_item,0,sizeof(temp_item)); + temp_item.nameid = ditem->nameid; + temp_item.amount = ditem->amount; + temp_item.identify = !itemdb_isequip3(temp_item.nameid); + + if(battle_config.item_auto_get==1){ + if(ditem->first_sd && (flag = pc_additem(ditem->first_sd,&temp_item,ditem->amount))){ + clif_additem(ditem->first_sd,0,0,flag); + map_addflooritem(&temp_item,1,ditem->m,ditem->x,ditem->y,ditem->first_sd,ditem->second_sd,ditem->third_sd,0); + } + free(ditem); + return 0; + } + + map_addflooritem(&temp_item,1,ditem->m,ditem->x,ditem->y,ditem->first_sd,ditem->second_sd,ditem->third_sd,0); + + free(ditem); + return 0; +} + +/*========================================== + * item drop (timer function)-lootitem with delay + *------------------------------------------ + */ +static int mob_delay_item_drop2(int tid,unsigned int tick,int id,int data) +{ + struct delay_item_drop2 *ditem; + int flag; + + nullpo_retr(0, ditem=(struct delay_item_drop2 *)id); + + if(battle_config.item_auto_get==1){ + if(ditem->first_sd && (flag = pc_additem(ditem->first_sd,&ditem->item_data,ditem->item_data.amount))){ + clif_additem(ditem->first_sd,0,0,flag); + map_addflooritem(&ditem->item_data,ditem->item_data.amount,ditem->m,ditem->x,ditem->y,ditem->first_sd,ditem->second_sd,ditem->third_sd,0); + } + free(ditem); + return 0; + } + + map_addflooritem(&ditem->item_data,ditem->item_data.amount,ditem->m,ditem->x,ditem->y,ditem->first_sd,ditem->second_sd,ditem->third_sd,0); + + free(ditem); + return 0; +} + +/*========================================== + * mob data is erased. + *------------------------------------------ + */ +int mob_delete(struct mob_data *md) +{ + nullpo_retr(1, md); + + if(md->bl.prev == NULL) + return 1; + mob_changestate(md,MS_DEAD,0); + clif_clearchar_area(&md->bl,1); + map_delblock(&md->bl); + if(mob_get_viewclass(md->class) <= 1000) + clif_clearchar_delay(gettick()+3000,&md->bl,0); + mob_deleteslave(md); + mob_setdelayspawn(md->bl.id); + return 0; +} + +int mob_catch_delete(struct mob_data *md,int type) +{ + nullpo_retr(1, md); + + if(md->bl.prev == NULL) + return 1; + mob_changestate(md,MS_DEAD,0); + clif_clearchar_area(&md->bl,type); + map_delblock(&md->bl); + mob_setdelayspawn(md->bl.id); + return 0; +} + +int mob_timer_delete(int tid, unsigned int tick, int id, int data) +{ + struct block_list *bl=map_id2bl(id); + struct mob_data *md; + + nullpo_retr(0, bl); + + md = (struct mob_data *)bl; + mob_catch_delete(md,3); + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int mob_deleteslave_sub(struct block_list *bl,va_list ap) +{ + struct mob_data *md; + int id; + + nullpo_retr(0, bl); + nullpo_retr(0, ap); + nullpo_retr(0, md = (struct mob_data *)bl); + + id=va_arg(ap,int); + if(md->master_id > 0 && md->master_id == id ) + mob_damage(NULL,md,md->hp,1); + return 0; +} +/*========================================== + * + *------------------------------------------ + */ +int mob_deleteslave(struct mob_data *md) +{ + nullpo_retr(0, md); + + map_foreachinarea(mob_deleteslave_sub, md->bl.m, + 0,0,map[md->bl.m].xs,map[md->bl.m].ys, + BL_MOB,md->bl.id); + return 0; +} + +/*========================================== + * It is the damage of sd to damage to md. + *------------------------------------------ + */ +int mob_damage(struct block_list *src,struct mob_data *md,int damage,int type) +{ + int i,count,minpos,mindmg; + struct map_session_data *sd = NULL,*tmpsd[DAMAGELOG_SIZE]; + struct { + struct party *p; + int id,base_exp,job_exp; + } pt[DAMAGELOG_SIZE]; + int pnum=0; + int mvp_damage,max_hp; + unsigned int tick = gettick(); + struct map_session_data *mvp_sd = NULL, *second_sd = NULL,*third_sd = NULL; + double dmg_rate,tdmg,temp; + struct item item; + int ret; + int drop_rate; + int skill,sp; + + nullpo_retr(0, md); //srcはNULLで呼ばれる場合もあるので、他でチェック + + max_hp = battle_get_max_hp(&md->bl); + + if(src && src->type == BL_PC) { + sd = (struct map_session_data *)src; + mvp_sd = sd; + } + +// if(battle_config.battle_log) +// printf("mob_damage %d %d %d\n",md->hp,max_hp,damage); + if(md->bl.prev==NULL){ + if(battle_config.error_log==1) + printf("mob_damage : BlockError!!\n"); + return 0; + } + + if(md->state.state==MS_DEAD || md->hp<=0) { + if(md->bl.prev != NULL) { + mob_changestate(md,MS_DEAD,0); + mobskill_use(md,tick,-1); // It is skill at the time of death. + clif_clearchar_area(&md->bl,1); + map_delblock(&md->bl); + mob_setdelayspawn(md->bl.id); + } + return 0; + } + + if(md->sc_data[SC_ENDURE].timer == -1) + mob_stop_walking(md,3); + if(damage > max_hp>>2) + skill_stop_dancing(&md->bl,0); + + if(md->hp > max_hp) + md->hp = max_hp; + + // The amount of overkill rounds to hp. + if(damage>md->hp) + damage=md->hp; + + if(!(type&2)) { + if(sd!=NULL){ + for(i=0,minpos=0,mindmg=0x7fffffff;i<DAMAGELOG_SIZE;i++){ + if(md->dmglog[i].id==sd->bl.id) + break; + if(md->dmglog[i].id==0){ + minpos=i; + mindmg=0; + } + else if(md->dmglog[i].dmg<mindmg){ + minpos=i; + mindmg=md->dmglog[i].dmg; + } + } + if(i<DAMAGELOG_SIZE) + md->dmglog[i].dmg+=damage; + else { + md->dmglog[minpos].id=sd->bl.id; + md->dmglog[minpos].dmg=damage; + } + + if(md->attacked_id <= 0 && md->state.special_mob_ai==0) + md->attacked_id = sd->bl.id; + } + if(src && src->type == BL_PET && battle_config.pet_attack_exp_to_master==1) { + struct pet_data *pd = (struct pet_data *)src; + nullpo_retr(0, pd); + for(i=0,minpos=0,mindmg=0x7fffffff;i<DAMAGELOG_SIZE;i++){ + if(md->dmglog[i].id==pd->msd->bl.id) + break; + if(md->dmglog[i].id==0){ + minpos=i; + mindmg=0; + } + else if(md->dmglog[i].dmg<mindmg){ + minpos=i; + mindmg=md->dmglog[i].dmg; + } + } + if(i<DAMAGELOG_SIZE) + md->dmglog[i].dmg+=(damage*battle_config.pet_attack_exp_rate)/100; + else { + md->dmglog[minpos].id=pd->msd->bl.id; + md->dmglog[minpos].dmg=(damage*battle_config.pet_attack_exp_rate)/100; + } + } + if(src && src->type == BL_MOB && ((struct mob_data*)src)->state.special_mob_ai){ + struct mob_data *md2 = (struct mob_data *)src; + nullpo_retr(0, md2); + for(i=0,minpos=0,mindmg=0x7fffffff;i<DAMAGELOG_SIZE;i++){ + if(md->dmglog[i].id==md2->master_id) + break; + if(md->dmglog[i].id==0){ + minpos=i; + mindmg=0; + } + else if(md->dmglog[i].dmg<mindmg){ + minpos=i; + mindmg=md->dmglog[i].dmg; + } + } + if(i<DAMAGELOG_SIZE) + md->dmglog[i].dmg+=damage; + else { + md->dmglog[minpos].id=md2->master_id; + md->dmglog[minpos].dmg=damage; + + if(md->attacked_id <= 0 && md->state.special_mob_ai==0) + md->attacked_id = md2->master_id; + } + } + + } + + md->hp-=damage; + + if(md->class >= 1285 && md->class <=1287) { // guardian hp update [Valaris] + struct guild_castle *gc=guild_mapname2gc(map[md->bl.m].name); + if(gc) { + + if(md->bl.id==gc->GID0) { + gc->Ghp0=md->hp; + if(gc->Ghp0<=0) { + guild_castledatasave(gc->castle_id,10,0); + guild_castledatasave(gc->castle_id,18,0); + } + } + if(md->bl.id==gc->GID1) { + gc->Ghp1=md->hp; + if(gc->Ghp1<=0) { + guild_castledatasave(gc->castle_id,11,0); + guild_castledatasave(gc->castle_id,19,0); + } + } + if(md->bl.id==gc->GID2) { + gc->Ghp2=md->hp; + if(gc->Ghp2<=0) { + guild_castledatasave(gc->castle_id,12,0); + guild_castledatasave(gc->castle_id,20,0); + } + } + if(md->bl.id==gc->GID3) { + gc->Ghp3=md->hp; + if(gc->Ghp3<=0) { + guild_castledatasave(gc->castle_id,13,0); + guild_castledatasave(gc->castle_id,21,0); + } + } + if(md->bl.id==gc->GID4) { + gc->Ghp4=md->hp; + if(gc->Ghp4<=0) { + guild_castledatasave(gc->castle_id,14,0); + guild_castledatasave(gc->castle_id,22,0); + } + } + if(md->bl.id==gc->GID5) { + gc->Ghp5=md->hp; + if(gc->Ghp5<=0) { + guild_castledatasave(gc->castle_id,15,0); + guild_castledatasave(gc->castle_id,23,0); + } + } + if(md->bl.id==gc->GID6) { + gc->Ghp6=md->hp; + if(gc->Ghp6<=0) { + guild_castledatasave(gc->castle_id,16,0); + guild_castledatasave(gc->castle_id,24,0); + } + } + if(md->bl.id==gc->GID7) { + gc->Ghp7=md->hp; + if(gc->Ghp7<=0) { + guild_castledatasave(gc->castle_id,17,0); + guild_castledatasave(gc->castle_id,25,0); + + } + } + } + } // end addition [Valaris] + + if(md->option&2 ) + skill_status_change_end(&md->bl, SC_HIDING, -1); + if(md->option&4 ) + skill_status_change_end(&md->bl, SC_CLOAKING, -1); + + if(md->state.special_mob_ai == 2){//スフィアーマイン + int skillidx=0; + + if((skillidx=mob_skillid2skillidx(md->class,NPC_SELFDESTRUCTION2))>=0){ + md->mode |= 0x1; + md->next_walktime=tick; + mobskill_use_id(md,&md->bl,skillidx);//自爆詠唱開始 + md->state.special_mob_ai++; + } + } + + if(md->hp>0){ + return 0; + } + + // ----- ここから死亡処理 ----- + + map_freeblock_lock(); + mob_changestate(md,MS_DEAD,0); + mobskill_use(md,tick,-1); // 死亡時スキル + + memset(tmpsd,0,sizeof(tmpsd)); + memset(pt,0,sizeof(pt)); + + max_hp = battle_get_max_hp(&md->bl); + + if(src && src->type == BL_MOB) + mob_unlocktarget((struct mob_data *)src,tick); + + /* ソウルドレイン */ + if(sd && (skill=pc_checkskill(sd,HW_SOULDRAIN))>0){ + clif_skill_nodamage(src,&md->bl,HW_SOULDRAIN,skill,1); + sp = (battle_get_lv(&md->bl))*(65+15*skill)/100; + if(sd->status.sp + sp > sd->status.max_sp) + sp = sd->status.max_sp - sd->status.sp; + sd->status.sp += sp; + clif_heal(sd->fd,SP_SP,sp); + } + + // map外に消えた人は計算から除くので + // overkill分は無いけどsumはmax_hpとは違う + + tdmg = 0; + for(i=0,count=0,mvp_damage=0;i<DAMAGELOG_SIZE;i++){ + if(md->dmglog[i].id==0) + continue; + tmpsd[i] = map_id2sd(md->dmglog[i].id); + if(tmpsd[i] == NULL) + continue; + count++; + if(tmpsd[i]->bl.m != md->bl.m || pc_isdead(tmpsd[i])) + continue; + + tdmg += (double)md->dmglog[i].dmg; + if(mvp_damage<md->dmglog[i].dmg){ + third_sd = second_sd; + second_sd = mvp_sd; + mvp_sd=tmpsd[i]; + mvp_damage=md->dmglog[i].dmg; + } + } + + // [MouseJstr] + if((map[md->bl.m].flag.pvp == 0) || (battle_config.pvp_exp == 1)) { + + if((double)max_hp < tdmg) + dmg_rate = ((double)max_hp) / tdmg; + else dmg_rate = 1; + + // 経験値の分配 + for(i=0;i<DAMAGELOG_SIZE;i++){ + int pid,base_exp,job_exp,flag=1; + double per; + struct party *p; + if(tmpsd[i]==NULL || tmpsd[i]->bl.m != md->bl.m) + continue; +/* jAthena's exp formula + per = ((double)md->dmglog[i].dmg)*(9.+(double)((count > 6)? 6:count))/10./((double)max_hp) * dmg_rate; + temp = ((double)mob_db[md->class].base_exp * (double)battle_config.base_exp_rate / 100. * per); + base_exp = (temp > 2147483647.)? 0x7fffffff:(int)temp; + if(mob_db[md->class].base_exp > 0 && base_exp < 1) base_exp = 1; + if(base_exp < 0) base_exp = 0; + temp = ((double)mob_db[md->class].job_exp * (double)battle_config.job_exp_rate / 100. * per); + job_exp = (temp > 2147483647.)? 0x7fffffff:(int)temp; + if(mob_db[md->class].job_exp > 0 && job_exp < 1) job_exp = 1; + if(job_exp < 0) job_exp = 0; +*/ +//eAthena's exp formula rather than jAthena's + per=(double)md->dmglog[i].dmg*256*(9+(double)((count > 6)? 6:count))/10/(double)max_hp; + if(per>512) per=512; + if(per<1) per=1; + base_exp=mob_db[md->class].base_exp*per/256; + if(base_exp < 1) base_exp = 1; + if(sd && md && battle_config.pk_mode==1 && (mob_db[md->class].lv - sd->status.base_level >= 20)) { + base_exp*=1.15; // pk_mode additional exp if monster >20 levels [Valaris] + } + if(md->state.special_mob_ai >= 1 && battle_config.alchemist_summon_reward != 1) base_exp = 0; // Added [Valaris] + job_exp=mob_db[md->class].job_exp*per/256; + if(job_exp < 1) job_exp = 1; + if(sd && md && battle_config.pk_mode==1 && (mob_db[md->class].lv - sd->status.base_level >= 20)) { + job_exp*=1.15; // pk_mode additional exp if monster >20 levels [Valaris] + } + if(md->state.special_mob_ai >= 1 && battle_config.alchemist_summon_reward != 1) job_exp = 0; // Added [Valaris] + + if((pid=tmpsd[i]->status.party_id)>0){ // パーティに入っている + int j=0; + for(j=0;j<pnum;j++) // 公平パーティリストにいるかどうか + if(pt[j].id==pid) + break; + if(j==pnum){ // いないときは公平かどうか確認 + if((p=party_search(pid))!=NULL && p->exp!=0){ + pt[pnum].id=pid; + pt[pnum].p=p; + pt[pnum].base_exp=base_exp; + pt[pnum].job_exp=job_exp; + pnum++; + flag=0; + } + }else{ // いるときは公平 + pt[j].base_exp+=base_exp; + pt[j].job_exp+=job_exp; + flag=0; + } + } + if(flag) // 各自所得 + pc_gainexp(tmpsd[i],base_exp,job_exp); + } + // 公平分配 + for(i=0;i<pnum;i++) + party_exp_share(pt[i].p,md->bl.m,pt[i].base_exp,pt[i].job_exp); + + // item drop + if(!(type&1)) { + for(i=0;i<8;i++){ + struct delay_item_drop *ditem; + int drop_rate; + + if(md->state.special_mob_ai >= 1 && battle_config.alchemist_summon_reward != 1) // Added [Valaris] + break; // End + + if(mob_db[md->class].dropitem[i].nameid <= 0) + continue; + drop_rate = mob_db[md->class].dropitem[i].p; + if(drop_rate <= 0 && battle_config.drop_rate0item==1) + drop_rate = 1; + if(battle_config.drops_by_luk>0 && sd && md) drop_rate+=(sd->status.luk*battle_config.drops_by_luk)/100; // drops affected by luk [Valaris] + if(sd && md && battle_config.pk_mode==1 && (mob_db[md->class].lv - sd->status.base_level >= 20)) drop_rate*=1.25; // pk_mode increase drops if 20 level difference [Valaris] + if(drop_rate <= rand()%10000) + continue; + + ditem=(struct delay_item_drop *)aCalloc(1,sizeof(struct delay_item_drop)); + ditem->nameid = mob_db[md->class].dropitem[i].nameid; + ditem->amount = 1; + ditem->m = md->bl.m; + ditem->x = md->bl.x; + ditem->y = md->bl.y; + ditem->first_sd = mvp_sd; + ditem->second_sd = second_sd; + ditem->third_sd = third_sd; + add_timer(tick+500+i,mob_delay_item_drop,(int)ditem,0); + } + if(sd && sd->state.attack_type == BF_WEAPON) { + for(i=0;i<sd->monster_drop_item_count;i++) { + struct delay_item_drop *ditem; + int race = battle_get_race(&md->bl); + if(sd->monster_drop_itemid[i] <= 0) + continue; + if(sd->monster_drop_race[i] & (1<<race) || + (mob_db[md->class].mode & 0x20 && sd->monster_drop_race[i] & 1<<10) || + (!(mob_db[md->class].mode & 0x20) && sd->monster_drop_race[i] & 1<<11) ) { + if(sd->monster_drop_itemrate[i] <= rand()%10000) + continue; + + ditem=(struct delay_item_drop *)aCalloc(1,sizeof(struct delay_item_drop)); + ditem->nameid = sd->monster_drop_itemid[i]; + ditem->amount = 1; + ditem->m = md->bl.m; + ditem->x = md->bl.x; + ditem->y = md->bl.y; + ditem->first_sd = mvp_sd; + ditem->second_sd = second_sd; + ditem->third_sd = third_sd; + add_timer(tick+520+i,mob_delay_item_drop,(int)ditem,0); + } + } + if(sd->get_zeny_num > 0) + pc_getzeny(sd,mob_db[md->class].lv*10 + rand()%(sd->get_zeny_num+1)); + } + if(md->lootitem) { + for(i=0;i<md->lootitem_count;i++) { + struct delay_item_drop2 *ditem; + + ditem=(struct delay_item_drop2 *)aCalloc(1,sizeof(struct delay_item_drop2)); + memcpy(&ditem->item_data,&md->lootitem[i],sizeof(md->lootitem[0])); + ditem->m = md->bl.m; + ditem->x = md->bl.x; + ditem->y = md->bl.y; + ditem->first_sd = mvp_sd; + ditem->second_sd = second_sd; + ditem->third_sd = third_sd; + add_timer(tick+540+i,mob_delay_item_drop2,(int)ditem,0); + } + } + } + + // mvp処理 + if(mvp_sd && mob_db[md->class].mexp > 0 ){ + int j; + int mexp; + temp = ((double)mob_db[md->class].mexp * (double)battle_config.mvp_exp_rate * (9.+(double)count)/1000.); + mexp = (temp > 2147483647.)? 0x7fffffff:(int)temp; + if(mexp < 1) mexp = 1; + clif_mvp_effect(mvp_sd); // エフェクト + clif_mvp_exp(mvp_sd,mexp); + pc_gainexp(mvp_sd,mexp,0); + for(j=0;j<3;j++){ + i = rand() % 3; + if(mob_db[md->class].mvpitem[i].nameid <= 0) + continue; + drop_rate = mob_db[md->class].mvpitem[i].p; + if(drop_rate <= 0 && battle_config.drop_rate0item==1) + drop_rate = 1; + if(drop_rate < battle_config.item_drop_mvp_min) + drop_rate = battle_config.item_drop_mvp_min; + if(drop_rate > battle_config.item_drop_mvp_max) + drop_rate = battle_config.item_drop_mvp_max; + if(drop_rate <= rand()%10000) + continue; + memset(&item,0,sizeof(item)); + item.nameid=mob_db[md->class].mvpitem[i].nameid; + item.identify=!itemdb_isequip3(item.nameid); + clif_mvp_item(mvp_sd,item.nameid); + if(mvp_sd->weight*2 > mvp_sd->max_weight) + map_addflooritem(&item,1,mvp_sd->bl.m,mvp_sd->bl.x,mvp_sd->bl.y,mvp_sd,second_sd,third_sd,1); + else if((ret = pc_additem(mvp_sd,&item,1))) { + clif_additem(sd,0,0,ret); + map_addflooritem(&item,1,mvp_sd->bl.m,mvp_sd->bl.x,mvp_sd->bl.y,mvp_sd,second_sd,third_sd,1); + } + break; + } + } + + } // [MouseJstr] + + // <Agit> NPC Event [OnAgitBreak] + if(md->npc_event[0] && strcmp(((md->npc_event)+strlen(md->npc_event)-13),"::OnAgitBreak") == 0) { + printf("MOB.C: Run NPC_Event[OnAgitBreak].\n"); + if (agit_flag == 1) //Call to Run NPC_Event[OnAgitBreak] + guild_agit_break(md); + } + + // SCRIPT実行 + if(md->npc_event[0]){ +// if(battle_config.battle_log==1) +// printf("mob_damage : run event : %s\n",md->npc_event); + if(src && src->type == BL_PET) + sd = ((struct pet_data *)src)->msd; + if(sd == NULL) { + if(mvp_sd != NULL) + sd = mvp_sd; + else { + struct map_session_data *tmpsd; + int i; + for(i=0;i<fd_max;i++){ + if(session[i] && (tmpsd=session[i]->session_data) && tmpsd->state.auth) { + if(md->bl.m == tmpsd->bl.m) { + sd = tmpsd; + break; + } + } + } + } + } + if(sd) + npc_event(sd,md->npc_event,0); + } + + clif_clearchar_area(&md->bl,1); + map_delblock(&md->bl); + if(mob_get_viewclass(md->class) <= 1000) + clif_clearchar_delay(tick+3000,&md->bl,0); + mob_deleteslave(md); + mob_setdelayspawn(md->bl.id); + map_freeblock_unlock(); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int mob_class_change(struct mob_data *md,int *value) +{ + unsigned int tick = gettick(); + int i,c,hp_rate,max_hp,class,count = 0; + + nullpo_retr(0, md); + nullpo_retr(0, value); + + if(value[0]<=1000 || value[0]>2000) + return 0; + if(md->bl.prev == NULL) return 0; + + while(count < 5 && value[count] > 1000 && value[count] <= 2000) count++; + if(count < 1) return 0; + + class = value[rand()%count]; + if(class<=1000 || class>2000) return 0; + + max_hp = battle_get_max_hp(&md->bl); + hp_rate = md->hp*100/max_hp; + clif_mob_class_change(md,class); + md->class = class; + max_hp = battle_get_max_hp(&md->bl); + if(battle_config.monster_class_change_full_recover==1) { + md->hp = max_hp; + memset(md->dmglog,0,sizeof(md->dmglog)); + } + else + md->hp = max_hp*hp_rate/100; + if(md->hp > max_hp) md->hp = max_hp; + else if(md->hp < 1) md->hp = 1; + + memcpy(md->name,mob_db[class].jname,24); + memset(&md->state,0,sizeof(md->state)); + md->attacked_id = 0; + md->target_id = 0; + md->move_fail_count = 0; + + md->speed = mob_db[md->class].speed; + md->def_ele = mob_db[md->class].element; + + mob_changestate(md,MS_IDLE,0); + skill_castcancel(&md->bl,0); + md->state.skillstate = MSS_IDLE; + md->last_thinktime = tick; + md->next_walktime = tick+rand()%50+5000; + md->attackabletime = tick; + md->canmove_tick = tick; + md->sg_count=0; + + for(i=0,c=tick-1000*3600*10;i<MAX_MOBSKILL;i++) + md->skilldelay[i] = c; + md->skillid=0; + md->skilllv=0; + + if(md->lootitem == NULL && mob_db[class].mode&0x02) + md->lootitem=(struct item *)aCalloc(LOOTITEM_SIZE,sizeof(struct item)); + + skill_clear_unitgroup(&md->bl); + skill_cleartimerskill(&md->bl); + + clif_clearchar_area(&md->bl,0); + clif_spawnmob(md); + + return 0; +} + +/*========================================== + * mob回復 + *------------------------------------------ + */ +int mob_heal(struct mob_data *md,int heal) +{ + int max_hp = battle_get_max_hp(&md->bl); + + nullpo_retr(0, md); + + md->hp += heal; + if( max_hp < md->hp ) + md->hp = max_hp; + + if(md->class >= 1285 && md->class <=1287) { // guardian hp update [Valaris] + struct guild_castle *gc=guild_mapname2gc(map[md->bl.m].name); + if(gc) { + if(md->bl.id==gc->GID0) gc->Ghp0=md->hp; + if(md->bl.id==gc->GID1) gc->Ghp1=md->hp; + if(md->bl.id==gc->GID2) gc->Ghp2=md->hp; + if(md->bl.id==gc->GID3) gc->Ghp3=md->hp; + if(md->bl.id==gc->GID4) gc->Ghp4=md->hp; + if(md->bl.id==gc->GID5) gc->Ghp5=md->hp; + if(md->bl.id==gc->GID6) gc->Ghp6=md->hp; + if(md->bl.id==gc->GID7) gc->Ghp7=md->hp; + } + } // end addition [Valaris] + + return 0; +} + + +/*========================================== + * Added by RoVeRT + *------------------------------------------ + */ +int mob_warpslave_sub(struct block_list *bl,va_list ap) +{ + struct mob_data *md=(struct mob_data *)bl; + int id,x,y; + id=va_arg(ap,int); + x=va_arg(ap,int); + y=va_arg(ap,int); + if( md->master_id==id ) { + mob_warp(md,-1,x,y,2); + } + return 0; +} + +/*========================================== + * Added by RoVeRT + *------------------------------------------ + */ +int mob_warpslave(struct mob_data *md,int x, int y) +{ +//printf("warp slave\n"); + map_foreachinarea(mob_warpslave_sub, md->bl.m, + x-AREA_SIZE,y-AREA_SIZE, + x+AREA_SIZE,y+AREA_SIZE,BL_MOB, + md->bl.id, md->bl.x, md->bl.y ); + return 0; +} + +/*========================================== + * mobワープ + *------------------------------------------ + */ +int mob_warp(struct mob_data *md,int m,int x,int y,int type) +{ + int i=0,c,xs=0,ys=0,bx=x,by=y; + + nullpo_retr(0, md); + + if( md->bl.prev==NULL ) + return 0; + + if( m<0 ) m=md->bl.m; + + if(type >= 0) { + if(map[md->bl.m].flag.monster_noteleport) + return 0; + clif_clearchar_area(&md->bl,type); + } + skill_unit_out_all(&md->bl,gettick(),1); + map_delblock(&md->bl); + + if(bx>0 && by>0){ // 位置指定の場合周囲9セルを探索 + xs=ys=9; + } + + while( ( x<0 || y<0 || ((c=read_gat(m,x,y))==1 || c==5) ) && (i++)<1000 ){ + if( xs>0 && ys>0 && i<250 ){ // 指定位置付近の探索 + x=bx+rand()%xs-xs/2; + y=by+rand()%ys-ys/2; + }else{ // 完全ランダム探索 + x=rand()%(map[m].xs-2)+1; + y=rand()%(map[m].ys-2)+1; + } + } + md->dir=0; + if(i<1000){ + md->bl.x=md->to_x=x; + md->bl.y=md->to_y=y; + md->bl.m=m; + }else { + m=md->bl.m; + if(battle_config.error_log==1) + printf("MOB %d warp failed, class = %d\n",md->bl.id,md->class); + } + + md->target_id=0; // タゲを解除する + md->state.targettype=NONE_ATTACKABLE; + md->attacked_id=0; + md->state.skillstate=MSS_IDLE; + mob_changestate(md,MS_IDLE,0); + + if(type>0 && i==1000) { + if(battle_config.battle_log==1) + printf("MOB %d warp to (%d,%d), class = %d\n",md->bl.id,x,y,md->class); + } + + map_addblock(&md->bl); + if(type>0) { + clif_spawnmob(md); + mob_warpslave(md,md->bl.x,md->bl.y); + } + + return 0; +} + +/*========================================== + * 画面内の取り巻きの数計算用(foreachinarea) + *------------------------------------------ + */ +int mob_countslave_sub(struct block_list *bl,va_list ap) +{ + int id,*c; + struct mob_data *md; + + id=va_arg(ap,int); + + nullpo_retr(0, bl); + nullpo_retr(0, ap); + nullpo_retr(0, c=va_arg(ap,int *)); + nullpo_retr(0, md = (struct mob_data *)bl); + + + if( md->master_id==id ) + (*c)++; + return 0; +} +/*========================================== + * 画面内の取り巻きの数計算 + *------------------------------------------ + */ +int mob_countslave(struct mob_data *md) +{ + int c=0; + + nullpo_retr(0, md); + + map_foreachinarea(mob_countslave_sub, md->bl.m, + 0,0,map[md->bl.m].xs-1,map[md->bl.m].ys-1, + BL_MOB,md->bl.id,&c); + return c; +} +/*========================================== + * 手下MOB召喚 + *------------------------------------------ + */ +int mob_summonslave(struct mob_data *md2,int *value,int amount,int flag) +{ + struct mob_data *md; + int bx,by,m,count = 0,class,k,a = amount; + + nullpo_retr(0, md2); + nullpo_retr(0, value); + + bx=md2->bl.x; + by=md2->bl.y; + m=md2->bl.m; + + if(value[0]<=1000 || value[0]>2000) // 値が異常なら召喚を止める + return 0; + while(count < 5 && value[count] > 1000 && value[count] <= 2000) count++; + if(count < 1) return 0; + + for(k=0;k<count;k++) { + amount = a; + class = value[k]; + if(class<=1000 || class>2000) continue; + for(;amount>0;amount--){ + int x=0,y=0,c=0,i=0; + md=(struct mob_data *)aCalloc(1,sizeof(struct mob_data)); + if(mob_db[class].mode&0x02) + md->lootitem=(struct item *)aCalloc(LOOTITEM_SIZE,sizeof(struct item)); + else + md->lootitem=NULL; + + while((x<=0 || y<=0 || (c=map_getcell(m,x,y))==1 || c==5 ) && (i++)<100){ + x=rand()%9-4+bx; + y=rand()%9-4+by; + } + if(i>=100){ + x=bx; + y=by; + } + + mob_spawn_dataset(md,"--ja--",class); + md->bl.m=m; + md->bl.x=x; + md->bl.y=y; + + md->m =m; + md->x0=x; + md->y0=y; + md->xs=0; + md->ys=0; + md->speed=md2->speed; + md->spawndelay1=-1; // 一度のみフラグ + md->spawndelay2=-1; // 一度のみフラグ + + memset(md->npc_event,0,sizeof(md->npc_event)); + md->bl.type=BL_MOB; + map_addiddb(&md->bl); + mob_spawn(md->bl.id); + clif_skill_nodamage(&md->bl,&md->bl,(flag)? NPC_SUMMONSLAVE:NPC_SUMMONMONSTER,a,1); + + if(flag) + md->master_id=md2->bl.id; + } + } + return 0; +} + +/*========================================== + * 自分をロックしているPCの数を数える(foreachclient) + *------------------------------------------ + */ +static int mob_counttargeted_sub(struct block_list *bl,va_list ap) +{ + int id,*c,target_lv; + struct block_list *src; + + id=va_arg(ap,int); + nullpo_retr(0, bl); + nullpo_retr(0, ap); + nullpo_retr(0, c=va_arg(ap,int *)); + + src=va_arg(ap,struct block_list *); + target_lv=va_arg(ap,int); + if(id == bl->id || (src && id == src->id)) return 0; + if(bl->type == BL_PC) { + struct map_session_data *sd = (struct map_session_data *)bl; + if(sd && sd->attacktarget == id && sd->attacktimer != -1 && sd->attacktarget_lv >= target_lv) + (*c)++; + } + else if(bl->type == BL_MOB) { + struct mob_data *md = (struct mob_data *)bl; + if(md && md->target_id == id && md->timer != -1 && md->state.state == MS_ATTACK && md->target_lv >= target_lv) + (*c)++; + } + else if(bl->type == BL_PET) { + struct pet_data *pd = (struct pet_data *)bl; + if(pd->target_id == id && pd->timer != -1 && pd->state.state == MS_ATTACK && pd->target_lv >= target_lv) + (*c)++; + } + return 0; +} +/*========================================== + * 自分をロックしているPCの数を数える + *------------------------------------------ + */ +int mob_counttargeted(struct mob_data *md,struct block_list *src,int target_lv) +{ + int c=0; + + nullpo_retr(0, md); + + map_foreachinarea(mob_counttargeted_sub, md->bl.m, + md->bl.x-AREA_SIZE,md->bl.y-AREA_SIZE, + md->bl.x+AREA_SIZE,md->bl.y+AREA_SIZE,0,md->bl.id,&c,src,target_lv); + return c; +} + +/*========================================== + *MOBskillから該当skillidのskillidxを返す + *------------------------------------------ + */ +int mob_skillid2skillidx(int class,int skillid) +{ + int i; + struct mob_skill *ms=mob_db[class].skill; + + if(ms==NULL) + return -1; + + for(i=0;i<mob_db[class].maxskill;i++){ + if(ms[i].skill_id == skillid) + return i; + } + return -1; + +} + +// +// MOBスキル +// + +/*========================================== + * スキル使用(詠唱完了、ID指定) + *------------------------------------------ + */ +int mobskill_castend_id( int tid, unsigned int tick, int id,int data ) +{ + struct mob_data* md=NULL; + struct block_list *bl; + struct block_list *mbl; + int range; + + if((mbl = map_id2bl(id)) == NULL ) //詠唱したMobがもういないというのは良くある正常処理 + return 0; + if((md=(struct mob_data *)mbl) == NULL ){ + printf("mobskill_castend_id nullpo mbl->id:%d\n",mbl->id); + return 0; + } + + if( md->bl.type!=BL_MOB || md->bl.prev==NULL ) + return 0; + + if( md->skilltimer != tid ) // タイマIDの確認 + return 0; + + md->skilltimer=-1; + //沈黙や状態異常など + if(md->sc_data){ + if(md->opt1>0 || md->sc_data[SC_DIVINA].timer != -1 || md->sc_data[SC_ROKISWEIL].timer != -1 || md->sc_data[SC_STEELBODY].timer != -1) + return 0; + if(md->sc_data[SC_AUTOCOUNTER].timer != -1 && md->skillid != KN_AUTOCOUNTER) //オートカウンター + return 0; + if(md->sc_data[SC_BLADESTOP].timer != -1) //白刃取り + return 0; + if(md->sc_data[SC_BERSERK].timer != -1) //バーサーク + return 0; + } + if(md->skillid != NPC_EMOTION) + md->last_thinktime=tick + battle_get_adelay(&md->bl); + + if((bl = map_id2bl(md->skilltarget)) == NULL || bl->prev==NULL){ //スキルターゲットが存在しない + //printf("mobskill_castend_id nullpo\n");//ターゲットがいないときはnullpoじゃなくて普通に終了 + return 0; + } + if(md->bl.m != bl->m) + return 0; + + if(md->skillid == PR_LEXAETERNA) { + struct status_change *sc_data = battle_get_sc_data(bl); + if(sc_data && (sc_data[SC_FREEZE].timer != -1 || (sc_data[SC_STONE].timer != -1 && sc_data[SC_STONE].val2 == 0))) + return 0; + } + else if(md->skillid == RG_BACKSTAP) { + int dir = map_calc_dir(&md->bl,bl->x,bl->y),t_dir = battle_get_dir(bl); + int dist = distance(md->bl.x,md->bl.y,bl->x,bl->y); + if(bl->type != BL_SKILL && (dist == 0 || map_check_dir(dir,t_dir))) + return 0; + } + if( ( (skill_get_inf(md->skillid)&1) || (skill_get_inf2(md->skillid)&4) ) && // 彼我敵対関係チェック + battle_check_target(&md->bl,bl, BCT_ENEMY)<=0 ) + return 0; + range = skill_get_range(md->skillid,md->skilllv); + if(range < 0) + range = battle_get_range(&md->bl) - (range + 1); + if(range + battle_config.mob_skill_add_range < distance(md->bl.x,md->bl.y,bl->x,bl->y)) + return 0; + + md->skilldelay[md->skillidx]=tick; + + if(battle_config.mob_skill_log==1) + printf("MOB skill castend skill=%d, class = %d\n",md->skillid,md->class); + mob_stop_walking(md,0); + + switch( skill_get_nk(md->skillid) ) + { + // 攻撃系/吹き飛ばし系 + case 0: case 2: + skill_castend_damage_id(&md->bl,bl,md->skillid,md->skilllv,tick,0); + break; + case 1:// 支援系 + if(!mob_db[md->class].skill[md->skillidx].val[0] && + (md->skillid==AL_HEAL || (md->skillid==ALL_RESURRECTION && bl->type != BL_PC)) && battle_check_undead(battle_get_race(bl),battle_get_elem_type(bl)) ) + skill_castend_damage_id(&md->bl,bl,md->skillid,md->skilllv,tick,0); + else + skill_castend_nodamage_id(&md->bl,bl,md->skillid,md->skilllv,tick,0); + break; + } + + + return 0; +} + +/*========================================== + * スキル使用(詠唱完了、場所指定) + *------------------------------------------ + */ +int mobskill_castend_pos( int tid, unsigned int tick, int id,int data ) +{ + struct mob_data* md=NULL; + struct block_list *bl; + int range,maxcount; + + //mobskill_castend_id同様詠唱したMobが詠唱完了時にもういないというのはありそうなのでnullpoから除外 + if((bl=map_id2bl(id))==NULL) + return 0; + + nullpo_retr(0, md=(struct mob_data *)bl); + + if( md->bl.type!=BL_MOB || md->bl.prev==NULL ) + return 0; + + if( md->skilltimer != tid ) // タイマIDの確認 + return 0; + + md->skilltimer=-1; + if(md->sc_data){ + if(md->opt1>0 || md->sc_data[SC_DIVINA].timer != -1 || md->sc_data[SC_ROKISWEIL].timer != -1 || md->sc_data[SC_STEELBODY].timer != -1) + return 0; + if(md->sc_data[SC_AUTOCOUNTER].timer != -1 && md->skillid != KN_AUTOCOUNTER) //オートカウンター + return 0; + if(md->sc_data[SC_BLADESTOP].timer != -1) //白刃取り + return 0; + if(md->sc_data[SC_BERSERK].timer != -1) //バーサーク + return 0; + } + + if(battle_config.monster_skill_reiteration == 0) { + range = -1; + switch(md->skillid) { + case MG_SAFETYWALL: + case WZ_FIREPILLAR: + case HT_SKIDTRAP: + case HT_LANDMINE: + case HT_ANKLESNARE: + case HT_SHOCKWAVE: + case HT_SANDMAN: + case HT_FLASHER: + case HT_FREEZINGTRAP: + case HT_BLASTMINE: + case HT_CLAYMORETRAP: + case PF_SPIDERWEB: /* スパイダーウェッブ */ + range = 0; + break; + case AL_PNEUMA: + case AL_WARP: + range = 1; + break; + } + if(range >= 0) { + if(skill_check_unit_range(md->bl.m,md->skillx,md->skilly,range,md->skillid) > 0) + return 0; + } + } + if(battle_config.monster_skill_nofootset==1) { + range = -1; + switch(md->skillid) { + case WZ_FIREPILLAR: + case HT_SKIDTRAP: + case HT_LANDMINE: + case HT_ANKLESNARE: + case HT_SHOCKWAVE: + case HT_SANDMAN: + case HT_FLASHER: + case HT_FREEZINGTRAP: + case HT_BLASTMINE: + case HT_CLAYMORETRAP: + case AM_DEMONSTRATION: + case PF_SPIDERWEB: /* スパイダーウェッブ */ + range = 1; + break; + case AL_WARP: + range = 0; + break; + } + if(range >= 0) { + if(skill_check_unit_range2(md->bl.m,md->skillx,md->skilly,range) > 0) + return 0; + } + } + + if(battle_config.monster_land_skill_limit==1) { + maxcount = skill_get_maxcount(md->skillid); + if(maxcount > 0) { + int i,c; + for(i=c=0;i<MAX_MOBSKILLUNITGROUP;i++) { + if(md->skillunit[i].alive_count > 0 && md->skillunit[i].skill_id == md->skillid) + c++; + } + if(c >= maxcount) + return 0; + } + } + + range = skill_get_range(md->skillid,md->skilllv); + if(range < 0) + range = battle_get_range(&md->bl) - (range + 1); + if(range + battle_config.mob_skill_add_range < distance(md->bl.x,md->bl.y,md->skillx,md->skilly)) + return 0; + md->skilldelay[md->skillidx]=tick; + + if(battle_config.mob_skill_log==1) + printf("MOB skill castend skill=%d, class = %d\n",md->skillid,md->class); + mob_stop_walking(md,0); + + skill_castend_pos2(&md->bl,md->skillx,md->skilly,md->skillid,md->skilllv,tick,0); + + return 0; +} + + +/*========================================== + * Skill use (an aria start, ID specification) + *------------------------------------------ + */ +int mobskill_use_id(struct mob_data *md,struct block_list *target,int skill_idx) +{ + int casttime,range; + struct mob_skill *ms; + int skill_id, skill_lv, forcecast = 0; + + nullpo_retr(0, md); + nullpo_retr(0, ms=&mob_db[md->class].skill[skill_idx]); + + if( target==NULL && (target=map_id2bl(md->target_id))==NULL ) + return 0; + + if( target->prev==NULL || md->bl.prev==NULL ) + return 0; + + skill_id=ms->skill_id; + skill_lv=ms->skill_lv; + + // 沈黙や異常 + if(md->sc_data){ + if(md->opt1>0 || md->sc_data[SC_DIVINA].timer != -1 || md->sc_data[SC_ROKISWEIL].timer != -1 || md->sc_data[SC_STEELBODY].timer != -1) + return 0; + if(md->sc_data[SC_AUTOCOUNTER].timer != -1 && md->skillid != KN_AUTOCOUNTER) //オートカウンター + return 0; + if(md->sc_data[SC_BLADESTOP].timer != -1) //白刃取り + return 0; + if(md->sc_data[SC_BERSERK].timer != -1) //バーサーク + return 0; + } + + if(md->option&4 && skill_id==TF_HIDING) + return 0; + if(md->option&2 && skill_id!=TF_HIDING && skill_id!=AS_GRIMTOOTH && skill_id!=RG_BACKSTAP && skill_id!=RG_RAID) + return 0; + + if(map[md->bl.m].flag.gvg && (skill_id == SM_ENDURE || skill_id == AL_TELEPORT || skill_id == AL_WARP || + skill_id == WZ_ICEWALL || skill_id == TF_BACKSLIDING)) + return 0; + + if(skill_get_inf2(skill_id)&0x200 && md->bl.id == target->id) + return 0; + + // 射程と障害物チェック + range = skill_get_range(skill_id,skill_lv); + if(range < 0) + range = battle_get_range(&md->bl) - (range + 1); + if(!battle_check_range(&md->bl,target,range)) + return 0; + +// delay=skill_delayfix(&md->bl, skill_get_delay( skill_id,skill_lv) ); + + casttime=skill_castfix(&md->bl,ms->casttime); + md->state.skillcastcancel=ms->cancel; + md->skilldelay[skill_idx]=gettick(); + + switch(skill_id){ /* 何か特殊な処理が必要 */ + case ALL_RESURRECTION: /* リザレクション */ + if(target->type != BL_PC && battle_check_undead(battle_get_race(target),battle_get_elem_type(target))){ /* 敵がアンデッドなら */ + forcecast=1; /* ターンアンデットと同じ詠唱時間 */ + casttime=skill_castfix(&md->bl, skill_get_cast(PR_TURNUNDEAD,skill_lv) ); + } + break; + case MO_EXTREMITYFIST: /*阿修羅覇鳳拳*/ + case SA_MAGICROD: + case SA_SPELLBREAKER: + forcecast=1; + break; + } + + if(battle_config.mob_skill_log==1) + printf("MOB skill use target_id=%d skill=%d lv=%d cast=%d, class = %d\n",target->id,skill_id,skill_lv,casttime,md->class); + + if(casttime>0 || forcecast){ // 詠唱が必要 +// struct mob_data *md2; + clif_skillcasting( &md->bl, + md->bl.id, target->id, 0,0, skill_id,casttime); + + // 詠唱反応モンスター +/* if( target->type==BL_MOB && mob_db[(md2=(struct mob_data *)target)->class].mode&0x10 && + md2->state.state!=MS_ATTACK){ + md2->target_id=md->bl.id; + md->state.targettype = ATTACKABLE; + md2->min_chase=13; + }*/ + } + + if( casttime<=0 ) // 詠唱の無いものはキャンセルされない + md->state.skillcastcancel=0; + + md->skilltarget = target->id; + md->skillx = 0; + md->skilly = 0; + md->skillid = skill_id; + md->skilllv = skill_lv; + md->skillidx = skill_idx; + + if(!(battle_config.monster_cloak_check_type&2) && md->sc_data[SC_CLOAKING].timer != -1 && md->skillid != AS_CLOAKING) + skill_status_change_end(&md->bl,SC_CLOAKING,-1); + + if( casttime>0 ){ + md->skilltimer = + add_timer( gettick()+casttime, mobskill_castend_id, md->bl.id, 0 ); + }else{ + md->skilltimer = -1; + mobskill_castend_id(md->skilltimer,gettick(),md->bl.id, 0); + } + + return 1; +} +/*========================================== + * スキル使用(場所指定) + *------------------------------------------ + */ +int mobskill_use_pos( struct mob_data *md, + int skill_x, int skill_y, int skill_idx) +{ + int casttime=0,range; + struct mob_skill *ms; + struct block_list bl; + int skill_id, skill_lv; + + nullpo_retr(0, md); + nullpo_retr(0, ms=&mob_db[md->class].skill[skill_idx]); + + if( md->bl.prev==NULL ) + return 0; + + skill_id=ms->skill_id; + skill_lv=ms->skill_lv; + + //沈黙や状態異常など + if(md->sc_data){ + if(md->opt1>0 || md->sc_data[SC_DIVINA].timer != -1 || md->sc_data[SC_ROKISWEIL].timer != -1 || md->sc_data[SC_STEELBODY].timer != -1) + return 0; + if(md->sc_data[SC_AUTOCOUNTER].timer != -1 && md->skillid != KN_AUTOCOUNTER) //オートカウンター + return 0; + if(md->sc_data[SC_BLADESTOP].timer != -1) //白刃取り + return 0; + if(md->sc_data[SC_BERSERK].timer != -1) //バーサーク + return 0; + } + + if(md->option&2) + return 0; + + if(map[md->bl.m].flag.gvg && (skill_id == SM_ENDURE || skill_id == AL_TELEPORT || skill_id == AL_WARP || + skill_id == WZ_ICEWALL || skill_id == TF_BACKSLIDING)) + return 0; + + // 射程と障害物チェック + bl.type = BL_NUL; + bl.m = md->bl.m; + bl.x = skill_x; + bl.y = skill_y; + range = skill_get_range(skill_id,skill_lv); + if(range < 0) + range = battle_get_range(&md->bl) - (range + 1); + if(!battle_check_range(&md->bl,&bl,range)) + return 0; + +// delay=skill_delayfix(&sd->bl, skill_get_delay( skill_id,skill_lv) ); + casttime=skill_castfix(&md->bl,ms->casttime); + md->skilldelay[skill_idx]=gettick(); + md->state.skillcastcancel=ms->cancel; + + if(battle_config.mob_skill_log==1) + printf("MOB skill use target_pos=(%d,%d) skill=%d lv=%d cast=%d, class = %d\n", + skill_x,skill_y,skill_id,skill_lv,casttime,md->class); + + if( casttime>0 ) // A cast time is required. + clif_skillcasting( &md->bl, + md->bl.id, 0, skill_x,skill_y, skill_id,casttime); + + if( casttime<=0 ) // A skill without a cast time wont be cancelled. + md->state.skillcastcancel=0; + + + md->skillx = skill_x; + md->skilly = skill_y; + md->skilltarget = 0; + md->skillid = skill_id; + md->skilllv = skill_lv; + md->skillidx = skill_idx; + if(!(battle_config.monster_cloak_check_type&2) && md->sc_data[SC_CLOAKING].timer != -1) + skill_status_change_end(&md->bl,SC_CLOAKING,-1); + if( casttime>0 ){ + md->skilltimer = + add_timer( gettick()+casttime, mobskill_castend_pos, md->bl.id, 0 ); + }else{ + md->skilltimer = -1; + mobskill_castend_pos(md->skilltimer,gettick(),md->bl.id, 0); + } + + return 1; +} + + +/*========================================== + * Friendly Mob whose HP is decreasing by a nearby MOB is looked for. + *------------------------------------------ + */ +int mob_getfriendhpltmaxrate_sub(struct block_list *bl,va_list ap) +{ + int rate; + struct mob_data **fr, *md, *mmd; + + nullpo_retr(0, bl); + nullpo_retr(0, ap); + nullpo_retr(0, mmd=va_arg(ap,struct mob_data *)); + + md=(struct mob_data *)bl; + + if( mmd->bl.id == bl->id ) + return 0; + rate=va_arg(ap,int); + fr=va_arg(ap,struct mob_data **); + if( md->hp < mob_db[md->class].max_hp*rate/100 ) + (*fr)=md; + return 0; +} +struct mob_data *mob_getfriendhpltmaxrate(struct mob_data *md,int rate) +{ + struct mob_data *fr=NULL; + const int r=8; + + nullpo_retr(NULL, md); + + map_foreachinarea(mob_getfriendhpltmaxrate_sub, md->bl.m, + md->bl.x-r ,md->bl.y-r, md->bl.x+r, md->bl.y+r, + BL_MOB,md,rate,&fr); + return fr; +} +/*========================================== + * What a status state suits by nearby MOB is looked for. + *------------------------------------------ + */ +int mob_getfriendstatus_sub(struct block_list *bl,va_list ap) +{ + int cond1,cond2; + struct mob_data **fr, *md, *mmd; + int flag=0; + + nullpo_retr(0, bl); + nullpo_retr(0, ap); + nullpo_retr(0, md=(struct mob_data *)bl); + nullpo_retr(0, mmd=va_arg(ap,struct mob_data *)); + + if( mmd->bl.id == bl->id ) + return 0; + cond1=va_arg(ap,int); + cond2=va_arg(ap,int); + fr=va_arg(ap,struct mob_data **); + if( cond2==-1 ){ + int j; + for(j=SC_STONE;j<=SC_BLIND && !flag;j++){ + flag=(md->sc_data[j].timer!=-1 ); + } + }else + flag=( md->sc_data[cond2].timer!=-1 ); + if( flag^( cond1==MSC_FRIENDSTATUSOFF ) ) + (*fr)=md; + + return 0; +} +struct mob_data *mob_getfriendstatus(struct mob_data *md,int cond1,int cond2) +{ + struct mob_data *fr=NULL; + const int r=8; + + nullpo_retr(0, md); + + map_foreachinarea(mob_getfriendstatus_sub, md->bl.m, + md->bl.x-r ,md->bl.y-r, md->bl.x+r, md->bl.y+r, + BL_MOB,md,cond1,cond2,&fr); + return fr; +} + +/*========================================== + * Skill use judging + *------------------------------------------ + */ +int mobskill_use(struct mob_data *md,unsigned int tick,int event) +{ + struct mob_skill *ms; +// struct block_list *target=NULL; + int i,max_hp; + + nullpo_retr(0, md); + nullpo_retr(0, ms = mob_db[md->class].skill); + + max_hp = battle_get_max_hp(&md->bl); + + if(battle_config.mob_skill_use == 0 || md->skilltimer != -1) + return 0; + + if(md->state.special_mob_ai) + return 0; + + if(md->sc_data[SC_SELFDESTRUCTION].timer!=-1) //自爆中はスキルを使わない + return 0; + + for(i=0;i<mob_db[md->class].maxskill;i++){ + int c2=ms[i].cond2,flag=0; + struct mob_data *fmd=NULL; + + // ディレイ中 + if( DIFF_TICK(tick,md->skilldelay[i])<ms[i].delay ) + continue; + + // 状態判定 + if( ms[i].state>=0 && ms[i].state!=md->state.skillstate ) + continue; + + // 条件判定 + flag=(event==ms[i].cond1); + if(!flag){ + switch( ms[i].cond1 ){ + case MSC_ALWAYS: + flag=1; break; + case MSC_MYHPLTMAXRATE: // HP< maxhp% + flag=( md->hp < max_hp*c2/100 ); break; + case MSC_MYSTATUSON: // status[num] on + case MSC_MYSTATUSOFF: // status[num] off + if( ms[i].cond2==-1 ){ + int j; + for(j=SC_STONE;j<=SC_BLIND && !flag;j++){ + flag=(md->sc_data[j].timer!=-1 ); + } + }else + flag=( md->sc_data[ms[i].cond2].timer!=-1 ); + flag^=( ms[i].cond1==MSC_MYSTATUSOFF ); break; + case MSC_FRIENDHPLTMAXRATE: // friend HP < maxhp% + flag=(( fmd=mob_getfriendhpltmaxrate(md,ms[i].cond2) )!=NULL ); break; + case MSC_FRIENDSTATUSON: // friend status[num] on + case MSC_FRIENDSTATUSOFF: // friend status[num] off + flag=(( fmd=mob_getfriendstatus(md,ms[i].cond1,ms[i].cond2) )!=NULL ); break; + case MSC_SLAVELT: // slave < num + flag=( mob_countslave(md) < c2 ); break; + case MSC_ATTACKPCGT: // attack pc > num + flag=( mob_counttargeted(md,NULL,0) > c2 ); break; + case MSC_SLAVELE: // slave <= num + flag=( mob_countslave(md) <= c2 ); break; + case MSC_ATTACKPCGE: // attack pc >= num + flag=( mob_counttargeted(md,NULL,0) >= c2 ); break; + case MSC_SKILLUSED: // specificated skill used + flag=( (event&0xffff)==MSC_SKILLUSED && ((event>>16)==c2 || c2==0)); break; + } + } + + // 確率判定 + if( flag && rand()%10000 < ms[i].permillage ){ + + if( skill_get_inf(ms[i].skill_id)&2 ){ + // 場所指定 + struct block_list *bl = NULL; + int x=0,y=0; + if( ms[i].target<=MST_AROUND ){ + bl= ((ms[i].target==MST_TARGET || ms[i].target==MST_AROUND5)? map_id2bl(md->target_id): + (ms[i].target==MST_FRIEND)? &fmd->bl : &md->bl); + if(bl!=NULL){ + x=bl->x; y=bl->y; + } + } + if( x<=0 || y<=0 ) + continue; + // 自分の周囲 + if( ms[i].target>=MST_AROUND1 ){ + int bx=x, by=y, i=0, c, m=bl->m, r=ms[i].target-MST_AROUND1; + do{ + bx=x + rand()%(r*2+3) - r; + by=y + rand()%(r*2+3) - r; + }while( ( bx<=0 || by<=0 || bx>=map[m].xs || by>=map[m].ys || + ((c=read_gat(m,bx,by))==1 || c==5) ) && (i++)<1000); + if(i<1000){ + x=bx; y=by; + } + } + // 相手の周囲 + if( ms[i].target>=MST_AROUND5 ){ + int bx=x, by=y, i=0, c, m=bl->m, r=(ms[i].target-MST_AROUND5)+1; + do{ + bx=x + rand()%(r*2+1) - r; + by=y + rand()%(r*2+1) - r; + }while( ( bx<=0 || by<=0 || bx>=map[m].xs || by>=map[m].ys || + ((c=read_gat(m,bx,by))==1 || c==5) ) && (i++)<1000); + if(i<1000){ + x=bx; y=by; + } + } + if(!mobskill_use_pos(md,x,y,i)) + return 0; + + }else{ + // ID指定 + if( ms[i].target<=MST_FRIEND ){ + struct block_list *bl = NULL; + bl= ((ms[i].target==MST_TARGET)? map_id2bl(md->target_id): + (ms[i].target==MST_FRIEND)? &fmd->bl : &md->bl); + if(bl && !mobskill_use_id(md,bl,i)) + return 0; + } + } + if(ms[i].emotion >= 0) + clif_emotion(&md->bl,ms[i].emotion); + return 1; + } + } + + return 0; +} +/*========================================== + * Skill use event processing + *------------------------------------------ + */ +int mobskill_event(struct mob_data *md,int flag) +{ + nullpo_retr(0, md); + + if(flag==-1 && mobskill_use(md,gettick(),MSC_CASTTARGETED)) + return 1; + if( (flag&BF_SHORT) && mobskill_use(md,gettick(),MSC_CLOSEDATTACKED)) + return 1; + if( (flag&BF_LONG) && mobskill_use(md,gettick(),MSC_LONGRANGEATTACKED)) + return 1; + return 0; +} +/*========================================== + * Mobがエンペリウムなどの場合の判定 + *------------------------------------------ + */ +int mob_gvmobcheck(struct map_session_data *sd, struct block_list *bl) +{ + struct mob_data *md=NULL; + + nullpo_retr(0,sd); + nullpo_retr(0,bl); + + if(bl->type==BL_MOB && (md=(struct mob_data *)bl) && + (md->class == 1288 || md->class == 1287 || md->class == 1286 || md->class == 1285)) + { + struct guild_castle *gc=guild_mapname2gc(map[sd->bl.m].name); + struct guild *g=guild_search(sd->status.guild_id); + + if(g == NULL && md->class == 1288) + return 0;//ギルド未加入ならダメージ無し + else if(gc != NULL && !map[sd->bl.m].flag.gvg) + return 0;//砦内でGvじゃないときはダメージなし + else if(g && gc != NULL && g->guild_id == gc->guild_id) + return 0;//自占領ギルドのエンペならダメージ無し + else if(g && guild_checkskill(g,GD_APPROVAL) <= 0 && md->class == 1288) + return 0;//正規ギルド承認がないとダメージ無し + + } + + return 1; +} +/*========================================== + * スキル用タイマー削除 + *------------------------------------------ + */ +int mobskill_deltimer(struct mob_data *md ) +{ + nullpo_retr(0, md); + + if( md->skilltimer!=-1 ){ + if( skill_get_inf( md->skillid )&2 ) + delete_timer( md->skilltimer, mobskill_castend_pos ); + else + delete_timer( md->skilltimer, mobskill_castend_id ); + md->skilltimer=-1; + } + return 0; +} +// +// 初期化 +// +/*========================================== + * Since un-setting [ mob ] up was used, it is an initial provisional value setup. + *------------------------------------------ + */ +static int mob_makedummymobdb(int class) +{ + int i; + + sprintf(mob_db[class].name,"mob%d",class); + sprintf(mob_db[class].jname,"mob%d",class); + mob_db[class].lv=1; + mob_db[class].max_hp=1000; + mob_db[class].max_sp=1; + mob_db[class].base_exp=2; + mob_db[class].job_exp=1; + mob_db[class].range=1; + mob_db[class].atk1=7; + mob_db[class].atk2=10; + mob_db[class].def=0; + mob_db[class].mdef=0; + mob_db[class].str=1; + mob_db[class].agi=1; + mob_db[class].vit=1; + mob_db[class].int_=1; + mob_db[class].dex=6; + mob_db[class].luk=2; + mob_db[class].range2=10; + mob_db[class].range3=10; + mob_db[class].size=0; + mob_db[class].race=0; + mob_db[class].element=0; + mob_db[class].mode=0; + mob_db[class].speed=300; + mob_db[class].adelay=1000; + mob_db[class].amotion=500; + mob_db[class].dmotion=500; + mob_db[class].dropitem[0].nameid=909; // Jellopy + mob_db[class].dropitem[0].p=1000; + for(i=1;i<8;i++){ + mob_db[class].dropitem[i].nameid=0; + mob_db[class].dropitem[i].p=0; + } + // Item1,Item2 + mob_db[class].mexp=0; + mob_db[class].mexpper=0; + for(i=0;i<3;i++){ + mob_db[class].mvpitem[i].nameid=0; + mob_db[class].mvpitem[i].p=0; + } + for(i=0;i<MAX_RANDOMMONSTER;i++) + mob_db[class].summonper[i]=0; + return 0; +} + +/*========================================== + * db/mob_db.txt reading + *------------------------------------------ + */ +static int mob_readdb(void) +{ + FILE *fp; + char line[1024]; + char *filename[]={ "db/mob_db.txt","db/mob_db2.txt" }; + int i; + + memset(mob_db,0,sizeof(mob_db)); + + for(i=0;i<2;i++){ + + fp=fopen(filename[i],"r"); + if(fp==NULL){ + if(i>0) + continue; + return -1; + } + while(fgets(line,1020,fp)){ + int class,i; + char *str[55],*p,*np; + + if(line[0] == '/' && line[1] == '/') + continue; + + for(i=0,p=line;i<55;i++){ + if((np=strchr(p,','))!=NULL){ + str[i]=p; + *np=0; + p=np+1; + } else + str[i]=p; + } + + class=atoi(str[0]); + if(class<=1000 || class>2000) + continue; + + mob_db[class].view_class=class; + memcpy(mob_db[class].name,str[1],24); + memcpy(mob_db[class].jname,str[2],24); + mob_db[class].lv=atoi(str[3]); + mob_db[class].max_hp=atoi(str[4]); + mob_db[class].max_sp=atoi(str[5]); + + mob_db[class].base_exp=atoi(str[6]); + if(mob_db[class].base_exp < 0) + mob_db[class].base_exp = 0; + else if(mob_db[class].base_exp > 0 && (mob_db[class].base_exp*battle_config.base_exp_rate/100 > 1000000000 || + mob_db[class].base_exp*battle_config.base_exp_rate/100 < 0)) + mob_db[class].base_exp=1000000000; + else + mob_db[class].base_exp*= battle_config.base_exp_rate/100; + + mob_db[class].job_exp=atoi(str[7]); + if(mob_db[class].job_exp < 0) + mob_db[class].job_exp = 0; + else if(mob_db[class].job_exp > 0 && (mob_db[class].job_exp*battle_config.job_exp_rate/100 > 1000000000 || + mob_db[class].job_exp*battle_config.job_exp_rate/100 < 0)) + mob_db[class].job_exp=1000000000; + else + mob_db[class].job_exp*=battle_config.job_exp_rate/100; + + mob_db[class].range=atoi(str[8]); + mob_db[class].atk1=atoi(str[9]); + mob_db[class].atk2=atoi(str[10]); + mob_db[class].def=atoi(str[11]); + mob_db[class].mdef=atoi(str[12]); + mob_db[class].str=atoi(str[13]); + mob_db[class].agi=atoi(str[14]); + mob_db[class].vit=atoi(str[15]); + mob_db[class].int_=atoi(str[16]); + mob_db[class].dex=atoi(str[17]); + mob_db[class].luk=atoi(str[18]); + mob_db[class].range2=atoi(str[19]); + mob_db[class].range3=atoi(str[20]); + mob_db[class].size=atoi(str[21]); + mob_db[class].race=atoi(str[22]); + mob_db[class].element=atoi(str[23]); + mob_db[class].mode=atoi(str[24]); + mob_db[class].speed=atoi(str[25]); + mob_db[class].adelay=atoi(str[26]); + mob_db[class].amotion=atoi(str[27]); + mob_db[class].dmotion=atoi(str[28]); + + for(i=0;i<8;i++){ + int rate = 0,type,ratemin,ratemax; + mob_db[class].dropitem[i].nameid=atoi(str[29+i*2]); + type = itemdb_type(mob_db[class].dropitem[i].nameid); + if (type == 0) { // Added [Valaris] + rate = battle_config.item_rate_heal; + ratemin = battle_config.item_drop_heal_min; + ratemax = battle_config.item_drop_heal_max; + } + else if (type == 2) { + rate = battle_config.item_rate_use; + ratemin = battle_config.item_drop_use_min; + ratemax = battle_config.item_drop_use_max; // End + } + else if (type == 4 || type == 5 || type == 8) { // Changed to include Pet Equip + rate = battle_config.item_rate_equip; + ratemin = battle_config.item_drop_equip_min; + ratemax = battle_config.item_drop_equip_max; + } + else if (type == 6) { + rate = battle_config.item_rate_card; + ratemin = battle_config.item_drop_card_min; + ratemax = battle_config.item_drop_card_max; + } + else { + rate = battle_config.item_rate_common; + ratemin = battle_config.item_drop_common_min; + ratemax = battle_config.item_drop_common_max; + } + rate = (rate / 100) * atoi(str[30+i*2]); + rate = (rate < ratemin)? ratemin: (rate > ratemax)? ratemax: rate; + mob_db[class].dropitem[i].p = rate; + } + // Item1,Item2 + mob_db[class].mexp=atoi(str[45])*battle_config.mvp_exp_rate/100; + mob_db[class].mexpper=atoi(str[46]); + for(i=0;i<3;i++){ + mob_db[class].mvpitem[i].nameid=atoi(str[47+i*2]); + mob_db[class].mvpitem[i].p=atoi(str[48+i*2])*battle_config.mvp_item_rate/100; + } + for(i=0;i<MAX_RANDOMMONSTER;i++) + mob_db[class].summonper[i]=0; + mob_db[class].maxskill=0; + + mob_db[class].sex=0; + mob_db[class].hair=0; + mob_db[class].hair_color=0; + mob_db[class].weapon=0; + mob_db[class].shield=0; + mob_db[class].head_top=0; + mob_db[class].head_mid=0; + mob_db[class].head_buttom=0; + mob_db[class].clothes_color=0; //Add for player monster dye - Valaris + } + fclose(fp); + printf("read %s done\n",filename[i]); + } + return 0; +} + +/*========================================== + * MOB display graphic change data reading + *------------------------------------------ + */ +static int mob_readdb_mobavail(void) +{ + FILE *fp; + char line[1024]; + int ln=0; + int class,j,k; + char *str[20],*p,*np; + + if( (fp=fopen("db/mob_avail.txt","r"))==NULL ){ + printf("can't read db/mob_avail.txt\n"); + return -1; + } + + while(fgets(line,1020,fp)){ + if(line[0]=='/' && line[1]=='/') + continue; + memset(str,0,sizeof(str)); + + for(j=0,p=line;j<12;j++){ + if((np=strchr(p,','))!=NULL){ + str[j]=p; + *np=0; + p=np+1; + } else + str[j]=p; + } + + if(str[0]==NULL) + continue; + + class=atoi(str[0]); + + if(class<=1000 || class>2000) // 値が異常なら処理しない。 + continue; + k=atoi(str[1]); + if(k >= 0) + mob_db[class].view_class=k; + + if((mob_db[class].view_class < 24) || (mob_db[class].view_class > 4000)) { + mob_db[class].sex=atoi(str[2]); + mob_db[class].hair=atoi(str[3]); + mob_db[class].hair_color=atoi(str[4]); + mob_db[class].weapon=atoi(str[5]); + mob_db[class].shield=atoi(str[6]); + mob_db[class].head_top=atoi(str[7]); + mob_db[class].head_mid=atoi(str[8]); + mob_db[class].head_buttom=atoi(str[9]); + mob_db[class].option=atoi(str[10])&~0x46; + mob_db[class].clothes_color=atoi(str[11]); // Monster player dye option - Valaris + } + + else if(atoi(str[2]) > 0) mob_db[class].equip=atoi(str[2]); // mob equipment [Valaris] + + ln++; + } + fclose(fp); + printf("read db/mob_avail.txt done (count=%d)\n",ln); + return 0; +} + +/*========================================== + * Reading of random monster data + *------------------------------------------ + */ +static int mob_read_randommonster(void) +{ + FILE *fp; + char line[1024]; + char *str[10],*p; + int i,j; + + const char* mobfile[] = { + "db/mob_branch.txt", + "db/mob_poring.txt", + "db/mob_boss.txt" }; + + for(i=0;i<MAX_RANDOMMONSTER;i++){ + mob_db[0].summonper[i] = 1002; // 設定し忘れた場合はポリンが出るようにしておく + fp=fopen(mobfile[i],"r"); + if(fp==NULL){ + printf("can't read %s\n",mobfile[i]); + return -1; + } + while(fgets(line,1020,fp)){ + int class,per; + if(line[0] == '/' && line[1] == '/') + continue; + memset(str,0,sizeof(str)); + for(j=0,p=line;j<3 && p;j++){ + str[j]=p; + p=strchr(p,','); + if(p) *p++=0; + } + + if(str[0]==NULL || str[2]==NULL) + continue; + + class = atoi(str[0]); + per=atoi(str[2]); + if((class>1000 && class<=2000) || class==0) + mob_db[class].summonper[i]=per; + } + fclose(fp); + printf("read %s done\n",mobfile[i]); + } + return 0; +} +/*========================================== + * db/mob_skill_db.txt reading + *------------------------------------------ + */ +static int mob_readskilldb(void) +{ + FILE *fp; + char line[1024]; + int i; + + const struct { + char str[32]; + int id; + } cond1[] = { + { "always", MSC_ALWAYS }, + { "myhpltmaxrate", MSC_MYHPLTMAXRATE }, + { "friendhpltmaxrate",MSC_FRIENDHPLTMAXRATE }, + { "mystatuson", MSC_MYSTATUSON }, + { "mystatusoff", MSC_MYSTATUSOFF }, + { "friendstatuson", MSC_FRIENDSTATUSON }, + { "friendstatusoff", MSC_FRIENDSTATUSOFF }, + { "attackpcgt", MSC_ATTACKPCGT }, + { "attackpcge", MSC_ATTACKPCGE }, + { "slavelt", MSC_SLAVELT }, + { "slavele", MSC_SLAVELE }, + { "closedattacked", MSC_CLOSEDATTACKED }, + { "longrangeattacked",MSC_LONGRANGEATTACKED }, + { "skillused", MSC_SKILLUSED }, + { "casttargeted", MSC_CASTTARGETED }, + }, cond2[] ={ + { "anybad", -1 }, + { "stone", SC_STONE }, + { "freeze", SC_FREEZE }, + { "stan", SC_STAN }, + { "sleep", SC_SLEEP }, + { "poison", SC_POISON }, + { "curse", SC_CURSE }, + { "silence", SC_SILENCE }, + { "confusion", SC_CONFUSION }, + { "blind", SC_BLIND }, + { "hiding", SC_HIDING }, + { "sight", SC_SIGHT }, + }, state[] = { + { "any", -1 }, + { "idle", MSS_IDLE }, + { "walk", MSS_WALK }, + { "attack", MSS_ATTACK }, + { "dead", MSS_DEAD }, + { "loot", MSS_LOOT }, + { "chase", MSS_CHASE }, + }, target[] = { + { "target", MST_TARGET }, + { "self", MST_SELF }, + { "friend", MST_FRIEND }, + { "around5", MST_AROUND5 }, + { "around6", MST_AROUND6 }, + { "around7", MST_AROUND7 }, + { "around8", MST_AROUND8 }, + { "around1", MST_AROUND1 }, + { "around2", MST_AROUND2 }, + { "around3", MST_AROUND3 }, + { "around4", MST_AROUND4 }, + { "around", MST_AROUND }, + }; + + int x; + char *filename[]={ "db/mob_skill_db.txt","db/mob_skill_db2.txt" }; + + for(x=0;x<2;x++){ + + fp=fopen(filename[x],"r"); + if(fp==NULL){ + if(x==0) + printf("can't read %s\n",filename[x]); + continue; + } + while(fgets(line,1020,fp)){ + char *sp[20],*p; + int mob_id; + struct mob_skill *ms; + int j=0; + + if(line[0] == '/' && line[1] == '/') + continue; + + memset(sp,0,sizeof(sp)); + for(i=0,p=line;i<18 && p;i++){ + sp[i]=p; + if((p=strchr(p,','))!=NULL) + *p++=0; + } + if( (mob_id=atoi(sp[0]))<=0 ) + continue; + + if( strcmp(sp[1],"clear")==0 ){ + memset(mob_db[mob_id].skill,0,sizeof(mob_db[mob_id].skill)); + mob_db[mob_id].maxskill=0; + continue; + } + + for(i=0;i<MAX_MOBSKILL;i++) + if( (ms=&mob_db[mob_id].skill[i])->skill_id == 0) + break; + if(i==MAX_MOBSKILL){ + printf("mob_skill: readdb: too many skill ! [%s] in %d[%s]\n", + sp[1],mob_id,mob_db[mob_id].jname); + continue; + } + + ms->state=atoi(sp[2]); + for(j=0;j<sizeof(state)/sizeof(state[0]);j++){ + if( strcmp(sp[2],state[j].str)==0) + ms->state=state[j].id; + } + ms->skill_id=atoi(sp[3]); + ms->skill_lv=atoi(sp[4]); + ms->permillage=atoi(sp[5]); + ms->casttime=atoi(sp[6]); + ms->delay=atoi(sp[7]); + ms->cancel=atoi(sp[8]); + if( strcmp(sp[8],"yes")==0 ) ms->cancel=1; + ms->target=atoi(sp[9]); + for(j=0;j<sizeof(target)/sizeof(target[0]);j++){ + if( strcmp(sp[9],target[j].str)==0) + ms->target=target[j].id; + } + ms->cond1=-1; + for(j=0;j<sizeof(cond1)/sizeof(cond1[0]);j++){ + if( strcmp(sp[10],cond1[j].str)==0) + ms->cond1=cond1[j].id; + } + ms->cond2=atoi(sp[11]); + for(j=0;j<sizeof(cond2)/sizeof(cond2[0]);j++){ + if( strcmp(sp[11],cond2[j].str)==0) + ms->cond2=cond2[j].id; + } + ms->val[0]=atoi(sp[12]); + ms->val[1]=atoi(sp[13]); + ms->val[2]=atoi(sp[14]); + ms->val[3]=atoi(sp[15]); + ms->val[4]=atoi(sp[16]); + if(sp[17] != NULL && strlen(sp[17])>2) + ms->emotion=atoi(sp[17]); + else + ms->emotion=-1; + mob_db[mob_id].maxskill=i+1; + } + fclose(fp); + printf("read %s done\n",filename[x]); + } + return 0; +} + +void mob_reload(void) +{ + /* + + <empty monster database> + mob_read(); + + */ + + do_init_mob(); +} + +#ifndef TXT_ONLY +/*========================================== + * SQL reading + *------------------------------------------ + */ +static int mob_read_sqldb(void) +{ + char line[1024]; + int i,class,ln=0; + char *str[55],*p,*np; + + memset(mob_db,0,sizeof(mob_db)); + + sprintf (tmp_sql, "SELECT * FROM `%s`",mob_db_db); + if(mysql_query(&mmysql_handle, tmp_sql) ) { + printf("DB server Error (select %s to Memory)- %s\n",mob_db_db,mysql_error(&mmysql_handle) ); + } + sql_res = mysql_store_result(&mmysql_handle); + if (sql_res) { + while((sql_row = mysql_fetch_row(sql_res))){ + sprintf(line,"%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s", + sql_row[0],sql_row[1],sql_row[2],sql_row[3],sql_row[4], + sql_row[5],sql_row[6],sql_row[7],sql_row[8],sql_row[9], + sql_row[10],sql_row[11],sql_row[12],sql_row[13],sql_row[14], + sql_row[15],sql_row[16],sql_row[17],sql_row[18],sql_row[19], + sql_row[20],sql_row[21],sql_row[22],sql_row[23],sql_row[24], + sql_row[25],sql_row[26],sql_row[27],sql_row[28],sql_row[29], + sql_row[30],sql_row[31],sql_row[32],sql_row[33],sql_row[34], + sql_row[35],sql_row[36],sql_row[37],sql_row[38],sql_row[39], + sql_row[40],sql_row[41],sql_row[42],sql_row[43],sql_row[44], + sql_row[45],sql_row[46],sql_row[47],sql_row[48],sql_row[49], + sql_row[50],sql_row[51],sql_row[52]); + + for(i=0,p=line;i<55;i++){ + if((np=strchr(p,','))!=NULL){ + str[i]=p; + *np=0; + p=np+1; + } else + str[i]=p; + } + + class=atoi(str[0]); + if(class<=1000 || class>2000) + continue; + + ln++; + + mob_db[class].view_class=class; + memcpy(mob_db[class].name,str[1],24); + memcpy(mob_db[class].jname,str[2],24); + mob_db[class].lv=atoi(str[3]); + mob_db[class].max_hp=atoi(str[4]); + mob_db[class].max_sp=atoi(str[5]); + mob_db[class].base_exp=atoi(str[6])* + battle_config.base_exp_rate/100; + if(mob_db[class].base_exp <= 0) + mob_db[class].base_exp = 1; + mob_db[class].job_exp=atoi(str[7])* + battle_config.job_exp_rate/100; + if(mob_db[class].job_exp <= 0) + mob_db[class].job_exp = 1; + mob_db[class].range=atoi(str[8]); + mob_db[class].atk1=atoi(str[9]); + mob_db[class].atk2=atoi(str[10]); + mob_db[class].def=atoi(str[11]); + mob_db[class].mdef=atoi(str[12]); + mob_db[class].str=atoi(str[13]); + mob_db[class].agi=atoi(str[14]); + mob_db[class].vit=atoi(str[15]); + mob_db[class].int_=atoi(str[16]); + mob_db[class].dex=atoi(str[17]); + mob_db[class].luk=atoi(str[18]); + mob_db[class].range2=atoi(str[19]); + mob_db[class].range3=atoi(str[20]); + mob_db[class].size=atoi(str[21]); + mob_db[class].race=atoi(str[22]); + mob_db[class].element=atoi(str[23]); + mob_db[class].mode=atoi(str[24]); + mob_db[class].speed=atoi(str[25]); + mob_db[class].adelay=atoi(str[26]); + mob_db[class].amotion=atoi(str[27]); + mob_db[class].dmotion=atoi(str[28]); + + for(i=0;i<8;i++){ + int rate = 0,type,ratemin,ratemax; + mob_db[class].dropitem[i].nameid=atoi(str[29+i*2]); + type = itemdb_type(mob_db[class].dropitem[i].nameid); + if (type == 0) { // Added by Valaris + rate = battle_config.item_rate_heal; + ratemin = battle_config.item_drop_heal_min; + ratemax = battle_config.item_drop_heal_max; + } + else if (type == 2) { + rate = battle_config.item_rate_use; + ratemin = battle_config.item_drop_use_min; + ratemax = battle_config.item_drop_use_max; // End + } + else if (type == 4 || type == 5 || type == 8) { // Changed to include Pet Equip + rate = battle_config.item_rate_equip; + ratemin = battle_config.item_drop_equip_min; + ratemax = battle_config.item_drop_equip_max; + } + else if (type == 6) { + rate = battle_config.item_rate_card; + ratemin = battle_config.item_drop_card_min; + ratemax = battle_config.item_drop_card_max; + } + else { + rate = battle_config.item_rate_common; + ratemin = battle_config.item_drop_common_min; + ratemax = battle_config.item_drop_common_max; + } + rate = (rate / 100) * atoi(str[30+i*2]); + rate = (rate < ratemin)? ratemin: (rate > ratemax)? ratemax: rate; + mob_db[class].dropitem[i].p = rate; + } + + mob_db[class].mexp=atoi(str[45])*battle_config.mvp_exp_rate/100; + mob_db[class].mexpper=atoi(str[46]); + for(i=0;i<3;i++){ + mob_db[class].mvpitem[i].nameid=atoi(str[47+i*2]); + mob_db[class].mvpitem[i].p=atoi(str[48+i*2])*battle_config.mvp_item_rate/100; + } + for(i=0;i<MAX_RANDOMMONSTER;i++) + mob_db[class].summonper[i]=0; + mob_db[class].maxskill=0; + + mob_db[class].sex=0; + mob_db[class].hair=0; + mob_db[class].hair_color=0; + mob_db[class].weapon=0; + mob_db[class].shield=0; + mob_db[class].head_top=0; + mob_db[class].head_mid=0; + mob_db[class].head_buttom=0; + } + mysql_free_result(sql_res); + printf("read %s done (count=%d)\n",mob_db_db,ln); + } + return 0; +} + +#endif /* not TXT_ONLY */ +/*========================================== + * Circumference initialization of mob + *------------------------------------------ + */ +int do_init_mob(void) +{ +#ifndef TXT_ONLY + if(db_use_sqldbs) + mob_read_sqldb(); + else +#endif /* TXT_ONLY */ + mob_readdb(); + + mob_readdb_mobavail(); + mob_read_randommonster(); + mob_readskilldb(); + + add_timer_func_list(mob_timer,"mob_timer"); + add_timer_func_list(mob_delayspawn,"mob_delayspawn"); + add_timer_func_list(mob_delay_item_drop,"mob_delay_item_drop"); + add_timer_func_list(mob_delay_item_drop2,"mob_delay_item_drop2"); + add_timer_func_list(mob_ai_hard,"mob_ai_hard"); + add_timer_func_list(mob_ai_lazy,"mob_ai_lazy"); + add_timer_func_list(mobskill_castend_id,"mobskill_castend_id"); + add_timer_func_list(mobskill_castend_pos,"mobskill_castend_pos"); + add_timer_func_list(mob_timer_delete,"mob_timer_delete"); + add_timer_interval(gettick()+MIN_MOBTHINKTIME,mob_ai_hard,0,0,MIN_MOBTHINKTIME); + add_timer_interval(gettick()+MIN_MOBTHINKTIME*10,mob_ai_lazy,0,0,MIN_MOBTHINKTIME*10); + + return 0; +} diff --git a/misc/src/map/mob.h b/misc/src/map/mob.h new file mode 100644 index 0000000..7c1467b --- /dev/null +++ b/misc/src/map/mob.h @@ -0,0 +1,137 @@ +// $Id: mob.h,v 1.4 2004/09/25 05:32:18 MouseJstr Exp $ +#ifndef _MOB_H_ +#define _MOB_H_ + +#define MAX_RANDOMMONSTER 3 + +struct mob_skill { + short state; + short skill_id,skill_lv; + short permillage; + int casttime,delay; + short cancel; + short cond1,cond2; + short target; + int val[5]; + short emotion; +}; + +struct mob_db { + char name[24],jname[24]; + int lv; + int max_hp,max_sp; + int base_exp,job_exp; + int atk1,atk2; + int def,mdef; + int str,agi,vit,int_,dex,luk; + int range,range2,range3; + int size,race,element,mode; + int speed,adelay,amotion,dmotion; + int mexp,mexpper; + struct { int nameid,p; } dropitem[8]; + struct { int nameid,p; } mvpitem[3]; + int view_class,sex; + short hair,hair_color,weapon,shield,head_top,head_mid,head_buttom,option,clothes_color; // [Valaris] + int equip; // [Valaris] + int summonper[MAX_RANDOMMONSTER]; + int maxskill; + struct mob_skill skill[MAX_MOBSKILL]; +}; +extern struct mob_db mob_db[]; + +enum { + MST_TARGET = 0, + MST_SELF = 1, + MST_FRIEND = 2, + MST_AROUND5 = 3, + MST_AROUND6 = 4, + MST_AROUND7 = 5, + MST_AROUND8 = 6, + MST_AROUND1 = 7, + MST_AROUND2 = 8, + MST_AROUND3 = 9, + MST_AROUND4 = 10, + MST_AROUND = MST_AROUND4, + + MSC_ALWAYS = 0x0000, + MSC_MYHPLTMAXRATE = 0x0001, + MSC_FRIENDHPLTMAXRATE= 0x0010, + MSC_MYSTATUSON = 0x0020, + MSC_MYSTATUSOFF = 0x0021, + MSC_FRIENDSTATUSON = 0x0030, + MSC_FRIENDSTATUSOFF = 0x0031, + + MSC_ATTACKPCGT = 0x0100, + MSC_ATTACKPCGE = 0x0101, + MSC_SLAVELT = 0x0110, + MSC_SLAVELE = 0x0111, + MSC_CLOSEDATTACKED = 0x1000, + MSC_LONGRANGEATTACKED= 0x1001, + MSC_SKILLUSED = 0x1010, + MSC_CASTTARGETED = 0x1011, +}; + +enum { + MSS_IDLE, // 待機 + MSS_WALK, // 移動 + MSS_ATTACK, // 攻撃 + MSS_DEAD, // 死亡 + MSS_LOOT, // ルート + MSS_CHASE, // 突撃 +}; + +int mobdb_searchname(const char *str); +int mobdb_checkid(const int id); +int mob_once_spawn(struct map_session_data *sd,char *mapname, + int x,int y,const char *mobname,int class,int amount,const char *event); +int mob_once_spawn_area(struct map_session_data *sd,char *mapname, + int x0,int y0,int x1,int y1, + const char *mobname,int class,int amount,const char *event); + +int mob_spawn_guardian(struct map_session_data *sd,char *mapname, // Spawning Guardians [Valaris] + int x,int y,const char *mobname,int class,int amount,const char *event,int guardian); // Spawning Guardians [Valaris] + + +int mob_walktoxy(struct mob_data *md,int x,int y,int easy); + +int mob_target(struct mob_data *md,struct block_list *bl,int dist); +int mob_stop_walking(struct mob_data *md,int type); +int mob_stopattack(struct mob_data *); +int mob_spawn(int); +int mob_damage(struct block_list *,struct mob_data*,int,int); +int mob_changestate(struct mob_data *md,int state,int type); +int mob_heal(struct mob_data*,int); +int mob_get_viewclass(int); +int mob_get_sex(int); +short mob_get_hair(int); +short mob_get_hair_color(int); +short mob_get_weapon(int); +short mob_get_shield(int); +short mob_get_head_top(int); +short mob_get_head_mid(int); +short mob_get_head_buttom(int); +short mob_get_clothes_color(int); //player mob dye [Valaris] +int mob_get_equip(int); // mob equip [Valaris] +int do_init_mob(void); + +int mob_delete(struct mob_data *md); +int mob_catch_delete(struct mob_data *md,int type); +int mob_timer_delete(int tid, unsigned int tick, int id, int data); + +int mob_deleteslave(struct mob_data *md); + +int mob_counttargeted(struct mob_data *md,struct block_list *src,int target_lv); + +int mob_class_change(struct mob_data *md,int *value); +int mob_warp(struct mob_data *md,int m,int x,int y,int type); + +int mobskill_use(struct mob_data *md,unsigned int tick,int event); +int mobskill_event(struct mob_data *md,int flag); +int mobskill_castend_id( int tid, unsigned int tick, int id,int data ); +int mobskill_castend_pos( int tid, unsigned int tick, int id,int data ); +int mob_summonslave(struct mob_data *md2,int *value,int amount,int flag); + +int mob_gvmobcheck(struct map_session_data *sd, struct block_list *bl); +void mob_reload(void); + +#endif diff --git a/misc/src/map/npc.c b/misc/src/map/npc.c new file mode 100644 index 0000000..05a5dc4 --- /dev/null +++ b/misc/src/map/npc.c @@ -0,0 +1,2035 @@ +// $Id: npc.c,v 1.5 2004/09/25 05:32:18 MouseJstr Exp $ +#include <stdio.h> +#include <stdlib.h> +#include <ctype.h> +#include <string.h> +#include <math.h> +#include <time.h> + +#include "db.h" +#include "timer.h" +#include "nullpo.h" +#include "malloc.h" +#include "map.h" +#include "npc.h" +#include "clif.h" +#include "intif.h" +#include "pc.h" +#include "itemdb.h" +#include "script.h" +#include "mob.h" +#include "pet.h" +#include "battle.h" +#include "skill.h" + +#ifdef MEMWATCH +#include "memwatch.h" +#endif + + + +struct npc_src_list { + struct npc_src_list * next; + struct npc_src_list * prev; + char name[4]; +} ; + +static struct npc_src_list *npc_src_first,*npc_src_last; +static int npc_id=START_NPC_NUM; +static int npc_warp,npc_shop,npc_script,npc_mob; + +int npc_get_new_npc_id(void){ return npc_id++; } + +static struct dbt *ev_db; +static struct dbt *npcname_db; + +struct event_data { + struct npc_data *nd; + int pos; +}; +static struct tm ev_tm_b; // 時計イベント用 + + +/*========================================== + * NPCの無効化/有効化 + * npc_enable + * npc_enable_sub 有効時にOnTouchイベントを実行 + *------------------------------------------ + */ +int npc_enable_sub( struct block_list *bl, va_list ap ) +{ + struct map_session_data *sd; + struct npc_data *nd; + char *name=(char *)aCalloc(50,sizeof(char)); + + nullpo_retr(0, bl); + nullpo_retr(0, ap); + nullpo_retr(0, nd=va_arg(ap,struct npc_data *)); + if(bl->type == BL_PC && (sd=(struct map_session_data *)bl)){ + + if (nd->flag&1) // 無効化されている + return 1; + + memcpy(name,nd->name,50); + if(sd->areanpc_id==nd->bl.id) + return 1; + sd->areanpc_id=nd->bl.id; + npc_event(sd,strcat(name,"::OnTouch"),0); + } + free(name); + return 0; +} +int npc_enable(const char *name,int flag) +{ + struct npc_data *nd=strdb_search(npcname_db,name); + if (nd==NULL) + return 0; + + if (flag&1) { // 有効化 + nd->flag&=~1; + clif_spawnnpc(nd); + }else if (flag&2){ + nd->flag&=~1; + nd->option = 0x0000; + clif_changeoption(&nd->bl); + }else if (flag&4){ + nd->flag|=1; + nd->option = 0x0002; + clif_changeoption(&nd->bl); + }else{ // 無効化 + nd->flag|=1; + clif_clearchar(&nd->bl,0); + } + if(flag&3 && (nd->u.scr.xs > 0 || nd->u.scr.ys >0)) + map_foreachinarea( npc_enable_sub,nd->bl.m,nd->bl.x-nd->u.scr.xs,nd->bl.y-nd->u.scr.ys,nd->bl.x+nd->u.scr.xs,nd->bl.y+nd->u.scr.ys,BL_PC,nd); + + return 0; +} + +/*========================================== + * NPCを名前で探す + *------------------------------------------ + */ +struct npc_data* npc_name2id(const char *name) +{ + return strdb_search(npcname_db,name); +} +/*========================================== + * イベントキューのイベント処理 + *------------------------------------------ + */ +int npc_event_dequeue(struct map_session_data *sd) +{ + nullpo_retr(0, sd); + + sd->npc_id=0; + if (sd->eventqueue[0][0]) { // キューのイベント処理 + char *name=(char *)aCalloc(50,sizeof(char)); + int i; + + memcpy(name,sd->eventqueue[0],50); + for(i=MAX_EVENTQUEUE-2;i>=0;i--) + memcpy(sd->eventqueue[i],sd->eventqueue[i+1],50); + add_timer(gettick()+100,npc_event_timer,sd->bl.id,(int)name); + } + return 0; +} + +int npc_delete(struct npc_data *nd) +{ + nullpo_retr(1, nd); + + if(nd->bl.prev == NULL) + return 1; + + clif_clearchar_area(&nd->bl,1); + map_delblock(&nd->bl); + return 0; +} + +/*========================================== + * イベントの遅延実行 + *------------------------------------------ + */ +int npc_event_timer(int tid,unsigned int tick,int id,int data) +{ + struct map_session_data *sd=map_id2sd(id); + if (sd==NULL) + return 0; + + npc_event(sd,(const char *)data,0); + free((void*)data); + return 0; +} + +int npc_timer_event(const char *eventname) // Added by RoVeRT +{ + struct event_data *ev=strdb_search(ev_db,eventname); + struct npc_data *nd; +// int xs,ys; + + if((ev==NULL || (nd=ev->nd)==NULL)){ + printf("npc_event: event not found [%s]\n",eventname); + return 0; + } + + run_script(nd->u.scr.script,ev->pos,nd->bl.id,nd->bl.id); + + return 0; +} +/* +int npc_timer_sub_sub(void *key,void *data,va_list ap) // Added by RoVeRT +{ + char *p=(char *)key; + struct event_data *ev=(struct event_data *)data; + int *c=va_arg(ap,int *); + int tick=0,ctick=gettick(); + char temp[10]; + char event[100]; + + if(ev->nd->bl.id==(int)*c && (p=strchr(p,':')) && p && strncasecmp("::OnTimer",p,8)==0 ){ + sscanf(&p[9],"%s",temp); + tick=atoi(temp); + + strcpy( event, ev->nd->name); + strcat( event, p); + + if (ctick >= ev->nd->lastaction && ctick - ev->nd->timer >= tick) { + npc_timer_event(event); + ev->nd->lastaction = ctick; + } + } + return 0; +} + +int npc_timer_sub(void *key,void *data,va_list ap) // Added by RoVeRT +{ + struct npc_data *nd=(struct npc_data*)data; + + if(nd->timer == -1) + return 0; + + strdb_foreach(ev_db,npc_timer_sub_sub,&nd->bl.id); + + return 0; +} + +int npc_timer(int tid,unsigned int tick,int id,int data) // Added by RoVeRT +{ + strdb_foreach(npcname_db,npc_timer_sub); + + free((void*)data); + return 0; +}*/ +/*========================================== + * イベント用ラベルのエクスポート + * npc_parse_script->strdb_foreachから呼ばれる + *------------------------------------------ + */ +int npc_event_export(void *key,void *data,va_list ap) +{ + char *lname=(char *)key; + int pos=(int)data; + struct npc_data *nd=va_arg(ap,struct npc_data *); + + if ((lname[0]=='O' || lname[0]=='o')&&(lname[1]=='N' || lname[1]=='n')) { + struct event_data *ev; + char *buf; + char *p=strchr(lname,':'); + // エクスポートされる + ev=calloc(sizeof(struct event_data), 1); + buf=calloc(50, 1); + if (ev==NULL || buf==NULL) { + printf("npc_event_export: out of memory !\n"); + exit(1); + }else if (p==NULL || (p-lname)>24) { + printf("npc_event_export: label name error !\n"); + exit(1); + }else{ + ev->nd=nd; + ev->pos=pos; + *p='\0'; + sprintf(buf,"%s::%s",nd->exname,lname); + *p=':'; + strdb_insert(ev_db,buf,ev); +// if (battle_config.etc_log) +// printf("npc_event_export: export [%s]\n",buf); + } + } + return 0; +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +/*========================================== + * 全てのNPCのOn*イベント実行 + *------------------------------------------ + */ +int npc_event_doall_sub(void *key,void *data,va_list ap) +{ + char *p=(char *)key; + struct event_data *ev; + int *c; + const char *name; + + nullpo_retr(0, ev=(struct event_data *)data); + nullpo_retr(0, ap); + nullpo_retr(0, c=va_arg(ap,int *)); + + name=va_arg(ap,const char *); + + if( (p=strchr(p,':')) && p && strcasecmp(name,p)==0 ){ + run_script(ev->nd->u.scr.script,ev->pos,0,ev->nd->bl.id); + (*c)++; + } + + return 0; +} +int npc_event_doall(const char *name) +{ + int c=0; + char buf[64]="::"; + + strncpy(buf+2,name,62); + strdb_foreach(ev_db,npc_event_doall_sub,&c,buf); + return c; +} + +int npc_event_do_sub(void *key,void *data,va_list ap) +{ + char *p=(char *)key; + struct event_data *ev; + int *c; + const char *name; + + nullpo_retr(0, ev=(struct event_data *)data); + nullpo_retr(0, ap); + nullpo_retr(0, c=va_arg(ap,int *)); + + name=va_arg(ap,const char *); + + if (p && strcasecmp(name,p)==0 ) { + run_script(ev->nd->u.scr.script,ev->pos,0,ev->nd->bl.id); + (*c)++; + } + + return 0; +} +int npc_event_do(const char *name) +{ + int c=0; + + if (*name==':' && name[1]==':') { + return npc_event_doall(name+2); + } + + strdb_foreach(ev_db,npc_event_do_sub,&c,name); + return c; +} + +/*========================================== + * 時計イベント実行 + *------------------------------------------ + */ +int npc_event_do_clock(int tid,unsigned int tick,int id,int data) +{ + time_t timer; + struct tm *t; + char buf[64]; + int c=0; + + time(&timer); + t=localtime(&timer); + + if (t->tm_min != ev_tm_b.tm_min ) { + sprintf(buf,"OnMinute%02d",t->tm_min); + c+=npc_event_doall(buf); + sprintf(buf,"OnClock%02d%02d",t->tm_hour,t->tm_min); + c+=npc_event_doall(buf); + } + if (t->tm_hour!= ev_tm_b.tm_hour) { + sprintf(buf,"OnHour%02d",t->tm_hour); + c+=npc_event_doall(buf); + } + if (t->tm_mday!= ev_tm_b.tm_mday) { + sprintf(buf,"OnDay%02d%02d",t->tm_mon+1,t->tm_mday); + c+=npc_event_doall(buf); + } + memcpy(&ev_tm_b,t,sizeof(ev_tm_b)); + return c; +} +/*========================================== + * OnInitイベント実行(&時計イベント開始) + *------------------------------------------ + */ +int npc_event_do_oninit(void) +{ + int c = npc_event_doall("OnInit"); + printf("npc: OnInit Event done. (%d npc)\n",c); + + add_timer_interval(gettick()+100, + npc_event_do_clock,0,0,1000); + + return 0; +} +/*========================================== + * OnTimer NPC event - by RoVeRT + *------------------------------------------ + */ +int npc_addeventtimer(struct npc_data *nd,int tick,const char *name) +{ + int i; + for(i=0;i<MAX_EVENTTIMER;i++) + if( nd->eventtimer[i]==-1 ) + break; + if(i<MAX_EVENTTIMER){ + char *evname=malloc(24); + if(evname==NULL){ + printf("npc_addeventtimer: out of memory !\n");exit(1); + } + memcpy(evname,name,24); + nd->eventtimer[i]=add_timer(gettick()+tick, + npc_event_timer,nd->bl.id,(int)evname); + }else + printf("npc_addtimer: event timer is full !\n"); + + return 0; +} + +int npc_deleventtimer(struct npc_data *nd,const char *name) +{ + int i; + for(i=0;i<MAX_EVENTTIMER;i++) + if( nd->eventtimer[i]!=-1 && strcmp( + (char *)(get_timer(nd->eventtimer[i])->data), name)==0 ){ + delete_timer(nd->eventtimer[i],npc_event_timer); + nd->eventtimer[i]=-1; + break; + } + + return 0; +} + +int npc_cleareventtimer(struct npc_data *nd) +{ + int i; + for(i=0;i<MAX_EVENTTIMER;i++) + if( nd->eventtimer[i]!=-1 ){ + delete_timer(nd->eventtimer[i],npc_event_timer); + nd->eventtimer[i]=-1; + } + + return 0; +} + +int npc_do_ontimer_sub(void *key,void *data,va_list ap) +{ + char *p=(char *)key; + struct event_data *ev=(struct event_data *)data; + int *c=va_arg(ap,int *); +// struct map_session_data *sd=va_arg(ap,struct map_session_data *); + int option=va_arg(ap,int); + int tick=0; + char temp[10]; + char event[50]; + + if(ev->nd->bl.id==(int)*c && (p=strchr(p,':')) && p && strncasecmp("::OnTimer",p,8)==0 ){ + sscanf(&p[9],"%s",temp); + tick=atoi(temp); + + strcpy( event, ev->nd->name); + strcat( event, p); + + if (option!=0) { + npc_addeventtimer(ev->nd,tick,event); + } else { + npc_deleventtimer(ev->nd,event); + } + } + return 0; +} +int npc_do_ontimer(int npc_id, struct map_session_data *sd, int option) +{ + strdb_foreach(ev_db,npc_do_ontimer_sub,&npc_id,sd,option); + return 0; +} +/*========================================== + * タイマーイベント用ラベルの取り込み + * npc_parse_script->strdb_foreachから呼ばれる + *------------------------------------------ + */ +int npc_timerevent_import(void *key,void *data,va_list ap) +{ + char *lname=(char *)key; + int pos=(int)data; + struct npc_data *nd=va_arg(ap,struct npc_data *); + int t=0,i=0; + + if(sscanf(lname,"OnTimer%d%n",&t,&i)==1 && lname[i]==':') { + // タイマーイベント + struct npc_timerevent_list *te=nd->u.scr.timer_event; + int j,i=nd->u.scr.timeramount; + if(te==NULL) te=malloc(sizeof(struct npc_timerevent_list)); + else te=realloc( te, sizeof(struct npc_timerevent_list) * (i+1) ); + if(te==NULL){ + printf("npc_timerevent_import: out of memory !\n"); + exit(1); + } + for(j=0;j<i;j++){ + if(te[j].timer>t){ + memmove(te+j+1,te+j,sizeof(struct npc_timerevent_list)*(i-j)); + break; + } + } + te[j].timer=t; + te[j].pos=pos; + nd->u.scr.timer_event=te; + nd->u.scr.timeramount=i+1; + } + return 0; +} +/*========================================== + * タイマーイベント実行 + *------------------------------------------ + */ +int npc_timerevent(int tid,unsigned int tick,int id,int data) +{ + int next,t; + struct npc_data* nd=(struct npc_data *)map_id2bl(id); + struct npc_timerevent_list *te; + if( nd==NULL || nd->u.scr.nexttimer<0 ){ + printf("npc_timerevent: ??\n"); + return 0; + } + nd->u.scr.timertick=tick; + te=nd->u.scr.timer_event+ nd->u.scr.nexttimer; + nd->u.scr.timerid = -1; + + t = nd->u.scr.timer+=data; + nd->u.scr.nexttimer++; + if( nd->u.scr.timeramount>nd->u.scr.nexttimer ){ + next= nd->u.scr.timer_event[ nd->u.scr.nexttimer ].timer - t; + nd->u.scr.timerid = add_timer(tick+next,npc_timerevent,id,next); + } + + run_script(nd->u.scr.script,te->pos,0,nd->bl.id); + return 0; +} +/*========================================== + * タイマーイベント開始 + *------------------------------------------ + */ +int npc_timerevent_start(struct npc_data *nd) +{ + int j,n, next; + + nullpo_retr(0, nd); + + n=nd->u.scr.timeramount; + if( nd->u.scr.nexttimer>=0 || n==0 ) + return 0; + + for(j=0;j<n;j++){ + if( nd->u.scr.timer_event[j].timer > nd->u.scr.timer ) + break; + } + nd->u.scr.nexttimer=j; + nd->u.scr.timertick=gettick(); + + if(j>=n) + return 0; + + next = nd->u.scr.timer_event[j].timer - nd->u.scr.timer; + nd->u.scr.timerid = add_timer(gettick()+next,npc_timerevent,nd->bl.id,next); + return 0; +} +/*========================================== + * タイマーイベント終了 + *------------------------------------------ + */ +int npc_timerevent_stop(struct npc_data *nd) +{ + nullpo_retr(0, nd); + + if( nd->u.scr.nexttimer>=0 ){ + nd->u.scr.nexttimer = -1; + nd->u.scr.timer += (int)(gettick() - nd->u.scr.timertick); + if(nd->u.scr.timerid!=-1) + delete_timer(nd->u.scr.timerid,npc_timerevent); + nd->u.scr.timerid = -1; + } + return 0; +} +/*========================================== + * タイマー値の所得 + *------------------------------------------ + */ +int npc_gettimerevent_tick(struct npc_data *nd) +{ + int tick; + + nullpo_retr(0, nd); + + tick=nd->u.scr.timer; + + if( nd->u.scr.nexttimer>=0 ) + tick += (int)(gettick() - nd->u.scr.timertick); + return tick; +} +/*========================================== + * タイマー値の設定 + *------------------------------------------ + */ +int npc_settimerevent_tick(struct npc_data *nd,int newtimer) +{ + int flag; + + nullpo_retr(0, nd); + + flag= nd->u.scr.nexttimer; + + npc_timerevent_stop(nd); + nd->u.scr.timer=newtimer; + if(flag>=0) + npc_timerevent_start(nd); + return 0; +} + +/*========================================== + * イベント型のNPC処理 + *------------------------------------------ + */ +int npc_event(struct map_session_data *sd,const char *eventname,int mob_kill) +{ + struct event_data *ev=strdb_search(ev_db,eventname); + struct npc_data *nd; + int xs,ys; + char mobevent[100]; + + if( sd == NULL ){ + printf("npc_event nullpo?\n"); + } + + if(ev==NULL && eventname && strcmp(((eventname)+strlen(eventname)-9),"::OnTouch") == 0) + return 1; + + if(ev==NULL || (nd=ev->nd)==NULL){ + if(mob_kill && (ev==NULL || (nd=ev->nd)==NULL)){ + strcpy( mobevent, eventname); + strcat( mobevent, "::OnMyMobDead"); + ev=strdb_search(ev_db,mobevent); + if (ev==NULL || (nd=ev->nd)==NULL) { + if (strncasecmp(eventname,"GM_MONSTER",10)!=0) + printf("npc_event: event not found [%s]\n",mobevent); + return 0; + } + } + else { + if(battle_config.error_log) + printf("npc_event: event not found [%s]\n",eventname); + return 0; + } + } + + xs=nd->u.scr.xs; + ys=nd->u.scr.ys; + if (xs>=0 && ys>=0 ) { + if (nd->bl.m != sd->bl.m ) + return 1; + if ( xs>0 && (sd->bl.x<nd->bl.x-xs/2 || nd->bl.x+xs/2<sd->bl.x) ) + return 1; + if ( ys>0 && (sd->bl.y<nd->bl.y-ys/2 || nd->bl.y+ys/2<sd->bl.y) ) + return 1; + } + + if ( sd->npc_id!=0) { +// if (battle_config.error_log) +// printf("npc_event: npc_id != 0\n"); + int i; + for(i=0;i<MAX_EVENTQUEUE;i++) + if (!sd->eventqueue[i][0]) + break; + if (i==MAX_EVENTQUEUE) { + if (battle_config.error_log) + printf("npc_event: event queue is full !\n"); + }else{ +// if (battle_config.etc_log) +// printf("npc_event: enqueue\n"); + memcpy(sd->eventqueue[i],eventname,50); + } + return 1; + } + if (nd->flag&1) { // 無効化されている + npc_event_dequeue(sd); + return 0; + } + + sd->npc_id=nd->bl.id; + sd->npc_pos=run_script(nd->u.scr.script,ev->pos,sd->bl.id,nd->bl.id); + return 0; +} + + +int npc_command_sub(void *key,void *data,va_list ap) +{ + char *p=(char *)key; + struct event_data *ev=(struct event_data *)data; + char *npcname=va_arg(ap,char *); + char *command=va_arg(ap,char *); + char temp[100]; + + if(strcmp(ev->nd->name,npcname)==0 && (p=strchr(p,':')) && p && strncasecmp("::OnCommand",p,10)==0 ){ + sscanf(&p[11],"%s",temp); + + if (strcmp(command,temp)==0) + run_script(ev->nd->u.scr.script,ev->pos,0,ev->nd->bl.id); + } + + return 0; +} + +int npc_command(struct map_session_data *sd,char *npcname,char *command) +{ + strdb_foreach(ev_db,npc_command_sub,npcname,command); + + return 0; +} +/*========================================== + * 接触型のNPC処理 + *------------------------------------------ + */ +int npc_touch_areanpc(struct map_session_data *sd,int m,int x,int y) +{ + int i,f=1; + int xs,ys; + + nullpo_retr(1, sd); + + if(sd->npc_id) + return 1; + + for(i=0;i<map[m].npc_num;i++) { + if (map[m].npc[i]->flag&1) { // 無効化されている + f=0; + continue; + } + + switch(map[m].npc[i]->bl.subtype) { + case WARP: + xs=map[m].npc[i]->u.warp.xs; + ys=map[m].npc[i]->u.warp.ys; + break; + case SCRIPT: + xs=map[m].npc[i]->u.scr.xs; + ys=map[m].npc[i]->u.scr.ys; + break; + default: + continue; + } + if (x >= map[m].npc[i]->bl.x-xs/2 && x < map[m].npc[i]->bl.x-xs/2+xs && + y >= map[m].npc[i]->bl.y-ys/2 && y < map[m].npc[i]->bl.y-ys/2+ys) + break; + } + if (i==map[m].npc_num) { + if (f) { + if (battle_config.error_log) + printf("npc_touch_areanpc : some bug \n"); + } + return 1; + } + switch(map[m].npc[i]->bl.subtype) { + case WARP: + skill_stop_dancing(&sd->bl,0); + pc_setpos(sd,map[m].npc[i]->u.warp.name,map[m].npc[i]->u.warp.x,map[m].npc[i]->u.warp.y,0); + break; + case SCRIPT: + { + char *name=(char *)aCalloc(50,sizeof(char)); + + memcpy(name,map[m].npc[i]->name,50); + if(sd->areanpc_id==map[m].npc[i]->bl.id) + return 1; + sd->areanpc_id=map[m].npc[i]->bl.id; + if(npc_event(sd,strcat(name,"::OnTouch"),0)>0) + npc_click(sd,map[m].npc[i]->bl.id); + free(name); + break; + } + } + return 0; +} + +/*========================================== + * 近くかどうかの判定 + *------------------------------------------ + */ +int npc_checknear(struct map_session_data *sd,int id) +{ + struct npc_data *nd; + + nullpo_retr(0, sd); + + nd=(struct npc_data *)map_id2bl(id); + if (nd==NULL || nd->bl.type!=BL_NPC) { + if (battle_config.error_log) + printf("no such npc : %d\n",id); + return 1; + } + + if (nd->class<0) // イベント系は常にOK + return 0; + + // エリア判定 + if (nd->bl.m!=sd->bl.m || + nd->bl.x<sd->bl.x-AREA_SIZE-1 || nd->bl.x>sd->bl.x+AREA_SIZE+1 || + nd->bl.y<sd->bl.y-AREA_SIZE-1 || nd->bl.y>sd->bl.y+AREA_SIZE+1) + return 1; + + return 0; +} + +/*========================================== + * クリック時のNPC処理 + *------------------------------------------ + */ +int npc_click(struct map_session_data *sd,int id) +{ + struct npc_data *nd; + + nullpo_retr(1, sd); + + if (sd->npc_id != 0) { + if (battle_config.error_log) + printf("npc_click: npc_id != 0\n"); + return 1; + } + + if (npc_checknear(sd,id)) + return 1; + + nd=(struct npc_data *)map_id2bl(id); + + if (nd->flag&1) // 無効化されている + return 1; + + sd->npc_id=id; + switch(nd->bl.subtype) { + case SHOP: + clif_npcbuysell(sd,id); + npc_event_dequeue(sd); + break; + case SCRIPT: + sd->npc_pos=run_script(nd->u.scr.script,0,sd->bl.id,id); + break; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int npc_scriptcont(struct map_session_data *sd,int id) +{ + struct npc_data *nd; + + nullpo_retr(1, sd); + + if (id!=sd->npc_id) + return 1; + if (npc_checknear(sd,id)) + return 1; + + nd=(struct npc_data *)map_id2bl(id); + + sd->npc_pos=run_script(nd->u.scr.script,sd->npc_pos,sd->bl.id,id); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int npc_buysellsel(struct map_session_data *sd,int id,int type) +{ + struct npc_data *nd; + + nullpo_retr(1, sd); + + if (npc_checknear(sd,id)) + return 1; + + nd=(struct npc_data *)map_id2bl(id); + if (nd->bl.subtype!=SHOP) { + if (battle_config.error_log) + printf("no such shop npc : %d\n",id); + sd->npc_id=0; + return 1; + } + if (nd->flag&1) // 無効化されている + return 1; + + sd->npc_shopid=id; + if (type==0) { + clif_buylist(sd,nd); + } else { + clif_selllist(sd); + } + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int npc_buylist(struct map_session_data *sd,int n,unsigned short *item_list) +{ + struct npc_data *nd; + double z; + int i,j,w,skill,itemamount=0,new=0; + + nullpo_retr(3, sd); + nullpo_retr(3, item_list); + + if (npc_checknear(sd,sd->npc_shopid)) + return 3; + + nd=(struct npc_data*)map_id2bl(sd->npc_shopid); + if (nd->bl.subtype!=SHOP) + return 3; + + for(i=0,w=0,z=0;i<n;i++) { + for(j=0;nd->u.shop_item[j].nameid;j++) { + if (nd->u.shop_item[j].nameid==item_list[i*2+1]) + break; + } + if (nd->u.shop_item[j].nameid==0) + return 3; + + if (itemdb_value_notdc(nd->u.shop_item[j].nameid)) + z+=(double)nd->u.shop_item[j].value * item_list[i*2]; + else + z+=(double)pc_modifybuyvalue(sd,nd->u.shop_item[j].value) * item_list[i*2]; + itemamount+=item_list[i*2]; + + switch(pc_checkadditem(sd,item_list[i*2+1],item_list[i*2])) { + case ADDITEM_EXIST: + break; + case ADDITEM_NEW: + new++; + break; + case ADDITEM_OVERAMOUNT: + return 2; + } + + w+=itemdb_weight(item_list[i*2+1]) * item_list[i*2]; + } + if (z > (double)sd->status.zeny) + return 1; // zeny不足 + if (w+sd->weight > sd->max_weight) + return 2; // 重量超過 + if (pc_inventoryblank(sd)<new) + return 3; // 種類数超過 + + pc_payzeny(sd,(int)z); + for(i=0;i<n;i++) { + struct item item_tmp; + + memset(&item_tmp,0,sizeof(item_tmp)); + item_tmp.nameid = item_list[i*2+1]; + item_tmp.identify = 1; // npc販売アイテムは鑑定済み + + pc_additem(sd,&item_tmp,item_list[i*2]); + } + + //商人経験値 +/* if ((sd->status.class == 5) || (sd->status.class == 10) || (sd->status.class == 18)) { + z = z * pc_checkskill(sd,MC_DISCOUNT) / ((1 + 300 / itemamount) * 4000) * battle_config.shop_exp; + pc_gainexp(sd,0,z); + }*/ + if (battle_config.shop_exp > 0 && z > 0 && (skill = pc_checkskill(sd,MC_DISCOUNT)) > 0) { + if (sd->status.skill[MC_DISCOUNT].flag != 0) + skill = sd->status.skill[MC_DISCOUNT].flag - 2; + if (skill > 0) { + z = (log(z * (double)skill) * (double)battle_config.shop_exp/100.); + if (z < 1) + z = 1; + pc_gainexp(sd,0,(int)z); + } + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int npc_selllist(struct map_session_data *sd,int n,unsigned short *item_list) +{ + double z; + int i,skill,itemamount=0; + + nullpo_retr(1, sd); + nullpo_retr(1, item_list); + + if (npc_checknear(sd,sd->npc_shopid)) + return 1; + for(i=0,z=0;i<n;i++) { + int nameid; + if (item_list[i*2]-2 <0 || item_list[i*2]-2 >=MAX_INVENTORY) + return 1; + nameid=sd->status.inventory[item_list[i*2]-2].nameid; + if (nameid == 0 || + sd->status.inventory[item_list[i*2]-2].amount < item_list[i*2+1]) + return 1; + if (itemdb_value_notoc(nameid)) + z+=(double)itemdb_value_sell(nameid) * item_list[i*2+1]; + else + z+=(double)pc_modifysellvalue(sd,itemdb_value_sell(nameid)) * item_list[i*2+1]; + itemamount+=item_list[i*2+1]; + } + + if (z > MAX_ZENY) z = MAX_ZENY; + pc_getzeny(sd,(int)z); + for(i=0;i<n;i++) { + int item_id=item_list[i*2]-2; + if( sd->status.inventory[item_id].nameid>0 && sd->inventory_data[item_id] != NULL && + sd->inventory_data[item_id]->type==7 && sd->status.inventory[item_id].amount>0 && + sd->status.inventory[item_id].card[0] == (short)0xff00) + if(search_petDB_index(sd->status.inventory[item_id].nameid, PET_EGG) >= 0) + intif_delete_petdata((*(long *)(&sd->status.inventory[item_id].card[1]))); + pc_delitem(sd,item_id,item_list[i*2+1],0); + } + + //商人経験値 +/* if ((sd->status.class == 5) || (sd->status.class == 10) || (sd->status.class == 18)) { + z = z * pc_checkskill(sd,MC_OVERCHARGE) / ((1 + 500 / itemamount) * 4000) * battle_config.shop_exp ; + pc_gainexp(sd,0,z); + }*/ + if (battle_config.shop_exp > 0 && z > 0 && (skill = pc_checkskill(sd,MC_OVERCHARGE)) > 0) { + if (sd->status.skill[MC_OVERCHARGE].flag != 0) + skill = sd->status.skill[MC_OVERCHARGE].flag - 2; + if (skill > 0) { + z = (log(z * (double)skill) * (double)battle_config.shop_exp/100.); + if (z < 1) + z = 1; + pc_gainexp(sd,0,(int)z); + } + } + + return 0; + +} + +// +// 初期化関係 +// + +/*========================================== + * 読み込むnpcファイルのクリア + *------------------------------------------ + */ +void npc_clearsrcfile() +{ + struct npc_src_list *p=npc_src_first; + + while( p ) { + struct npc_src_list *p2=p; + p=p->next; + free(p2); + } + npc_src_first=NULL; + npc_src_last=NULL; +} +/*========================================== + * 読み込むnpcファイルの追加 + *------------------------------------------ + */ +void npc_addsrcfile(char *name) +{ + struct npc_src_list *new; + size_t len; + + if ( strcmpi(name,"clear")==0 ) { + npc_clearsrcfile(); + return; + } + + len = sizeof(*new) + strlen(name); + new=(struct npc_src_list *)aCalloc(1,len); + new->next = NULL; + strncpy(new->name,name,strlen(name)+1); + if (npc_src_first==NULL) + npc_src_first = new; + if (npc_src_last) + npc_src_last->next = new; + + npc_src_last=new; +} +/*========================================== + * 読み込むnpcファイルの削除 + *------------------------------------------ + */ +void npc_delsrcfile(char *name) +{ + struct npc_src_list *p=npc_src_first,*pp=NULL,**lp=&npc_src_first; + + if ( strcmpi(name,"all")==0 ) { + npc_clearsrcfile(); + return; + } + + for( ; p; lp=&p->next,pp=p,p=p->next ) { + if ( strcmp(p->name,name)==0 ) { + *lp=p->next; + if ( npc_src_last==p ) + npc_src_last=pp; + free(p); + break; + } + } +} + +/*========================================== + * warp行解析 + *------------------------------------------ + */ +int npc_parse_warp(char *w1,char *w2,char *w3,char *w4) +{ + int x,y,xs,ys,to_x,to_y,m; + int i,j; + char mapname[24],to_mapname[24]; + struct npc_data *nd; + + // 引数の個数チェック + if (sscanf(w1,"%[^,],%d,%d",mapname,&x,&y) != 3 || + sscanf(w4,"%d,%d,%[^,],%d,%d",&xs,&ys,to_mapname,&to_x,&to_y) != 5) { + printf("bad warp line : %s\n",w3); + return 1; + } + + m=map_mapname2mapid(mapname); + + nd=(struct npc_data *)aCalloc(1,sizeof(struct npc_data)); + nd->bl.id=npc_get_new_npc_id(); + nd->n=map_addnpc(m,nd); + + nd->bl.prev = nd->bl.next = NULL; + nd->bl.m=m; + nd->bl.x=x; + nd->bl.y=y; + nd->dir=0; + nd->flag=0; + memcpy(nd->name,w3,24); + memcpy(nd->exname,w3,24); + + nd->chat_id=0; + if (!battle_config.warp_point_debug) + nd->class=WARP_CLASS; + else + nd->class=WARP_DEBUG_CLASS; + nd->speed=200; + nd->option = 0; + nd->opt1 = 0; + nd->opt2 = 0; + nd->opt3 = 0; + memcpy(nd->u.warp.name,to_mapname,16); + xs+=2; ys+=2; + nd->u.warp.x=to_x; + nd->u.warp.y=to_y; + nd->u.warp.xs=xs; + nd->u.warp.ys=ys; + + for(i=0;i<ys;i++) { + for(j=0;j<xs;j++) { + int t; + t=map_getcell(m,x-xs/2+j,y-ys/2+i); + if (t==1 || t==5) + continue; + map_setcell(m,x-xs/2+j,y-ys/2+i,t|0x80); + } + } + +// printf("warp npc %s %d read done\n",mapname,nd->bl.id); + npc_warp++; + nd->bl.type=BL_NPC; + nd->bl.subtype=WARP; + map_addblock(&nd->bl); + clif_spawnnpc(nd); + strdb_insert(npcname_db,nd->name,nd); + + return 0; +} + +/*========================================== + * shop行解析 + *------------------------------------------ + */ +static int npc_parse_shop(char *w1,char *w2,char *w3,char *w4) +{ + char *p; + int x, y, dir, m; + int max = 100, pos = 0; + char mapname[24]; + struct npc_data *nd; + + // 引数の個数チェック + if (sscanf(w1, "%[^,],%d,%d,%d", mapname, &x, &y, &dir) != 4 || + strchr(w4, ',') == NULL) { + printf("bad shop line : %s\n", w3); + return 1; + } + m = map_mapname2mapid(mapname); + + nd = (struct npc_data *)aCalloc(1,sizeof(struct npc_data) + + sizeof(nd->u.shop_item[0]) * (max + 1)); + p = strchr(w4, ','); + + while (p && pos < max) { + int nameid,value; + p++; + if (sscanf(p, "%d:%d", &nameid, &value) != 2) + break; + nd->u.shop_item[pos].nameid = nameid; + if (value < 0) { + struct item_data *id = itemdb_search(nameid); + value = id->value_buy; + } + nd->u.shop_item[pos].value = value; + pos++; + p=strchr(p,','); + } + if (pos == 0) { + free(nd); + return 1; + } + nd->u.shop_item[pos++].nameid = 0; + + nd->bl.prev = nd->bl.next = NULL; + nd->bl.m = m; + nd->bl.x = x; + nd->bl.y = y; + nd->bl.id = npc_get_new_npc_id(); + nd->dir = dir; + nd->flag = 0; + memcpy(nd->name, w3, 24); + nd->class = atoi(w4); + nd->speed = 200; + nd->chat_id = 0; + nd->option = 0; + nd->opt1 = 0; + nd->opt2 = 0; + nd->opt3 = 0; + + nd = (struct npc_data *)aRealloc(nd, + sizeof(struct npc_data) + sizeof(nd->u.shop_item[0]) * pos); + + //printf("shop npc %s %d read done\n",mapname,nd->bl.id); + npc_shop++; + nd->bl.type=BL_NPC; + nd->bl.subtype=SHOP; + nd->n=map_addnpc(m,nd); + map_addblock(&nd->bl); + clif_spawnnpc(nd); + strdb_insert(npcname_db,nd->name,nd); + + return 0; +} +/*========================================== + * NPCのラベルデータコンバート + *------------------------------------------ + */ +int npc_convertlabel_db(void *key,void *data,va_list ap) +{ + char *lname=(char *)key; + int pos=(int)data; + struct npc_data *nd; + struct npc_label_list *lst; + int num; + char *p=strchr(lname,':'); + + nullpo_retr(0, ap); + nullpo_retr(0, nd=va_arg(ap,struct npc_data *)); + + lst=nd->u.scr.label_list; + num=nd->u.scr.label_list_num; + if(!lst){ + lst=(struct npc_label_list *)aCalloc(1,sizeof(struct npc_label_list)); + num=0; + }else + lst=(struct npc_label_list *)aRealloc(lst,sizeof(struct npc_label_list)*(num+1)); + + *p='\0'; + strncpy(lst[num].name,lname,24); + *p=':'; + lst[num].pos=pos; + nd->u.scr.label_list=lst; + nd->u.scr.label_list_num=num+1; + return 0; +} +/*========================================== + * script行解析 + *------------------------------------------ + */ +static int npc_parse_script(char *w1,char *w2,char *w3,char *w4,char *first_line,FILE *fp,int *lines) +{ + int x,y,dir=0,m,xs=0,ys=0,class=0; // [Valaris] thanks to fov + char mapname[24]; + unsigned char *srcbuf=NULL,*script; + int srcsize=65536; + int startline=0; + unsigned char line[1024]; + int i; + struct npc_data *nd; + int evflag=0; + struct dbt *label_db; + char *p; + struct npc_label_list *label_dup=NULL; + int label_dupnum=0; + int src_id=0; + + if(strcmp(w1,"-")==0){ + x=0;y=0;m=-1; + }else{ + // 引数の個数チェック + if (sscanf(w1,"%[^,],%d,%d,%d",mapname,&x,&y,&dir) != 4 || + ( strcmp(w2,"script")==0 && strchr(w4,',')==NULL) ) { + printf("bad script line : %s\n",w3); + return 1; + } + m = map_mapname2mapid(mapname); + } + + if(strcmp(w2,"script")==0){ + // スクリプトの解析 + srcbuf=(char *)aCalloc(srcsize,sizeof(char)); + if (strchr(first_line,'{')) { + strcpy(srcbuf,strchr(first_line,'{')); + startline=*lines; + } else + srcbuf[0]=0; + while(1) { + for(i=strlen(srcbuf)-1;i>=0 && isspace(srcbuf[i]);i--); + if (i>=0 && srcbuf[i]=='}') + break; + fgets(line,1020,fp); + (*lines)++; + if (feof(fp)) + break; + if (strlen(srcbuf)+strlen(line)+1>=srcsize) { + srcsize += 65536; + srcbuf = (char *)aRealloc(srcbuf, srcsize); + memset(srcbuf + srcsize - 65536, '\0', 65536); + } + if (srcbuf[0]!='{') { + if (strchr(line,'{')) { + strcpy(srcbuf,strchr(line,'{')); + startline=*lines; + } + } else + strcat(srcbuf,line); + } + script=parse_script(srcbuf,startline); + if (script==NULL) { + // script parse error? + free(srcbuf); + return 1; + } + + }else{ + // duplicateする + + char srcname[128]; + struct npc_data *nd2; + if( sscanf(w2,"duplicate(%[^)])",srcname)!=1 ){ + printf("bad duplicate name! : %s",w2); + return 0; + } + if( (nd2=npc_name2id(srcname))==NULL ){ + printf("bad duplicate name! (not exist) : %s\n",srcname); + return 0; + } + script=nd2->u.scr.script; + label_dup=nd2->u.scr.label_list; + label_dupnum=nd2->u.scr.label_list_num; + src_id=nd2->bl.id; + + }// end of スクリプト解析 + + nd=(struct npc_data *)aCalloc(1,sizeof(struct npc_data)); + + if(m==-1){ + // スクリプトコピー用のダミーNPC + + }else if( sscanf(w4,"%d,%d,%d",&class,&xs,&ys)==3) { + // 接触型NPC + int i,j; + + if (xs>=0)xs=xs*2+1; + if (ys>=0)ys=ys*2+1; + + if (class>=0) { + + for(i=0;i<ys;i++) { + for(j=0;j<xs;j++) { + int t; + t=map_getcell(m,x-xs/2+j,y-ys/2+i); + if (t==1 || t==5) + continue; + map_setcell(m,x-xs/2+j,y-ys/2+i,t|0x80); + } + } + } + + nd->u.scr.xs=xs; + nd->u.scr.ys=ys; + } else { // クリック型NPC + class=atoi(w4); + nd->u.scr.xs=0; + nd->u.scr.ys=0; + } + + if (class<0 && m>=0) { // イベント型NPC + evflag=1; + } + + while((p=strchr(w3,':'))) { + if (p[1]==':') break; + } + if (p) { + *p=0; + memcpy(nd->name,w3,24); + memcpy(nd->exname,p+2,24); + }else{ + memcpy(nd->name,w3,24); + memcpy(nd->exname,w3,24); + } + + nd->bl.prev = nd->bl.next = NULL; + nd->bl.m = m; + nd->bl.x = x; + nd->bl.y = y; + nd->bl.id=npc_get_new_npc_id(); + nd->dir = dir; + nd->flag=0; + nd->class=class; + nd->speed=200; + nd->u.scr.script=script; + nd->u.scr.src_id=src_id; + nd->chat_id=0; + nd->option = 0; + nd->opt1 = 0; + nd->opt2 = 0; + nd->opt3 = 0; + + //printf("script npc %s %d %d read done\n",mapname,nd->bl.id,nd->class); + npc_script++; + nd->bl.type=BL_NPC; + nd->bl.subtype=SCRIPT; + if(m>=0){ + nd->n=map_addnpc(m,nd); + map_addblock(&nd->bl); + + if (evflag) { // イベント型 + struct event_data *ev=(struct event_data *)aCalloc(1,sizeof(struct event_data)); + ev->nd=nd; + ev->pos=0; + strdb_insert(ev_db,nd->exname,ev); + }else + clif_spawnnpc(nd); + } + strdb_insert(npcname_db,nd->exname,nd); + + + //----------------------------------------- + // ラベルデータの準備 + if(srcbuf){ + // script本体がある場合の処理 + + // ラベルデータのコンバート + label_db=script_get_label_db(); + strdb_foreach(label_db,npc_convertlabel_db,nd); + + // もう使わないのでバッファ解放 + free(srcbuf); + + }else{ + // duplicate + +// nd->u.scr.label_list=malloc(sizeof(struct npc_label_list)*label_dupnum); +// memcpy(nd->u.scr.label_list,label_dup,sizeof(struct npc_label_list)*label_dupnum); + + nd->u.scr.label_list=label_dup; // ラベルデータ共有 + nd->u.scr.label_list_num=label_dupnum; + } + + //----------------------------------------- + // イベント用ラベルデータのエクスポート + for(i=0;i<nd->u.scr.label_list_num;i++){ + char *lname=nd->u.scr.label_list[i].name; + int pos=nd->u.scr.label_list[i].pos; + + if ((lname[0]=='O' || lname[0]=='o')&&(lname[1]=='N' || lname[1]=='n')) { + struct event_data *ev; + char *buf; + // エクスポートされる + ev=(struct event_data *)aCalloc(1,sizeof(struct event_data)); + buf=(char *)aCalloc(50,sizeof(char)); + if (strlen(lname)>24) { + printf("npc_parse_script: label name error !\n"); + exit(1); + }else{ + ev->nd=nd; + ev->pos=pos; + sprintf(buf,"%s::%s",nd->exname,lname); + strdb_insert(ev_db,buf,ev); + } + } + } + + //----------------------------------------- + // ラベルデータからタイマーイベント取り込み + for(i=0;i<nd->u.scr.label_list_num;i++){ + int t=0,k=0; + char *lname=nd->u.scr.label_list[i].name; + int pos=nd->u.scr.label_list[i].pos; + if(sscanf(lname,"OnTimer%d%n",&t,&k)==1 && lname[k]=='\0') { + // タイマーイベント + struct npc_timerevent_list *te=nd->u.scr.timer_event; + int j,k=nd->u.scr.timeramount; + if(te==NULL) + te=(struct npc_timerevent_list *)aCalloc(1,sizeof(struct npc_timerevent_list)); + else + te=(struct npc_timerevent_list *)aRealloc( te, sizeof(struct npc_timerevent_list) * (k+1) ); + for(j=0;j<k;j++){ + if(te[j].timer>t){ + memmove(te+j+1,te+j,sizeof(struct npc_timerevent_list)*(k-j)); + break; + } + } + te[j].timer=t; + te[j].pos=pos; + nd->u.scr.timer_event=te; + nd->u.scr.timeramount=k+1; + } + } + nd->u.scr.nexttimer=-1; + nd->u.scr.timerid=-1; + + + return 0; +} + +/*========================================== + * function行解析 + *------------------------------------------ + */ +static int npc_parse_function(char *w1,char *w2,char *w3,char *w4,char *first_line,FILE *fp,int *lines) +{ + char *srcbuf=NULL,*script; + int srcsize=65536; + int startline=0; + char line[1024]; + int i; +// struct dbt *label_db; + char *p; + + // スクリプトの解析 + srcbuf=(char *)aCalloc(srcsize,sizeof(char)); + if (strchr(first_line,'{')) { + strcpy(srcbuf,strchr(first_line,'{')); + startline=*lines; + } else + srcbuf[0]=0; + while(1) { + for(i=strlen(srcbuf)-1;i>=0 && isspace(srcbuf[i]);i--); + if (i>=0 && srcbuf[i]=='}') + break; + fgets(line,1020,fp); + (*lines)++; + if (feof(fp)) + break; + if (strlen(srcbuf)+strlen(line)+1>=srcsize) { + srcsize += 65536; + srcbuf = (char *)aRealloc(srcbuf, srcsize); + memset(srcbuf + srcsize - 65536, '\0', 65536); + } + if (srcbuf[0]!='{') { + if (strchr(line,'{')) { + strcpy(srcbuf,strchr(line,'{')); + startline=*lines; + } + } else + strcat(srcbuf,line); + } + script=parse_script(srcbuf,startline); + if (script==NULL) { + // script parse error? + free(srcbuf); + return 1; + } + + p=(char *)aCalloc(50,sizeof(char)); + + strncpy(p,w3,50); + strdb_insert(script_get_userfunc_db(),p,script); + +// label_db=script_get_label_db(); + + // もう使わないのでバッファ解放 + free(srcbuf); + +// printf("function %s => %p\n",p,script); + + return 0; +} + + +/*========================================== + * mob行解析 + *------------------------------------------ + */ +int npc_parse_mob(char *w1,char *w2,char *w3,char *w4) +{ + int m,x,y,xs,ys,class,num,delay1,delay2; + int i; + char mapname[24]; + char eventname[24]=""; + struct mob_data *md; + + xs=ys=0; + delay1=delay2=0; + // 引数の個数チェック + if (sscanf(w1,"%[^,],%d,%d,%d,%d",mapname,&x,&y,&xs,&ys) < 3 || + sscanf(w4,"%d,%d,%d,%d,%s",&class,&num,&delay1,&delay2,eventname) < 2 ) { + printf("bad monster line : %s\n",w3); + return 1; + } + + m=map_mapname2mapid(mapname); + + if ( num>1 && battle_config.mob_count_rate!=100) { + if ( (num=num*battle_config.mob_count_rate/100)<1 ) + num=1; + } + + for(i=0;i<num;i++) { + md=(struct mob_data *)aCalloc(1,sizeof(struct mob_data)); + + md->bl.prev=NULL; + md->bl.next=NULL; + md->bl.m=m; + md->bl.x=x; + md->bl.y=y; + if(strcmp(w3,"--en--")==0) + memcpy(md->name,mob_db[class].name,24); + else if(strcmp(w3,"--ja--")==0) + memcpy(md->name,mob_db[class].jname,24); + else + memcpy(md->name,w3,24); + + md->n = i; + md->base_class = md->class = class; + md->bl.id=npc_get_new_npc_id(); + md->m =m; + md->x0=x; + md->y0=y; + md->xs=xs; + md->ys=ys; + md->spawndelay1=delay1; + md->spawndelay2=delay2; + + memset(&md->state,0,sizeof(md->state)); + md->timer = -1; + md->target_id=0; + md->attacked_id=0; + md->speed=mob_db[class].speed; + + if (mob_db[class].mode&0x02) + md->lootitem=(struct item *)aCalloc(LOOTITEM_SIZE,sizeof(struct item)); + else + md->lootitem=NULL; + + if (strlen(eventname)>=4) { + memcpy(md->npc_event,eventname,24); + }else + memset(md->npc_event,0,24); + + md->bl.type=BL_MOB; + map_addiddb(&md->bl); + mob_spawn(md->bl.id); + + npc_mob++; + } + //printf("warp npc %s %d read done\n",mapname,nd->bl.id); + + return 0; +} + +/*========================================== + * マップフラグ行の解析 + *------------------------------------------ + */ +static int npc_parse_mapflag(char *w1,char *w2,char *w3,char *w4) +{ + int m; + char mapname[24],savemap[16]; + int savex,savey; + char drop_arg1[16],drop_arg2[16]; + int drop_id=0,drop_type=0,drop_per=0; + + // 引数の個数チェック +// if ( sscanf(w1,"%[^,],%d,%d,%d",mapname,&x,&y,&dir) != 4 ) + if ( sscanf(w1,"%[^,]",mapname) != 1 ) + return 1; + + m=map_mapname2mapid(mapname); + if (m<0) + return 1; + +//マップフラグ + if ( strcmpi(w3,"nosave")==0) { + if (strcmp(w4,"SavePoint")==0) { + memcpy(map[m].save.map,"SavePoint",16); + map[m].save.x=-1; + map[m].save.y=-1; + }else if (sscanf(w4,"%[^,],%d,%d",savemap,&savex,&savey)==3) { + memcpy(map[m].save.map,savemap,16); + map[m].save.x=savex; + map[m].save.y=savey; + } + map[m].flag.nosave=1; + } + else if (strcmpi(w3,"nomemo")==0) { + map[m].flag.nomemo=1; + } + else if (strcmpi(w3,"noteleport")==0) { + map[m].flag.noteleport=1; + } + else if (strcmpi(w3,"nowarp")==0) { + map[m].flag.nowarp=1; + } + else if (strcmpi(w3,"nowarpto")==0) { + map[m].flag.nowarpto=1; + } + else if (strcmpi(w3,"noreturn")==0) { + map[m].flag.noreturn=1; + } + else if (strcmpi(w3,"monster_noteleport")==0) { + map[m].flag.monster_noteleport=1; + } + else if (strcmpi(w3,"nobranch")==0) { + map[m].flag.nobranch=1; + } + else if (strcmpi(w3,"nopenalty")==0) { + map[m].flag.nopenalty=1; + } + else if (strcmpi(w3,"pvp")==0) { + map[m].flag.pvp=1; + } + else if (strcmpi(w3,"pvp_noparty")==0) { + map[m].flag.pvp_noparty=1; + } + else if (strcmpi(w3,"pvp_noguild")==0) { + map[m].flag.pvp_noguild=1; + } + else if (strcmpi(w3,"pvp_nightmaredrop")==0) { + if (sscanf(w4,"%[^,],%[^,],%d",drop_arg1,drop_arg2,&drop_per)==3) { int i; + if(strcmp(drop_arg1,"random")==0) + drop_id = -1; + else if(itemdb_exists( (drop_id=atoi(drop_arg1)) )==NULL) + drop_id = 0; + if(strcmp(drop_arg2,"inventory")==0) + drop_type = 1; + else if(strcmp(drop_arg2,"equip")==0) + drop_type = 2; + else if(strcmp(drop_arg2,"all")==0) + drop_type = 3; + + if(drop_id != 0){ + for (i=0;i<MAX_DROP_PER_MAP;i++){ + if(map[m].drop_list[i].drop_id==0){ + map[m].drop_list[i].drop_id = drop_id; + map[m].drop_list[i].drop_type = drop_type; + map[m].drop_list[i].drop_per = drop_per; + break; + } + } + map[m].flag.pvp_nightmaredrop=1; + } + } + } + else if (strcmpi(w3,"pvp_nocalcrank")==0) { + map[m].flag.pvp_nocalcrank=1; + } + else if (strcmpi(w3,"gvg")==0) { + map[m].flag.gvg=1; + } + else if (strcmpi(w3,"gvg_noparty")==0) { + map[m].flag.gvg_noparty=1; + } + else if (strcmpi(w3,"nozenypenalty")==0) { + map[m].flag.nozenypenalty=1; + } + else if (strcmpi(w3,"notrade")==0) { + map[m].flag.notrade=1; + } + else if (strcmpi(w3,"noskill")==0) { + map[m].flag.noskill=1; + } + else if (battle_config.pk_mode && strcmpi(w3,"nopvp")==0) { // nopvp for pk mode [Valaris] + map[m].flag.nopvp=1; + map[m].flag.pvp=0; + } + else if (strcmpi(w3,"noicewall")==0) { // noicewall [Valaris] + map[m].flag.noicewall=1; + } + else if (strcmpi(w3,"snow")==0) { // snow [Valaris] + map[m].flag.snow=1; + } + else if (strcmpi(w3,"fog")==0) { // fog [Valaris] + map[m].flag.fog=1; + } + else if (strcmpi(w3,"sakura")==0) { // sakura [Valaris] + map[m].flag.sakura=1; + } + else if (strcmpi(w3,"leaves")==0) { // leaves [Valaris] + map[m].flag.leaves=1; + } + else if (strcmpi(w3,"rain")==0) { // rain [Valaris] + map[m].flag.rain=1; + } + + return 0; +} + +static int ev_db_final(void *key,void *data,va_list ap) +{ + free(data); + if(strstr(key,"::")!=NULL) + free(key); + return 0; +} +static int npcname_db_final(void *key,void *data,va_list ap) +{ + return 0; +} +/*========================================== + * 終了 + *------------------------------------------ + */ +int do_final_npc(void) +{ + int i; + struct block_list *bl; + struct npc_data *nd; + struct mob_data *md; + struct chat_data *cd; + struct pet_data *pd; + + if(ev_db) + strdb_final(ev_db,ev_db_final); + if(npcname_db) + strdb_final(npcname_db,npcname_db_final); + + for(i=START_NPC_NUM;i<npc_id;i++){ + if((bl=map_id2bl(i))){ + if(bl->type == BL_NPC && (nd = (struct npc_data *)bl)){ + if(nd->chat_id && (cd=(struct chat_data*)map_id2bl(nd->chat_id))){ + free(cd); + cd = NULL; + } + if(nd->bl.subtype == SCRIPT){ + if(nd->u.scr.timer_event) + free(nd->u.scr.timer_event); + if(nd->u.scr.src_id==0){ + if(nd->u.scr.script){ + free(nd->u.scr.script); + nd->u.scr.script=NULL; + } + if(nd->u.scr.label_list){ + free(nd->u.scr.label_list); + nd->u.scr.label_list = NULL; + } + } + } + free(nd); + nd = NULL; + }else if(bl->type == BL_MOB && (md = (struct mob_data *)bl)){ + if(md->lootitem){ + free(md->lootitem); + md->lootitem = NULL; + } + free(md); + md = NULL; + }else if(bl->type == BL_PET && (pd = (struct pet_data *)bl)){ + free(pd); + pd = NULL; + } + } + } + + return 0; +} + + +void ev_release(struct dbn *db, int which) +{ + if (which & 0x1) + free(db->key); + if (which & 0x2) + free(db->data); +} + +/*========================================== + * npc初期化 + *------------------------------------------ + */ +int do_init_npc(void) +{ + struct npc_src_list *nsl; + FILE *fp; + char line[1024]; + int m,lines; + + ev_db=strdb_init(24); + npcname_db=strdb_init(24); + + ev_db->release = ev_release; + + memset(&ev_tm_b,-1,sizeof(ev_tm_b)); + + for(nsl=npc_src_first;nsl;nsl=nsl->next) { + if(nsl->prev){ + free(nsl->prev); + nsl->prev = NULL; + } + fp=fopen(nsl->name,"r"); + if (fp==NULL) { + printf("file not found : %s\n",nsl->name); + exit(1); + } + lines=0; + while(fgets(line,1020,fp)) { + char w1[1024],w2[1024],w3[1024],w4[1024],mapname[1024]; + int i,j,w4pos,count; + lines++; + + if (line[0] == '/' && line[1] == '/') + continue; + // 不要なスペースやタブの連続は詰める + for(i=j=0;line[i];i++) { + if (line[i]==' ') { + if (!((line[i+1] && (isspace(line[i+1]) || line[i+1]==',')) || + (j && line[j-1]==','))) + line[j++]=' '; + } else if (line[i]=='\t') { + if (!(j && line[j-1]=='\t')) + line[j++]='\t'; + } else + line[j++]=line[i]; + } + // 最初はタブ区切りでチェックしてみて、ダメならスペース区切りで確認 + if ((count=sscanf(line,"%[^\t]\t%[^\t]\t%[^\t\r\n]\t%n%[^\t\r\n]",w1,w2,w3,&w4pos,w4)) < 3 && + (count=sscanf(line,"%s%s%s%n%s",w1,w2,w3,&w4pos,w4)) < 3) { + continue; + } + // マップの存在確認 + if( strcmp(w1,"-")!=0 && strcmpi(w1,"function")!=0 ){ + sscanf(w1,"%[^,]",mapname); + m = map_mapname2mapid(mapname); + if (strlen(mapname)>16 || m<0) { + // "mapname" is not assigned to this server + continue; + } + } + if (strcmpi(w2,"warp")==0 && count > 3) { + npc_parse_warp(w1,w2,w3,w4); + } else if (strcmpi(w2,"shop")==0 && count > 3) { + npc_parse_shop(w1,w2,w3,w4); + } else if (strcmpi(w2,"script")==0 && count > 3) { + if( strcmpi(w1,"function")==0 ){ + npc_parse_function(w1,w2,w3,w4,line+w4pos,fp,&lines); + }else{ + npc_parse_script(w1,w2,w3,w4,line+w4pos,fp,&lines); + } + } else if ( (i=0,sscanf(w2,"duplicate%n",&i), (i>0 && w2[i]=='(')) && count > 3) { + npc_parse_script(w1,w2,w3,w4,line+w4pos,fp,&lines); + } else if (strcmpi(w2,"monster")==0 && count > 3) { + npc_parse_mob(w1,w2,w3,w4); + } else if (strcmpi(w2,"mapflag")==0 && count >= 3) { + npc_parse_mapflag(w1,w2,w3,w4); + } + } + fclose(fp); + printf("\rLoading NPCs [%d]: %-54s",npc_id-START_NPC_NUM,nsl->name); + fflush(stdout); + } + printf("\rNPCs Loaded: %d [Warps:%d Shops:%d Scripts:%d Mobs:%d]\n", + npc_id-START_NPC_NUM,npc_warp,npc_shop,npc_script,npc_mob); + + add_timer_func_list(npc_event_timer,"npc_event_timer"); + add_timer_func_list(npc_event_do_clock,"npc_event_do_clock"); + add_timer_func_list(npc_timerevent,"npc_timerevent"); + + //exit(1); + + return 0; +} diff --git a/misc/src/map/npc.h b/misc/src/map/npc.h new file mode 100644 index 0000000..63d7765 --- /dev/null +++ b/misc/src/map/npc.h @@ -0,0 +1,48 @@ +// $Id: npc.h,v 1.5 2004/09/25 11:39:17 MouseJstr Exp $ +#ifndef _NPC_H_ +#define _NPC_H_ + +#define START_NPC_NUM 110000000 + +#define WARP_CLASS 45 +#define WARP_DEBUG_CLASS 722 +#define INVISIBLE_CLASS 32767 + +int npc_event_dequeue(struct map_session_data *sd); +int npc_event_timer(int tid,unsigned int tick,int id,int data); +int npc_event(struct map_session_data *sd,const char *npcname,int); +int npc_timer_event(const char *eventname); // Added by RoVeRT +int npc_command(struct map_session_data *sd,char *npcname,char *command); +int npc_touch_areanpc(struct map_session_data *,int,int,int); +int npc_click(struct map_session_data *,int); +int npc_scriptcont(struct map_session_data *,int); +int npc_checknear(struct map_session_data *,int); +int npc_buysellsel(struct map_session_data *,int,int); +int npc_buylist(struct map_session_data *,int,unsigned short *); +int npc_selllist(struct map_session_data *,int,unsigned short *); +int npc_parse_mob(char *w1,char *w2,char *w3,char *w4); +int npc_parse_warp(char *w1,char *w2,char *w3,char *w4); + +int npc_enable(const char *name,int flag); +struct npc_data* npc_name2id(const char *name); + +int npc_get_new_npc_id(void); + +void npc_addsrcfile(char *); +void npc_delsrcfile(char *); +int do_final_npc(void); +int do_init_npc(void); +int npc_event_do_oninit(void); +int npc_do_ontimer(int,struct map_session_data *,int); + +int npc_event_doall(const char *name); +int npc_event_do(const char *name); + +int npc_timerevent_start(struct npc_data *nd); +int npc_timerevent_stop(struct npc_data *nd); +int npc_gettimerevent_tick(struct npc_data *nd); +int npc_settimerevent_tick(struct npc_data *nd,int newtimer); +int npc_delete(struct npc_data *nd); + +#endif + diff --git a/misc/src/map/party.c b/misc/src/map/party.c new file mode 100644 index 0000000..7d8cdaf --- /dev/null +++ b/misc/src/map/party.c @@ -0,0 +1,644 @@ +// $Id: party.c,v 1.2 2004/09/22 02:59:47 Akitasha Exp $ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "party.h" +#include "db.h" +#include "timer.h" +#include "socket.h" +#include "nullpo.h" +#include "malloc.h" +#include "pc.h" +#include "map.h" +#include "battle.h" +#include "intif.h" +#include "clif.h" + +#ifdef MEMWATCH +#include "memwatch.h" +#endif + +#define PARTY_SEND_XYHP_INVERVAL 1000 // 座標やHP送信の間隔 + +static struct dbt* party_db; + +int party_send_xyhp_timer(int tid,unsigned int tick,int id,int data); +/*========================================== + * 終了 + *------------------------------------------ + */ +static int party_db_final(void *key,void *data,va_list ap) +{ + free(data); + return 0; +} +void do_final_party(void) +{ + if(party_db) + numdb_final(party_db,party_db_final); +} +// 初期化 +void do_init_party(void) +{ + party_db=numdb_init(); + add_timer_func_list(party_send_xyhp_timer,"party_send_xyhp_timer"); + add_timer_interval(gettick()+PARTY_SEND_XYHP_INVERVAL,party_send_xyhp_timer,0,0,PARTY_SEND_XYHP_INVERVAL); +} + +// 検索 +struct party *party_search(int party_id) +{ + return numdb_search(party_db,party_id); +} +int party_searchname_sub(void *key,void *data,va_list ap) +{ + struct party *p=(struct party *)data,**dst; + char *str; + str=va_arg(ap,char *); + dst=va_arg(ap,struct party **); + if(strcmpi(p->name,str)==0) + *dst=p; + return 0; +} +// パーティ名検索 +struct party* party_searchname(char *str) +{ + struct party *p=NULL; + numdb_foreach(party_db,party_searchname_sub,str,&p); + return p; +} +// 作成要求 +int party_create(struct map_session_data *sd,char *name) +{ + nullpo_retr(0, sd); + + if(sd->status.party_id==0) + intif_create_party(sd,name); + else + clif_party_created(sd,2); + return 0; +} + +// 作成可否 +int party_created(int account_id,int fail,int party_id,char *name) +{ + struct map_session_data *sd; + sd=map_id2sd(account_id); + + nullpo_retr(0, sd); + + if(fail==0){ + struct party *p; + sd->status.party_id=party_id; + if((p=numdb_search(party_db,party_id))!=NULL){ + printf("party: id already exists!\n"); + exit(1); + } + p=(struct party *)aCalloc(1,sizeof(struct party)); + p->party_id=party_id; + memcpy(p->name,name,24); + numdb_insert(party_db,party_id,p); + clif_party_created(sd,0); + }else{ + clif_party_created(sd,1); + } + return 0; +} + +// 情報要求 +int party_request_info(int party_id) +{ + return intif_request_partyinfo(party_id); +} + +// 所属キャラの確認 +int party_check_member(struct party *p) +{ + int i; + struct map_session_data *sd; + + nullpo_retr(0, p); + + for(i=0;i<fd_max;i++){ + if(session[i] && (sd=session[i]->session_data) && sd->state.auth){ + if(sd->status.party_id==p->party_id){ + int j,f=1; + for(j=0;j<MAX_PARTY;j++){ // パーティにデータがあるか確認 + if( p->member[j].account_id==sd->status.account_id){ + if( strcmp(p->member[j].name,sd->status.name)==0 ) + f=0; // データがある + else + p->member[j].sd=NULL; // 同垢別キャラだった + } + } + if(f){ + sd->status.party_id=0; + if(battle_config.error_log) + printf("party: check_member %d[%s] is not member\n",sd->status.account_id,sd->status.name); + } + } + } + } + return 0; +} + +// 情報所得失敗(そのIDのキャラを全部未所属にする) +int party_recv_noinfo(int party_id) +{ + int i; + struct map_session_data *sd; + for(i=0;i<fd_max;i++){ + if(session[i] && (sd=session[i]->session_data) && sd->state.auth){ + if(sd->status.party_id==party_id) + sd->status.party_id=0; + } + } + return 0; +} +// 情報所得 +int party_recv_info(struct party *sp) +{ + struct party *p; + int i; + + nullpo_retr(0, sp); + + if((p=numdb_search(party_db,sp->party_id))==NULL){ + p=(struct party *)aCalloc(1,sizeof(struct party)); + numdb_insert(party_db,sp->party_id,p); + + // 最初のロードなのでユーザーのチェックを行う + party_check_member(sp); + } + memcpy(p,sp,sizeof(struct party)); + + for(i=0;i<MAX_PARTY;i++){ // sdの設定 + struct map_session_data *sd = map_id2sd(p->member[i].account_id); + p->member[i].sd=(sd!=NULL && sd->status.party_id==p->party_id)?sd:NULL; + } + + clif_party_info(p,-1); + + for(i=0;i<MAX_PARTY;i++){ // 設定情報の送信 +// struct map_session_data *sd = map_id2sd(p->member[i].account_id); + struct map_session_data *sd = p->member[i].sd; + if(sd!=NULL && sd->party_sended==0){ + clif_party_option(p,sd,0x100); + sd->party_sended=1; + } + } + + return 0; +} + +// パーティへの勧誘 +int party_invite(struct map_session_data *sd,int account_id) +{ + struct map_session_data *tsd= map_id2sd(account_id); + struct party *p=party_search(sd->status.party_id); + int i; + + nullpo_retr(0, sd); + + if(tsd==NULL || p==NULL) + return 0; + if(!battle_config.invite_request_check) { + if (tsd->guild_invite>0 || tsd->trade_partner) { // 相手が取引中かどうか + clif_party_inviteack(sd,tsd->status.name,0); + return 0; + } + } + if( tsd->status.party_id>0 || tsd->party_invite>0 ){ // 相手の所属確認 + clif_party_inviteack(sd,tsd->status.name,0); + return 0; + } + for(i=0;i<MAX_PARTY;i++){ // 同アカウント確認 + if(p->member[i].account_id==account_id){ + clif_party_inviteack(sd,tsd->status.name,0); + return 0; + } + } + + tsd->party_invite=sd->status.party_id; + tsd->party_invite_account=sd->status.account_id; + + clif_party_invite(sd,tsd); + return 0; +} +// パーティ勧誘への返答 +int party_reply_invite(struct map_session_data *sd,int account_id,int flag) +{ + struct map_session_data *tsd= map_id2sd(account_id); + + nullpo_retr(0, sd); + + if(flag==1){ // 承諾 + //inter鯖へ追加要求 + intif_party_addmember( sd->party_invite, sd->status.account_id ); + return 0; + } + else { // 拒否 + sd->party_invite=0; + sd->party_invite_account=0; + if(tsd==NULL) + return 0; + clif_party_inviteack(tsd,sd->status.name,1); + } + return 0; +} +// パーティが追加された +int party_member_added(int party_id,int account_id,int flag) +{ + struct map_session_data *sd= map_id2sd(account_id),*sd2; + if(sd==NULL && flag==0){ + if(battle_config.error_log) + printf("party: member added error %d is not online\n",account_id); + intif_party_leave(party_id,account_id); // キャラ側に登録できなかったため脱退要求を出す + return 0; + } + sd2=map_id2sd(sd->party_invite_account); + sd->party_invite=0; + sd->party_invite_account=0; + + if(flag==1){ // 失敗 + if( sd2!=NULL ) + clif_party_inviteack(sd2,sd->status.name,0); + return 0; + } + + // 成功 + sd->party_sended=0; + sd->status.party_id=party_id; + + if( sd2!=NULL) + clif_party_inviteack(sd2,sd->status.name,2); + + // いちおう競合確認 + party_check_conflict(sd); + + return 0; +} +// パーティ除名要求 +int party_removemember(struct map_session_data *sd,int account_id,char *name) +{ + struct party *p; + int i; + + nullpo_retr(0, sd); + + if( (p = party_search(sd->status.party_id)) == NULL ) + return 0; + + for(i=0;i<MAX_PARTY;i++){ // リーダーかどうかチェック + if(p->member[i].account_id==sd->status.account_id) + if(p->member[i].leader==0) + return 0; + } + + for(i=0;i<MAX_PARTY;i++){ // 所属しているか調べる + if(p->member[i].account_id==account_id){ + intif_party_leave(p->party_id,account_id); + return 0; + } + } + return 0; +} + +// パーティ脱退要求 +int party_leave(struct map_session_data *sd) +{ + struct party *p; + int i; + + nullpo_retr(0, sd); + + if( (p = party_search(sd->status.party_id)) == NULL ) + return 0; + + for(i=0;i<MAX_PARTY;i++){ // 所属しているか + if(p->member[i].account_id==sd->status.account_id){ + intif_party_leave(p->party_id,sd->status.account_id); + return 0; + } + } + return 0; +} +// パーティメンバが脱退した +int party_member_leaved(int party_id,int account_id,char *name) +{ + struct map_session_data *sd=map_id2sd(account_id); + struct party *p=party_search(party_id); + if(p!=NULL){ + int i; + for(i=0;i<MAX_PARTY;i++) + if(p->member[i].account_id==account_id){ + clif_party_leaved(p,sd,account_id,name,0x00); + p->member[i].account_id=0; + p->member[i].sd=NULL; + } + } + if(sd!=NULL && sd->status.party_id==party_id){ + sd->status.party_id=0; + sd->party_sended=0; + } + return 0; +} +// パーティ解散通知 +int party_broken(int party_id) +{ + struct party *p; + int i; + if( (p=party_search(party_id))==NULL ) + return 0; + + for(i=0;i<MAX_PARTY;i++){ + if(p->member[i].sd!=NULL){ + clif_party_leaved(p,p->member[i].sd, + p->member[i].account_id,p->member[i].name,0x10); + p->member[i].sd->status.party_id=0; + p->member[i].sd->party_sended=0; + } + } + numdb_erase(party_db,party_id); + return 0; +} +// パーティの設定変更要求 +int party_changeoption(struct map_session_data *sd,int exp,int item) +{ + struct party *p; + + nullpo_retr(0, sd); + + if( sd->status.party_id==0 || (p=party_search(sd->status.party_id))==NULL ) + return 0; + intif_party_changeoption(sd->status.party_id,sd->status.account_id,exp,item); + return 0; +} +// パーティの設定変更通知 +int party_optionchanged(int party_id,int account_id,int exp,int item,int flag) +{ + struct party *p; + struct map_session_data *sd=map_id2sd(account_id); + if( (p=party_search(party_id))==NULL) + return 0; + + if(!(flag&0x01)) p->exp=exp; + if(!(flag&0x10)) p->item=item; + clif_party_option(p,sd,flag); + return 0; +} + +// パーティメンバの移動通知 +int party_recv_movemap(int party_id,int account_id,char *map,int online,int lv) +{ + struct party *p; + int i; + if( (p=party_search(party_id))==NULL) + return 0; + for(i=0;i<MAX_PARTY;i++){ + struct party_member *m=&p->member[i]; + if( m == NULL ){ + printf("party_recv_movemap nullpo?\n"); + return 0; + } + if(m->account_id==account_id){ + memcpy(m->map,map,16); + m->online=online; + m->lv=lv; + break; + } + } + if(i==MAX_PARTY){ + if(battle_config.error_log) + printf("party: not found member %d on %d[%s]",account_id,party_id,p->name); + return 0; + } + + for(i=0;i<MAX_PARTY;i++){ // sd再設定 + struct map_session_data *sd= map_id2sd(p->member[i].account_id); + p->member[i].sd=(sd!=NULL && sd->status.party_id==p->party_id)?sd:NULL; + } + + party_send_xy_clear(p); // 座標再通知要請 + + clif_party_info(p,-1); + return 0; +} + +// パーティメンバの移動 +int party_send_movemap(struct map_session_data *sd) +{ + struct party *p; + + nullpo_retr(0, sd); + + if( sd->status.party_id==0 ) + return 0; + intif_party_changemap(sd,1); + + if( sd->party_sended!=0 ) // もうパーティデータは送信済み + return 0; + + // 競合確認 + party_check_conflict(sd); + + // あるならパーティ情報送信 + if( (p=party_search(sd->status.party_id))!=NULL ){ + party_check_member(p); // 所属を確認する + if(sd->status.party_id==p->party_id){ + clif_party_info(p,sd->fd); + clif_party_option(p,sd,0x100); + sd->party_sended=1; + } + } + + return 0; +} +// パーティメンバのログアウト +int party_send_logout(struct map_session_data *sd) +{ + struct party *p; + + nullpo_retr(0, sd); + + if( sd->status.party_id>0 ) + intif_party_changemap(sd,0); + + // sdが無効になるのでパーティ情報から削除 + if( (p=party_search(sd->status.party_id))!=NULL ){ + int i; + for(i=0;i<MAX_PARTY;i++) + if(p->member[i].sd==sd) + p->member[i].sd=NULL; + } + + return 0; +} +// パーティメッセージ送信 +int party_send_message(struct map_session_data *sd,char *mes,int len) +{ + if(sd->status.party_id==0) + return 0; + intif_party_message(sd->status.party_id,sd->status.account_id,mes,len); + return 0; +} + +// パーティメッセージ受信 +int party_recv_message(int party_id,int account_id,char *mes,int len) +{ + struct party *p; + if( (p=party_search(party_id))==NULL) + return 0; + clif_party_message(p,account_id,mes,len); + return 0; +} +// パーティ競合確認 +int party_check_conflict(struct map_session_data *sd) +{ + nullpo_retr(0, sd); + + intif_party_checkconflict(sd->status.party_id,sd->status.account_id,sd->status.name); + return 0; +} + + +// 位置やHP通知用 +int party_send_xyhp_timer_sub(void *key,void *data,va_list ap) +{ + struct party *p=(struct party *)data; + int i; + + nullpo_retr(0, p); + + for(i=0;i<MAX_PARTY;i++){ + struct map_session_data *sd; + if((sd=p->member[i].sd)!=NULL){ + // 座標通知 + if(sd->party_x!=sd->bl.x || sd->party_y!=sd->bl.y){ + clif_party_xy(p,sd); + sd->party_x=sd->bl.x; + sd->party_y=sd->bl.y; + } + // HP通知 + if(sd->party_hp!=sd->status.hp){ + clif_party_hp(p,sd); + sd->party_hp=sd->status.hp; + } + + } + } + return 0; +} +// 位置やHP通知 +int party_send_xyhp_timer(int tid,unsigned int tick,int id,int data) +{ + numdb_foreach(party_db,party_send_xyhp_timer_sub,tick); + return 0; +} + +// 位置通知クリア +int party_send_xy_clear(struct party *p) +{ + int i; + + nullpo_retr(0, p); + + for(i=0;i<MAX_PARTY;i++){ + struct map_session_data *sd; + if((sd=p->member[i].sd)!=NULL){ + sd->party_x=-1; + sd->party_y=-1; + sd->party_hp=-1; + } + } + return 0; +} +// HP通知の必要性検査用(map_foreachinmoveareaから呼ばれる) +int party_send_hp_check(struct block_list *bl,va_list ap) +{ + int party_id; + int *flag; + struct map_session_data *sd; + + nullpo_retr(0, bl); + nullpo_retr(0, ap); + nullpo_retr(0, sd=(struct map_session_data *)bl); + + party_id=va_arg(ap,int); + flag=va_arg(ap,int *); + + if(sd->status.party_id==party_id){ + *flag=1; + sd->party_hp=-1; + } + return 0; +} + +// 経験値公平分配 +int party_exp_share(struct party *p,int map,int base_exp,int job_exp) +{ + struct map_session_data *sd; + int i,c; + + nullpo_retr(0, p); + + for(i=c=0;i<MAX_PARTY;i++) + if((sd=p->member[i].sd)!=NULL && sd->bl.m==map) + c++; + if(c==0) + return 0; + for(i=0;i<MAX_PARTY;i++) + if((sd=p->member[i].sd)!=NULL && sd->bl.m==map) + pc_gainexp(sd,base_exp/c+1,job_exp/c+1); + return 0; +} + +// 同じマップのパーティメンバー全体に処理をかける +// type==0 同じマップ +// !=0 画面内 +void party_foreachsamemap(int (*func)(struct block_list*,va_list), + struct map_session_data *sd,int type,...) +{ + struct party *p; + va_list ap; + int i; + int x0,y0,x1,y1; + struct block_list *list[MAX_PARTY]; + int blockcount=0; + + nullpo_retv(sd); + + if((p=party_search(sd->status.party_id))==NULL) + return; + + x0=sd->bl.x-AREA_SIZE; + y0=sd->bl.y-AREA_SIZE; + x1=sd->bl.x+AREA_SIZE; + y1=sd->bl.y+AREA_SIZE; + + va_start(ap,type); + + for(i=0;i<MAX_PARTY;i++){ + struct party_member *m=&p->member[i]; + if(m->sd!=NULL){ + if(sd->bl.m!=m->sd->bl.m) + continue; + if(type!=0 && + (m->sd->bl.x<x0 || m->sd->bl.y<y0 || + m->sd->bl.x>x1 || m->sd->bl.y>y1 ) ) + continue; + list[blockcount++]=&m->sd->bl; + } + } + + map_freeblock_lock(); // メモリからの解放を禁止する + + for(i=0;i<blockcount;i++) + if(list[i]->prev) // 有効かどうかチェック + func(list[i],ap); + + map_freeblock_unlock(); // 解放を許可する + + va_end(ap); +} diff --git a/misc/src/map/party.h b/misc/src/map/party.h new file mode 100644 index 0000000..28d8096 --- /dev/null +++ b/misc/src/map/party.h @@ -0,0 +1,47 @@ +// $Id: party.h,v 1.3 2004/09/25 05:32:18 MouseJstr Exp $ +#ifndef _PARTY_H_ +#define _PARTY_H_ + +#include <stdarg.h> + +struct party; +struct map_session_data; +struct block_list; + +void do_init_party(void); +void do_final_party(void); +struct party *party_search(int party_id); +struct party* party_searchname(char *str); + +int party_create(struct map_session_data *sd,char *name); +int party_created(int account_id,int fail,int party_id,char *name); +int party_request_info(int party_id); +int party_invite(struct map_session_data *sd,int account_id); +int party_member_added(int party_id,int account_id,int flag); +int party_leave(struct map_session_data *sd); +int party_removemember(struct map_session_data *sd,int account_id,char *name); +int party_member_leaved(int party_id,int account_id,char *name); +int party_reply_invite(struct map_session_data *sd,int account_id,int flag); +int party_recv_noinfo(int party_id); +int party_recv_info(struct party *sp); +int party_recv_movemap(int party_id,int account_id,char *map,int online,int lv); +int party_broken(int party_id); +int party_optionchanged(int party_id,int account_id,int exp,int item,int flag); +int party_changeoption(struct map_session_data *sd,int exp,int item); + +int party_send_movemap(struct map_session_data *sd); +int party_send_logout(struct map_session_data *sd); + +int party_send_message(struct map_session_data *sd,char *mes,int len); +int party_recv_message(int party_id,int account_id,char *mes,int len); + +int party_check_conflict(struct map_session_data *sd); + +int party_send_xy_clear(struct party *p); +int party_send_hp_check(struct block_list *bl,va_list ap); + +int party_exp_share(struct party *p,int map,int base_exp,int job_exp); + +void party_foreachsamemap(int (*func)(struct block_list *,va_list),struct map_session_data *sd,int type,...); + +#endif diff --git a/misc/src/map/path.c b/misc/src/map/path.c new file mode 100644 index 0000000..b2e0a78 --- /dev/null +++ b/misc/src/map/path.c @@ -0,0 +1,404 @@ +// $Id: path.c,v 1.1.1.1 2004/09/10 17:27:00 MagicalTux Exp $ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "map.h" +#include "battle.h" +#include "nullpo.h" + +#ifdef MEMWATCH +#include "memwatch.h" +#endif + +//#define PATH_STANDALONETEST + +#define MAX_HEAP 150 +struct tmp_path { short x,y,dist,before,cost; char dir,flag;}; +#define calc_index(x,y) (((x)+(y)*MAX_WALKPATH) & (MAX_WALKPATH*MAX_WALKPATH-1)) + +/*========================================== + * 経路探索補助heap push + *------------------------------------------ + */ +static void push_heap_path(int *heap,struct tmp_path *tp,int index) +{ + int i,h; + + if( heap == NULL || tp == NULL ){ + printf("push_heap_path nullpo\n"); + return; + } + + heap[0]++; + + for(h=heap[0]-1,i=(h-1)/2; + h>0 && tp[index].cost<tp[heap[i+1]].cost; + i=(h-1)/2) + heap[h+1]=heap[i+1],h=i; + heap[h+1]=index; +} + +/*========================================== + * 経路探索補助heap update + * costが減ったので根の方へ移動 + *------------------------------------------ + */ +static void update_heap_path(int *heap,struct tmp_path *tp,int index) +{ + int i,h; + + nullpo_retv(heap); + nullpo_retv(tp); + + for(h=0;h<heap[0];h++) + if(heap[h+1]==index) + break; + if(h==heap[0]){ + fprintf(stderr,"update_heap_path bug\n"); + exit(1); + } + for(i=(h-1)/2; + h>0 && tp[index].cost<tp[heap[i+1]].cost; + i=(h-1)/2) + heap[h+1]=heap[i+1],h=i; + heap[h+1]=index; +} + +/*========================================== + * 経路探索補助heap pop + *------------------------------------------ + */ +static int pop_heap_path(int *heap,struct tmp_path *tp) +{ + int i,h,k; + int ret,last; + + nullpo_retr(-1, heap); + nullpo_retr(-1, tp); + + if(heap[0]<=0) + return -1; + ret=heap[1]; + last=heap[heap[0]]; + heap[0]--; + + for(h=0,k=2;k<heap[0];k=k*2+2){ + if(tp[heap[k+1]].cost>tp[heap[k]].cost) + k--; + heap[h+1]=heap[k+1], h=k; + } + if(k==heap[0]) + heap[h+1]=heap[k], h=k-1; + + for(i=(h-1)/2; + h>0 && tp[heap[i+1]].cost>tp[last].cost; + i=(h-1)/2) + heap[h+1]=heap[i+1],h=i; + heap[h+1]=last; + + return ret; +} + +/*========================================== + * 現在の点のcost計算 + *------------------------------------------ + */ +static int calc_cost(struct tmp_path *p,int x1,int y1) +{ + int xd,yd; + + nullpo_retr(0, p); + + xd=x1-p->x; + if(xd<0) xd=-xd; + yd=y1-p->y; + if(yd<0) yd=-yd; + return (xd+yd)*10+p->dist; +} + +/*========================================== + * 必要ならpathを追加/修正する + *------------------------------------------ + */ +static int add_path(int *heap,struct tmp_path *tp,int x,int y,int dist,int dir,int before,int x1,int y1) +{ + int i; + + nullpo_retr(0, heap); + nullpo_retr(0, tp); + + i=calc_index(x,y); + + if(tp[i].x==x && tp[i].y==y){ + if(tp[i].dist>dist){ + tp[i].dist=dist; + tp[i].dir=dir; + tp[i].before=before; + tp[i].cost=calc_cost(&tp[i],x1,y1); + if(tp[i].flag) + push_heap_path(heap,tp,i); + else + update_heap_path(heap,tp,i); + tp[i].flag=0; + } + return 0; + } + + if(tp[i].x || tp[i].y) + return 1; + + tp[i].x=x; + tp[i].y=y; + tp[i].dist=dist; + tp[i].dir=dir; + tp[i].before=before; + tp[i].cost=calc_cost(&tp[i],x1,y1); + tp[i].flag=0; + push_heap_path(heap,tp,i); + + return 0; +} + + +/*========================================== + * (x,y)が移動不可能地帯かどうか + * flag 0x10000 遠距離攻撃判定 + *------------------------------------------ + */ +static int can_place(struct map_data *m,int x,int y,int flag) +{ + int c; + + nullpo_retr(0, m); + + c=read_gatp(m,x,y); + + if(c==1) + return 0; + if(!(flag&0x10000) && c==5) + return 0; + return 1; +} + +/*========================================== + * (x0,y0)から(x1,y1)へ1歩で移動可能か計算 + *------------------------------------------ + */ +static int can_move(struct map_data *m,int x0,int y0,int x1,int y1,int flag) +{ + nullpo_retr(0, m); + + if(x0-x1<-1 || x0-x1>1 || y0-y1<-1 || y0-y1>1) + return 0; + if(x1<0 || y1<0 || x1>=m->xs || y1>=m->ys) + return 0; + if(!can_place(m,x0,y0,flag)) + return 0; + if(!can_place(m,x1,y1,flag)) + return 0; + if(x0==x1 || y0==y1) + return 1; + if(!can_place(m,x0,y1,flag) || !can_place(m,x1,y0,flag)) + return 0; + return 1; +} +/*========================================== + * (x0,y0)から(dx,dy)方向へcountセル分 + * 吹き飛ばしたあとの座標を所得 + *------------------------------------------ + */ +int path_blownpos(int m,int x0,int y0,int dx,int dy,int count) +{ + struct map_data *md; + + if(!map[m].gat) + return -1; + md=&map[m]; + + if(count>15){ // 最大10マスに制限 + if(battle_config.error_log) + printf("path_blownpos: count too many %d !\n",count); + count=15; + } + if(dx>1 || dx<-1 || dy>1 || dy<-1){ + if(battle_config.error_log) + printf("path_blownpos: illeagal dx=%d or dy=%d !\n",dx,dy); + dx=(dx>=0)?1:((dx<0)?-1:0); + dy=(dy>=0)?1:((dy<0)?-1:0); + } + + while( (count--)>0 && (dx!=0 || dy!=0) ){ + if( !can_move(md,x0,y0,x0+dx,y0+dy,0) ){ + int fx=(dx!=0 && can_move(md,x0,y0,x0+dx,y0,0)); + int fy=(dy!=0 && can_move(md,x0,y0,x0,y0+dy,0)); + if( fx && fy ){ + if(rand()&1) dx=0; + else dy=0; + } + if( !fx ) dx=0; + if( !fy ) dy=0; + } + x0+=dx; + y0+=dy; + } + return (x0<<16)|y0; +} + +/*========================================== + * path探索 (x0,y0)->(x1,y1) + *------------------------------------------ + */ +int path_search(struct walkpath_data *wpd,int m,int x0,int y0,int x1,int y1,int flag) +{ + int heap[MAX_HEAP+1]; + struct tmp_path tp[MAX_WALKPATH*MAX_WALKPATH]; + int i,rp,x,y; + struct map_data *md; + int dx,dy; + + nullpo_retr(0, wpd); + + if(!map[m].gat) + return -1; + md=&map[m]; + if(x1<0 || x1>=md->xs || y1<0 || y1>=md->ys || (i=read_gatp(md,x1,y1))==1 || i==5) + return -1; + + // easy + dx = (x1-x0<0) ? -1 : 1; + dy = (y1-y0<0) ? -1 : 1; + for(x=x0,y=y0,i=0;x!=x1 || y!=y1;){ + if(i>=sizeof(wpd->path)) + return -1; + if(x!=x1 && y!=y1){ + if(!can_move(md,x,y,x+dx,y+dy,flag)) + break; + x+=dx; + y+=dy; + wpd->path[i++]=(dx<0) ? ((dy>0)? 1 : 3) : ((dy<0)? 5 : 7); + } else if(x!=x1){ + if(!can_move(md,x,y,x+dx,y ,flag)) + break; + x+=dx; + wpd->path[i++]=(dx<0) ? 2 : 6; + } else { // y!=y1 + if(!can_move(md,x,y,x ,y+dy,flag)) + break; + y+=dy; + wpd->path[i++]=(dy>0) ? 0 : 4; + } + if(x==x1 && y==y1){ + wpd->path_len=i; + wpd->path_pos=0; + wpd->path_half=0; + return 0; + } + } + if(flag&1) + return -1; + + memset(tp,0,sizeof(tp)); + + i=calc_index(x0,y0); + tp[i].x=x0; + tp[i].y=y0; + tp[i].dist=0; + tp[i].dir=0; + tp[i].before=0; + tp[i].cost=calc_cost(&tp[i],x1,y1); + tp[i].flag=0; + heap[0]=0; + push_heap_path(heap,tp,calc_index(x0,y0)); + while(1){ + int e=0,fromdir; + + if(heap[0]==0) + return -1; + rp=pop_heap_path(heap,tp); + x=tp[rp].x; + y=tp[rp].y; + if(x==x1 && y==y1){ + int len,j; + + for(len=0,i=rp;len<100 && i!=calc_index(x0,y0);i=tp[i].before,len++); + if(len==100 || len>=sizeof(wpd->path)) + return -1; + wpd->path_len=len; + wpd->path_pos=0; + wpd->path_half=0; + for(i=rp,j=len-1;j>=0;i=tp[i].before,j--) + wpd->path[j]=tp[i].dir; + + return 0; + } + fromdir=tp[rp].dir; + if(can_move(md,x,y,x+1,y-1,flag)) + e+=add_path(heap,tp,x+1,y-1,tp[rp].dist+14,5,rp,x1,y1); + if(can_move(md,x,y,x+1,y ,flag)) + e+=add_path(heap,tp,x+1,y ,tp[rp].dist+10,6,rp,x1,y1); + if(can_move(md,x,y,x+1,y+1,flag)) + e+=add_path(heap,tp,x+1,y+1,tp[rp].dist+14,7,rp,x1,y1); + if(can_move(md,x,y,x ,y+1,flag)) + e+=add_path(heap,tp,x ,y+1,tp[rp].dist+10,0,rp,x1,y1); + if(can_move(md,x,y,x-1,y+1,flag)) + e+=add_path(heap,tp,x-1,y+1,tp[rp].dist+14,1,rp,x1,y1); + if(can_move(md,x,y,x-1,y ,flag)) + e+=add_path(heap,tp,x-1,y ,tp[rp].dist+10,2,rp,x1,y1); + if(can_move(md,x,y,x-1,y-1,flag)) + e+=add_path(heap,tp,x-1,y-1,tp[rp].dist+14,3,rp,x1,y1); + if(can_move(md,x,y,x ,y-1,flag)) + e+=add_path(heap,tp,x ,y-1,tp[rp].dist+10,4,rp,x1,y1); + tp[rp].flag=1; + if(e || heap[0]>=MAX_HEAP-5) + return -1; + } + return -1; +} + +#ifdef PATH_STANDALONETEST +char gat[64][64]={ + {0,0,0,0,0,0,0,0,0,0}, + {0,0,0,0,0,0,0,0,0,0}, + {0,0,0,0,0,0,0,0,0,0}, + {0,0,0,0,0,0,0,0,0,0}, + {0,0,0,0,1,0,0,0,0,0}, +}; +struct map_data map[1]; + +/*========================================== + * 経路探索ルーチン単体テスト用main関数 + *------------------------------------------ + */ +void main(int argc,char *argv[]) +{ + struct walkpath_data wpd; + + map[0].gat=gat; + map[0].xs=64; + map[0].ys=64; + + path_search(&wpd,0,3,4,5,4); + path_search(&wpd,0,5,4,3,4); + path_search(&wpd,0,6,4,3,4); + path_search(&wpd,0,7,4,3,4); + path_search(&wpd,0,4,3,4,5); + path_search(&wpd,0,4,2,4,5); + path_search(&wpd,0,4,1,4,5); + path_search(&wpd,0,4,5,4,3); + path_search(&wpd,0,4,6,4,3); + path_search(&wpd,0,4,7,4,3); + path_search(&wpd,0,7,4,3,4); + path_search(&wpd,0,8,4,3,4); + path_search(&wpd,0,9,4,3,4); + path_search(&wpd,0,10,4,3,4); + path_search(&wpd,0,11,4,3,4); + path_search(&wpd,0,12,4,3,4); + path_search(&wpd,0,13,4,3,4); + path_search(&wpd,0,14,4,3,4); + path_search(&wpd,0,15,4,3,4); + path_search(&wpd,0,16,4,3,4); + path_search(&wpd,0,17,4,3,4); + path_search(&wpd,0,18,4,3,4); +} +#endif diff --git a/misc/src/map/pc.c b/misc/src/map/pc.c new file mode 100644 index 0000000..4e702c0 --- /dev/null +++ b/misc/src/map/pc.c @@ -0,0 +1,7485 @@ +// $Id: pc.c 101 2004-09-25 17:57:22Z Valaris $ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include <time.h> + +#include "socket.h" // [Valaris] +#include "timer.h" +#include "db.h" + +#include "malloc.h" +#include "map.h" +#include "chrif.h" +#include "clif.h" +#include "intif.h" +#include "pc.h" +#include "npc.h" +#include "mob.h" +#include "pet.h" +#include "itemdb.h" +#include "script.h" +#include "battle.h" +#include "skill.h" +#include "party.h" +#include "guild.h" +#include "chat.h" +#include "trade.h" +#include "storage.h" +#include "vending.h" +#include "nullpo.h" +#include "atcommand.h" + +#ifndef TXT_ONLY // mail system [Valaris] +#include "mail.h" +#endif + +#ifdef MEMWATCH +#include "memwatch.h" +#endif + +#define PVP_CALCRANK_INTERVAL 1000 // PVP順位計算の間隔 + +#define STATE_BLIND 0x10 + +static int max_weight_base[MAX_PC_CLASS]; +static int hp_coefficient[MAX_PC_CLASS]; +static int hp_coefficient2[MAX_PC_CLASS]; +static int hp_sigma_val[MAX_PC_CLASS][MAX_LEVEL]; +static int sp_coefficient[MAX_PC_CLASS]; +static int aspd_base[MAX_PC_CLASS][20]; +static char job_bonus[3][MAX_PC_CLASS][MAX_LEVEL]; +static int exp_table[14][MAX_LEVEL]; +static char statp[255][7]; +static struct { + int id; + int max; + struct { + short id,lv; + } need[6]; +} skill_tree[3][MAX_PC_CLASS][100]; + +static int atkmods[3][20]; // 武器ATKサイズ修正(size_fix.txt) +static int refinebonus[5][3]; // 精錬ボーナステーブル(refine_db.txt) +static int percentrefinery[5][10]; // 精錬成功率(refine_db.txt) + +static int dirx[8]={0,-1,-1,-1,0,1,1,1}; +static int diry[8]={1,1,0,-1,-1,-1,0,1}; + +static unsigned int equip_pos[11]={0x0080,0x0008,0x0040,0x0004,0x0001,0x0200,0x0100,0x0010,0x0020,0x0002,0x8000}; + +//static struct dbt *gm_account_db; +static struct gm_account *gm_account = NULL; +static int GM_num = 0; + +int pc_isGM(struct map_session_data *sd) { +// struct gm_account *p; + int i; + + nullpo_retr(0, sd); + +/* p = numdb_search(gm_account_db, sd->status.account_id); + if (p == NULL) + return 0; + return p->level;*/ + + for(i = 0; i < GM_num; i++) + if (gm_account[i].account_id == sd->status.account_id) + return gm_account[i].level; + return 0; + +} + +int pc_iskiller(struct map_session_data *src, struct map_session_data *target) { + nullpo_retr(0, src); + + if(src->bl.type!=BL_PC ) + return 0; + if (src->special_state.killer) + return 1; + + if(target->bl.type!=BL_PC ) + return 0; + if (target->special_state.killable) + return 1; + + return 0; +} + + +int pc_set_gm_level(int account_id, int level) { + int i; + for (i = 0; i < GM_num; i++) { + if (account_id == gm_account[i].account_id) { + gm_account[i].level = level; + return 0; + } + } + + GM_num++; + gm_account = realloc(gm_account, sizeof(struct gm_account) * GM_num); + gm_account[GM_num - 1].account_id = account_id; + gm_account[GM_num - 1].level = level; + return 0; +} + +int pc_getrefinebonus(int lv, int type) { + if (lv >= 0 && lv < 5 && type >= 0 && type < 3) + return refinebonus[lv][type]; + return 0; +} + +static int distance(int x0, int y0, int x1, int y1) { + int dx, dy; + + dx = abs(x0-x1); + dy = abs(y0-y1); + return dx>dy ? dx : dy; +} + +static int pc_invincible_timer(int tid,unsigned int tick,int id,int data) { + struct map_session_data *sd; + + if( (sd=(struct map_session_data *)map_id2sd(id)) == NULL || sd->bl.type!=BL_PC ) + return 1; + + if(sd->invincible_timer != tid){ + if(battle_config.error_log) + printf("invincible_timer %d != %d\n",sd->invincible_timer,tid); + return 0; + } + sd->invincible_timer=-1; + + return 0; +} + +int pc_setinvincibletimer(struct map_session_data *sd,int val) { + nullpo_retr(0, sd); + + if(sd->invincible_timer != -1) + delete_timer(sd->invincible_timer,pc_invincible_timer); + sd->invincible_timer = add_timer(gettick()+val,pc_invincible_timer,sd->bl.id,0); + return 0; +} + +int pc_delinvincibletimer(struct map_session_data *sd) { + nullpo_retr(0, sd); + + if(sd->invincible_timer != -1) { + delete_timer(sd->invincible_timer,pc_invincible_timer); + sd->invincible_timer = -1; + } + return 0; +} + +static int pc_spiritball_timer(int tid,unsigned int tick,int id,int data) { + struct map_session_data *sd; + int i; + + if( (sd=(struct map_session_data *)map_id2sd(id)) == NULL || sd->bl.type!=BL_PC ) + return 1; + + if(sd->spirit_timer[0] != tid){ + if(battle_config.error_log) + printf("spirit_timer %d != %d\n",sd->spirit_timer[0],tid); + return 0; + } + sd->spirit_timer[0]=-1; + for(i=1;i<sd->spiritball;i++) { + sd->spirit_timer[i-1] = sd->spirit_timer[i]; + sd->spirit_timer[i] = -1; + } + sd->spiritball--; + if(sd->spiritball < 0) + sd->spiritball = 0; + clif_spiritball(sd); + + return 0; +} + +int pc_addspiritball(struct map_session_data *sd,int interval,int max) { + int i; + + nullpo_retr(0, sd); + + if(max > MAX_SKILL_LEVEL) + max = MAX_SKILL_LEVEL; + if(sd->spiritball < 0) + sd->spiritball = 0; + + if(sd->spiritball >= max) { + if(sd->spirit_timer[0] != -1) { + delete_timer(sd->spirit_timer[0],pc_spiritball_timer); + sd->spirit_timer[0] = -1; + } + for(i=1;i<max;i++) { + sd->spirit_timer[i-1] = sd->spirit_timer[i]; + sd->spirit_timer[i] = -1; + } + } + else + sd->spiritball++; + + sd->spirit_timer[sd->spiritball-1] = add_timer(gettick()+interval,pc_spiritball_timer,sd->bl.id,0); + clif_spiritball(sd); + + return 0; +} + +int pc_delspiritball(struct map_session_data *sd,int count,int type) { + int i; + + nullpo_retr(0, sd); + + if(sd->spiritball <= 0) { + sd->spiritball = 0; + return 0; + } + + if(count > sd->spiritball) + count = sd->spiritball; + sd->spiritball -= count; + if(count > MAX_SKILL_LEVEL) + count = MAX_SKILL_LEVEL; + + for(i=0;i<count;i++) { + if(sd->spirit_timer[i] != -1) { + delete_timer(sd->spirit_timer[i],pc_spiritball_timer); + sd->spirit_timer[i] = -1; + } + } + for(i=count;i<MAX_SKILL_LEVEL;i++) { + sd->spirit_timer[i-count] = sd->spirit_timer[i]; + sd->spirit_timer[i] = -1; + } + + if(!type) + clif_spiritball(sd); + + return 0; +} + +int pc_setrestartvalue(struct map_session_data *sd,int type) { + //転生や養子の場合の元の職業を算出する + struct pc_base_job s_class; + + nullpo_retr(0, sd); + + s_class = pc_calc_base_job(sd->status.class); + + //----------------------- + // 死亡した + if(sd->special_state.restart_full_recover) { // オシリスカード + sd->status.hp=sd->status.max_hp; + sd->status.sp=sd->status.max_sp; + } + else { + if(s_class.job == 0 && battle_config.restart_hp_rate < 50) { //ノビは半分回復 + sd->status.hp=(sd->status.max_hp)/2; + } + else { + if(battle_config.restart_hp_rate <= 0) + sd->status.hp = 1; + else { + sd->status.hp = sd->status.max_hp * battle_config.restart_hp_rate /100; + if(sd->status.hp <= 0) + sd->status.hp = 1; + } + } + if(battle_config.restart_sp_rate > 0) { + int sp = sd->status.max_sp * battle_config.restart_sp_rate /100; + if(sd->status.sp < sp) + sd->status.sp = sp; + } + } + if(type&1) + clif_updatestatus(sd,SP_HP); + if(type&1) + clif_updatestatus(sd,SP_SP); + + /* removed exp penalty on spawn [Valaris] */ + + if(type&2 && sd->status.class != 0 && battle_config.zeny_penalty > 0 && !map[sd->bl.m].flag.nozenypenalty) { + int zeny = (int)((double)sd->status.zeny * (double)battle_config.zeny_penalty / 10000.); + if(zeny < 1) zeny = 1; + sd->status.zeny -= zeny; + if(sd->status.zeny < 0) sd->status.zeny = 0; + clif_updatestatus(sd,SP_ZENY); + } + + return 0; +} + +/*========================================== + * 自分をロックしているMOBの数を数える(foreachclient) + *------------------------------------------ + */ +static int pc_counttargeted_sub(struct block_list *bl,va_list ap) +{ + int id,*c,target_lv; + struct block_list *src; + + nullpo_retr(0, bl); + nullpo_retr(0, ap); + + id=va_arg(ap,int); + + nullpo_retr(0, c=va_arg(ap,int *)); + + src=va_arg(ap,struct block_list *); + target_lv=va_arg(ap,int); + if(id == bl->id || (src && id == src->id)) return 0; + if(bl->type == BL_PC) { + struct map_session_data *sd=(struct map_session_data *)bl; + if( sd && sd->attacktarget == id && sd->attacktimer != -1 && sd->attacktarget_lv >= target_lv) + (*c)++; + } + else if(bl->type == BL_MOB) { + struct mob_data *md = (struct mob_data *)bl; + if(md && md->target_id == id && md->timer != -1 && md->state.state == MS_ATTACK && md->target_lv >= target_lv) + + (*c)++; + //printf("md->target_lv:%d, target_lv:%d\n",((struct mob_data *)bl)->target_lv,target_lv); + } + return 0; +} + +int pc_counttargeted(struct map_session_data *sd,struct block_list *src,int target_lv) +{ + int c=0; + map_foreachinarea(pc_counttargeted_sub, sd->bl.m, + sd->bl.x-AREA_SIZE,sd->bl.y-AREA_SIZE, + sd->bl.x+AREA_SIZE,sd->bl.y+AREA_SIZE,0,sd->bl.id,&c,src,target_lv); + return c; +} + +/*========================================== + * ローカルプロトタイプ宣言 (必要な物のみ) + *------------------------------------------ + */ +static int pc_walktoxy_sub(struct map_session_data *); + +/*========================================== + * saveに必要なステータス修正を行なう + *------------------------------------------ + */ +int pc_makesavestatus(struct map_session_data *sd) +{ + nullpo_retr(0, sd); + + // 服の色は色々弊害が多いので保存対象にはしない + if(!battle_config.save_clothcolor) + sd->status.clothes_color=0; + + // 死亡状態だったのでhpを1、位置をセーブ場所に変更 + if(pc_isdead(sd)){ + pc_setrestartvalue(sd,0); + memcpy(&sd->status.last_point,&sd->status.save_point,sizeof(sd->status.last_point)); + } else { + memcpy(sd->status.last_point.map,sd->mapname,24); + sd->status.last_point.x = sd->bl.x; + sd->status.last_point.y = sd->bl.y; + } + + // セーブ禁止マップだったので指定位置に移動 + if(map[sd->bl.m].flag.nosave){ + struct map_data *m=&map[sd->bl.m]; + if(strcmp(m->save.map,"SavePoint")==0) + memcpy(&sd->status.last_point,&sd->status.save_point,sizeof(sd->status.last_point)); + else + memcpy(&sd->status.last_point,&m->save,sizeof(sd->status.last_point)); + } + + //マナーポイントがプラスだった場合0に + if(battle_config.muting_players && sd->status.manner > 0) + sd->status.manner = 0; + return 0; +} + +/*========================================== + * 接続時の初期化 + *------------------------------------------ + */ +int pc_setnewpc(struct map_session_data *sd, int account_id, int char_id, int login_id1, int client_tick, int sex, int fd) { + nullpo_retr(0, sd); + + sd->bl.id = account_id; + sd->char_id = char_id; + sd->login_id1 = login_id1; + sd->login_id2 = 0; // at this point, we can not know the value :( + sd->client_tick = client_tick; + sd->sex = sex; + sd->state.auth = 0; + sd->bl.type = BL_PC; + sd->canact_tick = sd->canmove_tick = gettick(); + sd->canlog_tick = gettick(); + sd->state.waitingdisconnect = 0; + + return 0; +} + +int pc_equippoint(struct map_session_data *sd,int n) +{ + int ep = 0; + //転生や養子の場合の元の職業を算出する + struct pc_base_job s_class; + + nullpo_retr(0, sd); + + s_class = pc_calc_base_job(sd->status.class); + + if(sd->inventory_data[n]) { + ep = sd->inventory_data[n]->equip; + if(sd->inventory_data[n]->look == 1 || sd->inventory_data[n]->look == 2 || sd->inventory_data[n]->look == 6) { + if(ep == 2 && (pc_checkskill(sd,AS_LEFT) > 0 || s_class.job == 12)) + return 34; + } + } + return ep; +} + +int pc_setinventorydata(struct map_session_data *sd) +{ + int i,id; + + nullpo_retr(0, sd); + + for(i=0;i<MAX_INVENTORY;i++) { + id = sd->status.inventory[i].nameid; + sd->inventory_data[i] = itemdb_search(id); + } + return 0; +} + +int pc_calcweapontype(struct map_session_data *sd) +{ + nullpo_retr(0, sd); + + if(sd->weapontype1 != 0 && sd->weapontype2 == 0) + sd->status.weapon = sd->weapontype1; + if(sd->weapontype1 == 0 && sd->weapontype2 != 0)// 左手武器 Only + sd->status.weapon = sd->weapontype2; + else if(sd->weapontype1 == 1 && sd->weapontype2 == 1)// 双短剣 + sd->status.weapon = 0x11; + else if(sd->weapontype1 == 2 && sd->weapontype2 == 2)// 双単手剣 + sd->status.weapon = 0x12; + else if(sd->weapontype1 == 6 && sd->weapontype2 == 6)// 双単手斧 + sd->status.weapon = 0x13; + else if( (sd->weapontype1 == 1 && sd->weapontype2 == 2) || + (sd->weapontype1 == 2 && sd->weapontype2 == 1) ) // 短剣 - 単手剣 + sd->status.weapon = 0x14; + else if( (sd->weapontype1 == 1 && sd->weapontype2 == 6) || + (sd->weapontype1 == 6 && sd->weapontype2 == 1) ) // 短剣 - 斧 + sd->status.weapon = 0x15; + else if( (sd->weapontype1 == 2 && sd->weapontype2 == 6) || + (sd->weapontype1 == 6 && sd->weapontype2 == 2) ) // 単手剣 - 斧 + sd->status.weapon = 0x16; + else + sd->status.weapon = sd->weapontype1; + + return 0; +} + +int pc_setequipindex(struct map_session_data *sd) +{ + int i,j; + + nullpo_retr(0, sd); + + for(i=0;i<11;i++) + sd->equip_index[i] = -1; + + for(i=0;i<MAX_INVENTORY;i++) { + if(sd->status.inventory[i].nameid <= 0) + continue; + if(sd->status.inventory[i].equip) { + for(j=0;j<11;j++) + if(sd->status.inventory[i].equip & equip_pos[j]) + sd->equip_index[j] = i; + if(sd->status.inventory[i].equip & 0x0002) { + if(sd->inventory_data[i]) + sd->weapontype1 = sd->inventory_data[i]->look; + else + sd->weapontype1 = 0; + } + if(sd->status.inventory[i].equip & 0x0020) { + if(sd->inventory_data[i]) { + if(sd->inventory_data[i]->type == 4) { + if(sd->status.inventory[i].equip == 0x0020) + sd->weapontype2 = sd->inventory_data[i]->look; + else + sd->weapontype2 = 0; + } + else + sd->weapontype2 = 0; + } + else + sd->weapontype2 = 0; + } + } + } + pc_calcweapontype(sd); + + return 0; +} + +int pc_isequip(struct map_session_data *sd,int n) +{ + struct item_data *item; + struct status_change *sc_data; + //転生や養子の場合の元の職業を算出する + + nullpo_retr(0, sd); + + item = sd->inventory_data[n]; + sc_data = battle_get_sc_data(&sd->bl); + //s_class = pc_calc_base_job(sd->status.class); + + if( battle_config.gm_allequip>0 && pc_isGM(sd)>=battle_config.gm_allequip ) + return 1; + + if(item == NULL) + return 0; + if(item->sex != 2 && sd->status.sex != item->sex) + return 0; + if(item->elv > 0 && sd->status.base_level < item->elv) + return 0; +// -- moonsoul (below statement substituted for commented out version further below +// as it allows all advanced classes to equip items their normal versions +// could equip) +// + if(((sd->status.class==13 || sd->status.class==4014) && ((1<<7)&item->class) == 0) || // have mounted classes use unmounted equipment [Valaris] + ((sd->status.class==21 || sd->status.class==4022) && ((1<<14)&item->class) == 0)) + return 0; + if(sd->status.class!=13 && sd->status.class!=4014 && sd->status.class!=21 && sd->status.class!=4022) + if((sd->status.class<=4000 && ((1<<sd->status.class)&item->class) == 0) || (sd->status.class>4000 && sd->status.class<4023 && ((1<<(sd->status.class-4001))&item->class) == 0) || + (sd->status.class>=4023 && ((1<<(sd->status.class-4023))&item->class) == 0)) + return 0; +// if(((1<<sd->status.class)&item->class) == 0) +// return 0; + if(map[sd->bl.m].flag.pvp && (item->flag.no_equip==1 || item->flag.no_equip==3)) + return 0; + if(map[sd->bl.m].flag.gvg && (item->flag.no_equip==2 || item->flag.no_equip==3)) + return 0; + if(item->equip & 0x0002 && sc_data && sc_data[SC_STRIPWEAPON].timer != -1) + return 0; + if(item->equip & 0x0020 && sc_data && sc_data[SC_STRIPSHIELD].timer != -1) + return 0; + if(item->equip & 0x0010 && sc_data && sc_data[SC_STRIPARMOR].timer != -1) + return 0; + if(item->equip & 0x0100 && sc_data && sc_data[SC_STRIPHELM].timer != -1) + return 0; + return 1; +} + +/*========================================== + * Weapon Breaking [Valaris] + *------------------------------------------ + */ +int pc_breakweapon(struct map_session_data *sd) +{ + struct item_data* item; + char output[255]; + int i; + + if(sd==NULL) + return -1; + if(sd->unbreakable>=rand()%100) + return 0; + if(sd->sc_data && sd->sc_data[SC_CP_WEAPON].timer != -1) + return 0; + + for(i=0;i<MAX_INVENTORY;i++){ + if(sd->status.inventory[i].equip && sd->status.inventory[i].equip & 0x0002 && !sd->status.inventory[i].broken){ + item=sd->inventory_data[i]; + sd->status.inventory[i].broken=1; + //pc_unequipitem(sd,i,0); + if(sd->status.inventory[i].equip && sd->status.inventory[i].equip & 0x0002 && + sd->status.inventory[i].broken==1){ + sprintf(output, "%s has broken.",item->jname); + clif_emotion(&sd->bl,23); + clif_displaymessage(sd->fd, output); + clif_equiplist(sd); + skill_status_change_start(&sd->bl,SC_BROKNWEAPON,0,0,0,0,0,0); + } + } + if(sd->status.inventory[i].broken==1) + return 0; + } + + return 0; +} +/*========================================== + * Armor Breaking [Valaris] + *------------------------------------------ + */ +int pc_breakarmor(struct map_session_data *sd) +{ + struct item_data* item; + char output[255]; + int i; + + if(sd==NULL) + return -1; + if(sd->unbreakable>=rand()%100) + return 0; + if(sd->sc_data && sd->sc_data[SC_CP_ARMOR].timer != -1) + return 0; + + for(i=0;i<MAX_INVENTORY;i++){ + if(sd->status.inventory[i].equip && sd->status.inventory[i].equip & 0x0010 && !sd->status.inventory[i].broken){ + item=sd->inventory_data[i]; + sd->status.inventory[i].broken=1; + //pc_unequipitem(sd,i,0); + if(sd->status.inventory[i].equip && sd->status.inventory[i].equip & 0x0010 && + sd->status.inventory[i].broken==1){ + sprintf(output, "%s has broken.",item->jname); + clif_emotion(&sd->bl,23); + clif_displaymessage(sd->fd, output); + clif_equiplist(sd); + skill_status_change_start(&sd->bl,SC_BROKNARMOR,0,0,0,0,0,0); + } + } + if(sd->status.inventory[i].broken==1) + return 0; + } + return 0; +} +/*========================================== + * session idに問題無し + * char鯖から送られてきたステータスを設定 + *------------------------------------------ + */ +int pc_authok(int id, int login_id2, time_t connect_until_time, struct mmo_charstatus *st) +{ + struct map_session_data *sd = NULL; + + struct party *p; + struct guild *g; + int i; + unsigned long tick = gettick(); + + sd = map_id2sd(id); + if(sd==NULL) + return 1; + + sd->login_id2 = login_id2; + + memcpy(&sd->status, st, sizeof(*st)); + + if (sd->status.sex != sd->sex) { + clif_authfail_fd(sd->fd, 0); + return 1; + } + + memset(&sd->state, 0, sizeof(sd->state)); + // 基本的な初期化 + sd->state.connect_new = 1; + sd->bl.prev = sd->bl.next = NULL; + + sd->weapontype1 = sd->weapontype2 = 0; + sd->view_class = sd->status.class; + sd->speed = DEFAULT_WALK_SPEED; + sd->state.dead_sit = 0; + sd->dir = 0; + sd->head_dir = 0; + sd->state.auth = 1; + sd->walktimer = -1; + sd->attacktimer = -1; + sd->followtimer = -1; // [MouseJstr] + sd->skilltimer = -1; + sd->skillitem = -1; + sd->skillitemlv = -1; + sd->invincible_timer = -1; + sd->sg_count = 0; + + sd->deal_locked = 0; + sd->trade_partner = 0; + + sd->inchealhptick = 0; + sd->inchealsptick = 0; + sd->hp_sub = 0; + sd->sp_sub = 0; + sd->inchealspirithptick = 0; + sd->inchealspiritsptick = 0; + sd->canact_tick = tick; + sd->canmove_tick = tick; + sd->attackabletime = tick; + + sd->doridori_counter = 0; + +#ifndef TXT_ONLY // mail system [Valaris] + if(battle_config.mail_system) + sd->mail_counter = 0; +#endif + sd->spiritball = 0; + for(i = 0; i < MAX_SKILL_LEVEL; i++) + sd->spirit_timer[i] = -1; + for(i = 0; i < MAX_SKILLTIMERSKILL; i++) + sd->skilltimerskill[i].timer = -1; + + memset(&sd->dev,0,sizeof(struct square)); + for(i = 0; i < 5; i++) { + sd->dev.val1[i] = 0; + sd->dev.val2[i] = 0; + } + + // アカウント変数の送信要求 + intif_request_accountreg(sd); + + // アイテムチェック + pc_setinventorydata(sd); + pc_checkitem(sd); + + // pet + sd->petDB = NULL; + sd->pd = NULL; + sd->pet_hungry_timer = -1; + memset(&sd->pet, 0, sizeof(struct s_pet)); + + // ステータス異常の初期化 + for(i = 0; i < MAX_STATUSCHANGE; i++) { + sd->sc_data[i].timer=-1; + sd->sc_data[i].val1 = sd->sc_data[i].val2 = sd->sc_data[i].val3 = sd->sc_data[i].val4 = 0; + } + sd->sc_count=0; + if ((battle_config.atc_gmonly == 0 || pc_isGM(sd)) && + (pc_isGM(sd) >= get_atcommand_level(AtCommand_Hide))) + sd->status.option &= (OPTION_MASK | OPTION_HIDE); + else + sd->status.option &= OPTION_MASK; + + // スキルユニット関係の初期化 + memset(sd->skillunit, 0, sizeof(sd->skillunit)); + memset(sd->skillunittick, 0, sizeof(sd->skillunittick)); + + // init ignore list + memset(sd->ignore, 0, sizeof(sd->ignore)); + + // パーティー関係の初期化 + sd->party_sended = 0; + sd->party_invite = 0; + sd->party_x = -1; + sd->party_y = -1; + sd->party_hp = -1; + + // ギルド関係の初期化 + sd->guild_sended = 0; + sd->guild_invite = 0; + sd->guild_alliance = 0; + + // イベント関係の初期化 + memset(sd->eventqueue, 0, sizeof(sd->eventqueue)); + for(i = 0; i < MAX_EVENTTIMER; i++) + sd->eventtimer[i] = -1; + + // 位置の設定 + pc_setpos(sd,sd->status.last_point.map, sd->status.last_point.x, sd->status.last_point.y, 0); + + // pet + if (sd->status.pet_id > 0) + intif_request_petdata(sd->status.account_id, sd->status.char_id, sd->status.pet_id); + + // パーティ、ギルドデータの要求 + if (sd->status.party_id > 0 && (p = party_search(sd->status.party_id)) == NULL) + party_request_info(sd->status.party_id); + if (sd->status.guild_id > 0 && (g = guild_search(sd->status.guild_id)) == NULL) + guild_request_info(sd->status.guild_id); + + // pvpの設定 + sd->pvp_rank = 0; + sd->pvp_point = 0; + sd->pvp_timer = -1; + + // 通知 + + clif_authok(sd); + map_addnickdb(sd); + if (map_charid2nick(sd->status.char_id) == NULL) + map_addchariddb(sd->status.char_id, sd->status.name); + + //スパノビ用死にカウンターのスクリプト変数からの読み出しとsdへのセット + sd->die_counter = pc_readglobalreg(sd,"PC_DIE_COUNTER"); + + if (night_flag == 1) { + char tmpstr[1024]; + strcpy(tmpstr, msg_txt(500)); // Actually, it's the night... + clif_wis_message(sd->fd, wisp_server_name, tmpstr, strlen(tmpstr)+1); + sd->opt2 |= STATE_BLIND; + } + + // ステータス初期計算など + pc_calcstatus(sd,1); + + if (pc_isGM(sd)) + printf("Connection accepted: character '%s' (account: %d; GM level %d).\n", sd->status.name, sd->status.account_id, pc_isGM(sd)); + else + printf("Connection accepted: Character '%s' (account: %d).\n", sd->status.name, sd->status.account_id); + + // Message of the Dayの送信 + { + char buf[256]; + FILE *fp; + if ((fp = fopen(motd_txt, "r")) != NULL) { + while (fgets(buf, sizeof(buf)-1, fp) != NULL) { + int i; + for(i=0; buf[i]; i++) { + if (buf[i] == '\r' || buf[i]== '\n') { + buf[i] = 0; + break; + } + } + clif_displaymessage(sd->fd, buf); + } + fclose(fp); + } + } + +#ifndef TXT_ONLY + if(battle_config.mail_system) + mail_check(sd,1); // check mail at login [Valaris] +#endif + + // message of the limited time of the account + if (connect_until_time != 0) { // don't display if it's unlimited or unknow value + char tmpstr[1024]; + strftime(tmpstr, sizeof(tmpstr) - 1, msg_txt(501), localtime(&connect_until_time)); // "Your account time limit is: %d-%m-%Y %H:%M:%S." + clif_wis_message(sd->fd, wisp_server_name, tmpstr, strlen(tmpstr)+1); + } + + return 0; +} + +/*========================================== + * session idに問題ありなので後始末 + *------------------------------------------ + */ +int pc_authfail(int id) { + struct map_session_data *sd; + + sd = map_id2sd(id); + if (sd == NULL) + return 1; + + clif_authfail_fd(sd->fd, 0); + + return 0; +} + +static int pc_calc_skillpoint(struct map_session_data* sd) +{ + int i,skill,skill_point=0; + + nullpo_retr(0, sd); + + for(i=1;i<MAX_SKILL;i++){ + if( (skill = pc_checkskill(sd,i)) > 0) { + if(!(skill_get_inf2(i)&0x01) || battle_config.quest_skill_learn) { + if(!sd->status.skill[i].flag) + skill_point += skill; + else if(sd->status.skill[i].flag > 2 && sd->status.skill[i].flag != 13) { + skill_point += (sd->status.skill[i].flag - 2); + } + } + } + } + + return skill_point; +} + +/*========================================== + * 覚えられるスキルの計算 + *------------------------------------------ + */ +int pc_calc_skilltree(struct map_session_data *sd) +{ + int i,id=0,flag; + int c=0, s=0; + //転生や養子の場合の元の職業を算出する + struct pc_base_job s_class; + + nullpo_retr(0, sd); + + s_class = pc_calc_base_job(sd->status.class); + c = s_class.job; + s = (s_class.upper==1) ? 1 : 0 ; //転生以外は通常のスキル? + + if((battle_config.skillup_limit) && ((c >= 0 && c < 23) || (c >= 4001 && c < 4023) || (c >= 4023 && c < 4045))) { + int skill_point = pc_calc_skillpoint(sd); + if(skill_point < 9) + c = 0; + else if((sd->status.skill_point >= sd->status.job_level && skill_point < 58) && ((c > 6 && c < 23) || (c > 4007 && c < 4023) || (c > 4029 && c < 4045))) { + switch(c) { + case 7: + case 14: + c = 1; + break; + case 8: + case 15: + c = 4; + break; + case 9: + case 16: + c = 2; + break; + case 10: + case 18: + c = 5; + break; + case 11: + case 19: + case 20: + c = 3; + break; + case 12: + case 17: + c = 6; + break; + case 4008: + case 4015: + c = 4002; + break; + case 4009: + case 4016: + c = 4005; + break; + case 4010: + case 4017: + c = 4003; + break; + case 4011: + case 4019: + c = 4006; + break; + case 4012: + case 4020: + case 4021: + c = 4004; + break; + case 4013: + case 4018: + c = 4007; + break; + case 4030: + case 4037: + c = 4024; + break; + case 4031: + case 4038: + c = 4027; + break; + case 4032: + case 4039: + c = 4025; + break; + case 4033: + case 4040: + c = 4028; + break; + case 4034: + case 4041: + case 4042: + c = 4026; + break; + case 4035: + case 4043: + c = 4029; + break; + + } + } + } + + for(i=0;i<MAX_SKILL;i++){ + if (sd->status.skill[i].flag != 13) sd->status.skill[i].id=0; + if (sd->status.skill[i].flag && sd->status.skill[i].flag != 13){ // cardスキルなら、 + sd->status.skill[i].lv=(sd->status.skill[i].flag==1)?0:sd->status.skill[i].flag-2; // 本当のlvに + sd->status.skill[i].flag=0; // flagは0にしておく + } + } + + if (battle_config.gm_allskill > 0 && pc_isGM(sd) >= battle_config.gm_allskill){ + // 全てのスキル + for(i=1;i<158;i++) + sd->status.skill[i].id=i; + for(i=210;i<291;i++) + sd->status.skill[i].id=i; + for(i=304;i<337;i++) + sd->status.skill[i].id=i; + if(battle_config.enable_upper_class){ //confで無効でなければ読み込む + for(i=355;i<MAX_SKILL;i++) + sd->status.skill[i].id=i; + } + }else{ + // 通常の計算 + do{ + flag=0; + for(i=0;(id=skill_tree[s][c][i].id)>0;i++){ + int j,f=1; + if(!battle_config.skillfree) { + for(j=0;j<5;j++) { + if( skill_tree[s][c][i].need[j].id && + pc_checkskill(sd,skill_tree[s][c][i].need[j].id) < skill_tree[s][c][i].need[j].lv) + f=0; + } + } + if(f && sd->status.skill[id].id==0 ){ + sd->status.skill[id].id=id; + flag=1; + } + } + }while(flag); + } +// if(battle_config.etc_log) +// printf("calc skill_tree\n"); + return 0; +} + +/*========================================== + * 重量アイコンの確認 + *------------------------------------------ + */ +int pc_checkweighticon(struct map_session_data *sd) +{ + int flag=0; + + nullpo_retr(0, sd); + + if(sd->weight*2 >= sd->max_weight) + flag=1; + if(sd->weight*10 >= sd->max_weight*9) + flag=2; + + if(flag==1){ + if(sd->sc_data[SC_WEIGHT50].timer==-1) + skill_status_change_start(&sd->bl,SC_WEIGHT50,0,0,0,0,0,0); + }else{ + skill_status_change_end(&sd->bl,SC_WEIGHT50,-1); + } + if(flag==2){ + if(sd->sc_data[SC_WEIGHT90].timer==-1) + skill_status_change_start(&sd->bl,SC_WEIGHT90,0,0,0,0,0,0); + }else{ + skill_status_change_end(&sd->bl,SC_WEIGHT90,-1); + } + return 0; +} + +/*========================================== + * パラメータ計算 + * first==0の時、計算対象のパラメータが呼び出し前から + * 変 化した場合自動でsendするが、 + * 能動的に変化させたパラメータは自前でsendするように + *------------------------------------------ + */ +int pc_calcstatus(struct map_session_data* sd,int first) +{ + int b_speed,b_max_hp,b_max_sp,b_hp,b_sp,b_weight,b_max_weight,b_paramb[6],b_parame[6],b_hit,b_flee; + int b_aspd,b_watk,b_def,b_watk2,b_def2,b_flee2,b_critical,b_attackrange,b_matk1,b_matk2,b_mdef,b_mdef2,b_class; + int b_base_atk; + struct skill b_skill[MAX_SKILL]; + int i,bl,index; + int skill,aspd_rate,wele,wele_,def_ele,refinedef=0; + int pele=0,pdef_ele=0; + int str,dstr,dex; + struct pc_base_job s_class; + + nullpo_retr(0, sd); + + //転生や養子の場合の元の職業を算出する + s_class = pc_calc_base_job(sd->status.class); + + b_speed = sd->speed; + b_max_hp = sd->status.max_hp; + b_max_sp = sd->status.max_sp; + b_hp = sd->status.hp; + b_sp = sd->status.sp; + b_weight = sd->weight; + b_max_weight = sd->max_weight; + memcpy(b_paramb,&sd->paramb,sizeof(b_paramb)); + memcpy(b_parame,&sd->paramc,sizeof(b_parame)); + memcpy(b_skill,&sd->status.skill,sizeof(b_skill)); + b_hit = sd->hit; + b_flee = sd->flee; + b_aspd = sd->aspd; + b_watk = sd->watk; + b_def = sd->def; + b_watk2 = sd->watk2; + b_def2 = sd->def2; + b_flee2 = sd->flee2; + b_critical = sd->critical; + b_attackrange = sd->attackrange; + b_matk1 = sd->matk1; + b_matk2 = sd->matk2; + b_mdef = sd->mdef; + b_mdef2 = sd->mdef2; + b_class = sd->view_class; + sd->view_class = sd->status.class; + b_base_atk = sd->base_atk; + + pc_calc_skilltree(sd); // スキルツリーの計算 + + sd->max_weight = max_weight_base[s_class.job]+sd->status.str*300; + + if(first&1) { + sd->weight=0; + for(i=0;i<MAX_INVENTORY;i++){ + if(sd->status.inventory[i].nameid==0 || sd->inventory_data[i] == NULL) + continue; + sd->weight += sd->inventory_data[i]->weight*sd->status.inventory[i].amount; + } + sd->cart_max_weight=battle_config.max_cart_weight; + sd->cart_weight=0; + sd->cart_max_num=MAX_CART; + sd->cart_num=0; + for(i=0;i<MAX_CART;i++){ + if(sd->status.cart[i].nameid==0) + continue; + sd->cart_weight+=itemdb_weight(sd->status.cart[i].nameid)*sd->status.cart[i].amount; + sd->cart_num++; + } + } + + memset(sd->paramb,0,sizeof(sd->paramb)); + memset(sd->parame,0,sizeof(sd->parame)); + sd->hit = 0; + sd->flee = 0; + sd->flee2 = 0; + sd->critical = 0; + sd->aspd = 0; + sd->watk = 0; + sd->def = 0; + sd->mdef = 0; + sd->watk2 = 0; + sd->def2 = 0; + sd->mdef2 = 0; + sd->status.max_hp = 0; + sd->status.max_sp = 0; + sd->attackrange = 0; + sd->attackrange_ = 0; + sd->atk_ele = 0; + sd->def_ele = 0; + sd->star =0; + sd->overrefine =0; + sd->matk1 =0; + sd->matk2 =0; + sd->speed = DEFAULT_WALK_SPEED ; + sd->hprate=100; + sd->sprate=100; + sd->castrate=100; + sd->dsprate=100; + sd->base_atk=0; + sd->arrow_atk=0; + sd->arrow_ele=0; + sd->arrow_hit=0; + sd->arrow_range=0; + sd->nhealhp=sd->nhealsp=sd->nshealhp=sd->nshealsp=sd->nsshealhp=sd->nsshealsp=0; + memset(sd->addele,0,sizeof(sd->addele)); + memset(sd->addrace,0,sizeof(sd->addrace)); + memset(sd->addsize,0,sizeof(sd->addsize)); + memset(sd->addele_,0,sizeof(sd->addele_)); + memset(sd->addrace_,0,sizeof(sd->addrace_)); + memset(sd->addsize_,0,sizeof(sd->addsize_)); + memset(sd->subele,0,sizeof(sd->subele)); + memset(sd->subrace,0,sizeof(sd->subrace)); + memset(sd->addeff,0,sizeof(sd->addeff)); + memset(sd->addeff2,0,sizeof(sd->addeff2)); + memset(sd->reseff,0,sizeof(sd->reseff)); + memset(&sd->special_state,0,sizeof(sd->special_state)); + memset(sd->weapon_coma_ele,0,sizeof(sd->weapon_coma_ele)); + memset(sd->weapon_coma_race,0,sizeof(sd->weapon_coma_race)); + + sd->watk_ = 0; //二刀流用(仮) + sd->watk_2 = 0; + sd->atk_ele_ = 0; + sd->star_ = 0; + sd->overrefine_ = 0; + + sd->aspd_rate = 100; + sd->speed_rate = 100; + sd->hprecov_rate = 100; + sd->sprecov_rate = 100; + sd->critical_def = 0; + sd->double_rate = 0; + sd->near_attack_def_rate = sd->long_attack_def_rate = 0; + sd->atk_rate = sd->matk_rate = 100; + sd->ignore_def_ele = sd->ignore_def_race = 0; + sd->ignore_def_ele_ = sd->ignore_def_race_ = 0; + sd->ignore_mdef_ele = sd->ignore_mdef_race = 0; + sd->arrow_cri = 0; + sd->magic_def_rate = sd->misc_def_rate = 0; + memset(sd->arrow_addele,0,sizeof(sd->arrow_addele)); + memset(sd->arrow_addrace,0,sizeof(sd->arrow_addrace)); + memset(sd->arrow_addsize,0,sizeof(sd->arrow_addsize)); + memset(sd->arrow_addeff,0,sizeof(sd->arrow_addeff)); + memset(sd->arrow_addeff2,0,sizeof(sd->arrow_addeff2)); + memset(sd->magic_addele,0,sizeof(sd->magic_addele)); + memset(sd->magic_addrace,0,sizeof(sd->magic_addrace)); + memset(sd->magic_subrace,0,sizeof(sd->magic_subrace)); + sd->perfect_hit = 0; + sd->critical_rate = sd->hit_rate = sd->flee_rate = sd->flee2_rate = 100; + sd->def_rate = sd->def2_rate = sd->mdef_rate = sd->mdef2_rate = 100; + sd->def_ratio_atk_ele = sd->def_ratio_atk_ele_ = 0; + sd->def_ratio_atk_race = sd->def_ratio_atk_race_ = 0; + sd->get_zeny_num = 0; + sd->add_damage_class_count = sd->add_damage_class_count_ = sd->add_magic_damage_class_count = 0; + sd->add_def_class_count = sd->add_mdef_class_count = 0; + sd->monster_drop_item_count = 0; + memset(sd->add_damage_classrate,0,sizeof(sd->add_damage_classrate)); + memset(sd->add_damage_classrate_,0,sizeof(sd->add_damage_classrate_)); + memset(sd->add_magic_damage_classrate,0,sizeof(sd->add_magic_damage_classrate)); + memset(sd->add_def_classrate,0,sizeof(sd->add_def_classrate)); + memset(sd->add_mdef_classrate,0,sizeof(sd->add_mdef_classrate)); + memset(sd->monster_drop_race,0,sizeof(sd->monster_drop_race)); + memset(sd->monster_drop_itemrate,0,sizeof(sd->monster_drop_itemrate)); + sd->speed_add_rate = sd->aspd_add_rate = 100; + sd->double_add_rate = sd->perfect_hit_add = sd->get_zeny_add_num = 0; + sd->splash_range = sd->splash_add_range = 0; + sd->autospell_id = sd->autospell_lv = sd->autospell_rate = 0; + sd->hp_drain_rate = sd->hp_drain_per = sd->sp_drain_rate = sd->sp_drain_per = 0; + sd->hp_drain_rate_ = sd->hp_drain_per_ = sd->sp_drain_rate_ = sd->sp_drain_per_ = 0; + sd->short_weapon_damage_return = sd->long_weapon_damage_return = 0; + sd->magic_damage_return = 0; //AppleGirl Was Here + sd->random_attack_increase_add = sd->random_attack_increase_per = 0; + + if(!sd->disguiseflag && sd->disguise) { + sd->disguise=0; + clif_changelook(&sd->bl,LOOK_WEAPON,sd->status.weapon); + clif_changelook(&sd->bl,LOOK_SHIELD,sd->status.shield); + clif_changelook(&sd->bl,LOOK_HEAD_BOTTOM,sd->status.head_bottom); + clif_changelook(&sd->bl,LOOK_HEAD_TOP,sd->status.head_top); + clif_changelook(&sd->bl,LOOK_HEAD_MID,sd->status.head_mid); + clif_clearchar(&sd->bl, 9); + pc_setpos(sd, sd->mapname, sd->bl.x, sd->bl.y, 3); + } + + for(i=0;i<10;i++) { + index = sd->equip_index[i]; + if(index < 0) + continue; + if(i == 9 && sd->equip_index[8] == index) + continue; + if(i == 5 && sd->equip_index[4] == index) + continue; + if(i == 6 && (sd->equip_index[5] == index || sd->equip_index[4] == index)) + continue; + + if(sd->inventory_data[index]) { + if(sd->inventory_data[index]->type == 4) { + if(sd->status.inventory[index].card[0]!=0x00ff && sd->status.inventory[index].card[0]!=0x00fe && sd->status.inventory[index].card[0]!=(short)0xff00) { + int j; + for(j=0;j<sd->inventory_data[index]->slot;j++){ // カード + int c=sd->status.inventory[index].card[j]; + if(c>0){ + if(i == 8 && sd->status.inventory[index].equip == 0x20) + sd->state.lr_flag = 1; + run_script(itemdb_equipscript(c),0,sd->bl.id,0); + sd->state.lr_flag = 0; + } + } + } + } + else if(sd->inventory_data[index]->type==5){ // 防具 + if(sd->status.inventory[index].card[0]!=0x00ff && sd->status.inventory[index].card[0]!=0x00fe && sd->status.inventory[index].card[0]!=(short)0xff00) { + int j; + for(j=0;j<sd->inventory_data[index]->slot;j++){ // カード + int c=sd->status.inventory[index].card[j]; + if(c>0) + run_script(itemdb_equipscript(c),0,sd->bl.id,0); + } + } + } + } + } + wele = sd->atk_ele; + wele_ = sd->atk_ele_; + def_ele = sd->def_ele; + if(sd->status.pet_id > 0) { + struct pet_data *pd=sd->pd; + if((pd && battle_config.pet_status_support==1) && (battle_config.pet_equip_required==0 || (battle_config.pet_equip_required && pd->equip > 0))) { + if(sd->status.pet_id > 0 && sd->petDB && sd->pet.intimate > 0) + run_script(sd->petDB->script,0,sd->bl.id,0); + pele = sd->atk_ele; + pdef_ele = sd->def_ele; + sd->atk_ele = sd->def_ele = 0; + } + } + memcpy(sd->paramcard,sd->parame,sizeof(sd->paramcard)); + + // 装備品によるステータス変化はここで実行 + for(i=0;i<10;i++) { + index = sd->equip_index[i]; + if(index < 0) + continue; + if(i == 9 && sd->equip_index[8] == index) + continue; + if(i == 5 && sd->equip_index[4] == index) + continue; + if(i == 6 && (sd->equip_index[5] == index || sd->equip_index[4] == index)) + continue; + if(sd->inventory_data[index]) { + sd->def += sd->inventory_data[index]->def; + if(sd->inventory_data[index]->type == 4) { + int r,wlv = sd->inventory_data[index]->wlv; + if(i == 8 && sd->status.inventory[index].equip == 0x20) { + //二刀流用データ入力 + sd->watk_ += sd->inventory_data[index]->atk; + sd->watk_2 = (r=sd->status.inventory[index].refine)* // 精錬攻撃力 + refinebonus[wlv][0]; + if( (r-=refinebonus[wlv][2])>0 ) // 過剰精錬ボーナス + sd->overrefine_ = r*refinebonus[wlv][1]; + + if(sd->status.inventory[index].card[0]==0x00ff){ // 製造武器 + sd->star_ = (sd->status.inventory[index].card[1]>>8); // 星のかけら + wele_= (sd->status.inventory[index].card[1]&0x0f); // 属 性 + } + sd->attackrange_ += sd->inventory_data[index]->range; + sd->state.lr_flag = 1; + run_script(sd->inventory_data[index]->equip_script,0,sd->bl.id,0); + sd->state.lr_flag = 0; + } + else { //二刀流武器以外 + sd->watk += sd->inventory_data[index]->atk; + sd->watk2 += (r=sd->status.inventory[index].refine)* // 精錬攻撃力 + refinebonus[wlv][0]; + if( (r-=refinebonus[wlv][2])>0 ) // 過剰精錬ボーナス + sd->overrefine += r*refinebonus[wlv][1]; + + if(sd->status.inventory[index].card[0]==0x00ff){ // 製造武器 + sd->star += (sd->status.inventory[index].card[1]>>8); // 星のかけら + wele = (sd->status.inventory[index].card[1]&0x0f); // 属 性 + } + sd->attackrange += sd->inventory_data[index]->range; + run_script(sd->inventory_data[index]->equip_script,0,sd->bl.id,0); + } + } + else if(sd->inventory_data[index]->type == 5) { + sd->watk += sd->inventory_data[index]->atk; + refinedef += sd->status.inventory[index].refine*refinebonus[0][0]; + run_script(sd->inventory_data[index]->equip_script,0,sd->bl.id,0); + } + } + } + + if(sd->equip_index[10] >= 0){ // 矢 + index = sd->equip_index[10]; + if(sd->inventory_data[index]){ //まだ属性が入っていない + sd->state.lr_flag = 2; + run_script(sd->inventory_data[index]->equip_script,0,sd->bl.id,0); + sd->state.lr_flag = 0; + sd->arrow_atk += sd->inventory_data[index]->atk; + } + } + sd->def += (refinedef+50)/100; + + if(sd->attackrange < 1) sd->attackrange = 1; + if(sd->attackrange_ < 1) sd->attackrange_ = 1; + if(sd->attackrange < sd->attackrange_) + sd->attackrange = sd->attackrange_; + if(sd->status.weapon == 11) + sd->attackrange += sd->arrow_range; + if(wele > 0) + sd->atk_ele = wele; + if(wele_ > 0) + sd->atk_ele_ = wele_; + if(def_ele > 0) + sd->def_ele = def_ele; + if(battle_config.pet_status_support) { + if(pele > 0 && !sd->atk_ele) + sd->atk_ele = pele; + if(pdef_ele > 0 && !sd->def_ele) + sd->def_ele = pdef_ele; + } + sd->double_rate += sd->double_add_rate; + sd->perfect_hit += sd->perfect_hit_add; + sd->get_zeny_num += sd->get_zeny_add_num; + sd->splash_range += sd->splash_add_range; + if(sd->speed_add_rate != 100) + sd->speed_rate += sd->speed_add_rate - 100; + if(sd->aspd_add_rate != 100) + sd->aspd_rate += sd->aspd_add_rate - 100; + + // 武器ATKサイズ補正 (右手) + sd->atkmods[0] = atkmods[0][sd->weapontype1]; + sd->atkmods[1] = atkmods[1][sd->weapontype1]; + sd->atkmods[2] = atkmods[2][sd->weapontype1]; + //武器ATKサイズ補正 (左手) + sd->atkmods_[0] = atkmods[0][sd->weapontype2]; + sd->atkmods_[1] = atkmods[1][sd->weapontype2]; + sd->atkmods_[2] = atkmods[2][sd->weapontype2]; + + // jobボーナス分 + for(i=0;i<sd->status.job_level && i<MAX_LEVEL;i++){ + if(job_bonus[s_class.upper][s_class.job][i]) + sd->paramb[job_bonus[s_class.upper][s_class.job][i]-1]++; + } + + if( (skill=pc_checkskill(sd,MC_INCCARRY))>0 ) // skill can be used with an item now, thanks to orn [Valaris] + sd->max_weight += skill*1000; + + if( (skill=pc_checkskill(sd,AC_OWL))>0 ) // ふくろうの目 + sd->paramb[4] += skill; + + // ステータス変化による基本パラメータ補正 + if(sd->sc_count){ + if(sd->sc_data[SC_CONCENTRATE].timer!=-1 && sd->sc_data[SC_QUAGMIRE].timer == -1){ // 集中力向上 + sd->paramb[1]+= (sd->status.agi+sd->paramb[1]+sd->parame[1]-sd->paramcard[1])*(2+sd->sc_data[SC_CONCENTRATE].val1)/100; + sd->paramb[4]+= (sd->status.dex+sd->paramb[4]+sd->parame[4]-sd->paramcard[4])*(2+sd->sc_data[SC_CONCENTRATE].val1)/100; + } + if(sd->sc_data[SC_INCREASEAGI].timer!=-1 && sd->sc_data[SC_QUAGMIRE].timer == -1 && sd->sc_data[SC_DONTFORGETME].timer == -1){ // 速度増加 + sd->paramb[1]+= 2+sd->sc_data[SC_INCREASEAGI].val1; + sd->speed -= sd->speed *25/100; + } + if(sd->sc_data[SC_DECREASEAGI].timer!=-1) // 速度減少(agiはbattle.cで) + sd->speed = sd->speed *125/100; + if(sd->sc_data[SC_CLOAKING].timer!=-1) + sd->speed = (sd->speed*(76+(sd->sc_data[SC_INCREASEAGI].val1*3)))/100; + if(sd->sc_data[SC_BLESSING].timer!=-1){ // ブレッシング + sd->paramb[0]+= sd->sc_data[SC_BLESSING].val1; + sd->paramb[3]+= sd->sc_data[SC_BLESSING].val1; + sd->paramb[4]+= sd->sc_data[SC_BLESSING].val1; + } + if(sd->sc_data[SC_GLORIA].timer!=-1) // グロリア + sd->paramb[5]+= 30; + if(sd->sc_data[SC_LOUD].timer!=-1 && sd->sc_data[SC_QUAGMIRE].timer == -1) // ラウドボイス + sd->paramb[0]+= 4; + if(sd->sc_data[SC_QUAGMIRE].timer!=-1){ // クァグマイア + sd->speed = sd->speed*3/2; + sd->paramb[1]-=(sd->status.agi+sd->paramb[1]+sd->parame[1])/2; + sd->paramb[4]-=(sd->status.dex+sd->paramb[4]+sd->parame[4])/2; + } + if(sd->sc_data[SC_TRUESIGHT].timer!=-1){ // トゥルーサイト + sd->paramb[0]+= 5; + sd->paramb[1]+= 5; + sd->paramb[2]+= 5; + sd->paramb[3]+= 5; + sd->paramb[4]+= 5; + sd->paramb[5]+= 5; + } + } + + //1度も死んでないJob70スパノビに+10 + if(s_class.job == 23 && sd->die_counter == 0 && sd->status.job_level >= 70){ + sd->paramb[0]+= 15; + sd->paramb[1]+= 15; + sd->paramb[2]+= 15; + sd->paramb[3]+= 15; + sd->paramb[4]+= 15; + sd->paramb[5]+= 15; + } + sd->paramc[0]=sd->status.str+sd->paramb[0]+sd->parame[0]; + sd->paramc[1]=sd->status.agi+sd->paramb[1]+sd->parame[1]; + sd->paramc[2]=sd->status.vit+sd->paramb[2]+sd->parame[2]; + sd->paramc[3]=sd->status.int_+sd->paramb[3]+sd->parame[3]; + sd->paramc[4]=sd->status.dex+sd->paramb[4]+sd->parame[4]; + sd->paramc[5]=sd->status.luk+sd->paramb[5]+sd->parame[5]; + for(i=0;i<6;i++) + if(sd->paramc[i] < 0) sd->paramc[i] = 0; + + if(sd->status.weapon == 11 || sd->status.weapon == 13 || sd->status.weapon == 14) { + str = sd->paramc[4]; + dex = sd->paramc[0]; + } + else { + str = sd->paramc[0]; + dex = sd->paramc[4]; + } + dstr = str/10; + sd->base_atk += str + dstr*dstr + dex/5 + sd->paramc[5]/5; + sd->matk1 += sd->paramc[3]+(sd->paramc[3]/5)*(sd->paramc[3]/5); + sd->matk2 += sd->paramc[3]+(sd->paramc[3]/7)*(sd->paramc[3]/7); + if(sd->matk1 < sd->matk2) { + int temp = sd->matk2; + sd->matk2 = sd->matk1; + sd->matk1 = temp; + } + sd->hit += sd->paramc[4] + sd->status.base_level; + sd->flee += sd->paramc[1] + sd->status.base_level; + sd->def2 += sd->paramc[2]; + sd->mdef2 += sd->paramc[3]; + sd->flee2 += sd->paramc[5]+10; + sd->critical += (sd->paramc[5]*3)+10; + + if(sd->base_atk < 1) + sd->base_atk = 1; + if(sd->critical_rate != 100) + sd->critical = (sd->critical*sd->critical_rate)/100; + if(sd->critical < 10) sd->critical = 10; + if(sd->hit_rate != 100) + sd->hit = (sd->hit*sd->hit_rate)/100; + if(sd->hit < 1) sd->hit = 1; + if(sd->flee_rate != 100) + sd->flee = (sd->flee*sd->flee_rate)/100; + if(sd->flee < 1) sd->flee = 1; + if(sd->flee2_rate != 100) + sd->flee2 = (sd->flee2*sd->flee2_rate)/100; + if(sd->flee2 < 10) sd->flee2 = 10; + if(sd->def_rate != 100) + sd->def = (sd->def*sd->def_rate)/100; + if(sd->def < 0) sd->def = 0; + if(sd->def2_rate != 100) + sd->def2 = (sd->def2*sd->def2_rate)/100; + if(sd->def2 < 1) sd->def2 = 1; + if(sd->mdef_rate != 100) + sd->mdef = (sd->mdef*sd->mdef_rate)/100; + if(sd->mdef < 0) sd->mdef = 0; + if(sd->mdef2_rate != 100) + sd->mdef2 = (sd->mdef2*sd->mdef2_rate)/100; + if(sd->mdef2 < 1) sd->mdef2 = 1; + + // 二刀流 ASPD 修正 + if (sd->status.weapon <= 16) + sd->aspd += aspd_base[s_class.job][sd->status.weapon]-(sd->paramc[1]*4+sd->paramc[4])*aspd_base[s_class.job][sd->status.weapon]/1000; + else + sd->aspd += ( + (aspd_base[s_class.job][sd->weapontype1]-(sd->paramc[1]*4+sd->paramc[4])*aspd_base[s_class.job][sd->weapontype1]/1000) + + (aspd_base[s_class.job][sd->weapontype2]-(sd->paramc[1]*4+sd->paramc[4])*aspd_base[s_class.job][sd->weapontype2]/1000) + ) * 140 / 200; + + aspd_rate = sd->aspd_rate; + + //攻撃速度増加 + + if( (skill=pc_checkskill(sd,AC_VULTURE))>0){ // ワシの目 + sd->hit += skill; + if(sd->status.weapon == 11) + sd->attackrange += skill; + } + + if( (skill=pc_checkskill(sd,BS_WEAPONRESEARCH))>0) // 武器研究の命中率増加 + sd->hit += skill*2; + if(sd->status.option&2 && (skill = pc_checkskill(sd,RG_TUNNELDRIVE))>0 ) // トンネルドライブ // トンネルドライブ + sd->speed += (1.2*DEFAULT_WALK_SPEED - skill*9); + if (pc_iscarton(sd) && (skill=pc_checkskill(sd,MC_PUSHCART))>0) // カートによる速度低下 + sd->speed += (10-skill) * (DEFAULT_WALK_SPEED * 0.1); + else if (pc_isriding(sd)) // ペコペコ乗りによる速度増加 + sd->speed -= (0.25 * DEFAULT_WALK_SPEED); + sd->max_weight += 1000; + if(sd->sc_count){ + if(sd->sc_data[SC_WINDWALK].timer!=-1) //ウィンドウォーク時はLv*2%減算 + sd->speed -= sd->speed *(sd->sc_data[SC_WINDWALK].val1*2)/100; + if(sd->sc_data[SC_CARTBOOST].timer!=-1) // カートブースト + sd->speed -= (DEFAULT_WALK_SPEED * 20)/100; + if(sd->sc_data[SC_BERSERK].timer!=-1) //バーサーク中はIAと同じぐらい速い? + sd->speed -= sd->speed *25/100; + if(sd->sc_data[SC_WEDDING].timer!=-1) //結婚中は歩くのが遅い + sd->speed = 2*DEFAULT_WALK_SPEED; + } + + if((skill=pc_checkskill(sd,CR_TRUST))>0) { // フェイス + sd->status.max_hp += skill*200; + sd->subele[6] += skill*5; + } + if((skill=pc_checkskill(sd,BS_SKINTEMPER))>0) + sd->subele[3] += skill*4; + + bl=sd->status.base_level; + + sd->status.max_hp += (3500 + bl*hp_coefficient2[s_class.job] + hp_sigma_val[s_class.job][(bl > 0)? bl-1:0])/100 * (100 + sd->paramc[2])/100 + (sd->parame[2] - sd->paramcard[2]); + if (s_class.upper==1) // [MouseJstr] + sd->status.max_hp = sd->status.max_hp * 130/100; + if(sd->hprate!=100) + sd->status.max_hp = sd->status.max_hp*sd->hprate/100; + + if(sd->sc_data && sd->sc_data[SC_BERSERK].timer!=-1){ // バーサーク + sd->status.max_hp = sd->status.max_hp * 3; + sd->status.hp = sd->status.hp * 3; + if(sd->status.max_hp > battle_config.max_hp) // removed negative max hp bug by Valaris + sd->status.max_hp = battle_config.max_hp; + if(sd->status.hp > battle_config.max_hp) // removed negative max hp bug by Valaris + sd->status.hp = battle_config.max_hp; + } + if(s_class.job == 23 && sd->status.base_level >= 99){ + sd->status.max_hp = sd->status.max_hp + 2000; + } + + if(sd->status.max_hp > battle_config.max_hp) // removed negative max hp bug by Valaris + sd->status.max_hp = battle_config.max_hp; + if(sd->status.max_hp <= 0) sd->status.max_hp = 1; // end + + // 最大SP計算 + sd->status.max_sp += ((sp_coefficient[s_class.job] * bl) + 1000)/100 * (100 + sd->paramc[3])/100 + (sd->parame[3] - sd->paramcard[3]); + if (s_class.upper==1) // [MouseJstr] + sd->status.max_sp = sd->status.max_sp * 130/100; + if(sd->sprate!=100) + sd->status.max_sp = sd->status.max_sp*sd->sprate/100; + + if((skill=pc_checkskill(sd,HP_MEDITATIO))>0) // メディテイティオ + sd->status.max_sp += sd->status.max_sp*skill/100; + if((skill=pc_checkskill(sd,HW_SOULDRAIN))>0) /* ソウルドレイン */ + sd->status.max_sp += sd->status.max_sp*2*skill/100; + + if(sd->status.max_sp < 0 || sd->status.max_sp > battle_config.max_sp) + sd->status.max_sp = battle_config.max_sp; + + //自然回復HP + sd->nhealhp = 1 + (sd->paramc[2]/5) + (sd->status.max_hp/200); + if((skill=pc_checkskill(sd,SM_RECOVERY)) > 0) { /* HP回復力向上 */ + sd->nshealhp = skill*5 + (sd->status.max_hp*skill/500); + if(sd->nshealhp > 0x7fff) sd->nshealhp = 0x7fff; + } + //自然回復SP + sd->nhealsp = 1 + (sd->paramc[3]/6) + (sd->status.max_sp/100); + if(sd->paramc[3] >= 120) + sd->nhealsp += ((sd->paramc[3]-120)>>1) + 4; + if((skill=pc_checkskill(sd,MG_SRECOVERY)) > 0) { /* SP回復力向上 */ + sd->nshealsp = skill*3 + (sd->status.max_sp*skill/500); + if(sd->nshealsp > 0x7fff) sd->nshealsp = 0x7fff; + } + + if((skill = pc_checkskill(sd,MO_SPIRITSRECOVERY)) > 0) { + sd->nsshealhp = skill*4 + (sd->status.max_hp*skill/500); + sd->nsshealsp = skill*2 + (sd->status.max_sp*skill/500); + if(sd->nsshealhp > 0x7fff) sd->nsshealhp = 0x7fff; + if(sd->nsshealsp > 0x7fff) sd->nsshealsp = 0x7fff; + } + if(sd->hprecov_rate != 100) { + sd->nhealhp = sd->nhealhp*sd->hprecov_rate/100; + if(sd->nhealhp < 1) sd->nhealhp = 1; + } + if(sd->sprecov_rate != 100) { + sd->nhealsp = sd->nhealsp*sd->sprecov_rate/100; + if(sd->nhealsp < 1) sd->nhealsp = 1; + } + if((skill=pc_checkskill(sd,HP_MEDITATIO)) > 0) { // メディテイティオはSPRではなく自然回復にかかる + sd->nhealsp += 3*skill*(sd->status.max_sp)/100; + if(sd->nhealsp > 0x7fff) sd->nhealsp = 0x7fff; + } + + // 種族耐性(これでいいの? ディバインプロテクションと同じ処理がいるかも) + if( (skill=pc_checkskill(sd,SA_DRAGONOLOGY))>0 ){ // ドラゴノロジー + skill = skill*4; + sd->addrace[9]+=skill; + sd->addrace_[9]+=skill; + sd->subrace[9]+=skill; + sd->magic_addrace[9]+=skill; + sd->magic_subrace[9]-=skill; + } + + //Flee上昇 + if( (skill=pc_checkskill(sd,TF_MISS))>0 ){ // 回避率増加 + if(sd->status.class==6||sd->status.class==4007 || sd->status.class==23){ + sd->flee += skill*3; + } + if(sd->status.class==12||sd->status.class==17||sd->status.class==4013||sd->status.class==4018) + sd->flee += skill*4; + if(sd->status.class==12||sd->status.class==4013) + sd->speed -= sd->speed *(skill*.5)/100; + } + if( (skill=pc_checkskill(sd,MO_DODGE))>0 ) // 見切り + sd->flee += (skill*3)>>1; + + // スキルやステータス異常による残りのパラメータ補正 + if(sd->sc_count){ + // ATK/DEF変化形 + if(sd->sc_data[SC_ANGELUS].timer!=-1) // エンジェラス + sd->def2 = sd->def2*(110+5*sd->sc_data[SC_ANGELUS].val1)/100; + if(sd->sc_data[SC_IMPOSITIO].timer!=-1) {// インポシティオマヌス + sd->watk += sd->sc_data[SC_IMPOSITIO].val1*5; + index = sd->equip_index[8]; + if(index >= 0 && sd->inventory_data[index] && sd->inventory_data[index]->type == 4) + sd->watk_ += sd->sc_data[SC_IMPOSITIO].val1*5; + } + if(sd->sc_data[SC_PROVOKE].timer!=-1){ // プロボック + sd->def2 = sd->def2*(100-6*sd->sc_data[SC_PROVOKE].val1)/100; + sd->base_atk = sd->base_atk*(100+2*sd->sc_data[SC_PROVOKE].val1)/100; + sd->watk = sd->watk*(100+2*sd->sc_data[SC_PROVOKE].val1)/100; + index = sd->equip_index[8]; + if(index >= 0 && sd->inventory_data[index] && sd->inventory_data[index]->type == 4) + sd->watk_ = sd->watk_*(100+2*sd->sc_data[SC_PROVOKE].val1)/100; + } + if(sd->sc_data[SC_ENDURE].timer!=-1) + sd->mdef2 += sd->sc_data[SC_ENDURE].val1; + if(sd->sc_data[SC_MINDBREAKER].timer!=-1){ // プロボック + sd->mdef2 = sd->mdef2*(100-6*sd->sc_data[SC_MINDBREAKER].val1)/100; + sd->matk1 = sd->matk1*(100+2*sd->sc_data[SC_MINDBREAKER].val1)/100; + sd->matk2 = sd->matk2*(100+2*sd->sc_data[SC_MINDBREAKER].val1)/100; + } + if(sd->sc_data[SC_POISON].timer!=-1) // 毒状態 + sd->def2 = sd->def2*75/100; + if(sd->sc_data[SC_DRUMBATTLE].timer!=-1){ // 戦太鼓の響き + sd->watk += sd->sc_data[SC_DRUMBATTLE].val2; + sd->def += sd->sc_data[SC_DRUMBATTLE].val3; + index = sd->equip_index[8]; + if(index >= 0 && sd->inventory_data[index] && sd->inventory_data[index]->type == 4) + sd->watk_ += sd->sc_data[SC_DRUMBATTLE].val2; + } + if(sd->sc_data[SC_NIBELUNGEN].timer!=-1) { // ニーベルングの指輪 + index = sd->equip_index[9]; + if(index >= 0 && sd->inventory_data[index] && sd->inventory_data[index]->wlv == 3) + sd->watk += sd->sc_data[SC_NIBELUNGEN].val3; + index = sd->equip_index[8]; + if(index >= 0 && sd->inventory_data[index] && sd->inventory_data[index]->wlv == 3) + sd->watk_ += sd->sc_data[SC_NIBELUNGEN].val3; + if(index >= 0 && sd->inventory_data[index] && sd->inventory_data[index]->wlv == 4) + sd->watk += sd->sc_data[SC_NIBELUNGEN].val2; + index = sd->equip_index[8]; + if(index >= 0 && sd->inventory_data[index] && sd->inventory_data[index]->wlv == 4) + sd->watk_ += sd->sc_data[SC_NIBELUNGEN].val2; + } + + if(sd->sc_data[SC_VOLCANO].timer!=-1 && sd->def_ele==3){ // ボルケーノ + sd->watk += sd->sc_data[SC_VIOLENTGALE].val3; + } + + if(sd->sc_data[SC_SIGNUMCRUCIS].timer!=-1) + sd->def = sd->def * (100 - sd->sc_data[SC_SIGNUMCRUCIS].val2)/100; + if(sd->sc_data[SC_ETERNALCHAOS].timer!=-1) // エターナルカオス + sd->def=0; + + if(sd->sc_data[SC_CONCENTRATION].timer!=-1){ //コンセントレーション + sd->watk = sd->watk * (100 + 5*sd->sc_data[SC_CONCENTRATION].val1)/100; + index = sd->equip_index[8]; + if(index >= 0 && sd->inventory_data[index] && sd->inventory_data[index]->type == 4) + sd->watk_ = sd->watk * (100 + 5*sd->sc_data[SC_CONCENTRATION].val1)/100; + sd->def = sd->def * (100 - 5*sd->sc_data[SC_CONCENTRATION].val1)/100; + } + + if(sd->sc_data[SC_MAGICPOWER].timer!=-1){ //魔法力増幅 + sd->matk1 = sd->matk1*(100+2*sd->sc_data[SC_MAGICPOWER].val1)/100; + sd->matk2 = sd->matk2*(100+2*sd->sc_data[SC_MAGICPOWER].val1)/100; + } + if(sd->sc_data[SC_ATKPOT].timer!=-1) + sd->watk += sd->sc_data[SC_ATKPOT].val1; + if(sd->sc_data[SC_MATKPOT].timer!=-1){ + sd->matk1 += sd->sc_data[SC_MATKPOT].val1; + sd->matk2 += sd->sc_data[SC_MATKPOT].val1; + } + + // ASPD/移動速度変化系 + if(sd->sc_data[SC_TWOHANDQUICKEN].timer != -1 && sd->sc_data[SC_QUAGMIRE].timer == -1 && sd->sc_data[SC_DONTFORGETME].timer == -1) // 2HQ + aspd_rate -= 30; + if(sd->sc_data[SC_ADRENALINE].timer != -1 && sd->sc_data[SC_TWOHANDQUICKEN].timer == -1 && + sd->sc_data[SC_QUAGMIRE].timer == -1 && sd->sc_data[SC_DONTFORGETME].timer == -1) { // アドレナリンラッシュ + if(sd->sc_data[SC_ADRENALINE].val2 || !battle_config.party_skill_penaly) + aspd_rate -= 30; + else + aspd_rate -= 25; + } + if(sd->sc_data[SC_SPEARSQUICKEN].timer != -1 && sd->sc_data[SC_ADRENALINE].timer == -1 && + sd->sc_data[SC_TWOHANDQUICKEN].timer == -1 && sd->sc_data[SC_QUAGMIRE].timer == -1 && sd->sc_data[SC_DONTFORGETME].timer == -1) // スピアクィッケン + aspd_rate -= sd->sc_data[SC_SPEARSQUICKEN].val2; + if(sd->sc_data[SC_ASSNCROS].timer!=-1 && // 夕陽のアサシンクロス + sd->sc_data[SC_TWOHANDQUICKEN].timer==-1 && sd->sc_data[SC_ADRENALINE].timer==-1 && sd->sc_data[SC_SPEARSQUICKEN].timer==-1 && + sd->sc_data[SC_DONTFORGETME].timer == -1) + aspd_rate -= 5+sd->sc_data[SC_ASSNCROS].val1+sd->sc_data[SC_ASSNCROS].val2+sd->sc_data[SC_ASSNCROS].val3; + if(sd->sc_data[SC_DONTFORGETME].timer!=-1){ // 私を忘れないで + aspd_rate += sd->sc_data[SC_DONTFORGETME].val1*3 + sd->sc_data[SC_DONTFORGETME].val2 + (sd->sc_data[SC_DONTFORGETME].val3>>16); + sd->speed= sd->speed*(100+sd->sc_data[SC_DONTFORGETME].val1*2 + sd->sc_data[SC_DONTFORGETME].val2 + (sd->sc_data[SC_DONTFORGETME].val3&0xffff))/100; + } + if( sd->sc_data[i=SC_SPEEDPOTION2].timer!=-1 || + sd->sc_data[i=SC_SPEEDPOTION1].timer!=-1 || + sd->sc_data[i=SC_SPEEDPOTION0].timer!=-1) // 増 速ポーション + aspd_rate -= sd->sc_data[i].val2; + + // HIT/FLEE変化系 + if(sd->sc_data[SC_WHISTLE].timer!=-1){ // 口笛 + sd->flee += sd->flee * (sd->sc_data[SC_WHISTLE].val1 + +sd->sc_data[SC_WHISTLE].val2+(sd->sc_data[SC_WHISTLE].val3>>16))/100; + sd->flee2+= (sd->sc_data[SC_WHISTLE].val1+sd->sc_data[SC_WHISTLE].val2+(sd->sc_data[SC_WHISTLE].val3&0xffff)) * 10; + } + if(sd->sc_data[SC_HUMMING].timer!=-1) // ハミング + sd->hit += (sd->sc_data[SC_HUMMING].val1*2+sd->sc_data[SC_HUMMING].val2 + +sd->sc_data[SC_HUMMING].val3) * sd->hit/100; + if(sd->sc_data[SC_VIOLENTGALE].timer!=-1 && sd->def_ele==4){ // バイオレントゲイル + sd->flee += sd->flee*sd->sc_data[SC_VIOLENTGALE].val3/100; + } + if(sd->sc_data[SC_BLIND].timer!=-1){ // 暗黒 + sd->hit -= sd->hit*25/100; + sd->flee -= sd->flee*25/100; + } + if(sd->sc_data[SC_WINDWALK].timer!=-1) // ウィンドウォーク + sd->flee += sd->flee*(sd->sc_data[SC_WINDWALK].val2)/100; + if(sd->sc_data[SC_SPIDERWEB].timer!=-1) //スパイダーウェブ + sd->flee -= sd->flee*50/100; + if(sd->sc_data[SC_TRUESIGHT].timer!=-1) //トゥルーサイト + sd->hit += 3*(sd->sc_data[SC_TRUESIGHT].val1); + if(sd->sc_data[SC_CONCENTRATION].timer!=-1) //コンセントレーション + sd->hit += (10*(sd->sc_data[SC_CONCENTRATION].val1)); + + // 耐性 + if(sd->sc_data[SC_SIEGFRIED].timer!=-1){ // 不死身のジークフリード + sd->subele[1] += sd->sc_data[SC_SIEGFRIED].val2; // 水 + sd->subele[2] += sd->sc_data[SC_SIEGFRIED].val2; // 水 + sd->subele[3] += sd->sc_data[SC_SIEGFRIED].val2; // 火 + sd->subele[4] += sd->sc_data[SC_SIEGFRIED].val2; // 水 + sd->subele[5] += sd->sc_data[SC_SIEGFRIED].val2; // 水 + sd->subele[6] += sd->sc_data[SC_SIEGFRIED].val2; // 水 + sd->subele[7] += sd->sc_data[SC_SIEGFRIED].val2; // 水 + sd->subele[8] += sd->sc_data[SC_SIEGFRIED].val2; // 水 + sd->subele[9] += sd->sc_data[SC_SIEGFRIED].val2; // 水 + } + if(sd->sc_data[SC_PROVIDENCE].timer!=-1){ // プロヴィデンス + sd->subele[6] += sd->sc_data[SC_PROVIDENCE].val2; // 対 聖属性 + sd->subrace[6] += sd->sc_data[SC_PROVIDENCE].val2; // 対 悪魔 + } + + // その他 + if(sd->sc_data[SC_APPLEIDUN].timer!=-1){ // イドゥンの林檎 + sd->status.max_hp += ((5+sd->sc_data[SC_APPLEIDUN].val1*2+((sd->sc_data[SC_APPLEIDUN].val2+1)>>1) + +sd->sc_data[SC_APPLEIDUN].val3/10) * sd->status.max_hp)/100; + if(sd->status.max_hp < 0 || sd->status.max_hp > battle_config.max_hp) + sd->status.max_hp = battle_config.max_hp; + } + if(sd->sc_data[SC_DELUGE].timer!=-1 && sd->def_ele==1){ // デリュージ + sd->status.max_hp += sd->status.max_hp*sd->sc_data[SC_DELUGE].val3/100; + if(sd->status.max_hp < 0 || sd->status.max_hp > battle_config.max_hp) + sd->status.max_hp = battle_config.max_hp; + } + if(sd->sc_data[SC_SERVICE4U].timer!=-1) { // サービスフォーユー + sd->status.max_sp += sd->status.max_sp*(10+sd->sc_data[SC_SERVICE4U].val1+sd->sc_data[SC_SERVICE4U].val2 + +sd->sc_data[SC_SERVICE4U].val3)/100; + if(sd->status.max_sp < 0 || sd->status.max_sp > battle_config.max_sp) + sd->status.max_sp = battle_config.max_sp; + sd->dsprate-=(10+sd->sc_data[SC_SERVICE4U].val1*3+sd->sc_data[SC_SERVICE4U].val2 + +sd->sc_data[SC_SERVICE4U].val3); + if(sd->dsprate<0)sd->dsprate=0; + } + + if(sd->sc_data[SC_FORTUNE].timer!=-1) // 幸運のキス + sd->critical += (10+sd->sc_data[SC_FORTUNE].val1+sd->sc_data[SC_FORTUNE].val2 + +sd->sc_data[SC_FORTUNE].val3)*10; + + if(sd->sc_data[SC_EXPLOSIONSPIRITS].timer!=-1){ // 爆裂波動 + if(s_class.job==23) + sd->critical += sd->sc_data[SC_EXPLOSIONSPIRITS].val1*100; + else + sd->critical += sd->sc_data[SC_EXPLOSIONSPIRITS].val2; + } + + if(sd->sc_data[SC_STEELBODY].timer!=-1){ // 金剛 + sd->def = 90; + sd->mdef = 90; + aspd_rate += 25; + sd->speed = (sd->speed * 125) / 100; + } + if(sd->sc_data[SC_DEFENDER].timer != -1) { + sd->aspd += (550 - sd->sc_data[SC_DEFENDER].val1*50); + sd->speed = (sd->speed * (155 - sd->sc_data[SC_DEFENDER].val1*5)) / 100; + } + if(sd->sc_data[SC_ENCPOISON].timer != -1) + sd->addeff[4] += sd->sc_data[SC_ENCPOISON].val2; + + if( sd->sc_data[SC_DANCING].timer!=-1 ){ // 演奏/ダンス使用中 + sd->speed*=4; + sd->nhealsp = 0; + sd->nshealsp = 0; + sd->nsshealsp = 0; + } + if(sd->sc_data[SC_CURSE].timer!=-1) + sd->speed += 450; + + if(sd->sc_data[SC_TRUESIGHT].timer!=-1) //トゥルーサイト + sd->critical += sd->critical*(sd->sc_data[SC_TRUESIGHT].val1)/100; + +/* if(sd->sc_data[SC_VOLCANO].timer!=-1) // エンチャントポイズン(属性はbattle.cで) + sd->addeff[2]+=sd->sc_data[SC_VOLCANO].val2;//% of granting + if(sd->sc_data[SC_DELUGE].timer!=-1) // エンチャントポイズン(属性はbattle.cで) + sd->addeff[0]+=sd->sc_data[SC_DELUGE].val2;//% of granting + */ + } + + if(sd->speed_rate != 100) + sd->speed = sd->speed*sd->speed_rate/100; + if(sd->speed < 1) sd->speed = 1; + if(aspd_rate != 100) + sd->aspd = sd->aspd*aspd_rate/100; + if(pc_isriding(sd)) // 騎兵修練 + sd->aspd = sd->aspd*(100 + 10*(5 - pc_checkskill(sd,KN_CAVALIERMASTERY)))/ 100; + if(sd->aspd < battle_config.max_aspd) sd->aspd = battle_config.max_aspd; + sd->amotion = sd->aspd; + sd->dmotion = 800-sd->paramc[1]*4; + if(sd->dmotion<400) + sd->dmotion = 400; + if(sd->skilltimer != -1 && (skill = pc_checkskill(sd,SA_FREECAST)) > 0) { + sd->prev_speed = sd->speed; + sd->speed = sd->speed*(175 - skill*5)/100; + } + + if(sd->status.hp>sd->status.max_hp) + sd->status.hp=sd->status.max_hp; + if(sd->status.sp>sd->status.max_sp) + sd->status.sp=sd->status.max_sp; + + if(first&4) + return 0; + if(first&3) { + clif_updatestatus(sd,SP_SPEED); + clif_updatestatus(sd,SP_MAXHP); + clif_updatestatus(sd,SP_MAXSP); + if(first&1) { + clif_updatestatus(sd,SP_HP); + clif_updatestatus(sd,SP_SP); + } + return 0; + } + + if(b_class != sd->view_class) { + clif_changelook(&sd->bl,LOOK_BASE,sd->view_class); +#if PACKETVER < 4 + clif_changelook(&sd->bl,LOOK_WEAPON,sd->status.weapon); + clif_changelook(&sd->bl,LOOK_SHIELD,sd->status.shield); +#else + clif_changelook(&sd->bl,LOOK_WEAPON,0); +#endif + } + + if( memcmp(b_skill,sd->status.skill,sizeof(sd->status.skill)) || b_attackrange != sd->attackrange) + clif_skillinfoblock(sd); // スキル送信 + + if(b_speed != sd->speed) + clif_updatestatus(sd,SP_SPEED); + if(b_weight != sd->weight) + clif_updatestatus(sd,SP_WEIGHT); + if(b_max_weight != sd->max_weight) { + clif_updatestatus(sd,SP_MAXWEIGHT); + pc_checkweighticon(sd); + } + for(i=0;i<6;i++) + if(b_paramb[i] + b_parame[i] != sd->paramb[i] + sd->parame[i]) + clif_updatestatus(sd,SP_STR+i); + if(b_hit != sd->hit) + clif_updatestatus(sd,SP_HIT); + if(b_flee != sd->flee) + clif_updatestatus(sd,SP_FLEE1); + if(b_aspd != sd->aspd) + clif_updatestatus(sd,SP_ASPD); + if(b_watk != sd->watk || b_base_atk != sd->base_atk) + clif_updatestatus(sd,SP_ATK1); + if(b_def != sd->def) + clif_updatestatus(sd,SP_DEF1); + if(b_watk2 != sd->watk2) + clif_updatestatus(sd,SP_ATK2); + if(b_def2 != sd->def2) + clif_updatestatus(sd,SP_DEF2); + if(b_flee2 != sd->flee2) + clif_updatestatus(sd,SP_FLEE2); + if(b_critical != sd->critical) + clif_updatestatus(sd,SP_CRITICAL); + if(b_matk1 != sd->matk1) + clif_updatestatus(sd,SP_MATK1); + if(b_matk2 != sd->matk2) + clif_updatestatus(sd,SP_MATK2); + if(b_mdef != sd->mdef) + clif_updatestatus(sd,SP_MDEF1); + if(b_mdef2 != sd->mdef2) + clif_updatestatus(sd,SP_MDEF2); + if(b_attackrange != sd->attackrange) + clif_updatestatus(sd,SP_ATTACKRANGE); + if(b_max_hp != sd->status.max_hp) + clif_updatestatus(sd,SP_MAXHP); + if(b_max_sp != sd->status.max_sp) + clif_updatestatus(sd,SP_MAXSP); + if(b_hp != sd->status.hp) + clif_updatestatus(sd,SP_HP); + if(b_sp != sd->status.sp) + clif_updatestatus(sd,SP_SP); + +/* if(before.cart_num != before.cart_num || before.cart_max_num != before.cart_max_num || + before.cart_weight != before.cart_weight || before.cart_max_weight != before.cart_max_weight ) + clif_updatestatus(sd,SP_CARTINFO);*/ + + if(sd->status.hp<sd->status.max_hp>>2 && pc_checkskill(sd,SM_AUTOBERSERK)>0 && + (sd->sc_data[SC_PROVOKE].timer==-1 || sd->sc_data[SC_PROVOKE].val2==0 ) && !pc_isdead(sd)) + // オートバーサーク発動 + skill_status_change_start(&sd->bl,SC_PROVOKE,10,1,0,0,0,0); + + return 0; +} + +/*========================================== + * 装 備品による能力等のボーナス設定 + *------------------------------------------ + */ +int pc_bonus(struct map_session_data *sd,int type,int val) +{ + nullpo_retr(0, sd); + + switch(type){ + case SP_STR: + case SP_AGI: + case SP_VIT: + case SP_INT: + case SP_DEX: + case SP_LUK: + if(sd->state.lr_flag != 2) + sd->parame[type-SP_STR]+=val; + break; + case SP_ATK1: + if(!sd->state.lr_flag) + sd->watk+=val; + else if(sd->state.lr_flag == 1) + sd->watk_+=val; + break; + case SP_ATK2: + if(!sd->state.lr_flag) + sd->watk2+=val; + else if(sd->state.lr_flag == 1) + sd->watk_2+=val; + break; + case SP_BASE_ATK: + if(sd->state.lr_flag != 2) + sd->base_atk+=val; + break; + case SP_MATK1: + if(sd->state.lr_flag != 2) + sd->matk1 += val; + break; + case SP_MATK2: + if(sd->state.lr_flag != 2) + sd->matk2 += val; + break; + case SP_MATK: + if(sd->state.lr_flag != 2) { + sd->matk1 += val; + sd->matk2 += val; + } + break; + case SP_DEF1: + if(sd->state.lr_flag != 2) + sd->def+=val; + break; + case SP_MDEF1: + if(sd->state.lr_flag != 2) + sd->mdef+=val; + break; + case SP_MDEF2: + if(sd->state.lr_flag != 2) + sd->mdef+=val; + break; + case SP_HIT: + if(sd->state.lr_flag != 2) + sd->hit+=val; + else + sd->arrow_hit+=val; + break; + case SP_FLEE1: + if(sd->state.lr_flag != 2) + sd->flee+=val; + break; + case SP_FLEE2: + if(sd->state.lr_flag != 2) + sd->flee2+=val*10; + break; + case SP_CRITICAL: + if(sd->state.lr_flag != 2) + sd->critical+=val*10; + else + sd->arrow_cri += val*10; + break; + case SP_ATKELE: + if(!sd->state.lr_flag) + sd->atk_ele=val; + else if(sd->state.lr_flag == 1) + sd->atk_ele_=val; + else if(sd->state.lr_flag == 2) + sd->arrow_ele=val; + break; + case SP_DEFELE: + if(sd->state.lr_flag != 2) + sd->def_ele=val; + break; + case SP_MAXHP: + if(sd->state.lr_flag != 2) + sd->status.max_hp+=val; + break; + case SP_MAXSP: + if(sd->state.lr_flag != 2) + sd->status.max_sp+=val; + break; + case SP_CASTRATE: + if(sd->state.lr_flag != 2) + sd->castrate+=val; + break; + case SP_MAXHPRATE: + if(sd->state.lr_flag != 2) + sd->hprate+=val; + break; + case SP_MAXSPRATE: + if(sd->state.lr_flag != 2) + sd->sprate+=val; + break; + case SP_SPRATE: + if(sd->state.lr_flag != 2) + sd->dsprate+=val; + break; + case SP_ATTACKRANGE: + if(!sd->state.lr_flag) + sd->attackrange += val; + else if(sd->state.lr_flag == 1) + sd->attackrange_ += val; + else if(sd->state.lr_flag == 2) + sd->arrow_range += val; + break; + case SP_ADD_SPEED: + if(sd->state.lr_flag != 2) + sd->speed -= val; + break; + case SP_SPEED_RATE: + if(sd->state.lr_flag != 2) { + if(sd->speed_rate > 100-val) + sd->speed_rate = 100-val; + } + break; + case SP_SPEED_ADDRATE: + if(sd->state.lr_flag != 2) + sd->speed_add_rate = sd->speed_add_rate * (100-val)/100; + break; + case SP_ASPD: + if(sd->state.lr_flag != 2) + sd->aspd -= val*10; + break; + case SP_ASPD_RATE: + if(sd->state.lr_flag != 2) { + if(sd->aspd_rate > 100-val) + sd->aspd_rate = 100-val; + } + break; + case SP_ASPD_ADDRATE: + if(sd->state.lr_flag != 2) + sd->aspd_add_rate = sd->aspd_add_rate * (100-val)/100; + break; + case SP_HP_RECOV_RATE: + if(sd->state.lr_flag != 2) + sd->hprecov_rate += val; + break; + case SP_SP_RECOV_RATE: + if(sd->state.lr_flag != 2) + sd->sprecov_rate += val; + break; + case SP_CRITICAL_DEF: + if(sd->state.lr_flag != 2) + sd->critical_def += val; + break; + case SP_NEAR_ATK_DEF: + if(sd->state.lr_flag != 2) + sd->near_attack_def_rate += val; + break; + case SP_LONG_ATK_DEF: + if(sd->state.lr_flag != 2) + sd->long_attack_def_rate += val; + break; + case SP_DOUBLE_RATE: + if(sd->state.lr_flag == 0 && sd->double_rate < val) + sd->double_rate = val; + break; + case SP_DOUBLE_ADD_RATE: + if(sd->state.lr_flag == 0) + sd->double_add_rate += val; + break; + case SP_MATK_RATE: + if(sd->state.lr_flag != 2) + sd->matk_rate += val; + break; + case SP_IGNORE_DEF_ELE: + if(!sd->state.lr_flag) + sd->ignore_def_ele |= 1<<val; + else if(sd->state.lr_flag == 1) + sd->ignore_def_ele_ |= 1<<val; + break; + case SP_IGNORE_DEF_RACE: + if(!sd->state.lr_flag) + sd->ignore_def_race |= 1<<val; + else if(sd->state.lr_flag == 1) + sd->ignore_def_race_ |= 1<<val; + break; + case SP_ATK_RATE: + if(sd->state.lr_flag != 2) + sd->atk_rate += val; + break; + case SP_MAGIC_ATK_DEF: + if(sd->state.lr_flag != 2) + sd->magic_def_rate += val; + break; + case SP_MISC_ATK_DEF: + if(sd->state.lr_flag != 2) + sd->misc_def_rate += val; + break; + case SP_IGNORE_MDEF_ELE: + if(sd->state.lr_flag != 2) + sd->ignore_mdef_ele |= 1<<val; + break; + case SP_IGNORE_MDEF_RACE: + if(sd->state.lr_flag != 2) + sd->ignore_mdef_race |= 1<<val; + break; + case SP_PERFECT_HIT_RATE: + if(sd->state.lr_flag != 2 && sd->perfect_hit < val) + sd->perfect_hit = val; + break; + case SP_PERFECT_HIT_ADD_RATE: + if(sd->state.lr_flag != 2) + sd->perfect_hit_add += val; + break; + case SP_CRITICAL_RATE: + if(sd->state.lr_flag != 2) + sd->critical_rate+=val; + break; + case SP_GET_ZENY_NUM: + if(sd->state.lr_flag != 2 && sd->get_zeny_num < val) + sd->get_zeny_num = val; + break; + case SP_ADD_GET_ZENY_NUM: + if(sd->state.lr_flag != 2) + sd->get_zeny_add_num += val; + break; + case SP_DEF_RATIO_ATK_ELE: + if(!sd->state.lr_flag) + sd->def_ratio_atk_ele |= 1<<val; + else if(sd->state.lr_flag == 1) + sd->def_ratio_atk_ele_ |= 1<<val; + break; + case SP_DEF_RATIO_ATK_RACE: + if(!sd->state.lr_flag) + sd->def_ratio_atk_race |= 1<<val; + else if(sd->state.lr_flag == 1) + sd->def_ratio_atk_race_ |= 1<<val; + break; + case SP_HIT_RATE: + if(sd->state.lr_flag != 2) + sd->hit_rate += val; + break; + case SP_FLEE_RATE: + if(sd->state.lr_flag != 2) + sd->flee_rate += val; + break; + case SP_FLEE2_RATE: + if(sd->state.lr_flag != 2) + sd->flee2_rate += val; + break; + case SP_DEF_RATE: + if(sd->state.lr_flag != 2) + sd->def_rate += val; + break; + case SP_DEF2_RATE: + if(sd->state.lr_flag != 2) + sd->def2_rate += val; + break; + case SP_MDEF_RATE: + if(sd->state.lr_flag != 2) + sd->mdef_rate += val; + break; + case SP_MDEF2_RATE: + if(sd->state.lr_flag != 2) + sd->mdef2_rate += val; + break; + case SP_RESTART_FULL_RECORVER: + if(sd->state.lr_flag != 2) + sd->special_state.restart_full_recover = 1; + break; + case SP_NO_CASTCANCEL: + if(sd->state.lr_flag != 2) + sd->special_state.no_castcancel = 1; + break; + case SP_NO_CASTCANCEL2: + if(sd->state.lr_flag != 2) + sd->special_state.no_castcancel2 = 1; + break; + case SP_NO_SIZEFIX: + if(sd->state.lr_flag != 2) + sd->special_state.no_sizefix = 1; + break; + case SP_NO_MAGIC_DAMAGE: + if(sd->state.lr_flag != 2) + sd->special_state.no_magic_damage = 1; + break; + case SP_NO_WEAPON_DAMAGE: + if(sd->state.lr_flag != 2) + sd->special_state.no_weapon_damage = 1; + break; + case SP_NO_GEMSTONE: + if(sd->state.lr_flag != 2) + sd->special_state.no_gemstone = 1; + break; + case SP_INFINITE_ENDURE: + if(sd->state.lr_flag != 2) + sd->special_state.infinite_endure = 1; + break; + case SP_SPLASH_RANGE: + if(sd->state.lr_flag != 2 && sd->splash_range < val) + sd->splash_range = val; + break; + case SP_SPLASH_ADD_RANGE: + if(sd->state.lr_flag != 2) + sd->splash_add_range += val; + break; + case SP_SHORT_WEAPON_DAMAGE_RETURN: + if(sd->state.lr_flag != 2) + sd->short_weapon_damage_return += val; + break; + case SP_LONG_WEAPON_DAMAGE_RETURN: + if(sd->state.lr_flag != 2) + sd->long_weapon_damage_return += val; + break; + case SP_MAGIC_DAMAGE_RETURN: //AppleGirl Was Here + if(sd->state.lr_flag != 2) + sd->magic_damage_return += val; + break; + case SP_ALL_STATS: // [Valaris] + if(sd->state.lr_flag!=2) { + sd->parame[SP_STR-SP_STR]+=val; + sd->parame[SP_AGI-SP_STR]+=val; + sd->parame[SP_VIT-SP_STR]+=val; + sd->parame[SP_INT-SP_STR]+=val; + sd->parame[SP_DEX-SP_STR]+=val; + sd->parame[SP_LUK-SP_STR]+=val; + clif_updatestatus(sd,13); + clif_updatestatus(sd,14); + clif_updatestatus(sd,15); + clif_updatestatus(sd,16); + clif_updatestatus(sd,17); + clif_updatestatus(sd,18); + } + break; + case SP_AGI_VIT: // [Valaris] + if(sd->state.lr_flag!=2) { + sd->parame[SP_AGI-SP_STR]+=val; + sd->parame[SP_VIT-SP_STR]+=val; + clif_updatestatus(sd,14); + clif_updatestatus(sd,15); + } + break; + case SP_AGI_DEX_STR: // [Valaris] + if(sd->state.lr_flag!=2) { + sd->parame[SP_AGI-SP_STR]+=val; + sd->parame[SP_DEX-SP_STR]+=val; + sd->parame[SP_STR-SP_STR]+=val; + clif_updatestatus(sd,14); + clif_updatestatus(sd,17); + clif_updatestatus(sd,13); + } + break; + case SP_PERFECT_HIDE: // [Valaris] + if(sd->state.lr_flag!=2) { + sd->perfect_hiding=1; + } + break; + case SP_DISGUISE: // Disguise script for items [Valaris] + if(sd->state.lr_flag!=2 && sd->disguiseflag==0) { + if(pc_isriding(sd)) { // temporary prevention of crash caused by peco + disguise, will look into a better solution [Valaris] + clif_displaymessage(sd->fd, "Cannot wear disguise when riding a Peco."); + break; + } + sd->disguise=val; + clif_clearchar(&sd->bl, 9); + pc_setpos(sd, sd->mapname, sd->bl.x, sd->bl.y, 3); + } + break; + case SP_UNBREAKABLE: + if(sd->state.lr_flag!=2) { + sd->unbreakable += val; + } + break; + default: + if(battle_config.error_log) + printf("pc_bonus: unknown type %d %d !\n",type,val); + break; + } + return 0; +} + +/*========================================== + * 装 備品による能力等のボーナス設定 + *------------------------------------------ + */ +int pc_bonus2(struct map_session_data *sd,int type,int type2,int val) +{ + int i; + + nullpo_retr(0, sd); + + switch(type){ + case SP_ADDELE: + if(!sd->state.lr_flag) + sd->addele[type2]+=val; + else if(sd->state.lr_flag == 1) + sd->addele_[type2]+=val; + else if(sd->state.lr_flag == 2) + sd->arrow_addele[type2]+=val; + break; + case SP_ADDRACE: + if(!sd->state.lr_flag) + sd->addrace[type2]+=val; + else if(sd->state.lr_flag == 1) + sd->addrace_[type2]+=val; + else if(sd->state.lr_flag == 2) + sd->arrow_addrace[type2]+=val; + break; + case SP_ADDSIZE: + if(!sd->state.lr_flag) + sd->addsize[type2]+=val; + else if(sd->state.lr_flag == 1) + sd->addsize_[type2]+=val; + else if(sd->state.lr_flag == 2) + sd->arrow_addsize[type2]+=val; + break; + case SP_SUBELE: + if(sd->state.lr_flag != 2) + sd->subele[type2]+=val; + break; + case SP_SUBRACE: + if(sd->state.lr_flag != 2) + sd->subrace[type2]+=val; + break; + case SP_ADDEFF: + if(sd->state.lr_flag != 2) + sd->addeff[type2]+=val; + else + sd->arrow_addeff[type2]+=val; + break; + case SP_ADDEFF2: + if(sd->state.lr_flag != 2) + sd->addeff2[type2]+=val; + else + sd->arrow_addeff2[type2]+=val; + break; + case SP_RESEFF: + if(sd->state.lr_flag != 2) + sd->reseff[type2]+=val; + break; + case SP_MAGIC_ADDELE: + if(sd->state.lr_flag != 2) + sd->magic_addele[type2]+=val; + break; + case SP_MAGIC_ADDRACE: + if(sd->state.lr_flag != 2) + sd->magic_addrace[type2]+=val; + break; + case SP_MAGIC_SUBRACE: + if(sd->state.lr_flag != 2) + sd->magic_subrace[type2]+=val; + break; + case SP_ADD_DAMAGE_CLASS: + if(!sd->state.lr_flag) { + for(i=0;i<sd->add_damage_class_count;i++) { + if(sd->add_damage_classid[i] == type2) { + sd->add_damage_classrate[i] += val; + break; + } + } + if(i >= sd->add_damage_class_count && sd->add_damage_class_count < 10) { + sd->add_damage_classid[sd->add_damage_class_count] = type2; + sd->add_damage_classrate[sd->add_damage_class_count] += val; + sd->add_damage_class_count++; + } + } + else if(sd->state.lr_flag == 1) { + for(i=0;i<sd->add_damage_class_count_;i++) { + if(sd->add_damage_classid_[i] == type2) { + sd->add_damage_classrate_[i] += val; + break; + } + } + if(i >= sd->add_damage_class_count_ && sd->add_damage_class_count_ < 10) { + sd->add_damage_classid_[sd->add_damage_class_count_] = type2; + sd->add_damage_classrate_[sd->add_damage_class_count_] += val; + sd->add_damage_class_count_++; + } + } + break; + case SP_ADD_MAGIC_DAMAGE_CLASS: + if(sd->state.lr_flag != 2) { + for(i=0;i<sd->add_magic_damage_class_count;i++) { + if(sd->add_magic_damage_classid[i] == type2) { + sd->add_magic_damage_classrate[i] += val; + break; + } + } + if(i >= sd->add_magic_damage_class_count && sd->add_magic_damage_class_count < 10) { + sd->add_magic_damage_classid[sd->add_magic_damage_class_count] = type2; + sd->add_magic_damage_classrate[sd->add_magic_damage_class_count] += val; + sd->add_magic_damage_class_count++; + } + } + break; + case SP_ADD_DEF_CLASS: + if(sd->state.lr_flag != 2) { + for(i=0;i<sd->add_def_class_count;i++) { + if(sd->add_def_classid[i] == type2) { + sd->add_def_classrate[i] += val; + break; + } + } + if(i >= sd->add_def_class_count && sd->add_def_class_count < 10) { + sd->add_def_classid[sd->add_def_class_count] = type2; + sd->add_def_classrate[sd->add_def_class_count] += val; + sd->add_def_class_count++; + } + } + break; + case SP_ADD_MDEF_CLASS: + if(sd->state.lr_flag != 2) { + for(i=0;i<sd->add_mdef_class_count;i++) { + if(sd->add_mdef_classid[i] == type2) { + sd->add_mdef_classrate[i] += val; + break; + } + } + if(i >= sd->add_mdef_class_count && sd->add_mdef_class_count < 10) { + sd->add_mdef_classid[sd->add_mdef_class_count] = type2; + sd->add_mdef_classrate[sd->add_mdef_class_count] += val; + sd->add_mdef_class_count++; + } + } + break; + case SP_HP_DRAIN_RATE: + if(!sd->state.lr_flag) { + sd->hp_drain_rate += type2; + sd->hp_drain_per += val; + } + else if(sd->state.lr_flag == 1) { + sd->hp_drain_rate_ += type2; + sd->hp_drain_per_ += val; + } + break; + case SP_SP_DRAIN_RATE: + if(!sd->state.lr_flag) { + sd->sp_drain_rate += type2; + sd->sp_drain_per += val; + } + else if(sd->state.lr_flag == 1) { + sd->sp_drain_rate_ += type2; + sd->sp_drain_per_ += val; + } + break; + case SP_WEAPON_COMA_ELE: + if(sd->state.lr_flag != 2) + sd->weapon_coma_ele[type2] += val; + break; + case SP_WEAPON_COMA_RACE: + if(sd->state.lr_flag != 2) + sd->weapon_coma_race[type2] += val; + break; + case SP_RANDOM_ATTACK_INCREASE: // [Valaris] + if(sd->state.lr_flag !=2){ + sd->random_attack_increase_add = type2; + sd->random_attack_increase_per += val; + break; + } // end addition + default: + if(battle_config.error_log) + printf("pc_bonus2: unknown type %d %d %d!\n",type,type2,val); + break; + } + return 0; +} + +int pc_bonus3(struct map_session_data *sd,int type,int type2,int type3,int val) +{ + int i; + switch(type){ + case SP_ADD_MONSTER_DROP_ITEM: + if(sd->state.lr_flag != 2) { + for(i=0;i<sd->monster_drop_item_count;i++) { + if(sd->monster_drop_itemid[i] == type2) { + sd->monster_drop_race[i] |= 1<<type3; + if(sd->monster_drop_itemrate[i] < val) + sd->monster_drop_itemrate[i] = val; + break; + } + } + if(i >= sd->monster_drop_item_count && sd->monster_drop_item_count < 10) { + sd->monster_drop_itemid[sd->monster_drop_item_count] = type2; + sd->monster_drop_race[sd->monster_drop_item_count] |= 1<<type3; + sd->monster_drop_itemrate[sd->monster_drop_item_count] = val; + sd->monster_drop_item_count++; + } + } + break; + case SP_AUTOSPELL: + if(sd->state.lr_flag != 2){ + sd->autospell_id = type2; + sd->autospell_lv = type3; + sd->autospell_rate = val; + } + break; + default: + if(battle_config.error_log) + printf("pc_bonus3: unknown type %d %d %d %d!\n",type,type2,type3,val); + break; + } + + return 0; +} + +/*========================================== + * スクリプトによるスキル所得 + *------------------------------------------ + */ +int pc_skill(struct map_session_data *sd,int id,int level,int flag) +{ + nullpo_retr(0, sd); + + if(level>MAX_SKILL_LEVEL){ + if(battle_config.error_log) + printf("support card skill only!\n"); + return 0; + } + if(!flag && (sd->status.skill[id].id == id || level == 0)){ // クエスト所得ならここで条件を確認して送信する + sd->status.skill[id].lv=level; + pc_calcstatus(sd,0); + clif_skillinfoblock(sd); + } + else if(sd->status.skill[id].lv < level){ // 覚えられるがlvが小さいなら + if(sd->status.skill[id].id==id) + sd->status.skill[id].flag=sd->status.skill[id].lv+2; // lvを記憶 + else { + sd->status.skill[id].id=id; + sd->status.skill[id].flag=1; // cardスキルとする + } + sd->status.skill[id].lv=level; + } + + return 0; +} + +/*========================================== + * カード挿入 + *------------------------------------------ + */ +int pc_insert_card(struct map_session_data *sd,int idx_card,int idx_equip) +{ + nullpo_retr(0, sd); + + if(idx_card >= 0 && idx_card < MAX_INVENTORY && idx_equip >= 0 && idx_equip < MAX_INVENTORY && sd->inventory_data[idx_card]) { + int i; + int nameid=sd->status.inventory[idx_equip].nameid; + int cardid=sd->status.inventory[idx_card].nameid; + int ep=sd->inventory_data[idx_card]->equip; + + if( nameid <= 0 || sd->inventory_data[idx_equip] == NULL || + (sd->inventory_data[idx_equip]->type!=4 && sd->inventory_data[idx_equip]->type!=5)|| // 装 備じゃない + ( sd->status.inventory[idx_equip].identify==0 ) || // 未鑑定 + ( sd->status.inventory[idx_equip].card[0]==0x00ff) || // 製造武器 + ( sd->status.inventory[idx_equip].card[0]==0x00fe) || + ( (sd->inventory_data[idx_equip]->equip&ep)==0 ) || // 装 備個所違い + ( sd->inventory_data[idx_equip]->type==4 && ep==32) || // 両 手武器と盾カード + ( sd->status.inventory[idx_equip].card[0]==(short)0xff00) || sd->status.inventory[idx_equip].equip){ + + clif_insert_card(sd,idx_equip,idx_card,1); + return 0; + } + for(i=0;i<sd->inventory_data[idx_equip]->slot;i++){ + if( sd->status.inventory[idx_equip].card[i] == 0){ + // 空きスロットがあったので差し込む + sd->status.inventory[idx_equip].card[i]=cardid; + + // カードは減らす + clif_insert_card(sd,idx_equip,idx_card,0); + pc_delitem(sd,idx_card,1,1); + return 0; + } + } + } + else + clif_insert_card(sd,idx_equip,idx_card,1); + + return 0; +} + +// +// アイテム物 +// + +/*========================================== + * スキルによる買い値修正 + *------------------------------------------ + */ +int pc_modifybuyvalue(struct map_session_data *sd,int orig_value) +{ + int skill,val = orig_value,rate1 = 0,rate2 = 0; + if((skill=pc_checkskill(sd,MC_DISCOUNT))>0) // ディスカウント + rate1 = 5+skill*2-((skill==10)? 1:0); + if((skill=pc_checkskill(sd,RG_COMPULSION))>0) // コムパルションディスカウント + rate2 = 5+skill*4; + if(rate1 < rate2) rate1 = rate2; + if(rate1) + val = (int)((double)orig_value*(double)(100-rate1)/100.); + if(val < 0) val = 0; + if(orig_value > 0 && val < 1) val = 1; + + return val; +} + +/*========================================== + * スキルによる売り値修正 + *------------------------------------------ + */ +int pc_modifysellvalue(struct map_session_data *sd,int orig_value) +{ + int skill,val = orig_value,rate = 0; + if((skill=pc_checkskill(sd,MC_OVERCHARGE))>0) // オーバーチャージ + rate = 5+skill*2-((skill==10)? 1:0); + if(rate) + val = (int)((double)orig_value*(double)(100+rate)/100.); + if(val < 0) val = 0; + if(orig_value > 0 && val < 1) val = 1; + + return val; +} + +/*========================================== + * アイテムを買った時に、新しいアイテム欄を使うか、 + * 3万個制限にかかるか確認 + *------------------------------------------ + */ +int pc_checkadditem(struct map_session_data *sd,int nameid,int amount) +{ + int i; + + nullpo_retr(0, sd); + + if(itemdb_isequip(nameid)) + return ADDITEM_NEW; + + for(i=0;i<MAX_INVENTORY;i++){ + if(sd->status.inventory[i].nameid==nameid){ + if(sd->status.inventory[i].amount+amount > MAX_AMOUNT) + return ADDITEM_OVERAMOUNT; + return ADDITEM_EXIST; + } + } + + if(amount > MAX_AMOUNT) + return ADDITEM_OVERAMOUNT; + return ADDITEM_NEW; +} + +/*========================================== + * 空きアイテム欄の個数 + *------------------------------------------ + */ +int pc_inventoryblank(struct map_session_data *sd) +{ + int i,b; + + nullpo_retr(0, sd); + + for(i=0,b=0;i<MAX_INVENTORY;i++){ + if(sd->status.inventory[i].nameid==0) + b++; + } + + return b; +} + +/*========================================== + * お金を払う + *------------------------------------------ + */ +int pc_payzeny(struct map_session_data *sd,int zeny) +{ + double z; + + nullpo_retr(0, sd); + + z = (double)sd->status.zeny; + if(sd->status.zeny<zeny || z - (double)zeny > MAX_ZENY) + return 1; + sd->status.zeny-=zeny; + clif_updatestatus(sd,SP_ZENY); + + return 0; +} + +/*========================================== + * お金を得る + *------------------------------------------ + */ +int pc_getzeny(struct map_session_data *sd,int zeny) +{ + double z; + + nullpo_retr(0, sd); + + z = (double)sd->status.zeny; + if(z + (double)zeny > MAX_ZENY) { + zeny = 0; + sd->status.zeny = MAX_ZENY; + } + sd->status.zeny+=zeny; + clif_updatestatus(sd,SP_ZENY); + + return 0; +} + +/*========================================== + * アイテムを探して、インデックスを返す + *------------------------------------------ + */ +int pc_search_inventory(struct map_session_data *sd,int item_id) +{ + int i; + + nullpo_retr(-1, sd); + + for(i=0;i<MAX_INVENTORY;i++) { + if(sd->status.inventory[i].nameid == item_id && + (sd->status.inventory[i].amount > 0 || item_id == 0)) + return i; + } + + return -1; +} + +/*========================================== + * アイテム追加。個数のみitem構造体の数字を無視 + *------------------------------------------ + */ +int pc_additem(struct map_session_data *sd,struct item *item_data,int amount) +{ + struct item_data *data; + int i,w; + + nullpo_retr(1, sd); + nullpo_retr(1, item_data); + + if(item_data->nameid <= 0 || amount <= 0) + return 1; + data = itemdb_search(item_data->nameid); + if((w = data->weight*amount) + sd->weight > sd->max_weight) + return 2; + + i = MAX_INVENTORY; + + if(!itemdb_isequip2(data)){ + // 装 備品ではないので、既所有品なら個数のみ変化させる + for(i=0;i<MAX_INVENTORY;i++) + if(sd->status.inventory[i].nameid == item_data->nameid && + sd->status.inventory[i].card[0] == item_data->card[0] && sd->status.inventory[i].card[1] == item_data->card[1] && + sd->status.inventory[i].card[2] == item_data->card[2] && sd->status.inventory[i].card[3] == item_data->card[3]) { + if(sd->status.inventory[i].amount+amount > MAX_AMOUNT) + return 5; + sd->status.inventory[i].amount+=amount; + clif_additem(sd,i,amount,0); + break; + } + } + if(i >= MAX_INVENTORY){ + // 装 備品か未所有品だったので空き欄へ追加 + i = pc_search_inventory(sd,0); + if(i >= 0) { + memcpy(&sd->status.inventory[i],item_data,sizeof(sd->status.inventory[0])); + sd->status.inventory[i].amount=amount; + sd->inventory_data[i]=data; + clif_additem(sd,i,amount,0); + } + else return 4; + } + sd->weight += w; + clif_updatestatus(sd,SP_WEIGHT); + + return 0; +} + +/*========================================== + * アイテムを減らす + *------------------------------------------ + */ +int pc_delitem(struct map_session_data *sd,int n,int amount,int type) +{ + nullpo_retr(1, sd); + + if(sd->status.inventory[n].nameid==0 || amount <= 0 || sd->status.inventory[n].amount<amount || sd->inventory_data[n] == NULL) + return 1; + + sd->status.inventory[n].amount -= amount; + sd->weight -= sd->inventory_data[n]->weight*amount ; + if(sd->status.inventory[n].amount<=0){ + if(sd->status.inventory[n].equip) + pc_unequipitem(sd,n,0); + memset(&sd->status.inventory[n],0,sizeof(sd->status.inventory[0])); + sd->inventory_data[n] = NULL; + } + if(!(type&1)) + clif_delitem(sd,n,amount); + if(!(type&2)) + clif_updatestatus(sd,SP_WEIGHT); + + return 0; +} + +/*========================================== + * アイテムを落す + *------------------------------------------ + */ +int pc_dropitem(struct map_session_data *sd,int n,int amount) +{ + nullpo_retr(1, sd); + + if (sd->status.inventory[n].nameid <= 0 || + sd->status.inventory[n].amount < amount || + sd->trade_partner != 0 || sd->vender_id != 0 || + sd->status.inventory[n].amount <= 0) + return 1; + map_addflooritem(&sd->status.inventory[n], amount, sd->bl.m, sd->bl.x, sd->bl.y, NULL, NULL, NULL, 0); + pc_delitem(sd, n, amount, 0); + + return 0; +} + +/*========================================== + * アイテムを拾う + *------------------------------------------ + */ +int pc_takeitem(struct map_session_data *sd,struct flooritem_data *fitem) +{ + int flag; + unsigned int tick = gettick(); + struct map_session_data *first_sd = NULL,*second_sd = NULL,*third_sd = NULL; + + nullpo_retr(0, sd); + nullpo_retr(0, fitem); + + if(fitem->first_get_id > 0) { + first_sd = map_id2sd(fitem->first_get_id); + if(tick < fitem->first_get_tick) { + if(fitem->first_get_id != sd->bl.id && !(first_sd && first_sd->status.party_id == sd->status.party_id)) { + clif_additem(sd,0,0,6); + return 0; + } + } + else if(fitem->second_get_id > 0) { + second_sd = map_id2sd(fitem->second_get_id); + if(tick < fitem->second_get_tick) { + if(fitem->first_get_id != sd->bl.id && fitem->second_get_id != sd->bl.id && + !(first_sd && first_sd->status.party_id == sd->status.party_id) && !(second_sd && second_sd->status.party_id == sd->status.party_id)) { + clif_additem(sd,0,0,6); + return 0; + } + } + else if(fitem->third_get_id > 0) { + third_sd = map_id2sd(fitem->third_get_id); + if(tick < fitem->third_get_tick) { + if(fitem->first_get_id != sd->bl.id && fitem->second_get_id != sd->bl.id && fitem->third_get_id != sd->bl.id && + !(first_sd && first_sd->status.party_id == sd->status.party_id) && !(second_sd && second_sd->status.party_id == sd->status.party_id) && + !(third_sd && third_sd->status.party_id == sd->status.party_id)) { + clif_additem(sd,0,0,6); + return 0; + } + } + } + } + } + if((flag = pc_additem(sd,&fitem->item_data,fitem->item_data.amount))) + // 重量overで取得失敗 + clif_additem(sd,0,0,flag); + else { + /* 取得成功 */ + if(sd->attacktimer != -1) + pc_stopattack(sd); + clif_takeitem(&sd->bl,&fitem->bl); + map_clearflooritem(fitem->bl.id); + } + return 0; +} + +int pc_isUseitem(struct map_session_data *sd,int n) +{ + struct item_data *item; + int nameid; + + nullpo_retr(0, sd); + + item = sd->inventory_data[n]; + nameid = sd->status.inventory[n].nameid; + + if(item == NULL) + return 0; + if((nameid == 605) && map[sd->bl.m].flag.gvg) + return 0; + if(nameid == 601 && (map[sd->bl.m].flag.noteleport || map[sd->bl.m].flag.gvg)) { + clif_skill_teleportmessage(sd,0); + return 0; + } + if(nameid == 602 && map[sd->bl.m].flag.noreturn) + return 0; + if(nameid == 604 && (map[sd->bl.m].flag.nobranch || map[sd->bl.m].flag.gvg)) + return 0; + if(item->sex != 2 && sd->status.sex != item->sex) + return 0; + if(item->elv > 0 && sd->status.base_level < item->elv) + return 0; + if(((sd->status.class==13 || sd->status.class==4014) && ((1<<7)&item->class) == 0) || // have mounted classes use unmounted items [Valaris] + ((sd->status.class==21 || sd->status.class==4022) && ((1<<14)&item->class) == 0)) + return 0; + if(sd->status.class!=13 && sd->status.class!=4014 && sd->status.class!=21 && sd->status.class!=4022) + if((sd->status.class<=4000 && ((1<<sd->status.class)&item->class) == 0) || (sd->status.class>4000 && sd->status.class<4023 && ((1<<(sd->status.class-4001))&item->class) == 0) || + (sd->status.class>=4023 && ((1<<(sd->status.class-4023))&item->class) == 0)) + return 0; + return 1; +} + +/*========================================== + * アイテムを使う + *------------------------------------------ + */ +int pc_useitem(struct map_session_data *sd,int n) +{ + int nameid,amount; + + nullpo_retr(1, sd); + + if(n >=0 && n < MAX_INVENTORY) { + nameid = sd->status.inventory[n].nameid; + amount = sd->status.inventory[n].amount; + if(sd->status.inventory[n].nameid <= 0 || + sd->status.inventory[n].amount <= 0 || + sd->sc_data[SC_BERSERK].timer!=-1 || + !pc_isUseitem(sd,n) ) { + clif_useitemack(sd,n,0,0); + return 1; + } + if(sd->inventory_data[n]) + run_script(sd->inventory_data[n]->use_script,0,sd->bl.id,0); + + clif_useitemack(sd,n,amount-1,1); + pc_delitem(sd,n,1,1); + } + + return 0; +} + +/*========================================== + * カートアイテム追加。個数のみitem構造体の数字を無視 + *------------------------------------------ + */ +int pc_cart_additem(struct map_session_data *sd,struct item *item_data,int amount) +{ + struct item_data *data; + int i,w; + + nullpo_retr(1, sd); + nullpo_retr(1, item_data); + + if(item_data->nameid <= 0 || amount <= 0) + return 1; + data = itemdb_search(item_data->nameid); + + if((w=data->weight*amount) + sd->cart_weight > sd->cart_max_weight) + return 1; + + i=MAX_CART; + if(!itemdb_isequip2(data)){ + // 装 備品ではないので、既所有品なら個数のみ変化させる + for(i=0;i<MAX_CART;i++){ + if(sd->status.cart[i].nameid==item_data->nameid && + sd->status.cart[i].card[0] == item_data->card[0] && sd->status.cart[i].card[1] == item_data->card[1] && + sd->status.cart[i].card[2] == item_data->card[2] && sd->status.cart[i].card[3] == item_data->card[3]){ + if(sd->status.cart[i].amount+amount > MAX_AMOUNT) + return 1; + sd->status.cart[i].amount+=amount; + clif_cart_additem(sd,i,amount,0); + break; + } + } + } + if(i >= MAX_CART){ + // 装 備品か未所有品だったので空き欄へ追加 + for(i=0;i<MAX_CART;i++){ + if(sd->status.cart[i].nameid==0){ + memcpy(&sd->status.cart[i],item_data,sizeof(sd->status.cart[0])); + sd->status.cart[i].amount=amount; + sd->cart_num++; + clif_cart_additem(sd,i,amount,0); + break; + } + } + if(i >= MAX_CART) + return 1; + } + sd->cart_weight += w; + clif_updatestatus(sd,SP_CARTINFO); + + return 0; +} + +/*========================================== + * カートアイテムを減らす + *------------------------------------------ + */ +int pc_cart_delitem(struct map_session_data *sd,int n,int amount,int type) +{ + nullpo_retr(1, sd); + + if(sd->status.cart[n].nameid==0 || + sd->status.cart[n].amount<amount) + return 1; + + sd->status.cart[n].amount -= amount; + sd->cart_weight -= itemdb_weight(sd->status.cart[n].nameid)*amount ; + if(sd->status.cart[n].amount <= 0){ + memset(&sd->status.cart[n],0,sizeof(sd->status.cart[0])); + sd->cart_num--; + } + if(!type) { + clif_cart_delitem(sd,n,amount); + clif_updatestatus(sd,SP_CARTINFO); + } + + return 0; +} + +/*========================================== + * カートへアイテム移動 + *------------------------------------------ + */ +int pc_putitemtocart(struct map_session_data *sd,int idx,int amount) { + struct item *item_data; + + nullpo_retr(0, sd); + nullpo_retr(0, item_data = &sd->status.inventory[idx]); + + if (item_data->nameid==0 || item_data->amount<amount || sd->vender_id) + return 1; + if (pc_cart_additem(sd,item_data,amount) == 0) + return pc_delitem(sd,idx,amount,0); + + return 1; +} + +/*========================================== + * カート内のアイテム数確認(個数の差分を返す) + *------------------------------------------ + */ +int pc_cartitem_amount(struct map_session_data *sd,int idx,int amount) +{ + struct item *item_data; + + nullpo_retr(-1, sd); + nullpo_retr(-1, item_data=&sd->status.cart[idx]); + + if( item_data->nameid==0 || !item_data->amount) + return -1; + return item_data->amount-amount; +} +/*========================================== + * カートからアイテム移動 + *------------------------------------------ + */ + +int pc_getitemfromcart(struct map_session_data *sd,int idx,int amount) +{ + struct item *item_data; + int flag; + + nullpo_retr(0, sd); + nullpo_retr(0, item_data=&sd->status.cart[idx]); + + if( item_data->nameid==0 || item_data->amount<amount || sd->vender_id ) + return 1; + if((flag = pc_additem(sd,item_data,amount)) == 0) + return pc_cart_delitem(sd,idx,amount,0); + + clif_additem(sd,0,0,flag); + return 1; +} + +/*========================================== + * アイテム鑑定 + *------------------------------------------ + */ +int pc_item_identify(struct map_session_data *sd,int idx) +{ + int flag=1; + + nullpo_retr(0, sd); + + if(idx >= 0 && idx < MAX_INVENTORY) { + if(sd->status.inventory[idx].nameid > 0 && sd->status.inventory[idx].identify == 0 ){ + flag=0; + sd->status.inventory[idx].identify=1; + } + clif_item_identified(sd,idx,flag); + } + else + clif_item_identified(sd,idx,flag); + + return !flag; +} + +/*========================================== + * スティル品公開 + *------------------------------------------ + */ +int pc_show_steal(struct block_list *bl,va_list ap) +{ + struct map_session_data *sd; + int itemid; + int type; + + struct item_data *item=NULL; + char output[100]; + + nullpo_retr(0, bl); + nullpo_retr(0, ap); + nullpo_retr(0, sd=va_arg(ap,struct map_session_data *)); + + itemid=va_arg(ap,int); + type=va_arg(ap,int); + + if(!type){ + if((item=itemdb_exists(itemid))==NULL) + sprintf(output,"%s stole an Unknown_Item.",sd->status.name); + else + sprintf(output,"%s stole %s.",sd->status.name,item->jname); + clif_displaymessage( ((struct map_session_data *)bl)->fd, output); + }else{ + sprintf(output,"%s has not stolen the item because of being overweight.",sd->status.name); + clif_displaymessage( ((struct map_session_data *)bl)->fd, output); + } + + return 0; +} +/*========================================== + * + *------------------------------------------ + */ +//** pc.c: Small Steal Item fix by fritz +int pc_steal_item(struct map_session_data *sd,struct block_list *bl) +{ + if(sd != NULL && bl != NULL && bl->type == BL_MOB) { + int i,skill,rate,itemid,flag, count; + struct mob_data *md; + md=(struct mob_data *)bl; + if(!md->state.steal_flag && mob_db[md->class].mexp <= 0 && !(mob_db[md->class].mode&0x20) && md->sc_data[SC_STONE].timer == -1 && md->sc_data[SC_FREEZE].timer == -1 && + (!(md->class>1324 && md->class<1364))) // prevent stealing from treasure boxes [Valaris] + { + skill = sd->paramc[4] - mob_db[md->class].dex + pc_checkskill(sd,TF_STEAL) + 10; + + if(0 < skill) + { + for(count = 8; count <= 8 && count != 0; count--) + { + i = rand()%8; + itemid = mob_db[md->class].dropitem[i].nameid; + + if(itemid > 0 && itemdb_type(itemid) != 6) + { + rate = (mob_db[md->class].dropitem[i].p / battle_config.item_rate_common * 100 * skill)/100; + + if(rand()%10000 < rate) + { + struct item tmp_item; + memset(&tmp_item,0,sizeof(tmp_item)); + tmp_item.nameid = itemid; + tmp_item.amount = 1; + tmp_item.identify = 1; + flag = pc_additem(sd,&tmp_item,1); + if(battle_config.show_steal_in_same_party) + { + party_foreachsamemap(pc_show_steal,sd,1,sd,tmp_item.nameid,0); + } + + if(flag) + { + if(battle_config.show_steal_in_same_party) + { + party_foreachsamemap(pc_show_steal,sd,1,sd,tmp_item.nameid,1); + } + + clif_additem(sd,0,0,flag); + } + md->state.steal_flag = 1; + return 1; + } + } + } + } + } + } + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int pc_steal_coin(struct map_session_data *sd,struct block_list *bl) +{ + if(sd != NULL && bl != NULL && bl->type == BL_MOB) { + int rate,skill; + struct mob_data *md=(struct mob_data *)bl; + if(md && !md->state.steal_coin_flag && md->sc_data && md->sc_data[SC_STONE].timer == -1 && md->sc_data[SC_FREEZE].timer == -1) { + skill = pc_checkskill(sd,RG_STEALCOIN)*10; + rate = skill + (sd->status.base_level - mob_db[md->class].lv)*3 + sd->paramc[4]*2 + sd->paramc[5]*2; + if(rand()%1000 < rate) { + pc_getzeny(sd,mob_db[md->class].lv*10 + rand()%100); + md->state.steal_coin_flag = 1; + return 1; + } + } + } + + return 0; +} +// +// +// +/*========================================== + * PCの位置設定 + *------------------------------------------ + */ +int pc_setpos(struct map_session_data *sd,char *mapname_org,int x,int y,int clrtype) +{ + char mapname[24]; + int m=0,c=0,disguise=0; + + nullpo_retr(0, sd); + + if(sd->chatID) // チャットから出る + chat_leavechat(sd); + if(sd->trade_partner) // 取引を中断する + trade_tradecancel(sd); + if(sd->state.storage_flag) + storage_guild_storage_quit(sd,0); + else + storage_storage_quit(sd); // 倉庫を開いてるなら保存する + + if(sd->party_invite>0) // パーティ勧誘を拒否する + party_reply_invite(sd,sd->party_invite_account,0); + if(sd->guild_invite>0) // ギルド勧誘を拒否する + guild_reply_invite(sd,sd->guild_invite,0); + if(sd->guild_alliance>0) // ギルド同盟勧誘を拒否する + guild_reply_reqalliance(sd,sd->guild_alliance_account,0); + + skill_castcancel(&sd->bl,0); // 詠唱中断 + pc_stop_walking(sd,0); // 歩行中断 + pc_stopattack(sd); // 攻撃中断 + + if(pc_issit(sd)) { + pc_setstand(sd); + skill_gangsterparadise(sd,0); + } + + if(sd->sc_data[SC_TRICKDEAD].timer != -1) + skill_status_change_end(&sd->bl, SC_TRICKDEAD, -1); + if(sd->status.option&2) + skill_status_change_end(&sd->bl, SC_HIDING, -1); + if(sd->status.option&4) + skill_status_change_end(&sd->bl, SC_CLOAKING, -1); + if(sd->status.option&16386) + skill_status_change_end(&sd->bl, SC_CHASEWALK, -1); + if(sd->sc_data[SC_BLADESTOP].timer!=-1) + skill_status_change_end(&sd->bl,SC_BLADESTOP,-1); + if(sd->sc_data[SC_DANCING].timer!=-1) // clear dance effect when warping [Valaris] + skill_stop_dancing(&sd->bl,0); + + if(sd->status.pet_id > 0 && sd->pd && sd->pet.intimate > 0) { + pet_stopattack(sd->pd); + pet_changestate(sd->pd,MS_IDLE,0); + } + + if(sd->disguise) { // clear disguises when warping [Valaris] + clif_clearchar(&sd->bl, 9); + disguise=sd->disguise; + sd->disguise=0; + } + + memcpy(mapname,mapname_org,24); + mapname[16]=0; + if(strstr(mapname,".gat")==NULL && strlen(mapname)<16){ + strcat(mapname,".gat"); + } + + m=map_mapname2mapid(mapname); + if(m<0){ + if(sd->mapname[0]){ + int ip,port; + if(map_mapname2ipport(mapname,&ip,&port)==0){ + skill_stop_dancing(&sd->bl,1); + skill_unit_out_all(&sd->bl,gettick(),1); + clif_clearchar_area(&sd->bl,clrtype&0xffff); + skill_gangsterparadise(sd,0); + map_delblock(&sd->bl); + if(sd->status.pet_id > 0 && sd->pd) { + if(sd->pd->bl.m != m && sd->pet.intimate <= 0) { + pet_remove_map(sd); + intif_delete_petdata(sd->status.pet_id); + sd->status.pet_id = 0; + sd->pd = NULL; + sd->petDB = NULL; + if(battle_config.pet_status_support) + pc_calcstatus(sd,2); + } + else if(sd->pet.intimate > 0) { + pet_stopattack(sd->pd); + pet_changestate(sd->pd,MS_IDLE,0); + clif_clearchar_area(&sd->pd->bl,clrtype&0xffff); + map_delblock(&sd->pd->bl); + } + } + memcpy(sd->mapname,mapname,24); + sd->bl.x=x; + sd->bl.y=y; + sd->state.waitingdisconnect=1; + pc_makesavestatus(sd); + if(sd->status.pet_id > 0 && sd->pd) + intif_save_petdata(sd->status.account_id,&sd->pet); + chrif_save(sd); + storage_storage_save(sd); + chrif_changemapserver(sd, mapname, x, y, ip, port); + return 0; + } + } +#if 0 + clif_authfail_fd(sd->fd,0); // cancel + clif_setwaitclose(sd->fd); +#endif + return 1; + } + + if(x <0 || x >= map[m].xs || y <0 || y >= map[m].ys) + x=y=0; + if((x==0 && y==0) || (c=read_gat(m,x,y))==1 || c==5){ + if(x||y) { + if(battle_config.error_log) + printf("stacked (%d,%d)\n",x,y); + } + do { + x=rand()%(map[m].xs-2)+1; + y=rand()%(map[m].ys-2)+1; + } while((c=read_gat(m,x,y))==1 || c==5); + } + + if(sd->mapname[0] && sd->bl.prev != NULL){ + skill_unit_out_all(&sd->bl,gettick(),1); + clif_clearchar_area(&sd->bl,clrtype&0xffff); + skill_gangsterparadise(sd,0); + map_delblock(&sd->bl); + // pet + if(sd->status.pet_id > 0 && sd->pd) { + if(sd->pd->bl.m != m && sd->pet.intimate <= 0) { + pet_remove_map(sd); + intif_delete_petdata(sd->status.pet_id); + sd->status.pet_id = 0; + sd->pd = NULL; + sd->petDB = NULL; + if(battle_config.pet_status_support) + pc_calcstatus(sd,2); + pc_makesavestatus(sd); + chrif_save(sd); + storage_storage_save(sd); + } + else if(sd->pet.intimate > 0) { + pet_stopattack(sd->pd); + pet_changestate(sd->pd,MS_IDLE,0); + clif_clearchar_area(&sd->pd->bl,clrtype&0xffff); + map_delblock(&sd->pd->bl); + } + } + clif_changemap(sd,map[m].name,x,y); // [MouseJstr] + } + + if(disguise) // disguise teleport fix [Valaris] + sd->disguise=disguise; + + memcpy(sd->mapname,mapname,24); + sd->bl.m = m; + sd->to_x = x; + sd->to_y = y; + + // moved and changed dance effect stopping + + sd->bl.x = x; + sd->bl.y = y; + + if(sd->status.pet_id > 0 && sd->pd && sd->pet.intimate > 0) { + sd->pd->bl.m = m; + sd->pd->bl.x = sd->pd->to_x = x; + sd->pd->bl.y = sd->pd->to_y = y; + sd->pd->dir = sd->dir; + } + +// map_addblock(&sd->bl); /// ブロック登録とspawnは +// clif_spawnpc(sd); + + return 0; +} + +/*========================================== + * PCのランダムワープ + *------------------------------------------ + */ +int pc_randomwarp(struct map_session_data *sd, int type) { + int x,y,c,i=0; + int m; + + nullpo_retr(0, sd); + + m=sd->bl.m; + + if (map[sd->bl.m].flag.noteleport) // テレポート禁止 + return 0; + + do{ + x=rand()%(map[m].xs-2)+1; + y=rand()%(map[m].ys-2)+1; + } while (((c=read_gat(m,x,y)) == 1 || c == 5) && (i++) < 1000); + + if (i < 1000) + pc_setpos(sd,map[m].name,x,y,type); + + return 0; +} + +/*========================================== + * 現在位置のメモ + *------------------------------------------ + */ +int pc_memo(struct map_session_data *sd, int i) { + int skill; + int j; + + nullpo_retr(0, sd); + + skill = pc_checkskill(sd, AL_WARP); + + if (i >= MIN_PORTAL_MEMO) + i -= MIN_PORTAL_MEMO; + else if (map[sd->bl.m].flag.nomemo || (map[sd->bl.m].flag.nowarpto && battle_config.any_warp_GM_min_level > pc_isGM(sd))) { + clif_skill_teleportmessage(sd, 1); + return 0; + } + + if (skill < 1) { + clif_skill_memo(sd,2); + } + + if (skill < 2 || i < -1 || i > 2) { + clif_skill_memo(sd, 1); + return 0; + } + + for(j = 0 ; j < 3; j++) { + if (strcmp(sd->status.memo_point[j].map, map[sd->bl.m].name) == 0) { + i = j; + break; + } + } + + if (i == -1) { + for(i = skill - 3; i >= 0; i--) { + memcpy(&sd->status.memo_point[i+1],&sd->status.memo_point[i], + sizeof(struct point)); + } + i = 0; + } + memcpy(sd->status.memo_point[i].map, map[sd->bl.m].name, 24); + sd->status.memo_point[i].x = sd->bl.x; + sd->status.memo_point[i].y = sd->bl.y; + + clif_skill_memo(sd, 0); + + return 1; +} + +/*========================================== + * + *------------------------------------------ + */ +int pc_can_reach(struct map_session_data *sd,int x,int y) +{ + struct walkpath_data wpd; + + nullpo_retr(0, sd); + + if( sd->bl.x==x && sd->bl.y==y ) // 同じマス + return 1; + + // 障害物判定 + wpd.path_len=0; + wpd.path_pos=0; + wpd.path_half=0; + return (path_search(&wpd,sd->bl.m,sd->bl.x,sd->bl.y,x,y,0)!=-1)?1:0; +} + +// +// 歩 行物 +// +/*========================================== + * 次の1歩にかかる時間を計算 + *------------------------------------------ + */ +static int calc_next_walk_step(struct map_session_data *sd) +{ + nullpo_retr(0, sd); + + if(sd->walkpath.path_pos>=sd->walkpath.path_len) + return -1; + if(sd->walkpath.path[sd->walkpath.path_pos]&1) + return sd->speed*14/10; + + return sd->speed; +} + +/*========================================== + * 半歩進む(timer関数) + *------------------------------------------ + */ +static int pc_walk(int tid,unsigned int tick,int id,int data) +{ + struct map_session_data *sd; + int i,ctype; + int moveblock; + int x,y,dx,dy; + + sd=map_id2sd(id); + if(sd==NULL) + return 0; + + if(sd->walktimer != tid){ + if(battle_config.error_log) + printf("pc_walk %d != %d\n",sd->walktimer,tid); + return 0; + } + sd->walktimer=-1; + if(sd->walkpath.path_pos>=sd->walkpath.path_len || sd->walkpath.path_pos!=data) + return 0; + + //歩いたので息吹のタイマーを初期化 + sd->inchealspirithptick = 0; + sd->inchealspiritsptick = 0; + + sd->walkpath.path_half ^= 1; + if(sd->walkpath.path_half==0){ // マス目中心へ到着 + sd->walkpath.path_pos++; + if(sd->state.change_walk_target){ + pc_walktoxy_sub(sd); + return 0; + } + } else { // マス目境界へ到着 + if(sd->walkpath.path[sd->walkpath.path_pos]>=8) + return 1; + + x = sd->bl.x; + y = sd->bl.y; + ctype = map_getcell(sd->bl.m,x,y); + if(ctype == 1 || ctype == 5) { + pc_stop_walking(sd,1); + return 0; + } + sd->dir=sd->head_dir=sd->walkpath.path[sd->walkpath.path_pos]; + dx = dirx[(int)sd->dir]; + dy = diry[(int)sd->dir]; + ctype = map_getcell(sd->bl.m,x+dx,y+dy); + if(ctype == 1 || ctype == 5) { + pc_walktoxy_sub(sd); + return 0; + } + + moveblock = ( x/BLOCK_SIZE != (x+dx)/BLOCK_SIZE || y/BLOCK_SIZE != (y+dy)/BLOCK_SIZE); + + sd->walktimer = 1; + map_foreachinmovearea(clif_pcoutsight,sd->bl.m,x-AREA_SIZE,y-AREA_SIZE,x+AREA_SIZE,y+AREA_SIZE,dx,dy,0,sd); + + x += dx; + y += dy; + + if(moveblock) map_delblock(&sd->bl); + sd->bl.x = x; + sd->bl.y = y; + if(moveblock) map_addblock(&sd->bl); + + if(sd->sc_data[SC_DANCING].timer!=-1) + skill_unit_move_unit_group((struct skill_unit_group *)sd->sc_data[SC_DANCING].val2,sd->bl.m,dx,dy); + + map_foreachinmovearea(clif_pcinsight,sd->bl.m,x-AREA_SIZE,y-AREA_SIZE,x+AREA_SIZE,y+AREA_SIZE,-dx,-dy,0,sd); + sd->walktimer = -1; + + if(sd->status.party_id>0){ // パーティのHP情報通知検査 + struct party *p=party_search(sd->status.party_id); + if(p!=NULL){ + int p_flag=0; + map_foreachinmovearea(party_send_hp_check,sd->bl.m,x-AREA_SIZE,y-AREA_SIZE,x+AREA_SIZE,y+AREA_SIZE,-dx,-dy,BL_PC,sd->status.party_id,&p_flag); + if(p_flag) + sd->party_hp=-1; + } + } + if(sd->status.option&4) // クローキングの消滅検査 + skill_check_cloaking(&sd->bl); + /* ディボーション検査 */ + for(i=0;i<5;i++) + if(sd->dev.val1[i]){ + skill_devotion3(&sd->bl,sd->dev.val1[i]); + break; + } + /* 被ディボーション検査 */ + if( sd->sc_data && sd->sc_data[SC_DEVOTION].val1){ + skill_devotion2(&sd->bl,sd->sc_data[SC_DEVOTION].val1); + } + + skill_unit_move(&sd->bl,tick,1); // スキルユニットの検査 + + if(map_getcell(sd->bl.m,x,y)&0x80) + npc_touch_areanpc(sd,sd->bl.m,x,y); + else + sd->areanpc_id=0; + } + if((i=calc_next_walk_step(sd))>0) { + i = i>>1; + if(i < 1 && sd->walkpath.path_half == 0) + i = 1; + sd->walktimer=add_timer(tick+i,pc_walk,id,sd->walkpath.path_pos); + } + + return 0; +} + +/*========================================== + * 移動可能か確認して、可能なら歩行開始 + *------------------------------------------ + */ +static int pc_walktoxy_sub(struct map_session_data *sd) +{ + struct walkpath_data wpd; + int i; + + nullpo_retr(1, sd); + + if(path_search(&wpd,sd->bl.m,sd->bl.x,sd->bl.y,sd->to_x,sd->to_y,0)) + return 1; + memcpy(&sd->walkpath,&wpd,sizeof(wpd)); + + clif_walkok(sd); + sd->state.change_walk_target=0; + + if((i=calc_next_walk_step(sd))>0){ + i = i>>2; + sd->walktimer=add_timer(gettick()+i,pc_walk,sd->bl.id,0); + } + clif_movechar(sd); + + return 0; +} + +/*========================================== + * pc歩 行要求 + *------------------------------------------ + */ +int pc_walktoxy(struct map_session_data *sd,int x,int y) +{ + + nullpo_retr(0, sd); + + sd->to_x=x; + sd->to_y=y; + + if(sd->walktimer != -1 && sd->state.change_walk_target==0){ + // 現在歩いている最中の目的地変更なのでマス目の中心に来た時に + // timer関数からpc_walktoxy_subを呼ぶようにする + sd->state.change_walk_target=1; + } else { + pc_walktoxy_sub(sd); + } + + return 0; +} + +/*========================================== + * 歩 行停止 + *------------------------------------------ + */ +int pc_stop_walking(struct map_session_data *sd,int type) +{ + nullpo_retr(0, sd); + + if(sd->walktimer != -1) { + delete_timer(sd->walktimer,pc_walk); + sd->walktimer=-1; + } + sd->walkpath.path_len=0; + sd->to_x = sd->bl.x; + sd->to_y = sd->bl.y; + if(type&0x01) + clif_fixpos(&sd->bl); + if(type&0x02 && battle_config.pc_damage_delay) { + unsigned int tick = gettick(); + int delay = battle_get_dmotion(&sd->bl); + if(sd->canmove_tick < tick) + sd->canmove_tick = tick + delay; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int pc_movepos(struct map_session_data *sd,int dst_x,int dst_y) +{ + int moveblock; + int dx,dy,dist; + + struct walkpath_data wpd; + + nullpo_retr(0, sd); + + if(path_search(&wpd,sd->bl.m,sd->bl.x,sd->bl.y,dst_x,dst_y,0)) + return 1; + + sd->dir = sd->head_dir = map_calc_dir(&sd->bl, dst_x,dst_y); + + dx = dst_x - sd->bl.x; + dy = dst_y - sd->bl.y; + dist = distance(sd->bl.x,sd->bl.y,dst_x,dst_y); + + moveblock = ( sd->bl.x/BLOCK_SIZE != dst_x/BLOCK_SIZE || sd->bl.y/BLOCK_SIZE != dst_y/BLOCK_SIZE); + + map_foreachinmovearea(clif_pcoutsight,sd->bl.m,sd->bl.x-AREA_SIZE,sd->bl.y-AREA_SIZE,sd->bl.x+AREA_SIZE,sd->bl.y+AREA_SIZE,dx,dy,0,sd); + + if(moveblock) map_delblock(&sd->bl); + sd->bl.x = dst_x; + sd->bl.y = dst_y; + if(moveblock) map_addblock(&sd->bl); + + map_foreachinmovearea(clif_pcinsight,sd->bl.m,sd->bl.x-AREA_SIZE,sd->bl.y-AREA_SIZE,sd->bl.x+AREA_SIZE,sd->bl.y+AREA_SIZE,-dx,-dy,0,sd); + + if(sd->status.party_id>0){ // パーティのHP情報通知検査 + struct party *p=party_search(sd->status.party_id); + if(p!=NULL){ + int flag=0; + map_foreachinmovearea(party_send_hp_check,sd->bl.m,sd->bl.x-AREA_SIZE,sd->bl.y-AREA_SIZE,sd->bl.x+AREA_SIZE,sd->bl.y+AREA_SIZE,-dx,-dy,BL_PC,sd->status.party_id,&flag); + if(flag) + sd->party_hp=-1; + } + } + + if(sd->status.option&4) // クローキングの消滅検査 + skill_check_cloaking(&sd->bl); + + skill_unit_move(&sd->bl,gettick(),dist+7); // スキルユニットの検査 + + if(map_getcell(sd->bl.m,sd->bl.x,sd->bl.y)&0x80) + npc_touch_areanpc(sd,sd->bl.m,sd->bl.x,sd->bl.y); + else + sd->areanpc_id=0; + return 0; +} + +// +// 武器戦闘 +// +/*========================================== + * スキルの検索 所有していた場合Lvが返る + *------------------------------------------ + */ +int pc_checkskill(struct map_session_data *sd,int skill_id) +{ + if(sd == NULL) return 0; + if( skill_id>=10000 ){ + struct guild *g; + if( sd->status.guild_id>0 && (g=guild_search(sd->status.guild_id))!=NULL) + return guild_checkskill(g,skill_id); + return 0; + } + + if(sd->status.skill[skill_id].id == skill_id) + return (sd->status.skill[skill_id].lv); + + return 0; +} + +/*========================================== + * 武器変更によるスキルの継続チェック + * 引数: + * struct map_session_data *sd セッションデータ + * int nameid 装備品ID + * 返り値: + * 0 変更なし + * -1 スキルを解除 + *------------------------------------------ + */ +int pc_checkallowskill(struct map_session_data *sd) +{ + nullpo_retr(0, sd); + + if( sd->sc_data == NULL ) + return 0; + + if(!(skill_get_weapontype(KN_TWOHANDQUICKEN)&(1<<sd->status.weapon)) && sd->sc_data[SC_TWOHANDQUICKEN].timer!=-1) { // 2HQ + skill_status_change_end(&sd->bl,SC_TWOHANDQUICKEN,-1); // 2HQを解除 + return -1; + } + if(!(skill_get_weapontype(LK_AURABLADE)&(1<<sd->status.weapon)) && sd->sc_data[SC_AURABLADE].timer!=-1) { /* オーラブレード */ + skill_status_change_end(&sd->bl,SC_AURABLADE,-1); /* オーラブレードを解除 */ + return -1; + } + if(!(skill_get_weapontype(LK_PARRYING)&(1<<sd->status.weapon)) && sd->sc_data[SC_PARRYING].timer!=-1) { /* パリイング */ + skill_status_change_end(&sd->bl,SC_PARRYING,-1); /* パリイングを解除 */ + return -1; + } + if(!(skill_get_weapontype(LK_CONCENTRATION)&(1<<sd->status.weapon)) && sd->sc_data[SC_CONCENTRATION].timer!=-1) { /* コンセントレーション */ + skill_status_change_end(&sd->bl,SC_CONCENTRATION,-1); /* コンセントレーションを解除 */ + return -1; + } + if(!(skill_get_weapontype(CR_SPEARQUICKEN)&(1<<sd->status.weapon)) && sd->sc_data[SC_SPEARSQUICKEN].timer!=-1){ // スピアクィッケン + skill_status_change_end(&sd->bl,SC_SPEARSQUICKEN,-1); // スピアクイッケンを解除 + return -1; + } + if(!(skill_get_weapontype(BS_ADRENALINE)&(1<<sd->status.weapon)) && sd->sc_data[SC_ADRENALINE].timer!=-1){ // アドレナリンラッシュ + skill_status_change_end(&sd->bl,SC_ADRENALINE,-1); // アドレナリンラッシュを解除 + return -1; + } + + if(sd->status.shield <= 0) { + if(sd->sc_data[SC_AUTOGUARD].timer!=-1){ // オートガード + skill_status_change_end(&sd->bl,SC_AUTOGUARD,-1); + return -1; + } + if(sd->sc_data[SC_DEFENDER].timer!=-1){ // ディフェンダー + skill_status_change_end(&sd->bl,SC_DEFENDER,-1); + return -1; + } + if(sd->sc_data[SC_REFLECTSHIELD].timer!=-1){ //リフレクトシールド + skill_status_change_end(&sd->bl,SC_REFLECTSHIELD,-1); + return -1; + } + } + + return 0; +} + +/*========================================== + * 装 備品のチェック + *------------------------------------------ + */ +int pc_checkequip(struct map_session_data *sd,int pos) +{ + int i; + + nullpo_retr(-1, sd); + + for(i=0;i<11;i++){ + if(pos & equip_pos[i]) + return sd->equip_index[i]; + } + + return -1; +} + +/*========================================== + * 転生職や養子職の元の職業を返す + *------------------------------------------ + */ +struct pc_base_job pc_calc_base_job(int b_class) +{ + struct pc_base_job bj; + //転生や養子の場合の元の職業を算出する + if(b_class < MAX_PC_CLASS){ //通常 + bj.job = b_class; + bj.upper = 0; + }else if(b_class >= 4001 && b_class < 4023){ //転生職 + bj.job = b_class - 4001; + bj.upper = 1; + }else if(b_class == 23 + 4023 -1){ //養子スパノビ + bj.job = b_class - (4023 - 1); + bj.upper = 2; + }else{ //養子スパノビ以外の養子 + bj.job = b_class - 4023; + bj.upper = 2; + } + + if(battle_config.enable_upper_class==0){ //confで無効になっていたらupper=0 + bj.upper = 0; + } + + if(bj.job == 0){ + bj.type = 0; + }else if(bj.job < 7){ + bj.type = 1; + }else{ + bj.type = 2; + } + + return bj; +} + +/*========================================== + * PCの攻撃 (timer関数) + *------------------------------------------ + */ +int pc_attack_timer(int tid,unsigned int tick,int id,int data) +{ + struct map_session_data *sd; + struct block_list *bl; + struct status_change *sc_data; + short *opt; + int dist,skill,range; + + sd=map_id2sd(id); + if(sd == NULL) + return 0; + if(sd->attacktimer != tid){ + if(battle_config.error_log) + printf("pc_attack_timer %d != %d\n",sd->attacktimer,tid); + return 0; + } + sd->attacktimer=-1; + + if(sd->bl.prev == NULL) + return 0; + + bl=map_id2bl(sd->attacktarget); + if(bl==NULL || bl->prev == NULL) + return 0; + + if(bl->type == BL_PC && pc_isdead((struct map_session_data *)bl)) + return 0; + + // 同じmapでないなら攻撃しない + // PCが死んでても攻撃しない + if(sd->bl.m != bl->m || pc_isdead(sd)) + return 0; + + if( sd->opt1>0 || sd->status.option&2 || sd->status.option&16388) // 異常などで攻撃できない + return 0; + + if(sd->sc_data[SC_AUTOCOUNTER].timer != -1) + return 0; + if(sd->sc_data[SC_BLADESTOP].timer != -1) + return 0; + + if((opt = battle_get_option(bl)) != NULL && *opt&0x46) + return 0; + if(((sc_data = battle_get_sc_data(bl)) != NULL && sc_data[SC_TRICKDEAD].timer != -1) || + ((sc_data = battle_get_sc_data(bl)) != NULL && sc_data[SC_BASILICA].timer != -1 )) + return 0; + + if(sd->skilltimer != -1 && pc_checkskill(sd,SA_FREECAST) <= 0) + return 0; + + if(!battle_config.sdelay_attack_enable && pc_checkskill(sd,SA_FREECAST) <= 0) { + if(DIFF_TICK(tick , sd->canact_tick) < 0) { + clif_skill_fail(sd,1,4,0); + return 0; + } + } + + dist = distance(sd->bl.x,sd->bl.y,bl->x,bl->y); + range = sd->attackrange; + if(sd->status.weapon != 11) range++; + if( dist > range ){ // 届 かないので移動 + if(pc_can_reach(sd,bl->x,bl->y)) + clif_movetoattack(sd,bl); + return 0; + } + + if(dist <= range && !battle_check_range(&sd->bl,bl,range) ) { + if(pc_can_reach(sd,bl->x,bl->y) && sd->canmove_tick < tick && (sd->sc_data[SC_ANKLE].timer == -1 || sd->sc_data[SC_SPIDERWEB].timer == -1)) + pc_walktoxy(sd,bl->x,bl->y); + sd->attackabletime = tick + (sd->aspd<<1); + } + else { + if(battle_config.pc_attack_direction_change) + sd->dir=sd->head_dir=map_calc_dir(&sd->bl, bl->x,bl->y ); // 向き設定 + + if(sd->walktimer != -1) + pc_stop_walking(sd,1); + + if(sd->sc_data[SC_COMBO].timer == -1) { + map_freeblock_lock(); + pc_stop_walking(sd,0); + sd->attacktarget_lv = battle_weapon_attack(&sd->bl,bl,tick,0); + if(!(battle_config.pc_cloak_check_type&2) && sd->sc_data[SC_CLOAKING].timer != -1) + skill_status_change_end(&sd->bl,SC_CLOAKING,-1); + if(sd->status.pet_id > 0 && sd->pd && sd->petDB && battle_config.pet_attack_support) + pet_target_check(sd,bl,0); + map_freeblock_unlock(); + if(sd->skilltimer != -1 && (skill = pc_checkskill(sd,SA_FREECAST)) > 0 ) // フリーキャスト + sd->attackabletime = tick + ((sd->aspd<<1)*(150 - skill*5)/100); + else + sd->attackabletime = tick + (sd->aspd<<1); + } + else if(sd->attackabletime <= tick) { + if(sd->skilltimer != -1 && (skill = pc_checkskill(sd,SA_FREECAST)) > 0 ) // フリーキャスト + sd->attackabletime = tick + ((sd->aspd<<1)*(150 - skill*5)/100); + else + sd->attackabletime = tick + (sd->aspd<<1); + } + if(sd->attackabletime <= tick) sd->attackabletime = tick + (battle_config.max_aspd<<1); + } + + if(sd->state.attack_continue) { + sd->attacktimer=add_timer(sd->attackabletime,pc_attack_timer,sd->bl.id,0); + } + + return 0; +} + +/*========================================== + * 攻撃要求 + * typeが1なら継続攻撃 + *------------------------------------------ + */ +int pc_attack(struct map_session_data *sd,int target_id,int type) +{ + struct block_list *bl; + int d; + + nullpo_retr(0, sd); + + bl=map_id2bl(target_id); + if(bl==NULL) + return 1; + + if(bl->type==BL_NPC) { // monster npcs [Valaris] + npc_click(sd,RFIFOL(sd->fd,2)); + return 0; + } + + if(!battle_check_target(&sd->bl,bl,BCT_ENEMY)) + return 1; + if(sd->attacktimer != -1) + pc_stopattack(sd); + sd->attacktarget=target_id; + sd->state.attack_continue=type; + + d=DIFF_TICK(sd->attackabletime,gettick()); + if(d>0 && d<2000){ // 攻撃delay中 + sd->attacktimer=add_timer(sd->attackabletime,pc_attack_timer,sd->bl.id,0); + } else { + // 本来timer関数なので引数を合わせる + pc_attack_timer(-1,gettick(),sd->bl.id,0); + } + + return 0; +} + +/*========================================== + * 継続攻撃停止 + *------------------------------------------ + */ +int pc_stopattack(struct map_session_data *sd) +{ + nullpo_retr(0, sd); + + if(sd->attacktimer != -1) { + delete_timer(sd->attacktimer,pc_attack_timer); + sd->attacktimer=-1; + } + sd->attacktarget=0; + sd->state.attack_continue=0; + + return 0; +} + +int pc_follow_timer(int tid,unsigned int tick,int id,int data) +{ + struct map_session_data *sd, *bl; + + sd=map_id2sd(id); + if(sd == NULL || sd->followtimer != tid) + return 0; + + sd->followtimer=-1; + + do { + if(sd->bl.prev == NULL) + break; + + bl=(struct map_session_data *) map_id2bl(sd->followtarget); + + if(bl==NULL) + return 0; + + if(bl->bl.prev == NULL) + break; + + if(bl->bl.type == BL_PC && pc_isdead((struct map_session_data *)bl)) + return 0; + + if (sd->skilltimer == -1 && sd->attacktimer == -1 && sd->walktimer == -1) { + if((sd->bl.m == bl->bl.m) && pc_can_reach(sd,bl->bl.x,bl->bl.y)) { + if (distance(sd->bl.x,sd->bl.y,bl->bl.x,bl->bl.y) > 5) + pc_walktoxy(sd,bl->bl.x,bl->bl.y); + } else + pc_setpos((struct map_session_data*)sd, bl->mapname, bl->bl.x, bl->bl.y, 3); + } + } while (0); + + sd->followtimer=add_timer(tick + sd->aspd,pc_follow_timer,sd->bl.id,0); + + return 0; +} + +int pc_follow(struct map_session_data *sd,int target_id) +{ + struct block_list *bl; + + bl=map_id2bl(target_id); + if(bl==NULL) + return 1; + sd->followtarget=target_id; + if(sd->followtimer != -1) { + delete_timer(sd->followtimer,pc_follow_timer); + sd->followtimer = -1; + } + + pc_follow_timer(-1,gettick(),sd->bl.id,0); + + return 0; +} + +int pc_checkbaselevelup(struct map_session_data *sd) +{ + int next = pc_nextbaseexp(sd); + + nullpo_retr(0, sd); + + if(sd->status.base_exp >= next && next > 0){ + struct pc_base_job s_class = pc_calc_base_job(sd->status.class); + + // base側レベルアップ処理 + sd->status.base_exp -= next; + + sd->status.base_level ++; + sd->status.status_point += (sd->status.base_level+14) / 5 ; + clif_updatestatus(sd,SP_STATUSPOINT); + clif_updatestatus(sd,SP_BASELEVEL); + clif_updatestatus(sd,SP_NEXTBASEEXP); + pc_calcstatus(sd,0); + pc_heal(sd,sd->status.max_hp,sd->status.max_sp); + + //スパノビはキリエ、イムポ、マニピ、グロ、サフラLv1がかかる + if(s_class.job == 23){ + skill_status_change_start(&sd->bl,SkillStatusChangeTable[PR_KYRIE],1,0,0,0,skill_get_time(PR_KYRIE,1),0 ); + skill_status_change_start(&sd->bl,SkillStatusChangeTable[PR_IMPOSITIO],1,0,0,0,skill_get_time(PR_IMPOSITIO,1),0 ); + skill_status_change_start(&sd->bl,SkillStatusChangeTable[PR_MAGNIFICAT],1,0,0,0,skill_get_time(PR_MAGNIFICAT,1),0 ); + skill_status_change_start(&sd->bl,SkillStatusChangeTable[PR_GLORIA],1,0,0,0,skill_get_time(PR_GLORIA,1),0 ); + skill_status_change_start(&sd->bl,SkillStatusChangeTable[PR_SUFFRAGIUM],1,0,0,0,skill_get_time(PR_SUFFRAGIUM,1),0 ); + } + + clif_misceffect(&sd->bl,0); + //レベルアップしたのでパーティー情報を更新する + //(公平範囲チェック) + party_send_movemap(sd); + return 1; + } + + return 0; +} + +int pc_checkjoblevelup(struct map_session_data *sd) +{ + int next = pc_nextjobexp(sd); + + nullpo_retr(0, sd); + + if(sd->status.job_exp >= next && next > 0){ + // job側レベルアップ処理 + sd->status.job_exp -= next; + sd->status.job_level ++; + clif_updatestatus(sd,SP_JOBLEVEL); + clif_updatestatus(sd,SP_NEXTJOBEXP); + sd->status.skill_point ++; + clif_updatestatus(sd,SP_SKILLPOINT); + pc_calcstatus(sd,0); + + clif_misceffect(&sd->bl,1); + return 1; + } + + return 0; +} + +/*========================================== + * 経験値取得 + *------------------------------------------ + */ +int pc_gainexp(struct map_session_data *sd,int base_exp,int job_exp) +{ + char output[256]; + nullpo_retr(0, sd); + + if(sd->bl.prev == NULL || pc_isdead(sd)) + return 0; + + if((battle_config.pvp_exp == 0) && map[sd->bl.m].flag.pvp) // [MouseJstr] + return 0; // no exp on pvp maps + + if(sd->sc_data[SC_RICHMANKIM].timer != -1) { // added bounds checking [Vaalris] + base_exp += base_exp*(25 + sd->sc_data[SC_RICHMANKIM].val1*25)/100; + job_exp += job_exp*(25 + sd->sc_data[SC_RICHMANKIM].val1*25)/100; + } + + if(sd->status.guild_id>0){ // ギルドに上納 + base_exp-=guild_payexp(sd,base_exp); + if(base_exp < 0) + base_exp = 0; + } + + if(!battle_config.multi_level_up && pc_nextbaseafter(sd)) { + while(sd->status.base_exp+base_exp >=pc_nextbaseafter(sd) && sd->status.base_exp <= pc_nextbaseexp(sd) && pc_nextbaseafter(sd) > 0) { + base_exp*=.90; + } + } + + sd->status.base_exp += base_exp; + if(sd->status.base_exp < 0) + sd->status.base_exp = 0; + + while(pc_checkbaselevelup(sd)) ; + + clif_updatestatus(sd,SP_BASEEXP); + if(!battle_config.multi_level_up && pc_nextjobafter(sd)) { + while(sd->status.job_exp+job_exp >= pc_nextjobafter(sd) && sd->status.job_exp <= pc_nextjobexp(sd) && pc_nextjobafter(sd) > 0) { + job_exp*=.90; + } + } + + sd->status.job_exp += job_exp; + if(sd->status.job_exp < 0) + sd->status.job_exp = 0; + + while(pc_checkjoblevelup(sd)) ; + + clif_updatestatus(sd,SP_JOBEXP); + + if(battle_config.disp_experience){ + sprintf(output, + "Experienced Gained Base:%d Job:%d",base_exp,job_exp); + clif_disp_onlyself(sd,output,strlen(output)); + } + + return 0; +} + +/*========================================== + * base level側必要経験値計算 + *------------------------------------------ + */ +int pc_nextbaseexp(struct map_session_data *sd) +{ + int i; + + nullpo_retr(0, sd); + + if(sd->status.base_level>=MAX_LEVEL || sd->status.base_level<=0) + return 0; + + if(sd->status.class==0) i=0; + else if(sd->status.class<=6) i=1; + else if(sd->status.class<=22) i=2; + else if(sd->status.class==23) i=3; + else if(sd->status.class==4001) i=4; + else if(sd->status.class<=4007) i=5; + else i=6; + + return exp_table[i][sd->status.base_level-1]; +} + +/*========================================== + * job level側必要経験値計算 + *------------------------------------------ + */ +int pc_nextjobexp(struct map_session_data *sd) +{ + int i; + + nullpo_retr(0, sd); + + if(sd->status.job_level>=MAX_LEVEL || sd->status.job_level<=0) + return 0; + + if(sd->status.class==0) i=7; + else if(sd->status.class<=6) i=8; + else if(sd->status.class<=22) i=9; + else if(sd->status.class==23) i=10; + else if(sd->status.class==4001) i=11; + else if(sd->status.class<=4007) i=12; + else i=13; + + return exp_table[i][sd->status.job_level-1]; +} + +/*========================================== + * base level after next [Valaris] + *------------------------------------------ + */ +int pc_nextbaseafter(struct map_session_data *sd) +{ + int i; + + nullpo_retr(0, sd); + + if(sd->status.base_level>=MAX_LEVEL || sd->status.base_level<=0) + return 0; + + if(sd->status.class==0) i=0; + else if(sd->status.class<=6) i=1; + else if(sd->status.class<=22) i=2; + else if(sd->status.class==23) i=3; + else if(sd->status.class==4001) i=4; + else if(sd->status.class<=4007) i=5; + else i=6; + + return exp_table[i][sd->status.base_level]; +} + +/*========================================== + * job level after next [Valaris] + *------------------------------------------ + */ +int pc_nextjobafter(struct map_session_data *sd) +{ + int i; + + nullpo_retr(0, sd); + + if(sd->status.job_level>=MAX_LEVEL || sd->status.job_level<=0) + return 0; + + if(sd->status.class==0) i=7; + else if(sd->status.class<=6) i=8; + else if(sd->status.class<=22) i=9; + else if(sd->status.class==23) i=10; + else if(sd->status.class==4001) i=11; + else if(sd->status.class<=4007) i=12; + else i=13; + + return exp_table[i][sd->status.job_level]; +} +/*========================================== + + * 必要ステータスポイント計算 + *------------------------------------------ + */ +int pc_need_status_point(struct map_session_data *sd,int type) +{ + int val; + + nullpo_retr(-1, sd); + + if(type<SP_STR || type>SP_LUK) + return -1; + val = + type==SP_STR ? sd->status.str : + type==SP_AGI ? sd->status.agi : + type==SP_VIT ? sd->status.vit : + type==SP_INT ? sd->status.int_: + type==SP_DEX ? sd->status.dex : sd->status.luk; + + return (val+9)/10+1; +} + +/*========================================== + * 能力値成長 + *------------------------------------------ + */ +int pc_statusup(struct map_session_data *sd,int type) +{ + int need,val = 0; + + nullpo_retr(0, sd); + + need=pc_need_status_point(sd,type); + if(type<SP_STR || type>SP_LUK || need<0 || need>sd->status.status_point){ + clif_statusupack(sd,type,0,0); + return 1; + } + switch(type){ + case SP_STR: + if(sd->status.str >= battle_config.max_parameter) { + clif_statusupack(sd,type,0,0); + return 1; + } + val= ++sd->status.str; + break; + case SP_AGI: + if(sd->status.agi >= battle_config.max_parameter) { + clif_statusupack(sd,type,0,0); + return 1; + } + val= ++sd->status.agi; + break; + case SP_VIT: + if(sd->status.vit >= battle_config.max_parameter) { + clif_statusupack(sd,type,0,0); + return 1; + } + val= ++sd->status.vit; + break; + case SP_INT: + if(sd->status.int_ >= battle_config.max_parameter) { + clif_statusupack(sd,type,0,0); + return 1; + } + val= ++sd->status.int_; + break; + case SP_DEX: + if(sd->status.dex >= battle_config.max_parameter) { + clif_statusupack(sd,type,0,0); + return 1; + } + val= ++sd->status.dex; + break; + case SP_LUK: + if(sd->status.luk >= battle_config.max_parameter) { + clif_statusupack(sd,type,0,0); + return 1; + } + val= ++sd->status.luk; + break; + } + sd->status.status_point-=need; + if(need!=pc_need_status_point(sd,type)){ + clif_updatestatus(sd,type-SP_STR+SP_USTR); + } + clif_updatestatus(sd,SP_STATUSPOINT); + clif_updatestatus(sd,type); + pc_calcstatus(sd,0); + clif_statusupack(sd,type,1,val); + + return 0; +} + +/*========================================== + * 能力値成長 + *------------------------------------------ + */ +int pc_statusup2(struct map_session_data *sd,int type,int val) +{ + nullpo_retr(0, sd); + + if(type<SP_STR || type>SP_LUK){ + clif_statusupack(sd,type,0,0); + return 1; + } + switch(type){ + case SP_STR: + if(sd->status.str + val >= battle_config.max_parameter) + val = battle_config.max_parameter; + else if(sd->status.str + val < 1) + val = 1; + else + val += sd->status.str; + sd->status.str = val; + break; + case SP_AGI: + if(sd->status.agi + val >= battle_config.max_parameter) + val = battle_config.max_parameter; + else if(sd->status.agi + val < 1) + val = 1; + else + val += sd->status.agi; + sd->status.agi = val; + break; + case SP_VIT: + if(sd->status.vit + val >= battle_config.max_parameter) + val = battle_config.max_parameter; + else if(sd->status.vit + val < 1) + val = 1; + else + val += sd->status.vit; + sd->status.vit = val; + break; + case SP_INT: + if(sd->status.int_ + val >= battle_config.max_parameter) + val = battle_config.max_parameter; + else if(sd->status.int_ + val < 1) + val = 1; + else + val += sd->status.int_; + sd->status.int_ = val; + break; + case SP_DEX: + if(sd->status.dex + val >= battle_config.max_parameter) + val = battle_config.max_parameter; + else if(sd->status.dex + val < 1) + val = 1; + else + val += sd->status.dex; + sd->status.dex = val; + break; + case SP_LUK: + if(sd->status.luk + val >= battle_config.max_parameter) + val = battle_config.max_parameter; + else if(sd->status.luk + val < 1) + val = 1; + else + val = sd->status.luk + val; + sd->status.luk = val; + break; + } + clif_updatestatus(sd,type-SP_STR+SP_USTR); + clif_updatestatus(sd,type); + pc_calcstatus(sd,0); + clif_statusupack(sd,type,1,val); + + return 0; +} + +/*========================================== + * スキルポイント割り振り + *------------------------------------------ + */ +int pc_skillup(struct map_session_data *sd,int skill_num) +{ + nullpo_retr(0, sd); + + if( skill_num>=10000 ){ + guild_skillup(sd,skill_num); + return 0; + } + + if( sd->status.skill_point>0 && + sd->status.skill[skill_num].id!=0 && + sd->status.skill[skill_num].lv < skill_get_max(skill_num) ) + { + sd->status.skill[skill_num].lv++; + sd->status.skill_point--; + pc_calcstatus(sd,0); + clif_skillup(sd,skill_num); + clif_updatestatus(sd,SP_SKILLPOINT); + clif_skillinfoblock(sd); + } + + return 0; +} + +/*========================================== + * /allskill + *------------------------------------------ + */ +int pc_allskillup(struct map_session_data *sd) +{ + int i,id; + int c=0, s=0; + //転生や養子の場合の元の職業を算出する + struct pc_base_job s_class; + + nullpo_retr(0, sd); + + s_class = pc_calc_base_job(sd->status.class); + c = s_class.job; + s = (s_class.upper==1) ? 1 : 0 ; //転生以外は通常のスキル? + + for(i=0;i<MAX_SKILL;i++){ + sd->status.skill[i].id=0; + if (sd->status.skill[i].flag && sd->status.skill[i].flag != 13){ // cardスキルなら、 + sd->status.skill[i].lv=(sd->status.skill[i].flag==1)?0:sd->status.skill[i].flag-2; // 本当のlvに + sd->status.skill[i].flag=0; // flagは0にしておく + } + } + + if (battle_config.gm_allskill > 0 && pc_isGM(sd) >= battle_config.gm_allskill){ + // 全てのスキル + for(i=1;i<158;i++) + sd->status.skill[i].lv=skill_get_max(i); + for(i=210;i<291;i++) + sd->status.skill[i].lv=skill_get_max(i); + for(i=304;i<MAX_SKILL;i++) + sd->status.skill[i].lv=skill_get_max(i); + } + else { + for(i=0;(id=skill_tree[s][c][i].id)>0;i++){ + if(sd->status.skill[id].id==0 && (!(skill_get_inf2(id)&0x01) || battle_config.quest_skill_learn) ) + sd->status.skill[id].lv=skill_get_max(id); + } + } + pc_calcstatus(sd,0); + + return 0; +} + +/*========================================== + * /resetlvl + *------------------------------------------ + */ +int pc_resetlvl(struct map_session_data* sd,int type) +{ + int i; + + nullpo_retr(0, sd); + + for(i=1;i<MAX_SKILL;i++){ + sd->status.skill[i].lv = 0; + } + + if(type == 1){ + sd->status.skill_point=0; + sd->status.base_level=1; + sd->status.job_level=1; + sd->status.base_exp=sd->status.base_exp=0; + sd->status.job_exp=sd->status.job_exp=0; + if(sd->status.option !=0) + sd->status.option = 0; + + sd->status.str=1; + sd->status.agi=1; + sd->status.vit=1; + sd->status.int_=1; + sd->status.dex=1; + sd->status.luk=1; + if(sd->status.class == 4001) + sd->status.status_point=100; + } + + if(type == 2){ + sd->status.skill_point=0; + sd->status.base_level=1; + sd->status.job_level=1; + sd->status.base_exp=0; + sd->status.job_exp=0; + } + if(type == 3){ + sd->status.base_level=1; + sd->status.base_exp=0; + } + if(type == 4){ + sd->status.job_level=1; + sd->status.job_exp=0; + } + + clif_updatestatus(sd,SP_STATUSPOINT); + clif_updatestatus(sd,SP_STR); + clif_updatestatus(sd,SP_AGI); + clif_updatestatus(sd,SP_VIT); + clif_updatestatus(sd,SP_INT); + clif_updatestatus(sd,SP_DEX); + clif_updatestatus(sd,SP_LUK); + clif_updatestatus(sd,SP_BASELEVEL); + clif_updatestatus(sd,SP_JOBLEVEL); + clif_updatestatus(sd,SP_STATUSPOINT); + clif_updatestatus(sd,SP_NEXTBASEEXP); + clif_updatestatus(sd,SP_NEXTJOBEXP); + clif_updatestatus(sd,SP_SKILLPOINT); + + clif_updatestatus(sd,SP_USTR); // Updates needed stat points - Valaris + clif_updatestatus(sd,SP_UAGI); + clif_updatestatus(sd,SP_UVIT); + clif_updatestatus(sd,SP_UINT); + clif_updatestatus(sd,SP_UDEX); + clif_updatestatus(sd,SP_ULUK); // End Addition + + for(i=0;i<11;i++) { // unequip items that can't be equipped by base 1 [Valaris] + if(sd->equip_index[i] >= 0) + if(!pc_isequip(sd,sd->equip_index[i])) + pc_unequipitem(sd,sd->equip_index[i],1); + } + + clif_skillinfoblock(sd); + pc_calcstatus(sd,0); + + return 0; +} +/*========================================== + * /resetstate + *------------------------------------------ + */ +int pc_resetstate(struct map_session_data* sd) +{ + #define sumsp(a) ((a)*((a-2)/10+2) - 5*((a-2)/10)*((a-2)/10) - 6*((a-2)/10) -2) +// int add=0; // Removed by Dexity + + nullpo_retr(0, sd); + +// New statpoint table used here - Dexity + sd->status.status_point = atoi (statp[sd->status.base_level - 1]); +// End addition + +// Removed by Dexity - old count +// add += sumsp(sd->status.str); +// add += sumsp(sd->status.agi); +// add += sumsp(sd->status.vit); +// add += sumsp(sd->status.int_); +// add += sumsp(sd->status.dex); +// add += sumsp(sd->status.luk); +// sd->status.status_point+=add; + + clif_updatestatus(sd,SP_STATUSPOINT); + + sd->status.str=1; + sd->status.agi=1; + sd->status.vit=1; + sd->status.int_=1; + sd->status.dex=1; + sd->status.luk=1; + + clif_updatestatus(sd,SP_STR); + clif_updatestatus(sd,SP_AGI); + clif_updatestatus(sd,SP_VIT); + clif_updatestatus(sd,SP_INT); + clif_updatestatus(sd,SP_DEX); + clif_updatestatus(sd,SP_LUK); + + clif_updatestatus(sd,SP_USTR); // Updates needed stat points - Valaris + clif_updatestatus(sd,SP_UAGI); + clif_updatestatus(sd,SP_UVIT); + clif_updatestatus(sd,SP_UINT); + clif_updatestatus(sd,SP_UDEX); + clif_updatestatus(sd,SP_ULUK); // End Addition + + pc_calcstatus(sd,0); + + return 0; +} + +/*========================================== + * /resetskill + *------------------------------------------ + */ +int pc_resetskill(struct map_session_data* sd) +{ + int i,skill; + + nullpo_retr(0, sd); + + for(i=1;i<MAX_SKILL;i++){ + if( (skill = pc_checkskill(sd,i)) > 0) { + if(!(skill_get_inf2(i)&0x01) || battle_config.quest_skill_learn) { + if(!sd->status.skill[i].flag) + sd->status.skill_point += skill; + else if(sd->status.skill[i].flag > 2 && sd->status.skill[i].flag != 13) { + sd->status.skill_point += (sd->status.skill[i].flag - 2); + } + sd->status.skill[i].lv = 0; + } + else if(battle_config.quest_skill_reset) + sd->status.skill[i].lv = 0; + sd->status.skill[i].flag = 0; + } + else + sd->status.skill[i].lv = 0; + } + clif_updatestatus(sd,SP_SKILLPOINT); + clif_skillinfoblock(sd); + pc_calcstatus(sd,0); + + return 0; +} + +/*========================================== + * pcにダメージを与える + *------------------------------------------ + */ +int pc_damage(struct block_list *src,struct map_session_data *sd,int damage) +{ + int i=0,j=0; + struct pc_base_job s_class; + + nullpo_retr(0, sd); + + //転生や養子の場合の元の職業を算出する + s_class = pc_calc_base_job(sd->status.class); + // 既に死んでいたら無効 + if(pc_isdead(sd)) + return 0; + // 座ってたら立ち上がる + if(pc_issit(sd)) { + pc_setstand(sd); + skill_gangsterparadise(sd,0); + } + + // 歩 いていたら足を止める + if(sd->sc_data[SC_ENDURE].timer == -1 && !sd->special_state.infinite_endure) + pc_stop_walking(sd,3); + // 演奏/ダンスの中断 + if(damage > sd->status.max_hp>>2) + skill_stop_dancing(&sd->bl,0); + + sd->status.hp-=damage; + if(sd->status.pet_id > 0 && sd->pd && sd->petDB && battle_config.pet_damage_support) + pet_target_check(sd,src,1); + + if (sd->sc_data[SC_TRICKDEAD].timer != -1) + skill_status_change_end(&sd->bl, SC_TRICKDEAD, -1); + if(sd->status.option&2) + skill_status_change_end(&sd->bl, SC_HIDING, -1); + if(sd->status.option&4) + skill_status_change_end(&sd->bl, SC_CLOAKING, -1); + if(sd->status.option&16386) + skill_status_change_end(&sd->bl, SC_CHASEWALK, -1); + + if(sd->status.hp>0){ + // まだ生きているならHP更新 + clif_updatestatus(sd,SP_HP); + + if(sd->status.hp<sd->status.max_hp>>2 && pc_checkskill(sd,SM_AUTOBERSERK)>0 && + (sd->sc_data[SC_PROVOKE].timer==-1 || sd->sc_data[SC_PROVOKE].val2==0 )) + // オートバーサーク発動 + skill_status_change_start(&sd->bl,SC_PROVOKE,10,1,0,0,0,0); + + sd->canlog_tick = gettick(); + + if(sd->status.party_id>0) { // on-the-fly party hp updates [Valaris] + struct party *p=party_search(sd->status.party_id); + if(p!=NULL) clif_party_hp(p,sd); + } // end addition [Valaris] + + return 0; + } + sd->status.hp = 0; + pc_setdead(sd); + if(sd->vender_id) + vending_closevending(sd); + + if(sd->status.pet_id > 0 && sd->pd) { + if(sd->petDB) { + sd->pet.intimate -= sd->petDB->die; + if(sd->pet.intimate < 0) + sd->pet.intimate = 0; + clif_send_petdata(sd,1,sd->pet.intimate); + } + } + + pc_stop_walking(sd,0); + skill_castcancel(&sd->bl,0); // 詠唱の中止 + clif_clearchar_area(&sd->bl,1); + skill_unit_out_all(&sd->bl,gettick(),1); + if(sd->sc_data[SC_BLADESTOP].timer!=-1)//白刃は事前に解除 + skill_status_change_end(&sd->bl,SC_BLADESTOP,-1); + pc_setglobalreg(sd,"PC_DIE_COUNTER",++sd->die_counter); //死にカウンター書き込み + skill_status_change_clear(&sd->bl,0); // ステータス異常を解除する + clif_updatestatus(sd,SP_HP); + pc_calcstatus(sd,0); + + for(i=0;i<5;i++) + if(sd->dev.val1[i]){ + skill_status_change_end(&map_id2sd(sd->dev.val1[i])->bl,SC_DEVOTION,-1); + sd->dev.val1[i] = sd->dev.val2[i]=0; + } + + if(battle_config.death_penalty_type>0) { // changed penalty options, added death by player if pk_mode [Valaris] + if(sd->status.class != 0 && !map[sd->bl.m].flag.nopenalty && !map[sd->bl.m].flag.gvg){ // only novices will recieve no penalty + if(battle_config.death_penalty_type==1 && battle_config.death_penalty_base > 0) + sd->status.base_exp -= (double)pc_nextbaseexp(sd) * (double)battle_config.death_penalty_base/10000; + if(battle_config.pk_mode && src && src->type==BL_PC) + sd->status.base_exp -= (double)pc_nextbaseexp(sd) * (double)battle_config.death_penalty_base/10000; + else if(battle_config.death_penalty_type==2 && battle_config.death_penalty_base > 0) { + if(pc_nextbaseexp(sd) > 0) + sd->status.base_exp -= (double)sd->status.base_exp * (double)battle_config.death_penalty_base/10000; + if(battle_config.pk_mode && src && src->type==BL_PC) + sd->status.base_exp -= (double)sd->status.base_exp * (double)battle_config.death_penalty_base/10000; + } + if(sd->status.base_exp < 0) + sd->status.base_exp = 0; + clif_updatestatus(sd,SP_BASEEXP); + + if(battle_config.death_penalty_type==1 && battle_config.death_penalty_job > 0) + sd->status.job_exp -= (double)pc_nextjobexp(sd) * (double)battle_config.death_penalty_job/10000; + if(battle_config.pk_mode && src && src->type==BL_PC) + sd->status.job_exp -= (double)pc_nextjobexp(sd) * (double)battle_config.death_penalty_job/10000; + else if(battle_config.death_penalty_type==2 && battle_config.death_penalty_job > 0) { + if(pc_nextjobexp(sd) > 0) + sd->status.job_exp -= (double)sd->status.job_exp * (double)battle_config.death_penalty_job/10000; + if(battle_config.pk_mode && src && src->type==BL_PC) + sd->status.job_exp -= (double)sd->status.job_exp * (double)battle_config.death_penalty_job/10000; + } + if(sd->status.job_exp < 0) + sd->status.job_exp = 0; + clif_updatestatus(sd,SP_JOBEXP); + } + } + //ナイトメアモードアイテムドロップ + if(map[sd->bl.m].flag.pvp_nightmaredrop){ // Moved this outside so it works when PVP isnt enabled and during pk mode [Ancyker] + for(j=0;j<MAX_DROP_PER_MAP;j++){ + int id = map[sd->bl.m].drop_list[j].drop_id; + int type = map[sd->bl.m].drop_list[j].drop_type; + int per = map[sd->bl.m].drop_list[j].drop_per; + if(id == 0) + continue; + if(id == -1){//ランダムドロップ + int eq_num=0,eq_n[MAX_INVENTORY]; + memset(eq_n,0,sizeof(eq_n)); + //先ず装備しているアイテム数をカウント + for(i=0;i<MAX_INVENTORY;i++){ + int k; + if( (type == 1 && !sd->status.inventory[i].equip) + || (type == 2 && sd->status.inventory[i].equip) + || type == 3){ + //InventoryIndexを格納 + for(k=0;k<MAX_INVENTORY;k++){ + if(eq_n[k] <= 0){ + eq_n[k]=i; + break; + } + } + eq_num++; + } + } + if(eq_num > 0){ + int n = eq_n[rand()%eq_num];//該当アイテムの中からランダム + if(rand()%10000 < per){ + if(sd->status.inventory[n].equip) + pc_unequipitem(sd,n,0); + pc_dropitem(sd,n,1); + } + } + } + else if(id > 0){ + for(i=0;i<MAX_INVENTORY;i++){ + if(sd->status.inventory[i].nameid == id//ItemIDが一致していて + && rand()%10000 < per//ドロップ率判定もOKで + && ((type == 1 && !sd->status.inventory[i].equip)//タイプ判定もOKならドロップ + || (type == 2 && sd->status.inventory[i].equip) + || type == 3) ){ + if(sd->status.inventory[i].equip) + pc_unequipitem(sd,i,0); + pc_dropitem(sd,i,1); + break; + } + } + } + } + } + // pvp + if( map[sd->bl.m].flag.pvp && !battle_config.pk_mode){ // disable certain pvp functions on pk_mode [Valaris] + //ランキング計算 + if(!map[sd->bl.m].flag.pvp_nocalcrank){ + sd->pvp_point-=5; + if(src && src->type==BL_PC ) + ((struct map_session_data *)src)->pvp_point++; + //} //fixed wrong '{' placement by Lupus + pc_setdead(sd); + } + // 強制送還 + if( sd->pvp_point < 0 ){ + sd->pvp_point=0; + pc_setstand(sd); + pc_setrestartvalue(sd,3); + pc_setpos(sd,sd->status.save_point.map,sd->status.save_point.x,sd->status.save_point.y,0); + } + } + //GvG + if(map[sd->bl.m].flag.gvg){ + pc_setstand(sd); + pc_setrestartvalue(sd,3); + pc_setpos(sd,sd->status.save_point.map,sd->status.save_point.x,sd->status.save_point.y,0); + } + + return 0; +} + +// +// script関 連 +// +/*========================================== + * script用PCステータス読み出し + *------------------------------------------ + */ +int pc_readparam(struct map_session_data *sd,int type) +{ + int val=0; + struct pc_base_job s_class; + + s_class = pc_calc_base_job(sd->status.class); + + nullpo_retr(0, sd); + + switch(type){ + case SP_SKILLPOINT: + val= sd->status.skill_point; + break; + case SP_STATUSPOINT: + val= sd->status.status_point; + break; + case SP_ZENY: + val= sd->status.zeny; + break; + case SP_BASELEVEL: + val= sd->status.base_level; + break; + case SP_JOBLEVEL: + val= sd->status.job_level; + break; + case SP_CLASS: + if(val>=24 && val < 45) + val+=3978; + else + val= sd->status.class; + break; + case SP_UPPER: + val= s_class.upper; + break; + case SP_SEX: + val= sd->sex; + break; + case SP_WEIGHT: + val= sd->weight; + break; + case SP_MAXWEIGHT: + val= sd->max_weight; + break; + case SP_BASEEXP: + val= sd->status.base_exp; + break; + case SP_JOBEXP: + val= sd->status.job_exp; + break; + case SP_NEXTBASEEXP: + val= pc_nextbaseexp(sd); + break; + case SP_NEXTJOBEXP: + val= pc_nextjobexp(sd); + break; + case SP_HP: + val= sd->status.hp; + break; + case SP_MAXHP: + val= sd->status.max_hp; + break; + case SP_SP: + val= sd->status.sp; + break; + case SP_MAXSP: + val= sd->status.max_sp; + break; + case SP_STR: + val= sd->status.str; + break; + case SP_AGI: + val= sd->status.agi; + break; + case SP_VIT: + val= sd->status.vit; + break; + case SP_INT: + val= sd->status.int_; + break; + case SP_DEX: + val= sd->status.dex; + break; + case SP_LUK: + val= sd->status.luk; + break; + case SP_FAME: + val= sd->fame; + break; + } + + return val; +} + +/*========================================== + * script用PCステータス設定 + *------------------------------------------ + */ +int pc_setparam(struct map_session_data *sd,int type,int val) +{ + int i = 0,up_level = 50; + struct pc_base_job s_class; + + nullpo_retr(0, sd); + + s_class = pc_calc_base_job(sd->status.class); + + switch(type){ + case SP_BASELEVEL: + if (val > sd->status.base_level) { + for (i = 1; i <= (val - sd->status.base_level); i++) + sd->status.status_point += (sd->status.base_level + i + 14) / 5 ; + } + sd->status.base_level = val; + sd->status.base_exp = 0; + clif_updatestatus(sd, SP_BASELEVEL); + clif_updatestatus(sd, SP_NEXTBASEEXP); + clif_updatestatus(sd, SP_STATUSPOINT); + clif_updatestatus(sd, SP_BASEEXP); + pc_calcstatus(sd, 0); + pc_heal(sd, sd->status.max_hp, sd->status.max_sp); + break; + case SP_JOBLEVEL: + if (sd->status.class == 0) + up_level -= 40; + if ((sd->status.class == 23) || (sd->status.class >= 4001 && sd->status.class <= 4022)) + up_level += 20; + if (val >= sd->status.job_level) { + if (val > up_level)val = up_level; + sd->status.skill_point += (val-sd->status.job_level); + sd->status.job_level = val; + sd->status.job_exp = 0; + clif_updatestatus(sd, SP_JOBLEVEL); + clif_updatestatus(sd, SP_NEXTJOBEXP); + clif_updatestatus(sd, SP_JOBEXP); + clif_updatestatus(sd, SP_SKILLPOINT); + pc_calcstatus(sd, 0); + clif_misceffect(&sd->bl, 1); + } else { + sd->status.job_level = val; + sd->status.job_exp = 0; + clif_updatestatus(sd, SP_JOBLEVEL); + clif_updatestatus(sd, SP_NEXTJOBEXP); + clif_updatestatus(sd, SP_JOBEXP); + pc_calcstatus(sd, 0); + } + clif_updatestatus(sd,type); + break; + case SP_SKILLPOINT: + sd->status.skill_point = val; + break; + case SP_STATUSPOINT: + sd->status.status_point = val; + break; + case SP_ZENY: + sd->status.zeny = val; + break; + case SP_BASEEXP: + if(pc_nextbaseexp(sd) > 0) { + sd->status.base_exp = val; + if(sd->status.base_exp < 0) + sd->status.base_exp=0; + pc_checkbaselevelup(sd); + } + break; + case SP_JOBEXP: + if(pc_nextjobexp(sd) > 0) { + sd->status.job_exp = val; + if(sd->status.job_exp < 0) + sd->status.job_exp=0; + pc_checkjoblevelup(sd); + } + break; + case SP_SEX: + sd->sex = val; + break; + case SP_WEIGHT: + sd->weight = val; + break; + case SP_MAXWEIGHT: + sd->max_weight = val; + break; + case SP_HP: + sd->status.hp = val; + break; + case SP_MAXHP: + sd->status.max_hp = val; + break; + case SP_SP: + sd->status.sp = val; + break; + case SP_MAXSP: + sd->status.max_sp = val; + break; + case SP_STR: + sd->status.str = val; + break; + case SP_AGI: + sd->status.agi = val; + break; + case SP_VIT: + sd->status.vit = val; + break; + case SP_INT: + sd->status.int_ = val; + break; + case SP_DEX: + sd->status.dex = val; + break; + case SP_LUK: + sd->status.luk = val; + break; + case SP_FAME: + sd->fame = val; + break; + } + clif_updatestatus(sd,type); + + return 0; +} + +/*========================================== + * HP/SP回復 + *------------------------------------------ + */ +int pc_heal(struct map_session_data *sd,int hp,int sp) +{ +// if(battle_config.battle_log) +// printf("heal %d %d\n",hp,sp); + + nullpo_retr(0, sd); + + if(pc_checkoverhp(sd)) { + if(hp > 0) + hp = 0; + } + if(pc_checkoversp(sd)) { + if(sp > 0) + sp = 0; + } + + if(sd->sc_data && sd->sc_data[SC_BERSERK].timer!=-1) //バーサーク中は回復させないらしい + return 0; + + if(hp+sd->status.hp>sd->status.max_hp) + hp=sd->status.max_hp-sd->status.hp; + if(sp+sd->status.sp>sd->status.max_sp) + sp=sd->status.max_sp-sd->status.sp; + sd->status.hp+=hp; + if(sd->status.hp <= 0) { + sd->status.hp = 0; + pc_damage(NULL,sd,1); + hp = 0; + } + sd->status.sp+=sp; + if(sd->status.sp <= 0) + sd->status.sp = 0; + if(hp) + clif_updatestatus(sd,SP_HP); + if(sp) + clif_updatestatus(sd,SP_SP); + + if(sd->status.party_id>0) { // on-the-fly party hp updates [Valaris] + struct party *p=party_search(sd->status.party_id); + if(p!=NULL) clif_party_hp(p,sd); + } // end addition [Valaris] + + return hp + sp; +} + +/*========================================== + * HP/SP回復 + *------------------------------------------ + */ +int pc_itemheal(struct map_session_data *sd,int hp,int sp) +{ + int bonus; +// if(battle_config.battle_log) +// printf("heal %d %d\n",hp,sp); + + nullpo_retr(0, sd); + + if(sd->sc_data && sd->sc_data[SC_GOSPEL].timer!=-1) //バーサーク中は回復させないらしい + return 0; + + if(sd->state.potionpitcher_flag) { + sd->potion_hp = hp; + sd->potion_sp = sp; + return 0; + } + + if(pc_checkoverhp(sd)) { + if(hp > 0) + hp = 0; + } + if(pc_checkoversp(sd)) { + if(sp > 0) + sp = 0; + } + if(hp > 0) { + bonus = (sd->paramc[2]<<1) + 100 + pc_checkskill(sd,SM_RECOVERY)*10; + if(bonus != 100) + hp = hp * bonus / 100; + bonus = 100 + pc_checkskill(sd,AM_LEARNINGPOTION)*5; + if(bonus != 100) + hp = hp * bonus / 100; + } + if(sp > 0) { + bonus = (sd->paramc[3]<<1) + 100 + pc_checkskill(sd,MG_SRECOVERY)*10; + if(bonus != 100) + sp = sp * bonus / 100; + bonus = 100 + pc_checkskill(sd,AM_LEARNINGPOTION)*5; + if(bonus != 100) + sp = sp * bonus / 100; + } + if(hp+sd->status.hp>sd->status.max_hp) + hp=sd->status.max_hp-sd->status.hp; + if(sp+sd->status.sp>sd->status.max_sp) + sp=sd->status.max_sp-sd->status.sp; + sd->status.hp+=hp; + if(sd->status.hp <= 0) { + sd->status.hp = 0; + pc_damage(NULL,sd,1); + hp = 0; + } + sd->status.sp+=sp; + if(sd->status.sp <= 0) + sd->status.sp = 0; + if(hp) + clif_updatestatus(sd,SP_HP); + if(sp) + clif_updatestatus(sd,SP_SP); + + return 0; +} + +/*========================================== + * HP/SP回復 + *------------------------------------------ + */ +int pc_percentheal(struct map_session_data *sd,int hp,int sp) +{ + nullpo_retr(0, sd); + + if(sd->state.potionpitcher_flag) { + sd->potion_per_hp = hp; + sd->potion_per_sp = sp; + return 0; + } + + if(pc_checkoverhp(sd)) { + if(hp > 0) + hp = 0; + } + if(pc_checkoversp(sd)) { + if(sp > 0) + sp = 0; + } + if(hp) { + if(hp >= 100) { + sd->status.hp = sd->status.max_hp; + } + else if(hp <= -100) { + sd->status.hp = 0; + pc_damage(NULL,sd,1); + } + else { + sd->status.hp += sd->status.max_hp*hp/100; + if(sd->status.hp > sd->status.max_hp) + sd->status.hp = sd->status.max_hp; + if(sd->status.hp <= 0) { + sd->status.hp = 0; + pc_damage(NULL,sd,1); + hp = 0; + } + } + } + if(sp) { + if(sp >= 100) { + sd->status.sp = sd->status.max_sp; + } + else if(sp <= -100) { + sd->status.sp = 0; + } + else { + sd->status.sp += sd->status.max_sp*sp/100; + if(sd->status.sp > sd->status.max_sp) + sd->status.sp = sd->status.max_sp; + if(sd->status.sp < 0) + sd->status.sp = 0; + } + } + if(hp) + clif_updatestatus(sd,SP_HP); + if(sp) + clif_updatestatus(sd,SP_SP); + + return 0; +} + +/*========================================== + * 職変更 + * 引数 job 職業 0〜23 + * upper 通常 0, 転生 1, 養子 2, そのまま -1 + *------------------------------------------ + */ +int pc_jobchange(struct map_session_data *sd,int job, int upper) +{ + int i; + int b_class = 0; + //転生や養子の場合の元の職業を算出する + struct pc_base_job s_class = pc_calc_base_job(sd->status.class); + + nullpo_retr(0, sd); + + if((job > 23) && (job < 68)) + job += 3977; + + if((job > 69) && (job < 4000)) + return 1; + + if(upper < 0) //現在転生かどうかを判断する + upper = s_class.upper; + + if(upper == 0){ //通常職ならjobそのまんま + b_class = job; + }else if(upper == 1){ + if(job == 23){ //転生にスパノビは存在しないのでお断り + return 1; + }else{ + b_class = job + 4001; + } + }else if(upper == 2){ //養子に結婚はないけどどうせ次で蹴られるからいいや + b_class = (job==23)?job + 4022:job + 4023; + }else{ + return 1; + } + + if((sd->status.sex == 0 && job == 19) || (sd->status.sex == 1 && job == 20) || + (sd->status.sex == 0 && job == 4020) || (sd->status.sex == 1 && job == 4021) || + job ==22 || sd->status.class == b_class) //♀はバードになれない、♂はダンサーになれない、結婚衣裳もお断り + return 1; + + sd->status.class = sd->view_class = b_class; + + sd->status.job_level=1; + sd->status.job_exp=0; + clif_updatestatus(sd,SP_JOBLEVEL); + clif_updatestatus(sd,SP_JOBEXP); + clif_updatestatus(sd,SP_NEXTJOBEXP); + + for(i=0;i<11;i++) { + if(sd->equip_index[i] >= 0) + if(!pc_isequip(sd,sd->equip_index[i])) + pc_unequipitem(sd,sd->equip_index[i],1); // 装備外し + } + + clif_changelook(&sd->bl,LOOK_BASE,sd->view_class); // move sprite update to prevent client crashes with incompatible equipment [Valaris] + if(sd->status.clothes_color > 0) + clif_changelook(&sd->bl,LOOK_CLOTHES_COLOR,sd->status.clothes_color); + if(battle_config.muting_players && sd->status.manner < 0) + clif_changestatus(&sd->bl,SP_MANNER,sd->status.manner); + + pc_calcstatus(sd,0); + pc_checkallowskill(sd); + pc_equiplookall(sd); + clif_equiplist(sd); + + if(pc_isriding(sd)) { // remove peco status if changing into invalid class [Valaris] + if(!(pc_checkskill(sd,KN_RIDING))) + pc_setoption(sd,sd->status.option|-0x0000); + if(pc_checkskill(sd,KN_RIDING)>0) + pc_setriding(sd); + } + + return 0; +} + +/*========================================== + * 見た目変更 + *------------------------------------------ + */ +int pc_equiplookall(struct map_session_data *sd) +{ + nullpo_retr(0, sd); + +#if PACKETVER < 4 + clif_changelook(&sd->bl,LOOK_WEAPON,sd->status.weapon); + clif_changelook(&sd->bl,LOOK_SHIELD,sd->status.shield); +#else + clif_changelook(&sd->bl,LOOK_WEAPON,0); + clif_changelook(&sd->bl,LOOK_SHOES,0); +#endif + clif_changelook(&sd->bl,LOOK_HEAD_BOTTOM,sd->status.head_bottom); + clif_changelook(&sd->bl,LOOK_HEAD_TOP,sd->status.head_top); + clif_changelook(&sd->bl,LOOK_HEAD_MID,sd->status.head_mid); + + return 0; +} + +/*========================================== + * 見た目変更 + *------------------------------------------ + */ +int pc_changelook(struct map_session_data *sd,int type,int val) +{ + nullpo_retr(0, sd); + + switch(type){ + case LOOK_HAIR: + sd->status.hair=val; + break; + case LOOK_WEAPON: + sd->status.weapon=val; + break; + case LOOK_HEAD_BOTTOM: + sd->status.head_bottom=val; + break; + case LOOK_HEAD_TOP: + sd->status.head_top=val; + break; + case LOOK_HEAD_MID: + sd->status.head_mid=val; + break; + case LOOK_HAIR_COLOR: + sd->status.hair_color=val; + break; + case LOOK_CLOTHES_COLOR: + sd->status.clothes_color=val; + break; + case LOOK_SHIELD: + sd->status.shield=val; + break; + case LOOK_SHOES: + break; + } + clif_changelook(&sd->bl,type,val); + + return 0; +} + +/*========================================== + * 付属品(鷹,ペコ,カート)設定 + *------------------------------------------ + */ +int pc_setoption(struct map_session_data *sd,int type) +{ + nullpo_retr(0, sd); + + sd->status.option=type; + clif_changeoption(&sd->bl); + pc_calcstatus(sd,0); + + return 0; +} + +/*========================================== + * カート設定 + *------------------------------------------ + */ +int pc_setcart(struct map_session_data *sd,int type) +{ + int cart[6]={0x0000,0x0008,0x0080,0x0100,0x0200,0x0400}; + + nullpo_retr(0, sd); + + if(pc_checkskill(sd,MC_PUSHCART)>0){ // プッシュカートスキル所持 + if(!pc_iscarton(sd)){ // カートを付けていない + pc_setoption(sd,cart[type]); + clif_cart_itemlist(sd); + clif_cart_equiplist(sd); + clif_updatestatus(sd,SP_CARTINFO); + clif_status_change(&sd->bl,0x0c,0); + } + else{ + pc_setoption(sd,cart[type]); + } + } + + return 0; +} + +/*========================================== + * 鷹設定 + *------------------------------------------ + */ +int pc_setfalcon(struct map_session_data *sd) +{ + if(pc_checkskill(sd,HT_FALCON)>0){ // ファルコンマスタリースキル所持 + pc_setoption(sd,sd->status.option|0x0010); + } + + return 0; +} + +/*========================================== + * ペコペコ設定 + *------------------------------------------ + */ +int pc_setriding(struct map_session_data *sd) +{ + if(sd->disguise > 0) { // temporary prevention of crash caused by peco + disguise, will look into a better solution [Valaris] + clif_displaymessage(sd->fd, "Cannot mount a Peco while in disguise."); + return 0; + } + + if((pc_checkskill(sd,KN_RIDING)>0)){ // ライディングスキル所持 + pc_setoption(sd,sd->status.option|0x0020); + + if(sd->status.class==7) + sd->status.class=sd->view_class=13; + + if(sd->status.class==14) + sd->status.class=sd->view_class=21; + + if(sd->status.class==4008) + sd->status.class=sd->view_class=4014; + + if(sd->status.class==4015) + sd->status.class=sd->view_class=4022; + } + + return 0; +} + +/*========================================== + * script用変数の値を読む + *------------------------------------------ + */ +int pc_readreg(struct map_session_data *sd,int reg) +{ + int i; + + nullpo_retr(0, sd); + + for(i=0;i<sd->reg_num;i++) + if(sd->reg[i].index==reg) + return sd->reg[i].data; + + return 0; +} +/*========================================== + * script用変数の値を設定 + *------------------------------------------ + */ +int pc_setreg(struct map_session_data *sd,int reg,int val) +{ + int i; + + nullpo_retr(0, sd); + + for (i = 0; i < sd->reg_num; i++) { + if (sd->reg[i].index == reg){ + sd->reg[i].data = val; + return 0; + } + } + sd->reg_num++; + sd->reg = realloc(sd->reg, sizeof(*(sd->reg)) * sd->reg_num); + if (sd->reg == NULL){ + printf("out of memory : pc_setreg\n"); + exit(1); + } +/* memset(sd->reg + (sd->reg_num - 1) * sizeof(*(sd->reg)), 0, + sizeof(*(sd->reg))); +*/ + sd->reg[i].index = reg; + sd->reg[i].data = val; + + return 0; +} + +/*========================================== + * script用文字列変数の値を読む + *------------------------------------------ + */ +char *pc_readregstr(struct map_session_data *sd,int reg) +{ + int i; + + nullpo_retr(0, sd); + + for(i=0;i<sd->regstr_num;i++) + if(sd->regstr[i].index==reg) + return sd->regstr[i].data; + + return NULL; +} +/*========================================== + * script用文字列変数の値を設定 + *------------------------------------------ + */ +int pc_setregstr(struct map_session_data *sd,int reg,char *str) +{ + int i; + + nullpo_retr(0, sd); + + if(strlen(str)+1 >= sizeof(sd->regstr[0].data)){ + printf("pc_setregstr: string too long !\n"); + return 0; + } + + for(i=0;i<sd->regstr_num;i++) + if(sd->regstr[i].index==reg){ + strcpy(sd->regstr[i].data,str); + return 0; + } + sd->regstr_num++; + sd->regstr = realloc(sd->regstr, sizeof(sd->regstr[0]) * sd->regstr_num); + if(sd->regstr==NULL){ + printf("out of memory : pc_setreg\n"); + exit(1); + } +/* memset(sd->reg + (sd->reg_num - 1) * sizeof(*(sd->reg)), 0, + sizeof(*(sd->reg))); +*/ + sd->regstr[i].index=reg; + strcpy(sd->regstr[i].data,str); + + return 0; +} + +/*========================================== + * script用グローバル変数の値を読む + *------------------------------------------ + */ +int pc_readglobalreg(struct map_session_data *sd,char *reg) +{ + int i; + + nullpo_retr(0, sd); + + for(i=0;i<sd->status.global_reg_num;i++){ + if(strcmp(sd->status.global_reg[i].str,reg)==0) + return sd->status.global_reg[i].value; + } + + return 0; +} + +/*========================================== + * script用グローバル変数の値を設定 + *------------------------------------------ + */ +int pc_setglobalreg(struct map_session_data *sd,char *reg,int val) +{ + int i; + + nullpo_retr(0, sd); + + //PC_DIE_COUNTERがスクリプトなどで変更された時の処理 + if(strcmp(reg,"PC_DIE_COUNTER") == 0 && sd->die_counter != val){ + sd->die_counter = val; + pc_calcstatus(sd,0); + } + if(val==0){ + for(i=0;i<sd->status.global_reg_num;i++){ + if(strcmp(sd->status.global_reg[i].str,reg)==0){ + sd->status.global_reg[i]=sd->status.global_reg[sd->status.global_reg_num-1]; + sd->status.global_reg_num--; + break; + } + } + return 0; + } + for(i=0;i<sd->status.global_reg_num;i++){ + if(strcmp(sd->status.global_reg[i].str,reg)==0){ + sd->status.global_reg[i].value=val; + return 0; + } + } + if(sd->status.global_reg_num<GLOBAL_REG_NUM){ + strcpy(sd->status.global_reg[i].str,reg); + sd->status.global_reg[i].value=val; + sd->status.global_reg_num++; + return 0; + } + if(battle_config.error_log) + printf("pc_setglobalreg : couldn't set %s (GLOBAL_REG_NUM = %d)\n", reg, GLOBAL_REG_NUM); + + return 1; +} + +/*========================================== + * script用アカウント変数の値を読む + *------------------------------------------ + */ +int pc_readaccountreg(struct map_session_data *sd,char *reg) +{ + int i; + + nullpo_retr(0, sd); + + for(i=0;i<sd->status.account_reg_num;i++){ + if(strcmp(sd->status.account_reg[i].str,reg)==0) + return sd->status.account_reg[i].value; + } + + return 0; +} +/*========================================== + * script用アカウント変数の値を設定 + *------------------------------------------ + */ +int pc_setaccountreg(struct map_session_data *sd,char *reg,int val) +{ + int i; + + nullpo_retr(0, sd); + + if(val==0){ + for(i=0;i<sd->status.account_reg_num;i++){ + if(strcmp(sd->status.account_reg[i].str,reg)==0){ + sd->status.account_reg[i]=sd->status.account_reg[sd->status.account_reg_num-1]; + sd->status.account_reg_num--; + break; + } + } + intif_saveaccountreg(sd); + return 0; + } + for(i=0;i<sd->status.account_reg_num;i++){ + if(strcmp(sd->status.account_reg[i].str,reg)==0){ + sd->status.account_reg[i].value=val; + intif_saveaccountreg(sd); + return 0; + } + } + if(sd->status.account_reg_num<ACCOUNT_REG_NUM){ + strcpy(sd->status.account_reg[i].str,reg); + sd->status.account_reg[i].value=val; + sd->status.account_reg_num++; + intif_saveaccountreg(sd); + return 0; + } + if(battle_config.error_log) + printf("pc_setaccountreg : couldn't set %s (ACCOUNT_REG_NUM = %d)\n", reg, ACCOUNT_REG_NUM); + + return 1; +} +/*========================================== + * script用アカウント変数2の値を読む + *------------------------------------------ + */ +int pc_readaccountreg2(struct map_session_data *sd,char *reg) +{ + int i; + + nullpo_retr(0, sd); + + for(i=0;i<sd->status.account_reg2_num;i++){ + if(strcmp(sd->status.account_reg2[i].str,reg)==0) + return sd->status.account_reg2[i].value; + } + + return 0; +} +/*========================================== + * script用アカウント変数2の値を設定 + *------------------------------------------ + */ +int pc_setaccountreg2(struct map_session_data *sd,char *reg,int val) +{ + int i; + + nullpo_retr(1, sd); + + if(val==0){ + for(i=0;i<sd->status.account_reg2_num;i++){ + if(strcmp(sd->status.account_reg2[i].str,reg)==0){ + sd->status.account_reg2[i]=sd->status.account_reg2[sd->status.account_reg2_num-1]; + sd->status.account_reg2_num--; + break; + } + } + chrif_saveaccountreg2(sd); + return 0; + } + for(i=0;i<sd->status.account_reg2_num;i++){ + if(strcmp(sd->status.account_reg2[i].str,reg)==0){ + sd->status.account_reg2[i].value=val; + chrif_saveaccountreg2(sd); + return 0; + } + } + if(sd->status.account_reg2_num<ACCOUNT_REG2_NUM){ + strcpy(sd->status.account_reg2[i].str,reg); + sd->status.account_reg2[i].value=val; + sd->status.account_reg2_num++; + chrif_saveaccountreg2(sd); + return 0; + } + if(battle_config.error_log) + printf("pc_setaccountreg2 : couldn't set %s (ACCOUNT_REG2_NUM = %d)\n", reg, ACCOUNT_REG2_NUM); + + return 1; +} +/*========================================== + * 精錬成功率 + *------------------------------------------ + */ +int pc_percentrefinery(struct map_session_data *sd,struct item *item) +{ + int percent; + + nullpo_retr(0, item); + percent=percentrefinery[itemdb_wlv(item->nameid)][(int)item->refine]; + + percent += pc_checkskill(sd,BS_WEAPONRESEARCH); // 武器研究スキル所持 + + // 確率の有効範囲チェック + if( percent > 100 ){ + percent = 100; + } + if( percent < 0 ){ + percent = 0; + } + + return percent; +} + +/*========================================== + * イベントタイマー処理 + *------------------------------------------ + */ +int pc_eventtimer(int tid,unsigned int tick,int id,int data) +{ + struct map_session_data *sd=map_id2sd(id); + int i; + if(sd==NULL) + return 0; + + for(i=0;i<MAX_EVENTTIMER;i++){ + if( sd->eventtimer[i]==tid ){ + sd->eventtimer[i]=-1; + npc_event(sd,(const char *)data,0); + break; + } + } + free((void *)data); + if(i==MAX_EVENTTIMER) { + if(battle_config.error_log) + printf("pc_eventtimer: no such event timer\n"); + } + + return 0; +} + +/*========================================== + * イベントタイマー追加 + *------------------------------------------ + */ +int pc_addeventtimer(struct map_session_data *sd,int tick,const char *name) +{ + int i; + + nullpo_retr(0, sd); + + for(i=0;i<MAX_EVENTTIMER;i++) + if( sd->eventtimer[i]==-1 ) + break; + if(i<MAX_EVENTTIMER){ + char *evname=(char *)aCalloc(24,sizeof(char)); + memcpy(evname,name,24); + sd->eventtimer[i]=add_timer(gettick()+tick, + pc_eventtimer,sd->bl.id,(int)evname); + } + + return 0; +} + +/*========================================== + * イベントタイマー削除 + *------------------------------------------ + */ +int pc_deleventtimer(struct map_session_data *sd,const char *name) +{ + int i; + + nullpo_retr(0, sd); + + for(i=0;i<MAX_EVENTTIMER;i++) + if( sd->eventtimer[i]!=-1 && strcmp( + (char *)(get_timer(sd->eventtimer[i])->data), name)==0 ){ + delete_timer(sd->eventtimer[i],pc_eventtimer); + sd->eventtimer[i]=-1; + break; + } + + return 0; +} + +/*========================================== + * イベントタイマーカウント値追加 + *------------------------------------------ + */ +int pc_addeventtimercount(struct map_session_data *sd,const char *name,int tick) +{ + int i; + + nullpo_retr(0, sd); + + for(i=0;i<MAX_EVENTTIMER;i++) + if( sd->eventtimer[i]!=-1 && strcmp( + (char *)(get_timer(sd->eventtimer[i])->data), name)==0 ){ + addtick_timer(sd->eventtimer[i],tick); + break; + } + + return 0; +} + +/*========================================== + * イベントタイマー全削除 + *------------------------------------------ + */ +int pc_cleareventtimer(struct map_session_data *sd) +{ + int i; + + nullpo_retr(0, sd); + + for(i=0;i<MAX_EVENTTIMER;i++) + if( sd->eventtimer[i]!=-1 ){ + delete_timer(sd->eventtimer[i],pc_eventtimer); + sd->eventtimer[i]=-1; + } + + return 0; +} + +// +// 装 備物 +// +/*========================================== + * アイテムを装備する + *------------------------------------------ + */ +int pc_equipitem(struct map_session_data *sd,int n,int pos) +{ + int i,nameid, arrow; + struct item_data *id; + //転生や養子の場合の元の職業を算出する + + nullpo_retr(0, sd); + + nameid = sd->status.inventory[n].nameid; + id = sd->inventory_data[n]; + pos = pc_equippoint(sd,n); + + if(battle_config.battle_log) + printf("equip %d(%d) %x:%x\n",nameid,n,id->equip,pos); + if(!pc_isequip(sd,n) || !pos || sd->status.inventory[n].broken==1 ) { // [Valaris] + clif_equipitemack(sd,n,0,0); // fail + return 0; + } + +// -- moonsoul (if player is berserk then cannot equip) +// + if(sd->sc_data[SC_BERSERK].timer!=-1){ + clif_equipitemack(sd,n,0,0); // fail + return 0; + } + + if(pos==0x88){ // アクセサリ用例外処理 + int epor=0; + if(sd->equip_index[0] >= 0) + epor |= sd->status.inventory[sd->equip_index[0]].equip; + if(sd->equip_index[1] >= 0) + epor |= sd->status.inventory[sd->equip_index[1]].equip; + epor &= 0x88; + pos = epor == 0x08 ? 0x80 : 0x08; + } + + // 二刀流処理 + if ((pos==0x22) // 一応、装備要求箇所が二刀流武器かチェックする + && (id->equip==2) // 単 手武器 + && (pc_checkskill(sd, AS_LEFT) > 0 || sd->status.class == 12) ) // 左手修錬有 + { + int tpos=0; + if(sd->equip_index[8] >= 0) + tpos |= sd->status.inventory[sd->equip_index[8]].equip; + if(sd->equip_index[9] >= 0) + tpos |= sd->status.inventory[sd->equip_index[9]].equip; + tpos &= 0x02; + pos = tpos == 0x02 ? 0x20 : 0x02; + } + + arrow=pc_search_inventory(sd,pc_checkequip(sd,9)); // Added by RoVeRT + for(i=0;i<11;i++) { + if(sd->equip_index[i] >= 0 && sd->status.inventory[sd->equip_index[i]].equip&pos) { + pc_unequipitem(sd,sd->equip_index[i],1); + } + } + // 弓矢装備 + if(pos==0x8000){ + clif_arrowequip(sd,n); + clif_arrow_fail(sd,3); // 3=矢が装備できました + } + else + clif_equipitemack(sd,n,pos,1); + + for(i=0;i<11;i++) { + if(pos & equip_pos[i]) + sd->equip_index[i] = n; + } + sd->status.inventory[n].equip=pos; + + if(sd->status.inventory[n].equip & 0x0002) { + if(sd->inventory_data[n]) + sd->weapontype1 = sd->inventory_data[n]->look; + else + sd->weapontype1 = 0; + pc_calcweapontype(sd); + clif_changelook(&sd->bl,LOOK_WEAPON,sd->status.weapon); + } + if(sd->status.inventory[n].equip & 0x0020) { + if(sd->inventory_data[n]) { + if(sd->inventory_data[n]->type == 4) { + sd->status.shield = 0; + if(sd->status.inventory[n].equip == 0x0020) + sd->weapontype2 = sd->inventory_data[n]->look; + else + sd->weapontype2 = 0; + } + else if(sd->inventory_data[n]->type == 5) { + sd->status.shield = sd->inventory_data[n]->look; + sd->weapontype2 = 0; + } + } + else + sd->status.shield = sd->weapontype2 = 0; + pc_calcweapontype(sd); + clif_changelook(&sd->bl,LOOK_SHIELD,sd->status.shield); + } + if(sd->status.inventory[n].equip & 0x0001) { + if(sd->inventory_data[n]) + sd->status.head_bottom = sd->inventory_data[n]->look; + else + sd->status.head_bottom = 0; + clif_changelook(&sd->bl,LOOK_HEAD_BOTTOM,sd->status.head_bottom); + } + if(sd->status.inventory[n].equip & 0x0100) { + if(sd->inventory_data[n]) + sd->status.head_top = sd->inventory_data[n]->look; + else + sd->status.head_top = 0; + clif_changelook(&sd->bl,LOOK_HEAD_TOP,sd->status.head_top); + } + if(sd->status.inventory[n].equip & 0x0200) { + if(sd->inventory_data[n]) + sd->status.head_mid = sd->inventory_data[n]->look; + else + sd->status.head_mid = 0; + clif_changelook(&sd->bl,LOOK_HEAD_MID,sd->status.head_mid); + } + if(sd->status.inventory[n].equip & 0x0040) + clif_changelook(&sd->bl,LOOK_SHOES,0); + + pc_checkallowskill(sd); // 装備品でスキルか解除されるかチェック + if (itemdb_look(sd->status.inventory[n].nameid) == 11 && arrow){ // Added by RoVeRT + clif_arrowequip(sd,arrow); + sd->status.inventory[arrow].equip=32768; + } + pc_calcstatus(sd,0); + + if(sd->special_state.infinite_endure) { + if(sd->sc_data[SC_ENDURE].timer == -1) + skill_status_change_start(&sd->bl,SC_ENDURE,10,1,0,0,0,0); + } + else { + if(sd->sc_data[SC_ENDURE].timer != -1 && sd->sc_data[SC_ENDURE].val2) + skill_status_change_end(&sd->bl,SC_ENDURE,-1); + } + + if(sd->sc_data[SC_SIGNUMCRUCIS].timer != -1 && !battle_check_undead(7,sd->def_ele)) + skill_status_change_end(&sd->bl,SC_SIGNUMCRUCIS,-1); + if(sd->sc_data[SC_DANCING].timer!=-1 && (sd->status.weapon != 13 && sd->status.weapon !=14)) + skill_stop_dancing(&sd->bl,0); + + return 0; +} + +/*========================================== + * 装 備した物を外す + *------------------------------------------ + */ +int pc_unequipitem(struct map_session_data *sd,int n,int type) +{ + nullpo_retr(0, sd); + +// -- moonsoul (if player is berserk then cannot unequip) +// + if(sd->sc_data[SC_BERSERK].timer!=-1){ + clif_unequipitemack(sd,n,0,0); + return 0; + } + + if(battle_config.battle_log) + printf("unequip %d %x:%x\n",n,pc_equippoint(sd,n),sd->status.inventory[n].equip); + if(sd->status.inventory[n].equip){ + int i; + for(i=0;i<11;i++) { + if(sd->status.inventory[n].equip & equip_pos[i]) + sd->equip_index[i] = -1; + } + if(sd->status.inventory[n].equip & 0x0002) { + sd->weapontype1 = 0; + sd->status.weapon = sd->weapontype2; + pc_calcweapontype(sd); + clif_changelook(&sd->bl,LOOK_WEAPON,sd->status.weapon); + } + if(sd->status.inventory[n].equip & 0x0020) { + sd->status.shield = sd->weapontype2 = 0; + pc_calcweapontype(sd); + clif_changelook(&sd->bl,LOOK_SHIELD,sd->status.shield); + } + if(sd->status.inventory[n].equip & 0x0001) { + sd->status.head_bottom = 0; + clif_changelook(&sd->bl,LOOK_HEAD_BOTTOM,sd->status.head_bottom); + } + if(sd->status.inventory[n].equip & 0x0100) { + sd->status.head_top = 0; + clif_changelook(&sd->bl,LOOK_HEAD_TOP,sd->status.head_top); + } + if(sd->status.inventory[n].equip & 0x0200) { + sd->status.head_mid = 0; + clif_changelook(&sd->bl,LOOK_HEAD_MID,sd->status.head_mid); + } + if(sd->status.inventory[n].equip & 0x0040) + clif_changelook(&sd->bl,LOOK_SHOES,0); + + if(sd->sc_data[SC_BROKNWEAPON].timer != -1 && sd->status.inventory[n].equip & 0x0002 && + sd->status.inventory[i].broken==1) + skill_status_change_end(&sd->bl,SC_BROKNWEAPON,-1); + + clif_unequipitemack(sd,n,sd->status.inventory[n].equip,1); + sd->status.inventory[n].equip=0; + if(!type) + pc_checkallowskill(sd); + if(sd->weapontype1 == 0 && sd->weapontype2 == 0) + skill_encchant_eremental_end(&sd->bl,-1); //武器持ち誓えは無条件で属性付与解除 + } else { + clif_unequipitemack(sd,n,0,0); + } + if(!type) { + pc_calcstatus(sd,0); + if(sd->sc_data[SC_SIGNUMCRUCIS].timer != -1 && !battle_check_undead(7,sd->def_ele)) + skill_status_change_end(&sd->bl,SC_SIGNUMCRUCIS,-1); + } + + return 0; +} + +/*========================================== + * アイテムのindex番号を詰めたり + * 装 備品の装備可能チェックを行なう + *------------------------------------------ + */ +int pc_checkitem(struct map_session_data *sd) +{ + int i,j,k,id,calc_flag = 0; + struct item_data *it=NULL; + + nullpo_retr(0, sd); + + // 所持品空き詰め + for(i=j=0;i<MAX_INVENTORY;i++){ + if( (id=sd->status.inventory[i].nameid)==0) + continue; + if( battle_config.item_check && !itemdb_available(id) ){ + if(battle_config.error_log) + printf("illeagal item id %d in %d[%s] inventory.\n",id,sd->bl.id,sd->status.name); + pc_delitem(sd,i,sd->status.inventory[i].amount,3); + continue; + } + if(i>j){ + memcpy(&sd->status.inventory[j],&sd->status.inventory[i],sizeof(struct item)); + sd->inventory_data[j] = sd->inventory_data[i]; + } + j++; + } + if(j < MAX_INVENTORY) + memset(&sd->status.inventory[j],0,sizeof(struct item)*(MAX_INVENTORY-j)); + for(k=j;k<MAX_INVENTORY;k++) + sd->inventory_data[k] = NULL; + + // カート内空き詰め + for(i=j=0;i<MAX_CART;i++){ + if( (id=sd->status.cart[i].nameid)==0 ) + continue; + if( battle_config.item_check && !itemdb_available(id) ){ + if(battle_config.error_log) + printf("illeagal item id %d in %d[%s] cart.\n",id,sd->bl.id,sd->status.name); + pc_cart_delitem(sd,i,sd->status.cart[i].amount,1); + continue; + } + if(i>j){ + memcpy(&sd->status.cart[j],&sd->status.cart[i],sizeof(struct item)); + } + j++; + } + if(j < MAX_CART) + memset(&sd->status.cart[j],0,sizeof(struct item)*(MAX_CART-j)); + + // 装 備位置チェック + + for(i=0;i<MAX_INVENTORY;i++){ + + it=sd->inventory_data[i]; + + if(sd->status.inventory[i].nameid==0) + continue; + if(sd->status.inventory[i].equip & ~pc_equippoint(sd,i)) { + sd->status.inventory[i].equip=0; + calc_flag = 1; + } + //装備制限チェック + if(sd->status.inventory[i].equip && map[sd->bl.m].flag.pvp && (it->flag.no_equip==1 || it->flag.no_equip==3)){//PvP制限 + sd->status.inventory[i].equip=0; + calc_flag = 1; + }else if(sd->status.inventory[i].equip && map[sd->bl.m].flag.gvg && (it->flag.no_equip==2 || it->flag.no_equip==3)){//GvG制限 + sd->status.inventory[i].equip=0; + calc_flag = 1; + } + } + + pc_setequipindex(sd); + if(calc_flag) + pc_calcstatus(sd,2); + + return 0; +} + +int pc_checkoverhp(struct map_session_data *sd) +{ + nullpo_retr(0, sd); + + if(sd->status.hp == sd->status.max_hp) + return 1; + if(sd->status.hp > sd->status.max_hp) { + sd->status.hp = sd->status.max_hp; + clif_updatestatus(sd,SP_HP); + return 2; + } + + return 0; +} + +int pc_checkoversp(struct map_session_data *sd) +{ + nullpo_retr(0, sd); + + if(sd->status.sp == sd->status.max_sp) + return 1; + if(sd->status.sp > sd->status.max_sp) { + sd->status.sp = sd->status.max_sp; + clif_updatestatus(sd,SP_SP); + return 2; + } + + return 0; +} + +/*========================================== + * PVP順位計算用(foreachinarea) + *------------------------------------------ + */ +int pc_calc_pvprank_sub(struct block_list *bl,va_list ap) +{ + struct map_session_data *sd1,*sd2=NULL; + + nullpo_retr(0, bl); + nullpo_retr(0, ap); + nullpo_retr(0, sd1=(struct map_session_data *)bl); + nullpo_retr(0, sd2=va_arg(ap,struct map_session_data *)); + + if( sd1->pvp_point > sd2->pvp_point ) + sd2->pvp_rank++; + return 0; +} +/*========================================== + * PVP順位計算 + *------------------------------------------ + */ +int pc_calc_pvprank(struct map_session_data *sd) +{ + int old; + struct map_data *m; + + nullpo_retr(0, sd); + nullpo_retr(0, m=&map[sd->bl.m]); + + old=sd->pvp_rank; + + if( !(m->flag.pvp) ) + return 0; + sd->pvp_rank=1; + map_foreachinarea(pc_calc_pvprank_sub,sd->bl.m,0,0,m->xs,m->ys,BL_PC,sd); + if(old!=sd->pvp_rank || sd->pvp_lastusers!=m->users) + clif_pvpset(sd,sd->pvp_rank,sd->pvp_lastusers=m->users,0); + return sd->pvp_rank; +} +/*========================================== + * PVP順位計算(timer) + *------------------------------------------ + */ +int pc_calc_pvprank_timer(int tid,unsigned int tick,int id,int data) +{ + struct map_session_data *sd=NULL; + if(battle_config.pk_mode) // disable pvp ranking if pk_mode on [Valaris] + return 0; + + sd=map_id2sd(id); + if(sd==NULL) + return 0; + sd->pvp_timer=-1; + if( pc_calc_pvprank(sd)>0 ) + sd->pvp_timer=add_timer( + gettick()+PVP_CALCRANK_INTERVAL, + pc_calc_pvprank_timer,id,data); + return 0; +} + +/*========================================== + * sdは結婚しているか(既婚の場合は相方のchar_idを返す) + *------------------------------------------ + */ +int pc_ismarried(struct map_session_data *sd) +{ + if(sd == NULL) + return -1; + if(sd->status.partner_id > 0) + return sd->status.partner_id; + else + return 0; +} +/*========================================== + * sdがdstsdと結婚(dstsd→sdの結婚処理も同時に行う) + *------------------------------------------ + */ +int pc_marriage(struct map_session_data *sd,struct map_session_data *dstsd) +{ + if(sd == NULL || dstsd == NULL || sd->status.partner_id > 0 || dstsd->status.partner_id > 0) + return -1; + sd->status.partner_id=dstsd->status.char_id; + dstsd->status.partner_id=sd->status.char_id; + return 0; +} + +/*========================================== + * sdが離婚(相手はsd->status.partner_idに依る)(相手も同時に離婚・結婚指輪自動剥奪) + *------------------------------------------ + */ +int pc_divorce(struct map_session_data *sd) +{ + struct map_session_data *p_sd=NULL; + if(sd == NULL || !pc_ismarried(sd)) + return -1; + + if( (p_sd=map_nick2sd(map_charid2nick(sd->status.partner_id))) !=NULL){ + int i; + if(p_sd->status.partner_id != sd->status.char_id || sd->status.partner_id != p_sd->status.char_id){ + printf("pc_divorce: Illegal partner_id sd=%d p_sd=%d\n",sd->status.partner_id,p_sd->status.partner_id); + return -1; + } + sd->status.partner_id=0; + p_sd->status.partner_id=0; + for(i=0;i<MAX_INVENTORY;i++) + if(sd->status.inventory[i].nameid == WEDDING_RING_M || sd->status.inventory[i].nameid == WEDDING_RING_F) + pc_delitem(sd,i,1,0); + for(i=0;i<MAX_INVENTORY;i++) + if(p_sd->status.inventory[i].nameid == WEDDING_RING_M || p_sd->status.inventory[i].nameid == WEDDING_RING_F) + pc_delitem(p_sd,i,1,0); + + }else{ + printf("pc_divorce: p_sd nullpo\n"); + return -1; + } + return 0; +} + +/*========================================== + * sdの相方のmap_session_dataを返す + *------------------------------------------ + */ +struct map_session_data *pc_get_partner(struct map_session_data *sd) +{ + struct map_session_data *p_sd = NULL; + char *nick; + if(sd == NULL || !pc_ismarried(sd)) + return NULL; + + nick=map_charid2nick(sd->status.partner_id); + + if (nick==NULL) + return NULL; + + if((p_sd=map_nick2sd(nick)) == NULL ) + return NULL; + + return p_sd; +} + +// +// 自然回復物 +// +/*========================================== + * SP回復量計算 + *------------------------------------------ + */ +static int natural_heal_tick,natural_heal_prev_tick,natural_heal_diff_tick; +static int pc_spheal(struct map_session_data *sd) +{ + int a; + struct guild_castle *gc = NULL; + + nullpo_retr(0, sd); + + a = natural_heal_diff_tick; + if(pc_issit(sd)) a += a; + if( sd->sc_data[SC_MAGNIFICAT].timer!=-1 ) // マグニフィカート + a += a; + + gc=guild_mapname2gc(sd->mapname); // Increased guild castle regen [Valaris] + if(gc) { + struct guild *g; + g=guild_search(sd->status.guild_id); + if(g && g->guild_id == gc->guild_id) + a += a; + } // end addition [Valaris] + + return a; +} + +/*========================================== + * HP回復量計算 + *------------------------------------------ + */ +static int pc_hpheal(struct map_session_data *sd) +{ + int a; + struct guild_castle *gc; + + nullpo_retr(0, sd); + + a = natural_heal_diff_tick; + if(pc_issit(sd)) a += a; + if( sd->sc_data[SC_MAGNIFICAT].timer!=-1 ) // Modified by RoVeRT + a += a; + + gc=guild_mapname2gc(sd->mapname); // Increased guild castle regen [Valaris] + if(gc) { + struct guild *g; + g=guild_search(sd->status.guild_id); + if(g && g->guild_id == gc->guild_id) + a += a; + } // end addition [Valaris] + + return a; +} + +static int pc_natural_heal_hp(struct map_session_data *sd) +{ + int bhp; + int inc_num,bonus,skill,hp_flag; + + nullpo_retr(0, sd); + + if (sd->sc_data[SC_TRICKDEAD].timer != -1) // Modified by RoVeRT + return 0; + + if(pc_checkoverhp(sd)) { + sd->hp_sub = sd->inchealhptick = 0; + return 0; + } + + bhp=sd->status.hp; + hp_flag = (pc_checkskill(sd,SM_MOVINGRECOVERY) > 0 && sd->walktimer != -1); + + if(sd->walktimer == -1) { + inc_num = pc_hpheal(sd); + if( sd->sc_data[SC_TENSIONRELAX].timer!=-1 ){ // テンションリラックス + sd->hp_sub += 2*inc_num; + sd->inchealhptick += 3*natural_heal_diff_tick; + }else{ + sd->hp_sub += inc_num; + sd->inchealhptick += natural_heal_diff_tick; + } + } + else if(hp_flag) { + inc_num = pc_hpheal(sd); + sd->hp_sub += inc_num; + sd->inchealhptick = 0; + } + else { + sd->hp_sub = sd->inchealhptick = 0; + return 0; + } + + if(sd->hp_sub >= battle_config.natural_healhp_interval) { + bonus = sd->nhealhp; + if(hp_flag) { + bonus >>= 2; + if(bonus <= 0) bonus = 1; + } + while(sd->hp_sub >= battle_config.natural_healhp_interval) { + sd->hp_sub -= battle_config.natural_healhp_interval; + if(sd->status.hp + bonus <= sd->status.max_hp) + sd->status.hp += bonus; + else { + sd->status.hp = sd->status.max_hp; + sd->hp_sub = sd->inchealhptick = 0; + } + } + } + if(bhp!=sd->status.hp) + clif_updatestatus(sd,SP_HP); + + if(sd->nshealhp > 0) { + if(sd->inchealhptick >= battle_config.natural_heal_skill_interval && sd->status.hp < sd->status.max_hp) { + bonus = sd->nshealhp; + while(sd->inchealhptick >= battle_config.natural_heal_skill_interval) { + sd->inchealhptick -= battle_config.natural_heal_skill_interval; + if(sd->status.hp + bonus <= sd->status.max_hp) + sd->status.hp += bonus; + else { + bonus = sd->status.max_hp - sd->status.hp; + sd->status.hp = sd->status.max_hp; + sd->hp_sub = sd->inchealhptick = 0; + } + clif_heal(sd->fd,SP_HP,bonus); + } + } + } + else sd->inchealhptick = 0; + + return 0; + + if(sd->sc_data[SC_APPLEIDUN].timer!=-1) { // Apple of Idun + if(sd->inchealhptick >= 6000 && sd->status.hp < sd->status.max_hp) { + bonus = skill*20; + while(sd->inchealhptick >= 6000) { + sd->inchealhptick -= 6000; + if(sd->status.hp + bonus <= sd->status.max_hp) + sd->status.hp += bonus; + else { + bonus = sd->status.max_hp - sd->status.hp; + sd->status.hp = sd->status.max_hp; + sd->hp_sub = sd->inchealhptick = 0; + } + clif_heal(sd->fd,SP_HP,bonus); + } + } + } + else sd->inchealhptick = 0; + + return 0; +} + +static int pc_natural_heal_sp(struct map_session_data *sd) +{ + int bsp; + int inc_num,bonus; + + nullpo_retr(0, sd); + + if (sd->sc_data[SC_TRICKDEAD].timer != -1) // Modified by RoVeRT + return 0; + + if(pc_checkoversp(sd)) { + sd->sp_sub = sd->inchealsptick = 0; + return 0; + } + + bsp=sd->status.sp; + + inc_num = pc_spheal(sd); + if(sd->sc_data[SC_EXPLOSIONSPIRITS].timer == -1) + sd->sp_sub += inc_num; + if(sd->walktimer == -1) + sd->inchealsptick += natural_heal_diff_tick; + else sd->inchealsptick = 0; + + if(sd->sp_sub >= battle_config.natural_healsp_interval){ + bonus = sd->nhealsp;; + while(sd->sp_sub >= battle_config.natural_healsp_interval){ + sd->sp_sub -= battle_config.natural_healsp_interval; + if(sd->status.sp + bonus <= sd->status.max_sp) + sd->status.sp += bonus; + else { + sd->status.sp = sd->status.max_sp; + sd->sp_sub = sd->inchealsptick = 0; + } + } + } + + if(bsp != sd->status.sp) + clif_updatestatus(sd,SP_SP); + + if(sd->nshealsp > 0) { + if(sd->inchealsptick >= battle_config.natural_heal_skill_interval && sd->status.sp < sd->status.max_sp) { + struct pc_base_job s_class = pc_calc_base_job(sd->status.class); + if(sd->doridori_counter && s_class.job == 23) + bonus = sd->nshealsp*2; + else + bonus = sd->nshealsp; + sd->doridori_counter = 0; + while(sd->inchealsptick >= battle_config.natural_heal_skill_interval) { + sd->inchealsptick -= battle_config.natural_heal_skill_interval; + if(sd->status.sp + bonus <= sd->status.max_sp) + sd->status.sp += bonus; + else { + bonus = sd->status.max_sp - sd->status.sp; + sd->status.sp = sd->status.max_sp; + sd->sp_sub = sd->inchealsptick = 0; + } + clif_heal(sd->fd,SP_SP,bonus); + } + } + } + else sd->inchealsptick = 0; + + return 0; +} + +static int pc_spirit_heal_hp(struct map_session_data *sd,int level) +{ + int bonus_hp,interval = battle_config.natural_heal_skill_interval; + + nullpo_retr(0, sd); + + if(pc_checkoverhp(sd)) { + sd->inchealspirithptick = 0; + return 0; + } + + sd->inchealspirithptick += natural_heal_diff_tick; + + if(sd->weight*100/sd->max_weight >= battle_config.natural_heal_weight_rate) + interval += interval; + + if(sd->inchealspirithptick >= interval) { + bonus_hp = sd->nsshealhp; + while(sd->inchealspirithptick >= interval) { + if(pc_issit(sd)) { + sd->inchealspirithptick -= interval; + if(sd->status.hp < sd->status.max_hp) { + if(sd->status.hp + bonus_hp <= sd->status.max_hp) + sd->status.hp += bonus_hp; + else { + bonus_hp = sd->status.max_hp - sd->status.hp; + sd->status.hp = sd->status.max_hp; + } + clif_heal(sd->fd,SP_HP,bonus_hp); + sd->inchealspirithptick = 0; + } + }else{ + sd->inchealspirithptick -= natural_heal_diff_tick; + break; + } + } + } + + return 0; +} +static int pc_spirit_heal_sp(struct map_session_data *sd,int level) +{ + int bonus_sp,interval = battle_config.natural_heal_skill_interval; + + nullpo_retr(0, sd); + + if(pc_checkoversp(sd)) { + sd->inchealspiritsptick = 0; + return 0; + } + + sd->inchealspiritsptick += natural_heal_diff_tick; + + if(sd->weight*100/sd->max_weight >= battle_config.natural_heal_weight_rate) + interval += interval; + + if(sd->inchealspiritsptick >= interval) { + bonus_sp = sd->nsshealsp; + while(sd->inchealspiritsptick >= interval) { + if(pc_issit(sd)) { + sd->inchealspiritsptick -= interval; + if(sd->status.sp < sd->status.max_sp) { + if(sd->status.sp + bonus_sp <= sd->status.max_sp) + sd->status.sp += bonus_sp; + else { + bonus_sp = sd->status.max_sp - sd->status.sp; + sd->status.sp = sd->status.max_sp; + } + clif_heal(sd->fd,SP_SP,bonus_sp); + sd->inchealspiritsptick = 0; + } + }else{ + sd->inchealspiritsptick -= natural_heal_diff_tick; + break; + } + } + } + + return 0; +} + +/*========================================== + * HP/SP 自然回復 各クライアント + *------------------------------------------ + */ + +static int pc_natural_heal_sub(struct map_session_data *sd,va_list ap) { + int skill; + + nullpo_retr(0, sd); + +// -- moonsoul (if conditions below altered to disallow natural healing if under berserk status) + if ((battle_config.natural_heal_weight_rate > 100 || sd->weight*100/sd->max_weight < battle_config.natural_heal_weight_rate) && + !pc_isdead(sd) && + !pc_ishiding(sd) && + sd->sc_data[SC_POISON].timer == -1 + ) { + pc_natural_heal_hp(sd); + if( sd->sc_data && sd->sc_data[SC_EXTREMITYFIST].timer == -1 && //阿修羅状態ではSPが回復しない + sd->sc_data[SC_DANCING].timer == -1 && //ダンス状態ではSPが回復しない + sd->sc_data[SC_BERSERK].timer == -1 //バーサーク状態ではSPが回復しない + ) + pc_natural_heal_sp(sd); + } else { + sd->hp_sub = sd->inchealhptick = 0; + sd->sp_sub = sd->inchealsptick = 0; + } + if((skill = pc_checkskill(sd,MO_SPIRITSRECOVERY)) > 0 && !pc_ishiding(sd) && sd->sc_data[SC_POISON].timer == -1 && sd->sc_data[SC_BERSERK].timer == -1){ + pc_spirit_heal_hp(sd,skill); + pc_spirit_heal_sp(sd,skill); + } + else { + sd->inchealspirithptick = 0; + sd->inchealspiritsptick = 0; + } + return 0; +} + +/*========================================== + * HP/SP自然回復 (interval timer関数) + *------------------------------------------ + */ +int pc_natural_heal(int tid,unsigned int tick,int id,int data) +{ + natural_heal_tick = tick; + natural_heal_diff_tick = DIFF_TICK(natural_heal_tick,natural_heal_prev_tick); + clif_foreachclient(pc_natural_heal_sub); + + natural_heal_prev_tick = tick; + return 0; +} + +/*========================================== + * セーブポイントの保存 + *------------------------------------------ + */ +int pc_setsavepoint(struct map_session_data *sd,char *mapname,int x,int y) +{ + nullpo_retr(0, sd); + + strncpy(sd->status.save_point.map,mapname,24); + sd->status.save_point.x = x; + sd->status.save_point.y = y; + + return 0; +} + +/*========================================== + * 自動セーブ 各クライアント + *------------------------------------------ + */ +static int last_save_fd,save_flag; +static int pc_autosave_sub(struct map_session_data *sd,va_list ap) +{ + nullpo_retr(0, sd); + + if(save_flag==0 && sd->fd>last_save_fd){ + struct guild_castle *gc=NULL; + int i; +// if(battle_config.save_log) +// printf("autosave %d\n",sd->fd); + // pet + if(sd->status.pet_id > 0 && sd->pd) + intif_save_petdata(sd->status.account_id,&sd->pet); + pc_makesavestatus(sd); + chrif_save(sd); + storage_storage_save(sd); + + for(i=0;i<MAX_GUILDCASTLE;i++){ + gc=guild_castle_search(i); + if(!gc) continue; + if(gc->visibleG0==1) guild_castledatasave(gc->castle_id,18,gc->Ghp0); + if(gc->visibleG1==1) guild_castledatasave(gc->castle_id,19,gc->Ghp1); + if(gc->visibleG2==1) guild_castledatasave(gc->castle_id,20,gc->Ghp2); + if(gc->visibleG3==1) guild_castledatasave(gc->castle_id,21,gc->Ghp3); + if(gc->visibleG4==1) guild_castledatasave(gc->castle_id,22,gc->Ghp4); + if(gc->visibleG5==1) guild_castledatasave(gc->castle_id,23,gc->Ghp5); + if(gc->visibleG6==1) guild_castledatasave(gc->castle_id,24,gc->Ghp6); + if(gc->visibleG7==1) guild_castledatasave(gc->castle_id,25,gc->Ghp7); + } + + save_flag=1; + last_save_fd = sd->fd; + } + + return 0; +} + +/*========================================== + * 自動セーブ (timer関数) + *------------------------------------------ + */ +int pc_autosave(int tid,unsigned int tick,int id,int data) +{ + int interval; + + save_flag=0; + clif_foreachclient(pc_autosave_sub); + if(save_flag==0) + last_save_fd=0; + + interval = autosave_interval/(clif_countusers()+1); + if(interval <= 0) + interval = 1; + add_timer(gettick()+interval,pc_autosave,0,0); + + return 0; +} + +int pc_read_gm_account(int fd) +{ +#ifdef TXT_ONLY + int i = 0; +#endif + if (gm_account != NULL) + free(gm_account); + GM_num = 0; +#ifdef TXT_ONLY + gm_account = calloc(sizeof(struct gm_account) * ((RFIFOW(fd,2) - 4) / 5), 1); + for (i = 4; i < RFIFOW(fd,2); i = i + 5) { + gm_account[GM_num].account_id = RFIFOL(fd,i); + gm_account[GM_num].level = (int)RFIFOB(fd,i+4); + //printf("GM account: %d -> level %d\n", gm_account[GM_num].account_id, gm_account[GM_num].level); + GM_num++; + } +#else + sprintf (tmp_lsql, "SELECT `%s`,`%s` FROM `%s` WHERE `%s`>='%d'",login_db_account_id,login_db_level,login_db,login_db_level,lowest_gm_level); + if(mysql_query(&lmysql_handle, tmp_lsql) ) { + printf("DB server Error (select %s to Memory)- %s\n",login_db,mysql_error(&lmysql_handle) ); + } + lsql_res = mysql_store_result(&lmysql_handle); + if (lsql_res) { + gm_account = calloc(sizeof(struct gm_account) * mysql_num_rows(lsql_res), 1); + while ((lsql_row = mysql_fetch_row(lsql_res))) { + gm_account[GM_num].account_id = atoi(lsql_row[0]); + gm_account[GM_num].level = atoi(lsql_row[1]); + printf("GM account: %d -> level %d\n", gm_account[GM_num].account_id, gm_account[GM_num].level); + GM_num++; + } + } + + mysql_free_result(lsql_res); +#endif /* TXT_ONLY */ + return GM_num; +} + +/*========================================== + * timer to do the day + *------------------------------------------ + */ +int map_day_timer(int tid, unsigned int tick, int id, int data) { // by [yor] + struct map_session_data *pl_sd = NULL; + int i; + char tmpstr[1024]; + + if (battle_config.day_duration > 0) { // if we want a day + if (night_flag != 0) { + strcpy(tmpstr, msg_txt(502)); // The day has arrived! + night_flag = 0; // 0=day, 1=night [Yor] + for(i = 0; i < fd_max; i++) { + if (session[i] && (pl_sd = session[i]->session_data) && pl_sd->state.auth) { + pl_sd->opt2 &= ~STATE_BLIND; + clif_changeoption(&pl_sd->bl); + clif_wis_message(pl_sd->fd, wisp_server_name, tmpstr, strlen(tmpstr)+1); + } + } + } + } + + return 0; +} + +/*========================================== + * timer to do the night + *------------------------------------------ + */ +int map_night_timer(int tid, unsigned int tick, int id, int data) { // by [yor] + struct map_session_data *pl_sd = NULL; + int i; + char tmpstr[1024]; + + if (battle_config.night_duration > 0) { // if we want a night + if (night_flag == 0) { + strcpy(tmpstr, msg_txt(503)); // The night has fallen... + night_flag = 1; // 0=day, 1=night [Yor] + for(i = 0; i < fd_max; i++) { + if (session[i] && (pl_sd = session[i]->session_data) && pl_sd->state.auth) { + pl_sd->opt2 |= STATE_BLIND; + clif_changeoption(&pl_sd->bl); + clif_wis_message(pl_sd->fd, wisp_server_name, tmpstr, strlen(tmpstr)+1); + } + } + } + } + + return 0; +} + +void pc_setstand(struct map_session_data *sd){ + nullpo_retv(sd); + + if(sd->sc_data && sd->sc_data[SC_TENSIONRELAX].timer!=-1) + skill_status_change_end(&sd->bl,SC_TENSIONRELAX,-1); + + sd->state.dead_sit = 0; +} + +// +// 初期化物 +// +/*========================================== + * 設定ファイル読み込む + * exp.txt 必要経験値 + * job_db1.txt 重量,hp,sp,攻撃速度 + * job_db2.txt job能力値ボーナス + * skill_tree.txt 各職毎のスキルツリー + * attr_fix.txt 属性修正テーブル + * size_fix.txt サイズ補正テーブル + * refine_db.txt 精錬データテーブル + *------------------------------------------ + */ +int pc_readdb(void) +{ + int i,j,k; + FILE *fp; + char line[1024],*p; + + // 必要経験値読み込み + + fp=fopen("db/exp.txt","r"); + if(fp==NULL){ + printf("can't read db/exp.txt\n"); + return 1; + } + i=0; + while(fgets(line, sizeof(line)-1, fp)){ + int bn,b1,b2,b3,b4,b5,b6,jn,j1,j2,j3,j4,j5,j6; + if(line[0]=='/' && line[1]=='/') + continue; + if(sscanf(line,"%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d",&bn,&b1,&b2,&b3,&b4,&b5,&b6,&jn,&j1,&j2,&j3,&j4,&j5,&j6)!=14) + continue; + exp_table[0][i]=bn; + exp_table[1][i]=b1; + exp_table[2][i]=b2; + exp_table[3][i]=b3; + exp_table[4][i]=b4; + exp_table[5][i]=b5; + exp_table[6][i]=b6; + exp_table[7][i]=jn; + exp_table[8][i]=j1; + exp_table[9][i]=j2; + exp_table[10][i]=j3; + exp_table[11][i]=j4; + exp_table[12][i]=j5; + exp_table[13][i]=j6; + i++; + if(i >= battle_config.maximum_level) + break; + } + fclose(fp); + printf("read db/exp.txt done\n"); + + // JOB補正数値1 + fp=fopen("db/job_db1.txt","r"); + if(fp==NULL){ + printf("can't read db/job_db1.txt\n"); + return 1; + } + i=0; + while(fgets(line, sizeof(line)-1, fp)){ + char *split[50]; + if(line[0]=='/' && line[1]=='/') + continue; + for(j=0,p=line;j<21 && p;j++){ + split[j]=p; + p=strchr(p,','); + if(p) *p++=0; + } + if(j<21) + continue; + max_weight_base[i]=atoi(split[0]); + hp_coefficient[i]=atoi(split[1]); + hp_coefficient2[i]=atoi(split[2]); + sp_coefficient[i]=atoi(split[3]); + for(j=0;j<17;j++) + aspd_base[i][j]=atoi(split[j+4]); + i++; +// -- moonsoul (below two lines added to accommodate high numbered new class ids) + if(i==24) + i=4001; + if(i==MAX_PC_CLASS) + break; + } + fclose(fp); + printf("read db/job_db1.txt done\n"); + + // JOBボーナス + fp=fopen("db/job_db2.txt","r"); + if(fp==NULL){ + printf("can't read db/job_db2.txt\n"); + return 1; + } + i=0; + while(fgets(line, sizeof(line)-1, fp)){ + if(line[0]=='/' && line[1]=='/') + continue; + for(j=0,p=line;j<MAX_LEVEL && p;j++){ + if(sscanf(p,"%d",&k)==0) + break; + job_bonus[0][i][j]=k; + job_bonus[2][i][j]=k; //養子職のボーナスは分からないので仮 + p=strchr(p,','); + if(p) p++; + } + i++; +// -- moonsoul (below two lines added to accommodate high numbered new class ids) + if(i==24) + i=4001; + if(i==MAX_PC_CLASS) + break; + } + fclose(fp); + printf("read db/job_db2.txt done\n"); + + // JOBボーナス2 転生職用 + fp=fopen("db/job_db2-2.txt","r"); + if(fp==NULL){ + printf("can't read db/job_db2-2.txt\n"); + return 1; + } + i=0; + while(fgets(line, sizeof(line)-1, fp)){ + if(line[0]=='/' && line[1]=='/') + continue; + for(j=0,p=line;j<MAX_LEVEL && p;j++){ + if(sscanf(p,"%d",&k)==0) + break; + job_bonus[1][i][j]=k; + p=strchr(p,','); + if(p) p++; + } + i++; + if(i==MAX_PC_CLASS) + break; + } + fclose(fp); + printf("read db/job_db2-2.txt done\n"); + + // スキルツリー + memset(skill_tree,0,sizeof(skill_tree)); + fp=fopen("db/skill_tree.txt","r"); + if(fp==NULL){ + printf("can't read db/skill_tree.txt\n"); + return 1; + } + while(fgets(line, sizeof(line)-1, fp)){ + char *split[50]; + if(line[0]=='/' && line[1]=='/') + continue; + for(j=0,p=line;j<13 && p;j++){ + split[j]=p; + p=strchr(p,','); + if(p) *p++=0; + } + if(j<13) + continue; + i=atoi(split[0]); + for(j=0;skill_tree[0][i][j].id;j++); + skill_tree[0][i][j].id=atoi(split[1]); + skill_tree[0][i][j].max=atoi(split[2]); + skill_tree[2][i][j].id=atoi(split[1]); //養子職は良く分からないので暫定 + skill_tree[2][i][j].max=atoi(split[2]); //養子職は良く分からないので暫定 + for(k=0;k<5;k++){ + skill_tree[0][i][j].need[k].id=atoi(split[k*2+3]); + skill_tree[0][i][j].need[k].lv=atoi(split[k*2+4]); + skill_tree[2][i][j].need[k].id=atoi(split[k*2+3]); //養子職は良く分からないので暫定 + skill_tree[2][i][j].need[k].lv=atoi(split[k*2+4]); //養子職は良く分からないので暫定 + } + } + fclose(fp); + printf("read db/skill_tree.txt done\n"); + + // 属性修正テーブル + for(i=0;i<4;i++) + for(j=0;j<10;j++) + for(k=0;k<10;k++) + attr_fix_table[i][j][k]=100; + fp=fopen("db/attr_fix.txt","r"); + if(fp==NULL){ + printf("can't read db/attr_fix.txt\n"); + return 1; + } + while(fgets(line, sizeof(line)-1, fp)){ + char *split[10]; + int lv,n; + if(line[0]=='/' && line[1]=='/') + continue; + for(j=0,p=line;j<3 && p;j++){ + split[j]=p; + p=strchr(p,','); + if(p) *p++=0; + } + lv=atoi(split[0]); + n=atoi(split[1]); +// printf("%d %d\n",lv,n); + + for(i=0;i<n;){ + if( !fgets(line, sizeof(line)-1, fp) ) + break; + if(line[0]=='/' && line[1]=='/') + continue; + + for(j=0,p=line;j<n && p;j++){ + while(*p==32 && *p>0) + p++; + attr_fix_table[lv-1][i][j]=atoi(p); + if(battle_config.attr_recover == 0 && attr_fix_table[lv-1][i][j] < 0) + attr_fix_table[lv-1][i][j] = 0; + p=strchr(p,','); + if(p) *p++=0; + } + + i++; + } + } + fclose(fp); + printf("read db/attr_fix.txt done\n"); + + // サイズ補正テーブル + for(i=0;i<3;i++) + for(j=0;j<20;j++) + atkmods[i][j]=100; + fp=fopen("db/size_fix.txt","r"); + if(fp==NULL){ + printf("can't read db/size_fix.txt\n"); + return 1; + } + i=0; + while(fgets(line, sizeof(line)-1, fp)){ + char *split[20]; + if(line[0]=='/' && line[1]=='/') + continue; + if(atoi(line)<=0) + continue; + memset(split,0,sizeof(split)); + for(j=0,p=line;j<20 && p;j++){ + split[j]=p; + p=strchr(p,','); + if(p) *p++=0; + } + for(j=0;j<20 && split[j];j++) + atkmods[i][j]=atoi(split[j]); + i++; + } + fclose(fp); + printf("read db/size_fix.txt done\n"); + + // 精錬データテーブル + for(i=0;i<5;i++){ + for(j=0;j<10;j++) + percentrefinery[i][j]=100; + refinebonus[i][0]=0; + refinebonus[i][1]=0; + refinebonus[i][2]=10; + } + fp=fopen("db/refine_db.txt","r"); + if(fp==NULL){ + printf("can't read db/refine_db.txt\n"); + return 1; + } + i=0; + while(fgets(line, sizeof(line)-1, fp)){ + char *split[16]; + if(line[0]=='/' && line[1]=='/') + continue; + if(atoi(line)<=0) + continue; + memset(split,0,sizeof(split)); + for(j=0,p=line;j<16 && p;j++){ + split[j]=p; + p=strchr(p,','); + if(p) *p++=0; + } + refinebonus[i][0]=atoi(split[0]); // 精錬ボーナス + refinebonus[i][1]=atoi(split[1]); // 過剰精錬ボーナス + refinebonus[i][2]=atoi(split[2]); // 安全精錬限界 + for(j=0;j<10 && split[j];j++) + percentrefinery[i][j]=atoi(split[j+3]); + i++; + } + fclose(fp); //Lupus. close this file!!! + printf("read db/refine_db.txt done\n"); + + return 0; +} + +static int pc_calc_sigma(void) +{ + int i,j,k; + + for(i=0;i<MAX_PC_CLASS;i++) { + memset(hp_sigma_val[i],0,sizeof(hp_sigma_val[i])); + for(k=0,j=2;j<=MAX_LEVEL;j++) { + k += hp_coefficient[i]*j + 50; + k -= k%100; + hp_sigma_val[i][j-1] = k; + } + } + return 0; +} + +static void pc_statpointdb(void) +{ + char * buf_stat; + int i=0,j=0,k=0,l=0, end = 0; + + FILE *stp; + + stp=fopen("db/statpoint.txt","r"); + + if(stp==NULL){ + printf("can't read db/statpoint.txt\n"); + return; + } + + fseek(stp, 0, SEEK_END); + end = ftell(stp); + rewind(stp); + + buf_stat = (char *) malloc (end + 1); + l = fread(buf_stat,1,end,stp); + fclose(stp); + printf("read db/statpoint.txt done (size=%d)\n",l); + + for(i=0;i<255;i++) { + j=0; + while (*(buf_stat+k)!='\n') { + statp[i][j]=*(buf_stat+k); + j++;k++; + } + statp[i][j+1]='\0'; + k++; + } + + free(buf_stat); +} + +/*========================================== + * pc関 係初期化 + *------------------------------------------ + */ +int do_init_pc(void) { + pc_readdb(); + pc_statpointdb(); + pc_calc_sigma(); + +// gm_account_db = numdb_init(); + + add_timer_func_list(pc_walk, "pc_walk"); + add_timer_func_list(pc_attack_timer, "pc_attack_timer"); + add_timer_func_list(pc_natural_heal, "pc_natural_heal"); + add_timer_func_list(pc_invincible_timer, "pc_invincible_timer"); + add_timer_func_list(pc_eventtimer, "pc_eventtimer"); + add_timer_func_list(pc_calc_pvprank_timer, "pc_calc_pvprank_timer"); + add_timer_func_list(pc_autosave, "pc_autosave"); + add_timer_func_list(pc_spiritball_timer, "pc_spiritball_timer"); + add_timer_interval((natural_heal_prev_tick = gettick() + NATURAL_HEAL_INTERVAL), pc_natural_heal, 0, 0, NATURAL_HEAL_INTERVAL); + add_timer(gettick() + autosave_interval, pc_autosave, 0, 0); + +#ifndef TXT_ONLY + pc_read_gm_account(0); +#endif /* not TXT_ONLY */ + + // add night/day timer (by [yor]) + add_timer_func_list(map_day_timer, "map_day_timer"); // by [yor] + add_timer_func_list(map_night_timer, "map_night_timer"); // by [yor] + { + int day_duration = battle_config.day_duration; + int night_duration = battle_config.night_duration; + if (day_duration < 60000) + day_duration = 60000; + if (night_duration < 60000) + night_duration = 60000; + if (battle_config.night_at_start == 0) { + night_flag = 0; // 0=day, 1=night [Yor] + day_timer_tid = add_timer_interval(gettick() + day_duration + night_duration, map_day_timer, 0, 0, day_duration + night_duration); + night_timer_tid = add_timer_interval(gettick() + day_duration, map_night_timer, 0, 0, day_duration + night_duration); + } else { + night_flag = 1; // 0=day, 1=night [Yor] + day_timer_tid = add_timer_interval(gettick() + night_duration, map_day_timer, 0, 0, day_duration + night_duration); + night_timer_tid = add_timer_interval(gettick() + day_duration + night_duration, map_night_timer, 0, 0, day_duration + night_duration); + } + } + + return 0; +} diff --git a/misc/src/map/pc.h b/misc/src/map/pc.h new file mode 100644 index 0000000..1919007 --- /dev/null +++ b/misc/src/map/pc.h @@ -0,0 +1,186 @@ +// $Id: pc.h,v 1.4 2004/09/25 05:32:18 MouseJstr Exp $ + +#ifndef _PC_H_ +#define _PC_H_ + +#include "map.h" + +#define OPTION_MASK 0xd7b8 +#define CART_MASK 0x788 + +#define pc_setdead(sd) ((sd)->state.dead_sit = 1) +#define pc_setsit(sd) ((sd)->state.dead_sit = 2) +//#define pc_setstand(sd) ((sd)->state.dead_sit = 0) +#define pc_isdead(sd) ((sd)->state.dead_sit == 1) +#define pc_issit(sd) ((sd)->state.dead_sit == 2) +#define pc_setdir(sd,b,h) ((sd)->dir = (b) ,(sd)->head_dir = (h) ) +#define pc_setchatid(sd,n) ((sd)->chatID = n) +#define pc_ishiding(sd) ((sd)->status.option&0x4006) +#define pc_iscarton(sd) ((sd)->status.option&CART_MASK) +#define pc_isfalcon(sd) ((sd)->status.option&0x0010) +#define pc_isriding(sd) ((sd)->status.option&0x0020) +#define pc_isinvisible(sd) ((sd)->status.option&0x0040) +#define pc_is50overweight(sd) (sd->weight*2 >= sd->max_weight) +#define pc_is90overweight(sd) (sd->weight*10 >= sd->max_weight*9) + +int pc_isGM(struct map_session_data *sd); +int pc_iskiller(struct map_session_data *src, struct map_session_data *target); // [MouseJstr] +int pc_getrefinebonus(int lv,int type); + +int pc_counttargeted(struct map_session_data *sd,struct block_list *src,int target_lv); +int pc_setrestartvalue(struct map_session_data *sd,int type); +int pc_makesavestatus(struct map_session_data *); +int pc_setnewpc(struct map_session_data*,int,int,int,int,int,int); +int pc_authok(int, int, time_t, struct mmo_charstatus *); +int pc_authfail(int); + +int pc_isequip(struct map_session_data *sd,int n); +int pc_equippoint(struct map_session_data *sd,int n); + +int pc_breakweapon(struct map_session_data *sd); // weapon breaking [Valaris] +int pc_breakarmor(struct map_session_data *sd); // armor breaking [Valaris] + +int pc_checkskill(struct map_session_data *sd,int skill_id); +int pc_checkallowskill(struct map_session_data *sd); +int pc_checkequip(struct map_session_data *sd,int pos); + +int pc_checkoverhp(struct map_session_data*); +int pc_checkoversp(struct map_session_data*); + +int pc_can_reach(struct map_session_data*,int,int); +int pc_walktoxy(struct map_session_data*,int,int); +int pc_stop_walking(struct map_session_data*,int); +int pc_movepos(struct map_session_data*,int,int); +int pc_setpos(struct map_session_data*,char*,int,int,int); +int pc_setsavepoint(struct map_session_data*,char*,int,int); +int pc_randomwarp(struct map_session_data *sd,int type); +int pc_memo(struct map_session_data *sd,int i); + +int pc_checkadditem(struct map_session_data*,int,int); +int pc_inventoryblank(struct map_session_data*); +int pc_search_inventory(struct map_session_data *sd,int item_id); +int pc_payzeny(struct map_session_data*,int); +int pc_additem(struct map_session_data*,struct item*,int); +int pc_getzeny(struct map_session_data*,int); +int pc_delitem(struct map_session_data*,int,int,int); +int pc_checkitem(struct map_session_data*); + +int pc_cart_additem(struct map_session_data *sd,struct item *item_data,int amount); +int pc_cart_delitem(struct map_session_data *sd,int n,int amount,int type); +int pc_putitemtocart(struct map_session_data *sd,int idx,int amount); +int pc_getitemfromcart(struct map_session_data *sd,int idx,int amount); +int pc_cartitem_amount(struct map_session_data *sd,int idx,int amount); + +int pc_takeitem(struct map_session_data*,struct flooritem_data*); +int pc_dropitem(struct map_session_data*,int,int); + +int pc_checkweighticon(struct map_session_data *sd); + +int pc_calcstatus(struct map_session_data*,int); +int pc_bonus(struct map_session_data*,int,int); +int pc_bonus2(struct map_session_data *sd,int,int,int); +int pc_bonus3(struct map_session_data *sd,int,int,int,int); +int pc_skill(struct map_session_data*,int,int,int); + +int pc_insert_card(struct map_session_data *sd,int idx_card,int idx_equip); + +int pc_item_identify(struct map_session_data *sd,int idx); +int pc_steal_item(struct map_session_data *sd,struct block_list *bl); +int pc_steal_coin(struct map_session_data *sd,struct block_list *bl); + +int pc_modifybuyvalue(struct map_session_data*,int); +int pc_modifysellvalue(struct map_session_data*,int); + +int pc_attack(struct map_session_data*,int,int); +int pc_stopattack(struct map_session_data*); + +int pc_follow(struct map_session_data*, int); // [MouseJstr] + +int pc_checkbaselevelup(struct map_session_data *sd); +int pc_checkjoblevelup(struct map_session_data *sd); +int pc_gainexp(struct map_session_data*,int,int); +int pc_nextbaseexp(struct map_session_data *); +int pc_nextbaseafter(struct map_session_data *); // [Valaris] +int pc_nextjobexp(struct map_session_data *); +int pc_nextjobafter(struct map_session_data *); // [Valaris] +int pc_need_status_point(struct map_session_data *,int); +int pc_statusup(struct map_session_data*,int); +int pc_statusup2(struct map_session_data*,int,int); +int pc_skillup(struct map_session_data*,int); +int pc_allskillup(struct map_session_data*); +int pc_resetlvl(struct map_session_data*,int type); +int pc_resetstate(struct map_session_data*); +int pc_resetskill(struct map_session_data*); +int pc_equipitem(struct map_session_data*,int,int); +int pc_unequipitem(struct map_session_data*,int,int); +int pc_checkitem(struct map_session_data*); +int pc_useitem(struct map_session_data*,int); + +int pc_damage(struct block_list *,struct map_session_data*,int); +int pc_heal(struct map_session_data *,int,int); +int pc_itemheal(struct map_session_data *sd,int hp,int sp); +int pc_percentheal(struct map_session_data *sd,int,int); +int pc_jobchange(struct map_session_data *,int, int); +int pc_setoption(struct map_session_data *,int); +int pc_setcart(struct map_session_data *sd,int type); +int pc_setfalcon(struct map_session_data *sd); +int pc_setriding(struct map_session_data *sd); +int pc_changelook(struct map_session_data *,int,int); +int pc_equiplookall(struct map_session_data *sd); + +int pc_readparam(struct map_session_data*,int); +int pc_setparam(struct map_session_data*,int,int); +int pc_readreg(struct map_session_data*,int); +int pc_setreg(struct map_session_data*,int,int); +char *pc_readregstr(struct map_session_data *sd,int reg); +int pc_setregstr(struct map_session_data *sd,int reg,char *str); +int pc_readglobalreg(struct map_session_data*,char*); +int pc_setglobalreg(struct map_session_data*,char*,int); +int pc_readaccountreg(struct map_session_data*,char*); +int pc_setaccountreg(struct map_session_data*,char*,int); +int pc_readaccountreg2(struct map_session_data*,char*); +int pc_setaccountreg2(struct map_session_data*,char*,int); +int pc_percentrefinery(struct map_session_data *sd,struct item *item); + +int pc_addeventtimer(struct map_session_data *sd,int tick,const char *name); +int pc_deleventtimer(struct map_session_data *sd,const char *name); +int pc_cleareventtimer(struct map_session_data *sd); +int pc_addeventtimercount(struct map_session_data *sd,const char *name,int tick); + +int pc_calc_pvprank(struct map_session_data *sd); +int pc_calc_pvprank_timer(int tid,unsigned int tick,int id,int data); + +int pc_ismarried(struct map_session_data *sd); +int pc_marriage(struct map_session_data *sd,struct map_session_data *dstsd); +int pc_divorce(struct map_session_data *sd); +struct map_session_data *pc_get_partner(struct map_session_data *sd); +int pc_set_gm_level(int account_id, int level); +void pc_setstand(struct map_session_data *sd); + + +struct pc_base_job{ + int job; //職業、ただし転生職や養子職の場合は元の職業を返す(廃プリ→プリ) + int type; //ノビ 0, 一次職 1, 二次職 2, スパノビ 3 + int upper; //通常 0, 転生 1, 養子 2 +}; + +struct pc_base_job pc_calc_base_job(int b_class);//転生や養子職の元の職業を返す + +int pc_read_gm_account(int fd); +int pc_setinvincibletimer(struct map_session_data *sd,int); +int pc_delinvincibletimer(struct map_session_data *sd); +int pc_addspiritball(struct map_session_data *sd,int,int); +int pc_delspiritball(struct map_session_data *sd,int,int); + +int do_init_pc(void); + +enum {ADDITEM_EXIST,ADDITEM_NEW,ADDITEM_OVERAMOUNT}; + +// timer for night.day +int day_timer_tid; +int night_timer_tid; +int map_day_timer(int,unsigned int,int,int); // by [yor] +int map_night_timer(int,unsigned int,int,int); // by [yor] + +#endif + diff --git a/misc/src/map/pet.c b/misc/src/map/pet.c new file mode 100644 index 0000000..6026b1e --- /dev/null +++ b/misc/src/map/pet.c @@ -0,0 +1,1651 @@ +// $Id: pet.c,v 1.4 2004/09/25 05:32:18 MouseJstr Exp $ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "db.h" +#include "timer.h" +#include "socket.h" +#include "nullpo.h" +#include "malloc.h" +#include "pc.h" +#include "map.h" +#include "intif.h" +#include "clif.h" +#include "chrif.h" +#include "pet.h" +#include "itemdb.h" +#include "battle.h" +#include "mob.h" +#include "npc.h" +#include "script.h" +#include "skill.h" + +#ifdef MEMWATCH +#include "memwatch.h" +#endif + +#define MIN_PETTHINKTIME 100 + +struct pet_db pet_db[MAX_PET_DB]; + +static int dirx[8]={0,-1,-1,-1,0,1,1,1}; +static int diry[8]={1,1,0,-1,-1,-1,0,1}; + +static int pet_timer(int tid,unsigned int tick,int id,int data); +static int pet_walktoxy_sub(struct pet_data *pd); + +static int distance(int x0,int y0,int x1,int y1) +{ + int dx,dy; + + dx=abs(x0-x1); + dy=abs(y0-y1); + return dx>dy ? dx : dy; +} + +static int calc_next_walk_step(struct pet_data *pd) +{ + nullpo_retr(0, pd); + + if(pd->walkpath.path_pos>=pd->walkpath.path_len) + return -1; + if(pd->walkpath.path[pd->walkpath.path_pos]&1) + return pd->speed*14/10; + return pd->speed; +} + +static int pet_performance_val(struct map_session_data *sd) +{ + nullpo_retr(0, sd); + + if(sd->pet.intimate > 900) + return (sd->petDB->s_perfor > 0)? 4:3; + else if(sd->pet.intimate > 750) + return 2; + else + return 1; +} + +int pet_hungry_val(struct map_session_data *sd) +{ + nullpo_retr(0, sd); + + if(sd->pet.hungry > 90) + return 4; + else if(sd->pet.hungry > 75) + return 3; + else if(sd->pet.hungry > 25) + return 2; + else if(sd->pet.hungry > 10) + return 1; + else + return 0; +} + +static int pet_can_reach(struct pet_data *pd,int x,int y) +{ + struct walkpath_data wpd; + + nullpo_retr(0, pd); + + if( pd->bl.x==x && pd->bl.y==y ) // 同じマス + return 1; + + // 障害物判定 + wpd.path_len=0; + wpd.path_pos=0; + wpd.path_half=0; + return (path_search(&wpd,pd->bl.m,pd->bl.x,pd->bl.y,x,y,0)!=-1)?1:0; +} + +static int pet_calc_pos(struct pet_data *pd,int tx,int ty,int dir) +{ + int x,y,dx,dy; + int i,j=0,k; + + nullpo_retr(0, pd); + + pd->to_x = tx; + pd->to_y = ty; + + if(dir >= 0 && dir < 8) { + dx = -dirx[dir]*2; + dy = -diry[dir]*2; + x = tx + dx; + y = ty + dy; + if(!(j=pet_can_reach(pd,x,y))) { + if(dx > 0) x--; + else if(dx < 0) x++; + if(dy > 0) y--; + else if(dy < 0) y++; + if(!(j=pet_can_reach(pd,x,y))) { + for(i=0;i<12;i++) { + k = rand()%8; + dx = -dirx[k]*2; + dy = -diry[k]*2; + x = tx + dx; + y = ty + dy; + if((j=pet_can_reach(pd,x,y))) + break; + else { + if(dx > 0) x--; + else if(dx < 0) x++; + if(dy > 0) y--; + else if(dy < 0) y++; + if((j=pet_can_reach(pd,x,y))) + break; + } + } + if(!j) { + x = tx; + y = ty; + if(!pet_can_reach(pd,x,y)) + return 1; + } + } + } + } + else + return 1; + + pd->to_x = x; + pd->to_y = y; + return 0; +} + +static int pet_attack(struct pet_data *pd,unsigned int tick,int data) +{ + struct mob_data *md; + int mode,race,range; + + nullpo_retr(0, pd); + + pd->state.state=MS_IDLE; + + md=(struct mob_data *)map_id2bl(pd->target_id); + if(md == NULL || md->bl.type != BL_MOB || pd->bl.m != md->bl.m || md->bl.prev == NULL || + distance(pd->bl.x,pd->bl.y,md->bl.x,md->bl.y) > 13) { + pd->target_id=0; + return 0; + } + + mode=mob_db[pd->class].mode; + race=mob_db[pd->class].race; + if(mob_db[pd->class].mexp <= 0 && !(mode&0x20) && (md->option & 0x06 && race != 4 && race != 6) ) { + pd->target_id=0; + return 0; + } + + range = mob_db[pd->class].range + 1; + if(distance(pd->bl.x,pd->bl.y,md->bl.x,md->bl.y) > range) + return 0; + if(battle_config.monster_attack_direction_change) + pd->dir=map_calc_dir(&pd->bl, md->bl.x,md->bl.y ); + + clif_fixpetpos(pd); + + pd->target_lv = battle_weapon_attack(&pd->bl,&md->bl,tick,0); + + pd->attackabletime = tick + battle_get_adelay(&pd->bl); + + pd->timer=add_timer(pd->attackabletime,pet_timer,pd->bl.id,0); + pd->state.state=MS_ATTACK; + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +static int pet_walk(struct pet_data *pd,unsigned int tick,int data) +{ + int moveblock; + int i,ctype; + int x,y,dx,dy; + + nullpo_retr(0, pd); + + pd->state.state=MS_IDLE; + if(pd->walkpath.path_pos >= pd->walkpath.path_len || pd->walkpath.path_pos != data) + return 0; + + pd->walkpath.path_half ^= 1; + if(pd->walkpath.path_half==0){ + pd->walkpath.path_pos++; + if(pd->state.change_walk_target){ + pet_walktoxy_sub(pd); + return 0; + } + } + else { + if(pd->walkpath.path[pd->walkpath.path_pos] >= 8) + return 1; + + x = pd->bl.x; + y = pd->bl.y; +/* ctype = map_getcell(pd->bl.m,x,y); + if(ctype == 1 || ctype == 5) { + pet_stop_walking(pd,1); + return 0; + }*/ + pd->dir=pd->walkpath.path[pd->walkpath.path_pos]; + dx = dirx[pd->dir]; + dy = diry[pd->dir]; + + ctype = map_getcell(pd->bl.m,x+dx,y+dy); + if(ctype == 1 || ctype == 5) { + pet_walktoxy_sub(pd); + return 0; + } + + moveblock = ( x/BLOCK_SIZE != (x+dx)/BLOCK_SIZE || y/BLOCK_SIZE != (y+dy)/BLOCK_SIZE); + + pd->state.state=MS_WALK; + map_foreachinmovearea(clif_petoutsight,pd->bl.m,x-AREA_SIZE,y-AREA_SIZE,x+AREA_SIZE,y+AREA_SIZE,dx,dy,BL_PC,pd); + + x += dx; + y += dy; + + if(moveblock) map_delblock(&pd->bl); + pd->bl.x = x; + pd->bl.y = y; + if(moveblock) map_addblock(&pd->bl); + + map_foreachinmovearea(clif_petinsight,pd->bl.m,x-AREA_SIZE,y-AREA_SIZE,x+AREA_SIZE,y+AREA_SIZE,-dx,-dy,BL_PC,pd); + pd->state.state=MS_IDLE; + } + if((i=calc_next_walk_step(pd))>0){ + i = i>>1; + if(i < 1 && pd->walkpath.path_half == 0) + i = 1; + pd->timer=add_timer(tick+i,pet_timer,pd->bl.id,pd->walkpath.path_pos); + pd->state.state=MS_WALK; + + if(pd->walkpath.path_pos >= pd->walkpath.path_len) + clif_fixpetpos(pd); + } + return 0; +} + +int pet_stopattack(struct pet_data *pd) +{ + nullpo_retr(0, pd); + + pd->target_id=0; + if(pd->state.state == MS_ATTACK) + pet_changestate(pd,MS_IDLE,0); + + return 0; +} + +int pet_target_check(struct map_session_data *sd,struct block_list *bl,int type) +{ + struct pet_data *pd; + struct mob_data *md; + int rate,mode,race; + + nullpo_retr(0, sd); + + pd = sd->pd; + + if(bl && pd && bl->type == BL_MOB && sd->pet.intimate > 900 && sd->pet.hungry > 0 && pd->class != battle_get_class(bl) + && pd->state.state != MS_DELAY) { + mode=mob_db[pd->class].mode; + race=mob_db[pd->class].race; + md=(struct mob_data *)bl; + if(md->bl.type != BL_MOB || pd->bl.m != md->bl.m || md->bl.prev == NULL || + distance(pd->bl.x,pd->bl.y,md->bl.x,md->bl.y) > 13) + return 0; + if(mob_db[pd->class].mexp <= 0 && !(mode&0x20) && (md->option & 0x06 && race!=4 && race!=6) ) + return 0; + if(!type) { + rate = sd->petDB->attack_rate; + rate = rate * (150 - (sd->pet.intimate - 1000))/100; + if(battle_config.pet_support_rate != 100) + rate = rate*battle_config.pet_support_rate/100; + if(sd->petDB->attack_rate > 0 && rate <= 0) + rate = 1; + } + else { + rate = sd->petDB->defence_attack_rate; + rate = rate * (150 - (sd->pet.intimate - 1000))/100; + if(battle_config.pet_support_rate != 100) + rate = rate*battle_config.pet_support_rate/100; + if(sd->petDB->defence_attack_rate > 0 && rate <= 0) + rate = 1; + } + if(rand()%10000 < rate) { + if(pd->target_id == 0 || rand()%10000 < sd->petDB->change_target_rate) + pd->target_id = bl->id; + } + } + return 0; +} + +int pet_changestate(struct pet_data *pd,int state,int type) +{ + unsigned int tick; + int i; + + nullpo_retr(0, pd); + + if(pd->timer != -1) + delete_timer(pd->timer,pet_timer); + pd->timer=-1; + pd->state.state=state; + + switch(state) { + case MS_WALK: + if((i=calc_next_walk_step(pd)) > 0){ + i = i>>2; + pd->timer=add_timer(gettick()+i,pet_timer,pd->bl.id,0); + } else + pd->state.state=MS_IDLE; + break; + case MS_ATTACK: + tick = gettick(); + i=DIFF_TICK(pd->attackabletime,tick); + if(i>0 && i<2000) + pd->timer=add_timer(pd->attackabletime,pet_timer,pd->bl.id,0); + else + pd->timer=add_timer(tick+1,pet_timer,pd->bl.id,0); + break; + case MS_DELAY: + pd->timer=add_timer(gettick()+type,pet_timer,pd->bl.id,0); + break; + } + + return 0; +} + +static int pet_timer(int tid,unsigned int tick,int id,int data) +{ + struct pet_data *pd; + + pd=(struct pet_data*)map_id2bl(id); + if(pd == NULL || pd->bl.type != BL_PET) + return 1; + + if(pd->timer != tid){ + if(battle_config.error_log) + printf("pet_timer %d != %d\n",pd->timer,tid); + return 0; + } + pd->timer=-1; + + if(pd->bl.prev == NULL) + return 1; + + switch(pd->state.state){ + case MS_WALK: + pet_walk(pd,tick,data); + break; + case MS_ATTACK: + pet_attack(pd,tick,data); + break; + case MS_DELAY: + pet_changestate(pd,MS_IDLE,0); + break; + default: + if(battle_config.error_log) + printf("pet_timer : %d ?\n",pd->state.state); + break; + } + + return 0; +} + +static int pet_walktoxy_sub(struct pet_data *pd) +{ + struct walkpath_data wpd; + + nullpo_retr(0, pd); + + if(path_search(&wpd,pd->bl.m,pd->bl.x,pd->bl.y,pd->to_x,pd->to_y,0)) + return 1; + memcpy(&pd->walkpath,&wpd,sizeof(wpd)); + + pd->state.change_walk_target=0; + pet_changestate(pd,MS_WALK,0); + clif_movepet(pd); +// if(battle_config.etc_log) +// printf("walkstart\n"); + + return 0; +} + +int pet_walktoxy(struct pet_data *pd,int x,int y) +{ + struct walkpath_data wpd; + + nullpo_retr(0, pd); + + if(pd->state.state == MS_WALK && path_search(&wpd,pd->bl.m,pd->bl.x,pd->bl.y,x,y,0)) + return 1; + + pd->to_x=x; + pd->to_y=y; + + if(pd->state.state == MS_WALK) { + pd->state.change_walk_target=1; + } else { + return pet_walktoxy_sub(pd); + } + + return 0; +} + +int pet_stop_walking(struct pet_data *pd,int type) +{ + nullpo_retr(0, pd); + + if(pd->state.state == MS_WALK || pd->state.state == MS_IDLE) { + pd->walkpath.path_len=0; + pd->to_x=pd->bl.x; + pd->to_y=pd->bl.y; + } + if(type&0x01) + clif_fixpetpos(pd); + if(type&~0xff) + pet_changestate(pd,MS_DELAY,type>>8); + else + pet_changestate(pd,MS_IDLE,0); + + return 0; +} + +static int pet_hungry(int tid,unsigned int tick,int id,int data) +{ + struct map_session_data *sd; + int interval,t; + + sd=map_id2sd(id); + if(sd==NULL) + return 1; + + if(sd->pet_hungry_timer != tid){ + if(battle_config.error_log) + printf("pet_hungry_timer %d != %d\n",sd->pet_hungry_timer,tid); + return 0; + } + sd->pet_hungry_timer = -1; + if(!sd->status.pet_id || !sd->pd || !sd->petDB) + return 1; + + sd->pet.hungry--; + t = sd->pet.intimate; + if(sd->pet.hungry < 0) { + if(sd->pd->target_id > 0) + pet_stopattack(sd->pd); + sd->pet.hungry = 0; + sd->pet.intimate -= battle_config.pet_hungry_friendly_decrease; + if(sd->pet.intimate <= 0) { + sd->pet.intimate = 0; + if(battle_config.pet_status_support && t > 0) { + if(sd->bl.prev != NULL) + pc_calcstatus(sd,0); + else + pc_calcstatus(sd,2); + } + } + clif_send_petdata(sd,1,sd->pet.intimate); + } + clif_send_petdata(sd,2,sd->pet.hungry); + + if(battle_config.pet_hungry_delay_rate != 100) + interval = (sd->petDB->hungry_delay*battle_config.pet_hungry_delay_rate)/100; + else + interval = sd->petDB->hungry_delay; + if(interval <= 0) + interval = 1; + sd->pet_hungry_timer = add_timer(tick+interval,pet_hungry,sd->bl.id,0); + + return 0; +} + +int search_petDB_index(int key,int type) +{ + int i; + + for(i=0;i<MAX_PET_DB;i++) { + if(pet_db[i].class <= 0) + continue; + switch(type) { + case PET_CLASS: + if(pet_db[i].class == key) + return i; + break; + case PET_CATCH: + if(pet_db[i].itemID == key) + return i; + break; + case PET_EGG: + if(pet_db[i].EggID == key) + return i; + break; + case PET_EQUIP: + if(pet_db[i].AcceID == key) + return i; + break; + case PET_FOOD: + if(pet_db[i].FoodID == key) + return i; + break; + default: + return -1; + } + } + return -1; +} + +int pet_hungry_timer_delete(struct map_session_data *sd) +{ + nullpo_retr(0, sd); + + if(sd->pet_hungry_timer != -1) { + delete_timer(sd->pet_hungry_timer,pet_hungry); + sd->pet_hungry_timer = -1; + } + + return 0; +} + +int pet_remove_map(struct map_session_data *sd) +{ + nullpo_retr(0, sd); + + if(sd->status.pet_id && sd->pd) { + + struct pet_data *pd=sd->pd; // [Valaris] + if(pd->skillbonustimer!=-1) pd->skillbonustimer=-1; + if(pd->skillbonusduration!=-1) pd->skillbonusduration=-1; + if(pd->skilltype !=-1) pd->skilltype=-1; + if(pd->skillval !=-1) pd->skillval=-1; + if(pd->skilltimer!=-1) pd->skilltimer=-1; + if(pd->skillduration!=-1) pd->skillduration=-1; + if(pd->skillbonustype!=-1) pd->skillbonustype=-1; + if(pd->skillbonusval!=-1) pd->skillbonusval=-1; + if(sd->perfect_hiding==1) sd->perfect_hiding=0; // end additions + + pet_changestate(sd->pd,MS_IDLE,0); + if(sd->pet_hungry_timer != -1) + pet_hungry_timer_delete(sd); + clif_clearchar_area(&sd->pd->bl,0); + map_delblock(&sd->pd->bl); + map_deliddb(&sd->pd->bl); + map_freeblock(sd->pd); + } + return 0; +} +struct delay_item_drop { + int m,x,y; + int nameid,amount; + struct map_session_data *first_sd,*second_sd,*third_sd; +}; + +struct delay_item_drop2 { + int m,x,y; + struct item item_data; + struct map_session_data *first_sd,*second_sd,*third_sd; +}; + +int pet_performance(struct map_session_data *sd) +{ + struct pet_data *pd; + + nullpo_retr(0, sd); + nullpo_retr(0, pd=sd->pd); + + pet_stop_walking(pd,2000<<8); + clif_pet_performance(&pd->bl,rand()%pet_performance_val(sd) + 1); + // ルートしたItemを落とさせる + pet_lootitem_drop(pd,NULL); + + return 0; +} + +int pet_return_egg(struct map_session_data *sd) +{ + struct item tmp_item; + int flag; + + nullpo_retr(0, sd); + + if(sd->status.pet_id && sd->pd) { + struct pet_data *pd=sd->pd; + pet_remove_map(sd); + sd->status.pet_id = 0; + sd->pd = NULL; + + if(sd->petDB == NULL) + return 1; + sd->pet.incuvate = 1; + memset(&tmp_item,0,sizeof(tmp_item)); + tmp_item.nameid = sd->petDB->EggID; + tmp_item.identify = 1; + tmp_item.card[0] = 0xff00; + *((long *)(&tmp_item.card[1])) = sd->pet.pet_id; + tmp_item.card[3] = sd->pet.rename_flag; + if((flag = pc_additem(sd,&tmp_item,1))) { + clif_additem(sd,0,0,flag); + map_addflooritem(&tmp_item,1,sd->bl.m,sd->bl.x,sd->bl.y,NULL,NULL,NULL,0); + } + if(battle_config.pet_status_support && sd->pet.intimate > 0) { + if(sd->bl.prev != NULL) + pc_calcstatus(sd,0); + else + pc_calcstatus(sd,2); + } + // ルートしたItemを落とさせる + pet_lootitem_drop(pd,sd); + + intif_save_petdata(sd->status.account_id,&sd->pet); + pc_makesavestatus(sd); + chrif_save(sd); + storage_storage_save(sd); + + sd->petDB = NULL; + } + + return 0; +} + +int pet_data_init(struct map_session_data *sd) +{ + struct pet_data *pd; + int i=0,interval=0; + + nullpo_retr(1, sd); + + if(sd->status.account_id != sd->pet.account_id || sd->status.char_id != sd->pet.char_id || + sd->status.pet_id != sd->pet.pet_id) { + sd->status.pet_id = 0; + return 1; + } + + i = search_petDB_index(sd->pet.class,PET_CLASS); + if(i < 0) { + sd->status.pet_id = 0; + return 1; + } + sd->petDB = &pet_db[i]; + sd->pd = pd = (struct pet_data *)aCalloc(1,sizeof(struct pet_data)); + + pd->bl.m = sd->bl.m; + pd->bl.prev = pd->bl.next = NULL; + pd->bl.x = pd->to_x = sd->bl.x; + pd->bl.y = pd->to_y = sd->bl.y; + pet_calc_pos(pd,sd->bl.x,sd->bl.y,sd->dir); + pd->bl.x = pd->to_x; + pd->bl.y = pd->to_y; + pd->bl.id = npc_get_new_npc_id(); + memcpy(pd->name,sd->pet.name,24); + pd->class = sd->pet.class; + pd->equip = sd->pet.equip; + pd->dir = sd->dir; + pd->speed = sd->petDB->speed; + pd->bl.subtype = MONS; + pd->bl.type = BL_PET; + memset(&pd->state,0,sizeof(pd->state)); + pd->state.state = MS_IDLE; + pd->state.change_walk_target = 0; + pd->timer = -1; + pd->target_id = 0; + pd->move_fail_count = 0; + pd->next_walktime = pd->attackabletime = pd->last_thinktime = gettick(); + pd->msd = sd; + + map_addiddb(&pd->bl); + + if(sd->pet_hungry_timer != -1) + pet_hungry_timer_delete(sd); + if(battle_config.pet_hungry_delay_rate != 100) + interval = (sd->petDB->hungry_delay*battle_config.pet_hungry_delay_rate)/100; + else + interval = sd->petDB->hungry_delay; + if(interval <= 0) + interval = 1; + sd->pet_hungry_timer = add_timer(gettick()+interval,pet_hungry,sd->bl.id,0); + pd->lootitem=(struct item *)aCalloc(PETLOOT_SIZE,sizeof(struct item)); + pd->lootitem_count = 0; + pd->lootitem_weight = 0; + pd->lootitem_timer = gettick(); + return 0; +} + +int pet_birth_process(struct map_session_data *sd) +{ + nullpo_retr(1, sd); + + if(sd->status.pet_id && sd->pet.incuvate == 1) { + sd->status.pet_id = 0; + return 1; + } + + sd->pet.incuvate = 0; + sd->pet.account_id = sd->status.account_id; + sd->pet.char_id = sd->status.char_id; + sd->status.pet_id = sd->pet.pet_id; + if(pet_data_init(sd)) { + sd->status.pet_id = 0; + sd->pet.incuvate = 1; + sd->pet.account_id = 0; + sd->pet.char_id = 0; + return 1; + } + + intif_save_petdata(sd->status.account_id,&sd->pet); + pc_makesavestatus(sd); + chrif_save(sd); + storage_storage_save(sd); + map_addblock(&sd->pd->bl); + clif_spawnpet(sd->pd); + clif_send_petdata(sd,0,0); + clif_send_petdata(sd,5,0x14); + clif_pet_equip(sd->pd,sd->pet.equip); + clif_send_petstatus(sd); + + return 0; +} + +int pet_recv_petdata(int account_id,struct s_pet *p,int flag) +{ + struct map_session_data *sd; + + sd = map_id2sd(account_id); + if(sd == NULL) + return 1; + if(flag == 1) { + sd->status.pet_id = 0; + return 1; + } + memcpy(&sd->pet,p,sizeof(struct s_pet)); + if(sd->pet.incuvate == 1) + pet_birth_process(sd); + else { + pet_data_init(sd); + if(sd->bl.prev != NULL) { + map_addblock(&sd->pd->bl); + clif_spawnpet(sd->pd); + clif_send_petdata(sd,0,0); + clif_send_petdata(sd,5,0x14); +// clif_pet_equip(sd->pd,sd->pet.equip); + clif_send_petstatus(sd); + } + } + if(battle_config.pet_status_support && sd->pet.intimate > 0) { + if(sd->bl.prev != NULL) + pc_calcstatus(sd,0); + else + pc_calcstatus(sd,2); + } + + return 0; +} + +int pet_select_egg(struct map_session_data *sd,short egg_index) +{ + nullpo_retr(0, sd); + + if(sd->status.inventory[egg_index].card[0] == (short)0xff00) + intif_request_petdata(sd->status.account_id,sd->status.char_id,*((long *)&sd->status.inventory[egg_index].card[1])); + else { + if(battle_config.error_log) + printf("wrong egg item inventory %d\n",egg_index); + } + pc_delitem(sd,egg_index,1,0); + + return 0; +} + +int pet_catch_process1(struct map_session_data *sd,int target_class) +{ + nullpo_retr(0, sd); + + sd->catch_target_class = target_class; + clif_catch_process(sd); + + return 0; +} + +int pet_catch_process2(struct map_session_data *sd,int target_id) +{ + struct mob_data *md; + int i=0,pet_catch_rate=0; + + nullpo_retr(1, sd); + + md=(struct mob_data*)map_id2bl(target_id); + if(!md){ + clif_pet_rulet(sd,0); + return 1; + } + + i = search_petDB_index(md->class,PET_CLASS); + if(md == NULL || md->bl.type != BL_MOB || md->bl.prev == NULL || i < 0 || sd->catch_target_class != md->class) { + clif_pet_rulet(sd,0); + return 1; + } + + //target_idによる敵→卵判定 +// if(battle_config.etc_log) +// printf("mob_id = %d, mob_class = %d\n",md->bl.id,md->class); + //成功の場合 + pet_catch_rate = (pet_db[i].capture + (sd->status.base_level - mob_db[md->class].lv)*30 + sd->paramc[5]*20)*(200 - md->hp*100/mob_db[md->class].max_hp)/100; + if(pet_catch_rate < 1) pet_catch_rate = 1; + if(battle_config.pet_catch_rate != 100) + pet_catch_rate = (pet_catch_rate*battle_config.pet_catch_rate)/100; + + if(rand()%10000 < pet_catch_rate) { + mob_catch_delete(md,0); + clif_pet_rulet(sd,1); +// if(battle_config.etc_log) +// printf("rulet success %d\n",target_id); + intif_create_pet(sd->status.account_id,sd->status.char_id,pet_db[i].class,mob_db[pet_db[i].class].lv, + pet_db[i].EggID,0,pet_db[i].intimate,100,0,1,pet_db[i].jname); + } + else + clif_pet_rulet(sd,0); + + return 0; +} + +int pet_get_egg(int account_id,int pet_id,int flag) +{ + struct map_session_data *sd; + struct item tmp_item; + int i=0,ret=0; + + if(!flag) { + sd = map_id2sd(account_id); + if(sd == NULL) + return 1; + + i = search_petDB_index(sd->catch_target_class,PET_CLASS); + if(i >= 0) { + memset(&tmp_item,0,sizeof(tmp_item)); + tmp_item.nameid = pet_db[i].EggID; + tmp_item.identify = 1; + tmp_item.card[0] = 0xff00; + *((long *)(&tmp_item.card[1])) = pet_id; + tmp_item.card[3] = sd->pet.rename_flag; + if((ret = pc_additem(sd,&tmp_item,1))) { + clif_additem(sd,0,0,ret); + map_addflooritem(&tmp_item,1,sd->bl.m,sd->bl.x,sd->bl.y,NULL,NULL,NULL,0); + } + } + else + intif_delete_petdata(pet_id); + } + + return 0; +} + +int pet_menu(struct map_session_data *sd,int menunum) +{ + nullpo_retr(0, sd); + + switch(menunum) { + case 0: + clif_send_petstatus(sd); + break; + case 1: + pet_food(sd); + break; + case 2: + pet_performance(sd); + break; + case 3: + pet_return_egg(sd); + break; + case 4: + pet_unequipitem(sd); + break; + } + return 0; +} + +int pet_change_name(struct map_session_data *sd,char *name) +{ + int i; + + nullpo_retr(1, sd); + + if(sd->pet.rename_flag == 1 && battle_config.pet_rename == 0) + return 1; + + for(i=0;i<24 && name[i];i++){ + if( !(name[i]&0xe0) || name[i]==0x7f) + return 1; + } + + pet_stop_walking(sd->pd,1); + memcpy(sd->pet.name,name,24); + memcpy(sd->pd->name,name,24); + clif_clearchar_area(&sd->pd->bl,0); + clif_spawnpet(sd->pd); + clif_send_petdata(sd,0,0); + clif_send_petdata(sd,5,0x14); + sd->pet.rename_flag = 1; + clif_pet_equip(sd->pd,sd->pet.equip); + clif_send_petstatus(sd); + + return 0; +} + +int pet_equipitem(struct map_session_data *sd,int index) +{ + int nameid; + + nullpo_retr(1, sd); + + nameid = sd->status.inventory[index].nameid; + if(sd->petDB == NULL) + return 1; + if(sd->petDB->AcceID == 0 || nameid != sd->petDB->AcceID || sd->pet.equip != 0) { + clif_equipitemack(sd,0,0,0); + return 1; + } + else { + pc_delitem(sd,index,1,0); + sd->pet.equip = sd->pd->equip = nameid; + pc_calcstatus(sd,0); + clif_pet_equip(sd->pd,nameid); + } + + return 0; +} + +int pet_unequipitem(struct map_session_data *sd) +{ + struct item tmp_item; + int nameid,flag; + + nullpo_retr(1, sd); + + if(sd->petDB == NULL) + return 1; + if(sd->pet.equip == 0) + return 1; + + nameid = sd->pet.equip; + sd->pet.equip = sd->pd->equip = 0; + pc_calcstatus(sd,0); + clif_pet_equip(sd->pd,0); + memset(&tmp_item,0,sizeof(tmp_item)); + tmp_item.nameid = nameid; + tmp_item.identify = 1; + if((flag = pc_additem(sd,&tmp_item,1))) { + clif_additem(sd,0,0,flag); + map_addflooritem(&tmp_item,1,sd->bl.m,sd->bl.x,sd->bl.y,NULL,NULL,NULL,0); + } + + return 0; +} + +int pet_food(struct map_session_data *sd) +{ + int i,k,t; + + nullpo_retr(1, sd); + + if(sd->petDB == NULL) + return 1; + i=pc_search_inventory(sd,sd->petDB->FoodID); + if(i < 0) { + clif_pet_food(sd,sd->petDB->FoodID,0); + return 1; + } + pc_delitem(sd,i,1,0); + t = sd->pet.intimate; + if(sd->pet.hungry > 90) + sd->pet.intimate -= sd->petDB->r_full; + else if(sd->pet.hungry > 75) { + if(battle_config.pet_friendly_rate != 100) + k = (sd->petDB->r_hungry * battle_config.pet_friendly_rate)/100; + else + k = sd->petDB->r_hungry; + k = k >> 1; + if(k <= 0) + k = 1; + sd->pet.intimate += k; + } + else { + if(battle_config.pet_friendly_rate != 100) + k = (sd->petDB->r_hungry * battle_config.pet_friendly_rate)/100; + else + k = sd->petDB->r_hungry; + sd->pet.intimate += k; + } + if(sd->pet.intimate <= 0) { + sd->pet.intimate = 0; + if(battle_config.pet_status_support && t > 0) { + if(sd->bl.prev != NULL) + pc_calcstatus(sd,0); + else + pc_calcstatus(sd,2); + } + } + else if(sd->pet.intimate > 1000) + sd->pet.intimate = 1000; + sd->pet.hungry += sd->petDB->fullness; + if(sd->pet.hungry > 100) + sd->pet.hungry = 100; + + clif_send_petdata(sd,2,sd->pet.hungry); + clif_send_petdata(sd,1,sd->pet.intimate); + clif_pet_food(sd,sd->petDB->FoodID,1); + + return 0; +} + +static int pet_randomwalk(struct pet_data *pd,int tick) +{ + const int retrycount=20; + int speed; + + nullpo_retr(0, pd); + + speed = battle_get_speed(&pd->bl); + + if(DIFF_TICK(pd->next_walktime,tick) < 0){ + int i,x,y,c,d=12-pd->move_fail_count; + if(d<5) d=5; + for(i=0;i<retrycount;i++){ + int r=rand(); + x=pd->bl.x+r%(d*2+1)-d; + y=pd->bl.y+r/(d*2+1)%(d*2+1)-d; + if((c=map_getcell(pd->bl.m,x,y))!=1 && c!=5 && pet_walktoxy(pd,x,y)==0){ + pd->move_fail_count=0; + break; + } + if(i+1>=retrycount){ + pd->move_fail_count++; + if(pd->move_fail_count>1000){ + if(battle_config.error_log) + printf("PET cant move. hold position %d, class = %d\n",pd->bl.id,pd->class); + pd->move_fail_count=0; + pet_changestate(pd,MS_DELAY,60000); + return 0; + } + } + } + for(i=c=0;i<pd->walkpath.path_len;i++){ + if(pd->walkpath.path[i]&1) + c+=speed*14/10; + else + c+=speed; + } + pd->next_walktime = tick+rand()%3000+3000+c; + + return 1; + } + return 0; +} + +static int pet_unlocktarget(struct pet_data *pd) +{ + nullpo_retr(0, pd); + + pd->target_id=0; + + return 0; +} + +static int pet_ai_sub_hard(struct pet_data *pd,unsigned int tick) +{ + struct map_session_data *sd = pd->msd; + struct mob_data *md = NULL; + int dist,i=0,dx,dy,ret; + int mode,race; + + nullpo_retr(0, pd); + + sd = pd->msd; + + if(pd->bl.prev == NULL || sd == NULL || sd->bl.prev == NULL) + return 0; + + if(DIFF_TICK(tick,pd->last_thinktime) < MIN_PETTHINKTIME) + return 0; + pd->last_thinktime=tick; + + if(pd->state.state == MS_DELAY || pd->bl.m != sd->bl.m) + return 0; + // ペットによるルート + if(!pd->target_id && pd->lootitem_count < PETLOOT_SIZE && pd->lootitem_count < pd->lootmax && pd->loot==1 && DIFF_TICK(gettick(),pd->lootitem_timer)>0) + map_foreachinarea(pet_ai_sub_hard_lootsearch,pd->bl.m, + pd->bl.x-AREA_SIZE*2,pd->bl.y-AREA_SIZE*2, + pd->bl.x+AREA_SIZE*2,pd->bl.y+AREA_SIZE*2, + BL_ITEM,pd,&i); + + if(sd->pet.intimate > 0) { + dist = distance(sd->bl.x,sd->bl.y,pd->bl.x,pd->bl.y); + if(dist > 12) { + if(pd->target_id > 0) + pet_unlocktarget(pd); + if(pd->timer != -1 && pd->state.state == MS_WALK && distance(pd->to_x,pd->to_y,sd->bl.x,sd->bl.y) < 3) + return 0; + pd->speed = (sd->speed>>1); + if(pd->speed <= 0) + pd->speed = 1; + pet_calc_pos(pd,sd->bl.x,sd->bl.y,sd->dir); + if(pet_walktoxy(pd,pd->to_x,pd->to_y)) + pet_randomwalk(pd,tick); + } + else if(pd->target_id - MAX_FLOORITEM > 0) { + mode=mob_db[pd->class].mode; + race=mob_db[pd->class].race; + md=(struct mob_data *)map_id2bl(pd->target_id); + if(md == NULL || md->bl.type != BL_MOB || pd->bl.m != md->bl.m || md->bl.prev == NULL || + distance(pd->bl.x,pd->bl.y,md->bl.x,md->bl.y) > 13) + pet_unlocktarget(pd); + else if(mob_db[pd->class].mexp <= 0 && !(mode&0x20) && (md->option & 0x06 && race!=4 && race!=6) ) + pet_unlocktarget(pd); + else if(!battle_check_range(&pd->bl,&md->bl,mob_db[pd->class].range)){ + if(pd->timer != -1 && pd->state.state == MS_WALK && distance(pd->to_x,pd->to_y,md->bl.x,md->bl.y) < 2) + return 0; + if( !pet_can_reach(pd,md->bl.x,md->bl.y)) + pet_unlocktarget(pd); + else { + i=0; + pd->speed = battle_get_speed(&pd->bl); + do { + if(i==0) { // 最初はAEGISと同じ方法で検索 + dx=md->bl.x - pd->bl.x; + dy=md->bl.y - pd->bl.y; + if(dx<0) dx++; + else if(dx>0) dx--; + if(dy<0) dy++; + else if(dy>0) dy--; + } + else { // だめならAthena式(ランダム) + dx=md->bl.x - pd->bl.x + rand()%3 - 1; + dy=md->bl.y - pd->bl.y + rand()%3 - 1; + } + ret=pet_walktoxy(pd,pd->bl.x+dx,pd->bl.y+dy); + i++; + } while(ret && i<5); + + if(ret) { // 移動不可能な所からの攻撃なら2歩下る + if(dx<0) dx=2; + else if(dx>0) dx=-2; + if(dy<0) dy=2; + else if(dy>0) dy=-2; + pet_walktoxy(pd,pd->bl.x+dx,pd->bl.y+dy); + } + } + } + else { + if(pd->state.state==MS_WALK) + pet_stop_walking(pd,1); + if(pd->state.state==MS_ATTACK) + return 0; + pet_changestate(pd,MS_ATTACK,0); + } + } + else if(pd->target_id > 0){ // ルート処理 + struct block_list *bl_item; + struct flooritem_data *fitem; + + bl_item = map_id2bl(pd->target_id); + if(bl_item == NULL || bl_item->type != BL_ITEM ||bl_item->m != pd->bl.m || + (dist=distance(pd->bl.x,pd->bl.y,bl_item->x,bl_item->y))>=5){ + // 遠すぎるかアイテムがなくなった + pet_unlocktarget(pd); + } + else if(dist){ + if(pd->timer != -1 && pd->state.state!=MS_ATTACK && (DIFF_TICK(pd->next_walktime,tick)<0 || distance(pd->to_x,pd->to_y,bl_item->x,bl_item->y) <= 0)) + return 0; // 既に移動中 + + pd->next_walktime=tick+500; + dx=bl_item->x - pd->bl.x; + dy=bl_item->y - pd->bl.y; + + ret=pet_walktoxy(pd,pd->bl.x+dx,pd->bl.y+dy); + } + else{ // アイテムまでたどり着いた + fitem = (struct flooritem_data *)bl_item; + if(pd->state.state==MS_ATTACK) + return 0; // 攻撃中 + if(pd->state.state==MS_WALK){ // 歩行中なら停止 + pet_stop_walking(pd,1); + } + if(pd->lootitem_count < PETLOOT_SIZE && pd->lootitem_count < pd->lootmax){ + memcpy(&pd->lootitem[pd->lootitem_count++],&fitem->item_data,sizeof(pd->lootitem[0])); + pd->lootitem_weight += itemdb_search(fitem->item_data.nameid)->weight*fitem->item_data.amount; + } + else if(pd->lootitem_count >= PETLOOT_SIZE || pd->lootitem_count >=pd->lootmax) { + pet_unlocktarget(pd); + return 0; + } + else { + if(pd->lootitem[0].card[0] == (short)0xff00) + intif_delete_petdata(*((long *)(&pd->lootitem[0].card[1]))); + for(i=0;i<PETLOOT_SIZE-1;i++) + memcpy(&pd->lootitem[i],&pd->lootitem[i+1],sizeof(pd->lootitem[0])); + memcpy(&pd->lootitem[PETLOOT_SIZE-1],&fitem->item_data,sizeof(pd->lootitem[0])); + } + map_clearflooritem(bl_item->id); + pet_unlocktarget(pd); + } + } + else { + if(dist <= 3 || (pd->timer != -1 && pd->state.state == MS_WALK && distance(pd->to_x,pd->to_y,sd->bl.x,sd->bl.y) < 3) ) + return 0; + pd->speed = battle_get_speed(&pd->bl); + pet_calc_pos(pd,sd->bl.x,sd->bl.y,sd->dir); + if(pet_walktoxy(pd,pd->to_x,pd->to_y)) + pet_randomwalk(pd,tick); + } + } + else { + pd->speed = battle_get_speed(&pd->bl); + if(pd->state.state == MS_ATTACK) + pet_stopattack(pd); + pet_randomwalk(pd,tick); + } + + return 0; +} + +static int pet_ai_sub_foreachclient(struct map_session_data *sd,va_list ap) +{ + unsigned int tick; + + nullpo_retr(0, sd); + nullpo_retr(0, ap); + + tick=va_arg(ap,unsigned int); + if(sd->status.pet_id && sd->pd && sd->petDB) + pet_ai_sub_hard(sd->pd,tick); + + return 0; +} + +static int pet_ai_hard(int tid,unsigned int tick,int id,int data) +{ + clif_foreachclient(pet_ai_sub_foreachclient,tick); + + return 0; +} + +int pet_ai_sub_hard_lootsearch(struct block_list *bl,va_list ap) +{ + struct pet_data* pd; + int dist,*itc; + + nullpo_retr(0, bl); + nullpo_retr(0, ap); + nullpo_retr(0, pd=va_arg(ap,struct pet_data *)); + nullpo_retr(0, itc=va_arg(ap,int *)); + + if(!pd->target_id){ + struct flooritem_data *fitem = (struct flooritem_data *)bl; + struct map_session_data *sd = NULL; + // ルート権無し + if(fitem && fitem->first_get_id>0) + sd = map_id2sd(fitem->first_get_id); + // Removed [Valaris] + //if((pd->lootitem_weight + (itemdb_search(fitem->item_data.))->weight * fitem->item_data.amount) > battle_config.pet_weight) + // return 0; + + if(!pd->lootitem || (pd->lootitem_count >= PETLOOT_SIZE) || (pd->lootitem_count >= pd->lootmax) || (sd && sd->pd != pd)) + return 0; + if(bl->m == pd->bl.m && (dist=distance(pd->bl.x,pd->bl.y,bl->x,bl->y))<5){ + if( pet_can_reach(pd,bl->x,bl->y) // 到達可能性判定 + && rand()%1000<1000/(++(*itc)) ){ // 範囲内PCで等確率にする + pd->target_id=bl->id; + } + } + } + return 0; +} +int pet_lootitem_drop(struct pet_data *pd,struct map_session_data *sd) +{ + int i,flag=0; + + if(pd){ + if(pd->lootitem) { + for(i=0;i<pd->lootitem_count;i++) { + struct delay_item_drop2 *ditem; + + ditem=(struct delay_item_drop2 *)aCalloc(1,sizeof(struct delay_item_drop2)); + memcpy(&ditem->item_data,&pd->lootitem[i],sizeof(pd->lootitem[0])); + ditem->m = pd->bl.m; + ditem->x = pd->bl.x; + ditem->y = pd->bl.y; + ditem->first_sd = 0; + ditem->second_sd = 0; + ditem->third_sd = 0; + // 落とさないで直接PCのItem欄へ + if(sd){ + if((flag = pc_additem(sd,&ditem->item_data,ditem->item_data.amount))){ + clif_additem(sd,0,0,flag); + map_addflooritem(&ditem->item_data,ditem->item_data.amount,ditem->m,ditem->x,ditem->y,ditem->first_sd,ditem->second_sd,ditem->third_sd,0); + } + free(ditem); + } + else + add_timer(gettick()+540+i,pet_delay_item_drop2,(int)ditem,0); + } + pd->lootitem=NULL; + pd->lootitem=(struct item *)aCalloc(PETLOOT_SIZE,sizeof(struct item)); + pd->lootitem_count = 0; + pd->lootitem_weight = 0; + pd->lootitem_timer = gettick()+10000; // 10*1000msの間拾わない + } + } + return 1; +} + +int pet_delay_item_drop2(int tid,unsigned int tick,int id,int data) +{ + struct delay_item_drop2 *ditem; + + ditem=(struct delay_item_drop2 *)id; + + map_addflooritem(&ditem->item_data,ditem->item_data.amount,ditem->m,ditem->x,ditem->y,ditem->first_sd,ditem->second_sd,ditem->third_sd,0); + + free(ditem); + return 0; +} + +/*========================================== + * pet bonus giving skills [Valaris] + *------------------------------------------ + */ + +int pet_skill_bonus(struct map_session_data *sd,struct pet_data *pd,int type,int val,int duration,int timer,int data) +{ + if(pd==NULL || sd==NULL) + return 1; + + pd->skillbonustype=type; + pd->skillbonusval=val; + pd->skillduration=duration; + pd->skilltimer=timer; + + pd->skillbonustimer=add_timer(gettick()+pd->skilltimer*1000,pet_skill_bonus_timer,sd->bl.id,0); + + return 0; + +} + +int pet_skill_bonus_timer(int tid,unsigned int tick,int id,int data) +{ + struct map_session_data *sd=(struct map_session_data*)map_id2bl(id); + struct pet_data *pd; + + if(sd==NULL || sd->bl.type!=BL_PC) + return 1; + + pd=sd->pd; + + if(pd==NULL || pd->bl.type!=BL_PET) + return 1; + + if(pd->skillbonustimer != tid) + return 0; + + pd->skillbonustimer=-1; + + pc_bonus(sd,pd->skillbonustype,pd->skillbonusval); + if(pd->skillbonustype < 56) clif_updatestatus(sd,pd->skillbonustype); + pd->skillbonusduration=add_timer(gettick()+pd->skillduration*1000,pet_skill_bonus_duration,sd->bl.id,0); + + return 0; +} + +int pet_skill_bonus_duration(int tid,unsigned int tick,int id,int data) +{ + struct map_session_data *sd=(struct map_session_data*)map_id2bl(id); + struct pet_data *pd; + + if(sd==NULL || sd->bl.type!=BL_PC) + return 1; + + pd=sd->pd; + + if(pd==NULL || pd->bl.type!=BL_PET) + return 1; + + if(pd->skillbonusduration != tid) + return 0; + + pd->skillbonusduration=-1; + + pc_bonus(sd,pd->skillbonustype,-pd->skillbonusval); + if(pd->skillbonustype < 56) clif_updatestatus(sd,pd->skillbonustype); + + pet_skill_bonus(sd,pd,pd->skillbonustype,pd->skillbonusval,pd->skillduration,pd->skilltimer,0); + + return 0; +} + +int pet_recovery_timer(int tid,unsigned int tick,int id,int data) +{ + struct map_session_data *sd=(struct map_session_data*)map_id2bl(id); + struct pet_data *pd; + + if(sd==NULL || sd->bl.type!=BL_PC) + return 1; + + pd=sd->pd; + + if(pd==NULL || pd->bl.type!=BL_PET) + return 1; + + if(pd->skillbonustimer != tid) + return 0; + + if(sd->sc_data[pd->skilltype].timer != -1) + skill_status_change_end(&sd->bl,pd->skilltype,-1); + + pd->skillbonustimer=add_timer(gettick()+pd->skilltimer*1000,pet_recovery_timer,sd->bl.id,0); + + return 0; +} + +int pet_heal_timer(int tid,unsigned int tick,int id,int data) +{ + struct map_session_data *sd=(struct map_session_data*)map_id2bl(id); + struct pet_data *pd; + + if(sd==NULL || sd->bl.type!=BL_PC) + return 1; + + pd=sd->pd; + + if(pd==NULL || pd->bl.type!=BL_PET) + return 1; + + if(pd->skillbonustimer != tid) + return 0; + + if(sd->status.hp < sd->status.max_hp * pd->skilltype/100) { + clif_skill_nodamage(&pd->bl,&sd->bl,AL_HEAL,pd->skillval,1); + pc_heal(sd,pd->skillval,0); + } + + pd->skillbonustimer=add_timer(gettick()+pd->skilltimer*1000,pet_heal_timer,sd->bl.id,0); + + return 0; +} + +int pet_mag_timer(int tid,unsigned int tick,int id,int data) +{ + struct pet_data *pd; + struct map_session_data *sd=(struct map_session_data*)map_id2bl(id); + + if(sd==NULL || sd->bl.type!=BL_PC) + return 1; + + pd=sd->pd; + + if(pd==NULL || pd->bl.type!=BL_PET) + return 1; + + if(pd->skillbonustimer != tid) + return 0; + + if(sd->status.hp < sd->status.max_hp * pd->skilltype/100 && sd->status.sp < sd->status.max_sp * pd->skillduration/100) { + clif_skill_nodamage(&pd->bl,&sd->bl,PR_MAGNIFICAT,pd->skillval,1); + skill_status_change_start(&sd->bl,SkillStatusChangeTable[PR_MAGNIFICAT],pd->skillval,0,0,0,skill_get_time(PR_MAGNIFICAT,pd->skillval),0 ); + } + + pd->skillbonustimer=add_timer(gettick()+pd->skilltimer*1000,pet_mag_timer,sd->bl.id,0); + + return 0; +} + +int pet_skillattack_timer(int tid,unsigned int tick,int id,int data) +{ + struct mob_data *md; + struct map_session_data *sd=(struct map_session_data*)map_id2bl(id); + struct pet_data *pd; + + if(sd==NULL || sd->bl.type!=BL_PC) + return 1; + + pd=sd->pd; + + if(pd==NULL || pd->bl.type!=BL_PET) + return 1; + + if(pd->skillbonustimer != tid) + return 0; + + md=(struct mob_data *)map_id2bl(sd->attacktarget); + if(md == NULL || md->bl.type != BL_MOB || pd->bl.m != md->bl.m || md->bl.prev == NULL || + distance(pd->bl.x,pd->bl.y,md->bl.x,md->bl.y) > 6) { + pd->target_id=0; + pd->skillbonustimer=add_timer(gettick()+100,pet_skillattack_timer,sd->bl.id,pd->skillduration); + return 0; + } + + if(md && rand()%100 < sd->pet.intimate*pd->skilltimer/100 ) { + if(pd->skilltype==6 || pd->skilltype==176) { + skill_castend_nodamage_id(&pd->bl,&md->bl,pd->skilltype,pd->skillval,tick,0); + } + + else if(pd->skilltype==110){ + skill_castend_pos2(&pd->bl,md->bl.x,md->bl.y,pd->skilltype,pd->skillval,tick,0); + } + + else if(pd->skilltype==91) { + skill_castend_pos2(&pd->bl,md->bl.x,md->bl.y,pd->skilltype,pd->skillval+rand()%100,tick,0); + } + else + skill_castend_damage_id(&pd->bl,&md->bl,pd->skilltype,pd->skillval,tick,0); + pd->skillbonustimer=add_timer(gettick()+1000,pet_skillattack_timer,sd->bl.id,0); + return 0; + } + + pd->skillbonustimer=add_timer(gettick()+100,pet_skillattack_timer,sd->bl.id,0); + + return 0; +} + +/*========================================== + *ペットデータ読み込み + *------------------------------------------ + */ +int read_petdb() +{ + FILE *fp; + char line[1024]; + int i; + int j=0; + char *filename[]={"db/pet_db.txt","db/pet_db2.txt"}; + + memset(pet_db,0,sizeof(pet_db)); + for(i=0;i<2;i++){ + fp=fopen(filename[i],"r"); + if(fp==NULL){ + if(i>0) + continue; + printf("can't read %s\n",filename[i]); + return -1; + } + while(fgets(line,1020,fp)){ + int nameid,i; + char *str[32],*p,*np; + + if(line[0] == '/' && line[1] == '/') + continue; + + for(i=0,p=line;i<20;i++){ + if((np=strchr(p,','))!=NULL){ + str[i]=p; + *np=0; + p=np+1; + } else { + str[i]=p; + p+=strlen(p); + } + } + + nameid=atoi(str[0]); + if(nameid<=0 || nameid>2000) + continue; + + //MobID,Name,JName,ItemID,EggID,AcceID,FoodID,"Fullness (1回の餌での満腹度増加率%)","HungryDeray (/min)","R_Hungry (空腹時餌やり親密度増加率%)","R_Full (とても満腹時餌やり親密度減少率%)","Intimate (捕獲時親密度%)","Die (死亡時親密度減少率%)","Capture (捕獲率%)",(Name) + pet_db[j].class = nameid; + memcpy(pet_db[j].name,str[1],24); + memcpy(pet_db[j].jname,str[2],24); + pet_db[j].itemID=atoi(str[3]); + pet_db[j].EggID=atoi(str[4]); + pet_db[j].AcceID=atoi(str[5]); + pet_db[j].FoodID=atoi(str[6]); + pet_db[j].fullness=atoi(str[7]); + pet_db[j].hungry_delay=atoi(str[8])*1000; + pet_db[j].r_hungry=atoi(str[9]); + if(pet_db[j].r_hungry <= 0) + pet_db[j].r_hungry=1; + pet_db[j].r_full=atoi(str[10]); + pet_db[j].intimate=atoi(str[11]); + pet_db[j].die=atoi(str[12]); + pet_db[j].capture=atoi(str[13]); + pet_db[j].speed=atoi(str[14]); + pet_db[j].s_perfor=(char)atoi(str[15]); + pet_db[j].talk_convert_class=atoi(str[16]); + pet_db[j].attack_rate=atoi(str[17]); + pet_db[j].defence_attack_rate=atoi(str[18]); + pet_db[j].change_target_rate=atoi(str[19]); + pet_db[j].script = NULL; + if((np=strchr(p,'{'))==NULL) + continue; + pet_db[j].script = parse_script(np,0); + j++; + } + fclose(fp); + printf("read %s done (count=%d)\n",filename[i],j); + } + return 0; +} + +/*========================================== + * スキル関係初期化処理 + *------------------------------------------ + */ +int do_init_pet(void) +{ + read_petdb(); + + add_timer_func_list(pet_timer,"pet_timer"); + add_timer_func_list(pet_hungry,"pet_hungry"); + add_timer_func_list(pet_ai_hard,"pet_ai_hard"); + add_timer_func_list(pet_skill_bonus_timer,"pet_skill_bonus_timer"); // [Valaris] + add_timer_func_list(pet_skill_bonus_duration,"pet_skill_bonus_duration"); // [Valaris] + add_timer_func_list(pet_recovery_timer,"pet_recovery_timer"); // [Valaris] + add_timer_func_list(pet_mag_timer,"pet_mag_timer"); // [Valaris] + add_timer_func_list(pet_heal_timer,"pet_heal_timer"); // [Valaris] + add_timer_func_list(pet_skillattack_timer,"pet_skillattack_timer"); // [Valaris] + add_timer_interval(gettick()+MIN_PETTHINKTIME,pet_ai_hard,0,0,MIN_PETTHINKTIME); + + return 0; +} + diff --git a/misc/src/map/pet.h b/misc/src/map/pet.h new file mode 100644 index 0000000..365a449 --- /dev/null +++ b/misc/src/map/pet.h @@ -0,0 +1,69 @@ +// $Id: pet.h,v 1.2 2004/09/25 05:32:18 MouseJstr Exp $ +#ifndef _PET_H_ +#define _PET_H_ + +#define MAX_PET_DB 100 +#define PETLOOT_SIZE 20 // [Valaris] + +struct pet_db { + int class; + char name[24],jname[24]; + int itemID; + int EggID; + int AcceID; + int FoodID; + int fullness; + int hungry_delay; + int r_hungry; + int r_full; + int intimate; + int die; + int capture; + int speed; + char s_perfor; + int talk_convert_class; + int attack_rate; + int defence_attack_rate; + int change_target_rate; + char *script; +}; +extern struct pet_db pet_db[MAX_PET_DB]; + +enum { PET_CLASS,PET_CATCH,PET_EGG,PET_EQUIP,PET_FOOD }; + +int pet_hungry_val(struct map_session_data *sd); +int pet_target_check(struct map_session_data *sd,struct block_list *bl,int type); +int pet_stopattack(struct pet_data *pd); +int pet_changestate(struct pet_data *pd,int state,int type); +int pet_walktoxy(struct pet_data *pd,int x,int y); +int pet_stop_walking(struct pet_data *pd,int type); +int search_petDB_index(int key,int type); +int pet_hungry_timer_delete(struct map_session_data *sd); +int pet_remove_map(struct map_session_data *sd); +int pet_data_init(struct map_session_data *sd); +int pet_birth_process(struct map_session_data *sd); +int pet_recv_petdata(int account_id,struct s_pet *p,int flag); +int pet_select_egg(struct map_session_data *sd,short egg_index); +int pet_catch_process1(struct map_session_data *sd,int target_class); +int pet_catch_process2(struct map_session_data *sd,int target_id); +int pet_get_egg(int account_id,int pet_id,int flag); +int pet_menu(struct map_session_data *sd,int menunum); +int pet_change_name(struct map_session_data *sd,char *name); +int pet_equipitem(struct map_session_data *sd,int index); +int pet_unequipitem(struct map_session_data *sd); +int pet_food(struct map_session_data *sd); +int pet_lootitem_drop(struct pet_data *pd,struct map_session_data *sd); +int pet_delay_item_drop2(int tid,unsigned int tick,int id,int data); +int pet_ai_sub_hard_lootsearch(struct block_list *bl,va_list ap); +int pet_skill_bonus(struct map_session_data *sd,struct pet_data *pd,int type,int val,int duration,int timer,int data); +int pet_skill_bonus_timer(int tid,unsigned int tick,int id,int data); // [Valaris] +int pet_skill_bonus_duration(int tid,unsigned int tick,int id,int data); // [Valaris] +int pet_recovery_timer(int tid,unsigned int tick,int id,int data); // [Valaris] +int pet_mag_timer(int tid,unsigned int tick,int id,int data); // [Valaris] +int pet_heal_timer(int tid,unsigned int tick,int id,int data); // [Valaris] +int pet_skillattack_timer(int tid,unsigned int tick,int id,int data); // [Valaris] + +int do_init_pet(void); + +#endif + diff --git a/misc/src/map/script.c b/misc/src/map/script.c new file mode 100644 index 0000000..a9a171b --- /dev/null +++ b/misc/src/map/script.c @@ -0,0 +1,6700 @@ +// $Id: script.c 148 2004-09-30 14:05:37Z MouseJstr $ +//#define DEBUG_FUNCIN +//#define DEBUG_DISP +//#define DEBUG_RUN + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> + +#ifndef LCCWIN32 +#include <sys/time.h> +#endif + +#include <time.h> + +#include "socket.h" +#include "timer.h" +#include "malloc.h" +#include "lock.h" + +#include "map.h" +#include "clif.h" +#include "chrif.h" +#include "itemdb.h" +#include "pc.h" +#include "script.h" +#include "storage.h" +#include "mob.h" +#include "npc.h" +#include "pet.h" +#include "intif.h" +#include "db.h" +#include "skill.h" +#include "chat.h" +#include "battle.h" +#include "party.h" +#include "guild.h" +#include "lock.h" +#include "atcommand.h" + +#ifdef MEMWATCH +#include "memwatch.h" +#endif + +#define SCRIPT_BLOCK_SIZE 256 +enum { LABEL_NEXTLINE=1,LABEL_START }; +static unsigned char * script_buf; +static int script_pos,script_size; + +char *str_buf; +int str_pos,str_size; +static struct { + int type; + int str; + int backpatch; + int label; + int (*func)(); + int val; + int next; +} *str_data; +int str_num=LABEL_START,str_data_size; +int str_hash[16]; + +static struct dbt *mapreg_db=NULL; +static struct dbt *mapregstr_db=NULL; +static int mapreg_dirty=-1; +char mapreg_txt[256]="save/mapreg.txt"; +#define MAPREG_AUTOSAVE_INTERVAL (10*1000) + +static struct dbt *scriptlabel_db=NULL; +static struct dbt *userfunc_db=NULL; + +struct dbt* script_get_label_db(){ return scriptlabel_db; } +struct dbt* script_get_userfunc_db(){ if(!userfunc_db) userfunc_db=strdb_init(50); return userfunc_db; } + +int scriptlabel_final(void *k,void *d,va_list ap){ return 0; } +static char pos[11][100] = {"頭","体","左手","右手","ローブ","靴","アクセサリー1","アクセサリー2","頭2","頭3","装着していない"}; + +static struct Script_Config { + int warn_func_no_comma; + int warn_cmd_no_comma; + int warn_func_mismatch_paramnum; + int warn_cmd_mismatch_paramnum; + int check_cmdcount; + int check_gotocount; +} script_config; +static int parse_cmd_if=0; +static int parse_cmd; + +/*========================================== + * ローカルプロトタイプ宣言 (必要な物のみ) + *------------------------------------------ + */ +unsigned char* parse_subexpr(unsigned char *,int); +int buildin_mes(struct script_state *st); +int buildin_goto(struct script_state *st); +int buildin_callsub(struct script_state *st); +int buildin_callfunc(struct script_state *st); +int buildin_return(struct script_state *st); +int buildin_getarg(struct script_state *st); +int buildin_next(struct script_state *st); +int buildin_close(struct script_state *st); +int buildin_close2(struct script_state *st); +int buildin_menu(struct script_state *st); +int buildin_rand(struct script_state *st); +int buildin_warp(struct script_state *st); +int buildin_areawarp(struct script_state *st); +int buildin_heal(struct script_state *st); +int buildin_itemheal(struct script_state *st); +int buildin_percentheal(struct script_state *st); +int buildin_jobchange(struct script_state *st); +int buildin_input(struct script_state *st); +int buildin_setlook(struct script_state *st); +int buildin_set(struct script_state *st); +int buildin_setarray(struct script_state *st); +int buildin_cleararray(struct script_state *st); +int buildin_copyarray(struct script_state *st); +int buildin_getarraysize(struct script_state *st); +int buildin_deletearray(struct script_state *st); +int buildin_getelementofarray(struct script_state *st); +int buildin_if(struct script_state *st); +int buildin_getitem(struct script_state *st); +int buildin_getitem2(struct script_state *st); +int buildin_makeitem(struct script_state *st); +int buildin_delitem(struct script_state *st); +int buildin_viewpoint(struct script_state *st); +int buildin_countitem(struct script_state *st); +int buildin_checkweight(struct script_state *st); +int buildin_readparam(struct script_state *st); +int buildin_getcharid(struct script_state *st); +int buildin_getpartyname(struct script_state *st); +int buildin_getpartymember(struct script_state *st); +int buildin_getguildname(struct script_state *st); +int buildin_getguildmaster(struct script_state *st); +int buildin_getguildmasterid(struct script_state *st); +int buildin_strcharinfo(struct script_state *st); +int buildin_getequipid(struct script_state *st); +int buildin_getequipname(struct script_state *st); +int buildin_getbrokenid(struct script_state *st); // [Valaris] +int buildin_repair(struct script_state *st); // [Valaris] +int buildin_getequipisequiped(struct script_state *st); +int buildin_getequipisenableref(struct script_state *st); +int buildin_getequipisidentify(struct script_state *st); +int buildin_getequiprefinerycnt(struct script_state *st); +int buildin_getequipweaponlv(struct script_state *st); +int buildin_getequippercentrefinery(struct script_state *st); +int buildin_successrefitem(struct script_state *st); +int buildin_failedrefitem(struct script_state *st); +int buildin_cutin(struct script_state *st); +int buildin_cutincard(struct script_state *st); +int buildin_statusup(struct script_state *st); +int buildin_statusup2(struct script_state *st); +int buildin_bonus(struct script_state *st); +int buildin_bonus2(struct script_state *st); +int buildin_bonus3(struct script_state *st); +int buildin_skill(struct script_state *st); +int buildin_guildskill(struct script_state *st); +int buildin_getskilllv(struct script_state *st); +int buildin_getgdskilllv(struct script_state *st); +int buildin_basicskillcheck(struct script_state *st); +int buildin_getgmlevel(struct script_state *st); +int buildin_end(struct script_state *st); +int buildin_checkoption(struct script_state *st); +int buildin_setoption(struct script_state *st); +int buildin_setcart(struct script_state *st); +int buildin_checkcart(struct script_state *st); // check cart [Valaris] +int buildin_setfalcon(struct script_state *st); +int buildin_checkfalcon(struct script_state *st); // check falcon [Valaris] +int buildin_setriding(struct script_state *st); +int buildin_checkriding(struct script_state *st); // check for pecopeco [Valaris] +int buildin_savepoint(struct script_state *st); +int buildin_gettimetick(struct script_state *st); +int buildin_gettime(struct script_state *st); +int buildin_gettimestr(struct script_state *st); +int buildin_openstorage(struct script_state *st); +int buildin_guildopenstorage(struct script_state *st); +int buildin_itemskill(struct script_state *st); +int buildin_produce(struct script_state *st); +int buildin_monster(struct script_state *st); +int buildin_areamonster(struct script_state *st); +int buildin_killmonster(struct script_state *st); +int buildin_killmonsterall(struct script_state *st); +int buildin_doevent(struct script_state *st); +int buildin_donpcevent(struct script_state *st); +int buildin_addtimer(struct script_state *st); +int buildin_deltimer(struct script_state *st); +int buildin_addtimercount(struct script_state *st); +int buildin_initnpctimer(struct script_state *st); +int buildin_stopnpctimer(struct script_state *st); +int buildin_startnpctimer(struct script_state *st); +int buildin_setnpctimer(struct script_state *st); +int buildin_getnpctimer(struct script_state *st); +int buildin_announce(struct script_state *st); +int buildin_mapannounce(struct script_state *st); +int buildin_areaannounce(struct script_state *st); +int buildin_getusers(struct script_state *st); +int buildin_getmapusers(struct script_state *st); +int buildin_getareausers(struct script_state *st); +int buildin_getareadropitem(struct script_state *st); +int buildin_enablenpc(struct script_state *st); +int buildin_disablenpc(struct script_state *st); +int buildin_enablearena(struct script_state *st); // Added by RoVeRT +int buildin_disablearena(struct script_state *st); // Added by RoVeRT +int buildin_hideoffnpc(struct script_state *st); +int buildin_hideonnpc(struct script_state *st); +int buildin_sc_start(struct script_state *st); +int buildin_sc_start2(struct script_state *st); +int buildin_sc_end(struct script_state *st); +int buildin_getscrate(struct script_state *st); +int buildin_debugmes(struct script_state *st); +int buildin_catchpet(struct script_state *st); +int buildin_birthpet(struct script_state *st); +int buildin_resetlvl(struct script_state *st); +int buildin_resetstatus(struct script_state *st); +int buildin_resetskill(struct script_state *st); +int buildin_changebase(struct script_state *st); +int buildin_changesex(struct script_state *st); +int buildin_waitingroom(struct script_state *st); +int buildin_delwaitingroom(struct script_state *st); +int buildin_enablewaitingroomevent(struct script_state *st); +int buildin_disablewaitingroomevent(struct script_state *st); +int buildin_getwaitingroomstate(struct script_state *st); +int buildin_warpwaitingpc(struct script_state *st); +int buildin_attachrid(struct script_state *st); +int buildin_detachrid(struct script_state *st); +int buildin_isloggedin(struct script_state *st); +int buildin_setmapflagnosave(struct script_state *st); +int buildin_setmapflag(struct script_state *st); +int buildin_removemapflag(struct script_state *st); +int buildin_pvpon(struct script_state *st); +int buildin_pvpoff(struct script_state *st); +int buildin_gvgon(struct script_state *st); +int buildin_gvgoff(struct script_state *st); +int buildin_emotion(struct script_state *st); +int buildin_maprespawnguildid(struct script_state *st); +int buildin_agitstart(struct script_state *st); // <Agit> +int buildin_agitend(struct script_state *st); +int buildin_agitcheck(struct script_state *st); // <Agitcheck> +int buildin_flagemblem(struct script_state *st); // Flag Emblem +int buildin_getcastlename(struct script_state *st); +int buildin_getcastledata(struct script_state *st); +int buildin_setcastledata(struct script_state *st); +int buildin_requestguildinfo(struct script_state *st); +int buildin_getequipcardcnt(struct script_state *st); +int buildin_successremovecards(struct script_state *st); +int buildin_failedremovecards(struct script_state *st); +int buildin_marriage(struct script_state *st); +int buildin_wedding_effect(struct script_state *st); +int buildin_divorce(struct script_state *st); +int buildin_getitemname(struct script_state *st); +int buildin_makepet(struct script_state *st); +int buildin_getexp(struct script_state *st); +int buildin_getinventorylist(struct script_state *st); +int buildin_getskilllist(struct script_state *st); +int buildin_clearitem(struct script_state *st); +int buildin_classchange(struct script_state *st); +int buildin_misceffect(struct script_state *st); +int buildin_soundeffect(struct script_state *st); +int buildin_setcastledata(struct script_state *st); +int buildin_mapwarp(struct script_state *st); +int buildin_inittimer(struct script_state *st); +int buildin_stoptimer(struct script_state *st); +int buildin_cmdothernpc(struct script_state *st); +int buildin_mobcount(struct script_state *st); +int buildin_strmobinfo(struct script_state *st); // Script for displaying mob info [Valaris] +int buildin_guardian(struct script_state *st); // Script for displaying mob info [Valaris] +int buildin_guardianinfo(struct script_state *st); // Script for displaying mob info [Valaris] +int buildin_petskillbonus(struct script_state *st); // petskillbonus [Valaris] +int buildin_petrecovery(struct script_state *st); // pet skill for curing status [Valaris] +int buildin_petloot(struct script_state *st); // pet looting [Valaris] +int buildin_petheal(struct script_state *st); // pet healing [Valaris] +int buildin_petmag(struct script_state *st); // pet magnificat [Valaris] +int buildin_petskillattack(struct script_state *st); // pet skill attacks [Valaris] +int buildin_npcskilleffect(struct script_state *st); // skill effects for npcs [Valaris] +int buildin_specialeffect(struct script_state *st); // special effect script [Valaris] +int buildin_specialeffect2(struct script_state *st); // special effect script [Valaris] +int buildin_nude(struct script_state *st); // nude [Valaris] +int buildin_gmcommand(struct script_state *st); // [MouseJstr] +int buildin_movenpc(struct script_state *st); // [MouseJstr] +int buildin_message(struct script_state *st); // [MouseJstr] +int buildin_npctalk(struct script_state *st); // [Valaris] +int buildin_hasitems(struct script_state *st); // [Valaris] +int buildin_getlook(struct script_state *st); //Lorky [Lupus] +int buildin_getsavepoint(struct script_state *st); //Lorky [Lupus] + + +void push_val(struct script_stack *stack,int type,int val); +int run_func(struct script_state *st); + +int mapreg_setreg(int num,int val); +int mapreg_setregstr(int num,const char *str); + +struct { + int (*func)(); + char *name; + char *arg; +} buildin_func[]={ + {buildin_mes,"mes","s"}, + {buildin_next,"next",""}, + {buildin_close,"close",""}, + {buildin_close2,"close2",""}, + {buildin_menu,"menu","*"}, + {buildin_goto,"goto","l"}, + {buildin_callsub,"callsub","i*"}, + {buildin_callfunc,"callfunc","s*"}, + {buildin_return,"return","*"}, + {buildin_getarg,"getarg","i"}, + {buildin_jobchange,"jobchange","i*"}, + {buildin_input,"input","*"}, + {buildin_warp,"warp","sii"}, + {buildin_areawarp,"areawarp","siiiisii"}, + {buildin_setlook,"setlook","ii"}, + {buildin_set,"set","ii"}, + {buildin_setarray,"setarray","ii*"}, + {buildin_cleararray,"cleararray","iii"}, + {buildin_copyarray,"copyarray","iii"}, + {buildin_getarraysize,"getarraysize","i"}, + {buildin_deletearray,"deletearray","ii"}, + {buildin_getelementofarray,"getelementofarray","ii"}, + {buildin_if,"if","i*"}, + {buildin_getitem,"getitem","ii**"}, + {buildin_getitem2,"getitem2","iiiiiiiii*"}, + {buildin_makeitem,"makeitem","iisii"}, + {buildin_delitem,"delitem","ii"}, + {buildin_cutin,"cutin","si"}, + {buildin_cutincard,"cutincard","i"}, + {buildin_viewpoint,"viewpoint","iiiii"}, + {buildin_heal,"heal","ii"}, + {buildin_itemheal,"itemheal","ii"}, + {buildin_percentheal,"percentheal","ii"}, + {buildin_rand,"rand","i*"}, + {buildin_countitem,"countitem","i"}, + {buildin_checkweight,"checkweight","ii"}, + {buildin_readparam,"readparam","i*"}, + {buildin_getcharid,"getcharid","i*"}, + {buildin_getpartyname,"getpartyname","i"}, + {buildin_getpartymember,"getpartymember","i"}, + {buildin_getguildname,"getguildname","i"}, + {buildin_getguildmaster,"getguildmaster","i"}, + {buildin_getguildmasterid,"getguildmasterid","i"}, + {buildin_strcharinfo,"strcharinfo","i"}, + {buildin_getequipid,"getequipid","i"}, + {buildin_getequipname,"getequipname","i"}, + {buildin_getbrokenid,"getbrokenid","i"}, // [Valaris] + {buildin_repair,"repair","i"}, // [Valaris] + {buildin_getequipisequiped,"getequipisequiped","i"}, + {buildin_getequipisenableref,"getequipisenableref","i"}, + {buildin_getequipisidentify,"getequipisidentify","i"}, + {buildin_getequiprefinerycnt,"getequiprefinerycnt","i"}, + {buildin_getequipweaponlv,"getequipweaponlv","i"}, + {buildin_getequippercentrefinery,"getequippercentrefinery","i"}, + {buildin_successrefitem,"successrefitem","i"}, + {buildin_failedrefitem,"failedrefitem","i"}, + {buildin_statusup,"statusup","i"}, + {buildin_statusup2,"statusup2","ii"}, + {buildin_bonus,"bonus","ii"}, + {buildin_bonus2,"bonus2","iii"}, + {buildin_bonus3,"bonus3","iiii"}, + {buildin_skill,"skill","ii*"}, + {buildin_guildskill,"guildskill","ii"}, + {buildin_getskilllv,"getskilllv","i"}, + {buildin_getgdskilllv,"getgdskilllv","ii"}, + {buildin_basicskillcheck,"basicskillcheck","*"}, + {buildin_getgmlevel,"getgmlevel","*"}, + {buildin_end,"end",""}, + {buildin_end,"break",""}, + {buildin_checkoption,"checkoption","i"}, + {buildin_setoption,"setoption","i"}, + {buildin_setcart,"setcart",""}, + {buildin_checkcart,"checkcart","*"}, //fixed by Lupus (added '*') + {buildin_setfalcon,"setfalcon",""}, + {buildin_checkfalcon,"checkfalcon","*"}, //fixed by Lupus (fixed wrong pointer, added '*') + {buildin_setriding,"setriding",""}, + {buildin_checkriding,"checkriding","*"}, //fixed by Lupus (fixed wrong pointer, added '*') + {buildin_savepoint,"save","sii"}, + {buildin_savepoint,"savepoint","sii"}, + {buildin_gettimetick,"gettimetick","i"}, + {buildin_gettime,"gettime","i"}, + {buildin_gettimestr,"gettimestr","si"}, + {buildin_openstorage,"openstorage",""}, + {buildin_guildopenstorage,"guildopenstorage","*"}, + {buildin_itemskill,"itemskill","iis"}, + {buildin_produce,"produce","i"}, + {buildin_monster,"monster","siisii*"}, + {buildin_areamonster,"areamonster","siiiisii*"}, + {buildin_killmonster,"killmonster","ss"}, + {buildin_killmonsterall,"killmonsterall","s"}, + {buildin_doevent,"doevent","s"}, + {buildin_donpcevent,"donpcevent","s"}, + {buildin_addtimer,"addtimer","is"}, + {buildin_deltimer,"deltimer","s"}, + {buildin_addtimercount,"addtimercount","si"}, + {buildin_initnpctimer,"initnpctimer","*"}, + {buildin_stopnpctimer,"stopnpctimer","*"}, + {buildin_startnpctimer,"startnpctimer","*"}, + {buildin_setnpctimer,"setnpctimer","*"}, + {buildin_getnpctimer,"getnpctimer","i*"}, + {buildin_announce,"announce","si"}, + {buildin_mapannounce,"mapannounce","ssi"}, + {buildin_areaannounce,"areaannounce","siiiisi"}, + {buildin_getusers,"getusers","i"}, + {buildin_getmapusers,"getmapusers","s"}, + {buildin_getareausers,"getareausers","siiii"}, + {buildin_getareadropitem,"getareadropitem","siiiii"}, + {buildin_enablenpc,"enablenpc","s"}, + {buildin_disablenpc,"disablenpc","s"}, + {buildin_enablearena,"enablearena",""}, // Added by RoVeRT + {buildin_disablearena,"disablearena",""}, // Added by RoVeRT + {buildin_hideoffnpc,"hideoffnpc","s"}, + {buildin_hideonnpc,"hideonnpc","s"}, + {buildin_sc_start,"sc_start","iii*"}, + {buildin_sc_start2,"sc_start2","iiii*"}, + {buildin_sc_end,"sc_end","i"}, + {buildin_getscrate,"getscrate","ii*"}, + {buildin_debugmes,"debugmes","s"}, + {buildin_catchpet,"pet","i"}, + {buildin_birthpet,"bpet",""}, + {buildin_resetlvl,"resetlvl","i"}, + {buildin_resetstatus,"resetstatus",""}, + {buildin_resetskill,"resetskill",""}, + {buildin_changebase,"changebase","i"}, + {buildin_changesex,"changesex",""}, + {buildin_waitingroom,"waitingroom","si*"}, + {buildin_warpwaitingpc,"warpwaitingpc","sii"}, + {buildin_delwaitingroom,"delwaitingroom","*"}, + {buildin_enablewaitingroomevent,"enablewaitingroomevent","*"}, + {buildin_disablewaitingroomevent,"disablewaitingroomevent","*"}, + {buildin_getwaitingroomstate,"getwaitingroomstate","i*"}, + {buildin_warpwaitingpc,"warpwaitingpc","sii*"}, + {buildin_attachrid,"attachrid","i"}, + {buildin_detachrid,"detachrid",""}, + {buildin_isloggedin,"isloggedin","i"}, + {buildin_setmapflagnosave,"setmapflagnosave","ssii"}, + {buildin_setmapflag,"setmapflag","si"}, + {buildin_removemapflag,"removemapflag","si"}, + {buildin_pvpon,"pvpon","s"}, + {buildin_pvpoff,"pvpoff","s"}, + {buildin_gvgon,"gvgon","s"}, + {buildin_gvgoff,"gvgoff","s"}, + {buildin_emotion,"emotion","i"}, + {buildin_maprespawnguildid,"maprespawnguildid","sii"}, + {buildin_agitstart,"agitstart",""}, // <Agit> + {buildin_agitend,"agitend",""}, + {buildin_agitcheck,"agitcheck","i"}, // <Agitcheck> + {buildin_flagemblem,"flagemblem","i"}, // Flag Emblem + {buildin_getcastlename,"getcastlename","s"}, + {buildin_getcastledata,"getcastledata","si*"}, + {buildin_setcastledata,"setcastledata","sii"}, + {buildin_requestguildinfo,"requestguildinfo","i*"}, + {buildin_getequipcardcnt,"getequipcardcnt","i"}, + {buildin_successremovecards,"successremovecards","i"}, + {buildin_failedremovecards,"failedremovecards","ii"}, + {buildin_marriage,"marriage","s"}, + {buildin_wedding_effect,"wedding",""}, + {buildin_divorce,"divorce",""}, + {buildin_getitemname,"getitemname","i"}, + {buildin_makepet,"makepet","i"}, + {buildin_getexp,"getexp","ii"}, + {buildin_getinventorylist,"getinventorylist",""}, + {buildin_getskilllist,"getskilllist",""}, + {buildin_clearitem,"clearitem",""}, + {buildin_classchange,"classchange","ii"}, + {buildin_misceffect,"misceffect","i"}, + {buildin_soundeffect,"soundeffect","si"}, + {buildin_strmobinfo,"strmobinfo","ii"}, // display mob data [Valaris] + {buildin_guardian,"guardian","siisii*i"}, // summon guardians + {buildin_guardianinfo,"guardianinfo","i"}, // display guardian data [Valaris] + {buildin_petskillbonus,"petskillbonus","iiii"}, // [Valaris] + {buildin_petrecovery,"petrecovery","ii"}, // [Valaris] + {buildin_petloot,"petloot","i"}, // [Valaris] + {buildin_petheal,"petheal","iii"}, // [Valaris] + {buildin_petmag,"petmag","iiii"}, // [Valaris] + {buildin_petskillattack,"petskillattack","iiii"}, // [Valaris] + {buildin_npcskilleffect,"npcskilleffect","iiii"}, // npc skill effect [Valaris] + {buildin_specialeffect,"specialeffect","i"}, // npc skill effect [Valaris] + {buildin_specialeffect2,"specialeffect2","i"}, // skill effect on players[Valaris] + {buildin_nude,"nude",""}, // nude command [Valaris] + {buildin_mapwarp,"mapwarp","ssii"}, // Added by RoVeRT + {buildin_inittimer,"inittimer",""}, + {buildin_stoptimer,"stoptimer",""}, + {buildin_cmdothernpc,"cmdothernpc","ss"}, + {buildin_gmcommand,"gmcommand","*"}, // [MouseJstr] +// {buildin_movenpc,"movenpc","siis"}, // [MouseJstr] + {buildin_message,"message","s*"}, // [MouseJstr] + {buildin_npctalk,"npctalk","*"}, // [Valaris] + {buildin_hasitems,"hasitems","*"}, // [Valaris] + {buildin_mobcount,"mobcount","ss"}, + {buildin_getlook,"getlook","i"}, + {buildin_getsavepoint,"getsavepoint","i"}, // End Additions + {NULL,NULL,NULL}, +}; +int buildin_message(struct script_state *st); // [MouseJstr] + + +enum { + C_NOP,C_POS,C_INT,C_PARAM,C_FUNC,C_STR,C_CONSTSTR,C_ARG, + C_NAME,C_EOL, C_RETINFO, + + C_LOR,C_LAND,C_LE,C_LT,C_GE,C_GT,C_EQ,C_NE, //operator + C_XOR,C_OR,C_AND,C_ADD,C_SUB,C_MUL,C_DIV,C_MOD,C_NEG,C_LNOT,C_NOT,C_R_SHIFT,C_L_SHIFT +}; + +/*========================================== + * 文字列のハッシュを計算 + *------------------------------------------ + */ +static int calc_hash(const unsigned char *p) +{ + int h=0; + while(*p){ + h=(h<<1)+(h>>3)+(h>>5)+(h>>8); + h+=*p++; + } + return h&15; +} + +/*========================================== + * str_dataの中に名前があるか検索する + *------------------------------------------ + */ +// 既存のであれば番号、無ければ-1 +static int search_str(const unsigned char *p) +{ + int i; + i=str_hash[calc_hash(p)]; + while(i){ + if(strcmp(str_buf+str_data[i].str,p)==0){ + return i; + } + i=str_data[i].next; + } + return -1; +} + +/*========================================== + * str_dataに名前を登録 + *------------------------------------------ + */ +// 既存のであれば番号、無ければ登録して新規番号 +static int add_str(const unsigned char *p) +{ + int i; + char *lowcase; + + lowcase=strdup(p); + for(i=0;lowcase[i];i++) + lowcase[i]=tolower(lowcase[i]); + if((i=search_str(lowcase))>=0){ + free(lowcase); + return i; + } + free(lowcase); + + i=calc_hash(p); + if(str_hash[i]==0){ + str_hash[i]=str_num; + } else { + i=str_hash[i]; + for(;;){ + if(strcmp(str_buf+str_data[i].str,p)==0){ + return i; + } + if(str_data[i].next==0) + break; + i=str_data[i].next; + } + str_data[i].next=str_num; + } + if(str_num>=str_data_size){ + str_data_size+=128; + str_data=aRealloc(str_data,sizeof(str_data[0])*str_data_size); + memset(str_data + (str_data_size - 128), '\0', 128); + } + while(str_pos+strlen(p)+1>=str_size){ + str_size+=256; + str_buf=(char *)aRealloc(str_buf,str_size); + memset(str_buf + (str_size - 256), '\0', 256); + } + strcpy(str_buf+str_pos,p); + str_data[str_num].type=C_NOP; + str_data[str_num].str=str_pos; + str_data[str_num].next=0; + str_data[str_num].func=NULL; + str_data[str_num].backpatch=-1; + str_data[str_num].label=-1; + str_pos+=strlen(p)+1; + return str_num++; +} + + +/*========================================== + * スクリプトバッファサイズの確認と拡張 + *------------------------------------------ + */ +static void check_script_buf(int size) +{ + if(script_pos+size>=script_size){ + script_size+=SCRIPT_BLOCK_SIZE; + script_buf=(char *)aRealloc(script_buf,script_size); + memset(script_buf + script_size - SCRIPT_BLOCK_SIZE, '\0', + SCRIPT_BLOCK_SIZE); + } +} + +/*========================================== + * スクリプトバッファに1バイト書き込む + *------------------------------------------ + */ +static void add_scriptb(int a) +{ + check_script_buf(1); + script_buf[script_pos++]=a; +} + +/*========================================== + * スクリプトバッファにデータタイプを書き込む + *------------------------------------------ + */ +static void add_scriptc(int a) +{ + while(a>=0x40){ + add_scriptb((a&0x3f)|0x40); + a=(a-0x40)>>6; + } + add_scriptb(a&0x3f); +} + +/*========================================== + * スクリプトバッファに整数を書き込む + *------------------------------------------ + */ +static void add_scripti(int a) +{ + while(a>=0x40){ + add_scriptb(a|0xc0); + a=(a-0x40)>>6; + } + add_scriptb(a|0x80); +} + +/*========================================== + * スクリプトバッファにラベル/変数/関数を書き込む + *------------------------------------------ + */ +// 最大16Mまで +static void add_scriptl(int l) +{ + int backpatch = str_data[l].backpatch; + + switch(str_data[l].type){ + case C_POS: + add_scriptc(C_POS); + add_scriptb(str_data[l].label); + add_scriptb(str_data[l].label>>8); + add_scriptb(str_data[l].label>>16); + break; + case C_NOP: + // ラベルの可能性があるのでbackpatch用データ埋め込み + add_scriptc(C_NAME); + str_data[l].backpatch=script_pos; + add_scriptb(backpatch); + add_scriptb(backpatch>>8); + add_scriptb(backpatch>>16); + break; + case C_INT: + add_scripti(str_data[l].val); + break; + default: + // もう他の用途と確定してるので数字をそのまま + add_scriptc(C_NAME); + add_scriptb(l); + add_scriptb(l>>8); + add_scriptb(l>>16); + break; + } +} + +/*========================================== + * ラベルを解決する + *------------------------------------------ + */ +void set_label(int l,int pos) +{ + int i,next; + + str_data[l].type=C_POS; + str_data[l].label=pos; + for(i=str_data[l].backpatch;i>=0 && i!=0x00ffffff;){ + next=(*(int*)(script_buf+i)) & 0x00ffffff; + script_buf[i-1]=C_POS; + script_buf[i]=pos; + script_buf[i+1]=pos>>8; + script_buf[i+2]=pos>>16; + i=next; + } +} + +/*========================================== + * スペース/コメント読み飛ばし + *------------------------------------------ + */ +static unsigned char *skip_space(unsigned char *p) +{ + while(1){ + while(isspace(*p)) + p++; + if(p[0]=='/' && p[1]=='/'){ + while(*p && *p!='\n') + p++; + } else if(p[0]=='/' && p[1]=='*'){ + p++; + while(*p && (p[-1]!='*' || p[0]!='/')) + p++; + if(*p) p++; + } else + break; + } + return p; +} + +/*========================================== + * 1単語スキップ + *------------------------------------------ + */ +static unsigned char *skip_word(unsigned char *p) +{ + // prefix + if(*p=='$') p++; // MAP鯖内共有変数用 + if(*p=='@') p++; // 一時的変数用(like weiss) + if(*p=='#') p++; // account変数用 + if(*p=='#') p++; // ワールドaccount変数用 + if(*p=='l') p++; // 一時的変数用(like weiss) + + while(isalnum(*p)||*p=='_'|| *p>=0x81) + if(*p>=0x81 && p[1]){ + p+=2; + } else + p++; + + // postfix + if(*p=='$') p++; // 文字列変数 + + return p; +} + +static unsigned char *startptr; +static int startline; + +/*========================================== + * エラーメッセージ出力 + *------------------------------------------ + */ +static void disp_error_message(const char *mes,const unsigned char *pos) +{ + int line,c=0,i; + unsigned char *p,*linestart,*lineend; + + for(line=startline,p=startptr;p && *p;line++){ + linestart=p; + lineend=strchr(p,'\n'); + if(lineend){ + c=*lineend; + *lineend=0; + } + if(lineend==NULL || pos<lineend){ + printf("%s line %d : ",mes,line); + for(i=0;(linestart[i]!='\r') && (linestart[i]!='\n') && linestart[i];i++){ + if(linestart+i!=pos) + printf("%c",linestart[i]); + else + printf("\'%c\'",linestart[i]); + } + printf("\a\n"); + if(lineend) + *lineend=c; + return; + } + *lineend=c; + p=lineend+1; + } +} + +/*========================================== + * 項の解析 + *------------------------------------------ + */ +unsigned char* parse_simpleexpr(unsigned char *p) +{ + int i; + p=skip_space(p); + +#ifdef DEBUG_FUNCIN + if(battle_config.etc_log) + printf("parse_simpleexpr %s\n",p); +#endif + if(*p==';' || *p==','){ + disp_error_message("unexpected expr end",p); + exit(1); + } + if(*p=='('){ + + p=parse_subexpr(p+1,-1); + p=skip_space(p); + if((*p++)!=')'){ + disp_error_message("unmatch ')'",p); + exit(1); + } + } else if(isdigit(*p) || ((*p=='-' || *p=='+') && isdigit(p[1]))){ + char *np; + i=strtoul(p,&np,0); + add_scripti(i); + p=np; + } else if(*p=='"'){ + add_scriptc(C_STR); + p++; + while(*p && *p!='"'){ + if(p[-1]<=0x7e && *p=='\\') + p++; + else if(*p=='\n'){ + disp_error_message("unexpected newline @ string",p); + exit(1); + } + add_scriptb(*p++); + } + if(!*p){ + disp_error_message("unexpected eof @ string",p); + exit(1); + } + add_scriptb(0); + p++; //'"' + } else { + int c,l; + char *p2; + // label , register , function etc + if(skip_word(p)==p){ + disp_error_message("unexpected character",p); + exit(1); + } + p2=skip_word(p); + c=*p2; *p2=0; // 名前をadd_strする + l=add_str(p); + + parse_cmd=l; // warn_*_mismatch_paramnumのために必要 + if(l==search_str("if")) // warn_cmd_no_commaのために必要 + parse_cmd_if++; +/* + // 廃止予定のl14/l15,およびプレフィックスlの警告 + if( strcmp(str_buf+str_data[l].str,"l14")==0 || + strcmp(str_buf+str_data[l].str,"l15")==0 ){ + disp_error_message("l14 and l15 is DEPRECATED. use @menu instead of l15.",p); + }else if(str_buf[str_data[l].str]=='l'){ + disp_error_message("prefix 'l' is DEPRECATED. use prefix '@' instead.",p2); + } +*/ + *p2=c; p=p2; + + if(str_data[l].type!=C_FUNC && c=='['){ + // array(name[i] => getelementofarray(name,i) ) + add_scriptl(search_str("getelementofarray")); + add_scriptc(C_ARG); + add_scriptl(l); + p=parse_subexpr(p+1,-1); + p=skip_space(p); + if((*p++)!=']'){ + disp_error_message("unmatch ']'",p); + exit(1); + } + add_scriptc(C_FUNC); + }else + add_scriptl(l); + + } + +#ifdef DEBUG_FUNCIN + if(battle_config.etc_log) + printf("parse_simpleexpr end %s\n",p); +#endif + return p; +} + +/*========================================== + * 式の解析 + *------------------------------------------ + */ +unsigned char* parse_subexpr(unsigned char *p,int limit) +{ + int op,opl,len; + char *tmpp; + +#ifdef DEBUG_FUNCIN + if(battle_config.etc_log) + printf("parse_subexpr %s\n",p); +#endif + p=skip_space(p); + + if(*p=='-'){ + tmpp=skip_space(p+1); + if(*tmpp==';' || *tmpp==','){ + add_scriptl(LABEL_NEXTLINE); + p++; + return p; + } + } + tmpp=p; + if((op=C_NEG,*p=='-') || (op=C_LNOT,*p=='!') || (op=C_NOT,*p=='~')){ + p=parse_subexpr(p+1,100); + add_scriptc(op); + } else + p=parse_simpleexpr(p); + p=skip_space(p); + while(((op=C_ADD,opl=6,len=1,*p=='+') || + (op=C_SUB,opl=6,len=1,*p=='-') || + (op=C_MUL,opl=7,len=1,*p=='*') || + (op=C_DIV,opl=7,len=1,*p=='/') || + (op=C_MOD,opl=7,len=1,*p=='%') || + (op=C_FUNC,opl=8,len=1,*p=='(') || + (op=C_LAND,opl=1,len=2,*p=='&' && p[1]=='&') || + (op=C_AND,opl=5,len=1,*p=='&') || + (op=C_LOR,opl=0,len=2,*p=='|' && p[1]=='|') || + (op=C_OR,opl=4,len=1,*p=='|') || + (op=C_XOR,opl=3,len=1,*p=='^') || + (op=C_EQ,opl=2,len=2,*p=='=' && p[1]=='=') || + (op=C_NE,opl=2,len=2,*p=='!' && p[1]=='=') || + (op=C_R_SHIFT,opl=5,len=2,*p=='>' && p[1]=='>') || + (op=C_GE,opl=2,len=2,*p=='>' && p[1]=='=') || + (op=C_GT,opl=2,len=1,*p=='>') || + (op=C_L_SHIFT,opl=5,len=2,*p=='<' && p[1]=='<') || + (op=C_LE,opl=2,len=2,*p=='<' && p[1]=='=') || + (op=C_LT,opl=2,len=1,*p=='<')) && opl>limit){ + p+=len; + if(op==C_FUNC){ + int i=0,func=parse_cmd; + const char *plist[128]; + + if( str_data[func].type!=C_FUNC ){ + disp_error_message("expect function",tmpp); + exit(0); + } + + add_scriptc(C_ARG); + do { + plist[i]=p; + p=parse_subexpr(p,-1); + p=skip_space(p); + if(*p==',') p++; + else if(*p!=')' && script_config.warn_func_no_comma){ + disp_error_message("expect ',' or ')' at func params",p); + } + p=skip_space(p); + i++; + } while(*p && *p!=')' && i<128); + plist[i]=p; + if(*(p++)!=')'){ + disp_error_message("func request '(' ')'",p); + exit(1); + } + + if( str_data[func].type==C_FUNC && script_config.warn_func_mismatch_paramnum){ + const char *arg=buildin_func[str_data[func].val].arg; + int j=0; + for(j=0;arg[j];j++) if(arg[j]=='*')break; + if( (arg[j]==0 && i!=j) || (arg[j]=='*' && i<j) ){ + disp_error_message("illegal number of parameters",plist[(i<j)?i:j]); + } + } + } else { + p=parse_subexpr(p,opl); + } + add_scriptc(op); + p=skip_space(p); + } +#ifdef DEBUG_FUNCIN + if(battle_config.etc_log) + printf("parse_subexpr end %s\n",p); +#endif + return p; /* return first untreated operator */ +} + +/*========================================== + * 式の評価 + *------------------------------------------ + */ +unsigned char* parse_expr(unsigned char *p) +{ +#ifdef DEBUG_FUNCIN + if(battle_config.etc_log) + printf("parse_expr %s\n",p); +#endif + switch(*p){ + case ')': case ';': case ':': case '[': case ']': + case '}': + disp_error_message("unexpected char",p); + exit(1); + } + p=parse_subexpr(p,-1); +#ifdef DEBUG_FUNCIN + if(battle_config.etc_log) + printf("parse_expr end %s\n",p); +#endif + return p; +} + +/*========================================== + * 行の解析 + *------------------------------------------ + */ +unsigned char* parse_line(unsigned char *p) +{ + int i=0,cmd; + const char *plist[128]; + char *p2; + + p=skip_space(p); + if(*p==';') + return p; + + parse_cmd_if=0; // warn_cmd_no_commaのために必要 + + // 最初は関数名 + p2=p; + p=parse_simpleexpr(p); + p=skip_space(p); + + cmd=parse_cmd; + if( str_data[cmd].type!=C_FUNC ){ + disp_error_message("expect command",p2); +// exit(0); + } + + add_scriptc(C_ARG); + while(p && *p && *p!=';' && i<128){ + plist[i]=p; + + p=parse_expr(p); + p=skip_space(p); + // 引数区切りの,処理 + if(*p==',') p++; + else if(*p!=';' && script_config.warn_cmd_no_comma && parse_cmd_if*2<=i ){ + disp_error_message("expect ',' or ';' at cmd params",p); + } + p=skip_space(p); + i++; + } + plist[i]=p; + if(!p || *(p++)!=';'){ + disp_error_message("need ';'",p); + exit(1); + } + add_scriptc(C_FUNC); + + if( str_data[cmd].type==C_FUNC && script_config.warn_cmd_mismatch_paramnum){ + const char *arg=buildin_func[str_data[cmd].val].arg; + int j=0; + for(j=0;arg[j];j++) if(arg[j]=='*')break; + if( (arg[j]==0 && i!=j) || (arg[j]=='*' && i<j) ){ + disp_error_message("illegal number of parameters",plist[(i<j)?i:j]); + } + } + + + return p; +} + +/*========================================== + * 組み込み関数の追加 + *------------------------------------------ + */ +static void add_buildin_func(void) +{ + int i,n; + for(i=0;buildin_func[i].func;i++){ + n=add_str(buildin_func[i].name); + str_data[n].type=C_FUNC; + str_data[n].val=i; + str_data[n].func=buildin_func[i].func; + } +} + +/*========================================== + * 定数データベースの読み込み + *------------------------------------------ + */ +static void read_constdb(void) +{ + FILE *fp; + char line[1024],name[1024]; + int val,n,i,type; + + fp=fopen("db/const.txt","r"); + if(fp==NULL){ + printf("can't read db/const.txt\n"); + return ; + } + while(fgets(line,1020,fp)){ + if(line[0]=='/' && line[1]=='/') + continue; + type=0; + if(sscanf(line,"%[A-Za-z0-9_],%d,%d",name,&val,&type)>=2 || + sscanf(line,"%[A-Za-z0-9_] %d %d",name,&val,&type)>=2){ + for(i=0;name[i];i++) + name[i]=tolower(name[i]); + n=add_str(name); + if(type==0) + str_data[n].type=C_INT; + else + str_data[n].type=C_PARAM; + str_data[n].val=val; + } + } + fclose(fp); +} + +/*========================================== + * スクリプトの解析 + *------------------------------------------ + */ +unsigned char* parse_script(unsigned char *src,int line) +{ + unsigned char *p,*tmpp; + int i; + static int first=1; + + if(first){ + add_buildin_func(); + read_constdb(); + } + first=0; + script_buf=(unsigned char *)aCalloc(SCRIPT_BLOCK_SIZE,sizeof(unsigned char)); + script_pos=0; + script_size=SCRIPT_BLOCK_SIZE; + str_data[LABEL_NEXTLINE].type=C_NOP; + str_data[LABEL_NEXTLINE].backpatch=-1; + str_data[LABEL_NEXTLINE].label=-1; + for(i=LABEL_START;i<str_num;i++){ + if(str_data[i].type==C_POS || str_data[i].type==C_NAME){ + str_data[i].type=C_NOP; + str_data[i].backpatch=-1; + str_data[i].label=-1; + } + } + + // 外部用label dbの初期化 + if(scriptlabel_db!=NULL) + strdb_final(scriptlabel_db,scriptlabel_final); + scriptlabel_db=strdb_init(50); + + // for error message + startptr = src; + startline = line; + + p=src; + p=skip_space(p); + if(*p!='{'){ + disp_error_message("not found '{'",p); + return NULL; + } + for(p++;p && *p && *p!='}';){ + p=skip_space(p); + // labelだけ特殊処理 + tmpp=skip_space(skip_word(p)); + if(*tmpp==':'){ + int l,c; + + c=*skip_word(p); + *skip_word(p)=0; + l=add_str(p); + if(str_data[l].label!=-1){ + *skip_word(p)=c; + disp_error_message("dup label ",p); + exit(1); + } + set_label(l,script_pos); + strdb_insert(scriptlabel_db,p,script_pos); // 外部用label db登録 + *skip_word(p)=c; + p=tmpp+1; + continue; + } + + // 他は全部一緒くた + p=parse_line(p); + p=skip_space(p); + add_scriptc(C_EOL); + + set_label(LABEL_NEXTLINE,script_pos); + str_data[LABEL_NEXTLINE].type=C_NOP; + str_data[LABEL_NEXTLINE].backpatch=-1; + str_data[LABEL_NEXTLINE].label=-1; + } + + add_scriptc(C_NOP); + + script_size = script_pos; + script_buf=(char *)aRealloc(script_buf,script_pos + 1); + + // 未解決のラベルを解決 + for(i=LABEL_START;i<str_num;i++){ + if(str_data[i].type==C_NOP){ + int j,next; + str_data[i].type=C_NAME; + str_data[i].label=i; + for(j=str_data[i].backpatch;j>=0 && j!=0x00ffffff;){ + next=(*(int*)(script_buf+j)) & 0x00ffffff; + script_buf[j]=i; + script_buf[j+1]=i>>8; + script_buf[j+2]=i>>16; + j=next; + } + } + } + +#ifdef DEBUG_DISP + for(i=0;i<script_pos;i++){ + if((i&15)==0) printf("%04x : ",i); + printf("%02x ",script_buf[i]); + if((i&15)==15) printf("\n"); + } + printf("\n"); +#endif + + return script_buf; +} + +// +// 実行系 +// +enum {STOP=1,END,RERUNLINE,GOTO,RETFUNC}; + +/*========================================== + * ridからsdへの解決 + *------------------------------------------ + */ +struct map_session_data *script_rid2sd(struct script_state *st) +{ + struct map_session_data *sd=map_id2sd(st->rid); + if(!sd){ + printf("script_rid2sd: fatal error ! player not attached!\n"); + } + return sd; +} + + +/*========================================== + * 変数の読み取り + *------------------------------------------ + */ +int get_val(struct script_state*st,struct script_data* data) +{ + struct map_session_data *sd=NULL; + if(data->type==C_NAME){ + char *name=str_buf+str_data[data->u.num&0x00ffffff].str; + char prefix=*name; + char postfix=name[strlen(name)-1]; + + if(prefix!='$'){ + if((sd=script_rid2sd(st))==NULL) + printf("get_val error name?:%s\n",name); + } + if(postfix=='$'){ + + data->type=C_CONSTSTR; + if( prefix=='@' || prefix=='l' ){ + if(sd) + data->u.str = pc_readregstr(sd,data->u.num); + }else if(prefix=='$'){ + data->u.str = (char *)numdb_search(mapregstr_db,data->u.num); + }else{ + printf("script: get_val: illegal scope string variable.\n"); + data->u.str = "!!ERROR!!"; + } + if( data->u.str == NULL ) + data->u.str =""; + + }else{ + + data->type=C_INT; + if(str_data[data->u.num&0x00ffffff].type==C_INT){ + data->u.num = str_data[data->u.num&0x00ffffff].val; + }else if(str_data[data->u.num&0x00ffffff].type==C_PARAM){ + if(sd) + data->u.num = pc_readparam(sd,str_data[data->u.num&0x00ffffff].val); + }else if(prefix=='@' || prefix=='l'){ + if(sd) + data->u.num = pc_readreg(sd,data->u.num); + }else if(prefix=='$'){ + data->u.num = (int)numdb_search(mapreg_db,data->u.num); + }else if(prefix=='#'){ + if( name[1]=='#'){ + if(sd) + data->u.num = pc_readaccountreg2(sd,name); + }else{ + if(sd) + data->u.num = pc_readaccountreg(sd,name); + } + }else{ + if(sd) + data->u.num = pc_readglobalreg(sd,name); + } + } + } + return 0; +} +/*========================================== + * 変数の読み取り2 + *------------------------------------------ + */ +void* get_val2(struct script_state*st,int num) +{ + struct script_data dat; + dat.type=C_NAME; + dat.u.num=num; + get_val(st,&dat); + if( dat.type==C_INT ) return (void*)dat.u.num; + else return (void*)dat.u.str; +} + +/*========================================== + * 変数設定用 + *------------------------------------------ + */ +static int set_reg(struct map_session_data *sd,int num,char *name,void *v) +{ + char prefix=*name; + char postfix=name[strlen(name)-1]; + + if( postfix=='$' ){ + char *str=(char*)v; + if( prefix=='@' || prefix=='l'){ + pc_setregstr(sd,num,str); + }else if(prefix=='$') { + mapreg_setregstr(num,str); + }else{ + printf("script: set_reg: illegal scope string variable !"); + } + }else{ + // 数値 + int val = (int)v; + if(str_data[num&0x00ffffff].type==C_PARAM){ + pc_setparam(sd,str_data[num&0x00ffffff].val,val); + }else if(prefix=='@' || prefix=='l') { + pc_setreg(sd,num,val); + }else if(prefix=='$') { + mapreg_setreg(num,val); + }else if(prefix=='#') { + if( name[1]=='#' ) + pc_setaccountreg2(sd,name,val); + else + pc_setaccountreg(sd,name,val); + }else{ + pc_setglobalreg(sd,name,val); + } + } + return 0; +} + +/*========================================== + * 文字列への変換 + *------------------------------------------ + */ +char* conv_str(struct script_state *st,struct script_data *data) +{ + get_val(st,data); + if(data->type==C_INT){ + char *buf; + buf=(char *)aCalloc(16,sizeof(char)); + sprintf(buf,"%d",data->u.num); + data->type=C_STR; + data->u.str=buf; +#if 1 + } else if(data->type==C_NAME){ + // テンポラリ。本来無いはず + data->type=C_CONSTSTR; + data->u.str=str_buf+str_data[data->u.num].str; +#endif + } + return data->u.str; +} + +/*========================================== + * 数値へ変換 + *------------------------------------------ + */ +int conv_num(struct script_state *st,struct script_data *data) +{ + char *p; + get_val(st,data); + if(data->type==C_STR || data->type==C_CONSTSTR){ + p=data->u.str; + data->u.num = atoi(p); + if(data->type==C_STR) + free(p); + data->type=C_INT; + } + return data->u.num; +} + +/*========================================== + * スタックへ数値をプッシュ + *------------------------------------------ + */ +void push_val(struct script_stack *stack,int type,int val) +{ + if(stack->sp >= stack->sp_max){ + stack->sp_max += 64; + stack->stack_data = (struct script_data *)aRealloc(stack->stack_data, + sizeof(stack->stack_data[0]) * stack->sp_max); + memset(stack->stack_data + (stack->sp_max - 64), 0, + 64 * sizeof(*(stack->stack_data))); + } +// if(battle_config.etc_log) +// printf("push (%d,%d)-> %d\n",type,val,stack->sp); + stack->stack_data[stack->sp].type=type; + stack->stack_data[stack->sp].u.num=val; + stack->sp++; +} + +/*========================================== + * スタックへ文字列をプッシュ + *------------------------------------------ + */ +void push_str(struct script_stack *stack,int type,unsigned char *str) +{ + if(stack->sp>=stack->sp_max){ + stack->sp_max += 64; + stack->stack_data = (struct script_data *)aRealloc(stack->stack_data, + sizeof(stack->stack_data[0]) * stack->sp_max); + memset(stack->stack_data + (stack->sp_max - 64), '\0', + 64 * sizeof(*(stack->stack_data))); + } +// if(battle_config.etc_log) +// printf("push (%d,%x)-> %d\n",type,str,stack->sp); + stack->stack_data[stack->sp].type=type; + stack->stack_data[stack->sp].u.str=str; + stack->sp++; +} + +/*========================================== + * スタックへ複製をプッシュ + *------------------------------------------ + */ +void push_copy(struct script_stack *stack,int pos) +{ + switch(stack->stack_data[pos].type){ + case C_CONSTSTR: + push_str(stack,C_CONSTSTR,stack->stack_data[pos].u.str); + break; + case C_STR: + push_str(stack,C_STR,strdup(stack->stack_data[pos].u.str)); + break; + default: + push_val(stack,stack->stack_data[pos].type,stack->stack_data[pos].u.num); + break; + } +} + +/*========================================== + * スタックからポップ + *------------------------------------------ + */ +void pop_stack(struct script_stack* stack,int start,int end) +{ + int i; + for(i=start;i<end;i++){ + if(stack->stack_data[i].type==C_STR){ + free(stack->stack_data[i].u.str); + } + } + if(stack->sp>end){ + memmove(&stack->stack_data[start],&stack->stack_data[end],sizeof(stack->stack_data[0])*(stack->sp-end)); + } + stack->sp-=end-start; +} + +// +// 埋め込み関数 +// +/*========================================== + * + *------------------------------------------ + */ +int buildin_mes(struct script_state *st) +{ + conv_str(st,& (st->stack->stack_data[st->start+2])); + clif_scriptmes(script_rid2sd(st),st->oid,st->stack->stack_data[st->start+2].u.str); + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int buildin_goto(struct script_state *st) +{ + int pos; + + if( st->stack->stack_data[st->start+2].type!=C_POS ){ + printf("script: goto: not label !\n"); + st->state=END; + return 0; + } + + pos=conv_num(st,& (st->stack->stack_data[st->start+2])); + st->pos=pos; + st->state=GOTO; + return 0; +} + +/*========================================== + * ユーザー定義関数の呼び出し + *------------------------------------------ + */ +int buildin_callfunc(struct script_state *st) +{ + char *scr; + char *str=conv_str(st,& (st->stack->stack_data[st->start+2])); + + if( (scr=strdb_search(script_get_userfunc_db(),str)) ){ + int i,j; + for(i=st->start+3,j=0;i<st->end;i++,j++) + push_copy(st->stack,i); + + push_val(st->stack,C_INT,j); // 引数の数をプッシュ + push_val(st->stack,C_INT,st->defsp); // 現在の基準スタックポインタをプッシュ + push_val(st->stack,C_INT,(int)st->script); // 現在のスクリプトをプッシュ + push_val(st->stack,C_RETINFO,st->pos); // 現在のスクリプト位置をプッシュ + + st->pos=0; + st->script=scr; + st->defsp=st->start+4+j; + st->state=GOTO; + }else{ + printf("script:callfunc: function not found! [%s]\n",str); + st->state=END; + } + return 0; +} +/*========================================== + * サブルーティンの呼び出し + *------------------------------------------ + */ +int buildin_callsub(struct script_state *st) +{ + int pos=conv_num(st,& (st->stack->stack_data[st->start+2])); + int i,j; + for(i=st->start+3,j=0;i<st->end;i++,j++) + push_copy(st->stack,i); + + push_val(st->stack,C_INT,j); // 引数の数をプッシュ + push_val(st->stack,C_INT,st->defsp); // 現在の基準スタックポインタをプッシュ + push_val(st->stack,C_INT,(int)st->script); // 現在のスクリプトをプッシュ + push_val(st->stack,C_RETINFO,st->pos); // 現在のスクリプト位置をプッシュ + + st->pos=pos; + st->defsp=st->start+4+j; + st->state=GOTO; + return 0; +} + +/*========================================== + * 引数の所得 + *------------------------------------------ + */ +int buildin_getarg(struct script_state *st) +{ + int num=conv_num(st,& (st->stack->stack_data[st->start+2])); + int max,stsp; + if( st->defsp<4 || st->stack->stack_data[st->defsp-1].type!=C_RETINFO ){ + printf("script:getarg without callfunc or callsub!\n"); + st->state=END; + return 0; + } + max=conv_num(st,& (st->stack->stack_data[st->defsp-4])); + stsp=st->defsp - max -4; + if( num >= max ){ + printf("script:getarg arg1(%d) out of range(%d) !\n",num,max); + st->state=END; + return 0; + } + push_copy(st->stack,stsp+num); + return 0; +} + +/*========================================== + * サブルーチン/ユーザー定義関数の終了 + *------------------------------------------ + */ +int buildin_return(struct script_state *st) +{ + if(st->end>st->start+2){ // 戻り値有り + push_copy(st->stack,st->start+2); + } + st->state=RETFUNC; + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int buildin_next(struct script_state *st) +{ + st->state=STOP; + clif_scriptnext(script_rid2sd(st),st->oid); + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int buildin_close(struct script_state *st) +{ + st->state=END; + clif_scriptclose(script_rid2sd(st),st->oid); + return 0; +} +int buildin_close2(struct script_state *st) +{ + st->state=STOP; + clif_scriptclose(script_rid2sd(st),st->oid); + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int buildin_menu(struct script_state *st) +{ + char *buf; + int len,i; + struct map_session_data *sd; + + sd=script_rid2sd(st); + + if(sd->state.menu_or_input==0){ + st->state=RERUNLINE; + sd->state.menu_or_input=1; + for(i=st->start+2,len=16;i<st->end;i+=2){ + conv_str(st,& (st->stack->stack_data[i])); + len+=strlen(st->stack->stack_data[i].u.str)+1; + } + buf=(char *)aCalloc(len,sizeof(char)); + buf[0]=0; + for(i=st->start+2,len=0;i<st->end;i+=2){ + strcat(buf,st->stack->stack_data[i].u.str); + strcat(buf,":"); + } + clif_scriptmenu(script_rid2sd(st),st->oid,buf); + free(buf); + } else if(sd->npc_menu==0xff){ // cansel + sd->state.menu_or_input=0; + st->state=END; + } else { // goto動作 + // ragemu互換のため + pc_setreg(sd,add_str("l15"),sd->npc_menu); + pc_setreg(sd,add_str("@menu"),sd->npc_menu); + sd->state.menu_or_input=0; + if(sd->npc_menu>0 && sd->npc_menu<(st->end-st->start)/2){ + int pos; + if( st->stack->stack_data[st->start+sd->npc_menu*2+1].type!=C_POS ){ + printf("script: menu: not label !\n"); + st->state=END; + return 0; + } + pos=conv_num(st,& (st->stack->stack_data[st->start+sd->npc_menu*2+1])); + st->pos=pos; + st->state=GOTO; + } + } + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int buildin_rand(struct script_state *st) +{ + int range,min,max; + + if(st->end>st->start+3){ + min=conv_num(st,& (st->stack->stack_data[st->start+2])); + max=conv_num(st,& (st->stack->stack_data[st->start+3])); + if(max<min){ + int tmp; + tmp=min; + min=max; + max=tmp; + } + range=max-min+1; + push_val(st->stack,C_INT,rand()%range+min); + } else { + range=conv_num(st,& (st->stack->stack_data[st->start+2])); + push_val(st->stack,C_INT,rand()%range); + } + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int buildin_warp(struct script_state *st) +{ + int x,y; + char *str; + struct map_session_data *sd=script_rid2sd(st); + + str=conv_str(st,& (st->stack->stack_data[st->start+2])); + x=conv_num(st,& (st->stack->stack_data[st->start+3])); + y=conv_num(st,& (st->stack->stack_data[st->start+4])); + if(strcmp(str,"Random")==0) + pc_randomwarp(sd,3); + else if(strcmp(str,"SavePoint")==0){ + if(map[sd->bl.m].flag.noreturn) // 蝶禁止 + return 0; + + pc_setpos(sd,sd->status.save_point.map, + sd->status.save_point.x,sd->status.save_point.y,3); + }else if(strcmp(str,"Save")==0){ + if(map[sd->bl.m].flag.noreturn) // 蝶禁止 + return 0; + + pc_setpos(sd,sd->status.save_point.map, + sd->status.save_point.x,sd->status.save_point.y,3); + }else + pc_setpos(sd,str,x,y,0); + return 0; +} +/*========================================== + * エリア指定ワープ + *------------------------------------------ + */ +int buildin_areawarp_sub(struct block_list *bl,va_list ap) +{ + int x,y; + char *map; + map=va_arg(ap, char *); + x=va_arg(ap,int); + y=va_arg(ap,int); + if(strcmp(map,"Random")==0) + pc_randomwarp((struct map_session_data *)bl,3); + else + pc_setpos((struct map_session_data *)bl,map,x,y,0); + return 0; +} +int buildin_areawarp(struct script_state *st) +{ + int x,y,m; + char *str; + char *mapname; + int x0,y0,x1,y1; + + mapname=conv_str(st,& (st->stack->stack_data[st->start+2])); + x0=conv_num(st,& (st->stack->stack_data[st->start+3])); + y0=conv_num(st,& (st->stack->stack_data[st->start+4])); + x1=conv_num(st,& (st->stack->stack_data[st->start+5])); + y1=conv_num(st,& (st->stack->stack_data[st->start+6])); + str=conv_str(st,& (st->stack->stack_data[st->start+7])); + x=conv_num(st,& (st->stack->stack_data[st->start+8])); + y=conv_num(st,& (st->stack->stack_data[st->start+9])); + + if( (m=map_mapname2mapid(mapname))< 0) + return 0; + + map_foreachinarea(buildin_areawarp_sub, + m,x0,y0,x1,y1,BL_PC, str,x,y ); + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int buildin_heal(struct script_state *st) +{ + int hp,sp; + + hp=conv_num(st,& (st->stack->stack_data[st->start+2])); + sp=conv_num(st,& (st->stack->stack_data[st->start+3])); + pc_heal(script_rid2sd(st),hp,sp); + return 0; +} +/*========================================== + * + *------------------------------------------ + */ +int buildin_itemheal(struct script_state *st) +{ + int hp,sp; + + hp=conv_num(st,& (st->stack->stack_data[st->start+2])); + sp=conv_num(st,& (st->stack->stack_data[st->start+3])); + pc_itemheal(script_rid2sd(st),hp,sp); + return 0; +} +/*========================================== + * + *------------------------------------------ + */ +int buildin_percentheal(struct script_state *st) +{ + int hp,sp; + + hp=conv_num(st,& (st->stack->stack_data[st->start+2])); + sp=conv_num(st,& (st->stack->stack_data[st->start+3])); + pc_percentheal(script_rid2sd(st),hp,sp); + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int buildin_jobchange(struct script_state *st) +{ + int job, upper=-1; + + job=conv_num(st,& (st->stack->stack_data[st->start+2])); + if( st->end>st->start+3 ) + upper=conv_num(st,& (st->stack->stack_data[st->start+3])); + + if ((job >= 0 && job < MAX_PC_CLASS)) + pc_jobchange(script_rid2sd(st),job, upper); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int buildin_input(struct script_state *st) +{ + struct map_session_data *sd=NULL; + int num=(st->end>st->start+2)?st->stack->stack_data[st->start+2].u.num:0; + char *name=(st->end>st->start+2)?str_buf+str_data[num&0x00ffffff].str:""; +// char prefix=*name; + char postfix=name[strlen(name)-1]; + + sd=script_rid2sd(st); + if(sd->state.menu_or_input){ + sd->state.menu_or_input=0; + if( postfix=='$' ){ + // 文字列 + if(st->end>st->start+2){ // 引数1個 + set_reg(sd,num,name,(void*)sd->npc_str); + }else{ + printf("buildin_input: string discarded !!\n"); + } + }else{ + + //commented by Lupus (check Value Number Input fix in clif.c) + //** Fix by fritz :X keeps people from abusing old input bugs + if(sd->npc_amount < 0) //** If input amount is less then 0 + { + clif_tradecancelled(sd); // added "Deal has been cancelled" message by Valaris + buildin_close(st); //** close + } + + // 数値 + if(st->end>st->start+2){ // 引数1個 + set_reg(sd,num,name,(void*)sd->npc_amount); + } else { + // ragemu互換のため + pc_setreg(sd,add_str("l14"),sd->npc_amount); + } + } + } else { + st->state=RERUNLINE; + if(postfix=='$')clif_scriptinputstr(sd,st->oid); + else clif_scriptinput(sd,st->oid); + sd->state.menu_or_input=1; + } + return 0; +} + + +/*========================================== + * + *------------------------------------------ + */ +int buildin_if(struct script_state *st) +{ + int sel,i; + + sel=conv_num(st,& (st->stack->stack_data[st->start+2])); + if(!sel) + return 0; + + // 関数名をコピー + push_copy(st->stack,st->start+3); + // 間に引数マーカを入れて + push_val(st->stack,C_ARG,0); + // 残りの引数をコピー + for(i=st->start+4;i<st->end;i++){ + push_copy(st->stack,i); + } + run_func(st); + + return 0; +} + + +/*========================================== + * 変数設定 + *------------------------------------------ + */ +int buildin_set(struct script_state *st) +{ + struct map_session_data *sd=NULL; + int num=st->stack->stack_data[st->start+2].u.num; + char *name=str_buf+str_data[num&0x00ffffff].str; + char prefix=*name; + char postfix=name[strlen(name)-1]; + + if( st->stack->stack_data[st->start+2].type!=C_NAME ){ + printf("script: buildin_set: not name\n"); + return 0; + } + + if( prefix!='$' ) + sd=script_rid2sd(st); + + + if( postfix=='$' ){ + // 文字列 + char *str = conv_str(st,& (st->stack->stack_data[st->start+3])); + set_reg(sd,num,name,(void*)str); + }else{ + // 数値 + int val = conv_num(st,& (st->stack->stack_data[st->start+3])); + set_reg(sd,num,name,(void*)val); + } + + return 0; +} +/*========================================== + * 配列変数設定 + *------------------------------------------ + */ +int buildin_setarray(struct script_state *st) +{ + struct map_session_data *sd=NULL; + int num=st->stack->stack_data[st->start+2].u.num; + char *name=str_buf+str_data[num&0x00ffffff].str; + char prefix=*name; + char postfix=name[strlen(name)-1]; + int i,j; + + if( prefix!='$' && prefix!='@' ){ + printf("buildin_setarray: illegal scope !\n"); + return 0; + } + if( prefix!='$' ) + sd=script_rid2sd(st); + + for(j=0,i=st->start+3; i<st->end && j<128;i++,j++){ + void *v; + if( postfix=='$' ) + v=(void*)conv_str(st,& (st->stack->stack_data[i])); + else + v=(void*)conv_num(st,& (st->stack->stack_data[i])); + set_reg( sd, num+(j<<24), name, v); + } + return 0; +} +/*========================================== + * 配列変数クリア + *------------------------------------------ + */ +int buildin_cleararray(struct script_state *st) +{ + struct map_session_data *sd=NULL; + int num=st->stack->stack_data[st->start+2].u.num; + char *name=str_buf+str_data[num&0x00ffffff].str; + char prefix=*name; + char postfix=name[strlen(name)-1]; + int sz=conv_num(st,& (st->stack->stack_data[st->start+4])); + int i; + void *v; + + if( prefix!='$' && prefix!='@' ){ + printf("buildin_cleararray: illegal scope !\n"); + return 0; + } + if( prefix!='$' ) + sd=script_rid2sd(st); + + if( postfix=='$' ) + v=(void*)conv_str(st,& (st->stack->stack_data[st->start+3])); + else + v=(void*)conv_num(st,& (st->stack->stack_data[st->start+3])); + + for(i=0;i<sz;i++) + set_reg(sd,num+(i<<24),name,v); + return 0; +} +/*========================================== + * 配列変数コピー + *------------------------------------------ + */ +int buildin_copyarray(struct script_state *st) +{ + struct map_session_data *sd=NULL; + int num=st->stack->stack_data[st->start+2].u.num; + char *name=str_buf+str_data[num&0x00ffffff].str; + char prefix=*name; + char postfix=name[strlen(name)-1]; + int num2=st->stack->stack_data[st->start+3].u.num; + char *name2=str_buf+str_data[num2&0x00ffffff].str; + char prefix2=*name2; + char postfix2=name2[strlen(name2)-1]; + int sz=conv_num(st,& (st->stack->stack_data[st->start+4])); + int i; + + if( prefix!='$' && prefix!='@' && prefix2!='$' && prefix2!='@' ){ + printf("buildin_copyarray: illegal scope !\n"); + return 0; + } + if( (postfix=='$' || postfix2=='$') && postfix!=postfix2 ){ + printf("buildin_copyarray: type mismatch !\n"); + return 0; + } + if( prefix!='$' || prefix2!='$' ) + sd=script_rid2sd(st); + + + for(i=0;i<sz;i++) + set_reg(sd,num+(i<<24),name, get_val2(st,num2+(i<<24)) ); + return 0; +} +/*========================================== + * 配列変数のサイズ所得 + *------------------------------------------ + */ +static int getarraysize(struct script_state *st,int num,int postfix) +{ + int i=(num>>24),c=i; + for(;i<128;i++){ + void *v=get_val2(st,num+(i<<24)); + if(postfix=='$' && *((char*)v) ) c=i; + if(postfix!='$' && (int)v )c=i; + } + return c+1; +} +int buildin_getarraysize(struct script_state *st) +{ + int num=st->stack->stack_data[st->start+2].u.num; + char *name=str_buf+str_data[num&0x00ffffff].str; + char prefix=*name; + char postfix=name[strlen(name)-1]; + + if( prefix!='$' && prefix!='@' ){ + printf("buildin_copyarray: illegal scope !\n"); + return 0; + } + + push_val(st->stack,C_INT,getarraysize(st,num,postfix) ); + return 0; +} +/*========================================== + * 配列変数から要素削除 + *------------------------------------------ + */ +int buildin_deletearray(struct script_state *st) +{ + struct map_session_data *sd=NULL; + int num=st->stack->stack_data[st->start+2].u.num; + char *name=str_buf+str_data[num&0x00ffffff].str; + char prefix=*name; + char postfix=name[strlen(name)-1]; + int count=1; + int i,sz=getarraysize(st,num,postfix)-(num>>24)-count+1; + + + if( (st->end > st->start+3) ) + count=conv_num(st,& (st->stack->stack_data[st->start+3])); + + if( prefix!='$' && prefix!='@' ){ + printf("buildin_deletearray: illegal scope !\n"); + return 0; + } + if( prefix!='$' ) + sd=script_rid2sd(st); + + for(i=0;i<sz;i++){ + set_reg(sd,num+(i<<24),name, get_val2(st,num+((i+count)<<24) ) ); + } + for(;i<(128-(num>>24));i++){ + if( postfix!='$' ) set_reg(sd,num+(i<<24),name, 0); + if( postfix=='$' ) set_reg(sd,num+(i<<24),name, ""); + } + return 0; +} + +/*========================================== + * 指定要素を表す値(キー)を所得する + *------------------------------------------ + */ +int buildin_getelementofarray(struct script_state *st) +{ + if( st->stack->stack_data[st->start+2].type==C_NAME ){ + int i=conv_num(st,& (st->stack->stack_data[st->start+3])); + if(i>127 || i<0){ + printf("script: getelementofarray (operator[]): param2 illegal number %d\n",i); + push_val(st->stack,C_INT,0); + }else{ + push_val(st->stack,C_NAME, + (i<<24) | st->stack->stack_data[st->start+2].u.num ); + } + }else{ + printf("script: getelementofarray (operator[]): param1 not name !\n"); + push_val(st->stack,C_INT,0); + } + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int buildin_setlook(struct script_state *st) +{ + int type,val; + + type=conv_num(st,& (st->stack->stack_data[st->start+2])); + val=conv_num(st,& (st->stack->stack_data[st->start+3])); + + pc_changelook(script_rid2sd(st),type,val); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int buildin_cutin(struct script_state *st) +{ + int type; + + conv_str(st,& (st->stack->stack_data[st->start+2])); + type=conv_num(st,& (st->stack->stack_data[st->start+3])); + + clif_cutin(script_rid2sd(st),st->stack->stack_data[st->start+2].u.str,type); + + return 0; +} +/*========================================== + * カードのイラストを表示する + *------------------------------------------ + */ +int buildin_cutincard(struct script_state *st) +{ + int itemid; + + itemid=conv_num(st,& (st->stack->stack_data[st->start+2])); + + clif_cutin(script_rid2sd(st),itemdb_search(itemid)->cardillustname,4); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int buildin_viewpoint(struct script_state *st) +{ + int type,x,y,id,color; + + type=conv_num(st,& (st->stack->stack_data[st->start+2])); + x=conv_num(st,& (st->stack->stack_data[st->start+3])); + y=conv_num(st,& (st->stack->stack_data[st->start+4])); + id=conv_num(st,& (st->stack->stack_data[st->start+5])); + color=conv_num(st,& (st->stack->stack_data[st->start+6])); + + clif_viewpoint(script_rid2sd(st),st->oid,type,x,y,id,color); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int buildin_countitem(struct script_state *st) +{ + int nameid=0,count=0,i; + struct map_session_data *sd; + + struct script_data *data; + + sd = script_rid2sd(st); + + data=&(st->stack->stack_data[st->start+2]); + get_val(st,data); + if( data->type==C_STR || data->type==C_CONSTSTR ){ + const char *name=conv_str(st,data); + struct item_data *item_data; + if( (item_data = itemdb_searchname(name)) != NULL) + nameid=item_data->nameid; + }else + nameid=conv_num(st,data); + + if (nameid>=500) //if no such ID then skip this iteration + for(i=0;i<MAX_INVENTORY;i++){ + if(sd->status.inventory[i].nameid==nameid) + count+=sd->status.inventory[i].amount; + } + else{ + if(battle_config.error_log) + printf("wrong item ID : countitem(%i)\n",nameid); + } + push_val(st->stack,C_INT,count); + + return 0; +} + +/*========================================== + * 重量チェック + *------------------------------------------ + */ +int buildin_checkweight(struct script_state *st) +{ + int nameid=0,amount; + struct map_session_data *sd; + struct script_data *data; + + sd = script_rid2sd(st); + + data=&(st->stack->stack_data[st->start+2]); + get_val(st,data); + if( data->type==C_STR || data->type==C_CONSTSTR ){ + const char *name=conv_str(st,data); + struct item_data *item_data = itemdb_searchname(name); + if( item_data ) + nameid=item_data->nameid; + }else + nameid=conv_num(st,data); + + amount=conv_num(st,& (st->stack->stack_data[st->start+3])); + if ( amount<=0 || nameid<500 ) { //if get wrong item ID or amount<=0, don't count weight of non existing items + push_val(st->stack,C_INT,0); + } + + sd=script_rid2sd(st); + if(itemdb_weight(nameid)*amount + sd->weight > sd->max_weight){ + push_val(st->stack,C_INT,0); + } else { + push_val(st->stack,C_INT,1); + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int buildin_getitem(struct script_state *st) +{ + int nameid,amount,flag = 0; + struct item item_tmp; + struct map_session_data *sd; + struct script_data *data; + + sd = script_rid2sd(st); + + data=&(st->stack->stack_data[st->start+2]); + get_val(st,data); + if( data->type==C_STR || data->type==C_CONSTSTR ){ + const char *name=conv_str(st,data); + struct item_data *item_data = itemdb_searchname(name); + nameid=512; //Apple item ID + if( item_data != NULL) + nameid=item_data->nameid; + }else + nameid=conv_num(st,data); + + if ( ( amount=conv_num(st,& (st->stack->stack_data[st->start+3])) ) <= 0) { + return 0; //return if amount <=0, skip the useles iteration + } + //Violet Box, Blue Box, etc - random item pick + if(nameid<0) { // ランダム + nameid=itemdb_searchrandomid(-nameid); + flag = 1; + } + + if(nameid > 0) { + memset(&item_tmp,0,sizeof(item_tmp)); + item_tmp.nameid=nameid; + if(!flag) + item_tmp.identify=1; + else + item_tmp.identify=!itemdb_isequip3(nameid); + if( st->end>st->start+5 ) //アイテムを指定したIDに渡す + sd=map_id2sd(conv_num(st,& (st->stack->stack_data[st->start+5]))); + if(sd == NULL) //アイテムを渡す相手がいなかったらお帰り + return 0; + if((flag = pc_additem(sd,&item_tmp,amount))) { + clif_additem(sd,0,0,flag); + map_addflooritem(&item_tmp,amount,sd->bl.m,sd->bl.x,sd->bl.y,NULL,NULL,NULL,0); + } + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int buildin_getitem2(struct script_state *st) +{ + int nameid,amount,flag = 0; + int iden,ref,attr,c1,c2,c3,c4; + struct item_data *item_data; + struct item item_tmp; + struct map_session_data *sd; + struct script_data *data; + + sd = script_rid2sd(st); + + data=&(st->stack->stack_data[st->start+2]); + get_val(st,data); + if( data->type==C_STR || data->type==C_CONSTSTR ){ + const char *name=conv_str(st,data); + struct item_data *item_data = itemdb_searchname(name); + nameid=512; //Apple item ID + if( item_data ) + nameid=item_data->nameid; + }else + nameid=conv_num(st,data); + + amount=conv_num(st,& (st->stack->stack_data[st->start+3])); + iden=conv_num(st,& (st->stack->stack_data[st->start+4])); + ref=conv_num(st,& (st->stack->stack_data[st->start+5])); + attr=conv_num(st,& (st->stack->stack_data[st->start+6])); + c1=conv_num(st,& (st->stack->stack_data[st->start+7])); + c2=conv_num(st,& (st->stack->stack_data[st->start+8])); + c3=conv_num(st,& (st->stack->stack_data[st->start+9])); + c4=conv_num(st,& (st->stack->stack_data[st->start+10])); + if( st->end>st->start+11 ) //アイテムを指定したIDに渡す + sd=map_id2sd(conv_num(st,& (st->stack->stack_data[st->start+11]))); + if(sd == NULL) //アイテムを渡す相手がいなかったらお帰り + return 0; + + if(nameid<0) { // ランダム + nameid=itemdb_searchrandomid(-nameid); + flag = 1; + } + + if(nameid > 0) { + memset(&item_tmp,0,sizeof(item_tmp)); + item_data=itemdb_search(nameid); + if(item_data->type==4 || item_data->type==5){ + if(ref > 10) ref = 10; + } + else if(item_data->type==7) { + iden = 1; + ref = 0; + } + else { + iden = 1; + ref = attr = 0; + } + + item_tmp.nameid=nameid; + if(!flag) + item_tmp.identify=iden; + else if(item_data->type==4 || item_data->type==5) + item_tmp.identify=0; + item_tmp.refine=ref; + item_tmp.attribute=attr; + item_tmp.card[0]=c1; + item_tmp.card[1]=c2; + item_tmp.card[2]=c3; + item_tmp.card[3]=c4; + if((flag = pc_additem(sd,&item_tmp,amount))) { + clif_additem(sd,0,0,flag); + map_addflooritem(&item_tmp,amount,sd->bl.m,sd->bl.x,sd->bl.y,NULL,NULL,NULL,0); + } + } + + return 0; +} +/*========================================== + * + *------------------------------------------ + */ +int buildin_makeitem(struct script_state *st) +{ + int nameid,amount,flag = 0; + int x,y,m; + char *mapname; + struct item item_tmp; + struct map_session_data *sd; + struct script_data *data; + + sd = script_rid2sd(st); + + data=&(st->stack->stack_data[st->start+2]); + get_val(st,data); + if( data->type==C_STR || data->type==C_CONSTSTR ){ + const char *name=conv_str(st,data); + struct item_data *item_data = itemdb_searchname(name); + nameid=512; //Apple Item ID + if( item_data ) + nameid=item_data->nameid; + }else + nameid=conv_num(st,data); + + amount=conv_num(st,& (st->stack->stack_data[st->start+3])); + mapname =conv_str(st,& (st->stack->stack_data[st->start+4])); + x =conv_num(st,& (st->stack->stack_data[st->start+5])); + y =conv_num(st,& (st->stack->stack_data[st->start+6])); + + if( sd && strcmp(mapname,"this")==0) + m=sd->bl.m; + else + m=map_mapname2mapid(mapname); + + if(nameid<0) { // ランダム + nameid=itemdb_searchrandomid(-nameid); + flag = 1; + } + + if(nameid > 0) { + memset(&item_tmp,0,sizeof(item_tmp)); + item_tmp.nameid=nameid; + if(!flag) + item_tmp.identify=1; + else + item_tmp.identify=!itemdb_isequip3(nameid); + +// clif_additem(sd,0,0,flag); + map_addflooritem(&item_tmp,amount,m,x,y,NULL,NULL,NULL,0); + } + + return 0; +} +/*========================================== + * + *------------------------------------------ + */ +int buildin_delitem(struct script_state *st) +{ + int nameid=0,amount,i; + struct map_session_data *sd; + struct script_data *data; + + sd = script_rid2sd(st); + + data=&(st->stack->stack_data[st->start+2]); + get_val(st,data); + if( data->type==C_STR || data->type==C_CONSTSTR ){ + const char *name=conv_str(st,data); + struct item_data *item_data = itemdb_searchname(name); + //nameid=512; + if( item_data ) + nameid=item_data->nameid; + }else + nameid=conv_num(st,data); + + amount=conv_num(st,& (st->stack->stack_data[st->start+3])); + + if (nameid<500 || amount<=0 ) {//by Lupus. Don't run FOR if u got wrong item ID or amount<=0 + //printf("wrong item ID or amount<=0 : delitem %i,\n",nameid,amount); + return 0; + } + sd=script_rid2sd(st); + + for(i=0;i<MAX_INVENTORY;i++){ + if(sd->status.inventory[i].nameid<=0 || sd->inventory_data[i] == NULL || + sd->inventory_data[i]->type!=7 || + sd->status.inventory[i].amount<=0) + continue; + if(sd->status.inventory[i].nameid == nameid){ + if(sd->status.inventory[i].card[0] == (short)0xff00){ + if(search_petDB_index(nameid, PET_EGG) >= 0){ + intif_delete_petdata(*((long *)(&sd->status.inventory[i].card[1]))); + break; + } + } + } + } + for(i=0;i<MAX_INVENTORY;i++){ + if(sd->status.inventory[i].nameid==nameid){ + if(sd->status.inventory[i].amount>=amount){ + pc_delitem(sd,i,amount,0); + break; + } else { + amount-=sd->status.inventory[i].amount; + if(amount==0) + amount=sd->status.inventory[i].amount; + pc_delitem(sd,i,amount,0); + break; + } + } + } + + return 0; +} + +/*========================================== + *キャラ関係のパラメータ取得 + *------------------------------------------ + */ +int buildin_readparam(struct script_state *st) +{ + int type; + struct map_session_data *sd; + + type=conv_num(st,& (st->stack->stack_data[st->start+2])); + if( st->end>st->start+3 ) + sd=map_nick2sd(conv_str(st,& (st->stack->stack_data[st->start+3]))); + else + sd=script_rid2sd(st); + + if(sd==NULL){ + push_val(st->stack,C_INT,-1); + return 0; + } + + push_val(st->stack,C_INT,pc_readparam(sd,type)); + + return 0; +} +/*========================================== + *キャラ関係のID取得 + *------------------------------------------ + */ +int buildin_getcharid(struct script_state *st) +{ + int num; + struct map_session_data *sd; + + num=conv_num(st,& (st->stack->stack_data[st->start+2])); + if( st->end>st->start+3 ) + sd=map_nick2sd(conv_str(st,& (st->stack->stack_data[st->start+3]))); + else + sd=script_rid2sd(st); + if(sd==NULL){ + push_val(st->stack,C_INT,-1); + return 0; + } + if(num==0) + push_val(st->stack,C_INT,sd->status.char_id); + if(num==1) + push_val(st->stack,C_INT,sd->status.party_id); + if(num==2) + push_val(st->stack,C_INT,sd->status.guild_id); + if(num==3) + push_val(st->stack,C_INT,sd->status.account_id); + return 0; +} +/*========================================== + *指定IDのPT名取得 + *------------------------------------------ + */ +char *buildin_getpartyname_sub(int party_id) +{ + struct party *p; + + p=NULL; + p=party_search(party_id); + + if(p!=NULL){ + char *buf; + buf=(char *)aCalloc(24,sizeof(char)); + strcpy(buf,p->name); + return buf; + } + + return 0; +} +int buildin_getpartyname(struct script_state *st) +{ + char *name; + int party_id; + + party_id=conv_num(st,& (st->stack->stack_data[st->start+2])); + name=buildin_getpartyname_sub(party_id); + if(name!=0) + push_str(st->stack,C_STR,name); + else + push_str(st->stack,C_CONSTSTR,"null"); + + return 0; +} +/*========================================== + *指定IDのPT人数とメンバーID取得 + *------------------------------------------ + */ +int buildin_getpartymember(struct script_state *st) +{ + struct party *p; + int i,j=0; + + p=NULL; + p=party_search(conv_num(st,& (st->stack->stack_data[st->start+2]))); + + if(p!=NULL){ + for(i=0;i<MAX_PARTY;i++){ + if(p->member[i].account_id){ +// printf("name:%s %d\n",p->member[i].name,i); + mapreg_setregstr(add_str("$@partymembername$")+(i<<24),p->member[i].name); + j++; + } + } + } + mapreg_setreg(add_str("$@partymembercount"),j); + + return 0; +} +/*========================================== + *指定IDのギルド名取得 + *------------------------------------------ + */ +char *buildin_getguildname_sub(int guild_id) +{ + struct guild *g=NULL; + g=guild_search(guild_id); + + if(g!=NULL){ + char *buf; + buf=(char *)aCalloc(24,sizeof(char)); + strcpy(buf,g->name); + return buf; + } + return 0; +} +int buildin_getguildname(struct script_state *st) +{ + char *name; + int guild_id=conv_num(st,& (st->stack->stack_data[st->start+2])); + name=buildin_getguildname_sub(guild_id); + if(name!=0) + push_str(st->stack,C_STR,name); + else + push_str(st->stack,C_CONSTSTR,"null"); + return 0; +} + +/*========================================== + *指定IDのGuildMaster名取得 + *------------------------------------------ + */ +char *buildin_getguildmaster_sub(int guild_id) +{ + struct guild *g=NULL; + g=guild_search(guild_id); + + if(g!=NULL){ + char *buf; + buf=(char *)aCalloc(24,sizeof(char)); + strncpy(buf,g->master, 23); + return buf; + } + + return 0; +} +int buildin_getguildmaster(struct script_state *st) +{ + char *master; + int guild_id=conv_num(st,& (st->stack->stack_data[st->start+2])); + master=buildin_getguildmaster_sub(guild_id); + if(master!=0) + push_str(st->stack,C_STR,master); + else + push_str(st->stack,C_CONSTSTR,"null"); + return 0; +} + +int buildin_getguildmasterid(struct script_state *st) +{ + char *master; + struct map_session_data *sd=NULL; + int guild_id=conv_num(st,& (st->stack->stack_data[st->start+2])); + master=buildin_getguildmaster_sub(guild_id); + if(master!=0){ + if((sd=map_nick2sd(master)) == NULL){ + push_val(st->stack,C_INT,0); + return 0; + } + push_val(st->stack,C_INT,sd->status.char_id); + }else{ + push_val(st->stack,C_INT,0); + } + return 0; +} + +/*========================================== + * キャラクタの名前 + *------------------------------------------ + */ +int buildin_strcharinfo(struct script_state *st) +{ + struct map_session_data *sd; + int num; + + sd=script_rid2sd(st); + num=conv_num(st,& (st->stack->stack_data[st->start+2])); + if(num==0){ + char *buf; + buf=(char *)aCalloc(24,sizeof(char)); + strncpy(buf,sd->status.name, 23); + push_str(st->stack,C_STR,buf); + } + if(num==1){ + char *buf; + buf=buildin_getpartyname_sub(sd->status.party_id); + if(buf!=0) + push_str(st->stack,C_STR,buf); + else + push_str(st->stack,C_CONSTSTR,""); + } + if(num==2){ + char *buf; + buf=buildin_getguildname_sub(sd->status.guild_id); + if(buf!=0) + push_str(st->stack,C_STR,buf); + else + push_str(st->stack,C_CONSTSTR,""); + } + + return 0; +} + +unsigned int equip[10]={0x0100,0x0010,0x0020,0x0002,0x0004,0x0040,0x0008,0x0080,0x0200,0x0001}; + +/*========================================== + * GetEquipID(Pos); Pos: 1-10 + *------------------------------------------ + */ +int buildin_getequipid(struct script_state *st) +{ + int i,num; + struct map_session_data *sd; + struct item_data* item; + + sd=script_rid2sd(st); + if(sd == NULL) + { + printf("getequipid: sd == NULL\n"); + return 0; + } + num=conv_num(st,& (st->stack->stack_data[st->start+2])); + i=pc_checkequip(sd,equip[num-1]); + if(i >= 0){ + item=sd->inventory_data[i]; + if(item) + push_val(st->stack,C_INT,item->nameid); + else + push_val(st->stack,C_INT,0); + }else{ + push_val(st->stack,C_INT,-1); + } + return 0; +} + +/*========================================== + * 装備名文字列(精錬メニュー用) + *------------------------------------------ + */ +int buildin_getequipname(struct script_state *st) +{ + int i,num; + struct map_session_data *sd; + struct item_data* item; + char *buf; + + buf=(char *)aCalloc(64,sizeof(char)); + sd=script_rid2sd(st); + num=conv_num(st,& (st->stack->stack_data[st->start+2])); + i=pc_checkequip(sd,equip[num-1]); + if(i >= 0){ + item=sd->inventory_data[i]; + if(item) + sprintf(buf,"%s-[%s]",pos[num-1],item->jname); + else + sprintf(buf,"%s-[%s]",pos[num-1],pos[10]); + }else{ + sprintf(buf,"%s-[%s]",pos[num-1],pos[10]); + } + push_str(st->stack,C_STR,buf); + + return 0; +} + +/*========================================== + * getbrokenid [Valaris] + *------------------------------------------ + */ +int buildin_getbrokenid(struct script_state *st) +{ + int i,num,id=0,brokencounter=0; + struct map_session_data *sd; + + sd=script_rid2sd(st); + + num=conv_num(st,& (st->stack->stack_data[st->start+2])); + for(i=0; i<MAX_INVENTORY; i++) { + if(sd->status.inventory[i].broken==1){ + brokencounter++; + if(num==brokencounter){ + id=sd->status.inventory[i].nameid; + break; + } + } + } + + push_val(st->stack,C_INT,id); + + return 0; +} + +/*========================================== + * repair [Valaris] + *------------------------------------------ + */ +int buildin_repair(struct script_state *st) +{ + int i,num; + int repaircounter=0; + struct map_session_data *sd; + + + sd=script_rid2sd(st); + + num=conv_num(st,& (st->stack->stack_data[st->start+2])); + for(i=0; i<MAX_INVENTORY; i++) { + if(sd->status.inventory[i].broken==1){ + repaircounter++; + if(num==repaircounter){ + sd->status.inventory[i].broken=0; + clif_equiplist(sd); + clif_produceeffect(sd, 0, sd->status.inventory[i].nameid); + clif_misceffect(&sd->bl, 3); + clif_displaymessage(sd->fd,"Item has been repaired."); + break; + } + } + } + + return 0; +} + +/*========================================== + * 装備チェック + *------------------------------------------ + */ +int buildin_getequipisequiped(struct script_state *st) +{ + int i,num; + struct map_session_data *sd; + + num=conv_num(st,& (st->stack->stack_data[st->start+2])); + sd=script_rid2sd(st); + i=pc_checkequip(sd,equip[num-1]); + if(i >= 0){ + push_val(st->stack,C_INT,1); + }else{ + push_val(st->stack,C_INT,0); + } + + return 0; +} + +/*========================================== + * 装備品精錬可能チェック + *------------------------------------------ + */ +int buildin_getequipisenableref(struct script_state *st) +{ + int i,num; + struct map_session_data *sd; + + num=conv_num(st,& (st->stack->stack_data[st->start+2])); + sd=script_rid2sd(st); + i=pc_checkequip(sd,equip[num-1]); + if(i >= 0 && num<7 && sd->inventory_data[i] && (num!=1 || sd->inventory_data[i]->def > 1 + || (sd->inventory_data[i]->def==1 && sd->inventory_data[i]->equip_script==NULL) + || (sd->inventory_data[i]->def<=0 && sd->inventory_data[i]->equip_script!=NULL)) + ){ + push_val(st->stack,C_INT,1); + }else{ + push_val(st->stack,C_INT,0); + } + + return 0; +} + +/*========================================== + * 装備品鑑定チェック + *------------------------------------------ + */ +int buildin_getequipisidentify(struct script_state *st) +{ + int i,num; + struct map_session_data *sd; + + num=conv_num(st,& (st->stack->stack_data[st->start+2])); + sd=script_rid2sd(st); + i=pc_checkequip(sd,equip[num-1]); + if(i >= 0) + push_val(st->stack,C_INT,sd->status.inventory[i].identify); + else + push_val(st->stack,C_INT,0); + + return 0; +} + +/*========================================== + * 装備品精錬度 + *------------------------------------------ + */ +int buildin_getequiprefinerycnt(struct script_state *st) +{ + int i,num; + struct map_session_data *sd; + + num=conv_num(st,& (st->stack->stack_data[st->start+2])); + sd=script_rid2sd(st); + i=pc_checkequip(sd,equip[num-1]); + if(i >= 0) + push_val(st->stack,C_INT,sd->status.inventory[i].refine); + else + push_val(st->stack,C_INT,0); + + return 0; +} + +/*========================================== + * 装備品武器LV + *------------------------------------------ + */ +int buildin_getequipweaponlv(struct script_state *st) +{ + int i,num; + struct map_session_data *sd; + + num=conv_num(st,& (st->stack->stack_data[st->start+2])); + sd=script_rid2sd(st); + i=pc_checkequip(sd,equip[num-1]); + if(i >= 0 && sd->inventory_data[i]) + push_val(st->stack,C_INT,sd->inventory_data[i]->wlv); + else + push_val(st->stack,C_INT,0); + + return 0; +} + +/*========================================== + * 装備品精錬成功率 + *------------------------------------------ + */ +int buildin_getequippercentrefinery(struct script_state *st) +{ + int i,num; + struct map_session_data *sd; + + num=conv_num(st,& (st->stack->stack_data[st->start+2])); + sd=script_rid2sd(st); + i=pc_checkequip(sd,equip[num-1]); + if(i >= 0) + push_val(st->stack,C_INT,pc_percentrefinery(sd,&sd->status.inventory[i])); + else + push_val(st->stack,C_INT,0); + + return 0; +} + +/*========================================== + * 精錬成功 + *------------------------------------------ + */ +int buildin_successrefitem(struct script_state *st) +{ + int i,num,ep; + struct map_session_data *sd; + + num=conv_num(st,& (st->stack->stack_data[st->start+2])); + sd=script_rid2sd(st); + i=pc_checkequip(sd,equip[num-1]); + if(i >= 0) { + ep=sd->status.inventory[i].equip; + + sd->status.inventory[i].refine++; + pc_unequipitem(sd,i,0); + clif_refine(sd->fd,sd,0,i,sd->status.inventory[i].refine); + clif_delitem(sd,i,1); + clif_additem(sd,i,1,0); + pc_equipitem(sd,i,ep); + clif_misceffect(&sd->bl,3); + } + + return 0; +} + +/*========================================== + * 精錬失敗 + *------------------------------------------ + */ +int buildin_failedrefitem(struct script_state *st) +{ + int i,num; + struct map_session_data *sd; + + num=conv_num(st,& (st->stack->stack_data[st->start+2])); + sd=script_rid2sd(st); + i=pc_checkequip(sd,equip[num-1]); + if(i >= 0) { + sd->status.inventory[i].refine = 0; + pc_unequipitem(sd,i,0); + // 精錬失敗エフェクトのパケット + clif_refine(sd->fd,sd,1,i,sd->status.inventory[i].refine); + pc_delitem(sd,i,1,0); + // 他の人にも失敗を通知 + clif_misceffect(&sd->bl,2); + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int buildin_statusup(struct script_state *st) +{ + int type; + struct map_session_data *sd; + + type=conv_num(st,& (st->stack->stack_data[st->start+2])); + sd=script_rid2sd(st); + pc_statusup(sd,type); + + return 0; +} +/*========================================== + * + *------------------------------------------ + */ +int buildin_statusup2(struct script_state *st) +{ + int type,val; + struct map_session_data *sd; + + type=conv_num(st,& (st->stack->stack_data[st->start+2])); + val=conv_num(st,& (st->stack->stack_data[st->start+3])); + sd=script_rid2sd(st); + pc_statusup2(sd,type,val); + + return 0; +} +/*========================================== + * 装備品による能力値ボーナス + *------------------------------------------ + */ +int buildin_bonus(struct script_state *st) +{ + int type,val; + struct map_session_data *sd; + + type=conv_num(st,& (st->stack->stack_data[st->start+2])); + val=conv_num(st,& (st->stack->stack_data[st->start+3])); + sd=script_rid2sd(st); + pc_bonus(sd,type,val); + + return 0; +} +/*========================================== + * 装備品による能力値ボーナス + *------------------------------------------ + */ +int buildin_bonus2(struct script_state *st) +{ + int type,type2,val; + struct map_session_data *sd; + + type=conv_num(st,& (st->stack->stack_data[st->start+2])); + type2=conv_num(st,& (st->stack->stack_data[st->start+3])); + val=conv_num(st,& (st->stack->stack_data[st->start+4])); + sd=script_rid2sd(st); + pc_bonus2(sd,type,type2,val); + + return 0; +} +/*========================================== + * 装備品による能力値ボーナス + *------------------------------------------ + */ +int buildin_bonus3(struct script_state *st) +{ + int type,type2,type3,val; + struct map_session_data *sd; + + type=conv_num(st,& (st->stack->stack_data[st->start+2])); + type2=conv_num(st,& (st->stack->stack_data[st->start+3])); + type3=conv_num(st,& (st->stack->stack_data[st->start+4])); + val=conv_num(st,& (st->stack->stack_data[st->start+5])); + sd=script_rid2sd(st); + pc_bonus3(sd,type,type2,type3,val); + + return 0; +} +/*========================================== + * スキル所得 + *------------------------------------------ + */ +int buildin_skill(struct script_state *st) +{ + int id,level,flag=1; + struct map_session_data *sd; + + id=conv_num(st,& (st->stack->stack_data[st->start+2])); + level=conv_num(st,& (st->stack->stack_data[st->start+3])); + if( st->end>st->start+4 ) + flag=conv_num(st,&(st->stack->stack_data[st->start+4]) ); + sd=script_rid2sd(st); + pc_skill(sd,id,level,flag); + + return 0; +} +/*========================================== + * ギルドスキル取得 + *------------------------------------------ + */ +int buildin_guildskill(struct script_state *st) +{ + int id,level; + struct map_session_data *sd; + int i=0; + + id=conv_num(st,& (st->stack->stack_data[st->start+2])); + level=conv_num(st,& (st->stack->stack_data[st->start+3])); +// if( st->end>st->start+4 ) +// flag=conv_num(st,&(st->stack->stack_data[st->start+4]) ); + sd=script_rid2sd(st); + for(i=0;i<level;i++) + guild_skillup(sd,id); + + return 0; +} +/*========================================== + * スキルレベル所得 + *------------------------------------------ + */ +int buildin_getskilllv(struct script_state *st) +{ + int id=conv_num(st,& (st->stack->stack_data[st->start+2])); + push_val(st->stack,C_INT, pc_checkskill( script_rid2sd(st) ,id) ); + return 0; +} +/*========================================== + * getgdskilllv(Guild_ID, Skill_ID); + * skill_id = 10000 : GD_APPROVAL + * 10001 : GD_KAFRACONTACT + * 10002 : GD_GUARDIANRESEARCH + * 10003 : GD_CHARISMA + * 10004 : GD_EXTENSION + *------------------------------------------ + */ +int buildin_getgdskilllv(struct script_state *st) +{ + int guild_id=conv_num(st,& (st->stack->stack_data[st->start+2])); + int skill_id=conv_num(st,& (st->stack->stack_data[st->start+3])); + struct guild *g=guild_search(guild_id); + push_val(st->stack,C_INT, (g==NULL)?-1:guild_checkskill(g,skill_id) ); + return 0; +/* + struct map_session_data *sd=NULL; + struct guild *g=NULL; + int skill_id; + + skill_id=conv_num(st,& (st->stack->stack_data[st->start+2])); + sd=script_rid2sd(st); + if(sd && sd->status.guild_id > 0) g=guild_search(sd->status.guild_id); + if(sd && g) { + push_val(st->stack,C_INT, guild_checkskill(g,skill_id+9999) ); + } else { + push_val(st->stack,C_INT,-1); + } + return 0; +*/ +} +/*========================================== + * + *------------------------------------------ + */ +int buildin_basicskillcheck(struct script_state *st) +{ + push_val(st->stack,C_INT, battle_config.basic_skill_check); + return 0; +} +/*========================================== + * + *------------------------------------------ + */ +int buildin_getgmlevel(struct script_state *st) +{ + push_val(st->stack,C_INT, pc_isGM(script_rid2sd(st))); + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int buildin_end(struct script_state *st) +{ + st->state = END; + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int buildin_checkoption(struct script_state *st) +{ + int type; + struct map_session_data *sd; + + type=conv_num(st,& (st->stack->stack_data[st->start+2])); + sd=script_rid2sd(st); + + if(sd->status.option & type){ + push_val(st->stack,C_INT,1); + } else { + push_val(st->stack,C_INT,0); + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int buildin_setoption(struct script_state *st) +{ + int type; + struct map_session_data *sd; + + type=conv_num(st,& (st->stack->stack_data[st->start+2])); + sd=script_rid2sd(st); + pc_setoption(sd,type); + + return 0; +} + +/*========================================== + * Checkcart [Valaris] + *------------------------------------------ + */ + +int buildin_checkcart(struct script_state *st) +{ + struct map_session_data *sd; + + sd=script_rid2sd(st); + + if(pc_iscarton(sd)){ + push_val(st->stack,C_INT,1); + } else { + push_val(st->stack,C_INT,0); + } + return 0; +} + +/*========================================== + * カートを付ける + *------------------------------------------ + */ +int buildin_setcart(struct script_state *st) +{ + struct map_session_data *sd; + + sd=script_rid2sd(st); + pc_setcart(sd,1); + + return 0; +} + +/*========================================== + * checkfalcon [Valaris] + *------------------------------------------ + */ + +int buildin_checkfalcon(struct script_state *st) +{ + struct map_session_data *sd; + + sd=script_rid2sd(st); + + if(pc_isfalcon(sd)){ + push_val(st->stack,C_INT,1); + } else { + push_val(st->stack,C_INT,0); + } + + return 0; +} + + +/*========================================== + * 鷹を付ける + *------------------------------------------ + */ +int buildin_setfalcon(struct script_state *st) +{ + struct map_session_data *sd; + + sd=script_rid2sd(st); + pc_setfalcon(sd); + + return 0; +} + +/*========================================== + * Checkcart [Valaris] + *------------------------------------------ + */ + +int buildin_checkriding(struct script_state *st) +{ + struct map_session_data *sd; + + sd=script_rid2sd(st); + + if(pc_isriding(sd)){ + push_val(st->stack,C_INT,1); + } else { + push_val(st->stack,C_INT,0); + } + + return 0; +} + + +/*========================================== + * ペコペコ乗り + *------------------------------------------ + */ +int buildin_setriding(struct script_state *st) +{ + struct map_session_data *sd; + + sd=script_rid2sd(st); + pc_setriding(sd); + + return 0; +} + +/*========================================== + * セーブポイントの保存 + *------------------------------------------ + */ +int buildin_savepoint(struct script_state *st) +{ + int x,y; + char *str; + + str=conv_str(st,& (st->stack->stack_data[st->start+2])); + x=conv_num(st,& (st->stack->stack_data[st->start+3])); + y=conv_num(st,& (st->stack->stack_data[st->start+4])); + pc_setsavepoint(script_rid2sd(st),str,x,y); + return 0; +} + +/*========================================== + * GetTimeTick(0: System Tick, 1: Time Second Tick) + *------------------------------------------ + */ +int buildin_gettimetick(struct script_state *st) /* Asgard Version */ +{ + int type; + time_t timer; + struct tm *t; + + type=conv_num(st,& (st->stack->stack_data[st->start+2])); + + switch(type){ + case 1: + //type 1:(Second Ticks: 0-86399, 00:00:00-23:59:59) + time(&timer); + t=localtime(&timer); + push_val(st->stack,C_INT,((t->tm_hour)*3600+(t->tm_min)*60+t->tm_sec)); + break; + case 0: + default: + //type 0:(System Ticks) + push_val(st->stack,C_INT,gettick()); + break; + } + return 0; +} + +/*========================================== + * GetTime(Type); + * 1: Sec 2: Min 3: Hour + * 4: WeekDay 5: MonthDay 6: Month + * 7: Year + *------------------------------------------ + */ +int buildin_gettime(struct script_state *st) /* Asgard Version */ +{ + int type; + time_t timer; + struct tm *t; + + type=conv_num(st,& (st->stack->stack_data[st->start+2])); + + time(&timer); + t=localtime(&timer); + + switch(type){ + case 1://Sec(0~59) + push_val(st->stack,C_INT,t->tm_sec); + break; + case 2://Min(0~59) + push_val(st->stack,C_INT,t->tm_min); + break; + case 3://Hour(0~23) + push_val(st->stack,C_INT,t->tm_hour); + break; + case 4://WeekDay(0~6) + push_val(st->stack,C_INT,t->tm_wday); + break; + case 5://MonthDay(01~31) + push_val(st->stack,C_INT,t->tm_mday); + break; + case 6://Month(01~12) + push_val(st->stack,C_INT,t->tm_mon+1); + break; + case 7://Year(20xx) + push_val(st->stack,C_INT,t->tm_year+1900); + break; + default://(format error) + push_val(st->stack,C_INT,-1); + break; + } + return 0; +} + +/*========================================== + * GetTimeStr("TimeFMT", Length); + *------------------------------------------ + */ +int buildin_gettimestr(struct script_state *st) +{ + char *tmpstr; + char *fmtstr; + int maxlen; + time_t now = time(NULL); + + fmtstr=conv_str(st,& (st->stack->stack_data[st->start+2])); + maxlen=conv_num(st,& (st->stack->stack_data[st->start+3])); + + tmpstr=(char *)aCalloc(maxlen+1,sizeof(char)); + strftime(tmpstr,maxlen,fmtstr,localtime(&now)); + tmpstr[maxlen]='\0'; + + push_str(st->stack,C_STR,tmpstr); + return 0; +} + +/*========================================== + * カプラ倉庫を開く + *------------------------------------------ + */ +int buildin_openstorage(struct script_state *st) +{ + storage_storageopen(script_rid2sd(st)); + return 0; +} + +int buildin_guildopenstorage(struct script_state *st) +{ + struct map_session_data *sd=script_rid2sd(st); + int ret; + ret = storage_guild_storageopen(sd); + push_val(st->stack,C_INT,ret); + return 0; +} + +/*========================================== + * アイテムによるスキル発動 + *------------------------------------------ + */ +int buildin_itemskill(struct script_state *st) +{ + int id,lv; + char *str; + struct map_session_data *sd=script_rid2sd(st); + + id=conv_num(st,& (st->stack->stack_data[st->start+2])); + lv=conv_num(st,& (st->stack->stack_data[st->start+3])); + str=conv_str(st,& (st->stack->stack_data[st->start+4])); + + // 詠唱中にスキルアイテムは使用できない + if(sd->skilltimer != -1) + return 0; + + sd->skillitem=id; + sd->skillitemlv=lv; + clif_item_skill(sd,id,lv,str); + return 0; +} +/*========================================== + * アイテム作成 + *------------------------------------------ + */ +int buildin_produce(struct script_state *st) +{ + int trigger; + struct map_session_data *sd=script_rid2sd(st); + + if( sd->state.produce_flag == 1) return 0; + trigger=conv_num(st,& (st->stack->stack_data[st->start+2])); + clif_skill_produce_mix_list(sd,trigger); + return 0; +} +/*========================================== + * NPCでペット作る + *------------------------------------------ + */ +int buildin_makepet(struct script_state *st) +{ + struct map_session_data *sd = script_rid2sd(st); + struct script_data *data; + int id,pet_id; + + data=&(st->stack->stack_data[st->start+2]); + get_val(st,data); + + id=conv_num(st,data); + + pet_id = search_petDB_index(id, PET_CLASS); + + if (pet_id < 0) + pet_id = search_petDB_index(id, PET_EGG); + if (pet_id >= 0 && sd) { + sd->catch_target_class = pet_db[pet_id].class; + intif_create_pet( + sd->status.account_id, sd->status.char_id, + pet_db[pet_id].class, mob_db[pet_db[pet_id].class].lv, + pet_db[pet_id].EggID, 0, pet_db[pet_id].intimate, + 100, 0, 1, pet_db[pet_id].jname); + } + + return 0; +} +/*========================================== + * NPCで経験値上げる + *------------------------------------------ + */ +int buildin_getexp(struct script_state *st) +{ + struct map_session_data *sd = script_rid2sd(st); + int base=0,job=0; + + base=conv_num(st,& (st->stack->stack_data[st->start+2])); + job =conv_num(st,& (st->stack->stack_data[st->start+3])); + if(base<0 || job<0) + return 0; + if(sd) + pc_gainexp(sd,base,job); + + return 0; +} + +/*========================================== + * モンスター発生 + *------------------------------------------ + */ +int buildin_monster(struct script_state *st) +{ + int class,amount,x,y; + char *str,*map,*event=""; + + map =conv_str(st,& (st->stack->stack_data[st->start+2])); + x =conv_num(st,& (st->stack->stack_data[st->start+3])); + y =conv_num(st,& (st->stack->stack_data[st->start+4])); + str =conv_str(st,& (st->stack->stack_data[st->start+5])); + class=conv_num(st,& (st->stack->stack_data[st->start+6])); + amount=conv_num(st,& (st->stack->stack_data[st->start+7])); + if( st->end>st->start+8 ) + event=conv_str(st,& (st->stack->stack_data[st->start+8])); + + mob_once_spawn(map_id2sd(st->rid),map,x,y,str,class,amount,event); + return 0; +} +/*========================================== + * モンスター発生 + *------------------------------------------ + */ +int buildin_areamonster(struct script_state *st) +{ + int class,amount,x0,y0,x1,y1; + char *str,*map,*event=""; + + map =conv_str(st,& (st->stack->stack_data[st->start+2])); + x0 =conv_num(st,& (st->stack->stack_data[st->start+3])); + y0 =conv_num(st,& (st->stack->stack_data[st->start+4])); + x1 =conv_num(st,& (st->stack->stack_data[st->start+5])); + y1 =conv_num(st,& (st->stack->stack_data[st->start+6])); + str =conv_str(st,& (st->stack->stack_data[st->start+7])); + class=conv_num(st,& (st->stack->stack_data[st->start+8])); + amount=conv_num(st,& (st->stack->stack_data[st->start+9])); + if( st->end>st->start+10 ) + event=conv_str(st,& (st->stack->stack_data[st->start+10])); + + mob_once_spawn_area(map_id2sd(st->rid),map,x0,y0,x1,y1,str,class,amount,event); + return 0; +} +/*========================================== + * モンスター削除 + *------------------------------------------ + */ +int buildin_killmonster_sub(struct block_list *bl,va_list ap) +{ + char *event=va_arg(ap,char *); + int allflag=va_arg(ap,int); + + if(!allflag){ + if(strcmp(event,((struct mob_data *)bl)->npc_event)==0) + mob_delete((struct mob_data *)bl); + return 0; + }else if(allflag){ + if(((struct mob_data *)bl)->spawndelay1==-1 && ((struct mob_data *)bl)->spawndelay2==-1) + mob_delete((struct mob_data *)bl); + return 0; + } + return 0; +} +int buildin_killmonster(struct script_state *st) +{ + char *mapname,*event; + int m,allflag=0; + mapname=conv_str(st,& (st->stack->stack_data[st->start+2])); + event=conv_str(st,& (st->stack->stack_data[st->start+3])); + if(strcmp(event,"All")==0) + allflag = 1; + + if( (m=map_mapname2mapid(mapname))<0 ) + return 0; + map_foreachinarea(buildin_killmonster_sub, + m,0,0,map[m].xs,map[m].ys,BL_MOB, event ,allflag); + return 0; +} + +int buildin_killmonsterall_sub(struct block_list *bl,va_list ap) +{ + mob_delete((struct mob_data *)bl); + return 0; +} +int buildin_killmonsterall(struct script_state *st) +{ + char *mapname; + int m; + mapname=conv_str(st,& (st->stack->stack_data[st->start+2])); + + if( (m=map_mapname2mapid(mapname))<0 ) + return 0; + map_foreachinarea(buildin_killmonsterall_sub, + m,0,0,map[m].xs,map[m].ys,BL_MOB); + return 0; +} + +/*========================================== + * イベント実行 + *------------------------------------------ + */ +int buildin_doevent(struct script_state *st) +{ + char *event; + event=conv_str(st,& (st->stack->stack_data[st->start+2])); + npc_event(map_id2sd(st->rid),event,0); + return 0; +} +/*========================================== + * NPC主体イベント実行 + *------------------------------------------ + */ +int buildin_donpcevent(struct script_state *st) +{ + char *event; + event=conv_str(st,& (st->stack->stack_data[st->start+2])); + npc_event_do(event); + return 0; +} +/*========================================== + * イベントタイマー追加 + *------------------------------------------ + */ +int buildin_addtimer(struct script_state *st) +{ + char *event; + int tick; + tick=conv_num(st,& (st->stack->stack_data[st->start+2])); + event=conv_str(st,& (st->stack->stack_data[st->start+3])); + pc_addeventtimer(script_rid2sd(st),tick,event); + return 0; +} +/*========================================== + * イベントタイマー削除 + *------------------------------------------ + */ +int buildin_deltimer(struct script_state *st) +{ + char *event; + event=conv_str(st,& (st->stack->stack_data[st->start+2])); + pc_deleventtimer(script_rid2sd(st),event); + return 0; +} +/*========================================== + * イベントタイマーのカウント値追加 + *------------------------------------------ + */ +int buildin_addtimercount(struct script_state *st) +{ + char *event; + int tick; + event=conv_str(st,& (st->stack->stack_data[st->start+2])); + tick=conv_num(st,& (st->stack->stack_data[st->start+3])); + pc_addeventtimercount(script_rid2sd(st),event,tick); + return 0; +} + +/*========================================== + * NPCタイマー初期化 + *------------------------------------------ + */ +int buildin_initnpctimer(struct script_state *st) +{ + struct npc_data *nd; + if( st->end > st->start+2 ) + nd=npc_name2id(conv_str(st,& (st->stack->stack_data[st->start+2]))); + else + nd=(struct npc_data *)map_id2bl(st->oid); + + npc_settimerevent_tick(nd,0); + npc_timerevent_start(nd); + return 0; +} +/*========================================== + * NPCタイマー開始 + *------------------------------------------ + */ +int buildin_startnpctimer(struct script_state *st) +{ + struct npc_data *nd; + if( st->end > st->start+2 ) + nd=npc_name2id(conv_str(st,& (st->stack->stack_data[st->start+2]))); + else + nd=(struct npc_data *)map_id2bl(st->oid); + + npc_timerevent_start(nd); + return 0; +} +/*========================================== + * NPCタイマー停止 + *------------------------------------------ + */ +int buildin_stopnpctimer(struct script_state *st) +{ + struct npc_data *nd; + if( st->end > st->start+2 ) + nd=npc_name2id(conv_str(st,& (st->stack->stack_data[st->start+2]))); + else + nd=(struct npc_data *)map_id2bl(st->oid); + + npc_timerevent_stop(nd); + return 0; +} +/*========================================== + * NPCタイマー情報所得 + *------------------------------------------ + */ +int buildin_getnpctimer(struct script_state *st) +{ + struct npc_data *nd; + int type=conv_num(st,& (st->stack->stack_data[st->start+2])); + int val=0; + if( st->end > st->start+3 ) + nd=npc_name2id(conv_str(st,& (st->stack->stack_data[st->start+3]))); + else + nd=(struct npc_data *)map_id2bl(st->oid); + + switch(type){ + case 0: val=npc_gettimerevent_tick(nd); break; + case 1: val= (nd->u.scr.nexttimer>=0); break; + case 2: val= nd->u.scr.timeramount; break; + } + push_val(st->stack,C_INT,val); + return 0; +} +/*========================================== + * NPCタイマー値設定 + *------------------------------------------ + */ +int buildin_setnpctimer(struct script_state *st) +{ + int tick; + struct npc_data *nd; + tick=conv_num(st,& (st->stack->stack_data[st->start+2])); + if( st->end > st->start+3 ) + nd=npc_name2id(conv_str(st,& (st->stack->stack_data[st->start+3]))); + else + nd=(struct npc_data *)map_id2bl(st->oid); + + npc_settimerevent_tick(nd,tick); + return 0; +} + +/*========================================== + * 天の声アナウンス + *------------------------------------------ + */ +int buildin_announce(struct script_state *st) +{ + char *str; + int flag; + str=conv_str(st,& (st->stack->stack_data[st->start+2])); + flag=conv_num(st,& (st->stack->stack_data[st->start+3])); + + if(flag&0x0f){ + struct block_list *bl=(flag&0x08)? map_id2bl(st->oid) : + (struct block_list *)script_rid2sd(st); + clif_GMmessage(bl,str,strlen(str)+1,flag); + }else + intif_GMmessage(str,strlen(str)+1,flag); + return 0; +} +/*========================================== + * 天の声アナウンス(特定マップ) + *------------------------------------------ + */ +int buildin_mapannounce_sub(struct block_list *bl,va_list ap) +{ + char *str; + int len,flag; + str=va_arg(ap,char *); + len=va_arg(ap,int); + flag=va_arg(ap,int); + clif_GMmessage(bl,str,len,flag|3); + return 0; +} +int buildin_mapannounce(struct script_state *st) +{ + char *mapname,*str; + int flag,m; + + mapname=conv_str(st,& (st->stack->stack_data[st->start+2])); + str=conv_str(st,& (st->stack->stack_data[st->start+3])); + flag=conv_num(st,& (st->stack->stack_data[st->start+4])); + + if( (m=map_mapname2mapid(mapname))<0 ) + return 0; + map_foreachinarea(buildin_mapannounce_sub, + m,0,0,map[m].xs,map[m].ys,BL_PC, str,strlen(str)+1,flag&0x10); + return 0; +} +/*========================================== + * 天の声アナウンス(特定エリア) + *------------------------------------------ + */ +int buildin_areaannounce(struct script_state *st) +{ + char *map,*str; + int flag,m; + int x0,y0,x1,y1; + + map=conv_str(st,& (st->stack->stack_data[st->start+2])); + x0=conv_num(st,& (st->stack->stack_data[st->start+3])); + y0=conv_num(st,& (st->stack->stack_data[st->start+4])); + x1=conv_num(st,& (st->stack->stack_data[st->start+5])); + y1=conv_num(st,& (st->stack->stack_data[st->start+6])); + str=conv_str(st,& (st->stack->stack_data[st->start+7])); + flag=conv_num(st,& (st->stack->stack_data[st->start+8])); + + if( (m=map_mapname2mapid(map))<0 ) + return 0; + + map_foreachinarea(buildin_mapannounce_sub, + m,x0,y0,x1,y1,BL_PC, str,strlen(str)+1,flag&0x10 ); + return 0; +} +/*========================================== + * ユーザー数所得 + *------------------------------------------ + */ +int buildin_getusers(struct script_state *st) +{ + int flag=conv_num(st,& (st->stack->stack_data[st->start+2])); + struct block_list *bl=map_id2bl((flag&0x08)?st->oid:st->rid); + int val=0; + switch(flag&0x07){ + case 0: val=map[bl->m].users; break; + case 1: val=map_getusers(); break; + } + push_val(st->stack,C_INT,val); + return 0; +} +/*========================================== + * マップ指定ユーザー数所得 + *------------------------------------------ + */ +int buildin_getmapusers(struct script_state *st) +{ + char *str; + int m; + str=conv_str(st,& (st->stack->stack_data[st->start+2])); + if( (m=map_mapname2mapid(str))< 0){ + push_val(st->stack,C_INT,-1); + return 0; + } + push_val(st->stack,C_INT,map[m].users); + return 0; +} +/*========================================== + * エリア指定ユーザー数所得 + *------------------------------------------ + */ +int buildin_getareausers_sub(struct block_list *bl,va_list ap) +{ + int *users=va_arg(ap,int *); + (*users)++; + return 0; +} +int buildin_getareausers(struct script_state *st) +{ + char *str; + int m,x0,y0,x1,y1,users=0; + str=conv_str(st,& (st->stack->stack_data[st->start+2])); + x0=conv_num(st,& (st->stack->stack_data[st->start+3])); + y0=conv_num(st,& (st->stack->stack_data[st->start+4])); + x1=conv_num(st,& (st->stack->stack_data[st->start+5])); + y1=conv_num(st,& (st->stack->stack_data[st->start+6])); + if( (m=map_mapname2mapid(str))< 0){ + push_val(st->stack,C_INT,-1); + return 0; + } + map_foreachinarea(buildin_getareausers_sub, + m,x0,y0,x1,y1,BL_PC,&users); + push_val(st->stack,C_INT,users); + return 0; +} + +/*========================================== + * エリア指定ドロップアイテム数所得 + *------------------------------------------ + */ +int buildin_getareadropitem_sub(struct block_list *bl,va_list ap) +{ + int item=va_arg(ap,int); + int *amount=va_arg(ap,int *); + struct flooritem_data *drop=(struct flooritem_data *)bl; + + if(drop->item_data.nameid==item) + (*amount)+=drop->item_data.amount; + + return 0; +} +int buildin_getareadropitem(struct script_state *st) +{ + char *str; + int m,x0,y0,x1,y1,item,amount=0; + struct script_data *data; + + str=conv_str(st,& (st->stack->stack_data[st->start+2])); + x0=conv_num(st,& (st->stack->stack_data[st->start+3])); + y0=conv_num(st,& (st->stack->stack_data[st->start+4])); + x1=conv_num(st,& (st->stack->stack_data[st->start+5])); + y1=conv_num(st,& (st->stack->stack_data[st->start+6])); + + data=&(st->stack->stack_data[st->start+7]); + get_val(st,data); + if( data->type==C_STR || data->type==C_CONSTSTR ){ + const char *name=conv_str(st,data); + struct item_data *item_data = itemdb_searchname(name); + item=512; + if( item_data ) + item=item_data->nameid; + }else + item=conv_num(st,data); + + if( (m=map_mapname2mapid(str))< 0){ + push_val(st->stack,C_INT,-1); + return 0; + } + map_foreachinarea(buildin_getareadropitem_sub, + m,x0,y0,x1,y1,BL_ITEM,item,&amount); + push_val(st->stack,C_INT,amount); + return 0; +} +/*========================================== + * NPCの有効化 + *------------------------------------------ + */ +int buildin_enablenpc(struct script_state *st) +{ + char *str; + str=conv_str(st,& (st->stack->stack_data[st->start+2])); + npc_enable(str,1); + return 0; +} +/*========================================== + * NPCの無効化 + *------------------------------------------ + */ +int buildin_disablenpc(struct script_state *st) +{ + char *str; + str=conv_str(st,& (st->stack->stack_data[st->start+2])); + npc_enable(str,0); + return 0; +} + +int buildin_enablearena(struct script_state *st) // Added by RoVeRT +{ + struct npc_data *nd=(struct npc_data *)map_id2bl(st->oid); + struct chat_data *cd; + + + if(nd==NULL || (cd=(struct chat_data *)map_id2bl(nd->chat_id))==NULL) + return 0; + + npc_enable(nd->name,1); + nd->arenaflag=1; + + if(cd->users>=cd->trigger && cd->npc_event[0]) + npc_timer_event(cd->npc_event); + + return 0; +} +int buildin_disablearena(struct script_state *st) // Added by RoVeRT +{ + struct npc_data *nd=(struct npc_data *)map_id2bl(st->oid); + nd->arenaflag=0; + + return 0; +} +/*========================================== + * 隠れているNPCの表示 + *------------------------------------------ + */ +int buildin_hideoffnpc(struct script_state *st) +{ + char *str; + str=conv_str(st,& (st->stack->stack_data[st->start+2])); + npc_enable(str,2); + return 0; +} +/*========================================== + * NPCをハイディング + *------------------------------------------ + */ +int buildin_hideonnpc(struct script_state *st) +{ + char *str; + str=conv_str(st,& (st->stack->stack_data[st->start+2])); + npc_enable(str,4); + return 0; +} +/*========================================== + * 状態異常にかかる + *------------------------------------------ + */ +int buildin_sc_start(struct script_state *st) +{ + struct block_list *bl; + int type,tick,val1; + type=conv_num(st,& (st->stack->stack_data[st->start+2])); + tick=conv_num(st,& (st->stack->stack_data[st->start+3])); + val1=conv_num(st,& (st->stack->stack_data[st->start+4])); + if( st->end>st->start+5 ) //指定したキャラを状態異常にする + bl = map_id2bl(conv_num(st,& (st->stack->stack_data[st->start+5]))); + else + bl = map_id2bl(st->rid); + if(bl->type == BL_PC && ((struct map_session_data *)bl)->state.potionpitcher_flag) + bl = map_id2bl(((struct map_session_data *)bl)->skilltarget); + skill_status_change_start(bl,type,val1,0,0,0,tick,0); + return 0; +} + +/*========================================== + * 状態異常にかかる(確率指定) + *------------------------------------------ + */ +int buildin_sc_start2(struct script_state *st) +{ + struct block_list *bl; + int type,tick,val1,per; + type=conv_num(st,& (st->stack->stack_data[st->start+2])); + tick=conv_num(st,& (st->stack->stack_data[st->start+3])); + val1=conv_num(st,& (st->stack->stack_data[st->start+4])); + per=conv_num(st,& (st->stack->stack_data[st->start+5])); + if( st->end>st->start+6 ) //指定したキャラを状態異常にする + bl = map_id2bl(conv_num(st,& (st->stack->stack_data[st->start+6]))); + else + bl = map_id2bl(st->rid); + if(bl->type == BL_PC && ((struct map_session_data *)bl)->state.potionpitcher_flag) + bl = map_id2bl(((struct map_session_data *)bl)->skilltarget); + if(rand()%10000 < per) + skill_status_change_start(bl,type,val1,0,0,0,tick,0); + return 0; +} + +/*========================================== + * 状態異常が直る + *------------------------------------------ + */ +int buildin_sc_end(struct script_state *st) +{ + struct block_list *bl; + int type; + type=conv_num(st,& (st->stack->stack_data[st->start+2])); + bl = map_id2bl(st->rid); + if(bl->type == BL_PC && ((struct map_session_data *)bl)->state.potionpitcher_flag) + bl = map_id2bl(((struct map_session_data *)bl)->skilltarget); + skill_status_change_end(bl,type,-1); +// if(battle_config.etc_log) +// printf("sc_end : %d %d\n",st->rid,type); + return 0; +} +/*========================================== + * 状態異常耐性を計算した確率を返す + *------------------------------------------ + */ +int buildin_getscrate(struct script_state *st) +{ + struct block_list *bl; + int sc_def=100,sc_def_mdef2,sc_def_vit2,sc_def_int2,sc_def_luk2; + int type,rate,luk; + + type=conv_num(st,& (st->stack->stack_data[st->start+2])); + rate=conv_num(st,& (st->stack->stack_data[st->start+3])); + if( st->end>st->start+4 ) //指定したキャラの耐性を計算する + bl = map_id2bl(conv_num(st,& (st->stack->stack_data[st->start+6]))); + else + bl = map_id2bl(st->rid); + + luk = battle_get_luk(bl); + sc_def_mdef2=100 - (3 + battle_get_mdef(bl) + luk/3); + sc_def_vit2=100 - (3 + battle_get_vit(bl) + luk/3); + sc_def_int2=100 - (3 + battle_get_int(bl) + luk/3); + sc_def_luk2=100 - (3 + luk); + + if(type==SC_STONE || type==SC_FREEZE) + sc_def=sc_def_mdef2; + else if(type==SC_STAN || type==SC_POISON || type==SC_SILENCE) + sc_def=sc_def_vit2; + else if(type==SC_SLEEP || type==SC_CONFUSION || type==SC_BLIND) + sc_def=sc_def_int2; + else if(type==SC_CURSE) + sc_def=sc_def_luk2; + + rate=rate*sc_def/100; + push_val(st->stack,C_INT,rate); + + return 0; + +} + +/*========================================== + * + *------------------------------------------ + */ +int buildin_debugmes(struct script_state *st) +{ + conv_str(st,& (st->stack->stack_data[st->start+2])); + printf("script debug : %d %d : %s\n",st->rid,st->oid,st->stack->stack_data[st->start+2].u.str); + return 0; +} + +/*========================================== + *捕獲アイテム使用 + *------------------------------------------ + */ +int buildin_catchpet(struct script_state *st) +{ + int pet_id; + struct map_session_data *sd; + pet_id= conv_num(st,& (st->stack->stack_data[st->start+2])); + sd=script_rid2sd(st); + pet_catch_process1(sd,pet_id); + return 0; +} + +/*========================================== + *携帯卵孵化機使用 + *------------------------------------------ + */ +int buildin_birthpet(struct script_state *st) +{ + struct map_session_data *sd; + sd=script_rid2sd(st); + clif_sendegg(sd); + return 0; +} + +/*========================================== + * Added - AppleGirl For Advanced Classes, (Updated for Cleaner Script Purposes) + *------------------------------------------ + */ +int buildin_resetlvl(struct script_state *st) +{ + struct map_session_data *sd; + + int type=conv_num(st,& (st->stack->stack_data[st->start+2])); + + sd=script_rid2sd(st); + pc_resetlvl(sd,type); + return 0; +} +/*========================================== + * ステータスリセット + *------------------------------------------ + */ +int buildin_resetstatus(struct script_state *st) +{ + struct map_session_data *sd; + sd=script_rid2sd(st); + pc_resetstate(sd); + return 0; +} + +/*========================================== + * スキルリセット + *------------------------------------------ + */ +int buildin_resetskill(struct script_state *st) +{ + struct map_session_data *sd; + sd=script_rid2sd(st); + pc_resetskill(sd); + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int buildin_changebase(struct script_state *st) +{ + struct map_session_data *sd=NULL; + int vclass; + + if( st->end>st->start+3 ) + sd=map_id2sd(conv_num(st,& (st->stack->stack_data[st->start+3]))); + else + sd=script_rid2sd(st); + + if(sd == NULL) + return 0; + + vclass = conv_num(st,& (st->stack->stack_data[st->start+2])); + if(vclass == 22 && !battle_config.wedding_modifydisplay) + return 0; + +// if(vclass==22) { +// pc_unequipitem(sd,sd->equip_index[9],0); // 装備外 +// } + + sd->view_class = vclass; + + return 0; +} + +/*========================================== + * 性別変換 + *------------------------------------------ + */ +int buildin_changesex(struct script_state *st) { + struct map_session_data *sd = NULL; + sd = script_rid2sd(st); + + if (sd->status.sex == 0) { + sd->status.sex = 1; + sd->sex = 1; + if (sd->status.class == 20 || sd->status.class == 4021) + sd->status.class -= 1; + } else if (sd->status.sex == 1) { + sd->status.sex = 0; + sd->sex = 0; + if(sd->status.class == 19 || sd->status.class == 4020) + sd->status.class += 1; + } + chrif_char_ask_name(-1, sd->status.name, 5, 0, 0, 0, 0, 0, 0); // type: 5 - changesex + chrif_save(sd); + return 0; +} + +/*========================================== + * npcチャット作成 + *------------------------------------------ + */ +int buildin_waitingroom(struct script_state *st) +{ + char *name,*ev=""; + int limit, trigger = 0,pub=1; + name=conv_str(st,& (st->stack->stack_data[st->start+2])); + limit= conv_num(st,& (st->stack->stack_data[st->start+3])); + if(limit==0) + pub=3; + + if( (st->end > st->start+5) ){ + struct script_data* data=&(st->stack->stack_data[st->start+5]); + get_val(st,data); + if(data->type==C_INT){ + // 新Athena仕様(旧Athena仕様と互換性あり) + ev=conv_str(st,& (st->stack->stack_data[st->start+4])); + trigger=conv_num(st,& (st->stack->stack_data[st->start+5])); + }else{ + // eathena仕様 + trigger=conv_num(st,& (st->stack->stack_data[st->start+4])); + ev=conv_str(st,& (st->stack->stack_data[st->start+5])); + } + }else{ + // 旧Athena仕様 + if( st->end > st->start+4 ) + ev=conv_str(st,& (st->stack->stack_data[st->start+4])); + } + chat_createnpcchat( (struct npc_data *)map_id2bl(st->oid), + limit,pub,trigger,name,strlen(name)+1,ev); + return 0; +} +/*========================================== + * npcチャット削除 + *------------------------------------------ + */ +int buildin_delwaitingroom(struct script_state *st) +{ + struct npc_data *nd; + if( st->end > st->start+2 ) + nd=npc_name2id(conv_str(st,& (st->stack->stack_data[st->start+2]))); + else + nd=(struct npc_data *)map_id2bl(st->oid); + chat_deletenpcchat(nd); + return 0; +} +/*========================================== + * npcチャット全員蹴り出す + *------------------------------------------ + */ +int buildin_waitingroomkickall(struct script_state *st) +{ + struct npc_data *nd; + struct chat_data *cd; + + if( st->end > st->start+2 ) + nd=npc_name2id(conv_str(st,& (st->stack->stack_data[st->start+2]))); + else + nd=(struct npc_data *)map_id2bl(st->oid); + + if(nd==NULL || (cd=(struct chat_data *)map_id2bl(nd->chat_id))==NULL ) + return 0; + chat_npckickall(cd); + return 0; +} + +/*========================================== + * npcチャットイベント有効化 + *------------------------------------------ + */ +int buildin_enablewaitingroomevent(struct script_state *st) +{ + struct npc_data *nd; + struct chat_data *cd; + + if( st->end > st->start+2 ) + nd=npc_name2id(conv_str(st,& (st->stack->stack_data[st->start+2]))); + else + nd=(struct npc_data *)map_id2bl(st->oid); + + if(nd==NULL || (cd=(struct chat_data *)map_id2bl(nd->chat_id))==NULL ) + return 0; + chat_enableevent(cd); + return 0; +} + +/*========================================== + * npcチャットイベント無効化 + *------------------------------------------ + */ +int buildin_disablewaitingroomevent(struct script_state *st) +{ + struct npc_data *nd; + struct chat_data *cd; + + if( st->end > st->start+2 ) + nd=npc_name2id(conv_str(st,& (st->stack->stack_data[st->start+2]))); + else + nd=(struct npc_data *)map_id2bl(st->oid); + + if(nd==NULL || (cd=(struct chat_data *)map_id2bl(nd->chat_id))==NULL ) + return 0; + chat_disableevent(cd); + return 0; +} +/*========================================== + * npcチャット状態所得 + *------------------------------------------ + */ +int buildin_getwaitingroomstate(struct script_state *st) +{ + struct npc_data *nd; + struct chat_data *cd; + int val=0,type; + type=conv_num(st,& (st->stack->stack_data[st->start+2])); + if( st->end > st->start+3 ) + nd=npc_name2id(conv_str(st,& (st->stack->stack_data[st->start+3]))); + else + nd=(struct npc_data *)map_id2bl(st->oid); + + if(nd==NULL || (cd=(struct chat_data *)map_id2bl(nd->chat_id))==NULL ){ + push_val(st->stack,C_INT,-1); + return 0; + } + + switch(type){ + case 0: val=cd->users; break; + case 1: val=cd->limit; break; + case 2: val=cd->trigger&0x7f; break; + case 3: val=((cd->trigger&0x80)>0); break; + case 32: val=(cd->users >= cd->limit); break; + case 33: val=(cd->users >= cd->trigger); break; + + case 4: + push_str(st->stack,C_CONSTSTR,cd->title); + return 0; + case 5: + push_str(st->stack,C_CONSTSTR,cd->pass); + return 0; + case 16: + push_str(st->stack,C_CONSTSTR,cd->npc_event); + return 0; + } + push_val(st->stack,C_INT,val); + return 0; +} + +/*========================================== + * チャットメンバー(規定人数)ワープ + *------------------------------------------ + */ +int buildin_warpwaitingpc(struct script_state *st) +{ + int x,y,i,n; + char *str; + struct npc_data *nd=(struct npc_data *)map_id2bl(st->oid); + struct chat_data *cd; + + if(nd==NULL || (cd=(struct chat_data *)map_id2bl(nd->chat_id))==NULL ) + return 0; + + n=cd->trigger&0x7f; + str=conv_str(st,& (st->stack->stack_data[st->start+2])); + x=conv_num(st,& (st->stack->stack_data[st->start+3])); + y=conv_num(st,& (st->stack->stack_data[st->start+4])); + + if( st->end > st->start+5 ) + n=conv_num(st,& (st->stack->stack_data[st->start+5])); + + for(i=0;i<n;i++){ + struct map_session_data *sd=cd->usersd[0]; // リスト先頭のPCを次々に。 + + mapreg_setreg(add_str("$@warpwaitingpc")+(i<<24),sd->bl.id); + + if(strcmp(str,"Random")==0) + pc_randomwarp(sd,3); + else if(strcmp(str,"SavePoint")==0){ + if(map[sd->bl.m].flag.noteleport) // テレポ禁止 + return 0; + + pc_setpos(sd,sd->status.save_point.map, + sd->status.save_point.x,sd->status.save_point.y,3); + }else + pc_setpos(sd,str,x,y,0); + } + mapreg_setreg(add_str("$@warpwaitingpcnum"),n); + return 0; +} +/*========================================== + * RIDのアタッチ + *------------------------------------------ + */ +int buildin_attachrid(struct script_state *st) +{ + st->rid=conv_num(st,& (st->stack->stack_data[st->start+2])); + push_val(st->stack,C_INT, (map_id2sd(st->rid)!=NULL)); + return 0; +} +/*========================================== + * RIDのデタッチ + *------------------------------------------ + */ +int buildin_detachrid(struct script_state *st) +{ + st->rid=0; + return 0; +} +/*========================================== + * 存在チェック + *------------------------------------------ + */ +int buildin_isloggedin(struct script_state *st) +{ + push_val(st->stack,C_INT, map_id2sd( + conv_num(st,& (st->stack->stack_data[st->start+2])) )!=NULL ); + return 0; +} + + +/*========================================== + * + *------------------------------------------ + */ +enum { MF_NOMEMO,MF_NOTELEPORT,MF_NOSAVE,MF_NOBRANCH,MF_NOPENALTY,MF_NOZENYPENALTY,MF_PVP,MF_PVP_NOPARTY,MF_PVP_NOGUILD,MF_GVG,MF_GVG_NOPARTY,MF_NOTRADE,MF_NOSKILL, MF_NOWARP,MF_NOPVP,MF_NOICEWALL, + MF_SNOW, MF_FOG, MF_SAKURA, MF_LEAVES, MF_RAIN }; + +int buildin_setmapflagnosave(struct script_state *st) +{ + int m,x,y; + char *str,*str2; + + str=conv_str(st,& (st->stack->stack_data[st->start+2])); + str2=conv_str(st,& (st->stack->stack_data[st->start+3])); + x=conv_num(st,& (st->stack->stack_data[st->start+4])); + y=conv_num(st,& (st->stack->stack_data[st->start+5])); + m = map_mapname2mapid(str); + if(m >= 0) { + map[m].flag.nosave=1; + memcpy(map[m].save.map,str2,16); + map[m].save.x=x; + map[m].save.y=y; + } + + return 0; +} + +int buildin_setmapflag(struct script_state *st) +{ + int m,i; + char *str; + + str=conv_str(st,& (st->stack->stack_data[st->start+2])); + i=conv_num(st,& (st->stack->stack_data[st->start+3])); + m = map_mapname2mapid(str); + if(m >= 0) { + switch(i) { + case MF_NOMEMO: + map[m].flag.nomemo=1; + break; + case MF_NOTELEPORT: + map[m].flag.noteleport=1; + break; + case MF_NOBRANCH: + map[m].flag.nobranch=1; + break; + case MF_NOPENALTY: + map[m].flag.nopenalty=1; + break; + case MF_PVP_NOPARTY: + map[m].flag.pvp_noparty=1; + break; + case MF_PVP_NOGUILD: + map[m].flag.pvp_noguild=1; + break; + case MF_GVG_NOPARTY: + map[m].flag.gvg_noparty=1; + break; + case MF_NOZENYPENALTY: + map[m].flag.nozenypenalty=1; + break; + case MF_NOTRADE: + map[m].flag.notrade=1; + break; + case MF_NOSKILL: + map[m].flag.noskill=1; + break; + case MF_NOWARP: + map[m].flag.nowarp=1; + break; + case MF_NOPVP: + map[m].flag.nopvp=1; + break; + case MF_NOICEWALL: // [Valaris] + map[m].flag.noicewall=1; + break; + case MF_SNOW: // [Valaris] + map[m].flag.snow=1; + break; + case MF_FOG: // [Valaris] + map[m].flag.fog=1; + break; + case MF_SAKURA: // [Valaris] + map[m].flag.sakura=1; + break; + case MF_LEAVES: // [Valaris] + map[m].flag.leaves=1; + break; + case MF_RAIN: // [Valaris] + map[m].flag.rain=1; + break; + } + } + + return 0; +} + +int buildin_removemapflag(struct script_state *st) +{ + int m,i; + char *str; + + str=conv_str(st,& (st->stack->stack_data[st->start+2])); + i=conv_num(st,& (st->stack->stack_data[st->start+3])); + m = map_mapname2mapid(str); + if(m >= 0) { + switch(i) { + case MF_NOMEMO: + map[m].flag.nomemo=0; + break; + case MF_NOTELEPORT: + map[m].flag.noteleport=0; + break; + case MF_NOSAVE: + map[m].flag.nosave=0; + break; + case MF_NOBRANCH: + map[m].flag.nobranch=0; + break; + case MF_NOPENALTY: + map[m].flag.nopenalty=0; + break; + case MF_PVP_NOPARTY: + map[m].flag.pvp_noparty=0; + break; + case MF_PVP_NOGUILD: + map[m].flag.pvp_noguild=0; + break; + case MF_GVG_NOPARTY: + map[m].flag.gvg_noparty=0; + break; + case MF_NOZENYPENALTY: + map[m].flag.nozenypenalty=0; + break; + case MF_NOSKILL: + map[m].flag.noskill=0; + break; + case MF_NOWARP: + map[m].flag.nowarp=0; + break; + case MF_NOPVP: + map[m].flag.nopvp=0; + break; + case MF_NOICEWALL: // [Valaris] + map[m].flag.noicewall=0; + break; + case MF_SNOW: // [Valaris] + map[m].flag.snow=0; + break; + case MF_FOG: // [Valaris] + map[m].flag.fog=0; + break; + case MF_SAKURA: // [Valaris] + map[m].flag.sakura=0; + break; + case MF_LEAVES: // [Valaris] + map[m].flag.leaves=0; + break; + case MF_RAIN: // [Valaris] + map[m].flag.rain=0; + break; + + } + } + + return 0; +} + +int buildin_pvpon(struct script_state *st) +{ + int m,i; + char *str; + struct map_session_data *pl_sd=NULL; + + str=conv_str(st,& (st->stack->stack_data[st->start+2])); + m = map_mapname2mapid(str); + if(m >= 0 && !map[m].flag.pvp && !map[m].flag.nopvp) { + map[m].flag.pvp = 1; + clif_send0199(m,1); + + if(battle_config.pk_mode) // disable ranking functions if pk_mode is on [Valaris] + return 0; + + for(i=0;i<fd_max;i++){ //人数分ループ + if(session[i] && (pl_sd=session[i]->session_data) && pl_sd->state.auth){ + if(m == pl_sd->bl.m && pl_sd->pvp_timer == -1) { + pl_sd->pvp_timer=add_timer(gettick()+200,pc_calc_pvprank_timer,pl_sd->bl.id,0); + pl_sd->pvp_rank=0; + pl_sd->pvp_lastusers=0; + pl_sd->pvp_point=5; + } + } + } + } + + return 0; +} + +int buildin_pvpoff(struct script_state *st) +{ + int m,i; + char *str; + struct map_session_data *pl_sd=NULL; + + str=conv_str(st,& (st->stack->stack_data[st->start+2])); + m = map_mapname2mapid(str); + if(m >= 0 && map[m].flag.pvp && map[m].flag.nopvp) { + map[m].flag.pvp = 0; + clif_send0199(m,0); + + if(battle_config.pk_mode) // disable ranking options if pk_mode is on [Valaris] + return 0; + + for(i=0;i<fd_max;i++){ //人数分ループ + if(session[i] && (pl_sd=session[i]->session_data) && pl_sd->state.auth){ + if(m == pl_sd->bl.m) { + clif_pvpset(pl_sd,0,0,2); + if(pl_sd->pvp_timer != -1) { + delete_timer(pl_sd->pvp_timer,pc_calc_pvprank_timer); + pl_sd->pvp_timer = -1; + } + } + } + } + } + + return 0; +} + +int buildin_gvgon(struct script_state *st) +{ + int m; + char *str; + + str=conv_str(st,& (st->stack->stack_data[st->start+2])); + m = map_mapname2mapid(str); + if(m >= 0 && !map[m].flag.gvg) { + map[m].flag.gvg = 1; + clif_send0199(m,3); + } + + return 0; +} +int buildin_gvgoff(struct script_state *st) +{ + int m; + char *str; + + str=conv_str(st,& (st->stack->stack_data[st->start+2])); + m = map_mapname2mapid(str); + if(m >= 0 && map[m].flag.gvg) { + map[m].flag.gvg = 0; + clif_send0199(m,0); + } + + return 0; +} +/*========================================== + * NPCエモーション + *------------------------------------------ + */ + +int buildin_emotion(struct script_state *st) +{ + int type; + type=conv_num(st,& (st->stack->stack_data[st->start+2])); + if(type < 0 || type > 100) + return 0; + clif_emotion(map_id2bl(st->oid),type); + return 0; +} + +int buildin_maprespawnguildid_sub(struct block_list *bl,va_list ap) +{ + int g_id=va_arg(ap,int); + int flag=va_arg(ap,int); + struct map_session_data *sd=NULL; + struct mob_data *md=NULL; + + if(bl->type == BL_PC) + sd=(struct map_session_data*)bl; + if(bl->type == BL_MOB) + md=(struct mob_data *)bl; + + if(sd){ + if((sd->status.guild_id == g_id) && (flag&1)) + pc_setpos(sd,sd->status.save_point.map,sd->status.save_point.x,sd->status.save_point.y,3); + else if((sd->status.guild_id != g_id) && (flag&2)) + pc_setpos(sd,sd->status.save_point.map,sd->status.save_point.x,sd->status.save_point.y,3); + else if (sd->status.guild_id == 0) // Warp out players not in guild [Valaris] + pc_setpos(sd,sd->status.save_point.map,sd->status.save_point.x,sd->status.save_point.y,3); // end addition [Valaris] + } + if(md && flag&4){ + if(md->class < 1285 || md->class > 1288) + mob_delete(md); + } + return 0; +} +int buildin_maprespawnguildid(struct script_state *st) +{ + char *mapname=conv_str(st,& (st->stack->stack_data[st->start+2])); + int g_id=conv_num(st,& (st->stack->stack_data[st->start+3])); + int flag=conv_num(st,& (st->stack->stack_data[st->start+4])); + + int m=map_mapname2mapid(mapname); + + if(m) map_foreachinarea(buildin_maprespawnguildid_sub,m,0,0,map[m].xs-1,map[m].ys-1,BL_NUL,g_id,flag); + return 0; +} + +int buildin_agitstart(struct script_state *st) +{ + if(agit_flag==1) return 1; // Agit already Start. + agit_flag=1; + guild_agit_start(); + return 0; +} + +int buildin_agitend(struct script_state *st) +{ + if(agit_flag==0) return 1; // Agit already End. + agit_flag=0; + guild_agit_end(); + return 0; +} +/*========================================== + * agitcheck 1; // choice script + * if(@agit_flag == 1) goto agit; + * if(agitcheck(0) == 1) goto agit; + *------------------------------------------ + */ +int buildin_agitcheck(struct script_state *st) +{ + struct map_session_data *sd; + int cond; + + sd=script_rid2sd(st); + cond=conv_num(st,& (st->stack->stack_data[st->start+2])); + + if(cond == 0) { + if (agit_flag==1) push_val(st->stack,C_INT,1); + if (agit_flag==0) push_val(st->stack,C_INT,0); + } else { + if (agit_flag==1) pc_setreg(sd,add_str("@agit_flag"),1); + if (agit_flag==0) pc_setreg(sd,add_str("@agit_flag"),0); + } + return 0; +} +int buildin_flagemblem(struct script_state *st) +{ + int g_id=conv_num(st,& (st->stack->stack_data[st->start+2])); + + if(g_id < 0) return 0; + +// printf("Script.c: [FlagEmblem] GuildID=%d, Emblem=%d.\n", g->guild_id, g->emblem_id); + ((struct npc_data *)map_id2bl(st->oid))->u.scr.guild_id = g_id; + return 1; +} + +int buildin_getcastlename(struct script_state *st) +{ + char *mapname=conv_str(st,& (st->stack->stack_data[st->start+2])); + struct guild_castle *gc; + int i; + char *buf=NULL; + for(i=0;i<MAX_GUILDCASTLE;i++){ + if( (gc=guild_castle_search(i)) != NULL ){ + if(strcmp(mapname,gc->map_name)==0){ + buf=(char *)aCalloc(24,sizeof(char)); + strncpy(buf,gc->castle_name,24); + break; + } + } + } + if(buf) + push_str(st->stack,C_STR,buf); + else + push_str(st->stack,C_CONSTSTR,""); + return 0; +} + +int buildin_getcastledata(struct script_state *st) +{ + char *mapname=conv_str(st,& (st->stack->stack_data[st->start+2])); + int index=conv_num(st,& (st->stack->stack_data[st->start+3])); + char *event=NULL; + struct guild_castle *gc; + int i,j; + + if( st->end>st->start+4 && index==0){ + for(i=0,j=-1;i<MAX_GUILDCASTLE;i++) + if( (gc=guild_castle_search(i)) != NULL && + strcmp(mapname,gc->map_name)==0 ) + j=i; + if(j>=0){ + event=conv_str(st,& (st->stack->stack_data[st->start+4])); + guild_addcastleinfoevent(j,17,event); + } + } + + for(i=0;i<MAX_GUILDCASTLE;i++){ + if( (gc=guild_castle_search(i)) != NULL ){ + if(strcmp(mapname,gc->map_name)==0){ + switch(index){ + case 0: for(j=1;j<26;j++) guild_castledataload(gc->castle_id,j); break; // Initialize[AgitInit] + case 1: push_val(st->stack,C_INT,gc->guild_id); break; + case 2: push_val(st->stack,C_INT,gc->economy); break; + case 3: push_val(st->stack,C_INT,gc->defense); break; + case 4: push_val(st->stack,C_INT,gc->triggerE); break; + case 5: push_val(st->stack,C_INT,gc->triggerD); break; + case 6: push_val(st->stack,C_INT,gc->nextTime); break; + case 7: push_val(st->stack,C_INT,gc->payTime); break; + case 8: push_val(st->stack,C_INT,gc->createTime); break; + case 9: push_val(st->stack,C_INT,gc->visibleC); break; + case 10: push_val(st->stack,C_INT,gc->visibleG0); break; + case 11: push_val(st->stack,C_INT,gc->visibleG1); break; + case 12: push_val(st->stack,C_INT,gc->visibleG2); break; + case 13: push_val(st->stack,C_INT,gc->visibleG3); break; + case 14: push_val(st->stack,C_INT,gc->visibleG4); break; + case 15: push_val(st->stack,C_INT,gc->visibleG5); break; + case 16: push_val(st->stack,C_INT,gc->visibleG6); break; + case 17: push_val(st->stack,C_INT,gc->visibleG7); break; + case 18: push_val(st->stack,C_INT,gc->Ghp0); break; + case 19: push_val(st->stack,C_INT,gc->Ghp1); break; + case 20: push_val(st->stack,C_INT,gc->Ghp2); break; + case 21: push_val(st->stack,C_INT,gc->Ghp3); break; + case 22: push_val(st->stack,C_INT,gc->Ghp4); break; + case 23: push_val(st->stack,C_INT,gc->Ghp5); break; + case 24: push_val(st->stack,C_INT,gc->Ghp6); break; + case 25: push_val(st->stack,C_INT,gc->Ghp7); break; + default: + push_val(st->stack,C_INT,0); break; + } + return 0; + } + } + } + push_val(st->stack,C_INT,0); + return 0; +} + +int buildin_setcastledata(struct script_state *st) +{ + char *mapname=conv_str(st,& (st->stack->stack_data[st->start+2])); + int index=conv_num(st,& (st->stack->stack_data[st->start+3])); + int value=conv_num(st,& (st->stack->stack_data[st->start+4])); + struct guild_castle *gc; + int i; + + for(i=0;i<MAX_GUILDCASTLE;i++){ + if( (gc=guild_castle_search(i)) != NULL ){ + if(strcmp(mapname,gc->map_name)==0){ + // Save Data byself First + switch(index){ + case 1: gc->guild_id = value; break; + case 2: gc->economy = value; break; + case 3: gc->defense = value; break; + case 4: gc->triggerE = value; break; + case 5: gc->triggerD = value; break; + case 6: gc->nextTime = value; break; + case 7: gc->payTime = value; break; + case 8: gc->createTime = value; break; + case 9: gc->visibleC = value; break; + case 10: gc->visibleG0 = value; break; + case 11: gc->visibleG1 = value; break; + case 12: gc->visibleG2 = value; break; + case 13: gc->visibleG3 = value; break; + case 14: gc->visibleG4 = value; break; + case 15: gc->visibleG5 = value; break; + case 16: gc->visibleG6 = value; break; + case 17: gc->visibleG7 = value; break; + case 18: gc->Ghp0 = value; break; + case 19: gc->Ghp1 = value; break; + case 20: gc->Ghp2 = value; break; + case 21: gc->Ghp3 = value; break; + case 22: gc->Ghp4 = value; break; + case 23: gc->Ghp5 = value; break; + case 24: gc->Ghp6 = value; break; + case 25: gc->Ghp7 = value; break; + default: return 0; + } + guild_castledatasave(gc->castle_id,index,value); + return 0; + } + } + } + return 0; +} + +/* ===================================================================== + * ギルド情報を要求する + * --------------------------------------------------------------------- + */ +int buildin_requestguildinfo(struct script_state *st) +{ + int guild_id=conv_num(st,& (st->stack->stack_data[st->start+2])); + char *event=NULL; + + if( st->end>st->start+3 ) + event=conv_str(st,& (st->stack->stack_data[st->start+3])); + + if(guild_id>0) + guild_npc_request_info(guild_id,event); + return 0; +} + +/* ===================================================================== + * カードの数を得る + * --------------------------------------------------------------------- + */ +int buildin_getequipcardcnt(struct script_state *st) +{ + int i,num; + struct map_session_data *sd; + int c=4; + + num=conv_num(st,& (st->stack->stack_data[st->start+2])); + sd=script_rid2sd(st); + i=pc_checkequip(sd,equip[num-1]); + if(sd->status.inventory[i].card[0] == 0x00ff){ // 製造武器はカードなし + push_val(st->stack,C_INT,0); + return 0; + } + do{ + if( (sd->status.inventory[i].card[c-1] > 4000) && + (sd->status.inventory[i].card[c-1] < 5000)){ + + push_val(st->stack,C_INT,(c)); + return 0; + } + }while(c--); + push_val(st->stack,C_INT,0); + return 0; +} + +/* ================================================================ + * カード取り外し成功 + * ---------------------------------------------------------------- + */ +int buildin_successremovecards(struct script_state *st) +{ + int i,num,cardflag=0,flag; + struct map_session_data *sd; + struct item item_tmp; + int c=4; + + num=conv_num(st,& (st->stack->stack_data[st->start+2])); + sd=script_rid2sd(st); + i=pc_checkequip(sd,equip[num-1]); + if(sd->status.inventory[i].card[0]==0x00ff){ // 製造武器は処理しない + return 0; + } + do{ + if( (sd->status.inventory[i].card[c-1] > 4000) && + (sd->status.inventory[i].card[c-1] < 5000)){ + + cardflag = 1; + item_tmp.id=0,item_tmp.nameid=sd->status.inventory[i].card[c-1]; + item_tmp.equip=0,item_tmp.identify=1,item_tmp.refine=0; + item_tmp.attribute=0; + item_tmp.card[0]=0,item_tmp.card[1]=0,item_tmp.card[2]=0,item_tmp.card[3]=0; + + if((flag=pc_additem(sd,&item_tmp,1))){ // 持てないならドロップ + clif_additem(sd,0,0,flag); + map_addflooritem(&item_tmp,1,sd->bl.m,sd->bl.x,sd->bl.y,NULL,NULL,NULL,0); + } + } + }while(c--); + + if(cardflag == 1){ // カードを取り除いたアイテム所得 + flag=0; + item_tmp.id=0,item_tmp.nameid=sd->status.inventory[i].nameid; + item_tmp.equip=0,item_tmp.identify=1,item_tmp.refine=sd->status.inventory[i].refine; + item_tmp.attribute=sd->status.inventory[i].attribute; + item_tmp.card[0]=0,item_tmp.card[1]=0,item_tmp.card[2]=0,item_tmp.card[3]=0; + pc_delitem(sd,i,1,0); + if((flag=pc_additem(sd,&item_tmp,1))){ // もてないならドロップ + clif_additem(sd,0,0,flag); + map_addflooritem(&item_tmp,1,sd->bl.m,sd->bl.x,sd->bl.y,NULL,NULL,NULL,0); + } + clif_misceffect(&sd->bl,3); + return 0; + } + return 0; +} + +/* ================================================================ + * カード取り外し失敗 slot,type + * type=0: 両方損失、1:カード損失、2:武具損失、3:損失無し + * ---------------------------------------------------------------- + */ +int buildin_failedremovecards(struct script_state *st) +{ + int i,num,cardflag=0,flag,typefail; + struct map_session_data *sd; + struct item item_tmp; + int c=4; + + num=conv_num(st,& (st->stack->stack_data[st->start+2])); + typefail=conv_num(st,& (st->stack->stack_data[st->start+3])); + sd=script_rid2sd(st); + i=pc_checkequip(sd,equip[num-1]); + if(sd->status.inventory[i].card[0]==0x00ff){ // 製造武器は処理しない + return 0; + } + do{ + if(( sd->status.inventory[i].card[c-1] > 4000) && + (sd->status.inventory[i].card[c-1] < 5000)){ + + cardflag = 1; + + if(typefail == 2){ // 武具のみ損失なら、カードは受け取らせる + item_tmp.id=0,item_tmp.nameid=sd->status.inventory[i].card[c-1]; + item_tmp.equip=0,item_tmp.identify=1,item_tmp.refine=0; + item_tmp.attribute=0; + item_tmp.card[0]=0,item_tmp.card[1]=0,item_tmp.card[2]=0,item_tmp.card[3]=0; + if((flag=pc_additem(sd,&item_tmp,1))){ + clif_additem(sd,0,0,flag); + map_addflooritem(&item_tmp,1,sd->bl.m,sd->bl.x,sd->bl.y,NULL,NULL,NULL,0); + } + } + } + }while(c--); + + if(cardflag == 1){ + + if(typefail == 0 || typefail == 2){ // 武具損失 + pc_delitem(sd,i,1,0); + clif_misceffect(&sd->bl,2); + return 0; + } + if(typefail == 1){ // カードのみ損失(武具を返す) + flag=0; + item_tmp.id=0,item_tmp.nameid=sd->status.inventory[i].nameid; + item_tmp.equip=0,item_tmp.identify=1,item_tmp.refine=sd->status.inventory[i].refine; + item_tmp.attribute=sd->status.inventory[i].attribute; + item_tmp.card[0]=0,item_tmp.card[1]=0,item_tmp.card[2]=0,item_tmp.card[3]=0; + pc_delitem(sd,i,1,0); + if((flag=pc_additem(sd,&item_tmp,1))){ + clif_additem(sd,0,0,flag); + map_addflooritem(&item_tmp,1,sd->bl.m,sd->bl.x,sd->bl.y,NULL,NULL,NULL,0); + } + } + clif_misceffect(&sd->bl,2); + return 0; + } + return 0; +} + +int buildin_mapwarp(struct script_state *st) // Added by RoVeRT +{ + int x,y,m; + char *str; + char *mapname; + int x0,y0,x1,y1; + + mapname=conv_str(st,& (st->stack->stack_data[st->start+2])); + x0=0; + y0=0; + x1=map[map_mapname2mapid(mapname)].xs; + y1=map[map_mapname2mapid(mapname)].ys; + str=conv_str(st,& (st->stack->stack_data[st->start+3])); + x=conv_num(st,& (st->stack->stack_data[st->start+4])); + y=conv_num(st,& (st->stack->stack_data[st->start+5])); + + if( (m=map_mapname2mapid(mapname))< 0) + return 0; + + map_foreachinarea(buildin_areawarp_sub, + m,x0,y0,x1,y1,BL_PC, str,x,y ); + return 0; +} + +int buildin_cmdothernpc(struct script_state *st) // Added by RoVeRT +{ + char *npc,*command; + + npc=conv_str(st,& (st->stack->stack_data[st->start+2])); + command=conv_str(st,& (st->stack->stack_data[st->start+3])); + + npc_command(map_id2sd(st->rid),npc,command); + return 0; +} + +int buildin_inittimer(struct script_state *st) // Added by RoVeRT +{ +// struct npc_data *nd=(struct npc_data*)map_id2bl(st->oid); + +// nd->lastaction=nd->timer=gettick(); + npc_do_ontimer(st->oid, map_id2sd(st->rid), 1); + + return 0; +} + +int buildin_stoptimer(struct script_state *st) // Added by RoVeRT +{ +// struct npc_data *nd=(struct npc_data*)map_id2bl(st->oid); + +// nd->lastaction=nd->timer=-1; + npc_do_ontimer(st->oid, map_id2sd(st->rid), 0); + + return 0; +} + +int buildin_mobcount_sub(struct block_list *bl,va_list ap) // Added by RoVeRT +{ + char *event=va_arg(ap,char *); + int *c=va_arg(ap,int *); + + if(strcmp(event,((struct mob_data *)bl)->npc_event)==0) + (*c)++; + return 0; +} + +int buildin_mobcount(struct script_state *st) // Added by RoVeRT +{ + char *mapname,*event; + int m,c=0; + mapname=conv_str(st,& (st->stack->stack_data[st->start+2])); + event=conv_str(st,& (st->stack->stack_data[st->start+3])); + + if( (m=map_mapname2mapid(mapname))<0 ) { + push_val(st->stack,C_INT,-1); + return 0; + } + map_foreachinarea(buildin_mobcount_sub, + m,0,0,map[m].xs,map[m].ys,BL_MOB, event,&c ); + + push_val(st->stack,C_INT, (c - 1)); + + return 0; +} +int buildin_marriage(struct script_state *st) +{ + char *partner=conv_str(st,& (st->stack->stack_data[st->start+2])); + struct map_session_data *sd=script_rid2sd(st); + struct map_session_data *p_sd=map_nick2sd(partner); + + if(sd==NULL || p_sd==NULL || pc_marriage(sd,p_sd) < 0){ + push_val(st->stack,C_INT,0); + return 0; + } + push_val(st->stack,C_INT,1); + return 0; +} +int buildin_wedding_effect(struct script_state *st) +{ + struct map_session_data *sd=script_rid2sd(st); + + if(sd==NULL) + return 0; + clif_wedding_effect(&sd->bl); + return 0; +} +int buildin_divorce(struct script_state *st) +{ + struct map_session_data *sd=script_rid2sd(st); + if(sd==NULL || pc_divorce(sd) < 0){ + push_val(st->stack,C_INT,0); + return 0; + } + push_val(st->stack,C_INT,1); + return 0; +} + +/*================================================ + * Script for Displaying MOB Information [Valaris] + *------------------------------------------------ + */ +int buildin_strmobinfo(struct script_state *st) +{ + + int num=conv_num(st,& (st->stack->stack_data[st->start+2])); + int class=conv_num(st,& (st->stack->stack_data[st->start+3])); + + if(num<=0 || num>=8 || (class>=0 && class<=1000) || class >2000) + return 0; + + if(num==1) { + char *buf; + buf=calloc(24, 1); + buf=mob_db[class].name; + push_str(st->stack,C_STR,buf); + return 0; + } + else if(num==2) { + char *buf; + buf=calloc(24, 1); + buf=mob_db[class].jname; + push_str(st->stack,C_STR,buf); + return 0; + } + else if(num==3) + push_val(st->stack,C_INT,mob_db[class].lv); + else if(num==4) + push_val(st->stack,C_INT,mob_db[class].max_hp); + else if(num==5) + push_val(st->stack,C_INT,mob_db[class].max_sp); + else if(num==6) + push_val(st->stack,C_INT,mob_db[class].base_exp); + else if(num==7) + push_val(st->stack,C_INT,mob_db[class].job_exp); + return 0; +} + +/*========================================== + * Summon guardians [Valaris] + *------------------------------------------ + */ +int buildin_guardian(struct script_state *st) +{ + int class=0,amount=1,x=0,y=0,guardian=0; + char *str,*map,*event=""; + + map =conv_str(st,& (st->stack->stack_data[st->start+2])); + x =conv_num(st,& (st->stack->stack_data[st->start+3])); + y =conv_num(st,& (st->stack->stack_data[st->start+4])); + str =conv_str(st,& (st->stack->stack_data[st->start+5])); + class=conv_num(st,& (st->stack->stack_data[st->start+6])); + amount=conv_num(st,& (st->stack->stack_data[st->start+7])); + event=conv_str(st,& (st->stack->stack_data[st->start+8])); + if( st->end>st->start+9 ) + guardian=conv_num(st,& (st->stack->stack_data[st->start+9])); + + mob_spawn_guardian(map_id2sd(st->rid),map,x,y,str,class,amount,event,guardian); + + return 0; +} + +/*================================================ + * Script for Displaying Guardian Info [Valaris] + *------------------------------------------------ + */ +int buildin_guardianinfo(struct script_state *st) +{ + int guardian=conv_num(st,& (st->stack->stack_data[st->start+2])); + struct map_session_data *sd=script_rid2sd(st); + struct guild_castle *gc=guild_mapname2gc(map[sd->bl.m].name); + + if(guardian==0 && gc->visibleG0 == 1) push_val(st->stack,C_INT,gc->Ghp0); + if(guardian==1 && gc->visibleG1 == 1) push_val(st->stack,C_INT,gc->Ghp1); + if(guardian==2 && gc->visibleG2 == 1) push_val(st->stack,C_INT,gc->Ghp2); + if(guardian==3 && gc->visibleG3 == 1) push_val(st->stack,C_INT,gc->Ghp3); + if(guardian==4 && gc->visibleG4 == 1) push_val(st->stack,C_INT,gc->Ghp4); + if(guardian==5 && gc->visibleG5 == 1) push_val(st->stack,C_INT,gc->Ghp5); + if(guardian==6 && gc->visibleG6 == 1) push_val(st->stack,C_INT,gc->Ghp6); + if(guardian==7 && gc->visibleG7 == 1) push_val(st->stack,C_INT,gc->Ghp7); + else push_val(st->stack,C_INT,-1); + + return 0; +} +/*========================================== + * IDからItem名 + *------------------------------------------ + */ +int buildin_getitemname(struct script_state *st) +{ + int item_id; + struct item_data *i_data; + char *item_name; + + item_id=conv_num(st,& (st->stack->stack_data[st->start+2])); + + i_data = NULL; + i_data = itemdb_search(item_id); + item_name=(char *)aCalloc(24,sizeof(char)); + + strncpy(item_name,i_data->jname,23); + push_str(st->stack,C_STR,item_name); + return 0; +} + +/*========================================== + * petskillbonus [Valaris] + *------------------------------------------ + */ + +int buildin_petskillbonus(struct script_state *st) +{ + int type,val,duration,timer; + struct pet_data *pd; + + struct map_session_data *sd=script_rid2sd(st); + + if(sd==NULL || sd->pd==NULL) + return 0; + + pd=sd->pd; + + if(pd==NULL) + return 0; + + type=conv_num(st,& (st->stack->stack_data[st->start+2])); + val=conv_num(st,& (st->stack->stack_data[st->start+3])); + duration=conv_num(st,& (st->stack->stack_data[st->start+4])); + timer=conv_num(st,& (st->stack->stack_data[st->start+5])); + + pd->skillbonusduration=-1; + pd->skillbonustimer=-1; + + pet_skill_bonus(sd,pd,type,val,duration,timer,0); + + return 0; +} + +/*========================================== + * pet looting [Valaris] + *------------------------------------------ + */ +int buildin_petloot(struct script_state *st) +{ + int max; + struct pet_data *pd; + + struct map_session_data *sd=script_rid2sd(st); + if(sd==NULL || sd->pd==NULL) + return 0; + + pd=sd->pd; + + if(pd==NULL) + return 0; + + max=conv_num(st,& (st->stack->stack_data[st->start+2])); + + if(!max) + return 0; + + pd->loot=1; + pd->lootmax=max; + + return 0; +} +/*========================================== + * PCの所持品情報読み取り + *------------------------------------------ + */ +int buildin_getinventorylist(struct script_state *st) +{ + struct map_session_data *sd=script_rid2sd(st); + int i,j=0; + if(!sd) return 0; + for(i=0;i<MAX_INVENTORY;i++){ + if(sd->status.inventory[i].nameid > 0 && sd->status.inventory[i].amount > 0){ + pc_setreg(sd,add_str("@inventorylist_id")+(j<<24),sd->status.inventory[i].nameid); + pc_setreg(sd,add_str("@inventorylist_amount")+(j<<24),sd->status.inventory[i].amount); + pc_setreg(sd,add_str("@inventorylist_equip")+(j<<24),sd->status.inventory[i].equip); + pc_setreg(sd,add_str("@inventorylist_refine")+(j<<24),sd->status.inventory[i].refine); + pc_setreg(sd,add_str("@inventorylist_identify")+(j<<24),sd->status.inventory[i].identify); + pc_setreg(sd,add_str("@inventorylist_attribute")+(j<<24),sd->status.inventory[i].attribute); + pc_setreg(sd,add_str("@inventorylist_card1")+(j<<24),sd->status.inventory[i].card[0]); + pc_setreg(sd,add_str("@inventorylist_card2")+(j<<24),sd->status.inventory[i].card[1]); + pc_setreg(sd,add_str("@inventorylist_card3")+(j<<24),sd->status.inventory[i].card[2]); + pc_setreg(sd,add_str("@inventorylist_card4")+(j<<24),sd->status.inventory[i].card[3]); + j++; + } + } + pc_setreg(sd,add_str("@inventorylist_count"),j); + return 0; +} + +int buildin_getskilllist(struct script_state *st) +{ + struct map_session_data *sd=script_rid2sd(st); + int i,j=0; + if(!sd) return 0; + for(i=0;i<MAX_SKILL;i++){ + if(sd->status.skill[i].id > 0 && sd->status.skill[i].lv > 0){ + pc_setreg(sd,add_str("@skilllist_id")+(j<<24),sd->status.skill[i].id); + pc_setreg(sd,add_str("@skilllist_lv")+(j<<24),sd->status.skill[i].lv); + pc_setreg(sd,add_str("@skilllist_flag")+(j<<24),sd->status.skill[i].flag); + j++; + } + } + pc_setreg(sd,add_str("@skilllist_count"),j); + return 0; +} + +int buildin_clearitem(struct script_state *st) +{ + struct map_session_data *sd=script_rid2sd(st); + int i; + if(sd==NULL) return 0; + for (i=0; i<MAX_INVENTORY; i++) { + if (sd->status.inventory[i].amount) + pc_delitem(sd, i, sd->status.inventory[i].amount, 0); + } + return 0; +} + +/*========================================== + * NPCクラスチェンジ + * classは変わりたいclass + * typeは通常0なのかな? + *------------------------------------------ + */ +int buildin_classchange(struct script_state *st) +{ + int class,type; + struct block_list *bl=map_id2bl(st->oid); + + if(bl==NULL) return 0; + + class=conv_num(st,& (st->stack->stack_data[st->start+2])); + type=conv_num(st,& (st->stack->stack_data[st->start+3])); + clif_class_change(bl,class,type); + return 0; +} + +/*========================================== + * NPCから発生するエフェクト + *------------------------------------------ + */ +int buildin_misceffect(struct script_state *st) +{ + int type; + + type=conv_num(st,& (st->stack->stack_data[st->start+2])); + if(st->oid) + clif_misceffect2(map_id2bl(st->oid),type); + else{ + struct map_session_data *sd=script_rid2sd(st); + if(sd) + clif_misceffect2(&sd->bl,type); + } + return 0; +} +/*========================================== + * サウンドエフェクト + *------------------------------------------ + */ +int buildin_soundeffect(struct script_state *st) +{ + struct map_session_data *sd=script_rid2sd(st); + char *name; + int type=0; + + + name=conv_str(st,& (st->stack->stack_data[st->start+2])); + type=conv_num(st,& (st->stack->stack_data[st->start+3])); + if(sd){ + if(st->oid) + clif_soundeffect(sd,map_id2bl(st->oid),name,type); + else{ + clif_soundeffect(sd,&sd->bl,name,type); + } + } + return 0; +} +/*========================================== + * pet status recovery [Valaris] + *------------------------------------------ + */ +int buildin_petrecovery(struct script_state *st) +{ + struct pet_data *pd; + + struct map_session_data *sd=script_rid2sd(st); + + if(sd==NULL || sd->pd==NULL) + return 0; + + pd=sd->pd; + + if(pd==NULL) + return 0; + + pd->skilltype=conv_num(st,& (st->stack->stack_data[st->start+2])); + pd->skilltimer=conv_num(st,& (st->stack->stack_data[st->start+3])); + + pd->skillbonustimer=add_timer(gettick()+pd->skilltimer*1000,pet_recovery_timer,sd->bl.id,0); + + return 0; +} + +/*========================================== + * pet healing [Valaris] + *------------------------------------------ + */ +int buildin_petheal(struct script_state *st) + +{ + struct pet_data *pd; + struct map_session_data *sd=script_rid2sd(st); + + if(sd==NULL || sd->pd==NULL) + return 0; + + pd=sd->pd; + + if(pd==NULL) + return 0; + + pd->skilltype=conv_num(st,& (st->stack->stack_data[st->start+2])); + pd->skillval=conv_num(st,& (st->stack->stack_data[st->start+3])); + pd->skilltimer=conv_num(st,& (st->stack->stack_data[st->start+4])); + + pd->skillbonustimer=add_timer(gettick()+pd->skilltimer*1000,pet_heal_timer,sd->bl.id,0); + + return 0; +} + +/*========================================== + * pet magnificat [Valaris] + *------------------------------------------ + */ +int buildin_petmag(struct script_state *st) +{ + struct pet_data *pd; + struct map_session_data *sd=script_rid2sd(st); + + if(sd==NULL || sd->pd==NULL) + return 0; + + pd=sd->pd; + + if(pd==NULL) + return 0; + + pd->skilltype=conv_num(st,& (st->stack->stack_data[st->start+2])); + pd->skillduration=conv_num(st,& (st->stack->stack_data[st->start+3])); + pd->skillval=conv_num(st,& (st->stack->stack_data[st->start+4])); + pd->skilltimer=conv_num(st,& (st->stack->stack_data[st->start+5])); + + pd->skillbonustimer=add_timer(gettick()+pd->skilltimer*1000,pet_mag_timer,sd->bl.id,0); + + return 0; +} + +/*========================================== + * pet attack skills [Valaris] + *------------------------------------------ + */ +int buildin_petskillattack(struct script_state *st) +{ + struct pet_data *pd; + struct map_session_data *sd=script_rid2sd(st); + + if(sd==NULL || sd->pd==NULL) + return 0; + + pd=sd->pd; + + if(pd==NULL) + return 0; + + pd->skilltype=conv_num(st,& (st->stack->stack_data[st->start+2])); + pd->skillval=conv_num(st,& (st->stack->stack_data[st->start+3])); + pd->skillduration=conv_num(st,& (st->stack->stack_data[st->start+4])); + pd->skilltimer=conv_num(st,& (st->stack->stack_data[st->start+5])); + + pd->skillbonustimer=add_timer(gettick()+100,pet_skillattack_timer,sd->bl.id,0); + + return 0; +} +/*========================================== + * NPC skill effects [Valaris] + *------------------------------------------ + */ +int buildin_npcskilleffect(struct script_state *st) +{ + struct npc_data *nd=(struct npc_data *)map_id2bl(st->oid); + + int skillid=conv_num(st,& (st->stack->stack_data[st->start+2])); + int skilllv=conv_num(st,& (st->stack->stack_data[st->start+3])); + int x=conv_num(st,& (st->stack->stack_data[st->start+4])); + int y=conv_num(st,& (st->stack->stack_data[st->start+5])); + + clif_skill_poseffect(&nd->bl,skillid,skilllv,x,y,gettick()); + + return 0; +} + +/*========================================== + * Special effects [Valaris] + *------------------------------------------ + */ +int buildin_specialeffect(struct script_state *st) +{ + struct block_list *bl=map_id2bl(st->oid); + + if(bl==NULL) + return 0; + + clif_specialeffect(bl,conv_num(st,& (st->stack->stack_data[st->start+2])), 0); + + return 0; +} + +int buildin_specialeffect2(struct script_state *st) +{ + struct map_session_data *sd=script_rid2sd(st); + + if(sd==NULL) + return 0; + + clif_specialeffect(&sd->bl,conv_num(st,& (st->stack->stack_data[st->start+2])), 0); + + return 0; +} + +/*========================================== + * Nude [Valaris] + *------------------------------------------ + */ + +int buildin_nude(struct script_state *st) +{ + struct map_session_data *sd=script_rid2sd(st); + int i; + + if(sd==NULL) + return 0; + + for(i=0;i<11;i++) + if(sd->equip_index[i] >= 0) + pc_unequipitem(sd,sd->equip_index[i],1); + + return 0; +} + +/*========================================== + * gmcommand [MouseJstr] + * + * suggested on the forums... + *------------------------------------------ + */ + +int buildin_gmcommand(struct script_state *st) +{ + struct map_session_data *sd; + char *cmd; + + sd = script_rid2sd(st); + cmd = conv_str(st,& (st->stack->stack_data[st->start+2])); + + is_atcommand(sd->fd, sd, cmd, 99); + + return 0; +} + +/*========================================== + * movenpc [MouseJstr] + *------------------------------------------ + */ + +int buildin_movenpc(struct script_state *st) +{ + struct map_session_data *sd; + char *map,*npc; + int x,y; + + sd = script_rid2sd(st); + + map = conv_str(st,& (st->stack->stack_data[st->start+2])); + x = conv_num(st,& (st->stack->stack_data[st->start+3])); + y = conv_num(st,& (st->stack->stack_data[st->start+4])); + npc = conv_str(st,& (st->stack->stack_data[st->start+5])); + + return 0; +} + +/*========================================== + * message [MouseJstr] + *------------------------------------------ + */ + +int buildin_message(struct script_state *st) +{ + struct map_session_data *sd; + char *msg,*player; + struct map_session_data *pl_sd = NULL; + + sd = script_rid2sd(st); + + player = conv_str(st,& (st->stack->stack_data[st->start+2])); + msg = conv_str(st,& (st->stack->stack_data[st->start+3])); + + if((pl_sd=map_nick2sd((char *) player)) == NULL) + return 1; + clif_displaymessage(pl_sd->fd, msg); + + return 0; +} + +/*========================================== + * npctalk (sends message to surrounding + * area) [Valaris] + *------------------------------------------ + */ + +int buildin_npctalk(struct script_state *st) +{ + char *str; + char message[255]; + + struct npc_data *nd=(struct npc_data *)map_id2bl(st->oid); + str=conv_str(st,& (st->stack->stack_data[st->start+2])); + + if(nd) { + memcpy(message,nd->name,24); + strcat(message," : "); + strcat(message,str); + clif_message(&(nd->bl), message); + } + + return 0; +} + +/*========================================== + * hasitems (checks to see if player has any + * items on them, if so will return a 1) + * [Valaris] + *------------------------------------------ + */ + +int buildin_hasitems(struct script_state *st) +{ + int i; + struct map_session_data *sd; + + sd=script_rid2sd(st); + + for(i=0; i<MAX_INVENTORY; i++) { + if(sd->status.inventory[i].amount) { + push_val(st->stack,C_INT,1); + return 0; + } + } + + push_val(st->stack,C_INT,0); + + return 0; +} + +/*========================================== + * getlook char info. getlook(arg) + *------------------------------------------ + */ +int buildin_getlook(struct script_state *st){ + int type,val; + struct map_session_data *sd; + sd=script_rid2sd(st); + + type=conv_num(st,& (st->stack->stack_data[st->start+2])); + val=-1; + switch(type){ + case LOOK_HAIR: //1 + val=sd->status.hair; + break; + case LOOK_WEAPON: //2 + val=sd->status.weapon; + break; + case LOOK_HEAD_BOTTOM: //3 + val=sd->status.head_bottom; + break; + case LOOK_HEAD_TOP: //4 + val=sd->status.head_top; + break; + case LOOK_HEAD_MID: //5 + val=sd->status.head_mid; + break; + case LOOK_HAIR_COLOR: //6 + val=sd->status.hair_color; + break; + case LOOK_CLOTHES_COLOR: //7 + val=sd->status.clothes_color; + break; + case LOOK_SHIELD: //8 + val=sd->status.shield; + break; + case LOOK_SHOES: //9 + break; + } + + push_val(st->stack,C_INT,val); + return 0; +} + +/*========================================== + * get char save point. argument: 0- map name, 1- x, 2- y + *------------------------------------------ +*/ +int buildin_getsavepoint(struct script_state *st) +{ + int x,y,type; + char *mapname; + struct map_session_data *sd; + + sd=script_rid2sd(st); + + type=conv_num(st,& (st->stack->stack_data[st->start+2])); + mapname=calloc(24, 1); + + x=sd->status.save_point.x; + y=sd->status.save_point.y; + strncpy(mapname,sd->status.save_point.map,24); + switch(type){ + case 0: + push_str(st->stack,C_STR,mapname); + break; + case 1: + push_val(st->stack,C_INT,x); + break; + case 2: + push_val(st->stack,C_INT,y); + break; + } + return 0; +} + + +// +// 実行部main +// +/*========================================== + * コマンドの読み取り + *------------------------------------------ + */ +static int unget_com_data=-1; +int get_com(unsigned char *script,int *pos) +{ + int i,j; + if(unget_com_data>=0){ + i=unget_com_data; + unget_com_data=-1; + return i; + } + if(script[*pos]>=0x80){ + return C_INT; + } + i=0; j=0; + while(script[*pos]>=0x40){ + i=script[(*pos)++]<<j; + j+=6; + } + return i+(script[(*pos)++]<<j); +} + +/*========================================== + * コマンドのプッシュバック + *------------------------------------------ + */ +void unget_com(int c) +{ + if(unget_com_data!=-1){ + if(battle_config.error_log) + printf("unget_com can back only 1 data\n"); + } + unget_com_data=c; +} + +/*========================================== + * 数値の所得 + *------------------------------------------ + */ +int get_num(unsigned char *script,int *pos) +{ + int i,j; + i=0; j=0; + while(script[*pos]>=0xc0){ + i+=(script[(*pos)++]&0x7f)<<j; + j+=6; + } + return i+((script[(*pos)++]&0x7f)<<j); +} + +/*========================================== + * スタックから値を取り出す + *------------------------------------------ + */ +int pop_val(struct script_state* st) +{ + if(st->stack->sp<=0) + return 0; + st->stack->sp--; + get_val(st,&(st->stack->stack_data[st->stack->sp])); + if(st->stack->stack_data[st->stack->sp].type==C_INT) + return st->stack->stack_data[st->stack->sp].u.num; + return 0; +} + +#define isstr(c) ((c).type==C_STR || (c).type==C_CONSTSTR) + +/*========================================== + * 加算演算子 + *------------------------------------------ + */ +void op_add(struct script_state* st) +{ + st->stack->sp--; + get_val(st,&(st->stack->stack_data[st->stack->sp])); + get_val(st,&(st->stack->stack_data[st->stack->sp-1])); + + if(isstr(st->stack->stack_data[st->stack->sp]) || isstr(st->stack->stack_data[st->stack->sp-1])){ + conv_str(st,&(st->stack->stack_data[st->stack->sp])); + conv_str(st,&(st->stack->stack_data[st->stack->sp-1])); + } + if(st->stack->stack_data[st->stack->sp].type==C_INT){ // ii + st->stack->stack_data[st->stack->sp-1].u.num += st->stack->stack_data[st->stack->sp].u.num; + } else { // ssの予定 + char *buf; + buf=(char *)aCalloc(strlen(st->stack->stack_data[st->stack->sp-1].u.str)+ + strlen(st->stack->stack_data[st->stack->sp].u.str)+1,sizeof(char)); + strcpy(buf,st->stack->stack_data[st->stack->sp-1].u.str); + strcat(buf,st->stack->stack_data[st->stack->sp].u.str); + if(st->stack->stack_data[st->stack->sp-1].type==C_STR) + free(st->stack->stack_data[st->stack->sp-1].u.str); + if(st->stack->stack_data[st->stack->sp].type==C_STR) + free(st->stack->stack_data[st->stack->sp].u.str); + st->stack->stack_data[st->stack->sp-1].type=C_STR; + st->stack->stack_data[st->stack->sp-1].u.str=buf; + } +} + +/*========================================== + * 二項演算子(文字列) + *------------------------------------------ + */ +void op_2str(struct script_state *st,int op,int sp1,int sp2) +{ + char *s1=st->stack->stack_data[sp1].u.str, + *s2=st->stack->stack_data[sp2].u.str; + int a=0; + + switch(op){ + case C_EQ: + a= (strcmp(s1,s2)==0); + break; + case C_NE: + a= (strcmp(s1,s2)!=0); + break; + case C_GT: + a= (strcmp(s1,s2)> 0); + break; + case C_GE: + a= (strcmp(s1,s2)>=0); + break; + case C_LT: + a= (strcmp(s1,s2)< 0); + break; + case C_LE: + a= (strcmp(s1,s2)<=0); + break; + default: + printf("illegal string operater\n"); + break; + } + + push_val(st->stack,C_INT,a); + + if(st->stack->stack_data[sp1].type==C_STR) free(s1); + if(st->stack->stack_data[sp2].type==C_STR) free(s2); +} +/*========================================== + * 二項演算子(数値) + *------------------------------------------ + */ +void op_2num(struct script_state *st,int op,int i1,int i2) +{ + switch(op){ + case C_SUB: + i1-=i2; + break; + case C_MUL: + i1*=i2; + break; + case C_DIV: + i1/=i2; + break; + case C_MOD: + i1%=i2; + break; + case C_AND: + i1&=i2; + break; + case C_OR: + i1|=i2; + break; + case C_XOR: + i1^=i2; + break; + case C_LAND: + i1=i1&&i2; + break; + case C_LOR: + i1=i1||i2; + break; + case C_EQ: + i1=i1==i2; + break; + case C_NE: + i1=i1!=i2; + break; + case C_GT: + i1=i1>i2; + break; + case C_GE: + i1=i1>=i2; + break; + case C_LT: + i1=i1<i2; + break; + case C_LE: + i1=i1<=i2; + break; + case C_R_SHIFT: + i1=i1>>i2; + break; + case C_L_SHIFT: + i1=i1<<i2; + break; + } + push_val(st->stack,C_INT,i1); +} +/*========================================== + * 二項演算子 + *------------------------------------------ + */ +void op_2(struct script_state *st,int op) +{ + int i1,i2; + char *s1=NULL,*s2=NULL; + + i2=pop_val(st); + if( isstr(st->stack->stack_data[st->stack->sp]) ) + s2=st->stack->stack_data[st->stack->sp].u.str; + + i1=pop_val(st); + if( isstr(st->stack->stack_data[st->stack->sp]) ) + s1=st->stack->stack_data[st->stack->sp].u.str; + + if( s1!=NULL && s2!=NULL ){ + // ss => op_2str + op_2str(st,op,st->stack->sp,st->stack->sp+1); + }else if( s1==NULL && s2==NULL ){ + // ii => op_2num + op_2num(st,op,i1,i2); + }else{ + // si,is => error + printf("script: op_2: int&str, str&int not allow."); + push_val(st->stack,C_INT,0); + } +} + +/*========================================== + * 単項演算子 + *------------------------------------------ + */ +void op_1num(struct script_state *st,int op) +{ + int i1; + i1=pop_val(st); + switch(op){ + case C_NEG: + i1=-i1; + break; + case C_NOT: + i1=~i1; + break; + case C_LNOT: + i1=!i1; + break; + } + push_val(st->stack,C_INT,i1); +} + + +/*========================================== + * 関数の実行 + *------------------------------------------ + */ +int run_func(struct script_state *st) +{ + int i,start_sp,end_sp,func; + + end_sp=st->stack->sp; + for(i=end_sp-1;i>=0 && st->stack->stack_data[i].type!=C_ARG;i--); + if(i==0){ + if(battle_config.error_log) + printf("function not found\n"); +// st->stack->sp=0; + st->state=END; + return 0; + } + start_sp=i-1; + st->start=i-1; + st->end=end_sp; + + func=st->stack->stack_data[st->start].u.num; + if( st->stack->stack_data[st->start].type!=C_NAME || str_data[func].type!=C_FUNC ){ + printf("run_func: not function and command! \n"); +// st->stack->sp=0; + st->state=END; + return 0; + } +#ifdef DEBUG_RUN + if(battle_config.etc_log) { + printf("run_func : %s? (%d(%d))\n",str_buf+str_data[func].str,func,str_data[func].type); + printf("stack dump :"); + for(i=0;i<end_sp;i++){ + switch(st->stack->stack_data[i].type){ + case C_INT: + printf(" int(%d)",st->stack->stack_data[i].u.num); + break; + case C_NAME: + printf(" name(%s)",str_buf+str_data[st->stack->stack_data[i].u.num].str); + break; + case C_ARG: + printf(" arg"); + break; + case C_POS: + printf(" pos(%d)",st->stack->stack_data[i].u.num); + break; + default: + printf(" %d,%d",st->stack->stack_data[i].type,st->stack->stack_data[i].u.num); + } + } + printf("\n"); + } +#endif + if(str_data[func].func){ + str_data[func].func(st); + } else { + if(battle_config.error_log) + printf("run_func : %s? (%d(%d))\n",str_buf+str_data[func].str,func,str_data[func].type); + push_val(st->stack,C_INT,0); + } + + pop_stack(st->stack,start_sp,end_sp); + + if(st->state==RETFUNC){ + // ユーザー定義関数からの復帰 + int olddefsp=st->defsp; + int i; + + pop_stack(st->stack,st->defsp,start_sp); // 復帰に邪魔なスタック削除 + if(st->defsp<4 || st->stack->stack_data[st->defsp-1].type!=C_RETINFO){ + printf("script:run_func(return) return without callfunc or callsub!\n"); + st->state=END; + return 0; + } + i = conv_num(st,& (st->stack->stack_data[st->defsp-4])); // 引数の数所得 + st->pos=conv_num(st,& (st->stack->stack_data[st->defsp-1])); // スクリプト位置の復元 + st->script=(char*)conv_num(st,& (st->stack->stack_data[st->defsp-2])); // スクリプトを復元 + st->defsp=conv_num(st,& (st->stack->stack_data[st->defsp-3])); // 基準スタックポインタを復元 + + pop_stack(st->stack,olddefsp-4-i,olddefsp); // 要らなくなったスタック(引数と復帰用データ)削除 + + st->state=GOTO; + } + + return 0; +} + +/*========================================== + * スクリプトの実行メイン部分 + *------------------------------------------ + */ +int run_script_main(unsigned char *script,int pos,int rid,int oid,struct script_state *st,unsigned char *rootscript) +{ + int c,rerun_pos; + int cmdcount=script_config.check_cmdcount; + int gotocount=script_config.check_gotocount; + struct script_stack *stack=st->stack; + + st->defsp=stack->sp; + st->script=script; + + rerun_pos=st->pos; + for(st->state=0;st->state==0;){ + switch(c=get_com(script,&st->pos)){ + case C_EOL: + if(stack->sp!=st->defsp){ + if(battle_config.error_log) + printf("stack.sp(%d) != default(%d)\n",stack->sp,st->defsp); + stack->sp=st->defsp; + } + rerun_pos=st->pos; + break; + case C_INT: + push_val(stack,C_INT,get_num(script,&st->pos)); + break; + case C_POS: + case C_NAME: + push_val(stack,c,(*(int*)(script+st->pos))&0xffffff); + st->pos+=3; + break; + case C_ARG: + push_val(stack,c,0); + break; + case C_STR: + push_str(stack,C_CONSTSTR,script+st->pos); + while(script[st->pos++]); + break; + case C_FUNC: + run_func(st); + if(st->state==GOTO){ + rerun_pos=st->pos; + script=st->script; + st->state=0; + if( gotocount>0 && (--gotocount)<=0 ){ + printf("run_script: infinity loop !\n"); + st->state=END; + } + } + break; + + case C_ADD: + op_add(st); + break; + + case C_SUB: + case C_MUL: + case C_DIV: + case C_MOD: + case C_EQ: + case C_NE: + case C_GT: + case C_GE: + case C_LT: + case C_LE: + case C_AND: + case C_OR: + case C_XOR: + case C_LAND: + case C_LOR: + case C_R_SHIFT: + case C_L_SHIFT: + op_2(st,c); + break; + + case C_NEG: + case C_NOT: + case C_LNOT: + op_1num(st,c); + break; + + case C_NOP: + st->state=END; + break; + + default: + if(battle_config.error_log) + printf("unknown command : %d @ %d\n",c,pos); + st->state=END; + break; + } + if( cmdcount>0 && (--cmdcount)<=0 ){ + printf("run_script: infinity loop !\n"); + st->state=END; + } + } + switch(st->state){ + case STOP: + break; + case END: + { + struct map_session_data *sd=map_id2sd(st->rid); + st->pos=-1; + if(sd && sd->npc_id==st->oid) + npc_event_dequeue(sd); + } + break; + case RERUNLINE: + { + st->pos=rerun_pos; + } + break; + } + + if( st->state!=END){ + // 再開するためにスタック情報を保存 + struct map_session_data *sd=map_id2sd(st->rid); + if(sd/* && sd->npc_stackbuf==NULL*/){ + if( sd->npc_stackbuf ) + free( sd->npc_stackbuf ); + sd->npc_stackbuf = (char *)aCalloc(sizeof(stack->stack_data[0])*stack->sp_max,sizeof(char)); + memcpy(sd->npc_stackbuf, stack->stack_data, sizeof(stack->stack_data[0]) * stack->sp_max); + sd->npc_stack = stack->sp; + sd->npc_stackmax = stack->sp_max; + sd->npc_script=script; + sd->npc_scriptroot=rootscript; + } + } + + return 0; +} + +/*========================================== + * スクリプトの実行 + *------------------------------------------ + */ +int run_script(unsigned char *script,int pos,int rid,int oid) +{ + struct script_stack stack; + struct script_state st; + struct map_session_data *sd=map_id2sd(rid); + unsigned char *rootscript=script; + + if(script==NULL || pos<0) + return -1; + + if(sd && sd->npc_stackbuf && sd->npc_scriptroot==(char*)rootscript){ + // 前回のスタックを復帰 + script=sd->npc_script; + stack.sp=sd->npc_stack; + stack.sp_max=sd->npc_stackmax; + stack.stack_data=(struct script_data *)aCalloc(stack.sp_max,sizeof(stack.stack_data[0])); + memcpy(stack.stack_data,sd->npc_stackbuf,sizeof(stack.stack_data[0])*stack.sp_max); + free(sd->npc_stackbuf); + sd->npc_stackbuf=NULL; + }else{ + // スタック初期化 + stack.sp=0; + stack.sp_max=64; + stack.stack_data=(struct script_data *)aCalloc(stack.sp_max,sizeof(stack.stack_data[0])); + } + st.stack=&stack; + st.pos=pos; + st.rid=rid; + st.oid=oid; + run_script_main(script,pos,rid,oid,&st,rootscript); + + free(stack.stack_data); + stack.stack_data=NULL; + return st.pos; +} + + +/*========================================== + * マップ変数の変更 + *------------------------------------------ + */ +int mapreg_setreg(int num,int val) +{ + if(val!=0) + numdb_insert(mapreg_db,num,val); + else + numdb_erase(mapreg_db,num); + + mapreg_dirty=1; + return 0; +} +/*========================================== + * 文字列型マップ変数の変更 + *------------------------------------------ + */ +int mapreg_setregstr(int num,const char *str) +{ + char *p; + + if( (p=numdb_search(mapregstr_db,num))!=NULL ) + free(p); + + if( str==NULL || *str==0 ){ + numdb_erase(mapregstr_db,num); + mapreg_dirty=1; + return 0; + } + p=(char *)aCalloc(strlen(str)+1, sizeof(char)); + strcpy(p,str); + numdb_insert(mapregstr_db,num,p); + mapreg_dirty=1; + return 0; +} + +/*========================================== + * 永続的マップ変数の読み込み + *------------------------------------------ + */ +static int script_load_mapreg() +{ + FILE *fp; + char line[1024]; + + if( (fp=fopen(mapreg_txt,"rt"))==NULL ) + return -1; + + while(fgets(line,sizeof(line),fp)){ + char buf1[256],buf2[1024],*p; + int n,v,s,i; + if( sscanf(line,"%255[^,],%d\t%n",buf1,&i,&n)!=2 && + (i=0,sscanf(line,"%[^\t]\t%n",buf1,&n)!=1) ) + continue; + if( buf1[strlen(buf1)-1]=='$' ){ + if( sscanf(line+n,"%[^\n\r]",buf2)!=1 ){ + printf("%s: %s broken data !\n",mapreg_txt,buf1); + continue; + } + p=(char *)aCalloc(strlen(buf2) + 1,sizeof(char)); + strcpy(p,buf2); + s=add_str(buf1); + numdb_insert(mapregstr_db,(i<<24)|s,p); + }else{ + if( sscanf(line+n,"%d",&v)!=1 ){ + printf("%s: %s broken data !\n",mapreg_txt,buf1); + continue; + } + s=add_str(buf1); + numdb_insert(mapreg_db,(i<<24)|s,v); + } + } + fclose(fp); + mapreg_dirty=0; + return 0; +} +/*========================================== + * 永続的マップ変数の書き込み + *------------------------------------------ + */ +static int script_save_mapreg_intsub(void *key,void *data,va_list ap) +{ + FILE *fp=va_arg(ap,FILE*); + int num=((int)key)&0x00ffffff, i=((int)key)>>24; + char *name=str_buf+str_data[num].str; + if( name[1]!='@' ){ + if(i==0) + fprintf(fp,"%s\t%d\n", name, (int)data); + else + fprintf(fp,"%s,%d\t%d\n", name, i, (int)data); + } + return 0; +} +static int script_save_mapreg_strsub(void *key,void *data,va_list ap) +{ + FILE *fp=va_arg(ap,FILE*); + int num=((int)key)&0x00ffffff, i=((int)key)>>24; + char *name=str_buf+str_data[num].str; + if( name[1]!='@' ){ + if(i==0) + fprintf(fp,"%s\t%s\n", name, (char *)data); + else + fprintf(fp,"%s,%d\t%s\n", name, i, (char *)data); + } + return 0; +} +static int script_save_mapreg() +{ + FILE *fp; + int lock; + + if( (fp=lock_fopen(mapreg_txt,&lock))==NULL ) + return -1; + numdb_foreach(mapreg_db,script_save_mapreg_intsub,fp); + numdb_foreach(mapregstr_db,script_save_mapreg_strsub,fp); + lock_fclose(fp,mapreg_txt,&lock); + mapreg_dirty=0; + return 0; +} +static int script_autosave_mapreg(int tid,unsigned int tick,int id,int data) +{ + if(mapreg_dirty) + script_save_mapreg(); + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +static int set_posword(char *p) +{ + char* np,* str[15]; + int i=0; + for(i=0;i<11;i++) { + if((np=strchr(p,','))!=NULL) { + str[i]=p; + *np=0; + p=np+1; + } else { + str[i]=p; + p+=strlen(p); + } + if(str[i]) + strcpy(pos[i],str[i]); + } + return 0; +} + +int script_config_read(char *cfgName) +{ + int i; + char line[1024],w1[1024],w2[1024]; + FILE *fp; + + script_config.warn_func_no_comma=1; + script_config.warn_cmd_no_comma=1; + script_config.warn_func_mismatch_paramnum=1; + script_config.warn_cmd_mismatch_paramnum=1; + script_config.check_cmdcount=8192; + script_config.check_gotocount=512; + + fp=fopen(cfgName,"r"); + if(fp==NULL){ + printf("file not found: %s\n",cfgName); + return 1; + } + while(fgets(line,1020,fp)){ + if(line[0] == '/' && line[1] == '/') + continue; + i=sscanf(line,"%[^:]: %[^\r\n]",w1,w2); + if(i!=2) + continue; + if(strcmpi(w1,"refine_posword")==0) { + set_posword(w2); + } + if(strcmpi(w1,"import")==0){ + script_config_read(w2); + } + } + fclose(fp); + + return 0; +} + +/*========================================== + * 終了 + *------------------------------------------ + */ +static int mapreg_db_final(void *key,void *data,va_list ap) +{ + return 0; +} +static int mapregstr_db_final(void *key,void *data,va_list ap) +{ + free(data); + return 0; +} +static int scriptlabel_db_final(void *key,void *data,va_list ap) +{ + return 0; +} +static int userfunc_db_final(void *key,void *data,va_list ap) +{ + free(key); + free(data); + return 0; +} +int do_final_script() +{ + if(mapreg_dirty>=0) + script_save_mapreg(); + if(script_buf) + free(script_buf); + + if(mapreg_db) + numdb_final(mapreg_db,mapreg_db_final); + if(mapregstr_db) + strdb_final(mapregstr_db,mapregstr_db_final); + if(scriptlabel_db) + strdb_final(scriptlabel_db,scriptlabel_db_final); + if(userfunc_db) + strdb_final(userfunc_db,userfunc_db_final); + + if (str_data) + free(str_data); + if (str_buf) + free(str_buf); + + return 0; +} +/*========================================== + * 初期化 + *------------------------------------------ + */ +int do_init_script() +{ + mapreg_db=numdb_init(); + mapregstr_db=numdb_init(); + script_load_mapreg(); + + add_timer_func_list(script_autosave_mapreg,"script_autosave_mapreg"); + add_timer_interval(gettick()+MAPREG_AUTOSAVE_INTERVAL, + script_autosave_mapreg,0,0,MAPREG_AUTOSAVE_INTERVAL); + + scriptlabel_db=strdb_init(50); + return 0; +} diff --git a/misc/src/map/script.h b/misc/src/map/script.h new file mode 100644 index 0000000..b50c466 --- /dev/null +++ b/misc/src/map/script.h @@ -0,0 +1,39 @@ +// $Id: script.h,v 1.2 2004/09/25 05:32:19 MouseJstr Exp $ +#ifndef _SCRIPT_H_ +#define _SCRIPT_H_ + +struct script_data { + int type; + union { + int num; + char *str; + } u; +}; + +struct script_stack { + int sp,sp_max; + struct script_data *stack_data; +}; +struct script_state { + struct script_stack *stack; + int start,end; + int pos,state; + int rid,oid; + char *script,*new_script; + int defsp,new_pos,new_defsp; +}; + +unsigned char * parse_script(unsigned char *,int); +int run_script(unsigned char *,int,int,int); + +struct dbt* script_get_label_db(); +struct dbt* script_get_userfunc_db(); + +int script_config_read(char *cfgName); +int do_init_script(); +int do_final_script(); + +extern char mapreg_txt[]; + +#endif + diff --git a/misc/src/map/skill.c b/misc/src/map/skill.c new file mode 100644 index 0000000..1e92b3f --- /dev/null +++ b/misc/src/map/skill.c @@ -0,0 +1,10637 @@ +// $Id: skill.c,v 1.8 2004/09/25 05:32:19 MouseJstr Exp $ +/* スキル関係 */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> + +#include "timer.h" +#include "nullpo.h" +#include "malloc.h" + +#include "skill.h" +#include "map.h" +#include "clif.h" +#include "pc.h" +#include "pet.h" +#include "mob.h" +#include "battle.h" +#include "party.h" +#include "itemdb.h" +#include "script.h" +#include "intif.h" + +#ifdef MEMWATCH +#include "memwatch.h" +#endif + +#define SKILLUNITTIMER_INVERVAL 100 + +#define STATE_BLIND 0x10 + +/* スキル番号=>ステータス異常番号変換テーブル */ +int SkillStatusChangeTable[]={ /* skill.hのenumのSC_***とあわせること */ +/* 0- */ + -1,-1,-1,-1,-1,-1, + SC_PROVOKE, /* プロボック */ + -1, 1,-1, +/* 10- */ + SC_SIGHT, /* サイト */ + -1,-1,-1,-1, + SC_FREEZE, /* フロストダイバー */ + SC_STONE, /* ストーンカース */ + -1,-1,-1, +/* 20- */ + -1,-1,-1,-1, + SC_RUWACH, /* ルアフ */ + -1,-1,-1,-1, + SC_INCREASEAGI, /* 速度増加 */ +/* 30- */ + SC_DECREASEAGI, /* 速度減少 */ + -1, + SC_SIGNUMCRUCIS, /* シグナムクルシス */ + SC_ANGELUS, /* エンジェラス */ + SC_BLESSING, /* ブレッシング */ + -1,-1,-1,-1,-1, +/* 40- */ + -1,-1,-1,-1,-1, + SC_CONCENTRATE, /* 集中力向上 */ + -1,-1,-1,-1, +/* 50- */ + -1, + SC_HIDING, /* ハイディング */ + -1,-1,-1,-1,-1,-1,-1,-1, +/* 60- */ + SC_TWOHANDQUICKEN, /* 2HQ */ + SC_AUTOCOUNTER, + -1,-1,-1,-1, + SC_IMPOSITIO, /* インポシティオマヌス */ + SC_SUFFRAGIUM, /* サフラギウム */ + SC_ASPERSIO, /* アスペルシオ */ + SC_BENEDICTIO, /* 聖体降福 */ +/* 70- */ + -1, + SC_SLOWPOISON, + -1, + SC_KYRIE, /* キリエエレイソン */ + SC_MAGNIFICAT, /* マグニフィカート */ + SC_GLORIA, /* グロリア */ + SC_DIVINA, /* レックスディビーナ */ + -1, + SC_AETERNA, /* レックスエーテルナ */ + -1, +/* 80- */ + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1, +/* 90- */ + -1,-1, + SC_QUAGMIRE, /* クァグマイア */ + -1,-1,-1,-1,-1,-1,-1, +/* 100- */ + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1, +/* 110- */ + -1, + SC_ADRENALINE, /* アドレナリンラッシュ */ + SC_WEAPONPERFECTION,/* ウェポンパーフェクション */ + SC_OVERTHRUST, /* オーバートラスト */ + SC_MAXIMIZEPOWER, /* マキシマイズパワー */ + -1,-1,-1,-1,-1, +/* 120- */ + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1, +/* 130- */ + -1,-1,-1,-1,-1, + SC_CLOAKING, /* クローキング */ + SC_STAN, /* ソニックブロー */ + -1, + SC_ENCPOISON, /* エンチャントポイズン */ + SC_POISONREACT, /* ポイズンリアクト */ +/* 140- */ + SC_POISON, /* ベノムダスト */ + SC_SPLASHER, /* ベナムスプラッシャー */ + -1, + SC_TRICKDEAD, /* 死んだふり */ + -1,-1,-1,-1,-1,-1, +/* 150- */ + -1,-1,-1,-1,-1, + SC_LOUD, /* ラウドボイス */ + -1, + SC_ENERGYCOAT, /* エナジーコート */ + -1,-1, +/* 160- */ + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1, + SC_SELFDESTRUCTION, + -1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1, + SC_KEEPING, + -1,-1, + SC_BARRIER, + -1,-1, + SC_HALLUCINATION, + -1,-1, +/* 210- */ + -1,-1,-1,-1,-1, + SC_STRIPWEAPON, + SC_STRIPSHIELD, + SC_STRIPARMOR, + SC_STRIPHELM, + -1, +/* 220- */ + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1, +/* 230- */ + -1,-1,-1,-1, + SC_CP_WEAPON, + SC_CP_SHIELD, + SC_CP_ARMOR, + SC_CP_HELM, + -1,-1, +/* 240- */ + -1,-1,-1,-1,-1,-1,-1,-1,-1, + SC_AUTOGUARD, +/* 250- */ + -1,-1, + SC_REFLECTSHIELD, + -1,-1, + SC_DEVOTION, + SC_PROVIDENCE, + SC_DEFENDER, + SC_SPEARSQUICKEN, + -1, +/* 260- */ + -1,-1,-1,-1,-1,-1,-1,-1, + SC_STEELBODY, + SC_BLADESTOP_WAIT, +/* 270- */ + SC_EXPLOSIONSPIRITS, + SC_EXTREMITYFIST, + -1,-1,-1,-1, + SC_MAGICROD, + -1,-1,-1, +/* 280- */ + SC_FLAMELAUNCHER, + SC_FROSTWEAPON, + SC_LIGHTNINGLOADER, + SC_SEISMICWEAPON, + -1, + SC_VOLCANO, + SC_DELUGE, + SC_VIOLENTGALE, + SC_LANDPROTECTOR, + -1, +/* 290- */ + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1, +/* 300- */ + -1,-1,-1,-1,-1,-1, + SC_LULLABY, + SC_RICHMANKIM, + SC_ETERNALCHAOS, + SC_DRUMBATTLE, +/* 310- */ + SC_NIBELUNGEN, + SC_ROKISWEIL, + SC_INTOABYSS, + SC_SIEGFRIED, + -1,-1,-1, + SC_DISSONANCE, + -1, + SC_WHISTLE, +/* 320- */ + SC_ASSNCROS, + SC_POEMBRAGI, + SC_APPLEIDUN, + -1,-1, + SC_UGLYDANCE, + -1, + SC_HUMMING, + SC_DONTFORGETME, + SC_FORTUNE, +/* 330- */ + SC_SERVICE4U, + SC_SELFDESTRUCTION, + -1,-1,-1,-1,-1,-1,-1,-1, +/* 340- */ + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1, +/* 350- */ + -1,-1,-1,-1,-1, + SC_AURABLADE, + SC_PARRYING, + SC_CONCENTRATION, + SC_TENSIONRELAX, + SC_BERSERK, +/* 360- */ + SC_BERSERK, + SC_ASSUMPTIO, + SC_BASILICA, + -1,-1,-1, + SC_MAGICPOWER, + -1,-1, + SC_GOSPEL, +/* 370- */ + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1, +/* 380- */ + SC_TRUESIGHT, + -1,-1, + SC_WINDWALK, + SC_MELTDOWN, + -1,-1, + SC_CARTBOOST, + -1, + SC_CHASEWALK, +/* 390- */ + SC_REJECTSWORD, + -1,-1,-1,-1,-1, + SC_MARIONETTE, + -1, + SC_HEADCRUSH, + SC_JOINTBEAT, +/* 400 */ + -1,-1, + SC_MINDBREAKER, + SC_MEMORIZE, + SC_FOGWALL, + SC_SPIDERWEB, + -1,-1,-1,-1, +/* 410- */ + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1, +}; + +struct skill_name_db skill_names[] = { + { AC_CHARGEARROW, "CHARGEARROW", "Charge_Arrow" } , + { AC_CONCENTRATION, "CONCENTRATION", "Improve_Concentration" } , + { AC_DOUBLE, "DOUBLE", "Double_Strafe" } , + { AC_MAKINGARROW, "MAKINGARROW", "Arrow_Creation" } , + { AC_OWL, "OWL", "Owl's_Eye" } , + { AC_SHOWER, "SHOWER", "Arrow_Shower" } , + { AC_VULTURE, "VULTURE", "Vulture's_Eye" } , + { ALL_RESURRECTION, "RESURRECTION", "Resurrection" } , + { AL_ANGELUS, "ANGELUS", "Angelus" } , + { AL_BLESSING, "BLESSING", "Blessing" } , + { AL_CRUCIS, "CRUCIS", "Signum_Crusis" } , + { AL_CURE, "CURE", "Cure" } , + { AL_DECAGI, "DECAGI", "Decrease_AGI" } , + { AL_DEMONBANE, "DEMONBANE", "Demon_Bane" } , + { AL_DP, "DP", "Divine_Protection" } , + { AL_HEAL, "HEAL", "Heal" } , + { AL_HOLYLIGHT, "HOLYLIGHT", "Holy_Light" } , + { AL_HOLYWATER, "HOLYWATER", "Aqua_Benedicta" } , + { AL_INCAGI, "INCAGI", "Increase_AGI" } , + { AL_PNEUMA, "PNEUMA", "Pneuma" } , + { AL_RUWACH, "RUWACH", "Ruwach" } , + { AL_TELEPORT, "TELEPORT", "Teleport" } , + { AL_WARP, "WARP", "Warp_Portal" } , + { AM_ACIDTERROR, "ACIDTERROR", "Acid_Terror" } , + { AM_AXEMASTERY, "AXEMASTERY", "Axe_Mastery" } , + { AM_BERSERKPITCHER, "BERSERKPITCHER", "Berserk Pitcher" } , + { AM_BIOETHICS, "BIOETHICS", "Bioethics" } , + { AM_BIOTECHNOLOGY, "BIOTECHNOLOGY", "Biotechnology" } , + { AM_CALLHOMUN, "CALLHOMUN", "Call_Homunculus" } , + { AM_CANNIBALIZE, "CANNIBALIZE", "Bio_Cannibalize" } , + { AM_CP_ARMOR, "ARMOR", "Chemical_Protection_Armor" } , + { AM_CP_HELM, "HELM", "Chemical_Protection_Helm" } , + { AM_CP_SHIELD, "SHIELD", "Chemical_Protection_Shield" } , + { AM_CP_WEAPON, "WEAPON", "Chemical_Protection_Weapon" } , + { AM_CREATECREATURE, "CREATECREATURE", "Life_Creation" } , + { AM_CULTIVATION, "CULTIVATION", "Cultivation" } , + { AM_DEMONSTRATION, "DEMONSTRATION", "Demonstration" } , + { AM_DRILLMASTER, "DRILLMASTER", "Drillmaster" } , + { AM_FLAMECONTROL, "FLAMECONTROL", "Flame_Control" } , + { AM_HEALHOMUN, "HEALHOMUN", "Heal_Homunculus" } , + { AM_LEARNINGPOTION, "LEARNINGPOTION", "AM_LEARNINGPOTION" } , + { AM_PHARMACY, "PHARMACY", "Pharmacy" } , + { AM_POTIONPITCHER, "POTIONPITCHER", "Potion_Pitcher" } , + { AM_REST, "REST", "Sabbath" } , + { AM_RESURRECTHOMUN, "RESURRECTHOMUN", "Ressurect_Homunculus" } , + { AM_SPHEREMINE, "SPHEREMINE", "Sphere_Mine" } , + { ASC_BREAKER, "BREAKER", "Breaker" } , + { ASC_CDP, "CDP", "Create_Deadly_Poison" } , + { ASC_EDP, "EDP", "Deadly_Poison_Enchantment" } , + { ASC_HALLUCINATION, "HALLUCINATION", "Hallucination_Walk" } , + { ASC_KATAR, "KATAR", "Advanced_Katar_Mastery" } , + { ASC_METEORASSAULT, "METEORASSAULT", "Meteor_Assault" } , + { AS_CLOAKING, "CLOAKING", "Cloaking" } , + { AS_ENCHANTPOISON, "ENCHANTPOISON", "Enchant_Poison" } , + { AS_GRIMTOOTH, "GRIMTOOTH", "Grimtooth" } , + { AS_KATAR, "KATAR", "Katar_Mastery" } , + { AS_LEFT, "LEFT", "Lefthand_Mastery" } , + { AS_POISONREACT, "POISONREACT", "Poison_React" } , + { AS_RIGHT, "RIGHT", "Righthand_Mastery" } , + { AS_SONICBLOW, "SONICBLOW", "Sonic_Blow" } , + { AS_SPLASHER, "SPLASHER", "Venom_Splasher" } , + { AS_VENOMDUST, "VENOMDUST", "Venom_Dust" } , + { BA_APPLEIDUN, "APPLEIDUN", "Apple_of_Idun" } , + { BA_ASSASSINCROSS, "ASSASSINCROSS", "Assassin_Cross" } , + { BA_DISSONANCE, "DISSONANCE", "Dissonance" } , + { BA_FROSTJOKE, "FROSTJOKE", "Dumb_Joke" } , + { BA_MUSICALLESSON, "MUSICALLESSON", "Musical_Lesson" } , + { BA_MUSICALSTRIKE, "MUSICALSTRIKE", "Musical_Strike" } , + { BA_POEMBRAGI, "POEMBRAGI", "Poem_of_Bragi" } , + { BA_WHISTLE, "WHISTLE", "Whistle" } , + { BD_ADAPTATION, "ADAPTATION", "Adaption" } , + { BD_DRUMBATTLEFIELD, "DRUMBATTLEFIELD", "Drumb_BattleField" } , + { BD_ENCORE, "ENCORE", "Encore" } , + { BD_ETERNALCHAOS, "ETERNALCHAOS", "Eternal_Chaos" } , + { BD_INTOABYSS, "INTOABYSS", "Into_the_Abyss" } , + { BD_LULLABY, "LULLABY", "Lullaby" } , + { BD_RAGNAROK, "RAGNAROK", "Ragnarok" } , + { BD_RICHMANKIM, "RICHMANKIM", "Rich_Mankim" } , + { BD_RINGNIBELUNGEN, "RINGNIBELUNGEN", "Ring_of_Nibelugen" } , + { BD_ROKISWEIL, "ROKISWEIL", "Loki's_Wail" } , + { BD_SIEGFRIED, "SIEGFRIED", "Invulnerable_Siegfried" } , + { BS_ADRENALINE, "ADRENALINE", "Adrenaline_Rush" } , + { BS_ADRENALINE2, "ADRENALINE2", "Adrenaline Rush 2" } , + { BS_AXE, "AXE", "Smith_Axe" } , + { BS_DAGGER, "DAGGER", "Smith_Dagger" } , + { BS_ENCHANTEDSTONE, "ENCHANTEDSTONE", "Enchantedstone_Craft" } , + { BS_FINDINGORE, "FINDINGORE", "Ore_Discovery" } , + { BS_HAMMERFALL, "HAMMERFALL", "Hammer_Fall" } , + { BS_HILTBINDING, "HILTBINDING", "Hilt_Binding" } , + { BS_IRON, "IRON", "Iron_Tempering" } , + { BS_KNUCKLE, "KNUCKLE", "Smith_Knucklebrace" } , + { BS_MACE, "MACE", "Smith_Mace" } , + { BS_MAXIMIZE, "MAXIMIZE", "Power_Maximize" } , + { BS_ORIDEOCON, "ORIDEOCON", "Orideocon_Research" } , + { BS_OVERTHRUST, "OVERTHRUST", "Power-Thrust" } , + { BS_REPAIRWEAPON, "REPAIRWEAPON", "Weapon_Repair" } , + { BS_SKINTEMPER, "SKINTEMPER", "Skin_Tempering" } , + { BS_SPEAR, "SPEAR", "Smith_Spear" } , + { BS_STEEL, "STEEL", "Steel_Tempering" } , + { BS_SWORD, "SWORD", "Smith_Sword" } , + { BS_TWOHANDSWORD, "TWOHANDSWORD", "Smith_Two-handed_Sword" } , + { BS_WEAPONPERFECT, "WEAPONPERFECT", "Weapon_Perfection" } , + { BS_WEAPONRESEARCH, "WEAPONRESEARCH", "Weaponry_Research" } , + { CG_ARROWVULCAN, "ARROWVULCAN", "Vulcan_Arrow" } , + { CG_MARIONETTE, "MARIONETTE", "Marionette_Control" } , + { CG_MOONLIT, "MOONLIT", "Moonlight_Petals" } , + { CH_CHAINCRUSH, "CHAINCRUSH", "Chain_Crush_Combo" } , + { CH_PALMSTRIKE, "PALMSTRIKE", "Palm_Push_Strike" } , + { CH_SOULCOLLECT, "SOULCOLLECT", "Collect_Soul" } , + { CH_TIGERFIST, "TIGERFIST", "Tiger_Knuckle_Fist" } , + { CR_ALCHEMY, "ALCHEMY", "Alchemy" } , + { CR_AUTOGUARD, "AUTOGUARD", "Guard" } , + { CR_DEFENDER, "DEFENDER", "Defender" } , + { CR_DEVOTION, "DEVOTION", "Sacrifice" } , + { CR_GRANDCROSS, "GRANDCROSS", "Grand_Cross" } , + { CR_HOLYCROSS, "HOLYCROSS", "Holy_Cross" } , + { CR_PROVIDENCE, "PROVIDENCE", "Providence" } , + { CR_REFLECTSHIELD, "REFLECTSHIELD", "Shield_Reflect" } , + { CR_SHIELDBOOMERANG, "SHIELDBOOMERANG", "Shield_Boomerang" } , + { CR_SHIELDCHARGE, "SHIELDCHARGE", "Shield_Charge" } , + { CR_SPEARQUICKEN, "SPEARQUICKEN", "Spear_Quicken" } , + { CR_SYNTHESISPOTION, "SYNTHESISPOTION", "Potion_Synthesis" } , + { CR_TRUST, "TRUST", "Faith" } , + { DC_DANCINGLESSON, "DANCINGLESSON", "Dancing_Lesson" } , + { DC_DONTFORGETME, "DONTFORGETME", "Don't_Forget_Me" } , + { DC_FORTUNEKISS, "FORTUNEKISS", "Fortune_Kiss" } , + { DC_HUMMING, "HUMMING", "Humming" } , + { DC_SCREAM, "SCREAM", "Scream" } , + { DC_SERVICEFORYOU, "SERVICEFORYOU", "Prostitute" } , + { DC_THROWARROW, "THROWARROW", "Throw_Arrow" } , + { DC_UGLYDANCE, "UGLYDANCE", "Ugly_Dance" } , + { HP_ASSUMPTIO, "ASSUMPTIO", "Assumptio" } , + { HP_BASILICA, "BASILICA", "Basilica" } , + { HP_MEDITATIO, "MEDITATIO", "Meditation" } , + { HT_ANKLESNARE, "ANKLESNARE", "Ankle_Snare" } , + { HT_BEASTBANE, "BEASTBANE", "Beast_Bane" } , + { HT_BLASTMINE, "BLASTMINE", "Blast_Mine" } , + { HT_BLITZBEAT, "BLITZBEAT", "Blitz_Beat" } , + { HT_CLAYMORETRAP, "CLAYMORETRAP", "Claymore_Trap" } , + { HT_DETECTING, "DETECTING", "Detect" } , + { HT_FALCON, "FALCON", "Falconry_Mastery" } , + { HT_FLASHER, "FLASHER", "Flasher" } , + { HT_FREEZINGTRAP, "FREEZINGTRAP", "Freezing_Trap" } , + { HT_LANDMINE, "LANDMINE", "Land_Mine" } , + { HT_REMOVETRAP, "REMOVETRAP", "Remove_Trap" } , + { HT_SANDMAN, "SANDMAN", "Sandman" } , + { HT_SHOCKWAVE, "SHOCKWAVE", "Shockwave_Trap" } , + { HT_SKIDTRAP, "SKIDTRAP", "Skid_Trap" } , + { HT_SPRINGTRAP, "SPRINGTRAP", "Spring_Trap" } , + { HT_STEELCROW, "STEELCROW", "Steel_Crow" } , + { HT_TALKIEBOX, "TALKIEBOX", "Talkie_Box" } , + { HW_MAGICCRASHER, "MAGICCRASHER", "Magic_Crasher" } , + { HW_MAGICPOWER, "MAGICPOWER", "Magic_Power" } , + { HW_NAPALMVULCAN, "NAPALMVULCAN", "Napalm_Vulcan" } , + { HW_SOULDRAIN, "SOULDRAIN", "Soul_Drain" } , + { KN_AUTOCOUNTER, "AUTOCOUNTER", "Counter_Attack" } , + { KN_BOWLINGBASH, "BOWLINGBASH", "Bowling_Bash" } , + { KN_BRANDISHSPEAR, "BRANDISHSPEAR", "Brandish_Spear" } , + { KN_CAVALIERMASTERY, "CAVALIERMASTERY", "Cavalier_Mastery" } , + { KN_PIERCE, "PIERCE", "Pierce" } , + { KN_RIDING, "RIDING", "Peco_Peco_Ride" } , + { KN_SPEARBOOMERANG, "SPEARBOOMERANG", "Spear_Boomerang" } , + { KN_SPEARMASTERY, "SPEARMASTERY", "Spear_Mastery" } , + { KN_SPEARSTAB, "SPEARSTAB", "Spear_Stab" } , + { KN_TWOHANDQUICKEN, "TWOHANDQUICKEN", "Twohand_Quicken" } , + { LK_AURABLADE, "AURABLADE", "Aura_Blade" } , + { LK_BERSERK, "BERSERK", "Berserk" } , + { LK_CONCENTRATION, "CONCENTRATION", "Concentration" } , + { LK_FURY, "FURY", "LK_FURY" } , + { LK_HEADCRUSH, "HEADCRUSH", "Head_Crusher" } , + { LK_JOINTBEAT, "JOINTBEAT", "Joint_Beat" } , + { LK_PARRYING, "PARRYING", "Parrying" } , + { LK_SPIRALPIERCE, "SPIRALPIERCE", "Spiral_Pierce" } , + { LK_TENSIONRELAX, "TENSIONRELAX", "Tension_Relax" } , + { MC_CARTREVOLUTION, "CARTREVOLUTION", "Cart_Revolution" } , + { MC_CHANGECART, "CHANGECART", "Change_Cart" } , + { MC_DISCOUNT, "DISCOUNT", "Discount" } , + { MC_IDENTIFY, "IDENTIFY", "Item_Appraisal" } , + { MC_INCCARRY, "INCCARRY", "Enlarge_Weight_Limit" } , + { MC_LOUD, "LOUD", "Lord_Exclamation" } , + { MC_MAMMONITE, "MAMMONITE", "Mammonite" } , + { MC_OVERCHARGE, "OVERCHARGE", "Overcharge" } , + { MC_PUSHCART, "PUSHCART", "Pushcart" } , + { MC_VENDING, "VENDING", "Vending" } , + { MG_COLDBOLT, "COLDBOLT", "Cold_Bolt" } , + { MG_ENERGYCOAT, "ENERGYCOAT", "Energy_Coat" } , + { MG_FIREBALL, "FIREBALL", "Fire_Ball" } , + { MG_FIREBOLT, "FIREBOLT", "Fire_Bolt" } , + { MG_FIREWALL, "FIREWALL", "Fire_Wall" } , + { MG_FROSTDIVER, "FROSTDIVER", "Frost_Diver" } , + { MG_LIGHTNINGBOLT, "LIGHTNINGBOLT", "Lightening_Bolt" } , + { MG_NAPALMBEAT, "NAPALMBEAT", "Napalm_Beat" } , + { MG_SAFETYWALL, "SAFETYWALL", "Safety_Wall" } , + { MG_SIGHT, "SIGHT", "Sight" } , + { MG_SOULSTRIKE, "SOULSTRIKE", "Soul_Strike" } , + { MG_SRECOVERY, "SRECOVERY", "Increase_SP_Recovery" } , + { MG_STONECURSE, "STONECURSE", "Stone_Curse" } , + { MG_THUNDERSTORM, "THUNDERSTORM", "Thunderstorm" } , + { MO_ABSORBSPIRITS, "ABSORBSPIRITS", "Absorb_Spirits" } , + { MO_BLADESTOP, "BLADESTOP", "Blade_Stop" } , + { MO_BODYRELOCATION, "BODYRELOCATION", "Body_Relocation" } , + { MO_CALLSPIRITS, "CALLSPIRITS", "Call_Spirits" } , + { MO_CHAINCOMBO, "CHAINCOMBO", "Chain_Combo" } , + { MO_COMBOFINISH, "COMBOFINISH", "Combo_Finish" } , + { MO_DODGE, "DODGE", "Dodge" } , + { MO_EXPLOSIONSPIRITS, "EXPLOSIONSPIRITS", "Explosion_Spirits" } , + { MO_EXTREMITYFIST, "EXTREMITYFIST", "Extremity_Fist" } , + { MO_FINGEROFFENSIVE, "FINGEROFFENSIVE", "Finger_Offensive" } , + { MO_INVESTIGATE, "INVESTIGATE", "Investigate" } , + { MO_IRONHAND, "IRONHAND", "Iron_Hand" } , + { MO_SPIRITSRECOVERY, "SPIRITSRECOVERY", "Spirit_Recovery" } , + { MO_STEELBODY, "STEELBODY", "Steel_Body" } , + { MO_TRIPLEATTACK, "TRIPLEATTACK", "Triple_Blows" } , + { NPC_ATTRICHANGE, "ATTRICHANGE", "NPC_ATTRICHANGE" } , + { NPC_BARRIER, "BARRIER", "NPC_BARRIER" } , + { NPC_BLINDATTACK, "BLINDATTACK", "NPC_BLINDATTACK" } , + { NPC_BLOODDRAIN, "BLOODDRAIN", "NPC_BLOODDRAIN" } , + { NPC_CHANGEDARKNESS, "CHANGEDARKNESS", "NPC_CHANGEDARKNESS" } , + { NPC_CHANGEFIRE, "CHANGEFIRE", "NPC_CHANGEFIRE" } , + { NPC_CHANGEGROUND, "CHANGEGROUND", "NPC_CHANGEGROUND" } , + { NPC_CHANGEHOLY, "CHANGEHOLY", "NPC_CHANGEHOLY" } , + { NPC_CHANGEPOISON, "CHANGEPOISON", "NPC_CHANGEPOISON" } , + { NPC_CHANGETELEKINESIS, "CHANGETELEKINESIS", "NPC_CHANGETELEKINESIS" } , + { NPC_CHANGEWATER, "CHANGEWATER", "NPC_CHANGEWATER" } , + { NPC_CHANGEWIND, "CHANGEWIND", "NPC_CHANGEWIND" } , + { NPC_COMBOATTACK, "COMBOATTACK", "NPC_COMBOATTACK" } , + { NPC_CRITICALSLASH, "CRITICALSLASH", "NPC_CRITICALSLASH" } , + { NPC_CURSEATTACK, "CURSEATTACK", "NPC_CURSEATTACK" } , + { NPC_DARKBLESSING, "DARKBLESSING", "NPC_DARKBLESSING" } , + { NPC_DARKBREATH, "DARKBREATH", "NPC_DARKBREATH" } , + { NPC_DARKCROSS, "DARKCROSS", "NPC_DARKCROSS" } , + { NPC_DARKNESSATTACK, "DARKNESSATTACK", "NPC_DARKNESSATTACK" } , + { NPC_DEFENDER, "DEFENDER", "NPC_DEFENDER" } , + { NPC_EMOTION, "EMOTION", "NPC_EMOTION" } , + { NPC_ENERGYDRAIN, "ENERGYDRAIN", "NPC_ENERGYDRAIN" } , + { NPC_FIREATTACK, "FIREATTACK", "NPC_FIREATTACK" } , + { NPC_GROUNDATTACK, "GROUNDATTACK", "NPC_GROUNDATTACK" } , + { NPC_GUIDEDATTACK, "GUIDEDATTACK", "NPC_GUIDEDATTACK" } , + { NPC_HALLUCINATION, "HALLUCINATION", "NPC_HALLUCINATION" } , + { NPC_HOLYATTACK, "HOLYATTACK", "NPC_HOLYATTACK" } , + { NPC_KEEPING, "KEEPING", "NPC_KEEPING" } , + { NPC_LICK, "LICK", "NPC_LICK" } , + { NPC_MAGICALATTACK, "MAGICALATTACK", "NPC_MAGICALATTACK" } , + { NPC_MENTALBREAKER, "MENTALBREAKER", "NPC_MENTALBREAKER" } , + { NPC_METAMORPHOSIS, "METAMORPHOSIS", "NPC_METAMORPHOSIS" } , + { NPC_PETRIFYATTACK, "PETRIFYATTACK", "NPC_PETRIFYATTACK" } , + { NPC_PIERCINGATT, "PIERCINGATT", "NPC_PIERCINGATT" } , + { NPC_POISON, "POISON", "NPC_POISON" } , + { NPC_POISONATTACK, "POISONATTACK", "NPC_POISONATTACK" } , + { NPC_PROVOCATION, "PROVOCATION", "NPC_PROVOCATION" } , + { NPC_RANDOMATTACK, "RANDOMATTACK", "NPC_RANDOMATTACK" } , + { NPC_RANGEATTACK, "RANGEATTACK", "NPC_RANGEATTACK" } , + { NPC_REBIRTH, "REBIRTH", "NPC_REBIRTH" } , + { NPC_SELFDESTRUCTION, "SELFDESTRUCTION", "Kabooooom!" } , + { NPC_SELFDESTRUCTION2, "SELFDESTRUCTION2", "NPC_SELFDESTRUCTION2" } , + { NPC_SILENCEATTACK, "SILENCEATTACK", "NPC_SILENCEATTACK" } , + { NPC_SLEEPATTACK, "SLEEPATTACK", "NPC_SLEEPATTACK" } , + { NPC_SMOKING, "SMOKING", "NPC_SMOKING" } , + { NPC_SPLASHATTACK, "SPLASHATTACK", "NPC_SPLASHATTACK" } , + { NPC_STUNATTACK, "STUNATTACK", "NPC_STUNATTACK" } , + { NPC_SUICIDE, "SUICIDE", "NPC_SUICIDE" } , + { NPC_SUMMONMONSTER, "SUMMONMONSTER", "NPC_SUMMONMONSTER" } , + { NPC_SUMMONSLAVE, "SUMMONSLAVE", "NPC_SUMMONSLAVE" } , + { NPC_TELEKINESISATTACK, "TELEKINESISATTACK", "NPC_TELEKINESISATTACK" } , + { NPC_TRANSFORMATION, "TRANSFORMATION", "NPC_TRANSFORMATION" } , + { NPC_WATERATTACK, "WATERATTACK", "NPC_WATERATTACK" } , + { NPC_WINDATTACK, "WINDATTACK", "NPC_WINDATTACK" } , + { NV_BASIC, "BASIC", "Basic_Skill" } , + { NV_FIRSTAID, "FIRSTAID", "First Aid" } , + { NV_TRICKDEAD, "TRICKDEAD", "Play_Dead" } , + { PA_GOSPEL, "GOSPEL", "Gospel" } , + { PA_PRESSURE, "PRESSURE", "Pressure" } , + { PA_SACRIFICE, "SACRIFICE", "Sacrificial_Ritual" } , + { PF_FOGWALL, "FOGWALL", "Wall_of_Fog" } , + { PF_HPCONVERSION, "HPCONVERSION", "Health_Conversion" } , + { PF_MEMORIZE, "MEMORIZE", "Memorize" } , + { PF_MINDBREAKER, "MINDBREAKER", "Mind_Breaker" } , + { PF_SOULBURN, "SOULBURN", "Soul_Burn" } , + { PF_SOULCHANGE, "SOULCHANGE", "Soul_Change" } , + { PF_SPIDERWEB, "SPIDERWEB", "Spider_Web" } , + { PR_ASPERSIO, "ASPERSIO", "Aspersio" } , + { PR_BENEDICTIO, "BENEDICTIO", "B.S_Sacramenti" } , + { PR_GLORIA, "GLORIA", "Gloria" } , + { PR_IMPOSITIO, "IMPOSITIO", "Impositio_Manus" } , + { PR_KYRIE, "KYRIE", "Kyrie_Eleison" } , + { PR_LEXAETERNA, "LEXAETERNA", "Lex_Aeterna" } , + { PR_LEXDIVINA, "LEXDIVINA", "Lex_Divina" } , + { PR_MACEMASTERY, "MACEMASTERY", "Mace_Mastery" } , + { PR_MAGNIFICAT, "MAGNIFICAT", "Magnificat" } , + { PR_MAGNUS, "MAGNUS", "Magnus_Exorcismus" } , + { PR_SANCTUARY, "SANCTUARY", "Santuary" } , + { PR_SLOWPOISON, "SLOWPOISON", "Slow_Poison" } , + { PR_STRECOVERY, "STRECOVERY", "Status_Recovery" } , + { PR_SUFFRAGIUM, "SUFFRAGIUM", "Suffragium" } , + { PR_TURNUNDEAD, "TURNUNDEAD", "Turn_Undead" } , + { RG_BACKSTAP, "BACKSTAP", "Back_Stab" } , + { RG_CLEANER, "CLEANER", "Remover" } , + { RG_COMPULSION, "COMPULSION", "Compulsion_Discount" } , + { RG_FLAGGRAFFITI, "FLAGGRAFFITI", "Flag_Graffity" } , + { RG_GANGSTER, "GANGSTER", "Gangster's_Paradise" } , + { RG_GRAFFITI, "GRAFFITI", "Graffiti" } , + { RG_INTIMIDATE, "INTIMIDATE", "Intimidate" } , + { RG_PLAGIARISM, "PLAGIARISM", "Plagiarism" } , + { RG_RAID, "RAID", "Raid" } , + { RG_SNATCHER, "SNATCHER", "Snatcher" } , + { RG_STEALCOIN, "STEALCOIN", "Steal_Coin" } , + { RG_STRIPARMOR, "STRIPARMOR", "Strip_Armor" } , + { RG_STRIPHELM, "STRIPHELM", "Strip_Helm" } , + { RG_STRIPSHIELD, "STRIPSHIELD", "Strip_Shield" } , + { RG_STRIPWEAPON, "STRIPWEAPON", "Strip_Weapon" } , + { RG_TUNNELDRIVE, "TUNNELDRIVE", "Tunnel_Drive" } , + { SA_ABRACADABRA, "ABRACADABRA", "Hocus-pocus" } , + { SA_ADVANCEDBOOK, "ADVANCEDBOOK", "Advanced_Book" } , + { SA_AUTOSPELL, "AUTOSPELL", "Auto_Cast" } , + { SA_CASTCANCEL, "CASTCANCEL", "Cast_Cancel" } , + { SA_CLASSCHANGE, "CLASSCHANGE", "Class_Change" } , + { SA_COMA, "COMA", "Coma" } , + { SA_DEATH, "DEATH", "Death" } , + { SA_DELUGE, "DELUGE", "Deluge" } , + { SA_DISPELL, "DISPELL", "Dispel" } , + { SA_DRAGONOLOGY, "DRAGONOLOGY", "Dragonology" } , + { SA_FLAMELAUNCHER, "FLAMELAUNCHER", "Flame_Launcher" } , + { SA_FORTUNE, "FORTUNE", "Fortune" } , + { SA_FREECAST, "FREECAST", "Cast_Freedom" } , + { SA_FROSTWEAPON, "FROSTWEAPON", "Frost_Weapon" } , + { SA_FULLRECOVERY, "FULLRECOVERY", "Full_Recovery" } , + { SA_GRAVITY, "GRAVITY", "Gravity" } , + { SA_INSTANTDEATH, "INSTANTDEATH", "Instant_Death" } , + { SA_LANDPROTECTOR, "LANDPROTECTOR", "Land_Protector" } , + { SA_LEVELUP, "LEVELUP", "Level_Up" } , + { SA_LIGHTNINGLOADER, "LIGHTNINGLOADER", "Lightning_Loader" } , + { SA_MAGICROD, "MAGICROD", "Magic_Rod" } , + { SA_MONOCELL, "MONOCELL", "Monocell" } , + { SA_QUESTION, "QUESTION", "Question?" } , + { SA_REVERSEORCISH, "REVERSEORCISH", "Reverse_Orcish" } , + { SA_SEISMICWEAPON, "SEISMICWEAPON", "Seismic_Weapon" } , + { SA_SPELLBREAKER, "SPELLBREAKER", "Break_Spell" } , + { SA_SUMMONMONSTER, "SUMMONMONSTER", "Summon_Monster" } , + { SA_TAMINGMONSTER, "TAMINGMONSTER", "Taming_Monster" } , + { SA_VIOLENTGALE, "VIOLENTGALE", "Violent_Gale" } , + { SA_VOLCANO, "VOLCANO", "Volcano" } , + { SG_DEVIL, "DEVIL", "Devil" } , + { SG_FEEL, "FEEL", "Feel" } , + { SG_FRIEND, "FRIEND", "Friend" } , + { SG_FUSION, "FUSION", "Fusion" } , + { SG_HATE, "HATE", "Hate" } , + { SG_KNOWLEDGE, "KNOWLEDGE", "Knowledge" } , + { SG_MOON_ANGER, "ANGER", "Moon Anger" } , + { SG_MOON_BLESS, "BLESS", "Moon Bless" } , + { SG_MOON_COMFORT, "COMFORT", "Moon Comfort" } , + { SG_MOON_WARM, "WARM", "Moon Warm" } , + { SG_STAR_ANGER, "ANGER", "Star Anger" } , + { SG_STAR_BLESS, "BLESS", "Star Bless" } , + { SG_STAR_COMFORT, "COMFORT", "Star Comfort" } , + { SG_STAR_WARM, "WARM", "Star Warm" } , + { SG_SUN_ANGER, "ANGER", "Sun Anger" } , + { SG_SUN_BLESS, "BLESS", "Sun Bless" } , + { SG_SUN_COMFORT, "COMFORT", "Sun Comfort" } , + { SG_SUN_WARM, "WARM", "Sun Warm" } , + { SL_ALCHEMIST, "ALCHEMIST", "Alchemist" } , + { SL_ASSASIN, "ASSASIN", "Assasin" } , + { SL_BARDDANCER, "BARDDANCER", "Bard Dancer" } , + { SL_BLACKSMITH, "BLACKSMITH", "Black Smith" } , + { SL_CRUSADER, "CRUSADER", "Crusader" } , + { SL_HUNTER, "HUNTER", "Hunter" } , + { SL_KAAHI, "KAAHI", "Kaahi" } , + { SL_KAINA, "KAINA", "Kaina" } , + { SL_KAITE, "KAITE", "Kaite" } , + { SL_KAIZEL, "KAIZEL", "Kaizel" } , + { SL_KAUPE, "KAUPE", "Kaupe" } , + { SL_KNIGHT, "KNIGHT", "Knight" } , + { SL_MONK, "MONK", "Monk" } , + { SL_PRIEST, "PRIEST", "Priest" } , + { SL_ROGUE, "ROGUE", "Rogue" } , + { SL_SAGE, "SAGE", "Sage" } , + { SL_SKA, "SKA", "SKA" } , + { SL_SKE, "SKE", "SKE" } , + { SL_SMA, "SMA", "SMA" } , + { SL_SOULLINKER, "SOULLINKER", "Soul Linker" } , + { SL_STAR, "STAR", "Star" } , + { SL_STIN, "STIN", "Stin" } , + { SL_STUN, "STUN", "Stun" } , + { SL_SUPERNOVICE, "SUPERNOVICE", "Super Novice" } , + { SL_SWOO, "SWOO", "Swoo" } , + { SL_WIZARD, "WIZARD", "Wizard" } , + { SM_AUTOBERSERK, "AUTOBERSERK", "Auto_Berserk" } , + { SM_BASH, "BASH", "Bash" } , + { SM_ENDURE, "ENDURE", "Endure" } , + { SM_FATALBLOW, "FATALBLOW", "Attack_Weak_Point" } , + { SM_MAGNUM, "MAGNUM", "Magnum_Break" } , + { SM_MOVINGRECOVERY, "MOVINGRECOVERY", "Moving_HP_Recovery" } , + { SM_PROVOKE, "PROVOKE", "Provoke" } , + { SM_RECOVERY, "RECOVERY", "Increase_HP_Recovery" } , + { SM_SWORD, "SWORD", "Sword_Mastery" } , + { SM_TWOHAND, "TWOHAND", "Two-Handed_Sword_Mastery" } , + { SN_FALCONASSAULT, "FALCONASSAULT", "Falcon_Assault" } , + { SN_SHARPSHOOTING, "SHARPSHOOTING", "Sharpshooting" } , + { SN_SIGHT, "SIGHT", "True_Sight" } , + { SN_WINDWALK, "WINDWALK", "Wind_Walk" } , + { ST_CHASEWALK, "CHASEWALK", "Chase_Walk" } , + { ST_REJECTSWORD, "REJECTSWORD", "Reject_Sword" } , + { ST_STEALBACKPACK, "STEALBACKPACK", "Steal_Backpack" } , + { TF_BACKSLIDING, "BACKSLIDING", "Back_Sliding" } , + { TF_DETOXIFY, "DETOXIFY", "Detoxify" } , + { TF_DOUBLE, "DOUBLE", "Double_Attack" } , + { TF_HIDING, "HIDING", "Hiding" } , + { TF_MISS, "MISS", "Improve_Dodge" } , + { TF_PICKSTONE, "PICKSTONE", "Take_Stone" } , + { TF_POISON, "POISON", "Envenom" } , + { TF_SPRINKLESAND, "SPRINKLESAND", "Throw_Sand" } , + { TF_STEAL, "STEAL", "Steal" } , + { TF_THROWSTONE, "THROWSTONE", "Throw_Stone" } , + { TK_COUNTER, "COUNTER", "Counter" } , + { TK_DODGE, "DODGE", "Dodge" } , + { TK_DOWNKICK, "DOWNKICK", "Down Kick" } , + { TK_HIGHJUMP, "HIGHJUMP", "High Jump" } , + { TK_HPTIME, "HPTIME", "HP Time" } , + { TK_JUMPKICK, "JUMPKICK", "Jump Kick" } , + { TK_POWER, "POWER", "Power" } , + { TK_READYCOUNTER, "READYCOUNTER", "Ready Counter" } , + { TK_READYDOWN, "READYDOWN", "Ready Down" } , + { TK_READYSTORM, "READYSTORM", "Ready Storm" } , + { TK_READYTURN, "READYTURN", "Ready Turn" } , + { TK_RUN, "RUN", "TK_RUN" } , + { TK_SEVENWIND, "SEVENWIND", "Seven Wind" } , + { TK_SPTIME, "SPTIME", "SP Time" } , + { TK_STORMKICK, "STORMKICK", "Storm Kick" } , + { TK_TURNKICK, "TURNKICK", "Turn Kick" } , + { WE_BABY, "BABY", "Adopt_Baby" } , + { WE_CALLBABY, "CALLBABY", "Call_Baby" } , + { WE_CALLPARENT, "CALLPARENT", "Call_Parent" } , + { WE_CALLPARTNER, "CALLPARTNER", "I Want to See You" } , + { WE_FEMALE, "FEMALE", "I Only Look Up to You" } , + { WE_MALE, "MALE", "I Will Protect You" } , + { WS_CARTBOOST, "CARTBOOST", "Cart_Boost" } , + { WS_CREATECOIN, "CREATECOIN", "Create_Coins" } , + { WS_CREATENUGGET, "CREATENUGGET", "Create_Nuggets" } , + { WS_MELTDOWN, "MELTDOWN", "Meltdown" } , + { WS_SYSTEMCREATE, "SYSTEMCREATE", "Create_System_tower" } , + { WZ_EARTHSPIKE, "EARTHSPIKE", "Earth_Spike" } , + { WZ_ESTIMATION, "ESTIMATION", "Sense" } , + { WZ_FIREIVY, "FIREIVY", "Fire_Ivy" } , + { WZ_FIREPILLAR, "FIREPILLAR", "Fire_Pillar" } , + { WZ_FROSTNOVA, "FROSTNOVA", "Frost_Nova" } , + { WZ_HEAVENDRIVE, "HEAVENDRIVE", "Heaven's_Drive" } , + { WZ_ICEWALL, "ICEWALL", "Ice_Wall" } , + { WZ_JUPITEL, "JUPITEL", "Jupitel_Thunder" } , + { WZ_METEOR, "METEOR", "Meteor_Storm" } , + { WZ_QUAGMIRE, "QUAGMIRE", "Quagmire" } , + { WZ_SIGHTRASHER, "SIGHTRASHER", "Sightrasher" } , + { WZ_STORMGUST, "STORMGUST", "Storm_Gust" } , + { WZ_VERMILION, "VERMILION", "Lord_of_Vermilion" } , + { WZ_WATERBALL, "WATERBALL", "Water_Ball" } , + { 0, 0, 0 } +}; + +static const int dirx[8]={0,-1,-1,-1,0,1,1,1}; +static const int diry[8]={1,1,0,-1,-1,-1,0,1}; + +static int rdamage; + +/* スキルデータベース */ +struct skill_db skill_db[MAX_SKILL_DB]; + +/* アイテム作成データベース */ +struct skill_produce_db skill_produce_db[MAX_SKILL_PRODUCE_DB]; + +/* 矢作成スキルデータベース */ +struct skill_arrow_db skill_arrow_db[MAX_SKILL_ARROW_DB]; + +/* アブラカダブラ発動スキルデータベース */ +struct skill_abra_db skill_abra_db[MAX_SKILL_ABRA_DB]; + +int skill_get_hit( int id ){ return skill_db[id].hit; } +int skill_get_inf( int id ){ return skill_db[id].inf; } +int skill_get_pl( int id ){ return skill_db[id].pl; } +int skill_get_nk( int id ){ return skill_db[id].nk; } +int skill_get_max( int id ){ return skill_db[id].max; } +int skill_get_range( int id , int lv ){ return (lv <= 0) ? 0:skill_db[id].range[lv-1]; } +int skill_get_hp( int id ,int lv ){ return (lv <= 0) ? 0:skill_db[id].hp[lv-1]; } +int skill_get_sp( int id ,int lv ){ return (lv <= 0) ? 0:skill_db[id].sp[lv-1]; } +int skill_get_zeny( int id ,int lv ){ return (lv <= 0) ? 0:skill_db[id].zeny[lv-1]; } +int skill_get_num( int id ,int lv ){ return (lv <= 0) ? 0:skill_db[id].num[lv-1]; } +int skill_get_cast( int id ,int lv ){ return (lv <= 0) ? 0:skill_db[id].cast[lv-1]; } +int skill_get_delay( int id ,int lv ){ return (lv <= 0) ? 0:skill_db[id].delay[lv-1]; } +int skill_get_time( int id ,int lv ){ return (lv <= 0) ? 0:skill_db[id].upkeep_time[lv-1]; } +int skill_get_time2( int id ,int lv ){ return (lv <= 0) ? 0:skill_db[id].upkeep_time2[lv-1]; } +int skill_get_castdef( int id ){ return skill_db[id].cast_def_rate; } +int skill_get_weapontype( int id ){ return skill_db[id].weapon; } +int skill_get_inf2( int id ){ return skill_db[id].inf2; } +int skill_get_maxcount( int id ){ return skill_db[id].maxcount; } +int skill_get_blewcount( int id ,int lv ){ return (lv <= 0) ? 0:skill_db[id].blewcount[lv-1]; } +int skill_get_mhp( int id ,int lv ){ return (lv <= 0) ? 0:skill_db[id].mhp[lv-1]; } +int skill_get_castnodex( int id ,int lv ){ return (lv <= 0) ? 0:skill_db[id].castnodex[lv-1]; } + +/* プロトタイプ */ +struct skill_unit_group *skill_unitsetting( struct block_list *src, int skillid,int skilllv,int x,int y,int flag); +int skill_check_condition( struct map_session_data *sd,int type); +int skill_castend_damage_id( struct block_list* src, struct block_list *bl,int skillid,int skilllv,unsigned int tick,int flag ); +int skill_frostjoke_scream(struct block_list *bl,va_list ap); +int skill_status_change_timer_sub(struct block_list *bl, va_list ap ); +int skill_attack_area(struct block_list *bl,va_list ap); +int skill_abra_dataset(int skilllv); +int skill_clear_element_field(struct block_list *bl); +int skill_landprotector(struct block_list *bl, va_list ap ); +int skill_trap_splash(struct block_list *bl, va_list ap ); +int skill_count_target(struct block_list *bl, va_list ap ); + +// [MouseJstr] - skill ok to cast? and when? +static int skillnotok(int skillid, struct map_session_data *sd) { + if (sd == 0) + return 0; + if (pc_isGM(sd) >= 20) + return 0; // gm's can do anything damn thing they want + switch (skillid) { + case AL_WARP: + case AL_TELEPORT: + case MC_VENDING: + case MC_IDENTIFY: + return 0; // always allowed + default: + return(map[sd->bl.m].flag.noskill); + } +} + + +static int distance(int x0,int y0,int x1,int y1) +{ + int dx,dy; + + dx=abs(x0-x1); + dy=abs(y0-y1); + return dx>dy ? dx : dy; +} + +/* スキルユニットIDを返す(これもデータベースに入れたいな) */ +int skill_get_unit_id(int id,int flag) +{ + + switch(id){ + case MG_SAFETYWALL: return 0x7e; /* セイフティウォール */ + case MG_FIREWALL: return 0x7f; /* ファイアーウォール */ + case AL_WARP: return (flag==0)?0x81:0x80; /* ワープポータル */ + case PR_BENEDICTIO: return 0x82; /* 聖体降福 */ + case PR_SANCTUARY: return 0x83; /* サンクチュアリ */ + case PR_MAGNUS: return 0x84; /* マグヌスエクソシズム */ + case AL_PNEUMA: return 0x85; /* ニューマ */ + case MG_THUNDERSTORM: return 0x86; /* サンダーストーム */ + case WZ_HEAVENDRIVE: return 0x86; /* ヘヴンズドライブ */ + case WZ_SIGHTRASHER: return 0x86; /* サイトラッシャー */ + case WZ_METEOR: return 0x86; /* メテオストーム */ + case WZ_VERMILION: return 0x86; /* ロードオブヴァーミリオン */ + case WZ_FROSTNOVA: return 0x86; /* フロストノヴァ */ + case WZ_STORMGUST: return 0x86; /* ストームガスト(とりあえずLoVと同じで処理) */ + case CR_GRANDCROSS: return 0x86; /* グランドクロス */ + case WZ_FIREPILLAR: return (flag==0)?0x87:0x88; /* ファイアーピラー */ + case HT_TALKIEBOX: return 0x99; /* トーキーボックス */ + case WZ_ICEWALL: return 0x8d; /* アイスウォール */ + case WZ_QUAGMIRE: return 0x8e; /* クァグマイア */ + case HT_BLASTMINE: return 0x8f; /* ブラストマイン */ + case HT_SKIDTRAP: return 0x90; /* スキッドトラップ */ + case HT_ANKLESNARE: return 0x91; /* アンクルスネア */ + case AS_VENOMDUST: return 0x92; /* ベノムダスト */ + case HT_LANDMINE: return 0x93; /* ランドマイン */ + case HT_SHOCKWAVE: return 0x94; /* ショックウェーブトラップ */ + case HT_SANDMAN: return 0x95; /* サンドマン */ + case HT_FLASHER: return 0x96; /* フラッシャー */ + case HT_FREEZINGTRAP: return 0x97; /* フリージングトラップ */ + case HT_CLAYMORETRAP: return 0x98; /* クレイモアートラップ */ + case SA_VOLCANO: return 0x9a; /* ボルケーノ */ + case SA_DELUGE: return 0x9b; /* デリュージ */ + case SA_VIOLENTGALE: return 0x9c; /* バイオレントゲイル */ + case SA_LANDPROTECTOR: return 0x9d; /* ランドプロテクター */ + case BD_LULLABY: return 0x9e; /* 子守歌 */ + case BD_RICHMANKIM: return 0x9f; /* ニヨルドの宴 */ + case BD_ETERNALCHAOS: return 0xa0; /* 永遠の混沌 */ + case BD_DRUMBATTLEFIELD:return 0xa1; /* 戦太鼓の響き */ + case BD_RINGNIBELUNGEN: return 0xa2; /* ニーベルングの指輪 */ + case BD_ROKISWEIL: return 0xa3; /* ロキの叫び */ + case BD_INTOABYSS: return 0xa4; /* 深淵の中に */ + case BD_SIEGFRIED: return 0xa5; /* 不死身のジークフリード */ + case BA_DISSONANCE: return 0xa6; /* 不協和音 */ + case BA_WHISTLE: return 0xa7; /* 口笛 */ + case BA_ASSASSINCROSS: return 0xa8; /* 夕陽のアサシンクロス */ + case BA_POEMBRAGI: return 0xa9; /* ブラギの詩 */ + case BA_APPLEIDUN: return 0xaa; /* イドゥンの林檎 */ + case DC_UGLYDANCE: return 0xab; /* 自分勝手なダンス */ + case DC_HUMMING: return 0xac; /* ハミング */ + case DC_DONTFORGETME: return 0xad; /* 私を忘れないで… */ + case DC_FORTUNEKISS: return 0xae; /* 幸運のキス */ + case DC_SERVICEFORYOU: return 0xaf; /* サービスフォーユー */ + case RG_GRAFFITI: return 0xb0; /* グラフィティ */ + case AM_DEMONSTRATION: return 0xb1; /* デモンストレーション */ + case WE_CALLPARTNER: return 0xb2; /* あなたに逢いたい */ + case PA_GOSPEL: return 0xb3; /* ゴスペル */ + case HP_BASILICA: return 0xb4; /* バジリカ */ + case PF_FOGWALL: return 0xb6; /* フォグウォール */ + case PF_SPIDERWEB: return 0xb7; /* スパイダーウェッブ */ + } + return 0; + /* + 0x89,0x8a,0x8b 表示無し + 0x9a 炎属性の詠唱みたいなエフェクト + 0x9b 水属性の詠唱みたいなエフェクト + 0x9c 風属性の詠唱みたいなエフェクト + 0x9d 白い小さなエフェクト + 0xb1 Alchemist Demonstration + 0xb2 = Pink Warp Portal + 0xb3 = Gospel For Paladin + 0xb4 = Basilica + 0xb5 = Empty + 0xb6 = Fog Wall for Professor + 0xb7 = Spider Web for Professor + 0xb8 = Empty + 0xb9 = + */ +} + +/*========================================== + * スキル追加効果 + *------------------------------------------ + */ +int skill_additional_effect( struct block_list* src, struct block_list *bl,int skillid,int skilllv,int attack_type,unsigned int tick) +{ + /* MOB追加効果スキル用 */ + const int sc[]={ + SC_POISON, SC_BLIND, SC_SILENCE, SC_STAN, + SC_STONE, SC_CURSE, SC_SLEEP + }; + const int sc2[]={ + MG_STONECURSE,MG_FROSTDIVER,NPC_STUNATTACK, + NPC_SLEEPATTACK,TF_POISON,NPC_CURSEATTACK, + NPC_SILENCEATTACK,0,NPC_BLINDATTACK + }; + + struct map_session_data *sd=NULL; + struct map_session_data *dstsd=NULL; + struct mob_data *md=NULL; + struct mob_data *dstmd=NULL; + struct pet_data *pd=NULL; + + int skill,skill2; + int rate,luk; + + int sc_def_mdef,sc_def_vit,sc_def_int,sc_def_luk; + int sc_def_mdef2,sc_def_vit2,sc_def_int2,sc_def_luk2; + + nullpo_retr(0, src); + nullpo_retr(0, bl); + + if(skilllv < 0) return 0; + + if(src->type==BL_PC){ + nullpo_retr(0, sd=(struct map_session_data *)src); + }else if(src->type==BL_MOB){ + nullpo_retr(0, md=(struct mob_data *)src); //未使用? + }else if(src->type==BL_PET){ + nullpo_retr(0, pd=(struct pet_data *)src); // [Valaris] + } + + //対象の耐性 + luk = battle_get_luk(bl); + sc_def_mdef=100 - (3 + battle_get_mdef(bl) + luk/3); + sc_def_vit=100 - (3 + battle_get_vit(bl) + luk/3); + sc_def_int=100 - (3 + battle_get_int(bl) + luk/3); + sc_def_luk=100 - (3 + luk); + //自分の耐性 + luk = battle_get_luk(src); + sc_def_mdef2=100 - (3 + battle_get_mdef(src) + luk/3); + sc_def_vit2=100 - (3 + battle_get_vit(src) + luk/3); + sc_def_int2=100 - (3 + battle_get_int(src) + luk/3); + sc_def_luk2=100 - (3 + luk); + if(bl->type==BL_PC) + dstsd=(struct map_session_data *)bl; + else if(bl->type==BL_MOB){ + dstmd=(struct mob_data *)bl; //未使用? + if(sc_def_mdef>50) + sc_def_mdef=50; + if(sc_def_vit>50) + sc_def_vit=50; + if(sc_def_int>50) + sc_def_int=50; + if(sc_def_luk>50) + sc_def_luk=50; + } + if(sc_def_mdef<0) + sc_def_mdef=0; + if(sc_def_vit<0) + sc_def_vit=0; + if(sc_def_int<0) + sc_def_int=0; + + switch(skillid){ + case 0: /* 通常攻撃 */ + /* 自動鷹 */ + if( sd && pc_isfalcon(sd) && sd->status.weapon == 11 && (skill=pc_checkskill(sd,HT_BLITZBEAT))>0 && + rand()%1000 <= sd->paramc[5]*10/3+1 ) { + int lv=(sd->status.job_level+9)/10; + skill_castend_damage_id(src,bl,HT_BLITZBEAT,(skill<lv)?skill:lv,tick,0xf00000); + } + // スナッチャー + if(sd && sd->status.weapon != 11 && (skill=pc_checkskill(sd,RG_SNATCHER)) > 0) + if((skill*15 + 55) + (skill2 = pc_checkskill(sd,TF_STEAL))*10 > rand()%1000) { + if(pc_steal_item(sd,bl)) + clif_skill_nodamage(src,bl,TF_STEAL,skill2,1); + else + clif_skill_fail(sd,skillid,0,0); + } + break; + + case SM_BASH: /* バッシュ(急所攻撃) */ + if( sd && (skill=pc_checkskill(sd,SM_FATALBLOW))>0 ){ + if( rand()%100 < 6*(skilllv-5)*sc_def_vit/100 ) + skill_status_change_start(bl,SC_STAN,skilllv,0,0,0,skill_get_time2(SM_FATALBLOW,skilllv),0); + } + break; + + case TF_POISON: /* インベナム */ + case AS_SPLASHER: /* ベナムスプラッシャー */ + if(rand()%100< (2*skilllv+10)*sc_def_vit/100 ) + skill_status_change_start(bl,SC_POISON,skilllv,0,0,0,skill_get_time2(skillid,skilllv),0); + else{ + if(sd && skillid==TF_POISON) + clif_skill_fail(sd,skillid,0,0); + } + break; + + case AS_SONICBLOW: /* ソニックブロー */ + if( rand()%100 < (2*skilllv+10)*sc_def_vit/100 ) + skill_status_change_start(bl,SC_STAN,skilllv,0,0,0,skill_get_time2(skillid,skilllv),0); + break; + + + case HT_FREEZINGTRAP: /* フリージングトラップ */ + rate=skilllv*3+35; + if(rand()%100 < rate*sc_def_mdef/100) + skill_status_change_start(bl,SC_FREEZE,skilllv,0,0,0,skill_get_time2(skillid,skilllv),0); + break; + + case MG_FROSTDIVER: /* フロストダイバー */ + case WZ_FROSTNOVA: /* フロストノヴァ */ + rate=(skilllv*3+35)*sc_def_mdef/100-(battle_get_int(bl)+battle_get_luk(bl))/15; + rate=rate<=5?5:rate; + if(rand()%100 < rate) + skill_status_change_start(bl,SC_FREEZE,skilllv,0,0,0,skill_get_time2(skillid,skilllv),0); + else if(sd) + clif_skill_fail(sd,skillid,0,0); + break; + + case WZ_STORMGUST: /* ストームガスト */ + { + struct status_change *sc_data = battle_get_sc_data(bl); + if(sc_data) { + sc_data[SC_FREEZE].val3++; + if(sc_data[SC_FREEZE].val3 >= 3) + skill_status_change_start(bl,SC_FREEZE,skilllv,0,0,0,skill_get_time2(skillid,skilllv),0); + } + } + break; + + case HT_LANDMINE: /* ランドマイン */ + if( rand()%100 < (5*skilllv+30)*sc_def_vit/100 ) + skill_status_change_start(bl,SC_STAN,skilllv,0,0,0,skill_get_time2(skillid,skilllv),0); + break; + + case HT_SHOCKWAVE: /* ショックウェーブトラップ */ + if(map[bl->m].flag.pvp && dstsd){ + dstsd->status.sp -= dstsd->status.sp*(5+15*skilllv)/100; + pc_calcstatus(dstsd,0); + } + break; + case HT_SANDMAN: /* サンドマン */ + if( rand()%100 < (5*skilllv+30)*sc_def_int/100 ) + skill_status_change_start(bl,SC_SLEEP,skilllv,0,0,0,skill_get_time2(skillid,skilllv),0); + break; + case TF_SPRINKLESAND: /* 砂まき */ + if( rand()%100 < 15*sc_def_int/100 ) + skill_status_change_start(bl,SC_BLIND,skilllv,0,0,0,skill_get_time2(skillid,skilllv),0); + break; + + case TF_THROWSTONE: /* 石投げ */ + if( rand()%100 < 5*sc_def_vit/100 ) + skill_status_change_start(bl,SC_STAN,skilllv,0,0,0,skill_get_time2(skillid,skilllv),0); + break; + + case CR_HOLYCROSS: /* ホーリークロス */ + if( rand()%100 < 3*skilllv*sc_def_int/100 ) + skill_status_change_start(bl,SC_BLIND,skilllv,0,0,0,skill_get_time2(skillid,skilllv),0); + break; + + case CR_GRANDCROSS: /* グランドクロス */ + { + int race = battle_get_race(bl); + if( (battle_check_undead(race,battle_get_elem_type(bl)) || race == 6) && rand()%100 < 100000*sc_def_int/100) //強制付与だが完全耐性には無効 + skill_status_change_start(bl,SC_BLIND,skilllv,0,0,0,skill_get_time2(skillid,skilllv),0); + } + break; + + case CR_SHIELDCHARGE: /* シールドチャージ */ + if( rand()%100 < (15 + skilllv*5)*sc_def_vit/100 ) + skill_status_change_start(bl,SC_STAN,skilllv,0,0,0,skill_get_time2(skillid,skilllv),0); + break; + + case RG_RAID: /* サプライズアタック */ + if( rand()%100 < (10+3*skilllv)*sc_def_vit/100 ) + skill_status_change_start(bl,SC_STAN,skilllv,0,0,0,skill_get_time2(skillid,skilllv),0); + if( rand()%100 < (10+3*skilllv)*sc_def_int/100 ) + skill_status_change_start(bl,SC_BLIND,skilllv,0,0,0,skill_get_time2(skillid,skilllv),0); + break; + case BA_FROSTJOKE: + if(rand()%100 < (15+5*skilllv)*sc_def_mdef/100) + skill_status_change_start(bl,SC_FREEZE,skilllv,0,0,0,skill_get_time2(skillid,skilllv),0); + break; + + case DC_SCREAM: + if( rand()%100 < (25+5*skilllv)*sc_def_vit/100 ) + skill_status_change_start(bl,SC_STAN,skilllv,0,0,0,skill_get_time2(skillid,skilllv),0); + break; + + case BD_LULLABY: /* 子守唄 */ + if( rand()%100 < 15*sc_def_int/100 ) + skill_status_change_start(bl,SC_SLEEP,skilllv,0,0,0,skill_get_time2(skillid,skilllv),0); + break; + + /* MOBの追加効果付きスキル */ + + case NPC_PETRIFYATTACK: + if(rand()%100 < sc_def_mdef) + skill_status_change_start(bl,sc[skillid-NPC_POISON],skilllv,0,0,0,skill_get_time2(skillid,skilllv),0); + break; + case NPC_POISON: + case NPC_SILENCEATTACK: + case NPC_STUNATTACK: + if(rand()%100 < sc_def_vit && src->type!=BL_PET) + skill_status_change_start(bl,sc[skillid-NPC_POISON],skilllv,0,0,0,skill_get_time2(skillid,skilllv),0); + if(src->type==BL_PET) + skill_status_change_start(bl,sc[skillid-NPC_POISON],skilllv,0,0,0,skilllv*1000,0); + break; + case NPC_CURSEATTACK: + if(rand()%100 < sc_def_luk) + skill_status_change_start(bl,sc[skillid-NPC_POISON],skilllv,0,0,0,skill_get_time2(skillid,skilllv),0); + break; + case NPC_SLEEPATTACK: + case NPC_BLINDATTACK: + if(rand()%100 < sc_def_int) + skill_status_change_start(bl,sc[skillid-NPC_POISON],skilllv,0,0,0,skill_get_time2(skillid,skilllv),0); + break; + case NPC_MENTALBREAKER: + if(dstsd) { + int sp = dstsd->status.max_sp*(10+skilllv)/100; + if(sp < 1) sp = 1; + pc_heal(dstsd,0,-sp); + } + break; + +// -- moonsoul (adding status effect chance given to wizard aoe skills meteor and vermillion) +// + case WZ_METEOR: + if(rand()%100 < sc_def_vit) + skill_status_change_start(bl,SC_STAN,skilllv,0,0,0,skill_get_time2(skillid,skilllv),0); + break; + case WZ_VERMILION: + if(rand()%100 < sc_def_int) + skill_status_change_start(bl,SC_BLIND,skilllv,0,0,0,skill_get_time2(skillid,skilllv),0); + break; + +// -- moonsoul (stun ability of new champion skill tigerfist) +// + case CH_TIGERFIST: + if( rand()%100 < (5 + skilllv*5)*sc_def_vit/100 ) + skill_status_change_start(bl,SC_STAN,skilllv,0,0,0,skill_get_time2(skillid,skilllv),0); + break; + + case LK_SPIRALPIERCE: + if( rand()%100 < (15 + skilllv*5)*sc_def_vit/100 ) + skill_status_change_start(bl,SC_STAN,skilllv,0,0,0,skill_get_time2(skillid,skilllv),0); + break; + case ST_REJECTSWORD: /* フリージングトラップ */ + if( rand()%100 < (10 + skilllv*5) ) + skill_status_change_start(bl,SC_AUTOCOUNTER,skilllv,0,0,0,skill_get_time2(skillid,skilllv),0); + break; + case PF_FOGWALL: /* ホーリークロス */ + if( rand()%100 < 3*skilllv*sc_def_int/100 ) + skill_status_change_start(bl,SC_BLIND,skilllv,0,0,0,skill_get_time2(skillid,skilllv),0); + break; + case LK_HEADCRUSH: /* ヘッドクラッシュ */ + {//条件が良く分からないので適当に + int race=battle_get_race(bl); + if( !(battle_check_undead(race,battle_get_elem_type(bl)) || race == 6) && rand()%100 < (2*skilllv+10)*sc_def_vit/100 ) + skill_status_change_start(bl,SC_HEADCRUSH,skilllv,0,0,0,skill_get_time2(skillid,skilllv),0); + } + break; + case LK_JOINTBEAT: /* ジョイントビート */ + //条件が良く分からないので適当に + if( rand()%100 < (2*skilllv+10)*sc_def_vit/100 ) + skill_status_change_start(bl,SC_JOINTBEAT,skilllv,0,0,0,skill_get_time2(skillid,skilllv),0); + break; + case PF_SPIDERWEB: /* スパイダーウェッブ */ + { + int sec=skill_get_time2(skillid,skilllv); + if(map[src->m].flag.pvp) //PvPでは拘束時間半減? + sec = sec/2; + battle_stopwalking(bl,1); + skill_status_change_start(bl,SC_SPIDERWEB,skilllv,0,0,0,sec,0); + } + break; + case ASC_METEORASSAULT: /* メテオアサルト */ + if( rand()%100 < (15 + skilllv*5)*sc_def_vit/100 ) //状態異常は詳細が分からないので適当に + skill_status_change_start(bl,SC_STAN,skilllv,0,0,0,skill_get_time2(skillid,skilllv),0); + if( rand()%100 < (10+3*skilllv)*sc_def_int/100 ) + skill_status_change_start(bl,SC_BLIND,skilllv,0,0,0,skill_get_time2(skillid,skilllv),0); + break; + case MO_EXTREMITYFIST: /* 阿修羅覇凰拳 */ + //阿修羅を使うと5分間自然回復しないようになる + skill_status_change_start(src,SkillStatusChangeTable[skillid],skilllv,0,0,0,skill_get_time2(skillid,skilllv),0 ); + break; + } + + if(sd && skillid != MC_CARTREVOLUTION && attack_type&BF_WEAPON){ /* カードによる追加効果 */ + int i; + int sc_def_card=100; + + for(i=SC_STONE;i<=SC_BLIND;i++){ + //対象に状態異常 + if(i==SC_STONE || i==SC_FREEZE) + sc_def_card=sc_def_mdef; + else if(i==SC_STAN || i==SC_POISON || i==SC_SILENCE) + sc_def_card=sc_def_vit; + else if(i==SC_SLEEP || i==SC_CONFUSION || i==SC_BLIND) + sc_def_card=sc_def_int; + else if(i==SC_CURSE) + sc_def_card=sc_def_luk; + + if(!sd->state.arrow_atk) { + if(rand()%10000 < (sd->addeff[i-SC_STONE])*sc_def_card/100 ){ + if(battle_config.battle_log) + printf("PC %d skill_addeff: cardによる異常発動 %d %d\n",sd->bl.id,i,sd->addeff[i-SC_STONE]); + skill_status_change_start(bl,i,7,0,0,0,(i==SC_CONFUSION)? 10000+7000:skill_get_time2(sc2[i-SC_STONE],7),0); + } + } + else { + if(rand()%10000 < (sd->addeff[i-SC_STONE]+sd->arrow_addeff[i-SC_STONE])*sc_def_card/100 ){ + if(battle_config.battle_log) + printf("PC %d skill_addeff: cardによる異常発動 %d %d\n",sd->bl.id,i,sd->addeff[i-SC_STONE]); + skill_status_change_start(bl,i,7,0,0,0,(i==SC_CONFUSION)? 10000+7000:skill_get_time2(sc2[i-SC_STONE],7),0); + } + } + //自分に状態異常 + if(i==SC_STONE || i==SC_FREEZE) + sc_def_card=sc_def_mdef2; + else if(i==SC_STAN || i==SC_POISON || i==SC_SILENCE) + sc_def_card=sc_def_vit2; + else if(i==SC_SLEEP || i==SC_CONFUSION || i==SC_BLIND) + sc_def_card=sc_def_int2; + else if(i==SC_CURSE) + sc_def_card=sc_def_luk2; + + if(!sd->state.arrow_atk) { + if(rand()%10000 < (sd->addeff2[i-SC_STONE])*sc_def_card/100 ){ + if(battle_config.battle_log) + printf("PC %d skill_addeff: cardによる異常発動 %d %d\n",src->id,i,sd->addeff2[i-SC_STONE]); + skill_status_change_start(src,i,7,0,0,0,(i==SC_CONFUSION)? 10000+7000:skill_get_time2(sc2[i-SC_STONE],7),0); + } + } + else { + if(rand()%10000 < (sd->addeff2[i-SC_STONE]+sd->arrow_addeff2[i-SC_STONE])*sc_def_card/100 ){ + if(battle_config.battle_log) + printf("PC %d skill_addeff: cardによる異常発動 %d %d\n",src->id,i,sd->addeff2[i-SC_STONE]); + skill_status_change_start(src,i,7,0,0,0,(i==SC_CONFUSION)? 10000+7000:skill_get_time2(sc2[i-SC_STONE],7),0); + } + } + } + } + return 0; +} + +/*========================================================================= + スキル攻撃吹き飛ばし処理 +-------------------------------------------------------------------------*/ +int skill_blown( struct block_list *src, struct block_list *target,int count) +{ + int dx=0,dy=0,nx,ny; + int x=target->x,y=target->y; + int ret,prev_state=MS_IDLE; + int moveblock; + struct map_session_data *sd=NULL; + struct mob_data *md=NULL; + struct pet_data *pd=NULL; + struct skill_unit *su=NULL; + + nullpo_retr(0, src); + nullpo_retr(0, target); + + if(target->type==BL_PC){ + nullpo_retr(0, sd=(struct map_session_data *)target); + }else if(target->type==BL_MOB){ + nullpo_retr(0, md=(struct mob_data *)target); + }else if(target->type==BL_PET){ + nullpo_retr(0, pd=(struct pet_data *)target); + }else if(target->type==BL_SKILL){ + nullpo_retr(0, su=(struct skill_unit *)target); + }else return 0; + + if(!(count&0x10000 && (sd||md||pd||su))){ /* 指定なしなら位置関係から方向を求める */ + dx=target->x-src->x; dx=(dx>0)?1:((dx<0)?-1: 0); + dy=target->y-src->y; dy=(dy>0)?1:((dy<0)?-1: 0); + } + if(dx==0 && dy==0){ + int dir=battle_get_dir(target); + if(dir>=0 && dir<8){ + dx=-dirx[dir]; + dy=-diry[dir]; + } + } + + ret=path_blownpos(target->m,x,y,dx,dy,count&0xffff); + nx=ret>>16; + ny=ret&0xffff; + moveblock=( x/BLOCK_SIZE != nx/BLOCK_SIZE || y/BLOCK_SIZE != ny/BLOCK_SIZE); + + if(count&0x20000) { + battle_stopwalking(target,1); + if(sd){ + sd->to_x=nx; + sd->to_y=ny; + sd->walktimer = 1; + clif_walkok(sd); + clif_movechar(sd); + } + else if(md) { + md->to_x=nx; + md->to_y=ny; + prev_state = md->state.state; + md->state.state = MS_WALK; + clif_fixmobpos(md); + } + else if(pd) { + pd->to_x=nx; + pd->to_y=ny; + prev_state = pd->state.state; + pd->state.state = MS_WALK; + clif_fixpetpos(pd); + } + } + else + battle_stopwalking(target,2); + + dx = nx - x; + dy = ny - y; + + if(sd) /* 画面外に出たので消去 */ + map_foreachinmovearea(clif_pcoutsight,target->m,x-AREA_SIZE,y-AREA_SIZE,x+AREA_SIZE,y+AREA_SIZE,dx,dy,0,sd); + else if(md) + map_foreachinmovearea(clif_moboutsight,target->m,x-AREA_SIZE,y-AREA_SIZE,x+AREA_SIZE,y+AREA_SIZE,dx,dy,BL_PC,md); + else if(pd) + map_foreachinmovearea(clif_petoutsight,target->m,x-AREA_SIZE,y-AREA_SIZE,x+AREA_SIZE,y+AREA_SIZE,dx,dy,BL_PC,pd); + + if(su){ + skill_unit_move_unit_group(su->group,target->m,dx,dy); + }else{ +// struct status_change *sc_data=battle_get_sc_data(target); + if(moveblock) map_delblock(target); + target->x=nx; + target->y=ny; + if(moveblock) map_addblock(target); +/*ダンス中にエフェクトは移動しないらしい + if(sc_data && sc_data[SC_DANCING].timer!=-1){ //対象がダンス中なのでエフェクトも移動 + struct skill_unit_group *sg=(struct skill_unit_group *)sc_data[SC_DANCING].val2; + if(sg) + skill_unit_move_unit_group(sg,target->m,dx,dy); + } +*/ + } + + if(sd) { /* 画面内に入ってきたので表示 */ + map_foreachinmovearea(clif_pcinsight,target->m,nx-AREA_SIZE,ny-AREA_SIZE,nx+AREA_SIZE,ny+AREA_SIZE,-dx,-dy,0,sd); + if(count&0x20000) + sd->walktimer = -1; + } + else if(md) { + map_foreachinmovearea(clif_mobinsight,target->m,nx-AREA_SIZE,ny-AREA_SIZE,nx+AREA_SIZE,ny+AREA_SIZE,-dx,-dy,BL_PC,md); + if(count&0x20000) + md->state.state = prev_state; + } + else if(pd) { + map_foreachinmovearea(clif_petinsight,target->m,nx-AREA_SIZE,ny-AREA_SIZE,nx+AREA_SIZE,ny+AREA_SIZE,-dx,-dy,BL_PC,pd); + if(count&0x20000) + pd->state.state = prev_state; + } + + skill_unit_move(target,gettick(),(count&0xffff)+7); /* スキルユニットの判定 */ + + return 0; +} + + +/* + * ========================================================================= + * スキル攻撃効果処理まとめ + * flagの説明。16進図 + * 00XRTTff + * ff = magicで計算に渡される) + * TT = パケットのtype部分(0でデフォルト) + * X = パケットのスキルLv + * R = 予約(skill_area_subで使用する) + *------------------------------------------------------------------------- + */ + +int skill_attack( int attack_type, struct block_list* src, struct block_list *dsrc, + struct block_list *bl,int skillid,int skilllv,unsigned int tick,int flag ) +{ + struct Damage dmg; + struct status_change *sc_data; + int type,lv,damage; + + rdamage = 0; + nullpo_retr(0, src); + nullpo_retr(0, dsrc); + nullpo_retr(0, bl); + + sc_data = battle_get_sc_data(bl); + +//何もしない判定ここから + if(dsrc->m != bl->m) //対象が同じマップにいなければ何もしない + return 0; + if(src->prev == NULL || dsrc->prev == NULL || bl->prev == NULL) //prevよくわからない※ + return 0; + if(src->type == BL_PC && pc_isdead((struct map_session_data *)src)) //術者?がPCですでに死んでいたら何もしない + return 0; + if(dsrc->type == BL_PC && pc_isdead((struct map_session_data *)dsrc)) //術者?がPCですでに死んでいたら何もしない + return 0; + if(bl->type == BL_PC && pc_isdead((struct map_session_data *)bl)) //対象がPCですでに死んでいたら何もしない + return 0; + if(skillnotok(skillid, (struct map_session_data *) bl)) + return 0; // [MouseJstr] + if(sc_data && sc_data[SC_HIDING].timer != -1) { //ハイディング状態で + if(skill_get_pl(skillid) != 2) //スキルの属性が地属性でなければ何もしない + return 0; + } + if(sc_data && sc_data[SC_TRICKDEAD].timer != -1) //死んだふり中は何もしない + return 0; + if(skillid == WZ_STORMGUST) { //使用スキルがストームガストで + if(sc_data && sc_data[SC_FREEZE].timer != -1) //凍結状態なら何もしない + return 0; + } + if(skillid == WZ_FROSTNOVA && dsrc->x == bl->x && dsrc->y == bl->y) //使用スキルがフロストノヴァで、dsrcとblが同じ場所なら何もしない + return 0; + if(src->type == BL_PC && ((struct map_session_data *)src)->chatID) //術者がPCでチャット中なら何もしない + return 0; + if(dsrc->type == BL_PC && ((struct map_session_data *)dsrc)->chatID) //術者がPCでチャット中なら何もしない + return 0; + if(src->type == BL_PC && bl && mob_gvmobcheck(((struct map_session_data *)src),bl)==0) + return 0; + +//何もしない判定ここまで + + type=-1; + lv=(flag>>20)&0xf; + dmg=battle_calc_attack(attack_type,src,bl,skillid,skilllv,flag&0xff ); //ダメージ計算 + +//マジックロッド処理ここから + if(attack_type&BF_MAGIC && sc_data && sc_data[SC_MAGICROD].timer != -1 && src == dsrc) { //魔法攻撃でマジックロッド状態でsrc=dsrcなら + dmg.damage = dmg.damage2 = 0; //ダメージ0 + if(bl->type == BL_PC) { //対象がPCの場合 + int sp = skill_get_sp(skillid,skilllv); //使用されたスキルのSPを吸収 + sp = sp * sc_data[SC_MAGICROD].val2 / 100; //吸収率計算 + if(skillid == WZ_WATERBALL && skilllv > 1) //ウォーターボールLv1以上 + sp = sp/((skilllv|1)*(skilllv|1)); //さらに計算? + if(sp > 0x7fff) sp = 0x7fff; //SP多すぎの場合は理論最大値 + else if(sp < 1) sp = 1; //1以下の場合は1 + if(((struct map_session_data *)bl)->status.sp + sp > ((struct map_session_data *)bl)->status.max_sp) { //回復SP+現在のSPがMSPより大きい場合 + sp = ((struct map_session_data *)bl)->status.max_sp - ((struct map_session_data *)bl)->status.sp; //SPをMSP-現在SPにする + ((struct map_session_data *)bl)->status.sp = ((struct map_session_data *)bl)->status.max_sp; //現在のSPにMSPを代入 + } + else //回復SP+現在のSPがMSPより小さい場合は回復SPを加算 + ((struct map_session_data *)bl)->status.sp += sp; + clif_heal(((struct map_session_data *)bl)->fd,SP_SP,sp); //SP回復エフェクトの表示 + ((struct map_session_data *)bl)->canact_tick = tick + skill_delayfix(bl, skill_get_delay(SA_MAGICROD,sc_data[SC_MAGICROD].val1)); // + } + clif_skill_nodamage(bl,bl,SA_MAGICROD,sc_data[SC_MAGICROD].val1,1); //マジックロッドエフェクトを表示 + } +//マジックロッド処理ここまで + + if(src->type==BL_PET) { // [Valaris] + dmg.damage=battle_attr_fix(skilllv, skill_get_pl(skillid), battle_get_element(bl) ); + dmg.damage2=0; + } + + damage = dmg.damage + dmg.damage2; + + if(lv==15) + lv=-1; + + if( flag&0xff00 ) + type=(flag&0xff00)>>8; + + if(damage <= 0 || damage < dmg.div_) //吹き飛ばし判定?※ + dmg.blewcount = 0; + + if(skillid == CR_GRANDCROSS) {//グランドクロス + if(battle_config.gx_disptype) dsrc = src; // 敵ダメージ白文字表示 + if( src == bl) type = 4; // 反動はダメージモーションなし + } + +//使用者がPCの場合の処理ここから + if(src->type == BL_PC) { + struct map_session_data *sd = (struct map_session_data *)src; + nullpo_retr(0, sd); +//連打掌(MO_CHAINCOMBO)ここから + if(skillid == MO_CHAINCOMBO) { + int delay = 1000 - 4 * battle_get_agi(src) - 2 * battle_get_dex(src); //基本ディレイの計算 + if(damage < battle_get_hp(bl)) { //ダメージが対象のHPより小さい場合 + if(pc_checkskill(sd, MO_COMBOFINISH) > 0 && sd->spiritball > 0) //猛龍拳(MO_COMBOFINISH)取得&気球保持時は+300ms + delay += 300 * battle_config.combo_delay_rate /100; //追加ディレイをconfにより調整 + + skill_status_change_start(src,SC_COMBO,MO_CHAINCOMBO,skilllv,0,0,delay,0); //コンボ状態に + } + sd->attackabletime = sd->canmove_tick = tick + delay; + clif_combo_delay(src,delay); //コンボディレイパケットの送信 + } +//連打掌(MO_CHAINCOMBO)ここまで +//猛龍拳(MO_COMBOFINISH)ここから + else if(skillid == MO_COMBOFINISH) { + int delay = 700 - 4 * battle_get_agi(src) - 2 * battle_get_dex(src); + if(damage < battle_get_hp(bl)) { + //阿修羅覇凰拳(MO_EXTREMITYFIST)取得&気球4個保持&爆裂波動(MO_EXPLOSIONSPIRITS)状態時は+300ms + //伏虎拳(CH_TIGERFIST)取得時も+300ms + if((pc_checkskill(sd, MO_EXTREMITYFIST) > 0 && sd->spiritball >= 4 && sd->sc_data[SC_EXPLOSIONSPIRITS].timer != -1) || + (pc_checkskill(sd, CH_TIGERFIST) > 0 && sd->spiritball > 0) || + (pc_checkskill(sd, CH_CHAINCRUSH) > 0 && sd->spiritball > 1)) + delay += 300 * battle_config.combo_delay_rate /100; //追加ディレイをconfにより調整 + + skill_status_change_start(src,SC_COMBO,MO_COMBOFINISH,skilllv,0,0,delay,0); //コンボ状態に + } + sd->attackabletime = sd->canmove_tick = tick + delay; + clif_combo_delay(src,delay); //コンボディレイパケットの送信 + } +//猛龍拳(MO_COMBOFINISH)ここまで +//伏虎拳(CH_TIGERFIST)ここから + else if(skillid == CH_TIGERFIST) { + int delay = 1000 - 4 * battle_get_agi(src) - 2 * battle_get_dex(src); + if(damage < battle_get_hp(bl)) { + if(pc_checkskill(sd, CH_CHAINCRUSH) > 0) //連柱崩撃(CH_CHAINCRUSH)取得時は+300ms + delay += 300 * battle_config.combo_delay_rate /100; //追加ディレイをconfにより調整 + + skill_status_change_start(src,SC_COMBO,CH_TIGERFIST,skilllv,0,0,delay,0); //コンボ状態に + } + sd->attackabletime = sd->canmove_tick = tick + delay; + clif_combo_delay(src,delay); //コンボディレイパケットの送信 + } +//伏虎拳(CH_TIGERFIST)ここまで +//連柱崩撃(CH_CHAINCRUSH)ここから + else if(skillid == CH_CHAINCRUSH) { + int delay = 1000 - 4 * battle_get_agi(src) - 2 * battle_get_dex(src); + if(damage < battle_get_hp(bl)) { + //阿修羅覇凰拳(MO_EXTREMITYFIST)取得&気球4個保持&爆裂波動(MO_EXPLOSIONSPIRITS)状態時は+300ms + if(pc_checkskill(sd, MO_EXTREMITYFIST) > 0 && sd->spiritball >= 4 && sd->sc_data[SC_EXPLOSIONSPIRITS].timer != -1) + delay += 300 * battle_config.combo_delay_rate /100; //追加ディレイをconfにより調整 + + skill_status_change_start(src,SC_COMBO,CH_CHAINCRUSH,skilllv,0,0,delay,0); //コンボ状態に + } + sd->attackabletime = sd->canmove_tick = tick + delay; + clif_combo_delay(src,delay); //コンボディレイパケットの送信 + } +//連柱崩撃(CH_CHAINCRUSH)ここまで + } +//使用者がPCの場合の処理ここまで +//武器スキル?ここから + //AppleGirl Was Here + if(attack_type&BF_MAGIC && damage > 0 && src != bl && src == dsrc) { //Blah Blah + if(bl->type == BL_PC) { //Blah Blah + struct map_session_data *tsd = (struct map_session_data *)bl; + if(tsd->magic_damage_return > 0) { //More Blah + rdamage += damage * tsd->magic_damage_return / 100; + if(rdamage < 1) rdamage = 1; + } + } + } + //Stop Here + if(attack_type&BF_WEAPON && damage > 0 && src != bl && src == dsrc) { //武器スキル&ダメージあり&使用者と対象者が違う&src=dsrc + if(dmg.flag&BF_SHORT) { //近距離攻撃時?※ + if(bl->type == BL_PC) { //対象がPCの時 + struct map_session_data *tsd = (struct map_session_data *)bl; + nullpo_retr(0, tsd); + if(tsd->short_weapon_damage_return > 0) { //近距離攻撃跳ね返し?※ + rdamage += damage * tsd->short_weapon_damage_return / 100; + if(rdamage < 1) rdamage = 1; + } + } + if(sc_data && sc_data[SC_REFLECTSHIELD].timer != -1) { //リフレクトシールド時 + rdamage += damage * sc_data[SC_REFLECTSHIELD].val2 / 100; //跳ね返し計算 + if(rdamage < 1) rdamage = 1; + } + } + else if(dmg.flag&BF_LONG) { //遠距離攻撃時?※ + if(bl->type == BL_PC) { //対象がPCの時 + struct map_session_data *tsd = (struct map_session_data *)bl; + nullpo_retr(0, tsd); + if(tsd->long_weapon_damage_return > 0) { //遠距離攻撃跳ね返し?※ + rdamage += damage * tsd->long_weapon_damage_return / 100; + if(rdamage < 1) rdamage = 1; + } + } + } + if(rdamage > 0) + clif_damage(src,src,tick, dmg.amotion,0,rdamage,1,4,0); + } +//武器スキル?ここまで + + switch(skillid){ + case WZ_SIGHTRASHER: + clif_skill_damage(src,bl,tick,dmg.amotion,dmg.dmotion, damage, dmg.div_, skillid, (lv!=0)?lv:skilllv, 5); + break; + case AS_SPLASHER: + clif_skill_damage(dsrc,bl,tick,dmg.amotion,dmg.dmotion, damage, dmg.div_, skillid, -1, 5); + break; + case NPC_SELFDESTRUCTION: + case NPC_SELFDESTRUCTION2: + break; + default: + clif_skill_damage(dsrc,bl,tick,dmg.amotion,dmg.dmotion, damage, dmg.div_, skillid, (lv!=0)?lv:skilllv, (skillid==0)? 5:type ); + } + if(dmg.blewcount > 0 && !map[src->m].flag.gvg) { /* 吹き飛ばし処理とそのパケット */ + if(skillid == WZ_SIGHTRASHER) + skill_blown(src,bl,dmg.blewcount); + else + skill_blown(dsrc,bl,dmg.blewcount); + if(bl->type == BL_MOB) + clif_fixmobpos((struct mob_data *)bl); + else if(bl->type == BL_PET) + clif_fixpetpos((struct pet_data *)bl); + else + clif_fixpos(bl); + } + + map_freeblock_lock(); + /* 実際にダメージ処理を行う */ + if(skillid != KN_BOWLINGBASH || flag) + battle_damage(src,bl,damage,0); + if(skillid == RG_INTIMIDATE && damage > 0 && !(battle_get_mode(bl)&0x20) && !map[src->m].flag.gvg ) { + int s_lv = battle_get_lv(src),t_lv = battle_get_lv(bl); + int rate = 50 + skilllv * 5; + rate = rate + (s_lv - t_lv); + if(rand()%100 < rate) + skill_addtimerskill(src,tick + 800,bl->id,0,0,skillid,skilllv,0,flag); + } + if(damage > 0 && dmg.flag&BF_SKILL && bl->type==BL_PC && pc_checkskill((struct map_session_data *)bl,RG_PLAGIARISM)){ + struct map_session_data *tsd = (struct map_session_data *)bl; + nullpo_retr(0, tsd); + if(!tsd->status.skill[skillid].id && !tsd->status.skill[skillid].id + && !(skillid > NPC_PIERCINGATT && skillid < NPC_SUMMONMONSTER) ){ + //既に盗んでいるスキルがあれば該当スキルを消す + if (tsd->cloneskill_id && tsd->cloneskill_lv && tsd->status.skill[tsd->cloneskill_id].flag==13){ + tsd->status.skill[tsd->cloneskill_id].id=0; + tsd->status.skill[tsd->cloneskill_id].lv=0; + tsd->status.skill[tsd->cloneskill_id].flag=0; + } + tsd->cloneskill_id=skillid; + tsd->cloneskill_lv=skilllv; + tsd->status.skill[skillid].id=skillid; + tsd->status.skill[skillid].lv=(pc_checkskill(tsd,RG_PLAGIARISM) > skill_get_max(skillid))? + skill_get_max(skillid):pc_checkskill(tsd,RG_PLAGIARISM); + tsd->status.skill[skillid].flag=13;//cloneskill flag + clif_skillinfoblock(tsd); + } + } + /* ダメージがあるなら追加効果判定 */ + if(bl->prev != NULL){ + struct map_session_data *sd = (struct map_session_data *)bl; + nullpo_retr(0, sd); + if( bl->type != BL_PC || (sd && !pc_isdead(sd)) ) { + if(damage > 0) + skill_additional_effect(src,bl,skillid,skilllv,attack_type,tick); + if(bl->type==BL_MOB && src!=bl) /* スキル使用条件のMOBスキル */ + { + struct mob_data *md=(struct mob_data *)bl; + nullpo_retr(0, md); + if(battle_config.mob_changetarget_byskill == 1) + { + int target; + target=md->target_id; + if(src->type == BL_PC) + md->target_id=src->id; + mobskill_use(md,tick,MSC_SKILLUSED|(skillid<<16)); + md->target_id=target; + } + else + mobskill_use(md,tick,MSC_SKILLUSED|(skillid<<16)); + } + } + } + + if(src->type == BL_PC && dmg.flag&BF_WEAPON && src != bl && src == dsrc && damage > 0) { + struct map_session_data *sd = (struct map_session_data *)src; + int hp = 0,sp = 0; + nullpo_retr(0, sd); + if(sd->hp_drain_rate && sd->hp_drain_per > 0 && dmg.damage > 0 && rand()%100 < sd->hp_drain_rate) { + hp += (dmg.damage * sd->hp_drain_per)/100; + if(sd->hp_drain_rate > 0 && hp < 1) hp = 1; + else if(sd->hp_drain_rate < 0 && hp > -1) hp = -1; + } + if(sd->hp_drain_rate_ && sd->hp_drain_per_ > 0 && dmg.damage2 > 0 && rand()%100 < sd->hp_drain_rate_) { + hp += (dmg.damage2 * sd->hp_drain_per_)/100; + if(sd->hp_drain_rate_ > 0 && hp < 1) hp = 1; + else if(sd->hp_drain_rate_ < 0 && hp > -1) hp = -1; + } + if(sd->sp_drain_rate > 0 && sd->sp_drain_per > 0 && dmg.damage > 0 && rand()%100 < sd->sp_drain_rate) { + sp += (dmg.damage * sd->sp_drain_per)/100; + if(sd->sp_drain_rate > 0 && sp < 1) sp = 1; + else if(sd->sp_drain_rate < 0 && sp > -1) sp = -1; + } + if(sd->sp_drain_rate_ > 0 && sd->sp_drain_per_ > 0 && dmg.damage2 > 0 && rand()%100 < sd->sp_drain_rate_) { + sp += (dmg.damage2 * sd->sp_drain_per_)/100; + if(sd->sp_drain_rate_ > 0 && sp < 1) sp = 1; + else if(sd->sp_drain_rate_ < 0 && sp > -1) sp = -1; + } + if(hp || sp) pc_heal(sd,hp,sp); + } + + if((skillid != KN_BOWLINGBASH || flag) && rdamage > 0) + battle_damage(bl,src,rdamage,0); + + if(attack_type&BF_WEAPON && sc_data && sc_data[SC_AUTOCOUNTER].timer != -1 && sc_data[SC_AUTOCOUNTER].val4 > 0) { + if(sc_data[SC_AUTOCOUNTER].val3 == dsrc->id) + battle_weapon_attack(bl,dsrc,tick,0x8000|sc_data[SC_AUTOCOUNTER].val1); + skill_status_change_end(bl,SC_AUTOCOUNTER,-1); + } + + map_freeblock_unlock(); + + return (dmg.damage+dmg.damage2); /* 与ダメを返す */ +} + +/*========================================== + * スキル範囲攻撃用(map_foreachinareaから呼ばれる) + * flagについて:16進図を確認 + * MSB <- 00fTffff ->LSB + * T =ターゲット選択用(BCT_*) + * ffff=自由に使用可能 + * 0 =予約。0に固定 + *------------------------------------------ + */ +static int skill_area_temp[8]; /* 一時変数。必要なら使う。 */ +typedef int (*SkillFunc)(struct block_list *,struct block_list *,int,int,unsigned int,int); +int skill_area_sub( struct block_list *bl,va_list ap ) +{ + struct block_list *src; + int skill_id,skill_lv,flag; + unsigned int tick; + SkillFunc func; + + nullpo_retr(0, bl); + nullpo_retr(0, ap); + + if(bl->type!=BL_PC && bl->type!=BL_MOB && bl->type!=BL_SKILL) + return 0; + + src=va_arg(ap,struct block_list *); //ここではsrcの値を参照していないのでNULLチェックはしない + skill_id=va_arg(ap,int); + skill_lv=va_arg(ap,int); + tick=va_arg(ap,unsigned int); + flag=va_arg(ap,int); + func=va_arg(ap,SkillFunc); + + if(battle_check_target(src,bl,flag) > 0) + func(src,bl,skill_id,skill_lv,tick,flag); + return 0; +} + +static int skill_check_unit_range_sub( struct block_list *bl,va_list ap ) +{ + struct skill_unit *unit; + int *c,x,y,range,sx[4],sy[4]; + int t_range,tx[4],ty[4]; + int i,r_flag,skillid; + + nullpo_retr(0, bl); + nullpo_retr(0, ap); + nullpo_retr(0, unit = (struct skill_unit *)bl); + nullpo_retr(0, c = va_arg(ap,int *)); + + if(bl->prev == NULL || bl->type != BL_SKILL) + return 0; + + if(!unit->alive) + return 0; + + x = va_arg(ap,int); + y = va_arg(ap,int); + range = va_arg(ap,int); + skillid = va_arg(ap,int); + + if(skillid == MG_SAFETYWALL || skillid == AL_PNEUMA) { + if(unit->group->unit_id != 0x7e && unit->group->unit_id != 0x85) + return 0; + } + else if(skillid == AL_WARP) { + if((unit->group->unit_id < 0x8f || unit->group->unit_id > 0x99) && unit->group->unit_id != 0x92) + return 0; + } + else if((skillid >= HT_SKIDTRAP && skillid <= HT_CLAYMORETRAP) || skillid == HT_TALKIEBOX) { + if((unit->group->unit_id < 0x8f || unit->group->unit_id > 0x99) && unit->group->unit_id != 0x92) + return 0; + } + else if(skillid == WZ_FIREPILLAR) { + if(unit->group->unit_id != 0x87) + return 0; + } + else return 0; + t_range=(unit->range!=0)? unit->range:unit->group->range; + tx[0] = tx[3] = unit->bl.x - t_range; + tx[1] = tx[2] = unit->bl.x + t_range; + ty[0] = ty[1] = unit->bl.y - t_range; + ty[2] = ty[3] = unit->bl.y + t_range; + sx[0] = sx[3] = x - range; + sx[1] = sx[2] = x + range; + sy[0] = sy[1] = y - range; + sy[2] = sy[3] = y + range; + for(i=r_flag=0;i<4;i++) { + if(sx[i] >= tx[0] && sx[i] <= tx[1] && sy[i] >= ty[0] && sy[i] <= ty[2]) { + r_flag = 1; + break; + } + if(tx[i] >= sx[0] && tx[i] <= sx[1] && ty[i] >= sy[0] && ty[i] <= sy[2]) { + r_flag = 1; + break; + } + } + if(r_flag) (*c)++; + + return 0; +} + +int skill_check_unit_range(int m,int x,int y,int range,int skillid) +{ + int c = 0; + + map_foreachinarea(skill_check_unit_range_sub,m,x-10,y-10,x+10,y+10,BL_SKILL,&c,x,y,range,skillid); + + return c; +} + +static int skill_check_unit_range2_sub( struct block_list *bl,va_list ap ) +{ + int *c; + + nullpo_retr(0, bl); + nullpo_retr(0, ap); + nullpo_retr(0, c = va_arg(ap,int *)); + + if(bl->prev == NULL || (bl->type != BL_PC && bl->type != BL_MOB)) + return 0; + + if(bl->type == BL_PC && pc_isdead((struct map_session_data *)bl)) + return 0; + + (*c)++; + + return 0; +} + +int skill_check_unit_range2(int m,int x,int y,int range) +{ + int c = 0; + + map_foreachinarea(skill_check_unit_range2_sub,m,x-range,y-range,x+range,y+range,0,&c); + + return c; +} + +/*========================================================================= + * 範囲スキル使用処理小分けここから + */ +/* 対象の数をカウントする。(skill_area_temp[0]を初期化しておくこと) */ +int skill_area_sub_count(struct block_list *src,struct block_list *target,int skillid,int skilllv,unsigned int tick,int flag) +{ + if(skill_area_temp[0] < 0xffff) + skill_area_temp[0]++; + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +static int skill_timerskill(int tid, unsigned int tick, int id,int data ) +{ + struct map_session_data *sd = NULL; + struct mob_data *md = NULL; + struct pet_data *pd = NULL; + struct block_list *src = map_id2bl(id),*target; + struct skill_timerskill *skl = NULL; + int range; + + nullpo_retr(0, src); + + if(src->prev == NULL) + return 0; + + if(src->type == BL_PC) { + nullpo_retr(0, sd = (struct map_session_data *)src); + skl = &sd->skilltimerskill[data]; + } + else if(src->type == BL_MOB) { + nullpo_retr(0, md = (struct mob_data *)src); + skl = &md->skilltimerskill[data]; + } + else if(src->type == BL_PET) { // [Valaris] + nullpo_retr(0, pd = (struct pet_data *)src); + skl = &pd->skilltimerskill[data]; + } + + else + return 0; + + nullpo_retr(0, skl); + + skl->timer = -1; + if(skl->target_id) { + struct block_list tbl; + target = map_id2bl(skl->target_id); + if(skl->skill_id == RG_INTIMIDATE) { + if(target == NULL) { + target = &tbl; //初期化してないのにアドレス突っ込んでいいのかな? + target->type = BL_NUL; + target->m = src->m; + target->prev = target->next = NULL; + } + } + if(target == NULL) + return 0; + if(target->prev == NULL && skl->skill_id != RG_INTIMIDATE) + return 0; + if(src->m != target->m) + return 0; + if(sd && pc_isdead(sd)) + return 0; + if(target->type == BL_PC && pc_isdead((struct map_session_data *)target) && skl->skill_id != RG_INTIMIDATE) + return 0; + + switch(skl->skill_id) { + case TF_BACKSLIDING: + clif_skill_nodamage(src,src,skl->skill_id,skl->skill_lv,1); + break; + case RG_INTIMIDATE: + if(sd && !map[src->m].flag.noteleport) { + int x,y,i,j,c; + pc_randomwarp(sd,3); + for(i=0;i<16;i++) { + j = rand()%8; + x = sd->bl.x + dirx[j]; + y = sd->bl.y + diry[j]; + if((c=map_getcell(sd->bl.m,x,y)) != 1 && c != 5) + break; + } + if(i >= 16) { + x = sd->bl.x; + y = sd->bl.y; + } + if(target->prev != NULL) { + if(target->type == BL_PC && !pc_isdead((struct map_session_data *)target)) + pc_setpos((struct map_session_data *)target,map[sd->bl.m].name,x,y,3); + else if(target->type == BL_MOB) + mob_warp((struct mob_data *)target,-1,x,y,3); + } + } + else if(md && !map[src->m].flag.monster_noteleport) { + int x,y,i,j,c; + mob_warp(md,-1,-1,-1,3); + for(i=0;i<16;i++) { + j = rand()%8; + x = md->bl.x + dirx[j]; + y = md->bl.y + diry[j]; + if((c=map_getcell(md->bl.m,x,y)) != 1 && c != 5) + break; + } + if(i >= 16) { + x = md->bl.x; + y = md->bl.y; + } + if(target->prev != NULL) { + if(target->type == BL_PC && !pc_isdead((struct map_session_data *)target)) + pc_setpos((struct map_session_data *)target,map[md->bl.m].name,x,y,3); + else if(target->type == BL_MOB) + mob_warp((struct mob_data *)target,-1,x,y,3); + } + } + break; + + case BA_FROSTJOKE: /* 寒いジョーク */ + case DC_SCREAM: /* スクリーム */ + range=15; //視界全体 + map_foreachinarea(skill_frostjoke_scream,src->m,src->x-range,src->y-range, + src->x+range,src->y+range,0,src,skl->skill_id,skl->skill_lv,tick); + break; + + default: + skill_attack(skl->type,src,src,target,skl->skill_id,skl->skill_lv,tick,skl->flag); + break; + } + } + else { + if(src->m != skl->map) + return 0; + switch(skl->skill_id) { + case WZ_METEOR: + if(skl->type >= 0) { + skill_unitsetting(src,skl->skill_id,skl->skill_lv,skl->type>>16,skl->type&0xFFFF,0); + clif_skill_poseffect(src,skl->skill_id,skl->skill_lv,skl->x,skl->y,tick); + } + else + skill_unitsetting(src,skl->skill_id,skl->skill_lv,skl->x,skl->y,0); + break; + } + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int skill_addtimerskill(struct block_list *src,unsigned int tick,int target,int x,int y,int skill_id,int skill_lv,int type,int flag) +{ + int i; + + nullpo_retr(1, src); + + if(src->type == BL_PC) { + struct map_session_data *sd = (struct map_session_data *)src; + nullpo_retr(1, sd); + for(i=0;i<MAX_SKILLTIMERSKILL;i++) { + if(sd->skilltimerskill[i].timer == -1) { + sd->skilltimerskill[i].timer = add_timer(tick, skill_timerskill, src->id, i); + sd->skilltimerskill[i].src_id = src->id; + sd->skilltimerskill[i].target_id = target; + sd->skilltimerskill[i].skill_id = skill_id; + sd->skilltimerskill[i].skill_lv = skill_lv; + sd->skilltimerskill[i].map = src->m; + sd->skilltimerskill[i].x = x; + sd->skilltimerskill[i].y = y; + sd->skilltimerskill[i].type = type; + sd->skilltimerskill[i].flag = flag; + + return 0; + } + } + return 1; + } + else if(src->type == BL_MOB) { + struct mob_data *md = (struct mob_data *)src; + nullpo_retr(1, md); + for(i=0;i<MAX_MOBSKILLTIMERSKILL;i++) { + if(md->skilltimerskill[i].timer == -1) { + md->skilltimerskill[i].timer = add_timer(tick, skill_timerskill, src->id, i); + md->skilltimerskill[i].src_id = src->id; + md->skilltimerskill[i].target_id = target; + md->skilltimerskill[i].skill_id = skill_id; + md->skilltimerskill[i].skill_lv = skill_lv; + md->skilltimerskill[i].map = src->m; + md->skilltimerskill[i].x = x; + md->skilltimerskill[i].y = y; + md->skilltimerskill[i].type = type; + md->skilltimerskill[i].flag = flag; + + return 0; + } + } + return 1; + } + else if(src->type == BL_PET) { // [Valaris] + struct pet_data *pd = (struct pet_data *)src; + nullpo_retr(1, pd); + for(i=0;i<MAX_MOBSKILLTIMERSKILL;i++) { + if(pd->skilltimerskill[i].timer == -1) { + pd->skilltimerskill[i].timer = add_timer(tick, skill_timerskill, src->id, i); + pd->skilltimerskill[i].src_id = src->id; + pd->skilltimerskill[i].target_id = target; + pd->skilltimerskill[i].skill_id = skill_id; + pd->skilltimerskill[i].skill_lv = skill_lv; + pd->skilltimerskill[i].map = src->m; + pd->skilltimerskill[i].x = x; + pd->skilltimerskill[i].y = y; + pd->skilltimerskill[i].type = type; + pd->skilltimerskill[i].flag = flag; + + return 0; + } + } + return 1; + } + return 1; +} + +/*========================================== + * + *------------------------------------------ + */ +int skill_cleartimerskill(struct block_list *src) +{ + int i; + + nullpo_retr(0, src); + + if(src->type == BL_PC) { + struct map_session_data *sd = (struct map_session_data *)src; + nullpo_retr(0, sd); + for(i=0;i<MAX_SKILLTIMERSKILL;i++) { + if(sd->skilltimerskill[i].timer != -1) { + delete_timer(sd->skilltimerskill[i].timer, skill_timerskill); + sd->skilltimerskill[i].timer = -1; + } + } + } + else if(src->type == BL_MOB) { + struct mob_data *md = (struct mob_data *)src; + nullpo_retr(0, md); + for(i=0;i<MAX_MOBSKILLTIMERSKILL;i++) { + if(md->skilltimerskill[i].timer != -1) { + delete_timer(md->skilltimerskill[i].timer, skill_timerskill); + md->skilltimerskill[i].timer = -1; + } + } + } + + return 0; +} + +/* 範囲スキル使用処理小分けここまで + * ------------------------------------------------------------------------- + */ + +/*========================================== + * スキル使用(詠唱完了、ID指定攻撃系) + * (スパゲッティに向けて1歩前進!(ダメポ)) + *------------------------------------------ + */ +int skill_castend_damage_id( struct block_list* src, struct block_list *bl,int skillid,int skilllv,unsigned int tick,int flag ) +{ + struct map_session_data *sd=NULL; + int i; + + nullpo_retr(1, src); + nullpo_retr(1, bl); + + if(src->type==BL_PC) + sd=(struct map_session_data *)src; + if(sd && pc_isdead(sd)) + return 1; + + if((skillid == WZ_SIGHTRASHER || skillid == CR_GRANDCROSS) && src != bl) + bl = src; + if(bl->prev == NULL) + return 1; + if(bl->type == BL_PC && pc_isdead((struct map_session_data *)bl)) + return 1; + map_freeblock_lock(); + switch(skillid) + { + /* 武器攻撃系スキル */ + case SM_BASH: /* バッシュ */ + case MC_MAMMONITE: /* メマーナイト */ + case AC_DOUBLE: /* ダブルストレイフィング */ + case AS_SONICBLOW: /* ソニックブロー */ + case KN_PIERCE: /* ピアース */ + case KN_SPEARBOOMERANG: /* スピアブーメラン */ + case TF_POISON: /* インベナム */ + case TF_SPRINKLESAND: /* 砂まき */ + case AC_CHARGEARROW: /* チャージアロー */ + case KN_SPEARSTAB: /* スピアスタブ */ + case RG_RAID: /* サプライズアタック */ + case RG_INTIMIDATE: /* インティミデイト */ + case BA_MUSICALSTRIKE: /* ミュージカルストライク */ + case DC_THROWARROW: /* 矢撃ち */ + case BA_DISSONANCE: /* 不協和音 */ + case CR_HOLYCROSS: /* ホーリークロス */ + case CR_SHIELDCHARGE: + case CR_SHIELDBOOMERANG: + + /* 以下MOB専用 */ + /* 単体攻撃、SP減少攻撃、遠距離攻撃、防御無視攻撃、多段攻撃 */ + case NPC_PIERCINGATT: + case NPC_MENTALBREAKER: + case NPC_RANGEATTACK: + case NPC_CRITICALSLASH: + case NPC_COMBOATTACK: + /* 必中攻撃、毒攻撃、暗黒攻撃、沈黙攻撃、スタン攻撃 */ + case NPC_GUIDEDATTACK: + case NPC_POISON: + case NPC_BLINDATTACK: + case NPC_SILENCEATTACK: + case NPC_STUNATTACK: + /* 石化攻撃、呪い攻撃、睡眠攻撃、ランダムATK攻撃 */ + case NPC_PETRIFYATTACK: + case NPC_CURSEATTACK: + case NPC_SLEEPATTACK: + case NPC_RANDOMATTACK: + /* 水属性攻撃、地属性攻撃、火属性攻撃、風属性攻撃 */ + case NPC_WATERATTACK: + case NPC_GROUNDATTACK: + case NPC_FIREATTACK: + case NPC_WINDATTACK: + /* 毒属性攻撃、聖属性攻撃、闇属性攻撃、念属性攻撃、SP減少攻撃 */ + case NPC_POISONATTACK: + case NPC_HOLYATTACK: + case NPC_DARKNESSATTACK: + case NPC_TELEKINESISATTACK: + case LK_AURABLADE: /* オーラブレード */ + case LK_SPIRALPIERCE: /* スパイラルピアース */ + case LK_HEADCRUSH: /* ヘッドクラッシュ */ + case LK_JOINTBEAT: /* ジョイントビート */ + case PA_PRESSURE: /* プレッシャー */ + case PA_SACRIFICE: /* サクリファイス */ + case SN_SHARPSHOOTING: /* シャープシューティング */ + case CG_ARROWVULCAN: /* アローバルカン */ + case ASC_BREAKER: /* ソウルブレーカー */ + case HW_MAGICCRASHER: /* マジッククラッシャー */ + skill_attack(BF_WEAPON,src,src,bl,skillid,skilllv,tick,flag); + break; + case NPC_DARKBREATH: + clif_emotion(src,7); + skill_attack(BF_MISC,src,src,bl,skillid,skilllv,tick,flag); + break; + case MO_INVESTIGATE: /* 発勁 */ + { + struct status_change *sc_data = battle_get_sc_data(src); + skill_attack(BF_WEAPON,src,src,bl,skillid,skilllv,tick,flag); + if(sc_data[SC_BLADESTOP].timer != -1) + skill_status_change_end(src,SC_BLADESTOP,-1); + } + break; + case SN_FALCONASSAULT: /* ファルコンアサルト */ + skill_attack(BF_MISC,src,src,bl,skillid,skilllv,tick,flag); + break; + case KN_BRANDISHSPEAR: /* ブランディッシュスピア */ + { + struct mob_data *md = (struct mob_data *)bl; + nullpo_retr(1, md); + skill_attack(BF_WEAPON,src,src,bl,skillid,skilllv,tick,flag); + if(md->hp > 0){ + skill_blown(src,bl,skill_get_blewcount(skillid,skilllv)); + if(bl->type == BL_MOB) + clif_fixmobpos((struct mob_data *)bl); + else if(bl->type == BL_PET) + clif_fixpetpos((struct pet_data *)bl); + else + clif_fixpos(bl); + } + } + break; + case RG_BACKSTAP: /* バックスタブ */ + { + int dir = map_calc_dir(src,bl->x,bl->y),t_dir = battle_get_dir(bl); + int dist = distance(src->x,src->y,bl->x,bl->y); + if((dist > 0 && !map_check_dir(dir,t_dir)) || bl->type == BL_SKILL) { + struct status_change *sc_data = battle_get_sc_data(src); + if(sc_data && sc_data[SC_HIDING].timer != -1) + skill_status_change_end(src, SC_HIDING, -1); // ハイディング解除 + skill_attack(BF_WEAPON,src,src,bl,skillid,skilllv,tick,flag); + skill_blown(src,bl,skill_get_blewcount(skillid,skilllv)); + } + else if(src->type == BL_PC) + clif_skill_fail(sd,sd->skillid,0,0); + } + break; + + case AM_ACIDTERROR: /* アシッドテラー */ + skill_attack(BF_WEAPON,src,src,bl,skillid,skilllv,tick,flag); + if(bl->type == BL_PC && rand()%100 < skill_get_time(skillid,skilllv) && battle_config.equipment_breaking) + pc_breakarmor((struct map_session_data *)bl); + break; + case MO_FINGEROFFENSIVE: /* 指弾 */ + { + struct status_change *sc_data = battle_get_sc_data(src); + + if(!battle_config.finger_offensive_type) + skill_attack(BF_WEAPON,src,src,bl,skillid,skilllv,tick,flag); + else { + skill_attack(BF_WEAPON,src,src,bl,skillid,skilllv,tick,flag); + if(sd) { + for(i=1;i<sd->spiritball_old;i++) + skill_addtimerskill(src,tick+i*200,bl->id,0,0,skillid,skilllv,BF_WEAPON,flag); + sd->canmove_tick = tick + (sd->spiritball_old-1)*200; + } + } + if(sc_data && sc_data[SC_BLADESTOP].timer != -1) + skill_status_change_end(src,SC_BLADESTOP,-1); + } + break; + case MO_CHAINCOMBO: /* 連打掌 */ + { + struct status_change *sc_data = battle_get_sc_data(src); + skill_attack(BF_WEAPON,src,src,bl,skillid,skilllv,tick,flag); + if(sc_data && sc_data[SC_BLADESTOP].timer != -1) + skill_status_change_end(src,SC_BLADESTOP,-1); + } + break; + case MO_COMBOFINISH: /* 猛龍拳 */ + case CH_TIGERFIST: /* 伏虎拳 */ + case CH_CHAINCRUSH: /* 連柱崩撃 */ + case CH_PALMSTRIKE: /* 猛虎硬派山 */ + skill_attack(BF_WEAPON,src,src,bl,skillid,skilllv,tick,flag); + break; + case MO_EXTREMITYFIST: /* 阿修羅覇鳳拳 */ + { + struct status_change *sc_data = battle_get_sc_data(src); + + if(sd) { + struct walkpath_data wpd; + int dx,dy; + + dx = bl->x - sd->bl.x; + dy = bl->y - sd->bl.y; + if(dx > 0) dx++; + else if(dx < 0) dx--; + if(dy > 0) dy++; + else if(dy < 0) dy--; + if(dx == 0 && dy == 0) dx++; + if(path_search(&wpd,src->m,sd->bl.x,sd->bl.y,sd->bl.x+dx,sd->bl.y+dy,1) == -1) { + dx = bl->x - sd->bl.x; + dy = bl->y - sd->bl.y; + if(path_search(&wpd,src->m,sd->bl.x,sd->bl.y,sd->bl.x+dx,sd->bl.y+dy,1) == -1) { + clif_skill_fail(sd,sd->skillid,0,0); + break; + } + } + sd->to_x = sd->bl.x + dx; + sd->to_y = sd->bl.y + dy; + skill_attack(BF_WEAPON,src,src,bl,skillid,skilllv,tick,flag); + clif_walkok(sd); + clif_movechar(sd); + if(dx < 0) dx = -dx; + if(dy < 0) dy = -dy; + sd->attackabletime = sd->canmove_tick = tick + 100 + sd->speed * ((dx > dy)? dx:dy); + if(sd->canact_tick < sd->canmove_tick) + sd->canact_tick = sd->canmove_tick; + pc_movepos(sd,sd->to_x,sd->to_y); + skill_status_change_end(&sd->bl,SC_COMBO,-1); + } + else + skill_attack(BF_WEAPON,src,src,bl,skillid,skilllv,tick,flag); + skill_status_change_end(src, SC_EXPLOSIONSPIRITS, -1); + if(sc_data && sc_data[SC_BLADESTOP].timer != -1) + skill_status_change_end(src,SC_BLADESTOP,-1); + } + break; + /* 武器系範囲攻撃スキル */ + case AC_SHOWER: /* アローシャワー */ + case SM_MAGNUM: /* マグナムブレイク */ + case AS_GRIMTOOTH: /* グリムトゥース */ + case MC_CARTREVOLUTION: /* カートレヴォリューション */ + case NPC_SPLASHATTACK: /* スプラッシュアタック */ + case ASC_METEORASSAULT: /* メテオアサルト */ + case AS_SPLASHER: /* [Valaris] */ + if(flag&1){ + /* 個別にダメージを与える */ + if(bl->id!=skill_area_temp[1]){ + int dist=0; + if(skillid==SM_MAGNUM){ /* マグナムブレイクなら中心からの距離を計算 */ + int dx=abs( bl->x - skill_area_temp[2] ); + int dy=abs( bl->y - skill_area_temp[3] ); + dist=((dx>dy)?dx:dy); + } + skill_attack(BF_WEAPON,src,src,bl,skillid,skilllv,tick, + 0x0500|dist ); + } + }else{ + int ar=1; + int x=bl->x,y=bl->y; + if( skillid==SM_MAGNUM){ + x=src->x; + y=src->y; + }else if(skillid==AC_SHOWER || skillid==ASC_METEORASSAULT) /* アローシャワー、メテオアサルト範囲5*5 */ + ar=2; + else if(skillid==AS_SPLASHER) /* ベナムスプラッシャー範囲3*3 */ + ar=1; + else if(skillid==NPC_SPLASHATTACK) /* スプラッシュアタックは範囲7*7 */ + ar=3; + skill_area_temp[1]=bl->id; + skill_area_temp[2]=x; + skill_area_temp[3]=y; + /* まずターゲットに攻撃を加える */ + skill_attack(BF_WEAPON,src,src,bl,skillid,skilllv,tick,0); + /* その後ターゲット以外の範囲内の敵全体に処理を行う */ + map_foreachinarea(skill_area_sub, + bl->m,x-ar,y-ar,x+ar,y+ar,0, + src,skillid,skilllv,tick, flag|BCT_ENEMY|1, + skill_castend_damage_id); + } + break; + + case KN_BOWLINGBASH: /* ボウリングバッシュ */ + if(flag&1){ + /* 個別にダメージを与える */ + if(bl->id!=skill_area_temp[1]) + skill_attack(BF_WEAPON,src,src,bl,skillid,skilllv,tick,0x0500); + } + else { + int damage; + map_freeblock_lock(); + damage = skill_attack(BF_WEAPON,src,src,bl,skillid,skilllv,tick,0); + if(damage > 0) { + int i,c; /* 他人から聞いた動きなので間違ってる可能性大&効率が悪いっす>< */ + c = skill_get_blewcount(skillid,skilllv); + if(map[bl->m].flag.gvg) c = 0; + for(i=0;i<c;i++){ + skill_blown(src,bl,1); + if(bl->type == BL_MOB) + clif_fixmobpos((struct mob_data *)bl); + else if(bl->type == BL_PET) + clif_fixpetpos((struct pet_data *)bl); + else + clif_fixpos(bl); + skill_area_temp[0]=0; + map_foreachinarea(skill_area_sub, + bl->m,bl->x-1,bl->y-1,bl->x+1,bl->y+1,0, + src,skillid,skilllv,tick, flag|BCT_ENEMY , + skill_area_sub_count); + if(skill_area_temp[0]>1) break; + } + skill_area_temp[1]=bl->id; + skill_area_temp[2]=bl->x; + skill_area_temp[3]=bl->y; + /* その後ターゲット以外の範囲内の敵全体に処理を行う */ + map_foreachinarea(skill_area_sub, + bl->m,bl->x-1,bl->y-1,bl->x+1,bl->y+1,0, + src,skillid,skilllv,tick, flag|BCT_ENEMY|1, + skill_castend_damage_id); + battle_damage(src,bl,damage,1); + if(rdamage > 0) + battle_damage(bl,src,rdamage,0); + } + map_freeblock_unlock(); + } + break; + + case ALL_RESURRECTION: /* リザレクション */ + case PR_TURNUNDEAD: /* ターンアンデッド */ + if(bl->type != BL_PC && battle_check_undead(battle_get_race(bl),battle_get_elem_type(bl))) + skill_attack(BF_MAGIC,src,src,bl,skillid,skilllv,tick,flag); + else { + map_freeblock_unlock(); + return 1; + } + break; + + /* 魔法系スキル */ + case MG_SOULSTRIKE: /* ソウルストライク */ + case MG_COLDBOLT: /* コールドボルト */ + case MG_FIREBOLT: /* ファイアーボルト */ + case MG_LIGHTNINGBOLT: /* ライトニングボルト */ + case WZ_EARTHSPIKE: /* アーススパイク */ + case AL_HEAL: /* ヒール */ + case AL_HOLYLIGHT: /* ホーリーライト */ + case MG_FROSTDIVER: /* フロストダイバー */ + case WZ_JUPITEL: /* ユピテルサンダー */ + case NPC_MAGICALATTACK: /* MOB:魔法打撃攻撃 */ + case PR_ASPERSIO: /* アスペルシオ */ + skill_attack(BF_MAGIC,src,src,bl,skillid,skilllv,tick,flag); + break; + + case WZ_WATERBALL: /* ウォーターボール */ + skill_attack(BF_MAGIC,src,src,bl,skillid,skilllv,tick,flag); + if(skilllv>1) + skill_status_change_start(src,SC_WATERBALL,skilllv,bl->id,0,0,0,0); + break; + + case PR_BENEDICTIO: /* 聖体降福 */ + if(battle_get_race(bl)==1 || battle_get_race(bl)==6) + skill_attack(BF_MAGIC,src,src,bl,skillid,skilllv,tick,flag); + break; + + /* 魔法系範囲攻撃スキル */ + case MG_NAPALMBEAT: /* ナパームビート */ + case MG_FIREBALL: /* ファイヤーボール */ + if(flag&1){ + /* 個別にダメージを与える */ + if(bl->id!=skill_area_temp[1]){ + if(skillid==MG_FIREBALL){ /* ファイヤーボールなら中心からの距離を計算 */ + int dx=abs( bl->x - skill_area_temp[2] ); + int dy=abs( bl->y - skill_area_temp[3] ); + skill_area_temp[0]=((dx>dy)?dx:dy); + } + skill_attack(BF_MAGIC,src,src,bl,skillid,skilllv,tick, + skill_area_temp[0]| 0x0500); + } + }else{ + int ar=(skillid==MG_NAPALMBEAT)?1:2; + skill_area_temp[1]=bl->id; + if(skillid==MG_NAPALMBEAT){ /* ナパームでは先に数える */ + skill_area_temp[0]=0; + map_foreachinarea(skill_area_sub, + bl->m,bl->x-1,bl->y-1,bl->x+1,bl->y+1,0, + src,skillid,skilllv,tick, flag|BCT_ENEMY , + skill_area_sub_count); + }else{ + skill_area_temp[0]=0; + skill_area_temp[2]=bl->x; + skill_area_temp[3]=bl->y; + } + /* まずターゲットに攻撃を加える */ + skill_attack(BF_MAGIC,src,src,bl,skillid,skilllv,tick, + skill_area_temp[0] ); + /* その後ターゲット以外の範囲内の敵全体に処理を行う */ + map_foreachinarea(skill_area_sub, + bl->m,bl->x-ar,bl->y-ar,bl->x+ar,bl->y+ar,0, + src,skillid,skilllv,tick, flag|BCT_ENEMY|1, + skill_castend_damage_id); + } + break; + + case HW_NAPALMVULCAN: // Fixed By SteelViruZ + if(flag&1){ + if(bl->id!=skill_area_temp[1]){ + skill_attack(BF_MAGIC,src,src,bl,skillid,skilllv,tick, + skill_area_temp[0]); + } + }else{ + int ar=(skillid==HW_NAPALMVULCAN)?1:2; + skill_area_temp[1]=bl->id; + if(skillid==HW_NAPALMVULCAN){ + skill_area_temp[0]=0; + map_foreachinarea(skill_area_sub, + bl->m,bl->x-1,bl->y-1,bl->x+1,bl->y+1,0, + src,skillid,skilllv,tick, flag|BCT_ENEMY , + skill_area_sub_count); + }else{ + skill_area_temp[0]=0; + skill_area_temp[2]=bl->x; + skill_area_temp[3]=bl->y; + } + skill_attack(BF_MAGIC,src,src,bl,skillid,skilllv,tick, + skill_area_temp[0] ); + map_foreachinarea(skill_area_sub, + bl->m,bl->x-ar,bl->y-ar,bl->x+ar,bl->y+ar,0, + src,skillid,skilllv,tick, flag|BCT_ENEMY|1, + skill_castend_damage_id); + } + break; + + case WZ_FROSTNOVA: /* フロストノヴァ */ + skill_castend_pos2(src,bl->x,bl->y,skillid,skilllv,tick,0); + skill_attack(BF_MAGIC,src,src,bl,skillid,skilllv,tick,flag); + break; + + case WZ_SIGHTRASHER: + clif_skill_nodamage(src,bl,skillid,skilllv,1); + skill_castend_pos2(src,bl->x,bl->y,skillid,skilllv,tick,0); + skill_status_change_end(src,SC_SIGHT,-1); + break; + + /* その他 */ + case HT_BLITZBEAT: /* ブリッツビート */ + if(flag&1){ + /* 個別にダメージを与える */ + if(bl->id!=skill_area_temp[1]) + skill_attack(BF_MISC,src,src,bl,skillid,skilllv,tick,skill_area_temp[0]|(flag&0xf00000)); + }else{ + skill_area_temp[0]=0; + skill_area_temp[1]=bl->id; + if(flag&0xf00000) + map_foreachinarea(skill_area_sub,bl->m,bl->x-1,bl->y-1,bl->x+1,bl->y+1,0, + src,skillid,skilllv,tick, flag|BCT_ENEMY ,skill_area_sub_count); + /* まずターゲットに攻撃を加える */ + skill_attack(BF_MISC,src,src,bl,skillid,skilllv,tick,skill_area_temp[0]|(flag&0xf00000)); + /* その後ターゲット以外の範囲内の敵全体に処理を行う */ + map_foreachinarea(skill_area_sub, + bl->m,bl->x-1,bl->y-1,bl->x+1,bl->y+1,0, + src,skillid,skilllv,tick, flag|BCT_ENEMY|1, + skill_castend_damage_id); + } + break; + + case CR_GRANDCROSS: /* グランドクロス */ + /* スキルユニット配置 */ + skill_castend_pos2(src,bl->x,bl->y,skillid,skilllv,tick,0); + if(sd) + sd->canmove_tick = tick + 1000; + else if(src->type == BL_MOB) + mob_changestate((struct mob_data *)src,MS_DELAY,1000); + break; + + case TF_THROWSTONE: /* 石投げ */ + case NPC_SMOKING: /* スモーキング */ + skill_attack(BF_MISC,src,src,bl,skillid,skilllv,tick,0 ); + break; + + case NPC_SELFDESTRUCTION: /* 自爆 */ + case NPC_SELFDESTRUCTION2: /* 自爆2 */ + if(flag&1){ + /* 個別にダメージを与える */ + if(src->type==BL_MOB){ + struct mob_data* mb = (struct mob_data*)src; + nullpo_retr(1, mb); + mb->hp=skill_area_temp[2]; + if(bl->id!=skill_area_temp[1]) + skill_attack(BF_MISC,src,src,bl,NPC_SELFDESTRUCTION,skilllv,tick,flag ); + mb->hp=1; + } + }else{ + struct mob_data *md; + if((md=(struct mob_data *)src)){ + skill_area_temp[1]=bl->id; + skill_area_temp[2]=battle_get_hp(src); + clif_skill_nodamage(src,src,NPC_SELFDESTRUCTION,-1,1); + map_foreachinarea(skill_area_sub, + bl->m,bl->x-5,bl->y-5,bl->x+5,bl->y+5,0, + src,skillid,skilllv,tick, flag|BCT_ENEMY|1, + skill_castend_damage_id); + battle_damage(src,src,md->hp,0); + } + } + break; + + /* HP吸収/HP吸収魔法 */ + case NPC_BLOODDRAIN: + case NPC_ENERGYDRAIN: + { + int heal; + heal = skill_attack((skillid==NPC_BLOODDRAIN)?BF_WEAPON:BF_MAGIC,src,src,bl,skillid,skilllv,tick,flag); + if( heal > 0 ){ + struct block_list tbl; + tbl.id = 0; + tbl.m = src->m; + tbl.x = src->x; + tbl.y = src->y; + clif_skill_nodamage(&tbl,src,AL_HEAL,heal,1); + battle_heal(NULL,src,heal,0,0); + } + } + break; + case 0: + if(sd) { + if(flag&3){ + if(bl->id!=skill_area_temp[1]) + skill_attack(BF_WEAPON,src,src,bl,skillid,skilllv,tick,0x0500); + } + else{ + int ar=sd->splash_range; + skill_area_temp[1]=bl->id; + map_foreachinarea(skill_area_sub, + bl->m, bl->x - ar, bl->y - ar, bl->x + ar, bl->y + ar, 0, + src, skillid, skilllv, tick, flag | BCT_ENEMY | 1, + skill_castend_damage_id); + } + } + break; + + default: + map_freeblock_unlock(); + return 1; + } + map_freeblock_unlock(); + + return 0; +} + +/*========================================== + * スキル使用(詠唱完了、ID指定支援系) + *------------------------------------------ + */ +int skill_castend_nodamage_id( struct block_list *src, struct block_list *bl,int skillid,int skilllv,unsigned int tick,int flag ) +{ + struct map_session_data *sd=NULL; + struct map_session_data *dstsd=NULL; + struct mob_data *md=NULL; + struct mob_data *dstmd=NULL; + int i,abra_skillid=0,abra_skilllv; + int sc_def_vit,sc_def_mdef,strip_fix,strip_time,strip_per; + int sc_dex,sc_luk; + //クラスチェンジ用ボスモンスターID + int changeclass[]={1038,1039,1046,1059,1086,1087,1112,1115 + ,1157,1159,1190,1272,1312,1373,1492}; + int poringclass[]={1002}; + + nullpo_retr(1, src); + nullpo_retr(1, bl); + + if(src->type==BL_PC) + sd=(struct map_session_data *)src; + else if(src->type==BL_MOB) + md=(struct mob_data *)src; + + sc_dex=battle_get_mdef(bl); + sc_luk=battle_get_luk(bl); + sc_def_vit = 100 - (3 + battle_get_vit(bl) + battle_get_luk(bl)/3); + sc_def_vit = 100 - (3 + battle_get_vit(bl) + battle_get_luk(bl)/3); + sc_def_mdef = 100 - (3 + battle_get_mdef(bl) + battle_get_luk(bl)/3); + strip_fix = battle_get_dex(src) - battle_get_dex(bl); + + if(bl->type==BL_PC){ + nullpo_retr(1, dstsd=(struct map_session_data *)bl); + }else if(bl->type==BL_MOB){ + nullpo_retr(1, dstmd=(struct mob_data *)bl); + if(sc_def_vit>50) + sc_def_vit=50; + if(sc_def_mdef>50) + sc_def_mdef=50; + } + if(sc_def_vit < 0) + sc_def_vit=0; + if(sc_def_mdef < 0) + sc_def_mdef=0; + if(strip_fix < 0) + strip_fix=0; + + if(bl == NULL || bl->prev == NULL) + return 1; + if(sd && pc_isdead(sd)) + return 1; + if(dstsd && pc_isdead(dstsd) && skillid != ALL_RESURRECTION) + return 1; + if(battle_get_class(bl) == 1288) + return 1; + if (skillnotok(skillid, (struct map_session_data *)bl)) // [MouseJstr] + return 0; + + map_freeblock_lock(); + switch(skillid) + { + case AL_HEAL: /* ヒール */ + { + int heal=skill_calc_heal( src, skilllv ); + int heal_get_jobexp; + int skill; + struct pc_base_job s_class; + + if( dstsd && dstsd->special_state.no_magic_damage ) + heal=0; /* 黄金蟲カード(ヒール量0) */ + if (sd){ + s_class = pc_calc_base_job(sd->status.class); + if((skill=pc_checkskill(sd,HP_MEDITATIO))>0) // メディテイティオ + heal += heal*(skill*2/100); + if(sd && dstsd && sd->status.partner_id == dstsd->status.char_id && s_class.job == 23 && sd->status.sex == 0) //自分も対象もPC、対象が自分のパートナー、自分がスパノビ、自分が♀なら + heal = heal*2; //スパノビの嫁が旦那にヒールすると2倍になる + } + + + clif_skill_nodamage(src,bl,skillid,heal,1); + heal_get_jobexp = battle_heal(NULL,bl,heal,0,0); + + // JOB経験値獲得 + if(src->type == BL_PC && bl->type==BL_PC && heal > 0 && src != bl && battle_config.heal_exp > 0){ + heal_get_jobexp = heal_get_jobexp * battle_config.heal_exp / 100; + if(heal_get_jobexp <= 0) + heal_get_jobexp = 1; + pc_gainexp((struct map_session_data *)src,0,heal_get_jobexp); + } + } + break; + + case ALL_RESURRECTION: /* リザレクション */ + if(bl->type==BL_PC){ + int per=0; + struct map_session_data *tsd = (struct map_session_data*)bl; + nullpo_retr(1, tsd); + if( (map[bl->m].flag.pvp) && tsd->pvp_point<0 ) + break; /* PVPで復活不可能状態 */ + + if(pc_isdead(tsd)){ /* 死亡判定 */ + clif_skill_nodamage(src,bl,skillid,skilllv,1); + switch(skilllv){ + case 1: per=10; break; + case 2: per=30; break; + case 3: per=50; break; + case 4: per=80; break; + } + tsd->status.hp=tsd->status.max_hp*per/100; + if(tsd->status.hp<=0) tsd->status.hp=1; + if(tsd->special_state.restart_full_recover ){ /* オシリスカード */ + tsd->status.hp=tsd->status.max_hp; + tsd->status.sp=tsd->status.max_sp; + } + pc_setstand(tsd); + if(battle_config.pc_invincible_time > 0) + pc_setinvincibletimer(tsd,battle_config.pc_invincible_time); + clif_updatestatus(tsd,SP_HP); + clif_resurrection(&tsd->bl,1); + if(src != bl && sd && battle_config.resurrection_exp > 0) { + int exp = 0,jexp = 0; + int lv = tsd->status.base_level - sd->status.base_level, jlv = tsd->status.job_level - sd->status.job_level; + if(lv > 0) { + exp = (int)((double)tsd->status.base_exp * (double)lv * (double)battle_config.resurrection_exp / 1000000.); + if(exp < 1) exp = 1; + } + if(jlv > 0) { + jexp = (int)((double)tsd->status.job_exp * (double)lv * (double)battle_config.resurrection_exp / 1000000.); + if(jexp < 1) jexp = 1; + } + if(exp > 0 || jexp > 0) + pc_gainexp(sd,exp,jexp); + } + } + } + break; + + case AL_DECAGI: /* 速度減少 */ + if( bl->type==BL_PC && ((struct map_session_data *)bl)->special_state.no_magic_damage ) + break; + if( rand()%100 < (50+skilllv*3+(battle_get_lv(src)+battle_get_int(src)/5)-sc_def_mdef) ) { + clif_skill_nodamage(src,bl,skillid,skilllv,1); + skill_status_change_start(bl,SkillStatusChangeTable[skillid],skilllv,0,0,0,skill_get_time(skillid,skilllv),0); + } + break; + + case AL_CRUCIS: + if(flag&1) { + int race = battle_get_race(bl),ele = battle_get_elem_type(bl); + if(battle_check_target(src,bl,BCT_ENEMY) && (race == 6 || battle_check_undead(race,ele))) { + int slv=battle_get_lv(src),tlv=battle_get_lv(bl),rate; + rate = 25 + skilllv*2 + slv - tlv; + if(rand()%100 < rate) + skill_status_change_start(bl,SkillStatusChangeTable[skillid],skilllv,0,0,0,0,0); + } + } + else { + int range = 15; + clif_skill_nodamage(src,bl,skillid,skilllv,1); + map_foreachinarea(skill_area_sub, + src->m,src->x-range,src->y-range,src->x+range,src->y+range,0, + src,skillid,skilllv,tick, flag|BCT_ENEMY|1, + skill_castend_nodamage_id); + } + break; + + case PR_LEXDIVINA: /* レックスディビーナ */ + { + struct status_change *sc_data = battle_get_sc_data(bl); + clif_skill_nodamage(src,bl,skillid,skilllv,1); + if( bl->type==BL_PC && ((struct map_session_data *)bl)->special_state.no_magic_damage ) + break; + if(sc_data && sc_data[SC_DIVINA].timer != -1) + skill_status_change_end(bl,SC_DIVINA,-1); + else if( rand()%100 < sc_def_vit ) { + skill_status_change_start(bl,SkillStatusChangeTable[skillid],skilllv,0,0,0,skill_get_time(skillid,skilllv),0); + } + } + break; + case SA_ABRACADABRA: + //require 1 yellow gemstone even with mistress card or Into the Abyss + if (pc_search_inventory(sd, 715) <= 0 ) { + clif_skill_fail(sd,sd->skillid,0,0); + break; + } + pc_delitem(sd, pc_search_inventory(sd, 715), 1, 0); + // + do{ + abra_skillid=skill_abra_dataset(skilllv); + }while(abra_skillid == 0); + abra_skilllv=skill_get_max(abra_skillid)>pc_checkskill(sd,SA_ABRACADABRA)?pc_checkskill(sd,SA_ABRACADABRA):skill_get_max(abra_skillid); + clif_skill_nodamage(src,bl,skillid,skilllv,1); + sd->skillitem=abra_skillid; + sd->skillitemlv=abra_skilllv; + clif_item_skill(sd,abra_skillid,abra_skilllv,"アブラカダブラ"); + break; + case SA_COMA: + clif_skill_nodamage(src,bl,skillid,skilllv,1); + if( bl->type==BL_PC && ((struct map_session_data *)bl)->special_state.no_magic_damage ) + break; + if(dstsd){ + dstsd->status.hp=1; + dstsd->status.sp=1; + clif_updatestatus(dstsd,SP_HP); + clif_updatestatus(dstsd,SP_SP); + } + if(dstmd) dstmd->hp=1; + break; + case SA_FULLRECOVERY: + clif_skill_nodamage(src,bl,skillid,skilllv,1); + if( bl->type==BL_PC && ((struct map_session_data *)bl)->special_state.no_magic_damage ) + break; + if(dstsd) pc_heal(dstsd,dstsd->status.max_hp,dstsd->status.max_sp); + if(dstmd) dstmd->hp=battle_get_max_hp(&dstmd->bl); + break; + case SA_SUMMONMONSTER: + clif_skill_nodamage(src,bl,skillid,skilllv,1); + if (sd) mob_once_spawn(sd,map[sd->bl.m].name,sd->bl.x,sd->bl.y,"--ja--",-1,1,""); + break; + case SA_LEVELUP: + clif_skill_nodamage(src,bl,skillid,skilllv,1); + if (sd && pc_nextbaseexp(sd)) pc_gainexp(sd,pc_nextbaseexp(sd)*10/100,0); + break; + + case SA_INSTANTDEATH: + clif_skill_nodamage(src,bl,skillid,skilllv,1); + if (sd) pc_damage(NULL,sd,sd->status.max_hp); + break; + + case SA_QUESTION: + case SA_GRAVITY: + clif_skill_nodamage(src,bl,skillid,skilllv,1); + break; + case SA_CLASSCHANGE: + clif_skill_nodamage(src,bl,skillid,skilllv,1); + if(dstmd) mob_class_change(dstmd,changeclass); + break; + case SA_MONOCELL: + clif_skill_nodamage(src,bl,skillid,skilllv,1); + if(dstmd) mob_class_change(dstmd,poringclass); + break; + case SA_DEATH: + clif_skill_nodamage(src,bl,skillid,skilllv,1); + if (dstsd) pc_damage(NULL,dstsd,dstsd->status.max_hp); + if (dstmd) mob_damage(NULL,dstmd,dstmd->hp,1); + break; + case SA_REVERSEORCISH: + clif_skill_nodamage(src,bl,skillid,skilllv,1); + if (dstsd) pc_setoption(dstsd,dstsd->status.option|0x0800); + break; + case SA_FORTUNE: + clif_skill_nodamage(src,bl,skillid,skilllv,1); + if(sd) pc_getzeny(sd,battle_get_lv(bl)*100); + break; + case SA_TAMINGMONSTER: + clif_skill_nodamage(src,bl,skillid,skilllv,1); + if (dstmd){ + for(i=0;i<MAX_PET_DB;i++){ + if(dstmd->class == pet_db[i].class){ + pet_catch_process1(sd,dstmd->class); + break; + } + } + } + break; + case AL_INCAGI: /* 速度増加 */ + case AL_BLESSING: /* ブレッシング */ + case PR_SLOWPOISON: + case PR_IMPOSITIO: /* イムポシティオマヌス */ + case PR_LEXAETERNA: /* レックスエーテルナ */ + case PR_SUFFRAGIUM: /* サフラギウム */ + case PR_BENEDICTIO: /* 聖体降福 */ + case CR_PROVIDENCE: /* プロヴィデンス */ + case CG_MARIONETTE: /* マリオネットコントロール */ + if( bl->type==BL_PC && ((struct map_session_data *)bl)->special_state.no_magic_damage ){ + clif_skill_nodamage(src,bl,skillid,skilllv,1); + }else{ + skill_status_change_start(bl,SkillStatusChangeTable[skillid],skilllv,0,0,0,skill_get_time(skillid,skilllv),0 ); + clif_skill_nodamage(src,bl,skillid,skilllv,1); + } + break; + + case SA_FLAMELAUNCHER: // added failure chance and chance to break weapon if turned on [Valaris] + case SA_FROSTWEAPON: + case SA_LIGHTNINGLOADER: + case SA_SEISMICWEAPON: + if(bl->type==BL_PC && ((struct map_session_data *)bl)->special_state.no_magic_damage ){ + clif_skill_nodamage(src,bl,skillid,skilllv,0); + break; + } + if(bl->type==BL_PC) { + struct map_session_data *sd2=(struct map_session_data *)bl; + if(sd2->status.weapon==0 || sd2->sc_data[SC_FLAMELAUNCHER].timer!=-1 || sd2->sc_data[SC_FROSTWEAPON].timer!=-1 || + sd2->sc_data[SC_LIGHTNINGLOADER].timer!=-1 || sd2->sc_data[SC_SEISMICWEAPON].timer!=-1 || + sd2->sc_data[SC_ENCPOISON].timer!=-1) { + clif_skill_fail(sd,skillid,0,0); + clif_skill_nodamage(src,bl,skillid,skilllv,0); + break; + } + } + if(rand()%100 > (75+skilllv*1) && (skilllv != 5)) { + clif_skill_fail(sd,skillid,0,0); + clif_skill_nodamage(src,bl,skillid,skilllv,0); + if(bl->type==BL_PC && battle_config.equipment_breaking) { + struct map_session_data *sd2=(struct map_session_data *)bl; + if(sd!=sd2) clif_displaymessage(sd->fd,"You broke target's weapon"); + pc_breakweapon(sd2); + } + break; + } + else { + skill_status_change_start(bl,SkillStatusChangeTable[skillid],skilllv,0,0,0,skill_get_time(skillid,skilllv),0 ); + clif_skill_nodamage(src,bl,skillid,skilllv,1); + } + break; + + case PR_ASPERSIO: /* アスペルシオ */ + clif_skill_nodamage(src,bl,skillid,skilllv,1); + if( bl->type==BL_PC && ((struct map_session_data *)bl)->special_state.no_magic_damage ) + break; + if(bl->type==BL_MOB) + break; + skill_status_change_start(bl,SkillStatusChangeTable[skillid],skilllv,0,0,0,skill_get_time(skillid,skilllv),0 ); + break; + case PR_KYRIE: /* キリエエレイソン */ + clif_skill_nodamage(bl,bl,skillid,skilllv,1); + if( bl->type==BL_PC && ((struct map_session_data *)bl)->special_state.no_magic_damage ) + break; + skill_status_change_start(bl,SkillStatusChangeTable[skillid],skilllv,0,0,0,skill_get_time(skillid,skilllv),0 ); + break; + case KN_AUTOCOUNTER: /* オートカウンター */ + case KN_TWOHANDQUICKEN: /* ツーハンドクイッケン */ + case CR_SPEARQUICKEN: /* スピアクイッケン */ + case CR_REFLECTSHIELD: + case AS_POISONREACT: /* ポイズンリアクト */ + case MC_LOUD: /* ラウドボイス */ + case MG_ENERGYCOAT: /* エナジーコート */ + case SM_ENDURE: /* インデュア */ + case MG_SIGHT: /* サイト */ + case AL_RUWACH: /* ルアフ */ + case MO_EXPLOSIONSPIRITS: // 爆裂波動 + case MO_STEELBODY: // 金剛 + case LK_AURABLADE: /* オーラブレード */ + case LK_PARRYING: /* パリイング */ + case LK_CONCENTRATION: /* コンセントレーション */ + case LK_BERSERK: /* バーサーク */ + case HP_ASSUMPTIO: /* */ + case WS_CARTBOOST: /* カートブースト */ + case SN_SIGHT: /* トゥルーサイト */ + case WS_MELTDOWN: /* メルトダウン */ + case ST_REJECTSWORD: /* リジェクトソード */ + case HW_MAGICPOWER: /* 魔法力増幅 */ + case PF_MEMORIZE: /* メモライズ */ + clif_skill_nodamage(src,bl,skillid,skilllv,1); + skill_status_change_start(bl,SkillStatusChangeTable[skillid],skilllv,0,0,0,skill_get_time(skillid,skilllv),0 ); + break; + case AS_ENCHANTPOISON: // Prevent spamming [Valaris] + if(bl->type==BL_PC) { + struct map_session_data *sd2=(struct map_session_data *)bl; + if(sd2->sc_data[SC_FLAMELAUNCHER].timer!=-1 || sd2->sc_data[SC_FROSTWEAPON].timer!=-1 || + sd2->sc_data[SC_LIGHTNINGLOADER].timer!=-1 || sd2->sc_data[SC_SEISMICWEAPON].timer!=-1 || + sd2->sc_data[SC_ENCPOISON].timer!=-1) { + clif_skill_nodamage(src,bl,skillid,skilllv,0); + clif_skill_fail(sd,skillid,0,0); + break; + } + } + clif_skill_nodamage(src,bl,skillid,skilllv,1); + skill_status_change_start(bl,SkillStatusChangeTable[skillid],skilllv,0,0,0,skill_get_time(skillid,skilllv),0 ); + break; + case LK_TENSIONRELAX: /* テンションリラックス */ + clif_skill_nodamage(src,bl,skillid,skilllv,1); + pc_setsit(sd); + clif_sitting(sd->fd,sd); + skill_status_change_start(bl,SkillStatusChangeTable[skillid],skilllv,0,0,0,skill_get_time(skillid,skilllv),0 ); + break; + case MC_CHANGECART: + clif_skill_nodamage(src,bl,skillid,skilllv,1); + break; + case AC_CONCENTRATION: /* 集中力向上 */ + { + int range = 1; + clif_skill_nodamage(src,bl,skillid,skilllv,1); + skill_status_change_start(bl,SkillStatusChangeTable[skillid],skilllv,0,0,0,skill_get_time(skillid,skilllv),0 ); + map_foreachinarea( skill_status_change_timer_sub, + src->m, src->x-range, src->y-range, src->x+range,src->y+range,0, + src,SkillStatusChangeTable[skillid],tick); + } + break; + case SM_PROVOKE: /* プロボック */ + { + struct status_change *sc_data = battle_get_sc_data(bl); + + /* MVPmobと不死には効かない */ + if((bl->type==BL_MOB && battle_get_mode(bl)&0x20) || battle_check_undead(battle_get_race(bl),battle_get_elem_type(bl))) //不死には効かない + { + map_freeblock_unlock(); + return 1; + } + + clif_skill_nodamage(src,bl,skillid,skilllv,1); + skill_status_change_start(bl,SkillStatusChangeTable[skillid],skilllv,0,0,0,skill_get_time(skillid,skilllv),0 ); + + if(dstmd && dstmd->skilltimer!=-1 && dstmd->state.skillcastcancel) // 詠唱妨害 + skill_castcancel(bl,0); + if(dstsd && dstsd->skilltimer!=-1 && (!dstsd->special_state.no_castcancel || map[bl->m].flag.gvg) + && dstsd->state.skillcastcancel && !dstsd->special_state.no_castcancel2) + skill_castcancel(bl,0); + + if(sc_data){ + if(sc_data[SC_FREEZE].timer!=-1) + skill_status_change_end(bl,SC_FREEZE,-1); + if(sc_data[SC_STONE].timer!=-1 && sc_data[SC_STONE].val2==0) + skill_status_change_end(bl,SC_STONE,-1); + if(sc_data[SC_SLEEP].timer!=-1) + skill_status_change_end(bl,SC_SLEEP,-1); + } + + if(bl->type==BL_MOB) { + int range = skill_get_range(skillid,skilllv); + if(range < 0) + range = battle_get_range(src) - (range + 1); + mob_target((struct mob_data *)bl,src,range); + } + } + break; + + case CR_DEVOTION: /* ディボーション */ + if(sd && dstsd){ + //転生や養子の場合の元の職業を算出する + + int lv = sd->status.base_level-dstsd->status.base_level; + lv = (lv<0)?-lv:lv; + if((dstsd->bl.type!=BL_PC) // 相手はPCじゃないとだめ + ||(sd->bl.id == dstsd->bl.id) // 相手が自分はだめ + ||(lv > 10) // レベル差±10まで + ||(!sd->status.party_id && !sd->status.guild_id) // PTにもギルドにも所属無しはだめ + ||((sd->status.party_id != dstsd->status.party_id) // 同じパーティーか、 + ||(sd->status.guild_id != dstsd->status.guild_id)) // 同じギルドじゃないとだめ + ||(dstsd->status.class==14||dstsd->status.class==21 + ||dstsd->status.class==4015||dstsd->status.class==4022)){ // クルセだめ + clif_skill_fail(sd,skillid,0,0); + map_freeblock_unlock(); + return 1; + } + for(i=0;i<skilllv;i++){ + if(!sd->dev.val1[i]){ // 空きがあったら入れる + sd->dev.val1[i] = bl->id; + sd->dev.val2[i] = bl->id; + break; + }else if(i==skilllv-1){ // 空きがなかった + clif_skill_fail(sd,skillid,0,0); + map_freeblock_unlock(); + return 1; + } + } + clif_skill_nodamage(src,bl,skillid,skilllv,1); + clif_devotion(sd,bl->id); + skill_status_change_start(bl,SkillStatusChangeTable[skillid],src->id,1,0,0,1000*(15+15*skilllv),0 ); + } + else clif_skill_fail(sd,skillid,0,0); + break; + case MO_CALLSPIRITS: // 気功 + if(sd) { + clif_skill_nodamage(src,bl,skillid,skilllv,1); + pc_addspiritball(sd,skill_get_time(skillid,skilllv),skilllv); + } + break; + case CH_SOULCOLLECT: // 狂気功 + if(sd) { + clif_skill_nodamage(src,bl,skillid,skilllv,1); + for(i=0;i<5;i++) + pc_addspiritball(sd,skill_get_time(skillid,skilllv),5); + } + break; + case MO_BLADESTOP: // 白刃取り + clif_skill_nodamage(src,bl,skillid,skilllv,1); + skill_status_change_start(src,SkillStatusChangeTable[skillid],skilllv,0,0,0,skill_get_time(skillid,skilllv),0 ); + break; + case MO_ABSORBSPIRITS: // 気奪 + i=0; + if(sd && dstsd) { + if(sd == dstsd || map[sd->bl.m].flag.pvp || map[sd->bl.m].flag.gvg) { + if(dstsd->spiritball > 0) { + clif_skill_nodamage(src,bl,skillid,skilllv,1); + i = dstsd->spiritball * 7; + pc_delspiritball(dstsd,dstsd->spiritball,0); + if(i > 0x7FFF) + i = 0x7FFF; + if(sd->status.sp + i > sd->status.max_sp) + i = sd->status.max_sp - sd->status.sp; + } + } + }else if(sd && dstmd){ //対象がモンスターの場合 + //20%の確率で対象のLv*2のSPを回復する。成功したときはターゲット(σ゚Д゚)σゲッツ!! + if(rand()%100<20){ + i=2*mob_db[dstmd->class].lv; + mob_target(dstmd,src,0); + } + } + if(i){ + sd->status.sp += i; + clif_heal(sd->fd,SP_SP,i); + } + else + clif_skill_nodamage(src,bl,skillid,skilllv,0); + break; + + case AC_MAKINGARROW: /* 矢作成 */ + if(sd) { + clif_arrow_create_list(sd); + clif_skill_nodamage(src,bl,skillid,skilllv,1); + } + break; + + case AM_PHARMACY: /* ポーション作成 */ + if(sd) { + clif_skill_produce_mix_list(sd,32); + clif_skill_nodamage(src,bl,skillid,skilllv,1); + } + break; + case WS_CREATECOIN: /* クリエイトコイン */ + if(sd) { + clif_skill_produce_mix_list(sd,64); + clif_skill_nodamage(src,bl,skillid,skilllv,1); + } + break; + case WS_CREATENUGGET: /* 塊製造 */ + if(sd) { + clif_skill_produce_mix_list(sd,128); + clif_skill_nodamage(src,bl,skillid,skilllv,1); + } + break; + case BS_HAMMERFALL: /* ハンマーフォール */ + clif_skill_nodamage(src,bl,skillid,skilllv,1); + if( bl->type==BL_PC && ((struct map_session_data *)bl)->special_state.no_weapon_damage ) + break; + if( rand()%100 < (20+ 10*skilllv)*sc_def_vit/100 ) { + skill_status_change_start(bl,SC_STAN,skilllv,0,0,0,skill_get_time2(skillid,skilllv),0); + } + break; + + case RG_RAID: /* サプライズアタック */ + clif_skill_nodamage(src,bl,skillid,skilllv,1); + { + int x=bl->x,y=bl->y; + skill_area_temp[1]=bl->id; + skill_area_temp[2]=x; + skill_area_temp[3]=y; + map_foreachinarea(skill_area_sub, + bl->m,x-1,y-1,x+1,y+1,0, + src,skillid,skilllv,tick, flag|BCT_ENEMY|1, + skill_castend_damage_id); + } + skill_status_change_end(src, SC_HIDING, -1); // ハイディング解除 + break; + + case KN_BRANDISHSPEAR: /*ブランディッシュスピア*/ + { + int c,n=4,ar; + int dir = map_calc_dir(src,bl->x,bl->y); + struct square tc; + int x=bl->x,y=bl->y; + ar=skilllv/3; + skill_brandishspear_first(&tc,dir,x,y); + skill_brandishspear_dir(&tc,dir,4); + /* 範囲C */ + if(skilllv == 10){ + for(c=1;c<4;c++){ + map_foreachinarea(skill_area_sub, + bl->m,tc.val1[c],tc.val2[c],tc.val1[c],tc.val2[c],0, + src,skillid,skilllv,tick, flag|BCT_ENEMY|n, + skill_castend_damage_id); + } + } + /* 範囲BA */ + if(skilllv > 6){ + skill_brandishspear_dir(&tc,dir,-1); + n--; + }else{ + skill_brandishspear_dir(&tc,dir,-2); + n-=2; + } + + if(skilllv > 3){ + for(c=0;c<5;c++){ + map_foreachinarea(skill_area_sub, + bl->m,tc.val1[c],tc.val2[c],tc.val1[c],tc.val2[c],0, + src,skillid,skilllv,tick, flag|BCT_ENEMY|n, + skill_castend_damage_id); + if(skilllv > 6 && n==3 && c==4){ + skill_brandishspear_dir(&tc,dir,-1); + n--;c=-1; + } + } + } + /* 範囲@ */ + for(c=0;c<10;c++){ + if(c==0||c==5) skill_brandishspear_dir(&tc,dir,-1); + map_foreachinarea(skill_area_sub, + bl->m,tc.val1[c%5],tc.val2[c%5],tc.val1[c%5],tc.val2[c%5],0, + src,skillid,skilllv,tick, flag|BCT_ENEMY|1, + skill_castend_damage_id); + } + } + break; + + /* パーティスキル */ + case AL_ANGELUS: /* エンジェラス */ + case PR_MAGNIFICAT: /* マグニフィカート */ + case PR_GLORIA: /* グロリア */ + case SN_WINDWALK: /* ウインドウォーク */ + if(sd == NULL || sd->status.party_id==0 || (flag&1) ){ + /* 個別の処理 */ + clif_skill_nodamage(bl,bl,skillid,skilllv,1); + if( bl->type==BL_PC && ((struct map_session_data *)bl)->special_state.no_magic_damage ) + break; + skill_status_change_start(bl,SkillStatusChangeTable[skillid],skilllv,0,0,0,skill_get_time(skillid,skilllv),0); + } + else{ + /* パーティ全体への処理 */ + party_foreachsamemap(skill_area_sub, + sd,1, + src,skillid,skilllv,tick, flag|BCT_PARTY|1, + skill_castend_nodamage_id); + } + break; + case BS_ADRENALINE: /* アドレナリンラッシュ */ + case BS_WEAPONPERFECT: /* ウェポンパーフェクション */ + case BS_OVERTHRUST: /* オーバートラスト */ + if(sd == NULL || sd->status.party_id==0 || (flag&1) ){ + /* 個別の処理 */ + clif_skill_nodamage(bl,bl,skillid,skilllv,1); + skill_status_change_start(bl,SkillStatusChangeTable[skillid],skilllv,(src == bl)? 1:0,0,0,skill_get_time(skillid,skilllv),0); + } + else{ + /* パーティ全体への処理 */ + party_foreachsamemap(skill_area_sub, + sd,1, + src,skillid,skilllv,tick, flag|BCT_PARTY|1, + skill_castend_nodamage_id); + } + break; + + /*(付加と解除が必要) */ + case BS_MAXIMIZE: /* マキシマイズパワー */ + case NV_TRICKDEAD: /* 死んだふり */ + case CR_DEFENDER: /* ディフェンダー */ + case CR_AUTOGUARD: /* オートガード */ + { + struct status_change *tsc_data = battle_get_sc_data(bl); + int sc=SkillStatusChangeTable[skillid]; + clif_skill_nodamage(src,bl,skillid,skilllv,1); + if( tsc_data ){ + if( tsc_data[sc].timer==-1 ) + /* 付加する */ + skill_status_change_start(bl,sc,skilllv,0,0,0,skill_get_time(skillid,skilllv),0); + else + /* 解除する */ + skill_status_change_end(bl, sc, -1); + } + } + break; + + case TF_HIDING: /* ハイディング */ + { + struct status_change *tsc_data = battle_get_sc_data(bl); + int sc=SkillStatusChangeTable[skillid]; + clif_skill_nodamage(src,bl,skillid,-1,1); + if( tsc_data ){ + if( tsc_data[sc].timer==-1 ) + /* 付加する */ + skill_status_change_start(bl,sc,skilllv,0,0,0,skill_get_time(skillid,skilllv),0); + else + /* 解除する */ + skill_status_change_end(bl, sc, -1); + } + } + break; + + case AS_CLOAKING: /* クローキング */ + { + struct status_change *tsc_data = battle_get_sc_data(bl); + int sc=SkillStatusChangeTable[skillid]; + clif_skill_nodamage(src,bl,skillid,-1,1); + if( tsc_data ){ + if( tsc_data[sc].timer==-1 ) + /* 付加する */ + skill_status_change_start(bl,sc,skilllv,0,0,0,skill_get_time(skillid,skilllv),0); + else + /* 解除する */ + skill_status_change_end(bl, sc, -1); + } + + skill_check_cloaking(bl); + } + break; + + case ST_CHASEWALK: /* ハイディング */ + { + struct status_change *tsc_data = battle_get_sc_data(bl); + int sc=SkillStatusChangeTable[skillid]; + clif_skill_nodamage(src,bl,skillid,-1,1); + if( tsc_data ){ + if( tsc_data[sc].timer==-1 ) + /* 付加する */ + skill_status_change_start(bl,sc,skilllv,0,0,0,skill_get_time(skillid,skilllv),0); + else + /* 解除する */ + skill_status_change_end(bl, sc, -1); + } + } + break; + + /* 対地スキル */ + case BD_LULLABY: /* 子守唄 */ + case BD_RICHMANKIM: /* ニヨルドの宴 */ + case BD_ETERNALCHAOS: /* 永遠の混沌 */ + case BD_DRUMBATTLEFIELD: /* 戦太鼓の響き */ + case BD_RINGNIBELUNGEN: /* ニーベルングの指輪 */ + case BD_ROKISWEIL: /* ロキの叫び */ + case BD_INTOABYSS: /* 深淵の中に */ + case BD_SIEGFRIED: /* 不死身のジークフリード */ + case BA_DISSONANCE: /* 不協和音 */ + case BA_POEMBRAGI: /* ブラギの詩 */ + case BA_WHISTLE: /* 口笛 */ + case BA_ASSASSINCROSS: /* 夕陽のアサシンクロス */ + case BA_APPLEIDUN: /* イドゥンの林檎 */ + case DC_UGLYDANCE: /* 自分勝手なダンス */ + case DC_HUMMING: /* ハミング */ + case DC_DONTFORGETME: /* 私を忘れないで… */ + case DC_FORTUNEKISS: /* 幸運のキス */ + case DC_SERVICEFORYOU: /* サービスフォーユー */ + case CG_MOONLIT: /* 月明りの泉に落ちる花びら */ + clif_skill_nodamage(src,bl,skillid,skilllv,1); + skill_unitsetting(src,skillid,skilllv,src->x,src->y,0); + break; + + case HP_BASILICA: /* バジリカ */ + case PA_GOSPEL: /* ゴスペル */ + skill_clear_unitgroup(src); + clif_skill_nodamage(src,bl,skillid,skilllv,1); + skill_unitsetting(src,skillid,skilllv,src->x,src->y,0); + break; + + case BD_ADAPTATION: /* アドリブ */ + { + struct status_change *sc_data = battle_get_sc_data(src); + if(sc_data && sc_data[SC_DANCING].timer!=-1){ + clif_skill_nodamage(src,bl,skillid,skilllv,1); + skill_stop_dancing(src,0); + } + } + break; + + case BA_FROSTJOKE: /* 寒いジョーク */ + case DC_SCREAM: /* スクリーム */ + clif_skill_nodamage(src,bl,skillid,skilllv,1); + skill_addtimerskill(src,tick+3000,bl->id,0,0,skillid,skilllv,0,flag); + break; + + case TF_STEAL: // スティール + if(sd) { + if(pc_steal_item(sd,bl)) + clif_skill_nodamage(src,bl,skillid,skilllv,1); + else + clif_skill_nodamage(src,bl,skillid,skilllv,0); + } + break; + + case RG_STEALCOIN: // スティールコイン + if(sd) { + if(pc_steal_coin(sd,bl)) { + int range = skill_get_range(skillid,skilllv); + if(range < 0) + range = battle_get_range(src) - (range + 1); + clif_skill_nodamage(src,bl,skillid,skilllv,1); + mob_target((struct mob_data *)bl,src,range); + } + else + clif_skill_nodamage(src,bl,skillid,skilllv,0); + } + break; + + case MG_STONECURSE: /* ストーンカース */ + if (bl->type==BL_MOB && battle_get_mode(bl)&0x20) { + clif_skill_fail(sd,sd->skillid,0,0); + break; + } + clif_skill_nodamage(src,bl,skillid,skilllv,1); + if( bl->type==BL_PC && ((struct map_session_data *)bl)->special_state.no_magic_damage ) + break; + if( rand()%100 < skilllv*4+20 && !battle_check_undead(battle_get_race(bl),battle_get_elem_type(bl))) + skill_status_change_start(bl,SC_STONE,skilllv,0,0,0,skill_get_time2(skillid,skilllv),0); + else if(sd) + clif_skill_fail(sd,skillid,0,0); + break; + + case NV_FIRSTAID: /* 応急手当 */ + clif_skill_nodamage(src,bl,skillid,5,1); + battle_heal(NULL,bl,5,0,0); + break; + + case AL_CURE: /* キュアー */ + clif_skill_nodamage(src,bl,skillid,skilllv,1); + if( bl->type==BL_PC && ((struct map_session_data *)bl)->special_state.no_magic_damage ) + break; + skill_status_change_end(bl, SC_SILENCE , -1 ); + skill_status_change_end(bl, SC_BLIND , -1 ); + skill_status_change_end(bl, SC_CONFUSION, -1 ); + if( battle_check_undead(battle_get_race(bl),battle_get_elem_type(bl)) ){//アンデッドなら暗闇効果 + skill_status_change_start(bl, SC_CONFUSION,1,0,0,0,6000,0); + } + break; + + case TF_DETOXIFY: /* 解毒 */ + clif_skill_nodamage(src,bl,skillid,skilllv,1); + skill_status_change_end(bl, SC_POISON , -1 ); + break; + + case PR_STRECOVERY: /* リカバリー */ + { + clif_skill_nodamage(src,bl,skillid,skilllv,1); + if( bl->type==BL_PC && ((struct map_session_data *)bl)->special_state.no_magic_damage ) + break; + skill_status_change_end(bl, SC_FREEZE , -1 ); + skill_status_change_end(bl, SC_STONE , -1 ); + skill_status_change_end(bl, SC_SLEEP , -1 ); + skill_status_change_end(bl, SC_STAN , -1 ); + if( battle_check_undead(battle_get_race(bl),battle_get_elem_type(bl)) ){//アンデッドなら暗闇効果 + int blind_time; + //blind_time=30-battle_get_vit(bl)/10-battle_get_int/15; + blind_time=30*(100-(battle_get_int(bl)+battle_get_vit(bl))/2)/100; + if(rand()%100 < (100-(battle_get_int(bl)/2+battle_get_vit(bl)/3+battle_get_luk(bl)/10))) + skill_status_change_start(bl, SC_BLIND,1,0,0,0,blind_time,0); + } + if(dstmd){ + dstmd->attacked_id=0; + dstmd->target_id=0; + dstmd->state.targettype = NONE_ATTACKABLE; + dstmd->state.skillstate=MSS_IDLE; + dstmd->next_walktime=tick+rand()%3000+3000; + } + } + break; + + case WZ_ESTIMATION: /* モンスター情報 */ + if(src->type==BL_PC){ + clif_skill_nodamage(src,bl,skillid,skilllv,1); + clif_skill_estimation((struct map_session_data *)src,bl); + } + break; + + case MC_IDENTIFY: /* アイテム鑑定 */ + if(sd) + clif_item_identify_list(sd); + break; + + case BS_REPAIRWEAPON: /* 武器修理 */ + if(sd) +//動作しないのでとりあえずコメントアウト +// clif_item_repair_list(sd); + break; + + case MC_VENDING: /* 露店開設 */ + if(sd) + clif_openvendingreq(sd,2+sd->skilllv); + break; + + case AL_TELEPORT: /* テレポート */ + if( sd ){ + if(map[sd->bl.m].flag.noteleport){ /* テレポ禁止 */ + clif_skill_teleportmessage(sd,0); + break; + } + clif_skill_nodamage(src,bl,skillid,skilllv,1); + if( sd->skilllv==1 ) + clif_skill_warppoint(sd,sd->skillid,"Random","","",""); + else{ + clif_skill_warppoint(sd,sd->skillid,"Random", + sd->status.save_point.map,"",""); + } + }else if( bl->type==BL_MOB ) + mob_warp((struct mob_data *)bl,-1,-1,-1,3); + break; + + case AL_HOLYWATER: /* アクアベネディクタ */ + if(sd) { + int eflag; + struct item item_tmp; + clif_skill_nodamage(src,bl,skillid,skilllv,1); + memset(&item_tmp,0,sizeof(item_tmp)); + item_tmp.nameid = 523; + item_tmp.identify = 1; + if(battle_config.holywater_name_input) { + item_tmp.card[0] = 0xfe; + item_tmp.card[1] = 0; + *((unsigned long *)(&item_tmp.card[2]))=sd->char_id; /* キャラID */ + } + eflag = pc_additem(sd,&item_tmp,1); + if(eflag) { + clif_additem(sd,0,0,eflag); + map_addflooritem(&item_tmp,1,sd->bl.m,sd->bl.x,sd->bl.y,NULL,NULL,NULL,0); + } + } + break; + case TF_PICKSTONE: + if(sd) { + int eflag; + struct item item_tmp; + struct block_list tbl; + clif_skill_nodamage(src,bl,skillid,skilllv,1); + memset(&item_tmp,0,sizeof(item_tmp)); + memset(&tbl,0,sizeof(tbl)); // [MouseJstr] + item_tmp.nameid = 7049; + item_tmp.identify = 1; + tbl.id = 0; + clif_takeitem(&sd->bl,&tbl); + eflag = pc_additem(sd,&item_tmp,1); + if(eflag) { + clif_additem(sd,0,0,eflag); + map_addflooritem(&item_tmp,1,sd->bl.m,sd->bl.x,sd->bl.y,NULL,NULL,NULL,0); + } + } + break; + + case RG_STRIPWEAPON: /* ストリップウェポン */ + { + struct status_change *tsc_data = battle_get_sc_data(bl); + + if(tsc_data && tsc_data[SC_CP_WEAPON].timer != -1 ) + break; + strip_per = 5+2*skilllv+strip_fix/5; + strip_time = skill_get_time(skillid,skilllv)+strip_fix/2; + if(rand()%100 < strip_per){ + clif_skill_nodamage(src,bl,skillid,skilllv,1); + skill_status_change_start(bl,SkillStatusChangeTable[skillid],skilllv,0,0,0,strip_time,0 ); + if(dstsd){ + for(i=0;i<MAX_INVENTORY;i++){ + if(dstsd->status.inventory[i].equip && dstsd->status.inventory[i].equip & 0x0002){ + pc_unequipitem(dstsd,i,0); + break; + } + } + } + } + } + break; + + case RG_STRIPSHIELD: /* ストリップシールド */ + { + struct status_change *tsc_data = battle_get_sc_data(bl); + + if(tsc_data && tsc_data[SC_CP_SHIELD].timer != -1 ) + break; + strip_per = 5+2*skilllv+strip_fix/5; + strip_time = skill_get_time(skillid,skilllv)+strip_fix/2; + if(rand()%100 < strip_per){ + clif_skill_nodamage(src,bl,skillid,skilllv,1); + skill_status_change_start(bl,SkillStatusChangeTable[skillid],skilllv,0,0,0,strip_time,0 ); + if(dstsd){ + for(i=0;i<MAX_INVENTORY;i++){ + if(dstsd->status.inventory[i].equip && dstsd->status.inventory[i].equip & 0x0020){ + pc_unequipitem(dstsd,i,0); + break; + } + } + } + } + } + break; + + case RG_STRIPARMOR: /* ストリップアーマー */ + { + struct status_change *tsc_data = battle_get_sc_data(bl); + + if(tsc_data && tsc_data[SC_CP_ARMOR].timer != -1 ) + break; + strip_per = 5+2*skilllv+strip_fix/5; + strip_time = skill_get_time(skillid,skilllv)+strip_fix/2; + if(rand()%100 < strip_per){ + clif_skill_nodamage(src,bl,skillid,skilllv,1); + skill_status_change_start(bl,SkillStatusChangeTable[skillid],skilllv,0,0,0,strip_time,0 ); + if(dstsd){ + for(i=0;i<MAX_INVENTORY;i++){ + if(dstsd->status.inventory[i].equip && dstsd->status.inventory[i].equip & 0x0010){ + pc_unequipitem(dstsd,i,0); + break; + } + } + } + } + } + break; + case RG_STRIPHELM: /* ストリップヘルム */ + { + struct status_change *tsc_data = battle_get_sc_data(bl); + + if(tsc_data && tsc_data[SC_CP_HELM].timer != -1 ) + break; + strip_per = 5+2*skilllv+strip_fix/5; + strip_time = skill_get_time(skillid,skilllv)+strip_fix/2; + if(rand()%100 < strip_per){ + clif_skill_nodamage(src,bl,skillid,skilllv,1); + skill_status_change_start(bl,SkillStatusChangeTable[skillid],skilllv,0,0,0,strip_time,0 ); + if(dstsd){ + for(i=0;i<MAX_INVENTORY;i++){ + if(dstsd->status.inventory[i].equip && dstsd->status.inventory[i].equip & 0x0100){ + pc_unequipitem(dstsd,i,0); + break; + } + } + } + } + } + break; + /* PotionPitcher */ + case AM_POTIONPITCHER: /* ポーションピッチャー */ + { + struct block_list tbl; + int i,x,hp = 0,sp = 0; + if(sd) { + if(sd==dstsd) { // cancel use on oneself + map_freeblock_unlock(); + return 1; + } + x = skilllv%11 - 1; + i = pc_search_inventory(sd,skill_db[skillid].itemid[x]); + if(i < 0 || skill_db[skillid].itemid[x] <= 0) { + clif_skill_fail(sd,skillid,0,0); + map_freeblock_unlock(); + return 1; + } + if(sd->inventory_data[i] == NULL || sd->status.inventory[i].amount < skill_db[skillid].amount[x]) { + clif_skill_fail(sd,skillid,0,0); + map_freeblock_unlock(); + return 1; + } + sd->state.potionpitcher_flag = 1; + sd->potion_hp = sd->potion_sp = sd->potion_per_hp = sd->potion_per_sp = 0; + sd->skilltarget = bl->id; + run_script(sd->inventory_data[i]->use_script,0,sd->bl.id,0); + pc_delitem(sd,i,skill_db[skillid].amount[x],0); + sd->state.potionpitcher_flag = 0; + if(sd->potion_per_hp > 0 || sd->potion_per_sp > 0) { + hp = battle_get_max_hp(bl) * sd->potion_per_hp / 100; + hp = hp * (100 + pc_checkskill(sd,AM_POTIONPITCHER)*10 + pc_checkskill(sd,AM_LEARNINGPOTION)*5)/100; + if(dstsd) { + sp = dstsd->status.max_sp * sd->potion_per_sp / 100; + sp = sp * (100 + pc_checkskill(sd,AM_POTIONPITCHER) + pc_checkskill(sd,AM_LEARNINGPOTION)*5)/100; + } + } + else { + if(sd->potion_hp > 0) { + hp = sd->potion_hp * (100 + pc_checkskill(sd,AM_POTIONPITCHER)*10 + pc_checkskill(sd,AM_LEARNINGPOTION)*5)/100; + hp = hp * (100 + (battle_get_vit(bl)<<1)) / 100; + if(dstsd) + hp = hp * (100 + pc_checkskill(dstsd,SM_RECOVERY)*10) / 100; + } + if(sd->potion_sp > 0) { + sp = sd->potion_sp * (100 + pc_checkskill(sd,AM_POTIONPITCHER) + pc_checkskill(sd,AM_LEARNINGPOTION)*5)/100; + sp = sp * (100 + (battle_get_int(bl)<<1)) / 100; + if(dstsd) + sp = sp * (100 + pc_checkskill(dstsd,MG_SRECOVERY)*10) / 100; + } + } + } + else { + hp = (1 + rand()%400) * (100 + skilllv*10) / 100; + hp = hp * (100 + (battle_get_vit(bl)<<1)) / 100; + if(dstsd) + hp = hp * (100 + pc_checkskill(dstsd,SM_RECOVERY)*10) / 100; + } + tbl.id = 0; + tbl.m = src->m; + tbl.x = src->x; + tbl.y = src->y; + clif_skill_nodamage(src,bl,skillid,skilllv,1); + if(hp > 0 || (hp <= 0 && sp <= 0)) + clif_skill_nodamage(&tbl,bl,AL_HEAL,hp,1); + if(sp > 0) + clif_skill_nodamage(&tbl,bl,MG_SRECOVERY,sp,1); + battle_heal(src,bl,hp,sp,0); + } + break; + case AM_CP_WEAPON: + { + struct status_change *tsc_data = battle_get_sc_data(bl); + clif_skill_nodamage(src,bl,skillid,skilllv,1); + if(tsc_data && tsc_data[SC_STRIPWEAPON].timer != -1) + skill_status_change_end(bl, SC_STRIPWEAPON, -1 ); + skill_status_change_start(bl,SkillStatusChangeTable[skillid],skilllv,0,0,0,skill_get_time(skillid,skilllv),0 ); + } + break; + case AM_CP_SHIELD: + { + struct status_change *tsc_data = battle_get_sc_data(bl); + clif_skill_nodamage(src,bl,skillid,skilllv,1); + if(tsc_data && tsc_data[SC_STRIPSHIELD].timer != -1) + skill_status_change_end(bl, SC_STRIPSHIELD, -1 ); + skill_status_change_start(bl,SkillStatusChangeTable[skillid],skilllv,0,0,0,skill_get_time(skillid,skilllv),0 ); + } + break; + case AM_CP_ARMOR: + { + struct status_change *tsc_data = battle_get_sc_data(bl); + clif_skill_nodamage(src,bl,skillid,skilllv,1); + if(tsc_data && tsc_data[SC_STRIPARMOR].timer != -1) + skill_status_change_end(bl, SC_STRIPARMOR, -1 ); + skill_status_change_start(bl,SkillStatusChangeTable[skillid],skilllv,0,0,0,skill_get_time(skillid,skilllv),0 ); + } + break; + case AM_CP_HELM: + { + struct status_change *tsc_data = battle_get_sc_data(bl); + clif_skill_nodamage(src,bl,skillid,skilllv,1); + if(tsc_data && tsc_data[SC_STRIPHELM].timer != -1) + skill_status_change_end(bl, SC_STRIPHELM, -1 ); + skill_status_change_start(bl,SkillStatusChangeTable[skillid],skilllv,0,0,0,skill_get_time(skillid,skilllv),0 ); + } + break; + case SA_DISPELL: /* ディスペル */ + { + int i; + clif_skill_nodamage(src,bl,skillid,skilllv,1); + if( bl->type==BL_PC && ((struct map_session_data *)bl)->special_state.no_magic_damage ) + break; + for(i=0;i<136;i++){ + if(i==SC_RIDING || i== SC_FALCON || i==SC_HALLUCINATION || i==SC_WEIGHT50 + || i==SC_WEIGHT90 || i==SC_STRIPWEAPON || i==SC_STRIPSHIELD || i==SC_STRIPARMOR + || i==SC_STRIPHELM || i==SC_CP_WEAPON || i==SC_CP_SHIELD || i==SC_CP_ARMOR + || i==SC_CP_HELM || i==SC_COMBO) + continue; + skill_status_change_end(bl,i,-1); + } + } + break; + + case TF_BACKSLIDING: /* バックステップ */ + battle_stopwalking(src,1); + skill_blown(src,bl,skill_get_blewcount(skillid,skilllv)|0x10000); + if(src->type == BL_MOB) + clif_fixmobpos((struct mob_data *)src); + else if(src->type == BL_PET) + clif_fixpetpos((struct pet_data *)src); + else if(src->type == BL_PC) + clif_fixpos(src); + skill_addtimerskill(src,tick + 200,src->id,0,0,skillid,skilllv,0,flag); + break; + + case SA_CASTCANCEL: + clif_skill_nodamage(src,bl,skillid,skilllv,1); + skill_castcancel(src,1); + if(sd) { + int sp = skill_get_sp(sd->skillid_old,sd->skilllv_old); + sp = sp * (90 - (skilllv-1)*20) / 100; + if(sp < 0) sp = 0; + pc_heal(sd,0,-sp); + } + break; + case SA_SPELLBREAKER: // スペルブレイカー + { + struct status_change *sc_data = battle_get_sc_data(bl); + int sp; + if(sc_data && sc_data[SC_MAGICROD].timer != -1) { + if(dstsd) { + sp = skill_get_sp(skillid,skilllv); + sp = sp * sc_data[SC_MAGICROD].val2 / 100; + if(sp > 0x7fff) sp = 0x7fff; + else if(sp < 1) sp = 1; + if(dstsd->status.sp + sp > dstsd->status.max_sp) { + sp = dstsd->status.max_sp - dstsd->status.sp; + dstsd->status.sp = dstsd->status.max_sp; + } + else + dstsd->status.sp += sp; + clif_heal(dstsd->fd,SP_SP,sp); + } + clif_skill_nodamage(bl,bl,SA_MAGICROD,sc_data[SC_MAGICROD].val1,1); + if(sd) { + sp = sd->status.max_sp/5; + if(sp < 1) sp = 1; + pc_heal(sd,0,-sp); + } + } + else { + int bl_skillid=0,bl_skilllv=0; + if(bl->type == BL_PC) { + if(dstsd && dstsd->skilltimer != -1) { + bl_skillid = dstsd->skillid; + bl_skilllv = dstsd->skilllv; + } + } + else if(bl->type == BL_MOB) { + if(dstmd && dstmd->skilltimer != -1) { + bl_skillid = dstmd->skillid; + bl_skilllv = dstmd->skilllv; + } + } + if(bl_skillid > 0 && skill_db[bl_skillid].skill_type == BF_MAGIC) { + clif_skill_nodamage(src,bl,skillid,skilllv,1); + skill_castcancel(bl,0); + sp = skill_get_sp(bl_skillid,bl_skilllv); + if(dstsd) + pc_heal(dstsd,0,-sp); + if(sd) { + sp = sp*(25*(skilllv-1))/100; + if(skilllv > 1 && sp < 1) sp = 1; + if(sp > 0x7fff) sp = 0x7fff; + else if(sp < 1) sp = 1; + if(sd->status.sp + sp > sd->status.max_sp) { + sp = sd->status.max_sp - sd->status.sp; + sd->status.sp = sd->status.max_sp; + } + else + sd->status.sp += sp; + clif_heal(sd->fd,SP_SP,sp); + } + } + else if(sd) + clif_skill_fail(sd,skillid,0,0); + } + } + break; + case SA_MAGICROD: + if( bl->type==BL_PC && ((struct map_session_data *)bl)->special_state.no_magic_damage ) + break; + skill_status_change_start(bl,SkillStatusChangeTable[skillid],skilllv,0,0,0,skill_get_time(skillid,skilllv),0 ); + break; + case SA_AUTOSPELL: /* オートスペル */ + clif_skill_nodamage(src,bl,skillid,skilllv,1); + if(sd) + clif_autospell(sd,skilllv); + else { + int maxlv=1,spellid=0; + static const int spellarray[3] = { MG_COLDBOLT,MG_FIREBOLT,MG_LIGHTNINGBOLT }; + if(skilllv >= 10) { + spellid = MG_FROSTDIVER; + maxlv = skilllv - 9; + } + else if(skilllv >=8) { + spellid = MG_FIREBALL; + maxlv = skilllv - 7; + } + else if(skilllv >=5) { + spellid = MG_SOULSTRIKE; + maxlv = skilllv - 4; + } + else if(skilllv >=2) { + int i = rand()%3; + spellid = spellarray[i]; + maxlv = skilllv - 1; + } + else if(skilllv > 0) { + spellid = MG_NAPALMBEAT; + maxlv = 3; + } + if(spellid > 0) + skill_status_change_start(src,SC_AUTOSPELL,skilllv,spellid,maxlv,0, + skill_get_time(SA_AUTOSPELL,skilllv),0); + } + break; + + /* ランダム属性変化、水属性変化、地、火、風 */ + case NPC_ATTRICHANGE: + case NPC_CHANGEWATER: + case NPC_CHANGEGROUND: + case NPC_CHANGEFIRE: + case NPC_CHANGEWIND: + /* 毒、聖、念、闇 */ + case NPC_CHANGEPOISON: + case NPC_CHANGEHOLY: + case NPC_CHANGEDARKNESS: + case NPC_CHANGETELEKINESIS: + if(md){ + clif_skill_nodamage(src,bl,skillid,skilllv,1); + md->def_ele=skill_get_pl(skillid); + if(md->def_ele==0) /* ランダム変化、ただし、*/ + md->def_ele=rand()%10; /* 不死属性は除く */ + md->def_ele+=(1+rand()%4)*20; /* 属性レベルはランダム */ + } + break; + + case NPC_PROVOCATION: + clif_skill_nodamage(src,bl,skillid,skilllv,1); + if(md) + clif_pet_performance(src,mob_db[md->class].skill[md->skillidx].val[0]); + break; + + case NPC_HALLUCINATION: + clif_skill_nodamage(src,bl,skillid,skilllv,1); + if( bl->type==BL_PC && ((struct map_session_data *)bl)->special_state.no_magic_damage ) + break; + skill_status_change_start(bl,SkillStatusChangeTable[skillid],skilllv,0,0,0,skill_get_time(skillid,skilllv),0 ); + break; + + case NPC_KEEPING: + case NPC_BARRIER: + { + int skill_time = skill_get_time(skillid,skilllv); + clif_skill_nodamage(src,bl,skillid,skilllv,1); + skill_status_change_start(bl,SkillStatusChangeTable[skillid],skilllv,0,0,0,skill_time,0 ); + mob_changestate((struct mob_data *)src,MS_DELAY,skill_time); + } + break; + + case NPC_DARKBLESSING: + { + int sc_def = 100 - battle_get_mdef(bl); + clif_skill_nodamage(src,bl,skillid,skilllv,1); + if( bl->type==BL_PC && ((struct map_session_data *)bl)->special_state.no_magic_damage ) + break; + if(battle_get_elem_type(bl) == 7 || battle_get_race(bl) == 6) + break; + if(rand()%100 < sc_def*(50+skilllv*5)/100) { + if(dstsd) { + int hp = battle_get_hp(bl)-1; + pc_heal(dstsd,-hp,0); + } + else if(dstmd) + dstmd->hp = 1; + } + } + break; + + case NPC_SELFDESTRUCTION: /* 自爆 */ + case NPC_SELFDESTRUCTION2: /* 自爆2 */ + skill_status_change_start(bl,SkillStatusChangeTable[skillid],skilllv,skillid,0,0,skill_get_time(skillid,skilllv),0); + break; + case NPC_LICK: + clif_skill_nodamage(src,bl,skillid,skilllv,1); + if( bl->type==BL_PC && ((struct map_session_data *)bl)->special_state.no_weapon_damage ) + break; + if(dstsd) + pc_heal(dstsd,0,-100); + if(rand()%100 < (skilllv*5)*sc_def_vit/100) + skill_status_change_start(bl,SC_STAN,skilllv,0,0,0,skill_get_time2(skillid,skilllv),0); + break; + + case NPC_SUICIDE: /* 自決 */ + if(src && bl && md){ + clif_skill_nodamage(src,bl,skillid,skilllv,1); + mob_damage(NULL,md,md->hp,0); + } + break; + + case NPC_SUMMONSLAVE: /* 手下召喚 */ + case NPC_SUMMONMONSTER: /* MOB召喚 */ + if(md && !md->master_id){ + mob_summonslave(md,mob_db[md->class].skill[md->skillidx].val,skilllv,(skillid==NPC_SUMMONSLAVE)?1:0); + } + break; + + case NPC_TRANSFORMATION: + case NPC_METAMORPHOSIS: + if(md) + mob_class_change(md,mob_db[md->class].skill[md->skillidx].val); + break; + + case NPC_EMOTION: /* エモーション */ + if(md) + clif_emotion(&md->bl,mob_db[md->class].skill[md->skillidx].val[0]); + break; + + case NPC_DEFENDER: + clif_skill_nodamage(src,bl,skillid,skilllv,1); + break; + + case WE_MALE: /* 君だけは護るよ */ + if(sd && dstsd){ + int hp_rate=(skilllv <= 0)? 0:skill_db[skillid].hp_rate[skilllv-1]; + int gain_hp=sd->status.max_hp*abs(hp_rate)/100;// 15% + clif_skill_nodamage(src,bl,skillid,gain_hp,1); + battle_heal(NULL,bl,gain_hp,0,0); + } + break; + case WE_FEMALE: /* あなたの為に犠牲になります */ + if(sd && dstsd){ + int sp_rate=(skilllv <= 0)? 0:skill_db[skillid].sp_rate[skilllv-1]; + int gain_sp=sd->status.max_sp*abs(sp_rate)/100;// 15% + clif_skill_nodamage(src,bl,skillid,gain_sp,1); + battle_heal(NULL,bl,0,gain_sp,0); + } + break; + + case WE_CALLPARTNER: /* あなたに会いたい */ + if(sd && dstsd){ + if(map[sd->bl.m].flag.nomemo){ + clif_skill_teleportmessage(sd,1); + return 0; + } + if((dstsd = pc_get_partner(sd)) == NULL){ + clif_skill_fail(sd,skillid,0,0); + return 0; + } + skill_unitsetting(src,skillid,skilllv,sd->bl.x,sd->bl.y,0); + } + break; + + case PF_HPCONVERSION: /* ライフ置き換え */ + clif_skill_nodamage(src,bl,skillid,skilllv,1); + if(sd){ + int conv_hp=0,conv_sp=0; + conv_hp=sd->status.hp/10; //基本はHPの10% + sd->status.hp -= conv_hp; //HPを減らす + conv_sp=conv_hp*20*skilllv/100; + conv_sp=(sd->status.sp+conv_sp>sd->status.max_sp)?sd->status.max_sp-sd->status.sp:conv_sp; + sd->status.sp += conv_sp; //SPを増やす + pc_heal(sd,-conv_hp,conv_sp); + clif_heal(sd->fd,SP_SP,conv_sp); + } + break; + case HT_REMOVETRAP: /* リムーブトラップ */ + clif_skill_nodamage(src,bl,skillid,skilllv,1); + { + struct skill_unit *su=NULL; + struct item item_tmp; + int flag; + if((bl->type==BL_SKILL) && + (su=(struct skill_unit *)bl) && + (su->group->src_id == src->id || map[bl->m].flag.pvp || map[bl->m].flag.gvg) && + (su->group->unit_id >= 0x8f && su->group->unit_id <= 0x99) && + (su->group->unit_id != 0x92)){ //罠を取り返す + if(sd){ + if(battle_config.skill_removetrap_type == 1){ + for(i=0;i<10;i++) { + if(skill_db[su->group->skill_id].itemid[i] > 0){ + memset(&item_tmp,0,sizeof(item_tmp)); + item_tmp.nameid = skill_db[su->group->skill_id].itemid[i]; + item_tmp.identify = 1; + if(item_tmp.nameid && (flag=pc_additem(sd,&item_tmp,skill_db[su->group->skill_id].amount[i]))){ + clif_additem(sd,0,0,flag); + map_addflooritem(&item_tmp,skill_db[su->group->skill_id].amount[i],sd->bl.m,sd->bl.x,sd->bl.y,NULL,NULL,NULL,0); + } + } + } + }else{ + memset(&item_tmp,0,sizeof(item_tmp)); + item_tmp.nameid = 1065; + item_tmp.identify = 1; + if(item_tmp.nameid && (flag=pc_additem(sd,&item_tmp,1))){ + clif_additem(sd,0,0,flag); + map_addflooritem(&item_tmp,1,sd->bl.m,sd->bl.x,sd->bl.y,NULL,NULL,NULL,0); + } + } + + } + if(su->group->unit_id == 0x91 && su->group->val2){ + struct block_list *target=map_id2bl(su->group->val2); + if(target && (target->type == BL_PC || target->type == BL_MOB)) + skill_status_change_end(target,SC_ANKLE,-1); + } + skill_delunit(su); + } + } + break; + case HT_SPRINGTRAP: /* スプリングトラップ */ + clif_skill_nodamage(src,bl,skillid,skilllv,1); + { + struct skill_unit *su=NULL; + if((bl->type==BL_SKILL) && (su=(struct skill_unit *)bl) && (su->group) ){ + switch(su->group->unit_id){ + case 0x8f: /* ブラストマイン */ + case 0x90: /* スキッドトラップ */ + case 0x93: /* ランドマイン */ + case 0x94: /* ショックウェーブトラップ */ + case 0x95: /* サンドマン */ + case 0x96: /* フラッシャー */ + case 0x97: /* フリージングトラップ */ + case 0x98: /* クレイモアートラップ */ + case 0x99: /* トーキーボックス */ + su->group->unit_id = 0x8c; + clif_changelook(bl,LOOK_BASE,su->group->unit_id); + su->group->limit=DIFF_TICK(tick+1500,su->group->tick); + su->limit=DIFF_TICK(tick+1500,su->group->tick); + } + } + } + break; + case BD_ENCORE: /* アンコール */ + clif_skill_nodamage(src,bl,skillid,skilllv,1); + if(sd) + skill_use_id(sd,src->id,sd->skillid_dance,sd->skilllv_dance); + break; + case AS_SPLASHER: /* ベナムスプラッシャー */ + if((double)battle_get_max_hp(bl)*2/3 < battle_get_hp(bl)) //HPが2/3以上残っていたら失敗 + return 1; + clif_skill_nodamage(src,bl,skillid,skilllv,1); + skill_status_change_start(bl,SkillStatusChangeTable[skillid],skilllv,skillid,src->id,0,skill_get_time(skillid,skilllv),0 ); + break; + case PF_MINDBREAKER: /* プロボック */ + { + struct status_change *sc_data = battle_get_sc_data(bl); + + /* MVPmobと不死には効かない */ + if((bl->type==BL_MOB && battle_get_mode(bl)&0x20) || battle_check_undead(battle_get_race(bl),battle_get_elem_type(bl))) //不死には効かない + { + map_freeblock_unlock(); + return 1; + } + + clif_skill_nodamage(src,bl,skillid,skilllv,1); + skill_status_change_start(bl,SkillStatusChangeTable[skillid],skilllv,0,0,0,skill_get_time(skillid,skilllv),0 ); + + if(dstmd && dstmd->skilltimer!=-1 && dstmd->state.skillcastcancel) // 詠唱妨害 + skill_castcancel(bl,0); + if(dstsd && dstsd->skilltimer!=-1 && (!dstsd->special_state.no_castcancel || map[bl->m].flag.gvg) + && dstsd->state.skillcastcancel && !dstsd->special_state.no_castcancel2) + skill_castcancel(bl,0); + + if(sc_data){ + if(sc_data[SC_FREEZE].timer!=-1) + skill_status_change_end(bl,SC_FREEZE,-1); + if(sc_data[SC_STONE].timer!=-1 && sc_data[SC_STONE].val2==0) + skill_status_change_end(bl,SC_STONE,-1); + if(sc_data[SC_SLEEP].timer!=-1) + skill_status_change_end(bl,SC_SLEEP,-1); + } + + if(bl->type==BL_MOB) { + int range = skill_get_range(skillid,skilllv); + if(range < 0) + range = battle_get_range(src) - (range + 1); + mob_target((struct mob_data *)bl,src,range); + } + } + break; + + + + + + + case RG_CLEANER: //AppleGirl + clif_skill_nodamage(src,bl,skillid,skilllv,1); + { + struct skill_unit *su=NULL; + if((bl->type==BL_SKILL) && + (su=(struct skill_unit *)bl) && + (su->group->src_id == src->id || map[bl->m].flag.pvp || map[bl->m].flag.gvg) && + (su->group->unit_id == 0xb0)){ //罠を取り返す + if(sd) + skill_delunit(su); + } + } + break; + default: + printf("Unknown skill used:%d\n",skillid); + map_freeblock_unlock(); + return 1; + } + + map_freeblock_unlock(); + return 0; +} + +/*========================================== + * スキル使用(詠唱完了、ID指定) + *------------------------------------------ + */ +int skill_castend_id( int tid, unsigned int tick, int id,int data ) +{ + struct map_session_data* sd = map_id2sd(id)/*,*target_sd=NULL*/; + struct block_list *bl; + int range,inf2; + + nullpo_retr(0, sd); + + if( sd->bl.prev == NULL ) //prevが無いのはありなの? + return 0; + + if(sd->skillid != SA_CASTCANCEL && sd->skilltimer != tid ) /* タイマIDの確認 */ + return 0; + if(sd->skillid != SA_CASTCANCEL && sd->skilltimer != -1 && pc_checkskill(sd,SA_FREECAST) > 0) { + sd->speed = sd->prev_speed; + clif_updatestatus(sd,SP_SPEED); + } + if(sd->skillid != SA_CASTCANCEL) + sd->skilltimer=-1; + + if((bl=map_id2bl(sd->skilltarget))==NULL || bl->prev==NULL) { + sd->canact_tick = tick; + sd->canmove_tick = tick; + sd->skillitem = sd->skillitemlv = -1; + return 0; + } + if(sd->bl.m != bl->m || pc_isdead(sd)) { //マップが違うか自分が死んでいる + sd->canact_tick = tick; + sd->canmove_tick = tick; + sd->skillitem = sd->skillitemlv = -1; + return 0; + } + + if(sd->skillid == PR_LEXAETERNA) { + struct status_change *sc_data = battle_get_sc_data(bl); + if(sc_data && (sc_data[SC_FREEZE].timer != -1 || (sc_data[SC_STONE].timer != -1 && sc_data[SC_STONE].val2 == 0))) { + clif_skill_fail(sd,sd->skillid,0,0); + sd->canact_tick = tick; + sd->canmove_tick = tick; + sd->skillitem = sd->skillitemlv = -1; + return 0; + } + } + else if(sd->skillid == RG_BACKSTAP) { + int dir = map_calc_dir(&sd->bl,bl->x,bl->y),t_dir = battle_get_dir(bl); + int dist = distance(sd->bl.x,sd->bl.y,bl->x,bl->y); + if(bl->type != BL_SKILL && (dist == 0 || map_check_dir(dir,t_dir))) { + clif_skill_fail(sd,sd->skillid,0,0); + sd->canact_tick = tick; + sd->canmove_tick = tick; + sd->skillitem = sd->skillitemlv = -1; + return 0; + } + } + + inf2 = skill_get_inf2(sd->skillid); + if( ( (skill_get_inf(sd->skillid)&1) || inf2&4 ) && // 彼我敵対関係チェック + battle_check_target(&sd->bl,bl, BCT_ENEMY)<=0 ) { + sd->canact_tick = tick; + sd->canmove_tick = tick; + sd->skillitem = sd->skillitemlv = -1; + return 0; + } + if(inf2 & 0xC00 && sd->bl.id != bl->id) { + int fail_flag = 1; + if(inf2 & 0x400 && battle_check_target(&sd->bl,bl, BCT_PARTY) > 0) + fail_flag = 0; + if(inf2 & 0x800 && sd->status.guild_id > 0 && sd->status.guild_id == battle_get_guild_id(bl)) + fail_flag = 0; + if(fail_flag) { + clif_skill_fail(sd,sd->skillid,0,0); + sd->canact_tick = tick; + sd->canmove_tick = tick; + sd->skillitem = sd->skillitemlv = -1; + return 0; + } + } + + range = skill_get_range(sd->skillid,sd->skilllv); + if(range < 0) + range = battle_get_range(&sd->bl) - (range + 1); + range += battle_config.pc_skill_add_range; + if((sd->skillid == MO_EXTREMITYFIST && sd->sc_data[SC_COMBO].timer != -1 && sd->sc_data[SC_COMBO].val1 == MO_COMBOFINISH) || + (sd->skillid == CH_TIGERFIST && sd->sc_data[SC_COMBO].timer != -1 && sd->sc_data[SC_COMBO].val1 == MO_COMBOFINISH) || + (sd->skillid == CH_CHAINCRUSH && sd->sc_data[SC_COMBO].timer != -1 && sd->sc_data[SC_COMBO].val1 == MO_COMBOFINISH) || + (sd->skillid == CH_CHAINCRUSH && sd->sc_data[SC_COMBO].timer != -1 && sd->sc_data[SC_COMBO].val1 == CH_TIGERFIST)) + range += skill_get_blewcount(MO_COMBOFINISH,sd->sc_data[SC_COMBO].val2); + if(battle_config.skill_out_range_consume) { // changed to allow casting when target walks out of range [Valaris] + if(range < distance(sd->bl.x,sd->bl.y,bl->x,bl->y)) { + clif_skill_fail(sd,sd->skillid,0,0); + sd->canact_tick = tick; + sd->canmove_tick = tick; + sd->skillitem = sd->skillitemlv = -1; + return 0; + } + } + if(!skill_check_condition(sd,1)) { /* 使用条件チェック */ + sd->canact_tick = tick; + sd->canmove_tick = tick; + sd->skillitem = sd->skillitemlv = -1; + return 0; + } + sd->skillitem = sd->skillitemlv = -1; + if(battle_config.skill_out_range_consume) { + if(range < distance(sd->bl.x,sd->bl.y,bl->x,bl->y)) { + clif_skill_fail(sd,sd->skillid,0,0); + sd->canact_tick = tick; + sd->canmove_tick = tick; + return 0; + } + } + + if(battle_config.pc_skill_log) + printf("PC %d skill castend skill=%d\n",sd->bl.id,sd->skillid); + pc_stop_walking(sd,0); + + switch( skill_get_nk(sd->skillid) ) + { + /* 攻撃系/吹き飛ばし系 */ + case 0: case 2: + skill_castend_damage_id(&sd->bl,bl,sd->skillid,sd->skilllv,tick,0); + break; + case 1:/* 支援系 */ + if( (sd->skillid==AL_HEAL || (sd->skillid==ALL_RESURRECTION && bl->type != BL_PC) || sd->skillid==PR_ASPERSIO) && battle_check_undead(battle_get_race(bl),battle_get_elem_type(bl))) + skill_castend_damage_id(&sd->bl,bl,sd->skillid,sd->skilllv,tick,0); + else + skill_castend_nodamage_id(&sd->bl,bl,sd->skillid,sd->skilllv,tick,0); + break; + } + + return 0; +} + +/*========================================== + * スキル使用(詠唱完了、場所指定の実際の処理) + *------------------------------------------ + */ +int skill_castend_pos2( struct block_list *src, int x,int y,int skillid,int skilllv,unsigned int tick,int flag) +{ + struct map_session_data *sd=NULL; + int i,tmpx = 0,tmpy = 0, x1 = 0, y1 = 0; + + nullpo_retr(0, src); + + if(src->type==BL_PC){ + nullpo_retr(0, sd=(struct map_session_data *)src); + } + if( skillid != WZ_METEOR && + skillid != WZ_SIGHTRASHER && + skillid != AM_CANNIBALIZE && + skillid != AM_SPHEREMINE) + clif_skill_poseffect(src,skillid,skilllv,x,y,tick); + + if (skillnotok(skillid, sd)) // [MouseJstr] + return 0; + + switch(skillid) + { + case PR_BENEDICTIO: /* 聖体降福 */ + skill_area_temp[1]=src->id; + map_foreachinarea(skill_area_sub, + src->m,x-1,y-1,x+1,y+1,0, + src,skillid,skilllv,tick, flag|BCT_NOENEMY|1, + skill_castend_nodamage_id); + map_foreachinarea(skill_area_sub, + src->m,x-1,y-1,x+1,y+1,0, + src,skillid,skilllv,tick, flag|BCT_ENEMY|1, + skill_castend_damage_id); + break; + + case BS_HAMMERFALL: /* ハンマーフォール */ + skill_area_temp[1]=src->id; + skill_area_temp[2]=x; + skill_area_temp[3]=y; + map_foreachinarea(skill_area_sub, + src->m,x-2,y-2,x+2,y+2,0, + src,skillid,skilllv,tick, flag|BCT_ENEMY|2, + skill_castend_nodamage_id); + break; + + case HT_DETECTING: /* ディテクティング */ + { + const int range=7; + map_foreachinarea( skill_status_change_timer_sub, + src->m, src->x-range, src->y-range, src->x+range,src->y+range,0, + src,SC_SIGHT,tick); + } + break; + + case MG_SAFETYWALL: /* セイフティウォール */ + case MG_FIREWALL: /* ファイヤーウォール */ + case MG_THUNDERSTORM: /* サンダーストーム */ + case AL_PNEUMA: /* ニューマ */ + case WZ_ICEWALL: /* アイスウォール */ + case WZ_FIREPILLAR: /* ファイアピラー */ + case WZ_SIGHTRASHER: + case WZ_QUAGMIRE: /* クァグマイア */ + case WZ_VERMILION: /* ロードオブヴァーミリオン */ + case WZ_FROSTNOVA: /* フロストノヴァ */ + case WZ_STORMGUST: /* ストームガスト */ + case WZ_HEAVENDRIVE: /* ヘヴンズドライブ */ + case PR_SANCTUARY: /* サンクチュアリ */ + case PR_MAGNUS: /* マグヌスエクソシズム */ + case CR_GRANDCROSS: /* グランドクロス */ + case HT_SKIDTRAP: /* スキッドトラップ */ + case HT_LANDMINE: /* ランドマイン */ + case HT_ANKLESNARE: /* アンクルスネア */ + case HT_SHOCKWAVE: /* ショックウェーブトラップ */ + case HT_SANDMAN: /* サンドマン */ + case HT_FLASHER: /* フラッシャー */ + case HT_FREEZINGTRAP: /* フリージングトラップ */ + case HT_BLASTMINE: /* ブラストマイン */ + case HT_CLAYMORETRAP: /* クレイモアートラップ */ + case AS_VENOMDUST: /* ベノムダスト */ + case AM_DEMONSTRATION: /* デモンストレーション */ + case PF_SPIDERWEB: /* スパイダーウェッブ */ + case PF_FOGWALL: /* フォグウォール */ + case HT_TALKIEBOX: /* トーキーボックス */ + skill_unitsetting(src,skillid,skilllv,x,y,0); + break; + + case RG_GRAFFITI: /* Graffiti [Valaris] */ + skill_clear_unitgroup(src); + skill_unitsetting(src,skillid,skilllv,x,y,0); + break; + + case SA_VOLCANO: /* ボルケーノ */ + case SA_DELUGE: /* デリュージ */ + case SA_VIOLENTGALE: /* バイオレントゲイル */ + case SA_LANDPROTECTOR: /* ランドプロテクター */ + skill_clear_element_field(src);//既に自分が発動している属性場をクリア + skill_unitsetting(src,skillid,skilllv,x,y,0); + break; + + case WZ_METEOR: //メテオストーム + { + int flag=0; + for(i=0;i<2+(skilllv>>1);i++) { + int j=0, c; + do { + tmpx = x + (rand()%7 - 3); + tmpy = y + (rand()%7 - 3); + if(tmpx < 0) + tmpx = 0; + else if(tmpx >= map[src->m].xs) + tmpx = map[src->m].xs - 1; + if(tmpy < 0) + tmpy = 0; + else if(tmpy >= map[src->m].ys) + tmpy = map[src->m].ys - 1; + j++; + } while(((c=map_getcell(src->m,tmpx,tmpy))==1 || c==5) && j<100); + if(j >= 100) + continue; + if(flag==0){ + clif_skill_poseffect(src,skillid,skilllv,tmpx,tmpy,tick); + flag=1; + } + if(i > 0) + skill_addtimerskill(src,tick+i*1000,0,tmpx,tmpy,skillid,skilllv,(x1<<16)|y1,flag); + x1 = tmpx; + y1 = tmpy; + } + skill_addtimerskill(src,tick+i*1000,0,tmpx,tmpy,skillid,skilllv,-1,flag); + } + break; + + case AL_WARP: /* ワープポータル */ + if(sd) { + if(map[sd->bl.m].flag.noteleport) /* テレポ禁止 */ + break; + clif_skill_warppoint(sd,sd->skillid,sd->status.save_point.map, + (sd->skilllv>1)?sd->status.memo_point[0].map:"", + (sd->skilllv>2)?sd->status.memo_point[1].map:"", + (sd->skilllv>3)?sd->status.memo_point[2].map:""); + } + break; + case MO_BODYRELOCATION: + if(sd){ + pc_movepos(sd,x,y); + }else if( src->type==BL_MOB ) + mob_warp((struct mob_data *)src,-1,x,y,0); + break; + case AM_CANNIBALIZE: // バイオプラント + if(sd){ + int mx,my,id=0; + struct mob_data *md; + + mx = x;// + (rand()%10 - 5); + my = y;// + (rand()%10 - 5); + id=mob_once_spawn(sd,"this",mx,my,"--ja--",1118,1,""); + if( (md=(struct mob_data *)map_id2bl(id)) !=NULL ){ + md->master_id=sd->bl.id; + md->hp=2210+skilllv*200; + md->state.special_mob_ai=1; + md->deletetimer=add_timer(gettick()+skill_get_time(skillid,skilllv),mob_timer_delete,id,0); + } + clif_skill_poseffect(src,skillid,skilllv,x,y,tick); + } + break; + case AM_SPHEREMINE: // スフィアーマイン + if(sd){ + int mx,my,id=0; + struct mob_data *md; + + mx = x;// + (rand()%10 - 5); + my = y;// + (rand()%10 - 5); + id=mob_once_spawn(sd,"this",mx,my,"--ja--",1142,1,""); + if( (md=(struct mob_data *)map_id2bl(id)) !=NULL ){ + md->master_id=sd->bl.id; + md->hp=1000+skilllv*200; + md->state.special_mob_ai=2; + md->deletetimer=add_timer(gettick()+skill_get_time(skillid,skilllv),mob_timer_delete,id,0); + } + clif_skill_poseffect(src,skillid,skilllv,x,y,tick); + } + break; + } + + return 0; +} + +/*========================================== + * スキル使用(詠唱完了、map指定) + *------------------------------------------ + */ +int skill_castend_map( struct map_session_data *sd,int skill_num, const char *map) +{ + int x=0,y=0; + + nullpo_retr(0, sd); + if( sd->bl.prev == NULL || pc_isdead(sd) ) + return 0; + + if( sd->opt1>0 || sd->status.option&2 ) + return 0; + //スキルが使えない状態異常中 + if(sd->sc_data){ + if( sd->sc_data[SC_DIVINA].timer!=-1 || + sd->sc_data[SC_ROKISWEIL].timer!=-1 || + sd->sc_data[SC_AUTOCOUNTER].timer != -1 || + sd->sc_data[SC_STEELBODY].timer != -1 || + sd->sc_data[SC_DANCING].timer!=-1 || + sd->sc_data[SC_BERSERK].timer != -1 ) + return 0; + } + + if( skill_num != sd->skillid) /* 不正パケットらしい */ + return 0; + + pc_stopattack(sd); + + if(battle_config.pc_skill_log) + printf("PC %d skill castend skill =%d map=%s\n",sd->bl.id,skill_num,map); + pc_stop_walking(sd,0); + + if(strcmp(map,"cancel")==0) + return 0; + + switch(skill_num){ + case AL_TELEPORT: /* テレポート */ + if(strcmp(map,"Random")==0) + pc_randomwarp(sd,3); + else + pc_setpos(sd,sd->status.save_point.map, + sd->status.save_point.x,sd->status.save_point.y,3); + break; + + case AL_WARP: /* ワープポータル */ + { + const struct point *p[]={ + &sd->status.save_point,&sd->status.memo_point[0], + &sd->status.memo_point[1],&sd->status.memo_point[2], + }; + struct skill_unit_group *group; + int i; + int maxcount=0; + + if((maxcount = skill_get_maxcount(sd->skillid)) > 0) { + int c; + for(i=c=0;i<MAX_SKILLUNITGROUP;i++) { + if(sd->skillunit[i].alive_count > 0 && sd->skillunit[i].skill_id == sd->skillid) + c++; + } + if(c >= maxcount) { + clif_skill_fail(sd,sd->skillid,0,0); + sd->canact_tick = gettick(); + sd->canmove_tick = gettick(); + sd->skillitem = sd->skillitemlv = -1; + return 0; + } + } + + for(i=0;i<sd->skilllv;i++){ + if(strcmp(map,p[i]->map)==0){ + x=p[i]->x; + y=p[i]->y; + break; + } + } + if(x==0 || y==0) /* 不正パケット? */ + return 0; + + if(!skill_check_condition(sd,3)) + return 0; + if((group=skill_unitsetting(&sd->bl,sd->skillid,sd->skilllv,sd->skillx,sd->skilly,0))==NULL) + return 0; + group->valstr=(char *)aCalloc(24,sizeof(char)); + memcpy(group->valstr,map,24); + group->val2=(x<<16)|y; + } + break; + } + + return 0; +} + +/*========================================== + * スキルユニット設定処理 + *------------------------------------------ + */ +struct skill_unit_group *skill_unitsetting( struct block_list *src, int skillid,int skilllv,int x,int y,int flag) +{ + struct skill_unit_group *group; + int i,count=1,limit=10000,val1=0,val2=0; + int target=BCT_ENEMY,interval=1000,range=0; + int dir=0,aoe_diameter=0; // -- aoe_diameter (moonsoul) added for sage Area Of Effect skills + + nullpo_retr(0, src); + + switch(skillid){ /* 設定 */ + + case MG_SAFETYWALL: /* セイフティウォール */ + limit=skill_get_time(skillid,skilllv); + val2=skilllv+1; + interval = -1; + target=(battle_config.defnotenemy)?BCT_NOENEMY:BCT_ALL; + break; + + case MG_FIREWALL: /* ファイヤーウォール */ + if(src->x == x && src->y == y) + dir = 2; + else + dir=map_calc_dir(src,x,y); + if(dir&1) count=5; + else count=3; + limit=skill_get_time(skillid,skilllv); + val2=4+skilllv; + interval=1; + break; + + case AL_PNEUMA: /* ニューマ */ + limit=skill_get_time(skillid,skilllv); + interval = -1; + target=(battle_config.defnotenemy)?BCT_NOENEMY:BCT_ALL; + count = 9; + break; + + case AL_WARP: /* ワープポータル */ + target=BCT_ALL; + val1=skilllv+6; + if(flag==0) + limit=2000; + else + limit=skill_get_time(skillid,skilllv); + break; + + case PR_SANCTUARY: /* サンクチュアリ */ + count=21; + limit=skill_get_time(skillid,skilllv); + val1=skilllv+3; + val2=(skilllv>6)?777:skilllv*100; + target=BCT_ALL; + range=1; + break; + + case PR_MAGNUS: /* マグヌスエクソシズム */ + count=33; + limit=skill_get_time(skillid,skilllv); + interval=3000; + break; + + case WZ_FIREPILLAR: /* ファイアーピラー */ + if(flag==0) + limit=skill_get_time(skillid,skilllv); + else + limit=1000; + interval=2000; + val1=skilllv+2; + range=1; + break; + + case MG_THUNDERSTORM: /* サンダーストーム */ + limit=500; + range=1; + break; + + case WZ_FROSTNOVA: /* フロストノヴァ */ + limit=500; + range=5; + break; + case WZ_HEAVENDRIVE: /* ヘヴンズドライブ */ + limit=500; + range=2; + break; + + case WZ_METEOR: /* メテオストーム */ + limit=500; + range=3; + break; + + case WZ_SIGHTRASHER: + limit=500; + count=41; + break; + + case WZ_VERMILION: /* ロードオブヴァーミリオン */ + limit=4100; + interval=1000; + range=6; + break; + + case WZ_ICEWALL: /* アイスウォール */ + limit=skill_get_time(skillid,skilllv); + count=5; + break; + + case WZ_STORMGUST: /* ストームガスト */ + limit=4600; + interval=450; + range=5; + break; + + case WZ_QUAGMIRE: /* クァグマイア */ + limit=skill_get_time(skillid,skilllv); + interval=200; + count=25; + break; + + case HT_SKIDTRAP: /* スキッドトラップ */ + case HT_LANDMINE: /* ランドマイン */ + case HT_ANKLESNARE: /* アンクルスネア */ + case HT_SANDMAN: /* サンドマン */ + case PF_SPIDERWEB: /* スパイダーウェッブ */ + case HT_FLASHER: /* フラッシャー */ + case HT_FREEZINGTRAP: /* フリージングトラップ */ + case HT_BLASTMINE: /* ブラストマイン */ + case HT_CLAYMORETRAP: /* クレイモアートラップ */ + limit=skill_get_time(skillid,skilllv); + range=1; + break; + + case HT_TALKIEBOX: /* トーキーボックス */ + limit=skill_get_time(skillid,skilllv); + range=1; + target=BCT_ALL; + break; + + case HT_SHOCKWAVE: /* ショックウェーブトラップ */ + limit=skill_get_time(skillid,skilllv); + range=1; + val1=skilllv*15+10; + break; + + case AS_VENOMDUST: /* ベノムダスト */ + limit=skill_get_time(skillid,skilllv); + interval=1000; + count=5; + break; + + case CR_GRANDCROSS: /* グランドクロス */ + count=29; + limit=1000; + interval=300; + break; + + case SA_VOLCANO: /* ボルケーノ */ + case SA_DELUGE: /* デリュージ */ + case SA_VIOLENTGALE: /* バイオレントゲイル */ + limit=skill_get_time(skillid,skilllv); + count=skilllv<=2?25:(skilllv<=4?49:81); + target=BCT_ALL; + break; + + case SA_LANDPROTECTOR: /* グランドクロス */ + limit=skill_get_time(skillid,skilllv); // changed to get duration from cast_db (moonsoul) + val1=skilllv*15+10; + aoe_diameter=skilllv+skilllv%2+5; + target=BCT_ALL; + count=aoe_diameter*aoe_diameter; // -- this will not function if changed to ^2 (moonsoul) + break; + + case BD_LULLABY: /* 子守唄 */ + case BD_ETERNALCHAOS: /* エターナルカオス */ + case BD_ROKISWEIL: /* ロキの叫び */ + count=81; + limit=skill_get_time(skillid,skilllv); + range=5; + target=BCT_ALL; + break; + case BD_RICHMANKIM: + case BD_DRUMBATTLEFIELD: /* 戦太鼓の響き */ + case BD_RINGNIBELUNGEN: /* ニーベルングの指輪 */ + case BD_INTOABYSS: /* 深淵の中に */ + case BD_SIEGFRIED: /* 不死身のジークフリード */ + count=81; + limit=skill_get_time(skillid,skilllv); + range=5; + target=BCT_PARTY; + break; + + case BA_WHISTLE: /* 口笛 */ + count=49; + limit=skill_get_time(skillid,skilllv); + range=5; + target=BCT_NOENEMY; + if(src->type == BL_PC) + val1 = (pc_checkskill((struct map_session_data *)src,BA_MUSICALLESSON)+1)>>1; + val2 = ((battle_get_agi(src)/10)&0xffff)<<16; + val2 |= (battle_get_luk(src)/10)&0xffff; + break; + case DC_HUMMING: /* ハミング */ + count=49; + limit=skill_get_time(skillid,skilllv); + range=5; + target=BCT_NOENEMY; + if(src->type == BL_PC) + val1 = (pc_checkskill((struct map_session_data *)src,DC_DANCINGLESSON)+1)>>1; + val2 = battle_get_dex(src)/10; + break; + + case BA_DISSONANCE: /* 不協和音 */ + case DC_UGLYDANCE: /* 自分勝手なダンス */ + count=49; + limit=skill_get_time(skillid,skilllv); + range=5; + target=BCT_ENEMY; + break; + + case DC_DONTFORGETME: /* 私を忘れないで… */ + count=49; + limit=skill_get_time(skillid,skilllv); + range=5; + target=BCT_ENEMY; + if(src->type == BL_PC) + val1 = (pc_checkskill((struct map_session_data *)src,DC_DANCINGLESSON)+1)>>1; + val2 = ((battle_get_str(src)/20)&0xffff)<<16; + val2 |= (battle_get_agi(src)/10)&0xffff; + break; + case BA_POEMBRAGI: /* ブラギの詩 */ + count=49; + limit=skill_get_time(skillid,skilllv); + range=5; + target=BCT_NOENEMY; + if(src->type == BL_PC) + val1 = pc_checkskill((struct map_session_data *)src,BA_MUSICALLESSON); + val2 = ((battle_get_dex(src)/10)&0xffff)<<16; + val2 |= (battle_get_int(src)/5)&0xffff; + break; + case BA_APPLEIDUN: /* イドゥンの林檎 */ + count=49; + limit=skill_get_time(skillid,skilllv); + range=5; + target=BCT_NOENEMY; + if(src->type == BL_PC) + val1 = ((pc_checkskill((struct map_session_data *)src,BA_MUSICALLESSON))&0xffff)<<16; + else + val1 = 0; + val1 |= (battle_get_vit(src))&0xffff; + val2 = 0;//回復用タイムカウンタ(6秒毎に1増加) + break; + case DC_SERVICEFORYOU: /* サービスフォーユー */ + count=49; + limit=skill_get_time(skillid,skilllv); + range=5; + target=BCT_PARTY; + if(src->type == BL_PC) + val1 = (pc_checkskill((struct map_session_data *)src,DC_DANCINGLESSON)+1)>>1; + val2 = battle_get_int(src)/10; + break; + case BA_ASSASSINCROSS: /* 夕陽のアサシンクロス */ + count=49; + limit=skill_get_time(skillid,skilllv); + range=5; + target=BCT_NOENEMY; + if(src->type == BL_PC) + val1 = (pc_checkskill((struct map_session_data *)src,BA_MUSICALLESSON)+1)>>1; + val2 = battle_get_agi(src)/20; + break; + case DC_FORTUNEKISS: /* 幸運のキス */ + count=49; + limit=skill_get_time(skillid,skilllv); + range=5; + target=BCT_NOENEMY; + if(src->type == BL_PC) + val1 = (pc_checkskill((struct map_session_data *)src,DC_DANCINGLESSON)+1)>>1; + val2 = battle_get_luk(src)/10; + break; + case AM_DEMONSTRATION: /* デモンストレーション */ + limit=skill_get_time(skillid,skilllv); + interval=1000; + range=1; + target=BCT_ENEMY; + break; + case WE_CALLPARTNER: /* あなたに逢いたい */ + limit=skill_get_time(skillid,skilllv); + range=-1; + break; + + case HP_BASILICA: /* バジリカ */ + limit=skill_get_time(skillid,skilllv); + target=BCT_ALL; + range=3; + //Fix to prevent the priest from walking while Basilica is up. + battle_stopwalking(src,1); + skill_status_change_start(src,SC_ANKLE,skilllv,0,0,0,limit,0); + break; + case PA_GOSPEL: /* ゴスペル */ + count=49; + target=BCT_PARTY; + limit=skill_get_time(skillid,skilllv); + break; + case PF_FOGWALL: /* フォグウォール */ + count=15; + limit=skill_get_time(skillid,skilllv); + break; + case RG_GRAFFITI: /* Graffiti */ + count=1; // Leave this at 1 [Valaris] + limit=600000; // Time length [Valaris] + break; + }; + + nullpo_retr(NULL, group=skill_initunitgroup(src,count,skillid,skilllv,skill_get_unit_id(skillid,flag&1))); + group->limit=limit; + group->val1=val1; + group->val2=val2; + group->target_flag=target; + group->interval=interval; + group->range=range; + if(skillid==HT_TALKIEBOX || + skillid==RG_GRAFFITI){ + group->valstr=calloc(80, 1); + if(group->valstr==NULL){ + printf("skill_castend_map: out of memory !\n"); + exit(1); + } + memcpy(group->valstr,talkie_mes,80); + } + for(i=0;i<count;i++){ + struct skill_unit *unit; + int ux=x,uy=y,val1=skilllv,val2=0,limit=group->limit,alive=1; + int range=group->range; + switch(skillid){ /* 設定 */ + case AL_PNEUMA: /* ニューマ */ + { + static const int dx[9]={-1, 0, 1,-1, 0, 1,-1, 0, 1}; + static const int dy[9]={-1,-1,-1, 0, 0, 0, 1, 1, 1}; + ux+=dx[i]; + uy+=dy[i]; + } + break; + case MG_FIREWALL: /* ファイヤーウォール */ + { + if(dir&1){ /* 斜め配置 */ + static const int dx[][5]={ + { 1,1,0,0,-1 }, { -1,-1,0,0,1 }, + },dy[][5]={ + { 1,0,0,-1,-1 }, { 1,0,0,-1,-1 }, + }; + ux+=dx[(dir>>1)&1][i]; + uy+=dy[(dir>>1)&1][i]; + }else{ /* 上下配置 */ + if(dir%4==0) /* 上下 */ + ux+=i-1; + else /* 左右 */ + uy+=i-1; + } + val2=group->val2; + } + break; + + case PR_SANCTUARY: /* サンクチュアリ */ + { + static const int dx[]={ + -1,0,1, -2,-1,0,1,2, -2,-1,0,1,2, -2,-1,0,1,2, -1,0,1 }; + static const int dy[]={ + -2,-2,-2, -1,-1,-1,-1,-1, 0,0,0,0,0, 1,1,1,1,1, 2,2,2, }; + ux+=dx[i]; + uy+=dy[i]; + } + break; + + case PR_MAGNUS: /* マグヌスエクソシズム */ + { + static const int dx[]={ -1,0,1, -1,0,1, -3,-2,-1,0,1,2,3, + -3,-2,-1,0,1,2,3, -3,-2,-1,0,1,2,3, -1,0,1, -1,0,1, }; + static const int dy[]={ + -3,-3,-3, -2,-2,-2, -1,-1,-1,-1,-1,-1,-1, + 0,0,0,0,0,0,0, 1,1,1,1,1,1,1, 2,2,2, 3,3,3 }; + ux+=dx[i]; + uy+=dy[i]; + } + break; + + case WZ_SIGHTRASHER: + { + static const int dx[]={ + -5, 0, 5, -4, 0, 4, -3, 0, 3, -2, 0, 2, -1, 0, 1, -5,-4,-3,-2,-1, 0, 1, 2, 3, 4, 5, -1, 0, 1, -2, 0, 2, -3, 0, 3, -4, 0, 4, -5, 0, 5 }; + static const int dy[]={ + -5,-5,-5, -4,-4,-4, -3,-3,-3, -2,-2,-2, -1,-1,-1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5 }; + ux+=dx[i]; + uy+=dy[i]; + } + break; + + case WZ_ICEWALL: /* アイスウォール */ + { + static const int dirx[8]={0,-1,-1,-1,0,1,1,1}; + static const int diry[8]={1,1,0,-1,-1,-1,0,1}; + if(skilllv <= 1) + val1 = 500; + else + val1 = 200 + 200*skilllv; + if(src->x == x && src->y == y) + dir = 2; + else + dir=map_calc_dir(src,x,y); + ux+=(2-i)*diry[dir]; + uy+=(i-2)*dirx[dir]; + } + break; + + case WZ_QUAGMIRE: /* クァグマイア */ + ux+=(i%5-2); + uy+=(i/5-2); + if(i==12) + range=2; + else + range=-1; + + break; + + case AS_VENOMDUST: /* ベノムダスト */ + { + static const int dx[]={-1,0,0,0,1}; + static const int dy[]={0,-1,0,1,0}; + ux+=dx[i]; + uy+=dy[i]; + } + break; + + case CR_GRANDCROSS: /* グランドクロス */ + { + static const int dx[]={ + 0, 0, -1,0,1, -2,-1,0,1,2, -4,-3,-2,-1,0,1,2,3,4, -2,-1,0,1,2, -1,0,1, 0, 0, }; + static const int dy[]={ + -4, -3, -2,-2,-2, -1,-1,-1,-1,-1, 0,0,0,0,0,0,0,0,0, 1,1,1,1,1, 2,2,2, 3, 4, }; + ux+=dx[i]; + uy+=dy[i]; + } + break; + case SA_VOLCANO: /* ボルケーノ */ + case SA_DELUGE: /* デリュージ */ + case SA_VIOLENTGALE: /* バイオレントゲイル */ + { + int u_range=0,central=0; + if(skilllv<=2){ + u_range=2; + central=12; + }else if(skilllv<=4){ + u_range=3; + central=24; + }else if(skilllv>=5){ + u_range=4; + central=40; + } + ux+=(i%(u_range*2+1)-u_range); + uy+=(i/(u_range*2+1)-u_range); + + if(i==central) + range=u_range;//中央のユニットの効果範囲は全範囲 + else + range=-1;//中央以外のユニットは飾り + } + break; + case SA_LANDPROTECTOR: /* ランドプロテクター */ + { + int u_range=0; + + if(skilllv<=2) u_range=3; + else if(skilllv<=4) u_range=4; + else if(skilllv>=5) u_range=5; + + ux+=(i%(u_range*2+1)-u_range); + uy+=(i/(u_range*2+1)-u_range); + + range=0; + } + break; + + /* ダンスなど */ + case BD_LULLABY: /* 子守歌 */ + case BD_RICHMANKIM: /* ニヨルドの宴 */ + case BD_ETERNALCHAOS: /* 永遠の混沌 */ + case BD_DRUMBATTLEFIELD:/* 戦太鼓の響き */ + case BD_RINGNIBELUNGEN: /* ニーベルングの指輪 */ + case BD_ROKISWEIL: /* ロキの叫び */ + case BD_INTOABYSS: /* 深淵の中に */ + case BD_SIEGFRIED: /* 不死身のジークフリード */ + ux+=(i%9-4); + uy+=(i/9-4); + if(i==40) + range=4; /* 中心の場合は範囲を4にオーバーライド */ + else + range=-1; /* 中心じゃない場合は範囲を-1にオーバーライド */ + break; + case BA_DISSONANCE: /* 不協和音 */ + case BA_WHISTLE: /* 口笛 */ + case BA_ASSASSINCROSS: /* 夕陽のアサシンクロス */ + case BA_POEMBRAGI: /* ブラギの詩 */ + case BA_APPLEIDUN: /* イドゥンの林檎 */ + case DC_UGLYDANCE: /* 自分勝手なダンス */ + case DC_HUMMING: /* ハミング */ + case DC_DONTFORGETME: /* 私を忘れないで… */ + case DC_FORTUNEKISS: /* 幸運のキス */ + case DC_SERVICEFORYOU: /* サービスフォーユー */ + ux+=(i%7-3); + uy+=(i/7-3); + if(i==40) + range=4; /* 中心の場合は範囲を4にオーバーライド */ + else + range=-1; /* 中心じゃない場合は範囲を-1にオーバーライド */ + break; + case PA_GOSPEL: /* ゴスペル */ + ux+=(i%7-3); + uy+=(i/7-3); + break; + case PF_FOGWALL: /* フォグウォール */ + ux+=(i%5-2); + uy+=(i/5-1); + break; + case RG_GRAFFITI: /* Graffiti [Valaris] */ + ux+=(i%5-2); + uy+=(i/5-2); + break; + } + //直上スキルの場合設置座標上にランドプロテクターがないかチェック + if(range<=0) + map_foreachinarea(skill_landprotector,src->m,ux,uy,ux,uy,BL_SKILL,skillid,&alive); + + if(skillid==WZ_ICEWALL && alive){ + val2=map_getcell(src->m,ux,uy); + if(val2==5 || val2==1) + alive=0; + else { + map_setcell(src->m,ux,uy,5); + clif_changemapcell(src->m,ux,uy,5,0); + } + } + + if(alive){ + nullpo_retr(NULL, unit=skill_initunit(group,i,ux,uy)); + unit->val1=val1; + unit->val2=val2; + unit->limit=limit; + unit->range=range; + } + } + return group; +} + +/*========================================== + * スキルユニットの発動イベント + *------------------------------------------ + */ +int skill_unit_onplace(struct skill_unit *src,struct block_list *bl,unsigned int tick) +{ + struct skill_unit_group *sg; + struct block_list *ss; + struct skill_unit_group_tickset *ts; + struct map_session_data *srcsd=NULL; + int diff,goflag,splash_count=0; + + nullpo_retr(0, src); + nullpo_retr(0, bl); + + if( bl->prev==NULL || !src->alive || (bl->type == BL_PC && pc_isdead((struct map_session_data *)bl) ) ) + return 0; + + nullpo_retr(0, sg=src->group); + nullpo_retr(0, ss=map_id2bl(sg->src_id)); + + if(ss->type == BL_PC) + nullpo_retr(0, srcsd=(struct map_session_data *)ss); + if(srcsd && srcsd->chatID) + return 0; + + if( bl->type!=BL_PC && bl->type!=BL_MOB ) + return 0; + nullpo_retr(0, ts=skill_unitgrouptickset_search( bl, sg->group_id)); + diff=DIFF_TICK(tick,ts->tick); + goflag=(diff>sg->interval || diff<0); + if (sg->skill_id == CR_GRANDCROSS && !battle_config.gx_allhit) // 重なっていたら3HITしない + goflag = (diff>sg->interval*map_count_oncell(bl->m,bl->x,bl->y) || diff<0); + + //対象がLP上に居る場合は無効 + map_foreachinarea(skill_landprotector,bl->m,bl->x,bl->y,bl->x,bl->y,BL_SKILL,0,&goflag); + + if(!goflag) + return 0; + ts->tick=tick; + ts->group_id=sg->group_id; + + switch(sg->unit_id){ + case 0x83: /* サンクチュアリ */ + { + int race=battle_get_race(bl); + int damage_flag = (battle_check_undead(race,battle_get_elem_type(bl)) || race == 6)? 1:0; + + if( battle_get_hp(bl)>=battle_get_max_hp(bl) && !damage_flag) + break; + + if((sg->val1--)<=0){ + skill_delunitgroup(sg); + return 0; + } + if(!damage_flag) { + int heal=sg->val2; + if( bl->type==BL_PC && ((struct map_session_data *)bl)->special_state.no_magic_damage) + heal=0; /* 黄金蟲カード(ヒール量0) */ + clif_skill_nodamage(&src->bl,bl,AL_HEAL,heal,1); + battle_heal(NULL,bl,heal,0,0); + } + else + skill_attack(BF_MAGIC,ss,&src->bl,bl,sg->skill_id,sg->skill_lv,tick,0); + } + break; + + case 0x84: /* マグヌスエクソシズム */ + { + int race=battle_get_race(bl); + int damage_flag = (battle_check_undead(race,battle_get_elem_type(bl)) || race == 6)? 1:0; + + if(!damage_flag) + return 0; + skill_attack(BF_MAGIC,ss,&src->bl,bl,sg->skill_id,sg->skill_lv,tick,0); + } + break; + + case 0x85: /* ニューマ */ + { + struct skill_unit *unit2; + struct status_change *sc_data=battle_get_sc_data(bl); + int type=SC_PNEUMA; + if(sc_data && sc_data[type].timer==-1) + skill_status_change_start(bl,type,sg->skill_lv,(int)src,0,0,0,0); + else if((unit2=(struct skill_unit *)sc_data[type].val2) && unit2 != src ){ + if(DIFF_TICK(sg->tick,unit2->group->tick)>0 ) + skill_status_change_start(bl,type,sg->skill_lv,(int)src,0,0,0,0); + ts->tick-=sg->interval; + } + } + break; + case 0x7e: /* セイフティウォール */ + { + struct skill_unit *unit2; + struct status_change *sc_data=battle_get_sc_data(bl); + int type=SC_SAFETYWALL; + if(sc_data && sc_data[type].timer==-1) + skill_status_change_start(bl,type,sg->skill_lv,(int)src,0,0,0,0); + else if((unit2=(struct skill_unit *)sc_data[type].val2) && unit2 != src ){ + if(sg->val1 < unit2->group->val1 ) + skill_status_change_start(bl,type,sg->skill_lv,(int)src,0,0,0,0); + ts->tick-=sg->interval; + } + } + break; + + case 0x86: /* ロードオブヴァーミリオン(&ストームガスト &グランドクロス) */ + skill_attack(BF_MAGIC,ss,&src->bl,bl,sg->skill_id,sg->skill_lv,tick,0); + break; + + case 0x7f: /* ファイヤーウォール */ + if( (src->val2--)>0) + skill_attack(BF_MAGIC,ss,&src->bl,bl, + sg->skill_id,sg->skill_lv,tick,0); + if( src->val2<=0 ) + skill_delunit(src); + break; + + case 0x87: /* ファイアーピラー(発動前) */ + skill_delunit(src); + skill_unitsetting(ss,sg->skill_id,sg->skill_lv,src->bl.x,src->bl.y,1); + break; + + case 0x88: /* ファイアーピラー(発動後) */ + if(DIFF_TICK(tick,sg->tick) < 150) + skill_attack(BF_MAGIC,ss,&src->bl,bl,sg->skill_id,sg->skill_lv,tick,0); + break; + + case 0x90: /* スキッドトラップ */ + { + int i,c = skill_get_blewcount(sg->skill_id,sg->skill_lv); + if(map[bl->m].flag.gvg) c = 0; + for(i=0;i<c;i++) + skill_blown(&src->bl,bl,1|0x30000); + sg->unit_id = 0x8c; + clif_changelook(&src->bl,LOOK_BASE,sg->unit_id); + sg->limit=DIFF_TICK(tick,sg->tick)+1500; + } + break; + + case 0x93: /* ランドマイン */ + skill_attack(BF_MISC,ss,&src->bl,bl,sg->skill_id,sg->skill_lv,tick,0); + sg->unit_id = 0x8c; + clif_changelook(&src->bl,LOOK_BASE,0x88); + sg->limit=DIFF_TICK(tick,sg->tick)+1500; + break; + + case 0x8f: /* ブラストマイン */ + case 0x94: /* ショックウェーブトラップ */ + case 0x95: /* サンドマン */ + case 0x96: /* フラッシャー */ + case 0x97: /* フリージングトラップ */ + case 0x98: /* クレイモアートラップ */ + map_foreachinarea(skill_count_target,src->bl.m + ,src->bl.x-src->range,src->bl.y-src->range + ,src->bl.x+src->range,src->bl.y+src->range + ,0,&src->bl,&splash_count); + map_foreachinarea(skill_trap_splash,src->bl.m + ,src->bl.x-src->range,src->bl.y-src->range + ,src->bl.x+src->range,src->bl.y+src->range + ,0,&src->bl,tick,splash_count); + sg->unit_id = 0x8c; + clif_changelook(&src->bl,LOOK_BASE,sg->unit_id); + sg->limit=DIFF_TICK(tick,sg->tick)+1500; + break; + + case 0x91: /* アンクルスネア */ + { + struct status_change *sc_data=battle_get_sc_data(bl); + if(sg->val2==0 && sc_data && sc_data[SC_ANKLE].timer==-1){ + int moveblock = ( bl->x/BLOCK_SIZE != src->bl.x/BLOCK_SIZE || bl->y/BLOCK_SIZE != src->bl.y/BLOCK_SIZE); + int sec=skill_get_time2(sg->skill_id,sg->skill_lv) - (double)battle_get_agi(bl)*0.1; + if(battle_get_mode(bl)&0x20) + sec = sec/5; + battle_stopwalking(bl,1); + skill_status_change_start(bl,SC_ANKLE,sg->skill_lv,0,0,0,sec,0); + + if(moveblock) map_delblock(bl); + bl->x = src->bl.x; + bl->y = src->bl.y; + if(moveblock) map_addblock(bl); + if(bl->type == BL_MOB) + clif_fixmobpos((struct mob_data *)bl); + else if(bl->type == BL_PET) + clif_fixpetpos((struct pet_data *)bl); + else + clif_fixpos(bl); + clif_01ac(&src->bl); + sg->limit=DIFF_TICK(tick,sg->tick) + sec; + sg->val2=bl->id; + } + } + break; + + case 0x80: /* ワープポータル(発動後) */ + if(bl->type==BL_PC){ + struct map_session_data *sd = (struct map_session_data *)bl; + if(sd && src->bl.m == bl->m && src->bl.x == bl->x && src->bl.y == bl->y && src->bl.x == sd->to_x && src->bl.y == sd->to_y) { + if( battle_config.chat_warpportal || !sd->chatID ){ + if((sg->val1--)>0){ + pc_setpos(sd,sg->valstr,sg->val2>>16,sg->val2&0xffff,3); + if(sg->src_id == bl->id ||( strcmp(map[src->bl.m].name,sg->valstr) == 0 && src->bl.x == (sg->val2>>16) && src->bl.y == (sg->val2&0xffff) )) + skill_delunitgroup(sg); + }else + skill_delunitgroup(sg); + } + } + }else if(bl->type==BL_MOB && battle_config.mob_warpportal){ + int m=map_mapname2mapid(sg->valstr); + struct mob_data *md; + md=(struct mob_data *)bl; + mob_warp((struct mob_data *)bl,m,sg->val2>>16,sg->val2&0xffff,3); + } + break; + + case 0x8e: /* クァグマイア */ + { + int type=SkillStatusChangeTable[sg->skill_id]; + if( bl->type==BL_PC && ((struct map_session_data *)bl)->special_state.no_magic_damage ) + break; + if( battle_get_sc_data(bl)[type].timer==-1 ) + skill_status_change_start(bl,type,sg->skill_lv,(int)src,0,0,skill_get_time2(sg->skill_id,sg->skill_lv),0); + } + break; + case 0x92: /* ベノムダスト */ + { + struct status_change *sc_data=battle_get_sc_data(bl); + int type=SkillStatusChangeTable[sg->skill_id]; + if( sc_data && sc_data[type].timer==-1 ) + skill_status_change_start(bl,type,sg->skill_lv,(int)src,0,0,skill_get_time2(sg->skill_id,sg->skill_lv),0); + } + break; + case 0x9a: /* ボルケーノ */ + case 0x9b: /* デリュージ */ + case 0x9c: /* バイオレントゲイル */ + { + struct skill_unit *unit2; + struct status_change *sc_data=battle_get_sc_data(bl); + int type=SkillStatusChangeTable[sg->skill_id]; + if(sc_data && sc_data[type].timer==-1) + skill_status_change_start(bl,type,sg->skill_lv,(int)src,0,0,skill_get_time2(sg->skill_id,sg->skill_lv),0); + else if((unit2=(struct skill_unit *)sc_data[type].val2) && unit2 != src ){ + if( DIFF_TICK(sg->tick,unit2->group->tick)>0 ) + skill_status_change_start(bl,type,sg->skill_lv,(int)src,0,0,skill_get_time2(sg->skill_id,sg->skill_lv),0); + ts->tick-=sg->interval; + } + } break; + + case 0x9e: /* 子守唄 */ + case 0x9f: /* ニヨルドの宴 */ + case 0xa0: /* 永遠の混沌 */ + case 0xa1: /* 戦太鼓の響き */ + case 0xa2: /* ニーベルングの指輪 */ + case 0xa3: /* ロキの叫び */ + case 0xa4: /* 深淵の中に */ + case 0xa5: /* 不死身のジークフリード */ + case 0xa6: /* 不協和音 */ + case 0xa7: /* 口笛 */ + case 0xa8: /* 夕陽のアサシンクロス */ + case 0xa9: /* ブラギの詩 */ + case 0xab: /* 自分勝手なダンス */ + case 0xac: /* ハミング */ + case 0xad: /* 私を忘れないで… */ + case 0xae: /* 幸運のキス */ + case 0xaf: /* サービスフォーユー */ + case 0xb4: + { + struct skill_unit *unit2; + struct status_change *sc_data=battle_get_sc_data(bl); + int type=SkillStatusChangeTable[sg->skill_id]; + if(sg->src_id == bl->id) + break; + if(sc_data && sc_data[type].timer==-1) + skill_status_change_start(bl,type,sg->skill_lv,sg->val1,sg->val2, + (int)src,skill_get_time2(sg->skill_id,sg->skill_lv),0); + else if( (unit2=(struct skill_unit *)sc_data[type].val4) && unit2 != src ){ + if( unit2->group && DIFF_TICK(sg->tick,unit2->group->tick)>0 ) + skill_status_change_start(bl,type,sg->skill_lv,sg->val1,sg->val2, + (int)src,skill_get_time2(sg->skill_id,sg->skill_lv),0); + ts->tick-=sg->interval; + } + } break; + + case 0xaa: /* イドゥンの林檎 */ + { + struct skill_unit *unit2; + struct status_change *sc_data=battle_get_sc_data(bl); + int type=SkillStatusChangeTable[sg->skill_id]; + if(sg->src_id == bl->id) + break; + if( sc_data && sc_data[type].timer==-1) + skill_status_change_start(bl,type,sg->skill_lv,(sg->val1)>>16,(sg->val1)&0xffff, + (int)src,skill_get_time2(sg->skill_id,sg->skill_lv),0); + else if((unit2=(struct skill_unit *)sc_data[type].val4) && unit2 != src ){ + if( DIFF_TICK(sg->tick,unit2->group->tick)>0 ) + skill_status_change_start(bl,type,sg->skill_lv,(sg->val1)>>16,(sg->val1)&0xffff, + (int)src,skill_get_time2(sg->skill_id,sg->skill_lv),0); + ts->tick-=sg->interval; + } + } break; + + case 0xb1: /* デモンストレーション */ + skill_attack(BF_WEAPON,ss,&src->bl,bl,sg->skill_id,sg->skill_lv,tick,0); + if(bl->type == BL_PC && rand()%100 < sg->skill_lv && battle_config.equipment_breaking) + pc_breakweapon((struct map_session_data *)bl); + break; + case 0x99: /* トーキーボックス */ + if(sg->src_id == bl->id) //自分が踏んでも発動しない + break; + if(sg->val2==0){ + clif_talkiebox(&src->bl,sg->valstr); + sg->unit_id = 0x8c; + clif_changelook(&src->bl,LOOK_BASE,sg->unit_id); + sg->limit=DIFF_TICK(tick,sg->tick)+5000; + sg->val2=-1; //踏んだ + } + break; + case 0xb2: /* あなたを_会いたいです */ + case 0xb3: /* ゴスペル */ + case 0xb6: /* フォグウォール */ + //とりあえず何もしない + break; + + + + + + + case 0xb7: /* スパイダーウェッブ */ + if(sg->val2==0){ + int moveblock = ( bl->x/BLOCK_SIZE != src->bl.x/BLOCK_SIZE || bl->y/BLOCK_SIZE != src->bl.y/BLOCK_SIZE); + skill_additional_effect(ss,bl,sg->skill_id,sg->skill_lv,BF_MISC,tick); + if(moveblock) map_delblock(bl); + bl->x = (&src->bl)->x; + bl->y = (&src->bl)->y; + if(moveblock) map_addblock(bl); + if(bl->type == BL_MOB) + clif_fixmobpos((struct mob_data *)bl); + else if(bl->type == BL_PET) + clif_fixpetpos((struct pet_data *)bl); + else + clif_fixpos(bl); + clif_01ac(&src->bl); + sg->limit=DIFF_TICK(tick,sg->tick) + skill_get_time2(sg->skill_id,sg->skill_lv); + sg->val2=bl->id; + } + break; + +/* default: + if(battle_config.error_log) + printf("skill_unit_onplace: Unknown skill unit id=%d block=%d\n",sg->unit_id,bl->id); + break;*/ + } + if(bl->type==BL_MOB && ss!=bl) /* スキル使用条件のMOBスキル */ + { + if(battle_config.mob_changetarget_byskill == 1) + { + int target=((struct mob_data *)bl)->target_id; + if(ss->type == BL_PC) + ((struct mob_data *)bl)->target_id=ss->id; + mobskill_use((struct mob_data *)bl,tick,MSC_SKILLUSED|(sg->skill_id<<16)); + ((struct mob_data *)bl)->target_id=target; + } + else + mobskill_use((struct mob_data *)bl,tick,MSC_SKILLUSED|(sg->skill_id<<16)); + } + + return 0; +} +/*========================================== + * スキルユニットから離脱する(もしくはしている)場合 + *------------------------------------------ + */ +int skill_unit_onout(struct skill_unit *src,struct block_list *bl,unsigned int tick) +{ + struct skill_unit_group *sg; + + nullpo_retr(0, src); + nullpo_retr(0, bl); + nullpo_retr(0, sg=src->group); + + if( bl->prev==NULL || !src->alive ) + return 0; + + if( bl->type!=BL_PC && bl->type!=BL_MOB ) + return 0; + + switch(sg->unit_id){ + case 0x7e: /* セイフティウォール */ + case 0x85: /* ニューマ */ + case 0x8e: /* クァグマイア */ + { + struct status_change *sc_data=battle_get_sc_data(bl); + int type= + (sg->unit_id==0x85)?SC_PNEUMA: + ((sg->unit_id==0x7e)?SC_SAFETYWALL: + SC_QUAGMIRE); + if((type != SC_QUAGMIRE || bl->type != BL_MOB) && + sc_data && sc_data[type].timer!=-1 && ((struct skill_unit *)sc_data[type].val2)==src){ + skill_status_change_end(bl,type,-1); + } + } break; + + case 0x91: /* アンクルスネア */ + { + struct block_list *target=map_id2bl(sg->val2); + if( target && target==bl ){ + skill_status_change_end(bl,SC_ANKLE,-1); + sg->limit=DIFF_TICK(tick,sg->tick)+1000; + } + } + break; + case 0xb5: + case 0xb8: + { + struct block_list *target=map_id2bl(sg->val2); + if( target==bl ) + skill_status_change_end(bl,SC_SPIDERWEB,-1); + sg->limit=DIFF_TICK(tick,sg->tick)+1000; + } + break; + case 0xb6: + { + struct block_list *target=map_id2bl(sg->val2); + if( target==bl ) + skill_status_change_end(bl,SC_FOGWALL,-1); + sg->limit=DIFF_TICK(tick,sg->tick)+1000; + } + break; + case 0x9a: /* ボルケーノ */ + case 0x9b: /* デリュージ */ + case 0x9c: /* バイオレントゲイル */ + { + struct status_change *sc_data=battle_get_sc_data(bl); + struct skill_unit *su; + int type=SkillStatusChangeTable[sg->skill_id]; + if( sc_data && sc_data[type].timer!=-1 && (su=((struct skill_unit *)sc_data[type].val2)) && su == src ){ + skill_status_change_end(bl,type,-1); + } + } + break; + + case 0x9e: /* 子守唄 */ + case 0x9f: /* ニヨルドの宴 */ + case 0xa0: /* 永遠の混沌 */ + case 0xa1: /* 戦太鼓の響き */ + case 0xa2: /* ニーベルングの指輪 */ + case 0xa3: /* ロキの叫び */ + case 0xa4: /* 深淵の中に */ + case 0xa5: /* 不死身のジークフリード */ + case 0xa6: /* 不協和音 */ + case 0xa7: /* 口笛 */ + case 0xa8: /* 夕陽のアサシンクロス */ + case 0xa9: /* ブラギの詩 */ + case 0xaa: /* イドゥンの林檎 */ + case 0xab: /* 自分勝手なダンス */ + case 0xac: /* ハミング */ + case 0xad: /* 私を忘れないで… */ + case 0xae: /* 幸運のキス */ + case 0xaf: /* サービスフォーユー */ + case 0xb4: + { + struct status_change *sc_data=battle_get_sc_data(bl); + struct skill_unit *su; + int type=SkillStatusChangeTable[sg->skill_id]; + if( sc_data && sc_data[type].timer!=-1 && (su=((struct skill_unit *)sc_data[type].val4)) && su == src ){ + skill_status_change_end(bl,type,-1); + } + } + break; + case 0xb7: /* スパイダーウェッブ */ + { + struct block_list *target=map_id2bl(sg->val2); + if( target && target==bl ) + skill_status_change_end(bl,SC_SPIDERWEB,-1); + sg->limit=DIFF_TICK(tick,sg->tick)+1000; + } + break; + +/* default: + if(battle_config.error_log) + printf("skill_unit_onout: Unknown skill unit id=%d block=%d\n",sg->unit_id,bl->id); + break;*/ + } + skill_unitgrouptickset_delete(bl,sg->group_id); + return 0; +} +/*========================================== + * スキルユニットの削除イベント + *------------------------------------------ + */ +int skill_unit_ondelete(struct skill_unit *src,struct block_list *bl,unsigned int tick) +{ + struct skill_unit_group *sg; + + nullpo_retr(0, src); + nullpo_retr(0, bl); + nullpo_retr(0, sg = src->group); + + if( bl->prev==NULL || !src->alive ) + return 0; + + if( bl->type!=BL_PC && bl->type!=BL_MOB ) + return 0; + + switch(sg->unit_id){ + case 0x85: /* ニューマ */ + case 0x7e: /* セイフティウォール */ + case 0x8e: /* クァグマイヤ */ + case 0x9a: /* ボルケーノ */ + case 0x9b: /* デリュージ */ + case 0x9c: /* バイオレントゲイル */ + case 0x9e: /* 子守唄 */ + case 0x9f: /* ニヨルドの宴 */ + case 0xa0: /* 永遠の混沌 */ + case 0xa1: /* 戦太鼓の響き */ + case 0xa2: /* ニーベルングの指輪 */ + case 0xa3: /* ロキの叫び */ + case 0xa4: /* 深淵の中に */ + case 0xa5: /* 不死身のジークフリード */ + case 0xa6: /* 不協和音 */ + case 0xa7: /* 口笛 */ + case 0xa8: /* 夕陽のアサシンクロス */ + case 0xa9: /* ブラギの詩 */ + case 0xaa: /* イドゥンの林檎 */ + case 0xab: /* 自分勝手なダンス */ + case 0xac: /* ハミング */ + case 0xad: /* 私を忘れないで… */ + case 0xae: /* 幸運のキス */ + case 0xaf: /* サービスフォーユー */ + case 0xb4: + return skill_unit_onout(src,bl,tick); + +/* default: + if(battle_config.error_log) + printf("skill_unit_ondelete: Unknown skill unit id=%d block=%d\n",sg->unit_id,bl->id); + break;*/ + } + skill_unitgrouptickset_delete(bl,sg->group_id); + return 0; +} +/*========================================== + * スキルユニットの限界イベント + *------------------------------------------ + */ +int skill_unit_onlimit(struct skill_unit *src,unsigned int tick) +{ + struct skill_unit_group *sg; + + nullpo_retr(0, src); + nullpo_retr(0, sg=src->group); + + switch(sg->unit_id){ + case 0x81: /* ワープポータル(発動前) */ + { + struct skill_unit_group *group= + skill_unitsetting(map_id2bl(sg->src_id),sg->skill_id,sg->skill_lv, + src->bl.x,src->bl.y,1); + if(group == NULL) + return 0; + group->valstr=calloc(24, 1); + if(group->valstr==NULL){ + printf("skill_unit_onlimit: out of memory !\n"); + exit(1); + } + memcpy(group->valstr,sg->valstr,24); + group->val2=sg->val2; + } + break; + + case 0x8d: /* アイスウォール */ + map_setcell(src->bl.m,src->bl.x,src->bl.y,src->val2); + clif_changemapcell(src->bl.m,src->bl.x,src->bl.y,src->val2,1); + break; + case 0xb2: /* あなたに会いたい */ + { + struct map_session_data *sd = NULL; + struct map_session_data *p_sd = NULL; + if((sd = (struct map_session_data *)(map_id2bl(sg->src_id))) == NULL) + return 0; + if((p_sd = pc_get_partner(sd)) == NULL) + return 0; + + pc_setpos(p_sd,map[src->bl.m].name,src->bl.x,src->bl.y,3); + } + break; + } + return 0; +} +/*========================================== + * スキルユニットのダメージイベント + *------------------------------------------ + */ +int skill_unit_ondamaged(struct skill_unit *src,struct block_list *bl, + int damage,unsigned int tick) +{ + struct skill_unit_group *sg; + + nullpo_retr(0, src); + nullpo_retr(0, sg=src->group); + + switch(sg->unit_id){ + case 0x8d: /* アイスウォール */ + src->val1-=damage; + break; + case 0x8f: /* ブラストマイン */ + case 0x98: /* クレイモアートラップ */ + skill_blown(bl,&src->bl,2); //吹き飛ばしてみる + break; + default: + damage = 0; + break; + } + return damage; +} + + +/*---------------------------------------------------------------------------- */ + +/*========================================== + * スキル使用(詠唱完了、場所指定) + *------------------------------------------ + */ +int skill_castend_pos( int tid, unsigned int tick, int id,int data ) +{ + struct map_session_data* sd=map_id2sd(id)/*,*target_sd=NULL*/; + int range,maxcount; + + nullpo_retr(0, sd); + + if( sd->bl.prev == NULL ) + return 0; + if( sd->skilltimer != tid ) /* タイマIDの確認 */ + return 0; + if(sd->skilltimer != -1 && pc_checkskill(sd,SA_FREECAST) > 0) { + sd->speed = sd->prev_speed; + clif_updatestatus(sd,SP_SPEED); + } + sd->skilltimer=-1; + if(pc_isdead(sd)) { + sd->canact_tick = tick; + sd->canmove_tick = tick; + sd->skillitem = sd->skillitemlv = -1; + return 0; + } + + if(battle_config.pc_skill_reiteration == 0) { + range = -1; + switch(sd->skillid) { + case MG_SAFETYWALL: + case WZ_FIREPILLAR: + case HT_SKIDTRAP: + case HT_LANDMINE: + case HT_ANKLESNARE: + case HT_SHOCKWAVE: + case HT_SANDMAN: + case HT_FLASHER: + case HT_FREEZINGTRAP: + case HT_BLASTMINE: + case HT_CLAYMORETRAP: + case HT_TALKIEBOX: + case AL_WARP: + case PF_SPIDERWEB: /* スパイダーウェッブ */ + case RG_GRAFFITI: /* グラフィティ */ + range = 0; + break; + case AL_PNEUMA: + range = 1; + break; + } + if(range >= 0) { + if(skill_check_unit_range(sd->bl.m,sd->skillx,sd->skilly,range,sd->skillid) > 0) { + clif_skill_fail(sd,sd->skillid,0,0); + sd->canact_tick = tick; + sd->canmove_tick = tick; + sd->skillitem = sd->skillitemlv = -1; + return 0; + } + } + } + if(battle_config.pc_skill_nofootset) { + range = -1; + switch(sd->skillid) { + case WZ_FIREPILLAR: + case HT_SKIDTRAP: + case HT_LANDMINE: + case HT_ANKLESNARE: + case HT_SHOCKWAVE: + case HT_SANDMAN: + case HT_FLASHER: + case HT_FREEZINGTRAP: + case HT_BLASTMINE: + case HT_CLAYMORETRAP: + case HT_TALKIEBOX: + case PF_SPIDERWEB: /* スパイダーウェッブ */ + case WZ_ICEWALL: + range = 1; + break; + case AL_WARP: + range = 0; + break; + } + if(range >= 0) { + if(skill_check_unit_range2(sd->bl.m,sd->skillx,sd->skilly,range) > 0) { + clif_skill_fail(sd,sd->skillid,0,0); + sd->canact_tick = tick; + sd->canmove_tick = tick; + sd->skillitem = sd->skillitemlv = -1; + return 0; + } + } + } + + if(battle_config.pc_land_skill_limit) { + maxcount = skill_get_maxcount(sd->skillid); + if(maxcount > 0) { + int i,c; + for(i=c=0;i<MAX_SKILLUNITGROUP;i++) { + if(sd->skillunit[i].alive_count > 0 && sd->skillunit[i].skill_id == sd->skillid) + c++; + } + if(c >= maxcount) { + clif_skill_fail(sd,sd->skillid,0,0); + sd->canact_tick = tick; + sd->canmove_tick = tick; + sd->skillitem = sd->skillitemlv = -1; + return 0; + } + } + } + + range = skill_get_range(sd->skillid,sd->skilllv); + if(range < 0) + range = battle_get_range(&sd->bl) - (range + 1); + range += battle_config.pc_skill_add_range; + if(battle_config.skill_out_range_consume) { // changed to allow casting when target walks out of range [Valaris] + if(range < distance(sd->bl.x,sd->bl.y,sd->skillx,sd->skilly)) { + clif_skill_fail(sd,sd->skillid,0,0); + sd->canact_tick = tick; + sd->canmove_tick = tick; + sd->skillitem = sd->skillitemlv = -1; + return 0; + } + } + if(!skill_check_condition(sd,1)) { /* 使用条件チェック */ + sd->canact_tick = tick; + sd->canmove_tick = tick; + sd->skillitem = sd->skillitemlv = -1; + return 0; + } + sd->skillitem = sd->skillitemlv = -1; + if(battle_config.skill_out_range_consume) { + if(range < distance(sd->bl.x,sd->bl.y,sd->skillx,sd->skilly)) { + clif_skill_fail(sd,sd->skillid,0,0); + sd->canact_tick = tick; + sd->canmove_tick = tick; + return 0; + } + } + + if(battle_config.pc_skill_log) + printf("PC %d skill castend skill=%d\n",sd->bl.id,sd->skillid); + pc_stop_walking(sd,0); + + skill_castend_pos2(&sd->bl,sd->skillx,sd->skilly,sd->skillid,sd->skilllv,tick,0); + + return 0; +} + +/*========================================== + * 範囲内キャラ存在確認判定処理(foreachinarea) + *------------------------------------------ + */ + +static int skill_check_condition_char_sub(struct block_list *bl,va_list ap) +{ + int *c; + struct block_list *src; + struct map_session_data *sd; + struct map_session_data *ssd; + struct pc_base_job s_class; + struct pc_base_job ss_class; + + nullpo_retr(0, bl); + nullpo_retr(0, ap); + nullpo_retr(0, sd=(struct map_session_data*)bl); + nullpo_retr(0, src=va_arg(ap,struct block_list *)); + nullpo_retr(0, c=va_arg(ap,int *)); + nullpo_retr(0, ssd=(struct map_session_data*)src); + + s_class = pc_calc_base_job(sd->status.class); + //チェックしない設定ならcにありえない大きな数字を返して終了 + if(!battle_config.player_skill_partner_check){ //本当はforeachの前にやりたいけど設定適用箇所をまとめるためにここへ + (*c)=99; + return 0; + } + + ; + ss_class = pc_calc_base_job(ssd->status.class); + + switch(ssd->skillid){ + case PR_BENEDICTIO: /* 聖体降福 */ + if(sd != ssd && (sd->status.class == 4 || sd->status.class == 8 || sd->status.class == 15 || + sd->status.class == 4005 || sd->status.class == 4009 || sd->status.class == 4016) && + (sd->bl.x == ssd->bl.x - 1 || sd->bl.x == ssd->bl.x + 1) && sd->status.sp >= 10) + (*c)++; + break; + case BD_LULLABY: /* 子守歌 */ + case BD_RICHMANKIM: /* ニヨルドの宴 */ + case BD_ETERNALCHAOS: /* 永遠の混沌 */ + case BD_DRUMBATTLEFIELD: /* 戦太鼓の響き */ + case BD_RINGNIBELUNGEN: /* ニーベルングの指輪 */ + case BD_ROKISWEIL: /* ロキの叫び */ + case BD_INTOABYSS: /* 深淵の中に */ + case BD_SIEGFRIED: /* 不死身のジークフリード */ + case BD_RAGNAROK: /* 神々の黄昏 */ + case CG_MOONLIT: /* 月明りの泉に落ちる花びら */ + if(sd != ssd && + ((ssd->status.class==19 && sd->status.class==20) || + (ssd->status.class==20 && sd->status.class==19) || + (ssd->status.class==4020 && sd->status.class==4021) || + (ssd->status.class==4021 && sd->status.class==4020) || + (ssd->status.class==20 && sd->status.class==4020) || + (ssd->status.class==19 && sd->status.class==4021)) && + pc_checkskill(sd,ssd->skillid) > 0 && + (*c)==0 && + sd->status.party_id == ssd->status.party_id && + !pc_issit(sd) && + sd->sc_data[SC_DANCING].timer==-1 + ) + (*c)=pc_checkskill(sd,ssd->skillid); + break; + } + return 0; +} +/*========================================== + * 範囲内キャラ存在確認判定後スキル使用処理(foreachinarea) + *------------------------------------------ + */ + +static int skill_check_condition_use_sub(struct block_list *bl,va_list ap) +{ + int *c; + struct block_list *src; + struct map_session_data *sd; + struct map_session_data *ssd; + struct pc_base_job s_class; + struct pc_base_job ss_class; + int skillid,skilllv; + + nullpo_retr(0, bl); + nullpo_retr(0, ap); + nullpo_retr(0, sd=(struct map_session_data*)bl); + nullpo_retr(0, src=va_arg(ap,struct block_list *)); + nullpo_retr(0, c=va_arg(ap,int *)); + nullpo_retr(0, ssd=(struct map_session_data*)src); + + s_class = pc_calc_base_job(sd->status.class); + + //チェックしない設定ならcにありえない大きな数字を返して終了 + if(!battle_config.player_skill_partner_check){ //本当はforeachの前にやりたいけど設定適用箇所をまとめるためにここへ + (*c)=99; + return 0; + } + + ss_class = pc_calc_base_job(ssd->status.class); + skillid=ssd->skillid; + skilllv=ssd->skilllv; + switch(skillid){ + case PR_BENEDICTIO: /* 聖体降福 */ + if(sd != ssd && (sd->status.class == 4 || sd->status.class == 8 || sd->status.class == 15 || + sd->status.class == 4005 || sd->status.class == 4009 || sd->status.class == 4016) && + (sd->bl.x == ssd->bl.x - 1 || sd->bl.x == ssd->bl.x + 1) && sd->status.sp >= 10){ + sd->status.sp -= 10; + pc_calcstatus(sd,0); + (*c)++; + } + break; + case BD_LULLABY: /* 子守歌 */ + case BD_RICHMANKIM: /* ニヨルドの宴 */ + case BD_ETERNALCHAOS: /* 永遠の混沌 */ + case BD_DRUMBATTLEFIELD: /* 戦太鼓の響き */ + case BD_RINGNIBELUNGEN: /* ニーベルングの指輪 */ + case BD_ROKISWEIL: /* ロキの叫び */ + case BD_INTOABYSS: /* 深淵の中に */ + case BD_SIEGFRIED: /* 不死身のジークフリード */ + case BD_RAGNAROK: /* 神々の黄昏 */ + case CG_MOONLIT: /* 月明りの泉に落ちる花びら */ + if(sd != ssd && //本人以外で + ((ssd->status.class==19 && sd->status.class==20) || + (ssd->status.class==20 && sd->status.class==19) || + (ssd->status.class==4020 && sd->status.class==4021) || + (ssd->status.class==4021 && sd->status.class==4020) || + (ssd->status.class==20 && sd->status.class==4020) || + (ssd->status.class==19 && sd->status.class==4021)) && //自分がダンサーならバードで + pc_checkskill(sd,skillid) > 0 && //スキルを持っていて + (*c)==0 && //最初の一人で + sd->status.party_id == ssd->status.party_id && //パーティーが同じで + !pc_issit(sd) && //座ってない + sd->sc_data[SC_DANCING].timer==-1 //ダンス中じゃない + ){ + ssd->sc_data[SC_DANCING].val4=bl->id; + clif_skill_nodamage(bl,src,skillid,skilllv,1); + skill_status_change_start(bl,SC_DANCING,skillid,ssd->sc_data[SC_DANCING].val2,0,src->id,skill_get_time(skillid,skilllv)+1000,0); + sd->skillid_dance=sd->skillid=skillid; + sd->skilllv_dance=sd->skilllv=skilllv; + (*c)++; + } + break; + } + return 0; +} +/*========================================== + * 範囲内バイオプラント、スフィアマイン用Mob存在確認判定処理(foreachinarea) + *------------------------------------------ + */ + +static int skill_check_condition_mob_master_sub(struct block_list *bl,va_list ap) +{ + int *c,src_id=0,mob_class=0; + struct mob_data *md; + + nullpo_retr(0, bl); + nullpo_retr(0, ap); + nullpo_retr(0, md=(struct mob_data*)bl); + nullpo_retr(0, src_id=va_arg(ap,int)); + nullpo_retr(0, mob_class=va_arg(ap,int)); + nullpo_retr(0, c=va_arg(ap,int *)); + + if(md->class==mob_class && md->master_id==src_id) + (*c)++; + return 0; +} + +/*========================================== + * スキル使用条件(偽で使用失敗) + *------------------------------------------ + */ +int skill_check_condition(struct map_session_data *sd,int type) +{ + int i,hp,sp,hp_rate,sp_rate,zeny,weapon,state,spiritball,skill,lv,mhp; + int index[10],itemid[10],amount[10]; + + nullpo_retr(0, sd); + + if( battle_config.gm_skilluncond>0 && pc_isGM(sd)>= battle_config.gm_skilluncond ) { + sd->skillitem = sd->skillitemlv = -1; + return 1; + } + + if( sd->opt1>0) { + clif_skill_fail(sd,sd->skillid,0,0); + sd->skillitem = sd->skillitemlv = -1; + return 0; + } + if(pc_is90overweight(sd)) { + clif_skill_fail(sd,sd->skillid,9,0); + sd->skillitem = sd->skillitemlv = -1; + return 0; + } + + if(sd->skillid == AC_MAKINGARROW && sd->state.make_arrow_flag == 1) { + sd->skillitem = sd->skillitemlv = -1; + return 0; + } + if(sd->skillid == AM_PHARMACY && sd->state.produce_flag == 1) { + sd->skillitem = sd->skillitemlv = -1; + return 0; + } + + if(sd->skillitem == sd->skillid) { /* アイテムの場合無条件成功 */ + if(type&1) + sd->skillitem = sd->skillitemlv = -1; + return 1; + } + if( sd->opt1>0 ){ + clif_skill_fail(sd,sd->skillid,0,0); + return 0; + } + if(sd->sc_data){ + if( sd->sc_data[SC_DIVINA].timer!=-1 || + sd->sc_data[SC_ROKISWEIL].timer!=-1 || + (sd->sc_data[SC_AUTOCOUNTER].timer != -1 && sd->skillid != KN_AUTOCOUNTER) || + sd->sc_data[SC_STEELBODY].timer != -1 || + sd->sc_data[SC_BERSERK].timer != -1 + ){ + clif_skill_fail(sd,sd->skillid,0,0); + return 0; /* 状態異常や沈黙など */ + } + } + skill = sd->skillid; + lv = sd->skilllv; + hp=skill_get_hp(skill, lv); /* 消費HP */ + sp=skill_get_sp(skill, lv); /* 消費SP */ + if((sd->skillid_old == BD_ENCORE) && skill==sd->skillid_dance) + sp=sp/2; //アンコール時はSP消費が半分 + hp_rate = (lv <= 0)? 0:skill_db[skill].hp_rate[lv-1]; + sp_rate = (lv <= 0)? 0:skill_db[skill].sp_rate[lv-1]; + zeny = skill_get_zeny(skill,lv); + weapon = skill_db[skill].weapon; + state = skill_db[skill].state; + spiritball = (lv <= 0)? 0:skill_db[skill].spiritball[lv-1]; + mhp=skill_get_mhp(skill, lv); /* 消費HP */ + for(i=0;i<10;i++) { + itemid[i] = skill_db[skill].itemid[i]; + amount[i] = skill_db[skill].amount[i]; + } + if(mhp > 0) + hp += (sd->status.max_hp * mhp)/100; + if(hp_rate > 0) + hp += (sd->status.hp * hp_rate)/100; + else + hp += (sd->status.max_hp * abs(hp_rate))/100; + if(sp_rate > 0) + sp += (sd->status.sp * sp_rate)/100; + else + sp += (sd->status.max_sp * abs(sp_rate))/100; + if(sd->dsprate!=100) + sp=sp*sd->dsprate/100; /* 消費SP修正 */ + + switch(skill) { + case SA_CASTCANCEL: + if(sd->skilltimer == -1) { + clif_skill_fail(sd,skill,0,0); + return 0; + } + break; + case BS_MAXIMIZE: /* マキシマイズパワー */ + case NV_TRICKDEAD: /* 死んだふり */ + case TF_HIDING: /* ハイディング */ + case AS_CLOAKING: /* クローキング */ + case CR_AUTOGUARD: /* オートガード */ + case CR_DEFENDER: /* ディフェンダー */ + case ST_CHASEWALK: + if(sd->sc_data[SkillStatusChangeTable[skill]].timer!=-1) + return 1; /* 解除する場合はSP消費しない */ + break; + case AL_TELEPORT: + case AL_WARP: + if(map[sd->bl.m].flag.noteleport) { + clif_skill_teleportmessage(sd,0); + return 0; + } + break; + case MO_CALLSPIRITS: /* 気功 */ + if(sd->spiritball >= lv) { + clif_skill_fail(sd,skill,0,0); + return 0; + } + break; + case CH_SOULCOLLECT: /* 狂気功 */ + if(sd->spiritball >= 5) { + clif_skill_fail(sd,skill,0,0); + return 0; + } + break; + case MO_FINGEROFFENSIVE: //指弾 + if (sd->spiritball > 0 && sd->spiritball < spiritball) { + spiritball = sd->spiritball; + sd->spiritball_old = sd->spiritball; + } + else sd->spiritball_old = lv; + break; + case MO_CHAINCOMBO: //連打掌 + if(sd->sc_data[SC_BLADESTOP].timer==-1){ + if(sd->sc_data[SC_COMBO].timer == -1 || sd->sc_data[SC_COMBO].val1 != MO_TRIPLEATTACK) + return 0; + } + break; + case MO_COMBOFINISH: //猛龍拳 + if(sd->sc_data[SC_COMBO].timer == -1 || sd->sc_data[SC_COMBO].val1 != MO_CHAINCOMBO) + return 0; + break; + case CH_TIGERFIST: //伏虎拳 + if(sd->sc_data[SC_COMBO].timer == -1 || sd->sc_data[SC_COMBO].val1 != MO_COMBOFINISH) + return 0; + break; + case CH_CHAINCRUSH: //連柱崩撃 + if(sd->sc_data[SC_COMBO].timer == -1) + return 0; + if(sd->sc_data[SC_COMBO].val1 != MO_COMBOFINISH && sd->sc_data[SC_COMBO].val1 != CH_TIGERFIST) + return 0; + break; + case MO_EXTREMITYFIST: // 阿修羅覇鳳拳 + if((sd->sc_data[SC_COMBO].timer != -1 && (sd->sc_data[SC_COMBO].val1 == MO_COMBOFINISH || sd->sc_data[SC_COMBO].val1 == CH_CHAINCRUSH)) || sd->sc_data[SC_BLADESTOP].timer!=-1) + spiritball--; + break; + case BD_ADAPTATION: /* アドリブ */ + { + struct skill_unit_group *group=NULL; + if(sd->sc_data[SC_DANCING].timer==-1 || ((group=(struct skill_unit_group*)sd->sc_data[SC_DANCING].val2) && (skill_get_time(sd->sc_data[SC_DANCING].val1,group->skill_lv) - sd->sc_data[SC_DANCING].val3*1000) <= skill_get_time2(skill,lv))){ //ダンス中で使用後5秒以上のみ? + clif_skill_fail(sd,skill,0,0); + return 0; + } + } + break; + case PR_BENEDICTIO: /* 聖体降福 */ + { + int range=1; + int c=0; + if(!(type&1)){ + map_foreachinarea(skill_check_condition_char_sub,sd->bl.m, + sd->bl.x-range,sd->bl.y-range, + sd->bl.x+range,sd->bl.y+range,BL_PC,&sd->bl,&c); + if(c<2){ + clif_skill_fail(sd,skill,0,0); + return 0; + } + }else{ + map_foreachinarea(skill_check_condition_use_sub,sd->bl.m, + sd->bl.x-range,sd->bl.y-range, + sd->bl.x+range,sd->bl.y+range,BL_PC,&sd->bl,&c); + } + } + break; + case WE_CALLPARTNER: /* あなたに逢いたい */ + if(!sd->status.partner_id){ + clif_skill_fail(sd,skill,0,0); + return 0; + } + break; + case AM_CANNIBALIZE: /* バイオプラント */ + case AM_SPHEREMINE: /* スフィアーマイン */ + if(type&1){ + int c=0; + int maxcount=skill_get_maxcount(skill); + int mob_class=(skill==AM_CANNIBALIZE)?1118:1142; + if(battle_config.pc_land_skill_limit && maxcount>0) { + map_foreachinarea(skill_check_condition_mob_master_sub ,sd->bl.m, 0, 0, map[sd->bl.m].xs, map[sd->bl.m].ys, BL_MOB, sd->bl.id, mob_class,&c ); + if(c >= maxcount){ + clif_skill_fail(sd,skill,0,0); + return 0; + } + } + } + break; + case MG_FIREWALL: /* ファイアーウォール */ + /* 数制限 */ + if(battle_config.pc_land_skill_limit) { + int maxcount = skill_get_maxcount(skill); + if(maxcount > 0) { + int i,c; + for(i=c=0;i<MAX_SKILLUNITGROUP;i++) { + if(sd->skillunit[i].alive_count > 0 && sd->skillunit[i].skill_id == skill) + c++; + } + if(c >= maxcount) { + clif_skill_fail(sd,skill,0,0); + return 0; + } + } + } + break; + } + + if(!(type&2)){ + if( hp>0 && sd->status.hp < hp) { /* HPチェック */ + clif_skill_fail(sd,skill,2,0); /* HP不足:失敗通知 */ + return 0; + } + if( sp>0 && sd->status.sp < sp) { /* SPチェック */ + clif_skill_fail(sd,skill,1,0); /* SP不足:失敗通知 */ + return 0; + } + if( zeny>0 && sd->status.zeny < zeny) { + clif_skill_fail(sd,skill,5,0); + return 0; + } + if(!(weapon & (1<<sd->status.weapon) ) ) { + clif_skill_fail(sd,skill,6,0); + return 0; + } + if( spiritball > 0 && sd->spiritball < spiritball) { + clif_skill_fail(sd,skill,0,0); // 氣球不足 + return 0; + } + } + + switch(state) { + case ST_HIDING: + if(!(sd->status.option&2)) { + clif_skill_fail(sd,skill,0,0); + return 0; + } + break; + case ST_CLOAKING: + if(!(sd->status.option&4)) { + clif_skill_fail(sd,skill,0,0); + return 0; + } + break; + case ST_HIDDEN: + if(!pc_ishiding(sd)) { + clif_skill_fail(sd,skill,0,0); + return 0; + } + break; + case ST_RIDING: + if(!pc_isriding(sd)) { + clif_skill_fail(sd,skill,0,0); + return 0; + } + break; + case ST_FALCON: + if(!pc_isfalcon(sd)) { + clif_skill_fail(sd,skill,0,0); + return 0; + } + break; + case ST_CART: + if(!pc_iscarton(sd)) { + clif_skill_fail(sd,skill,0,0); + return 0; + } + break; + case ST_SHIELD: + if(sd->status.shield <= 0) { + clif_skill_fail(sd,skill,0,0); + return 0; + } + break; + case ST_SIGHT: + if(sd->sc_data[SC_SIGHT].timer == -1 && type&1) { + clif_skill_fail(sd,skill,0,0); + return 0; + } + break; + case ST_EXPLOSIONSPIRITS: + if(sd->sc_data[SC_EXPLOSIONSPIRITS].timer == -1) { + clif_skill_fail(sd,skill,0,0); + return 0; + } + break; + case ST_RECOV_WEIGHT_RATE: + if(battle_config.natural_heal_weight_rate <= 100 && sd->weight*100/sd->max_weight >= battle_config.natural_heal_weight_rate) { + clif_skill_fail(sd,skill,0,0); + return 0; + } + break; + case ST_MOVE_ENABLE: + { + struct walkpath_data wpd; + if(path_search(&wpd,sd->bl.m,sd->bl.x,sd->bl.y,sd->skillx,sd->skilly,1)==-1) { + clif_skill_fail(sd,skill,0,0); + return 0; + } + } + break; + case ST_WATER: + if(map_getcell(sd->bl.m,sd->bl.x,sd->bl.y) != 3 && (sd->sc_data[SC_DELUGE].timer==-1)){ //水場判定 + clif_skill_fail(sd,skill,0,0); + return 0; + } + break; + } + + for(i=0;i<10;i++) { + int x = lv%11 - 1; + index[i] = -1; + if(itemid[i] <= 0) + continue; + if(itemid[i] >= 715 && itemid[i] <= 717 && sd->special_state.no_gemstone) + continue; + if(((itemid[i] >= 715 && itemid[i] <= 717) || itemid[i] == 1065) && sd->sc_data[SC_INTOABYSS].timer != -1) + continue; + if(skill == AM_POTIONPITCHER && i != x) + continue; + + index[i] = pc_search_inventory(sd,itemid[i]); + if(index[i] < 0 || sd->status.inventory[index[i]].amount < amount[i]) { + if(itemid[i] == 716 || itemid[i] == 717) + clif_skill_fail(sd,skill,(7+(itemid[i]-716)),0); + else + clif_skill_fail(sd,skill,0,0); + return 0; + } + } + + if(!(type&1)) + return 1; + + if(skill != AM_POTIONPITCHER) { + if(skill == AL_WARP && !(type&2)) + return 1; + for(i=0;i<10;i++) { + if(index[i] >= 0) + pc_delitem(sd,index[i],amount[i],0); // アイテム消費 + } + } + + if(type&2) + return 1; + + if(sp > 0) { // SP消費 + sd->status.sp-=sp; + clif_updatestatus(sd,SP_SP); + } + if(hp > 0) { // HP消費 + sd->status.hp-=hp; + clif_updatestatus(sd,SP_HP); + } + if(zeny > 0) // Zeny消費 + pc_payzeny(sd,zeny); + if(spiritball > 0) // 氣球消費 + pc_delspiritball(sd,spiritball,0); + + + return 1; +} + +/*========================================== + * 詠唱時間計算 + *------------------------------------------ + */ +int skill_castfix( struct block_list *bl, int time ) +{ + struct map_session_data *sd; + struct mob_data *md; // [Valaris] + struct status_change *sc_data; + int dex; + int castrate=100; + int skill,lv,castnodex; + + nullpo_retr(0, bl); + + if(bl->type==BL_MOB){ // Crash fix [Valaris] + md=(struct mob_data*)bl; + skill = md->skillid; + lv = md->skilllv; + } + + else { + sd=(struct map_session_data*)bl; + skill = sd->skillid; + lv = sd->skilllv; + } + + sc_data = battle_get_sc_data(bl); + dex=battle_get_dex(bl); + + if (skill > MAX_SKILL_DB || skill < 0) + return 0; + + castnodex=skill_get_castnodex(skill, lv); + + if(time==0) + return 0; + if(castnodex > 0 && bl->type==BL_PC) + castrate=((struct map_session_data *)bl)->castrate; + else if (castnodex <= 0 && bl->type==BL_PC) { + castrate=((struct map_session_data *)bl)->castrate; + time=time*castrate*(battle_config.castrate_dex_scale - dex)/(battle_config.castrate_dex_scale * 100); + time=time*battle_config.cast_rate/100; + } + + /* サフラギウム */ + if(sc_data && sc_data[SC_SUFFRAGIUM].timer!=-1 ){ + time=time*(100-sc_data[SC_SUFFRAGIUM].val1*15)/100; + skill_status_change_end( bl, SC_SUFFRAGIUM, -1); + } + /* ブラギの詩 */ + if(sc_data && sc_data[SC_POEMBRAGI].timer!=-1 ) + time=time*(100-(sc_data[SC_POEMBRAGI].val1*3+sc_data[SC_POEMBRAGI].val2 + +(sc_data[SC_POEMBRAGI].val3>>16)))/100; + + return (time>0)?time:0; +} +/*========================================== + * ディレイ計算 + *------------------------------------------ + */ +int skill_delayfix( struct block_list *bl, int time ) +{ + struct status_change *sc_data; + + nullpo_retr(0, bl); + + sc_data = battle_get_sc_data(bl); + if(time<=0) + return 0; + + if(bl->type == BL_PC) { + if( battle_config.delay_dependon_dex ) /* dexの影響を計算する */ + time=time*(battle_config.castrate_dex_scale - battle_get_dex(bl))/battle_config.castrate_dex_scale; + time=time*battle_config.delay_rate/100; + } + + /* ブラギの詩 */ + if(sc_data && sc_data[SC_POEMBRAGI].timer!=-1 ) + time=time*(100-(sc_data[SC_POEMBRAGI].val1*3+sc_data[SC_POEMBRAGI].val2 + +(sc_data[SC_POEMBRAGI].val3&0xffff)))/100; + + return (time>0)?time:0; +} + +/*========================================== + * スキル使用(ID指定) + *------------------------------------------ + */ +int skill_use_id( struct map_session_data *sd, int target_id, + int skill_num, int skill_lv) +{ + unsigned int tick; + int casttime=0,delay=0,skill,range; + struct map_session_data* target_sd=NULL; + int forcecast=0; + struct block_list *bl; + struct status_change *sc_data; + tick=gettick(); + + nullpo_retr(0, sd); + + if( (bl=map_id2bl(target_id)) == NULL ){ +/* if(battle_config.error_log) + printf("skill target not found %d\n",target_id); */ + return 0; + } + if(sd->bl.m != bl->m || pc_isdead(sd)) + return 0; + + if(skillnotok(skill_num, sd)) // [MouseJstr] + return 0; + + if(sd->skillid==WZ_ICEWALL && map[sd->bl.m].flag.noicewall && !map[sd->bl.m].flag.pvp) { // noicewall flag [Valaris] + clif_skill_fail(sd,sd->skillid,0,0); + return 0; + } + sc_data=sd->sc_data; + + /* 沈黙や異常(ただし、グリムなどの判定をする) */ + if( sd->opt1>0 ) + return 0; + if(sd->sc_data){ + if(sc_data[SC_CHASEWALK].timer != -1) return 0; + if(sc_data[SC_VOLCANO].timer != -1){ + if(skill_num==WZ_ICEWALL) return 0; + } + if(sc_data[SC_ROKISWEIL].timer!=-1){ + if(skill_num==BD_ADAPTATION) return 0; + } + if( sd->sc_data[SC_DIVINA].timer!=-1 || + sd->sc_data[SC_ROKISWEIL].timer!=-1 || + (sd->sc_data[SC_AUTOCOUNTER].timer != -1 && sd->skillid != KN_AUTOCOUNTER) || + sd->sc_data[SC_STEELBODY].timer != -1 || + sd->sc_data[SC_BERSERK].timer != -1 ){ + return 0; /* 状態異常や沈黙など */ + } + + if(sc_data[SC_BLADESTOP].timer != -1){ + int lv = sc_data[SC_BLADESTOP].val1; + if(sc_data[SC_BLADESTOP].val2==1) return 0;//白羽された側なのでダメ + if(lv==1) return 0; + if(lv==2 && skill_num!=MO_FINGEROFFENSIVE) return 0; + if(lv==3 && skill_num!=MO_FINGEROFFENSIVE && skill_num!=MO_INVESTIGATE) return 0; + if(lv==4 && skill_num!=MO_FINGEROFFENSIVE && skill_num!=MO_INVESTIGATE && skill_num!=MO_CHAINCOMBO) return 0; + if(lv==5 && skill_num!=MO_FINGEROFFENSIVE && skill_num!=MO_INVESTIGATE && skill_num!=MO_CHAINCOMBO && skill_num!=MO_EXTREMITYFIST) return 0; + } + } + + if(sd->status.option&4 && skill_num==TF_HIDING) + return 0; + if(sd->status.option&2 && skill_num!=TF_HIDING && skill_num!=AS_GRIMTOOTH && skill_num!=RG_BACKSTAP && skill_num!=RG_RAID ) + return 0; + + if(map[sd->bl.m].flag.gvg){ //GvGで使用できないスキル + switch(skill_num){ + case SM_ENDURE: + case AL_TELEPORT: + case AL_WARP: + case WZ_ICEWALL: + case TF_BACKSLIDING: + case LK_BERSERK: + case HP_BASILICA: + case ST_CHASEWALK: + return 0; + } + } + + /* 演奏/ダンス中 */ + if( sc_data && sc_data[SC_DANCING].timer!=-1 ){ +// if(battle_config.pc_skill_log) +// printf("dancing! %d\n",skill_num); + if( sc_data[SC_DANCING].val4 && skill_num!=BD_ADAPTATION ) //合奏中はアドリブ以外不可 + return 0; + if(skill_num!=BD_ADAPTATION && skill_num!=BA_MUSICALSTRIKE && skill_num!=DC_THROWARROW){ + return 0; + } + } + + if(skill_get_inf2(skill_num)&0x200 && sd->bl.id == target_id) + return 0; + //直前のスキルが何か覚える必要のあるスキル + switch(skill_num){ + case SA_CASTCANCEL: + if(sd->skillid != skill_num){ //キャストキャンセル自体は覚えない + sd->skillid_old = sd->skillid; + sd->skilllv_old = sd->skilllv; + break; + } + case BD_ENCORE: /* アンコール */ + if(!sd->skillid_dance){ //前回使用した踊りがないとだめ + clif_skill_fail(sd,skill_num,0,0); + return 0; + }else{ + sd->skillid_old = skill_num; + } + break; + } + + sd->skillid = skill_num; + sd->skilllv = skill_lv; + + switch(skill_num){ //事前にレベルが変わったりするスキル + case BD_LULLABY: /* 子守歌 */ + case BD_RICHMANKIM: /* ニヨルドの宴 */ + case BD_ETERNALCHAOS: /* 永遠の混沌 */ + case BD_DRUMBATTLEFIELD: /* 戦太鼓の響き */ + case BD_RINGNIBELUNGEN: /* ニーベルングの指輪 */ + case BD_ROKISWEIL: /* ロキの叫び */ + case BD_INTOABYSS: /* 深淵の中に */ + case BD_SIEGFRIED: /* 不死身のジークフリード */ + case BD_RAGNAROK: /* 神々の黄昏 */ + case CG_MOONLIT: /* 月明りの泉に落ちる花びら */ + { + int range=1; + int c=0; + map_foreachinarea(skill_check_condition_char_sub,sd->bl.m, + sd->bl.x-range,sd->bl.y-range, + sd->bl.x+range,sd->bl.y+range,BL_PC,&sd->bl,&c); + if(c<1){ + clif_skill_fail(sd,skill_num,0,0); + return 0; + }else if(c==99){ //相方不要設定だった + ; + }else{ + sd->skilllv=(c + skill_lv)/2; + } + } + break; + } + + if(!skill_check_condition(sd,0)) return 0; + + /* 射程と障害物チェック */ + range = skill_get_range(skill_num,skill_lv); + if(range < 0) + range = battle_get_range(&sd->bl) - (range + 1); + if(!battle_check_range(&sd->bl,bl,range) ) + return 0; + + if(bl->type==BL_PC) { + target_sd=(struct map_session_data*)bl; + if(target_sd && skill_num == ALL_RESURRECTION && !pc_isdead(target_sd)) + return 0; + } + if((skill_num != MO_CHAINCOMBO && + skill_num != MO_COMBOFINISH && + skill_num != MO_EXTREMITYFIST && + skill_num != CH_TIGERFIST && + skill_num != CH_CHAINCRUSH) || + (skill_num == MO_EXTREMITYFIST && sd->state.skill_flag) ) + pc_stopattack(sd); + + casttime=skill_castfix(&sd->bl, skill_get_cast( skill_num,skill_lv) ); + if(skill_num != SA_MAGICROD) + delay=skill_delayfix(&sd->bl, skill_get_delay( skill_num,skill_lv) ); + sd->state.skillcastcancel = skill_db[skill_num].castcancel; + + switch(skill_num){ /* 何か特殊な処理が必要 */ +// case AL_HEAL: /* ヒール */ +// if(battle_check_undead(battle_get_race(bl),battle_get_elem_type(bl))) +// forcecast=1; /* ヒールアタックなら詠唱エフェクト有り */ +// break; + case ALL_RESURRECTION: /* リザレクション */ + if(bl->type != BL_PC && battle_check_undead(battle_get_race(bl),battle_get_elem_type(bl))){ /* 敵がアンデッドなら */ + forcecast=1; /* ターンアンデットと同じ詠唱時間 */ + casttime=skill_castfix(&sd->bl, skill_get_cast(PR_TURNUNDEAD,skill_lv) ); + } + break; + case MO_FINGEROFFENSIVE: /* 指弾 */ + casttime += casttime * ((skill_lv > sd->spiritball)? sd->spiritball:skill_lv); + break; + case MO_CHAINCOMBO: /*連打掌*/ + target_id = sd->attacktarget; + if( sc_data && sc_data[SC_BLADESTOP].timer!=-1 ){ + struct block_list *tbl; + if((tbl=(struct block_list *)sc_data[SC_BLADESTOP].val4) == NULL) //ターゲットがいない? + return 0; + target_id = tbl->id; + } + break; + case MO_COMBOFINISH: /*猛龍拳*/ + case CH_TIGERFIST: /* 伏虎拳 */ + case CH_CHAINCRUSH: /* 連柱崩撃 */ + target_id = sd->attacktarget; + break; + +// -- moonsoul (altered to allow proper usage of extremity from new champion combos) +// + case MO_EXTREMITYFIST: /*阿修羅覇鳳拳*/ + if(sc_data && sc_data[SC_COMBO].timer != -1 && (sc_data[SC_COMBO].val1 == MO_COMBOFINISH || sc_data[SC_COMBO].val1 == CH_CHAINCRUSH)) { + casttime = 0; + target_id = sd->attacktarget; + } + forcecast=1; + break; + case SA_MAGICROD: + case SA_SPELLBREAKER: + forcecast=1; + break; + case WE_MALE: + case WE_FEMALE: + { + struct map_session_data *p_sd = NULL; + if((p_sd = pc_get_partner(sd)) == NULL) + return 0; + target_id = p_sd->bl.id; + //rangeをもう1回検査 + range = skill_get_range(skill_num,skill_lv); + if(range < 0) + range = battle_get_range(&sd->bl) - (range + 1); + if(!battle_check_range(&sd->bl,&p_sd->bl,range) ){ + return 0; + } + } + break; + case AS_SPLASHER: /* ベナムスプラッシャー */ + { + struct status_change *t_sc_data = battle_get_sc_data(bl); + if(t_sc_data && t_sc_data[SC_POISON].timer==-1){ + clif_skill_fail(sd,skill_num,0,10); + return 0; + } + } + break; + case PF_MEMORIZE: /* メモライズ */ + casttime = 12000; + break; + + } + + //メモライズ状態ならキャストタイムが1/3 + if(sc_data && sc_data[SC_MEMORIZE].timer != -1 && casttime > 0){ + casttime = casttime/3; + if((--sc_data[SC_MEMORIZE].val2)<=0) + skill_status_change_end(&sd->bl, SC_MEMORIZE, -1); + } + + if(battle_config.pc_skill_log) + printf("PC %d skill use target_id=%d skill=%d lv=%d cast=%d\n",sd->bl.id,target_id,skill_num,skill_lv,casttime); + +// if(sd->skillitem == skill_num) +// casttime = delay = 0; + + if( casttime>0 || forcecast ){ /* 詠唱が必要 */ + struct mob_data *md; + clif_skillcasting( &sd->bl, + sd->bl.id, target_id, 0,0, skill_num,casttime); + + /* 詠唱反応モンスター */ + if( bl->type==BL_MOB && (md=(struct mob_data *)bl) && mob_db[md->class].mode&0x10 && + md->state.state!=MS_ATTACK && sd->invincible_timer == -1){ + md->target_id=sd->bl.id; + md->state.targettype = ATTACKABLE; + md->min_chase=13; + } + } + + if( casttime<=0 ) /* 詠唱の無いものはキャンセルされない */ + sd->state.skillcastcancel=0; + + sd->skilltarget = target_id; +/* sd->cast_target_bl = bl; */ + sd->skillx = 0; + sd->skilly = 0; + sd->canact_tick = tick + casttime + delay; + sd->canmove_tick = tick; + if(!(battle_config.pc_cloak_check_type&2) && sc_data && sc_data[SC_CLOAKING].timer != -1 && sd->skillid != AS_CLOAKING) + skill_status_change_end(&sd->bl,SC_CLOAKING,-1); + if(casttime > 0) { + sd->skilltimer = add_timer( tick+casttime, skill_castend_id, sd->bl.id, 0 ); + if((skill = pc_checkskill(sd,SA_FREECAST)) > 0) { + sd->prev_speed = sd->speed; + sd->speed = sd->speed*(175 - skill*5)/100; + clif_updatestatus(sd,SP_SPEED); + } + else + pc_stop_walking(sd,0); + } + else { + if(skill_num != SA_CASTCANCEL) + sd->skilltimer = -1; + skill_castend_id(sd->skilltimer,tick,sd->bl.id,0); + } + + //マジックパワーの効果終了 + if(sc_data && sc_data[SC_MAGICPOWER].timer != -1 && skill_num != HW_MAGICPOWER) + skill_status_change_end(&sd->bl,SC_MAGICPOWER,-1); + + return 0; +} + +/*========================================== + * スキル使用(場所指定) + *------------------------------------------ + */ +int skill_use_pos( struct map_session_data *sd, + int skill_x, int skill_y, int skill_num, int skill_lv) +{ + struct block_list bl; + struct status_change *sc_data; + unsigned int tick; + int casttime=0,delay=0,skill,range; + + nullpo_retr(0, sd); + + if(pc_isdead(sd)) + return 0; + + if (skillnotok(skill_num, sd)) // [MoueJstr] + return 0; + + sc_data=sd->sc_data; + + if( sd->opt1>0 ) + return 0; + if(sc_data){ + if( sc_data[SC_DIVINA].timer!=-1 || + sc_data[SC_ROKISWEIL].timer!=-1 || + sc_data[SC_AUTOCOUNTER].timer != -1 || + sc_data[SC_STEELBODY].timer != -1 || + sc_data[SC_DANCING].timer!=-1 || + sc_data[SC_BERSERK].timer != -1 ) + return 0; /* 状態異常や沈黙など */ + } + + if(sd->status.option&2) + return 0; + + if(map[sd->bl.m].flag.gvg && (skill_num == SM_ENDURE || skill_num == AL_TELEPORT || skill_num == AL_WARP || + skill_num == WZ_ICEWALL || skill_num == TF_BACKSLIDING)) + return 0; + + sd->skillid = skill_num; + sd->skilllv = skill_lv; + sd->skillx = skill_x; + sd->skilly = skill_y; + if(!skill_check_condition(sd,0)) return 0; + + /* 射程と障害物チェック */ + bl.type = BL_NUL; + bl.m = sd->bl.m; + bl.x = skill_x; + bl.y = skill_y; + range = skill_get_range(skill_num,skill_lv); + if(range < 0) + range = battle_get_range(&sd->bl) - (range + 1); + if(!battle_check_range(&sd->bl,&bl,range) ) + return 0; + + pc_stopattack(sd); + + casttime=skill_castfix(&sd->bl, skill_get_cast( skill_num,skill_lv) ); + delay=skill_delayfix(&sd->bl, skill_get_delay( skill_num,skill_lv) ); + sd->state.skillcastcancel = skill_db[skill_num].castcancel; + + if(battle_config.pc_skill_log) + printf("PC %d skill use target_pos=(%d,%d) skill=%d lv=%d cast=%d\n",sd->bl.id,skill_x,skill_y,skill_num,skill_lv,casttime); + +// if(sd->skillitem == skill_num) +// casttime = delay = 0; + //メモライズ状態ならキャストタイムが1/3 + if(sc_data && sc_data[SC_MEMORIZE].timer != -1 && casttime > 0){ + casttime = casttime/3; + if((--sc_data[SC_MEMORIZE].val2)<=0) + skill_status_change_end(&sd->bl, SC_MEMORIZE, -1); + } + + if( casttime>0 ) /* 詠唱が必要 */ + clif_skillcasting( &sd->bl, + sd->bl.id, 0, skill_x,skill_y, skill_num,casttime); + + if( casttime<=0 ) /* 詠唱の無いものはキャンセルされない */ + sd->state.skillcastcancel=0; + + sd->skilltarget = 0; +/* sd->cast_target_bl = NULL; */ + tick=gettick(); + sd->canact_tick = tick + casttime + delay; + sd->canmove_tick = tick; + if(!(battle_config.pc_cloak_check_type&2) && sc_data && sc_data[SC_CLOAKING].timer != -1) + skill_status_change_end(&sd->bl,SC_CLOAKING,-1); + if(casttime > 0) { + sd->skilltimer = add_timer( tick+casttime, skill_castend_pos, sd->bl.id, 0 ); + if((skill = pc_checkskill(sd,SA_FREECAST)) > 0) { + sd->prev_speed = sd->speed; + sd->speed = sd->speed*(175 - skill*5)/100; + clif_updatestatus(sd,SP_SPEED); + } + else + pc_stop_walking(sd,0); + } + else { + sd->skilltimer = -1; + skill_castend_pos(sd->skilltimer,tick,sd->bl.id,0); + } + //マジックパワーの効果終了 + if(sc_data && sc_data[SC_MAGICPOWER].timer != -1 && skill_num != HW_MAGICPOWER) + skill_status_change_end(&sd->bl,SC_MAGICPOWER,-1); + + return 0; +} + +/*========================================== + * スキル詠唱キャンセル + *------------------------------------------ + */ +int skill_castcancel(struct block_list *bl,int type) +{ + int inf; + int ret=0; + + nullpo_retr(0, bl); + + if(bl->type==BL_PC){ + struct map_session_data *sd=(struct map_session_data *)bl; + unsigned long tick=gettick(); + nullpo_retr(0, sd); + sd->canact_tick=tick; + sd->canmove_tick = tick; + if( sd->skilltimer!=-1){ + if(pc_checkskill(sd,SA_FREECAST) > 0) { + sd->speed = sd->prev_speed; + clif_updatestatus(sd,SP_SPEED); + } + if(!type) { + if((inf = skill_get_inf( sd->skillid )) == 2 || inf == 32) + ret=delete_timer( sd->skilltimer, skill_castend_pos ); + else + ret=delete_timer( sd->skilltimer, skill_castend_id ); + if(ret<0) + printf("delete timer error : skillid : %d\n",sd->skillid); + } + else { + if((inf = skill_get_inf( sd->skillid_old )) == 2 || inf == 32) + ret=delete_timer( sd->skilltimer, skill_castend_pos ); + else + ret=delete_timer( sd->skilltimer, skill_castend_id ); + if(ret<0) + printf("delete timer error : skillid : %d\n",sd->skillid_old); + } + sd->skilltimer=-1; + clif_skillcastcancel(bl); + } + + return 0; + }else if(bl->type==BL_MOB){ + struct mob_data *md=(struct mob_data *)bl; + nullpo_retr(0, md); + if( md->skilltimer!=-1 ){ + if((inf = skill_get_inf( md->skillid )) == 2 || inf == 32) + ret=delete_timer( md->skilltimer, mobskill_castend_pos ); + else + ret=delete_timer( md->skilltimer, mobskill_castend_id ); + md->skilltimer=-1; + clif_skillcastcancel(bl); + } + if(ret<0) + printf("delete timer error : skillid : %d\n",md->skillid); + return 0; + } + return 1; +} +/*========================================= + * ブランディッシュスピア 初期範囲決定 + *---------------------------------------- + */ +void skill_brandishspear_first(struct square *tc,int dir,int x,int y){ + + nullpo_retv(tc); + + if(dir == 0){ + tc->val1[0]=x-2; + tc->val1[1]=x-1; + tc->val1[2]=x; + tc->val1[3]=x+1; + tc->val1[4]=x+2; + tc->val2[0]= + tc->val2[1]= + tc->val2[2]= + tc->val2[3]= + tc->val2[4]=y-1; + } + else if(dir==2){ + tc->val1[0]= + tc->val1[1]= + tc->val1[2]= + tc->val1[3]= + tc->val1[4]=x+1; + tc->val2[0]=y+2; + tc->val2[1]=y+1; + tc->val2[2]=y; + tc->val2[3]=y-1; + tc->val2[4]=y-2; + } + else if(dir==4){ + tc->val1[0]=x-2; + tc->val1[1]=x-1; + tc->val1[2]=x; + tc->val1[3]=x+1; + tc->val1[4]=x+2; + tc->val2[0]= + tc->val2[1]= + tc->val2[2]= + tc->val2[3]= + tc->val2[4]=y+1; + } + else if(dir==6){ + tc->val1[0]= + tc->val1[1]= + tc->val1[2]= + tc->val1[3]= + tc->val1[4]=x-1; + tc->val2[0]=y+2; + tc->val2[1]=y+1; + tc->val2[2]=y; + tc->val2[3]=y-1; + tc->val2[4]=y-2; + } + else if(dir==1){ + tc->val1[0]=x-1; + tc->val1[1]=x; + tc->val1[2]=x+1; + tc->val1[3]=x+2; + tc->val1[4]=x+3; + tc->val2[0]=y-4; + tc->val2[1]=y-3; + tc->val2[2]=y-1; + tc->val2[3]=y; + tc->val2[4]=y+1; + } + else if(dir==3){ + tc->val1[0]=x+3; + tc->val1[1]=x+2; + tc->val1[2]=x+1; + tc->val1[3]=x; + tc->val1[4]=x-1; + tc->val2[0]=y-1; + tc->val2[1]=y; + tc->val2[2]=y+1; + tc->val2[3]=y+2; + tc->val2[4]=y+3; + } + else if(dir==5){ + tc->val1[0]=x+1; + tc->val1[1]=x; + tc->val1[2]=x-1; + tc->val1[3]=x-2; + tc->val1[4]=x-3; + tc->val2[0]=y+3; + tc->val2[1]=y+2; + tc->val2[2]=y+1; + tc->val2[3]=y; + tc->val2[4]=y-1; + } + else if(dir==7){ + tc->val1[0]=x-3; + tc->val1[1]=x-2; + tc->val1[2]=x-1; + tc->val1[3]=x; + tc->val1[4]=x+1; + tc->val2[1]=y; + tc->val2[0]=y+1; + tc->val2[2]=y-1; + tc->val2[3]=y-2; + tc->val2[4]=y-3; + } + +} + +/*========================================= + * ブランディッシュスピア 方向判定 範囲拡張 + *----------------------------------------- + */ +void skill_brandishspear_dir(struct square *tc,int dir,int are){ + + int c; + + nullpo_retv(tc); + + for(c=0;c<5;c++){ + if(dir==0){ + tc->val2[c]+=are; + }else if(dir==1){ + tc->val1[c]-=are; tc->val2[c]+=are; + }else if(dir==2){ + tc->val1[c]-=are; + }else if(dir==3){ + tc->val1[c]-=are; tc->val2[c]-=are; + }else if(dir==4){ + tc->val2[c]-=are; + }else if(dir==5){ + tc->val1[c]+=are; tc->val2[c]-=are; + }else if(dir==6){ + tc->val1[c]+=are; + }else if(dir==7){ + tc->val1[c]+=are; tc->val2[c]+=are; + } + } +} + +/*========================================== + * ディボーション 有効確認 + *------------------------------------------ + */ +void skill_devotion(struct map_session_data *md,int target) +{ + // 総確認 + int n; + + nullpo_retv(md); + + for(n=0;n<5;n++){ + if(md->dev.val1[n]){ + struct map_session_data *sd = map_id2sd(md->dev.val1[n]); + // 相手が見つからない // 相手をディボしてるのが自分じゃない // 距離が離れてる + if( sd == NULL || (sd->sc_data && (md->bl.id != sd->sc_data[SC_DEVOTION].val1)) || skill_devotion3(&md->bl,md->dev.val1[n])){ + skill_devotion_end(md,sd,n); + } + } + } +} +void skill_devotion2(struct block_list *bl,int crusader) +{ + // 被ディボーションが歩いた時の距離チェック + struct map_session_data *sd = map_id2sd(crusader); + + nullpo_retv(bl); + + if(sd) skill_devotion3(&sd->bl,bl->id); +} +int skill_devotion3(struct block_list *bl,int target) +{ + // クルセが歩いた時の距離チェック + struct map_session_data *md; + struct map_session_data *sd; + int n,r=0; + + nullpo_retr(1, bl); + + if( (md = (struct map_session_data *)bl) == NULL || (sd = map_id2sd(target)) == NULL ) + return 1; + else + r = distance(bl->x,bl->y,sd->bl.x,sd->bl.y); + + if(pc_checkskill(sd,CR_DEVOTION)+6 < r){ // 許容範囲を超えてた + for(n=0;n<5;n++) + if(md->dev.val1[n]==target) + md->dev.val2[n]=0; // 離れた時は、糸を切るだけ + clif_devotion(md,sd->bl.id); + return 1; + } + return 0; +} + +void skill_devotion_end(struct map_session_data *md,struct map_session_data *sd,int target) +{ + // クルセと被ディボキャラのリセット + nullpo_retv(md); + nullpo_retv(sd); + + md->dev.val1[target]=md->dev.val2[target]=0; + if(sd && sd->sc_data){ + // skill_status_change_end(sd->bl,SC_DEVOTION,-1); + sd->sc_data[SC_DEVOTION].val1=0; + sd->sc_data[SC_DEVOTION].val2=0; + clif_status_change(&sd->bl,SC_DEVOTION,0); + clif_devotion(md,sd->bl.id); + } +} +/*========================================== + * オートスペル + *------------------------------------------ + */ +int skill_autospell(struct map_session_data *sd,int skillid) +{ + int skilllv; + int maxlv=1,lv; + + nullpo_retr(0, sd); + + skilllv = pc_checkskill(sd,SA_AUTOSPELL); + + if(skillid==MG_NAPALMBEAT) maxlv=3; + else if(skillid==MG_COLDBOLT || skillid==MG_FIREBOLT || skillid==MG_LIGHTNINGBOLT){ + if(skilllv==2) maxlv=1; + else if(skilllv==3) maxlv=2; + else if(skilllv>=4) maxlv=3; + } + else if(skillid==MG_SOULSTRIKE){ + if(skilllv==5) maxlv=1; + else if(skilllv==6) maxlv=2; + else if(skilllv>=7) maxlv=3; + } + else if(skillid==MG_FIREBALL){ + if(skilllv==8) maxlv=1; + else if(skilllv>=9) maxlv=2; + } + else if(skillid==MG_FROSTDIVER) maxlv=1; + else return 0; + + if(maxlv > (lv=pc_checkskill(sd,skillid))) + maxlv = lv; + + skill_status_change_start(&sd->bl,SC_AUTOSPELL,skilllv,skillid,maxlv,0, // val1:スキルID val2:使用最大Lv + skill_get_time(SA_AUTOSPELL,skilllv),0);// にしてみたけどbscriptが書き易い・・・? + return 0; +} + +/*========================================== + * ギャングスターパラダイス判定処理(foreachinarea) + *------------------------------------------ + */ + +static int skill_gangster_count(struct block_list *bl,va_list ap) +{ + int *c; + struct map_session_data *sd; + + nullpo_retr(0, bl); + nullpo_retr(0, ap); + + sd=(struct map_session_data*)bl; + c=va_arg(ap,int *); + + if(sd && c && pc_issit(sd) && pc_checkskill(sd,RG_GANGSTER) > 0) + (*c)++; + return 0; +} + +static int skill_gangster_in(struct block_list *bl,va_list ap) +{ + struct map_session_data *sd; + + nullpo_retr(0, bl); + nullpo_retr(0, ap); + + sd=(struct map_session_data*)bl; + if(sd && pc_issit(sd) && pc_checkskill(sd,RG_GANGSTER) > 0) + sd->state.gangsterparadise=1; + return 0; +} + +static int skill_gangster_out(struct block_list *bl,va_list ap) +{ + struct map_session_data *sd; + + nullpo_retr(0, bl); + nullpo_retr(0, ap); + + sd=(struct map_session_data*)bl; + if(sd && sd->state.gangsterparadise) + sd->state.gangsterparadise=0; + return 0; +} + +int skill_gangsterparadise(struct map_session_data *sd ,int type) +{ + int range=1; + int c=0; + + nullpo_retr(0, sd); + + if(pc_checkskill(sd,RG_GANGSTER) <= 0) + return 0; + + if(type==1) {/* 座った時の処理 */ + map_foreachinarea(skill_gangster_count,sd->bl.m, + sd->bl.x-range,sd->bl.y-range, + sd->bl.x+range,sd->bl.y+range,BL_PC,&c); + if(c > 0) {/*ギャングスター成功したら自分にもギャングスター属性付与*/ + map_foreachinarea(skill_gangster_in,sd->bl.m, + sd->bl.x-range,sd->bl.y-range, + sd->bl.x+range,sd->bl.y+range,BL_PC); + sd->state.gangsterparadise = 1; + } + return 0; + } + else if(type==0) {/* 立ち上がったときの処理 */ + map_foreachinarea(skill_gangster_count,sd->bl.m, + sd->bl.x-range,sd->bl.y-range, + sd->bl.x+range,sd->bl.y+range,BL_PC,&c); + if(c < 1) + map_foreachinarea(skill_gangster_out,sd->bl.m, + sd->bl.x-range,sd->bl.y-range, + sd->bl.x+range,sd->bl.y+range,BL_PC); + sd->state.gangsterparadise = 0; + return 0; + } + return 0; +} +/*========================================== + * 寒いジョーク・スクリーム判定処理(foreachinarea) + *------------------------------------------ + */ +int skill_frostjoke_scream(struct block_list *bl,va_list ap) +{ + struct block_list *src; + int skillnum,skilllv; + unsigned int tick; + + nullpo_retr(0, bl); + nullpo_retr(0, ap); + nullpo_retr(0, src=va_arg(ap,struct block_list*)); + + skillnum=va_arg(ap,int); + skilllv=va_arg(ap,int); + tick=va_arg(ap,unsigned int); + + if(src == bl)//自分には効かない + return 0; + + if(battle_check_target(src,bl,BCT_ENEMY) > 0) + skill_additional_effect(src,bl,skillnum,skilllv,BF_MISC,tick); + else if(battle_check_target(src,bl,BCT_PARTY) > 0) { + if(rand()%100 < 10)//PTメンバにも低確率でかかる(とりあえず10%) + skill_additional_effect(src,bl,skillnum,skilllv,BF_MISC,tick); + } + + return 0; +} + +/*========================================== + *アブラカダブラの使用スキル決定(決定スキルがダメなら0を返す) + *------------------------------------------ + */ +int skill_abra_dataset(int skilllv) +{ + int skill = rand()%331; + //dbに基づくレベル・確率判定 + if(skill_abra_db[skill].req_lv > skilllv || rand()%10000 >= skill_abra_db[skill].per) return 0; + //NPCスキルはダメ + if(skill >= NPC_PIERCINGATT && skill <= NPC_SUMMONMONSTER) return 0; + //演奏スキルはダメ + if(skill_is_danceskill(skill)) return 0; + + return skill; +} + +/*========================================== + * + *------------------------------------------ + */ +int skill_attack_area(struct block_list *bl,va_list ap) +{ + struct block_list *src,*dsrc; + int atk_type,skillid,skilllv,flag,type; + unsigned int tick; + + nullpo_retr(0, bl); + nullpo_retr(0, ap); + + atk_type = va_arg(ap,int); + if((src=va_arg(ap,struct block_list*)) == NULL) + return 0; + if((dsrc=va_arg(ap,struct block_list*)) == NULL) + return 0; + skillid=va_arg(ap,int); + skilllv=va_arg(ap,int); + tick=va_arg(ap,unsigned int); + flag=va_arg(ap,int); + type=va_arg(ap,int); + + if(battle_check_target(dsrc,bl,type) > 0) + skill_attack(atk_type,src,dsrc,bl,skillid,skilllv,tick,flag); + + return 0; +} +/*========================================== + * + *------------------------------------------ + */ +int skill_clear_element_field(struct block_list *bl) +{ + struct mob_data *md=NULL; + struct map_session_data *sd=NULL; + int i,skillid; + + nullpo_retr(0, bl); + + if(bl->type==BL_MOB) + md=(struct mob_data *)bl; + if(bl->type==BL_PC) + sd=(struct map_session_data *)bl; + + for(i=0;i<MAX_MOBSKILLUNITGROUP;i++){ + if(sd){ + skillid=sd->skillunit[i].skill_id; + if(skillid==SA_DELUGE||skillid==SA_VOLCANO||skillid==SA_VIOLENTGALE||skillid==SA_LANDPROTECTOR) + skill_delunitgroup(&sd->skillunit[i]); + }else if(md){ + skillid=md->skillunit[i].skill_id; + if(skillid==SA_DELUGE||skillid==SA_VOLCANO||skillid==SA_VIOLENTGALE||skillid==SA_LANDPROTECTOR) + skill_delunitgroup(&md->skillunit[i]); + } + } + return 0; +} +/*========================================== + * ランドプロテクターチェック(foreachinarea) + *------------------------------------------ + */ +int skill_landprotector(struct block_list *bl, va_list ap ) +{ + int skillid; + int *alive; + struct skill_unit *unit; + + nullpo_retr(0, bl); + nullpo_retr(0, ap); + + skillid=va_arg(ap,int); + alive=va_arg(ap,int *); + if((unit=(struct skill_unit *)bl) == NULL) + return 0; + + if(skillid==SA_LANDPROTECTOR){ + skill_delunit(unit); + }else{ + if(alive && unit->group->skill_id==SA_LANDPROTECTOR) + (*alive)=0; + } + return 0; +} +/*========================================== + * イドゥンの林檎の回復処理(foreachinarea) + *------------------------------------------ + */ +int skill_idun_heal(struct block_list *bl, va_list ap ) +{ + struct skill_unit *unit; + struct skill_unit_group *sg; + int heal; + + nullpo_retr(0, bl); + nullpo_retr(0, ap); + nullpo_retr(0, unit = va_arg(ap,struct skill_unit *)); + nullpo_retr(0, sg = unit->group); + + heal=30+sg->skill_lv*5+((sg->val1)>>16)*5+((sg->val1)&0xfff)/2; + + if(bl->type == BL_SKILL || bl->id == sg->src_id) + return 0; + + if(bl->type == BL_PC || bl->type == BL_MOB){ + clif_skill_nodamage(&unit->bl,bl,AL_HEAL,heal,1); + battle_heal(NULL,bl,heal,0,0); + } + return 0; +} + +/*========================================== + * 指定範囲内でsrcに対して有効なターゲットのblの数を数える(foreachinarea) + *------------------------------------------ + */ +int skill_count_target(struct block_list *bl, va_list ap ){ + struct block_list *src; + int *c; + + nullpo_retr(0, bl); + nullpo_retr(0, ap); + + if((src = va_arg(ap,struct block_list *)) == NULL) + return 0; + if((c = va_arg(ap,int *)) == NULL) + return 0; + if(battle_check_target(src,bl,BCT_ENEMY) > 0) + (*c)++; + return 0; +} +/*========================================== + * トラップ範囲処理(foreachinarea) + *------------------------------------------ + */ +int skill_trap_splash(struct block_list *bl, va_list ap ) +{ + struct block_list *src; + int tick; + int splash_count; + struct skill_unit *unit; + struct skill_unit_group *sg; + struct block_list *ss; + int i; + + nullpo_retr(0, bl); + nullpo_retr(0, ap); + nullpo_retr(0, src = va_arg(ap,struct block_list *)); + nullpo_retr(0, unit = (struct skill_unit *)src); + nullpo_retr(0, sg = unit->group); + nullpo_retr(0, ss = map_id2bl(sg->src_id)); + + tick = va_arg(ap,int); + splash_count = va_arg(ap,int); + + if(battle_check_target(src,bl,BCT_ENEMY) > 0){ + switch(sg->unit_id){ + case 0x95: /* サンドマン */ + case 0x96: /* フラッシャー */ + case 0x94: /* ショックウェーブトラップ */ + skill_additional_effect(ss,bl,sg->skill_id,sg->skill_lv,BF_MISC,tick); + break; + case 0x8f: /* ブラストマイン */ + case 0x98: /* クレイモアートラップ */ + for(i=0;i<splash_count;i++){ + skill_attack(BF_MISC,ss,src,bl,sg->skill_id,sg->skill_lv,tick,(sg->val2)?0x0500:0); + } + case 0x97: /* フリージングトラップ */ + skill_attack(BF_WEAPON, ss,src,bl,sg->skill_id,sg->skill_lv,tick,(sg->val2)?0x0500:0); + break; + default: + break; + } + } + + return 0; +} +/*---------------------------------------------------------------------------- + * ステータス異常 + *---------------------------------------------------------------------------- + */ + +/*========================================== + * ステータス異常タイマー範囲処理 + *------------------------------------------ + */ +int skill_status_change_timer_sub(struct block_list *bl, va_list ap ) +{ + struct block_list *src; + int type; + unsigned int tick; + + nullpo_retr(0, bl); + nullpo_retr(0, ap); + nullpo_retr(0, src=va_arg(ap,struct block_list*)); + type=va_arg(ap,int); + tick=va_arg(ap,unsigned int); + + if(bl->type!=BL_PC && bl->type!=BL_MOB) + return 0; + + switch( type ){ + case SC_SIGHT: /* サイト */ + case SC_CONCENTRATE: + if( (*battle_get_option(bl))&6 ){ + skill_status_change_end( bl, SC_HIDING, -1); + skill_status_change_end( bl, SC_CLOAKING, -1); + } + break; + case SC_RUWACH: /* ルアフ */ + if( (*battle_get_option(bl))&6 ){ + skill_status_change_end( bl, SC_HIDING, -1); + skill_status_change_end( bl, SC_CLOAKING, -1); + if(battle_check_target( src,bl, BCT_ENEMY ) > 0) { + struct status_change *sc_data = battle_get_sc_data(bl); + skill_attack(BF_MAGIC,src,src,bl,AL_RUWACH,sc_data[type].val1,tick,0); + } + } + break; + } + return 0; +} + +/*========================================== + * ステータス異常終了 + *------------------------------------------ + */ +int skill_status_change_end(struct block_list* bl, int type, int tid) +{ + struct status_change* sc_data; + int opt_flag=0, calc_flag = 0; + short *sc_count, *option, *opt1, *opt2, *opt3; + + nullpo_retr(0, bl); + if(bl->type!=BL_PC && bl->type!=BL_MOB) { + if(battle_config.error_log) + printf("skill_status_change_end: neither MOB nor PC !\n"); + return 0; + } + nullpo_retr(0, sc_data = battle_get_sc_data(bl)); + nullpo_retr(0, sc_count = battle_get_sc_count(bl)); + nullpo_retr(0, option = battle_get_option(bl)); + nullpo_retr(0, opt1 = battle_get_opt1(bl)); + nullpo_retr(0, opt2 = battle_get_opt2(bl)); + nullpo_retr(0, opt3 = battle_get_opt3(bl)); + + if ((*sc_count) > 0 && sc_data[type].timer != -1 && (sc_data[type].timer == tid || tid == -1)) { + + if (tid == -1) // タイマから呼ばれていないならタイマ削除をする + delete_timer(sc_data[type].timer,skill_status_change_timer); + + /* 該当の異常を正常に戻す */ + sc_data[type].timer=-1; + (*sc_count)--; + + switch(type){ /* 異常の種類ごとの処理 */ + case SC_PROVOKE: /* プロボック */ + case SC_CONCENTRATE: /* 集中力向上 */ + case SC_BLESSING: /* ブレッシング */ + case SC_ANGELUS: /* アンゼルス */ + case SC_INCREASEAGI: /* 速度上昇 */ + case SC_DECREASEAGI: /* 速度減少 */ + case SC_SIGNUMCRUCIS: /* シグナムクルシス */ + case SC_HIDING: + case SC_TWOHANDQUICKEN: /* 2HQ */ + case SC_ADRENALINE: /* アドレナリンラッシュ */ + case SC_ENCPOISON: /* エンチャントポイズン */ + case SC_IMPOSITIO: /* インポシティオマヌス */ + case SC_GLORIA: /* グロリア */ + case SC_LOUD: /* ラウドボイス */ + case SC_QUAGMIRE: /* クァグマイア */ + case SC_PROVIDENCE: /* プロヴィデンス */ + case SC_SPEARSQUICKEN: /* スピアクイッケン */ + case SC_VOLCANO: + case SC_DELUGE: + case SC_VIOLENTGALE: + case SC_ETERNALCHAOS: /* エターナルカオス */ + case SC_DRUMBATTLE: /* 戦太鼓の響き */ + case SC_NIBELUNGEN: /* ニーベルングの指輪 */ + case SC_SIEGFRIED: /* 不死身のジークフリード */ + case SC_WHISTLE: /* 口笛 */ + case SC_ASSNCROS: /* 夕陽のアサシンクロス */ + case SC_HUMMING: /* ハミング */ + case SC_DONTFORGETME: /* 私を忘れないで */ + case SC_FORTUNE: /* 幸運のキス */ + case SC_SERVICE4U: /* サービスフォーユー */ + case SC_EXPLOSIONSPIRITS: // 爆裂波動 + case SC_STEELBODY: // 金剛 + case SC_DEFENDER: + case SC_SPEEDPOTION0: /* 増速ポーション */ + case SC_SPEEDPOTION1: + case SC_SPEEDPOTION2: + case SC_APPLEIDUN: /* イドゥンの林檎 */ + case SC_RIDING: + case SC_BLADESTOP_WAIT: + case SC_AURABLADE: /* オーラブレード */ + case SC_PARRYING: /* パリイング */ + case SC_CONCENTRATION: /* コンセントレーション */ + case SC_TENSIONRELAX: /* テンションリラックス */ + case SC_ASSUMPTIO: /* アシャンプティオ */ + case SC_WINDWALK: /* ウインドウォーク */ + case SC_TRUESIGHT: /* トゥルーサイト */ + case SC_SPIDERWEB: /* スパイダーウェッブ */ + case SC_MAGICPOWER: /* 魔法力増幅 */ + case SC_CHASEWALK: + case SC_ATKPOT: /* attack potion [Valaris] */ + case SC_MATKPOT: /* magic attack potion [Valaris] */ + case SC_WEDDING: //結婚用(結婚衣裳になって歩くのが遅いとか) + case SC_MELTDOWN: /* メルトダウン */ + calc_flag = 1; + break; + case SC_BERSERK: /* バーサーク */ + calc_flag = 1; + clif_status_change(bl,SC_INCREASEAGI,0); /* アイコン消去 */ + break; + case SC_DEVOTION: /* ディボーション */ + { + struct map_session_data *md = map_id2sd(sc_data[type].val1); + sc_data[type].val1=sc_data[type].val2=0; + skill_devotion(md,bl->id); + calc_flag = 1; + } + break; + case SC_BLADESTOP: + { + struct status_change *t_sc_data = battle_get_sc_data((struct block_list *)sc_data[type].val4); + //片方が切れたので相手の白刃状態が切れてないのなら解除 + if(t_sc_data && t_sc_data[SC_BLADESTOP].timer!=-1) + skill_status_change_end((struct block_list *)sc_data[type].val4,SC_BLADESTOP,-1); + + if(sc_data[type].val2==2) + clif_bladestop((struct block_list *)sc_data[type].val3,(struct block_list *)sc_data[type].val4,0); + } + break; + case SC_DANCING: + { + struct map_session_data *dsd; + struct status_change *d_sc_data; + if(sc_data[type].val4 && (dsd=map_id2sd(sc_data[type].val4))){ + d_sc_data = dsd->sc_data; + //合奏で相手がいる場合相手のval4を0にする + if(d_sc_data && d_sc_data[type].timer!=-1) + d_sc_data[type].val4=0; + } + } + calc_flag = 1; + break; + case SC_GRAFFITI: + { + struct skill_unit_group *sg=(struct skill_unit_group *)sc_data[type].val4; //val4がグラフィティのgroup_id + if(sg) + skill_delunitgroup(sg); + } + break; + case SC_NOCHAT: //チャット禁止状態 + { + struct map_session_data *sd=NULL; + if(bl->type == BL_PC && (sd=(struct map_session_data *)bl)){ + sd->status.manner = 0; + clif_updatestatus(sd,SP_MANNER); + } + } + break; + case SC_SPLASHER: /* ベナムスプラッシャー */ + { + struct block_list *src=map_id2bl(sc_data[type].val3); + if(src && tid!=-1){ + //自分にダメージ&周囲3*3にダメージ + skill_castend_damage_id(src, bl,sc_data[type].val2,sc_data[type].val1,gettick(),0 ); + } + } + break; + case SC_SELFDESTRUCTION: /* 自爆 */ + { + //自分のダメージは0にして + struct mob_data *md=NULL; + if(bl->type == BL_MOB && (md=(struct mob_data*)bl)) + skill_castend_damage_id(bl, bl,sc_data[type].val2,sc_data[type].val1,gettick(),0 ); + } + break; + /* option1 */ + case SC_FREEZE: + sc_data[type].val3 = 0; + break; + + /* option2 */ + case SC_POISON: /* 毒 */ + case SC_BLIND: /* 暗黒 */ + case SC_CURSE: + calc_flag = 1; + break; + } + + if(bl->type==BL_PC && type<SC_SENDMAX) + clif_status_change(bl,type,0); /* アイコン消去 */ + + switch(type){ /* 正常に戻るときなにか処理が必要 */ + case SC_STONE: + case SC_FREEZE: + case SC_STAN: + case SC_SLEEP: + *opt1 = 0; + opt_flag = 1; + break; + + case SC_POISON: + case SC_CURSE: + case SC_SILENCE: + case SC_BLIND: + *opt2 &= ~(1<<(type-SC_POISON)); + opt_flag = 1; + break; + + case SC_SIGNUMCRUCIS: + *opt2 &= ~0x40; + opt_flag = 1; + break; + + case SC_HIDING: + case SC_CLOAKING: + *option &= ~((type == SC_HIDING) ? 2 : 4); + opt_flag = 1 ; + break; + + case SC_CHASEWALK: + *option &= ~16388; + opt_flag = 1 ; + break; + + case SC_SIGHT: + *option &= ~1; + opt_flag = 1; + break; + case SC_WEDDING: //結婚用(結婚衣裳になって歩くのが遅いとか) + *option &= ~4096; + opt_flag = 1; + break; + case SC_RUWACH: + *option &= ~8192; + opt_flag = 1; + break; + + //opt3 + case SC_TWOHANDQUICKEN: /* 2HQ */ + case SC_SPEARSQUICKEN: /* スピアクイッケン */ + case SC_CONCENTRATION: /* コンセントレーション */ + *opt3 &= ~1; + break; + case SC_OVERTHRUST: /* オーバースラスト */ + *opt3 &= ~2; + break; + case SC_ENERGYCOAT: /* エナジーコート */ + *opt3 &= ~4; + break; + case SC_EXPLOSIONSPIRITS: // 爆裂波動 + *opt3 &= ~8; + break; + case SC_STEELBODY: // 金剛 + *opt3 &= ~16; + break; + case SC_BLADESTOP: /* 白刃取り */ + *opt3 &= ~32; + break; + case SC_BERSERK: /* バーサーク */ + *opt3 &= ~128; + break; + case SC_MARIONETTE: /* マリオネットコントロール */ + *opt3 &= ~1024; + break; + case SC_ASSUMPTIO: /* アスムプティオ */ + *opt3 &= ~2048; + break; + } + + if (night_flag == 1 && (*opt2 & STATE_BLIND) == 0 && bl->type == BL_PC) { // by [Yor] + *opt2 |= STATE_BLIND; + opt_flag = 1; + } + + if(opt_flag) /* optionの変更を伝える */ + clif_changeoption(bl); + + if (bl->type == BL_PC && calc_flag) + pc_calcstatus((struct map_session_data *)bl,0); /* ステータス再計算 */ + } + + return 0; +} +/*========================================== + * ステータス異常終了タイマー + *------------------------------------------ + */ +int skill_status_change_timer(int tid, unsigned int tick, int id, int data) +{ + int type=data; + struct block_list *bl; + struct map_session_data *sd=NULL; + struct status_change *sc_data; + //short *sc_count; //使ってない? + + if( (bl=map_id2bl(id)) == NULL ) + return 0; //該当IDがすでに消滅しているというのはいかにもありそうなのでスルーしてみる + nullpo_retr(0, sc_data=battle_get_sc_data(bl)); + + if(bl->type==BL_PC) + sd=(struct map_session_data *)bl; + + //sc_count=battle_get_sc_count(bl); //使ってない? + + if(sc_data[type].timer != tid) { + if(battle_config.error_log) + printf("skill_status_change_timer %d != %d\n",tid,sc_data[type].timer); + } + + switch(type){ /* 特殊な処理になる場合 */ + case SC_MAXIMIZEPOWER: /* マキシマイズパワー */ + case SC_CLOAKING: /* クローキング */ + case SC_CHASEWALK: + if(sd){ + if( sd->status.sp > 0 ){ /* SP切れるまで持続 */ + sd->status.sp--; + clif_updatestatus(sd,SP_SP); + sc_data[type].timer=add_timer( /* タイマー再設定 */ + sc_data[type].val2+tick, skill_status_change_timer, + bl->id, data); + return 0; + } + } + break; + + case SC_HIDING: /* ハイディング */ + if(sd){ /* SPがあって、時間制限の間は持続 */ + if( sd->status.sp > 0 && (--sc_data[type].val2)>0 ){ + if(sc_data[type].val2 % (sc_data[type].val1+3) ==0 ){ + sd->status.sp--; + clif_updatestatus(sd,SP_SP); + } + sc_data[type].timer=add_timer( /* タイマー再設定 */ + 1000+tick, skill_status_change_timer, + bl->id, data); + return 0; + } + } + break; + + case SC_SIGHT: /* サイト */ + { + const int range=7; + map_foreachinarea( skill_status_change_timer_sub, + bl->m, bl->x-range, bl->y-range, bl->x+range,bl->y+range,0, + bl,type,tick); + + if( (--sc_data[type].val2)>0 ){ + sc_data[type].timer=add_timer( /* タイマー再設定 */ + 250+tick, skill_status_change_timer, + bl->id, data); + return 0; + } + } + break; + case SC_RUWACH: /* ルアフ */ + { + const int range=5; + map_foreachinarea( skill_status_change_timer_sub, + bl->m, bl->x-range, bl->y-range, bl->x+range,bl->y+range,0, + bl,type,tick); + + if( (--sc_data[type].val2)>0 ){ + sc_data[type].timer=add_timer( /* タイマー再設定 */ + 250+tick, skill_status_change_timer, + bl->id, data); + return 0; + } + } + break; + + case SC_SIGNUMCRUCIS: /* シグナムクルシス */ + { + int race = battle_get_race(bl); + if(race == 6 || battle_check_undead(race,battle_get_elem_type(bl))) { + sc_data[type].timer=add_timer(1000*600+tick,skill_status_change_timer, bl->id, data ); + return 0; + } + } + break; + + case SC_PROVOKE: /* プロボック/オートバーサーク */ + if(sc_data[type].val2!=0){ /* オートバーサーク(1秒ごとにHPチェック) */ + if(sd && sd->status.hp>sd->status.max_hp>>2) /* 停止 */ + break; + sc_data[type].timer=add_timer( 1000+tick,skill_status_change_timer, bl->id, data ); + return 0; + } + break; + + case SC_WATERBALL: /* ウォーターボール */ + { + struct block_list *target=map_id2bl(sc_data[type].val2); + if(target==NULL || target->prev==NULL) + break; + skill_attack(BF_MAGIC,bl,bl,target,WZ_WATERBALL,sc_data[type].val1,tick,0); + if((--sc_data[type].val3)>0) { + sc_data[type].timer=add_timer( 150+tick,skill_status_change_timer, bl->id, data ); + return 0; + } + } + break; + + case SC_ENDURE: /* インデュア */ + if(sd && sd->special_state.infinite_endure) { + sc_data[type].timer=add_timer( 1000*600+tick,skill_status_change_timer, bl->id, data ); + sc_data[type].val2=1; + return 0; + } + break; + + case SC_DISSONANCE: /* 不協和音 */ + if( (--sc_data[type].val2)>0){ + struct skill_unit *unit= + (struct skill_unit *)sc_data[type].val4; + struct block_list *src; + + if(!unit || !unit->group) + break; + src=map_id2bl(unit->group->src_id); + if(!src) + break; + skill_attack(BF_MISC,src,&unit->bl,bl,unit->group->skill_id,sc_data[type].val1,tick,0); + sc_data[type].timer=add_timer(skill_get_time2(unit->group->skill_id,unit->group->skill_lv)+tick, + skill_status_change_timer, bl->id, data ); + return 0; + } + break; + + case SC_LULLABY: /* 子守唄 */ + if( (--sc_data[type].val2)>0){ + struct skill_unit *unit= + (struct skill_unit *)sc_data[type].val4; + if(!unit || !unit->group || unit->group->src_id==bl->id) + break; + skill_additional_effect(bl,bl,unit->group->skill_id,sc_data[type].val1,BF_LONG|BF_SKILL|BF_MISC,tick); + sc_data[type].timer=add_timer(skill_get_time(unit->group->skill_id,unit->group->skill_lv)/10+tick, + skill_status_change_timer, bl->id, data ); + return 0; + } + break; + + case SC_STONE: + if(sc_data[type].val2 != 0) { + short *opt1 = battle_get_opt1(bl); + sc_data[type].val2 = 0; + sc_data[type].val4 = 0; + battle_stopwalking(bl,1); + if(opt1) { + *opt1 = 1; + clif_changeoption(bl); + } + sc_data[type].timer=add_timer(1000+tick,skill_status_change_timer, bl->id, data ); + return 0; + } + else if( (--sc_data[type].val3) > 0) { + int hp = battle_get_max_hp(bl); + if((++sc_data[type].val4)%5 == 0 && battle_get_hp(bl) > hp>>2) { + hp = hp/100; + if(hp < 1) hp = 1; + if(bl->type == BL_PC) + pc_heal((struct map_session_data *)bl,-hp,0); + else if(bl->type == BL_MOB){ + struct mob_data *md; + if((md=((struct mob_data *)bl)) == NULL) + break; + md->hp -= hp; + } + } + sc_data[type].timer=add_timer(1000+tick,skill_status_change_timer, bl->id, data ); + return 0; + } + break; + case SC_POISON: + if(sc_data[SC_SLOWPOISON].timer == -1) { + if( (--sc_data[type].val3) > 0) { + int hp = battle_get_max_hp(bl); + if(battle_get_hp(bl) > hp>>2) { + if(bl->type == BL_PC) { + hp = 3 + hp*3/200; + pc_heal((struct map_session_data *)bl,-hp,0); + } + else if(bl->type == BL_MOB) { + struct mob_data *md; + if((md=((struct mob_data *)bl)) == NULL) + break; + hp = 3 + hp/200; + md->hp -= hp; + } + } + sc_data[type].timer=add_timer(1000+tick,skill_status_change_timer, bl->id, data ); + } + } + else + sc_data[type].timer=add_timer(1000+tick,skill_status_change_timer, bl->id, data ); + break; + case SC_TENSIONRELAX: /* テンションリラックス */ + if(sd){ /* SPがあって、HPが満タンでなければ継続 */ + if( sd->status.sp > 12 && sd->status.max_hp > sd->status.hp ){ + if(sc_data[type].val2 % (sc_data[type].val1+3) ==0 ){ + sd->status.sp -= 12; + clif_updatestatus(sd,SP_SP); + } + sc_data[type].timer=add_timer( /* タイマー再設定 */ + 10000+tick, skill_status_change_timer, + bl->id, data); + return 0; + } + if(sd->status.max_hp <= sd->status.hp) + skill_status_change_end(&sd->bl,SC_TENSIONRELAX,-1); + } + break; + + /* 時間切れ無し?? */ + case SC_AETERNA: + case SC_TRICKDEAD: + case SC_RIDING: + case SC_FALCON: + case SC_WEIGHT50: + case SC_WEIGHT90: + case SC_MAGICPOWER: /* 魔法力増幅 */ + case SC_REJECTSWORD: /* リジェクトソード */ + case SC_MEMORIZE: /* メモライズ */ + case SC_BROKNWEAPON: + case SC_BROKNARMOR: + if(sc_data[type].timer==tid) + sc_data[type].timer=add_timer( 1000*600+tick,skill_status_change_timer, bl->id, data ); + return 0; + + case SC_DANCING: //ダンススキルの時間SP消費 + { + int s=0; + if(sd){ + if(sd->status.sp > 0 && (--sc_data[type].val3)>0){ + switch(sc_data[type].val1){ + case BD_RICHMANKIM: /* ニヨルドの宴 3秒にSP1 */ + case BD_DRUMBATTLEFIELD: /* 戦太鼓の響き 3秒にSP1 */ + case BD_RINGNIBELUNGEN: /* ニーベルングの指輪 3秒にSP1 */ + case BD_SIEGFRIED: /* 不死身のジークフリード 3秒にSP1 */ + case BA_DISSONANCE: /* 不協和音 3秒でSP1 */ + case BA_ASSASSINCROSS: /* 夕陽のアサシンクロス 3秒でSP1 */ + case DC_UGLYDANCE: /* 自分勝手なダンス 3秒でSP1 */ + s=3; + break; + case BD_LULLABY: /* 子守歌 4秒にSP1 */ + case BD_ETERNALCHAOS: /* 永遠の混沌 4秒にSP1 */ + case BD_ROKISWEIL: /* ロキの叫び 4秒にSP1 */ + case DC_FORTUNEKISS: /* 幸運のキス 4秒でSP1 */ + s=4; + break; + case BD_INTOABYSS: /* 深淵の中に 5秒にSP1 */ + case BA_WHISTLE: /* 口笛 5秒でSP1 */ + case DC_HUMMING: /* ハミング 5秒でSP1 */ + case BA_POEMBRAGI: /* ブラギの詩 5秒でSP1 */ + case DC_SERVICEFORYOU: /* サービスフォーユー 5秒でSP1 */ + s=5; + break; + case BA_APPLEIDUN: /* イドゥンの林檎 6秒でSP1 */ + s=6; + break; + case DC_DONTFORGETME: /* 私を忘れないで… 10秒でSP1 */ + case CG_MOONLIT: /* 月明りの泉に落ちる花びら 10秒でSP1? */ + s=10; + break; + } + if(s && ((sc_data[type].val3 % s) == 0)){ + sd->status.sp--; + clif_updatestatus(sd,SP_SP); + } + sc_data[type].timer=add_timer( /* タイマー再設定 */ + 1000+tick, skill_status_change_timer, + bl->id, data); + return 0; + } + } + } + break; + case SC_BERSERK: /* バーサーク */ + if(sd){ /* HPが100以上なら継続 */ + if( (sd->status.hp - sd->status.hp/100) > 100 ){ + sd->status.hp -= sd->status.hp/100; + clif_updatestatus(sd,SP_HP); + sc_data[type].timer=add_timer( /* タイマー再設定 */ + 15000+tick, skill_status_change_timer, + bl->id, data); + return 0; + } + } + break; + case SC_WEDDING: //結婚用(結婚衣裳になって歩くのが遅いとか) + if(sd){ + time_t timer; + if(time(&timer) < ((sc_data[type].val2) + 3600)){ //1時間たっていないので継続 + sc_data[type].timer=add_timer( /* タイマー再設定 */ + 10000+tick, skill_status_change_timer, + bl->id, data); + return 0; + } + } + break; + case SC_NOCHAT: //チャット禁止状態 + if(sd && battle_config.muting_players){ + time_t timer; + if((++sd->status.manner) && time(&timer) < ((sc_data[type].val2) + 60*(0-sd->status.manner))){ //開始からstatus.manner分経ってないので継続 + clif_updatestatus(sd,SP_MANNER); + sc_data[type].timer=add_timer( /* タイマー再設定(60秒) */ + 60000+tick, skill_status_change_timer, + bl->id, data); + return 0; + } + } + break; + case SC_SELFDESTRUCTION: /* 自爆 */ + if(--sc_data[type].val3>0){ + struct mob_data *md; + if(bl->type==BL_MOB && (md=(struct mob_data *)bl) && md->speed > 250){ + md->speed -= 250; + md->next_walktime=tick; + } + sc_data[type].timer=add_timer( /* タイマー再設定 */ + 1000+tick, skill_status_change_timer, + bl->id, data); + return 0; + } + break; + } + + return skill_status_change_end( bl,type,tid ); +} + +/*========================================== + * ステータス異常終了 + *------------------------------------------ + */ +int skill_encchant_eremental_end(struct block_list *bl,int type) +{ + struct status_change *sc_data; + + nullpo_retr(0, bl); + nullpo_retr(0, sc_data=battle_get_sc_data(bl)); + + if( type!=SC_ENCPOISON && sc_data[SC_ENCPOISON].timer!=-1 ) /* エンチャントポイズン解除 */ + skill_status_change_end(bl,SC_ENCPOISON,-1); + if( type!=SC_ASPERSIO && sc_data[SC_ASPERSIO].timer!=-1 ) /* アスペルシオ解除 */ + skill_status_change_end(bl,SC_ASPERSIO,-1); + if( type!=SC_FLAMELAUNCHER && sc_data[SC_FLAMELAUNCHER].timer!=-1 ) /* フレイムランチャ解除 */ + skill_status_change_end(bl,SC_FLAMELAUNCHER,-1); + if( type!=SC_FROSTWEAPON && sc_data[SC_FROSTWEAPON].timer!=-1 ) /* フロストウェポン解除 */ + skill_status_change_end(bl,SC_FROSTWEAPON,-1); + if( type!=SC_LIGHTNINGLOADER && sc_data[SC_LIGHTNINGLOADER].timer!=-1 ) /* ライトニングローダー解除 */ + skill_status_change_end(bl,SC_LIGHTNINGLOADER,-1); + if( type!=SC_SEISMICWEAPON && sc_data[SC_SEISMICWEAPON].timer!=-1 ) /* サイスミックウェポン解除 */ + skill_status_change_end(bl,SC_SEISMICWEAPON,-1); + + return 0; +} +/*========================================== + * ステータス異常開始 + *------------------------------------------ + */ +int skill_status_change_start(struct block_list *bl, int type, int val1, int val2, int val3, int val4, int tick, int flag) +{ + struct map_session_data *sd = NULL; + struct status_change* sc_data; + short *sc_count, *option, *opt1, *opt2, *opt3; + int opt_flag = 0, calc_flag = 0,updateflag = 0, race, mode, elem, undead_flag; + int scdef=0; + + nullpo_retr(0, bl); + if(bl->type == BL_SKILL) + return 0; + nullpo_retr(0, sc_data=battle_get_sc_data(bl)); + nullpo_retr(0, sc_count=battle_get_sc_count(bl)); + nullpo_retr(0, option=battle_get_option(bl)); + nullpo_retr(0, opt1=battle_get_opt1(bl)); + nullpo_retr(0, opt2=battle_get_opt2(bl)); + nullpo_retr(0, opt3=battle_get_opt3(bl)); + + + race=battle_get_race(bl); + mode=battle_get_mode(bl); + elem=battle_get_elem_type(bl); + undead_flag=battle_check_undead(race,elem); + + if(type == SC_AETERNA && (sc_data[SC_STONE].timer != -1 || sc_data[SC_FREEZE].timer != -1) ) + return 0; + + switch(type){ + case SC_STONE: + case SC_FREEZE: + scdef=3+battle_get_mdef(bl)+battle_get_luk(bl)/3; + break; + case SC_STAN: + case SC_SILENCE: + case SC_POISON: + scdef=3+battle_get_vit(bl)+battle_get_luk(bl)/3; + break; + case SC_SLEEP: + case SC_BLIND: + scdef=3+battle_get_int(bl)+battle_get_luk(bl)/3; + break; + case SC_CURSE: + scdef=3+battle_get_luk(bl); + break; + +// case SC_CONFUSION: + default: + scdef=0; + } + if(scdef>=100) + return 0; + if(bl->type==BL_PC){ + sd=(struct map_session_data *)bl; + if( sd && type == SC_ADRENALINE && !(skill_get_weapontype(BS_ADRENALINE)&(1<<sd->status.weapon))) + return 0; + + if(SC_STONE<=type && type<=SC_BLIND){ /* カードによる耐性 */ + if( sd && sd->reseff[type-SC_STONE] > 0 && rand()%10000<sd->reseff[type-SC_STONE]){ + if(battle_config.battle_log) + printf("PC %d skill_sc_start: cardによる異常耐性発動\n",sd->bl.id); + return 0; + } + } + } + else if(bl->type == BL_MOB) { + } + else { + if(battle_config.error_log) + printf("skill_status_change_start: neither MOB nor PC !\n"); + return 0; + } + + if(type==SC_FREEZE && undead_flag && !(flag&1)) + return 0; + + if((type == SC_ADRENALINE || type == SC_WEAPONPERFECTION || type == SC_OVERTHRUST) && + sc_data[type].timer != -1 && sc_data[type].val2 && !val2) + return 0; + + if(mode & 0x20 && (type==SC_STONE || type==SC_FREEZE || + type==SC_STAN || type==SC_SLEEP || type==SC_SILENCE || type==SC_QUAGMIRE || type == SC_DECREASEAGI || type == SC_SIGNUMCRUCIS || type == SC_PROVOKE || + (type == SC_BLESSING && (undead_flag || race == 6))) && !(flag&1)){ + /* ボスには効かない(ただしカードによる効果は適用される) */ + return 0; + } + if(type==SC_FREEZE || type==SC_STAN || type==SC_SLEEP) + battle_stopwalking(bl,1); + + if(sc_data[type].timer != -1){ /* すでに同じ異常になっている場合タイマ解除 */ + if(sc_data[type].val1 > val1 && type != SC_COMBO && type != SC_DANCING && type != SC_DEVOTION && + type != SC_SPEEDPOTION0 && type != SC_SPEEDPOTION1 && type != SC_SPEEDPOTION2 + && type != SC_ATKPOT && type != SC_MATKPOT) // added atk and matk potions [Valaris] + return 0; + if(type >=SC_STAN && type <= SC_BLIND) + return 0;/* 継ぎ足しができない状態異常である時は状態異常を行わない */ + if(type == SC_GRAFFITI){ //異常中にもう一度状態異常になった時に解除してから再度かかる + skill_status_change_end(bl,type,-1); + }else{ + (*sc_count)--; + delete_timer(sc_data[type].timer, skill_status_change_timer); + sc_data[type].timer = -1; + } + } + + switch(type){ /* 異常の種類ごとの処理 */ + case SC_PROVOKE: /* プロボック */ + calc_flag = 1; + if(tick <= 0) tick = 1000; /* (オートバーサーク) */ + break; + case SC_ENDURE: /* インデュア */ + if(tick <= 0) tick = 1000 * 60; + break; + case SC_CONCENTRATE: /* 集中力向上 */ + calc_flag = 1; + break; + case SC_BLESSING: /* ブレッシング */ + { + if(bl->type == BL_PC || (!undead_flag && race != 6)) { + if(sc_data[SC_CURSE].timer!=-1 ) + skill_status_change_end(bl,SC_CURSE,-1); + if(sc_data[SC_STONE].timer!=-1 && sc_data[SC_STONE].val2 == 0) + skill_status_change_end(bl,SC_STONE,-1); + } + calc_flag = 1; + } + break; + case SC_ANGELUS: /* アンゼルス */ + calc_flag = 1; + break; + case SC_INCREASEAGI: /* 速度上昇 */ + calc_flag = 1; + if(sc_data[SC_DECREASEAGI].timer!=-1 ) + skill_status_change_end(bl,SC_DECREASEAGI,-1); + if(sc_data[SC_WINDWALK].timer!=-1 ) /* ウインドウォーク */ + skill_status_change_end(bl,SC_WINDWALK,-1); + break; + case SC_DECREASEAGI: /* 速度減少 */ + calc_flag = 1; + if(sc_data[SC_INCREASEAGI].timer!=-1 ) + skill_status_change_end(bl,SC_INCREASEAGI,-1); + break; + case SC_SIGNUMCRUCIS: /* シグナムクルシス */ + calc_flag = 1; +// val2 = 14 + val1; + val2 = 10 + val1*2; + tick = 600*1000; + clif_emotion(bl,4); + break; + case SC_SLOWPOISON: + if(sc_data[SC_POISON].timer == -1 ) + return 0; + break; + case SC_TWOHANDQUICKEN: /* 2HQ */ + *opt3 |= 1; + calc_flag = 1; + break; + case SC_ADRENALINE: /* アドレナリンラッシュ */ + calc_flag = 1; + break; + case SC_WEAPONPERFECTION: /* ウェポンパーフェクション */ + if(battle_config.party_skill_penaly && !val2) tick /= 5; + break; + case SC_OVERTHRUST: /* オーバースラスト */ + *opt3 |= 2; + if(battle_config.party_skill_penaly && !val2) tick /= 10; + break; + case SC_MAXIMIZEPOWER: /* マキシマイズパワー(SPが1減る時間,val2にも) */ + if(bl->type == BL_PC) + val2 = tick; + else + tick = 5000*val1; + break; + case SC_ENCPOISON: /* エンチャントポイズン */ + calc_flag = 1; + val2=(((val1 - 1) / 2) + 3)*100; /* 毒付与確率 */ + skill_encchant_eremental_end(bl,SC_ENCPOISON); + break; + case SC_POISONREACT: /* ポイズンリアクト */ + break; + case SC_IMPOSITIO: /* インポシティオマヌス */ + calc_flag = 1; + break; + case SC_ASPERSIO: /* アスペルシオ */ + skill_encchant_eremental_end(bl,SC_ASPERSIO); + break; + case SC_SUFFRAGIUM: /* サフラギム */ + case SC_BENEDICTIO: /* 聖体 */ + case SC_MAGNIFICAT: /* マグニフィカート */ + case SC_AETERNA: /* エーテルナ */ + break; + case SC_ENERGYCOAT: /* エナジーコート */ + *opt3 |= 4; + break; + case SC_MAGICROD: + val2 = val1*20; + break; + case SC_KYRIE: /* キリエエレイソン */ + val2 = battle_get_max_hp(bl) * (val1 * 2 + 10) / 100;/* 耐久度 */ + val3 = (val1 / 2 + 5); /* 回数 */ +// -- moonsoul (added to undo assumptio status if target has it) + if(sc_data[SC_ASSUMPTIO].timer!=-1 ) + skill_status_change_end(bl,SC_ASSUMPTIO,-1); + break; + case SC_MINDBREAKER: + calc_flag = 1; + if(tick <= 0) tick = 1000; /* (オートバーサーク) */ + case SC_GLORIA: /* グロリア */ + calc_flag = 1; + break; + case SC_LOUD: /* ラウドボイス */ + calc_flag = 1; + break; + case SC_TRICKDEAD: /* 死んだふり */ + break; + case SC_QUAGMIRE: /* クァグマイア */ + calc_flag = 1; + if(sc_data[SC_CONCENTRATE].timer!=-1 ) /* 集中力向上解除 */ + skill_status_change_end(bl,SC_CONCENTRATE,-1); + if(sc_data[SC_INCREASEAGI].timer!=-1 ) /* 速度上昇解除 */ + skill_status_change_end(bl,SC_INCREASEAGI,-1); + if(sc_data[SC_TWOHANDQUICKEN].timer!=-1 ) + skill_status_change_end(bl,SC_TWOHANDQUICKEN,-1); + if(sc_data[SC_SPEARSQUICKEN].timer!=-1 ) + skill_status_change_end(bl,SC_SPEARSQUICKEN,-1); + if(sc_data[SC_ADRENALINE].timer!=-1 ) + skill_status_change_end(bl,SC_ADRENALINE,-1); + if(sc_data[SC_LOUD].timer!=-1 ) + skill_status_change_end(bl,SC_LOUD,-1); + if(sc_data[SC_TRUESIGHT].timer!=-1 ) /* トゥルーサイト */ + skill_status_change_end(bl,SC_TRUESIGHT,-1); + if(sc_data[SC_WINDWALK].timer!=-1 ) /* ウインドウォーク */ + skill_status_change_end(bl,SC_WINDWALK,-1); + if(sc_data[SC_CARTBOOST].timer!=-1 ) /* カートブースト */ + skill_status_change_end(bl,SC_CARTBOOST,-1); + break; + case SC_FLAMELAUNCHER: /* フレームランチャー */ + skill_encchant_eremental_end(bl,SC_FLAMELAUNCHER); + break; + case SC_FROSTWEAPON: /* フロストウェポン */ + skill_encchant_eremental_end(bl,SC_FROSTWEAPON); + break; + case SC_LIGHTNINGLOADER: /* ライトニングローダー */ + skill_encchant_eremental_end(bl,SC_LIGHTNINGLOADER); + break; + case SC_SEISMICWEAPON: /* サイズミックウェポン */ + skill_encchant_eremental_end(bl,SC_SEISMICWEAPON); + break; + case SC_DEVOTION: /* ディボーション */ + calc_flag = 1; + break; + case SC_PROVIDENCE: /* プロヴィデンス */ + calc_flag = 1; + val2=val1*5; + break; + case SC_REFLECTSHIELD: + val2=10+val1*3; + break; + case SC_STRIPWEAPON: + case SC_STRIPSHIELD: + case SC_STRIPARMOR: + case SC_STRIPHELM: + case SC_CP_WEAPON: + case SC_CP_SHIELD: + case SC_CP_ARMOR: + case SC_CP_HELM: + break; + + case SC_AUTOSPELL: /* オートスペル */ + val4 = 5 + val1*2; + break; + + case SC_VOLCANO: + calc_flag = 1; + val3 = val1*10; + val4 = val1>=5?20: (val1==4?19: (val1==3?17: ( val1==2?14:10 ) ) ); + break; + case SC_DELUGE: + calc_flag = 1; + val3 = val1>=5?15: (val1==4?14: (val1==3?12: ( val1==2?9:5 ) ) ); + val4 = val1>=5?20: (val1==4?19: (val1==3?17: ( val1==2?14:10 ) ) ); + break; + case SC_VIOLENTGALE: + calc_flag = 1; + val3 = val1*3; + val4 = val1>=5?20: (val1==4?19: (val1==3?17: ( val1==2?14:10 ) ) ); + break; + + case SC_SPEARSQUICKEN: /* スピアクイッケン */ + calc_flag = 1; + val2 = 20+val1; + *opt3 |= 1; + break; + case SC_COMBO: + break; + case SC_BLADESTOP_WAIT: /* 白刃取り(待ち) */ + break; + case SC_BLADESTOP: /* 白刃取り */ + if(val2==2) clif_bladestop((struct block_list *)val3,(struct block_list *)val4,1); + *opt3 |= 32; + break; + + case SC_LULLABY: /* 子守唄 */ + val2 = 11; + break; + case SC_RICHMANKIM: + break; + case SC_ETERNALCHAOS: /* エターナルカオス */ + calc_flag = 1; + break; + case SC_DRUMBATTLE: /* 戦太鼓の響き */ + calc_flag = 1; + val2 = (val1+1)*25; + val3 = (val1+1)*2; + break; + case SC_NIBELUNGEN: /* ニーベルングの指輪 */ + calc_flag = 1; + val2 = (val1+2)*50; + val3 = (val1+2)*25; + break; + case SC_ROKISWEIL: /* ロキの叫び */ + break; + case SC_INTOABYSS: /* 深淵の中に */ + break; + case SC_SIEGFRIED: /* 不死身のジークフリード */ + calc_flag = 1; + val2 = 40 + val1*5; + val3 = val1*10; + break; + case SC_DISSONANCE: /* 不協和音 */ + val2 = 10; + break; + case SC_WHISTLE: /* 口笛 */ + calc_flag = 1; + break; + case SC_ASSNCROS: /* 夕陽のアサシンクロス */ + calc_flag = 1; + break; + case SC_POEMBRAGI: /* ブラギの詩 */ + break; + case SC_APPLEIDUN: /* イドゥンの林檎 */ + calc_flag = 1; + break; + case SC_UGLYDANCE: /* 自分勝手なダンス */ + val2 = 10; + break; + case SC_HUMMING: /* ハミング */ + calc_flag = 1; + break; + case SC_DONTFORGETME: /* 私を忘れないで */ + calc_flag = 1; + if(sc_data[SC_INCREASEAGI].timer!=-1 ) /* 速度上昇解除 */ + skill_status_change_end(bl,SC_INCREASEAGI,-1); + if(sc_data[SC_TWOHANDQUICKEN].timer!=-1 ) + skill_status_change_end(bl,SC_TWOHANDQUICKEN,-1); + if(sc_data[SC_SPEARSQUICKEN].timer!=-1 ) + skill_status_change_end(bl,SC_SPEARSQUICKEN,-1); + if(sc_data[SC_ADRENALINE].timer!=-1 ) + skill_status_change_end(bl,SC_ADRENALINE,-1); + if(sc_data[SC_ASSNCROS].timer!=-1 ) + skill_status_change_end(bl,SC_ASSNCROS,-1); + if(sc_data[SC_TRUESIGHT].timer!=-1 ) /* トゥルーサイト */ + skill_status_change_end(bl,SC_TRUESIGHT,-1); + if(sc_data[SC_WINDWALK].timer!=-1 ) /* ウインドウォーク */ + skill_status_change_end(bl,SC_WINDWALK,-1); + if(sc_data[SC_CARTBOOST].timer!=-1 ) /* カートブースト */ + skill_status_change_end(bl,SC_CARTBOOST,-1); + break; + case SC_FORTUNE: /* 幸運のキス */ + calc_flag = 1; + break; + case SC_SERVICE4U: /* サービスフォーユー */ + calc_flag = 1; + break; + case SC_DANCING: /* ダンス/演奏中 */ + calc_flag = 1; + val3= tick / 1000; + tick = 1000; + break; + + case SC_EXPLOSIONSPIRITS: // 爆裂波動 + calc_flag = 1; + val2 = 75 + 25*val1; + *opt3 |= 8; + break; + case SC_STEELBODY: // 金剛 + calc_flag = 1; + *opt3 |= 16; + break; + case SC_EXTREMITYFIST: /* 阿修羅覇凰拳 */ + break; + case SC_AUTOCOUNTER: + val3 = val4 = 0; + break; + + case SC_SPEEDPOTION0: /* 増速ポーション */ + case SC_SPEEDPOTION1: + case SC_SPEEDPOTION2: + calc_flag = 1; + tick = 1000 * tick; + val2 = 5*(2+type-SC_SPEEDPOTION0); + break; + + /* atk & matk potions [Valaris] */ + case SC_ATKPOT: + case SC_MATKPOT: + calc_flag = 1; + tick = 1000 * tick; + break; + case SC_WEDDING: //結婚用(結婚衣裳になって歩くのが遅いとか) + { + time_t timer; + + calc_flag = 1; + tick = 10000; + if(!val2) + val2 = time(&timer); + } + break; + case SC_NOCHAT: //チャット禁止状態 + { + time_t timer; + + if(!battle_config.muting_players) + break; + + tick = 60000; + if(!val2) + val2 = time(&timer); + updateflag = SP_MANNER; + } + break; + case SC_SELFDESTRUCTION: //自爆 + clif_skillcasting(bl,bl->id, bl->id,0,0,331,skill_get_time(val2,val1)); + val3 = tick / 1000; + tick = 1000; + break; + + /* option1 */ + case SC_STONE: /* 石化 */ + if(!(flag&2)) { + int sc_def = battle_get_mdef(bl)*200; + tick = tick - sc_def; + } + val3 = tick/1000; + if(val3 < 1) val3 = 1; + tick = 5000; + val2 = 1; + break; + case SC_SLEEP: /* 睡眠 */ + if(!(flag&2)) { +// int sc_def = 100 - (battle_get_int(bl) + battle_get_luk(bl)/3); +// tick = tick * sc_def / 100; +// if(tick < 1000) tick = 1000; + tick = 30000;//睡眠はステータス耐性に関わらず30秒 + } + break; + case SC_FREEZE: /* 凍結 */ + if(!(flag&2)) { + int sc_def = 100 - battle_get_mdef(bl); + tick = tick * sc_def / 100; + } + break; + case SC_STAN: /* スタン(val2にミリ秒セット) */ + if(!(flag&2)) { + int sc_def = 100 - (battle_get_vit(bl) + battle_get_luk(bl)/3); + tick = tick * sc_def / 100; + } + break; + + /* option2 */ + case SC_POISON: /* 毒 */ + calc_flag = 1; + if(!(flag&2)) { + int sc_def = 100 - (battle_get_vit(bl) + battle_get_luk(bl)/5); + tick = tick * sc_def / 100; + } + val3 = tick/1000; + if(val3 < 1) val3 = 1; + tick = 1000; + break; + case SC_SILENCE: /* 沈黙(レックスデビーナ) */ + if(!(flag&2)) { + int sc_def = 100 - battle_get_vit(bl); + tick = tick * sc_def / 100; + } + break; + case SC_BLIND: /* 暗黒 */ + calc_flag = 1; + if(!(flag&2)) { + int sc_def = battle_get_lv(bl)/10 + battle_get_int(bl)/15; + tick = 30000 - sc_def; + } + break; + case SC_CURSE: + calc_flag = 1; + if(!(flag&2)) { + int sc_def = 100 - battle_get_vit(bl); + tick = tick * sc_def / 100; + } + break; + + /* option */ + case SC_HIDING: /* ハイディング */ + calc_flag = 1; + if(bl->type == BL_PC) { + val2 = tick / 1000; /* 持続時間 */ + tick = 1000; + } + break; + case SC_CHASEWALK: + case SC_CLOAKING: /* クローキング */ + if(bl->type == BL_PC) + val2 = tick; + else + tick = 5000*val1; + break; + case SC_SIGHT: /* サイト/ルアフ */ + case SC_RUWACH: + val2 = tick/250; + tick = 10; + break; + + /* セーフティウォール、ニューマ */ + case SC_SAFETYWALL: case SC_PNEUMA: + tick=((struct skill_unit *)val2)->group->limit; + break; + + /* アンクル */ + case SC_ANKLE: + break; + + /* ウォーターボール */ + case SC_WATERBALL: + tick=150; + if(val1>5) //レベルが5以上の場合は25発に制限(1発目はすでに打ってるので-1) + val3=5*5-1; + else + val3= (val1|1)*(val1|1)-1; + break; + + /* スキルじゃない/時間に関係しない */ + case SC_RIDING: + calc_flag = 1; + tick = 600*1000; + break; + case SC_FALCON: + case SC_WEIGHT50: + case SC_WEIGHT90: + case SC_BROKNWEAPON: + case SC_BROKNARMOR: + tick=600*1000; + break; + + case SC_AUTOGUARD: + { + int i,t; + for(i=val2=0;i<val1;i++) { + t = 5-(i>>1); + val2 += (t < 0)? 1:t; + } + } + break; + + case SC_DEFENDER: + calc_flag = 1; + val2 = 5 + val1*15; + break; + + case SC_KEEPING: + case SC_BARRIER: + case SC_HALLUCINATION: + break; + case SC_CONCENTRATION: /* コンセントレーション */ + *opt3 |= 1; + calc_flag = 1; + break; + case SC_TENSIONRELAX: /* テンションリラックス */ + calc_flag = 1; + if(bl->type == BL_PC) { + tick = 10000; + } + break; + case SC_AURABLADE: /* オーラブレード */ + case SC_PARRYING: /* パリイング */ +// case SC_ASSUMPTIO: /* */ + case SC_HEADCRUSH: /* ヘッドクラッシュ */ + case SC_JOINTBEAT: /* ジョイントビート */ +// case SC_MARIONETTE: /* マリオネットコントロール */ + + //とりあえず手抜き + break; + +// -- moonsoul (for new upper class related skill status effects) +/* + case SC_AURABLADE: + val2 = val1*10; + break; + case SC_PARRYING: + val2=val1*3; + break; + case SC_CONCENTRATION: + calc_flag=1; + val2=val1*10; + val3=val1*5; + break; + case SC_TENSIONRELAX: +// val2 = 10; +// val3 = 15; + break; + case SC_BERSERK: + calc_flag=1; + break; + case SC_ASSUMPTIO: + if(sc_data[SC_KYRIE].timer!=-1 ) + skill_status_change_end(bl,SC_KYRIE,-1); + break; +*/ + case SC_WINDWALK: /* ウインドウォーク */ + calc_flag = 1; + val2 = (val1 / 2); //Flee上昇率 + break; + case SC_BERSERK: /* バーサーク */ + if(sd){ + sd->status.sp = 0; + clif_updatestatus(sd,SP_SP); + clif_status_change(bl,SC_INCREASEAGI,1); /* アイコン表示 */ + } + *opt3 |= 128; + tick = 1000; + calc_flag = 1; + break; + case SC_ASSUMPTIO: /* アスムプティオ */ + *opt3 |= 2048; + break; + case SC_MARIONETTE: /* マリオネットコントロール */ + *opt3 |= 1024; + break; + case SC_MELTDOWN: /* メルトダウン */ + case SC_CARTBOOST: /* カートブースト */ + case SC_TRUESIGHT: /* トゥルーサイト */ + case SC_SPIDERWEB: /* スパイダーウェッブ */ + case SC_MAGICPOWER: /* 魔法力増幅 */ + calc_flag = 1; + break; + case SC_REJECTSWORD: /* リジェクトソード */ + val2 = 3; //3回攻撃を跳ね返す + break; + case SC_MEMORIZE: /* メモライズ */ + val2 = 3; //3回詠唱を1/3にする + break; + case SC_GRAFFITI: /* グラフィティ */ + { + struct skill_unit_group *sg = skill_unitsetting(bl,RG_GRAFFITI,val1,val2,val3,0); + if(sg) + val4 = (int)sg; + } + break; + case SC_SPLASHER: /* ベナムスプラッシャー */ + break; + default: + if(battle_config.error_log) + printf("UnknownStatusChange [%d]\n", type); + return 0; + } + + if(bl->type==BL_PC && type<SC_SENDMAX) + clif_status_change(bl,type,1); /* アイコン表示 */ + + /* optionの変更 */ + switch(type){ + case SC_STONE: + case SC_FREEZE: + case SC_STAN: + case SC_SLEEP: + battle_stopattack(bl); /* 攻撃停止 */ + skill_stop_dancing(bl,0); /* 演奏/ダンスの中断 */ + { /* 同時に掛からないステータス異常を解除 */ + int i; + for(i = SC_STONE; i <= SC_SLEEP; i++){ + if(sc_data[i].timer != -1){ + (*sc_count)--; + delete_timer(sc_data[i].timer, skill_status_change_timer); + sc_data[i].timer = -1; + } + } + } + if(type == SC_STONE) + *opt1 = 6; + else + *opt1 = type - SC_STONE + 1; + opt_flag = 1; + break; + case SC_POISON: + case SC_CURSE: + case SC_SILENCE: + case SC_BLIND: + *opt2 |= 1<<(type-SC_POISON); + opt_flag = 1; + break; + case SC_SIGNUMCRUCIS: + *opt2 |= 0x40; + opt_flag = 1; + break; + case SC_HIDING: + case SC_CLOAKING: + battle_stopattack(bl); /* 攻撃停止 */ + *option |= ((type==SC_HIDING)?2:4); + opt_flag =1 ; + break; + case SC_CHASEWALK: + battle_stopattack(bl); /* 攻撃停止 */ + *option |= 16388; + opt_flag =1 ; + break; + case SC_SIGHT: + *option |= 1; + opt_flag = 1; + break; + case SC_RUWACH: + *option |= 8192; + opt_flag = 1; + break; + case SC_WEDDING: + *option |= 4096; + opt_flag = 1; + } + + if(opt_flag) /* optionの変更 */ + clif_changeoption(bl); + + (*sc_count)++; /* ステータス異常の数 */ + + sc_data[type].val1 = val1; + sc_data[type].val2 = val2; + sc_data[type].val3 = val3; + sc_data[type].val4 = val4; + /* タイマー設定 */ + sc_data[type].timer = add_timer( + gettick() + tick, skill_status_change_timer, bl->id, type); + + if(bl->type==BL_PC && calc_flag) + pc_calcstatus(sd,0); /* ステータス再計算 */ + + if(bl->type==BL_PC && updateflag) + clif_updatestatus(sd,updateflag); /* ステータスをクライアントに送る */ + + return 0; +} +/*========================================== + * ステータス異常全解除 + *------------------------------------------ + */ +int skill_status_change_clear(struct block_list *bl, int type) +{ + struct status_change* sc_data; + short *sc_count, *option, *opt1, *opt2, *opt3; + int i; + + nullpo_retr(0, bl); + nullpo_retr(0, sc_data = battle_get_sc_data(bl)); + nullpo_retr(0, sc_count = battle_get_sc_count(bl)); + nullpo_retr(0, option = battle_get_option(bl)); + nullpo_retr(0, opt1 = battle_get_opt1(bl)); + nullpo_retr(0, opt2 = battle_get_opt2(bl)); + nullpo_retr(0, opt3 = battle_get_opt3(bl)); + + if (*sc_count == 0) + return 0; + for(i = 0; i < MAX_STATUSCHANGE; i++){ + if(sc_data[i].timer != -1){ /* 異常があるならタイマーを削除する */ +/* + delete_timer(sc_data[i].timer, skill_status_change_timer); + sc_data[i].timer = -1; + + if (!type && i < SC_SENDMAX) + clif_status_change(bl, i, 0); +*/ + + skill_status_change_end(bl, i, -1); + } + } + *sc_count = 0; + *opt1 = 0; + *opt2 = 0; + *opt3 = 0; + *option &= OPTION_MASK; + + if (night_flag == 1 && type == BL_PC) // by [Yor] + *opt2 |= STATE_BLIND; + + if(!type || type&2) + clif_changeoption(bl); + + return 0; +} + +/* クローキング検査(周りに移動不可能地帯があるか) */ +int skill_check_cloaking(struct block_list *bl) +{ + struct map_session_data *sd=NULL; + static int dx[]={-1, 0, 1,-1, 1,-1, 0, 1}; + static int dy[]={-1,-1,-1, 0, 0, 1, 1, 1}; + int end=1,i; + + nullpo_retr(0, bl); + + if(pc_checkskill(sd,AS_CLOAKING)>2) + return 0; + if(bl->type == BL_PC && battle_config.pc_cloak_check_type&1) + return 0; + if(bl->type == BL_MOB && battle_config.monster_cloak_check_type&1) + return 0; + for(i=0;i<sizeof(dx)/sizeof(dx[0]);i++){ + int c=map_getcell(bl->m,bl->x+dx[i],bl->y+dy[i]); + if(c==1 || c==5) end=0; + } + if(end){ + skill_status_change_end(bl, SC_CLOAKING, -1); + *battle_get_option(bl)&=~4; /* 念のための処理 */ + } + return end; +} + +/* + *---------------------------------------------------------------------------- + * スキルユニット + *---------------------------------------------------------------------------- + */ + +/*========================================== + * 演奏/ダンススキルかどうか判定 + * 引数 スキルID + * 戻り ダンスじゃない=0 合奏=2 それ以外のダンス=1 + *------------------------------------------ + */ +int skill_is_danceskill(int id) +{ + int i; + switch(id){ + case BD_LULLABY: /* 子守歌 */ + case BD_RICHMANKIM: /* ニヨルドの宴 */ + case BD_ETERNALCHAOS: /* 永遠の混沌 */ + case BD_DRUMBATTLEFIELD: /* 戦太鼓の響き */ + case BD_RINGNIBELUNGEN: /* ニーベルングの指輪 */ + case BD_ROKISWEIL: /* ロキの叫び */ + case BD_INTOABYSS: /* 深淵の中に */ + case BD_SIEGFRIED: /* 不死身のジークフリード */ + case BD_RAGNAROK: /* 神々の黄昏 */ + case CG_MOONLIT: /* 月明りの泉に落ちる花びら */ + i=2; + break; + case BA_DISSONANCE: /* 不協和音 */ + case BA_FROSTJOKE: /* 寒いジョーク */ + case BA_WHISTLE: /* 口笛 */ + case BA_ASSASSINCROSS: /* 夕陽のアサシンクロス */ + case BA_POEMBRAGI: /* ブラギの詩 */ + case BA_APPLEIDUN: /* イドゥンの林檎 */ + case DC_UGLYDANCE: /* 自分勝手なダンス */ + case DC_SCREAM: /* スクリーム */ + case DC_HUMMING: /* ハミング */ + case DC_DONTFORGETME: /* 私を忘れないで… */ + case DC_FORTUNEKISS: /* 幸運のキス */ + case DC_SERVICEFORYOU: /* サービスフォーユー */ + i=1; + break; + default: + i=0; + } + return i; +} + +/*========================================== + * 演奏/ダンスをやめる + * flag 1で合奏中なら相方にユニットを任せる + * + *------------------------------------------ + */ +void skill_stop_dancing(struct block_list *src, int flag) +{ + struct status_change* sc_data; + struct skill_unit_group* group; + + nullpo_retv(src); + + sc_data=battle_get_sc_data(src); + if(sc_data && sc_data[SC_DANCING].timer==-1) + return; + group=(struct skill_unit_group *)sc_data[SC_DANCING].val2; //ダンスのスキルユニットIDはval2に入ってる + if(group && src->type==BL_PC && sc_data && sc_data[SC_DANCING].val4){ //合奏中断 + struct map_session_data* dsd=map_id2sd(sc_data[SC_DANCING].val4); //相方のsd取得 + if(flag){ //ログアウトなど片方が落ちても演奏が継続される + if(dsd && src->id == group->src_id){ //グループを持ってるPCが落ちる + group->src_id=sc_data[SC_DANCING].val4; //相方にグループを任せる + if(flag&1) //ログアウト + dsd->sc_data[SC_DANCING].val4=0; //相方の相方を0にして合奏終了→通常のダンス状態 + if(flag&2) //ハエ飛びなど + return; //合奏もダンス状態も終了させない&スキルユニットは置いてけぼり + }else if(dsd && dsd->bl.id == group->src_id){ //相方がグループを持っているPCが落ちる(自分はグループを持っていない) + if(flag&1) //ログアウト + dsd->sc_data[SC_DANCING].val4=0; //相方の相方を0にして合奏終了→通常のダンス状態 + if(flag&2) //ハエ飛びなど + return; //合奏もダンス状態も終了させない&スキルユニットは置いてけぼり + } + skill_status_change_end(src,SC_DANCING,-1);//自分のステータスを終了させる + //そしてグループは消さない&消さないのでステータス計算もいらない? + return; + }else{ + if(dsd && src->id == group->src_id){ //グループを持ってるPCが止める + skill_status_change_end((struct block_list *)dsd,SC_DANCING,-1);//相手のステータスを終了させる + } + if(dsd && dsd->bl.id == group->src_id){ //相方がグループを持っているPCが止める(自分はグループを持っていない) + skill_status_change_end(src,SC_DANCING,-1);//自分のステータスを終了させる + } + } + } + if(flag&2 && group && src->type==BL_PC){ //ハエで飛んだときとかはユニットも飛ぶ + struct map_session_data *sd = (struct map_session_data *)src; + skill_unit_move_unit_group(group, sd->bl.m,(sd->to_x - sd->bl.x),(sd->to_y - sd->bl.y)); + return; + } + skill_delunitgroup(group); + if(src->type==BL_PC) + pc_calcstatus((struct map_session_data *)src,0); +} + +/*========================================== + * スキルユニット初期化 + *------------------------------------------ + */ +struct skill_unit *skill_initunit(struct skill_unit_group *group,int idx,int x,int y) +{ + struct skill_unit *unit; + + nullpo_retr(NULL, group); + nullpo_retr(NULL, unit=&group->unit[idx]); + + if(!unit->alive) + group->alive_count++; + + unit->bl.id=map_addobject(&unit->bl); + unit->bl.type=BL_SKILL; + unit->bl.m=group->map; + unit->bl.x=x; + unit->bl.y=y; + unit->group=group; + unit->val1=unit->val2=0; + unit->alive=1; + + map_addblock(&unit->bl); + clif_skill_setunit(unit); + return unit; +} + +int skill_unit_timer_sub_ondelete( struct block_list *bl, va_list ap ); +/*========================================== + * スキルユニット削除 + *------------------------------------------ + */ +int skill_delunit(struct skill_unit *unit) +{ + struct skill_unit_group *group; + int range; + + nullpo_retr(0, unit); + if(!unit->alive) + return 0; + nullpo_retr(0, group=unit->group); + + /* onlimitイベント呼び出し */ + skill_unit_onlimit( unit,gettick() ); + + /* ondeleteイベント呼び出し */ + range=group->range; + map_foreachinarea( skill_unit_timer_sub_ondelete, unit->bl.m, + unit->bl.x-range,unit->bl.y-range,unit->bl.x+range,unit->bl.y+range,0, + &unit->bl,gettick() ); + + clif_skill_delunit(unit); + + unit->group=NULL; + unit->alive=0; + map_delobjectnofree(unit->bl.id); + if(group->alive_count>0 && (--group->alive_count)<=0) + skill_delunitgroup(group); + + return 0; +} +/*========================================== + * スキルユニットグループ初期化 + *------------------------------------------ + */ +static int skill_unit_group_newid=10; +struct skill_unit_group *skill_initunitgroup(struct block_list *src, + int count,int skillid,int skilllv,int unit_id) +{ + int i; + struct skill_unit_group *group=NULL, *list=NULL; + int maxsug=0; + + nullpo_retr(NULL, src); + + if(src->type==BL_PC){ + list=((struct map_session_data *)src)->skillunit; + maxsug=MAX_SKILLUNITGROUP; + }else if(src->type==BL_MOB){ + list=((struct mob_data *)src)->skillunit; + maxsug=MAX_MOBSKILLUNITGROUP; + }else if(src->type==BL_PET){ + list=((struct pet_data *)src)->skillunit; + maxsug=MAX_MOBSKILLUNITGROUP; + } + if(list){ + for(i=0;i<maxsug;i++) /* 空いているもの検索 */ + if(list[i].group_id==0){ + group=&list[i]; + break; + } + + if(group==NULL){ /* 空いてないので古いもの検索 */ + int j=0; + unsigned maxdiff=0,x,tick=gettick(); + for(i=0;i<maxsug;i++) + if((x=DIFF_TICK(tick,list[i].tick))>maxdiff){ + maxdiff=x; + j=i; + } + skill_delunitgroup(&list[j]); + group=&list[j]; + } + } + + if(group==NULL){ + printf("skill_initunitgroup: error unit group !\n"); + exit(1); + } + + group->src_id=src->id; + group->party_id=battle_get_party_id(src); + group->guild_id=battle_get_guild_id(src); + group->group_id=skill_unit_group_newid++; + if(skill_unit_group_newid<=0) + skill_unit_group_newid=10; + group->unit=(struct skill_unit *)aCalloc(count,sizeof(struct skill_unit)); + group->unit_count=count; + group->val1=group->val2=0; + group->skill_id=skillid; + group->skill_lv=skilllv; + group->unit_id=unit_id; + group->map=src->m; + group->range=0; + group->limit=10000; + group->interval=1000; + group->tick=gettick(); + group->valstr=NULL; + + if( skill_is_danceskill(skillid) ){ + struct map_session_data *sd = NULL; + if(src->type==BL_PC && (sd=(struct map_session_data *)src) ){ + sd->skillid_dance=skillid; + sd->skilllv_dance=skilllv; + } + skill_status_change_start(src,SC_DANCING,skillid,(int)group,0,0,skill_get_time(skillid,skilllv)+1000,0); + switch(skillid){ //合奏スキルは相方をダンス状態にする + case BD_LULLABY: /* 子守歌 */ + case BD_RICHMANKIM: /* ニヨルドの宴 */ + case BD_ETERNALCHAOS: /* 永遠の混沌 */ + case BD_DRUMBATTLEFIELD: /* 戦太鼓の響き */ + case BD_RINGNIBELUNGEN: /* ニーベルングの指輪 */ + case BD_ROKISWEIL: /* ロキの叫び */ + case BD_INTOABYSS: /* 深淵の中に */ + case BD_SIEGFRIED: /* 不死身のジークフリード */ + case BD_RAGNAROK: /* 神々の黄昏 */ + case CG_MOONLIT: /* 月明りの泉に落ちる花びら */ + { + int range=1; + int c=0; + if(sd){ + map_foreachinarea(skill_check_condition_use_sub,sd->bl.m, + sd->bl.x-range,sd->bl.y-range, + sd->bl.x+range,sd->bl.y+range,BL_PC,&sd->bl,&c); + } + } + } + } + return group; +} + +/*========================================== + * スキルユニットグループ削除 + *------------------------------------------ + */ +int skill_delunitgroup(struct skill_unit_group *group) +{ + struct block_list *src; + int i; + + nullpo_retr(0, group); + if(group->unit_count<=0) + return 0; + + src=map_id2bl(group->src_id); + if( skill_is_danceskill(group->skill_id) ){ //ダンススキルはダンス状態を解除する + if(src) + skill_status_change_end(src,SC_DANCING,-1); + } + + group->alive_count=0; + if(group->unit!=NULL){ + for(i=0;i<group->unit_count;i++) + if(group->unit[i].alive) + skill_delunit(&group->unit[i]); + } + if(group->valstr!=NULL){ + map_freeblock(group->valstr); + group->valstr=NULL; + } + + map_freeblock(group->unit); /* free()の替わり */ + group->unit=NULL; + group->src_id=0; + group->group_id=0; + group->unit_count=0; + return 0; +} + +/*========================================== + * スキルユニットグループ全削除 + *------------------------------------------ + */ +int skill_clear_unitgroup(struct block_list *src) +{ + struct skill_unit_group *group=NULL; + int maxsug=0; + + nullpo_retr(0, src); + + if(src->type==BL_PC){ + group=((struct map_session_data *)src)->skillunit; + maxsug=MAX_SKILLUNITGROUP; + }else if(src->type==BL_MOB){ + group=((struct mob_data *)src)->skillunit; + maxsug=MAX_MOBSKILLUNITGROUP; + }else if(src->type==BL_PET){ // [Valaris] + group=((struct pet_data *)src)->skillunit; + maxsug=MAX_MOBSKILLUNITGROUP; + } + if(group){ + int i; + for(i=0;i<maxsug;i++) + if(group[i].group_id>0 && group[i].src_id == src->id) + skill_delunitgroup(&group[i]); + } + return 0; +} + +/*========================================== + * スキルユニットグループの被影響tick検索 + *------------------------------------------ + */ +struct skill_unit_group_tickset *skill_unitgrouptickset_search( + struct block_list *bl,int group_id) +{ + int i,j=0,k,s=group_id%MAX_SKILLUNITGROUPTICKSET; + struct skill_unit_group_tickset *set=NULL; + + nullpo_retr(0, bl); + + if(bl->type==BL_PC){ + set=((struct map_session_data *)bl)->skillunittick; + }else{ + set=((struct mob_data *)bl)->skillunittick; + } + if(set==NULL) + return 0; + for(i=0;i<MAX_SKILLUNITGROUPTICKSET;i++) + if( set[(k=(i+s)%MAX_SKILLUNITGROUPTICKSET)].group_id == group_id ) + return &set[k]; + else if( set[k].group_id==0 ) + j=k; + + return &set[j]; +} + +/*========================================== + * スキルユニットグループの被影響tick削除 + *------------------------------------------ + */ +int skill_unitgrouptickset_delete(struct block_list *bl,int group_id) +{ + int i,s=group_id%MAX_SKILLUNITGROUPTICKSET; + struct skill_unit_group_tickset *set=NULL,*ts; + + nullpo_retr(0, bl); + + if(bl->type==BL_PC){ + set=((struct map_session_data *)bl)->skillunittick; + }else{ + set=((struct mob_data *)bl)->skillunittick; + } + + if(set!=NULL){ + + for(i=0;i<MAX_SKILLUNITGROUPTICKSET;i++) + if( (ts=&set[(i+s)%MAX_SKILLUNITGROUPTICKSET])->group_id == group_id ) + ts->group_id=0; + + } + return 0; +} + +/*========================================== + * スキルユニットタイマー発動処理用(foreachinarea) + *------------------------------------------ + */ +int skill_unit_timer_sub_onplace( struct block_list *bl, va_list ap ) +{ + struct block_list *src; + struct skill_unit *su; + unsigned int tick; + + nullpo_retr(0, bl); + nullpo_retr(0, ap); + src=va_arg(ap,struct block_list*); + + tick=va_arg(ap,unsigned int); + su = (struct skill_unit *)src; + + if( su && su->alive ) { + struct skill_unit_group *sg; + sg = su->group; + if(sg && battle_check_target(src,bl,sg->target_flag )>0) + skill_unit_onplace( su, bl, tick ); + } + return 0; +} + +/*========================================== + * スキルユニットタイマー削除処理用(foreachinarea) + *------------------------------------------ + */ +int skill_unit_timer_sub_ondelete( struct block_list *bl, va_list ap ) +{ + struct block_list *src; + struct skill_unit *su; + unsigned int tick; + + nullpo_retr(0, bl); + nullpo_retr(0, ap); + src=va_arg(ap,struct block_list*); + + tick=va_arg(ap,unsigned int); + su = (struct skill_unit *)src; + + if( su && su->alive ){ + struct skill_unit_group *sg; + sg = su->group; + if( sg && battle_check_target(src,bl,sg->target_flag )>0 ) + skill_unit_ondelete( su, bl, tick ); + } + return 0; +} + +/*========================================== + * スキルユニットタイマー処理用(foreachobject) + *------------------------------------------ + */ +int skill_unit_timer_sub( struct block_list *bl, va_list ap ) +{ + struct skill_unit *unit; + struct skill_unit_group *group; + int range; + unsigned int tick; + + nullpo_retr(0, bl); + nullpo_retr(0, ap); + nullpo_retr(0, unit=(struct skill_unit *)bl); + nullpo_retr(0, group=unit->group); + tick=va_arg(ap,unsigned int); + + if(!unit->alive) + return 0; + + range=(unit->range!=0)?unit->range:group->range; + + /* onplaceイベント呼び出し */ + if(unit->alive && unit->range>=0){ + map_foreachinarea( skill_unit_timer_sub_onplace, bl->m, + bl->x-range,bl->y-range,bl->x+range,bl->y+range,0, + bl,tick); + if(group->unit_id == 0xaa && DIFF_TICK(tick,group->tick)>=6000*group->val2){ + map_foreachinarea( skill_idun_heal, bl->m, + bl->x-range,bl->y-range,bl->x+range,bl->y+range,0,unit); + group->val2++; + } + } + /* 時間切れ削除 */ + if(unit->alive && + (DIFF_TICK(tick,group->tick)>=group->limit || DIFF_TICK(tick,group->tick)>=unit->limit) ){ + switch(group->unit_id){ + + + + + + + case 0x8f: /* ブラストマイン */ + group->unit_id = 0x8c; + clif_changelook(bl,LOOK_BASE,group->unit_id); + group->limit=DIFF_TICK(tick+1500,group->tick); + unit->limit=DIFF_TICK(tick+1500,group->tick); + break; + case 0x90: /* スキッドトラップ */ + case 0x91: /* アンクルスネア */ + case 0x93: /* ランドマイン */ + case 0x94: /* ショックウェーブトラップ */ + case 0x95: /* サンドマン */ + case 0x96: /* フラッシャー */ + case 0x97: /* フリージングトラップ */ + case 0x98: /* クレイモアートラップ */ + case 0x99: /* トーキーボックス */ + { + struct block_list *src=map_id2bl(group->src_id); + if(group->unit_id == 0x91 && group->val2); + else{ + if(src && src->type==BL_PC){ + struct item item_tmp; + memset(&item_tmp,0,sizeof(item_tmp)); + item_tmp.nameid=1065; + item_tmp.identify=1; + map_addflooritem(&item_tmp,1,bl->m,bl->x,bl->y,NULL,NULL,NULL,0); // 罠返還 + } + } + } + default: + skill_delunit(unit); + } + } + + if(group->unit_id == 0x8d) { + unit->val1 -= 5; + if(unit->val1 <= 0 && unit->limit + group->tick > tick + 700) + unit->limit = DIFF_TICK(tick+700,group->tick); + } + + return 0; +} +/*========================================== + * スキルユニットタイマー処理 + *------------------------------------------ + */ +int skill_unit_timer( int tid,unsigned int tick,int id,int data) +{ + map_freeblock_lock(); + + map_foreachobject( skill_unit_timer_sub, BL_SKILL, tick ); + + map_freeblock_unlock(); + + return 0; +} + +/*========================================== + * スキルユニット移動時処理用(foreachinarea) + *------------------------------------------ + */ +int skill_unit_out_all_sub( struct block_list *bl, va_list ap ) +{ + struct skill_unit *unit; + struct skill_unit_group *group; + struct block_list *src; + int range; + unsigned int tick; + + nullpo_retr(0, bl); + nullpo_retr(0, ap); + nullpo_retr(0, src=va_arg(ap,struct block_list*)); + nullpo_retr(0, unit=(struct skill_unit *)bl); + nullpo_retr(0, group=unit->group); + + tick=va_arg(ap,unsigned int); + + if(!unit->alive || src->prev==NULL) + return 0; + + range=(unit->range!=0)?unit->range:group->range; + + if( range<0 || battle_check_target(bl,src,group->target_flag )<=0 ) + return 0; + + if( src->x >= bl->x-range && src->x <= bl->x+range && + src->y >= bl->y-range && src->y <= bl->y+range ) + skill_unit_onout( unit, src, tick ); + + return 0; +} + + +/*========================================== + * スキルユニット移動時処理 + *------------------------------------------ + */ +int skill_unit_out_all( struct block_list *bl,unsigned int tick,int range) +{ + nullpo_retr(0, bl); + + if( bl->prev==NULL ) + return 0; + + if(range<7) + range=7; + map_foreachinarea( skill_unit_out_all_sub, + bl->m,bl->x-range,bl->y-range,bl->x+range,bl->y+range,BL_SKILL, + bl,tick ); + + return 0; +} + +/*========================================== + * スキルユニット移動時処理用(foreachinarea) + *------------------------------------------ + */ +int skill_unit_move_sub( struct block_list *bl, va_list ap ) +{ + struct skill_unit *unit; + struct skill_unit_group *group; + struct block_list *src; + int range; + unsigned int tick; + + nullpo_retr(0, bl); + nullpo_retr(0, ap); + nullpo_retr(0, unit=(struct skill_unit *)bl); + nullpo_retr(0, src=va_arg(ap,struct block_list*)); + + tick=va_arg(ap,unsigned int); + + if(!unit->alive || src->prev==NULL) + return 0; + + if((group=unit->group) == NULL) + return 0; + range=(unit->range!=0)?unit->range:group->range; + + if( range<0 || battle_check_target(bl,src,group->target_flag )<=0 ) + return 0; + + if( src->x >= bl->x-range && src->x <= bl->x+range && + src->y >= bl->y-range && src->y <= bl->y+range ) + skill_unit_onplace( unit, src, tick ); + else + skill_unit_onout( unit, src, tick ); + + return 0; +} + +/*========================================== + * スキルユニット移動時処理 + *------------------------------------------ + */ +int skill_unit_move( struct block_list *bl,unsigned int tick,int range) +{ + nullpo_retr(0, bl); + + if( bl->prev==NULL ) + return 0; + + if(range<7) + range=7; + map_foreachinarea( skill_unit_move_sub, + bl->m,bl->x-range,bl->y-range,bl->x+range,bl->y+range,BL_SKILL, + bl,tick ); + + return 0; +} + +/*========================================== + * スキルユニット自体の移動時処理(foreachinarea) + *------------------------------------------ + */ +int skill_unit_move_unit_group_sub( struct block_list *bl, va_list ap ) +{ + struct skill_unit *unit; + struct skill_unit_group *group; + struct block_list *src; + int range; + unsigned int tick; + + nullpo_retr(0, bl); + nullpo_retr(0, ap); + nullpo_retr(0, src=va_arg(ap,struct block_list*)); + nullpo_retr(0, unit=(struct skill_unit *)src); + nullpo_retr(0, group=unit->group); + + tick=va_arg(ap,unsigned int); + + if(!unit->alive || bl->prev==NULL) + return 0; + + range=(unit->range!=0)?unit->range:group->range; + + if( range<0 || battle_check_target(src,bl,group->target_flag )<=0 ) + return 0; + if( bl->x >= src->x-range && bl->x <= src->x+range && + bl->y >= src->y-range && bl->y <= src->y+range ) + skill_unit_onplace( unit, bl, tick ); + else + skill_unit_onout( unit, bl, tick ); + return 0; +} + +/*========================================== + * スキルユニット自体の移動時処理 + * 引数はグループと移動量 + *------------------------------------------ + */ +int skill_unit_move_unit_group( struct skill_unit_group *group, int m,int dx,int dy) +{ + nullpo_retr(0, group); + + if( group->unit_count<=0) + return 0; + + if(group->unit!=NULL){ + if(!battle_config.unit_movement_type){ + int i; + for(i=0;i<group->unit_count;i++){ + struct skill_unit *unit=&group->unit[i]; + if(unit->alive && !(m==unit->bl.m && dx==0 && dy==0)){ + int range=unit->range; + map_delblock(&unit->bl); + unit->bl.m = m; + unit->bl.x += dx; + unit->bl.y += dy; + map_addblock(&unit->bl); + clif_skill_setunit(unit); + if(range>0){ + if(range<7) + range=7; + map_foreachinarea( skill_unit_move_unit_group_sub, unit->bl.m, + unit->bl.x-range,unit->bl.y-range,unit->bl.x+range,unit->bl.y+range,0, + &unit->bl,gettick() ); + } + } + } + }else{ + int i,j, *r_flag, *s_flag, *m_flag; + struct skill_unit *unit1; + struct skill_unit *unit2; + r_flag = (int *) malloc(sizeof(int) * group->unit_count); + s_flag = (int *) malloc(sizeof(int) * group->unit_count); + m_flag = (int *) malloc(sizeof(int) * group->unit_count); + memset(r_flag,0, sizeof(int) * group->unit_count);// 継承フラグ + memset(s_flag,0, sizeof(int) * group->unit_count);// 継承フラグ + memset(m_flag,0, sizeof(int) * group->unit_count);// 継承フラグ + + //先にフラグを全部決める + for(i=0;i<group->unit_count;i++){ + int move_check=0;// かぶりフラグ + unit1=&group->unit[i]; + for(j=0;j<group->unit_count;j++){ + unit2=&group->unit[j]; + if(unit1->bl.m==m && unit1->bl.x+dx==unit2->bl.x && unit1->bl.y+dy==unit2->bl.y){ + //移動先にユニットがかぶってたら + s_flag[i]=1;// 移動前のユニットナンバーの継承フラグon + r_flag[j]=1;// かぶるユニットナンバーの残留フラグon + move_check=1;//ユニットがかぶった。 + break; + } + } + if(!move_check)// ユニットがかぶってなかったら + m_flag[i]=1;// 移動前ユニットナンバーの移動フラグon + } + + //フラグに基づいてユニット移動 + for(i=0;i<group->unit_count;i++){ + unit1=&group->unit[i]; + if(m_flag[i]){// 移動フラグがonで + if(!r_flag[i]){// 残留フラグがoffなら + //単純移動(rangeも継承の必要無し) + int range=unit1->range; + map_delblock(&unit1->bl); + unit1->bl.m = m; + unit1->bl.x += dx; + unit1->bl.y += dy; + map_addblock(&unit1->bl); + clif_skill_setunit(unit1); + if(range > 0){ + if(range < 7) + range = 7; + map_foreachinarea( skill_unit_move_unit_group_sub, unit1->bl.m, + unit1->bl.x-range,unit1->bl.y-range,unit1->bl.x+range,unit1->bl.y+range,0, + &unit1->bl,gettick() ); + } + }else{// 残留フラグがonなら + //空ユニットになるので、継承可能なユニットを探す + for(j=0;j<group->unit_count;j++){ + unit2=&group->unit[j]; + if(s_flag[j] && !r_flag[j]){ + // 継承移動(range継承付き) + int range=unit1->range; + map_delblock(&unit2->bl); + unit2->bl.m = m; + unit2->bl.x = unit1->bl.x + dx; + unit2->bl.y = unit1->bl.y + dy; + unit2->range = unit1->range; + map_addblock(&unit2->bl); + clif_skill_setunit(unit2); + if(range > 0){ + if(range < 7) + range = 7; + map_foreachinarea( skill_unit_move_unit_group_sub, unit2->bl.m, + unit2->bl.x-range,unit2->bl.y-range,unit2->bl.x+range,unit2->bl.y+range,0, + &unit2->bl,gettick() ); + } + s_flag[j]=0;// 継承完了したのでoff + break; + } + } + } + } + } + free(r_flag); + free(s_flag); + free(m_flag); + } + } + return 0; +} + +/*---------------------------------------------------------------------------- + * アイテム合成 + *---------------------------------------------------------------------------- + */ + +/*========================================== + * アイテム合成可能判定 + *------------------------------------------ + */ +int skill_can_produce_mix( struct map_session_data *sd, int nameid, int trigger ) +{ + int i,j; + + nullpo_retr(0, sd); + + if(nameid<=0) + return 0; + + for(i=0;i<MAX_SKILL_PRODUCE_DB;i++){ + if(skill_produce_db[i].nameid == nameid ) + break; + } + if( i >= MAX_SKILL_PRODUCE_DB ) /* データベースにない */ + return 0; + + if(trigger>=0){ + if(trigger==32 || trigger==16 || trigger==64){ + if(skill_produce_db[i].itemlv!=trigger) /* ファーマシー*ポーション類と溶鉱炉*鉱石以外はだめ */ + return 0; + }else{ + if(skill_produce_db[i].itemlv>=16) /* 武器以外はだめ */ + return 0; + if( itemdb_wlv(nameid)>trigger ) /* 武器Lv判定 */ + return 0; + } + } + if( (j=skill_produce_db[i].req_skill)>0 && pc_checkskill(sd,j)<=0 ) + return 0; /* スキルが足りない */ + + for(j=0;j<5;j++){ + int id,x,y; + if( (id=skill_produce_db[i].mat_id[j]) <= 0 ) /* これ以上は材料要らない */ + continue; + if(skill_produce_db[i].mat_amount[j] <= 0) { + if(pc_search_inventory(sd,id) < 0) + return 0; + } + else { + for(y=0,x=0;y<MAX_INVENTORY;y++) + if( sd->status.inventory[y].nameid == id ) + x+=sd->status.inventory[y].amount; + if(x<skill_produce_db[i].mat_amount[j]) /* アイテムが足りない */ + return 0; + } + } + return i+1; +} + +/*========================================== + * アイテム合成可能判定 + *------------------------------------------ + */ +int skill_produce_mix( struct map_session_data *sd, + int nameid, int slot1, int slot2, int slot3 ) +{ + int slot[3]; + int i,sc,ele,idx,equip,wlv,make_per,flag; + + nullpo_retr(0, sd); + + if( !(idx=skill_can_produce_mix(sd,nameid,-1)) ) /* 条件不足 */ + return 0; + idx--; + slot[0]=slot1; + slot[1]=slot2; + slot[2]=slot3; + + /* 埋め込み処理 */ + for(i=0,sc=0,ele=0;i<3;i++){ + int j; + if( slot[i]<=0 ) + continue; + j = pc_search_inventory(sd,slot[i]); + if(j < 0) /* 不正パケット(アイテム存在)チェック */ + continue; + if(slot[i]==1000){ /* 星のかけら */ + pc_delitem(sd,j,1,1); + sc++; + } + if(slot[i]>=994 && slot[i]<=997 && ele==0){ /* 属性石 */ + static const int ele_table[4]={3,1,4,2}; + pc_delitem(sd,j,1,1); + ele=ele_table[slot[i]-994]; + } + } + + /* 材料消費 */ + for(i=0;i<5;i++){ + int j,id,x; + if( (id=skill_produce_db[idx].mat_id[i]) <= 0 ) + continue; + x=skill_produce_db[idx].mat_amount[i]; /* 必要な個数 */ + do{ /* 2つ以上のインデックスにまたがっているかもしれない */ + int y=0; + j = pc_search_inventory(sd,id); + + if(j >= 0){ + y = sd->status.inventory[j].amount; + if(y>x)y=x; /* 足りている */ + pc_delitem(sd,j,y,0); + }else { + if(battle_config.error_log) + printf("skill_produce_mix: material item error\n"); + } + + x-=y; /* まだ足りない個数を計算 */ + }while( j>=0 && x>0 ); /* 材料を消費するか、エラーになるまで繰り返す */ + } + + /* 確率判定 */ + equip = itemdb_isequip(nameid); + if(!equip) { + if(skill_produce_db[idx].req_skill==AM_PHARMACY) { + if((nameid >= 501 && nameid <= 506) || (nameid >= 545 && nameid <= 547) || nameid == 525) + make_per = 2000 + sd->status.base_level*30 + sd->paramc[3]*20 + sd->paramc[4]*15 + pc_checkskill(sd,AM_LEARNINGPOTION)*100 + pc_checkskill(sd,AM_PHARMACY)*300 + pc_checkskill(sd,AM_POTIONPITCHER)*100; + else if(nameid == 970) + make_per = 1500 + sd->status.base_level*30 + sd->paramc[3]*20 + sd->paramc[4]*15 + pc_checkskill(sd,AM_LEARNINGPOTION)*100 + pc_checkskill(sd,AM_PHARMACY)*300; + else if(nameid == 7135) + make_per = 1000 + sd->status.base_level*30 + sd->paramc[3]*20 + sd->paramc[4]*15 + pc_checkskill(sd,AM_LEARNINGPOTION)*100 + pc_checkskill(sd,AM_PHARMACY)*300 + pc_checkskill(sd,AM_DEMONSTRATION)*100; + else if(nameid == 7136) + make_per = 1000 + sd->status.base_level*30 + sd->paramc[3]*20 + sd->paramc[4]*15 + pc_checkskill(sd,AM_LEARNINGPOTION)*100 + pc_checkskill(sd,AM_PHARMACY)*300 + pc_checkskill(sd,AM_ACIDTERROR)*100; + else if(nameid == 7137) + make_per = 1000 + sd->status.base_level*30 + sd->paramc[3]*20 + sd->paramc[4]*15 + pc_checkskill(sd,AM_LEARNINGPOTION)*100 + pc_checkskill(sd,AM_PHARMACY)*300 + pc_checkskill(sd,AM_CANNIBALIZE)*100; + else if(nameid == 7138) + make_per = 1000 + sd->status.base_level*30 + sd->paramc[3]*20 + sd->paramc[4]*15 + pc_checkskill(sd,AM_LEARNINGPOTION)*100 + pc_checkskill(sd,AM_PHARMACY)*300 + pc_checkskill(sd,AM_SPHEREMINE)*100; + else if(nameid == 7139) + make_per = 1000 + sd->status.base_level*30 + sd->paramc[3]*20 + sd->paramc[4]*15 + pc_checkskill(sd,AM_LEARNINGPOTION)*100 + pc_checkskill(sd,AM_PHARMACY)*300 + pc_checkskill(sd,AM_CP_WEAPON)*100 + + pc_checkskill(sd,AM_CP_SHIELD)*100 + pc_checkskill(sd,AM_CP_ARMOR)*100 + pc_checkskill(sd,AM_CP_HELM)*100; + else + make_per = 1000 + sd->status.base_level*30 + sd->paramc[3]*20 + sd->paramc[4]*15 + pc_checkskill(sd,AM_LEARNINGPOTION)*100 + pc_checkskill(sd,AM_PHARMACY)*300; + } + else { + if(nameid == 998) + make_per = 2000 + sd->status.base_level*30 + sd->paramc[4]*20 + sd->paramc[5]*10 + pc_checkskill(sd,skill_produce_db[idx].req_skill)*600; + else if(nameid == 985) + make_per = 1000 + sd->status.base_level*30 + sd->paramc[4]*20 + sd->paramc[5]*10 + (pc_checkskill(sd,skill_produce_db[idx].req_skill)-1)*500; + else + make_per = 1000 + sd->status.base_level*30 + sd->paramc[4]*20 + sd->paramc[5]*10 + pc_checkskill(sd,skill_produce_db[idx].req_skill)*500; + } + } + else { + int add_per; + if(pc_search_inventory(sd,989) >= 0) add_per = 750; + else if(pc_search_inventory(sd,988) >= 0) add_per = 500; + else if(pc_search_inventory(sd,987) >= 0) add_per = 250; + else if(pc_search_inventory(sd,986) >= 0) add_per = 0; + else add_per = -500; + if(ele) add_per -= 500; + add_per -= sc*500; + wlv = itemdb_wlv(nameid); + make_per = ((250 + sd->status.base_level*15 + sd->paramc[4]*10 + sd->paramc[5]*5 + pc_checkskill(sd,skill_produce_db[idx].req_skill)*500 + + add_per) * (100 - (wlv - 1)*20))/100 + pc_checkskill(sd,BS_WEAPONRESEARCH)*100 + ((wlv >= 3)? pc_checkskill(sd,BS_ORIDEOCON)*100 : 0); + } + + if(make_per < 1) make_per = 1; + + if(skill_produce_db[idx].req_skill==AM_PHARMACY) { + if( battle_config.pp_rate!=100 ) + make_per=make_per*battle_config.pp_rate/100; + } + else { + if( battle_config.wp_rate!=100 ) /* 確率補正 */ + make_per=make_per*battle_config.wp_rate/100; + } + +// if(battle_config.etc_log) +// printf("make rate = %d\n",make_per); + + if(rand()%10000 < make_per){ + /* 成功 */ + struct item tmp_item; + memset(&tmp_item,0,sizeof(tmp_item)); + tmp_item.nameid=nameid; + tmp_item.amount=1; + tmp_item.identify=1; + if(equip){ /* 武器の場合 */ + tmp_item.card[0]=0x00ff; /* 製造武器フラグ */ + tmp_item.card[1]=((sc*5)<<8)+ele; /* 属性とつよさ */ + *((unsigned long *)(&tmp_item.card[2]))=sd->char_id; /* キャラID */ + } + else if((battle_config.produce_item_name_input && skill_produce_db[idx].req_skill!=AM_PHARMACY) || + (battle_config.produce_potion_name_input && skill_produce_db[idx].req_skill==AM_PHARMACY)) { + tmp_item.card[0]=0x00fe; + tmp_item.card[1]=0; + *((unsigned long *)(&tmp_item.card[2]))=sd->char_id; /* キャラID */ + } + + if(skill_produce_db[idx].req_skill!=AM_PHARMACY && skill_produce_db[idx].req_skill!=WS_CREATECOIN) { //武器製造の場合 + clif_produceeffect(sd,0,nameid);/* 武器製造エフェクトパケット */ + clif_misceffect(&sd->bl,3); /* 他人にも成功を通知(精錬成功エフェクトと同じでいいの?) */ + } + else if(skill_produce_db[idx].req_skill==AM_PHARMACY){ //ファーマシーの場合 + clif_produceeffect(sd,2,nameid);/* 製薬エフェクトパケット */ + clif_misceffect(&sd->bl,5); /* 他人にも成功を通知*/ + }else{ + clif_produceeffect(sd,0,nameid);/* 不明なのでとりあえず製造エフェクトパケット */ + clif_misceffect(&sd->bl,3); /* 他人にも成功を通知*/ + } + + if((flag = pc_additem(sd,&tmp_item,1))) { + clif_additem(sd,0,0,flag); + map_addflooritem(&tmp_item,1,sd->bl.m,sd->bl.x,sd->bl.y,NULL,NULL,NULL,0); + } + } + else { + if(skill_produce_db[idx].req_skill!=AM_PHARMACY) { //武器製造の場合 + clif_produceeffect(sd,1,nameid);/* 武器製造失敗エフェクトパケット */ + clif_misceffect(&sd->bl,2); /* 他人にも失敗を通知 */ + } + else if(skill_produce_db[idx].req_skill==AM_PHARMACY){ //ファーマシーの場合 + clif_produceeffect(sd,3,nameid);/* 製薬失敗エフェクトパケット */ + clif_misceffect(&sd->bl,6); /* 他人にも失敗を通知*/ + }else{ + clif_produceeffect(sd,1,nameid);/* 不明なのでとりあえず製造失敗エフェクトパケット */ + clif_misceffect(&sd->bl,2); /* 他人にも失敗を通知*/ + } + } + return 0; +} + +int skill_arrow_create( struct map_session_data *sd,int nameid) +{ + int i,j,flag,index=-1; + struct item tmp_item; + + nullpo_retr(0, sd); + + if(nameid <= 0) + return 1; + + for(i=0;i<MAX_SKILL_ARROW_DB;i++) + if(nameid == skill_arrow_db[i].nameid) { + index = i; + break; + } + + if(index < 0 || (j = pc_search_inventory(sd,nameid)) < 0) + return 1; + + pc_delitem(sd,j,1,0); + for(i=0;i<5;i++) { + memset(&tmp_item,0,sizeof(tmp_item)); + tmp_item.identify = 1; + tmp_item.nameid = skill_arrow_db[index].cre_id[i]; + tmp_item.amount = skill_arrow_db[index].cre_amount[i]; + if(battle_config.making_arrow_name_input) { + tmp_item.card[0]=0x00fe; + tmp_item.card[1]=0; + *((unsigned long *)(&tmp_item.card[2]))=sd->char_id; /* キャラID */ + } + if(tmp_item.nameid <= 0 || tmp_item.amount <= 0) + continue; + if((flag = pc_additem(sd,&tmp_item,tmp_item.amount))) { + clif_additem(sd,0,0,flag); + map_addflooritem(&tmp_item,tmp_item.amount,sd->bl.m,sd->bl.x,sd->bl.y,NULL,NULL,NULL,0); + } + } + + return 0; +} + +/*---------------------------------------------------------------------------- + * 初期化系 + */ + +/*========================================== + * スキル関係ファイル読み込み + * skill_db.txt スキルデータ + * skill_cast_db.txt スキルの詠唱時間とディレイデータ + * produce_db.txt アイテム作成スキル用データ + * create_arrow_db.txt 矢作成スキル用データ + * abra_db.txt アブラカダブラ発動スキルデータ + *------------------------------------------ + */ +int skill_readdb(void) +{ + int i,j,k,l,m; + FILE *fp; + char line[1024],*p; + char *filename[]={"db/produce_db.txt","db/produce_db2.txt"}; + + /* スキルデータベース */ + memset(skill_db,0,sizeof(skill_db)); + fp=fopen("db/skill_db.txt","r"); + if(fp==NULL){ + printf("can't read db/skill_db.txt\n"); + return 1; + } + while(fgets(line,1020,fp)){ + char *split[50], *split2[MAX_SKILL_LEVEL]; + if(line[0]=='/' && line[1]=='/') + continue; + for(j=0,p=line;j<14 && p;j++){ + split[j]=p; + p=strchr(p,','); + if(p) *p++=0; + } + if(split[13]==NULL || j<14) + continue; + + i=atoi(split[0]); + if(i<0 || i>MAX_SKILL_DB) + continue; + +/* printf("skill id=%d\n",i); */ + memset(split2,0,sizeof(split2)); + for(j=0,p=split[1];j<MAX_SKILL_LEVEL && p;j++){ + split2[j]=p; + p=strchr(p,':'); + if(p) *p++=0; + } + for(k=0;k<MAX_SKILL_LEVEL;k++) + skill_db[i].range[k]=(split2[k])? atoi(split2[k]):atoi(split2[0]); + skill_db[i].hit=atoi(split[2]); + skill_db[i].inf=atoi(split[3]); + skill_db[i].pl=atoi(split[4]); + skill_db[i].nk=atoi(split[5]); + skill_db[i].max=atoi(split[6]); + + memset(split2,0,sizeof(split2)); + for(j=0,p=split[7];j<MAX_SKILL_LEVEL && p;j++){ + split2[j]=p; + p=strchr(p,':'); + if(p) *p++=0; + } + for(k=0;k<MAX_SKILL_LEVEL;k++) + skill_db[i].num[k]=(split2[k])? atoi(split2[k]):atoi(split2[0]); + + if(strcmpi(split[8],"yes") == 0) + skill_db[i].castcancel=1; + else + skill_db[i].castcancel=0; + skill_db[i].cast_def_rate=atoi(split[9]); + skill_db[i].inf2=atoi(split[10]); + skill_db[i].maxcount=atoi(split[11]); + if(strcmpi(split[12],"weapon") == 0) + skill_db[i].skill_type=BF_WEAPON; + else if(strcmpi(split[12],"magic") == 0) + skill_db[i].skill_type=BF_MAGIC; + else if(strcmpi(split[12],"misc") == 0) + skill_db[i].skill_type=BF_MISC; + else + skill_db[i].skill_type=0; + memset(split2,0,sizeof(split2)); + for(j=0,p=split[13];j<MAX_SKILL_LEVEL && p;j++){ + split2[j]=p; + p=strchr(p,':'); + if(p) *p++=0; + } + for(k=0;k<MAX_SKILL_LEVEL;k++) + skill_db[i].blewcount[k]=(split2[k])? atoi(split2[k]):atoi(split2[0]); + } + fclose(fp); + printf("read db/skill_db.txt done\n"); + + fp=fopen("db/skill_require_db.txt","r"); + if(fp==NULL){ + printf("can't read db/skill_require_db.txt\n"); + return 1; + } + while(fgets(line,1020,fp)){ + char *split[51], *split2[MAX_SKILL_LEVEL]; + if(line[0]=='/' && line[1]=='/') + continue; + for(j=0,p=line;j<30 && p;j++){ + split[j]=p; + p=strchr(p,','); + if(p) *p++=0; + } + if(split[29]==NULL || j<30) + continue; + + i=atoi(split[0]); + if(i<0 || i>MAX_SKILL_DB) + continue; + + memset(split2,0,sizeof(split2)); + for(j=0,p=split[1];j<MAX_SKILL_LEVEL && p;j++){ + split2[j]=p; + p=strchr(p,':'); + if(p) *p++=0; + } + for(k=0;k<MAX_SKILL_LEVEL;k++) + skill_db[i].hp[k]=(split2[k])? atoi(split2[k]):atoi(split2[0]); + + memset(split2,0,sizeof(split2)); + for(j=0,p=split[2];j<MAX_SKILL_LEVEL && p;j++){ + split2[j]=p; + p=strchr(p,':'); + if(p) *p++=0; + } + for(k=0;k<MAX_SKILL_LEVEL;k++) + skill_db[i].mhp[k]=(split2[k])? atoi(split2[k]):atoi(split2[0]); + + memset(split2,0,sizeof(split2)); + for(j=0,p=split[3];j<MAX_SKILL_LEVEL && p;j++){ + split2[j]=p; + p=strchr(p,':'); + if(p) *p++=0; + } + for(k=0;k<MAX_SKILL_LEVEL;k++) + skill_db[i].sp[k]=(split2[k])? atoi(split2[k]):atoi(split2[0]); + + memset(split2,0,sizeof(split2)); + for(j=0,p=split[4];j<MAX_SKILL_LEVEL && p;j++){ + split2[j]=p; + p=strchr(p,':'); + if(p) *p++=0; + } + for(k=0;k<MAX_SKILL_LEVEL;k++) + skill_db[i].hp_rate[k]=(split2[k])? atoi(split2[k]):atoi(split2[0]); + + memset(split2,0,sizeof(split2)); + for(j=0,p=split[5];j<MAX_SKILL_LEVEL && p;j++){ + split2[j]=p; + p=strchr(p,':'); + if(p) *p++=0; + } + for(k=0;k<MAX_SKILL_LEVEL;k++) + skill_db[i].sp_rate[k]=(split2[k])? atoi(split2[k]):atoi(split2[0]); + + memset(split2,0,sizeof(split2)); + for(j=0,p=split[6];j<MAX_SKILL_LEVEL && p;j++){ + split2[j]=p; + p=strchr(p,':'); + if(p) *p++=0; + } + for(k=0;k<MAX_SKILL_LEVEL;k++) + skill_db[i].zeny[k]=(split2[k])? atoi(split2[k]):atoi(split2[0]); + + memset(split2,0,sizeof(split2)); + for(j=0,p=split[7];j<32 && p;j++){ + split2[j]=p; + p=strchr(p,':'); + if(p) *p++=0; + } + for(k=0;k<32 && split2[k];k++) { + l = atoi(split2[k]); + if(l == 99) { + skill_db[i].weapon = 0xffffffff; + break; + } + else + skill_db[i].weapon |= 1<<l; + } + + if( strcmpi(split[8],"hiding")==0 ) skill_db[i].state=ST_HIDING; + else if( strcmpi(split[8],"cloaking")==0 ) skill_db[i].state=ST_CLOAKING; + else if( strcmpi(split[8],"hidden")==0 ) skill_db[i].state=ST_HIDDEN; + else if( strcmpi(split[8],"riding")==0 ) skill_db[i].state=ST_RIDING; + else if( strcmpi(split[8],"falcon")==0 ) skill_db[i].state=ST_FALCON; + else if( strcmpi(split[8],"cart")==0 ) skill_db[i].state=ST_CART; + else if( strcmpi(split[8],"shield")==0 ) skill_db[i].state=ST_SHIELD; + else if( strcmpi(split[8],"sight")==0 ) skill_db[i].state=ST_SIGHT; + else if( strcmpi(split[8],"explosionspirits")==0 ) skill_db[i].state=ST_EXPLOSIONSPIRITS; + else if( strcmpi(split[8],"recover_weight_rate")==0 ) skill_db[i].state=ST_RECOV_WEIGHT_RATE; + else if( strcmpi(split[8],"move_enable")==0 ) skill_db[i].state=ST_MOVE_ENABLE; + else if( strcmpi(split[8],"water")==0 ) skill_db[i].state=ST_WATER; + else skill_db[i].state=ST_NONE; + + memset(split2,0,sizeof(split2)); + for(j=0,p=split[9];j<MAX_SKILL_LEVEL && p;j++){ + split2[j]=p; + p=strchr(p,':'); + if(p) *p++=0; + } + for(k=0;k<MAX_SKILL_LEVEL;k++) + skill_db[i].spiritball[k]=(split2[k])? atoi(split2[k]):atoi(split2[0]); + skill_db[i].itemid[0]=atoi(split[10]); + skill_db[i].amount[0]=atoi(split[11]); + skill_db[i].itemid[1]=atoi(split[12]); + skill_db[i].amount[1]=atoi(split[13]); + skill_db[i].itemid[2]=atoi(split[14]); + skill_db[i].amount[2]=atoi(split[15]); + skill_db[i].itemid[3]=atoi(split[16]); + skill_db[i].amount[3]=atoi(split[17]); + skill_db[i].itemid[4]=atoi(split[18]); + skill_db[i].amount[4]=atoi(split[19]); + skill_db[i].itemid[5]=atoi(split[20]); + skill_db[i].amount[5]=atoi(split[21]); + skill_db[i].itemid[6]=atoi(split[22]); + skill_db[i].amount[6]=atoi(split[23]); + skill_db[i].itemid[7]=atoi(split[24]); + skill_db[i].amount[7]=atoi(split[25]); + skill_db[i].itemid[8]=atoi(split[26]); + skill_db[i].amount[8]=atoi(split[27]); + skill_db[i].itemid[9]=atoi(split[28]); + skill_db[i].amount[9]=atoi(split[29]); + } + fclose(fp); + printf("read db/skill_require_db.txt done\n"); + + /* キャスティングデータベース */ + fp=fopen("db/skill_cast_db.txt","r"); + if(fp==NULL){ + printf("can't read db/skill_cast_db.txt\n"); + return 1; + } + while(fgets(line,1020,fp)){ + char *split[50], *split2[MAX_SKILL_LEVEL]; + memset(split,0,sizeof(split)); // [Valaris] thanks to fov + if(line[0]=='/' && line[1]=='/') + continue; + for(j=0,p=line;j<5 && p;j++){ + split[j]=p; + p=strchr(p,','); + if(p) *p++=0; + } + if(split[4]==NULL || j<5) + continue; + + i=atoi(split[0]); + if(i<0 || i>MAX_SKILL_DB) + continue; + + memset(split2,0,sizeof(split2)); + for(j=0,p=split[1];j<MAX_SKILL_LEVEL && p;j++){ + split2[j]=p; + p=strchr(p,':'); + if(p) *p++=0; + } + for(k=0;k<MAX_SKILL_LEVEL;k++) + skill_db[i].cast[k]=(split2[k])? atoi(split2[k]):atoi(split2[0]); + + memset(split2,0,sizeof(split2)); + for(j=0,p=split[2];j<MAX_SKILL_LEVEL && p;j++){ + split2[j]=p; + p=strchr(p,':'); + if(p) *p++=0; + } + for(k=0;k<MAX_SKILL_LEVEL;k++) + skill_db[i].delay[k]=(split2[k])? atoi(split2[k]):atoi(split2[0]); + + memset(split2,0,sizeof(split2)); + for(j=0,p=split[3];j<MAX_SKILL_LEVEL && p;j++){ + split2[j]=p; + p=strchr(p,':'); + if(p) *p++=0; + } + for(k=0;k<MAX_SKILL_LEVEL;k++) + skill_db[i].upkeep_time[k]=(split2[k])? atoi(split2[k]):atoi(split2[0]); + + memset(split2,0,sizeof(split2)); + for(j=0,p=split[4];j<MAX_SKILL_LEVEL && p;j++){ + split2[j]=p; + p=strchr(p,':'); + if(p) *p++=0; + } + for(k=0;k<MAX_SKILL_LEVEL;k++) + skill_db[i].upkeep_time2[k]=(split2[k])? atoi(split2[k]):atoi(split2[0]); + } + fclose(fp); + printf("read db/skill_cast_db.txt done\n"); + + /* 製造系スキルデータベース */ + memset(skill_produce_db,0,sizeof(skill_produce_db)); + for(m=0;m<2;m++){ + fp=fopen(filename[m],"r"); + if(fp==NULL){ + if(m>0) + continue; + printf("can't read %s\n",filename[m]); + return 1; + } + k=0; + while(fgets(line,1020,fp)){ + char *split[16]; + int x,y; + if(line[0]=='/' && line[1]=='/') + continue; + memset(split,0,sizeof(split)); + for(j=0,p=line;j<13 && p;j++){ + split[j]=p; + p=strchr(p,','); + if(p) *p++=0; + } + if(split[0]==NULL) + continue; + i=atoi(split[0]); + if(i<=0) + continue; + + skill_produce_db[k].nameid=i; + skill_produce_db[k].itemlv=atoi(split[1]); + skill_produce_db[k].req_skill=atoi(split[2]); + + for(x=3,y=0;split[x] && split[x+1] && y<5;x+=2,y++){ + skill_produce_db[k].mat_id[y]=atoi(split[x]); + skill_produce_db[k].mat_amount[y]=atoi(split[x+1]); + } + k++; + if(k >= MAX_SKILL_PRODUCE_DB) + break; + } + fclose(fp); + printf("read %s done (count=%d)\n",filename[m],k); + } + + memset(skill_arrow_db,0,sizeof(skill_arrow_db)); + fp=fopen("db/create_arrow_db.txt","r"); + if(fp==NULL){ + printf("can't read db/create_arrow_db.txt\n"); + return 1; + } + k=0; + while(fgets(line,1020,fp)){ + char *split[16]; + int x,y; + if(line[0]=='/' && line[1]=='/') + continue; + memset(split,0,sizeof(split)); + for(j=0,p=line;j<13 && p;j++){ + split[j]=p; + p=strchr(p,','); + if(p) *p++=0; + } + if(split[0]==NULL) + continue; + i=atoi(split[0]); + if(i<=0) + continue; + + skill_arrow_db[k].nameid=i; + + for(x=1,y=0;split[x] && split[x+1] && y<5;x+=2,y++){ + skill_arrow_db[k].cre_id[y]=atoi(split[x]); + skill_arrow_db[k].cre_amount[y]=atoi(split[x+1]); + } + k++; + if(k >= MAX_SKILL_ARROW_DB) + break; + } + fclose(fp); + printf("read db/create_arrow_db.txt done (count=%d)\n",k); + + memset(skill_abra_db,0,sizeof(skill_abra_db)); + fp=fopen("db/abra_db.txt","r"); + if(fp==NULL){ + printf("can't read db/abra_db.txt\n"); + return 1; + } + k=0; + while(fgets(line,1020,fp)){ + char *split[16]; + if(line[0]=='/' && line[1]=='/') + continue; + memset(split,0,sizeof(split)); + for(j=0,p=line;j<13 && p;j++){ + split[j]=p; + p=strchr(p,','); + if(p) *p++=0; + } + if(split[0]==NULL) + continue; + i=atoi(split[0]); + if(i<=0) + continue; + + skill_abra_db[i].req_lv=atoi(split[2]); + skill_abra_db[i].per=atoi(split[3]); + + k++; + if(k >= MAX_SKILL_ABRA_DB) + break; + } + fclose(fp); + printf("read db/abra_db.txt done (count=%d)\n",k); + + fp=fopen("db/skill_castnodex_db.txt","r"); + if(fp==NULL){ + printf("can't read db/skill_castnodex_db.txt\n"); + return 1; + } + while(fgets(line,1020,fp)){ + char *split[50], *split2[MAX_SKILL_LEVEL]; + memset(split,0,sizeof(split)); + if(line[0]=='/' && line[1]=='/') + continue; + for(j=0,p=line;j<2 && p;j++){ + split[j]=p; + p=strchr(p,','); + if(p) *p++=0; + } + + i=atoi(split[0]); + if(i<0 || i>MAX_SKILL_DB) + continue; + + memset(split2,0,sizeof(split2)); + for(j=0,p=split[1];j<MAX_SKILL_LEVEL && p;j++){ + split2[j]=p; + p=strchr(p,':'); + if(p) *p++=0; + } + for(k=0;k<MAX_SKILL_LEVEL;k++) + skill_db[i].castnodex[k]=(split2[k])? atoi(split2[k]):atoi(split2[0]); + } + fclose(fp); + printf("read db/skill_castnodex_db.txt done\n"); + + return 0; +} + +void skill_reload(void) +{ + /* + + <empty skill database> + <?> + + */ + + do_init_skill(); +} + +/*========================================== + * スキル関係初期化処理 + *------------------------------------------ + */ +int do_init_skill(void) +{ + skill_readdb(); + + add_timer_func_list(skill_unit_timer,"skill_unit_timer"); + add_timer_func_list(skill_castend_id,"skill_castend_id"); + add_timer_func_list(skill_castend_pos,"skill_castend_pos"); + add_timer_func_list(skill_timerskill,"skill_timerskill"); + add_timer_func_list(skill_status_change_timer,"skill_status_change_timer"); + add_timer_interval(gettick()+SKILLUNITTIMER_INVERVAL,skill_unit_timer,0,0,SKILLUNITTIMER_INVERVAL); + + return 0; +} diff --git a/misc/src/map/skill.h b/misc/src/map/skill.h new file mode 100644 index 0000000..6cb3d88 --- /dev/null +++ b/misc/src/map/skill.h @@ -0,0 +1,842 @@ +// $Id: skill.h,v 1.5 2004/09/25 05:32:19 MouseJstr Exp $ +#ifndef _SKILL_H_ +#define _SKILL_H_ + +#include "map.h" + +#define MAX_SKILL_DB 450 +#define MAX_SKILL_PRODUCE_DB 150 +#define MAX_SKILL_ARROW_DB 150 +#define MAX_SKILL_ABRA_DB 350 + +// スキルデータベース +struct skill_db { + int range[MAX_SKILL_LEVEL],hit,inf,pl,nk,max; + int num[MAX_SKILL_LEVEL]; + int cast[MAX_SKILL_LEVEL],delay[MAX_SKILL_LEVEL]; + int upkeep_time[MAX_SKILL_LEVEL],upkeep_time2[MAX_SKILL_LEVEL]; + int castcancel,cast_def_rate; + int inf2,maxcount,skill_type; + int blewcount[MAX_SKILL_LEVEL]; + int hp[MAX_SKILL_LEVEL],sp[MAX_SKILL_LEVEL],mhp[MAX_SKILL_LEVEL],hp_rate[MAX_SKILL_LEVEL],sp_rate[MAX_SKILL_LEVEL],zeny[MAX_SKILL_LEVEL]; + int weapon,state,spiritball[MAX_SKILL_LEVEL]; + int itemid[10],amount[10]; + int castnodex[MAX_SKILL_LEVEL]; +}; +extern struct skill_db skill_db[MAX_SKILL_DB]; + +struct skill_name_db { + int id; // skill id + char *name; // search strings + char *desc; // description that shows up for search's +}; +extern struct skill_name_db skill_names[]; + +// アイテム作成データベース +struct skill_produce_db { + int nameid, trigger; + int req_skill,itemlv; + int mat_id[5],mat_amount[5]; +}; +extern struct skill_produce_db skill_produce_db[MAX_SKILL_PRODUCE_DB]; + +// 矢作成データベース +struct skill_arrow_db { + int nameid, trigger; + int cre_id[5],cre_amount[5]; +}; +extern struct skill_arrow_db skill_arrow_db[MAX_SKILL_ARROW_DB]; + +// アブラカダブラデータベース +struct skill_abra_db { + int nameid; + int req_lv; + int per; +}; +extern struct skill_abra_db skill_abra_db[MAX_SKILL_ABRA_DB]; + +struct block_list; +struct map_session_data; +struct skill_unit; +struct skill_unit_group; + +int do_init_skill(void); + +// スキルデータベースへのアクセサ +int skill_get_hit( int id ); +int skill_get_inf( int id ); +int skill_get_pl( int id ); +int skill_get_nk( int id ); +int skill_get_max( int id ); +int skill_get_range( int id , int lv ); +int skill_get_hp( int id ,int lv ); +int skill_get_mhp( int id ,int lv ); +int skill_get_sp( int id ,int lv ); +int skill_get_zeny( int id ,int lv ); +int skill_get_num( int id ,int lv ); +int skill_get_cast( int id ,int lv ); +int skill_get_delay( int id ,int lv ); +int skill_get_time( int id ,int lv ); +int skill_get_time2( int id ,int lv ); +int skill_get_castdef( int id ); +int skill_get_weapontype( int id ); +int skill_get_unit_id(int id,int flag); +int skill_get_inf2( int id ); +int skill_get_maxcount( int id ); +int skill_get_blewcount( int id ,int lv ); + +// スキルの使用 +int skill_use_id( struct map_session_data *sd, int target_id, + int skill_num,int skill_lv); +int skill_use_pos( struct map_session_data *sd, + int skill_x, int skill_y, int skill_num, int skill_lv); + +int skill_castend_map( struct map_session_data *sd,int skill_num, const char *map); + +int skill_cleartimerskill(struct block_list *src); +int skill_addtimerskill(struct block_list *src,unsigned int tick,int target,int x,int y,int skill_id,int skill_lv,int type,int flag); + +// 追加効果 +int skill_additional_effect( struct block_list* src, struct block_list *bl,int skillid,int skilllv,int attack_type,unsigned int tick); + +// ユニットスキル +struct skill_unit *skill_initunit(struct skill_unit_group *group,int idx,int x,int y); +int skill_delunit(struct skill_unit *unit); +struct skill_unit_group *skill_initunitgroup(struct block_list *src, + int count,int skillid,int skilllv,int unit_id); +int skill_delunitgroup(struct skill_unit_group *group); +struct skill_unit_group_tickset *skill_unitgrouptickset_search( + struct block_list *bl,int group_id); +int skill_unitgrouptickset_delete(struct block_list *bl,int group_id); +int skill_clear_unitgroup(struct block_list *src); + +int skill_unit_ondamaged(struct skill_unit *src,struct block_list *bl, + int damage,unsigned int tick); + +int skill_castfix( struct block_list *bl, int time ); +int skill_delayfix( struct block_list *bl, int time ); +int skill_check_unit_range(int m,int x,int y,int range,int skillid); +int skill_check_unit_range2(int m,int x,int y,int range); +// -- moonsoul (added skill_check_unit_cell) +int skill_check_unit_cell(int skillid,int m,int x,int y,int unit_id); +int skill_unit_out_all( struct block_list *bl,unsigned int tick,int range); +int skill_unit_move( struct block_list *bl,unsigned int tick,int range); +int skill_unit_move_unit_group( struct skill_unit_group *group, int m,int dx,int dy); + +struct skill_unit_group *skill_check_dancing( struct block_list *src ); +void skill_stop_dancing(struct block_list *src, int flag); + +// 詠唱キャンセル +int skill_castcancel(struct block_list *bl,int type); + +int skill_gangsterparadise(struct map_session_data *sd ,int type); +void skill_brandishspear_first(struct square *tc,int dir,int x,int y); +void skill_brandishspear_dir(struct square *tc,int dir,int are); +int skill_autospell(struct map_session_data *md,int skillid); +void skill_devotion(struct map_session_data *md,int target); +void skill_devotion2(struct block_list *bl,int crusader); +int skill_devotion3(struct block_list *bl,int target); +void skill_devotion_end(struct map_session_data *md,struct map_session_data *sd,int target); + +#define skill_calc_heal(bl,skill_lv) (( battle_get_lv(bl)+battle_get_int(bl) )/8 *(4+ skill_lv*8)) + +// その他 +int skill_check_cloaking(struct block_list *bl); +int skill_is_danceskill(int id); + +// ステータス異常 +int skill_status_change_start(struct block_list *bl,int type,int val1,int val2,int val3,int val4,int tick,int flag); +int skill_status_change_timer(int tid, unsigned int tick, int id, int data); +int skill_encchant_eremental_end(struct block_list *bl, int type); +int skill_status_change_end( struct block_list* bl , int type,int tid ); +int skill_status_change_clear(struct block_list *bl,int type); + + +// アイテム作成 +int skill_can_produce_mix( struct map_session_data *sd, int nameid, int trigger ); +int skill_produce_mix( struct map_session_data *sd, + int nameid, int slot1, int slot2, int slot3 ); + +int skill_arrow_create( struct map_session_data *sd,int nameid); + +// mobスキルのため +int skill_castend_nodamage_id( struct block_list *src, struct block_list *bl,int skillid,int skilllv,unsigned int tick,int flag ); +int skill_castend_damage_id( struct block_list* src, struct block_list *bl,int skillid,int skilllv,unsigned int tick,int flag ); +int skill_castend_pos2( struct block_list *src, int x,int y,int skillid,int skilllv,unsigned int tick,int flag); + +// スキル攻撃一括処理 +int skill_attack( int attack_type, struct block_list* src, struct block_list *dsrc, + struct block_list *bl,int skillid,int skilllv,unsigned int tick,int flag ); + +void skill_reload(void); + +enum { + ST_NONE,ST_HIDING,ST_CLOAKING,ST_HIDDEN,ST_RIDING,ST_FALCON,ST_CART,ST_SHIELD,ST_SIGHT,ST_EXPLOSIONSPIRITS, + ST_RECOV_WEIGHT_RATE,ST_MOVE_ENABLE,ST_WATER, +}; + +enum { // struct map_session_data の status_changeの番号テーブル +// SC_SENDMAX未満はクライアントへの通知あり。 +// 2-2次職の値はなんかめちゃくちゃっぽいので暫定。たぶん変更されます。 + SC_SENDMAX =128, + SC_PROVOKE = 0, + SC_ENDURE = 1, + SC_TWOHANDQUICKEN = 2, + SC_CONCENTRATE = 3, + SC_HIDING = 4, + SC_CLOAKING = 5, + SC_ENCPOISON = 6, + SC_POISONREACT = 7, + SC_QUAGMIRE = 8, + SC_ANGELUS = 9, + SC_BLESSING =10, + SC_SIGNUMCRUCIS =11, + SC_INCREASEAGI =12, + SC_DECREASEAGI =13, + SC_SLOWPOISON =14, + SC_IMPOSITIO =15, + SC_SUFFRAGIUM =16, + SC_ASPERSIO =17, + SC_BENEDICTIO =18, + SC_KYRIE =19, + SC_MAGNIFICAT =20, + SC_GLORIA =21, + SC_AETERNA =22, + SC_ADRENALINE =23, + SC_WEAPONPERFECTION =24, + SC_OVERTHRUST =25, + SC_MAXIMIZEPOWER =26, + SC_RIDING =27, + SC_FALCON =28, + SC_TRICKDEAD =29, + SC_LOUD =30, + SC_ENERGYCOAT =31, + SC_HALLUCINATION =34, + SC_WEIGHT50 =35, + SC_WEIGHT90 =36, + SC_SPEEDPOTION0 =37, + SC_SPEEDPOTION1 =38, + SC_SPEEDPOTION2 =39, + SC_STRIPWEAPON =50, + SC_STRIPSHIELD =51, + SC_STRIPARMOR =52, + SC_STRIPHELM =53, + SC_CP_WEAPON =54, + SC_CP_SHIELD =55, + SC_CP_ARMOR =56, + SC_CP_HELM =57, + SC_AUTOGUARD =58, + SC_REFLECTSHIELD =59, + SC_DEVOTION =60, + SC_PROVIDENCE =61, + SC_DEFENDER =62, + SC_AUTOSPELL =65, + SC_SPEARSQUICKEN =68, + SC_EXPLOSIONSPIRITS =86, + SC_STEELBODY =87, + SC_COMBO =89, + SC_FLAMELAUNCHER =90, + SC_FROSTWEAPON =91, + SC_LIGHTNINGLOADER =92, + SC_SEISMICWEAPON =93, + SC_AURABLADE =103, /* オーラブレード */ + SC_PARRYING =104, /* パリイング */ + SC_CONCENTRATION =105, /* コンセントレーション */ + SC_TENSIONRELAX =106, /* テンションリラックス */ + SC_BERSERK =107, /* バーサーク */ + SC_ASSUMPTIO =110, /* アシャンプティオ */ + SC_MAGICPOWER =113, /* 魔法力増幅 */ + SC_TRUESIGHT =115, /* トゥルーサイト */ + SC_WINDWALK =116, /* ウインドウォーク */ + SC_MELTDOWN =117, /* メルトダウン */ + SC_CARTBOOST =118, /* カートブースト */ + SC_REJECTSWORD =120, /* リジェクトソード */ + SC_MARIONETTE =121, /* マリオネットコントロール */ + SC_HEADCRUSH =124, /* ヘッドクラッシュ */ + SC_JOINTBEAT =125, /* ジョイントビート */ + + SC_STONE =128, + SC_FREEZE =129, + SC_STAN =130, + SC_SLEEP =131, + SC_POISON =132, + SC_CURSE =133, + SC_SILENCE =134, + SC_CONFUSION =135, + SC_BLIND =136, + SC_DIVINA = SC_SILENCE, + + SC_SAFETYWALL =140, + SC_PNEUMA =141, + SC_WATERBALL =142, + SC_ANKLE =143, + SC_DANCING =144, + SC_KEEPING =145, + SC_BARRIER =146, + + SC_MAGICROD =149, + SC_SIGHT =150, + SC_RUWACH =151, + SC_AUTOCOUNTER =152, + SC_VOLCANO =153, + SC_DELUGE =154, + SC_VIOLENTGALE =155, + SC_BLADESTOP_WAIT =156, + SC_BLADESTOP =157, + SC_EXTREMITYFIST =158, + SC_GRAFFITI =159, + + SC_LULLABY =160, + SC_RICHMANKIM =161, + SC_ETERNALCHAOS =162, + SC_DRUMBATTLE =163, + SC_NIBELUNGEN =164, + SC_ROKISWEIL =165, + SC_INTOABYSS =166, + SC_SIEGFRIED =167, + SC_DISSONANCE =168, + SC_WHISTLE =169, + SC_ASSNCROS =170, + SC_POEMBRAGI =171, + SC_APPLEIDUN =172, + SC_UGLYDANCE =173, + SC_HUMMING =174, + SC_DONTFORGETME =175, + SC_FORTUNE =176, + SC_SERVICE4U =177, + + SC_SPIDERWEB =180, /* スパイダーウェッブ */ + SC_MEMORIZE =181, /* メモライズ */ + + SC_WEDDING =187, //結婚用(結婚衣裳になって歩くのが遅いとか) + SC_NOCHAT =188, //赤エモ状態 + SC_SPLASHER =189, /* ベナムスプラッシャー */ + SC_SELFDESTRUCTION =190, /* 自爆 */ + + +// Used by English Team + SC_BROKNARMOR =32, + SC_BROKNWEAPON =33, + SC_SIGHTTRASHER =73, + SC_BASILICA =125, + SC_ENSEMBLE =159, + SC_FOGWALL =178, + SC_GOSPEL =179, + SC_LANDPROTECTOR =182, + SC_ADAPTATION =183, + SC_CHASEWALK =184, + SC_ATKPOT =185, // [Valaris] + SC_MATKPOT =186, // [Valaris] + SC_MINDBREAKER =191, + SC_SPELLBREAKER =192, + +// -- testing various SC effects +// SC_AURABLADE =81, +// SC_CONCENTRATION =83, +// SC_TENSIONRELAX =84, +// SC_BERSERK =85, +// SC_CALLSPIRITS =100, +// SC_PARRYING =100, +// SC_FREECAST =101, +// SC_ABSORBSPIRIT =102, +// SC_ASSUMPTIO =114, +// SC_SHARPSHOOT =127, +// SC_GANGSTER =184, +// SC_CANNIBALIZE =186, +// SC_SPHEREMINE =187, +// SC_METEOSTORM =189, +// SC_CASTCANCEL =190, +// SC_SPIDERWEB =191, +}; +extern int SkillStatusChangeTable[]; + +enum { + NV_BASIC = 1, + + SM_SWORD, + SM_TWOHAND, + SM_RECOVERY, + SM_BASH, + SM_PROVOKE, + SM_MAGNUM, + SM_ENDURE, + + MG_SRECOVERY, + MG_SIGHT, + MG_NAPALMBEAT, + MG_SAFETYWALL, + MG_SOULSTRIKE, + MG_COLDBOLT, + MG_FROSTDIVER, + MG_STONECURSE, + MG_FIREBALL, + MG_FIREWALL, + MG_FIREBOLT, + MG_LIGHTNINGBOLT, + MG_THUNDERSTORM, + + AL_DP, + AL_DEMONBANE, + AL_RUWACH, + AL_PNEUMA, + AL_TELEPORT, + AL_WARP, + AL_HEAL, + AL_INCAGI, + AL_DECAGI, + AL_HOLYWATER, + AL_CRUCIS, + AL_ANGELUS, + AL_BLESSING, + AL_CURE, + + MC_INCCARRY, + MC_DISCOUNT, + MC_OVERCHARGE, + MC_PUSHCART, + MC_IDENTIFY, + MC_VENDING, + MC_MAMMONITE, + + AC_OWL, + AC_VULTURE, + AC_CONCENTRATION, + AC_DOUBLE, + AC_SHOWER, + + TF_DOUBLE, + TF_MISS, + TF_STEAL, + TF_HIDING, + TF_POISON, + TF_DETOXIFY, + + ALL_RESURRECTION, + + KN_SPEARMASTERY, + KN_PIERCE, + KN_BRANDISHSPEAR, + KN_SPEARSTAB, + KN_SPEARBOOMERANG, + KN_TWOHANDQUICKEN, + KN_AUTOCOUNTER, + KN_BOWLINGBASH, + KN_RIDING, + KN_CAVALIERMASTERY, + + PR_MACEMASTERY, + PR_IMPOSITIO, + PR_SUFFRAGIUM, + PR_ASPERSIO, + PR_BENEDICTIO, + PR_SANCTUARY, + PR_SLOWPOISON, + PR_STRECOVERY, + PR_KYRIE, + PR_MAGNIFICAT, + PR_GLORIA, + PR_LEXDIVINA, + PR_TURNUNDEAD, + PR_LEXAETERNA, + PR_MAGNUS, + + WZ_FIREPILLAR, + WZ_SIGHTRASHER, + WZ_FIREIVY, + WZ_METEOR, + WZ_JUPITEL, + WZ_VERMILION, + WZ_WATERBALL, + WZ_ICEWALL, + WZ_FROSTNOVA, + WZ_STORMGUST, + WZ_EARTHSPIKE, + WZ_HEAVENDRIVE, + WZ_QUAGMIRE, + WZ_ESTIMATION, + + BS_IRON, + BS_STEEL, + BS_ENCHANTEDSTONE, + BS_ORIDEOCON, + BS_DAGGER, + BS_SWORD, + BS_TWOHANDSWORD, + BS_AXE, + BS_MACE, + BS_KNUCKLE, + BS_SPEAR, + BS_HILTBINDING, + BS_FINDINGORE, + BS_WEAPONRESEARCH, + BS_REPAIRWEAPON, + BS_SKINTEMPER, + BS_HAMMERFALL, + BS_ADRENALINE, + BS_WEAPONPERFECT, + BS_OVERTHRUST, + BS_MAXIMIZE, + + HT_SKIDTRAP, + HT_LANDMINE, + HT_ANKLESNARE, + HT_SHOCKWAVE, + HT_SANDMAN, + HT_FLASHER, + HT_FREEZINGTRAP, + HT_BLASTMINE, + HT_CLAYMORETRAP, + HT_REMOVETRAP, + HT_TALKIEBOX, + HT_BEASTBANE, + HT_FALCON, + HT_STEELCROW, + HT_BLITZBEAT, + HT_DETECTING, + HT_SPRINGTRAP, + + AS_RIGHT, + AS_LEFT, + AS_KATAR, + AS_CLOAKING, + AS_SONICBLOW, + AS_GRIMTOOTH, + AS_ENCHANTPOISON, + AS_POISONREACT, + AS_VENOMDUST, + AS_SPLASHER, + + NV_FIRSTAID, + NV_TRICKDEAD, + SM_MOVINGRECOVERY, + SM_FATALBLOW, + SM_AUTOBERSERK, + AC_MAKINGARROW, + AC_CHARGEARROW, + TF_SPRINKLESAND, + TF_BACKSLIDING, + TF_PICKSTONE, + TF_THROWSTONE, + MC_CARTREVOLUTION, + MC_CHANGECART, + MC_LOUD, + AL_HOLYLIGHT, + MG_ENERGYCOAT, + + NPC_PIERCINGATT, + NPC_MENTALBREAKER, + NPC_RANGEATTACK, + NPC_ATTRICHANGE, + NPC_CHANGEWATER, + NPC_CHANGEGROUND, + NPC_CHANGEFIRE, + NPC_CHANGEWIND, + NPC_CHANGEPOISON, + NPC_CHANGEHOLY, + NPC_CHANGEDARKNESS, + NPC_CHANGETELEKINESIS, + NPC_CRITICALSLASH, + NPC_COMBOATTACK, + NPC_GUIDEDATTACK, + NPC_SELFDESTRUCTION, + NPC_SPLASHATTACK, + NPC_SUICIDE, + NPC_POISON, + NPC_BLINDATTACK, + NPC_SILENCEATTACK, + NPC_STUNATTACK, + NPC_PETRIFYATTACK, + NPC_CURSEATTACK, + NPC_SLEEPATTACK, + NPC_RANDOMATTACK, + NPC_WATERATTACK, + NPC_GROUNDATTACK, + NPC_FIREATTACK, + NPC_WINDATTACK, + NPC_POISONATTACK, + NPC_HOLYATTACK, + NPC_DARKNESSATTACK, + NPC_TELEKINESISATTACK, + NPC_MAGICALATTACK, + NPC_METAMORPHOSIS, + NPC_PROVOCATION, + NPC_SMOKING, + NPC_SUMMONSLAVE, + NPC_EMOTION, + NPC_TRANSFORMATION, + NPC_BLOODDRAIN, + NPC_ENERGYDRAIN, + NPC_KEEPING, + NPC_DARKBREATH, + NPC_DARKBLESSING, + NPC_BARRIER, + NPC_DEFENDER, + NPC_LICK, + NPC_HALLUCINATION, + NPC_REBIRTH, + NPC_SUMMONMONSTER, + + RG_SNATCHER, + RG_STEALCOIN, + RG_BACKSTAP, + RG_TUNNELDRIVE, + RG_RAID, + RG_STRIPWEAPON, + RG_STRIPSHIELD, + RG_STRIPARMOR, + RG_STRIPHELM, + RG_INTIMIDATE, + RG_GRAFFITI, + RG_FLAGGRAFFITI, + RG_CLEANER, + RG_GANGSTER, + RG_COMPULSION, + RG_PLAGIARISM, + + AM_AXEMASTERY, + AM_LEARNINGPOTION, + AM_PHARMACY, + AM_DEMONSTRATION, + AM_ACIDTERROR, + AM_POTIONPITCHER, + AM_CANNIBALIZE, + AM_SPHEREMINE, + AM_CP_WEAPON, + AM_CP_SHIELD, + AM_CP_ARMOR, + AM_CP_HELM, + AM_BIOETHICS, + AM_BIOTECHNOLOGY, + AM_CREATECREATURE, + AM_CULTIVATION, + AM_FLAMECONTROL, + AM_CALLHOMUN, + AM_REST, + AM_DRILLMASTER, + AM_HEALHOMUN, + AM_RESURRECTHOMUN, + + CR_TRUST, + CR_AUTOGUARD, + CR_SHIELDCHARGE, + CR_SHIELDBOOMERANG, + CR_REFLECTSHIELD, + CR_HOLYCROSS, + CR_GRANDCROSS, + CR_DEVOTION, + CR_PROVIDENCE, + CR_DEFENDER, + CR_SPEARQUICKEN, + + MO_IRONHAND, + MO_SPIRITSRECOVERY, + MO_CALLSPIRITS, + MO_ABSORBSPIRITS, + MO_TRIPLEATTACK, + MO_BODYRELOCATION, + MO_DODGE, + MO_INVESTIGATE, + MO_FINGEROFFENSIVE, + MO_STEELBODY, + MO_BLADESTOP, + MO_EXPLOSIONSPIRITS, + MO_EXTREMITYFIST, + MO_CHAINCOMBO, + MO_COMBOFINISH, + + SA_ADVANCEDBOOK, + SA_CASTCANCEL, + SA_MAGICROD, + SA_SPELLBREAKER, + SA_FREECAST, + SA_AUTOSPELL, + SA_FLAMELAUNCHER, + SA_FROSTWEAPON, + SA_LIGHTNINGLOADER, + SA_SEISMICWEAPON, + SA_DRAGONOLOGY, + SA_VOLCANO, + SA_DELUGE, + SA_VIOLENTGALE, + SA_LANDPROTECTOR, + SA_DISPELL, + SA_ABRACADABRA, + SA_MONOCELL, + SA_CLASSCHANGE, + SA_SUMMONMONSTER, + SA_REVERSEORCISH, + SA_DEATH, + SA_FORTUNE, + SA_TAMINGMONSTER, + SA_QUESTION, + SA_GRAVITY, + SA_LEVELUP, + SA_INSTANTDEATH, + SA_FULLRECOVERY, + SA_COMA, + + BD_ADAPTATION, + BD_ENCORE, + BD_LULLABY, + BD_RICHMANKIM, + BD_ETERNALCHAOS, + BD_DRUMBATTLEFIELD, + BD_RINGNIBELUNGEN, + BD_ROKISWEIL, + BD_INTOABYSS, + BD_SIEGFRIED, + BD_RAGNAROK, + + BA_MUSICALLESSON, + BA_MUSICALSTRIKE, + BA_DISSONANCE, + BA_FROSTJOKE, + BA_WHISTLE, + BA_ASSASSINCROSS, + BA_POEMBRAGI, + BA_APPLEIDUN, + + DC_DANCINGLESSON, + DC_THROWARROW, + DC_UGLYDANCE, + DC_SCREAM, + DC_HUMMING, + DC_DONTFORGETME, + DC_FORTUNEKISS, + DC_SERVICEFORYOU, + + WE_MALE = 334, + WE_FEMALE, + WE_CALLPARTNER, + + NPC_SELFDESTRUCTION2 = 331, + NPC_DARKCROSS = 338, + + LK_AURABLADE = 355, + LK_PARRYING, + LK_CONCENTRATION, + LK_TENSIONRELAX, + LK_BERSERK, + LK_FURY, + HP_ASSUMPTIO, + HP_BASILICA, + HP_MEDITATIO, + HW_SOULDRAIN, + HW_MAGICCRASHER, + HW_MAGICPOWER, + PA_PRESSURE, + PA_SACRIFICE, + PA_GOSPEL, + CH_PALMSTRIKE, + CH_TIGERFIST, + CH_CHAINCRUSH, + PF_HPCONVERSION, + PF_SOULCHANGE, + PF_SOULBURN, + ASC_KATAR, + ASC_HALLUCINATION, + ASC_EDP, + ASC_BREAKER, + SN_SIGHT, + SN_FALCONASSAULT, + SN_SHARPSHOOTING, + SN_WINDWALK, + WS_MELTDOWN, + WS_CREATECOIN, + WS_CREATENUGGET, + WS_CARTBOOST, + WS_SYSTEMCREATE, + ST_CHASEWALK, + ST_REJECTSWORD, + ST_STEALBACKPACK, + CR_ALCHEMY, + CR_SYNTHESISPOTION, + CG_ARROWVULCAN, + CG_MOONLIT, + CG_MARIONETTE, + LK_SPIRALPIERCE, + LK_HEADCRUSH, + LK_JOINTBEAT, + HW_NAPALMVULCAN, + CH_SOULCOLLECT, + PF_MINDBREAKER, + PF_MEMORIZE, + PF_FOGWALL, + PF_SPIDERWEB, + ASC_METEORASSAULT, + ASC_CDP, + WE_BABY, + WE_CALLPARENT, + WE_CALLBABY, + TK_RUN, + TK_READYSTORM, + TK_STORMKICK, + TK_READYDOWN, + TK_DOWNKICK, + TK_READYTURN, + TK_TURNKICK, + TK_READYCOUNTER, + TK_COUNTER, + TK_DODGE, + TK_JUMPKICK, + TK_HPTIME, + TK_SPTIME, + TK_POWER, + TK_SEVENWIND, + TK_HIGHJUMP, + SG_FEEL, + SG_SUN_WARM, + SG_MOON_WARM, + SG_STAR_WARM, + SG_SUN_COMFORT, + SG_MOON_COMFORT, + SG_STAR_COMFORT, + SG_HATE, + SG_SUN_ANGER, + SG_MOON_ANGER, + SG_STAR_ANGER, + SG_SUN_BLESS, + SG_MOON_BLESS, + SG_STAR_BLESS, + SG_DEVIL, + SG_FRIEND, + SG_KNOWLEDGE, + SG_FUSION, + SL_ALCHEMIST, + AM_BERSERKPITCHER, + SL_MONK, + SL_STAR, + SL_SAGE, + SL_CRUSADER, + SL_SUPERNOVICE, + SL_KNIGHT, + SL_WIZARD, + SL_PRIEST, + SL_BARDDANCER, + SL_ROGUE, + SL_ASSASIN, + SL_BLACKSMITH, + BS_ADRENALINE2, + SL_HUNTER, + SL_SOULLINKER, + SL_KAIZEL, + SL_KAAHI, + SL_KAUPE, + SL_KAITE, + SL_KAINA, + SL_STIN, + SL_STUN, + SL_SMA, + SL_SWOO, + SL_SKE, + SL_SKA, + + GD_APPROVAL=10000, + GD_KAFRACONTACT, + GD_GUARDIANRESEARCH, + GD_CHARISMA, + GD_EXTENSION, +}; + +#endif + diff --git a/misc/src/map/storage.c b/misc/src/map/storage.c new file mode 100644 index 0000000..696a74e --- /dev/null +++ b/misc/src/map/storage.c @@ -0,0 +1,576 @@ +// $Id: storage.c,v 1.3 2004/09/25 02:05:22 MouseJstr Exp $ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "db.h" +#include "itemdb.h" +#include "clif.h" +#include "intif.h" +#include "pc.h" +#include "storage.h" +#include "guild.h" +#include "nullpo.h" + +#ifdef MEMWATCH +#include "memwatch.h" +#endif + +static struct dbt *storage_db; +static struct dbt *guild_storage_db; + +/*========================================== + * 倉庫内アイテムソート + *------------------------------------------ + */ +int storage_comp_item(const void *_i1, const void *_i2){ +struct item *i1=(struct item *)_i1; +struct item *i2=(struct item *)_i2; + + if (i1->nameid == i2->nameid) { + return 0; + } else if (!(i1->nameid) || !(i1->amount)){ + return 1; + } else if (!(i2->nameid) || !(i2->amount)){ + return -1; + } else { + return i1->nameid - i2->nameid; + } +} + + +void sortage_sortitem(struct storage* stor){ + nullpo_retv(stor); + + qsort(stor->storage, MAX_STORAGE, sizeof(struct item), storage_comp_item); +} + +void sortage_gsortitem(struct guild_storage* gstor){ + nullpo_retv(gstor); + + qsort(gstor->storage, MAX_GUILD_STORAGE, sizeof(struct item), storage_comp_item); +} + +/*========================================== + * 初期化とか + *------------------------------------------ + */ +int do_init_storage(void) // map.c::do_init()から呼ばれる +{ + storage_db=numdb_init(); + guild_storage_db=numdb_init(); + return 1; +} + +void do_final_storage(void) // map.c::do_final()から呼ばれる +{ +} + +struct storage *account2storage(int account_id) +{ + struct storage *stor; + stor=numdb_search(storage_db,account_id); + if(stor == NULL) { + stor = calloc(sizeof(struct storage), 1); + if(stor == NULL){ + printf("storage: out of memory!\n"); + exit(0); + } + memset(stor,0,sizeof(struct storage)); + stor->account_id=account_id; + numdb_insert(storage_db,stor->account_id,stor); + } + return stor; +} + +// Just to ask storage, without creation +struct storage *account2storage2(int account_id) { + return numdb_search(storage_db, account_id); +} + +int storage_delete(int account_id) +{ + struct storage *stor = numdb_search(storage_db,account_id); + if(stor) { + numdb_erase(storage_db,account_id); + free(stor); + } + return 0; +} + +/*========================================== + * カプラ倉庫を開く + *------------------------------------------ + */ +int storage_storageopen(struct map_session_data *sd) +{ + struct storage *stor; + + nullpo_retr(0, sd); + + if((stor = numdb_search(storage_db,sd->status.account_id)) != NULL) { + stor->storage_status = 1; + sd->state.storage_flag = 0; + clif_storageitemlist(sd,stor); + clif_storageequiplist(sd,stor); + clif_updatestorageamount(sd,stor); + return 0; + } else + intif_request_storage(sd->status.account_id); + + return 1; +} + +/*========================================== + * カプラ倉庫へアイテム追加 + *------------------------------------------ + */ +int storage_additem(struct map_session_data *sd,struct storage *stor,struct item *item_data,int amount) +{ + struct item_data *data; + int i; + + nullpo_retr(1, sd); + nullpo_retr(1, stor); + nullpo_retr(1, item_data); + + if(item_data->nameid <= 0 || amount <= 0) + return 1; + nullpo_retr(1, data = itemdb_search(item_data->nameid)); + + i=MAX_STORAGE; + if(!itemdb_isequip2(data)){ + // 装備品ではないので、既所有品なら個数のみ変化させる + for(i=0;i<MAX_STORAGE;i++){ + if(stor->storage[i].nameid == item_data->nameid && + stor->storage[i].card[0] == item_data->card[0] && stor->storage[i].card[1] == item_data->card[1] && + stor->storage[i].card[2] == item_data->card[2] && stor->storage[i].card[3] == item_data->card[3]){ + if(stor->storage[i].amount+amount > MAX_AMOUNT) + return 1; + stor->storage[i].amount+=amount; + clif_storageitemadded(sd,stor,i,amount); + break; + } + } + } + if(i>=MAX_STORAGE){ + // 装備品か未所有品だったので空き欄へ追加 + for(i=0;i<MAX_STORAGE;i++){ + if(stor->storage[i].nameid==0){ + memcpy(&stor->storage[i],item_data,sizeof(stor->storage[0])); + stor->storage[i].amount=amount; + stor->storage_amount++; + clif_storageitemadded(sd,stor,i,amount); + clif_updatestorageamount(sd,stor); + break; + } + } + if(i>=MAX_STORAGE) + return 1; + } + return 0; +} +/*========================================== + * カプラ倉庫アイテムを減らす + *------------------------------------------ + */ +int storage_delitem(struct map_session_data *sd,struct storage *stor,int n,int amount) +{ + nullpo_retr(1, sd); + nullpo_retr(1, stor); + + if(stor->storage[n].nameid==0 || stor->storage[n].amount<amount) + return 1; + + stor->storage[n].amount-=amount; + if(stor->storage[n].amount==0){ + memset(&stor->storage[n],0,sizeof(stor->storage[0])); + stor->storage_amount--; + clif_updatestorageamount(sd,stor); + } + clif_storageitemremoved(sd,n,amount); + + return 0; +} +/*========================================== + * カプラ倉庫へ入れる + *------------------------------------------ + */ +int storage_storageadd(struct map_session_data *sd,int index,int amount) +{ + struct storage *stor; + + nullpo_retr(0, sd); + nullpo_retr(0, stor=account2storage(sd->status.account_id)); + + if( (stor->storage_amount <= MAX_STORAGE) && (stor->storage_status == 1) ) { // storage not full & storage open + if(index>=0 && index<MAX_INVENTORY) { // valid index + if( (amount <= sd->status.inventory[index].amount) && (amount > 0) ) { //valid amount + if(storage_additem(sd,stor,&sd->status.inventory[index],amount)==0) + // remove item from inventory + pc_delitem(sd,index,amount,0); + } // valid amount + }// valid index + }// storage not full & storage open + + return 0; +} + +/*========================================== + * カプラ倉庫から出す + *------------------------------------------ + */ +int storage_storageget(struct map_session_data *sd,int index,int amount) +{ + struct storage *stor; + int flag; + + nullpo_retr(0, sd); + nullpo_retr(0, stor=account2storage(sd->status.account_id)); + + if(stor->storage_status == 1) { // storage open + if(index>=0 && index<MAX_STORAGE) { // valid index + if( (amount <= stor->storage[index].amount) && (amount > 0) ) { //valid amount + if((flag = pc_additem(sd,&stor->storage[index],amount)) == 0) + storage_delitem(sd,stor,index,amount); + else + clif_additem(sd,0,0,flag); + } // valid amount + }// valid index + }// storage open + + return 0; +} +/*========================================== + * カプラ倉庫へカートから入れる + *------------------------------------------ + */ +int storage_storageaddfromcart(struct map_session_data *sd,int index,int amount) +{ + struct storage *stor; + + nullpo_retr(0, sd); + nullpo_retr(0, stor=account2storage(sd->status.account_id)); + + if( (stor->storage_amount <= MAX_STORAGE) && (stor->storage_status == 1) ) { // storage not full & storage open + if(index>=0 && index<MAX_INVENTORY) { // valid index + if( (amount <= sd->status.cart[index].amount) && (amount > 0) ) { //valid amount + if(storage_additem(sd,stor,&sd->status.cart[index],amount)==0) + pc_cart_delitem(sd,index,amount,0); + } // valid amount + }// valid index + }// storage not full & storage open + + return 0; +} + +/*========================================== + * カプラ倉庫からカートへ出す + *------------------------------------------ + */ +int storage_storagegettocart(struct map_session_data *sd,int index,int amount) +{ + struct storage *stor; + + nullpo_retr(0, sd); + nullpo_retr(0, stor=account2storage(sd->status.account_id)); + + if(stor->storage_status == 1) { // storage open + if(index>=0 && index<MAX_STORAGE) { // valid index + if( (amount <= stor->storage[index].amount) && (amount > 0) ) { //valid amount + if(pc_cart_additem(sd,&stor->storage[index],amount)==0){ + storage_delitem(sd,stor,index,amount); + } + } // valid amount + }// valid index + }// storage open + + return 0; +} + + +/*========================================== + * カプラ倉庫を閉じる + *------------------------------------------ + */ +int storage_storageclose(struct map_session_data *sd) +{ + struct storage *stor; + + nullpo_retr(0, sd); + nullpo_retr(0, stor=account2storage(sd->status.account_id)); + + stor->storage_status=0; + sd->state.storage_flag = 0; + clif_storageclose(sd); + + sortage_sortitem(stor); + return 0; +} + +/*========================================== + * ログアウト時開いているカプラ倉庫の保存 + *------------------------------------------ + */ +int storage_storage_quit(struct map_session_data *sd) +{ + struct storage *stor; + + nullpo_retr(0, sd); + + stor = numdb_search(storage_db,sd->status.account_id); + if(stor) stor->storage_status = 0; + + return 0; +} + +int storage_storage_save(struct map_session_data *sd) +{ + struct storage *stor; + + nullpo_retr(0, sd); + + stor=numdb_search(storage_db,sd->status.account_id); + if(stor) intif_send_storage(stor); + + return 0; +} + +struct guild_storage *guild2storage(int guild_id) +{ + struct guild_storage *gs = NULL; + if(guild_search(guild_id) != NULL) { + gs=numdb_search(guild_storage_db,guild_id); + if(gs == NULL) { + gs = calloc(sizeof(struct guild_storage), 1); + if(gs==NULL){ + printf("storage: out of memory!\n"); + exit(0); + } + gs->guild_id=guild_id; + numdb_insert(guild_storage_db,gs->guild_id,gs); + } + } + return gs; +} + +int guild_storage_delete(int guild_id) +{ + struct guild_storage *gstor = numdb_search(guild_storage_db,guild_id); + if(gstor) { + numdb_erase(guild_storage_db,guild_id); + free(gstor); + } + return 0; +} + +int storage_guild_storageopen(struct map_session_data *sd) +{ + struct guild_storage *gstor; + + nullpo_retr(0, sd); + + if(sd->status.guild_id <= 0) + return 2; + if((gstor = numdb_search(guild_storage_db,sd->status.guild_id)) != NULL) { + if(gstor->storage_status) + return 1; + gstor->storage_status = 1; + sd->state.storage_flag = 1; + clif_guildstorageitemlist(sd,gstor); + clif_guildstorageequiplist(sd,gstor); + clif_updateguildstorageamount(sd,gstor); + return 0; + } + else { + gstor = guild2storage(sd->status.guild_id); + gstor->storage_status = 1; + intif_request_guild_storage(sd->status.account_id,sd->status.guild_id); + } + + return 0; +} + +int guild_storage_additem(struct map_session_data *sd,struct guild_storage *stor,struct item *item_data,int amount) +{ + struct item_data *data; + int i; + + nullpo_retr(1, sd); + nullpo_retr(1, stor); + nullpo_retr(1, item_data); + nullpo_retr(1, data = itemdb_search(item_data->nameid)); + + if(item_data->nameid <= 0 || amount <= 0) + return 1; + + i=MAX_GUILD_STORAGE; + if(!itemdb_isequip2(data)){ + // 装備品ではないので、既所有品なら個数のみ変化させる + for(i=0;i<MAX_GUILD_STORAGE;i++){ + if(stor->storage[i].nameid == item_data->nameid && + stor->storage[i].card[0] == item_data->card[0] && stor->storage[i].card[1] == item_data->card[1] && + stor->storage[i].card[2] == item_data->card[2] && stor->storage[i].card[3] == item_data->card[3]){ + if(stor->storage[i].amount+amount > MAX_AMOUNT) + return 1; + stor->storage[i].amount+=amount; + clif_guildstorageitemadded(sd,stor,i,amount); + break; + } + } + } + if(i>=MAX_GUILD_STORAGE){ + // 装備品か未所有品だったので空き欄へ追加 + for(i=0;i<MAX_GUILD_STORAGE;i++){ + if(stor->storage[i].nameid==0){ + memcpy(&stor->storage[i],item_data,sizeof(stor->storage[0])); + stor->storage[i].amount=amount; + stor->storage_amount++; + clif_guildstorageitemadded(sd,stor,i,amount); + clif_updateguildstorageamount(sd,stor); + break; + } + } + if(i>=MAX_GUILD_STORAGE) + return 1; + } + return 0; +} + +int guild_storage_delitem(struct map_session_data *sd,struct guild_storage *stor,int n,int amount) +{ + nullpo_retr(1, sd); + nullpo_retr(1, stor); + + if(stor->storage[n].nameid==0 || stor->storage[n].amount<amount) + return 1; + + stor->storage[n].amount-=amount; + if(stor->storage[n].amount==0){ + memset(&stor->storage[n],0,sizeof(stor->storage[0])); + stor->storage_amount--; + clif_updateguildstorageamount(sd,stor); + } + clif_storageitemremoved(sd,n,amount); + + return 0; +} + +int storage_guild_storageadd(struct map_session_data *sd,int index,int amount) +{ + struct guild_storage *stor; + + nullpo_retr(0, sd); + + if((stor=guild2storage(sd->status.guild_id)) != NULL) { + if( (stor->storage_amount <= MAX_GUILD_STORAGE) && (stor->storage_status == 1) ) { // storage not full & storage open + if(index>=0 && index<MAX_INVENTORY) { // valid index + if( (amount <= sd->status.inventory[index].amount) && (amount > 0) ) { //valid amount + if(guild_storage_additem(sd,stor,&sd->status.inventory[index],amount)==0) + // remove item from inventory + pc_delitem(sd,index,amount,0); + } // valid amount + }// valid index + }// storage not full & storage open + } + + return 0; +} + +int storage_guild_storageget(struct map_session_data *sd,int index,int amount) +{ + struct guild_storage *stor; + int flag; + + nullpo_retr(0, sd); + + if((stor=guild2storage(sd->status.guild_id)) != NULL) { + if(stor->storage_status == 1) { // storage open + if(index>=0 && index<MAX_GUILD_STORAGE) { // valid index + if( (amount <= stor->storage[index].amount) && (amount > 0) ) { //valid amount + if((flag = pc_additem(sd,&stor->storage[index],amount)) == 0) + guild_storage_delitem(sd,stor,index,amount); + else + clif_additem(sd,0,0,flag); + } // valid amount + }// valid index + }// storage open + } + + return 0; +} + +int storage_guild_storageaddfromcart(struct map_session_data *sd,int index,int amount) +{ + struct guild_storage *stor; + + nullpo_retr(0, sd); + + if((stor=guild2storage(sd->status.guild_id)) != NULL) { + if( (stor->storage_amount <= MAX_GUILD_STORAGE) && (stor->storage_status == 1) ) { // storage not full & storage open + if(index>=0 && index<MAX_INVENTORY) { // valid index + if( (amount <= sd->status.cart[index].amount) && (amount > 0) ) { //valid amount + if(guild_storage_additem(sd,stor,&sd->status.cart[index],amount)==0) + pc_cart_delitem(sd,index,amount,0); + } // valid amount + }// valid index + }// storage not full & storage open + } + + return 0; +} + +int storage_guild_storagegettocart(struct map_session_data *sd,int index,int amount) +{ + struct guild_storage *stor; + + nullpo_retr(0, sd); + + if((stor=guild2storage(sd->status.guild_id)) != NULL) { + if(stor->storage_status == 1) { // storage open + if(index>=0 && index<MAX_GUILD_STORAGE) { // valid index + if( (amount <= stor->storage[index].amount) && (amount > 0) ) { //valid amount + if(pc_cart_additem(sd,&stor->storage[index],amount)==0){ + guild_storage_delitem(sd,stor,index,amount); + } + } // valid amount + }// valid index + }// storage open + } + + return 0; +} + +int storage_guild_storageclose(struct map_session_data *sd) +{ + struct guild_storage *stor; + + nullpo_retr(0, sd); + + if((stor=guild2storage(sd->status.guild_id)) != NULL) { + intif_send_guild_storage(sd->status.account_id,stor); + stor->storage_status = 0; + sd->state.storage_flag = 0; + sortage_gsortitem(stor); + } + clif_storageclose(sd); + + return 0; +} + +int storage_guild_storage_quit(struct map_session_data *sd,int flag) +{ + struct guild_storage *stor; + + nullpo_retr(0, sd); + + stor = numdb_search(guild_storage_db,sd->status.guild_id); + if(stor) { + if(!flag) + intif_send_guild_storage(sd->status.account_id,stor); + stor->storage_status = 0; + sd->state.storage_flag = 0; + } + + return 0; +} diff --git a/misc/src/map/storage.h b/misc/src/map/storage.h new file mode 100644 index 0000000..489741c --- /dev/null +++ b/misc/src/map/storage.h @@ -0,0 +1,38 @@ +// $Id: storage.h,v 1.3 2004/09/25 05:32:19 MouseJstr Exp $ +#ifndef _STORAGE_H_ +#define _STORAGE_H_ + +#include "mmo.h" + +int storage_storageopen(struct map_session_data *sd); +int storage_storageadd(struct map_session_data *sd,int index,int amount); +int storage_storageget(struct map_session_data *sd,int index,int amount); +int storage_storageaddfromcart(struct map_session_data *sd,int index,int amount); +int storage_storagegettocart(struct map_session_data *sd,int index,int amount); +int storage_storageclose(struct map_session_data *sd); +int do_init_storage(void); +void do_final_storage(void); +struct storage *account2storage(int account_id); +struct storage *account2storage2(int account_id); +int storage_delete(int account_id); +int storage_storage_quit(struct map_session_data *sd); +int storage_storage_save(struct map_session_data *sd); + +struct guild_storage *guild2storage(int guild_id); +int guild_storage_delete(int guild_id); +int storage_guild_storageopen(struct map_session_data *sd); +int guild_storage_additem(struct map_session_data *sd,struct guild_storage *stor,struct item *item_data,int amount); +int guild_storage_delitem(struct map_session_data *sd,struct guild_storage *stor,int n,int amount); +int storage_guild_storageadd(struct map_session_data *sd,int index,int amount); +int storage_guild_storageget(struct map_session_data *sd,int index,int amount); +int storage_guild_storageaddfromcart(struct map_session_data *sd,int index,int amount); +int storage_guild_storagegettocart(struct map_session_data *sd,int index,int amount); +int storage_guild_storageclose(struct map_session_data *sd); +int storage_guild_storage_quit(struct map_session_data *sd,int flag); + +int storage_comp_item(const void *_i1, const void *_i2); +//int storage_comp_item(const struct item* i1, const struct item* i2); +void sortage_sortitem(struct storage* stor); +void sortage_gsortitem(struct guild_storage* gstor); + +#endif diff --git a/misc/src/map/trade.c b/misc/src/map/trade.c new file mode 100644 index 0000000..da43d67 --- /dev/null +++ b/misc/src/map/trade.c @@ -0,0 +1,255 @@ +#include <stdio.h> +#include <string.h> + +#include "clif.h" +#include "itemdb.h" +#include "map.h" +#include "trade.h" +#include "pc.h" +#include "npc.h" +#include "battle.h" +#include "nullpo.h" + +/*========================================== + * 取引要請を相手に送る + *------------------------------------------ + */ +void trade_traderequest(struct map_session_data *sd,int target_id) +{ + struct map_session_data *target_sd; + + nullpo_retv(sd); + + if((target_sd = map_id2sd(target_id)) != NULL){ + if(!battle_config.invite_request_check) { + if(target_sd->guild_invite>0 || target_sd->party_invite>0){ + clif_tradestart(sd,2); // 相手はPT要請中かGuild要請中 + return; + } + } + if((target_sd->trade_partner !=0) || (sd->trade_partner !=0)) { + trade_tradecancel(sd); //person is in another trade + } + else{ + if(sd->bl.m != target_sd->bl.m + || (sd->bl.x - target_sd->bl.x <= -5 || sd->bl.x - target_sd->bl.x >= 5) + || (sd->bl.y - target_sd->bl.y <= -5 || sd->bl.y - target_sd->bl.y >= 5)) { + clif_tradestart(sd,0); //too far + } + else if(sd!=target_sd) { + target_sd->trade_partner = sd->status.account_id; + sd->trade_partner = target_sd->status.account_id; + clif_traderequest(target_sd,sd->status.name); + } + } + } + else{ + clif_tradestart(sd,1); //character does not exist + } +} + +/*========================================== + * 取引要請 + *------------------------------------------ + */ +void trade_tradeack(struct map_session_data *sd,int type) +{ + struct map_session_data *target_sd; + + nullpo_retv(sd); + + if((target_sd = map_id2sd(sd->trade_partner)) != NULL){ + clif_tradestart(target_sd,type); + clif_tradestart(sd,type); + if(type == 4){ // Cancel + sd->deal_locked =0; + sd->trade_partner=0; + target_sd->deal_locked=0; + target_sd->trade_partner=0; + } + if(sd->npc_id != 0) + npc_event_dequeue(sd); + if(target_sd->npc_id != 0) + npc_event_dequeue(target_sd); + } +} + +/*========================================== + * アイテム追加 + *------------------------------------------ + */ +void trade_tradeadditem(struct map_session_data *sd,int index,int amount) +{ + struct map_session_data *target_sd; + int trade_i; + int trade_weight=0; + int c; + + nullpo_retv(sd); + + if(((target_sd = map_id2sd(sd->trade_partner)) != NULL) && (sd->deal_locked < 1)){ + if(index<2 || index>=MAX_INVENTORY+2){ + if(index == 0 && amount > 0 && amount <= sd->status.zeny){ + sd->deal_zeny=amount; + clif_tradeadditem(sd,target_sd,0,amount); + } + }else if(amount <= sd->status.inventory[index-2].amount && amount > 0){ + for(trade_i=0; trade_i<10;trade_i++){ + if(sd->deal_item_amount[trade_i] == 0){ + trade_weight+=sd->inventory_data[index-2]->weight*amount; + if(target_sd->weight + trade_weight > target_sd->max_weight){ + clif_tradeitemok(sd,index,0,1); //fail to add item -- the player was over weighted. + amount = 0; // [MouseJstr] + }else{ + for(c=0; c==trade_i-1;c++){ // re-deal exploit protection [Valaris] + if(sd->deal_item_index[c]==index) { + trade_tradecancel(sd); + return; + } + } + sd->deal_item_index[trade_i] =index; + sd->deal_item_amount[trade_i]+=amount; + clif_tradeitemok(sd,index,amount,0); //success to add item + clif_tradeadditem(sd,target_sd,index,amount); + } + break; + }else{ + trade_weight+=sd->inventory_data[sd->deal_item_index[trade_i]-2]->weight*sd->deal_item_amount[trade_i]; + } + } + } + } +} + +/*========================================== + * アイテム追加完了(ok押し) + *------------------------------------------ + */ +void trade_tradeok(struct map_session_data *sd) +{ + struct map_session_data *target_sd; + int trade_i; + + nullpo_retv(sd); + + for(trade_i=0;trade_i<10;trade_i++) { + if(sd->deal_item_amount[trade_i]>sd->status.inventory[sd->deal_item_index[trade_i]-2].amount || + sd->deal_item_amount[trade_i]<0) { + trade_tradecancel(sd); + return; + } + + } + + if((target_sd = map_id2sd(sd->trade_partner)) != NULL){ + sd->deal_locked=1; + clif_tradeitemok(sd,0,0,0); + clif_tradedeal_lock(sd,0); + clif_tradedeal_lock(target_sd,1); + } +} + +/*========================================== + * 取引キャンセル + *------------------------------------------ + */ +void trade_tradecancel(struct map_session_data *sd) +{ + struct map_session_data *target_sd; + int trade_i; + + nullpo_retv(sd); + + if((target_sd = map_id2sd(sd->trade_partner)) != NULL){ + for(trade_i=0; trade_i<10;trade_i++) { //give items back (only virtual) + if(sd->deal_item_amount[trade_i] != 0) { + clif_additem(sd,sd->deal_item_index[trade_i]-2,sd->deal_item_amount[trade_i],0); + sd->deal_item_index[trade_i] =0; + sd->deal_item_amount[trade_i]=0; + } + if(target_sd->deal_item_amount[trade_i] != 0) { + clif_additem(target_sd,target_sd->deal_item_index[trade_i]-2,target_sd->deal_item_amount[trade_i],0); + target_sd->deal_item_index[trade_i] =0; + target_sd->deal_item_amount[trade_i]=0; + } + } + if(sd->deal_zeny) { + clif_updatestatus(sd,SP_ZENY); + sd->deal_zeny=0; + } + if(target_sd->deal_zeny) { + clif_updatestatus(target_sd,SP_ZENY); + target_sd->deal_zeny=0; + } + sd->deal_locked =0; + sd->trade_partner=0; + target_sd->deal_locked=0; + target_sd->trade_partner=0; + clif_tradecancelled(sd); + clif_tradecancelled(target_sd); + } +} + +/*========================================== + * 取引許諾(trade押し) + *------------------------------------------ + */ +void trade_tradecommit(struct map_session_data *sd) +{ + struct map_session_data *target_sd; + int trade_i; + + nullpo_retv(sd); + + if((target_sd = map_id2sd(sd->trade_partner)) != NULL){ + if( (sd->deal_locked >=1) && (target_sd->deal_locked >=1) ){ // both have pressed 'ok' + if(sd->deal_locked < 2) {sd->deal_locked=2;} // set locked to 2 + if(target_sd->deal_locked==2) { // the other one pressed 'trade' too + for(trade_i=0; trade_i<10;trade_i++) { + if(sd->deal_item_amount[trade_i] != 0) { + int n=sd->deal_item_index[trade_i]-2; + int flag; + flag = pc_additem(target_sd,&sd->status.inventory[n],sd->deal_item_amount[trade_i]); + if(flag==0) + pc_delitem(sd,n,sd->deal_item_amount[trade_i],1); + else + clif_additem(sd,n,sd->deal_item_amount[trade_i],0); + sd->deal_item_index[trade_i] =0; + sd->deal_item_amount[trade_i]=0; + } + if(target_sd->deal_item_amount[trade_i] != 0) { + int n=target_sd->deal_item_index[trade_i]-2; + int flag; + flag = pc_additem(sd,&target_sd->status.inventory[n],target_sd->deal_item_amount[trade_i]); + if(flag==0) + pc_delitem(target_sd,n,target_sd->deal_item_amount[trade_i],1); + else + clif_additem(target_sd,n,target_sd->deal_item_amount[trade_i],0); + target_sd->deal_item_index[trade_i] =0; + target_sd->deal_item_amount[trade_i]=0; + } + } + if(sd->deal_zeny) { + sd->status.zeny -= sd->deal_zeny; + clif_updatestatus(sd,SP_ZENY); + target_sd->status.zeny += sd->deal_zeny; + clif_updatestatus(target_sd,SP_ZENY); + sd->deal_zeny=0; + } + if(target_sd->deal_zeny) { + target_sd->status.zeny -= target_sd->deal_zeny; + clif_updatestatus(target_sd,SP_ZENY); + sd->status.zeny += target_sd->deal_zeny; + clif_updatestatus(sd,SP_ZENY); + target_sd->deal_zeny=0; + } + sd->deal_locked =0; + sd->trade_partner=0; + target_sd->deal_locked=0; + target_sd->trade_partner=0; + clif_tradecompleted(sd,0); + clif_tradecompleted(target_sd,0); + } + } + } +} diff --git a/misc/src/map/trade.h b/misc/src/map/trade.h new file mode 100644 index 0000000..01cbce7 --- /dev/null +++ b/misc/src/map/trade.h @@ -0,0 +1,13 @@ +// $Id: trade.h,v 1.2 2004/09/25 05:32:19 MouseJstr Exp $ +#ifndef _TRADE_H_ +#define _TRADE_H_ + +#include "map.h" +void trade_traderequest(struct map_session_data *sd,int target_id); +void trade_tradeack(struct map_session_data *sd,int type); +void trade_tradeadditem(struct map_session_data *sd,int index,int amount); +void trade_tradeok(struct map_session_data *sd); +void trade_tradecancel(struct map_session_data *sd); +void trade_tradecommit(struct map_session_data *sd); + +#endif // _TRADE_H_ diff --git a/misc/src/map/vending.c b/misc/src/map/vending.c new file mode 100644 index 0000000..189584d --- /dev/null +++ b/misc/src/map/vending.c @@ -0,0 +1,163 @@ +// $Id: vending.c,v 1.2 2004/09/25 05:32:19 MouseJstr Exp $ +#include <stdio.h> +#include <string.h> + +#include "clif.h" +#include "itemdb.h" +#include "map.h" +#include "vending.h" +#include "pc.h" +#include "skill.h" +#include "battle.h" +#include "nullpo.h" + +/*========================================== + * 露店閉鎖 + *------------------------------------------ +*/ +void vending_closevending(struct map_session_data *sd) +{ + + nullpo_retv(sd); + + sd->vender_id=0; + clif_closevendingboard(&sd->bl,0); +} + +/*========================================== + * 露店アイテムリスト要求 + *------------------------------------------ + */ +void vending_vendinglistreq(struct map_session_data *sd,int id) +{ + struct map_session_data *vsd; + + nullpo_retv(sd); + + if( (vsd=map_id2sd(id)) == NULL ) + return; + if(vsd->vender_id==0) + return; + clif_vendinglist(sd,id,vsd->vending); +} + +/*========================================== + * 露店アイテム購入 + *------------------------------------------ + */ +void vending_purchasereq(struct map_session_data *sd,int len,int id,unsigned char *p) +{ + int i,j,w,z,new=0,blank,vend_list[12]; + short amount,index; + struct map_session_data *vsd=map_id2sd(id); + + nullpo_retv(sd); + + blank=pc_inventoryblank(sd); + + if(vsd==NULL) + return; + if(vsd->vender_id==0) + return; + if(vsd->vender_id==sd->bl.id) + return; + for(i=0,w=z=0;8+4*i<len;i++){ + amount=*(short*)(p+4*i); + index=*(short*)(p+2+4*i)-2; +/* + if(amount < 0) return; //add + for(j=0;j<vsd->vend_num;j++) + if(0<vsd->vending[j].amount && amount<=vsd->vending[j].amount && vsd->vending[j].index==index) + break; +*/ +//ADD_start + for(j=0;j < vsd->vend_num;j++) { + if(0 < vsd->vending[j].amount && vsd->vending[j].index==index) { + if(amount > vsd->vending[j].amount || amount <= 0) { + clif_buyvending(sd,index,vsd->vending[j].amount,4); + return; + } + if(amount <= vsd->vending[j].amount) break; + } + } +//ADD_end + if(j==vsd->vend_num) + return; // 売り切れ + vend_list[i]=j; + z+=vsd->vending[j].value*amount; + if(z > sd->status.zeny){ + clif_buyvending(sd,index,amount,1); + return; // zeny不足 + } + w+=itemdb_weight(vsd->status.cart[index].nameid)*amount; + if(w+sd->weight > sd->max_weight){ + clif_buyvending(sd,index,amount,2); + return; // 重量超過 + } + switch(pc_checkadditem(sd,vsd->status.cart[index].nameid,amount)){ + case ADDITEM_EXIST: + break; + case ADDITEM_NEW: + new++; + if(new > blank) + return; // 種類数超過 + break; + case ADDITEM_OVERAMOUNT: + return; // アイテム数超過 + } + } + if(z < 0 || z > MAX_ZENY){ //Zeny Bug Fixed by Darkchild + clif_tradecancelled(sd); + clif_tradecancelled(vsd); + return; + } + pc_payzeny(sd,z); + pc_getzeny(vsd,z); + for(i=0;8+4*i<len;i++){ + amount=*(short*)(p+4*i); + index=*(short*)(p+2+4*i)-2; + if(amount < 0) break; //add + pc_additem(sd,&vsd->status.cart[index],amount); + vsd->vending[vend_list[i]].amount-=amount; + pc_cart_delitem(vsd,index,amount,0); + clif_vendingreport(vsd,index,amount); + } +} + +/*========================================== + * 露店開設 + *------------------------------------------ + */ +void vending_openvending(struct map_session_data *sd,int len,char *message,int flag,unsigned char *p) +{ + int i; + + nullpo_retv(sd); + + if(!pc_checkskill(sd,MC_VENDING) || !pc_iscarton(sd)) { // cart skill and cart check [Valaris] + clif_skill_fail(sd,MC_VENDING,0,0); + return; + } + + if(flag){ + for(i=0;85+8*i<len;i++){ + sd->vending[i].index=*(short*)(p+8*i)-2; + sd->vending[i].amount=*(short*)(p+2+8*i); + sd->vending[i].value=*(int*)(p+4+8*i); + if(sd->vending[i].value>battle_config.vending_max_value)sd->vending[i].value=battle_config.vending_max_value; + // カート内のアイテム数と販売するアイテム数に相違があったら中止 + if(pc_cartitem_amount(sd,sd->vending[i].index,sd->vending[i].amount)<0 || sd->vending[i].value < 0) { // fixes by Valaris and fritz + clif_skill_fail(sd,MC_VENDING,0,0); + return; + } + } + sd->vender_id=sd->bl.id; + sd->vend_num=i; + strcpy(sd->message,message); + if(clif_openvending(sd,sd->vender_id,sd->vending) > 0) + clif_showvendingboard(&sd->bl,message,0); + else + sd->vender_id=0; + } +} + diff --git a/misc/src/map/vending.h b/misc/src/map/vending.h new file mode 100644 index 0000000..41aa731 --- /dev/null +++ b/misc/src/map/vending.h @@ -0,0 +1,12 @@ +// $Id: vending.h,v 1.2 2004/09/25 05:32:19 MouseJstr Exp $ +#ifndef _VENDING_H_ +#define _VENDING_H_ + +#include "map.h" + +void vending_closevending(struct map_session_data *sd); +void vending_openvending(struct map_session_data *sd,int len,char *message,int flag,unsigned char *p); +void vending_vendinglistreq(struct map_session_data *sd,int id); +void vending_purchasereq(struct map_session_data *sd,int len,int id,unsigned char *p); + +#endif // _VENDING_H_ diff --git a/misc/src/mra.patch b/misc/src/mra.patch new file mode 100644 index 0000000..b8ae365 --- /dev/null +++ b/misc/src/mra.patch @@ -0,0 +1,69 @@ +diff -u -r athena/src/map/clif.c athenanew/src/map/clif.c +--- athena/src/map/clif.c 2005-04-16 17:07:03.000000000 +0000 ++++ athenanew/src/map/clif.c 2005-05-21 18:25:01.121659080 +0000 +@@ -3208,17 +3208,19 @@ + * アイテム追加成功/失敗 + *------------------------------------------ + */ +-int clif_tradeitemok(struct map_session_data *sd,int index,int fail) ++int clif_tradeitemok(struct map_session_data *sd,int index,int amount,int fail) + { + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; +- WFIFOW(fd,0)=0xea; ++ WFIFOW(fd,0)=0x1b1; ++ //WFIFOW(fd,0)=0xea; + WFIFOW(fd,2)=index; +- WFIFOB(fd,4)=fail; +- WFIFOSET(fd,packet_len_table[0xea]); ++ WFIFOW(fd,4)=amount; ++ WFIFOB(fd,6)=fail; ++ WFIFOSET(fd,packet_len_table[0x1b1]); + + return 0; + } +diff -u -r athena/src/map/clif.h athenanew/src/map/clif.h +--- athena/src/map/clif.h 2005-04-16 17:06:56.000000000 +0000 ++++ athenanew/src/map/clif.h 2005-05-21 18:25:33.040806632 +0000 +@@ -97,7 +97,7 @@ + int clif_traderequest(struct map_session_data *sd,char *name); + int clif_tradestart(struct map_session_data *sd,int type); + int clif_tradeadditem(struct map_session_data *sd,struct map_session_data *tsd,int index,int amount); +-int clif_tradeitemok(struct map_session_data *sd,int index,int fail); ++int clif_tradeitemok(struct map_session_data *sd,int index,int amount,int fail); + int clif_tradedeal_lock(struct map_session_data *sd,int fail); + int clif_tradecancelled(struct map_session_data *sd); + int clif_tradecompleted(struct map_session_data *sd,int fail); +diff -u -r athena/src/map/trade.c athenanew/src/map/trade.c +--- athena/src/map/trade.c 2005-04-16 17:08:06.000000000 +0000 ++++ athenanew/src/map/trade.c 2005-05-21 18:26:46.750601040 +0000 +@@ -98,7 +98,7 @@ + if(sd->deal_item_amount[trade_i] == 0){ + trade_weight+=sd->inventory_data[index-2]->weight*amount; + if(target_sd->weight + trade_weight > target_sd->max_weight){ +- clif_tradeitemok(sd,index,1); //fail to add item -- the player was over weighted. ++ clif_tradeitemok(sd,index,0,1); //fail to add item -- the player was over weighted. + amount = 0; // [MouseJstr] + }else{ + for(c=0; c==trade_i-1;c++){ // re-deal exploit protection [Valaris] +@@ -109,7 +109,7 @@ + } + sd->deal_item_index[trade_i] =index; + sd->deal_item_amount[trade_i]+=amount; +- clif_tradeitemok(sd,index,0); //success to add item ++ clif_tradeitemok(sd,index,amount,0); //success to add item + clif_tradeadditem(sd,target_sd,index,amount); + } + break; +@@ -143,7 +143,7 @@ + + if((target_sd = map_id2sd(sd->trade_partner)) != NULL){ + sd->deal_locked=1; +- clif_tradeitemok(sd,0,0); ++ clif_tradeitemok(sd,0,0,0); + clif_tradedeal_lock(sd,0); + clif_tradedeal_lock(target_sd,1); + } diff --git a/misc/src/tmw-server.dev b/misc/src/tmw-server.dev new file mode 100644 index 0000000..8098eb2 --- /dev/null +++ b/misc/src/tmw-server.dev @@ -0,0 +1,169 @@ +[Project]
+FileName=tmw-server.dev
+Name=tmw-server
+UnitCount=12
+Type=0
+Ver=1
+ObjFiles=
+Includes=
+Libs=
+PrivateResource=
+ResourceIncludes=
+MakeIncludes=
+Compiler=
+CppCompiler=
+Linker=
+IsCpp=1
+Icon=
+ExeOutput=
+ObjectOutput=
+OverrideOutput=0
+OverrideOutputName=
+HostApplication=
+Folders=
+CommandLine=
+UseCustomMakefile=0
+CustomMakefile=
+IncludeVersionInfo=0
+SupportXPThemes=0
+CompilerSet=0
+CompilerSettings=
+
+[Unit1]
+FileName=src\char\char.c
+CompileCpp=1
+Folder=tmw-server
+Compile=1
+Link=1
+Priority=1000
+OverrideBuildCmd=0
+BuildCmd=
+
+[Unit2]
+FileName=src\char\char.h
+CompileCpp=1
+Folder=tmw-server
+Compile=1
+Link=1
+Priority=1000
+OverrideBuildCmd=0
+BuildCmd=
+
+[Unit3]
+FileName=src\char\int_guild.c
+CompileCpp=1
+Folder=tmw-server
+Compile=1
+Link=1
+Priority=1000
+OverrideBuildCmd=0
+BuildCmd=
+
+[Unit4]
+FileName=src\char\int_guild.h
+CompileCpp=1
+Folder=tmw-server
+Compile=1
+Link=1
+Priority=1000
+OverrideBuildCmd=0
+BuildCmd=
+
+[Unit5]
+FileName=src\char\int_party.c
+CompileCpp=1
+Folder=tmw-server
+Compile=1
+Link=1
+Priority=1000
+OverrideBuildCmd=0
+BuildCmd=
+
+[Unit6]
+FileName=src\char\int_party.h
+CompileCpp=1
+Folder=tmw-server
+Compile=1
+Link=1
+Priority=1000
+OverrideBuildCmd=0
+BuildCmd=
+
+[Unit7]
+FileName=src\char\int_pet.c
+CompileCpp=1
+Folder=tmw-server
+Compile=1
+Link=1
+Priority=1000
+OverrideBuildCmd=0
+BuildCmd=
+
+[Unit8]
+FileName=src\char\int_pet.h
+CompileCpp=1
+Folder=tmw-server
+Compile=1
+Link=1
+Priority=1000
+OverrideBuildCmd=0
+BuildCmd=
+
+[Unit9]
+FileName=src\char\int_storage.c
+CompileCpp=1
+Folder=tmw-server
+Compile=1
+Link=1
+Priority=1000
+OverrideBuildCmd=0
+BuildCmd=
+
+[Unit10]
+FileName=src\char\int_storage.h
+CompileCpp=1
+Folder=tmw-server
+Compile=1
+Link=1
+Priority=1000
+OverrideBuildCmd=0
+BuildCmd=
+
+[Unit11]
+FileName=src\char\inter.c
+CompileCpp=1
+Folder=tmw-server
+Compile=1
+Link=1
+Priority=1000
+OverrideBuildCmd=0
+BuildCmd=
+
+[Unit12]
+FileName=src\char\inter.h
+CompileCpp=1
+Folder=tmw-server
+Compile=1
+Link=1
+Priority=1000
+OverrideBuildCmd=0
+BuildCmd=
+
+[VersionInfo]
+Major=0
+Minor=1
+Release=1
+Build=1
+LanguageID=1033
+CharsetID=1252
+CompanyName=
+FileVersion=
+FileDescription=Developed using the Dev-C++ IDE
+InternalName=
+LegalCopyright=
+LegalTrademarks=
+OriginalFilename=
+ProductName=
+ProductVersion=
+AutoIncBuildNr=0
+
diff --git a/misc/src/tmw-server.layout b/misc/src/tmw-server.layout new file mode 100644 index 0000000..6a8b2b4 --- /dev/null +++ b/misc/src/tmw-server.layout @@ -0,0 +1,43 @@ +[Editor_0]
+CursorCol=23
+CursorRow=94
+TopLine=85
+LeftChar=1
+Open=0
+Top=0
+[Editors]
+Focused=-1
+Order=
+[Editor_1]
+Open=0
+Top=0
+[Editor_2]
+Open=0
+Top=0
+[Editor_3]
+Open=0
+Top=0
+[Editor_4]
+Open=0
+Top=0
+[Editor_5]
+Open=0
+Top=0
+[Editor_6]
+Open=0
+Top=0
+[Editor_7]
+Open=0
+Top=0
+[Editor_8]
+Open=0
+Top=0
+[Editor_9]
+Open=0
+Top=0
+[Editor_10]
+Open=0
+Top=0
+[Editor_11]
+Open=0
+Top=0
diff --git a/misc/src/tool/Makefile b/misc/src/tool/Makefile new file mode 100644 index 0000000..8734041 --- /dev/null +++ b/misc/src/tool/Makefile @@ -0,0 +1,6 @@ +all:
+ $(CC) -o adduser adduser.c
+
+clean:
+ rm -f adduser
+ rm -f *.exe
diff --git a/misc/src/tool/adduser.c b/misc/src/tool/adduser.c new file mode 100644 index 0000000..1219540 --- /dev/null +++ b/misc/src/tool/adduser.c @@ -0,0 +1,96 @@ +/* + This program adds an user to account.txt + Don't usr it When login-sever is working. +*/ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +char *account_txt = "../save/account.txt"; + +//----------------------------------------------------- +// Function to suppress control characters in a string. +//----------------------------------------------------- +int remove_control_chars(unsigned char *str) { + int i; + int change = 0; + + for(i = 0; str[i]; i++) { + if (str[i] < 32) { + str[i] = '_'; + change = 1; + } + } + + return change; +} + +int main(int argc, char *argv[]) { + + char username[24]; + char password[24]; + char sex[2]; + + int next_id, id; + char line[1024]; + + // Check to see if account.txt exists. + printf("Checking if '%s' file exists...\n", account_txt); + FILE *FPaccin = fopen(account_txt, "r"); + if (FPaccin == NULL) { + printf("'%s' file not found!\n", account_txt); + printf("Run the setup wizard please.\n"); + exit(0); + } + + next_id = 2000000; + while(fgets(line, sizeof(line)-1, FPaccin)) { + if (line[0] == '/' && line[1] == '/') { continue; } + if (sscanf(line, "%d\t%%newid%%\n", &id) == 1) { + if (next_id < id) { + next_id = id; + } + } else { + sscanf(line,"%i%[^ ]", &id); + if (next_id <= id) { + next_id = id +1; + } + } + } + close(FPaccin); + printf("File exists.\n"); + + printf("Don't create an account if the login-server is online!!!\n"); + printf("If the login-server is online, press ctrl+C now to stop this software.\n"); + printf("\n"); + + strcpy(username, ""); + while (strlen(username) < 4 || strlen(username) > 23) { + printf("Enter an username (4-23 characters): "); + scanf("%s", &username); + username[23] = 0; + remove_control_chars(username); + } + + strcpy(password, ""); + while (strlen(password) < 4 || strlen(password) > 23) { + printf("Enter a password (4-23 characters): "); + scanf("%s", &password); + password[23] = 0; + remove_control_chars(password); + } + + strcpy(sex, ""); + while (strcmp(sex, "F") != 0 && strcmp(sex, "M") != 0) { + printf("Enter a gender (M for male, F for female): "); + scanf("%s", &sex); + } + + FILE *FPaccout = fopen(account_txt, "r+"); + fseek(FPaccout, 0, SEEK_END); + fprintf(FPaccout, "%i %s %s - %s -\r\n", next_id, username, password, sex); + close(FPaccout); + + printf("Account added.\n"); +} diff --git a/misc/src/tool/backup b/misc/src/tool/backup new file mode 100644 index 0000000..2b5a958 --- /dev/null +++ b/misc/src/tool/backup @@ -0,0 +1,100 @@ +#!/usr/bin/perl
+
+##########################################################################
+# Athena用データバックアップツール
+#
+# Athenaの各種データファイル*.txtをバックアップするツール
+#
+#-------------------------------------------------------------------------
+# 設定方法
+# 実行する時のカレントフォルダからのデータへのパス、ファイルのリストを
+# 正しく設定します。バックアップ先のフォルダは自動作成されないので、
+# 自分で作成しておく必要があります。
+# フォルダの最後の「/」は省略できません。
+#
+# フォルダは引数でも指定できます。例>./backup ../save/ ./backup_data/
+# フォルダの最後の「/」は省略できません。
+#
+# 実行するとバックアップ先のフォルダへ、ファイル名に現在の日付と時刻を
+# つけてファイルをコピーします。
+#
+# * toolフォルダ内にbackup_dataフォルダを作成し、
+# athena.shの中に「./tool/backup ./save/ ./tool/backup_data/」
+# という行を追加すると、athenaを起動するたびにバックアップが取れます
+#
+# 復元するときは引数に「-r 日付と時刻」を指定します。
+# またその後ろにフォルダを指定することも出来ます
+# 例1> ./backup -r 200309191607
+# 例2> ./backup -r 200309191607 ../save ./backup_data/
+# この例では2003/09/19の16:07分にバックアップしたデータを復元しています
+#
+# 復元するとき、Athenaディレクトリにあるデータは *.bak に名前を変更して
+# 残しているので、いらない場合は rm *.bak などで消してください。
+#
+##########################################################################
+
+$sdir="../save/"; #バックアップ元(Athenaのディレクトリ/save/)
+$tdir="./backup_data/"; #バックアップ先
+
+@files=( #ファイルのリスト
+ "account","athena","storage","party","guild","castle","pet"
+);
+
+
+#-------------------------------設定ここまで-----------------------------
+
+
+
+
+
+
+
+
+
+
+
+if($ARGV[0]=~/^\-r$/i || $ARGV[0]=~/\-\-(recover|restore)/i){
+ #復元処理
+
+ $file=$ARGV[1];
+ $sdir=$ARGV[2]||$sdir;
+ $tdir=$ARGV[3]||$tdir;
+ &restorecopy($_) foreach @files;
+ exit(0);
+}
+
+#バックアップ処理
+$sdir=$ARGV[0]||$sdir;
+$tdir=$ARGV[1]||$tdir;
+
+unless( -d $tdir ){
+ print "$0: \"$tdir\" : No such directory\n";
+ exit(1);
+}
+
+(undef,$min,$hour,$day,$month,$year)=localtime;
+
+$file=sprintf("%04d%02d%02d%02d%02d",
+ $year+1900, $month+1, $day, $hour, $min );
+
+&backupcopy($_) foreach @files;
+exit(0);
+
+sub backupcopy {
+ my($name)= @_;
+ system("cp $sdir$name.txt $tdir$name$file.txt");
+}
+
+sub restorecopy {
+ my($name)= @_;
+ unless( -f "$sdir$name.txt" ){
+ printf("$0: \"$sdir$name.txt\" not found!\n");
+ return 0;
+ }
+ unless( -f "$tdir$name$file.txt" ){
+ printf("$0: \"$tdir$name$file.txt\" not found!\n");
+ return 0;
+ }
+ rename "$sdir$name.txt","$sdir$name.bak";
+ system("cp $tdir$name$file.txt $sdir$name.txt");
+}
diff --git a/misc/src/tool/cgi/addaccount.cgi b/misc/src/tool/cgi/addaccount.cgi new file mode 100644 index 0000000..7d1788c --- /dev/null +++ b/misc/src/tool/cgi/addaccount.cgi @@ -0,0 +1,204 @@ +#!/usr/bin/perl + +#========================================================================= +# addaccount.cgi ver.1.00 +# ladminをラップした、アカウントを作成するCGI。 +# ladmin ver.1.04での動作を確認。 +# +# ** 設定方法 ** +# +# - 下の$ladmin変数にladminへのパスを設定すること。 +# - UNIX系OSで使用する場合はladminと共に改行コードを変換すること、また +# ファイル先頭行をperlの正しいパスにすること。例> $ which perl +# - サーバープログラムやブラウザによっては $cgiuri にこのファイルへの +# 完全なURIをセットしなければならない場合もある。 +# - perlにパスが通っていない場合は $perl をperlへの正しいパスにすること。 +# - 他は普通のCGIと同じです。(実行権やcgi-binフォルダなど) +# +# ** その他 ** +# addaccount.cgi をブラウザで開くとサンプルHTML(そのまま使えます)が +# 開きます。また、このcgiはブラウザから送られるAccept-Languageが +# jaで始まっていればメッセージの一部を日本語に変換します。 +# (IEならインターネットオプションの言語設定で一番上に日本語を置く) +# それ以外の場合は英語のまま出力します。 +#------------------------------------------------------------------------- + +my($ladmin) = "../ladmin"; # ladminのパス(おそらく変更が必要) + +my($cgiuri) = "./addaccount.cgi"; # このファイルのURI +my($perl) = "perl"; # perlのコマンド名 + + + +#--------------------------- 設定ここまで -------------------------------- + + + + + + +use strict; +use CGI; + +my($cgi)= new CGI; +my(%langconv)=( + 'Athena login-server administration tool.*' => '', + 'logged on.*' => '', +); + +# ----- 日本語環境なら変換テーブルをセット ----- +if($ENV{'HTTP_ACCEPT_LANGUAGE'}=~/^ja/){ + my(%tmp)=( + 'Account \[(.+)\] is successfully created.*' + => 'アカウント "$1" を作成しました.', + 'Account \[(.+)\] creation failed\. same account exists.*' + => 'アカウント "$1" は既に存在します.', + 'Illeagal charactor found in UserID.*' + => 'IDの中に不正な文字があります.', + 'Illeagal charactor found in Password.*' + => 'Passwordの中に不正な文字があります.', + 'input UserID 4-24 bytes.' + => 'IDは半角4〜24文字で入力してください.', + 'input Password 4-24 bytes.' + => 'Passwordは半角4〜24文字で入力してください.', + 'Illeagal gender.*' + => '性別がおかしいです.', + 'Cant connect to login server.*' + => 'ログインサーバーに接続できません.', + 'login error.*' + => 'ログインサーバーへの管理者権限ログインに失敗しました', + "Can't execute ladmin.*" + => 'ladminの実行に失敗しました', + 'UserID "(.+)" is already used.*' + => 'ID "$1" は既に使用されています.', + 'You can use UserID \"(.+)\".*' + => 'ID "$1" は使用可能です.', + + 'account making' =>'アカウント作成', + '\>UserID' =>'>ID', + '\>Password' =>'>パスワード', + '\>Gender' =>'>性別', + '\>Male' =>'>男性', + '\>Female' =>'>女性', + '\"Make Account\"' =>'"アカウント作成"', + '\"Check UserID\"' =>'"IDのチェック"', + ); + map { $langconv{$_}=$tmp{$_}; } keys (%tmp); +} + +# ----- 追加 ----- +if( $cgi->param("addaccount") ){ + my($userid)= $cgi->param("userid"); + my($passwd)= $cgi->param("passwd"); + my($gender)= lc(substr($cgi->param("gender"),0,1)); + if(length($userid)<4 || length($userid)>24){ + HttpError("input UserID 4-24 bytes."); + } + if(length($passwd)<4 || length($passwd)>24){ + HttpError("input Password 4-24 bytes."); + } + if($userid=~/[^0-9A-Za-z\@\_\-\']/){ + HttpError("Illeagal charactor found in UserID."); + } + if($passwd=~/[\x00-\x1f\x80-\xff\']/){ + HttpError("Illeagal charactor found in Password."); + } + if($gender!~/[mf]/){ + HttpError("Gender error."); + } + open PIPE,"$perl $ladmin --add $userid $gender $passwd |" + or HttpError("Can't execute ladmin."); + my(@msg)=<PIPE>; + close PIPE; + HttpMsg(@msg); +} +# ----- 存在チェック ----- +elsif( $cgi->param("check") ){ + my($userid)= $cgi->param("userid"); + if(length($userid)<4 || length($userid)>24){ + HttpError("input UserID 4-24 bytes."); + } + if($userid=~/[^0-9A-Za-z\@\_\-\']/){ + HttpError("Illeagal charactor found in UserID."); + } + open PIPE,"$perl $ladmin --search --regex \\b$userid\\b |" + or HttpError("Can't execute ladmin."); + my(@msg)=<PIPE>; + close PIPE; + if(scalar(@msg)==6 && (split /[\s\0]+/,substr($msg[4],11,24))[0] eq $userid){ + HttpMsg("NG : UserID \"$userid\" is already used."); + }elsif(scalar(@msg)==5){ + HttpMsg("OK : You can use UserID \"$userid\""); + } + HttpError("ladmin error ?\n---output---\n",@msg); +} + +# ----- フォーム ----- +else{ + print LangConv( <<"EOM" ); +Content-type: text/html\n +<html> + <head> + <title>Athena account making cgi</title> + </head> + <body> + <h1>Athena account making cgi</h1> + <form action="$cgiuri" method="post"> + <table border=2> + <tr> + <th>UserID</th> + <td><input name="userid" size=24 maxlength=24></td> + </tr> + <tr> + <th>Password</th> + <td><input name="passwd" size=24 maxlength=24 type="password"></td> + </tr> + <tr> + <th>Gender</th> + <td> + <input type="radio" name="gender" value="male">Male + <input type="radio" name="gender" value="female">Female + </td> + </tr> + <tr> + <td colspan=2> + <input type="submit" name="addaccount" value="Make Account"> + <input type="submit" name="check" value="Check UserID"> + </td> + </tr> + </table> + </form> + </body> +</html> +EOM + exit; +} + +sub LangConv { + my(@lst)= @_; + my($a,$b,@out)=(); + foreach $a(@lst){ + foreach $b(keys %langconv){ + $a=~s/$b/$langconv{$b}/g; + my($rep1)=$1; + $a=~s/\$1/$rep1/g; + } + push @out,$a; + } + return @out; +} + +sub HttpMsg { + my($msg)=join("", LangConv(@_)); + $msg=~s/\n/<br>\n/g; + print LangConv("Content-type: text/html\n\n"),$msg; + exit; +} + +sub HttpError { + my($msg)=join("", LangConv(@_)); + $msg=~s/\n/<br>\n/g; + print LangConv("Content-type: text/html\n\n"),$msg; + exit; +} + diff --git a/misc/src/tool/checkversion b/misc/src/tool/checkversion new file mode 100644 index 0000000..1351652 --- /dev/null +++ b/misc/src/tool/checkversion @@ -0,0 +1,85 @@ +#!/usr/bin/perl -w
+
+##########################################################################
+# INFORMATION TOOL ABOUT THE SERVERS VERSION OF ATHENA
+#
+# By connection on a server, this software display the version of the
+# designed server.
+#-------------------------------------------------------------------------
+# Usages:
+# ./checkversion IP:port
+# ./checkversion IP port
+# perl checkversion IP:port
+# perl checkversion IP port
+#
+# note: default port: 6900
+#
+# When successfull, the software return the value 0.
+#
+##########################################################################
+
+#------------------------- start of configuration ------------------------
+
+$connecttimeout = 10; # Connection Timeout (in seconds)
+
+#-------------------------- End of configuration -------------------------
+
+use IO::Socket;
+
+unless($ARGV[0]) {
+ print "USAGE: $0 server_ip:port\n";
+ exit(1);
+}
+
+$server = $ARGV[0];
+$port = $ARGV[1];
+$port = $1 if ($server =~ s/:(\d+)//);
+$port ||= 6900;
+
+# Connection to the server
+my($so,$er) = ();
+eval{
+ $so = IO::Socket::INET->new(
+ PeerAddr=> $server,
+ PeerPort=> $port,
+ Proto => "tcp",
+ Timeout => $connecttimeout) or $er = 1;
+};
+
+if($er || $@) {
+ print "Can't not connect to server [$server:$port] !\n";
+ exit(2);
+}
+
+# Request for the server version
+print $so pack("v",30000); # 0x7530
+$so->flush();
+
+# Receiving of the answer of the server
+if (read($so,$buf,10) < 10) {
+ print "Invalid answer. It isn't an athena server or it is a too old version.\n";
+ exit(5);
+}
+
+# Sending end of connection to the server
+print $so pack("v",30002); # 0x7532
+$so->flush();
+
+# Analyse of the answer
+my($ret,$maver,$miver,$rev,$dev,$mod,$type,$mdver) = unpack("v c6 v",$buf);
+
+if ($ret != 30001) { # 0x7531
+ print "Invalid answer. It isn't an athena server or it is a too old version.\n";
+ exit(6);
+}
+
+my(@stype) = ();
+foreach $i(0..3) {
+ push @stype,(("login","char","inter","map")[$i]) if( $type & (1<<$i) );
+}
+print " ".join("/",@stype)." server [$server:$port].\n";
+printf " Athena version %s-%d.%d", ("stable","dev")[$dev], $maver,$miver;
+printf " revision %d",$rev if $rev;
+printf "%s%d\n",("","-mod")[$mod],$mdver;
+
+exit(0);
diff --git a/misc/src/tool/convert.c b/misc/src/tool/convert.c new file mode 100644 index 0000000..16631c9 --- /dev/null +++ b/misc/src/tool/convert.c @@ -0,0 +1,296 @@ +#include <stdio.h> +#include <stdlib.h> + +#define RETCODE "\r\n" + +#define MAX_INVENTORY 100 +#define MAX_CART 100 +#define MAX_SKILL 350 +#define GLOBAL_REG_NUM 16 + +struct item { + int id; + short nameid; + short amount; + short equip; + char identify; + char refine; + char attribute; + short card[4]; +}; +struct point{ + char map[16]; + short x,y; +}; +struct skill { + unsigned short id,lv,flag; +}; +struct global_reg { + char str[16]; + int value; +}; + +struct mmo_charstatus { + int char_id; + int account_id; + int base_exp,job_exp,zeny; + + short class; + short status_point,skill_point; + short hp,max_hp,sp,max_sp; + short option,karma,manner; + short hair,hair_color,clothes_color; + int party_id,guild_id,pet_id; + + short weapon,shield; + short head_top,head_mid,head_bottom; + + char name[24]; + unsigned char base_level,job_level; + unsigned char str,agi,vit,int_,dex,luk,char_num,sex; + + struct point last_point,save_point,memo_point[3]; + struct item inventory[MAX_INVENTORY],cart[MAX_CART]; + struct skill skill[MAX_SKILL]; + int global_reg_num; + struct global_reg global_reg[GLOBAL_REG_NUM]; +}; + +int mmo_char_tostr(char *str,struct mmo_charstatus *p) +{ + int i; + sprintf(str,"%d\t%d,%d\t%s\t%d,%d,%d\t%d,%d,%d\t%d,%d,%d,%d\t%d,%d,%d,%d,%d,%d\t%d,%d" + "\t%d,%d,%d\t%d,%d,%d\t%d,%d,%d\t%d,%d,%d,%d,%d" + "\t%s,%d,%d\t%s,%d,%d", + p->char_id,p->account_id,p->char_num,p->name, // + p->class,p->base_level,p->job_level, + p->base_exp,p->job_exp,p->zeny, + p->hp,p->max_hp,p->sp,p->max_sp, + p->str,p->agi,p->vit,p->int_,p->dex,p->luk, + p->status_point,p->skill_point, + p->option,p->karma,p->manner, // + p->party_id,p->guild_id,p->pet_id, + p->hair,p->hair_color,p->clothes_color, + p->weapon,p->shield,p->head_top,p->head_mid,p->head_bottom, + p->last_point.map,p->last_point.x,p->last_point.y, // + p->save_point.map,p->save_point.x,p->save_point.y + ); + strcat(str,"\t"); + for(i=0;i<3;i++) + if(p->memo_point[i].map[0]){ + sprintf(str+strlen(str),"%s,%d,%d",p->memo_point[i].map,p->memo_point[i].x,p->memo_point[i].y); + } + strcat(str,"\t"); + for(i=0;i<MAX_INVENTORY;i++) + if(p->inventory[i].nameid){ + sprintf(str+strlen(str),"%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d ", + p->inventory[i].id,p->inventory[i].nameid,p->inventory[i].amount,p->inventory[i].equip, + p->inventory[i].identify,p->inventory[i].refine,p->inventory[i].attribute, + p->inventory[i].card[0],p->inventory[i].card[1],p->inventory[i].card[2],p->inventory[i].card[3]); + } + strcat(str,"\t"); + for(i=0;i<MAX_CART;i++) + if(p->cart[i].nameid){ + sprintf(str+strlen(str),"%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d ", + p->cart[i].id,p->cart[i].nameid,p->cart[i].amount,p->cart[i].equip, + p->cart[i].identify,p->cart[i].refine,p->cart[i].attribute, + p->cart[i].card[0],p->cart[i].card[1],p->cart[i].card[2],p->cart[i].card[3]); + } + strcat(str,"\t"); + for(i=0;i<MAX_SKILL;i++) + if(p->skill[i].id){ + sprintf(str+strlen(str),"%d,%d ",p->skill[i].id,p->skill[i].lv); + } + strcat(str,"\t"); + for(i=0;i<p->global_reg_num;i++) + sprintf(str+strlen(str),"%s,%d ",p->global_reg[i].str,p->global_reg[i].value); + strcat(str,"\t"); + return 0; +} + +int mmo_char_fromstr(char *str,struct mmo_charstatus *p) +{ + int tmp_int[256]; + int set,next,len,i; + + set=sscanf(str,"%d\t%d,%d\t%[^\t]\t%d,%d,%d\t%d,%d,%d\t%d,%d,%d,%d\t%d,%d,%d,%d,%d,%d\t%d,%d" + "\t%d,%d,%d\t%d,%d\t%d,%d,%d\t%d,%d,%d,%d,%d" + "\t%[^,],%d,%d\t%[^,],%d,%d%n", + &tmp_int[0],&tmp_int[1],&tmp_int[2],p->name, // + &tmp_int[3],&tmp_int[4],&tmp_int[5], + &tmp_int[6],&tmp_int[7],&tmp_int[8], + &tmp_int[9],&tmp_int[10],&tmp_int[11],&tmp_int[12], + &tmp_int[13],&tmp_int[14],&tmp_int[15],&tmp_int[16],&tmp_int[17],&tmp_int[18], + &tmp_int[19],&tmp_int[20], + &tmp_int[21],&tmp_int[22],&tmp_int[23], // + &tmp_int[24],&tmp_int[25], + &tmp_int[26],&tmp_int[27],&tmp_int[28], + &tmp_int[29],&tmp_int[30],&tmp_int[31],&tmp_int[32],&tmp_int[33], + p->last_point.map,&tmp_int[34],&tmp_int[35], // + p->save_point.map,&tmp_int[36],&tmp_int[37],&next + ); + p->char_id=tmp_int[0]; + p->account_id=tmp_int[1]; + p->char_num=tmp_int[2]; + p->class=tmp_int[3]; + p->base_level=tmp_int[4]; + p->job_level=tmp_int[5]; + p->base_exp=tmp_int[6]; + p->job_exp=tmp_int[7]; + p->zeny=tmp_int[8]; + p->hp=tmp_int[9]; + p->max_hp=tmp_int[10]; + p->sp=tmp_int[11]; + p->max_sp=tmp_int[12]; + p->str=tmp_int[13]; + p->agi=tmp_int[14]; + p->vit=tmp_int[15]; + p->int_=tmp_int[16]; + p->dex=tmp_int[17]; + p->luk=tmp_int[18]; + p->status_point=tmp_int[19]; + p->skill_point=tmp_int[20]; + p->option=tmp_int[21]; + p->karma=tmp_int[22]; + p->manner=tmp_int[23]; + p->party_id=tmp_int[24]; + p->guild_id=tmp_int[25]; + p->pet_id=0; + p->hair=tmp_int[26]; + p->hair_color=tmp_int[27]; + p->clothes_color=tmp_int[28]; + p->weapon=tmp_int[29]; + p->shield=tmp_int[30]; + p->head_top=tmp_int[31]; + p->head_mid=tmp_int[32]; + p->head_bottom=tmp_int[33]; + p->last_point.x=tmp_int[34]; + p->last_point.y=tmp_int[35]; + p->save_point.x=tmp_int[36]; + p->save_point.y=tmp_int[37]; + if(set!=41) + return 0; + if(str[next]=='\n' || str[next]=='\r') + return 1; // 新規データ + next++; + for(i=0;str[next] && str[next]!='\t';i++){ + set=sscanf(str+next,"%[^,],%d,%d%n",p->memo_point[i].map,&tmp_int[0],&tmp_int[1],&len); + if(set!=3) + return 0; + p->memo_point[i].x=tmp_int[0]; + p->memo_point[i].y=tmp_int[1]; + next+=len; + if(str[next]==' ') + next++; + } + next++; + for(i=0;str[next] && str[next]!='\t';i++){ + set=sscanf(str+next,"%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d%n", + &tmp_int[0],&tmp_int[1],&tmp_int[2],&tmp_int[3], + &tmp_int[4],&tmp_int[5],&tmp_int[6], + &tmp_int[7],&tmp_int[8],&tmp_int[9],&tmp_int[10],&len); + if(set!=11) + return 0; + p->inventory[i].id=tmp_int[0]; + p->inventory[i].nameid=tmp_int[1]; + p->inventory[i].amount=tmp_int[2]; + p->inventory[i].equip=tmp_int[3]; + p->inventory[i].identify=tmp_int[4]; + p->inventory[i].refine=tmp_int[5]; + p->inventory[i].attribute=tmp_int[6]; + p->inventory[i].card[0]=tmp_int[7]; + p->inventory[i].card[1]=tmp_int[8]; + p->inventory[i].card[2]=tmp_int[9]; + p->inventory[i].card[3]=tmp_int[10]; + next+=len; + if(str[next]==' ') + next++; + } + next++; + for(i=0;str[next] && str[next]!='\t';i++){ + set=sscanf(str+next,"%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d%n", + &tmp_int[0],&tmp_int[1],&tmp_int[2],&tmp_int[3], + &tmp_int[4],&tmp_int[5],&tmp_int[6], + &tmp_int[7],&tmp_int[8],&tmp_int[9],&tmp_int[10],&len); + if(set!=11) + return 0; + p->cart[i].id=tmp_int[0]; + p->cart[i].nameid=tmp_int[1]; + p->cart[i].amount=tmp_int[2]; + p->cart[i].equip=tmp_int[3]; + p->cart[i].identify=tmp_int[4]; + p->cart[i].refine=tmp_int[5]; + p->cart[i].attribute=tmp_int[6]; + p->cart[i].card[0]=tmp_int[7]; + p->cart[i].card[1]=tmp_int[8]; + p->cart[i].card[2]=tmp_int[9]; + p->cart[i].card[3]=tmp_int[10]; + next+=len; + if(str[next]==' ') + next++; + } + next++; + for(i=0;str[next] && str[next]!='\t';i++){ + set=sscanf(str+next,"%d,%d%n", + &tmp_int[0],&tmp_int[1],&len); + if(set!=2) + return 0; + p->skill[tmp_int[0]].id=tmp_int[0]; + p->skill[tmp_int[0]].lv=tmp_int[1]; + next+=len; + if(str[next]==' ') + next++; + } + next++; + for(i=0;str[next] && str[next]!='\t' && str[next]!='\n' && str[next]!='\r';i++){ //global_reg実装以前のathena.txt互換のため一応'\n'チェック + set=sscanf(str+next,"%[^,],%d%n", + p->global_reg[i].str,&p->global_reg[i].value,&len); + if(set!=2) + return 0; + next+=len; + if(str[next]==' ') + next++; + } + p->global_reg_num=i; + return 1; +} + +int mmo_char_convert(char *fname1,char *fname2) +{ + char line[65536]; + int ret; + struct mmo_charstatus char_dat; + FILE *ifp,*ofp; + + ifp=fopen(fname1,"r"); + ofp=fopen(fname2,"w"); + if(ifp==NULL) { + printf("file not found %s\n",fname1); + return 0; + } + if(ofp==NULL) { + printf("file open error %s\n",fname2); + return 0; + } + while(fgets(line,65535,ifp)){ + memset(&char_dat,0,sizeof(struct mmo_charstatus)); + ret=mmo_char_fromstr(line,&char_dat); + if(ret){ + mmo_char_tostr(line,&char_dat); + fprintf(ofp,"%s" RETCODE,line); + } + } + fcloseall(); + return 0; +} + +int main(int argc,char *argv[]) +{ + if(argc < 3) { + printf("Usage: convert <input filename> <output filename>\n"); + exit(0); + } + mmo_char_convert(argv[1],argv[2]); + + return 0; +} diff --git a/misc/src/tool/getlogincount b/misc/src/tool/getlogincount new file mode 100644 index 0000000..6a20992 --- /dev/null +++ b/misc/src/tool/getlogincount @@ -0,0 +1,122 @@ +#!/usr/bin/perl -w
+
+##########################################################################
+# INFORMATION TOOL ABOUT THE # OF ONLINE PLAYERS ON ATHENA SERVERS
+#
+# By connection on the athena login-server, this software displays the
+# number of online players.
+#
+#-------------------------------------------------------------------------
+# Software usage:
+# Configure the IP, the port and a valid account of the server.
+# After, use at your choice:
+# ./getlogincount - display the number of online players on all servers.
+# ./getlogincount --premier or
+# ./getlogincount --first -- display the number of online players of the
+# first server in the received list.
+# ./getlogincount [servername] -- display the number of online players
+# of the specified server.
+#
+# If successfull, the software return the value 0.
+#
+##########################################################################
+
+#------------------------------ CONFIGURATION ----------------------------
+
+$loginserverip = "127.0.0.1"; # IP of the login-server
+$loginserverport = 6900; # port of the login-server
+$loginaccount = "s1"; # a valid account name
+$loginpasswd = "p1"; # the password of the valid account name
+
+$connecttimeout = 10; # Connection timeout (in seconds)
+
+#-------------------------------------------------------------------------
+
+use IO::Socket;
+
+my($sname) = $ARGV[0];
+if (!defined($sname)) {
+ $sname = "";
+}
+
+# Connection to the login-server
+my($so,$er) = ();
+eval{
+ $so = IO::Socket::INET->new(
+ PeerAddr=> $loginserverip,
+ PeerPort=> $loginserverport,
+ Proto => "tcp",
+ Timeout => $connecttimeout) or $er=1;
+};
+if($er || $@){
+ print "Can't not connect to the login-server [${loginserverip}:$loginserverport] !\n";
+ exit(2);
+}
+
+# Request to connect on login-server
+print $so pack("v V a24 a24 C",0x0064,9,$loginaccount,$loginpasswd,3);
+$so->flush();
+
+# Fail to connect
+if(unpack("v", &soread(\$so,2)) != 0x0069) {
+ print "Login error.\n";
+ exit(3);
+}
+
+# Get length of the received packet
+my($plen) = unpack("v",&soread(\$so,2))-4;
+
+# Suppress information of the account (we need only information about the servers)
+&soread(\$so,43);
+$plen -= 43;
+
+# Check about the number of online servers
+if ($plen < 32) {
+ printf "No server is connected to login-server.\n";
+ exit(1);
+}
+
+# Read information of the servers
+my(@slist) = ();
+for(;$plen > 0;$plen -= 32) {
+ my($name,$count) = unpack("x6 a20 V",&soread(\$so,32));
+ $name = substr($name,0,index($name,"\0"));
+ push @slist, [ $name, $count ];
+}
+
+# Display the result
+if($sname eq "--first" || $sname eq "--premier") { # If we ask only for the first server
+ printf "%-20s : %5d\n",$slist[0][0],$slist[0][1];
+} elsif ($sname eq "") { # If we ask for all servers
+ foreach $i(@slist) {
+ printf "%-20s : %5d\n",$i->[0],$i->[1];
+ }
+} else { # If we ask for a specified server (by its name)
+ my($flag) = 1;
+ foreach $i(@slist) {
+ if($i->[0] eq $sname) {
+ printf "%-20s : %5d\n",$i->[0],$i->[1];
+ $flag = 0;
+ }
+ }
+ if($flag) { # If the server doesn't exist
+ printf "The server [$sname] doesn't exist.\n";
+ exit(1);
+ }
+}
+
+# End of the software
+$so->shutdown(2);
+$so->close();
+exit(0);
+
+# Sub-function: get data from the socket
+sub soread {
+ my($so,$len) = @_;
+ my($sobuf);
+ if(read($$so,$sobuf,$len) < $len) {
+ print "Socket read error.\n";
+ exit(5);
+ }
+ return $sobuf;
+};
diff --git a/misc/src/tool/ladmin b/misc/src/tool/ladmin new file mode 100644 index 0000000..e3319d5 --- /dev/null +++ b/misc/src/tool/ladmin @@ -0,0 +1,3793 @@ +#!/usr/bin/perl
+use POSIX;
+##########################################################################
+# EAthena login-server remote administration tool
+# New ladamin by [Yor]
+##########################################################################
+#-------------------------------INSTRUCTIONS------------------------------
+# Set the 4 variables below:
+# IP of the login server.
+# Port where the login-server listens incoming packets.
+# Password of administration (same of config_athena.conf).
+# Displayed language of the sofware (if not correct, english is used).
+# IMPORTANT:
+# Be sure that you authorize remote administration in login-server
+# (see login_athena.conf, 'admin_state' parameter)
+#-------------------------------------------------------------------------
+my($loginserverip) = "127.0.0.1"; # IP of login-server
+my($loginserverport) = 6900; # Port of login-server
+my($loginserveradminpassword) = "admin"; # Administration password
+my($connecttimeout) = 10; # Timeout of connection (in seconds)
+my($passenc) = 2; # Encoding type of the password
+my($defaultlanguage) = "E"; # Default language (F: Fran軋is/E: English)
+ # (if it's not 'F', default is English)
+
+#-------------------------------------------------------------------------
+# LIST of COMMANDs that you can type at the prompt:
+# To use these commands you can only type only the first letters.
+# You must type a minimum of letters (you can not type 'a',
+# because ladmin doesn't know if it's for 'aide' or for 'add')
+# <Example> q <= quit, li <= list, pass <= passwd, etc.
+#
+# Note: every time you must give a account_name, you can use "" or '' (spaces can be included)
+#
+# aide/help/?
+# Display the description of the commands
+# aide/help/? [command]
+# Display the description of the specified command
+#
+# add <account_name> <sex> <password>
+# Create an account with the default email (a@a.com).
+# Concerning the sex, only the first letter is used (F or M).
+# The e-mail is set to a@a.com (default e-mail). It's like to have no e-mail.
+# When the password is omitted, the input is done without displaying of the pressed keys.
+# <example> add testname Male testpass
+#
+# ban/banish yyyy/mm/dd hh:mm:ss <account name>
+# Changes the final date of a banishment of an account.
+# Same command of banset, except that account_name is at end
+#
+# banadd <account_name> <modifier>
+# Adds or substracts time from the final date of a banishment of an account.
+# Modifier is done as follows:
+# Adjustment value (-1, 1, +1, etc...)
+# Modified element:
+# a or y: year
+# m: month
+# j or d: day
+# h: hour
+# mn: minute
+# s: second
+# <example> banadd testname +1m-2mn1s-6y
+# this example adds 1 month and 1 second, and substracts 2 minutes and 6 years at the same time.
+# NOTE: If you modify the final date of a non-banished account,
+# you fix the final date to (actual time +- adjustments)
+#
+# banset <account_name> yyyy/mm/dd [hh:mm:ss]
+# Changes the final date of a banishment of an account.
+# Default time: 23:59:59
+# banset <account_name> 0
+# Set a non-banished account (0 = unbanished).
+#
+# block <account name>
+# Set state 5 (You have been blocked by the GM Team) to an account.
+# Same command of state <account_name> 5.
+#
+# check <account_name> <password>
+# Check the validity of a password for an account
+# NOTE: Server will never sends back a password.
+# It's the only method you have to know if a password is correct.
+# The other method is to have a ('physical') access to the accounts file.
+#
+# create <account_name> <sex> <email> <password>
+# Like the 'add' command, but with e-mail moreover.
+# <example> create testname Male my@mail.com testpass
+#
+# del <account name>
+# Remove an account.
+# This order requires confirmation. After confirmation, the account is deleted.
+#
+# email <account_name> <email>
+# Modify the e-mail of an account.
+#
+# getcount
+# Give the number of players online on all char-servers.
+#
+# gm <account_name> [GM_level]
+# Modify the GM level of an account.
+# Default value remove GM level (GM level = 0).
+# <example> gm testname 80
+#
+# id <account name>
+# Give the id of an account.
+#
+# info <account_id>
+# Display complete information of an account.
+#
+# kami <message>
+# Sends a broadcast message on all map-server (in yellow).
+# kamib <message>
+# Sends a broadcast message on all map-server (in blue).
+#
+# language <language>
+# Change the language of displaying.
+#
+# list/ls [start_id [end_id]]
+# Display a list of accounts.
+# 'start_id', 'end_id': indicate end and start identifiers.
+# Research by name is not possible with this command.
+# <example> list 10 9999999
+#
+# listBan/lsBan [start_id [end_id]]
+# Like list/ls, but only for accounts with state or banished
+#
+# listGM/lsGM [start_id [end_id]]
+# Like list/ls, but only for GM accounts
+#
+# listOK/lsOK [start_id [end_id]]
+# Like list/ls, but only for accounts without state and not banished
+#
+# memo <account_name> <memo>
+# Modify the memo of an account.
+# 'memo': it can have until 253 characters (with spaces or not).
+#
+# name <account_id>
+# Give the name of an account.
+#
+# passwd <account_name> <new_password>
+# Change the password of an account.
+# When new password is omitted, the input is done without displaying of the pressed keys.
+#
+# quit/end/exit
+# End of the program of administration
+#
+# reloadGM
+# Reload GM configuration file
+#
+# search <expression>
+# Seek accounts.
+# Displays the accounts whose names correspond.
+# search -r/-e/--expr/--regex <expression>
+# Seek accounts by regular expression.
+# Displays the accounts whose names correspond.
+#
+# sex <account_name> <sex>
+# Modify the sex of an account.
+# <example> sex testname Male
+#
+# state <account_name> <new_state> <error_message_#7>
+# Change the state of an account.
+# 'new_state': state is the state of the packet 0x006a + 1. The possibilities are:
+# 0 = Account ok 6 = Your Game's EXE file is not the latest version
+# 1 = Unregistered ID 7 = You are Prohibited to log in until %s
+# 2 = Incorrect Password 8 = Server is jammed due to over populated
+# 3 = This ID is expired 9 = No MSG
+# 4 = Rejected from Server 100 = This ID has been totally erased
+# 5 = You have been blocked by the GM Team
+# all other values are 'No MSG', then use state 9 please.
+# 'error_message_#7': message of the code error 6 = Your are Prohibited to log in until %s (packet 0x006a)
+#
+# timeadd <account_name> <modifier>
+# Adds or substracts time from the validity limit of an account.
+# Modifier is done as follows:
+# Adjustment value (-1, 1, +1, etc...)
+# Modified element:
+# a or y: year
+# m: month
+# j or d: day
+# h: hour
+# mn: minute
+# s: second
+# <example> timeadd testname +1m-2mn1s-6y
+# this example adds 1 month and 1 second, and substracts 2 minutes and 6 years at the same time.
+# NOTE: You can not modify a unlimited validity limit.
+# If you want modify it, you want probably create a limited validity limit.
+# So, at first, you must set the validity limit to a date/time.
+#
+# timeset <account_name> yyyy/mm/dd [hh:mm:ss]
+# Changes the validity limit of an account.
+# Default time: 23:59:59
+# timeset <account_name> 0
+# Gives an unlimited validity limit (0 = unlimited).
+#
+# unban/unbanish <account name>
+# Unban an account.
+# Same command of banset 0.
+#
+# unblock <account name>
+# Set state 0 (Account ok) to an account.
+# Same command of state <account_name> 0.
+#
+# version
+# Display the version of the login-server.
+#
+# who <account name>
+# Displays complete information of an account.
+#
+#-------------------------------------------------------------------------
+# Possibilities to execute ladmin in command line by usage of the software with a parameter:
+# ./ladmin --mode param1 ...
+#
+# --makesymlink -- Create the symbolic links for a use in shell
+# --add <account_name> <sex> <password> -- Create an account with the default email (or -a)
+# --ban yyyy/mm/dd hh:mm:ss <account_name> -- Change the final date of a banishment of an account (or -b)
+# --banadd <account_name> <modifier> -- Add or substract time from the final date of a banishment of an account (or - ba)
+# --banset <account_name> yyyy/mm/dd [hh:mm:ss] -- Change the final date of a banishment of an account (or -bs)
+# --banset <account_name> 0 -- Unbanish an account (or -bs)
+# --block <account_name> -- Set state 5 to an account (or -bl)
+# --check <account_name> <password> -- Check the validity of a password for an account (or -check)
+# --create <account_name> <sex> <email> <password> -- Create an account with email (or -c)
+# --del <account_name> -- Remove an account (or -d)
+# --email <account_name> <email> -- Modify an email of an account (or -e)
+# --getcount -- Give the number of players online on all char-servers (or -g)
+# --gm <account_name> <GM_level> -- Change the GM level of an account (or -gm)
+# --id <account_name> -- Give the id of an account (or -i)
+# --info <account_id> -- Display complete information of an account (or -info)
+# --kami <message> -- Sends a broadcast message on all map-server (in yellow).
+# --kamib <message> -- Sends a broadcast message on all map-server (in blue).
+# --language <language> -- Change the language of displaying (-lang).
+# --list [First_id [Last_id]] -- Display a list of accounts (or -l)
+# --listBan [start_id [end_id]] -- Display a list of accounts with state or banished (or -lBan)
+# --listGM [First_id [Last_id]] -- Display a list of GM accounts (or -lGM)
+# --listOK [start_id [end_id]] -- Display a list of accounts without state and not banished (or -lOK)
+# --memo <account_name> <memo> -- Modify the memo of an account (or -e)
+# --name <account_id> -- Give the name of an account (or -n)
+# --passwd <account_name> <new_password> -- Change the password of an account (or -p)
+# --reloadGM -- Reload GM configuration file (or -r)
+# --search <expression> -- Seek accounts (or -s)
+# --search -e/-r/--expr/--regex <expression> -- Seek accounts by REGEX (or -s)
+# --sex <account_name> <sex> -- Change the sex of an account (or -sex)
+# --state <account_name> <new_state> <error_message_#7> -- Change the state of an account (or -t)
+# --timeadd <account_name> <modifier> -- Add or substract time from the validity limit of an account (or - ta)
+# --timeset <account_name> yyyy/mm/dd [hh:mm:ss] -- Change the validify limit of an account (or -ts)
+# --timeset <account_name> 0 -- Give a unlimited validity limit (or -ts)
+# --unban/unbanish <account_name> -- Unban an account (or -uba)
+# --unblock <account_name> -- Set state 0 to an account (or -ubl)
+# --version -- Display the version of the login-server (or -v)
+# --who <account_name> -- Display complete information of an account (or -w)
+#
+# <example> ./ladmin --addaccount testname Male testpass
+#
+#-------------------------------------------------------------------------
+# Possibilities to execute ladmin with symbolic links in Shell
+# To create the symbolic links, execute ladmin with the '-- makesymlink' option.
+#
+# addaccount <account_name> <sex> <password> -- Create an account with the default email
+# banaccount yyyy/mm/dd hh:mm:ss <account_name> -- Change the final date of a banishment of an account
+# banaddaccount <account_name> <modifier> -- Add or substract time from the final date of a banishment of an account
+# bansetaccount <account_name> yyyy/mm/dd [hh:mm:ss] -- Change the final date of a banishment of an account
+# bansetaccount <account_name> 0 -- Unbanish an account
+# blockaccount <account_name> -- Set state 5 (blocked by the GM Team) to an account
+# checkaccount <account_name> <password> -- Check the validity of a password for an account
+# createaccount <account_name> <sex> <email> <password> -- Create an account with email
+# delaccount <account_name> -- Remove an account
+# emailaccount <account_name> <email> -- Modify an email of an account
+# getcount -- Give the number of players online on all char-servers
+# gmaccount <account_name> <GM_level> -- Change the GM level of an account
+# idaccount <account_name> -- Give the id of an account
+# infoaccount <account_id> -- Display complete information of an account
+# kami <message> -- Sends a broadcast message on all map-server (in yellow).
+# kamib <message> -- Sends a broadcast message on all map-server (in blue).
+# ladminlanguage <language> -- Change the language of displaying.
+# listaccount [First_id [Last_id]] -- Display a list of accounts
+# listBanaccount [start_id [end_id]] -- Display a list of accounts with state or banished
+# listGMaccount [First_id [Last_id]] -- Display a list of GM accounts
+# listOKaccount [start_id [end_id]] -- Display a list of accounts without state and not banished
+# loginserverversion -- Display the version of the login-server
+# memoaccount <account_name> <memo> -- Modify the memo of an account
+# nameaccount <account_id> -- Give the name of an account
+# passwdaccount <account_name> <new_password> -- Change the password of an account
+# reloadGM -- Reload GM configuration file
+# searchaccount <expression> -- Seek accounts
+# searchaccount -e/-r/--expr/--regex <expression> -- Seek accounts by REGEX
+# sexaccount <account_name> <sex> -- Change the sex of an account (or -sex)
+# stateaccount <account_name> <new_state> <error_message_#7> -- Change the state of an account
+# timeaddaccount <account_name> <modifier> -- Add or substract time from the validity limit of an account
+# timesetaccount <account_name> yyyy/mm/dd [hh:mm:ss] -- Change the validify limit of an account
+# timesetaccount <account_name> 0 -- Give a unlimited validity limit
+# unbanaccount <account_name> -- Unban an account
+# unblockaccount <account_name> -- Set state 0 (Account ok) to an account
+# whoaccount <account_name> -- Display complete information of an account
+# <exemple> ./addaccount testname Male testpass
+#
+#-------------------------------------------------------------------------
+# About the encoding:
+#
+# The Digest::MD5 module is necessary to use the encrypted password system.
+# When the software cannot found the Digest::MD5 module,
+# encoding is automatically disabled ($passenc=0), which allows
+# to use this program in any cases.
+#
+#-------------------------------------------------------------------------
+# How to use ladmin with UNIX:
+#
+# You excecute ladmin as a standard command.
+# <Example of preparation to have an access to ladmin>
+# $ mv ladmin ladmin_org
+# $ nkf -eLu ladmin_org > ladmin
+# $ chmod 700 ladmin
+# <Example to start directly ladmin>
+# $ perl ladmin
+#
+##########################################################################
+
+
+use strict;
+use IO::Socket;
+use Term::ReadLine;
+eval { use POSIX qw(:termios_h); };
+eval { use Digest::MD5 qw(md5); } if $passenc;
+$passenc = 0 if($@);
+
+my($ver) = "1.00";
+
+# Start of termios
+my($termios, $orgterml, $termlecho, $termlnoecho) = ();
+eval{
+ $termios = POSIX::Termios->new();
+ $termios->getattr(fileno(STDIN));
+ $orgterml = $termios->getlflag();
+ $termlecho = ECHO | ECHOK | ICANON;
+ $termlnoecho = $orgterml & ~$termlecho;
+};
+
+# Modification of termios for the displaying of passwords (no displays for pressed keys)
+sub cbreak() {
+ if ($termios) {
+ $termios->setlflag($termlnoecho);
+ $termios->setcc(VTIME, 1);
+ $termios->setattr(fileno(STDIN), TCSANOW);
+ }
+}
+# Modification of termios to return at the normal displaying (after input of the passwords)
+sub cooked() {
+ if ($termios) {
+ $termios->setlflag($orgterml);
+ $termios->setcc(VTIME,0);
+ $termios->setattr(fileno(STDIN),TCSANOW);
+ }
+}
+END{ cooked() }
+
+if ($defaultlanguage eq "F") {
+ print "Outil d'administration distance de eAthena V.$ver\n";
+} else {
+ print "EAthena login-server administration tool V.$ver\n";
+}
+
+# Creation of the symbolic links for call of the program in line command of the shell
+if ($ARGV[0] eq "--makesymlink") {
+ symlink $0, "loginserverversion";
+ symlink $0, "addaccount";
+ symlink $0, "banaccount";
+ symlink $0, "banaddaccount";
+ symlink $0, "bansetaccount";
+ symlink $0, "blockaccount";
+ symlink $0, "checkaccount";
+ symlink $0, "createaccount";
+ symlink $0, "delaccount";
+ symlink $0, "emailaccount";
+ symlink $0, "getcount";
+ symlink $0, "gmaccount";
+ symlink $0, "idaccount";
+ symlink $0, "infoaccount";
+ symlink $0, "kami";
+ symlink $0, "kamib";
+ symlink $0, "ladminlanguage";
+ symlink $0, "listaccount";
+ symlink $0, "listBanaccount";
+ symlink $0, "listGMaccount";
+ symlink $0, "listOKaccount";
+ symlink $0, "memoaccount";
+ symlink $0, "nameaccount";
+ symlink $0, "passwdaccount";
+ symlink $0, "reloadGM";
+ symlink $0, "searchaccount";
+ symlink $0, "sexaccount";
+ symlink $0, "stateaccount";
+ symlink $0, "timeaddaccount";
+ symlink $0, "timesetaccount";
+ symlink $0, "unbanaccount";
+ symlink $0, "unblockaccount";
+ symlink $0, "whoaccount";
+ if ($defaultlanguage eq "F") {
+ print "Liens symbliques cr鳬s.\n";
+ } else {
+ print "Symbolic links created.\n";
+ }
+ exit(0);
+}
+
+# Connection to the login-server
+my($so,$er) = ();
+eval{
+ $so = IO::Socket::INET->new(
+ PeerAddr=> $loginserverip,
+ PeerPort=> $loginserverport,
+# Proto => "tcp",
+ Timeout => $connecttimeout) or $er = 1;
+};
+if ($er || $@) {
+ if ($defaultlanguage eq "F") {
+ print "\nImpossible de se connecter au serveur de login [${loginserverip}:$loginserverport] !\n";
+ } else {
+ print "\nImpossible to have a connection with the login-server [${loginserverip}:$loginserverport] !\n";
+ }
+ print "$!\n"; # Displaying of the error
+ exit(2);
+}
+
+# Sending the administration password
+if ($passenc == 0) {
+ print $so pack("v2a24",0x7918,0,$loginserveradminpassword);
+ $so->flush();
+} else {
+ print $so pack("v",0x791a);
+ $so->flush();
+ my($buf) = readso(4);
+ if (unpack("v",$buf) != 0x01dc) {
+ if ($defaultlanguage eq "F") {
+ print "Erreur au login (馗hec de la cr饌tion de la clef md5).\n";
+ } else {
+ print "Error at login (failure of the md5 key creation).\n";
+ }
+ }
+ $buf = readso(unpack("x2v",$buf)-4);
+ my($md5bin) = md5(($passenc == 1) ? $buf.$loginserveradminpassword : $loginserveradminpassword.$buf);
+ print $so pack("v2a16",0x7918,$passenc,$md5bin);
+ $so->flush();
+}
+
+# Waiting of the server reply
+my($buf) = readso(3);
+
+if (unpack("v",$buf) != 0x7919 || unpack("x2c",$buf) != 0) {
+ if ($defaultlanguage eq "F") {
+ print "Erreur de login:\n";
+ print " - mot de passe incorrect,\n";
+ print " - syst鑪e d'administration non activ, ou\n";
+ print " - IP non autoris馥.\n";
+ } else {
+ print "Error at login:\n";
+ print " - incorrect password,\n";
+ print " - administration system not activated, or\n";
+ print " - unauthorised IP.\n";
+ }
+ quit();
+ exit(4);
+}
+
+if ($defaultlanguage eq "F") {
+ print "Connexion 騁ablie.\n";
+} else {
+ print "Established connection.\n";
+}
+
+#-------------------------------------------------------------------------
+# Here are checked the command lines with arguments and symbolic links (no prompt)
+
+if ($0 =~ /addaccount$/ ||
+ (($ARGV[0] eq "-a" || $ARGV[0] eq "--add") && ((shift @ARGV), 1))) {
+ my($r) = addaccount($ARGV[0], $ARGV[1], $ARGV[2]);
+ quit();
+ exit($r);
+} elsif ($0 =~ /banaccount$/ || $0 =~ /banishaccount$/ ||
+ (($ARGV[0] eq "-b" || $ARGV[0] eq "--ban" || $ARGV[0] eq "--banish") && ((shift @ARGV), 1))) {
+ my($r) = bansetaccount($ARGV[1], $ARGV[2], $ARGV[0]);
+ quit();
+ exit($r);
+} elsif ($0 =~ /banaddaccount$/ ||
+ (($ARGV[0] eq "-ba" || $ARGV[0] eq "--banadd") && ((shift @ARGV), 1))) {
+ my($r) = banaddaccount($ARGV[0], $ARGV[1]);
+ quit();
+ exit($r);
+} elsif ($0 =~ /bansetaccount$/ ||
+ (($ARGV[0] eq "-bs" || $ARGV[0] eq "--banset") && ((shift @ARGV), 1))) {
+ my($r) = bansetaccount($ARGV[0], $ARGV[1], $ARGV[2]);
+ quit();
+ exit($r);
+} elsif ($0 =~ /blockaccount$/ ||
+ (($ARGV[0] eq "-bl" || $ARGV[0] eq "--block") && ((shift @ARGV), 1))) {
+ my($r) = changestate($ARGV[0], 5, "");
+ quit();
+ exit($r);
+} elsif ($0 =~ /checkaccount$/ ||
+ (($ARGV[0] eq "-check" || $ARGV[0] eq "--check") && ((shift @ARGV), 1))) {
+ my($r) = checkaccount($ARGV[0], $ARGV[1]);
+ quit();
+ exit($r);
+} elsif ($0 =~ /createaccount$/ ||
+ (($ARGV[0] eq "-c" || $ARGV[0] eq "--create") && ((shift @ARGV), 1))) {
+ my($r) = createaccount($ARGV[0], $ARGV[1], $ARGV[2], $ARGV[3]);
+ quit();
+ exit($r);
+} elsif ($0 =~ /delaccount$/ ||
+ (($ARGV[0] eq "-d" || $ARGV[0] eq "--del") && ((shift @ARGV), 1))) {
+ my($r) = delaccount($ARGV[0]);
+ quit();
+ exit($r);
+} elsif ($0 =~ /emailaccount$/ ||
+ (($ARGV[0] eq "-e" || $ARGV[0] eq "--email") && ((shift @ARGV), 1))) {
+ my($r) = changeemail($ARGV[0], $ARGV[1]);
+ quit();
+ exit($r);
+} elsif ($0 =~ /getcount$/ ||
+ (($ARGV[0] eq "-g" || $ARGV[0] eq "--getcount") && ((shift @ARGV), 1))) {
+ my($r) = getlogincount();
+ quit();
+ exit($r);
+} elsif ($0 =~ /gmaccount$/ ||
+ (($ARGV[0] eq "-gm" || $ARGV[0] eq "--gm") && ((shift @ARGV), 1))) {
+ my($r) = changegmlevel($ARGV[0], $ARGV[1]);
+ quit();
+ exit($r);
+} elsif ($0 =~ /id$/ ||
+ (($ARGV[0] eq "-i" || $ARGV[0] eq "--id") && ((shift @ARGV), 1))) {
+ my($r) = idaccount($ARGV[0]);
+ quit();
+ exit($r);
+} elsif ($0 =~ /infoaccount$/ ||
+ (($ARGV[0] eq "-info" || $ARGV[0] eq "--info") && ((shift @ARGV), 1))) {
+ my($r) = infoaccount($ARGV[0]);
+ quit();
+ exit($r);
+} elsif ($0 =~ /kami$/ ||
+ (($ARGV[0] eq "-kami" || $ARGV[0] eq "--kami") && ((shift @ARGV), 1))) {
+ my($r) = sendbroadcast(0, $ARGV[0]);
+ quit();
+ exit($r);
+} elsif ($0 =~ /kamib$/ ||
+ (($ARGV[0] eq "-kamib" || $ARGV[0] eq "--kamib") && ((shift @ARGV), 1))) {
+ my($r) = sendbroadcast(0x10, $ARGV[0]);
+ quit();
+ exit($r);
+} elsif ($0 =~ /ladminlanguage$/ ||
+ (($ARGV[0] eq "-lang" || $ARGV[0] eq "--language") && ((shift @ARGV), 1))) {
+ my($r) = changelanguage($ARGV[0]);
+ quit();
+ exit($r);
+} elsif ($0 =~ /listaccount$/ ||
+ (($ARGV[0] eq "-l" || $ARGV[0] eq "--list") && ((shift @ARGV), 1))) {
+ my($r) = listaccount(int($ARGV[0]), int($ARGV[1]), 0); # 0: to list all
+ quit();
+ exit($r);
+} elsif ($0 =~ /listBanaccount$/ ||
+ (($ARGV[0] eq "-lBan" || $ARGV[0] eq "--listBan") && ((shift @ARGV), 1))) {
+ my($r) = listaccount(int($ARGV[0]), int($ARGV[1]), 3); # 3: to list only accounts with state or banished
+ quit();
+ exit($r);
+} elsif ($0 =~ /listGMaccount$/ ||
+ (($ARGV[0] eq "-lGM" || $ARGV[0] eq "--listGM") && ((shift @ARGV), 1))) {
+ my($r) = listaccount(int($ARGV[0]), int($ARGV[1]), 1); # 1: to list only GM
+ quit();
+ exit($r);
+} elsif ($0 =~ /listOKaccount$/ ||
+ (($ARGV[0] eq "-lOK" || $ARGV[0] eq "--listOK") && ((shift @ARGV), 1))) {
+ my($r) = listaccount(int($ARGV[0]), int($ARGV[1]), 4); # 4: to list only accounts without state and not banished
+ quit();
+ exit($r);
+} elsif ($0 =~ /loginserverversion$/ ||
+ (($ARGV[0] eq "-v" || $ARGV[0] eq "--version") && ((shift @ARGV), 1))) {
+ my($r) = checkloginversion();
+ quit();
+ exit($r);
+} elsif ($0 =~ /memoaccount$/ ||
+ (($ARGV[0] eq "-m" || $ARGV[0] eq "--memo") && ((shift @ARGV), 1))) {
+ my($r) = changememo($ARGV[0], $ARGV[1]);
+ quit();
+ exit($r);
+} elsif ($0 =~ /nameaccount$/ ||
+ (($ARGV[0] eq "-n" || $ARGV[0] eq "--name") && ((shift @ARGV), 1))) {
+ my($r) = nameaccount(int($ARGV[0]));
+ quit();
+ exit($r);
+} elsif ($0 =~ /passwdaccount$/ ||
+ (($ARGV[0] eq "-p" || $ARGV[0] eq "--passwd") && ((shift @ARGV), 1))) {
+ my($r) = changepasswd($ARGV[0], $ARGV[1]);
+ quit();
+ exit($r);
+} elsif ($0 =~ /reloadGM$/ ||
+ (($ARGV[0] eq "-r" || $ARGV[0] eq "--reloadGM") && ((shift @ARGV), 1))) {
+ my($r) = reloadGM();
+ quit();
+ exit($r);
+} elsif ($0 =~ /searchaccount$/ ||
+ (($ARGV[0] eq "-s" || $ARGV[0] eq "--search") && ((shift @ARGV), 1))) {
+ my($r) = searchaccount($ARGV[0], $ARGV[1]);
+ quit();
+ exit($r);
+} elsif ($0 =~ /sexaccount$/ ||
+ (($ARGV[0] eq "-sex" || $ARGV[0] eq "--sex") && ((shift @ARGV), 1))) {
+ my($r) = changesex($ARGV[0], $ARGV[1]);
+ quit();
+ exit($r);
+} elsif ($0 =~ /stateaccount$/ ||
+ (($ARGV[0] eq "-t" || $ARGV[0] eq "--state") && ((shift @ARGV), 1))) {
+ my($r) = changestate($ARGV[0], $ARGV[1], $ARGV[2]);
+ quit();
+ exit($r);
+} elsif ($0 =~ /timeaddaccount$/ ||
+ (($ARGV[0] eq "-ta" || $ARGV[0] eq "--timeadd") && ((shift @ARGV), 1))) {
+ my($r) = timeaddaccount($ARGV[0], $ARGV[1]);
+ quit();
+ exit($r);
+} elsif ($0 =~ /timesetaccount$/ ||
+ (($ARGV[0] eq "-ts" || $ARGV[0] eq "--timeset") && ((shift @ARGV), 1))) {
+ my($r) = timesetaccount($ARGV[0], $ARGV[1], $ARGV[2]);
+ quit();
+ exit($r);
+} elsif ($0 =~ /unbanaccount$/ || $0 =~ /unbanishaccount$/ ||
+ (($ARGV[0] eq "-uba" || $ARGV[0] eq "--unban" || $ARGV[0] eq "--unbanish") && ((shift @ARGV), 1))) {
+ my($r) = bansetaccount($ARGV[0], 0, "");
+ quit();
+ exit($r);
+} elsif ($0 =~ /unblockaccount$/ ||
+ (($ARGV[0] eq "-ubl" || $ARGV[0] eq "--unblock") && ((shift @ARGV), 1))) {
+ my($r) = changestate($ARGV[0], 0, "");
+ quit();
+ exit($r);
+} elsif ($0 =~ /whoaccount$/ ||
+ (($ARGV[0] eq "-w" || $ARGV[0] eq "--who") && ((shift @ARGV), 1))) {
+ my($r) = whoaccount($ARGV[0]);
+ quit();
+ exit($r);
+}
+
+#-------------------------------------------------------------------------
+if ($defaultlanguage eq "F") {
+ print "Lecture de la version du serveur de login...\n";
+} else {
+ print "Reading of the version of the login-server...\n";
+}
+checkloginversion();
+
+# Set the prompt line
+my($term) = new Term::ReadLine "ladmin";
+
+# Here begin the infinite loop to read prompts
+while(1) {
+ # Displaying of the prompt
+ print "\n";
+ if ($defaultlanguage eq "F") {
+ printf "\033[32mPour afficher les commandes, tapez 'Entr馥'.\033[0m\n";
+ } else {
+ printf "\033[32mTo list the commands, type 'enter'.\033[0m\n";
+ }
+ my($cmd) = $term->readline("ladmin> ");
+ # split and recovery of the input
+ chomp $cmd; # remove cariage return
+ $cmd =~ s/\x1b\[\d*\w//g; # remove (esc)[(number)(1alpha) = screen control sequence
+ $cmd =~ s/[\x00-\x1f]//g; # remove control char
+ my($command, $parameters) = split /\s+/,$cmd,2; # extract command and parameters
+ $command = lc($command); # command in lowercase
+ my(@paramlist) = split /\s+/,$parameters; # get list of parameters
+
+ if ($command eq "?" || $command eq "") {
+ $command = "aide" if ($defaultlanguage eq "F");
+ $command = "help" if ($defaultlanguage ne "F");
+ }
+
+ # Analyse of the command
+ eval {
+# help
+ if ("aide" =~ /^\Q$command/ && $command ne "a") { # check 1 letter command: 'aide' or 'add'?
+ displayhelp("aide", $paramlist[0]);
+ } elsif ("help" =~ /^\Q$command/) {
+ displayhelp("help", $paramlist[0]);
+
+# general commands
+ } elsif ("add" =~ /^\Q$command/ && $command ne "a") { # check 1 letter command: 'aide' or 'add'?
+ if (@paramlist = ($parameters =~ m/^"(.*)"\s+(\S+)\s+(.*)/)) {
+ addaccount($paramlist[0], $paramlist[1], $paramlist[2]); # <account_name> <sex> <password>
+ } elsif (@paramlist = ($parameters =~ m/^"(.*)"\s+(\S+)/)) {
+ addaccount($paramlist[0], $paramlist[1], ""); # <account_name> <sex> <password>
+ } elsif (@paramlist = ($parameters =~ m/^'(.*)'\s+(\S+)\s+(.*)/)) {
+ addaccount($paramlist[0], $paramlist[1], $paramlist[2]); # <account_name> <sex> <password>
+ } elsif (@paramlist = ($parameters =~ m/^'(.*)'\s+(\S+)/)) {
+ addaccount($paramlist[0], $paramlist[1], ""); # <account_name> <sex> <password>
+ } else {
+ @paramlist = split /\s+/,$parameters;
+ addaccount($paramlist[0], $paramlist[1], $paramlist[2]); # <account_name> <sex> <password>
+ }
+
+ } elsif ($command eq "ban" || ("banish" =~ /^\Q$command/ && length($command) >= 4)) {
+ if (@paramlist = ($parameters =~ m/^(\S+)\s+(\S+)\s+"(.*)"/)) { # yyyy/mm/dd hh:mm:ss <account_name>
+ bansetaccount($paramlist[2], $paramlist[0], $paramlist[1]); # <account_name> yyyy/mm/dd [hh:mm:ss]
+ } elsif (@paramlist = ($parameters =~ m/^(\S+)\s+(\S+)\s+'(.*)'/)) { # yyyy/mm/dd hh:mm:ss <account_name>
+ bansetaccount($paramlist[2], $paramlist[0], $paramlist[1]); # <account_name> yyyy/mm/dd [hh:mm:ss]
+ } else {
+ @paramlist = split /\s+/,$parameters,3; # yyyy/mm/dd hh:mm:ss <account_name>
+ bansetaccount($paramlist[2], $paramlist[0], $paramlist[1]); # <account_name> yyyy/mm/dd [hh:mm:ss]
+ }
+
+ } elsif (("banadd" =~ /^\Q$command/ || $command eq "ba") && $command ne "b") { # check 1 letter command: 'ba' or 'bs'?
+ if (@paramlist = ($parameters =~ m/^"(.*)"\s+(\S+)/)) {
+ banaddaccount($paramlist[0], $paramlist[1]); # <account_name> <modifier>
+ } elsif (@paramlist = ($parameters =~ m/^'(.*)'\s+(\S+)/)) {
+ banaddaccount($paramlist[0], $paramlist[1]); # <account_name> <modifier>
+ } else {
+ @paramlist = split /\s+/,$parameters;
+ banaddaccount($paramlist[0], $paramlist[1]); # <account_name> <modifier>
+ }
+
+ } elsif (("banset" =~ /^\Q$command/ || $command eq "bs") && $command ne "b") { # check 1 letter command: 'ba' or 'bs'?
+ if (@paramlist = ($parameters =~ m/^"(.*)"\s+(\S+)\s+(\S+)/)) {
+ bansetaccount($paramlist[0], $paramlist[1], $paramlist[2]); # <account_name> yyyy/mm/dd [hh:mm:ss]
+ } elsif (@paramlist = ($parameters =~ m/^"(.*)"\s+(\S+)/)) {
+ bansetaccount($paramlist[0], $paramlist[1], "23:59:59"); # <account_name> yyyy/mm/dd [hh:mm:ss]
+ } elsif (@paramlist = ($parameters =~ m/^'(.*)'\s+(\S+)\s+(\S+)/)) {
+ bansetaccount($paramlist[0], $paramlist[1], $paramlist[2]); # <account_name> yyyy/mm/dd [hh:mm:ss]
+ } elsif (@paramlist = ($parameters =~ m/^'(.*)'\s+(\S+)/)) {
+ bansetaccount($paramlist[0], $paramlist[1], "23:59:59"); # <account_name> yyyy/mm/dd [hh:mm:ss]
+ } else {
+ @paramlist = split /\s+/,$parameters;
+ bansetaccount($paramlist[0], $paramlist[1], $paramlist[2]); # <account_name> yyyy/mm/dd [hh:mm:ss]
+ }
+
+ } elsif ("block" =~ /^\Q$command/ && length($command) >= 2) {
+ if (@paramlist = ($parameters =~ m/^"(.*)"/)) {
+ changestate($paramlist[0], 5, ""); # <account_name> <new_state> <error_message_#7>
+ } elsif (@paramlist = ($parameters =~ m/^'(.*)'/)) {
+ changestate($paramlist[0], 5, ""); # <account_name> <new_state> <error_message_#7>
+ } else {
+ @paramlist = split /\s+/,$parameters,1;
+ changestate($paramlist[0], 5, ""); # <account_name> <new_state> <error_message_#7>
+ }
+
+ } elsif ("check" =~ /^\Q$command/ && $command ne "c") { # check 1 letter command: 'check' or 'create'?
+ if (@paramlist = ($parameters =~ m/^"(.*)"\s+(.*)/)) {
+ checkaccount($paramlist[0], $paramlist[1]); # <account_name> <password>
+ } elsif (@paramlist = ($parameters =~ m/^"(.*)"/)) {
+ checkaccount($paramlist[0], ""); # <account_name> <password>
+ } elsif (@paramlist = ($parameters =~ m/^'(.*)'\s+(.*)/)) {
+ checkaccount($paramlist[0], $paramlist[1]); # <account_name> <password>
+ } elsif (@paramlist = ($parameters =~ m/^'(.*)'/)) {
+ checkaccount($paramlist[0], ""); # <account_name> <password>
+ } else {
+ @paramlist = split /\s+/,$parameters;
+ checkaccount($paramlist[0], $paramlist[1]); # <account_name> <password>
+ }
+
+ } elsif ("create" =~ /^\Q$command/ && $command ne "c") { # check 1 letter command: 'check' or 'create'?
+ if (@paramlist = ($parameters =~ m/^"(.*)"\s+(\S+)\s+(\S+)\s+(.*)/)) {
+ createaccount($paramlist[0], $paramlist[1], $paramlist[2], $paramlist[3]); # <account_name> <sex> <email> <password>
+ } elsif (@paramlist = ($parameters =~ m/^"(.*)"\s+(\S+)\s+(\S+)/)) {
+ createaccount($paramlist[0], $paramlist[1], $paramlist[2], ""); # <account_name> <sex> <email> <password>
+ } elsif (@paramlist = ($parameters =~ m/^'(.*)'\s+(\S+)\s+(\S+)\s+(.*)/)) {
+ createaccount($paramlist[0], $paramlist[1], $paramlist[2], $paramlist[3]); # <account_name> <sex> <email> <password>
+ } elsif (@paramlist = ($parameters =~ m/^'(.*)'\s+(\S+)\s+(\S+)/)) {
+ createaccount($paramlist[0], $paramlist[1], $paramlist[2], ""); # <account_name> <sex> <email> <password>
+ } else {
+ @paramlist = split /\s+/,$parameters;
+ createaccount($paramlist[0], $paramlist[1], $paramlist[2], $paramlist[3]); # <account_name> <sex> <email> <password>
+ }
+
+ } elsif ("del" =~ /^\Q$command/ || "delete" =~ /^\Q$command/) {
+ if (@paramlist = ($parameters =~ m/^"(.*)"/)) {
+ delaccount($paramlist[0]); # <account_name>
+ } elsif (@paramlist = ($parameters =~ m/^'(.*)'/)) {
+ delaccount($paramlist[0]); # <account_name>
+ } else {
+ @paramlist = split /\s+/,$parameters,1;
+ delaccount($paramlist[0]); # <account_name>
+ }
+
+ } elsif ("email" =~ /^\Q$command/ && $command ne "e") { # check 1 letter command: 'email', 'end' or 'exit'?
+ if (@paramlist = ($parameters =~ m/^"(.*)"\s+(\S+)/)) {
+ changeemail($paramlist[0], $paramlist[1]); # <account_name> <email>
+ } elsif (@paramlist = ($parameters =~ m/^'(.*)'\s+(\S+)/)) {
+ changeemail($paramlist[0], $paramlist[1]); # <account_name> <email>
+ } else {
+ @paramlist = split /\s+/,$parameters;
+ changeemail($paramlist[0], $paramlist[1]); # <account_name> <email>
+ }
+
+ } elsif ("getcount" =~ /^\Q$command/ && $command ne "g") { # check 1 letter command: 'getcount' or 'gm'?
+ getlogincount();
+
+ } elsif ("gm" =~ /^\Q$command/ && $command ne "g") { # check 1 letter command: 'getcount' or 'gm'?
+ if (@paramlist = ($parameters =~ m/^"(.*)"\s+(\S+)/)) {
+ changegmlevel($paramlist[0], int($paramlist[1])); # <account_name> <GM_level>
+ } elsif (@paramlist = ($parameters =~ m/^"(.*)"/)) {
+ changegmlevel($paramlist[0], 0); # <account_name> <GM_level>
+ } elsif (@paramlist = ($parameters =~ m/^'(.*)'\s+(\S+)/)) {
+ changegmlevel($paramlist[0], int($paramlist[1])); # <account_name> <GM_level>
+ } elsif (@paramlist = ($parameters =~ m/^'(.*)'/)) {
+ changegmlevel($paramlist[0], 0); # <account_name> <GM_level>
+ } else {
+ @paramlist = split /\s+/,$parameters;
+ changegmlevel($paramlist[0], int($paramlist[1])); # <account_name> <GM_level>
+ }
+
+ } elsif ("id" =~ /^\Q$command/ && $command ne "i") { # check 1 letter command: 'id' or 'info'?
+ if (@paramlist = ($parameters =~ m/^"(.*)"/)) {
+ idaccount($paramlist[0]); # <account_name>
+ } elsif (@paramlist = ($parameters =~ m/^'(.*)'/)) {
+ idaccount($paramlist[0]); # <account_name>
+ } else {
+ @paramlist = split /\s+/,$parameters,1;
+ idaccount($paramlist[0]); # <account_name>
+ }
+
+ } elsif ("info" =~ /^\Q$command/ && $command ne "i") { # check 1 letter command: 'id' or 'info'?
+ infoaccount(int($paramlist[0])); # <account_id>
+
+ } elsif ($command eq "kami") { # check all letters command: 'kami' or 'kamib'?
+ @paramlist = split /\s+/,$parameters,1;
+ sendbroadcast(0, $paramlist[0]); # <type> <message>
+
+ } elsif ($command eq "kamib") { # check all letters command: 'kami' or 'kamib'?
+ @paramlist = split /\s+/,$parameters,1;
+ sendbroadcast(0x10, $paramlist[0]); # <type> <message>
+
+ } elsif ("language" =~ /^\Q$command/ && $command ne "l") { # check 1 letter command: 'list' or 'language'?
+ changelanguage($paramlist[0]); # <language>
+
+ } elsif (("list" =~ /^\Q$command/ || $command eq "ls") && $command ne "l") { # check 1 letter command: 'list' or 'language'?
+ listaccount(int($paramlist[0]), int($paramlist[1]), 0); # [start_id [end_id]] 0: to list all
+
+ } elsif (("listban" =~ /^\Q$command/ || $command eq "lsban") && $command ne "l") { # need to specificaly write Ban to have this list # check 1 letter command: 'list' or 'language'?
+ listaccount(int($paramlist[0]), int($paramlist[1]), 3); # [start_id [end_id]] 3: to list only accounts with state or banished
+
+ } elsif (("listgm" =~ /^\Q$command/ || $command eq "lsgm") && $command ne "l") { # need to specificaly write GM to have this list # check 1 letter command: 'list' or 'language'?
+ listaccount(int($paramlist[0]), int($paramlist[1]), 1); # [start_id [end_id]] 1: to list only GM
+
+ } elsif (("listok" =~ /^\Q$command/ || $command eq "lsok") && $command ne "l") { # need to specificaly write OK to have this list # check 1 letter command: 'list' or 'language'?
+ listaccount(int($paramlist[0]), int($paramlist[1]), 4); # [start_id [end_id]] 4: to list only accounts without state and not banished
+
+ } elsif ("memo" =~ /^\Q$command/) {
+ if (@paramlist = ($parameters =~ m/^"(.*)"\s+(.*)/)) {
+ changememo($paramlist[0], $paramlist[1]); # <account_name> <memo>
+ } elsif (@paramlist = ($parameters =~ m/^'(.*)'\s+(.*)/)) {
+ changememo($paramlist[0], $paramlist[1]); # <account_name> <memo>
+ } else {
+ @paramlist = split /\s+/,$parameters,2;
+ changememo($paramlist[0], $paramlist[1]); # <account_name> <memo>
+ }
+
+ } elsif ("name" =~ /^\Q$command/) {
+ nameaccount(int($paramlist[0])); # <account_id>
+
+ } elsif ("passwd" =~ /^\Q$command/ || "password" =~ /^\Q$command/) {
+ if (@paramlist = ($parameters =~ m/^"(.*)"\s+(.*)/)) {
+ changepasswd($paramlist[0], $paramlist[1]); # <account_name> <new_password>
+ } elsif (@paramlist = ($parameters =~ m/^"(.*)"/)) {
+ changepasswd($paramlist[0], ""); # <account_name> <new_password>
+ } elsif (@paramlist = ($parameters =~ m/^'(.*)'\s+(.*)/)) {
+ changepasswd($paramlist[0], $paramlist[1]); # <account_name> <new_password>
+ } elsif (@paramlist = ($parameters =~ m/^'(.*)'/)) {
+ changepasswd($paramlist[0], ""); # <account_name> <new_password>
+ } else {
+ @paramlist = split /\s+/,$parameters,2;
+ changepasswd($paramlist[0], $paramlist[1]); # <account_name> <new_password>
+ }
+
+ } elsif ("reloadgm" =~ /^\Q$command/) {
+ reloadGM();
+
+ } elsif ("search" =~ /^\Q$command/ && $command ne "s" && # check 1 letter command: 'search', 'state' or 'sex'?
+ $command ne "se") { # check 2 letters command: 'search' or 'sex'?
+ if (@paramlist = ($parameters =~ m/^(-{1,2}[re]\S*)\s+(.*)/)) {
+ searchaccount($paramlist[0], $paramlist[1]); # -r/-e/--expr/--regex <expression> | <expression>
+ } else {
+ @paramlist = split /\s+/,$parameters,1;
+ searchaccount($paramlist[0], ""); # -r/-e/--expr/--regex <expression> | <expression>
+ }
+
+ } elsif ("sex" =~ /^\Q$command/ && $command ne "s" && # check 1 letter command: 'search', 'state' or 'sex'?
+ $command ne "se") { # check 2 letters command: 'search' or 'sex'?
+ if (@paramlist = ($parameters =~ m/^"(.*)"\s+(\S+)/)) {
+ changesex($paramlist[0], $paramlist[1]); # <account_name> <sex>
+ } elsif (@paramlist = ($parameters =~ m/^'(.*)'\s+(\S+)/)) {
+ changesex($paramlist[0], $paramlist[1]); # <account_name> <sex>
+ } else {
+ @paramlist = split /\s+/,$parameters;
+ changesex($paramlist[0], $paramlist[1]); # <account_name> <sex>
+ }
+
+ } elsif ("state" =~ /^\Q$command/ && $command ne "s") { # check 1 letter command: 'search', 'state' or 'sex'?
+ if (@paramlist = ($parameters =~ m/^"(.*)"\s+(\d+)\s+(.*)/)) {
+ changestate($paramlist[0], int($paramlist[1]), $paramlist[2]); # <account_name> <new_state> <error_message_#7>
+ } elsif (@paramlist = ($parameters =~ m/^"(.*)"\s+(\d+)/)) {
+ changestate($paramlist[0], int($paramlist[1]), ""); # <account_name> <new_state> <error_message_#7>
+ } elsif (@paramlist = ($parameters =~ m/^'(.*)'\s+(\d+)\s+(.*)/)) {
+ changestate($paramlist[0], int($paramlist[1]), $paramlist[2]); # <account_name> <new_state> <error_message_#7>
+ } elsif (@paramlist = ($parameters =~ m/^'(.*)'\s+(\d+)/)) {
+ changestate($paramlist[0], int($paramlist[1]), ""); # <account_name> <new_state> <error_message_#7>
+ } else {
+ @paramlist = split /\s+/,$parameters,3;
+ changestate($paramlist[0], int($paramlist[1]), $paramlist[2]); # <account_name> <new_state> <error_message_#7>
+ }
+
+ } elsif (("timeadd" =~ /^\Q$command/ || $command eq "ta") && $command ne "t") { # check 1 letter command: 'ta' or 'ts'?
+ if (@paramlist = ($parameters =~ m/^"(.*)"\s+(\S+)/)) {
+ timeaddaccount($paramlist[0], $paramlist[1]); # <account_name> <modifier>
+ } elsif (@paramlist = ($parameters =~ m/^'(.*)'\s+(\S+)/)) {
+ timeaddaccount($paramlist[0], $paramlist[1]); # <account_name> <modifier>
+ } else {
+ @paramlist = split /\s+/,$parameters;
+ timeaddaccount($paramlist[0], $paramlist[1]); # <account_name> <modifier>
+ }
+
+ } elsif (("timeset" =~ /^\Q$command/ || $command eq "ts") && $command ne "t") { # check 1 letter command: 'ta' or 'ts'?
+ if (@paramlist = ($parameters =~ m/^"(.*)"\s+(\S+)\s+(\S+)/)) {
+ timesetaccount($paramlist[0], $paramlist[1], $paramlist[2]); # <account_name> yyyy/mm/dd [hh:mm:ss]
+ } elsif (@paramlist = ($parameters =~ m/^"(.*)"\s+(\S+)/)) {
+ timesetaccount($paramlist[0], $paramlist[1], "23:59:59"); # <account_name> yyyy/mm/dd [hh:mm:ss]
+ } elsif (@paramlist = ($parameters =~ m/^'(.*)'\s+(\S+)\s+(\S+)/)) {
+ timesetaccount($paramlist[0], $paramlist[1], $paramlist[2]); # <account_name> yyyy/mm/dd [hh:mm:ss]
+ } elsif (@paramlist = ($parameters =~ m/^'(.*)'\s+(\S+)/)) {
+ timesetaccount($paramlist[0], $paramlist[1], "23:59:59"); # <account_name> yyyy/mm/dd [hh:mm:ss]
+ } else {
+ @paramlist = split /\s+/,$parameters;
+ timesetaccount($paramlist[0], $paramlist[1], $paramlist[2]); # <account_name> yyyy/mm/dd [hh:mm:ss]
+ }
+
+ } elsif ($command eq "unban" || ("unbanish" =~ /^\Q$command/ && length($command) >= 4)) {
+ if (@paramlist = ($parameters =~ m/^"(.*)"/)) {
+ bansetaccount($paramlist[0], 0, ""); # <account_name> yyyy/mm/dd [hh:mm:ss]
+ } elsif (@paramlist = ($parameters =~ m/^'(.*)'/)) {
+ bansetaccount($paramlist[0], 0, ""); # <account_name> yyyy/mm/dd [hh:mm:ss]
+ } else {
+ @paramlist = split /\s+/,$parameters,1;
+ bansetaccount($paramlist[0], 0, ""); # <account_name> yyyy/mm/dd [hh:mm:ss]
+ }
+
+ } elsif ("unblock" =~ /^\Q$command/ && length($command) >= 4) {
+ if (@paramlist = ($parameters =~ m/^"(.*)"/)) {
+ changestate($paramlist[0], 0, ""); # <account_name> <new_state> <error_message_#7>
+ } elsif (@paramlist = ($parameters =~ m/^'(.*)'/)) {
+ changestate($paramlist[0], 0, ""); # <account_name> <new_state> <error_message_#7>
+ } else {
+ @paramlist = split /\s+/,$parameters,1;
+ changestate($paramlist[0], 0, ""); # <account_name> <new_state> <error_message_#7>
+ }
+
+ } elsif ("version" =~ /^\Q$command/) {
+ checkloginversion();
+
+ } elsif ("who" =~ /^\Q$command/) {
+ if (@paramlist = ($parameters =~ m/^"(.*)"/)) {
+ whoaccount($paramlist[0]); # <account_name>
+ } elsif (@paramlist = ($parameters =~ m/^'(.*)'/)) {
+ whoaccount($paramlist[0]); # <account_name>
+ } else {
+ @paramlist = split /\s+/,$parameters,1;
+ whoaccount($paramlist[0]); # <account_name>
+ }
+
+# quit
+ } elsif ("quit" =~ /^\Q$command/ ||
+ (("end" =~ /^\Q$command/ || "exit" =~ /^\Q$command/) && $command ne "e")) { # check 1 letter command: 'email', 'end' or 'exit'?
+ last;
+
+# unknown command
+ } elsif ($command) {
+ if ($defaultlanguage eq "F") {
+ print "Commande inconnue [".$command."]\n";
+ } else {
+ print "Unknown command [".$command."]\n";
+ }
+ }
+# $term->addhistory($cmd) if $command;
+ };
+ if ($@) {
+ if ($defaultlanguage eq "F") {
+ print "Erreur [".$command."]\n$@";
+ } else {
+ print "Error [".$command."]\n$@";
+ }
+ }
+};
+
+# End of the software
+quit();
+
+if ($defaultlanguage eq "F") {
+ print "Au revoir.\n";
+} else {
+ print "Bye.\n";
+}
+exit(0);
+
+#--------------------------------------------------------------------------
+
+# Sub-function: Displaying of the version of the login-server
+sub checkloginversion() {
+ print $so pack("v",30000); # 0x7530
+ $so->flush();
+ $buf = readso(10);
+ # Analyse du Packet
+ my($ret, $maver, $miver, $rev, $dev, $mod, $type, $mdver) = unpack("vc6v", $buf);
+ if ($ret != 30001) { #0x7531
+ if ($defaultlanguage eq "F") {
+ print "Probl鑪e de connexion au serveur (r駱onse incorrecte).\n";
+ } else {
+ print "Connection error to the server (incorrect answer).\n";
+ }
+ exit(6);
+ }
+
+ print " Login-Server [$loginserverip:$loginserverport]\n";
+ printf " eAthena version %s-%d.%d", ("stable", "dev")[$dev], $maver, $miver;
+ printf " revision %d", $rev if $rev;
+ printf "%s%d.\n", ("", "-mod")[$mod], $mdver;
+ return 0;
+}
+
+#--------------------------------------------------------------------------
+
+# Sub-function: Displaying of the help
+sub displayhelp() {
+ my($help, $receivedcommand) = @_;
+
+ my($command) = lc($receivedcommand); # command in lowercase
+
+ if ($command eq "") {
+ $command = "not a command"; # any value that is not a command
+ }
+
+ if ($command eq "?") {
+ $command = "aide" if ($defaultlanguage eq "F");
+ $command = "help" if ($defaultlanguage ne "F");
+ }
+
+ if ($help eq "aide") {
+ if ("aide" =~ /^\Q$command/ && $command ne "a") { # check 1 letter command: 'aide' or 'add'?
+ printf "aide/help/?\n";
+ printf " Affiche la description des commandes\n";
+ printf "aide/help/? [commande]\n";
+ printf " Affiche la description de la commande specifi馥\n";
+ } elsif ("help" =~ /^\Q$command/) {
+ printf "aide/help/?\n";
+ printf " Display the description of the commands\n";
+ printf "aide/help/? [command]\n";
+ printf " Display the description of the specified command\n";
+ } elsif ("add" =~ /^\Q$command/ && $command ne "a") { # check 1 letter command: 'aide' or 'add'?
+ printf "add <nomcompte> <sexe> <motdepasse>\n";
+ printf " Cr馥 un compte avec l'email par d馭aut (a\@a.com).\n";
+ printf " Concernant le sexe, seule la premi鑽e lettre compte (F ou M).\n";
+ printf " L'e-mail est a\@a.com (e-mail par d馭aut). C'est comme n'avoir aucun e-mail.\n";
+ printf " Lorsque motdepasse est omis, la saisie se fait sans que la frappe se voit.\n";
+ printf " <exemple> add testname Male testpass\n";
+ } elsif ($command eq "ban" || ("banish" =~ /^\Q$command/ && length($command) >= 4)) {
+ printf "ban/banish aaaa/mm/jj hh:mm:ss <nomcompte>\n";
+ printf " Change la date de fin de bannissement d'un compte.\n";
+ printf " La diff駻ence avec banset est la position du nom du compte.\n";
+ } elsif (("banadd" =~ /^\Q$command/ || $command eq "ba") && $command ne "b") { # check 1 letter command: 'ba' or 'bs'?
+ printf "banadd <nomcompte> <Modificateur>\n";
+ printf " Ajoute ou soustrait du temps la date de banissement d'un compte.\n";
+ printf " Les modificateurs sont construits comme suit:\n";
+ printf " Valeur d'ajustement (-1, 1, +1, etc...)\n";
+ printf " El駑ent modifi:\n";
+ printf " a ou y: ann馥\n";
+ printf " m: mois\n";
+ printf " j ou d: jour\n";
+ printf " h: heure\n";
+ printf " mn: minute\n";
+ printf " s: seconde\n";
+ printf " <exemple> banadd testname +1m-2mn1s-6a\n";
+ printf " Cette exemple ajoute 1 mois et une seconde, et soustrait 2 minutes\n";
+ printf " et 6 ans dans le m麥e temps.\n";
+ printf "NOTE: Si vous modifez la date de banissement d'un compte non bani,\n";
+ printf " vous indiquez comme date (le moment actuel +- les ajustements)\n";
+ } elsif (("banset" =~ /^\Q$command/ || $command eq "bs") && $command ne "b") { # check 1 letter command: 'ba' or 'bs'?
+ printf "banset <nomcompte> aaaa/mm/jj [hh:mm:ss]\n";
+ printf " Change la date de fin de bannissement d'un compte.\n";
+ printf " Heure par d馭aut: 23:59:59\n";
+ printf "banset <nomcompte> 0\n";
+ printf " D饕anni un compte (0 = de-banni).\n";
+ } elsif ("block" =~ /^\Q$command/ && length($command) >= 2) {
+ printf "block <nom compte>\n";
+ printf " Place le status d'un compte 5 (You have been blocked by the GM Team).\n";
+ printf " La commande est l'駲uivalent de state <nom_compte> 5.\n";
+ } elsif ("check" =~ /^\Q$command/ && $command ne "c") { # check 1 letter command: 'check' or 'create'?
+ printf "check <nomcompte> <motdepasse>\n";
+ printf " V駻ifie la validit d'un mot de passe pour un compte\n";
+ printf " NOTE: Le serveur n'enverra jamais un mot de passe.\n";
+ printf " C'est la seule m騁hode que vous poss馘ez pour savoir\n";
+ printf " si un mot de passe est le bon. L'autre m騁hode est\n";
+ printf " d'avoir un acc鑚 ('physique') au fichier des comptes.\n";
+ } elsif ("create" =~ /^\Q$command/ && $command ne "c") { # check 1 letter command: 'check' or 'create'?
+ printf "create <nomcompte> <sexe> <email> <motdepasse>\n";
+ printf " Comme la commande add, mais avec l'e-mail en plus.\n";
+ printf " <exemple> create testname Male mon\@mail.com testpass\n";
+ } elsif ("del" =~ /^\Q$command/ || "delete" =~ /^\Q$command/) {
+ printf "del <nomcompte>\n";
+ printf " Supprime un compte.\n";
+ printf " La commande demande confirmation. Apr鑚 confirmation, le compte est d騁ruit.\n";
+ } elsif ("email" =~ /^\Q$command/ && $command ne "e") { # check 1 letter command: 'email', 'end' or 'exit'?
+ printf "email <nomcompte> <email>\n";
+ printf " Modifie l'e-mail d'un compte.\n";
+ } elsif ("getcount" =~ /^\Q$command/ && $command ne "g") { # check 1 letter command: 'getcount' or 'gm'?
+ printf "getcount\n";
+ printf " Donne le nombre de joueurs en ligne par serveur de char.\n";
+ } elsif ("gm" =~ /^\Q$command/ && $command ne "g") { # check 1 letter command: 'getcount' or 'gm'?
+ printf "gm <nomcompte> [Niveau_GM]\n";
+ printf " Modifie le niveau de GM d'un compte.\n";
+ printf " Valeur par d馭aut: 0 (suppression du niveau de GM).\n";
+ printf " <exemple> gm nomtest 80\n";
+ } elsif ("id" =~ /^\Q$command/ && $command ne "i") { # check 1 letter command: 'id' or 'info'?
+ printf "id <nomcompte>\n";
+ printf " Donne l'id d'un compte.\n";
+ } elsif ("info" =~ /^\Q$command/ && $command ne "i") { # check 1 letter command: 'id' or 'info'?
+ printf "info <idcompte>\n";
+ printf " Affiche les informations sur un compte.\n";
+ } elsif ($command eq "kami") { # check all letters command: 'kami' or 'kamib'?
+ printf "kami <message>\n";
+ printf " Envoi un message g駭駻al sur tous les serveurs de map (en jaune).\n";
+ } elsif ($command eq "kamib") { # check all letters command: 'kami' or 'kamib'?
+ printf "kamib <message>\n";
+ printf " Envoi un message g駭駻al sur tous les serveurs de map (en bleu).\n";
+ } elsif ("language" =~ /^\Q$command/ && $command ne "l") { # check 1 letter command: 'list' or 'language'?
+ printf("language <langue>\n");
+ printf(" Change la langue d'affichage.\n");
+ printf(" Langues possibles: 'Fran軋is' ou 'English'.\n");
+ } elsif (("list" =~ /^\Q$command/ || $command eq "ls") && $command ne "l") { # check 1 letter command: 'list' or 'language'?
+ printf "list/ls [Premier_id [Dernier_id]]\n";
+ printf " Affiche une liste de comptes.\n";
+ printf " 'Premier_id', 'Dernier_id': indique les identifiants de d駱art et de fin.\n";
+ printf " La recherche par nom n'est pas possible avec cette commande.\n";
+ printf " <example> list 10 9999999\n";
+ } elsif (("listban" =~ /^\Q$command/ || $command eq "lsban") && $command ne "l") { # need to specificaly write Ban to have this list # check 1 letter command: 'list' or 'language'?
+ printf "listBan/lsBan [Premier_id [Dernier_id]]\n";
+ printf " Comme list/ls, mais seulement pour les comptes GM avec un statut ou bannis.\n";
+ } elsif (("listgm" =~ /^\Q$command/ || $command eq "lsgm") && $command ne "l") { # need to specificaly write GM to have this list # check 1 letter command: 'list' or 'language'?
+ printf "listGM/lsGM [Premier_id [Dernier_id]]\n";
+ printf " Comme list/ls, mais seulement pour les comptes GM.\n";
+ } elsif (("listok" =~ /^\Q$command/ || $command eq "lsok") && $command ne "l") { # need to specificaly write OK to have this list # check 1 letter command: 'list' or 'language'?
+ printf "listOK/lsOK [Premier_id [Dernier_id]]\n";
+ printf " Comme list/ls, mais seulement pour les comptes sans statut et non bannis.\n";
+ } elsif ("memo" =~ /^\Q$command/) {
+ printf "memo <nomcompte> <memo>\n";
+ printf " Modifie le m駑o d'un compte.\n";
+ printf " 'memo': Il peut avoir jusqu' 253 caract鑽es (avec des espaces ou non).\n";
+ } elsif ("name" =~ /^\Q$command/) {
+ printf "name <idcompte>\n";
+ printf " Donne le nom d'un compte.\n";
+ } elsif ("passwd" =~ /^\Q$command/ || "password" =~ /^\Q$command/) {
+ printf "passwd <nomcompte> <nouveaumotdepasse>\n";
+ printf " Change le mot de passe d'un compte.\n";
+ printf " Lorsque nouveaumotdepasse est omis,\n";
+ printf " la saisie se fait sans que la frappe ne se voit.\n";
+ } elsif ("reloadgm" =~ /^\Q$command/) {
+ printf "reloadGM\n";
+ printf " Reload GM configuration file\n";
+ } elsif ("search" =~ /^\Q$command/ && $command ne "s" && # check 1 letter command: 'search', 'state' or 'sex'?
+ $command ne "se") { # check 2 letters command: 'search' or 'sex'?
+ printf "search <expression>\n";
+ printf " Cherche des comptes.\n";
+ printf " Affiche les comptes dont les noms correspondent.\n";
+ printf "search -r/-e/--expr/--regex <expression>\n";
+ printf " Cherche des comptes par expression reguli鑽e.\n";
+ printf " Affiche les comptes dont les noms correspondent.\n";
+ } elsif ("sex" =~ /^\Q$command/ && $command ne "s" && # check 1 letter command: 'search', 'state' or 'sex'?
+ $command ne "se") { # check 2 letters command: 'search' or 'sex'?
+ printf "sex <nomcompte> <sexe>\n";
+ printf " Modifie le sexe d'un compte.\n";
+ printf " <exemple> sex testname Male\n";
+ } elsif ("state" =~ /^\Q$command/ && $command ne "s") { # check 1 letter command: 'search', 'state' or 'sex'?
+ printf "state <nomcompte> <nouveaustatut> <message_erreur_7>\n";
+ printf " Change le statut d'un compte.\n";
+ printf " 'nouveaustatut': Le statut est le m麥e que celui du packet 0x006a + 1.\n";
+ printf " les possibilit駸 sont:\n";
+ printf " 0 = Compte ok\n";
+ printf " 1 = Unregistered ID\n";
+ printf " 2 = Incorrect Password\n";
+ printf " 3 = This ID is expired\n";
+ printf " 4 = Rejected from Server\n";
+ printf " 5 = You have been blocked by the GM Team\n";
+ printf " 6 = Your Game's EXE file is not the latest version\n";
+ printf " 7 = You are Prohibited to log in until...\n";
+ printf " 8 = Server is jammed due to over populated\n";
+ printf " 9 = No MSG\n";
+ printf " 100 = This ID has been totally erased\n";
+ printf " all other values are 'No MSG', then use state 9 please.\n";
+ printf " 'message_erreur_7': message du code erreur 6 =\n";
+ printf " = Your are Prohibited to log in until... (packet 0x006a)\n";
+ } elsif (("timeadd" =~ /^\Q$command/ || $command eq "ta") && $command ne "t") { # check 1 letter command: 'ta' or 'ts'?
+ printf "timeadd <nomcompte> <modificateur>\n";
+ printf " Ajoute/soustrait du temps la limite de validit d'un compte.\n";
+ printf " Le modificateur est compos comme suit:\n";
+ printf " Valeur modificatrice (-1, 1, +1, etc...)\n";
+ printf " El駑ent modifi:\n";
+ printf " a ou y: ann馥\n";
+ printf " m: mois\n";
+ printf " j ou d: jour\n";
+ printf " h: heure\n";
+ printf " mn: minute\n";
+ printf " s: seconde\n";
+ printf " <exemple> timeadd testname +1m-2mn1s-6a\n";
+ printf " Cette exemple ajoute 1 mois et une seconde, et soustrait 2 minutes\n";
+ printf " et 6 ans dans le m麥e temps.\n";
+ printf "NOTE: Vous ne pouvez pas modifier une limite de validit illimit馥. Si vous\n";
+ printf " d駸irez le faire, c'est que vous voulez probablement cr馥r un limite de\n";
+ printf " validit limit馥. Donc, en premier, fix une limite de valitid.\n";
+ } elsif (("timeset" =~ /^\Q$command/ || $command eq "ts") && $command ne "t") { # check 1 letter command: 'ta' or 'ts'?
+ printf "timeset <nomcompte> aaaa/mm/jj [hh:mm:ss]\n";
+ printf " Change la limite de validit d'un compte.\n";
+ printf " Heure par d馭aut: 23:59:59\n";
+ printf "timeset <nomcompte> 0\n";
+ printf " Donne une limite de validit illimit馥 (0 = illimit馥).\n";
+ } elsif ($command eq "unban" || ("unbanish" =~ /^\Q$command/ && length($command) >= 4)) {
+ printf "unban/unbanish <nom compte>\n";
+ printf " Ote le banissement d'un compte.\n";
+ printf " La commande est l'駲uivalent de banset <nom_compte> 0.\n";
+ } elsif ("unblock" =~ /^\Q$command/ && length($command) >= 4) {
+ printf "unblock <nom compte>\n";
+ printf " Place le status d'un compte 0 (Compte ok).\n";
+ printf " La commande est l'駲uivalent de state <nom_compte> 0.\n";
+ } elsif ("version" =~ /^\Q$command/) {
+ printf "version\n";
+ printf " Affiche la version du login-serveur.\n";
+ } elsif ("who" =~ /^\Q$command/) {
+ printf "who <nomcompte>\n";
+ printf " Affiche les informations sur un compte.\n";
+ } elsif ("quit" =~ /^\Q$command/ ||
+ (("end" =~ /^\Q$command/ || "exit" =~ /^\Q$command/) && $command ne "e")) { # check 1 letter command: 'email', 'end' or 'exit'?\n";
+ printf "quit/end/exit\n";
+ printf " Fin du programme d'administration.\n";
+ } else {
+ if ($receivedcommand ne "") {
+ printf "Commande inconnue [%s] pour l'aide. Affichage de toutes les commandes.\n", $receivedcommand;
+ }
+ print << "ENDOFAIDE";
+ aide/help/? -- Affiche cet aide
+ aide/help/? [commande] -- Affiche l'aide de la commande
+ add <nomcompte> <sexe> <motdepasse> -- Cr馥 un compte (sans email)
+ ban/banish aaaa/mm/jj hh:mm:ss <nomcompte>-- Change la date finale de banismnt
+ banadd/ba <nomcompte> <modificateur> -- Ajout/soustrait du temps la
+ exemple: ba moncompte +1m-2mn1s-2y date finale de banissement
+ banset/bs <nomcompte> aaaa/mm/jj [hh:mm:ss] -- Change la date fin de banisemnt
+ banset/bs <nomcompte> 0 -- D-banis un compte.
+ block <nom compte> -- Mets le status d'un compte 5 (blocked by the GM Team)
+ check <nomcompte> <motdepasse> -- V駻ifie un mot de passe d'un compte
+ create <nomcompte> <sexe> <email> <motdepasse> -- Cr馥 un compte (avec email)
+ del <nomcompte> -- Supprime un compte
+ email <nomcompte> <email> -- Modifie l'e-mail d'un compte
+ getcount -- Donne le nb de joueurs en ligne
+ gm <nomcompte> [Niveau_GM] -- Modifie le niveau de GM d'un compte
+ id <nomcompte> -- Donne l'id d'un compte
+ info <idcompte> -- Affiche les infos sur un compte
+ kami <message> -- Envoi un message g駭駻al (en jaune)
+ kamib <message> -- Envoi un message g駭駻al (en bleu)
+ language <langue> -- Change la langue d'affichage.
+ list/ls [Premier_id [Dernier_id] ] -- Affiche une liste de comptes
+ listBan/lsBan [Premier_id [Dernier_id] ]-- Affiche une liste de comptes
+ avec un statut ou bannis
+ listGM/lsGM [Premier_id [Dernier_id] ] -- Affiche une liste de comptes GM
+ listOK/lsOK [Premier_id [Dernier_id] ] -- Affiche une liste de comptes
+ sans status et non bannis
+ memo <nomcompte> <memo> -- Modifie le memo d'un compte
+ name <idcompte> -- Donne le nom d'un compte
+ passwd <nomcompte> <nouveaumotdepasse> -- Change le mot de passe d'un compte
+ quit/end/exit -- Fin du programme d'administation
+ reloadGM -- Recharger le fichier de config des GM
+ search <expression> -- Cherche des comptes
+ search -e/-r/--expr/--regex <expression> -- Cherche des comptes par REGEX
+ sex <nomcompte> <sexe> -- Modifie le sexe d'un compte
+ state <nomcompte> <nouveaustatut> <messageerr7> -- Change le statut d'1 compte
+ timeadd/ta <nomcompte> <modificateur> -- Ajout/soustrait du temps la
+ exemple: ta moncompte +1m-2mn1s-2y limite de validit
+ timeset/ts <nomcompte> aaaa/mm/jj [hh:mm:ss] -- Change la limite de validit
+ timeset/ts <nomcompte> 0 -- limite de validit = illimit馥
+ unban/unbanish <nom compte> -- Ote le banissement d'un compte
+ unblock <nom compte> -- Mets le status d'un compte 0 (Compte ok)
+ version -- Donne la version du login-serveur
+ who <nomcompte> -- Affiche les infos sur un compte
+ENDOFAIDE
+ printf(" Note: Pour les noms de compte avec des espaces, tapez \"<nom compte>\" (ou ').\n");
+ }
+ } else {
+ if ("aide" =~ /^\Q$command/ && $command ne "a") { # check 1 letter command: 'aide' or 'add'?
+ printf "aide/help/?\n";
+ printf " Display the description of the commands\n";
+ printf "aide/help/? [command]\n";
+ printf " Display the description of the specified command\n";
+ } elsif ("help" =~ /^\Q$command/) {
+ printf "aide/help/?\n";
+ printf " Display the description of the commands\n";
+ printf "aide/help/? [command]\n";
+ printf " Display the description of the specified command\n";
+ } elsif ("add" =~ /^\Q$command/ && $command ne "a") { # check 1 letter command: 'aide' or 'add'?
+ printf "add <account_name> <sex> <password>\n";
+ printf " Create an account with the default email (a\@a.com).\n";
+ printf " Concerning the sex, only the first letter is used (F or M).\n";
+ printf " The e-mail is set to a\@a.com (default e-mail). It's like to have no e-mail.\n";
+ printf " When the password is omitted,\n";
+ printf " the input is done without displaying of the pressed keys.\n";
+ printf " <example> add testname Male testpass\n";
+ } elsif ($command eq "ban" || ("banish" =~ /^\Q$command/ && length($command) >= 4)) {
+ printf "ban/banish yyyy/mm/dd hh:mm:ss <account_name>\n";
+ printf " Changes the final date of a banishment of an account.\n";
+ printf " The difference with banset is the position of the account name.\n";
+ } elsif (("banadd" =~ /^\Q$command/ || $command eq "ba") && $command ne "b") { # check 1 letter command: 'ba' or 'bs'?
+ printf "banadd <account_name> <modifier>\n";
+ printf " Adds or substracts time from the final date of a banishment of an account.\n";
+ printf " Modifier is done as follows:\n";
+ printf " Adjustment value (-1, 1, +1, etc...)\n";
+ printf " Modified element:\n";
+ printf " a or y: year\n";
+ printf " m: month\n";
+ printf " j or d: day\n";
+ printf " h: hour\n";
+ printf " mn: minute\n";
+ printf " s: second\n";
+ printf " <example> banadd testname +1m-2mn1s-6y\n";
+ printf " this example adds 1 month and 1 second, and substracts 2 minutes\n";
+ printf " and 6 years at the same time.\n";
+ printf "NOTE: If you modify the final date of a non-banished account,\n";
+ printf " you fix the final date to (actual time +- adjustments)\n";
+ } elsif (("banset" =~ /^\Q$command/ || $command eq "bs") && $command ne "b") { # check 1 letter command: 'ba' or 'bs'?
+ printf "banset <account_name> yyyy/mm/dd [hh:mm:ss]\n";
+ printf " Changes the final date of a banishment of an account.\n";
+ printf " Default time: 23:59:59\n";
+ printf "banset <account_name> 0\n";
+ printf " Set a non-banished account (0 = unbanished).\n";
+ } elsif ("block" =~ /^\Q$command/ && length($command) >= 2) {
+ printf "block <account name>\n";
+ printf " Set state 5 (You have been blocked by the GM Team) to an account.\n";
+ printf " Same command of state <account_name> 5.\n";
+ } elsif ("check" =~ /^\Q$command/ && $command ne "c") { # check 1 letter command: 'check' or 'create'?
+ printf "check <account_name> <password>\n";
+ printf " Check the validity of a password for an account.\n";
+ printf " NOTE: Server will never sends back a password.\n";
+ printf " It's the only method you have to know if a password is correct.\n";
+ printf " The other method is to have a ('physical') access to the accounts file.\n";
+ } elsif ("create" =~ /^\Q$command/ && $command ne "c") { # check 1 letter command: 'check' or 'create'?
+ printf "create <account_name> <sex> <email> <password>\n";
+ printf " Like the 'add' command, but with e-mail moreover.\n";
+ printf " <example> create testname Male my\@mail.com testpass\n";
+ } elsif ("del" =~ /^\Q$command/ || "delete" =~ /^\Q$command/) {
+ printf "del <account_name>\n";
+ printf " Remove an account.\n";
+ printf " This order requires confirmation. After confirmation, the account is deleted.\n";
+ } elsif ("email" =~ /^\Q$command/ && $command ne "e") { # check 1 letter command: 'email', 'end' or 'exit'?
+ printf "email <account_name> <email>\n";
+ printf " Modify the e-mail of an account.\n";
+ } elsif ("getcount" =~ /^\Q$command/ && $command ne "g") { # check 1 letter command: 'getcount' or 'gm'?
+ printf "getcount\n";
+ printf " Give the number of players online on all char-servers.\n";
+ } elsif ("gm" =~ /^\Q$command/ && $command ne "g") { # check 1 letter command: 'getcount' or 'gm'?
+ printf "gm <account_name> [GM_level]\n";
+ printf " Modify the GM level of an account.\n";
+ printf " Default value remove GM level (GM level = 0).\n";
+ printf " <example> gm testname 80\n";
+ } elsif ("id" =~ /^\Q$command/ && $command ne "i") { # check 1 letter command: 'id' or 'info'?
+ printf "id <account_name>\n";
+ printf " Give the id of an account.\n";
+ } elsif ("info" =~ /^\Q$command/ && $command ne "i") { # check 1 letter command: 'id' or 'info'?
+ printf "info <account_id>\n";
+ printf " Display complete information of an account.\n";
+ } elsif ($command eq "kami") { # check all letters command: 'kami' or 'kamib'?
+ printf "kami <message>\n";
+ printf " Sends a broadcast message on all map-server (in yellow).\n";
+ } elsif ($command eq "kamib") { # check all letters command: 'kami' or 'kamib'?
+ printf "kamib <message>\n";
+ printf " Sends a broadcast message on all map-server (in blue).\n";
+ } elsif ("language" =~ /^\Q$command/ && $command ne "l") { # check 1 letter command: 'list' or 'language'?
+ printf("language <language>\n");
+ printf(" Change the language of displaying.\n");
+ printf(" Possible languages: Fran軋is or English.\n");
+ } elsif (("list" =~ /^\Q$command/ || $command eq "ls") && $command ne "l") { # check 1 letter command: 'list' or 'language'?
+ printf "list/ls [start_id [end_id]]\n";
+ printf " Display a list of accounts.\n";
+ printf " 'start_id', 'end_id': indicate end and start identifiers.\n";
+ printf " Research by name is not possible with this command.\n";
+ printf " <example> list 10 9999999\n";
+ } elsif (("listban" =~ /^\Q$command/ || $command eq "lsban") && $command ne "l") { # need to specificaly write Ban to have this list # check 1 letter command: 'list' or 'language'?
+ printf "listBan/lsBan [start_id [end_id]]\n";
+ printf " Like list/ls, but only for accounts with state or banished.\n";
+ } elsif (("listgm" =~ /^\Q$command/ || $command eq "lsgm") && $command ne "l") { # need to specificaly write GM to have this list # check 1 letter command: 'list' or 'language'?
+ printf "listGM/lsGM [start_id [end_id]]\n";
+ printf " Like list/ls, but only for GM accounts.\n";
+ } elsif (("listok" =~ /^\Q$command/ || $command eq "lsok") && $command ne "l") { # need to specificaly write OK to have this list # check 1 letter command: 'list' or 'language'?
+ printf "listOK/lsOK [start_id [end_id]]\n";
+ printf " Like list/ls, but only for accounts without state and not banished.\n";
+ } elsif ("memo" =~ /^\Q$command/) {
+ printf "memo <account_name> <memo>\n";
+ printf " Modify the memo of an account.\n";
+ printf " 'memo': it can have until 253 characters (with spaces or not).\n";
+ } elsif ("name" =~ /^\Q$command/) {
+ printf "name <account_id>\n";
+ printf " Give the name of an account.\n";
+ } elsif ("passwd" =~ /^\Q$command/ || "password" =~ /^\Q$command/) {
+ printf "passwd <account_name> <new_password>\n";
+ printf " Change the password of an account.\n";
+ printf " When new password is omitted,\n";
+ printf " the input is done without displaying of the pressed keys.\n";
+ } elsif ("reloadgm" =~ /^\Q$command/) {
+ printf "reloadGM\n";
+ printf " Reload GM configuration file\n";
+ } elsif ("search" =~ /^\Q$command/ && $command ne "s" && # check 1 letter command: 'search', 'state' or 'sex'?
+ $command ne "se") { # check 2 letters command: 'search' or 'sex'?
+ printf "search <expression>\n";
+ printf " Seek accounts.\n";
+ printf " Displays the accounts whose names correspond.\n";
+ printf "search -r/-e/--expr/--regex <expression>\n";
+ printf " Seek accounts by regular expression.\n";
+ printf " Displays the accounts whose names correspond.\n";
+ } elsif ("sex" =~ /^\Q$command/ && $command ne "s" && # check 1 letter command: 'search', 'state' or 'sex'?
+ $command ne "se") { # check 2 letters command: 'search' or 'sex'?
+ printf "sex <account_name> <sex>\n";
+ printf " Modify the sex of an account.\n";
+ printf " <example> sex testname Male\n";
+ } elsif ("state" =~ /^\Q$command/ && $command ne "s") { # check 1 letter command: 'search', 'state' or 'sex'?
+ printf "state <account_name> <new_state> <error_message_#7>\n";
+ printf " Change the state of an account.\n";
+ printf " 'new_state': state is the state of the packet 0x006a + 1.\n";
+ printf " The possibilities are:\n";
+ printf " 0 = Account ok\n";
+ printf " 1 = Unregistered ID\n";
+ printf " 2 = Incorrect Password\n";
+ printf " 3 = This ID is expired\n";
+ printf " 4 = Rejected from Server\n";
+ printf " 5 = You have been blocked by the GM Team\n";
+ printf " 6 = Your Game's EXE file is not the latest version\n";
+ printf " 7 = You are Prohibited to log in until...\n";
+ printf " 8 = Server is jammed due to over populated\n";
+ printf " 9 = No MSG\n";
+ printf " 100 = This ID has been totally erased\n";
+ printf " all other values are 'No MSG', then use state 9 please.\n";
+ printf " 'error_message_#7': message of the code error 6\n";
+ printf " = Your are Prohibited to log in until... (packet 0x006a)\n";
+ } elsif (("timeadd" =~ /^\Q$command/ || $command eq "ta") && $command ne "t") { # check 1 letter command: 'ta' or 'ts'?
+ printf "timeadd <account_name> <modifier>\n";
+ printf " Adds or substracts time from the validity limit of an account.\n";
+ printf " Modifier is done as follows:\n";
+ printf " Adjustment value (-1, 1, +1, etc...)\n";
+ printf " Modified element:\n";
+ printf " a or y: year\n";
+ printf " m: month\n";
+ printf " j or d: day\n";
+ printf " h: hour\n";
+ printf " mn: minute\n";
+ printf " s: second\n";
+ printf " <example> timeadd testname +1m-2mn1s-6y\n";
+ printf " this example adds 1 month and 1 second, and substracts 2 minutes\n";
+ printf " and 6 years at the same time.\n";
+ printf "NOTE: You can not modify a unlimited validity limit.\n";
+ printf " If you want modify it, you want probably create a limited validity limit.\n";
+ printf " So, at first, you must set the validity limit to a date/time.\n";
+ } elsif (("timeset" =~ /^\Q$command/ || $command eq "ts") && $command ne "t") { # check 1 letter command: 'ta' or 'ts'?
+ printf "timeset <account_name> yyyy/mm/dd [hh:mm:ss]\n";
+ printf " Changes the validity limit of an account.\n";
+ printf " Default time: 23:59:59\n";
+ printf "timeset <account_name> 0\n";
+ printf " Gives an unlimited validity limit (0 = unlimited).\n";
+ } elsif ($command eq "unban" || ("unbanish" =~ /^\Q$command/ && length($command) >= 4)) {
+ printf "unban/unbanish <account name>\n";
+ printf " Remove the banishment of an account.\n";
+ printf " This command works like banset <account_name> 0.\n";
+ } elsif ("unblock" =~ /^\Q$command/ && length($command) >= 4) {
+ printf "unblock <account name>\n";
+ printf " Set state 0 (Account ok) to an account.\n";
+ printf " This command works like state <account_name> 0.\n";
+ } elsif ("version" =~ /^\Q$command/) {
+ printf "version\n";
+ printf " Display the version of the login-server.\n";
+ } elsif ("who" =~ /^\Q$command/) {
+ printf "who <account_name>\n";
+ printf " Displays complete information of an account.\n";
+ } elsif ("quit" =~ /^\Q$command/ ||
+ (("end" =~ /^\Q$command/ || "exit" =~ /^\Q$command/) && $command ne "e")) { # check 1 letter command: 'email', 'end' or 'exit'?\n";
+ printf "quit/end/exit\n";
+ printf " End of the program of administration.\n";
+ } else {
+ if ($receivedcommand ne "") {
+ printf "Unknown command [%s] for help. Displaying of all commands.\n", $receivedcommand;
+ }
+ print << "ENDOFHELP";
+ aide/help/? -- Display this help
+ aide/help/? [command] -- Display the help of the command
+ add <account_name> <sex> <password> -- Create an account with default email
+ ban/banish yyyy/mm/dd hh:mm:ss <account_name> -- Change final date of a ban
+ banadd/ba <account_name> <modifier> -- Add or substract time from the final
+ example: ba apple +1m-2mn1s-2y date of a banishment of an account
+ banset/bs <account_name> yyyy/mm/dd [hh:mm:ss] -- Change final date of a ban
+ banset/bs <account_name> 0 -- Un-banish an account
+ block <account name> -- Set state 5 (blocked by the GM Team) to an account
+ check <account_name> <password> -- Check the validity of a password
+ create <account_name> <sex> <email> <passwrd> -- Create an account with email
+ del <account_name> -- Remove an account
+ email <account_name> <email> -- Modify an email of an account
+ getcount -- Give the number of players online
+ gm <account_name> [GM_level] -- Modify the GM level of an account
+ id <account_name> -- Give the id of an account
+ info <account_id> -- Display all information of an account
+ kami <message> -- Sends a broadcast message (in yellow)
+ kamib <message> -- Sends a broadcast message (in blue)
+ language <language> -- Change the language of displaying.
+ list/ls [First_id [Last_id]] -- Display a list of accounts
+ listBan/lsBan [First_id [Last_id]] -- Display a list of accounts
+ with state or banished
+ listGM/lsGM [First_id [Last_id]] -- Display a list of GM accounts
+ listOK/lsOK [First_id [Last_id]] -- Display a list of accounts
+ without state and not banished
+ memo <account_name> <memo> -- Modify the memo of an account
+ name <account_id> -- Give the name of an account
+ passwd <account_name> <new_password> -- Change the password of an account
+ quit/end/exit -- End of the program of administation
+ reloadGM -- Reload GM configuration file
+ search <expression> -- Seek accounts
+ search -e/-r/--expr/--regex <expressn> -- Seek accounts by regular-expression
+ sex <nomcompte> <sexe> -- Modify the sex of an account
+ state <account_name> <new_state> <error_message_#7> -- Change the state
+ timeadd/ta <account_name> <modifier> -- Add or substract time from the
+ example: ta apple +1m-2mn1s-2y validity limit of an account
+ timeset/ts <account_name> yyyy/mm/dd [hh:mm:ss] -- Change the validify limit
+ timeset/ts <account_name> 0 -- Give a unlimited validity limit
+ unban/unbanish <account name> -- Remove the banishment of an account
+ unblock <account name> -- Set state 0 (Account ok) to an account
+ version -- Gives the version of the login-server
+ who <account_name> -- Display all information of an account
+ENDOFHELP
+ printf(" Note: To use spaces in an account name, type \"<account name>\" (or ').\n");
+ }
+ }
+
+ return 0;
+}
+#--------------------------------------------------------------------------
+
+# Sub-function: Displaying of the accounts list
+sub listaccount() {
+ my($st, $ed, $listflag) = @_;
+ my($i);
+ my($n) = (0);
+ # 0123456789 01 01234567890123456789012301234 012345 0123456789012345678901234567
+ if ($defaultlanguage eq "F") {
+ print " id_compte GM nom_utilisateur sexe count statut\n";
+ } else {
+ print "account_id GM user_name sex count state\n";
+ }
+ print "-------------------------------------------------------------------------------\n";
+ while(1) {
+ print $so pack("vV2", 0x7920, $st, $ed);
+ $so->flush();
+ $buf = readso(4);
+ if (unpack("v", $buf) != 0x7921) {
+ if ($defaultlanguage eq "F") {
+ print "Probl鑪e de connexion au serveur (r駱onse incorrecte).\n";
+ } else {
+ print "Connection error to the server (incorrect answer).\n";
+ }
+ exit(10);
+ }
+ my($len) = unpack("x2v", $buf);
+ last if ($len <= 4);
+ for($i = 4; $i < $len; $i += 38) {
+ my(@dat) = unpack("VCa24cVV", readso(38));
+ $st = $dat[0] + 1;
+ if ($listflag == 0 ||
+ ($listflag == 1 && $dat[1] > 0) || # check GM flag
+ ($listflag == 3 && $dat[5] != 0) || # check with state or banished
+ ($listflag == 4 && $dat[5] == 0)) { # check without state and not banished
+ printf "%10d %2s %-24s%-5s %6d %-27s\n", $dat[0],
+ ($dat[1] == 0 ? " " : $dat[1]),
+ $dat[2],
+ ($defaultlanguage eq "F" ? ("Femme","Male","Servr")[$dat[3]] : ("Femal","Male","Servr")[$dat[3]]),
+ $dat[4],
+ (($defaultlanguage eq "F" ? "Compte Ok" : "Account OK"),
+ "Unregistered ID",
+ "Incorrect Password",
+ "This ID is expired",
+ "Rejected from Server",
+ "Blocked by the GM Team", # You have been blocked by the GM Team
+ "Your EXE file is too old", # Your Game's EXE file is not the latest version
+ "Banishement or\n Prohibited to login until %s", # You are Prohibited to log in until %s
+ "Server is over populated", # Server is jammed due to over populated
+ "No MSG",
+ "This ID is totally erased")[$dat[5] == 100 ? 10 : $dat[5]]; # This ID has been totally erased
+ $n++;
+ }
+ }
+ }
+ if ($defaultlanguage eq "F") {
+ if ($n == 0) {
+ print "Aucun compte trouv.\n";
+ } elsif ($n == 1) {
+ print "1 compte trouv.\n";
+ } else {
+ print "$n comptes trouv駸.\n";
+ }
+ } else {
+ if ($n == 0) {
+ print "No account found.\n";
+ } elsif ($n == 1) {
+ print "1 account found.\n";
+ } else {
+ print "$n accounts found.\n";
+ }
+ }
+ return 0;
+}
+
+#--------------------------------------------------------------------------
+
+# Sub-function: add an account with the default e-mail
+sub addaccount() {
+ my($userid, $sex, $passwd) = @_;
+ if ($userid eq "" || !defined($userid)) {
+ if ($defaultlanguage eq "F") {
+ print "Entrez un nom de compte svp.\n";
+ print "<exemple> add nomtest Male motdepassetest\n";
+ } else {
+ print "Please input an account name.\n";
+ print "<example> add testname Male testpass\n";
+ }
+ return 136;
+ }
+ if (verify_accountname($userid) == 0) {
+ return 102;
+ }
+# if ($userid =~ /[^A-Za-z0-9\@-_]/) {
+# if ($defaultlanguage eq "F") {
+# print "Caract鑽e interdit trouv dans le nom du compte ".$`."[${&}]${'}\n";
+# } else {
+# print "Illegal character found in the account name ".$`."[${&}]${'}\n";
+# }
+# return 101;
+# }
+ $sex = uc(substr($sex, 0, 1));
+ if ($sex !~ /^[MF]$/) {
+ if ($defaultlanguage eq "F") {
+ print "Sexe incorrect [$sex]. Entrez M ou F svp.\n";
+ } else {
+ print "Illegal gender [$sex]. Please input M or F.\n";
+ }
+ return 103;
+ }
+ if ($passwd eq "") {
+ return 108 if (($passwd = typepasswd()) eq "");
+ }
+ if (verify_password($passwd) == 0) {
+ return 104;
+ }
+ print $so pack("va24a24a1a40", 0x7930, $userid, $passwd, $sex, "");
+ $so->flush();
+ $buf = readso(2);
+ if (unpack("v", $buf) != 0x7931) {
+ if ($defaultlanguage eq "F") {
+ print "Probl鑪e de connexion au serveur (r駱onse incorrecte).\n";
+ } else {
+ print "Connection error to the server (incorrect answer).\n";
+ }
+ return 106;
+ }
+ $buf = readso(28);
+ if (unpack("V", $buf) == -1 || unpack("V", $buf) == 4294967295) {
+ if ($defaultlanguage eq "F") {
+ print "Echec la cr饌tion du compte [$userid]. Un compte identique existe d駛.\n";
+ } else {
+ print "Account [$userid] creation failed. Same account already exists.\n";
+ }
+ return 107;
+ } else {
+ if ($defaultlanguage eq "F") {
+ printf "Compte [$userid] cr鳬 avec succ鑚 [id: %d].\n", unpack("V",$buf);
+ } else {
+ printf "Account [$userid] is successfully created [id: %d].\n", unpack("V",$buf);
+ }
+ }
+ return 0;
+}
+
+#--------------------------------------------------------------------------
+
+# Sub-function: add an account with an e-mail
+sub createaccount() {
+ my($userid, $sex, $email, $passwd) = @_;
+ if ($userid eq "") {
+ if ($defaultlanguage eq "F") {
+ print "Entrez un nom de compte svp.\n";
+ print "<exemple> create nomtest Male mon\@email.com motdepassetest\n";
+ } else {
+ print "Please input an account name.\n";
+ print "<example> create testname Male my\@mail.com testpass\n";
+ }
+ return 136;
+ }
+ if (verify_accountname($userid) == 0) {
+ return 102;
+ }
+# if ($userid =~ /[^A-Za-z0-9\@-_]/) {
+# if ($defaultlanguage eq "F") {
+# print "Caract鑽e interdit trouv dans le nom du compte ".$`."[${&}]${'}\n";
+# } else {
+# print "Illegal character found in the account name ".$`."[${&}]${'}\n";
+# }
+# return 101;
+# }
+ $sex = uc(substr($sex, 0, 1));
+ if ($sex !~ /^[MF]$/) {
+ if ($defaultlanguage eq "F") {
+ print "Sexe incorrect [$sex]. Entrez M ou F svp.\n";
+ } else {
+ print "Illegal gender [$sex]. Please input M or F.\n";
+ }
+ return 103;
+ }
+ if (length($email) < 3) {
+ if ($defaultlanguage eq "F") {
+ print "Email trop courte [$email]. Entrez une e-mail valide svp.\n";
+ } else {
+ print "Email is too short [$email]. Please input a valid e-mail.\n";
+ }
+ return 109;
+ }
+ if (length($email) > 39) {
+ if ($defaultlanguage eq "F") {
+ print "Email trop longue [$email]. Entrez une e-mail de 39 caract鑽es maximum svp.\n";
+ } else {
+ print "Email is too long [$email]. Please input an e-mail with 39 bytes at the most.\n";
+ }
+ return 109;
+ }
+ if (verify_email($email) == 0) {
+ if ($defaultlanguage eq "F") {
+ print "Email incorrecte [$email]. Entrez une e-mail valide svp.\n";
+ } else {
+ print "Invalid email [$email]. Please input a valid e-mail.\n";
+ }
+ return 109;
+ }
+ if ($passwd eq "") {
+ return 108 if (($passwd = typepasswd()) eq "");
+ }
+ if (verify_password($passwd) == 0) {
+ return 104;
+ }
+ print $so pack("va24a24a1a40", 0x7930, $userid, $passwd, $sex, $email);
+ $so->flush();
+ $buf = readso(2);
+ if (unpack("v", $buf) != 0x7931) {
+ if ($defaultlanguage eq "F") {
+ print "Probl鑪e de connexion au serveur (r駱onse incorrecte).\n";
+ } else {
+ print "Connection error to the server (incorrect answer).\n";
+ }
+ return 106;
+ }
+ $buf = readso(28);
+ if (unpack("V", $buf) == -1 || unpack("V", $buf) == 4294967295) {
+ if ($defaultlanguage eq "F") {
+ print "Echec la cr饌tion du compte [$userid]. Un compte identique existe d駛.\n";
+ } else {
+ print "Account [$userid] creation failed. Same account already exists.\n";
+ }
+ return 107;
+ } else {
+ if ($defaultlanguage eq "F") {
+ printf "Compte [$userid] cr鳬 avec succ鑚 [id: %d].\n", unpack("V",$buf);
+ } else {
+ printf "Account [$userid] is successfully created [id: %d].\n", unpack("V",$buf);
+ }
+ }
+ return 0;
+}
+
+#--------------------------------------------------------------------------
+
+# Sub-function: deletion of an account
+sub delaccount() {
+ my($userid) = @_;
+ if ($userid eq "") {
+ if ($defaultlanguage eq "F") {
+ print "Entrez un nom de compte svp.\n";
+ print "<exemple> del nomtestasupprimer\n";
+ } else {
+ print "Please input an account name.\n";
+ print "<example> del testnametodelete\n";
+ }
+ return 136;
+ }
+ if (verify_accountname($userid) == 0) {
+ return 102;
+ }
+ if ($defaultlanguage eq "F") {
+ print "** Etes-vous vraiment sr de vouloir SUPPRIMER le compte [$userid]? (o/n) ";
+ } else {
+ print "** Are you really sure to DELETE account [$userid]? (y/n) ";
+ }
+ if (lc(substr(<STDIN>, 0, 1)) !~ /[oy]/) {
+ if ($defaultlanguage eq "F") {
+ print "Suppression annul馥\n.";
+ } else {
+ print "Deletion canceled\n";
+ }
+ return 121;
+ }
+ print $so pack("va24", 0x7932, $userid);
+ $so->flush();
+ $buf = readso(2);
+ if (unpack("v", $buf) != 0x7933) {
+ if ($defaultlanguage eq "F") {
+ print "Probl鑪e de connexion au serveur (r駱onse incorrecte).\n";
+ } else {
+ print "Connection error to the server (incorrect answer).\n";
+ }
+ return 122;
+ }
+ $buf = readso(28);
+ my($id2, $name) = unpack("Va24", $buf);
+ while (length($name) > 0 && substr($name, length($name)-1, 1) eq chr(0)) {
+ chop($name);
+ };
+ if ($id2 == -1 || $id2 == 4294967295) {
+ if ($defaultlanguage eq "F") {
+ print "Echec de la suppression du compte [$userid]. Le compte n'existe pas.\n";
+ } else {
+ print "Account [$userid] deletion failed. Account doesn't exist.\n";
+ }
+ return 123;
+ } else {
+ if ($defaultlanguage eq "F") {
+ print "Compte [$name][id: $id2] SUPPRIME avec succ鑚.\n";
+ } else {
+ print "Account [$name][id: $id2] is successfully DELETED.\n";
+ }
+ }
+ return 0;
+}
+
+#--------------------------------------------------------------------------
+
+# Sub-function: modification of a password
+sub changepasswd() {
+ my($userid, $passwd) = @_;
+ if ($userid eq "") {
+ if ($defaultlanguage eq "F") {
+ print "Entrez un nom de compte svp.\n";
+ print "<exemple> passwd nomtest nouveaumotdepasse\n";
+ } else {
+ print "Please input an account name.\n";
+ print "<example> passwd testname newpassword\n";
+ }
+ return 136;
+ }
+ if (verify_accountname($userid) == 0) {
+ return 102;
+ }
+ if ($passwd eq "") {
+ return 134 if (($passwd = typepasswd()) eq "");
+ }
+ if (verify_password($passwd) == 0) {
+ return 131;
+ }
+ print $so pack("va24a24", 0x7934, $userid,$passwd);
+ $so->flush();
+ $buf = readso(2);
+ if (unpack("v", $buf) != 0x7935) {
+ if ($defaultlanguage eq "F") {
+ print "Probl鑪e de connexion au serveur (r駱onse incorrecte).\n";
+ } else {
+ print "Connection error to the server (incorrect answer).\n";
+ }
+ return 132;
+ }
+ $buf = readso(28);
+ my($id2, $name) = unpack("Va24", $buf);
+ while (length($name) > 0 && substr($name, length($name)-1, 1) eq chr(0)) {
+ chop($name);
+ };
+ if ($id2 == -1 || $id2 == 4294967295) {
+ if ($defaultlanguage eq "F") {
+ print "Echec de la modification du mot de passe du compte [$userid].\n";
+ print "Le compte [$userid] n'existe pas.\n";
+ } else {
+ print "Account [$userid] password changing failed.\n";
+ print "Account [$userid] doesn't exist.\n";
+ }
+ return 133;
+ } else {
+ if ($defaultlanguage eq "F") {
+ print "Modification du mot de passe du compte [$name][id: $id2] r騏ssie.\n";
+ } else {
+ print "Account [$name][id: $id2] password successfully changed.\n";
+ }
+ }
+ return 130;
+}
+
+#--------------------------------------------------------------------------
+
+# Sub-function: modification of an account e-mail
+sub changeemail() {
+ my($userid, $email) = @_;
+ if ($userid eq "") {
+ if ($defaultlanguage eq "F") {
+ print "Entrez un nom de compte svp.\n";
+ print "<exemple> email testname nouveauemail\n";
+ } else {
+ print "Please input an account name.\n";
+ print "<example> email testname newemail\n";
+ }
+ return 136;
+ }
+ if (verify_accountname($userid) == 0) {
+ return 102;
+ }
+ if (length($email) < 3) {
+ if ($defaultlanguage eq "F") {
+ print "Email trop courte [$email]. Entrez une e-mail valide svp.\n";
+ } else {
+ print "Email is too short [$email]. Please input a valid e-mail.\n";
+ }
+ return 109;
+ }
+ if (length($email) > 39) {
+ if ($defaultlanguage eq "F") {
+ print "Email trop longue [$email]. Entrez une e-mail de 39 caract鑽es maximum svp.\n";
+ } else {
+ print "Email is too long [$email]. Please input an e-mail with 39 bytes at the most.\n";
+ }
+ return 109;
+ }
+ if (verify_email($email) == 0) {
+ if ($defaultlanguage eq "F") {
+ print "Email incorrect [$email]. Entrez une e-mail valide svp.\n";
+ } else {
+ print "Invalid email [$email]. Please input a valid e-mail.\n";
+ }
+ return 109;
+ }
+ print $so pack("va24a40", 0x7940, $userid, $email);
+ $so->flush();
+ $buf = readso(2);
+ if (unpack("v", $buf) != 0x7941) {
+ if ($defaultlanguage eq "F") {
+ print "Probl鑪e de connexion au serveur (r駱onse incorrecte).\n";
+ } else {
+ print "Connection error to the server (incorrect answer).\n";
+ }
+ return 162;
+ }
+ $buf = readso(28);
+ my($id2, $name) = unpack("Va24", $buf);
+ while (length($name) > 0 && substr($name, length($name)-1, 1) eq chr(0)) {
+ chop($name);
+ };
+ if ($id2 == -1 || $id2 == 4294967295) {
+ if ($defaultlanguage eq "F") {
+ print "Echec de la modification de l'e-mail du compte [$userid].\n";
+ print "Le compte [$userid] n'existe pas.\n";
+ } else {
+ print "Account [$userid] e-mail changing failed.\n";
+ print "Account [$userid] doesn't exist.\n";
+ }
+ return 133;
+ } else {
+ if ($defaultlanguage eq "F") {
+ print "Modification de l'e-mail du compte [$name][id: $id2] r騏ssie.\n";
+ } else {
+ print "Account [$name][id: $id2] e-mail successfully changed.\n";
+ }
+ }
+ return 160;
+}
+
+#--------------------------------------------------------------------------
+
+# Sub-function: search of accounts
+sub searchaccount() {
+ my($p1, $p2) = @_;
+ my($exp) = ("");
+ if ($p1 eq "-e" || $p1 eq "-r" || $p1 eq "--regex" || $p1 eq "--expr") {
+ if ($p2 eq "") {
+ if ($defaultlanguage eq "F") {
+ print "Entrez une expression r馮uli鑽e ou utilisez 'ls' pour avoir tous les comptes.\n";
+ } else {
+ print "Input a regular expression or use 'ls' to obtain all accounts.\n";
+ }
+ return 141;
+ }
+ $exp = $p2;
+ } else {
+ if ($p1 eq "") {
+ if ($defaultlanguage eq "F") {
+ print "Entrez une chane ou utilisez 'ls' pour avoir tous les comptes.\n";
+ } else {
+ print "Input a string or use 'ls' to obtain all accounts.\n";
+ }
+ return 141;
+ }
+ my($c) = 0;
+ $exp = lc($p1);
+ $exp =~ s/([\@])/\\$1/g;
+ $c += $exp =~ s/([\-\[\]])/\\$1/g;
+ $c += $exp =~ s/([\*\?])/.$1/g;
+ $c += $exp =~ s/\\\[(.)\\\-(.)\\\]/[$1-$2]/g;
+ $exp = "^$exp\$" if $c;
+ }
+ if (eval{ "" =~ /$exp/; }, $@) {
+ if ($defaultlanguage eq "F") {
+ print "Expression r馮uli鑽e non reconnue.\n";
+ } else {
+ print "Regular-Expression compiling failed.\n";
+ }
+ return 141;
+ }
+ my($i);
+ my($n, $st) = (0, 0);
+ # 0123456789 01 01234567890123456789012301234 012345 0123456789012345678901234567
+ if ($defaultlanguage eq "F") {
+ print " id_compte GM nom_utilisateur sexe count statut\n";
+ } else {
+ print "account_id GM user_name sex count state\n";
+ }
+ print "-------------------------------------------------------------------------------\n";
+ while(1) {
+ print $so pack("vV2", 0x7920, $st, 0);
+ $so->flush();
+ $buf = readso(4);
+ if (unpack("v", $buf) != 0x7921) {
+ if ($defaultlanguage eq "F") {
+ print "Probl鑪e de connexion au serveur (r駱onse incorrecte).\n";
+ } else {
+ print "Connection error to the server (incorrect answer).\n";
+ }
+ exit(10);
+ }
+ my($len) = unpack("x2v", $buf);
+ last if ($len <= 4);
+ for($i = 4; $i < $len; $i += 38) {
+ my(@dat) = unpack("VCa24cVV", readso(38));
+ $st = $dat[0] + 1;
+ next if (lc($dat[2]) !~ /$exp/);
+ printf "%10d %2s %-24s%-5s %6d %-27s\n", $dat[0],
+ ($dat[1] == 0 ? " " : $dat[1]),
+ $dat[2],
+ ($defaultlanguage eq "F" ? ("Femme","Male","Servr")[$dat[3]] : ("Femal","Male","Servr")[$dat[3]]),
+ $dat[4],
+ (($defaultlanguage eq "F" ? "Compte Ok" : "Account OK"),
+ "Unregistered ID",
+ "Incorrect Password",
+ "This ID is expired",
+ "Rejected from Server",
+ "Blocked by the GM Team", # You have been blocked by the GM Team
+ "Your EXE file is too old", # Your Game's EXE file is not the latest version
+ "Banishement or\n Prohibited to login until %s", # You are Prohibited to log in until %s
+ "Server is over populated", # Server is jammed due to over populated
+ "No MSG",
+ "This ID is totally erased")[$dat[5] == 100 ? 10 : $dat[5]]; # This ID has been totally erased
+ $n++;
+ }
+ }
+ if ($defaultlanguage eq "F") {
+ if ($n == 0) {
+ print "Aucun compte trouv.\n";
+ } elsif ($n == 1) {
+ print "1 compte trouv.\n";
+ } else {
+ print "$n comptes trouv駸.\n";
+ }
+ } else {
+ if ($n == 0) {
+ print "No account found.\n";
+ } elsif ($n == 1) {
+ print "1 account found.\n";
+ } else {
+ print "$n accounts found.\n";
+ }
+ }
+ return 0;
+}
+
+#--------------------------------------------------------------------------
+
+# Sub-function: modify the sex of an account
+sub changesex() {
+ my($userid, $sex) = @_;
+ if ($userid eq "" || !defined($userid)) {
+ if ($defaultlanguage eq "F") {
+ print "Entrez un nom de compte svp.\n";
+ print "<exemple> sex nomtest Male\n";
+ } else {
+ print "Please input an account name.\n";
+ print "<example> sex testname Male\n";
+ }
+ return 136;
+ }
+ if (verify_accountname($userid) == 0) {
+ return 102;
+ }
+# if ($userid =~ /[^A-Za-z0-9\@-_]/) {
+# if ($defaultlanguage eq "F") {
+# print "Caract鑽e interdit trouv dans le nom du compte ".$`."[${&}]${'}\n";
+# } else {
+# print "Illegal character found in the account name ".$`."[${&}]${'}\n";
+# }
+# return 101;
+# }
+ $sex = uc(substr($sex, 0, 1));
+ if ($sex !~ /^[MF]$/) {
+ if ($defaultlanguage eq "F") {
+ print "Sexe incorrect [$sex]. Entrez M ou F svp.\n";
+ } else {
+ print "Illegal gender [$sex]. Please input M or F.\n";
+ }
+ return 103;
+ }
+ print $so pack("va24a1", 0x793c, $userid, $sex);
+ $so->flush();
+ $buf = readso(2);
+ if (unpack("v", $buf) != 0x793d) {
+ if ($defaultlanguage eq "F") {
+ print "Probl鑪e de connexion au serveur (r駱onse incorrecte).\n";
+ } else {
+ print "Connection error to the server (incorrect answer).\n";
+ }
+ return 152;
+ }
+ $buf = readso(28);
+ my($id2, $name) = unpack("Va24", $buf);
+ while (length($name) > 0 && substr($name, length($name)-1, 1) eq chr(0)) {
+ chop($name);
+ };
+ if ($id2 == -1 || $id2 == 4294967295) {
+ if ($defaultlanguage eq "F") {
+ print "Echec du changement du sexe du compte [$userid].\n";
+ print "Le compte n'existe pas ou le sexe est d駛 celui demand.\n";
+ } else {
+ print "Account [$userid] sex changing failed.\n";
+ print "Account doesn't exist or the sex is already the good sex.\n";
+ }
+ } else {
+ if ($defaultlanguage eq "F") {
+ print "Sexe du compte [$name][id: $id2] chang avec succ鑚.\n";
+ } else {
+ print "Account [$name][id: $id2] sex successfully changed.\n";
+ }
+ }
+ return 0;
+}
+
+#--------------------------------------------------------------------------
+
+# Sub-function: modify the GM level of an account
+sub changegmlevel() {
+ my($userid, $gm_level) = @_;
+ if ($userid eq "" || !defined($userid)) {
+ if ($defaultlanguage eq "F") {
+ print "Entrez un nom de compte svp.\n";
+ print "<exemple> gm nomtest 80\n";
+ } else {
+ print "Please input an account name.\n";
+ print "<example> gm testname 80\n";
+ }
+ return 136;
+ }
+ if (verify_accountname($userid) == 0) {
+ return 102;
+ }
+# if ($userid =~ /[^A-Za-z0-9\@-_]/) {
+# if ($defaultlanguage eq "F") {
+# print "Caract鑽e interdit trouv dans le nom du compte ".$`."[${&}]${'}\n";
+# } else {
+# print "Illegal character found in the account name ".$`."[${&}]${'}\n";
+# }
+# return 101;
+# }
+ $gm_level = int($gm_level);
+ if ($gm_level < 0 || $gm_level > 99) {
+ if ($defaultlanguage eq "F") {
+ print "Niveau de GM incorrect [$gm_level]. Entrez une valeur de 0 99 svp.\n";
+ } else {
+ print "Illegal GM level [$gm_level]. Please input a value from 0 to 99.\n";
+ }
+ return 103;
+ }
+ print $so pack("va24C", 0x793e, $userid, $gm_level);
+ $so->flush();
+ $buf = readso(2);
+ if (unpack("v", $buf) != 0x793f) {
+ if ($defaultlanguage eq "F") {
+ print "Probl鑪e de connexion au serveur (r駱onse incorrecte).\n";
+ } else {
+ print "Connection error to the server (incorrect answer).\n";
+ }
+ return 152;
+ }
+ $buf = readso(28);
+ my($id2, $name) = unpack("Va24", $buf);
+ while (length($name) > 0 && substr($name, length($name)-1, 1) eq chr(0)) {
+ chop($name);
+ };
+ if ($id2 == -1 || $id2 == 4294967295) {
+ if ($defaultlanguage eq "F") {
+ print "Echec du changement du niveau de GM du compte [$userid].\n";
+ print "Le compte n'existe pas, le niveau de GM est d駛 celui demand,\n";
+ print "ou il est impossible de modifier le fichier des comptes GM.\n";
+ } else {
+ print "Account [$userid] GM level changing failed.\n";
+ print "Account doesn't exist, the GM level is already the good GM level,\n";
+ print "or it's impossible to modify the GM accounts file.\n";
+ }
+ } else {
+ if ($defaultlanguage eq "F") {
+ print "Niveau de GM du compte [$name][id: $id2] chang avec succ鑚.\n";
+ } else {
+ print "Account [$name][id: $id2] GM level successfully changed.\n";
+ }
+ }
+ return 0;
+}
+
+#--------------------------------------------------------------------------
+
+# Sub-function: Modification of a state
+sub changestate {
+ my($userid, $s, $error_message) = @_;
+ # Valid values: 0: ok, or value of the 0x006a packet + 1
+ if ($s eq "" || (($s < 0 || $s > 9) && $s != 100)) {
+ if ($defaultlanguage eq "F") {
+ print "Entrez une des valeurs suivantes svp:\n";
+ print " 0 = Compte ok 6 = Your Game's EXE file is not the latest version\n";
+ } else {
+ print "Please input one of these values:\n";
+ print " 0 = Account ok 6 = Your Game's EXE file is not the latest version\n";
+ }
+ print " 1 = Unregistered ID 7 = You are Prohibited to log in until %s\n";
+ print " 2 = Incorrect Password 8 = Server is jammed due to over populated\n";
+ print " 3 = This ID is expired 9 = No MSG\n";
+ print " 4 = Rejected from Server 100 = This ID has been totally erased\n";
+ print " 5 = You have been blocked by the GM Team\n";
+ if ($defaultlanguage eq "F") {
+ print "<exemples> state nomtest 5\n";
+ print " state nomtest 7 fin de votre ban\n";
+ print " block <nom du compte>\n";
+ print " unblock <nom du compte>\n";
+ } else {
+ print "<examples> state testname 5\n";
+ print " state testname 7 end of your ban\n";
+ print " block <account name>\n";
+ print " unblock <account name>\n";
+ }
+ return 151;
+ }
+ if ($userid eq "") {
+ if ($defaultlanguage eq "F") {
+ print "Entrez un nom de compte svp.\n";
+ print "<exemples> state nomtest 5\n";
+ print " state nomtest 7 fin de votre ban\n";
+ print " block <nom du compte>\n";
+ print " unblock <nom du compte>\n";
+ } else {
+ print "Please input an account name.\n";
+ print "<examples> state testname 5\n";
+ print " state testname 7 end of your ban\n";
+ print " block <account name>\n";
+ print " unblock <account name>\n";
+ }
+ return 136;
+ }
+ if (verify_accountname($userid) == 0) {
+ return 102;
+ }
+ if ($s != 7) {
+ $error_message = "-";
+ } else {
+ if (length($error_message) < 1) {
+ if ($defaultlanguage eq "F") {
+ print "Message d'erreur trop court. Entrez un message de 1-19 caract鑽es.\n";
+ } else {
+ print "Error message is too short. Please input a message of 1-19 bytes.\n";
+ }
+ return 102;
+ }
+ if (length($error_message) > 19) {
+ if ($defaultlanguage eq "F") {
+ print "Message d'erreur trop long. Entrez un message de 1-19 caract鑽es.\n";
+ } else {
+ print "Error message is too long. Please input a message of 1-19 bytes.\n";
+ }
+ return 102;
+ }
+ }
+ print $so pack("va24Va20", 0x7936, $userid, $s, $error_message);
+ $so->flush();
+ $buf = readso(2);
+ if (unpack("v", $buf) != 0x7937) {
+ if ($defaultlanguage eq "F") {
+ print "Probl鑪e de connexion au serveur (r駱onse incorrecte).\n";
+ } else {
+ print "Connection error to the server (incorrect answer).\n";
+ }
+ return 152;
+ }
+ $buf = readso(32);
+ my(@dat) = unpack("Va24V", $buf);
+ while (length($dat[1]) > 0 && substr($dat[1], length($dat[1])-1, 1) eq chr(0)) {
+ chop($dat[1]);
+ };
+ if ($dat[0] != -1 && $dat[0] != 4294967295) {
+ if ($defaultlanguage eq "F") {
+ print "Statut du compte [$dat[1]][id: $dat[0]] chang avec succ鑚 en [";
+ } else {
+ print "Account [$dat[1]][id: $dat[0]] state successfully changed in [";
+ }
+ print ((($defaultlanguage eq "F" ? "Compte Ok" : "Account OK"),
+ "Unregistered ID",
+ "Incorrect Password",
+ "This ID is expired",
+ "Rejected from Server",
+ "You have been blocked by the GM Team",
+ "Your Game's EXE file is not the latest version",
+ "You are Prohibited to log in until %s",
+ "Server is jammed due to over populated",
+ "No MSG",
+ "This ID has been totally erased")[$dat[2] == 100 ? 10 : $dat[2]]);
+ print "].\n";
+ } else {
+ if ($defaultlanguage eq "F") {
+ print "Echec du changement du statut du compte [$userid]. Le compte n'existe pas.\n";
+ } else {
+ print "Account [$userid] state changing failed. Account doesn't exist.\n";
+ }
+ }
+}
+
+#--------------------------------------------------------------------------
+
+# Sub-function: Displaying of the number of online players
+sub getlogincount {
+ # Request to the login-server
+ print $so pack("v", 0x7938);
+ $so->flush();
+
+ $buf = readso(4);
+ # Connection failed
+ if (unpack("v", $buf) != 0x7939) {
+ if ($defaultlanguage eq "F") {
+ print "Probl鑪e de connexion au serveur (r駱onse incorrecte).\n";
+ } else {
+ print "Connection error to the server (incorrect answer).\n";
+ }
+ exit(3);
+ }
+
+ # Get length of the received packet
+ my($len) = unpack("x2v", $buf) - 4;
+
+ # Read information of the servers
+ if ($len < 1) {
+ if ($defaultlanguage eq "F") {
+ printf " Aucun serveur n'est connect au login serveur.\n";
+ } else {
+ printf " No server is connected to the login-server.\n";
+ }
+ } else {
+ my(@slist) = ();
+ for(; $len > 0; $len -= 32) {
+ my($name, $count) = unpack("x6 a20 V", readso(32));
+ $name = substr($name, 0, index($name, "\0"));
+ push @slist, [ $name, $count ];
+ }
+ # Displaying of result
+ my($i);
+ if ($defaultlanguage eq "F") {
+ printf " Nombre de joueurs en ligne (serveur: nb):\n";
+ } else {
+ printf " Number of online players (server: number).\n";
+ }
+ foreach $i(@slist) {
+ printf " %-20s : %5d\n", $i->[0], $i->[1];
+ }
+ }
+}
+
+#--------------------------------------------------------------------------
+
+# Sub-function: Modification of a memo field
+sub changememo {
+ my($userid, $memo) = @_;
+ if ($userid eq "") {
+ if ($defaultlanguage eq "F") {
+ print "Entrez un nom de compte svp.\n";
+ print "<exemple> memo nomtest nouveau memo\n";
+ } else {
+ print "Please input an account name.\n";
+ print "<example> memo testname new memo\n";
+ }
+ return 136;
+ }
+ if (verify_accountname($userid) == 0) {
+ return 102;
+ }
+ if (length($memo) > 254) {
+ if ($defaultlanguage eq "F") {
+ print "M駑o trop long (".length($memo)." caract鑽es).\n";
+ print "Entrez un m駑o de 254 caract鑽es maximum svp.\n";
+ } else {
+ print "Memo is too long (".length($memo)." characters).\n";
+ print "Please input a memo of 254 bytes at the maximum.\n";
+ }
+ return 102;
+ }
+ if (length($memo) == 0) {
+ print $so pack("va24v", 0x7942, $userid, 0);
+ } else {
+ print $so pack("va24va".length($memo), 0x7942, $userid, length($memo), $memo);
+ }
+ $so->flush();
+ $buf = readso(2);
+ if (unpack("v", $buf) != 0x7943) {
+ if ($defaultlanguage eq "F") {
+ print "Probl鑪e de connexion au serveur (r駱onse incorrecte).\n";
+ } else {
+ print "Connection error to the server (incorrect answer).\n";
+ }
+ return 152;
+ }
+ $buf = readso(28);
+ my($id2, $name) = unpack("Va24", $buf);
+ while (length($name) > 0 && substr($name, length($name)-1, 1) eq chr(0)) {
+ chop($name);
+ };
+ if ($id2 == -1 || $id2 == 4294967295) {
+ if ($defaultlanguage eq "F") {
+ print "Echec du changement du m駑o du compte [$userid]. Le compte n'existe pas.\n";
+ } else {
+ print "Account [$userid] memo changing failed. Account doesn't exist.\n";
+ }
+ } else {
+ if ($defaultlanguage eq "F") {
+ print "M駑o du compte [$name][id: $id2] chang avec succ鑚.\n";
+ } else {
+ print "Account [$name][id: $id2] memo successfully changed.\n";
+ }
+ }
+}
+
+#--------------------------------------------------------------------------
+
+# Sub-function: Request to obtain an account id
+sub idaccount() {
+ my($userid) = @_;
+ if ($userid eq "") {
+ if ($defaultlanguage eq "F") {
+ print "Entrez un nom de compte svp.\n";
+ print "<exemple> id nomtest\n";
+ } else {
+ print "Please input an account name.\n";
+ print "<example> id testname\n";
+ }
+ return 136;
+ }
+ if (verify_accountname($userid) == 0) {
+ return 102;
+ }
+ print $so pack("va24", 0x7944, $userid);
+ $so->flush();
+ $buf = readso(2);
+ if (unpack("v", $buf) != 0x7945) {
+ if ($defaultlanguage eq "F") {
+ print "Probl鑪e de connexion au serveur (r駱onse incorrecte).\n";
+ } else {
+ print "Connection error to the server (incorrect answer).\n";
+ }
+ return 122;
+ }
+ $buf = readso(28);
+ my($id2, $name) = unpack("Va24", $buf);
+ while (length($name) > 0 && substr($name, length($name)-1, 1) eq chr(0)) {
+ chop($name);
+ };
+ if ($id2 == -1 || $id2 == 4294967295) {
+ if ($defaultlanguage eq "F") {
+ print "Impossible de trouver l'id du compte [$userid]. Le compte n'existe pas.\n";
+ } else {
+ print "Unabled to find the account [$userid] id. Account doesn't exist.\n";
+ }
+ return 123;
+ } else {
+ if ($defaultlanguage eq "F") {
+ print "Le compte [$name] a pour id: $id2.\n";
+ } else {
+ print "The account [$name] have the id: $id2.\n";
+ }
+ }
+ return 0;
+}
+
+#--------------------------------------------------------------------------
+
+# Sub-function: Request to obtain an account name
+sub nameaccount() {
+ my($id) = @_;
+ if ($id < 0) {
+ if ($defaultlanguage eq "F") {
+ print "Entrez un id ayant une valeur positive svp.\n";
+ } else {
+ print "Please input a positive value for the id.\n";
+ }
+ return 136;
+ }
+ print $so pack("vV", 0x7946, $id);
+ $so->flush();
+ $buf = readso(2);
+ if (unpack("v", $buf) != 0x7947) {
+ if ($defaultlanguage eq "F") {
+ print "Probl鑪e de connexion au serveur (r駱onse incorrecte).\n";
+ } else {
+ print "Connection error to the server (incorrect answer).\n";
+ }
+ return 122;
+ }
+ $buf = readso(28);
+ my($id2, $name) = unpack("Va24", $buf);
+ while (length($name) > 0 && substr($name, length($name)-1, 1) eq chr(0)) {
+ chop($name);
+ };
+ if (length($name) == 0 || $name eq "") {
+ if ($defaultlanguage eq "F") {
+ print "Impossible de trouver le nom du compte [id: $id2]. Le compte n'existe pas.\n";
+ } else {
+ print "Unabled to find the account [id: $id2] name. Account doesn't exist.\n";
+ }
+ return 123;
+ } else {
+ if ($defaultlanguage eq "F") {
+ print "Le compte [id: $id2] a pour nom: $name.\n";
+ } else {
+ print "The account [id: $id2] have the name: $name.\n";
+ }
+ }
+ return 0;
+}
+
+#--------------------------------------------------------------------------
+
+# Sub-function: Set a validity limit of an account
+sub timesetaccount() {
+ my($userid, $date, $time) = @_;
+ if ($userid eq "") {
+ if ($defaultlanguage eq "F") {
+ print "Entrez un nom de compte svp.\n";
+ print "<exemple>: timeset <nom_du_compte> aaaa/mm/jj [hh:mm:ss]\n";
+ print " timeset <nom_du_compte> 0 (0 = illimit)\n";
+ printf " Heure par d馭aut [hh:mm:ss]: 23:59:59\n";
+ } else {
+ print "Please input an account name.\n";
+ print "<example>: timeset <account_name> yyyy/mm/dd [hh:mm:ss]\n";
+ print " timeset <account_name> 0 (0 = unlimited)\n";
+ printf " Default time [hh:mm:ss]: 23:59:59\n";
+ }
+ return 136;
+ }
+ if (verify_accountname($userid) == 0) {
+ return 102;
+ }
+ my($year, $month, $day) = split(/[.\-\/]/, $date);
+ my($hour, $minute, $second) = split(/:/, $time);
+ if ($time eq "") {
+ $hour = 23;
+ $minute = 59;
+ $second = 59;
+ }
+ my($timestamp);
+ if ($year eq "" ||
+ ($year != 0 && ($month eq "" || $day eq "" || $hour eq "" || $minute eq "" || $second eq ""))) {
+ if ($defaultlanguage eq "F") {
+ print "Entrez 0 ou une date et une heure svp (format: 0 ou aaaa/mm/jj hh:mm:ss).\n";
+ } else {
+ print "Please input 0 or a date and a time (format: 0 or yyyy/mm/dd hh:mm:ss).\n";
+ }
+ return 102;
+ }
+ if ($year == 0) {
+ $timestamp = 0;
+ } else {
+ if ($year < 70) {
+ $year = $year + 100;
+ }
+ if ($year >= 1900) {
+ $year = $year - 1900;
+ }
+ if ($month < 1 || $month > 12) {
+ if ($defaultlanguage eq "F") {
+ print "Entrez un mois correct svp (entre 1 et 12).\n";
+ } else {
+ print "Please give a correct value for the month (from 1 to 12).\n";
+ }
+ return 102;
+ }
+ $month = $month - 1;
+ if ($day < 1 || $day > 31) {
+ if ($defaultlanguage eq "F") {
+ print "Entrez un jour correct svp (entre 1 et 31).\n";
+ } else {
+ print "Please give a correct value for the day (from 1 to 31).\n";
+ }
+ return 102;
+ }
+ if ((($month == 3 || $month == 5 || $month == 8 || $month == 10) && $day > 30) ||
+ ($month == 1 && $day > 29)) {
+ if ($defaultlanguage eq "F") {
+ print "Entrez un jour correct en fonction du mois svp.\n";
+ } else {
+ print "Please give a correct value for a day of this month.\n";
+ }
+ return 102;
+ }
+ if ($hour < 0 || $hour > 23) {
+ if ($defaultlanguage eq "F") {
+ print "Entrez une heure correcte svp (entre 0 et 23).\n";
+ } else {
+ print "Please give a correct value for the hour (from 0 to 23).\n";
+ }
+ return 102;
+ }
+ if ($minute < 0 || $minute > 59) {
+ if ($defaultlanguage eq "F") {
+ print "Entrez des minutes correctes svp (entre 0 et 59).\n";
+ } else {
+ print "Please give a correct value for the minutes (from 0 to 59).\n";
+ }
+ return 102;
+ }
+ if ($second < 0 || $second > 59) {
+ if ($defaultlanguage eq "F") {
+ print "Entrez des secondes correctes svp (entre 0 et 59).\n";
+ } else {
+ print "Please give a correct value for the seconds (from 0 to 59).\n";
+ }
+ return 102;
+ }
+ $timestamp = POSIX::mktime($second, $minute, $hour, $day, $month, $year, 0, 0, -1); # -1: no winter/summer time modification
+ if ($timestamp == undef) {
+ if ($defaultlanguage eq "F") {
+ print "Date incorrecte.\n";
+ print "Ajoutez 0 ou une date et une heure svp (format: 0 ou aaaa/mm/jj hh:mm:ss).\n";
+ } else {
+ print "Invalid date.\n";
+ print "Please add 0 or a date and a time (format: 0 or yyyy/mm/dd hh:mm:ss).\n";
+ }
+ return 102;
+ }
+ }
+
+ print $so pack("va24V", 0x7948, $userid, $timestamp);
+ $so->flush();
+ $buf = readso(2);
+ if (unpack("v", $buf) != 0x7949) {
+ if ($defaultlanguage eq "F") {
+ print "Probl鑪e de connexion au serveur (r駱onse incorrecte).\n";
+ } else {
+ print "Connection error to the server (incorrect answer).\n";
+ }
+ return 152;
+ }
+ $buf = readso(32);
+ my(@dat) = unpack("Va24V", $buf);
+ while (length($dat[1]) > 0 && substr($dat[1], length($dat[1])-1, 1) eq chr(0)) {
+ chop($dat[1]);
+ };
+ if ($dat[0] != -1 && $dat[0] != 4294967295) {
+ if ($defaultlanguage eq "F") {
+ print "Limite de validit du compte [$dat[1]][id: $dat[0]] chang馥 avec succ鑚 ".
+ ($dat[2] == 0 ? "en [illimit饐.\n" : "pour 黎re jusqu'au ".(POSIX::ctime($dat[2])));
+ } else {
+ print "Validity Limit of the account [$dat[1]][id: $dat[0]] successfully changed ".
+ ($dat[2] == 0 ? "to [unlimited].\n" : "to be until ".(POSIX::ctime($dat[2])));
+ }
+ # localtime($dat[2]) is also possible to display instead of POSIX::ctime.
+ } else {
+ if ($defaultlanguage eq "F") {
+ print "Echec du changement de la validit du compte [$userid]. Le compte n'existe pas.\n";
+ } else {
+ print "Account [$userid] validity limit changing failed. Account doesn't exist.\n";
+ }
+ }
+
+ return 0;
+}
+
+#--------------------------------------------------------------------------
+
+# Sub-function: Add/substract time to the validity limit of an account
+sub timeaddaccount() {
+ my($userid, $modif) = @_;
+ if ($userid eq "") {
+ if ($defaultlanguage eq "F") {
+ print "Entrez un nom de compte svp.\n";
+ print " <exemple> timeadd nomtest +1m-2mn1s-6y\n";
+ print " Cette exemple ajoute 1 mois et 1 seconde, et soustrait 2 minutes\n";
+ print " et 6 ans dans le m麥e temps.\n";
+ } else {
+ print "Please input an account name.\n";
+ print " <example> timeadd testname +1m-2mn1s-6y\n";
+ print " this example adds 1 month and 1 second, and substracts 2 minutes\n";
+ print " and 6 years at the same time.\n";
+ }
+ return 136;
+ }
+ if (verify_accountname($userid) == 0) {
+ return 102;
+ }
+ my($year, $month, $day) = (0, 0 ,0);
+ my($hour, $minute, $second) = (0, 0 ,0);
+
+ $modif = lc($modif);
+ while (length($modif) > 0) {
+ my($value) = int($modif);
+ if ($value == 0) {
+ $modif = substr($modif, 1);
+ } else {
+ if (substr($modif, 0, 1) =~ /[\-\+]/) {
+ $modif = substr($modif, 1);
+ }
+ while (length($modif) > 0 && substr($modif, 0, 1) =~ /[0-9]/) {
+ $modif = substr($modif, 1);
+ }
+ if (index($modif, "s") == 0) {
+ $second = $value;
+ $modif = substr($modif, 1);
+ } elsif (index($modif, "mn") == 0) {
+ $minute = $value;
+ $modif = substr($modif, 2);
+ } elsif (index($modif, "h") == 0) {
+ $hour = $value;
+ $modif = substr($modif, 1);
+ } elsif (index($modif, "d") == 0 || index($modif, "j") == 0) {
+ $day = $value;
+ $modif = substr($modif, 1);
+ } elsif (index($modif, "m") == 0) {
+ $month = $value;
+ $modif = substr($modif, 1);
+ } elsif (index($modif, "y") == 0 || index($modif, "a") == 0) {
+ $year = $value;
+ $modif = substr($modif, 1);
+ } else {
+ $modif = substr($modif, 1);
+ }
+ }
+ }
+
+ if ($defaultlanguage eq "F") {
+ print " ann馥: $year\n";
+ print " mois: $month\n";
+ print " jour: $day\n";
+ print " heure: $hour\n";
+ print " minute: $minute\n";
+ print " seconde: $second\n";
+ } else {
+ print " year: $year\n";
+ print " month: $month\n";
+ print " day: $day\n";
+ print " hour: $hour\n";
+ print " minute: $minute\n";
+ print " second: $second\n";
+ }
+
+ if ($year == 0 && $month == 0 && $day == 0 && $hour == 0 && $minute == 0 && $second == 0) {
+ if ($defaultlanguage eq "F") {
+ print "Vous devez entrer un ajustement avec cette commande, svp:\n";
+ print " Valeur d'ajustement (-1, 1, +1, etc...)\n";
+ print " Element modifi:\n";
+ print " a ou y: ann馥\n";
+ print " m: mois\n";
+ print " j ou d: jour\n";
+ print " h: heure\n";
+ print " mn: minute\n";
+ print " s: seconde\n";
+ print " <exemple> timeadd nomtest +1m-2mn1s-6y\n";
+ print " Cette exemple ajoute 1 mois et 1 seconde, et soustrait 2 minutes\n";
+ print " et 6 ans dans le m麥e temps.\n";
+ } else {
+ print "Please give an adjustment with this command:\n";
+ print " Adjustment value (-1, 1, +1, etc...)\n";
+ print " Modified element:\n";
+ print " a or y: year\n";
+ print " m: month\n";
+ print " j or d: day\n";
+ print " h: hour\n";
+ print " mn: minute\n";
+ print " s: second\n";
+ print " <example> timeadd testname +1m-2mn1s-6y\n";
+ print " this example adds 1 month and 1 second, and substracts 2 minutes\n";
+ print " and 6 years at the same time.\n";
+ }
+ return 137;
+ }
+ if ($year > 127 || $year < -127) {
+ if ($defaultlanguage eq "F") {
+ print "Entrez un ajustement d'ann馥s correct (de -127 127), svp.\n";
+ } else {
+ print "Please give a correct adjustment for the years (from -127 to 127).\n";
+ }
+ return 137;
+ }
+ if ($month > 255 || $month < -255) {
+ if ($defaultlanguage eq "F") {
+ print "Entrez un ajustement de mois correct (de -255 255), svp.\n";
+ } else {
+ print "Please give a correct adjustment for the months (from -255 to 255).\n";
+ }
+ return 137;
+ }
+ if ($day > 32767 || $day < -32767) {
+ if ($defaultlanguage eq "F") {
+ print "Entrez un ajustement de jours correct (de -32767 32767), svp.\n";
+ } else {
+ print "Please give a correct adjustment for the days (from -32767 to 32767).\n";
+ }
+ return 137;
+ }
+ if ($hour > 32767 || $hour < -32767) {
+ if ($defaultlanguage eq "F") {
+ print "Entrez un ajustement d'heures correct (de -32767 32767), svp.\n";
+ } else {
+ print "Please give a correct adjustment for the hours (from -32767 to 32767).\n";
+ }
+ return 137;
+ }
+ if ($minute > 32767 || $minute < -32767) {
+ if ($defaultlanguage eq "F") {
+ print "Entrez un ajustement de minutes correct (de -32767 32767), svp.\n";
+ } else {
+ print "Please give a correct adjustment for the minutes (from -32767 to 32767).\n";
+ }
+ return 137;
+ }
+ if ($second > 32767 || $second < -32767) {
+ if ($defaultlanguage eq "F") {
+ print "Entrez un ajustement de secondes correct (de -32767 32767), svp.\n";
+ } else {
+ print "Please give a correct adjustment for the seconds (from -32767 to 32767).\n";
+ }
+ return 137;
+ }
+
+ print $so pack("va24vvvvvv", 0x7950, $userid, $year, $month, $day, $hour, $minute, $second);
+ $so->flush();
+ $buf = readso(2);
+ if (unpack("v", $buf) != 0x7951) {
+ if ($defaultlanguage eq "F") {
+ print "Probl鑪e de connexion au serveur (r駱onse incorrecte).\n";
+ } else {
+ print "Connection error to the server (incorrect answer).\n";
+ }
+ return 152;
+ }
+ $buf = readso(32);
+ my(@dat) = unpack("Va24V", $buf);
+ while (length($dat[1]) > 0 && substr($dat[1], length($dat[1])-1, 1) eq chr(0)) {
+ chop($dat[1]);
+ };
+ if ($dat[0] == -1 || $dat[0] == 4294967295) {
+ if ($defaultlanguage eq "F") {
+ print "Echec du changement de la validit du compte [$userid]. Le compte n'existe pas.\n";
+ } else {
+ print "Account [$userid] validity limit changing failed. Account doesn't exist.\n";
+ }
+ } elsif ($dat[2] == 0) {
+ if ($defaultlanguage eq "F") {
+ print "Limite de validit du compte [$dat[1]][id: $dat[0]] inchang馥.\n";
+ print "Le compte a une validit illimit馥 ou\n";
+ print "la modification est impossible avec les ajustements demand駸.\n";
+ } else {
+ print "Validity limit of the account [$dat[1]][id: $dat[0]] unchanged.\n";
+ print "The account have an unlimited validity limit or\n";
+ print "the changing is impossible with the proposed adjustments.\n";
+ }
+ } else {
+ if ($defaultlanguage eq "F") {
+ print "Limite de validit du compte [$dat[1]][id: $dat[0]] chang馥 avec succ鑚 ".
+ ($dat[2] == 0 ? "en [illimit饐.\n" : "pour 黎re jusqu'au ".(POSIX::ctime($dat[2])));
+ } else {
+ print "Validity limit of the account [$dat[1]][id: $dat[0]] successfully changed ".
+ ($dat[2] == 0 ? "to [unlimited].\n" : "to be until ".(POSIX::ctime($dat[2])));
+ }
+ # localtime($dat[2]) is also possible to display instead of POSIX::ctime.
+ }
+
+ return 0;
+}
+
+#--------------------------------------------------------------------------
+
+# Sub-function: Set the final date of a banishment of an account
+sub bansetaccount() {
+ my($userid, $date, $time) = @_;
+ if ($userid eq "") {
+ if ($defaultlanguage eq "F") {
+ print "Entrez un nom de compte svp.\n";
+ print "<exemple>: banset <nom_du_compte> aaaa/mm/jj [hh:mm:ss]\n";
+ print " banset <nom_du_compte> 0 (0 = d-bani)\n";
+ print " ban/banish aaaa/mm/jj hh:mm:ss <nom du compte>\n";
+ print " unban/unbanish <nom du compte>\n";
+ printf " Heure par d馭aut [hh:mm:ss]: 23:59:59\n";
+ } else {
+ print "Please input an account name.\n";
+ print "<example>: banset <account_name> yyyy/mm/dd [hh:mm:ss]\n";
+ print " banset <account_name> 0 (0 = un-banished)\n";
+ print " ban/banish yyyy/mm/dd hh:mm:ss <account name>\n";
+ print " unban/unbanish <account name>\n";
+ printf " Default time [hh:mm:ss]: 23:59:59\n";
+ }
+ return 136;
+ }
+ if (verify_accountname($userid) == 0) {
+ return 102;
+ }
+ my($year, $month, $day) = split(/[.\-\/]/, $date);
+ my($hour, $minute, $second) = split(/:/, $time);
+ if ($time eq "") {
+ $hour = 23;
+ $minute = 59;
+ $second = 59;
+ }
+ my($timestamp);
+ if ($year eq "" ||
+ ($year != 0 && ($month eq "" || $day eq "" || $hour eq "" || $minute eq "" || $second eq ""))) {
+ if ($defaultlanguage eq "F") {
+ print "Entrez 0 ou une date et une heure svp (format: 0 ou aaaa/mm/jj hh:mm:ss).\n";
+ } else {
+ print "Please input 0 or a date and a time (format: 0 or yyyy/mm/dd hh:mm:ss).\n";
+ }
+ return 102;
+ }
+ if ($year == 0) {
+ $timestamp = 0;
+ } else {
+ if ($year < 70) {
+ $year = $year + 100;
+ }
+ if ($year >= 1900) {
+ $year = $year - 1900;
+ }
+ if ($month < 1 || $month > 12) {
+ if ($defaultlanguage eq "F") {
+ print "Entrez un mois correct svp (entre 1 et 12).\n";
+ } else {
+ print "Please give a correct value for the month (from 1 to 12).\n";
+ }
+ return 102;
+ }
+ $month = $month - 1;
+ if ($day < 1 || $day > 31) {
+ if ($defaultlanguage eq "F") {
+ print "Entrez un jour correct svp (entre 1 et 31).\n";
+ } else {
+ print "Please give a correct value for the day (from 1 to 31).\n";
+ }
+ return 102;
+ }
+ if ((($month == 3 || $month == 5 || $month == 8 || $month == 10) && $day > 30) ||
+ ($month == 1 && $day > 29)) {
+ if ($defaultlanguage eq "F") {
+ print "Entrez un jour correct en fonction du mois svp.\n";
+ } else {
+ print "Please give a correct value for a day of this month.\n";
+ }
+ return 102;
+ }
+ if ($hour < 0 || $hour > 23) {
+ if ($defaultlanguage eq "F") {
+ print "Entrez une heure correcte svp (entre 0 et 23).\n";
+ } else {
+ print "Please give a correct value for the hour (from 0 to 23).\n";
+ }
+ return 102;
+ }
+ if ($minute < 0 || $minute > 59) {
+ if ($defaultlanguage eq "F") {
+ print "Entrez des minutes correctes svp (entre 0 et 59).\n";
+ } else {
+ print "Please give a correct value for the minutes (from 0 to 59).\n";
+ }
+ return 102;
+ }
+ if ($second < 0 || $second > 59) {
+ if ($defaultlanguage eq "F") {
+ print "Entrez des secondes correctes svp (entre 0 et 59).\n";
+ } else {
+ print "Please give a correct value for the seconds (from 0 to 59).\n";
+ }
+ return 102;
+ }
+ $timestamp = POSIX::mktime($second, $minute, $hour, $day, $month, $year, 0, 0, -1); # -1: no winter/summer time modification
+ if ($timestamp == undef) {
+ if ($defaultlanguage eq "F") {
+ print "Date incorrecte.\n";
+ print "Ajoutez 0 ou une date et une heure svp (format: 0 ou aaaa/mm/jj hh:mm:ss).\n";
+ } else {
+ print "Invalid date.\n";
+ print "Please add 0 or a date and a time (format: 0 or yyyy/mm/dd hh:mm:ss).\n";
+ }
+ return 102;
+ }
+ }
+
+ print $so pack("va24V", 0x794a, $userid, $timestamp);
+ $so->flush();
+ $buf = readso(2);
+ if (unpack("v", $buf) != 0x794b) {
+ if ($defaultlanguage eq "F") {
+ print "Probl鑪e de connexion au serveur (r駱onse incorrecte).\n";
+ } else {
+ print "Connection error to the server (incorrect answer).\n";
+ }
+ return 152;
+ }
+ $buf = readso(32);
+ my(@dat) = unpack("Va24V", $buf);
+ while (length($dat[1]) > 0 && substr($dat[1], length($dat[1])-1, 1) eq chr(0)) {
+ chop($dat[1]);
+ };
+ if ($dat[0] != -1 && $dat[0] != 4294967295) {
+ if ($defaultlanguage eq "F") {
+ print "Date finale de banissement du compte [$dat[1]][id: $dat[0]] chang馥 avec succ鑚 ".
+ ($dat[2] == 0 ? "en [d-bannie].\n" : "pour 黎re jusqu'au ".(POSIX::ctime($dat[2])));
+ } else {
+ print "Final date of banishment of the account [$dat[1]][id: $dat[0]] successfully changed ".
+ ($dat[2] == 0 ? "to [unbanished].\n" : "to be until ".(POSIX::ctime($dat[2])));
+ }
+ # localtime($dat[2]) is also possible to display instead of POSIX::ctime.
+ } else {
+ if ($defaultlanguage eq "F") {
+ print "Echec du changement de la date finale de banissement du compte [$userid]. Le compte n'existe pas.\n";
+ } else {
+ print "Account [$userid] final date of banishment changing failed. Account doesn't exist.\n";
+ }
+ }
+
+ return 0;
+}
+
+#--------------------------------------------------------------------------
+
+# Sub-function: Add/substract time to the final date of a banishment of an account
+sub banaddaccount() {
+ my($userid, $modif) = @_;
+ if ($userid eq "") {
+ if ($defaultlanguage eq "F") {
+ print "Entrez un nom de compte svp.\n";
+ print " <exemple> banadd nomtest +1m-2mn1s-6y\n";
+ print " Cette exemple ajoute 1 mois et 1 seconde, et soustrait 2 minutes\n";
+ print " et 6 ans dans le m麥e temps.\n";
+ } else {
+ print "Please input an account name.\n";
+ print " <example> banadd testname +1m-2mn1s-6y\n";
+ print " this example adds 1 month and 1 second, and substracts 2 minutes\n";
+ print " and 6 years at the same time.\n";
+ }
+ return 136;
+ }
+ if (verify_accountname($userid) == 0) {
+ return 102;
+ }
+ my($year, $month, $day) = (0, 0 ,0);
+ my($hour, $minute, $second) = (0, 0 ,0);
+
+ $modif = lc($modif);
+ while (length($modif) > 0) {
+ my($value) = int($modif);
+ if ($value == 0) {
+ $modif = substr($modif, 1);
+ } else {
+ if (substr($modif, 0, 1) =~ /[\-\+]/) {
+ $modif = substr($modif, 1);
+ }
+ while (length($modif) > 0 && substr($modif, 0, 1) =~ /[0-9]/) {
+ $modif = substr($modif, 1);
+ }
+ if (index($modif, "s") == 0) {
+ $second = $value;
+ $modif = substr($modif, 1);
+ } elsif (index($modif, "mn") == 0) {
+ $minute = $value;
+ $modif = substr($modif, 2);
+ } elsif (index($modif, "h") == 0) {
+ $hour = $value;
+ $modif = substr($modif, 1);
+ } elsif (index($modif, "d") == 0 || index($modif, "j") == 0) {
+ $day = $value;
+ $modif = substr($modif, 1);
+ } elsif (index($modif, "m") == 0) {
+ $month = $value;
+ $modif = substr($modif, 1);
+ } elsif (index($modif, "y") == 0 || index($modif, "a") == 0) {
+ $year = $value;
+ $modif = substr($modif, 1);
+ } else {
+ $modif = substr($modif, 1);
+ }
+ }
+ }
+
+ if ($defaultlanguage eq "F") {
+ print " ann馥: $year\n";
+ print " mois: $month\n";
+ print " jour: $day\n";
+ print " heure: $hour\n";
+ print " minute: $minute\n";
+ print " seconde: $second\n";
+ } else {
+ print " year: $year\n";
+ print " month: $month\n";
+ print " day: $day\n";
+ print " hour: $hour\n";
+ print " minute: $minute\n";
+ print " second: $second\n";
+ }
+
+ if ($year == 0 && $month == 0 && $day == 0 && $hour == 0 && $minute == 0 && $second == 0) {
+ if ($defaultlanguage eq "F") {
+ print "Vous devez entrer un ajustement avec cette commande, svp:\n";
+ print " Valeur d'ajustement (-1, 1, +1, etc...)\n";
+ print " Element modifi:\n";
+ print " a ou y: ann馥\n";
+ print " m: mois\n";
+ print " j ou d: jour\n";
+ print " h: heure\n";
+ print " mn: minute\n";
+ print " s: seconde\n";
+ print " <exemple> banadd nomtest +1m-2mn1s-6y\n";
+ print " Cette exemple ajoute 1 mois et 1 seconde, et soustrait 2 minutes\n";
+ print " et 6 ans dans le m麥e temps.\n";
+ } else {
+ print "Please give an adjustment with this command:\n";
+ print " Adjustment value (-1, 1, +1, etc...)\n";
+ print " Modified element:\n";
+ print " a or y: year\n";
+ print " m: month\n";
+ print " j or d: day\n";
+ print " h: hour\n";
+ print " mn: minute\n";
+ print " s: second\n";
+ print " <example> banadd testname +1m-2mn1s-6y\n";
+ print " this example adds 1 month and 1 second, and substracts 2 minutes\n";
+ print " and 6 years at the same time.\n";
+ }
+ return 137;
+ }
+ if ($year > 127 || $year < -127) {
+ if ($defaultlanguage eq "F") {
+ print "Entrez un ajustement d'ann馥s correct (de -127 127), svp.\n";
+ } else {
+ print "Please give a correct adjustment for the years (from -127 to 127).\n";
+ }
+ return 137;
+ }
+ if ($month > 255 || $month < -255) {
+ if ($defaultlanguage eq "F") {
+ print "Entrez un ajustement de mois correct (de -255 255), svp.\n";
+ } else {
+ print "Please give a correct adjustment for the months (from -255 to 255).\n";
+ }
+ return 137;
+ }
+ if ($day > 32767 || $day < -32767) {
+ if ($defaultlanguage eq "F") {
+ print "Entrez un ajustement de jours correct (de -32767 32767), svp.\n";
+ } else {
+ print "Please give a correct adjustment for the days (from -32767 to 32767).\n";
+ }
+ return 137;
+ }
+ if ($hour > 32767 || $hour < -32767) {
+ if ($defaultlanguage eq "F") {
+ print "Entrez un ajustement d'heures correct (de -32767 32767), svp.\n";
+ } else {
+ print "Please give a correct adjustment for the hours (from -32767 to 32767).\n";
+ }
+ return 137;
+ }
+ if ($minute > 32767 || $minute < -32767) {
+ if ($defaultlanguage eq "F") {
+ print "Entrez un ajustement de minutes correct (de -32767 32767), svp.\n";
+ } else {
+ print "Please give a correct adjustment for the minutes (from -32767 to 32767).\n";
+ }
+ return 137;
+ }
+ if ($second > 32767 || $second < -32767) {
+ if ($defaultlanguage eq "F") {
+ print "Entrez un ajustement de secondes correct (de -32767 32767), svp.\n";
+ } else {
+ print "Please give a correct adjustment for the seconds (from -32767 to 32767).\n";
+ }
+ return 137;
+ }
+
+ print $so pack("va24vvvvvv", 0x794c, $userid, $year, $month, $day, $hour, $minute, $second);
+ $so->flush();
+ $buf = readso(2);
+ if (unpack("v", $buf) != 0x794d) {
+ if ($defaultlanguage eq "F") {
+ print "Probl鑪e de connexion au serveur (r駱onse incorrecte).\n";
+ } else {
+ print "Connection error to the server (incorrect answer).\n";
+ }
+ return 152;
+ }
+ $buf = readso(32);
+ my(@dat) = unpack("Va24V", $buf);
+ while (length($dat[1]) > 0 && substr($dat[1], length($dat[1])-1, 1) eq chr(0)) {
+ chop($dat[1]);
+ };
+ if ($dat[0] == -1 || $dat[0] == 4294967295) {
+ if ($defaultlanguage eq "F") {
+ print "Echec du changement de la date finale de banissement du compte [$userid]. Le compte n'existe pas.\n";
+ } else {
+ print "Account [$userid] final date of banishment changing failed. Account doesn't exist.\n";
+ }
+ } else {
+ if ($defaultlanguage eq "F") {
+ print "Date finale de banissement du compte [$dat[1]][id: $dat[0]] chang馥 avec succ鑚 ".
+ ($dat[2] == 0 ? "en [d-bannie].\n" : "pour 黎re jusqu'au ".(POSIX::ctime($dat[2])));
+ } else {
+ print "Final date of banishment of the account [$dat[1]][id: $dat[0]] successfully changed ".
+ ($dat[2] == 0 ? "to [unbanished].\n" : "to be until ".(POSIX::ctime($dat[2])));
+ }
+ # localtime($dat[2]) is also possible to display instead of POSIX::ctime.
+ }
+
+ return 0;
+}
+
+#--------------------------------------------------------------------------
+
+# Sub-function: Request to displaying information about an account (by its name)
+sub whoaccount() {
+ my($userid) = @_;
+ if ($userid eq "") {
+ if ($defaultlanguage eq "F") {
+ print "Entrez un nom de compte svp.\n";
+ print "<exemple> who nomtest\n";
+ } else {
+ print "Please input an account name.\n";
+ print "<example> who testname\n";
+ }
+ return 136;
+ }
+ if (verify_accountname($userid) == 0) {
+ return 102;
+ }
+
+ print $so pack("va24", 0x7952, $userid);
+ $so->flush();
+
+ $buf = readso(2);
+ if (unpack("v", $buf) != 0x7953) {
+ if ($defaultlanguage eq "F") {
+ print "Probl鑪e de connexion au serveur (r駱onse incorrecte).\n";
+ } else {
+ print "Connection error to the server (incorrect answer).\n";
+ }
+ return 122;
+ }
+ my($id2, $GM_level, $name, $sex, $count, $status, $error_message, $last_login, $last_ip, $email, $validite, $ban_date, $memo_size) = unpack("VCa24cVVa20a24a16a40VVv", readso(148));
+ my($memo) = "";
+ if ($memo_size > 0) {
+ $memo = unpack("a".$memo_size, readso($memo_size));
+ }
+ while (length($name) > 0 && substr($name, length($name)-1, 1) eq chr(0)) {
+ chop($name);
+ };
+ while (length($error_message) > 0 && substr($error_message, length($error_message)-1, 1) eq chr(0)) {
+ chop($error_message);
+ };
+ while (length($last_login) > 0 && substr($last_login, length($last_login)-1, 1) eq chr(0)) {
+ chop($last_login);
+ };
+ while (length($last_ip) > 0 && substr($last_ip, length($last_ip)-1, 1) eq chr(0)) {
+ chop($last_ip);
+ };
+ while (length($email) > 0 && substr($email, length($email)-1, 1) eq chr(0)) {
+ chop($email);
+ };
+ while (length($memo) > 0 && substr($memo, length($memo)-1, 1) eq chr(0)) {
+ chop($memo);
+ };
+
+ if ($id2 == -1 || $id2 == 4294967295) {
+ if ($defaultlanguage eq "F") {
+ print "Impossible de trouver le compte [$userid]. Le compte n'existe pas.\n";
+ } else {
+ print "Unabled to find the account [$userid]. Account doesn't exist.\n";
+ }
+ return 123;
+ } else {
+ if ($defaultlanguage eq "F") {
+ print "Le compte [$userid] a les caract駻istiques suivantes:\n";
+ } else {
+ print "The account [$userid] is set with:\n";
+ }
+ if ($GM_level == 0) {
+ print " Id: $id2 (non-GM)\n";
+ } else {
+ if ($defaultlanguage eq "F") {
+ print " Id: $id2 (GM niveau $GM_level)\n";
+ } else {
+ print " Id: $id2 (GM level $GM_level)\n";
+ }
+ }
+ if ($defaultlanguage eq "F") {
+ print " Nom: '$name'\n";
+ print " Sexe: ".("Femme", "Male", "Serveur")[$sex]."\n";
+ } else {
+ print " Name: '$name'\n";
+ print " Sex: ".("Female", "Male", "Server")[$sex]."\n";
+ }
+ print " E-mail: $email\n";
+ if ($status == 7) {
+ print " Statut: 7 [You are Prohibited to log in until $error_message]\n";
+ } else {
+ print " Statut: $status [".(
+ ($defaultlanguage eq "F" ? "Compte Ok" : "Account OK"),
+ "Unregistered ID",
+ "Incorrect Password",
+ "This ID is expired",
+ "Rejected from Server",
+ "You have been blocked by the GM Team",
+ "Your Game's EXE file is not the latest version",
+ "You are Prohibited to log in until %s",
+ "Server is jammed due to over populated",
+ "No MSG",
+ "This ID is totally erased")[$status == 100 ? 10 : $status]."]\n";
+ }
+ if ($defaultlanguage eq "F") {
+ print " Banissement: ".($ban_date == 0 ? "non banni.\n" : "jusqu'au ".(POSIX::ctime($ban_date)));
+ print " Compteur: $count connexion".("s", "")[$count > 1 ? 0 : 1]."\n";
+ print " Derni鑽e connexion le: $last_login (ip: $last_ip)\n";
+ print " Limite de validit: ".($validite == 0 ? "illimit.\n" : "jusqu'au ".(POSIX::ctime($validite)));
+ } else {
+ print " Banishment: ".($ban_date == 0 ? "not banished.\n" : "until ".(POSIX::ctime($ban_date)));
+ print " Count: $count connection".("s", "")[$count > 1 ? 0 : 1]."\n";
+ print " Last connection at: $last_login (ip: $last_ip)\n";
+ print " Validity limit: ".($validite == 0 ? "unlimited.\n" : "until ".(POSIX::ctime($validite)));
+ }
+ print " Memo: '$memo'\n";
+ }
+ return 0;
+}
+
+#--------------------------------------------------------------------------
+
+# Sub-function: Request to displaying information about an account (by its id)
+sub infoaccount() {
+ my($id) = @_;
+ if ($id < 0) {
+ if ($defaultlanguage eq "F") {
+ print "Entrez un id ayant une valeur positive svp.\n";
+ } else {
+ print "Please input a positive value for the id.\n";
+ }
+ return 136;
+ }
+
+ print $so pack("vV", 0x7954, $id);
+ $so->flush();
+
+ $buf = readso(2);
+ if (unpack("v", $buf) != 0x7953) {
+ if ($defaultlanguage eq "F") {
+ print "Probl鑪e de connexion au serveur (r駱onse incorrecte).\n";
+ } else {
+ print "Connection error to the server (incorrect answer).\n";
+ }
+ return 122;
+ }
+ my($id2, $GM_level, $name, $sex, $count, $status, $error_message, $last_login, $last_ip, $email, $validite, $ban_date, $memo_size) = unpack("VCa24cVVa20a24a16a40VVv", readso(148));
+ my($memo) = "";
+ if ($memo_size > 0) {
+ $memo = unpack("a".$memo_size, readso($memo_size));
+ }
+ while (length($name) > 0 && substr($name, length($name)-1, 1) eq chr(0)) {
+ chop($name);
+ };
+ while (length($error_message) > 0 && substr($error_message, length($error_message)-1, 1) eq chr(0)) {
+ chop($error_message);
+ };
+ while (length($last_login) > 0 && substr($last_login, length($last_login)-1, 1) eq chr(0)) {
+ chop($last_login);
+ };
+ while (length($last_ip) > 0 && substr($last_ip, length($last_ip)-1, 1) eq chr(0)) {
+ chop($last_ip);
+ };
+ while (length($email) > 0 && substr($email, length($email)-1, 1) eq chr(0)) {
+ chop($email);
+ };
+ while (length($memo) > 0 && substr($memo, length($memo)-1, 1) eq chr(0)) {
+ chop($memo);
+ };
+
+ if (length($name) == 0 || $name eq "") {
+ if ($defaultlanguage eq "F") {
+ print "Impossible de trouver le nom du compte [id: $id2]. Le compte n'existe pas.\n";
+ } else {
+ print "Unabled to find the account [id: $id2] name. Account doesn't exist.\n";
+ }
+ return 123;
+ } else {
+ if ($defaultlanguage eq "F") {
+ print "Le compte [id: $id2] a les caract駻istiques suivantes:\n";
+ } else {
+ print "The account [id: $id2] is set with:\n";
+ }
+ if ($GM_level == 0) {
+ print " Id: $id2 (non-GM)\n";
+ } else {
+ if ($defaultlanguage eq "F") {
+ print " Id: $id2 (GM niveau $GM_level)\n";
+ } else {
+ print " Id: $id2 (GM level $GM_level)\n";
+ }
+ }
+ if ($defaultlanguage eq "F") {
+ print " Nom: '$name'\n";
+ print " Sexe: ".("Femme", "Male", "Serveur")[$sex]."\n";
+ } else {
+ print " Name: '$name'\n";
+ print " Sex: ".("Female", "Male", "Server")[$sex]."\n";
+ }
+ print " E-mail: $email\n";
+ if ($status == 7) {
+ print " Statut: 7 [You are Prohibited to log in until $error_message]\n";
+ } else {
+ print " Statut: $status [".(
+ ($defaultlanguage eq "F" ? "Compte Ok" : "Account OK"),
+ "Unregistered ID",
+ "Incorrect Password",
+ "This ID is expired",
+ "Rejected from Server",
+ "You have been blocked by the GM Team",
+ "Your Game's EXE file is not the latest version",
+ "You are Prohibited to log in until %s",
+ "Server is jammed due to over populated",
+ "No MSG",
+ "This ID is totally erased")[$status == 100 ? 10 : $status]."]\n";
+ }
+ if ($defaultlanguage eq "F") {
+ print " Banissement: ".($ban_date == 0 ? "non banni.\n" : "jusqu'au ".(POSIX::ctime($ban_date)));
+ print " Compteur: $count connexion".("s", "")[$count > 1 ? 0 : 1]."\n";
+ print " Derni鑽e connexion le: $last_login (ip: $last_ip)\n";
+ print " Limite de validit: ".($validite == 0 ? "illimit.\n" : "jusqu'au ".(POSIX::ctime($validite)));
+ } else {
+ print " Banishment: ".($ban_date == 0 ? "not banished.\n" : "until ".(POSIX::ctime($ban_date)));
+ print " Count: $count connection".("s", "")[$count > 1 ? 0 : 1]."\n";
+ print " Last connection at: $last_login (ip: $last_ip)\n";
+ print " Validity limit: ".($validite == 0 ? "unlimited.\n" : "until ".(POSIX::ctime($validite)));
+ }
+ print " Memo: '$memo'\n";
+ }
+ return 0;
+}
+
+#--------------------------------------------------------------------------
+
+# Sub-function: Check the validity of a password
+# (Note: never send back a password with login-server!! security of passwords)
+sub checkaccount() {
+ my($userid, $passwd) = @_;
+ if ($userid eq "") {
+ if ($defaultlanguage eq "F") {
+ print "Entrez un nom de compte svp.\n";
+ print "<exemple> check testname motdepasse\n";
+ } else {
+ print "Please input an account name.\n";
+ print "<example> check testname password\n";
+ }
+ return 136;
+ }
+ if (verify_accountname($userid) == 0) {
+ return 102;
+ }
+ if ($passwd eq "") {
+ return 134 if (($passwd = typepasswd()) eq "");
+ }
+ if (verify_password($passwd) == 0) {
+ return 131;
+ }
+ print $so pack("va24a24", 0x793a, $userid,$passwd);
+ $so->flush();
+ $buf = readso(2);
+ if (unpack("v", $buf) != 0x793b) {
+ if ($defaultlanguage eq "F") {
+ print "Probl鑪e de connexion au serveur (r駱onse incorrecte).\n";
+ } else {
+ print "Connection error to the server (incorrect answer).\n";
+ }
+ return 132;
+ }
+ $buf = readso(28);
+ my($id2, $name) = unpack("Va24", $buf);
+ while (length($name) > 0 && substr($name, length($name)-1, 1) eq chr(0)) {
+ chop($name);
+ };
+ if ($id2 == -1 || $id2 == 4294967295) {
+ if ($defaultlanguage eq "F") {
+ print "Le compte [$userid] n'existe pas ou le mot de passe est incorrect.\n";
+ } else {
+ print "The account [$userid] doesn't exist or the password is incorrect.\n";
+ }
+ return 133;
+ } else {
+ if ($defaultlanguage eq "F") {
+ print "Le mot de passe donn correspond bien au compte [$name][id: $id2].\n";
+ } else {
+ print "The proposed password is correct for the account [$name][id: $id2].\n";
+ }
+ }
+ return 130;
+}
+
+#--------------------------------------------------------------------------
+
+# Sub-function: Request to login-server to reload GM configuration file
+sub reloadGM() {
+ print $so pack("v", 0x7955);
+ $so->flush();
+ if ($defaultlanguage eq "F") {
+ print "Demande de recharger le fichier de configuration des GM envoy馥.\n";
+ print "V駻ifiez les comptes GM actuels (apr鑚 rechargement):\n";
+ } else {
+ print "Request to reload the GM configuration file sended.\n";
+ print "Check the actual GM accounts (after reloading):\n";
+ }
+ &listaccount(0, 0, 1); # 1: to list only GM
+ return 180;
+}
+
+#--------------------------------------------------------------------------
+
+# Sub-function: Send a broadcast message
+sub sendbroadcast() {
+ my($type, $message) = @_;
+ if ($message eq "" || length($message) == 0) {
+ if ($defaultlanguage eq "F") {
+ print "Entrez un message svp.\n";
+ if ($type == 0) {
+ print "<exemple> kami un message\n";
+ } else {
+ print "<exemple> kamib un message\n";
+ }
+ } else {
+ print "Please input a message.\n";
+ if ($type == 0) {
+ print "<example> kami a message\n";
+ } else {
+ print "<example> kamib a message\n";
+ }
+ }
+ return 136;
+ }
+
+ print $so pack("vvVa".length($message), 0x794e, $type, length($message), $message);
+ $so->flush();
+ $buf = readso(2);
+ if (unpack("v", $buf) != 0x794f) {
+ if ($defaultlanguage eq "F") {
+ print "Probl鑪e de connexion au serveur (r駱onse incorrecte).\n";
+ } else {
+ print "Connection error to the server (incorrect answer).\n";
+ }
+ return 152;
+ }
+ $buf = readso(2);
+ my($answer) = unpack("v", $buf);
+ if ($answer == -1 || $answer == 65535) {
+ if ($defaultlanguage eq "F") {
+ print "Echec de l'envoi du message. Aucun server de char en ligne.\n";
+ } else {
+ print "Message sending failed. No online char-server.\n";
+ }
+ } else {
+ if ($defaultlanguage eq "F") {
+ print "Message transmis au server de logins avec succ鑚.\n";
+ } else {
+ print "Message successfully sended to login-server.\n";
+ }
+ }
+}
+
+#--------------------------------------------------------------------------
+
+# Sub-function: Change language of displaying
+sub changelanguage() {
+ my($language) = @_;
+ if ($language eq "" || length($language) == 0) {
+ if ($defaultlanguage == 'F') {
+ printf("Entrez une langue svp.\n");
+ printf("<exemple> language english\n");
+ printf(" language fran軋is\n");
+ } else {
+ printf("Please input a language.\n");
+ printf("<example> language english\n");
+ printf(" language fran軋is\n");
+ }
+ return 136;
+ }
+
+ $language = uc(substr($language, 0, 1));
+ if ($language =~ /^[EF]$/) {
+ $defaultlanguage = $language;
+ if ($defaultlanguage == 'F') {
+ printf("Changement de la langue d'affichage en Fran軋is.\n");
+ } else {
+ printf("Displaying language changed to English.\n");
+ }
+ } else {
+ if ($defaultlanguage == 'F') {
+ printf("Langue non param騁r馥 (langues possibles: 'Fran軋is' ou 'English').\n");
+ } else {
+ printf("Undefined language (possible languages: Fran軋is or English).\n");
+ }
+ }
+
+ return 0;
+}
+
+#--------------------------------------------------------------------------
+
+# Sub-function: sending 'end of connection' packet
+sub quit() {
+ print $so pack("v", 0x7532);
+ $so->flush();
+}
+
+#--------------------------------------------------------------------------
+
+# Sub-function: Get datas from the socket
+sub readso() {
+ my($len) = shift;
+ my($buf);
+ if (read($so, $buf, $len) < $len) {
+ if ($defaultlanguage eq "F") {
+ print "Erreur de lecture sur la Socket.\n";
+ } else {
+ print "Socket read error.\n";
+ }
+ exit(3);
+ }
+ return $buf;
+}
+
+#--------------------------------------------------------------------------
+
+# Sub-function: Input of a password
+sub typepasswd {
+ my($passwd1, $passwd2);
+ cbreak();
+ if ($defaultlanguage eq "F") {
+ print "Entrez le mot de passe > "; $passwd1 = <STDIN>; chomp($passwd1); print "\n";
+ print "R-entrez le mot de passe > "; $passwd2 = <STDIN>; chomp($passwd2); print "\n";
+ } else {
+ print "Type the password > "; $passwd1 = <STDIN>; chomp($passwd1); print "\n";
+ print "Verify the password > "; $passwd2 = <STDIN>; chomp($passwd2); print "\n";
+ }
+ cooked();
+ if ($passwd1 ne $passwd2) {
+ if ($defaultlanguage eq "F") {
+ print "Erreur de v駻ification du mot de passe: Saisissez le m麥e mot de passe svp.\n";
+ } else {
+ print "Password verification failed. Please input same password.\n";
+ }
+ return "";
+ }
+ return $passwd1;
+}
+
+#--------------------------------------------------------------------------
+
+# Sub-function: Return ordonal text of a number
+sub makeordinal {
+ my($c) = shift;
+ if ($defaultlanguage eq "F") {
+ if ($c < 1) {
+ return $c;
+ }
+ return $c.("er", "鑪e")[$c == 1 ? 0 : 1];
+ } else {
+ if ($c % 10 < 4 && $c % 10 != 0 && ($c < 10 || $c > 20)) {
+ return $c.("st","nd","rd")[$c % 10 - 1];
+ }
+ return $c."th";
+ }
+}
+
+#--------------------------------------------------------------------------
+
+# Sub-function: Test of the validity of an account name (return 0 if incorrect, and 1 if ok)
+sub verify_accountname {
+ my($account_name) = @_; # Get the account_name
+ if ($account_name =~ /[\x00-\x1f]/) { # remove control char
+ my($c) = length($`) + 1;
+ if ($defaultlanguage eq "F") {
+ print "Caract鑽e interdit trouv dans le nom du compte (".makeordinal($c)." caract鑽e).\n";
+ } else {
+ print "Illegal character found in the account name (".makeordinal($c)." character).\n";
+ }
+ return 0;
+ }
+ if (length($account_name) < 4) {
+ if ($defaultlanguage eq "F") {
+ print "Nom du compte trop court. Entrez un nom de compte de 4-23 caract鑽es.\n";
+ } else {
+ print "Account name is too short. Please input an account name of 4-23 bytes.\n";
+ }
+ return 0;
+ }
+ if (length($account_name) > 23) {
+ if ($defaultlanguage eq "F") {
+ print "Nom du compte trop long. Entrez un nom de compte de 4-23 caract鑽es.\n";
+ } else {
+ print "Account name is too long. Please input an account name of 4-23 bytes.\n";
+ }
+ return 0;
+ }
+ return 1;
+}
+
+#--------------------------------------------------------------------------
+
+# Sub-function: Test of the validity of password (return 0 if incorrect, and 1 if ok)
+sub verify_password {
+ my($password) = @_; # Get the password
+ if ($password =~ /[\x00-\x1f]/) {
+ my($c) = length($`) + 1;
+ if ($defaultlanguage eq "F") {
+ print "Caract鑽e interdit trouv dans le mot de passe (".makeordinal($c)." caract鑽e).\n";
+ } else {
+ print "Illegal character found in the password (".makeordinal($c)." character).\n";
+ }
+ return 0;
+ }
+ if (length($password) < 4) {
+ if ($defaultlanguage eq "F") {
+ print "Mot de passe trop court. Entrez un mot de passe de 4-23 caract鑽es.\n";
+ } else {
+ print "Password is too short. Please input a password of 4-23 bytes.\n";
+ }
+ return 0;
+ }
+ if (length($password) > 23) {
+ if ($defaultlanguage eq "F") {
+ print "Mot de passe trop long. Entrez un mot de passe de 4-23 caract鑽es.\n";
+ } else {
+ print "Password is too long. Please input a password of 4-23 bytes.\n";
+ }
+ return 0;
+ }
+ return 1;
+}
+
+#--------------------------------------------------------------------------
+
+# Sub-function: Test of the validity of an e-mail (return 0 if incorrect, and 1 if ok)
+sub verify_email {
+ my($email) = @_; # Get the e-mail
+ # To ignore a '.' before the @ (wanadoo, a provider, do that)
+ $email =~ s/\.\@/\@/;
+ # If the e-mail is void, it's not correct -> return 0
+ if ($email eq '') {
+ return(0);
+ }
+ # If the e-mail have no "@", it's not correct -> return 0
+ if ($email !~ /\@/) {
+ return(0);
+ }
+ # If the e-mail have a ",", a space, a tab or a ";", it's not correct -> return 0
+ if ($email =~ /[\,|\s|\;]/) {
+ return(0)
+ };
+ # IF
+ # (the e-mail contains 2 "@", or ".." or "@." or starts or finishes by a ".")
+ # OR IF
+ # (the e-mail doesn't contain "@localhost" AND
+ # - it doesn't contain characters followed by "@" itself followed by letters itself followed by "." and 2 or more letters
+ # - or an IP address)
+ # -> so, it's not good ! (finish !)
+ if ($email =~ /(@.*@)|(\.\.)|(@\.)|(\.@)|(^\.)|(\.$)/ ||
+ ($email !~ /^.+\@localhost$/ &&
+ $email !~ /^.+\@\[?(\w|[-.])+\.[a-zA-Z]{2,3}|[0-9]{1,3}\]?$/)) {
+ return(0); # non-valid email
+ } else {
+ # If not, the e-email address is correct
+ return(1); # valid email
+ }
+}
\ No newline at end of file diff --git a/misc/src/tool/mapcheck.sh b/misc/src/tool/mapcheck.sh new file mode 100644 index 0000000..337884c --- /dev/null +++ b/misc/src/tool/mapcheck.sh @@ -0,0 +1,34 @@ +#!/bin/sh +echo "============================================" +echo "= map server status checker... =" +echo "============================================" +./map-server.exe & +sleep 40 + +while [ 0 ] +do + pcpu=` top -n 1| grep map-server | awk '{print $9}' | awk 'BEGIN{FS="."} {print $1}' ` + if [ "$pcpu" -gt 80 ];then + echo "============================================" + echo "map server is more than 80% (now $pcpu%)" + echo "============================================" + ppid=` ps -a | grep map-server | awk '{print $1}' ` + kill $ppid + ./map-server.exe & + sleep 40 + else + pmapct=` ps -a| grep map-server | wc -l ` + if [ "$pmapct" -eq 0 ];then + echo "============================================" + echo "map server is not running..." + echo "restart map server..." + echo "============================================" + ./map-server.exe & + sleep 40 + #echo "test" + else + echo "map server is ok (now $pcpu%)..." + sleep 5 + fi + fi +done
\ No newline at end of file diff --git a/misc/src/tool/mapchecker.sh b/misc/src/tool/mapchecker.sh new file mode 100644 index 0000000..7250c34 --- /dev/null +++ b/misc/src/tool/mapchecker.sh @@ -0,0 +1,56 @@ +#!/bin/bash + +athena_dir="/home/athena/658/" + +while [ true ] ; do + +if [ ` ps fauxw | grep map-server | grep -v grep | wc -l ` -eq 0 ];then + #echo `date` " -- map-server crashed - restarting" + echo `date` " -- map-server crashed - restarting" >> /var/log/athena_status.log + killall -9 map-server + cd $athena_dir + nohup ./map-server ./conf/map_athena.conf ./inter_athena.conf & + sleep 240 + #sleep 40 #for fast pc's remove the "#" at the beginning of the line and delete the line above +fi + + +if [ ` ps fauxw | grep map-server | grep -v grep | awk '{print $3}' | awk 'BEGIN{FS="."} {print $1}' ` -gt 10 ];then + #echo `date` " -- mapserver cpuload over 10 - restarting" + echo `date` " -- mapserver cpuload over 10 - restarting" >> /var/log/athena_status.log + killall -9 map-server + cd $athena_dir + nohup ./map-server ./conf/map_athena.conf ./inter_athena.conf & + sleep 240 + #sleep 40 #for fast pc's remove the "#" at the beginning of the line and delete the line above + #echo `date` " -- restarted" + echo `date` " -- restarted" >> /var/log/athena_status.log +fi + +if [ ` ps fauxw | grep char-server | grep -v grep | wc -l ` -eq 0 ];then + #echo `date` " -- char server crashed - restarting" + echo `date` " -- char server crashed - restarting" >> /var/log/athena_status.log + killall -9 char-server + cd $athena_dir + nohup ./char-server ./conf/char_athena.conf ./conf/inter_athena.conf & + #echo `date` " -- restarted" + echo `date` " -- restarted" >> /var/log/athena_status.log + +fi + +if [ ` ps fauxw | grep login-server | grep -v grep | wc -l ` -eq 0 ];then + #echo `date` " -- login server crashed - restarting" + echo `date` " -- login server crashed - restarting" >> /var/log/athena_status.log + killall -9 login-server + cd $athena_dir + nohup ./login-server ./conf/login_athena.conf & + #echo `date` " -- restarted" + echo `date` " -- restarted" >> /var/log/athena_status.log + +fi + + +#echo `date` " -- everything is fine" +echo `date` " -- everything is fine" >> /var/log/athena_status.log +sleep 30 +done diff --git a/misc/src/txt-converter/char/GNUmakefile b/misc/src/txt-converter/char/GNUmakefile new file mode 100644 index 0000000..b88df26 --- /dev/null +++ b/misc/src/txt-converter/char/GNUmakefile @@ -0,0 +1,13 @@ +all: char-converter
+sql: char-converter
+
+COMMON_OBJ = ../../common/core.o ../../common/socket.o ../../common/timer.o ../../common/db.o ../../common/malloc.o
+
+char-converter: char-converter.o strlib.o $(COMMON_OBJ)
+ $(CC) -o ../../../$@ $^ $(LIB_S)
+
+char-converter.o: char-converter.c char.h strlib.h
+strlib.o: strlib.c strlib.h
+clean:
+ rm -f *.o ../../../char-converter
+
diff --git a/misc/src/txt-converter/char/Makefile b/misc/src/txt-converter/char/Makefile new file mode 100644 index 0000000..b88df26 --- /dev/null +++ b/misc/src/txt-converter/char/Makefile @@ -0,0 +1,13 @@ +all: char-converter
+sql: char-converter
+
+COMMON_OBJ = ../../common/core.o ../../common/socket.o ../../common/timer.o ../../common/db.o ../../common/malloc.o
+
+char-converter: char-converter.o strlib.o $(COMMON_OBJ)
+ $(CC) -o ../../../$@ $^ $(LIB_S)
+
+char-converter.o: char-converter.c char.h strlib.h
+strlib.o: strlib.c strlib.h
+clean:
+ rm -f *.o ../../../char-converter
+
diff --git a/misc/src/txt-converter/char/char-converter.c b/misc/src/txt-converter/char/char-converter.c new file mode 100644 index 0000000..44f6d29 --- /dev/null +++ b/misc/src/txt-converter/char/char-converter.c @@ -0,0 +1,842 @@ +// $Id: char-converter.c,v 1.1.1.1 2004/09/10 17:45:03 MagicalTux Exp $ +// original : char2.c 2003/03/14 11:58:35 Rev.1.5 + +#include <sys/types.h> +#include <sys/socket.h> +#include <stdio.h> +#include <stdlib.h> +#include <netinet/in.h> +#include <sys/time.h> +#include <sys/ioctl.h> +#include <unistd.h> +#include <signal.h> +#include <fcntl.h> +#include <string.h> +#include <arpa/inet.h> + + +#define STORAGE_MEMINC 16 + +#include "char.h" +#include "strlib.h" + +#ifdef MEMWATCH +#include "memwatch.h" +#endif + +char pet_txt[256]="save/pet.txt"; +char storage_txt[256]="save/storage.txt"; + +MYSQL mysql_handle; +MYSQL_RES* sql_res ; +MYSQL_ROW sql_row ; +int sql_fields, sql_cnt; +char tmp_sql[65535]; + +int db_server_port = 3306; +char db_server_ip[16] = "127.0.0.1"; +char db_server_id[32] = "ragnarok"; +char db_server_pw[32] = "ragnarok"; +char db_server_logindb[32] = "ragnarok"; + +struct storage *storage=NULL; + +struct mmo_map_server server[MAX_MAP_SERVERS]; +int server_fd[MAX_MAP_SERVERS]; + +int login_fd; +char userid[24]; +char passwd[24]; +char server_name[20]; +char login_ip_str[16]; +int login_port = 6900; +char char_ip_str[16]; +int char_ip; +int char_port = 6121; +int char_maintenance; +int char_new; +char char_txt[256]; + +char t_name[256]; + +#define CHAR_STATE_WAITAUTH 0 +#define CHAR_STATE_AUTHOK 1 +struct char_session_data{ + int state; + int account_id, login_id1, login_id2, sex; + int found_char[9]; +}; + +#define AUTH_FIFO_SIZE 256 +struct { + int account_id, char_id, login_id1, char_pos, delflag, sex; +} auth_fifo[AUTH_FIFO_SIZE]; +int auth_fifo_pos=0; + +int char_id_count=100000; +struct mmo_charstatus *char_dat; +int char_num, char_max; +int max_connect_user=0; +int autosave_interval=DEFAULT_AUTOSAVE_INTERVAL; + +// テハア タァト。(conf ニトタマキホコホナヘ タ郛ウチ、 ー。エノ) +struct point start_point={"new_1-1.gat", 53,111}; + + + +int inter_pet_fromstr(char *str, struct s_pet *p) +{ + int s; + int tmp_int[16]; + char tmp_str[256]; + + memset(p, 0, sizeof(struct s_pet)); + +// printf("sscanf pet main info\n"); + s=sscanf(str,"%d, %d,%[^\t]\t%d, %d, %d, %d, %d, %d, %d, %d, %d", &tmp_int[0], &tmp_int[1], tmp_str, &tmp_int[2], + &tmp_int[3], &tmp_int[4], &tmp_int[5], &tmp_int[6], &tmp_int[7], &tmp_int[8], &tmp_int[9], &tmp_int[10]); + + if(s!=12) + return 1; + + p->pet_id = tmp_int[0]; + p->class = tmp_int[1]; + memcpy(p->name, tmp_str, 24); + p->account_id = tmp_int[2]; + p->char_id = tmp_int[3]; + p->level = tmp_int[4]; + p->egg_id = tmp_int[5]; + p->equip = tmp_int[6]; + p->intimate = tmp_int[7]; + p->hungry = tmp_int[8]; + p->rename_flag = tmp_int[9]; + p->incuvate = tmp_int[10]; + + if(p->hungry < 0) + p->hungry = 0; + else if(p->hungry > 100) + p->hungry = 100; + if(p->intimate < 0) + p->intimate = 0; + else if(p->intimate > 1000) + p->intimate = 1000; + + return 0; +} +//--------------------------------------------------------- +int inter_pet_tosql(int pet_id, struct s_pet *p) { + //`pet` (`pet_id`, `class`,`name`,`account_id`,`char_id`,`level`,`egg_id`,`equip`,`intimate`,`hungry`,`rename_flag`,`incuvate`) + + char tmp_sql[65535]; + MYSQL_RES* sql_res ; + MYSQL_ROW sql_row ; + + jstrescapecpy (t_name, p->name); + if(p->hungry < 0) + p->hungry = 0; + else if(p->hungry > 100) + p->hungry = 100; + if(p->intimate < 0) + p->intimate = 0; + else if(p->intimate > 1000) + p->intimate = 1000; + sprintf(tmp_sql,"SELECT * FROM `pet` WHERE `pet_id`='%d'",pet_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle) ); + } + sql_res = mysql_store_result(&mysql_handle) ; + sql_row = mysql_fetch_row(sql_res); //row fetching + if (!sql_row) //no row -> insert + sprintf(tmp_sql,"INSERT INTO `pet` (`pet_id`, `class`,`name`,`account_id`,`char_id`,`level`,`egg_id`,`equip`,`intimate`,`hungry`,`rename_flag`,`incuvate`) VALUES ('%d', '%d', '%s', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d')", + p->pet_id, p->class, t_name, p->account_id, p->char_id, p->level, p->egg_id, + p->equip, p->intimate, p->hungry, p->rename_flag, p->incuvate); + else //row reside -> updating + sprintf(tmp_sql, "UPDATE `pet` SET `class`='%d',`name`='%s',`account_id`='%d',`char_id`='%d',`level`='%d',`egg_id`='%d',`equip`='%d',`intimate`='%d',`hungry`='%d',`rename_flag`='%d',`incuvate`='%d' WHERE `pet_id`='%d'", + p->class, t_name, p->account_id, p->char_id, p->level, p->egg_id, + p->equip, p->intimate, p->hungry, p->rename_flag, p->incuvate, p->pet_id); + mysql_free_result(sql_res) ; //resource free + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle) ); + } + + printf ("pet dump success! - %d:%s\n", pet_id, p->name); + + return 0; +} + +int storage_tosql(int account_id,struct storage *p){ + // id -> DB dump + // storage {`account_id`/`id`/`nameid`/`amount`/`equip`/`identify`/`refine`/`attribute`/`card0`/`card1`/`card2`/`card3`} + int i,j; + + j=0; + + //printf ("starting storage dump to DB - id: %d\n", account_id); + + //delete old data. + sprintf(tmp_sql,"DELETE FROM `storage` WHERE `account_id`='%d'",account_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle) ); + } + + //printf ("all storage item was deleted ok\n"); + + for(i=0;i<MAX_STORAGE;i++) { + //printf ("save storage num: %d (%d:%d)\n",i, p->storage[i].nameid , p->storage[i].amount); + + if( (p->storage[i].nameid) && (p->storage[i].amount) ){ + sprintf(tmp_sql,"INSERT INTO `storage` (`account_id`,`nameid`,`amount`,`equip`,`identify`,`refine`,`attribute`,`card0`,`card1`,`card2`,`card3`,`broken`) VALUES ('%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d')", + p->account_id, p->storage[i].nameid, p->storage[i].amount, p->storage[i].equip, + p->storage[i].identify, p->storage[i].refine, p->storage[i].attribute, + p->storage[i].card[0], p->storage[i].card[1], p->storage[i].card[2], p->storage[i].card[3], p->storage[i].broken ); + //printf ("%s\n",tmp_sql); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle) ); + } + j++; + } + } + + printf ("storage dump to DB - id: %d (total: %d)\n", account_id, j); + return 0; +} +// char to storage +int storage_fromstr(char *str, struct storage *p) +{ + int tmp_int[256]; + int set, next, len, i; + + set=sscanf(str,"%d, %d%n", &tmp_int[0], &tmp_int[1], &next); + p->storage_amount=tmp_int[1]; + + if(set!=2) + return 0; + if(str[next]=='\n' || str[next]=='\r') + return 1; + next++; + for(i=0;str[next] && str[next]!='\t';i++){ + if(sscanf(str + next, "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d%n", + &tmp_int[0], &tmp_int[1], &tmp_int[2], &tmp_int[3], + &tmp_int[4], &tmp_int[5], &tmp_int[6], + &tmp_int[7], &tmp_int[8], &tmp_int[9], &tmp_int[10], &tmp_int[11], &len) == 12) { + p->storage[i].id = tmp_int[0]; + p->storage[i].nameid = tmp_int[1]; + p->storage[i].amount = tmp_int[2]; + p->storage[i].equip = tmp_int[3]; + p->storage[i].identify = tmp_int[4]; + p->storage[i].refine = tmp_int[5]; + p->storage[i].attribute = tmp_int[6]; + p->storage[i].card[0] = tmp_int[7]; + p->storage[i].card[1] = tmp_int[8]; + p->storage[i].card[2] = tmp_int[9]; + p->storage[i].card[3] = tmp_int[10]; + p->storage[i].broken = tmp_int[11]; + next += len; + if (str[next] == ' ') + next++; + } + + else if(sscanf(str + next, "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d%n", + &tmp_int[0], &tmp_int[1], &tmp_int[2], &tmp_int[3], + &tmp_int[4], &tmp_int[5], &tmp_int[6], + &tmp_int[7], &tmp_int[8], &tmp_int[9], &tmp_int[10], &len) == 11) { + p->storage[i].id = tmp_int[0]; + p->storage[i].nameid = tmp_int[1]; + p->storage[i].amount = tmp_int[2]; + p->storage[i].equip = tmp_int[3]; + p->storage[i].identify = tmp_int[4]; + p->storage[i].refine = tmp_int[5]; + p->storage[i].attribute = tmp_int[6]; + p->storage[i].card[0] = tmp_int[7]; + p->storage[i].card[1] = tmp_int[8]; + p->storage[i].card[2] = tmp_int[9]; + p->storage[i].card[3] = tmp_int[10]; + p->storage[i].broken = 0; + next += len; + if (str[next] == ' ') + next++; + } + + else return 0; + } + return 1; +} + +///////////////////////////////// +int mmo_char_fromstr(char *str, struct mmo_charstatus *p) { + int tmp_int[256]; + int set, next, len, i; + + // initilialise character + memset(p, '\0', sizeof(struct mmo_charstatus)); + + // If it's not char structure of version 1008 and after + if ((set = sscanf(str, "%d\t%d,%d\t%[^\t]\t%d,%d,%d\t%d,%d,%d\t%d,%d,%d,%d\t%d,%d,%d,%d,%d,%d\t%d,%d" + "\t%d,%d,%d\t%d,%d,%d\t%d,%d,%d\t%d,%d,%d,%d,%d" + "\t%[^,],%d,%d\t%[^,],%d,%d,%d%n", + &tmp_int[0], &tmp_int[1], &tmp_int[2], p->name, // + &tmp_int[3], &tmp_int[4], &tmp_int[5], + &tmp_int[6], &tmp_int[7], &tmp_int[8], + &tmp_int[9], &tmp_int[10], &tmp_int[11], &tmp_int[12], + &tmp_int[13], &tmp_int[14], &tmp_int[15], &tmp_int[16], &tmp_int[17], &tmp_int[18], + &tmp_int[19], &tmp_int[20], + &tmp_int[21], &tmp_int[22], &tmp_int[23], // + &tmp_int[24], &tmp_int[25], &tmp_int[26], + &tmp_int[27], &tmp_int[28], &tmp_int[29], + &tmp_int[30], &tmp_int[31], &tmp_int[32], &tmp_int[33], &tmp_int[34], + p->last_point.map, &tmp_int[35], &tmp_int[36], // + p->save_point.map, &tmp_int[37], &tmp_int[38], &tmp_int[39], &next)) != 43) { + tmp_int[39] = 0; // partner id + // If not char structure from version 384 to 1007 + if ((set = sscanf(str, "%d\t%d,%d\t%[^\t]\t%d,%d,%d\t%d,%d,%d\t%d,%d,%d,%d\t%d,%d,%d,%d,%d,%d\t%d,%d" + "\t%d,%d,%d\t%d,%d,%d\t%d,%d,%d\t%d,%d,%d,%d,%d" + "\t%[^,],%d,%d\t%[^,],%d,%d%n", + &tmp_int[0], &tmp_int[1], &tmp_int[2], p->name, // + &tmp_int[3], &tmp_int[4], &tmp_int[5], + &tmp_int[6], &tmp_int[7], &tmp_int[8], + &tmp_int[9], &tmp_int[10], &tmp_int[11], &tmp_int[12], + &tmp_int[13], &tmp_int[14], &tmp_int[15], &tmp_int[16], &tmp_int[17], &tmp_int[18], + &tmp_int[19], &tmp_int[20], + &tmp_int[21], &tmp_int[22], &tmp_int[23], // + &tmp_int[24], &tmp_int[25], &tmp_int[26], + &tmp_int[27], &tmp_int[28], &tmp_int[29], + &tmp_int[30], &tmp_int[31], &tmp_int[32], &tmp_int[33], &tmp_int[34], + p->last_point.map, &tmp_int[35], &tmp_int[36], // + p->save_point.map, &tmp_int[37], &tmp_int[38], &next)) != 42) { + // It's char structure of a version before 384 + tmp_int[26] = 0; // pet id + set = sscanf(str, "%d\t%d,%d\t%[^\t]\t%d,%d,%d\t%d,%d,%d\t%d,%d,%d,%d\t%d,%d,%d,%d,%d,%d\t%d,%d" + "\t%d,%d,%d\t%d,%d\t%d,%d,%d\t%d,%d,%d,%d,%d" + "\t%[^,],%d,%d\t%[^,],%d,%d%n", + &tmp_int[0], &tmp_int[1], &tmp_int[2], p->name, // + &tmp_int[3], &tmp_int[4], &tmp_int[5], + &tmp_int[6], &tmp_int[7], &tmp_int[8], + &tmp_int[9], &tmp_int[10], &tmp_int[11], &tmp_int[12], + &tmp_int[13], &tmp_int[14], &tmp_int[15], &tmp_int[16], &tmp_int[17], &tmp_int[18], + &tmp_int[19], &tmp_int[20], + &tmp_int[21], &tmp_int[22], &tmp_int[23], // + &tmp_int[24], &tmp_int[25], // + &tmp_int[27], &tmp_int[28], &tmp_int[29], + &tmp_int[30], &tmp_int[31], &tmp_int[32], &tmp_int[33], &tmp_int[34], + p->last_point.map, &tmp_int[35], &tmp_int[36], // + p->save_point.map, &tmp_int[37], &tmp_int[38], &next); + set += 2; + //printf("char: old char data ver.1\n"); + // Char structure of version 1007 or older + } else { + set++; + //printf("char: old char data ver.2\n"); + } + // Char structure of version 1008+ + } else { + //printf("char: new char data ver.3\n"); + } + if (set != 43) + return 0; + + p->char_id = tmp_int[0]; + p->account_id = tmp_int[1]; + p->char_num = tmp_int[2]; + p->class = tmp_int[3]; + p->base_level = tmp_int[4]; + p->job_level = tmp_int[5]; + p->base_exp = tmp_int[6]; + p->job_exp = tmp_int[7]; + p->zeny = tmp_int[8]; + p->hp = tmp_int[9]; + p->max_hp = tmp_int[10]; + p->sp = tmp_int[11]; + p->max_sp = tmp_int[12]; + p->str = tmp_int[13]; + p->agi = tmp_int[14]; + p->vit = tmp_int[15]; + p->int_ = tmp_int[16]; + p->dex = tmp_int[17]; + p->luk = tmp_int[18]; + p->status_point = tmp_int[19]; + p->skill_point = tmp_int[20]; + p->option = tmp_int[21]; + p->karma = tmp_int[22]; + p->manner = tmp_int[23]; + p->party_id = tmp_int[24]; + p->guild_id = tmp_int[25]; + p->pet_id = tmp_int[26]; + p->hair = tmp_int[27]; + p->hair_color = tmp_int[28]; + p->clothes_color = tmp_int[29]; + p->weapon = tmp_int[30]; + p->shield = tmp_int[31]; + p->head_top = tmp_int[32]; + p->head_mid = tmp_int[33]; + p->head_bottom = tmp_int[34]; + p->last_point.x = tmp_int[35]; + p->last_point.y = tmp_int[36]; + p->save_point.x = tmp_int[37]; + p->save_point.y = tmp_int[38]; + p->partner_id = tmp_int[39]; + + // Some checks + for(i = 0; i < char_num; i++) { + if (char_dat[i].char_id == p->char_id) { + printf("\033[1;31mmmo_auth_init: ******Error: a character has an identical id to another.\n"); + printf(" character id #%d -> new character not readed.\n", p->char_id); + printf(" Character saved in log file.\033[0m\n"); + return -1; + } else if (strcmp(char_dat[i].name, p->name) == 0) { + printf("\033[1;31mmmo_auth_init: ******Error: character name already exists.\n"); + printf(" character name '%s' -> new character not readed.\n", p->name); + printf(" Character saved in log file.\033[0m\n"); + return -2; + } + } + + if (str[next] == '\n' || str[next] == '\r') + return 1; // 新規データ + + next++; + + for(i = 0; str[next] && str[next] != '\t'; i++) { + set = sscanf(str+next, "%[^,],%d,%d%n", p->memo_point[i].map, &tmp_int[0], &tmp_int[1], &len); + if (set != 3) + return -3; + p->memo_point[i].x = tmp_int[0]; + p->memo_point[i].y = tmp_int[1]; + next += len; + if (str[next] == ' ') + next++; + } + + next++; + + for(i = 0; str[next] && str[next] != '\t'; i++) { + if (sscanf(str + next, "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d%n", + &tmp_int[0], &tmp_int[1], &tmp_int[2], &tmp_int[3], + &tmp_int[4], &tmp_int[5], &tmp_int[6], + &tmp_int[7], &tmp_int[8], &tmp_int[9], &tmp_int[10], &tmp_int[11], &len) == 12) { + // do nothing, it's ok + } else if(sscanf(str + next, "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d%n", + &tmp_int[0], &tmp_int[1], &tmp_int[2], &tmp_int[3], + &tmp_int[4], &tmp_int[5], &tmp_int[6], + &tmp_int[7], &tmp_int[8], &tmp_int[9], &tmp_int[10], &len) == 11) { + tmp_int[11] = 0; // broken doesn't exist in this version -> 0 + } else // invalid structure + return -4; + p->inventory[i].id = tmp_int[0]; + p->inventory[i].nameid = tmp_int[1]; + p->inventory[i].amount = tmp_int[2]; + p->inventory[i].equip = tmp_int[3]; + p->inventory[i].identify = tmp_int[4]; + p->inventory[i].refine = tmp_int[5]; + p->inventory[i].attribute = tmp_int[6]; + p->inventory[i].card[0] = tmp_int[7]; + p->inventory[i].card[1] = tmp_int[8]; + p->inventory[i].card[2] = tmp_int[9]; + p->inventory[i].card[3] = tmp_int[10]; + p->inventory[i].broken = tmp_int[11]; + next += len; + if (str[next] == ' ') + next++; + } + + next++; + + for(i = 0; str[next] && str[next] != '\t'; i++) { + if (sscanf(str + next, "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d%n", + &tmp_int[0], &tmp_int[1], &tmp_int[2], &tmp_int[3], + &tmp_int[4], &tmp_int[5], &tmp_int[6], + &tmp_int[7], &tmp_int[8], &tmp_int[9], &tmp_int[10], &tmp_int[11], &len) == 12) { + // do nothing, it's ok + } else if (sscanf(str + next, "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d%n", + &tmp_int[0], &tmp_int[1], &tmp_int[2], &tmp_int[3], + &tmp_int[4], &tmp_int[5], &tmp_int[6], + &tmp_int[7], &tmp_int[8], &tmp_int[9], &tmp_int[10], &len) == 11) { + tmp_int[11] = 0; // broken doesn't exist in this version -> 0 + } else // invalid structure + return -5; + p->cart[i].id = tmp_int[0]; + p->cart[i].nameid = tmp_int[1]; + p->cart[i].amount = tmp_int[2]; + p->cart[i].equip = tmp_int[3]; + p->cart[i].identify = tmp_int[4]; + p->cart[i].refine = tmp_int[5]; + p->cart[i].attribute = tmp_int[6]; + p->cart[i].card[0] = tmp_int[7]; + p->cart[i].card[1] = tmp_int[8]; + p->cart[i].card[2] = tmp_int[9]; + p->cart[i].card[3] = tmp_int[10]; + p->cart[i].broken = tmp_int[11]; + next += len; + if (str[next] == ' ') + next++; + } + + next++; + + for(i = 0; str[next] && str[next] != '\t'; i++) { + set = sscanf(str + next, "%d,%d%n", &tmp_int[0], &tmp_int[1], &len); + if (set != 2) + return -6; + p->skill[tmp_int[0]].id = tmp_int[0]; + p->skill[tmp_int[0]].lv = tmp_int[1]; + next += len; + if (str[next] == ' ') + next++; + } + + next++; + + for(i = 0; str[next] && str[next] != '\t' && str[next] != '\n' && str[next] != '\r'; i++) { // global_reg実装以前のathena.txt互換のため一応'\n'チェック + set = sscanf(str + next, "%[^,],%d%n", p->global_reg[i].str, &p->global_reg[i].value, &len); + if (set != 2) { + // because some scripts are not correct, the str can be "". So, we must check that. + // If it's, we must not refuse the character, but just this REG value. + // Character line will have something like: nov_2nd_cos,9 ,9 nov_1_2_cos_c,1 (here, ,9 is not good) + if (str[next] == ',' && sscanf(str + next, ",%d%n", &p->global_reg[i].value, &len) == 1) + i--; + else + return -7; + } + next += len; + if (str[next] == ' ') + next++; + } + p->global_reg_num = i; + + return 1; +} + +//========================================================================================================== +int mmo_char_tosql(int char_id, struct mmo_charstatus *p){ + int i,save_flag; + + save_flag = char_id; + printf("request save char data... (%d)\n",char_id); + + //`char`( `char_id`,`account_id`,`char_num`,`name`,`class`,`base_level`,`job_level`,`base_exp`,`job_exp`,`zeny`, //9 + //`str`,`agi`,`vit`,`int`,`dex`,`luk`, //15 + //`max_hp`,`hp`,`max_sp`,`sp`,`status_point`,`skill_point`, //21 + //`option`,`karma`,`manner`,`party_id`,`guild_id`,`pet_id`, //27 + //`hair`,`hair_color`,`clothes_color`,`weapon`,`shield`,`head_top`,`head_mid`,`head_bottom`, //35 + //`last_map`,`last_x`,`last_y`,`save_map`,`save_x`,`save_y`) + sprintf(tmp_sql ,"INSERT INTO `char` SET `char_id`='%d', `account_id`='%d', `char_num`='%d', `name`='%s', `class`='%d', `base_level`='%d', `job_level`='%d'," + "`base_exp`='%d', `job_exp`='%d', `zeny`='%d'," + "`max_hp`='%d',`hp`='%d',`max_sp`='%d',`sp`='%d',`status_point`='%d',`skill_point`='%d'," + "`str`='%d',`agi`='%d',`vit`='%d',`int`='%d',`dex`='%d',`luk`='%d'," + "`option`='%d',`karma`='%d',`manner`='%d',`party_id`='%d',`guild_id`='%d',`pet_id`='%d'," + "`hair`='%d',`hair_color`='%d',`clothes_color`='%d',`weapon`='%d',`shield`='%d',`head_top`='%d',`head_mid`='%d',`head_bottom`='%d'," + "`last_map`='%s',`last_x`='%d',`last_y`='%d',`save_map`='%s',`save_x`='%d',`save_y`='%d', `partner_id` = '%d'", + char_id,p->account_id,p->char_num,p->name,p->class, p->base_level, p->job_level, + p->base_exp, p->job_exp, p->zeny, + p->max_hp, p->hp, p->max_sp, p->sp, p->status_point, p->skill_point, + p->str, p->agi, p->vit, p->int_, p->dex, p->luk, + p->option, p->karma, p->manner, p->party_id, p->guild_id, p->pet_id, + p->hair, p->hair_color, p->clothes_color, + p->weapon, p->shield, p->head_top, p->head_mid, p->head_bottom, + p->last_point.map, p->last_point.x, p->last_point.y, + p->save_point.map, p->save_point.x, p->save_point.y, p->partner_id + ); + + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (update `char`)- %s\n", mysql_error(&mysql_handle) ); + } + + //`memo` (`memo_id`,`char_id`,`map`,`x`,`y`) + sprintf(tmp_sql,"DELETE FROM `memo` WHERE `char_id`='%d'",char_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (delete `memo`)- %s\n", mysql_error(&mysql_handle) ); + } + + //insert here. + for(i=0;i<10;i++){ + if(p->memo_point[i].map[0]){ + sprintf(tmp_sql,"INSERT INTO `memo`(`char_id`,`map`,`x`,`y`) VALUES ('%d', '%s', '%d', '%d')", + char_id, p->memo_point[i].map, p->memo_point[i].x, p->memo_point[i].y); + if(mysql_query(&mysql_handle, tmp_sql) ) + printf("DB server Error (insert `memo`)- %s\n", mysql_error(&mysql_handle) ); + } + } + //`inventory` (`id`,`char_id`, `nameid`, `amount`, `equip`, `identify`, `refine`, `attribute`, `card0`, `card1`, `card2`, `card3`, `broken`) + sprintf(tmp_sql,"DELETE FROM `inventory` WHERE `char_id`='%d'",char_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (delete `inventory`)- %s\n", mysql_error(&mysql_handle) ); + } + + //insert here. + for(i=0;i<MAX_INVENTORY;i++){ + if(p->inventory[i].nameid){ + sprintf(tmp_sql,"INSERT INTO `inventory`(`id`,`char_id`, `nameid`, `amount`, `equip`, `identify`, `refine`, `attribute`, `card0`, `card1`, `card2`, `card3`, `broken`)" + " VALUES ('%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d')", + p->inventory[i].id, char_id,p->inventory[i].nameid, p->inventory[i].amount, p->inventory[i].equip, + p->inventory[i].identify, p->inventory[i].refine, p->inventory[i].attribute, + p->inventory[i].card[0], p->inventory[i].card[1], p->inventory[i].card[2], p->inventory[i].card[3], p->inventory[i].broken); + if(mysql_query(&mysql_handle, tmp_sql) ) + printf("DB server Error (insert `inventory`)- %s\n", mysql_error(&mysql_handle) ); + } + } + + //`cart_inventory` (`id`,`char_id`, `nameid`, `amount`, `equip`, `identify`, `refine`, `attribute`, `card0`, `card1`, `card2`, `card3`, `broken`) + sprintf (tmp_sql, "DELETE FROM `cart_inventory` WHERE `char_id`='%d'", char_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (delete `cart_inventory`)- %s\n", mysql_error(&mysql_handle) ); + } + + //insert here. + for(i=0;i<MAX_CART;i++){ + if(p->cart[i].nameid){ + sprintf(tmp_sql,"INSERT INTO `cart_inventory`(`id`,`char_id`, `nameid`, `amount`, `equip`, `identify`, `refine`, `attribute`, `card0`, `card1`, `card2`, `card3`, `broken`)" + " VALUES ('%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d' )", + p->cart[i].id, char_id, p->cart[i].nameid, p->cart[i].amount, p->cart[i].equip, + p->cart[i].identify, p->cart[i].refine, p->cart[i].attribute, + p->cart[i].card[0], p->cart[i].card[1], p->cart[i].card[2], p->cart[i].card[3], p->cart[i].broken ); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (insert `cart_inventory`)- %s\n", mysql_error(&mysql_handle) ); + } + } + } + + + //`skill` (`char_id`, `id`, `lv`) + sprintf(tmp_sql,"DELETE FROM `skill` WHERE `char_id`='%d'",char_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (delete `skill`)- %s\n", mysql_error(&mysql_handle) ); + } + + //insert here. + for(i=0;i<MAX_SKILL;i++){ + if(p->skill[i].id){ + if (p->skill[i].id && p->skill[i].flag!=1) { + sprintf(tmp_sql,"INSERT INTO `skill`(`char_id`,`id`, `lv`) VALUES ('%d', '%d', '%d')", + char_id, p->skill[i].id, (p->skill[i].flag==0)?p->skill[i].lv:p->skill[i].flag-2); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (insert `skill`)- %s\n", mysql_error(&mysql_handle) ); + } + } + } + } + //`global_reg_value` (`char_id`, `str`, `value`) + sprintf(tmp_sql,"DELETE FROM `global_reg_value` WHERE `char_id`='%d'",char_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (delete `global_reg_value`)- %s\n", mysql_error(&mysql_handle) ); + } + + //insert here. + for(i=0;i<p->global_reg_num;i++){ + if(p->global_reg[i].value !=0){ + sprintf(tmp_sql,"INSERT INTO `global_reg_value` (`char_id`, `str`, `value`) VALUES ('%d', '%s','%d')", + char_id, p->global_reg[i].str, p->global_reg[i].value); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (insert `global_reg_value`)- %s\n", mysql_error(&mysql_handle) ); + } + } + } + + printf("saving char is done... (%d)\n",char_id); + save_flag = 0; + + return 0; +} +//========================================================================================================== + +int mmo_char_init(void){ + char line[65536]; + struct s_pet *p; + int ret; + int i=0,set,tmp_int[2], c= 0; + char input; + FILE *fp; + + + //DB connection initialized + mysql_init(&mysql_handle); + printf("Connect DB server.... (inter server)\n"); + if(!mysql_real_connect(&mysql_handle, db_server_ip, db_server_id, db_server_pw, + db_server_logindb ,db_server_port, (char *)NULL, 0)) { + //pointer check + printf("%s\n",mysql_error(&mysql_handle)); + exit(1); + } + else { + printf ("connect success! (inter server)\n"); + } + + + + printf("Warning : Make sure you backup your databases before continuing!\n"); + printf("\nDo you wish to convert your Character Database to SQL? (y/n) : "); + input=getchar(); + if(input == 'y' || input == 'Y'){ + printf("\nConverting Character Database...\n"); + fp=fopen("save/athena.txt","r"); + char_dat=malloc(sizeof(char_dat[0])*256); + char_max=256; + if(fp==NULL) + return 0; + while(fgets(line, 65535, fp)){ + if(char_num>=char_max){ + char_max+=256; + char_dat=realloc(char_dat, sizeof(char_dat[0]) *char_max); + } + memset(&char_dat[char_num], 0, sizeof(char_dat[0])); + ret=mmo_char_fromstr(line, &char_dat[char_num]); + if(ret){ + mmo_char_tosql(char_dat[char_num].char_id , &char_dat[char_num]); + printf ("convert %d -> DB\n",char_dat[char_num].char_id); + if(char_dat[char_num].char_id>=char_id_count) + char_id_count=char_dat[char_num].char_id+1; + char_num++; + } + } + printf("char data convert end\n"); + fclose(fp); + } + + while(getchar() != '\n'); + printf("\nDo you wish to convert your Storage Database to SQL? (y/n) : "); + input=getchar(); + if(input == 'y' || input == 'Y') { + printf("\nConverting Storage Database...\n"); + fp=fopen(storage_txt,"r"); + if(fp==NULL){ + printf("cant't read : %s\n",storage_txt); + return 0; + } + + while(fgets(line,65535,fp)){ + set=sscanf(line,"%d,%d",&tmp_int[0],&tmp_int[1]); + if(set==2) { + if(i==0){ + storage=malloc(sizeof(struct storage)); + }else{ + storage=realloc(storage,sizeof(struct storage)*(i+1)); + } + memset(&storage[i],0,sizeof(struct storage)); + storage[i].account_id=tmp_int[0]; + storage_fromstr(line,&storage[i]); + storage_tosql(tmp_int[0],&storage[i]); //to sql. (dump) + i++; + } + } + fclose(fp); + } + + while(getchar() != '\n'); + printf("\nDo you wish to convert your Pet Database to SQL? (y/n) : "); + input=getchar(); + if(input == 'y' || input == 'Y') { + printf("\nConverting Pet Database...\n"); + if( (fp=fopen(pet_txt,"r")) ==NULL ) + return 1; + + p=malloc(sizeof(struct s_pet)); + while(fgets(line, sizeof(line), fp)){ + if(p==NULL){ + printf("int_pet: out of memory!\n"); + exit(0); + } + if(inter_pet_fromstr(line, p)==0 && p->pet_id>0){ + //pet dump + inter_pet_tosql(p->pet_id,p); + }else{ + printf("int_pet: broken data [%s] line %d\n", pet_txt, c); + } + c++; + } + fclose(fp); + } + + return 0; +} +int inter_config_read(const char *cfgName) { + printf ("start reading interserver configuration: %s\n",cfgName); + int i; + char line[1024], w1[1024], w2[1024]; + FILE *fp; + + fp=fopen(cfgName,"r"); + if(fp==NULL){ + printf("file not found: %s\n", cfgName); + return 1; + } + while(fgets(line, 1020, fp)){ + i=sscanf(line,"%[^:]:%s", w1, w2); + if(i!=2) + continue; + if(strcmpi(w1,"storage_txt")==0){ + printf ("set storage_txt : %s\n",w2); + strncpy(storage_txt, w2, sizeof(storage_txt)); + } else if(strcmpi(w1,"pet_txt")==0){ + printf ("set pet_txt : %s\n",w2); + strncpy(pet_txt, w2, sizeof(pet_txt)); + } + //add for DB connection + else if(strcmpi(w1,"db_server_ip")==0){ + strcpy(db_server_ip, w2); + printf ("set db_server_ip : %s\n",w2); + } + else if(strcmpi(w1,"db_server_port")==0){ + db_server_port=atoi(w2); + printf ("set db_server_port : %s\n",w2); + } + else if(strcmpi(w1,"db_server_id")==0){ + strcpy(db_server_id, w2); + printf ("set db_server_id : %s\n",w2); + } + else if(strcmpi(w1,"db_server_pw")==0){ + strcpy(db_server_pw, w2); + printf ("set db_server_pw : %s\n",w2); + } + else if(strcmpi(w1,"db_server_logindb")==0){ + strcpy(db_server_logindb, w2); + printf ("set db_server_logindb : %s\n",w2); + } + } + fclose(fp); + + printf ("success reading interserver configuration\n"); + + return 0; +} + +int char_config_read(const char *cfgName) { + printf ("start reading interserver configuration: %s\n",cfgName); + int i; + char line[1024], w1[1024], w2[1024]; + FILE *fp; + + fp=fopen(cfgName,"r"); + if(fp==NULL){ + printf("file not found: %s\n", cfgName); + return 1; + } + + while(fgets(line, 1020, fp)){ + if(line[0] == '/' && line[1] == '/') + continue; + + i=sscanf(line,"%[^:]:%s", w1, w2); + if(i!=2) + continue; + if(strcmpi(w1,"char_txt")==0){ + printf ("set char_txt : %s\n",w2); + strcpy(char_txt, w2); + } + } + fclose(fp); + printf("reading configure done.....\n"); + + return 0; +} + +int do_init(int argc, char **argv){ + + + char_config_read((argc>1)?argv[1]:CHAR_CONF_NAME); + inter_config_read((argc>2)?argv[2]:inter_cfgName); + + mmo_char_init(); + printf ("all conversion success!\n"); + exit (0); + return 0; +} + + diff --git a/misc/src/txt-converter/char/char.h b/misc/src/txt-converter/char/char.h new file mode 100644 index 0000000..4712b4d --- /dev/null +++ b/misc/src/txt-converter/char/char.h @@ -0,0 +1,38 @@ +#include "../../common/core.h" +#include "../../common/socket.h" +#include "../../common/timer.h" +#include "../common/mmo.h" +#include "../../common/version.h" +#include "../../common/db.h" + +#ifndef _CHAR_H_ +#define _CHAR_H_ + +#define MAX_MAP_SERVERS 30 + +#define CHAR_CONF_NAME "conf/char_athena.conf" + +#define UNKNOWN_CHAR_NAME "Unknown" + +#define DEFAULT_AUTOSAVE_INTERVAL 300*1000 + +struct mmo_map_server{ + long ip; + short port; + int users; + char map[MAX_MAP_PER_SERVER][16]; +}; + +int mapif_sendall(unsigned char *buf,unsigned int len); +int mapif_sendallwos(int fd,unsigned char *buf,unsigned int len); +int mapif_send(int fd,unsigned char *buf,unsigned int len); + +extern int autosave_interval; + +#endif + +#include "inter.h" +#include "int_pet.h" +#include "int_guild.h" +#include "int_party.h" +#include "int_storage.h" diff --git a/misc/src/txt-converter/char/int_guild.h b/misc/src/txt-converter/char/int_guild.h new file mode 100644 index 0000000..2ea8594 --- /dev/null +++ b/misc/src/txt-converter/char/int_guild.h @@ -0,0 +1,10 @@ +#ifndef _INT_GUILD_H_ +#define _INT_GUILD_H_ + +int inter_guild_init(); +int inter_guild_save(); +int inter_guild_parse_frommap(int fd); + +extern char guild_txt[256]; + +#endif diff --git a/misc/src/txt-converter/char/int_party.h b/misc/src/txt-converter/char/int_party.h new file mode 100644 index 0000000..036db1a --- /dev/null +++ b/misc/src/txt-converter/char/int_party.h @@ -0,0 +1,11 @@ +#ifndef _INT_PARTY_H_ +#define _INT_PARTY_H_ + +int inter_party_init(); +int inter_party_save(); + +int inter_party_parse_frommap(int fd); + +extern char party_txt[256]; + +#endif diff --git a/misc/src/txt-converter/char/int_pet.h b/misc/src/txt-converter/char/int_pet.h new file mode 100644 index 0000000..27ba4fc --- /dev/null +++ b/misc/src/txt-converter/char/int_pet.h @@ -0,0 +1,12 @@ +#ifndef _INT_PET_H_ +#define _INT_PET_H_ + +int inter_pet_init(); +int inter_pet_save(); +int inter_pet_delete(int pet_id); + +int inter_pet_parse_frommap(int fd); + +//extern char pet_txt[256]; + +#endif diff --git a/misc/src/txt-converter/char/int_storage.h b/misc/src/txt-converter/char/int_storage.h new file mode 100644 index 0000000..3572ae5 --- /dev/null +++ b/misc/src/txt-converter/char/int_storage.h @@ -0,0 +1,11 @@ +#ifndef _INT_STORAGE_H_ +#define _INT_STORAGE_H_ + +int inter_storage_init(); +int inter_storage_save(); + +int inter_storage_parse_frommap(int fd); + +//extern char storage_txt[256]; + +#endif diff --git a/misc/src/txt-converter/char/inter.h b/misc/src/txt-converter/char/inter.h new file mode 100644 index 0000000..ee39944 --- /dev/null +++ b/misc/src/txt-converter/char/inter.h @@ -0,0 +1,28 @@ +#ifndef _INTER_H_ +#define _INTER_H_ + +int inter_init(const char *file); +int inter_save(); +int inter_parse_frommap(int fd); + +int inter_check_length(int fd,int length); + +#define inter_cfgName "conf/inter_athena.conf" + + +//add include for DBMS(mysql) +#include <mysql.h> + +extern MYSQL mysql_handle; +extern char tmp_sql[65535]; +extern MYSQL_RES* sql_res ; +extern MYSQL_ROW sql_row ; +extern int sql_cnt; + +extern int db_server_port; +extern char db_server_ip[16]; +extern char db_server_id[32]; +extern char db_server_pw[32]; +extern char db_server_logindb[32]; + +#endif diff --git a/misc/src/txt-converter/char/strlib.c b/misc/src/txt-converter/char/strlib.c new file mode 100644 index 0000000..60803c1 --- /dev/null +++ b/misc/src/txt-converter/char/strlib.c @@ -0,0 +1,66 @@ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "strlib.h" + +//----------------------------------------------- +// string lib. +unsigned char* jstrescape (unsigned char* pt) { + //copy from here + unsigned char * ptr; + int i =0, j=0; + + //copy string to temporary + ptr = malloc(J_MAX_MALLOC_SIZE); + strcpy (ptr,pt); + + while (ptr[i] != '\0') { + switch (ptr[i]) { + case '\'': + pt[j++] = '\\'; + pt[j++] = ptr[i++]; + break; + default: + pt[j++] = ptr[i++]; + } + } + pt[j++] = '\0'; + free (ptr); + return (unsigned char*) &pt[0]; +} + +unsigned char* jstrescapecpy (unsigned char* pt,unsigned char* spt) { + //copy from here + int i =0, j=0; + + while (spt[i] != '\0') { + switch (spt[i]) { + case '\'': + pt[j++] = '\\'; + pt[j++] = spt[i++]; + break; + default: + pt[j++] = spt[i++]; + } + } + pt[j++] = '\0'; + return (unsigned char*) &pt[0]; +} +int jmemescapecpy (unsigned char* pt,unsigned char* spt, int size) { + //copy from here + int i =0, j=0; + + while (i < size) { + switch (spt[i]) { + case '\'': + pt[j++] = '\\'; + pt[j++] = spt[i++]; + break; + default: + pt[j++] = spt[i++]; + } + } + // copy size is 0 ~ (j-1) + return j; +} diff --git a/misc/src/txt-converter/char/strlib.h b/misc/src/txt-converter/char/strlib.h new file mode 100644 index 0000000..442cfac --- /dev/null +++ b/misc/src/txt-converter/char/strlib.h @@ -0,0 +1,9 @@ +#ifndef _J_STR_H_ +#define _J_STR_H_ +#define J_MAX_MALLOC_SIZE 65535 +//string functions. +//code by Jioh L. Jung +unsigned char* jstrescape (unsigned char* pt); +unsigned char* jstrescapecpy (unsigned char* pt,unsigned char* spt); +int jmemescapecpy (unsigned char* pt,unsigned char* spt, int size); +#endif diff --git a/misc/src/txt-converter/common/mmo.h b/misc/src/txt-converter/common/mmo.h new file mode 100644 index 0000000..df42783 --- /dev/null +++ b/misc/src/txt-converter/common/mmo.h @@ -0,0 +1,280 @@ +// Original : mmo.h 2003/03/14 12:07:02 Rev.1.7 + +#ifndef _MMO_H_ +#define _MMO_H_ + +#include <time.h> + +#ifdef CYGWIN +// txtやlogなどの書き出すファイルの改行コード +#define RETCODE "\r\n" // (CR/LF:Windows系) +#else +#define RETCODE "\n" // (LF:Unix系) +#endif + +#define FIFOSIZE_SERVERLINK 128*1024 + +#define MAX_MAP_PER_SERVER 512 +#define MAX_INVENTORY 100 +#define MAX_AMOUNT 30000 +#define MAX_ZENY 1000000000 // 1G zeny +#define MAX_CART 100 +#define MAX_SKILL 450 +#define GLOBAL_REG_NUM 96 +#define ACCOUNT_REG_NUM 16 +#define ACCOUNT_REG2_NUM 16 +#define DEFAULT_WALK_SPEED 150 +#define MIN_WALK_SPEED 0 +#define MAX_WALK_SPEED 1000 +#define MAX_STORAGE 100 +#define MAX_GUILD_STORAGE 1000 +#define MAX_PARTY 12 +#define MAX_GUILD 56 // increased max guild members to accomodate for +2 increase for extension levels [Valaris] +#define MAX_GUILDPOSITION 56 // increased max guild positions to accomodate for all members [Valaris] +#define MAX_GUILDEXPLUSION 32 +#define MAX_GUILDALLIANCE 16 +#define MAX_GUILDSKILL 8 +#define MAX_GUILDCASTLE 24 // increased to include novice castles [Valaris] +#define MAX_GUILDLEVEL 50 + +#define MIN_HAIR_STYLE 0 +#define MAX_HAIR_STYLE 20 +#define MIN_HAIR_COLOR 0 +#define MAX_HAIR_COLOR 9 +#define MIN_CLOTH_COLOR 0 +#define MAX_CLOTH_COLOR 4 + +// for produce +#define MIN_ATTRIBUTE 0 +#define MAX_ATTRIBUTE 4 +#define ATTRIBUTE_NORMAL 0 +#define MIN_STAR 0 +#define MAX_STAR 3 + +#define MIN_PORTAL_MEMO 0 +#define MAX_PORTAL_MEMO 2 + +#define MAX_STATUS_TYPE 5 + +#define WEDDING_RING_M 2634 +#define WEDDING_RING_F 2635 + +#define CHAR_CONF_NAME "conf/char_athena.conf" + +struct item { + int id; + short nameid; + short amount; + unsigned short equip; + char identify; + char refine; + char attribute; + short card[4]; + short broken; +}; +struct point{ + char map[24]; + short x,y; +}; +struct skill { + unsigned short id,lv,flag; +}; +struct global_reg { + char str[32]; + int value; +}; +struct s_pet { + int account_id; + int char_id; + int pet_id; + short class; + short level; + short egg_id;//pet egg id + short equip;//pet equip name_id + short intimate;//pet friendly + short hungry;//pet hungry + char name[24]; + char rename_flag; + char incuvate; +}; + +struct mmo_charstatus { + int char_id; + int account_id; + int partner_id; + + int base_exp,job_exp,zeny; + + short class; + short status_point,skill_point; + int hp,max_hp,sp,max_sp; + short option,karma,manner; + short hair,hair_color,clothes_color; + int party_id,guild_id,pet_id; + + short weapon,shield; + short head_top,head_mid,head_bottom; + + char name[24]; + unsigned char base_level,job_level; + short str,agi,vit,int_,dex,luk; + unsigned char char_num,sex; + + struct point last_point,save_point,memo_point[10]; + struct item inventory[MAX_INVENTORY],cart[MAX_CART]; + struct skill skill[MAX_SKILL]; + int global_reg_num; + struct global_reg global_reg[GLOBAL_REG_NUM]; + int account_reg_num; + struct global_reg account_reg[ACCOUNT_REG_NUM]; + int account_reg2_num; + struct global_reg account_reg2[ACCOUNT_REG2_NUM]; +}; + +struct storage { + int account_id; + short storage_status; + short storage_amount; + struct item storage[MAX_STORAGE]; +}; + +struct guild_storage { + int guild_id; + short storage_status; + short storage_amount; + struct item storage[MAX_GUILD_STORAGE]; +}; + +struct map_session_data; + +struct gm_account { + int account_id; + int level; +}; + +struct party_member { + int account_id; + char name[24],map[24]; + int leader,online,lv; + struct map_session_data *sd; +}; +struct party { + int party_id; + char name[24]; + int exp; + int item; + struct party_member member[MAX_PARTY]; +}; + +struct guild_member { + int account_id, char_id; + short hair,hair_color,gender,class,lv; + int exp,exp_payper; + short online,position; + int rsv1,rsv2; + char name[24]; + struct map_session_data *sd; +}; +struct guild_position { + char name[24]; + int mode; + int exp_mode; +}; +struct guild_alliance { + int opposition; + int guild_id; + char name[24]; +}; +struct guild_explusion { + char name[24]; + char mes[40]; + char acc[40]; + int account_id; + int rsv1,rsv2,rsv3; +}; +struct guild_skill { + int id,lv; +}; +struct guild { + int guild_id; + short guild_lv, connect_member, max_member, average_lv; + int exp,next_exp,skill_point,castle_id; + char name[24],master[24]; + struct guild_member member[MAX_GUILD]; + struct guild_position position[MAX_GUILDPOSITION]; + char mes1[60],mes2[120]; + int emblem_len,emblem_id; + char emblem_data[2048]; + struct guild_alliance alliance[MAX_GUILDALLIANCE]; + struct guild_explusion explusion[MAX_GUILDEXPLUSION]; + struct guild_skill skill[MAX_GUILDSKILL]; +}; +struct guild_castle { + int castle_id; + char map_name[24]; + char castle_name[24]; + int guild_id; + int economy; + int defense; + int triggerE; + int triggerD; + int nextTime; + int payTime; + int createTime; + int visibleC; + int visibleG0; + int visibleG1; + int visibleG2; + int visibleG3; + int visibleG4; + int visibleG5; + int visibleG6; + int visibleG7; + int Ghp0; // added Guardian HP [Valaris] + int Ghp1; + int Ghp2; + int Ghp3; + int Ghp4; + int Ghp5; + int Ghp6; + int Ghp7; + int GID0; + int GID1; + int GID2; + int GID3; + int GID4; + int GID5; + int GID6; + int GID7; // end addition [Valaris] + +}; +struct square { + int val1[5]; + int val2[5]; +}; + +enum { + GBI_EXP =1, // ギルドのEXP + GBI_GUILDLV =2, // ギルドのLv + GBI_SKILLPOINT =3, // ギルドのスキルポイント + GBI_SKILLLV =4, // ギルドスキルLv + + GMI_POSITION =0, // メンバーの役職変更 + GMI_EXP =1, // メンバーのEXP + +}; + +#ifndef strcmpi +#define strcmpi strcasecmp +#endif +#ifndef stricmp +#define stricmp strcasecmp +#endif +#ifndef strncmpi +#define strncmpi strncasecmp +#endif +#ifndef strnicmp +#define strnicmp strncasecmp +#endif + +#endif // _MMO_H_ diff --git a/misc/src/txt-converter/login/GNUmakefile b/misc/src/txt-converter/login/GNUmakefile new file mode 100644 index 0000000..965a0e0 --- /dev/null +++ b/misc/src/txt-converter/login/GNUmakefile @@ -0,0 +1,11 @@ +all: login-converter
+sql: login-converter
+
+COMMON_OBJ = ../../common/core.o ../../common/socket.o ../../common/timer.o ../../common/db.o ../../common/malloc.o
+COMMON_H = ../../common/core.h ../../common/socket.h ../../common/timer.h ../../common/mmo.h ../../common/version.h ../../common/db.h ../../common/malloc.h
+
+login-converter: login-converter.o ../../login_sql/md5calc.o ../../login_sql/strlib.o $(COMMON_OBJ)
+ $(CC) -o ../../../$@ $^ $(LIB_S)
+login-converter.o: login-converter.c ../../login_sql/login.h ../../login_sql/md5calc.h ../../login_sql/strlib.h $(COMMON_H)
+clean:
+ rm -f *.o ../../../login-converter
diff --git a/misc/src/txt-converter/login/Makefile b/misc/src/txt-converter/login/Makefile new file mode 100644 index 0000000..965a0e0 --- /dev/null +++ b/misc/src/txt-converter/login/Makefile @@ -0,0 +1,11 @@ +all: login-converter
+sql: login-converter
+
+COMMON_OBJ = ../../common/core.o ../../common/socket.o ../../common/timer.o ../../common/db.o ../../common/malloc.o
+COMMON_H = ../../common/core.h ../../common/socket.h ../../common/timer.h ../../common/mmo.h ../../common/version.h ../../common/db.h ../../common/malloc.h
+
+login-converter: login-converter.o ../../login_sql/md5calc.o ../../login_sql/strlib.o $(COMMON_OBJ)
+ $(CC) -o ../../../$@ $^ $(LIB_S)
+login-converter.o: login-converter.c ../../login_sql/login.h ../../login_sql/md5calc.h ../../login_sql/strlib.h $(COMMON_H)
+clean:
+ rm -f *.o ../../../login-converter
diff --git a/misc/src/txt-converter/login/login-converter.c b/misc/src/txt-converter/login/login-converter.c new file mode 100644 index 0000000..c0055da --- /dev/null +++ b/misc/src/txt-converter/login/login-converter.c @@ -0,0 +1,252 @@ +// $Id: login-converter.c,v 1.1.1.1 2004/09/10 17:45:03 MagicalTux Exp $ +// original : login2.c 2003/01/28 02:29:17 Rev.1.1.1.1 +// login data file to mysql conversion utility. +// +#include <sys/types.h> +#include <sys/socket.h> +#include <stdio.h> +#include <stdlib.h> +#include <netinet/in.h> +#include <sys/time.h> +#include <time.h> +#include <sys/ioctl.h> +#include <unistd.h> +#include <signal.h> +#include <fcntl.h> +#include <string.h> +#include <arpa/inet.h> + +#include <mysql.h> + +#include "../../common/core.h" +#include "../../common/socket.h" +#include "../../login/login.h" +#include "../../common/mmo.h" +#include "../../common/version.h" +#include "../../common/db.h" + +int account_id_count = START_ACCOUNT_NUM; +int server_num; +int new_account_flag = 0; +int login_port = 6900; + +struct mmo_char_server server[MAX_SERVERS]; +int server_fd[MAX_SERVERS]; + +#define AUTH_FIFO_SIZE 256 +struct { + int account_id,login_id1,login_id2; + int sex,delflag; +} auth_fifo[AUTH_FIFO_SIZE]; +int auth_fifo_pos=0; +struct { + int account_id, sex; + char userid[24], pass[24], lastlogin[24]; + int logincount; + int state; // packet 0x006a value + 1 (0: compte OK) + char email[40]; // e-mail (by default: a@a.com) + char error_message[20]; // Message of error code #6 = Your are Prohibited to log in until %s (packet 0x006a) + time_t ban_until_time; // # of seconds 1/1/1970 (timestamp): ban time limit of the account (0 = no ban) + time_t connect_until_time; // # of seconds 1/1/1970 (timestamp): Validity limit of the account (0 = unlimited) + char last_ip[16]; // save of last IP of connection + char memo[255]; // a memo field + int account_reg2_num; + struct global_reg account_reg2[ACCOUNT_REG2_NUM]; +} *auth_dat; +int auth_num=0,auth_max=0; + +char login_account_id[256]="account_id"; +char login_userid[256]="userid"; +char login_user_pass[256]="user_pass"; +char login_db[256]="login"; + +static struct dbt *gm_account_db; + +int db_server_port = 3306; +char db_server_ip[16] = "127.0.0.1"; +char db_server_id[32] = "ragnarok"; +char db_server_pw[32] = "ragnarok"; +char db_server_logindb[32] = "ragnarok"; + +int isGM(int account_id) +{ + struct gm_account *p; + p = numdb_search(gm_account_db,account_id); + if( p == NULL) + return 0; + return p->level; +} + +int read_gm_account() +{ + char line[8192]; + struct gm_account *p; + FILE *fp; + int c=0; + + gm_account_db = numdb_init(); + printf("gm_account: read start\n"); + + if( (fp=fopen("conf/GM_account.txt","r"))==NULL ) + return 1; + while(fgets(line,sizeof(line),fp)){ + if(line[0] == '/' && line[1] == '/') + continue; + p=malloc(sizeof(struct gm_account)); + if(p==NULL){ + printf("gm_account: out of memory!\n"); + exit(0); + } + if(sscanf(line,"%d %d",&p->account_id,&p->level) != 2 || p->level <= 0) { + printf("gm_account: broken data [conf/GM_account.txt] line %d\n",c); + } + else { + if(p->level > 99) + p->level = 99; + numdb_insert(gm_account_db,p->account_id,p); + } + c++; + } + fclose(fp); + printf("gm_account: read done (%d gm account ID)\n",c); + return 0; +} + +int mmo_auth_init(void) +{ + MYSQL mysql_handle; + char tmpsql[1024]; + MYSQL_RES* sql_res ; + MYSQL_ROW sql_row ; + + mysql_init(&mysql_handle); + if(!mysql_real_connect(&mysql_handle, db_server_ip, db_server_id, db_server_pw, + db_server_logindb ,db_server_port, (char *)NULL, 0)) { + //pointer check + printf("%s\n",mysql_error(&mysql_handle)); + exit(1); + } + else { + printf ("connect success!\n"); + } + printf ("convert start...\n"); + + FILE *fp; + int account_id, logincount, user_level, state, n, i; + char line[2048], userid[2048], pass[2048], lastlogin[2048], sex, email[2048], error_message[2048], last_ip[2048], memo[2048]; + time_t ban_until_time; + time_t connect_until_time; + char t_uid[256]; + + fp=fopen("save/account.txt","r"); + auth_dat=malloc(sizeof(auth_dat[0])*256); + auth_max=256; + if(fp==NULL) + return 0; + while(fgets(line,1023,fp)!=NULL){ + + if(line[0]=='/' && line[1]=='/') + continue; + + i = sscanf(line, "%d\t%[^\t]\t%[^\t]\t%[^\t]\t%c\t%d\t%d\t" + "%[^\t]\t%[^\t]\t%ld\t%[^\t]\t%[^\t]\t%ld%n", + &account_id, userid, pass, lastlogin, &sex, &logincount, &state, + email, error_message, &connect_until_time, last_ip, memo, &ban_until_time, &n); + + sprintf(tmpsql, "SELECT `%s`,`%s`,`%s`,`lastlogin`,`logincount`,`sex`,`connect_until`,`last_ip`,`ban_until`,`state`" + " FROM `%s` WHERE `%s`='%s'", login_account_id, login_userid, login_user_pass, login_db, login_userid, t_uid); + + if(mysql_query(&mysql_handle, tmpsql) ) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle) ); + } + user_level = isGM(account_id); + printf ("userlevel: %s (%d)- %d\n",userid, account_id, user_level); + sql_res = mysql_store_result(&mysql_handle) ; + sql_row = mysql_fetch_row(sql_res); //row fetching + if (!sql_row) //no row -> insert + sprintf(tmpsql, "INSERT INTO `login` (`account_id`, `userid`, `user_pass`, `lastlogin`, `sex`, `logincount`, `email`, `level`) VALUES (%d, '%s', '%s', '%s', '%c', %d, 'user@athena', %d);",account_id , userid, pass,lastlogin,sex,logincount, user_level); + else //row reside -> updating + sprintf(tmpsql, "UPDATE `login` SET `account_id`='%d', `userid`='%s', `user_pass`='%s', `lastlogin`='%s', `sex`='%c', `logincount`='%d', `email`='user@athena', `level`='%d'\nWHERE `account_id`='%d';",account_id , userid, pass,lastlogin,sex,logincount, user_level, account_id); + printf ("Query: %s\n",tmpsql); + mysql_free_result(sql_res) ; //resource free + if(mysql_query(&mysql_handle, tmpsql) ) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle) ); + } + } + fclose(fp); + + printf ("convert end...\n"); + + return 0; +} + +// アカウントデ??ベ?スの書き込み +void nowork(void) +{ + //null +} + +int login_config_read(const char *cfgName){ + int i; + char line[1024], w1[1024], w2[1024]; + FILE *fp; + + fp=fopen(cfgName,"r"); + + if(fp==NULL){ + printf("file not found: %s\n", cfgName); + return 1; + } + printf ("start reading configuration...\n"); + while(fgets(line, 1020, fp)){ + if(line[0] == '/' && line[1] == '/') + continue; + + i=sscanf(line,"%[^:]:%s", w1, w2); + if(i!=2) + continue; + + + //add for DB connection + if(strcmpi(w1,"db_server_ip")==0){ + strcpy(db_server_ip, w2); + printf ("set db_server_ip : %s\n",w2); + } + else if(strcmpi(w1,"db_server_port")==0){ + db_server_port=atoi(w2); + printf ("set db_server_port : %s\n",w2); + } + else if(strcmpi(w1,"db_server_id")==0){ + strcpy(db_server_id, w2); + printf ("set db_server_id : %s\n",w2); + } + else if(strcmpi(w1,"db_server_pw")==0){ + strcpy(db_server_pw, w2); + printf ("set db_server_pw : %s\n",w2); + } + else if(strcmpi(w1,"db_server_logindb")==0){ + strcpy(db_server_logindb, w2); + printf ("set db_server_logindb : %s\n",w2); + } + } + fclose(fp); + printf ("End reading configuration...\n"); + return 0; +} + +int do_init(int argc,char **argv) +{ + char input; + login_config_read( (argc>1)?argv[1]:LOGIN_CONF_NAME ); + read_gm_account(); + + printf("\nWarning : Make sure you backup your databases before continuing!\n"); + printf("\nDo you wish to convert your Login Database to SQL? (y/n) : "); + input=getchar(); + if(input == 'y' || input == 'Y') + mmo_auth_init(); + + exit(0); +} + + diff --git a/misc/src/webserver/Makefile b/misc/src/webserver/Makefile new file mode 100644 index 0000000..f664a7d --- /dev/null +++ b/misc/src/webserver/Makefile @@ -0,0 +1,26 @@ +cc = gcc
+
+all:
+ #Generate framework...
+ $(cc) -c parse.c
+ $(cc) -c generate.c
+ $(cc) -c htmlstyle.c
+ $(cc) -c logs.c
+
+ #Generate "pages"...
+ cd pages && $(cc) -c about.c && cd ..
+ cd pages && $(cc) -c sample.c && cd ..
+ cd pages && $(cc) -c notdone.c && cd ..
+
+ #Building the server...
+ $(cc) -o webserver main.c parse.o generate.o htmlstyle.o \
+ logs.o pages/about.o pages/sample.o pages/notdone.o
+
+
+
+
+clean:
+ rm -f *.o
+ rm -f pages/*.o
+ rm -f webserver
+
diff --git a/misc/src/webserver/doc/API.txt b/misc/src/webserver/doc/API.txt new file mode 100644 index 0000000..c80f7bd --- /dev/null +++ b/misc/src/webserver/doc/API.txt @@ -0,0 +1,50 @@ +Here's the webserver API, so you can work on the webserver. + +My personal goal is to make this interface simple, so that coding it +will be like coding in some scripting language... + + + +char *get_param(char in_string[500], char swhat[500]); + +This function simply returns various data from the query string. + *Pass get_param NOTHING longer than 500 in length! + + What do I pass where in_string is? + The query string. + + What do I pass where swhat is? + One of two things... + Either 0 for the path of the 'page' + or you can pass it the param you wish to lookup. + + + + + + +char *get_query(char *inquery); + +This function simply returns a query string from the raw server request. +This is used once in main, I doubt you'll need it. + + + + + +void web_send(int sockin, char *in_data); + +Super easy way of sending data to a webpage! +Simply put in the socket name and then the data. + + Ex: + web_send(socket, "I like cheese!\n"); + + + + +char *html_header(char* title); +Easy way to print the eAthena header for the server. + + Ex: + web_send(sockethere, html_header("About")); diff --git a/misc/src/webserver/doc/README b/misc/src/webserver/doc/README new file mode 100644 index 0000000..edcabf1 --- /dev/null +++ b/misc/src/webserver/doc/README @@ -0,0 +1,11 @@ +This readme is intended for the programmers of eAthena.
+
+This webserver's apis are in API.txt.
+
+To make this simple, generate.c should handle most of the work this sever does
+in terms of what people see.
+
+When a request is made the server shoots it off to generate.c.
+
+You are welcome to create more functions used by generate.c to generate pages
+though, so don't feel limited by that one file.
diff --git a/misc/src/webserver/generate.c b/misc/src/webserver/generate.c new file mode 100644 index 0000000..ad050db --- /dev/null +++ b/misc/src/webserver/generate.c @@ -0,0 +1,38 @@ + +void generate_page(char password[25], int sock_in, char *query, char *ip) +{ + char *page = get_param(query, 0); + char *ppass = get_param(query, "password"); + + + if ( (ppass == 0) || (strcmp(password, ppass) != 0) ) + { + web_send(sock_in, html_header("Enter your password")); + web_send(sock_in, "<H1>NOT LOGGED IN!</H1><form action=\"/\" method=\"GET\">\n"); + web_send(sock_in, "Enter your password:<br>\n<input type=\"text\" name=\"password\">\n"); + web_send(sock_in, "<input type=\"submit\" value=\"Login\">\n"); + } + else + { + + + //To make this simple, we will have a bunch of if statements + //that then shoot out data off into functions. + + + //The 'index' + if ( strcmp(page, "/") == 0 ) + generate_notdone(sock_in, query, ip); + + + //About page: + if ( strcmp(page, "/about.html") == 0 ) + generate_about(sock_in, query, ip); + + + //Test page: + if ( strcmp(page, "/testing/") == 0 ) + generate_sample(sock_in, query, ip); + + } +} diff --git a/misc/src/webserver/htmlstyle.c b/misc/src/webserver/htmlstyle.c new file mode 100644 index 0000000..c3a4b92 --- /dev/null +++ b/misc/src/webserver/htmlstyle.c @@ -0,0 +1,51 @@ +char output[10000]; + +char *html_header(char *title) +{ + memset(output, 0x0, 10000); + char *text = "<body text=\"#000000\" bgcolor=\"#939393\" link=\"#0033FF\">\n" + "<br><table width=\"92%\" cellspacing=\"1\" cellpadding=\"0\" border=\"0\"\n" + "align=\"center\" class=\"bordercolor\"><tbody><tr><td class=\"bordercolor\" width=\"100%\">\n" + "<table bgcolor=\"#ffffff\" width=\"100%\" cellspacing=\"0\" cellpadding=\"0\">\n" + "<tbody><tr><td><table border=\"0\" width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" bgcolor=\"#ffffff\">\n" + "<tbody><tr><img src=\"http://eathena.sourceforge.net/athena.jpg\" alt=\"Athena\">\n" + "<td bgcolor=\"#ffffff\"></td></tr></tbody></table></td></tr></tbody></table>\n" + "</td></tr><tr align=\"left\"><td class=\"bordercolor\"><table bgcolor=\"#c6c6c6\" width=\"100%\" cellspacing=\"0\"\n" + "cellpadding=\"0\" style=\"text-align: left; margin-right: auto; margin-left: 0px;\">\n"; + "<tbody><tr><td width=\"100%\" align=\"center\"><table border=\"0\" width=\"100%\" cellpadding=\"3\"\n" + "cellspacing=\"0\" bgcolor=\"#c6c6c6\" align=\"center\"><tbody><tr>" + "<td valign=\"middle\" bgcolor=\"#c6c6c6\" align=\"center\"><a href=\"/cgi-bin/forum/YaBB.cgi\">" + "<span style=\"text-decoration: underline;\"><span style=\"font-weight: bold;\">\n" + "To the Forum</span></span></a><br></td></tr></tbody></table></td></tr></tbody>\n" + "</table></td></tr><tr><td class=\"bordercolor\" align=\"center\">\n" + "<table bgcolor=\"#ffffff\" width=\"100%\" cellspacing=\"0\" cellpadding=\"0\" align=\"center\">\n" + "<tbody><tr><td width=\"100%\" align=\"center\"><table border=\"0\" width=\"100%\" cellpadding=\"5\"\n" + "cellspacing=\"0\" bgcolor=\"#ffffff\" align=\"center\"><tbody><tr>\n" + "<td valign=\"middle\" bgcolor=\"#ffffff\" align=\"center\"><font size=\"2\" color=\"#6e94b7\">\n" + "<b>Athena</b> « Portal »</font></td></tr></tbody></table></td></tr></tbody>" + "</table></td></tr></tbody></table>\n"; + + sprintf(output, "<title>%s</title>\n%s\n", title, text); + + return output; +} + + + +char *html_start_form(char *location, char *action) +{ + memset(output, 0x0, 10000); + sprintf(output, "<form action=\"%s\" method=\"%s\">", location, action); + return output; + + +} + + +char *html_end_forum(void) +{ + return "</form>"; +} + + + diff --git a/misc/src/webserver/logs.c b/misc/src/webserver/logs.c new file mode 100644 index 0000000..405b488 --- /dev/null +++ b/misc/src/webserver/logs.c @@ -0,0 +1,8 @@ +#include <time.h> + +void log_visit(char *query, char *ip) +{ + time_t timer; + timer=time(NULL); + printf("%s - \"%s\" - %s", ip, query, asctime(localtime(&timer))); +} diff --git a/misc/src/webserver/main.c b/misc/src/webserver/main.c new file mode 100644 index 0000000..5936255 --- /dev/null +++ b/misc/src/webserver/main.c @@ -0,0 +1,142 @@ +/*************************************************************************** + description + ------------------- + author : (C) 2004 by Michael J. Flickinger + email : mjflick@cpan.org + + ***************************************************************************/ + +/*************************************************************************** + * * + * 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 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> +#include <string.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <sys/wait.h> +#include <signal.h> + +#define BLOG 10 + +char *header = "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\n"; +char recvin[500], password[25]; +int s_port; + +void sigchld_handler(int s) +{ + while(wait(NULL) > 0); +} + +int main(int argc, char **argv) +{ + if (argc < 3) + { + printf("eAthena Web Server\n"); + printf("usage: %s [password] [port]\n", argv[0]); + exit(0); + } + + s_port = atoi(argv[2]); + + if ((s_port < 1) || (s_port > 65534)) + { + printf("Error: The port you choose is not valid port.\n"); + exit(0); + } + + if (strlen(argv[1]) > 25) + { + printf("Error: Your password is too long.\n"); + printf("It must be shorter than 25 characters.\n"); + exit(0); + } + + memset(password, 0x0, 25); + memcpy(password, argv[1], strlen(argv[1])); + + int sockfd, new_fd; + struct sockaddr_in my_addr; + struct sockaddr_in their_addr; + int sin_size; + + struct sigaction sa; + + int yes=1; + + if ((sockfd = socket(AF_INET, SOCK_STREAM,0)) == -1) + { + perror("Darn, this is broken."); + exit(0); + } + + if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1) + { + perror("Error... :-("); + } + + //Now we know we have a working socket. :-) + + my_addr.sin_family = AF_INET; + my_addr.sin_port = htons(s_port); + my_addr.sin_addr.s_addr = INADDR_ANY; + memset(&(my_addr.sin_zero), '\0', 8); + + if ( bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr)) == -1) + { + perror("can not bind to this port"); + exit(0); + } + + if ( listen(sockfd, BLOG) == -1) + { + perror("can not listen on port"); + exit(0); + } + + sa.sa_handler = sigchld_handler; + + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_RESTART; + + if (sigaction(SIGCHLD, &sa, NULL) == -1) + { + perror("sigaction sucks"); + exit(0); + } + + printf("The eAthena webserver is up and listening on port %i.\n", s_port); + + while(1) + { + sin_size = sizeof(struct sockaddr_in); + new_fd = accept(sockfd, (struct sockaddr *)&their_addr, &sin_size); + + if (!fork()) + { + close(sockfd); + memset(recvin, 0x0, 500); + recv(new_fd, recvin, 500, 0); + send(new_fd, header, strlen(header), 0); + generate_page(password, new_fd, get_query(recvin), inet_ntoa(their_addr.sin_addr)); + log_visit(get_query(recvin), inet_ntoa(their_addr.sin_addr)); + + close(new_fd); + exit(0); + } + close(new_fd); + } + + return 0; +} diff --git a/misc/src/webserver/pages/about.c b/misc/src/webserver/pages/about.c new file mode 100644 index 0000000..2b0002a --- /dev/null +++ b/misc/src/webserver/pages/about.c @@ -0,0 +1,6 @@ +void generate_about(int sock_in, char *query, char *ip) +{ +//printf("%s", html_header("About")); + web_send(sock_in, html_header("About")); + web_send(sock_in, "<center>eAthena Web Server!</center>\n"); +} diff --git a/misc/src/webserver/pages/notdone.c b/misc/src/webserver/pages/notdone.c new file mode 100644 index 0000000..a6492e3 --- /dev/null +++ b/misc/src/webserver/pages/notdone.c @@ -0,0 +1,5 @@ +void generate_notdone(int sock_in, char *query, char *ip) +{ + web_send(sock_in, "<title>Not here!</title>\n"); + web_send(sock_in, "<h2><center>This page/feature is not done yet.</center>\n</h2>"); +} diff --git a/misc/src/webserver/pages/sample.c b/misc/src/webserver/pages/sample.c new file mode 100644 index 0000000..be900a1 --- /dev/null +++ b/misc/src/webserver/pages/sample.c @@ -0,0 +1,24 @@ + + +void generate_sample(int sock_in, char *query, char *ip) +{ + + char *name = get_param(query, "name"); + + web_send(sock_in, "<title>SAMPLE</title>\n"); + + + //If a name was not entered... + if ( name == '\0' ) + { + web_send(sock_in, "<form action=\"/testing/\" method=\"GET\">\n"); + web_send(sock_in, "<input type=\"text\" name=\"name\">\n"); + web_send(sock_in, "<input type=\"submit\">\n"); + } + else + { + web_send(sock_in, "Your name is: "); + web_send(sock_in, get_param(query, "name")); + } +printf("OK!\n"); +} diff --git a/misc/src/webserver/parse.c b/misc/src/webserver/parse.c new file mode 100644 index 0000000..8e54a81 --- /dev/null +++ b/misc/src/webserver/parse.c @@ -0,0 +1,135 @@ +#include <stdlib.h> + +char filtered_query[2000]; +char rdata[500]; +char param_n[500]; +char param_d[500]; + + +char *get_query(char *inquery) +{ + memset(filtered_query, 0x0, 2000); + sscanf(inquery, "GET %s %[$]", filtered_query); + return(filtered_query); +} + +void web_send(int sockin, char *in_data) +{ + send(sockin, in_data, strlen(in_data), 0); +} + + +//THIS IS BAD CODE BE CAREFULL WITH IT! +//Watch out for buffer overflow... +//When using please make sure to check the string size. + +//Also note: +//I take no pride in this code, it is a really bad way of doing this... +char *get_param(char in_string[500], char swhat[500]) +{ + int i = 0; + int marker, iswitch, pint, dint; + char flux[500]; + memset(flux, 0x0, 500); + + //Get the path of out "page" + if (swhat == 0) + { + //while i is not equal to array size + while (i != 500) + { + //if there is a question mark, halt! + if (in_string[i] == '?') + { + i = 499; + } + else + rdata[i] = in_string[i]; + + i++; + } + return rdata; + } + else //so, we want a param... + { + //calculate where param begins + while (i != 500) + { + if (in_string[i] == '?') + { + marker = i + 1; + i = 499; + } + i++; + } + + i = 0; + + //keep morons from trying to crash this + if ((marker > 500)||(marker < 1)) + marker = 500; + + while(marker != 500) + { + if ((in_string[marker] != '&') && (in_string[marker] != '\0')) + { + flux[i] = in_string[marker]; + i++; + } + else + { + + //we have a param, now we must dig through it + + //clear temp vars + memset(param_n, 0x0, 500); + memset(param_d, 0x0, 500); + iswitch = 0; + pint = 0; + dint = 0; + i = 0; + + //split result into param_n and param_d + while(i != 500) + { + if ( (flux[i] != '=') && (flux[i] != '\0') ) + { + if (iswitch == 0) + { + param_n[pint] = flux[i]; + pint++; + } + else + { + param_d[dint] = flux[i]; + dint++; + } + } + else + { + iswitch = 1; + } + if (flux[i] == '\0') + i = 499; + + i++; + } + + if ( strcmp(param_n, swhat) == 0 ) + { + return param_d; + } + + i = 0; + } + + if (in_string[marker] == '\0') + { + marker = 499; + } + marker++; + } + return 0; + } +} + diff --git a/misc/src/xand1_1.patch b/misc/src/xand1_1.patch new file mode 100644 index 0000000..5715664 --- /dev/null +++ b/misc/src/xand1_1.patch @@ -0,0 +1,132 @@ +--- npc/tulimshar/monster_guide.txt 2005-08-05 23:03:44.144096000 +0200 ++++ npc/tulimshar/monster_guide.txt.new 2005-08-08 14:03:51.154608000 +0200 +@@ -1,91 +1,3 @@ +-new_3-1.gat,53,185,0 script ConquestMob0 -1,{ +-OnInit: +-// all monsters ingame by 31.Jul 2005 sorted by map, monsterID +-areamonster "new_1-1.gat",15,17,105,103,"RedScorpion",1004, 1,"ConquestMob-new_1-1::OnGuardianDied1004"; +-areamonster "new_1-1.gat",15,17,105,103,"GreenSlime",1005, 50,"ConquestMob-new_1-1::OnGuardianDied1005"; +-areamonster "new_1-1.gat",15,17,105,103,"GiantMaggot",1006, 30,"ConquestMob-new_1-1::OnGuardianDied1006"; +-areamonster "new_2-1.gat",31,31,90,97,"RedSlime",1008, 24,"ConquestMob-new_2-1::OnGuardianDied1008"; +-areamonster "new_2-1.gat",53,34,96,36,"RedSlime",1008, 6,"ConquestMob-new_2-1::OnGuardianDied1008b"; +-areamonster "new_2-1.gat",31,31,90,97,"BlackScorpion",1009, 15,"ConquestMob-new_2-1::OnGuardianDied1009"; +-areamonster "new_2-1.gat",84,52,93,91,"BlackScorpion",1009, 5,"ConquestMob-new_2-1::OnGuardianDied1009a"; +-areamonster "new_3-1.gat",22,42,142,79,"Maggot",1002, 35,"ConquestMob-new_3-1::OnGuardianDied1002"; +-areamonster "new_3-1.gat",22,42,142,79,"Scorpion",1003, 10,"ConquestMob-new_3-1::OnGuardianDied1003"; +-areamonster "new_5-1.gat",32,32,90,100,"YellowSlime",1007, 20,"ConquestMob-new_5-1::OnGuardianDied1007"; +-areamonster "new_5-1.gat",88,33,98,42,"RedSlime",1008, 3,"ConquestMob-new_5-1::OnGuardianDied1008a"; +-areamonster "new_5-1.gat",32,32,90,100,"Spider",1012, 8,"ConquestMob-new_5-1::OnGuardianDied1012"; +-areamonster "new_5-1.gat",81,32,85,38,"Spider",1012, 2,"ConquestMob-new_5-1::OnGuardianDied1012a"; +-areamonster "new_7-1.gat",22,27,176,174,"Snake",1010, 15,"ConquestMob-new_7-1::OnGuardianDied1010"; +-break; +-} +-new_1-1.gat,53,185,0 script ConquestMob-new_1-1 -1,{ +-// event when mob dies +-OnGuardianDied1004: +- if (MPQUEST == 1) set Mobpt,Mobpt+42; +- areamonster "new_1-1.gat",15,17,105,103,"RedScorpion",1004, 1,"ConquestMob-new_1-1::OnGuardianDied1004"; +- break; +-OnGuardianDied1005: +- if (MPQUEST == 1) set Mobpt,Mobpt+5; +- areamonster "new_1-1.gat",15,17,105,103,"GreenSlime",1005, 1,"ConquestMob-new_1-1::OnGuardianDied1005"; +- break; +-OnGuardianDied1006: +- if (MPQUEST == 1) set Mobpt,Mobpt+14; +- areamonster "new_1-1.gat",15,17,105,103,"GiantMaggot",1006, 1,"ConquestMob-new_1-1::OnGuardianDied1006"; +- break; +-} +-new_2-1.gat,53,185,0 script ConquestMob-new_2-1 -1,{ +-OnGuardianDied1008: +- if (MPQUEST == 1) set Mobpt,Mobpt+18; +- areamonster "new_2-1.gat",31,31,90,97,"RedSlime",1008, 1,"ConquestMob-new_2-1::OnGuardianDied1008"; +- break; +-OnGuardianDied1008b: +- if (MPQUEST == 1) set Mobpt,Mobpt+18; +- areamonster "new_2-1.gat",53,34,96,36,"RedSlime",1008, 1,"ConquestMob-new_2-1::OnGuardianDied1008b"; +- break; +-OnGuardianDied1009: +- if (MPQUEST == 1) set Mobpt,Mobpt+45; +- areamonster "new_2-1.gat",31,31,90,97,"BlackScorpion",1009, 1,"ConquestMob-new_2-1::OnGuardianDied1009"; +- break; +-OnGuardianDied1009a: +- if (MPQUEST == 1) set Mobpt,Mobpt+45; +- areamonster "new_2-1.gat",84,52,93,91,"BlackScorpion",1009, 1,"ConquestMob-new_2-1::OnGuardianDied1009a"; +- break; +-} +-new_3-1.gat,53,185,0 script ConquestMob-new_3-1 -1,{ +-OnGuardianDied1002: +- if (MPQUEST == 1) set Mobpt,Mobpt+1; +- areamonster "new_3-1.gat",22,42,142,79,"Maggot",1002, 1,"ConquestMob-new_3-1::OnGuardianDied1002"; +- break; +-OnGuardianDied1003: +- if (MPQUEST == 1) set Mobpt,Mobpt+2; +- areamonster "new_3-1.gat",22,42,142,79,"Scorpion",1003, 1,"ConquestMob-new_3-1::OnGuardianDied1003"; +- break; +-} +-new_5-1.gat,53,185,0 script ConquestMob-new_5-1 -1,{ +-OnGuardianDied1007: +- if (MPQUEST == 1) set Mobpt,Mobpt+9; +- areamonster "new_5-1.gat",32,32,90,100,"YellowSlime",1007, 1,"ConquestMob-new_5-1::OnGuardianDied1007"; +- break; +-// 3 Red Slimes guard treasure +-OnGuardianDied1008a: +- if (MPQUEST == 1) set Mobpt,Mobpt+18; +- areamonster "new_5-1.gat",88,33,98,42,"RedSlime",1008, 1,"ConquestMob-new_5-1::OnGuardianDied1008a"; +- break; +-OnGuardianDied1012: +- if (MPQUEST == 1) set Mobpt,Mobpt+56; +- areamonster "new_5-1.gat",32,32,90,100,"Spider",1012, 1,"ConquestMob-new_5-1::OnGuardianDied1012"; +- break; +-// 2 spiders guard entrance to treasure +-OnGuardianDied1012a: +- if (MPQUEST == 1) set Mobpt,Mobpt+56; +- areamonster "new_5-1.gat",81,32,85,38,"Spider",1012, 1,"ConquestMob-new_5-1::OnGuardianDied1012a"; +- break; +-} +-new_7-1.gat,53,185,0 script ConquestMob-new_7-1 -1,{ +-OnGuardianDied1010: +- if (MPQUEST == 1) set Mobpt,Mobpt+51; +- areamonster "new_7-1.gat",22,27,176,174,"Snake",1010, 1,"ConquestMob-new_7-1::OnGuardianDied1010"; +- break; +-} + new_3-1.gat,46,66,0 script MonsterGuide 102,{ + if(MPQUEST == 0) goto Register; + mes "[Monster Guide]"; +--- conf/map_athena.conf 2005-07-18 14:57:41.000000000 +0200 ++++ conf/map_athena.conf.new 2005-08-08 14:03:04.888080000 +0200 +@@ -57,7 +57,6 @@ + + // NPCs + +-npc: npc/tulimshar/monsters.txt + npc: npc/tulimshar/barber.txt + npc: npc/tulimshar/monster_guide.txt + npc: npc/tulimshar/ptsrewards.txt +@@ -68,10 +67,15 @@ + npc: npc/tulimshar/warps.txt + npc: npc/tulimshar/shop.txt + ++npc: npc/monsters/monsters-new_1-1.txt ++npc: npc/monsters/monsters-new_2-1.txt ++npc: npc/monsters/monsters-new_3-1.txt ++npc: npc/monsters/monsters-new_5-1.txt ++npc: npc/monsters/monsters-new_7-1.txt ++ + npc: npc/tonori/cave.txt + npc: npc/tonori/miners.txt + npc: npc/tonori/warps.txt +-npc: npc/tonori/monsters.txt + + npc: npc/guide.txt + npc: npc/nekkio.txt +--- db/mob_db.txt 2005-07-15 10:25:21.000000000 +0200 ++++ db/mob_db.txt.new 2005-08-08 14:23:23.680617600 +0200 +@@ -10,6 +10,7 @@ + 1007,Yellow_slime,Yellow Slime,1,400,0,20,2,1,20,25,2,7,10,8,2,1,34,1,1,1,1,0,20,131,1400,1800,672,480,534,200,519,100,501,350,502,250,522,10,909,0,909,0,0,0,0,0,0,0,,,,,, + 1008,Red_slime,Red Slime,1,400,0,65,56,1,30,50,2,7,15,10,2,1,25,1,1,1,1,0,20,135,1300,1500,672,480,1201,300,509,110,521,200,523,40,525,80,535,750,528,250,531,150,0,0,0,0,,,,,, + 1009,BlackScorpion,Black Scorpion,1,1100,0,200,70,1,80,90,4,6,20,20,10,10,35,10,1,1,1,0,20,133,1000,1500,672,480,523,150,509,100,518,800,909,0,909,0,909,0,909,0,0,0,0,0,0,0,,,,,, +-1010,Snake,Snake,1,2000,0,172,70,1,150,80,4,6,20,20,10,10,35,10,2,1,1,0,20,133,1000,1500,672,480,0,0,0,0,0,0,909,0,909,0,909,0,909,0,0,0,0,0,0,0,,,,,, ++1010,Snake,Snake,1,2200,0,250,120,1,150,80,4,6,15,25,10,10,45,5,2,1,1,0,20,133,1000,1500,672,480,524,50,527,500,0,0,909,0,909,0,909,0,909,0,0,0,0,0,0,0,,,,,, + 1011,Fire_Goblin,Fire Goblin,1,50,0,3,2,1,7,10,0,5,1,1,1,0,6,30,1,1,1,3,21,129,800,1872,672,480,505,800,501,150,518,800,502,150,521,70,522,1,909,0,0,0,0,0,0,0,,,,,, + 1012,Spider,Spider,1,2000,0,230,90,1,150,80,4,6,20,20,10,10,35,10,2,1,1,0,25,175,1000,1500,672,480,537,500,535,100,0,0,909,0,909,0,909,0,909,0,0,0,0,0,0,0,,,,,, ++1013,SpiderRage,Raging Spider,1,3000,0,450,170,1,230,150,5,2,35,30,10,2,40,10,2,1,1,0,25,175,700,1500,672,480,537,1000,535,5000,0,0,909,0,909,0,909,0,909,0,0,0,0,0,0,0,,,,,, diff --git a/npc/cave-small/hermit.txt b/npc/cave-small/hermit.txt new file mode 100644 index 0000000..671c2ee --- /dev/null +++ b/npc/cave-small/hermit.txt @@ -0,0 +1,14 @@ +new_4-1.gat,30,30,0 script Hermit 116,{ + mes "[Hermit]"; + mes "Do you want to go back outside?"; + next; + menu + "Yes please",yes, + "Not yet",notyet; +yes: + warp "new_9-1.gat",60,95; + close; +notyet: + close; + +}
\ No newline at end of file diff --git a/npc/cave-small/monsters.txt b/npc/cave-small/monsters.txt new file mode 100644 index 0000000..feeb5ea --- /dev/null +++ b/npc/cave-small/monsters.txt @@ -0,0 +1 @@ +new_4-1.gat,0,0,0,0 monster Bat 1017,20,0,0,0
\ No newline at end of file diff --git a/npc/cave1/miners.txt b/npc/cave1/miners.txt new file mode 100644 index 0000000..5dec880 --- /dev/null +++ b/npc/cave1/miners.txt @@ -0,0 +1,41 @@ +new_2-1.gat,35,35,6 script Miner 109,{
+ mes "[Miner]";
+ mes "It's very dangerous in here";
+ mes "Be careful";
+ close;
+}
+
+new_1-1.gat,19,99,0 shop CaveShop 109,525:800,531:3000,530:8000,1199:2
+
+new_5-1.gat,32,94,6 script Lift 109,{
+ mes "[Miner]";
+ mes "Do you want me to lift you to the upper level?";
+ next;
+ menu
+ "Sure",sure,
+ "Not yet!",notyet;
+
+sure:
+ warp "new_2-1.gat",87,99;
+ close;
+
+notyet:
+ close;
+}
+
+new_2-1.gat,85,97,6 script LiftDown 109,{
+ mes "[Miner]";
+ mes "We discovered an underground palace";
+ mes "Do you want me to let you in?";
+ next;
+ menu
+ "Sure",sure,
+ "Not yet!",notyet;
+
+sure:
+ warp "new_5-1.gat",34,92;
+ close;
+
+notyet:
+ close;
+}
diff --git a/npc/cave1/monsters.txt b/npc/cave1/monsters.txt new file mode 100644 index 0000000..302bfb1 --- /dev/null +++ b/npc/cave1/monsters.txt @@ -0,0 +1,26 @@ +// Map: new_2-1 +// This is the cave below Tulimshar entrance -- lv1 +// +// ALWAYS keep a backup file before edit (experience :) +// The last param is the Event on Mobdeath +// + +new_2-1.gat,0,0,0,0 monster RedSlime 1008,30,2500,0,ConquestMob2::OnRedSlime +new_2-1.gat,0,0,0,0 monster ScorpionB 1009,15,2500,0,ConquestMob2::OnScorpB + + +new_2-1.gat,0,0,0 script ConquestMob2 -1,{ +// id 1008 = Red Slime +OnRedSlime: + if (MPQUEST == 1) set Mobpt,Mobpt+18; + break; + +// id 1009 == Black Scorpion +OnScorpB: + if (MPQUEST == 1) set Mobpt,Mobpt+45; + break; +end; +} + + + diff --git a/npc/cave1/passages.txt b/npc/cave1/passages.txt new file mode 100644 index 0000000..ec363db --- /dev/null +++ b/npc/cave1/passages.txt @@ -0,0 +1,4 @@ +new_2-1.gat,36,30 warp caveexit 3,0,new_1-1.gat,25,95 +new_1-1.gat,19,94 warp caveentrance 1,1,new_2-1.gat,37,31 +new_1-1.gat,111,79 warp eastdesert 2,2,new_7-1.gat,23,40 +new_7-1.gat,18,39 warp westdesert 3,4,new_1-1.gat,106,78
\ No newline at end of file diff --git a/npc/cave2/chest.txt b/npc/cave2/chest.txt new file mode 100644 index 0000000..4da805f --- /dev/null +++ b/npc/cave2/chest.txt @@ -0,0 +1,28 @@ +new_5-1.gat,93,37,0 script Treasure 111,{ + if( ChestQuest == 1) goto done; + mes "[Chest]"; + mes "Would you try to open it?"; + next; + menu "Yup",L_1,"Nope",L_2; + +L_1: + if(countitem(537)<3)goto L_3; + delitem 537,3; + getitem 536,1; + mes "[Chest]"; + mes "You opened it and found a short sword!"; + set ChestQuest,1; + close; + +L_2: + close; + +L_3: + mes "It seems that this is not the right key..."; + close; + done: + mes "[Chest]"; + mes "You already opened this chest."; + close; + +} diff --git a/npc/cave2/monsters.txt b/npc/cave2/monsters.txt new file mode 100644 index 0000000..c1d5e28 --- /dev/null +++ b/npc/cave2/monsters.txt @@ -0,0 +1,25 @@ +// Map: new_5-1 +// This is the cave below Tulimshar second level aka "Underground Palace" +// +// ALWAYS keep a backup file before edit (experience :) +// The last param is the Event on Mobdeath +// + +new_5-1.gat,0,0,0,0 monster Spider 1012,10,2500,0,ConquestMob5::OnSpider +new_5-1.gat,0,0,0,0 monster SlimeY 1007,25,2500,0,ConquestMob5::OnSlimeY + +new_5-1.gat,0,0,0 script ConquestMob5 -1,{ +// id 1012 = Spider +OnSpider: + if (MPQUEST == 1) set Mobpt,Mobpt+56; + break; + +// id 1007 == Yellow Slime +OnSlimeY: + if (MPQUEST == 1) set Mobpt,Mobpt+9; + break; +end; +} + + + diff --git a/npc/cave2/passages.txt b/npc/cave2/passages.txt new file mode 100644 index 0000000..ec363db --- /dev/null +++ b/npc/cave2/passages.txt @@ -0,0 +1,4 @@ +new_2-1.gat,36,30 warp caveexit 3,0,new_1-1.gat,25,95 +new_1-1.gat,19,94 warp caveentrance 1,1,new_2-1.gat,37,31 +new_1-1.gat,111,79 warp eastdesert 2,2,new_7-1.gat,23,40 +new_7-1.gat,18,39 warp westdesert 3,4,new_1-1.gat,106,78
\ No newline at end of file diff --git a/npc/eastern-desert/monsters.txt b/npc/eastern-desert/monsters.txt new file mode 100644 index 0000000..54999fd --- /dev/null +++ b/npc/eastern-desert/monsters.txt @@ -0,0 +1,2 @@ +new_7-1.gat,0,0,0,0 monster Maggot 1002,20,0,0,0 +new_7-1.gat,0,0,0,0 monster Snake 1010,20,0,0,0
\ No newline at end of file diff --git a/npc/eastern-desert/passages.txt b/npc/eastern-desert/passages.txt new file mode 100644 index 0000000..ec363db --- /dev/null +++ b/npc/eastern-desert/passages.txt @@ -0,0 +1,4 @@ +new_2-1.gat,36,30 warp caveexit 3,0,new_1-1.gat,25,95 +new_1-1.gat,19,94 warp caveentrance 1,1,new_2-1.gat,37,31 +new_1-1.gat,111,79 warp eastdesert 2,2,new_7-1.gat,23,40 +new_7-1.gat,18,39 warp westdesert 3,4,new_1-1.gat,106,78
\ No newline at end of file diff --git a/npc/tulimshar-casino/casino.txt b/npc/tulimshar-casino/casino.txt new file mode 100644 index 0000000..dae9324 --- /dev/null +++ b/npc/tulimshar-casino/casino.txt @@ -0,0 +1,360 @@ +// Warps room 1 +new_8-1.gat,45,24 warp upstairs 0,2,new_8-1.gat,65,25 +new_8-1.gat,25,36 warp outside 0,2,new_3-1.gat,33,73 +new_3-1.gat,32,72 warp inside 1,0,new_8-1.gat,25,34 +// Warps room 2 +new_8-1.gat,65,25 warp downstairs 0,1,new_8-1.gat,42,25 +new_8-1.gat,68,22 warp tocasino 2,0,new_8-1.gat,32,72 +new_8-1.gat,67,31 warp tobedroom1 1,1,new_8-1.gat,102,23 +new_8-1.gat,76,31 warp tobedroom2 1,1,new_8-1.gat,130,23 +// Warps room 3 +new_8-1.gat,102,22 warp bedroom1tohall 2,0,new_8-1.gat,67,29 +// Warps room 4 +new_8-1.gat,130,22 warp bedroom2tohall 2,0,new_8-1.gat,76,29 +// Warps room 5 +new_8-1.gat,32,74 warp tohall 1,0,new_8-1.gat,68,23 + +new_8-1.gat,26,26,0 shop InnKeeper 112,539:175,513:20,519 + +new_8-1.gat,103,26,0 script Waitress 118,{ + mes "[Rebecca]"; + mes "Would you like to rest? It's only 100 gp."; + next; + + menu "Yes",-,"No",L_No; + mes "Sleep well!"; + next; + + if (zeny < 100) goto L_NoMoney; + set zeny,zeny-100; + heal 10000,10000; + close; + +L_No: + mes "See you."; + close; + +L_NoMoney: + mes "Hey! You don't have enough money!"; + close; +} + +new_8-1.gat,134,23,0 script Worker 117,{ + mes "[Valdo]"; + mes "Please let me work, I'm really in a hurry!"; + close; +} + +new_8-1.gat,37,65,0 script Slot1 127,{ + mes "Pull the lever..."; + next; + + if(countitem(503) < 1) goto L_NoCoin; + delitem 503,1; + set @Temp1,rand(7); + set @Temp2,rand(7); + set @Temp3,rand(7); + mes "Numbers: "+ @Temp1 +"/"+ @Temp2 +"/"+ @Temp3 +""; + next; + + if(@Temp1 != @Temp2) goto L_Lost; + if(@Temp2 != @Temp3) goto L_Lost; + if(@Temp1 != @Temp3) goto L_Lost; + mes "Congratulations! You won!"; + mes "You get 10 casino coins"; + getitem 503,10; + close; + +L_Lost: + mes "You lost!"; + close; + +L_NoCoin: + mes "Insert coin"; + close; +} + +new_8-1.gat,39,65,0 script Slot2 127,{ + mes "Pull the lever..."; + next; + + if(countitem(503) < 1) goto L_NoCoin; + delitem 503,1; + set @Temp1,rand(7); + set @Temp2,rand(7); + set @Temp3,rand(7); + mes "Numbers: "+ @Temp1 +"/"+ @Temp2 +"/"+ @Temp3 +""; + next; + + if(@Temp1 != @Temp2) goto L_Lost; + if(@Temp2 != @Temp3) goto L_Lost; + if(@Temp1 != @Temp3) goto L_Lost; + mes "Congratulations! You won!"; + mes "You get 10 casino coins"; + getitem 503,10; + close; + +L_Lost: + mes "You lost!"; + close; + +L_NoCoin: + mes "Insert coin"; + close; +} + +new_8-1.gat,41,65,0 script Slot3 127,{ + mes "Pull the lever..."; + next; + + if(countitem(503) < 1) goto L_NoCoin; + delitem 503,1; + set @Temp1,rand(7); + set @Temp2,rand(7); + set @Temp3,rand(7); + mes "Numbers: "+ @Temp1 +"/"+ @Temp2 +"/"+ @Temp3 +""; + next; + + if(@Temp1 != @Temp2) goto L_Lost; + if(@Temp2 != @Temp3) goto L_Lost; + if(@Temp1 != @Temp3) goto L_Lost; + mes "Congratulations! You won!"; + mes "You get 10 casino coins"; + getitem 503,10; + close; + +L_Lost: + mes "You lost!"; + close; + +L_NoCoin: + mes "Insert coin"; + close; +} + +new_8-1.gat,32,67,0 shop MoneyChanger 124,503:10 + +new_8-1.gat,28,63,0 script BlackJack 107,{ + mes "[Croupier]"; + mes "Would you like to play Black Jack?"; + mes "You will need 15 casino coins"; + next; + + menu "Yes",-,"No",L_No; + if(countitem(503) < 15) goto L_NoCoin; + delitem 503,15; + set @croupier,rand(0, 4); + set @croupier,@croupier + 17; + set @player,rand(4, 20); + mes "You got " + @player + " with your cards."; + mes "Do you want another card?"; + next; + + menu "Yes",L_Another,"No",L_End; + +L_Another: + set @player,@player+rand(2, 11); + if (@player > 21) goto L_Lost; + mes "You got " + @player + " with your cards."; + mes "Do you want another card?"; + next; + + menu "Yes",L_Another,"No",L_End; +L_End: + if (@player < @croupier) goto L_Lost; + mes "Congratulations, you won!"; + mes "I had " + @croupier + ""; + mes "You get 50 casino coins"; + getitem 503,50; + close; + +L_No: + mes "As you wish"; + close; + +L_NoCoin: + mes "You need at least 15 coins"; + close; + +L_Lost: + mes "I'm sorry but you lost"; + mes "You got " + @player + " with your cards."; + mes "I had " + @croupier + ""; + close; +} + +new_8-1.gat,22,69,0 script Roulette 107,{ + mes "Good evening monsieur..."; + mes "How much would you like to bet?"; + next; + + menu "Maybe I'll play later",L_Later,"1 coin",-,"5 coins",L_b5,"10 coins",L_b10,"50 coins",L_b50,"100 coins",L_b100; + set @bet,1; + goto L_Check; +L_b5: + set @bet,5; + goto L_Check; +L_b10: + set @bet,10; + goto L_Check; +L_b50: + set @bet,50; + goto L_Check; +L_b100: + set @bet,100; + goto L_Check; + +L_Check: + if(countitem(503) < @bet) goto L_NoCoin; + delitem 503,@bet; + menu "Choose a color",-,"Choose a number",L_Number; + menu "Black",-,"Red",-; + set @color,rand(2); + if(@color == 1) goto L_Lost; + mes "You won!"; + getitem 503,@bet * 2; + close; + +L_Number: + menu "0",-,"00",L00,"1",L1,"2",L2,"3",L3,"4",L4,"5",L5,"6",L6,"7",L7, + "8",L8,"9",L9,"10",L10,"11",L11,"12",L12,"13",L13,"14",L14,"15",L15, + "16",L16,"17",L17,"18",L18,"19",L19,"20",L20,"21",L21,"22",L22, + "23",L23,"24",L24,"25",L25,"26",L26,"27",L27,"28",L28,"29",L29, + "30",L30,"31",L31,"32",L32,"33",L33,"34",L34,"35",L35,"36",L36; + set @number,0; + goto L_CheckNumber; +L00: + set @number,37; + goto L_CheckNumber; +L1: + set @number,1; + goto L_CheckNumber; +L2: + set @number,2; + goto L_CheckNumber; +L3: + set @number,3; + goto L_CheckNumber; +L4: + set @number,4; + goto L_CheckNumber; +L5: + set @number,5; + goto L_CheckNumber; +L6: + set @number,6; + goto L_CheckNumber; +L7: + set @number,7; + goto L_CheckNumber; +L8: + set @number,8; + goto L_CheckNumber; +L9: + set @number,9; + goto L_CheckNumber; +L10: + set @number,10; + goto L_CheckNumber; +L11: + set @number,11; + goto L_CheckNumber; +L12: + set @number,12; + goto L_CheckNumber; +L13: + set @number,13; + goto L_CheckNumber; +L14: + set @number,14; + goto L_CheckNumber; +L15: + set @number,15; + goto L_CheckNumber; +L16: + set @number,16; + goto L_CheckNumber; +L17: + set @number,17; + goto L_CheckNumber; +L18: + set @number,18; + goto L_CheckNumber; +L19: + set @number,19; + goto L_CheckNumber; +L20: + set @number,20; + goto L_CheckNumber; +L21: + set @number,21; + goto L_CheckNumber; +L22: + set @number,22; + goto L_CheckNumber; +L23: + set @number,23; + goto L_CheckNumber; +L24: + set @number,24; + goto L_CheckNumber; +L25: + set @number,25; + goto L_CheckNumber; +L26: + set @number,26; + goto L_CheckNumber; +L27: + set @number,27; + goto L_CheckNumber; +L28: + set @number,28; + goto L_CheckNumber; +L29: + set @number,29; + goto L_CheckNumber; +L30: + set @number,30; + goto L_CheckNumber; +L31: + set @number,31; + goto L_CheckNumber; +L32: + set @number,32; + goto L_CheckNumber; +L33: + set @number,33; + goto L_CheckNumber; +L34: + set @number,34; + goto L_CheckNumber; +L35: + set @number,35; + goto L_CheckNumber; +L36: + set @number,36; + goto L_CheckNumber; + +L_CheckNumber: + set @roulette,rand(38); + if (@roulette == 37) mes "We got a 00"; + if (@roulette < 37) mes "We got a " + @roulette; + if (@number != @roulette) goto L_Lost; + mes "You won!"; + getitem 503,@bet * 10; + close; + +L_Later: + mes "Rien ne va plus..."; + close; + +L_NoCoin: + mes "You don't have enough coins"; + close; + +L_Lost: + mes "I'm sorry, you lost"; + close; +} + + diff --git a/npc/tulimshar/banker.txt b/npc/tulimshar/banker.txt new file mode 100644 index 0000000..7cd4a2b --- /dev/null +++ b/npc/tulimshar/banker.txt @@ -0,0 +1 @@ +new_3-1.gat,27,73,0 script Banker 107,{mes "[Banker]";mes "Welcome to the bank!";mes "How can I help you?";next;menu "Deposite",L_Dep,"Withdraw",L_With,"Nevermind that",L_Nev;L_Dep: mes "Sorry, but we're still moving inventory!"; close;L_With: mes "Sorry, but we're still moving inventory!"; close;L_Nev: mes "Goodbye then"; close;}
\ No newline at end of file diff --git a/npc/tulimshar/barber.txt b/npc/tulimshar/barber.txt new file mode 100644 index 0000000..35104b2 --- /dev/null +++ b/npc/tulimshar/barber.txt @@ -0,0 +1,89 @@ +new_3-1.gat,102,31,0 script Barber 100,{ + mes "[Barber]"; + mes "I'm the greatest barber in the whole mana world! Would you like a change of style or color?"; + next; + + menu + "Change my style",style, + "Change my color",color, + "Nah I'm fine",fine; + +style: + menu + "Classic",style1, + "Curly",style2, + "Scruffy",style3, + "Slick",style4, + "Mohawk",style5, + "Uncombed",style6; + +style1: + setlook 1,1; + close; +style2: + setlook 1,2; + close; +style3: + setlook 1,3; + close; +style4: + setlook 1,4; + close; +style5: + setlook 1,5; + close; +style6: + setlook 1,6; + close; + +color: + menu + "Orange",orange, + "Green",green, + "Red",red, + "Purple",purple, + "White",white, + "Blonde",blonde, + "Light blue",lightblue, + "Brown",brown, + "Blue",blue, + "Violet",violet, + "No hair",nohair; + +fine: + close; + +orange: + setlook 6,1; + close; +green: + setlook 6,2; + close; +red: + setlook 6,3; + close; +purple: + setlook 6,4; + close; +white: + setlook 6,5; + close; +blonde: + setlook 6,6; + close; +lightblue: + setlook 6,7; + close; +brown: + setlook 6,8; + close; +blue: + setlook 6,9; + close; +violet: + setlook 6,10; + close; +nohair: + setlook 6,11; + close; +} diff --git a/npc/tulimshar/children.txt b/npc/tulimshar/children.txt new file mode 100644 index 0000000..697935c --- /dev/null +++ b/npc/tulimshar/children.txt @@ -0,0 +1,93 @@ +new_3-1.gat,57,70,0 script Child1 108,{
+ set @TEMP,rand(7);
+ if(@TEMP == 1) goto L_1;
+ if(@TEMP == 2) goto L_2;
+ if(@TEMP == 3) goto L_3;
+ if(@TEMP == 4) goto L_4;
+ if(@TEMP == 5) goto L_5;
+ if(@TEMP == 6) goto L_6;
+ if(@TEMP == 7) goto L_7;
+ if(@TEMP == 0) goto L_1;
+
+L_1:
+ mes "[Child]";
+ mes "Maggots are soo slimey!";
+ close;
+
+L_2:
+ mes "[Child]";
+ mes "Want to play ball with me?";
+ close;
+
+L_3:
+ mes "[Child]";
+ mes "Monsters roam the land, i hate scorpions!";
+ close;
+
+L_4:
+ mes "[Child]";
+ mes "When i grow up, I want to be strong enough to kill a scorpion!";
+ close;
+
+L_5:
+ mes "[Child]";
+ mes "Items dropped by monsters can be sold.";
+ close;
+
+L_6:
+ mes "[Child]";
+ mes "Items such as foods, can be eaten to regain Health Points.";
+ close;
+
+L_7:
+ mes "[Child]";
+ mes "I want to be a Doctor when i grow up!";
+ close;
+}
+
+new_3-1.gat,120,33,0 script Child2 103,{
+ set @TEMP,rand(7);
+ if(@TEMP == 1) goto L_1;
+ if(@TEMP == 2) goto L_2;
+ if(@TEMP == 3) goto L_3;
+ if(@TEMP == 4) goto L_4;
+ if(@TEMP == 5) goto L_5;
+ if(@TEMP == 6) goto L_6;
+ if(@TEMP == 7) goto L_7;
+ if(@TEMP == 0) goto L_1;
+
+L_1:
+ mes "[Child]";
+ mes "If i learned anything from school, Grenxen founded Tulimshar.";
+ close;
+
+L_2:
+ mes "[Child]";
+ mes "When i was picking rocks from the field, i saw a red scorpion.";
+ close;
+
+L_3:
+ mes "[Child]";
+ mes "Its polite to state your name before talking to anyone.";
+ close;
+
+L_4:
+ mes "[Child]";
+ mes "I have a Scorpion Doll!";
+ close;
+
+L_5:
+ mes "[Child]";
+ mes "I carry spare Cactus Juices while on the field.";
+ close;
+
+L_6:
+ mes "[Child]";
+ mes "Items such as foods, can be eaten to regain Health Points.";
+ close;
+
+L_7:
+ mes "[Child]";
+ mes "Grenxen is the Demon that founded Tulimshar.";
+ close;
+}
diff --git a/npc/tulimshar/elanore.txt b/npc/tulimshar/elanore.txt new file mode 100644 index 0000000..6348dd6 --- /dev/null +++ b/npc/tulimshar/elanore.txt @@ -0,0 +1,132 @@ +//Heal NPC costs 100 money + +new_3-1.gat,40,66,0 script Elanore 108,{ + + if (baselevel > 10) goto L_NoHeal; + + set @TEMP,rand(3); + + if(@TEMP == 0) goto Heal1; + + if(@TEMP == 1) goto Heal2; + + if(@TEMP == 2) goto Heal3; + + if(@TEMP == 3) goto Heal4; + +Heal1: + + mes "[Elanore]"; + + mes "You don't look too well, let me treat your wounds."; + + next; + + goto Heal_L; + + + +Heal2: + + mes "[Elanore]"; + + mes "I will make quick work of your wounds."; + + next; + + goto Heal_L; + + + +Heal3: + + mes "[Elanore]"; + + mes "Need a healing?."; + + next; + + goto Heal_L; + + + +Heal4: + + mes "[Elanore]"; + + mes "Sometimes you just need to run from battle."; + + next; + + goto Heal_L; + + + +Heal_L: + + if(@TEMP == 0) goto Heal_1; + + if(@TEMP == 1) goto Heal_2; + + if(@TEMP == 2) goto Heal_3; + + if(@TEMP == 3) goto Heal_4; + +Heal_1: + + mes "[Elanore]"; + + mes "Here you go!"; + + heal 10000,10000; + + close; + + + +Heal_2: + + mes "[Elanore]"; + + mes "Painless wasnt it?"; + + mes "Here you go!"; + + heal 10000,10000; + + close; + +Heal_3: + + mes "[Elanore]"; + + mes "You should be more careful."; + + heal 10000,10000; + + close; + +Heal_4: + + mes "[Elanore]"; + + mes "Much better right?!"; + + heal 10000,10000; + + close; + +L_NoHeal: + + mes "[Elanore]"; + + mes "I'm sorry but I'm here only to help young people."; + + mes "Your level is already higher than 10."; + + mes "You can get some rest in the inn near here."; + + close; + +} + diff --git a/npc/tulimshar/guards.txt b/npc/tulimshar/guards.txt new file mode 100644 index 0000000..cd918c8 --- /dev/null +++ b/npc/tulimshar/guards.txt @@ -0,0 +1,56 @@ +new_3-1.gat,48,79,0 script Guard 104,{
+ mes "[Guard]";
+ mes "Protecting Tulimshar is my job.";
+ next;
+ menu
+ "Can you give me any tips?",L_Tip,
+ "Information",L_Info,;
+
+L_Tip:
+ set @TEMP,rand(5);
+ if(@TEMP == 0) goto L_0;
+ if(@TEMP == 1) goto L_1;
+ if(@TEMP == 2) goto L_2;
+ if(@TEMP == 3) goto L_3;
+ if(@TEMP == 4) goto L_4;
+ if(@TEMP == 5) goto L_5;
+L_1:
+ mes "[Guard]";
+ mes "Try to carry spare potion when on fields or in dungeons, they will come in handy.";
+ close;
+L_2:
+ mes "[Guard]";
+ mes "Always carry a map of where you are, ask the Guides in most towns to get one.";
+ close;
+L_3:
+ mes "[Guard]";
+ mes "When in a dungeon, monsters are more aggressive than if they were outside.";
+ close;
+L_4:
+ mes "[Guard]";
+ mes "When gambling for money in casino's, make sure you have enough emergency money.";
+ close;
+L_5:
+ mes "[Guard]";
+ mes "Never underestimate the enemy.";
+ close;
+L_0:
+ mes "[Guard]";
+ mes "Let me think of a few... Oh! Do NOT attack Red scorpions unless you can kill it for sure!";
+ close;
+
+L_Info:
+ mes "[Guard]";
+ mes "Sorry I have no information for you, try asking for a tip.";
+ close;
+}
+
+new_3-1.gat,40,79,0 script SGuard 104,{
+ mes "[Soldier]";
+ mes "ZzzZzzZ...";
+ next;
+ mes "[Soldier]";
+ mes "Heh, what?";
+ mes "I wasn't sleeping, I was just resting my eyes!";
+close;
+}
diff --git a/npc/tulimshar/guide.txt b/npc/tulimshar/guide.txt new file mode 100644 index 0000000..33f380a --- /dev/null +++ b/npc/tulimshar/guide.txt @@ -0,0 +1,87 @@ +new_3-1.gat,33,30,0 script Guide 102,{ + mes "[Guide]"; + mes "Would you like to know about something?"; + next; + menua: + menu "Fighting",L_Fight,"Items",L_Items,"Monsters",L_Monster,"Stylist",L_Style,"Quests",L_Quests,"NPCs",L_NPC,"Commands",L_Comm,"Quick Keys",L_Key,"I know everything!",L_Know; +L_Fight: + mes "[Guide]"; + mes "People live in this world by living off of monsters"; + mes "You can fight monsters and even players by hitting the [CTRL] key, or left mouse click."; + next; + mes "[Guide]"; + mes "If you get tired of pressing the key too much, you can also type shift+ctrl"; + mes "This will make your character attack continuously for the time you are inactive."; + next; + goto menua; +L_Items: + mes "[Guide]"; + mes "There are three types of items."; + mes "They can be Consumables, Equipment, or Miscellaneous"; + next; + mes "[Guide]"; + mes "Consumable items such as Potions, can be used only once"; + mes "after use, they will dissapear from your inventory."; + next; + mes "[Guide]"; + mes "Equipment items like Armors, Weapons, Accessories"; + mes "can be equipped for fashionable purposes or to raise your status"; + next; + mes "[Guide]"; + mes "Miscellaneous items such as maggot slime, are used"; + mes "in creating other items, or just to trade and sell."; + next; + goto menua; +L_Monster: + mes "[Guide]"; + mes "In every world, there are beasts. Monsters can be found almost anywhere!~"; + mes "To fight them, please read [Fighting] if you do not know how"; + next; + mes "There a several types of monsters, Aggressive, Neutral, Assistants"; + next; + mes "[Guide]"; + mes "Agressive monsters know that they are always in danger"; + mes "so therefore they always keep their guard up"; + mes "Making them attack anybody in sight"; + next; + mes "[Guide]"; + mes "Neutral monsters tend to just lounge around until attacked"; + mes "They will leave everything alone unless they are threatened"; + next; + mes "[Guide]"; + mes "Assistants are monsters who help eachother, there havent been any yet"; + mes "But there soon will be, These monsters attack in groups if they are threatened"; + next; + goto menua; +L_Style: + mes "[Guide]"; + mes "The stylist NPC will cut and perm your hair!"; + mes "they are known for their hair growth formula"; + next; + goto menua; +L_Quests: + mes "[Guide]"; + mes "There are people in the world in need of help!"; + mes "Most of these people aren't afraid to give rewards to those who help them"; + mes "So be nice and help people along the way!"; + next; + goto menua; +L_NPC: + mes "[Guide]"; + mes "NPC[Non Playable Characters] are people who are always in the game"; + mes "Tending to many variatys of services from just chatting to helping others."; + next; + goto menua; +L_Comm: + mes "[Guide]"; + mes "There are no /Commands available currently"; + next; + goto menua; +L_Key: + mes "[Guide]"; + mes "There are many key combinations, press F1 for a short list of them!"; + next; + goto menua; +L_Know: + close; +} diff --git a/npc/tulimshar/man.txt b/npc/tulimshar/man.txt new file mode 100644 index 0000000..265a5d9 --- /dev/null +++ b/npc/tulimshar/man.txt @@ -0,0 +1,18 @@ +new_3-1.gat,114,43,0 script Guard 102,{
+ mes "[Man]";
+ mes "Ouch! It hurts, this wound I got from battle.";
+ next;
+ menu "Tell me about it",L_Exp,"Nevermind",L_Nev;
+
+L_Exp:
+ mes "I was fighting scorpions for experience and I bumped into a RED one. I had NEVER seen it before!";
+ next;
+ mes "Luckily i had a camera with me! Here's a picture of it... Let me find it, I put it in my pocket somewhere...";
+ next;
+ mes "Oh man! My pockets have been ripped clean off!";
+ close;
+
+L_Nev:
+ mes "Hmpf!";
+ close;
+}
diff --git a/npc/tulimshar/merchant.txt b/npc/tulimshar/merchant.txt new file mode 100644 index 0000000..5956bfb --- /dev/null +++ b/npc/tulimshar/merchant.txt @@ -0,0 +1 @@ +new_3-1.gat,54,45,0 shop Neko 101,501:50,502:60,1201:100,1202:1000,522:300,521:1000,523:8000
diff --git a/npc/tulimshar/monster_guide.txt b/npc/tulimshar/monster_guide.txt new file mode 100644 index 0000000..002f7d7 --- /dev/null +++ b/npc/tulimshar/monster_guide.txt @@ -0,0 +1,36 @@ +new_3-1.gat,46,66,0 script MonsterGuide 102,{ +if(MPQUEST == 0) goto Register; + mes "[Monster Guide]"; + mes "You currently have " +Mobpt+ " Monster Points"; + mes "These points are acquired while killing monsters"; + close; +Register: + mes "[Monster Guide]"; + mes "Oh my, you dont seem to be registered as a Quest Participant, would you like to register?"; + next; + menu "Register",L_R,"Skip",L_N,"Information",L_I; + L_R: + mes "[Monster Guide]"; + mes "Give me a second to look over your paperwork."; + next; + mes "[Monster Guide]"; + mes "Well, looks like you qualify!"; + mes "Welcome to the questing world!"; + set MPQUEST,1; + close; + L_N: + mes "[Monster Guide]"; + mes "Very well, you dont know what your missing."; + close; + L_I: + mes "[Monster Guide]"; + mes "Here in The Mana World, there are certain rewards for your vanquishing of foes."; + mes "For example, there are Monster Points, every monster you kill has a certain amount of points that get added to your account."; + mes "The more points you have, the more expensive things you can buy using them."; + next; + mes "[Monster Guide]"; + mes "So whaddya say, sign up wont u?"; + close; +} + + diff --git a/npc/tulimshar/monsters.txt b/npc/tulimshar/monsters.txt new file mode 100644 index 0000000..645c562 --- /dev/null +++ b/npc/tulimshar/monsters.txt @@ -0,0 +1,32 @@ +// Map: new_3-1 +// This is Tulimshar the city - southern Part with market and casino/inn. +// ALWAYS keep a backup file before edit (experience :) +// The last param is the Event on Mobdeath +// + +new_3-1.gat,0,0,0,0 monster Maggot 1002,35,2000,0,ConquestMob3::OnMaggotDead +new_3-1.gat,0,0,0,0 monster Scorpion 1003,25,2500,0,ConquestMob3::OnScorpionDead +new_3-1.gat,0,0,0,0 monster Firegoblin 1011,20,2500,0,ConquestMob3::OnFireGoblinnDead + +new_3-1.gat,0,0,0 script ConquestMob3 -1,{ +// id 1002 = Maggot +OnMaggotDead: + if (MPQUEST == 1) set Mobpt,Mobpt+1; + break; + +// id 1003 = Scorpion +OnScorpionDead: + if (MPQUEST == 1) set Mobpt,Mobpt+2; + break; +end; +} + +// id 1011 = FireGoblin +OnFireGoblinDead: + if (MPQUEST == 1) set Mobpt,Mobpt+2; + break; +end; +} + + + diff --git a/npc/tulimshar/passages.txt b/npc/tulimshar/passages.txt new file mode 100644 index 0000000..afecf42 --- /dev/null +++ b/npc/tulimshar/passages.txt @@ -0,0 +1,2 @@ +new_1-1.gat,56,12 warp totown 5,1,new_3-1.gat,44,80 +new_3-1.gat,42,88 warp tofield 5,2,new_1-1.gat,58,17 diff --git a/npc/tulimshar/rewards_master.txt b/npc/tulimshar/rewards_master.txt new file mode 100644 index 0000000..7f0c07e --- /dev/null +++ b/npc/tulimshar/rewards_master.txt @@ -0,0 +1,185 @@ +new_3-1.gat,55,51,0 script Rewards 106,{ + if (MPQUEST == 0) goto Register; + + mes "[Rewards Master]"; + mes "Welcome! Would you like to exchange some points for items?"; + next; + menu "Yes",Y1,"No Thanks",LEAVE; + next; + +Register: + mes "[Rewards Master]"; + mes "Hey, it seems like you didn't register as a quest participant yet! You can sign up at the gate below."; + close; + +Y1: + mes "[Rewards Master]"; + mes "Ok lets check those points."; + if (tvis == 0) set tvis,1; + if (Mobpt < tvis) goto NotEnough; + set Mobpt,Mobpt-tvis; + set tvis,tvis+1; + next; + set @TEMP,rand(1); + if(@TEMP ==0) goto R_1; + if(@TEMP ==1) goto R_2; +R_1: + set @TEMP,rand(19); + if(@TEMP ==0) goto R1_1; + if(@TEMP ==1) goto R1_2; + if(@TEMP ==2) goto R1_3; + if(@TEMP ==3) goto R1_4; + if(@TEMP ==4) goto R1_5; + if(@TEMP ==5) goto R1_6; + if(@TEMP ==6) goto R1_7; + if(@TEMP ==7) goto R1_8; + if(@TEMP ==8) goto R1_9; + if(@TEMP ==9) goto R1_10; + if(@TEMP ==10) goto R1_11; + if(@TEMP ==11) goto R1_12; + if(@TEMP ==12) goto R1_13; + if(@TEMP ==13) goto R1_14; + if(@TEMP ==14) goto R1_15; + if(@TEMP ==15) goto R1_16; + if(@TEMP ==16) goto R1_17; + if(@TEMP ==17) goto R1_18; + if(@TEMP ==18) goto R1_19; + if(@TEMP ==19) goto R1_20; + +R1_1: + mes "[Rewards Master]"; + mes "You got Cactus Drink."; + getitem 501,1; + goto Q; +R1_2: + mes "[Rewards Master]"; + mes "You got Cactus Potion."; + getitem 502,1; + goto Q; +R1_3: + mes "[Rewards Master]"; + mes "You got Casino Coins."; + getitem 503,1; + goto Q; +R1_4: + mes "[Rewards Master]"; + mes "You got Decor Candy Cane."; + getitem 504,1; + goto Q; +R1_5: + mes "[Rewards Master]"; + mes "You got Maggot Slime."; + getitem 505,1; + goto Q; +R1_6: + mes "[Rewards Master]"; + mes "You got Candy Cane."; + getitem 506,1; + goto Q; +R1_7: + mes "[Rewards Master]"; + mes "You got Scorpion Stinger."; + getitem 507,1; + goto Q; +R1_8: + mes "[Rewards Master]"; + mes "You got Xmas Cake."; + getitem 508,1; + goto Q; +R1_9: + mes "[Rewards Master]"; + mes "You got Chocolate Bar."; + getitem 509,1; + goto Q; +R1_10: + mes "[Rewards Master]"; + mes "You got Candy."; + getitem 510,1; + goto Q; +R1_11: + mes "[Rewards Master]"; + mes "You got Santa Hat."; + getitem 511,1; + goto Q; +R1_12: + mes "[Rewards Master]"; + mes "You got Ginger Bread Man."; + getitem 512,1; + goto Q; +R1_13: + mes "[Rewards Master]"; + mes "You got Cake."; + getitem 513,1; + goto Q; +R1_14: + mes "[Rewards Master]"; + mes "You got Candy Cane."; + getitem 514,1; + goto Q; +R1_15: + mes "[Rewards Master]"; + mes "You got Purple Present."; + getitem 515,1; + goto Q; +R1_16: + mes "[Rewards Master]"; + mes "You got Blue Present."; + getitem 516,1; + goto Q; +R1_17: + mes "[Rewards Master]"; + mes "You got Red Scorpion Stinger."; + getitem 517,1; + goto Q; +R1_18: + mes "[Rewards Master]"; + mes "You got Bug Leg."; + getitem 518,1; + goto Q; +R1_19: + mes "[Rewards Master]"; + mes "You got Cherry Cake."; + getitem 519,1; + goto Q; +R1_20: + mes "[Rewards Master]"; + mes "You got Easter Egg."; + getitem 520,1; + goto Q; + +R_2: + set @TEMP,rand(3); + if(@TEMP ==0) goto R2_1; + if(@TEMP ==1) goto R2_2; + if(@TEMP ==2) goto R2_3; + if(@TEMP ==3) goto R2_4; + +R2_1: + mes "[Rewards Master]"; + mes "You got an Arrow."; + getitem 1199,1; + goto Q; +R2_2: + mes "[Rewards Master]"; + mes "You got a Bow."; + getitem 1200,1; + goto Q; +R2_3: + mes "[Rewards Master]"; + mes "You got a Knife."; + getitem 1201,1; + goto Q; +R2_4: + mes "[Rewards Master]"; + mes "You got an Cotton Shirt."; + getitem 1202,1; + goto Q; + +NotEnough: + next; + mes "[Rewards Master]"; + mes "Kill some more monsters first."; + close; +LEAVE: + close; +} diff --git a/npc/tulimshar/sandra.txt b/npc/tulimshar/sandra.txt new file mode 100644 index 0000000..6a19ffa --- /dev/null +++ b/npc/tulimshar/sandra.txt @@ -0,0 +1,81 @@ +new_3-1.gat,72,61,0 script Sandra 114,{ + if(Scorp == 2) goto done; + if(Scorp == 1) goto reas; + set @TEMP,rand(3); + if(@TEMP == 1) goto L_1; + if(@TEMP == 2) goto L_2; + if(@TEMP == 3) goto L_3; + if(@TEMP == 0) goto L_4; +L_1: + mes "[Sandra]"; + mes "In the outskirts of Tulimshar, there wanders a red scorpion. I need a favor, please help me."; + next; + goto red; +L_2: + mes "[Sandra]"; + mes "When you venture in the outskirts of Tulimshar, you can spot a red scorpion. Will you help me kill one?"; + next; + goto red; +L_3: + mes "[Sandra]"; + mes "The red scorpion stinger carries many properties used in potions."; + next; + goto red; +L_4: + mes "[Sandra]"; + mes "You look sturdy enough, will you help me get something?"; + next; + goto red; +red: + menu "Yes",L_kl,"No",L_N; +L_kl: + if(@TEMP == 1) goto K_1; + if(@TEMP == 2) goto K_2; + if(@TEMP == 3) goto K_3; + if(@TEMP == 0) goto K_1; +K_1: + mes "[Sandra]"; + mes "I need you to slaughter the red scorpion found outside of Tulimshar and bring me 5 [Red Stingers]"; + next; + goto set1; +K_2: + mes "[Sandra]"; + mes "I heard a while ago that the stinger of a red scorpion can be used for medical purposes. I need you to help me get 5 [Red Stingers]"; + next; + goto set1; +K_3: + mes "[Sandra]"; + mes "Bring me 5 [Red Stingers] and i will reward you greatly"; + next; + goto set1; +set1: + set Scorp,1; + mes "[Sandra]"; + mes "Please bring me them!"; + close; +reas: + if(countitem(517) >= 5) goto have; + mes "[Sandra]"; + mes "Please hurry and bring me 5 [Red Stingers]"; + close; +have: + mes "[Sandra]"; + mes "Excelent!"; + mes "You brought me 5 [Red Stingers]!"; + delitem 517,5; + next; + mes "[Sandra]"; + mes "Here you go, your reward!"; + mes "+Got Bow"; + mes "+Got Arrows 100"; + getitem 1200,1; + getitem 1199,100; + set Scorp,2; + close; +done: + mes "[Sandra]"; + mes "Thank you for all your help!"; + close; +L_N: + close; +} diff --git a/npc/tulimshar/vincent.txt b/npc/tulimshar/vincent.txt new file mode 100644 index 0000000..c485db3 --- /dev/null +++ b/npc/tulimshar/vincent.txt @@ -0,0 +1,90 @@ +new_3-1.gat,137,34,0 script Vincent 113,{ + if(Bugleg == 1) goto reas; + if(Bugleg == 2) goto done; + set @TEMP,rand(3); + if(@TEMP == 1) goto L_1; + if(@TEMP == 2) goto L_2; + if(@TEMP == 3) goto L_3; + if(@TEMP == 0) goto L_4; + +L_1: + mes "[Vincent]"; + mes "I just need 30 more [Bug Legs] to finish my action figure!"; + next; + goto main1; +L_2: + mes "[Vincent]"; + mes "This maggot action figure is awesome! I just need to attach [30 Part A{Bug Leg}]"; + next; + goto main1; +L_3: + mes "[Vincent]"; + mes "This is a great action figure! A must have! All i need is a few parts..."; + goto main1; +L_4: + mes "[Vincent]"; + mes "Can you get me 30 [Bug Legs]? I need them to replace the action figure parts."; + next; + goto main1; + +main1: + mes "[Vincent]"; + mes "Will you help me find 30 [Bug Legs]?"; + next; + menu "Yes",B_1,"No",B_2; +B_1: + set Bugleg,1; + set @TEMP,rand(3); + if(@TEMP == 1) goto J_1; + if(@TEMP == 2) goto J_2; + if(@TEMP == 3) goto J_3; + if(@TEMP == 0) goto J_4; +J_1: + mes "[Vincent]"; + mes "Thank you!"; + next; + goto main2; +J_2: + mes "[Vincent]"; + mes "I don't know how to thank you enough!"; + next; + goto main2; +J_3: + mes "[Vincent]"; + mes "I will thank you when I get them!"; + next; + goto main2; +J_4: + mes "[Vincent]"; + mes "I'm sure I will give a small reward. :D"; + next; + goto main2; +main2: + mes "[Vincent]"; + mes "Now please go get me 30 [Bug Legs]"; + close; +reas: + if(countitem(518) >= 30) goto have; + mes "[Vincent]"; + mes "Please help me collect 30 [Bug Legs]!"; + close; +have: + mes "[Vincent]"; + mes "Excellent! Finally I can complete the model!!"; + delitem 518,30; + next; + mes "[Vincent]"; + mes "Here you go, a little of my appreciation!"; + next; + mes "Got 1000 GP"; + set zeny,zeny+1000; + set Bugleg,2; + close; +done: + mes "[Vincent]"; + mes "Thanks for your help!"; + close; +B_2: + close; +} + diff --git a/npc/western-desert/dark_mage.txt b/npc/western-desert/dark_mage.txt new file mode 100644 index 0000000..06d9b35 --- /dev/null +++ b/npc/western-desert/dark_mage.txt @@ -0,0 +1,37 @@ +new_1-1.gat,24,24,0 script SoulThief 103,{ + + if (class == 1) goto L_Soul; + mes "[Dark Mage]"; + mes "Psss hey would you like to have more power?"; + next; + + menu "You fool, that's not possible",-,"I'm listening...",L_Go; + close; + +L_Go: + mes "I can give you ancient powers that will let you become the greatest warrior in The Mana World, or the best merchant."; + mes "But everything has got his price..."; + next; + + mes "Oh nothing important nor expensive."; + mes "Just your soul!"; + next; + + menu "My soul? Here it is",L_Soul,"I need a better explanation!",-; + + mes "Well there's a very small possibility that your player will be translated into the new server."; + mes "I said a very, very small possibility."; + mes "Players without a soul instead will be lost forever..."; + mes "Come on, you won't loose that much!"; + next; + + menu "Hmmm ok, I guess you can take it.",L_Soul,"Maybe I'll think about it a bit more",-; + mes "I know you'll come here again when you'll get killed by a maggot, begging me to help you"; + close; + +L_Soul: + mes "[Dark Mage]"; + mes "Hrhrhr, your soul is finally mine!"; + jobchange 1; + close; +}
\ No newline at end of file diff --git a/npc/western-desert/merchant.txt b/npc/western-desert/merchant.txt new file mode 100644 index 0000000..77f63d4 --- /dev/null +++ b/npc/western-desert/merchant.txt @@ -0,0 +1 @@ +new_1-1.gat,19,99,0 shop CaveShop 109,525:800,531:3000,530:8000,1199:5 diff --git a/npc/western-desert/monsters.txt b/npc/western-desert/monsters.txt new file mode 100644 index 0000000..adb609e --- /dev/null +++ b/npc/western-desert/monsters.txt @@ -0,0 +1,3 @@ +new_1-1.gat,0,0,0,0 monster GreenSlime 1005,50,0,0,0 +new_1-1.gat,0,0,0,0 monster GiantMaggot 1006,30,0,0,0 +new_1-1.gat,0,0,0,0 monster RedScorpion 1004,20,0,0,0 diff --git a/npc/western-desert/nomads.txt b/npc/western-desert/nomads.txt new file mode 100644 index 0000000..1c747c5 --- /dev/null +++ b/npc/western-desert/nomads.txt @@ -0,0 +1,23 @@ +new_1-1.gat,15,67,0 script Nomad1 132,{ + mes "[Nomad]"; + mes "This is really a nice place."; + + mes "Don't you think so?"; + close; +} + +new_1-1.gat,18,68,0 script Nomad2 128,{ + mes "[Nomad]"; + mes "I found a passage to a green land through the mountains west of here"; + + mes "Do you want me to show you the way?"; + next; + menu + "Sure",sure, + "No thank you",no; +sure: + warp "new_9-1.gat",38,95; mes "I'll be awaiting you here."; mes "Tell me when you want to go back."; + close; +no: + close; +}
\ No newline at end of file diff --git a/npc/western-desert/passages.txt b/npc/western-desert/passages.txt new file mode 100644 index 0000000..ec363db --- /dev/null +++ b/npc/western-desert/passages.txt @@ -0,0 +1,4 @@ +new_2-1.gat,36,30 warp caveexit 3,0,new_1-1.gat,25,95 +new_1-1.gat,19,94 warp caveentrance 1,1,new_2-1.gat,37,31 +new_1-1.gat,111,79 warp eastdesert 2,2,new_7-1.gat,23,40 +new_7-1.gat,18,39 warp westdesert 3,4,new_1-1.gat,106,78
\ No newline at end of file diff --git a/npc/woodland/alchemist.txt b/npc/woodland/alchemist.txt new file mode 100644 index 0000000..e065af3 --- /dev/null +++ b/npc/woodland/alchemist.txt @@ -0,0 +1,39 @@ +new_9-1.gat,117,77,0 script Alchemist 103,{ + mes "[Alchemist]"; + mes "I'm learning the ancient science of the alchemy."; + mes "I already know how to create a couple of potions!!"; + next; + + mes "[Alchemist]"; + mes "Do you want me to create one for you?"; + next; + + menu "Iron potion",L_iron,"Concentration potion",L_concentration,"No thanks.",L_no; +L_iron: + if(countitem(566) < 20) goto L_no_iron; + mes "[Alchemist]"; + mes "Great! You brought me exactly what I need!"; + mes "Here it is your Iron potion."; + delitem 566,20; + getitem 567,1; + close; + +L_concentration: + if(countitem(565) < 20) goto L_no_concentration; + mes "[Alchemist]"; + mes "Great! You brought me exactly what I need!"; + mes "Here it is your Concentration potion."; + delitem 565,20; + getitem 568,1; + close; + +L_no_iron: + mes "[Alchemist]"; + mes "You have to bring me 20 small mushrooms."; + close; + +L_no_concentration: + mes "[Alchemist]"; + mes "You have to bring me 20 petals."; + close; +}
\ No newline at end of file diff --git a/npc/woodland/monsters.txt b/npc/woodland/monsters.txt new file mode 100644 index 0000000..a25b251 --- /dev/null +++ b/npc/woodland/monsters.txt @@ -0,0 +1,2 @@ +new_9-1.gat,0,0,0,0 monster EvilMushroom 1013,25,0,0,0 +new_9-1.gat,0,0,0,0 monster SleepFlower 1014,40,0,0,0 diff --git a/npc/woodland/passages.txt b/npc/woodland/passages.txt new file mode 100644 index 0000000..ae94482 --- /dev/null +++ b/npc/woodland/passages.txt @@ -0,0 +1,54 @@ +new_9-1.gat,58,91 warp tofield 1,1,new_4-1.gat,15,15 +new_9-1.gat,41,92,0 script Nomad3 131,{ + + mes "[Nomad]"; + + mes "Do you want to go back?"; + + next; + + menu + + "Yes please",yes, + + "Not yet",notyet; + +yes: + + warp "new_1-1.gat",18,70; + + close; + +notyet: + + close; + +} + +new_9-1.gat,73,29,0 script Child 114,{ + + mes "[Taro]"; + + mes "I saw Santa Claus going up this road."; + + mes "Will you help me following him?"; + + next; + + menu + + "Of course, I'll help you!",yes, + + "No, it's probably dangerous out there.",no; + +yes: + + warp "new_10-1.gat",56,61; + + close; + +no: + + close; + +}
\ No newline at end of file diff --git a/npc/xmas/monsters.txt b/npc/xmas/monsters.txt new file mode 100644 index 0000000..df7ad8a --- /dev/null +++ b/npc/xmas/monsters.txt @@ -0,0 +1,2 @@ +new_10-1.gat,0,0,0,0 monster SantaSlime 1015,10,0,0,0 +new_10-1.gat,0,0,0,0 monster RudolphSlime 1016,25,0,0,0
\ No newline at end of file diff --git a/npc/xmas/santa.txt b/npc/xmas/santa.txt new file mode 100644 index 0000000..c53ce55 --- /dev/null +++ b/npc/xmas/santa.txt @@ -0,0 +1,50 @@ +new_10-1.gat,74,75,0 script Santa 105,{ +if (ChristmasQuest2 == 1) goto done; +mes "[Santa]"; +mes "Ho ho ho."; +mes "Hello my young friend."; +mes "Would you like to help me?"; +next; +mes "Those monsters stolen my presents."; +mes "If you help me, I'll give you something very nice"; +next; +L_M: +menu "I have some present boxes",L_Y,"Hmm, see you later",L_N,"What you need exactly?",L_R; + L_Y: + mes "[Santa]"; + mes "Hmm, let me check what you have."; + next; + if(countitem(515)<25) goto NoItem; + if(countitem(516)<20) goto NoItem; + if(countitem(538)<5) goto NoItem; + mes "Great! Here is something for you"; + delitem 515,25; + delitem 516,20; + delitem 538,5; + getitem 563+rand(2),1; + set ChristmasQuest2,1; + close; + L_R: + mes "[Santa]"; + mes "I need:"; + mes "25 purple present boxes"; + mes "20 blue present boxes"; + mes "5 green present boxes"; + next; + goto L_M; + L_N: + mes "[Santa]"; + mes "Enjoy your holidays and I wish you an happy Christmas!"; + close; + NoItem: + mes "[Santa]"; + mes "You dont seem to have enough presents."; + close; + done: + mes "[Santa]"; + mes "All the children got their Christmas presents."; + mes "Thank you for your help."; + close; + +} + diff --git a/npc/xmas/snowman.txt b/npc/xmas/snowman.txt new file mode 100644 index 0000000..2cb9c85 --- /dev/null +++ b/npc/xmas/snowman.txt @@ -0,0 +1,44 @@ +new_10-1.gat,77,44,0 script Santa 129,{ +if( ChristmasQuest == 1) goto done; +mes "[Snowman]"; +mes "Hello there young man."; +mes "Would you like a Christmas hat?"; +next; +L_M: +menu "Yes",L_Y,"No",L_N,"What do you need?",L_R; + L_Y: + mes "[Snowman]"; + mes "Hmm, let me see what you have."; + next; + if(countitem(510)<15) goto NoItem; + if(countitem(509)<10) goto NoItem; + if(countitem(502)<5) goto NoItem; + mes "Here you go, enjoy your new hat!"; + delitem 510,15; + delitem 509,10; + delitem 502,5; + getitem 511,1; + set ChristmasQuest,1; + close; + L_R: + mes "[Snowman]"; + mes "For this special hat, I need a pint of magic and a little help."; + mes "Just kidding. I would like to get some food;"; + mes "15 Candies"; + mes "10 Chocolate bars"; + mes "5 Cactus potions"; + next; + goto L_M; + L_N: + mes "[Snowman]"; + mes "Well, thats too bad, but make sure to have the holiday spirit!"; + close; + NoItem: + mes "[Snowman]"; + mes "Well, I am interested in some food and you don't have enought to get a hat."; + close; + done: + mes "[Snowman]"; + mes "Thank you for help!"; + close; +}
\ No newline at end of file diff --git a/npc/xmas/taro.txt b/npc/xmas/taro.txt new file mode 100644 index 0000000..6950d95 --- /dev/null +++ b/npc/xmas/taro.txt @@ -0,0 +1,13 @@ +new_10-1.gat,57,61,0 script Child 114,{ + mes "[Taro]"; + mes "I'm scared!!!"; + mes "Bring me back, pleeeease!!!"; + next; + menu + "Ok",yes, + "Just give me one more minute.",no; +yes: + warp "new_9-1.gat",73,31; close; +no: + close; +}
\ No newline at end of file diff --git a/save/account.txt b/save/account.txt new file mode 100644 index 0000000..c96372b --- /dev/null +++ b/save/account.txt @@ -0,0 +1,14 @@ +// Accounts file: here are saved all information about the accounts. +// Structure: ID, account name, password, last login time, sex, # of logins, state, email, error message for state 7, validity time, last (accepted) login ip, memo field, ban timestamp, repeated(register text, register value) +// Some explanations: +// account name : between 4 to 23 char for a normal account (standard client can't send less than 4 char). +// account password: between 4 to 23 char +// sex : M or F for normal accounts, S for server accounts +// state : 0: account is ok, 1 to 256: error code of packet 0x006a + 1 +// email : between 3 to 39 char (a@a.com is like no email) +// error message : text for the state 7: 'Your are Prohibited to login until <text>'. Max 19 char +// valitidy time : 0: unlimited account, <other value>: date calculated by addition of 1/1/1970 + value (number of seconds since the 1/1/1970) +// memo field : max 254 char +// ban time : 0: no ban, <other value>: banned until the date: date calculated by addition of 1/1/1970 + value (number of seconds since the 1/1/1970) +0 s1 p1 2006-03-25 16:09:35.113 S 38706 0 a@a.com - 0 - - 0 +1 s2 p2 2006-03-04 16:54:40.974 S 3 0 a@a.com - 0 - - 0 diff --git a/src/Makefile b/src/Makefile new file mode 100644 index 0000000..00ec3b9 --- /dev/null +++ b/src/Makefile @@ -0,0 +1,126 @@ +# $Id: Makefile 158 2004-10-01 03:45:15Z PoW $
+
+CC = gcc -pipe
+PACKETDEF = -DPACKETVER=5 -DNEW_006b
+#PACKETDEF = -DPACKETVER=4 -DNEW_006b
+#PACKETDEF = -DPACKETVER=3 -DNEW_006b
+#PACKETDEF = -DPACKETVER=2 -DNEW_006b
+#PACKETDEF = -DPACKETVER=1 -DNEW_006b
+
+PLATFORM = $(shell uname)
+
+ifeq ($(findstring FreeBSD,$(PLATFORM)), FreeBSD)
+MAKE = gmake
+else
+MAKE = make
+endif
+
+OPT = -g -O2
+
+ifeq ($(findstring CYGWIN,$(PLATFORM)), CYGWIN)
+OS_TYPE = -DCYGWIN
+CFLAGS = $(OPT) -Wall -DFD_SETSIZE=4096 -I../common $(PACKETDEF) $(OS_TYPE)
+else
+OS_TYPE =
+CFLAGS = $(OPT) -Wall -I../common $(PACKETDEF) $(OS_TYPE)
+endif
+
+MYSQLFLAG_INCLUDE_DEFAULT = /usr/local/include/mysql
+
+ifdef SQLFLAG
+MYSQLFLAG_CONFIG = $(shell which mysql_config)
+ifeq ($(findstring /,$(MYSQLFLAG_CONFIG)), /)
+MYSQLFLAG_VERSION = $(shell $(MYSQLFLAG_CONFIG) --version | sed s:\\..*::)
+endif
+
+ifeq ($(findstring 5,$(MYSQLFLAG_VERSION)), 5)
+MYSQLFLAG_CONFIG_ARGUMENT = --include
+endif
+ifeq ($(findstring 4,$(MYSQLFLAG_VERSION)), 4)
+MYSQLFLAG_CONFIG_ARGUMENT = --include
+endif
+ifndef MYSQLFLAG_CONFIG_ARGUMENT
+MYSQLFLAG_CONFIG_ARGUMENT = --cflags
+endif
+
+ifeq ($(findstring /,$(MYSQLFLAG_CONFIG)), /)
+MYSQLFLAG_INCLUDE = $(shell $(MYSQLFLAG_CONFIG) $(MYSQLFLAG_CONFIG_ARGUMENT))
+else
+MYSQLFLAG_INCLUDE = -I$(MYSQLFLAG_INCLUDE_DEFAULT)
+endif
+
+LIB_S_DEFAULT = -L/usr/local/lib/mysql -lmysqlclient -lz
+MYSQLFLAG_CONFIG = $(shell which mysql_config)
+ifeq ($(findstring /,$(MYSQLFLAG_CONFIG)), /)
+LIB_S = $(shell $(MYSQLFLAG_CONFIG) --libs)
+else
+LIB_S = $(LIB_S_DEFAULT)
+endif
+
+MYLIB = CC="$(CC)" CFLAGS="$(CFLAGS) $(MYSQLFLAG_INCLUDE)" LIB_S="$(LIB_S)"
+
+endif
+
+MKDEF = CC="$(CC)" CFLAGS="$(CFLAGS)"
+
+all: conf txt
+
+conf:
+ cp -r conf-tmpl conf
+ rm -rf conf/.svn conf/*/.svn
+
+txt : src/common/GNUmakefile src/login/GNUmakefile src/char/GNUmakefile src/map/GNUmakefile src/ladmin/GNUmakefile conf
+ cd src ; cd common ; $(MAKE) $(MKDEF) $@ ; cd ..
+ cd src ; cd login ; $(MAKE) $(MKDEF) $@ ; cd ..
+ cd src ; cd char ; $(MAKE) $(MKDEF) $@ ; cd ..
+ cd src ; cd map ; $(MAKE) $(MKDEF) $@ ; cd ..
+ cd src ; cd ladmin ; $(MAKE) $(MKDEF) $@ ; cd ..
+
+
+ifdef SQLFLAG
+sql: src/common/GNUmakefile src/login_sql/GNUmakefile src/char_sql/GNUmakefile src/map/GNUmakefile src/ladmin/GNUmakefile src/txt-converter/login/GNUmakefile src/txt-converter/char/GNUmakefile conf
+ cd src ; cd common ; $(MAKE) $(MKDEF) $@ ; cd ..
+ cd src ; cd login_sql ; $(MAKE) $(MYLIB) $@ ; cd ..
+ cd src ; cd char_sql ; $(MAKE) $(MYLIB) $@ ; cd ..
+ cd src ; cd map ; $(MAKE) $(MYLIB) $@ ; cd ..
+ cd src ; cd ladmin ; $(MAKE) $(MKDEF) $@ ; cd ..
+ cd src ; cd txt-converter ; cd login ; $(MAKE) $(MYLIB) $@ ; cd ..
+ cd src ; cd txt-converter ; cd char ; $(MAKE) $(MYLIB) $@ ; cd ..
+else
+sql:
+ $(MAKE) CC="$(CC)" OPT="$(OPT)" SQLFLAG=1 $@
+endif
+
+clean: src/common/GNUmakefile src/login/GNUmakefile src/char/GNUmakefile src/map/GNUmakefile src/ladmin/GNUmakefile src/txt-converter/login/GNUmakefile src/txt-converter/char/GNUmakefile
+ cd src ; cd common ; $(MAKE) $(MKDEF) $@ ; cd ..
+ cd src ; cd login ; $(MAKE) $(MKDEF) $@ ; cd ..
+ cd src ; cd login_sql ; $(MAKE) $(MKLIB) $@ ; cd ..
+ cd src ; cd char ; $(MAKE) $(MKDEF) $@ ; cd ..
+ cd src ; cd char_sql ; $(MAKE) $(MKLIB) $@ ; cd ..
+ cd src ; cd map ; $(MAKE) $(MKLIB) $@ ; cd ..
+ cd src ; cd ladmin ; $(MAKE) $(MKDEF) $@ ; cd ..
+ cd src ; cd txt-converter ; cd login ; $(MAKE) $(MKLIB) $@ ; cd ..
+ cd src ; cd txt-converter ; cd char ; $(MAKE) $(MKLIB) $@ ; cd ..
+
+tools:
+ cd tool && $(MAKE) $(MKDEF) && cd ..
+ $(CC) -o setupwizard setupwizard.c
+
+src/common/GNUmakefile: src/common/Makefile
+ sed -e 's/$$>/$$^/' src/common/Makefile > src/common/GNUmakefile
+src/login/GNUmakefile: src/login/Makefile
+ sed -e 's/$$>/$$^/' src/login/Makefile > src/login/GNUmakefile
+src/login_sql/GNUmakefile: src/login_sql/Makefile
+ sed -e 's/$$>/$$^/' src/login_sql/Makefile > src/login_sql/GNUmakefile
+src/char/GNUmakefile: src/char/Makefile
+ sed -e 's/$$>/$$^/' src/char/Makefile > src/char/GNUmakefile
+src/char_sql/GNUmakefile: src/char_sql/Makefile
+ sed -e 's/$$>/$$^/' src/char_sql/Makefile > src/char_sql/GNUmakefile
+src/map/GNUmakefile: src/map/Makefile
+ sed -e 's/$$>/$$^/' src/map/Makefile > src/map/GNUmakefile
+src/ladmin/GNUmakefile: src/ladmin/Makefile
+ sed -e 's/$$>/$$^/' src/ladmin/Makefile > src/ladmin/GNUmakefile
+src/txt-converter/login/GNUmakefile: src/txt-converter/login/Makefile
+ sed -e 's/$$>/$$^/' src/txt-converter/login/Makefile > src/txt-converter/login/GNUmakefile
+src/txt-converter/char/GNUmakefile: src/txt-converter/char/Makefile
+ sed -e 's/$$>/$$^/' src/txt-converter/char/Makefile > src/txt-converter/char/GNUmakefile
diff --git a/src/Makefile-optimized b/src/Makefile-optimized new file mode 100644 index 0000000..63dc5ff --- /dev/null +++ b/src/Makefile-optimized @@ -0,0 +1,49 @@ +# $Id: Makefile-optimized,v 1.7 2004/07/29 09:35:21 Yor Exp $
+
+CC = gcc -pipe
+PACKETDEF = -DPACKETVER=5 -DNEW_006b
+#PACKETDEF = -DPACKETVER=4 -DNEW_006b
+#PACKETDEF = -DPACKETVER=3 -DNEW_006b
+#PACKETDEF = -DPACKETVER=2 -DNEW_006b
+#PACKETDEF = -DPACKETVER=1 -DNEW_006b
+
+PLATFORM = $(shell uname)
+
+ifeq ($(findstring FreeBSD,$(PLATFORM)), FreeBSD)
+MAKE = gmake
+else
+MAKE = make
+endif
+
+ifeq ($(findstring CYGWIN,$(PLATFORM)), CYGWIN)
+OS_TYPE = -DCYGWIN
+CFLAGS = -O2 -Wall -DFD_SETSIZE=4096 -I../common $(PACKETDEF) $(OS_TYPE)
+else
+OS_TYPE =
+CFLAGS = -O2 -Wall -I../common $(PACKETDEF) $(OS_TYPE)
+endif
+
+MKDEF = CC="$(CC)" CFLAGS="$(CFLAGS)"
+
+
+all clean: src/common/GNUmakefile src/login/GNUmakefile src/char_unblocked/GNUmakefile src/map/GNUmakefile src/ladmin/GNUmakefile
+ cd src ; cd common ; $(MAKE) $(MKDEF) $@ ; cd ..
+ cd src ; cd login ; $(MAKE) $(MKDEF) $@ ; cd ..
+ cd src ; cd char_unblocked ; $(MAKE) $(MKDEF) $@ ; cd ..
+ cd src ; cd map ; $(MAKE) $(MKDEF) $@ ; cd ..
+ cd src ; cd ladmin ; $(MAKE) $(MKDEF) $@ ; cd ..
+
+tools:
+ cd tool && $(MAKE) $(MKDEF) && cd ..
+ $(CC) -o setupwizard setupwizard.c
+
+src/common/GNUmakefile: src/common/Makefile
+ sed -e 's/$$>/$$^/' src/common/Makefile > src/common/GNUmakefile
+src/login/GNUmakefile: src/login/Makefile
+ sed -e 's/$$>/$$^/' src/login/Makefile > src/login/GNUmakefile
+src/char_unblocked/GNUmakefile: src/char_unblocked/Makefile
+ sed -e 's/$$>/$$^/' src/char_unblocked/Makefile > src/char_unblocked/GNUmakefile
+src/map/GNUmakefile: src/map/Makefile
+ sed -e 's/$$>/$$^/' src/map/Makefile > src/map/GNUmakefile
+src/ladmin/GNUmakefile: src/ladmin/Makefile
+ sed -e 's/$$>/$$^/' src/ladmin/Makefile > src/ladmin/GNUmakefile
diff --git a/src/char/GNUmakefile b/src/char/GNUmakefile new file mode 100644 index 0000000..dd21288 --- /dev/null +++ b/src/char/GNUmakefile @@ -0,0 +1,17 @@ +all: char-server
+txt: char-server
+
+COMMON_OBJ = ../common/core.o ../common/socket.o ../common/timer.o ../common/db.o ../common/lock.o ../common/malloc.o
+COMMON_H = ../common/core.h ../common/socket.h ../common/timer.h ../common/mmo.h ../common/db.h ../common/lock.h ../common/timer.h ../common/malloc.h
+char-server: char.o inter.o int_party.o int_guild.o int_storage.o int_pet.o $(COMMON_OBJ)
+ $(CC) -o ../../$@ $^
+
+char.o: char.c char.h inter.h int_pet.h $(COMMON_H) ../common/version.h
+inter.o: inter.c inter.h int_party.h int_guild.h int_storage.h int_pet.h char.h $(COMMON_H)
+int_party.o: int_party.c int_party.h inter.h char.h $(COMMON_H)
+int_guild.o: int_guild.c int_guild.h int_storage.h inter.h char.h $(COMMON_H)
+int_storage.o: int_storage.c int_storage.h int_guild.h inter.h char.h $(COMMON_H)
+int_pet.o: int_pet.c int_pet.h inter.h char.h $(COMMON_H)
+
+clean:
+ rm -f *.o ../../char-server
diff --git a/src/char/Makefile b/src/char/Makefile new file mode 100644 index 0000000..6eaae48 --- /dev/null +++ b/src/char/Makefile @@ -0,0 +1,17 @@ +all: char-server
+txt: char-server
+
+COMMON_OBJ = ../common/core.o ../common/socket.o ../common/timer.o ../common/db.o ../common/lock.o ../common/malloc.o
+COMMON_H = ../common/core.h ../common/socket.h ../common/timer.h ../common/mmo.h ../common/db.h ../common/lock.h ../common/timer.h ../common/malloc.h
+char-server: char.o inter.o int_party.o int_guild.o int_storage.o int_pet.o $(COMMON_OBJ)
+ $(CC) -o ../../$@ $>
+
+char.o: char.c char.h inter.h int_pet.h $(COMMON_H) ../common/version.h
+inter.o: inter.c inter.h int_party.h int_guild.h int_storage.h int_pet.h char.h $(COMMON_H)
+int_party.o: int_party.c int_party.h inter.h char.h $(COMMON_H)
+int_guild.o: int_guild.c int_guild.h int_storage.h inter.h char.h $(COMMON_H)
+int_storage.o: int_storage.c int_storage.h int_guild.h inter.h char.h $(COMMON_H)
+int_pet.o: int_pet.c int_pet.h inter.h char.h $(COMMON_H)
+
+clean:
+ rm -f *.o ../../char-server
diff --git a/src/char/char.c b/src/char/char.c new file mode 100644 index 0000000..c279102 --- /dev/null +++ b/src/char/char.c @@ -0,0 +1,3149 @@ +// $Id: char.c,v 1.3 2004/09/13 16:52:16 Yor Exp $ +// original : char2.c 2003/03/14 11:58:35 Rev.1.5 + +#include <sys/types.h> +#include <sys/socket.h> +#include <stdio.h> +#include <stdlib.h> +#include <netinet/in.h> +#include <sys/time.h> +#include <time.h> +#include <sys/ioctl.h> +#include <unistd.h> +#include <signal.h> +#include <fcntl.h> +#include <string.h> +#include <arpa/inet.h> +#include <netdb.h> +#include <stdarg.h> + +#include "core.h" +#include "socket.h" +#include "timer.h" +#include "mmo.h" +#include "version.h" +#include "lock.h" +#include "char.h" + +#include "inter.h" +#include "int_pet.h" +#include "int_guild.h" +#include "int_party.h" +#include "int_storage.h" + +#ifdef MEMWATCH +#include "memwatch.h" +#endif + +struct mmo_map_server server[MAX_MAP_SERVERS]; +int server_fd[MAX_MAP_SERVERS]; +int server_freezeflag[MAX_MAP_SERVERS]; // Map-server anti-freeze system. Counter. 5 ok, 4...0 freezed +int anti_freeze_enable = 0; +int ANTI_FREEZE_INTERVAL = 6; + +int login_fd, char_fd; +char userid[24]; +char passwd[24]; +char server_name[20]; +char wisp_server_name[24] = "Server"; +char login_ip_str[16]; +int login_ip; +int login_port = 6900; +char char_ip_str[16]; +int char_ip; +int char_port = 6121; +int char_maintenance; +int char_new; +int email_creation = 0; // disabled by default +char char_txt[1024]; +char backup_txt[1024]; //By zanetheinsane +char backup_txt_flag = 0; // The backup_txt file was created because char deletion bug existed. Now it's finish and that take a lot of time to create a second file when there are a lot of characters. => option By [Yor] +char unknown_char_name[1024] = "Unknown"; +char char_log_filename[1024] = "log/char.log"; +//Added for lan support +char lan_map_ip[128]; +int subneti[4]; +int subnetmaski[4]; +int name_ignoring_case = 0; // Allow or not identical name for characters but with a different case by [Yor] +int char_name_option = 0; // Option to know which letters/symbols are authorised in the name of a character (0: all, 1: only those in char_name_letters, 2: all EXCEPT those in char_name_letters) by [Yor] +char char_name_letters[1024] = ""; // list of letters/symbols authorised (or not) in a character name. by [Yor] + +struct char_session_data{ + int account_id, login_id1, login_id2, sex; + int found_char[9]; + char email[40]; // e-mail (default: a@a.com) by [Yor] + time_t connect_until_time; // # of seconds 1/1/1970 (timestamp): Validity limit of the account (0 = unlimited) +}; + +#define AUTH_FIFO_SIZE 256 +struct { + int account_id, char_id, login_id1, login_id2, ip, char_pos, delflag, sex; + time_t connect_until_time; // # of seconds 1/1/1970 (timestamp): Validity limit of the account (0 = unlimited) +} auth_fifo[AUTH_FIFO_SIZE]; +int auth_fifo_pos = 0; + +int check_ip_flag = 1; // It's to check IP of a player between char-server and other servers (part of anti-hacking system) + +int char_id_count = 150000; +struct mmo_charstatus *char_dat; +int char_num, char_max; +int max_connect_user = 0; +int autosave_interval = DEFAULT_AUTOSAVE_INTERVAL; +int start_zeny = 500; +int start_weapon = 1201; +int start_armor = 1202; + +// Initial position (it's possible to set it in conf file) +struct point start_point = {"new_1-1.gat", 53, 111}; + +struct gm_account *gm_account = NULL; +int GM_num = 0; + +// online players by [Yor] +char online_txt_filename[1024] = "online.txt"; +char online_html_filename[1024] = "online.html"; +int online_sorting_option = 0; // sorting option to display online players in online files +int online_display_option = 1; // display options: to know which columns must be displayed +int online_refresh_html = 20; // refresh time (in sec) of the html file in the explorer +int online_gm_display_min_level = 20; // minimum GM level to display 'GM' when we want to display it + +int *online_chars; // same size of char_dat, and id value of current server (or -1) +time_t update_online; // to update online files when we receiving information from a server (not less than 8 seconds) + +//------------------------------ +// Writing function of logs file +//------------------------------ +int char_log(char *fmt, ...) { + FILE *logfp; + va_list ap; + struct timeval tv; + char tmpstr[2048]; + + va_start(ap, fmt); + + logfp = fopen(char_log_filename, "a"); + if (logfp) { + if (fmt[0] == '\0') // jump a line if no message + fprintf(logfp, RETCODE); + else { + gettimeofday(&tv, NULL); + strftime(tmpstr, 24, "%d-%m-%Y %H:%M:%S", localtime(&(tv.tv_sec))); + sprintf(tmpstr + 19, ".%03d: %s", (int)tv.tv_usec / 1000, fmt); + vfprintf(logfp, tmpstr, ap); + } + fclose(logfp); + } + + va_end(ap); + return 0; +} + +//----------------------------------------------------- +// Function to suppress control characters in a string. +//----------------------------------------------------- +int remove_control_chars(unsigned char *str) { + int i; + int change = 0; + + for(i = 0; str[i]; i++) { + if (str[i] < 32) { + str[i] = '_'; + change = 1; + } + } + + return change; +} + +//---------------------------------------------------------------------- +// Determine if an account (id) is a GM account +// and returns its level (or 0 if it isn't a GM account or if not found) +//---------------------------------------------------------------------- +int isGM(int account_id) { + int i; + + for(i = 0; i < GM_num; i++) + if (gm_account[i].account_id == account_id) + return gm_account[i].level; + return 0; +} + +//---------------------------------------------- +// Search an character id +// (return character index or -1 (if not found)) +// If exact character name is not found, +// the function checks without case sensitive +// and returns index if only 1 character is found +// and similar to the searched name. +//---------------------------------------------- +int search_character_index(char* character_name) { + int i, quantity, index; + + quantity = 0; + index = -1; + for(i = 0; i < char_num; i++) { + // Without case sensitive check (increase the number of similar character names found) + if (stricmp(char_dat[i].name, character_name) == 0) { + // Strict comparison (if found, we finish the function immediatly with correct value) + if (strcmp(char_dat[i].name, character_name) == 0) + return i; + quantity++; + index = i; + } + } + // Here, the exact character name is not found + // We return the found index of a similar account ONLY if there is 1 similar character + if (quantity == 1) + return index; + + // Exact character name is not found and 0 or more than 1 similar characters have been found ==> we say not found + return -1; +} + +//------------------------------------- +// Return character name with the index +//------------------------------------- +char * search_character_name(int index) { + + if (index >= 0 && index < char_num) + return char_dat[index].name; + + return unknown_char_name; +} + +//------------------------------------------------- +// Function to create the character line (for save) +//------------------------------------------------- +int mmo_char_tostr(char *str, struct mmo_charstatus *p) { + int i; + char *str_p = str; + + // on multi-map server, sometimes it's posssible that last_point become void. (reason???) We check that to not lost character at restart. + if (p->last_point.map[0] == '\0') { + memcpy(p->last_point.map, "prontera.gat", 16); + p->last_point.x = 273; + p->last_point.y = 354; + } + + str_p += sprintf(str_p, "%d\t%d,%d\t%s\t%d,%d,%d\t%d,%d,%d\t%d,%d,%d,%d\t%d,%d,%d,%d,%d,%d\t%d,%d" + "\t%d,%d,%d\t%d,%d,%d\t%d,%d,%d\t%d,%d,%d,%d,%d" + "\t%s,%d,%d\t%s,%d,%d,%d\t", + p->char_id, p->account_id, p->char_num, p->name, // + p->class, p->base_level, p->job_level, + p->base_exp, p->job_exp, p->zeny, + p->hp, p->max_hp, p->sp, p->max_sp, + p->str, p->agi, p->vit, p->int_, p->dex, p->luk, + p->status_point, p->skill_point, + p->option, p->karma, p->manner, // + p->party_id, p->guild_id, p->pet_id, + p->hair, p->hair_color, p->clothes_color, + p->weapon, p->shield, p->head_top, p->head_mid, p->head_bottom, + p->last_point.map, p->last_point.x, p->last_point.y, // + p->save_point.map, p->save_point.x, p->save_point.y, + p->partner_id); + for(i = 0; i < 10; i++) + if (p->memo_point[i].map[0]) { + str_p += sprintf(str_p, "%s,%d,%d", p->memo_point[i].map, p->memo_point[i].x, p->memo_point[i].y); + } + *(str_p++) = '\t'; + + for(i = 0; i < MAX_INVENTORY; i++) + if (p->inventory[i].nameid) { + str_p += sprintf(str_p, "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d ", + p->inventory[i].id, p->inventory[i].nameid, p->inventory[i].amount, p->inventory[i].equip, + p->inventory[i].identify, p->inventory[i].refine, p->inventory[i].attribute, + p->inventory[i].card[0], p->inventory[i].card[1], p->inventory[i].card[2], p->inventory[i].card[3], + p->inventory[i].broken); + } + *(str_p++) = '\t'; + + for(i = 0; i < MAX_CART; i++) + if (p->cart[i].nameid) { + str_p += sprintf(str_p, "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d ", + p->cart[i].id, p->cart[i].nameid, p->cart[i].amount, p->cart[i].equip, + p->cart[i].identify, p->cart[i].refine, p->cart[i].attribute, + p->cart[i].card[0], p->cart[i].card[1], p->cart[i].card[2], p->cart[i].card[3], + p->cart[i].broken); + } + *(str_p++) = '\t'; + + for(i = 0; i < MAX_SKILL; i++) + if (p->skill[i].id && p->skill[i].flag != 1) { + str_p += sprintf(str_p, "%d,%d ", p->skill[i].id, (p->skill[i].flag == 0) ? p->skill[i].lv : p->skill[i].flag-2); + } + *(str_p++) = '\t'; + + for(i = 0; i < p->global_reg_num; i++) + if (p->global_reg[i].str[0]) + str_p += sprintf(str_p, "%s,%d ", p->global_reg[i].str, p->global_reg[i].value); + *(str_p++) = '\t'; + + *str_p = '\0'; + return 0; +} + +//------------------------------------------------------------------------- +// Function to set the character from the line (at read of characters file) +//------------------------------------------------------------------------- +int mmo_char_fromstr(char *str, struct mmo_charstatus *p) { + int tmp_int[256]; + int set, next, len, i; + + // initilialise character + memset(p, '\0', sizeof(struct mmo_charstatus)); + + // If it's not char structure of version 1008 and after + if ((set = sscanf(str, "%d\t%d,%d\t%[^\t]\t%d,%d,%d\t%d,%d,%d\t%d,%d,%d,%d\t%d,%d,%d,%d,%d,%d\t%d,%d" + "\t%d,%d,%d\t%d,%d,%d\t%d,%d,%d\t%d,%d,%d,%d,%d" + "\t%[^,],%d,%d\t%[^,],%d,%d,%d%n", + &tmp_int[0], &tmp_int[1], &tmp_int[2], p->name, // + &tmp_int[3], &tmp_int[4], &tmp_int[5], + &tmp_int[6], &tmp_int[7], &tmp_int[8], + &tmp_int[9], &tmp_int[10], &tmp_int[11], &tmp_int[12], + &tmp_int[13], &tmp_int[14], &tmp_int[15], &tmp_int[16], &tmp_int[17], &tmp_int[18], + &tmp_int[19], &tmp_int[20], + &tmp_int[21], &tmp_int[22], &tmp_int[23], // + &tmp_int[24], &tmp_int[25], &tmp_int[26], + &tmp_int[27], &tmp_int[28], &tmp_int[29], + &tmp_int[30], &tmp_int[31], &tmp_int[32], &tmp_int[33], &tmp_int[34], + p->last_point.map, &tmp_int[35], &tmp_int[36], // + p->save_point.map, &tmp_int[37], &tmp_int[38], &tmp_int[39], &next)) != 43) { + tmp_int[39] = 0; // partner id + // If not char structure from version 384 to 1007 + if ((set = sscanf(str, "%d\t%d,%d\t%[^\t]\t%d,%d,%d\t%d,%d,%d\t%d,%d,%d,%d\t%d,%d,%d,%d,%d,%d\t%d,%d" + "\t%d,%d,%d\t%d,%d,%d\t%d,%d,%d\t%d,%d,%d,%d,%d" + "\t%[^,],%d,%d\t%[^,],%d,%d%n", + &tmp_int[0], &tmp_int[1], &tmp_int[2], p->name, // + &tmp_int[3], &tmp_int[4], &tmp_int[5], + &tmp_int[6], &tmp_int[7], &tmp_int[8], + &tmp_int[9], &tmp_int[10], &tmp_int[11], &tmp_int[12], + &tmp_int[13], &tmp_int[14], &tmp_int[15], &tmp_int[16], &tmp_int[17], &tmp_int[18], + &tmp_int[19], &tmp_int[20], + &tmp_int[21], &tmp_int[22], &tmp_int[23], // + &tmp_int[24], &tmp_int[25], &tmp_int[26], + &tmp_int[27], &tmp_int[28], &tmp_int[29], + &tmp_int[30], &tmp_int[31], &tmp_int[32], &tmp_int[33], &tmp_int[34], + p->last_point.map, &tmp_int[35], &tmp_int[36], // + p->save_point.map, &tmp_int[37], &tmp_int[38], &next)) != 42) { + // It's char structure of a version before 384 + tmp_int[26] = 0; // pet id + set = sscanf(str, "%d\t%d,%d\t%[^\t]\t%d,%d,%d\t%d,%d,%d\t%d,%d,%d,%d\t%d,%d,%d,%d,%d,%d\t%d,%d" + "\t%d,%d,%d\t%d,%d\t%d,%d,%d\t%d,%d,%d,%d,%d" + "\t%[^,],%d,%d\t%[^,],%d,%d%n", + &tmp_int[0], &tmp_int[1], &tmp_int[2], p->name, // + &tmp_int[3], &tmp_int[4], &tmp_int[5], + &tmp_int[6], &tmp_int[7], &tmp_int[8], + &tmp_int[9], &tmp_int[10], &tmp_int[11], &tmp_int[12], + &tmp_int[13], &tmp_int[14], &tmp_int[15], &tmp_int[16], &tmp_int[17], &tmp_int[18], + &tmp_int[19], &tmp_int[20], + &tmp_int[21], &tmp_int[22], &tmp_int[23], // + &tmp_int[24], &tmp_int[25], // + &tmp_int[27], &tmp_int[28], &tmp_int[29], + &tmp_int[30], &tmp_int[31], &tmp_int[32], &tmp_int[33], &tmp_int[34], + p->last_point.map, &tmp_int[35], &tmp_int[36], // + p->save_point.map, &tmp_int[37], &tmp_int[38], &next); + set += 2; + //printf("char: old char data ver.1\n"); + // Char structure of version 1007 or older + } else { + set++; + //printf("char: old char data ver.2\n"); + } + // Char structure of version 1008+ + } else { + //printf("char: new char data ver.3\n"); + } + if (set != 43) + return 0; + + p->char_id = tmp_int[0]; + p->account_id = tmp_int[1]; + p->char_num = tmp_int[2]; + p->class = tmp_int[3]; + p->base_level = tmp_int[4]; + p->job_level = tmp_int[5]; + p->base_exp = tmp_int[6]; + p->job_exp = tmp_int[7]; + p->zeny = tmp_int[8]; + p->hp = tmp_int[9]; + p->max_hp = tmp_int[10]; + p->sp = tmp_int[11]; + p->max_sp = tmp_int[12]; + p->str = tmp_int[13]; + p->agi = tmp_int[14]; + p->vit = tmp_int[15]; + p->int_ = tmp_int[16]; + p->dex = tmp_int[17]; + p->luk = tmp_int[18]; + p->status_point = tmp_int[19]; + p->skill_point = tmp_int[20]; + p->option = tmp_int[21]; + p->karma = tmp_int[22]; + p->manner = tmp_int[23]; + p->party_id = tmp_int[24]; + p->guild_id = tmp_int[25]; + p->pet_id = tmp_int[26]; + p->hair = tmp_int[27]; + p->hair_color = tmp_int[28]; + p->clothes_color = tmp_int[29]; + p->weapon = tmp_int[30]; + p->shield = tmp_int[31]; + p->head_top = tmp_int[32]; + p->head_mid = tmp_int[33]; + p->head_bottom = tmp_int[34]; + p->last_point.x = tmp_int[35]; + p->last_point.y = tmp_int[36]; + p->save_point.x = tmp_int[37]; + p->save_point.y = tmp_int[38]; + p->partner_id = tmp_int[39]; + + // Some checks + for(i = 0; i < char_num; i++) { + if (char_dat[i].char_id == p->char_id) { + printf("\033[1;31mmmo_auth_init: ******Error: a character has an identical id to another.\n"); + printf(" character id #%d -> new character not readed.\n", p->char_id); + printf(" Character saved in log file.\033[0m\n"); + return -1; + } else if (strcmp(char_dat[i].name, p->name) == 0) { + printf("\033[1;31mmmo_auth_init: ******Error: character name already exists.\n"); + printf(" character name '%s' -> new character not readed.\n", p->name); + printf(" Character saved in log file.\033[0m\n"); + return -2; + } + } + + if (strcmpi(wisp_server_name, p->name) == 0) { + printf("mmo_auth_init: ******WARNING: character name has wisp server name.\n"); + printf(" Character name '%s' = wisp server name '%s'.\n", p->name, wisp_server_name); + printf(" Character readed. Suggestion: change the wisp server name.\n"); + char_log("mmo_auth_init: ******WARNING: character name has wisp server name: Character name '%s' = wisp server name '%s'." RETCODE, + p->name, wisp_server_name); + } + + if (str[next] == '\n' || str[next] == '\r') + return 1; // 新規データ + + next++; + + for(i = 0; str[next] && str[next] != '\t'; i++) { + if (sscanf(str+next, "%[^,],%d,%d%n", p->memo_point[i].map, &tmp_int[0], &tmp_int[1], &len) != 3) + return -3; + p->memo_point[i].x = tmp_int[0]; + p->memo_point[i].y = tmp_int[1]; + next += len; + if (str[next] == ' ') + next++; + } + + next++; + + for(i = 0; str[next] && str[next] != '\t'; i++) { + if (sscanf(str + next, "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d%n", + &tmp_int[0], &tmp_int[1], &tmp_int[2], &tmp_int[3], + &tmp_int[4], &tmp_int[5], &tmp_int[6], + &tmp_int[7], &tmp_int[8], &tmp_int[9], &tmp_int[10], &tmp_int[11], &len) == 12) { + // do nothing, it's ok + } else if (sscanf(str + next, "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d%n", + &tmp_int[0], &tmp_int[1], &tmp_int[2], &tmp_int[3], + &tmp_int[4], &tmp_int[5], &tmp_int[6], + &tmp_int[7], &tmp_int[8], &tmp_int[9], &tmp_int[10], &len) == 11) { + tmp_int[11] = 0; // broken doesn't exist in this version -> 0 + } else // invalid structure + return -4; + p->inventory[i].id = tmp_int[0]; + p->inventory[i].nameid = tmp_int[1]; + p->inventory[i].amount = tmp_int[2]; + p->inventory[i].equip = tmp_int[3]; + p->inventory[i].identify = tmp_int[4]; + p->inventory[i].refine = tmp_int[5]; + p->inventory[i].attribute = tmp_int[6]; + p->inventory[i].card[0] = tmp_int[7]; + p->inventory[i].card[1] = tmp_int[8]; + p->inventory[i].card[2] = tmp_int[9]; + p->inventory[i].card[3] = tmp_int[10]; + p->inventory[i].broken = tmp_int[11]; + next += len; + if (str[next] == ' ') + next++; + } + + next++; + + for(i = 0; str[next] && str[next] != '\t'; i++) { + if (sscanf(str + next, "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d%n", + &tmp_int[0], &tmp_int[1], &tmp_int[2], &tmp_int[3], + &tmp_int[4], &tmp_int[5], &tmp_int[6], + &tmp_int[7], &tmp_int[8], &tmp_int[9], &tmp_int[10], &tmp_int[11], &len) == 12) { + // do nothing, it's ok + } else if (sscanf(str + next, "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d%n", + &tmp_int[0], &tmp_int[1], &tmp_int[2], &tmp_int[3], + &tmp_int[4], &tmp_int[5], &tmp_int[6], + &tmp_int[7], &tmp_int[8], &tmp_int[9], &tmp_int[10], &len) == 11) { + tmp_int[11] = 0; // broken doesn't exist in this version -> 0 + } else // invalid structure + return -5; + p->cart[i].id = tmp_int[0]; + p->cart[i].nameid = tmp_int[1]; + p->cart[i].amount = tmp_int[2]; + p->cart[i].equip = tmp_int[3]; + p->cart[i].identify = tmp_int[4]; + p->cart[i].refine = tmp_int[5]; + p->cart[i].attribute = tmp_int[6]; + p->cart[i].card[0] = tmp_int[7]; + p->cart[i].card[1] = tmp_int[8]; + p->cart[i].card[2] = tmp_int[9]; + p->cart[i].card[3] = tmp_int[10]; + p->cart[i].broken = tmp_int[11]; + next += len; + if (str[next] == ' ') + next++; + } + + next++; + + for(i = 0; str[next] && str[next] != '\t'; i++) { + if (sscanf(str + next, "%d,%d%n", &tmp_int[0], &tmp_int[1], &len) != 2) + return -6; + p->skill[tmp_int[0]].id = tmp_int[0]; + p->skill[tmp_int[0]].lv = tmp_int[1]; + next += len; + if (str[next] == ' ') + next++; + } + + next++; + + for(i = 0; str[next] && str[next] != '\t' && str[next] != '\n' && str[next] != '\r'; i++) { // global_reg実装以前のathena.txt互換のため一応'\n'チェック + if (sscanf(str + next, "%[^,],%d%n", p->global_reg[i].str, &p->global_reg[i].value, &len) != 2) { + // because some scripts are not correct, the str can be "". So, we must check that. + // If it's, we must not refuse the character, but just this REG value. + // Character line will have something like: nov_2nd_cos,9 ,9 nov_1_2_cos_c,1 (here, ,9 is not good) + if (str[next] == ',' && sscanf(str + next, ",%d%n", &p->global_reg[i].value, &len) == 1) + i--; + else + return -7; + } + next += len; + if (str[next] == ' ') + next++; + } + p->global_reg_num = i; + + return 1; +} + +//--------------------------------- +// Function to read characters file +//--------------------------------- +int mmo_char_init(void) { + char line[65536]; + int i; + int ret, line_count; + FILE *fp; + + char_max = 256; + char_dat = calloc(sizeof(struct mmo_charstatus) * 256, 1); + if (!char_dat) { + printf("out of memory: mmo_char_init (calloc of char_dat).\n"); + exit(1); + } + online_chars = calloc(sizeof(int) * 256, 1); + if (!online_chars) { + printf("out of memory: mmo_char_init (calloc of online_chars).\n"); + exit(1); + } + for(i = 0; i < char_max; i++) + online_chars[i] = -1; + + char_num = 0; + + fp = fopen(char_txt, "r"); + if (fp == NULL) { + printf("Characters file not found: %s.\n", char_txt); + char_log("Characters file not found: %s." RETCODE, char_txt); + char_log("Id for the next created character: %d." RETCODE, char_id_count); + return 0; + } + + line_count = 0; + while(fgets(line, sizeof(line)-1, fp)) { + int i, j; + line_count++; + + if (line[0] == '/' && line[1] == '/') + continue; + line[sizeof(line)-1] = '\0'; + + j = 0; + if (sscanf(line, "%d\t%%newid%%%n", &i, &j) == 1 && j > 0) { + if (char_id_count < i) + char_id_count = i; + continue; + } + + if (char_num >= char_max) { + char_max += 256; + char_dat = realloc(char_dat, sizeof(struct mmo_charstatus) * char_max); + if (!char_dat) { + printf("Out of memory: mmo_char_init (realloc of char_dat).\n"); + char_log("Out of memory: mmo_char_init (realloc of char_dat)." RETCODE); + exit(1); + } + online_chars = realloc(online_chars, sizeof(int) * char_max); + if (!online_chars) { + printf("Out of memory: mmo_char_init (realloc of online_chars).\n"); + char_log("Out of memory: mmo_char_init (realloc of online_chars)." RETCODE); + exit(1); + } + for(i = char_max - 256; i < char_max; i++) + online_chars[i] = -1; + } + + ret = mmo_char_fromstr(line, &char_dat[char_num]); + if (ret > 0) { // negative value or zero for errors + if (char_dat[char_num].char_id >= char_id_count) + char_id_count = char_dat[char_num].char_id + 1; + char_num++; + } else { + printf("mmo_char_init: in characters file, unable to read the line #%d.\n", line_count); + printf(" -> Character saved in log file.\n"); + switch (ret) { + case -1: + char_log("Duplicate character id in the next character line (character not readed):" RETCODE); + break; + case -2: + char_log("Duplicate character name in the next character line (character not readed):" RETCODE); + break; + case -3: + char_log("Invalid memo point structure in the next character line (character not readed):" RETCODE); + break; + case -4: + char_log("Invalid inventory item structure in the next character line (character not readed):" RETCODE); + break; + case -5: + char_log("Invalid cart item structure in the next character line (character not readed):" RETCODE); + break; + case -6: + char_log("Invalid skill structure in the next character line (character not readed):" RETCODE); + break; + case -7: + char_log("Invalid register structure in the next character line (character not readed):" RETCODE); + break; + default: // 0 + char_log("Unabled to get a character in the next line - Basic structure of line (before inventory) is incorrect (character not readed):" RETCODE); + break; + } + char_log("%s", line); + } + } + fclose(fp); + + if (char_num == 0) { + printf("mmo_char_init: No character found in %s.\n", char_txt); + char_log("mmo_char_init: No character found in %s." RETCODE, char_txt); + } else if (char_num == 1) { + printf("mmo_char_init: 1 character read in %s.\n", char_txt); + char_log("mmo_char_init: 1 character read in %s." RETCODE, char_txt); + } else { + printf("mmo_char_init: %d characters read in %s.\n", char_num, char_txt); + char_log("mmo_char_init: %d characters read in %s." RETCODE, char_num, char_txt); + } + + char_log("Id for the next created character: %d." RETCODE, char_id_count); + + return 0; +} + +//--------------------------------------------------------- +// Function to save characters in files (speed up by [Yor]) +//--------------------------------------------------------- +void mmo_char_sync(void) { + char line[65536]; + int i, j, k; + int lock; + FILE *fp; + int id[char_num]; + + // Sorting before save (by [Yor]) + for(i = 0; i < char_num; i++) { + id[i] = i; + for(j = 0; j < i; j++) { + if ((char_dat[i].account_id < char_dat[id[j]].account_id) || + // if same account id, we sort by slot. + (char_dat[i].account_id == char_dat[id[j]].account_id && + char_dat[i].char_num < char_dat[id[j]].char_num)) { + for(k = i; k > j; k--) + id[k] = id[k-1]; + id[j] = i; // id[i] + break; + } + } + } + + // Data save + fp = lock_fopen(char_txt, &lock); + if (fp == NULL) { + printf("WARNING: Server can't not save characters.\n"); + char_log("WARNING: Server can't not save characters." RETCODE); + } else { + for(i = 0; i < char_num; i++) { + // create only once the line, and save it in the 2 files (it's speeder than repeat twice the loop and create twice the line) + mmo_char_tostr(line, &char_dat[id[i]]); // use of sorted index + fprintf(fp, "%s" RETCODE, line); + } + fprintf(fp, "%d\t%%newid%%" RETCODE, char_id_count); + lock_fclose(fp, char_txt, &lock); + } + + // Data save (backup) + if (backup_txt_flag) { // The backup_txt file was created because char deletion bug existed. Now it's finish and that take a lot of time to create a second file when there are a lot of characters. => option By [Yor] + fp = lock_fopen(backup_txt, &lock); + if (fp == NULL) { + printf("WARNING: Server can't not create backup of characters file.\n"); + char_log("WARNING: Server can't not create backup of characters file." RETCODE); + return; + } + for(i = 0; i < char_num; i++) { + // create only once the line, and save it in the 2 files (it's speeder than repeat twice the loop and create twice the line) + mmo_char_tostr(line, &char_dat[id[i]]); // use of sorted index + fprintf(fp, "%s" RETCODE, line); + } + fprintf(fp, "%d\t%%newid%%" RETCODE, char_id_count); + lock_fclose(fp, backup_txt, &lock); + } + + return; +} + +//---------------------------------------------------- +// Function to save (in a periodic way) datas in files +//---------------------------------------------------- +int mmo_char_sync_timer(int tid, unsigned int tick, int id, int data) { + mmo_char_sync(); + inter_save(); + return 0; +} + +//----------------------------------- +// Function to create a new character +//----------------------------------- +int make_new_char(int fd, unsigned char *dat) { + int i, j; + struct char_session_data *sd; + + sd = session[fd]->session_data; + + // remove control characters from the name + dat[23] = '\0'; + if (remove_control_chars(dat)) { + char_log("Make new char error (control char received in the name): (connection #%d, account: %d)." RETCODE, + fd, sd->account_id); + return -1; + } + + // check lenght of character name + if (strlen(dat) < 4) { + char_log("Make new char error (character name too small): (connection #%d, account: %d, name: '%s')." RETCODE, + fd, sd->account_id, dat); + return -1; + } + + // Check Authorised letters/symbols in the name of the character + if (char_name_option == 1) { // only letters/symbols in char_name_letters are authorised + for (i = 0; dat[i]; i++) + if (strchr(char_name_letters, dat[i]) == NULL) { + char_log("Make new char error (invalid letter in the name): (connection #%d, account: %d), name: %s, invalid letter: %c." RETCODE, + fd, sd->account_id, dat, dat[i]); + return -1; + } + } else if (char_name_option == 2) { // letters/symbols in char_name_letters are forbidden + for (i = 0; dat[i]; i++) + if (strchr(char_name_letters, dat[i]) != NULL) { + char_log("Make new char error (invalid letter in the name): (connection #%d, account: %d), name: %s, invalid letter: %c." RETCODE, + fd, sd->account_id, dat, dat[i]); + return -1; + } + } // else, all letters/symbols are authorised (except control char removed before) + + if (dat[24] + dat[25] + dat[26] + dat[27] + dat[28] + dat[29] != 5*6 || // stats + dat[30] >= 9 || // slots (dat[30] can not be negativ) + dat[33] <= 0 || dat[33] >= 20 || // hair style + dat[31] >= 12) { // hair color (dat[31] can not be negativ) + char_log("Make new char error (invalid values): (connection #%d, account: %d) slot %d, name: %s, stats: %d+%d+%d+%d+%d+%d=%d, hair: %d, hair color: %d" RETCODE, + fd, sd->account_id, dat[30], dat, dat[24], dat[25], dat[26], dat[27], dat[28], dat[29], dat[24] + dat[25] + dat[26] + dat[27] + dat[28] + dat[29], dat[33], dat[31]); + return -1; + } + + // check individual stat value + for(i = 24; i <= 29; i++) { + if (dat[i] < 1 || dat[i] > 9) { + char_log("Make new char error (invalid stat value: not between 1 to 9): (connection #%d, account: %d) slot %d, name: %s, stats: %d+%d+%d+%d+%d+%d=%d, hair: %d, hair color: %d" RETCODE, + fd, sd->account_id, dat[30], dat, dat[24], dat[25], dat[26], dat[27], dat[28], dat[29], dat[24] + dat[25] + dat[26] + dat[27] + dat[28] + dat[29], dat[33], dat[31]); + return -1; + } + } + + for(i = 0; i < char_num; i++) { + if ((name_ignoring_case != 0 && strcmp(char_dat[i].name, dat) == 0) || + (name_ignoring_case == 0 && strcmpi(char_dat[i].name, dat) == 0)) { + char_log("Make new char error (name already exists): (connection #%d, account: %d) slot %d, name: %s (actual name of other char: %d), stats: %d+%d+%d+%d+%d+%d=%d, hair: %d, hair color: %d." RETCODE, + fd, sd->account_id, dat[30], dat, char_dat[i].name, dat[24], dat[25], dat[26], dat[27], dat[28], dat[29], dat[24] + dat[25] + dat[26] + dat[27] + dat[28] + dat[29], dat[33], dat[31]); + return -1; + } + if (char_dat[i].account_id == sd->account_id && char_dat[i].char_num == dat[30]) { + char_log("Make new char error (slot already used): (connection #%d, account: %d) slot %d, name: %s (actual name of other char: %d), stats: %d+%d+%d+%d+%d+%d=%d, hair: %d, hair color: %d." RETCODE, + fd, sd->account_id, dat[30], dat, char_dat[i].name, dat[24], dat[25], dat[26], dat[27], dat[28], dat[29], dat[24] + dat[25] + dat[26] + dat[27] + dat[28] + dat[29], dat[33], dat[31]); + return -1; + } + } + + if (strcmp(wisp_server_name, dat) == 0) { + char_log("Make new char error (name used is wisp name for server): (connection #%d, account: %d) slot %d, name: %s (actual name of other char: %d), stats: %d+%d+%d+%d+%d+%d=%d, hair: %d, hair color: %d." RETCODE, + fd, sd->account_id, dat[30], dat, char_dat[i].name, dat[24], dat[25], dat[26], dat[27], dat[28], dat[29], dat[24] + dat[25] + dat[26] + dat[27] + dat[28] + dat[29], dat[33], dat[31]); + return -1; + } + + if (char_num >= char_max) { + char_max += 256; + char_dat = realloc(char_dat, sizeof(struct mmo_charstatus) * char_max); + if (!char_dat) { + printf("Out of memory: make_new_char (realloc of char_dat).\n"); + char_log("Out of memory: make_new_char (realloc of char_dat)." RETCODE); + exit(1); + } + online_chars = realloc(online_chars, sizeof(int) * char_max); + if (!online_chars) { + printf("Out of memory: make_new_char (realloc of online_chars).\n"); + char_log("Out of memory: make_new_char (realloc of online_chars)." RETCODE); + exit(1); + } + for(j = char_max - 256; j < char_max; j++) + online_chars[j] = -1; + } + + char_log("Creation of New Character: (connection #%d, account: %d) slot %d, character Name: %s, stats: %d+%d+%d+%d+%d+%d=%d, hair: %d, hair color: %d." RETCODE, + fd, sd->account_id, dat[30], dat, dat[24], dat[25], dat[26], dat[27], dat[28], dat[29], dat[24] + dat[25] + dat[26] + dat[27] + dat[28] + dat[29], dat[33], dat[31]); + + memset(&char_dat[i], 0, sizeof(struct mmo_charstatus)); + + char_dat[i].char_id = char_id_count++; + char_dat[i].account_id = sd->account_id; + char_dat[i].char_num = dat[30]; + strcpy(char_dat[i].name, dat); + char_dat[i].class = 0; + char_dat[i].base_level = 1; + char_dat[i].job_level = 1; + char_dat[i].base_exp = 0; + char_dat[i].job_exp = 0; + char_dat[i].zeny = start_zeny; + char_dat[i].str = dat[24]; + char_dat[i].agi = dat[25]; + char_dat[i].vit = dat[26]; + char_dat[i].int_ = dat[27]; + char_dat[i].dex = dat[28]; + char_dat[i].luk = dat[29]; + char_dat[i].max_hp = 40 * (100 + char_dat[i].vit) / 100; + char_dat[i].max_sp = 11 * (100 + char_dat[i].int_) / 100; + char_dat[i].hp = char_dat[i].max_hp; + char_dat[i].sp = char_dat[i].max_sp; + char_dat[i].status_point = 0; + char_dat[i].skill_point = 0; + char_dat[i].option = 0; + char_dat[i].karma = 0; + char_dat[i].manner = 0; + char_dat[i].party_id = 0; + char_dat[i].guild_id = 0; + char_dat[i].hair = dat[33]; + char_dat[i].hair_color = dat[31]; + char_dat[i].clothes_color = 0; + char_dat[i].inventory[0].nameid = start_weapon; // Knife + char_dat[i].inventory[0].amount = 1; + char_dat[i].inventory[0].equip = 0x02; + char_dat[i].inventory[0].identify = 1; + char_dat[i].inventory[0].broken = 0; + char_dat[i].inventory[1].nameid = start_armor; // Cotton Shirt + char_dat[i].inventory[1].amount = 1; + char_dat[i].inventory[1].equip = 0x10; + char_dat[i].inventory[1].identify = 1; + char_dat[i].inventory[1].broken = 0; + char_dat[i].weapon = 1; + char_dat[i].shield = 0; + char_dat[i].head_top = 0; + char_dat[i].head_mid = 0; + char_dat[i].head_bottom = 0; + memcpy(&char_dat[i].last_point, &start_point, sizeof(start_point)); + memcpy(&char_dat[i].save_point, &start_point, sizeof(start_point)); + char_num++; + + mmo_char_sync(); + return i; +} + +//---------------------------------------------------- +// This function return the name of the job (by [Yor]) +//---------------------------------------------------- +char * job_name(int class) { + switch (class) { + case 0: return "Novice"; + case 1: return "Swordsman"; + case 2: return "Mage"; + case 3: return "Archer"; + case 4: return "Acolyte"; + case 5: return "Merchant"; + case 6: return "Thief"; + case 7: return "Knight"; + case 8: return "Priest"; + case 9: return "Wizard"; + case 10: return "Blacksmith"; + case 11: return "Hunter"; + case 12: return "Assassin"; + case 13: return "Knight 2"; + case 14: return "Crusader"; + case 15: return "Monk"; + case 16: return "Sage"; + case 17: return "Rogue"; + case 18: return "Alchemist"; + case 19: return "Bard"; + case 20: return "Dancer"; + case 21: return "Crusader 2"; + case 22: return "Wedding"; + case 23: return "Super Novice"; + case 4001: return "Novice High"; + case 4002: return "Swordsman High"; + case 4003: return "Mage High"; + case 4004: return "Archer High"; + case 4005: return "Acolyte High"; + case 4006: return "Merchant High"; + case 4007: return "Thief High"; + case 4008: return "Lord Knight"; + case 4009: return "High Priest"; + case 4010: return "High Wizard"; + case 4011: return "Whitesmith"; + case 4012: return "Sniper"; + case 4013: return "Assassin Cross"; + case 4014: return "Peko Knight"; + case 4015: return "Paladin"; + case 4016: return "Champion"; + case 4017: return "Professor"; + case 4018: return "Stalker"; + case 4019: return "Creator"; + case 4020: return "Clown"; + case 4021: return "Gypsy"; + case 4022: return "Peko Paladin"; + case 4023: return "Baby Novice"; + case 4024: return "Baby Swordsman"; + case 4025: return "Baby Mage"; + case 4026: return "Baby Archer"; + case 4027: return "Baby Acolyte"; + case 4028: return "Baby Merchant"; + case 4029: return "Baby Thief"; + case 4030: return "Baby Knight"; + case 4031: return "Baby Priest"; + case 4032: return "Baby Wizard"; + case 4033: return "Baby Blacksmith"; + case 4034: return "Baby Hunter"; + case 4035: return "Baby Assassin"; + case 4036: return "Baby Peco Knight"; + case 4037: return "Baby Crusader"; + case 4038: return "Baby Monk"; + case 4039: return "Baby Sage"; + case 4040: return "Baby Rogue"; + case 4041: return "Baby Alchemist"; + case 4042: return "Baby Bard"; + case 4043: return "Baby Dancer"; + case 4044: return "Baby Peco Crusader"; + case 4045: return "Super Baby"; + } + return "Unknown Job"; +} + +//------------------------------------------------------------- +// Function to create the online files (txt and html). by [Yor] +//------------------------------------------------------------- +void create_online_files(void) { + int i, j, k, l; // for loops + int players; // count the number of players + FILE *fp; // for the txt file + FILE *fp2; // for the html file + char temp[256]; // to prepare what we must display + time_t time_server; // for number of seconds + struct tm *datetime; // variable for time in structure ->tm_mday, ->tm_sec, ... + int id[char_num]; + + if (online_display_option == 0) // we display nothing, so return + return; + + //char_log("Creation of online players files." RETCODE); + + // Get number of online players, id of each online players + players = 0; + // sort online characters. + for(i = 0; i < char_num; i++) { + if (online_chars[i] != -1) { + id[players] = i; + // use sorting option + switch (online_sorting_option) { + case 1: // by name (without case sensitive) + { + char *p_name = char_dat[i].name; //speed up sorting when there are a lot of players. But very rarely players have same name. + for(j = 0; j < players; j++) + if (stricmp(p_name, char_dat[id[j]].name) < 0 || + // if same name, we sort with case sensitive. + (stricmp(p_name, char_dat[id[j]].name) == 0 && + strcmp(p_name, char_dat[id[j]].name) < 0)) { + for(k = players; k > j; k--) + id[k] = id[k-1]; + id[j] = i; // id[players] + break; + } + } + break; + case 2: // by zeny + for(j = 0; j < players; j++) + if (char_dat[i].zeny < char_dat[id[j]].zeny || + // if same number of zenys, we sort by name. + (char_dat[i].zeny == char_dat[id[j]].zeny && + stricmp(char_dat[i].name, char_dat[id[j]].name) < 0)) { + for(k = players; k > j; k--) + id[k] = id[k-1]; + id[j] = i; // id[players] + break; + } + break; + case 3: // by base level + for(j = 0; j < players; j++) + if (char_dat[i].base_level < char_dat[id[j]].base_level || + // if same base level, we sort by base exp. + (char_dat[i].base_level == char_dat[id[j]].base_level && + char_dat[i].base_exp < char_dat[id[j]].base_exp)) { + for(k = players; k > j; k--) + id[k] = id[k-1]; + id[j] = i; // id[players] + break; + } + break; + case 4: // by job (and job level) + for(j = 0; j < players; j++) + if (char_dat[i].class < char_dat[id[j]].class || + // if same job, we sort by job level. + (char_dat[i].class == char_dat[id[j]].class && + char_dat[i].job_level < char_dat[id[j]].job_level) || + // if same job and job level, we sort by job exp. + (char_dat[i].class == char_dat[id[j]].class && + char_dat[i].job_level == char_dat[id[j]].job_level && + char_dat[i].job_exp < char_dat[id[j]].job_exp)) { + for(k = players; k > j; k--) + id[k] = id[k-1]; + id[j] = i; // id[players] + break; + } + break; + case 5: // by location map name + { + int cpm_result; // A lot of player maps are identical. So, test if done often twice. + for(j = 0; j < players; j++) + if ((cpm_result = strcmp(char_dat[i].last_point.map, char_dat[id[j]].last_point.map)) < 0 || // no map are identical and with upper cases (not use stricmp) + // if same map name, we sort by name. + (cpm_result == 0 && + stricmp(char_dat[i].name, char_dat[id[j]].name) < 0)) { + for(k = players; k > j; k--) + id[k] = id[k-1]; + id[j] = i; // id[players] + break; + } + } + break; + default: // 0 or invalid value: no sorting + break; + } + players++; + } + } + + // write files + fp = fopen(online_txt_filename, "w"); + if (fp != NULL) { + fp2 = fopen(online_html_filename, "w"); + if (fp2 != NULL) { + // get time + time(&time_server); // get time in seconds since 1/1/1970 + datetime = localtime(&time_server); // convert seconds in structure + strftime(temp, sizeof(temp), "%d %b %Y %X", datetime); // like sprintf, but only for date/time (05 dec 2003 15:12:52) + // write heading + fprintf(fp2, "<HTML>\n"); + fprintf(fp2, " <META http-equiv=\"Refresh\" content=\"%d\">\n", online_refresh_html); // update on client explorer every x seconds + fprintf(fp2, " <HEAD>\n"); + fprintf(fp2, " <TITLE>Online Players on %s</TITLE>\n", server_name); + fprintf(fp2, " </HEAD>\n"); + fprintf(fp2, " <BODY>\n"); + fprintf(fp2, " <H3>Online Players on %s (%s):</H3>\n", server_name, temp); + fprintf(fp, "Online Players on %s (%s):\n", server_name, temp); + fprintf(fp, "\n"); + + // If we display at least 1 player + if (players > 0) { + j = 0; // count the number of characters for the txt version and to set the separate line + fprintf(fp2, " <table border=\"1\" cellspacing=\"1\">\n"); + fprintf(fp2, " <tr>\n"); + if ((online_display_option & 1) || (online_display_option & 64)) { + fprintf(fp2, " <td><b>Name</b></td>\n"); + if (online_display_option & 64) { + fprintf(fp, "Name "); // 30 + j += 30; + } else { + fprintf(fp, "Name "); // 25 + j += 25; + } + } + if ((online_display_option & 6) == 6) { + fprintf(fp2, " <td><b>Job (levels)</b></td>\n"); + fprintf(fp, "Job Levels "); // 27 + j += 27; + } else if (online_display_option & 2) { + fprintf(fp2, " <td><b>Job</b></td>\n"); + fprintf(fp, "Job "); // 19 + j += 19; + } else if (online_display_option & 4) { + fprintf(fp2, " <td><b>Levels</b></td>\n"); + fprintf(fp, " Levels "); // 8 + j += 8; + } + if (online_display_option & 24) { // 8 or 16 + fprintf(fp2, " <td><b>Location</b></td>\n"); + if (online_display_option & 16) { + fprintf(fp, "Location ( x , y ) "); // 23 + j += 23; + } else { + fprintf(fp, "Location "); // 13 + j += 13; + } + } + if (online_display_option & 32) { + fprintf(fp2, " <td ALIGN=CENTER><b>zenys</b></td>\n"); + fprintf(fp, " Zenys "); // 16 + j += 16; + } + fprintf(fp2, " </tr>\n"); + fprintf(fp, "\n"); + for (k = 0; k < j; k++) + fprintf(fp, "-"); + fprintf(fp, "\n"); + + // display each player. + for (i = 0; i < players; i++) { + // get id of the character (more speed) + j = id[i]; + fprintf(fp2, " <tr>\n"); + // displaying the character name + if ((online_display_option & 1) || (online_display_option & 64)) { // without/with 'GM' display + strcpy(temp, char_dat[j].name); + l = isGM(char_dat[j].account_id); + if (online_display_option & 64) { + if (l >= online_gm_display_min_level) + fprintf(fp, "%-24s (GM) ", temp); + else + fprintf(fp, "%-24s ", temp); + } else + fprintf(fp, "%-24s ", temp); + // name of the character in the html (no < >, because that create problem in html code) + fprintf(fp2, " <td>"); + if ((online_display_option & 64) && l >= online_gm_display_min_level) + fprintf(fp2, "<b>"); + for (k = 0; temp[k]; k++) { + switch(temp[k]) { + case '<': // < + fprintf(fp2, "<"); + break; + case '>': // > + fprintf(fp2, ">"); + break; + default: + fprintf(fp2, "%c", temp[k]); + break; + }; + } + if ((online_display_option & 64) && l >= online_gm_display_min_level) + fprintf(fp2, "</b> (GM)"); + fprintf(fp2, "</td>\n"); + } + // displaying of the job + if (online_display_option & 6) { + char * jobname = job_name(char_dat[j].class); + if ((online_display_option & 6) == 6) { + fprintf(fp2, " <td>%s %d/%d</td>\n", jobname, char_dat[j].base_level, char_dat[j].job_level); + fprintf(fp, "%-18s %3d/%3d ", jobname, char_dat[j].base_level, char_dat[j].job_level); + } else if (online_display_option & 2) { + fprintf(fp2, " <td>%s</td>\n", jobname); + fprintf(fp, "%-18s ", jobname); + } else if (online_display_option & 4) { + fprintf(fp2, " <td>%d/%d</td>\n", char_dat[j].base_level, char_dat[j].job_level); + fprintf(fp, "%3d/%3d ", char_dat[j].base_level, char_dat[j].job_level); + } + } + // displaying of the map + if (online_display_option & 24) { // 8 or 16 + // prepare map name + memset(temp, 0, sizeof(temp)); + strncpy(temp, char_dat[j].last_point.map, 16); + if (strchr(temp, '.') != NULL) + temp[strchr(temp, '.') - temp] = '\0'; // suppress the '.gat' + // write map name + if (online_display_option & 16) { // map-name AND coordonates + fprintf(fp2, " <td>%s (%d, %d)</td>\n", temp, char_dat[j].last_point.x, char_dat[j].last_point.y); + fprintf(fp, "%-12s (%3d,%3d) ", temp, char_dat[j].last_point.x, char_dat[j].last_point.y); + } else { + fprintf(fp2, " <td>%s</td>\n", temp); + fprintf(fp, "%-12s ", temp); + } + } + // displaying number of zenys + if (online_display_option & 32) { + // write number of zenys + if (char_dat[j].zeny == 0) { // if no zeny + fprintf(fp2, " <td ALIGN=RIGHT>no zeny</td>\n"); + fprintf(fp, " no zeny "); + } else { + fprintf(fp2, " <td ALIGN=RIGHT>%d z</td>\n", char_dat[j].zeny); + fprintf(fp, "%13d z ", char_dat[j].zeny); + } + } + fprintf(fp, "\n"); + fprintf(fp2, " </tr>\n"); + } + fprintf(fp2, " </table>\n"); + fprintf(fp, "\n"); + } + + // Displaying number of online players + if (players == 0) { + fprintf(fp2, " <p>No user is online.</p>\n"); + fprintf(fp, "No user is online.\n"); + // no display if only 1 player + } else if (players == 1) { + } else { + fprintf(fp2, " <p>%d users are online.</p>\n", players); + fprintf(fp, "%d users are online.\n", players); + } + fprintf(fp2, " </BODY>\n"); + fprintf(fp2, "</HTML>\n"); + fclose(fp2); + } + fclose(fp); + } + + return; +} + +//--------------------------------------------------------------------- +// This function return the number of online players in all map-servers +//--------------------------------------------------------------------- +int count_users(void) { + int i, users; + + users = 0; + for(i = 0; i < MAX_MAP_SERVERS; i++) + if (server_fd[i] >= 0) + users += server[i].users; + + return users; +} + +//---------------------------------------- +// Function to send characters to a player +//---------------------------------------- +int mmo_char_send006b(int fd, struct char_session_data *sd) { + int i, j, found_num; + struct mmo_charstatus *p; +#ifdef NEW_006b + const int offset = 24; +#else + const int offset = 4; +#endif + + found_num = 0; + for(i = 0; i < char_num; i++) { + if (char_dat[i].account_id == sd->account_id) { + sd->found_char[found_num] = i; + found_num++; + if (found_num == 9) + break; + } + } + for(i = found_num; i < 9; i++) + sd->found_char[i] = -1; + + memset(WFIFOP(fd,0), 0, offset + found_num * 106); + WFIFOW(fd,0) = 0x6b; + WFIFOW(fd,2) = offset + found_num * 106; + + for(i = 0; i < found_num; i++) { + p = &char_dat[sd->found_char[i]]; + j = offset + (i * 106); // increase speed of code + + WFIFOL(fd,j) = p->char_id; + WFIFOL(fd,j+4) = p->base_exp; + WFIFOL(fd,j+8) = p->zeny; + WFIFOL(fd,j+12) = p->job_exp; + WFIFOL(fd,j+16) = p->job_level; + + WFIFOL(fd,j+20) = 0; + WFIFOL(fd,j+24) = 0; + WFIFOL(fd,j+28) = p->option; + + WFIFOL(fd,j+32) = p->karma; + WFIFOL(fd,j+36) = p->manner; + + WFIFOW(fd,j+40) = p->status_point; + WFIFOW(fd,j+42) = (p->hp > 0x7fff) ? 0x7fff : p->hp; + WFIFOW(fd,j+44) = (p->max_hp > 0x7fff) ? 0x7fff : p->max_hp; + WFIFOW(fd,j+46) = (p->sp > 0x7fff) ? 0x7fff : p->sp; + WFIFOW(fd,j+48) = (p->max_sp > 0x7fff) ? 0x7fff : p->max_sp; + WFIFOW(fd,j+50) = DEFAULT_WALK_SPEED; // p->speed; + WFIFOW(fd,j+52) = p->class; + WFIFOW(fd,j+54) = p->hair; + WFIFOW(fd,j+56) = p->weapon; + WFIFOW(fd,j+58) = p->base_level; + WFIFOW(fd,j+60) = p->skill_point; + WFIFOW(fd,j+62) = p->head_bottom; + WFIFOW(fd,j+64) = p->shield; + WFIFOW(fd,j+66) = p->head_top; + WFIFOW(fd,j+68) = p->head_mid; + WFIFOW(fd,j+70) = p->hair_color; + WFIFOW(fd,j+72) = p->clothes_color; + + memcpy(WFIFOP(fd,j+74), p->name, 24); + + WFIFOB(fd,j+98) = (p->str > 255) ? 255 : p->str; + WFIFOB(fd,j+99) = (p->agi > 255) ? 255 : p->agi; + WFIFOB(fd,j+100) = (p->vit > 255) ? 255 : p->vit; + WFIFOB(fd,j+101) = (p->int_ > 255) ? 255 : p->int_; + WFIFOB(fd,j+102) = (p->dex > 255) ? 255 : p->dex; + WFIFOB(fd,j+103) = (p->luk > 255) ? 255 : p->luk; + WFIFOB(fd,j+104) = p->char_num; + } + + WFIFOSET(fd,WFIFOW(fd,2)); + + return 0; +} + +int set_account_reg2(int acc, int num, struct global_reg *reg) { + int i, c; + + c = 0; + for(i = 0; i < char_num; i++) { + if (char_dat[i].account_id == acc) { + memcpy(char_dat[i].account_reg2, reg, sizeof(char_dat[i].account_reg2)); + char_dat[i].account_reg2_num = num; + c++; + } + } + return c; +} + +// 離婚(char削除時に使用) +int char_divorce(struct mmo_charstatus *cs) { + if (cs == NULL) + return 0; + + if (cs->partner_id > 0){ + int i, j; + for(i = 0; i < char_num; i++) { + if (char_dat[i].char_id == cs->partner_id && char_dat[i].partner_id == cs->char_id) { + cs->partner_id = 0; + char_dat[i].partner_id = 0; + for(j = 0; j < MAX_INVENTORY; j++) + if (char_dat[i].inventory[j].nameid == WEDDING_RING_M || char_dat[i].inventory[j].nameid == WEDDING_RING_F) + memset(&char_dat[i].inventory[j], 0, sizeof(char_dat[i].inventory[0])); + if (cs->inventory[j].nameid == WEDDING_RING_M || cs->inventory[j].nameid == WEDDING_RING_F) + memset(&cs->inventory[j], 0, sizeof(cs->inventory[0])); + return 0; + } + } + } + return 0; +} + +//------------------------------------------------------------ +// E-mail check: return 0 (not correct) or 1 (valid). by [Yor] +//------------------------------------------------------------ +int e_mail_check(unsigned char *email) { + char ch; + unsigned char* last_arobas; + + // athena limits + if (strlen(email) < 3 || strlen(email) > 39) + return 0; + + // part of RFC limits (official reference of e-mail description) + if (strchr(email, '@') == NULL || email[strlen(email)-1] == '@') + return 0; + + if (email[strlen(email)-1] == '.') + return 0; + + last_arobas = strrchr(email, '@'); + + if (strstr(last_arobas, "@.") != NULL || + strstr(last_arobas, "..") != NULL) + return 0; + + for(ch = 1; ch < 32; ch++) { + if (strchr(last_arobas, ch) != NULL) { + return 0; + break; + } + } + + if (strchr(last_arobas, ' ') != NULL || + strchr(last_arobas, ';') != NULL) + return 0; + + // all correct + return 1; +} + +//---------------------------------------------------------------------- +// Force disconnection of an online player (with account value) by [Yor] +//---------------------------------------------------------------------- +int disconnect_player(int accound_id) { + int i; + struct char_session_data *sd; + + // disconnect player if online on char-server + for(i = 0; i < fd_max; i++) { + if (session[i] && (sd = session[i]->session_data)) { + if (sd->account_id == accound_id) { + session[i]->eof = 1; + return 1; + } + } + } + + return 0; +} + +// キャラ削除に伴うデータ削除 +static int char_delete(struct mmo_charstatus *cs) { + int j; + + // ペット削除 + if (cs->pet_id) + inter_pet_delete(cs->pet_id); + for (j = 0; j < MAX_INVENTORY; j++) + if (cs->inventory[j].card[0] == (short)0xff00) + inter_pet_delete(*((long *)(&cs->inventory[j].card[2]))); + for (j = 0; j < MAX_CART; j++) + if (cs->cart[j].card[0] == (short)0xff00) + inter_pet_delete(*((long *)(&cs->cart[j].card[2]))); + // ギルド脱退 + if (cs->guild_id) + inter_guild_leave(cs->guild_id, cs->account_id, cs->char_id); + // パーティー脱退 + if (cs->party_id) + inter_party_leave(cs->party_id, cs->account_id); + // 離婚 + if (cs->partner_id){ + // 離婚情報をmapに通知 + char buf[10]; + WBUFW(buf,0) = 0x2b12; + WBUFL(buf,2) = cs->char_id; + WBUFL(buf,6) = cs->partner_id; + mapif_sendall(buf,10); + // 離婚 + char_divorce(cs); + } + return 0; +} + +int parse_tologin(int fd) { + int i; + struct char_session_data *sd; + + // only login-server can have an access to here. + // so, if it isn't the login-server, we disconnect the session (fd != login_fd). + if (fd != login_fd || session[fd]->eof) { + if (fd == login_fd) { + printf("Char-server can't connect to login-server (connection #%d).\n", fd); + login_fd = -1; + } + close(fd); + delete_session(fd); + return 0; + } + + sd = session[fd]->session_data; + + while(RFIFOREST(fd) >= 2) { +// printf("parse_tologin: connection #%d, packet: 0x%x (with being read: %d bytes).\n", fd, RFIFOW(fd,0), RFIFOREST(fd)); + + switch(RFIFOW(fd,0)) { + case 0x2711: + if (RFIFOREST(fd) < 3) + return 0; + if (RFIFOB(fd,2)) { +// printf("connect login server error : %d\n", RFIFOB(fd,2)); + printf("Can not connect to login-server.\n"); + printf("The server communication passwords (default s1/p1) is probably invalid.\n"); + printf("Also, please make sure your accounts file (default: accounts.txt) has those values present.\n"); + printf("If you changed the communication passwords, change them back at map_athena.conf and char_athena.conf\n"); + exit(1); + } else { + printf("Connected to login-server (connection #%d).\n", fd); + // if no map-server already connected, display a message... + for(i = 0; i < MAX_MAP_SERVERS; i++) + if (server_fd[i] >= 0 && server[i].map[0][0]) // if map-server online and at least 1 map + break; + if (i == MAX_MAP_SERVERS) + printf("Awaiting maps from map-server.\n"); + } + RFIFOSKIP(fd,3); + break; + + case 0x2713: + if (RFIFOREST(fd) < 51) + return 0; +// printf("parse_tologin 2713 : %d\n", RFIFOB(fd,6)); + for(i = 0; i < fd_max; i++) { + if (session[i] && (sd = session[i]->session_data) && sd->account_id == RFIFOL(fd,2)) { + if (RFIFOB(fd,6) != 0) { + WFIFOW(i,0) = 0x6c; + WFIFOB(i,2) = 0x42; + WFIFOSET(i,3); + } else if (max_connect_user == 0 || count_users() < max_connect_user) { +// if (max_connect_user == 0) +// printf("max_connect_user (unlimited) -> accepted.\n"); +// else +// printf("count_users(): %d < max_connect_user (%d) -> accepted.\n", count_users(), max_connect_user); + memcpy(sd->email, RFIFOP(fd, 7), 40); + if (e_mail_check(sd->email) == 0) + strncpy(sd->email, "a@a.com", 40); // default e-mail + sd->connect_until_time = (time_t)RFIFOL(fd,47); + // send characters to player + mmo_char_send006b(i, sd); + } else { + // refuse connection: too much online players +// printf("count_users(): %d < max_connect_use (%d) -> fail...\n", count_users(), max_connect_user); + WFIFOW(i,0) = 0x6c; + WFIFOW(i,2) = 0; + WFIFOSET(i,3); + } + break; + } + } + RFIFOSKIP(fd,51); + break; + + // Receiving of an e-mail/time limit from the login-server (answer of a request because a player comes back from map-server to char-server) by [Yor] + case 0x2717: + if (RFIFOREST(fd) < 50) + return 0; + for(i = 0; i < fd_max; i++) { + if (session[i] && (sd = session[i]->session_data)) { + if (sd->account_id == RFIFOL(fd,2)) { + memcpy(sd->email, RFIFOP(fd,6), 40); + if (e_mail_check(sd->email) == 0) + strncpy(sd->email, "a@a.com", 40); // default e-mail + sd->connect_until_time = (time_t)RFIFOL(fd,46); + break; + } + } + } + RFIFOSKIP(fd,50); + break; + + case 0x2721: // gm reply + if (RFIFOREST(fd) < 10) + return 0; + { + unsigned char buf[10]; + WBUFW(buf,0) = 0x2b0b; + WBUFL(buf,2) = RFIFOL(fd,2); // account + WBUFL(buf,6) = RFIFOL(fd,6); // GM level + mapif_sendall(buf,10); +// printf("parse_tologin: To become GM answer: char -> map.\n"); + } + RFIFOSKIP(fd,10); + break; + + case 0x2723: // changesex reply (modified by [Yor]) + if (RFIFOREST(fd) < 7) + return 0; + { + int acc, sex, i, j; + unsigned char buf[7]; + acc = RFIFOL(fd,2); + sex = RFIFOB(fd,6); + RFIFOSKIP(fd, 7); + if (acc > 0) { + for (i = 0; i < char_num; i++) { + if (char_dat[i].account_id == acc) { + int jobclass = char_dat[i].class; + char_dat[i].sex = sex; + auth_fifo[i].sex = sex; + if (jobclass == 19 || jobclass == 20 || + jobclass == 4020 || jobclass == 4021 || + jobclass == 4042 || jobclass == 4043) { + // job modification + if (jobclass == 19 || jobclass == 20) { + char_dat[i].class = (sex) ? 19 : 20; + } else if (jobclass == 4020 || jobclass == 4021) { + char_dat[i].class = (sex) ? 4020 : 4021; + } else if (jobclass == 4042 || jobclass == 4043) { + char_dat[i].class = (sex) ? 4042 : 4043; + } + // remove specifical skills of classes 19, 4020 and 4042 + for(j = 315; j <= 322; j++) { + if (char_dat[i].skill[j].id > 0 && !char_dat[i].skill[j].flag) { + char_dat[i].skill_point += char_dat[i].skill[j].lv; + char_dat[i].skill[j].id = 0; + char_dat[i].skill[j].lv = 0; + } + } + // remove specifical skills of classes 20, 4021 and 4043 + for(j = 323; j <= 330; j++) { + if (char_dat[i].skill[j].id > 0 && !char_dat[i].skill[j].flag) { + char_dat[i].skill_point += char_dat[i].skill[j].lv; + char_dat[i].skill[j].id = 0; + char_dat[i].skill[j].lv = 0; + } + } + } + // to avoid any problem with equipment and invalid sex, equipment is unequiped. + for (j = 0; j < MAX_INVENTORY; j++) { + if (char_dat[i].inventory[j].nameid && char_dat[i].inventory[j].equip) + char_dat[i].inventory[j].equip = 0; + } + char_dat[i].weapon = 0; + char_dat[i].shield = 0; + char_dat[i].head_top = 0; + char_dat[i].head_mid = 0; + char_dat[i].head_bottom = 0; + } + } + // disconnect player if online on char-server + disconnect_player(acc); + } + WBUFW(buf,0) = 0x2b0d; + WBUFL(buf,2) = acc; + WBUFB(buf,6) = sex; + mapif_sendall(buf, 7); + } + break; + + case 0x2726: // Request to send a broadcast message (no answer) + if (RFIFOREST(fd) < 8 || RFIFOREST(fd) < (8 + RFIFOL(fd,4))) + return 0; + if (RFIFOL(fd,4) < 1) + char_log("Receiving a message for broadcast, but message is void." RETCODE); + else { + // at least 1 map-server + for(i = 0; i < MAX_MAP_SERVERS; i++) + if (server_fd[i] >= 0) + break; + if (i == MAX_MAP_SERVERS) + char_log("'ladmin': Receiving a message for broadcast, but no map-server is online." RETCODE); + else { + char buf[128]; + char message[RFIFOL(fd,4) + 1]; // +1 to add a null terminated if not exist in the packet + int lp; + char *p; + memset(message, '\0', sizeof(message)); + memcpy(message, RFIFOP(fd,8), RFIFOL(fd,4)); + message[sizeof(message)-1] = '\0'; + remove_control_chars(message); + // remove all first spaces + p = message; + while(p[0] == ' ') + p++; + // if message is only composed of spaces + if (p[0] == '\0') + char_log("Receiving a message for broadcast, but message is only a lot of spaces." RETCODE); + // else send message to all map-servers + else { + if (RFIFOW(fd,2) == 0) { + char_log("'ladmin': Receiving a message for broadcast (message (in yellow): %s)" RETCODE, + message); + lp = 4; + } else { + char_log("'ladmin': Receiving a message for broadcast (message (in blue): %s)" RETCODE, + message); + lp = 8; + } + // split message to max 80 char + while(p[0] != '\0') { // if not finish + if (p[0] == ' ') // jump if first char is a space + p++; + else { + char split[80]; + char* last_space; + sscanf(p, "%79[^\t]", split); // max 79 char, any char (\t is control char and control char was removed before) + split[sizeof(split)-1] = '\0'; // last char always \0 + if ((last_space = strrchr(split, ' ')) != NULL) { // searching space from end of the string + last_space[0] = '\0'; // replace it by NULL to have correct length of split + p++; // to jump the new NULL + } + p += strlen(split); + // send broadcast to all map-servers + WBUFW(buf,0) = 0x3800; + WBUFW(buf,2) = lp + strlen(split) + 1; + WBUFL(buf,4) = 0x65756c62; // only write if in blue (lp = 8) + memcpy(WBUFP(buf,lp), split, strlen(split) + 1); + mapif_sendall(buf, WBUFW(buf,2)); + } + } + } + } + } + RFIFOSKIP(fd,8 + RFIFOL(fd,4)); + break; + + // account_reg2変更通知 + case 0x2729: + if (RFIFOREST(fd) < 4 || RFIFOREST(fd) < RFIFOW(fd,2)) + return 0; + { + struct global_reg reg[ACCOUNT_REG2_NUM]; + unsigned char buf[4096]; + int j, p, acc; + acc = RFIFOL(fd,4); + for (p = 8, j = 0; p < RFIFOW(fd,2) && j < ACCOUNT_REG2_NUM; p += 36, j++) { + memcpy(reg[j].str, RFIFOP(fd,p), 32); + reg[j].value = RFIFOL(fd,p+32); + } + set_account_reg2(acc, j, reg); + // 同垢ログインを禁止していれば送る必要は無い + memcpy(buf, RFIFOP(fd,0), RFIFOW(fd,2)); + WBUFW(buf,0) = 0x2b11; + mapif_sendall(buf, WBUFW(buf,2)); + RFIFOSKIP(fd,RFIFOW(fd,2)); +// printf("char: save_account_reg_reply\n"); + } + break; + + // Account deletion notification (from login-server) + case 0x2730: + if (RFIFOREST(fd) < 6) + return 0; + // Deletion of all characters of the account + for(i = 0; i < char_num; i++) { + if (char_dat[i].account_id == RFIFOL(fd,2)) { + char_delete(&char_dat[i]); + if (i < char_num - 1) { + memcpy(&char_dat[i], &char_dat[char_num-1], sizeof(struct mmo_charstatus)); + // if moved character owns to deleted account, check again it's character + if (char_dat[i].account_id == RFIFOL(fd,2)) { + i--; + // Correct moved character reference in the character's owner by [Yor] + } else { + int j, k; + struct char_session_data *sd2; + for (j = 0; j < fd_max; j++) { + if (session[j] && (sd2 = session[j]->session_data) && + sd2->account_id == char_dat[char_num-1].account_id) { + for (k = 0; k < 9; k++) { + if (sd2->found_char[k] == char_num-1) { + sd2->found_char[k] = i; + break; + } + } + break; + } + } + } + } + char_num--; + } + } + // Deletion of the storage + inter_storage_delete(RFIFOL(fd,2)); + // send to all map-servers to disconnect the player + { + unsigned char buf[6]; + WBUFW(buf,0) = 0x2b13; + WBUFL(buf,2) = RFIFOL(fd,2); + mapif_sendall(buf, 6); + } + // disconnect player if online on char-server + disconnect_player(RFIFOL(fd,2)); + RFIFOSKIP(fd,6); + break; + + // State change of account/ban notification (from login-server) by [Yor] + case 0x2731: + if (RFIFOREST(fd) < 11) + return 0; + // send to all map-servers to disconnect the player + { + unsigned char buf[11]; + WBUFW(buf,0) = 0x2b14; + WBUFL(buf,2) = RFIFOL(fd,2); + WBUFB(buf,6) = RFIFOB(fd,6); // 0: change of statut, 1: ban + WBUFL(buf,7) = RFIFOL(fd,7); // status or final date of a banishment + mapif_sendall(buf, 11); + } + // disconnect player if online on char-server + disconnect_player(RFIFOL(fd,2)); + RFIFOSKIP(fd,11); + break; + + // Receiving GM acounts info from login-server (by [Yor]) + case 0x2732: + if (RFIFOREST(fd) < 4 || RFIFOREST(fd) < RFIFOW(fd,2)) + return 0; + { + char buf[32000]; + if (gm_account != NULL) + free(gm_account); + gm_account = calloc(sizeof(struct gm_account) * ((RFIFOW(fd,2) - 4) / 5), 1); + GM_num = 0; + for (i = 4; i < RFIFOW(fd,2); i = i + 5) { + gm_account[GM_num].account_id = RFIFOL(fd,i); + gm_account[GM_num].level = (int)RFIFOB(fd,i+4); + //printf("GM account: %d -> level %d\n", gm_account[GM_num].account_id, gm_account[GM_num].level); + GM_num++; + } + printf("From login-server: receiving of %d GM accounts information.\n", GM_num); + char_log("From login-server: receiving of %d GM accounts information." RETCODE, GM_num); + create_online_files(); // update online players files (perhaps some online players change of GM level) + // send new gm acccounts level to map-servers + memcpy(buf, RFIFOP(fd,0), RFIFOW(fd,2)); + WBUFW(buf,0) = 0x2b15; + mapif_sendall(buf, RFIFOW(fd,2)); + } + RFIFOSKIP(fd,RFIFOW(fd,2)); + break; + + default: + session[fd]->eof = 1; + return 0; + } + } + RFIFOFLUSH(fd); + + return 0; +} + +//-------------------------------- +// Map-server anti-freeze system +//-------------------------------- +int map_anti_freeze_system(int tid, unsigned int tick, int id, int data) { + int i; + + //printf("Entering in map_anti_freeze_system function to check freeze of servers.\n"); + for(i = 0; i < MAX_MAP_SERVERS; i++) { + if (server_fd[i] >= 0) {// if map-server is online + //printf("map_anti_freeze_system: server #%d, flag: %d.\n", i, server_freezeflag[i]); + if (server_freezeflag[i]-- < 1) { // Map-server anti-freeze system. Counter. 5 ok, 4...0 freezed + printf("Map-server anti-freeze system: char-server #%d is freezed -> disconnection.\n", i); + char_log("Map-server anti-freeze system: char-server #%d is freezed -> disconnection." RETCODE, + i); + session[server_fd[i]]->eof = 1; + } + } + } + + return 0; +} + +int parse_frommap(int fd) { + int i, j; + int id; + + for(id = 0; id < MAX_MAP_SERVERS; id++) + if (server_fd[id] == fd) + break; + if (id == MAX_MAP_SERVERS || session[fd]->eof) { + if (id < MAX_MAP_SERVERS) { + printf("Map-server %d (session #%d) has disconnected.\n", id, fd); + memset(&server[id], 0, sizeof(struct mmo_map_server)); + server_fd[id] = -1; + for(j = 0; j < char_num; j++) + if (online_chars[j] == fd) + online_chars[j] = -1; + create_online_files(); // update online players files (to remove all online players of this server) + } + close(fd); + delete_session(fd); + return 0; + } + + while(RFIFOREST(fd) >= 2) { +// printf("parse_frommap: connection #%d, packet: 0x%x (with being read: %d bytes).\n", fd, RFIFOW(fd,0), RFIFOREST(fd)); + + switch(RFIFOW(fd,0)) { + // request from map-server to reload GM accounts. Transmission to login-server (by Yor) + case 0x2af7: + if (login_fd > 0) { // don't send request if no login-server + WFIFOW(login_fd,0) = 0x2709; + WFIFOSET(login_fd, 2); +// printf("char : request from map-server to reload GM accounts -> login-server.\n"); + } + RFIFOSKIP(fd,2); + break; + + // Receiving map names list from the map-server + case 0x2afa: + if (RFIFOREST(fd) < 4 || RFIFOREST(fd) < RFIFOW(fd,2)) + return 0; + memset(server[id].map, 0, sizeof(server[id].map)); + j = 0; + for(i = 4; i < RFIFOW(fd,2); i += 16) { + memcpy(server[id].map[j], RFIFOP(fd,i), 16); +// printf("set map %d.%d : %s\n", id, j, server[id].map[j]); + j++; + } + { + unsigned char *p = (unsigned char *)&server[id].ip; + printf("Map-Server %d connected: %d maps, from IP %d.%d.%d.%d port %d.\n", + id, j, p[0], p[1], p[2], p[3], server[id].port); + printf("Map-server %d loading complete.\n", id); + char_log("Map-Server %d connected: %d maps, from IP %d.%d.%d.%d port %d. Map-server %d loading complete." RETCODE, + id, j, p[0], p[1], p[2], p[3], server[id].port, id); + } + WFIFOW(fd,0) = 0x2afb; + WFIFOB(fd,2) = 0; + memcpy(WFIFOP(fd,3), wisp_server_name, 24); // name for wisp to player + WFIFOSET(fd,27); + { + unsigned char buf[16384]; + int x; + if (j == 0) { + printf("WARNING: Map-Server %d have NO map.\n", id); + char_log("WARNING: Map-Server %d have NO map." RETCODE, id); + // Transmitting maps information to the other map-servers + } else { + WBUFW(buf,0) = 0x2b04; + WBUFW(buf,2) = j * 16 + 10; + WBUFL(buf,4) = server[id].ip; + WBUFW(buf,8) = server[id].port; + memcpy(WBUFP(buf,10), RFIFOP(fd,4), j * 16); + mapif_sendallwos(fd, buf, WBUFW(buf,2)); + } + // Transmitting the maps of the other map-servers to the new map-server + for(x = 0; x < MAX_MAP_SERVERS; x++) { + if (server_fd[x] >= 0 && x != id) { + WFIFOW(fd,0) = 0x2b04; + WFIFOL(fd,4) = server[x].ip; + WFIFOW(fd,8) = server[x].port; + j = 0; + for(i = 0; i < MAX_MAP_PER_SERVER; i++) + if (server[x].map[i][0]) + memcpy(WFIFOP(fd,10+(j++)*16), server[x].map[i], 16); + if (j > 0) { + WFIFOW(fd,2) = j * 16 + 10; + WFIFOSET(fd,WFIFOW(fd,2)); + } + } + } + } + RFIFOSKIP(fd,RFIFOW(fd,2)); + break; + + // 認証要求 + case 0x2afc: + if (RFIFOREST(fd) < 22) + return 0; + //printf("auth_fifo search: account: %d, char: %d, secure: %08x-%08x\n", RFIFOL(fd,2), RFIFOL(fd,6), RFIFOL(fd,10), RFIFOL(fd,14)); + for(i = 0; i < AUTH_FIFO_SIZE; i++) { + if (auth_fifo[i].account_id == RFIFOL(fd,2) && + auth_fifo[i].char_id == RFIFOL(fd,6) && + auth_fifo[i].login_id1 == RFIFOL(fd,10) && +#if CMP_AUTHFIFO_LOGIN2 != 0 + // here, it's the only area where it's possible that we doesn't know login_id2 (map-server asks just after 0x72 packet, that doesn't given the value) + (auth_fifo[i].login_id2 == RFIFOL(fd,14) || RFIFOL(fd,14) == 0) && // relate to the versions higher than 18 +#endif + (!check_ip_flag || auth_fifo[i].ip == RFIFOL(fd,18)) && + !auth_fifo[i].delflag) { + auth_fifo[i].delflag = 1; + WFIFOW(fd,0) = 0x2afd; + WFIFOW(fd,2) = 16 + sizeof(struct mmo_charstatus); + WFIFOL(fd,4) = RFIFOL(fd,2); + WFIFOL(fd,8) = auth_fifo[i].login_id2; + WFIFOL(fd,12) = (unsigned long)auth_fifo[i].connect_until_time; + char_dat[auth_fifo[i].char_pos].sex = auth_fifo[i].sex; + memcpy(WFIFOP(fd,16), &char_dat[auth_fifo[i].char_pos], sizeof(struct mmo_charstatus)); + WFIFOSET(fd, WFIFOW(fd,2)); + //printf("auth_fifo search success (auth #%d, account %d, character: %d).\n", i, RFIFOL(fd,2), RFIFOL(fd,6)); + break; + } + } + if (i == AUTH_FIFO_SIZE) { + WFIFOW(fd,0) = 0x2afe; + WFIFOL(fd,2) = RFIFOL(fd,2); + WFIFOSET(fd,6); + printf("auth_fifo search error! account %d not authentified.\n", RFIFOL(fd,2)); + } + RFIFOSKIP(fd,22); + break; + + // MAPサーバー上のユーザー数受信 + case 0x2aff: + if (RFIFOREST(fd) < 6 || RFIFOREST(fd) < RFIFOW(fd,2)) + return 0; + server[id].users = RFIFOW(fd,4); + if(anti_freeze_enable) + server_freezeflag[id] = 5; // Map anti-freeze system. Counter. 5 ok, 4...0 freezed + // remove all previously online players of the server + for(i = 0; i < char_num; i++) + if (online_chars[i] == id) + online_chars[i] = -1; + // add online players in the list by [Yor] + for(i = 0; i < server[id].users; i++) { + int char_id = RFIFOL(fd,6+i*4); + for(j = 0; j < char_num; j++) + if (char_dat[j].char_id == char_id) { + online_chars[j] = id; + //printf("%d\n", char_id); + break; + } + } + if (update_online < time(NULL)) { // Time is done + update_online = time(NULL) + 8; + create_online_files(); // only every 8 sec. (normally, 1 server send users every 5 sec.) Don't update every time, because that takes time, but only every 2 connection. + // it set to 8 sec because is more than 5 (sec) and if we have more than 1 map-server, informations can be received in shifted. + } + RFIFOSKIP(fd,6+i*4); + break; + + // キャラデータ保存 + case 0x2b01: + if (RFIFOREST(fd) < 4 || RFIFOREST(fd) < RFIFOW(fd,2)) + return 0; + for(i = 0; i < char_num; i++) { + if (char_dat[i].account_id == RFIFOL(fd,4) && + char_dat[i].char_id == RFIFOL(fd,8)) + break; + } + if (i != char_num) + memcpy(&char_dat[i], RFIFOP(fd,12), sizeof(struct mmo_charstatus)); + RFIFOSKIP(fd,RFIFOW(fd,2)); + break; + + // キャラセレ要求 + case 0x2b02: + if (RFIFOREST(fd) < 18) + return 0; + if (auth_fifo_pos >= AUTH_FIFO_SIZE) + auth_fifo_pos = 0; + //printf("auth_fifo set (auth #%d) - account: %d, secure: %08x-%08x\n", auth_fifo_pos, RFIFOL(fd,2), RFIFOL(fd,6), RFIFOL(fd,10)); + auth_fifo[auth_fifo_pos].account_id = RFIFOL(fd,2); + auth_fifo[auth_fifo_pos].char_id = 0; + auth_fifo[auth_fifo_pos].login_id1 = RFIFOL(fd,6); + auth_fifo[auth_fifo_pos].login_id2 = RFIFOL(fd,10); + auth_fifo[auth_fifo_pos].delflag = 2; + auth_fifo[auth_fifo_pos].char_pos = 0; + auth_fifo[auth_fifo_pos].connect_until_time = 0; // unlimited/unknown time by default (not display in map-server) + auth_fifo[auth_fifo_pos].ip = RFIFOL(fd,14); + auth_fifo_pos++; + WFIFOW(fd,0) = 0x2b03; + WFIFOL(fd,2) = RFIFOL(fd,2); + WFIFOB(fd,6) = 0; + WFIFOSET(fd,7); + RFIFOSKIP(fd,18); + break; + + // マップサーバー間移動要求 + case 0x2b05: + if (RFIFOREST(fd) < 49) + return 0; + if (auth_fifo_pos >= AUTH_FIFO_SIZE) + auth_fifo_pos = 0; + WFIFOW(fd,0) = 0x2b06; + memcpy(WFIFOP(fd,2), RFIFOP(fd,2), 42); + //printf("auth_fifo set (auth#%d) - account: %d, secure: 0x%08x-0x%08x\n", auth_fifo_pos, RFIFOL(fd,2), RFIFOL(fd,6), RFIFOL(fd,10)); + auth_fifo[auth_fifo_pos].account_id = RFIFOL(fd,2); + auth_fifo[auth_fifo_pos].char_id = RFIFOL(fd,14); + auth_fifo[auth_fifo_pos].login_id1 = RFIFOL(fd,6); + auth_fifo[auth_fifo_pos].login_id2 = RFIFOL(fd,10); + auth_fifo[auth_fifo_pos].delflag = 0; + auth_fifo[auth_fifo_pos].sex = RFIFOB(fd,44); + auth_fifo[auth_fifo_pos].connect_until_time = 0; // unlimited/unknown time by default (not display in map-server) + auth_fifo[auth_fifo_pos].ip = RFIFOL(fd,45); + for(i = 0; i < char_num; i++) + if (char_dat[i].account_id == RFIFOL(fd,2) && + char_dat[i].char_id == RFIFOL(fd,14)) { + auth_fifo[auth_fifo_pos].char_pos = i; + auth_fifo_pos++; + WFIFOL(fd,6) = 0; + break; + } + if (i == char_num) + WFIFOW(fd,6) = 1; + WFIFOSET(fd,44); + RFIFOSKIP(fd,49); + break; + + // キャラ名検索 + case 0x2b08: + if (RFIFOREST(fd) < 6) + return 0; + for(i = 0; i < char_num; i++) { + if (char_dat[i].char_id == RFIFOL(fd,2)) + break; + } + WFIFOW(fd,0) = 0x2b09; + WFIFOL(fd,2) = RFIFOL(fd,2); + if (i != char_num) + memcpy(WFIFOP(fd,6), char_dat[i].name, 24); + else + memcpy(WFIFOP(fd,6), unknown_char_name, 24); + WFIFOSET(fd,30); + RFIFOSKIP(fd,6); + break; + + // it is a request to become GM + case 0x2b0a: + if (RFIFOREST(fd) < 4 || RFIFOREST(fd) < RFIFOW(fd,2)) + return 0; +// printf("parse_frommap: change gm -> login, account: %d, pass: '%s'.\n", RFIFOL(fd,4), RFIFOP(fd,8)); + if (login_fd > 0) { // don't send request if no login-server + WFIFOW(login_fd,0) = 0x2720; + memcpy(WFIFOP(login_fd,2), RFIFOP(fd,2), RFIFOW(fd,2)-2); + WFIFOSET(login_fd, RFIFOW(fd,2)); + } else { + WFIFOW(fd,0) = 0x2b0b; + WFIFOL(fd,2) = RFIFOL(fd,4); + WFIFOL(fd,6) = 0; + WFIFOSET(fd, 10); + } + RFIFOSKIP(fd, RFIFOW(fd,2)); + break; + + // Map server send information to change an email of an account -> login-server + case 0x2b0c: + if (RFIFOREST(fd) < 86) + return 0; + if (login_fd > 0) { // don't send request if no login-server + memcpy(WFIFOP(login_fd,0), RFIFOP(fd,0), 86); // 0x2722 <account_id>.L <actual_e-mail>.40B <new_e-mail>.40B + WFIFOW(login_fd,0) = 0x2722; + WFIFOSET(login_fd, 86); + } + RFIFOSKIP(fd, 86); + break; + + // Map server ask char-server about a character name to do some operations (all operations are transmitted to login-server) + case 0x2b0e: + if (RFIFOREST(fd) < 44) + return 0; + { + char character_name[24]; + int acc = RFIFOL(fd,2); // account_id of who ask (-1 if nobody) + memcpy(character_name, RFIFOP(fd,6), 24); + character_name[sizeof(character_name) -1] = '\0'; + // prepare answer + WFIFOW(fd,0) = 0x2b0f; // answer + WFIFOL(fd,2) = acc; // who want do operation + WFIFOW(fd,30) = RFIFOW(fd, 30); // type of operation: 1-block, 2-ban, 3-unblock, 4-unban, 5-changesex + // search character + i = search_character_index(character_name); + if (i >= 0) { + memcpy(WFIFOP(fd,6), search_character_name(i), 24); // put correct name if found + WFIFOW(fd,32) = 0; // answer: 0-login-server resquest done, 1-player not found, 2-gm level too low, 3-login-server offline + switch(RFIFOW(fd, 30)) { + case 1: // block + if (acc == -1 || isGM(acc) >= isGM(char_dat[i].account_id)) { + if (login_fd > 0) { // don't send request if no login-server + WFIFOW(login_fd,0) = 0x2724; + WFIFOL(login_fd,2) = char_dat[i].account_id; // account value + WFIFOL(login_fd,6) = 5; // status of the account + WFIFOSET(login_fd, 10); +// printf("char : status -> login: account %d, status: %d \n", char_dat[i].account_id, 5); + } else + WFIFOW(fd,32) = 3; // answer: 0-login-server resquest done, 1-player not found, 2-gm level too low, 3-login-server offline + } else + WFIFOW(fd,32) = 2; // answer: 0-login-server resquest done, 1-player not found, 2-gm level too low, 3-login-server offline + break; + case 2: // ban + if (acc == -1 || isGM(acc) >= isGM(char_dat[i].account_id)) { + if (login_fd > 0) { // don't send request if no login-server + WFIFOW(login_fd, 0) = 0x2725; + WFIFOL(login_fd, 2) = char_dat[i].account_id; // account value + WFIFOW(login_fd, 6) = RFIFOW(fd,32); // year + WFIFOW(login_fd, 8) = RFIFOW(fd,34); // month + WFIFOW(login_fd,10) = RFIFOW(fd,36); // day + WFIFOW(login_fd,12) = RFIFOW(fd,38); // hour + WFIFOW(login_fd,14) = RFIFOW(fd,40); // minute + WFIFOW(login_fd,16) = RFIFOW(fd,42); // second + WFIFOSET(login_fd,18); +// printf("char : status -> login: account %d, ban: %dy %dm %dd %dh %dmn %ds\n", +// char_dat[i].account_id, (short)RFIFOW(fd,32), (short)RFIFOW(fd,34), (short)RFIFOW(fd,36), (short)RFIFOW(fd,38), (short)RFIFOW(fd,40), (short)RFIFOW(fd,42)); + } else + WFIFOW(fd,32) = 3; // answer: 0-login-server resquest done, 1-player not found, 2-gm level too low, 3-login-server offline + } else + WFIFOW(fd,32) = 2; // answer: 0-login-server resquest done, 1-player not found, 2-gm level too low, 3-login-server offline + break; + case 3: // unblock + if (acc == -1 || isGM(acc) >= isGM(char_dat[i].account_id)) { + if (login_fd > 0) { // don't send request if no login-server + WFIFOW(login_fd,0) = 0x2724; + WFIFOL(login_fd,2) = char_dat[i].account_id; // account value + WFIFOL(login_fd,6) = 0; // status of the account + WFIFOSET(login_fd, 10); +// printf("char : status -> login: account %d, status: %d \n", char_dat[i].account_id, 0); + } else + WFIFOW(fd,32) = 3; // answer: 0-login-server resquest done, 1-player not found, 2-gm level too low, 3-login-server offline + } else + WFIFOW(fd,32) = 2; // answer: 0-login-server resquest done, 1-player not found, 2-gm level too low, 3-login-server offline + break; + case 4: // unban + if (acc == -1 || isGM(acc) >= isGM(char_dat[i].account_id)) { + if (login_fd > 0) { // don't send request if no login-server + WFIFOW(login_fd, 0) = 0x272a; + WFIFOL(login_fd, 2) = char_dat[i].account_id; // account value + WFIFOSET(login_fd, 6); +// printf("char : status -> login: account %d, unban request\n", char_dat[i].account_id); + } else + WFIFOW(fd,32) = 3; // answer: 0-login-server resquest done, 1-player not found, 2-gm level too low, 3-login-server offline + } else + WFIFOW(fd,32) = 2; // answer: 0-login-server resquest done, 1-player not found, 2-gm level too low, 3-login-server offline + break; + case 5: // changesex + if (acc == -1 || isGM(acc) >= isGM(char_dat[i].account_id)) { + if (login_fd > 0) { // don't send request if no login-server + WFIFOW(login_fd, 0) = 0x2727; + WFIFOL(login_fd, 2) = char_dat[i].account_id; // account value + WFIFOSET(login_fd, 6); +// printf("char : status -> login: account %d, change sex request\n", char_dat[i].account_id); + } else + WFIFOW(fd,32) = 3; // answer: 0-login-server resquest done, 1-player not found, 2-gm level too low, 3-login-server offline + } else + WFIFOW(fd,32) = 2; // answer: 0-login-server resquest done, 1-player not found, 2-gm level too low, 3-login-server offline + break; + } + } else { + // character name not found + memcpy(WFIFOP(fd,6), character_name, 24); + WFIFOW(fd,32) = 1; // answer: 0-login-server resquest done, 1-player not found, 2-gm level too low, 3-login-server offline + } + // send answer if a player ask, not if the server ask + if (acc != -1) { + WFIFOSET(fd, 34); + } + RFIFOSKIP(fd, 44); + break; + } + +// case 0x2b0f: not more used (available for futur usage) + + // account_reg保存要求 + case 0x2b10: + if (RFIFOREST(fd) < 4 || RFIFOREST(fd) < RFIFOW(fd,2)) + return 0; + { + struct global_reg reg[ACCOUNT_REG2_NUM]; + int p, acc; + acc = RFIFOL(fd,4); + for(p = 8, j = 0; p < RFIFOW(fd,2) && j < ACCOUNT_REG2_NUM; p += 36, j++) { + memcpy(reg[j].str, RFIFOP(fd,p), 32); + reg[j].value = RFIFOL(fd, p+32); + } + set_account_reg2(acc, j, reg); + // loginサーバーへ送る + if (login_fd > 0) { // don't send request if no login-server + WFIFOW(login_fd, 0) = 0x2728; + memcpy(WFIFOP(login_fd,0), RFIFOP(fd,0), RFIFOW(fd,2)); + WFIFOSET(login_fd, WFIFOW(login_fd,2)); + } + // ワールドへの同垢ログインがなければmapサーバーに送る必要はない + //memcpy(buf, RFIFOP(fd,0), RFIFOW(fd,2)); + //WBUFW(buf,0) = 0x2b11; + //mapif_sendall(buf, WBUFW(buf,2)); + RFIFOSKIP(fd, RFIFOW(fd,2)); +// printf("char: save_account_reg (from map)\n"); + break; + } + + default: + // inter server処理に渡す + { + int r = inter_parse_frommap(fd); + if (r == 1) // 処理できた + break; + if (r == 2) // パケット長が足りない + return 0; + } + // inter server処理でもない場合は切断 + printf("char: unknown packet 0x%04x (%d bytes to read in buffer)! (from map).\n", RFIFOW(fd,0), RFIFOREST(fd)); + session[fd]->eof = 1; + return 0; + } + } + return 0; +} + +int search_mapserver(char *map) { + int i, j; + char temp_map[16]; + int temp_map_len; + +// printf("Searching the map-server for map '%s'... ", map); + strncpy(temp_map, map, sizeof(temp_map)); + temp_map[sizeof(temp_map)-1] = '\0'; + if (strchr(temp_map, '.') != NULL) + temp_map[strchr(temp_map, '.') - temp_map + 1] = '\0'; // suppress the '.gat', but conserve the '.' to be sure of the name of the map + + temp_map_len = strlen(temp_map); + for(i = 0; i < MAX_MAP_SERVERS; i++) + if (server_fd[i] >= 0) + for (j = 0; server[i].map[j][0]; j++) + //printf("%s : %s = %d\n", server[i].map[j], map, strncmp(server[i].map[j], temp_map, temp_map_len)); + if (strncmp(server[i].map[j], temp_map, temp_map_len) == 0) { +// printf("found -> server #%d.\n", i); + return i; + } + +// printf("not found.\n"); + return -1; +} + +// char_mapifの初期化処理(現在はinter_mapif初期化のみ) +static int char_mapif_init(int fd) { + return inter_mapif_init(fd); +} + +//----------------------------------------------------- +// Test to know if an IP come from LAN or WAN. by [Yor] +//----------------------------------------------------- +int lan_ip_check(unsigned char *p){ + int i; + int lancheck = 1; + +// printf("lan_ip_check: to compare: %d.%d.%d.%d, network: %d.%d.%d.%d/%d.%d.%d.%d\n", +// p[0], p[1], p[2], p[3], +// subneti[0], subneti[1], subneti[2], subneti[3], +// subnetmaski[0], subnetmaski[1], subnetmaski[2], subnetmaski[3]); + for(i = 0; i < 4; i++) { + if ((subneti[i] & subnetmaski[i]) != (p[i] & subnetmaski[i])) { + lancheck = 0; + break; + } + } + printf("LAN test (result): %s source\033[0m.\n", (lancheck) ? "\033[1;36mLAN" : "\033[1;32mWAN"); + return lancheck; +} + +int parse_char(int fd) { + int i, ch; + char email[40]; + struct char_session_data *sd; + unsigned char *p = (unsigned char *) &session[fd]->client_addr.sin_addr; + + if (login_fd < 0 || session[fd]->eof) { // disconnect any player (already connected to char-server or coming back from map-server) if login-server is diconnected. + if (fd == login_fd) + login_fd = -1; + close(fd); + delete_session(fd); + return 0; + } + + sd = session[fd]->session_data; + + while (RFIFOREST(fd) >= 2) { +// if (RFIFOW(fd,0) < 30000) +// printf("parse_char: connection #%d, packet: 0x%x (with being read: %d bytes).\n", fd, RFIFOW(fd,0), RFIFOREST(fd)); + + switch(RFIFOW(fd,0)) { + case 0x20b: //20040622暗号化ragexe対応 + if (RFIFOREST(fd) < 19) + return 0; + RFIFOSKIP(fd,19); + break; + + case 0x65: // 接続要求 + if (RFIFOREST(fd) < 17) + return 0; + { + int GM_value; + if ((GM_value = isGM(RFIFOL(fd,2)))) + printf("Account Logged On; Account ID: %d (GM level %d).\n", RFIFOL(fd,2), GM_value); + else + printf("Account Logged On; Account ID: %d.\n", RFIFOL(fd,2)); + if (sd == NULL) { + sd = session[fd]->session_data = calloc(sizeof(struct char_session_data), 1); + memset(sd, 0, sizeof(struct char_session_data)); + memcpy(sd->email, "no mail", 40); // put here a mail without '@' to refuse deletion if we don't receive the e-mail + sd->connect_until_time = 0; // unknow or illimited (not displaying on map-server) + } + sd->account_id = RFIFOL(fd,2); + sd->login_id1 = RFIFOL(fd,6); + sd->login_id2 = RFIFOL(fd,10); + sd->sex = RFIFOB(fd,16); + // send back account_id + WFIFOL(fd,0) = RFIFOL(fd,2); + WFIFOSET(fd,4); + // search authentification + for(i = 0; i < AUTH_FIFO_SIZE; i++) { + if (auth_fifo[i].account_id == sd->account_id && + auth_fifo[i].login_id1 == sd->login_id1 && +#if CMP_AUTHFIFO_LOGIN2 != 0 + auth_fifo[i].login_id2 == sd->login_id2 && // relate to the versions higher than 18 +#endif + (!check_ip_flag || auth_fifo[i].ip == session[fd]->client_addr.sin_addr.s_addr) && + auth_fifo[i].delflag == 2) { + auth_fifo[i].delflag = 1; + if (max_connect_user == 0 || count_users() < max_connect_user) { + if (login_fd > 0) { // don't send request if no login-server + // request to login-server to obtain e-mail/time limit + WFIFOW(login_fd,0) = 0x2716; + WFIFOL(login_fd,2) = sd->account_id; + WFIFOSET(login_fd,6); + } + // send characters to player + mmo_char_send006b(fd, sd); + } else { + // refuse connection (over populated) + WFIFOW(fd,0) = 0x6c; + WFIFOW(fd,2) = 0; + WFIFOSET(fd,3); + } + break; + } + } + // authentification not found + if (i == AUTH_FIFO_SIZE) { + if (login_fd > 0) { // don't send request if no login-server + WFIFOW(login_fd,0) = 0x2712; // ask login-server to authentify an account + WFIFOL(login_fd,2) = sd->account_id; + WFIFOL(login_fd,6) = sd->login_id1; + WFIFOL(login_fd,10) = sd->login_id2; // relate to the versions higher than 18 + WFIFOB(login_fd,14) = sd->sex; + WFIFOL(login_fd,15) = session[fd]->client_addr.sin_addr.s_addr; + WFIFOSET(login_fd,19); + } else { // if no login-server, we must refuse connection + WFIFOW(fd,0) = 0x6c; + WFIFOW(fd,2) = 0; + WFIFOSET(fd,3); + } + } + } + RFIFOSKIP(fd,17); + break; + + case 0x66: // キャラ選択 + if (RFIFOREST(fd) < 3) + return 0; + + // if we activated email creation and email is default email + if (email_creation != 0 && strcmp(sd->email, "a@a.com") == 0 && login_fd > 0) { // to modify an e-mail, login-server must be online + WFIFOW(fd, 0) = 0x70; + WFIFOB(fd, 2) = 0; // 00 = Incorrect Email address + WFIFOSET(fd, 3); + + // otherwise, load the character + } else { + for (ch = 0; ch < 9; ch++) + if (sd->found_char[ch] >= 0 && char_dat[sd->found_char[ch]].char_num == RFIFOB(fd,2)) + break; + if (ch != 9) { + char_log("Character Selected, Account ID: %d, Character Slot: %d, Character Name: %s." RETCODE, + sd->account_id, RFIFOB(fd,2), char_dat[sd->found_char[ch]].name); + // searching map server + i = search_mapserver(char_dat[sd->found_char[ch]].last_point.map); + // if map is not found, we check major cities + if (i < 0) { + if ((i = search_mapserver("prontera.gat")) >= 0) { // check is done without 'gat'. + memcpy(char_dat[sd->found_char[ch]].last_point.map, "prontera.gat", 16); + char_dat[sd->found_char[ch]].last_point.x = 273; // savepoint coordonates + char_dat[sd->found_char[ch]].last_point.y = 354; + } else if ((i = search_mapserver("geffen.gat")) >= 0) { // check is done without 'gat'. + memcpy(char_dat[sd->found_char[ch]].last_point.map, "geffen.gat", 16); + char_dat[sd->found_char[ch]].last_point.x = 120; // savepoint coordonates + char_dat[sd->found_char[ch]].last_point.y = 100; + } else if ((i = search_mapserver("morocc.gat")) >= 0) { // check is done without 'gat'. + memcpy(char_dat[sd->found_char[ch]].last_point.map, "morocc.gat", 16); + char_dat[sd->found_char[ch]].last_point.x = 160; // savepoint coordonates + char_dat[sd->found_char[ch]].last_point.y = 94; + } else if ((i = search_mapserver("alberta.gat")) >= 0) { // check is done without 'gat'. + memcpy(char_dat[sd->found_char[ch]].last_point.map, "alberta.gat", 16); + char_dat[sd->found_char[ch]].last_point.x = 116; // savepoint coordonates + char_dat[sd->found_char[ch]].last_point.y = 57; + } else if ((i = search_mapserver("payon.gat")) >= 0) { // check is done without 'gat'. + memcpy(char_dat[sd->found_char[ch]].last_point.map, "payon.gat", 16); + char_dat[sd->found_char[ch]].last_point.x = 87; // savepoint coordonates + char_dat[sd->found_char[ch]].last_point.y = 117; + } else if ((i = search_mapserver("izlude.gat")) >= 0) { // check is done without 'gat'. + memcpy(char_dat[sd->found_char[ch]].last_point.map, "izlude.gat", 16); + char_dat[sd->found_char[ch]].last_point.x = 94; // savepoint coordonates + char_dat[sd->found_char[ch]].last_point.y = 103; + } else { + int j; + // get first online server (with a map) + i = 0; + for(j = 0; j < MAX_MAP_SERVERS; j++) + if (server_fd[j] >= 0 && server[j].map[0][0]) { // change save point to one of map found on the server (the first) + i = j; + memcpy(char_dat[sd->found_char[ch]].last_point.map, server[j].map[0], 16); + printf("Map-server #%d found with a map: '%s'.\n", j, server[j].map[0]); + // coordonates are unknown + break; + } + // if no map-server is connected, we send: server closed + if (j == MAX_MAP_SERVERS) { + WFIFOW(fd,0) = 0x81; + WFIFOL(fd,2) = 1; // 01 = Server closed + WFIFOSET(fd,3); + RFIFOSKIP(fd,3); + break; + } + } + } + WFIFOW(fd,0) = 0x71; + WFIFOL(fd,2) = char_dat[sd->found_char[ch]].char_id; + memcpy(WFIFOP(fd,6), char_dat[sd->found_char[ch]].last_point.map, 16); + printf("Character selection '%s' (account: %d, slot: %d).\n", char_dat[sd->found_char[ch]].name, sd->account_id, ch); + printf("--Send IP of map-server. "); + if (lan_ip_check(p)) + WFIFOL(fd, 22) = inet_addr(lan_map_ip); + else + WFIFOL(fd, 22) = server[i].ip; + WFIFOW(fd,26) = server[i].port; + WFIFOSET(fd,28); + if (auth_fifo_pos >= AUTH_FIFO_SIZE) + auth_fifo_pos = 0; + //printf("auth_fifo set #%d - account %d, char: %d, secure: %08x-%08x\n", auth_fifo_pos, sd->account_id, char_dat[sd->found_char[ch]].char_id, sd->login_id1, sd->login_id2); + auth_fifo[auth_fifo_pos].account_id = sd->account_id; + auth_fifo[auth_fifo_pos].char_id = char_dat[sd->found_char[ch]].char_id; + auth_fifo[auth_fifo_pos].login_id1 = sd->login_id1; + auth_fifo[auth_fifo_pos].login_id2 = sd->login_id2; + auth_fifo[auth_fifo_pos].delflag = 0; + auth_fifo[auth_fifo_pos].char_pos = sd->found_char[ch]; + auth_fifo[auth_fifo_pos].sex = sd->sex; + auth_fifo[auth_fifo_pos].connect_until_time = sd->connect_until_time; + auth_fifo[auth_fifo_pos].ip = session[fd]->client_addr.sin_addr.s_addr; + auth_fifo_pos++; + } + } + RFIFOSKIP(fd,3); + break; + + case 0x67: // 作成 + if (RFIFOREST(fd) < 37) + return 0; + i = make_new_char(fd, RFIFOP(fd,2)); + if (i < 0) { + WFIFOW(fd,0) = 0x6e; + WFIFOB(fd,2) = 0x00; + WFIFOSET(fd,3); + RFIFOSKIP(fd,37); + break; + } + + WFIFOW(fd,0) = 0x6d; + memset(WFIFOP(fd,2), 0, 106); + + WFIFOL(fd,2) = char_dat[i].char_id; + WFIFOL(fd,2+4) = char_dat[i].base_exp; + WFIFOL(fd,2+8) = char_dat[i].zeny; + WFIFOL(fd,2+12) = char_dat[i].job_exp; + WFIFOL(fd,2+16) = char_dat[i].job_level; + + WFIFOL(fd,2+28) = char_dat[i].karma; + WFIFOL(fd,2+32) = char_dat[i].manner; + + WFIFOW(fd,2+40) = 0x30; + WFIFOW(fd,2+42) = (char_dat[i].hp > 0x7fff) ? 0x7fff : char_dat[i].hp; + WFIFOW(fd,2+44) = (char_dat[i].max_hp > 0x7fff) ? 0x7fff : char_dat[i].max_hp; + WFIFOW(fd,2+46) = (char_dat[i].sp > 0x7fff) ? 0x7fff : char_dat[i].sp; + WFIFOW(fd,2+48) = (char_dat[i].max_sp > 0x7fff) ? 0x7fff : char_dat[i].max_sp; + WFIFOW(fd,2+50) = DEFAULT_WALK_SPEED; // char_dat[i].speed; + WFIFOW(fd,2+52) = char_dat[i].class; + WFIFOW(fd,2+54) = char_dat[i].hair; + + WFIFOW(fd,2+58) = char_dat[i].base_level; + WFIFOW(fd,2+60) = char_dat[i].skill_point; + + WFIFOW(fd,2+64) = char_dat[i].shield; + WFIFOW(fd,2+66) = char_dat[i].head_top; + WFIFOW(fd,2+68) = char_dat[i].head_mid; + WFIFOW(fd,2+70) = char_dat[i].hair_color; + + memcpy(WFIFOP(fd,2+74), char_dat[i].name, 24); + + WFIFOB(fd,2+98) = (char_dat[i].str > 255) ? 255 : char_dat[i].str; + WFIFOB(fd,2+99) = (char_dat[i].agi > 255) ? 255 : char_dat[i].agi; + WFIFOB(fd,2+100) = (char_dat[i].vit > 255) ? 255 : char_dat[i].vit; + WFIFOB(fd,2+101) = (char_dat[i].int_ > 255) ? 255 : char_dat[i].int_; + WFIFOB(fd,2+102) = (char_dat[i].dex > 255) ? 255 : char_dat[i].dex; + WFIFOB(fd,2+103) = (char_dat[i].luk > 255) ? 255 : char_dat[i].luk; + WFIFOB(fd,2+104) = char_dat[i].char_num; + + WFIFOSET(fd,108); + RFIFOSKIP(fd,37); + for(ch = 0; ch < 9; ch++) { + if (sd->found_char[ch] == -1) { + sd->found_char[ch] = i; + break; + } + } + + case 0x68: // delete char //Yor's Fix + if (RFIFOREST(fd) < 46) + return 0; + memcpy(email, RFIFOP(fd,6), 40); + if (e_mail_check(email) == 0) + strncpy(email, "a@a.com", 40); // default e-mail + + // if we activated email creation and email is default email + if (email_creation != 0 && strcmp(sd->email, "a@a.com") == 0 && login_fd > 0) { // to modify an e-mail, login-server must be online + // if sended email is incorrect e-mail + if (strcmp(email, "a@a.com") == 0) { + WFIFOW(fd, 0) = 0x70; + WFIFOB(fd, 2) = 0; // 00 = Incorrect Email address + WFIFOSET(fd, 3); + RFIFOSKIP(fd,46); + // we act like we have selected a character + } else { + // we change the packet to set it like selection. + for (i = 0; i < 9; i++) + if (char_dat[sd->found_char[i]].char_id == RFIFOL(fd,2)) { + // we save new e-mail + memcpy(sd->email, email, 40); + // we send new e-mail to login-server ('online' login-server is checked before) + WFIFOW(login_fd,0) = 0x2715; + WFIFOL(login_fd,2) = sd->account_id; + memcpy(WFIFOP(login_fd, 6), email, 40); + WFIFOSET(login_fd,46); + // skip part of the packet! (46, but leave the size of select packet: 3) + RFIFOSKIP(fd,43); + // change value to put new packet (char selection) + RFIFOW(fd, 0) = 0x66; + RFIFOB(fd, 2) = char_dat[sd->found_char[i]].char_num; + // not send packet, it's modify of actual packet + break; + } + if (i == 9) { + WFIFOW(fd, 0) = 0x70; + WFIFOB(fd, 2) = 0; // 00 = Incorrect Email address + WFIFOSET(fd, 3); + RFIFOSKIP(fd,46); + } + } + + // otherwise, we delete the character + } else { + if (strcmpi(email, sd->email) != 0) { // if it's an invalid email + WFIFOW(fd, 0) = 0x70; + WFIFOB(fd, 2) = 0; // 00 = Incorrect Email address + WFIFOSET(fd, 3); + // if mail is correct + } else { + for (i = 0; i < 9; i++) { + struct mmo_charstatus *cs = NULL; + if ((cs = &char_dat[sd->found_char[i]])->char_id == RFIFOL(fd,2)) { + char_delete(cs); // deletion process + + if (sd->found_char[i] != char_num - 1) { + memcpy(&char_dat[sd->found_char[i]], &char_dat[char_num-1], sizeof(struct mmo_charstatus)); + // Correct moved character reference in the character's owner + { + int j, k; + struct char_session_data *sd2; + for (j = 0; j < fd_max; j++) { + if (session[j] && (sd2 = session[j]->session_data) && + sd2->account_id == char_dat[char_num-1].account_id) { + for (k = 0; k < 9; k++) { + if (sd2->found_char[k] == char_num-1) { + sd2->found_char[k] = sd->found_char[i]; + break; + } + } + break; + } + } + } + } + + char_num--; + for(ch = i; ch < 9-1; ch++) + sd->found_char[ch] = sd->found_char[ch+1]; + sd->found_char[8] = -1; + WFIFOW(fd,0) = 0x6f; + WFIFOSET(fd,2); + break; + } + } + + if (i == 9) { + WFIFOW(fd,0) = 0x70; + WFIFOB(fd,2) = 0; + WFIFOSET(fd,3); + } + } + RFIFOSKIP(fd,46); + } + break; + + case 0x2af8: // マップサーバーログイン + if (RFIFOREST(fd) < 60) + return 0; + WFIFOW(fd,0) = 0x2af9; + for(i = 0; i < MAX_MAP_SERVERS; i++) { + if (server_fd[i] < 0) + break; + } + if (i == MAX_MAP_SERVERS || strcmp(RFIFOP(fd,2), userid) || strcmp(RFIFOP(fd,26), passwd)){ + WFIFOB(fd,2) = 3; + WFIFOSET(fd,3); + RFIFOSKIP(fd,60); + } else { + int len; + WFIFOB(fd,2) = 0; + session[fd]->func_parse = parse_frommap; + server_fd[i] = fd; + if(anti_freeze_enable) + server_freezeflag[i] = 5; // Map anti-freeze system. Counter. 5 ok, 4...0 freezed + server[i].ip = RFIFOL(fd,54); + server[i].port = RFIFOW(fd,58); + server[i].users = 0; + memset(server[i].map, 0, sizeof(server[i].map)); + WFIFOSET(fd,3); + RFIFOSKIP(fd,60); + realloc_fifo(fd, FIFOSIZE_SERVERLINK, FIFOSIZE_SERVERLINK); + char_mapif_init(fd); + // send gm acccounts level to map-servers + len = 4; + WFIFOW(fd,0) = 0x2b15; + for(i = 0; i < GM_num; i++) { + WFIFOL(fd,len) = gm_account[i].account_id; + WFIFOB(fd,len+4) = (unsigned char)gm_account[i].level; + len += 5; + } + WFIFOW(fd,2) = len; + WFIFOSET(fd,len); + return 0; + } + break; + + case 0x187: // Alive信号? + if (RFIFOREST(fd) < 6) + return 0; + RFIFOSKIP(fd, 6); + break; + + case 0x7530: // Athena情報所得 + WFIFOW(fd,0) = 0x7531; + WFIFOB(fd,2) = ATHENA_MAJOR_VERSION; + WFIFOB(fd,3) = ATHENA_MINOR_VERSION; + WFIFOB(fd,4) = ATHENA_REVISION; + WFIFOB(fd,5) = ATHENA_RELEASE_FLAG; + WFIFOB(fd,6) = ATHENA_OFFICIAL_FLAG; + WFIFOB(fd,7) = ATHENA_SERVER_INTER | ATHENA_SERVER_CHAR; + WFIFOW(fd,8) = ATHENA_MOD_VERSION; + WFIFOSET(fd,10); + RFIFOSKIP(fd,2); + return 0; + + case 0x7532: // 接続の切断(defaultと処理は一緒だが明示的にするため) + session[fd]->eof = 1; + return 0; + + default: + session[fd]->eof = 1; + return 0; + } + } + RFIFOFLUSH(fd); + return 0; +} + +// 全てのMAPサーバーにデータ送信(送信したmap鯖の数を返す) +int mapif_sendall(unsigned char *buf, unsigned int len) { + int i, c; + + c = 0; + for(i = 0; i < MAX_MAP_SERVERS; i++) { + int fd; + if ((fd = server_fd[i]) >= 0) { + memcpy(WFIFOP(fd,0), buf, len); + WFIFOSET(fd,len); + c++; + } + } + return c; +} + +// 自分以外の全てのMAPサーバーにデータ送信(送信したmap鯖の数を返す) +int mapif_sendallwos(int sfd, unsigned char *buf, unsigned int len) { + int i, c; + + c = 0; + for(i = 0; i < MAX_MAP_SERVERS; i++) { + int fd; + if ((fd = server_fd[i]) >= 0 && fd != sfd) { + memcpy(WFIFOP(fd,0), buf, len); + WFIFOSET(fd, len); + c++; + } + } + return c; +} +// MAPサーバーにデータ送信(map鯖生存確認有り) +int mapif_send(int fd, unsigned char *buf, unsigned int len) { + int i; + + if (fd >= 0) { + for(i = 0; i < MAX_MAP_SERVERS; i++) { + if (fd == server_fd[i]) { + memcpy(WFIFOP(fd,0), buf, len); + WFIFOSET(fd,len); + return 1; + } + } + } + return 0; +} + +int send_users_tologin(int tid, unsigned int tick, int id, int data) { + int users = count_users(); + char buf[16]; + + if (login_fd > 0 && session[login_fd]) { + // send number of user to login server + WFIFOW(login_fd,0) = 0x2714; + WFIFOL(login_fd,2) = users; + WFIFOSET(login_fd,6); + } + // send number of players to all map-servers + WBUFW(buf,0) = 0x2b00; + WBUFL(buf,2) = users; + mapif_sendall(buf, 6); + + return 0; +} + +int check_connect_login_server(int tid, unsigned int tick, int id, int data) { + if (login_fd <= 0 || session[login_fd] == NULL) { + printf("Attempt to connect to login-server...\n"); + login_fd = make_connection(login_ip, login_port); + session[login_fd]->func_parse = parse_tologin; + realloc_fifo(login_fd, FIFOSIZE_SERVERLINK, FIFOSIZE_SERVERLINK); + WFIFOW(login_fd,0) = 0x2710; + memset(WFIFOP(login_fd,2), 0, 24); + memcpy(WFIFOP(login_fd,2), userid, strlen(userid) < 24 ? strlen(userid) : 24); + memset(WFIFOP(login_fd,26), 0, 24); + memcpy(WFIFOP(login_fd,26), passwd, strlen(passwd) < 24 ? strlen(passwd) : 24); + WFIFOL(login_fd,50) = 0; + WFIFOL(login_fd,54) = char_ip; + WFIFOL(login_fd,58) = char_port; + memset(WFIFOP(login_fd,60), 0, 20); + memcpy(WFIFOP(login_fd,60), server_name, strlen(server_name) < 20 ? strlen(server_name) : 20); + WFIFOW(login_fd,80) = 0; + WFIFOW(login_fd,82) = char_maintenance; + WFIFOW(login_fd,84) = char_new; + WFIFOSET(login_fd,86); + } + return 0; +} + +//---------------------------------------------------------- +// Return numerical value of a switch configuration by [Yor] +// on/off, english, fran軋is, deutsch, espaol +//---------------------------------------------------------- +int config_switch(const char *str) { + if (strcmpi(str, "on") == 0 || strcmpi(str, "yes") == 0 || strcmpi(str, "oui") == 0 || strcmpi(str, "ja") == 0 || strcmpi(str, "si") == 0) + return 1; + if (strcmpi(str, "off") == 0 || strcmpi(str, "no") == 0 || strcmpi(str, "non") == 0 || strcmpi(str, "nein") == 0) + return 0; + + return atoi(str); +} + +//------------------------------------------- +// Reading Lan Support configuration by [Yor] +//------------------------------------------- +int lan_config_read(const char *lancfgName) { + int j; + struct hostent * h = NULL; + char line[1024], w1[1024], w2[1024]; + FILE *fp; + + // set default configuration + strncpy(lan_map_ip, "127.0.0.1", sizeof(lan_map_ip)); + subneti[0] = 127; + subneti[1] = 0; + subneti[2] = 0; + subneti[3] = 1; + for(j = 0; j < 4; j++) + subnetmaski[j] = 255; + + fp = fopen(lancfgName, "r"); + + if (fp == NULL) { + printf("LAN support configuration file not found: %s\n", lancfgName); + return 1; + } + + printf ("---start reading of Lan Support configuration...\n"); + + while(fgets(line, sizeof(line)-1, fp)) { + if (line[0] == '/' && line[1] == '/') + continue; + + line[sizeof(line)-1] = '\0'; + if (sscanf(line, "%[^:]: %[^\r\n]", w1, w2) != 2) + continue; + + remove_control_chars(w1); + remove_control_chars(w2); + if (strcmpi(w1, "lan_map_ip") == 0) { // Read map-server Lan IP Address + h = gethostbyname(w2); + if (h != NULL) { + sprintf(lan_map_ip, "%d.%d.%d.%d", (unsigned char)h->h_addr[0], (unsigned char)h->h_addr[1], (unsigned char)h->h_addr[2], (unsigned char)h->h_addr[3]); + } else { + strncpy(lan_map_ip, w2, sizeof(lan_map_ip)); + lan_map_ip[sizeof(lan_map_ip)-1] = 0; + } + printf("LAN IP of map-server: %s.\n", lan_map_ip); + } else if (strcmpi(w1, "subnet") == 0) { // Read Subnetwork + for(j = 0; j < 4; j++) + subneti[j] = 0; + h = gethostbyname(w2); + if (h != NULL) { + for(j = 0; j < 4; j++) + subneti[j] = (unsigned char)h->h_addr[j]; + } else { + sscanf(w2, "%d.%d.%d.%d", &subneti[0], &subneti[1], &subneti[2], &subneti[3]); + } + printf("Sub-network of the map-server: %d.%d.%d.%d.\n", subneti[0], subneti[1], subneti[2], subneti[3]); + } else if (strcmpi(w1, "subnetmask") == 0){ // Read Subnetwork Mask + for(j = 0; j < 4; j++) + subnetmaski[j] = 255; + h = gethostbyname(w2); + if (h != NULL) { + for(j = 0; j < 4; j++) + subnetmaski[j] = (unsigned char)h->h_addr[j]; + } else { + sscanf(w2, "%d.%d.%d.%d", &subnetmaski[0], &subnetmaski[1], &subnetmaski[2], &subnetmaski[3]); + } + printf("Sub-network mask of the map-server: %d.%d.%d.%d.\n", subnetmaski[0], subnetmaski[1], subnetmaski[2], subnetmaski[3]); + } + } + fclose(fp); + + // sub-network check of the map-server + { + unsigned int a0, a1, a2, a3; + unsigned char p[4]; + sscanf(lan_map_ip, "%d.%d.%d.%d", &a0, &a1, &a2, &a3); + p[0] = a0; p[1] = a1; p[2] = a2; p[3] = a3; + printf("LAN test of LAN IP of the map-server: "); + if (lan_ip_check(p) == 0) { + printf("\033[1;31m***ERROR: LAN IP of the map-server doesn't belong to the specified Sub-network.\033[0m\n"); + } + } + + printf("---End reading of Lan Support configuration...\n"); + + return 0; +} + +int char_config_read(const char *cfgName) { + struct hostent *h = NULL; + char line[1024], w1[1024], w2[1024]; + FILE *fp = fopen(cfgName, "r"); + + if (fp == NULL) { + printf("Configuration file not found: %s.\n", cfgName); + exit(1); + } + + while(fgets(line, sizeof(line)-1, fp)) { + if (line[0] == '/' && line[1] == '/') + continue; + + line[sizeof(line)-1] = '\0'; + if (sscanf(line, "%[^:]: %[^\r\n]", w1, w2) != 2) + continue; + + remove_control_chars(w1); + remove_control_chars(w2); + if (strcmpi(w1, "userid") == 0) { + memcpy(userid, w2, 24); + } else if (strcmpi(w1, "passwd") == 0) { + memcpy(passwd, w2, 24); + } else if (strcmpi(w1, "server_name") == 0) { + memcpy(server_name, w2, sizeof(server_name)); + server_name[sizeof(server_name) - 1] = '\0'; + printf("%s server has been intialized\n", w2); + } else if (strcmpi(w1, "wisp_server_name") == 0) { + if (strlen(w2) >= 4) { + memcpy(wisp_server_name, w2, sizeof(wisp_server_name)); + wisp_server_name[sizeof(wisp_server_name) - 1] = '\0'; + } + } else if (strcmpi(w1, "login_ip") == 0) { + h = gethostbyname(w2); + if (h != NULL) { + printf("Login server IP address : %s -> %d.%d.%d.%d\n", w2, (unsigned char)h->h_addr[0], (unsigned char)h->h_addr[1], (unsigned char)h->h_addr[2], (unsigned char)h->h_addr[3]); + sprintf(login_ip_str, "%d.%d.%d.%d", (unsigned char)h->h_addr[0], (unsigned char)h->h_addr[1], (unsigned char)h->h_addr[2], (unsigned char)h->h_addr[3]); + } else + memcpy(login_ip_str, w2, 16); + } else if (strcmpi(w1, "login_port") == 0) { + login_port = atoi(w2); + } else if (strcmpi(w1, "char_ip") == 0) { + h = gethostbyname(w2); + if (h != NULL) { + printf("Character server IP address : %s -> %d.%d.%d.%d\n", w2, (unsigned char)h->h_addr[0], (unsigned char)h->h_addr[1], (unsigned char)h->h_addr[2], (unsigned char)h->h_addr[3]); + sprintf(char_ip_str, "%d.%d.%d.%d", (unsigned char)h->h_addr[0], (unsigned char)h->h_addr[1], (unsigned char)h->h_addr[2], (unsigned char)h->h_addr[3]); + } else + memcpy(char_ip_str, w2, 16); + } else if (strcmpi(w1, "char_port") == 0) { + char_port = atoi(w2); + } else if (strcmpi(w1, "char_maintenance") == 0) { + char_maintenance = atoi(w2); + } else if (strcmpi(w1, "char_new") == 0) { + char_new = atoi(w2); + } else if (strcmpi(w1, "email_creation") == 0) { + email_creation = config_switch(w2); + } else if (strcmpi(w1, "char_txt") == 0) { + strcpy(char_txt, w2); + } else if (strcmpi(w1, "backup_txt") == 0) { //By zanetheinsane + strcpy(backup_txt, w2); + } else if (strcmpi(w1, "backup_txt_flag") == 0) { // The backup_txt file was created because char deletion bug existed. Now it's finish and that take a lot of time to create a second file when there are a lot of characters. By [Yor] + backup_txt_flag = config_switch(w2); + } else if (strcmpi(w1, "max_connect_user") == 0) { + max_connect_user = atoi(w2); + if (max_connect_user < 0) + max_connect_user = 0; // unlimited online players + } else if (strcmpi(w1, "check_ip_flag") == 0) { + check_ip_flag = config_switch(w2); + } else if (strcmpi(w1, "autosave_time") == 0) { + autosave_interval = atoi(w2)*1000; + if (autosave_interval <= 0) + autosave_interval = DEFAULT_AUTOSAVE_INTERVAL; + } else if (strcmpi(w1, "start_point") == 0) { + char map[32]; + int x, y; + if (sscanf(w2, "%[^,],%d,%d", map, &x, &y) < 3) + continue; + if (strstr(map, ".gat") != NULL) { // Verify at least if '.gat' is in the map name + memcpy(start_point.map, map, 16); + start_point.x = x; + start_point.y = y; + } + } else if (strcmpi(w1, "start_zeny") == 0) { + start_zeny = atoi(w2); + if (start_zeny < 0) + start_zeny = 0; + } else if (strcmpi(w1, "start_weapon") == 0) { + start_zeny = atoi(w2); + if (start_weapon < 0) + start_weapon = 0; + } else if (strcmpi(w1, "start_armor") == 0) { + start_zeny = atoi(w2); + if (start_armor < 0) + start_armor = 0; + } else if (strcmpi(w1, "unknown_char_name") == 0) { + strcpy(unknown_char_name, w2); + unknown_char_name[24] = 0; + } else if (strcmpi(w1, "char_log_filename") == 0) { + strcpy(char_log_filename, w2); + } else if (strcmpi(w1, "name_ignoring_case") == 0) { + name_ignoring_case = config_switch(w2); + } else if (strcmpi(w1, "char_name_option") == 0) { + char_name_option = atoi(w2); + } else if (strcmpi(w1, "char_name_letters") == 0) { + strcpy(char_name_letters, w2); +// online files options + } else if (strcmpi(w1, "online_txt_filename") == 0) { + strcpy(online_txt_filename, w2); + } else if (strcmpi(w1, "online_html_filename") == 0) { + strcpy(online_html_filename, w2); + } else if (strcmpi(w1, "online_sorting_option") == 0) { + online_sorting_option = atoi(w2); + } else if (strcmpi(w1, "online_display_option") == 0) { + online_display_option = atoi(w2); + } else if (strcmpi(w1, "online_gm_display_min_level") == 0) { // minimum GM level to display 'GM' when we want to display it + online_gm_display_min_level = atoi(w2); + if (online_gm_display_min_level < 5) // send online file every 5 seconds to player is enough + online_gm_display_min_level = 5; + } else if (strcmpi(w1, "online_refresh_html") == 0) { + online_refresh_html = atoi(w2); + if (online_refresh_html < 1) + online_refresh_html = 1; + } else if(strcmpi(w1,"anti_freeze_enable")==0){ + anti_freeze_enable = config_switch(w2); + } else if (strcmpi(w1, "anti_freeze_interval") == 0) { + ANTI_FREEZE_INTERVAL = atoi(w2); + if (ANTI_FREEZE_INTERVAL < 5) + ANTI_FREEZE_INTERVAL = 5; // minimum 5 seconds + } else if (strcmpi(w1, "import") == 0) { + char_config_read(w2); + } + } + fclose(fp); + + return 0; +} + +void do_final(void) { + int i; + + // write online players files with no player + for(i = 0; i < char_num; i++) + online_chars[i] = -1; + create_online_files(); + free(online_chars); + + mmo_char_sync(); + inter_save(); + + if (gm_account != NULL) + free(gm_account); + + free(char_dat); + delete_session(login_fd); + delete_session(char_fd); + + char_log("----End of char-server (normal end with closing of all files)." RETCODE); +} + +int do_init(int argc, char **argv) { + int i; + + // a newline in the log... + char_log(""); + char_log("The char-server starting..." RETCODE); + + char_config_read((argc < 2) ? CHAR_CONF_NAME : argv[1]); + lan_config_read((argc > 1) ? argv[1] : LOGIN_LAN_CONF_NAME); + + login_ip = inet_addr(login_ip_str); + char_ip = inet_addr(char_ip_str); + + for(i = 0; i < MAX_MAP_SERVERS; i++) { + memset(&server[i], 0, sizeof(struct mmo_map_server)); + server_fd[i] = -1; + } + + mmo_char_init(); + + update_online = time(NULL); + create_online_files(); // update online players files at start of the server + + inter_init((argc > 2) ? argv[2] : inter_cfgName); // inter server 初期化 + + set_termfunc(do_final); + set_defaultparse(parse_char); + + char_fd = make_listen_port(char_port); + + add_timer_func_list(check_connect_login_server, "check_connect_login_server"); + add_timer_func_list(send_users_tologin, "send_users_tologin"); + add_timer_func_list(mmo_char_sync_timer, "mmo_char_sync_timer"); + + i = add_timer_interval(gettick() + 1000, check_connect_login_server, 0, 0, 10 * 1000); + i = add_timer_interval(gettick() + 1000, send_users_tologin, 0, 0, 5 * 1000); + i = add_timer_interval(gettick() + autosave_interval, mmo_char_sync_timer, 0, 0, autosave_interval); + + if(anti_freeze_enable > 0) { + add_timer_func_list(map_anti_freeze_system, "map_anti_freeze_system"); + i = add_timer_interval(gettick() + 1000, map_anti_freeze_system, 0, 0, ANTI_FREEZE_INTERVAL * 1000); // checks every X seconds user specifies + } + + char_log("The char-server is ready (Server is listening on the port %d)." RETCODE, char_port); + + printf("The char-server is \033[1;32mready\033[0m (Server is listening on the port %d).\n\n", char_port); + + return 0; +} diff --git a/src/char/char.h b/src/char/char.h new file mode 100644 index 0000000..989ca2f --- /dev/null +++ b/src/char/char.h @@ -0,0 +1,31 @@ +// $Id: char.h,v 1.1.1.1 2004/09/10 17:26:50 MagicalTux Exp $ +#ifndef _CHAR_H_ +#define _CHAR_H_ + +#define MAX_MAP_SERVERS 30 + +#define CHAR_CONF_NAME "conf/char_athena.conf" + +#define LOGIN_LAN_CONF_NAME "conf/lan_support.conf" + +#define DEFAULT_AUTOSAVE_INTERVAL 300*1000 + +struct mmo_map_server{ + long ip; + short port; + int users; + char map[MAX_MAP_PER_SERVER][16]; +}; + +int search_character_index(char* character_name); +char * search_character_name(int index); + +int mapif_sendall(unsigned char *buf, unsigned int len); +int mapif_sendallwos(int fd,unsigned char *buf, unsigned int len); +int mapif_send(int fd,unsigned char *buf, unsigned int len); + +int char_log(char *fmt, ...); + +extern int autosave_interval; + +#endif diff --git a/src/char/diff.diff b/src/char/diff.diff new file mode 100644 index 0000000..61e91c7 --- /dev/null +++ b/src/char/diff.diff @@ -0,0 +1,4242 @@ +--- char.c 2006-02-17 04:15:48.000000000 +0100 ++++ newchar/char.c 2006-03-15 19:55:35.000000000 +0100 +@@ -1,73 +1,100 @@ +-// $Id: char.c,v 1.3 2004/09/13 16:52:16 Yor Exp $ +-// original : char2.c 2003/03/14 11:58:35 Rev.1.5 ++// Copyright (c) Athena Dev Teams - Licensed under GNU GPL
++// For more information, see LICENCE in the main folder
+ + #include <sys/types.h> +-#include <sys/socket.h> + #include <stdio.h> + #include <stdlib.h> ++
++#ifdef _WIN32
++#include <winsock.h>
++typedef long in_addr_t;
++#else
++#include <sys/socket.h>
+ #include <netinet/in.h> +-#include <sys/time.h> +-#include <time.h> ++#include <arpa/inet.h>
++#include <netdb.h>
+ #include <sys/ioctl.h> ++#include <sys/time.h>
+ #include <unistd.h> ++#endif
++
++#include <time.h>
+ #include <signal.h> + #include <fcntl.h> + #include <string.h> +-#include <arpa/inet.h> +-#include <netdb.h> + #include <stdarg.h> ++#include <limits.h>
+ +-#include "core.h" +-#include "socket.h" +-#include "timer.h" +-#include "mmo.h" +-#include "version.h" +-#include "lock.h" +-#include "char.h" ++#include "../common/strlib.h"
++#include "../common/core.h"
++#include "../common/socket.h"
++#include "../common/timer.h"
++#include "../common/mmo.h"
++#include "../common/db.h"
++#include "../common/version.h"
++#include "../common/lock.h"
++#include "../common/showmsg.h"
++#include "../common/malloc.h"
+ ++#include "char.h"
+ #include "inter.h" + #include "int_pet.h" + #include "int_guild.h" + #include "int_party.h" + #include "int_storage.h" +- +-#ifdef MEMWATCH +-#include "memwatch.h" ++#ifdef ENABLE_SC_SAVING
++#include "int_status.h"
+ #endif + + struct mmo_map_server server[MAX_MAP_SERVERS]; + int server_fd[MAX_MAP_SERVERS]; +-int server_freezeflag[MAX_MAP_SERVERS]; // Map-server anti-freeze system. Counter. 5 ok, 4...0 freezed +-int anti_freeze_enable = 0; +-int ANTI_FREEZE_INTERVAL = 6; + + int login_fd, char_fd; + char userid[24]; + char passwd[24]; + char server_name[20]; +-char wisp_server_name[24] = "Server"; ++char wisp_server_name[NAME_LENGTH] = "Server";
++int login_ip_set_ = 0;
+ char login_ip_str[16]; +-int login_ip; ++in_addr_t login_ip;
+ int login_port = 6900; ++int char_ip_set_ = 0;
+ char char_ip_str[16]; +-int char_ip; ++int bind_ip_set_ = 0;
++char bind_ip_str[16];
++in_addr_t char_ip;
+ int char_port = 6121; + int char_maintenance; + int char_new; ++int char_new_display;
+ int email_creation = 0; // disabled by default +-char char_txt[1024]; +-char backup_txt[1024]; //By zanetheinsane ++char char_txt[1024]="save/athena.txt";
++char backup_txt[1024]="save/backup.txt"; //By zanetheinsane
++char friends_txt[1024]="save/friends.txt"; // davidsiaw
+ char backup_txt_flag = 0; // The backup_txt file was created because char deletion bug existed. Now it's finish and that take a lot of time to create a second file when there are a lot of characters. => option By [Yor] + char unknown_char_name[1024] = "Unknown"; + char char_log_filename[1024] = "log/char.log"; +-//Added for lan support +-char lan_map_ip[128]; +-int subneti[4]; +-int subnetmaski[4]; ++char db_path[1024]="db";
++
++// Advanced subnet check [LuzZza]
++struct _subnet {
++ long subnet;
++ long mask;
++ long char_ip;
++ long map_ip;
++} subnet[16];
++
++int subnet_count = 0;
++
+ int name_ignoring_case = 0; // Allow or not identical name for characters but with a different case by [Yor] + int char_name_option = 0; // Option to know which letters/symbols are authorised in the name of a character (0: all, 1: only those in char_name_letters, 2: all EXCEPT those in char_name_letters) by [Yor] ++//The following are characters that are trimmed regardless because they cause confusion and problems on the servers. [Skotlex]
++#define TRIM_CHARS "\032\t\n "
+ char char_name_letters[1024] = ""; // list of letters/symbols authorised (or not) in a character name. by [Yor] + ++int log_char = 1; // loggin char or not [devil]
++int log_inter = 1; // loggin inter or not [devil]
++
+ struct char_session_data{ + int account_id, login_id1, login_id2, sex; + int found_char[9]; +@@ -83,18 +110,31 @@ + int auth_fifo_pos = 0; + + int check_ip_flag = 1; // It's to check IP of a player between char-server and other servers (part of anti-hacking system) ++static int online_check = 1; //If one, it won't let players connect when their account is already registered online and will send the relevant map server a kick user request. [Skotlex]
++
++int char_id_count = START_CHAR_NUM;
++struct character_data {
++ struct mmo_charstatus status;
++ int global_num;
++ struct global_reg global[GLOBAL_REG_NUM];
++} *char_dat;
+ +-int char_id_count = 150000; +-struct mmo_charstatus *char_dat; + int char_num, char_max; + int max_connect_user = 0; ++int gm_allow_level = 99;
+ int autosave_interval = DEFAULT_AUTOSAVE_INTERVAL; ++int save_log = 1;
+ int start_zeny = 500; + int start_weapon = 1201; +-int start_armor = 1202; ++int start_armor = 2301;
++
++//Custom limits for the fame lists. [Skotlex]
++int fame_list_size_chemist = MAX_FAME_LIST;
++int fame_list_size_smith = MAX_FAME_LIST;
++int fame_list_size_taekwon = MAX_FAME_LIST;
+ + // Initial position (it's possible to set it in conf file) +-struct point start_point = {"new_1-1.gat", 53, 111}; ++struct point start_point = { 0, 53, 111};
+ + struct gm_account *gm_account = NULL; + int GM_num = 0; +@@ -107,16 +147,31 @@ + int online_refresh_html = 20; // refresh time (in sec) of the html file in the explorer + int online_gm_display_min_level = 20; // minimum GM level to display 'GM' when we want to display it + +-int *online_chars; // same size of char_dat, and id value of current server (or -1) ++//These are used to aid the map server in identifying valid clients. [Skotlex]
++static int max_account_id = DEFAULT_MAX_ACCOUNT_ID, max_char_id = DEFAULT_MAX_CHAR_ID;
++
++struct online_char_data {
++ int account_id;
++ int char_id;
++ short server;
++ unsigned waiting_disconnect :1;
++};
++
++struct dbt *online_char_db; //Holds all online characters.
++
+ time_t update_online; // to update online files when we receiving information from a server (not less than 8 seconds) + ++int console = 0;
++
+ //------------------------------ + // Writing function of logs file + //------------------------------ + int char_log(char *fmt, ...) { ++ if(log_char)
++ {
+ FILE *logfp; + va_list ap; +- struct timeval tv; ++ time_t raw_time;
+ char tmpstr[2048]; + + va_start(ap, fmt); +@@ -126,33 +181,16 @@ + if (fmt[0] == '\0') // jump a line if no message + fprintf(logfp, RETCODE); + else { +- gettimeofday(&tv, NULL); +- strftime(tmpstr, 24, "%d-%m-%Y %H:%M:%S", localtime(&(tv.tv_sec))); +- sprintf(tmpstr + 19, ".%03d: %s", (int)tv.tv_usec / 1000, fmt); ++ time(&raw_time);
++ strftime(tmpstr, 24, "%d-%m-%Y %H:%M:%S", localtime(&raw_time));
++ sprintf(tmpstr + 19, ": %s", fmt);
+ vfprintf(logfp, tmpstr, ap); + } + fclose(logfp); + } +- + va_end(ap); +- return 0; +-} +- +-//----------------------------------------------------- +-// Function to suppress control characters in a string. +-//----------------------------------------------------- +-int remove_control_chars(unsigned char *str) { +- int i; +- int change = 0; +- +- for(i = 0; str[i]; i++) { +- if (str[i] < 32) { +- str[i] = '_'; +- change = 1; +- } + } +- +- return change; ++ return 0;
+ } + + //---------------------------------------------------------------------- +@@ -183,9 +221,9 @@ + index = -1; + for(i = 0; i < char_num; i++) { + // Without case sensitive check (increase the number of similar character names found) +- if (stricmp(char_dat[i].name, character_name) == 0) { ++ if (stricmp(char_dat[i].status.name, character_name) == 0) {
+ // Strict comparison (if found, we finish the function immediatly with correct value) +- if (strcmp(char_dat[i].name, character_name) == 0) ++ if (strcmp(char_dat[i].status.name, character_name) == 0)
+ return i; + quantity++; + index = i; +@@ -206,30 +244,140 @@ + char * search_character_name(int index) { + + if (index >= 0 && index < char_num) +- return char_dat[index].name; ++ return char_dat[index].status.name;
+ + return unknown_char_name; + } + ++static void * create_online_char_data(DBKey key, va_list args) {
++ struct online_char_data* character;
++ character = aCalloc(1, sizeof(struct online_char_data));
++ character->account_id = key.i;
++ character->char_id = -1;
++ character->server = -1;
++ return character;
++}
++
++static int chardb_waiting_disconnect(int tid, unsigned int tick, int id, int data);
++
+ //------------------------------------------------- +-// Function to create the character line (for save) ++// Set Character online/offline [Wizputer]
+ //------------------------------------------------- +-int mmo_char_tostr(char *str, struct mmo_charstatus *p) { ++
++void set_char_online(int map_id, int char_id, int account_id) {
++ struct online_char_data* character;
++
++ if ( char_id != 99 && (max_account_id < account_id || max_char_id < char_id))
++ { //Notify map-server of the new max IDs [Skotlex]
++ if (account_id > max_account_id)
++ max_account_id = account_id;
++ if (char_id > max_char_id)
++ max_char_id = char_id;
++ mapif_send_maxid(max_account_id, max_char_id);
++ }
++ character = idb_ensure(online_char_db, account_id, create_online_char_data);
++ if (online_check && character->char_id != -1 && character->server > -1 && character->server != map_id)
++ {
++ ShowNotice("set_char_online: Character %d:%d marked in map server %d, but map server %d claims to have (%d:%d) online!\n",
++ character->account_id, character->char_id, character->server, map_id, account_id, char_id);
++ mapif_disconnectplayer(server_fd[character->server], character->account_id, character->char_id, 2);
++ }
++ character->waiting_disconnect = 0;
++ character->char_id = (char_id==99)?-1:char_id;
++ character->server = (char_id==99)?-1:map_id;
++
++ if (login_fd <= 0 || session[login_fd]->eof)
++ return;
++ WFIFOHEAD(login_fd, 6);
++ WFIFOW(login_fd,0) = 0x272b;
++ WFIFOL(login_fd,2) = account_id;
++ WFIFOSET(login_fd,6);
++
++ //printf ("set online\n");
++}
++void set_char_offline(int char_id, int account_id) {
++ struct online_char_data* character;
++
++ if ((character = idb_get(online_char_db, account_id)) != NULL)
++ { //We don't free yet to avoid aCalloc/aFree spamming during char change. [Skotlex]
++ character->char_id = -1;
++ character->server = -1;
++ character->waiting_disconnect = 0;
++ }
++ if (login_fd <= 0 || session[login_fd]->eof)
++ return;
++ WFIFOHEAD(login_fd, 6);
++ WFIFOW(login_fd,0) = 0x272c;
++ WFIFOL(login_fd,2) = account_id;
++ WFIFOSET(login_fd,6);
++
++}
++
++static int char_db_setoffline(DBKey key, void* data, va_list ap) {
++ struct online_char_data* character = (struct online_char_data*)data;
++ int server = va_arg(ap, int);
++ if (server == -1) {
++ character->char_id = -1;
++ character->server = -1;
++ character->waiting_disconnect = 0;
++ } else if (character->server == server)
++ character->server = -2; //In some map server that we aren't connected to.
++ return 0;
++}
++
++void set_all_offline(void) {
++ online_char_db->foreach(online_char_db,char_db_setoffline,-1);
++ if (login_fd <= 0 || session[login_fd]->eof)
++ return;
++ WFIFOHEAD(login_fd, 6);
++ WFIFOW(login_fd,0) = 0x272c;
++ WFIFOL(login_fd,2) = 99;
++ WFIFOSET(login_fd,6);
++
++ //printf ("set all offline\n");
++}
++
++/*---------------------------------------------------
++ Make a data line for friends list
++ --------------------------------------------------*/
++
++int mmo_friends_list_data_str(char *str, struct mmo_charstatus *p) {
+ int i; + char *str_p = str; ++ str_p += sprintf(str_p, "%d", p->char_id);
++
++ for (i=0;i<MAX_FRIENDS;i++){
++ if (p->friends[i].account_id > 0 && p->friends[i].char_id > 0 && p->friends[i].name[0])
++ str_p += sprintf(str_p, ",%d,%d,%s", p->friends[i].account_id, p->friends[i].char_id, p->friends[i].name);
++ else
++ str_p += sprintf(str_p,",,,");
++ }
++
++ str_p += '\0';
++
++ return 0;
++}
++
++//-------------------------------------------------
++// Function to create the character line (for save)
++//-------------------------------------------------
++int mmo_char_tostr(char *str, struct mmo_charstatus *p, struct global_reg *reg, int reg_num) {
++ int i,j;
++ char *str_p = str;
+ ++ /* We shouldn't need this anymore... [Skotlex]
+ // on multi-map server, sometimes it's posssible that last_point become void. (reason???) We check that to not lost character at restart. +- if (p->last_point.map[0] == '\0') { +- memcpy(p->last_point.map, "prontera.gat", 16); ++ if (!p->last_point.map) {
++ p->last_point.map = mapindex_name2id(MAP_PRONTERA);
+ p->last_point.x = 273; + p->last_point.y = 354; + } +- +- str_p += sprintf(str_p, "%d\t%d,%d\t%s\t%d,%d,%d\t%d,%d,%d\t%d,%d,%d,%d\t%d,%d,%d,%d,%d,%d\t%d,%d" ++ */
++ str_p += sprintf(str_p, "%d\t%d,%d\t%s\t%d,%d,%d\t%u,%u,%d\t%d,%d,%d,%d\t%d,%d,%d,%d,%d,%d\t%d,%d"
+ "\t%d,%d,%d\t%d,%d,%d\t%d,%d,%d\t%d,%d,%d,%d,%d" +- "\t%s,%d,%d\t%s,%d,%d,%d\t", ++ "\t%s,%d,%d\t%s,%d,%d,%d,%d,%d,%d,%d\t",
+ p->char_id, p->account_id, p->char_num, p->name, // +- p->class, p->base_level, p->job_level, ++ p->class_, p->base_level, p->job_level,
+ p->base_exp, p->job_exp, p->zeny, + p->hp, p->max_hp, p->sp, p->max_sp, + p->str, p->agi, p->vit, p->int_, p->dex, p->luk, +@@ -238,32 +386,34 @@ + p->party_id, p->guild_id, p->pet_id, + p->hair, p->hair_color, p->clothes_color, + p->weapon, p->shield, p->head_top, p->head_mid, p->head_bottom, +- p->last_point.map, p->last_point.x, p->last_point.y, // +- p->save_point.map, p->save_point.x, p->save_point.y, +- p->partner_id); ++ mapindex_id2name(p->last_point.map), p->last_point.x, p->last_point.y, //
++ mapindex_id2name(p->save_point.map), p->save_point.x, p->save_point.y,
++ p->partner_id,p->father,p->mother,p->child,p->fame);
+ for(i = 0; i < 10; i++) +- if (p->memo_point[i].map[0]) { +- str_p += sprintf(str_p, "%s,%d,%d", p->memo_point[i].map, p->memo_point[i].x, p->memo_point[i].y); ++ if (p->memo_point[i].map) {
++ str_p += sprintf(str_p, "%s,%d,%d", mapindex_id2name(p->memo_point[i].map), p->memo_point[i].x, p->memo_point[i].y);
+ } + *(str_p++) = '\t'; + + for(i = 0; i < MAX_INVENTORY; i++) + if (p->inventory[i].nameid) { +- str_p += sprintf(str_p, "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d ", ++ str_p += sprintf(str_p,"%d,%d,%d,%d,%d,%d,%d",
+ p->inventory[i].id, p->inventory[i].nameid, p->inventory[i].amount, p->inventory[i].equip, +- p->inventory[i].identify, p->inventory[i].refine, p->inventory[i].attribute, +- p->inventory[i].card[0], p->inventory[i].card[1], p->inventory[i].card[2], p->inventory[i].card[3], +- p->inventory[i].broken); ++ p->inventory[i].identify,p->inventory[i].refine,p->inventory[i].attribute);
++ for(j=0; j<MAX_SLOTS; j++)
++ str_p += sprintf(str_p,",%d",p->inventory[i].card[j]);
++ str_p += sprintf(str_p," ");
+ } + *(str_p++) = '\t'; + + for(i = 0; i < MAX_CART; i++) + if (p->cart[i].nameid) { +- str_p += sprintf(str_p, "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d ", ++ str_p += sprintf(str_p,"%d,%d,%d,%d,%d,%d,%d",
+ p->cart[i].id, p->cart[i].nameid, p->cart[i].amount, p->cart[i].equip, +- p->cart[i].identify, p->cart[i].refine, p->cart[i].attribute, +- p->cart[i].card[0], p->cart[i].card[1], p->cart[i].card[2], p->cart[i].card[3], +- p->cart[i].broken); ++ p->cart[i].identify,p->cart[i].refine,p->cart[i].attribute);
++ for(j=0; j<MAX_SLOTS; j++)
++ str_p += sprintf(str_p,",%d",p->cart[i].card[j]);
++ str_p += sprintf(str_p," ");
+ } + *(str_p++) = '\t'; + +@@ -273,9 +423,9 @@ + } + *(str_p++) = '\t'; + +- for(i = 0; i < p->global_reg_num; i++) +- if (p->global_reg[i].str[0]) +- str_p += sprintf(str_p, "%s,%d ", p->global_reg[i].str, p->global_reg[i].value); ++ for(i = 0; i < reg_num; i++)
++ if (reg[i].str[0])
++ str_p += sprintf(str_p, "%s,%s ", reg[i].str, reg[i].value);
+ *(str_p++) = '\t'; + + *str_p = '\0'; +@@ -285,20 +435,62 @@ + //------------------------------------------------------------------------- + // Function to set the character from the line (at read of characters file) + //------------------------------------------------------------------------- +-int mmo_char_fromstr(char *str, struct mmo_charstatus *p) { ++int mmo_char_fromstr(char *str, struct mmo_charstatus *p, struct global_reg *reg, int *reg_num) {
++ char tmp_str[3][128]; //To avoid deleting chars with too long names.
+ int tmp_int[256]; +- int set, next, len, i; ++ unsigned int tmp_uint[2]; //To read exp....
++ int set, next, len, i, j;
+ + // initilialise character + memset(p, '\0', sizeof(struct mmo_charstatus)); + +- // If it's not char structure of version 1008 and after +- if ((set = sscanf(str, "%d\t%d,%d\t%[^\t]\t%d,%d,%d\t%d,%d,%d\t%d,%d,%d,%d\t%d,%d,%d,%d,%d,%d\t%d,%d" ++ // If it's not char structure of version 1488 and after
++ if ((set = sscanf(str, "%d\t%d,%d\t%127[^\t]\t%d,%d,%d\t%u,%u,%d\t%d,%d,%d,%d\t%d,%d,%d,%d,%d,%d\t%d,%d"
++ "\t%d,%d,%d\t%d,%d,%d\t%d,%d,%d\t%d,%d,%d,%d,%d"
++ "\t%127[^,],%d,%d\t%127[^,],%d,%d,%d,%d,%d,%d,%d%n",
++ &tmp_int[0], &tmp_int[1], &tmp_int[2], tmp_str[0],
++ &tmp_int[3], &tmp_int[4], &tmp_int[5],
++ &tmp_uint[0], &tmp_uint[1], &tmp_int[8],
++ &tmp_int[9], &tmp_int[10], &tmp_int[11], &tmp_int[12],
++ &tmp_int[13], &tmp_int[14], &tmp_int[15], &tmp_int[16], &tmp_int[17], &tmp_int[18],
++ &tmp_int[19], &tmp_int[20],
++ &tmp_int[21], &tmp_int[22], &tmp_int[23], //
++ &tmp_int[24], &tmp_int[25], &tmp_int[26],
++ &tmp_int[27], &tmp_int[28], &tmp_int[29],
++ &tmp_int[30], &tmp_int[31], &tmp_int[32], &tmp_int[33], &tmp_int[34],
++ tmp_str[1], &tmp_int[35], &tmp_int[36],
++ tmp_str[2], &tmp_int[37], &tmp_int[38], &tmp_int[39],
++ &tmp_int[40], &tmp_int[41], &tmp_int[42], &tmp_int[43], &next)) != 47)
++ {
++ tmp_int[43] = 0;
++ // If it's not char structure of version 1363 and after
++ if ((set = sscanf(str, "%d\t%d,%d\t%127[^\t]\t%d,%d,%d\t%u,%u,%d\t%d,%d,%d,%d\t%d,%d,%d,%d,%d,%d\t%d,%d"
++ "\t%d,%d,%d\t%d,%d,%d\t%d,%d,%d\t%d,%d,%d,%d,%d"
++ "\t%127[^,],%d,%d\t%127[^,],%d,%d,%d,%d,%d,%d%n",
++ &tmp_int[0], &tmp_int[1], &tmp_int[2], tmp_str[0], //
++ &tmp_int[3], &tmp_int[4], &tmp_int[5],
++ &tmp_uint[0], &tmp_uint[1], &tmp_int[8],
++ &tmp_int[9], &tmp_int[10], &tmp_int[11], &tmp_int[12],
++ &tmp_int[13], &tmp_int[14], &tmp_int[15], &tmp_int[16], &tmp_int[17], &tmp_int[18],
++ &tmp_int[19], &tmp_int[20],
++ &tmp_int[21], &tmp_int[22], &tmp_int[23], //
++ &tmp_int[24], &tmp_int[25], &tmp_int[26],
++ &tmp_int[27], &tmp_int[28], &tmp_int[29],
++ &tmp_int[30], &tmp_int[31], &tmp_int[32], &tmp_int[33], &tmp_int[34],
++ tmp_str[1], &tmp_int[35], &tmp_int[36], //
++ tmp_str[2], &tmp_int[37], &tmp_int[38], &tmp_int[39],
++ &tmp_int[40], &tmp_int[41], &tmp_int[42], &next)) != 46)
++ {
++ tmp_int[40] = 0; // father
++ tmp_int[41] = 0; // mother
++ tmp_int[42] = 0; // child
++ // If it's not char structure of version 1008 and before 1363
++ if ((set = sscanf(str, "%d\t%d,%d\t%127[^\t]\t%d,%d,%d\t%u,%u,%d\t%d,%d,%d,%d\t%d,%d,%d,%d,%d,%d\t%d,%d"
+ "\t%d,%d,%d\t%d,%d,%d\t%d,%d,%d\t%d,%d,%d,%d,%d" +- "\t%[^,],%d,%d\t%[^,],%d,%d,%d%n", +- &tmp_int[0], &tmp_int[1], &tmp_int[2], p->name, // ++ "\t%127[^,],%d,%d\t%127[^,],%d,%d,%d%n",
++ &tmp_int[0], &tmp_int[1], &tmp_int[2], tmp_str[0], //
+ &tmp_int[3], &tmp_int[4], &tmp_int[5], +- &tmp_int[6], &tmp_int[7], &tmp_int[8], ++ &tmp_uint[0], &tmp_uint[1], &tmp_int[8],
+ &tmp_int[9], &tmp_int[10], &tmp_int[11], &tmp_int[12], + &tmp_int[13], &tmp_int[14], &tmp_int[15], &tmp_int[16], &tmp_int[17], &tmp_int[18], + &tmp_int[19], &tmp_int[20], +@@ -306,16 +498,17 @@ + &tmp_int[24], &tmp_int[25], &tmp_int[26], + &tmp_int[27], &tmp_int[28], &tmp_int[29], + &tmp_int[30], &tmp_int[31], &tmp_int[32], &tmp_int[33], &tmp_int[34], +- p->last_point.map, &tmp_int[35], &tmp_int[36], // +- p->save_point.map, &tmp_int[37], &tmp_int[38], &tmp_int[39], &next)) != 43) { ++ tmp_str[1], &tmp_int[35], &tmp_int[36], //
++ tmp_str[2], &tmp_int[37], &tmp_int[38], &tmp_int[39], &next)) != 43)
++ {
+ tmp_int[39] = 0; // partner id + // If not char structure from version 384 to 1007 +- if ((set = sscanf(str, "%d\t%d,%d\t%[^\t]\t%d,%d,%d\t%d,%d,%d\t%d,%d,%d,%d\t%d,%d,%d,%d,%d,%d\t%d,%d" ++ if ((set = sscanf(str, "%d\t%d,%d\t%127[^\t]\t%d,%d,%d\t%u,%u,%d\t%d,%d,%d,%d\t%d,%d,%d,%d,%d,%d\t%d,%d"
+ "\t%d,%d,%d\t%d,%d,%d\t%d,%d,%d\t%d,%d,%d,%d,%d" +- "\t%[^,],%d,%d\t%[^,],%d,%d%n", +- &tmp_int[0], &tmp_int[1], &tmp_int[2], p->name, // ++ "\t%127[^,],%d,%d\t%127[^,],%d,%d%n",
++ &tmp_int[0], &tmp_int[1], &tmp_int[2], tmp_str[0], //
+ &tmp_int[3], &tmp_int[4], &tmp_int[5], +- &tmp_int[6], &tmp_int[7], &tmp_int[8], ++ &tmp_uint[0], &tmp_uint[1], &tmp_int[8],
+ &tmp_int[9], &tmp_int[10], &tmp_int[11], &tmp_int[12], + &tmp_int[13], &tmp_int[14], &tmp_int[15], &tmp_int[16], &tmp_int[17], &tmp_int[18], + &tmp_int[19], &tmp_int[20], +@@ -323,16 +516,17 @@ + &tmp_int[24], &tmp_int[25], &tmp_int[26], + &tmp_int[27], &tmp_int[28], &tmp_int[29], + &tmp_int[30], &tmp_int[31], &tmp_int[32], &tmp_int[33], &tmp_int[34], +- p->last_point.map, &tmp_int[35], &tmp_int[36], // +- p->save_point.map, &tmp_int[37], &tmp_int[38], &next)) != 42) { ++ tmp_str[1], &tmp_int[35], &tmp_int[36], //
++ tmp_str[2], &tmp_int[37], &tmp_int[38], &next)) != 42)
++ {
+ // It's char structure of a version before 384 + tmp_int[26] = 0; // pet id +- set = sscanf(str, "%d\t%d,%d\t%[^\t]\t%d,%d,%d\t%d,%d,%d\t%d,%d,%d,%d\t%d,%d,%d,%d,%d,%d\t%d,%d" ++ set = sscanf(str, "%d\t%d,%d\t%127[^\t]\t%d,%d,%d\t%u,%u,%d\t%d,%d,%d,%d\t%d,%d,%d,%d,%d,%d\t%d,%d"
+ "\t%d,%d,%d\t%d,%d\t%d,%d,%d\t%d,%d,%d,%d,%d" +- "\t%[^,],%d,%d\t%[^,],%d,%d%n", +- &tmp_int[0], &tmp_int[1], &tmp_int[2], p->name, // ++ "\t%127[^,],%d,%d\t%127[^,],%d,%d%n",
++ &tmp_int[0], &tmp_int[1], &tmp_int[2], tmp_str[0], //
+ &tmp_int[3], &tmp_int[4], &tmp_int[5], +- &tmp_int[6], &tmp_int[7], &tmp_int[8], ++ &tmp_uint[0], &tmp_uint[1], &tmp_int[8],
+ &tmp_int[9], &tmp_int[10], &tmp_int[11], &tmp_int[12], + &tmp_int[13], &tmp_int[14], &tmp_int[15], &tmp_int[16], &tmp_int[17], &tmp_int[18], + &tmp_int[19], &tmp_int[20], +@@ -340,8 +534,8 @@ + &tmp_int[24], &tmp_int[25], // + &tmp_int[27], &tmp_int[28], &tmp_int[29], + &tmp_int[30], &tmp_int[31], &tmp_int[32], &tmp_int[33], &tmp_int[34], +- p->last_point.map, &tmp_int[35], &tmp_int[36], // +- p->save_point.map, &tmp_int[37], &tmp_int[38], &next); ++ tmp_str[1], &tmp_int[35], &tmp_int[36], //
++ tmp_str[2], &tmp_int[37], &tmp_int[38], &next);
+ set += 2; + //printf("char: old char data ver.1\n"); + // Char structure of version 1007 or older +@@ -351,19 +545,30 @@ + } + // Char structure of version 1008+ + } else { ++ set += 3;
+ //printf("char: new char data ver.3\n"); + } +- if (set != 43) ++ // Char structture of version 1363+
++ } else {
++ set++;
++ //printf("char: new char data ver.4\n");
++ }
++ // Char structure of version 1488+
++ } else {
++ //printf("char: new char data ver.5\n");
++ }
++ if (set != 47)
+ return 0; + ++ memcpy(p->name, tmp_str[0], NAME_LENGTH-1); //Overflow protection [Skotlex]
+ p->char_id = tmp_int[0]; + p->account_id = tmp_int[1]; + p->char_num = tmp_int[2]; +- p->class = tmp_int[3]; ++ p->class_ = tmp_int[3];
+ p->base_level = tmp_int[4]; + p->job_level = tmp_int[5]; +- p->base_exp = tmp_int[6]; +- p->job_exp = tmp_int[7]; ++ p->base_exp = tmp_uint[0];
++ p->job_exp = tmp_uint[1];
+ p->zeny = tmp_int[8]; + p->hp = tmp_int[9]; + p->max_hp = tmp_int[10]; +@@ -391,31 +596,37 @@ + p->head_top = tmp_int[32]; + p->head_mid = tmp_int[33]; + p->head_bottom = tmp_int[34]; ++ p->last_point.map = mapindex_name2id(tmp_str[1]);
+ p->last_point.x = tmp_int[35]; + p->last_point.y = tmp_int[36]; ++ p->save_point.map = mapindex_name2id(tmp_str[2]);
+ p->save_point.x = tmp_int[37]; + p->save_point.y = tmp_int[38]; + p->partner_id = tmp_int[39]; ++ p->father = tmp_int[40];
++ p->mother = tmp_int[41];
++ p->child = tmp_int[42];
++ p->fame = tmp_int[43];
+ + // Some checks + for(i = 0; i < char_num; i++) { +- if (char_dat[i].char_id == p->char_id) { +- printf("\033[1;31mmmo_auth_init: ******Error: a character has an identical id to another.\n"); +- printf(" character id #%d -> new character not readed.\n", p->char_id); +- printf(" Character saved in log file.\033[0m\n"); ++ if (char_dat[i].status.char_id == p->char_id) {
++ ShowError(CL_RED"mmmo_auth_init: a character has an identical id to another.\n");
++ ShowError(" character id #%d -> new character not readed.\n", p->char_id);
++ ShowError(" Character saved in log file."CL_RESET"\n");
+ return -1; +- } else if (strcmp(char_dat[i].name, p->name) == 0) { +- printf("\033[1;31mmmo_auth_init: ******Error: character name already exists.\n"); +- printf(" character name '%s' -> new character not readed.\n", p->name); +- printf(" Character saved in log file.\033[0m\n"); ++ } else if (strcmp(char_dat[i].status.name, p->name) == 0) {
++ ShowError(CL_RED"mmmo_auth_init: a character name already exists.\n");
++ ShowError(" character name '%s' -> new character not read.\n", p->name);
++ ShowError(" Character saved in log file."CL_RESET"\n");
+ return -2; + } + } + + if (strcmpi(wisp_server_name, p->name) == 0) { +- printf("mmo_auth_init: ******WARNING: character name has wisp server name.\n"); +- printf(" Character name '%s' = wisp server name '%s'.\n", p->name, wisp_server_name); +- printf(" Character readed. Suggestion: change the wisp server name.\n"); ++ ShowWarning("mmo_auth_init: ******WARNING: character name has wisp server name.\n");
++ ShowWarning(" Character name '%s' = wisp server name '%s'.\n", p->name, wisp_server_name);
++ ShowWarning(" Character readed. Suggestion: change the wisp server name.\n");
+ char_log("mmo_auth_init: ******WARNING: character name has wisp server name: Character name '%s' = wisp server name '%s'." RETCODE, + p->name, wisp_server_name); + } +@@ -426,8 +637,9 @@ + next++; + + for(i = 0; str[next] && str[next] != '\t'; i++) { +- if (sscanf(str+next, "%[^,],%d,%d%n", p->memo_point[i].map, &tmp_int[0], &tmp_int[1], &len) != 3) ++ if (sscanf(str+next, "%[^,],%d,%d%n", tmp_str[0], &tmp_int[0], &tmp_int[1], &len) != 3)
+ return -3; ++ p->memo_point[i].map = mapindex_name2id(tmp_str[0]);
+ p->memo_point[i].x = tmp_int[0]; + p->memo_point[i].y = tmp_int[1]; + next += len; +@@ -438,18 +650,10 @@ + next++; + + for(i = 0; str[next] && str[next] != '\t'; i++) { +- if (sscanf(str + next, "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d%n", ++ if(sscanf(str + next, "%d,%d,%d,%d,%d,%d,%d%[0-9,-]%n",
+ &tmp_int[0], &tmp_int[1], &tmp_int[2], &tmp_int[3], +- &tmp_int[4], &tmp_int[5], &tmp_int[6], +- &tmp_int[7], &tmp_int[8], &tmp_int[9], &tmp_int[10], &tmp_int[11], &len) == 12) { +- // do nothing, it's ok +- } else if (sscanf(str + next, "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d%n", +- &tmp_int[0], &tmp_int[1], &tmp_int[2], &tmp_int[3], +- &tmp_int[4], &tmp_int[5], &tmp_int[6], +- &tmp_int[7], &tmp_int[8], &tmp_int[9], &tmp_int[10], &len) == 11) { +- tmp_int[11] = 0; // broken doesn't exist in this version -> 0 +- } else // invalid structure +- return -4; ++ &tmp_int[4], &tmp_int[5], &tmp_int[6], tmp_str[0], &len) == 8)
++ {
+ p->inventory[i].id = tmp_int[0]; + p->inventory[i].nameid = tmp_int[1]; + p->inventory[i].amount = tmp_int[2]; +@@ -457,31 +661,23 @@ + p->inventory[i].identify = tmp_int[4]; + p->inventory[i].refine = tmp_int[5]; + p->inventory[i].attribute = tmp_int[6]; +- p->inventory[i].card[0] = tmp_int[7]; +- p->inventory[i].card[1] = tmp_int[8]; +- p->inventory[i].card[2] = tmp_int[9]; +- p->inventory[i].card[3] = tmp_int[10]; +- p->inventory[i].broken = tmp_int[11]; ++
++ for(j = 0; j < MAX_SLOTS && tmp_str[0] && sscanf(tmp_str[0], ",%d%[0-9,-]",&tmp_int[0], tmp_str[0]) > 0; j++)
++ p->inventory[i].card[j] = tmp_int[0];
++
+ next += len; + if (str[next] == ' ') + next++; ++ } else // invalid structure
++ return -4;
+ } +- + next++; + + for(i = 0; str[next] && str[next] != '\t'; i++) { +- if (sscanf(str + next, "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d%n", +- &tmp_int[0], &tmp_int[1], &tmp_int[2], &tmp_int[3], +- &tmp_int[4], &tmp_int[5], &tmp_int[6], +- &tmp_int[7], &tmp_int[8], &tmp_int[9], &tmp_int[10], &tmp_int[11], &len) == 12) { +- // do nothing, it's ok +- } else if (sscanf(str + next, "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d%n", ++ if(sscanf(str + next, "%d,%d,%d,%d,%d,%d,%d%[0-9,-]%n",
+ &tmp_int[0], &tmp_int[1], &tmp_int[2], &tmp_int[3], +- &tmp_int[4], &tmp_int[5], &tmp_int[6], +- &tmp_int[7], &tmp_int[8], &tmp_int[9], &tmp_int[10], &len) == 11) { +- tmp_int[11] = 0; // broken doesn't exist in this version -> 0 +- } else // invalid structure +- return -5; ++ &tmp_int[4], &tmp_int[5], &tmp_int[6], tmp_str[0], &len) == 8)
++ {
+ p->cart[i].id = tmp_int[0]; + p->cart[i].nameid = tmp_int[1]; + p->cart[i].amount = tmp_int[2]; +@@ -489,14 +685,15 @@ + p->cart[i].identify = tmp_int[4]; + p->cart[i].refine = tmp_int[5]; + p->cart[i].attribute = tmp_int[6]; +- p->cart[i].card[0] = tmp_int[7]; +- p->cart[i].card[1] = tmp_int[8]; +- p->cart[i].card[2] = tmp_int[9]; +- p->cart[i].card[3] = tmp_int[10]; +- p->cart[i].broken = tmp_int[11]; ++
++ for(j = 0; j < MAX_SLOTS && tmp_str && sscanf(tmp_str[0], ",%d%[0-9,-]",&tmp_int[0], tmp_str[0]) > 0; j++)
++ p->cart[i].card[j] = tmp_int[0];
++
+ next += len; + if (str[next] == ' ') + next++; ++ } else // invalid structure
++ return -5;
+ } + + next++; +@@ -514,11 +711,11 @@ + next++; + + for(i = 0; str[next] && str[next] != '\t' && str[next] != '\n' && str[next] != '\r'; i++) { // global_reg実装以前のathena.txt互換のため一応'\n'チェック +- if (sscanf(str + next, "%[^,],%d%n", p->global_reg[i].str, &p->global_reg[i].value, &len) != 2) { ++ if (sscanf(str + next, "%[^,],%[^ ] %n", reg[i].str, reg[i].value, &len) != 2) {
+ // because some scripts are not correct, the str can be "". So, we must check that. + // If it's, we must not refuse the character, but just this REG value. + // Character line will have something like: nov_2nd_cos,9 ,9 nov_1_2_cos_c,1 (here, ,9 is not good) +- if (str[next] == ',' && sscanf(str + next, ",%d%n", &p->global_reg[i].value, &len) == 1) ++ if (str[next] == ',' && sscanf(str + next, ",%[^ ] %n", reg[i].value, &len) == 1)
+ i--; + else + return -7; +@@ -527,39 +724,127 @@ + if (str[next] == ' ') + next++; + } +- p->global_reg_num = i; ++ *reg_num = i;
+ + return 1; + } ++//---------------------------------
++// Function to read friend list
++//---------------------------------
++
++int parse_friend_txt(struct mmo_charstatus *p)
++{
++ char line[1024], temp[1024];
++ int pos = 0, count = 0, next;
++ int i,len;
++ FILE *fp;
++
++ // Open the file and look for the ID
++ fp = fopen(friends_txt, "r");
++
++ if(fp == NULL)
++ return -1;
++
++ while(fgets(line, sizeof(line)-1, fp)) {
++
++ if(line[0] == '/' && line[1] == '/')
++ continue;
++ if (sscanf(line, "%d%n",&i, &pos) < 1 || i != p->char_id)
++ continue; //Not this line...
++ //Read friends
++ len = strlen(line);
++ next = pos;
++ for (count = 0; next < len && count < MAX_FRIENDS; count++)
++ { //Read friends.
++ if (sscanf(line+next, ",%d,%d,%23[^,]%n",&p->friends[count].account_id,&p->friends[count].char_id, p->friends[count].name, &pos) < 3)
++ { //Invalid friend?
++ memset(&p->friends[count], 0, sizeof(p->friends[count]));
++ break;
++ }
++ next+=pos;
++ //What IF the name contains a comma? while the next field is not a
++ //number, we assume it belongs to the current name. [Skotlex]
++ //NOTE: Of course, this will fail if someone sets their name to something like
++ //Bob,2005 but... meh, it's the problem of parsing a text file (encasing it in "
++ //won't do as quotes are also valid name chars!)
++ while(next < len && sscanf(line+next, ",%23[^,]%n", temp, &len) > 0)
++ {
++ if (atoi(temp))
++ { //We read the next friend, just continue.
++ break;
++ } else { //Append the name.
++ next+=len;
++ if (strlen(p->friends[count].name) + strlen(temp) +1 < NAME_LENGTH)
++ {
++ strcat(p->friends[count].name, ",");
++ strcat(p->friends[count].name, temp);
++ }
++ }
++ } //End Guess Block
++ } //Friend's for.
++ break; //Finished reading.
++ }
++ /*
++ //Character names must not exceed the 23+\0 limit. [Skotlex]
++ sscanf(line, "%d,%d,%23[^,],%d,%23[^,],%d,%23[^,],%d,%23[^,],%d,%23[^,],%d,%23[^,],%d,%23[^,],%d,%23[^,],%d,%23[^,],%d,%23[^,],%d,%23[^,],%d,%23[^,],%d,%23[^,],%d,%23[^,],%d,%23[^,],%d,%23[^,],%d,%23[^,],%d,%23[^,],%d,%23[^,],%d,%23s",&cid,
++ &temp[0],p->friend_name[0],
++ &temp[1],p->friend_name[1],
++ &temp[2],p->friend_name[2],
++ &temp[3],p->friend_name[3],
++ &temp[4],p->friend_name[4],
++ &temp[5],p->friend_name[5],
++ &temp[6],p->friend_name[6],
++ &temp[7],p->friend_name[7],
++ &temp[8],p->friend_name[8],
++ &temp[9],p->friend_name[9],
++ &temp[10],p->friend_name[10],
++ &temp[11],p->friend_name[11],
++ &temp[12],p->friend_name[12],
++ &temp[13],p->friend_name[13],
++ &temp[14],p->friend_name[14],
++ &temp[15],p->friend_name[15],
++ &temp[16],p->friend_name[16],
++ &temp[17],p->friend_name[17],
++ &temp[18],p->friend_name[18],
++ &temp[19],p->friend_name[19]);
++ if (cid == p->char_id)
++ break;
++ }
++ // No register of friends list
++ if (cid == 0) {
++ fclose(fp);
++ return 0;
++ }
++
++ // Fill in the list
++
++ for (i=0; i<MAX_FRIENDS; i++)
++ p->friend_id[i] = temp[i];
++*/
++ fclose(fp);
++ return count;
++}
+ + //--------------------------------- + // Function to read characters file + //--------------------------------- + int mmo_char_init(void) { + char line[65536]; +- int i; + int ret, line_count; + FILE *fp; + + char_max = 256; +- char_dat = calloc(sizeof(struct mmo_charstatus) * 256, 1); ++ char_dat = (struct character_data*)aCalloc(sizeof(struct character_data) * 256, 1);
+ if (!char_dat) { +- printf("out of memory: mmo_char_init (calloc of char_dat).\n"); +- exit(1); +- } +- online_chars = calloc(sizeof(int) * 256, 1); +- if (!online_chars) { +- printf("out of memory: mmo_char_init (calloc of online_chars).\n"); ++ ShowFatalError("out of memory: mmo_char_init (calloc of char_dat).\n");
+ exit(1); + } +- for(i = 0; i < char_max; i++) +- online_chars[i] = -1; +- + char_num = 0; + + fp = fopen(char_txt, "r"); ++
+ if (fp == NULL) { +- printf("Characters file not found: %s.\n", char_txt); ++ ShowError("Characters file not found: %s.\n", char_txt);
+ char_log("Characters file not found: %s." RETCODE, char_txt); + char_log("Id for the next created character: %d." RETCODE, char_id_count); + return 0; +@@ -583,30 +868,26 @@ + + if (char_num >= char_max) { + char_max += 256; +- char_dat = realloc(char_dat, sizeof(struct mmo_charstatus) * char_max); ++ char_dat = (struct character_data*)aRealloc(char_dat, sizeof(struct character_data) * char_max);
+ if (!char_dat) { +- printf("Out of memory: mmo_char_init (realloc of char_dat).\n"); ++ ShowFatalError("Out of memory: mmo_char_init (realloc of char_dat).\n");
+ char_log("Out of memory: mmo_char_init (realloc of char_dat)." RETCODE); + exit(1); + } +- online_chars = realloc(online_chars, sizeof(int) * char_max); +- if (!online_chars) { +- printf("Out of memory: mmo_char_init (realloc of online_chars).\n"); +- char_log("Out of memory: mmo_char_init (realloc of online_chars)." RETCODE); +- exit(1); +- } +- for(i = char_max - 256; i < char_max; i++) +- online_chars[i] = -1; + } + +- ret = mmo_char_fromstr(line, &char_dat[char_num]); ++ ret = mmo_char_fromstr(line, &char_dat[char_num].status, char_dat[char_num].global, &char_dat[char_num].global_num);
++
++ // Initialize friends list
++ parse_friend_txt(&char_dat[char_num].status); // Grab friends for the character
++
+ if (ret > 0) { // negative value or zero for errors +- if (char_dat[char_num].char_id >= char_id_count) +- char_id_count = char_dat[char_num].char_id + 1; ++ if (char_dat[char_num].status.char_id >= char_id_count)
++ char_id_count = char_dat[char_num].status.char_id + 1;
+ char_num++; + } else { +- printf("mmo_char_init: in characters file, unable to read the line #%d.\n", line_count); +- printf(" -> Character saved in log file.\n"); ++ ShowError("mmo_char_init: in characters file, unable to read the line #%d.\n", line_count);
++ ShowError(" -> Character saved in log file.\n");
+ switch (ret) { + case -1: + char_log("Duplicate character id in the next character line (character not readed):" RETCODE); +@@ -639,13 +920,13 @@ + fclose(fp); + + if (char_num == 0) { +- printf("mmo_char_init: No character found in %s.\n", char_txt); ++ ShowNotice("mmo_char_init: No character found in %s.\n", char_txt);
+ char_log("mmo_char_init: No character found in %s." RETCODE, char_txt); + } else if (char_num == 1) { +- printf("mmo_char_init: 1 character read in %s.\n", char_txt); ++ ShowStatus("mmo_char_init: 1 character read in %s.\n", char_txt);
+ char_log("mmo_char_init: 1 character read in %s." RETCODE, char_txt); + } else { +- printf("mmo_char_init: %d characters read in %s.\n", char_num, char_txt); ++ ShowStatus("mmo_char_init: %d characters read in %s.\n", char_num, char_txt);
+ char_log("mmo_char_init: %d characters read in %s." RETCODE, char_num, char_txt); + } + +@@ -658,20 +939,21 @@ + // Function to save characters in files (speed up by [Yor]) + //--------------------------------------------------------- + void mmo_char_sync(void) { +- char line[65536]; ++ char line[65536],f_line[1024];
+ int i, j, k; + int lock; +- FILE *fp; +- int id[char_num]; ++ FILE *fp,*f_fp;
++ //int *id = (int *) aMalloc(sizeof(int) * char_num);
++ CREATE_BUFFER(id, int, char_num);
+ + // Sorting before save (by [Yor]) + for(i = 0; i < char_num; i++) { + id[i] = i; + for(j = 0; j < i; j++) { +- if ((char_dat[i].account_id < char_dat[id[j]].account_id) || ++ if ((char_dat[i].status.account_id < char_dat[id[j]].status.account_id) ||
+ // if same account id, we sort by slot. +- (char_dat[i].account_id == char_dat[id[j]].account_id && +- char_dat[i].char_num < char_dat[id[j]].char_num)) { ++ (char_dat[i].status.account_id == char_dat[id[j]].status.account_id &&
++ char_dat[i].status.char_num < char_dat[id[j]].status.char_num)) {
+ for(k = i; k > j; k--) + id[k] = id[k-1]; + id[j] = i; // id[i] +@@ -683,12 +965,12 @@ + // Data save + fp = lock_fopen(char_txt, &lock); + if (fp == NULL) { +- printf("WARNING: Server can't not save characters.\n"); ++ ShowWarning("Server can't not save characters.\n");
+ char_log("WARNING: Server can't not save characters." RETCODE); + } else { + for(i = 0; i < char_num; i++) { + // create only once the line, and save it in the 2 files (it's speeder than repeat twice the loop and create twice the line) +- mmo_char_tostr(line, &char_dat[id[i]]); // use of sorted index ++ mmo_char_tostr(line, &char_dat[id[i]].status, char_dat[id[i]].global, char_dat[id[i]].global_num); // use of sorted index
+ fprintf(fp, "%s" RETCODE, line); + } + fprintf(fp, "%d\t%%newid%%" RETCODE, char_id_count); +@@ -699,19 +981,33 @@ + if (backup_txt_flag) { // The backup_txt file was created because char deletion bug existed. Now it's finish and that take a lot of time to create a second file when there are a lot of characters. => option By [Yor] + fp = lock_fopen(backup_txt, &lock); + if (fp == NULL) { +- printf("WARNING: Server can't not create backup of characters file.\n"); ++ ShowWarning("Server can't not create backup of characters file.\n");
+ char_log("WARNING: Server can't not create backup of characters file." RETCODE); ++ //aFree(id); // free up the memory before leaving -.- [Ajarn]
++ DELETE_BUFFER(id);
+ return; + } + for(i = 0; i < char_num; i++) { + // create only once the line, and save it in the 2 files (it's speeder than repeat twice the loop and create twice the line) +- mmo_char_tostr(line, &char_dat[id[i]]); // use of sorted index ++ mmo_char_tostr(line, &char_dat[id[i]].status,char_dat[id[i]].global, char_dat[id[i]].global_num); // use of sorted index
+ fprintf(fp, "%s" RETCODE, line); + } + fprintf(fp, "%d\t%%newid%%" RETCODE, char_id_count); + lock_fclose(fp, backup_txt, &lock); + } + ++ // Friends List data save (davidsiaw)
++ f_fp = lock_fopen(friends_txt, &lock);
++ for(i = 0; i < char_num; i++) {
++ mmo_friends_list_data_str(f_line, &char_dat[id[i]].status);
++ fprintf(f_fp, "%s" RETCODE, f_line);
++ }
++
++ lock_fclose(f_fp, friends_txt, &lock);
++
++ //aFree(id);
++ DELETE_BUFFER(id);
++
+ return; + } + +@@ -719,6 +1015,8 @@ + // Function to save (in a periodic way) datas in files + //---------------------------------------------------- + int mmo_char_sync_timer(int tid, unsigned int tick, int id, int data) { ++ if (save_log)
++ ShowInfo("Saving all files...\n");
+ mmo_char_sync(); + inter_save(); + return 0; +@@ -728,21 +1026,33 @@ + // Function to create a new character + //----------------------------------- + int make_new_char(int fd, unsigned char *dat) { +- int i, j; ++ int i;
+ struct char_session_data *sd; ++ char name[NAME_LENGTH];
+ +- sd = session[fd]->session_data; ++ sd = (struct char_session_data*)session[fd]->session_data;
+ + // remove control characters from the name +- dat[23] = '\0'; +- if (remove_control_chars(dat)) { ++ strncpy(name, dat, NAME_LENGTH);
++ name[NAME_LENGTH-1] = '\0'; //Trunc name to max possible value (23)
++
++ trim(name,TRIM_CHARS); //Trim character name. [Skotlex]
++
++ //check name != main chat nick [LuzZza]
++ if(strcmpi(name, main_chat_nick) == 0) {
++ char_log("Create char failed (%d): this nick (%s) reserved for mainchat messages." RETCODE,
++ sd->account_id, name);
++ return -1;
++ }
++
++ if (remove_control_chars((unsigned char *)name)) {
+ char_log("Make new char error (control char received in the name): (connection #%d, account: %d)." RETCODE, + fd, sd->account_id); + return -1; + } + + // check lenght of character name +- if (strlen(dat) < 4) { ++ if (strlen(name) < 4) {
+ char_log("Make new char error (character name too small): (connection #%d, account: %d, name: '%s')." RETCODE, + fd, sd->account_id, dat); + return -1; +@@ -750,15 +1060,15 @@ + + // Check Authorised letters/symbols in the name of the character + if (char_name_option == 1) { // only letters/symbols in char_name_letters are authorised +- for (i = 0; dat[i]; i++) +- if (strchr(char_name_letters, dat[i]) == NULL) { ++ for (i = 0; i < NAME_LENGTH && name[i]; i++)
++ if (strchr(char_name_letters, name[i]) == NULL) {
+ char_log("Make new char error (invalid letter in the name): (connection #%d, account: %d), name: %s, invalid letter: %c." RETCODE, +- fd, sd->account_id, dat, dat[i]); ++ fd, sd->account_id, name, name[i]);
+ return -1; + } + } else if (char_name_option == 2) { // letters/symbols in char_name_letters are forbidden +- for (i = 0; dat[i]; i++) +- if (strchr(char_name_letters, dat[i]) != NULL) { ++ for (i = 0; i < NAME_LENGTH && name[i]; i++)
++ if (strchr(char_name_letters, name[i]) != NULL) {
+ char_log("Make new char error (invalid letter in the name): (connection #%d, account: %d), name: %s, invalid letter: %c." RETCODE, + fd, sd->account_id, dat, dat[i]); + return -1; +@@ -767,8 +1077,8 @@ + + if (dat[24] + dat[25] + dat[26] + dat[27] + dat[28] + dat[29] != 5*6 || // stats + dat[30] >= 9 || // slots (dat[30] can not be negativ) +- dat[33] <= 0 || dat[33] >= 20 || // hair style +- dat[31] >= 12) { // hair color (dat[31] can not be negativ) ++ dat[33] <= 0 || dat[33] >= 24 || // hair style
++ dat[31] >= 9) { // hair color (dat[31] can not be negativ)
+ char_log("Make new char error (invalid values): (connection #%d, account: %d) slot %d, name: %s, stats: %d+%d+%d+%d+%d+%d=%d, hair: %d, hair color: %d" RETCODE, + fd, sd->account_id, dat[30], dat, dat[24], dat[25], dat[26], dat[27], dat[28], dat[29], dat[24] + dat[25] + dat[26] + dat[27] + dat[28] + dat[29], dat[33], dat[31]); + return -1; +@@ -781,98 +1091,96 @@ + fd, sd->account_id, dat[30], dat, dat[24], dat[25], dat[26], dat[27], dat[28], dat[29], dat[24] + dat[25] + dat[26] + dat[27] + dat[28] + dat[29], dat[33], dat[31]); + return -1; + } ++ } // now we know that every stat has proper value but we have to check if str/int agi/luk vit/dex pairs are correct
++
++ if( ((dat[24]+dat[27]) > 10) || ((dat[25]+dat[29]) > 10) || ((dat[26]+dat[28]) > 10) ) {
++ if (log_char) {
++ char_log("Make new char error (invalid stat value): (connection #%d, account: %d) slot %d, name: %s, stats: %d+%d+%d+%d+%d+%d=%d, hair: %d, hair color: %d" RETCODE,
++ fd, sd->account_id, dat[30], dat, dat[24], dat[25], dat[26], dat[27], dat[28], dat[29], dat[24] + dat[25] + dat[26] + dat[27] + dat[28] + dat[29], dat[33], dat[31]);
++ return -1;
+ } ++ } // now when we have passed all stat checks
+ + for(i = 0; i < char_num; i++) { +- if ((name_ignoring_case != 0 && strcmp(char_dat[i].name, dat) == 0) || +- (name_ignoring_case == 0 && strcmpi(char_dat[i].name, dat) == 0)) { ++ if ((name_ignoring_case != 0 && strncmp(char_dat[i].status.name, name, NAME_LENGTH) == 0) ||
++ (name_ignoring_case == 0 && strncmpi(char_dat[i].status.name, name, NAME_LENGTH) == 0)) {
+ char_log("Make new char error (name already exists): (connection #%d, account: %d) slot %d, name: %s (actual name of other char: %d), stats: %d+%d+%d+%d+%d+%d=%d, hair: %d, hair color: %d." RETCODE, +- fd, sd->account_id, dat[30], dat, char_dat[i].name, dat[24], dat[25], dat[26], dat[27], dat[28], dat[29], dat[24] + dat[25] + dat[26] + dat[27] + dat[28] + dat[29], dat[33], dat[31]); ++ fd, sd->account_id, dat[30], dat, char_dat[i].status.name, dat[24], dat[25], dat[26], dat[27], dat[28], dat[29], dat[24] + dat[25] + dat[26] + dat[27] + dat[28] + dat[29], dat[33], dat[31]);
+ return -1; + } +- if (char_dat[i].account_id == sd->account_id && char_dat[i].char_num == dat[30]) { ++ if (char_dat[i].status.account_id == sd->account_id && char_dat[i].status.char_num == dat[30]) {
+ char_log("Make new char error (slot already used): (connection #%d, account: %d) slot %d, name: %s (actual name of other char: %d), stats: %d+%d+%d+%d+%d+%d=%d, hair: %d, hair color: %d." RETCODE, +- fd, sd->account_id, dat[30], dat, char_dat[i].name, dat[24], dat[25], dat[26], dat[27], dat[28], dat[29], dat[24] + dat[25] + dat[26] + dat[27] + dat[28] + dat[29], dat[33], dat[31]); ++ fd, sd->account_id, dat[30], dat, char_dat[i].status.name, dat[24], dat[25], dat[26], dat[27], dat[28], dat[29], dat[24] + dat[25] + dat[26] + dat[27] + dat[28] + dat[29], dat[33], dat[31]);
+ return -1; + } + } + +- if (strcmp(wisp_server_name, dat) == 0) { ++ if (strcmp(wisp_server_name, name) == 0) {
+ char_log("Make new char error (name used is wisp name for server): (connection #%d, account: %d) slot %d, name: %s (actual name of other char: %d), stats: %d+%d+%d+%d+%d+%d=%d, hair: %d, hair color: %d." RETCODE, +- fd, sd->account_id, dat[30], dat, char_dat[i].name, dat[24], dat[25], dat[26], dat[27], dat[28], dat[29], dat[24] + dat[25] + dat[26] + dat[27] + dat[28] + dat[29], dat[33], dat[31]); ++ fd, sd->account_id, dat[30], name, char_dat[i].status.name, dat[24], dat[25], dat[26], dat[27], dat[28], dat[29], dat[24] + dat[25] + dat[26] + dat[27] + dat[28] + dat[29], dat[33], dat[31]);
+ return -1; + } + + if (char_num >= char_max) { + char_max += 256; +- char_dat = realloc(char_dat, sizeof(struct mmo_charstatus) * char_max); ++ char_dat = (struct character_data*)aRealloc(char_dat, sizeof(struct character_data) * char_max);
+ if (!char_dat) { +- printf("Out of memory: make_new_char (realloc of char_dat).\n"); ++ ShowFatalError("Out of memory: make_new_char (realloc of char_dat).\n");
+ char_log("Out of memory: make_new_char (realloc of char_dat)." RETCODE); + exit(1); + } +- online_chars = realloc(online_chars, sizeof(int) * char_max); +- if (!online_chars) { +- printf("Out of memory: make_new_char (realloc of online_chars).\n"); +- char_log("Out of memory: make_new_char (realloc of online_chars)." RETCODE); +- exit(1); +- } +- for(j = char_max - 256; j < char_max; j++) +- online_chars[j] = -1; + } + + char_log("Creation of New Character: (connection #%d, account: %d) slot %d, character Name: %s, stats: %d+%d+%d+%d+%d+%d=%d, hair: %d, hair color: %d." RETCODE, +- fd, sd->account_id, dat[30], dat, dat[24], dat[25], dat[26], dat[27], dat[28], dat[29], dat[24] + dat[25] + dat[26] + dat[27] + dat[28] + dat[29], dat[33], dat[31]); ++ fd, sd->account_id, dat[30], name, dat[24], dat[25], dat[26], dat[27], dat[28], dat[29], dat[24] + dat[25] + dat[26] + dat[27] + dat[28] + dat[29], dat[33], dat[31]);
+ +- memset(&char_dat[i], 0, sizeof(struct mmo_charstatus)); ++ memset(&char_dat[i], 0, sizeof(struct character_data));
+ +- char_dat[i].char_id = char_id_count++; +- char_dat[i].account_id = sd->account_id; +- char_dat[i].char_num = dat[30]; +- strcpy(char_dat[i].name, dat); +- char_dat[i].class = 0; +- char_dat[i].base_level = 1; +- char_dat[i].job_level = 1; +- char_dat[i].base_exp = 0; +- char_dat[i].job_exp = 0; +- char_dat[i].zeny = start_zeny; +- char_dat[i].str = dat[24]; +- char_dat[i].agi = dat[25]; +- char_dat[i].vit = dat[26]; +- char_dat[i].int_ = dat[27]; +- char_dat[i].dex = dat[28]; +- char_dat[i].luk = dat[29]; +- char_dat[i].max_hp = 40 * (100 + char_dat[i].vit) / 100; +- char_dat[i].max_sp = 11 * (100 + char_dat[i].int_) / 100; +- char_dat[i].hp = char_dat[i].max_hp; +- char_dat[i].sp = char_dat[i].max_sp; +- char_dat[i].status_point = 0; +- char_dat[i].skill_point = 0; +- char_dat[i].option = 0; +- char_dat[i].karma = 0; +- char_dat[i].manner = 0; +- char_dat[i].party_id = 0; +- char_dat[i].guild_id = 0; +- char_dat[i].hair = dat[33]; +- char_dat[i].hair_color = dat[31]; +- char_dat[i].clothes_color = 0; +- char_dat[i].inventory[0].nameid = start_weapon; // Knife +- char_dat[i].inventory[0].amount = 1; +- char_dat[i].inventory[0].equip = 0x02; +- char_dat[i].inventory[0].identify = 1; +- char_dat[i].inventory[0].broken = 0; +- char_dat[i].inventory[1].nameid = start_armor; // Cotton Shirt +- char_dat[i].inventory[1].amount = 1; +- char_dat[i].inventory[1].equip = 0x10; +- char_dat[i].inventory[1].identify = 1; +- char_dat[i].inventory[1].broken = 0; +- char_dat[i].weapon = 1; +- char_dat[i].shield = 0; +- char_dat[i].head_top = 0; +- char_dat[i].head_mid = 0; +- char_dat[i].head_bottom = 0; +- memcpy(&char_dat[i].last_point, &start_point, sizeof(start_point)); +- memcpy(&char_dat[i].save_point, &start_point, sizeof(start_point)); ++ char_dat[i].status.char_id = char_id_count++;
++ char_dat[i].status.account_id = sd->account_id;
++ char_dat[i].status.char_num = dat[30];
++ strcpy(char_dat[i].status.name,name);
++ char_dat[i].status.class_ = 0;
++ char_dat[i].status.base_level = 1;
++ char_dat[i].status.job_level = 1;
++ char_dat[i].status.base_exp = 0;
++ char_dat[i].status.job_exp = 0;
++ char_dat[i].status.zeny = start_zeny;
++ char_dat[i].status.str = dat[24];
++ char_dat[i].status.agi = dat[25];
++ char_dat[i].status.vit = dat[26];
++ char_dat[i].status.int_ = dat[27];
++ char_dat[i].status.dex = dat[28];
++ char_dat[i].status.luk = dat[29];
++ char_dat[i].status.max_hp = 40 * (100 + char_dat[i].status.vit) / 100;
++ char_dat[i].status.max_sp = 11 * (100 + char_dat[i].status.int_) / 100;
++ char_dat[i].status.hp = char_dat[i].status.max_hp;
++ char_dat[i].status.sp = char_dat[i].status.max_sp;
++ char_dat[i].status.status_point = 0;
++ char_dat[i].status.skill_point = 0;
++ char_dat[i].status.option = 0;
++ char_dat[i].status.karma = 0;
++ char_dat[i].status.manner = 0;
++ char_dat[i].status.party_id = 0;
++ char_dat[i].status.guild_id = 0;
++ char_dat[i].status.hair = dat[33];
++ char_dat[i].status.hair_color = dat[31];
++ char_dat[i].status.clothes_color = 0;
++ char_dat[i].status.inventory[0].nameid = start_weapon; // Knife
++ char_dat[i].status.inventory[0].amount = 1;
++ char_dat[i].status.inventory[0].equip = 0x02;
++ char_dat[i].status.inventory[0].identify = 1;
++ char_dat[i].status.inventory[1].nameid = start_armor; // Cotton Shirt
++ char_dat[i].status.inventory[1].amount = 1;
++ char_dat[i].status.inventory[1].equip = 0x10;
++ char_dat[i].status.inventory[1].identify = 1;
++ char_dat[i].status.weapon = 1;
++ char_dat[i].status.shield = 0;
++ char_dat[i].status.head_top = 0;
++ char_dat[i].status.head_mid = 0;
++ char_dat[i].status.head_bottom = 0;
++ memcpy(&char_dat[i].status.last_point, &start_point, sizeof(start_point));
++ memcpy(&char_dat[i].status.save_point, &start_point, sizeof(start_point));
+ char_num++; + + mmo_char_sync(); +@@ -882,8 +1190,8 @@ + //---------------------------------------------------- + // This function return the name of the job (by [Yor]) + //---------------------------------------------------- +-char * job_name(int class) { +- switch (class) { ++char * job_name(int class_) {
++ switch (class_) {
+ case 0: return "Novice"; + case 1: return "Swordsman"; + case 2: return "Mage"; +@@ -957,108 +1265,138 @@ + return "Unknown Job"; + } + +-//------------------------------------------------------------- +-// Function to create the online files (txt and html). by [Yor] +-//------------------------------------------------------------- +-void create_online_files(void) { +- int i, j, k, l; // for loops +- int players; // count the number of players +- FILE *fp; // for the txt file +- FILE *fp2; // for the html file +- char temp[256]; // to prepare what we must display +- time_t time_server; // for number of seconds +- struct tm *datetime; // variable for time in structure ->tm_mday, ->tm_sec, ... +- int id[char_num]; +- +- if (online_display_option == 0) // we display nothing, so return +- return; ++static int create_online_files_sub(DBKey key, void* data, va_list va)
++{
++ struct online_char_data *character;
++ int* players;
++ int *id;
++ int j,k,l;
++ character = (struct online_char_data*) data;
++ players = va_arg(va, int*);
++ id = va_arg(va, int*);
+ +- //char_log("Creation of online players files." RETCODE); ++ // check if map-server is online
++ if (character->server == -1 || character->char_id == -1) { //Character not currently online.
++ return -1;
++ }
+ +- // Get number of online players, id of each online players +- players = 0; +- // sort online characters. +- for(i = 0; i < char_num; i++) { +- if (online_chars[i] != -1) { +- id[players] = i; ++ j = character->server;
++ if (server_fd[j] < 0) {
++ server[j].users = 0;
++ if (kick_on_disconnect)
++ {
++ character->char_id = -1;
++ character->server = -1;
++ }
++ return -1;
++ }
++ // search position of character in char_dat and sort online characters.
++ for(j = 0; j < char_num; j++) {
++ if (char_dat[j].status.char_id != character->char_id)
++ continue;
++ id[*players] = j;
+ // use sorting option + switch (online_sorting_option) { + case 1: // by name (without case sensitive) +- { +- char *p_name = char_dat[i].name; //speed up sorting when there are a lot of players. But very rarely players have same name. +- for(j = 0; j < players; j++) +- if (stricmp(p_name, char_dat[id[j]].name) < 0 || ++ for(k = 0; k < *players; k++)
++ if (stricmp(char_dat[j].status.name, char_dat[id[k]].status.name) < 0 ||
+ // if same name, we sort with case sensitive. +- (stricmp(p_name, char_dat[id[j]].name) == 0 && +- strcmp(p_name, char_dat[id[j]].name) < 0)) { +- for(k = players; k > j; k--) +- id[k] = id[k-1]; +- id[j] = i; // id[players] ++ (stricmp(char_dat[j].status.name, char_dat[id[k]].status.name) == 0 &&
++ strcmp(char_dat[j].status.name, char_dat[id[k]].status.name) < 0)) {
++ for(l = *players; l > k; l--)
++ id[l] = id[l-1];
++ id[k] = j; // id[*players]
+ break; + } +- } + break; + case 2: // by zeny +- for(j = 0; j < players; j++) +- if (char_dat[i].zeny < char_dat[id[j]].zeny || ++ for(k = 0; k < *players; k++)
++ if (char_dat[j].status.zeny < char_dat[id[k]].status.zeny ||
+ // if same number of zenys, we sort by name. +- (char_dat[i].zeny == char_dat[id[j]].zeny && +- stricmp(char_dat[i].name, char_dat[id[j]].name) < 0)) { +- for(k = players; k > j; k--) +- id[k] = id[k-1]; +- id[j] = i; // id[players] ++ (char_dat[j].status.zeny == char_dat[id[k]].status.zeny &&
++ stricmp(char_dat[j].status.name, char_dat[id[k]].status.name) < 0)) {
++ for(l = *players; l > k; l--)
++ id[l] = id[l-1];
++ id[k] = j; // id[*players]
+ break; + } + break; + case 3: // by base level +- for(j = 0; j < players; j++) +- if (char_dat[i].base_level < char_dat[id[j]].base_level || ++ for(k = 0; k < *players; k++)
++ if (char_dat[j].status.base_level < char_dat[id[k]].status.base_level ||
+ // if same base level, we sort by base exp. +- (char_dat[i].base_level == char_dat[id[j]].base_level && +- char_dat[i].base_exp < char_dat[id[j]].base_exp)) { +- for(k = players; k > j; k--) +- id[k] = id[k-1]; +- id[j] = i; // id[players] ++ (char_dat[j].status.base_level == char_dat[id[k]].status.base_level &&
++ char_dat[j].status.base_exp < char_dat[id[k]].status.base_exp)) {
++ for(l = *players; l > k; l--)
++ id[l] = id[l-1];
++ id[k] = j; // id[*players]
+ break; + } + break; + case 4: // by job (and job level) +- for(j = 0; j < players; j++) +- if (char_dat[i].class < char_dat[id[j]].class || ++ for(k = 0; k < *players; k++)
++ if (char_dat[j].status.class_ < char_dat[id[k]].status.class_ ||
+ // if same job, we sort by job level. +- (char_dat[i].class == char_dat[id[j]].class && +- char_dat[i].job_level < char_dat[id[j]].job_level) || ++ (char_dat[j].status.class_ == char_dat[id[k]].status.class_ &&
++ char_dat[j].status.job_level < char_dat[id[k]].status.job_level) ||
+ // if same job and job level, we sort by job exp. +- (char_dat[i].class == char_dat[id[j]].class && +- char_dat[i].job_level == char_dat[id[j]].job_level && +- char_dat[i].job_exp < char_dat[id[j]].job_exp)) { +- for(k = players; k > j; k--) +- id[k] = id[k-1]; +- id[j] = i; // id[players] ++ (char_dat[j].status.class_ == char_dat[id[k]].status.class_ &&
++ char_dat[j].status.job_level == char_dat[id[k]].status.job_level &&
++ char_dat[j].status.job_exp < char_dat[id[k]].status.job_exp)) {
++ for(l = *players; l > k; l--)
++ id[l] = id[l-1];
++ id[k] = j; // id[*players]
+ break; + } + break; + case 5: // by location map name + { +- int cpm_result; // A lot of player maps are identical. So, test if done often twice. +- for(j = 0; j < players; j++) +- if ((cpm_result = strcmp(char_dat[i].last_point.map, char_dat[id[j]].last_point.map)) < 0 || // no map are identical and with upper cases (not use stricmp) ++ const char *map1, *map2;
++ map1 = mapindex_id2name(char_dat[j].status.last_point.map);
++
++ for(k = 0; k < *players; k++) {
++ map2 = mapindex_id2name(char_dat[id[k]].status.last_point.map);
++ if (!map1 || !map2 || //Avoid sorting if either one failed to resolve.
++ stricmp(map1, map2) < 0 ||
+ // if same map name, we sort by name. +- (cpm_result == 0 && +- stricmp(char_dat[i].name, char_dat[id[j]].name) < 0)) { +- for(k = players; k > j; k--) +- id[k] = id[k-1]; +- id[j] = i; // id[players] ++ (stricmp(map1, map2) == 0 &&
++ stricmp(char_dat[j].status.name, char_dat[id[k]].status.name) < 0)) {
++ for(l = *players; l > k; l--)
++ id[l] = id[l-1];
++ id[k] = j; // id[*players]
+ break; + } + } ++ }
+ break; + default: // 0 or invalid value: no sorting + break; + } +- players++; ++ (*players)++;
++ break;
+ } ++ return 0;
+ } ++//-------------------------------------------------------------
++// Function to create the online files (txt and html). by [Yor]
++//-------------------------------------------------------------
++void create_online_files(void) {
++ unsigned int k, j; // for loop with strlen comparing
++ int i, l; // for loops
++ int players; // count the number of players
++ FILE *fp; // for the txt file
++ FILE *fp2; // for the html file
++ char temp[256]; // to prepare what we must display
++ time_t time_server; // for number of seconds
++ struct tm *datetime; // variable for time in structure ->tm_mday, ->tm_sec, ...
++ int id[4096];
++
++ if (online_display_option == 0) // we display nothing, so return
++ return;
++
++ // Get number of online players, id of each online players, and verify if a server is offline
++ players = 0;
++ online_char_db->foreach(online_char_db, create_online_files_sub, &players, &id);
+ + // write files + fp = fopen(online_txt_filename, "w"); +@@ -1080,8 +1418,9 @@ + fprintf(fp, "Online Players on %s (%s):\n", server_name, temp); + fprintf(fp, "\n"); + +- // If we display at least 1 player +- if (players > 0) { ++ for (i = 0; i < players; i++) {
++ // if it's the first player
++ if (i == 0) {
+ j = 0; // count the number of characters for the txt version and to set the separate line + fprintf(fp2, " <table border=\"1\" cellspacing=\"1\">\n"); + fprintf(fp2, " <tr>\n"); +@@ -1128,16 +1467,14 @@ + for (k = 0; k < j; k++) + fprintf(fp, "-"); + fprintf(fp, "\n"); +- +- // display each player. +- for (i = 0; i < players; i++) { ++ }
++ fprintf(fp2, " <tr>\n");
+ // get id of the character (more speed) + j = id[i]; +- fprintf(fp2, " <tr>\n"); + // displaying the character name + if ((online_display_option & 1) || (online_display_option & 64)) { // without/with 'GM' display +- strcpy(temp, char_dat[j].name); +- l = isGM(char_dat[j].account_id); ++ strcpy(temp, char_dat[j].status.name);
++ l = isGM(char_dat[j].status.account_id);
+ if (online_display_option & 64) { + if (l >= online_gm_display_min_level) + fprintf(fp, "%-24s (GM) ", temp); +@@ -1149,7 +1486,7 @@ + fprintf(fp2, " <td>"); + if ((online_display_option & 64) && l >= online_gm_display_min_level) + fprintf(fp2, "<b>"); +- for (k = 0; temp[k]; k++) { ++ for (k = 0; k < strlen(temp); k++) {
+ switch(temp[k]) { + case '<': // < + fprintf(fp2, "<"); +@@ -1168,48 +1505,51 @@ + } + // displaying of the job + if (online_display_option & 6) { +- char * jobname = job_name(char_dat[j].class); ++ char * jobname = job_name(char_dat[j].status.class_);
+ if ((online_display_option & 6) == 6) { +- fprintf(fp2, " <td>%s %d/%d</td>\n", jobname, char_dat[j].base_level, char_dat[j].job_level); +- fprintf(fp, "%-18s %3d/%3d ", jobname, char_dat[j].base_level, char_dat[j].job_level); ++ fprintf(fp2, " <td>%s %d/%d</td>\n", jobname, char_dat[j].status.base_level, char_dat[j].status.job_level);
++ fprintf(fp, "%-18s %3d/%3d ", jobname, char_dat[j].status.base_level, char_dat[j].status.job_level);
+ } else if (online_display_option & 2) { + fprintf(fp2, " <td>%s</td>\n", jobname); + fprintf(fp, "%-18s ", jobname); + } else if (online_display_option & 4) { +- fprintf(fp2, " <td>%d/%d</td>\n", char_dat[j].base_level, char_dat[j].job_level); +- fprintf(fp, "%3d/%3d ", char_dat[j].base_level, char_dat[j].job_level); ++ fprintf(fp2, " <td>%d/%d</td>\n", char_dat[j].status.base_level, char_dat[j].status.job_level);
++ fprintf(fp, "%3d/%3d ", char_dat[j].status.base_level, char_dat[j].status.job_level);
+ } + } + // displaying of the map + if (online_display_option & 24) { // 8 or 16 + // prepare map name +- memset(temp, 0, sizeof(temp)); +- strncpy(temp, char_dat[j].last_point.map, 16); +- if (strchr(temp, '.') != NULL) +- temp[strchr(temp, '.') - temp] = '\0'; // suppress the '.gat' ++ memcpy(temp, mapindex_id2name(char_dat[j].status.last_point.map), MAP_NAME_LENGTH);
++ temp[MAP_NAME_LENGTH] = '\0';
++ if (strstr(temp, ".gat") != NULL) {
++ temp[strstr(temp, ".gat") - temp] = 0; // suppress the '.gat'
++ }
+ // write map name +- if (online_display_option & 16) { // map-name AND coordonates +- fprintf(fp2, " <td>%s (%d, %d)</td>\n", temp, char_dat[j].last_point.x, char_dat[j].last_point.y); +- fprintf(fp, "%-12s (%3d,%3d) ", temp, char_dat[j].last_point.x, char_dat[j].last_point.y); ++ if (online_display_option & 16) { // map-name AND coordinates
++ fprintf(fp2, " <td>%s (%d, %d)</td>\n", temp, char_dat[j].status.last_point.x, char_dat[j].status.last_point.y);
++ fprintf(fp, "%-12s (%3d,%3d) ", temp, char_dat[j].status.last_point.x, char_dat[j].status.last_point.y);
+ } else { + fprintf(fp2, " <td>%s</td>\n", temp); + fprintf(fp, "%-12s ", temp); + } + } +- // displaying number of zenys ++ // displaying nimber of zenys
+ if (online_display_option & 32) { + // write number of zenys +- if (char_dat[j].zeny == 0) { // if no zeny ++ if (char_dat[j].status.zeny == 0) { // if no zeny
+ fprintf(fp2, " <td ALIGN=RIGHT>no zeny</td>\n"); + fprintf(fp, " no zeny "); + } else { +- fprintf(fp2, " <td ALIGN=RIGHT>%d z</td>\n", char_dat[j].zeny); +- fprintf(fp, "%13d z ", char_dat[j].zeny); ++ fprintf(fp2, " <td ALIGN=RIGHT>%d z</td>\n", char_dat[j].status.zeny);
++ fprintf(fp, "%13d z ", char_dat[j].status.zeny);
+ } + } + fprintf(fp, "\n"); + fprintf(fp2, " </tr>\n"); + } ++ // If we display at least 1 player
++ if (players > 0) {
+ fprintf(fp2, " </table>\n"); + fprintf(fp, "\n"); + } +@@ -1218,8 +1558,9 @@ + if (players == 0) { + fprintf(fp2, " <p>No user is online.</p>\n"); + fprintf(fp, "No user is online.\n"); +- // no display if only 1 player + } else if (players == 1) { ++ fprintf(fp2, " <p>%d user is online.</p>\n", players);
++ fprintf(fp, "%d user is online.\n", players);
+ } else { + fprintf(fp2, " <p>%d users are online.</p>\n", players); + fprintf(fp, "%d users are online.\n", players); +@@ -1254,15 +1595,17 @@ + int mmo_char_send006b(int fd, struct char_session_data *sd) { + int i, j, found_num; + struct mmo_charstatus *p; +-#ifdef NEW_006b ++//#ifdef NEW_006b
+ const int offset = 24; +-#else +- const int offset = 4; +-#endif ++//#else
++// const int offset = 4;
++//#endif
++
++ set_char_online(0, 99,sd->account_id);
+ + found_num = 0; + for(i = 0; i < char_num; i++) { +- if (char_dat[i].account_id == sd->account_id) { ++ if (char_dat[i].status.account_id == sd->account_id) {
+ sd->found_char[found_num] = i; + found_num++; + if (found_num == 9) +@@ -1272,18 +1615,19 @@ + for(i = found_num; i < 9; i++) + sd->found_char[i] = -1; + ++ WFIFOHEAD(fd, offset + found_num * 106);
+ memset(WFIFOP(fd,0), 0, offset + found_num * 106); + WFIFOW(fd,0) = 0x6b; + WFIFOW(fd,2) = offset + found_num * 106; + + for(i = 0; i < found_num; i++) { +- p = &char_dat[sd->found_char[i]]; ++ p = &char_dat[sd->found_char[i]].status;
+ j = offset + (i * 106); // increase speed of code + + WFIFOL(fd,j) = p->char_id; +- WFIFOL(fd,j+4) = p->base_exp; ++ WFIFOL(fd,j+4) = p->base_exp>LONG_MAX?LONG_MAX:p->base_exp;
+ WFIFOL(fd,j+8) = p->zeny; +- WFIFOL(fd,j+12) = p->job_exp; ++ WFIFOL(fd,j+12) = p->job_exp>LONG_MAX?LONG_MAX:p->job_exp;
+ WFIFOL(fd,j+16) = p->job_level; + + WFIFOL(fd,j+20) = 0; +@@ -1299,9 +1643,16 @@ + WFIFOW(fd,j+46) = (p->sp > 0x7fff) ? 0x7fff : p->sp; + WFIFOW(fd,j+48) = (p->max_sp > 0x7fff) ? 0x7fff : p->max_sp; + WFIFOW(fd,j+50) = DEFAULT_WALK_SPEED; // p->speed; +- WFIFOW(fd,j+52) = p->class; ++ WFIFOW(fd,j+52) = p->class_;
+ WFIFOW(fd,j+54) = p->hair; +- WFIFOW(fd,j+56) = p->weapon; ++
++ // pecopeco knights/crusaders crash fix
++ if (p->class_ == 13 || p->class_ == 21 ||
++ p->class_ == 4014 || p->class_ == 4022 ||
++ p->class_ == 4036 || p->class_ == 4044)
++ WFIFOW(fd,j+56) = 0;
++ else WFIFOW(fd,j+56) = p->weapon;
++
+ WFIFOW(fd,j+58) = p->base_level; + WFIFOW(fd,j+60) = p->skill_point; + WFIFOW(fd,j+62) = p->head_bottom; +@@ -1311,7 +1662,7 @@ + WFIFOW(fd,j+70) = p->hair_color; + WFIFOW(fd,j+72) = p->clothes_color; + +- memcpy(WFIFOP(fd,j+74), p->name, 24); ++ memcpy(WFIFOP(fd,j+74), p->name, NAME_LENGTH);
+ + WFIFOB(fd,j+98) = (p->str > 255) ? 255 : p->str; + WFIFOB(fd,j+99) = (p->agi > 255) ? 255 : p->agi; +@@ -1327,34 +1678,20 @@ + return 0; + } + +-int set_account_reg2(int acc, int num, struct global_reg *reg) { +- int i, c; ++// 離婚(char削除時に使用)
++int char_divorce(struct mmo_charstatus *cs) {
++ if (cs == NULL)
++ return 0;
+ +- c = 0; ++ if (cs->partner_id > 0){
++ int i, j;
+ for(i = 0; i < char_num; i++) { +- if (char_dat[i].account_id == acc) { +- memcpy(char_dat[i].account_reg2, reg, sizeof(char_dat[i].account_reg2)); +- char_dat[i].account_reg2_num = num; +- c++; +- } +- } +- return c; +-} +- +-// 離婚(char削除時に使用) +-int char_divorce(struct mmo_charstatus *cs) { +- if (cs == NULL) +- return 0; +- +- if (cs->partner_id > 0){ +- int i, j; +- for(i = 0; i < char_num; i++) { +- if (char_dat[i].char_id == cs->partner_id && char_dat[i].partner_id == cs->char_id) { ++ if (char_dat[i].status.char_id == cs->partner_id && char_dat[i].status.partner_id == cs->char_id) {
+ cs->partner_id = 0; +- char_dat[i].partner_id = 0; ++ char_dat[i].status.partner_id = 0;
+ for(j = 0; j < MAX_INVENTORY; j++) +- if (char_dat[i].inventory[j].nameid == WEDDING_RING_M || char_dat[i].inventory[j].nameid == WEDDING_RING_F) +- memset(&char_dat[i].inventory[j], 0, sizeof(char_dat[i].inventory[0])); ++ if (char_dat[i].status.inventory[j].nameid == WEDDING_RING_M || char_dat[i].status.inventory[j].nameid == WEDDING_RING_F)
++ memset(&char_dat[i].status.inventory[j], 0, sizeof(char_dat[i].status.inventory[0]));
+ if (cs->inventory[j].nameid == WEDDING_RING_M || cs->inventory[j].nameid == WEDDING_RING_F) + memset(&cs->inventory[j], 0, sizeof(cs->inventory[0])); + return 0; +@@ -1364,12 +1701,22 @@ + return 0; + } + ++int char_married(int pl1,int pl2) {
++ return (char_dat[pl1].status.char_id == char_dat[pl2].status.partner_id && char_dat[pl2].status.char_id == char_dat[pl1].status.partner_id);
++}
++
++int char_child(int parent_id, int child_id) {
++ return (char_dat[parent_id].status.child == char_dat[child_id].status.char_id &&
++ ((char_dat[parent_id].status.char_id == char_dat[child_id].status.father) ||
++ (char_dat[parent_id].status.char_id == char_dat[child_id].status.mother)));
++}
++
+ //------------------------------------------------------------ + // E-mail check: return 0 (not correct) or 1 (valid). by [Yor] + //------------------------------------------------------------ +-int e_mail_check(unsigned char *email) { ++int e_mail_check(char *email) {
+ char ch; +- unsigned char* last_arobas; ++ char* last_arobas;
+ + // athena limits + if (strlen(email) < 3 || strlen(email) > 39) +@@ -1412,7 +1759,7 @@ + + // disconnect player if online on char-server + for(i = 0; i < fd_max; i++) { +- if (session[i] && (sd = session[i]->session_data)) { ++ if (session[i] && (sd = (struct char_session_data*)session[i]->session_data)) {
+ if (sd->account_id == accound_id) { + session[i]->eof = 1; + return 1; +@@ -1432,20 +1779,20 @@ + inter_pet_delete(cs->pet_id); + for (j = 0; j < MAX_INVENTORY; j++) + if (cs->inventory[j].card[0] == (short)0xff00) +- inter_pet_delete(*((long *)(&cs->inventory[j].card[2]))); ++ inter_pet_delete(MakeDWord(cs->inventory[j].card[1],cs->inventory[j].card[2]));
+ for (j = 0; j < MAX_CART; j++) + if (cs->cart[j].card[0] == (short)0xff00) +- inter_pet_delete(*((long *)(&cs->cart[j].card[2]))); ++ inter_pet_delete( MakeDWord(cs->cart[j].card[1],cs->cart[j].card[2]) );
+ // ギルド脱退 + if (cs->guild_id) + inter_guild_leave(cs->guild_id, cs->account_id, cs->char_id); + // パーティー脱退 + if (cs->party_id) +- inter_party_leave(cs->party_id, cs->account_id); ++ inter_party_leave(cs->party_id, cs->account_id, cs->char_id);
+ // 離婚 + if (cs->partner_id){ + // 離婚情報をmapに通知 +- char buf[10]; ++ unsigned char buf[10];
+ WBUFW(buf,0) = 0x2b12; + WBUFL(buf,2) = cs->char_id; + WBUFL(buf,6) = cs->partner_id; +@@ -1459,22 +1806,24 @@ + int parse_tologin(int fd) { + int i; + struct char_session_data *sd; ++ RFIFOHEAD(fd);
+ + // only login-server can have an access to here. + // so, if it isn't the login-server, we disconnect the session (fd != login_fd). +- if (fd != login_fd || session[fd]->eof) { ++ if (fd != login_fd)
++ session[fd]->eof = 1;
++ if(session[fd]->eof) {
+ if (fd == login_fd) { +- printf("Char-server can't connect to login-server (connection #%d).\n", fd); ++ ShowWarning("Connection to login-server lost (connection #%d).\n", fd);
+ login_fd = -1; + } +- close(fd); +- delete_session(fd); ++ do_close(fd);
+ return 0; + } + +- sd = session[fd]->session_data; ++ sd = (struct char_session_data*)session[fd]->session_data;
+ +- while(RFIFOREST(fd) >= 2) { ++ while(RFIFOREST(fd) >= 2 && !session[fd]->eof) {
+ // printf("parse_tologin: connection #%d, packet: 0x%x (with being read: %d bytes).\n", fd, RFIFOW(fd,0), RFIFOREST(fd)); + + switch(RFIFOW(fd,0)) { +@@ -1483,19 +1832,21 @@ + return 0; + if (RFIFOB(fd,2)) { + // printf("connect login server error : %d\n", RFIFOB(fd,2)); +- printf("Can not connect to login-server.\n"); +- printf("The server communication passwords (default s1/p1) is probably invalid.\n"); +- printf("Also, please make sure your accounts file (default: accounts.txt) has those values present.\n"); +- printf("If you changed the communication passwords, change them back at map_athena.conf and char_athena.conf\n"); ++ ShowError("Can not connect to the login-server.\n");
++ ShowError("The server communication passwords (default s1/p1) are probably invalid.\n");
++ ShowInfo("Also, please make sure your accounts file (default: accounts.txt) has those values present.\n");
++ ShowInfo("The communication passwords can be changed in map_athena.conf and char_athena.conf\n");
+ exit(1); + } else { +- printf("Connected to login-server (connection #%d).\n", fd); ++ ShowStatus("Connected to login-server (connection #%d).\n", fd);
++ if (kick_on_disconnect)
++ set_all_offline();
+ // if no map-server already connected, display a message... + for(i = 0; i < MAX_MAP_SERVERS; i++) +- if (server_fd[i] >= 0 && server[i].map[0][0]) // if map-server online and at least 1 map ++ if (server_fd[i] >= 0 && server[i].map[0]) // if map-server online and at least 1 map
+ break; + if (i == MAX_MAP_SERVERS) +- printf("Awaiting maps from map-server.\n"); ++ ShowStatus("Awaiting maps from map-server.\n");
+ } + RFIFOSKIP(fd,3); + break; +@@ -1505,8 +1856,9 @@ + return 0; + // printf("parse_tologin 2713 : %d\n", RFIFOB(fd,6)); + for(i = 0; i < fd_max; i++) { +- if (session[i] && (sd = session[i]->session_data) && sd->account_id == RFIFOL(fd,2)) { ++ if (session[i] && (sd = (struct char_session_data*)session[i]->session_data) && sd->account_id == RFIFOL(fd,2)) {
+ if (RFIFOB(fd,6) != 0) { ++ WFIFOHEAD(i, 3);
+ WFIFOW(i,0) = 0x6c; + WFIFOB(i,2) = 0x42; + WFIFOSET(i,3); +@@ -1521,9 +1873,14 @@ + sd->connect_until_time = (time_t)RFIFOL(fd,47); + // send characters to player + mmo_char_send006b(i, sd); ++ } else if(isGM(sd->account_id) >= gm_allow_level) {
++ sd->connect_until_time = (time_t)RFIFOL(fd,47);
++ // send characters to player
++ mmo_char_send006b(i, sd);
+ } else { + // refuse connection: too much online players + // printf("count_users(): %d < max_connect_use (%d) -> fail...\n", count_users(), max_connect_user); ++ WFIFOHEAD(fd, 3);
+ WFIFOW(i,0) = 0x6c; + WFIFOW(i,2) = 0; + WFIFOSET(i,3); +@@ -1539,7 +1896,7 @@ + if (RFIFOREST(fd) < 50) + return 0; + for(i = 0; i < fd_max; i++) { +- if (session[i] && (sd = session[i]->session_data)) { ++ if (session[i] && (sd = (struct char_session_data*)session[i]->session_data)) {
+ if (sd->account_id == RFIFOL(fd,2)) { + memcpy(sd->email, RFIFOP(fd,6), 40); + if (e_mail_check(sd->email) == 0) +@@ -1552,6 +1909,40 @@ + RFIFOSKIP(fd,50); + break; + ++ // login-server alive packet
++ case 0x2718:
++ if (RFIFOREST(fd) < 2)
++ return 0;
++ RFIFOSKIP(fd,2);
++ break;
++
++ // Receiving authentification from Freya-type login server (to avoid char->login->char)
++ case 0x2719:
++ if (RFIFOREST(fd) < 18)
++ return 0;
++ // to conserv a maximum of authentification, search if account is already authentified and replace it
++ // that will reduce multiple connection too
++ for(i = 0; i < AUTH_FIFO_SIZE; i++)
++ if (auth_fifo[i].account_id == RFIFOL(fd,2))
++ break;
++ // if not found, use next value
++ if (i == AUTH_FIFO_SIZE) {
++ if (auth_fifo_pos >= AUTH_FIFO_SIZE)
++ auth_fifo_pos = 0;
++ i = auth_fifo_pos;
++ auth_fifo_pos++;
++ }
++ auth_fifo[i].account_id = RFIFOL(fd,2);
++ auth_fifo[i].char_id = 0;
++ auth_fifo[i].login_id1 = RFIFOL(fd,6);
++ auth_fifo[i].login_id2 = RFIFOL(fd,10);
++ auth_fifo[i].delflag = 2; // 0: auth_fifo canceled/void, 2: auth_fifo received from login/map server in memory, 1: connection authentified
++ auth_fifo[i].char_pos = 0;
++ auth_fifo[i].connect_until_time = 0; // unlimited/unknown time by default (not display in map-server)
++ auth_fifo[i].ip = RFIFOL(fd,14);
++ RFIFOSKIP(fd,18);
++ break;
++
+ case 0x2721: // gm reply + if (RFIFOREST(fd) < 10) + return 0; +@@ -1576,49 +1967,55 @@ + sex = RFIFOB(fd,6); + RFIFOSKIP(fd, 7); + if (acc > 0) { +- for (i = 0; i < char_num; i++) { +- if (char_dat[i].account_id == acc) { +- int jobclass = char_dat[i].class; +- char_dat[i].sex = sex; ++ for(i = 0; i < AUTH_FIFO_SIZE; i++) {
++ if (auth_fifo[i].account_id == acc)
+ auth_fifo[i].sex = sex; ++ }
++ for (i = 0; i < char_num; i++) {
++ if (char_dat[i].status.account_id == acc) {
++ int jobclass = char_dat[i].status.class_;
++ char_dat[i].status.sex = sex;
+ if (jobclass == 19 || jobclass == 20 || + jobclass == 4020 || jobclass == 4021 || + jobclass == 4042 || jobclass == 4043) { + // job modification + if (jobclass == 19 || jobclass == 20) { +- char_dat[i].class = (sex) ? 19 : 20; ++ char_dat[i].status.class_ = (sex) ? 19 : 20;
+ } else if (jobclass == 4020 || jobclass == 4021) { +- char_dat[i].class = (sex) ? 4020 : 4021; ++ char_dat[i].status.class_ = (sex) ? 4020 : 4021;
+ } else if (jobclass == 4042 || jobclass == 4043) { +- char_dat[i].class = (sex) ? 4042 : 4043; ++ char_dat[i].status.class_ = (sex) ? 4042 : 4043;
+ } + // remove specifical skills of classes 19, 4020 and 4042 + for(j = 315; j <= 322; j++) { +- if (char_dat[i].skill[j].id > 0 && !char_dat[i].skill[j].flag) { +- char_dat[i].skill_point += char_dat[i].skill[j].lv; +- char_dat[i].skill[j].id = 0; +- char_dat[i].skill[j].lv = 0; ++ if (char_dat[i].status.skill[j].id > 0 && !char_dat[i].status.skill[j].flag) {
++ char_dat[i].status.skill_point += char_dat[i].status.skill[j].lv;
++ char_dat[i].status.skill[j].id = 0;
++ char_dat[i].status.skill[j].lv = 0;
+ } + } + // remove specifical skills of classes 20, 4021 and 4043 + for(j = 323; j <= 330; j++) { +- if (char_dat[i].skill[j].id > 0 && !char_dat[i].skill[j].flag) { +- char_dat[i].skill_point += char_dat[i].skill[j].lv; +- char_dat[i].skill[j].id = 0; +- char_dat[i].skill[j].lv = 0; ++ if (char_dat[i].status.skill[j].id > 0 && !char_dat[i].status.skill[j].flag) {
++ char_dat[i].status.skill_point += char_dat[i].status.skill[j].lv;
++ char_dat[i].status.skill[j].id = 0;
++ char_dat[i].status.skill[j].lv = 0;
+ } + } + } + // to avoid any problem with equipment and invalid sex, equipment is unequiped. + for (j = 0; j < MAX_INVENTORY; j++) { +- if (char_dat[i].inventory[j].nameid && char_dat[i].inventory[j].equip) +- char_dat[i].inventory[j].equip = 0; ++ if (char_dat[i].status.inventory[j].nameid && char_dat[i].status.inventory[j].equip)
++ char_dat[i].status.inventory[j].equip = 0;
+ } +- char_dat[i].weapon = 0; +- char_dat[i].shield = 0; +- char_dat[i].head_top = 0; +- char_dat[i].head_mid = 0; +- char_dat[i].head_bottom = 0; ++ char_dat[i].status.weapon = 0;
++ char_dat[i].status.shield = 0;
++ char_dat[i].status.head_top = 0;
++ char_dat[i].status.head_mid = 0;
++ char_dat[i].status.head_bottom = 0;
++
++ if (char_dat[i].status.guild_id) //If there is a guild, update the guild_member data [Skotlex]
++ inter_guild_sex_changed(char_dat[i].status.guild_id, acc, char_dat[i].status.char_id, sex);
+ } + } + // disconnect player if online on char-server +@@ -1644,14 +2041,14 @@ + if (i == MAX_MAP_SERVERS) + char_log("'ladmin': Receiving a message for broadcast, but no map-server is online." RETCODE); + else { +- char buf[128]; +- char message[RFIFOL(fd,4) + 1]; // +1 to add a null terminated if not exist in the packet ++ unsigned char buf[128];
++ char message[4096]; // +1 to add a null terminated if not exist in the packet
+ int lp; + char *p; + memset(message, '\0', sizeof(message)); + memcpy(message, RFIFOP(fd,8), RFIFOL(fd,4)); + message[sizeof(message)-1] = '\0'; +- remove_control_chars(message); ++ remove_control_chars((unsigned char *)message);
+ // remove all first spaces + p = message; + while(p[0] == ' ') +@@ -1702,22 +2099,13 @@ + case 0x2729: + if (RFIFOREST(fd) < 4 || RFIFOREST(fd) < RFIFOW(fd,2)) + return 0; +- { +- struct global_reg reg[ACCOUNT_REG2_NUM]; +- unsigned char buf[4096]; +- int j, p, acc; +- acc = RFIFOL(fd,4); +- for (p = 8, j = 0; p < RFIFOW(fd,2) && j < ACCOUNT_REG2_NUM; p += 36, j++) { +- memcpy(reg[j].str, RFIFOP(fd,p), 32); +- reg[j].value = RFIFOL(fd,p+32); +- } +- set_account_reg2(acc, j, reg); +- // 同垢ログインを禁止していれば送る必要は無い ++ { //Receive account_reg2 registry, forward to map servers.
++ unsigned char buf[ACCOUNT_REG2_NUM*(256+32+2)+16];
+ memcpy(buf, RFIFOP(fd,0), RFIFOW(fd,2)); +- WBUFW(buf,0) = 0x2b11; ++// WBUFW(buf,0) = 0x2b11;
++ WBUFW(buf,0) = 0x3804; //Map server can now receive all kinds of reg values with the same packet. [Skotlex]
+ mapif_sendall(buf, WBUFW(buf,2)); + RFIFOSKIP(fd,RFIFOW(fd,2)); +-// printf("char: save_account_reg_reply\n"); + } + break; + +@@ -1727,20 +2115,20 @@ + return 0; + // Deletion of all characters of the account + for(i = 0; i < char_num; i++) { +- if (char_dat[i].account_id == RFIFOL(fd,2)) { +- char_delete(&char_dat[i]); ++ if (char_dat[i].status.account_id == RFIFOL(fd,2)) {
++ char_delete(&char_dat[i].status);
+ if (i < char_num - 1) { +- memcpy(&char_dat[i], &char_dat[char_num-1], sizeof(struct mmo_charstatus)); ++ memcpy(&char_dat[i], &char_dat[char_num-1], sizeof(struct character_data));
+ // if moved character owns to deleted account, check again it's character +- if (char_dat[i].account_id == RFIFOL(fd,2)) { ++ if (char_dat[i].status.account_id == RFIFOL(fd,2)) {
+ i--; + // Correct moved character reference in the character's owner by [Yor] + } else { + int j, k; + struct char_session_data *sd2; + for (j = 0; j < fd_max; j++) { +- if (session[j] && (sd2 = session[j]->session_data) && +- sd2->account_id == char_dat[char_num-1].account_id) { ++ if (session[j] && (sd2 = (struct char_session_data*)session[j]->session_data) &&
++ sd2->account_id == char_dat[char_num-1].status.account_id) {
+ for (k = 0; k < 9; k++) { + if (sd2->found_char[k] == char_num-1) { + sd2->found_char[k] = i; +@@ -1792,10 +2180,10 @@ + if (RFIFOREST(fd) < 4 || RFIFOREST(fd) < RFIFOW(fd,2)) + return 0; + { +- char buf[32000]; ++ unsigned char buf[32000];
+ if (gm_account != NULL) +- free(gm_account); +- gm_account = calloc(sizeof(struct gm_account) * ((RFIFOW(fd,2) - 4) / 5), 1); ++ aFree(gm_account);
++ gm_account = (struct gm_account*)aCalloc(sizeof(struct gm_account) * ((RFIFOW(fd,2) - 4) / 5), 1);
+ GM_num = 0; + for (i = 4; i < RFIFOW(fd,2); i = i + 5) { + gm_account[GM_num].account_id = RFIFOL(fd,i); +@@ -1803,8 +2191,8 @@ + //printf("GM account: %d -> level %d\n", gm_account[GM_num].account_id, gm_account[GM_num].level); + GM_num++; + } +- printf("From login-server: receiving of %d GM accounts information.\n", GM_num); +- char_log("From login-server: receiving of %d GM accounts information." RETCODE, GM_num); ++ ShowStatus("From login-server: receiving information of %d GM accounts.\n", GM_num);
++ char_log("From login-server: receiving information of %d GM accounts." RETCODE, GM_num);
+ create_online_files(); // update online players files (perhaps some online players change of GM level) + // send new gm acccounts level to map-servers + memcpy(buf, RFIFOP(fd,0), RFIFOW(fd,2)); +@@ -1814,7 +2202,101 @@ + RFIFOSKIP(fd,RFIFOW(fd,2)); + break; + ++ // Receive GM accounts [Freya login server packet by Yor]
++ case 0x2733:
++ // add test here to remember that the login-server is Freya-type
++ // sprintf (login_server_type, "Freya");
++ if (RFIFOREST(fd) < 7)
++ return 0;
++ {
++ unsigned char buf[32000];
++ int new_level = 0;
++ for(i = 0; i < GM_num; i++)
++ if (gm_account[i].account_id == RFIFOL(fd,2)) {
++ if (gm_account[i].level != (int)RFIFOB(fd,6)) {
++ gm_account[i].level = (int)RFIFOB(fd,6);
++ new_level = 1;
++ }
++ break;
++ }
++ // if not found, add it
++ if (i == GM_num) {
++ // limited to 4000, because we send information to char-servers (more than 4000 GM accounts???)
++ // int (id) + int (level) = 8 bytes * 4000 = 32k (limit of packets in windows)
++ if (((int)RFIFOB(fd,6)) > 0 && GM_num < 4000) {
++ if (GM_num == 0) {
++ gm_account = (struct gm_account*)aMalloc(sizeof(struct gm_account));
++ } else {
++ gm_account = (struct gm_account*)aRealloc(gm_account, sizeof(struct gm_account) * (GM_num + 1));
++ }
++ gm_account[GM_num].account_id = RFIFOL(fd,2);
++ gm_account[GM_num].level = (int)RFIFOB(fd,6);
++ new_level = 1;
++ GM_num++;
++ if (GM_num >= 4000) {
++ ShowWarning("4000 GM accounts found. Next GM accounts are not readed.\n");
++ char_log("***WARNING: 4000 GM accounts found. Next GM accounts are not readed." RETCODE);
++ }
++ }
++ }
++ if (new_level == 1) {
++ int len;
++ ShowStatus("From login-server: receiving GM account information (%d: level %d).\n", RFIFOL(fd,2), (int)RFIFOB(fd,6));
++ char_log("From login-server: receiving a GM account information (%d: level %d)." RETCODE, RFIFOL(fd,2), (int)RFIFOB(fd,6));
++ //create_online_files(); // not change online file for only 1 player (in next timer, that will be done
++ // send gm acccounts level to map-servers
++ len = 4;
++ WBUFW(buf,0) = 0x2b15;
++
++ for(i = 0; i < GM_num; i++) {
++ WBUFL(buf, len) = gm_account[i].account_id;
++ WBUFB(buf, len+4) = (unsigned char)gm_account[i].level;
++ len += 5;
++ }
++ WBUFW(buf, 2) = len;
++ mapif_sendall(buf, len);
++ }
++ }
++ RFIFOSKIP(fd,7);
++ break;
++
++ //Login server request to kick a character out. [Skotlex]
++ case 0x2734:
++ if (RFIFOREST(fd) < 6)
++ return 0;
++ {
++ struct online_char_data* character;
++ int aid = RFIFOL(fd,2);
++ if ((character = idb_get(online_char_db, aid)) != NULL)
++ { //Kick out this player.
++ if (character->server > -1)
++ { //Kick it from the map server it is on.
++ mapif_disconnectplayer(server_fd[character->server], character->account_id, character->char_id, 2);
++ if (!character->waiting_disconnect)
++ add_timer(gettick()+15000, chardb_waiting_disconnect, character->account_id, 0);
++ character->waiting_disconnect = 1;
++ } else { //Manual kick from char server.
++ struct char_session_data *tsd;
++ int i;
++ for(i = 0; i < fd_max; i++) {
++ if (session[i] && (tsd = (struct char_session_data*)session[i]->session_data) && tsd->account_id == aid)
++ {
++ WFIFOHEAD(fd, 3);
++ WFIFOW(i,0) = 0x81;
++ WFIFOB(i,2) = 2;
++ WFIFOSET(i,3);
++ break;
++ }
++ }
++ if (i == fd_max) //Shouldn't happen, but just in case.
++ set_char_offline(99, aid);
++ }
++ }
++ RFIFOSKIP(fd,6);
++ }
++ break;
+ default: ++ ShowWarning("Unknown packet 0x%04x received from login-server, disconnecting.\n", RFIFOW(fd,0));
+ session[fd]->eof = 1; + return 0; + } +@@ -1824,57 +2306,130 @@ + return 0; + } + +-//-------------------------------- +-// Map-server anti-freeze system +-//-------------------------------- +-int map_anti_freeze_system(int tid, unsigned int tick, int id, int data) { +- int i; ++int request_accreg2(int account_id, int char_id) {
++ if (login_fd > 0) {
++ WFIFOW(login_fd, 0) = 0x272e;
++ WFIFOL(login_fd, 2) = account_id;
++ WFIFOL(login_fd, 6) = char_id;
++ WFIFOSET(login_fd, 10);
++ return 1;
++ }
++ return 0;
++}
+ +- //printf("Entering in map_anti_freeze_system function to check freeze of servers.\n"); +- for(i = 0; i < MAX_MAP_SERVERS; i++) { +- if (server_fd[i] >= 0) {// if map-server is online +- //printf("map_anti_freeze_system: server #%d, flag: %d.\n", i, server_freezeflag[i]); +- if (server_freezeflag[i]-- < 1) { // Map-server anti-freeze system. Counter. 5 ok, 4...0 freezed +- printf("Map-server anti-freeze system: char-server #%d is freezed -> disconnection.\n", i); +- char_log("Map-server anti-freeze system: char-server #%d is freezed -> disconnection." RETCODE, +- i); +- session[server_fd[i]]->eof = 1; ++//Send packet forward to login-server for account saving
++int save_accreg2(unsigned char* buf, int len) {
++ if (login_fd > 0) {
++ WFIFOHEAD(login_fd, len+4);
++ memcpy(WFIFOP(login_fd,4), buf, len);
++ WFIFOW(login_fd,0) = 0x2728;
++ WFIFOW(login_fd,2) = len+4;
++ WFIFOSET(login_fd,len+4);
++ return 1;
+ } ++ return 0;
+ } ++
++//Receive Registry information for a character.
++int char_parse_Registry(int account_id, int char_id, unsigned char *buf, int buf_len) {
++ int i,j,p,len;
++ for (i = 0; i < char_num; i++) {
++ if (char_dat[i].status.account_id == account_id && char_dat[i].status.char_id == char_id)
++ break;
++ }
++ if(i >= char_num) //Character not found?
++ return 1;
++ for(j=0,p=0;j<GLOBAL_REG_NUM && p<buf_len;j++){
++ sscanf(WBUFP(buf,p), "%31c%n",char_dat[i].global[j].str,&len);
++ char_dat[i].global[j].str[len]='\0';
++ p +=len+1; //+1 to skip the '\0' between strings.
++ sscanf(WBUFP(buf,p), "%255c%n",char_dat[i].global[j].value,&len);
++ char_dat[i].global[j].value[len]='\0';
++ p +=len+1;
++ }
++ char_dat[i].global_num = j;
++ return 0;
+ } + ++//Reply to map server with acc reg values.
++int char_account_reg_reply(int fd,int account_id,int char_id) {
++ int i,j,p;
++ WFIFOHEAD(login_fd, GLOBAL_REG_NUM*288 + 13);
++ WFIFOW(fd,0)=0x3804;
++ WFIFOL(fd,4)=account_id;
++ WFIFOL(fd,8)=char_id;
++ WFIFOB(fd,12)=3; //Type 3: char acc reg.
++ for (i = 0;i < char_num; i++) {
++ if (char_dat[i].status.account_id == account_id && char_dat[i].status.char_id == char_id)
++ break;
++ }
++ if(i >= char_num){ //Character not found? Sent empty packet.
++ WFIFOW(fd,2)=13;
++ }else{
++ for (p=13,j = 0; j < char_dat[i].global_num; j++) {
++ if (char_dat[i].global[j].str[0]) {
++ p+= sprintf(WFIFOP(fd,p), "%s", char_dat[i].global[j].str)+1; //We add 1 to consider the '\0' in place.
++ p+= sprintf(WFIFOP(fd,p), "%s", char_dat[i].global[j].value)+1;
++ }
++ }
++ WFIFOW(fd,2)=p;
++ }
++ WFIFOSET(fd,WFIFOW(fd,2));
+ return 0; + } + ++int search_mapserver(unsigned short map, long ip, short port);
++
+ int parse_frommap(int fd) { + int i, j; + int id; ++ RFIFOHEAD(fd);
+ + for(id = 0; id < MAX_MAP_SERVERS; id++) + if (server_fd[id] == fd) + break; +- if (id == MAX_MAP_SERVERS || session[fd]->eof) { ++ if(id==MAX_MAP_SERVERS)
++ session[fd]->eof=1;
++ if(session[fd]->eof){
+ if (id < MAX_MAP_SERVERS) { +- printf("Map-server %d (session #%d) has disconnected.\n", id, fd); +- memset(&server[id], 0, sizeof(struct mmo_map_server)); ++ unsigned char buf[16384];
++ ShowStatus("Map-server %d has disconnected.\n", id);
++ //Notify other map servers that this one is gone. [Skotlex]
++ WBUFW(buf,0) = 0x2b20;
++ WBUFL(buf,4) = server[id].ip;
++ WBUFW(buf,8) = server[id].port;
++ j = 0;
++ for(i = 0; i < MAX_MAP_PER_SERVER; i++)
++ if (server[id].map[i])
++ WBUFW(buf,10+(j++)*4) = server[id].map[i];
++ if (j > 0) {
++ WBUFW(buf,2) = j * 4 + 10;
++ mapif_sendallwos(fd, buf, WBUFW(buf,2));
++ }
+ server_fd[id] = -1; +- for(j = 0; j < char_num; j++) +- if (online_chars[j] == fd) +- online_chars[j] = -1; +- create_online_files(); // update online players files (to remove all online players of this server) ++ online_char_db->foreach(online_char_db,char_db_setoffline,i); //Tag relevant chars as 'in disconnected' server.
+ } +- close(fd); +- delete_session(fd); ++ do_close(fd);
++ create_online_files();
+ return 0; + } + +- while(RFIFOREST(fd) >= 2) { ++ while(RFIFOREST(fd) >= 2 && !session[fd]->eof) {
+ // printf("parse_frommap: connection #%d, packet: 0x%x (with being read: %d bytes).\n", fd, RFIFOW(fd,0), RFIFOREST(fd)); + + switch(RFIFOW(fd,0)) { ++
++ // map-server alive packet
++ case 0x2718:
++ if (RFIFOREST(fd) < 2)
++ return 0;
++ RFIFOSKIP(fd,2);
++ break;
++
+ // request from map-server to reload GM accounts. Transmission to login-server (by Yor) + case 0x2af7: + if (login_fd > 0) { // don't send request if no login-server ++ WFIFOHEAD(login_fd, 2);
+ WFIFOW(login_fd,0) = 0x2709; + WFIFOSET(login_fd, 2); + // printf("char : request from map-server to reload GM accounts -> login-server.\n"); +@@ -1888,36 +2443,41 @@ + return 0; + memset(server[id].map, 0, sizeof(server[id].map)); + j = 0; +- for(i = 4; i < RFIFOW(fd,2); i += 16) { +- memcpy(server[id].map[j], RFIFOP(fd,i), 16); +-// printf("set map %d.%d : %s\n", id, j, server[id].map[j]); ++ for(i = 4; i < RFIFOW(fd,2); i += 4) {
++ server[id].map[j] = RFIFOW(fd,i);
+ j++; + } + { + unsigned char *p = (unsigned char *)&server[id].ip; +- printf("Map-Server %d connected: %d maps, from IP %d.%d.%d.%d port %d.\n", ++ ShowStatus("Map-Server %d connected: %d maps, from IP %d.%d.%d.%d port %d.\n",
+ id, j, p[0], p[1], p[2], p[3], server[id].port); +- printf("Map-server %d loading complete.\n", id); ++ ShowStatus("Map-server %d loading complete.\n", id);
+ char_log("Map-Server %d connected: %d maps, from IP %d.%d.%d.%d port %d. Map-server %d loading complete." RETCODE, + id, j, p[0], p[1], p[2], p[3], server[id].port, id); ++ if (kick_on_disconnect)
++ set_all_offline();
++ if (max_account_id != DEFAULT_MAX_ACCOUNT_ID || max_char_id != DEFAULT_MAX_CHAR_ID)
++ mapif_send_maxid(max_account_id, max_char_id); //Send the current max ids to the server to keep in sync [Skotlex]
+ } ++ WFIFOHEAD(fd, 3 + NAME_LENGTH);
+ WFIFOW(fd,0) = 0x2afb; + WFIFOB(fd,2) = 0; +- memcpy(WFIFOP(fd,3), wisp_server_name, 24); // name for wisp to player +- WFIFOSET(fd,27); ++ memcpy(WFIFOP(fd,3), wisp_server_name, NAME_LENGTH); // name for wisp to player
++ WFIFOSET(fd,3+NAME_LENGTH);
++ //WFIFOSET(fd,27);
+ { + unsigned char buf[16384]; + int x; + if (j == 0) { +- printf("WARNING: Map-Server %d have NO map.\n", id); ++ ShowWarning("Map-Server %d have NO map.\n", id);
+ char_log("WARNING: Map-Server %d have NO map." RETCODE, id); + // Transmitting maps information to the other map-servers + } else { + WBUFW(buf,0) = 0x2b04; +- WBUFW(buf,2) = j * 16 + 10; ++ WBUFW(buf,2) = j * 4 + 10;
+ WBUFL(buf,4) = server[id].ip; + WBUFW(buf,8) = server[id].port; +- memcpy(WBUFP(buf,10), RFIFOP(fd,4), j * 16); ++ memcpy(WBUFP(buf,10), RFIFOP(fd,4), j * 4);
+ mapif_sendallwos(fd, buf, WBUFW(buf,2)); + } + // Transmitting the maps of the other map-servers to the new map-server +@@ -1928,10 +2488,10 @@ + WFIFOW(fd,8) = server[x].port; + j = 0; + for(i = 0; i < MAX_MAP_PER_SERVER; i++) +- if (server[x].map[i][0]) +- memcpy(WFIFOP(fd,10+(j++)*16), server[x].map[i], 16); ++ if (server[x].map[i])
++ WFIFOW(fd,10+(j++)*4) = server[x].map[i];
+ if (j > 0) { +- WFIFOW(fd,2) = j * 16 + 10; ++ WFIFOW(fd,2) = j * 4 + 10;
+ WFIFOSET(fd,WFIFOW(fd,2)); + } + } +@@ -1940,83 +2500,95 @@ + RFIFOSKIP(fd,RFIFOW(fd,2)); + break; + +- // 認証要求 ++ //Packet command is now used for sc_data request. [Skotlex]
+ case 0x2afc: +- if (RFIFOREST(fd) < 22) ++ if (RFIFOREST(fd) < 10)
+ return 0; +- //printf("auth_fifo search: account: %d, char: %d, secure: %08x-%08x\n", RFIFOL(fd,2), RFIFOL(fd,6), RFIFOL(fd,10), RFIFOL(fd,14)); +- for(i = 0; i < AUTH_FIFO_SIZE; i++) { +- if (auth_fifo[i].account_id == RFIFOL(fd,2) && +- auth_fifo[i].char_id == RFIFOL(fd,6) && +- auth_fifo[i].login_id1 == RFIFOL(fd,10) && +-#if CMP_AUTHFIFO_LOGIN2 != 0 +- // here, it's the only area where it's possible that we doesn't know login_id2 (map-server asks just after 0x72 packet, that doesn't given the value) +- (auth_fifo[i].login_id2 == RFIFOL(fd,14) || RFIFOL(fd,14) == 0) && // relate to the versions higher than 18 ++ {
++#ifdef ENABLE_SC_SAVING
++ int aid, cid;
++ struct scdata *data;
++ aid = RFIFOL(fd,2);
++ cid = RFIFOL(fd,6);
+ #endif +- (!check_ip_flag || auth_fifo[i].ip == RFIFOL(fd,18)) && +- !auth_fifo[i].delflag) { +- auth_fifo[i].delflag = 1; +- WFIFOW(fd,0) = 0x2afd; +- WFIFOW(fd,2) = 16 + sizeof(struct mmo_charstatus); +- WFIFOL(fd,4) = RFIFOL(fd,2); +- WFIFOL(fd,8) = auth_fifo[i].login_id2; +- WFIFOL(fd,12) = (unsigned long)auth_fifo[i].connect_until_time; +- char_dat[auth_fifo[i].char_pos].sex = auth_fifo[i].sex; +- memcpy(WFIFOP(fd,16), &char_dat[auth_fifo[i].char_pos], sizeof(struct mmo_charstatus)); ++ RFIFOSKIP(fd, 10);
++#ifdef ENABLE_SC_SAVING
++ data = status_search_scdata(aid, cid);
++ if (data->count > 0)
++ { //Deliver status change data.
++ int i;
++
++ WFIFOW(fd,0) = 0x2b1d;
++ WFIFOW(fd,2) = 14 + data->count*sizeof(struct status_change_data);
++ WFIFOL(fd,4) = aid;
++ WFIFOL(fd,8) = cid;
++ WFIFOW(fd,12) = data->count;
++ for (i = 0; i < data->count; i++)
++ memcpy(WFIFOP(fd,14+i*sizeof(struct status_change_data)), &data->data[i], sizeof(struct status_change_data));
+ WFIFOSET(fd, WFIFOW(fd,2)); +- //printf("auth_fifo search success (auth #%d, account %d, character: %d).\n", i, RFIFOL(fd,2), RFIFOL(fd,6)); +- break; ++ status_delete_scdata(aid, cid); //Data sent, so it needs be discarded now.
+ } ++#endif
++ break;
+ } +- if (i == AUTH_FIFO_SIZE) { +- WFIFOW(fd,0) = 0x2afe; +- WFIFOL(fd,2) = RFIFOL(fd,2); +- WFIFOSET(fd,6); +- printf("auth_fifo search error! account %d not authentified.\n", RFIFOL(fd,2)); ++
++ //set MAP user count
++ case 0x2afe:
++ if (RFIFOREST(fd) < 4)
++ return 0;
++ if (RFIFOW(fd,2) != server[id].users) {
++ server[id].users = RFIFOW(fd,2);
++ ShowInfo("User Count: %d (Server: %d)\n", server[id].users, id);
+ } +- RFIFOSKIP(fd,22); ++ RFIFOSKIP(fd, 4);
+ break; +- +- // MAPサーバー上のユーザー数受信 ++ //set MAP users
+ case 0x2aff: + if (RFIFOREST(fd) < 6 || RFIFOREST(fd) < RFIFOW(fd,2)) + return 0; + server[id].users = RFIFOW(fd,4); +- if(anti_freeze_enable) +- server_freezeflag[id] = 5; // Map anti-freeze system. Counter. 5 ok, 4...0 freezed +- // remove all previously online players of the server +- for(i = 0; i < char_num; i++) +- if (online_chars[i] == id) +- online_chars[i] = -1; +- // add online players in the list by [Yor] ++ // add online players in the list by [Yor], adapted to use dbs by [Skotlex]
++ j = 0;
++ online_char_db->foreach(online_char_db,char_db_setoffline,id); //Set all chars from this server as 'unknown'
+ for(i = 0; i < server[id].users; i++) { +- int char_id = RFIFOL(fd,6+i*4); +- for(j = 0; j < char_num; j++) +- if (char_dat[j].char_id == char_id) { +- online_chars[j] = id; +- //printf("%d\n", char_id); +- break; ++ int aid, cid;
++ struct online_char_data* character;
++ aid = RFIFOL(fd,6+i*8);
++ cid = RFIFOL(fd,6+i*8+4);
++ character = idb_ensure(online_char_db, aid, create_online_char_data);
++ if (online_check && character->server > -1 && character->server != id)
++ {
++ ShowNotice("Set map user: Character (%d:%d) marked on map server %d, but map server %d claims to have (%d:%d) online!\n",
++ character->account_id, character->char_id, character->server, id, aid, cid);
++ mapif_disconnectplayer(server_fd[character->server], character->account_id, character->char_id, 2);
+ } ++ character->char_id = cid;
++ character->server = id;
+ } + if (update_online < time(NULL)) { // Time is done + update_online = time(NULL) + 8; + create_online_files(); // only every 8 sec. (normally, 1 server send users every 5 sec.) Don't update every time, because that takes time, but only every 2 connection. + // it set to 8 sec because is more than 5 (sec) and if we have more than 1 map-server, informations can be received in shifted. + } +- RFIFOSKIP(fd,6+i*4); ++ //If any chars remain in -2, they will be cleaned in the cleanup timer.
++ RFIFOSKIP(fd,6+i*8);
+ break; + + // キャラデータ保存 ++ // Recieve character data from map-server
+ case 0x2b01: + if (RFIFOREST(fd) < 4 || RFIFOREST(fd) < RFIFOW(fd,2)) + return 0; + for(i = 0; i < char_num; i++) { +- if (char_dat[i].account_id == RFIFOL(fd,4) && +- char_dat[i].char_id == RFIFOL(fd,8)) ++ if (char_dat[i].status.account_id == RFIFOL(fd,4) &&
++ char_dat[i].status.char_id == RFIFOL(fd,8))
+ break; + } + if (i != char_num) +- memcpy(&char_dat[i], RFIFOP(fd,12), sizeof(struct mmo_charstatus)); ++ memcpy(&char_dat[i].status, RFIFOP(fd,13), sizeof(struct mmo_charstatus));
++ if (RFIFOB(fd,12)) { //Flag, set character offline. [Skotlex]
++ set_char_offline(RFIFOL(fd,8),RFIFOL(fd,4));
++ }
+ RFIFOSKIP(fd,RFIFOW(fd,2)); + break; + +@@ -2026,7 +2598,6 @@ + return 0; + if (auth_fifo_pos >= AUTH_FIFO_SIZE) + auth_fifo_pos = 0; +- //printf("auth_fifo set (auth #%d) - account: %d, secure: %08x-%08x\n", auth_fifo_pos, RFIFOL(fd,2), RFIFOL(fd,6), RFIFOL(fd,10)); + auth_fifo[auth_fifo_pos].account_id = RFIFOL(fd,2); + auth_fifo[auth_fifo_pos].char_id = 0; + auth_fifo[auth_fifo_pos].login_id1 = RFIFOL(fd,6); +@@ -2043,35 +2614,58 @@ + RFIFOSKIP(fd,18); + break; + +- // マップサーバー間移動要求 ++ // request "change map server"
+ case 0x2b05: +- if (RFIFOREST(fd) < 49) ++ if (RFIFOREST(fd) < 35)
+ return 0; +- if (auth_fifo_pos >= AUTH_FIFO_SIZE) +- auth_fifo_pos = 0; +- WFIFOW(fd,0) = 0x2b06; +- memcpy(WFIFOP(fd,2), RFIFOP(fd,2), 42); +- //printf("auth_fifo set (auth#%d) - account: %d, secure: 0x%08x-0x%08x\n", auth_fifo_pos, RFIFOL(fd,2), RFIFOL(fd,6), RFIFOL(fd,10)); +- auth_fifo[auth_fifo_pos].account_id = RFIFOL(fd,2); +- auth_fifo[auth_fifo_pos].char_id = RFIFOL(fd,14); +- auth_fifo[auth_fifo_pos].login_id1 = RFIFOL(fd,6); +- auth_fifo[auth_fifo_pos].login_id2 = RFIFOL(fd,10); +- auth_fifo[auth_fifo_pos].delflag = 0; +- auth_fifo[auth_fifo_pos].sex = RFIFOB(fd,44); +- auth_fifo[auth_fifo_pos].connect_until_time = 0; // unlimited/unknown time by default (not display in map-server) +- auth_fifo[auth_fifo_pos].ip = RFIFOL(fd,45); +- for(i = 0; i < char_num; i++) +- if (char_dat[i].account_id == RFIFOL(fd,2) && +- char_dat[i].char_id == RFIFOL(fd,14)) { +- auth_fifo[auth_fifo_pos].char_pos = i; +- auth_fifo_pos++; +- WFIFOL(fd,6) = 0; ++ {
++ unsigned short name;
++ int map_id, map_fd = -1, i;
++ struct online_char_data* data;
++ struct mmo_charstatus* char_data;
++
++ name = RFIFOW(fd,18);
++ map_id = search_mapserver(name, RFIFOL(fd,24), RFIFOW(fd,28)); //Locate mapserver by ip and port.
++ if (map_id >= 0)
++ map_fd = server_fd[map_id];
++ for(i = 0; i < char_num; i++) {
++ if (char_dat[i].status.account_id == RFIFOL(fd,2) &&
++ char_dat[i].status.char_id == RFIFOL(fd,14))
+ break; + } +- if (i == char_num) +- WFIFOW(fd,6) = 1; +- WFIFOSET(fd,44); +- RFIFOSKIP(fd,49); ++ char_data = i< char_num? &char_dat[i].status:NULL;
++ //Tell the new map server about this player using Kevin's new auth packet. [Skotlex]
++ if (map_fd>=0 && session[map_fd] && char_data)
++ { //Send the map server the auth of this player.
++ //Update the "last map" as this is where the player must be spawned on the new map server.
++ char_data->last_point.map = RFIFOW(fd,18);
++ char_data->last_point.x = RFIFOW(fd,20);
++ char_data->last_point.y = RFIFOW(fd,22);
++
++ WFIFOW(map_fd,0) = 0x2afd;
++ WFIFOW(map_fd,2) = 20 + sizeof(struct mmo_charstatus);
++ WFIFOL(map_fd,4) = RFIFOL(fd, 2); //Account ID
++ WFIFOL(map_fd,8) = RFIFOL(fd, 6); //Login1
++ WFIFOL(map_fd,16) = RFIFOL(fd,10); //Login2
++ WFIFOL(map_fd,12) = (unsigned long)0; //TODO: connect_until_time, how do I figure it out right now?
++ memcpy(WFIFOP(map_fd,20), char_data, sizeof(struct mmo_charstatus));
++ WFIFOSET(map_fd, WFIFOW(map_fd,2));
++ data = idb_ensure(online_char_db, RFIFOL(fd, 2), create_online_char_data);
++ data->char_id = char_data->char_id;
++ data->server = map_id; //Update server where char is.
++
++ //Reply with an ack.
++ WFIFOW(fd, 0) = 0x2b06;
++ memcpy(WFIFOP(fd,2), RFIFOP(fd,2), 28);
++ WFIFOSET(fd, 30);
++ } else { //Reply with nak
++ WFIFOW(fd, 0) = 0x2b06;
++ memcpy(WFIFOP(fd,2), RFIFOP(fd,2), 28);
++ WFIFOL(fd, 6) = 0; //Set login1 to 0.
++ WFIFOSET(fd, 30);
++ }
++ RFIFOSKIP(fd, 35);
++ }
+ break; + + // キャラ名検索 +@@ -2079,16 +2673,17 @@ + if (RFIFOREST(fd) < 6) + return 0; + for(i = 0; i < char_num; i++) { +- if (char_dat[i].char_id == RFIFOL(fd,2)) ++ if (char_dat[i].status.char_id == RFIFOL(fd,2))
+ break; + } + WFIFOW(fd,0) = 0x2b09; + WFIFOL(fd,2) = RFIFOL(fd,2); + if (i != char_num) +- memcpy(WFIFOP(fd,6), char_dat[i].name, 24); ++ memcpy(WFIFOP(fd,6), char_dat[i].status.name, NAME_LENGTH);
+ else +- memcpy(WFIFOP(fd,6), unknown_char_name, 24); +- WFIFOSET(fd,30); ++ memcpy(WFIFOP(fd,6), unknown_char_name, NAME_LENGTH);
++ WFIFOSET(fd,6+NAME_LENGTH);
++ //WFIFOSET(fd,30);
+ RFIFOSKIP(fd,6); + break; + +@@ -2127,10 +2722,10 @@ + if (RFIFOREST(fd) < 44) + return 0; + { +- char character_name[24]; ++ char character_name[NAME_LENGTH];
+ int acc = RFIFOL(fd,2); // account_id of who ask (-1 if nobody) +- memcpy(character_name, RFIFOP(fd,6), 24); +- character_name[sizeof(character_name) -1] = '\0'; ++ memcpy(character_name, RFIFOP(fd,6), NAME_LENGTH-1);
++ character_name[NAME_LENGTH -1] = '\0';
+ // prepare answer + WFIFOW(fd,0) = 0x2b0f; // answer + WFIFOL(fd,2) = acc; // who want do operation +@@ -2138,14 +2733,15 @@ + // search character + i = search_character_index(character_name); + if (i >= 0) { +- memcpy(WFIFOP(fd,6), search_character_name(i), 24); // put correct name if found +- WFIFOW(fd,32) = 0; // answer: 0-login-server resquest done, 1-player not found, 2-gm level too low, 3-login-server offline ++ memcpy(WFIFOP(fd,6), search_character_name(i), NAME_LENGTH); // put correct name if found
++ WFIFOW(fd,6+NAME_LENGTH) = 0; // answer: 0-login-server resquest done, 1-player not found, 2-gm level too low, 3-login-server offline
++ //WFIFOW(fd,32) = 0; // answer: 0-login-server resquest done, 1-player not found, 2-gm level too low, 3-login-server offline
+ switch(RFIFOW(fd, 30)) { + case 1: // block +- if (acc == -1 || isGM(acc) >= isGM(char_dat[i].account_id)) { ++ if (acc == -1 || isGM(acc) >= isGM(char_dat[i].status.account_id)) {
+ if (login_fd > 0) { // don't send request if no login-server + WFIFOW(login_fd,0) = 0x2724; +- WFIFOL(login_fd,2) = char_dat[i].account_id; // account value ++ WFIFOL(login_fd,2) = char_dat[i].status.account_id; // account value
+ WFIFOL(login_fd,6) = 5; // status of the account + WFIFOSET(login_fd, 10); + // printf("char : status -> login: account %d, status: %d \n", char_dat[i].account_id, 5); +@@ -2155,10 +2751,10 @@ + WFIFOW(fd,32) = 2; // answer: 0-login-server resquest done, 1-player not found, 2-gm level too low, 3-login-server offline + break; + case 2: // ban +- if (acc == -1 || isGM(acc) >= isGM(char_dat[i].account_id)) { ++ if (acc == -1 || isGM(acc) >= isGM(char_dat[i].status.account_id)) {
+ if (login_fd > 0) { // don't send request if no login-server + WFIFOW(login_fd, 0) = 0x2725; +- WFIFOL(login_fd, 2) = char_dat[i].account_id; // account value ++ WFIFOL(login_fd, 2) = char_dat[i].status.account_id; // account value
+ WFIFOW(login_fd, 6) = RFIFOW(fd,32); // year + WFIFOW(login_fd, 8) = RFIFOW(fd,34); // month + WFIFOW(login_fd,10) = RFIFOW(fd,36); // day +@@ -2174,10 +2770,10 @@ + WFIFOW(fd,32) = 2; // answer: 0-login-server resquest done, 1-player not found, 2-gm level too low, 3-login-server offline + break; + case 3: // unblock +- if (acc == -1 || isGM(acc) >= isGM(char_dat[i].account_id)) { ++ if (acc == -1 || isGM(acc) >= isGM(char_dat[i].status.account_id)) {
+ if (login_fd > 0) { // don't send request if no login-server + WFIFOW(login_fd,0) = 0x2724; +- WFIFOL(login_fd,2) = char_dat[i].account_id; // account value ++ WFIFOL(login_fd,2) = char_dat[i].status.account_id; // account value
+ WFIFOL(login_fd,6) = 0; // status of the account + WFIFOSET(login_fd, 10); + // printf("char : status -> login: account %d, status: %d \n", char_dat[i].account_id, 0); +@@ -2187,10 +2783,10 @@ + WFIFOW(fd,32) = 2; // answer: 0-login-server resquest done, 1-player not found, 2-gm level too low, 3-login-server offline + break; + case 4: // unban +- if (acc == -1 || isGM(acc) >= isGM(char_dat[i].account_id)) { ++ if (acc == -1 || isGM(acc) >= isGM(char_dat[i].status.account_id)) {
+ if (login_fd > 0) { // don't send request if no login-server + WFIFOW(login_fd, 0) = 0x272a; +- WFIFOL(login_fd, 2) = char_dat[i].account_id; // account value ++ WFIFOL(login_fd, 2) = char_dat[i].status.account_id; // account value
+ WFIFOSET(login_fd, 6); + // printf("char : status -> login: account %d, unban request\n", char_dat[i].account_id); + } else +@@ -2199,10 +2795,10 @@ + WFIFOW(fd,32) = 2; // answer: 0-login-server resquest done, 1-player not found, 2-gm level too low, 3-login-server offline + break; + case 5: // changesex +- if (acc == -1 || isGM(acc) >= isGM(char_dat[i].account_id)) { ++ if (acc == -1 || isGM(acc) >= isGM(char_dat[i].status.account_id)) {
+ if (login_fd > 0) { // don't send request if no login-server + WFIFOW(login_fd, 0) = 0x2727; +- WFIFOL(login_fd, 2) = char_dat[i].account_id; // account value ++ WFIFOL(login_fd, 2) = char_dat[i].status.account_id; // account value
+ WFIFOSET(login_fd, 6); + // printf("char : status -> login: account %d, change sex request\n", char_dat[i].account_id); + } else +@@ -2213,12 +2809,14 @@ + } + } else { + // character name not found +- memcpy(WFIFOP(fd,6), character_name, 24); +- WFIFOW(fd,32) = 1; // answer: 0-login-server resquest done, 1-player not found, 2-gm level too low, 3-login-server offline ++ memcpy(WFIFOP(fd,6), character_name, NAME_LENGTH);
++ WFIFOW(fd,8+NAME_LENGTH) = 1; // answer: 0-login-server resquest done, 1-player not found, 2-gm level too low, 3-login-server offline
++ //WFIFOW(fd,32) = 1; // answer: 0-login-server resquest done, 1-player not found, 2-gm level too low, 3-login-server offline
+ } + // send answer if a player ask, not if the server ask + if (acc != -1) { +- WFIFOSET(fd, 34); ++ //WFIFOSET(fd, 34);
++ WFIFOSET(fd, 10+NAME_LENGTH);
+ } + RFIFOSKIP(fd, 44); + break; +@@ -2226,34 +2824,157 @@ + + // case 0x2b0f: not more used (available for futur usage) + +- // account_reg保存要求 +- case 0x2b10: +- if (RFIFOREST(fd) < 4 || RFIFOREST(fd) < RFIFOW(fd,2)) ++ //Packet 0x2b10 deprecated in favor of packet 0x3004 for registry saving. [Skotlex]
++ //case 0x2b10:
++
++ // Recieve rates [Wizputer]
++ case 0x2b16:
++ if (RFIFOREST(fd) < 6 || RFIFOREST(fd) < RFIFOW(fd,8))
++ return 0;
++ // Txt doesn't need this packet, so just skip it
++ RFIFOSKIP(fd,RFIFOW(fd,8));
++ break;
++
++ // Character disconnected set online 0 [Wizputer]
++ case 0x2b17:
++ if (RFIFOREST(fd) < 6)
++ return 0;
++ //printf("Setting %d char offline\n",RFIFOL(fd,2));
++ set_char_offline(RFIFOL(fd,2),RFIFOL(fd,6));
++ RFIFOSKIP(fd,10);
++ break;
++
++ // Reset all chars to offline [Wizputer]
++ case 0x2b18:
++ ShowNotice("Map server [%d] requested to set all characters offline.\n", id);
++ set_all_offline();
++ RFIFOSKIP(fd,2);
++ break;
++
++ // Character set online [Wizputer]
++ case 0x2b19:
++ if (RFIFOREST(fd) < 6)
++ return 0;
++ //printf("Setting %d char online\n",RFIFOL(fd,2));
++ set_char_online(id, RFIFOL(fd,2),RFIFOL(fd,6));
++ RFIFOSKIP(fd,10);
++ break;
++
++ // Request sending of fame list
++ case 0x2b1a:
++ if (RFIFOREST(fd) < 2)
+ return 0; + { +- struct global_reg reg[ACCOUNT_REG2_NUM]; +- int p, acc; +- acc = RFIFOL(fd,4); +- for(p = 8, j = 0; p < RFIFOW(fd,2) && j < ACCOUNT_REG2_NUM; p += 36, j++) { +- memcpy(reg[j].str, RFIFOP(fd,p), 32); +- reg[j].value = RFIFOL(fd, p+32); ++ int i, j, k, len = 8;
++ unsigned char buf[32000];
++ struct fame_list fame_item;
++ //struct mmo_charstatus *dat;
++ //dat = (struct mmo_charstatus *)aCalloc(char_num, sizeof(struct mmo_charstatus *));
++ CREATE_BUFFER(id, int, char_num);
++
++ // copy character list into buffer
++ //for (i = 0; i < char_num; i++)
++ // dat[i] = char_dat[i];
++ // sort according to fame
++ // qsort(dat, char_num, sizeof(struct mmo_charstatus *), sort_fame);
++
++ for(i = 0; i < char_num; i++) {
++ id[i] = i;
++ for(j = 0; j < i; j++) {
++ if (char_dat[i].status.fame > char_dat[id[j]].status.fame) {
++ for(k = i; k > j; k--)
++ id[k] = id[k-1];
++ id[j] = i; // id[i]
++ break;
+ } +- set_account_reg2(acc, j, reg); +- // loginサーバーへ送る +- if (login_fd > 0) { // don't send request if no login-server +- WFIFOW(login_fd, 0) = 0x2728; +- memcpy(WFIFOP(login_fd,0), RFIFOP(fd,0), RFIFOW(fd,2)); +- WFIFOSET(login_fd, WFIFOW(login_fd,2)); + } +- // ワールドへの同垢ログインがなければmapサーバーに送る必要はない +- //memcpy(buf, RFIFOP(fd,0), RFIFOW(fd,2)); +- //WBUFW(buf,0) = 0x2b11; +- //mapif_sendall(buf, WBUFW(buf,2)); ++ }
++
++ // starting to send to map
++ WBUFW(buf,0) = 0x2b1b;
++ // add list for blacksmiths
++ for (i = 0, j = 0; i < char_num && j < fame_list_size_smith; i++) {
++ if (char_dat[id[i]].status.fame && (char_dat[id[i]].status.class_ == 10 ||
++ char_dat[id[i]].status.class_ == 4011 ||
++ char_dat[id[i]].status.class_ == 4033))
++ {
++ fame_item.id = char_dat[id[i]].status.char_id;
++ fame_item.fame = char_dat[id[i]].status.fame;
++ strncpy(fame_item.name, char_dat[id[i]].status.name, NAME_LENGTH);
++
++ memcpy(WBUFP(buf, len), &fame_item, sizeof(struct fame_list));
++ len += sizeof(struct fame_list);
++ j++;
++ }
++ }
++ // add blacksmith's block length
++ WBUFW(buf, 6) = len;
++
++ // add list for alchemists
++ for (i = 0, j = 0; i < char_num && j < fame_list_size_chemist; i++) {
++ if (char_dat[id[i]].status.fame && (char_dat[id[i]].status.class_ == 18 ||
++ char_dat[id[i]].status.class_ == 4019 ||
++ char_dat[id[i]].status.class_ == 4041))
++ {
++ fame_item.id = char_dat[id[i]].status.char_id;
++ fame_item.fame = char_dat[id[i]].status.fame;
++ strncpy(fame_item.name, char_dat[id[i]].status.name, NAME_LENGTH);
++
++ memcpy(WBUFP(buf, len), &fame_item, sizeof(struct fame_list));
++ len += sizeof(struct fame_list);
++ j++;
++ }
++ }
++ // add alchemist's block length
++ WBUFW(buf, 4) = len;
++
++ // adding list for taekwons
++ for (i = 0, j = 0; i < char_num && j < fame_list_size_taekwon; i++) {
++ if (char_dat[id[i]].status.fame && char_dat[id[i]].status.class_ == 4046)
++ {
++ fame_item.id = char_dat[id[i]].status.char_id;
++ fame_item.fame = char_dat[id[i]].status.fame;
++ strncpy(fame_item.name, char_dat[id[i]].status.name, NAME_LENGTH);
++
++ memcpy(WBUFP(buf, len), &fame_item, sizeof(struct fame_list));
++ len += sizeof(struct fame_list);
++ j++;
++ }
++ }
++ // add total packet length
++ WBUFW(buf, 2) = len;
++
++ // sending to all maps
++ mapif_sendall(buf, len);
++ // done!
++ //aFree(dat);
++ DELETE_BUFFER(id);
++ RFIFOSKIP(fd,2);
++ break;
++ }
++ //Request to save status change data. [Skotlex]
++ case 0x2b1c:
++ if (RFIFOREST(fd) < 4 || RFIFOREST(fd) < RFIFOW(fd,2))
++ return 0;
++ {
++#ifdef ENABLE_SC_SAVING
++ int count, aid, cid, i;
++ struct scdata *data;
++ aid = RFIFOL(fd, 4);
++ cid = RFIFOL(fd, 8);
++ count = RFIFOW(fd, 12);
++ data = status_search_scdata(aid, cid);
++ if (data->count != count)
++ {
++ data->count = count;
++ data->data = aRealloc(data->data, count*sizeof(struct status_change_data));
++ }
++ for (i = 0; i < count; i++)
++ memcpy (&data->data[i], RFIFOP(fd, 14+i*sizeof(struct status_change_data)), sizeof(struct status_change_data));
++#endif
+ RFIFOSKIP(fd, RFIFOW(fd,2)); +-// printf("char: save_account_reg (from map)\n"); + break; + } +- + default: + // inter server処理に渡す + { +@@ -2264,7 +2985,7 @@ + return 0; + } + // inter server処理でもない場合は切断 +- printf("char: unknown packet 0x%04x (%d bytes to read in buffer)! (from map).\n", RFIFOW(fd,0), RFIFOREST(fd)); ++ ShowError("Unknown packet 0x%04x from map server, disconnecting.\n", RFIFOW(fd,0));
+ session[fd]->eof = 1; + return 0; + } +@@ -2272,28 +2993,20 @@ + return 0; + } + +-int search_mapserver(char *map) { ++int search_mapserver(unsigned short map, long ip, short port) {
+ int i, j; +- char temp_map[16]; +- int temp_map_len; +- +-// printf("Searching the map-server for map '%s'... ", map); +- strncpy(temp_map, map, sizeof(temp_map)); +- temp_map[sizeof(temp_map)-1] = '\0'; +- if (strchr(temp_map, '.') != NULL) +- temp_map[strchr(temp_map, '.') - temp_map + 1] = '\0'; // suppress the '.gat', but conserve the '.' to be sure of the name of the map + +- temp_map_len = strlen(temp_map); + for(i = 0; i < MAX_MAP_SERVERS; i++) + if (server_fd[i] >= 0) +- for (j = 0; server[i].map[j][0]; j++) +- //printf("%s : %s = %d\n", server[i].map[j], map, strncmp(server[i].map[j], temp_map, temp_map_len)); +- if (strncmp(server[i].map[j], temp_map, temp_map_len) == 0) { +-// printf("found -> server #%d.\n", i); ++ for (j = 0; server[i].map[j]; j++)
++ if (server[i].map[j] == map) {
++ if (ip > 0 && server[i].ip != ip)
++ continue;
++ if (port > 0 && server[i].port != port)
++ continue;
+ return i; + } + +-// printf("not found.\n"); + return -1; + } + +@@ -2302,48 +3015,81 @@ + return inter_mapif_init(fd); + } + +-//----------------------------------------------------- +-// Test to know if an IP come from LAN or WAN. by [Yor] +-//----------------------------------------------------- +-int lan_ip_check(unsigned char *p){ ++//--------------------------------------------
++// Test to know if an IP come from LAN or WAN.
++// Rewrote: Adnvanced subnet check [LuzZza]
++//--------------------------------------------
++int lan_subnetcheck(long *p) {
++
+ int i; +- int lancheck = 1; ++ unsigned char *sbn, *msk, *src = (unsigned char *)p;
+ +-// printf("lan_ip_check: to compare: %d.%d.%d.%d, network: %d.%d.%d.%d/%d.%d.%d.%d\n", +-// p[0], p[1], p[2], p[3], +-// subneti[0], subneti[1], subneti[2], subneti[3], +-// subnetmaski[0], subnetmaski[1], subnetmaski[2], subnetmaski[3]); +- for(i = 0; i < 4; i++) { +- if ((subneti[i] & subnetmaski[i]) != (p[i] & subnetmaski[i])) { +- lancheck = 0; +- break; ++ for(i=0; i<subnet_count; i++) {
++
++ if((subnet[i].subnet & subnet[i].mask) == (*p & subnet[i].mask)) {
++
++ sbn = (unsigned char *)&subnet[i].subnet;
++ msk = (unsigned char *)&subnet[i].mask;
++
++ ShowInfo("Subnet check [%u.%u.%u.%u]: Matches "CL_CYAN"%u.%u.%u.%u/%u.%u.%u.%u"CL_RESET"\n",
++ src[0], src[1], src[2], src[3], sbn[0], sbn[1], sbn[2], sbn[3], msk[0], msk[1], msk[2], msk[3]);
++
++ return subnet[i].map_ip;
+ } + } +- printf("LAN test (result): %s source\033[0m.\n", (lancheck) ? "\033[1;36mLAN" : "\033[1;32mWAN"); +- return lancheck; ++
++ ShowInfo("Subnet check [%u.%u.%u.%u]: "CL_CYAN"WAN"CL_RESET"\n", src[0], src[1], src[2], src[3]);
++ return 0;
+ } + + int parse_char(int fd) { ++
+ int i, ch; ++ unsigned short cmd;
+ char email[40]; ++ int map_fd;
+ struct char_session_data *sd; + unsigned char *p = (unsigned char *) &session[fd]->client_addr.sin_addr; ++ long subnet_map_ip;
++
++ RFIFOHEAD(fd);
++
++ sd = (struct char_session_data*)session[fd]->session_data;
+ +- if (login_fd < 0 || session[fd]->eof) { // disconnect any player (already connected to char-server or coming back from map-server) if login-server is diconnected. ++ if (login_fd < 0)
++ session[fd]->eof = 1;
++ if(session[fd]->eof) { // disconnect any player (already connected to char-server or coming back from map-server) if login-server is diconnected.
+ if (fd == login_fd) + login_fd = -1; +- close(fd); +- delete_session(fd); ++ if (sd != NULL)
++ {
++ struct online_char_data* data = idb_get(online_char_db, sd->account_id);
++ if (!data || data->server== -1) //If it is not in any server, send it offline. [Skotlex]
++ set_char_offline(99,sd->account_id);
++ }
++ do_close(fd);
+ return 0; + } + +- sd = session[fd]->session_data; ++ while(RFIFOREST(fd) >= 2 && !session[fd]->eof) {
++ cmd = RFIFOW(fd,0);
++ // crc32のスキップ用
++ if( sd==NULL && // 未ログインor管理パケット
++ RFIFOREST(fd)>=4 && // 最低バイト数制限 & 0x7530,0x7532管理パケ除去
++ RFIFOREST(fd)<=21 && // 最大バイト数制限 & サーバーログイン除去
++ cmd!=0x20b && // md5通知パケット除去
++ (RFIFOREST(fd)<6 || RFIFOW(fd,4)==0x65) ){ // 次に何かパケットが来てるなら、接続でないとだめ
++ RFIFOSKIP(fd,4);
++ cmd = RFIFOW(fd,0);
++ ShowDebug("parse_char : %d crc32 skipped\n",fd);
++ if(RFIFOREST(fd)==0)
++ return 0;
++ }
+ +- while (RFIFOREST(fd) >= 2) { +-// if (RFIFOW(fd,0) < 30000) +-// printf("parse_char: connection #%d, packet: 0x%x (with being read: %d bytes).\n", fd, RFIFOW(fd,0), RFIFOREST(fd)); ++//For use in packets that depend on an sd being present [Skotlex]
++#define FIFOSD_CHECK(rest) { if(RFIFOREST(fd) < rest) return 0; if (sd==NULL) { RFIFOSKIP(fd,rest); return 0; } }
+ +- switch(RFIFOW(fd,0)) { ++ switch(cmd){
+ case 0x20b: //20040622暗号化ragexe対応 + if (RFIFOREST(fd) < 19) + return 0; +@@ -2356,13 +3102,15 @@ + { + int GM_value; + if ((GM_value = isGM(RFIFOL(fd,2)))) +- printf("Account Logged On; Account ID: %d (GM level %d).\n", RFIFOL(fd,2), GM_value); ++ ShowInfo("Account Logged On; Account ID: %d (GM level %d).\n", RFIFOL(fd,2), GM_value);
+ else +- printf("Account Logged On; Account ID: %d.\n", RFIFOL(fd,2)); ++ ShowInfo("Account Logged On; Account ID: %d.\n", RFIFOL(fd,2));
+ if (sd == NULL) { +- sd = session[fd]->session_data = calloc(sizeof(struct char_session_data), 1); +- memset(sd, 0, sizeof(struct char_session_data)); +- memcpy(sd->email, "no mail", 40); // put here a mail without '@' to refuse deletion if we don't receive the e-mail ++ sd = (struct char_session_data*)aCalloc(sizeof(struct char_session_data), 1);
++ session[fd]->session_data = sd;
++
++// memset(sd, 0, sizeof(struct char_session_data)); aCalloc does this [Skotlex]
++ strncpy(sd->email, "no mail", 40); // put here a mail without '@' to refuse deletion if we don't receive the e-mail
+ sd->connect_until_time = 0; // unknow or illimited (not displaying on map-server) + } + sd->account_id = RFIFOL(fd,2); +@@ -2370,6 +3118,7 @@ + sd->login_id2 = RFIFOL(fd,10); + sd->sex = RFIFOB(fd,16); + // send back account_id ++ WFIFOHEAD(fd, 4);
+ WFIFOL(fd,0) = RFIFOL(fd,2); + WFIFOSET(fd,4); + // search authentification +@@ -2382,6 +3131,44 @@ + (!check_ip_flag || auth_fifo[i].ip == session[fd]->client_addr.sin_addr.s_addr) && + auth_fifo[i].delflag == 2) { + auth_fifo[i].delflag = 1; ++
++ if (online_check)
++ { // check if character is not online already. [Skotlex]
++ struct online_char_data* character;
++ character = idb_get(online_char_db, sd->account_id);
++
++ if (character)
++ {
++ if(character->server > -1)
++ { //Kick it from the map server it is on.
++ mapif_disconnectplayer(server_fd[character->server], character->account_id, character->char_id, 2);
++ if (!character->waiting_disconnect)
++ add_timer(gettick()+20000, chardb_waiting_disconnect, character->account_id, 0);
++ character->waiting_disconnect = 1;
++ /* Not a good idea because this would trigger when you do a char-change from the map server! [Skotlex]
++ } else { //Manual kick from char server.
++ struct char_session_data *tsd;
++ int i;
++ for(i = 0; i < fd_max; i++) {
++ if (session[i] && i!=fd && (tsd = (struct char_session_data*)session[i]->session_data) && tsd->account_id == sd->account_id)
++ {
++ WFIFOW(i,0) = 0x81;
++ WFIFOB(i,2) = 2;
++ WFIFOSET(i,3);
++ break;
++ }
++ }
++ if (i == fd_max) //Shouldn't happen, but just in case.
++ set_char_offline(99, sd->account_id);
++ */
++ WFIFOW(fd,0) = 0x81;
++ WFIFOB(fd,2) = 8;
++ WFIFOSET(fd,3);
++ break;
++ }
++ }
++ }
++
+ if (max_connect_user == 0 || count_users() < max_connect_user) { + if (login_fd > 0) { // don't send request if no login-server + // request to login-server to obtain e-mail/time limit +@@ -2421,89 +3208,103 @@ + break; + + case 0x66: // キャラ選択 +- if (RFIFOREST(fd) < 3) +- return 0; ++ FIFOSD_CHECK(3);
++ {
++ int char_num = RFIFOB(fd,2);
++ struct mmo_charstatus *cd;
++ RFIFOSKIP(fd,3);
+ + // if we activated email creation and email is default email + if (email_creation != 0 && strcmp(sd->email, "a@a.com") == 0 && login_fd > 0) { // to modify an e-mail, login-server must be online ++ WFIFOHEAD(fd, 3);
+ WFIFOW(fd, 0) = 0x70; + WFIFOB(fd, 2) = 0; // 00 = Incorrect Email address + WFIFOSET(fd, 3); +- ++ break;
++ }
+ // otherwise, load the character +- } else { + for (ch = 0; ch < 9; ch++) +- if (sd->found_char[ch] >= 0 && char_dat[sd->found_char[ch]].char_num == RFIFOB(fd,2)) ++ if (sd->found_char[ch] >= 0 && char_dat[sd->found_char[ch]].status.char_num == char_num)
+ break; +- if (ch != 9) { ++ if (ch == 9)
++ { //Not found?? May be forged packet.
++ break;
++ }
++ cd = &char_dat[sd->found_char[ch]].status;
+ char_log("Character Selected, Account ID: %d, Character Slot: %d, Character Name: %s." RETCODE, +- sd->account_id, RFIFOB(fd,2), char_dat[sd->found_char[ch]].name); ++ sd->account_id, char_num, cd->name);
++
++ cd->sex = sd->sex;
++
+ // searching map server +- i = search_mapserver(char_dat[sd->found_char[ch]].last_point.map); ++ i = search_mapserver(cd->last_point.map,-1,-1);
+ // if map is not found, we check major cities + if (i < 0) { +- if ((i = search_mapserver("prontera.gat")) >= 0) { // check is done without 'gat'. +- memcpy(char_dat[sd->found_char[ch]].last_point.map, "prontera.gat", 16); +- char_dat[sd->found_char[ch]].last_point.x = 273; // savepoint coordonates +- char_dat[sd->found_char[ch]].last_point.y = 354; +- } else if ((i = search_mapserver("geffen.gat")) >= 0) { // check is done without 'gat'. +- memcpy(char_dat[sd->found_char[ch]].last_point.map, "geffen.gat", 16); +- char_dat[sd->found_char[ch]].last_point.x = 120; // savepoint coordonates +- char_dat[sd->found_char[ch]].last_point.y = 100; +- } else if ((i = search_mapserver("morocc.gat")) >= 0) { // check is done without 'gat'. +- memcpy(char_dat[sd->found_char[ch]].last_point.map, "morocc.gat", 16); +- char_dat[sd->found_char[ch]].last_point.x = 160; // savepoint coordonates +- char_dat[sd->found_char[ch]].last_point.y = 94; +- } else if ((i = search_mapserver("alberta.gat")) >= 0) { // check is done without 'gat'. +- memcpy(char_dat[sd->found_char[ch]].last_point.map, "alberta.gat", 16); +- char_dat[sd->found_char[ch]].last_point.x = 116; // savepoint coordonates +- char_dat[sd->found_char[ch]].last_point.y = 57; +- } else if ((i = search_mapserver("payon.gat")) >= 0) { // check is done without 'gat'. +- memcpy(char_dat[sd->found_char[ch]].last_point.map, "payon.gat", 16); +- char_dat[sd->found_char[ch]].last_point.x = 87; // savepoint coordonates +- char_dat[sd->found_char[ch]].last_point.y = 117; +- } else if ((i = search_mapserver("izlude.gat")) >= 0) { // check is done without 'gat'. +- memcpy(char_dat[sd->found_char[ch]].last_point.map, "izlude.gat", 16); +- char_dat[sd->found_char[ch]].last_point.x = 94; // savepoint coordonates +- char_dat[sd->found_char[ch]].last_point.y = 103; ++ unsigned short j;
++ ShowWarning("Unable to find map-server for '%s', resorting to sending to a major city.\n", mapindex_id2name(cd->last_point.map));
++ if ((i = search_mapserver((j=mapindex_name2id(MAP_PRONTERA)),-1,-1)) >= 0) {
++ cd->last_point.map = j;
++ cd->last_point.x = 273; // savepoint coordinates
++ cd->last_point.y = 354;
++ } else if ((i = search_mapserver((j=mapindex_name2id(MAP_GEFFEN)),-1,-1)) >= 0) {
++ cd->last_point.map = j;
++ cd->last_point.x = 120; // savepoint coordinates
++ cd->last_point.y = 100;
++ } else if ((i = search_mapserver((j=mapindex_name2id(MAP_MORROC)),-1,-1)) >= 0) {
++ cd->last_point.map = j;
++ cd->last_point.x = 160; // savepoint coordinates
++ cd->last_point.y = 94;
++ } else if ((i = search_mapserver((j=mapindex_name2id(MAP_ALBERTA)),-1,-1)) >= 0) {
++ cd->last_point.map = j;
++ cd->last_point.x = 116; // savepoint coordinates
++ cd->last_point.y = 57;
++ } else if ((i = search_mapserver((j=mapindex_name2id(MAP_PAYON)),-1,-1)) >= 0) {
++ cd->last_point.map = j;
++ cd->last_point.x = 87; // savepoint coordinates
++ cd->last_point.y = 117;
++ } else if ((i = search_mapserver((j=mapindex_name2id(MAP_IZLUDE)),-1,-1)) >= 0) {
++ cd->last_point.map = j;
++ cd->last_point.x = 94; // savepoint coordinates
++ cd->last_point.y = 103;
+ } else { +- int j; + // get first online server (with a map) + i = 0; + for(j = 0; j < MAX_MAP_SERVERS; j++) +- if (server_fd[j] >= 0 && server[j].map[0][0]) { // change save point to one of map found on the server (the first) ++ if (server_fd[j] >= 0 && server[j].map[0]) { // change save point to one of map found on the server (the first)
+ i = j; +- memcpy(char_dat[sd->found_char[ch]].last_point.map, server[j].map[0], 16); +- printf("Map-server #%d found with a map: '%s'.\n", j, server[j].map[0]); +- // coordonates are unknown ++ cd->last_point.map = server[j].map[0];
++ ShowInfo("Map-server #%d found with a map: '%s'.\n", j, mapindex_id2name(server[j].map[0]));
++ // coordinates are unknown
+ break; + } + // if no map-server is connected, we send: server closed + if (j == MAX_MAP_SERVERS) { ++ WFIFOHEAD(fd, 3);
+ WFIFOW(fd,0) = 0x81; +- WFIFOL(fd,2) = 1; // 01 = Server closed ++ WFIFOB(fd,2) = 1; // 01 = Server closed
+ WFIFOSET(fd,3); +- RFIFOSKIP(fd,3); + break; + } + } + } ++ WFIFOHEAD(fd, 28);
+ WFIFOW(fd,0) = 0x71; +- WFIFOL(fd,2) = char_dat[sd->found_char[ch]].char_id; +- memcpy(WFIFOP(fd,6), char_dat[sd->found_char[ch]].last_point.map, 16); +- printf("Character selection '%s' (account: %d, slot: %d).\n", char_dat[sd->found_char[ch]].name, sd->account_id, ch); +- printf("--Send IP of map-server. "); +- if (lan_ip_check(p)) +- WFIFOL(fd, 22) = inet_addr(lan_map_ip); ++ WFIFOL(fd,2) = cd->char_id;
++ memcpy(WFIFOP(fd,6), mapindex_id2name(cd->last_point.map), MAP_NAME_LENGTH);
++ ShowInfo("Character selection '%s' (account: %d, slot: %d).\n", cd->name, sd->account_id, ch);
++
++ // Andvanced subnet check [LuzZza]
++ if((subnet_map_ip = lan_subnetcheck((long *)p)))
++ WFIFOL(fd,22) = subnet_map_ip;
+ else + WFIFOL(fd, 22) = server[i].ip; ++
+ WFIFOW(fd,26) = server[i].port; + WFIFOSET(fd,28); + if (auth_fifo_pos >= AUTH_FIFO_SIZE) + auth_fifo_pos = 0; +- //printf("auth_fifo set #%d - account %d, char: %d, secure: %08x-%08x\n", auth_fifo_pos, sd->account_id, char_dat[sd->found_char[ch]].char_id, sd->login_id1, sd->login_id2); + auth_fifo[auth_fifo_pos].account_id = sd->account_id; +- auth_fifo[auth_fifo_pos].char_id = char_dat[sd->found_char[ch]].char_id; ++ auth_fifo[auth_fifo_pos].char_id = cd->char_id;
+ auth_fifo[auth_fifo_pos].login_id1 = sd->login_id1; + auth_fifo[auth_fifo_pos].login_id2 = sd->login_id2; + auth_fifo[auth_fifo_pos].delflag = 0; +@@ -2511,62 +3312,109 @@ + auth_fifo[auth_fifo_pos].sex = sd->sex; + auth_fifo[auth_fifo_pos].connect_until_time = sd->connect_until_time; + auth_fifo[auth_fifo_pos].ip = session[fd]->client_addr.sin_addr.s_addr; +- auth_fifo_pos++; ++
++ //Send NEW auth packet [Kevin]
++ if ((map_fd = server_fd[i]) < 1 || session[map_fd] == NULL)
++ { //0 Should not be a valid server_fd [Skotlex]
++ ShowError("parse_char: Attempting to write to invalid session %d! Map Server #%d disconnected.\n", map_fd, i);
++ server_fd[i] = -1;
++ memset(&server[i], 0, sizeof(struct mmo_map_server));
++ //Send server closed.
++ WFIFOW(fd,0) = 0x81;
++ WFIFOB(fd,2) = 1; // 01 = Server closed
++ WFIFOSET(fd,3);
++ break;
+ } ++ WFIFOW(map_fd,0) = 0x2afd;
++ WFIFOW(map_fd,2) = 20 + sizeof(struct mmo_charstatus);
++ WFIFOL(map_fd,4) = auth_fifo[auth_fifo_pos].account_id;
++ WFIFOL(map_fd,8) = auth_fifo[auth_fifo_pos].login_id1;
++ WFIFOL(map_fd,16) = auth_fifo[auth_fifo_pos].login_id2;
++ WFIFOL(map_fd,12) = (unsigned long)auth_fifo[auth_fifo_pos].connect_until_time;
++ memcpy(WFIFOP(map_fd,20), cd, sizeof(struct mmo_charstatus));
++ WFIFOSET(map_fd, WFIFOW(map_fd,2));
++
++ set_char_online(i, cd->char_id, cd->account_id);
++ //Sets char online in the party and breaks even share if needed.
++ inter_party_logged(cd->party_id, cd->account_id, cd->char_id);
++
++ auth_fifo_pos++;
+ } +- RFIFOSKIP(fd,3); + break; + + case 0x67: // 作成 +- if (RFIFOREST(fd) < 37) +- return 0; ++ FIFOSD_CHECK(37);
++
++ if(char_new == 0) //turn character creation on/off [Kevin]
++ i = -2;
++ else
+ i = make_new_char(fd, RFIFOP(fd,2)); +- if (i < 0) { ++
++ if(i == -1){ //added some better faile reporting to client on the txt version [Kevin]
++ //already exists
++ WFIFOHEAD(fd, 3);
+ WFIFOW(fd,0) = 0x6e; + WFIFOB(fd,2) = 0x00; + WFIFOSET(fd,3); + RFIFOSKIP(fd,37); + break; ++ }else if(i == -2){
++ //denied
++ WFIFOHEAD(fd, 3);
++ WFIFOW(fd, 0) = 0x6e;
++ WFIFOB(fd, 2) = 0x02;
++ WFIFOSET(fd, 3);
++ RFIFOSKIP(fd, 37);
++ break;
++ }else if(i == -3){
++ //underaged XD
++ WFIFOHEAD(fd, 3);
++ WFIFOW(fd, 0) = 0x6e;
++ WFIFOB(fd, 2) = 0x01;
++ WFIFOSET(fd, 3);
++ RFIFOSKIP(fd, 37);
++ break;
+ } + ++ WFIFOHEAD(fd, 108);
+ WFIFOW(fd,0) = 0x6d; + memset(WFIFOP(fd,2), 0, 106); + +- WFIFOL(fd,2) = char_dat[i].char_id; +- WFIFOL(fd,2+4) = char_dat[i].base_exp; +- WFIFOL(fd,2+8) = char_dat[i].zeny; +- WFIFOL(fd,2+12) = char_dat[i].job_exp; +- WFIFOL(fd,2+16) = char_dat[i].job_level; ++ WFIFOL(fd,2) = char_dat[i].status.char_id;
++ WFIFOL(fd,2+4) = char_dat[i].status.base_exp>LONG_MAX?LONG_MAX:char_dat[i].status.base_exp;
++ WFIFOL(fd,2+8) = char_dat[i].status.zeny;
++ WFIFOL(fd,2+12) = char_dat[i].status.job_exp>LONG_MAX?LONG_MAX:char_dat[i].status.job_exp;
++ WFIFOL(fd,2+16) = char_dat[i].status.job_level;
+ +- WFIFOL(fd,2+28) = char_dat[i].karma; +- WFIFOL(fd,2+32) = char_dat[i].manner; ++ WFIFOL(fd,2+28) = char_dat[i].status.karma;
++ WFIFOL(fd,2+32) = char_dat[i].status.manner;
+ + WFIFOW(fd,2+40) = 0x30; +- WFIFOW(fd,2+42) = (char_dat[i].hp > 0x7fff) ? 0x7fff : char_dat[i].hp; +- WFIFOW(fd,2+44) = (char_dat[i].max_hp > 0x7fff) ? 0x7fff : char_dat[i].max_hp; +- WFIFOW(fd,2+46) = (char_dat[i].sp > 0x7fff) ? 0x7fff : char_dat[i].sp; +- WFIFOW(fd,2+48) = (char_dat[i].max_sp > 0x7fff) ? 0x7fff : char_dat[i].max_sp; +- WFIFOW(fd,2+50) = DEFAULT_WALK_SPEED; // char_dat[i].speed; +- WFIFOW(fd,2+52) = char_dat[i].class; +- WFIFOW(fd,2+54) = char_dat[i].hair; +- +- WFIFOW(fd,2+58) = char_dat[i].base_level; +- WFIFOW(fd,2+60) = char_dat[i].skill_point; +- +- WFIFOW(fd,2+64) = char_dat[i].shield; +- WFIFOW(fd,2+66) = char_dat[i].head_top; +- WFIFOW(fd,2+68) = char_dat[i].head_mid; +- WFIFOW(fd,2+70) = char_dat[i].hair_color; +- +- memcpy(WFIFOP(fd,2+74), char_dat[i].name, 24); +- +- WFIFOB(fd,2+98) = (char_dat[i].str > 255) ? 255 : char_dat[i].str; +- WFIFOB(fd,2+99) = (char_dat[i].agi > 255) ? 255 : char_dat[i].agi; +- WFIFOB(fd,2+100) = (char_dat[i].vit > 255) ? 255 : char_dat[i].vit; +- WFIFOB(fd,2+101) = (char_dat[i].int_ > 255) ? 255 : char_dat[i].int_; +- WFIFOB(fd,2+102) = (char_dat[i].dex > 255) ? 255 : char_dat[i].dex; +- WFIFOB(fd,2+103) = (char_dat[i].luk > 255) ? 255 : char_dat[i].luk; +- WFIFOB(fd,2+104) = char_dat[i].char_num; ++ WFIFOW(fd,2+42) = (char_dat[i].status.hp > 0x7fff) ? 0x7fff : char_dat[i].status.hp;
++ WFIFOW(fd,2+44) = (char_dat[i].status.max_hp > 0x7fff) ? 0x7fff : char_dat[i].status.max_hp;
++ WFIFOW(fd,2+46) = (char_dat[i].status.sp > 0x7fff) ? 0x7fff : char_dat[i].status.sp;
++ WFIFOW(fd,2+48) = (char_dat[i].status.max_sp > 0x7fff) ? 0x7fff : char_dat[i].status.max_sp;
++ WFIFOW(fd,2+50) = DEFAULT_WALK_SPEED; // char_dat[i].status.speed;
++ WFIFOW(fd,2+52) = char_dat[i].status.class_;
++ WFIFOW(fd,2+54) = char_dat[i].status.hair;
++
++ WFIFOW(fd,2+58) = char_dat[i].status.base_level;
++ WFIFOW(fd,2+60) = char_dat[i].status.skill_point;
++
++ WFIFOW(fd,2+64) = char_dat[i].status.shield;
++ WFIFOW(fd,2+66) = char_dat[i].status.head_top;
++ WFIFOW(fd,2+68) = char_dat[i].status.head_mid;
++ WFIFOW(fd,2+70) = char_dat[i].status.hair_color;
++
++ memcpy(WFIFOP(fd,2+74), char_dat[i].status.name, NAME_LENGTH);
++
++ WFIFOB(fd,2+98) = (char_dat[i].status.str > 255) ? 255 : char_dat[i].status.str;
++ WFIFOB(fd,2+99) = (char_dat[i].status.agi > 255) ? 255 : char_dat[i].status.agi;
++ WFIFOB(fd,2+100) = (char_dat[i].status.vit > 255) ? 255 : char_dat[i].status.vit;
++ WFIFOB(fd,2+101) = (char_dat[i].status.int_ > 255) ? 255 : char_dat[i].status.int_;
++ WFIFOB(fd,2+102) = (char_dat[i].status.dex > 255) ? 255 : char_dat[i].status.dex;
++ WFIFOB(fd,2+103) = (char_dat[i].status.luk > 255) ? 255 : char_dat[i].status.luk;
++ WFIFOB(fd,2+104) = char_dat[i].status.char_num;
+ + WFIFOSET(fd,108); + RFIFOSKIP(fd,37); +@@ -2578,8 +3426,8 @@ + } + + case 0x68: // delete char //Yor's Fix +- if (RFIFOREST(fd) < 46) +- return 0; ++ FIFOSD_CHECK(46);
++
+ memcpy(email, RFIFOP(fd,6), 40); + if (e_mail_check(email) == 0) + strncpy(email, "a@a.com", 40); // default e-mail +@@ -2596,7 +3444,7 @@ + } else { + // we change the packet to set it like selection. + for (i = 0; i < 9; i++) +- if (char_dat[sd->found_char[i]].char_id == RFIFOL(fd,2)) { ++ if (char_dat[sd->found_char[i]].status.char_id == RFIFOL(fd,2)) {
+ // we save new e-mail + memcpy(sd->email, email, 40); + // we send new e-mail to login-server ('online' login-server is checked before) +@@ -2608,7 +3456,7 @@ + RFIFOSKIP(fd,43); + // change value to put new packet (char selection) + RFIFOW(fd, 0) = 0x66; +- RFIFOB(fd, 2) = char_dat[sd->found_char[i]].char_num; ++ RFIFOB(fd, 2) = char_dat[sd->found_char[i]].status.char_num;
+ // not send packet, it's modify of actual packet + break; + } +@@ -2630,7 +3478,7 @@ + } else { + for (i = 0; i < 9; i++) { + struct mmo_charstatus *cs = NULL; +- if ((cs = &char_dat[sd->found_char[i]])->char_id == RFIFOL(fd,2)) { ++ if ((cs = &char_dat[sd->found_char[i]].status)->char_id == RFIFOL(fd,2)) {
+ char_delete(cs); // deletion process + + if (sd->found_char[i] != char_num - 1) { +@@ -2640,8 +3488,8 @@ + int j, k; + struct char_session_data *sd2; + for (j = 0; j < fd_max; j++) { +- if (session[j] && (sd2 = session[j]->session_data) && +- sd2->account_id == char_dat[char_num-1].account_id) { ++ if (session[j] && (sd2 = (struct char_session_data*)session[j]->session_data) &&
++ sd2->account_id == char_dat[char_num-1].status.account_id) {
+ for (k = 0; k < 9; k++) { + if (sd2->found_char[k] == char_num-1) { + sd2->found_char[k] = sd->found_char[i]; +@@ -2682,7 +3530,7 @@ + if (server_fd[i] < 0) + break; + } +- if (i == MAX_MAP_SERVERS || strcmp(RFIFOP(fd,2), userid) || strcmp(RFIFOP(fd,26), passwd)){ ++ if (i == MAX_MAP_SERVERS || strcmp((char*)RFIFOP(fd,2), userid) || strcmp((char*)RFIFOP(fd,26), passwd)){
+ WFIFOB(fd,2) = 3; + WFIFOSET(fd,3); + RFIFOSKIP(fd,60); +@@ -2691,8 +3539,6 @@ + WFIFOB(fd,2) = 0; + session[fd]->func_parse = parse_frommap; + server_fd[i] = fd; +- if(anti_freeze_enable) +- server_freezeflag[i] = 5; // Map anti-freeze system. Counter. 5 ok, 4...0 freezed + server[i].ip = RFIFOL(fd,54); + server[i].port = RFIFOW(fd,58); + server[i].users = 0; +@@ -2735,9 +3581,6 @@ + return 0; + + case 0x7532: // 接続の切断(defaultと処理は一緒だが明示的にするため) +- session[fd]->eof = 1; +- return 0; +- + default: + session[fd]->eof = 1; + return 0; +@@ -2747,6 +3590,30 @@ + return 0; + } + ++// Console Command Parser [Wizputer]
++int parse_console(char *buf) {
++ char *type,*command;
++
++ type = (char *)aCalloc(64,1);
++ command = (char *)aCalloc(64,1);
++
++// memset(type,0,64);
++// memset(command,0,64);
++
++ ShowStatus("Console: %s\n",buf);
++
++ if ( sscanf(buf, "%[^:]:%[^\n]", type , command ) < 2 )
++ sscanf(buf,"%[^\n]",type);
++
++ ShowDebug("Type of command: %s || Command: %s \n",type,command);
++
++ if(buf) aFree(buf);
++ if(type) aFree(type);
++ if(command) aFree(command);
++
++ return 0;
++}
++
+ // 全てのMAPサーバーにデータ送信(送信したmap鯖の数を返す) + int mapif_sendall(unsigned char *buf, unsigned int len) { + int i, c; +@@ -2755,6 +3622,16 @@ + for(i = 0; i < MAX_MAP_SERVERS; i++) { + int fd; + if ((fd = server_fd[i]) >= 0) { ++ if (session[fd] == NULL)
++ { //Could this be the crash's source? [Skotlex]
++ ShowError("mapif_sendall: Attempting to write to invalid session %d! Map Server #%d disconnected.\n", fd, i);
++ server_fd[i] = -1;
++ memset(&server[i], 0, sizeof(struct mmo_map_server));
++ continue;
++ }
++ WFIFOHEAD(fd, len);
++ if (WFIFOSPACE(fd) < len) //Increase buffer size.
++ realloc_writefifo(fd, len);
+ memcpy(WFIFOP(fd,0), buf, len); + WFIFOSET(fd,len); + c++; +@@ -2771,6 +3648,9 @@ + for(i = 0; i < MAX_MAP_SERVERS; i++) { + int fd; + if ((fd = server_fd[i]) >= 0 && fd != sfd) { ++ WFIFOHEAD(fd, len);
++ if (WFIFOSPACE(fd) < len) //Increase buffer size.
++ realloc_writefifo(fd, len);
+ memcpy(WFIFOP(fd,0), buf, len); + WFIFOSET(fd, len); + c++; +@@ -2785,6 +3665,9 @@ + if (fd >= 0) { + for(i = 0; i < MAX_MAP_SERVERS; i++) { + if (fd == server_fd[i]) { ++ WFIFOHEAD(fd, len);
++ if (WFIFOSPACE(fd) < len) //Increase buffer size.
++ realloc_writefifo(fd, len);
+ memcpy(WFIFOP(fd,0), buf, len); + WFIFOSET(fd,len); + return 1; +@@ -2796,10 +3679,11 @@ + + int send_users_tologin(int tid, unsigned int tick, int id, int data) { + int users = count_users(); +- char buf[16]; ++ unsigned char buf[16];
+ + if (login_fd > 0 && session[login_fd]) { + // send number of user to login server ++ WFIFOHEAD(login_fd, 6);
+ WFIFOW(login_fd,0) = 0x2714; + WFIFOL(login_fd,2) = users; + WFIFOSET(login_fd,6); +@@ -2812,17 +3696,52 @@ + return 0; + } + ++static int send_accounts_tologin_sub(DBKey key, void* data, va_list ap) {
++ struct online_char_data* character = (struct online_char_data*)data;
++ int *i = va_arg(ap, int*);
++ int count = va_arg(ap, int);
++ if ((*i) >= count)
++ return 0; //This is an error that shouldn't happen....
++ if(character->server > -1) {
++ WFIFOHEAD(login_fd, 8+count*4);
++ WFIFOL(login_fd, 8+(*i)*4) =character->account_id;
++ (*i)++;
++ return 1;
++ }
++ return 0;
++}
++
++int send_accounts_tologin(int tid, unsigned int tick, int id, int data) {
++ int users = count_users(), i=0;
++
++ if (login_fd > 0 && session[login_fd]) {
++ // send account list to login server
++ WFIFOHEAD(login_fd, 8+users*4);
++ WFIFOW(login_fd,0) = 0x272d;
++ WFIFOL(login_fd,4) = users;
++ online_char_db->foreach(online_char_db, send_accounts_tologin_sub, &i);
++ WFIFOW(login_fd,2) = 8+ i*4;
++ if (i > 0)
++ WFIFOSET(login_fd,WFIFOW(login_fd,2));
++ }
++ return 0;
++}
++
+ int check_connect_login_server(int tid, unsigned int tick, int id, int data) { + if (login_fd <= 0 || session[login_fd] == NULL) { +- printf("Attempt to connect to login-server...\n"); ++ ShowInfo("Attempt to connect to login-server...\n");
+ login_fd = make_connection(login_ip, login_port); ++ if (login_fd == -1)
++ { //Try again later... [Skotlex]
++ login_fd = 0;
++ return 0;
++ }
+ session[login_fd]->func_parse = parse_tologin; + realloc_fifo(login_fd, FIFOSIZE_SERVERLINK, FIFOSIZE_SERVERLINK); ++ WFIFOHEAD(login_fd, 86);
+ WFIFOW(login_fd,0) = 0x2710; +- memset(WFIFOP(login_fd,2), 0, 24); +- memcpy(WFIFOP(login_fd,2), userid, strlen(userid) < 24 ? strlen(userid) : 24); +- memset(WFIFOP(login_fd,26), 0, 24); +- memcpy(WFIFOP(login_fd,26), passwd, strlen(passwd) < 24 ? strlen(passwd) : 24); ++ memcpy(WFIFOP(login_fd,2), userid, 24);
++ memcpy(WFIFOP(login_fd,26), passwd, 24);
+ WFIFOL(login_fd,50) = 0; + WFIFOL(login_fd,54) = char_ip; + WFIFOL(login_fd,58) = char_port; +@@ -2830,12 +3749,28 @@ + memcpy(WFIFOP(login_fd,60), server_name, strlen(server_name) < 20 ? strlen(server_name) : 20); + WFIFOW(login_fd,80) = 0; + WFIFOW(login_fd,82) = char_maintenance; +- WFIFOW(login_fd,84) = char_new; ++
++ WFIFOW(login_fd,84) = char_new_display; //only display (New) if they want to [Kevin]
++
+ WFIFOSET(login_fd,86); + } + return 0; + } + ++//------------------------------------------------
++//Invoked 15 seconds after mapif_disconnectplayer in case the map server doesn't
++//replies/disconnect the player we tried to kick. [Skotlex]
++//------------------------------------------------
++static int chardb_waiting_disconnect(int tid, unsigned int tick, int id, int data)
++{
++ struct online_char_data* character;
++ if ((character = idb_get(online_char_db, id)) != NULL && character->waiting_disconnect)
++ { //Mark it offline due to timeout.
++ set_char_offline(character->char_id, character->account_id);
++ }
++ return 0;
++}
++
+ //---------------------------------------------------------- + // Return numerical value of a switch configuration by [Yor] + // on/off, english, fran軋is, deutsch, espaol +@@ -2849,92 +3784,56 @@ + return atoi(str); + } + +-//------------------------------------------- +-// Reading Lan Support configuration by [Yor] +-//------------------------------------------- +-int lan_config_read(const char *lancfgName) { +- int j; +- struct hostent * h = NULL; +- char line[1024], w1[1024], w2[1024]; +- FILE *fp; ++//----------------------------------
++// Reading Lan Support configuration
++// Rewrote: Anvanced subnet check [LuzZza]
++//----------------------------------
++int char_lan_config_read(const char *lancfgName) {
+ +- // set default configuration +- strncpy(lan_map_ip, "127.0.0.1", sizeof(lan_map_ip)); +- subneti[0] = 127; +- subneti[1] = 0; +- subneti[2] = 0; +- subneti[3] = 1; +- for(j = 0; j < 4; j++) +- subnetmaski[j] = 255; +- +- fp = fopen(lancfgName, "r"); ++ FILE *fp;
++ int line_num = 0;
++ char line[1024], w1[64], w2[64], w3[64], w4[64], w5[64];
+ +- if (fp == NULL) { +- printf("LAN support configuration file not found: %s\n", lancfgName); ++ if((fp = fopen(lancfgName, "r")) == NULL) {
++ ShowWarning("LAN Support configuration file is not found: %s\n", lancfgName);
+ return 1; + } + +- printf ("---start reading of Lan Support configuration...\n"); ++ ShowInfo("Reading the configuration file %s...\n", lancfgName);
+ + while(fgets(line, sizeof(line)-1, fp)) { +- if (line[0] == '/' && line[1] == '/') ++
++ line_num++;
++ if ((line[0] == '/' && line[1] == '/') || line[0] == '\n' || line[1] == '\n')
+ continue; + + line[sizeof(line)-1] = '\0'; +- if (sscanf(line, "%[^:]: %[^\r\n]", w1, w2) != 2) +- continue; ++ if(sscanf(line,"%[^:]: %[^/]/%[^:]:%[^:]:%[^\r\n]", w1, w2, w3, w4, w5) != 5) {
+ +- remove_control_chars(w1); +- remove_control_chars(w2); +- if (strcmpi(w1, "lan_map_ip") == 0) { // Read map-server Lan IP Address +- h = gethostbyname(w2); +- if (h != NULL) { +- sprintf(lan_map_ip, "%d.%d.%d.%d", (unsigned char)h->h_addr[0], (unsigned char)h->h_addr[1], (unsigned char)h->h_addr[2], (unsigned char)h->h_addr[3]); +- } else { +- strncpy(lan_map_ip, w2, sizeof(lan_map_ip)); +- lan_map_ip[sizeof(lan_map_ip)-1] = 0; +- } +- printf("LAN IP of map-server: %s.\n", lan_map_ip); +- } else if (strcmpi(w1, "subnet") == 0) { // Read Subnetwork +- for(j = 0; j < 4; j++) +- subneti[j] = 0; +- h = gethostbyname(w2); +- if (h != NULL) { +- for(j = 0; j < 4; j++) +- subneti[j] = (unsigned char)h->h_addr[j]; +- } else { +- sscanf(w2, "%d.%d.%d.%d", &subneti[0], &subneti[1], &subneti[2], &subneti[3]); +- } +- printf("Sub-network of the map-server: %d.%d.%d.%d.\n", subneti[0], subneti[1], subneti[2], subneti[3]); +- } else if (strcmpi(w1, "subnetmask") == 0){ // Read Subnetwork Mask +- for(j = 0; j < 4; j++) +- subnetmaski[j] = 255; +- h = gethostbyname(w2); +- if (h != NULL) { +- for(j = 0; j < 4; j++) +- subnetmaski[j] = (unsigned char)h->h_addr[j]; +- } else { +- sscanf(w2, "%d.%d.%d.%d", &subnetmaski[0], &subnetmaski[1], &subnetmaski[2], &subnetmaski[3]); +- } +- printf("Sub-network mask of the map-server: %d.%d.%d.%d.\n", subnetmaski[0], subnetmaski[1], subnetmaski[2], subnetmaski[3]); +- } ++ ShowWarning("Error syntax of configuration file %s in line %d.\n", lancfgName, line_num);
++ continue;
+ } +- fclose(fp); + +- // sub-network check of the map-server +- { +- unsigned int a0, a1, a2, a3; +- unsigned char p[4]; +- sscanf(lan_map_ip, "%d.%d.%d.%d", &a0, &a1, &a2, &a3); +- p[0] = a0; p[1] = a1; p[2] = a2; p[3] = a3; +- printf("LAN test of LAN IP of the map-server: "); +- if (lan_ip_check(p) == 0) { +- printf("\033[1;31m***ERROR: LAN IP of the map-server doesn't belong to the specified Sub-network.\033[0m\n"); +- } ++ remove_control_chars((unsigned char *)w1);
++ remove_control_chars((unsigned char *)w2);
++ remove_control_chars((unsigned char *)w3);
++ remove_control_chars((unsigned char *)w4);
++ remove_control_chars((unsigned char *)w5);
++
++ if(strcmpi(w1, "subnet") == 0) {
++
++ subnet[subnet_count].subnet = inet_addr(w2);
++ subnet[subnet_count].mask = inet_addr(w3);
++ subnet[subnet_count].char_ip = inet_addr(w4);
++ subnet[subnet_count].map_ip = inet_addr(w5);
++
++ subnet_count++;
+ } + +- printf("---End reading of Lan Support configuration...\n"); ++ ShowStatus("Information about %d subnetworks readen.\n", subnet_count);
++ }
+ ++ fclose(fp);
+ return 0; + } + +@@ -2944,10 +3843,11 @@ + FILE *fp = fopen(cfgName, "r"); + + if (fp == NULL) { +- printf("Configuration file not found: %s.\n", cfgName); ++ ShowFatalError("Configuration file not found: %s.\n", cfgName);
+ exit(1); + } + ++ ShowInfo("Reading configuration file %s...\n", cfgName);
+ while(fgets(line, sizeof(line)-1, fp)) { + if (line[0] == '/' && line[1] == '/') + continue; +@@ -2956,86 +3856,122 @@ + if (sscanf(line, "%[^:]: %[^\r\n]", w1, w2) != 2) + continue; + +- remove_control_chars(w1); +- remove_control_chars(w2); +- if (strcmpi(w1, "userid") == 0) { +- memcpy(userid, w2, 24); ++ remove_control_chars((unsigned char *)w1);
++ remove_control_chars((unsigned char *)w2);
++ if(strcmpi(w1,"timestamp_format") == 0) {
++ strncpy(timestamp_format, w2, 20);
++ } else if(strcmpi(w1,"console_silent")==0){
++ msg_silent = 0; //To always allow the next line to show up.
++ ShowInfo("Console Silent Setting: %d\n", atoi(w2));
++ msg_silent = atoi(w2);
++ } else if (strcmpi(w1, "userid") == 0) {
++ strncpy(userid, w2, 24);
+ } else if (strcmpi(w1, "passwd") == 0) { +- memcpy(passwd, w2, 24); ++ strncpy(passwd, w2, 24);
+ } else if (strcmpi(w1, "server_name") == 0) { + memcpy(server_name, w2, sizeof(server_name)); + server_name[sizeof(server_name) - 1] = '\0'; +- printf("%s server has been intialized\n", w2); ++ ShowStatus("%s server has been initialized\n", w2);
+ } else if (strcmpi(w1, "wisp_server_name") == 0) { + if (strlen(w2) >= 4) { + memcpy(wisp_server_name, w2, sizeof(wisp_server_name)); + wisp_server_name[sizeof(wisp_server_name) - 1] = '\0'; + } + } else if (strcmpi(w1, "login_ip") == 0) { ++ login_ip_set_ = 1;
+ h = gethostbyname(w2); + if (h != NULL) { +- printf("Login server IP address : %s -> %d.%d.%d.%d\n", w2, (unsigned char)h->h_addr[0], (unsigned char)h->h_addr[1], (unsigned char)h->h_addr[2], (unsigned char)h->h_addr[3]); ++ ShowStatus("Login server IP address : %s -> %d.%d.%d.%d\n", w2, (unsigned char)h->h_addr[0], (unsigned char)h->h_addr[1], (unsigned char)h->h_addr[2], (unsigned char)h->h_addr[3]);
+ sprintf(login_ip_str, "%d.%d.%d.%d", (unsigned char)h->h_addr[0], (unsigned char)h->h_addr[1], (unsigned char)h->h_addr[2], (unsigned char)h->h_addr[3]); + } else + memcpy(login_ip_str, w2, 16); + } else if (strcmpi(w1, "login_port") == 0) { + login_port = atoi(w2); + } else if (strcmpi(w1, "char_ip") == 0) { ++ char_ip_set_ = 1;
+ h = gethostbyname(w2); + if (h != NULL) { +- printf("Character server IP address : %s -> %d.%d.%d.%d\n", w2, (unsigned char)h->h_addr[0], (unsigned char)h->h_addr[1], (unsigned char)h->h_addr[2], (unsigned char)h->h_addr[3]); ++ ShowStatus("Character server IP address : %s -> %d.%d.%d.%d\n", w2, (unsigned char)h->h_addr[0], (unsigned char)h->h_addr[1], (unsigned char)h->h_addr[2], (unsigned char)h->h_addr[3]);
+ sprintf(char_ip_str, "%d.%d.%d.%d", (unsigned char)h->h_addr[0], (unsigned char)h->h_addr[1], (unsigned char)h->h_addr[2], (unsigned char)h->h_addr[3]); + } else + memcpy(char_ip_str, w2, 16); ++ } else if (strcmpi(w1, "bind_ip") == 0) {
++ bind_ip_set_ = 1;
++ h = gethostbyname(w2);
++ if (h != NULL) {
++ ShowStatus("Character server binding IP address : %s -> %d.%d.%d.%d\n", w2, (unsigned char)h->h_addr[0], (unsigned char)h->h_addr[1], (unsigned char)h->h_addr[2], (unsigned char)h->h_addr[3]);
++ sprintf(bind_ip_str, "%d.%d.%d.%d", (unsigned char)h->h_addr[0], (unsigned char)h->h_addr[1], (unsigned char)h->h_addr[2], (unsigned char)h->h_addr[3]);
++ } else
++ memcpy(bind_ip_str, w2, 16);
+ } else if (strcmpi(w1, "char_port") == 0) { + char_port = atoi(w2); + } else if (strcmpi(w1, "char_maintenance") == 0) { + char_maintenance = atoi(w2); + } else if (strcmpi(w1, "char_new") == 0) { + char_new = atoi(w2); ++ } else if (strcmpi(w1, "char_new_display") == 0) {
++ char_new_display = atoi(w2);
+ } else if (strcmpi(w1, "email_creation") == 0) { + email_creation = config_switch(w2); + } else if (strcmpi(w1, "char_txt") == 0) { + strcpy(char_txt, w2); ++ } else if (strcmpi(w1, "scdata_txt") == 0) { //By Skotlex
++ strcpy(scdata_txt, w2);
+ } else if (strcmpi(w1, "backup_txt") == 0) { //By zanetheinsane + strcpy(backup_txt, w2); ++ } else if (strcmpi(w1, "friends_txt") == 0) { //By davidsiaw
++ strcpy(friends_txt, w2);
+ } else if (strcmpi(w1, "backup_txt_flag") == 0) { // The backup_txt file was created because char deletion bug existed. Now it's finish and that take a lot of time to create a second file when there are a lot of characters. By [Yor] + backup_txt_flag = config_switch(w2); + } else if (strcmpi(w1, "max_connect_user") == 0) { + max_connect_user = atoi(w2); + if (max_connect_user < 0) + max_connect_user = 0; // unlimited online players ++ } else if(strcmpi(w1, "gm_allow_level") == 0) {
++ gm_allow_level = atoi(w2);
++ if(gm_allow_level < 0)
++ gm_allow_level = 99;
+ } else if (strcmpi(w1, "check_ip_flag") == 0) { + check_ip_flag = config_switch(w2); ++ } else if (strcmpi(w1, "online_check") == 0) {
++ online_check = config_switch(w2);
+ } else if (strcmpi(w1, "autosave_time") == 0) { + autosave_interval = atoi(w2)*1000; + if (autosave_interval <= 0) + autosave_interval = DEFAULT_AUTOSAVE_INTERVAL; ++ } else if (strcmpi(w1, "save_log") == 0) {
++ save_log = config_switch(w2);
+ } else if (strcmpi(w1, "start_point") == 0) { + char map[32]; + int x, y; + if (sscanf(w2, "%[^,],%d,%d", map, &x, &y) < 3) + continue; + if (strstr(map, ".gat") != NULL) { // Verify at least if '.gat' is in the map name +- memcpy(start_point.map, map, 16); ++ start_point.map = mapindex_name2id(map);
++ if (!start_point.map) {
++ ShowError("Specified start_point %s not found in map-index cache.\n", map);
++ start_point.map = 0;
++ }
+ start_point.x = x; + start_point.y = y; + } ++ } else if(strcmpi(w1,"log_char")==0) { //log char or not [devil]
++ log_char = atoi(w2);
+ } else if (strcmpi(w1, "start_zeny") == 0) { + start_zeny = atoi(w2); + if (start_zeny < 0) + start_zeny = 0; + } else if (strcmpi(w1, "start_weapon") == 0) { +- start_zeny = atoi(w2); ++ start_weapon = atoi(w2);
+ if (start_weapon < 0) + start_weapon = 0; + } else if (strcmpi(w1, "start_armor") == 0) { +- start_zeny = atoi(w2); ++ start_armor = atoi(w2);
+ if (start_armor < 0) + start_armor = 0; + } else if (strcmpi(w1, "unknown_char_name") == 0) { + strcpy(unknown_char_name, w2); +- unknown_char_name[24] = 0; ++ unknown_char_name[NAME_LENGTH-1] = '\0';
+ } else if (strcmpi(w1, "char_log_filename") == 0) { + strcpy(char_log_filename, w2); + } else if (strcmpi(w1, "name_ignoring_case") == 0) { +@@ -3061,52 +3997,130 @@ + online_refresh_html = atoi(w2); + if (online_refresh_html < 1) + online_refresh_html = 1; +- } else if(strcmpi(w1,"anti_freeze_enable")==0){ +- anti_freeze_enable = config_switch(w2); +- } else if (strcmpi(w1, "anti_freeze_interval") == 0) { +- ANTI_FREEZE_INTERVAL = atoi(w2); +- if (ANTI_FREEZE_INTERVAL < 5) +- ANTI_FREEZE_INTERVAL = 5; // minimum 5 seconds ++ } else if(strcmpi(w1,"db_path")==0) {
++ strcpy(db_path,w2);
++ } else if (strcmpi(w1, "console") == 0) {
++ if(strcmpi(w2,"on") == 0 || strcmpi(w2,"yes") == 0 )
++ console = 1;
++ } else if (strcmpi(w1, "fame_list_alchemist") == 0) {
++ fame_list_size_chemist = atoi(w2);
++ if (fame_list_size_chemist > MAX_FAME_LIST) {
++ ShowWarning("Max fame list size is %d (fame_list_alchemist)\n", MAX_FAME_LIST);
++ fame_list_size_chemist = MAX_FAME_LIST;
++ }
++ } else if (strcmpi(w1, "fame_list_blacksmith") == 0) {
++ fame_list_size_smith = atoi(w2);
++ if (fame_list_size_smith > MAX_FAME_LIST) {
++ ShowWarning("Max fame list size is %d (fame_list_blacksmith)\n", MAX_FAME_LIST);
++ fame_list_size_smith = MAX_FAME_LIST;
++ }
++ } else if (strcmpi(w1, "fame_list_taekwon") == 0) {
++ fame_list_size_taekwon = atoi(w2);
++ if (fame_list_size_taekwon > MAX_FAME_LIST) {
++ ShowWarning("Max fame list size is %d (fame_list_taekwon)\n", MAX_FAME_LIST);
++ fame_list_size_taekwon = MAX_FAME_LIST;
++ }
+ } else if (strcmpi(w1, "import") == 0) { + char_config_read(w2); + } + } + fclose(fp); + ++ ShowInfo("done reading %s.\n", cfgName);
+ return 0; + } + ++int chardb_final(int key, void* data, va_list va)
++{
++ aFree(data);
++ return 0;
++}
+ void do_final(void) { +- int i; +- ++ ShowStatus("Terminating server.\n");
+ // write online players files with no player +- for(i = 0; i < char_num; i++) +- online_chars[i] = -1; ++ online_char_db->clear(online_char_db, NULL); //clean the db...
+ create_online_files(); +- free(online_chars); ++ online_char_db->destroy(online_char_db, NULL); //dispose the db...
+ + mmo_char_sync(); + inter_save(); ++ set_all_offline();
+ +- if (gm_account != NULL) +- free(gm_account); ++ if(gm_account) aFree(gm_account);
++ if(char_dat) aFree(char_dat);
+ +- free(char_dat); + delete_session(login_fd); + delete_session(char_fd); + ++#ifdef ENABLE_SC_SAVING
++ status_final();
++#endif
++ inter_final();
++ mapindex_final();
++
+ char_log("----End of char-server (normal end with closing of all files)." RETCODE); + } + ++void set_server_type(void)
++{
++ SERVER_TYPE = ATHENA_SERVER_CHAR;
++}
++
++static int online_data_cleanup_sub(DBKey key, void *data, va_list ap)
++{
++ struct online_char_data *character= (struct online_char_data*)data;
++ if (character->server == -2) //Unknown server.. set them offline
++ set_char_offline(character->char_id, character->account_id);
++ if (character->server < 0)
++ //Free data from players that have not been online for a while.
++ db_remove(online_char_db, key);
++ return 0;
++}
++
++static int online_data_cleanup(int tid, unsigned int tick, int id, int data)
++{
++ online_char_db->foreach(online_char_db, online_data_cleanup_sub);
++ return 0;
++}
++
+ int do_init(int argc, char **argv) { + int i; + ++ mapindex_init(); //Needed here for the start-point reading.
++ start_point.map = mapindex_name2id("new_1-1.gat");
++ char_config_read((argc < 2) ? CHAR_CONF_NAME : argv[1]);
++ char_lan_config_read((argc > 3) ? argv[3] : LOGIN_LAN_CONF_NAME);
++
++ if (strcmp(userid, "s1")==0 && strcmp(passwd, "p1")==0) {
++ ShowError("Using the default user/password s1/p1 is NOT RECOMMENDED.\n");
++ ShowNotice("Please edit your save/account.txt file to create a proper inter-server user/password (gender 'S')\n");
++ ShowNotice("And then change the user/password to use in conf/char_athena.conf (or conf/import/char_conf.txt)\n");
++ }
++
+ // a newline in the log... + char_log(""); ++ // moved behind char_config_read in case we changed the filename [celest]
+ char_log("The char-server starting..." RETCODE); + +- char_config_read((argc < 2) ? CHAR_CONF_NAME : argv[1]); +- lan_config_read((argc > 1) ? argv[1] : LOGIN_LAN_CONF_NAME); ++ if ((naddr_ != 0) && (login_ip_set_ == 0 || char_ip_set_ == 0)) {
++ // The char server should know what IP address it is running on
++ // - MouseJstr
++ int localaddr = ntohl(addr_[0]);
++ unsigned char *ptr = (unsigned char *) &localaddr;
++ char buf[16];
++ sprintf(buf, "%d.%d.%d.%d", ptr[0], ptr[1], ptr[2], ptr[3]);;
++ if (naddr_ != 1)
++ ShowStatus("Multiple interfaces detected.. using %s as our IP address\n", buf);
++ else
++ ShowStatus("Defaulting to %s as our IP address\n", buf);
++ if (login_ip_set_ == 0)
++ strcpy(login_ip_str, buf);
++ if (char_ip_set_ == 0)
++ strcpy(char_ip_str, buf);
++
++ if (ptr[0] == 192 && ptr[1] == 168)
++ ShowWarning("Firewall detected.. edit lan_support.conf and char_athena.conf\n");
++ }
+ + login_ip = inet_addr(login_ip_str); + char_ip = inet_addr(char_ip_str); +@@ -3116,34 +4130,45 @@ + server_fd[i] = -1; + } + +- mmo_char_init(); ++ online_char_db = db_alloc(__FILE__,__LINE__,DB_INT,DB_OPT_RELEASE_DATA,sizeof(int));
+ ++ mmo_char_init();
++#ifdef ENABLE_SC_SAVING
++ status_init();
++#endif
+ update_online = time(NULL); + create_online_files(); // update online players files at start of the server + + inter_init((argc > 2) ? argv[2] : inter_cfgName); // inter server 初期化 + +- set_termfunc(do_final); + set_defaultparse(parse_char); + +- char_fd = make_listen_port(char_port); ++ if (bind_ip_set_)
++ char_fd = make_listen_bind(inet_addr(bind_ip_str),char_port);
++ else
++ char_fd = make_listen_bind(INADDR_ANY,char_port);
+ + add_timer_func_list(check_connect_login_server, "check_connect_login_server"); + add_timer_func_list(send_users_tologin, "send_users_tologin"); ++ add_timer_func_list(send_accounts_tologin, "send_accounts_tologin");
+ add_timer_func_list(mmo_char_sync_timer, "mmo_char_sync_timer"); ++ add_timer_func_list(chardb_waiting_disconnect, "chardb_waiting_disconnect");
++ add_timer_func_list(online_data_cleanup, "online_data_cleanup");
+ +- i = add_timer_interval(gettick() + 1000, check_connect_login_server, 0, 0, 10 * 1000); +- i = add_timer_interval(gettick() + 1000, send_users_tologin, 0, 0, 5 * 1000); +- i = add_timer_interval(gettick() + autosave_interval, mmo_char_sync_timer, 0, 0, autosave_interval); +- +- if(anti_freeze_enable > 0) { +- add_timer_func_list(map_anti_freeze_system, "map_anti_freeze_system"); +- i = add_timer_interval(gettick() + 1000, map_anti_freeze_system, 0, 0, ANTI_FREEZE_INTERVAL * 1000); // checks every X seconds user specifies ++ add_timer_interval(gettick() + 600*1000, online_data_cleanup, 0, 0, 600 * 1000);
++ add_timer_interval(gettick() + 1000, check_connect_login_server, 0, 0, 10 * 1000);
++ add_timer_interval(gettick() + 1000, send_users_tologin, 0, 0, 5 * 1000);
++ add_timer_interval(gettick() + 3600*1000, send_accounts_tologin, 0, 0, 3600*1000); //Sync online accounts every hour
++ add_timer_interval(gettick() + autosave_interval, mmo_char_sync_timer, 0, 0, autosave_interval);
++
++ if(console) {
++ set_defaultconsoleparse(parse_console);
++ start_console();
+ } + + char_log("The char-server is ready (Server is listening on the port %d)." RETCODE, char_port); + +- printf("The char-server is \033[1;32mready\033[0m (Server is listening on the port %d).\n\n", char_port); ++ ShowStatus("The char-server is "CL_GREEN"ready"CL_RESET" (Server is listening on the port %d).\n\n", char_port);
+ + return 0; + } diff --git a/src/char/int_guild.c b/src/char/int_guild.c new file mode 100644 index 0000000..665e017 --- /dev/null +++ b/src/char/int_guild.c @@ -0,0 +1,1441 @@ +// $Id: int_guild.c,v 1.2 2004/09/25 19:36:53 Akitasha Exp $ +#include "inter.h" +#include "int_guild.h" +#include "int_storage.h" +#include "mmo.h" +#include "char.h" +#include "socket.h" +#include "db.h" +#include "lock.h" + +#include <string.h> +#include <stdio.h> +#include <stdlib.h> + +char guild_txt[1024] = "save/guild.txt"; +char castle_txt[1024] = "save/castle.txt"; + +static struct dbt *guild_db; +static struct dbt *castle_db; + +static int guild_newid = 10000; + +static int guild_exp[100]; + +int mapif_parse_GuildLeave(int fd, int guild_id, int account_id, int char_id, int flag, const char *mes); +int mapif_guild_broken(int guild_id, int flag); +int guild_check_empty(struct guild *g); +int guild_calcinfo(struct guild *g); +int mapif_guild_basicinfochanged(int guild_id, int type, const void *data, int len); +int mapif_guild_info(int fd, struct guild *g); +int guild_break_sub(void *key, void *data, va_list ap); + +// ギルドデータの文字列への変換 +int inter_guild_tostr(char *str, struct guild *g) { + int i, c, len; + + // 基本データ + len = sprintf(str, "%d\t%s\t%s\t%d,%d,%d,%d,%d\t%s#\t%s#\t", + g->guild_id, g->name, g->master, + g->guild_lv, g->max_member, g->exp, g->skill_point, g->castle_id, + g->mes1, g->mes2); + // メンバー + for(i = 0; i < g->max_member; i++) { + struct guild_member *m = &g->member[i]; + len += sprintf(str + len, "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d\t%s\t", + m->account_id, m->char_id, + m->hair, m->hair_color, m->gender, + m->class, m->lv, m->exp, m->exp_payper, m->position, + ((m->account_id > 0) ? m->name : "-")); + } + // 役職 + for(i = 0; i < MAX_GUILDPOSITION; i++) { + struct guild_position *p = &g->position[i]; + len += sprintf(str + len, "%d,%d\t%s#\t", p->mode, p->exp_mode, p->name); + } + // エンブレム + len += sprintf(str + len, "%d,%d,", g->emblem_len, g->emblem_id); + for(i = 0; i < g->emblem_len; i++) { + len += sprintf(str + len, "%02x", (unsigned char)(g->emblem_data[i])); + } + len += sprintf(str + len, "$\t"); + // 同盟リスト + c = 0; + for(i = 0; i < MAX_GUILDALLIANCE; i++) + if (g->alliance[i].guild_id > 0) + c++; + len += sprintf(str + len, "%d\t", c); + for(i = 0; i < MAX_GUILDALLIANCE; i++) { + struct guild_alliance *a = &g->alliance[i]; + if (a->guild_id > 0) + len += sprintf(str + len, "%d,%d\t%s\t", a->guild_id, a->opposition, a->name); + } + // 追放リスト + c = 0; + for(i = 0; i < MAX_GUILDEXPLUSION; i++) + if (g->explusion[i].account_id > 0) + c++; + len += sprintf(str + len, "%d\t", c); + for(i = 0; i < MAX_GUILDEXPLUSION; i++) { + struct guild_explusion *e = &g->explusion[i]; + if (e->account_id > 0) + len += sprintf(str + len, "%d,%d,%d,%d\t%s\t%s\t%s#\t", + e->account_id, e->rsv1, e->rsv2, e->rsv3, + e->name, e->acc, e->mes ); + } + // ギルドスキル + for(i = 0; i < MAX_GUILDSKILL; i++) { + len += sprintf(str + len, "%d,%d ", g->skill[i].id, g->skill[i].lv); + } + len += sprintf(str + len, "\t"); + + return 0; +} + +// ギルドデータの文字列からの変換 +int inter_guild_fromstr(char *str, struct guild *g) { + int i, j, c; + int tmp_int[16]; + char tmp_str[4][256]; + char tmp_str2[4096]; + char *pstr; + + // 基本データ + memset(g, 0, sizeof(struct guild)); + if (sscanf(str, "%d\t%[^\t]\t%[^\t]\t%d,%d,%d,%d,%d\t%[^\t]\t%[^\t]\t", &tmp_int[0], + tmp_str[0], tmp_str[1], + &tmp_int[1], &tmp_int[2], &tmp_int[3], &tmp_int[4], &tmp_int[5], + tmp_str[2], tmp_str[3]) < 8) + return 1; + + g->guild_id = tmp_int[0]; + g->guild_lv = tmp_int[1]; + g->max_member = tmp_int[2]; + g->exp = tmp_int[3]; + g->skill_point = tmp_int[4]; + g->castle_id = tmp_int[5]; + memcpy(g->name, tmp_str[0], 24); + memcpy(g->master, tmp_str[1], 24); + memcpy(g->mes1, tmp_str[2], 60); + memcpy(g->mes2, tmp_str[3], 120); + g->mes1[strlen(g->mes1)-1] = 0; + g->mes2[strlen(g->mes2)-1] = 0; + + for(j = 0; j < 6 && str != NULL; j++) // 位置スキップ + str = strchr(str + 1, '\t'); +// printf("GuildBaseInfo OK\n"); + + // メンバー + for(i = 0; i < g->max_member; i++) { + struct guild_member *m = &g->member[i]; + if (sscanf(str + 1, "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d\t%[^\t]\t", + &tmp_int[0], &tmp_int[1], &tmp_int[2], &tmp_int[3], &tmp_int[4], + &tmp_int[5], &tmp_int[6], &tmp_int[7], &tmp_int[8], &tmp_int[9], + tmp_str[0]) < 11) + return 1; + m->account_id = tmp_int[0]; + m->char_id = tmp_int[1]; + m->hair = tmp_int[2]; + m->hair_color = tmp_int[3]; + m->gender = tmp_int[4]; + m->class = tmp_int[5]; + m->lv = tmp_int[6]; + m->exp = tmp_int[7]; + m->exp_payper = tmp_int[8]; + m->position = tmp_int[9]; + memcpy(m->name, tmp_str[0], 24); + + for(j = 0; j < 2 && str != NULL; j++) // 位置スキップ + str = strchr(str + 1, '\t'); + } +// printf("GuildMemberInfo OK\n"); + // 役職 + i = 0; + while (sscanf(str+1, "%d,%d%n", &tmp_int[0], &tmp_int[1], &j) == 2 && str[1+j] == '\t') { + struct guild_position *p = &g->position[i]; + if (sscanf(str+1, "%d,%d\t%[^\t]\t", &tmp_int[0], &tmp_int[1], tmp_str[0]) < 3) + return 1; + p->mode = tmp_int[0]; + p->exp_mode = tmp_int[1]; + tmp_str[0][strlen(tmp_str[0])-1] = 0; + memcpy(p->name, tmp_str[0], 24); + + for(j = 0; j < 2 && str != NULL; j++) // 位置スキップ + str = strchr(str+1, '\t'); + i++; + } +// printf("GuildPositionInfo OK\n"); + // エンブレム + tmp_int[1] = 0; + if (sscanf(str + 1, "%d,%d,%[^\t]\t", &tmp_int[0], &tmp_int[1], tmp_str2)< 3 && + sscanf(str + 1, "%d,%[^\t]\t", &tmp_int[0], tmp_str2) < 2) + return 1; + g->emblem_len = tmp_int[0]; + g->emblem_id = tmp_int[1]; + for(i = 0, pstr = tmp_str2; i < g->emblem_len; i++, pstr += 2) { + int c1 = pstr[0], c2 = pstr[1], x1 = 0, x2 = 0; + if (c1 >= '0' && c1 <= '9') x1 = c1 - '0'; + if (c1 >= 'a' && c1 <= 'f') x1 = c1 - 'a' + 10; + if (c1 >= 'A' && c1 <= 'F') x1 = c1 - 'A' + 10; + if (c2 >= '0' && c2 <= '9') x2 = c2 - '0'; + if (c2 >= 'a' && c2 <= 'f') x2 = c2 - 'a' + 10; + if (c2 >= 'A' && c2 <= 'F') x2 = c2 - 'A' + 10; + g->emblem_data[i] = (x1<<4) | x2; + } +// printf("GuildEmblemInfo OK\n"); + str=strchr(str + 1, '\t'); // 位置スキップ + + // 同盟リスト + if (sscanf(str + 1, "%d\t", &c) < 1) + return 1; + str = strchr(str + 1, '\t'); // 位置スキップ + for(i = 0; i < c; i++) { + struct guild_alliance *a = &g->alliance[i]; + if (sscanf(str + 1, "%d,%d\t%[^\t]\t", &tmp_int[0], &tmp_int[1], tmp_str[0]) < 3) + return 1; + a->guild_id = tmp_int[0]; + a->opposition = tmp_int[1]; + memcpy(a->name, tmp_str[0], 24); + + for(j = 0; j < 2 && str != NULL; j++) // 位置スキップ + str = strchr(str + 1, '\t'); + } +// printf("GuildAllianceInfo OK\n"); + // 追放リスト + if (sscanf(str+1, "%d\t", &c) < 1) + return 1; + str = strchr(str + 1, '\t'); // 位置スキップ + for(i = 0; i < c; i++) { + struct guild_explusion *e = &g->explusion[i]; + if (sscanf(str + 1, "%d,%d,%d,%d\t%[^\t]\t%[^\t]\t%[^\t]\t", + &tmp_int[0], &tmp_int[1], &tmp_int[2], &tmp_int[3], + tmp_str[0], tmp_str[1], tmp_str[2]) < 6) + return 1; + e->account_id = tmp_int[0]; + e->rsv1 = tmp_int[1]; + e->rsv2 = tmp_int[2]; + e->rsv3 = tmp_int[3]; + memcpy(e->name, tmp_str[0], 24); + memcpy(e->acc, tmp_str[1], 24); + tmp_str[2][strlen(tmp_str[2])-1] = 0; + memcpy(e->mes, tmp_str[2], 40); + + for(j = 0; j < 4 && str != NULL; j++) // 位置スキップ + str = strchr(str + 1, '\t'); + } +// printf("GuildExplusionInfo OK\n"); + // ギルドスキル + for(i = 0; i < MAX_GUILDSKILL; i++) { + if (sscanf(str+1,"%d,%d ", &tmp_int[0], &tmp_int[1]) < 2) + break; + g->skill[i].id = tmp_int[0]; + g->skill[i].lv = tmp_int[1]; + str = strchr(str + 1, ' '); + } + str = strchr(str + 1, '\t'); +// printf("GuildSkillInfo OK\n"); + + return 0; +} + +// ギルド城データの文字列への変換 +int inter_guildcastle_tostr(char *str, struct guild_castle *gc) { + int len; + + len = sprintf(str, "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d", // added Guardian HP [Valaris] + gc->castle_id, gc->guild_id, gc->economy, gc->defense, gc->triggerE, + gc->triggerD, gc->nextTime, gc->payTime, gc->createTime, gc->visibleC, + gc->visibleG0, gc->visibleG1, gc->visibleG2, gc->visibleG3, gc->visibleG4, + gc->visibleG5, gc->visibleG6, gc->visibleG7, gc->Ghp0, gc->Ghp1, gc->Ghp2, + gc->Ghp3, gc->Ghp4, gc->Ghp5, gc->Ghp6, gc->Ghp7); + + return 0; +} + +// ギルド城データの文字列からの変換 +int inter_guildcastle_fromstr(char *str, struct guild_castle *gc) { + int tmp_int[26]; + + memset(gc, 0, sizeof(struct guild_castle)); + // new structure of guild castle + if (sscanf(str, "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d", + &tmp_int[0], &tmp_int[1], &tmp_int[2], &tmp_int[3], &tmp_int[4], &tmp_int[5], &tmp_int[6], + &tmp_int[7], &tmp_int[8], &tmp_int[9], &tmp_int[10], &tmp_int[11], &tmp_int[12], &tmp_int[13], + &tmp_int[14], &tmp_int[15], &tmp_int[16], &tmp_int[17], &tmp_int[18], &tmp_int[19], &tmp_int[20], + &tmp_int[21], &tmp_int[22], &tmp_int[23], &tmp_int[24], &tmp_int[25]) == 26) { + gc->castle_id = tmp_int[0]; + gc->guild_id = tmp_int[1]; + gc->economy = tmp_int[2]; + gc->defense = tmp_int[3]; + gc->triggerE = tmp_int[4]; + gc->triggerD = tmp_int[5]; + gc->nextTime = tmp_int[6]; + gc->payTime = tmp_int[7]; + gc->createTime = tmp_int[8]; + gc->visibleC = tmp_int[9]; + gc->visibleG0 = tmp_int[10]; + gc->visibleG1 = tmp_int[11]; + gc->visibleG2 = tmp_int[12]; + gc->visibleG3 = tmp_int[13]; + gc->visibleG4 = tmp_int[14]; + gc->visibleG5 = tmp_int[15]; + gc->visibleG6 = tmp_int[16]; + gc->visibleG7 = tmp_int[17]; + gc->Ghp0 = tmp_int[18]; + gc->Ghp1 = tmp_int[19]; + gc->Ghp2 = tmp_int[20]; + gc->Ghp3 = tmp_int[21]; + gc->Ghp4 = tmp_int[22]; + gc->Ghp5 = tmp_int[23]; + gc->Ghp6 = tmp_int[24]; + gc->Ghp7 = tmp_int[25]; // end additions [Valaris] + // old structure of guild castle + } else if (sscanf(str, "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d", + &tmp_int[0], &tmp_int[1], &tmp_int[2], &tmp_int[3], &tmp_int[4], &tmp_int[5], &tmp_int[6], + &tmp_int[7], &tmp_int[8], &tmp_int[9], &tmp_int[10], &tmp_int[11], &tmp_int[12], &tmp_int[13], + &tmp_int[14], &tmp_int[15], &tmp_int[16], &tmp_int[17]) == 18) { + gc->castle_id = tmp_int[0]; + gc->guild_id = tmp_int[1]; + gc->economy = tmp_int[2]; + gc->defense = tmp_int[3]; + gc->triggerE = tmp_int[4]; + gc->triggerD = tmp_int[5]; + gc->nextTime = tmp_int[6]; + gc->payTime = tmp_int[7]; + gc->createTime = tmp_int[8]; + gc->visibleC = tmp_int[9]; + gc->visibleG0 = tmp_int[10]; + gc->visibleG1 = tmp_int[11]; + gc->visibleG2 = tmp_int[12]; + gc->visibleG3 = tmp_int[13]; + gc->visibleG4 = tmp_int[14]; + gc->visibleG5 = tmp_int[15]; + gc->visibleG6 = tmp_int[16]; + gc->visibleG7 = tmp_int[17]; + if (gc->visibleG0 == 1) + gc->Ghp0 = 15670 + 2000 * gc->defense; + else + gc->Ghp0 = 0; + if (gc->visibleG1 == 1) + gc->Ghp1 = 15670 + 2000 * gc->defense; + else + gc->Ghp1 = 0; + if (gc->visibleG2 == 1) + gc->Ghp2 = 15670 + 2000 * gc->defense; + else + gc->Ghp2 = 0; + if (gc->visibleG3 == 1) + gc->Ghp3 = 30214 + 2000 * gc->defense; + else + gc->Ghp3 = 0; + if (gc->visibleG4 == 1) + gc->Ghp4 = 30214 + 2000 * gc->defense; + else + gc->Ghp4 = 0; + if (gc->visibleG5 == 1) + gc->Ghp5 = 28634 + 2000 * gc->defense; + else + gc->Ghp5 = 0; + if (gc->visibleG6 == 1) + gc->Ghp6 = 28634 + 2000 * gc->defense; + else + gc->Ghp6 = 0; + if (gc->visibleG7 == 1) + gc->Ghp7 = 28634 + 2000 * gc->defense; + else + gc->Ghp7 = 0; + } else { + return 1; + } + + return 0; +} + +// ギルド関連データベース読み込み +int inter_guild_readdb() { + int i; + FILE *fp; + char line[1024]; + + fp = fopen("db/exp_guild.txt", "r"); + if (fp == NULL) { + printf("can't read db/exp_guild.txt\n"); + return 1; + } + i = 0; + while(fgets(line, sizeof(line)-1, fp) && i < 100){ + if (line[0] == '/' && line[1] == '/') + continue; + guild_exp[i] = atoi(line); + i++; + } + fclose(fp); + + return 0; +} + +// ギルドデータの読み込み +int inter_guild_init() { + char line[16384]; + struct guild *g; + struct guild_castle *gc; + FILE *fp; + int i, j, c = 0; + + inter_guild_readdb(); + + guild_db = numdb_init(); + castle_db = numdb_init(); + + if ((fp = fopen(guild_txt,"r")) == NULL) + return 1; + while(fgets(line, sizeof(line)-1, fp)) { + j = 0; + if (sscanf(line, "%d\t%%newid%%\n%n", &i, &j) == 1 && j > 0 && guild_newid <= i) { + guild_newid = i; + continue; + } + + g = calloc(sizeof(struct guild), 1); + if(g == NULL){ + printf("int_guild: out of memory!\n"); + exit(0); + } + memset(g, 0, sizeof(struct guild)); + if (inter_guild_fromstr(line, g) == 0 && g->guild_id > 0) { + if (g->guild_id >= guild_newid) + guild_newid = g->guild_id + 1; + numdb_insert(guild_db, g->guild_id, g); + guild_check_empty(g); + guild_calcinfo(g); + } else { + printf("int_guild: broken data [%s] line %d\n", guild_txt, c); + free(g); + } + c++; + } + fclose(fp); +// printf("int_guild: %s read done (%d guilds)\n", guild_txt, c); + + c = 0;//カウンタ初期化 + + if ((fp = fopen(castle_txt, "r")) == NULL) { + return 1; + } + + while(fgets(line, sizeof(line)-1, fp)) { + gc = calloc(sizeof(struct guild_castle), 1); + if(gc == NULL){ + printf("int_guild: out of memory!\n"); + exit(0); + } + memset(gc, 0, sizeof(struct guild_castle)); + if (inter_guildcastle_fromstr(line, gc) == 0) { + numdb_insert(castle_db, gc->castle_id, gc); + } else { + printf("int_guild: broken data [%s] line %d\n", castle_txt, c); + free(gc); + } + c++; + } + + if (!c) { + printf(" %s - making Default Data...\n", castle_txt); + //デフォルトデータを作成 + for(i = 0; i < MAX_GUILDCASTLE; i++) { + gc = calloc(sizeof(struct guild_castle), 1); + if (gc == NULL) { + printf("int_guild: out of memory!\n"); + exit(0); + } + memset(gc, 0, sizeof(struct guild_castle)); + gc->castle_id = i; + gc->guild_id = 0; + gc->economy = 0; + gc->defense = 0; + gc->triggerE = 0; + gc->triggerD = 0; + gc->nextTime = 0; + gc->payTime = 0; + gc->createTime = 0; + gc->visibleC = 0; + gc->visibleG0 = 0; + gc->visibleG1 = 0; + gc->visibleG2 = 0; + gc->visibleG3 = 0; + gc->visibleG4 = 0; + gc->visibleG5 = 0; + gc->visibleG6 = 0; + gc->visibleG7 = 0; + gc->Ghp0 = 0; // guardian HP [Valaris] + gc->Ghp1 = 0; + gc->Ghp2 = 0; + gc->Ghp3 = 0; + gc->Ghp4 = 0; + gc->Ghp5 = 0; + gc->Ghp6 = 0; + gc->Ghp7 = 0; // end additions [Valaris] + numdb_insert(castle_db, gc->castle_id, gc); + } + printf(" %s - making done\n",castle_txt); + return 0; + } + + fclose(fp); + + return 0; +} + +struct guild *inter_guild_search(int guild_id) { + struct guild *g; + + g=numdb_search(guild_db, guild_id); + + return g; +} + +// ギルドデータのセーブ用 +int inter_guild_save_sub(void *key,void *data,va_list ap) { + char line[16384]; + FILE *fp; + + inter_guild_tostr(line,(struct guild *)data); + fp=va_arg(ap,FILE *); + fprintf(fp,"%s" RETCODE,line); + + return 0; +} + +// ギルド城データのセーブ用 +int inter_castle_save_sub(void *key, void *data, va_list ap) { + char line[16384]; + FILE *fp; + + inter_guildcastle_tostr(line, (struct guild_castle *)data); + fp = va_arg(ap, FILE *); + fprintf(fp, "%s" RETCODE, line); + + return 0; +} + +// ギルドデータのセーブ +int inter_guild_save() { + FILE *fp; + int lock; + + if ((fp = lock_fopen(guild_txt, &lock)) == NULL) { + printf("int_guild: cant write [%s] !!! data is lost !!!\n", guild_txt); + return 1; + } + numdb_foreach(guild_db, inter_guild_save_sub, fp); +// fprintf(fp, "%d\t%%newid%%\n", guild_newid); + lock_fclose(fp, guild_txt, &lock); +// printf("int_guild: %s saved.\n", guild_txt); + + if ((fp = lock_fopen(castle_txt,&lock)) == NULL) { + printf("int_guild: cant write [%s] !!! data is lost !!!\n", castle_txt); + return 1; + } + numdb_foreach(castle_db, inter_castle_save_sub, fp); + lock_fclose(fp, castle_txt, &lock); + + return 0; +} + +// ギルド名検索用 +int search_guildname_sub(void *key, void *data, va_list ap) { + struct guild *g = (struct guild *)data, **dst; + char *str; + + str = va_arg(ap, char *); + dst = va_arg(ap, struct guild **); + if (strcmpi(g->name, str) == 0) + *dst = g; + return 0; +} + +// ギルド名検索 +struct guild* search_guildname(char *str) { + struct guild *g = NULL; + numdb_foreach(guild_db, search_guildname_sub, str, &g); + return g; +} + +// ギルドが空かどうかチェック +int guild_check_empty(struct guild *g) { + int i; + + for(i = 0; i < g->max_member; i++) { + if (g->member[i].account_id > 0) { + return 0; + } + } + // 誰もいないので解散 + numdb_foreach(guild_db, guild_break_sub, g->guild_id); + numdb_erase(guild_db, g->guild_id); + inter_guild_storage_delete(g->guild_id); + mapif_guild_broken(g->guild_id, 0); + free(g); + + return 1; +} + +// キャラの競合がないかチェック用 +int guild_check_conflict_sub(void *key, void *data, va_list ap) { + struct guild *g = (struct guild *)data; + int guild_id, account_id, char_id, i; + + guild_id = va_arg(ap, int); + account_id = va_arg(ap, int); + char_id = va_arg(ap, int); + + if (g->guild_id == guild_id) // 本来の所属なので問題なし + return 0; + + for(i = 0; i < MAX_GUILD; i++) { + if (g->member[i].account_id == account_id && g->member[i].char_id == char_id) { + // 別のギルドに偽の所属データがあるので脱退 + printf("int_guild: guild conflict! %d,%d %d!=%d\n", account_id, char_id, guild_id, g->guild_id); + mapif_parse_GuildLeave(-1, g->guild_id, account_id, char_id, 0, "**データ競合**"); + } + } + + return 0; +} +// キャラの競合がないかチェック +int guild_check_conflict(int guild_id, int account_id, int char_id) { + numdb_foreach(guild_db, guild_check_conflict_sub, guild_id, account_id, char_id); + + return 0; +} + +int guild_nextexp(int level) { + if (level < 100) + return guild_exp[level-1]; + + return 0; +} + +// ギルドスキルがあるか確認 +int guild_checkskill(struct guild *g, int id){ + return g->skill[id-10000].lv; +} + +// ギルドの情報の再計算 +int guild_calcinfo(struct guild *g) { + int i, c, nextexp; + struct guild before = *g; + + // スキルIDの設定 + for(i = 0; i < 5; i++) + g->skill[i].id = i + 10000; + + // ギルドレベル + if (g->guild_lv <= 0) + g->guild_lv = 1; + nextexp = guild_nextexp(g->guild_lv); + if (nextexp > 0) { + while(g->exp >= nextexp) { // レベルアップ処理 + g->exp -= nextexp; + g->guild_lv++; + g->skill_point++; + nextexp = guild_nextexp(g->guild_lv); + } + } + + // ギルドの次の経験値 + g->next_exp = guild_nextexp(g->guild_lv); + + // メンバ上限(ギルド拡張適用) + g->max_member = 16 + guild_checkskill(g, 10004) * 2; + + // 平均レベルとオンライン人数 + g->average_lv = 0; + g->connect_member = 0; + c = 0; + for(i = 0; i < g->max_member; i++) { + if (g->member[i].account_id > 0) { + g->average_lv += g->member[i].lv; + c++; + if (g->member[i].online > 0) + g->connect_member++; + } + } + g->average_lv /= c; + + // 全データを送る必要がありそう + if (g->max_member != before.max_member || + g->guild_lv != before.guild_lv || + g->skill_point != before.skill_point) { + mapif_guild_info(-1, g); + return 1; + } + + return 0; +} + +//------------------------------------------------------------------- +// map serverへの通信 + +// ギルド作成可否 +int mapif_guild_created(int fd, int account_id, struct guild *g) { + WFIFOW(fd,0) = 0x3830; + WFIFOL(fd,2) = account_id; + if (g != NULL) { + WFIFOL(fd,6) = g->guild_id; + printf("int_guild: created! %d %s\n", g->guild_id, g->name); + }else{ + WFIFOL(fd,6) = 0; + } + WFIFOSET(fd,10); + return 0; +} + +// ギルド情報見つからず +int mapif_guild_noinfo(int fd, int guild_id) { + WFIFOW(fd,0) = 0x3831; + WFIFOW(fd,2) = 8; + WFIFOL(fd,4) = guild_id; + WFIFOSET(fd,8); + printf("int_guild: info not found %d\n", guild_id); + + return 0; +} + +// ギルド情報まとめ送り +int mapif_guild_info(int fd, struct guild *g) { + unsigned char buf[4 + sizeof(struct guild)]; + + WBUFW(buf,0) = 0x3831; + memcpy(buf + 4, g, sizeof(struct guild)); + WBUFW(buf,2) = 4 + sizeof(struct guild); +// printf("int_guild: sizeof(guild)=%d\n", sizeof(struct guild)); + if (fd < 0) + mapif_sendall(buf, WBUFW(buf,2)); + else + mapif_send(fd, buf, WBUFW(buf,2)); +// printf("int_guild: info %d %s\n", p->guild_id, p->name); + + return 0; +} + +// メンバ追加可否 +int mapif_guild_memberadded(int fd, int guild_id, int account_id, int char_id, int flag) { + WFIFOW(fd,0) = 0x3832; + WFIFOL(fd,2) = guild_id; + WFIFOL(fd,6) = account_id; + WFIFOL(fd,10) = char_id; + WFIFOB(fd,14) = flag; + WFIFOSET(fd, 15); + + return 0; +} + +// 脱退/追放通知 +int mapif_guild_leaved(int guild_id, int account_id, int char_id, int flag, const char *name, const char *mes) { + unsigned char buf[79]; + + WBUFW(buf, 0) = 0x3834; + WBUFL(buf, 2) = guild_id; + WBUFL(buf, 6) = account_id; + WBUFL(buf,10) = char_id; + WBUFB(buf,14) = flag; + memcpy(WBUFP(buf,15), mes, 40); + memcpy(WBUFP(buf,55), name, 24); + mapif_sendall(buf, 79); + printf("int_guild: guild leaved %d %d %s %s\n", guild_id, account_id, name, mes); + + return 0; +} + +// オンライン状態とLv更新通知 +int mapif_guild_memberinfoshort(struct guild *g, int idx) { + unsigned char buf[19]; + + WBUFW(buf, 0) = 0x3835; + WBUFL(buf, 2) = g->guild_id; + WBUFL(buf, 6) = g->member[idx].account_id; + WBUFL(buf,10) = g->member[idx].char_id; + WBUFB(buf,14) = g->member[idx].online; + WBUFW(buf,15) = g->member[idx].lv; + WBUFW(buf,17) = g->member[idx].class; + mapif_sendall(buf, 19); + return 0; +} + +// 解散通知 +int mapif_guild_broken(int guild_id, int flag) { + unsigned char buf[7]; + + WBUFW(buf,0) = 0x3836; + WBUFL(buf,2) = guild_id; + WBUFB(buf,6) = flag; + mapif_sendall(buf, 7); + printf("int_guild: broken %d\n", guild_id); + + return 0; +} + +// ギルド内発言 +int mapif_guild_message(int guild_id, int account_id, char *mes, int len) { + unsigned char buf[len+12]; + + WBUFW(buf,0) = 0x3837; + WBUFW(buf,2) = len + 12; + WBUFL(buf,4) = guild_id; + WBUFL(buf,8) = account_id; + memcpy(WBUFP(buf,12), mes, len); + mapif_sendall(buf, len + 12); + + return 0; +} + +// ギルド基本情報変更通知 +int mapif_guild_basicinfochanged(int guild_id, int type, const void *data, int len) { + unsigned char buf[2048]; + + WBUFW(buf,0) = 0x3839; + WBUFW(buf,2) = len+10; + WBUFL(buf,4) = guild_id; + WBUFW(buf,8) = type; + memcpy(WBUFP(buf,10),data,len); + mapif_sendall(buf,len+10); + return 0; +} + +// ギルドメンバ情報変更通知 +int mapif_guild_memberinfochanged(int guild_id, int account_id, int char_id, int type, const void *data, int len) { + unsigned char buf[len + 18]; + + WBUFW(buf, 0) = 0x383a; + WBUFW(buf, 2) = len + 18; + WBUFL(buf, 4) = guild_id; + WBUFL(buf, 8) = account_id; + WBUFL(buf,12) = char_id; + WBUFW(buf,16) = type; + memcpy(WBUFP(buf,18), data, len); + mapif_sendall(buf,len+18); + + return 0; +} + +// ギルドスキルアップ通知 +int mapif_guild_skillupack(int guild_id, int skill_num, int account_id) { + unsigned char buf[14]; + + WBUFW(buf, 0) = 0x383c; + WBUFL(buf, 2) = guild_id; + WBUFL(buf, 6) = skill_num; + WBUFL(buf,10) = account_id; + mapif_sendall(buf, 14); + + return 0; +} + +// ギルド同盟/敵対通知 +int mapif_guild_alliance(int guild_id1, int guild_id2, int account_id1, int account_id2, int flag, const char *name1, const char *name2) { + unsigned char buf[67]; + + WBUFW(buf, 0) = 0x383d; + WBUFL(buf, 2) = guild_id1; + WBUFL(buf, 6) = guild_id2; + WBUFL(buf,10) = account_id1; + WBUFL(buf,14) = account_id2; + WBUFB(buf,18) = flag; + memcpy(WBUFP(buf,19), name1, 24); + memcpy(WBUFP(buf,43), name2, 24); + mapif_sendall(buf, 67); + + return 0; +} + +// ギルド役職変更通知 +int mapif_guild_position(struct guild *g, int idx) { + unsigned char buf[sizeof(struct guild_position) + 12]; + + WBUFW(buf,0) = 0x383b; + WBUFW(buf,2) = sizeof(struct guild_position) + 12; + WBUFL(buf,4) = g->guild_id; + WBUFL(buf,8) = idx; + memcpy(WBUFP(buf,12), &g->position[idx], sizeof(struct guild_position)); + mapif_sendall(buf, WBUFW(buf,2)); + + return 0; +} + +// ギルド告知変更通知 +int mapif_guild_notice(struct guild *g) { + unsigned char buf[186]; + + WBUFW(buf,0) = 0x383e; + WBUFL(buf,2) = g->guild_id; + memcpy(WBUFP(buf,6), g->mes1, 60); + memcpy(WBUFP(buf,66), g->mes2, 120); + mapif_sendall(buf, 186); + + return 0; +} + +// ギルドエンブレム変更通知 +int mapif_guild_emblem(struct guild *g) { + unsigned char buf[2048]; + + WBUFW(buf,0) = 0x383f; + WBUFW(buf,2) = g->emblem_len + 12; + WBUFL(buf,4) = g->guild_id; + WBUFL(buf,8) = g->emblem_id; + memcpy(WBUFP(buf,12), g->emblem_data, g->emblem_len); + mapif_sendall(buf, WBUFW(buf,2)); + + return 0; +} + +int mapif_guild_castle_dataload(int castle_id, int index, int value) { + unsigned char buf[9]; + + WBUFW(buf,0) = 0x3840; + WBUFW(buf,2) = castle_id; + WBUFB(buf,4) = index; + WBUFL(buf,5) = value; + mapif_sendall(buf,9); + + return 0; +} + +int mapif_guild_castle_datasave(int castle_id, int index, int value) { + unsigned char buf[9]; + + WBUFW(buf,0) = 0x3841; + WBUFW(buf,2) = castle_id; + WBUFB(buf,4) = index; + WBUFL(buf,5) = value; + mapif_sendall(buf,9); + + return 0; +} + +int mapif_guild_castle_alldataload_sub(void *key, void *data, va_list ap) { + int fd = va_arg(ap, int); + int *p = va_arg(ap, int*); + + memcpy(WFIFOP(fd,*p), (struct guild_castle*)data, sizeof(struct guild_castle)); + (*p) += sizeof(struct guild_castle); + + return 0; +} + +int mapif_guild_castle_alldataload(int fd) { + int len = 4; + + WFIFOW(fd,0) = 0x3842; + numdb_foreach(castle_db, mapif_guild_castle_alldataload_sub, fd, &len); + WFIFOW(fd,2) = len; + WFIFOSET(fd, len); + + return 0; +} + +//------------------------------------------------------------------- +// map serverからの通信 + +// ギルド作成要求 +int mapif_parse_CreateGuild(int fd, int account_id, char *name, struct guild_member *master) { + struct guild *g; + int i; + + for(i = 0; i < 24 && name[i]; i++) { + if (!(name[i] & 0xe0) || name[i] == 0x7f) { + printf("int_guild: illeagal guild name [%s]\n", name); + mapif_guild_created(fd, account_id, NULL); + return 0; + } + } + + if ((g = search_guildname(name)) != NULL) { + printf("int_guild: same name guild exists [%s]\n", name); + mapif_guild_created(fd, account_id, NULL); + return 0; + } + g = calloc(sizeof(struct guild), 1); + if (g == NULL) { + printf("int_guild: CreateGuild: out of memory !\n"); + mapif_guild_created(fd, account_id, NULL); + exit(0); + } + memset(g, 0, sizeof(struct guild)); + g->guild_id = guild_newid++; + memcpy(g->name, name, 24); + memcpy(g->master, master->name, 24); + memcpy(&g->member[0], master, sizeof(struct guild_member)); + + g->position[0].mode = 0x11; + strcpy(g->position[ 0].name, "GuildMaster"); + strcpy(g->position[MAX_GUILDPOSITION-1].name, "Newbie"); + for(i = 1; i < MAX_GUILDPOSITION-1; i++) + sprintf(g->position[i].name, "Position %d", i + 1); + + // ここでギルド情報計算が必要と思われる + g->max_member = 16; + g->average_lv = master->lv; + for(i = 0; i < 5; i++) + g->skill[i].id = i + 10000; + + numdb_insert(guild_db, g->guild_id, g); + + mapif_guild_created(fd, account_id, g); + mapif_guild_info(fd, g); + + inter_log("guild %s (id=%d) created by master %s (id=%d)" RETCODE, + name, g->guild_id, master->name, master->account_id); + + return 0; +} + +// ギルド情報要求 +int mapif_parse_GuildInfo(int fd, int guild_id) { + struct guild *g; + + g = numdb_search(guild_db, guild_id); + if (g != NULL){ + guild_calcinfo(g); + mapif_guild_info(fd, g); + } else + mapif_guild_noinfo(fd, guild_id); + + return 0; +} + +// ギルドメンバ追加要求 +int mapif_parse_GuildAddMember(int fd, int guild_id, struct guild_member *m) { + struct guild *g; + int i; + + g = numdb_search(guild_db, guild_id); + if (g == NULL) { + mapif_guild_memberadded(fd, guild_id, m->account_id, m->char_id, 1); + return 0; + } + + for(i = 0; i < g->max_member; i++) { + if (g->member[i].account_id == 0) { + memcpy(&g->member[i], m, sizeof(struct guild_member)); + mapif_guild_memberadded(fd, guild_id, m->account_id, m->char_id, 0); + guild_calcinfo(g); + mapif_guild_info(-1, g); + + return 0; + } + } + mapif_guild_memberadded(fd, guild_id, m->account_id, m->char_id, 1); + + return 0; +} + +// ギルド脱退/追放要求 +int mapif_parse_GuildLeave(int fd, int guild_id, int account_id, int char_id, int flag, const char *mes) { + struct guild *g = NULL; + int i, j; + + g = numdb_search(guild_db, guild_id); + if (g != NULL) { + for(i = 0; i < MAX_GUILD; i++) { + if (g->member[i].account_id == account_id && g->member[i].char_id == char_id) { +// printf("%d %d\n", i, (int)(&g->member[i])); +// printf("%d %s\n", i, g->member[i].name); + + if (flag) { // 追放の場合追放リストに入れる + for(j = 0; j < MAX_GUILDEXPLUSION; j++) { + if (g->explusion[j].account_id == 0) + break; + } + if (j == MAX_GUILDEXPLUSION) { // 一杯なので古いのを消す + for(j = 0; j < MAX_GUILDEXPLUSION - 1; j++) + g->explusion[j] = g->explusion[j+1]; + j = MAX_GUILDEXPLUSION - 1; + } + g->explusion[j].account_id = account_id; + memcpy(g->explusion[j].acc, "dummy", 24); + memcpy(g->explusion[j].name, g->member[i].name, 24); + memcpy(g->explusion[j].mes, mes, 40); + } + + mapif_guild_leaved(guild_id, account_id, char_id, flag, g->member[i].name, mes); +// printf("%d %d\n", i, (int)(&g->member[i])); +// printf("%d %s\n", i, (&g->member[i])->name); + memset(&g->member[i], 0, sizeof(struct guild_member)); + + if (guild_check_empty(g) == 0) + mapif_guild_info(-1,g);// まだ人がいるのでデータ送信 + + return 0; + } + } + } + return 0; +} + +// オンライン/Lv更新 +int mapif_parse_GuildChangeMemberInfoShort(int fd, int guild_id, int account_id, int char_id, int online, int lv, int class) { + struct guild *g; + int i, alv, c; + + g = numdb_search(guild_db, guild_id); + if (g == NULL) + return 0; + + g->connect_member = 0; + + alv = 0; + c = 0; + for(i = 0; i < MAX_GUILD; i++) { + if (g->member[i].account_id == account_id && g->member[i].char_id == char_id) { + g->member[i].online = online; + g->member[i].lv = lv; + g->member[i].class = class; + mapif_guild_memberinfoshort(g, i); + } + if (g->member[i].account_id > 0) { + alv += g->member[i].lv; + c++; + } + if (g->member[i].online) + g->connect_member++; + } + // 平均レベル + g->average_lv = alv / c; + + return 0; +} + +// ギルド解散処理用(同盟/敵対を解除) +int guild_break_sub(void *key, void *data, va_list ap) { + struct guild *g = (struct guild *)data; + int guild_id = va_arg(ap, int); + int i; + + for(i = 0; i < MAX_GUILDALLIANCE; i++) { + if (g->alliance[i].guild_id == guild_id) + g->alliance[i].guild_id = 0; + } + return 0; +} + +// ギルド解散要求 +int mapif_parse_BreakGuild(int fd, int guild_id) { + struct guild *g; + + g = numdb_search(guild_db, guild_id); + if(g == NULL) + return 0; + + numdb_foreach(guild_db, guild_break_sub, guild_id); + numdb_erase(guild_db, guild_id); + inter_guild_storage_delete(guild_id); + mapif_guild_broken(guild_id, 0); + + inter_log("guild %s (id=%d) broken" RETCODE, g->name, guild_id); + free(g); + + return 0; +} + +// ギルドメッセージ送信 +int mapif_parse_GuildMessage(int fd, int guild_id, int account_id, char *mes, int len) { + return mapif_guild_message(guild_id, account_id, mes, len); +} + +// ギルド基本データ変更要求 +int mapif_parse_GuildBasicInfoChange(int fd, int guild_id, int type, const char *data, int len) { + struct guild *g; + short dw = *((short *)data); + + g = numdb_search(guild_db, guild_id); + if (g == NULL) + return 0; + + switch(type) { + case GBI_GUILDLV: + if (dw > 0 && g->guild_lv + dw <= 50) { + g->guild_lv+=dw; + g->skill_point+=dw; + } else if (dw < 0 && g->guild_lv + dw >= 1) + g->guild_lv += dw; + mapif_guild_info(-1, g); + return 0; + default: + printf("int_guild: GuildBasicInfoChange: Unknown type %d\n", type); + break; + } + mapif_guild_basicinfochanged(guild_id, type, data, len); + + return 0; +} + +// ギルドメンバデータ変更要求 +int mapif_parse_GuildMemberInfoChange(int fd, int guild_id, int account_id, int char_id, int type, const char *data, int len) { + int i; + struct guild *g; + + g = numdb_search(guild_db, guild_id); + if(g == NULL) + return 0; + + for(i = 0; i < g->max_member; i++) + if (g->member[i].account_id == account_id && g->member[i].char_id == char_id) + break; + if (i == g->max_member) { + printf("int_guild: GuildMemberChange: Not found %d,%d in %d[%s]\n", account_id, char_id, guild_id, g->name); + return 0; + } + switch(type) { + case GMI_POSITION: // 役職 + g->member[i].position = *((int *)data); + break; + case GMI_EXP: // EXP + { + int exp, oldexp = g->member[i].exp; + exp = g->member[i].exp = *((unsigned int *)data); + g->exp += (exp - oldexp); + guild_calcinfo(g); // Lvアップ判断 + mapif_guild_basicinfochanged(guild_id, GBI_EXP, &g->exp, 4); + } + break; + default: + printf("int_guild: GuildMemberInfoChange: Unknown type %d\n", type); + break; + } + mapif_guild_memberinfochanged(guild_id, account_id, char_id, type, data, len); + + return 0; +} + +// ギルド役職名変更要求 +int mapif_parse_GuildPosition(int fd, int guild_id, int idx, struct guild_position *p) { + struct guild *g = numdb_search(guild_db, guild_id); + + if (g == NULL || idx < 0 || idx >= MAX_GUILDPOSITION) { + return 0; + } + memcpy(&g->position[idx], p, sizeof(struct guild_position)); + mapif_guild_position(g, idx); + printf("int_guild: position changed %d\n", idx); + + return 0; +} + +// ギルドスキルアップ要求 +int mapif_parse_GuildSkillUp(int fd, int guild_id, int skill_num, int account_id) { + struct guild *g = numdb_search(guild_db, guild_id); + int idx = skill_num - 10000; + + if (g == NULL || skill_num < 10000) + return 0; + + if (g->skill_point > 0 && g->skill[idx].id > 0 && g->skill[idx].lv < 10) { + g->skill[idx].lv++; + g->skill_point--; + if (guild_calcinfo(g) == 0) + mapif_guild_info(-1, g); + mapif_guild_skillupack(guild_id, skill_num, account_id); + printf("int_guild: skill %d up\n", skill_num); + } + + return 0; +} + +// ギルド同盟要求 +int mapif_parse_GuildAlliance(int fd, int guild_id1, int guild_id2, int account_id1, int account_id2, int flag) { + struct guild *g[2]; + int j, i; + + g[0] = numdb_search(guild_db, guild_id1); + g[1] = numdb_search(guild_db, guild_id2); + if (g[0] == NULL || g[1] == NULL) + return 0; + + if (!(flag & 0x8)) { + for(i = 0; i < 2 - (flag & 1); i++) { + for(j = 0; j < MAX_GUILDALLIANCE; j++) + if (g[i]->alliance[j].guild_id == 0) { + g[i]->alliance[j].guild_id = g[1-i]->guild_id; + memcpy(g[i]->alliance[j].name, g[1-i]->name, 24); + g[i]->alliance[j].opposition = flag & 1; + break; + } + } + } else { // 関係解消 + for(i = 0; i < 2 - (flag & 1); i++) { + for(j = 0; j < MAX_GUILDALLIANCE; j++) + if (g[i]->alliance[j].guild_id == g[1-i]->guild_id && g[i]->alliance[j].opposition == (flag & 1)) { + g[i]->alliance[j].guild_id = 0; + break; + } + } + } + mapif_guild_alliance(guild_id1, guild_id2, account_id1, account_id2, flag, g[0]->name, g[1]->name); + + return 0; +} + +// ギルド告知変更要求 +int mapif_parse_GuildNotice(int fd, int guild_id, const char *mes1, const char *mes2) { + struct guild *g; + + g = numdb_search(guild_db, guild_id); + if (g == NULL) + return 0; + memcpy(g->mes1, mes1, 60); + memcpy(g->mes2, mes2, 120); + + return mapif_guild_notice(g); +} + +// ギルドエンブレム変更要求 +int mapif_parse_GuildEmblem(int fd, int len, int guild_id, int dummy, const char *data) { + struct guild *g; + + g = numdb_search(guild_db, guild_id); + if (g == NULL) + return 0; + memcpy(g->emblem_data, data, len); + g->emblem_len = len; + g->emblem_id++; + + return mapif_guild_emblem(g); +} + +int mapif_parse_GuildCastleDataLoad(int fd, int castle_id, int index) { + struct guild_castle *gc = numdb_search(castle_db, castle_id); + + if (gc == NULL) { + return mapif_guild_castle_dataload(castle_id, 0, 0); + } + switch(index) { + case 1: return mapif_guild_castle_dataload(gc->castle_id, index, gc->guild_id); + case 2: return mapif_guild_castle_dataload(gc->castle_id, index, gc->economy); + case 3: return mapif_guild_castle_dataload(gc->castle_id, index, gc->defense); + case 4: return mapif_guild_castle_dataload(gc->castle_id, index, gc->triggerE); + case 5: return mapif_guild_castle_dataload(gc->castle_id, index, gc->triggerD); + case 6: return mapif_guild_castle_dataload(gc->castle_id, index, gc->nextTime); + case 7: return mapif_guild_castle_dataload(gc->castle_id, index, gc->payTime); + case 8: return mapif_guild_castle_dataload(gc->castle_id, index, gc->createTime); + case 9: return mapif_guild_castle_dataload(gc->castle_id, index, gc->visibleC); + case 10: return mapif_guild_castle_dataload(gc->castle_id, index, gc->visibleG0); + case 11: return mapif_guild_castle_dataload(gc->castle_id, index, gc->visibleG1); + case 12: return mapif_guild_castle_dataload(gc->castle_id, index, gc->visibleG2); + case 13: return mapif_guild_castle_dataload(gc->castle_id, index, gc->visibleG3); + case 14: return mapif_guild_castle_dataload(gc->castle_id, index, gc->visibleG4); + case 15: return mapif_guild_castle_dataload(gc->castle_id, index, gc->visibleG5); + case 16: return mapif_guild_castle_dataload(gc->castle_id, index, gc->visibleG6); + case 17: return mapif_guild_castle_dataload(gc->castle_id, index, gc->visibleG7); + case 18: return mapif_guild_castle_dataload(gc->castle_id, index, gc->Ghp0); // guardian HP [Valaris] + case 19: return mapif_guild_castle_dataload(gc->castle_id, index, gc->Ghp1); + case 20: return mapif_guild_castle_dataload(gc->castle_id, index, gc->Ghp2); + case 21: return mapif_guild_castle_dataload(gc->castle_id, index, gc->Ghp3); + case 22: return mapif_guild_castle_dataload(gc->castle_id, index, gc->Ghp4); + case 23: return mapif_guild_castle_dataload(gc->castle_id, index, gc->Ghp5); + case 24: return mapif_guild_castle_dataload(gc->castle_id, index, gc->Ghp6); + case 25: return mapif_guild_castle_dataload(gc->castle_id, index, gc->Ghp7); // end additions [Valaris] + + default: + printf("mapif_parse_GuildCastleDataLoad ERROR!! (Not found index=%d)\n", index); + return 0; + } + + return 0; +} + +int mapif_parse_GuildCastleDataSave(int fd, int castle_id, int index, int value) { + struct guild_castle *gc=numdb_search(castle_db, castle_id); + + if (gc == NULL) { + return mapif_guild_castle_datasave(castle_id, index, value); + } + switch(index) { + case 1: + if (gc->guild_id != value) { + int gid = (value) ? value : gc->guild_id; + struct guild *g = numdb_search(guild_db, gid); + inter_log("guild %s (id=%d) %s castle id=%d" RETCODE, + (g) ? g->name : "??", gid, (value) ? "occupy" : "abandon", index); + } + gc->guild_id = value; + break; + case 2: gc->economy = value; break; + case 3: gc->defense = value; break; + case 4: gc->triggerE = value; break; + case 5: gc->triggerD = value; break; + case 6: gc->nextTime = value; break; + case 7: gc->payTime = value; break; + case 8: gc->createTime = value; break; + case 9: gc->visibleC = value; break; + case 10: gc->visibleG0 = value; break; + case 11: gc->visibleG1 = value; break; + case 12: gc->visibleG2 = value; break; + case 13: gc->visibleG3 = value; break; + case 14: gc->visibleG4 = value; break; + case 15: gc->visibleG5 = value; break; + case 16: gc->visibleG6 = value; break; + case 17: gc->visibleG7 = value; break; + case 18: gc->Ghp0 = value; break; // guardian HP [Valaris] + case 19: gc->Ghp1 = value; break; + case 20: gc->Ghp2 = value; break; + case 21: gc->Ghp3 = value; break; + case 22: gc->Ghp4 = value; break; + case 23: gc->Ghp5 = value; break; + case 24: gc->Ghp6 = value; break; + case 25: gc->Ghp7 = value; break; // end additions [Valaris] + default: + printf("mapif_parse_GuildCastleDataSave ERROR!! (Not found index=%d)\n", index); + return 0; + } + + return mapif_guild_castle_datasave(gc->castle_id, index, value); +} + +// ギルドチェック要求 +int mapif_parse_GuildCheck(int fd, int guild_id, int account_id, int char_id) { + return guild_check_conflict(guild_id, account_id, char_id); +} + +// map server からの通信 +// ・1パケットのみ解析すること +// ・パケット長データはinter.cにセットしておくこと +// ・パケット長チェックや、RFIFOSKIPは呼び出し元で行われるので行ってはならない +// ・エラーなら0(false)、そうでないなら1(true)をかえさなければならない +int inter_guild_parse_frommap(int fd) { + switch(RFIFOW(fd,0)) { + case 0x3030: mapif_parse_CreateGuild(fd, RFIFOL(fd,4), RFIFOP(fd,8), (struct guild_member *)RFIFOP(fd,32)); break; + case 0x3031: mapif_parse_GuildInfo(fd, RFIFOL(fd,2)); break; + case 0x3032: mapif_parse_GuildAddMember(fd, RFIFOL(fd,4), (struct guild_member *)RFIFOP(fd,8)); break; + case 0x3034: mapif_parse_GuildLeave(fd, RFIFOL(fd,2), RFIFOL(fd,6), RFIFOL(fd,10), RFIFOB(fd,14), RFIFOP(fd,15)); break; + case 0x3035: mapif_parse_GuildChangeMemberInfoShort(fd, RFIFOL(fd,2), RFIFOL(fd,6), RFIFOL(fd,10), RFIFOB(fd,14), RFIFOW(fd,15), RFIFOW(fd,17)); break; + case 0x3036: mapif_parse_BreakGuild(fd, RFIFOL(fd,2)); break; + case 0x3037: mapif_parse_GuildMessage(fd, RFIFOL(fd,4), RFIFOL(fd,8), RFIFOP(fd,12), RFIFOW(fd,2)-12); break; + case 0x3038: mapif_parse_GuildCheck(fd, RFIFOL(fd,2), RFIFOL(fd,6), RFIFOL(fd,10)); break; + case 0x3039: mapif_parse_GuildBasicInfoChange(fd, RFIFOL(fd,4), RFIFOW(fd,8), RFIFOP(fd,10), RFIFOW(fd,2)-10); break; + case 0x303A: mapif_parse_GuildMemberInfoChange(fd, RFIFOL(fd,4), RFIFOL(fd,8), RFIFOL(fd,12), RFIFOW(fd,16), RFIFOP(fd,18), RFIFOW(fd,2)-18); break; + case 0x303B: mapif_parse_GuildPosition(fd, RFIFOL(fd,4), RFIFOL(fd,8), (struct guild_position *)RFIFOP(fd,12)); break; + case 0x303C: mapif_parse_GuildSkillUp(fd, RFIFOL(fd,2), RFIFOL(fd,6), RFIFOL(fd,10)); break; + case 0x303D: mapif_parse_GuildAlliance(fd, RFIFOL(fd,2), RFIFOL(fd,6), RFIFOL(fd,10), RFIFOL(fd,14), RFIFOB(fd,18)); break; + case 0x303E: mapif_parse_GuildNotice(fd, RFIFOL(fd,2), RFIFOP(fd,6), RFIFOP(fd,66)); break; + case 0x303F: mapif_parse_GuildEmblem(fd, RFIFOW(fd,2)-12, RFIFOL(fd,4), RFIFOL(fd,8), RFIFOP(fd,12)); break; + case 0x3040: mapif_parse_GuildCastleDataLoad(fd, RFIFOW(fd,2), RFIFOB(fd,4)); break; + case 0x3041: mapif_parse_GuildCastleDataSave(fd, RFIFOW(fd,2), RFIFOB(fd,4), RFIFOL(fd,5)); break; + + default: + return 0; + } + + return 1; +} + +// マップサーバーの接続時処理 +int inter_guild_mapif_init(int fd) { + return mapif_guild_castle_alldataload(fd); +} + +// サーバーから脱退要求(キャラ削除用) +int inter_guild_leave(int guild_id, int account_id, int char_id) { + return mapif_parse_GuildLeave(-1, guild_id, account_id, char_id, 0, "**サーバー命令**"); +} diff --git a/src/char/int_guild.h b/src/char/int_guild.h new file mode 100644 index 0000000..555f5e1 --- /dev/null +++ b/src/char/int_guild.h @@ -0,0 +1,16 @@ +// $Id: int_guild.h,v 1.1.1.1 2004/09/10 17:26:51 MagicalTux Exp $ +#ifndef _INT_GUILD_H_ +#define _INT_GUILD_H_ + +int inter_guild_init(); +int inter_guild_save(); +int inter_guild_parse_frommap(int fd); +struct guild *inter_guild_search(int guild_id); +int inter_guild_mapif_init(int fd); + +int inter_guild_leave(int guild_id,int account_id,int char_id); + +extern char guild_txt[1024]; +extern char castle_txt[1024]; + +#endif diff --git a/src/char/int_party.c b/src/char/int_party.c new file mode 100644 index 0000000..0fd58fa --- /dev/null +++ b/src/char/int_party.c @@ -0,0 +1,595 @@ +// $Id: int_party.c,v 1.1.1.1 2004/09/10 17:26:51 MagicalTux Exp $ +#include "inter.h" +#include "int_party.h" +#include "mmo.h" +#include "char.h" +#include "socket.h" +#include "db.h" +#include "lock.h" +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +char party_txt[1024] = "save/party.txt"; + +static struct dbt *party_db; +static int party_newid = 100; + +int mapif_party_broken(int party_id, int flag); +int party_check_empty(struct party *p); +int mapif_parse_PartyLeave(int fd, int party_id, int account_id); + +// パーティデータの文字列への変換 +int inter_party_tostr(char *str, struct party *p) { + int i, len; + + len = sprintf(str, "%d\t%s\t%d,%d\t", p->party_id, p->name, p->exp, p->item); + for(i = 0; i < MAX_PARTY; i++) { + struct party_member *m = &p->member[i]; + len += sprintf(str + len, "%d,%d\t%s\t", m->account_id, m->leader, ((m->account_id > 0) ? m->name : "NoMember")); + } + + return 0; +} + +// パーティデータの文字列からの変換 +int inter_party_fromstr(char *str, struct party *p) { + int i, j; + int tmp_int[16]; + char tmp_str[256]; + + memset(p, 0, sizeof(struct party)); + +// printf("sscanf party main info\n"); + if (sscanf(str, "%d\t%[^\t]\t%d,%d\t", &tmp_int[0], tmp_str, &tmp_int[1], &tmp_int[2]) != 4) + return 1; + + p->party_id = tmp_int[0]; + strcpy(p->name, tmp_str); + p->exp = tmp_int[1]; + p->item = tmp_int[2]; +// printf("%d [%s] %d %d\n", tmp_int[0], tmp_str[0], tmp_int[1], tmp_int[2]); + + for(j = 0; j < 3 && str != NULL; j++) + str = strchr(str + 1, '\t'); + + for(i = 0; i < MAX_PARTY; i++) { + struct party_member *m = &p->member[i]; + if (str == NULL) + return 1; +// printf("sscanf party member info %d\n", i); + + if (sscanf(str + 1, "%d,%d\t%[^\t]\t", &tmp_int[0], &tmp_int[1], tmp_str) != 3) + return 1; + + m->account_id = tmp_int[0]; + m->leader = tmp_int[1]; + strncpy(m->name, tmp_str, sizeof(m->name)); +// printf(" %d %d [%s]\n", tmp_int[0], tmp_int[1], tmp_str); + + for(j = 0; j < 2 && str != NULL; j++) + str = strchr(str + 1, '\t'); + } + + return 0; +} + +// パーティデータのロード +int inter_party_init() { + char line[8192]; + struct party *p; + FILE *fp; + int c = 0; + int i, j; + + party_db = numdb_init(); + + if ((fp = fopen(party_txt, "r")) == NULL) + return 1; + + while(fgets(line, sizeof(line) - 1, fp)) { + j = 0; + if (sscanf(line, "%d\t%%newid%%\n%n", &i, &j) == 1 && j > 0 && party_newid <= i) { + party_newid = i; + continue; + } + + p = calloc(sizeof(struct party), 1); + if (p == NULL){ + printf("int_party: out of memory!\n"); + exit(0); + } + memset(p, 0, sizeof(struct party)); + if (inter_party_fromstr(line, p) == 0 && p->party_id > 0) { + if (p->party_id >= party_newid) + party_newid = p->party_id + 1; + numdb_insert(party_db, p->party_id, p); + party_check_empty(p); + } else { + printf("int_party: broken data [%s] line %d\n", party_txt, c + 1); + free(p); + } + c++; + } + fclose(fp); +// printf("int_party: %s read done (%d parties)\n", party_txt, c); + + return 0; +} + +// パーティーデータのセーブ用 +int inter_party_save_sub(void *key, void *data, va_list ap) { + char line[8192]; + FILE *fp; + + inter_party_tostr(line, (struct party *)data); + fp = va_arg(ap, FILE *); + fprintf(fp, "%s" RETCODE, line); + + return 0; +} + +// パーティーデータのセーブ +int inter_party_save() { + FILE *fp; + int lock; + + if ((fp = lock_fopen(party_txt, &lock)) == NULL) { + printf("int_party: cant write [%s] !!! data is lost !!!\n", party_txt); + return 1; + } + numdb_foreach(party_db, inter_party_save_sub, fp); +// fprintf(fp, "%d\t%%newid%%\n", party_newid); + lock_fclose(fp,party_txt, &lock); +// printf("int_party: %s saved.\n", party_txt); + + return 0; +} + +// パーティ名検索用 +int search_partyname_sub(void *key,void *data,va_list ap) { + struct party *p = (struct party *)data,**dst; + char *str; + + str = va_arg(ap, char *); + dst = va_arg(ap, struct party **); + if (strcmpi(p->name, str) == 0) + *dst = p; + + return 0; +} + +// パーティ名検索 +struct party* search_partyname(char *str) { + struct party *p = NULL; + numdb_foreach(party_db, search_partyname_sub, str, &p); + + return p; +} + +// EXP公平分配できるかチェック +int party_check_exp_share(struct party *p) { + int i; + int maxlv = 0, minlv = 0x7fffffff; + + for(i = 0; i < MAX_PARTY; i++) { + int lv = p->member[i].lv; + if (p->member[i].online) { + if (lv < minlv) + minlv = lv; + if (maxlv < lv) + maxlv = lv; + } + } + + return (maxlv == 0 || maxlv-minlv <= party_share_level); +} + +// パーティが空かどうかチェック +int party_check_empty(struct party *p) { + int i; + +// printf("party check empty %08X\n", (int)p); + for(i = 0; i < MAX_PARTY; i++) { +// printf("%d acc=%d\n", i, p->member[i].account_id); + if (p->member[i].account_id > 0) { + return 0; + } + } + // 誰もいないので解散 + mapif_party_broken(p->party_id, 0); + numdb_erase(party_db, p->party_id); + free(p); + + return 1; +} + +// キャラの競合がないかチェック用 +int party_check_conflict_sub(void *key, void *data, va_list ap) { + struct party *p = (struct party *)data; + int party_id, account_id, i; + char *nick; + + party_id=va_arg(ap, int); + account_id=va_arg(ap, int); + nick=va_arg(ap, char *); + + if (p->party_id == party_id) // 本来の所属なので問題なし + return 0; + + for(i = 0; i < MAX_PARTY; i++) { + if (p->member[i].account_id == account_id && strcmp(p->member[i].name, nick) == 0) { + // 別のパーティに偽の所属データがあるので脱退 + printf("int_party: party conflict! %d %d %d\n", account_id, party_id, p->party_id); + mapif_parse_PartyLeave(-1, p->party_id, account_id); + } + } + + return 0; +} + +// キャラの競合がないかチェック +int party_check_conflict(int party_id, int account_id, char *nick) { + numdb_foreach(party_db, party_check_conflict_sub, party_id, account_id, nick); + + return 0; +} + +//------------------------------------------------------------------- +// map serverへの通信 + +// パーティ作成可否 +int mapif_party_created(int fd,int account_id, struct party *p) { + WFIFOW(fd,0) = 0x3820; + WFIFOL(fd,2) = account_id; + if (p != NULL) { + WFIFOB(fd,6) = 0; + WFIFOL(fd,7) = p->party_id; + memcpy(WFIFOP(fd,11), p->name, 24); + printf("int_party: created! %d %s\n", p->party_id, p->name); + } else { + WFIFOB(fd,6) = 1; + WFIFOL(fd,7) = 0; + memcpy(WFIFOP(fd,11), "error", 24); + } + WFIFOSET(fd,35); + + return 0; +} + +// パーティ情報見つからず +int mapif_party_noinfo(int fd, int party_id) { + WFIFOW(fd,0) = 0x3821; + WFIFOW(fd,2) = 8; + WFIFOL(fd,4) = party_id; + WFIFOSET(fd,8); + printf("int_party: info not found %d\n", party_id); + + return 0; +} + +// パーティ情報まとめ送り +int mapif_party_info(int fd, struct party *p) { + unsigned char buf[4 + sizeof(struct party)]; + + WBUFW(buf,0) = 0x3821; + memcpy(buf + 4, p, sizeof(struct party)); + WBUFW(buf,2) = 4 + sizeof(struct party); + if (fd < 0) + mapif_sendall(buf, WBUFW(buf,2)); + else + mapif_send(fd, buf, WBUFW(buf,2)); +// printf("int_party: info %d %s\n", p->party_id, p->name); + + return 0; +} + +// パーティメンバ追加可否 +int mapif_party_memberadded(int fd, int party_id, int account_id, int flag) { + WFIFOW(fd,0) = 0x3822; + WFIFOL(fd,2) = party_id; + WFIFOL(fd,6) = account_id; + WFIFOB(fd,10) = flag; + WFIFOSET(fd,11); + + return 0; +} + +// パーティ設定変更通知 +int mapif_party_optionchanged(int fd,struct party *p, int account_id, int flag) { + unsigned char buf[15]; + + WBUFW(buf,0) = 0x3823; + WBUFL(buf,2) = p->party_id; + WBUFL(buf,6) = account_id; + WBUFW(buf,10) = p->exp; + WBUFW(buf,12) = p->item; + WBUFB(buf,14) = flag; + if (flag == 0) + mapif_sendall(buf, 15); + else + mapif_send(fd, buf, 15); + printf("int_party: option changed %d %d %d %d %d\n", p->party_id, account_id, p->exp, p->item, flag); + + return 0; +} + +// パーティ脱退通知 +int mapif_party_leaved(int party_id,int account_id, char *name) { + unsigned char buf[34]; + + WBUFW(buf,0) = 0x3824; + WBUFL(buf,2) = party_id; + WBUFL(buf,6) = account_id; + memcpy(WBUFP(buf,10), name, 24); + mapif_sendall(buf, 34); + printf("int_party: party leaved %d %d %s\n", party_id, account_id, name); + + return 0; +} + +// パーティマップ更新通知 +int mapif_party_membermoved(struct party *p, int idx) { + unsigned char buf[29]; + + WBUFW(buf,0) = 0x3825; + WBUFL(buf,2) = p->party_id; + WBUFL(buf,6) = p->member[idx].account_id; + memcpy(WBUFP(buf,10), p->member[idx].map, 16); + WBUFB(buf,26) = p->member[idx].online; + WBUFW(buf,27) = p->member[idx].lv; + mapif_sendall(buf, 29); + + return 0; +} + +// パーティ解散通知 +int mapif_party_broken(int party_id, int flag) { + unsigned char buf[7]; + WBUFW(buf,0) = 0x3826; + WBUFL(buf,2) = party_id; + WBUFB(buf,6) = flag; + mapif_sendall(buf, 7); + printf("int_party: broken %d\n", party_id); + + return 0; +} + +// パーティ内発言 +int mapif_party_message(int party_id, int account_id, char *mes, int len) { + unsigned char buf[len+12]; + + WBUFW(buf,0) = 0x3827; + WBUFW(buf,2) = len + 12; + WBUFL(buf,4) = party_id; + WBUFL(buf,8) = account_id; + memcpy(WBUFP(buf,12), mes, len); + mapif_sendall(buf,len + 12); + + return 0; +} + +//------------------------------------------------------------------- +// map serverからの通信 + + +// パーティ +int mapif_parse_CreateParty(int fd, int account_id, char *name, char *nick, char *map, int lv) { + struct party *p; + int i; + + for(i = 0; i < 24 && name[i]; i++) { + if (!(name[i] & 0xe0) || name[i] == 0x7f) { + printf("int_party: illegal party name [%s]\n", name); + mapif_party_created(fd, account_id, NULL); + return 0; + } + } + + if ((p = search_partyname(name)) != NULL) { + printf("int_party: same name party exists [%s]\n", name); + mapif_party_created(fd, account_id, NULL); + return 0; + } + p = calloc(sizeof(struct party), 1); + if (p == NULL) { + printf("int_party: out of memory !\n"); + mapif_party_created(fd,account_id,NULL); + return 0; + } + memset(p, 0, sizeof(struct party)); + p->party_id = party_newid++; + memcpy(p->name, name, 24); + p->exp = 0; + p->item = 0; + p->member[0].account_id = account_id; + memcpy(p->member[0].name, nick, 24); + memcpy(p->member[0].map, map, 16); + p->member[0].leader = 1; + p->member[0].online = 1; + p->member[0].lv = lv; + + numdb_insert(party_db, p->party_id, p); + + mapif_party_created(fd, account_id, p); + mapif_party_info(fd, p); + + return 0; +} + +// パーティ情報要求 +int mapif_parse_PartyInfo(int fd, int party_id) { + struct party *p; + + p = numdb_search(party_db, party_id); + if (p != NULL) + mapif_party_info(fd, p); + else + mapif_party_noinfo(fd, party_id); + + return 0; +} + +// パーティ追加要求 +int mapif_parse_PartyAddMember(int fd, int party_id, int account_id, char *nick, char *map, int lv) { + struct party *p; + int i; + + p = numdb_search(party_db, party_id); + if (p == NULL) { + mapif_party_memberadded(fd, party_id, account_id, 1); + return 0; + } + + for(i = 0; i < MAX_PARTY; i++) { + if (p->member[i].account_id == 0) { + int flag = 0; + + p->member[i].account_id = account_id; + memcpy(p->member[i].name, nick, 24); + memcpy(p->member[i].map, map, 16); + p->member[i].leader = 0; + p->member[i].online = 1; + p->member[i].lv = lv; + mapif_party_memberadded(fd, party_id, account_id, 0); + mapif_party_info(-1, p); + + if (p->exp > 0 && !party_check_exp_share(p)) { + p->exp = 0; + flag = 0x01; + } + if (flag) + mapif_party_optionchanged(fd, p, 0, 0); + return 0; + } + } + mapif_party_memberadded(fd, party_id, account_id, 1); + + return 0; +} + +// パーティー設定変更要求 +int mapif_parse_PartyChangeOption(int fd, int party_id, int account_id, int exp, int item) { + struct party *p; + int flag = 0; + + p = numdb_search(party_db, party_id); + if (p == NULL) + return 0; + + p->exp = exp; + if (exp>0 && !party_check_exp_share(p)) { + flag |= 0x01; + p->exp = 0; + } + + p->item = item; + + mapif_party_optionchanged(fd, p, account_id, flag); + return 0; +} + +// パーティ脱退要求 +int mapif_parse_PartyLeave(int fd, int party_id, int account_id) { + struct party *p; + int i; + + p = numdb_search(party_db, party_id); + if (p != NULL) { + for(i = 0; i < MAX_PARTY; i++) { + if (p->member[i].account_id == account_id) { + mapif_party_leaved(party_id, account_id, p->member[i].name); + + memset(&p->member[i], 0, sizeof(struct party_member)); + if (party_check_empty(p) == 0) + mapif_party_info(-1, p);// まだ人がいるのでデータ送信 + return 0; + } + } + } + + return 0; +} + +// パーティマップ更新要求 +int mapif_parse_PartyChangeMap(int fd, int party_id, int account_id, char *map, int online, int lv) { + struct party *p; + int i; + + p = numdb_search(party_db, party_id); + if (p == NULL) + return 0; + + for(i = 0; i < MAX_PARTY; i++) { + if (p->member[i].account_id == account_id) { + int flag = 0; + + memcpy(p->member[i].map, map, 16); + p->member[i].online = online; + p->member[i].lv = lv; + mapif_party_membermoved(p, i); + + if (p->exp > 0 && !party_check_exp_share(p)) { + p->exp = 0; + flag = 1; + } + if (flag) + mapif_party_optionchanged(fd, p, 0, 0); + break; + } + } + + return 0; +} + +// パーティ解散要求 +int mapif_parse_BreakParty(int fd, int party_id) { + struct party *p; + + p = numdb_search(party_db, party_id); + if (p == NULL) + return 0; + + numdb_erase(party_db, party_id); + mapif_party_broken(fd, party_id); + + return 0; +} + +// パーティメッセージ送信 +int mapif_parse_PartyMessage(int fd, int party_id, int account_id, char *mes, int len) { + return mapif_party_message(party_id, account_id, mes, len); +} +// パーティチェック要求 +int mapif_parse_PartyCheck(int fd, int party_id, int account_id, char *nick) { + return party_check_conflict(party_id, account_id, nick); +} + +// map server からの通信 +// ・1パケットのみ解析すること +// ・パケット長データはinter.cにセットしておくこと +// ・パケット長チェックや、RFIFOSKIPは呼び出し元で行われるので行ってはならない +// ・エラーなら0(false)、そうでないなら1(true)をかえさなければならない +int inter_party_parse_frommap(int fd) { + switch(RFIFOW(fd,0)) { + case 0x3020: mapif_parse_CreateParty(fd, RFIFOL(fd,2), RFIFOP(fd,6), RFIFOP(fd,30), RFIFOP(fd,54), RFIFOW(fd,70)); break; + case 0x3021: mapif_parse_PartyInfo(fd, RFIFOL(fd,2)); break; + case 0x3022: mapif_parse_PartyAddMember(fd, RFIFOL(fd,2), RFIFOL(fd,6), RFIFOP(fd,10), RFIFOP(fd,34), RFIFOW(fd,50)); break; + case 0x3023: mapif_parse_PartyChangeOption(fd, RFIFOL(fd,2), RFIFOL(fd,6), RFIFOW(fd,10), RFIFOW(fd,12)); break; + case 0x3024: mapif_parse_PartyLeave(fd, RFIFOL(fd,2), RFIFOL(fd,6)); break; + case 0x3025: mapif_parse_PartyChangeMap(fd, RFIFOL(fd,2), RFIFOL(fd,6), RFIFOP(fd,10), RFIFOB(fd,26), RFIFOW(fd,27)); break; + case 0x3026: mapif_parse_BreakParty(fd, RFIFOL(fd,2)); break; + case 0x3027: mapif_parse_PartyMessage(fd, RFIFOL(fd,4), RFIFOL(fd,8), RFIFOP(fd,12), RFIFOW(fd,2)-12); break; + case 0x3028: mapif_parse_PartyCheck(fd, RFIFOL(fd,2), RFIFOL(fd,6), RFIFOP(fd,10)); break; + default: + return 0; + } + + return 1; +} + +// サーバーから脱退要求(キャラ削除用) +int inter_party_leave(int party_id, int account_id) { + return mapif_parse_PartyLeave(-1, party_id, account_id); +} + diff --git a/src/char/int_party.h b/src/char/int_party.h new file mode 100644 index 0000000..b265b4c --- /dev/null +++ b/src/char/int_party.h @@ -0,0 +1,14 @@ +// $Id: int_party.h,v 1.1.1.1 2004/09/10 17:26:51 MagicalTux Exp $ +#ifndef _INT_PARTY_H_ +#define _INT_PARTY_H_ + +int inter_party_init(); +int inter_party_save(); + +int inter_party_parse_frommap(int fd); + +int inter_party_leave(int party_id,int account_id); + +extern char party_txt[1024]; + +#endif diff --git a/src/char/int_pet.c b/src/char/int_pet.c new file mode 100644 index 0000000..cff1e43 --- /dev/null +++ b/src/char/int_pet.c @@ -0,0 +1,364 @@ +// $Id: int_pet.c,v 1.1.1.1 2004/09/10 17:26:51 MagicalTux Exp $ +#include "inter.h" +#include "int_pet.h" +#include "mmo.h" +#include "char.h" +#include "socket.h" +#include "db.h" +#include "lock.h" +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + + +char pet_txt[1024]="save/pet.txt"; + +static struct dbt *pet_db; +static int pet_newid = 100; + +int inter_pet_tostr(char *str,struct s_pet *p) +{ + int len; + + if(p->hungry < 0) + p->hungry = 0; + else if(p->hungry > 100) + p->hungry = 100; + if(p->intimate < 0) + p->intimate = 0; + else if(p->intimate > 1000) + p->intimate = 1000; + + len=sprintf(str,"%d,%d,%s\t%d,%d,%d,%d,%d,%d,%d,%d,%d", + p->pet_id,p->class,p->name,p->account_id,p->char_id,p->level,p->egg_id, + p->equip,p->intimate,p->hungry,p->rename_flag,p->incuvate); + + return 0; +} + +int inter_pet_fromstr(char *str,struct s_pet *p) +{ + int s; + int tmp_int[16]; + char tmp_str[256]; + + memset(p,0,sizeof(struct s_pet)); + +// printf("sscanf pet main info\n"); + s=sscanf(str,"%d,%d,%[^\t]\t%d,%d,%d,%d,%d,%d,%d,%d,%d",&tmp_int[0],&tmp_int[1],tmp_str,&tmp_int[2], + &tmp_int[3],&tmp_int[4],&tmp_int[5],&tmp_int[6],&tmp_int[7],&tmp_int[8],&tmp_int[9],&tmp_int[10]); + + if(s!=12) + return 1; + + p->pet_id = tmp_int[0]; + p->class = tmp_int[1]; + memcpy(p->name,tmp_str,24); + p->account_id = tmp_int[2]; + p->char_id = tmp_int[3]; + p->level = tmp_int[4]; + p->egg_id = tmp_int[5]; + p->equip = tmp_int[6]; + p->intimate = tmp_int[7]; + p->hungry = tmp_int[8]; + p->rename_flag = tmp_int[9]; + p->incuvate = tmp_int[10]; + + if(p->hungry < 0) + p->hungry = 0; + else if(p->hungry > 100) + p->hungry = 100; + if(p->intimate < 0) + p->intimate = 0; + else if(p->intimate > 1000) + p->intimate = 1000; + + return 0; +} + +int inter_pet_init() +{ + char line[8192]; + struct s_pet *p; + FILE *fp; + int c=0; + + pet_db=numdb_init(); + + if( (fp=fopen(pet_txt,"r"))==NULL ) + return 1; + while(fgets(line,sizeof(line),fp)){ + p=calloc(sizeof(struct s_pet), 1); + if(p==NULL){ + printf("int_pet: out of memory!\n"); + exit(0); + } + memset(p,0,sizeof(struct s_pet)); + if(inter_pet_fromstr(line,p)==0 && p->pet_id>0){ + if( p->pet_id >= pet_newid) + pet_newid=p->pet_id+1; + numdb_insert(pet_db,p->pet_id,p); + }else{ + printf("int_pet: broken data [%s] line %d\n",pet_txt,c); + free(p); + } + c++; + } + fclose(fp); +// printf("int_pet: %s read done (%d pets)\n",pet_txt,c); + return 0; +} + +int inter_pet_save_sub(void *key,void *data,va_list ap) +{ + char line[8192]; + FILE *fp; + inter_pet_tostr(line,(struct s_pet *)data); + fp=va_arg(ap,FILE *); + fprintf(fp,"%s" RETCODE,line); + return 0; +} + +int inter_pet_save() +{ + FILE *fp; + int lock; + if( (fp=lock_fopen(pet_txt,&lock))==NULL ){ + printf("int_pet: cant write [%s] !!! data is lost !!!\n",pet_txt); + return 1; + } + numdb_foreach(pet_db,inter_pet_save_sub,fp); + lock_fclose(fp,pet_txt,&lock); +// printf("int_pet: %s saved.\n",pet_txt); + return 0; +} + +int inter_pet_delete(int pet_id) +{ + struct s_pet *p; + p = numdb_search(pet_db,pet_id); + if( p == NULL) + return 1; + else { + numdb_erase(pet_db,pet_id); + printf("pet_id: %d deleted\n",pet_id); + } + return 0; +} + +int mapif_pet_created(int fd,int account_id,struct s_pet *p) +{ + WFIFOW(fd,0)=0x3880; + WFIFOL(fd,2)=account_id; + if(p!=NULL){ + WFIFOB(fd,6)=0; + WFIFOL(fd,7)=p->pet_id; + printf("int_pet: created! %d %s\n",p->pet_id,p->name); + }else{ + WFIFOB(fd,6)=1; + WFIFOL(fd,7)=0; + } + WFIFOSET(fd,11); + + return 0; +} + +int mapif_pet_info(int fd,int account_id,struct s_pet *p) +{ + WFIFOW(fd,0)=0x3881; + WFIFOW(fd,2)=sizeof(struct s_pet) + 9; + WFIFOL(fd,4)=account_id; + WFIFOB(fd,8)=0; + memcpy(WFIFOP(fd,9),p,sizeof(struct s_pet)); + WFIFOSET(fd,WFIFOW(fd,2)); + + return 0; +} + +int mapif_pet_noinfo(int fd,int account_id) +{ + WFIFOW(fd,0)=0x3881; + WFIFOW(fd,2)=sizeof(struct s_pet) + 9; + WFIFOL(fd,4)=account_id; + WFIFOB(fd,8)=1; + memset(WFIFOP(fd,9),0,sizeof(struct s_pet)); + WFIFOSET(fd,WFIFOW(fd,2)); + + return 0; +} + +int mapif_save_pet_ack(int fd,int account_id,int flag) +{ + WFIFOW(fd,0)=0x3882; + WFIFOL(fd,2)=account_id; + WFIFOB(fd,6)=flag; + WFIFOSET(fd,7); + + return 0; +} + +int mapif_delete_pet_ack(int fd,int flag) +{ + WFIFOW(fd,0)=0x3883; + WFIFOB(fd,2)=flag; + WFIFOSET(fd,3); + + return 0; +} + +int mapif_create_pet(int fd,int account_id,int char_id,short pet_class,short pet_lv,short pet_egg_id, + short pet_equip,short intimate,short hungry,char rename_flag,char incuvate,char *pet_name) +{ + struct s_pet *p; + p=malloc(sizeof(struct s_pet)); + if(p==NULL){ + printf("int_pet: out of memory !\n"); + mapif_pet_created(fd,account_id,NULL); + return 0; + } + memset(p,0,sizeof(struct s_pet)); + p->pet_id = pet_newid++; + memcpy(p->name,pet_name,24); + if(incuvate == 1) + p->account_id = p->char_id = 0; + else { + p->account_id = account_id; + p->char_id = char_id; + } + p->class = pet_class; + p->level = pet_lv; + p->egg_id = pet_egg_id; + p->equip = pet_equip; + p->intimate = intimate; + p->hungry = hungry; + p->rename_flag = rename_flag; + p->incuvate = incuvate; + + if(p->hungry < 0) + p->hungry = 0; + else if(p->hungry > 100) + p->hungry = 100; + if(p->intimate < 0) + p->intimate = 0; + else if(p->intimate > 1000) + p->intimate = 1000; + + numdb_insert(pet_db,p->pet_id,p); + + mapif_pet_created(fd,account_id,p); + + return 0; +} + +int mapif_load_pet(int fd,int account_id,int char_id,int pet_id) +{ + struct s_pet *p; + p=numdb_search(pet_db,pet_id); + if(p!=NULL) { + if(p->incuvate == 1) { + p->account_id = p->char_id = 0; + mapif_pet_info(fd,account_id,p); + } + else if(account_id == p->account_id && char_id == p->char_id) + mapif_pet_info(fd,account_id,p); + else + mapif_pet_noinfo(fd,account_id); + } + else + mapif_pet_noinfo(fd,account_id); + + return 0; +} + +int mapif_save_pet(int fd,int account_id,struct s_pet *data) +{ + struct s_pet *p; + int pet_id; + int len=RFIFOW(fd,2); + if(sizeof(struct s_pet)!=len-8) { + printf("inter pet: data size error %d %d\n",sizeof(struct s_pet),len-8); + } + else{ + pet_id = data->pet_id; + p=numdb_search(pet_db,pet_id); + if(p == NULL) { + p=malloc(sizeof(struct s_pet)); + if(p==NULL){ + printf("int_pet: out of memory !\n"); + mapif_save_pet_ack(fd,account_id,1); + return 0; + } + memset(p,0,sizeof(struct s_pet)); + p->pet_id = data->pet_id; + if(p->pet_id == 0) + data->pet_id = p->pet_id = pet_newid++; + numdb_insert(pet_db,p->pet_id,p); + } + if(data->hungry < 0) + data->hungry = 0; + else if(data->hungry > 100) + data->hungry = 100; + if(data->intimate < 0) + data->intimate = 0; + else if(data->intimate > 1000) + data->intimate = 1000; + memcpy(p,data,sizeof(struct s_pet)); + if(p->incuvate == 1) + p->account_id = p->char_id = 0; + + mapif_save_pet_ack(fd,account_id,0); + } + + return 0; +} + +int mapif_delete_pet(int fd,int pet_id) +{ + mapif_delete_pet_ack(fd,inter_pet_delete(pet_id)); + + return 0; +} + +int mapif_parse_CreatePet(int fd) +{ + mapif_create_pet(fd,RFIFOL(fd,2),RFIFOL(fd,6),RFIFOW(fd,10),RFIFOW(fd,12),RFIFOW(fd,14),RFIFOW(fd,16),RFIFOL(fd,18), + RFIFOL(fd,20),RFIFOB(fd,22),RFIFOB(fd,23),RFIFOP(fd,24)); + return 0; +} + +int mapif_parse_LoadPet(int fd) +{ + mapif_load_pet(fd,RFIFOL(fd,2),RFIFOL(fd,6),RFIFOL(fd,10)); + return 0; +} + +int mapif_parse_SavePet(int fd) +{ + mapif_save_pet(fd,RFIFOL(fd,4),(struct s_pet *)RFIFOP(fd,8)); + return 0; +} + +int mapif_parse_DeletePet(int fd) +{ + mapif_delete_pet(fd,RFIFOL(fd,2)); + return 0; +} + +// map server からの通信 +// ・1パケットのみ解析すること +// ・パケット長データはinter.cにセットしておくこと +// ・パケット長チェックや、RFIFOSKIPは呼び出し元で行われるので行ってはならない +// ・エラーなら0(false)、そうでないなら1(true)をかえさなければならない +int inter_pet_parse_frommap(int fd) +{ + switch(RFIFOW(fd,0)){ + case 0x3080: mapif_parse_CreatePet(fd); break; + case 0x3081: mapif_parse_LoadPet(fd); break; + case 0x3082: mapif_parse_SavePet(fd); break; + case 0x3083: mapif_parse_DeletePet(fd); break; + default: + return 0; + } + return 1; +} + diff --git a/src/char/int_pet.h b/src/char/int_pet.h new file mode 100644 index 0000000..993f913 --- /dev/null +++ b/src/char/int_pet.h @@ -0,0 +1,13 @@ +// $Id: int_pet.h,v 1.1.1.1 2004/09/10 17:26:51 MagicalTux Exp $ +#ifndef _INT_PET_H_ +#define _INT_PET_H_ + +int inter_pet_init(); +int inter_pet_save(); +int inter_pet_delete(int pet_id); + +int inter_pet_parse_frommap(int fd); + +extern char pet_txt[1024]; + +#endif diff --git a/src/char/int_storage.c b/src/char/int_storage.c new file mode 100644 index 0000000..8b656fc --- /dev/null +++ b/src/char/int_storage.c @@ -0,0 +1,504 @@ +// $Id: int_storage.c,v 1.1.1.1 2004/09/10 17:26:51 MagicalTux Exp $ +#include "inter.h" +#include "int_storage.h" +#include "int_pet.h" +#include "int_guild.h" +#include "mmo.h" +#include "char.h" +#include "socket.h" +#include "db.h" +#include "lock.h" + +#include <string.h> +#include <stdlib.h> + +// ファイル名のデフォルト +// inter_config_read()で再設定される +char storage_txt[1024]="save/storage.txt"; +char guild_storage_txt[1024]="save/g_storage.txt"; + +static struct dbt *storage_db; +static struct dbt *guild_storage_db; + +// 倉庫データを文字列に変換 +int storage_tostr(char *str,struct storage *p) +{ + int i,f=0; + char *str_p = str; + str_p += sprintf(str_p,"%d,%d\t",p->account_id,p->storage_amount); + + for(i=0;i<MAX_STORAGE;i++) + if( (p->storage[i].nameid) && (p->storage[i].amount) ){ + str_p += sprintf(str_p,"%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d ", + p->storage[i].id,p->storage[i].nameid,p->storage[i].amount,p->storage[i].equip, + p->storage[i].identify,p->storage[i].refine,p->storage[i].attribute, + p->storage[i].card[0],p->storage[i].card[1],p->storage[i].card[2],p->storage[i].card[3],p->storage[i].broken); + f++; + } + + *(str_p++)='\t'; + + *str_p='\0'; + if(!f) + str[0]=0; + return 0; +} + +// 文字列を倉庫データに変換 +int storage_fromstr(char *str,struct storage *p) +{ + int tmp_int[256]; + int set,next,len,i; + + set=sscanf(str,"%d,%d%n",&tmp_int[0],&tmp_int[1],&next); + p->storage_amount=tmp_int[1]; + + if(set!=2) + return 1; + if(str[next]=='\n' || str[next]=='\r') + return 0; + next++; + for(i=0;str[next] && str[next]!='\t';i++){ + if(sscanf(str + next, "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d%n", + &tmp_int[0], &tmp_int[1], &tmp_int[2], &tmp_int[3], + &tmp_int[4], &tmp_int[5], &tmp_int[6], + &tmp_int[7], &tmp_int[8], &tmp_int[9], &tmp_int[10], &tmp_int[11], &len) == 12) { + p->storage[i].id = tmp_int[0]; + p->storage[i].nameid = tmp_int[1]; + p->storage[i].amount = tmp_int[2]; + p->storage[i].equip = tmp_int[3]; + p->storage[i].identify = tmp_int[4]; + p->storage[i].refine = tmp_int[5]; + p->storage[i].attribute = tmp_int[6]; + p->storage[i].card[0] = tmp_int[7]; + p->storage[i].card[1] = tmp_int[8]; + p->storage[i].card[2] = tmp_int[9]; + p->storage[i].card[3] = tmp_int[10]; + p->storage[i].broken = tmp_int[11]; + next += len; + if (str[next] == ' ') + next++; + } + + else if(sscanf(str + next, "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d%n", + &tmp_int[0], &tmp_int[1], &tmp_int[2], &tmp_int[3], + &tmp_int[4], &tmp_int[5], &tmp_int[6], + &tmp_int[7], &tmp_int[8], &tmp_int[9], &tmp_int[10], &len) == 11) { + p->storage[i].id = tmp_int[0]; + p->storage[i].nameid = tmp_int[1]; + p->storage[i].amount = tmp_int[2]; + p->storage[i].equip = tmp_int[3]; + p->storage[i].identify = tmp_int[4]; + p->storage[i].refine = tmp_int[5]; + p->storage[i].attribute = tmp_int[6]; + p->storage[i].card[0] = tmp_int[7]; + p->storage[i].card[1] = tmp_int[8]; + p->storage[i].card[2] = tmp_int[9]; + p->storage[i].card[3] = tmp_int[10]; + p->storage[i].broken = 0; + next += len; + if (str[next] == ' ') + next++; + } + + else return 1; + } + return 0; +} + +int guild_storage_tostr(char *str,struct guild_storage *p) +{ + int i,f=0; + char *str_p = str; + str_p+=sprintf(str,"%d,%d\t",p->guild_id,p->storage_amount); + + for(i=0;i<MAX_GUILD_STORAGE;i++) + if( (p->storage[i].nameid) && (p->storage[i].amount) ){ + str_p += sprintf(str_p,"%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d ", + p->storage[i].id,p->storage[i].nameid,p->storage[i].amount,p->storage[i].equip, + p->storage[i].identify,p->storage[i].refine,p->storage[i].attribute, + p->storage[i].card[0],p->storage[i].card[1],p->storage[i].card[2],p->storage[i].card[3],p->storage[i].broken); + f++; + } + + *(str_p++)='\t'; + + *str_p='\0'; + if(!f) + str[0]=0; + return 0; +} + +int guild_storage_fromstr(char *str,struct guild_storage *p) +{ + int tmp_int[256]; + int set,next,len,i; + + set=sscanf(str,"%d,%d%n",&tmp_int[0],&tmp_int[1],&next); + p->storage_amount=tmp_int[1]; + + if(set!=2) + return 1; + if(str[next]=='\n' || str[next]=='\r') + return 0; + next++; + for(i=0;str[next] && str[next]!='\t';i++){ + if(sscanf(str + next, "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d%n", + &tmp_int[0], &tmp_int[1], &tmp_int[2], &tmp_int[3], + &tmp_int[4], &tmp_int[5], &tmp_int[6], + &tmp_int[7], &tmp_int[8], &tmp_int[9], &tmp_int[10], &tmp_int[11], &len) == 12) { + p->storage[i].id = tmp_int[0]; + p->storage[i].nameid = tmp_int[1]; + p->storage[i].amount = tmp_int[2]; + p->storage[i].equip = tmp_int[3]; + p->storage[i].identify = tmp_int[4]; + p->storage[i].refine = tmp_int[5]; + p->storage[i].attribute = tmp_int[6]; + p->storage[i].card[0] = tmp_int[7]; + p->storage[i].card[1] = tmp_int[8]; + p->storage[i].card[2] = tmp_int[9]; + p->storage[i].card[3] = tmp_int[10]; + p->storage[i].broken = tmp_int[11]; + next += len; + if (str[next] == ' ') + next++; + } + + else if(sscanf(str + next, "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d%n", + &tmp_int[0], &tmp_int[1], &tmp_int[2], &tmp_int[3], + &tmp_int[4], &tmp_int[5], &tmp_int[6], + &tmp_int[7], &tmp_int[8], &tmp_int[9], &tmp_int[10], &len) == 11) { + p->storage[i].id = tmp_int[0]; + p->storage[i].nameid = tmp_int[1]; + p->storage[i].amount = tmp_int[2]; + p->storage[i].equip = tmp_int[3]; + p->storage[i].identify = tmp_int[4]; + p->storage[i].refine = tmp_int[5]; + p->storage[i].attribute = tmp_int[6]; + p->storage[i].card[0] = tmp_int[7]; + p->storage[i].card[1] = tmp_int[8]; + p->storage[i].card[2] = tmp_int[9]; + p->storage[i].card[3] = tmp_int[10]; + p->storage[i].broken = 0; + next += len; + if (str[next] == ' ') + next++; + } + + else return 1; + } + return 0; +} + +// アカウントから倉庫データインデックスを得る(新規倉庫追加可能) +struct storage *account2storage(int account_id) +{ + struct storage *s; + s=numdb_search(storage_db,account_id); + if(s == NULL) { + s = calloc(sizeof(struct storage), 1); + if(s==NULL){ + printf("int_storage: out of memory!\n"); + exit(0); + } + memset(s,0,sizeof(struct storage)); + s->account_id=account_id; + numdb_insert(storage_db,s->account_id,s); + } + return s; +} + +struct guild_storage *guild2storage(int guild_id) +{ + struct guild_storage *gs = NULL; + if(inter_guild_search(guild_id) != NULL) { + gs=numdb_search(guild_storage_db,guild_id); + if(gs == NULL) { + gs = calloc(sizeof(struct guild_storage), 1); + if(gs==NULL){ + printf("int_storage: out of memory!\n"); + exit(0); + } + memset(gs,0,sizeof(struct guild_storage)); + gs->guild_id=guild_id; + numdb_insert(guild_storage_db,gs->guild_id,gs); + } + } + return gs; +} + +//--------------------------------------------------------- +// 倉庫データを読み込む +int inter_storage_init() +{ + char line[65536]; + int c=0,tmp_int; + struct storage *s; + struct guild_storage *gs; + FILE *fp; + + storage_db = numdb_init(); + + fp=fopen(storage_txt,"r"); + if(fp==NULL){ + printf("cant't read : %s\n",storage_txt); + return 1; + } + while(fgets(line,65535,fp)){ + sscanf(line,"%d",&tmp_int); + s=calloc(sizeof(struct storage), 1); + if(s==NULL){ + printf("int_storage: out of memory!\n"); + exit(0); + } + memset(s,0,sizeof(struct storage)); + s->account_id=tmp_int; + if(s->account_id > 0 && storage_fromstr(line,s) == 0) { + numdb_insert(storage_db,s->account_id,s); + } + else{ + printf("int_storage: broken data [%s] line %d\n",storage_txt,c); + free(s); + } + c++; + } + fclose(fp); + + c = 0; + guild_storage_db = numdb_init(); + + fp=fopen(guild_storage_txt,"r"); + if(fp==NULL){ + printf("cant't read : %s\n",guild_storage_txt); + return 1; + } + while(fgets(line,65535,fp)){ + sscanf(line,"%d",&tmp_int); + gs=calloc(sizeof(struct guild_storage), 1); + if(gs==NULL){ + printf("int_storage: out of memory!\n"); + exit(0); + } + memset(gs,0,sizeof(struct guild_storage)); + gs->guild_id=tmp_int; + if(gs->guild_id > 0 && guild_storage_fromstr(line,gs) == 0) { + numdb_insert(guild_storage_db,gs->guild_id,gs); + } + else{ + printf("int_storage: broken data [%s] line %d\n",guild_storage_txt,c); + free(gs); + } + c++; + } + fclose(fp); + + return 0; +} + +int inter_storage_save_sub(void *key,void *data,va_list ap) +{ + char line[65536]; + FILE *fp; + storage_tostr(line,(struct storage *)data); + fp=va_arg(ap,FILE *); + if(*line) + fprintf(fp,"%s" RETCODE,line); + return 0; +} +//--------------------------------------------------------- +// 倉庫データを書き込む +int inter_storage_save() +{ + FILE *fp; + int lock; + if( (fp=lock_fopen(storage_txt,&lock))==NULL ){ + printf("int_storage: cant write [%s] !!! data is lost !!!\n",storage_txt); + return 1; + } + numdb_foreach(storage_db,inter_storage_save_sub,fp); + lock_fclose(fp,storage_txt,&lock); +// printf("int_storage: %s saved.\n",storage_txt); + return 0; +} + +int inter_guild_storage_save_sub(void *key,void *data,va_list ap) +{ + char line[65536]; + FILE *fp; + if(inter_guild_search(((struct guild_storage *)data)->guild_id) != NULL) { + guild_storage_tostr(line,(struct guild_storage *)data); + fp=va_arg(ap,FILE *); + if(*line) + fprintf(fp,"%s" RETCODE,line); + } + return 0; +} +//--------------------------------------------------------- +// 倉庫データを書き込む +int inter_guild_storage_save() +{ + FILE *fp; + int lock; + if( (fp=lock_fopen(guild_storage_txt,&lock))==NULL ){ + printf("int_storage: cant write [%s] !!! data is lost !!!\n",guild_storage_txt); + return 1; + } + numdb_foreach(guild_storage_db,inter_guild_storage_save_sub,fp); + lock_fclose(fp,guild_storage_txt,&lock); +// printf("int_storage: %s saved.\n",guild_storage_txt); + return 0; +} + +// 倉庫データ削除 +int inter_storage_delete(int account_id) +{ + struct storage *s = numdb_search(storage_db,account_id); + if(s) { + int i; + for(i=0;i<s->storage_amount;i++){ + if(s->storage[i].card[0] == (short)0xff00) + inter_pet_delete(*((long *)(&s->storage[i].card[2]))); + } + numdb_erase(storage_db,account_id); + free(s); + } + return 0; +} + +// ギルド倉庫データ削除 +int inter_guild_storage_delete(int guild_id) +{ + struct guild_storage *gs = numdb_search(guild_storage_db,guild_id); + if(gs) { + int i; + for(i=0;i<gs->storage_amount;i++){ + if(gs->storage[i].card[0] == (short)0xff00) + inter_pet_delete(*((long *)(&gs->storage[i].card[2]))); + } + numdb_erase(guild_storage_db,guild_id); + free(gs); + } + return 0; +} + +//--------------------------------------------------------- +// map serverへの通信 + +// 倉庫データの送信 +int mapif_load_storage(int fd,int account_id) +{ + struct storage *s=account2storage(account_id); + WFIFOW(fd,0)=0x3810; + WFIFOW(fd,2)=sizeof(struct storage)+8; + WFIFOL(fd,4)=account_id; + memcpy(WFIFOP(fd,8),s,sizeof(struct storage)); + WFIFOSET(fd,WFIFOW(fd,2)); + return 0; +} +// 倉庫データ保存完了送信 +int mapif_save_storage_ack(int fd,int account_id) +{ + WFIFOW(fd,0)=0x3811; + WFIFOL(fd,2)=account_id; + WFIFOB(fd,6)=0; + WFIFOSET(fd,7); + return 0; +} + +int mapif_load_guild_storage(int fd,int account_id,int guild_id) +{ + struct guild_storage *gs=guild2storage(guild_id); + WFIFOW(fd,0)=0x3818; + if(gs) { + WFIFOW(fd,2)=sizeof(struct guild_storage)+12; + WFIFOL(fd,4)=account_id; + WFIFOL(fd,8)=guild_id; + memcpy(WFIFOP(fd,12),gs,sizeof(struct guild_storage)); + } + else { + WFIFOW(fd,2)=12; + WFIFOL(fd,4)=account_id; + WFIFOL(fd,8)=0; + } + WFIFOSET(fd,WFIFOW(fd,2)); + + return 0; +} +int mapif_save_guild_storage_ack(int fd,int account_id,int guild_id,int fail) +{ + WFIFOW(fd,0)=0x3819; + WFIFOL(fd,2)=account_id; + WFIFOL(fd,6)=guild_id; + WFIFOB(fd,10)=fail; + WFIFOSET(fd,11); + return 0; +} + +//--------------------------------------------------------- +// map serverからの通信 + +// 倉庫データ要求受信 +int mapif_parse_LoadStorage(int fd) +{ + mapif_load_storage(fd,RFIFOL(fd,2)); + return 0; +} +// 倉庫データ受信&保存 +int mapif_parse_SaveStorage(int fd) +{ + struct storage *s; + int account_id=RFIFOL(fd,4); + int len=RFIFOW(fd,2); + if(sizeof(struct storage)!=len-8){ + printf("inter storage: data size error %d %d\n",sizeof(struct storage),len-8); + } + else { + s=account2storage(account_id); + memcpy(s,RFIFOP(fd,8),sizeof(struct storage)); + mapif_save_storage_ack(fd,account_id); + } + return 0; +} + +int mapif_parse_LoadGuildStorage(int fd) +{ + mapif_load_guild_storage(fd,RFIFOL(fd,2),RFIFOL(fd,6)); + return 0; +} +int mapif_parse_SaveGuildStorage(int fd) +{ + struct guild_storage *gs; + int guild_id=RFIFOL(fd,8); + int len=RFIFOW(fd,2); + if(sizeof(struct guild_storage)!=len-12){ + printf("inter storage: data size error %d %d\n",sizeof(struct guild_storage),len-12); + } + else { + gs=guild2storage(guild_id); + if(gs) { + memcpy(gs,RFIFOP(fd,12),sizeof(struct guild_storage)); + mapif_save_guild_storage_ack(fd,RFIFOL(fd,4),guild_id,0); + } + else + mapif_save_guild_storage_ack(fd,RFIFOL(fd,4),guild_id,1); + } + return 0; +} + +// map server からの通信 +// ・1パケットのみ解析すること +// ・パケット長データはinter.cにセットしておくこと +// ・パケット長チェックや、RFIFOSKIPは呼び出し元で行われるので行ってはならない +// ・エラーなら0(false)、そうでないなら1(true)をかえさなければならない +int inter_storage_parse_frommap(int fd) +{ + switch(RFIFOW(fd,0)){ + case 0x3010: mapif_parse_LoadStorage(fd); break; + case 0x3011: mapif_parse_SaveStorage(fd); break; + case 0x3018: mapif_parse_LoadGuildStorage(fd); break; + case 0x3019: mapif_parse_SaveGuildStorage(fd); break; + default: + return 0; + } + return 1; +} diff --git a/src/char/int_storage.h b/src/char/int_storage.h new file mode 100644 index 0000000..d918f5f --- /dev/null +++ b/src/char/int_storage.h @@ -0,0 +1,16 @@ +// $Id: int_storage.h,v 1.1.1.1 2004/09/10 17:26:51 MagicalTux Exp $ +#ifndef _INT_STORAGE_H_ +#define _INT_STORAGE_H_ + +int inter_storage_init(); +int inter_storage_save(); +int inter_guild_storage_save(); +int inter_storage_delete(int account_id); +int inter_guild_storage_delete(int guild_id); + +int inter_storage_parse_frommap(int fd); + +extern char storage_txt[1024]; +extern char guild_storage_txt[1024]; + +#endif diff --git a/src/char/inter.c b/src/char/inter.c new file mode 100644 index 0000000..29ec57b --- /dev/null +++ b/src/char/inter.c @@ -0,0 +1,561 @@ +// $Id: inter.c,v 1.1.1.1 2004/09/10 17:26:51 MagicalTux Exp $ +#include "mmo.h" +#include "char.h" +#include "socket.h" +#include "timer.h" +#include "db.h" +#include <string.h> +#include <stdlib.h> + +#include "inter.h" +#include "int_party.h" +#include "int_guild.h" +#include "int_storage.h" +#include "int_pet.h" +#include "lock.h" + +#define WISDATA_TTL (60*1000) // Existence time of Wisp/page data (60 seconds) + // that is the waiting time of answers of all map-servers +#define WISDELLIST_MAX 256 // Number of elements of Wisp/page data deletion list + +char inter_log_filename[1024] = "log/inter.log"; + +char accreg_txt[1024] = "save/accreg.txt"; +static struct dbt *accreg_db = NULL; + +struct accreg { + int account_id, reg_num; + struct global_reg reg[ACCOUNT_REG_NUM]; +}; + +int party_share_level = 10; + + +// 送信パケット長リスト +int inter_send_packet_length[] = { + -1,-1,27,-1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + -1, 7, 0, 0, 0, 0, 0, 0, -1,11, 0, 0, 0, 0, 0, 0, + 35,-1,11,15, 34,29, 7,-1, 0, 0, 0, 0, 0, 0, 0, 0, + 10,-1,15, 0, 79,19, 7,-1, 0,-1,-1,-1, 14,67,186,-1, + 9, 9,-1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 11,-1, 7, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +}; +// 受信パケット長リスト +int inter_recv_packet_length[] = { + -1,-1, 7,-1, -1, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 6,-1, 0, 0, 0, 0, 0, 0, 10,-1, 0, 0, 0, 0, 0, 0, + 72, 6,52,14, 10,29, 6,-1, 34, 0, 0, 0, 0, 0, 0, 0, + -1, 6,-1, 0, 55,19, 6,-1, 14,-1,-1,-1, 14,19,186,-1, + 5, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 48,14,-1, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +}; + +struct WisData { + int id, fd, count, len; + unsigned long tick; + unsigned char src[24], dst[24], msg[1024]; +}; +static struct dbt * wis_db = NULL; +static int wis_dellist[WISDELLIST_MAX], wis_delnum; + + +//-------------------------------------------------------- + +// アカウント変数を文字列へ変換 +int inter_accreg_tostr(char *str, struct accreg *reg) { + int j; + char *p = str; + + p += sprintf(p, "%d\t", reg->account_id); + for(j = 0; j < reg->reg_num; j++) { + p += sprintf(p,"%s,%d ", reg->reg[j].str, reg->reg[j].value); + } + + return 0; +} + +// アカウント変数を文字列から変換 +int inter_accreg_fromstr(const char *str, struct accreg *reg) { + int j, v, n; + char buf[128]; + const char *p = str; + + if (sscanf(p, "%d\t%n", ®->account_id, &n ) != 1 || reg->account_id <= 0) + return 1; + + for(j = 0, p += n; j < ACCOUNT_REG_NUM; j++, p += n) { + if (sscanf(p, "%[^,],%d %n", buf, &v, &n) != 2) + break; + memcpy(reg->reg[j].str, buf, 32); + reg->reg[j].value = v; + } + reg->reg_num = j; + + return 0; +} + +// アカウント変数の読み込み +int inter_accreg_init() { + char line[8192]; + FILE *fp; + int c = 0; + struct accreg *reg; + + accreg_db = numdb_init(); + + if( (fp = fopen(accreg_txt, "r")) == NULL) + return 1; + while(fgets(line, sizeof(line)-1, fp)){ + line[sizeof(line)-1] = '\0'; + + reg = calloc(sizeof(struct accreg), 1); + if (reg == NULL) { + printf("inter: accreg: out of memory!\n"); + exit(0); + } + if (inter_accreg_fromstr(line, reg) == 0 && reg->account_id > 0) { + numdb_insert(accreg_db, reg->account_id, reg); + } else { + printf("inter: accreg: broken data [%s] line %d\n", accreg_txt, c); + free(reg); + } + c++; + } + fclose(fp); +// printf("inter: %s read done (%d)\n", accreg_txt, c); + + return 0; +} + +// アカウント変数のセーブ用 +int inter_accreg_save_sub(void *key, void *data, va_list ap) { + char line[8192]; + FILE *fp; + struct accreg *reg = (struct accreg *)data; + + if (reg->reg_num > 0) { + inter_accreg_tostr(line,reg); + fp = va_arg(ap, FILE *); + fprintf(fp, "%s" RETCODE, line); + } + + return 0; +} + +// アカウント変数のセーブ +int inter_accreg_save() { + FILE *fp; + int lock; + + if ((fp = lock_fopen(accreg_txt,&lock)) == NULL) { + printf("int_accreg: cant write [%s] !!! data is lost !!!\n", accreg_txt); + return 1; + } + numdb_foreach(accreg_db, inter_accreg_save_sub,fp); + lock_fclose(fp, accreg_txt, &lock); +// printf("inter: %s saved.\n", accreg_txt); + + return 0; +} + +//-------------------------------------------------------- + +/*========================================== + * 設定ファイルを読み込む + *------------------------------------------ + */ +int inter_config_read(const char *cfgName) { + char line[1024], w1[1024], w2[1024]; + FILE *fp; + + fp = fopen(cfgName, "r"); + if (fp == NULL) { + printf("file not found: %s\n", cfgName); + return 1; + } + while(fgets(line, sizeof(line) - 1, fp)) { + if (line[0] == '/' && line[1] == '/') + continue; + line[sizeof(line)-1] = '\0'; + + if (sscanf(line,"%[^:]: %[^\r\n]", w1, w2) != 2) + continue; + + if (strcmpi(w1, "storage_txt") == 0) { + strncpy(storage_txt, w2, sizeof(storage_txt)); + } else if (strcmpi(w1, "party_txt") == 0) { + strncpy(party_txt, w2, sizeof(party_txt)); + } else if (strcmpi(w1, "guild_txt") == 0) { + strncpy(guild_txt, w2, sizeof(guild_txt)); + } else if (strcmpi(w1, "pet_txt") == 0) { + strncpy(pet_txt, w2, sizeof(pet_txt)); + } else if (strcmpi(w1, "castle_txt") == 0) { + strncpy(castle_txt, w2, sizeof(castle_txt)); + } else if (strcmpi(w1, "accreg_txt") == 0) { + strncpy(accreg_txt, w2, sizeof(accreg_txt)); + } else if (strcmpi(w1, "guild_storage_txt") == 0) { + strncpy(guild_storage_txt, w2, sizeof(guild_storage_txt)); + } else if (strcmpi(w1, "party_share_level") == 0) { + party_share_level = atoi(w2); + if (party_share_level < 0) + party_share_level = 0; + } else if (strcmpi(w1, "inter_log_filename") == 0) { + strncpy(inter_log_filename, w2, sizeof(inter_log_filename)); + } else if (strcmpi(w1, "import") == 0) { + inter_config_read(w2); + } + } + fclose(fp); + + return 0; +} + +// ログ書き出し +int inter_log(char *fmt,...) { + FILE *logfp; + va_list ap; + + va_start(ap,fmt); + logfp = fopen(inter_log_filename, "a"); + if (logfp) { + vfprintf(logfp, fmt, ap); + fclose(logfp); + } + va_end(ap); + + return 0; +} + +// セーブ +int inter_save() { + inter_party_save(); + inter_guild_save(); + inter_storage_save(); + inter_guild_storage_save(); + inter_pet_save(); + inter_accreg_save(); + + return 0; +} + +// 初期化 +int inter_init(const char *file) { + inter_config_read(file); + + wis_db = numdb_init(); + + inter_party_init(); + inter_guild_init(); + inter_storage_init(); + inter_pet_init(); + inter_accreg_init(); + + return 0; +} + +// マップサーバー接続 +int inter_mapif_init(int fd) { + inter_guild_mapif_init(fd); + + return 0; +} + +//-------------------------------------------------------- +// sended packets to map-server + +// GMメッセージ送信 +int mapif_GMmessage(unsigned char *mes, int len) { + unsigned char buf[len]; + + WBUFW(buf,0) = 0x3800; + WBUFW(buf,2) = len; + memcpy(WBUFP(buf,4), mes, len - 4); + mapif_sendall(buf, len); +// printf("inter server: GM:%d %s\n", len, mes); + + return 0; +} + +// Wisp/page transmission to all map-server +int mapif_wis_message(struct WisData *wd) { + unsigned char buf[56 + wd->len]; + + WBUFW(buf, 0) = 0x3801; + WBUFW(buf, 2) = 56 + wd->len; + WBUFL(buf, 4) = wd->id; + memcpy(WBUFP(buf, 8), wd->src, 24); + memcpy(WBUFP(buf,32), wd->dst, 24); + memcpy(WBUFP(buf,56), wd->msg, wd->len); + wd->count = mapif_sendall(buf, WBUFW(buf,2)); + + return 0; +} + +// Wisp/page transmission result to map-server +int mapif_wis_end(struct WisData *wd, int flag) { + unsigned char buf[27]; + + WBUFW(buf, 0) = 0x3802; + memcpy(WBUFP(buf, 2), wd->src, 24); + WBUFB(buf,26) = flag; // flag: 0: success to send wisper, 1: target character is not loged in?, 2: ignored by target + mapif_send(wd->fd, buf, 27); +// printf("inter server wis_end: flag: %d\n", flag); + + return 0; +} + +// アカウント変数送信 +int mapif_account_reg(int fd, unsigned char *src) { + unsigned char buf[WBUFW(src,2)]; + + memcpy(WBUFP(buf,0),src,WBUFW(src,2)); + WBUFW(buf, 0) = 0x3804; + mapif_sendallwos(fd, buf, WBUFW(buf,2)); + + return 0; +} + +// アカウント変数要求返信 +int mapif_account_reg_reply(int fd,int account_id) { + struct accreg *reg = numdb_search(accreg_db,account_id); + + WFIFOW(fd,0) = 0x3804; + WFIFOL(fd,4) = account_id; + if (reg == NULL) { + WFIFOW(fd,2) = 8; + } else { + int j, p; + for(j = 0, p = 8; j < reg->reg_num; j++, p += 36) { + memcpy(WFIFOP(fd,p), reg->reg[j].str, 32); + WFIFOL(fd,p+32) = reg->reg[j].value; + } + WFIFOW(fd,2) = p; + } + WFIFOSET(fd,WFIFOW(fd,2)); + + return 0; +} + +//-------------------------------------------------------- + +// Existence check of WISP data +int check_ttl_wisdata_sub(void *key, void *data, va_list ap) { + unsigned long tick; + struct WisData *wd = (struct WisData *)data; + tick = va_arg(ap, unsigned long); + + if (DIFF_TICK(tick, wd->tick) > WISDATA_TTL && wis_delnum < WISDELLIST_MAX) + wis_dellist[wis_delnum++] = wd->id; + + return 0; +} + +int check_ttl_wisdata() { + unsigned long tick = gettick(); + int i; + + do { + wis_delnum = 0; + numdb_foreach(wis_db, check_ttl_wisdata_sub, tick); + for(i = 0; i < wis_delnum; i++) { + struct WisData *wd = numdb_search(wis_db, wis_dellist[i]); + printf("inter: wis data id=%d time out : from %s to %s\n", wd->id, wd->src, wd->dst); + // removed. not send information after a timeout. Just no answer for the player + //mapif_wis_end(wd, 1); // flag: 0: success to send wisper, 1: target character is not loged in?, 2: ignored by target + numdb_erase(wis_db, wd->id); + free(wd); + } + } while(wis_delnum >= WISDELLIST_MAX); + + return 0; +} + +//-------------------------------------------------------- +// received packets from map-server + +// GMメッセージ送信 +int mapif_parse_GMmessage(int fd) { + mapif_GMmessage(RFIFOP(fd,4), RFIFOW(fd,2)); + + return 0; +} + +// Wisp/page request to send +int mapif_parse_WisRequest(int fd) { + struct WisData* wd; + static int wisid = 0; + int index; + + if (RFIFOW(fd,2)-52 >= sizeof(wd->msg)) { + printf("inter: Wis message size too long.\n"); + return 0; + } else if (RFIFOW(fd,2)-52 <= 0) { // normaly, impossible, but who knows... + printf("inter: Wis message doesn't exist.\n"); + return 0; + } + + // search if character exists before to ask all map-servers + if ((index = search_character_index(RFIFOP(fd,28))) == -1) { + unsigned char buf[27]; + WBUFW(buf, 0) = 0x3802; + memcpy(WBUFP(buf, 2), RFIFOP(fd, 4), 24); + WBUFB(buf,26) = 1; // flag: 0: success to send wisper, 1: target character is not loged in?, 2: ignored by target + mapif_send(fd, buf, 27); + // Character exists. So, ask all map-servers + } else { + // to be sure of the correct name, rewrite it + memset(RFIFOP(fd,28), 0, 24); + strncpy(RFIFOP(fd,28), search_character_name(index), 24); + // if source is destination, don't ask other servers. + if (strcmp(RFIFOP(fd,4),RFIFOP(fd,28)) == 0) { + unsigned char buf[27]; + WBUFW(buf, 0) = 0x3802; + memcpy(WBUFP(buf, 2), RFIFOP(fd, 4), 24); + WBUFB(buf,26) = 1; // flag: 0: success to send wisper, 1: target character is not loged in?, 2: ignored by target + mapif_send(fd, buf, 27); + } else { + + wd = (struct WisData *)calloc(sizeof(struct WisData), 1); + if (wd == NULL){ + printf("inter: WisRequest: out of memory !\n"); + return 0; + } + + // Whether the failure of previous wisp/page transmission (timeout) + check_ttl_wisdata(); + + wd->id = ++wisid; + wd->fd = fd; + wd->len= RFIFOW(fd,2)-52; + memcpy(wd->src, RFIFOP(fd, 4), 24); + memcpy(wd->dst, RFIFOP(fd,28), 24); + memcpy(wd->msg, RFIFOP(fd,52), wd->len); + wd->tick = gettick(); + numdb_insert(wis_db, wd->id, wd); + mapif_wis_message(wd); + } + } + + return 0; +} + +// Wisp/page transmission result +int mapif_parse_WisReply(int fd) { + int id = RFIFOL(fd,2), flag = RFIFOB(fd,6); + struct WisData *wd = numdb_search(wis_db, id); + + if (wd == NULL) + return 0; // This wisp was probably suppress before, because it was timeout of because of target was found on another map-server + + if ((--wd->count) <= 0 || flag != 1) { + mapif_wis_end(wd, flag); // flag: 0: success to send wisper, 1: target character is not loged in?, 2: ignored by target + numdb_erase(wis_db, id); + free(wd); + } + + return 0; +} + +// Received wisp message from map-server for ALL gm (just copy the message and resends it to ALL map-servers) +int mapif_parse_WisToGM(int fd) { + unsigned char buf[RFIFOW(fd,2)]; // 0x3003/0x3803 <packet_len>.w <wispname>.24B <min_gm_level>.w <message>.?B + + memcpy(WBUFP(buf,0), RFIFOP(fd,0), RFIFOW(fd,2)); + WBUFW(buf, 0) = 0x3803; + mapif_sendall(buf, RFIFOW(fd,2)); + + return 0; +} + +// アカウント変数保存要求 +int mapif_parse_AccReg(int fd) { + int j, p; + struct accreg *reg = numdb_search(accreg_db, RFIFOL(fd,4)); + + if (reg == NULL) { + if ((reg = calloc(sizeof(struct accreg), 1)) == NULL) { + printf("inter: accreg: out of memory !\n"); + exit(0); + } + reg->account_id = RFIFOL(fd,4); + numdb_insert(accreg_db, RFIFOL(fd,4), reg); + } + + for(j = 0, p = 8; j < ACCOUNT_REG_NUM && p < RFIFOW(fd,2); j++, p += 36) { + memcpy(reg->reg[j].str, RFIFOP(fd,p), 32); + reg->reg[j].value = RFIFOL(fd, p + 32); + } + reg->reg_num = j; + + mapif_account_reg(fd, RFIFOP(fd,0)); // 他のMAPサーバーに送信 + + return 0; +} + +// アカウント変数送信要求 +int mapif_parse_AccRegRequest(int fd) { +// printf("mapif: accreg request\n"); + return mapif_account_reg_reply(fd, RFIFOL(fd,2)); +} + +//-------------------------------------------------------- + +// map server からの通信(1パケットのみ解析すること) +// エラーなら0(false)、処理できたなら1、 +// パケット長が足りなければ2をかえさなければならない +int inter_parse_frommap(int fd) { + int cmd = RFIFOW(fd,0); + int len = 0; + + // inter鯖管轄かを調べる + if (cmd < 0x3000 || cmd >= 0x3000 + (sizeof(inter_recv_packet_length) / sizeof(inter_recv_packet_length[0]))) + return 0; + + // パケット長を調べる + if ((len = inter_check_length(fd, inter_recv_packet_length[cmd - 0x3000])) == 0) + return 2; + + switch(cmd) { + case 0x3000: mapif_parse_GMmessage(fd); break; + case 0x3001: mapif_parse_WisRequest(fd); break; + case 0x3002: mapif_parse_WisReply(fd); break; + case 0x3003: mapif_parse_WisToGM(fd); break; + case 0x3004: mapif_parse_AccReg(fd); break; + case 0x3005: mapif_parse_AccRegRequest(fd); break; + default: + if (inter_party_parse_frommap(fd)) + break; + if (inter_guild_parse_frommap(fd)) + break; + if (inter_storage_parse_frommap(fd)) + break; + if (inter_pet_parse_frommap(fd)) + break; + return 0; + } + RFIFOSKIP(fd, len); + + return 1; +} + +// RFIFOのパケット長確認 +// 必要パケット長があればパケット長、まだ足りなければ0 +int inter_check_length(int fd, int length) { + if (length == -1) { // 可変パケット長 + if (RFIFOREST(fd) < 4) // パケット長が未着 + return 0; + length = RFIFOW(fd,2); + } + + if (RFIFOREST(fd) < length) // パケットが未着 + return 0; + + return length; +} + diff --git a/src/char/inter.h b/src/char/inter.h new file mode 100644 index 0000000..b004c9b --- /dev/null +++ b/src/char/inter.h @@ -0,0 +1,19 @@ +// $Id: inter.h,v 1.1.1.1 2004/09/10 17:26:51 MagicalTux Exp $ +#ifndef _INTER_H_ +#define _INTER_H_ + +int inter_init(const char *file); +int inter_save(); +int inter_parse_frommap(int fd); +int inter_mapif_init(int fd); + +int inter_check_length(int fd,int length); + +int inter_log(char *fmt,...); + +#define inter_cfgName "conf/inter_athena.conf" + +extern int party_share_level; +extern char inter_log_filename[1024]; + +#endif diff --git a/src/char_sql/GNUmakefile b/src/char_sql/GNUmakefile new file mode 100644 index 0000000..9c7b5e5 --- /dev/null +++ b/src/char_sql/GNUmakefile @@ -0,0 +1,20 @@ +all: char-server_sql +sql: char-server_sql + +COMMON_OBJ = ../common/core.o ../common/socket.o ../common/timer.o ../common/db.o ../common/malloc.o +COMMON_H = ../common/core.h ../common/socket.h ../common/timer.h ../common/db.h ../common/malloc.h + +char-server_sql: char.o inter.o int_party.o int_guild.o int_storage.o int_pet.o strlib.o itemdb.o $(COMMON_OBJ) + $(CC) -o ../../$@ $^ $(LIB_S) + +char.o: char.c char.h strlib.h itemdb.h +inter.o: inter.c inter.h int_party.h int_guild.h int_storage.h int_pet.h ../common/mmo.h char.h ../common/socket.h +int_party.o: int_party.c int_party.h inter.h ../common/mmo.h char.h ../common/socket.h ../common/timer.h ../common/db.h +int_guild.o: int_guild.c int_guild.h inter.h ../common/mmo.h char.h ../common/socket.h ../common/db.h +int_storage.o: int_storage.c int_storage.h char.h itemdb.h +int_pet.o: int_pet.c int_pet.h inter.h char.h ../common/mmo.h ../common/socket.h ../common/db.h +strlib.o: strlib.c strlib.h +itemdb.o: itemdb.c itemdb.h ../common/db.h ../common/mmo.h + +clean: + rm -f *.o ../../char-server_sql diff --git a/src/char_sql/Makefile b/src/char_sql/Makefile new file mode 100644 index 0000000..cefe36e --- /dev/null +++ b/src/char_sql/Makefile @@ -0,0 +1,20 @@ +all: char-server_sql
+sql: char-server_sql
+
+COMMON_OBJ = ../common/core.o ../common/socket.o ../common/timer.o ../common/db.o ../common/malloc.o
+COMMON_H = ../common/core.h ../common/socket.h ../common/timer.h ../common/db.h ../common/malloc.h
+
+char-server_sql: char.o inter.o int_party.o int_guild.o int_storage.o int_pet.o strlib.o itemdb.o $(COMMON_OBJ)
+ $(CC) -o ../../$@ $^ $(LIB_S)
+
+char.o: char.c char.h strlib.h itemdb.h
+inter.o: inter.c inter.h int_party.h int_guild.h int_storage.h int_pet.h ../common/mmo.h char.h ../common/socket.h
+int_party.o: int_party.c int_party.h inter.h ../common/mmo.h char.h ../common/socket.h ../common/timer.h ../common/db.h
+int_guild.o: int_guild.c int_guild.h inter.h ../common/mmo.h char.h ../common/socket.h ../common/db.h
+int_storage.o: int_storage.c int_storage.h char.h itemdb.h
+int_pet.o: int_pet.c int_pet.h inter.h char.h ../common/mmo.h ../common/socket.h ../common/db.h
+strlib.o: strlib.c strlib.h
+itemdb.o: itemdb.c itemdb.h ../common/db.h ../common/mmo.h
+
+clean:
+ rm -f *.o ../../char-server_sql
diff --git a/src/char_sql/char.c b/src/char_sql/char.c new file mode 100644 index 0000000..11b6a49 --- /dev/null +++ b/src/char_sql/char.c @@ -0,0 +1,2917 @@ +// $Id: char.c,v 1.16 2004/09/23 18:31:16 MouseJstr Exp $ +// original : char2.c 2003/03/14 11:58:35 Rev.1.5 +// +// original code from athena +// SQL conversion by Jioh L. Jung +// TXT 1.105 +#include <sys/types.h> + +#ifdef LCCWIN32 +#include <winsock.h> +#pragma lib <libmysql.lib> +#else +#include <sys/socket.h> +#include <netinet/in.h> +#include <sys/time.h> +#include <sys/ioctl.h> +#include <arpa/inet.h> +#include <netdb.h> +#endif + +#include "utils.h" +#include <stdio.h> +#include <stdlib.h> +#include <netinet/in.h> +#include <sys/time.h> +#include <time.h> +#include <sys/ioctl.h> +#include <unistd.h> +#include <signal.h> +#include <fcntl.h> +#include <string.h> +#include <arpa/inet.h> +#include <netdb.h> +#include <stdarg.h> + +#include "char.h" +#include "strlib.h" +#include "itemdb.h" +#include "inter.h" + +#ifdef MEMWATCH +#include "memwatch.h" +#endif + +char char_db[256] = "char"; +char cart_db[256] = "cart_inventory"; +char inventory_db[256] = "inventory"; +char charlog_db[256] = "charlog"; +char storage_db[256] = "storage"; +char interlog_db[256] = "interlog"; +char reg_db[256] = "global_reg_value"; +char skill_db[256] = "skill"; +char memo_db[256] = "memo"; +char guild_db[256] = "guild"; +char guild_alliance_db[256] = "guild_alliance"; +char guild_castle_db[256] = "guild_castle"; +char guild_expulsion_db[256] = "guild_expulsion"; +char guild_member_db[256] = "guild_member"; +char guild_position_db[256] = "guild_position"; +char guild_skill_db[256] = "guild_skill"; +char guild_storage_db[256] = "guild_storage"; +char party_db[256] = "party"; +char pet_db[256] = "pet"; +char login_db[256] = "login"; + +char login_db_account_id[32] = "account_id"; +char login_db_level[32] = "level"; + +int lowest_gm_level = 1; + +unsigned char *SQL_CONF_NAME = "conf/inter_athena.conf"; + +struct mmo_map_server server[MAX_MAP_SERVERS]; +int server_fd[MAX_MAP_SERVERS]; +int server_freezeflag[MAX_MAP_SERVERS]; // Map-server anti-freeze system. Counter. 5 ok, 4...0 freezed + +int anti_freeze_enable = 0; +int ANTI_FREEZE_INTERVAL = 6; + +int login_fd, char_fd; +char userid[24]; +char passwd[24]; +char server_name[20]; +char wisp_server_name[24] = "Server"; +char login_ip_str[128]; +int login_ip; +int login_port = 6900; +char char_ip_str[128]; +int char_ip; +int char_port = 6121; +int char_maintenance; +int char_new; +int name_ignoring_case = 0; // Allow or not identical name for characters but with a different case by [Yor] +int char_name_option = 0; // Option to know which letters/symbols are authorised in the name of a character (0: all, 1: only those in char_name_letters, 2: all EXCEPT those in char_name_letters) by [Yor] +char char_name_letters[1024] = ""; // list of letters/symbols used to authorise or not a name of a character. by [Yor] + +char lan_map_ip[128]; // Lan map ip added by kashy +int subnetmaski[4]; // Subnetmask added by kashy +char unknown_char_name[1024] = "Unknown"; + +struct char_session_data{ + int account_id,login_id1,login_id2,sex; + int found_char[9]; + char email[40]; // e-mail (default: a@a.com) by [Yor] + time_t connect_until_time; // # of seconds 1/1/1970 (timestamp): Validity limit of the account (0 = unlimited) +}; + +#define AUTH_FIFO_SIZE 256 +struct { + int account_id,char_id,login_id1,login_id2,ip,char_pos,delflag,sex; + time_t connect_until_time; // # of seconds 1/1/1970 (timestamp): Validity limit of the account (0 = unlimited) +} auth_fifo[AUTH_FIFO_SIZE]; +int auth_fifo_pos = 0; + +int check_ip_flag = 1; // It's to check IP of a player between char-server and other servers (part of anti-hacking system) + +int char_id_count = 150000; +struct mmo_charstatus *char_dat; +int char_num,char_max; +int max_connect_user = 0; +int autosave_interval = DEFAULT_AUTOSAVE_INTERVAL; +int start_zeny = 500; +int start_weapon = 1201; +int start_armor = 2301; + +// check for exit signal +// 0 is saving complete +// other is char_id +unsigned int save_flag = 0; + +// start point (you can reset point on conf file) +struct point start_point = {"new_1-1.gat", 53, 111}; + +struct gm_account *gm_account = NULL; +int GM_num = 0; + +//----------------------------------------------------- +// Function to suppress control characters in a string. +//----------------------------------------------------- +int remove_control_chars(unsigned char *str) { + int i; + int change = 0; + + for(i = 0; i < strlen(str); i++) { + if (str[i] < 32) { + str[i] = '_'; + change = 1; + } + } + + return change; +} + +//---------------------------------------------------------------------- +// Determine if an account (id) is a GM account +// and returns its level (or 0 if it isn't a GM account or if not found) +//---------------------------------------------------------------------- +// Removed since nothing GM related goes on in the char server [CLOWNISIUS] +int isGM(int account_id) { + int i; + + for(i = 0; i < GM_num; i++) + if (gm_account[i].account_id == account_id) + return gm_account[i].level; + return 0; +} + +void read_gm_account(void) { + if (gm_account != NULL) + free(gm_account); + GM_num = 0; + + sprintf(tmp_lsql, "SELECT `%s`,`%s` FROM `%s` WHERE `%s`>='%d'",login_db_account_id,login_db_level,login_db,login_db_level,lowest_gm_level); + if (mysql_query(&lmysql_handle, tmp_lsql)) { + printf("DB server Error (select %s to Memory)- %s\n",login_db,mysql_error(&lmysql_handle)); + } + lsql_res = mysql_store_result(&lmysql_handle); + if (lsql_res) { + gm_account = calloc(sizeof(struct gm_account) * mysql_num_rows(lsql_res), 1); + while ((lsql_row = mysql_fetch_row(lsql_res))) { + gm_account[GM_num].account_id = atoi(lsql_row[0]); + gm_account[GM_num].level = atoi(lsql_row[1]); + GM_num++; + } + } + + mysql_free_result(lsql_res); +} + +//===================================================================================================== +int mmo_char_tosql(int char_id, struct mmo_charstatus *p){ + int i=0,party_exist,guild_exist; + int eqcount=1; + int noteqcount=1; + char temp_str[32]; + + struct itemtemp mapitem; + if (char_id!=p->char_id) return 0; + + save_flag = p->char_id; + printf("(\033[1;32m%d\033[0m) %s \trequest save char data - ",char_id,char_dat[0].name); + + + +//for(testcount=1;testcount<50;testcount++){//---------------------------test count-------------------- +// printf("test count : %d\n", testcount); +// eqcount=1; +// noteqcount=1; +// dbeqcount=1; +// dbnoteqcount=1; +//----------------------------------------------------------------------------------------------------- + +//=========================================map inventory data > memory =============================== + //map inventory data + for(i=0;i<MAX_INVENTORY;i++){ + if(p->inventory[i].nameid>0){ + if(itemdb_isequip(p->inventory[i].nameid)==1){ + mapitem.equip[eqcount].flag=0; + mapitem.equip[eqcount].id = p->inventory[i].id; + mapitem.equip[eqcount].nameid=p->inventory[i].nameid; + mapitem.equip[eqcount].amount = p->inventory[i].amount; + mapitem.equip[eqcount].equip = p->inventory[i].equip; + mapitem.equip[eqcount].identify = p->inventory[i].identify; + mapitem.equip[eqcount].refine = p->inventory[i].refine; + mapitem.equip[eqcount].attribute = p->inventory[i].attribute; + mapitem.equip[eqcount].card[0] = p->inventory[i].card[0]; + mapitem.equip[eqcount].card[1] = p->inventory[i].card[1]; + mapitem.equip[eqcount].card[2] = p->inventory[i].card[2]; + mapitem.equip[eqcount].card[3] = p->inventory[i].card[3]; + mapitem.equip[eqcount].broken = p->inventory[i].broken; + eqcount++; + } + else if(itemdb_isequip(p->inventory[i].nameid)==0){ + mapitem.notequip[noteqcount].flag=0; + mapitem.notequip[noteqcount].id = p->inventory[i].id; + mapitem.notequip[noteqcount].nameid=p->inventory[i].nameid; + mapitem.notequip[noteqcount].amount = p->inventory[i].amount; + mapitem.notequip[noteqcount].equip = p->inventory[i].equip; + mapitem.notequip[noteqcount].identify = p->inventory[i].identify; + mapitem.notequip[noteqcount].refine = p->inventory[i].refine; + mapitem.notequip[noteqcount].attribute = p->inventory[i].attribute; + mapitem.notequip[noteqcount].card[0] = p->inventory[i].card[0]; + mapitem.notequip[noteqcount].card[1] = p->inventory[i].card[1]; + mapitem.notequip[noteqcount].card[2] = p->inventory[i].card[2]; + mapitem.notequip[noteqcount].card[3] = p->inventory[i].card[3]; + mapitem.notequip[noteqcount].broken = p->inventory[i].broken; + noteqcount++; + } + } + } + //printf("- Save item data to MySQL!\n"); + memitemdata_to_sql(mapitem, eqcount, noteqcount, p->char_id,TABLE_INVENTORY); + +//=========================================map cart data > memory ==================================== + eqcount=1; + noteqcount=1; + + //map cart data + for(i=0;i<MAX_CART;i++){ + if(p->cart[i].nameid>0){ + if(itemdb_isequip(p->cart[i].nameid)==1){ + mapitem.equip[eqcount].flag=0; + mapitem.equip[eqcount].id = p->cart[i].id; + mapitem.equip[eqcount].nameid=p->cart[i].nameid; + mapitem.equip[eqcount].amount = p->cart[i].amount; + mapitem.equip[eqcount].equip = p->cart[i].equip; + mapitem.equip[eqcount].identify = p->cart[i].identify; + mapitem.equip[eqcount].refine = p->cart[i].refine; + mapitem.equip[eqcount].attribute = p->cart[i].attribute; + mapitem.equip[eqcount].card[0] = p->cart[i].card[0]; + mapitem.equip[eqcount].card[1] = p->cart[i].card[1]; + mapitem.equip[eqcount].card[2] = p->cart[i].card[2]; + mapitem.equip[eqcount].card[3] = p->cart[i].card[3]; + mapitem.equip[eqcount].broken = p->cart[i].broken; + eqcount++; + } + else if(itemdb_isequip(p->cart[i].nameid)==0){ + mapitem.notequip[noteqcount].flag=0; + mapitem.notequip[noteqcount].id = p->cart[i].id; + mapitem.notequip[noteqcount].nameid=p->cart[i].nameid; + mapitem.notequip[noteqcount].amount = p->cart[i].amount; + mapitem.notequip[noteqcount].equip = p->cart[i].equip; + mapitem.notequip[noteqcount].identify = p->cart[i].identify; + mapitem.notequip[noteqcount].refine = p->cart[i].refine; + mapitem.notequip[noteqcount].attribute = p->cart[i].attribute; + mapitem.notequip[noteqcount].card[0] = p->cart[i].card[0]; + mapitem.notequip[noteqcount].card[1] = p->cart[i].card[1]; + mapitem.notequip[noteqcount].card[2] = p->cart[i].card[2]; + mapitem.notequip[noteqcount].card[3] = p->cart[i].card[3]; + mapitem.notequip[noteqcount].broken = p->cart[i].broken; + noteqcount++; + } + } + } + + //printf("- Save cart data to MySQL!\n"); + memitemdata_to_sql(mapitem, eqcount, noteqcount, p->char_id,TABLE_CART); + +//===================================================================================================== + +//}//---------------------------test count------------------------------ + //check party_exist + party_exist=0; + sprintf(tmp_sql, "SELECT count(*) FROM `%s` WHERE `party_id` = '%d'",party_db, p->party_id); + if (mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + sql_res = mysql_store_result(&mysql_handle); + sql_row = mysql_fetch_row(sql_res); + if (sql_row) party_exist = atoi(sql_row[0]); + mysql_free_result(sql_res); + + //check guild_exist + guild_exist=0; + sprintf(tmp_sql, "SELECT count(*) FROM `%s` WHERE `guild_id` = '%d'",guild_db, p->guild_id); + if (mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + sql_res = mysql_store_result(&mysql_handle); + sql_row = mysql_fetch_row(sql_res); + if (sql_row) guild_exist = atoi(sql_row[0]); + mysql_free_result(sql_res); + + if (guild_exist==0) p->guild_id=0; + if (party_exist==0) p->party_id=0; + + //sql query + //`char`( `char_id`,`account_id`,`char_num`,`name`,`class`,`base_level`,`job_level`,`base_exp`,`job_exp`,`zeny`, //9 + //`str`,`agi`,`vit`,`int`,`dex`,`luk`, //15 + //`max_hp`,`hp`,`max_sp`,`sp`,`status_point`,`skill_point`, //21 + //`option`,`karma`,`manner`,`party_id`,`guild_id`,`pet_id`, //27 + //`hair`,`hair_color`,`clothes_color`,`weapon`,`shield`,`head_top`,`head_mid`,`head_bottom`, //35 + //`last_map`,`last_x`,`last_y`,`save_map`,`save_x`,`save_y`) + //printf("- Save char data to MySQL!\n"); + sprintf(tmp_sql ,"UPDATE `%s` SET `class`='%d', `base_level`='%d', `job_level`='%d'," + "`base_exp`='%d', `job_exp`='%d', `zeny`='%d'," + "`max_hp`='%d',`hp`='%d',`max_sp`='%d',`sp`='%d',`status_point`='%d',`skill_point`='%d'," + "`str`='%d',`agi`='%d',`vit`='%d',`int`='%d',`dex`='%d',`luk`='%d'," + "`option`='%d',`karma`='%d',`manner`='%d',`party_id`='%d',`guild_id`='%d',`pet_id`='%d'," + "`hair`='%d',`hair_color`='%d',`clothes_color`='%d',`weapon`='%d',`shield`='%d',`head_top`='%d',`head_mid`='%d',`head_bottom`='%d'," + "`last_map`='%s',`last_x`='%d',`last_y`='%d',`save_map`='%s',`save_x`='%d',`save_y`='%d',`partner_id`='%d' WHERE `account_id`='%d' AND `char_id` = '%d'", + char_db, p->class, p->base_level, p->job_level, + p->base_exp, p->job_exp, p->zeny, + p->max_hp, p->hp, p->max_sp, p->sp, p->status_point, p->skill_point, + p->str, p->agi, p->vit, p->int_, p->dex, p->luk, + p->option, p->karma, p->manner, p->party_id, p->guild_id, p->pet_id, + p->hair, p->hair_color, p->clothes_color, + p->weapon, p->shield, p->head_top, p->head_mid, p->head_bottom, + p->last_point.map, p->last_point.x, p->last_point.y, + p->save_point.map, p->save_point.x, p->save_point.y, p->partner_id, p->account_id, p->char_id + ); + + if(mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error (update `char`)- %s\n", mysql_error(&mysql_handle)); + } + + //printf("- Save memo data to MySQL!\n"); + //`memo` (`memo_id`,`char_id`,`map`,`x`,`y`) + sprintf(tmp_sql,"DELETE FROM `%s` WHERE `char_id`='%d'",memo_db, p->char_id); + if(mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error (delete `memo`)- %s\n", mysql_error(&mysql_handle)); + } + + //insert here. + for(i=0;i<10;i++){ + if(p->memo_point[i].map[0]){ + sprintf(tmp_sql,"INSERT INTO `%s`(`char_id`,`map`,`x`,`y`) VALUES ('%d', '%s', '%d', '%d')", + memo_db, char_id, p->memo_point[i].map, p->memo_point[i].x, p->memo_point[i].y); + if(mysql_query(&mysql_handle, tmp_sql)) + printf("DB server Error (insert `memo`)- %s\n", mysql_error(&mysql_handle)); + } + } + + //printf("- Save skill data to MySQL!\n"); + //`skill` (`char_id`, `id`, `lv`) + sprintf(tmp_sql,"DELETE FROM `%s` WHERE `char_id`='%d'",skill_db, p->char_id); + if(mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error (delete `skill`)- %s\n", mysql_error(&mysql_handle)); + } + //printf("- Insert skill \n"); + //insert here. + for(i=0;i<MAX_SKILL;i++){ + if(p->skill[i].id){ + if (p->skill[i].id && p->skill[i].flag!=1) { + sprintf(tmp_sql,"INSERT delayed INTO `%s`(`char_id`, `id`, `lv`) VALUES ('%d', '%d','%d')", + skill_db, char_id, p->skill[i].id, (p->skill[i].flag==0)?p->skill[i].lv:p->skill[i].flag-2); + if(mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error (insert `skill`)- %s\n", mysql_error(&mysql_handle)); + } + } + } + } + + + //printf("- Save global_reg_value data to MySQL!\n"); + //`global_reg_value` (`char_id`, `str`, `value`) + sprintf(tmp_sql,"DELETE FROM `%s` WHERE `type`=3 AND `char_id`='%d'",reg_db, p->char_id); + if (mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error (delete `global_reg_value`)- %s\n", mysql_error(&mysql_handle)); + } + + //insert here. + for(i=0;i<p->global_reg_num;i++){ + if (p->global_reg[i].str) { + if(p->global_reg[i].value !=0){ + sprintf(tmp_sql,"INSERT INTO `%s` (`char_id`, `str`, `value`) VALUES ('%d', '%s','%d')", + reg_db, char_id, jstrescapecpy(temp_str,(unsigned char*)p->global_reg[i].str), p->global_reg[i].value); + if(mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error (insert `global_reg_value`)- %s\n", mysql_error(&mysql_handle)); + } + } + } + } + printf("saving char is done.\n"); + save_flag = 0; + + return 0; +} + +int memitemdata_to_sql(struct itemtemp mapitem, int eqcount, int noteqcount, int char_id, int tableswitch){ + //equ + int i, j; + int dbeqcount = 1; + int dbnoteqcount = 1; + struct itemtemp dbitem; + char tablename[16]; + char selectoption[16]; + + switch (tableswitch){ + case TABLE_INVENTORY: + sprintf(tablename,"%s",inventory_db); + sprintf(selectoption,"char_id"); + break; + case TABLE_CART: + sprintf(tablename,"%s",cart_db); + sprintf(selectoption,"char_id"); + break; + case TABLE_STORAGE: + sprintf(tablename,"%s",storage_db); + sprintf(selectoption,"account_id"); + break; + case TABLE_GUILD_STORAGE: + sprintf(tablename,"%s",guild_storage_db); + sprintf(selectoption,"guild_id"); + break; + } + //printf("Working Table : %s \n",tablename); + + //=======================================mysql database data > memory=============================================== + + sprintf(tmp_sql, "SELECT `id`, `nameid`, `amount`, `equip`, `identify`, `refine`, `attribute`, `card0`, `card1`, `card2`, `card3` , `broken` " + "FROM `%s` WHERE `%s`='%d'",tablename ,selectoption ,char_id); + if (mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error (select `%s` to Memory)- %s\n",tablename ,mysql_error(&mysql_handle)); + } + sql_res = mysql_store_result(&mysql_handle); + if (sql_res) { + for(i=0;(sql_row = mysql_fetch_row(sql_res));i++){ + if (itemdb_isequip(atoi(sql_row[1]))==1){ + dbitem.equip[dbeqcount].flag=0; + dbitem.equip[dbeqcount].id = atoi(sql_row[0]); + dbitem.equip[dbeqcount].nameid = atoi(sql_row[1]); + dbitem.equip[dbeqcount].amount = atoi(sql_row[2]); + dbitem.equip[dbeqcount].equip = atoi(sql_row[3]); + dbitem.equip[dbeqcount].identify = atoi(sql_row[4]); + dbitem.equip[dbeqcount].refine = atoi(sql_row[5]); + dbitem.equip[dbeqcount].attribute = atoi(sql_row[6]); + dbitem.equip[dbeqcount].card[0] = atoi(sql_row[7]); + dbitem.equip[dbeqcount].card[1] = atoi(sql_row[8]); + dbitem.equip[dbeqcount].card[2] = atoi(sql_row[9]); + dbitem.equip[dbeqcount].card[3] = atoi(sql_row[10]); + dbitem.equip[dbeqcount].broken = atoi(sql_row[11]); + dbeqcount++; + }else { + dbitem.notequip[dbnoteqcount].flag=0; + dbitem.notequip[dbnoteqcount].id = atoi(sql_row[0]); + dbitem.notequip[dbnoteqcount].nameid = atoi(sql_row[1]); + dbitem.notequip[dbnoteqcount].amount = atoi(sql_row[2]); + dbitem.notequip[dbnoteqcount].equip = atoi(sql_row[3]); + dbitem.notequip[dbnoteqcount].identify = atoi(sql_row[4]); + dbitem.notequip[dbnoteqcount].refine = atoi(sql_row[5]); + dbitem.notequip[dbnoteqcount].attribute = atoi(sql_row[6]); + dbitem.notequip[dbnoteqcount].card[0] = atoi(sql_row[7]); + dbitem.notequip[dbnoteqcount].card[1] = atoi(sql_row[8]); + dbitem.notequip[dbnoteqcount].card[2] = atoi(sql_row[9]); + dbitem.notequip[dbnoteqcount].card[3] = atoi(sql_row[10]); + dbitem.notequip[dbnoteqcount].broken = atoi(sql_row[11]); + dbnoteqcount++; + } + } + mysql_free_result(sql_res); + } + + //==============================================Memory data > SQL =============================== + //======================================Equip ITEM======================================= + if((eqcount==1) && (dbeqcount==1)){//printf("%s Equip Empty\n",tablename); + //item empty + } else { + + for(i=1;i<eqcount;i++){ + for(j=1;j<dbeqcount;j++){ + if(mapitem.equip[i].flag==1) break; + if(!(dbitem.equip[j].flag==1)){ + if(mapitem.equip[i].nameid==dbitem.equip[j].nameid){ + if ((mapitem.equip[i].equip==dbitem.equip[j].equip) && (mapitem.equip[i].identify==dbitem.equip[j].identify) && (mapitem.equip[i].amount==dbitem.equip[j].amount) && + + (mapitem.equip[i].refine==dbitem.equip[j].refine) && (mapitem.equip[i].attribute==dbitem.equip[j].attribute) && (mapitem.equip[i].card[0]==dbitem.equip[j].card[0]) && + (mapitem.equip[i].card[1]==dbitem.equip[j].card[1]) && (mapitem.equip[i].card[2]==dbitem.equip[j].card[2]) && (mapitem.equip[i].card[3]==dbitem.equip[j].card[3]) && + (mapitem.equip[i].broken == dbitem.equip[j].broken)) { + mapitem.equip[i].flag = 1; + dbitem.equip[j].flag = 1; + //printf("the same item : %d , equip : %d , i : %d , flag : %d\n", mapitem.equip[i].nameid,mapitem.equip[i].equip , i, mapitem.equip[i].flag); //DEBUG-STRING + } else { + sprintf(tmp_sql,"UPDATE `%s` SET `equip`='%d', `identify`='%d', `refine`='%d'," + "`attribute`='%d', `card0`='%d', `card1`='%d', `card2`='%d', `card3`='%d', `broken`='%d', `amount`='%d' WHERE `id`='%d' LIMIT 1", + tablename, mapitem.equip[i].equip, mapitem.equip[i].identify, mapitem.equip[i].refine,mapitem.equip[i].attribute, mapitem.equip[i].card[0], + mapitem.equip[i].card[1], mapitem.equip[i].card[2], mapitem.equip[i].card[3], mapitem.equip[i].broken, mapitem.equip[i].amount, dbitem.equip[j].id); + //printf("%s\n",tmp_sql); + if(mysql_query(&mysql_handle, tmp_sql)) + printf("DB server Error (UPdate `equ %s`)- %s\n", tablename, mysql_error(&mysql_handle)); + mapitem.equip[i].flag=1; + dbitem.equip[j].flag=1; + //printf("not the same item : %d ; i : %d ; flag : %d\n", mapitem.equip[i].nameid, i, mapitem.equip[i].flag); + } + } + } + } + } + + //printf("dbeqcount = %d\n",dbeqcount); + + for(i=1;i<dbeqcount;i++){ + //printf("dbitem.equip[i].flag = %d , dbitem.equip[i].id = %d\n",dbitem.equip[i].flag,dbitem.equip[i].id); + if (!(dbitem.equip[i].flag == 1)) { + sprintf(tmp_sql,"DELETE from `%s` where `id`='%d'",tablename , dbitem.equip[i].id); + //printf("%s", tmp_sql); + if(mysql_query(&mysql_handle, tmp_sql)) + printf("DB server Error (DELETE `equ %s`)- %s\n", tablename ,mysql_error(&mysql_handle)); + } + } + for(i=1;i<eqcount;i++){ + if(!(mapitem.equip[i].flag==1)){ + sprintf(tmp_sql,"INSERT INTO `%s`(`%s`, `nameid`, `amount`, `equip`, `identify`, `refine`, `attribute`, `card0`, `card1`, `card2`, `card3`, `broken`)" + " VALUES ( '%d','%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d')", + tablename, selectoption, char_id, mapitem.equip[i].nameid, mapitem.equip[i].amount, mapitem.equip[i].equip, mapitem.equip[i].identify, mapitem.equip[i].refine, + mapitem.equip[i].attribute, mapitem.equip[i].card[0], mapitem.equip[i].card[1], mapitem.equip[i].card[2], mapitem.equip[i].card[3], mapitem.equip[i].broken); + //printf("%s", tmp_sql); + if(mysql_query(&mysql_handle, tmp_sql)) + printf("DB server Error (INSERT `equ %s`)- %s\n",tablename ,mysql_error(&mysql_handle)); + } + } + + //======================================DEBUG================================================= + +// gettimeofday(&tv,NULL); +// strftime(tmpstr,24,"%Y-%m-%d %H:%M:%S",localtime(&(tv.tv_sec))); +// printf("\n\n"); +// printf("Working Table Name : EQU %s, Count : map %3d | db %3d \n",tablename ,eqcount ,dbeqcount); +// printf("*********************************************************************************\n"); +// printf("======================================MAP===================Char ID %10d===\n",char_id); +// printf("==flag ===name ===equip===ident===amoun===attri===card0===card1===card2===card3==\n"); +// for(j=1;j<eqcount;j++) +// printf("| %5d | %5d | %5d | %5d | %5d | %5d | %5d | %5d | %5d | %5d |\n", mapitem.equip[j].flag,mapitem.equip[j].nameid, mapitem.equip[j].equip, mapitem.equip[j].identify, mapitem.equip[j].refine,mapitem.equip[j].attribute, mapitem.equip[j].card[0], mapitem.equip[j].card[1], mapitem.equip[j].card[2], mapitem.equip[j].card[3]); +// printf("======================================DB=========================================\n"); +// printf("==flag ===name ===equip===ident===refin===attri===card0===card1===card2===card3==\n"); +// for(j=1;j<dbeqcount;j++) +// printf("| %5d | %5d | %5d | %5d | %5d | %5d | %5d | %5d | %5d | %5d |\n", dbitem.equip[j].flag ,dbitem.equip[j].nameid, dbitem.equip[j].equip, dbitem.equip[j].identify, dbitem.equip[j].amount,dbitem.equip[j].attribute, dbitem.equip[j].card[0], dbitem.equip[j].card[1], dbitem.equip[j].card[2], dbitem.equip[j].card[3]); +// printf("=================================================================================\n"); +// printf("=================================================Data Time %s===\n", tmpstr); +// printf("=================================================================================\n"); + + } + + //======================================DEBUG================================================== + + //=============================Not Equip ITEM========================================== + if((noteqcount==1) && (dbnoteqcount==1)){ + //printf("%s Not Equip Empty\n",tablename); + //item empty + } else { + + for(i=1;i<noteqcount;i++){ + for(j=1;j<dbnoteqcount;j++){ + if(mapitem.notequip[i].flag==1) break; + if(!(dbitem.notequip[j].flag==1)){ + if(mapitem.notequip[i].nameid==dbitem.notequip[j].nameid){ + if ((mapitem.notequip[i].amount==dbitem.notequip[j].amount) && (mapitem.notequip[i].equip==dbitem.notequip[j].equip) && (mapitem.notequip[i].identify==dbitem.notequip[j].identify) + && (mapitem.notequip[i].attribute==dbitem.notequip[j].attribute)) + { mapitem.notequip[i].flag=1; + dbitem.notequip[j].flag=1; + //printf("the same item : %d ; i : %d ; flag : %d\n", mapitem.notequip[i].nameid, i, mapitem.notequip[i].flag); //DEBUG-STRING + } + else{ + sprintf(tmp_sql,"UPDATE `%s` SET `amount`='%d', `equip`='%d', `identify`='%d'," + "`attribute`='%d' WHERE `%s`='%d' AND `nameid`='%d'", + tablename, mapitem.notequip[i].amount, mapitem.notequip[i].equip, mapitem.notequip[i].identify, mapitem.notequip[i].attribute, + selectoption, char_id, mapitem.notequip[i].nameid); + //printf("%s",tmp_sql); + if(mysql_query(&mysql_handle, tmp_sql)) + printf("DB server Error (UPdate `notequ %s`)- %s\n",tablename ,mysql_error(&mysql_handle)); + + mapitem.notequip[i].flag=1; + dbitem.notequip[j].flag=1; + } + } + } + } + } + + //printf("dbnoteqcount = %d\n",dbnoteqcount); + + for(i=1;i<dbnoteqcount;i++){ + //printf("dbitem.notequip[i].flag = %d , dbitem.notequip[i].id = %d\n",dbitem.notequip[i].flag,dbitem.notequip[i].id); + if(!(dbitem.notequip[i].flag==1)){ + sprintf(tmp_sql,"DELETE from `%s` where `id`='%d'", tablename, dbitem.notequip[i].id); + //printf("%s", tmp_sql); + if(mysql_query(&mysql_handle, tmp_sql)) + printf("DB server Error (DELETE `notequ %s`)- %s\n", tablename ,mysql_error(&mysql_handle)); + } + } + for(i=1;i<noteqcount;i++){ + if(!(mapitem.notequip[i].flag==1)){ + sprintf(tmp_sql,"INSERT INTO `%s`( `%s`, `nameid`, `amount`, `equip`, `identify`, `refine`, `attribute`, `card0`, `card1`, `card2`, `card3`, `broken`)" + " VALUES ('%d','%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d')", + tablename ,selectoption , char_id, mapitem.notequip[i].nameid, mapitem.notequip[i].amount, mapitem.notequip[i].equip, mapitem.notequip[i].identify, mapitem.notequip[i].refine, + mapitem.notequip[i].attribute, mapitem.notequip[i].card[0], mapitem.notequip[i].card[1], mapitem.notequip[i].card[2], mapitem.notequip[i].card[3], mapitem.equip[i].broken); + //printf("%s", tmp_sql); + if(mysql_query(&mysql_handle, tmp_sql)) + printf("DB server Error (INSERT `notequ %s`)- %s\n", tablename, mysql_error(&mysql_handle)); + } + } + + //======================================DEBUG================================================= + +// gettimeofday(&tv,NULL); +// strftime(tmpstr,24,"%Y-%m-%d %H:%M:%S",localtime(&(tv.tv_sec))); +// printf("\n\n"); +// printf("Working Table Name : Not EQU %s, Count : map %3d | db %3d \n",tablename ,noteqcount ,dbnoteqcount); +// printf("*********************************************************************************\n"); +// printf("======================================MAP===================Char ID %10d===\n",char_id); +// printf("==flag ===name ===equip===ident===refin===attri===card0===card1===card2===card3==\n"); +// for(j=1;j<noteqcount;j++) +// printf("| %5d | %5d | %5d | %5d | %5d | %5d | %5d | %5d | %5d | %5d |\n", mapitem.notequip[j].flag,mapitem.notequip[j].nameid, mapitem.notequip[j].equip, mapitem.notequip[j].identify, mapitem.notequip[j].refine,mapitem.notequip[j].attribute, mapitem.notequip[j].card[0], mapitem.notequip[j].card[1], mapitem.notequip[j].card[2], mapitem.notequip[j].card[3]); +// printf("======================================DB=========================================\n"); +// printf("==flag ===name ===equip===ident===refin===attri===card0===card1===card2===card3==\n"); +// for(j=1;j<dbnoteqcount;j++) +// printf("| %5d | %5d | %5d | %5d | %5d | %5d | %5d | %5d | %5d | %5d |\n", dbitem.notequip[j].flag ,dbitem.notequip[j].nameid, dbitem.notequip[j].equip, dbitem.notequip[j].identify, dbitem.notequip[j].refine,dbitem.notequip[j].attribute, dbitem.notequip[j].card[0], dbitem.notequip[j].card[1], dbitem.notequip[j].card[2], dbitem.notequip[j].card[3]); +// printf("=================================================================================\n"); +// printf("=================================================Data Time %s===\n", tmpstr); +// printf("=================================================================================\n"); +// + } + return 0; +} +//===================================================================================================== +int mmo_char_fromsql(int char_id, struct mmo_charstatus *p, int online){ + int i, n; + + memset(p, 0, sizeof(struct mmo_charstatus)); + + p->char_id = char_id; + printf("Loaded: "); + //`char`( `char_id`,`account_id`,`char_num`,`name`,`class`,`base_level`,`job_level`,`base_exp`,`job_exp`,`zeny`, //9 + //`str`,`agi`,`vit`,`int`,`dex`,`luk`, //15 + //`max_hp`,`hp`,`max_sp`,`sp`,`status_point`,`skill_point`, //21 + //`option`,`karma`,`manner`,`party_id`,`guild_id`,`pet_id`, //27 + //`hair`,`hair_color`,`clothes_color`,`weapon`,`shield`,`head_top`,`head_mid`,`head_bottom`, //35 + //`last_map`,`last_x`,`last_y`,`save_map`,`save_x`,`save_y`) + //splite 2 parts. cause veeeery long SQL syntax + + sprintf(tmp_sql, "SELECT `char_id`,`account_id`,`char_num`,`name`,`class`,`base_level`,`job_level`,`base_exp`,`job_exp`,`zeny`," + "`str`,`agi`,`vit`,`int`,`dex`,`luk`, `max_hp`,`hp`,`max_sp`,`sp`,`status_point`,`skill_point` FROM `%s` WHERE `char_id` = '%d'",char_db, char_id); + + if (mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error (select `char`)- %s\n", mysql_error(&mysql_handle)); + } + + sql_res = mysql_store_result(&mysql_handle); + + if (sql_res) { + sql_row = mysql_fetch_row(sql_res); + + p->char_id = char_id; + p->account_id = atoi(sql_row[1]); + p->char_num = atoi(sql_row[2]); + strcpy(p->name, sql_row[3]); + p->class = atoi(sql_row[4]); + p->base_level = atoi(sql_row[5]); + p->job_level = atoi(sql_row[6]); + p->base_exp = atoi(sql_row[7]); + p->job_exp = atoi(sql_row[8]); + p->zeny = atoi(sql_row[9]); + p->str = atoi(sql_row[10]); + p->agi = atoi(sql_row[11]); + p->vit = atoi(sql_row[12]); + p->int_ = atoi(sql_row[13]); + p->dex = atoi(sql_row[14]); + p->luk = atoi(sql_row[15]); + p->max_hp = atoi(sql_row[16]); + p->hp = atoi(sql_row[17]); + p->max_sp = atoi(sql_row[18]); + p->sp = atoi(sql_row[19]); + p->status_point = atoi(sql_row[20]); + p->skill_point = atoi(sql_row[21]); + //free mysql result. + mysql_free_result(sql_res); + } else + printf("char1 - failed\n"); //Error?! ERRRRRR WHAT THAT SAY!? + printf("(\033[1;32m%d\033[0m)\033[1;32m%s\033[0m\t[",p->char_id,p->name); + printf("char1 "); + + sprintf(tmp_sql, "SELECT `option`,`karma`,`manner`,`party_id`,`guild_id`,`pet_id`,`hair`,`hair_color`," + "`clothes_color`,`weapon`,`shield`,`head_top`,`head_mid`,`head_bottom`," + "`last_map`,`last_x`,`last_y`,`save_map`,`save_x`,`save_y`, `partner_id` FROM `%s` WHERE `char_id` = '%d'",char_db, char_id); + if (mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error (select `char2`)- %s\n", mysql_error(&mysql_handle)); + } + + sql_res = mysql_store_result(&mysql_handle); + if (sql_res) { + sql_row = mysql_fetch_row(sql_res); + + + p->option = atoi(sql_row[0]); p->karma = atoi(sql_row[1]); p->manner = atoi(sql_row[2]); + p->party_id = atoi(sql_row[3]); p->guild_id = atoi(sql_row[4]); p->pet_id = atoi(sql_row[5]); + + p->hair = atoi(sql_row[6]); p->hair_color = atoi(sql_row[7]); p->clothes_color = atoi(sql_row[8]); + p->weapon = atoi(sql_row[9]); p->shield = atoi(sql_row[10]); + p->head_top = atoi(sql_row[11]); p->head_mid = atoi(sql_row[12]); p->head_bottom = atoi(sql_row[13]); + strcpy(p->last_point.map,sql_row[14]); p->last_point.x = atoi(sql_row[15]); p->last_point.y = atoi(sql_row[16]); + strcpy(p->save_point.map,sql_row[17]); p->save_point.x = atoi(sql_row[18]); p->save_point.y = atoi(sql_row[19]); + p->partner_id = atoi(sql_row[20]); + + //free mysql result. + mysql_free_result(sql_res); + } else + printf("char2 - failed\n"); //Error?! ERRRRRR WHAT THAT SAY!? + + printf("char2 "); + + //read memo data + //`memo` (`memo_id`,`char_id`,`map`,`x`,`y`) + sprintf(tmp_sql, "SELECT `map`,`x`,`y` FROM `%s` WHERE `char_id`='%d'",memo_db, char_id); + if (mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error (select `memo`)- %s\n", mysql_error(&mysql_handle)); + } + sql_res = mysql_store_result(&mysql_handle); + + if (sql_res) { + for(i=0;(sql_row = mysql_fetch_row(sql_res));i++){ + strcpy (p->memo_point[i].map,sql_row[0]); + p->memo_point[i].x=atoi(sql_row[1]); + p->memo_point[i].y=atoi(sql_row[2]); + //i ++; + } + mysql_free_result(sql_res); + } + printf("memo "); + + //read inventory + //`inventory` (`id`,`char_id`, `nameid`, `amount`, `equip`, `identify`, `refine`, `attribute`, `card0`, `card1`, `card2`, `card3`) + sprintf(tmp_sql, "SELECT `id`, `nameid`, `amount`, `equip`, `identify`, `refine`, `attribute`, `card0`, `card1`, `card2`, `card3`, `broken` " + "FROM `%s` WHERE `char_id`='%d'",inventory_db, char_id); + if (mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error (select `inventory`)- %s\n", mysql_error(&mysql_handle)); + } + sql_res = mysql_store_result(&mysql_handle); + if (sql_res) { + for(i=0;(sql_row = mysql_fetch_row(sql_res));i++){ + p->inventory[i].id = atoi(sql_row[0]); + p->inventory[i].nameid = atoi(sql_row[1]); + p->inventory[i].amount = atoi(sql_row[2]); + p->inventory[i].equip = atoi(sql_row[3]); + p->inventory[i].identify = atoi(sql_row[4]); + p->inventory[i].refine = atoi(sql_row[5]); + p->inventory[i].attribute = atoi(sql_row[6]); + p->inventory[i].card[0] = atoi(sql_row[7]); + p->inventory[i].card[1] = atoi(sql_row[8]); + p->inventory[i].card[2] = atoi(sql_row[9]); + p->inventory[i].card[3] = atoi(sql_row[10]); + p->inventory[i].broken = atoi(sql_row[11]); + } + mysql_free_result(sql_res); + } + printf("inventory "); + + + //read cart. + //`cart_inventory` (`id`,`char_id`, `nameid`, `amount`, `equip`, `identify`, `refine`, `attribute`, `card0`, `card1`, `card2`, `card3`) + sprintf(tmp_sql, "SELECT `id`, `nameid`, `amount`, `equip`, `identify`, `refine`, `attribute`, `card0`, `card1`, `card2`, `card3`, `broken` " + "FROM `%s` WHERE `char_id`='%d'",cart_db, char_id); + if (mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error (select `cart_inventory`)- %s\n", mysql_error(&mysql_handle)); + } + sql_res = mysql_store_result(&mysql_handle); + if (sql_res) { + for(i=0;(sql_row = mysql_fetch_row(sql_res));i++){ + p->cart[i].id = atoi(sql_row[0]); + p->cart[i].nameid = atoi(sql_row[1]); + p->cart[i].amount = atoi(sql_row[2]); + p->cart[i].equip = atoi(sql_row[3]); + p->cart[i].identify = atoi(sql_row[4]); + p->cart[i].refine = atoi(sql_row[5]); + p->cart[i].attribute = atoi(sql_row[6]); + p->cart[i].card[0] = atoi(sql_row[7]); + p->cart[i].card[1] = atoi(sql_row[8]); + p->cart[i].card[2] = atoi(sql_row[9]); + p->cart[i].card[3] = atoi(sql_row[10]); + p->cart[i].broken = atoi(sql_row[11]); + } + mysql_free_result(sql_res); + } + printf("cart "); + + //read skill + //`skill` (`char_id`, `id`, `lv`) + sprintf(tmp_sql, "SELECT `id`, `lv` FROM `%s` WHERE `char_id`='%d'",skill_db, char_id); + if (mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error (select `skill`)- %s\n", mysql_error(&mysql_handle)); + } + sql_res = mysql_store_result(&mysql_handle); + if (sql_res) { + for(i=0;(sql_row = mysql_fetch_row(sql_res));i++){ + n = atoi(sql_row[0]); + p->skill[n].id = n; //memory!? shit!. + p->skill[n].lv = atoi(sql_row[1]); + } + mysql_free_result(sql_res); + } + printf("skill "); + + //global_reg + //`global_reg_value` (`char_id`, `str`, `value`) + sprintf(tmp_sql, "SELECT `str`, `value` FROM `%s` WHERE `type`=3 AND `char_id`='%d'",reg_db, char_id); + if (mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error (select `global_reg_value`)- %s\n", mysql_error(&mysql_handle)); + } + i = 0; + sql_res = mysql_store_result(&mysql_handle); + if (sql_res) { + for(i=0;(sql_row = mysql_fetch_row(sql_res));i++){ + strcpy (p->global_reg[i].str, sql_row[0]); + p->global_reg[i].value = atoi (sql_row[1]); + } + mysql_free_result(sql_res); + } + p->global_reg_num=i; + + if (online) { + sprintf(tmp_sql, "UPDATE `%s` SET `online`='%d' WHERE `char_id`='%d'",char_db,online,char_id); + if (mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error (set char online)- %s\n", mysql_error(&mysql_handle)); + } + } + + printf("global_reg]\n"); //ok. all data load successfuly! + + //printf("char cloade"); + + return 1; +} +//========================================================================================================== +int mmo_char_sql_init(void) { + int i; + + printf("init start.......\n"); + // memory initialize + // no need to set twice size in this routine. but some cause segmentation error. :P + printf("initializing char memory...(%d byte)\n",sizeof(struct mmo_charstatus)*2); + CREATE(char_dat, struct mmo_charstatus, 2); + + memset(char_dat, 0, sizeof(struct mmo_charstatus)*2); +/* Initialized in inter.c already [Wizputer] + // DB connection initialized + // for char-server session only + mysql_init(&mysql_handle); + printf("Connect DB server....(char server)\n"); + if(!mysql_real_connect(&mysql_handle, char_server_ip, char_server_id, char_server_pw, char_server_db ,char_server_port, (char *)NULL, 0)) { + // SQL connection pointer check + printf("%s\n",mysql_error(&mysql_handle)); + exit(1); + } else { + printf("connect success! (char server)\n"); + } +*/ + sprintf(tmp_sql , "SELECT count(*) FROM `%s`", char_db); + if (mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + sql_res = mysql_store_result(&mysql_handle) ; + sql_row = mysql_fetch_row(sql_res); + printf("total char data -> '%s'.......\n",sql_row[0]); + i = atoi (sql_row[0]); + mysql_free_result(sql_res); + + if (i !=0) { + sprintf(tmp_sql , "SELECT max(`char_id`) FROM `%s`", char_db); + if (mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + sql_res = mysql_store_result(&mysql_handle) ; + sql_row = mysql_fetch_row(sql_res); + char_id_count = atoi (sql_row[0]); + + mysql_free_result(sql_res); + } else + printf("set char_id_count: %d.......\n",char_id_count); + + sprintf(tmp_sql,"UPDATE `%s` SET `online`='0' WHERE `online`='1'", char_db); + if (mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error (reset_online `%s`)- %s\n", char_db, mysql_error(&mysql_handle)); + } + printf("init end.......\n"); + + return 0; +} + +//========================================================================================================== + +int make_new_char_sql(int fd, unsigned char *dat) { + struct char_session_data *sd; + char t_name[100]; + int i; + //aphostropy error check! - fixed! + jstrescapecpy(t_name, dat); + printf("making new char -"); + + sd = session[fd]->session_data; + + // Check Authorised letters/symbols in the name of the character + if (char_name_option == 1) { // only letters/symbols in char_name_letters are authorised + for (i = 0; i < strlen(dat); i++) + if (strchr(char_name_letters, dat[i]) == NULL) + return -1; + } else if (char_name_option == 2) { // letters/symbols in char_name_letters are forbidden + for (i = 0; i < strlen(dat); i++) + if (strchr(char_name_letters, dat[i]) != NULL) + return -1; + } // else, all letters/symbols are authorised (except control char removed before) + + //check stat error + if ((dat[24]+dat[25]+dat[26]+dat[27]+dat[28]+dat[29]!=5*6 ) || + (dat[30] >= 9) || + (dat[33] <= 0) || (dat[33] >= 20) || + (dat[31] >= 9)) { + + // check individual stat value + for(i = 24; i <= 29; i++) { + if (dat[i] < 1 || dat[i] > 9) { + return -1; + } + } + + // char.log to charlog + sprintf(tmp_sql,"INSERT INTO `%s` (`time`, `char_msg`,`account_id`,`char_num`,`name`,`str`,`agi`,`vit`,`int`,`dex`,`luk`,`hair`,`hair_color`)" + "VALUES (NOW(), '%s', '%d', '%d', '%s', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d')", + charlog_db,"make new char error", sd->account_id, dat[30], dat, dat[24], dat[25], dat[26], dat[27], dat[28], dat[29], dat[33], dat[31]); + //query + if (mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + printf("make new char error %d-%d %s %d, %d, %d, %d, %d, %d %d, %d" RETCODE, + fd, dat[30], dat, dat[24], dat[25], dat[26], dat[27], dat[28], dat[29], dat[33], dat[31]); + return -1; + } + + // char.log to charlog + sprintf(tmp_sql,"INSERT INTO `%s`(`time`, `char_msg`,`account_id`,`char_num`,`name`,`str`,`agi`,`vit`,`int`,`dex`,`luk`,`hair`,`hair_color`)" + "VALUES (NOW(), '%s', '%d', '%d', '%s', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d')", + charlog_db,"make new char", sd->account_id, dat[30], dat, dat[24], dat[25], dat[26], dat[27], dat[28], dat[29], dat[33], dat[31]); + //query + if (mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + printf("make new char %d-%d %s %d, %d, %d, %d, %d, %d - %d, %d" RETCODE, + fd, dat[30], dat, dat[24], dat[25], dat[26], dat[27], dat[28], dat[29], dat[33], dat[31]); + + sprintf(tmp_sql, "SELECT count(*) FROM `%s` WHERE `name` = '%s'",char_db, t_name); + if (mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + return -1; + } + sql_res = mysql_store_result(&mysql_handle); + sql_row = mysql_fetch_row(sql_res); + printf("\033[1;32m name check result : %s -\033[0m ",sql_row[0]); + if (atoi(sql_row[0]) > 0) { + mysql_free_result(sql_res); + return -1; + } else + mysql_free_result(sql_res); + + // check char slot. + sprintf(tmp_sql, "SELECT count(*) FROM `%s` WHERE `account_id` = '%d' AND `char_num` = '%d'",char_db, sd->account_id, dat[30]); + if (mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + sql_res = mysql_store_result(&mysql_handle); + sql_row = mysql_fetch_row(sql_res); + + //printf("slot check result : %s\n",sql_row[0]); + if (atoi(sql_row[0]) > 0) { + mysql_free_result(sql_res); + return -1; + } else + mysql_free_result(sql_res); + + char_id_count++; + + // make new char. + sprintf(tmp_sql,"INSERT INTO `%s` (`char_id`,`account_id`,`char_num`,`name`,`zeny`,`str`,`agi`,`vit`,`int`,`dex`,`luk`,`max_hp`,`hp`,`max_sp`,`sp`,`hair`,`hair_color`)" + " VALUES ('%d', '%d', '%d', '%s', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d','%d', '%d','%d', '%d')", + char_db, char_id_count, sd->account_id , dat[30] , t_name, start_zeny, dat[24], dat[25], dat[26], dat[27], dat[28], dat[29], + (40 * (100 + dat[26])/100) , (40 * (100 + dat[26])/100 ), (11 * (100 + dat[27])/100), (11 * (100 + dat[27])/100), dat[33], dat[31]); + if (mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error (insert `char`)- %s\n", mysql_error(&mysql_handle)); + } + + //`inventory` (`id`,`char_id`, `nameid`, `amount`, `equip`, `identify`, `refine`, `attribute`, `card0`, `card1`, `card2`, `card3`) + sprintf(tmp_sql,"INSERT INTO `%s` (`char_id`,`nameid`, `amount`, `equip`, `identify`) VALUES ('%d', '%d', '%d', '%d', '%d')", + inventory_db, char_id_count, 1201,1,0x02,1); //add Knife + if (mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error (insert `inventory`)- %s\n", mysql_error(&mysql_handle)); + } + + sprintf(tmp_sql,"INSERT INTO `%s` (`char_id`,`nameid`, `amount`, `equip`, `identify`) VALUES ('%d', '%d', '%d', '%d', '%d')", + inventory_db, char_id_count, 2301,1,0x10,1); //add Cotton Shirt + if (mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error (insert `inventory`)- %s\n", mysql_error(&mysql_handle)); + } + // respawn map and start point set + sprintf(tmp_sql,"UPDATE `%s` SET `last_map`='%s',`last_x`='%d',`last_y`='%d',`save_map`='%s',`save_x`='%d',`save_y`='%d' WHERE `char_id` = '%d'", + char_db, start_point.map,start_point.x,start_point.y, start_point.map,start_point.x,start_point.y, char_id_count); + if (mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error (update `char`)- %s\n", mysql_error(&mysql_handle)); + } + printf("making new char success - id:(\033[1;32m%d\033[0m\tname:\033[1;32%s\033[0m\n", char_id_count, t_name); + return char_id_count; +} + +//========================================================================================================== + +void mmo_char_sync(void){ + printf("mmo_char_sync() - nothing to do\n"); +} + +// to do +/////////////////////////// + +int mmo_char_sync_timer(int tid, unsigned int tick, int id, int data) { + printf("mmo_char_sync_timer() tic - no works to do\n"); + return 0; +} + +int count_users(void) { + int i, users; + + if (login_fd > 0 && session[login_fd]){ + users = 0; + for(i = 0; i < MAX_MAP_SERVERS; i++) { + if (server_fd[i] >= 0) { + users += server[i].users; + } + } + return users; + } + return 0; +} + +int mmo_char_send006b(int fd, struct char_session_data *sd) { + int i, j, found_num = 0; + struct mmo_charstatus *p = NULL; +// hehe. commented other. anyway there's no need to use older version. +// if use older packet version just uncomment that! +//#ifdef NEW_006b + const int offset = 24; +//#else +// int offset = 4; +//#endif + + printf("mmo_char_send006b start.. (account:%d)\n",sd->account_id); +// printf("offset -> %d...\n",offset); + + //search char. + sprintf(tmp_sql, "SELECT `char_id` FROM `%s` WHERE `account_id` = '%d'",char_db, sd->account_id); + if (mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + sql_res = mysql_store_result(&mysql_handle); + if (sql_res) { + found_num = mysql_num_rows(sql_res); + printf("number of chars: %d\n", found_num); + i = 0; + while((sql_row = mysql_fetch_row(sql_res))) { + sd->found_char[i] = atoi(sql_row[0]); + i++; + } + mysql_free_result(sql_res); + } + +// printf("char fetching end (total: %d)....\n", found_num); + + for(i = found_num; i < 9; i++) + sd->found_char[i] = -1; + + memset(WFIFOP(fd, 0), 0, offset + found_num * 106); + WFIFOW(fd, 0) = 0x6b; + WFIFOW(fd, 2) = offset + found_num * 106; + + printf("(\033[1;13m%d\033[0m) Request Char Data:\n",sd->account_id); + + for(i = 0; i < found_num; i++) { + mmo_char_fromsql(sd->found_char[i], char_dat, 0); + + p = &char_dat[0]; + + j = offset + (i * 106); // increase speed of code + + WFIFOL(fd,j) = p->char_id; + WFIFOL(fd,j+4) = p->base_exp; + WFIFOL(fd,j+8) = p->zeny; + WFIFOL(fd,j+12) = p->job_exp; + WFIFOL(fd,j+16) = p->job_level; + + WFIFOL(fd,j+20) = 0; + WFIFOL(fd,j+24) = 0; + WFIFOL(fd,j+28) = p->option; + + WFIFOL(fd,j+32) = p->karma; + WFIFOL(fd,j+36) = p->manner; + + WFIFOW(fd,j+40) = p->status_point; + WFIFOW(fd,j+42) = (p->hp > 0x7fff) ? 0x7fff : p->hp; + WFIFOW(fd,j+44) = (p->max_hp > 0x7fff) ? 0x7fff : p->max_hp; + WFIFOW(fd,j+46) = (p->sp > 0x7fff) ? 0x7fff : p->sp; + WFIFOW(fd,j+48) = (p->max_sp > 0x7fff) ? 0x7fff : p->max_sp; + WFIFOW(fd,j+50) = DEFAULT_WALK_SPEED; // p->speed; + WFIFOW(fd,j+52) = p->class; + WFIFOW(fd,j+54) = p->hair; + WFIFOW(fd,j+56) = p->weapon; + WFIFOW(fd,j+58) = p->base_level; + WFIFOW(fd,j+60) = p->skill_point; + WFIFOW(fd,j+62) = p->head_bottom; + WFIFOW(fd,j+64) = p->shield; + WFIFOW(fd,j+66) = p->head_top; + WFIFOW(fd,j+68) = p->head_mid; + WFIFOW(fd,j+70) = p->hair_color; + WFIFOW(fd,j+72) = p->clothes_color; + + memcpy(WFIFOP(fd,j+74), p->name, 24); + + WFIFOB(fd,j+98) = (p->str > 255) ? 255 : p->str; + WFIFOB(fd,j+99) = (p->agi > 255) ? 255 : p->agi; + WFIFOB(fd,j+100) = (p->vit > 255) ? 255 : p->vit; + WFIFOB(fd,j+101) = (p->int_ > 255) ? 255 : p->int_; + WFIFOB(fd,j+102) = (p->dex > 255) ? 255 : p->dex; + WFIFOB(fd,j+103) = (p->luk > 255) ? 255 : p->luk; + WFIFOB(fd,j+104) = p->char_num; + } + + WFIFOSET(fd,WFIFOW(fd,2)); +// printf("mmo_char_send006b end..\n"); + return 0; +} + +int parse_tologin(int fd) { + int i; + struct char_session_data *sd; + + // only login-server can have an access to here. + // so, if it isn't the login-server, we disconnect the session. + //session eof check! + if (fd != login_fd || session[fd]->eof) { + if (fd == login_fd) { + printf("Char-server can't connect to login-server (connection #%d).\n", fd); + login_fd = -1; + } + close(fd); + delete_session(fd); + return 0; + } + + sd = session[fd]->session_data; + + // hehe. no need to set user limite on SQL version. :P + // but char limitation is good way to maintain server. :D + while(RFIFOREST(fd) >= 2) { +// printf("parse_tologin : %d %d %x\n", fd, RFIFOREST(fd), RFIFOW(fd, 0)); + + switch(RFIFOW(fd, 0)){ + case 0x2711: + if (RFIFOREST(fd) < 3) + return 0; + if (RFIFOB(fd, 2)) { + //printf("connect login server error : %d\n", RFIFOB(fd, 2)); + printf("Can not connect to login-server.\n"); + printf("The server communication passwords (default s1/p1) is probably invalid.\n"); + printf("Also, please make sure your login db has the username/password present and the sex of the account is S.\n"); + printf("If you changed the communication passwords, change them back at map_athena.conf and char_athena.conf\n"); + return 0; + //exit(1); //fixed for server shutdown. + }else { + printf("Connected to login-server (connection #%d).\n", fd); + // if no map-server already connected, display a message... + for(i = 0; i < MAX_MAP_SERVERS; i++) + if (server_fd[i] >= 0 && server[i].map[0][0]) // if map-server online and at least 1 map + break; + if (i == MAX_MAP_SERVERS) + printf("Awaiting maps from map-server.\n"); + } + RFIFOSKIP(fd, 3); + break; + + case 0x2713: + if(RFIFOREST(fd)<51) + return 0; + for(i = 0; i < fd_max; i++) { + if (session[i] && (sd = session[i]->session_data) && sd->account_id == RFIFOL(fd,2)) { + if (RFIFOB(fd,6) != 0) { + WFIFOW(i,0) = 0x6c; + WFIFOB(i,2) = 0x42; + WFIFOSET(i,3); + } else if (max_connect_user == 0 || count_users() < max_connect_user) { +// if (max_connect_user == 0) +// printf("max_connect_user (unlimited) -> accepted.\n"); +// else +// printf("count_users(): %d < max_connect_user (%d) -> accepted.\n", count_users(), max_connect_user); + sd->connect_until_time = (time_t)RFIFOL(fd,47); + // send characters to player + mmo_char_send006b(i, sd); + } else { + // refuse connection: too much online players +// printf("count_users(): %d < max_connect_use (%d) -> fail...\n", count_users(), max_connect_user); + WFIFOW(i,0) = 0x6c; + WFIFOW(i,2) = 0; + WFIFOSET(i,3); + } + } + } + RFIFOSKIP(fd,51); + break; + + case 0x2717: + if (RFIFOREST(fd) < 50) + return 0; + for(i = 0; i < fd_max; i++) { + if (session[i] && (sd = session[i]->session_data)) { + if (sd->account_id == RFIFOL(fd,2)) { + sd->connect_until_time = (time_t)RFIFOL(fd,46); + break; + } + } + } + RFIFOSKIP(fd,50); + break; + +/* case 0x2721: // gm reply. I don't want to support this function. + printf("0x2721:GM reply\n"); + { + int oldacc, newacc; + unsigned char buf[64]; + if (RFIFOREST(fd) < 10) + return 0; + oldacc = RFIFOL(fd, 2); + newacc = RFIFOL(fd, 6); + RFIFOSKIP(fd, 10); + if (newacc > 0) { + for(i=0;i<char_num;i++){ + if(char_dat[i].account_id==oldacc) + char_dat[i].account_id=newacc; + } + } + WBUFW(buf,0)=0x2b0b; + WBUFL(buf,2)=oldacc; + WBUFL(buf,6)=newacc; + mapif_sendall(buf,10); +// printf("char -> map\n"); + } + break; +*/ + case 0x2723: // changesex reply (modified by [Yor]) + if (RFIFOREST(fd) < 7) + return 0; + { + int acc, sex; + unsigned char buf[16]; + + acc = RFIFOL(fd,2); + sex = RFIFOB(fd,6); + RFIFOSKIP(fd, 7); + if (acc > 0) { + sprintf(tmp_sql, "SELECT `char_id`,`class`,`skill_point` FROM `%s` WHERE `account_id` = '%d'",char_db, acc); + if (mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error (select `char`)- %s\n", mysql_error(&mysql_handle)); + } + sql_res = mysql_store_result(&mysql_handle); + + if (sql_res) { + sql_row = mysql_fetch_row(sql_res); + int char_id = atoi(sql_row[0]); + int jobclass = atoi(sql_row[1]); + int skill_point = atoi(sql_row[2]); + int class = jobclass; + if (jobclass == 19 || jobclass == 20 || + jobclass == 4020 || jobclass == 4021 || + jobclass == 4042 || jobclass == 4043) { + // job modification + if (jobclass == 19 || jobclass == 20) { + class = (sex) ? 19 : 20; + } else if (jobclass == 4020 || jobclass == 4021) { + class = (sex) ? 4020 : 4021; + } else if (jobclass == 4042 || jobclass == 4043) { + class = (sex) ? 4042 : 4043; + } + // remove specifical skills of classes 19,20 4020,4021 and 4042,4043 + sprintf(tmp_sql, "SELECT `lv` FROM `%s` WHERE `char_id` = '%d' AND `id` >= '315' AND `id` <= '330'",skill_db, char_id); + if (mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error (select `char`)- %s\n", mysql_error(&mysql_handle)); + } + sql_res = mysql_store_result(&mysql_handle); + if (sql_res) { + while(( sql_row = mysql_fetch_row(sql_res))) { + skill_point += atoi(sql_row[0]); + } + } + sprintf(tmp_sql, "DELETE FROM `%s` WHERE `char_id` = '%d' AND `id` >= '315' AND `id` <= '330'",skill_db, char_id); + if (mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error (select `char`)- %s\n", mysql_error(&mysql_handle)); + } + } + // to avoid any problem with equipment and invalid sex, equipment is unequiped. + sprintf(tmp_sql, "UPDATE `%s` SET `equip` = '0' WHERE `char_id` = '%d'",inventory_db, char_id); + if (mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error (select `char`)- %s\n", mysql_error(&mysql_handle)); + } + sprintf(tmp_sql, "UPDATE `%s` SET `class`='%d' , `skill_point`='%d' , `weapon`='0' , `shield='0' , `head_top`='0' , `head_mid`='0' , `head_bottom`='0' WHERE `char_id` = '%d'",char_db, class, skill_point, char_id); + if (mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error (select `char`)- %s\n", mysql_error(&mysql_handle)); + } + } + } + // disconnect player if online on char-server + for(i = 0; i < fd_max; i++) { + if (session[i] && (sd = session[i]->session_data)) { + if (sd->account_id == acc) { + session[i]->eof = 1; + break; + } + } + } + + WBUFW(buf,0) = 0x2b0d; + WBUFL(buf,2) = acc; + WBUFB(buf,6) = sex; + + mapif_sendall(buf, 7); + } + break; + + // account_reg2変更通知 + case 0x2729: + if (RFIFOREST(fd) < 4 || RFIFOREST(fd) < RFIFOW(fd,2)) + return 0; + { + struct global_reg reg[ACCOUNT_REG2_NUM]; + unsigned char buf[4096]; + int j, p, acc; + acc = RFIFOL(fd,4); + for(p = 8, j = 0; p < RFIFOW(fd,2) && j < ACCOUNT_REG2_NUM; p += 36, j++) { + memcpy(reg[j].str, RFIFOP(fd,p), 32); + reg[j].value = RFIFOL(fd,p+32); + } + // set_account_reg2(acc,j,reg); + // 同垢ログインを禁止していれば送る必要は無い + memcpy(buf,RFIFOP(fd,0), RFIFOW(fd,2)); + WBUFW(buf,0) = 0x2b11; + mapif_sendall(buf, WBUFW(buf,2)); + RFIFOSKIP(fd, RFIFOW(fd,2)); +// printf("char: save_account_reg_reply\n"); + } + break; + + // State change of account/ban notification (from login-server) by [Yor] + case 0x2731: + if (RFIFOREST(fd) < 11) + return 0; + // send to all map-servers to disconnect the player + { + unsigned char buf[16]; + WBUFW(buf,0) = 0x2b14; + WBUFL(buf,2) = RFIFOL(fd,2); + WBUFB(buf,6) = RFIFOB(fd,6); // 0: change of statut, 1: ban + WBUFL(buf,7) = RFIFOL(fd,7); // status or final date of a banishment + mapif_sendall(buf, 11); + } + // disconnect player if online on char-server + for(i = 0; i < fd_max; i++) { + if (session[i] && (sd = session[i]->session_data)) { + if (sd->account_id == RFIFOL(fd,2)) { + session[i]->eof = 1; + break; + } + } + } + RFIFOSKIP(fd,11); + break; + + default: + printf("set eof.\n"); + session[fd]->eof = 1; + return 0; + } + } + + RFIFOFLUSH(fd); + + return 0; +} + +//-------------------------------- +// Map-server anti-freeze system +//-------------------------------- +int map_anti_freeze_system(int tid, unsigned int tick, int id, int data) { + int i; + + for(i = 0; i < MAX_MAP_SERVERS; i++) { + if (server_fd[i] >= 0) {// if map-server is online + printf("map_anti_freeze_system: server #%d, flag: %d.\n", i, server_freezeflag[i]); + if (server_freezeflag[i]-- < 1) {// Map-server anti-freeze system. Counter. 5 ok, 4...0 freezed + printf("Map-server anti-freeze system: char-server #%d is frozen -> disconnection.\n", i); + session[server_fd[i]]->eof = 1; + sprintf(tmp_sql, "DELETE FROM `ragsrvinfo` WHERE `index`='%d'", server_fd[i]); + if (mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + } + } + } + + return 0; +} + +int parse_frommap(int fd) { + int i = 0, j = 0; + int id; + + // Sometimes fd=0, and it will cause server crash. Don't know why. :( + if (fd <= 0) { + printf("parse_frommap error fd=0\n"); + return 0; + } + + for(id = 0; id < MAX_MAP_SERVERS; id++) + if (server_fd[id] == fd) + break; + if(id == MAX_MAP_SERVERS || session[fd]->eof) { + if (id < MAX_MAP_SERVERS) { + memset(&server[id], 0, sizeof(struct mmo_map_server)); + printf("Map-server %d (session #%d) has disconnected.\n", id, fd); + sprintf(tmp_sql, "DELETE FROM `ragsrvinfo` WHERE `index`='%d'", server_fd[id]); + if (mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + server_fd[id] = -1; + } + close(fd); + delete_session(fd); + return 0; + } + + while(RFIFOREST(fd) >= 2) { +// printf("parse_frommap : %d %d %x\n", fd, RFIFOREST(fd), RFIFOW(fd,0)); + + switch(RFIFOW(fd, 0)) { + case 0x2af7: + RFIFOSKIP(fd,2); + read_gm_account(); + break; + + // mapserver -> map names recv. + case 0x2afa: + if (RFIFOREST(fd) < 4 || RFIFOREST(fd) < RFIFOW(fd,2)) + return 0; + memset(server[id].map, 0, sizeof(server[id].map)); + j = 0; + for(i = 4; i < RFIFOW(fd,2); i += 16) { + memcpy(server[id].map[j], RFIFOP(fd,i), 16); +// printf("set map %d.%d : %s\n", id, j, server[id].map[j]); + j++; + } + i = server[id].ip; + { + unsigned char *p = (unsigned char *)&server[id].ip; + printf("Map-Server %d connected: %d maps, from IP %d.%d.%d.%d port %d.\n", + id, j, p[0], p[1], p[2], p[3], server[id].port); + printf("Map-server %d loading complete.\n", id); + } + WFIFOW(fd,0) = 0x2afb; + WFIFOB(fd,2) = 0; + memcpy(WFIFOP(fd,3), wisp_server_name, 24); // name for wisp to player + WFIFOSET(fd,27); + { + unsigned char buf[16384]; + int x; + if (j == 0) { + printf("WARNING: Map-Server %d have NO maps.\n", id); + // Transmitting maps information to the other map-servers + } else { + WBUFW(buf,0) = 0x2b04; + WBUFW(buf,2) = j * 16 + 10; + WBUFL(buf,4) = server[id].ip; + WBUFW(buf,8) = server[id].port; + memcpy(WBUFP(buf,10), RFIFOP(fd,4), j * 16); + mapif_sendallwos(fd, buf, WBUFW(buf,2)); + } + // Transmitting the maps of the other map-servers to the new map-server + for(x = 0; x < MAX_MAP_SERVERS; x++) { + if (server_fd[x] >= 0 && x != id) { + WFIFOW(fd,0) = 0x2b04; + WFIFOL(fd,4) = server[x].ip; + WFIFOW(fd,8) = server[x].port; + j = 0; + for(i = 0; i < MAX_MAP_PER_SERVER; i++) + if (server[x].map[i][0]) + memcpy(WFIFOP(fd,10+(j++)*16), server[x].map[i], 16); + if (j > 0) { + WFIFOW(fd,2) = j * 16 + 10; + WFIFOSET(fd,WFIFOW(fd,2)); + } + } + } + } + RFIFOSKIP(fd,RFIFOW(fd,2)); + break; + + // auth request + case 0x2afc: + if (RFIFOREST(fd) < 22) + return 0; +// printf("(AUTH request) auth_fifo search %d %d %d\n", RFIFOL(fd, 2), RFIFOL(fd, 6), RFIFOL(fd, 10)); + for(i = 0; i < AUTH_FIFO_SIZE; i++) { + if (auth_fifo[i].account_id == RFIFOL(fd,2) && + auth_fifo[i].char_id == RFIFOL(fd,6) && + auth_fifo[i].login_id1 == RFIFOL(fd,10) && +#if CMP_AUTHFIFO_LOGIN2 != 0 + // here, it's the only area where it's possible that we doesn't know login_id2 (map-server asks just after 0x72 packet, that doesn't given the value) + (auth_fifo[i].login_id2 == RFIFOL(fd,14) || RFIFOL(fd,14) == 0) && // relate to the versions higher than 18 +#endif + (!check_ip_flag || auth_fifo[i].ip == RFIFOL(fd,18)) && + !auth_fifo[i].delflag) { + auth_fifo[i].delflag = 1; + WFIFOW(fd,0) = 0x2afd; + WFIFOW(fd,2) = 16 + sizeof(struct mmo_charstatus); + WFIFOL(fd,4) = RFIFOL(fd,2); + WFIFOL(fd,8) = auth_fifo[i].login_id2; + WFIFOL(fd,12) = (unsigned long)auth_fifo[i].connect_until_time; + mmo_char_fromsql(auth_fifo[i].char_id, char_dat, 1); + char_dat[0].sex = auth_fifo[i].sex; + memcpy(WFIFOP(fd,16), &char_dat[0], sizeof(struct mmo_charstatus)); + WFIFOSET(fd, WFIFOW(fd,2)); + //printf("auth_fifo search success (auth #%d, account %d, character: %d).\n", i, RFIFOL(fd,2), RFIFOL(fd,6)); + break; + } + } + if (i == AUTH_FIFO_SIZE) { + WFIFOW(fd,0) = 0x2afe; + WFIFOL(fd,2) = RFIFOL(fd,2); + WFIFOSET(fd,6); +// printf("(AUTH request) auth_fifo search error!\n"); + } + RFIFOSKIP(fd,22); + break; + + // set MAP user + case 0x2aff: + if (RFIFOREST(fd) < 6 || RFIFOREST(fd) < RFIFOW(fd,2)) + return 0; + if (RFIFOW(fd,4) != server[id].users) + printf("map user: %d\n", RFIFOW(fd,4)); + server[id].users = RFIFOW(fd,4); + if(anti_freeze_enable) + server_freezeflag[id] = 5; // Map anti-freeze system. Counter. 5 ok, 4...0 freezed + RFIFOSKIP(fd,RFIFOW(fd,2)); + break; + + // char saving + case 0x2b01: + i = 0; + if (RFIFOREST(fd) < 4 || RFIFOREST(fd) < RFIFOW(fd,2)) + return 0; + //check account + sprintf(tmp_sql, "SELECT count(*) FROM `%s` WHERE `account_id` = '%d' AND `char_id`='%d'",char_db, RFIFOL(fd,4),RFIFOL(fd,8)); + if (mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + sql_res = mysql_store_result(&mysql_handle); + if (sql_res) { + sql_row = mysql_fetch_row(sql_res); + if (sql_row) + i = atoi(sql_row[0]); + } + mysql_free_result(sql_res); + + if (i == 1) { + memcpy(&char_dat[0], RFIFOP(fd,12), sizeof(struct mmo_charstatus)); + mmo_char_tosql(RFIFOL(fd,8), char_dat); + //save to DB + } + RFIFOSKIP(fd,RFIFOW(fd,2)); + break; + + // req char selection + case 0x2b02: + if (RFIFOREST(fd) < 18) + return 0; + + if (auth_fifo_pos >= AUTH_FIFO_SIZE) + auth_fifo_pos = 0; + +// printf("(charselect) auth_fifo set %d - account_id:%08x login_id1:%08x\n", auth_fifo_pos, RFIFOL(fd, 2), RFIFOL(fd, 6)); + auth_fifo[auth_fifo_pos].account_id = RFIFOL(fd, 2); + auth_fifo[auth_fifo_pos].char_id = 0; + auth_fifo[auth_fifo_pos].login_id1 = RFIFOL(fd, 6); + auth_fifo[auth_fifo_pos].login_id2 = RFIFOL(fd,10); + auth_fifo[auth_fifo_pos].delflag = 2; + auth_fifo[auth_fifo_pos].char_pos = 0; + auth_fifo[auth_fifo_pos].connect_until_time = 0; // unlimited/unknown time by default (not display in map-server) + auth_fifo[auth_fifo_pos].ip = RFIFOL(fd,14); + auth_fifo_pos++; + + WFIFOW(fd, 0) = 0x2b03; + WFIFOL(fd, 2) = RFIFOL(fd, 2); + WFIFOB(fd, 6) = 0; + WFIFOSET(fd, 7); + + RFIFOSKIP(fd, 18); + break; + + // request "change map server" + case 0x2b05: + if (RFIFOREST(fd) < 49) + return 0; + + if (auth_fifo_pos >= AUTH_FIFO_SIZE) + auth_fifo_pos = 0; + + WFIFOW(fd, 0) = 0x2b06; + memcpy(WFIFOP(fd,2), RFIFOP(fd,2), 42); +// printf("(map change) auth_fifo set %d - account_id:%08x login_id1:%08x\n", auth_fifo_pos, RFIFOL(fd, 2), RFIFOL(fd, 6)); + auth_fifo[auth_fifo_pos].account_id = RFIFOL(fd, 2); + auth_fifo[auth_fifo_pos].login_id1 = RFIFOL(fd, 6); + auth_fifo[auth_fifo_pos].login_id2 = RFIFOL(fd,10); + auth_fifo[auth_fifo_pos].char_id = RFIFOL(fd,14); + auth_fifo[auth_fifo_pos].delflag = 0; + auth_fifo[auth_fifo_pos].sex = RFIFOB(fd,44); + auth_fifo[auth_fifo_pos].ip = RFIFOL(fd,45); + + sprintf(tmp_sql, "SELECT count(*) FROM `%s` WHERE `account_id` = '%d' AND `char_id`='%d'", char_db, RFIFOL(fd,2), RFIFOL(fd,14)); + if (mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + sql_res = mysql_store_result(&mysql_handle); + + if (( sql_row = mysql_fetch_row(sql_res))) { + i = atoi(sql_row[0]); + mysql_free_result(sql_res); + + auth_fifo[auth_fifo_pos].char_pos = auth_fifo[auth_fifo_pos].char_id; + auth_fifo_pos++; + + WFIFOL(fd,6) = 0; + break; + } + if (i == 0) + WFIFOW(fd,6) = 1; + + WFIFOSET(fd,44); + RFIFOSKIP(fd,49); + break; + + // char name check + case 0x2b08: + if (RFIFOREST(fd) < 6) + return 0; + + sprintf(tmp_sql, "SELECT `name` FROM `%s` WHERE `char_id`='%d'", char_db, RFIFOL(fd,2)); + if (mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + sql_res = mysql_store_result(&mysql_handle); + + sql_row = mysql_fetch_row(sql_res); + + WFIFOW(fd,0) = 0x2b09; + WFIFOL(fd,2) = RFIFOL(fd,2); + + if (sql_row) + memcpy(WFIFOP(fd,6), sql_row[0], 24); + else + memcpy(WFIFOP(fd,6), unknown_char_name, 24); + mysql_free_result(sql_res); + + WFIFOSET(fd,30); + + RFIFOSKIP(fd,6); + break; + +/* // I want become GM - fuck! + case 0x2b0a: + if(RFIFOREST(fd)<4) + return 0; + if(RFIFOREST(fd)<RFIFOW(fd,2)) + return 0; + memcpy(WFIFOP(login_fd,2),RFIFOP(fd,2),RFIFOW(fd,2)-2); + WFIFOW(login_fd,0)=0x2720; + WFIFOSET(login_fd,RFIFOW(fd,2)); +// printf("char : change gm -> login %d %s %d\n", RFIFOL(fd, 4), RFIFOP(fd, 8), RFIFOW(fd, 2)); + RFIFOSKIP(fd, RFIFOW(fd, 2)); + break; + */ + + // account_reg保存要求 + case 0x2b10: + if (RFIFOREST(fd) < 4 || RFIFOREST(fd) < RFIFOW(fd,2)) + return 0; + { + struct global_reg reg[ACCOUNT_REG2_NUM]; + int j,p,acc; + acc=RFIFOL(fd,4); + for(p=8,j=0;p<RFIFOW(fd,2) && j<ACCOUNT_REG2_NUM;p+=36,j++){ + memcpy(reg[j].str,RFIFOP(fd,p),32); + reg[j].value=RFIFOL(fd,p+32); + } + // set_account_reg2(acc,j,reg); + // loginサーバーへ送る + if (login_fd > 0) { // don't send request if no login-server + WFIFOW(login_fd, 0) = 0x2728; + memcpy(WFIFOP(login_fd,0), RFIFOP(fd,0), RFIFOW(fd,2)); + WFIFOSET(login_fd, WFIFOW(login_fd,2)); + } + // ワールドへの同垢ログインがなければmapサーバーに送る必要はない + //memcpy(buf,RFIFOP(fd,0),RFIFOW(fd,2)); + //WBUFW(buf,0)=0x2b11; + //mapif_sendall(buf,WBUFW(buf,2)); + RFIFOSKIP(fd,RFIFOW(fd,2)); +// printf("char: save_account_reg (from map)\n"); + } + break; + + // Map server send information to change an email of an account -> login-server + case 0x2b0c: + if (RFIFOREST(fd) < 86) + return 0; + if (login_fd > 0) { // don't send request if no login-server + memcpy(WFIFOP(login_fd,0), RFIFOP(fd,0), 86); // 0x2722 <account_id>.L <actual_e-mail>.40B <new_e-mail>.40B + WFIFOW(login_fd,0) = 0x2722; + WFIFOSET(login_fd, 86); + } + RFIFOSKIP(fd, 86); + break; + + // Receiving from map-server a status change resquest. Transmission to login-server (by Yor) + case 0x2b0e: + if (RFIFOREST(fd) < 44) + return 0; + { + char character_name[24]; + int acc = RFIFOL(fd,2); // account_id of who ask (-1 if nobody) + memcpy(character_name, RFIFOP(fd,6), 24); + character_name[sizeof(character_name) -1] = '\0'; + // prepare answer + WFIFOW(fd,0) = 0x2b0f; // answer + WFIFOL(fd,2) = acc; // who want do operation + WFIFOW(fd,30) = RFIFOW(fd, 30); // type of operation: 1-block, 2-ban, 3-unblock, 4-unban + sprintf(tmp_sql, "SELECT `account_id`,`name` FROM `%s` WHERE `name` = '%s'",char_db, character_name); + if (mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error (select `char`)- %s\n", mysql_error(&mysql_handle)); + } + + sql_res = mysql_store_result(&mysql_handle); + + if (sql_res) { + if (mysql_num_rows(sql_res)) { + sql_row = mysql_fetch_row(sql_res); + memcpy(WFIFOP(fd,6), sql_row[1], 24); // put correct name if found + WFIFOW(fd,32) = 0; // answer: 0-login-server resquest done, 1-player not found, 2-gm level too low, 3-login-server offline + switch(RFIFOW(fd, 30)) { + case 1: // block + if (acc == -1 || isGM(acc) >= isGM(atoi(sql_row[0]))) { + if (login_fd > 0) { // don't send request if no login-server + WFIFOW(login_fd,0) = 0x2724; + WFIFOL(login_fd,2) = atoi(sql_row[0]); // account value + WFIFOL(login_fd,6) = 5; // status of the account + WFIFOSET(login_fd, 10); +// printf("char : status -> login: account %d, status: %d \n", char_dat[i].account_id, 5); + } else + WFIFOW(fd,32) = 3; // answer: 0-login-server resquest done, 1-player not found, 2-gm level too low, 3-login-server offline + } else + WFIFOW(fd,32) = 2; // answer: 0-login-server resquest done, 1-player not found, 2-gm level too low, 3-login-server offline + break; + case 2: // ban + if (acc == -1 || isGM(acc) >= isGM(atoi(sql_row[0]))) { + if (login_fd > 0) { // don't send request if no login-server + WFIFOW(login_fd, 0) = 0x2725; + WFIFOL(login_fd, 2) = atoi(sql_row[0]); // account value + WFIFOW(login_fd, 6) = RFIFOW(fd,32); // year + WFIFOW(login_fd, 8) = RFIFOW(fd,34); // month + WFIFOW(login_fd,10) = RFIFOW(fd,36); // day + WFIFOW(login_fd,12) = RFIFOW(fd,38); // hour + WFIFOW(login_fd,14) = RFIFOW(fd,40); // minute + WFIFOW(login_fd,16) = RFIFOW(fd,42); // second + WFIFOSET(login_fd,18); +// printf("char : status -> login: account %d, ban: %dy %dm %dd %dh %dmn %ds\n", +// char_dat[i].account_id, (short)RFIFOW(fd,32), (short)RFIFOW(fd,34), (short)RFIFOW(fd,36), (short)RFIFOW(fd,38), (short)RFIFOW(fd,40), (short)RFIFOW(fd,42)); + } else + WFIFOW(fd,32) = 3; // answer: 0-login-server resquest done, 1-player not found, 2-gm level too low, 3-login-server offline + } else + WFIFOW(fd,32) = 2; // answer: 0-login-server resquest done, 1-player not found, 2-gm level too low, 3-login-server offline + break; + case 3: // unblock + if (acc == -1 || isGM(acc) >= isGM(atoi(sql_row[0]))) { + if (login_fd > 0) { // don't send request if no login-server + WFIFOW(login_fd,0) = 0x2724; + WFIFOL(login_fd,2) = atoi(sql_row[0]); // account value + WFIFOL(login_fd,6) = 0; // status of the account + WFIFOSET(login_fd, 10); +// printf("char : status -> login: account %d, status: %d \n", char_dat[i].account_id, 0); + } else + WFIFOW(fd,32) = 3; // answer: 0-login-server resquest done, 1-player not found, 2-gm level too low, 3-login-server offline + } else + WFIFOW(fd,32) = 2; // answer: 0-login-server resquest done, 1-player not found, 2-gm level too low, 3-login-server offline + break; + case 4: // unban + if (acc == -1 || isGM(acc) >= isGM(atoi(sql_row[0]))) { + if (login_fd > 0) { // don't send request if no login-server + WFIFOW(login_fd, 0) = 0x272a; + WFIFOL(login_fd, 2) = atoi(sql_row[0]); // account value + WFIFOSET(login_fd, 6); +// printf("char : status -> login: account %d, unban request\n", char_dat[i].account_id); + } else + WFIFOW(fd,32) = 3; // answer: 0-login-server resquest done, 1-player not found, 2-gm level too low, 3-login-server offline + } else + WFIFOW(fd,32) = 2; // answer: 0-login-server resquest done, 1-player not found, 2-gm level too low, 3-login-server offline + break; + case 5: // changesex + if (acc == -1 || isGM(acc) >= isGM(atoi(sql_row[0]))) { + if (login_fd > 0) { // don't send request if no login-server + WFIFOW(login_fd, 0) = 0x2727; + WFIFOL(login_fd, 2) = atoi(sql_row[0]); // account value + WFIFOSET(login_fd, 6); +// printf("char : status -> login: account %d, change sex request\n", char_dat[i].account_id); + } else + WFIFOW(fd,32) = 3; // answer: 0-login-server resquest done, 1-player not found, 2-gm level too low, 3-login-server offline + } else + WFIFOW(fd,32) = 2; // answer: 0-login-server resquest done, 1-player not found, 2-gm level too low, 3-login-server offline + break; + } + } else { + // character name not found + memcpy(WFIFOP(fd,6), character_name, 24); + WFIFOW(fd,32) = 1; // answer: 0-login-server resquest done, 1-player not found, 2-gm level too low, 3-login-server offline + } + // send answer if a player ask, not if the server ask + if (acc != -1) { + WFIFOSET(fd, 34); + } + } + } + RFIFOSKIP(fd, 44); + break; + + // Recieve rates [Wizputer] + case 0x2b16: + if (RFIFOREST(fd) < 6 || RFIFOREST(fd) < RFIFOW(fd,8)) + return 0; + sprintf(tmp_sql, "INSERT INTO `ragsrvinfo` SET `index`='%d',`name`='%s',`exp`='%d',`jexp`='%d',`drop`='%d',`motd`='%s'", + fd, server_name, RFIFOW(fd,2), RFIFOW(fd,4), RFIFOW(fd,6), RFIFOP(fd,10)); + if (mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + RFIFOSKIP(fd,RFIFOW(fd,8)); + break; + + // Character disconnected set online 0 [Wizputer] + case 0x2b17: + if (RFIFOREST(fd) < 6 ) + return 0; + //printf("Setting %d char offline\n",RFIFOL(fd,2)); + sprintf(tmp_sql,"UPDATE `%s` SET `online`='0' WHERE `char_id`='%d'", char_db, RFIFOL(fd,2)); + if (mysql_query(&mysql_handle, tmp_sql)) + printf("DB server Error (update online `%s`)- %s\n", char_db, mysql_error(&mysql_handle)); + RFIFOSKIP(fd,6); + break; + + default: + // inter server - packet + { + int r = inter_parse_frommap(fd); + if (r == 1) break; // processed + if (r == 2) return 0; // need more packet + } + + // no inter server packet. no char server packet -> disconnect + printf("parse_frommap: unknown packet %x! \n", RFIFOW(fd,0)); + session[fd]->eof = 1; + return 0; + } + } + return 0; +} + +int search_mapserver(char *map) { + int i, j; + char temp_map[16]; + int temp_map_len; + +// printf("Searching the map-server for map '%s'... ", map); + strncpy(temp_map, map, sizeof(temp_map)); + temp_map[sizeof(temp_map)-1] = '\0'; + if (strchr(temp_map, '.') != NULL) + temp_map[strchr(temp_map, '.') - temp_map + 1] = '\0'; // suppress the '.gat', but conserve the '.' to be sure of the name of the map + + temp_map_len = strlen(temp_map); + for(i = 0; i < MAX_MAP_SERVERS; i++) + if (server_fd[i] >= 0) + for (j = 0; server[i].map[j][0]; j++) + //printf("%s : %s = %d\n", server[i].map[j], map, strncmp(server[i].map[j], temp_map, temp_map_len)); + if (strncmp(server[i].map[j], temp_map, temp_map_len) == 0) { +// printf("found -> server #%d.\n", i); + return i; + } + +// printf("not found.\n"); + return -1; +} + +int char_mapif_init(int fd) { + return inter_mapif_init(fd); +} + +//----------------------------------------------------- +// Test to know if an IP come from LAN or WAN. by [Yor] +//----------------------------------------------------- +int lan_ip_check(unsigned char *p){ + int i; + int lancheck = 1; + int subneti[4]; + unsigned int k0, k1, k2, k3; + + sscanf(lan_map_ip, "%d.%d.%d.%d", &k0, &k1, &k2, &k3); + subneti[0] = k0; subneti[1] = k1; subneti[2] = k2; subneti[3] = k3; + +// printf("lan_ip_check: to compare: %d.%d.%d.%d, network: %d.%d.%d.%d/%d.%d.%d.%d\n", +// p[0], p[1], p[2], p[3], +// subneti[0], subneti[1], subneti[2], subneti[3], +// subnetmaski[0], subnetmaski[1], subnetmaski[2], subnetmaski[3]); + for(i = 0; i < 4; i++) { + if ((subneti[i] & subnetmaski[i]) != (p[i] & subnetmaski[i])) { + lancheck = 0; + break; + } + } +// printf("LAN test (result): %s source\033[0m.\n", (lancheck) ? "\033[1;36mLAN" : "\033[1;32mWAN"); + return lancheck; +} + +int parse_char(int fd) { + int i, ch = 0; + char email[40]; + struct char_session_data *sd; + unsigned char *p = (unsigned char *) &session[fd]->client_addr.sin_addr; + + if(login_fd < 0 || session[fd]->eof) { + if (fd == login_fd) + login_fd = -1; + close(fd); + delete_session(fd); + return 0; + } + + sd = session[fd]->session_data; + + while(RFIFOREST(fd) >= 2) { +// if (RFIFOW(fd, 0) < 30000) +// printf("parse_char : %d %d %x\n", fd, RFIFOREST(fd), RFIFOW(fd, 0)); + + switch(RFIFOW(fd, 0)) { + case 0x20b: //20040622 encryption ragexe correspondence + if (RFIFOREST(fd) < 19) + return 0; + RFIFOSKIP(fd,19); + break; + + case 0x65: // request to connect + printf("request connect - account_id:%d/login_id1:%d/login_id2:%d\n", RFIFOL(fd, 2), RFIFOL(fd, 6), RFIFOL(fd, 10)); + if (RFIFOREST(fd) < 17) + return 0; + { +/*removed from isGM setup + if (isGM(RFIFOL(fd,2))) + printf("Account Logged On; Account ID: %d (GM level %d).\n", RFIFOL(fd,2), isGM(RFIFOL(fd,2))); + else + printf("Account Logged On; Account ID: %d.\n", RFIFOL(fd,2)); +*/ + if (sd == NULL) { + CREATE(session[fd]->session_data, struct char_session_data, 1); + sd = session[fd]->session_data; + sd->connect_until_time = 0; // unknow or illimited (not displaying on map-server) + } + sd->account_id = RFIFOL(fd, 2); + sd->login_id1 = RFIFOL(fd, 6); + sd->login_id2 = RFIFOL(fd, 10); + sd->sex = RFIFOB(fd, 16); + + WFIFOL(fd, 0) = RFIFOL(fd, 2); + WFIFOSET(fd, 4); + + for(i = 0; i < AUTH_FIFO_SIZE; i++) { + if (auth_fifo[i].account_id == sd->account_id && + auth_fifo[i].login_id1 == sd->login_id1 && +#if CMP_AUTHFIFO_LOGIN2 != 0 + auth_fifo[i].login_id2 == sd->login_id2 && // relate to the versions higher than 18 +#endif + (!check_ip_flag || auth_fifo[i].ip == session[fd]->client_addr.sin_addr.s_addr) && + auth_fifo[i].delflag == 2) { + auth_fifo[i].delflag = 1; + + if (max_connect_user == 0 || count_users() < max_connect_user) { + if (login_fd > 0) { // don't send request if no login-server + // request to login-server to obtain e-mail/time limit + WFIFOW(login_fd,0) = 0x2716; + WFIFOL(login_fd,2) = sd->account_id; + WFIFOSET(login_fd,6); + } + // send characters to player + mmo_char_send006b(fd, sd); + } else { + // refuse connection (over populated) + WFIFOW(fd,0) = 0x6c; + WFIFOW(fd,2) = 0; + WFIFOSET(fd,3); + } +// printf("connection request> set delflag 1(o:2)- account_id:%d/login_id1:%d(fifo_id:%d)\n", sd->account_id, sd->login_id1, i); + break; + } + } + if (i == AUTH_FIFO_SIZE) { + if (login_fd > 0) { // don't send request if no login-server + WFIFOW(login_fd,0) = 0x2712; // ask login-server to authentify an account + WFIFOL(login_fd,2) = sd->account_id; + WFIFOL(login_fd,6) = sd->login_id1; + WFIFOL(login_fd,10) = sd->login_id2; + WFIFOB(login_fd,14) = sd->sex; + WFIFOL(login_fd,15) = session[fd]->client_addr.sin_addr.s_addr; + WFIFOSET(login_fd,19); + } else { // if no login-server, we must refuse connection + WFIFOW(fd,0) = 0x6c; + WFIFOW(fd,2) = 0; + WFIFOSET(fd,3); + } + } + } + RFIFOSKIP(fd, 17); + break; + + case 0x66: // char select +// printf("0x66> request connect - account_id:%d/char_num:%d\n",sd->account_id,RFIFOB(fd, 2)); + if (RFIFOREST(fd) < 3) + return 0; + + sprintf(tmp_sql, "SELECT `char_id` FROM `%s` WHERE `account_id`='%d' AND `char_num`='%d'",char_db, sd->account_id, RFIFOB(fd, 2)); + if (mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + sql_res = mysql_store_result(&mysql_handle); + + sql_row = mysql_fetch_row(sql_res); + + if (sql_row) + mmo_char_fromsql(atoi(sql_row[0]), char_dat, 0); + else { + mysql_free_result(sql_res); + RFIFOSKIP(fd, 3); + break; + } + + sprintf(tmp_sql,"INSERT INTO `%s`(`time`, `account_id`,`char_num`,`name`) VALUES (NOW(), '%d', '%d', '%s')", + charlog_db, sd->account_id, RFIFOB(fd, 2), char_dat[0].name); + //query + if(mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + printf("(\033[1;64m%d\033[0m) char selected (\033[1;32m%d\033[0m) \033[1;32m%s\033[0m" RETCODE, sd->account_id, RFIFOB(fd, 2), char_dat[0].name); + + i = search_mapserver(char_dat[0].last_point.map); + + // if map is not found, we check major cities + if (i < 0) { + if ((i = search_mapserver("prontera.gat")) >= 0) { // check is done without 'gat'. + memcpy(char_dat[0].last_point.map, "prontera.gat", 16); + char_dat[0].last_point.x = 273; // savepoint coordonates + char_dat[0].last_point.y = 354; + } else if ((i = search_mapserver("geffen.gat")) >= 0) { // check is done without 'gat'. + memcpy(char_dat[0].last_point.map, "geffen.gat", 16); + char_dat[0].last_point.x = 120; // savepoint coordonates + char_dat[0].last_point.y = 100; + } else if ((i = search_mapserver("morocc.gat")) >= 0) { // check is done without 'gat'. + memcpy(char_dat[0].last_point.map, "morocc.gat", 16); + char_dat[0].last_point.x = 160; // savepoint coordonates + char_dat[0].last_point.y = 94; + } else if ((i = search_mapserver("alberta.gat")) >= 0) { // check is done without 'gat'. + memcpy(char_dat[0].last_point.map, "alberta.gat", 16); + char_dat[0].last_point.x = 116; // savepoint coordonates + char_dat[0].last_point.y = 57; + } else if ((i = search_mapserver("payon.gat")) >= 0) { // check is done without 'gat'. + memcpy(char_dat[0].last_point.map, "payon.gat", 16); + char_dat[0].last_point.x = 87; // savepoint coordonates + char_dat[0].last_point.y = 117; + } else if ((i = search_mapserver("izlude.gat")) >= 0) { // check is done without 'gat'. + memcpy(char_dat[0].last_point.map, "izlude.gat", 16); + char_dat[0].last_point.x = 94; // savepoint coordonates + char_dat[0].last_point.y = 103; + } else { + int j; + // get first online server + i = 0; + for(j = 0; j < MAX_MAP_SERVERS; j++) + if (server_fd[j] >= 0 && server[j].map[0][0]) { + i = j; + printf("Map-server #%d found with a map: '%s'.\n", j, server[j].map[0]); + break; + } + // if no map-servers are connected, we send: server closed + if (j == MAX_MAP_SERVERS) { + WFIFOW(fd,0) = 0x81; + WFIFOL(fd,2) = 1; // 01 = Server closed + WFIFOSET(fd,3); + RFIFOSKIP(fd,3); + break; + } + } + } + WFIFOW(fd, 0) =0x71; + WFIFOL(fd, 2) =char_dat[0].char_id; + memcpy(WFIFOP(fd, 6), char_dat[0].last_point.map, 16); + //Lan check added by Kashy + if (lan_ip_check(p)) + WFIFOL(fd, 22) = inet_addr(lan_map_ip); + else + WFIFOL(fd, 22) = server[i].ip; + WFIFOW(fd, 26) = server[i].port; + WFIFOSET(fd, 28); + + if (auth_fifo_pos >= AUTH_FIFO_SIZE) { + auth_fifo_pos = 0; + } +// printf("auth_fifo set (auth_fifo_pos:%d) - account_id:%d char_id:%d login_id1:%d\n", auth_fifo_pos, sd->account_id, char_dat[0].char_id, sd->login_id1); + auth_fifo[auth_fifo_pos].account_id = sd->account_id; + auth_fifo[auth_fifo_pos].char_id = char_dat[0].char_id; + auth_fifo[auth_fifo_pos].login_id1 = sd->login_id1; + auth_fifo[auth_fifo_pos].login_id2 = sd->login_id2; + auth_fifo[auth_fifo_pos].delflag = 0; + //auth_fifo[auth_fifo_pos].char_pos = sd->found_char[ch]; + auth_fifo[auth_fifo_pos].char_pos = 0; + auth_fifo[auth_fifo_pos].sex = sd->sex; + auth_fifo[auth_fifo_pos].connect_until_time = sd->connect_until_time; + auth_fifo[auth_fifo_pos].ip = session[fd]->client_addr.sin_addr.s_addr; + auth_fifo_pos++; +// printf("0x66> end\n"); + RFIFOSKIP(fd, 3); + break; + + case 0x67: // make new +// printf("0x67>request make new char\n"); + if (RFIFOREST(fd) < 37) + return 0; + i = make_new_char_sql(fd, RFIFOP(fd, 2)); + if (i < 0) { + WFIFOW(fd, 0) = 0x6e; + WFIFOB(fd, 2) = 0x00; + WFIFOSET(fd, 3); + RFIFOSKIP(fd, 37); + break; + } + + WFIFOW(fd, 0) = 0x6d; + memset(WFIFOP(fd, 2), 0x00, 106); + + mmo_char_fromsql(i, char_dat, 0); + i = 0; + WFIFOL(fd, 2) = char_dat[i].char_id; + WFIFOL(fd,2+4) = char_dat[i].base_exp; + WFIFOL(fd,2+8) = char_dat[i].zeny; + WFIFOL(fd,2+12) = char_dat[i].job_exp; + WFIFOL(fd,2+16) = char_dat[i].job_level; + + WFIFOL(fd,2+28) = char_dat[i].karma; + WFIFOL(fd,2+32) = char_dat[i].manner; + + WFIFOW(fd,2+40) = 0x30; + WFIFOW(fd,2+42) = (char_dat[i].hp > 0x7fff) ? 0x7fff : char_dat[i].hp; + WFIFOW(fd,2+44) = (char_dat[i].max_hp > 0x7fff) ? 0x7fff : char_dat[i].max_hp; + WFIFOW(fd,2+46) = (char_dat[i].sp > 0x7fff) ? 0x7fff : char_dat[i].sp; + WFIFOW(fd,2+48) = (char_dat[i].max_sp > 0x7fff) ? 0x7fff : char_dat[i].max_sp; + WFIFOW(fd,2+50) = DEFAULT_WALK_SPEED; // char_dat[i].speed; + WFIFOW(fd,2+52) = char_dat[i].class; + WFIFOW(fd,2+54) = char_dat[i].hair; + + WFIFOW(fd,2+58) = char_dat[i].base_level; + WFIFOW(fd,2+60) = char_dat[i].skill_point; + + WFIFOW(fd,2+64) = char_dat[i].shield; + WFIFOW(fd,2+66) = char_dat[i].head_top; + WFIFOW(fd,2+68) = char_dat[i].head_mid; + WFIFOW(fd,2+70) = char_dat[i].hair_color; + + memcpy(WFIFOP(fd,2+74), char_dat[i].name, 24); + + WFIFOB(fd,2+98) = char_dat[i].str; + WFIFOB(fd,2+99) = char_dat[i].agi; + WFIFOB(fd,2+100) = char_dat[i].vit; + WFIFOB(fd,2+101) = char_dat[i].int_; + WFIFOB(fd,2+102) = char_dat[i].dex; + WFIFOB(fd,2+103) = char_dat[i].luk; + WFIFOB(fd,2+104) = char_dat[i].char_num; + + WFIFOSET(fd, 108); + RFIFOSKIP(fd, 37); + //to do + for(ch = 0; ch < 9; ch++) { + if (sd->found_char[ch] == -1) { + sd->found_char[ch] = char_dat[i].char_id; + break; + } + } + + case 0x68: // delete + if (RFIFOREST(fd) < 46) + return 0; + printf("\033[1;31m Request Char Del:\033[0m \033[1;32m%d\033[0m(\033[1;32m%d\033[0m)\n", sd->account_id, RFIFOL(fd, 2)); + memcpy(email, RFIFOP(fd,6), 40); + sprintf(tmp_sql, "SELECT `email` FROM `%s` WHERE `%s`='%d'",login_db, login_db_account_id, sd->account_id); + if (mysql_query(&lmysql_handle, tmp_sql)) { + printf("\033[1;31m DB server Error Delete Char data - %s \033[0m \n", mysql_error(&lmysql_handle)); + } + sql_res = mysql_store_result(&lmysql_handle); + if (sql_res) { + sql_row = mysql_fetch_row(sql_res); + if (strcmp(email,sql_row[0]) == 0) { + mysql_free_result(sql_res); + } else { + WFIFOW(fd, 0) = 0x70; + WFIFOB(fd, 2) = 0; + WFIFOSET(fd, 3); + RFIFOSKIP(fd, 46); + mysql_free_result(sql_res); + break; + } + } else { + WFIFOW(fd, 0) = 0x70; + WFIFOB(fd, 2) = 0; + WFIFOSET(fd, 3); + RFIFOSKIP(fd, 46); + mysql_free_result(sql_res); + break; + } + sprintf(tmp_sql, "SELECT `name`,`partner_id` FROM `%s` WHERE `char_id`='%d'",char_db, RFIFOL(fd,2)); + if (mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + sql_res = mysql_store_result(&mysql_handle); + if(sql_res) + sql_row = mysql_fetch_row(sql_res); + + if (sql_res && sql_row[0]) { + //delete char from SQL + sprintf(tmp_sql,"DELETE FROM `%s` WHERE `char_id`='%d'",pet_db, RFIFOL(fd, 2)); + if(mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + sprintf(tmp_sql,"DELETE FROM `%s` WHERE `char_id`='%d'",inventory_db, RFIFOL(fd, 2)); + if(mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + + sprintf(tmp_sql,"DELETE FROM `%s` WHERE `char_id`='%d'",cart_db, RFIFOL(fd, 2)); + if(mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + + sprintf(tmp_sql,"DELETE FROM `%s` WHERE `char_id`='%d'",memo_db, RFIFOL(fd, 2)); + if(mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + + sprintf(tmp_sql,"DELETE FROM `%s` WHERE `char_id`='%d'",skill_db, RFIFOL(fd, 2)); + if(mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + + sprintf(tmp_sql,"DELETE FROM `%s` WHERE `char_id`='%d'",char_db, RFIFOL(fd, 2)); + if(mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + + //Divorce [Wizputer] + if (sql_row[1] != 0) { + char buf[64]; + sprintf(tmp_sql,"UPDATE `%s` SET `partner_id`='0' WHERE `char_id`='%d'",char_db,atoi(sql_row[1])); + if(mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + sprintf(tmp_sql,"DELETE FROM `%s` WHERE (`nameid`='%d' OR `nameid`='%d') AND `char_id`='%d'",inventory_db,WEDDING_RING_M,WEDDING_RING_F,atoi(sql_row[1])); + if(mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + WBUFW(buf,0) = 0x2b12; + WBUFL(buf,2) = atoi(sql_row[0]); + WBUFL(buf,6) = atoi(sql_row[1]); + mapif_sendall(buf,10); + } + // Also delete info from guildtables. + sprintf(tmp_sql,"DELETE FROM `%s` WHERE `char_id`='%d'",guild_member_db, RFIFOL(fd,2)); + if (mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + + sprintf(tmp_sql, "SELECT `guild_id` FROM `%s` WHERE `master` = '%s'", guild_db, sql_row[0]); + + if (mysql_query(&mysql_handle, tmp_sql) == 0) { + sql_res = mysql_store_result(&mysql_handle); + + if (sql_res != NULL) { + if (mysql_num_rows(sql_res) != 0) { + sql_row = mysql_fetch_row(sql_res); + + sprintf(tmp_sql,"DELETE FROM `%s` WHERE `guild_id` = '%d'", guild_db, atoi(sql_row[0])); + if (mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + + sprintf(tmp_sql,"DELETE FROM `%s` WHERE `guild_id` = '%d'", guild_member_db, atoi(sql_row[0])); + if (mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + + sprintf(tmp_sql,"DELETE FROM `%s` WHERE `guild_id` = '%d'", guild_castle_db, atoi(sql_row[0])); + if (mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + + sprintf(tmp_sql,"DELETE FROM `%s` WHERE `guild_id` = '%d'", guild_storage_db, atoi(sql_row[0])); + if (mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + + sprintf(tmp_sql,"DELETE FROM `%s` WHERE `guild_id` = '%d' OR `alliance_id` = '%d'", guild_alliance_db, atoi(sql_row[0]), atoi(sql_row[0])); + if (mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + + sprintf(tmp_sql,"DELETE FROM `%s` WHERE `guild_id` = '%d'", guild_position_db, atoi(sql_row[0])); + if (mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + + sprintf(tmp_sql,"DELETE FROM `%s` WHERE `guild_id` = '%d'", guild_skill_db, atoi(sql_row[0])); + if (mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + + sprintf(tmp_sql,"DELETE FROM `%s` WHERE `guild_id` = '%d'", guild_expulsion_db, atoi(sql_row[0])); + if (mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + + mysql_free_result(sql_res); + } + } else { + if (mysql_errno(&mysql_handle) != 0) { + printf("Database server error: %s\n", mysql_error(&mysql_handle)); + } + } + } else { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + } + + for(i = 0; i < 9; i++) { + printf("char comp: %d - %d (%d)\n", sd->found_char[i], RFIFOL(fd, 2), sd->account_id); + if (sd->found_char[i] == RFIFOL(fd, 2)) { + for(ch = i; ch < 9-1; ch++) + sd->found_char[ch] = sd->found_char[ch+1]; + sd->found_char[8] = -1; + break; + } + } + if (i == 9) { // reject + WFIFOW(fd, 0) = 0x70; + WFIFOB(fd, 2) = 0; + WFIFOSET(fd, 3); + } else { // deleted! + WFIFOW(fd, 0) = 0x6f; + WFIFOSET(fd, 2); + } + RFIFOSKIP(fd, 46); + break; + + case 0x2af8: // login as map-server + if (RFIFOREST(fd) < 60) + return 0; + WFIFOW(fd, 0) = 0x2af9; + for(i = 0; i < MAX_MAP_SERVERS; i++) { + if (server_fd[i] < 0) + break; + } + if (i == MAX_MAP_SERVERS || strcmp(RFIFOP(fd,2), userid) || strcmp(RFIFOP(fd,26), passwd)) { + WFIFOB(fd,2) = 3; + WFIFOSET(fd, 3); + } else { +// int len; + WFIFOB(fd,2) = 0; + WFIFOSET(fd, 3); + session[fd]->func_parse = parse_frommap; + server_fd[i] = fd; + if(anti_freeze_enable) + server_freezeflag[i] = 5; // Map anti-freeze system. Counter. 5 ok, 4...0 freezed + server[i].ip = RFIFOL(fd, 54); + server[i].port = RFIFOW(fd, 58); + server[i].users = 0; + memset(server[i].map, 0, sizeof(server[i].map)); + realloc_fifo(fd, FIFOSIZE_SERVERLINK, FIFOSIZE_SERVERLINK); + char_mapif_init(fd); + // send gm acccounts level to map-servers +/* removed by CLOWNISIUS due to isGM + len = 4; + WFIFOW(fd,0) = 0x2b15; + for(i = 0; i < GM_num; i++) { + WFIFOL(fd,len) = gm_account[i].account_id; + WFIFOB(fd,len+4) = (unsigned char)gm_account[i].level; + len += 5; + } + WFIFOW(fd,2) = len; + WFIFOSET(fd,len);*/ + } + RFIFOSKIP(fd,60); + break; + + case 0x187: // Alive? + if (RFIFOREST(fd) < 6) { + return 0; + } + RFIFOSKIP(fd, 6); + break; + + case 0x7530: // Athena info get + WFIFOW(fd, 0) = 0x7531; + WFIFOB(fd, 2) = ATHENA_MAJOR_VERSION; + WFIFOB(fd, 3) = ATHENA_MINOR_VERSION; + WFIFOB(fd, 4) = ATHENA_REVISION; + WFIFOB(fd, 5) = ATHENA_RELEASE_FLAG; + WFIFOB(fd, 6) = ATHENA_OFFICIAL_FLAG; + WFIFOB(fd, 7) = ATHENA_SERVER_INTER | ATHENA_SERVER_CHAR; + WFIFOW(fd, 8) = ATHENA_MOD_VERSION; + WFIFOSET(fd, 10); + RFIFOSKIP(fd, 2); + return 0; + + case 0x7532: // disconnect(default also disconnect) + default: + session[fd]->eof = 1; + return 0; + } + } + RFIFOFLUSH(fd); + + return 0; +} + +// MAP send all +int mapif_sendall(unsigned char *buf, unsigned int len) { + int i, c; + int fd; + + c = 0; + for(i = 0; i < MAX_MAP_SERVERS; i++) { + if ((fd = server_fd[i]) >= 0) { + memcpy(WFIFOP(fd,0), buf, len); + WFIFOSET(fd,len); + c++; + } + } + + return c; +} + +int mapif_sendallwos(int sfd, unsigned char *buf, unsigned int len) { + int i, c; + int fd; + + c = 0; + for(i=0, c=0;i<MAX_MAP_SERVERS;i++){ + if ((fd = server_fd[i]) >= 0 && fd != sfd) { + memcpy(WFIFOP(fd,0), buf, len); + WFIFOSET(fd, len); + c++; + } + } + + return c; +} + +int mapif_send(int fd, unsigned char *buf, unsigned int len) { + int i; + + if (fd >= 0) { + for(i = 0; i < MAX_MAP_SERVERS; i++) { + if (fd == server_fd[i]) { + memcpy(WFIFOP(fd,0), buf, len); + WFIFOSET(fd,len); + return 1; + } + } + } + return 0; +} + +int send_users_tologin(int tid, unsigned int tick, int id, int data) { + int users = count_users(); + char buf[16]; + + if (login_fd > 0 && session[login_fd]) { + // send number of user to login server + WFIFOW(login_fd,0) = 0x2714; + WFIFOL(login_fd,2) = users; + WFIFOSET(login_fd,6); + } + // send number of players to all map-servers + WBUFW(buf,0) = 0x2b00; + WBUFL(buf,2) = users; + mapif_sendall(buf, 6); + + return 0; +} + +int check_connect_login_server(int tid, unsigned int tick, int id, int data) { + if (login_fd <= 0 || session[login_fd] == NULL) { + printf("Attempt to connect to login-server...\n"); + login_fd = make_connection(login_ip, login_port); + session[login_fd]->func_parse = parse_tologin; + realloc_fifo(login_fd, FIFOSIZE_SERVERLINK, FIFOSIZE_SERVERLINK); + WFIFOW(login_fd,0) = 0x2710; + memset(WFIFOP(login_fd,2), 0, 24); + memcpy(WFIFOP(login_fd,2), userid, strlen(userid) < 24 ? strlen(userid) : 24); + memset(WFIFOP(login_fd,26), 0, 24); + memcpy(WFIFOP(login_fd,26), passwd, strlen(passwd) < 24 ? strlen(passwd) : 24); + WFIFOL(login_fd,50) = 0; + WFIFOL(login_fd,54) = char_ip; + WFIFOL(login_fd,58) = char_port; + memset(WFIFOP(login_fd,60), 0, 20); + memcpy(WFIFOP(login_fd,60), server_name, strlen(server_name) < 20 ? strlen(server_name) : 20); + WFIFOW(login_fd,80) = 0; + WFIFOW(login_fd,82) = char_maintenance; + WFIFOW(login_fd,84) = char_new; + WFIFOSET(login_fd,86); + } + return 0; +} + +//---------------------------------------------------------- +// Return numerical value of a switch configuration by [Yor] +// on/off, english, fran軋is, deutsch, espaol +//---------------------------------------------------------- +int config_switch(const char *str) { + if (strcmpi(str, "on") == 0 || strcmpi(str, "yes") == 0 || strcmpi(str, "oui") == 0 || strcmpi(str, "ja") == 0 || strcmpi(str, "si") == 0) + return 1; + if (strcmpi(str, "off") == 0 || strcmpi(str, "no") == 0 || strcmpi(str, "non") == 0 || strcmpi(str, "nein") == 0) + return 0; + + return atoi(str); +} + +// Lan Support conf reading added by Kashy +int char_lan_config_read(const char *lancfgName){ + char subnetmask[128]; + char line[1024], w1[1024], w2[1024]; + FILE *fp; + struct hostent * h = NULL; + + if ((fp = fopen(lancfgName, "r")) == NULL) { + printf("file not found: %s\n", lancfgName); + return 1; + } + + printf("Start reading of Lan Support configuration file\n"); + + while(fgets(line, sizeof(line)-1, fp)){ + if (line[0] == '/' && line[1] == '/') + continue; + + if (sscanf(line, "%[^:]: %[^\r\n]", w1, w2) != 2) + continue; + + else if (strcmpi(w1, "lan_map_ip") == 0) { + h = gethostbyname(w2); + if (h != NULL) { + sprintf(lan_map_ip, "%d.%d.%d.%d", (unsigned char)h->h_addr[0], (unsigned char)h->h_addr[1], (unsigned char)h->h_addr[2], (unsigned char)h->h_addr[3]); + } else { + strncpy(lan_map_ip, w2, sizeof(lan_map_ip)); + lan_map_ip[sizeof(lan_map_ip)-1] = 0; + } + printf("set Lan_map_IP : %s\n", lan_map_ip); + } + + else if (strcmpi(w1, "subnetmask") == 0) { + unsigned int k0, k1, k2, k3; + strcpy(subnetmask, w2); + sscanf(subnetmask, "%d.%d.%d.%d", &k0, &k1, &k2, &k3); + subnetmaski[0] = k0; subnetmaski[1] = k1; subnetmaski[2] = k2; subnetmaski[3] = k3; + printf("set subnetmask : %s\n", w2); + } + } + fclose(fp); + + printf("End reading of Lan Support configuration file\n"); + return 0; +} + +void do_final(void) { + printf("Doing final stage...\n"); + //mmo_char_sync(); + //inter_save(); + do_final_itemdb(); + //check SQL save progress. + //wait until save char complete + printf("waiting until char saving complete...\n"); + do { + sleep (0); + }while (save_flag != 0); + + sprintf(tmp_sql,"UPDATE `%s` SET `online`='0' WHERE `online`='1'", char_db); + if (mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error (insert `char`)- %s\n", mysql_error(&mysql_handle)); + } + + sprintf(tmp_sql,"DELETE FROM `ragsrvinfo"); + if (mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error (insert `char`)- %s\n", mysql_error(&mysql_handle)); + } + + if (gm_account != NULL) + free(gm_account); + + free(char_dat); + free(gm_account); + delete_session(login_fd); + delete_session(char_fd); + + mysql_close(&mysql_handle); + mysql_close(&lmysql_handle); + + printf("ok! all done...\n"); +} + +void sql_config_read(const char *cfgName){ /* Kalaspuff, to get login_db */ + char line[1024], w1[1024], w2[1024]; + FILE *fp; + + printf("reading configure: %s\n", cfgName); + + if ((fp = fopen(cfgName, "r")) == NULL) { + printf("file not found: %s\n", cfgName); + exit(1); + } + + while(fgets(line, sizeof(line)-1, fp)){ + if(line[0] == '/' && line[1] == '/') + continue; + + if (sscanf(line, "%[^:]: %[^\r\n]", w1, w2) != 2) + continue; + + if(strcmpi(w1, "login_db") == 0) { + strcpy(login_db, w2); + }else if(strcmpi(w1,"char_db")==0){ + strcpy(char_db,w2); + }else if(strcmpi(w1,"cart_db")==0){ + strcpy(cart_db,w2); + }else if(strcmpi(w1,"inventory_db")==0){ + strcpy(inventory_db,w2); + }else if(strcmpi(w1,"charlog_db")==0){ + strcpy(charlog_db,w2); + }else if(strcmpi(w1,"storage_db")==0){ + strcpy(storage_db,w2); + }else if(strcmpi(w1,"reg_db")==0){ + strcpy(reg_db,w2); + }else if(strcmpi(w1,"skill_db")==0){ + strcpy(skill_db,w2); + }else if(strcmpi(w1,"interlog_db")==0){ + strcpy(interlog_db,w2); + }else if(strcmpi(w1,"memo_db")==0){ + strcpy(memo_db,w2); + }else if(strcmpi(w1,"guild_db")==0){ + strcpy(guild_db,w2); + }else if(strcmpi(w1,"guild_alliance_db")==0){ + strcpy(guild_alliance_db,w2); + }else if(strcmpi(w1,"guild_castle_db")==0){ + strcpy(guild_castle_db,w2); + }else if(strcmpi(w1,"guild_expulsion_db")==0){ + strcpy(guild_expulsion_db,w2); + }else if(strcmpi(w1,"guild_member_db")==0){ + strcpy(guild_member_db,w2); + }else if(strcmpi(w1,"guild_skill_db")==0){ + strcpy(guild_skill_db,w2); + }else if(strcmpi(w1,"guild_position_db")==0){ + strcpy(guild_position_db,w2); + }else if(strcmpi(w1,"guild_storage_db")==0){ + strcpy(guild_storage_db,w2); + }else if(strcmpi(w1,"party_db")==0){ + strcpy(party_db,w2); + }else if(strcmpi(w1,"pet_db")==0){ + strcpy(pet_db,w2); + //Map server option to use SQL db or not + }else if(strcmpi(w1,"use_sql_db")==0){ // added for sql item_db read for char server [Valaris] + if (strcmpi(w2, "yes")) { + db_use_sqldbs = 1; + } else if (strcmpi(w2, "no")) { + db_use_sqldbs = 0; + } + printf("Using SQL dbs: %s\n",w2); + //custom columns for login database + }else if(strcmpi(w1,"login_db_level")==0){ + strcpy(login_db_level,w2); + }else if(strcmpi(w1,"login_db_account_id")==0){ + strcpy(login_db_account_id,w2); + }else if(strcmpi(w1,"lowest_gm_level")==0){ + lowest_gm_level = atoi(w2); + printf("set lowest_gm_level : %s\n",w2); + } + } + fclose(fp); + printf("reading configure done.....\n"); +} + +int char_config_read(const char *cfgName) { + struct hostent *h = NULL; + char line[1024], w1[1024], w2[1024]; + FILE *fp; + + if ((fp = fopen(cfgName, "r")) == NULL) { + printf("Configuration file not found: %s.\n", cfgName); + exit(1); + } + + while(fgets(line, sizeof(line)-1, fp)) { + if (line[0] == '/' && line[1] == '/') + continue; + + line[sizeof(line)-1] = '\0'; + if (sscanf(line,"%[^:]: %[^\r\n]", w1, w2) != 2) + continue; + + remove_control_chars(w1); + remove_control_chars(w2); + if (strcmpi(w1, "userid") == 0) { + memcpy(userid, w2, 24); + } else if (strcmpi(w1, "passwd") == 0) { + memcpy(passwd, w2, 24); + } else if (strcmpi(w1, "server_name") == 0) { + memcpy(server_name, w2, 16); + printf("%s server has been intialized\n", w2); + } else if (strcmpi(w1, "wisp_server_name") == 0) { + if (strlen(w2) >= 4) { + memcpy(wisp_server_name, w2, sizeof(wisp_server_name)); + wisp_server_name[sizeof(wisp_server_name) - 1] = '\0'; + } + } else if (strcmpi(w1, "login_ip") == 0) { + h = gethostbyname (w2); + if (h != NULL) { + printf("Login server IP address : %s -> %d.%d.%d.%d\n", w2, (unsigned char)h->h_addr[0], (unsigned char)h->h_addr[1], (unsigned char)h->h_addr[2], (unsigned char)h->h_addr[3]); + sprintf(login_ip_str, "%d.%d.%d.%d", (unsigned char)h->h_addr[0], (unsigned char)h->h_addr[1], (unsigned char)h->h_addr[2], (unsigned char)h->h_addr[3]); + } else + memcpy(login_ip_str,w2,16); + } else if (strcmpi(w1, "login_port") == 0) { + login_port=atoi(w2); + } else if (strcmpi(w1, "char_ip") == 0) { + h = gethostbyname (w2); + if(h != NULL) { + printf("Character server IP address : %s -> %d.%d.%d.%d\n", w2, (unsigned char)h->h_addr[0], (unsigned char)h->h_addr[1], (unsigned char)h->h_addr[2], (unsigned char)h->h_addr[3]); + sprintf(char_ip_str, "%d.%d.%d.%d", (unsigned char)h->h_addr[0], (unsigned char)h->h_addr[1], (unsigned char)h->h_addr[2], (unsigned char)h->h_addr[3]); + } else + memcpy(char_ip_str, w2, 16); + } else if (strcmpi(w1, "char_port") == 0) { + char_port = atoi(w2); + } else if (strcmpi(w1, "char_maintenance") == 0) { + char_maintenance = atoi(w2); + } else if (strcmpi(w1, "char_new")==0){ + char_new = atoi(w2); + } else if (strcmpi(w1, "max_connect_user") == 0) { + max_connect_user = atoi(w2); + if (max_connect_user < 0) + max_connect_user = 0; // unlimited online players + } else if (strcmpi(w1, "check_ip_flag") == 0) { + check_ip_flag = config_switch(w2); + } else if (strcmpi(w1, "autosave_time") == 0) { + autosave_interval = atoi(w2)*1000; + if (autosave_interval <= 0) + autosave_interval = DEFAULT_AUTOSAVE_INTERVAL; + } else if (strcmpi(w1, "start_point") == 0) { + char map[32]; + int x, y; + if (sscanf(w2,"%[^,],%d,%d", map, &x, &y) < 3) + continue; + if (strstr(map, ".gat") != NULL) { // Verify at least if '.gat' is in the map name + memcpy(start_point.map, map, 16); + start_point.x = x; + start_point.y = y; + } + } else if (strcmpi(w1, "start_zeny") == 0) { + start_zeny = atoi(w2); + if (start_zeny < 0) + start_zeny = 0; + } else if (strcmpi(w1, "start_weapon") == 0) { + start_zeny = atoi(w2); + if (start_weapon < 0) + start_weapon = 0; + } else if (strcmpi(w1, "start_armor") == 0) { + start_zeny = atoi(w2); + if (start_armor < 0) + start_armor = 0; + } else if (strcmpi(w1, "unknown_char_name") == 0) { + strcpy(unknown_char_name, w2); + unknown_char_name[24] = 0; + } else if (strcmpi(w1, "name_ignoring_case") == 0) { + name_ignoring_case = config_switch(w2); + } else if (strcmpi(w1, "char_name_option") == 0) { + char_name_option = atoi(w2); + } else if (strcmpi(w1, "char_name_letters") == 0) { + strcpy(char_name_letters, w2); + } else if (strcmpi(w1, "check_ip_flag") == 0) { + check_ip_flag = config_switch(w2); + // anti-freeze options [Valaris] + } else if(strcmpi(w1,"anti_freeze_enable")==0){ + anti_freeze_enable = config_switch(w2); + } else if (strcmpi(w1, "anti_freeze_interval") == 0) { + ANTI_FREEZE_INTERVAL = atoi(w2); + if (ANTI_FREEZE_INTERVAL < 5) + ANTI_FREEZE_INTERVAL = 5; // minimum 5 seconds + } else if (strcmpi(w1, "import") == 0) { + char_config_read(w2); + } + } + fclose(fp); + +//Read ItemDB + do_init_itemdb(); + + return 0; +} + +int do_init(int argc, char **argv){ + int i; + + for(i = 0; i < MAX_MAP_SERVERS; i++) { + memset(&server[i], 0, sizeof(struct mmo_map_server)); + server_fd[i] = -1; + } + + char_config_read((argc < 2) ? CHAR_CONF_NAME : argv[1]); + char_lan_config_read((argc > 1) ? argv[1] : LAN_CONF_NAME); + sql_config_read(SQL_CONF_NAME); + + printf("charserver configuration reading done.....\n"); + + inter_init((argc > 2) ? argv[2] : inter_cfgName); // inter server テハア篳ュ + printf("interserver configuration reading done.....\n"); + + printf("start char server initializing.....\n"); + mmo_char_sql_init(); + printf("char server initializing done.....\n"); + + printf("set parser -> parse_char().....\n"); + set_defaultparse(parse_char); + + printf("set terminate function -> do_final().....\n"); + set_termfunc(do_final); + + printf("open port %d.....\n",char_port); + char_fd = make_listen_port(char_port); + + login_ip = inet_addr(login_ip_str); + char_ip = inet_addr(char_ip_str); + + // send ALIVE PING to login server. + printf("add interval tic (check_connect_login_server)....\n"); + i = add_timer_interval(gettick() + 10, check_connect_login_server, 0, 0, 10 * 1000); + + // send USER COUNT PING to login server. + printf("add interval tic (send_users_tologin)....\n"); + i = add_timer_interval(gettick() + 10, send_users_tologin, 0, 0, 5 * 1000); + + //no need to set sync timer on SQL version. + //printf("add interval tic (mmo_char_sync_timer)....\n"); + //i = add_timer_interval(gettick() + 10, mmo_char_sync_timer, 0, 0, autosave_interval); + + if(anti_freeze_enable > 0) { + add_timer_func_list(map_anti_freeze_system, "map_anti_freeze_system"); + i = add_timer_interval(gettick() + 1000, map_anti_freeze_system, 0, 0, ANTI_FREEZE_INTERVAL * 1000); // checks every X seconds user specifies + } + + read_gm_account(); + + printf("char server init func end (now unlimited loop start!)....\n"); + printf("The char-server is \033[1;32mready\033[0m (Server is listening on the port %d).\n\n", char_port); + return 0; +} + + diff --git a/src/char_sql/char.h b/src/char_sql/char.h new file mode 100644 index 0000000..116a301 --- /dev/null +++ b/src/char_sql/char.h @@ -0,0 +1,82 @@ +#include "../common/core.h" +#include "../common/socket.h" +#include "../common/timer.h" +#include "../common/mmo.h" +#include "../common/version.h" +#include "../common/db.h" + +#ifndef _CHAR_H_ +#define _CHAR_H_ + +#define MAX_MAP_SERVERS 30 + +#define LAN_CONF_NAME "conf/lan_support.conf" + +#define DEFAULT_AUTOSAVE_INTERVAL 300*1000 + +struct mmo_map_server{ + long ip; + short port; + int users; + char map[MAX_MAP_PER_SERVER][16]; +}; +struct itemtmp { + int flag;//checked = 1 else 0 + int id; + short nameid; + short amount; + unsigned short equip; + char identify; + char refine; + char attribute; + short card[4]; + short broken; +}; +enum { + TABLE_INVENTORY, + TABLE_CART, + TABLE_STORAGE, + TABLE_GUILD_STORAGE, +}; +struct itemtemp{ + struct itemtmp equip[MAX_GUILD_STORAGE],notequip[MAX_GUILD_STORAGE]; +}; +int memitemdata_to_sql(struct itemtemp mapitem, int eqcount, int noteqcount, int char_id,int tableswitch); +int mapif_sendall(unsigned char *buf,unsigned int len); +int mapif_sendallwos(int fd,unsigned char *buf,unsigned int len); +int mapif_send(int fd,unsigned char *buf,unsigned int len); + +extern int autosave_interval; +extern char char_db[256]; +extern char cart_db[256]; +extern char inventory_db[256]; +extern char charlog_db[256]; +extern char storage_db[256]; +extern char interlog_db[256]; +extern char reg_db[256]; +extern char skill_db[256]; +extern char memo_db[256]; +extern char guild_db[256]; +extern char guild_alliance_db[256]; +extern char guild_castle_db[256]; +extern char guild_expulsion_db[256]; +extern char guild_member_db[256]; +extern char guild_position_db[256]; +extern char guild_skill_db[256]; +extern char guild_storage_db[256]; +extern char party_db[256]; +extern char pet_db[256]; + +int db_use_sqldbs; // added for sql item_db read for char server [Valaris] +extern char login_db_level[32]; +extern char login_db_account_id[32]; + +extern int lowest_gm_level; + +#endif + +#include "inter.h" +#include "int_pet.h" +#include "int_guild.h" +#include "int_party.h" +#include "int_storage.h" diff --git a/src/char_sql/int_guild.c b/src/char_sql/int_guild.c new file mode 100644 index 0000000..1983896 --- /dev/null +++ b/src/char_sql/int_guild.c @@ -0,0 +1,1604 @@ +// +// original code from athena +// SQL conversion by hack +// + +#include "char.h" +#include "strlib.h" +#include "int_storage.h" +#include "inter.h" +#include "int_guild.h" +#include "int_storage.h" +#include "mmo.h" +#include "socket.h" + +#include <string.h> +#include <stdio.h> +#include <stdlib.h> + +static struct guild *guild_pt; +static struct guild *guild_pt2; +static struct guild_castle * guildcastle_pt; +static int guild_newid=10000; + +static int guild_exp[100]; + +int mapif_parse_GuildLeave(int fd,int guild_id,int account_id,int char_id,int flag,const char *mes); +int mapif_guild_broken(int guild_id,int flag); +int guild_check_empty(struct guild *g); +int guild_calcinfo(struct guild *g); +int mapif_guild_basicinfochanged(int guild_id,int type,const void *data,int len); +int mapif_guild_info(int fd,struct guild *g); +int guild_break_sub(void *key,void *data,va_list ap); + + +// Save guild into sql +int inter_guild_tosql(struct guild *g,int flag) +{ + // 1 `guild` (`guild_id`, `name`,`master`,`guild_lv`,`connect_member`,`max_member`,`average_lv`,`exp`,`next_exp`,`skill_point`,`castle_id`,`mes1`,`mes2`,`emblem_len`,`emblem_id`,`emblem_data`) + // 2 `guild_member` (`guild_id`,`account_id`,`char_id`,`hair`,`hair_color`,`gender`,`class`,`lv`,`exp`,`exp_payper`,`online`,`position`,`rsv1`,`rsv2`,`name`) + // 4 `guild_position` (`guild_id`,`position`,`name`,`mode`,`exp_mode`) + // 8 `guild_alliance` (`guild_id`,`opposition`,`alliance_id`,`name`) + // 16 `guild_expulsion` (`guild_id`,`name`,`mes`,`acc`,`account_id`,`rsv1`,`rsv2`,`rsv3`) + // 32 `guild_skill` (`guild_id`,`id`,`lv`) + + char t_name[100],t_master[24],t_mes1[60],t_mes2[120],t_member[24],t_position[24],t_alliance[24]; // temporay storage for str convertion; + char t_ename[24],t_emes[40]; + char emblem_data[4096]; + int i=0; + int guild_exist=0,guild_member=0,guild_online_member=0; + + if (g->guild_id<=0) return -1; + + printf("(\033[1;35m%d\033[0m) Request save guild - ",g->guild_id); + + jstrescapecpy(t_name, g->name); + + //printf("- Check if guild %d exists\n",g->guild_id); + sprintf(tmp_sql, "SELECT count(*) FROM `%s` WHERE `guild_id`='%d'",guild_db,g->guild_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (delete `guild`)- %s\n", mysql_error(&mysql_handle) ); + } + sql_res = mysql_store_result(&mysql_handle) ; + if (sql_res!=NULL && mysql_num_rows(sql_res)>0) { + sql_row = mysql_fetch_row(sql_res); + guild_exist = atoi (sql_row[0]); + //printf("- Check if guild %d exists : %s\n",g->guild_id,((guild_exist==0)?"No":"Yes")); + } + mysql_free_result(sql_res) ; //resource free + + if (guild_exist >0){ + // Check members in party + sprintf(tmp_sql,"SELECT count(*) FROM `%s` WHERE `guild_id`='%d'",guild_member_db, g->guild_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle) ); + return -1; + } + sql_res = mysql_store_result(&mysql_handle) ; + if (sql_res!=NULL && mysql_num_rows(sql_res)>0) { + sql_row = mysql_fetch_row(sql_res); + + guild_member = atoi (sql_row[0]); + // printf("- Check members in guild %d : %d \n",g->guild_id,guild_member); + + } + mysql_free_result(sql_res) ; //resource free + + // Delete old guild from sql + if (flag&1||guild_member==0){ + // printf("- Delete guild %d from guild\n",g->guild_id); + sprintf(tmp_sql, "DELETE FROM `%s` WHERE `guild_id`='%d'",guild_db, g->guild_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (delete `guild`)- %s\n", mysql_error(&mysql_handle) ); + } + } + if (flag&2||guild_member==0){ + // printf("- Delete guild %d from guild_member\n",g->guild_id); + sprintf(tmp_sql, "DELETE FROM `%s` WHERE `guild_id`='%d'",guild_member_db, g->guild_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (delete `guild_member`)- %s\n", mysql_error(&mysql_handle) ); + } + sprintf(tmp_sql, "UPDATE `%s` SET `guild_id`='0' WHERE `guild_id`='%d'",char_db, g->guild_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (update `char`)- %s\n", mysql_error(&mysql_handle) ); + } + } + if (flag&32||guild_member==0){ + // printf("- Delete guild %d from guild_skill\n",g->guild_id); + sprintf(tmp_sql, "DELETE FROM `%s` WHERE `guild_id`='%d'",guild_skill_db, g->guild_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (delete `guild_skill`)- %s\n", mysql_error(&mysql_handle) ); + } + } + if (flag&4||guild_member==0){ + // printf("- Delete guild %d from guild_position\n",g->guild_id); + sprintf(tmp_sql, "DELETE FROM `%s` WHERE `guild_id`='%d'",guild_position_db, g->guild_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (delete `guild_position`)- %s\n", mysql_error(&mysql_handle) ); + } + } + if (flag&16||guild_member==0){ + // printf("- Delete guild %d from guild_expulsion\n",g->guild_id); + sprintf(tmp_sql, "DELETE FROM `%s` WHERE `guild_id`='%d'",guild_expulsion_db, g->guild_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (delete `guild_expulsion`)- %s\n", mysql_error(&mysql_handle) ); + } + } + if (flag&8||guild_member==0){ + // printf("- Delete guild %d from guild_alliance\n",g->guild_id); + sprintf(tmp_sql, "DELETE FROM `%s` WHERE `guild_id`='%d' OR `alliance_id`='%d'",guild_alliance_db, g->guild_id,g->guild_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (delete `guild_alliance`)- %s\n", mysql_error(&mysql_handle) ); + } + } + if (flag&2||guild_member==0){ + // printf("- Delete guild %d from char\n",g->guild_id); + sprintf(tmp_sql, "UPDATE `%s` SET `guild_id`='0' WHERE `guild_id`='%d'",char_db, g->guild_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (delete `guild_alliance`)- %s\n", mysql_error(&mysql_handle) ); + } + } + if (guild_member==0){ + // printf("- Delete guild %d from guild_castle\n",g->guild_id); + sprintf(tmp_sql, "DELETE FROM `%s` WHERE `guild_id`='%d'",guild_castle_db, g->guild_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (delete `guild_castle`)- %s\n", mysql_error(&mysql_handle) ); + } + } + } + + guild_online_member = 0; + i=0; + while (i<g->max_member) { + if (g->member[i].account_id>0) guild_online_member++; + i++; + } + + // No member in guild , no need to create it in sql + if (guild_member <= 0 && guild_online_member <=0) { + inter_guild_storage_delete(g->guild_id); + printf("No member in guild %d , break it! \n",g->guild_id); + return -2; + } + + // Insert new guild to sqlserver + if (flag&1||guild_member==0){ + int len=0; + //printf("- Insert guild %d to guild\n",g->guild_id); + for(i=0;i<g->emblem_len;i++){ + len+=sprintf(emblem_data+len,"%02x",(unsigned char)(g->emblem_data[i])); + //printf("%02x",(unsigned char)(g->emblem_data[i])); + } + emblem_data[len] = '\0'; + //printf("- emblem_len = %d \n",g->emblem_len); + sprintf(tmp_sql,"INSERT INTO `%s` " + "(`guild_id`, `name`,`master`,`guild_lv`,`connect_member`,`max_member`,`average_lv`,`exp`,`next_exp`,`skill_point`,`castle_id`,`mes1`,`mes2`,`emblem_len`,`emblem_id`,`emblem_data`) " + "VALUES ('%d', '%s', '%s', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%s', '%s', '%d', '%d', '%s')", + guild_db, g->guild_id,t_name,jstrescapecpy(t_master,g->master), + g->guild_lv,g->connect_member,g->max_member,g->average_lv,g->exp,g->next_exp,g->skill_point,g->castle_id, + jstrescapecpy(t_mes1,g->mes1),jstrescapecpy(t_mes2,g->mes2),g->emblem_len,g->emblem_id,emblem_data); + //printf(" %s\n",tmp_sql); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (insert `guild`)- %s\n", mysql_error(&mysql_handle) ); + } + } + + if (flag&2||guild_member==0){ + //printf("- Insert guild %d to guild_member\n",g->guild_id); + for(i=0;i<g->max_member;i++){ + if (g->member[i].account_id>0){ + struct guild_member *m = &g->member[i]; + sprintf(tmp_sql,"DELETE FROM `%s` WHERE `char_id`='%d'",guild_member_db, m->char_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (delete `guild_member`)- %s\n", mysql_error(&mysql_handle) ); + } + sprintf(tmp_sql,"INSERT INTO `%s` " + "(`guild_id`,`account_id`,`char_id`,`hair`,`hair_color`,`gender`,`class`,`lv`,`exp`,`exp_payper`,`online`,`position`,`rsv1`,`rsv2`,`name`) " + "VALUES ('%d','%d','%d','%d','%d', '%d','%d','%d','%d','%d','%d','%d','%d','%d','%s')", + guild_member_db, g->guild_id, + m->account_id,m->char_id, + m->hair,m->hair_color,m->gender, + m->class,m->lv,m->exp,m->exp_payper,m->online,m->position, + 0,0, + jstrescapecpy(t_member,m->name)); + //printf(" %s\n",tmp_sql); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (insert `guild_member`)- %s\n", mysql_error(&mysql_handle) ); + } + sprintf(tmp_sql, "UPDATE `%s` SET `guild_id`='%d' WHERE `account_id`='%d' AND `char_id`='%d'",char_db, g->guild_id,m->account_id,m->char_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (update `char`)- %s\n", mysql_error(&mysql_handle) ); + } + } + } + } + + if (flag&4||guild_member==0){ + //printf("- Insert guild %d to guild_position\n",g->guild_id); + for(i=0;i<MAX_GUILDPOSITION;i++){ + struct guild_position *p = &g->position[i]; + sprintf(tmp_sql,"INSERT INTO `%s` (`guild_id`,`position`,`name`,`mode`,`exp_mode`) VALUES ('%d','%d', '%s','%d','%d')", + guild_position_db, g->guild_id, i, jstrescapecpy(t_position,p->name),p->mode,p->exp_mode); + //printf(" %s\n",tmp_sql); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (insert `guild_position`)- %s\n", mysql_error(&mysql_handle) ); + } + } + } + + if (flag&8||guild_member==0){ + //printf("- Insert guild %d to guild_alliance\n",g->guild_id); + for(i=0;i<MAX_GUILDALLIANCE;i++){ + struct guild_alliance *a=&g->alliance[i]; + if(a->guild_id>0){ + sprintf(tmp_sql,"INSERT INTO `%s` (`guild_id`,`opposition`,`alliance_id`,`name`) " + "VALUES ('%d','%d','%d','%s')", + guild_alliance_db, g->guild_id,a->opposition,a->guild_id,jstrescapecpy(t_alliance,a->name)); + //printf(" %s\n",tmp_sql); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (insert `guild_alliance`)- %s\n", mysql_error(&mysql_handle) ); + } + sprintf(tmp_sql,"INSERT INTO `%s` (`guild_id`,`opposition`,`alliance_id`,`name`) " + "VALUES ('%d','%d','%d','%s')", + guild_alliance_db, a->guild_id,a->opposition,g->guild_id,t_name); + //printf(" %s\n",tmp_sql); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (insert `guild_alliance`)- %s\n", mysql_error(&mysql_handle) ); + } + } + } + } + + if (flag&16||guild_member==0){ + //printf("- Insert guild %d to guild_expulsion\n",g->guild_id); + for(i=0;i<MAX_GUILDEXPLUSION;i++){ + struct guild_explusion *e=&g->explusion[i]; + if(e->account_id>0){ + sprintf(tmp_sql,"INSERT INTO `%s` (`guild_id`,`name`,`mes`,`acc`,`account_id`,`rsv1`,`rsv2`,`rsv3`) " + "VALUES ('%d','%s','%s','%s','%d','%d','%d','%d')", + guild_expulsion_db, g->guild_id, + jstrescapecpy(t_ename,e->name),jstrescapecpy(t_emes,e->mes),e->acc,e->account_id,e->rsv1,e->rsv2,e->rsv3 ); + //printf(" %s\n",tmp_sql); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (insert `guild_expulsion`)- %s\n", mysql_error(&mysql_handle) ); + } + } + } + } + + if (flag&32||guild_member==0){ + //printf("- Insert guild %d to guild_skill\n",g->guild_id); + for(i=0;i<MAX_GUILDSKILL;i++){ + if (g->skill[i].id>0){ + sprintf(tmp_sql,"INSERT INTO `%s` (`guild_id`,`id`,`lv`) VALUES ('%d','%d','%d')", + guild_skill_db, g->guild_id,g->skill[i].id,g->skill[i].lv); + //printf("%s\n",tmp_sql); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (insert `guild_skill`)- %s\n", mysql_error(&mysql_handle) ); + } + } + } + } + + printf("Save guild done\n"); + return 0; +} + +// Read guild from sql +int inter_guild_fromsql(int guild_id,struct guild *g) +{ + int i; + char emblem_data[4096]; + char *pstr; + + if (g==NULL) return 0; + memset(g,0,sizeof(struct guild)); + if (guild_id==0) return 0; + +// printf("Retrieve guild information from sql ......\n"); +// printf("- Read guild %d from sql \n",guild_id); + + sprintf(tmp_sql,"SELECT `guild_id`, `name`,`master`,`guild_lv`,`connect_member`,`max_member`,`average_lv`,`exp`,`next_exp`,`skill_point`,`castle_id`,`mes1`,`mes2`,`emblem_len`,`emblem_id`,`emblem_data` " + "FROM `%s` WHERE `guild_id`='%d'",guild_db, guild_id); + //printf(" %s\n",tmp_sql); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (select `guild`)- %s\n", mysql_error(&mysql_handle) ); + return 0; + } + + sql_res = mysql_store_result(&mysql_handle) ; + if (sql_res!=NULL && mysql_num_rows(sql_res)>0) { + sql_row = mysql_fetch_row(sql_res); + if (sql_row==NULL) { + mysql_free_result(sql_res); + return 0; + } + + g->guild_id=atoi(sql_row[0]); + strncpy(g->name,sql_row[1],24); + strncpy(g->master,sql_row[2],24); + g->guild_lv=atoi(sql_row[3]); + g->connect_member=atoi(sql_row[4]); + g->max_member=atoi(sql_row[5]); + g->average_lv=atoi(sql_row[6]); + g->exp=atoi(sql_row[7]); + g->next_exp=atoi(sql_row[8]); + g->skill_point=atoi(sql_row[9]); + g->castle_id=atoi(sql_row[10]); + strncpy(g->mes1,sql_row[11],60); + strncpy(g->mes2,sql_row[12],120); + g->emblem_len=atoi(sql_row[13]); + g->emblem_id=atoi(sql_row[14]); + strncpy(emblem_data,sql_row[15],4096); + for(i=0,pstr=emblem_data;i<g->emblem_len;i++,pstr+=2){ + int c1=pstr[0],c2=pstr[1],x1=0,x2=0; + if(c1>='0' && c1<='9')x1=c1-'0'; + if(c1>='a' && c1<='f')x1=c1-'a'+10; + if(c1>='A' && c1<='F')x1=c1-'A'+10; + if(c2>='0' && c2<='9')x2=c2-'0'; + if(c2>='a' && c2<='f')x2=c2-'a'+10; + if(c2>='A' && c2<='F')x2=c2-'A'+10; + g->emblem_data[i]=(x1<<4)|x2; + } + } + mysql_free_result(sql_res); + + //printf("- Read guild_member %d from sql \n",guild_id); + sprintf(tmp_sql,"SELECT `guild_id`,`account_id`,`char_id`,`hair`,`hair_color`,`gender`,`class`,`lv`,`exp`,`exp_payper`,`online`,`position`,`rsv1`,`rsv2`,`name` " + "FROM `%s` WHERE `guild_id`='%d' ORDER BY `position`", guild_member_db, guild_id); + //printf(" %s\n",tmp_sql); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (select `guild_member`)- %s\n", mysql_error(&mysql_handle) ); + return 0; + } + sql_res = mysql_store_result(&mysql_handle) ; + if (sql_res!=NULL && mysql_num_rows(sql_res)>0) { + int i; + for(i=0;((sql_row = mysql_fetch_row(sql_res))&&i<g->max_member);i++){ + struct guild_member *m = &g->member[i]; + m->account_id=atoi(sql_row[1]); + m->char_id=atoi(sql_row[2]); + m->hair=atoi(sql_row[3]); + m->hair_color=atoi(sql_row[4]); + m->gender=atoi(sql_row[5]); + m->class=atoi(sql_row[6]); + m->lv=atoi(sql_row[7]); + m->exp=atoi(sql_row[8]); + m->exp_payper=atoi(sql_row[9]); + m->online=atoi(sql_row[10]); + m->position=atoi(sql_row[11]); + strncpy(m->name,sql_row[14],24); + } + } + mysql_free_result(sql_res); + + //printf("- Read guild_position %d from sql \n",guild_id); + sprintf(tmp_sql,"SELECT `guild_id`,`position`,`name`,`mode`,`exp_mode` FROM `%s` WHERE `guild_id`='%d'",guild_position_db, guild_id); + //printf(" %s\n",tmp_sql); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (select `guild_position`)- %s\n", mysql_error(&mysql_handle) ); + return 0; + } + sql_res = mysql_store_result(&mysql_handle) ; + if (sql_res!=NULL && mysql_num_rows(sql_res)>0) { + int i; + for(i=0;((sql_row = mysql_fetch_row(sql_res))&&i<MAX_GUILDPOSITION);i++){ + int position = atoi(sql_row[1]); + struct guild_position *p = &g->position[position]; + strncpy(p->name,sql_row[2],24); + p->mode=atoi(sql_row[3]); + p->exp_mode=atoi(sql_row[4]); + } + } + mysql_free_result(sql_res); + + //printf("- Read guild_alliance %d from sql \n",guild_id); + sprintf(tmp_sql,"SELECT `guild_id`,`opposition`,`alliance_id`,`name` FROM `%s` WHERE `guild_id`='%d'",guild_alliance_db, guild_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (select `guild_alliance`)- %s\n", mysql_error(&mysql_handle) ); + return 0; + } + sql_res = mysql_store_result(&mysql_handle) ; + if (sql_res!=NULL && mysql_num_rows(sql_res)>0) { + int i; + for(i=0;((sql_row = mysql_fetch_row(sql_res))&&i<MAX_GUILDALLIANCE);i++){ + struct guild_alliance *a = &g->alliance[i]; + a->opposition=atoi(sql_row[1]); + a->guild_id=atoi(sql_row[2]); + strncpy(a->name,sql_row[3],24); + } + } + mysql_free_result(sql_res); + + //printf("- Read guild_expulsion %d from sql \n",guild_id); + sprintf(tmp_sql,"SELECT `guild_id`,`name`,`mes`,`acc`,`account_id`,`rsv1`,`rsv2`,`rsv3` FROM `%s` WHERE `guild_id`='%d'",guild_expulsion_db, guild_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (select `guild_expulsion`)- %s\n", mysql_error(&mysql_handle) ); + return 0; + } + sql_res = mysql_store_result(&mysql_handle) ; + if (sql_res!=NULL && mysql_num_rows(sql_res)>0) { + int i; + for(i=0;((sql_row = mysql_fetch_row(sql_res))&&i<MAX_GUILDEXPLUSION);i++){ + struct guild_explusion *e = &g->explusion[i]; + + strncpy(e->name,sql_row[1],24); + strncpy(e->mes,sql_row[2],40); + strncpy(e->acc,sql_row[3],24); + e->account_id=atoi(sql_row[4]); + e->rsv1=atoi(sql_row[5]); + e->rsv2=atoi(sql_row[6]); + e->rsv3=atoi(sql_row[7]); + + } + } + mysql_free_result(sql_res); + + //printf("- Read guild_skill %d from sql \n",guild_id); + sprintf(tmp_sql,"SELECT `guild_id`,`id`,`lv` FROM `%s` WHERE `guild_id`='%d' ORDER BY `id`",guild_skill_db, guild_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (select `guild_skill`)- %s\n", mysql_error(&mysql_handle) ); + return 0; + } + sql_res = mysql_store_result(&mysql_handle) ; + if (sql_res!=NULL && mysql_num_rows(sql_res)>0) { + int i; + for(i=0;((sql_row = mysql_fetch_row(sql_res))&&i<MAX_GUILDSKILL);i++){ + g->skill[i].id=atoi(sql_row[1]); + g->skill[i].lv=atoi(sql_row[2]); + } + } + mysql_free_result(sql_res); + +// printf("Successfully retrieve guild information from sql!\n"); + + return 0; + +} + +// Save guild_castle to sql +int inter_guildcastle_tosql(struct guild_castle *gc) +{ + // `guild_castle` (`castle_id`, `guild_id`, `economy`, `defense`, `triggerE`, `triggerD`, `nextTime`, `payTime`, `createTime`, `visibleC`, `visibleG0`, `visibleG1`, `visibleG2`, `visibleG3`, `visibleG4`, `visibleG5`, `visibleG6`, `visibleG7`) + + if (gc==NULL) return 0; + //printf("Save to guild_castle\n"); + sprintf(tmp_sql,"DELETE FROM `%s` WHERE `castle_id`='%d'",guild_castle_db, gc->castle_id); + //printf(" %s\n",tmp_sql); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle) ); + return 0; + } + + sprintf(tmp_sql,"INSERT INTO `%s` " + "(`castle_id`, `guild_id`, `economy`, `defense`, `triggerE`, `triggerD`, `nextTime`, `payTime`, `createTime`," + "`visibleC`, `visibleG0`, `visibleG1`, `visibleG2`, `visibleG3`, `visibleG4`, `visibleG5`, `visibleG6`, `visibleG7`," + "`Ghp0`, `Ghp1`, `Ghp2`, `Ghp3`, `Ghp4`, `Ghp5`, `Ghp6`, `Ghp7`)" + "VALUES ('%d','%d','%d','%d','%d','%d','%d','%d','%d','%d','%d','%d','%d','%d','%d','%d','%d','%d','%d','%d','%d','%d','%d','%d','%d','%d')", + guild_castle_db, gc->castle_id, gc->guild_id, gc->economy, gc->defense, gc->triggerE, gc->triggerD, gc->nextTime, gc->payTime, + gc->createTime, gc->visibleC, gc->visibleG0, gc->visibleG1, gc->visibleG2, gc->visibleG3, gc->visibleG4, gc->visibleG5, + gc->visibleG6, gc->visibleG7, gc->Ghp0, gc->Ghp1, gc->Ghp2, gc->Ghp3, gc->Ghp4, gc->Ghp5, gc->Ghp6, gc->Ghp7); + //printf(" %s\n",tmp_sql); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle) ); + return 0; + } + + sprintf(tmp_sql,"UPDATE `%s` SET `castle_id`='-1' WHERE `castle_id`='%d'",guild_db, gc->castle_id); + //printf(" %s\n",tmp_sql); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle) ); + return 0; + } + + sprintf(tmp_sql,"UPDATE `%s` SET `castle_id`='%d' WHERE `guild_id`='%d'",guild_db, gc->castle_id,gc->guild_id); + //printf(" %s\n",tmp_sql); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle) ); + return 0; + } + + return 0; +} +// Read guild_castle from sql +int inter_guildcastle_fromsql(int castle_id,struct guild_castle *gc) +{ + + if (gc==NULL) return 0; + //printf("Read from guild_castle\n"); + memset(gc,0,sizeof(struct guild_castle)); + gc->castle_id=castle_id; + if (castle_id==-1) return 0; + sprintf(tmp_sql,"SELECT `castle_id`, `guild_id`, `economy`, `defense`, `triggerE`, `triggerD`, `nextTime`, `payTime`, `createTime`, " + "`visibleC`, `visibleG0`, `visibleG1`, `visibleG2`, `visibleG3`, `visibleG4`, `visibleG5`, `visibleG6`, `visibleG7`," + "`Ghp0`, `Ghp1`, `Ghp2`, `Ghp3`, `Ghp4`, `Ghp5`, `Ghp6`, `Ghp7`" + " FROM `%s` WHERE `castle_id`='%d'",guild_castle_db, castle_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle) ); + return 0; + } + sql_res = mysql_store_result(&mysql_handle) ; + if (sql_res!=NULL && mysql_num_rows(sql_res)>0) { + sql_row = mysql_fetch_row(sql_res); + if (sql_row==NULL){ + mysql_free_result(sql_res); + return 0; + } + + gc->guild_id = atoi (sql_row[1]); + gc->economy = atoi (sql_row[2]); + gc->defense = atoi (sql_row[3]); + gc->triggerE = atoi (sql_row[4]); + gc->triggerD = atoi (sql_row[5]); + gc->nextTime = atoi (sql_row[6]); + gc->payTime = atoi (sql_row[7]); + gc->createTime = atoi (sql_row[8]); + gc->visibleC = atoi (sql_row[9]); + gc->visibleG0 = atoi (sql_row[10]); + gc->visibleG1 = atoi (sql_row[11]); + gc->visibleG2 = atoi (sql_row[12]); + gc->visibleG3 = atoi (sql_row[13]); + gc->visibleG4 = atoi (sql_row[14]); + gc->visibleG5 = atoi (sql_row[15]); + gc->visibleG6 = atoi (sql_row[16]); + gc->visibleG7 = atoi (sql_row[17]); + gc->Ghp0 = atoi (sql_row[18]); + gc->Ghp1 = atoi (sql_row[19]); + gc->Ghp2 = atoi (sql_row[20]); + gc->Ghp3 = atoi (sql_row[21]); + gc->Ghp4 = atoi (sql_row[22]); + gc->Ghp5 = atoi (sql_row[23]); + gc->Ghp6 = atoi (sql_row[24]); + gc->Ghp7 = atoi (sql_row[25]); + + //printf("Read Castle %d of guild %d from sql \n",castle_id,gc->guild_id); + + } + mysql_free_result(sql_res) ; //resource free + return 0; +} + +// Read exp_guild.txt +int inter_guild_readdb() +{ + int i; + FILE *fp; + char line[1024]; + for (i=0;i<100;i++) guild_exp[i]=0; + + fp=fopen("db/exp_guild.txt","r"); + if(fp==NULL){ + printf("can't read db/exp_guild.txt\n"); + return 1; + } + i=0; + while(fgets(line,256,fp) && i<100){ + if(line[0]=='/' && line[1]=='/') + continue; + guild_exp[i]=atoi(line); + i++; + } + fclose(fp); + + return 0; +} + + +// Initialize guild sql +int inter_guild_sql_init() +{ + int i; + + printf("interserver guild memory initialize.... (%d byte)\n",sizeof(struct guild)); + guild_pt = calloc(sizeof(struct guild), 1); + guild_pt2= calloc(sizeof(struct guild), 1); + guildcastle_pt=calloc(sizeof(struct guild_castle), 1); + + inter_guild_readdb(); // Read exp + + sprintf(tmp_sql,"UPDATE `%s` SET `online`='0'",guild_member_db); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (update `char`)- %s\n", mysql_error(&mysql_handle) ); + exit(0); + } + + sprintf (tmp_sql , "SELECT count(*) FROM `%s`",guild_db); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle) ); + exit(0); + } + sql_res = mysql_store_result(&mysql_handle) ; + sql_row = mysql_fetch_row(sql_res); + printf("total guild data -> '%s'.......\n",sql_row[0]); + i = atoi (sql_row[0]); + mysql_free_result(sql_res); + + if (i > 0) { + //set party_newid + sprintf (tmp_sql , "SELECT max(`guild_id`) FROM `%s`",guild_db); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle) ); + exit(0); + } + + sql_res = mysql_store_result(&mysql_handle) ; + sql_row = mysql_fetch_row(sql_res); + guild_newid = atoi(sql_row[0])+1; + mysql_free_result(sql_res); + } + + printf("set guild_newid: %d.......\n",guild_newid); + + return 0; +} + + +// Get guild by its name +struct guild* search_guildname(char *str) +{ + struct guild *g=guild_pt; + char t_name[24]; + int guild_id=0; + printf("search_guildname\n"); + sprintf (tmp_sql , "SELECT `guild_id` FROM `%s` WHERE `name`='%s'",guild_db, jstrescapecpy(t_name,str)); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle) ); + } + sql_res = mysql_store_result(&mysql_handle) ; + if (sql_res!=NULL && mysql_num_rows(sql_res)>0) { + sql_row = mysql_fetch_row(sql_res); + guild_id = atoi (sql_row[0]); + } + mysql_free_result(sql_res); + inter_guild_fromsql(guild_id,g); + return g; +} + +// Check if guild is empty +int guild_check_empty(struct guild *g) +{ + int i; + for(i=0;i<g->max_member;i++){ + if(g->member[i].account_id>0){ + return 0; + } + } + + // 誰もいないので解散 + mapif_guild_broken(g->guild_id,0); + inter_guild_storage_delete(g->guild_id); + inter_guild_tosql(g,255); + memset(g,0,sizeof(struct guild)); + return 1; +} + +int guild_nextexp(int level) +{ + if(level < 100 && level >0) // Change by hack + return guild_exp[level-1]; + + return 0; +} + +// ギルドスキルがあるか確認 +int guild_checkskill(struct guild *g,int id){ return g->skill[id-10000].lv; } + +// ギルドの情報の再計算 +int guild_calcinfo(struct guild *g) +{ + int i,c,nextexp; + struct guild before=*g; + + // スキルIDの設定 + for(i=0;i<5;i++) + g->skill[i].id=i+10000; + + // ギルドレベル + if(g->guild_lv<=0) g->guild_lv=1; + nextexp = guild_nextexp(g->guild_lv); + if(nextexp > 0) { + while(g->exp >= nextexp && nextexp>0){ // Change by hack + g->exp-=nextexp; + g->guild_lv++; + g->skill_point++; + nextexp = guild_nextexp(g->guild_lv); + } + } + + // ギルドの次の経験値 + g->next_exp = guild_nextexp(g->guild_lv); + + // メンバ上限(ギルド拡張適用) + g->max_member=16+guild_checkskill(g,10004)*2; // Updated max_members [PoW] + + // 平均レベルとオンライン人数 + g->average_lv=0; + g->connect_member=0; + for(i=c=0;i<g->max_member;i++){ + if(g->member[i].account_id>0){ + g->average_lv+=g->member[i].lv; + c++; + + if(g->member[i].online>0) + g->connect_member++; + } + } + g->average_lv/=c; + + // 全データを送る必要がありそう + if( g->max_member!=before.max_member || + g->guild_lv!=before.guild_lv || + g->skill_point!=before.skill_point ){ + mapif_guild_info(-1,g); + return 1; + } + + return 0; +} + +//------------------------------------------------------------------- +// map serverへの通信 + +// ギルド作成可否 +int mapif_guild_created(int fd,int account_id,struct guild *g) +{ + WFIFOW(fd,0)=0x3830; + WFIFOL(fd,2)=account_id; + if(g!=NULL){ + WFIFOL(fd,6)=g->guild_id; + printf("int_guild: created! %d %s\n",g->guild_id,g->name); + }else{ + WFIFOL(fd,6)=0; + } + WFIFOSET(fd,10); + return 0; +} +// ギルド情報見つからず +int mapif_guild_noinfo(int fd,int guild_id) +{ + WFIFOW(fd,0)=0x3831; + WFIFOW(fd,2)=8; + WFIFOL(fd,4)=guild_id; + WFIFOSET(fd,8); + printf("int_guild: info not found %d\n",guild_id); + return 0; +} +// ギルド情報まとめ送り +int mapif_guild_info(int fd,struct guild *g) +{ + unsigned char buf[16384]; + WBUFW(buf,0)=0x3831; + memcpy(buf+4,g,sizeof(struct guild)); + WBUFW(buf,2)=4+sizeof(struct guild); +// printf("int_guild: sizeof(guild)=%d\n",sizeof(struct guild)); + if(fd<0) + mapif_sendall(buf,WBUFW(buf,2)); + else + mapif_send(fd,buf,WBUFW(buf,2)); +// printf("int_guild: info %d %s\n",p->guild_id,p->name); + return 0; +} + +// メンバ追加可否 +int mapif_guild_memberadded(int fd,int guild_id,int account_id,int char_id,int flag) +{ + WFIFOW(fd,0)=0x3832; + WFIFOL(fd,2)=guild_id; + WFIFOL(fd,6)=account_id; + WFIFOL(fd,10)=char_id; + WFIFOB(fd,14)=flag; + WFIFOSET(fd,15); + return 0; +} +// 脱退/追放通知 +int mapif_guild_leaved(int guild_id,int account_id,int char_id,int flag, + const char *name,const char *mes) +{ + unsigned char buf[128]; + WBUFW(buf, 0)=0x3834; + WBUFL(buf, 2)=guild_id; + WBUFL(buf, 6)=account_id; + WBUFL(buf,10)=char_id; + WBUFB(buf,14)=flag; + memcpy(WBUFP(buf,15),mes,40); + memcpy(WBUFP(buf,55),name,24); + mapif_sendall(buf,79); + printf("int_guild: guild leaved %d %d %s %s\n",guild_id,account_id,name,mes); + return 0; +} + +// オンライン状態とLv更新通知 +int mapif_guild_memberinfoshort(struct guild *g,int idx) +{ + unsigned char buf[32]; + WBUFW(buf, 0)=0x3835; + WBUFL(buf, 2)=g->guild_id; + WBUFL(buf, 6)=g->member[idx].account_id; + WBUFL(buf,10)=g->member[idx].char_id; + WBUFB(buf,14)=g->member[idx].online; + WBUFW(buf,15)=g->member[idx].lv; + WBUFW(buf,17)=g->member[idx].class; + mapif_sendall(buf,19); + return 0; +} + +// 解散通知 +int mapif_guild_broken(int guild_id,int flag) +{ + unsigned char buf[16]; + WBUFW(buf,0)=0x3836; + WBUFL(buf,2)=guild_id; + WBUFB(buf,6)=flag; + mapif_sendall(buf,7); + printf("int_guild: broken %d\n",guild_id); + return 0; +} + +// ギルド内発言 +int mapif_guild_message(int guild_id,int account_id,char *mes,int len) +{ + unsigned char buf[512]; + WBUFW(buf,0)=0x3837; + WBUFW(buf,2)=len+12; + WBUFL(buf,4)=guild_id; + WBUFL(buf,8)=account_id; + memcpy(WBUFP(buf,12),mes,len); + mapif_sendall(buf,len+12); + return 0; +} + +// ギルド基本情報変更通知 +int mapif_guild_basicinfochanged(int guild_id,int type,const void *data,int len) +{ + unsigned char buf[2048]; + WBUFW(buf, 0)=0x3839; + WBUFW(buf, 2)=len+10; + WBUFL(buf, 4)=guild_id; + WBUFW(buf, 8)=type; + memcpy(WBUFP(buf,10),data,len); + mapif_sendall(buf,len+10); + return 0; +} +// ギルドメンバ情報変更通知 +int mapif_guild_memberinfochanged(int guild_id,int account_id,int char_id, + int type,const void *data,int len) +{ + unsigned char buf[2048]; + WBUFW(buf, 0)=0x383a; + WBUFW(buf, 2)=len+18; + WBUFL(buf, 4)=guild_id; + WBUFL(buf, 8)=account_id; + WBUFL(buf,12)=char_id; + WBUFW(buf,16)=type; + memcpy(WBUFP(buf,18),data,len); + mapif_sendall(buf,len+18); + return 0; +} +// ギルドスキルアップ通知 +int mapif_guild_skillupack(int guild_id,int skill_num,int account_id) +{ + unsigned char buf[16]; + WBUFW(buf, 0)=0x383c; + WBUFL(buf, 2)=guild_id; + WBUFL(buf, 6)=skill_num; + WBUFL(buf,10)=account_id; + mapif_sendall(buf,14); + return 0; +} +// ギルド同盟/敵対通知 +int mapif_guild_alliance(int guild_id1,int guild_id2,int account_id1,int account_id2, + int flag,const char *name1,const char *name2) +{ + unsigned char buf[128]; + WBUFW(buf, 0)=0x383d; + WBUFL(buf, 2)=guild_id1; + WBUFL(buf, 6)=guild_id2; + WBUFL(buf,10)=account_id1; + WBUFL(buf,14)=account_id2; + WBUFB(buf,18)=flag; + memcpy(WBUFP(buf,19),name1,24); + memcpy(WBUFP(buf,43),name2,24); + mapif_sendall(buf,67); + return 0; +} + +// ギルド役職変更通知 +int mapif_guild_position(struct guild *g,int idx) +{ + unsigned char buf[128]; + WBUFW(buf,0)=0x383b; + WBUFW(buf,2)=sizeof(struct guild_position)+12; + WBUFL(buf,4)=g->guild_id; + WBUFL(buf,8)=idx; + memcpy(WBUFP(buf,12),&g->position[idx],sizeof(struct guild_position)); + mapif_sendall(buf,WBUFW(buf,2)); + return 0; +} + +// ギルド告知変更通知 +int mapif_guild_notice(struct guild *g) +{ + unsigned char buf[256]; + WBUFW(buf,0)=0x383e; + WBUFL(buf,2)=g->guild_id; + memcpy(WBUFP(buf,6),g->mes1,60); + memcpy(WBUFP(buf,66),g->mes2,120); + mapif_sendall(buf,186); + return 0; +} +// ギルドエンブレム変更通知 +int mapif_guild_emblem(struct guild *g) +{ + unsigned char buf[2048]; + WBUFW(buf,0)=0x383f; + WBUFW(buf,2)=g->emblem_len+12; + WBUFL(buf,4)=g->guild_id; + WBUFL(buf,8)=g->emblem_id; + memcpy(WBUFP(buf,12),g->emblem_data,g->emblem_len); + mapif_sendall(buf,WBUFW(buf,2)); + return 0; +} + +int mapif_guild_castle_dataload(int castle_id,int index,int value) // <Agit> +{ + unsigned char buf[16]; + WBUFW(buf, 0)=0x3840; + WBUFW(buf, 2)=castle_id; + WBUFB(buf, 4)=index; + WBUFL(buf, 5)=value; + mapif_sendall(buf,9); + return 0; +} + +int mapif_guild_castle_datasave(int castle_id,int index,int value) // <Agit> +{ + unsigned char buf[16]; + WBUFW(buf, 0)=0x3841; + WBUFW(buf, 2)=castle_id; + WBUFB(buf, 4)=index; + WBUFL(buf, 5)=value; + mapif_sendall(buf,9); + return 0; +} + +int mapif_guild_castle_alldataload(int fd) { + struct guild_castle* gc = guildcastle_pt; + int i, len = 4; + + WFIFOW(fd,0) = 0x3842; + sprintf(tmp_sql, "SELECT * FROM `%s` ORDER BY `castle_id`", guild_castle_db); + if (mysql_query(&mysql_handle, tmp_sql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle) ); + } + sql_res = mysql_store_result(&mysql_handle); + if (sql_res != NULL && mysql_num_rows(sql_res) > 0) { + for(i = 0; ((sql_row = mysql_fetch_row(sql_res)) && i < MAX_GUILDCASTLE); i++) { + memset(gc, 0, sizeof(struct guild_castle)); + gc->castle_id = atoi(sql_row[0]); + gc->guild_id = atoi(sql_row[1]); + gc->economy = atoi(sql_row[2]); + gc->defense = atoi(sql_row[3]); + gc->triggerE = atoi(sql_row[4]); + gc->triggerD = atoi(sql_row[5]); + gc->nextTime = atoi(sql_row[6]); + gc->payTime = atoi(sql_row[7]); + gc->createTime = atoi(sql_row[8]); + gc->visibleC = atoi(sql_row[9]); + gc->visibleG0 = atoi(sql_row[10]); + gc->visibleG1 = atoi(sql_row[11]); + gc->visibleG2 = atoi(sql_row[12]); + gc->visibleG3 = atoi(sql_row[13]); + gc->visibleG4 = atoi(sql_row[14]); + gc->visibleG5 = atoi(sql_row[15]); + gc->visibleG6 = atoi(sql_row[16]); + gc->visibleG7 = atoi(sql_row[17]); + gc->Ghp0 = atoi(sql_row[18]); + gc->Ghp1 = atoi(sql_row[19]); + gc->Ghp2 = atoi(sql_row[20]); + gc->Ghp3 = atoi(sql_row[21]); + gc->Ghp4 = atoi(sql_row[22]); + gc->Ghp5 = atoi(sql_row[23]); + gc->Ghp6 = atoi(sql_row[24]); + gc->Ghp7 = atoi(sql_row[25]); + memcpy(WFIFOP(fd,len), gc, sizeof(struct guild_castle)); + len += sizeof(struct guild_castle); + } + } + mysql_free_result(sql_res); + WFIFOW(fd,2) = len; + WFIFOSET(fd,len); + + return 0; +} + + +//------------------------------------------------------------------- +// map serverからの通信 + + +// ギルド作成要求 +int mapif_parse_CreateGuild(int fd,int account_id,char *name,struct guild_member *master) +{ + struct guild *g; + int i; + + printf("CreateGuild\n"); + g=search_guildname(name); + if(g!=NULL&&g->guild_id>0){ + printf("int_guild: same name guild exists [%s]\n",name); + mapif_guild_created(fd,account_id,NULL); + return 0; + } + g=guild_pt; + memset(g,0,sizeof(struct guild)); + g->guild_id=guild_newid++; + memcpy(g->name,name,24); + memcpy(g->master,master->name,24); + memcpy(&g->member[0],master,sizeof(struct guild_member)); + + g->position[0].mode=0x11; + strcpy(g->position[ 0].name,"GuildMaster"); + strcpy(g->position[MAX_GUILDPOSITION-1].name,"Newbie"); + for(i=1;i<MAX_GUILDPOSITION-1;i++) + sprintf(g->position[i].name,"Position %d",i+1); + + // Initialize guild property + g->max_member=16; + g->average_lv=master->lv; + g->castle_id=-1; + for(i=0;i<5;i++) + g->skill[i].id=i+10000; + + // Save to sql + printf("Create initialize OK!\n"); + i=inter_guild_tosql(g,255); + + if (i<0) { + mapif_guild_created(fd,account_id,NULL); + return 0; + } + + // Report to client + mapif_guild_created(fd,account_id,g); + mapif_guild_info(fd,g); + + inter_log("guild %s (id=%d) created by master %s (id=%d)" RETCODE, + name, g->guild_id, master->name, master->account_id ); + + + return 0; +} +// Return guild info to client +int mapif_parse_GuildInfo(int fd,int guild_id) +{ + struct guild *g; + g=guild_pt; + inter_guild_fromsql(guild_id,g); + if(g!=NULL&&g->guild_id>0){ + guild_calcinfo(g); + mapif_guild_info(fd,g); + //inter_guild_tosql(g,1); // Change guild + }else + mapif_guild_noinfo(fd,guild_id); + return 0; +} +// Add member to guild +int mapif_parse_GuildAddMember(int fd,int guild_id,struct guild_member *m) +{ + struct guild *g; + int i; + g=guild_pt; + inter_guild_fromsql(guild_id,g); + + if(g==NULL||g->guild_id<=0){ + mapif_guild_memberadded(fd,guild_id,m->account_id,m->char_id,1); + return 0; + } + + for(i=0;i<g->max_member;i++){ + if(g->member[i].account_id==0){ + + memcpy(&g->member[i],m,sizeof(struct guild_member)); + mapif_guild_memberadded(fd,guild_id,m->account_id,m->char_id,0); + guild_calcinfo(g); + mapif_guild_info(-1,g); + inter_guild_tosql(g,3); // Change guild & guild_member + return 0; + } + } + mapif_guild_memberadded(fd,guild_id,m->account_id,m->char_id,1); + //inter_guild_tosql(g,3); // Change guild & guild_member + return 0; +} +// Delete member from guild +int mapif_parse_GuildLeave(int fd,int guild_id,int account_id,int char_id,int flag,const char *mes) +{ + struct guild *g=NULL; + g=guild_pt; + inter_guild_fromsql(guild_id,g); + + if(g!=NULL&&g->guild_id>0){ + int i; + for(i=0;i<g->max_member;i++){ + if( g->member[i].account_id==account_id && + g->member[i].char_id==char_id){ + printf("%d %d\n",i, (int)(&g->member[i])); + printf("%d %s\n",i, g->member[i].name); + + if(flag){ // 追放の場合追放リストに入れる + int j; + for(j=0;j<MAX_GUILDEXPLUSION;j++){ + if(g->explusion[j].account_id==0) + break; + } + if(j==MAX_GUILDEXPLUSION){ // 一杯なので古いのを消す + for(j=0;j<MAX_GUILDEXPLUSION-1;j++) + g->explusion[j]=g->explusion[j+1]; + j=MAX_GUILDEXPLUSION-1; + } + g->explusion[j].account_id=account_id; + memcpy(g->explusion[j].acc,"dummy",24); + memcpy(g->explusion[j].name,g->member[i].name,24); + memcpy(g->explusion[j].mes,mes,40); + } + + mapif_guild_leaved(guild_id,account_id,char_id,flag,g->member[i].name,mes); + printf("%d %d\n",i, (int)(&g->member[i])); + printf("%d %s\n",i, (&g->member[i])->name); + memset(&g->member[i],0,sizeof(struct guild_member)); + + if( guild_check_empty(g)==0 ) + mapif_guild_info(-1,g);// まだ人がいるのでデータ送信 + /* + else + inter_guild_save(); // 解散したので一応セーブ + return 0;*/ + } + } + guild_calcinfo(g); + inter_guild_tosql(g,19); // Change guild & guild_member & guild_expulsion + }else{ + sprintf(tmp_sql, "UPDATE `%s` SET `guild_id`='0' WHERE `account_id`='%d' AND `char_id`='%d'",char_db, account_id,char_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (update `char`)- %s\n", mysql_error(&mysql_handle) ); + } + /* mapif_guild_leaved(guild_id,account_id,char_id,flag,g->member[i].name,mes); */ + } + + + return 0; +} +// Change member info +int mapif_parse_GuildChangeMemberInfoShort(int fd,int guild_id, + int account_id,int char_id,int online,int lv,int class) +{ + // Could speed up by manipulating only guild_member + struct guild *g; + int i,alv,c; + g=guild_pt; + inter_guild_fromsql(guild_id,g); + + if(g==NULL||g->guild_id<=0){ + return 0; + } + + g->connect_member=0; + + for(i=0,alv=0,c=0;i<g->max_member;i++){ + if( g->member[i].account_id==account_id && + g->member[i].char_id==char_id){ + + g->member[i].online=online; + g->member[i].lv=lv; + g->member[i].class=class; + mapif_guild_memberinfoshort(g,i); + } + if( g->member[i].account_id>0 ){ + alv+=g->member[i].lv; + c++; + } + if( g->member[i].online ) + g->connect_member++; + } + // 平均レベル + g->average_lv=alv/c; + + inter_guild_tosql(g,3); // Change guild & guild_member + + return 0; +} + +// BreakGuild +int mapif_parse_BreakGuild(int fd,int guild_id) +{ + struct guild *g; + g=guild_pt; + inter_guild_fromsql(guild_id,g); + if(g==NULL){ + return 0; + } + + // Delete guild from sql + //printf("- Delete guild %d from guild\n",guild_id); + sprintf(tmp_sql, "DELETE FROM `%s` WHERE `guild_id`='%d'",guild_db, guild_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (delete `guild`)- %s\n", mysql_error(&mysql_handle) ); + } + //printf("- Delete guild %d from guild_member\n",guild_id); + sprintf(tmp_sql, "DELETE FROM `%s` WHERE `guild_id`='%d'",guild_member_db, guild_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (delete `guild_member`)- %s\n", mysql_error(&mysql_handle) ); + } + //printf("- Delete guild %d from guild_skill\n",guild_id); + sprintf(tmp_sql, "DELETE FROM `%s` WHERE `guild_id`='%d'",guild_skill_db, guild_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (delete `guild_skill`)- %s\n", mysql_error(&mysql_handle) ); + } + //printf("- Delete guild %d from guild_position\n",guild_id); + sprintf(tmp_sql, "DELETE FROM `%s` WHERE `guild_id`='%d'",guild_position_db, guild_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (delete `guild_position`)- %s\n", mysql_error(&mysql_handle) ); + } + //printf("- Delete guild %d from guild_expulsion\n",guild_id); + sprintf(tmp_sql, "DELETE FROM `%s` WHERE `guild_id`='%d'",guild_expulsion_db, guild_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (delete `guild_expulsion`)- %s\n", mysql_error(&mysql_handle) ); + } + //printf("- Delete guild %d from guild_alliance\n",guild_id); + sprintf(tmp_sql, "DELETE FROM `%s` WHERE `guild_id`='%d' OR `alliance_id`='%d'",guild_alliance_db, guild_id,guild_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (delete `guild_position`)- %s\n", mysql_error(&mysql_handle) ); + } + + //printf("- Delete guild %d from guild_castle\n",guild_id); + sprintf(tmp_sql, "DELETE FROM `%s` WHERE `guild_id`='%d'",guild_castle_db, guild_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (delete `guild_position`)- %s\n", mysql_error(&mysql_handle) ); + } + + //printf("- Update guild %d of char\n",guild_id); + sprintf(tmp_sql, "UPDATE `%s` SET `guild_id`='0' WHERE `guild_id`='%d'",char_db, guild_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (delete `guild_position`)- %s\n", mysql_error(&mysql_handle) ); + } + + inter_guild_storage_delete(guild_id); + mapif_guild_broken(guild_id,0); + + inter_log("guild %s (id=%d) broken" RETCODE,g->name,guild_id); + + return 0; +} + +// ギルドメッセージ送信 +int mapif_parse_GuildMessage(int fd,int guild_id,int account_id,char *mes,int len) +{ + return mapif_guild_message(guild_id,account_id,mes,len); +} +// ギルド基本データ変更要求 +int mapif_parse_GuildBasicInfoChange(int fd,int guild_id, + int type,const char *data,int len) +{ + struct guild *g; +// int dd=*((int *)data); + short dw=*((short *)data); + g=guild_pt; + inter_guild_fromsql(guild_id,g); + + if(g==NULL||g->guild_id<=0){ + return 0; + } + switch(type){ + case GBI_GUILDLV: { + printf("GBI_GUILDLV\n"); + if(dw>0 && g->guild_lv+dw<=50){ + g->guild_lv+=dw; + g->skill_point+=dw; + }else if(dw<0 && g->guild_lv+dw>=1) + g->guild_lv+=dw; + mapif_guild_info(-1,g); + inter_guild_tosql(g,1); + } return 0; + default: + printf("int_guild: GuildBasicInfoChange: Unknown type %d\n",type); + break; + } + mapif_guild_basicinfochanged(guild_id,type,data,len); + //inter_guild_tosql(g,1); // Change guild + return 0; +} + +// ギルドメンバデータ変更要求 +int mapif_parse_GuildMemberInfoChange(int fd,int guild_id,int account_id,int char_id, + int type,const char *data,int len) +{ + // Could make some improvement in speed, because only change guild_member + int i; + struct guild *g; + g=guild_pt; + inter_guild_fromsql(guild_id,g); + //printf("GuildMemberInfoChange %s \n",(type==GMI_EXP)?"GMI_EXP":"OTHER"); + + if(g==NULL){ + return 0; + } + for(i=0;i<g->max_member;i++) + if( g->member[i].account_id==account_id && + g->member[i].char_id==char_id ) + break; + if(i==g->max_member){ + printf("int_guild: GuildMemberChange: Not found %d,%d in %d[%s]\n", + account_id,char_id,guild_id,g->name); + return 0; + } + switch(type){ + case GMI_POSITION: // 役職 + g->member[i].position=*((int *)data); + break; + case GMI_EXP: { // EXP + int exp,oldexp=g->member[i].exp; + exp=g->member[i].exp=*((unsigned int *)data); + g->exp+=(exp-oldexp); + guild_calcinfo(g); // Lvアップ判断 + mapif_guild_basicinfochanged(guild_id,GBI_EXP,&g->exp,4); + }break; + default: + printf("int_guild: GuildMemberInfoChange: Unknown type %d\n",type); + break; + } + mapif_guild_memberinfochanged(guild_id,account_id,char_id,type,data,len); + inter_guild_tosql(g,3); // Change guild & guild_member + return 0; +} + +// ギルド役職名変更要求 +int mapif_parse_GuildPosition(int fd,int guild_id,int idx,struct guild_position *p) +{ + // Could make some improvement in speed, because only change guild_position + struct guild *g; + g=guild_pt; + inter_guild_fromsql(guild_id,g); + + if(g==NULL || idx<0 || idx>=MAX_GUILDPOSITION){ + return 0; + } + memcpy(&g->position[idx],p,sizeof(struct guild_position)); + mapif_guild_position(g,idx); + printf("int_guild: position changed %d\n",idx); + inter_guild_tosql(g,4); // Change guild_position + return 0; +} +// ギルドスキルアップ要求 +int mapif_parse_GuildSkillUp(int fd,int guild_id,int skill_num,int account_id) +{ + // Could make some improvement in speed, because only change guild_position + struct guild *g; + g=guild_pt; + inter_guild_fromsql(guild_id,g); + int idx=skill_num-10000; + if(g==NULL || skill_num<10000) + return 0; + //printf("GuildSkillUp\n"); + + if( g->skill_point>0 && g->skill[idx].id>0 && + g->skill[idx].lv<10 ){ + g->skill[idx].lv++; + g->skill_point--; + if(guild_calcinfo(g)==0) + mapif_guild_info(-1,g); + mapif_guild_skillupack(guild_id,skill_num,account_id); + printf("int_guild: skill %d up\n",skill_num); + inter_guild_tosql(g,33); // Change guild & guild_skill + } + + return 0; +} +// ギルド同盟要求 +int mapif_parse_GuildAlliance(int fd,int guild_id1,int guild_id2, + int account_id1,int account_id2,int flag) +{ + // Could speed up + struct guild *g[2]; + int j,i; + g[0]=guild_pt; + g[1]=guild_pt2; + inter_guild_fromsql(guild_id1,g[0]); + inter_guild_fromsql(guild_id2,g[1]); + + if(g[0]==NULL || g[1]==NULL || g[0]->guild_id ==0 || g[1]->guild_id==0) + return 0; + + if(!(flag&0x8)){ + for(i=0;i<2-(flag&1);i++){ + for(j=0;j<MAX_GUILDALLIANCE;j++) + if(g[i]->alliance[j].guild_id==0){ + g[i]->alliance[j].guild_id=g[1-i]->guild_id; + memcpy(g[i]->alliance[j].name,g[1-i]->name,24); + g[i]->alliance[j].opposition=flag&1; + break; + } + } + }else{ // 関係解消 + for(i=0;i<2-(flag&1);i++){ + for(j=0;j<MAX_GUILDALLIANCE;j++) + if( g[i]->alliance[j].guild_id==g[1-i]->guild_id && + g[i]->alliance[j].opposition==(flag&1)){ + g[i]->alliance[j].guild_id=0; + break; + } + } + } + mapif_guild_alliance(guild_id1,guild_id2,account_id1,account_id2,flag, + g[0]->name,g[1]->name); + inter_guild_tosql(g[0],8); // Change guild_alliance + inter_guild_tosql(g[1],8); // Change guild_alliance + return 0; +} +// ギルド告知変更要求 +int mapif_parse_GuildNotice(int fd,int guild_id,const char *mes1,const char *mes2) +{ + struct guild *g; + g=guild_pt; + inter_guild_fromsql(guild_id,g); + + if(g==NULL||g->guild_id<=0) + return 0; + memcpy(g->mes1,mes1,60); + memcpy(g->mes2,mes2,120); + inter_guild_tosql(g,1); // Change mes of guild + return mapif_guild_notice(g); +} +// ギルドエンブレム変更要求 +int mapif_parse_GuildEmblem(int fd,int len,int guild_id,int dummy,const char *data) +{ + struct guild *g; + g=guild_pt; + inter_guild_fromsql(guild_id,g); + + if(g==NULL||g->guild_id<=0) + return 0; + memcpy(g->emblem_data,data,len); + g->emblem_len=len; + g->emblem_id++; + inter_guild_tosql(g,1); // Change guild + return mapif_guild_emblem(g); +} + +int mapif_parse_GuildCastleDataLoad(int fd,int castle_id,int index) // <Agit> +{ + struct guild_castle *gc=guildcastle_pt; + inter_guildcastle_fromsql(castle_id, gc); + if(gc==NULL||gc->castle_id==-1){ + return mapif_guild_castle_dataload(castle_id,0,0); + } + switch(index){ + case 1: return mapif_guild_castle_dataload(gc->castle_id,index,gc->guild_id); break; + case 2: return mapif_guild_castle_dataload(gc->castle_id,index,gc->economy); break; + case 3: return mapif_guild_castle_dataload(gc->castle_id,index,gc->defense); break; + case 4: return mapif_guild_castle_dataload(gc->castle_id,index,gc->triggerE); break; + case 5: return mapif_guild_castle_dataload(gc->castle_id,index,gc->triggerD); break; + case 6: return mapif_guild_castle_dataload(gc->castle_id,index,gc->nextTime); break; + case 7: return mapif_guild_castle_dataload(gc->castle_id,index,gc->payTime); break; + case 8: return mapif_guild_castle_dataload(gc->castle_id,index,gc->createTime); break; + case 9: return mapif_guild_castle_dataload(gc->castle_id,index,gc->visibleC); break; + case 10: return mapif_guild_castle_dataload(gc->castle_id,index,gc->visibleG0); break; + case 11: return mapif_guild_castle_dataload(gc->castle_id,index,gc->visibleG1); break; + case 12: return mapif_guild_castle_dataload(gc->castle_id,index,gc->visibleG2); break; + case 13: return mapif_guild_castle_dataload(gc->castle_id,index,gc->visibleG3); break; + case 14: return mapif_guild_castle_dataload(gc->castle_id,index,gc->visibleG4); break; + case 15: return mapif_guild_castle_dataload(gc->castle_id,index,gc->visibleG5); break; + case 16: return mapif_guild_castle_dataload(gc->castle_id,index,gc->visibleG6); break; + case 17: return mapif_guild_castle_dataload(gc->castle_id,index,gc->visibleG7); break; + case 18: return mapif_guild_castle_dataload(gc->castle_id,index,gc->Ghp0); break; // guardian HP [Valaris] + case 19: return mapif_guild_castle_dataload(gc->castle_id,index,gc->Ghp1); break; + case 20: return mapif_guild_castle_dataload(gc->castle_id,index,gc->Ghp2); break; + case 21: return mapif_guild_castle_dataload(gc->castle_id,index,gc->Ghp3); break; + case 22: return mapif_guild_castle_dataload(gc->castle_id,index,gc->Ghp4); break; + case 23: return mapif_guild_castle_dataload(gc->castle_id,index,gc->Ghp5); break; + case 24: return mapif_guild_castle_dataload(gc->castle_id,index,gc->Ghp6); break; + case 25: return mapif_guild_castle_dataload(gc->castle_id,index,gc->Ghp7); break; // end additions [Valaris] + default: + printf("mapif_parse_GuildCastleDataLoad ERROR!! (Not found index=%d)\n", index); + return 0; + } +} + +int mapif_parse_GuildCastleDataSave(int fd,int castle_id,int index,int value) // <Agit> +{ + struct guild_castle *gc=guildcastle_pt; + inter_guildcastle_fromsql(castle_id, gc); + if(gc==NULL||gc->castle_id==-1){ + return mapif_guild_castle_datasave(castle_id,index,value); + } + switch(index){ + case 1: + if( gc->guild_id!=value ){ + int gid=(value)?value:gc->guild_id; + struct guild *g=guild_pt; + inter_guild_fromsql(gid, g); + inter_log("guild %s (id=%d) %s castle id=%d" RETCODE, + (g)?g->name:"??" ,gid, (value)?"occupy":"abandon", index); + } + gc->guild_id = value; + break; + case 2: gc->economy = value; break; + case 3: gc->defense = value; break; + case 4: gc->triggerE = value; break; + case 5: gc->triggerD = value; break; + case 6: gc->nextTime = value; break; + case 7: gc->payTime = value; break; + case 8: gc->createTime = value; break; + case 9: gc->visibleC = value; break; + case 10: gc->visibleG0 = value; break; + case 11: gc->visibleG1 = value; break; + case 12: gc->visibleG2 = value; break; + case 13: gc->visibleG3 = value; break; + case 14: gc->visibleG4 = value; break; + case 15: gc->visibleG5 = value; break; + case 16: gc->visibleG6 = value; break; + case 17: gc->visibleG7 = value; break; + case 18: gc->Ghp0 = value; break; // guardian HP [Valaris] + case 19: gc->Ghp1 = value; break; + case 20: gc->Ghp2 = value; break; + case 21: gc->Ghp3 = value; break; + case 22: gc->Ghp4 = value; break; + case 23: gc->Ghp5 = value; break; + case 24: gc->Ghp6 = value; break; + case 25: gc->Ghp7 = value; break; // end additions [Valaris] + default: + printf("mapif_parse_GuildCastleDataSave ERROR!! (Not found index=%d)\n", index); + return 0; + } + inter_guildcastle_tosql(gc); + return mapif_guild_castle_datasave(gc->castle_id,index,value); +} + +// ギルドチェック要求 +int mapif_parse_GuildCheck(int fd,int guild_id,int account_id,int char_id) +{ + // What does this mean? Check if belong to another guild? + return 0; +} + +// map server からの通信 +// ・1パケットのみ解析すること +// ・パケット長データはinter.cにセットしておくこと +// ・パケット長チェックや、RFIFOSKIPは呼び出し元で行われるので行ってはならない +// ・エラーなら0(false)、そうでないなら1(true)をかえさなければならない +int inter_guild_parse_frommap(int fd) +{ + switch(RFIFOW(fd,0)){ + case 0x3030: mapif_parse_CreateGuild(fd,RFIFOL(fd,4),RFIFOP(fd,8),(struct guild_member *)RFIFOP(fd,32)); break; + case 0x3031: mapif_parse_GuildInfo(fd,RFIFOL(fd,2)); break; + case 0x3032: mapif_parse_GuildAddMember(fd,RFIFOL(fd,4),(struct guild_member *)RFIFOP(fd,8)); break; + case 0x3034: mapif_parse_GuildLeave(fd,RFIFOL(fd,2),RFIFOL(fd,6),RFIFOL(fd,10),RFIFOB(fd,14),RFIFOP(fd,15)); break; + case 0x3035: mapif_parse_GuildChangeMemberInfoShort(fd,RFIFOL(fd,2),RFIFOL(fd,6),RFIFOL(fd,10),RFIFOB(fd,14),RFIFOW(fd,15),RFIFOW(fd,17)); break; + case 0x3036: mapif_parse_BreakGuild(fd,RFIFOL(fd,2)); break; + case 0x3037: mapif_parse_GuildMessage(fd,RFIFOL(fd,4),RFIFOL(fd,8),RFIFOP(fd,12),RFIFOW(fd,2)-12); break; + case 0x3038: mapif_parse_GuildCheck(fd,RFIFOL(fd,2),RFIFOL(fd,6),RFIFOL(fd,10)); break; + case 0x3039: mapif_parse_GuildBasicInfoChange(fd,RFIFOL(fd,4),RFIFOW(fd,8),RFIFOP(fd,10),RFIFOW(fd,2)-10); break; + case 0x303A: mapif_parse_GuildMemberInfoChange(fd,RFIFOL(fd,4),RFIFOL(fd,8),RFIFOL(fd,12),RFIFOW(fd,16),RFIFOP(fd,18),RFIFOW(fd,2)-18); break; + case 0x303B: mapif_parse_GuildPosition(fd,RFIFOL(fd,4),RFIFOL(fd,8),(struct guild_position *)RFIFOP(fd,12)); break; + case 0x303C: mapif_parse_GuildSkillUp(fd,RFIFOL(fd,2),RFIFOL(fd,6),RFIFOL(fd,10)); break; + case 0x303D: mapif_parse_GuildAlliance(fd,RFIFOL(fd,2),RFIFOL(fd,6),RFIFOL(fd,10),RFIFOL(fd,14),RFIFOB(fd,18)); break; + case 0x303E: mapif_parse_GuildNotice(fd,RFIFOL(fd,2),RFIFOP(fd,6),RFIFOP(fd,66)); break; + case 0x303F: mapif_parse_GuildEmblem(fd,RFIFOW(fd,2)-12,RFIFOL(fd,4),RFIFOL(fd,8),RFIFOP(fd,12)); break; + case 0x3040: mapif_parse_GuildCastleDataLoad(fd,RFIFOW(fd,2),RFIFOB(fd,4)); break; + case 0x3041: mapif_parse_GuildCastleDataSave(fd,RFIFOW(fd,2),RFIFOB(fd,4),RFIFOL(fd,5)); break; + + default: + return 0; + } + return 1; +} + +int inter_guild_mapif_init(int fd) +{ + return mapif_guild_castle_alldataload(fd); +} + +// サーバーから脱退要求(キャラ削除用) +int inter_guild_leave(int guild_id,int account_id,int char_id) +{ + return mapif_parse_GuildLeave(-1,guild_id,account_id,char_id,0,"**サーバー命令**"); +} diff --git a/src/char_sql/int_guild.h b/src/char_sql/int_guild.h new file mode 100644 index 0000000..8f4203d --- /dev/null +++ b/src/char_sql/int_guild.h @@ -0,0 +1,10 @@ +#ifndef _INT_GUILD_H_ +#define _INT_GUILD_H_ + +int inter_guild_parse_frommap(int fd); +int inter_guild_sql_init(); +int inter_guild_mapif_init(int fd); + +int inter_guild_leave(int guild_id,int account_id,int char_id); + +#endif diff --git a/src/char_sql/int_party.c b/src/char_sql/int_party.c new file mode 100644 index 0000000..84de078 --- /dev/null +++ b/src/char_sql/int_party.c @@ -0,0 +1,755 @@ +// +// original code from athena +// SQL conversion by hack +// + +#include "char.h" +#include "strlib.h" +#include "socket.h" +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +static struct party *party_pt; +static int party_newid=100; + +int mapif_party_broken(int party_id,int flag); +int party_check_empty(struct party *p); +int mapif_parse_PartyLeave(int fd,int party_id,int account_id); + +// Save party to mysql +int inter_party_tosql(int party_id,struct party *p) +{ + // 'party' ('party_id','name','exp','item','leader') + + char t_name[100]; + char t_member[24]; + int party_member = 0, party_online_member = 0; + int party_exist = 0; + int leader_id = 0; + int i = 0; + + printf("(\033[1;64m%d\033[0m) Request save party - ",party_id); + + jstrescapecpy(t_name, p->name); + + if (p==NULL || party_id==0 || p->party_id ==0 || party_id!=p->party_id) { + printf("- Party pointer or party_id error \n"); + return 0; + } + + // Check if party exists + sprintf(tmp_sql,"SELECT count(*) FROM `%s` WHERE `party_id`='%d'",party_db, party_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle) ); + return 0; + } + sql_res = mysql_store_result(&mysql_handle) ; + if (sql_res!=NULL && mysql_num_rows(sql_res)>0) { + sql_row = mysql_fetch_row(sql_res); + party_exist = atoi (sql_row[0]); + //printf("- Check if party %d exists : %s\n",party_id,party_exist==0?"No":"Yes"); + } + mysql_free_result(sql_res) ; //resource free + + if (party_exist >0){ + // Check members in party + sprintf(tmp_sql,"SELECT count(*) FROM `%s` WHERE `party_id`='%d'",char_db, party_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle) ); + return 0; + } + sql_res = mysql_store_result(&mysql_handle) ; + if (sql_res!=NULL && mysql_num_rows(sql_res)>0) { + sql_row = mysql_fetch_row(sql_res); + + party_member = atoi (sql_row[0]); + // printf("- Check members in party %d : %d \n",party_id,party_member); + + } + mysql_free_result(sql_res) ; //resource free + + party_online_member = 0; + i=0; + while (i<MAX_PARTY){ + if (p->member[i].account_id>0) party_online_member++; + i++; + } + + //if (party_online_member==0) printf("- No member online \n"); else printf("- Some member %d online \n", party_online_member); + + if (party_member <= 0 && party_online_member == 0) { + + // Delete the party, if has no member. + sprintf(tmp_sql,"DELETE FROM `%s` WHERE `party_id`='%d'",party_db, party_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle) ); + } + // printf("No member in party %d, break it \n",party_id); + memset(p, 0, sizeof(struct party)); + return 0; + } else { + // Update party information, if exists + + int i=0; + + for (i=0;i<MAX_PARTY;i++){ + + if (p->member[i].account_id>0){ + sprintf(tmp_sql,"UPDATE `%s` SET `party_id`='%d', `online`='%d' WHERE `account_id`='%d' AND `name`='%s'", + char_db, party_id, p->member[i].online, p->member[i].account_id,jstrescapecpy(t_member,p->member[i].name)); + //printf("%s",tmp_sql); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (update `char`)- %s\n", mysql_error(&mysql_handle) ); + } + } + } + + + sprintf(tmp_sql,"UPDATE `%s` SET `name`='%s', `exp`='%d', `item`='%d', `leader_id`=`leader_id` WHERE `party_id`='%d'", + party_db, t_name,p->exp,p->item,party_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (inset/update `party`)- %s\n", mysql_error(&mysql_handle) ); + } + + + // printf("- Update party %d information \n",party_id); + } + } else { + // Add new party, if not exist + int i = 0; + while (i<MAX_PARTY&&((p->member[i].account_id>0&&p->member[i].leader==0)||(p->member[i].account_id<0))) i++; + if (i<MAX_PARTY) leader_id = p->member[i].account_id; + sprintf(tmp_sql,"INSERT INTO `%s` (`party_id`, `name`, `exp`, `item`, `leader_id`) VALUES ('%d', '%s', '%d', '%d', '%d')", + party_db, party_id, t_name, p->exp, p->item,leader_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (inset/update `party`)- %s\n", mysql_error(&mysql_handle) ); + return 0; + } + + sprintf(tmp_sql,"UPDATE `%s` SET `party_id`='%d', `online`='1' WHERE `account_id`='%d' AND `name`='%s'", + char_db, party_id,leader_id, jstrescapecpy(t_member,p->member[i].name)); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (inset/update `party`)- %s\n", mysql_error(&mysql_handle) ); + } + + //printf("- Insert new party %d \n",party_id); + } + + printf("Party save success\n"); + return 0; + +} + +// Read party from mysql +int inter_party_fromsql(int party_id,struct party *p) +{ + int leader_id=0; + printf("(\033[1;64m%d\033[0m) Request load party - ",party_id); + + memset(p, 0, sizeof(struct party)); + + sprintf(tmp_sql,"SELECT `party_id`, `name`,`exp`,`item`, `leader_id` FROM `%s` WHERE `party_id`='%d'",party_db, party_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (select `party`)- %s\n", mysql_error(&mysql_handle) ); + return 0; + } + + sql_res = mysql_store_result(&mysql_handle) ; + if (sql_res!=NULL && mysql_num_rows(sql_res)>0) { + sql_row = mysql_fetch_row(sql_res); + // printf("- Read party %d from MySQL\n",party_id); + p->party_id = party_id; + strcpy(p->name, sql_row[1]); + p->exp = atoi(sql_row[2]); + p->item = atoi(sql_row[3]); + leader_id = atoi(sql_row[4]); + } else { + mysql_free_result(sql_res); + // printf("- Cannot find party %d \n",party_id); + return 0; + } + + mysql_free_result(sql_res); + + // Load members + sprintf(tmp_sql,"SELECT `account_id`, `name`,`base_level`,`last_map`,`online` FROM `%s` WHERE `party_id`='%d'",char_db, party_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (select `party`)- %s\n", mysql_error(&mysql_handle) ); + return 0; + } + sql_res = mysql_store_result(&mysql_handle) ; + if (sql_res!=NULL && mysql_num_rows(sql_res)>0) { + int i; + for(i=0;(sql_row = mysql_fetch_row(sql_res));i++){ + struct party_member *m = &p->member[i]; + m->account_id = atoi(sql_row[0]); + if (m->account_id == leader_id) m->leader = 1; else m->leader = 0; + strncpy(m->name,sql_row[1],sizeof(m->name)); + m->lv = atoi(sql_row[2]); + strncpy(m->map,sql_row[3],sizeof(m->map)); + m->online = atoi(sql_row[4]); + } + // printf("- %d members found in party %d \n",i,party_id); + } + mysql_free_result(sql_res); + + + printf("Party load success\n"); + return 0; + +} + +int inter_party_sql_init(){ + int i; + + //memory alloc + printf("interserver party memory initialize.... (%d byte)\n",sizeof(struct party)); + party_pt = calloc(sizeof(struct party), 1); + + sprintf(tmp_sql,"UPDATE `%s` SET `online`='0'", char_db); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (update `char`)- %s\n", mysql_error(&mysql_handle) ); + } + + sprintf (tmp_sql , "SELECT count(*) FROM `%s`",party_db); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle) ); + } + sql_res = mysql_store_result(&mysql_handle) ; + sql_row = mysql_fetch_row(sql_res); + printf("total party data -> '%s'.......\n",sql_row[0]); + i = atoi (sql_row[0]); + mysql_free_result(sql_res); + + if (i > 0) { + //set party_newid + sprintf (tmp_sql , "SELECT max(`party_id`) FROM `%s`", party_db); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle) ); + } + + sql_res = mysql_store_result(&mysql_handle) ; + + sql_row = mysql_fetch_row(sql_res); + party_newid = atoi (sql_row[0])+1; + mysql_free_result(sql_res); + } + + printf("set party_newid: %d.......\n",party_newid); + + return 0; +} + + +// Search for the party according to its name +struct party* search_partyname(char *str) +{ + struct party *p=NULL; + int leader_id = 0; + char t_name[24]; + + sprintf(tmp_sql,"SELECT `party_id`, `name`,`exp`,`item`,`leader_id` FROM `%s` WHERE `name`='%s'",party_db, jstrescapecpy(t_name,str)); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (select `party`)- %s\n", mysql_error(&mysql_handle) ); + } + sql_res = mysql_store_result(&mysql_handle) ; + if (sql_res==NULL || mysql_num_rows(sql_res)<=0) { mysql_free_result(sql_res); return p; } + sql_row = mysql_fetch_row(sql_res); + p = party_pt; + p->party_id = atoi(sql_row[0]); + strcpy(p->name, sql_row[1]); + p->exp = atoi(sql_row[2]); + p->item = atoi(sql_row[3]); + leader_id = atoi(sql_row[4]); + mysql_free_result(sql_res); + + // Load members + sprintf(tmp_sql,"SELECT `account_id`, `name`,`base_level`,`last_map`,`online` FROM `%s` WHERE `party_id`='%d'",char_db, p->party_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (select `party`)- %s\n", mysql_error(&mysql_handle) ); + return 0; + } + sql_res = mysql_store_result(&mysql_handle) ; + if (sql_res!=NULL && mysql_num_rows(sql_res)>0) { + int i; + for(i=0;(sql_row = mysql_fetch_row(sql_res));i++){ + struct party_member *m = &p->member[i]; + m->account_id = atoi(sql_row[0]); + if (m->account_id == leader_id) m->leader = 1; else m->leader = 0; + strncpy(m->name,sql_row[1],sizeof(m->name)); + m->lv = atoi(sql_row[2]); + strncpy(m->map,sql_row[3],sizeof(m->map)); + m->online = atoi(sql_row[4]); + } + printf("- %d members found in party %d \n",i,p->party_id); + } + mysql_free_result(sql_res); + + return p; +} + +// EXP公平分配できるかチェック +int party_check_exp_share(struct party *p) +{ + int i; + int maxlv=0,minlv=0x7fffffff; + for(i=0;i<MAX_PARTY;i++){ + int lv=p->member[i].lv; + if( p->member[i].online ){ + if( lv < minlv ) minlv=lv; + if( maxlv < lv ) maxlv=lv; + } + } + return (maxlv==0 || maxlv-minlv<=party_share_level); +} +// Is there any member in the party? +int party_check_empty(struct party *p) +{ + int i; + if (p==NULL||p->party_id==0) return 1; +// printf("party check empty %08X\n",(int)p); + for(i=0;i<MAX_PARTY;i++){ +// printf("%d acc=%d\n",i,p->member[i].account_id); + if(p->member[i].account_id>0){ + return 0; + } + } + // If there is no member, then break the party + mapif_party_broken(p->party_id,0); + inter_party_tosql(p->party_id,p); + return 1; +} + + +// Check if a member is in two party, not necessary :) +int party_check_conflict(int party_id,int account_id,char *nick) +{ + return 0; +} + +//------------------------------------------------------------------- +// map serverへの通信 + +// パーティ作成可否 +int mapif_party_created(int fd,int account_id,struct party *p) +{ + WFIFOW(fd,0)=0x3820; + WFIFOL(fd,2)=account_id; + if(p!=NULL){ + WFIFOB(fd,6)=0; + WFIFOL(fd,7)=p->party_id; + memcpy(WFIFOP(fd,11),p->name,24); + printf("int_party: created! %d %s\n",p->party_id,p->name); + }else{ + WFIFOB(fd,6)=1; + WFIFOL(fd,7)=0; + memcpy(WFIFOP(fd,11),"error",24); + } + WFIFOSET(fd,35); + return 0; +} + +// パーティ情報見つからず +int mapif_party_noinfo(int fd,int party_id) +{ + WFIFOW(fd,0)=0x3821; + WFIFOW(fd,2)=8; + WFIFOL(fd,4)=party_id; + WFIFOSET(fd,8); + printf("int_party: info not found %d\n",party_id); + return 0; +} +// パーティ情報まとめ送り +int mapif_party_info(int fd,struct party *p) +{ + unsigned char buf[1024]; + WBUFW(buf,0)=0x3821; + memcpy(buf+4,p,sizeof(struct party)); + WBUFW(buf,2)=4+sizeof(struct party); + if(fd<0) + mapif_sendall(buf,WBUFW(buf,2)); + else + mapif_send(fd,buf,WBUFW(buf,2)); +// printf("int_party: info %d %s\n",p->party_id,p->name); + return 0; +} +// パーティメンバ追加可否 +int mapif_party_memberadded(int fd,int party_id,int account_id,int flag) +{ + WFIFOW(fd,0)=0x3822; + WFIFOL(fd,2)=party_id; + WFIFOL(fd,6)=account_id; + WFIFOB(fd,10)=flag; + WFIFOSET(fd,11); + return 0; +} +// パーティ設定変更通知 +int mapif_party_optionchanged(int fd,struct party *p,int account_id,int flag) +{ + unsigned char buf[16]; + WBUFW(buf,0)=0x3823; + WBUFL(buf,2)=p->party_id; + WBUFL(buf,6)=account_id; + WBUFW(buf,10)=p->exp; + WBUFW(buf,12)=p->item; + WBUFB(buf,14)=flag; + if(flag==0) + mapif_sendall(buf,15); + else + mapif_send(fd,buf,15); + //printf("int_party: option changed %d %d %d %d %d\n",p->party_id,account_id,p->exp,p->item,flag); + return 0; +} +// パーティ脱退通知 +int mapif_party_leaved(int party_id,int account_id,char *name) +{ + unsigned char buf[64]; + WBUFW(buf,0)=0x3824; + WBUFL(buf,2)=party_id; + WBUFL(buf,6)=account_id; + memcpy(WBUFP(buf,10),name,24); + mapif_sendall(buf,34); + //printf("int_party: party leaved %d %d %s\n",party_id,account_id,name); + return 0; +} +// パーティマップ更新通知 +int mapif_party_membermoved(struct party *p,int idx) +{ + unsigned char buf[32]; + WBUFW(buf,0)=0x3825; + WBUFL(buf,2)=p->party_id; + WBUFL(buf,6)=p->member[idx].account_id; + memcpy(WBUFP(buf,10),p->member[idx].map,16); + WBUFB(buf,26)=p->member[idx].online; + WBUFW(buf,27)=p->member[idx].lv; + mapif_sendall(buf,29); + return 0; +} +// パーティ解散通知 +int mapif_party_broken(int party_id,int flag) +{ + unsigned char buf[16]; + WBUFW(buf,0)=0x3826; + WBUFL(buf,2)=party_id; + WBUFB(buf,6)=flag; + mapif_sendall(buf,7); + //printf("int_party: broken %d\n",party_id); + return 0; +} +// パーティ内発言 +int mapif_party_message(int party_id,int account_id,char *mes,int len) +{ + unsigned char buf[512]; + WBUFW(buf,0)=0x3827; + WBUFW(buf,2)=len+12; + WBUFL(buf,4)=party_id; + WBUFL(buf,8)=account_id; + memcpy(WBUFP(buf,12),mes,len); + mapif_sendall(buf,len+12); + return 0; +} + +//------------------------------------------------------------------- +// map serverからの通信 + + +// Create Party +int mapif_parse_CreateParty(int fd,int account_id,char *name,char *nick,char *map,int lv) +{ + struct party *p; + if( (p=search_partyname(name))!=NULL){ +// printf("int_party: same name party exists [%s]\n",name); + mapif_party_created(fd,account_id,NULL); + return 0; + } + p=party_pt; + if(p==NULL){ + printf("int_party: out of memory !\n"); + mapif_party_created(fd,account_id,NULL); + return 0; + } + memset(p,0,sizeof(struct party)); + p->party_id=party_newid++; + memcpy(p->name,name,24); + p->exp=0; + p->item=0; + p->member[0].account_id=account_id; + memcpy(p->member[0].name,nick,24); + memcpy(p->member[0].map,map,16); + p->member[0].leader=1; + p->member[0].online=1; + p->member[0].lv=lv; + + inter_party_tosql(p->party_id,p); + + mapif_party_created(fd,account_id,p); + mapif_party_info(fd,p); + + return 0; +} +// パーティ情報要求 +int mapif_parse_PartyInfo(int fd,int party_id) +{ + struct party *p = party_pt; + if(p==NULL){ + printf("int_party: out of memory !\n"); + return 0; + } + inter_party_fromsql(party_id, p); + + if(p->party_id >= 0) + mapif_party_info(fd,p); + else + mapif_party_noinfo(fd,party_id); + return 0; +} +// パーティ追加要求 +int mapif_parse_PartyAddMember(int fd,int party_id,int account_id,char *nick,char *map,int lv) +{ + struct party *p; + int i; + + p = party_pt; + if(p==NULL){ + printf("int_party: out of memory !\n"); + return 0; + } + inter_party_fromsql(party_id, p); + + if(p->party_id <= 0){ + mapif_party_memberadded(fd,party_id,account_id,1); + return 0; + } + + for(i=0;i<MAX_PARTY;i++){ + if(p->member[i].account_id==0){ + int flag=0; + + p->member[i].account_id=account_id; + memcpy(p->member[i].name,nick,24); + memcpy(p->member[i].map,map,16); + p->member[i].leader=0; + p->member[i].online=1; + p->member[i].lv=lv; + mapif_party_memberadded(fd,party_id,account_id,0); + mapif_party_info(-1,p); + + if( p->exp>0 && !party_check_exp_share(p) ){ + p->exp=0; + flag=0x01; + } + if(flag) + mapif_party_optionchanged(fd,p,0,0); + + inter_party_tosql(party_id, p); + return 0; + } + } + mapif_party_memberadded(fd,party_id,account_id,1); + //inter_party_tosql(party_id, p); + return 0; +} +// パーティー設定変更要求 +int mapif_parse_PartyChangeOption(int fd,int party_id,int account_id,int exp,int item) +{ + struct party *p; + int flag=0; + + p = party_pt; + if(p==NULL){ + printf("int_party: out of memory !\n"); + return 0; + } + + inter_party_fromsql(party_id, p); + + if(p->party_id <= 0){ + return 0; + } + + p->exp=exp; + if( exp>0 && !party_check_exp_share(p) ){ + flag|=0x01; + p->exp=0; + } + + p->item=item; + + mapif_party_optionchanged(fd,p,account_id,flag); + inter_party_tosql(party_id, p); + return 0; +} +// パーティ脱退要求 +int mapif_parse_PartyLeave(int fd,int party_id,int account_id) +{ + char t_member[24]; + struct party *p = party_pt; + if(p==NULL){ + printf("int_party: out of memory !\n"); + return 0; + } + + inter_party_fromsql(party_id, p); + + if(p->party_id >= 0){ + int i,j; + for(i=0;i<MAX_PARTY;i++){ + + if(p->member[i].account_id==account_id){ + //printf("p->member[i].account_id = %d , account_id = %d \n",p->member[i].account_id,account_id); + mapif_party_leaved(party_id,account_id,p->member[i].name); + + + + // Update char information, does the name need encoding? + sprintf(tmp_sql,"UPDATE `%s` SET `party_id`='0', `online`='1' WHERE `party_id`='%d' AND `name`='%s'", + char_db, party_id, jstrescapecpy(t_member,p->member[i].name)); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (update `char`)- %s\n", mysql_error(&mysql_handle) ); + } +// printf("Delete member %s from MySQL \n", p->member[i].name); + + if (p->member[i].leader==1){ + for(j=0;j<MAX_PARTY;j++) + { + //printf("j = %d , p->member[j].account_id = %d , p->member[j].account_id = %d \n",j,p->member[j].account_id,p->member[j].account_id); + if(p->member[j].account_id>0&&j!=i){ + mapif_party_leaved(party_id,p->member[j].account_id,p->member[j].name); + // Update char information, does the name need encoding? + sprintf(tmp_sql,"UPDATE `%s` SET `party_id`='0', `online`='1' WHERE `party_id`='%d' AND `name`='%s'", + char_db, party_id, jstrescapecpy(t_member,p->member[i].name)); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (update `char`)- %s\n", mysql_error(&mysql_handle) ); + } +// printf("Delete member %s from MySQL \n", p->member[j].name); + } + } + // Delete the party, if has no member. + sprintf(tmp_sql,"DELETE FROM `%s` WHERE `party_id`='%d'",party_db, party_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle) ); + } +// printf("Leader breaks party %d \n",party_id); + memset(p, 0, sizeof(struct party)); + }else memset(&p->member[i],0,sizeof(struct party_member)); + + break; + + } + } + if( party_check_empty(p)==0 ) + mapif_party_info(-1,p);// まだ人がいるのでデータ送信 + /* + else + inter_party_tosql(party_id,p); // Break the party if no member + */ + }else{ + sprintf(tmp_sql,"UPDATE `%s` SET `party_id`='0' WHERE `party_id`='%d' AND `account_id`='%d' AND `online`='1'", + char_db, party_id, account_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (update `char`)- %s\n", mysql_error(&mysql_handle) ); + } + } + return 0; +} +// When member goes to other map +int mapif_parse_PartyChangeMap(int fd,int party_id,int account_id,char *map,int online,int lv) +{ + struct party *p; + int i; + + p = party_pt; + if(p==NULL){ + printf("int_party: out of memory !\n"); + return 0; + } + inter_party_fromsql(party_id, p); + + if(p->party_id <= 0){ + return 0; + } + for(i=0;i<MAX_PARTY;i++){ + if(p->member[i].account_id==account_id){ + int flag=0; + + memcpy(p->member[i].map,map,16); + p->member[i].online=online; + p->member[i].lv=lv; + mapif_party_membermoved(p,i); + + if( p->exp>0 && !party_check_exp_share(p) ){ + p->exp=0; + flag=1; + } + if(flag) + mapif_party_optionchanged(fd,p,0,0); + break; + } + } + inter_party_tosql(party_id, p); + return 0; +} +// パーティ解散要求 +int mapif_parse_BreakParty(int fd,int party_id) +{ + struct party *p; + + p = party_pt; + if(p==NULL){ + printf("int_party: out of memory !\n"); + return 0; + } + + inter_party_fromsql(party_id, p); + + if(p->party_id <= 0){ + return 0; + } + inter_party_tosql(party_id,p); + + mapif_party_broken(fd,party_id); + return 0; +} +// パーティメッセージ送信 +int mapif_parse_PartyMessage(int fd,int party_id,int account_id,char *mes,int len) +{ + return mapif_party_message(party_id,account_id,mes,len); +} +// パーティチェック要求 +int mapif_parse_PartyCheck(int fd,int party_id,int account_id,char *nick) +{ + return party_check_conflict(party_id,account_id,nick); +} + +// map server からの通信 +// ・1パケットのみ解析すること +// ・パケット長データはinter.cにセットしておくこと +// ・パケット長チェックや、RFIFOSKIPは呼び出し元で行われるので行ってはならない +// ・エラーなら0(false)、そうでないなら1(true)をかえさなければならない +int inter_party_parse_frommap(int fd) +{ + switch(RFIFOW(fd,0)){ + case 0x3020: mapif_parse_CreateParty(fd,RFIFOL(fd,2),RFIFOP(fd,6),RFIFOP(fd,30),RFIFOP(fd,54),RFIFOW(fd,70)); break; + case 0x3021: mapif_parse_PartyInfo(fd,RFIFOL(fd,2)); break; + case 0x3022: mapif_parse_PartyAddMember(fd,RFIFOL(fd,2),RFIFOL(fd,6),RFIFOP(fd,10),RFIFOP(fd,34),RFIFOW(fd,50)); break; + case 0x3023: mapif_parse_PartyChangeOption(fd,RFIFOL(fd,2),RFIFOL(fd,6),RFIFOW(fd,10),RFIFOW(fd,12)); break; + case 0x3024: mapif_parse_PartyLeave(fd,RFIFOL(fd,2),RFIFOL(fd,6)); break; + case 0x3025: mapif_parse_PartyChangeMap(fd,RFIFOL(fd,2),RFIFOL(fd,6),RFIFOP(fd,10),RFIFOB(fd,26),RFIFOW(fd,27)); break; + case 0x3026: mapif_parse_BreakParty(fd,RFIFOL(fd,2)); break; + case 0x3027: mapif_parse_PartyMessage(fd,RFIFOL(fd,4),RFIFOL(fd,8),RFIFOP(fd,12),RFIFOW(fd,2)-12); break; + case 0x3028: mapif_parse_PartyCheck(fd,RFIFOL(fd,2),RFIFOL(fd,6),RFIFOP(fd,10)); break; + default: + return 0; + } + return 1; +} + +// サーバーから脱退要求(キャラ削除用) +int inter_party_leave(int party_id,int account_id) +{ + return mapif_parse_PartyLeave(-1,party_id,account_id); +} + + + diff --git a/src/char_sql/int_party.h b/src/char_sql/int_party.h new file mode 100644 index 0000000..04f71c8 --- /dev/null +++ b/src/char_sql/int_party.h @@ -0,0 +1,8 @@ +#ifndef _INT_PARTY_H_ +#define _INT_PARTY_H_ + +int inter_party_parse_frommap(int fd); +int inter_party_sql_init(); +int inter_party_leave(int party_id,int account_id); + +#endif diff --git a/src/char_sql/int_pet.c b/src/char_sql/int_pet.c new file mode 100644 index 0000000..9b5566d --- /dev/null +++ b/src/char_sql/int_pet.c @@ -0,0 +1,324 @@ +// +// original code from athena +// SQL conversion by Jioh L. Jung +// +#include "char.h" +#include "strlib.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +struct s_pet *pet_pt; +static int pet_newid = 100; + + +//--------------------------------------------------------- +int inter_pet_tosql(int pet_id, struct s_pet *p) { + //`pet` (`pet_id`, `class`,`name`,`account_id`,`char_id`,`level`,`egg_id`,`equip`,`intimate`,`hungry`,`rename_flag`,`incuvate`) + char t_name[100]; + + printf("request save pet: %d.......\n",pet_id); + + jstrescapecpy(t_name, p->name); + + if(p->hungry < 0) + p->hungry = 0; + else if(p->hungry > 100) + p->hungry = 100; + if(p->intimate < 0) + p->intimate = 0; + else if(p->intimate > 1000) + p->intimate = 1000; + sprintf(tmp_sql,"SELECT * FROM `%s` WHERE `pet_id`='%d'",pet_db, pet_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle) ); + } + sql_res = mysql_store_result(&mysql_handle) ; + if (sql_res!=NULL && mysql_num_rows(sql_res)>0) + //row reside -> updating + sprintf(tmp_sql, "UPDATE `%s` SET `class`='%d',`name`='%s',`account_id`='%d',`char_id`='%d',`level`='%d',`egg_id`='%d',`equip`='%d',`intimate`='%d',`hungry`='%d',`rename_flag`='%d',`incuvate`='%d' WHERE `pet_id`='%d'", + pet_db, p->class, t_name, p->account_id, p->char_id, p->level, p->egg_id, + p->equip, p->intimate, p->hungry, p->rename_flag, p->incuvate, p->pet_id); + else //no row -> insert + sprintf(tmp_sql,"INSERT INTO `%s` (`pet_id`, `class`,`name`,`account_id`,`char_id`,`level`,`egg_id`,`equip`,`intimate`,`hungry`,`rename_flag`,`incuvate`) VALUES ('%d', '%d', '%s', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d')", + pet_db, pet_id, p->class, t_name, p->account_id, p->char_id, p->level, p->egg_id, + p->equip, p->intimate, p->hungry, p->rename_flag, p->incuvate); + mysql_free_result(sql_res) ; //resource free + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (inset/update `pet`)- %s\n", mysql_error(&mysql_handle) ); + } + + printf("pet save success.......\n"); + return 0; +} + +int inter_pet_fromsql(int pet_id, struct s_pet *p){ + + printf("request load pet: %d.......\n",pet_id); + + memset(p, 0, sizeof(struct s_pet)); + + //`pet` (`pet_id`, `class`,`name`,`account_id`,`char_id`,`level`,`egg_id`,`equip`,`intimate`,`hungry`,`rename_flag`,`incuvate`) + + sprintf(tmp_sql,"SELECT `pet_id`, `class`,`name`,`account_id`,`char_id`,`level`,`egg_id`,`equip`,`intimate`,`hungry`,`rename_flag`,`incuvate` FROM `%s` WHERE `pet_id`='%d'",pet_db, pet_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (select `pet`)- %s\n", mysql_error(&mysql_handle) ); + return 0; + } + sql_res = mysql_store_result(&mysql_handle) ; + if (sql_res!=NULL && mysql_num_rows(sql_res)>0) { + sql_row = mysql_fetch_row(sql_res); + + p->pet_id = pet_id; + p->class = atoi(sql_row[1]); + memcpy(p->name, sql_row[2],24); + p->account_id = atoi(sql_row[3]); + p->char_id = atoi(sql_row[4]); + p->level = atoi(sql_row[5]); + p->egg_id = atoi(sql_row[6]); + p->equip = atoi(sql_row[7]); + p->intimate = atoi(sql_row[8]); + p->hungry = atoi(sql_row[9]); + p->rename_flag = atoi(sql_row[10]); + p->incuvate = atoi(sql_row[11]); + } + if(p->hungry < 0) + p->hungry = 0; + else if(p->hungry > 100) + p->hungry = 100; + if(p->intimate < 0) + p->intimate = 0; + else if(p->intimate > 1000) + p->intimate = 1000; + + mysql_free_result(sql_res); + + printf("pet load success.......\n"); + return 0; +} +//---------------------------------------------- + +int inter_pet_sql_init(){ + int i; + + //memory alloc + printf("interserver pet memory initialize.... (%d byte)\n",sizeof(struct s_pet)); + pet_pt = calloc(sizeof(struct s_pet), 1); + + sprintf (tmp_sql , "SELECT count(*) FROM `%s`", pet_db); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle) ); + exit(0); + } + sql_res = mysql_store_result(&mysql_handle) ; + sql_row = mysql_fetch_row(sql_res); + printf("total pet data -> '%s'.......\n",sql_row[0]); + i = atoi (sql_row[0]); + mysql_free_result(sql_res); + + if (i > 0) { + //set pet_newid + sprintf (tmp_sql , "SELECT max(`pet_id`) FROM `%s`",pet_db ); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle) ); + } + + sql_res = mysql_store_result(&mysql_handle) ; + + sql_row = mysql_fetch_row(sql_res); + pet_newid = atoi (sql_row[0]); + mysql_free_result(sql_res); + } + + printf("set pet_newid: %d.......\n",pet_newid); + + return 0; +} +//---------------------------------- +int inter_pet_delete(int pet_id){ + printf("request delete pet: %d.......\n",pet_id); + + sprintf(tmp_sql,"DELETE FROM `%s` WHERE `pet_id`='%d'",pet_db, pet_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle) ); + } + return 0; +} +//------------------------------------------------------ +int mapif_pet_created(int fd, int account_id, struct s_pet *p) +{ + WFIFOW(fd, 0) =0x3880; + WFIFOL(fd, 2) =account_id; + if(p!=NULL){ + WFIFOB(fd, 6)=0; + WFIFOL(fd, 7) =p->pet_id; + printf("int_pet: created! %d %s\n", p->pet_id, p->name); + }else{ + WFIFOB(fd, 6)=1; + WFIFOL(fd, 7)=0; + } + WFIFOSET(fd, 11); + + return 0; +} + +int mapif_pet_info(int fd, int account_id, struct s_pet *p){ + WFIFOW(fd, 0) =0x3881; + WFIFOW(fd, 2) =sizeof(struct s_pet) + 9; + WFIFOL(fd, 4) =account_id; + WFIFOB(fd, 8)=0; + memcpy(WFIFOP(fd, 9), p, sizeof(struct s_pet)); + WFIFOSET(fd, WFIFOW(fd, 2)); + + return 0; +} + +int mapif_pet_noinfo(int fd, int account_id){ + WFIFOW(fd, 0) =0x3881; + WFIFOW(fd, 2) =sizeof(struct s_pet) + 9; + WFIFOL(fd, 4) =account_id; + WFIFOB(fd, 8)=1; + memset(WFIFOP(fd, 9), 0, sizeof(struct s_pet)); + WFIFOSET(fd, WFIFOW(fd, 2)); + + return 0; +} + +int mapif_save_pet_ack(int fd, int account_id, int flag){ + WFIFOW(fd, 0) =0x3882; + WFIFOL(fd, 2) =account_id; + WFIFOB(fd, 6) =flag; + WFIFOSET(fd, 7); + + return 0; +} + +int mapif_delete_pet_ack(int fd, int flag){ + WFIFOW(fd, 0) =0x3883; + WFIFOB(fd, 2) =flag; + WFIFOSET(fd, 3); + + return 0; +} + +int mapif_create_pet(int fd, int account_id, int char_id, short pet_class, short pet_lv, short pet_egg_id, + short pet_equip, short intimate, short hungry, char rename_flag, char incuvate, char *pet_name){ + + memset(pet_pt, 0, sizeof(struct s_pet)); + pet_pt->pet_id = pet_newid++; + memcpy(pet_pt->name, pet_name, 24); + if(incuvate == 1) + pet_pt->account_id = pet_pt->char_id = 0; + else { + pet_pt->account_id = account_id; + pet_pt->char_id = char_id; + } + pet_pt->class = pet_class; + pet_pt->level = pet_lv; + pet_pt->egg_id = pet_egg_id; + pet_pt->equip = pet_equip; + pet_pt->intimate = intimate; + pet_pt->hungry = hungry; + pet_pt->rename_flag = rename_flag; + pet_pt->incuvate = incuvate; + + if(pet_pt->hungry < 0) + pet_pt->hungry = 0; + else if(pet_pt->hungry > 100) + pet_pt->hungry = 100; + if(pet_pt->intimate < 0) + pet_pt->intimate = 0; + else if(pet_pt->intimate > 1000) + pet_pt->intimate = 1000; + + inter_pet_tosql(pet_pt->pet_id,pet_pt); + + mapif_pet_created(fd, account_id, pet_pt); + + return 0; +} + +int mapif_load_pet(int fd, int account_id, int char_id, int pet_id){ + memset(pet_pt, 0, sizeof(struct s_pet)); + + inter_pet_fromsql(pet_id, pet_pt); + + if(pet_pt!=NULL) { + if(pet_pt->incuvate == 1) { + pet_pt->account_id = pet_pt->char_id = 0; + mapif_pet_info(fd, account_id, pet_pt); + } + else if(account_id == pet_pt->account_id && char_id == pet_pt->char_id) + mapif_pet_info(fd, account_id, pet_pt); + else + mapif_pet_noinfo(fd, account_id); + } + else + mapif_pet_noinfo(fd, account_id); + + return 0; +} + +int mapif_save_pet(int fd, int account_id, struct s_pet *data) { + //here process pet save request. + int len=RFIFOW(fd, 2); + if(sizeof(struct s_pet)!=len-8) { + printf("inter pet: data size error %d %d\n", sizeof(struct s_pet), len-8); + } + + else{ + if(data->hungry < 0) + data->hungry = 0; + else if(data->hungry > 100) + data->hungry = 100; + if(data->intimate < 0) + data->intimate = 0; + else if(data->intimate > 1000) + data->intimate = 1000; + inter_pet_tosql(data->pet_id,data); + mapif_save_pet_ack(fd, account_id, 0); + } + + return 0; +} + +int mapif_delete_pet(int fd, int pet_id){ + mapif_delete_pet_ack(fd, inter_pet_delete(pet_id)); + + return 0; +} + +int mapif_parse_CreatePet(int fd){ + mapif_create_pet(fd, RFIFOL(fd, 2), RFIFOL(fd, 6), RFIFOW(fd, 10), RFIFOW(fd, 12), RFIFOW(fd, 14), RFIFOW(fd, 16), RFIFOL(fd, 18), + RFIFOL(fd, 20), RFIFOB(fd, 22), RFIFOB(fd, 23), RFIFOP(fd, 24)); + return 0; +} + +int mapif_parse_LoadPet(int fd){ + mapif_load_pet(fd, RFIFOL(fd, 2), RFIFOL(fd, 6), RFIFOL(fd, 10)); + return 0; +} + +int mapif_parse_SavePet(int fd){ + mapif_save_pet(fd, RFIFOL(fd, 4), (struct s_pet *) RFIFOP(fd, 8)); + return 0; +} + +int mapif_parse_DeletePet(int fd){ + mapif_delete_pet(fd, RFIFOL(fd, 2)); + return 0; +} + +int inter_pet_parse_frommap(int fd){ + switch(RFIFOW(fd, 0)){ + case 0x3080: mapif_parse_CreatePet(fd); break; + case 0x3081: mapif_parse_LoadPet(fd); break; + case 0x3082: mapif_parse_SavePet(fd); break; + case 0x3083: mapif_parse_DeletePet(fd); break; + default: + return 0; + } + return 1; +} + diff --git a/src/char_sql/int_pet.h b/src/char_sql/int_pet.h new file mode 100644 index 0000000..b6e3f1b --- /dev/null +++ b/src/char_sql/int_pet.h @@ -0,0 +1,12 @@ +#ifndef _INT_PET_H_ +#define _INT_PET_H_ + +int inter_pet_init(); +int inter_pet_save(); +int inter_pet_delete(int pet_id); + +int inter_pet_parse_frommap(int fd); +int inter_pet_sql_init(); +//extern char pet_txt[256]; + +#endif diff --git a/src/char_sql/int_storage.c b/src/char_sql/int_storage.c new file mode 100644 index 0000000..fdf85ae --- /dev/null +++ b/src/char_sql/int_storage.c @@ -0,0 +1,366 @@ +// +// original code from athena +// SQL conversion by Jioh L. Jung +// +#include "char.h" +#include "itemdb.h" +#include <string.h> +#include <stdlib.h> + +#define STORAGE_MEMINC 16 + +// reset by inter_config_read() +struct storage *storage_pt=NULL; +struct guild_storage *guild_storage_pt=NULL; + + +// storage data -> DB conversion +int storage_tosql(int account_id,struct storage *p){ + int i; + int eqcount=1; + int noteqcount=1; + struct itemtemp mapitem; + for(i=0;i<MAX_STORAGE;i++){ + if(p->storage[i].nameid>0){ + if(itemdb_isequip(p->storage[i].nameid)==1){ + mapitem.equip[eqcount].flag=0; + mapitem.equip[eqcount].id = p->storage[i].id; + mapitem.equip[eqcount].nameid=p->storage[i].nameid; + mapitem.equip[eqcount].amount = p->storage[i].amount; + mapitem.equip[eqcount].equip = p->storage[i].equip; + mapitem.equip[eqcount].identify = p->storage[i].identify; + mapitem.equip[eqcount].refine = p->storage[i].refine; + mapitem.equip[eqcount].attribute = p->storage[i].attribute; + mapitem.equip[eqcount].card[0] = p->storage[i].card[0]; + mapitem.equip[eqcount].card[1] = p->storage[i].card[1]; + mapitem.equip[eqcount].card[2] = p->storage[i].card[2]; + mapitem.equip[eqcount].card[3] = p->storage[i].card[3]; + mapitem.equip[eqcount].broken = p->storage[i].broken; + eqcount++; + } + else if(itemdb_isequip(p->storage[i].nameid)==0){ + mapitem.notequip[noteqcount].flag=0; + mapitem.notequip[noteqcount].id = p->storage[i].id; + mapitem.notequip[noteqcount].nameid=p->storage[i].nameid; + mapitem.notequip[noteqcount].amount = p->storage[i].amount; + mapitem.notequip[noteqcount].equip = p->storage[i].equip; + mapitem.notequip[noteqcount].identify = p->storage[i].identify; + mapitem.notequip[noteqcount].refine = p->storage[i].refine; + mapitem.notequip[noteqcount].attribute = p->storage[i].attribute; + mapitem.notequip[noteqcount].card[0] = p->storage[i].card[0]; + mapitem.notequip[noteqcount].card[1] = p->storage[i].card[1]; + mapitem.notequip[noteqcount].card[2] = p->storage[i].card[2]; + mapitem.notequip[noteqcount].card[3] = p->storage[i].card[3]; + mapitem.notequip[noteqcount].broken = p->storage[i].broken; + noteqcount++; + } + } + } + + memitemdata_to_sql(mapitem, eqcount, noteqcount, account_id,TABLE_STORAGE); + + //printf ("storage dump to DB - id: %d (total: %d)\n", account_id, j); + return 0; +} + +// DB -> storage data conversion +int storage_fromsql(int account_id, struct storage *p){ + int i=0; + + memset(p,0,sizeof(struct storage)); //clean up memory + p->storage_amount = 0; + p->account_id = account_id; + + // storage {`account_id`/`id`/`nameid`/`amount`/`equip`/`identify`/`refine`/`attribute`/`card0`/`card1`/`card2`/`card3`} + sprintf(tmp_sql,"SELECT `id`,`nameid`,`amount`,`equip`,`identify`,`refine`,`attribute`,`card0`,`card1`,`card2`,`card3`,`broken` FROM `%s` WHERE `account_id`='%d'",storage_db, account_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle) ); + } + sql_res = mysql_store_result(&mysql_handle) ; + + if (sql_res) { + while((sql_row = mysql_fetch_row(sql_res))) { //start to fetch + p->storage[i].id= atoi(sql_row[0]); + p->storage[i].nameid= atoi(sql_row[1]); + p->storage[i].amount= atoi(sql_row[2]); + p->storage[i].equip= atoi(sql_row[3]); + p->storage[i].identify= atoi(sql_row[4]); + p->storage[i].refine= atoi(sql_row[5]); + p->storage[i].attribute= atoi(sql_row[6]); + p->storage[i].card[0]= atoi(sql_row[7]); + p->storage[i].card[1]= atoi(sql_row[8]); + p->storage[i].card[2]= atoi(sql_row[9]); + p->storage[i].card[3]= atoi(sql_row[10]); + p->storage[i].broken = atoi(sql_row[11]); + p->storage_amount = ++i; + } + mysql_free_result(sql_res); + } + + printf ("storage load complete from DB - id: %d (total: %d)\n", account_id, p->storage_amount); + return 1; +} + +// Save guild_storage data to sql +int guild_storage_tosql(int guild_id, struct guild_storage *p){ + int i; + int eqcount=1; + int noteqcount=1; + struct itemtemp mapitem; + for(i=0;i<MAX_GUILD_STORAGE;i++){ + if(p->storage[i].nameid>0){ + if(itemdb_isequip(p->storage[i].nameid)==1){ + mapitem.equip[eqcount].flag=0; + mapitem.equip[eqcount].id = p->storage[i].id; + mapitem.equip[eqcount].nameid=p->storage[i].nameid; + mapitem.equip[eqcount].amount = p->storage[i].amount; + mapitem.equip[eqcount].equip = p->storage[i].equip; + mapitem.equip[eqcount].identify = p->storage[i].identify; + mapitem.equip[eqcount].refine = p->storage[i].refine; + mapitem.equip[eqcount].attribute = p->storage[i].attribute; + mapitem.equip[eqcount].card[0] = p->storage[i].card[0]; + mapitem.equip[eqcount].card[1] = p->storage[i].card[1]; + mapitem.equip[eqcount].card[2] = p->storage[i].card[2]; + mapitem.equip[eqcount].card[3] = p->storage[i].card[3]; + mapitem.equip[eqcount].broken = p->storage[i].broken; + eqcount++; + } + else if(itemdb_isequip(p->storage[i].nameid)==0){ + mapitem.notequip[noteqcount].flag=0; + mapitem.notequip[noteqcount].id = p->storage[i].id; + mapitem.notequip[noteqcount].nameid=p->storage[i].nameid; + mapitem.notequip[noteqcount].amount = p->storage[i].amount; + mapitem.notequip[noteqcount].equip = p->storage[i].equip; + mapitem.notequip[noteqcount].identify = p->storage[i].identify; + mapitem.notequip[noteqcount].refine = p->storage[i].refine; + mapitem.notequip[noteqcount].attribute = p->storage[i].attribute; + mapitem.notequip[noteqcount].card[0] = p->storage[i].card[0]; + mapitem.notequip[noteqcount].card[1] = p->storage[i].card[1]; + mapitem.notequip[noteqcount].card[2] = p->storage[i].card[2]; + mapitem.notequip[noteqcount].card[3] = p->storage[i].card[3]; + mapitem.notequip[noteqcount].broken = p->storage[i].broken; + noteqcount++; + } + } + } + + memitemdata_to_sql(mapitem, eqcount, noteqcount, guild_id,TABLE_GUILD_STORAGE); + + printf ("guild storage save to DB - id: %d (total: %d)\n", guild_id,i); + return 0; +} + +// Load guild_storage data to mem +int guild_storage_fromsql(int guild_id, struct guild_storage *p){ + int i=0; + struct guild_storage *gs=guild_storage_pt; + p=gs; + + memset(p,0,sizeof(struct guild_storage)); //clean up memory + p->storage_amount = 0; + p->guild_id = guild_id; + + // storage {`guild_id`/`id`/`nameid`/`amount`/`equip`/`identify`/`refine`/`attribute`/`card0`/`card1`/`card2`/`card3`} + sprintf(tmp_sql,"SELECT `id`,`nameid`,`amount`,`equip`,`identify`,`refine`,`attribute`,`card0`,`card1`,`card2`,`card3`,`broken` FROM `%s` WHERE `guild_id`='%d'",guild_storage_db, guild_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle) ); + } + sql_res = mysql_store_result(&mysql_handle) ; + + if (sql_res) { + while((sql_row = mysql_fetch_row(sql_res))) { //start to fetch + p->storage[i].id= atoi(sql_row[0]); + p->storage[i].nameid= atoi(sql_row[1]); + p->storage[i].amount= atoi(sql_row[2]); + p->storage[i].equip= atoi(sql_row[3]); + p->storage[i].identify= atoi(sql_row[4]); + p->storage[i].refine= atoi(sql_row[5]); + p->storage[i].attribute= atoi(sql_row[6]); + p->storage[i].card[0]= atoi(sql_row[7]); + p->storage[i].card[1]= atoi(sql_row[8]); + p->storage[i].card[2]= atoi(sql_row[9]); + p->storage[i].card[3]= atoi(sql_row[10]); + p->storage[i].broken = atoi(sql_row[11]); + p->storage_amount = ++i; + } + mysql_free_result(sql_res); + } + printf ("guild storage load complete from DB - id: %d (total: %d)\n", guild_id, p->storage_amount); + return 0; +} + +//--------------------------------------------------------- +// storage data initialize +int inter_storage_sql_init(){ + + //memory alloc + printf("interserver storage memory initialize....(%d byte)\n",sizeof(struct storage)); + storage_pt=calloc(sizeof(struct storage), 1); + guild_storage_pt=calloc(sizeof(struct guild_storage), 1); + memset(storage_pt,0,sizeof(struct storage)); + memset(guild_storage_pt,0,sizeof(struct guild_storage)); + + return 1; +} +// 倉庫データ削除 +int inter_storage_delete(int account_id) +{ + sprintf(tmp_sql, "DELETE FROM `%s` WHERE `account_id`='%d'",storage_db, account_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (delete `storage`)- %s\n", mysql_error(&mysql_handle) ); + } + return 0; +} +int inter_guild_storage_delete(int guild_id) +{ + sprintf(tmp_sql, "DELETE FROM `%s` WHERE `guild_id`='%d'",guild_storage_db, guild_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (delete `guild_storage`)- %s\n", mysql_error(&mysql_handle) ); + } + return 0; +} + +//--------------------------------------------------------- +// packet from map server + +// recive packet about storage data +int mapif_load_storage(int fd,int account_id){ + //load from DB + storage_fromsql(account_id, storage_pt); + WFIFOW(fd,0)=0x3810; + WFIFOW(fd,2)=sizeof(struct storage)+8; + WFIFOL(fd,4)=account_id; + memcpy(WFIFOP(fd,8),storage_pt,sizeof(struct storage)); + WFIFOSET(fd,WFIFOW(fd,2)); + return 0; +} +// send ack to map server which is "storage data save ok." +int mapif_save_storage_ack(int fd,int account_id){ + WFIFOW(fd,0)=0x3811; + WFIFOL(fd,2)=account_id; + WFIFOB(fd,6)=0; + WFIFOSET(fd,7); + return 0; +} + +int mapif_load_guild_storage(int fd,int account_id,int guild_id) +{ + int guild_exist=0; + WFIFOW(fd,0)=0x3818; + + // Check if guild exists, I may write a function for this later, coz I use it several times. + //printf("- Check if guild %d exists\n",g->guild_id); + sprintf(tmp_sql, "SELECT count(*) FROM `%s` WHERE `guild_id`='%d'",guild_db, guild_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (delete `guild`)- %s\n", mysql_error(&mysql_handle) ); + } + sql_res = mysql_store_result(&mysql_handle) ; + if (sql_res!=NULL && mysql_num_rows(sql_res)>0) { + sql_row = mysql_fetch_row(sql_res); + guild_exist = atoi (sql_row[0]); + //printf("- Check if guild %d exists : %s\n",g->guild_id,((guild_exist==0)?"No":"Yes")); + } + mysql_free_result(sql_res) ; //resource free + + if(guild_exist==1) { + guild_storage_fromsql(guild_id,guild_storage_pt); + WFIFOW(fd,2)=sizeof(struct guild_storage)+12; + WFIFOL(fd,4)=account_id; + WFIFOL(fd,8)=guild_id; + memcpy(WFIFOP(fd,12),guild_storage_pt,sizeof(struct guild_storage)); + } + else { + WFIFOW(fd,2)=12; + WFIFOL(fd,4)=account_id; + WFIFOL(fd,8)=0; + } + WFIFOSET(fd,WFIFOW(fd,2)); + + return 0; +} +int mapif_save_guild_storage_ack(int fd,int account_id,int guild_id,int fail) +{ + WFIFOW(fd,0)=0x3819; + WFIFOL(fd,2)=account_id; + WFIFOL(fd,6)=guild_id; + WFIFOB(fd,10)=fail; + WFIFOSET(fd,11); + return 0; +} + +//--------------------------------------------------------- +// packet from map server + +// recive request about storage data +int mapif_parse_LoadStorage(int fd){ + mapif_load_storage(fd,RFIFOL(fd,2)); + return 0; +} +// storage data recive and save +int mapif_parse_SaveStorage(int fd){ + int account_id=RFIFOL(fd,4); + int len=RFIFOW(fd,2); + + if(sizeof(struct storage)!=len-8){ + printf("inter storage: data size error %d %d\n",sizeof(struct storage),len-8); + }else{ + memcpy(&storage_pt[0],RFIFOP(fd,8),sizeof(struct storage)); + storage_tosql(account_id, storage_pt); + mapif_save_storage_ack(fd,account_id); + } + return 0; +} + +int mapif_parse_LoadGuildStorage(int fd) +{ + mapif_load_guild_storage(fd,RFIFOL(fd,2),RFIFOL(fd,6)); + return 0; +} + +int mapif_parse_SaveGuildStorage(int fd) +{ + int guild_exist=0; + int guild_id=RFIFOL(fd,8); + int len=RFIFOW(fd,2); + if(sizeof(struct guild_storage)!=len-12){ + printf("inter storage: data size error %d %d\n",sizeof(struct guild_storage),len-12); + } + else { + // Check if guild exists, I may write a function for this later, coz I use it several times. + //printf("- Check if guild %d exists\n",g->guild_id); + sprintf(tmp_sql, "SELECT count(*) FROM `%s` WHERE `guild_id`='%d'",guild_db, guild_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (delete `guild`)- %s\n", mysql_error(&mysql_handle) ); + } + sql_res = mysql_store_result(&mysql_handle) ; + if (sql_res!=NULL && mysql_num_rows(sql_res)>0) { + sql_row = mysql_fetch_row(sql_res); + guild_exist = atoi (sql_row[0]); + //printf("- Check if guild %d exists : %s\n",g->guild_id,((guild_exist==0)?"No":"Yes")); + } + mysql_free_result(sql_res) ; //resource free + + if(guild_exist==1) { + memcpy(guild_storage_pt,RFIFOP(fd,12),sizeof(struct guild_storage)); + guild_storage_tosql(guild_id,guild_storage_pt); + mapif_save_guild_storage_ack(fd,RFIFOL(fd,4),guild_id,0); + } + else + mapif_save_guild_storage_ack(fd,RFIFOL(fd,4),guild_id,1); + } + return 0; +} + + +int inter_storage_parse_frommap(int fd){ + switch(RFIFOW(fd,0)){ + case 0x3010: mapif_parse_LoadStorage(fd); break; + case 0x3011: mapif_parse_SaveStorage(fd); break; + case 0x3018: mapif_parse_LoadGuildStorage(fd); break; + case 0x3019: mapif_parse_SaveGuildStorage(fd); break; + default: + return 0; + } + return 1; +} + diff --git a/src/char_sql/int_storage.h b/src/char_sql/int_storage.h new file mode 100644 index 0000000..f9f37db --- /dev/null +++ b/src/char_sql/int_storage.h @@ -0,0 +1,13 @@ +#ifndef _INT_STORAGE_H_ +#define _INT_STORAGE_H_ + +int inter_storage_sql_init(); +int inter_storage_delete(int account_id); +int inter_guild_storage_delete(int guild_id); + +int inter_storage_parse_frommap(int fd); + + +//extern char storage_txt[256]; + +#endif diff --git a/src/char_sql/inter.c b/src/char_sql/inter.c new file mode 100644 index 0000000..c8fa9b4 --- /dev/null +++ b/src/char_sql/inter.c @@ -0,0 +1,573 @@ +// +// original code from athena +// SQL conversion by Jioh L. Jung +// + +#include <string.h> +#include <stdlib.h> + +#include "char.h" +#include "strlib.h" +#include "inter.h" +#include "int_party.h" +#include "int_guild.h" +#include "int_storage.h" +#include "int_pet.h" +#include "lock.h" + +#define WISDATA_TTL (60*1000) // Wisデータの生存時間(60秒) +#define WISDELLIST_MAX 256 // Wisデータ削除リストの要素数 + + +struct accreg { + int account_id,reg_num; + struct global_reg reg[ACCOUNT_REG_NUM]; +}; + +static struct accreg *accreg_pt; + + +int party_share_level = 10; +MYSQL mysql_handle; +MYSQL_RES* sql_res ; +MYSQL_ROW sql_row ; +int sql_fields, sql_cnt; +char tmp_sql[65535]; + +MYSQL lmysql_handle; +char tmp_lsql[65535]; +MYSQL_RES* lsql_res ; +MYSQL_ROW lsql_row ; + +int char_server_port = 3306; +char char_server_ip[32] = "127.0.0.1"; +char char_server_id[32] = "ragnarok"; +char char_server_pw[32] = "ragnarok"; +char char_server_db[32] = "ragnarok"; + +int login_server_port = 3306; +char login_server_ip[32] = "127.0.0.1"; +char login_server_id[32] = "ragnarok"; +char login_server_pw[32] = "ragnarok"; +char login_server_db[32] = "ragnarok"; + +// sending packet list +int inter_send_packet_length[]={ + -1,-1,27, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + -1, 7, 0, 0, 0, 0, 0, 0, -1,11, 0, 0, 0, 0, 0, 0, + 35,-1,11,15, 34,29, 7,-1, 0, 0, 0, 0, 0, 0, 0, 0, + 10,-1,15, 0, 79,19, 7,-1, 0,-1,-1,-1, 14,67,186,-1, + 9, 9,-1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 11,-1, 7, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +}; +// recv. packet list +int inter_recv_packet_length[]={ + -1,-1, 7, 0, -1, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 6,-1, 0, 0, 0, 0, 0, 0, 10,-1, 0, 0, 0, 0, 0, 0, + 72, 6,52,14, 10,29, 6,-1, 34, 0, 0, 0, 0, 0, 0, 0, + -1, 6,-1, 0, 55,19, 6,-1, 14,-1,-1,-1, 14,19,186,-1, + 5, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 48,14,-1, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +}; + +struct WisData { + int id,fd,count,len; + unsigned long tick; + unsigned char src[24],dst[24],msg[512]; +}; +static struct dbt * wis_db = NULL; +static int wis_dellist[WISDELLIST_MAX], wis_delnum; + +//-------------------------------------------------------- +// Save account_reg to sql (type=2) +int inter_accreg_tosql(int account_id,struct accreg *reg){ + + int j; + char temp_str[32]; + if (account_id<=0) return 0; + reg->account_id=account_id; + + //`global_reg_value` (`type`, `account_id`, `char_id`, `str`, `value`) + sprintf(tmp_sql,"DELETE FROM `%s` WHERE `type`=2 AND `account_id`='%d'",reg_db, account_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (delete `global_reg_value`)- %s\n", mysql_error(&mysql_handle) ); + } + + if (reg->reg_num<=0) return 0; + + for(j=0;j<reg->reg_num;j++){ + if(reg->reg[j].str != NULL){ + sprintf(tmp_sql,"INSERT INTO `%s` (`type`, `account_id`, `str`, `value`) VALUES (2,'%d', '%s','%d')", + reg_db, reg->account_id, jstrescapecpy(temp_str,reg->reg[j].str), reg->reg[j].value); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (insert `global_reg_value`)- %s\n", mysql_error(&mysql_handle) ); + } + } + } + return 0; +} + +// Load account_reg from sql (type=2) +int inter_accreg_fromsql(int account_id,struct accreg *reg) +{ + int j=0; + if (reg==NULL) return 0; + memset(reg, 0, sizeof(struct accreg)); + reg->account_id=account_id; + + //`global_reg_value` (`type`, `account_id`, `char_id`, `str`, `value`) + sprintf (tmp_sql, "SELECT `str`, `value` FROM `%s` WHERE `type`=2 AND `account_id`='%d'",reg_db, reg->account_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (select `global_reg_value`)- %s\n", mysql_error(&mysql_handle) ); + } + sql_res = mysql_store_result(&mysql_handle); + + if (sql_res) { + for(j=0;(sql_row = mysql_fetch_row(sql_res));j++){ + memcpy(reg->reg[j].str, sql_row[0],32); + reg->reg[j].value = atoi(sql_row[1]); + } + mysql_free_result(sql_res); + } + reg->reg_num=j; + return 0; +} + +// Initialize +int inter_accreg_sql_init() +{ + CREATE(accreg_pt, struct accreg, 1); + return 0; + +} + +/*========================================== + * read config file + *------------------------------------------ + */ +int inter_config_read(const char *cfgName) { + printf ("start reading interserver configuration: %s\n",cfgName); + int i; + char line[1024], w1[1024], w2[1024]; + FILE *fp; + + fp=fopen(cfgName,"r"); + if(fp==NULL){ + printf("file not found: %s\n", cfgName); + return 1; + } + while(fgets(line, 1020, fp)){ + i=sscanf(line,"%[^:]: %[^\r\n]",w1,w2); + if(i!=2) + continue; + + if(strcmpi(w1,"char_server_ip")==0){ + strcpy(char_server_ip, w2); + printf ("set char_server_ip : %s\n",w2); + } + else if(strcmpi(w1,"char_server_port")==0){ + char_server_port=atoi(w2); + printf ("set char_server_port : %s\n",w2); + } + else if(strcmpi(w1,"char_server_id")==0){ + strcpy(char_server_id, w2); + printf ("set char_server_id : %s\n",w2); + } + else if(strcmpi(w1,"char_server_pw")==0){ + strcpy(char_server_pw, w2); + printf ("set char_server_pw : %s\n",w2); + } + else if(strcmpi(w1,"char_server_db")==0){ + strcpy(char_server_db, w2); + printf ("set char_server_db : %s\n",w2); + } + //Logins information to be read from the inter_athena.conf + //for character deletion (checks email in the loginDB) + + else if(strcmpi(w1,"login_server_ip")==0){ + strcpy(login_server_ip, w2); + printf ("set login_server_ip : %s\n",w2); + } + else if(strcmpi(w1,"login_server_port")==0){ + login_server_port=atoi(w2); + printf ("set login_server_port : %s\n",w2); + } + else if(strcmpi(w1,"login_server_id")==0){ + strcpy(login_server_id, w2); + printf ("set login_server_id : %s\n",w2); + } + else if(strcmpi(w1,"login_server_pw")==0){ + strcpy(login_server_pw, w2); + printf ("set login_server_pw : %s\n",w2); + } + else if(strcmpi(w1,"login_server_db")==0){ + strcpy(login_server_db, w2); + printf ("set login_server_db : %s\n",w2); + } + else if(strcmpi(w1,"party_share_level")==0){ + party_share_level=atoi(w2); + if(party_share_level < 0) party_share_level = 0; + }else if(strcmpi(w1,"import")==0){ + inter_config_read(w2); + } + else if(strcmpi(w1,"login_server_db")==0){ + strcpy(login_server_db, w2); + printf ("set login_server_db : %s\n",w2); + } + } + fclose(fp); + + printf ("success reading interserver configuration\n"); + + return 0; +} + +// Save interlog into sql +int inter_log(char *fmt,...) +{ + char str[255]; + char temp_str[255]; + va_list ap; + va_start(ap,fmt); + + vsprintf(str,fmt,ap); + sprintf(tmp_sql,"INSERT INTO `%s` (`time`, `log`) VALUES (NOW(), '%s')",interlog_db, jstrescapecpy(temp_str,str)); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (insert `interlog`)- %s\n", mysql_error(&mysql_handle) ); + } + + va_end(ap); + return 0; +} + + +// initialize +int inter_init(const char *file) +{ + //int i; + + printf ("interserver initialize...\n"); + inter_config_read(file); + + //DB connection initialized + mysql_init(&mysql_handle); + printf("Connect Character DB server.... (Character Server)\n"); + if(!mysql_real_connect(&mysql_handle, char_server_ip, char_server_id, char_server_pw, + char_server_db ,char_server_port, (char *)NULL, 0)) { + //pointer check + printf("%s\n",mysql_error(&mysql_handle)); + exit(1); + } + else { + printf ("Connect Success! (Character Server)\n"); + } + + mysql_init(&lmysql_handle); + printf("Connect Character DB server.... (login server)\n"); + if(!mysql_real_connect(&lmysql_handle, login_server_ip, login_server_id, login_server_pw, + login_server_db ,login_server_port, (char *)NULL, 0)) { + //pointer check + printf("%s\n",mysql_error(&lmysql_handle)); + exit(1); + }else { + printf ("Connect Success! (Login Server)"); + } + wis_db = numdb_init(); + inter_guild_sql_init(); + inter_storage_sql_init(); + inter_party_sql_init(); + + inter_pet_sql_init(); + inter_accreg_sql_init(); + + //printf ("interserver timer initializing : %d sec...\n",autosave_interval); + //i=add_timer_interval(gettick()+autosave_interval,inter_save_timer,0,0,autosave_interval); + + return 0; +} + +int inter_mapif_init(int fd) { + inter_guild_mapif_init(fd); + + return 0; +} + + +//-------------------------------------------------------- + +// GM message sending +int mapif_GMmessage(unsigned char *mes, int len) { + unsigned char buf[len]; + + WBUFW(buf, 0) = 0x3800; + WBUFW(buf, 2) = len; + memcpy(WBUFP(buf, 4), mes, len-4); + mapif_sendall(buf, len); + printf("\033[1;34m inter server: GM[len:%d] - '%s' \033[0m\n", len, mes); + return 0; +} + +// Wis sending +int mapif_wis_message(struct WisData *wd) { + unsigned char buf[56 + wd->len]; + + WBUFW(buf, 0) = 0x3801; + WBUFW(buf, 2) = 56 +wd->len; + WBUFL(buf, 4) = wd->id; + memcpy(WBUFP(buf, 8), wd->src, 24); + memcpy(WBUFP(buf,32), wd->dst, 24); + memcpy(WBUFP(buf,56), wd->msg, wd->len); + wd->count = mapif_sendall(buf,WBUFW(buf,2)); + + return 0; +} +// Wis sending result +int mapif_wis_end(struct WisData *wd,int flag) +{ + unsigned char buf[27]; + + WBUFW(buf, 0)=0x3802; + memcpy(WBUFP(buf, 2),wd->src,24); + WBUFB(buf,26)=flag; + mapif_send(wd->fd,buf,27); +// printf("inter server wis_end %d\n",flag); + return 0; +} + +int mapif_account_reg(int fd,unsigned char *src) +{ + unsigned char buf[WBUFW(src,2)]; + memcpy(WBUFP(buf,0),src,WBUFW(src,2)); + WBUFW(buf, 0)=0x3804; + mapif_sendallwos(fd,buf,WBUFW(buf,2)); + return 0; +} + +// Send the requested account_reg +int mapif_account_reg_reply(int fd,int account_id) +{ + struct accreg *reg=accreg_pt; + inter_accreg_fromsql(account_id,reg); + + WFIFOW(fd,0)=0x3804; + WFIFOL(fd,4)=account_id; + if(reg->reg_num==0){ + WFIFOW(fd,2)=8; + }else{ + int j,p; + for(j=0,p=8;j<reg->reg_num;j++,p+=36){ + memcpy(WFIFOP(fd,p),reg->reg[j].str,32); + WFIFOL(fd,p+32)=reg->reg[j].value; + } + WFIFOW(fd,2)=p; + } + WFIFOSET(fd,WFIFOW(fd,2)); + return 0; +} + +//-------------------------------------------------------- + +// Existence check of WISP data +int check_ttl_wisdata_sub(void *key, void *data, va_list ap) { + unsigned long tick; + struct WisData *wd = (struct WisData *)data; + tick = va_arg(ap, unsigned long); + + if (DIFF_TICK(tick, wd->tick) > WISDATA_TTL && wis_delnum < WISDELLIST_MAX) + wis_dellist[wis_delnum++] = wd->id; + + return 0; +} + +int check_ttl_wisdata() { + unsigned long tick = gettick(); + int i; + + do { + wis_delnum = 0; + numdb_foreach(wis_db, check_ttl_wisdata_sub, tick); + for(i = 0; i < wis_delnum; i++) { + struct WisData *wd = numdb_search(wis_db, wis_dellist[i]); + printf("inter: wis data id=%d time out : from %s to %s\n", wd->id, wd->src, wd->dst); + // removed. not send information after a timeout. Just no answer for the player + //mapif_wis_end(wd, 1); // flag: 0: success to send wisper, 1: target character is not loged in?, 2: ignored by target + numdb_erase(wis_db, wd->id); + free(wd); + } + } while(wis_delnum >= WISDELLIST_MAX); + + return 0; +} + +//-------------------------------------------------------- + +// GM message sending +int mapif_parse_GMmessage(int fd) +{ + mapif_GMmessage(RFIFOP(fd, 4), RFIFOW(fd, 2)); + return 0; +} + + +// Wisp/page request to send +int mapif_parse_WisRequest(int fd) { + struct WisData* wd; + static int wisid = 0; + + if (RFIFOW(fd,2)-52 >= sizeof(wd->msg)) { + printf("inter: Wis message size too long.\n"); + return 0; + } else if (RFIFOW(fd,2)-52 <= 0) { // normaly, impossible, but who knows... + printf("inter: Wis message doesn't exist.\n"); + return 0; + } + sprintf (tmp_sql, "SELECT `name` FROM `%s` WHERE `char_id`='%d'",char_db, (int) RFIFOP(fd,28)); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle) ); + } + sql_res = mysql_store_result(&mysql_handle); + + // search if character exists before to ask all map-servers + if (!(sql_row = mysql_fetch_row(sql_res))) { + unsigned char buf[27]; + WBUFW(buf, 0) = 0x3802; + memcpy(WBUFP(buf, 2), RFIFOP(fd, 4), 24); + WBUFB(buf,26) = 1; // flag: 0: success to send wisper, 1: target character is not loged in?, 2: ignored by target + mapif_send(fd, buf, 27); + // Character exists. So, ask all map-servers + } else { + // to be sure of the correct name, rewrite it + memset(RFIFOP(fd,28), 0, 24); + strncpy(RFIFOP(fd,28), sql_row[0], 24); + // if source is destination, don't ask other servers. + if (strcmp(RFIFOP(fd,4),RFIFOP(fd,28)) == 0) { + unsigned char buf[27]; + WBUFW(buf, 0) = 0x3802; + memcpy(WBUFP(buf, 2), RFIFOP(fd, 4), 24); + WBUFB(buf,26) = 1; // flag: 0: success to send wisper, 1: target character is not loged in?, 2: ignored by target + mapif_send(fd, buf, 27); + } else { + + CREATE(wd, struct WisData, 1); + + // Whether the failure of previous wisp/page transmission (timeout) + check_ttl_wisdata(); + + wd->id = ++wisid; + wd->fd = fd; + wd->len= RFIFOW(fd,2)-52; + memcpy(wd->src, RFIFOP(fd, 4), 24); + memcpy(wd->dst, RFIFOP(fd,28), 24); + memcpy(wd->msg, RFIFOP(fd,52), wd->len); + wd->tick = gettick(); + numdb_insert(wis_db, wd->id, wd); + mapif_wis_message(wd); + } + } + + return 0; +} + + +// Wisp/page transmission result +int mapif_parse_WisReply(int fd) { + int id = RFIFOL(fd,2), flag = RFIFOB(fd,6); + struct WisData *wd = numdb_search(wis_db, id); + + if (wd == NULL) + return 0; // This wisp was probably suppress before, because it was timeout of because of target was found on another map-server + + if ((--wd->count) <= 0 || flag != 1) { + mapif_wis_end(wd, flag); // flag: 0: success to send wisper, 1: target character is not loged in?, 2: ignored by target + numdb_erase(wis_db, id); + free(wd); + } + + return 0; +} + + +// Save account_reg into sql (type=2) +int mapif_parse_AccReg(int fd) +{ + int j,p; + struct accreg *reg=accreg_pt; + int account_id = RFIFOL(fd,4); + memset(accreg_pt,0,sizeof(struct accreg)); + + for(j=0,p=8;j<ACCOUNT_REG_NUM && p<RFIFOW(fd,2);j++,p+=36){ + memcpy(reg->reg[j].str,RFIFOP(fd,p),32); + reg->reg[j].value=RFIFOL(fd,p+32); + } + reg->reg_num=j; + + inter_accreg_tosql(account_id,reg); + mapif_account_reg(fd,RFIFOP(fd,0)); // Send confirm message to map + return 0; +} + +// Request the value of account_reg +int mapif_parse_AccRegRequest(int fd) +{ +// printf("mapif: accreg request\n"); + return mapif_account_reg_reply(fd,RFIFOL(fd,2)); +} + + + +//-------------------------------------------------------- +int inter_parse_frommap(int fd) +{ + int cmd=RFIFOW(fd,0); + int len=0; + + // inter鯖管轄かを調べる + if(cmd<0x3000 || cmd>=0x3000+( sizeof(inter_recv_packet_length)/ + sizeof(inter_recv_packet_length[0]) ) ) + return 0; + + // パケット長を調べる + if( (len=inter_check_length(fd,inter_recv_packet_length[cmd-0x3000]))==0 ) + return 2; + + switch(cmd){ + case 0x3000: mapif_parse_GMmessage(fd); break; + case 0x3001: mapif_parse_WisRequest(fd); break; + case 0x3002: mapif_parse_WisReply(fd); break; + case 0x3004: mapif_parse_AccReg(fd); break; + case 0x3005: mapif_parse_AccRegRequest(fd); break; + default: + if( inter_party_parse_frommap(fd) ) + break; + if( inter_guild_parse_frommap(fd) ) + break; + if( inter_storage_parse_frommap(fd) ) + break; + if( inter_pet_parse_frommap(fd) ) + break; + return 0; + } + RFIFOSKIP(fd, len ); + return 1; +} + +// RFIFO check +int inter_check_length(int fd, int length) +{ + if(length==-1){ // v-len packet + if(RFIFOREST(fd)<4) // packet not yet + return 0; + length = RFIFOW(fd, 2); + } + + if(RFIFOREST(fd)<length) // packet not yet + return 0; + + return length; +} diff --git a/src/char_sql/inter.h b/src/char_sql/inter.h new file mode 100644 index 0000000..841d534 --- /dev/null +++ b/src/char_sql/inter.h @@ -0,0 +1,44 @@ +#ifndef _INTER_H_ +#define _INTER_H_ + +int inter_init(const char *file); +int inter_parse_frommap(int fd); +int inter_mapif_init(int fd); + + +int inter_check_length(int fd,int length); + +int inter_log(char *fmt,...); + +#define inter_cfgName "conf/inter_athena.conf" + +extern int party_share_level; +extern char inter_log_filename[1024]; + +//add include for DBMS(mysql) +#include <mysql.h> + +extern MYSQL mysql_handle; +extern char tmp_sql[65535]; +extern MYSQL_RES* sql_res ; +extern MYSQL_ROW sql_row ; +extern int sql_cnt; + +extern MYSQL lmysql_handle; +extern char tmp_lsql[65535]; +extern MYSQL_RES* lsql_res ; +extern MYSQL_ROW lsql_row ; + +extern int char_server_port; +extern char char_server_ip[32]; +extern char char_server_id[32]; +extern char char_server_pw[32]; +extern char char_server_db[32]; + +extern int login_db_server_port; +extern char login_db_server_ip[32]; +extern char login_db_server_id[32]; +extern char login_db_server_pw[32]; +extern char login_db_server_db[32]; + +#endif diff --git a/src/char_sql/itemdb.c b/src/char_sql/itemdb.c new file mode 100644 index 0000000..0bed07c --- /dev/null +++ b/src/char_sql/itemdb.c @@ -0,0 +1,247 @@ +// $Id: itemdb.c,v 1.1.1.1 2004/09/10 17:44:48 MagicalTux Exp $ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "itemdb.h" +#include "db.h" +#include "inter.h" +#include "char.h" +#include "utils.h" + +#ifdef MEMWATCH +#include "memwatch.h" +#endif + +#define MAX_RANDITEM 2000 + +// ** ITEMDB_OVERRIDE_NAME_VERBOSE ** +// 定義すると、itemdb.txtとgrfで名前が異なる場合、表示します. +//#define ITEMDB_OVERRIDE_NAME_VERBOSE 1 + +char item_db_db[256]="item_db"; // added to specify item_db sql table [Valaris] + +static struct dbt* item_db; + +/*========================================== + * DBの検索 + *------------------------------------------ + */ +struct item_data* itemdb_search(int nameid) +{ + struct item_data *id; + + id=numdb_search(item_db,nameid); + if(id) return id; + + CREATE(id, struct item_data, 1); + + numdb_insert(item_db,nameid,id); + + + if(nameid>500 && nameid<600) + id->type=0; //heal item + else if(nameid>600 && nameid<700) + id->type=2; //use item + else if((nameid>700 && nameid<1100) || + (nameid>7000 && nameid<8000)) + id->type=3; //correction + else if(nameid>=1750 && nameid<1771) + id->type=10; //arrow + else if(nameid>1100 && nameid<2000) + id->type=4; //weapon + else if((nameid>2100 && nameid<3000) || + (nameid>5000 && nameid<6000)) + id->type=5; //armor + else if(nameid>4000 && nameid<5000) + id->type=6; //card + else if(nameid>9000 && nameid<10000) + id->type=7; //egg + else if(nameid>10000) + id->type=8; //petequip + + return id; +} + +/*========================================== + * + *------------------------------------------ + */ +int itemdb_isequip(int nameid) +{ + int type=itemdb_type(nameid); + if(type==0 || type==2 || type==3 || type==6 || type==10) + return 0; + return 1; +} +/*========================================== + * + *------------------------------------------ + */ +int itemdb_isequip2(struct item_data *data) +{ + if(data) { + int type=data->type; + if(type==0 || type==2 || type==3 || type==6 || type==10) + return 0; + else + return 1; + } + return 0; +} + + + +/*========================================== + * アイテムデータベースの読み込み + *------------------------------------------ + */ +static int itemdb_readdb(void) +{ + FILE *fp; + char line[1024]; + int ln=0; + int nameid,j; + char *str[32],*p,*np; + struct item_data *id; + + fp=fopen("db/item_db.txt","r"); + if(fp==NULL){ + printf("can't read db/item_db.txt\n"); + exit(1); + } + while(fgets(line,1020,fp)){ + if(line[0]=='/' && line[1]=='/') + continue; + memset(str,0,sizeof(str)); + for(j=0,np=p=line;j<17 && p;j++){ + str[j]=p; + p=strchr(p,','); + if(p){ *p++=0; np=p; } + } + if(str[0]==NULL) + continue; + + nameid=atoi(str[0]); + if(nameid<=0 || nameid>=20000) + continue; + ln++; + + //ID,Name,Jname,Type,Price,Sell,Weight,ATK,DEF,Range,Slot,Job,Gender,Loc,wLV,eLV,View + id=itemdb_search(nameid); + memcpy(id->name,str[1],24); + memcpy(id->jname,str[2],24); + id->type=atoi(str[3]); + + } + fclose(fp); + printf("read db/item_db.txt done (count=%d)\n",ln); + return 0; +} + +static int itemdb_read_sqldb(void) // sql item_db read, shortened version of map-server item_db read [Valaris] +{ + unsigned int nameid; // Type should be "unsigned short int", but currently isn't for compatibility with numdb_insert() + struct item_data *id; + + // ---------- + + // Output query to retrieve all rows from the item database table + sprintf(tmp_sql, "SELECT * FROM `%s`", item_db_db); + + // Execute the query; if the query execution fails, output an error + if (mysql_query(&mysql_handle, tmp_sql)) { + printf("Database server error (executing query for %s): %s\n", item_db_db, mysql_error(&mysql_handle)); + } + + // Store the query result + sql_res = mysql_store_result(&mysql_handle); + + // If the storage of the query result succeeded + if (sql_res) { + // Parse each row in the query result into sql_row + while ((sql_row = mysql_fetch_row(sql_res))) { + nameid = atoi(sql_row[0]); + + // If the identifier is not within the valid range, process the next row + if (nameid == 0 || nameid >= 20000) { // Should ">= 20000" be "> 20000"? + continue; + } + + // ---------- + + // Insert a new row into the item database +/* + id = calloc(sizeof(struct item_data), 1); + + if (id == NULL) { + printf("out of memory : itemdb_read_sqldb\n"); + exit(1); + } + + memset(id, 0, sizeof(struct item_data)); + numdb_insert(item_db, nameid, id); + + // ---------- +*/ + id=itemdb_search(nameid); + + memcpy(id->name, sql_row[1], 24); + memcpy(id->jname, sql_row[2], 24); + + id->type = atoi(sql_row[3]); + } + + // If the retrieval failed, output an error + if (mysql_errno(&mysql_handle)) { + printf("Database server error (retrieving rows from %s): %s\n", item_db_db, mysql_error(&mysql_handle)); + } + + printf("read %s done (count = %lu)\n", item_db_db, (unsigned long) mysql_num_rows(sql_res)); + + // Free the query result + mysql_free_result(sql_res); + } else { + printf("MySQL error (storing query result for %s): %s\n", item_db_db, mysql_error(&mysql_handle)); + } + + return 0; +} + +static int itemdb_final(void *key,void *data,va_list ap) +{ + struct item_data *id; + + id=data; + if(id->use_script) + free(id->use_script); + if(id->equip_script) + free(id->equip_script); + free(id); + + return 0; +} + + +/*========================================== + * + *------------------------------------------ + */ +void do_final_itemdb(void) +{ + if(item_db){ + numdb_final(item_db,itemdb_final); + item_db=NULL; + } +} +int do_init_itemdb(void) +{ + item_db = numdb_init(); + + if (db_use_sqldbs) // it db_use_sqldbs in inter config are yes, will read from item_db for char server display [Valaris] + itemdb_read_sqldb(); + else + itemdb_readdb(); + return 0; +} + diff --git a/src/char_sql/itemdb.h b/src/char_sql/itemdb.h new file mode 100644 index 0000000..dea835e --- /dev/null +++ b/src/char_sql/itemdb.h @@ -0,0 +1,34 @@ +#ifndef _ITEMDB_H_ +#define _ITEMDB_H_ + +struct item_data { + int nameid; + char name[24],jname[24]; + int value_buy,value_sell,value_notdc,value_notoc; + int type; + int class; + int sex; + int equip; + int weight; + int atk; + int def; + int range; + int slot; + int look; + int elv; + int wlv; + char *use_script; // 回復とかも全部この中でやろうかなと + char *equip_script; // 攻撃,防御の属性設定もこの中で可能かな? + char available; +}; + +struct item_data* itemdb_search(int nameid); +#define itemdb_type(n) itemdb_search(n)->type + +int itemdb_isequip(int); +int itemdb_isequip2(struct item_data *); + +void do_final_itemdb(void); +int do_init_itemdb(void); + +#endif diff --git a/src/char_sql/make.sh b/src/char_sql/make.sh new file mode 100644 index 0000000..a4ca8b5 --- /dev/null +++ b/src/char_sql/make.sh @@ -0,0 +1,11 @@ +#!/bin/sh + rsqlt=`rm -rf *.o` + gcc -c char.c -I/usr/local/include/mysql/ + gcc -c int_guild.c -I/usr/local/include/mysql/ + gcc -c int_party.c -I/usr/local/include/mysql/ + gcc -c int_pet.c -I/usr/local/include/mysql/ + gcc -c int_storage.c -I/usr/local/include/mysql/ + gcc -c inter.c -I/usr/local/include/mysql/ + gcc -c strlib.c + gcc -c itemdb.c -I../common/ + gcc -o ../char-server inter.o char.o int_pet.o int_storage.o int_guild.o int_party.o strlib.o itemdb.o ../common/core.o ../common/socket.o ../common/timer.o ../common/db.o -L/usr/local/lib/mysql -lmysqlclient -lz diff --git a/src/char_sql/readme.txt b/src/char_sql/readme.txt new file mode 100644 index 0000000..41b1144 --- /dev/null +++ b/src/char_sql/readme.txt @@ -0,0 +1,250 @@ +サソ//Encoded with UTF-8 (UNICODE) +//--------------------------------------------- +// V.018 - Aarlex +1. ADD Makefile & GNUmakefile +2. fix guild_leave. +3. fix char select windows HP & SP value error. + +// V.017 - Aarlex +1. fix guild member view job update.(For mod-0728) + inter.c + int_guild.c + +// V.016 - by Aarlex +1. Add e-mail check when you Delete char data. +2. modify storage save func like 014. +2. remove Lan_support func. + +// v.014 - by Aarlex +I rewrite save function. +besause myfriend find that Mysql will use more than 40% CPU. +And log file is too big (4 days 22G ..= =+) +(maybe he sets autosave_time less then 1 min.) +but. i still rewrite save func. +char server will delete all of user item(inventory & Cart) data then insert them again before. +so i use two struct to save item data from map & database. +then compare two struct to get different . +AND add some debug message.but message maybe too much XD. + + +1. ADDED itemdb.c itemdb.h +2. modify mmo_char_tosql(). +3. ADDED memitemdata_to_sql(). +4. ADDED some debug message in memitemdata_to_sql(). +5. modify make.sh + +// v.013 - by Aarlex +1. some SQL sentance fix in old version Mysql . + +2. in_guild.c mapif_guild_leaved() + unsigned char buf[64] -> unsigned char buf[128] + +3. in_pet.c inter_pet_tosql() + if (sql_res) - > if (mysql_num_rows(sql_res)!=0) + +4. in_char.c mmo_char_send006b() + + WFIFOW(fd, offset+(i*106)+42) = char_dat[0].hp -> WFIFOW(fd,offset+(i*106)+42) = (char_dat[j].hp > 0x7fff)? 0x7fff:char_dat[j].hp; + WFIFOW(fd, offset+(i*106)+44) = char_dat[0].max_hp -> WFIFOW(fd,offset+(i*106)+44) = (char_dat[j].max_hp > 0x7fff)? 0x7fff:char_dat[j].max_hp; + WFIFOW(fd, offset+(i*106)+46) = char_dat[0].sp -> WFIFOW(fd,offset+(i*106)+46) = (char_dat[j].sp > 0x7fff)? 0x7fff:char_dat[j].sp + WFIFOW(fd, offset+(i*106)+48) = char_dat[0].max_sp -> WFIFOW(fd,offset+(i*106)+48) = (char_dat[j].max_sp > 0x7fff)? 0x7fff:char_dat[j].max_sp; + + in_char.c parse_char() + + WFIFOW(fd, offset+(i*106)+42) = char_dat[0].hp -> WFIFOW(fd,offset+(i*106)+42) = (char_dat[j].hp > 0x7fff)? 0x7fff:char_dat[j].hp; + WFIFOW(fd, offset+(i*106)+44) = char_dat[0].max_hp -> WFIFOW(fd,offset+(i*106)+44) = (char_dat[j].max_hp > 0x7fff)? 0x7fff:char_dat[j].max_hp; + WFIFOW(fd, offset+(i*106)+46) = char_dat[0].sp -> WFIFOW(fd,offset+(i*106)+46) = (char_dat[j].sp > 0x7fff)? 0x7fff:char_dat[j].sp + WFIFOW(fd, offset+(i*106)+48) = char_dat[0].max_sp -> WFIFOW(fd,offset+(i*106)+48) = (char_dat[j].max_sp > 0x7fff)? 0x7fff:char_dat[j].max_sp; + +// v.012 - by Jazz +1. 0627 official version縺ォ蜷医o縺帙※菫ョ豁」縺励∪縺励◆. +2. no more binary files are supplied. + +//--------------------------------------------- +// v.011 - by Mark +1. Fixed a couple bugs which would cause segfaults under linux :) + +//--------------------------------------------- +// v.010 - by Jazz +1. added some debug info - for reporting. + +//--------------------------------------------- +// v.009 - by Jazz +1. code added for debug. +2. some SQL sentance fix. + +//--------------------------------------------- +// v.009 - by Jazz +1. fix crash bug. - pet db. + +//--------------------------------------------- +// v.008 - by Jazz +1. DB table fix. - char.fix-from.007.to.008.sql + +譌「蟄倥ョ table 讒矩縺ォ縺ッ遏「縺瑚」逹隗」髯、縺輔l繧句エ蜷医′縺ゅj縺セ縺. + +item.equip縺ッ 'unsigned short' 蠖「蠑上〒縺. +縺薙ョ縺溘a縺ォ SQL table繧剃ソョ豁」縺励↑縺代l縺ー縺ェ繧翫∪縺帙s. + +菫ョ豁」繧ウ繝シ繝峨ッ char.fix-from.007.to.008.sql 縺ァ縺. +MySQL縺ァ荳蠎ヲ陦後▲縺ヲ縺上l繧後ー驕ゥ逕ィ縺輔l縺セ縺. 譌「蟄倥ョ繝繝シ繧ソ縺ッ螳牙ィ縺ァ縺. + +//--------------------------------------------- +// v.007 - by Jazz +1. domain 隗」驥医↓蟇セ縺吶k蝠城。後r菫ョ豁」縺励∪縺励◆. + +//--------------------------------------------- +// v.006 - by Jazz +1. crash bug fix. - when your pet DB is empty + +//--------------------------------------------- +// v.005 - by Jazz +1. 0590 official version縺ォ蜷医o縺帙※菫ョ豁」縺励∪縺励◆. + +//--------------------------------------------- +// v.004 - by Jazz +1. 0586 official version縺ォ蜷医o縺帙※菫ョ豁」縺励∪縺励◆. + +//--------------------------------------------- +// v.003 - by Jazz +1. official縺ョ guild.c 縺ィ party.c 繝輔ぃ繧、繝ォ縺ァ縺セ縺溷堺ソョ豁」縺励∪縺励◆. + +//--------------------------------------------- +// v.002 - by Jazz +1. aphostropy 蝠城。後r隗」豎コ縺励∪縺励◆. 縺薙l縺ッ菫晏ョ峨→騾」髢「縺後≠繧句撫鬘後〒縺. +2. SQL縺ョ讒矩繧剃ソョ豁」縺励∪縺励◆. + +//--------------------------------------------- +// v.001 - by Jazz +1. 荳逡ェ逶ョ螳牙ョ version縺ァ縺. alpha version縺ォ蝠城。檎せ繧貞、壽焚菫ョ豁」縺励∪縺励◆. + +//------------------------------------------------------------------------ +//For JAPANESE USER +Athena Char-Server for MySQL. 005 + +荳蠢 guild縺ィ party縺ッ繧医¥蜻シ縺ウ蜃コ縺励&繧後k驛ィ蛻縺ェ縺ョ縺ァ譌「蟄倥ョ athena char-server縺御スソ縺」縺ヲ縺繧 file 蝓コ逶、縺ョ讒矩繧呈戟縺」縺ヲ陦後″縺セ縺. +縺薙l縺ッ, 荳蠎ヲ縺ォ繝。繝「繝ェ繝シ縺ァ逧隱ュ繧薙〒, 繧ゅ≧荳蠎ヲ縺ォ逧 file縺ァ菴ソ縺譁ケ縺碁Κ荳九′蟆代↑縺上°縺九k縺ィ諤昴>縺セ縺. +縺昴@縺ヲ char 繝繝シ繧ソ縺ォ豈斐∋縺ヲ, lost縺ォ縺ェ縺」縺ヲ繧ょ撫鬘後′繧ゅ▲縺ィ蟆代↑縺縺ィ蛻、譁ュ縺励※縺昴≧縺励∪縺励◆. + +MySQL繝舌シ繧ク繝ァ繝ウ縺ョ compile縺ッ MySQL Clients Library縺悟ソ隕√〒縺. windows(cygwin) 繝舌シ繧ク繝ァ繝ウ縺ォ繧ウ繝ウ繝代う繝ォ縺輔l縺 binary繧呈キサ莉倥@縺セ縺励◆. + +險ュ鄂ョ: +縺セ縺 text->DB縺ョ converter縺ッ縺セ縺ィ繧ゅ↓謾ッ謠エ縺励↑縺縺ァ縺. 蜀驛ィ逧縺ォ縺。繧縺」縺ィ繝舌げ縺檎匱隕九&繧後※繝舌げ繧剃ソョ豁」荳ュ縺ァ縺. +縺ァ縺阪k縺縺第掠縺 upload繧偵@縺セ縺. + +1. char.sql繧 MySQL縺ォ dump縺励∪縺. + +2. inter_athena.conf縺ォ谺。繧定ソス蜉縺励∪縺. 縺昴@縺ヲ閾ェ蛻縺ョ DB繧オ繝シ繝舌シ縺ョ諠蝣ア縺ォ蜷医o縺帙※縺上l縺セ縺. +縺薙%縺ァ windows(cygwin)縺ョ蝣エ蜷医↓縺ッ localhost繧剃スソ縺」縺ヲ縺ッ縺縺代↑縺縺ァ縺. ip縺ァ譖ク縺縺ヲ縺上l縺ェ縺代l縺ー縺ェ繧翫∪縺帙s. +localhost縺ァ菴ソ縺蝣エ蜷 UNIX domain socket縺御ス懷虚縺吶k縺九i騾」邨舌′荳榊庄閭ス縺ォ縺ェ繧翫∪縺. + +//3306 is default +db_server_port: 3306 +//DB ip +db_server_ip: 127.0.0.1 +//DB id +db_server_id: ragnarok +//DB pass +db_server_pw: ragnarok +//DB name +db_server_logindb: ragnarok + +3. MySQL 繝舌シ繧ク繝ァ繝ウ縺ッ 2蛟九ョ MySQL connect session繧呈戟縺。縺セ縺. +荳縺、縺ッ繧ュ繝」繝ゥ繧ッ繧ソ繝シ繝繝シ繧ソ繧定ェュ繧薙〒譖ク縺上ョ縺ォ菴ソ繧上l縺ヲ, 莠檎分逶ョ縺ッ inter server縺ョ縺溘a縺ォ騾」邨舌@縺セ縺. + +髢狗匱縺ィ繝繧ケ繝育腸蠅縺ッ P4 2.4a/1024MB/WinXP pro(MediaCenter Edition 2K4 KR)/Cygwin GCC 縺ァ縺. +譛ャ莠コ縺碁沒蝗ス莠コ縺縺九i髻灘嵜隱樣幕逋コ閠縺ョ騾」邨。繧よュ楢ソ弱@縺セ縺. +髻灘嵜隱槭→譌・譛ャ隱槭ョ荳頑焔縺ェ譁ケ縺ッ騾」邨。繧偵¥縺縺輔>. 譌・譛ャ隱槭ョ菴ソ逕ィ縺ォ髮」縺励&繧呈─縺倥※縺縺セ縺. + +迴セ蝨ィ DarkWeiss縺 login server縺ォ MySQL繧呈髪謠エ縺怜ァ九a縺セ縺励◆. +縺励°縺 athena縺ョ縺昴l縺ッ DarkWeiss 繧医j繧ゅ▲縺ィ繧医¥菴懷虚縺吶k縺ィ諤昴>縺セ縺. + +contact : jazz@creper.com + +//------------------------------------------------------------------------ +//For KOREAN USER +Athena Char-Server for MySQL. 005 + +攵卿 guild凰 party株 梵」シ 从カ罹据株 カカ擽攵 クー。エ攪 athena char-serverー ぎ圸葺ウ 梭株 file クーー們攪 オャ。ー・シ ーァウ ー瀧笈共. +擽 イ捩, 復イ溢乱 ゥ罷ェィヲャ。 ェィ草 攷ウ, 共亨 復イ溢乱 ェィ草 file。 堂株 ェス擽 カ葺ー イ アクヲー共ウ 晝ー鮒笈共. +キクヲャウ char 魂擽┣乱 ケ紛, lostー 据鵠攵巡 ャク懋ー 鵠 共ウ 倹卿紛 キクイ 葺慣笈共. + +MySQLイ攪 compile捩 MySQL Clients Libraryー 符囈鮒笈共. windows(cygwin) イ愍。 サエ血攵頗 binary・シ イィカ葺慣笈共. + +└ケ: +符ァ text->DB攪 converter株 罹劇。 ァ寳葺ァ 賦慣笈共. ざカ愍。 平ー イキクー ー懋イャ据牟 イキク・シ 們菩、卓桿笈共. +据巡。 ケィヲャ upload・シ 葺イ慣笈共. + +1. char.sql揆 MySQL乱 dump鮒笈共. + +2. inter_athena.conf乱 共搆揆 カ緋ー 鮒笈共. キクヲャウ 梵侠攪 DB罹イ攪 簿ウエ乱 ァ樌カ肥牟 、鷺笈共. +流クー乱 windows(cygwin)攪 イス垈乱株 localhost・シ 堂ゥエ 譜姓笈共. ip。 牟」シ牟幣 鮒笈共. +localhost。 ぎ圸葺株 イス垈 UNIX domain socket擽 梠徐 葺クー 阜ャク乱 硫イー擽 カ一ー冠紛 ァ瀧笈共. + +//3306 is default +db_server_port: 3306 +//DB ip +db_server_ip: 127.0.0.1 +//DB id +db_server_id: ragnarok +//DB pass +db_server_pw: ragnarok +//DB name +db_server_logindb: ragnarok + +3. MySQL イ捩 2ー懍攪 MySQL connect session揆 ーァ瀧笈共. +葺x株 コ尖ヲュ┣ 魂擽┣・シ 攷ウ 堂株魂 ぎ圸据ゥー, 草イ溢ァク株 inter server・シ 怱紛 硫イー鮒笈共. + +ー罹ー懋ウシ 護侃敢 劍イス捩 P4 2.4a/1024MB/WinXP pro(MediaCenter Edition 2K4 KR)/Cygwin GCC 桿笈共. +ウク攤擽 復オュ攤擽クー 阜ャク乱 復オュ牟 ー罹ー懍梵攪 硫攷巡 劍鮒笈共. +復オュ牟凰 攵ウク牟ー 冠呰復 カ捩 硫攷揆 」シ┷囈. 攵ウク牟攪 ぎ圸乱 牟、它揆 叶⊂ウ 梭慣笈共. + +椪 DarkWeissー login server乱 MySQL揆 ァ寳葺クー 亨梠毎慣笈共. +葺ァァ athena攪 キクイ捩 DarkWeiss ウエ共 鵠 椈 梠徐復共ウ 晝ー鮒笈共. + +contact : jazz@creper.com + +//------------------------------------------------------------------------ +//For ENGLISH USER +Athena Char-Server for MySQL. alpha 005 + += hehe. My English is poor. and I have no time to write. :) + +anyway this version use guild and party data on text file base system. +It accesses many times, so memory dumping is useful for less cpu consume. + +MySQL version need MySQL Clients Library to compile. This include Win32-binary compiled by cygwin-gcc. + +Install: +not yet text->DB converter. I found some bug on it, so It's under debug progress. + +1. dump char.sql to MySQL. + +2. add below on inter_athena.conf. and set your own information. +do not use 'localhost' as domain on cygwin. you must set as ip. +if you use localhost on cygwin, cygwin tries to connect MySQL as UNIX domain socket. +but, MySQL does not support UNIX domain socket on windows. + +//3306 is default +db_server_port: 3306 +//DB ip +db_server_ip: 127.0.0.1 +//DB id +db_server_id: ragnarok +//DB pass +db_server_pw: ragnarok +//DB name +db_server_logindb: ragnarok + +3. MySQL version has 2 MySQL connect session. +one is for char-server and the other one is for inter-server. + +developement enviroment) + P4 2.4a/1024MB/WinXP pro(MediaCenter Edition 2K4 KR)/Cygwin GCC + +I'm korean, so contect if U're Korean developer. +Please contact me, If U can use Korean & Japanese well. + +DarkWeiss starts to support MySQL version of login server, but Athena's one works better, I thnik. + +contact : jazz@creper.com diff --git a/src/char_sql/strlib.c b/src/char_sql/strlib.c new file mode 100644 index 0000000..b113d96 --- /dev/null +++ b/src/char_sql/strlib.c @@ -0,0 +1,79 @@ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "strlib.h" +#include "utils.h" + +//----------------------------------------------- +// string lib. +unsigned char* jstrescape (unsigned char* pt) { + //copy from here + unsigned char * ptr; + int i =0, j=0; + + //copy string to temporary + CREATE(ptr, char, J_MAX_MALLOC_SIZE); + strcpy (ptr,pt); + + while (ptr[i] != '\0') { + switch (ptr[i]) { + case '\'': + pt[j++] = '\\'; + pt[j++] = ptr[i++]; + break; + case '\\': + pt[j++] = '\\'; + pt[j++] = ptr[i++]; + break; + default: + pt[j++] = ptr[i++]; + } + } + pt[j++] = '\0'; + free (ptr); + return (unsigned char*) &pt[0]; +} + +unsigned char* jstrescapecpy (unsigned char* pt,unsigned char* spt) { + //copy from here + int i =0, j=0; + + while (spt[i] != '\0') { + switch (spt[i]) { + case '\'': + pt[j++] = '\\'; + pt[j++] = spt[i++]; + break; + case '\\': + pt[j++] = '\\'; + pt[j++] = spt[i++]; + break; + default: + pt[j++] = spt[i++]; + } + } + pt[j++] = '\0'; + return (unsigned char*) &pt[0]; +} +int jmemescapecpy (unsigned char* pt,unsigned char* spt, int size) { + //copy from here + int i =0, j=0; + + while (i < size) { + switch (spt[i]) { + case '\'': + pt[j++] = '\\'; + pt[j++] = spt[i++]; + break; + case '\\': + pt[j++] = '\\'; + pt[j++] = spt[i++]; + break; + default: + pt[j++] = spt[i++]; + } + } + // copy size is 0 ~ (j-1) + return j; +} diff --git a/src/char_sql/strlib.h b/src/char_sql/strlib.h new file mode 100644 index 0000000..6b61690 --- /dev/null +++ b/src/char_sql/strlib.h @@ -0,0 +1,10 @@ +#ifndef _J_STR_LIB_H_ +#define _J_STR_LIB_H_ +#define J_MAX_MALLOC_SIZE 65535 +// String function library. +// code by Jioh L. Jung (ziozzang@4wish.net) +// This code is under license "BSD" +unsigned char* jstrescape (unsigned char* pt); +unsigned char* jstrescapecpy (unsigned char* pt,unsigned char* spt); +int jmemescapecpy (unsigned char* pt,unsigned char* spt, int size); +#endif diff --git a/src/common/GNUmakefile b/src/common/GNUmakefile new file mode 100644 index 0000000..689ac3b --- /dev/null +++ b/src/common/GNUmakefile @@ -0,0 +1,13 @@ +txt sql all: core.o socket.o timer.o grfio.o db.o lock.o nullpo.o malloc.o
+
+core.o: core.c core.h
+socket.o: socket.c socket.h mmo.h
+timer.o: timer.c timer.h
+grfio.o: grfio.c grfio.h
+db.o: db.c db.h
+lock.o: lock.h
+nullpo.o: nullpo.c nullpo.h
+malloc.o: malloc.c malloc.h
+
+clean:
+ rm -f *.o
diff --git a/src/common/Makefile b/src/common/Makefile new file mode 100644 index 0000000..689ac3b --- /dev/null +++ b/src/common/Makefile @@ -0,0 +1,13 @@ +txt sql all: core.o socket.o timer.o grfio.o db.o lock.o nullpo.o malloc.o
+
+core.o: core.c core.h
+socket.o: socket.c socket.h mmo.h
+timer.o: timer.c timer.h
+grfio.o: grfio.c grfio.h
+db.o: db.c db.h
+lock.o: lock.h
+nullpo.o: nullpo.c nullpo.h
+malloc.o: malloc.c malloc.h
+
+clean:
+ rm -f *.o
diff --git a/src/common/core.c b/src/common/core.c new file mode 100644 index 0000000..62af254 --- /dev/null +++ b/src/common/core.c @@ -0,0 +1,152 @@ +// $Id: core.c,v 1.1.1.1 2004/09/10 17:44:49 MagicalTux Exp $ +// original : core.c 2003/02/26 18:03:12 Rev 1.7 + +#include <stdio.h> +#include <stdlib.h> +#ifndef LCCWIN32 +#include <unistd.h> +#endif +#include <signal.h> + +#include "core.h" +#include "socket.h" +#include "timer.h" +#include "version.h" + +#ifdef MEMWATCH +#include "memwatch.h" +#endif + +static void (*term_func)(void)=NULL; + +/*====================================== + * CORE : Set function + *-------------------------------------- + */ +void set_termfunc(void (*termfunc)(void)) +{ + term_func = termfunc; +} + +/*====================================== + * CORE : Signal Sub Function + *-------------------------------------- + */ + +static void sig_proc(int sn) +{ + int i; + switch(sn){ + case SIGINT: + case SIGTERM: + if(term_func) + term_func(); + for(i=0;i<fd_max;i++){ + if(!session[i]) + continue; + close(i); + } + exit(0); + break; + } +} + +/*====================================== + * CORE : Display title + *-------------------------------------- + */ + +static void display_title(void) +{ + // for help with the console colors look here: + // http://www.edoceo.com/liberum/?doc=printf-with-color + // some code explanation (used here): + // \033[2J : clear screen and go up/left (0, 0 position) + // \033[K : clear line from actual position to end of the line + // \033[0m : reset color parameter + // \033[1m : use bold for font + printf("\033[2J"); // clear screen and go up/left (0, 0 position in text) + printf("\033[37;44m (=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=)\033[K\033[0m\n"); // white writing (37) on blue background (44), \033[K clean until end of file + printf("\033[0;44m (\033[1;33m (c)2004 eAthena Development Team presents \033[0;44m)\033[K\033[0m\n"); // yellow writing (33) + printf("\033[0;44m (\033[1m ______ __ __ \033[0;44m)\033[K\033[0m\n"); // 1: bold char, 0: normal char + printf("\033[0;44m (\033[1m /\\ _ \\/\\ \\__/\\ \\ v%2d.%02d.%02d \033[0;44m)\033[K\033[0m\n", ATHENA_MAJOR_VERSION, ATHENA_MINOR_VERSION, ATHENA_REVISION); // 1: bold char, 0: normal char + printf("\033[0;44m (\033[1m __\\ \\ \\_\\ \\ \\ ,_\\ \\ \\___ __ ___ __ \033[0;44m)\033[K\033[0m\n"); // 1: bold char, 0: normal char + printf("\033[0;44m (\033[1m /'__`\\ \\ __ \\ \\ \\/\\ \\ _ `\\ /'__`\\/' _ `\\ /'__`\\ \033[0;44m)\033[K\033[0m\n"); // 1: bold char, 0: normal char + printf("\033[0;44m (\033[1m /\\ __/\\ \\ \\/\\ \\ \\ \\_\\ \\ \\ \\ \\/\\ __//\\ \\/\\ \\/\\ \\_\\.\\_ \033[0;44m)\033[K\033[0m\n"); // 1: bold char, 0: normal char + printf("\033[0;44m (\033[1m \\ \\____\\\\ \\_\\ \\_\\ \\__\\\\ \\_\\ \\_\\ \\____\\ \\_\\ \\_\\ \\__/.\\_\\ \033[0;44m)\033[K\033[0m\n"); // 1: bold char, 0: normal char + printf("\033[0;44m (\033[1m \\/____/ \\/_/\\/_/\\/__/ \\/_/\\/_/\\/____/\\/_/\\/_/\\/__/\\/_/ \033[0;44m)\033[K\033[0m\n"); // 1: bold char, 0: normal char + printf("\033[0;44m (\033[1m _ _ _ _ _ _ _ _ _ _ _ _ _ \033[0;44m)\033[K\033[0m\n"); // 1: bold char, 0: normal char + printf("\033[0;44m (\033[1m / \\ / \\ / \\ / \\ / \\ / \\ / \\ / \\ / \\ / \\ / \\ / \\ / \\ \033[0;44m)\033[K\033[0m\n"); // 1: bold char, 0: normal char + printf("\033[0;44m (\033[1m ( e | n | g | l | i | s | h ) ( A | t | h | e | n | a ) \033[0;44m)\033[K\033[0m\n"); // 1: bold char, 0: normal char + printf("\033[0;44m (\033[1m \\_/ \\_/ \\_/ \\_/ \\_/ \\_/ \\_/ \\_/ \\_/ \\_/ \\_/ \\_/ \\_/ \033[0;44m)\033[K\033[0m\n"); // 1: bold char, 0: normal char + printf("\033[37;44m (=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=)\033[K\033[0m\n\n"); // reset color +} + +// Added by Gabuzomeu +// +// This is an implementation of signal() using sigaction() for portability. +// (sigaction() is POSIX; signal() is not.) Taken from Stevens' _Advanced +// Programming in the UNIX Environment_. +// +#ifndef SIGPIPE +#define SIGPIPE SIGINT +#endif + +#ifndef POSIX +#define compat_signal(signo, func) signal(signo, func) +#else +sigfunc *compat_signal(int signo, sigfunc *func) +{ + struct sigaction sact, oact; + + sact.sa_handler = func; + sigemptyset(&sact.sa_mask); + sact.sa_flags = 0; +#ifdef SA_INTERRUPT + sact.sa_flags |= SA_INTERRUPT; /* SunOS */ +#endif + + if (sigaction(signo, &sact, &oact) < 0) + return (SIG_ERR); + + return (oact.sa_handler); +} +#endif + + +/*====================================== + * CORE : MAINROUTINE + *-------------------------------------- + */ + +int runflag = 1; + +int main(int argc,char **argv) +{ + int next; + + Net_Init(); + do_socket(); + + compat_signal(SIGPIPE,SIG_IGN); + compat_signal(SIGTERM,sig_proc); + compat_signal(SIGINT,sig_proc); + + // Signal to create coredumps by system when necessary (crash) + compat_signal(SIGSEGV, SIG_DFL); +#ifndef LCCWIN32 + compat_signal(SIGBUS, SIG_DFL); + compat_signal(SIGTRAP, SIG_DFL); +#endif + compat_signal(SIGILL, SIG_DFL); + + display_title(); + + do_init(argc,argv); + while(runflag){ + next=do_timer(gettick_nocache()); + do_sendrecv(next); + do_parsepacket(); + } + return 0; +} diff --git a/src/common/core.h b/src/common/core.h new file mode 100644 index 0000000..bc2be02 --- /dev/null +++ b/src/common/core.h @@ -0,0 +1,12 @@ +// original : core.h 2003/03/14 11:55:25 Rev 1.4 + +#ifndef _CORE_H_ +#define _CORE_H_ + +extern int runflag; + +int do_init(int,char**); + +void set_termfunc(void (*termfunc)(void)); + +#endif // _CORE_H_ diff --git a/src/common/db.c b/src/common/db.c new file mode 100644 index 0000000..a2dc695 --- /dev/null +++ b/src/common/db.c @@ -0,0 +1,500 @@ +// $Id: db.c,v 1.2 2004/09/23 14:43:06 MouseJstr Exp $ +// #define MALLOC_DBN +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include "db.h" +#include "utils.h" + +#ifdef MEMWATCH +#include "memwatch.h" +#endif + +#define ROOT_SIZE 4096 +#ifdef MALLOC_DBN +static struct dbn *dbn_root[512], *dbn_free; +static int dbn_root_rest=0,dbn_root_num=0; + +static void * malloc_dbn(void) +{ + struct dbn* ret; + + if(dbn_free==NULL){ + if(dbn_root_rest<=0){ + CREATE(dbn_root[dbn_root_num], struct dbn, ROOT_SIZE); + + dbn_root_rest=ROOT_SIZE; + dbn_root_num++; + } + return &(dbn_root[dbn_root_num-1][--dbn_root_rest]); + } + ret=dbn_free; + dbn_free = dbn_free->parent; + return ret; +} + +static void free_dbn(struct dbn* add_dbn) +{ + add_dbn->parent = dbn_free; + dbn_free = add_dbn; +} +#endif + +static int strdb_cmp(struct dbt* table,void* a,void* b) +{ + if(table->maxlen) + return strncmp(a,b,table->maxlen); + return strcmp(a,b); +} + +static unsigned int strdb_hash(struct dbt* table,void* a) +{ + int i; + unsigned int h; + unsigned char *p=a; + + i=table->maxlen; + if(i==0) i=0x7fffffff; + for(h=0;*p && --i>=0;){ + h=(h*33 + *p++) ^ (h>>24); + } + return h; +} + +struct dbt* strdb_init(int maxlen) +{ + int i; + struct dbt* table; + + CREATE(table, struct dbt, 1); + + table->cmp=strdb_cmp; + table->hash=strdb_hash; + table->maxlen=maxlen; + for(i=0;i<HASH_SIZE;i++) + table->ht[i]=NULL; + return table; +} + +static int numdb_cmp(struct dbt* table,void* a,void* b) +{ + int ia,ib; + + ia=(int)a; + ib=(int)b; + + if((ia^ib) & 0x80000000) + return ia<0 ? -1 : 1; + + return ia-ib; +} + +static unsigned int numdb_hash(struct dbt* table,void* a) +{ + return (unsigned int)a; +} + +struct dbt* numdb_init(void) +{ + int i; + struct dbt* table; + + CREATE(table, struct dbt, 1); + + table->cmp=numdb_cmp; + table->hash=numdb_hash; + table->maxlen=sizeof(int); + for(i=0;i<HASH_SIZE;i++) + table->ht[i]=NULL; + return table; +} + +void* db_search(struct dbt *table,void* key) +{ + struct dbn *p; + + for(p=table->ht[table->hash(table,key) % HASH_SIZE];p;){ + int c=table->cmp(table,key,p->key); + if(c==0) + return p->data; + if(c<0) + p=p->left; + else + p=p->right; + } + return NULL; +} + +void * db_search2(struct dbt *table, const char *key) +{ + int i,sp; + struct dbn *p,*pn,*stack[64]; + int slen = strlen(key); + + for(i=0;i<HASH_SIZE;i++){ + if((p=table->ht[i])==NULL) + continue; + sp=0; + while(1){ + if (strncasecmp(key, p->key, slen) == 0) + return p->data; + if((pn=p->left)!=NULL){ + if(p->right){ + stack[sp++]=p->right; + } + p=pn; + } else { + if(p->right){ + p=p->right; + } else { + if(sp==0) + break; + p=stack[--sp]; + } + } + } + } + return 0; +} + +static void db_rotate_left(struct dbn *p,struct dbn **root) +{ + struct dbn * y = p->right; + p->right = y->left; + if (y->left !=0) + y->left->parent = p; + y->parent = p->parent; + + if (p == *root) + *root = y; + else if (p == p->parent->left) + p->parent->left = y; + else + p->parent->right = y; + y->left = p; + p->parent = y; +} + +static void db_rotate_right(struct dbn *p,struct dbn **root) +{ + struct dbn * y = p->left; + p->left = y->right; + if (y->right != 0) + y->right->parent = p; + y->parent = p->parent; + + if (p == *root) + *root = y; + else if (p == p->parent->right) + p->parent->right = y; + else + p->parent->left = y; + y->right = p; + p->parent = y; +} + +static void db_rebalance(struct dbn *p,struct dbn **root) +{ + p->color = RED; + while(p!=*root && p->parent->color==RED){ // rootは必ず黒で親は赤いので親の親は必ず存在する + if (p->parent == p->parent->parent->left) { + struct dbn *y = p->parent->parent->right; + if (y && y->color == RED) { + p->parent->color = BLACK; + y->color = BLACK; + p->parent->parent->color = RED; + p = p->parent->parent; + } else { + if (p == p->parent->right) { + p = p->parent; + db_rotate_left(p, root); + } + p->parent->color = BLACK; + p->parent->parent->color = RED; + db_rotate_right(p->parent->parent, root); + } + } else { + struct dbn* y = p->parent->parent->left; + if (y && y->color == RED) { + p->parent->color = BLACK; + y->color = BLACK; + p->parent->parent->color = RED; + p = p->parent->parent; + } else { + if (p == p->parent->left) { + p = p->parent; + db_rotate_right(p, root); + } + p->parent->color = BLACK; + p->parent->parent->color = RED; + db_rotate_left(p->parent->parent, root); + } + } + } + (*root)->color=BLACK; +} + +static void db_rebalance_erase(struct dbn *z,struct dbn **root) +{ + struct dbn *y = z, *x = NULL, *x_parent = NULL; + + if (y->left == NULL) + x = y->right; + else if (y->right == NULL) + x = y->left; + else { + y = y->right; + while (y->left != NULL) + y = y->left; + x = y->right; + } + if (y != z) { // 左右が両方埋まっていた時 yをzの位置に持ってきてzを浮かせる + z->left->parent = y; + y->left = z->left; + if (y != z->right) { + x_parent = y->parent; + if (x) x->parent = y->parent; + y->parent->left = x; + y->right = z->right; + z->right->parent = y; + } else + x_parent = y; + if (*root == z) + *root = y; + else if (z->parent->left == z) + z->parent->left = y; + else + z->parent->right = y; + y->parent = z->parent; + { int tmp=y->color; y->color=z->color; z->color=tmp; } + y = z; + } else { // どちらか空いていた場合 xをzの位置に持ってきてzを浮かせる + x_parent = y->parent; + if (x) x->parent = y->parent; + if (*root == z) + *root = x; + else if (z->parent->left == z) + z->parent->left = x; + else + z->parent->right = x; + } + // ここまで色の移動の除いて通常の2分木と同じ + if (y->color != RED) { // 赤が消える分には影響無し + while (x != *root && (x == NULL || x->color == BLACK)) + if (x == x_parent->left) { + struct dbn* w = x_parent->right; + if (w->color == RED) { + w->color = BLACK; + x_parent->color = RED; + db_rotate_left(x_parent, root); + w = x_parent->right; + } + if ((w->left == NULL || + w->left->color == BLACK) && + (w->right == NULL || + w->right->color == BLACK)) { + w->color = RED; + x = x_parent; + x_parent = x_parent->parent; + } else { + if (w->right == NULL || + w->right->color == BLACK) { + if (w->left) w->left->color = BLACK; + w->color = RED; + db_rotate_right(w, root); + w = x_parent->right; + } + w->color = x_parent->color; + x_parent->color = BLACK; + if (w->right) w->right->color = BLACK; + db_rotate_left(x_parent, root); + break; + } + } else { // same as above, with right <-> left. + struct dbn* w = x_parent->left; + if (w->color == RED) { + w->color = BLACK; + x_parent->color = RED; + db_rotate_right(x_parent, root); + w = x_parent->left; + } + if ((w->right == NULL || + w->right->color == BLACK) && + (w->left == NULL || + w->left->color == BLACK)) { + w->color = RED; + x = x_parent; + x_parent = x_parent->parent; + } else { + if (w->left == NULL || + w->left->color == BLACK) { + if (w->right) w->right->color = BLACK; + w->color = RED; + db_rotate_left(w, root); + w = x_parent->left; + } + w->color = x_parent->color; + x_parent->color = BLACK; + if (w->left) w->left->color = BLACK; + db_rotate_right(x_parent, root); + break; + } + } + if (x) x->color = BLACK; + } +} + +struct dbn* db_insert(struct dbt *table,void* key,void* data) +{ + struct dbn *p,*priv; + int c,hash; + + hash = table->hash(table,key) % HASH_SIZE; + for(c=0,priv=NULL ,p = table->ht[hash];p;){ + c=table->cmp(table,key,p->key); + if(c==0){ // replace + if (table->release) + table->release(p, 3); + p->data=data; + p->key=key; + return p; + } + priv=p; + if(c<0){ + p=p->left; + } else { + p=p->right; + } + } +#ifdef MALLOC_DBN + p=malloc_dbn(); +#else + CREATE(p, struct dbn, 1); +#endif + if(p==NULL){ + printf("out of memory : db_insert\n"); + return NULL; + } + p->parent= NULL; + p->left = NULL; + p->right = NULL; + p->key = key; + p->data = data; + p->color = RED; + if(c==0){ // hash entry is empty + table->ht[hash] = p; + p->color = BLACK; + } else { + if(c<0){ // left node + priv->left = p; + p->parent=priv; + } else { // right node + priv->right = p; + p->parent=priv; + } + if(priv->color==RED){ // must rebalance + db_rebalance(p,&table->ht[hash]); + } + } + return p; +} + +void* db_erase(struct dbt *table,void* key) +{ + void *data; + struct dbn *p; + int c,hash; + + hash = table->hash(table,key) % HASH_SIZE; + for(c=0,p = table->ht[hash];p;){ + c=table->cmp(table,key,p->key); + if(c==0) + break; + if(c<0) + p=p->left; + else + p=p->right; + } + if(!p) + return NULL; + data=p->data; + db_rebalance_erase(p,&table->ht[hash]); +#ifdef MALLOC_DBN + free_dbn(p); +#else + free(p); +#endif + return data; +} + +void db_foreach(struct dbt *table,int (*func)(void*,void*,va_list),...) +{ + int i,sp; + // red-black treeなので64個stackがあれば2^32個ノードまで大丈夫 + struct dbn *p,*pn,*stack[64]; + va_list ap; + + va_start(ap,func); + for(i=0;i<HASH_SIZE;i++){ + if((p=table->ht[i])==NULL) + continue; + sp=0; + while(1){ + func(p->key,p->data,ap); + if((pn=p->left)!=NULL){ + if(p->right){ + stack[sp++]=p->right; + } + p=pn; + } else { + if(p->right){ + p=p->right; + } else { + if(sp==0) + break; + p=stack[--sp]; + } + } + } + } + va_end(ap); +} + +void db_final(struct dbt *table,int (*func)(void*,void*,va_list),...) +{ + int i,sp; + struct dbn *p,*pn,*stack[64]; + va_list ap; + + va_start(ap,func); + for(i=0;i<HASH_SIZE;i++){ + if((p=table->ht[i])==NULL) + continue; + sp=0; + while(1){ + if(func) + func(p->key,p->data,ap); + if((pn=p->left)!=NULL){ + if(p->right){ + stack[sp++]=p->right; + } + } else { + if(p->right){ + pn=p->right; + } else { + if(sp==0) + break; + pn=stack[--sp]; + } + } +#ifdef MALLOC_DBN + free_dbn(p); +#else + free(p); +#endif + p=pn; + } + } + free(table); + va_end(ap); +} diff --git a/src/common/db.h b/src/common/db.h new file mode 100644 index 0000000..ea9acea --- /dev/null +++ b/src/common/db.h @@ -0,0 +1,47 @@ +#ifndef _DB_H_ +#define _DB_H_ + +#include <stdarg.h> + +#define HASH_SIZE (256+27) + +#define RED 0 +#define BLACK 1 + +struct dbn { + struct dbn *parent,*left,*right; + int color; + void *key; + void *data; +}; + +struct dbt { + int (*cmp)(struct dbt*,void*,void*); + unsigned int (*hash)(struct dbt*,void*); + // which 1 - key, 2 - data, 3 - both + void (*release)(struct dbn*,int which); + int maxlen; + struct dbn *ht[HASH_SIZE]; +}; + +#define strdb_search(t,k) db_search((t),(void*)(k)) +#define strdb_insert(t,k,d) db_insert((t),(void*)(k),(void*)(d)) +#define strdb_erase(t,k) db_erase ((t),(void*)(k)) +#define strdb_foreach db_foreach +#define strdb_final db_final +#define numdb_search(t,k) db_search((t),(void*)(k)) +#define numdb_insert(t,k,d) db_insert((t),(void*)(k),(void*)(d)) +#define numdb_erase(t,k) db_erase ((t),(void*)(k)) +#define numdb_foreach db_foreach +#define numdb_final db_final + +struct dbt* strdb_init(int maxlen); +struct dbt* numdb_init(void); +void* db_search(struct dbt *table,void* key); +void* db_search2(struct dbt *table, const char *key); // [MouseJstr] +struct dbn* db_insert(struct dbt *table,void* key,void* data); +void* db_erase(struct dbt *table,void* key); +void db_foreach(struct dbt*,int(*)(void*,void*,va_list),...); +void db_final(struct dbt*,int(*)(void*,void*,va_list),...); + +#endif diff --git a/src/common/grfio.c b/src/common/grfio.c new file mode 100644 index 0000000..08a8b2a --- /dev/null +++ b/src/common/grfio.c @@ -0,0 +1,948 @@ +/********************************************************************* + * + * Ragnarok Online Emulator : grfio.c -- grf file I/O Module + *-------------------------------------------------------------------- + * special need library : zlib + ********************************************************************* + * $Id: grfio.c,v 1.2 2004/09/29 17:31:49 kalaspuff Exp $ + * + * 2002/12/18... the original edition + * 2003/01/23 ... Code correction + * 2003/02/01 ... An addition and decryption processing are improved for LocalFile and two or more GRF(s) check processing. + * 2003/02/02 ... Even if there is no grf it does not stop -- as -- correction + * 2003/02/02... grf reading specification can be added later -- as -- correction (grfio_add function addition) + * 2003/02 / 03... at the time of grfio_resourcecheck processing the entry addition processing method -- correction + * 2003/02/05... change of the processing in grfio_init + * 2003/02/23... a local file check -- GRFIO_LOCAL -- switch (Defoe -- Function Off) + * 2003/10/21 ... The data of alpha client was read. + * 2003/11/10 ... Ready new grf format. + * 2003/11/11 ... version check fix & bug fix + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include <sys/stat.h> + +#include <zlib.h> + +#include "utils.h" +#include "grfio.h" +#include "mmo.h" + +#ifdef MEMWATCH +#include "memwatch.h" +#endif + +typedef unsigned char BYTE; +typedef unsigned short WORD; +typedef unsigned long DWORD; + +static char data_file[1024] = ""; // "data.grf"; +static char sdata_file[1024] = ""; // "sdata.grf"; +static char adata_file[1024] = ""; // "adata.grf"; +static char data_dir[1024] = ""; // "../"; + +// accessor to data_file,adata_file,sdata_file +char *grfio_setdatafile(const char *str){ strcpy(data_file,str); return data_file; } +char *grfio_setadatafile(const char *str){ strcpy(adata_file,str); return adata_file; } +char *grfio_setsdatafile(const char *str){ strcpy(sdata_file,str); return sdata_file; } + +//---------------------------- +// file entry table struct +//---------------------------- +typedef struct { + int srclen; // compressed size + int srclen_aligned; // + int declen; // original size + int srcpos; + short next; + char cycle; + char type; + char fn[128-4*5]; // file name + char gentry; // read grf file select +} FILELIST; +//gentry ... 0 : It acquires from a local file. +// It acquires from the resource file of 1>=:gentry_table[gentry-1]. +// 1<=: Check a local file. +// If it is, after re-setting to 0, it acquires from a local file. +// If there is nothing, mark reversal will be carried out, and it will re-set, and will acquire from a resource file as well as 1>=. + +//Since char defines *FILELIST.gentry, the maximum which can be added by grfio_add becomes by 127 pieces. + +#define GENTRY_LIMIT 127 +#define FILELIST_LIMIT 32768 // temporary maximum, and a theory top maximum are 2G. + +static FILELIST *filelist; +static int filelist_entrys; +static int filelist_maxentry; + +static char **gentry_table; +static int gentry_entrys; +static int gentry_maxentry; + +//---------------------------- +// file list hash table +//---------------------------- +static int filelist_hash[256]; + +//---------------------------- +// grf decode data table +//---------------------------- +static unsigned char BitMaskTable[8] = { + 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01 +}; + +static char BitSwapTable1[64] = { + 58, 50, 42, 34, 26, 18, 10, 2, 60, 52, 44, 36, 28, 20, 12, 4, + 62, 54, 46, 38, 30, 22, 14, 6, 64, 56, 48, 40, 32, 24, 16, 8, + 57, 49, 41, 33, 25, 17, 9, 1, 59, 51, 43, 35, 27, 19, 11, 3, + 61, 53, 45, 37, 29, 21, 13, 5, 63, 55, 47, 39, 31, 23, 15, 7 +}; +static char BitSwapTable2[64] = { + 40, 8, 48, 16, 56, 24, 64, 32, 39, 7, 47, 15, 55, 23, 63, 31, + 38, 6, 46, 14, 54, 22, 62, 30, 37, 5, 45, 13, 53, 21, 61, 29, + 36, 4, 44, 12, 52, 20, 60, 28, 35, 3, 43, 11, 51, 19, 59, 27, + 34, 2, 42, 10, 50, 18, 58, 26, 33, 1, 41, 9, 49, 17, 57, 25 +}; +static char BitSwapTable3[32] = { + 16, 7, 20, 21, 29, 12, 28, 17, 1, 15, 23, 26, 5, 18, 31, 10, + 2, 8, 24, 14, 32, 27, 3, 9, 19, 13, 30, 6, 22, 11, 4, 25 +}; + +static unsigned char NibbleData[4][64]={ + { + 0xef, 0x03, 0x41, 0xfd, 0xd8, 0x74, 0x1e, 0x47, 0x26, 0xef, 0xfb, 0x22, 0xb3, 0xd8, 0x84, 0x1e, + 0x39, 0xac, 0xa7, 0x60, 0x62, 0xc1, 0xcd, 0xba, 0x5c, 0x96, 0x90, 0x59, 0x05, 0x3b, 0x7a, 0x85, + 0x40, 0xfd, 0x1e, 0xc8, 0xe7, 0x8a, 0x8b, 0x21, 0xda, 0x43, 0x64, 0x9f, 0x2d, 0x14, 0xb1, 0x72, + 0xf5, 0x5b, 0xc8, 0xb6, 0x9c, 0x37, 0x76, 0xec, 0x39, 0xa0, 0xa3, 0x05, 0x52, 0x6e, 0x0f, 0xd9, + }, { + 0xa7, 0xdd, 0x0d, 0x78, 0x9e, 0x0b, 0xe3, 0x95, 0x60, 0x36, 0x36, 0x4f, 0xf9, 0x60, 0x5a, 0xa3, + 0x11, 0x24, 0xd2, 0x87, 0xc8, 0x52, 0x75, 0xec, 0xbb, 0xc1, 0x4c, 0xba, 0x24, 0xfe, 0x8f, 0x19, + 0xda, 0x13, 0x66, 0xaf, 0x49, 0xd0, 0x90, 0x06, 0x8c, 0x6a, 0xfb, 0x91, 0x37, 0x8d, 0x0d, 0x78, + 0xbf, 0x49, 0x11, 0xf4, 0x23, 0xe5, 0xce, 0x3b, 0x55, 0xbc, 0xa2, 0x57, 0xe8, 0x22, 0x74, 0xce, + }, { + 0x2c, 0xea, 0xc1, 0xbf, 0x4a, 0x24, 0x1f, 0xc2, 0x79, 0x47, 0xa2, 0x7c, 0xb6, 0xd9, 0x68, 0x15, + 0x80, 0x56, 0x5d, 0x01, 0x33, 0xfd, 0xf4, 0xae, 0xde, 0x30, 0x07, 0x9b, 0xe5, 0x83, 0x9b, 0x68, + 0x49, 0xb4, 0x2e, 0x83, 0x1f, 0xc2, 0xb5, 0x7c, 0xa2, 0x19, 0xd8, 0xe5, 0x7c, 0x2f, 0x83, 0xda, + 0xf7, 0x6b, 0x90, 0xfe, 0xc4, 0x01, 0x5a, 0x97, 0x61, 0xa6, 0x3d, 0x40, 0x0b, 0x58, 0xe6, 0x3d, + }, { + 0x4d, 0xd1, 0xb2, 0x0f, 0x28, 0xbd, 0xe4, 0x78, 0xf6, 0x4a, 0x0f, 0x93, 0x8b, 0x17, 0xd1, 0xa4, + 0x3a, 0xec, 0xc9, 0x35, 0x93, 0x56, 0x7e, 0xcb, 0x55, 0x20, 0xa0, 0xfe, 0x6c, 0x89, 0x17, 0x62, + 0x17, 0x62, 0x4b, 0xb1, 0xb4, 0xde, 0xd1, 0x87, 0xc9, 0x14, 0x3c, 0x4a, 0x7e, 0xa8, 0xe2, 0x7d, + 0xa0, 0x9f, 0xf6, 0x5c, 0x6a, 0x09, 0x8d, 0xf0, 0x0f, 0xe3, 0x53, 0x25, 0x95, 0x36, 0x28, 0xcb, + } +}; +/*----------------- + * long data get + */ +static unsigned int getlong(unsigned char *p) +{ + return *p+p[1]*256+(p[2]+p[3]*256)*65536; +} + +/*========================================== + * Grf data decode : Subs + *------------------------------------------ + */ +static void NibbleSwap(BYTE *Src, int len) +{ + for(;0<len;len--,Src++) { + *Src = (*Src>>4) | (*Src<<4); + } +} + +static void BitConvert(BYTE *Src,char *BitSwapTable) +{ + int lop,prm; + BYTE tmp[8]; + *(DWORD*)tmp=*(DWORD*)(tmp+4)=0; + for(lop=0;lop!=64;lop++) { + prm = BitSwapTable[lop]-1; + if (Src[(prm >> 3) & 7] & BitMaskTable[prm & 7]) { + tmp[(lop >> 3) & 7] |= BitMaskTable[lop & 7]; + } + } + *(DWORD*)Src = *(DWORD*)tmp; + *(DWORD*)(Src+4) = *(DWORD*)(tmp+4); +} + +static void BitConvert4(BYTE *Src) +{ + int lop,prm; + BYTE tmp[8]; + tmp[0] = ((Src[7]<<5) | (Src[4]>>3)) & 0x3f; // ..0 vutsr + tmp[1] = ((Src[4]<<1) | (Src[5]>>7)) & 0x3f; // ..srqpo n + tmp[2] = ((Src[4]<<5) | (Src[5]>>3)) & 0x3f; // ..o nmlkj + tmp[3] = ((Src[5]<<1) | (Src[6]>>7)) & 0x3f; // ..kjihg f + tmp[4] = ((Src[5]<<5) | (Src[6]>>3)) & 0x3f; // ..g fedcb + tmp[5] = ((Src[6]<<1) | (Src[7]>>7)) & 0x3f; // ..cba98 7 + tmp[6] = ((Src[6]<<5) | (Src[7]>>3)) & 0x3f; // ..8 76543 + tmp[7] = ((Src[7]<<1) | (Src[4]>>7)) & 0x3f; // ..43210 v + + for(lop=0;lop!=4;lop++) { + tmp[lop] = (NibbleData[lop][tmp[lop*2]] & 0xf0) + | (NibbleData[lop][tmp[lop*2+1]] & 0x0f); + } + + *(DWORD*)(tmp+4)=0; + for(lop=0;lop!=32;lop++) { + prm = BitSwapTable3[lop]-1; + if (tmp[prm >> 3] & BitMaskTable[prm & 7]) { + tmp[(lop >> 3) + 4] |= BitMaskTable[lop & 7]; + } + } + *(DWORD*)Src ^= *(DWORD*)(tmp+4); +} + +static void decode_des_etc(BYTE *buf,int len,int type,int cycle) +{ + int lop,cnt=0; + if(cycle<3) cycle=3; + else if(cycle<5) cycle++; + else if(cycle<7) cycle+=9; + else cycle+=15; + + for(lop=0;lop*8<len;lop++,buf+=8) { + if(lop<20 || (type==0 && lop%cycle==0)){ // des + BitConvert(buf,BitSwapTable1); + BitConvert4(buf); + BitConvert(buf,BitSwapTable2); + } else { + if(cnt==7 && type==0){ + int a; + BYTE tmp[8]; + *(DWORD*)tmp = *(DWORD*)buf; + *(DWORD*)(tmp+4) = *(DWORD*)(buf+4); + cnt=0; + buf[0]=tmp[3]; + buf[1]=tmp[4]; + buf[2]=tmp[6]; + buf[3]=tmp[0]; + buf[4]=tmp[1]; + buf[5]=tmp[2]; + buf[6]=tmp[5]; + a=tmp[7]; + if(a==0x00) a=0x2b; + else if(a==0x2b) a=0x00; + else if(a==0x01) a=0x68; + else if(a==0x68) a=0x01; + else if(a==0x48) a=0x77; + else if(a==0x77) a=0x48; + else if(a==0x60) a=0xff; + else if(a==0xff) a=0x60; + else if(a==0x6c) a=0x80; + else if(a==0x80) a=0x6c; + else if(a==0xb9) a=0xc0; + else if(a==0xc0) a=0xb9; + else if(a==0xeb) a=0xfe; + else if(a==0xfe) a=0xeb; + buf[7]=a; + } + cnt++; + } + } +} +/*========================================== + * Grf data decode sub : zip + *------------------------------------------ + */ +static int decode_zip(Bytef* dest, uLongf* destLen, const Bytef* source, uLong sourceLen) +{ + z_stream stream; + int err; + + stream.next_in = (Bytef*)source; + stream.avail_in = (uInt)sourceLen; + /* Check for source > 64K on 16-bit machine: */ + if ((uLong)stream.avail_in != sourceLen) return Z_BUF_ERROR; + + stream.next_out = dest; + stream.avail_out = (uInt)*destLen; + if ((uLong)stream.avail_out != *destLen) return Z_BUF_ERROR; + + stream.zalloc = (alloc_func)0; + stream.zfree = (free_func)0; + + err = inflateInit(&stream); + if (err != Z_OK) return err; + + err = inflate(&stream, Z_FINISH); + if (err != Z_STREAM_END) { + inflateEnd(&stream); + return err == Z_OK ? Z_BUF_ERROR : err; + } + *destLen = stream.total_out; + + err = inflateEnd(&stream); + return err; +} +/*********************************************************** + *** File List Sobroutines *** + ***********************************************************/ + +/*========================================== + * File List : Hash make + *------------------------------------------ + */ +static int filehash(unsigned char *fname) +{ + unsigned int hash=0; + while(*fname) { + hash = ((hash<<1)+(hash>>7)*9+tolower(*fname)); + fname++; + } + return hash & 255; +} + +/*========================================== + * File List : Hash initalize + *------------------------------------------ + */ +static void hashinit(void) +{ + int lop; + for(lop=0;lop<256;lop++) + filelist_hash[lop]=-1; +} + +/*========================================== + * File List : File find + *------------------------------------------ + */ +FILELIST *filelist_find(char *fname) +{ + int hash; + + for(hash=filelist_hash[filehash(fname)];hash>=0;hash=filelist[hash].next) { + if(strcasecmp(filelist[hash].fn,fname)==0) + break; + } + + return (hash>=0)? &filelist[hash] : NULL; +} + +/*========================================== + * File List : Filelist add + *------------------------------------------ + */ +#define FILELIST_ADDS 1024 // number increment of file lists ` + +static FILELIST* filelist_add(FILELIST *entry) +{ + int hash; + + if (filelist_entrys>=FILELIST_LIMIT) { + printf("filelist limit : filelist_add\n"); + exit(1); + } + + if (filelist_entrys>=filelist_maxentry) { + FILELIST *new_filelist = (FILELIST*)realloc( + (void*)filelist, (filelist_maxentry+FILELIST_ADDS)*sizeof(FILELIST) ); + if (new_filelist != NULL) { + filelist = new_filelist; + memset(filelist + filelist_maxentry, '\0', + FILELIST_ADDS * sizeof(FILELIST)); + filelist_maxentry += FILELIST_ADDS; + } else { + printf("out of memory : filelist_add\n"); + exit(1); + } + } + + memcpy( &filelist[filelist_entrys], entry, sizeof(FILELIST) ); + + hash = filehash(entry->fn); + filelist[filelist_entrys].next = filelist_hash[hash]; + filelist_hash[hash] = filelist_entrys; + + filelist_entrys++; + + return &filelist[filelist_entrys-1]; +} + +static FILELIST* filelist_modify(FILELIST *entry) +{ + FILELIST *fentry; + if ((fentry=filelist_find(entry->fn))!=NULL) { + int tmp = fentry->next; + memcpy( fentry, entry, sizeof(FILELIST) ); + fentry->next = tmp; + } else { + fentry = filelist_add(entry); + } + return fentry; +} + +/*========================================== + * File List : filelist size adjust + *------------------------------------------ + */ +static void filelist_adjust(void) +{ + if (filelist!=NULL) { + if (filelist_maxentry>filelist_entrys) { + FILELIST *new_filelist = (FILELIST*)realloc( + (void*)filelist,filelist_entrys*sizeof(FILELIST) ); + if (new_filelist != NULL) { + filelist = new_filelist; + filelist_maxentry = filelist_entrys; + } else { + printf("out of memory : filelist\n"); + exit(1); + } + } + } +} + +/*********************************************************** + *** Grfio Sobroutines *** + ***********************************************************/ +/*========================================== + * Grfio : Resnametable replace + *------------------------------------------ + */ +char* grfio_resnametable(char* fname, char *lfname) +{ + FILE *fp; + char *p; + char w1[256],w2[256],restable[256],line[512]; + + sprintf(restable,"%sdata\\resnametable.txt",data_dir); + + for(p=&restable[0];*p!=0;p++) if (*p=='\\') *p = '/'; + + fp = fopen(restable,"rb"); + if(fp==NULL) { + printf("%s not found\n",restable); + exit(1); // 1:not found error + } + + while(fgets(line,508,fp)){ + if((sscanf(line,"%[^#]#%[^#]#",w1,w2)==2) && (sscanf(fname,"%*5s%s",lfname)==1) && (!strcmpi(w1,lfname))){ + sprintf(lfname,"data\\%s",w2); + fclose(fp); + return lfname; + } + } + fclose(fp); + return fname; + +} + +/*========================================== + * Grfio : Resource file size get + *------------------------------------------ + */ +int grfio_size(char *fname) +{ + FILELIST *entry; + + entry = filelist_find(fname); + + if (entry==NULL || entry->gentry<0) { // LocalFileCheck + char lfname[256],rname[256],*p; + FILELIST lentry; + struct stat st; + + //printf("%s\t",fname); + sprintf(rname,"%s",grfio_resnametable(fname,lfname)); + //printf("%s\n",rname); + sprintf(lfname,"%s%s",data_dir,rname); + //printf("%s\n",lfname); + + for(p=&lfname[0];*p!=0;p++) if (*p=='\\') *p = '/'; // * At the time of Unix + + if (stat(lfname,&st)==0) { + strncpy(lentry.fn, fname, sizeof(lentry.fn)-1 ); + lentry.declen = st.st_size; + lentry.gentry = 0; // 0:LocalFile + entry = filelist_modify(&lentry); + } else if (entry==NULL) { + printf("%s not found\n", fname); + //exit(1); + return -1; + } + } + return entry->declen; +} + +/*========================================== + * Grfio : Resource file read & size get + *------------------------------------------ + */ +void* grfio_reads(char *fname, int *size) +{ + FILE *in = NULL; + unsigned char *buf=NULL,*buf2=NULL; + char *gfname; + FILELIST *entry; + + entry = filelist_find(fname); + + if (entry==NULL || entry->gentry<=0) { // LocalFileCheck + char lfname[256],rname[256],*p; + FILELIST lentry; + + strncpy(lfname,fname,255); + sprintf(rname,"%s",grfio_resnametable(fname,lfname)); + sprintf(lfname,"%s%s",data_dir,rname); + //printf("%s\n",lfname); + + for(p=&lfname[0];*p!=0;p++) if (*p=='\\') *p = '/'; // * At the time of Unix + + in = fopen(lfname,"rb"); + if(in!=NULL) { + if (entry!=NULL && entry->gentry==0) { + lentry.declen=entry->declen; + } else { + fseek(in,0,2); // SEEK_END + lentry.declen = ftell(in); + } + fseek(in,0,0); // SEEK_SET + buf2 = calloc(lentry.declen+1024, 1); + if (buf2==NULL) { + printf("file read memory allocate error : declen\n"); + goto errret; + } + fread(buf2,1,lentry.declen,in); + fclose(in); in = NULL; + strncpy( lentry.fn, fname, sizeof(lentry.fn)-1 ); + lentry.gentry = 0; // 0:LocalFile + entry = filelist_modify(&lentry); + } else { + if (entry!=NULL && entry->gentry<0) { + entry->gentry = -entry->gentry; // local file checked + } else { + printf("%s not found\n", fname); + //goto errret; + free(buf2); + return NULL; + } + } + } + if (entry!=NULL && entry->gentry>0) { // Archive[GRF] File Read + buf = calloc(entry->srclen_aligned+1024, 1); + if (buf==NULL) { + printf("file read memory allocate error : srclen_aligned\n"); + goto errret; + } + gfname = gentry_table[entry->gentry-1]; + in = fopen(gfname,"rb"); + if(in==NULL) { + printf("%s not found\n",gfname); + //goto errret; + free(buf); + return NULL; + } + fseek(in,entry->srcpos,0); + fread(buf,1,entry->srclen_aligned,in); + fclose(in); + buf2=calloc(entry->declen+1024, 1); + if (buf2==NULL) { + printf("file decode memory allocate error\n"); + goto errret; + } + if(entry->type==1 || entry->type==3 || entry->type==5) { + uLongf len; + if (entry->cycle>=0) { + decode_des_etc(buf,entry->srclen_aligned,entry->cycle==0,entry->cycle); + } + len=entry->declen; + decode_zip(buf2,&len,buf,entry->srclen); + if(len!=entry->declen) { + printf("decode_zip size miss match err: %d != %d\n",(int)len,entry->declen); + goto errret; + } + } else { + memcpy(buf2,buf,entry->declen); + } + free(buf); + } + if (size!=NULL && entry!=NULL) + *size = entry->declen; + return buf2; +errret: + if (buf!=NULL) free(buf); + if (buf2!=NULL) free(buf2); + if (in!=NULL) fclose(in); + exit(1); //return NULL; +} + +/*========================================== + * Grfio : Resource file read + *------------------------------------------ + */ +void* grfio_read(char *fname) +{ + return grfio_reads(fname,NULL); +} + +/*========================================== + * Resource filename decode + *------------------------------------------ + */ +static unsigned char * decode_filename(unsigned char *buf,int len) +{ + int lop; + for(lop=0;lop<len;lop+=8) { + NibbleSwap(&buf[lop],8); + BitConvert(&buf[lop],BitSwapTable1); + BitConvert4(&buf[lop]); + BitConvert(&buf[lop],BitSwapTable2); + } + return buf; +} + +/*========================================== + * Grfio : Entry table read + *------------------------------------------ + */ +static int grfio_entryread(char *gfname,int gentry) +{ + FILE *fp; + int grf_size,list_size; + unsigned char grf_header[0x2e]; + int lop,entry,entrys,ofs,grf_version; + unsigned char *fname; + unsigned char *grf_filelist; + + fp = fopen(gfname,"rb"); + if(fp==NULL) { + printf("%s not found\n",gfname); + return 1; // 1:not found error + } + + fseek(fp,0,2); // SEEK_END + grf_size = ftell(fp); + fseek(fp,0,0); // SEEK_SET + fread(grf_header,1,0x2e,fp); + if(strcmp(grf_header,"Master of Magic") || fseek(fp,getlong(grf_header+0x1e),1)){ // SEEK_CUR + fclose(fp); + printf("%s read error\n",gfname); + return 2; // 2:file format error + } + + grf_version = getlong(grf_header+0x2a) >> 8; + + if (grf_version==0x01) { //****** Grf version 01xx ****** + list_size = grf_size-ftell(fp); + grf_filelist = calloc(list_size, 1); + if(grf_filelist==NULL){ + fclose(fp); + printf("out of memory : grf_filelist\n"); + return 3; // 3:memory alloc error + } + fread(grf_filelist,1,list_size,fp); + fclose(fp); + + entrys = getlong(grf_header+0x26) - getlong(grf_header+0x22) - 7; + + // Get an entry + for(entry=0,ofs=0;entry<entrys;entry++){ + int ofs2,srclen,srccount,type; + char *period_ptr; + FILELIST aentry; + + ofs2 = ofs+getlong(grf_filelist+ofs)+4; + type = grf_filelist[ofs2+12]; + if( type!=0 ){ // Directory Index ... skip + fname = decode_filename(grf_filelist+ofs+6,grf_filelist[ofs]-6); + if(strlen(fname)>sizeof(aentry.fn)-1){ + printf("file name too long : %s\n",fname); + free(grf_filelist); + exit(1); + } + srclen=0; + if((period_ptr=rindex(fname,'.'))!=NULL){ + for(lop=0;lop<4;lop++) { + if(strcasecmp(period_ptr,".gnd\0.gat\0.act\0.str"+lop*5)==0) + break; + } + srclen=getlong(grf_filelist+ofs2)-getlong(grf_filelist+ofs2+8)-715; + if(lop==4) { + for(lop=10,srccount=1;srclen>=lop;lop=lop*10,srccount++); + } else { + srccount=0; + } + } else { + srccount=0; + } + + aentry.srclen = srclen; + aentry.srclen_aligned = getlong(grf_filelist+ofs2+4)-37579; + aentry.declen = getlong(grf_filelist+ofs2+8); + aentry.srcpos = getlong(grf_filelist+ofs2+13)+0x2e; + aentry.cycle = srccount; + aentry.type = type; + strncpy(aentry.fn,fname,sizeof(aentry.fn)-1); +#ifdef GRFIO_LOCAL + aentry.gentry = -(gentry+1); // As Flag for making it a negative number carrying out the first time LocalFileCheck +#else + aentry.gentry = gentry+1; // With no first time LocalFileCheck +#endif + filelist_modify(&aentry); + } + ofs = ofs2 + 17; + } + free(grf_filelist); + + } else if (grf_version==0x02) { //****** Grf version 02xx ****** + unsigned char eheader[8]; + unsigned char *rBuf; + uLongf rSize,eSize; + + fread(eheader,1,8,fp); + rSize = getlong(eheader); // Read Size + eSize = getlong(eheader+4); // Extend Size + + if (rSize > grf_size-ftell(fp)) { + fclose(fp); + printf("Illegal data format : grf compress entry size\n"); + return 4; + } + + rBuf = calloc( rSize , 1); // Get a Read Size + if (rBuf==NULL) { + fclose(fp); + printf("out of memory : grf compress entry table buffer\n"); + return 3; + } + grf_filelist = calloc( eSize , 1); // Get a Extend Size + if (grf_filelist==NULL) { + free(rBuf); + fclose(fp); + printf("out of memory : grf extract entry table buffer\n"); + return 3; + } + fread(rBuf,1,rSize,fp); + fclose(fp); + decode_zip(grf_filelist,&eSize,rBuf,rSize); // Decode function + list_size = eSize; + free(rBuf); + + entrys = getlong(grf_header+0x26) - 7; + + // Get an entry + for(entry=0,ofs=0;entry<entrys;entry++){ + int ofs2,srclen,srccount,type; + FILELIST aentry; + + fname = grf_filelist+ofs; + if (strlen(fname)>sizeof(aentry.fn)-1) { + printf("grf : file name too long : %s\n",fname); + free(grf_filelist); + exit(1); + } + ofs2 = ofs+strlen(grf_filelist+ofs)+1; + type = grf_filelist[ofs2+12]; + if(type==1 || type==3 || type==5) { + srclen=getlong(grf_filelist+ofs2); + if (grf_filelist[ofs2+12]==3) { + for(lop=10,srccount=1;srclen>=lop;lop=lop*10,srccount++); + } else if (grf_filelist[ofs2+12]==5) { + srccount = 0; + } else { // if (grf_filelist[ofs2+12]==1) { + srccount = -1; + } + + aentry.srclen = srclen; + aentry.srclen_aligned = getlong(grf_filelist+ofs2+4); + aentry.declen = getlong(grf_filelist+ofs2+8); + aentry.srcpos = getlong(grf_filelist+ofs2+13)+0x2e; + aentry.cycle = srccount; + aentry.type = type; + strncpy(aentry.fn,fname,sizeof(aentry.fn)-1); +#ifdef GRFIO_LOCAL + aentry.gentry = -(gentry+1); // As Flag for making it a negative number carrying out the first time LocalFileCheck +#else + aentry.gentry = gentry+1; // With no first time LocalFileCheck +#endif + filelist_modify(&aentry); + } + ofs = ofs2 + 17; + } + free(grf_filelist); + + } else { //****** Grf Other version ****** + fclose(fp); + printf("not support grf versions : %04x\n",getlong(grf_header+0x2a)); + return 4; + } + + filelist_adjust(); // Unnecessary area release of filelist + + return 0; // 0:no error +} + +/*========================================== + * Grfio : Resource file check + *------------------------------------------ + */ +static void grfio_resourcecheck() +{ + int size; + unsigned char *buf,*ptr; + char w1[256],w2[256],src[256],dst[256]; + FILELIST *entry; + + buf=grfio_reads("data\\resnametable.txt",&size); + buf[size] = 0; + + for(ptr=buf;ptr-buf<size;) { + if(sscanf(ptr,"%[^#]#%[^#]#",w1,w2)==2){ + if(strstr(w2,"bmp")){ + sprintf(src,"data\\texture\\%s",w1); + sprintf(dst,"data\\texture\\%s",w2); + } else { + sprintf(src,"data\\%s",w1); + sprintf(dst,"data\\%s",w2); + } + entry = filelist_find(dst); + if (entry!=NULL) { + FILELIST fentry; + memcpy( &fentry, entry, sizeof(FILELIST) ); + strncpy( fentry.fn ,src, sizeof(fentry.fn)-1 ); + filelist_modify(&fentry); + } else { + //printf("file not found in data.grf : %s < %s\n",dst,src); + } + } + ptr = strchr(ptr,'\n'); // Next line + if (!ptr) break; + ptr++; + } + free(buf); + filelist_adjust(); // Unnecessary area release of filelist +} + +/*========================================== + * Grfio : Resource add + *------------------------------------------ + */ +#define GENTRY_ADDS 16 // The number increment of gentry_table entries + +int grfio_add(char *fname) +{ + int len,result; + char *buf; + + if (gentry_entrys>=GENTRY_LIMIT) { + printf("gentrys limit : grfio_add\n"); + exit(1); + } + + printf("%s file reading...\n",fname); + + if (gentry_entrys>=gentry_maxentry) { + char **new_gentry = (char**)realloc( + (void*)gentry_table,(gentry_maxentry+GENTRY_ADDS)*sizeof(char*) ); + if (new_gentry!=NULL) { + int lop; + gentry_table = new_gentry; + gentry_maxentry += GENTRY_ADDS; + for(lop=gentry_entrys;lop<gentry_maxentry;lop++) + gentry_table[lop] = NULL; + } else { + printf("out of memory : grfio_add\n"); + exit(1); + } + } + len = strlen( fname ); + buf = calloc(len+1, 1); + if (buf==NULL) { + printf("out of memory : gentry\n"); + exit(1); + } + strcpy( buf, fname ); + gentry_table[gentry_entrys++] = buf; + + result = grfio_entryread(fname,gentry_entrys-1); + + if (result==0) { + // Resource check + grfio_resourcecheck(); + } + + return result; +} + +/*========================================== + * Grfio : Finalize + *------------------------------------------ + */ +void grfio_final(void) +{ + int lop; + + if (filelist!=NULL) free(filelist); + filelist = NULL; + filelist_entrys = filelist_maxentry = 0; + + if (gentry_table!=NULL) { + for(lop=0;lop<gentry_entrys;lop++) { + if (gentry_table[lop]!=NULL) { + free(gentry_table[lop]); + } + } + free(gentry_table); + } + gentry_table = NULL; + gentry_entrys = gentry_maxentry = 0; +} + +/*========================================== + * Grfio : Initialize + *------------------------------------------ + */ +void grfio_init(char *fname) +{ + FILE *data_conf; + char line[1024], w1[1024], w2[1024]; + int result = 0, result2 = 0, result3 = 0; + + data_conf = fopen(fname, "r"); + + // It will read, if there is grf-files.txt. + if (data_conf) { + while(fgets(line, 1020, data_conf)) { + if (sscanf(line, "%[^:]: %[^\r\n]", w1, w2) == 2) { + if(strcmp(w1, "data") == 0) + strcpy(data_file, w2); + else if(strcmp(w1, "sdata") == 0) + strcpy(sdata_file, w2); + else if(strcmp(w1, "adata") == 0) + strcpy(adata_file, w2); + else if(strcmp(w1,"data_dir") == 0) + strcpy(data_dir, w2); + } + } + + fclose(data_conf); + printf("read %s done\n",fname); + } // end of reading grf-files.txt + + hashinit(); // hash table initialization + + filelist = NULL; filelist_entrys = filelist_maxentry = 0; + gentry_table = NULL; gentry_entrys = gentry_maxentry = 0; + atexit(grfio_final); // End processing definition + + // Entry table reading + + if (strcmp(data_file, "") != 0) // If data directive exists in grf-files.txt (i.e. data_file is not equal to "") + result = grfio_add(data_file); // Primary data file + + if (strcmp(sdata_file, "") != 0) // If sdata directive exists in grf-files.txt (i.e. sdata_file is not equal to "") + result2 = grfio_add(sdata_file); // Sakray data file + + if (strcmp(adata_file, "") != 0) // If data directive exists in grf-files.txt (i.e. adata_file is not equal to "") + result3 = grfio_add(adata_file); // Alpha version data file + + if (result != 0 && result2 != 0 && result3 != 0) { + printf("not grf file readed exit!!\n"); + exit(1); // It ends, if a resource cannot read one. + } +} diff --git a/src/common/grfio.h b/src/common/grfio.h new file mode 100644 index 0000000..53b9da8 --- /dev/null +++ b/src/common/grfio.h @@ -0,0 +1,16 @@ +// $Id: grfio.h,v 1.1.1.1 2004/09/10 17:44:49 MagicalTux Exp $ +#ifndef _GRFIO_H_ +#define _GRFIO_H_ + +void grfio_init(char*); // GRFIO Initialize +int grfio_add(char*); // GRFIO Resource file add +void* grfio_read(char*); // GRFIO data file read +void* grfio_reads(char*,int*); // GRFIO data file read & size get +int grfio_size(char*); // GRFIO data file size get + +// Accessor to GRF filenames +char *grfio_setdatafile(const char *str); +char *grfio_setadatafile(const char *str); +char *grfio_setsdatafile(const char *str); + +#endif // _GRFIO_H_ diff --git a/src/common/lock.c b/src/common/lock.c new file mode 100644 index 0000000..9a2205b --- /dev/null +++ b/src/common/lock.c @@ -0,0 +1,37 @@ + +#include <stdio.h> +#include "lock.h" + +// 書き込みファイルの保護処理 +// (書き込みが終わるまで、旧ファイルを保管しておく) + +// 新しいファイルの書き込み開始 +FILE* lock_fopen(const char* filename,int *info) { + char newfile[512]; + FILE *fp; + int no = 0; + + // 安全なファイル名を得る(手抜き) + do { + sprintf(newfile,"%s_%04d.tmp",filename,++no); + } while((fp = fopen(newfile,"r")) && (fclose(fp), no<9999) ); + *info = no; + return fopen(newfile,"w"); +} + +// 旧ファイルを削除&新ファイルをリネーム +int lock_fclose(FILE *fp,const char* filename,int *info) { + int ret = 0; + char newfile[512]; + if(fp != NULL) { + ret = fclose(fp); + sprintf(newfile,"%s_%04d.tmp",filename,*info); + remove(filename); + // このタイミングで落ちると最悪。 + rename(newfile,filename); + return ret; + } else { + return 1; + } +} + diff --git a/src/common/lock.h b/src/common/lock.h new file mode 100644 index 0000000..795bf88 --- /dev/null +++ b/src/common/lock.h @@ -0,0 +1,8 @@ +#ifndef _LOCK_H_ +#define _LOCK_H_ + +FILE* lock_fopen(const char* filename,int *info); +int lock_fclose(FILE *fp,const char* filename,int *info); + +#endif + diff --git a/src/common/malloc.c b/src/common/malloc.c new file mode 100644 index 0000000..eda9bc2 --- /dev/null +++ b/src/common/malloc.c @@ -0,0 +1,44 @@ +#include <stdio.h> +#include <stdlib.h> +#include "malloc.h" + +void* aMalloc_( size_t size, const char *file, int line, const char *func ) +{ + void *ret; + +// printf("%s:%d: in func %s: malloc %d\n",file,line,func,size); + ret=malloc(size); + if(ret==NULL){ + printf("%s:%d: in func %s: malloc error out of memory!\n",file,line,func); + exit(1); + + } + return ret; +} +void* aCalloc_( size_t num, size_t size, const char *file, int line, const char *func ) +{ + void *ret; + +// printf("%s:%d: in func %s: calloc %d %d\n",file,line,func,num,size); + ret=calloc(num,size); + if(ret==NULL){ + printf("%s:%d: in func %s: calloc error out of memory!\n",file,line,func); + exit(1); + + } + return ret; +} + +void* aRealloc_( void *p, size_t size, const char *file, int line, const char *func ) +{ + void *ret; + +// printf("%s:%d: in func %s: realloc %p %d\n",file,line,func,p,size); + ret=realloc(p,size); + if(ret==NULL){ + printf("%s:%d: in func %s: realloc error out of memory!\n",file,line,func); + exit(1); + + } + return ret; +} diff --git a/src/common/malloc.h b/src/common/malloc.h new file mode 100644 index 0000000..3733a5e --- /dev/null +++ b/src/common/malloc.h @@ -0,0 +1,25 @@ +#ifndef _MALLOC_H_ +#define _MALLOC_H_ + +#include <stdlib.h> + +#if __STDC_VERSION__ < 199901L +# if __GNUC__ >= 2 +# define __func__ __FUNCTION__ +# else +# define __func__ "" +# endif +#endif + +#define ALC_MARK __FILE__, __LINE__, __func__ + +void* aMalloc_( size_t size, const char *file, int line, const char *func ); +void* aCalloc_( size_t num, size_t size, const char *file, int line, const char *func ); +void* aRealloc_( void *p, size_t size, const char *file, int line, const char *func ); + +#define aMalloc(n) aMalloc_(n,ALC_MARK) +#define aCalloc(m,n) aCalloc_(m,n,ALC_MARK) +#define aRealloc(p,n) aRealloc_(p,n,ALC_MARK) + + +#endif diff --git a/src/common/mmo.h b/src/common/mmo.h new file mode 100644 index 0000000..4105135 --- /dev/null +++ b/src/common/mmo.h @@ -0,0 +1,304 @@ +// $Id: mmo.h,v 1.3 2004/09/25 20:12:25 PoW Exp $ +// Original : mmo.h 2003/03/14 12:07:02 Rev.1.7 + +#ifndef _MMO_H_ +#define _MMO_H_ + +#include <time.h> +#include "utils.h" // LCCWIN32 + +#ifdef CYGWIN +// txtやlogなどの書き出すファイルの改行コード +#define RETCODE "\r\n" // (CR/LF:Windows系) +#else +#define RETCODE "\n" // (LF:Unix系) +#endif + +#define FIFOSIZE_SERVERLINK 128*1024 + +// set to 0 to not check IP of player between each server. +// set to another value if you want to check (1) +#define CMP_AUTHFIFO_IP 1 + +#define CMP_AUTHFIFO_LOGIN2 1 + +#define MAX_MAP_PER_SERVER 512 +#define MAX_INVENTORY 100 +#define MAX_AMOUNT 30000 +#define MAX_ZENY 1000000000 // 1G zeny +#define MAX_CART 100 +#define MAX_SKILL 450 +#define GLOBAL_REG_NUM 96 +#define ACCOUNT_REG_NUM 16 +#define ACCOUNT_REG2_NUM 16 +#define DEFAULT_WALK_SPEED 150 +#define MIN_WALK_SPEED 0 +#define MAX_WALK_SPEED 1000 +#define MAX_STORAGE 300 +#define MAX_GUILD_STORAGE 1000 +#define MAX_PARTY 12 +#define MAX_GUILD 36 // increased max guild members to accomodate for +2 increase for extension levels [Valaris] (removed) [PoW] +#define MAX_GUILDPOSITION 20 // increased max guild positions to accomodate for all members [Valaris] (removed) [PoW] +#define MAX_GUILDEXPLUSION 32 +#define MAX_GUILDALLIANCE 16 +#define MAX_GUILDSKILL 8 +#define MAX_GUILDCASTLE 24 // increased to include novice castles [Valaris] +#define MAX_GUILDLEVEL 50 + +#define MIN_HAIR_STYLE battle_config.min_hair_style +#define MAX_HAIR_STYLE battle_config.max_hair_style +#define MIN_HAIR_COLOR battle_config.min_hair_color +#define MAX_HAIR_COLOR battle_config.max_hair_color +#define MIN_CLOTH_COLOR battle_config.min_cloth_color +#define MAX_CLOTH_COLOR battle_config.max_cloth_color + +// for produce +#define MIN_ATTRIBUTE 0 +#define MAX_ATTRIBUTE 4 +#define ATTRIBUTE_NORMAL 0 +#define MIN_STAR 0 +#define MAX_STAR 3 + +#define MIN_PORTAL_MEMO 0 +#define MAX_PORTAL_MEMO 2 + +#define MAX_STATUS_TYPE 5 + +#define WEDDING_RING_M 2634 +#define WEDDING_RING_F 2635 + +#define CHAR_CONF_NAME "conf/char_athena.conf" + +struct item { + int id; + short nameid; + short amount; + unsigned short equip; + char identify; + char refine; + char attribute; + short card[4]; + short broken; +}; + +struct point{ + char map[24]; + short x,y; +}; + +struct skill { + unsigned short id,lv,flag; +}; + +struct global_reg { + char str[32]; + int value; +}; + +struct s_pet { + int account_id; + int char_id; + int pet_id; + short class; + short level; + short egg_id;//pet egg id + short equip;//pet equip name_id + short intimate;//pet friendly + short hungry;//pet hungry + char name[24]; + char rename_flag; + char incuvate; +}; + +struct mmo_charstatus { + int char_id; + int account_id; + int partner_id; + + int base_exp,job_exp,zeny; + + short class; + short status_point,skill_point; + int hp,max_hp,sp,max_sp; + short option,karma,manner; + short hair,hair_color,clothes_color; + int party_id,guild_id,pet_id; + + short weapon,shield; + short head_top,head_mid,head_bottom; + + char name[24]; + unsigned char base_level,job_level; + short str,agi,vit,int_,dex,luk; + unsigned char char_num,sex; + + unsigned long mapip; + unsigned int mapport; + + struct point last_point,save_point,memo_point[10]; + struct item inventory[MAX_INVENTORY],cart[MAX_CART]; + struct skill skill[MAX_SKILL]; + int global_reg_num; + struct global_reg global_reg[GLOBAL_REG_NUM]; + int account_reg_num; + struct global_reg account_reg[ACCOUNT_REG_NUM]; + int account_reg2_num; + struct global_reg account_reg2[ACCOUNT_REG2_NUM]; +}; + +struct storage { + int account_id; + short storage_status; + short storage_amount; + struct item storage[MAX_STORAGE]; +}; + +struct guild_storage { + int guild_id; + short storage_status; + short storage_amount; + struct item storage[MAX_GUILD_STORAGE]; +}; + +struct map_session_data; + +struct gm_account { + int account_id; + int level; +}; + +struct party_member { + int account_id; + char name[24],map[24]; + int leader,online,lv; + struct map_session_data *sd; +}; + +struct party { + int party_id; + char name[24]; + int exp; + int item; + struct party_member member[MAX_PARTY]; +}; + +struct guild_member { + int account_id, char_id; + short hair,hair_color,gender,class,lv; + int exp,exp_payper; + short online,position; + int rsv1,rsv2; + char name[24]; + struct map_session_data *sd; +}; + +struct guild_position { + char name[24]; + int mode; + int exp_mode; +}; + +struct guild_alliance { + int opposition; + int guild_id; + char name[24]; +}; + +struct guild_explusion { + char name[24]; + char mes[40]; + char acc[40]; + int account_id; + int rsv1,rsv2,rsv3; +}; + +struct guild_skill { + int id,lv; +}; + +struct guild { + int guild_id; + short guild_lv, connect_member, max_member, average_lv; + int exp,next_exp,skill_point,castle_id; + char name[24],master[24]; + struct guild_member member[MAX_GUILD]; + struct guild_position position[MAX_GUILDPOSITION]; + char mes1[60],mes2[120]; + int emblem_len,emblem_id; + char emblem_data[2048]; + struct guild_alliance alliance[MAX_GUILDALLIANCE]; + struct guild_explusion explusion[MAX_GUILDEXPLUSION]; + struct guild_skill skill[MAX_GUILDSKILL]; +}; + +struct guild_castle { + int castle_id; + char map_name[24]; + char castle_name[24]; + char castle_event[24]; + int guild_id; + int economy; + int defense; + int triggerE; + int triggerD; + int nextTime; + int payTime; + int createTime; + int visibleC; + int visibleG0; + int visibleG1; + int visibleG2; + int visibleG3; + int visibleG4; + int visibleG5; + int visibleG6; + int visibleG7; + int Ghp0; // added Guardian HP [Valaris] + int Ghp1; + int Ghp2; + int Ghp3; + int Ghp4; + int Ghp5; + int Ghp6; + int Ghp7; + int GID0; + int GID1; + int GID2; + int GID3; + int GID4; + int GID5; + int GID6; + int GID7; // end addition [Valaris] +}; +struct square { + int val1[5]; + int val2[5]; +}; + +enum { + GBI_EXP =1, // ギルドのEXP + GBI_GUILDLV =2, // ギルドのLv + GBI_SKILLPOINT =3, // ギルドのスキルポイント + GBI_SKILLLV =4, // ギルドスキルLv + + GMI_POSITION =0, // メンバーの役職変更 + GMI_EXP =1, // メンバーのEXP + +}; + +#ifndef LCCWIN32 +#ifndef strcmpi +#define strcmpi strcasecmp +#endif +#ifndef stricmp +#define stricmp strcasecmp +#endif +#ifndef strncmpi +#define strncmpi strncasecmp +#endif +#ifndef strnicmp +#define strnicmp strncasecmp +#endif +#endif + +#endif // _MMO_H_ diff --git a/src/common/nullpo.c b/src/common/nullpo.c new file mode 100644 index 0000000..5fbf5fc --- /dev/null +++ b/src/common/nullpo.c @@ -0,0 +1,90 @@ +#include <stdio.h> +#include <stdarg.h> +#include <string.h> +#include "nullpo.h" +// #include "logs.h" // 布石してみる + +static void nullpo_info_core(const char *file, int line, const char *func, + const char *fmt, va_list ap); + +/*====================================== + * Nullチェック 及び 情報出力 + *-------------------------------------- + */ +int nullpo_chk_f(const char *file, int line, const char *func, const void *target, + const char *fmt, ...) +{ + va_list ap; + + if (target != NULL) + return 0; + + va_start(ap, fmt); + nullpo_info_core(file, line, func, fmt, ap); + va_end(ap); + return 1; +} + +int nullpo_chk(const char *file, int line, const char *func, const void *target) +{ + if (target != NULL) + return 0; + + nullpo_info_core(file, line, func, NULL, NULL); + return 1; +} + + +/*====================================== + * nullpo情報出力(外部呼出し向けラッパ) + *-------------------------------------- + */ +void nullpo_info_f(const char *file, int line, const char *func, + const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + nullpo_info_core(file, line, func, fmt, ap); + va_end(ap); +} + +void nullpo_info(const char *file, int line, const char *func) +{ + nullpo_info_core(file, line, func, NULL, NULL); +} + + +/*====================================== + * nullpo情報出力(Main) + *-------------------------------------- + */ +static void nullpo_info_core(const char *file, int line, const char *func, + const char *fmt, va_list ap) +{ + if (file == NULL) + file = "??"; + + func = + func == NULL ? "unknown": + func[0] == '\0' ? "unknown": + func; + + printf("--- nullpo info --------------------------------------------\n"); + printf("%s:%d: in func `%s'\n", file, line, func); + if (fmt != NULL) + { + if (fmt[0] != '\0') + { + vprintf(fmt, ap); + + // 最後に改行したか確認 + if (fmt[strlen(fmt)-1] != '\n') + printf("\n"); + } + } + printf("--- end nullpo info ----------------------------------------\n"); + + // ここらでnullpoログをファイルに書き出せたら + // まとめて提出できるなと思っていたり。 +} diff --git a/src/common/nullpo.h b/src/common/nullpo.h new file mode 100644 index 0000000..2d33500 --- /dev/null +++ b/src/common/nullpo.h @@ -0,0 +1,222 @@ +#ifndef _NULLPO_H_ +#define _NULLPO_H_ + + +#define NULLPO_CHECK 1 + // 全体のスイッチを宣言しているヘッダがあれば + // そこに移動していただけると + + +#if __STDC_VERSION__ < 199901L +# if __GNUC__ >= 2 +# define __func__ __FUNCTION__ +# else +# define __func__ "" +# endif +#endif + +#ifdef LCCWIN32 +#define __attribute__(x) /* nothing */ +#endif + + +#define NLP_MARK __FILE__, __LINE__, __func__ + +/*---------------------------------------------------------------------------- + * Macros + *---------------------------------------------------------------------------- + */ +/*====================================== + * Nullチェック 及び 情報出力後 return + *・展開するとifとかreturn等が出るので + * 一行単体で使ってください。 + *・nullpo_ret(x = func()); + * のような使用法も想定しています。 + *-------------------------------------- + * nullpo_ret(t) + * 戻り値 0固定 + * [引数] + * t チェック対象 + *-------------------------------------- + * nullpo_retv(t) + * 戻り値 なし + * [引数] + * t チェック対象 + *-------------------------------------- + * nullpo_retr(ret, t) + * 戻り値 指定 + * [引数] + * ret return(ret); + * t チェック対象 + *-------------------------------------- + * nullpo_ret_f(t, fmt, ...) + * 詳細情報出力用 + * 戻り値 0 + * [引数] + * t チェック対象 + * fmt ... vprintfに渡される + * 備考や関係変数の書き出しなどに + *-------------------------------------- + * nullpo_retv_f(t, fmt, ...) + * 詳細情報出力用 + * 戻り値 なし + * [引数] + * t チェック対象 + * fmt ... vprintfに渡される + * 備考や関係変数の書き出しなどに + *-------------------------------------- + * nullpo_retr_f(ret, t, fmt, ...) + * 詳細情報出力用 + * 戻り値 指定 + * [引数] + * ret return(ret); + * t チェック対象 + * fmt ... vprintfに渡される + * 備考や関係変数の書き出しなどに + *-------------------------------------- + */ + +#if NULLPO_CHECK + +#define nullpo_ret(t) \ + if (nullpo_chk(NLP_MARK, (void *)(t))) {return(0);} + +#define nullpo_retv(t) \ + if (nullpo_chk(NLP_MARK, (void *)(t))) {return;} + +#define nullpo_retr(ret, t) \ + if (nullpo_chk(NLP_MARK, (void *)(t))) {return(ret);} + + +// 可変引数マクロに関する条件コンパイル +#if __STDC_VERSION__ >= 199901L +/* C99に対応 */ +#define nullpo_ret_f(t, fmt, ...) \ + if (nullpo_chk_f(NLP_MARK, (void *)(t), (fmt), __VA_ARGS__)) {return(0);} + +#define nullpo_retv_f(t, fmt, ...) \ + if (nullpo_chk_f(NLP_MARK, (void *)(t), (fmt), __VA_ARGS__)) {return;} + +#define nullpo_retr_f(ret, t, fmt, ...) \ + if (nullpo_chk_f(NLP_MARK, (void *)(t), (fmt), __VA_ARGS__)) {return(ret);} + +#elif __GNUC__ >= 2 +/* GCC用 */ +#define nullpo_ret_f(t, fmt, args...) \ + if (nullpo_chk_f(NLP_MARK, (void *)(t), (fmt), ## args)) {return(0);} + +#define nullpo_retv_f(t, fmt, args...) \ + if (nullpo_chk_f(NLP_MARK, (void *)(t), (fmt), ## args)) {return;} + +#define nullpo_retr_f(ret, t, fmt, args...) \ + if (nullpo_chk_f(NLP_MARK, (void *)(t), (fmt), ## args)) {return(ret);} + +#else + +/* その他の場合・・・ orz */ + +#endif + +#else /* NULLPO_CHECK */ +/* No Nullpo check */ + +// if((t)){;} +// 良い方法が思いつかなかったので・・・苦肉の策です。 +// 一応ワーニングは出ないはず + +#define nullpo_ret(t) if((t)){;} +#define nullpo_retv(t) if((t)){;} +#define nullpo_retr(ret, t) if((t)){;} + +// 可変引数マクロに関する条件コンパイル +#if __STDC_VERSION__ >= 199901L +/* C99に対応 */ +#define nullpo_ret_f(t, fmt, ...) if((t)){;} +#define nullpo_retv_f(t, fmt, ...) if((t)){;} +#define nullpo_retr_f(ret, t, fmt, ...) if((t)){;} + +#elif __GNUC__ >= 2 +/* GCC用 */ +#define nullpo_ret_f(t, fmt, args...) if((t)){;} +#define nullpo_retv_f(t, fmt, args...) if((t)){;} +#define nullpo_retr_f(ret, t, fmt, args...) if((t)){;} + +#else +/* その他の場合・・・ orz */ +#endif + +#endif /* NULLPO_CHECK */ + +/*---------------------------------------------------------------------------- + * Functions + *---------------------------------------------------------------------------- + */ +/*====================================== + * nullpo_chk + * Nullチェック 及び 情報出力 + * [引数] + * file __FILE__ + * line __LINE__ + * func __func__ (関数名) + * これらには NLP_MARK を使うとよい + * target チェック対象 + * [返り値] + * 0 OK + * 1 NULL + *-------------------------------------- + */ +int nullpo_chk(const char *file, int line, const char *func, const void *target); + + +/*====================================== + * nullpo_chk_f + * Nullチェック 及び 詳細な情報出力 + * [引数] + * file __FILE__ + * line __LINE__ + * func __func__ (関数名) + * これらには NLP_MARK を使うとよい + * target チェック対象 + * fmt ... vprintfに渡される + * 備考や関係変数の書き出しなどに + * [返り値] + * 0 OK + * 1 NULL + *-------------------------------------- + */ +int nullpo_chk_f(const char *file, int line, const char *func, const void *target, + const char *fmt, ...) + __attribute__((format(printf,5,6))); + + +/*====================================== + * nullpo_info + * nullpo情報出力 + * [引数] + * file __FILE__ + * line __LINE__ + * func __func__ (関数名) + * これらには NLP_MARK を使うとよい + *-------------------------------------- + */ +void nullpo_info(const char *file, int line, const char *func); + + +/*====================================== + * nullpo_info_f + * nullpo詳細情報出力 + * [引数] + * file __FILE__ + * line __LINE__ + * func __func__ (関数名) + * これらには NLP_MARK を使うとよい + * fmt ... vprintfに渡される + * 備考や関係変数の書き出しなどに + *-------------------------------------- + */ +void nullpo_info_f(const char *file, int line, const char *func, + const char *fmt, ...) + __attribute__((format(printf,4,5))); + + +#endif diff --git a/src/common/socket.c b/src/common/socket.c new file mode 100644 index 0000000..1711286 --- /dev/null +++ b/src/common/socket.c @@ -0,0 +1,439 @@ +// $Id: socket.c,v 1.1.1.1 2004/09/10 17:44:49 MagicalTux Exp $ +// original : core.c 2003/02/26 18:03:12 Rev 1.7 + +#include <stdio.h> +#include <stdlib.h> +#include <sys/types.h> + +#ifdef LCCWIN32 +#define WIN32_LEAN_AND_MEAN +#include <windows.h> +#include <winsock2.h> +#else +#include <sys/socket.h> +#include <netinet/in.h> +#include <netinet/tcp.h> +#include <sys/time.h> +#include <unistd.h> +#endif + +#include <fcntl.h> +#include <string.h> + +#include "mmo.h" // [Valaris] thanks to fov +#include "socket.h" +#include "utils.h" + +#ifdef MEMWATCH +#include "memwatch.h" +#endif + +fd_set readfds; +int fd_max; + +int rfifo_size = 65536; +int wfifo_size = 65536; + +struct socket_data *session[FD_SETSIZE]; + +static int null_parse(int fd); +static int (*default_func_parse)(int) = null_parse; + +/*====================================== + * CORE : Set function + *-------------------------------------- + */ +void set_defaultparse(int (*defaultparse)(int)) +{ + default_func_parse = defaultparse; +} + +/*====================================== + * CORE : Socket Sub Function + *-------------------------------------- + */ + +static int recv_to_fifo(int fd) +{ + int len; + + //printf("recv_to_fifo : %d %d\n",fd,session[fd]->eof); + if(session[fd]->eof) + return -1; + + +#ifdef LCCWIN32 + len = recv(fd,session[fd]->rdata+session[fd]->rdata_size, RFIFOSPACE(fd), 0); +#else + len=read(fd,session[fd]->rdata+session[fd]->rdata_size,RFIFOSPACE(fd)); +#endif + +// printf (":::RECEIVE:::\n"); +// dump(session[fd]->rdata, len); printf ("\n"); + + //{ int i; printf("recv %d : ",fd); for(i=0;i<len;i++){ printf("%02x ",RFIFOB(fd,session[fd]->rdata_size+i)); } printf("\n");} + if(len>0){ + session[fd]->rdata_size+=len; + } else if(len<=0){ + // value of connection is not necessary the same +// if (fd == 4) // Removed [Yor] +// printf("Char-Server Has Disconnected.\n"); +// else if (fd == 5) // Removed [Yor] +// printf("Attempt To Log In Successful.\n"); +// else if (fd == 7) // Removed [Yor] +// printf("Char-Server Has Disconnected.\n"); +// else if (fd == 8) // Removed [Valaris] +// printf("%s has logged off your server.\n",RFIFOP(fd,6)); // Removed [Valaris] + +// else if (fd != 8) // [Valaris] + printf("set eof : connection #%d\n", fd); + session[fd]->eof=1; + } + return 0; +} + +static int send_from_fifo(int fd) +{ + int len; + + //printf("send_from_fifo : %d\n",fd); + if(session[fd]->eof) + return -1; + +#ifdef LCCWIN32 + len = send(fd, session[fd]->wdata,session[fd]->wdata_size, 0); +#else + len=write(fd,session[fd]->wdata,session[fd]->wdata_size); +#endif + +// printf (":::SEND:::\n"); +// dump(session[fd]->wdata, len); printf ("\n"); + + //{ int i; printf("send %d : ",fd); for(i=0;i<len;i++){ printf("%02x ",session[fd]->wdata[i]); } printf("\n");} + if(len>0){ + if(len<session[fd]->wdata_size){ + memmove(session[fd]->wdata,session[fd]->wdata+len,session[fd]->wdata_size-len); + session[fd]->wdata_size-=len; + } else { + session[fd]->wdata_size=0; + } + } else { + printf("set eof :%d\n",fd); + session[fd]->eof=1; + } + return 0; +} + +static int null_parse(int fd) +{ + printf("null_parse : %d\n",fd); + RFIFOSKIP(fd,RFIFOREST(fd)); + return 0; +} + +/*====================================== + * CORE : Socket Function + *-------------------------------------- + */ + +static int connect_client(int listen_fd) +{ + int fd; + struct sockaddr_in client_address; + int len; + int result; + int yes = 1; // reuse fix + + //printf("connect_client : %d\n",listen_fd); + + len=sizeof(client_address); + + fd=accept(listen_fd,(struct sockaddr*)&client_address,&len); + if(fd_max<=fd) fd_max=fd+1; + +// setsockopt(fd,SOL_SOCKET,SO_REUSEADDR,NULL,0); + setsockopt(fd,SOL_SOCKET,SO_REUSEADDR,(char *)&yes,sizeof yes); // reuse fix +#ifdef SO_REUSEPORT +// setsockopt(fd,SOL_SOCKET,SO_REUSEPORT,NULL,0); + setsockopt(fd,SOL_SOCKET,SO_REUSEPORT,(char *)&yes,sizeof yes); //reuse fix +#endif +// setsockopt(fd,IPPROTO_TCP,TCP_NODELAY,NULL,0); + setsockopt(fd,IPPROTO_TCP,TCP_NODELAY,(char *)&yes,sizeof yes); // reuse fix + + if(fd==-1){ + perror("accept"); + } else { + FD_SET(fd,&readfds); + } + +#ifdef LCCWIN32 + { + unsigned long val = 1; + ioctlsocket(fd, FIONBIO, &val); + } +#else + result = fcntl(fd, F_SETFL, O_NONBLOCK); +#endif + + CREATE(session[fd], struct socket_data, 1); + CREATE(session[fd]->rdata, char, rfifo_size); + CREATE(session[fd]->wdata, char, wfifo_size); + + session[fd]->max_rdata = rfifo_size; + session[fd]->max_wdata = wfifo_size; + session[fd]->func_recv = recv_to_fifo; + session[fd]->func_send = send_from_fifo; + session[fd]->func_parse = default_func_parse; + session[fd]->client_addr = client_address; + + //printf("new_session : %d %d\n",fd,session[fd]->eof); + return fd; +} + +int make_listen_port(int port) +{ + struct sockaddr_in server_address; + int fd; + int result; + int yes = 1; // reuse fix + + fd = socket( AF_INET, SOCK_STREAM, 0 ); + if(fd_max<=fd) fd_max=fd+1; + +#ifdef LCCWIN32 + { + unsigned long val = 1; + ioctlsocket(fd, FIONBIO, &val); + } +#else + result = fcntl(fd, F_SETFL, O_NONBLOCK); +#endif + +// setsockopt(fd,SOL_SOCKET,SO_REUSEADDR,NULL,0); + setsockopt(fd,SOL_SOCKET,SO_REUSEADDR,(char *)&yes,sizeof yes); // reuse fix +#ifdef SO_REUSEPORT +// setsockopt(fd,SOL_SOCKET,SO_REUSEPORT,NULL,0); + setsockopt(fd,SOL_SOCKET,SO_REUSEPORT,(char *)&yes,sizeof yes); //reuse fix +#endif +// setsockopt(fd,IPPROTO_TCP,TCP_NODELAY,NULL,0); + setsockopt(fd,IPPROTO_TCP,TCP_NODELAY,(char *)&yes,sizeof yes); // reuse fix + + server_address.sin_family = AF_INET; + server_address.sin_addr.s_addr = htonl( INADDR_ANY ); + server_address.sin_port = htons(port); + + result = bind(fd, (struct sockaddr*)&server_address, sizeof(server_address)); + if( result == -1 ) { + perror("bind"); + exit(1); + } + result = listen( fd, 5 ); + if( result == -1 ) { /* error */ + perror("listen"); + exit(1); + } + + FD_SET(fd, &readfds ); + + CREATE(session[fd], struct socket_data, 1); + + if(session[fd]==NULL){ + printf("out of memory : make_listen_port\n"); + exit(1); + } + memset(session[fd],0,sizeof(*session[fd])); + session[fd]->func_recv = connect_client; + + return fd; +} + +int make_connection(long ip,int port) +{ + struct sockaddr_in server_address; + int fd; + int result; + int yes = 1; // reuse fix + + fd = socket( AF_INET, SOCK_STREAM, 0 ); + if(fd_max<=fd) fd_max=fd+1; +// setsockopt(fd,SOL_SOCKET,SO_REUSEADDR,NULL,0); + setsockopt(fd,SOL_SOCKET,SO_REUSEADDR,(char *)&yes,sizeof yes); // reuse fix +#ifdef SO_REUSEPORT +// setsockopt(fd,SOL_SOCKET,SO_REUSEPORT,NULL,0); + setsockopt(fd,SOL_SOCKET,SO_REUSEPORT,(char *)&yes,sizeof yes); //reuse fix +#endif +// setsockopt(fd,IPPROTO_TCP,TCP_NODELAY,NULL,0); + setsockopt(fd,IPPROTO_TCP,TCP_NODELAY,(char *)&yes,sizeof yes); // reuse fix + + server_address.sin_family = AF_INET; + server_address.sin_addr.s_addr = ip; + server_address.sin_port = htons(port); + +#ifdef LCCWIN32 + { + unsigned long val = 1; + ioctlsocket(fd, FIONBIO, &val); + } +#else + result = fcntl(fd, F_SETFL, O_NONBLOCK); +#endif + + result = connect(fd, (struct sockaddr *)(&server_address),sizeof(struct sockaddr_in)); + + FD_SET(fd,&readfds); + + CREATE(session[fd], struct socket_data, 1); + CREATE(session[fd]->rdata, char, rfifo_size); + CREATE(session[fd]->wdata, char, wfifo_size); + + session[fd]->max_rdata = rfifo_size; + session[fd]->max_wdata = wfifo_size; + session[fd]->func_recv = recv_to_fifo; + session[fd]->func_send = send_from_fifo; + session[fd]->func_parse = default_func_parse; + + return fd; +} + +int delete_session(int fd) +{ + if(fd<0 || fd>=FD_SETSIZE) + return -1; + FD_CLR(fd,&readfds); + if(session[fd]){ + if(session[fd]->rdata) + free(session[fd]->rdata); + if(session[fd]->wdata) + free(session[fd]->wdata); + if(session[fd]->session_data) + free(session[fd]->session_data); + free(session[fd]); + } + session[fd]=NULL; + //printf("delete_session:%d\n",fd); + return 0; +} + +int realloc_fifo(int fd,int rfifo_size,int wfifo_size) +{ + struct socket_data *s=session[fd]; + if( s->max_rdata != rfifo_size && s->rdata_size < rfifo_size){ + RECREATE(s->rdata, char, rfifo_size); + s->max_rdata = rfifo_size; + } + if( s->max_wdata != wfifo_size && s->wdata_size < wfifo_size){ + RECREATE(s->wdata, char, wfifo_size); + s->max_wdata = wfifo_size; + } + return 0; +} + +int WFIFOSET(int fd,int len) +{ + struct socket_data *s=session[fd]; + if( s->wdata_size+len+16384 > s->max_wdata ){ + realloc_fifo(fd,s->max_rdata, s->max_wdata <<1 ); + printf("socket: %d wdata expanded to %d bytes.\n",fd, s->max_wdata); + } + s->wdata_size=(s->wdata_size+(len)+2048 < s->max_wdata) ? + s->wdata_size+len : (printf("socket: %d wdata lost !!\n",fd),s->wdata_size); + return 0; +} + +int do_sendrecv(int next) +{ + fd_set rfd,wfd; + struct timeval timeout; + int ret,i; + + rfd=readfds; + FD_ZERO(&wfd); + for(i=0;i<fd_max;i++){ + if(!session[i] && FD_ISSET(i,&readfds)){ + printf("force clr fds %d\n",i); + FD_CLR(i,&readfds); + continue; + } + if(!session[i]) + continue; + if(session[i]->wdata_size) + FD_SET(i,&wfd); + } + timeout.tv_sec = next/1000; + timeout.tv_usec = next%1000*1000; + ret = select(fd_max,&rfd,&wfd,NULL,&timeout); + if(ret<=0) + return 0; + for(i=0;i<fd_max;i++){ + if(!session[i]) + continue; + if(FD_ISSET(i,&wfd)){ + //printf("write:%d\n",i); + if(session[i]->func_send) + //send_from_fifo(i); + session[i]->func_send(i); + } + if(FD_ISSET(i,&rfd)){ + //printf("read:%d\n",i); + if(session[i]->func_recv) + //recv_to_fifo(i); + session[i]->func_recv(i); + } + } + return 0; +} + +int do_parsepacket(void) +{ + int i; + for(i=0;i<fd_max;i++){ + if(!session[i]) + continue; + if(session[i]->rdata_size==0 && session[i]->eof==0) + continue; + if(session[i]->func_parse){ + session[i]->func_parse(i); + if(!session[i]) + continue; + } + RFIFOFLUSH(i); + } + return 0; +} + +void do_socket(void) +{ + FD_ZERO(&readfds); +} + +int RFIFOSKIP(int fd,int len) +{ + struct socket_data *s=session[fd]; + + if (s->rdata_size-s->rdata_pos-len<0) { + fprintf(stderr,"too many skip\n"); + exit(1); + } + + s->rdata_pos = s->rdata_pos+len; + + return 0; +} + + +int Net_Init(void) +{ + #ifdef LCCWIN32 + /* Start up the windows networking */ + WORD version_wanted = MAKEWORD(1,1); + WSADATA wsaData; + + if ( WSAStartup(version_wanted, &wsaData) != 0 ) { + printf("SYSERR: WinSock not available!\n"); + exit(1); + } + #endif + + return(0); +} + diff --git a/src/common/socket.h b/src/common/socket.h new file mode 100644 index 0000000..fe06e40 --- /dev/null +++ b/src/common/socket.h @@ -0,0 +1,96 @@ +// original : core.h 2003/03/14 11:55:25 Rev 1.4 + +#ifndef _SOCKET_H_ +#define _SOCKET_H_ + +#include <stdio.h> + +#ifdef LCCWIN32 +#include <winsock.h> +#else +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#endif + +// define declaration + +#define RFIFOP(fd,pos) (session[fd]->rdata+session[fd]->rdata_pos+(pos)) +#define RFIFOB(fd,pos) (*(unsigned char*)(session[fd]->rdata+session[fd]->rdata_pos+(pos))) +#define RFIFOW(fd,pos) (*(unsigned short*)(session[fd]->rdata+session[fd]->rdata_pos+(pos))) +#define RFIFOL(fd,pos) (*(unsigned int*)(session[fd]->rdata+session[fd]->rdata_pos+(pos))) +//#define RFIFOSKIP(fd,len) ((session[fd]->rdata_size-session[fd]->rdata_pos-(len)<0) ? (fprintf(stderr,"too many skip\n"),exit(1)) : (session[fd]->rdata_pos+=(len))) +#define RFIFOREST(fd) (session[fd]->rdata_size-session[fd]->rdata_pos) +#define RFIFOFLUSH(fd) (memmove(session[fd]->rdata,RFIFOP(fd,0),RFIFOREST(fd)),session[fd]->rdata_size=RFIFOREST(fd),session[fd]->rdata_pos=0) +#define RFIFOSPACE(fd) (session[fd]->max_rdata-session[fd]->rdata_size) +#define RBUFP(p,pos) (((unsigned char*)(p))+(pos)) +#define RBUFB(p,pos) (*(unsigned char*)RBUFP((p),(pos))) +#define RBUFW(p,pos) (*(unsigned short*)RBUFP((p),(pos))) +#define RBUFL(p,pos) (*(unsigned int*)RBUFP((p),(pos))) + +#define WFIFOSPACE(fd) (session[fd]->max_wdata-session[fd]->wdata_size) +#define WFIFOP(fd,pos) (session[fd]->wdata+session[fd]->wdata_size+(pos)) +#define WFIFOB(fd,pos) (*(unsigned char*)(session[fd]->wdata+session[fd]->wdata_size+(pos))) +#define WFIFOW(fd,pos) (*(unsigned short*)(session[fd]->wdata+session[fd]->wdata_size+(pos))) +#define WFIFOL(fd,pos) (*(unsigned int*)(session[fd]->wdata+session[fd]->wdata_size+(pos))) +// use function instead of macro. +//#define WFIFOSET(fd,len) (session[fd]->wdata_size = (session[fd]->wdata_size+(len)+2048 < session[fd]->max_wdata) ? session[fd]->wdata_size+len : session[fd]->wdata_size) +#define WBUFP(p,pos) (((unsigned char*)(p))+(pos)) +#define WBUFB(p,pos) (*(unsigned char*)WBUFP((p),(pos))) +#define WBUFW(p,pos) (*(unsigned short*)WBUFP((p),(pos))) +#define WBUFL(p,pos) (*(unsigned int*)WBUFP((p),(pos))) + +#ifdef __INTERIX +#define FD_SETSIZE 4096 +#endif // __INTERIX + + +/* Removed Cygwin FD_SETSIZE declarations, now are directly passed on to the compiler through Makefile [Valaris] */ + +// Struct declaration + +struct socket_data{ + int eof; + unsigned char *rdata,*wdata; + int max_rdata,max_wdata; + int rdata_size,wdata_size; + int rdata_pos; + struct sockaddr_in client_addr; + int (*func_recv)(int); + int (*func_send)(int); + int (*func_parse)(int); + void* session_data; +}; + +// Data prototype declaration + +#ifdef LCCWIN32 + + #undef FD_SETSIZE + #define FD_SETSIZE 4096 + +#endif + +extern struct socket_data *session[FD_SETSIZE]; + +extern int rfifo_size,wfifo_size; +extern int fd_max; + +// Function prototype declaration + +int make_listen_port(int); +int make_connection(long,int); +int delete_session(int); +int realloc_fifo(int fd,int rfifo_size,int wfifo_size); +int WFIFOSET(int fd,int len); +int RFIFOSKIP(int fd,int len); + +int do_sendrecv(int next); +int do_parsepacket(void); +void do_socket(void); + +void set_defaultparse(int (*defaultparse)(int)); + +int Net_Init(void); + +#endif // _SOCKET_H_ diff --git a/src/common/timer.c b/src/common/timer.c new file mode 100644 index 0000000..8193ff9 --- /dev/null +++ b/src/common/timer.c @@ -0,0 +1,312 @@ +// $Id: timer.c,v 1.1.1.1 2004/09/10 17:44:49 MagicalTux Exp $ +// original : core.c 2003/02/26 18:03:12 Rev 1.7 + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> + +#ifdef LCCWIN32 +#include <winsock.h> +#else +#include <sys/socket.h> +#include <sys/time.h> +#endif + +#include "timer.h" +#include "utils.h" + +#ifdef MEMWATCH +#include "memwatch.h" +#endif + +static struct TimerData* timer_data; +static int timer_data_max,timer_data_num; +static int* free_timer_list; +static int free_timer_list_max, free_timer_list_pos; + +static int timer_heap_max; +static int* timer_heap = NULL; + +// for debug +struct timer_func_list { + int (*func)(int,unsigned int,int,int); + struct timer_func_list* next; + char* name; +}; +static struct timer_func_list* tfl_root; + +#if defined(LCCWIN32) +void gettimeofday(struct timeval *t, struct timezone *dummy) +{ + DWORD millisec = GetTickCount(); + + t->tv_sec = (int) (millisec / 1000); + t->tv_usec = (millisec % 1000) * 1000; +} + +#endif + + +// +int add_timer_func_list(int (*func)(int,unsigned int,int,int),char* name) +{ + struct timer_func_list* tfl; + + CREATE(tfl, struct timer_func_list, 1); + CREATE(tfl->name, char, strlen(name) + 1); + + tfl->next = tfl_root; + tfl->func = func; + strcpy(tfl->name,name); + tfl_root = tfl; + + return 0; +} + +char* search_timer_func_list(int (*func)(int,unsigned int,int,int)) +{ + struct timer_func_list* tfl; + for(tfl = tfl_root;tfl;tfl = tfl->next) { + if (func == tfl->func) + return tfl->name; + } + return "???"; +} + +/*---------------------------- + * Get tick time + *----------------------------*/ +static unsigned int gettick_cache; +static int gettick_count; +unsigned int gettick_nocache(void) +{ + struct timeval tval; + gettimeofday(&tval,NULL); + gettick_count = 256; + return gettick_cache = tval.tv_sec * 1000 + tval.tv_usec/1000; +} + +unsigned int gettick(void) +{ + gettick_count--; + if (gettick_count<0) + return gettick_nocache(); + return gettick_cache; +} + +/*====================================== + * CORE : Timer Heap + *-------------------------------------- + */ +static void push_timer_heap(int index) +{ + int i, h; + + if (timer_heap == NULL || timer_heap[0] + 1 >= timer_heap_max) { + int first = timer_heap == NULL; + + timer_heap_max += 256; + RECREATE(timer_heap, int, timer_heap_max); + memset(timer_heap + (timer_heap_max - 256), 0, sizeof(int) * 256); + if (first) + timer_heap[0] = 0; + } + + timer_heap[0]++; + + for (h = timer_heap[0]-1, i = (h - 1) / 2; + h > 0 && DIFF_TICK(timer_data[index].tick, + timer_data[timer_heap[i + 1]].tick) < 0; + i = (h - 1) / 2) { + timer_heap[h + 1] = timer_heap[i + 1]; + h = i; + } + timer_heap[h + 1] = index; +} + +static int top_timer_heap() +{ + if (timer_heap == NULL || timer_heap[0] <= 0) + return -1; + + return timer_heap[1]; +} + +static int pop_timer_heap() +{ + int i,h,k; + int ret,last; + + if (timer_heap == NULL || timer_heap[0] <= 0) + return -1; + ret = timer_heap[1]; + last = timer_heap[timer_heap[0]]; + timer_heap[0]--; + + for(h = 0,k = 2;k<timer_heap[0];k = k * 2 + 2) { + if (DIFF_TICK(timer_data[timer_heap[k + 1]].tick , timer_data[timer_heap[k]].tick)>0) + k--; + timer_heap[h + 1] = timer_heap[k + 1], h = k; + } + if (k == timer_heap[0]) + timer_heap[h + 1] = timer_heap[k], h = k-1; + + for(i = (h-1)/2; + h>0 && DIFF_TICK(timer_data[timer_heap[i + 1]].tick , timer_data[last].tick)>0; + i = (h-1)/2) { + timer_heap[h + 1] = timer_heap[i + 1],h = i; + } + timer_heap[h + 1] = last; + + return ret; +} + +int add_timer(unsigned int tick,int (*func)(int,unsigned int,int,int),int id,int data) +{ + struct TimerData* td; + int i; + + if (free_timer_list_pos) { + do { + i = free_timer_list[--free_timer_list_pos]; + } while(i >= timer_data_num && free_timer_list_pos > 0); + } else + i = timer_data_num; + if (i >= timer_data_num) + for (i = timer_data_num;i<timer_data_max && timer_data[i].type; i++); + if (i >= timer_data_num && i >= timer_data_max) { + int j; + if (timer_data_max == 0) { + timer_data_max = 256; + CREATE(timer_data, struct TimerData, timer_data_max); + } else { + timer_data_max += 256; + RECREATE(timer_data, struct TimerData, timer_data_max); + if (timer_data == NULL) { + printf("out of memory : add_timer timer_data\n"); + exit(1); + } + memset(timer_data + (timer_data_max - 256), 0, + sizeof(struct TimerData) * 256); + } + for(j = timer_data_max-256;j<timer_data_max; j++) + timer_data[j].type = 0; + } + td = &timer_data[i]; + td->tick = tick; + td->func = func; + td->id = id; + td->data = data; + td->type = TIMER_ONCE_AUTODEL; + td->interval = 1000; + push_timer_heap(i); + if (i >= timer_data_num) + timer_data_num = i + 1; + return i; +} + +int add_timer_interval(unsigned int tick,int (*func)(int,unsigned int,int,int),int id,int data,int interval) +{ + int tid; + tid = add_timer(tick,func,id,data); + timer_data[tid].type = TIMER_INTERVAL; + timer_data[tid].interval = interval; + return tid; +} + +int delete_timer(int id,int (*func)(int,unsigned int,int,int)) +{ + if (id <= 0 || id >= timer_data_num) { + printf("delete_timer error : no such timer %d\n", id); + return -1; + } + if (timer_data[id].func != func) { + printf("delete_timer error : function dismatch %08x(%s) != %08x(%s)\n", + (int)timer_data[id].func, + search_timer_func_list(timer_data[id].func), + (int)func, + search_timer_func_list(func)); + return -2; + } + // そのうち消えるにまかせる + timer_data[id].func = NULL; + timer_data[id].type = TIMER_ONCE_AUTODEL; + timer_data[id].tick -= 60 * 60 * 1000; + return 0; +} + +int addtick_timer(int tid,unsigned int tick) +{ + return timer_data[tid].tick += tick; +} +struct TimerData* get_timer(int tid) +{ + return &timer_data[tid]; +} + + +int do_timer(unsigned int tick) +{ + int i,nextmin = 1000; + +#if 0 + static int disp_tick = 0; + if (DIFF_TICK(disp_tick,tick)<-5000 || DIFF_TICK(disp_tick,tick)>5000) { + printf("timer %d(%d + %d)\n",timer_data_num,timer_heap[0],free_timer_list_pos); + disp_tick = tick; + } +#endif + + while((i = top_timer_heap()) >= 0) { + if (DIFF_TICK(timer_data[i].tick , tick)>0) { + nextmin = DIFF_TICK(timer_data[i].tick , tick); + break; + } + pop_timer_heap(); + timer_data[i].type |= TIMER_REMOVE_HEAP; + if (timer_data[i].func) { + if (DIFF_TICK(timer_data[i].tick , tick) < -1000) { + // 1秒以上の大幅な遅延が発生しているので、 + // timer処理タイミングを現在値とする事で + // 呼び出し時タイミング(引数のtick)相対で処理してる + // timer関数の次回処理タイミングを遅らせる + timer_data[i].func(i,tick,timer_data[i].id,timer_data[i].data); + } else { + timer_data[i].func(i,timer_data[i].tick,timer_data[i].id,timer_data[i].data); + } + } + if (timer_data[i].type&TIMER_REMOVE_HEAP) { + switch(timer_data[i].type & ~TIMER_REMOVE_HEAP) { + case TIMER_ONCE_AUTODEL: + timer_data[i].type = 0; + if (free_timer_list_pos >= free_timer_list_max) { + free_timer_list_max += 256; + RECREATE(free_timer_list, int, free_timer_list_max); + memset(free_timer_list + (free_timer_list_max - 256), 0, + 256 * sizeof(free_timer_list[0])); + } + free_timer_list[free_timer_list_pos++] = i; + break; + case TIMER_INTERVAL: + if (DIFF_TICK(timer_data[i].tick , tick) < -1000) { + timer_data[i].tick = tick + timer_data[i].interval; + } else { + timer_data[i].tick += timer_data[i].interval; + } + timer_data[i].type &= ~TIMER_REMOVE_HEAP; + push_timer_heap(i); + break; + } + } + } + + if (nextmin<10) + nextmin = 10; + return nextmin; +} + +void timer_final() +{ + free(timer_data); +} diff --git a/src/common/timer.h b/src/common/timer.h new file mode 100644 index 0000000..f6fc5c8 --- /dev/null +++ b/src/common/timer.h @@ -0,0 +1,45 @@ +// original : core.h 2003/03/14 11:55:25 Rev 1.4 + +#ifndef _TIMER_H_ +#define _TIMER_H_ + +#define BASE_TICK 5 + +#define TIMER_ONCE_AUTODEL 1 +#define TIMER_INTERVAL 2 +#define TIMER_REMOVE_HEAP 16 + +#define DIFF_TICK(a,b) ((int)((a)-(b))) + +// Struct declaration + +struct TimerData { + unsigned int tick; + int (*func)(int,unsigned int,int,int); + int id; + int data; + int type; + int interval; + int heap_pos; +}; + +// Function prototype declaration + +unsigned int gettick_nocache(void); +unsigned int gettick(void); + +int add_timer(unsigned int,int (*)(int,unsigned int,int,int),int,int); +int add_timer_interval(unsigned int,int (*)(int,unsigned int,int,int),int,int,int); +int delete_timer(int,int (*)(int,unsigned int,int,int)); + +int addtick_timer(int tid,unsigned int tick); +struct TimerData *get_timer(int tid); + +int do_timer(unsigned int tick); + +int add_timer_func_list(int (*)(int,unsigned int,int,int),char*); +char* search_timer_func_list(int (*)(int,unsigned int,int,int)); + +extern void timer_final(); + +#endif // _TIMER_H_ diff --git a/src/common/utils.c b/src/common/utils.c new file mode 100644 index 0000000..b0ecd26 --- /dev/null +++ b/src/common/utils.c @@ -0,0 +1,108 @@ +#include <string.h> +#include "utils.h" +#include <stdio.h> + +void dump(unsigned char *buffer, int num) +{ + int icnt,jcnt; + + printf(" Hex ASCII\n"); + printf(" ----------------------------------------------- ----------------"); + + for (icnt=0;icnt<num;icnt+=16) { + printf("\n%p ",&buffer[icnt]); + for (jcnt=icnt;jcnt<icnt+16;++jcnt) { + if (jcnt < num) { + printf("%02hX ",buffer[jcnt]); + } else + printf(" "); + } + + printf(" | "); + + for (jcnt=icnt;jcnt<icnt+16;++jcnt) { + if (jcnt < num) { + if (buffer[jcnt] > 31 && buffer[jcnt] < 127) + printf("%c",buffer[jcnt]); + else + printf("."); + } else + printf(" "); + } + } + printf("\n"); +} + + +#ifdef LCCWIN32 +char *rindex(char *str, char c) +{ + char *sptr; + + sptr = str; + while(*sptr) + ++sptr; + if (c == '\0') + return(sptr); + while(str != sptr) + if (*sptr-- == c) + return(++sptr); + return(NULL); +} + +int strcasecmp(const char *arg1, const char *arg2) +{ + int chk, i; + + if (arg1 == NULL || arg2 == NULL) { + printf("SYSERR: str_cmp() passed a NULL pointer, %p or %p.\n", arg1, arg2); + return (0); + } + + for (i = 0; arg1[i] || arg2[i]; i++) + if ((chk = LOWER(arg1[i]) - LOWER(arg2[i])) != 0) + return (chk); /* not equal */ + + return (0); +} + +int strncasecmp(const char *arg1, const char *arg2, int n) +{ + int chk, i; + + if (arg1 == NULL || arg2 == NULL) { + printf("SYSERR: strn_cmp() passed a NULL pointer, %p or %p.\n", arg1, arg2); + return (0); + } + + for (i = 0; (arg1[i] || arg2[i]) && (n > 0); i++, n--) + if ((chk = LOWER(arg1[i]) - LOWER(arg2[i])) != 0) + return (chk); /* not equal */ + + return (0); +} + +void str_upper(char *name) +{ + + int len = strlen(name); + while (len--) { + if (*name >= 'a' && *name <= 'z') + *name -= ('a' - 'A'); + name++; + } +} + +void str_lower(char *name) +{ + int len = strlen(name); + + while (len--) { + if (*name >= 'A' && *name <= 'Z') + *name += ('a' - 'A'); + name++; + } +} + +#endif + diff --git a/src/common/utils.h b/src/common/utils.h new file mode 100644 index 0000000..29463cf --- /dev/null +++ b/src/common/utils.h @@ -0,0 +1,33 @@ + +#ifndef NULL +#define NULL (void *)0 +#endif + +#define LOWER(c) (((c)>='A' && (c) <= 'Z') ? ((c)+('a'-'A')) : (c)) +#define UPPER(c) (((c)>='a' && (c) <= 'z') ? ((c)+('A'-'a')) : (c) ) + +/* strcasecmp -> stricmp -> str_cmp */ + + +#ifdef LCCWIN32 + int strcasecmp(const char *arg1, const char *arg2); + int strncasecmp(const char *arg1, const char *arg2, int n); + void str_upper(char *name); + void str_lower(char *name); + char *rindex(char *str, char c); +#endif + + + void dump(unsigned char *buffer, int num); + + +#define CREATE(result, type, number) do {\ + if ((number) * sizeof(type) <= 0) \ + printf("SYSERR: Zero bytes or less requested at %s:%d.\n", __FILE__, __LINE__); \ + if (!((result) = (type *) calloc ((number), sizeof(type)))) \ + { perror("SYSERR: malloc failure"); abort(); } } while(0) + +#define RECREATE(result,type,number) do {\ + if (!((result) = (type *) realloc ((result), sizeof(type) * (number))))\ + { printf("SYSERR: realloc failure"); abort(); } } while(0) + diff --git a/src/common/version.h b/src/common/version.h new file mode 100644 index 0000000..e33e2b3 --- /dev/null +++ b/src/common/version.h @@ -0,0 +1,27 @@ +// $Id: version.h,v 1.2 2004/09/22 09:49:06 PoW Exp $ +#ifndef _VERSION_H_ +#define _VERSION_H_ + +#define ATHENA_MAJOR_VERSION 1 // Major Version +#define ATHENA_MINOR_VERSION 0 // Minor Version +#define ATHENA_REVISION 0 // Revision + +#define ATHENA_RELEASE_FLAG 1 // 1=Develop,0=Stable +#define ATHENA_OFFICIAL_FLAG 1 // 1=Mod,0=Official + +#define ATHENA_SERVER_LOGIN 1 // login server +#define ATHENA_SERVER_CHAR 2 // char server +#define ATHENA_SERVER_INTER 4 // inter server +#define ATHENA_SERVER_MAP 8 // map server + +// ATHENA_MOD_VERSIONはパッチ番号です。 +// これは無理に変えなくても気が向いたら変える程度の扱いで。 +// (毎回アップロードの度に変更するのも面倒と思われるし、そもそも +// この項目を参照する人がいるかどうかで疑問だから。) +// その程度の扱いなので、サーバーに問い合わせる側も、あくまで目安程度の扱いで +// あんまり信用しないこと。 +// 鯖snapshotの時や、大きな変更があった場合は設定してほしいです。 +// C言語の仕様上、最初に0を付けると8進数になるので間違えないで下さい。 +#define ATHENA_MOD_VERSION 1052 // mod version (patch No.) + +#endif diff --git a/src/ladmin/GNUmakefile b/src/ladmin/GNUmakefile new file mode 100644 index 0000000..ce19d9d --- /dev/null +++ b/src/ladmin/GNUmakefile @@ -0,0 +1,14 @@ +all: ladmin
+txt: ladmin
+sql: ladmin
+
+COMMON_OBJ = ../common/core.o ../common/socket.o ../common/timer.o ../common/db.o ../common/malloc.o
+COMMON_H = ../common/core.h ../common/socket.h ../common/timer.h ../common/mmo.h ../common/version.h ../common/db.h ../common/malloc.h
+
+ladmin: ladmin.o md5calc.o $(COMMON_OBJ)
+ $(CC) -o ../../$@ ladmin.o md5calc.o $(COMMON_OBJ)
+ladmin.o: ladmin.c ladmin.h md5calc.h $(COMMON_H)
+md5calc.o: md5calc.c md5calc.h
+
+clean:
+ rm -f *.o ../../ladmin
diff --git a/src/ladmin/Makefile b/src/ladmin/Makefile new file mode 100644 index 0000000..ce19d9d --- /dev/null +++ b/src/ladmin/Makefile @@ -0,0 +1,14 @@ +all: ladmin
+txt: ladmin
+sql: ladmin
+
+COMMON_OBJ = ../common/core.o ../common/socket.o ../common/timer.o ../common/db.o ../common/malloc.o
+COMMON_H = ../common/core.h ../common/socket.h ../common/timer.h ../common/mmo.h ../common/version.h ../common/db.h ../common/malloc.h
+
+ladmin: ladmin.o md5calc.o $(COMMON_OBJ)
+ $(CC) -o ../../$@ ladmin.o md5calc.o $(COMMON_OBJ)
+ladmin.o: ladmin.c ladmin.h md5calc.h $(COMMON_H)
+md5calc.o: md5calc.c md5calc.h
+
+clean:
+ rm -f *.o ../../ladmin
diff --git a/src/ladmin/ladmin.c b/src/ladmin/ladmin.c new file mode 100644 index 0000000..497f3bd --- /dev/null +++ b/src/ladmin/ladmin.c @@ -0,0 +1,4385 @@ +// $Id: ladmin.c,v 1.1.1.1 2004/09/10 17:26:52 MagicalTux Exp $ +/////////////////////////////////////////////////////////////////////////// +// EAthena login-server remote administration tool +// Ladamin in C by [Yor] +// if you modify this software, modify ladmin in tool too. +/////////////////////////////////////////////////////////////////////////// + +#include <sys/types.h> +#include <sys/socket.h> +#include <stdio.h> +#include <stdlib.h> +#include <netinet/in.h> +#include <sys/time.h> // gettimeofday +#include <time.h> +#include <sys/ioctl.h> +#include <unistd.h> // close +#include <signal.h> +#include <fcntl.h> +#include <string.h> // str* +#include <arpa/inet.h> // inet_addr +#include <netdb.h> // gethostbyname +#include <stdarg.h> // valist +#include <ctype.h> // tolower + +#include "core.h" +#include "socket.h" +#include "ladmin.h" +#include "version.h" +#include "mmo.h" + +#ifdef PASSWORDENC +#include "md5calc.h" +#endif + +#ifdef MEMWATCH +#include "memwatch.h" +#endif + +//-------------------------------INSTRUCTIONS------------------------------ +// Set the variables below: +// IP of the login server. +// Port where the login-server listens incoming packets. +// Password of administration (same of config_athena.conf). +// Displayed language of the sofware (if not correct, english is used). +// IMPORTANT: +// Be sure that you authorize remote administration in login-server +// (see login_athena.conf, 'admin_state' parameter) +//------------------------------------------------------------------------- +char loginserverip[16] = "127.0.0.1"; // IP of login-server +int loginserverport = 6900; // Port of login-server +char loginserveradminpassword[24] = "admin"; // Administration password +#ifdef PASSWORDENC +int passenc = 2; // Encoding type of the password +#else +int passenc = 0; // Encoding type of the password +#endif +char defaultlanguage = 'E'; // Default language (F: Fran軋is/E: English) + // (if it's not 'F', default is English) +char ladmin_log_filename[1024] = "log/ladmin.log"; +char date_format[32] = "%Y-%m-%d %H:%M:%S"; +//------------------------------------------------------------------------- +// LIST of COMMANDs that you can type at the prompt: +// To use these commands you can only type only the first letters. +// You must type a minimum of letters (you can not type 'a', +// because ladmin doesn't know if it's for 'aide' or for 'add') +// <Example> q <= quit, li <= list, pass <= passwd, etc. +// +// Note: every time you must give a account_name, you can use "" or '' (spaces can be included) +// +// aide/help/? +// Display the description of the commands +// aide/help/? [command] +// Display the description of the specified command +// +// add <account_name> <sex> <password> +// Create an account with the default email (a@a.com). +// Concerning the sex, only the first letter is used (F or M). +// The e-mail is set to a@a.com (default e-mail). It's like to have no e-mail. +// When the password is omitted, the input is done without displaying of the pressed keys. +// <example> add testname Male testpass +// +// ban/banish yyyy/mm/dd hh:mm:ss <account name> +// Changes the final date of a banishment of an account. +// Like banset, but <account name> is at end. +// +// banadd <account_name> <modifier> +// Adds or substracts time from the final date of a banishment of an account. +// Modifier is done as follows: +// Adjustment value (-1, 1, +1, etc...) +// Modified element: +// a or y: year +// m: month +// j or d: day +// h: hour +// mn: minute +// s: second +// <example> banadd testname +1m-2mn1s-6y +// this example adds 1 month and 1 second, and substracts 2 minutes and 6 years at the same time. +// NOTE: If you modify the final date of a non-banished account, +// you fix the final date to (actual time +- adjustments) +// +// banset <account_name> yyyy/mm/dd [hh:mm:ss] +// Changes the final date of a banishment of an account. +// Default time [hh:mm:ss]: 23:59:59. +// banset <account_name> 0 +// Set a non-banished account (0 = unbanished). +// +// block <account name> +// Set state 5 (You have been blocked by the GM Team) to an account. +// Like state <account name> 5. +// +// check <account_name> <password> +// Check the validity of a password for an account +// NOTE: Server will never sends back a password. +// It's the only method you have to know if a password is correct. +// The other method is to have a ('physical') access to the accounts file. +// +// create <account_name> <sex> <email> <password> +// Like the 'add' command, but with e-mail moreover. +// <example> create testname Male my@mail.com testpass +// +// del <account name> +// Remove an account. +// This order requires confirmation. After confirmation, the account is deleted. +// +// email <account_name> <email> +// Modify the e-mail of an account. +// +// getcount +// Give the number of players online on all char-servers. +// +// gm <account_name> [GM_level] +// Modify the GM level of an account. +// Default value remove GM level (GM level = 0). +// <example> gm testname 80 +// +// id <account name> +// Give the id of an account. +// +// info <account_id> +// Display complete information of an account. +// +// kami <message> +// Sends a broadcast message on all map-server (in yellow). +// kamib <message> +// Sends a broadcast message on all map-server (in blue). +// +// language <language> +// Change the language of displaying. +// +// list/ls [start_id [end_id]] +// Display a list of accounts. +// 'start_id', 'end_id': indicate end and start identifiers. +// Research by name is not possible with this command. +// <example> list 10 9999999 +// +// listBan/lsBan [start_id [end_id]] +// Like list/ls, but only for accounts with state or banished +// +// listGM/lsGM [start_id [end_id]] +// Like list/ls, but only for GM accounts +// +// listOK/lsOK [start_id [end_id]] +// Like list/ls, but only for accounts without state and not banished +// +// memo <account_name> <memo> +// Modify the memo of an account. +// 'memo': it can have until 253 characters (with spaces or not). +// +// name <account_id> +// Give the name of an account. +// +// passwd <account_name> <new_password> +// Change the password of an account. +// When new password is omitted, the input is done without displaying of the pressed keys. +// +// quit/end/exit +// End of the program of administration +// +// reloadGM +// Reload GM configuration file +// +// search <expression> +// Seek accounts. +// Displays the accounts whose names correspond. +// search -r/-e/--expr/--regex <expression> +// Seek accounts by regular expression. +// Displays the accounts whose names correspond. +// +// sex <account_name> <sex> +// Modify the sex of an account. +// <example> sex testname Male +// +// state <account_name> <new_state> <error_message_#7> +// Change the state of an account. +// 'new_state': state is the state of the packet 0x006a + 1. The possibilities are: +// 0 = Account ok 6 = Your Game's EXE file is not the latest version +// 1 = Unregistered ID 7 = You are Prohibited to log in until %s +// 2 = Incorrect Password 8 = Server is jammed due to over populated +// 3 = This ID is expired 9 = No MSG +// 4 = Rejected from Server 100 = This ID has been totally erased +// 5 = You have been blocked by the GM Team +// all other values are 'No MSG', then use state 9 please. +// 'error_message_#7': message of the code error 6 = Your are Prohibited to log in until %s (packet 0x006a) +// +// timeadd <account_name> <modifier> +// Adds or substracts time from the validity limit of an account. +// Modifier is done as follows: +// Adjustment value (-1, 1, +1, etc...) +// Modified element: +// a or y: year +// m: month +// j or d: day +// h: hour +// mn: minute +// s: second +// <example> timeadd testname +1m-2mn1s-6y +// this example adds 1 month and 1 second, and substracts 2 minutes and 6 years at the same time. +// NOTE: You can not modify a unlimited validity limit. +// If you want modify it, you want probably create a limited validity limit. +// So, at first, you must set the validity limit to a date/time. +// +// timeset <account_name> yyyy/mm/dd [hh:mm:ss] +// Changes the validity limit of an account. +// Default time [hh:mm:ss]: 23:59:59. +// timeset <account_name> 0 +// Gives an unlimited validity limit (0 = unlimited). +// +// unban/unbanish <account name> +// Unban an account. +// Like banset <account name> 0. +// +// unblock <account name> +// Set state 0 (Account ok) to an account. +// Like state <account name> 0. +// +// version +// Display the version of the login-server. +// +// who <account name> +// Displays complete information of an account. +// +//------------------------------------------------------------------------- +int login_fd; +int login_ip; +int bytes_to_read = 0; // flag to know if we waiting bytes from login-server +char command[1024]; +char parameters[1024]; +int list_first, list_last, list_type, list_count; // parameter to display a list of accounts +int already_exit_function = 0; // sometimes, the exit function is called twice... so, don't log twice the message + +//------------------------------ +// Writing function of logs file +//------------------------------ +int ladmin_log(char *fmt, ...) { + FILE *logfp; + va_list ap; + struct timeval tv; + char tmpstr[2048]; + + va_start(ap, fmt); + + logfp = fopen(ladmin_log_filename, "a"); + if (logfp) { + if (fmt[0] == '\0') // jump a line if no message + fprintf(logfp, RETCODE); + else { + gettimeofday(&tv, NULL); + strftime(tmpstr, 24, date_format, localtime(&(tv.tv_sec))); + sprintf(tmpstr + strlen(tmpstr), ".%03d: %s", (int)tv.tv_usec / 1000, fmt); + vfprintf(logfp, tmpstr, ap); + } + fclose(logfp); + } + + va_end(ap); + return 0; +} + +//----------------------------------------------------- +// Function to suppress control characters in a string. +//----------------------------------------------------- +int remove_control_chars(unsigned char *str) { + int i; + int change = 0; + + for(i = 0; str[i]; i++) { + if (str[i] < 32) { + str[i] = '_'; + change = 1; + } + } + + return change; +} + +//--------------------------------------------- +// Function to return ordonal text of a number. +//--------------------------------------------- +char* makeordinal(int number) { + if (defaultlanguage == 'F') { + if (number == 0) + return ""; + else if (number == 1) + return "er"; + else + return "鑪e"; + } else { + if ((number % 10) < 4 && (number % 10) != 0 && (number < 10 || number > 20)) { + if ((number % 10) == 1) + return "st"; + else if ((number % 10) == 2) + return "nd"; + else + return "rd"; + } else { + return "th"; + } + } + return ""; +} + +//----------------------------------------------------------------------------------------- +// Function to test of the validity of an account name (return 0 if incorrect, and 1 if ok) +//----------------------------------------------------------------------------------------- +int verify_accountname(char* account_name) { + int i; + + for(i = 0; account_name[i]; i++) { + if (account_name[i] < 32) { + if (defaultlanguage == 'F') { + printf("Caract鑽e interdit trouv dans le nom du compte (%d%s caract鑽e).\n", i+1, makeordinal(i+1)); + ladmin_log("Caract鑽e interdit trouv dans le nom du compte (%d%s caract鑽e)." RETCODE, i+1, makeordinal(i+1)); + } else { + printf("Illegal character found in the account name (%d%s character).\n", i+1, makeordinal(i+1)); + ladmin_log("Illegal character found in the account name (%d%s character)." RETCODE, i+1, makeordinal(i+1)); + } + return 0; + } + } + + if (strlen(account_name) < 4) { + if (defaultlanguage == 'F') { + printf("Nom du compte trop court. Entrez un nom de compte de 4-23 caract鑽es.\n"); + ladmin_log("Nom du compte trop court. Entrez un nom de compte de 4-23 caract鑽es." RETCODE); + } else { + printf("Account name is too short. Please input an account name of 4-23 bytes.\n"); + ladmin_log("Account name is too short. Please input an account name of 4-23 bytes." RETCODE); + } + return 0; + } + + if (strlen(account_name) > 23) { + if (defaultlanguage == 'F') { + printf("Nom du compte trop long. Entrez un nom de compte de 4-23 caract鑽es.\n"); + ladmin_log("Nom du compte trop long. Entrez un nom de compte de 4-23 caract鑽es." RETCODE); + } else { + printf("Account name is too long. Please input an account name of 4-23 bytes.\n"); + ladmin_log("Account name is too long. Please input an account name of 4-23 bytes." RETCODE); + } + return 0; + } + + return 1; +} + +//--------------------------------------------------- +// E-mail check: return 0 (not correct) or 1 (valid). +//--------------------------------------------------- +int e_mail_check(unsigned char *email) { + char ch; + unsigned char* last_arobas; + + // athena limits + if (strlen(email) < 3 || strlen(email) > 39) + return 0; + + // part of RFC limits (official reference of e-mail description) + if (strchr(email, '@') == NULL || email[strlen(email)-1] == '@') + return 0; + + if (email[strlen(email)-1] == '.') + return 0; + + last_arobas = strrchr(email, '@'); + + if (strstr(last_arobas, "@.") != NULL || + strstr(last_arobas, "..") != NULL) + return 0; + + for(ch = 1; ch < 32; ch++) { + if (strchr(last_arobas, ch) != NULL) { + return 0; + break; + } + } + + if (strchr(last_arobas, ' ') != NULL || + strchr(last_arobas, ';') != NULL) + return 0; + + // all correct + return 1; +} + +//---------------------------------- +// Sub-function: Input of a password +//---------------------------------- +int typepasswd(char * password) { + char password1[1023], password2[1023]; + int letter; + int i; + + if (defaultlanguage == 'F') { + ladmin_log("Aucun mot de passe n'a 騁 donn. Demande d'un mot de passe." RETCODE); + } else { + ladmin_log("No password was given. Request to obtain a password." RETCODE); + } + + memset(password1, '\0', sizeof(password1)); + memset(password2, '\0', sizeof(password2)); + if (defaultlanguage == 'F') + printf("\033[1;36m Entrez le mot de passe > \033[0;32;42m"); + else + printf("\033[1;36m Type the password > \033[0;32;42m"); + i = 0; + while ((letter = getchar()) != '\n') + password1[i++] = letter; + if (defaultlanguage == 'F') + printf("\033[0m\033[1;36m R-entrez le mot de passe > \033[0;32;42m"); + else + printf("\033[0m\033[1;36m Verify the password > \033[0;32;42m"); + i = 0; + while ((letter = getchar()) != '\n') + password2[i++] = letter; + + printf("\033[0m"); + fflush(stdout); + fflush(stdin); + + if (strcmp(password1, password2) != 0) { + if (defaultlanguage == 'F') { + printf("Erreur de v駻ification du mot de passe: Saisissez le m麥e mot de passe svp.\n"); + ladmin_log("Erreur de v駻ification du mot de passe: Saisissez le m麥e mot de passe svp." RETCODE); + ladmin_log(" Premier mot de passe: %s, second mot de passe: %s." RETCODE, password1, password2); + } else { + printf("Password verification failed. Please input same password.\n"); + ladmin_log("Password verification failed. Please input same password." RETCODE); + ladmin_log(" First password: %s, second password: %s." RETCODE, password1, password2); + } + return 0; + } + if (defaultlanguage == 'F') { + ladmin_log("Mot de passe saisi: %s." RETCODE, password1); + } else { + ladmin_log("Typed password: %s." RETCODE, password1); + } + strcpy(password, password1); + return 1; +} + +//------------------------------------------------------------------------------------ +// Sub-function: Test of the validity of password (return 0 if incorrect, and 1 if ok) +//------------------------------------------------------------------------------------ +int verify_password(char * password) { + int i; + + for(i = 0; password[i]; i++) { + if (password[i] < 32) { + if (defaultlanguage == 'F') { + printf("Caract鑽e interdit trouv dans le mot de passe (%d%s caract鑽e).\n", i+1, makeordinal(i+1)); + ladmin_log("Caract鑽e interdit trouv dans le nom du compte (%d%s caract鑽e)." RETCODE, i+1, makeordinal(i+1)); + } else { + printf("Illegal character found in the password (%d%s character).\n", i+1, makeordinal(i+1)); + ladmin_log("Illegal character found in the password (%d%s character)." RETCODE, i+1, makeordinal(i+1)); + } + return 0; + } + } + + if (strlen(password) < 4) { + if (defaultlanguage == 'F') { + printf("Nom du compte trop court. Entrez un nom de compte de 4-23 caract鑽es.\n"); + ladmin_log("Nom du compte trop court. Entrez un nom de compte de 4-23 caract鑽es." RETCODE); + } else { + printf("Account name is too short. Please input an account name of 4-23 bytes.\n"); + ladmin_log("Account name is too short. Please input an account name of 4-23 bytes." RETCODE); + } + return 0; + } + + if (strlen(password) > 23) { + if (defaultlanguage == 'F') { + printf("Mot de passe trop long. Entrez un mot de passe de 4-23 caract鑽es.\n"); + ladmin_log("Mot de passe trop long. Entrez un mot de passe de 4-23 caract鑽es." RETCODE); + } else { + printf("Password is too long. Please input a password of 4-23 bytes.\n"); + ladmin_log("Password is too long. Please input a password of 4-23 bytes." RETCODE); + } + return 0; + } + + return 1; +} + +//------------------------------------------------------------------ +// Sub-function: Check the name of a command (return complete name) +//----------------------------------------------------------------- +int check_command(char * command) { +// help + if (strncmp(command, "aide", 2) == 0 && strncmp(command, "aide", strlen(command)) == 0) // not 1 letter command: 'aide' or 'add'? + strcpy(command, "aide"); + else if (strncmp(command, "help", 1) == 0 && strncmp(command, "help", strlen(command)) == 0) + strcpy(command, "help"); +// general commands + else if (strncmp(command, "add", 2) == 0 && strncmp(command, "add", strlen(command)) == 0) // not 1 letter command: 'aide' or 'add'? + strcpy(command, "add"); + else if ((strncmp(command, "ban", 3) == 0 && strncmp(command, "ban", strlen(command)) == 0) || + (strncmp(command, "banish", 4) == 0 && strncmp(command, "banish", strlen(command)) == 0)) + strcpy(command, "ban"); + else if ((strncmp(command, "banadd", 4) == 0 && strncmp(command, "banadd", strlen(command)) == 0) || // not 1 letter command: 'ba' or 'bs'? 'banadd' or 'banset' ? + strcmp(command, "ba") == 0) + strcpy(command, "banadd"); + else if ((strncmp(command, "banset", 4) == 0 && strncmp(command, "banset", strlen(command)) == 0) || // not 1 letter command: 'ba' or 'bs'? 'banadd' or 'banset' ? + strcmp(command, "bs") == 0) + strcpy(command, "banset"); + else if (strncmp(command, "block", 2) == 0 && strncmp(command, "block", strlen(command)) == 0) + strcpy(command, "block"); + else if (strncmp(command, "check", 2) == 0 && strncmp(command, "check", strlen(command)) == 0) // not 1 letter command: 'check' or 'create'? + strcpy(command, "check"); + else if (strncmp(command, "create", 2) == 0 && strncmp(command, "create", strlen(command)) == 0) // not 1 letter command: 'check' or 'create'? + strcpy(command, "create"); + else if (strncmp(command, "delete", 1) == 0 && strncmp(command, "delete", strlen(command)) == 0) + strcpy(command, "delete"); + else if ((strncmp(command, "email", 2) == 0 && strncmp(command, "email", strlen(command)) == 0) || // not 1 letter command: 'email', 'end' or 'exit'? + (strncmp(command, "e-mail", 2) == 0 && strncmp(command, "e-mail", strlen(command)) == 0)) + strcpy(command, "email"); + else if (strncmp(command, "getcount", 2) == 0 && strncmp(command, "getcount", strlen(command)) == 0) // not 1 letter command: 'getcount' or 'gm'? + strcpy(command, "getcount"); +// else if (strncmp(command, "gm", 2) == 0 && strncmp(command, "gm", strlen(command)) == 0) // not 1 letter command: 'getcount' or 'gm'? +// strcpy(command, "gm"); +// else if (strncmp(command, "id", 2) == 0 && strncmp(command, "id", strlen(command)) == 0) // not 1 letter command: 'id' or 'info'? +// strcpy(command, "id"); + else if (strncmp(command, "info", 2) == 0 && strncmp(command, "info", strlen(command)) == 0) // not 1 letter command: 'id' or 'info'? + strcpy(command, "info"); +// else if (strncmp(command, "kami", 4) == 0 && strncmp(command, "kami", strlen(command)) == 0) // only all letters command: 'kami' or 'kamib'? +// strcpy(command, "kami"); +// else if (strncmp(command, "kamib", 5) == 0 && strncmp(command, "kamib", strlen(command)) == 0) // only all letters command: 'kami' or 'kamib'? +// strcpy(command, "kamib"); + else if ((strncmp(command, "language", 2) == 0 && strncmp(command, "language", strlen(command)) == 0)) // not 1 letter command: 'language' or 'list'? + strcpy(command, "language"); + else if ((strncmp(command, "list", 2) == 0 && strncmp(command, "list", strlen(command)) == 0) || // 'list' is default list command // not 1 letter command: 'language' or 'list'? + strcmp(command, "ls") == 0) + strcpy(command, "list"); + else if ((strncmp(command, "listban", 5) == 0 && strncmp(command, "listban", strlen(command)) == 0) || + (strncmp(command, "lsban", 3) == 0 && strncmp(command, "lsban", strlen(command)) == 0) || + strcmp(command, "lb") == 0) + strcpy(command, "listban"); + else if ((strncmp(command, "listgm", 5) == 0 && strncmp(command, "listgm", strlen(command)) == 0) || + (strncmp(command, "lsgm", 3) == 0 && strncmp(command, "lsgm", strlen(command)) == 0) || + strcmp(command, "lg") == 0) + strcpy(command, "listgm"); + else if ((strncmp(command, "listok", 5) == 0 && strncmp(command, "listok", strlen(command)) == 0) || + (strncmp(command, "lsok", 3) == 0 && strncmp(command, "lsok", strlen(command)) == 0) || + strcmp(command, "lo") == 0) + strcpy(command, "listok"); + else if (strncmp(command, "memo", 1) == 0 && strncmp(command, "memo", strlen(command)) == 0) + strcpy(command, "memo"); + else if (strncmp(command, "name", 1) == 0 && strncmp(command, "name", strlen(command)) == 0) + strcpy(command, "name"); + else if ((strncmp(command, "password", 1) == 0 && strncmp(command, "password", strlen(command)) == 0) || + strcmp(command, "passwd") == 0) + strcpy(command, "password"); + else if (strncmp(command, "reloadgm", 1) == 0 && strncmp(command, "reloadgm", strlen(command)) == 0) + strcpy(command, "reloadgm"); + else if (strncmp(command, "search", 3) == 0 && strncmp(command, "search", strlen(command)) == 0) // not 1 letter command: 'search', 'state' or 'sex'? + strcpy(command, "search"); // not 2 letters command: 'search' or 'sex'? +// else if (strncmp(command, "sex", 3) == 0 && strncmp(command, "sex", strlen(command)) == 0) // not 1 letter command: 'search', 'state' or 'sex'? +// strcpy(command, "sex"); // not 2 letters command: 'search' or 'sex'? + else if (strncmp(command, "state", 2) == 0 && strncmp(command, "state", strlen(command)) == 0) // not 1 letter command: 'search', 'state' or 'sex'? + strcpy(command, "state"); + else if ((strncmp(command, "timeadd", 5) == 0 && strncmp(command, "timeadd", strlen(command)) == 0) || // not 1 letter command: 'ta' or 'ts'? 'timeadd' or 'timeset'? + strcmp(command, "ta") == 0) + strcpy(command, "timeadd"); + else if ((strncmp(command, "timeset", 5) == 0 && strncmp(command, "timeset", strlen(command)) == 0) || // not 1 letter command: 'ta' or 'ts'? 'timeadd' or 'timeset'? + strcmp(command, "ts") == 0) + strcpy(command, "timeset"); + else if ((strncmp(command, "unban", 5) == 0 && strncmp(command, "unban", strlen(command)) == 0) || + (strncmp(command, "unbanish", 4) == 0 && strncmp(command, "unbanish", strlen(command)) == 0)) + strcpy(command, "unban"); + else if (strncmp(command, "unblock", 4) == 0 && strncmp(command, "unblock", strlen(command)) == 0) + strcpy(command, "unblock"); + else if (strncmp(command, "version", 1) == 0 && strncmp(command, "version", strlen(command)) == 0) + strcpy(command, "version"); + else if (strncmp(command, "who", 1) == 0 && strncmp(command, "who", strlen(command)) == 0) + strcpy(command, "who"); +// quit + else if (strncmp(command, "quit", 1) == 0 && strncmp(command, "quit", strlen(command)) == 0) + strcpy(command, "quit"); + else if (strncmp(command, "exit", 2) == 0 && strncmp(command, "exit", strlen(command)) == 0) // not 1 letter command: 'email', 'end' or 'exit'? + strcpy(command, "exit"); + else if (strncmp(command, "end", 2) == 0 && strncmp(command, "end", strlen(command)) == 0) // not 1 letter command: 'email', 'end' or 'exit'? + strcpy(command, "end"); + + return 0; +} + +//----------------------------------------- +// Sub-function: Display commands of ladmin +//----------------------------------------- +void display_help(char* param, int language) { + char command[1023]; + int i; + + memset(command, '\0', sizeof(command)); + + if (sscanf(param, "%s ", command) < 1 || strlen(command) == 0) + strcpy(command, ""); // any value that is not a command + + if (command[0] == '?') { + if (defaultlanguage == 'F') + strcpy(command, "aide"); + else + strcpy(command, "help"); + } + + // lowercase for command + for (i = 0; command[i]; i++) + command[i] = tolower(command[i]); + + // Analyse of the command + check_command(command); // give complete name to the command + + if (defaultlanguage == 'F') { + ladmin_log("Affichage des commandes ou d'une commande." RETCODE); + } else { + ladmin_log("Displaying of the commands or a command." RETCODE); + } + + if (language == 1) { + if (strcmp(command, "aide") == 0) { + printf("aide/help/?\n"); + printf(" Affiche la description des commandes\n"); + printf("aide/help/? [commande]\n"); + printf(" Affiche la description de la commande specifi馥\n"); + } else if (strcmp(command, "help") == 0 ) { + printf("aide/help/?\n"); + printf(" Display the description of the commands\n"); + printf("aide/help/? [command]\n"); + printf(" Display the description of the specified command\n"); +// general commands + } else if (strcmp(command, "add") == 0) { + printf("add <nomcompte> <sexe> <motdepasse>\n"); + printf(" Cr馥 un compte avec l'email par d馭aut (a@a.com).\n"); + printf(" Concernant le sexe, seule la premi鑽e lettre compte (F ou M).\n"); + printf(" L'e-mail est a@a.com (e-mail par d馭aut). C'est comme n'avoir aucun e-mail.\n"); + printf(" Lorsque motdepasse est omis, la saisie se fait sans que la frappe se voit.\n"); + printf(" <exemple> add testname Male testpass\n"); + } else if (strcmp(command, "ban") == 0) { + printf("ban/banish aaaa/mm/jj hh:mm:ss <nom compte>\n"); + printf(" Change la date de fin de bannissement d'un compte.\n"); + printf(" Comme banset, mais <nom compte> est la fin.\n"); + } else if (strcmp(command, "banadd") == 0) { + printf("banadd <nomcompte> <Modificateur>\n"); + printf(" Ajoute ou soustrait du temps la date de banissement d'un compte.\n"); + printf(" Les modificateurs sont construits comme suit:\n"); + printf(" Valeur d'ajustement (-1, 1, +1, etc...)\n"); + printf(" El駑ent modifi:\n"); + printf(" a ou y: ann馥\n"); + printf(" m: mois\n"); + printf(" j ou d: jour\n"); + printf(" h: heure\n"); + printf(" mn: minute\n"); + printf(" s: seconde\n"); + printf(" <exemple> banadd testname +1m-2mn1s-6a\n"); + printf(" Cette exemple ajoute 1 mois et une seconde, et soustrait 2 minutes\n"); + printf(" et 6 ans dans le m麥e temps.\n"); + printf("NOTE: Si vous modifez la date de banissement d'un compte non bani,\n"); + printf(" vous indiquez comme date (le moment actuel +- les ajustements)\n"); + } else if (strcmp(command, "banset") == 0) { + printf("banset <nomcompte> aaaa/mm/jj [hh:mm:ss]\n"); + printf(" Change la date de fin de bannissement d'un compte.\n"); + printf(" Heure par d馭aut [hh:mm:ss]: 23:59:59.\n"); + printf("banset <nomcompte> 0\n"); + printf(" D饕anni un compte (0 = de-banni).\n"); + } else if (strcmp(command, "block") == 0) { + printf("block <nom compte>\n"); + printf(" Place le status d'un compte 5 (You have been blocked by the GM Team).\n"); + printf(" La commande est l'駲uivalent de state <nom_compte> 5.\n"); + } else if (strcmp(command, "check") == 0) { + printf("check <nomcompte> <motdepasse>\n"); + printf(" V駻ifie la validit d'un mot de passe pour un compte\n"); + printf(" NOTE: Le serveur n'enverra jamais un mot de passe.\n"); + printf(" C'est la seule m騁hode que vous poss馘ez pour savoir\n"); + printf(" si un mot de passe est le bon. L'autre m騁hode est\n"); + printf(" d'avoir un acc鑚 ('physique') au fichier des comptes.\n"); + } else if (strcmp(command, "create") == 0) { + printf("create <nomcompte> <sexe> <email> <motdepasse>\n"); + printf(" Comme la commande add, mais avec l'e-mail en plus.\n"); + printf(" <exemple> create testname Male mon@mail.com testpass\n"); + } else if (strcmp(command, "delete") == 0) { + printf("del <nom compte>\n"); + printf(" Supprime un compte.\n"); + printf(" La commande demande confirmation. Apr鑚 confirmation, le compte est d騁ruit.\n"); + } else if (strcmp(command, "email") == 0) { + printf("email <nomcompte> <email>\n"); + printf(" Modifie l'e-mail d'un compte.\n"); + } else if (strcmp(command, "getcount") == 0) { + printf("getcount\n"); + printf(" Donne le nombre de joueurs en ligne par serveur de char.\n"); + } else if (strcmp(command, "gm") == 0) { + printf("gm <nomcompte> [Niveau_GM]\n"); + printf(" Modifie le niveau de GM d'un compte.\n"); + printf(" Valeur par d馭aut: 0 (suppression du niveau de GM).\n"); + printf(" <exemple> gm nomtest 80\n"); + } else if (strcmp(command, "id") == 0) { + printf("id <nom compte>\n"); + printf(" Donne l'id d'un compte.\n"); + } else if (strcmp(command, "info") == 0) { + printf("info <idcompte>\n"); + printf(" Affiche les informations sur un compte.\n"); + } else if (strcmp(command, "kami") == 0) { + printf("kami <message>\n"); + printf(" Envoi un message g駭駻al sur tous les serveurs de map (en jaune).\n"); + } else if (strcmp(command, "kamib") == 0) { + printf("kamib <message>\n"); + printf(" Envoi un message g駭駻al sur tous les serveurs de map (en bleu).\n"); + } else if (strcmp(command, "language") == 0) { + printf("language <langue>\n"); + printf(" Change la langue d'affichage.\n"); + printf(" Langues possibles: 'Fran軋is' ou 'English'.\n"); + } else if (strcmp(command, "list") == 0) { + printf("list/ls [Premier_id [Dernier_id]]\n"); + printf(" Affiche une liste de comptes.\n"); + printf(" 'Premier_id', 'Dernier_id': indique les identifiants de d駱art et de fin.\n"); + printf(" La recherche par nom n'est pas possible avec cette commande.\n"); + printf(" <example> list 10 9999999\n"); + } else if (strcmp(command, "listban") == 0) { + printf("listBan/lsBan [Premier_id [Dernier_id]]\n"); + printf(" Comme list/ls, mais seulement pour les comptes avec statut ou bannis.\n"); + } else if (strcmp(command, "listgm") == 0) { + printf("listGM/lsGM [Premier_id [Dernier_id]]\n"); + printf(" Comme list/ls, mais seulement pour les comptes GM.\n"); + } else if (strcmp(command, "listok") == 0) { + printf("listOK/lsOK [Premier_id [Dernier_id]]\n"); + printf(" Comme list/ls, mais seulement pour les comptes sans statut et non bannis.\n"); + } else if (strcmp(command, "memo") == 0) { + printf("memo <nomcompte> <memo>\n"); + printf(" Modifie le m駑o d'un compte.\n"); + printf(" 'memo': Il peut avoir jusqu' 253 caract鑽es (avec des espaces ou non).\n"); + } else if (strcmp(command, "name") == 0) { + printf("name <idcompte>\n"); + printf(" Donne le nom d'un compte.\n"); + } else if (strcmp(command, "password") == 0) { + printf("passwd <nomcompte> <nouveaumotdepasse>\n"); + printf(" Change le mot de passe d'un compte.\n"); + printf(" Lorsque nouveaumotdepasse est omis,\n"); + printf(" la saisie se fait sans que la frappe ne se voit.\n"); + } else if (strcmp(command, "reloadgm") == 0) { + printf("reloadGM\n"); + printf(" Reload GM configuration file\n"); + } else if (strcmp(command, "search") == 0) { + printf("search <expression>\n"); + printf(" Cherche des comptes.\n"); + printf(" Affiche les comptes dont les noms correspondent.\n"); +// printf("search -r/-e/--expr/--regex <expression>\n"); +// printf(" Cherche des comptes par expression reguli鑽e.\n"); +// printf(" Affiche les comptes dont les noms correspondent.\n"); + } else if (strcmp(command, "sex") == 0) { + printf("sex <nomcompte> <sexe>\n"); + printf(" Modifie le sexe d'un compte.\n"); + printf(" <exemple> sex testname Male\n"); + } else if (strcmp(command, "state") == 0) { + printf("state <nomcompte> <nouveaustatut> <message_erreur_7>\n"); + printf(" Change le statut d'un compte.\n"); + printf(" 'nouveaustatut': Le statut est le m麥e que celui du packet 0x006a + 1.\n"); + printf(" les possibilit駸 sont:\n"); + printf(" 0 = Compte ok\n"); + printf(" 1 = Unregistered ID\n"); + printf(" 2 = Incorrect Password\n"); + printf(" 3 = This ID is expired\n"); + printf(" 4 = Rejected from Server\n"); + printf(" 5 = You have been blocked by the GM Team\n"); + printf(" 6 = Your Game's EXE file is not the latest version\n"); + printf(" 7 = You are Prohibited to log in until...\n"); + printf(" 8 = Server is jammed due to over populated\n"); + printf(" 9 = No MSG\n"); + printf(" 100 = This ID has been totally erased\n"); + printf(" all other values are 'No MSG', then use state 9 please.\n"); + printf(" 'message_erreur_7': message du code erreur 6 =\n"); + printf(" = Your are Prohibited to log in until... (packet 0x006a)\n"); + } else if (strcmp(command, "timeadd") == 0) { + printf("timeadd <nomcompte> <modificateur>\n"); + printf(" Ajoute/soustrait du temps la limite de validit d'un compte.\n"); + printf(" Le modificateur est compos comme suit:\n"); + printf(" Valeur modificatrice (-1, 1, +1, etc...)\n"); + printf(" El駑ent modifi:\n"); + printf(" a ou y: ann馥\n"); + printf(" m: mois\n"); + printf(" j ou d: jour\n"); + printf(" h: heure\n"); + printf(" mn: minute\n"); + printf(" s: seconde\n"); + printf(" <exemple> timeadd testname +1m-2mn1s-6a\n"); + printf(" Cette exemple ajoute 1 mois et une seconde, et soustrait 2 minutes\n"); + printf(" et 6 ans dans le m麥e temps.\n"); + printf("NOTE: Vous ne pouvez pas modifier une limite de validit illimit馥. Si vous\n"); + printf(" d駸irez le faire, c'est que vous voulez probablement cr馥r un limite de\n"); + printf(" validit limit馥. Donc, en premier, fix une limite de valitid.\n"); + } else if (strcmp(command, "timeadd") == 0) { + printf("timeset <nomcompte> aaaa/mm/jj [hh:mm:ss]\n"); + printf(" Change la limite de validit d'un compte.\n"); + printf(" Heure par d馭aut [hh:mm:ss]: 23:59:59.\n"); + printf("timeset <nomcompte> 0\n"); + printf(" Donne une limite de validit illimit馥 (0 = illimit馥).\n"); + } else if (strcmp(command, "unban") == 0) { + printf("unban/unbanish <nom compte>\n"); + printf(" Ote le banissement d'un compte.\n"); + printf(" La commande est l'駲uivalent de banset <nom_compte> 0.\n"); + } else if (strcmp(command, "unblock") == 0) { + printf("unblock <nom compte>\n"); + printf(" Place le status d'un compte 0 (Compte ok).\n"); + printf(" La commande est l'駲uivalent de state <nom_compte> 0.\n"); + } else if (strcmp(command, "version") == 0) { + printf("version\n"); + printf(" Affiche la version du login-serveur.\n"); + } else if (strcmp(command, "who") == 0) { + printf("who <nom compte>\n"); + printf(" Affiche les informations sur un compte.\n"); +// quit + } else if (strcmp(command, "quit") == 0 || + strcmp(command, "exit") == 0 || + strcmp(command, "end") == 0) { + printf("quit/end/exit\n"); + printf(" Fin du programme d'administration.\n"); +// unknown command + } else { + if (strlen(command) > 0) + printf("Commande inconnue [%s] pour l'aide. Affichage de toutes les commandes.\n", command); + printf(" aide/help/? -- Affiche cet aide\n"); + printf(" aide/help/? [commande] -- Affiche l'aide de la commande\n"); + printf(" add <nomcompte> <sexe> <motdepasse> -- Cr馥 un compte (sans email)\n"); + printf(" ban/banish aaaa/mm/jj hh:mm:ss <nom compte> -- Fixe la date finale de banismnt\n"); + printf(" banadd/ba <nomcompte> <modificateur> -- Ajout/soustrait du temps la\n"); + printf(" exemple: ba moncompte +1m-2mn1s-2y date finale de banissement\n"); + printf(" banset/bs <nomcompte> aaaa/mm/jj [hh:mm:ss] -- Change la date fin de banisemnt\n"); + printf(" banset/bs <nomcompte> 0 -- D-banis un compte.\n"); + printf(" block <nom compte> -- Mets le status d'un compte 5 (blocked by the GM Team)\n"); + printf(" check <nomcompte> <motdepasse> -- V駻ifie un mot de passe d'un compte\n"); + printf(" create <nomcompte> <sexe> <email> <motdepasse> -- Cr馥 un compte (avec email)\n"); + printf(" del <nom compte> -- Supprime un compte\n"); + printf(" email <nomcompte> <email> -- Modifie l'e-mail d'un compte\n"); + printf(" getcount -- Donne le nb de joueurs en ligne\n"); + printf(" gm <nomcompte> [Niveau_GM] -- Modifie le niveau de GM d'un compte\n"); + printf(" id <nom compte> -- Donne l'id d'un compte\n"); + printf(" info <idcompte> -- Affiche les infos sur un compte\n"); + printf(" kami <message> -- Envoi un message g駭駻al (en jaune)\n"); + printf(" kamib <message> -- Envoi un message g駭駻al (en bleu)\n"); + printf(" language <langue> -- Change la langue d'affichage.\n"); + printf(" list/ls [Premier_id [Dernier_id] ] -- Affiche une liste de comptes\n"); + printf(" listBan/lsBan [Premier_id [Dernier_id] ] -- Affiche une liste de comptes\n"); + printf(" avec un statut ou bannis\n"); + printf(" listGM/lsGM [Premier_id [Dernier_id] ] -- Affiche une liste de comptes GM\n"); + printf(" listOK/lsOK [Premier_id [Dernier_id] ] -- Affiche une liste de comptes\n"); + printf(" sans status et non bannis\n"); + printf(" memo <nomcompte> <memo> -- Modifie le memo d'un compte\n"); + printf(" name <idcompte> -- Donne le nom d'un compte\n"); + printf(" passwd <nomcompte> <nouveaumotdepasse> -- Change le mot de passe d'un compte\n"); + printf(" quit/end/exit -- Fin du programme d'administation\n"); + printf(" reloadGM -- Recharger le fichier de config des GM\n"); + printf(" search <expression> -- Cherche des comptes\n"); +// printf(" search -e/-r/--expr/--regex <expression> -- Cherche des comptes par REGEX\n"); + printf(" sex <nomcompte> <sexe> -- Modifie le sexe d'un compte\n"); + printf(" state <nomcompte> <nouveaustatut> <messageerr7> -- Change le statut d'1 compte\n"); + printf(" timeadd/ta <nomcompte> <modificateur> -- Ajout/soustrait du temps la\n"); + printf(" exemple: ta moncompte +1m-2mn1s-2y limite de validit饅n"); + printf(" timeset/ts <nomcompte> aaaa/mm/jj [hh:mm:ss] -- Change la limite de validit饅n"); + printf(" timeset/ts <nomcompte> 0 -- limite de validit = illimit馥\n"); + printf(" unban/unbanish <nom compte> -- Ote le banissement d'un compte\n"); + printf(" unblock <nom compte> -- Mets le status d'un compte 0 (Compte ok)\n"); + printf(" version -- Donne la version du login-serveur\n"); + printf(" who <nom compte> -- Affiche les infos sur un compte\n"); + printf(" Note: Pour les noms de compte avec des espaces, tapez \"<nom compte>\" (ou ').\n"); + } + } else { + if (strcmp(command, "aide") == 0) { + printf("aide/help/?\n"); + printf(" Display the description of the commands\n"); + printf("aide/help/? [command]\n"); + printf(" Display the description of the specified command\n"); + } else if (strcmp(command, "help") == 0 ) { + printf("aide/help/?\n"); + printf(" Display the description of the commands\n"); + printf("aide/help/? [command]\n"); + printf(" Display the description of the specified command\n"); +// general commands + } else if (strcmp(command, "add") == 0) { + printf("add <account_name> <sex> <password>\n"); + printf(" Create an account with the default email (a@a.com).\n"); + printf(" Concerning the sex, only the first letter is used (F or M).\n"); + printf(" The e-mail is set to a@a.com (default e-mail). It's like to have no e-mail.\n"); + printf(" When the password is omitted,\n"); + printf(" the input is done without displaying of the pressed keys.\n"); + printf(" <example> add testname Male testpass\n"); + } else if (strcmp(command, "ban") == 0) { + printf("ban/banish yyyy/mm/dd hh:mm:ss <account name>\n"); + printf(" Changes the final date of a banishment of an account.\n"); + printf(" Like banset, but <account name> is at end.\n"); + } else if (strcmp(command, "banadd") == 0) { + printf("banadd <account_name> <modifier>\n"); + printf(" Adds or substracts time from the final date of a banishment of an account.\n"); + printf(" Modifier is done as follows:\n"); + printf(" Adjustment value (-1, 1, +1, etc...)\n"); + printf(" Modified element:\n"); + printf(" a or y: year\n"); + printf(" m: month\n"); + printf(" j or d: day\n"); + printf(" h: hour\n"); + printf(" mn: minute\n"); + printf(" s: second\n"); + printf(" <example> banadd testname +1m-2mn1s-6y\n"); + printf(" this example adds 1 month and 1 second, and substracts 2 minutes\n"); + printf(" and 6 years at the same time.\n"); + printf("NOTE: If you modify the final date of a non-banished account,\n"); + printf(" you fix the final date to (actual time +- adjustments)\n"); + } else if (strcmp(command, "banset") == 0) { + printf("banset <account_name> yyyy/mm/dd [hh:mm:ss]\n"); + printf(" Changes the final date of a banishment of an account.\n"); + printf(" Default time [hh:mm:ss]: 23:59:59.\n"); + printf("banset <account_name> 0\n"); + printf(" Set a non-banished account (0 = unbanished).\n"); + } else if (strcmp(command, "block") == 0) { + printf("block <account name>\n"); + printf(" Set state 5 (You have been blocked by the GM Team) to an account.\n"); + printf(" This command works like state <account_name> 5.\n"); + } else if (strcmp(command, "check") == 0) { + printf("check <account_name> <password>\n"); + printf(" Check the validity of a password for an account.\n"); + printf(" NOTE: Server will never sends back a password.\n"); + printf(" It's the only method you have to know if a password is correct.\n"); + printf(" The other method is to have a ('physical') access to the accounts file.\n"); + } else if (strcmp(command, "create") == 0) { + printf("create <account_name> <sex> <email> <password>\n"); + printf(" Like the 'add' command, but with e-mail moreover.\n"); + printf(" <example> create testname Male my@mail.com testpass\n"); + } else if (strcmp(command, "delete") == 0) { + printf("del <account name>\n"); + printf(" Remove an account.\n"); + printf(" This order requires confirmation. After confirmation, the account is deleted.\n"); + } else if (strcmp(command, "email") == 0) { + printf("email <account_name> <email>\n"); + printf(" Modify the e-mail of an account.\n"); + } else if (strcmp(command, "getcount") == 0) { + printf("getcount\n"); + printf(" Give the number of players online on all char-servers.\n"); + } else if (strcmp(command, "gm") == 0) { + printf("gm <account_name> [GM_level]\n"); + printf(" Modify the GM level of an account.\n"); + printf(" Default value remove GM level (GM level = 0).\n"); + printf(" <example> gm testname 80\n"); + } else if (strcmp(command, "id") == 0) { + printf("id <account name>\n"); + printf(" Give the id of an account.\n"); + } else if (strcmp(command, "info") == 0) { + printf("info <account_id>\n"); + printf(" Display complete information of an account.\n"); + } else if (strcmp(command, "kami") == 0) { + printf("kami <message>\n"); + printf(" Sends a broadcast message on all map-server (in yellow).\n"); + } else if (strcmp(command, "kamib") == 0) { + printf("kamib <message>\n"); + printf(" Sends a broadcast message on all map-server (in blue).\n"); + } else if (strcmp(command, "language") == 0) { + printf("language <language>\n"); + printf(" Change the language of displaying.\n"); + printf(" Possible languages: Fran軋is or English.\n"); + } else if (strcmp(command, "list") == 0) { + printf("list/ls [start_id [end_id]]\n"); + printf(" Display a list of accounts.\n"); + printf(" 'start_id', 'end_id': indicate end and start identifiers.\n"); + printf(" Research by name is not possible with this command.\n"); + printf(" <example> list 10 9999999\n"); + } else if (strcmp(command, "listban") == 0) { + printf("listBan/lsBan [start_id [end_id]]\n"); + printf(" Like list/ls, but only for accounts with state or banished.\n"); + } else if (strcmp(command, "listgm") == 0) { + printf("listGM/lsGM [start_id [end_id]]\n"); + printf(" Like list/ls, but only for GM accounts.\n"); + } else if (strcmp(command, "listok") == 0) { + printf("listOK/lsOK [start_id [end_id]]\n"); + printf(" Like list/ls, but only for accounts without state and not banished.\n"); + } else if (strcmp(command, "memo") == 0) { + printf("memo <account_name> <memo>\n"); + printf(" Modify the memo of an account.\n"); + printf(" 'memo': it can have until 253 characters (with spaces or not).\n"); + } else if (strcmp(command, "name") == 0) { + printf("name <account_id>\n"); + printf(" Give the name of an account.\n"); + } else if (strcmp(command, "password") == 0) { + printf("passwd <account_name> <new_password>\n"); + printf(" Change the password of an account.\n"); + printf(" When new password is omitted,\n"); + printf(" the input is done without displaying of the pressed keys.\n"); + } else if (strcmp(command, "reloadgm") == 0) { + printf("reloadGM\n"); + printf(" Reload GM configuration file\n"); + } else if (strcmp(command, "search") == 0) { + printf("search <expression>\n"); + printf(" Seek accounts.\n"); + printf(" Displays the accounts whose names correspond.\n"); +// printf("search -r/-e/--expr/--regex <expression>\n"); +// printf(" Seek accounts by regular expression.\n"); +// printf(" Displays the accounts whose names correspond.\n"); + } else if (strcmp(command, "sex") == 0) { + printf("sex <account_name> <sex>\n"); + printf(" Modify the sex of an account.\n"); + printf(" <example> sex testname Male\n"); + } else if (strcmp(command, "state") == 0) { + printf("state <account_name> <new_state> <error_message_#7>\n"); + printf(" Change the state of an account.\n"); + printf(" 'new_state': state is the state of the packet 0x006a + 1.\n"); + printf(" The possibilities are:\n"); + printf(" 0 = Account ok\n"); + printf(" 1 = Unregistered ID\n"); + printf(" 2 = Incorrect Password\n"); + printf(" 3 = This ID is expired\n"); + printf(" 4 = Rejected from Server\n"); + printf(" 5 = You have been blocked by the GM Team\n"); + printf(" 6 = Your Game's EXE file is not the latest version\n"); + printf(" 7 = You are Prohibited to log in until...\n"); + printf(" 8 = Server is jammed due to over populated\n"); + printf(" 9 = No MSG\n"); + printf(" 100 = This ID has been totally erased\n"); + printf(" all other values are 'No MSG', then use state 9 please.\n"); + printf(" 'error_message_#7': message of the code error 6\n"); + printf(" = Your are Prohibited to log in until... (packet 0x006a)\n"); + } else if (strcmp(command, "timeadd") == 0) { + printf("timeadd <account_name> <modifier>\n"); + printf(" Adds or substracts time from the validity limit of an account.\n"); + printf(" Modifier is done as follows:\n"); + printf(" Adjustment value (-1, 1, +1, etc...)\n"); + printf(" Modified element:\n"); + printf(" a or y: year\n"); + printf(" m: month\n"); + printf(" j or d: day\n"); + printf(" h: hour\n"); + printf(" mn: minute\n"); + printf(" s: second\n"); + printf(" <example> timeadd testname +1m-2mn1s-6y\n"); + printf(" this example adds 1 month and 1 second, and substracts 2 minutes\n"); + printf(" and 6 years at the same time.\n"); + printf("NOTE: You can not modify a unlimited validity limit.\n"); + printf(" If you want modify it, you want probably create a limited validity limit.\n"); + printf(" So, at first, you must set the validity limit to a date/time.\n"); + } else if (strcmp(command, "timeadd") == 0) { + printf("timeset <account_name> yyyy/mm/dd [hh:mm:ss]\n"); + printf(" Changes the validity limit of an account.\n"); + printf(" Default time [hh:mm:ss]: 23:59:59.\n"); + printf("timeset <account_name> 0\n"); + printf(" Gives an unlimited validity limit (0 = unlimited).\n"); + } else if (strcmp(command, "unban") == 0) { + printf("unban/unbanish <account name>\n"); + printf(" Remove the banishment of an account.\n"); + printf(" This command works like banset <account_name> 0.\n"); + } else if (strcmp(command, "unblock") == 0) { + printf("unblock <account name>\n"); + printf(" Set state 0 (Account ok) to an account.\n"); + printf(" This command works like state <account_name> 0.\n"); + } else if (strcmp(command, "version") == 0) { + printf("version\n"); + printf(" Display the version of the login-server.\n"); + } else if (strcmp(command, "who") == 0) { + printf("who <account name>\n"); + printf(" Displays complete information of an account.\n"); +// quit + } else if (strcmp(command, "quit") == 0 || + strcmp(command, "exit") == 0 || + strcmp(command, "end") == 0) { + printf("quit/end/exit\n"); + printf(" End of the program of administration.\n"); +// unknown command + } else { + if (strlen(command) > 0) + printf("Unknown command [%s] for help. Displaying of all commands.\n", command); + printf(" aide/help/? -- Display this help\n"); + printf(" aide/help/? [command] -- Display the help of the command\n"); + printf(" add <account_name> <sex> <password> -- Create an account with default email\n"); + printf(" ban/banish yyyy/mm/dd hh:mm:ss <account name> -- Change final date of a ban\n"); + printf(" banadd/ba <account_name> <modifier> -- Add or substract time from the final\n"); + printf(" example: ba apple +1m-2mn1s-2y date of a banishment of an account\n"); + printf(" banset/bs <account_name> yyyy/mm/dd [hh:mm:ss] -- Change final date of a ban\n"); + printf(" banset/bs <account_name> 0 -- Un-banish an account\n"); + printf(" block <account name> -- Set state 5 (blocked by the GM Team) to an account\n"); + printf(" check <account_name> <password> -- Check the validity of a password\n"); + printf(" create <account_name> <sex> <email> <passwrd> -- Create an account with email\n"); + printf(" del <account name> -- Remove an account\n"); + printf(" email <account_name> <email> -- Modify an email of an account\n"); + printf(" getcount -- Give the number of players online\n"); + printf(" gm <account_name> [GM_level] -- Modify the GM level of an account\n"); + printf(" id <account name> -- Give the id of an account\n"); + printf(" info <account_id> -- Display all information of an account\n"); + printf(" kami <message> -- Sends a broadcast message (in yellow)\n"); + printf(" kamib <message> -- Sends a broadcast message (in blue)\n"); + printf(" language <language> -- Change the language of displaying.\n"); + printf(" list/ls [First_id [Last_id]] -- Display a list of accounts\n"); + printf(" listBan/lsBan [First_id [Last_id] ] -- Display a list of accounts\n"); + printf(" with state or banished\n"); + printf(" listGM/lsGM [First_id [Last_id]] -- Display a list of GM accounts\n"); + printf(" listOK/lsOK [First_id [Last_id] ] -- Display a list of accounts\n"); + printf(" without state and not banished\n"); + printf(" memo <account_name> <memo> -- Modify the memo of an account\n"); + printf(" name <account_id> -- Give the name of an account\n"); + printf(" passwd <account_name> <new_password> -- Change the password of an account\n"); + printf(" quit/end/exit -- End of the program of administation\n"); + printf(" reloadGM -- Reload GM configuration file\n"); + printf(" search <expression> -- Seek accounts\n"); +// printf(" search -e/-r/--expr/--regex <expressn> -- Seek accounts by regular-expression\n"); + printf(" sex <nomcompte> <sexe> -- Modify the sex of an account\n"); + printf(" state <account_name> <new_state> <error_message_#7> -- Change the state\n"); + printf(" timeadd/ta <account_name> <modifier> -- Add or substract time from the\n"); + printf(" example: ta apple +1m-2mn1s-2y validity limit of an account\n"); + printf(" timeset/ts <account_name> yyyy/mm/dd [hh:mm:ss] -- Change the validify limit\n"); + printf(" timeset/ts <account_name> 0 -- Give a unlimited validity limit\n"); + printf(" unban/unbanish <account name> -- Remove the banishment of an account\n"); + printf(" unblock <account name> -- Set state 0 (Account ok) to an account\n"); + printf(" version -- Gives the version of the login-server\n"); + printf(" who <account name> -- Display all information of an account\n"); + printf(" who <account name> -- Display all information of an account\n"); + printf(" Note: To use spaces in an account name, type \"<account name>\" (or ').\n"); + } + } +} + +//----------------------------- +// Sub-function: add an account +//----------------------------- +int addaccount(char* param, int emailflag) { + char name[1023], sex[1023], email[1023], password[1023]; +// int i; + + memset(name, '\0', sizeof(name)); + memset(sex, '\0', sizeof(sex)); + memset(email, '\0', sizeof(email)); + memset(password, '\0', sizeof(password)); + + if (emailflag == 0) { // add command + if (sscanf(param, "\"%[^\"]\" %s %[^\r\n]", name, sex, password) < 2 && // password can be void + sscanf(param, "'%[^']' %s %[^\r\n]", name, sex, password) < 2 && // password can be void + sscanf(param, "%s %s %[^\r\n]", name, sex, password) < 2) { // password can be void + if (defaultlanguage == 'F') { + printf("Entrez un nom de compte, un sexe et un mot de passe svp.\n"); + printf("<exemple> add nomtest Male motdepassetest\n"); + ladmin_log("Nombre incorrect de param鑼res pour cr馥r un compte (commande 'add')." RETCODE); + } else { + printf("Please input an account name, a sex and a password.\n"); + printf("<example> add testname Male testpass\n"); + ladmin_log("Incomplete parameters to create an account ('add' command)." RETCODE); + } + return 136; + } + strcpy(email, "a@a.com"); // default email + } else { // 1: create command + if (sscanf(param, "\"%[^\"]\" %s %s %[^\r\n]", name, sex, email, password) < 3 && // password can be void + sscanf(param, "'%[^']' %s %s %[^\r\n]", name, sex, email, password) < 3 && // password can be void + sscanf(param, "%s %s %s %[^\r\n]", name, sex, email, password) < 3) { // password can be void + if (defaultlanguage == 'F') { + printf("Entrez un nom de compte, un sexe et un mot de passe svp.\n"); + printf("<exemple> create nomtest Male mo@mail.com motdepassetest\n"); + ladmin_log("Nombre incorrect de param鑼res pour cr馥r un compte (commande 'create')." RETCODE); + } else { + printf("Please input an account name, a sex and a password.\n"); + printf("<example> create testname Male my@mail.com testpass\n"); + ladmin_log("Incomplete parameters to create an account ('create' command)." RETCODE); + } + return 136; + } + } + if (verify_accountname(name) == 0) { + return 102; + } + +/* for(i = 0; name[i]; i++) { + if (strchr("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_", name[i]) == NULL) { + if (defaultlanguage == 'F') { + printf("Caract鑽e interdit (%c) trouv dans le nom du compte (%d%s caract鑽e).\n", name[i], i+1, makeordinal(i+1)); + ladmin_log("Caract鑽e interdit (%c) trouv dans le nom du compte (%d%s caract鑽e)." RETCODE, name[i], i+1, makeordinal(i+1)); + } else { + printf("Illegal character (%c) found in the account name (%d%s character).\n", name[i], i+1, makeordinal(i+1)); + ladmin_log("Illegal character (%c) found in the account name (%d%s character)." RETCODE, name[i], i+1, makeordinal(i+1)); + } + return 101; + } + }*/ + + sex[0] = toupper(sex[0]); + if (strchr("MF", sex[0]) == NULL) { + if (defaultlanguage == 'F') { + printf("Sexe incorrect [%s]. Entrez M ou F svp.\n", sex); + ladmin_log("Sexe incorrect [%s]. Entrez M ou F svp." RETCODE, sex); + } else { + printf("Illegal gender [%s]. Please input M or F.\n", sex); + ladmin_log("Illegal gender [%s]. Please input M or F." RETCODE, sex); + } + return 103; + } + + if (strlen(email) < 3) { + if (defaultlanguage == 'F') { + printf("Email trop courte [%s]. Entrez une e-mail valide svp.\n", email); + ladmin_log("Email trop courte [%s]. Entrez une e-mail valide svp." RETCODE, email); + } else { + printf("Email is too short [%s]. Please input a valid e-mail.\n", email); + ladmin_log("Email is too short [%s]. Please input a valid e-mail." RETCODE, email); + } + return 109; + } + if (strlen(email) > 39) { + if (defaultlanguage == 'F') { + printf("Email trop longue [%s]. Entrez une e-mail de 39 caract鑽es maximum svp.\n", email); + ladmin_log("Email trop longue [%s]. Entrez une e-mail de 39 caract鑽es maximum svp." RETCODE, email); + } else { + printf("Email is too long [%s]. Please input an e-mail with 39 bytes at the most.\n", email); + ladmin_log("Email is too long [%s]. Please input an e-mail with 39 bytes at the most." RETCODE, email); + } + return 109; + } + if (e_mail_check(email) == 0) { + if (defaultlanguage == 'F') { + printf("Email incorrecte [%s]. Entrez une e-mail valide svp.\n", email); + ladmin_log("Email incorrecte [%s]. Entrez une e-mail valide svp." RETCODE, email); + } else { + printf("Invalid email [%s]. Please input a valid e-mail.\n", email); + ladmin_log("Invalid email [%s]. Please input a valid e-mail." RETCODE, email); + } + return 109; + } + + if (strlen(password) == 0) { + if (typepasswd(password) == 0) + return 108; + } + if (verify_password(password) == 0) + return 104; + + if (defaultlanguage == 'F') { + ladmin_log("Envoi d'un requ黎e au serveur de logins pour cr馥r un compte." RETCODE); + } else { + ladmin_log("Request to login-server to create an account." RETCODE); + } + + WFIFOW(login_fd,0) = 0x7930; + memcpy(WFIFOP(login_fd,2), name, 24); + memcpy(WFIFOP(login_fd,26), password, 24); + WFIFOB(login_fd,50) = sex[0]; + memcpy(WFIFOP(login_fd,51), email, 40); + WFIFOSET(login_fd,91); + bytes_to_read = 1; + + return 0; +} + +//--------------------------------------------------------------------------------- +// Sub-function: Add/substract time to the final date of a banishment of an account +//--------------------------------------------------------------------------------- +int banaddaccount(char* param) { + char name[1023], modif[1023]; + int year, month, day, hour, minute, second; + char * p_modif; + int value, i; + + memset(name, '\0', sizeof(name)); + memset(modif, '\0', sizeof(modif)); + year = month = day = hour = minute = second = 0; + + if (sscanf(param, "\"%[^\"]\" %[^\r\n]", name, modif) < 2 && + sscanf(param, "'%[^']' %[^\r\n]", name, modif) < 2 && + sscanf(param, "%s %[^\r\n]", name, modif) < 2) { + if (defaultlanguage == 'F') { + printf("Entrez un nom de compte et un modificateur svp.\n"); + printf(" <exemple> banadd nomtest +1m-2mn1s-6y\n"); + printf(" Cette exemple ajoute 1 mois et 1 seconde, et soustrait 2 minutes\n"); + printf(" et 6 ans dans le m麥e temps.\n"); + ladmin_log("Nombre incorrect de param鑼res pour modifier la fin de ban d'un compte (commande 'banadd')." RETCODE); + } else { + printf("Please input an account name and a modifier.\n"); + printf(" <example>: banadd testname +1m-2mn1s-6y\n"); + printf(" this example adds 1 month and 1 second, and substracts 2 minutes\n"); + printf(" and 6 years at the same time.\n"); + ladmin_log("Incomplete parameters to modify the ban date/time of an account ('banadd' command)." RETCODE); + } + return 136; + } + if (verify_accountname(name) == 0) { + return 102; + } + + // lowercase for modif + for (i = 0; modif[i]; i++) + modif[i] = tolower(modif[i]); + p_modif = modif; + while (strlen(p_modif) > 0) { + value = atoi(p_modif); + if (value == 0) { + p_modif++; + } else { + if (p_modif[0] == '-' || p_modif[0] == '+') + p_modif++; + while (strlen(p_modif) > 0 && p_modif[0] >= '0' && p_modif[0] <= '9') { + p_modif++; + } + if (p_modif[0] == 's') { + second = value; + p_modif++; + } else if (p_modif[0] == 'm' && p_modif[1] == 'n') { + minute = value; + p_modif += 2; + } else if (p_modif[0] == 'h') { + hour = value; + p_modif++; + } else if (p_modif[0] == 'd' || p_modif[0] == 'j') { + day = value; + p_modif += 2; + } else if (p_modif[0] == 'm') { + month = value; + p_modif++; + } else if (p_modif[0] == 'y' || p_modif[0] == 'a') { + year = value; + p_modif++; + } else { + p_modif++; + } + } + } + + if (defaultlanguage == 'F') { + printf(" ann馥: %d\n", year); + printf(" mois: %d\n", month); + printf(" jour: %d\n", day); + printf(" heure: %d\n", hour); + printf(" minute: %d\n", minute); + printf(" seconde: %d\n", second); + } else { + printf(" year: %d\n", year); + printf(" month: %d\n", month); + printf(" day: %d\n", day); + printf(" hour: %d\n", hour); + printf(" minute: %d\n", minute); + printf(" second: %d\n", second); + } + + if (year == 0 && month == 0 && day == 0 && hour == 0 && minute == 0 && second == 0) { + if (defaultlanguage == 'F') { + printf("Vous devez entrer un ajustement avec cette commande, svp:\n"); + printf(" Valeur d'ajustement (-1, 1, +1, etc...)\n"); + printf(" Element modifi:\n"); + printf(" a ou y: ann馥\n"); + printf(" m: mois\n"); + printf(" j ou d: jour\n"); + printf(" h: heure\n"); + printf(" mn: minute\n"); + printf(" s: seconde\n"); + printf(" <exemple> banadd nomtest +1m-2mn1s-6y\n"); + printf(" Cette exemple ajoute 1 mois et 1 seconde, et soustrait 2 minutes\n"); + printf(" et 6 ans dans le m麥e temps.\n"); + ladmin_log("Aucun ajustement n'est pas un ajustement (commande 'banadd')." RETCODE); + } else { + printf("Please give an adjustment with this command:\n"); + printf(" Adjustment value (-1, 1, +1, etc...)\n"); + printf(" Modified element:\n"); + printf(" a or y: year\n"); + printf(" m: month\n"); + printf(" j or d: day\n"); + printf(" h: hour\n"); + printf(" mn: minute\n"); + printf(" s: second\n"); + printf(" <example> banadd testname +1m-2mn1s-6y\n"); + printf(" this example adds 1 month and 1 second, and substracts 2 minutes\n"); + printf(" and 6 years at the same time.\n"); + ladmin_log("No adjustment isn't an adjustment ('banadd' command)." RETCODE); + } + return 137; + } + if (year > 127 || year < -127) { + if (defaultlanguage == 'F') { + printf("Entrez un ajustement d'ann馥s correct (de -127 127), svp.\n"); + ladmin_log("Ajustement de l'ann馥 hors norme (commande 'banadd')." RETCODE); + } else { + printf("Please give a correct adjustment for the years (from -127 to 127).\n"); + ladmin_log("Abnormal adjustement for the year ('banadd' command)." RETCODE); + } + return 137; + } + if (month > 255 || month < -255) { + if (defaultlanguage == 'F') { + printf("Entrez un ajustement de mois correct (de -255 255), svp.\n"); + ladmin_log("Ajustement du mois hors norme (commande 'banadd')." RETCODE); + } else { + printf("Please give a correct adjustment for the months (from -255 to 255).\n"); + ladmin_log("Abnormal adjustement for the month ('banadd' command)." RETCODE); + } + return 137; + } + if (day > 32767 || day < -32767) { + if (defaultlanguage == 'F') { + printf("Entrez un ajustement de jours correct (de -32767 32767), svp.\n"); + ladmin_log("Ajustement des jours hors norme (commande 'banadd')." RETCODE); + } else { + printf("Please give a correct adjustment for the days (from -32767 to 32767).\n"); + ladmin_log("Abnormal adjustement for the days ('banadd' command)." RETCODE); + } + return 137; + } + if (hour > 32767 || hour < -32767) { + if (defaultlanguage == 'F') { + printf("Entrez un ajustement d'heures correct (de -32767 32767), svp.\n"); + ladmin_log("Ajustement des heures hors norme (commande 'banadd')." RETCODE); + } else { + printf("Please give a correct adjustment for the hours (from -32767 to 32767).\n"); + ladmin_log("Abnormal adjustement for the hours ('banadd' command)." RETCODE); + } + return 137; + } + if (minute > 32767 || minute < -32767) { + if (defaultlanguage == 'F') { + printf("Entrez un ajustement de minutes correct (de -32767 32767), svp.\n"); + ladmin_log("Ajustement des minutes hors norme (commande 'banadd')." RETCODE); + } else { + printf("Please give a correct adjustment for the minutes (from -32767 to 32767).\n"); + ladmin_log("Abnormal adjustement for the minutes ('banadd' command)." RETCODE); + } + return 137; + } + if (second > 32767 || second < -32767) { + if (defaultlanguage == 'F') { + printf("Entrez un ajustement de secondes correct (de -32767 32767), svp.\n"); + ladmin_log("Ajustement des secondes hors norme (commande 'banadd')." RETCODE); + } else { + printf("Please give a correct adjustment for the seconds (from -32767 to 32767).\n"); + ladmin_log("Abnormal adjustement for the seconds ('banadd' command)." RETCODE); + } + return 137; + } + + if (defaultlanguage == 'F') { + ladmin_log("Envoi d'un requ黎e au serveur de logins pour modifier la date d'un bannissement." RETCODE); + } else { + ladmin_log("Request to login-server to modify a ban date/time." RETCODE); + } + + WFIFOW(login_fd,0) = 0x794c; + memcpy(WFIFOP(login_fd,2), name, 24); + WFIFOW(login_fd,26) = (short)year; + WFIFOW(login_fd,28) = (short)month; + WFIFOW(login_fd,30) = (short)day; + WFIFOW(login_fd,32) = (short)hour; + WFIFOW(login_fd,34) = (short)minute; + WFIFOW(login_fd,36) = (short)second; + WFIFOSET(login_fd,38); + bytes_to_read = 1; + + return 0; +} + +//----------------------------------------------------------------------- +// Sub-function of sub-function banaccount, unbanaccount or bansetaccount +// Set the final date of a banishment of an account +//----------------------------------------------------------------------- +int bansetaccountsub(char* name, char* date, char* time) { + int year, month, day, hour, minute, second; + time_t ban_until_time; // # of seconds 1/1/1970 (timestamp): ban time limit of the account (0 = no ban) + struct tm *tmtime; + + year = month = day = hour = minute = second = 0; + ban_until_time = 0; + tmtime = localtime(&ban_until_time); // initialize + + if (verify_accountname(name) == 0) { + return 102; + } + + if (atoi(date) != 0 && + ((sscanf(date, "%d/%d/%d", &year, &month, &day) < 3 && + sscanf(date, "%d-%d-%d", &year, &month, &day) < 3 && + sscanf(date, "%d.%d.%d", &year, &month, &day) < 3) || + sscanf(time, "%d:%d:%d", &hour, &minute, &second) < 3)) { + if (defaultlanguage == 'F') { + printf("Entrez une date et une heure svp (format: aaaa/mm/jj hh:mm:ss).\n"); + printf("Vous pouvez aussi mettre 0 la place si vous utilisez la commande 'banset'.\n"); + ladmin_log("Format incorrect pour la date/heure (commande'banset' ou 'ban')." RETCODE); + } else { + printf("Please input a date and a time (format: yyyy/mm/dd hh:mm:ss).\n"); + printf("You can imput 0 instead of if you use 'banset' command.\n"); + ladmin_log("Invalid format for the date/time ('banset' or 'ban' command)." RETCODE); + } + return 102; + } + + if (atoi(date) == 0) { + ban_until_time = 0; + } else { + if (year < 70) { + year = year + 100; + } + if (year >= 1900) { + year = year - 1900; + } + if (month < 1 || month > 12) { + if (defaultlanguage == 'F') { + printf("Entrez un mois correct svp (entre 1 et 12).\n"); + ladmin_log("Mois incorrect pour la date (command 'banset' ou 'ban')." RETCODE); + } else { + printf("Please give a correct value for the month (from 1 to 12).\n"); + ladmin_log("Invalid month for the date ('banset' or 'ban' command)." RETCODE); + } + return 102; + } + month = month - 1; + if (day < 1 || day > 31) { + if (defaultlanguage == 'F') { + printf("Entrez un jour correct svp (entre 1 et 31).\n"); + ladmin_log("Jour incorrect pour la date (command 'banset' ou 'ban')." RETCODE); + } else { + printf("Please give a correct value for the day (from 1 to 31).\n"); + ladmin_log("Invalid day for the date ('banset' or 'ban' command)." RETCODE); + } + return 102; + } + if (((month == 3 || month == 5 || month == 8 || month == 10) && day > 30) || + (month == 1 && day > 29)) { + if (defaultlanguage == 'F') { + printf("Entrez un jour correct en fonction du mois (%d) svp.\n", month); + ladmin_log("Jour incorrect pour ce mois correspondant (command 'banset' ou 'ban')." RETCODE); + } else { + printf("Please give a correct value for a day of this month (%d).\n", month); + ladmin_log("Invalid day for this month ('banset' or 'ban' command)." RETCODE); + } + return 102; + } + if (hour < 0 || hour > 23) { + if (defaultlanguage == 'F') { + printf("Entrez une heure correcte svp (entre 0 et 23).\n"); + ladmin_log("Heure incorrecte pour l'heure (command 'banset' ou 'ban')." RETCODE); + } else { + printf("Please give a correct value for the hour (from 0 to 23).\n"); + ladmin_log("Invalid hour for the time ('banset' or 'ban' command)." RETCODE); + } + return 102; + } + if (minute < 0 || minute > 59) { + if (defaultlanguage == 'F') { + printf("Entrez des minutes correctes svp (entre 0 et 59).\n"); + ladmin_log("Minute incorrecte pour l'heure (command 'banset' ou 'ban')." RETCODE); + } else { + printf("Please give a correct value for the minutes (from 0 to 59).\n"); + ladmin_log("Invalid minute for the time ('banset' or 'ban' command)." RETCODE); + } + return 102; + } + if (second < 0 || second > 59) { + if (defaultlanguage == 'F') { + printf("Entrez des secondes correctes svp (entre 0 et 59).\n"); + ladmin_log("Seconde incorrecte pour l'heure (command 'banset' ou 'ban')." RETCODE); + } else { + printf("Please give a correct value for the seconds (from 0 to 59).\n"); + ladmin_log("Invalid second for the time ('banset' or 'ban' command)." RETCODE); + } + return 102; + } + tmtime->tm_year = year; + tmtime->tm_mon = month; + tmtime->tm_mday = day; + tmtime->tm_hour = hour; + tmtime->tm_min = minute; + tmtime->tm_sec = second; + tmtime->tm_isdst = -1; // -1: no winter/summer time modification + ban_until_time = mktime(tmtime); + if (ban_until_time == -1) { + if (defaultlanguage == 'F') { + printf("Date incorrecte.\n"); + printf("Entrez une date et une heure svp (format: aaaa/mm/jj hh:mm:ss).\n"); + printf("Vous pouvez aussi mettre 0 la place si vous utilisez la commande 'banset'.\n"); + ladmin_log("Date incorrecte. (command 'banset' ou 'ban')." RETCODE); + } else { + printf("Invalid date.\n"); + printf("Please input a date and a time (format: yyyy/mm/dd hh:mm:ss).\n"); + printf("You can imput 0 instead of if you use 'banset' command.\n"); + ladmin_log("Invalid date. ('banset' or 'ban' command)." RETCODE); + } + return 102; + } + } + + if (defaultlanguage == 'F') { + ladmin_log("Envoi d'un requ黎e au serveur de logins pour fixer un ban." RETCODE); + } else { + ladmin_log("Request to login-server to set a ban." RETCODE); + } + + WFIFOW(login_fd,0) = 0x794a; + memcpy(WFIFOP(login_fd,2), name, 24); + WFIFOL(login_fd,26) = (int)ban_until_time; + WFIFOSET(login_fd,30); + bytes_to_read = 1; + + return 0; +} + +//--------------------------------------------------------------------- +// Sub-function: Set the final date of a banishment of an account (ban) +//--------------------------------------------------------------------- +int banaccount(char* param) { + char name[1023], date[1023], time[1023]; + + memset(name, '\0', sizeof(name)); + memset(date, '\0', sizeof(date)); + memset(time, '\0', sizeof(time)); + + if (sscanf(param, "%s %s \"%[^\"]\"", date, time, name) < 3 && + sscanf(param, "%s %s '%[^']'", date, time, name) < 3 && + sscanf(param, "%s %s %[^\r\n]", date, time, name) < 3) { + if (defaultlanguage == 'F') { + printf("Entrez un nom de compte, une date et une heure svp.\n"); + printf("<exemple>: banset <nom_du_compte> aaaa/mm/jj [hh:mm:ss]\n"); + printf(" banset <nom_du_compte> 0 (0 = d-bani)\n"); + printf(" ban/banish aaaa/mm/jj hh:mm:ss <nom du compte>\n"); + printf(" unban/unbanish <nom du compte>\n"); + printf(" Heure par d馭aut [hh:mm:ss]: 23:59:59.\n"); + ladmin_log("Nombre incorrect de param鑼res pour fixer un ban (commande 'banset' ou 'ban')." RETCODE); + } else { + printf("Please input an account name, a date and a hour.\n"); + printf("<example>: banset <account_name> yyyy/mm/dd [hh:mm:ss]\n"); + printf(" banset <account_name> 0 (0 = un-banished)\n"); + printf(" ban/banish yyyy/mm/dd hh:mm:ss <account name>\n"); + printf(" unban/unbanish <account name>\n"); + printf(" Default time [hh:mm:ss]: 23:59:59.\n"); + ladmin_log("Incomplete parameters to set a ban ('banset' or 'ban' command)." RETCODE); + } + return 136; + } + + return bansetaccountsub(name, date, time); +} + +//------------------------------------------------------------------------ +// Sub-function: Set the final date of a banishment of an account (banset) +//------------------------------------------------------------------------ +int bansetaccount(char* param) { + char name[1023], date[1023], time[1023]; + + memset(name, '\0', sizeof(name)); + memset(date, '\0', sizeof(date)); + memset(time, '\0', sizeof(time)); + + if (sscanf(param, "\"%[^\"]\" %s %[^\r\n]", name, date, time) < 2 && // if date = 0, time can be void + sscanf(param, "'%[^']' %s %[^\r\n]", name, date, time) < 2 && // if date = 0, time can be void + sscanf(param, "%s %s %[^\r\n]", name, date, time) < 2) { // if date = 0, time can be void + if (defaultlanguage == 'F') { + printf("Entrez un nom de compte, une date et une heure svp.\n"); + printf("<exemple>: banset <nom_du_compte> aaaa/mm/jj [hh:mm:ss]\n"); + printf(" banset <nom_du_compte> 0 (0 = d-bani)\n"); + printf(" ban/banish aaaa/mm/jj hh:mm:ss <nom du compte>\n"); + printf(" unban/unbanish <nom du compte>\n"); + printf(" Heure par d馭aut [hh:mm:ss]: 23:59:59.\n"); + ladmin_log("Nombre incorrect de param鑼res pour fixer un ban (commande 'banset' ou 'ban')." RETCODE); + } else { + printf("Please input an account name, a date and a hour.\n"); + printf("<example>: banset <account_name> yyyy/mm/dd [hh:mm:ss]\n"); + printf(" banset <account_name> 0 (0 = un-banished)\n"); + printf(" ban/banish yyyy/mm/dd hh:mm:ss <account name>\n"); + printf(" unban/unbanish <account name>\n"); + printf(" Default time [hh:mm:ss]: 23:59:59.\n"); + ladmin_log("Incomplete parameters to set a ban ('banset' or 'ban' command)." RETCODE); + } + return 136; + } + + if (time[0] == '\0') + strcpy(time, "23:59:59"); + + return bansetaccountsub(name, date, time); +} + +//------------------------------------------------- +// Sub-function: unbanishment of an account (unban) +//------------------------------------------------- +int unbanaccount(char* param) { + char name[1023]; + + memset(name, '\0', sizeof(name)); + + if (strlen(param) == 0 || + (sscanf(param, "\"%[^\"]\"", name) < 1 && + sscanf(param, "'%[^']'", name) < 1 && + sscanf(param, "%[^\r\n]", name) < 1) || + strlen(name) == 0) { + if (defaultlanguage == 'F') { + printf("Entrez un nom de compte svp.\n"); + printf("<exemple>: banset <nom_du_compte> aaaa/mm/jj [hh:mm:ss]\n"); + printf(" banset <nom_du_compte> 0 (0 = d-bani)\n"); + printf(" ban/banish aaaa/mm/jj hh:mm:ss <nom du compte>\n"); + printf(" unban/unbanish <nom du compte>\n"); + printf(" Heure par d馭aut [hh:mm:ss]: 23:59:59.\n"); + ladmin_log("Nombre incorrect de param鑼res pour fixer un ban (commande 'unban')." RETCODE); + } else { + printf("Please input an account name.\n"); + printf("<example>: banset <account_name> yyyy/mm/dd [hh:mm:ss]\n"); + printf(" banset <account_name> 0 (0 = un-banished)\n"); + printf(" ban/banish yyyy/mm/dd hh:mm:ss <account name>\n"); + printf(" unban/unbanish <account name>\n"); + printf(" Default time [hh:mm:ss]: 23:59:59.\n"); + ladmin_log("Incomplete parameters to set a ban ('unban' command)." RETCODE); + } + return 136; + } + + return bansetaccountsub(name, "0", ""); +} + +//--------------------------------------------------------- +// Sub-function: Asking to check the validity of a password +// (Note: never send back a password with login-server!! security of passwords) +//--------------------------------------------------------- +int checkaccount(char* param) { + char name[1023], password[1023]; + + memset(name, '\0', sizeof(name)); + memset(password, '\0', sizeof(password)); + + if (sscanf(param, "\"%[^\"]\" %[^\r\n]", name, password) < 1 && // password can be void + sscanf(param, "'%[^']' %[^\r\n]", name, password) < 1 && // password can be void + sscanf(param, "%s %[^\r\n]", name, password) < 1) { // password can be void + if (defaultlanguage == 'F') { + printf("Entrez un nom de compte svp.\n"); + printf("<exemple> check testname motdepasse\n"); + ladmin_log("Nombre incorrect de param鑼res pour tester le mot d'un passe d'un compte (commande 'check')." RETCODE); + } else { + printf("Please input an account name.\n"); + printf("<example> check testname password\n"); + ladmin_log("Incomplete parameters to check the password of an account ('check' command)." RETCODE); + } + return 136; + } + + if (verify_accountname(name) == 0) { + return 102; + } + + if (strlen(password) == 0) { + if (typepasswd(password) == 0) + return 134; + } + if (verify_password(password) == 0) + return 131; + + if (defaultlanguage == 'F') { + ladmin_log("Envoi d'un requ黎e au serveur de logins pour test un mot de passe." RETCODE); + } else { + ladmin_log("Request to login-server to check a password." RETCODE); + } + + WFIFOW(login_fd,0) = 0x793a; + memcpy(WFIFOP(login_fd,2), name, 24); + memcpy(WFIFOP(login_fd,26), password, 24); + WFIFOSET(login_fd,50); + bytes_to_read = 1; + + return 0; +} + +//------------------------------------------------ +// Sub-function: Asking for deletion of an account +//------------------------------------------------ +int delaccount(char* param) { + char name[1023]; + char letter; + char confirm[1023]; + int i; + + memset(name, '\0', sizeof(name)); + + if (strlen(param) == 0 || + (sscanf(param, "\"%[^\"]\"", name) < 1 && + sscanf(param, "'%[^']'", name) < 1 && + sscanf(param, "%[^\r\n]", name) < 1) || + strlen(name) == 0) { + if (defaultlanguage == 'F') { + printf("Entrez un nom de compte svp.\n"); + printf("<exemple> del nomtestasupprimer\n"); + ladmin_log("Aucun nom donn pour supprimer un compte (commande 'delete')." RETCODE); + } else { + printf("Please input an account name.\n"); + printf("<example> del testnametodelete\n"); + ladmin_log("No name given to delete an account ('delete' command)." RETCODE); + } + return 136; + } + + if (verify_accountname(name) == 0) { + return 102; + } + + memset(confirm, '\0', sizeof(confirm)); + while ((confirm[0] != 'o' || defaultlanguage != 'F') && confirm[0] != 'n' && (confirm[0] != 'y' || defaultlanguage == 'F')) { + if (defaultlanguage == 'F') + printf("\033[1;36m ** Etes-vous vraiment sr de vouloir SUPPRIMER le compte [$userid]? (o/n) > \033[0m"); + else + printf("\033[1;36m ** Are you really sure to DELETE account [$userid]? (y/n) > \033[0m"); + fflush(stdout); + memset(confirm, '\0', sizeof(confirm)); + i = 0; + while ((letter = getchar()) != '\n') + confirm[i++] = letter; + } + + if (confirm[0] == 'n') { + if (defaultlanguage == 'F') { + printf("Suppression annul馥.\n"); + ladmin_log("Suppression annul馥 par l'utilisateur (commande 'delete')." RETCODE); + } else { + printf("Deletion canceled.\n"); + ladmin_log("Deletion canceled by user ('delete' command)." RETCODE); + } + return 121; + } + + if (defaultlanguage == 'F') { + ladmin_log("Envoi d'un requ黎e au serveur de logins pour d騁ruire un compte." RETCODE); + } else { + ladmin_log("Request to login-server to delete an acount." RETCODE); + } + + WFIFOW(login_fd,0) = 0x7932; + memcpy(WFIFOP(login_fd,2), name, 24); + WFIFOSET(login_fd,26); + bytes_to_read = 1; + + return 0; +} + +//---------------------------------------------------------- +// Sub-function: Asking to modification of an account e-mail +//---------------------------------------------------------- +int changeemail(char* param) { + char name[1023], email[1023]; + + memset(name, '\0', sizeof(name)); + memset(email, '\0', sizeof(email)); + + if (sscanf(param, "\"%[^\"]\" %[^\r\n]", name, email) < 2 && + sscanf(param, "'%[^']' %[^\r\n]", name, email) < 2 && + sscanf(param, "%s %[^\r\n]", name, email) < 2) { + if (defaultlanguage == 'F') { + printf("Entrez un nom de compte et une email svp.\n"); + printf("<exemple> email testname nouveauemail\n"); + ladmin_log("Nombre incorrect de param鑼res pour changer l'email d'un compte (commande 'email')." RETCODE); + } else { + printf("Please input an account name and an email.\n"); + printf("<example> email testname newemail\n"); + ladmin_log("Incomplete parameters to change the email of an account ('email' command)." RETCODE); + } + return 136; + } + + if (verify_accountname(name) == 0) { + return 102; + } + + if (strlen(email) < 3) { + if (defaultlanguage == 'F') { + printf("Email trop courte [%s]. Entrez une e-mail valide svp.\n", email); + ladmin_log("Email trop courte [%s]. Entrez une e-mail valide svp." RETCODE, email); + } else { + printf("Email is too short [%s]. Please input a valid e-mail.\n", email); + ladmin_log("Email is too short [%s]. Please input a valid e-mail." RETCODE, email); + } + return 109; + } + if (strlen(email) > 39) { + if (defaultlanguage == 'F') { + printf("Email trop longue [%s]. Entrez une e-mail de 39 caract鑽es maximum svp.\n", email); + ladmin_log("Email trop longue [%s]. Entrez une e-mail de 39 caract鑽es maximum svp." RETCODE, email); + } else { + printf("Email is too long [%s]. Please input an e-mail with 39 bytes at the most.\n", email); + ladmin_log("Email is too long [%s]. Please input an e-mail with 39 bytes at the most." RETCODE, email); + } + return 109; + } + if (e_mail_check(email) == 0) { + if (defaultlanguage == 'F') { + printf("Email incorrecte [%s]. Entrez une e-mail valide svp.\n", email); + ladmin_log("Email incorrecte [%s]. Entrez une e-mail valide svp." RETCODE, email); + } else { + printf("Invalid email [%s]. Please input a valid e-mail.\n", email); + ladmin_log("Invalid email [%s]. Please input a valid e-mail." RETCODE, email); + } + return 109; + } + + if (defaultlanguage == 'F') { + ladmin_log("Envoi d'un requ黎e au serveur de logins pour changer une email." RETCODE); + } else { + ladmin_log("Request to login-server to change an email." RETCODE); + } + + WFIFOW(login_fd,0) = 0x7940; + memcpy(WFIFOP(login_fd,2), name, 24); + memcpy(WFIFOP(login_fd,26), email, 40); + WFIFOSET(login_fd,66); + bytes_to_read = 1; + + return 0; +} + +//----------------------------------------------------- +// Sub-function: Asking of the number of online players +//----------------------------------------------------- +int getlogincount() { + if (defaultlanguage == 'F') { + ladmin_log("Envoi d'un requ黎e au serveur de logins pour obtenir le nombre de joueurs en jeu." RETCODE); + } else { + ladmin_log("Request to login-server to obtain the # of online players." RETCODE); + } + + WFIFOW(login_fd,0) = 0x7938; + WFIFOSET(login_fd,2); + bytes_to_read = 1; + + return 0; +} + +//---------------------------------------------------------- +// Sub-function: Asking to modify the GM level of an account +//---------------------------------------------------------- +int changegmlevel(char* param) { + char name[1023]; + int GM_level; + + memset(name, '\0', sizeof(name)); + GM_level = 0; + + if (sscanf(param, "\"%[^\"]\" %d", name, &GM_level) < 1 && + sscanf(param, "'%[^']' %d", name, &GM_level) < 1 && + sscanf(param, "%s %d", name, &GM_level) < 1) { + if (defaultlanguage == 'F') { + printf("Entrez un nom de compte et un niveau de GM svp.\n"); + printf("<exemple> gm nomtest 80\n"); + ladmin_log("Nombre incorrect de param鑼res pour changer le Niveau de GM d'un compte (commande 'gm')." RETCODE); + } else { + printf("Please input an account name and a GM level.\n"); + printf("<example> gm testname 80\n"); + ladmin_log("Incomplete parameters to change the GM level of an account ('gm' command)." RETCODE); + } + return 136; + } + + if (verify_accountname(name) == 0) { + return 102; + } + + if (GM_level < 0 || GM_level > 99) { + if (defaultlanguage == 'F') { + printf("Niveau de GM incorrect [%d]. Entrez une valeur de 0 99 svp.\n", GM_level); + ladmin_log("Niveau de GM incorrect [%d]. La valeur peut 黎re de 0 99." RETCODE, GM_level); + } else { + printf("Illegal GM level [%d]. Please input a value from 0 to 99.\n", GM_level); + ladmin_log("Illegal GM level [%d]. The value can be from 0 to 99." RETCODE, GM_level); + } + return 103; + } + + if (defaultlanguage == 'F') { + ladmin_log("Envoi d'un requ黎e au serveur de logins pour changer un niveau de GM." RETCODE); + } else { + ladmin_log("Request to login-server to change a GM level." RETCODE); + } + + WFIFOW(login_fd,0) = 0x793e; + memcpy(WFIFOP(login_fd,2), name, 24); + WFIFOB(login_fd,26) = GM_level; + WFIFOSET(login_fd,27); + bytes_to_read = 1; + + return 0; +} + +//--------------------------------------------- +// Sub-function: Asking to obtain an account id +//--------------------------------------------- +int idaccount(char* param) { + char name[1023]; + + memset(name, '\0', sizeof(name)); + + if (strlen(param) == 0 || + (sscanf(param, "\"%[^\"]\"", name) < 1 && + sscanf(param, "'%[^']'", name) < 1 && + sscanf(param, "%[^\r\n]", name) < 1) || + strlen(name) == 0) { + if (defaultlanguage == 'F') { + printf("Entrez un nom de compte svp.\n"); + printf("<exemple> id nomtest\n"); + ladmin_log("Aucun nom donn pour rechecher l'id d'un compte (commande 'id')." RETCODE); + } else { + printf("Please input an account name.\n"); + printf("<example> id testname\n"); + ladmin_log("No name given to search an account id ('id' command)." RETCODE); + } + return 136; + } + + if (verify_accountname(name) == 0) { + return 102; + } + + if (defaultlanguage == 'F') { + ladmin_log("Envoi d'un requ黎e au serveur de logins pour connatre l'id d'un compte." RETCODE); + } else { + ladmin_log("Request to login-server to know an account id." RETCODE); + } + + WFIFOW(login_fd,0) = 0x7944; + memcpy(WFIFOP(login_fd,2), name, 24); + WFIFOSET(login_fd,26); + bytes_to_read = 1; + + return 0; +} + +//---------------------------------------------------------------------------- +// Sub-function: Asking to displaying information about an account (by its id) +//---------------------------------------------------------------------------- +int infoaccount(int account_id) { + if (account_id < 0) { + if (defaultlanguage == 'F') { + printf("Entrez un id ayant une valeur positive svp.\n"); + ladmin_log("Une valeur n馮ative a 騁 donn pour trouver le compte." RETCODE); + } else { + printf("Please input a positive value for the id.\n"); + ladmin_log("Negative value was given to found the account." RETCODE); + } + return 136; + } + + if (defaultlanguage == 'F') { + ladmin_log("Envoi d'un requ黎e au serveur de logins pour obtenir le information d'un compte (par l'id)." RETCODE); + } else { + ladmin_log("Request to login-server to obtain information about an account (by its id)." RETCODE); + } + + WFIFOW(login_fd,0) = 0x7954; + WFIFOL(login_fd,2) = account_id; + WFIFOSET(login_fd,6); + bytes_to_read = 1; + + return 0; +} + +//--------------------------------------- +// Sub-function: Send a broadcast message +//--------------------------------------- +int sendbroadcast(short type, char* message) { + if (strlen(message) == 0) { + if (defaultlanguage == 'F') { + printf("Entrez un message svp.\n"); + if (type == 0) { + printf("<exemple> kami un message\n"); + } else { + printf("<exemple> kamib un message\n"); + } + ladmin_log("Le message est vide (commande 'kami(b)')." RETCODE); + } else { + printf("Please input a message.\n"); + if (type == 0) { + printf("<example> kami a message\n"); + } else { + printf("<example> kamib a message\n"); + } + ladmin_log("The message is void ('kami(b)' command)." RETCODE); + } + return 136; + } + + WFIFOW(login_fd,0) = 0x794e; + WFIFOW(login_fd,2) = type; + WFIFOL(login_fd,4) = strlen(message)+1; + memcpy(WFIFOP(login_fd,8), message, strlen(message)+1); + WFIFOSET(login_fd,8+strlen(message)+1); + bytes_to_read = 1; + + return 0; +} + +//-------------------------------------------- +// Sub-function: Change language of displaying +//-------------------------------------------- +int changelanguage(char* language) { + if (strlen(language) == 0) { + if (defaultlanguage == 'F') { + printf("Entrez une langue svp.\n"); + printf("<exemple> language english\n"); + printf(" language fran軋is\n"); + ladmin_log("La langue est vide (commande 'language')." RETCODE); + } else { + printf("Please input a language.\n"); + printf("<example> language english\n"); + printf(" language fran軋is\n"); + ladmin_log("The language is void ('language' command)." RETCODE); + } + return 136; + } + + language[0] = toupper(language[0]); + if (language[0] == 'F' || language[0] == 'E') { + defaultlanguage = language[0]; + if (defaultlanguage == 'F') { + printf("Changement de la langue d'affichage en Fran軋is.\n"); + ladmin_log("Changement de la langue d'affichage en Fran軋is." RETCODE); + } else { + printf("Displaying language changed to English.\n"); + ladmin_log("Displaying language changed to English." RETCODE); + } + } else { + if (defaultlanguage == 'F') { + printf("Langue non param騁r馥 (langues possibles: 'Fran軋is' ou 'English').\n"); + ladmin_log("Langue non param騁r馥 (Fran軋is ou English n馗essaire)." RETCODE); + } else { + printf("Undefined language (possible languages: Fran軋is or English).\n"); + ladmin_log("Undefined language (must be Fran軋is or English)." RETCODE); + } + } + + return 0; +} + +//-------------------------------------------------------- +// Sub-function: Asking to Displaying of the accounts list +//-------------------------------------------------------- +int listaccount(char* param, int type) { +//int list_first, list_last, list_type; // parameter to display a list of accounts + int i; + + list_type = type; + + // set default values + list_first = 0; + list_last = 0; + + if (list_type == 1) { // if listgm + // get all accounts = use default + } else if (list_type == 2) { // if search + for (i = 0; param[i]; i++) + param[i] = tolower(param[i]); + // get all accounts = use default + } else if (list_type == 3) { // if listban + // get all accounts = use default + } else if (list_type == 4) { // if listok + // get all accounts = use default + } else { // if list (list_type == 0) + switch(sscanf(param, "%d %d", &list_first, &list_last)) { + case 0: + // get all accounts = use default + break; + case 1: + list_last = 0; + // use tests of the following value + default: + if (list_first < 0) + list_first = 0; + if (list_last < list_first || list_last < 0) + list_last = 0; + break; + } + } + + if (defaultlanguage == 'F') { + ladmin_log("Envoi d'un requ黎e au serveur de logins pour obtenir la liste des comptes de %d %d." RETCODE, list_first, list_last); + } else { + ladmin_log("Request to login-server to obtain the list of accounts from %d to %d." RETCODE, list_first, list_last); + } + + WFIFOW(login_fd,0) = 0x7920; + WFIFOL(login_fd,2) = list_first; + WFIFOL(login_fd,6) = list_last; + WFIFOSET(login_fd,10); + bytes_to_read = 1; + + // 0123456789 01 01234567890123456789012301234 012345 0123456789012345678901234567 + if (defaultlanguage == 'F') { + printf(" id_compte GM nom_utilisateur sexe count statut\n"); + } else { + printf("account_id GM user_name sex count state\n"); + } + printf("-------------------------------------------------------------------------------\n"); + list_count = 0; + + return 0; +} + +//-------------------------------------------- +// Sub-function: Asking to modify a memo field +//-------------------------------------------- +int changememo(char* param) { + char name[1023], memo[1023]; + + memset(name, '\0', sizeof(name)); + memset(memo, '\0', sizeof(memo)); + + if (sscanf(param, "\"%[^\"]\" %[^\r\n]", name, memo) < 1 && // memo can be void + sscanf(param, "'%[^']' %[^\r\n]", name, memo) < 1 && // memo can be void + sscanf(param, "%s %[^\r\n]", name, memo) < 1) { // memo can be void + if (defaultlanguage == 'F') { + printf("Entrez un nom de compte et un m駑o svp.\n"); + printf("<exemple> memo nomtest nouveau memo\n"); + ladmin_log("Nombre incorrect de param鑼res pour changer le m駑o d'un compte (commande 'email')." RETCODE); + } else { + printf("Please input an account name and a memo.\n"); + printf("<example> memo testname new memo\n"); + ladmin_log("Incomplete parameters to change the memo of an account ('email' command)." RETCODE); + } + return 136; + } + + if (verify_accountname(name) == 0) { + return 102; + } + + if (strlen(memo) > 254) { + if (defaultlanguage == 'F') { + printf("M駑o trop long (%d caract鑽es).\n", strlen(memo)); + printf("Entrez un m駑o de 254 caract鑽es maximum svp.\n"); + ladmin_log("M駑o trop long (%d caract鑽es). Entrez un m駑o de 254 caract鑽es maximum svp." RETCODE, strlen(memo)); + } else { + printf("Memo is too long (%d characters).\n", strlen(memo)); + printf("Please input a memo of 254 bytes at the maximum.\n"); + ladmin_log("Email is too long (%d characters). Please input a memo of 254 bytes at the maximum." RETCODE, strlen(memo)); + } + return 102; + } + + if (defaultlanguage == 'F') { + ladmin_log("Envoi d'un requ黎e au serveur de logins pour changer un m駑o." RETCODE); + } else { + ladmin_log("Request to login-server to change a memo." RETCODE); + } + + WFIFOW(login_fd,0) = 0x7942; + memcpy(WFIFOP(login_fd,2), name, 24); + WFIFOW(login_fd,26) = strlen(memo); + if (strlen(memo) > 0) + memcpy(WFIFOP(login_fd,28), memo, strlen(memo)); + WFIFOSET(login_fd,28+strlen(memo)); + bytes_to_read = 1; + + return 0; +} + +//----------------------------------------------- +// Sub-function: Asking to obtain an account name +//----------------------------------------------- +int nameaccount(int id) { + if (id < 0) { + if (defaultlanguage == 'F') { + printf("Entrez un id ayant une valeur positive svp.\n"); + ladmin_log("Id n馮atif donn pour rechecher le nom d'un compte (commande 'name')." RETCODE); + } else { + printf("Please input a positive value for the id.\n"); + ladmin_log("Negativ id given to search an account name ('name' command)." RETCODE); + } + return 136; + } + + if (defaultlanguage == 'F') + ladmin_log("Envoi d'un requ黎e au serveur de logins pour connatre le nom d'un compte." RETCODE); + else + ladmin_log("Request to login-server to know an account name." RETCODE); + + WFIFOW(login_fd,0) = 0x7946; + WFIFOL(login_fd,2) = id; + WFIFOSET(login_fd,6); + bytes_to_read = 1; + + return 0; +} + +//------------------------------------------ +// Sub-function: Asking to modify a password +// (Note: never send back a password with login-server!! security of passwords) +//------------------------------------------ +int changepasswd(char* param) { + char name[1023], password[1023]; + + memset(name, '\0', sizeof(name)); + memset(password, '\0', sizeof(password)); + + if (sscanf(param, "\"%[^\"]\" %[^\r\n]", name, password) < 1 && + sscanf(param, "'%[^']' %[^\r\n]", name, password) < 1 && + sscanf(param, "%s %[^\r\n]", name, password) < 1) { + if (defaultlanguage == 'F') { + printf("Entrez un nom de compte svp.\n"); + printf("<exemple> passwd nomtest nouveaumotdepasse\n"); + ladmin_log("Nombre incorrect de param鑼res pour changer le mot d'un passe d'un compte (commande 'password')." RETCODE); + } else { + printf("Please input an account name.\n"); + printf("<example> passwd testname newpassword\n"); + ladmin_log("Incomplete parameters to change the password of an account ('password' command)." RETCODE); + } + return 136; + } + + if (verify_accountname(name) == 0) { + return 102; + } + + if (strlen(password) == 0) { + if (typepasswd(password) == 0) + return 134; + } + if (verify_password(password) == 0) + return 131; + + if (defaultlanguage == 'F') { + ladmin_log("Envoi d'un requ黎e au serveur de logins pour changer un mot de passe." RETCODE); + } else { + ladmin_log("Request to login-server to change a password." RETCODE); + } + + WFIFOW(login_fd,0) = 0x7934; + memcpy(WFIFOP(login_fd,2), name, 24); + memcpy(WFIFOP(login_fd,26), password, 24); + WFIFOSET(login_fd,50); + bytes_to_read = 1; + + return 0; +} + +//---------------------------------------------------------------------- +// Sub-function: Request to login-server to reload GM configuration file +// this function have no answer +//---------------------------------------------------------------------- +int reloadGM() { + WFIFOW(login_fd,0) = 0x7955; + WFIFOSET(login_fd,2); + bytes_to_read = 0; + + if (defaultlanguage == 'F') { + ladmin_log("Demande de recharger le fichier de configuration des GM envoy馥." RETCODE); + printf("Demande de recharger le fichier de configuration des GM envoy馥.\n"); + printf("V駻ifiez les comptes GM actuels (apr鑚 rechargement):\n"); + } else { + ladmin_log("Request to reload the GM configuration file sended." RETCODE); + printf("Request to reload the GM configuration file sended.\n"); + printf("Check the actual GM accounts (after reloading):\n"); + } + listaccount(parameters, 1); // 1: to list only GM + + return 180; +} + +//----------------------------------------------------- +// Sub-function: Asking to modify the sex of an account +//----------------------------------------------------- +int changesex(char* param) { + char name[1023], sex[1023]; + + memset(name, '\0', sizeof(name)); + memset(sex, '\0', sizeof(sex)); + + if (sscanf(param, "\"%[^\"]\" %[^\r\n]", name, sex) < 2 && + sscanf(param, "'%[^']' %[^\r\n]", name, sex) < 2 && + sscanf(param, "%s %[^\r\n]", name, sex) < 2) { + if (defaultlanguage == 'F') { + printf("Entrez un nom de compte et un sexe svp.\n"); + printf("<exemple> sex nomtest Male\n"); + ladmin_log("Nombre incorrect de param鑼res pour changer le sexe d'un compte (commande 'sex')." RETCODE); + } else { + printf("Please input an account name and a sex.\n"); + printf("<example> sex testname Male\n"); + ladmin_log("Incomplete parameters to change the sex of an account ('sex' command)." RETCODE); + } + return 136; + } + + if (verify_accountname(name) == 0) { + return 102; + } + + sex[0] = toupper(sex[0]); + if (strchr("MF", sex[0]) == NULL) { + if (defaultlanguage == 'F') { + printf("Sexe incorrect [%s]. Entrez M ou F svp.\n", sex); + ladmin_log("Sexe incorrect [%s]. Entrez M ou F svp." RETCODE, sex); + } else { + printf("Illegal gender [%s]. Please input M or F.\n", sex); + ladmin_log("Illegal gender [%s]. Please input M or F." RETCODE, sex); + } + return 103; + } + + if (defaultlanguage == 'F') { + ladmin_log("Envoi d'un requ黎e au serveur de logins pour changer un sexe." RETCODE); + } else { + ladmin_log("Request to login-server to change a sex." RETCODE); + } + + WFIFOW(login_fd,0) = 0x793c; + memcpy(WFIFOP(login_fd,2), name, 24); + WFIFOB(login_fd,26) = sex[0]; + WFIFOSET(login_fd,27); + bytes_to_read = 1; + + return 0; +} + +//------------------------------------------------------------------------- +// Sub-function of sub-function changestate, blockaccount or unblockaccount +// Asking to modify the state of an account +//------------------------------------------------------------------------- +int changestatesub(char* name, int state, char* error_message7) { + char error_message[1023]; // need to use, because we can modify error_message7 + + memset(error_message, '\0', sizeof(error_message)); + strncpy(error_message, error_message7, sizeof(error_message)-1); + + if ((state < 0 || state > 9) && state != 100) { // Valid values: 0: ok, or value of the 0x006a packet + 1 + if (defaultlanguage == 'F') { + printf("Entrez une des statuts suivantes svp:\n"); + printf(" 0 = Compte ok 6 = Your Game's EXE file is not the latest version\n"); + } else { + printf("Please input one of these states:\n"); + printf(" 0 = Account ok 6 = Your Game's EXE file is not the latest version\n"); + } + printf(" 1 = Unregistered ID 7 = You are Prohibited to log in until + message\n"); + printf(" 2 = Incorrect Password 8 = Server is jammed due to over populated\n"); + printf(" 3 = This ID is expired 9 = No MSG\n"); + printf(" 4 = Rejected from Server 100 = This ID has been totally erased\n"); + printf(" 5 = You have been blocked by the GM Team\n"); + if (defaultlanguage == 'F') { + printf("<exemples> state nomtest 5\n"); + printf(" state nomtest 7 fin de votre ban\n"); + printf(" block <nom compte>\n"); + printf(" unblock <nom compte>\n"); + ladmin_log("Valeur incorrecte pour le statut d'un compte (commande 'state', 'block' ou 'unblock')." RETCODE); + } else { + printf("<examples> state testname 5\n"); + printf(" state testname 7 end of your ban\n"); + printf(" block <account name>\n"); + printf(" unblock <account name>\n"); + ladmin_log("Invalid value for the state of an account ('state', 'block' or 'unblock' command)." RETCODE); + } + return 151; + } + + if (verify_accountname(name) == 0) { + return 102; + } + + if (state != 7) { + strcpy(error_message, "-"); + } else { + if (strlen(error_message) < 1) { + if (defaultlanguage == 'F') { + printf("Message d'erreur trop court. Entrez un message de 1-19 caract鑽es.\n"); + ladmin_log("Message d'erreur trop court. Entrez un message de 1-19 caract鑽es." RETCODE); + } else { + printf("Error message is too short. Please input a message of 1-19 bytes.\n"); + ladmin_log("Error message is too short. Please input a message of 1-19 bytes." RETCODE); + } + return 102; + } + if (strlen(error_message) > 19) { + if (defaultlanguage == 'F') { + printf("Message d'erreur trop long. Entrez un message de 1-19 caract鑽es.\n"); + ladmin_log("Message d'erreur trop long. Entrez un message de 1-19 caract鑽es." RETCODE); + } else { + printf("Error message is too long. Please input a message of 1-19 bytes.\n"); + ladmin_log("Error message is too long. Please input a message of 1-19 bytes." RETCODE); + } + return 102; + } + } + + if (defaultlanguage == 'F') { + ladmin_log("Envoi d'un requ黎e au serveur de logins pour changer un statut." RETCODE); + } else { + ladmin_log("Request to login-server to change a state." RETCODE); + } + + WFIFOW(login_fd,0) = 0x7936; + memcpy(WFIFOP(login_fd,2), name, 24); + WFIFOL(login_fd,26) = state; + memcpy(WFIFOP(login_fd,30), error_message, 20); + WFIFOSET(login_fd,50); + bytes_to_read = 1; + + return 0; +} + +//------------------------------------------------------- +// Sub-function: Asking to modify the state of an account +//------------------------------------------------------- +int changestate(char* param) { + char name[1023], error_message[1023]; + int state; + + memset(name, '\0', sizeof(name)); + memset(error_message, '\0', sizeof(error_message)); + + if (sscanf(param, "\"%[^\"]\" %d %[^\r\n]", name, &state, error_message) < 2 && + sscanf(param, "'%[^']' %d %[^\r\n]", name, &state, error_message) < 2 && + sscanf(param, "%s %d %[^\r\n]", name, &state, error_message) < 2) { + if (defaultlanguage == 'F') { + printf("Entrez un nom de compte et un statut svp.\n"); + printf("<exemples> state nomtest 5\n"); + printf(" state nomtest 7 fin de votre ban\n"); + printf(" block <nom compte>\n"); + printf(" unblock <nom compte>\n"); + ladmin_log("Nombre incorrect de param鑼res pour changer le statut d'un compte (commande 'state')." RETCODE); + } else { + printf("Please input an account name and a state.\n"); + printf("<examples> state testname 5\n"); + printf(" state testname 7 end of your ban\n"); + printf(" block <account name>\n"); + printf(" unblock <account name>\n"); + ladmin_log("Incomplete parameters to change the state of an account ('state' command)." RETCODE); + } + return 136; + } + + return changestatesub(name, state, error_message); +} + +//------------------------------------------- +// Sub-function: Asking to unblock an account +//------------------------------------------- +int unblockaccount(char* param) { + char name[1023]; + + memset(name, '\0', sizeof(name)); + + if (strlen(param) == 0 || + (sscanf(param, "\"%[^\"]\"", name) < 1 && + sscanf(param, "'%[^']'", name) < 1 && + sscanf(param, "%[^\r\n]", name) < 1) || + strlen(name) == 0) { + if (defaultlanguage == 'F') { + printf("Entrez un nom de compte svp.\n"); + printf("<exemples> state nomtest 5\n"); + printf(" state nomtest 7 fin de votre ban\n"); + printf(" block <nom compte>\n"); + printf(" unblock <nom compte>\n"); + ladmin_log("Nombre incorrect de param鑼res pour changer le statut d'un compte (commande 'unblock')." RETCODE); + } else { + printf("Please input an account name.\n"); + printf("<examples> state testname 5\n"); + printf(" state testname 7 end of your ban\n"); + printf(" block <account name>\n"); + printf(" unblock <account name>\n"); + ladmin_log("Incomplete parameters to change the state of an account ('unblock' command)." RETCODE); + } + return 136; + } + + return changestatesub(name, 0, "-"); // state 0, no error message +} + +//------------------------------------------- +// Sub-function: Asking to unblock an account +//------------------------------------------- +int blockaccount(char* param) { + char name[1023]; + + memset(name, '\0', sizeof(name)); + + if (strlen(param) == 0 || + (sscanf(param, "\"%[^\"]\"", name) < 1 && + sscanf(param, "'%[^']'", name) < 1 && + sscanf(param, "%[^\r\n]", name) < 1) || + strlen(name) == 0) { + if (defaultlanguage == 'F') { + printf("Entrez un nom de compte svp.\n"); + printf("<exemples> state nomtest 5\n"); + printf(" state nomtest 7 fin de votre ban\n"); + printf(" block <nom compte>\n"); + printf(" unblock <nom compte>\n"); + ladmin_log("Nombre incorrect de param鑼res pour changer le statut d'un compte (commande 'block')." RETCODE); + } else { + printf("Please input an account name.\n"); + printf("<examples> state testname 5\n"); + printf(" state testname 7 end of your ban\n"); + printf(" block <account name>\n"); + printf(" unblock <account name>\n"); + ladmin_log("Incomplete parameters to change the state of an account ('block' command)." RETCODE); + } + return 136; + } + + return changestatesub(name, 5, "-"); // state 5, no error message +} + +//--------------------------------------------------------------------- +// Sub-function: Add/substract time to the validity limit of an account +//--------------------------------------------------------------------- +int timeaddaccount(char* param) { + char name[1023], modif[1023]; + int year, month, day, hour, minute, second; + char * p_modif; + int value, i; + + memset(name, '\0', sizeof(name)); + memset(modif, '\0', sizeof(modif)); + year = month = day = hour = minute = second = 0; + + if (sscanf(param, "\"%[^\"]\" %[^\r\n]", name, modif) < 2 && + sscanf(param, "'%[^']' %[^\r\n]", name, modif) < 2 && + sscanf(param, "%s %[^\r\n]", name, modif) < 2) { + if (defaultlanguage == 'F') { + printf("Entrez un nom de compte et un modificateur svp.\n"); + printf(" <exemple> timeadd nomtest +1m-2mn1s-6y\n"); + printf(" Cette exemple ajoute 1 mois et 1 seconde, et soustrait 2 minutes\n"); + printf(" et 6 ans dans le m麥e temps.\n"); + ladmin_log("Nombre incorrect de param鑼res pour modifier une date limite d'utilisation (commande 'timeadd')." RETCODE); + } else { + printf("Please input an account name and a modifier.\n"); + printf(" <example>: timeadd testname +1m-2mn1s-6y\n"); + printf(" this example adds 1 month and 1 second, and substracts 2 minutes\n"); + printf(" and 6 years at the same time.\n"); + ladmin_log("Incomplete parameters to modify a limit time ('timeadd' command)." RETCODE); + } + return 136; + } + if (verify_accountname(name) == 0) { + return 102; + } + + // lowercase for modif + for (i = 0; modif[i]; i++) + modif[i] = tolower(modif[i]); + p_modif = modif; + while (strlen(p_modif) > 0) { + value = atoi(p_modif); + if (value == 0) { + p_modif++; + } else { + if (p_modif[0] == '-' || p_modif[0] == '+') + p_modif++; + while (strlen(p_modif) > 0 && p_modif[0] >= '0' && p_modif[0] <= '9') { + p_modif++; + } + if (p_modif[0] == 's') { + second = value; + p_modif++; + } else if (p_modif[0] == 'm' && p_modif[1] == 'n') { + minute = value; + p_modif += 2; + } else if (p_modif[0] == 'h') { + hour = value; + p_modif++; + } else if (p_modif[0] == 'd' || p_modif[0] == 'j') { + day = value; + p_modif += 2; + } else if (p_modif[0] == 'm') { + month = value; + p_modif++; + } else if (p_modif[0] == 'y' || p_modif[0] == 'a') { + year = value; + p_modif++; + } else { + p_modif++; + } + } + } + + if (defaultlanguage == 'F') { + printf(" ann馥: %d\n", year); + printf(" mois: %d\n", month); + printf(" jour: %d\n", day); + printf(" heure: %d\n", hour); + printf(" minute: %d\n", minute); + printf(" seconde: %d\n", second); + } else { + printf(" year: %d\n", year); + printf(" month: %d\n", month); + printf(" day: %d\n", day); + printf(" hour: %d\n", hour); + printf(" minute: %d\n", minute); + printf(" second: %d\n", second); + } + + if (year == 0 && month == 0 && day == 0 && hour == 0 && minute == 0 && second == 0) { + if (defaultlanguage == 'F') { + printf("Vous devez entrer un ajustement avec cette commande, svp:\n"); + printf(" Valeur d'ajustement (-1, 1, +1, etc...)\n"); + printf(" El駑ent modifi:\n"); + printf(" a ou y: ann馥\n"); + printf(" m: mois\n"); + printf(" j ou d: jour\n"); + printf(" h: heure\n"); + printf(" mn: minute\n"); + printf(" s: seconde\n"); + printf(" <exemple> timeadd nomtest +1m-2mn1s-6y\n"); + printf(" Cette exemple ajoute 1 mois et 1 seconde, et soustrait 2 minutes\n"); + printf(" et 6 ans dans le m麥e temps.\n"); + ladmin_log("Aucun ajustement n'est pas un ajustement (commande 'timeadd')." RETCODE); + } else { + printf("Please give an adjustment with this command:\n"); + printf(" Adjustment value (-1, 1, +1, etc...)\n"); + printf(" Modified element:\n"); + printf(" a or y: year\n"); + printf(" m: month\n"); + printf(" j or d: day\n"); + printf(" h: hour\n"); + printf(" mn: minute\n"); + printf(" s: second\n"); + printf(" <example> timeadd testname +1m-2mn1s-6y\n"); + printf(" this example adds 1 month and 1 second, and substracts 2 minutes\n"); + printf(" and 6 years at the same time.\n"); + ladmin_log("No adjustment isn't an adjustment ('timeadd' command)." RETCODE); + } + return 137; + } + if (year > 127 || year < -127) { + if (defaultlanguage == 'F') { + printf("Entrez un ajustement d'ann馥s correct (de -127 127), svp.\n"); + ladmin_log("Ajustement de l'ann馥 hors norme ('timeadd' command)." RETCODE); + } else { + printf("Please give a correct adjustment for the years (from -127 to 127).\n"); + ladmin_log("Abnormal adjustement for the year ('timeadd' command)." RETCODE); + } + return 137; + } + if (month > 255 || month < -255) { + if (defaultlanguage == 'F') { + printf("Entrez un ajustement de mois correct (de -255 255), svp.\n"); + ladmin_log("Ajustement du mois hors norme ('timeadd' command)." RETCODE); + } else { + printf("Please give a correct adjustment for the months (from -255 to 255).\n"); + ladmin_log("Abnormal adjustement for the month ('timeadd' command)." RETCODE); + } + return 137; + } + if (day > 32767 || day < -32767) { + if (defaultlanguage == 'F') { + printf("Entrez un ajustement de jours correct (de -32767 32767), svp.\n"); + ladmin_log("Ajustement des jours hors norme ('timeadd' command)." RETCODE); + } else { + printf("Please give a correct adjustment for the days (from -32767 to 32767).\n"); + ladmin_log("Abnormal adjustement for the days ('timeadd' command)." RETCODE); + } + return 137; + } + if (hour > 32767 || hour < -32767) { + if (defaultlanguage == 'F') { + printf("Entrez un ajustement d'heures correct (de -32767 32767), svp.\n"); + ladmin_log("Ajustement des heures hors norme ('timeadd' command)." RETCODE); + } else { + printf("Please give a correct adjustment for the hours (from -32767 to 32767).\n"); + ladmin_log("Abnormal adjustement for the hours ('timeadd' command)." RETCODE); + } + return 137; + } + if (minute > 32767 || minute < -32767) { + if (defaultlanguage == 'F') { + printf("Entrez un ajustement de minutes correct (de -32767 32767), svp.\n"); + ladmin_log("Ajustement des minutes hors norme ('timeadd' command)." RETCODE); + } else { + printf("Please give a correct adjustment for the minutes (from -32767 to 32767).\n"); + ladmin_log("Abnormal adjustement for the minutes ('timeadd' command)." RETCODE); + } + return 137; + } + if (second > 32767 || second < -32767) { + if (defaultlanguage == 'F') { + printf("Entrez un ajustement de secondes correct (de -32767 32767), svp.\n"); + ladmin_log("Ajustement des secondes hors norme ('timeadd' command)." RETCODE); + } else { + printf("Please give a correct adjustment for the seconds (from -32767 to 32767).\n"); + ladmin_log("Abnormal adjustement for the seconds ('timeadd' command)." RETCODE); + } + return 137; + } + + if (defaultlanguage == 'F') { + ladmin_log("Envoi d'un requ黎e au serveur de logins pour modifier une date limite d'utilisation." RETCODE); + } else { + ladmin_log("Request to login-server to modify a time limit." RETCODE); + } + + WFIFOW(login_fd,0) = 0x7950; + memcpy(WFIFOP(login_fd,2), name, 24); + WFIFOW(login_fd,26) = (short)year; + WFIFOW(login_fd,28) = (short)month; + WFIFOW(login_fd,30) = (short)day; + WFIFOW(login_fd,32) = (short)hour; + WFIFOW(login_fd,34) = (short)minute; + WFIFOW(login_fd,36) = (short)second; + WFIFOSET(login_fd,38); + bytes_to_read = 1; + + return 0; +} + +//------------------------------------------------- +// Sub-function: Set a validity limit of an account +//------------------------------------------------- +int timesetaccount(char* param) { + char name[1023], date[1023], time[1023]; + int year, month, day, hour, minute, second; + time_t connect_until_time; // # of seconds 1/1/1970 (timestamp): Validity limit of the account (0 = unlimited) + struct tm *tmtime; + + memset(name, '\0', sizeof(name)); + memset(date, '\0', sizeof(date)); + memset(time, '\0', sizeof(time)); + year = month = day = hour = minute = second = 0; + connect_until_time = 0; + tmtime = localtime(&connect_until_time); // initialize + + if (sscanf(param, "\"%[^\"]\" %s %[^\r\n]", name, date, time) < 2 && // if date = 0, time can be void + sscanf(param, "'%[^']' %s %[^\r\n]", name, date, time) < 2 && // if date = 0, time can be void + sscanf(param, "%s %s %[^\r\n]", name, date, time) < 2) { // if date = 0, time can be void + if (defaultlanguage == 'F') { + printf("Entrez un nom de compte, une date et une heure svp.\n"); + printf("<exemple>: timeset <nom_du_compte> aaaa/mm/jj [hh:mm:ss]\n"); + printf(" timeset <nom_du_compte> 0 (0 = illimit)\n"); + printf(" Heure par d馭aut [hh:mm:ss]: 23:59:59.\n"); + ladmin_log("Nombre incorrect de param鑼res pour fixer une date limite d'utilisation (commande 'timeset')." RETCODE); + } else { + printf("Please input an account name, a date and a hour.\n"); + printf("<example>: timeset <account_name> yyyy/mm/dd [hh:mm:ss]\n"); + printf(" timeset <account_name> 0 (0 = unlimited)\n"); + printf(" Default time [hh:mm:ss]: 23:59:59.\n"); + ladmin_log("Incomplete parameters to set a limit time ('timeset' command)." RETCODE); + } + return 136; + } + if (verify_accountname(name) == 0) { + return 102; + } + + if (time[0] == '\0') + strcpy(time, "23:59:59"); + + if (atoi(date) != 0 && + ((sscanf(date, "%d/%d/%d", &year, &month, &day) < 3 && + sscanf(date, "%d-%d-%d", &year, &month, &day) < 3 && + sscanf(date, "%d.%d.%d", &year, &month, &day) < 3 && + sscanf(date, "%d'%d'%d", &year, &month, &day) < 3) || + sscanf(time, "%d:%d:%d", &hour, &minute, &second) < 3)) { + if (defaultlanguage == 'F') { + printf("Entrez 0 ou une date et une heure svp (format: 0 ou aaaa/mm/jj hh:mm:ss).\n"); + ladmin_log("Format incorrect pour la date/heure ('timeset' command)." RETCODE); + } else { + printf("Please input 0 or a date and a time (format: 0 or yyyy/mm/dd hh:mm:ss).\n"); + ladmin_log("Invalid format for the date/time ('timeset' command)." RETCODE); + } + return 102; + } + + if (atoi(date) == 0) { + connect_until_time = 0; + } else { + if (year < 70) { + year = year + 100; + } + if (year >= 1900) { + year = year - 1900; + } + if (month < 1 || month > 12) { + if (defaultlanguage == 'F') { + printf("Entrez un mois correct svp (entre 1 et 12).\n"); + ladmin_log("Mois incorrect pour la date ('timeset' command)." RETCODE); + } else { + printf("Please give a correct value for the month (from 1 to 12).\n"); + ladmin_log("Invalid month for the date ('timeset' command)." RETCODE); + } + return 102; + } + month = month - 1; + if (day < 1 || day > 31) { + if (defaultlanguage == 'F') { + printf("Entrez un jour correct svp (entre 1 et 31).\n"); + ladmin_log("Jour incorrect pour la date ('timeset' command)." RETCODE); + } else { + printf("Please give a correct value for the day (from 1 to 31).\n"); + ladmin_log("Invalid day for the date ('timeset' command)." RETCODE); + } + return 102; + } + if (((month == 3 || month == 5 || month == 8 || month == 10) && day > 30) || + (month == 1 && day > 29)) { + if (defaultlanguage == 'F') { + printf("Entrez un jour correct en fonction du mois (%d) svp.\n", month); + ladmin_log("Jour incorrect pour ce mois correspondant ('timeset' command)." RETCODE); + } else { + printf("Please give a correct value for a day of this month (%d).\n", month); + ladmin_log("Invalid day for this month ('timeset' command)." RETCODE); + } + return 102; + } + if (hour < 0 || hour > 23) { + if (defaultlanguage == 'F') { + printf("Entrez une heure correcte svp (entre 0 et 23).\n"); + ladmin_log("Heure incorrecte pour l'heure ('timeset' command)." RETCODE); + } else { + printf("Please give a correct value for the hour (from 0 to 23).\n"); + ladmin_log("Invalid hour for the time ('timeset' command)." RETCODE); + } + return 102; + } + if (minute < 0 || minute > 59) { + if (defaultlanguage == 'F') { + printf("Entrez des minutes correctes svp (entre 0 et 59).\n"); + ladmin_log("Minute incorrecte pour l'heure ('timeset' command)." RETCODE); + } else { + printf("Please give a correct value for the minutes (from 0 to 59).\n"); + ladmin_log("Invalid minute for the time ('timeset' command)." RETCODE); + } + return 102; + } + if (second < 0 || second > 59) { + if (defaultlanguage == 'F') { + printf("Entrez des secondes correctes svp (entre 0 et 59).\n"); + ladmin_log("Seconde incorrecte pour l'heure ('timeset' command)." RETCODE); + } else { + printf("Please give a correct value for the seconds (from 0 to 59).\n"); + ladmin_log("Invalid second for the time ('timeset' command)." RETCODE); + } + return 102; + } + tmtime->tm_year = year; + tmtime->tm_mon = month; + tmtime->tm_mday = day; + tmtime->tm_hour = hour; + tmtime->tm_min = minute; + tmtime->tm_sec = second; + tmtime->tm_isdst = -1; // -1: no winter/summer time modification + connect_until_time = mktime(tmtime); + if (connect_until_time == -1) { + if (defaultlanguage == 'F') { + printf("Date incorrecte.\n"); + printf("Ajoutez 0 ou une date et une heure svp (format: 0 ou aaaa/mm/jj hh:mm:ss).\n"); + ladmin_log("Date incorrecte. ('timeset' command)." RETCODE); + } else { + printf("Invalid date.\n"); + printf("Please add 0 or a date and a time (format: 0 or yyyy/mm/dd hh:mm:ss).\n"); + ladmin_log("Invalid date. ('timeset' command)." RETCODE); + } + return 102; + } + } + + if (defaultlanguage == 'F') { + ladmin_log("Envoi d'un requ黎e au serveur de logins pour fixer une date limite d'utilisation." RETCODE); + } else { + ladmin_log("Request to login-server to set a time limit." RETCODE); + } + + WFIFOW(login_fd,0) = 0x7948; + memcpy(WFIFOP(login_fd,2), name, 24); + WFIFOL(login_fd,26) = (int)connect_until_time; + WFIFOSET(login_fd,30); + bytes_to_read = 1; + + return 0; +} + +//------------------------------------------------------------------------------ +// Sub-function: Asking to displaying information about an account (by its name) +//------------------------------------------------------------------------------ +int whoaccount(char* param) { + char name[1023]; + + memset(name, '\0', sizeof(name)); + + if (strlen(param) == 0 || + (sscanf(param, "\"%[^\"]\"", name) < 1 && + sscanf(param, "'%[^']'", name) < 1 && + sscanf(param, "%[^\r\n]", name) < 1) || + strlen(name) == 0) { + if (defaultlanguage == 'F') { + printf("Entrez un nom de compte svp.\n"); + printf("<exemple> who nomtest\n"); + ladmin_log("Aucun nom n'a 騁 donn pour trouver le compte." RETCODE); + } else { + printf("Please input an account name.\n"); + printf("<example> who testname\n"); + ladmin_log("No name was given to found the account." RETCODE); + } + return 136; + } + if (verify_accountname(name) == 0) { + return 102; + } + + if (defaultlanguage == 'F') { + ladmin_log("Envoi d'un requ黎e au serveur de logins pour obtenir le information d'un compte (par le nom)." RETCODE); + } else { + ladmin_log("Request to login-server to obtain information about an account (by its name)." RETCODE); + } + + WFIFOW(login_fd,0) = 0x7952; + memcpy(WFIFOP(login_fd,2), name, 24); + WFIFOSET(login_fd,26); + bytes_to_read = 1; + + return 0; +} + +//-------------------------------------------------------- +// Sub-function: Asking of the version of the login-server +//-------------------------------------------------------- +int checkloginversion() { + if (defaultlanguage == 'F') + ladmin_log("Envoi d'un requ黎e au serveur de logins pour obtenir sa version." RETCODE); + else + ladmin_log("Request to login-server to obtain its version." RETCODE); + + WFIFOW(login_fd,0) = 0x7530; + WFIFOSET(login_fd,2); + bytes_to_read = 1; + + return 0; +} + +//--------------------------------------------- +// Prompt function +// this function wait until user type a command +// and analyse the command. +//--------------------------------------------- +int prompt() { + int i, j; + char buf[1024]; + char *p; + + // while we don't wait new packets + while (bytes_to_read == 0) { + // for help with the console colors look here: + // http://www.edoceo.com/liberum/?doc=printf-with-color + // some code explanation (used here): + // \033[2J : clear screen and go up/left (0, 0 position) + // \033[K : clear line from actual position to end of the line + // \033[0m : reset color parameter + // \033[1m : use bold for font + printf("\n"); + if (defaultlanguage == 'F') + printf("\033[32mPour afficher les commandes, tapez 'Entr馥'.\033[0m\n"); + else + printf("\033[32mTo list the commands, type 'enter'.\033[0m\n"); + printf("\033[0;36mLadmin-> \033[0m"); + printf("\033[1m"); + fflush(stdout); + + // get command and parameter + memset(buf, '\0', sizeof(buf)); + fflush(stdin); + fgets(buf, 1023, stdin); + buf[1023] = '\0'; + + printf("\033[0m"); + fflush(stdout); + + // remove final \n + if((p = strrchr(buf, '\n')) != NULL) + p[0] = '\0'; + // remove all control char + for (i = 0; buf[i]; i++) + if (buf[i] < 32) { + // remove cursor control. + if (buf[i] == 27 && buf[i+1] == '[' && + (buf[i+2] == 'H' || // home position (cursor) + buf[i+2] == 'J' || // clear screen + buf[i+2] == 'A' || // up 1 line + buf[i+2] == 'B' || // down 1 line + buf[i+2] == 'C' || // right 1 position + buf[i+2] == 'D' || // left 1 position + buf[i+2] == 'G')) { // center cursor (windows) + for (j = i; buf[j]; j++) + buf[j] = buf[j+3]; + } else if (buf[i] == 27 && buf[i+1] == '[' && buf[i+2] == '2' && buf[i+3] == 'J') { // clear screen + for (j = i; buf[j]; j++) + buf[j] = buf[j+4]; + } else if (buf[i] == 27 && buf[i+1] == '[' && buf[i+3] == '~' && + (buf[i+2] == '1' || // home (windows) + buf[i+2] == '2' || // insert (windows) + buf[i+2] == '3' || // del (windows) + buf[i+2] == '4' || // end (windows) + buf[i+2] == '5' || // pgup (windows) + buf[i+2] == '6')) { // pgdown (windows) + for (j = i; buf[j]; j++) + buf[j] = buf[j+4]; + } else { + // remove other control char. + for (j = i; buf[j]; j++) + buf[j] = buf[j+1]; + } + i--; + } + + // extract command name and parameters + memset(command, '\0', sizeof(command)); + memset(parameters, '\0', sizeof(parameters)); + sscanf(buf, "%1023s %[^\n]", command, parameters); + command[1023] = '\0'; + parameters[1023] = '\0'; + + // lowercase for command line + for (i = 0; command[i]; i++) + command[i] = tolower(command[i]); + + if (command[0] == '?' || strlen(command) == 0) { + if (defaultlanguage == 'F') { + strcpy(buf, "aide"); + strcpy(command, "aide"); + } else { + strcpy(buf, "help"); + strcpy(command, "help"); + } + } + + // Analyse of the command + check_command(command); // give complete name to the command + + if (strlen(parameters) == 0) { + if (defaultlanguage == 'F') { + ladmin_log("Commande: '%s' (sans param鑼re)" RETCODE, command, parameters); + } else { + ladmin_log("Command: '%s' (without parameters)" RETCODE, command, parameters); + } + } else { + if (defaultlanguage == 'F') { + ladmin_log("Commande: '%s', param鑼res: '%s'" RETCODE, command, parameters); + } else { + ladmin_log("Command: '%s', parameters: '%s'" RETCODE, command, parameters); + } + } + + // Analyse of the command +// help + if (strcmp(command, "aide") == 0) { + display_help(parameters, 1); // 1: french + } else if (strcmp(command, "help") == 0 ) { + display_help(parameters, 0); // 0: english +// general commands + } else if (strcmp(command, "add") == 0) { + addaccount(parameters, 0); // 0: no email + } else if (strcmp(command, "ban") == 0) { + banaccount(parameters); + } else if (strcmp(command, "banadd") == 0) { + banaddaccount(parameters); + } else if (strcmp(command, "banset") == 0) { + bansetaccount(parameters); + } else if (strcmp(command, "block") == 0) { + blockaccount(parameters); + } else if (strcmp(command, "check") == 0) { + checkaccount(parameters); + } else if (strcmp(command, "create") == 0) { + addaccount(parameters, 1); // 1: with email + } else if (strcmp(command, "delete") == 0) { + delaccount(parameters); + } else if (strcmp(command, "email") == 0) { + changeemail(parameters); + } else if (strcmp(command, "getcount") == 0) { + getlogincount(); + } else if (strcmp(command, "gm") == 0) { + changegmlevel(parameters); + } else if (strcmp(command, "id") == 0) { + idaccount(parameters); + } else if (strcmp(command, "info") == 0) { + infoaccount(atoi(parameters)); + } else if (strcmp(command, "kami") == 0) { + sendbroadcast(0, parameters); // flag for normal + } else if (strcmp(command, "kamib") == 0) { + sendbroadcast(0x10, parameters); // flag for blue + } else if (strcmp(command, "language") == 0) { + changelanguage(parameters); + } else if (strcmp(command, "list") == 0) { + listaccount(parameters, 0); // 0: to list all + } else if (strcmp(command, "listban") == 0) { + listaccount(parameters, 3); // 3: to list only accounts with state or bannished + } else if (strcmp(command, "listgm") == 0) { + listaccount(parameters, 1); // 1: to list only GM + } else if (strcmp(command, "listok") == 0) { + listaccount(parameters, 4); // 4: to list only accounts without state and not bannished + } else if (strcmp(command, "memo") == 0) { + changememo(parameters); + } else if (strcmp(command, "name") == 0) { + nameaccount(atoi(parameters)); + } else if (strcmp(command, "password") == 0) { + changepasswd(parameters); + } else if (strcmp(command, "reloadgm") == 0) { + reloadGM(); + } else if (strcmp(command, "search") == 0) { // no regex in C version + listaccount(parameters, 2); // 2: to list with pattern + } else if (strcmp(command, "sex") == 0) { + changesex(parameters); + } else if (strcmp(command, "state") == 0) { + changestate(parameters); + } else if (strcmp(command, "timeadd") == 0) { + timeaddaccount(parameters); + } else if (strcmp(command, "timeset") == 0) { + timesetaccount(parameters); + } else if (strcmp(command, "unban") == 0) { + unbanaccount(parameters); + } else if (strcmp(command, "unblock") == 0) { + unblockaccount(parameters); + } else if (strcmp(command, "version") == 0) { + checkloginversion(); + } else if (strcmp(command, "who") == 0) { + whoaccount(parameters); +// quit + } else if (strcmp(command, "quit") == 0 || + strcmp(command, "exit") == 0 || + strcmp(command, "end") == 0) { + if (defaultlanguage == 'F') { + printf("Au revoir.\n"); + } else { + printf("Bye.\n"); + } + exit(0); +// unknown command + } else { + if (defaultlanguage == 'F') { + printf("Commande inconnue [%s].\n", buf); + ladmin_log("Commande inconnue [%s]." RETCODE, buf); + } else { + printf("Unknown command [%s].\n", buf); + ladmin_log("Unknown command [%s]." RETCODE, buf); + } + } + } + + return 0; +} + +//------------------------------------------------------------- +// Function: Parse receiving informations from the login-server +//------------------------------------------------------------- +int parse_fromlogin(int fd) { + struct char_session_data *sd; + + if (session[fd]->eof) { + if (defaultlanguage == 'F') { + printf("Impossible de se connecter au serveur de login [%s:%d] !\n", loginserverip, loginserverport); + ladmin_log("Impossible de se connecter au serveur de login [%s:%d] !" RETCODE, loginserverip, loginserverport); + } else { + printf("Impossible to have a connection with the login-server [%s:%d] !\n", loginserverip, loginserverport); + ladmin_log("Impossible to have a connection with the login-server [%s:%d] !" RETCODE, loginserverip, loginserverport); + } + close(fd); + delete_session(fd); + exit (0); + } + +// printf("parse_fromlogin : %d %d %d\n", fd, RFIFOREST(fd), RFIFOW(fd,0)); + sd = session[fd]->session_data; + + while(RFIFOREST(fd) >= 2) { + switch(RFIFOW(fd,0)) { + case 0x7919: // answer of a connection request + if (RFIFOREST(fd) < 3) + return 0; + if (RFIFOB(fd,2) != 0) { + if (defaultlanguage == 'F') { + printf("Erreur de login:\n"); + printf(" - mot de passe incorrect,\n"); + printf(" - syst鑪e d'administration non activ, ou\n"); + printf(" - IP non autoris馥.\n"); + ladmin_log("Erreur de login: mot de passe incorrect, syst鑪e d'administration non activ, ou IP non autoris馥." RETCODE); + } else { + printf("Error at login:\n"); + printf(" - incorrect password,\n"); + printf(" - administration system not activated, or\n"); + printf(" - unauthorised IP.\n"); + ladmin_log("Error at login: incorrect password, administration system not activated, or unauthorised IP." RETCODE); + } + session[fd]->eof = 1; + //bytes_to_read = 1; // not stop at prompt + } else { + if (defaultlanguage == 'F') { + printf("Connexion 騁ablie.\n"); + ladmin_log("Connexion 騁ablie." RETCODE); + printf("Lecture de la version du serveur de login...\n"); + ladmin_log("Lecture de la version du serveur de login..." RETCODE); + } else { + printf("Established connection.\n"); + ladmin_log("Established connection." RETCODE); + printf("Reading of the version of the login-server...\n"); + ladmin_log("Reading of the version of the login-server..." RETCODE); + } + //bytes_to_read = 1; // unchanged + checkloginversion(); + } + RFIFOSKIP(fd,3); + break; + +#ifdef PASSWORDENC + case 0x01dc: // answer of a coding key request + if (RFIFOREST(fd) < 4 || RFIFOREST(fd) < RFIFOW(fd,2)) + return 0; + { + char md5str[64] = "", md5bin[32], md5key[RFIFOW(fd,2) - 4 + 1]; + memcpy(md5key, RFIFOP(fd,4), RFIFOW(fd,2) - 4); + md5key[sizeof(md5key)-1] = '0'; + if (passenc == 1) { + strncpy(md5str, RFIFOP(fd,4), RFIFOW(fd,2) - 4); + strcat(md5str, loginserveradminpassword); + } else if (passenc == 2) { + strncpy(md5str, loginserveradminpassword, sizeof(loginserveradminpassword)); + strcat(md5str, RFIFOP(fd,4)); + } + MD5_String2binary(md5str, md5bin); + WFIFOW(login_fd,0) = 0x7918; // Request for administation login (encrypted password) + WFIFOW(login_fd,2) = passenc; // Encrypted type + memcpy(WFIFOP(login_fd,4), md5bin, 16); + WFIFOSET(login_fd,20); + if (defaultlanguage == 'F') { + printf("R馗eption de la clef MD5.\n"); + ladmin_log("R馗eption de la clef MD5." RETCODE); + printf("Envoi du mot de passe crypt...\n"); + ladmin_log("Envoi du mot de passe crypt..." RETCODE); + } else { + printf("Receiving of the MD5 key.\n"); + ladmin_log("Receiving of the MD5 key." RETCODE); + printf("Sending of the encrypted password...\n"); + ladmin_log("Sending of the encrypted password..." RETCODE); + } + } + bytes_to_read = 1; + RFIFOSKIP(fd,RFIFOW(fd,2)); + break; +#endif + + case 0x7531: // Displaying of the version of the login-server + if (RFIFOREST(fd) < 10) + return 0; + printf(" Login-Server [%s:%d]\n", loginserverip, loginserverport); + if (((int)RFIFOB(login_fd,5)) == 0) { + printf(" eAthena version stable-%d.%d", (int)RFIFOB(login_fd,2), (int)RFIFOB(login_fd,3)); + } else { + printf(" eAthena version dev-%d.%d", (int)RFIFOB(login_fd,2), (int)RFIFOB(login_fd,3)); + } + if (((int)RFIFOB(login_fd,4)) == 0) + printf(" revision %d", (int)RFIFOB(login_fd,4)); + if (((int)RFIFOB(login_fd,6)) == 0) + printf("%d.\n", RFIFOW(login_fd,8)); + else + printf("-mod%d.\n", RFIFOW(login_fd,8)); + bytes_to_read = 0; + RFIFOSKIP(fd,10); + break; + + case 0x7921: // Displaying of the list of accounts + if (RFIFOREST(fd) < 4 || RFIFOREST(fd) < RFIFOW(fd,2)) + return 0; + if (RFIFOW(fd,2) < 5) { + if (defaultlanguage == 'F') { + ladmin_log(" R馗eption d'une liste des comptes vide." RETCODE); + if (list_count == 0) + printf("Aucun compte trouv.\n"); + else if (list_count == 1) + printf("1 compte trouv.\n"); + else + printf("%d comptes trouv駸.\n", list_count); + } else { + ladmin_log(" Receiving of a void accounts list." RETCODE); + if (list_count == 0) + printf("No account found.\n"); + else if (list_count == 1) + printf("1 account found.\n"); + else + printf("%d accounts found.\n", list_count); + } + bytes_to_read = 0; + } else { + int i; + if (defaultlanguage == 'F') + ladmin_log(" R馗eption d'une liste des comptes." RETCODE); + else + ladmin_log(" Receiving of a accounts list." RETCODE); + for(i = 4; i < RFIFOW(fd,2); i += 38) { + int j; + char userid[24]; + char lower_userid[24]; + memcpy(userid, RFIFOP(fd,i + 5), sizeof(userid)); + userid[sizeof(userid)-1] = '\0'; + memset(lower_userid, '\0', sizeof(lower_userid)); + for (j = 0; userid[j]; j++) + lower_userid[j] = tolower(userid[j]); + list_first = RFIFOL(fd,i) + 1; + // here are checks... + if (list_type == 0 || + (list_type == 1 && RFIFOB(fd,i+4) > 0) || + (list_type == 2 && strstr(lower_userid, parameters) != NULL) || + (list_type == 3 && RFIFOL(fd,i+34) != 0) || + (list_type == 4 && RFIFOL(fd,i+34) == 0)) { + printf("%10d ", RFIFOL(fd,i)); + if (RFIFOB(fd,i+4) == 0) + printf(" "); + else + printf("%2d ", (int)RFIFOB(fd,i+4)); + printf("%-24s", userid); + if (defaultlanguage == 'F') { + if (RFIFOB(fd,i+29) == 0) + printf("%-5s ", "Femme"); + else if (RFIFOB(fd,i+29) == 1) + printf("%-5s ", "Male"); + else + printf("%-5s ", "Servr"); + } else { + if (RFIFOB(fd,i+29) == 0) + printf("%-5s ", "Femal"); + else if (RFIFOB(fd,i+29) == 1) + printf("%-5s ", "Male"); + else + printf("%-5s ", "Servr"); + } + printf("%6d ", RFIFOL(fd,i+30)); + switch(RFIFOL(fd,i+34)) { + case 0: + if (defaultlanguage == 'F') + printf("%-27s\n", "Compte Ok"); + else + printf("%-27s\n", "Account OK"); + break; + case 1: + printf("%-27s\n", "Unregistered ID"); + break; + case 2: + printf("%-27s\n", "Incorrect Password"); + break; + case 3: + printf("%-27s\n", "This ID is expired"); + break; + case 4: + printf("%-27s\n", "Rejected from Server"); + break; + case 5: + printf("%-27s\n", "Blocked by the GM Team"); // You have been blocked by the GM Team + break; + case 6: + printf("%-27s\n", "Your EXE file is too old"); // Your Game's EXE file is not the latest version + break; + case 7: + printf("%-27s\n", "Banishement or"); + printf(" Prohibited to login until...\n"); // You are Prohibited to log in until %s + break; + case 8: + printf("%-27s\n", "Server is over populated"); + break; + case 9: + printf("%-27s\n", "No MSG"); + break; + default: // 100 + printf("%-27s\n", "This ID is totally erased"); // This ID has been totally erased + break; + } + list_count++; + } + } + // asking of the following acounts + if (defaultlanguage == 'F') + ladmin_log("Envoi d'un requ黎e au serveur de logins pour obtenir la liste des comptes de %d %d (compl駑ent)." RETCODE, list_first, list_last); + else + ladmin_log("Request to login-server to obtain the list of accounts from %d to %d (complement)." RETCODE, list_first, list_last); + WFIFOW(login_fd,0) = 0x7920; + WFIFOL(login_fd,2) = list_first; + WFIFOL(login_fd,6) = list_last; + WFIFOSET(login_fd,10); + bytes_to_read = 1; + } + RFIFOSKIP(fd,RFIFOW(fd,2)); + break; + + case 0x7931: // Answer of login-server about an account creation + if (RFIFOREST(fd) < 30) + return 0; + if (RFIFOL(fd,2) == -1) { + if (defaultlanguage == 'F') { + printf("Echec la cr饌tion du compte [%s]. Un compte identique existe d駛.\n", RFIFOP(fd,6)); + ladmin_log("Echec la cr饌tion du compte [%s]. Un compte identique existe d駛." RETCODE, RFIFOP(fd,6)); + } else { + printf("Account [%s] creation failed. Same account already exists.\n", RFIFOP(fd,6)); + ladmin_log("Account [%s] creation failed. Same account already exists." RETCODE, RFIFOP(fd,6)); + } + } else { + if (defaultlanguage == 'F') { + printf("Compte [%s] cr鳬 avec succ鑚 [id: %d].\n", RFIFOP(fd,6), RFIFOL(fd,2)); + ladmin_log("Compte [%s] cr鳬 avec succ鑚 [id: %d]." RETCODE, RFIFOP(fd,6), RFIFOL(fd,2)); + } else { + printf("Account [%s] is successfully created [id: %d].\n", RFIFOP(fd,6), RFIFOL(fd,2)); + ladmin_log("Account [%s] is successfully created [id: %d]." RETCODE, RFIFOP(fd,6), RFIFOL(fd,2)); + } + } + bytes_to_read = 0; + RFIFOSKIP(fd,30); + break; + + case 0x7933: // Answer of login-server about an account deletion + if (RFIFOREST(fd) < 30) + return 0; + if (RFIFOL(fd,2) == -1) { + if (defaultlanguage == 'F') { + printf("Echec de la suppression du compte [%s]. Le compte n'existe pas.\n", RFIFOP(fd,6)); + ladmin_log("Echec de la suppression du compte [%s]. Le compte n'existe pas." RETCODE, RFIFOP(fd,6)); + } else { + printf("Account [%s] deletion failed. Account doesn't exist.\n", RFIFOP(fd,6)); + ladmin_log("Account [%s] deletion failed. Account doesn't exist." RETCODE, RFIFOP(fd,6)); + } + } else { + if (defaultlanguage == 'F') { + printf("Compte [%s][id: %d] SUPPRIME avec succ鑚.\n", RFIFOP(fd,6), RFIFOL(fd,2)); + ladmin_log("Compte [%s][id: %d] SUPPRIME avec succ鑚." RETCODE, RFIFOP(fd,6), RFIFOL(fd,2)); + } else { + printf("Account [%s][id: %d] is successfully DELETED.\n", RFIFOP(fd,6), RFIFOL(fd,2)); + ladmin_log("Account [%s][id: %d] is successfully DELETED." RETCODE, RFIFOP(fd,6), RFIFOL(fd,2)); + } + } + bytes_to_read = 0; + RFIFOSKIP(fd,30); + break; + + case 0x7935: // answer of the change of an account password + if (RFIFOREST(fd) < 30) + return 0; + if (RFIFOL(fd,2) == -1) { + if (defaultlanguage == 'F') { + printf("Echec de la modification du mot de passe du compte [%s].\n", RFIFOP(fd,6)); + printf("Le compte [%s] n'existe pas.\n", RFIFOP(fd,6)); + ladmin_log("Echec de la modification du mot de passe du compte. Le compte [%s] n'existe pas." RETCODE, RFIFOP(fd,6)); + } else { + printf("Account [%s] password changing failed.\n", RFIFOP(fd,6)); + printf("Account [%s] doesn't exist.\n", RFIFOP(fd,6)); + ladmin_log("Account password changing failed. The compte [%s] doesn't exist." RETCODE, RFIFOP(fd,6)); + } + } else { + if (defaultlanguage == 'F') { + printf("Modification du mot de passe du compte [%s][id: %d] r騏ssie.\n", RFIFOP(fd,6), RFIFOL(fd,2)); + ladmin_log("Modification du mot de passe du compte [%s][id: %d] r騏ssie." RETCODE, RFIFOP(fd,6), RFIFOL(fd,2)); + } else { + printf("Account [%s][id: %d] password successfully changed.\n", RFIFOP(fd,6), RFIFOL(fd,2)); + ladmin_log("Account [%s][id: %d] password successfully changed." RETCODE, RFIFOP(fd,6), RFIFOL(fd,2)); + } + } + bytes_to_read = 0; + RFIFOSKIP(fd,30); + break; + + case 0x7937: // answer of the change of an account state + if (RFIFOREST(fd) < 34) + return 0; + if (RFIFOL(fd,2) == -1) { + if (defaultlanguage == 'F') { + printf("Echec du changement du statut du compte [%s]. Le compte n'existe pas.\n", RFIFOP(fd,6)); + ladmin_log("Echec du changement du statut du compte [%s]. Le compte n'existe pas." RETCODE, RFIFOP(fd,6)); + } else { + printf("Account [%s] state changing failed. Account doesn't exist.\n", RFIFOP(fd,6)); + ladmin_log("Account [%s] state changing failed. Account doesn't exist." RETCODE, RFIFOP(fd,6)); + } + } else { + char tmpstr[256]; + if (defaultlanguage == 'F') { + sprintf(tmpstr, "Statut du compte [%s] chang avec succ鑚 en [", RFIFOP(fd,6)); + } else { + sprintf(tmpstr, "Account [%s] state successfully changed in [", RFIFOP(fd,6)); + } + switch(RFIFOL(fd,30)) { + case 0: + if (defaultlanguage == 'F') + strcat(tmpstr, "0: Compte Ok"); + else + strcat(tmpstr, "0: Account OK"); + break; + case 1: + strcat(tmpstr, "1: Unregistered ID"); + break; + case 2: + strcat(tmpstr, "2: Incorrect Password"); + break; + case 3: + strcat(tmpstr, "3: This ID is expired"); + break; + case 4: + strcat(tmpstr, "4: Rejected from Server"); + break; + case 5: + strcat(tmpstr, "5: You have been blocked by the GM Team"); + break; + case 6: + strcat(tmpstr, "6: [Your Game's EXE file is not the latest version"); + break; + case 7: + strcat(tmpstr, "7: You are Prohibited to log in until..."); + break; + case 8: + strcat(tmpstr, "8: Server is jammed due to over populated"); + break; + case 9: + strcat(tmpstr, "9: No MSG"); + break; + default: // 100 + strcat(tmpstr, "100: This ID is totally erased"); + break; + } + strcat(tmpstr, "]"); + printf("%s\n", tmpstr); + ladmin_log("%s%s", tmpstr, RETCODE); + } + bytes_to_read = 0; + RFIFOSKIP(fd,34); + break; + + case 0x7939: // answer of the number of online players + if (RFIFOREST(fd) < 4 || RFIFOREST(fd) < RFIFOW(fd,2)) + return 0; + { + // Get length of the received packet + int i; + char name[20]; + if (defaultlanguage == 'F') { + ladmin_log(" R馗eption du nombre de joueurs en ligne." RETCODE); + } else { + ladmin_log(" Receiving of the number of online players." RETCODE); + } + // Read information of the servers + if (RFIFOW(fd,2) < 5) { + if (defaultlanguage == 'F') { + printf(" Aucun serveur n'est connect au login serveur.\n"); + } else { + printf(" No server is connected to the login-server.\n"); + } + } else { + if (defaultlanguage == 'F') { + printf(" Nombre de joueurs en ligne (serveur: nb):\n"); + } else { + printf(" Number of online players (server: number).\n"); + } + // Displaying of result + for(i = 4; i < RFIFOW(fd,2); i += 32) { + memcpy(name, RFIFOP(fd,i+6), sizeof(name)); + name[sizeof(name) - 1] = '\0'; + printf(" %-20s : %5d\n", name, RFIFOW(fd,i+26)); + } + } + } + bytes_to_read = 0; + RFIFOSKIP(fd,RFIFOW(fd,2)); + break; + + case 0x793b: // answer of the check of a password + if (RFIFOREST(fd) < 30) + return 0; + if (RFIFOL(fd,2) == -1) { + if (defaultlanguage == 'F') { + printf("Le compte [%s] n'existe pas ou le mot de passe est incorrect.\n", RFIFOP(fd,6)); + ladmin_log("Le compte [%s] n'existe pas ou le mot de passe est incorrect." RETCODE, RFIFOP(fd,6)); + } else { + printf("The account [%s] doesn't exist or the password is incorrect.\n", RFIFOP(fd,6)); + ladmin_log("The account [%s] doesn't exist or the password is incorrect." RETCODE, RFIFOP(fd,6)); + } + } else { + if (defaultlanguage == 'F') { + printf("Le mot de passe donn correspond bien au compte [%s][id: %d].\n", RFIFOP(fd,6), RFIFOL(fd,2)); + ladmin_log("Le mot de passe donn correspond bien au compte [%s][id: %d]." RETCODE, RFIFOP(fd,6), RFIFOL(fd,2)); + } else { + printf("The proposed password is correct for the account [%s][id: %d].\n", RFIFOP(fd,6), RFIFOL(fd,2)); + ladmin_log("The proposed password is correct for the account [%s][id: %d]." RETCODE, RFIFOP(fd,6), RFIFOL(fd,2)); + } + } + bytes_to_read = 0; + RFIFOSKIP(fd,30); + break; + + case 0x793d: // answer of the change of an account sex + if (RFIFOREST(fd) < 30) + return 0; + if (RFIFOL(fd,2) == -1) { + if (defaultlanguage == 'F') { + printf("Echec de la modification du sexe du compte [%s].\n", RFIFOP(fd,6)); + printf("Le compte [%s] n'existe pas ou le sexe est d駛 celui demand.\n", RFIFOP(fd,6)); + ladmin_log("Echec de la modification du sexe du compte. Le compte [%s] n'existe pas ou le sexe est d駛 celui demand." RETCODE, RFIFOP(fd,6)); + } else { + printf("Account [%s] sex changing failed.\n", RFIFOP(fd,6)); + printf("Account [%s] doesn't exist or the sex is already the good sex.\n", RFIFOP(fd,6)); + ladmin_log("Account sex changing failed. The compte [%s] doesn't exist or the sex is already the good sex." RETCODE, RFIFOP(fd,6)); + } + } else { + if (defaultlanguage == 'F') { + printf("Sexe du compte [%s][id: %d] chang avec succ鑚.\n", RFIFOP(fd,6), RFIFOL(fd,2)); + ladmin_log("Sexe du compte [%s][id: %d] chang avec succ鑚." RETCODE, RFIFOP(fd,6), RFIFOL(fd,2)); + } else { + printf("Account [%s][id: %d] sex successfully changed.\n", RFIFOP(fd,6), RFIFOL(fd,2)); + ladmin_log("Account [%s][id: %d] sex successfully changed." RETCODE, RFIFOP(fd,6), RFIFOL(fd,2)); + } + } + bytes_to_read = 0; + RFIFOSKIP(fd,30); + break; + + case 0x793f: // answer of the change of an account GM level + if (RFIFOREST(fd) < 30) + return 0; + if (RFIFOL(fd,2) == -1) { + if (defaultlanguage == 'F') { + printf("Echec de la modification du niveau de GM du compte [%s].\n", RFIFOP(fd,6)); + printf("Le compte [%s] n'existe pas, le niveau de GM est d駛 celui demand饅n", RFIFOP(fd,6)); + printf("ou il est impossible de modifier le fichier des comptes GM.\n"); + ladmin_log("Echec de la modification du niveau de GM du compte. Le compte [%s] n'existe pas, le niveau de GM est d駛 celui demand ou il est impossible de modifier le fichier des comptes GM." RETCODE, RFIFOP(fd,6)); + } else { + printf("Account [%s] GM level changing failed.\n", RFIFOP(fd,6)); + printf("Account [%s] doesn't exist, the GM level is already the good GM level\n", RFIFOP(fd,6)); + printf("or it's impossible to modify the GM accounts file.\n"); + ladmin_log("Account GM level changing failed. The compte [%s] doesn't exist, the GM level is already the good sex or it's impossible to modify the GM accounts file." RETCODE, RFIFOP(fd,6)); + } + } else { + if (defaultlanguage == 'F') { + printf("Niveau de GM du compte [%s][id: %d] chang avec succ鑚.\n", RFIFOP(fd,6), RFIFOL(fd,2)); + ladmin_log("Niveau de GM du compte [%s][id: %d] chang avec succ鑚." RETCODE, RFIFOP(fd,6), RFIFOL(fd,2)); + } else { + printf("Account [%s][id: %d] GM level successfully changed.\n", RFIFOP(fd,6), RFIFOL(fd,2)); + ladmin_log("Account [%s][id: %d] GM level successfully changed." RETCODE, RFIFOP(fd,6), RFIFOL(fd,2)); + } + } + bytes_to_read = 0; + RFIFOSKIP(fd,30); + break; + + case 0x7941: // answer of the change of an account email + if (RFIFOREST(fd) < 30) + return 0; + if (RFIFOL(fd,2) == -1) { + if (defaultlanguage == 'F') { + printf("Echec de la modification de l'e-mail du compte [%s].\n", RFIFOP(fd,6)); + printf("Le compte [%s] n'existe pas.\n", RFIFOP(fd,6)); + ladmin_log("Echec de la modification de l'e-mail du compte. Le compte [%s] n'existe pas." RETCODE, RFIFOP(fd,6)); + } else { + printf("Account [%s] e-mail changing failed.\n", RFIFOP(fd,6)); + printf("Account [%s] doesn't exist.\n", RFIFOP(fd,6)); + ladmin_log("Account e-mail changing failed. The compte [%s] doesn't exist." RETCODE, RFIFOP(fd,6)); + } + } else { + if (defaultlanguage == 'F') { + printf("Modification de l'e-mail du compte [%s][id: %d] r騏ssie.\n", RFIFOP(fd,6), RFIFOL(fd,2)); + ladmin_log("Modification de l'e-mail du compte [%s][id: %d] r騏ssie." RETCODE, RFIFOP(fd,6), RFIFOL(fd,2)); + } else { + printf("Account [%s][id: %d] e-mail successfully changed.\n", RFIFOP(fd,6), RFIFOL(fd,2)); + ladmin_log("Account [%s][id: %d] e-mail successfully changed." RETCODE, RFIFOP(fd,6), RFIFOL(fd,2)); + } + } + bytes_to_read = 0; + RFIFOSKIP(fd,30); + break; + + case 0x7943: // answer of the change of an account memo + if (RFIFOREST(fd) < 30) + return 0; + if (RFIFOL(fd,2) == -1) { + if (defaultlanguage == 'F') { + printf("Echec du changement du m駑o du compte [%s]. Le compte n'existe pas.\n", RFIFOP(fd,6)); + ladmin_log("Echec du changement du m駑o du compte [%s]. Le compte n'existe pas." RETCODE, RFIFOP(fd,6)); + } else { + printf("Account [%s] memo changing failed. Account doesn't exist.\n", RFIFOP(fd,6)); + ladmin_log("Account [%s] memo changing failed. Account doesn't exist." RETCODE, RFIFOP(fd,6)); + } + } else { + if (defaultlanguage == 'F') { + printf("M駑o du compte [%s][id: %d] chang avec succ鑚.\n", RFIFOP(fd,6), RFIFOL(fd,2)); + ladmin_log("M駑o du compte [%s][id: %d] chang avec succ鑚." RETCODE, RFIFOP(fd,6), RFIFOL(fd,2)); + } else { + printf("Account [%s][id: %d] memo successfully changed.\n", RFIFOP(fd,6), RFIFOL(fd,2)); + ladmin_log("Account [%s][id: %d] memo successfully changed." RETCODE, RFIFOP(fd,6), RFIFOL(fd,2)); + } + } + bytes_to_read = 0; + RFIFOSKIP(fd,30); + break; + + case 0x7945: // answer of an account id search + if (RFIFOREST(fd) < 30) + return 0; + if (RFIFOL(fd,2) == -1) { + if (defaultlanguage == 'F') { + printf("Impossible de trouver l'id du compte [%s]. Le compte n'existe pas.\n", RFIFOP(fd,6)); + ladmin_log("Impossible de trouver l'id du compte [%s]. Le compte n'existe pas." RETCODE, RFIFOP(fd,6)); + } else { + printf("Unable to find the account [%s] id. Account doesn't exist.\n", RFIFOP(fd,6)); + ladmin_log("Unable to find the account [%s] id. Account doesn't exist." RETCODE, RFIFOP(fd,6)); + } + } else { + if (defaultlanguage == 'F') { + printf("Le compte [%s] a pour id: %d.\n", RFIFOP(fd,6), RFIFOL(fd,2)); + ladmin_log("Le compte [%s] a pour id: %d." RETCODE, RFIFOP(fd,6), RFIFOL(fd,2)); + } else { + printf("The account [%s] have the id: %d.\n", RFIFOP(fd,6), RFIFOL(fd,2)); + ladmin_log("The account [%s] have the id: %d." RETCODE, RFIFOP(fd,6), RFIFOL(fd,2)); + } + } + bytes_to_read = 0; + RFIFOSKIP(fd,30); + break; + + case 0x7947: // answer of an account name search + if (RFIFOREST(fd) < 30) + return 0; + if (strcmp(RFIFOP(fd,6), "") == 0) { + if (defaultlanguage == 'F') { + printf("Impossible de trouver le nom du compte [%d]. Le compte n'existe pas.\n", RFIFOL(fd,2)); + ladmin_log("Impossible de trouver le nom du compte [%d]. Le compte n'existe pas." RETCODE, RFIFOL(fd,2)); + } else { + printf("Unable to find the account [%d] name. Account doesn't exist.\n", RFIFOL(fd,2)); + ladmin_log("Unable to find the account [%d] name. Account doesn't exist." RETCODE, RFIFOL(fd,2)); + } + } else { + if (defaultlanguage == 'F') { + printf("Le compte [id: %d] a pour nom: %s.\n", RFIFOL(fd,2), RFIFOP(fd,6)); + ladmin_log("Le compte [id: %d] a pour nom: %s." RETCODE, RFIFOL(fd,2), RFIFOP(fd,6)); + } else { + printf("The account [id: %d] have the name: %s.\n", RFIFOL(fd,2), RFIFOP(fd,6)); + ladmin_log("The account [id: %d] have the name: %s." RETCODE, RFIFOL(fd,2), RFIFOP(fd,6)); + } + } + bytes_to_read = 0; + RFIFOSKIP(fd,30); + break; + + case 0x7949: // answer of an account validity limit set + if (RFIFOREST(fd) < 34) + return 0; + if (RFIFOL(fd,2) == -1) { + if (defaultlanguage == 'F') { + printf("Echec du changement de la validit du compte [%s]. Le compte n'existe pas.\n", RFIFOP(fd,6)); + ladmin_log("Echec du changement de la validit du compte [%s]. Le compte n'existe pas." RETCODE, RFIFOP(fd,6)); + } else { + printf("Account [%s] validity limit changing failed. Account doesn't exist.\n", RFIFOP(fd,6)); + ladmin_log("Account [%s] validity limit changing failed. Account doesn't exist." RETCODE, RFIFOP(fd,6)); + } + } else { + time_t timestamp = RFIFOL(fd,30); + if (timestamp == 0) { + if (defaultlanguage == 'F') { + printf("Limite de validit du compte [%s][id: %d] chang馥 avec succ鑚 en [illimit饐.\n", RFIFOP(fd,6), RFIFOL(fd,2)); + ladmin_log("Limite de validit du compte [%s][id: %d] chang馥 avec succ鑚 en [illimit饐." RETCODE, RFIFOP(fd,6), RFIFOL(fd,2)); + } else { + printf("Validity Limit of the account [%s][id: %d] successfully changed to [unlimited].\n", RFIFOP(fd,6), RFIFOL(fd,2)); + ladmin_log("Validity Limit of the account [%s][id: %d] successfully changed to [unlimited]." RETCODE, RFIFOP(fd,6), RFIFOL(fd,2)); + } + } else { + char tmpstr[128]; + strftime(tmpstr, 24, date_format, localtime(×tamp)); + if (defaultlanguage == 'F') { + printf("Limite de validit du compte [%s][id: %d] chang馥 avec succ鑚 pour 黎re jusqu'au %s.\n", RFIFOP(fd,6), RFIFOL(fd,2), tmpstr); + ladmin_log("Limite de validit du compte [%s][id: %d] chang馥 avec succ鑚 pour 黎re jusqu'au %s." RETCODE, RFIFOP(fd,6), RFIFOL(fd,2), tmpstr); + } else { + printf("Validity Limit of the account [%s][id: %d] successfully changed to be until %s.\n", RFIFOP(fd,6), RFIFOL(fd,2), tmpstr); + ladmin_log("Validity Limit of the account [%s][id: %d] successfully changed to be until %s." RETCODE, RFIFOP(fd,6), RFIFOL(fd,2), tmpstr); + } + } + } + bytes_to_read = 0; + RFIFOSKIP(fd,34); + break; + + case 0x794b: // answer of an account ban set + if (RFIFOREST(fd) < 34) + return 0; + if (RFIFOL(fd,2) == -1) { + if (defaultlanguage == 'F') { + printf("Echec du changement de la date finale de banissement du compte [%s]. Le compte n'existe pas.\n", RFIFOP(fd,6)); + ladmin_log("Echec du changement de la date finale de banissement du compte [%s]. Le compte n'existe pas." RETCODE, RFIFOP(fd,6)); + } else { + printf("Account [%s] final date of banishment changing failed. Account doesn't exist.\n", RFIFOP(fd,6)); + ladmin_log("Account [%s] final date of banishment changing failed. Account doesn't exist." RETCODE, RFIFOP(fd,6)); + } + } else { + time_t timestamp = RFIFOL(fd,30); + if (timestamp == 0) { + if (defaultlanguage == 'F') { + printf("Date finale de banissement du compte [%s][id: %d] chang馥 avec succ鑚 en [d-bannie].\n", RFIFOP(fd,6), RFIFOL(fd,2)); + ladmin_log("Date finale de banissement du compte [%s][id: %d] chang馥 avec succ鑚 en [d-bannie]." RETCODE, RFIFOP(fd,6), RFIFOL(fd,2)); + } else { + printf("Final date of banishment of the account [%s][id: %d] successfully changed to [unbanished].\n", RFIFOP(fd,6), RFIFOL(fd,2)); + ladmin_log("Final date of banishment of the account [%s][id: %d] successfully changed to [unbanished]." RETCODE, RFIFOP(fd,6), RFIFOL(fd,2)); + } + } else { + char tmpstr[128]; + strftime(tmpstr, 24, date_format, localtime(×tamp)); + if (defaultlanguage == 'F') { + printf("Date finale de banissement du compte [%s][id: %d] chang馥 avec succ鑚 pour 黎re jusqu'au %s.\n", RFIFOP(fd,6), RFIFOL(fd,2), tmpstr); + ladmin_log("Date finale de banissement du compte [%s][id: %d] chang馥 avec succ鑚 pour 黎re jusqu'au %s." RETCODE, RFIFOP(fd,6), RFIFOL(fd,2), tmpstr); + } else { + printf("Final date of banishment of the account [%s][id: %d] successfully changed to be until %s.\n", RFIFOP(fd,6), RFIFOL(fd,2), tmpstr); + ladmin_log("Final date of banishment of the account [%s][id: %d] successfully changed to be until %s." RETCODE, RFIFOP(fd,6), RFIFOL(fd,2), tmpstr); + } + } + } + bytes_to_read = 0; + RFIFOSKIP(fd,34); + break; + + case 0x794d: // answer of an account ban date/time changing + if (RFIFOREST(fd) < 34) + return 0; + if (RFIFOL(fd,2) == -1) { + if (defaultlanguage == 'F') { + printf("Echec du changement de la date finale de banissement du compte [%s]. Le compte n'existe pas.\n", RFIFOP(fd,6)); + ladmin_log("Echec du changement de la date finale de banissement du compte [%s]. Le compte n'existe pas." RETCODE, RFIFOP(fd,6)); + } else { + printf("Account [%s] final date of banishment changing failed. Account doesn't exist.\n", RFIFOP(fd,6)); + ladmin_log("Account [%s] final date of banishment changing failed. Account doesn't exist." RETCODE, RFIFOP(fd,6)); + } + } else { + time_t timestamp = RFIFOL(fd,30); + if (timestamp == 0) { + if (defaultlanguage == 'F') { + printf("Date finale de banissement du compte [%s][id: %d] chang馥 avec succ鑚 en [d-bannie].\n", RFIFOP(fd,6), RFIFOL(fd,2)); + ladmin_log("Date finale de banissement du compte [%s][id: %d] chang馥 avec succ鑚 en [d-bannie]." RETCODE, RFIFOP(fd,6), RFIFOL(fd,2)); + } else { + printf("Final date of banishment of the account [%s][id: %d] successfully changed to [unbanished].\n", RFIFOP(fd,6), RFIFOL(fd,2)); + ladmin_log("Final date of banishment of the account [%s][id: %d] successfully changed to [unbanished]." RETCODE, RFIFOP(fd,6), RFIFOL(fd,2)); + } + } else { + char tmpstr[128]; + strftime(tmpstr, 24, date_format, localtime(×tamp)); + if (defaultlanguage == 'F') { + printf("Date finale de banissement du compte [%s][id: %d] chang馥 avec succ鑚 pour 黎re jusqu'au %s.\n", RFIFOP(fd,6), RFIFOL(fd,2), tmpstr); + ladmin_log("Date finale de banissement du compte [%s][id: %d] chang馥 avec succ鑚 pour 黎re jusqu'au %s." RETCODE, RFIFOP(fd,6), RFIFOL(fd,2), tmpstr); + } else { + printf("Final date of banishment of the account [%s][id: %d] successfully changed to be until %s.\n", RFIFOP(fd,6), RFIFOL(fd,2), tmpstr); + ladmin_log("Final date of banishment of the account [%s][id: %d] successfully changed to be until %s." RETCODE, RFIFOP(fd,6), RFIFOL(fd,2), tmpstr); + } + } + } + bytes_to_read = 0; + RFIFOSKIP(fd,34); + break; + + case 0x794f: // answer of a broadcast + if (RFIFOREST(fd) < 4) + return 0; + if (RFIFOW(fd,2) == (unsigned short)-1) { + if (defaultlanguage == 'F') { + printf("Echec de l'envoi du message. Aucun server de char en ligne.\n"); + ladmin_log("Echec de l'envoi du message. Aucun server de char en ligne." RETCODE); + } else { + printf("Message sending failed. No online char-server.\n"); + ladmin_log("Message sending failed. No online char-server." RETCODE); + } + } else { + if (defaultlanguage == 'F') { + printf("Message transmis au server de logins avec succ鑚.\n"); + ladmin_log("Message transmis au server de logins avec succ鑚." RETCODE); + } else { + printf("Message successfully sended to login-server.\n"); + ladmin_log("Message successfully sended to login-server." RETCODE); + } + } + bytes_to_read = 0; + RFIFOSKIP(fd,4); + break; + + case 0x7951: // answer of an account validity limit changing + if (RFIFOREST(fd) < 34) + return 0; + if (RFIFOL(fd,2) == -1) { + if (defaultlanguage == 'F') { + printf("Echec du changement de la validit du compte [%s]. Le compte n'existe pas.\n", RFIFOP(fd,6)); + ladmin_log("Echec du changement de la validit du compte [%s]. Le compte n'existe pas." RETCODE, RFIFOP(fd,6)); + } else { + printf("Account [%s] validity limit changing failed. Account doesn't exist.\n", RFIFOP(fd,6)); + ladmin_log("Account [%s] validity limit changing failed. Account doesn't exist." RETCODE, RFIFOP(fd,6)); + } + } else { + time_t timestamp = RFIFOL(fd,30); + if (timestamp == 0) { + if (defaultlanguage == 'F') { + printf("Limite de validit du compte [%s][id: %d] inchang馥.\n", RFIFOP(fd,6), RFIFOL(fd,2)); + printf("Le compte a une validit illimit馥 ou\n"); + printf("la modification est impossible avec les ajustements demand駸.\n"); + ladmin_log("Limite de validit du compte [%s][id: %d] inchang馥. Le compte a une validit illimit馥 ou la modification est impossible avec les ajustements demand駸." RETCODE, RFIFOP(fd,6), RFIFOL(fd,2)); + } else { + printf("Validity limit of the account [%s][id: %d] unchanged.\n", RFIFOP(fd,6), RFIFOL(fd,2)); + printf("The account have an unlimited validity limit or\n"); + printf("the changing is impossible with the proposed adjustments.\n"); + ladmin_log("Validity limit of the account [%s][id: %d] unchanged. The account have an unlimited validity limit or the changing is impossible with the proposed adjustments." RETCODE, RFIFOP(fd,6), RFIFOL(fd,2)); + } + } else { + char tmpstr[128]; + strftime(tmpstr, 24, date_format, localtime(×tamp)); + if (defaultlanguage == 'F') { + printf("Limite de validit du compte [%s][id: %d] chang馥 avec succ鑚 pour 黎re jusqu'au %s.\n", RFIFOP(fd,6), RFIFOL(fd,2), tmpstr); + ladmin_log("Limite de validit du compte [%s][id: %d] chang馥 avec succ鑚 pour 黎re jusqu'au %s." RETCODE, RFIFOP(fd,6), RFIFOL(fd,2), tmpstr); + } else { + printf("Validity limit of the account [%s][id: %d] successfully changed to be until %s.\n", RFIFOP(fd,6), RFIFOL(fd,2), tmpstr); + ladmin_log("Validity limit of the account [%s][id: %d] successfully changed to be until %s." RETCODE, RFIFOP(fd,6), RFIFOL(fd,2), tmpstr); + } + } + } + bytes_to_read = 0; + RFIFOSKIP(fd,34); + break; + + case 0x7953: // answer of a request about informations of an account (by account name/id) + if (RFIFOREST(fd) < 150 || RFIFOREST(fd) < (150 + RFIFOW(fd,148))) + return 0; + { + char userid[24], error_message[20], lastlogin[24], last_ip[16], email[40], memo[255]; + time_t ban_until_time; // # of seconds 1/1/1970 (timestamp): ban time limit of the account (0 = no ban) + time_t connect_until_time; // # of seconds 1/1/1970 (timestamp): Validity limit of the account (0 = unlimited) + memcpy(userid, RFIFOP(fd,7), sizeof(userid)); + userid[sizeof(userid)-1] = '\0'; + memcpy(error_message, RFIFOP(fd,40), sizeof(error_message)); + error_message[sizeof(error_message)-1] = '\0'; + memcpy(lastlogin, RFIFOP(fd,60), sizeof(lastlogin)); + lastlogin[sizeof(lastlogin)-1] = '\0'; + memcpy(last_ip, RFIFOP(fd,84), sizeof(last_ip)); + last_ip[sizeof(last_ip)-1] = '\0'; + memcpy(email, RFIFOP(fd,100), sizeof(email)); + email[sizeof(email)-1] = '\0'; + connect_until_time = (time_t)RFIFOL(fd,140); + ban_until_time = (time_t)RFIFOL(fd,144); + memset(memo, '\0', sizeof(memo)); + strncpy(memo, RFIFOP(fd,150), RFIFOW(fd,148)); + if (RFIFOL(fd,2) == -1) { + if (defaultlanguage == 'F') { + printf("Impossible de trouver le compte [%s]. Le compte n'existe pas.\n", parameters); + ladmin_log("Impossible de trouver le compte [%s]. Le compte n'existe pas." RETCODE, parameters); + } else { + printf("Unabled to find the account [%s]. Account doesn't exist.\n", parameters); + ladmin_log("Unabled to find the account [%s]. Account doesn't exist." RETCODE, parameters); + } + } else if (strlen(userid) == 0) { + if (defaultlanguage == 'F') { + printf("Impossible de trouver le compte [id: %s]. Le compte n'existe pas.\n", parameters); + ladmin_log("Impossible de trouver le compte [id: %s]. Le compte n'existe pas." RETCODE, parameters); + } else { + printf("Unabled to find the account [id: %s]. Account doesn't exist.\n", parameters); + ladmin_log("Unabled to find the account [id: %s]. Account doesn't exist." RETCODE, parameters); + } + } else { + if (defaultlanguage == 'F') { + ladmin_log("R馗eption d'information concernant un compte." RETCODE); + printf("Le compte a les caract駻istiques suivantes:\n"); + } else { + ladmin_log("Receiving information about an account." RETCODE); + printf("The account is set with:\n"); + } + if (RFIFOB(fd,6) == 0) { + printf(" Id: %d (non-GM)\n", RFIFOL(fd,2)); + } else { + if (defaultlanguage == 'F') { + printf(" Id: %d (GM niveau %d)\n", RFIFOL(fd,2), (int)RFIFOB(fd,6)); + } else { + printf(" Id: %d (GM level %d)\n", RFIFOL(fd,2), (int)RFIFOB(fd,6)); + } + } + if (defaultlanguage == 'F') { + printf(" Nom: '%s'\n", userid); + if (RFIFOB(fd,31) == 0) + printf(" Sexe: Femme\n"); + else if (RFIFOB(fd,31) == 1) + printf(" Sexe: Male\n"); + else + printf(" Sexe: Serveur\n"); + } else { + printf(" Name: '%s'\n", userid); + if (RFIFOB(fd,31) == 0) + printf(" Sex: Female\n"); + else if (RFIFOB(fd,31) == 1) + printf(" Sex: Male\n"); + else + printf(" Sex: Server\n"); + } + printf(" E-mail: %s\n", email); + switch(RFIFOL(fd,36)) { + case 0: + if (defaultlanguage == 'F') + printf(" Statut: 0 [Compte Ok]\n"); + else + printf(" Statut: 0 [Account OK]\n"); + break; + case 1: + printf(" Statut: 1 [Unregistered ID]\n"); + break; + case 2: + printf(" Statut: 2 [Incorrect Password]\n"); + break; + case 3: + printf(" Statut: 3 [This ID is expired]\n"); + break; + case 4: + printf(" Statut: 4 [Rejected from Server]\n"); + break; + case 5: + printf(" Statut: 5 [You have been blocked by the GM Team]\n"); + break; + case 6: + printf(" Statut: 6 [Your Game's EXE file is not the latest version]\n"); + break; + case 7: + printf(" Statut: 7 [You are Prohibited to log in until %s]\n", error_message); + break; + case 8: + printf(" Statut: 8 [Server is jammed due to over populated]\n"); + break; + case 9: + printf(" Statut: 9 [No MSG]\n"); + break; + default: // 100 + printf(" Statut: %d [This ID is totally erased]\n", RFIFOL(fd,36)); + break; + } + if (defaultlanguage == 'F') { + if (ban_until_time == 0) { + printf(" Banissement: non banni.\n"); + } else { + char tmpstr[128]; + strftime(tmpstr, 24, date_format, localtime(&ban_until_time)); + printf(" Banissement: jusqu'au %s.\n", tmpstr); + } + if (RFIFOL(fd,32) > 1) + printf(" Compteur: %d connexions.\n", RFIFOL(fd,32)); + else + printf(" Compteur: %d connexion.\n", RFIFOL(fd,32)); + printf(" Derni鑽e connexion le: %s (ip: %s)\n", lastlogin, last_ip); + if (connect_until_time == 0) { + printf(" Limite de validit: illimit.\n"); + } else { + char tmpstr[128]; + strftime(tmpstr, 24, date_format, localtime(&connect_until_time)); + printf(" Limite de validit: jusqu'au %s.\n", tmpstr); + } + } else { + if (ban_until_time == 0) { + printf(" Banishment: not banished.\n"); + } else { + char tmpstr[128]; + strftime(tmpstr, 24, date_format, localtime(&ban_until_time)); + printf(" Banishment: until %s.\n", tmpstr); + } + if (RFIFOL(fd,32) > 1) + printf(" Count: %d connections.\n", RFIFOL(fd,32)); + else + printf(" Count: %d connection.\n", RFIFOL(fd,32)); + printf(" Last connection at: %s (ip: %s)\n", lastlogin, last_ip); + if (connect_until_time == 0) { + printf(" Validity limit: unlimited.\n"); + } else { + char tmpstr[128]; + strftime(tmpstr, 24, date_format, localtime(&connect_until_time)); + printf(" Validity limit: until %s.\n", tmpstr); + } + } + printf(" Memo: '%s'\n", memo); + } + } + bytes_to_read = 0; + RFIFOSKIP(fd,150 + RFIFOW(fd,148)); + break; + + default: + printf("Remote administration has been disconnected (unknown packet).\n"); + ladmin_log("'End of connection, unknown packet." RETCODE); + session[fd]->eof = 1; + return 0; + } + } + + // if we don't wait new packets, do the prompt + prompt(); + + return 0; +} + +//------------------------------------ +// Function to connect to login-server +//------------------------------------ +int Connect_login_server() { + if (defaultlanguage == 'F') { + printf("Essai de connection au server de logins...\n"); + ladmin_log("Essai de connection au server de logins..." RETCODE); + } else { + printf("Attempt to connect to login-server...\n"); + ladmin_log("Attempt to connect to login-server..." RETCODE); + } + + login_fd = make_connection(login_ip, loginserverport); + +#ifdef PASSWORDENC + if (passenc == 0) { +#endif + WFIFOW(login_fd,0) = 0x7918; // Request for administation login + WFIFOW(login_fd,2) = 0; // no encrypted + memcpy(WFIFOP(login_fd,4), loginserveradminpassword, 24); + WFIFOSET(login_fd,28); + bytes_to_read = 1; + if (defaultlanguage == 'F') { + printf("Envoi du mot de passe...\n"); + ladmin_log("Envoi du mot de passe..." RETCODE); + } else { + printf("Sending of the password...\n"); + ladmin_log("Sending of the password..." RETCODE); + } +#ifdef PASSWORDENC + } else { + WFIFOW(login_fd,0) = 0x791a; // Sending request about the coding key + WFIFOSET(login_fd,2); + bytes_to_read = 1; + if (defaultlanguage == 'F') { + printf("Demande de la clef MD5...\n"); + ladmin_log("Demande de la clef MD5..." RETCODE); + } else { + printf("Request about the MD5 key...\n"); + ladmin_log("Request about the MD5 key..." RETCODE); + } + } +#endif + + return 0; +} + +//------------------------------------------------- +// Return numerical value of a switch configuration +// on/off, english, fran軋is, deutsch, espaol +//------------------------------------------------- +int config_switch(const char *str) { + if (strcmpi(str, "on") == 0 || strcmpi(str, "yes") == 0 || strcmpi(str, "oui") == 0 || strcmpi(str, "ja") == 0 || strcmpi(str, "si") == 0) + return 1; + if (strcmpi(str, "off") == 0 || strcmpi(str, "no") == 0 || strcmpi(str, "non") == 0 || strcmpi(str, "nein") == 0) + return 0; + + return atoi(str); +} + +//----------------------------------- +// Reading general configuration file +//----------------------------------- +int ladmin_config_read(const char *cfgName) { + char line[1024], w1[1024], w2[1024]; + FILE *fp; + + fp = fopen(cfgName, "r"); + if (fp == NULL) { + if (defaultlanguage == 'F') { + printf("\033[0mFichier de configuration (%s) non trouv.\n", cfgName); + } else { + printf("\033[0mConfiguration file (%s) not found.\n", cfgName); + } + return 1; + } + + if (defaultlanguage == 'F') { + printf("\033[0m---D饕ut de lecture du fichier de configuration Ladmin (%s)\n", cfgName); + } else { + printf("\033[0m---Start reading of Ladmin configuration file (%s)\n", cfgName); + } + while(fgets(line, sizeof(line)-1, fp)) { + if (line[0] == '/' && line[1] == '/') + continue; + + line[sizeof(line)-1] = '\0'; + if (sscanf(line, "%[^:]: %[^\r\n]", w1, w2) == 2) { + remove_control_chars(w1); + remove_control_chars(w2); + + if(strcmpi(w1,"login_ip")==0){ + struct hostent *h = gethostbyname (w2); + if (h != NULL) { + if (defaultlanguage == 'F') { + printf("Adresse du serveur de logins: %s -> %d.%d.%d.%d\n", w2, (unsigned char)h->h_addr[0], (unsigned char)h->h_addr[1], (unsigned char)h->h_addr[2], (unsigned char)h->h_addr[3]); + } else { + printf("Login server IP address: %s -> %d.%d.%d.%d\n", w2, (unsigned char)h->h_addr[0], (unsigned char)h->h_addr[1], (unsigned char)h->h_addr[2], (unsigned char)h->h_addr[3]); + } + sprintf(loginserverip, "%d.%d.%d.%d", (unsigned char)h->h_addr[0], (unsigned char)h->h_addr[1], (unsigned char)h->h_addr[2], (unsigned char)h->h_addr[3]); + } else + memcpy(loginserverip, w2, 16); + } else if (strcmpi(w1, "login_port") == 0) { + loginserverport = atoi(w2); + } else if (strcmpi(w1, "admin_pass") == 0) { + strncpy(loginserveradminpassword, w2, sizeof(loginserveradminpassword)); + loginserveradminpassword[sizeof(loginserveradminpassword)-1] = '\0'; +#ifdef PASSWORDENC + } else if (strcmpi(w1, "passenc") == 0) { + passenc = atoi(w2); + if (passenc < 0 || passenc > 2) + passenc = 0; +#endif + } else if (strcmpi(w1, "defaultlanguage") == 0) { + if (w2[0] == 'F' || w2[0] == 'E') + defaultlanguage = w2[0]; + } else if (strcmpi(w1, "ladmin_log_filename") == 0) { + strncpy(ladmin_log_filename, w2, sizeof(ladmin_log_filename)); + ladmin_log_filename[sizeof(ladmin_log_filename)-1] = '\0'; + } else if (strcmpi(w1, "date_format") == 0) { // note: never have more than 19 char for the date! + switch (atoi(w2)) { + case 0: + strcpy(date_format, "%d-%m-%Y %H:%M:%S"); // 31-12-2004 23:59:59 + break; + case 1: + strcpy(date_format, "%m-%d-%Y %H:%M:%S"); // 12-31-2004 23:59:59 + break; + case 2: + strcpy(date_format, "%Y-%d-%m %H:%M:%S"); // 2004-31-12 23:59:59 + break; + case 3: + strcpy(date_format, "%Y-%m-%d %H:%M:%S"); // 2004-12-31 23:59:59 + break; + } + } else if (strcmpi(w1, "import") == 0) { + ladmin_config_read(w2); + } + } + } + fclose(fp); + + login_ip = inet_addr(loginserverip); + + if (defaultlanguage == 'F') { + printf("---Lecture du fichier de configuration Ladmin termin馥.\n"); + } else { + printf("---End reading of Ladmin configuration file.\n"); + } + + return 0; +} + +//-------------------------------------- +// Function called at exit of the server +//-------------------------------------- +void do_final(void) { + + if (already_exit_function == 0) { + delete_session(login_fd); + + if (defaultlanguage == 'F') { + printf("\033[0m----Fin de Ladmin (fin normale avec fermeture de tous les fichiers).\n"); + ladmin_log("----Fin de Ladmin (fin normale avec fermeture de tous les fichiers)." RETCODE); + } else { + printf("\033[0m----End of Ladmin (normal end with closing of all files).\n"); + ladmin_log("----End of Ladmin (normal end with closing of all files)." RETCODE); + } + + already_exit_function = 1; + } +} + +//------------------------ +// Main function of ladmin +//------------------------ +int do_init(int argc, char **argv) { + // read ladmin configuration + ladmin_config_read((argc > 1) ? argv[1] : LADMIN_CONF_NAME); + + ladmin_log(""); + if (defaultlanguage == 'F') { + ladmin_log("Fichier de configuration lu." RETCODE); + } else { + ladmin_log("Configuration file readed." RETCODE); + } + + srand(time(NULL)); + + set_termfunc(do_final); + set_defaultparse(parse_fromlogin); + + if (defaultlanguage == 'F') { + printf("Outil d'administration distance de eAthena.\n"); + printf("(pour eAthena version %d.%d.%d.)\n", ATHENA_MAJOR_VERSION, ATHENA_MINOR_VERSION, ATHENA_REVISION); + } else { + printf("EAthena login-server administration tool.\n"); + printf("(for eAthena version %d.%d.%d.)\n", ATHENA_MAJOR_VERSION, ATHENA_MINOR_VERSION, ATHENA_REVISION); + } + + if (defaultlanguage == 'F') { + ladmin_log("Ladmin est pr黎." RETCODE); + printf("Ladmin est \033[1;32mpr黎\033[0m.\n\n"); + } else { + ladmin_log("Ladmin is ready." RETCODE); + printf("Ladmin is \033[1;32mready\033[0m.\n\n"); + } + + Connect_login_server(); + + atexit(do_final); + + return 0; +} diff --git a/src/ladmin/ladmin.h b/src/ladmin/ladmin.h new file mode 100644 index 0000000..f76bfc2 --- /dev/null +++ b/src/ladmin/ladmin.h @@ -0,0 +1,11 @@ +// $Id: ladmin.h,v 1.1.1.1 2004/09/10 17:26:52 MagicalTux Exp $ +#ifndef _LADMIN_H_ +#define _LADMIN_H_ + +#define LADMIN_CONF_NAME "conf/ladmin_athena.conf" +#define PASSWORDENC 3 // A definition is given when making an encryption password correspond. + // It is 1 at the time of passwordencrypt. + // It is made into 2 at the time of passwordencrypt2. + // When it is made 3, it corresponds to both. + +#endif diff --git a/src/ladmin/md5calc.c b/src/ladmin/md5calc.c new file mode 100644 index 0000000..7b9a9a2 --- /dev/null +++ b/src/ladmin/md5calc.c @@ -0,0 +1,237 @@ +// $Id: md5calc.c,v 1.1.1.1 2004/09/10 17:26:53 MagicalTux Exp $ +/*********************************************************** + * md5 calculation algorithm + * + * The source code referred to the following URL. + * http://www.geocities.co.jp/SiliconValley-Oakland/8878/lab17/lab17.html + * + ***********************************************************/ + +#include "md5calc.h" +#include <string.h> +#include <stdio.h> + +#ifndef UINT_MAX +#define UINT_MAX 4294967295U +#endif + +// Global variable +static unsigned int *pX; + +// Stirng Table +static const unsigned int T[] = { + 0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, //0 + 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501, //4 + 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, //8 + 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821, //12 + 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, //16 + 0xd62f105d, 0x2441453, 0xd8a1e681, 0xe7d3fbc8, //20 + 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, //24 + 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a, //28 + 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, //32 + 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70, //36 + 0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x4881d05, //40 + 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665, //44 + 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, //48 + 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1, //52 + 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, //56 + 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391 //60 +}; + +// ROTATE_LEFT The left is made to rotate x [ n-bit ]. This is diverted as it is from RFC. +#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32-(n)))) + +// The function used for other calculation +static unsigned int F(unsigned int X, unsigned int Y, unsigned int Z) +{ + return (X & Y) | (~X & Z); +} +static unsigned int G(unsigned int X, unsigned int Y, unsigned int Z) +{ + return (X & Z) | (Y & ~Z); +} +static unsigned int H(unsigned int X, unsigned int Y, unsigned int Z) +{ + return X ^ Y ^ Z; +} +static unsigned int I(unsigned int X, unsigned int Y, unsigned int Z) +{ + return Y ^ (X | ~Z); +} + +static unsigned int Round(unsigned int a, unsigned int b, unsigned int FGHI, + unsigned int k, unsigned int s, unsigned int i) +{ + return b + ROTATE_LEFT(a + FGHI + pX[k] + T[i], s); +} + +static void Round1(unsigned int *a, unsigned int b, unsigned int c, + unsigned int d,unsigned int k, unsigned int s, unsigned int i) +{ + *a = Round(*a, b, F(b,c,d), k, s, i); +} +static void Round2(unsigned int *a, unsigned int b, unsigned int c, + unsigned int d,unsigned int k, unsigned int s, unsigned int i) +{ + *a = Round(*a, b, G(b,c,d), k, s, i); +} +static void Round3(unsigned int *a, unsigned int b, unsigned int c, + unsigned int d,unsigned int k, unsigned int s, unsigned int i) +{ + *a = Round(*a, b, H(b,c,d), k, s, i); +} +static void Round4(unsigned int *a, unsigned int b, unsigned int c, + unsigned int d,unsigned int k, unsigned int s, unsigned int i) +{ + *a = Round(*a, b, I(b,c,d), k, s, i); +} + +static void MD5_Round_Calculate(const unsigned char *block, + unsigned int *A2, unsigned int *B2, unsigned int *C2, unsigned int *D2) +{ + //create X It is since it is required. + unsigned int X[16]; //512bit 64byte + int j,k; + + //Save A as AA, B as BB, C as CC, and and D as DD (saving of A, B, C, and D) + unsigned int A=*A2, B=*B2, C=*C2, D=*D2; + unsigned int AA = A,BB = B,CC = C,DD = D; + + //It is a large region variable reluctantly because of calculation of a round. . . for Round1...4 + pX = X; + + //Copy block(padding_message) i into X + for (j=0,k=0; j<64; j+=4,k++) + X[k] = ( (unsigned int )block[j] ) // 8byte*4 -> 32byte conversion + | ( ((unsigned int )block[j+1]) << 8 ) // A function called Decode as used in the field of RFC + | ( ((unsigned int )block[j+2]) << 16 ) + | ( ((unsigned int )block[j+3]) << 24 ); + + + //Round 1 + Round1(&A,B,C,D, 0, 7, 0); Round1(&D,A,B,C, 1, 12, 1); Round1(&C,D,A,B, 2, 17, 2); Round1(&B,C,D,A, 3, 22, 3); + Round1(&A,B,C,D, 4, 7, 4); Round1(&D,A,B,C, 5, 12, 5); Round1(&C,D,A,B, 6, 17, 6); Round1(&B,C,D,A, 7, 22, 7); + Round1(&A,B,C,D, 8, 7, 8); Round1(&D,A,B,C, 9, 12, 9); Round1(&C,D,A,B, 10, 17, 10); Round1(&B,C,D,A, 11, 22, 11); + Round1(&A,B,C,D, 12, 7, 12); Round1(&D,A,B,C, 13, 12, 13); Round1(&C,D,A,B, 14, 17, 14); Round1(&B,C,D,A, 15, 22, 15); + + //Round 2 + Round2(&A,B,C,D, 1, 5, 16); Round2(&D,A,B,C, 6, 9, 17); Round2(&C,D,A,B, 11, 14, 18); Round2(&B,C,D,A, 0, 20, 19); + Round2(&A,B,C,D, 5, 5, 20); Round2(&D,A,B,C, 10, 9, 21); Round2(&C,D,A,B, 15, 14, 22); Round2(&B,C,D,A, 4, 20, 23); + Round2(&A,B,C,D, 9, 5, 24); Round2(&D,A,B,C, 14, 9, 25); Round2(&C,D,A,B, 3, 14, 26); Round2(&B,C,D,A, 8, 20, 27); + Round2(&A,B,C,D, 13, 5, 28); Round2(&D,A,B,C, 2, 9, 29); Round2(&C,D,A,B, 7, 14, 30); Round2(&B,C,D,A, 12, 20, 31); + + //Round 3 + Round3(&A,B,C,D, 5, 4, 32); Round3(&D,A,B,C, 8, 11, 33); Round3(&C,D,A,B, 11, 16, 34); Round3(&B,C,D,A, 14, 23, 35); + Round3(&A,B,C,D, 1, 4, 36); Round3(&D,A,B,C, 4, 11, 37); Round3(&C,D,A,B, 7, 16, 38); Round3(&B,C,D,A, 10, 23, 39); + Round3(&A,B,C,D, 13, 4, 40); Round3(&D,A,B,C, 0, 11, 41); Round3(&C,D,A,B, 3, 16, 42); Round3(&B,C,D,A, 6, 23, 43); + Round3(&A,B,C,D, 9, 4, 44); Round3(&D,A,B,C, 12, 11, 45); Round3(&C,D,A,B, 15, 16, 46); Round3(&B,C,D,A, 2, 23, 47); + + //Round 4 + Round4(&A,B,C,D, 0, 6, 48); Round4(&D,A,B,C, 7, 10, 49); Round4(&C,D,A,B, 14, 15, 50); Round4(&B,C,D,A, 5, 21, 51); + Round4(&A,B,C,D, 12, 6, 52); Round4(&D,A,B,C, 3, 10, 53); Round4(&C,D,A,B, 10, 15, 54); Round4(&B,C,D,A, 1, 21, 55); + Round4(&A,B,C,D, 8, 6, 56); Round4(&D,A,B,C, 15, 10, 57); Round4(&C,D,A,B, 6, 15, 58); Round4(&B,C,D,A, 13, 21, 59); + Round4(&A,B,C,D, 4, 6, 60); Round4(&D,A,B,C, 11, 10, 61); Round4(&C,D,A,B, 2, 15, 62); Round4(&B,C,D,A, 9, 21, 63); + + // Then perform the following additions. (let's add) + *A2 = A + AA; + *B2 = B + BB; + *C2 = C + CC; + *D2 = D + DD; + + //The clearance of confidential information + memset(pX, 0, sizeof(X)); +} + +//------------------------------------------------------------------- +// The function for the exteriors + +/** output is the coded binary in the character sequence which wants to code string. */ +void MD5_String2binary(const char * string, char * output) +{ +//var + /*8bit*/ + unsigned char padding_message[64]; //Extended message 512bit 64byte + unsigned char *pstring; //The position of string in the present scanning notes is held. + +// unsigned char digest[16]; + /*32bit*/ + unsigned int string_byte_len, //The byte chief of string is held. + string_bit_len, //The bit length of string is held. + copy_len, //The number of bytes which is used by 1-3 and which remained + msg_digest[4]; //Message digest 128bit 4byte + unsigned int *A = &msg_digest[0], //The message digest in accordance with RFC (reference) + *B = &msg_digest[1], + *C = &msg_digest[2], + *D = &msg_digest[3]; + int i; + +//prog + //Step 3.Initialize MD Buffer (although it is the initialization; step 3 of A, B, C, and D -- unavoidable -- a head) + *A = 0x67452301; + *B = 0xefcdab89; + *C = 0x98badcfe; + *D = 0x10325476; + + //Step 1.Append Padding Bits (extension of a mark bit) + //1-1 + string_byte_len = strlen(string); //The byte chief of a character sequence is acquired. + pstring = (unsigned char *)string; //The position of the present character sequence is set. + + //1-2 Repeat calculation until length becomes less than 64 bytes. + for (i=string_byte_len; 64<=i; i-=64,pstring+=64) + MD5_Round_Calculate(pstring, A,B,C,D); + + //1-3 + copy_len = string_byte_len % 64; //The number of bytes which remained is computed. + strncpy((char *)padding_message, (char *)pstring, copy_len); //A message is copied to an extended bit sequence. + memset(padding_message+copy_len, 0, 64 - copy_len); //It buries by 0 until it becomes extended bit length. + padding_message[copy_len] |= 0x80; //The next of a message is 1. + + //1-4 + //If 56 bytes or more (less than 64 bytes) of remainder becomes, it will calculate by extending to 64 bytes. + if (56 <= copy_len) { + MD5_Round_Calculate(padding_message, A,B,C,D); + memset(padding_message, 0, 56); //56 bytes is newly fill uped with 0. + } + + + //Step 2.Append Length (the information on length is added) + string_bit_len = string_byte_len * 8; //From the byte chief to bit length (32 bytes of low rank) + memcpy(&padding_message[56], &string_bit_len, 4); //32 bytes of low rank is set. + + //When bit length cannot be expressed in 32 bytes of low rank, it is a beam raising to a higher rank. + if (UINT_MAX / 8 < string_byte_len) { + unsigned int high = (string_byte_len - UINT_MAX / 8) * 8; + memcpy(&padding_message[60], &high, 4); + } else + memset(&padding_message[60], 0, 4); //In this case, it is good for a higher rank at 0. + + //Step 4.Process Message in 16-Word Blocks (calculation of MD5) + MD5_Round_Calculate(padding_message, A,B,C,D); + + + //Step 5.Output (output) + memcpy(output,msg_digest,16); +// memcpy (digest, msg_digest, and 16); //8 byte*4 < - 32byte conversion A function called Encode as used in the field of RFC +/* sprintf(output, + "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", + digest[ 0], digest[ 1], digest[ 2], digest[ 3], + digest[ 4], digest[ 5], digest[ 6], digest[ 7], + digest[ 8], digest[ 9], digest[10], digest[11], + digest[12], digest[13], digest[14], digest[15]);*/ +} + +/** output is the coded character sequence in the character sequence which wants to code string. */ +void MD5_String(const char * string, char * output) +{ + unsigned char digest[16]; + + MD5_String2binary(string,digest); + sprintf(output, + "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", + digest[ 0], digest[ 1], digest[ 2], digest[ 3], + digest[ 4], digest[ 5], digest[ 6], digest[ 7], + digest[ 8], digest[ 9], digest[10], digest[11], + digest[12], digest[13], digest[14], digest[15]); +} + diff --git a/src/ladmin/md5calc.h b/src/ladmin/md5calc.h new file mode 100644 index 0000000..b4dd614 --- /dev/null +++ b/src/ladmin/md5calc.h @@ -0,0 +1,8 @@ +// $Id: md5calc.h,v 1.1.1.1 2004/09/10 17:26:53 MagicalTux Exp $ +#ifndef _MD5CALC_H_ +#define _MD5CALC_H_ + +void MD5_String(const char * string, char * output); +void MD5_String2binary(const char * string, char * output); + +#endif diff --git a/src/login/GNUmakefile b/src/login/GNUmakefile new file mode 100644 index 0000000..df6cb21 --- /dev/null +++ b/src/login/GNUmakefile @@ -0,0 +1,13 @@ +all: login-server
+txt: login-server
+
+COMMON_OBJ = ../common/core.o ../common/socket.o ../common/timer.o ../common/db.o ../common/lock.o ../common/malloc.o
+COMMON_H = ../common/core.h ../common/socket.h ../common/timer.h ../common/mmo.h ../common/version.h ../common/db.h ../common/lock.h ../common/malloc.h
+
+login-server: login.o md5calc.o $(COMMON_OBJ)
+ $(CC) -o ../../$@ login.o md5calc.o $(COMMON_OBJ)
+login.o: login.c login.h md5calc.h $(COMMON_H)
+md5calc.o: md5calc.c md5calc.h
+
+clean:
+ rm -f *.o ../../login-server
diff --git a/src/login/Makefile b/src/login/Makefile new file mode 100644 index 0000000..df6cb21 --- /dev/null +++ b/src/login/Makefile @@ -0,0 +1,13 @@ +all: login-server
+txt: login-server
+
+COMMON_OBJ = ../common/core.o ../common/socket.o ../common/timer.o ../common/db.o ../common/lock.o ../common/malloc.o
+COMMON_H = ../common/core.h ../common/socket.h ../common/timer.h ../common/mmo.h ../common/version.h ../common/db.h ../common/lock.h ../common/malloc.h
+
+login-server: login.o md5calc.o $(COMMON_OBJ)
+ $(CC) -o ../../$@ login.o md5calc.o $(COMMON_OBJ)
+login.o: login.c login.h md5calc.h $(COMMON_H)
+md5calc.o: md5calc.c md5calc.h
+
+clean:
+ rm -f *.o ../../login-server
diff --git a/src/login/login.c b/src/login/login.c new file mode 100644 index 0000000..8d9818d --- /dev/null +++ b/src/login/login.c @@ -0,0 +1,3698 @@ +// $Id: login.c,v 1.1.1.1 2004/09/10 17:26:53 MagicalTux Exp $ +// new version of the login-server by [Yor] + +#include <sys/types.h> +#include <sys/socket.h> +#include <stdio.h> +#include <stdlib.h> +#include <netinet/in.h> +#include <sys/time.h> +#include <time.h> +#include <sys/ioctl.h> +#include <sys/stat.h> // for stat/lstat/fstat +#include <unistd.h> +#include <signal.h> +#include <fcntl.h> +#include <string.h> +#include <arpa/inet.h> +#include <netdb.h> + +#include "core.h" +#include "socket.h" +#include "timer.h" +#include "login.h" +#include "mmo.h" +#include "version.h" +#include "db.h" +#include "lock.h" + +#ifdef PASSWORDENC +#include "md5calc.h" +#endif + +#ifdef MEMWATCH +#include "memwatch.h" +#endif + +int account_id_count = START_ACCOUNT_NUM; +int server_num; +int new_account_flag = 0; +int login_port = 6900; +char lan_char_ip[16]; +int subneti[4]; +int subnetmaski[4]; + +char account_filename[1024] = "save/account.txt"; +char GM_account_filename[1024] = "conf/GM_account.txt"; +char login_log_filename[1024] = "log/login.log"; +char login_log_unknown_packets_filename[1024] = "log/login_unknown_packets.log"; +char date_format[32] = "%Y-%m-%d %H:%M:%S"; +int save_unknown_packets = 0; +long creation_time_GM_account_file; +int gm_account_filename_check_timer = 15; // Timer to check if GM_account file has been changed and reload GM account automaticaly (in seconds; default: 15) + +int display_parse_login = 0; // 0: no, 1: yes +int display_parse_admin = 0; // 0: no, 1: yes +int display_parse_fromchar = 0; // 0: no, 1: yes (without packet 0x2714), 2: all packets + +struct mmo_char_server server[MAX_SERVERS]; +int server_fd[MAX_SERVERS]; +int server_freezeflag[MAX_SERVERS]; // Char-server anti-freeze system. Counter. 5 ok, 4...0 freezed +int anti_freeze_enable = 0; +int ANTI_FREEZE_INTERVAL = 15; + +int login_fd; + +enum { + ACO_DENY_ALLOW = 0, + ACO_ALLOW_DENY, + ACO_MUTUAL_FAILTURE, + ACO_STRSIZE = 128, +}; + +int access_order = ACO_DENY_ALLOW; +int access_allownum = 0; +int access_denynum = 0; +char *access_allow = NULL; +char *access_deny = NULL; + +int access_ladmin_allownum = 0; +char *access_ladmin_allow = NULL; + +int min_level_to_connect = 0; // minimum level of player/GM (0: player, 1-99: gm) to connect on the server +int add_to_unlimited_account = 0; // Give possibility or not to adjust (ladmin command: timeadd) the time of an unlimited account. +int start_limited_time = -1; // Starting additional sec from now for the limited time at creation of accounts (-1: unlimited time, 0 or more: additional sec from now) +int check_ip_flag = 1; // It's to check IP of a player between login-server and char-server (part of anti-hacking system) + +struct login_session_data { + int md5keylen; + char md5key[20]; +}; + +#define AUTH_FIFO_SIZE 256 +struct { + int account_id, login_id1, login_id2; + int ip, sex, delflag; +} auth_fifo[AUTH_FIFO_SIZE]; +int auth_fifo_pos = 0; + +struct auth_dat { + int account_id, sex; + char userid[24], pass[24], lastlogin[24]; + int logincount; + int state; // packet 0x006a value + 1 (0: compte OK) + char email[40]; // e-mail (by default: a@a.com) + char error_message[20]; // Message of error code #6 = Your are Prohibited to log in until %s (packet 0x006a) + time_t ban_until_time; // # of seconds 1/1/1970 (timestamp): ban time limit of the account (0 = no ban) + time_t connect_until_time; // # of seconds 1/1/1970 (timestamp): Validity limit of the account (0 = unlimited) + char last_ip[16]; // save of last IP of connection + char memo[255]; // a memo field + int account_reg2_num; + struct global_reg account_reg2[ACCOUNT_REG2_NUM]; +} *auth_dat; + +int auth_num = 0, auth_max = 0; + +// define the number of times that some players must authentify them before to save account file. +// it's just about normal authentification. If an account is created or modified, save is immediatly done. +// An authentification just change last connected IP and date. It already save in log file. +// set minimum auth change before save: +#define AUTH_BEFORE_SAVE_FILE 10 +// set divider of auth_num to found number of change before save +#define AUTH_SAVE_FILE_DIVIDER 50 +int auth_before_save_file = 0; // Counter. First save when 1st char-server do connection. + +int admin_state = 0; +char admin_pass[24] = ""; +char gm_pass[64] = ""; +int level_new_gm = 60; + +static struct dbt *gm_account_db; + +//------------------------------ +// Writing function of logs file +//------------------------------ +int login_log(char *fmt, ...) { + FILE *logfp; + va_list ap; + struct timeval tv; + char tmpstr[2048]; + + va_start(ap, fmt); + + logfp = fopen(login_log_filename, "a"); + if (logfp) { + if (fmt[0] == '\0') // jump a line if no message + fprintf(logfp, RETCODE); + else { + gettimeofday(&tv, NULL); + strftime(tmpstr, 24, date_format, localtime(&(tv.tv_sec))); + sprintf(tmpstr + strlen(tmpstr), ".%03d: %s", (int)tv.tv_usec / 1000, fmt); + vfprintf(logfp, tmpstr, ap); + } + fclose(logfp); + } + + va_end(ap); + return 0; +} + +//---------------------------------------------------------------------- +// Determine if an account (id) is a GM account +// and returns its level (or 0 if it isn't a GM account or if not found) +//---------------------------------------------------------------------- +int isGM(int account_id) { + struct gm_account *p; + + p = numdb_search(gm_account_db, account_id); + if (p == NULL) + return 0; + return p->level; +} + +//------------------------------------------------------- +// Reading function of GM accounts file (and their level) +//------------------------------------------------------- +int read_gm_account() { + char line[512]; + struct gm_account *p; + FILE *fp; + int c = 0; + int GM_level; + struct stat file_stat; + + free(gm_account_db); + gm_account_db = numdb_init(); + + // get last modify time/date + if (stat(GM_account_filename, &file_stat)) + creation_time_GM_account_file = 0; // error + else + creation_time_GM_account_file = file_stat.st_mtime; + + if ((fp = fopen(GM_account_filename, "r")) == NULL) { + printf("read_gm_account: GM accounts file [%s] not found.\n", GM_account_filename); + printf(" Actually, there is no GM accounts on the server.\n"); + login_log("read_gm_account: GM accounts file [%s] not found." RETCODE, GM_account_filename); + login_log(" Actually, there is no GM accounts on the server." RETCODE); + return 1; + } + // limited to 4000, because we send information to char-servers (more than 4000 GM accounts???) + // int (id) + int (level) = 8 bytes * 4000 = 32k (limit of packets in windows) + while(fgets(line, sizeof(line)-1, fp) && c < 4000) { + if ((line[0] == '/' && line[1] == '/') || line[0] == '\0' || line[0] == '\n' || line[0] == '\r') + continue; + p = calloc(sizeof(struct gm_account), 1); + if (p == NULL) { + printf("read_gm_account: memory allocation failure (malloc)!\n"); + exit(0); + } + if (sscanf(line, "%d %d", &p->account_id, &p->level) != 2 && sscanf(line, "%d: %d", &p->account_id, &p->level) != 2) + printf("read_gm_account: file [%s], invalid 'id_acount level' format.\n", GM_account_filename); + else if (p->level <= 0) + printf("read_gm_account: file [%s] %dth account (invalid level [0 or negative]: %d).\n", GM_account_filename, c+1, p->level); + else { + if (p->level > 99) { + printf("read_gm_account: file [%s] %dth account (invalid level, but corrected: %d->99).\n", GM_account_filename, c+1, p->level); + p->level = 99; + } + if ((GM_level = isGM(p->account_id)) > 0) { // if it's not a new account + if (GM_level == p->level) + printf("read_gm_account: GM account %d defined twice (same level: %d).\n", p->account_id, p->level); + else + printf("read_gm_account: GM account %d defined twice (levels: %d and %d).\n", p->account_id, GM_level, p->level); + } + if (GM_level != p->level) { // if new account or new level + numdb_insert(gm_account_db, p->account_id, p); + //printf("GM account:%d, level: %d->%d\n", p->account_id, GM_level, p->level); + if (GM_level == 0) { // if new account + c++; + if (c >= 4000) { + printf("***WARNING: 4000 GM accounts found. Next GM accounts are not readed.\n"); + login_log("***WARNING: 4000 GM accounts found. Next GM accounts are not readed." RETCODE); + } + } + } + } + } + fclose(fp); + + printf("read_gm_account: file '%s' readed (%d GM accounts found).\n", GM_account_filename, c); + login_log("read_gm_account: file '%s' readed (%d GM accounts found)." RETCODE, GM_account_filename, c); + + return 0; +} + +//-------------------------------------------------------------- +// Test of the IP mask +// (ip: IP to be tested, str: mask x.x.x.x/# or x.x.x.x/y.y.y.y) +//-------------------------------------------------------------- +int check_ipmask(unsigned int ip, const unsigned char *str) { + unsigned int mask = 0, i = 0, m, ip2, a0, a1, a2, a3; + unsigned char *p = (unsigned char *)&ip2, *p2 = (unsigned char *)&mask; + + if (sscanf(str, "%d.%d.%d.%d/%n", &a0, &a1, &a2, &a3, &i) != 4 || i == 0) + return 0; + p[0] = a0; p[1] = a1; p[2] = a2; p[3] = a3; + + if (sscanf(str+i, "%d.%d.%d.%d", &a0, &a1, &a2, &a3) == 4) { + p2[0] = a0; p2[1] = a1; p2[2] = a2; p2[3] = a3; + mask = ntohl(mask); + } else if (sscanf(str+i, "%d", &m) == 1 && m >= 0 && m <= 32) { + for(i = 0; i < m && i < 32; i++) + mask = (mask >> 1) | 0x80000000; + } else { + printf("check_ipmask: invalid mask [%s].\n", str); + return 0; + } + +// printf("Tested IP: %08x, network: %08x, network mask: %08x\n", +// (unsigned int)ntohl(ip), (unsigned int)ntohl(ip2), (unsigned int)mask); + return ((ntohl(ip) & mask) == (ntohl(ip2) & mask)); +} + +//--------------------- +// Access control by IP +//--------------------- +int check_ip(unsigned int ip) { + int i; + unsigned char *p = (unsigned char *)&ip; + char buf[32]; + enum { ACF_DEF, ACF_ALLOW, ACF_DENY } flag = ACF_DEF; + + if (access_allownum == 0 && access_denynum == 0) + return 1; // When there is no restriction, all IP are authorised. + +// + 012.345.: front match form, or +// all: all IP are matched, or +// 012.345.678.901/24: network form (mask with # of bits), or +// 012.345.678.901/255.255.255.0: network form (mask with ip mask) +// + Note about the DNS resolution (like www.ne.jp, etc.): +// There is no guarantee to have an answer. +// If we have an answer, there is no guarantee to have a 100% correct value. +// And, the waiting time (to check) can be long (over 1 minute to a timeout). That can block the software. +// So, DNS notation isn't authorised for ip checking. + sprintf(buf, "%d.%d.%d.%d.", p[0], p[1], p[2], p[3]); + + for(i = 0; i < access_allownum; i++) { + const char *p = access_allow + i * ACO_STRSIZE; + if (memcmp(p, buf, strlen(p)) == 0 || check_ipmask(ip, p)) { + flag = ACF_ALLOW; + if (access_order == ACO_ALLOW_DENY) + return 1; // With 'allow, deny' (deny if not allow), allow has priority + break; + } + } + + for(i = 0; i < access_denynum; i++) { + const char *p = access_deny + i * ACO_STRSIZE; + if (memcmp(p, buf, strlen(p)) == 0 || check_ipmask(ip, p)) { + flag = ACF_DENY; + return 0; // At this point, if it's 'deny', we refuse connection. + break; + } + } + + return (flag == ACF_ALLOW || access_order == ACO_DENY_ALLOW) ? 1:0; + // With 'mutual-failture', only 'allow' and non 'deny' IP are authorised. + // A non 'allow' (even non 'deny') IP is not authorised. It's like: if allowed and not denied, it's authorised. + // So, it's disapproval if you have no description at the time of 'mutual-failture'. + // With 'deny,allow' (allow if not deny), because here it's not deny, we authorise. +} + +//-------------------------------- +// Access control by IP for ladmin +//-------------------------------- +int check_ladminip(unsigned int ip) { + int i; + unsigned char *p = (unsigned char *)&ip; + char buf[32]; + + if (access_ladmin_allownum == 0) + return 1; // When there is no restriction, all IP are authorised. + +// + 012.345.: front match form, or +// all: all IP are matched, or +// 012.345.678.901/24: network form (mask with # of bits), or +// 012.345.678.901/255.255.255.0: network form (mask with ip mask) +// + Note about the DNS resolution (like www.ne.jp, etc.): +// There is no guarantee to have an answer. +// If we have an answer, there is no guarantee to have a 100% correct value. +// And, the waiting time (to check) can be long (over 1 minute to a timeout). That can block the software. +// So, DNS notation isn't authorised for ip checking. + sprintf(buf, "%d.%d.%d.%d.", p[0], p[1], p[2], p[3]); + + for(i = 0; i < access_ladmin_allownum; i++) { + const char *p = access_ladmin_allow + i * ACO_STRSIZE; + if (memcmp(p, buf, strlen(p)) == 0 || check_ipmask(ip, p)) { + return 1; + } + } + + return 0; +} + +//----------------------------------------------------- +// Function to suppress control characters in a string. +//----------------------------------------------------- +int remove_control_chars(unsigned char *str) { + int i; + int change = 0; + + for(i = 0; str[i]; i++) { + if (str[i] < 32) { + str[i] = '_'; + change = 1; + } + } + + return change; +} + +//--------------------------------------------------- +// E-mail check: return 0 (not correct) or 1 (valid). +//--------------------------------------------------- +int e_mail_check(unsigned char *email) { + char ch; + unsigned char* last_arobas; + + // athena limits + if (strlen(email) < 3 || strlen(email) > 39) + return 0; + + // part of RFC limits (official reference of e-mail description) + if (strchr(email, '@') == NULL || email[strlen(email)-1] == '@') + return 0; + + if (email[strlen(email)-1] == '.') + return 0; + + last_arobas = strrchr(email, '@'); + + if (strstr(last_arobas, "@.") != NULL || + strstr(last_arobas, "..") != NULL) + return 0; + + for(ch = 1; ch < 32; ch++) { + if (strchr(last_arobas, ch) != NULL) { + return 0; + break; + } + } + + if (strchr(last_arobas, ' ') != NULL || + strchr(last_arobas, ';') != NULL) + return 0; + + // all correct + return 1; +} + +//----------------------------------------------- +// Search an account id +// (return account index or -1 (if not found)) +// If exact account name is not found, +// the function checks without case sensitive +// and returns index if only 1 account is found +// and similar to the searched name. +//----------------------------------------------- +int search_account_index(char* account_name) { + int i, quantity, index; + + quantity = 0; + index = -1; + for(i = 0; i < auth_num; i++) { + // Without case sensitive check (increase the number of similar account names found) + if (stricmp(auth_dat[i].userid, account_name) == 0) { + // Strict comparison (if found, we finish the function immediatly with correct value) + if (strcmp(auth_dat[i].userid, account_name) == 0) + return i; + quantity++; + index = i; + } + } + // Here, the exact account name is not found + // We return the found index of a similar account ONLY if there is 1 similar account + if (quantity == 1) + return index; + + // Exact account name is not found and 0 or more than 1 similar accounts have been found ==> we say not found + return -1; +} + +//-------------------------------------------------------- +// Create a string to save the account in the account file +//-------------------------------------------------------- +int mmo_auth_tostr(char *str, struct auth_dat *p) { + int i; + char *str_p = str; + + str_p += sprintf(str_p, "%d\t%s\t%s\t%s\t%c\t%d\t%d\t" + "%s\t%s\t%ld\t%s\t%s\t%ld\t", + p->account_id, p->userid, p->pass, p->lastlogin, + (p->sex == 2) ? 'S' : (p->sex ? 'M' : 'F'), + p->logincount, p->state, + p->email, p->error_message, + p->connect_until_time, p->last_ip, p->memo, p->ban_until_time); + + for(i = 0; i < p->account_reg2_num; i++) + if (p->account_reg2[i].str[0]) + str_p += sprintf(str_p, "%s,%d ", p->account_reg2[i].str, p->account_reg2[i].value); + + return 0; +} + +//--------------------------------- +// Reading of the accounts database +//--------------------------------- +int mmo_auth_init(void) { + FILE *fp; + int account_id, logincount, state, n, i, j, v; + char line[2048], *p, userid[2048], pass[2048], lastlogin[2048], sex, email[2048], error_message[2048], last_ip[2048], memo[2048]; + time_t ban_until_time; + time_t connect_until_time; + char str[2048]; + int GM_count = 0; + int server_count = 0; + + auth_dat = calloc(sizeof(struct auth_dat) * 256, 1); + auth_max = 256; + + fp = fopen(account_filename, "r"); + if (fp == NULL) { + // no account file -> no account -> no login, including char-server (ERROR) + printf("\033[1;31mmmo_auth_init: Accounts file [%s] not found.\033[0m\n", account_filename); + return 0; + } + + while(fgets(line, sizeof(line)-1, fp) != NULL) { + if (line[0] == '/' && line[1] == '/') + continue; + line[sizeof(line)-1] = '\0'; + p = line; + + // database version reading (v2) + if (((i = sscanf(line, "%d\t%[^\t]\t%[^\t]\t%[^\t]\t%c\t%d\t%d\t" + "%[^\t]\t%[^\t]\t%ld\t%[^\t]\t%[^\t]\t%ld%n", + &account_id, userid, pass, lastlogin, &sex, &logincount, &state, + email, error_message, &connect_until_time, last_ip, memo, &ban_until_time, &n)) == 13 && line[n] == '\t') || + ((i = sscanf(line, "%d\t%[^\t]\t%[^\t]\t%[^\t]\t%c\t%d\t%d\t" + "%[^\t]\t%[^\t]\t%ld\t%[^\t]\t%[^\t]%n", + &account_id, userid, pass, lastlogin, &sex, &logincount, &state, + email, error_message, &connect_until_time, last_ip, memo, &n)) == 12 && line[n] == '\t')) { + n = n + 1; + + // Some checks + if (account_id > END_ACCOUNT_NUM) { + printf("\033[1;31mmmo_auth_init: ******Error: an account has an id higher than %d\n", END_ACCOUNT_NUM); + printf(" account id #%d -> account not read (saved in log file).\033[0m\n", account_id); + login_log("mmmo_auth_init: ******Error: an account has an id higher than %d." RETCODE, END_ACCOUNT_NUM); + login_log(" account id #%d -> account not read (saved in next line):" RETCODE, account_id); + login_log("%s", line); + continue; + } + userid[23] = '\0'; + remove_control_chars(userid); + for(j = 0; j < auth_num; j++) { + if (auth_dat[j].account_id == account_id) { + printf("\033[1;31mmmo_auth_init: ******Error: an account has an identical id to another.\n"); + printf(" account id #%d -> new account not read (saved in log file).\033[0m\n", account_id); + login_log("mmmo_auth_init: ******Error: an account has an identical id to another." RETCODE); + login_log(" account id #%d -> new account not read (saved in next line):" RETCODE, account_id); + login_log("%s", line); + break; + } else if (strcmp(auth_dat[j].userid, userid) == 0) { + printf("\033[1;31mmmo_auth_init: ******Error: account name already exists.\n"); + printf(" account name '%s' -> new account not read.\n", userid); // 2 lines, account name can be long. + printf(" Account saved in log file.\033[0m\n"); + login_log("mmmo_auth_init: ******Error: an account has an identical id to another." RETCODE); + login_log(" account id #%d -> new account not read (saved in next line):" RETCODE, account_id); + login_log("%s", line); + break; + } + } + if (j != auth_num) + continue; + + if (auth_num >= auth_max) { + auth_max += 256; + auth_dat = realloc(auth_dat, sizeof(struct auth_dat) * auth_max); + } + + memset(&auth_dat[auth_num], '\0', sizeof(struct auth_dat)); + + auth_dat[auth_num].account_id = account_id; + + strncpy(auth_dat[auth_num].userid, userid, 24); + + pass[23] = '\0'; + remove_control_chars(pass); + strncpy(auth_dat[auth_num].pass, pass, 24); + + lastlogin[23] = '\0'; + remove_control_chars(lastlogin); + strncpy(auth_dat[auth_num].lastlogin, lastlogin, 24); + + auth_dat[auth_num].sex = (sex == 'S' || sex == 's') ? 2 : (sex == 'M' || sex == 'm'); + + if (logincount >= 0) + auth_dat[auth_num].logincount = logincount; + else + auth_dat[auth_num].logincount = 0; + + if (state > 255) + auth_dat[auth_num].state = 100; + else if (state < 0) + auth_dat[auth_num].state = 0; + else + auth_dat[auth_num].state = state; + + if (e_mail_check(email) == 0) { + printf("Account %s (%d): invalid e-mail (replaced par a@a.com).\n", auth_dat[auth_num].userid, auth_dat[auth_num].account_id); + strncpy(auth_dat[auth_num].email, "a@a.com", 40); + } else { + remove_control_chars(email); + strncpy(auth_dat[auth_num].email, email, 40); + } + + error_message[19] = '\0'; + remove_control_chars(error_message); + if (error_message[0] == '\0' || state != 7) { // 7, because state is packet 0x006a value + 1 + strncpy(auth_dat[auth_num].error_message, "-", 20); + } else { + strncpy(auth_dat[auth_num].error_message, error_message, 20); + } + + if (i == 13) + auth_dat[auth_num].ban_until_time = ban_until_time; + else + auth_dat[auth_num].ban_until_time = 0; + + auth_dat[auth_num].connect_until_time = connect_until_time; + + last_ip[15] = '\0'; + remove_control_chars(last_ip); + strncpy(auth_dat[auth_num].last_ip, last_ip, 16); + + memo[254] = '\0'; + remove_control_chars(memo); + strncpy(auth_dat[auth_num].memo, memo, 255); + + for(j = 0; j < ACCOUNT_REG2_NUM; j++) { + p += n; + if (sscanf(p, "%[^\t,],%d %n", str, &v, &n) != 2) { + // We must check if a str is void. If it's, we can continue to read other REG2. + // Account line will have something like: str2,9 ,9 str3,1 (here, ,9 is not good) + if (p[0] == ',' && sscanf(p, ",%d %n", &v, &n) == 1) { + j--; + continue; + } else + break; + } + str[31] = '\0'; + remove_control_chars(str); + strncpy(auth_dat[auth_num].account_reg2[j].str, str, 32); + auth_dat[auth_num].account_reg2[j].value = v; + } + auth_dat[auth_num].account_reg2_num = j; + + if (isGM(account_id) > 0) + GM_count++; + if (auth_dat[auth_num].sex == 2) + server_count++; + + auth_num++; + if (account_id >= account_id_count) + account_id_count = account_id + 1; + + // Old athena database version reading (v1) + } else if ((i = sscanf(line, "%d\t%[^\t]\t%[^\t]\t%[^\t]\t%c\t%d\t%d\t%n", + &account_id, userid, pass, lastlogin, &sex, &logincount, &state, &n)) >= 5) { + if (account_id > END_ACCOUNT_NUM) { + printf("\033[1;31mmmo_auth_init: ******Error: an account has an id higher than %d\n", END_ACCOUNT_NUM); + printf(" account id #%d -> account not read (saved in log file).\033[0m\n", account_id); + login_log("mmmo_auth_init: ******Error: an account has an id higher than %d." RETCODE, END_ACCOUNT_NUM); + login_log(" account id #%d -> account not read (saved in next line):" RETCODE, account_id); + login_log("%s", line); + continue; + } + userid[23] = '\0'; + remove_control_chars(userid); + for(j = 0; j < auth_num; j++) { + if (auth_dat[j].account_id == account_id) { + printf("\033[1;31mmmo_auth_init: ******Error: an account has an identical id to another.\n"); + printf(" account id #%d -> new account not read (saved in log file).\033[0m\n", account_id); + login_log("mmmo_auth_init: ******Error: an account has an identical id to another." RETCODE); + login_log(" account id #%d -> new account not read (saved in next line):" RETCODE, account_id); + login_log("%s", line); + break; + } else if (strcmp(auth_dat[j].userid, userid) == 0) { + printf("\033[1;31mmmo_auth_init: ******Error: account name already exists.\n"); + printf(" account name '%s' -> new account not read.\n", userid); // 2 lines, account name can be long. + printf(" Account saved in log file.\033[0m\n"); + login_log("mmmo_auth_init: ******Error: an account has an identical id to another." RETCODE); + login_log(" account id #%d -> new account not read (saved in next line):" RETCODE, account_id); + login_log("%s", line); + break; + } + } + if (j != auth_num) + continue; + + if (auth_num >= auth_max) { + auth_max += 256; + auth_dat = realloc(auth_dat, sizeof(struct auth_dat) * auth_max); + } + + memset(&auth_dat[auth_num], '\0', sizeof(struct auth_dat)); + + auth_dat[auth_num].account_id = account_id; + + strncpy(auth_dat[auth_num].userid, userid, 24); + + pass[23] = '\0'; + remove_control_chars(pass); + strncpy(auth_dat[auth_num].pass, pass, 24); + + lastlogin[23] = '\0'; + remove_control_chars(lastlogin); + strncpy(auth_dat[auth_num].lastlogin, lastlogin, 24); + + auth_dat[auth_num].sex = (sex == 'S' || sex == 's') ? 2 : (sex == 'M' || sex == 'm'); + + if (i >= 6) { + if (logincount >= 0) + auth_dat[auth_num].logincount = logincount; + else + auth_dat[auth_num].logincount = 0; + } else + auth_dat[auth_num].logincount = 0; + + if (i >= 7) { + if (state > 255) + auth_dat[auth_num].state = 100; + else if (state < 0) + auth_dat[auth_num].state = 0; + else + auth_dat[auth_num].state = state; + } else + auth_dat[auth_num].state = 0; + + // Initialization of new data + strncpy(auth_dat[auth_num].email, "a@a.com", 40); + strncpy(auth_dat[auth_num].error_message, "-", 20); + auth_dat[auth_num].ban_until_time = 0; + auth_dat[auth_num].connect_until_time = 0; + strncpy(auth_dat[auth_num].last_ip, "-", 16); + strncpy(auth_dat[auth_num].memo, "-", 255); + + for(j = 0; j < ACCOUNT_REG2_NUM; j++) { + p += n; + if (sscanf(p, "%[^\t,],%d %n", str, &v, &n) != 2) { + // We must check if a str is void. If it's, we can continue to read other REG2. + // Account line will have something like: str2,9 ,9 str3,1 (here, ,9 is not good) + if (p[0] == ',' && sscanf(p, ",%d %n", &v, &n) == 1) { + j--; + continue; + } else + break; + } + str[31] = '\0'; + remove_control_chars(str); + strncpy(auth_dat[auth_num].account_reg2[j].str, str, 32); + auth_dat[auth_num].account_reg2[j].value = v; + } + auth_dat[auth_num].account_reg2_num = j; + + if (isGM(account_id) > 0) + GM_count++; + if (auth_dat[auth_num].sex == 2) + server_count++; + + auth_num++; + if (account_id >= account_id_count) + account_id_count = account_id + 1; + + } else { + i = 0; + if (sscanf(line, "%d\t%%newid%%\n%n", &account_id, &i) == 1 && + i > 0 && account_id > account_id_count) + account_id_count = account_id; + } + } + fclose(fp); + + if (auth_num == 0) { + printf("mmo_auth_init: No account found in %s.\n", account_filename); + sprintf(line, "No account found in %s.", account_filename); + } else { + if (auth_num == 1) { + printf("mmo_auth_init: 1 account read in %s,\n", account_filename); + sprintf(line, "1 account read in %s,", account_filename); + } else { + printf("mmo_auth_init: %d accounts read in %s,\n", auth_num, account_filename); + sprintf(line, "%d accounts read in %s,", auth_num, account_filename); + } + if (GM_count == 0) { + printf(" of which is no GM account, and "); + sprintf(str, "%s of which is no GM account and", line); + } else if (GM_count == 1) { + printf(" of which is 1 GM account, and "); + sprintf(str, "%s of which is 1 GM account and", line); + } else { + printf(" of which is %d GM accounts, and ", GM_count); + sprintf(str, "%s of which is %d GM accounts and", line, GM_count); + } + if (server_count == 0) { + printf("no server account ('S').\n"); + sprintf(line, "%s no server account ('S').", str); + } else if (server_count == 1) { + printf("1 server account ('S').\n"); + sprintf(line, "%s 1 server account ('S').", str); + } else { + printf("%d server accounts ('S').\n", server_count); + sprintf(line, "%s %d server accounts ('S').", str, server_count); + } + } + login_log("%s" RETCODE, line); + + return 0; +} + +//------------------------------------------ +// Writing of the accounts database file +// (accounts are sorted by id before save) +//------------------------------------------ +void mmo_auth_sync(void) { + FILE *fp; + int i, j, k, lock; + int id[auth_num]; + char line[65536]; + + // Sorting before save + for(i = 0; i < auth_num; i++) { + id[i] = i; + for(j = 0; j < i; j++) { + if (auth_dat[i].account_id < auth_dat[id[j]].account_id) { + for(k = i; k > j; k--) + id[k] = id[k-1]; + id[j] = i; // id[i] + break; + } + } + } + + // Data save + fp = lock_fopen(account_filename, &lock); + if (fp == NULL) + return; + fprintf(fp, "// Accounts file: here are saved all information about the accounts.\n"); + fprintf(fp, "// Structure: ID, account name, password, last login time, sex, # of logins, state, email, error message for state 7, validity time, last (accepted) login ip, memo field, ban timestamp, repeated(register text, register value)\n"); + fprintf(fp, "// Some explanations:\n"); + fprintf(fp, "// account name : between 4 to 23 char for a normal account (standard client can't send less than 4 char).\n"); + fprintf(fp, "// account password: between 4 to 23 char\n"); + fprintf(fp, "// sex : M or F for normal accounts, S for server accounts\n"); + fprintf(fp, "// state : 0: account is ok, 1 to 256: error code of packet 0x006a + 1\n"); + fprintf(fp, "// email : between 3 to 39 char (a@a.com is like no email)\n"); + fprintf(fp, "// error message : text for the state 7: 'Your are Prohibited to login until <text>'. Max 19 char\n"); + fprintf(fp, "// valitidy time : 0: unlimited account, <other value>: date calculated by addition of 1/1/1970 + value (number of seconds since the 1/1/1970)\n"); + fprintf(fp, "// memo field : max 254 char\n"); + fprintf(fp, "// ban time : 0: no ban, <other value>: banned until the date: date calculated by addition of 1/1/1970 + value (number of seconds since the 1/1/1970)\n"); + for(i = 0; i < auth_num; i++) { + k = id[i]; // use of sorted index + if (auth_dat[k].account_id < 0) + continue; + + mmo_auth_tostr(line, &auth_dat[k]); + fprintf(fp, "%s" RETCODE, line); + } + fprintf(fp, "%d\t%%newid%%\n", account_id_count); + + lock_fclose(fp, account_filename, &lock); + + // set new counter to minimum number of auth before save + auth_before_save_file = auth_num / AUTH_SAVE_FILE_DIVIDER; // Re-initialise counter. We have save. + if (auth_before_save_file < AUTH_BEFORE_SAVE_FILE) + auth_before_save_file = AUTH_BEFORE_SAVE_FILE; + + return; +} + +//----------------------------------------------------- +// Check if we must save accounts file or not +// every minute, we check if we must save because we +// have do some authentifications without arrive to +// the minimum of authentifications for the save. +// Note: all other modification of accounts (deletion, +// change of some informations excepted lastip/ +// lastlogintime, creation) are always save +// immediatly and set the minimum of +// authentifications to its initialization value. +//----------------------------------------------------- +int check_auth_sync(int tid, unsigned int tick, int id, int data) { + // we only save if necessary: + // we have do some authentifications without do saving + if (auth_before_save_file < AUTH_BEFORE_SAVE_FILE || + auth_before_save_file < (int)(auth_num / AUTH_SAVE_FILE_DIVIDER)) + mmo_auth_sync(); + + return 0; +} + +//-------------------------------------------------------------------- +// Packet send to all char-servers, except one (wos: without our self) +//-------------------------------------------------------------------- +int charif_sendallwos(int sfd, unsigned char *buf, unsigned int len) { + int i, c; + + for(i = 0, c = 0; i < MAX_SERVERS; i++) { + int fd; + if ((fd = server_fd[i]) >= 0 && fd != sfd) { + memcpy(WFIFOP(fd,0), buf, len); + WFIFOSET(fd, len); + c++; + } + } + return c; +} + +//----------------------------------------------------- +// Send GM accounts to all char-server +//----------------------------------------------------- +void send_GM_accounts() { + int i; + char buf[32000]; + int GM_value; + int len; + + len = 4; + WBUFW(buf,0) = 0x2732; + for(i = 0; i < auth_num; i++) + // send only existing accounts. We can not create a GM account when server is online. + if ((GM_value = isGM(auth_dat[i].account_id)) > 0) { + WBUFL(buf,len) = auth_dat[i].account_id; + WBUFB(buf,len+4) = (unsigned char)GM_value; + len += 5; + } + WBUFW(buf,2) = len; + charif_sendallwos(-1, buf, len); + + return; +} + +//----------------------------------------------------- +// Check if GM file account have been changed +//----------------------------------------------------- +int check_GM_file(int tid, unsigned int tick, int id, int data) { + struct stat file_stat; + long new_time; + + // if we would not check + if (gm_account_filename_check_timer < 1) + return 0; + + // get last modify time/date + if (stat(GM_account_filename, &file_stat)) + new_time = 0; // error + else + new_time = file_stat.st_mtime; + + if (new_time != creation_time_GM_account_file) { + read_gm_account(); + send_GM_accounts(); + } + + return 0; +} + +//------------------------------------- +// Account creation (with e-mail check) +//------------------------------------- +int mmo_auth_new(struct mmo_account* account, char sex, char* email) { + time_t timestamp, timestamp_temp; + struct tm *tmtime; + int i = auth_num; + + if (auth_num >= auth_max) { + auth_max += 256; + auth_dat = realloc(auth_dat, sizeof(struct auth_dat) * auth_max); + } + + memset(&auth_dat[i], '\0', sizeof(struct auth_dat)); + + while (isGM(account_id_count) > 0) + account_id_count++; + + auth_dat[i].account_id = account_id_count++; + + strncpy(auth_dat[i].userid, account->userid, 24); + auth_dat[i].userid[23] = '\0'; + + strncpy(auth_dat[i].pass, account->passwd, 24); + auth_dat[i].pass[23] = '\0'; + + memcpy(auth_dat[i].lastlogin, "-", 2); + + auth_dat[i].sex = (sex == 'M'); + + auth_dat[i].logincount = 0; + + auth_dat[i].state = 0; + + if (e_mail_check(email) == 0) + strncpy(auth_dat[i].email, "a@a.com", 40); + else + strncpy(auth_dat[i].email, email, 40); + + strncpy(auth_dat[i].error_message, "-", 20); + + auth_dat[i].ban_until_time = 0; + + if (start_limited_time < 0) + auth_dat[i].connect_until_time = 0; // unlimited + else { // limited time + timestamp = time(NULL) + start_limited_time; + // double conversion to be sure that it is possible + tmtime = localtime(×tamp); + timestamp_temp = mktime(tmtime); + if (timestamp_temp != -1 && (timestamp_temp + 3600) >= timestamp) // check possible value and overflow (and avoid summer/winter hour) + auth_dat[i].connect_until_time = timestamp_temp; + else + auth_dat[i].connect_until_time = 0; // unlimited + } + + strncpy(auth_dat[i].last_ip, "-", 16); + + strncpy(auth_dat[i].memo, "-", 255); + + auth_dat[i].account_reg2_num = 0; + + auth_num++; + + return (account_id_count - 1); +} + +//--------------------------------------- +// Check/authentification of a connection +//--------------------------------------- +int mmo_auth(struct mmo_account* account, int fd) { + int i; + struct timeval tv; + char tmpstr[256]; + int len, newaccount = 0; + char md5str[64], md5bin[32]; + char ip[16]; + unsigned char *sin_addr = (unsigned char *)&session[fd]->client_addr.sin_addr; + + sprintf(ip, "%d.%d.%d.%d", sin_addr[0], sin_addr[1], sin_addr[2], sin_addr[3]); + + len = strlen(account->userid) - 2; + // Account creation with _M/_F + if (account->passwdenc == 0 && account->userid[len] == '_' && + (account->userid[len+1] == 'F' || account->userid[len+1] == 'M') && new_account_flag == 1 && + account_id_count <= END_ACCOUNT_NUM && len >= 4 && strlen(account->passwd) >= 4) { + if (new_account_flag == 1) + newaccount = 1; + account->userid[len] = '\0'; + } + + // Strict account search + for(i = 0; i < auth_num; i++) { + if (strcmp(account->userid, auth_dat[i].userid) == 0) + break; + } + // if there is no creation request and strict account search fails, we do a no sensitive case research for index + if (newaccount == 0 && i == auth_num) { + i = search_account_index(account->userid); + if (i == -1) + i = auth_num; + else + memcpy(account->userid, auth_dat[i].userid, 24); // for the possible tests/checks afterwards (copy correcte sensitive case). + } + + if (i != auth_num) { + int encpasswdok = 0; + struct login_session_data *ld; + if (newaccount) { + login_log("Attempt of creation of an already existant account (account: %s_%c, pass: %s, received pass: %s, ip: %s)" RETCODE, + account->userid, account->userid[len+1], auth_dat[i].pass, account->passwd, ip); + return 9; // 9 = Account already exists + } + ld = session[fd]->session_data; +#ifdef PASSWORDENC + if (account->passwdenc > 0) { + int j = account->passwdenc; + if (!ld) { + login_log("Md5 key not created (account: %s, ip: %s)" RETCODE, account->userid, ip); + return 1; // 1 = Incorrect Password + } + if (j > 2) + j = 1; + do { + if (j == 1) { + strncpy(md5str, ld->md5key, sizeof(ld->md5key)); // 20 + strcat(md5str, auth_dat[i].pass); // 24 + } else if (j == 2) { + strncpy(md5str, auth_dat[i].pass, sizeof(auth_dat[i].pass)); // 24 + strcat(md5str, ld->md5key); // 20 + } else + md5str[0] = '\0'; + md5str[sizeof(md5str)-1] = '\0'; // 64 + MD5_String2binary(md5str, md5bin); + encpasswdok = (memcmp(account->passwd, md5bin, 16) == 0); + } while (j < 2 && !encpasswdok && (j++) != account->passwdenc); +// printf("key[%s] md5 [%s] ", md5key, md5); +// printf("client [%s] accountpass [%s]\n", account->passwd, auth_dat[i].pass); + } +#endif + if ((strcmp(account->passwd, auth_dat[i].pass) && !encpasswdok)) { + if (account->passwdenc == 0) + login_log("Invalid password (account: %s, pass: %s, received pass: %s, ip: %s)" RETCODE, account->userid, auth_dat[i].pass, account->passwd, ip); +#ifdef PASSWORDENC + else { + char logbuf[512], *p = logbuf; + int j; + p += sprintf(p, "Invalid password (account: %s, received md5[", account->userid); + for(j = 0; j < 16; j++) + p += sprintf(p, "%02x", ((unsigned char *)account->passwd)[j]); + p += sprintf(p,"] calculated md5["); + for(j = 0; j < 16; j++) + p += sprintf(p, "%02x", ((unsigned char *)md5bin)[j]); + p += sprintf(p, "] md5 key["); + for(j = 0; j < ld->md5keylen; j++) + p += sprintf(p, "%02x", ((unsigned char *)ld->md5key)[j]); + p += sprintf(p, "], ip: %s)" RETCODE, ip); + login_log(logbuf); + } +#endif + return 1; // 1 = Incorrect Password + } + + if (auth_dat[i].state) { + login_log("Connection refused (account: %s, pass: %s, state: %d, ip: %s)" RETCODE, + account->userid, account->passwd, auth_dat[i].state, ip); + switch(auth_dat[i].state) { // packet 0x006a value + 1 + case 1: // 0 = Unregistered ID + case 2: // 1 = Incorrect Password + case 3: // 2 = This ID is expired + case 4: // 3 = Rejected from Server + case 5: // 4 = You have been blocked by the GM Team + case 6: // 5 = Your Game's EXE file is not the latest version + case 7: // 6 = Your are Prohibited to log in until %s + case 8: // 7 = Server is jammed due to over populated + case 9: // 8 = No MSG (actually, all states after 9 except 99 are No MSG, use only this) + case 100: // 99 = This ID has been totally erased + return auth_dat[i].state - 1; + break; + default: + return 99; // 99 = ID has been totally erased + break; + } + } + + if (auth_dat[i].ban_until_time != 0) { // if account is banned + strftime(tmpstr, 20, date_format, localtime(&auth_dat[i].ban_until_time)); + tmpstr[19] = '\0'; + if (auth_dat[i].ban_until_time > time(NULL)) { // always banned + login_log("Connection refused (account: %s, pass: %s, banned until %s, ip: %s)" RETCODE, + account->userid, account->passwd, tmpstr, ip); + return 6; // 6 = Your are Prohibited to log in until %s + } else { // ban is finished + login_log("End of ban (account: %s, pass: %s, previously banned until %s -> not more banned, ip: %s)" RETCODE, + account->userid, account->passwd, tmpstr, ip); + auth_dat[i].ban_until_time = 0; // reset the ban time + } + } + + if (auth_dat[i].connect_until_time != 0 && auth_dat[i].connect_until_time < time(NULL)) { + login_log("Connection refused (account: %s, pass: %s, expired ID, ip: %s)" RETCODE, + account->userid, account->passwd, ip); + return 2; // 2 = This ID is expired + } + + login_log("Authentification accepted (account: %s (id: %d), ip: %s)" RETCODE, account->userid, auth_dat[i].account_id, ip); + } else { + if (newaccount == 0) { + login_log("Unknown account (account: %s, received pass: %s, ip: %s)" RETCODE, + account->userid, account->passwd, ip); + return 0; // 0 = Unregistered ID + } else { + int new_id = mmo_auth_new(account, account->userid[len+1], "a@a.com"); + login_log("Account creation and authentification accepted (account %s (id: %d), pass: %s, sex: %c, connection with _F/_M, ip: %s)" RETCODE, + account->userid, new_id, account->passwd, account->userid[len+1], ip); + auth_before_save_file = 0; // Creation of an account -> save accounts file immediatly + } + } + + gettimeofday(&tv, NULL); + strftime(tmpstr, 24, date_format, localtime(&(tv.tv_sec))); + sprintf(tmpstr + strlen(tmpstr), ".%03d", (int)tv.tv_usec / 1000); + + account->account_id = auth_dat[i].account_id; + account->login_id1 = rand(); + account->login_id2 = rand(); + memcpy(account->lastlogin, auth_dat[i].lastlogin, 24); + memcpy(auth_dat[i].lastlogin, tmpstr, 24); + account->sex = auth_dat[i].sex; + strncpy(auth_dat[i].last_ip, ip, 16); + auth_dat[i].logincount++; + + // Save until for change ip/time of auth is not very useful => limited save for that + // Save there informations isnot necessary, because they are saved in log file. + if (--auth_before_save_file <= 0) // Reduce counter. 0 or less, we save + mmo_auth_sync(); + + return -1; // account OK +} + +//------------------------------- +// Char-server anti-freeze system +//------------------------------- +int char_anti_freeze_system(int tid, unsigned int tick, int id, int data) { + int i; + + //printf("Entering in char_anti_freeze_system function to check freeze of servers.\n"); + for(i = 0; i < MAX_SERVERS; i++) { + if (server_fd[i] >= 0) {// if char-server is online + //printf("char_anti_freeze_system: server #%d '%s', flag: %d.\n", i, server[i].name, server_freezeflag[i]); + if (server_freezeflag[i]-- < 1) { // Char-server anti-freeze system. Counter. 5 ok, 4...0 freezed + printf("Char-server anti-freeze system: char-server #%d '%s' is freezed -> disconnection.\n", i, server[i].name); + login_log("Char-server anti-freeze system: char-server #%d '%s' is freezed -> disconnection." RETCODE, + i, server[i].name); + session[server_fd[i]]->eof = 1; + } + } + } + + return 0; +} + +//-------------------------------- +// Packet parsing for char-servers +//-------------------------------- +int parse_fromchar(int fd) { + int i, j, id; + unsigned char *p = (unsigned char *) &session[fd]->client_addr.sin_addr; + char ip[16]; + + sprintf(ip, "%d.%d.%d.%d", p[0], p[1], p[2], p[3]); + + for(id = 0; id < MAX_SERVERS; id++) + if (server_fd[id] == fd) + break; + if (id == MAX_SERVERS || session[fd]->eof) { + if (id < MAX_SERVERS) { + printf("Char-server '%s' has disconnected.\n", server[id].name); + login_log("Char-server '%s' has disconnected (ip: %s)." RETCODE, + server[id].name, ip); + server_fd[id] = -1; + memset(&server[id], 0, sizeof(struct mmo_char_server)); + } + close(fd); + delete_session(fd); + return 0; + } + + while (RFIFOREST(fd) >= 2) { + if (display_parse_fromchar == 2 || (display_parse_fromchar == 1 && RFIFOW(fd,0) != 0x2714)) // 0x2714 is done very often (number of players) + printf("parse_fromchar: connection #%d, packet: 0x%x (with being read: %d bytes).\n", fd, RFIFOW(fd,0), RFIFOREST(fd)); + + switch (RFIFOW(fd,0)) { + // request from map-server via char-server to reload GM accounts (by Yor). + case 0x2709: + login_log("Char-server '%s': Request to re-load GM configuration file (ip: %s)." RETCODE, server[id].name, ip); + read_gm_account(); + // send GM accounts to all char-servers + send_GM_accounts(); + RFIFOSKIP(fd,2); + break; + + case 0x2712: // request from char-server to authentify an account + if (RFIFOREST(fd) < 19) + return 0; + { + int acc; + acc = RFIFOL(fd,2); // speed up + for(i = 0; i < AUTH_FIFO_SIZE; i++) { + if (auth_fifo[i].account_id == acc && + auth_fifo[i].login_id1 == RFIFOL(fd,6) && +#if CMP_AUTHFIFO_LOGIN2 != 0 + auth_fifo[i].login_id2 == RFIFOL(fd,10) && // relate to the versions higher than 18 +#endif + auth_fifo[i].sex == RFIFOB(fd,14) && + (!check_ip_flag || auth_fifo[i].ip == RFIFOL(fd,15)) && + !auth_fifo[i].delflag) { + int p, k; + auth_fifo[i].delflag = 1; + login_log("Char-server '%s': authentification of the account %d accepted (ip: %s)." RETCODE, + server[id].name, acc, ip); +// printf("%d\n", i); + for(k = 0; k < auth_num; k++) { + if (auth_dat[k].account_id == acc) { + WFIFOW(fd,0) = 0x2729; // Sending of the account_reg2 + WFIFOL(fd,4) = acc; + for(p = 8, j = 0; j < auth_dat[k].account_reg2_num; p += 36, j++) { + memcpy(WFIFOP(fd,p), auth_dat[k].account_reg2[j].str, 32); + WFIFOL(fd,p+32) = auth_dat[k].account_reg2[j].value; + } + WFIFOW(fd,2) = p; + WFIFOSET(fd,p); +// printf("parse_fromchar: Sending of account_reg2: login->char (auth fifo)\n"); + WFIFOW(fd,0) = 0x2713; + WFIFOL(fd,2) = acc; + WFIFOB(fd,6) = 0; + memcpy(WFIFOP(fd, 7), auth_dat[k].email, 40); + WFIFOL(fd,47) = (unsigned long)auth_dat[k].connect_until_time; + WFIFOSET(fd,51); + break; + } + } + break; + } + } + // authentification not found + if (i == AUTH_FIFO_SIZE) { + login_log("Char-server '%s': authentification of the account %d REFUSED (ip: %s)." RETCODE, + server[id].name, acc, ip); + WFIFOW(fd,0) = 0x2713; + WFIFOL(fd,2) = acc; + WFIFOB(fd,6) = 1; + // It is unnecessary to send email + // It is unnecessary to send validity date of the account + WFIFOSET(fd,51); + } + } + RFIFOSKIP(fd,19); + break; + + case 0x2714: + if (RFIFOREST(fd) < 6) + return 0; + //printf("parse_fromchar: Receiving of the users number of the server '%s': %d\n", server[id].name, RFIFOL(fd,2)); + server[id].users = RFIFOL(fd,2); + if(anti_freeze_enable) + server_freezeflag[id] = 5; // Char anti-freeze system. Counter. 5 ok, 4...0 freezed + RFIFOSKIP(fd,6); + break; + + // we receive a e-mail creation of an account with a default e-mail (no answer) + case 0x2715: { + int acc; + char email[40]; + if (RFIFOREST(fd) < 46) + return 0; + acc = RFIFOL(fd,2); // speed up + memcpy(email, RFIFOP(fd,6), 40); + email[39] = '\0'; + remove_control_chars(email); + //printf("parse_fromchar: an e-mail creation of an account with a default e-mail: server '%s', account: %d, e-mail: '%s'.\n", server[id].name, acc, RFIFOP(fd,6)); + if (e_mail_check(email) == 0) + login_log("Char-server '%s': Attempt to create an e-mail on an account with a default e-mail REFUSED - e-mail is invalid (account: %d, ip: %s)" RETCODE, + server[id].name, acc, ip); + else { + for(i = 0; i < auth_num; i++) { + if (auth_dat[i].account_id == acc && (strcmp(auth_dat[i].email, "a@a.com") == 0 || auth_dat[i].email[0] == '\0')) { + memcpy(auth_dat[i].email, email, 40); + login_log("Char-server '%s': Create an e-mail on an account with a default e-mail (account: %d, new e-mail: %s, ip: %s)." RETCODE, + server[id].name, acc, email, ip); + // Save + mmo_auth_sync(); + break; + } + } + if (i == auth_num) + login_log("Char-server '%s': Attempt to create an e-mail on an account with a default e-mail REFUSED - account doesn't exist or e-mail of account isn't default e-mail (account: %d, ip: %s)." RETCODE, + server[id].name, acc, ip); + } + RFIFOSKIP(fd,46); + break; + + // We receive an e-mail/limited time request, because a player comes back from a map-server to the char-server + } + case 0x2716: + if (RFIFOREST(fd) < 6) + return 0; + //printf("parse_fromchar: E-mail/limited time request from '%s' server (concerned account: %d)\n", server[id].name, RFIFOL(fd,2)); + for(i = 0; i < auth_num; i++) { + if (auth_dat[i].account_id == RFIFOL(fd,2)) { + login_log("Char-server '%s': e-mail of the account %d found (ip: %s)." RETCODE, + server[id].name, RFIFOL(fd,2), ip); + WFIFOW(fd,0) = 0x2717; + WFIFOL(fd,2) = RFIFOL(fd,2); + memcpy(WFIFOP(fd, 6), auth_dat[i].email, 40); + WFIFOL(fd,46) = (unsigned long)auth_dat[i].connect_until_time; + WFIFOSET(fd,50); + break; + } + } + if (i == auth_num) { + login_log("Char-server '%s': e-mail of the account %d NOT found (ip: %s)." RETCODE, + server[id].name, RFIFOL(fd,2), ip); + } + RFIFOSKIP(fd,6); + break; + + case 0x2720: // To become GM request + if (RFIFOREST(fd) < 4 || RFIFOREST(fd) < RFIFOW(fd,2)) + return 0; + { + int acc; + unsigned char buf[10]; + FILE *fp; + acc = RFIFOL(fd,4); + //printf("parse_fromchar: Request to become a GM acount from %d account.\n", acc); + WBUFW(buf,0) = 0x2721; + WBUFL(buf,2) = acc; + WBUFL(buf,6) = 0; + if (strcmp(RFIFOP(fd,8), gm_pass) == 0) { + // only non-GM can become GM + if (isGM(acc) == 0) { + // if we autorise creation + if (level_new_gm > 0) { + // if we can open the file to add the new GM + if ((fp = fopen(GM_account_filename, "a")) != NULL) { + char tmpstr[24]; + struct timeval tv; + gettimeofday(&tv, NULL); + strftime(tmpstr, 23, date_format, localtime(&(tv.tv_sec))); + fprintf(fp, RETCODE "// %s: @GM command on account %d" RETCODE "%d %d" RETCODE, tmpstr, acc, acc, level_new_gm); + fclose(fp); + WBUFL(buf,6) = level_new_gm; + read_gm_account(); + send_GM_accounts(); + printf("GM Change of the account %d: level 0 -> %d.\n", acc, level_new_gm); + login_log("Char-server '%s': GM Change of the account %d: level 0 -> %d (ip: %s)." RETCODE, + server[id].name, acc, level_new_gm, ip); + } else { + printf("Error of GM change (suggested account: %d, correct password, unable to add a GM account in GM accounts file)\n", acc); + login_log("Char-server '%s': Error of GM change (suggested account: %d, correct password, unable to add a GM account in GM accounts file, ip: %s)." RETCODE, + server[id].name, acc, ip); + } + } else { + printf("Error of GM change (suggested account: %d, correct password, but GM creation is disable (level_new_gm = 0))\n", acc); + login_log("Char-server '%s': Error of GM change (suggested account: %d, correct password, but GM creation is disable (level_new_gm = 0), ip: %s)." RETCODE, + server[id].name, acc, ip); + } + } else { + printf("Error of GM change (suggested account: %d (already GM), correct password).\n", acc); + login_log("Char-server '%s': Error of GM change (suggested account: %d (already GM), correct password, ip: %s)." RETCODE, + server[id].name, acc, ip); + } + } else { + printf("Error of GM change (suggested account: %d, invalid password).\n", acc); + login_log("Char-server '%s': Error of GM change (suggested account: %d, invalid password, ip: %s)." RETCODE, + server[id].name, acc, ip); + } + charif_sendallwos(-1, buf, 10); + } + RFIFOSKIP(fd, RFIFOW(fd,2)); + return 0; + + // Map server send information to change an email of an account via char-server + case 0x2722: // 0x2722 <account_id>.L <actual_e-mail>.40B <new_e-mail>.40B + if (RFIFOREST(fd) < 86) + return 0; + { + int acc; + char actual_email[40], new_email[40]; + acc = RFIFOL(fd,2); + memcpy(actual_email, RFIFOP(fd,6), 40); + actual_email[39] = '\0'; + remove_control_chars(actual_email); + memcpy(new_email, RFIFOP(fd,46), 40); + new_email[39] = '\0'; + remove_control_chars(new_email); + if (e_mail_check(actual_email) == 0) + login_log("Char-server '%s': Attempt to modify an e-mail on an account (@email GM command), but actual email is invalid (account: %d, ip: %s)" RETCODE, + server[id].name, acc, ip); + else if (e_mail_check(new_email) == 0) + login_log("Char-server '%s': Attempt to modify an e-mail on an account (@email GM command) with a invalid new e-mail (account: %d, ip: %s)" RETCODE, + server[id].name, acc, ip); + else if (strcmpi(new_email, "a@a.com") == 0) + login_log("Char-server '%s': Attempt to modify an e-mail on an account (@email GM command) with a default e-mail (account: %d, ip: %s)" RETCODE, + server[id].name, acc, ip); + else { + for(i = 0; i < auth_num; i++) { + if (auth_dat[i].account_id == acc) { + if (strcmpi(auth_dat[i].email, actual_email) == 0) { + memcpy(auth_dat[i].email, new_email, 40); + login_log("Char-server '%s': Modify an e-mail on an account (@email GM command) (account: %d (%s), new e-mail: %s, ip: %s)." RETCODE, + server[id].name, acc, auth_dat[i].userid, new_email, ip); + // Save + mmo_auth_sync(); + } else + login_log("Char-server '%s': Attempt to modify an e-mail on an account (@email GM command), but actual e-mail is incorrect (account: %d (%s), actual e-mail: %s, proposed e-mail: %s, ip: %s)." RETCODE, + server[id].name, acc, auth_dat[i].userid, auth_dat[i].email, actual_email, ip); + break; + } + } + if (i == auth_num) + login_log("Char-server '%s': Attempt to modify an e-mail on an account (@email GM command), but account doesn't exist (account: %d, ip: %s)." RETCODE, + server[id].name, acc, ip); + } + } + RFIFOSKIP(fd, 86); + break; + + // Receiving of map-server via char-server a status change resquest (by Yor) + case 0x2724: + if (RFIFOREST(fd) < 10) + return 0; + { + int acc, statut; + acc = RFIFOL(fd,2); + statut = RFIFOL(fd,6); + for(i = 0; i < auth_num; i++) { + if (auth_dat[i].account_id == acc) { + if (auth_dat[i].state != statut) { + login_log("Char-server '%s': Status change (account: %d, new status %d, ip: %s)." RETCODE, + server[id].name, acc, statut, ip); + if (statut != 0) { + unsigned char buf[16]; + WBUFW(buf,0) = 0x2731; + WBUFL(buf,2) = acc; + WBUFB(buf,6) = 0; // 0: change of statut, 1: ban + WBUFL(buf,7) = statut; // status or final date of a banishment + charif_sendallwos(-1, buf, 11); + for(j = 0; j < AUTH_FIFO_SIZE; j++) + if (auth_fifo[j].account_id == acc) + auth_fifo[j].login_id1++; // to avoid reconnection error when come back from map-server (char-server will ask again the authentification) + } + auth_dat[i].state = statut; + // Save + mmo_auth_sync(); + } else + login_log("Char-server '%s': Error of Status change - actual status is already the good status (account: %d, status %d, ip: %s)." RETCODE, + server[id].name, acc, statut, ip); + break; + } + } + if (i == auth_num) { + login_log("Char-server '%s': Error of Status change (account: %d not found, suggested status %d, ip: %s)." RETCODE, + server[id].name, acc, statut, ip); + } + RFIFOSKIP(fd,10); + } + return 0; + + case 0x2725: // Receiving of map-server via char-server a ban resquest (by Yor) + if (RFIFOREST(fd) < 18) + return 0; + { + int acc; + acc = RFIFOL(fd,2); + for(i = 0; i < auth_num; i++) { + if (auth_dat[i].account_id == acc) { + time_t timestamp; + struct tm *tmtime; + if (auth_dat[i].ban_until_time == 0 || auth_dat[i].ban_until_time < time(NULL)) + timestamp = time(NULL); + else + timestamp = auth_dat[i].ban_until_time; + tmtime = localtime(×tamp); + tmtime->tm_year = tmtime->tm_year + (short)RFIFOW(fd,6); + tmtime->tm_mon = tmtime->tm_mon + (short)RFIFOW(fd,8); + tmtime->tm_mday = tmtime->tm_mday + (short)RFIFOW(fd,10); + tmtime->tm_hour = tmtime->tm_hour + (short)RFIFOW(fd,12); + tmtime->tm_min = tmtime->tm_min + (short)RFIFOW(fd,14); + tmtime->tm_sec = tmtime->tm_sec + (short)RFIFOW(fd,16); + timestamp = mktime(tmtime); + if (timestamp != -1) { + if (timestamp <= time(NULL)) + timestamp = 0; + if (auth_dat[i].ban_until_time != timestamp) { + if (timestamp != 0) { + unsigned char buf[16]; + char tmpstr[2048]; + strftime(tmpstr, 24, date_format, localtime(×tamp)); + login_log("Char-server '%s': Ban request (account: %d, new final date of banishment: %d (%s), ip: %s)." RETCODE, + server[id].name, acc, timestamp, (timestamp == 0 ? "no banishment" : tmpstr), ip); + WBUFW(buf,0) = 0x2731; + WBUFL(buf,2) = auth_dat[i].account_id; + WBUFB(buf,6) = 1; // 0: change of statut, 1: ban + WBUFL(buf,7) = timestamp; // status or final date of a banishment + charif_sendallwos(-1, buf, 11); + for(j = 0; j < AUTH_FIFO_SIZE; j++) + if (auth_fifo[j].account_id == acc) + auth_fifo[j].login_id1++; // to avoid reconnection error when come back from map-server (char-server will ask again the authentification) + } else { + login_log("Char-server '%s': Error of ban request (account: %d, new date unbans the account, ip: %s)." RETCODE, + server[id].name, acc, ip); + } + auth_dat[i].ban_until_time = timestamp; + // Save + mmo_auth_sync(); + } else { + login_log("Char-server '%s': Error of ban request (account: %d, no change for ban date, ip: %s)." RETCODE, + server[id].name, acc, ip); + } + } else { + login_log("Char-server '%s': Error of ban request (account: %d, invalid date, ip: %s)." RETCODE, + server[id].name, acc, ip); + } + break; + } + } + if (i == auth_num) { + login_log("Char-server '%s': Error of ban request (account: %d not found, ip: %s)." RETCODE, + server[id].name, acc, ip); + } + RFIFOSKIP(fd,18); + } + return 0; + + case 0x2727: // Change of sex (sex is reversed) + if (RFIFOREST(fd) < 6) + return 0; + { + int acc, sex; + acc = RFIFOL(fd,2); + for(i = 0; i < auth_num; i++) { +// printf("%d,", auth_dat[i].account_id); + if (auth_dat[i].account_id == acc) { + if (auth_dat[i].sex == 2) + login_log("Char-server '%s': Error of sex change - Server account (suggested account: %d, actual sex %d (Server), ip: %s)." RETCODE, + server[id].name, acc, auth_dat[i].sex, ip); + else { + unsigned char buf[16]; + if (auth_dat[i].sex == 0) + sex = 1; + else + sex = 0; + login_log("Char-server '%s': Sex change (account: %d, new sex %c, ip: %s)." RETCODE, + server[id].name, acc, (sex == 2) ? 'S' : (sex ? 'M' : 'F'), ip); + for(j = 0; j < AUTH_FIFO_SIZE; j++) + if (auth_fifo[j].account_id == acc) + auth_fifo[j].login_id1++; // to avoid reconnection error when come back from map-server (char-server will ask again the authentification) + auth_dat[i].sex = sex; + WBUFW(buf,0) = 0x2723; + WBUFL(buf,2) = acc; + WBUFB(buf,6) = sex; + charif_sendallwos(-1, buf, 7); + // Save + mmo_auth_sync(); + } + break; + } + } + if (i == auth_num) { + login_log("Char-server '%s': Error of sex change (account: %d not found, sex would be reversed, ip: %s)." RETCODE, + server[id].name, acc, ip); + } + RFIFOSKIP(fd,6); + } + return 0; + + case 0x2728: // We receive account_reg2 from a char-server, and we send them to other char-servers. + if (RFIFOREST(fd) < 4 || RFIFOREST(fd) < RFIFOW(fd,2)) + return 0; + { + int acc, p; + acc = RFIFOL(fd,4); + for(i = 0; i < auth_num; i++) { + if (auth_dat[i].account_id == acc) { + unsigned char buf[RFIFOW(fd,2)+1]; + login_log("Char-server '%s': receiving (from the char-server) of account_reg2 (account: %d, ip: %s)." RETCODE, + server[id].name, acc, ip); + for(p = 8, j = 0; p < RFIFOW(fd,2) && j < ACCOUNT_REG2_NUM; p += 36, j++) { + memcpy(auth_dat[i].account_reg2[j].str, RFIFOP(fd,p), 32); + auth_dat[i].account_reg2[j].str[31] = '\0'; + remove_control_chars(auth_dat[i].account_reg2[j].str); + auth_dat[i].account_reg2[j].value = RFIFOL(fd,p+32); + } + auth_dat[i].account_reg2_num = j; + // Sending information towards the other char-servers. + memcpy(WBUFP(buf,0), RFIFOP(fd,0), RFIFOW(fd,2)); + WBUFW(buf,0) = 0x2729; + charif_sendallwos(fd, buf, WBUFW(buf,2)); + // Save + mmo_auth_sync(); +// printf("parse_fromchar: receiving (from the char-server) of account_reg2 (account id: %d).\n", acc); + break; + } + } + if (i == auth_num) { +// printf("parse_fromchar: receiving (from the char-server) of account_reg2 (unknwon account id: %d).\n", acc); + login_log("Char-server '%s': receiving (from the char-server) of account_reg2 (account: %d not found, ip: %s)." RETCODE, + server[id].name, acc, ip); + } + } + RFIFOSKIP(fd,RFIFOW(fd,2)); + break; + + case 0x272a: // Receiving of map-server via char-server a unban resquest (by Yor) + if (RFIFOREST(fd) < 6) + return 0; + { + int acc; + acc = RFIFOL(fd,2); + for(i = 0; i < auth_num; i++) { + if (auth_dat[i].account_id == acc) { + if (auth_dat[i].ban_until_time != 0) { + auth_dat[i].ban_until_time = 0; + login_log("Char-server '%s': UnBan request (account: %d, ip: %s)." RETCODE, + server[id].name, acc, ip); + } else { + login_log("Char-server '%s': Error of UnBan request (account: %d, no change for unban date, ip: %s)." RETCODE, + server[id].name, acc, ip); + } + break; + } + } + if (i == auth_num) { + login_log("Char-server '%s': Error of UnBan request (account: %d not found, ip: %s)." RETCODE, + server[id].name, acc, ip); + } + RFIFOSKIP(fd,6); + } + return 0; + + default: + { + FILE *logfp; + char tmpstr[24]; + struct timeval tv; + logfp = fopen(login_log_unknown_packets_filename, "a"); + if (logfp) { + gettimeofday(&tv, NULL); + strftime(tmpstr, 23, date_format, localtime(&(tv.tv_sec))); + fprintf(logfp, "%s.%03d: receiving of an unknown packet -> disconnection" RETCODE, tmpstr, (int)tv.tv_usec / 1000); + fprintf(logfp, "parse_fromchar: connection #%d (ip: %s), packet: 0x%x (with being read: %d)." RETCODE, fd, ip, RFIFOW(fd,0), RFIFOREST(fd)); + fprintf(logfp, "Detail (in hex):" RETCODE); + fprintf(logfp, "---- 00-01-02-03-04-05-06-07 08-09-0A-0B-0C-0D-0E-0F" RETCODE); + memset(tmpstr, '\0', sizeof(tmpstr)); + for(i = 0; i < RFIFOREST(fd); i++) { + if ((i & 15) == 0) + fprintf(logfp, "%04X ",i); + fprintf(logfp, "%02x ", RFIFOB(fd,i)); + if (RFIFOB(fd,i) > 0x1f) + tmpstr[i % 16] = RFIFOB(fd,i); + else + tmpstr[i % 16] = '.'; + if ((i - 7) % 16 == 0) // -8 + 1 + fprintf(logfp, " "); + else if ((i + 1) % 16 == 0) { + fprintf(logfp, " %s" RETCODE, tmpstr); + memset(tmpstr, '\0', sizeof(tmpstr)); + } + } + if (i % 16 != 0) { + for(j = i; j % 16 != 0; j++) { + fprintf(logfp, " "); + if ((j - 7) % 16 == 0) // -8 + 1 + fprintf(logfp, " "); + } + fprintf(logfp, " %s" RETCODE, tmpstr); + } + fprintf(logfp, RETCODE); + fclose(logfp); + } + } + printf("parse_fromchar: Unknown packet 0x%x (from a char-server)! -> disconnection.\n", RFIFOW(fd,0)); + session[fd]->eof = 1; + printf("Char-server has been disconnected (unknown packet).\n"); + return 0; + } + } + return 0; +} + +//--------------------------------------- +// Packet parsing for administation login +//--------------------------------------- +int parse_admin(int fd) { + int i, j; + unsigned char *p = (unsigned char *) &session[fd]->client_addr.sin_addr; + char* account_name; + char ip[16]; + + sprintf(ip, "%d.%d.%d.%d", p[0], p[1], p[2], p[3]); + + if (session[fd]->eof) { + close(fd); + delete_session(fd); + printf("Remote administration has disconnected (session #%d).\n", fd); + return 0; + } + + while(RFIFOREST(fd) >= 2) { + if (display_parse_admin == 1) + printf("parse_admin: connection #%d, packet: 0x%x (with being read: %d).\n", fd, RFIFOW(fd,0), RFIFOREST(fd)); + + switch(RFIFOW(fd,0)) { + case 0x7530: // Request of the server version + login_log("'ladmin': Sending of the server version (ip: %s)" RETCODE, ip); + WFIFOW(fd,0) = 0x7531; + WFIFOB(fd,2) = ATHENA_MAJOR_VERSION; + WFIFOB(fd,3) = ATHENA_MINOR_VERSION; + WFIFOB(fd,4) = ATHENA_REVISION; + WFIFOB(fd,5) = ATHENA_RELEASE_FLAG; + WFIFOB(fd,6) = ATHENA_OFFICIAL_FLAG; + WFIFOB(fd,7) = ATHENA_SERVER_LOGIN; + WFIFOW(fd,8) = ATHENA_MOD_VERSION; + WFIFOSET(fd,10); + RFIFOSKIP(fd,2); + break; + + case 0x7532: // Request of end of connection + login_log("'ladmin': End of connection (ip: %s)" RETCODE, ip); + RFIFOSKIP(fd,2); + session[fd]->eof = 1; + break; + + case 0x7920: // Request of an accounts list + if (RFIFOREST(fd) < 10) + return 0; + { + int st, ed, len; + int id[auth_num]; + st = RFIFOL(fd,2); + ed = RFIFOL(fd,6); + RFIFOSKIP(fd,10); + WFIFOW(fd,0) = 0x7921; + if (st < 0) + st = 0; + if (ed > END_ACCOUNT_NUM || ed < st || ed <= 0) + ed = END_ACCOUNT_NUM; + login_log("'ladmin': Sending an accounts list (ask: from %d to %d, ip: %s)" RETCODE, st, ed, ip); + // Sort before send + for(i = 0; i < auth_num; i++) { + int k; + id[i] = i; + for(j = 0; j < i; j++) { + if (auth_dat[id[i]].account_id < auth_dat[id[j]].account_id) { + for(k = i; k > j; k--) { + id[k] = id[k-1]; + } + id[j] = i; // id[i] + break; + } + } + } + // Sending accounts information + len = 4; + for(i = 0; i < auth_num && len < 30000; i++) { + int account_id = auth_dat[id[i]].account_id; // use sorted index + if (account_id >= st && account_id <= ed) { + j = id[i]; + WFIFOL(fd,len) = account_id; + WFIFOB(fd,len+4) = (unsigned char)isGM(account_id); + memcpy(WFIFOP(fd,len+5), auth_dat[j].userid, 24); + WFIFOB(fd,len+29) = auth_dat[j].sex; + WFIFOL(fd,len+30) = auth_dat[j].logincount; + if (auth_dat[j].state == 0 && auth_dat[j].ban_until_time != 0) // if no state and banished + WFIFOL(fd,len+34) = 7; // 6 = Your are Prohibited to log in until %s + else + WFIFOL(fd,len+34) = auth_dat[j].state; + len += 38; + } + } + WFIFOW(fd,2) = len; + WFIFOSET(fd,len); + } + break; + + case 0x7930: // Request for an account creation + if (RFIFOREST(fd) < 91) + return 0; + { + struct mmo_account ma; + ma.userid = RFIFOP(fd, 2); + ma.passwd = RFIFOP(fd, 26); + memcpy(ma.lastlogin, "-", 2); + ma.sex = RFIFOB(fd,50); + WFIFOW(fd,0) = 0x7931; + WFIFOL(fd,2) = -1; + memcpy(WFIFOP(fd,6), RFIFOP(fd,2), 24); + if (strlen(ma.userid) > 23 || strlen(ma.passwd) > 23) { + login_log("'ladmin': Attempt to create an invalid account (account or pass is too long, ip: %s)" RETCODE, + ip); + } else if (strlen(ma.userid) < 4 || strlen(ma.passwd) < 4) { + login_log("'ladmin': Attempt to create an invalid account (account or pass is too short, ip: %s)" RETCODE, + ip); + } else if (ma.sex != 'F' && ma.sex != 'M') { + login_log("'ladmin': Attempt to create an invalid account (account: %s, received pass: %s, invalid sex, ip: %s)" RETCODE, + ma.userid, ma.passwd, ip); + } else if (account_id_count > END_ACCOUNT_NUM) { + login_log("'ladmin': Attempt to create an account, but there is no more available id number (account: %s, pass: %s, sex: %c, ip: %s)" RETCODE, + ma.userid, ma.passwd, ma.sex, ip); + } else { + remove_control_chars(ma.userid); + remove_control_chars(ma.passwd); + for(i = 0; i < auth_num; i++) { + if (strncmp(auth_dat[i].userid, ma.userid, 24) == 0) { + login_log("'ladmin': Attempt to create an already existing account (account: %s, pass: %s, received pass: %s, ip: %s)" RETCODE, + auth_dat[i].userid, auth_dat[i].pass, ma.passwd, ip); + break; + } + } + if (i == auth_num) { + int new_id; + char email[40]; + memcpy(email, RFIFOP(fd,51), 40); + email[39] = '\0'; + remove_control_chars(email); + new_id = mmo_auth_new(&ma, ma.sex, email); + login_log("'ladmin': Account creation (account: %s (id: %d), pass: %s, sex: %c, email: %s, ip: %s)" RETCODE, + ma.userid, new_id, ma.passwd, ma.sex, auth_dat[i].email, ip); + WFIFOL(fd,2) = new_id; + mmo_auth_sync(); + } + } + WFIFOSET(fd,30); + RFIFOSKIP(fd,91); + } + break; + + case 0x7932: // Request for an account deletion + if (RFIFOREST(fd) < 26) + return 0; + WFIFOW(fd,0) = 0x7933; + WFIFOL(fd,2) = -1; + account_name = RFIFOP(fd,2); + account_name[23] = '\0'; + remove_control_chars(account_name); + i = search_account_index(account_name); + if (i != -1) { + // Char-server is notified of deletion (for characters deletion). + unsigned char buf[65535]; + WBUFW(buf,0) = 0x2730; + WBUFL(buf,2) = auth_dat[i].account_id; + charif_sendallwos(-1, buf, 6); + // send answer + memcpy(WFIFOP(fd,6), auth_dat[i].userid, 24); + WFIFOL(fd,2) = auth_dat[i].account_id; + // save deleted account in log file + login_log("'ladmin': Account deletion (account: %s, id: %d, ip: %s) - saved in next line:" RETCODE, + auth_dat[i].userid, auth_dat[i].account_id, ip); + mmo_auth_tostr(buf, &auth_dat[i]); + login_log("%s" RETCODE, buf); + // delete account + memset(auth_dat[i].userid, '\0', sizeof(auth_dat[i].userid)); + auth_dat[i].account_id = -1; + mmo_auth_sync(); + } else { + memcpy(WFIFOP(fd,6), account_name, 24); + login_log("'ladmin': Attempt to delete an unknown account (account: %s, ip: %s)" RETCODE, + account_name, ip); + } + WFIFOSET(fd,30); + RFIFOSKIP(fd,26); + break; + + case 0x7934: // Request to change a password + if (RFIFOREST(fd) < 50) + return 0; + WFIFOW(fd,0) = 0x7935; + WFIFOL(fd,2) = -1; + account_name = RFIFOP(fd,2); + account_name[23] = '\0'; + remove_control_chars(account_name); + i = search_account_index(account_name); + if (i != -1) { + memcpy(WFIFOP(fd,6), auth_dat[i].userid, 24); + memcpy(auth_dat[i].pass, RFIFOP(fd,26), 24); + auth_dat[i].pass[23] = '\0'; + remove_control_chars(auth_dat[i].pass); + WFIFOL(fd,2) = auth_dat[i].account_id; + login_log("'ladmin': Modification of a password (account: %s, new password: %s, ip: %s)" RETCODE, + auth_dat[i].userid, auth_dat[i].pass, ip); + mmo_auth_sync(); + } else { + memcpy(WFIFOP(fd,6), account_name, 24); + login_log("'ladmin': Attempt to modify the password of an unknown account (account: %s, ip: %s)" RETCODE, + account_name, ip); + } + WFIFOSET(fd,30); + RFIFOSKIP(fd,50); + break; + + case 0x7936: // Request to modify a state + if (RFIFOREST(fd) < 50) + return 0; + { + char error_message[20]; + int statut; + WFIFOW(fd,0) = 0x7937; + WFIFOL(fd,2) = -1; + account_name = RFIFOP(fd,2); + account_name[23] = '\0'; + remove_control_chars(account_name); + statut = RFIFOL(fd,26); + memcpy(error_message, RFIFOP(fd,30), 20); + error_message[19] = '\0'; + remove_control_chars(error_message); + if (statut != 7 || error_message[0] == '\0') { // 7: // 6 = Your are Prohibited to log in until %s + strcpy(error_message, "-"); + } + i = search_account_index(account_name); + if (i != -1) { + memcpy(WFIFOP(fd,6), auth_dat[i].userid, 24); + WFIFOL(fd,2) = auth_dat[i].account_id; + if (auth_dat[i].state == statut && strcmp(auth_dat[i].error_message, error_message) == 0) + login_log("'ladmin': Modification of a state, but the state of the account is already the good state (account: %s, received state: %d, ip: %s)" RETCODE, + account_name, statut, ip); + else { + if (statut == 7) + login_log("'ladmin': Modification of a state (account: %s, new state: %d - prohibited to login until '%s', ip: %s)" RETCODE, + auth_dat[i].userid, statut, error_message, ip); + else + login_log("'ladmin': Modification of a state (account: %s, new state: %d, ip: %s)" RETCODE, + auth_dat[i].userid, statut, ip); + if (auth_dat[i].state == 0) { + unsigned char buf[16]; + WBUFW(buf,0) = 0x2731; + WBUFL(buf,2) = auth_dat[i].account_id; + WBUFB(buf,6) = 0; // 0: change of statut, 1: ban + WBUFL(buf,7) = statut; // status or final date of a banishment + charif_sendallwos(-1, buf, 11); + for(j = 0; j < AUTH_FIFO_SIZE; j++) + if (auth_fifo[j].account_id == auth_dat[i].account_id) + auth_fifo[j].login_id1++; // to avoid reconnection error when come back from map-server (char-server will ask again the authentification) + } + auth_dat[i].state = statut; + memcpy(auth_dat[i].error_message, error_message, 20); + mmo_auth_sync(); + } + } else { + memcpy(WFIFOP(fd,6), account_name, 24); + login_log("'ladmin': Attempt to modify the state of an unknown account (account: %s, received state: %d, ip: %s)" RETCODE, + account_name, statut, ip); + } + WFIFOL(fd,30) = statut; + } + WFIFOSET(fd,34); + RFIFOSKIP(fd,50); + break; + + case 0x7938: // Request for servers list and # of online players + login_log("'ladmin': Sending of servers list (ip: %s)" RETCODE, ip); + server_num = 0; + for(i = 0; i < MAX_SERVERS; i++) { + if (server_fd[i] >= 0) { + WFIFOL(fd,4+server_num*32) = server[i].ip; + WFIFOW(fd,4+server_num*32+4) = server[i].port; + memcpy(WFIFOP(fd,4+server_num*32+6), server[i].name, 20); + WFIFOW(fd,4+server_num*32+26) = server[i].users; + WFIFOW(fd,4+server_num*32+28) = server[i].maintenance; + WFIFOW(fd,4+server_num*32+30) = server[i].new; + server_num++; + } + } + WFIFOW(fd,0) = 0x7939; + WFIFOW(fd,2) = 4 + 32 * server_num; + WFIFOSET(fd,4+32*server_num); + RFIFOSKIP(fd,2); + break; + + case 0x793a: // Request to password check + if (RFIFOREST(fd) < 50) + return 0; + WFIFOW(fd,0) = 0x793b; + WFIFOL(fd,2) = -1; + account_name = RFIFOP(fd,2); + account_name[23] = '\0'; + remove_control_chars(account_name); + i = search_account_index(account_name); + if (i != -1) { + memcpy(WFIFOP(fd,6), auth_dat[i].userid, 24); + if (strcmp(auth_dat[i].pass, RFIFOP(fd,26)) == 0) { + WFIFOL(fd,2) = auth_dat[i].account_id; + login_log("'ladmin': Check of password OK (account: %s, password: %s, ip: %s)" RETCODE, + auth_dat[i].userid, auth_dat[i].pass, ip); + } else { + char pass[24]; + memcpy(pass, RFIFOP(fd,26), 24); + pass[23] = '\0'; + remove_control_chars(pass); + login_log("'ladmin': Failure of password check (account: %s, proposed pass: %s, ip: %s)" RETCODE, + auth_dat[i].userid, pass, ip); + } + } else { + memcpy(WFIFOP(fd,6), account_name, 24); + login_log("'ladmin': Attempt to check the password of an unknown account (account: %s, ip: %s)" RETCODE, + account_name, ip); + } + WFIFOSET(fd,30); + RFIFOSKIP(fd,50); + break; + + case 0x793c: // Request to modify sex + if (RFIFOREST(fd) < 27) + return 0; + WFIFOW(fd,0) = 0x793d; + WFIFOL(fd,2) = -1; + account_name = RFIFOP(fd,2); + account_name[23] = '\0'; + remove_control_chars(account_name); + memcpy(WFIFOP(fd,6), account_name, 24); + { + char sex; + sex = RFIFOB(fd,26); + if (sex != 'F' && sex != 'M') { + if (sex > 31) + login_log("'ladmin': Attempt to give an invalid sex (account: %s, received sex: %c, ip: %s)" RETCODE, + account_name, sex, ip); + else + login_log("'ladmin': Attempt to give an invalid sex (account: %s, received sex: 'control char', ip: %s)" RETCODE, + account_name, ip); + } else { + i = search_account_index(account_name); + if (i != -1) { + memcpy(WFIFOP(fd,6), auth_dat[i].userid, 24); + if (auth_dat[i].sex != ((sex == 'S' || sex == 's') ? 2 : (sex == 'M' || sex == 'm'))) { + unsigned char buf[16]; + WFIFOL(fd,2) = auth_dat[i].account_id; + for(j = 0; j < AUTH_FIFO_SIZE; j++) + if (auth_fifo[j].account_id == auth_dat[i].account_id) + auth_fifo[j].login_id1++; // to avoid reconnection error when come back from map-server (char-server will ask again the authentification) + auth_dat[i].sex = (sex == 'S' || sex == 's') ? 2 : (sex == 'M' || sex == 'm'); + login_log("'ladmin': Modification of a sex (account: %s, new sex: %c, ip: %s)" RETCODE, + auth_dat[i].userid, sex, ip); + mmo_auth_sync(); + // send to all char-server the change + WBUFW(buf,0) = 0x2723; + WBUFL(buf,2) = auth_dat[i].account_id; + WBUFB(buf,6) = auth_dat[i].sex; + charif_sendallwos(-1, buf, 7); + } else { + login_log("'ladmin': Modification of a sex, but the sex is already the good sex (account: %s, sex: %c, ip: %s)" RETCODE, + auth_dat[i].userid, sex, ip); + } + } else { + login_log("'ladmin': Attempt to modify the sex of an unknown account (account: %s, received sex: %c, ip: %s)" RETCODE, + account_name, sex, ip); + } + } + } + WFIFOSET(fd,30); + RFIFOSKIP(fd,27); + break; + + case 0x793e: // Request to modify GM level + if (RFIFOREST(fd) < 27) + return 0; + WFIFOW(fd,0) = 0x793f; + WFIFOL(fd,2) = -1; + account_name = RFIFOP(fd,2); + account_name[23] = '\0'; + remove_control_chars(account_name); + memcpy(WFIFOP(fd,6), account_name, 24); + { + char new_gm_level; + new_gm_level = RFIFOB(fd,26); + if (new_gm_level < 0 || new_gm_level > 99) { + login_log("'ladmin': Attempt to give an invalid GM level (account: %s, received GM level: %d, ip: %s)" RETCODE, + account_name, (int)new_gm_level, ip); + } else { + i = search_account_index(account_name); + if (i != -1) { + int acc = auth_dat[i].account_id; + memcpy(WFIFOP(fd,6), auth_dat[i].userid, 24); + if (isGM(acc) != new_gm_level) { + // modification of the file + FILE *fp, *fp2; + int lock; + char line[512]; + int GM_account, GM_level; + int modify_flag; + char tmpstr[24]; + struct timeval tv; + if ((fp2 = lock_fopen(GM_account_filename, &lock)) != NULL) { + if ((fp = fopen(GM_account_filename, "r")) != NULL) { + gettimeofday(&tv, NULL); + strftime(tmpstr, 23, date_format, localtime(&(tv.tv_sec))); + modify_flag = 0; + // read/write GM file + while(fgets(line, sizeof(line)-1, fp)) { + while(line[0] != '\0' && (line[strlen(line)-1] == '\n' || line[strlen(line)-1] == '\r')) + line[strlen(line)-1] = '\0'; + if ((line[0] == '/' && line[1] == '/') || line[0] == '\0') + fprintf(fp2, "%s" RETCODE, line); + else { + if (sscanf(line, "%d %d", &GM_account, &GM_level) != 2 && sscanf(line, "%d: %d", &GM_account, &GM_level) != 2) + fprintf(fp2, "%s" RETCODE, line); + else if (GM_account != acc) + fprintf(fp2, "%s" RETCODE, line); + else if (new_gm_level < 1) { + fprintf(fp2, "// %s: 'ladmin' GM level removed on account %d '%s' (previous level: %d)" RETCODE "//%d %d" RETCODE, tmpstr, acc, auth_dat[i].userid, GM_level, acc, new_gm_level); + modify_flag = 1; + } else { + fprintf(fp2, "// %s: 'ladmin' GM level on account %d '%s' (previous level: %d)" RETCODE "%d %d" RETCODE, tmpstr, acc, auth_dat[i].userid, GM_level, acc, new_gm_level); + modify_flag = 1; + } + } + } + if (modify_flag == 0) + fprintf(fp2, "// %s: 'ladmin' GM level on account %d '%s' (previous level: 0)" RETCODE "%d %d" RETCODE, tmpstr, acc, auth_dat[i].userid, acc, new_gm_level); + fclose(fp); + } else { + login_log("'ladmin': Attempt to modify of a GM level - impossible to read GM accounts file (account: %s (%d), received GM level: %d, ip: %s)" RETCODE, + auth_dat[i].userid, acc, (int)new_gm_level, ip); + } + if (lock_fclose(fp2, GM_account_filename, &lock) == 0) { + WFIFOL(fd,2) = acc; + login_log("'ladmin': Modification of a GM level (account: %s (%d), new GM level: %d, ip: %s)" RETCODE, + auth_dat[i].userid, acc, (int)new_gm_level, ip); + // read and send new GM informations + read_gm_account(); + send_GM_accounts(); + } else { + login_log("'ladmin': Attempt to modify of a GM level - impossible to write GM accounts file (account: %s (%d), received GM level: %d, ip: %s)" RETCODE, + auth_dat[i].userid, acc, (int)new_gm_level, ip); + } + } else { + login_log("'ladmin': Attempt to modify of a GM level - impossible to write GM accounts file (account: %s (%d), received GM level: %d, ip: %s)" RETCODE, + auth_dat[i].userid, acc, (int)new_gm_level, ip); + } + } else { + login_log("'ladmin': Attempt to modify of a GM level, but the GM level is already the good GM level (account: %s (%d), GM level: %d, ip: %s)" RETCODE, + auth_dat[i].userid, acc, (int)new_gm_level, ip); + } + } else { + login_log("'ladmin': Attempt to modify the GM level of an unknown account (account: %s, received GM level: %d, ip: %s)" RETCODE, + account_name, (int)new_gm_level, ip); + } + } + } + WFIFOSET(fd,30); + RFIFOSKIP(fd,27); + break; + + case 0x7940: // Request to modify e-mail + if (RFIFOREST(fd) < 66) + return 0; + WFIFOW(fd,0) = 0x7941; + WFIFOL(fd,2) = -1; + account_name = RFIFOP(fd,2); + account_name[23] = '\0'; + remove_control_chars(account_name); + memcpy(WFIFOP(fd,6), account_name, 24); + { + char email[40]; + memcpy(email, RFIFOP(fd,26), 40); + if (e_mail_check(email) == 0) { + login_log("'ladmin': Attempt to give an invalid e-mail (account: %s, ip: %s)" RETCODE, + account_name, ip); + } else { + remove_control_chars(email); + i = search_account_index(account_name); + if (i != -1) { + memcpy(WFIFOP(fd,6), auth_dat[i].userid, 24); + memcpy(auth_dat[i].email, email, 40); + WFIFOL(fd,2) = auth_dat[i].account_id; + login_log("'ladmin': Modification of an email (account: %s, new e-mail: %s, ip: %s)" RETCODE, + auth_dat[i].userid, email, ip); + mmo_auth_sync(); + } else { + login_log("'ladmin': Attempt to modify the e-mail of an unknown account (account: %s, received e-mail: %s, ip: %s)" RETCODE, + account_name, email, ip); + } + } + } + WFIFOSET(fd,30); + RFIFOSKIP(fd,66); + break; + + case 0x7942: // Request to modify memo field + if (RFIFOREST(fd) < 28 || RFIFOREST(fd) < (28 + RFIFOW(fd,26))) + return 0; + WFIFOW(fd,0) = 0x7943; + WFIFOL(fd,2) = -1; + account_name = RFIFOP(fd,2); + account_name[23] = '\0'; + remove_control_chars(account_name); + i = search_account_index(account_name); + if (i != -1) { + int size_of_memo = sizeof(auth_dat[i].memo); + memcpy(WFIFOP(fd,6), auth_dat[i].userid, 24); + memset(auth_dat[i].memo, '\0', size_of_memo); + if (RFIFOW(fd,26) == 0) { + strncpy(auth_dat[i].memo, "-", size_of_memo); + } else if (RFIFOW(fd,26) > size_of_memo - 1) { + memcpy(auth_dat[i].memo, RFIFOP(fd,28), size_of_memo - 1); + } else { + memcpy(auth_dat[i].memo, RFIFOP(fd,28), RFIFOW(fd,26)); + } + auth_dat[i].memo[size_of_memo - 1] = '\0'; + remove_control_chars(auth_dat[i].memo); + WFIFOL(fd,2) = auth_dat[i].account_id; + login_log("'ladmin': Modification of a memo field (account: %s, new memo: %s, ip: %s)" RETCODE, + auth_dat[i].userid, auth_dat[i].memo, ip); + mmo_auth_sync(); + } else { + memcpy(WFIFOP(fd,6), account_name, 24); + login_log("'ladmin': Attempt to modify the memo field of an unknown account (account: %s, ip: %s)" RETCODE, + account_name, ip); + } + WFIFOSET(fd,30); + RFIFOSKIP(fd,28 + RFIFOW(fd,26)); + break; + + case 0x7944: // Request to found an account id + if (RFIFOREST(fd) < 26) + return 0; + WFIFOW(fd,0) = 0x7945; + WFIFOL(fd,2) = -1; + account_name = RFIFOP(fd,2); + account_name[23] = '\0'; + remove_control_chars(account_name); + i = search_account_index(account_name); + if (i != -1) { + memcpy(WFIFOP(fd,6), auth_dat[i].userid, 24); + WFIFOL(fd,2) = auth_dat[i].account_id; + login_log("'ladmin': Request (by the name) of an account id (account: %s, id: %d, ip: %s)" RETCODE, + auth_dat[i].userid, auth_dat[i].account_id, ip); + } else { + memcpy(WFIFOP(fd,6), account_name, 24); + login_log("'ladmin': ID request (by the name) of an unknown account (account: %s, ip: %s)" RETCODE, + account_name, ip); + } + WFIFOSET(fd,30); + RFIFOSKIP(fd,26); + break; + + case 0x7946: // Request to found an account name + if (RFIFOREST(fd) < 6) + return 0; + WFIFOW(fd,0) = 0x7947; + WFIFOL(fd,2) = RFIFOL(fd,2); + memset(WFIFOP(fd,6), '\0', 24); + for(i = 0; i < auth_num; i++) { + if (auth_dat[i].account_id == RFIFOL(fd,2)) { + strncpy(WFIFOP(fd,6), auth_dat[i].userid, 24); + login_log("'ladmin': Request (by id) of an account name (account: %s, id: %d, ip: %s)" RETCODE, + auth_dat[i].userid, RFIFOL(fd,2), ip); + break; + } + } + if (i == auth_num) { + login_log("'ladmin': Name request (by id) of an unknown account (id: %d, ip: %s)" RETCODE, + RFIFOL(fd,2), ip); + strncpy(WFIFOP(fd,6), "", 24); + } + WFIFOSET(fd,30); + RFIFOSKIP(fd,6); + break; + + case 0x7948: // Request to change the validity limit (timestamp) (absolute value) + if (RFIFOREST(fd) < 30) + return 0; + { + time_t timestamp; + char tmpstr[2048]; + WFIFOW(fd,0) = 0x7949; + WFIFOL(fd,2) = -1; + account_name = RFIFOP(fd,2); + account_name[23] = '\0'; + remove_control_chars(account_name); + timestamp = (time_t)RFIFOL(fd,26); + strftime(tmpstr, 24, date_format, localtime(×tamp)); + i = search_account_index(account_name); + if (i != -1) { + memcpy(WFIFOP(fd,6), auth_dat[i].userid, 24); + login_log("'ladmin': Change of a validity limit (account: %s, new validity: %d (%s), ip: %s)" RETCODE, + auth_dat[i].userid, timestamp, (timestamp == 0 ? "unlimited" : tmpstr), ip); + auth_dat[i].connect_until_time = timestamp; + WFIFOL(fd,2) = auth_dat[i].account_id; + mmo_auth_sync(); + } else { + memcpy(WFIFOP(fd,6), account_name, 24); + login_log("'ladmin': Attempt to change the validity limit of an unknown account (account: %s, received validity: %d (%s), ip: %s)" RETCODE, + account_name, timestamp, (timestamp == 0 ? "unlimited" : tmpstr), ip); + } + WFIFOL(fd,30) = timestamp; + } + WFIFOSET(fd,34); + RFIFOSKIP(fd,30); + break; + + case 0x794a: // Request to change the final date of a banishment (timestamp) (absolute value) + if (RFIFOREST(fd) < 30) + return 0; + { + time_t timestamp; + char tmpstr[2048]; + WFIFOW(fd,0) = 0x794b; + WFIFOL(fd,2) = -1; + account_name = RFIFOP(fd,2); + account_name[23] = '\0'; + remove_control_chars(account_name); + timestamp = (time_t)RFIFOL(fd,26); + if (timestamp <= time(NULL)) + timestamp = 0; + strftime(tmpstr, 24, date_format, localtime(×tamp)); + i = search_account_index(account_name); + if (i != -1) { + memcpy(WFIFOP(fd,6), auth_dat[i].userid, 24); + WFIFOL(fd,2) = auth_dat[i].account_id; + login_log("'ladmin': Change of the final date of a banishment (account: %s, new final date of banishment: %d (%s), ip: %s)" RETCODE, + auth_dat[i].userid, timestamp, (timestamp == 0 ? "no banishment" : tmpstr), ip); + if (auth_dat[i].ban_until_time != timestamp) { + if (timestamp != 0) { + unsigned char buf[16]; + WBUFW(buf,0) = 0x2731; + WBUFL(buf,2) = auth_dat[i].account_id; + WBUFB(buf,6) = 1; // 0: change of statut, 1: ban + WBUFL(buf,7) = timestamp; // status or final date of a banishment + charif_sendallwos(-1, buf, 11); + for(j = 0; j < AUTH_FIFO_SIZE; j++) + if (auth_fifo[j].account_id == auth_dat[i].account_id) + auth_fifo[j].login_id1++; // to avoid reconnection error when come back from map-server (char-server will ask again the authentification) + } + auth_dat[i].ban_until_time = timestamp; + mmo_auth_sync(); + } + } else { + memcpy(WFIFOP(fd,6), account_name, 24); + login_log("'ladmin': Attempt to change the final date of a banishment of an unknown account (account: %s, received final date of banishment: %d (%s), ip: %s)" RETCODE, + account_name, timestamp, (timestamp == 0 ? "no banishment" : tmpstr), ip); + } + WFIFOL(fd,30) = timestamp; + } + WFIFOSET(fd,34); + RFIFOSKIP(fd,30); + break; + + case 0x794c: // Request to change the final date of a banishment (timestamp) (relative change) + if (RFIFOREST(fd) < 38) + return 0; + { + time_t timestamp; + struct tm *tmtime; + char tmpstr[2048]; + WFIFOW(fd,0) = 0x794d; + WFIFOL(fd,2) = -1; + account_name = RFIFOP(fd,2); + account_name[23] = '\0'; + remove_control_chars(account_name); + i = search_account_index(account_name); + if (i != -1) { + WFIFOL(fd,2) = auth_dat[i].account_id; + memcpy(WFIFOP(fd,6), auth_dat[i].userid, 24); + if (auth_dat[i].ban_until_time == 0 || auth_dat[i].ban_until_time < time(NULL)) + timestamp = time(NULL); + else + timestamp = auth_dat[i].ban_until_time; + tmtime = localtime(×tamp); + tmtime->tm_year = tmtime->tm_year + (short)RFIFOW(fd,26); + tmtime->tm_mon = tmtime->tm_mon + (short)RFIFOW(fd,28); + tmtime->tm_mday = tmtime->tm_mday + (short)RFIFOW(fd,30); + tmtime->tm_hour = tmtime->tm_hour + (short)RFIFOW(fd,32); + tmtime->tm_min = tmtime->tm_min + (short)RFIFOW(fd,34); + tmtime->tm_sec = tmtime->tm_sec + (short)RFIFOW(fd,36); + timestamp = mktime(tmtime); + if (timestamp != -1) { + if (timestamp <= time(NULL)) + timestamp = 0; + strftime(tmpstr, 24, date_format, localtime(×tamp)); + login_log("'ladmin': Adjustment of a final date of a banishment (account: %s, (%+d y %+d m %+d d %+d h %+d mn %+d s) -> new validity: %d (%s), ip: %s)" RETCODE, + auth_dat[i].userid, (short)RFIFOW(fd,26), (short)RFIFOW(fd,28), (short)RFIFOW(fd,30), (short)RFIFOW(fd,32), (short)RFIFOW(fd,34), (short)RFIFOW(fd,36), timestamp, (timestamp == 0 ? "no banishment" : tmpstr), ip); + if (auth_dat[i].ban_until_time != timestamp) { + if (timestamp != 0) { + unsigned char buf[16]; + WBUFW(buf,0) = 0x2731; + WBUFL(buf,2) = auth_dat[i].account_id; + WBUFB(buf,6) = 1; // 0: change of statut, 1: ban + WBUFL(buf,7) = timestamp; // status or final date of a banishment + charif_sendallwos(-1, buf, 11); + for(j = 0; j < AUTH_FIFO_SIZE; j++) + if (auth_fifo[j].account_id == auth_dat[i].account_id) + auth_fifo[j].login_id1++; // to avoid reconnection error when come back from map-server (char-server will ask again the authentification) + } + auth_dat[i].ban_until_time = timestamp; + mmo_auth_sync(); + } + } else { + strftime(tmpstr, 24, date_format, localtime(&auth_dat[i].ban_until_time)); + login_log("'ladmin': Impossible to adjust the final date of a banishment (account: %s, %d (%s) + (%+d y %+d m %+d d %+d h %+d mn %+d s) -> ???, ip: %s)" RETCODE, + auth_dat[i].userid, auth_dat[i].ban_until_time, (auth_dat[i].ban_until_time == 0 ? "no banishment" : tmpstr), (short)RFIFOW(fd,26), (short)RFIFOW(fd,28), (short)RFIFOW(fd,30), (short)RFIFOW(fd,32), (short)RFIFOW(fd,34), (short)RFIFOW(fd,36), ip); + } + WFIFOL(fd,30) = (unsigned long)auth_dat[i].ban_until_time; + } else { + memcpy(WFIFOP(fd,6), account_name, 24); + login_log("'ladmin': Attempt to adjust the final date of a banishment of an unknown account (account: %s, ip: %s)" RETCODE, + account_name, ip); + WFIFOL(fd,30) = 0; + } + } + WFIFOSET(fd,34); + RFIFOSKIP(fd,38); + break; + + case 0x794e: // Request to send a broadcast message + if (RFIFOREST(fd) < 8 || RFIFOREST(fd) < (8 + RFIFOL(fd,4))) + return 0; + WFIFOW(fd,0) = 0x794f; + WFIFOW(fd,2) = -1; + if (RFIFOL(fd,4) < 1) { + login_log("'ladmin': Receiving a message for broadcast, but message is void (ip: %s)" RETCODE, + ip); + } else { + // at least 1 char-server + for(i = 0; i < MAX_SERVERS; i++) + if (server_fd[i] >= 0) + break; + if (i == MAX_SERVERS) { + login_log("'ladmin': Receiving a message for broadcast, but no char-server is online (ip: %s)" RETCODE, + ip); + } else { + char buf[32000]; + char message[32000]; + WFIFOW(fd,2) = 0; + memset(message, '\0', sizeof(message)); + memcpy(message, RFIFOP(fd,8), RFIFOL(fd,4)); + message[sizeof(message)-1] = '\0'; + remove_control_chars(message); + if (RFIFOW(fd,2) == 0) + login_log("'ladmin': Receiving a message for broadcast (message (in yellow): %s, ip: %s)" RETCODE, + message, ip); + else + login_log("'ladmin': Receiving a message for broadcast (message (in blue): %s, ip: %s)" RETCODE, + message, ip); + // send same message to all char-servers (no answer) + memcpy(WBUFP(buf,0), RFIFOP(fd,0), 8 + RFIFOL(fd,4)); + WBUFW(buf,0) = 0x2726; + charif_sendallwos(-1, buf, 8 + RFIFOL(fd,4)); + } + } + WFIFOSET(fd,4); + RFIFOSKIP(fd,8 + RFIFOL(fd,4)); + break; + + case 0x7950: // Request to change the validity limite (timestamp) (relative change) + if (RFIFOREST(fd) < 38) + return 0; + { + time_t timestamp; + struct tm *tmtime; + char tmpstr[2048]; + char tmpstr2[2048]; + WFIFOW(fd,0) = 0x7951; + WFIFOL(fd,2) = -1; + account_name = RFIFOP(fd,2); + account_name[23] = '\0'; + remove_control_chars(account_name); + i = search_account_index(account_name); + if (i != -1) { + WFIFOL(fd,2) = auth_dat[i].account_id; + memcpy(WFIFOP(fd,6), auth_dat[i].userid, 24); + timestamp = auth_dat[i].connect_until_time; + if (add_to_unlimited_account == 0 && timestamp == 0) { + login_log("'ladmin': Attempt to adjust the validity limit of an unlimited account (account: %s, ip: %s)" RETCODE, + auth_dat[i].userid, ip); + WFIFOL(fd,30) = 0; + } else { + if (timestamp == 0 || timestamp < time(NULL)) + timestamp = time(NULL); + tmtime = localtime(×tamp); + tmtime->tm_year = tmtime->tm_year + (short)RFIFOW(fd,26); + tmtime->tm_mon = tmtime->tm_mon + (short)RFIFOW(fd,28); + tmtime->tm_mday = tmtime->tm_mday + (short)RFIFOW(fd,30); + tmtime->tm_hour = tmtime->tm_hour + (short)RFIFOW(fd,32); + tmtime->tm_min = tmtime->tm_min + (short)RFIFOW(fd,34); + tmtime->tm_sec = tmtime->tm_sec + (short)RFIFOW(fd,36); + timestamp = mktime(tmtime); + if (timestamp != -1) { + strftime(tmpstr, 24, date_format, localtime(&auth_dat[i].connect_until_time)); + strftime(tmpstr2, 24, date_format, localtime(×tamp)); + login_log("'ladmin': Adjustment of a validity limit (account: %s, %d (%s) + (%+d y %+d m %+d d %+d h %+d mn %+d s) -> new validity: %d (%s), ip: %s)" RETCODE, + auth_dat[i].userid, auth_dat[i].connect_until_time, (auth_dat[i].connect_until_time == 0 ? "unlimited" : tmpstr), (short)RFIFOW(fd,26), (short)RFIFOW(fd,28), (short)RFIFOW(fd,30), (short)RFIFOW(fd,32), (short)RFIFOW(fd,34), (short)RFIFOW(fd,36), timestamp, (timestamp == 0 ? "unlimited" : tmpstr2), ip); + auth_dat[i].connect_until_time = timestamp; + mmo_auth_sync(); + WFIFOL(fd,30) = (unsigned long)auth_dat[i].connect_until_time; + } else { + strftime(tmpstr, 24, date_format, localtime(&auth_dat[i].connect_until_time)); + login_log("'ladmin': Impossible to adjust a validity limit (account: %s, %d (%s) + (%+d y %+d m %+d d %+d h %+d mn %+d s) -> ???, ip: %s)" RETCODE, + auth_dat[i].userid, auth_dat[i].connect_until_time, (auth_dat[i].connect_until_time == 0 ? "unlimited" : tmpstr), (short)RFIFOW(fd,26), (short)RFIFOW(fd,28), (short)RFIFOW(fd,30), (short)RFIFOW(fd,32), (short)RFIFOW(fd,34), (short)RFIFOW(fd,36), ip); + WFIFOL(fd,30) = 0; + } + } + } else { + memcpy(WFIFOP(fd,6), account_name, 24); + login_log("'ladmin': Attempt to adjust the validity limit of an unknown account (account: %s, ip: %s)" RETCODE, + account_name, ip); + WFIFOL(fd,30) = 0; + } + } + WFIFOSET(fd,34); + RFIFOSKIP(fd,38); + break; + + case 0x7952: // Request about informations of an account (by account name) + if (RFIFOREST(fd) < 26) + return 0; + WFIFOW(fd,0) = 0x7953; + WFIFOL(fd,2) = -1; + account_name = RFIFOP(fd,2); + account_name[23] = '\0'; + remove_control_chars(account_name); + i = search_account_index(account_name); + if (i != -1) { + WFIFOL(fd,2) = auth_dat[i].account_id; + WFIFOB(fd,6) = (unsigned char)isGM(auth_dat[i].account_id); + memcpy(WFIFOP(fd,7), auth_dat[i].userid, 24); + WFIFOB(fd,31) = auth_dat[i].sex; + WFIFOL(fd,32) = auth_dat[i].logincount; + WFIFOL(fd,36) = auth_dat[i].state; + memcpy(WFIFOP(fd,40), auth_dat[i].error_message, 20); + memcpy(WFIFOP(fd,60), auth_dat[i].lastlogin, 24); + memcpy(WFIFOP(fd,84), auth_dat[i].last_ip, 16); + memcpy(WFIFOP(fd,100), auth_dat[i].email, 40); + WFIFOL(fd,140) = (unsigned long)auth_dat[i].connect_until_time; + WFIFOL(fd,144) = (unsigned long)auth_dat[i].ban_until_time; + WFIFOW(fd,148) = strlen(auth_dat[i].memo); + if (auth_dat[i].memo[0]) { + memcpy(WFIFOP(fd,150), auth_dat[i].memo, strlen(auth_dat[i].memo)); + } + login_log("'ladmin': Sending information of an account (request by the name; account: %s, id: %d, ip: %s)" RETCODE, + auth_dat[i].userid, auth_dat[i].account_id, ip); + WFIFOSET(fd,150+strlen(auth_dat[i].memo)); + } else { + memcpy(WFIFOP(fd,7), account_name, 24); + WFIFOW(fd,148) = 0; + login_log("'ladmin': Attempt to obtain information (by the name) of an unknown account (account: %s, ip: %s)" RETCODE, + account_name, ip); + WFIFOSET(fd,150); + } + RFIFOSKIP(fd,26); + break; + + case 0x7954: // Request about information of an account (by account id) + if (RFIFOREST(fd) < 6) + return 0; + WFIFOW(fd,0) = 0x7953; + WFIFOL(fd,2) = RFIFOL(fd,2); + memset(WFIFOP(fd,7), '\0', 24); + for(i = 0; i < auth_num; i++) { + if (auth_dat[i].account_id == RFIFOL(fd,2)) { + login_log("'ladmin': Sending information of an account (request by the id; account: %s, id: %d, ip: %s)" RETCODE, + auth_dat[i].userid, RFIFOL(fd,2), ip); + WFIFOB(fd,6) = (unsigned char)isGM(auth_dat[i].account_id); + memcpy(WFIFOP(fd,7), auth_dat[i].userid, 24); + WFIFOB(fd,31) = auth_dat[i].sex; + WFIFOL(fd,32) = auth_dat[i].logincount; + WFIFOL(fd,36) = auth_dat[i].state; + memcpy(WFIFOP(fd,40), auth_dat[i].error_message, 20); + memcpy(WFIFOP(fd,60), auth_dat[i].lastlogin, 24); + memcpy(WFIFOP(fd,84), auth_dat[i].last_ip, 16); + memcpy(WFIFOP(fd,100), auth_dat[i].email, 40); + WFIFOL(fd,140) = (unsigned long)auth_dat[i].connect_until_time; + WFIFOL(fd,144) = (unsigned long)auth_dat[i].ban_until_time; + WFIFOW(fd,148) = strlen(auth_dat[i].memo); + if (auth_dat[i].memo[0]) { + memcpy(WFIFOP(fd,150), auth_dat[i].memo, strlen(auth_dat[i].memo)); + } + WFIFOSET(fd,150+strlen(auth_dat[i].memo)); + break; + } + } + if (i == auth_num) { + login_log("'ladmin': Attempt to obtain information (by the id) of an unknown account (id: %d, ip: %s)" RETCODE, + RFIFOL(fd,2), ip); + strncpy(WFIFOP(fd,7), "", 24); + WFIFOW(fd,148) = 0; + WFIFOSET(fd,150); + } + RFIFOSKIP(fd,6); + break; + + case 0x7955: // Request to reload GM file (no answer) + login_log("'ladmin': Request to re-load GM configuration file (ip: %s)." RETCODE, ip); + read_gm_account(); + // send GM accounts to all char-servers + send_GM_accounts(); + RFIFOSKIP(fd,2); + break; + + default: + { + FILE *logfp; + char tmpstr[24]; + struct timeval tv; + logfp = fopen(login_log_unknown_packets_filename, "a"); + if (logfp) { + gettimeofday(&tv, NULL); + strftime(tmpstr, 23, date_format, localtime(&(tv.tv_sec))); + fprintf(logfp, "%s.%03d: receiving of an unknown packet -> disconnection" RETCODE, tmpstr, (int)tv.tv_usec / 1000); + fprintf(logfp, "parse_admin: connection #%d (ip: %s), packet: 0x%x (with being read: %d)." RETCODE, fd, ip, RFIFOW(fd,0), RFIFOREST(fd)); + fprintf(logfp, "Detail (in hex):" RETCODE); + fprintf(logfp, "---- 00-01-02-03-04-05-06-07 08-09-0A-0B-0C-0D-0E-0F" RETCODE); + memset(tmpstr, '\0', sizeof(tmpstr)); + for(i = 0; i < RFIFOREST(fd); i++) { + if ((i & 15) == 0) + fprintf(logfp, "%04X ",i); + fprintf(logfp, "%02x ", RFIFOB(fd,i)); + if (RFIFOB(fd,i) > 0x1f) + tmpstr[i % 16] = RFIFOB(fd,i); + else + tmpstr[i % 16] = '.'; + if ((i - 7) % 16 == 0) // -8 + 1 + fprintf(logfp, " "); + else if ((i + 1) % 16 == 0) { + fprintf(logfp, " %s" RETCODE, tmpstr); + memset(tmpstr, '\0', sizeof(tmpstr)); + } + } + if (i % 16 != 0) { + for(j = i; j % 16 != 0; j++) { + fprintf(logfp, " "); + if ((j - 7) % 16 == 0) // -8 + 1 + fprintf(logfp, " "); + } + fprintf(logfp, " %s" RETCODE, tmpstr); + } + fprintf(logfp, RETCODE); + fclose(logfp); + } + } + login_log("'ladmin': End of connection, unknown packet (ip: %s)" RETCODE, ip); + session[fd]->eof = 1; + printf("Remote administration has been disconnected (unknown packet).\n"); + return 0; + } + //WFIFOW(fd,0) = 0x791f; + //WFIFOSET(fd,2); + } + return 0; +} + +//-------------------------------------------- +// Test to know if an IP come from LAN or WAN. +//-------------------------------------------- +int lan_ip_check(unsigned char *p) { + int i; + int lancheck = 1; + +// printf("lan_ip_check: to compare: %d.%d.%d.%d, network: %d.%d.%d.%d/%d.%d.%d.%d\n", +// p[0], p[1], p[2], p[3], +// subneti[0], subneti[1], subneti[2], subneti[3], +// subnetmaski[0], subnetmaski[1], subnetmaski[2], subnetmaski[3]); + for(i = 0; i < 4; i++) { + if ((subneti[i] & subnetmaski[i]) != (p[i] & subnetmaski[i])) { + lancheck = 0; + break; + } + } + printf("LAN test (result): %s source\033[0m.\n", (lancheck) ? "\033[1;36mLAN" : "\033[1;32mWAN"); + return lancheck; +} + +//---------------------------------------------------------------------------------------- +// Default packet parsing (normal players or administation/char-server connexion requests) +//---------------------------------------------------------------------------------------- +int parse_login(int fd) { + struct mmo_account account; + int result, i, j; + unsigned char *p = (unsigned char *) &session[fd]->client_addr.sin_addr; + char ip[16]; + + sprintf(ip, "%d.%d.%d.%d", p[0], p[1], p[2], p[3]); + + if (session[fd]->eof) { + close(fd); + delete_session(fd); + return 0; + } + + while(RFIFOREST(fd) >= 2) { + if (display_parse_login == 1) { + if (RFIFOW(fd,0) == 0x64 || RFIFOW(fd,0) == 0x01dd) { + if (RFIFOREST(fd) >= ((RFIFOW(fd,0) == 0x64) ? 55 : 47)) + printf("parse_login: connection #%d, packet: 0x%x (with being read: %d), account: %s.\n", fd, RFIFOW(fd,0), RFIFOREST(fd), RFIFOP(fd,6)); + } else if (RFIFOW(fd,0) == 0x2710) { + if (RFIFOREST(fd) >= 86) + printf("parse_login: connection #%d, packet: 0x%x (with being read: %d), server: %s.\n", fd, RFIFOW(fd,0), RFIFOREST(fd), RFIFOP(fd,60)); + } else + printf("parse_login: connection #%d, packet: 0x%x (with being read: %d).\n", fd, RFIFOW(fd,0), RFIFOREST(fd)); + } + + switch(RFIFOW(fd,0)) { + case 0x200: // New alive packet: structure: 0x200 <account.userid>.24B. used to verify if client is always alive. + if (RFIFOREST(fd) < 26) + return 0; + RFIFOSKIP(fd,26); + break; + + case 0x204: // New alive packet: structure: 0x204 <encrypted.account.userid>.16B. (new ragexe from 22 june 2004) + if (RFIFOREST(fd) < 18) + return 0; + RFIFOSKIP(fd,18); + break; + + case 0x64: // Ask connection of a client + case 0x01dd: // Ask connection of a client (encryption mode) + if (RFIFOREST(fd) < ((RFIFOW(fd,0) == 0x64) ? 55 : 47)) + return 0; + + account.userid = RFIFOP(fd,6); + account.userid[23] = '\0'; + remove_control_chars(account.userid); + account.passwd = RFIFOP(fd,30); + if (RFIFOW(fd,0) == 0x64) { + account.passwd[23] = '\0'; + remove_control_chars(account.passwd); + } +#ifdef PASSWORDENC + account.passwdenc = (RFIFOW(fd,0) == 0x64) ? 0 : PASSWORDENC; +#else + account.passwdenc = 0; +#endif + + if (RFIFOW(fd,0) == 0x64) { + login_log("Request for connection (non encryption mode) of %s (ip: %s)." RETCODE, account.userid, ip); + } else { + login_log("Request for connection (encryption mode) of %s (ip: %s)." RETCODE, account.userid, ip); + } + + if (!check_ip(session[fd]->client_addr.sin_addr.s_addr)) { + login_log("Connection refused: IP isn't authorised (deny/allow, ip: %s)." RETCODE, ip); + WFIFOW(fd,0) = 0x6a; + WFIFOB(fd,2) = 0x03; + WFIFOSET(fd,3); + RFIFOSKIP(fd,(RFIFOW(fd,0) == 0x64) ? 55 : 47); + break; + } + + result = mmo_auth(&account, fd); + if (result == -1) { + int gm_level = isGM(account.account_id); + if (min_level_to_connect > gm_level) { + login_log("Connection refused: the minimum GM level for connection is %d (account: %s, GM level: %d, ip: %s)." RETCODE, + min_level_to_connect, account.userid, gm_level, ip); + WFIFOW(fd,0) = 0x81; + WFIFOL(fd,2) = 1; // 01 = Server closed + WFIFOSET(fd,3); + } else { + if (gm_level) + printf("Connection of the GM (level:%d) account '%s' accepted.\n", gm_level, account.userid); + else + printf("Connection of the account '%s' accepted.\n", account.userid); + server_num = 0; + for(i = 0; i < MAX_SERVERS; i++) { + if (server_fd[i] >= 0) { + if (lan_ip_check(p)) + WFIFOL(fd,47+server_num*32) = inet_addr(lan_char_ip); + else + WFIFOL(fd,47+server_num*32) = server[i].ip; + WFIFOW(fd,47+server_num*32+4) = server[i].port; + memcpy(WFIFOP(fd,47+server_num*32+6), server[i].name, 20); + WFIFOW(fd,47+server_num*32+26) = server[i].users; + WFIFOW(fd,47+server_num*32+28) = server[i].maintenance; + WFIFOW(fd,47+server_num*32+30) = server[i].new; + server_num++; + } + } + // if at least 1 char-server + if (server_num > 0) { + WFIFOW(fd,0) = 0x69; + WFIFOW(fd,2) = 47+32*server_num; + WFIFOL(fd,4) = account.login_id1; + WFIFOL(fd,8) = account.account_id; + WFIFOL(fd,12) = account.login_id2; + WFIFOL(fd,16) = 0; // in old version, that was for ip (not more used) + memcpy(WFIFOP(fd,20), account.lastlogin, 24); // in old version, that was for name (not more used) + WFIFOB(fd,46) = account.sex; + WFIFOSET(fd,47+32*server_num); + if (auth_fifo_pos >= AUTH_FIFO_SIZE) + auth_fifo_pos = 0; + auth_fifo[auth_fifo_pos].account_id = account.account_id; + auth_fifo[auth_fifo_pos].login_id1 = account.login_id1; + auth_fifo[auth_fifo_pos].login_id2 = account.login_id2; + auth_fifo[auth_fifo_pos].sex = account.sex; + auth_fifo[auth_fifo_pos].delflag = 0; + auth_fifo[auth_fifo_pos].ip = session[fd]->client_addr.sin_addr.s_addr; + auth_fifo_pos++; + // if no char-server, don't send void list of servers, just disconnect the player with proper message + } else { + login_log("Connection refused: there is no char-server online (account: %s, ip: %s)." RETCODE, + account.userid, ip); + WFIFOW(fd,0) = 0x81; + WFIFOL(fd,2) = 1; // 01 = Server closed + WFIFOSET(fd,3); + } + } + } else { + memset(WFIFOP(fd,0), '\0', 23); + WFIFOW(fd,0) = 0x6a; + WFIFOB(fd,2) = result; + if (result == 6) { // 6 = Your are Prohibited to log in until %s + i = search_account_index(account.userid); + if (i != -1) { + if (auth_dat[i].ban_until_time != 0) { // if account is banned, we send ban timestamp + char tmpstr[256]; + strftime(tmpstr, 20, date_format, localtime(&auth_dat[i].ban_until_time)); + tmpstr[19] = '\0'; + memcpy(WFIFOP(fd,3), tmpstr, 20); + } else { // we send error message + memcpy(WFIFOP(fd,3), auth_dat[i].error_message, 20); + } + } + } + WFIFOSET(fd,23); + } + RFIFOSKIP(fd,(RFIFOW(fd,0) == 0x64) ? 55 : 47); + break; + + case 0x01db: // Sending request of the coding key + case 0x791a: // Sending request of the coding key (administration packet) + { + struct login_session_data *ld; + if (session[fd]->session_data) { + printf("login: abnormal request of MD5 key (already opened session).\n"); + session[fd]->eof = 1; + return 0; + } + ld = session[fd]->session_data = calloc(sizeof(*ld), 1); + if (!ld) { + printf("login: Request for md5 key: memory allocation failure (malloc)!\n"); + session[fd]->eof = 1; + return 0; + } + if (RFIFOW(fd,0) == 0x01db) { + login_log("Sending request of the coding key (ip: %s)" RETCODE, ip); + } else { + login_log("'ladmin': Sending request of the coding key (ip: %s)" RETCODE, ip); + } + // Creation of the coding key + memset(ld->md5key, '\0', sizeof(ld->md5key)); + ld->md5keylen = rand() % 4 + 12; + for(i = 0; i < ld->md5keylen; i++) + ld->md5key[i] = rand() % 255 + 1; + + RFIFOSKIP(fd,2); + WFIFOW(fd,0) = 0x01dc; + WFIFOW(fd,2) = 4 + ld->md5keylen; + memcpy(WFIFOP(fd,4), ld->md5key, ld->md5keylen); + WFIFOSET(fd,WFIFOW(fd,2)); + } + break; + + case 0x2710: // Connection request of a char-server + if (RFIFOREST(fd) < 86) + return 0; + { + int GM_value, len; + unsigned char* server_name; + account.userid = RFIFOP(fd,2); + account.userid[23] = '\0'; + remove_control_chars(account.userid); + account.passwd = RFIFOP(fd,26); + account.passwd[23] = '\0'; + remove_control_chars(account.passwd); + account.passwdenc = 0; + server_name = RFIFOP(fd,60); + server_name[19] = '\0'; + remove_control_chars(server_name); + login_log("Connection request of the char-server '%s' @ %d.%d.%d.%d:%d (ip: %s)" RETCODE, + server_name, RFIFOB(fd,54), RFIFOB(fd,55), RFIFOB(fd,56), RFIFOB(fd,57), RFIFOW(fd,58), ip); + result = mmo_auth(&account, fd); + if (result == -1 && account.sex == 2 && account.account_id < MAX_SERVERS && server_fd[account.account_id] == -1) { + login_log("Connection of the char-server '%s' accepted (account: %s, pass: %s, ip: %s)" RETCODE, + server_name, account.userid, account.passwd, ip); + printf("Connection of the char-server '%s' accepted.\n", server_name); + memset(&server[account.account_id], 0, sizeof(struct mmo_char_server)); + server[account.account_id].ip = RFIFOL(fd,54); + server[account.account_id].port = RFIFOW(fd,58); + memcpy(server[account.account_id].name, server_name, 20); + server[account.account_id].users = 0; + server[account.account_id].maintenance = RFIFOW(fd,82); + server[account.account_id].new = RFIFOW(fd,84); + server_fd[account.account_id] = fd; + if(anti_freeze_enable) + server_freezeflag[account.account_id] = 5; // Char-server anti-freeze system. Counter. 5 ok, 4...0 freezed + WFIFOW(fd,0) = 0x2711; + WFIFOB(fd,2) = 0; + WFIFOSET(fd,3); + session[fd]->func_parse = parse_fromchar; + realloc_fifo(fd, FIFOSIZE_SERVERLINK, FIFOSIZE_SERVERLINK); + // send GM account to char-server + len = 4; + WFIFOW(fd,0) = 0x2732; + for(i = 0; i < auth_num; i++) + // send only existing accounts. We can not create a GM account when server is online. + if ((GM_value = isGM(auth_dat[i].account_id)) > 0) { + WFIFOL(fd,len) = auth_dat[i].account_id; + WFIFOB(fd,len+4) = (unsigned char)GM_value; + len += 5; + } + WFIFOW(fd,2) = len; + WFIFOSET(fd,len); + } else { + login_log("Connexion of the char-server '%s' REFUSED (account: %s, pass: %s, ip: %s)" RETCODE, + server_name, account.userid, account.passwd, ip); + WFIFOW(fd,0) = 0x2711; + WFIFOB(fd,2) = 3; + WFIFOSET(fd,3); + } + } + RFIFOSKIP(fd,86); + return 0; + + case 0x7530: // Request of the server version + login_log("Sending of the server version (ip: %s)" RETCODE, ip); + WFIFOW(fd,0) = 0x7531; + WFIFOB(fd,2) = ATHENA_MAJOR_VERSION; + WFIFOB(fd,3) = ATHENA_MINOR_VERSION; + WFIFOB(fd,4) = ATHENA_REVISION; + WFIFOB(fd,5) = ATHENA_RELEASE_FLAG; + WFIFOB(fd,6) = ATHENA_OFFICIAL_FLAG; + WFIFOB(fd,7) = ATHENA_SERVER_LOGIN; + WFIFOW(fd,8) = ATHENA_MOD_VERSION; + WFIFOSET(fd,10); + RFIFOSKIP(fd,2); + break; + + case 0x7532: // Request to end connection + login_log("End of connection (ip: %s)" RETCODE, ip); + session[fd]->eof = 1; + return 0; + + case 0x7918: // Request for administation login + if (RFIFOREST(fd) < 4 || RFIFOREST(fd) < ((RFIFOW(fd,2) == 0) ? 28 : 20)) + return 0; + WFIFOW(fd,0) = 0x7919; + WFIFOB(fd,2) = 1; + if (!check_ladminip(session[fd]->client_addr.sin_addr.s_addr)) { + login_log("'ladmin'-login: Connection in administration mode refused: IP isn't authorised (ladmin_allow, ip: %s)." RETCODE, ip); + } else { + struct login_session_data *ld = session[fd]->session_data; + if (RFIFOW(fd,2) == 0) { // non encrypted password + unsigned char* password; + password = RFIFOP(fd,4); + password[23] = '\0'; + remove_control_chars(password); + // If remote administration is enabled and password sent by client matches password read from login server configuration file + if ((admin_state == 1) && (strcmp(password, admin_pass) == 0)) { + login_log("'ladmin'-login: Connection in administration mode accepted (non encrypted password: %s, ip: %s)" RETCODE, password, ip); + printf("Connection of a remote administration accepted (non encrypted password).\n"); + WFIFOB(fd,2) = 0; + session[fd]->func_parse = parse_admin; + } else if (admin_state != 1) + login_log("'ladmin'-login: Connection in administration mode REFUSED - remote administration is disabled (non encrypted password: %s, ip: %s)" RETCODE, password, ip); + else + login_log("'ladmin'-login: Connection in administration mode REFUSED - invalid password (non encrypted password: %s, ip: %s)" RETCODE, password, ip); + } else { // encrypted password + if (!ld) + printf("'ladmin'-login: error! MD5 key not created/requested for an administration login.\n"); + else { + char md5str[64] = "", md5bin[32]; + if (RFIFOW(fd,2) == 1) { + strncpy(md5str, ld->md5key, sizeof(ld->md5key)); // 20 + strcat(md5str, admin_pass); // 24 + } else if (RFIFOW(fd,2) == 2) { + strncpy(md5str, admin_pass, sizeof(admin_pass)); // 24 + strcat(md5str, ld->md5key); // 20 + } + MD5_String2binary(md5str, md5bin); + // If remote administration is enabled and password hash sent by client matches hash of password read from login server configuration file + if ((admin_state == 1) && (memcmp(md5bin, RFIFOP(fd,4), 16) == 0)) { + login_log("'ladmin'-login: Connection in administration mode accepted (encrypted password, ip: %s)" RETCODE, ip); + printf("Connection of a remote administration accepted (encrypted password).\n"); + WFIFOB(fd,2) = 0; + session[fd]->func_parse = parse_admin; + } else if (admin_state != 1) + login_log("'ladmin'-login: Connection in administration mode REFUSED - remote administration is disabled (encrypted password, ip: %s)" RETCODE, ip); + else + login_log("'ladmin'-login: Connection in administration mode REFUSED - invalid password (encrypted password, ip: %s)" RETCODE, ip); + } + } + } + WFIFOSET(fd,3); + RFIFOSKIP(fd, (RFIFOW(fd,2) == 0) ? 28 : 20); + break; + + default: + if (save_unknown_packets) { + FILE *logfp; + char tmpstr[24]; + struct timeval tv; + logfp = fopen(login_log_unknown_packets_filename, "a"); + if (logfp) { + gettimeofday(&tv, NULL); + strftime(tmpstr, 23, date_format, localtime(&(tv.tv_sec))); + fprintf(logfp, "%s.%03d: receiving of an unknown packet -> disconnection" RETCODE, tmpstr, (int)tv.tv_usec / 1000); + fprintf(logfp, "parse_login: connection #%d (ip: %s), packet: 0x%x (with being read: %d)." RETCODE, fd, ip, RFIFOW(fd,0), RFIFOREST(fd)); + fprintf(logfp, "Detail (in hex):" RETCODE); + fprintf(logfp, "---- 00-01-02-03-04-05-06-07 08-09-0A-0B-0C-0D-0E-0F" RETCODE); + memset(tmpstr, '\0', sizeof(tmpstr)); + for(i = 0; i < RFIFOREST(fd); i++) { + if ((i & 15) == 0) + fprintf(logfp, "%04X ",i); + fprintf(logfp, "%02x ", RFIFOB(fd,i)); + if (RFIFOB(fd,i) > 0x1f) + tmpstr[i % 16] = RFIFOB(fd,i); + else + tmpstr[i % 16] = '.'; + if ((i - 7) % 16 == 0) // -8 + 1 + fprintf(logfp, " "); + else if ((i + 1) % 16 == 0) { + fprintf(logfp, " %s" RETCODE, tmpstr); + memset(tmpstr, '\0', sizeof(tmpstr)); + } + } + if (i % 16 != 0) { + for(j = i; j % 16 != 0; j++) { + fprintf(logfp, " "); + if ((j - 7) % 16 == 0) // -8 + 1 + fprintf(logfp, " "); + } + fprintf(logfp, " %s" RETCODE, tmpstr); + } + fprintf(logfp, RETCODE); + fclose(logfp); + } + } + login_log("End of connection, unknown packet (ip: %s)" RETCODE, ip); + session[fd]->eof = 1; + return 0; + } + } + return 0; +} + + +//------------------------------------------------- +// Return numerical value of a switch configuration +// on/off, english, fran軋is, deutsch, espaol +//------------------------------------------------- +int config_switch(const char *str) { + if (strcmpi(str, "on") == 0 || strcmpi(str, "yes") == 0 || strcmpi(str, "oui") == 0 || strcmpi(str, "ja") == 0 || strcmpi(str, "si") == 0) + return 1; + if (strcmpi(str, "off") == 0 || strcmpi(str, "no") == 0 || strcmpi(str, "non") == 0 || strcmpi(str, "nein") == 0) + return 0; + + return atoi(str); +} + +//---------------------------------- +// Reading Lan Support configuration +//---------------------------------- +int login_lan_config_read(const char *lancfgName) { + int j; + struct hostent * h = NULL; + char line[1024], w1[1024], w2[1024]; + FILE *fp; + + // set default configuration + strncpy(lan_char_ip, "127.0.0.1", sizeof(lan_char_ip)); + subneti[0] = 127; + subneti[1] = 0; + subneti[2] = 0; + subneti[3] = 1; + for(j = 0; j < 4; j++) + subnetmaski[j] = 255; + + fp = fopen(lancfgName, "r"); + + if (fp == NULL) { + printf("***WARNING: LAN Support configuration file is not found: %s\n", lancfgName); + return 1; + } + + printf("---Start reading Lan Support configuration file\n"); + + while(fgets(line, sizeof(line)-1, fp)) { + if (line[0] == '/' && line[1] == '/') + continue; + + line[sizeof(line)-1] = '\0'; + if (sscanf(line,"%[^:]: %[^\r\n]", w1, w2) != 2) + continue; + + remove_control_chars(w1); + remove_control_chars(w2); + if (strcmpi(w1, "lan_char_ip") == 0) { // Read Char-Server Lan IP Address + h = gethostbyname(w2); + if (h != NULL) { + sprintf(lan_char_ip, "%d.%d.%d.%d", (unsigned char)h->h_addr[0], (unsigned char)h->h_addr[1], (unsigned char)h->h_addr[2], (unsigned char)h->h_addr[3]); + } else { + strncpy(lan_char_ip, w2, sizeof(lan_char_ip)); + lan_char_ip[sizeof(lan_char_ip)-1] = '\0'; + } + printf("LAN IP of char-server: %s.\n", lan_char_ip); + } else if (strcmpi(w1, "subnet") == 0) { // Read Subnetwork + for(j = 0; j < 4; j++) + subneti[j] = 0; + h = gethostbyname(w2); + if (h != NULL) { + for(j = 0; j < 4; j++) + subneti[j] = (unsigned char)h->h_addr[j]; + } else { + sscanf(w2, "%d.%d.%d.%d", &subneti[0], &subneti[1], &subneti[2], &subneti[3]); + } + printf("Sub-network of the char-server: %d.%d.%d.%d.\n", subneti[0], subneti[1], subneti[2], subneti[3]); + } else if (strcmpi(w1, "subnetmask") == 0) { // Read Subnetwork Mask + for(j = 0; j < 4; j++) + subnetmaski[j] = 255; + h = gethostbyname(w2); + if (h != NULL) { + for(j = 0; j < 4; j++) + subnetmaski[j] = (unsigned char)h->h_addr[j]; + } else { + sscanf(w2, "%d.%d.%d.%d", &subnetmaski[0], &subnetmaski[1], &subnetmaski[2], &subnetmaski[3]); + } + printf("Sub-network mask of the char-server: %d.%d.%d.%d.\n", subnetmaski[0], subnetmaski[1], subnetmaski[2], subnetmaski[3]); + } + } + fclose(fp); + + // log the LAN configuration + login_log("The LAN configuration of the server is set:" RETCODE); + login_log("- with LAN IP of char-server: %s." RETCODE, lan_char_ip); + login_log("- with the sub-network of the char-server: %d.%d.%d.%d/%d.%d.%d.%d." RETCODE, + subneti[0], subneti[1], subneti[2], subneti[3], subnetmaski[0], subnetmaski[1], subnetmaski[2], subnetmaski[3]); + + // sub-network check of the char-server + { + unsigned int a0, a1, a2, a3; + unsigned char p[4]; + sscanf(lan_char_ip, "%d.%d.%d.%d", &a0, &a1, &a2, &a3); + p[0] = a0; p[1] = a1; p[2] = a2; p[3] = a3; + printf("LAN test of LAN IP of the char-server: "); + if (lan_ip_check(p) == 0) { + printf("\033[1;31m***ERROR: LAN IP of the char-server doesn't belong to the specified Sub-network\033[0m\n"); + login_log("***ERROR: LAN IP of the char-server doesn't belong to the specified Sub-network." RETCODE); + } + } + + printf("---End reading of Lan Support configuration file\n"); + + return 0; +} + +//----------------------------------- +// Reading general configuration file +//----------------------------------- +int login_config_read(const char *cfgName) { + char line[1024], w1[1024], w2[1024]; + FILE *fp; + + fp = fopen(cfgName, "r"); + if (fp == NULL) { + printf("Configuration file (%s) not found.\n", cfgName); + return 1; + } + + printf("---Start reading of Login Server configuration file (%s)\n", cfgName); + while(fgets(line, sizeof(line)-1, fp)) { + if (line[0] == '/' && line[1] == '/') + continue; + + line[sizeof(line)-1] = '\0'; + if (sscanf(line, "%[^:]: %[^\r\n]", w1, w2) == 2) { + remove_control_chars(w1); + remove_control_chars(w2); + + if (strcmpi(w1, "admin_state") == 0) { + admin_state = config_switch(w2); + } else if (strcmpi(w1, "admin_pass") == 0) { + strncpy(admin_pass, w2, sizeof(admin_pass)); + admin_pass[sizeof(admin_pass)-1] = '\0'; + } else if (strcmpi(w1, "ladminallowip") == 0) { + if (strcmpi(w2, "clear") == 0) { + if (access_ladmin_allow) + free(access_ladmin_allow); + access_ladmin_allow = NULL; + access_ladmin_allownum = 0; + } else { + if (strcmpi(w2, "all") == 0) { + // reset all previous values + if (access_ladmin_allow) + free(access_ladmin_allow); + // set to all + access_ladmin_allow = calloc(ACO_STRSIZE, 1); + access_ladmin_allownum = 1; + access_ladmin_allow[0] = '\0'; + } else if (w2[0] && !(access_ladmin_allownum == 1 && access_ladmin_allow[0] == '\0')) { // don't add IP if already 'all' + if (access_ladmin_allow) + access_ladmin_allow = realloc(access_ladmin_allow, (access_ladmin_allownum+1) * ACO_STRSIZE); + else + access_ladmin_allow = calloc(ACO_STRSIZE, 1); + strncpy(access_ladmin_allow + (access_ladmin_allownum++) * ACO_STRSIZE, w2, ACO_STRSIZE); + access_ladmin_allow[access_ladmin_allownum * ACO_STRSIZE - 1] = '\0'; + } + } + } else if (strcmpi(w1, "gm_pass") == 0) { + strncpy(gm_pass, w2, sizeof(gm_pass)); + gm_pass[sizeof(gm_pass)-1] = '\0'; + } else if (strcmpi(w1, "level_new_gm") == 0) { + level_new_gm = atoi(w2); + } else if (strcmpi(w1, "new_account") == 0) { + new_account_flag = config_switch(w2); + } else if (strcmpi(w1, "login_port") == 0) { + login_port = atoi(w2); + } else if (strcmpi(w1, "account_filename") == 0) { + strncpy(account_filename, w2, sizeof(account_filename)); + account_filename[sizeof(account_filename)-1] = '\0'; + } else if (strcmpi(w1, "gm_account_filename") == 0) { + strncpy(GM_account_filename, w2, sizeof(GM_account_filename)); + GM_account_filename[sizeof(GM_account_filename)-1] = '\0'; + } else if (strcmpi(w1, "gm_account_filename_check_timer") == 0) { + gm_account_filename_check_timer = atoi(w2); + } else if (strcmpi(w1, "login_log_filename") == 0) { + strncpy(login_log_filename, w2, sizeof(login_log_filename)); + login_log_filename[sizeof(login_log_filename)-1] = '\0'; + } else if (strcmpi(w1, "login_log_unknown_packets_filename") == 0) { + strncpy(login_log_unknown_packets_filename, w2, sizeof(login_log_unknown_packets_filename)); + login_log_unknown_packets_filename[sizeof(login_log_unknown_packets_filename)-1] = '\0'; + } else if (strcmpi(w1, "save_unknown_packets") == 0) { + save_unknown_packets = config_switch(w2); + } else if (strcmpi(w1, "display_parse_login") == 0) { + display_parse_login = config_switch(w2); // 0: no, 1: yes + } else if (strcmpi(w1, "display_parse_admin") == 0) { + display_parse_admin = config_switch(w2); // 0: no, 1: yes + } else if (strcmpi(w1, "display_parse_fromchar") == 0) { + display_parse_fromchar = config_switch(w2); // 0: no, 1: yes (without packet 0x2714), 2: all packets + } else if (strcmpi(w1, "date_format") == 0) { // note: never have more than 19 char for the date! + switch (atoi(w2)) { + case 0: + strcpy(date_format, "%d-%m-%Y %H:%M:%S"); // 31-12-2004 23:59:59 + break; + case 1: + strcpy(date_format, "%m-%d-%Y %H:%M:%S"); // 12-31-2004 23:59:59 + break; + case 2: + strcpy(date_format, "%Y-%d-%m %H:%M:%S"); // 2004-31-12 23:59:59 + break; + case 3: + strcpy(date_format, "%Y-%m-%d %H:%M:%S"); // 2004-12-31 23:59:59 + break; + } + } else if (strcmpi(w1, "min_level_to_connect") == 0) { + min_level_to_connect = atoi(w2); + } else if (strcmpi(w1, "add_to_unlimited_account") == 0) { + add_to_unlimited_account = config_switch(w2); + } else if (strcmpi(w1, "start_limited_time") == 0) { + start_limited_time = atoi(w2); + } else if (strcmpi(w1, "check_ip_flag") == 0) { + check_ip_flag = config_switch(w2); + } else if (strcmpi(w1, "order") == 0) { + access_order = atoi(w2); + if (strcmpi(w2, "deny,allow") == 0 || + strcmpi(w2, "deny, allow") == 0) access_order = ACO_DENY_ALLOW; + if (strcmpi(w2, "allow,deny") == 0 || + strcmpi(w2, "allow, deny") == 0) access_order = ACO_ALLOW_DENY; + if (strcmpi(w2, "mutual-failture") == 0 || + strcmpi(w2, "mutual-failure") == 0) access_order = ACO_MUTUAL_FAILTURE; + } else if (strcmpi(w1, "allow") == 0) { + if (strcmpi(w2, "clear") == 0) { + if (access_allow) + free(access_allow); + access_allow = NULL; + access_allownum = 0; + } else { + if (strcmpi(w2, "all") == 0) { + // reset all previous values + if (access_allow) + free(access_allow); + // set to all + access_allow = calloc(ACO_STRSIZE, 1); + access_allownum = 1; + access_allow[0] = '\0'; + } else if (w2[0] && !(access_allownum == 1 && access_allow[0] == '\0')) { // don't add IP if already 'all' + if (access_allow) + access_allow = realloc(access_allow, (access_allownum+1) * ACO_STRSIZE); + else + access_allow = calloc(ACO_STRSIZE, 1); + strncpy(access_allow + (access_allownum++) * ACO_STRSIZE, w2, ACO_STRSIZE); + access_allow[access_allownum * ACO_STRSIZE - 1] = '\0'; + } + } + } else if (strcmpi(w1, "deny") == 0) { + if (strcmpi(w2, "clear") == 0) { + if (access_deny) + free(access_deny); + access_deny = NULL; + access_denynum = 0; + } else { + if (strcmpi(w2, "all") == 0) { + // reset all previous values + if (access_deny) + free(access_deny); + // set to all + access_deny = calloc(ACO_STRSIZE, 1); + access_denynum = 1; + access_deny[0] = '\0'; + } else if (w2[0] && !(access_denynum == 1 && access_deny[0] == '\0')) { // don't add IP if already 'all' + if (access_deny) + access_deny = realloc(access_deny, (access_denynum+1) * ACO_STRSIZE); + else + access_deny = calloc(ACO_STRSIZE, 1); + strncpy(access_deny + (access_denynum++) * ACO_STRSIZE, w2, ACO_STRSIZE); + access_deny[access_denynum * ACO_STRSIZE - 1] = '\0'; + } + } + } else if(strcmpi(w1,"anti_freeze_enable")==0){ + anti_freeze_enable = config_switch(w2); + } else if (strcmpi(w1, "anti_freeze_interval") == 0) { + ANTI_FREEZE_INTERVAL = atoi(w2); + if (ANTI_FREEZE_INTERVAL < 5) + ANTI_FREEZE_INTERVAL = 5; // minimum 5 seconds + } else if (strcmpi(w1, "import") == 0) { + login_config_read(w2); + } + } + } + fclose(fp); + + printf("---End reading of Login Server configuration file.\n"); + + return 0; +} + +//------------------------------------- +// Displaying of configuration warnings +//------------------------------------- +void display_conf_warnings(void) { + if (admin_state != 0 && admin_state != 1) { + printf("***WARNING: Invalid value for admin_state parameter -> set to 0 (no remote admin).\n"); + admin_state = 0; + } + + if (admin_state == 1) { + if (admin_pass[0] == '\0') { + printf("***WARNING: Administrator password is void (admin_pass).\n"); + } else if (strcmp(admin_pass, "admin") == 0) { + printf("***WARNING: You are using the default administrator password (admin_pass).\n"); + printf(" We highly recommend that you change it.\n"); + } + } + + if (gm_pass[0] == '\0') { + printf("***WARNING: 'To GM become' password is void (gm_pass).\n"); + printf(" We highly recommend that you set one password.\n"); + } else if (strcmp(gm_pass, "gm") == 0) { + printf("***WARNING: You are using the default GM password (gm_pass).\n"); + printf(" We highly recommend that you change it.\n"); + } + + if (level_new_gm < 0 || level_new_gm > 99) { + printf("***WARNING: Invalid value for level_new_gm parameter -> set to 60 (default).\n"); + level_new_gm = 60; + } + + if (new_account_flag != 0 && new_account_flag != 1) { + printf("***WARNING: Invalid value for new_account parameter -> set to 0 (no new account).\n"); + new_account_flag = 0; + } + + if (login_port < 1024 || login_port > 65535) { + printf("***WARNING: Invalid value for login_port parameter -> set to 6900 (default).\n"); + login_port = 6900; + } + + if (gm_account_filename_check_timer < 0) { + printf("***WARNING: Invalid value for gm_account_filename_check_timer parameter.\n"); + printf(" -> set to 15 sec (default).\n"); + gm_account_filename_check_timer = 15; + } else if (gm_account_filename_check_timer == 1) { + printf("***WARNING: Invalid value for gm_account_filename_check_timer parameter.\n"); + printf(" -> set to 2 sec (minimum value).\n"); + gm_account_filename_check_timer = 2; + } + + if (save_unknown_packets != 0 && save_unknown_packets != 1) { + printf("WARNING: Invalid value for save_unknown_packets parameter -> set to 0-no save.\n"); + save_unknown_packets = 0; + } + + if (display_parse_login != 0 && display_parse_login != 1) { // 0: no, 1: yes + printf("***WARNING: Invalid value for display_parse_login parameter\n"); + printf(" -> set to 0 (no display).\n"); + display_parse_login = 0; + } + + if (display_parse_admin != 0 && display_parse_admin != 1) { // 0: no, 1: yes + printf("***WARNING: Invalid value for display_parse_admin parameter\n"); + printf(" -> set to 0 (no display).\n"); + display_parse_admin = 0; + } + + if (display_parse_fromchar < 0 || display_parse_fromchar > 2) { // 0: no, 1: yes (without packet 0x2714), 2: all packets + printf("***WARNING: Invalid value for display_parse_fromchar parameter\n"); + printf(" -> set to 0 (no display).\n"); + display_parse_fromchar = 0; + } + + if (min_level_to_connect < 0) { // 0: all players, 1-99 at least gm level x + printf("***WARNING: Invalid value for min_level_to_connect (%d) parameter\n", min_level_to_connect); + printf(" -> set to 0 (any player).\n"); + min_level_to_connect = 0; + } else if (min_level_to_connect > 99) { // 0: all players, 1-99 at least gm level x + printf("***WARNING: Invalid value for min_level_to_connect (%d) parameter\n", min_level_to_connect); + printf(" -> set to 99 (only GM level 99).\n"); + min_level_to_connect = 99; + } + + if (add_to_unlimited_account != 0 && add_to_unlimited_account != 1) { // 0: no, 1: yes + printf("***WARNING: Invalid value for add_to_unlimited_account parameter\n"); + printf(" -> set to 0 (impossible to add a time to an unlimited account).\n"); + add_to_unlimited_account = 0; + } + + if (start_limited_time < -1) { // -1: create unlimited account, 0 or more: additionnal sec from now to create limited time + printf("***WARNING: Invalid value for start_limited_time parameter\n"); + printf(" -> set to -1 (new accounts are created with unlimited time).\n"); + start_limited_time = -1; + } + + if (check_ip_flag != 0 && check_ip_flag != 1) { // 0: no, 1: yes + printf("***WARNING: Invalid value for check_ip_flag parameter\n"); + printf(" -> set to 1 (check players ip between login-server & char-server).\n"); + check_ip_flag = 1; + } + + if (access_order == ACO_DENY_ALLOW) { + if (access_denynum == 1 && access_deny[0] == '\0') { + printf("***WARNING: The IP security order is 'deny,allow' (allow if not deny).\n"); + printf(" And you refuse ALL IP.\n"); + } + } else if (access_order == ACO_ALLOW_DENY) { + if (access_allownum == 0) { + printf("***WARNING: The IP security order is 'allow,deny' (deny if not allow).\n"); + printf(" But, NO IP IS AUTHORISED!\n"); + } + } else { // ACO_MUTUAL_FAILTURE + if (access_allownum == 0) { + printf("***WARNING: The IP security order is 'mutual-failture'\n"); + printf(" (allow if in the allow list and not in the deny list).\n"); + printf(" But, NO IP IS AUTHORISED!\n"); + } else if (access_denynum == 1 && access_deny[0] == '\0') { + printf("***WARNING: The IP security order is mutual-failture\n"); + printf(" (allow if in the allow list and not in the deny list).\n"); + printf(" But, you refuse ALL IP!\n"); + } + } + + return; +} + +//------------------------------- +// Save configuration in log file +//------------------------------- +void save_config_in_log(void) { + int i; + + // a newline in the log... + login_log(""); + login_log("The login-server starting..." RETCODE); + + // save configuration in log file + login_log("The configuration of the server is set:" RETCODE); + + if (admin_state != 1) + login_log("- with no remote administration." RETCODE); + else if (admin_pass[0] == '\0') + login_log("- with a remote administration with a VOID password." RETCODE); + else if (strcmp(admin_pass, "admin") == 0) + login_log("- with a remote administration with the DEFAULT password." RETCODE); + else + login_log("- with a remote administration with the password of %d character(s)." RETCODE, strlen(admin_pass)); + if (access_ladmin_allownum == 0 || (access_ladmin_allownum == 1 && access_ladmin_allow[0] == '\0')) { + login_log("- to accept any IP for remote administration" RETCODE); + } else { + login_log("- to accept following IP for remote administration:" RETCODE); + for(i = 0; i < access_ladmin_allownum; i++) + login_log(" %s" RETCODE, (char *)(access_ladmin_allow + i * ACO_STRSIZE)); + } + + if (gm_pass[0] == '\0') + login_log("- with a VOID 'To GM become' password (gm_pass)." RETCODE); + else if (strcmp(gm_pass, "gm") == 0) + login_log("- with the DEFAULT 'To GM become' password (gm_pass)." RETCODE); + else + login_log("- with a 'To GM become' password (gm_pass) of %d character(s)." RETCODE, strlen(gm_pass)); + if (level_new_gm == 0) + login_log("- to refuse any creation of GM with @gm." RETCODE); + else + login_log("- to create GM with level '%d' when @gm is used." RETCODE, level_new_gm); + + if (new_account_flag == 1) + login_log("- to ALLOW new users (with _F/_M)." RETCODE); + else + login_log("- to NOT ALLOW new users (with _F/_M)." RETCODE); + login_log("- with port: %d." RETCODE, login_port); + login_log("- with the accounts file name: '%s'." RETCODE, account_filename); + login_log("- with the GM accounts file name: '%s'." RETCODE, GM_account_filename); + if (gm_account_filename_check_timer == 0) + login_log("- to NOT check GM accounts file modifications." RETCODE); + else + login_log("- to check GM accounts file modifications every %d seconds." RETCODE, gm_account_filename_check_timer); + + // not necessary to log the 'login_log_filename', we are inside :) + + login_log("- with the unknown packets file name: '%s'." RETCODE, login_log_unknown_packets_filename); + if (save_unknown_packets) + login_log("- to SAVE all unkown packets." RETCODE); + else + login_log("- to SAVE only unkown packets sending by a char-server or a remote administration." RETCODE); + if (display_parse_login) + login_log("- to display normal parse packets on console." RETCODE); + else + login_log("- to NOT display normal parse packets on console." RETCODE); + if (display_parse_admin) + login_log("- to display administration parse packets on console." RETCODE); + else + login_log("- to NOT display administration parse packets on console." RETCODE); + if (display_parse_fromchar) + login_log("- to display char-server parse packets on console." RETCODE); + else + login_log("- to NOT display char-server parse packets on console." RETCODE); + + if (min_level_to_connect == 0) // 0: all players, 1-99 at least gm level x + login_log("- with no minimum level for connection." RETCODE); + else if (min_level_to_connect == 99) + login_log("- to accept only GM with level 99." RETCODE); + else + login_log("- to accept only GM with level %d or more." RETCODE, min_level_to_connect); + + if (add_to_unlimited_account) + login_log("- to authorize adjustment (with timeadd ladmin) on an unlimited account." RETCODE); + else + login_log("- to refuse adjustment (with timeadd ladmin) on an unlimited account. You must use timeset (ladmin command) before." RETCODE); + + if (start_limited_time < 0) + login_log("- to create new accounts with an unlimited time." RETCODE); + else if (start_limited_time == 0) + login_log("- to create new accounts with a limited time: time of creation." RETCODE); + else + login_log("- to create new accounts with a limited time: time of creation + %d second(s)." RETCODE, start_limited_time); + + if (check_ip_flag) + login_log("- with control of players IP between login-server and char-server." RETCODE); + else + login_log("- to not check players IP between login-server and char-server." RETCODE); + + if (access_order == ACO_DENY_ALLOW) { + if (access_denynum == 0) { + login_log("- with the IP security order: 'deny,allow' (allow if not deny). You refuse no IP." RETCODE); + } else if (access_denynum == 1 && access_deny[0] == '\0') { + login_log("- with the IP security order: 'deny,allow' (allow if not deny). You refuse ALL IP." RETCODE); + } else { + login_log("- with the IP security order: 'deny,allow' (allow if not deny). Refused IP are:" RETCODE); + for(i = 0; i < access_denynum; i++) + login_log(" %s" RETCODE, (char *)(access_deny + i * ACO_STRSIZE)); + } + } else if (access_order == ACO_ALLOW_DENY) { + if (access_allownum == 0) { + login_log("- with the IP security order: 'allow,deny' (deny if not allow). But, NO IP IS AUTHORISED!" RETCODE); + } else if (access_allownum == 1 && access_allow[0] == '\0') { + login_log("- with the IP security order: 'allow,deny' (deny if not allow). You authorise ALL IP." RETCODE); + } else { + login_log("- with the IP security order: 'allow,deny' (deny if not allow). Authorised IP are:" RETCODE); + for(i = 0; i < access_allownum; i++) + login_log(" %s" RETCODE, (char *)(access_allow + i * ACO_STRSIZE)); + } + } else { // ACO_MUTUAL_FAILTURE + login_log("- with the IP security order: 'mutual-failture' (allow if in the allow list and not in the deny list)." RETCODE); + if (access_allownum == 0) { + login_log(" But, NO IP IS AUTHORISED!" RETCODE); + } else if (access_denynum == 1 && access_deny[0] == '\0') { + login_log(" But, you refuse ALL IP!" RETCODE); + } else { + if (access_allownum == 1 && access_allow[0] == '\0') { + login_log(" You authorise ALL IP." RETCODE); + } else { + login_log(" Authorised IP are:" RETCODE); + for(i = 0; i < access_allownum; i++) + login_log(" %s" RETCODE, (char *)(access_allow + i * ACO_STRSIZE)); + } + login_log(" Refused IP are:" RETCODE); + for(i = 0; i < access_denynum; i++) + login_log(" %s" RETCODE, (char *)(access_deny + i * ACO_STRSIZE)); + } + } +} + +//-------------------------------------- +// Function called at exit of the server +//-------------------------------------- +void do_final(void) { + int i, fd; + + mmo_auth_sync(); + + free(auth_dat); + free(gm_account_db); + for (i = 0; i < MAX_SERVERS; i++) { + if ((fd = server_fd[i]) >= 0) + delete_session(fd); + } + delete_session(login_fd); + + login_log("----End of login-server (normal end with closing of all files)." RETCODE); +} + +//------------------------------ +// Main function of login-server +//------------------------------ +int do_init(int argc, char **argv) { + int i, j; + + // read login-server configuration + login_config_read((argc > 1) ? argv[1] : LOGIN_CONF_NAME); + display_conf_warnings(); // not in login_config_read, because we can use 'import' option, and display same message twice or more + save_config_in_log(); // not before, because log file name can be changed + login_lan_config_read((argc > 1) ? argv[1] : LAN_CONF_NAME); + + srand(time(NULL)); + + for(i = 0; i< AUTH_FIFO_SIZE; i++) + auth_fifo[i].delflag = 1; + for(i = 0; i < MAX_SERVERS; i++) + server_fd[i] = -1; + + gm_account_db = numdb_init(); + + read_gm_account(); + mmo_auth_init(); + set_termfunc(mmo_auth_sync); + set_defaultparse(parse_login); + login_fd = make_listen_port(login_port); + + add_timer_func_list(check_auth_sync, "check_auth_sync"); + + i = add_timer_interval(gettick() + 60000, check_auth_sync, 0, 0, 60000); // every 60 sec we check if we must save accounts file (only if necessary to save) + + if(anti_freeze_enable > 0) { + add_timer_func_list(char_anti_freeze_system, "char_anti_freeze_system"); + i = add_timer_interval(gettick() + 1000, char_anti_freeze_system, 0, 0, ANTI_FREEZE_INTERVAL * 1000); + } + + // add timer to check GM accounts file modification + j = gm_account_filename_check_timer; + if (j == 0) // if we would not to check, we check every 60 sec, just to have timer (if we change timer, is was not necessary to check if timer already exists) + j = 60; + add_timer_func_list(check_GM_file, "check_GM_file"); + i = add_timer_interval(gettick() + j * 1000, check_GM_file, 0, 0, j * 1000); // every x sec we check if gm file has been changed + + login_log("The login-server is ready (Server is listening on the port %d)." RETCODE, login_port); + + printf("The login-server is \033[1;32mready\033[0m (Server is listening on the port %d).\n\n", login_port); + + atexit(do_final); + return 0; +} + + diff --git a/src/login/login.h b/src/login/login.h new file mode 100644 index 0000000..3a8a6d5 --- /dev/null +++ b/src/login/login.h @@ -0,0 +1,38 @@ +// $Id: login.h,v 1.1.1.1 2004/09/10 17:26:53 MagicalTux Exp $ +#ifndef _LOGIN_H_ +#define _LOGIN_H_ + +#define MAX_SERVERS 30 + +#define LOGIN_CONF_NAME "conf/login_athena.conf" +#define LAN_CONF_NAME "conf/lan_support.conf" +#define PASSWORDENC 3 // A definition is given when making an encryption password correspond. + // It is 1 at the time of passwordencrypt. + // It is made into 2 at the time of passwordencrypt2. + // When it is made 3, it corresponds to both. +#define START_ACCOUNT_NUM 2000000 +#define END_ACCOUNT_NUM 100000000 + +struct mmo_account { + char* userid; + char* passwd; + int passwdenc; + + long account_id; + long login_id1; + long login_id2; + long char_id; + char lastlogin[24]; + int sex; +}; + +struct mmo_char_server { + char name[20]; + long ip; + short port; + int users; + int maintenance; + int new; +}; + +#endif diff --git a/src/login/md5calc.c b/src/login/md5calc.c new file mode 100644 index 0000000..96bfc34 --- /dev/null +++ b/src/login/md5calc.c @@ -0,0 +1,237 @@ +// $Id: md5calc.c,v 1.1.1.1 2004/09/10 17:26:54 MagicalTux Exp $ +/*********************************************************** + * md5 calculation algorithm + * + * The source code referred to the following URL. + * http://www.geocities.co.jp/SiliconValley-Oakland/8878/lab17/lab17.html + * + ***********************************************************/ + +#include "md5calc.h" +#include <string.h> +#include <stdio.h> + +#ifndef UINT_MAX +#define UINT_MAX 4294967295U +#endif + +// Global variable +static unsigned int *pX; + +// Stirng Table +static const unsigned int T[] = { + 0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, //0 + 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501, //4 + 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, //8 + 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821, //12 + 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, //16 + 0xd62f105d, 0x2441453, 0xd8a1e681, 0xe7d3fbc8, //20 + 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, //24 + 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a, //28 + 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, //32 + 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70, //36 + 0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x4881d05, //40 + 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665, //44 + 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, //48 + 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1, //52 + 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, //56 + 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391 //60 +}; + +// ROTATE_LEFT The left is made to rotate x [ n-bit ]. This is diverted as it is from RFC. +#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32-(n)))) + +// The function used for other calculation +static unsigned int F(unsigned int X, unsigned int Y, unsigned int Z) +{ + return (X & Y) | (~X & Z); +} +static unsigned int G(unsigned int X, unsigned int Y, unsigned int Z) +{ + return (X & Z) | (Y & ~Z); +} +static unsigned int H(unsigned int X, unsigned int Y, unsigned int Z) +{ + return X ^ Y ^ Z; +} +static unsigned int I(unsigned int X, unsigned int Y, unsigned int Z) +{ + return Y ^ (X | ~Z); +} + +static unsigned int Round(unsigned int a, unsigned int b, unsigned int FGHI, + unsigned int k, unsigned int s, unsigned int i) +{ + return b + ROTATE_LEFT(a + FGHI + pX[k] + T[i], s); +} + +static void Round1(unsigned int *a, unsigned int b, unsigned int c, + unsigned int d,unsigned int k, unsigned int s, unsigned int i) +{ + *a = Round(*a, b, F(b,c,d), k, s, i); +} +static void Round2(unsigned int *a, unsigned int b, unsigned int c, + unsigned int d,unsigned int k, unsigned int s, unsigned int i) +{ + *a = Round(*a, b, G(b,c,d), k, s, i); +} +static void Round3(unsigned int *a, unsigned int b, unsigned int c, + unsigned int d,unsigned int k, unsigned int s, unsigned int i) +{ + *a = Round(*a, b, H(b,c,d), k, s, i); +} +static void Round4(unsigned int *a, unsigned int b, unsigned int c, + unsigned int d,unsigned int k, unsigned int s, unsigned int i) +{ + *a = Round(*a, b, I(b,c,d), k, s, i); +} + +static void MD5_Round_Calculate(const unsigned char *block, + unsigned int *A2, unsigned int *B2, unsigned int *C2, unsigned int *D2) +{ + //create X It is since it is required. + unsigned int X[16]; //512bit 64byte + int j,k; + + //Save A as AA, B as BB, C as CC, and and D as DD (saving of A, B, C, and D) + unsigned int A=*A2, B=*B2, C=*C2, D=*D2; + unsigned int AA = A,BB = B,CC = C,DD = D; + + //It is a large region variable reluctantly because of calculation of a round. . . for Round1...4 + pX = X; + + //Copy block(padding_message) i into X + for (j=0,k=0; j<64; j+=4,k++) + X[k] = ( (unsigned int )block[j] ) // 8byte*4 -> 32byte conversion + | ( ((unsigned int )block[j+1]) << 8 ) // A function called Decode as used in the field of RFC + | ( ((unsigned int )block[j+2]) << 16 ) + | ( ((unsigned int )block[j+3]) << 24 ); + + + //Round 1 + Round1(&A,B,C,D, 0, 7, 0); Round1(&D,A,B,C, 1, 12, 1); Round1(&C,D,A,B, 2, 17, 2); Round1(&B,C,D,A, 3, 22, 3); + Round1(&A,B,C,D, 4, 7, 4); Round1(&D,A,B,C, 5, 12, 5); Round1(&C,D,A,B, 6, 17, 6); Round1(&B,C,D,A, 7, 22, 7); + Round1(&A,B,C,D, 8, 7, 8); Round1(&D,A,B,C, 9, 12, 9); Round1(&C,D,A,B, 10, 17, 10); Round1(&B,C,D,A, 11, 22, 11); + Round1(&A,B,C,D, 12, 7, 12); Round1(&D,A,B,C, 13, 12, 13); Round1(&C,D,A,B, 14, 17, 14); Round1(&B,C,D,A, 15, 22, 15); + + //Round 2 + Round2(&A,B,C,D, 1, 5, 16); Round2(&D,A,B,C, 6, 9, 17); Round2(&C,D,A,B, 11, 14, 18); Round2(&B,C,D,A, 0, 20, 19); + Round2(&A,B,C,D, 5, 5, 20); Round2(&D,A,B,C, 10, 9, 21); Round2(&C,D,A,B, 15, 14, 22); Round2(&B,C,D,A, 4, 20, 23); + Round2(&A,B,C,D, 9, 5, 24); Round2(&D,A,B,C, 14, 9, 25); Round2(&C,D,A,B, 3, 14, 26); Round2(&B,C,D,A, 8, 20, 27); + Round2(&A,B,C,D, 13, 5, 28); Round2(&D,A,B,C, 2, 9, 29); Round2(&C,D,A,B, 7, 14, 30); Round2(&B,C,D,A, 12, 20, 31); + + //Round 3 + Round3(&A,B,C,D, 5, 4, 32); Round3(&D,A,B,C, 8, 11, 33); Round3(&C,D,A,B, 11, 16, 34); Round3(&B,C,D,A, 14, 23, 35); + Round3(&A,B,C,D, 1, 4, 36); Round3(&D,A,B,C, 4, 11, 37); Round3(&C,D,A,B, 7, 16, 38); Round3(&B,C,D,A, 10, 23, 39); + Round3(&A,B,C,D, 13, 4, 40); Round3(&D,A,B,C, 0, 11, 41); Round3(&C,D,A,B, 3, 16, 42); Round3(&B,C,D,A, 6, 23, 43); + Round3(&A,B,C,D, 9, 4, 44); Round3(&D,A,B,C, 12, 11, 45); Round3(&C,D,A,B, 15, 16, 46); Round3(&B,C,D,A, 2, 23, 47); + + //Round 4 + Round4(&A,B,C,D, 0, 6, 48); Round4(&D,A,B,C, 7, 10, 49); Round4(&C,D,A,B, 14, 15, 50); Round4(&B,C,D,A, 5, 21, 51); + Round4(&A,B,C,D, 12, 6, 52); Round4(&D,A,B,C, 3, 10, 53); Round4(&C,D,A,B, 10, 15, 54); Round4(&B,C,D,A, 1, 21, 55); + Round4(&A,B,C,D, 8, 6, 56); Round4(&D,A,B,C, 15, 10, 57); Round4(&C,D,A,B, 6, 15, 58); Round4(&B,C,D,A, 13, 21, 59); + Round4(&A,B,C,D, 4, 6, 60); Round4(&D,A,B,C, 11, 10, 61); Round4(&C,D,A,B, 2, 15, 62); Round4(&B,C,D,A, 9, 21, 63); + + // Then perform the following additions. (let's add) + *A2 = A + AA; + *B2 = B + BB; + *C2 = C + CC; + *D2 = D + DD; + + //The clearance of confidential information + memset(pX, 0, sizeof(X)); +} + +//------------------------------------------------------------------- +// The function for the exteriors + +/** output is the coded binary in the character sequence which wants to code string. */ +void MD5_String2binary(const char * string, char * output) +{ +//var + /*8bit*/ + unsigned char padding_message[64]; //Extended message 512bit 64byte + unsigned char *pstring; //The position of string in the present scanning notes is held. + +// unsigned char digest[16]; + /*32bit*/ + unsigned int string_byte_len, //The byte chief of string is held. + string_bit_len, //The bit length of string is held. + copy_len, //The number of bytes which is used by 1-3 and which remained + msg_digest[4]; //Message digest 128bit 4byte + unsigned int *A = &msg_digest[0], //The message digest in accordance with RFC (reference) + *B = &msg_digest[1], + *C = &msg_digest[2], + *D = &msg_digest[3]; + int i; + +//prog + //Step 3.Initialize MD Buffer (although it is the initialization; step 3 of A, B, C, and D -- unavoidable -- a head) + *A = 0x67452301; + *B = 0xefcdab89; + *C = 0x98badcfe; + *D = 0x10325476; + + //Step 1.Append Padding Bits (extension of a mark bit) + //1-1 + string_byte_len = strlen(string); //The byte chief of a character sequence is acquired. + pstring = (unsigned char *)string; //The position of the present character sequence is set. + + //1-2 Repeat calculation until length becomes less than 64 bytes. + for (i=string_byte_len; 64<=i; i-=64,pstring+=64) + MD5_Round_Calculate(pstring, A,B,C,D); + + //1-3 + copy_len = string_byte_len % 64; //The number of bytes which remained is computed. + strncpy((char *)padding_message, (char *)pstring, copy_len); //A message is copied to an extended bit sequence. + memset(padding_message+copy_len, 0, 64 - copy_len); //It buries by 0 until it becomes extended bit length. + padding_message[copy_len] |= 0x80; //The next of a message is 1. + + //1-4 + //If 56 bytes or more (less than 64 bytes) of remainder becomes, it will calculate by extending to 64 bytes. + if (56 <= copy_len) { + MD5_Round_Calculate(padding_message, A,B,C,D); + memset(padding_message, 0, 56); //56 bytes is newly fill uped with 0. + } + + + //Step 2.Append Length (the information on length is added) + string_bit_len = string_byte_len * 8; //From the byte chief to bit length (32 bytes of low rank) + memcpy(&padding_message[56], &string_bit_len, 4); //32 bytes of low rank is set. + + //When bit length cannot be expressed in 32 bytes of low rank, it is a beam raising to a higher rank. + if (UINT_MAX / 8 < string_byte_len) { + unsigned int high = (string_byte_len - UINT_MAX / 8) * 8; + memcpy(&padding_message[60], &high, 4); + } else + memset(&padding_message[60], 0, 4); //In this case, it is good for a higher rank at 0. + + //Step 4.Process Message in 16-Word Blocks (calculation of MD5) + MD5_Round_Calculate(padding_message, A,B,C,D); + + + //Step 5.Output (output) + memcpy(output,msg_digest,16); +// memcpy (digest, msg_digest, and 16); //8 byte*4 < - 32byte conversion A function called Encode as used in the field of RFC +/* sprintf(output, + "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", + digest[ 0], digest[ 1], digest[ 2], digest[ 3], + digest[ 4], digest[ 5], digest[ 6], digest[ 7], + digest[ 8], digest[ 9], digest[10], digest[11], + digest[12], digest[13], digest[14], digest[15]);*/ +} + +/** output is the coded character sequence in the character sequence which wants to code string. */ +void MD5_String(const char * string, char * output) +{ + unsigned char digest[16]; + + MD5_String2binary(string,digest); + sprintf(output, + "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", + digest[ 0], digest[ 1], digest[ 2], digest[ 3], + digest[ 4], digest[ 5], digest[ 6], digest[ 7], + digest[ 8], digest[ 9], digest[10], digest[11], + digest[12], digest[13], digest[14], digest[15]); +} + diff --git a/src/login/md5calc.h b/src/login/md5calc.h new file mode 100644 index 0000000..9137b5b --- /dev/null +++ b/src/login/md5calc.h @@ -0,0 +1,8 @@ +// $Id: md5calc.h,v 1.1.1.1 2004/09/10 17:26:54 MagicalTux Exp $ +#ifndef _MD5CALC_H_ +#define _MD5CALC_H_ + +void MD5_String(const char * string, char * output); +void MD5_String2binary(const char * string, char * output); + +#endif diff --git a/src/login_sql/GNUmakefile b/src/login_sql/GNUmakefile new file mode 100644 index 0000000..c245b79 --- /dev/null +++ b/src/login_sql/GNUmakefile @@ -0,0 +1,17 @@ +all: login-server_sql +sql: login-server_sql + +shared_libs=all +COMMON_OBJ = ../common/core.o ../common/socket.o ../common/timer.o ../common/db.o ../common/malloc.o +COMMON_H = ../common/core.h ../common/socket.h ../common/timer.h ../common/mmo.h ../common/version.h ../common/db.h ../common/malloc.h + +login-server_sql: login.o md5calc.o strlib.o $(COMMON_OBJ) + $(CC) -o ../../$@ $^ $(LIB_S) + +login.o: login.c login.h md5calc.h strlib.h $(COMMON_H) +md5calc.o: md5calc.c md5calc.h +strlib.o: strlib.c strlib.h + +clean: + rm -f *.o ../../login-server_sql + diff --git a/src/login_sql/Makefile b/src/login_sql/Makefile new file mode 100644 index 0000000..d1f2734 --- /dev/null +++ b/src/login_sql/Makefile @@ -0,0 +1,17 @@ +all: login-server_sql
+sql: login-server_sql
+
+shared_libs=all
+COMMON_OBJ = ../common/core.o ../common/socket.o ../common/timer.o ../common/db.o ../common/malloc.o
+COMMON_H = ../common/core.h ../common/socket.h ../common/timer.h ../common/mmo.h ../common/version.h ../common/db.h ../common/malloc.h
+
+login-server_sql: login.o md5calc.o strlib.o $(COMMON_OBJ)
+ $(CC) -o ../../$@ $^ $(LIB_S)
+
+login.o: login.c login.h md5calc.h strlib.h $(COMMON_H)
+md5calc.o: md5calc.c md5calc.h
+strlib.o: strlib.c strlib.h
+
+clean:
+ rm -f *.o ../../login-server_sql
+
diff --git a/src/login_sql/login.c b/src/login_sql/login.c new file mode 100644 index 0000000..2fab051 --- /dev/null +++ b/src/login_sql/login.c @@ -0,0 +1,1682 @@ +// $Id: login.c,v 1.6 2004/09/19 21:12:07 Valaris Exp $ +// original : login2.c 2003/01/28 02:29:17 Rev.1.1.1.1 +// txt version 1.100 + +#include <sys/types.h> + +#ifdef LCCWIN32 +#include <winsock.h> +#pragma lib <libmysql.lib> +#else +#include <sys/socket.h> +#include <netinet/in.h> +#include <sys/time.h> +#include <sys/ioctl.h> +#include <arpa/inet.h> +#endif + +#include <stdio.h> +#include <stdlib.h> +#include <netinet/in.h> +#include <sys/time.h> +#include <time.h> +#include <sys/ioctl.h> +#include <sys/stat.h> // for stat/lstat/fstat +#include <unistd.h> +#include <signal.h> +#include <fcntl.h> +#include <string.h> +#include <arpa/inet.h> + +//add include for DBMS(mysql) +#include <mysql.h> + +#include "strlib.h" +#include "timer.h" +/* +#include "timer.h" +#include "core.h" +#include "socket.h" +#include "login.h" +#include "mmo.h" +#include "version.h" +#include "db.h" +*/ + +#include "../common/core.h" +#include "../common/socket.h" +#include "login.h" +#include "../common/mmo.h" +#include "../common/version.h" +#include "../common/db.h" +#include "../common/timer.h" + +#ifdef PASSWORDENC +#include "md5calc.h" +#endif + +#ifdef MEMWATCH +#include "memwatch.h" +#endif + +#define J_MAX_MALLOC_SIZE 65535 + +//----------------------------------------------------- +// global variable +//----------------------------------------------------- +int account_id_count = START_ACCOUNT_NUM; +int server_num; +int new_account_flag = 0; +int login_port = 6900; +char lan_char_ip[128]; // Lan char ip added by kashy +int subnetmaski[4]; // Subnetmask added by kashy + +struct mmo_char_server server[MAX_SERVERS]; +int server_fd[MAX_SERVERS]; +int server_freezeflag[MAX_SERVERS]; // Char-server anti-freeze system. Counter. 5 ok, 4...0 freezed +int anti_freeze_enable = 0; +int ANTI_FREEZE_INTERVAL = 15; + +int login_fd; + +char date_format[32] = "%Y-%m-%d %H:%M:%S"; +int auth_num = 0, auth_max = 0; + +int min_level_to_connect = 0; // minimum level of player/GM (0: player, 1-99: gm) to connect on the server +int check_ip_flag = 1; // It's to check IP of a player between login-server and char-server (part of anti-hacking system) + +MYSQL mysql_handle; + +int ipban = 1; +int dynamic_account_ban = 1; +int dynamic_account_ban_class = 0; +int dynamic_pass_failure_ban = 1; +int dynamic_pass_failure_ban_time = 5; +int dynamic_pass_failure_ban_how_many = 3; +int dynamic_pass_failure_ban_how_long = 60; + +int login_server_port = 3306; +char login_server_ip[32] = "127.0.0.1"; +char login_server_id[32] = "ragnarok"; +char login_server_pw[32] = "ragnarok"; +char login_server_db[32] = "ragnarok"; +int use_md5_passwds = 0; +char login_db[256] = "login"; +char loginlog_db[256] = "loginlog"; + +// added to help out custom login tables, without having to recompile +// source so options are kept in the login_athena.conf or the inter_athena.conf +char login_db_account_id[256] = "account_id"; +char login_db_userid[256] = "userid"; +char login_db_user_pass[256] = "user_pass"; +char login_db_level[256] = "level"; + +char tmpsql[65535], tmp_sql[65535]; + +//----------------------------------------------------- + +#define AUTH_FIFO_SIZE 256 +struct { + int account_id,login_id1,login_id2; + int ip,sex,delflag; +} auth_fifo[AUTH_FIFO_SIZE]; + +int auth_fifo_pos = 0; + + +//----------------------------------------------------- + +static char md5key[20], md5keylen = 16; + +//----------------------------------------------------- +// check user level +//----------------------------------------------------- + +int isGM(int account_id) { + int level; + + MYSQL_RES* sql_res; + MYSQL_ROW sql_row; + level = 0; + sprintf(tmpsql,"SELECT `%s` FROM `%s` WHERE `%s`='%d'", login_db_level, login_db, login_db_account_id, account_id); + if (mysql_query(&mysql_handle, tmpsql)) { + printf("DB server Error (select GM Level to Memory)- %s\n", mysql_error(&mysql_handle)); + } + sql_res = mysql_store_result(&mysql_handle); + if (sql_res) { + sql_row = mysql_fetch_row(sql_res); + level = atoi(sql_row[0]); + if (level > 99) + level = 99; + } + + if (level == 0) { + return 0; + //not GM + } + + mysql_free_result(sql_res); + + return level; +} + +//----------------------------------------------------- +// Function to suppress control characters in a string. +//----------------------------------------------------- +int remove_control_chars(unsigned char *str) { + int i; + int change = 0; + + for(i = 0; str[i]; i++) { + if (str[i] < 32) { + str[i] = '_'; + change = 1; + } + } + + return change; +} + +//--------------------------------------------------- +// E-mail check: return 0 (not correct) or 1 (valid). +//--------------------------------------------------- +int e_mail_check(unsigned char *email) { + char ch; + unsigned char* last_arobas; + + // athena limits + if (strlen(email) < 3 || strlen(email) > 39) + return 0; + + // part of RFC limits (official reference of e-mail description) + if (strchr(email, '@') == NULL || email[strlen(email)-1] == '@') + return 0; + + if (email[strlen(email)-1] == '.') + return 0; + + last_arobas = strrchr(email, '@'); + + if (strstr(last_arobas, "@.") != NULL || + strstr(last_arobas, "..") != NULL) + return 0; + + for(ch = 1; ch < 32; ch++) { + if (strchr(last_arobas, ch) != NULL) { + return 0; + break; + } + } + + if (strchr(last_arobas, ' ') != NULL || + strchr(last_arobas, ';') != NULL) + return 0; + + // all correct + return 1; +} + +//----------------------------------------------------- +// Read Account database - mysql db +//----------------------------------------------------- +int mmo_auth_sqldb_init(void) { + + printf("Login server init....\n"); + + // memory initialize + printf("memory initialize....\n"); + + mysql_init(&mysql_handle); + + // DB connection start + printf("Connect Login Database Server....\n"); + if (!mysql_real_connect(&mysql_handle, login_server_ip, login_server_id, login_server_pw, + login_server_db, login_server_port, (char *)NULL, 0)) { + // pointer check + printf("%s\n", mysql_error(&mysql_handle)); + exit(1); + } else { + printf("connect success!\n"); + } + + sprintf(tmpsql, "INSERT INTO `%s`(`time`,`ip`,`user`,`rcode`,`log`) VALUES (NOW(), '', 'lserver', '100','login server started')", loginlog_db); + + //query + if (mysql_query(&mysql_handle, tmpsql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + + return 0; +} + +//----------------------------------------------------- +// DB server connect check +//----------------------------------------------------- +void mmo_auth_sqldb_sync(void) { + // db connect check? or close? + // ping pong DB server -if losted? then connect try. else crash. +} + +//----------------------------------------------------- +// close DB +//----------------------------------------------------- +void mmo_db_close(void) { + + //set log. + sprintf(tmpsql,"INSERT INTO `%s`(`time`,`ip`,`user`,`rcode`,`log`) VALUES (NOW(), '', 'lserver','100', 'login server shutdown')", loginlog_db); + + //query + if (mysql_query(&mysql_handle, tmpsql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + + //delete all server status + sprintf(tmpsql,"DELETE FROM `sstatus`"); + //query + if (mysql_query(&mysql_handle, tmpsql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + + mysql_close(&mysql_handle); + printf("close DB connect....\n"); + + int i, fd; + + for (i = 0; i < MAX_SERVERS; i++) { + if ((fd = server_fd[i]) >= 0) + delete_session(fd); + } + delete_session(login_fd); +} + +//----------------------------------------------------- +// Make new account +//----------------------------------------------------- +int mmo_auth_sqldb_new(struct mmo_account* account,const char *tmpstr, char sex) { + //no need on DB version + + printf("Request new account.... - not support on this version\n"); + + return 0; +} + +//----------------------------------------------------- +// Make new account +//----------------------------------------------------- +int mmo_auth_new(struct mmo_account* account, const char *tmpstr, char sex) { + + return 0; +} + +#ifdef LCCWIN32 +extern void gettimeofday(struct timeval *t, struct timezone *dummy); +#endif + +//----------------------------------------------------- +// Auth +//----------------------------------------------------- +int mmo_auth( struct mmo_account* account , int fd){ + struct timeval tv; + time_t ban_until_time; + char tmpstr[256]; + char t_uid[256], t_pass[256]; + char user_password[256]; + + MYSQL_RES* sql_res ; + MYSQL_ROW sql_row ; + //int sql_fields, sql_cnt; + char md5str[64], md5bin[32]; + + char ip[16]; + + unsigned char *sin_addr = (unsigned char *)&session[fd]->client_addr.sin_addr; + + printf ("auth start...\n"); + + sprintf(ip, "%d.%d.%d.%d", sin_addr[0], sin_addr[1], sin_addr[2], sin_addr[3]); + + // auth start : time seed + gettimeofday(&tv, NULL); + strftime(tmpstr, 24, "%Y-%m-%d %H:%M:%S",localtime(&(tv.tv_sec))); + sprintf(tmpstr+19, ".%03d", (int)tv.tv_usec/1000); + + jstrescapecpy(t_uid,account->userid); + jstrescapecpy(t_pass, account->passwd); + + // make query + sprintf(tmpsql, "SELECT `%s`,`%s`,`%s`,`lastlogin`,`logincount`,`sex`,`connect_until`,`last_ip`,`ban_until`,`state`,`%s`" + " FROM `%s` WHERE `%s`='%s'", login_db_account_id, login_db_userid, login_db_user_pass, login_db_level, login_db, login_db_userid, t_uid); + //login {0-account_id/1-userid/2-user_pass/3-lastlogin/4-logincount/5-sex/6-connect_untl/7-last_ip/8-ban_until/9-state} + + // query + if (mysql_query(&mysql_handle, tmpsql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + sql_res = mysql_store_result(&mysql_handle) ; + if (sql_res) { + sql_row = mysql_fetch_row(sql_res); //row fetching + if (!sql_row) { + //there's no id. + printf ("auth failed no account %s %s %s\n", tmpstr, account->userid, account->passwd); + mysql_free_result(sql_res); + return 0; + } + } else { + printf("mmo_auth DB result error ! \n"); + return 0; + } + // Documented by CLOWNISIUS || LLRO || Gunstar lead this one with me + // IF changed to diferent returns~ you get diferent responses from your msgstringtable.txt + //Ireturn 2 == line 9 + //Ireturn 5 == line 311 + //Ireturn 6 == line 450 + //Ireturn 7 == line 440 + //Ireturn 8 == line 682 + //Ireturn 9 == line 704 + //Ireturn 10 == line 705 + //Ireturn 11 == line 706 + //Ireturn 12 == line 707 + //Ireturn 13 == line 708 + //Ireturn 14 == line 709 + //Ireturn 15 == line 710 + //Ireturn -1 == line 010 + // Check status + { + if (atoi(sql_row[9]) == -3) { + //id is banned + mysql_free_result(sql_res); + return -3; + } else if (atoi(sql_row[9]) == -2) { //dynamic ban + //id is banned + mysql_free_result(sql_res); + //add IP list. + return -2; + } + + if (use_md5_passwds) { + MD5_String(account->passwd,user_password); + } else { + jstrescapecpy(user_password, account->passwd); + } + printf("account id ok encval:%d\n",account->passwdenc); + int encpasswdok = 0; +#ifdef PASSWORDENC + if (account->passwdenc > 0) { + printf ("start md5calc..\n"); + int j = account->passwdenc; + if (j > 2) + j = 1; + do { + if (j == 1) { + sprintf(md5str, "%s%s", md5key,sql_row[2]); + } else if (j == 2) { + sprintf(md5str, "%s%s", sql_row[2], md5key); + } else + md5str[0] = 0; + printf("j:%d mdstr:%s\n", j, md5str); + MD5_String2binary(md5str, md5bin); + encpasswdok = (memcmp(user_password, md5bin, 16) == 0); + } while (j < 2 && !encpasswdok && (j++) != account->passwdenc); + //printf("key[%s] md5 [%s] ", md5key, md5); + printf("client [%s] accountpass [%s]\n", user_password, sql_row[2]); + printf ("end md5calc..\n"); + } +#endif + if ((strcmp(user_password, sql_row[2]) && !encpasswdok)) { + if (account->passwdenc == 0) { + printf ("auth failed pass error %s %s %s" RETCODE, tmpstr, account->userid, user_password); +#ifdef PASSWORDENC + } else { + char logbuf[1024], *p = logbuf; + int j; + p += sprintf(p, "auth failed pass error %s %s recv-md5[", tmpstr, account->userid); + for(j = 0; j < 16; j++) + p += sprintf(p, "%02x", ((unsigned char *)user_password)[j]); + p += sprintf(p, "] calc-md5["); + for(j = 0; j < 16; j++) + p += sprintf(p, "%02x", ((unsigned char *)md5bin)[j]); + p += sprintf(p, "] md5key["); + for(j = 0; j < md5keylen; j++) + p += sprintf(p, "%02x", ((unsigned char *)md5key)[j]); + p += sprintf(p, "]" RETCODE); + printf("%s\n", p); +#endif + } + return 1; + } + printf("auth ok %s %s" RETCODE, tmpstr, account->userid); + } + + if (atoi(sql_row[9])) { + switch(atoi(sql_row[9])) { // packet 0x006a value + 1 + case 1: // 0 = Unregistered ID + case 2: // 1 = Incorrect Password + case 3: // 2 = This ID is expired + case 4: // 3 = Rejected from Server + case 5: // 4 = You have been blocked by the GM Team + case 6: // 5 = Your Game's EXE file is not the latest version + case 7: // 6 = Your are Prohibited to log in until %s + case 8: // 7 = Server is jammed due to over populated + case 9: // 8 = No MSG (actually, all states after 9 except 99 are No MSG, use only this) + case 100: // 99 = This ID has been totally erased + printf("Auth Error #%d\n", atoi(sql_row[9])); + return atoi(sql_row[9]) - 1; + break; + default: + return 99; // 99 = ID has been totally erased + break; + } + } + +/* +// do not remove this section. this is meant for future, and current forums usage +// as a login manager and CP for login server. [CLOWNISIUS] + if (atoi(sql_row[10]) == 1) { + return 4; + } + + if (atoi(sql_row[10]) >= 5) { + switch(atoi(sql_row[10])) { + case 5: + return 5; + break; + case 6: + return 7; + break; + case 7: + return 9; + break; + case 8: + return 10; + break; + case 9: + return 11; + break; + default: + return 10; + break; + } + } +*/ + ban_until_time = atol(sql_row[8]); + + //login {0-account_id/1-userid/2-user_pass/3-lastlogin/4-logincount/5-sex/6-connect_untl/7-last_ip/8-ban_until/9-state} + if (ban_until_time != 0) { // if account is banned + strftime(tmpstr, 20, date_format, localtime(&ban_until_time)); + tmpstr[19] = '\0'; + if (ban_until_time > time(NULL)) { // always banned + return 6; // 6 = Your are Prohibited to log in until %s + } else { // ban is finished + // reset the ban time + sprintf(tmpsql, "UPDATE `%s` SET `ban_until`='0' WHERE `%s`='%s'", login_db, login_db_userid, t_uid); + if (mysql_query(&mysql_handle, tmpsql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + } + } + + if (atol(sql_row[6]) != 0 && atol(sql_row[6]) < time(NULL)) { + return 2; // 2 = This ID is expired + } + + account->account_id = atoi(sql_row[0]); + account->login_id1 = rand(); + account->login_id2 = rand(); + memcpy(tmpstr, sql_row[3], 19); + memcpy(account->lastlogin, tmpstr, 24); + account->sex = sql_row[5][0] == 'S' ? 2 : sql_row[5][0]=='M'; + + sprintf(tmpsql, "UPDATE `%s` SET `lastlogin` = NOW(), `logincount`=`logincount` +1, `last_ip`='%s' WHERE `%s` = '%s'", + login_db, ip, login_db_userid, sql_row[1]); + mysql_free_result(sql_res) ; //resource free + if (mysql_query(&mysql_handle, tmpsql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + + return -1; +} + +// Send to char +int charif_sendallwos(int sfd, unsigned char *buf, unsigned int len) { + int i, c; + int fd; + + c = 0; + for(i = 0; i < MAX_SERVERS; i++) { + if ((fd = server_fd[i]) > 0 && fd != sfd) { + memcpy(WFIFOP(fd,0), buf, len); + WFIFOSET(fd,len); + c++; + } + } + + return c; +} + +//-------------------------------- +// Char-server anti-freeze system +//-------------------------------- +int char_anti_freeze_system(int tid, unsigned int tick, int id, int data) { + int i; + + for(i = 0; i < MAX_SERVERS; i++) { + if (server_fd[i] >= 0) {// if char-server is online +// printf("char_anti_freeze_system: server #%d '%s', flag: %d.\n", i, server[i].name, server_freezeflag[i]); + if (server_freezeflag[i]-- < 1) {// Char-server anti-freeze system. Counter. 5 ok, 4...0 freezed + session[server_fd[i]]->eof = 1; + } + } + } + + return 0; +} + +//----------------------------------------------------- +// char-server packet parse +//----------------------------------------------------- +int parse_fromchar(int fd){ + int i, id; + MYSQL_RES* sql_res; + MYSQL_ROW sql_row = NULL; + + unsigned char *p = (unsigned char *) &session[fd]->client_addr.sin_addr; + char ip[16]; + + sprintf(ip, "%d.%d.%d.%d", p[0], p[1], p[2], p[3]); + + for(id = 0; id < MAX_SERVERS; id++) + if (server_fd[id] == fd) + break; + + if (id == MAX_SERVERS || session[fd]->eof) { + if (id < MAX_SERVERS) { + printf("Char-server '%s' has disconnected.\n", server[id].name); + server_fd[id] = -1; + memset(&server[id], 0, sizeof(struct mmo_char_server)); + // server delete + sprintf(tmpsql, "DELETE FROM `sstatus` WHERE `index`='%d'", id); + // query + if (mysql_query(&mysql_handle, tmpsql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + } + close(fd); + delete_session(fd); + return 0; + } + + while(RFIFOREST(fd) >= 2) { +// printf("char_parse: %d %d packet case=%x\n", fd, RFIFOREST(fd), RFIFOW(fd, 0)); + + switch (RFIFOW(fd,0)) { + case 0x2712: + if (RFIFOREST(fd) < 19) + return 0; + { + int account_id; + account_id = RFIFOL(fd,2); // speed up + for(i=0;i<AUTH_FIFO_SIZE;i++){ + if (auth_fifo[i].account_id == account_id && + auth_fifo[i].login_id1 == RFIFOL(fd,6) && +#if CMP_AUTHFIFO_LOGIN2 != 0 + auth_fifo[i].login_id2 == RFIFOL(fd,10) && // relate to the versions higher than 18 +#endif + auth_fifo[i].sex == RFIFOB(fd,14) && +#if CMP_AUTHFIFO_IP != 0 + auth_fifo[i].ip == RFIFOL(fd,15) && +#endif + !auth_fifo[i].delflag) { + auth_fifo[i].delflag = 1; + printf("auth -> %d\n", i); + break; + } + } + + if (i != AUTH_FIFO_SIZE) { // send account_reg + int p; + time_t connect_until_time = 0; + char email[40] = ""; + account_id=RFIFOL(fd,2); + sprintf(tmpsql, "SELECT `email`,`connect_until` FROM `%s` WHERE `%s`='%d'", login_db, login_db_account_id, account_id); + if (mysql_query(&mysql_handle, tmpsql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + sql_res = mysql_store_result(&mysql_handle) ; + if (sql_res) { + sql_row = mysql_fetch_row(sql_res); + connect_until_time = atol(sql_row[1]); + strcpy(email, sql_row[0]); + } + mysql_free_result(sql_res); + if (account_id > 0) { + sprintf(tmpsql, "SELECT `str`,`value` FROM `global_reg_value` WHERE `type`='1' AND `account_id`='%d'",account_id); + if (mysql_query(&mysql_handle, tmpsql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + sql_res = mysql_store_result(&mysql_handle) ; + if (sql_res) { + WFIFOW(fd,0) = 0x2729; + WFIFOL(fd,4) = account_id; + for(p = 8; (sql_row = mysql_fetch_row(sql_res));p+=36){ + memcpy(WFIFOP(fd,p), sql_row[0], 32); + WFIFOL(fd,p+32) = atoi(sql_row[1]); + } + WFIFOW(fd,2) = p; + WFIFOSET(fd,p); + //printf("account_reg2 send : login->char (auth fifo)\n"); + WFIFOW(fd,0) = 0x2713; + WFIFOL(fd,2) = account_id; + WFIFOB(fd,6) = 0; + memcpy(WFIFOP(fd, 7), email, 40); + WFIFOL(fd,47) = (unsigned long) connect_until_time; + WFIFOSET(fd,51); + } + mysql_free_result(sql_res); + } + } else { + WFIFOW(fd,0) = 0x2713; + WFIFOL(fd,2) = account_id; + WFIFOB(fd,6) = 1; + WFIFOSET(fd,51); + } + } + RFIFOSKIP(fd,19); + break; + + case 0x2714: + if (RFIFOREST(fd) < 6) + return 0; + // how many users on world? (update) + if (server[id].users != RFIFOL(fd,2)) + printf("set users %s : %d\n", server[id].name, RFIFOL(fd,2)); + server[id].users = RFIFOL(fd,2); + if(anti_freeze_enable) + server_freezeflag[id] = 5; // Char anti-freeze system. Counter. 5 ok, 4...0 freezed + + sprintf(tmpsql,"UPDATE `sstatus` SET `user` = '%d' WHERE `index` = '%d'", server[id].users, id); + // query + if (mysql_query(&mysql_handle, tmpsql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + RFIFOSKIP(fd,6); + break; + + // We receive an e-mail/limited time request, because a player comes back from a map-server to the char-server + case 0x2716: + if (RFIFOREST(fd) < 6) + return 0; + { + int account_id; + time_t connect_until_time = 0; + char email[40] = ""; + account_id=RFIFOL(fd,2); + sprintf(tmpsql,"SELECT `email`,`connect_until` FROM `%s` WHERE `%s`='%d'",login_db, login_db_account_id, account_id); + if(mysql_query(&mysql_handle, tmpsql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + sql_res = mysql_store_result(&mysql_handle) ; + if (sql_res) { + sql_row = mysql_fetch_row(sql_res); + connect_until_time = atol(sql_row[1]); + strcpy(email, sql_row[0]); + } + mysql_free_result(sql_res); + //printf("parse_fromchar: E-mail/limited time request from '%s' server (concerned account: %d)\n", server[id].name, RFIFOL(fd,2)); + WFIFOW(fd,0) = 0x2717; + WFIFOL(fd,2) = RFIFOL(fd,2); + memcpy(WFIFOP(fd, 6), email, 40); + WFIFOL(fd,46) = (unsigned long) connect_until_time; + WFIFOSET(fd,50); + } + RFIFOSKIP(fd,6); + break; + + case 0x2720: // GM + if (RFIFOREST(fd) < 4) + return 0; + if (RFIFOREST(fd) < RFIFOW(fd,2)) + return 0; + //oldacc = RFIFOL(fd,4); + printf("change GM isn't support in this login server version.\n"); + printf("change GM error 0 %s\n", RFIFOP(fd, 8)); + + RFIFOSKIP(fd, RFIFOW(fd, 2)); + WFIFOW(fd, 0) = 0x2721; + WFIFOL(fd, 2) = RFIFOL(fd,4); // oldacc; + WFIFOL(fd, 6) = 0; // newacc; + WFIFOSET(fd, 10); + return 0; + + // Map server send information to change an email of an account via char-server + case 0x2722: // 0x2722 <account_id>.L <actual_e-mail>.40B <new_e-mail>.40B + if (RFIFOREST(fd) < 86) + return 0; + { + int acc; + char actual_email[40], new_email[40]; + acc = RFIFOL(fd,2); + memcpy(actual_email, RFIFOP(fd,6), 40); + memcpy(new_email, RFIFOP(fd,46), 40); + if (e_mail_check(actual_email) == 0) + printf("Char-server '%s': Attempt to modify an e-mail on an account (@email GM command), but actual email is invalid (account: %d, ip: %s)" RETCODE, + server[id].name, acc, ip); + else if (e_mail_check(new_email) == 0) + printf("Char-server '%s': Attempt to modify an e-mail on an account (@email GM command) with a invalid new e-mail (account: %d, ip: %s)" RETCODE, + server[id].name, acc, ip); + else if (strcmpi(new_email, "a@a.com") == 0) + printf("Char-server '%s': Attempt to modify an e-mail on an account (@email GM command) with a default e-mail (account: %d, ip: %s)" RETCODE, + server[id].name, acc, ip); + else { + sprintf(tmpsql, "SELECT `%s`,`email` FROM `%s` WHERE `%s` = '%d'", login_db_userid, login_db, login_db_account_id, acc); + if (mysql_query(&mysql_handle, tmpsql)) + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + sql_res = mysql_store_result(&mysql_handle); + if (sql_res) { + sql_row = mysql_fetch_row(sql_res); //row fetching + + if (strcmpi(sql_row[1], actual_email) == 0) { + sprintf(tmpsql, "UPDATE `%s` SET `email` = '%s' WHERE `%s` = '%d'", login_db, new_email, login_db_account_id, acc); + // query + if (mysql_query(&mysql_handle, tmpsql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + printf("Char-server '%s': Modify an e-mail on an account (@email GM command) (account: %d (%s), new e-mail: %s, ip: %s)." RETCODE, + server[id].name, acc, sql_row[0], actual_email, ip); + } + } + + } + } + RFIFOSKIP(fd, 86); + break; + + case 0x2724: // Receiving of map-server via char-server a status change resquest (by Yor) + if (RFIFOREST(fd) < 10) + return 0; + { + int acc, statut; + acc = RFIFOL(fd,2); + statut = RFIFOL(fd,6); + sprintf(tmpsql, "SELECT `state` FROM `%s` WHERE `%s` = '%d'", login_db, login_db_account_id, acc); + if (mysql_query(&mysql_handle, tmpsql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + sql_res = mysql_store_result(&mysql_handle); + if (sql_res) { + sql_row = mysql_fetch_row(sql_res); // row fetching + } + if (atoi(sql_row[0]) != statut && statut != 0) { + unsigned char buf[16]; + WBUFW(buf,0) = 0x2731; + WBUFL(buf,2) = acc; + WBUFB(buf,6) = 0; // 0: change of statut, 1: ban + WBUFL(buf,7) = statut; // status or final date of a banishment + charif_sendallwos(-1, buf, 11); + } + sprintf(tmpsql,"UPDATE `%s` SET `state` = '%d' WHERE `%s` = '%d'", login_db, statut,login_db_account_id,acc); + //query + if(mysql_query(&mysql_handle, tmpsql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + RFIFOSKIP(fd,10); + } + return 0; + + case 0x2725: // Receiving of map-server via char-server a ban resquest (by Yor) + if (RFIFOREST(fd) < 18) + return 0; + { + int acc; + struct tm *tmtime; + time_t timestamp, tmptime; + acc = RFIFOL(fd,2); + sprintf(tmpsql, "SELECT `ban_until` FROM `%s` WHERE `%s` = '%d'",login_db,login_db_account_id,acc); + if (mysql_query(&mysql_handle, tmpsql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + sql_res = mysql_store_result(&mysql_handle); + if (sql_res) { + sql_row = mysql_fetch_row(sql_res); // row fetching + } + tmptime = atol(sql_row[0]); + if (tmptime == 0 || tmptime < time(NULL)) + timestamp = time(NULL); + else + timestamp = tmptime; + tmtime = localtime(×tamp); + tmtime->tm_year = tmtime->tm_year + (short)RFIFOW(fd,6); + tmtime->tm_mon = tmtime->tm_mon + (short)RFIFOW(fd,8); + tmtime->tm_mday = tmtime->tm_mday + (short)RFIFOW(fd,10); + tmtime->tm_hour = tmtime->tm_hour + (short)RFIFOW(fd,12); + tmtime->tm_min = tmtime->tm_min + (short)RFIFOW(fd,14); + tmtime->tm_sec = tmtime->tm_sec + (short)RFIFOW(fd,16); + timestamp = mktime(tmtime); + if (timestamp != -1) { + if (timestamp <= time(NULL)) + timestamp = 0; + if (tmptime != timestamp) { + if (timestamp != 0) { + unsigned char buf[16]; + WBUFW(buf,0) = 0x2731; + WBUFL(buf,2) = acc; + WBUFB(buf,6) = 1; // 0: change of statut, 1: ban + WBUFL(buf,7) = timestamp; // status or final date of a banishment + charif_sendallwos(-1, buf, 11); + } + printf("Account: %d Banned until: %ld\n", acc, timestamp); + sprintf(tmpsql, "UPDATE `%s` SET `ban_until` = '%ld', `state`='7' WHERE `%s` = '%d'", login_db, timestamp, login_db_account_id, acc); + // query + if (mysql_query(&mysql_handle, tmpsql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + } + } + RFIFOSKIP(fd,18); + break; + } + return 0; + + case 0x2727: + if (RFIFOREST(fd) < 6) + return 0; + { + int acc,sex; + unsigned char buf[16]; + acc=RFIFOL(fd,4); + sprintf(tmpsql,"SELECT `sex` FROM `%s` WHERE `%s` = '%d'",login_db,login_db_account_id,acc); + + if(mysql_query(&mysql_handle, tmpsql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + return 0; + } + + sql_res = mysql_store_result(&mysql_handle) ; + + if (sql_res) { + if (mysql_num_rows(sql_res) == 0) { + mysql_free_result(sql_res); + return 0; + } + sql_row = mysql_fetch_row(sql_res); //row fetching + } + + if (strcmpi(sql_row[0], "M") == 0) + sex = 1; + else + sex = 0; + sprintf(tmpsql,"UPDATE `%s` SET `sex` = '%c' WHERE `%s` = '%d'", login_db, (sex==0?'M':'F'), login_db_account_id, acc); + //query + if(mysql_query(&mysql_handle, tmpsql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + WBUFW(buf,0) = 0x2723; + WBUFL(buf,2) = acc; + WBUFB(buf,6) = sex; + charif_sendallwos(-1, buf, 7); + RFIFOSKIP(fd,6); + } + return 0; + + case 0x2728: // save account_reg + if (RFIFOREST(fd) < 4 || RFIFOREST(fd) < RFIFOW(fd,2)) + return 0; + { + int acc,p,j; + char str[32]; + char temp_str[32]; + int value; + acc=RFIFOL(fd,4); + + if (acc>0){ + unsigned char buf[RFIFOW(fd,2)+1]; + for(p=8,j=0;p<RFIFOW(fd,2) && j<ACCOUNT_REG2_NUM;p+=36,j++){ + memcpy(str,RFIFOP(fd,p),32); + value=RFIFOL(fd,p+32); + sprintf(tmpsql,"DELETE FROM `global_reg_value` WHERE `type`='1' AND `account_id`='%d' AND `str`='%s';",acc,jstrescapecpy(temp_str,str)); + if(mysql_query(&mysql_handle, tmpsql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + sprintf(tmpsql,"INSERT INTO `global_reg_value` (`type`, `account_id`, `str`, `value`) VALUES ( 1 , '%d' , '%s' , '%d');", acc, jstrescapecpy(temp_str,str), value); + if(mysql_query(&mysql_handle, tmpsql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + } + + // Send to char + memcpy(WBUFP(buf,0),RFIFOP(fd,0),RFIFOW(fd,2)); + WBUFW(buf,0)=0x2729; + charif_sendallwos(fd,buf,WBUFW(buf,2)); + } + } + RFIFOSKIP(fd,RFIFOW(fd,2)); + //printf("login: save account_reg (from char)\n"); + break; + + case 0x272a: // Receiving of map-server via char-server a unban resquest (by Yor) + if (RFIFOREST(fd) < 6) + return 0; + { + int acc; + acc = RFIFOL(fd,2); + sprintf(tmpsql,"SELECT `ban_until` FROM `%s` WHERE `%s` = '%d'",login_db,login_db_account_id,acc); + if(mysql_query(&mysql_handle, tmpsql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + sql_res = mysql_store_result(&mysql_handle) ; + if (sql_res) { + sql_row = mysql_fetch_row(sql_res); //row fetching + } + if (atol(sql_row[0]) != 0) { + sprintf(tmpsql,"UPDATE `%s` SET `ban_until` = '0', `state`='0' WHERE `%s` = '%d'", login_db,login_db_account_id,acc); + //query + if(mysql_query(&mysql_handle, tmpsql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + break; + } + RFIFOSKIP(fd,6); + } + return 0; + + default: + printf("login: unknown packet %x! (from char).\n", RFIFOW(fd,0)); + session[fd]->eof = 1; + return 0; + } + } + + return 0; +} + +//Lan ip check added by Kashy +int lan_ip_check(unsigned char *p) { + int y; + int lancheck = 1; + int lancharip[4]; + + unsigned int k0, k1, k2, k3; + sscanf(lan_char_ip, "%d.%d.%d.%d", &k0, &k1, &k2, &k3); + lancharip[0] = k0; lancharip[1] = k1; lancharip[2] = k2; lancharip[3] = k3; + + for(y = 0; y < 4; y++) { + if ((lancharip[y] & subnetmaski[y])!= (p[y])) + lancheck = 0; + break; } + + printf("LAN check: %s.\n", (lancheck) ? "\033[1;32mLAN\033[0m" : "\033[1;31mWAN\033[0m"); + return lancheck; +} + +//---------------------------------------------------------------------------------------- +// Default packet parsing (normal players or administation/char-server connection requests) +//---------------------------------------------------------------------------------------- +int parse_login(int fd) { + //int len; + + MYSQL_RES* sql_res ; + MYSQL_ROW sql_row = NULL; + + char t_uid[100]; + //int sql_fields, sql_cnt; + struct mmo_account account; + + int result, i; + unsigned char *p = (unsigned char *) &session[fd]->client_addr.sin_addr; + char ip[16]; + + sprintf(ip, "%d.%d.%d.%d", p[0], p[1], p[2], p[3]); + + if (ipban > 0) { + //ip ban + //p[0], p[1], p[2], p[3] + //request DB connection + //check + sprintf(tmpsql, "SELECT count(*) FROM `ipbanlist` WHERE `list` = '%d.*.*.*' OR `list` = '%d.%d.*.*' OR `list` = '%d.%d.%d.*' OR `list` = '%d.%d.%d.%d'", + p[0], p[0], p[1], p[0], p[1], p[2], p[0], p[1], p[2], p[3]); + if (mysql_query(&mysql_handle, tmpsql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + + sql_res = mysql_store_result(&mysql_handle) ; + sql_row = mysql_fetch_row(sql_res); //row fetching + + if (atoi(sql_row[0]) >0) { + // ip ban ok. + printf ("packet from banned ip : %d.%d.%d.%d" RETCODE, p[0], p[1], p[2], p[3]); + sprintf(tmpsql,"INSERT INTO `%s`(`time`,`ip`,`user`,`rcode`,`log`) VALUES (NOW(), '%d.%d.%d.%d', 'unknown','-3', 'ip banned')", loginlog_db, p[0], p[1], p[2], p[3]); + + // query + if(mysql_query(&mysql_handle, tmpsql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + printf ("close session connection...\n"); + + // close connection + session[fd]->eof = 1; + + } else { + printf ("packet from ip (ban check ok) : %d.%d.%d.%d" RETCODE, p[0], p[1], p[2], p[3]); + } + mysql_free_result(sql_res); + } + + if (session[fd]->eof) { + for(i = 0; i < MAX_SERVERS; i++) + if (server_fd[i] == fd) + server_fd[i] = -1; + close(fd); + delete_session(fd); + return 0; + } + + while(RFIFOREST(fd)>=2){ + printf("parse_login : %d %d packet case=%x\n", fd, RFIFOREST(fd), RFIFOW(fd,0)); + + switch(RFIFOW(fd,0)){ + case 0x200: // New alive packet: structure: 0x200 <account.userid>.24B. used to verify if client is always alive. + if (RFIFOREST(fd) < 26) + return 0; + RFIFOSKIP(fd,26); + break; + + case 0x204: // New alive packet: structure: 0x204 <encrypted.account.userid>.16B. (new ragexe from 22 june 2004) + if (RFIFOREST(fd) < 18) + return 0; + RFIFOSKIP(fd,18); + break; + + case 0x64: // request client login + case 0x01dd: // request client login with encrypt + if(RFIFOREST(fd)< ((RFIFOW(fd, 0) ==0x64)?55:47)) + return 0; + + printf("client connection request %s from %d.%d.%d.%d\n", RFIFOP(fd, 6), p[0], p[1], p[2], p[3]); + + account.userid = RFIFOP(fd, 6); + account.passwd = RFIFOP(fd, 30); +#ifdef PASSWORDENC + account.passwdenc= (RFIFOW(fd,0)==0x64)?0:PASSWORDENC; +#else + account.passwdenc=0; +#endif + result=mmo_auth(&account, fd); + + jstrescapecpy(t_uid,RFIFOP(fd, 6)); + if(result==-1){ + int gm_level = isGM(account.account_id); + if (min_level_to_connect > gm_level) { + WFIFOW(fd,0) = 0x81; + WFIFOL(fd,2) = 1; // 01 = Server closed + WFIFOSET(fd,3); + } else { + if (p[0] != 127) { + sprintf(tmpsql,"INSERT INTO `%s`(`time`,`ip`,`user`,`rcode`,`log`) VALUES (NOW(), '%d.%d.%d.%d', '%s','100', 'login ok')", loginlog_db, p[0], p[1], p[2], p[3], t_uid); + //query + if(mysql_query(&mysql_handle, tmpsql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + } + if (gm_level) + printf("Connection of the GM (level:%d) account '%s' accepted.\n", gm_level, account.userid); + else + printf("Connection of the account '%s' accepted.\n", account.userid); + server_num=0; + for(i = 0; i < MAX_SERVERS; i++) { + if (server_fd[i] >= 0) { + //Lan check added by Kashy + if (lan_ip_check(p)) + WFIFOL(fd,47+server_num*32) = inet_addr(lan_char_ip); + else + WFIFOL(fd,47+server_num*32) = server[i].ip; + WFIFOW(fd,47+server_num*32+4) = server[i].port; + memcpy(WFIFOP(fd,47+server_num*32+6), server[i].name, 20); + WFIFOW(fd,47+server_num*32+26) = server[i].users; + WFIFOW(fd,47+server_num*32+28) = server[i].maintenance; + WFIFOW(fd,47+server_num*32+30) = server[i].new; + server_num++; + } + } + // if at least 1 char-server + if (server_num > 0) { + WFIFOW(fd,0)=0x69; + WFIFOW(fd,2)=47+32*server_num; + WFIFOL(fd,4)=account.login_id1; + WFIFOL(fd,8)=account.account_id; + WFIFOL(fd,12)=account.login_id2; + WFIFOL(fd,16)=0; + memcpy(WFIFOP(fd,20),account.lastlogin,24); + WFIFOB(fd,46)=account.sex; + WFIFOSET(fd,47+32*server_num); + if(auth_fifo_pos>=AUTH_FIFO_SIZE) + auth_fifo_pos=0; + auth_fifo[auth_fifo_pos].account_id=account.account_id; + auth_fifo[auth_fifo_pos].login_id1=account.login_id1; + auth_fifo[auth_fifo_pos].login_id2=account.login_id2; + auth_fifo[auth_fifo_pos].sex=account.sex; + auth_fifo[auth_fifo_pos].delflag=0; + auth_fifo[auth_fifo_pos].ip = session[fd]->client_addr.sin_addr.s_addr; + auth_fifo_pos++; + } else { + WFIFOW(fd,0) = 0x81; + WFIFOL(fd,2) = 1; // 01 = Server closed + WFIFOSET(fd,3); + } + } + } else { + char tmp_sql[512]; + char error[64]; + sprintf(tmp_sql,"INSERT INTO `%s`(`time`,`ip`,`user`,`rcode`,`log`) VALUES (NOW(), '%d.%d.%d.%d', '%s', '%d','login failed : %%s')", loginlog_db, p[0], p[1], p[2], p[3], t_uid, result); + switch((result + 1)) { + case -2: //-3 = Account Banned + sprintf(tmpsql,tmp_sql,"Account banned."); + sprintf(error,"Account banned."); + break; + case -1: //-2 = Dynamic Ban + sprintf(tmpsql,tmp_sql,"dynamic ban (ip and account)."); + sprintf(error,"dynamic ban (ip and account)."); + break; + case 1: // 0 = Unregistered ID + sprintf(tmpsql,tmp_sql,"Unregisterd ID."); + sprintf(error,"Unregisterd ID."); + break; + case 2: // 1 = Incorrect Password + sprintf(tmpsql,tmp_sql,"Incorrect Password."); + sprintf(error,"Incorrect Password."); + break; + case 3: // 2 = This ID is expired + sprintf(tmpsql,tmp_sql,"Account Expired."); + sprintf(error,"Account Expired."); + break; + case 4: // 3 = Rejected from Server + sprintf(tmpsql,tmp_sql,"Rejected from server."); + sprintf(error,"Rejected from server."); + break; + case 5: // 4 = You have been blocked by the GM Team + sprintf(tmpsql,tmp_sql,"Blocked by GM."); + sprintf(error,"Blocked by GM."); + break; + case 6: // 5 = Your Game's EXE file is not the latest version + sprintf(tmpsql,tmp_sql,"Not latest game EXE."); + sprintf(error,"Not latest game EXE."); + break; + case 7: // 6 = Your are Prohibited to log in until %s + sprintf(tmpsql,tmp_sql,"Banned."); + sprintf(error,"Banned."); + break; + case 8: // 7 = Server is jammed due to over populated + sprintf(tmpsql,tmp_sql,"Server Over-population."); + sprintf(error,"Server Over-population."); + break; + case 9: // 8 = No MSG (actually, all states after 9 except 99 are No MSG, use only this) + sprintf(tmpsql,tmp_sql," "); + sprintf(error," "); + break; + case 100: // 99 = This ID has been totally erased + sprintf(tmpsql,tmp_sql,"Account gone."); + sprintf(error,"Account gone."); + break; + default: + sprintf(tmpsql,tmp_sql,"Uknown Error."); + sprintf(error,"Uknown Error."); + break; + } + //query + if(mysql_query(&mysql_handle, tmpsql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + if ((result == 1) && (dynamic_pass_failure_ban != 0)){ // failed password + sprintf(tmpsql,"SELECT count(*) FROM `%s` WHERE `ip` = '%d.%d.%d.%d' AND `rcode` = '1' AND `time` > NOW() - INTERVAL %d MINUTE", + loginlog_db, p[0], p[1], p[2], p[3], dynamic_pass_failure_ban_time); //how many times filed account? in one ip. + if(mysql_query(&mysql_handle, tmpsql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + //check query result + sql_res = mysql_store_result(&mysql_handle) ; + sql_row = mysql_fetch_row(sql_res); //row fetching + + if (atoi(sql_row[0]) >= dynamic_pass_failure_ban_how_many ) { + sprintf(tmpsql,"INSERT INTO `ipbanlist`(`list`,`btime`,`rtime`,`reason`) VALUES ('%d.%d.%d.*', NOW() , NOW() + INTERVAL %d MINUTE ,'Password error ban: %s')", p[0], p[1], p[2], dynamic_pass_failure_ban_how_long, t_uid); + if(mysql_query(&mysql_handle, tmpsql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + } + mysql_free_result(sql_res); + } + else if (result == -2){ //dynamic banned - add ip to ban list. + sprintf(tmpsql,"INSERT INTO `ipbanlist`(`list`,`btime`,`rtime`,`reason`) VALUES ('%d.%d.%d.*', NOW() , NOW() + INTERVAL 1 MONTH ,'Dynamic banned user id : %s')", p[0], p[1], p[2], t_uid); + if(mysql_query(&mysql_handle, tmpsql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + result = -3; + } + + sprintf(tmpsql,"SELECT `ban_until` FROM `%s` WHERE `%s` = '%s'",login_db,login_db_userid, t_uid); + if(mysql_query(&mysql_handle, tmpsql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + sql_res = mysql_store_result(&mysql_handle) ; + if (sql_res) { + sql_row = mysql_fetch_row(sql_res); //row fetching + } + //cannot connect login failed + memset(WFIFOP(fd,0),'\0',23); + WFIFOW(fd,0)=0x6a; + WFIFOB(fd,2)=result; + if (result == 6) { // 6 = Your are Prohibited to log in until %s + if (atol(sql_row[0]) != 0) { // if account is banned, we send ban timestamp + char tmpstr[256]; + time_t ban_until_time; + ban_until_time = atol(sql_row[0]); + strftime(tmpstr, 20, date_format, localtime(&ban_until_time)); + tmpstr[19] = '\0'; + memcpy(WFIFOP(fd,3), tmpstr, 20); + } else { // we send error message + memcpy(WFIFOP(fd,3), error, 20); + } + } + WFIFOSET(fd,23); + } + RFIFOSKIP(fd,(RFIFOW(fd,0)==0x64)?55:47); + break; + + case 0x01db: // request password key + if (session[fd]->session_data) { + printf("login: abnormal request of MD5 key (already opened session).\n"); + session[fd]->eof = 1; + return 0; + } + printf("Request Password key -%s\n",md5key); + RFIFOSKIP(fd,2); + WFIFOW(fd,0)=0x01dc; + WFIFOW(fd,2)=4+md5keylen; + memcpy(WFIFOP(fd,4),md5key,md5keylen); + WFIFOSET(fd,WFIFOW(fd,2)); + break; + + case 0x2710: // request Char-server connection + if(RFIFOREST(fd)<86) + return 0; + { + sprintf(tmpsql,"INSERT INTO `%s`(`time`,`ip`,`user`,`rcode`,`log`) VALUES (NOW(), '%d.%d.%d.%d', '%s@%s','100', 'charserver - %s@%d.%d.%d.%d:%d')", loginlog_db, p[0], p[1], p[2], p[3], RFIFOP(fd, 2),RFIFOP(fd, 60),RFIFOP(fd, 60), RFIFOB(fd, 54), RFIFOB(fd, 55), RFIFOB(fd, 56), RFIFOB(fd, 57), RFIFOW(fd, 58)); + + //query + if(mysql_query(&mysql_handle, tmpsql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + printf("server connection request %s @ %d.%d.%d.%d:%d (%d.%d.%d.%d)\n", + RFIFOP(fd, 60), RFIFOB(fd, 54), RFIFOB(fd, 55), RFIFOB(fd, 56), RFIFOB(fd, 57), RFIFOW(fd, 58), + p[0], p[1], p[2], p[3]); + unsigned char* server_name; + account.userid = RFIFOP(fd, 2); + account.passwd = RFIFOP(fd, 26); + account.passwdenc = 0; + server_name = RFIFOP(fd,60); + result = mmo_auth(&account, fd); + //printf("Result: %d - Sex: %d - Account ID: %d\n",result,account.sex,(int) account.account_id); + + if(result == -1 && account.sex==2 && account.account_id<MAX_SERVERS && server_fd[account.account_id]==-1){ + printf("Connection of the char-server '%s' accepted.\n", server_name); + memset(&server[account.account_id], 0, sizeof(struct mmo_char_server)); + server[account.account_id].ip=RFIFOL(fd,54); + server[account.account_id].port=RFIFOW(fd,58); + memcpy(server[account.account_id].name,RFIFOP(fd,60),20); + server[account.account_id].users=0; + server[account.account_id].maintenance=RFIFOW(fd,82); + server[account.account_id].new=RFIFOW(fd,84); + server_fd[account.account_id]=fd; + if(anti_freeze_enable) + server_freezeflag[account.account_id] = 5; // Char-server anti-freeze system. Counter. 5 ok, 4...0 freezed + sprintf(tmpsql,"DELETE FROM `sstatus` WHERE `index`='%ld'", account.account_id); + //query + if(mysql_query(&mysql_handle, tmpsql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + + jstrescapecpy(t_uid,server[account.account_id].name); + sprintf(tmpsql,"INSERT INTO `sstatus`(`index`,`name`,`user`) VALUES ( '%ld', '%s', '%d')", + account.account_id, server[account.account_id].name,0); + //query + if(mysql_query(&mysql_handle, tmpsql)) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + WFIFOW(fd,0)=0x2711; + WFIFOB(fd,2)=0; + WFIFOSET(fd,3); + session[fd]->func_parse=parse_fromchar; + realloc_fifo(fd,FIFOSIZE_SERVERLINK,FIFOSIZE_SERVERLINK); + } else { + WFIFOW(fd, 0) =0x2711; + WFIFOB(fd, 2)=3; + WFIFOSET(fd, 3); + } + } + RFIFOSKIP(fd, 86); + return 0; + + case 0x7530: // request Athena information + WFIFOW(fd,0)=0x7531; + WFIFOB(fd,2)=ATHENA_MAJOR_VERSION; + WFIFOB(fd,3)=ATHENA_MINOR_VERSION; + WFIFOB(fd,4)=ATHENA_REVISION; + WFIFOB(fd,5)=ATHENA_RELEASE_FLAG; + WFIFOB(fd,6)=ATHENA_OFFICIAL_FLAG; + WFIFOB(fd,7)=ATHENA_SERVER_LOGIN; + WFIFOW(fd,8)=ATHENA_MOD_VERSION; + WFIFOSET(fd,10); + RFIFOSKIP(fd,2); + printf ("Athena version check...\n"); + break; + + case 0x7532: + default: + printf ("End of connection (ip: %s)" RETCODE, ip); + session[fd]->eof = 1; + return 0; + } + } + + return 0; +} + +//------------------------------------------------- +// Return numerical value of a switch configuration +// on/off, english, fran軋is, deutsch, espaol +//------------------------------------------------- +int config_switch(const char *str) { + if (strcmpi(str, "on") == 0 || strcmpi(str, "yes") == 0 || strcmpi(str, "oui") == 0 || strcmpi(str, "ja") == 0 || strcmpi(str, "si") == 0) + return 1; + if (strcmpi(str, "off") == 0 || strcmpi(str, "no") == 0 || strcmpi(str, "non") == 0 || strcmpi(str, "nein") == 0) + return 0; + + return atoi(str); +} + + +//Lan Support conf reading added by Kashy +int login_lan_config_read(const char *lancfgName){ + int i; + char subnetmask[128]; + char line[1024], w1[1024], w2[1024]; + FILE *fp; + + fp=fopen(lancfgName, "r"); + + if (fp == NULL) { + printf("file not found: %s\n", lancfgName); + return 1; + } + printf("Start reading of Lan Support configuration file\n"); + while(fgets(line, sizeof(line)-1, fp)){ + if (line[0] == '/' && line[1] == '/') + continue; + + i = sscanf(line,"%[^:]: %[^\r\n]",w1,w2); + if(i!=2) + continue; + + else if(strcmpi(w1,"lan_char_ip")==0){ + strcpy(lan_char_ip, w2); + printf ("set Lan_Char_IP : %s\n",w2); + } + + else if(strcmpi(w1,"subnetmask")==0){ + strcpy(subnetmask, w2); + unsigned int k0, k1, k2, k3; + sscanf(subnetmask, "%d.%d.%d.%d", &k0, &k1, &k2, &k3); + subnetmaski[0] = k0; subnetmaski[1] = k1; subnetmaski[2] = k2; subnetmaski[3] = k3; + printf ("set subnetmask : %s\n",w2); + } + } + fclose(fp); + + { + unsigned int a0, a1, a2, a3; + unsigned char p[4]; + sscanf(lan_char_ip, "%d.%d.%d.%d", &a0, &a1, &a2, &a3); + p[0] = a0; p[1] = a1; p[2] = a2; p[3] = a3; + printf("LAN test of LAN IP of the char-server: "); + if (lan_ip_check(p) == 0) { + printf("\033[1;31m***ERROR: LAN IP of the char-server doesn't belong to the specified Sub-network\033[0m\n"); + } + } + + printf("End reading of Lan Support configuration file\n"); + + return 0; +} + +//----------------------------------------------------- +//BANNED IP CHECK. +//----------------------------------------------------- +int ip_ban_check(int tid, unsigned int tick, int id, int data){ + + //query + if(mysql_query(&mysql_handle, "DELETE FROM `ipbanlist` WHERE `rtime` <= NOW()")) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle)); + } + + return 0; +} + +//----------------------------------------------------- +// reading configuration +//----------------------------------------------------- +int login_config_read(const char *cfgName){ + int i; + char line[1024], w1[1024], w2[1024]; + FILE *fp; + + fp=fopen(cfgName,"r"); + + if(fp==NULL){ + printf("Configuration file (%s) not found.\n", cfgName); + return 1; + } + printf ("start reading configuration...\n"); + while(fgets(line, sizeof(line)-1, fp)){ + if(line[0] == '/' && line[1] == '/') + continue; + + i=sscanf(line,"%[^:]: %[^\r\n]",w1,w2); + if(i!=2) + continue; + + else if(strcmpi(w1,"login_port")==0){ + login_port=atoi(w2); + printf ("set login_port : %s\n",w2); + } + else if(strcmpi(w1,"ipban")==0){ + ipban=atoi(w2); + printf ("set ipban : %d\n",ipban); + } + //account ban -> ip ban + else if(strcmpi(w1,"dynamic_account_ban")==0){ + dynamic_account_ban=atoi(w2); + printf ("set dynamic_account_ban : %d\n",dynamic_account_ban); + } + else if(strcmpi(w1,"dynamic_account_ban_class")==0){ + dynamic_account_ban_class=atoi(w2); + printf ("set dynamic_account_ban_class : %d\n",dynamic_account_ban_class); + } + //dynamic password error ban + else if(strcmpi(w1,"dynamic_pass_failure_ban")==0){ + dynamic_pass_failure_ban=atoi(w2); + printf ("set dynamic_pass_failure_ban : %d\n",dynamic_pass_failure_ban); + } + else if(strcmpi(w1,"dynamic_pass_failure_ban_time")==0){ + dynamic_pass_failure_ban_time=atoi(w2); + printf ("set dynamic_pass_failure_ban_time : %d\n",dynamic_pass_failure_ban_time); + } + else if(strcmpi(w1,"dynamic_pass_failure_ban_how_many")==0){ + dynamic_pass_failure_ban_how_many=atoi(w2); + printf ("set dynamic_pass_failure_ban_how_many : %d\n",dynamic_pass_failure_ban_how_many); + } + else if(strcmpi(w1,"dynamic_pass_failure_ban_how_long")==0){ + dynamic_pass_failure_ban_how_long=atoi(w2); + printf ("set dynamic_pass_failure_ban_how_long : %d\n",dynamic_pass_failure_ban_how_long); + } + else if(strcmpi(w1,"anti_freeze_enable")==0){ + anti_freeze_enable = config_switch(w2); + } + else if (strcmpi(w1, "anti_freeze_interval") == 0) { + ANTI_FREEZE_INTERVAL = atoi(w2); + if (ANTI_FREEZE_INTERVAL < 5) + ANTI_FREEZE_INTERVAL = 5; // minimum 5 seconds + } + else if (strcmpi(w1, "import") == 0) { + login_config_read(w2); + } + else if(strcmpi(w1,"use_MD5_passwords")==0){ + if (!strcmpi(w2,"yes")) { + use_md5_passwds=1; + } else if (!strcmpi(w2,"no")){ + use_md5_passwds=0; + } + printf ("Using MD5 Passwords: %s \n",w2); + } + else if (strcmpi(w1, "date_format") == 0) { // note: never have more than 19 char for the date! + switch (atoi(w2)) { + case 0: + strcpy(date_format, "%d-%m-%Y %H:%M:%S"); // 31-12-2004 23:59:59 + break; + case 1: + strcpy(date_format, "%m-%d-%Y %H:%M:%S"); // 12-31-2004 23:59:59 + break; + case 2: + strcpy(date_format, "%Y-%d-%m %H:%M:%S"); // 2004-31-12 23:59:59 + break; + case 3: + strcpy(date_format, "%Y-%m-%d %H:%M:%S"); // 2004-12-31 23:59:59 + break; + } + } + else if (strcmpi(w1, "min_level_to_connect") == 0) { + min_level_to_connect = atoi(w2); + } + else if (strcmpi(w1, "check_ip_flag") == 0) { + check_ip_flag = config_switch(w2); + } + } + fclose(fp); + printf ("End reading configuration...\n"); + return 0; +} + +void sql_config_read(const char *cfgName){ /* Kalaspuff, to get login_db */ + int i; + char line[1024], w1[1024], w2[1024]; + printf("reading configure: %s\n", cfgName); + FILE *fp=fopen(cfgName,"r"); + if(fp==NULL){ + printf("file not found: %s\n",cfgName); + exit(1); + } + while(fgets(line, sizeof(line)-1, fp)){ + if(line[0] == '/' && line[1] == '/') + continue; + i=sscanf(line,"%[^:]: %[^\r\n]",w1,w2); + if(i!=2) + continue; + if (strcmpi(w1, "login_db") == 0) { + strcpy(login_db, w2); + } + //add for DB connection + else if(strcmpi(w1,"login_server_ip")==0){ + strcpy(login_server_ip, w2); + printf ("set login_server_ip : %s\n",w2); + } + else if(strcmpi(w1,"login_server_port")==0){ + login_server_port=atoi(w2); + printf ("set login_server_port : %s\n",w2); + } + else if(strcmpi(w1,"login_server_id")==0){ + strcpy(login_server_id, w2); + printf ("set login_server_id : %s\n",w2); + } + else if(strcmpi(w1,"login_server_pw")==0){ + strcpy(login_server_pw, w2); + printf ("set login_server_pw : %s\n",w2); + } + else if(strcmpi(w1,"login_server_db")==0){ + strcpy(login_server_db, w2); + printf ("set login_server_db : %s\n",w2); + } + //added for custom column names for custom login table + else if(strcmpi(w1,"login_db_account_id")==0){ + strcpy(login_db_account_id, w2); + } + else if(strcmpi(w1,"login_db_userid")==0){ + strcpy(login_db_userid, w2); + } + else if(strcmpi(w1,"login_db_user_pass")==0){ + strcpy(login_db_user_pass, w2); + } + else if(strcmpi(w1,"login_db_level")==0){ + strcpy(login_db_level, w2); + } + //end of custom table config + else if (strcmpi(w1, "loginlog_db") == 0) { + strcpy(loginlog_db, w2); + } + } + fclose(fp); + printf("reading configure done.....\n"); +} + +int do_init(int argc,char **argv){ + //initialize login server + int i; + + //read login configue + login_config_read( (argc>1)?argv[1]:LOGIN_CONF_NAME ); + sql_config_read(SQL_CONF_NAME); + login_lan_config_read((argc > 1) ? argv[1] : LAN_CONF_NAME); + //Generate Passworded Key. + printf ("memset md5key \n"); + memset(md5key, 0, sizeof(md5key)); + printf ("memset md5key complete\n"); + printf ("memset keyleng\n"); + md5keylen=rand()%4+12; + for(i=0;i<md5keylen;i++) + md5key[i]=rand()%255+1; + printf ("memset keyleng complete\n"); + + printf ("set FIFO Size\n"); + for(i=0;i<AUTH_FIFO_SIZE;i++) + auth_fifo[i].delflag=1; + printf ("set FIFO Size complete\n"); + + printf ("set max servers\n"); + for(i=0;i<MAX_SERVERS;i++) + server_fd[i]=-1; + printf ("set max servers complete\n"); + //server port open & binding + + login_fd=make_listen_port(login_port); + + //Auth start + printf ("Running mmo_auth_sqldb_init()\n"); + mmo_auth_sqldb_init(); + printf ("finished mmo_auth_sqldb_init()\n"); + //sync account when terminating. + //but no need when you using DBMS (mysql) + set_termfunc(mmo_db_close); + + //set default parser as parse_login function + set_defaultparse(parse_login); + + if(anti_freeze_enable > 0) { + add_timer_func_list(char_anti_freeze_system, "char_anti_freeze_system"); + i = add_timer_interval(gettick()+1000, char_anti_freeze_system, 0, 0, ANTI_FREEZE_INTERVAL * 1000); + } + + // ban deleter timer - 1 minute term + printf("add interval tic (ip_ban_check)....\n"); + i=add_timer_interval(gettick()+10, ip_ban_check,0,0,60*1000); + + printf("The login-server is \033[1;32mready\033[0m (Server is listening on the port %d).\n\n", login_port); + + return 0; +} + + diff --git a/src/login_sql/login.h b/src/login_sql/login.h new file mode 100644 index 0000000..6335168 --- /dev/null +++ b/src/login_sql/login.h @@ -0,0 +1,41 @@ +#ifndef _LOGIN_H_ +#define _LOGIN_H_ + +#define MAX_SERVERS 30 + +#define LOGIN_CONF_NAME "conf/login_athena.conf" +#define SQL_CONF_NAME "conf/inter_athena.conf" +#define LAN_CONF_NAME "conf/lan_support.conf" + +#define PASSWORDENC 3 // A definition is given when making an encryption password correspond. + // It is 1 at the time of passwordencrypt. + // It is made into 2 at the time of passwordencrypt2. + // When it is made 3, it corresponds to both. + +#define START_ACCOUNT_NUM 2000000 +#define END_ACCOUNT_NUM 100000000 + +struct mmo_account { + char* userid; + char* passwd; + int passwdenc; + + long account_id; + long login_id1; + long login_id2; + long char_id; + char lastlogin[24]; + int sex; +}; + +struct mmo_char_server { + char name[20]; + long ip; + short port; + int users; + int maintenance; + int new; +}; + + +#endif diff --git a/src/login_sql/make.sh b/src/login_sql/make.sh new file mode 100644 index 0000000..198d33b --- /dev/null +++ b/src/login_sql/make.sh @@ -0,0 +1,6 @@ +#!/bin/sh + rsqlt=`rm -rf *.o` + gcc -c login.c -I/usr/local/include/mysql/ + gcc -c md5calc.c -I/usr/local/include/mysql/ + gcc -c strlib.c + gcc -o login-server login.o strlib.o md5calc.o ../common/core.o ../common/socket.o ../common/timer.o ../common/db.o -L/usr/local/lib/mysql/ -lmysqlclient -lz diff --git a/src/login_sql/md5calc.c b/src/login_sql/md5calc.c new file mode 100644 index 0000000..58cea12 --- /dev/null +++ b/src/login_sql/md5calc.c @@ -0,0 +1,236 @@ +/*********************************************************** + * md5 calculation algorithm + * + * The source code referred to the following URL. + * http://www.geocities.co.jp/SiliconValley-Oakland/8878/lab17/lab17.html + * + ***********************************************************/ + +#include "md5calc.h" +#include <string.h> +#include <stdio.h> + +#ifndef UINT_MAX +#define UINT_MAX 4294967295U +#endif + +// Global variable +static unsigned int *pX; + +// Stirng Table +static const unsigned int T[] = { + 0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, //0 + 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501, //4 + 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, //8 + 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821, //12 + 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, //16 + 0xd62f105d, 0x2441453, 0xd8a1e681, 0xe7d3fbc8, //20 + 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, //24 + 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a, //28 + 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, //32 + 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70, //36 + 0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x4881d05, //40 + 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665, //44 + 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, //48 + 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1, //52 + 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, //56 + 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391 //60 +}; + +// ROTATE_LEFT The left is made to rotate x [ n-bit ]. This is diverted as it is from RFC. +#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32-(n)))) + +// The function used for other calculation +static unsigned int F(unsigned int X, unsigned int Y, unsigned int Z) +{ + return (X & Y) | (~X & Z); +} +static unsigned int G(unsigned int X, unsigned int Y, unsigned int Z) +{ + return (X & Z) | (Y & ~Z); +} +static unsigned int H(unsigned int X, unsigned int Y, unsigned int Z) +{ + return X ^ Y ^ Z; +} +static unsigned int I(unsigned int X, unsigned int Y, unsigned int Z) +{ + return Y ^ (X | ~Z); +} + +static unsigned int Round(unsigned int a, unsigned int b, unsigned int FGHI, + unsigned int k, unsigned int s, unsigned int i) +{ + return b + ROTATE_LEFT(a + FGHI + pX[k] + T[i], s); +} + +static void Round1(unsigned int *a, unsigned int b, unsigned int c, + unsigned int d,unsigned int k, unsigned int s, unsigned int i) +{ + *a = Round(*a, b, F(b,c,d), k, s, i); +} +static void Round2(unsigned int *a, unsigned int b, unsigned int c, + unsigned int d,unsigned int k, unsigned int s, unsigned int i) +{ + *a = Round(*a, b, G(b,c,d), k, s, i); +} +static void Round3(unsigned int *a, unsigned int b, unsigned int c, + unsigned int d,unsigned int k, unsigned int s, unsigned int i) +{ + *a = Round(*a, b, H(b,c,d), k, s, i); +} +static void Round4(unsigned int *a, unsigned int b, unsigned int c, + unsigned int d,unsigned int k, unsigned int s, unsigned int i) +{ + *a = Round(*a, b, I(b,c,d), k, s, i); +} + +static void MD5_Round_Calculate(const unsigned char *block, + unsigned int *A2, unsigned int *B2, unsigned int *C2, unsigned int *D2) +{ + //create X It is since it is required. + unsigned int X[16]; //512bit 64byte + int j,k; + + //Save A as AA, B as BB, C as CC, and and D as DD (saving of A, B, C, and D) + unsigned int A=*A2, B=*B2, C=*C2, D=*D2; + unsigned int AA = A,BB = B,CC = C,DD = D; + + //It is a large region variable reluctantly because of calculation of a round. . . for Round1...4 + pX = X; + + //Copy block(padding_message) i into X + for (j=0,k=0; j<64; j+=4,k++) + X[k] = ( (unsigned int )block[j] ) // 8byte*4 -> 32byte conversion + | ( ((unsigned int )block[j+1]) << 8 ) // A function called Decode as used in the field of RFC + | ( ((unsigned int )block[j+2]) << 16 ) + | ( ((unsigned int )block[j+3]) << 24 ); + + + //Round 1 + Round1(&A,B,C,D, 0, 7, 0); Round1(&D,A,B,C, 1, 12, 1); Round1(&C,D,A,B, 2, 17, 2); Round1(&B,C,D,A, 3, 22, 3); + Round1(&A,B,C,D, 4, 7, 4); Round1(&D,A,B,C, 5, 12, 5); Round1(&C,D,A,B, 6, 17, 6); Round1(&B,C,D,A, 7, 22, 7); + Round1(&A,B,C,D, 8, 7, 8); Round1(&D,A,B,C, 9, 12, 9); Round1(&C,D,A,B, 10, 17, 10); Round1(&B,C,D,A, 11, 22, 11); + Round1(&A,B,C,D, 12, 7, 12); Round1(&D,A,B,C, 13, 12, 13); Round1(&C,D,A,B, 14, 17, 14); Round1(&B,C,D,A, 15, 22, 15); + + //Round 2 + Round2(&A,B,C,D, 1, 5, 16); Round2(&D,A,B,C, 6, 9, 17); Round2(&C,D,A,B, 11, 14, 18); Round2(&B,C,D,A, 0, 20, 19); + Round2(&A,B,C,D, 5, 5, 20); Round2(&D,A,B,C, 10, 9, 21); Round2(&C,D,A,B, 15, 14, 22); Round2(&B,C,D,A, 4, 20, 23); + Round2(&A,B,C,D, 9, 5, 24); Round2(&D,A,B,C, 14, 9, 25); Round2(&C,D,A,B, 3, 14, 26); Round2(&B,C,D,A, 8, 20, 27); + Round2(&A,B,C,D, 13, 5, 28); Round2(&D,A,B,C, 2, 9, 29); Round2(&C,D,A,B, 7, 14, 30); Round2(&B,C,D,A, 12, 20, 31); + + //Round 3 + Round3(&A,B,C,D, 5, 4, 32); Round3(&D,A,B,C, 8, 11, 33); Round3(&C,D,A,B, 11, 16, 34); Round3(&B,C,D,A, 14, 23, 35); + Round3(&A,B,C,D, 1, 4, 36); Round3(&D,A,B,C, 4, 11, 37); Round3(&C,D,A,B, 7, 16, 38); Round3(&B,C,D,A, 10, 23, 39); + Round3(&A,B,C,D, 13, 4, 40); Round3(&D,A,B,C, 0, 11, 41); Round3(&C,D,A,B, 3, 16, 42); Round3(&B,C,D,A, 6, 23, 43); + Round3(&A,B,C,D, 9, 4, 44); Round3(&D,A,B,C, 12, 11, 45); Round3(&C,D,A,B, 15, 16, 46); Round3(&B,C,D,A, 2, 23, 47); + + //Round 4 + Round4(&A,B,C,D, 0, 6, 48); Round4(&D,A,B,C, 7, 10, 49); Round4(&C,D,A,B, 14, 15, 50); Round4(&B,C,D,A, 5, 21, 51); + Round4(&A,B,C,D, 12, 6, 52); Round4(&D,A,B,C, 3, 10, 53); Round4(&C,D,A,B, 10, 15, 54); Round4(&B,C,D,A, 1, 21, 55); + Round4(&A,B,C,D, 8, 6, 56); Round4(&D,A,B,C, 15, 10, 57); Round4(&C,D,A,B, 6, 15, 58); Round4(&B,C,D,A, 13, 21, 59); + Round4(&A,B,C,D, 4, 6, 60); Round4(&D,A,B,C, 11, 10, 61); Round4(&C,D,A,B, 2, 15, 62); Round4(&B,C,D,A, 9, 21, 63); + + // Then perform the following additions. (let's add) + *A2 = A + AA; + *B2 = B + BB; + *C2 = C + CC; + *D2 = D + DD; + + //The clearance of confidential information + memset(pX, 0, sizeof(X)); +} + +//------------------------------------------------------------------- +// The function for the exteriors + +/** output is the coded binary in the character sequence which wants to code string. */ +void MD5_String2binary(const char * string, char * output) +{ +//var + /*8bit*/ + unsigned char padding_message[64]; //Extended message 512bit 64byte + unsigned char *pstring; //The position of string in the present scanning notes is held. + +// unsigned char digest[16]; + /*32bit*/ + unsigned int string_byte_len, //The byte chief of string is held. + string_bit_len, //The bit length of string is held. + copy_len, //The number of bytes which is used by 1-3 and which remained + msg_digest[4]; //Message digest 128bit 4byte + unsigned int *A = &msg_digest[0], //The message digest in accordance with RFC (reference) + *B = &msg_digest[1], + *C = &msg_digest[2], + *D = &msg_digest[3]; + int i; + +//prog + //Step 3.Initialize MD Buffer (although it is the initialization; step 3 of A, B, C, and D -- unavoidable -- a head) + *A = 0x67452301; + *B = 0xefcdab89; + *C = 0x98badcfe; + *D = 0x10325476; + + //Step 1.Append Padding Bits (extension of a mark bit) + //1-1 + string_byte_len = strlen(string); //The byte chief of a character sequence is acquired. + pstring = (unsigned char *)string; //The position of the present character sequence is set. + + //1-2 Repeat calculation until length becomes less than 64 bytes. + for (i=string_byte_len; 64<=i; i-=64,pstring+=64) + MD5_Round_Calculate(pstring, A,B,C,D); + + //1-3 + copy_len = string_byte_len % 64; //The number of bytes which remained is computed. + strncpy((char *)padding_message, (char *)pstring, copy_len); //A message is copied to an extended bit sequence. + memset(padding_message+copy_len, 0, 64 - copy_len); //It buries by 0 until it becomes extended bit length. + padding_message[copy_len] |= 0x80; //The next of a message is 1. + + //1-4 + //If 56 bytes or more (less than 64 bytes) of remainder becomes, it will calculate by extending to 64 bytes. + if (56 <= copy_len) { + MD5_Round_Calculate(padding_message, A,B,C,D); + memset(padding_message, 0, 56); //56 bytes is newly fill uped with 0. + } + + + //Step 2.Append Length (the information on length is added) + string_bit_len = string_byte_len * 8; //From the byte chief to bit length (32 bytes of low rank) + memcpy(&padding_message[56], &string_bit_len, 4); //32 bytes of low rank is set. + + //When bit length cannot be expressed in 32 bytes of low rank, it is a beam raising to a higher rank. + if (UINT_MAX / 8 < string_byte_len) { + unsigned int high = (string_byte_len - UINT_MAX / 8) * 8; + memcpy(&padding_message[60], &high, 4); + } else + memset(&padding_message[60], 0, 4); //In this case, it is good for a higher rank at 0. + + //Step 4.Process Message in 16-Word Blocks (calculation of MD5) + MD5_Round_Calculate(padding_message, A,B,C,D); + + + //Step 5.Output (output) + memcpy(output,msg_digest,16); +// memcpy (digest, msg_digest, and 16); //8 byte*4 < - 32byte conversion A function called Encode as used in the field of RFC +/* sprintf(output, + "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", + digest[ 0], digest[ 1], digest[ 2], digest[ 3], + digest[ 4], digest[ 5], digest[ 6], digest[ 7], + digest[ 8], digest[ 9], digest[10], digest[11], + digest[12], digest[13], digest[14], digest[15]);*/ +} + +/** output is the coded character sequence in the character sequence which wants to code string. */ +void MD5_String(const char * string, char * output) +{ + unsigned char digest[16]; + + MD5_String2binary(string,digest); + sprintf(output, + "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", + digest[ 0], digest[ 1], digest[ 2], digest[ 3], + digest[ 4], digest[ 5], digest[ 6], digest[ 7], + digest[ 8], digest[ 9], digest[10], digest[11], + digest[12], digest[13], digest[14], digest[15]); +} + diff --git a/src/login_sql/md5calc.h b/src/login_sql/md5calc.h new file mode 100644 index 0000000..9bc554f --- /dev/null +++ b/src/login_sql/md5calc.h @@ -0,0 +1,7 @@ +#ifndef _MD5CALC_H_ +#define _MD5CALC_H_ + +void MD5_String(const char * string, char * output); +void MD5_String2binary(const char * string, char * output); + +#endif diff --git a/src/login_sql/readme.txt b/src/login_sql/readme.txt new file mode 100644 index 0000000..b945af7 --- /dev/null +++ b/src/login_sql/readme.txt @@ -0,0 +1,120 @@ +サソ// Encoded UTF-8 +//--------------------------------------------- +// 007 - by Jazz +1. 0590 official version縺ォ蜷医o縺帙※菫ョ豁」縺励∪縺励◆. + +//--------------------------------------------- +// 006 - by Jazz +1. 繝代せ繝ッ繝シ繝画アコ縺セ縺」縺溷屓謨ー髢馴&縺譎りェ蜍 IP驕ョ譁ュ蜈キ迴セ. +2. login_athena.conf繧剃ソョ豁」 + +//--------------------------------------------- +// 005 - by Jazz +1. 險ア螳ケ縺励↑縺 ID縺梧磁霑代ョ譎りェ蜍輔〒 IP驕ョ譁ュ讖溯ス蜈キ迴セ. +2. 閾ェ蜍輔〒驕ョ譁ュ隗」髯、讖溯ス蜈キ迴セ. + +//--------------------------------------------- +// 004 - by Jazz +1. aphostropy 蝠城。後r隗」豎コ縺励∪縺励◆. 縺薙l縺ッ菫晏ョ峨→騾」髢「縺後≠繧句撫鬘後〒縺. +2. SQL縺ョ讒矩繧剃ソョ豁」縺励∪縺励◆. +3. server 迥カ諷九r SQL縺ァ蜃コ蜉. + +//--------------------------------------------- +// 003 - by Jazz +1. 謗・邯夊ィ倬鹸繧 DB縺ァ蜃コ蜉. +2. IP蝓コ逶、縺ョ謗・邯夐ョ譁ュ蜈キ迴セ. + +//--------------------------------------------- +// 002 - by Jazz +1. 縺縺上▽縺九ョ隧ウ邏ー蝠城。檎せ菫ョ豁」. + +//--------------------------------------------- +// 001 - by Jazz +1. 荳逡ェ逶ョ螳牙ョ version縺ァ縺. + + +// notice some.. +In this program, new parts are under BSD License. +and imported parts are under GPL as athena did. + +this program is not public license. but modification and editing are +unlimit permitted. but auther don't have any responsibility on that. + +this realase does not gurantee working perfectly. and, I would +answer neither that question nor technical one. + +I use... +P4 2.4Gh/512MB/WinXP/cygwin + +athena is under GPL license. +Ragnarok is Gravity's trademark. +login-db for athena is BSD license except code from original athena. +cygwin is Redhat's trademark. + +// About compile. +You need mysqlclient library to compile this. You can get this by +compiling mysql source. + +"-lmysqlclient" option needed when you did compile. + +some patch need on mysql when you did compile under cygwin. + +// ェーァ 簿ウエ乱 劇葺流 + +擽 売。懋キク棹乱 ヨ。 ァ誤豆牟ァ カカ捩 BSD 攵擽┥侃 符椈乱 ーー小 姓笈共. +キクヲャウ import 頗 カカ捩 寳椈 athena攪 GPL 攵擽┥侃 符椈乱 梭慣笈共. + +擽 売。懋キク棹捩 public licenseー 符漁笈共. 葺ァァ ウイス, ー懍。ー, 們菩捩 +ャエ懦復 来圸据ゥー キク乱 劇復 ア桷捩 梵ー ァァ 賦慣笈共. + +椪 擽 ヲエヲャヲ壱株 刋イス梭 徐梠物揆 ウエ棗葺ァ 賦慣笈共. キクヲャウ キク乱 劇復 +メ橋 戦復 壱劇 ー幗ァ 賦慣笈共. + +クー唖攤 カカ攪 メ橋捩 ー幗ァ 賦慣笈共. + +ー罹ー 劍イス +P4 2.4Gh/512MB/WinXP/cygwin + +athena株 GPL 攵擽┥侃・シ 、 鮒笈共. +Ragnarok株 Gravity攪 trademark桿笈共. +login-db for athena株 athena乱 ーク乖 カカ揆 懍匣葺ゥエ BSD 攵擽┥侃・シ 伐ヲ笈共. +cygwin捩 Redhat攪 trademark桿笈共. + +// サエ血攵乱 劇葺流. + +擽 売。懋キク棹揆 サエ血攵葺クー 怱葺流 mysqlclient乱 劇復 libraryー 符囈鮒笈共. +擽株 mysql攪 護侃・シ サエ血攵葺ゥエ 冥揆 梭慣笈共. + +サエ血攵亨乱 -lmysqlclient 亰們擽 符囈鮒笈共. + +cygwin攪 イス垈株 ェーァ 肩ケ俾ー 符囈鮒笈共. + +// 縺縺上▽縺九ョ諠蝣ア縺ォ蟇セ縺励※ + +縺薙ョ繝励Ο繧ー繝ゥ繝縺ァ譁ー縺溘↓菴懊i繧後◆驛ィ蛻縺ッ BSD 繝ゥ繧、繧サ繝ウ繧ケ縺ョ荳九↓驟榊ク縺ォ縺ェ繧翫∪縺. +縺昴@縺ヲ import 縺ォ縺ェ縺」縺滄Κ蛻縺ッ蜈縲 athena縺ョ GPL 繝ゥ繧、繧サ繝ウ繧ケ縺ョ荳九↓縺ゅj縺セ縺. + +縺薙ョ繝励Ο繧ー繝ゥ繝縺ッ public license縺ァ縺ッ縺ェ縺縺ァ縺. 縺励°縺怜、画峩, 謾ケ騾, 菫ョ豁」縺ッ +辟。蛻カ髯占ィア螳ケ縺輔l縺ヲ縺昴l縺ォ蟇セ縺吶k雋ャ莉サ縺ッ闡苓縺瑚イ縺代↑縺縺ァ縺. + +迴セ蝨ィ縺薙ョ繝ェ繝ェ繝シ繧ケ縺ッ螳悟」√↓蜍穂ス懊☆繧九%縺ィ繧剃ソ晞囿縺励↑縺縺ァ縺. 縺昴@縺ヲ蠖シ縺ォ螟ァ髻 +逶ク隲繧らオカ蟇セ蜿励¢縺ェ縺縺ァ縺. + +謚陦鍋噪縺ェ驛ィ蛻縺ョ逶ク隲縺ッ蜿励¢縺ェ縺縺ァ縺. + +髢狗匱迺ー蠅 +P4 2.4Gh/512MB/WinXP/cygwin + +athena縺ッ GPL 繝ゥ繧、繧サ繝ウ繧ケ繧貞ョ医j縺セ縺. +Ragnarok縺ッ Gravity縺ョ trademark縺ァ縺. +login-db for athena縺ッ athena縺九i謖√▲縺ヲ譚・縺滄Κ蛻繧帝勁縺代ー BSD 繝ゥ繧、繧サ繝ウ繧ケ縺ォ莉倥″縺セ縺. +cygwin縺ッ Redhat縺ョ trademark縺ァ縺. + +// 繧ウ繝ウ繝代う繝ォ縺ォ蟇セ縺励※. + +縺薙ョ繝励Ο繧ー繝ゥ繝繧偵さ繝ウ繝代う繝ォ縺吶k縺溘a縺ォ mysqlclient縺ォ蟇セ縺吶k library縺悟ソ隕√〒縺. +縺薙l縺ッ mysql縺ョ繧ス繝シ繧ケ繧偵さ繝ウ繝代う繝ォ縺吶l縺ー蠕励k縺薙→縺後〒縺阪∪縺. + +繧ウ繝ウ繝代う繝ォ縺ョ譎ゅ↓ -lmysqlclient 繧ェ繝励す繝ァ繝ウ縺悟ソ隕√〒縺. + +cygwin縺ョ蝣エ蜷医ッ縺縺上▽縺九ョ繝代ャ繝√′蠢隕√〒縺.
\ No newline at end of file diff --git a/src/login_sql/strlib.c b/src/login_sql/strlib.c new file mode 100644 index 0000000..7f6e197 --- /dev/null +++ b/src/login_sql/strlib.c @@ -0,0 +1,58 @@ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "strlib.h" +#include "utils.h" + +//----------------------------------------------- +// string lib. +unsigned char* jstrescape (unsigned char* pt) { + //copy from here + unsigned char * ptr; + int i =0, j=0; + + //copy string to temporary + CREATE(ptr, char, J_MAX_MALLOC_SIZE); + strcpy (ptr,pt); + + while (ptr[i] != '\0') { + switch (ptr[i]) { + case '\'': + pt[j++] = '\\'; + pt[j++] = ptr[i++]; + break; + case '\\': + pt[j++] = '\\'; + pt[j++] = ptr[i++]; + break; + default: + pt[j++] = ptr[i++]; + } + } + pt[j++] = '\0'; + free (ptr); + return (unsigned char*) &pt[0]; +} + +unsigned char* jstrescapecpy (unsigned char* pt,unsigned char* spt) { + //copy from here + int i =0, j=0; + + while (spt[i] != '\0') { + switch (spt[i]) { + case '\'': + pt[j++] = '\\'; + pt[j++] = spt[i++]; + break; + case '\\': + pt[j++] = '\\'; + pt[j++] = spt[i++]; + break; + default: + pt[j++] = spt[i++]; + } + } + pt[j++] = '\0'; + return (unsigned char*) &pt[0]; +} diff --git a/src/login_sql/strlib.h b/src/login_sql/strlib.h new file mode 100644 index 0000000..ddf29ab --- /dev/null +++ b/src/login_sql/strlib.h @@ -0,0 +1,9 @@ +#ifndef _J_STR_LIB_H_ +#define _J_STR_LIB_H_ +#define J_MAX_MALLOC_SIZE 65535 +// String function library. +// code by Jioh L. Jung (ziozzang@4wish.net) +// This code is under license "BSD" +unsigned char* jstrescape (unsigned char* pt); +unsigned char* jstrescapecpy (unsigned char* pt,unsigned char* spt); +#endif diff --git a/src/login_sql/timer.h b/src/login_sql/timer.h new file mode 100644 index 0000000..be9706a --- /dev/null +++ b/src/login_sql/timer.h @@ -0,0 +1,43 @@ +// original : core.h 2003/03/14 11:55:25 Rev 1.4 + +#ifndef _TIMER_H_ +#define _TIMER_H_ + +#define BASE_TICK 5 + +#define TIMER_ONCE_AUTODEL 1 +#define TIMER_INTERVAL 2 +#define TIMER_REMOVE_HEAP 16 + +#define DIFF_TICK(a,b) ((int)((a)-(b))) + +// Struct declaration + +struct TimerData { + unsigned int tick; + int (*func)(int,unsigned int,int,int); + int id; + int data; + int type; + int interval; + int heap_pos; +}; + +// Function prototype declaration + +unsigned int gettick_nocache(void); +unsigned int gettick(void); + +int add_timer(unsigned int,int (*)(int,unsigned int,int,int),int,int); +int add_timer_interval(unsigned int,int (*)(int,unsigned int,int,int),int,int,int); +int delete_timer(int,int (*)(int,unsigned int,int,int)); + +int addtick_timer(int tid,unsigned int tick); +struct TimerData *get_timer(int tid); + +int do_timer(unsigned int tick); + +int add_timer_func_list(int (*)(int,unsigned int,int,int),char*); +char* search_timer_func_list(int (*)(int,unsigned int,int,int)); + +#endif // _TIMER_H_ diff --git a/src/map/GNUmakefile b/src/map/GNUmakefile new file mode 100644 index 0000000..852bbb9 --- /dev/null +++ b/src/map/GNUmakefile @@ -0,0 +1,72 @@ +all: txt sql
+
+txt: txtobj map-server
+
+sql: sqlobj map-server_sql
+
+txtobj:
+ mkdir txtobj
+
+sqlobj:
+ mkdir sqlobj
+
+COMMON_OBJ = ../common/core.o ../common/socket.o ../common/timer.o ../common/grfio.o ../common/db.o ../common/lock.o ../common/nullpo.o ../common/malloc.o
+LIBS = -lz -lm
+
+map-server: txtobj/map.o txtobj/chrif.o txtobj/clif.o txtobj/pc.o txtobj/npc.o txtobj/chat.o txtobj/path.o txtobj/itemdb.o txtobj/mob.o txtobj/script.o txtobj/storage.o txtobj/skill.o txtobj/atcommand.o txtobj/battle.o txtobj/intif.o txtobj/trade.o txtobj/party.o txtobj/vending.o txtobj/guild.o txtobj/pet.o $(COMMON_OBJ)
+ $(CC) -o ../../$@ $^ $(LIBS)
+
+map-server_sql: sqlobj/map.o sqlobj/chrif.o sqlobj/clif.o sqlobj/pc.o sqlobj/npc.o sqlobj/chat.o sqlobj/path.o sqlobj/itemdb.o sqlobj/mob.o sqlobj/script.o sqlobj/storage.o sqlobj/skill.o sqlobj/atcommand.o sqlobj/battle.o sqlobj/intif.o sqlobj/trade.o sqlobj/party.o sqlobj/vending.o sqlobj/guild.o sqlobj/pet.o sqlobj/mail.o $(COMMON_OBJ)
+ $(CC) -o ../../$@ $^ $(LIB_S)
+
+txtobj/%.o: %.c
+ $(COMPILE.c) -DTXT_ONLY $(OUTPUT_OPTION) $<
+
+sqlobj/%.o: %.c
+ $(COMPILE.c) $(OUTPUT_OPTION) $<
+
+txtobj/map.o: map.c map.h chrif.h clif.h npc.h pc.h mob.h chat.h skill.h itemdb.h storage.h party.h pet.h atcommand.h ../common/core.h ../common/timer.h ../common/db.h ../common/grfio.h ../common/mmo.h
+txtobj/chrif.o: chrif.c map.h battle.h chrif.h clif.h intif.h pc.h npc.h ../common/socket.h ../common/timer.h ../common/mmo.h
+txtobj/clif.o: clif.c map.h chrif.h clif.h mob.h intif.h pc.h npc.h itemdb.h chat.h script.h storage.h party.h guild.h atcommand.h pet.h atcommand.h ../common/socket.h ../common/timer.h ../common/mmo.h ../common/version.h
+txtobj/pc.o: pc.c map.h clif.h intif.h pc.h npc.h mob.h itemdb.h battle.h skill.h script.h party.h guild.h pet.h trade.h storage.h chat.h vending.h ../common/timer.h ../common/mmo.h ../common/db.h
+txtobj/npc.o: npc.c map.h npc.h clif.h pc.h script.h mob.h itemdb.h battle.h ../common/db.h ../common/timer.h ../common/mmo.h
+txtobj/chat.o: chat.c map.h clif.h pc.h chat.h ../common/db.h ../common/mmo.h
+txtobj/path.o: path.c map.h battle.h ../common/mmo.h
+txtobj/itemdb.o: itemdb.c map.h battle.h itemdb.h ../common/db.h ../common/grfio.h ../common/mmo.h
+txtobj/mob.o: mob.c map.h clif.h intif.h pc.h mob.h skill.h battle.h npc.h itemdb.h ../common/timer.h ../common/socket.h ../common/mmo.h
+txtobj/script.o: script.c itemdb.h map.h pc.h mob.h clif.h intif.h npc.h script.h storage.h skill.h pet.h battle.h ../common/timer.h ../common/socket.h ../common/db.h ../common/mmo.h ../common/lock.h
+txtobj/storage.o: storage.c itemdb.h pc.h clif.h intif.h storage.h guild.h ../common/mmo.h ../common/db.h
+txtobj/skill.o: skill.c skill.h map.h clif.h pc.h mob.h battle.h itemdb.h script.h ../common/timer.h ../common/mmo.h
+txtobj/atcommand.o: atcommand.c atcommand.h itemdb.h pc.h map.h skill.h clif.h mob.h intif.h battle.h storage.h guild.h pet.h ../common/socket.h ../common/timer.h ../common/mmo.h
+txtobj/battle.o: battle.c battle.h skill.h map.h mob.h pc.h pet.h guild.h ../common/timer.h ../common/mmo.h
+txtobj/intif.o: intif.c intif.h chrif.h clif.h party.h guild.h storage.h map.h battle.h pet.h ../common/socket.h ../common/mmo.h
+txtobj/trade.o: trade.c trade.h clif.h itemdb.h map.h pc.h npc.h ../common/mmo.h
+txtobj/party.o: party.c party.h clif.h intif.h pc.h map.h battle.h ../common/db.h ../common/socket.h ../common/timer.h ../common/mmo.h
+txtobj/vending.o: vending.c vending.h clif.h itemdb.h map.h pc.h ../common/mmo.h
+txtobj/guild.o: guild.c guild.h storage.h battle.h clif.h intif.h pc.h npc.h map.h ../common/db.h ../common/socket.h ../common/timer.h ../common/mmo.h
+txtobj/pet.o: pet.c pet.h map.h clif.h chrif.h intif.h pc.h itemdb.h battle.h mob.h npc.h script.h ../common/db.h ../common/socket.h ../common/timer.h ../common/mmo.h
+
+sqlobj/map.o: map.c map.h chrif.h clif.h npc.h pc.h mob.h chat.h skill.h itemdb.h storage.h party.h pet.h atcommand.h ../common/core.h ../common/timer.h ../common/db.h ../common/grfio.h ../common/mmo.h
+sqlobj/chrif.o: chrif.c map.h battle.h chrif.h clif.h intif.h pc.h npc.h ../common/socket.h ../common/timer.h ../common/mmo.h
+sqlobj/clif.o: clif.c map.h chrif.h clif.h mob.h intif.h pc.h npc.h itemdb.h chat.h script.h storage.h party.h guild.h atcommand.h pet.h atcommand.h ../common/socket.h ../common/timer.h ../common/mmo.h ../common/version.h
+sqlobj/pc.o: pc.c map.h clif.h intif.h pc.h npc.h mob.h itemdb.h battle.h skill.h script.h party.h guild.h pet.h trade.h storage.h chat.h vending.h ../common/timer.h ../common/mmo.h ../common/db.h
+sqlobj/npc.o: npc.c map.h npc.h clif.h pc.h script.h mob.h itemdb.h battle.h ../common/db.h ../common/timer.h ../common/mmo.h
+sqlobj/chat.o: chat.c map.h clif.h pc.h chat.h ../common/db.h ../common/mmo.h
+sqlobj/path.o: path.c map.h battle.h ../common/mmo.h
+sqlobj/itemdb.o: itemdb.c map.h battle.h itemdb.h ../common/db.h ../common/grfio.h ../common/mmo.h
+sqlobj/mob.o: mob.c map.h clif.h intif.h pc.h mob.h skill.h battle.h npc.h itemdb.h ../common/timer.h ../common/socket.h ../common/mmo.h
+sqlobj/script.o: script.c itemdb.h map.h pc.h mob.h clif.h intif.h npc.h script.h storage.h skill.h pet.h battle.h ../common/timer.h ../common/socket.h ../common/db.h ../common/mmo.h ../common/lock.h
+sqlobj/storage.o: storage.c itemdb.h pc.h clif.h intif.h storage.h guild.h ../common/mmo.h ../common/db.h
+sqlobj/skill.o: skill.c skill.h map.h clif.h pc.h mob.h battle.h itemdb.h script.h ../common/timer.h ../common/mmo.h
+sqlobj/atcommand.o: atcommand.c atcommand.h itemdb.h pc.h map.h skill.h clif.h mob.h intif.h battle.h storage.h guild.h pet.h ../common/socket.h ../common/timer.h ../common/mmo.h
+sqlobj/battle.o: battle.c battle.h skill.h map.h mob.h pc.h pet.h guild.h ../common/timer.h ../common/mmo.h
+sqlobj/intif.o: intif.c intif.h chrif.h clif.h party.h guild.h storage.h map.h battle.h pet.h ../common/socket.h ../common/mmo.h
+sqlobj/trade.o: trade.c trade.h clif.h itemdb.h map.h pc.h npc.h ../common/mmo.h
+sqlobj/party.o: party.c party.h clif.h intif.h pc.h map.h battle.h ../common/db.h ../common/socket.h ../common/timer.h ../common/mmo.h
+sqlobj/vending.o: vending.c vending.h clif.h itemdb.h map.h pc.h ../common/mmo.h
+sqlobj/guild.o: guild.c guild.h storage.h battle.h clif.h intif.h pc.h npc.h map.h ../common/db.h ../common/socket.h ../common/timer.h ../common/mmo.h
+sqlobj/pet.o: pet.c pet.h map.h clif.h chrif.h intif.h pc.h itemdb.h battle.h mob.h npc.h script.h ../common/db.h ../common/socket.h ../common/timer.h ../common/mmo.h
+sqlobj/mail.o: mail.c mail.h
+
+clean:
+ rm -rf *.o ../../map-server ../../map-server_sql sqlobj txtobj
diff --git a/src/map/Makefile b/src/map/Makefile new file mode 100644 index 0000000..3f5396e --- /dev/null +++ b/src/map/Makefile @@ -0,0 +1,72 @@ +all: txt sql
+
+txt: txtobj map-server
+
+sql: sqlobj map-server_sql
+
+txtobj:
+ mkdir txtobj
+
+sqlobj:
+ mkdir sqlobj
+
+COMMON_OBJ = ../common/core.o ../common/socket.o ../common/timer.o ../common/grfio.o ../common/db.o ../common/lock.o ../common/nullpo.o ../common/malloc.o
+LIBS = -lz -lm
+
+map-server: txtobj/map.o txtobj/chrif.o txtobj/clif.o txtobj/pc.o txtobj/npc.o txtobj/chat.o txtobj/path.o txtobj/itemdb.o txtobj/mob.o txtobj/script.o txtobj/storage.o txtobj/skill.o txtobj/atcommand.o txtobj/battle.o txtobj/intif.o txtobj/trade.o txtobj/party.o txtobj/vending.o txtobj/guild.o txtobj/pet.o $(COMMON_OBJ)
+ $(CC) -o ../../$@ $> $(LIBS)
+
+map-server_sql: sqlobj/map.o sqlobj/chrif.o sqlobj/clif.o sqlobj/pc.o sqlobj/npc.o sqlobj/chat.o sqlobj/path.o sqlobj/itemdb.o sqlobj/mob.o sqlobj/script.o sqlobj/storage.o sqlobj/skill.o sqlobj/atcommand.o sqlobj/battle.o sqlobj/intif.o sqlobj/trade.o sqlobj/party.o sqlobj/vending.o sqlobj/guild.o sqlobj/pet.o sqlobj/mail.o $(COMMON_OBJ)
+ $(CC) -o ../../$@ $> $(LIB_S)
+
+txtobj/%.o: %.c
+ $(COMPILE.c) -DTXT_ONLY $(OUTPUT_OPTION) $<
+
+sqlobj/%.o: %.c
+ $(COMPILE.c) $(OUTPUT_OPTION) $<
+
+txtobj/map.o: map.c map.h chrif.h clif.h npc.h pc.h mob.h chat.h skill.h itemdb.h storage.h party.h pet.h atcommand.h ../common/core.h ../common/timer.h ../common/db.h ../common/grfio.h ../common/mmo.h
+txtobj/chrif.o: chrif.c map.h battle.h chrif.h clif.h intif.h pc.h npc.h ../common/socket.h ../common/timer.h ../common/mmo.h
+txtobj/clif.o: clif.c map.h chrif.h clif.h mob.h intif.h pc.h npc.h itemdb.h chat.h script.h storage.h party.h guild.h atcommand.h pet.h atcommand.h ../common/socket.h ../common/timer.h ../common/mmo.h ../common/version.h
+txtobj/pc.o: pc.c map.h clif.h intif.h pc.h npc.h mob.h itemdb.h battle.h skill.h script.h party.h guild.h pet.h trade.h storage.h chat.h vending.h ../common/timer.h ../common/mmo.h ../common/db.h
+txtobj/npc.o: npc.c map.h npc.h clif.h pc.h script.h mob.h itemdb.h battle.h ../common/db.h ../common/timer.h ../common/mmo.h
+txtobj/chat.o: chat.c map.h clif.h pc.h chat.h ../common/db.h ../common/mmo.h
+txtobj/path.o: path.c map.h battle.h ../common/mmo.h
+txtobj/itemdb.o: itemdb.c map.h battle.h itemdb.h ../common/db.h ../common/grfio.h ../common/mmo.h
+txtobj/mob.o: mob.c map.h clif.h intif.h pc.h mob.h skill.h battle.h npc.h itemdb.h ../common/timer.h ../common/socket.h ../common/mmo.h
+txtobj/script.o: script.c itemdb.h map.h pc.h mob.h clif.h intif.h npc.h script.h storage.h skill.h pet.h battle.h ../common/timer.h ../common/socket.h ../common/db.h ../common/mmo.h ../common/lock.h
+txtobj/storage.o: storage.c itemdb.h pc.h clif.h intif.h storage.h guild.h ../common/mmo.h ../common/db.h
+txtobj/skill.o: skill.c skill.h map.h clif.h pc.h mob.h battle.h itemdb.h script.h ../common/timer.h ../common/mmo.h
+txtobj/atcommand.o: atcommand.c atcommand.h itemdb.h pc.h map.h skill.h clif.h mob.h intif.h battle.h storage.h guild.h pet.h ../common/socket.h ../common/timer.h ../common/mmo.h
+txtobj/battle.o: battle.c battle.h skill.h map.h mob.h pc.h pet.h guild.h ../common/timer.h ../common/mmo.h
+txtobj/intif.o: intif.c intif.h chrif.h clif.h party.h guild.h storage.h map.h battle.h pet.h ../common/socket.h ../common/mmo.h
+txtobj/trade.o: trade.c trade.h clif.h itemdb.h map.h pc.h npc.h ../common/mmo.h
+txtobj/party.o: party.c party.h clif.h intif.h pc.h map.h battle.h ../common/db.h ../common/socket.h ../common/timer.h ../common/mmo.h
+txtobj/vending.o: vending.c vending.h clif.h itemdb.h map.h pc.h ../common/mmo.h
+txtobj/guild.o: guild.c guild.h storage.h battle.h clif.h intif.h pc.h npc.h map.h ../common/db.h ../common/socket.h ../common/timer.h ../common/mmo.h
+txtobj/pet.o: pet.c pet.h map.h clif.h chrif.h intif.h pc.h itemdb.h battle.h mob.h npc.h script.h ../common/db.h ../common/socket.h ../common/timer.h ../common/mmo.h
+
+sqlobj/map.o: map.c map.h chrif.h clif.h npc.h pc.h mob.h chat.h skill.h itemdb.h storage.h party.h pet.h atcommand.h ../common/core.h ../common/timer.h ../common/db.h ../common/grfio.h ../common/mmo.h
+sqlobj/chrif.o: chrif.c map.h battle.h chrif.h clif.h intif.h pc.h npc.h ../common/socket.h ../common/timer.h ../common/mmo.h
+sqlobj/clif.o: clif.c map.h chrif.h clif.h mob.h intif.h pc.h npc.h itemdb.h chat.h script.h storage.h party.h guild.h atcommand.h pet.h atcommand.h ../common/socket.h ../common/timer.h ../common/mmo.h ../common/version.h
+sqlobj/pc.o: pc.c map.h clif.h intif.h pc.h npc.h mob.h itemdb.h battle.h skill.h script.h party.h guild.h pet.h trade.h storage.h chat.h vending.h ../common/timer.h ../common/mmo.h ../common/db.h
+sqlobj/npc.o: npc.c map.h npc.h clif.h pc.h script.h mob.h itemdb.h battle.h ../common/db.h ../common/timer.h ../common/mmo.h
+sqlobj/chat.o: chat.c map.h clif.h pc.h chat.h ../common/db.h ../common/mmo.h
+sqlobj/path.o: path.c map.h battle.h ../common/mmo.h
+sqlobj/itemdb.o: itemdb.c map.h battle.h itemdb.h ../common/db.h ../common/grfio.h ../common/mmo.h
+sqlobj/mob.o: mob.c map.h clif.h intif.h pc.h mob.h skill.h battle.h npc.h itemdb.h ../common/timer.h ../common/socket.h ../common/mmo.h
+sqlobj/script.o: script.c itemdb.h map.h pc.h mob.h clif.h intif.h npc.h script.h storage.h skill.h pet.h battle.h ../common/timer.h ../common/socket.h ../common/db.h ../common/mmo.h ../common/lock.h
+sqlobj/storage.o: storage.c itemdb.h pc.h clif.h intif.h storage.h guild.h ../common/mmo.h ../common/db.h
+sqlobj/skill.o: skill.c skill.h map.h clif.h pc.h mob.h battle.h itemdb.h script.h ../common/timer.h ../common/mmo.h
+sqlobj/atcommand.o: atcommand.c atcommand.h itemdb.h pc.h map.h skill.h clif.h mob.h intif.h battle.h storage.h guild.h pet.h ../common/socket.h ../common/timer.h ../common/mmo.h
+sqlobj/battle.o: battle.c battle.h skill.h map.h mob.h pc.h pet.h guild.h ../common/timer.h ../common/mmo.h
+sqlobj/intif.o: intif.c intif.h chrif.h clif.h party.h guild.h storage.h map.h battle.h pet.h ../common/socket.h ../common/mmo.h
+sqlobj/trade.o: trade.c trade.h clif.h itemdb.h map.h pc.h npc.h ../common/mmo.h
+sqlobj/party.o: party.c party.h clif.h intif.h pc.h map.h battle.h ../common/db.h ../common/socket.h ../common/timer.h ../common/mmo.h
+sqlobj/vending.o: vending.c vending.h clif.h itemdb.h map.h pc.h ../common/mmo.h
+sqlobj/guild.o: guild.c guild.h storage.h battle.h clif.h intif.h pc.h npc.h map.h ../common/db.h ../common/socket.h ../common/timer.h ../common/mmo.h
+sqlobj/pet.o: pet.c pet.h map.h clif.h chrif.h intif.h pc.h itemdb.h battle.h mob.h npc.h script.h ../common/db.h ../common/socket.h ../common/timer.h ../common/mmo.h
+sqlobj/mail.o: mail.c mail.h
+
+clean:
+ rm -rf *.o ../../map-server ../../map-server_sql sqlobj txtobj
diff --git a/src/map/atcommand.c b/src/map/atcommand.c new file mode 100644 index 0000000..8dc39ba --- /dev/null +++ b/src/map/atcommand.c @@ -0,0 +1,7753 @@ +// $Id: atcommand.c 148 2004-09-30 14:05:37Z MouseJstr $ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include <math.h> + +#include "socket.h" +#include "timer.h" +#include "nullpo.h" + +#include "clif.h" +#include "chrif.h" +#include "intif.h" +#include "itemdb.h" +#include "map.h" +#include "pc.h" +#include "skill.h" +#include "mob.h" +#include "pet.h" +#include "battle.h" +#include "party.h" +#include "guild.h" +#include "atcommand.h" +#include "script.h" +#include "npc.h" +#include "trade.h" +#include "core.h" + +#ifndef TXT_ONLY +#include "mail.h" +#endif + +#define STATE_BLIND 0x10 + +static char command_symbol = '@'; // first char of the commands (by [Yor]) + +static char msg_table[1000][1024]; // Server messages (0-499 reserved for GM commands, 500-999 reserved for others) + +#define ATCOMMAND_FUNC(x) int atcommand_ ## x (const int fd, struct map_session_data* sd, const char* command, const char* message) +ATCOMMAND_FUNC(broadcast); +ATCOMMAND_FUNC(localbroadcast); +ATCOMMAND_FUNC(rurap); +ATCOMMAND_FUNC(rura); +ATCOMMAND_FUNC(where); +ATCOMMAND_FUNC(jumpto); +ATCOMMAND_FUNC(jump); +ATCOMMAND_FUNC(who); +ATCOMMAND_FUNC(who2); +ATCOMMAND_FUNC(who3); +ATCOMMAND_FUNC(whomap); +ATCOMMAND_FUNC(whomap2); +ATCOMMAND_FUNC(whomap3); +ATCOMMAND_FUNC(whogm); // by Yor +ATCOMMAND_FUNC(save); +ATCOMMAND_FUNC(load); +ATCOMMAND_FUNC(speed); +ATCOMMAND_FUNC(storage); +ATCOMMAND_FUNC(guildstorage); +ATCOMMAND_FUNC(option); +ATCOMMAND_FUNC(hide); +ATCOMMAND_FUNC(jobchange); +ATCOMMAND_FUNC(die); +ATCOMMAND_FUNC(kill); +ATCOMMAND_FUNC(alive); +ATCOMMAND_FUNC(kami); +ATCOMMAND_FUNC(heal); +ATCOMMAND_FUNC(item); +ATCOMMAND_FUNC(item2); +ATCOMMAND_FUNC(itemreset); +ATCOMMAND_FUNC(itemcheck); +ATCOMMAND_FUNC(baselevelup); +ATCOMMAND_FUNC(joblevelup); +ATCOMMAND_FUNC(help); +ATCOMMAND_FUNC(gm); +ATCOMMAND_FUNC(pvpoff); +ATCOMMAND_FUNC(pvpon); +ATCOMMAND_FUNC(gvgoff); +ATCOMMAND_FUNC(gvgon); +ATCOMMAND_FUNC(model); +ATCOMMAND_FUNC(go); +ATCOMMAND_FUNC(monster); +ATCOMMAND_FUNC(spawn); +ATCOMMAND_FUNC(killmonster); +ATCOMMAND_FUNC(killmonster2); +ATCOMMAND_FUNC(refine); +ATCOMMAND_FUNC(produce); +ATCOMMAND_FUNC(memo); +ATCOMMAND_FUNC(gat); +ATCOMMAND_FUNC(packet); +ATCOMMAND_FUNC(statuspoint); +ATCOMMAND_FUNC(skillpoint); +ATCOMMAND_FUNC(zeny); +ATCOMMAND_FUNC(param); +ATCOMMAND_FUNC(guildlevelup); +ATCOMMAND_FUNC(makeegg); +ATCOMMAND_FUNC(hatch); +ATCOMMAND_FUNC(petfriendly); +ATCOMMAND_FUNC(pethungry); +ATCOMMAND_FUNC(petrename); +ATCOMMAND_FUNC(charpetrename); // by Yor +ATCOMMAND_FUNC(recall); +ATCOMMAND_FUNC(recallall); +ATCOMMAND_FUNC(character_job); +ATCOMMAND_FUNC(revive); +ATCOMMAND_FUNC(character_stats); +ATCOMMAND_FUNC(character_stats_all); +ATCOMMAND_FUNC(character_option); +ATCOMMAND_FUNC(character_save); +ATCOMMAND_FUNC(night); +ATCOMMAND_FUNC(day); +ATCOMMAND_FUNC(doom); +ATCOMMAND_FUNC(doommap); +ATCOMMAND_FUNC(raise); +ATCOMMAND_FUNC(raisemap); +ATCOMMAND_FUNC(character_baselevel); +ATCOMMAND_FUNC(character_joblevel); +ATCOMMAND_FUNC(kick); +ATCOMMAND_FUNC(kickall); +ATCOMMAND_FUNC(allskill); +ATCOMMAND_FUNC(questskill); +ATCOMMAND_FUNC(charquestskill); +ATCOMMAND_FUNC(lostskill); +ATCOMMAND_FUNC(charlostskill); +ATCOMMAND_FUNC(spiritball); +ATCOMMAND_FUNC(party); +ATCOMMAND_FUNC(guild); +ATCOMMAND_FUNC(charskreset); +ATCOMMAND_FUNC(charstreset); +ATCOMMAND_FUNC(charreset); +ATCOMMAND_FUNC(charstpoint); +ATCOMMAND_FUNC(charmodel); +ATCOMMAND_FUNC(charskpoint); +ATCOMMAND_FUNC(charzeny); +ATCOMMAND_FUNC(agitstart); +ATCOMMAND_FUNC(agitend); +ATCOMMAND_FUNC(reloaditemdb); +ATCOMMAND_FUNC(reloadmobdb); +ATCOMMAND_FUNC(reloadskilldb); +#ifndef TXT_ONLY +ATCOMMAND_FUNC(rehash);// by Fr3DBr +#else /* TXT_ONLY */ +ATCOMMAND_FUNC(reloadscript); +#endif /* TXT_ONLY */ +ATCOMMAND_FUNC(reloadgmdb); // by Yor +ATCOMMAND_FUNC(mapexit); +ATCOMMAND_FUNC(idsearch); +ATCOMMAND_FUNC(mapinfo); +ATCOMMAND_FUNC(dye); //** by fritz +ATCOMMAND_FUNC(hair_style); //** by fritz +ATCOMMAND_FUNC(hair_color); //** by fritz +ATCOMMAND_FUNC(stat_all); //** by fritz +ATCOMMAND_FUNC(char_change_sex); // by Yor +ATCOMMAND_FUNC(char_block); // by Yor +ATCOMMAND_FUNC(char_ban); // by Yor +ATCOMMAND_FUNC(char_unblock); // by Yor +ATCOMMAND_FUNC(char_unban); // by Yor +ATCOMMAND_FUNC(mount_peco); // by Valaris +ATCOMMAND_FUNC(char_mount_peco); // by Yor +ATCOMMAND_FUNC(guildspy); // [Syrus22] +ATCOMMAND_FUNC(partyspy); // [Syrus22] +ATCOMMAND_FUNC(repairall); // [Valaris] +ATCOMMAND_FUNC(guildrecall); // by Yor +ATCOMMAND_FUNC(partyrecall); // by Yor +//ATCOMMAND_FUNC(nuke); // [Valaris] +ATCOMMAND_FUNC(enablenpc); +ATCOMMAND_FUNC(disablenpc); +ATCOMMAND_FUNC(servertime); // by Yor +ATCOMMAND_FUNC(chardelitem); // by Yor +ATCOMMAND_FUNC(jail); // by Yor +ATCOMMAND_FUNC(unjail); // by Yor +ATCOMMAND_FUNC(disguise); // [Valaris] +ATCOMMAND_FUNC(undisguise); // by Yor +ATCOMMAND_FUNC(ignorelist); // by Yor +ATCOMMAND_FUNC(charignorelist); // by Yor +ATCOMMAND_FUNC(inall); // by Yor +ATCOMMAND_FUNC(exall); // by Yor +ATCOMMAND_FUNC(chardisguise); // Kalaspuff +ATCOMMAND_FUNC(charundisguise); // Kalaspuff +ATCOMMAND_FUNC(email); // by Yor +ATCOMMAND_FUNC(effect);//by Apple +ATCOMMAND_FUNC(character_item_list); // by Yor +ATCOMMAND_FUNC(character_storage_list); // by Yor +ATCOMMAND_FUNC(character_cart_list); // by Yor +ATCOMMAND_FUNC(addwarp); // by MouseJstr +ATCOMMAND_FUNC(follow); // by MouseJstr +ATCOMMAND_FUNC(skillon); // by MouseJstr +ATCOMMAND_FUNC(skilloff); // by MouseJstr +ATCOMMAND_FUNC(killer); // by MouseJstr +ATCOMMAND_FUNC(npcmove); // by MouseJstr +ATCOMMAND_FUNC(killable); // by MouseJstr +ATCOMMAND_FUNC(charkillable); // by MouseJstr +ATCOMMAND_FUNC(chareffect); // by MouseJstr +ATCOMMAND_FUNC(chardye); // by MouseJstr +ATCOMMAND_FUNC(charhairstyle); // by MouseJstr +ATCOMMAND_FUNC(charhaircolor); // by MouseJstr +ATCOMMAND_FUNC(dropall); // by MouseJstr +ATCOMMAND_FUNC(chardropall); // by MouseJstr +ATCOMMAND_FUNC(storeall); // by MouseJstr +ATCOMMAND_FUNC(charstoreall); // by MouseJstr +ATCOMMAND_FUNC(skillid); // by MouseJstr +ATCOMMAND_FUNC(useskill); // by MouseJstr +ATCOMMAND_FUNC(summon); +ATCOMMAND_FUNC(rain); +ATCOMMAND_FUNC(snow); +ATCOMMAND_FUNC(sakura); +ATCOMMAND_FUNC(fog); +ATCOMMAND_FUNC(leaves); +ATCOMMAND_FUNC(adjgmlvl); // by MouseJstr +ATCOMMAND_FUNC(adjcmdlvl); // by MouseJstr +ATCOMMAND_FUNC(trade); // by MouseJstr +ATCOMMAND_FUNC(unmute); // [Valaris] + +#ifndef TXT_ONLY +ATCOMMAND_FUNC(checkmail); // [Valaris] +ATCOMMAND_FUNC(listmail); // [Valaris] +ATCOMMAND_FUNC(listnewmail); // [Valaris] +ATCOMMAND_FUNC(readmail); // [Valaris] +ATCOMMAND_FUNC(sendmail); // [Valaris] +ATCOMMAND_FUNC(sendprioritymail); // [Valaris] +ATCOMMAND_FUNC(deletemail); // [Valaris] +ATCOMMAND_FUNC(sound); // [Valaris] +ATCOMMAND_FUNC(refreshonline); // [Valaris] +#endif /* TXT_ONLY */ + +/*========================================== + *AtCommandInfo atcommand_info[]構造体の定義 + *------------------------------------------ + */ + +// First char of commands is configured in atcommand_athena.conf. Leave @ in this list for default value. +// to set default level, read atcommand_athena.conf first please. +static AtCommandInfo atcommand_info[] = { + { AtCommand_RuraP, "@rura+", 60, atcommand_rurap }, + { AtCommand_RuraP, "@charwarp", 60, atcommand_rurap }, + { AtCommand_Rura, "@rura", 40, atcommand_rura }, + { AtCommand_Warp, "@warp", 40, atcommand_rura }, + { AtCommand_Where, "@where", 1, atcommand_where }, + { AtCommand_JumpTo, "@jumpto", 20, atcommand_jumpto }, // + /shift + { AtCommand_JumpTo, "@warpto", 20, atcommand_jumpto }, + { AtCommand_JumpTo, "@goto", 20, atcommand_jumpto }, + { AtCommand_Jump, "@jump", 40, atcommand_jump }, + { AtCommand_Who, "@who", 20, atcommand_who }, + { AtCommand_Who, "@whois", 20, atcommand_who }, + { AtCommand_Who2, "@who2", 20, atcommand_who2 }, + { AtCommand_Who3, "@who3", 20, atcommand_who3 }, + { AtCommand_WhoMap, "@whomap", 20, atcommand_whomap }, + { AtCommand_WhoMap2, "@whomap2", 20, atcommand_whomap2 }, + { AtCommand_WhoMap3, "@whomap3", 20, atcommand_whomap3 }, + { AtCommand_WhoGM, "@whogm", 20, atcommand_whogm }, // by Yor + { AtCommand_Save, "@save", 40, atcommand_save }, + { AtCommand_Load, "@return", 40, atcommand_load }, + { AtCommand_Load, "@load", 40, atcommand_load }, + { AtCommand_Speed, "@speed", 40, atcommand_speed }, + { AtCommand_Storage, "@storage", 1, atcommand_storage }, + { AtCommand_GuildStorage, "@gstorage", 50, atcommand_guildstorage }, + { AtCommand_Option, "@option", 40, atcommand_option }, + { AtCommand_Hide, "@hide", 40, atcommand_hide }, // + /hide + { AtCommand_JobChange, "@jobchange", 40, atcommand_jobchange }, + { AtCommand_JobChange, "@job", 40, atcommand_jobchange }, + { AtCommand_Die, "@die", 1, atcommand_die }, + { AtCommand_Kill, "@kill", 60, atcommand_kill }, + { AtCommand_Alive, "@alive", 60, atcommand_alive }, + { AtCommand_Kami, "@kami", 40, atcommand_kami }, + { AtCommand_KamiB, "@kamib", 40, atcommand_kami }, + { AtCommand_Heal, "@heal", 40, atcommand_heal }, + { AtCommand_Item, "@item", 60, atcommand_item }, + { AtCommand_Item2, "@item2", 60, atcommand_item2 }, + { AtCommand_ItemReset, "@itemreset", 40, atcommand_itemreset }, + { AtCommand_ItemCheck, "@itemcheck", 60, atcommand_itemcheck }, + { AtCommand_BaseLevelUp, "@lvup", 60, atcommand_baselevelup }, + { AtCommand_BaseLevelUp, "@blevel", 60, atcommand_baselevelup }, + { AtCommand_BaseLevelUp, "@baselvlup", 60, atcommand_baselevelup }, + { AtCommand_JobLevelUp, "@jlevel", 60, atcommand_joblevelup }, + { AtCommand_JobLevelUp, "@joblvup", 60, atcommand_joblevelup }, + { AtCommand_JobLevelUp, "@joblvlup", 60, atcommand_joblevelup }, + { AtCommand_H, "@h", 20, atcommand_help }, + { AtCommand_Help, "@help", 20, atcommand_help }, + { AtCommand_GM, "@gm", 100, atcommand_gm }, + { AtCommand_PvPOff, "@pvpoff", 40, atcommand_pvpoff }, + { AtCommand_PvPOn, "@pvpon", 40, atcommand_pvpon }, + { AtCommand_GvGOff, "@gvgoff", 40, atcommand_gvgoff }, + { AtCommand_GvGOff, "@gpvpoff", 40, atcommand_gvgoff }, + { AtCommand_GvGOn, "@gvgon", 40, atcommand_gvgon }, + { AtCommand_GvGOn, "@gpvpon", 40, atcommand_gvgon }, + { AtCommand_Model, "@model", 20, atcommand_model }, + { AtCommand_Go, "@go", 10, atcommand_go }, + { AtCommand_Spawn, "@monster", 50, atcommand_spawn }, + { AtCommand_Spawn, "@spawn", 50, atcommand_spawn }, + //{ AtCommand_Spawn, "@summon", 50, atcommand_spawn }, + { AtCommand_Monster, "@monster2", 50, atcommand_monster }, + { AtCommand_KillMonster, "@killmonster", 60, atcommand_killmonster }, + { AtCommand_KillMonster2, "@killmonster2", 40, atcommand_killmonster2 }, + { AtCommand_Refine, "@refine", 60, atcommand_refine }, + { AtCommand_Produce, "@produce", 60, atcommand_produce }, + { AtCommand_Memo, "@memo", 40, atcommand_memo }, + { AtCommand_GAT, "@gat", 99, atcommand_gat }, // debug function + { AtCommand_Packet, "@packet", 99, atcommand_packet }, // debug function + { AtCommand_StatusPoint, "@stpoint", 60, atcommand_statuspoint }, + { AtCommand_SkillPoint, "@skpoint", 60, atcommand_skillpoint }, + { AtCommand_Zeny, "@zeny", 60, atcommand_zeny }, + { AtCommand_Strength, "@str", 60, atcommand_param }, + { AtCommand_Agility, "@agi", 60, atcommand_param }, + { AtCommand_Vitality, "@vit", 60, atcommand_param }, + { AtCommand_Intelligence, "@int", 60, atcommand_param }, + { AtCommand_Dexterity, "@dex", 60, atcommand_param }, + { AtCommand_Luck, "@luk", 60, atcommand_param }, + { AtCommand_GuildLevelUp, "@guildlvup", 60, atcommand_guildlevelup }, + { AtCommand_GuildLevelUp, "@guildlvlup", 60, atcommand_guildlevelup }, + { AtCommand_MakeEgg, "@makeegg", 60, atcommand_makeegg }, + { AtCommand_Hatch, "@hatch", 60, atcommand_hatch }, + { AtCommand_PetFriendly, "@petfriendly", 40, atcommand_petfriendly }, + { AtCommand_PetHungry, "@pethungry", 40, atcommand_pethungry }, + { AtCommand_PetRename, "@petrename", 1, atcommand_petrename }, + { AtCommand_CharPetRename, "@charpetrename", 50, atcommand_charpetrename }, // by Yor + { AtCommand_Recall, "@recall", 60, atcommand_recall }, // + /recall + { AtCommand_CharacterJob, "@charjob", 60, atcommand_character_job }, + { AtCommand_CharacterJob, "@charjobchange", 60, atcommand_character_job }, + { AtCommand_Revive, "@revive", 60, atcommand_revive }, + { AtCommand_CharacterStats, "@charstats", 40, atcommand_character_stats }, + { AtCommand_CharacterStatsAll, "@charstatsall", 40, atcommand_character_stats_all }, + { AtCommand_CharacterOption, "@charoption", 60, atcommand_character_option }, + { AtCommand_CharacterSave, "@charsave", 60, atcommand_character_save }, + { AtCommand_Night, "@night", 80, atcommand_night }, + { AtCommand_Day, "@day", 80, atcommand_day }, + { AtCommand_Doom, "@doom", 80, atcommand_doom }, + { AtCommand_DoomMap, "@doommap", 80, atcommand_doommap }, + { AtCommand_Raise, "@raise", 80, atcommand_raise }, + { AtCommand_RaiseMap, "@raisemap", 80, atcommand_raisemap }, + { AtCommand_CharacterBaseLevel, "@charbaselvl", 60, atcommand_character_baselevel }, + { AtCommand_CharacterJobLevel, "@charjlvl", 60, atcommand_character_joblevel }, + { AtCommand_Kick, "@kick", 20, atcommand_kick }, // + right click menu for GM "(name) force to quit" + { AtCommand_KickAll, "@kickall", 99, atcommand_kickall }, + { AtCommand_AllSkill, "@allskill", 60, atcommand_allskill }, + { AtCommand_AllSkill, "@allskills", 60, atcommand_allskill }, + { AtCommand_AllSkill, "@skillall", 60, atcommand_allskill }, + { AtCommand_AllSkill, "@skillsall", 60, atcommand_allskill }, + { AtCommand_QuestSkill, "@questskill", 40, atcommand_questskill }, + { AtCommand_CharQuestSkill, "@charquestskill", 60, atcommand_charquestskill }, + { AtCommand_LostSkill, "@lostskill", 40, atcommand_lostskill }, + { AtCommand_CharLostSkill, "@charlostskill", 60, atcommand_charlostskill }, + { AtCommand_SpiritBall, "@spiritball", 40, atcommand_spiritball }, + { AtCommand_Party, "@party", 1, atcommand_party }, + { AtCommand_Guild, "@guild", 50, atcommand_guild }, + { AtCommand_AgitStart, "@agitstart", 60, atcommand_agitstart }, + { AtCommand_AgitEnd, "@agitend", 60, atcommand_agitend }, + { AtCommand_MapExit, "@mapexit", 99, atcommand_mapexit }, + { AtCommand_IDSearch, "@idsearch", 60, atcommand_idsearch }, + { AtCommand_MapMove, "@mapmove", 40, atcommand_rura }, // /mm command + { AtCommand_Broadcast, "@broadcast", 40, atcommand_broadcast }, // /b and /nb command + { AtCommand_LocalBroadcast, "@localbroadcast", 40, atcommand_localbroadcast }, // /lb and /nlb command + { AtCommand_RecallAll, "@recallall", 80, atcommand_recallall }, + { AtCommand_CharSkReset, "@charskreset", 60, atcommand_charskreset }, + { AtCommand_CharStReset, "@charstreset", 60, atcommand_charstreset }, + { AtCommand_ReloadItemDB, "@reloaditemdb", 99, atcommand_reloaditemdb }, // admin command + { AtCommand_ReloadMobDB, "@reloadmobdb", 99, atcommand_reloadmobdb }, // admin command + { AtCommand_ReloadSkillDB, "@reloadskilldb", 99, atcommand_reloadskilldb }, // admin command +#ifndef TXT_ONLY + { AtCommand_Rehash, "@rehash", 99, atcommand_rehash }, // admin command +#else /* TXT_ONLY */ + { AtCommand_ReloadScript, "@reloadscript", 99, atcommand_reloadscript }, // admin command +#endif /* TXT_ONLY */ + { AtCommand_ReloadGMDB, "@reloadgmdb", 99, atcommand_reloadgmdb }, // admin command + { AtCommand_CharReset, "@charreset", 60, atcommand_charreset }, + { AtCommand_CharModel, "@charmodel", 50, atcommand_charmodel }, + { AtCommand_CharSKPoint, "@charskpoint", 60, atcommand_charskpoint }, + { AtCommand_CharSTPoint, "@charstpoint", 60, atcommand_charstpoint }, + { AtCommand_CharZeny, "@charzeny", 60, atcommand_charzeny }, + { AtCommand_MapInfo, "@mapinfo", 99, atcommand_mapinfo }, + { AtCommand_Dye, "@dye", 40, atcommand_dye }, // by fritz + { AtCommand_Dye, "@ccolor", 40, atcommand_dye }, // by fritz + { AtCommand_Hstyle, "@hairstyle", 40, atcommand_hair_style }, // by fritz + { AtCommand_Hstyle, "@hstyle", 40, atcommand_hair_style }, // by fritz + { AtCommand_Hcolor, "@haircolor", 40, atcommand_hair_color }, // by fritz + { AtCommand_Hcolor, "@hcolor", 40, atcommand_hair_color }, // by fritz + { AtCommand_StatAll, "@statall", 60, atcommand_stat_all }, // by fritz + { AtCommand_StatAll, "@statsall", 60, atcommand_stat_all }, + { AtCommand_StatAll, "@allstats", 60, atcommand_stat_all }, // by fritz + { AtCommand_StatAll, "@allstat", 60, atcommand_stat_all }, // by fritz + { AtCommand_CharChangeSex, "@charchangesex", 60, atcommand_char_change_sex }, // by Yor + { AtCommand_CharBlock, "@block", 60, atcommand_char_block }, // by Yor + { AtCommand_CharBlock, "@charblock", 60, atcommand_char_block }, // by Yor + { AtCommand_CharBan, "@ban", 60, atcommand_char_ban }, // by Yor + { AtCommand_CharBan, "@banish", 60, atcommand_char_ban }, // by Yor + { AtCommand_CharBan, "@charban", 60, atcommand_char_ban }, // by Yor + { AtCommand_CharBan, "@charbanish", 60, atcommand_char_ban }, // by Yor + { AtCommand_CharUnBlock, "@unblock", 60, atcommand_char_unblock }, // by Yor + { AtCommand_CharUnBlock, "@charunblock", 60, atcommand_char_unblock }, // by Yor + { AtCommand_CharUnBan, "@unban", 60, atcommand_char_unban }, // by Yor + { AtCommand_CharUnBan, "@unbanish", 60, atcommand_char_unban }, // by Yor + { AtCommand_CharUnBan, "@charunban", 60, atcommand_char_unban }, // by Yor + { AtCommand_CharUnBan, "@charunbanish", 60, atcommand_char_unban }, // by Yor + { AtCommand_MountPeco, "@mountpeco", 20, atcommand_mount_peco }, // by Valaris + { AtCommand_CharMountPeco, "@charmountpeco", 50, atcommand_char_mount_peco }, // by Yor + { AtCommand_GuildSpy, "@guildspy", 60, atcommand_guildspy }, // [Syrus22] + { AtCommand_PartySpy, "@partyspy", 60, atcommand_partyspy }, // [Syrus22] + { AtCommand_RepairAll, "@repairall", 60, atcommand_repairall }, // [Valaris] + { AtCommand_GuildRecall, "@guildrecall", 60, atcommand_guildrecall }, // by Yor + { AtCommand_PartyRecall, "@partyrecall", 60, atcommand_partyrecall }, // by Yor +// { AtCommand_Nuke, "@nuke", 60, atcommand_nuke }, // [Valaris] + { AtCommand_Enablenpc, "@enablenpc", 80, atcommand_enablenpc }, // [] + { AtCommand_Disablenpc, "@disablenpc", 80, atcommand_disablenpc }, // [] + { AtCommand_ServerTime, "@time", 0, atcommand_servertime }, // by Yor + { AtCommand_ServerTime, "@date", 0, atcommand_servertime }, // by Yor + { AtCommand_ServerTime, "@server_date", 0, atcommand_servertime }, // by Yor + { AtCommand_ServerTime, "@serverdate", 0, atcommand_servertime }, // by Yor + { AtCommand_ServerTime, "@server_time", 0, atcommand_servertime }, // by Yor + { AtCommand_ServerTime, "@servertime", 0, atcommand_servertime }, // by Yor + { AtCommand_CharDelItem, "@chardelitem", 60, atcommand_chardelitem }, // by Yor + { AtCommand_Jail, "@jail", 60, atcommand_jail }, // by Yor + { AtCommand_UnJail, "@unjail", 60, atcommand_unjail }, // by Yor + { AtCommand_UnJail, "@discharge", 60, atcommand_unjail }, // by Yor + { AtCommand_Disguise, "@disguise", 20, atcommand_disguise }, // [Valaris] + { AtCommand_UnDisguise, "@undisguise", 20, atcommand_undisguise }, // by Yor + { AtCommand_IgnoreList, "@ignorelist", 0, atcommand_ignorelist }, // by Yor + { AtCommand_CharIgnoreList, "@charignorelist", 20, atcommand_charignorelist }, // by Yor + { AtCommand_IgnoreList, "@inall", 20, atcommand_inall }, // by Yor + { AtCommand_ExAll, "@exall", 20, atcommand_exall }, // by Yor + { AtCommand_CharDisguise, "@chardisguise", 60, atcommand_chardisguise }, // Kalaspuff + { AtCommand_CharUnDisguise, "@charundisguise", 60, atcommand_charundisguise }, // Kalaspuff + { AtCommand_EMail, "@email", 0, atcommand_email }, // by Yor + { AtCommand_Effect, "@effect", 40, atcommand_effect }, // by Apple + { AtCommand_Char_Item_List, "@charitemlist", 40, atcommand_character_item_list }, // by Yor + { AtCommand_Char_Storage_List, "@charstoragelist", 40, atcommand_character_storage_list }, // by Yor + { AtCommand_Char_Cart_List, "@charcartlist", 40, atcommand_character_cart_list }, // by Yor + { AtCommand_Follow, "@follow", 10, atcommand_follow }, // by MouseJstr + { AtCommand_AddWarp, "@addwarp", 20, atcommand_addwarp }, // by MouseJstr + { AtCommand_SkillOn, "@skillon", 20, atcommand_skillon }, // by MouseJstr + { AtCommand_SkillOff, "@skilloff", 20, atcommand_skilloff }, // by MouseJstr + { AtCommand_Killer, "@killer", 60, atcommand_killer }, // by MouseJstr + { AtCommand_NpcMove, "@npcmove", 20, atcommand_npcmove }, // by MouseJstr + { AtCommand_Killable, "@killable", 40, atcommand_killable }, // by MouseJstr + { AtCommand_CharKillable, "@charkillable", 40, atcommand_charkillable }, // by MouseJstr + { AtCommand_Chareffect, "@chareffect", 40, atcommand_chareffect }, // MouseJstr + //{ AtCommand_Chardye, "@chardye", 40, atcommand_chardye }, // MouseJstr + //{ AtCommand_Charhairstyle, "@charhairstyle", 40, atcommand_charhairstyle }, // MouseJstr + //{ AtCommand_Charhaircolor, "@charhaircolor", 40, atcommand_charhaircolor }, // MouseJstr + { AtCommand_Dropall, "@dropall", 40, atcommand_dropall }, // MouseJstr + { AtCommand_Chardropall, "@chardropall", 40, atcommand_chardropall }, // MouseJstr + { AtCommand_Storeall, "@storeall", 40, atcommand_storeall }, // MouseJstr + { AtCommand_Charstoreall, "@charstoreall", 40, atcommand_charstoreall }, // MouseJstr + { AtCommand_Skillid, "@skillid", 40, atcommand_skillid }, // MouseJstr + { AtCommand_Useskill, "@useskill", 40, atcommand_useskill }, // MouseJstr + { AtCommand_Rain, "@rain", 99, atcommand_rain }, + { AtCommand_Snow, "@snow", 99, atcommand_snow }, + { AtCommand_Sakura, "@sakura", 99, atcommand_sakura }, + { AtCommand_Fog, "@fog", 99, atcommand_fog }, + { AtCommand_Leaves, "@leaves", 99, atcommand_leaves }, + //{ AtCommand_Shuffle, "@shuffle", 99, atcommand_shuffle }, + //{ AtCommand_Maintenance, "@maintenance", 99, atcommand_maintenance }, + //{ AtCommand_Misceffect, "@misceffect", 60, atcommand_misceffect }, + { AtCommand_Summon, "@summon", 60, atcommand_summon }, + { AtCommand_AdjGmLvl, "@adjgmlvl", 99, atcommand_adjgmlvl }, + { AtCommand_AdjCmdLvl, "@adjcmdlvl", 99, atcommand_adjcmdlvl }, + { AtCommand_Trade, "@trade", 60, atcommand_trade }, + { AtCommand_UnMute, "@unmute", 60, atcommand_unmute }, // [Valaris] + +#ifndef TXT_ONLY // sql-only commands + { AtCommand_CheckMail, "@checkmail", 1, atcommand_listmail }, // [Valaris] + { AtCommand_ListMail, "@listmail", 1, atcommand_listmail }, // [Valaris] + { AtCommand_ListNewMail, "@listnewmail", 1, atcommand_listmail }, // [Valaris] + { AtCommand_ReadMail, "@readmail", 1, atcommand_readmail }, // [Valaris] + { AtCommand_DeleteMail, "@deletemail", 1, atcommand_readmail }, // [Valaris] + { AtCommand_SendMail, "@sendmail", 1, atcommand_sendmail }, // [Valaris] + { AtCommand_SendPriorityMail, "@sendprioritymail",80, atcommand_sendmail }, // [Valaris] + { AtCommand_RefreshOnline, "@refreshonline", 99, atcommand_refreshonline }, // [Valaris] +#endif /* TXT_ONLY */ + +// add new commands before this line + { AtCommand_Unknown, NULL, 1, NULL } +}; + +/*==================================================== + * This function return the name of the job (by [Yor]) + *---------------------------------------------------- + */ +char * job_name(int class) { + switch (class) { + case 0: return "Novice"; + case 1: return "Swordsman"; + case 2: return "Mage"; + case 3: return "Archer"; + case 4: return "Acolyte"; + case 5: return "Merchant"; + case 6: return "Thief"; + case 7: return "Knight"; + case 8: return "Priest"; + case 9: return "Wizard"; + case 10: return "Blacksmith"; + case 11: return "Hunter"; + case 12: return "Assassin"; + case 13: return "Knight 2"; + case 14: return "Crusader"; + case 15: return "Monk"; + case 16: return "Sage"; + case 17: return "Rogue"; + case 18: return "Alchemist"; + case 19: return "Bard"; + case 20: return "Dancer"; + case 21: return "Crusader 2"; + case 22: return "Wedding"; + case 23: return "Super Novice"; + case 4001: return "Novice High"; + case 4002: return "Swordsman High"; + case 4003: return "Mage High"; + case 4004: return "Archer High"; + case 4005: return "Acolyte High"; + case 4006: return "Merchant High"; + case 4007: return "Thief High"; + case 4008: return "Lord Knight"; + case 4009: return "High Priest"; + case 4010: return "High Wizard"; + case 4011: return "Whitesmith"; + case 4012: return "Sniper"; + case 4013: return "Assassin Cross"; + case 4014: return "Peko Knight"; + case 4015: return "Paladin"; + case 4016: return "Champion"; + case 4017: return "Professor"; + case 4018: return "Stalker"; + case 4019: return "Creator"; + case 4020: return "Clown"; + case 4021: return "Gypsy"; + case 4022: return "Peko Paladin"; + case 4023: return "Baby Novice"; + case 4024: return "Baby Swordsman"; + case 4025: return "Baby Mage"; + case 4026: return "Baby Archer"; + case 4027: return "Baby Acolyte"; + case 4028: return "Baby Merchant"; + case 4029: return "Baby Thief"; + case 4030: return "Baby Knight"; + case 4031: return "Baby Priest"; + case 4032: return "Baby Wizard"; + case 4033: return "Baby Blacksmith"; + case 4034: return "Baby Hunter"; + case 4035: return "Baby Assassin"; + case 4036: return "Baby Peco Knight"; + case 4037: return "Baby Crusader"; + case 4038: return "Baby Monk"; + case 4039: return "Baby Sage"; + case 4040: return "Baby Rogue"; + case 4041: return "Baby Alchemist"; + case 4042: return "Baby Bard"; + case 4043: return "Baby Dancer"; + case 4044: return "Baby Peco Crusader"; + case 4045: return "Super Baby"; + } + return "Unknown Job"; +} + +//----------------------------------------------------------- +// Return the message string of the specified number by [Yor] +//----------------------------------------------------------- +char * msg_txt(int msg_number) { + if (msg_number >= 0 && msg_number < (int)(sizeof(msg_table) / sizeof(msg_table[0])) && + msg_table[msg_number] != NULL && msg_table[msg_number][0] != '\0') + return msg_table[msg_number]; + + return "??"; +} + +//------------------------------------------------------------ +// E-mail check: return 0 (not correct) or 1 (valid). by [Yor] +//------------------------------------------------------------ +int e_mail_check(unsigned char *email) { + char ch; + unsigned char* last_arobas; + + // athena limits + if (strlen(email) < 3 || strlen(email) > 39) + return 0; + + // part of RFC limits (official reference of e-mail description) + if (strchr(email, '@') == NULL || email[strlen(email)-1] == '@') + return 0; + + if (email[strlen(email)-1] == '.') + return 0; + + last_arobas = strrchr(email, '@'); + + if (strstr(last_arobas, "@.") != NULL || + strstr(last_arobas, "..") != NULL) + return 0; + + for(ch = 1; ch < 32; ch++) { + if (strchr(last_arobas, ch) != NULL) { + return 0; + break; + } + } + + if (strchr(last_arobas, ' ') != NULL || + strchr(last_arobas, ';') != NULL) + return 0; + + // all correct + return 1; +} + +/*========================================== + * get_atcommand_level @コマンドの必要レベルを取得 + *------------------------------------------ + */ +int get_atcommand_level(const AtCommandType type) { + int i; + + for (i = 0; atcommand_info[i].type != AtCommand_None; i++) + if (atcommand_info[i].type == type) + return atcommand_info[i].level; + + return 100; // 100: command can not be used +} + +/*========================================== + *is_atcommand @コマンドに存在するかどうか確認する + *------------------------------------------ + */ +AtCommandType +is_atcommand(const int fd, struct map_session_data* sd, const char* message, int gmlvl) { + const char* str = message; + int s_flag = 0; + AtCommandInfo info; + AtCommandType type; + + nullpo_retr(AtCommand_None, sd); + + if (!message || !*message) + return AtCommand_None; + + memset(&info, 0, sizeof(info)); + str += strlen(sd->status.name); + while (*str && (isspace(*str) || (s_flag == 0 && *str == ':'))) { + if (*str == ':') + s_flag = 1; + str++; + } + if (!*str) + return AtCommand_None; + + type = atcommand(gmlvl > 0 ? gmlvl : pc_isGM(sd), str, &info); + if (type != AtCommand_None) { + char command[100]; + char output[200]; + const char* p = str; + memset(command, '\0', sizeof(command)); + memset(output, '\0', sizeof(output)); + while (*p && !isspace(*p)) + p++; + if (p - str >= sizeof(command)) // too long + return AtCommand_Unknown; + strncpy(command, str, p - str); + while (isspace(*p)) + p++; + + if (type == AtCommand_Unknown || info.proc == NULL) { + sprintf(output, msg_table[153], command); // %s is Unknown Command. + clif_displaymessage(fd, output); + } else { + if (info.proc(fd, sd, command, p) != 0) { + // Command can not be executed + sprintf(output, msg_table[154], command); // %s failed. + clif_displaymessage(fd, output); + } + } + + return info.type; + } + + return AtCommand_None; +} + +/*========================================== + * + *------------------------------------------ + */ +AtCommandType atcommand(const int level, const char* message, struct AtCommandInfo* info) { + char* p = (char *)message; // it's 'char' and not 'const char' to have possibility to modify the first character if necessary + + if (!info) + return AtCommand_None; + if (battle_config.atc_gmonly != 0 && !level) // level = pc_isGM(sd) + return AtCommand_None; + if (!p || !*p) { + fprintf(stderr, "at command message is empty\n"); + return AtCommand_None; + } + + if (*p == command_symbol) { // check first char. + char command[101]; + int i = 0; + memset(info, 0, sizeof(AtCommandInfo)); + sscanf(p, "%100s", command); + command[sizeof(command)-1] = '\0'; + + while (atcommand_info[i].type != AtCommand_Unknown) { + if (strcmpi(command+1, atcommand_info[i].command+1) == 0 && level >= atcommand_info[i].level) { + p[0] = atcommand_info[i].command[0]; // set correct first symbol for after. + break; + } + i++; + } + + if (atcommand_info[i].type == AtCommand_Unknown) { + // doesn't return Unknown if player is normal player (display the text, not display: unknown command) + if (level == 0) + return AtCommand_None; + else + return AtCommand_Unknown; + } + memcpy(info, &atcommand_info[i], sizeof atcommand_info[i]); + } else { + return AtCommand_None; + } + + return info->type; +} + +/*========================================== + * + *------------------------------------------ + */ +static int atkillmonster_sub(struct block_list *bl, va_list ap) { + int flag = va_arg(ap, int); + + nullpo_retr(0, bl); + + if (flag) + mob_damage(NULL, (struct mob_data *)bl, ((struct mob_data *)bl)->hp, 2); + else + mob_delete((struct mob_data *)bl); + + return 0; +} + +#ifndef TXT_ONLY +static int atkillnpc_sub(struct block_list *bl, va_list ap) +{ + int flag = va_arg(ap,int); + + nullpo_retr(0, bl); + + npc_delete((struct npc_data *)bl); + + flag = 0; + + return 0; +} + +void rehash( const int fd, struct map_session_data* sd ) +{ + int map_id = 0; + + int LOADED_MAPS = map_num; + + for (map_id = 0; map_id < LOADED_MAPS;map_id++) { + + if (map_id > LOADED_MAPS) + break; + + map_foreachinarea(atkillmonster_sub, map_id, 0, 0, map[map_id].xs, map[map_id].ys, BL_MOB, 0); + map_foreachinarea(atkillnpc_sub, map_id, 0, 0, map[map_id].xs, map[map_id].ys, BL_NPC, 0); + } +} + +#endif /* not TXT_ONLY */ +/*========================================== + * Read Message Data + *------------------------------------------ + */ +int msg_config_read(const char *cfgName) { + int msg_number; + char line[1024], w1[1024], w2[1024]; + FILE *fp; + + if ((fp = fopen(cfgName, "r")) == NULL) { + printf("Messages file not found: %s\n", cfgName); + return 1; + } + + while(fgets(line, sizeof(line)-1, fp)) { + if (line[0] == '/' && line[1] == '/') + continue; + if (sscanf(line, "%[^:]: %[^\r\n]", w1, w2) == 2) { + if (strcmpi(w1, "import") == 0) { + msg_config_read(w2); + } else { + msg_number = atoi(w1); + if (msg_number >= 0 && msg_number < (int)(sizeof(msg_table) / sizeof(msg_table[0]))) + strcpy(msg_table[msg_number], w2); + // printf("message #%d: '%s'.\n", msg_number, msg_table[msg_number]); + } + } + } + fclose(fp); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +static AtCommandInfo* get_atcommandinfo_byname(const char* name) { + int i; + + for (i = 0; atcommand_info[i].type != AtCommand_Unknown; i++) + if (strcmpi(atcommand_info[i].command + 1, name) == 0) + return &atcommand_info[i]; + + return NULL; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_config_read(const char *cfgName) { + char line[1024], w1[1024], w2[1024]; + AtCommandInfo* p; + FILE* fp; + + if ((fp = fopen(cfgName, "r")) == NULL) { + printf("At commands configuration file not found: %s\n", cfgName); + return 1; + } + + while (fgets(line, sizeof(line)-1, fp)) { + if (line[0] == '/' && line[1] == '/') + continue; + + if (sscanf(line, "%1023[^:]:%1023s", w1, w2) != 2) + continue; + p = get_atcommandinfo_byname(w1); + if (p != NULL) { + p->level = atoi(w2); + if (p->level > 100) + p->level = 100; + else if (p->level < 0) + p->level = 0; + } + + if (strcmpi(w1, "import") == 0) + atcommand_config_read(w2); + else if (strcmpi(w1, "command_symbol") == 0 && w2[0] > 31 && + w2[0] != '/' && // symbol of standard ragnarok GM commands + w2[0] != '%') // symbol of party chat speaking + command_symbol = w2[0]; + } + fclose(fp); + + return 0; +} + +/*========================================== +// @ command processing functions + *------------------------------------------ + */ + +/*========================================== + * @rura+ + *------------------------------------------ + */ +int atcommand_rurap( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char map_name[100]; + char character[100]; + int x = 0, y = 0; + struct map_session_data *pl_sd; + int m; + + memset(map_name, '\0', sizeof(map_name)); + memset(character, '\0', sizeof(character)); + + if (!message || !*message || sscanf(message, "%99s %d %d %99[^\n]", map_name, &x, &y, character) < 4) { + clif_displaymessage(fd, "Usage: @charwarp/@rura+ <mapname> <x> <y> <char name>"); + return -1; + } + + if (x <= 0) + x = rand() % 399 + 1; + if (y <= 0) + y = rand() % 399 + 1; + if (strstr(map_name, ".gat") == NULL && strstr(map_name, ".afm") == NULL && strlen(map_name) < 13) // 16 - 4 (.gat) + strcat(map_name, ".gat"); + + if ((pl_sd = map_nick2sd(character)) != NULL) { + if (pc_isGM(sd) >= pc_isGM(pl_sd)) { // you can rura+ only lower or same GM level + if (x > 0 && x < 400 && y > 0 && y < 400) { + m = map_mapname2mapid(map_name); + if (m >= 0 && map[m].flag.nowarpto && battle_config.any_warp_GM_min_level > pc_isGM(sd)) { + clif_displaymessage(fd, "You are not authorised to warp someone to this map."); + return -1; + } + if (pl_sd->bl.m >= 0 && map[pl_sd->bl.m].flag.nowarp && battle_config.any_warp_GM_min_level > pc_isGM(sd)) { + clif_displaymessage(fd, "You are not authorised to warp this player from its actual map."); + return -1; + } + if (pc_setpos(pl_sd, map_name, x, y, 3) == 0) { + clif_displaymessage(pl_sd->fd, msg_table[0]); // Warped. + clif_displaymessage(fd, msg_table[15]); // Player warped (message sends to player too). + } else { + clif_displaymessage(fd, msg_table[1]); // Map not found. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[2]); // Coordinates out of range. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[81]); // Your GM level don't authorise you to do this action on this player. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[3]); // Character not found. + return -1; + } + + return 0; +} + +// @rura +/*========================================== + * + *------------------------------------------ + */ +int atcommand_rura( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char map_name[100]; + int x = 0, y = 0; + int m; + + memset(map_name, '\0', sizeof(map_name)); + + if (!message || !*message || sscanf(message, "%99s %d %d", map_name, &x, &y) < 1) { + clif_displaymessage(fd, "Please, enter a map (usage: @warp/@rura/@mapmove <mapname> <x> <y>)."); + return -1; + } + + if (x <= 0) + x = rand() % 399 + 1; + if (y <= 0) + y = rand() % 399 + 1; + + if (strstr(map_name, ".gat") == NULL && strstr(map_name, ".afm") == NULL && strlen(map_name) < 13) // 16 - 4 (.gat) + strcat(map_name, ".gat"); + + if (x > 0 && x < 400 && y > 0 && y < 400) { + m = map_mapname2mapid(map_name); + if (m >= 0 && map[m].flag.nowarpto && battle_config.any_warp_GM_min_level > pc_isGM(sd)) { + clif_displaymessage(fd, "You are not authorised to warp you to this map."); + return -1; + } + if (sd->bl.m >= 0 && map[sd->bl.m].flag.nowarp && battle_config.any_warp_GM_min_level > pc_isGM(sd)) { + clif_displaymessage(fd, "You are not authorised to warp you from your actual map."); + return -1; + } + if (pc_setpos(sd, map_name, x, y, 3) == 0) + clif_displaymessage(fd, msg_table[0]); // Warped. + else { + clif_displaymessage(fd, msg_table[1]); // Map not found. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[2]); // Coordinates out of range. + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_where( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char character[100]; + char output[200]; + struct map_session_data *pl_sd; + + memset(character, '\0', sizeof(character)); + memset(output, '\0', sizeof(output)); + + if (sscanf(message, "%99[^\n]", character) < 1) + strcpy(character, sd->status.name); + + if ((pl_sd = map_nick2sd(character)) != NULL && + !((battle_config.hide_GM_session || (pl_sd->status.option & OPTION_HIDE)) && (pc_isGM(pl_sd) > pc_isGM(sd)))) { // you can look only lower or same level + sprintf(output, "%s: %s (%d,%d)", pl_sd->status.name, pl_sd->mapname, pl_sd->bl.x, pl_sd->bl.y); + clif_displaymessage(fd, output); + } else { + clif_displaymessage(fd, msg_table[3]); // Character not found. + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_jumpto( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char character[100]; + char output[200]; + struct map_session_data *pl_sd; + + memset(character, '\0', sizeof(character)); + memset(output, '\0', sizeof(output)); + + if (!message || !*message || sscanf(message, "%99[^\n]", character) < 1) { + clif_displaymessage(fd, "Please, enter a player name (usage: @jumpto/@warpto/@goto <char name>)."); + return -1; + } + + if ((pl_sd = map_nick2sd(character)) != NULL) { + if (pl_sd->bl.m >= 0 && map[pl_sd->bl.m].flag.nowarpto && battle_config.any_warp_GM_min_level > pc_isGM(sd)) { + clif_displaymessage(fd, "You are not authorised to warp you to the map of this player."); + return -1; + } + if (sd->bl.m >= 0 && map[sd->bl.m].flag.nowarp && battle_config.any_warp_GM_min_level > pc_isGM(sd)) { + clif_displaymessage(fd, "You are not authorised to warp you from your actual map."); + return -1; + } + pc_setpos(sd, pl_sd->mapname, pl_sd->bl.x, pl_sd->bl.y, 3); + sprintf(output, msg_table[4], character); // Jump to %s + clif_displaymessage(fd, output); + } else { + clif_displaymessage(fd, msg_table[3]); // Character not found. + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_jump( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char output[200]; + int x = 0, y = 0; + + memset(output, '\0', sizeof(output)); + + sscanf(message, "%d %d", &x, &y); + + if (x <= 0) + x = rand() % 399 + 1; + if (y <= 0) + y = rand() % 399 + 1; + if (x > 0 && x < 400 && y > 0 && y < 400) { + if (sd->bl.m >= 0 && map[sd->bl.m].flag.nowarpto && battle_config.any_warp_GM_min_level > pc_isGM(sd)) { + clif_displaymessage(fd, "You are not authorised to warp you to your actual map."); + return -1; + } + if (sd->bl.m >= 0 && map[sd->bl.m].flag.nowarp && battle_config.any_warp_GM_min_level > pc_isGM(sd)) { + clif_displaymessage(fd, "You are not authorised to warp you from your actual map."); + return -1; + } + pc_setpos(sd, sd->mapname, x, y, 3); + sprintf(output, msg_table[5], x, y); // Jump to %d %d + clif_displaymessage(fd, output); + } else { + clif_displaymessage(fd, msg_table[2]); // Coordinates out of range. + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_who( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char output[200]; + struct map_session_data *pl_sd; + int i, j, count; + int pl_GM_level, GM_level; + char match_text[100]; + char player_name[24]; + + memset(output, '\0', sizeof(output)); + memset(match_text, '\0', sizeof(match_text)); + memset(player_name, '\0', sizeof(player_name)); + + if (sscanf(message, "%99[^\n]", match_text) < 1) + strcpy(match_text, ""); + for (j = 0; match_text[j]; j++) + match_text[j] = tolower(match_text[j]); + + count = 0; + GM_level = pc_isGM(sd); + for (i = 0; i < fd_max; i++) { + if (session[i] && (pl_sd = session[i]->session_data) && pl_sd->state.auth) { + pl_GM_level = pc_isGM(pl_sd); + if (!((battle_config.hide_GM_session || (pl_sd->status.option & OPTION_HIDE)) && (pl_GM_level > GM_level))) { // you can look only lower or same level + memcpy(player_name, pl_sd->status.name, 24); + for (j = 0; player_name[j]; j++) + player_name[j] = tolower(player_name[j]); + if (strstr(player_name, match_text) != NULL) { // search with no case sensitive + if (pl_GM_level > 0) + sprintf(output, "Name: %s (GM:%d) | Location: %s %d %d", pl_sd->status.name, pl_GM_level, pl_sd->mapname, pl_sd->bl.x, pl_sd->bl.y); + else + sprintf(output, "Name: %s | Location: %s %d %d", pl_sd->status.name, pl_sd->mapname, pl_sd->bl.x, pl_sd->bl.y); + clif_displaymessage(fd, output); + count++; + } + } + } + } + + if (count == 0) + clif_displaymessage(fd, msg_table[28]); // No player found. + else if (count == 1) + clif_displaymessage(fd, msg_table[29]); // 1 player found. + else { + sprintf(output, msg_table[30], count); // %d players found. + clif_displaymessage(fd, output); + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_who2( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char output[200]; + struct map_session_data *pl_sd; + int i, j, count; + int pl_GM_level, GM_level; + char match_text[100]; + char player_name[24]; + + memset(output, '\0', sizeof(output)); + memset(match_text, '\0', sizeof(match_text)); + memset(player_name, '\0', sizeof(player_name)); + + if (sscanf(message, "%99[^\n]", match_text) < 1) + strcpy(match_text, ""); + for (j = 0; match_text[j]; j++) + match_text[j] = tolower(match_text[j]); + + count = 0; + GM_level = pc_isGM(sd); + for (i = 0; i < fd_max; i++) { + if (session[i] && (pl_sd = session[i]->session_data) && pl_sd->state.auth) { + pl_GM_level = pc_isGM(pl_sd); + if (!((battle_config.hide_GM_session || (pl_sd->status.option & OPTION_HIDE)) && (pl_GM_level > GM_level))) { // you can look only lower or same level + memcpy(player_name, pl_sd->status.name, 24); + for (j = 0; player_name[j]; j++) + player_name[j] = tolower(player_name[j]); + if (strstr(player_name, match_text) != NULL) { // search with no case sensitive + if (pl_GM_level > 0) + sprintf(output, "Name: %s (GM:%d) | BLvl: %d | Job: %s (Lvl: %d)", pl_sd->status.name, pl_GM_level, pl_sd->status.base_level, job_name(pl_sd->status.class), pl_sd->status.job_level); + else + sprintf(output, "Name: %s | BLvl: %d | Job: %s (Lvl: %d)", pl_sd->status.name, pl_sd->status.base_level, job_name(pl_sd->status.class), pl_sd->status.job_level); + clif_displaymessage(fd, output); + count++; + } + } + } + } + + if (count == 0) + clif_displaymessage(fd, msg_table[28]); // No player found. + else if (count == 1) + clif_displaymessage(fd, msg_table[29]); // 1 player found. + else { + sprintf(output, msg_table[30], count); // %d players found. + clif_displaymessage(fd, output); + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_who3( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char temp0[100]; + char temp1[100]; + char output[200]; + struct map_session_data *pl_sd; + int i, j, count; + int pl_GM_level, GM_level; + char match_text[100]; + char player_name[24]; + struct guild *g; + struct party *p; + + memset(temp0, '\0', sizeof(temp0)); + memset(temp1, '\0', sizeof(temp1)); + memset(output, '\0', sizeof(output)); + memset(match_text, '\0', sizeof(match_text)); + memset(player_name, '\0', sizeof(player_name)); + + if (sscanf(message, "%99[^\n]", match_text) < 1) + strcpy(match_text, ""); + for (j = 0; match_text[j]; j++) + match_text[j] = tolower(match_text[j]); + + count = 0; + GM_level = pc_isGM(sd); + for (i = 0; i < fd_max; i++) { + if (session[i] && (pl_sd = session[i]->session_data) && pl_sd->state.auth) { + pl_GM_level = pc_isGM(pl_sd); + if (!((battle_config.hide_GM_session || (pl_sd->status.option & OPTION_HIDE)) && (pl_GM_level > GM_level))) { // you can look only lower or same level + memcpy(player_name, pl_sd->status.name, 24); + for (j = 0; player_name[j]; j++) + player_name[j] = tolower(player_name[j]); + if (strstr(player_name, match_text) != NULL) { // search with no case sensitive + g = guild_search(pl_sd->status.guild_id); + if (g == NULL) + sprintf(temp1, "None"); + else + sprintf(temp1, "%s", g->name); + p = party_search(pl_sd->status.party_id); + if (p == NULL) + sprintf(temp0, "None"); + else + sprintf(temp0, "%s", p->name); + if (pl_GM_level > 0) + sprintf(output, "Name: %s (GM:%d) | Party: '%s' | Guild: '%s'", pl_sd->status.name, pl_GM_level, temp0, temp1); + else + sprintf(output, "Name: %s | Party: '%s' | Guild: '%s'", pl_sd->status.name, temp0, temp1); + clif_displaymessage(fd, output); + count++; + } + } + } + } + + if (count == 0) + clif_displaymessage(fd, msg_table[28]); // No player found. + else if (count == 1) + clif_displaymessage(fd, msg_table[29]); // 1 player found. + else { + sprintf(output, msg_table[30], count); // %d players found. + clif_displaymessage(fd, output); + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_whomap( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char output[200]; + struct map_session_data *pl_sd; + int i, count; + int pl_GM_level, GM_level; + int map_id; + char map_name[100]; + + memset(output, '\0', sizeof(output)); + memset(map_name, '\0', sizeof(map_name)); + + if (!message || !*message) + map_id = sd->bl.m; + else { + sscanf(message, "%99s", map_name); + if (strstr(map_name, ".gat") == NULL && strstr(map_name, ".afm") == NULL && strlen(map_name) < 13) // 16 - 4 (.gat) + strcat(map_name, ".gat"); + if ((map_id = map_mapname2mapid(map_name)) < 0) + map_id = sd->bl.m; + } + + count = 0; + GM_level = pc_isGM(sd); + for (i = 0; i < fd_max; i++) { + if (session[i] && (pl_sd = session[i]->session_data) && pl_sd->state.auth) { + pl_GM_level = pc_isGM(pl_sd); + if (!((battle_config.hide_GM_session || (pl_sd->status.option & OPTION_HIDE)) && (pl_GM_level > GM_level))) { // you can look only lower or same level + if (pl_sd->bl.m == map_id) { + if (pl_GM_level > 0) + sprintf(output, "Name: %s (GM:%d) | Location: %s %d %d", pl_sd->status.name, pl_GM_level, pl_sd->mapname, pl_sd->bl.x, pl_sd->bl.y); + else + sprintf(output, "Name: %s | Location: %s %d %d", pl_sd->status.name, pl_sd->mapname, pl_sd->bl.x, pl_sd->bl.y); + clif_displaymessage(fd, output); + count++; + } + } + } + } + + if (count == 0) + sprintf(output, msg_table[54], map[map_id].name); // No player found in map '%s'. + else if (count == 1) + sprintf(output, msg_table[55], map[map_id].name); // 1 player found in map '%s'. + else { + sprintf(output, msg_table[56], count, map[map_id].name); // %d players found in map '%s'. + } + clif_displaymessage(fd, output); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_whomap2( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char output[200]; + struct map_session_data *pl_sd; + int i, count; + int pl_GM_level, GM_level; + int map_id = 0; + char map_name[100]; + + memset(output, '\0', sizeof(output)); + memset(map_name, '\0', sizeof(map_name)); + + if (!message || !*message) + map_id = sd->bl.m; + else { + sscanf(message, "%99s", map_name); + if (strstr(map_name, ".gat") == NULL && strstr(map_name, ".afm") == NULL && strlen(map_name) < 13) // 16 - 4 (.gat) + strcat(map_name, ".gat"); + if ((map_id = map_mapname2mapid(map_name)) < 0) + map_id = sd->bl.m; + } + + count = 0; + GM_level = pc_isGM(sd); + for (i = 0; i < fd_max; i++) { + if (session[i] && (pl_sd = session[i]->session_data) && pl_sd->state.auth) { + pl_GM_level = pc_isGM(pl_sd); + if (!((battle_config.hide_GM_session || (pl_sd->status.option & OPTION_HIDE)) && (pl_GM_level > GM_level))) { // you can look only lower or same level + if (pl_sd->bl.m == map_id) { + if (pl_GM_level > 0) + sprintf(output, "Name: %s (GM:%d) | BLvl: %d | Job: %s (Lvl: %d)", pl_sd->status.name, pl_GM_level, pl_sd->status.base_level, job_name(pl_sd->status.class), pl_sd->status.job_level); + else + sprintf(output, "Name: %s | BLvl: %d | Job: %s (Lvl: %d)", pl_sd->status.name, pl_sd->status.base_level, job_name(pl_sd->status.class), pl_sd->status.job_level); + clif_displaymessage(fd, output); + count++; + } + } + } + } + + if (count == 0) + sprintf(output, msg_table[54], map[map_id].name); // No player found in map '%s'. + else if (count == 1) + sprintf(output, msg_table[55], map[map_id].name); // 1 player found in map '%s'. + else { + sprintf(output, msg_table[56], count, map[map_id].name); // %d players found in map '%s'. + } + clif_displaymessage(fd, output); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_whomap3( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char temp0[100]; + char temp1[100]; + char output[200]; + struct map_session_data *pl_sd; + int i, count; + int pl_GM_level, GM_level; + int map_id = 0; + char map_name[100]; + struct guild *g; + struct party *p; + + memset(temp0, '\0', sizeof(temp0)); + memset(temp1, '\0', sizeof(temp1)); + memset(output, '\0', sizeof(output)); + memset(map_name, '\0', sizeof(map_name)); + + if (!message || !*message) + map_id = sd->bl.m; + else { + sscanf(message, "%99s", map_name); + if (strstr(map_name, ".gat") == NULL && strstr(map_name, ".afm") == NULL && strlen(map_name) < 13) // 16 - 4 (.gat) + strcat(map_name, ".gat"); + if ((map_id = map_mapname2mapid(map_name)) < 0) + map_id = sd->bl.m; + } + + count = 0; + GM_level = pc_isGM(sd); + for (i = 0; i < fd_max; i++) { + if (session[i] && (pl_sd = session[i]->session_data) && pl_sd->state.auth) { + pl_GM_level = pc_isGM(pl_sd); + if (!((battle_config.hide_GM_session || (pl_sd->status.option & OPTION_HIDE)) && (pl_GM_level > GM_level))) { // you can look only lower or same level + if (pl_sd->bl.m == map_id) { + g = guild_search(pl_sd->status.guild_id); + if (g == NULL) + sprintf(temp1, "None"); + else + sprintf(temp1, "%s", g->name); + p = party_search(pl_sd->status.party_id); + if (p == NULL) + sprintf(temp0, "None"); + else + sprintf(temp0, "%s", p->name); + if (pl_GM_level > 0) + sprintf(output, "Name: %s (GM:%d) | Party: '%s' | Guild: '%s'", pl_sd->status.name, pl_GM_level, temp0, temp1); + else + sprintf(output, "Name: %s | Party: '%s' | Guild: '%s'", pl_sd->status.name, temp0, temp1); + clif_displaymessage(fd, output); + count++; + } + } + } + } + + if (count == 0) + sprintf(output, msg_table[54], map[map_id].name); // No player found in map '%s'. + else if (count == 1) + sprintf(output, msg_table[55], map[map_id].name); // 1 player found in map '%s'. + else { + sprintf(output, msg_table[56], count, map[map_id].name); // %d players found in map '%s'. + } + clif_displaymessage(fd, output); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_whogm( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char temp0[100]; + char temp1[100]; + char output[200]; + struct map_session_data *pl_sd; + int i, j, count; + int pl_GM_level, GM_level; + char match_text[100]; + char player_name[24]; + struct guild *g; + struct party *p; + + memset(temp0, '\0', sizeof(temp0)); + memset(temp1, '\0', sizeof(temp1)); + memset(output, '\0', sizeof(output)); + memset(match_text, '\0', sizeof(match_text)); + memset(player_name, '\0', sizeof(player_name)); + + if (sscanf(message, "%99[^\n]", match_text) < 1) + strcpy(match_text, ""); + for (j = 0; match_text[j]; j++) + match_text[j] = tolower(match_text[j]); + + count = 0; + GM_level = pc_isGM(sd); + for (i = 0; i < fd_max; i++) { + if (session[i] && (pl_sd = session[i]->session_data) && pl_sd->state.auth) { + pl_GM_level = pc_isGM(pl_sd); + if (pl_GM_level > 0) { + if (!((battle_config.hide_GM_session || (pl_sd->status.option & OPTION_HIDE)) && (pl_GM_level > GM_level))) { // you can look only lower or same level + memcpy(player_name, pl_sd->status.name, 24); + for (j = 0; player_name[j]; j++) + player_name[j] = tolower(player_name[j]); + if (strstr(player_name, match_text) != NULL) { // search with no case sensitive + sprintf(output, "Name: %s (GM:%d) | Location: %s %d %d", pl_sd->status.name, pl_GM_level, pl_sd->mapname, pl_sd->bl.x, pl_sd->bl.y); + clif_displaymessage(fd, output); + sprintf(output, " BLvl: %d | Job: %s (Lvl: %d)", pl_sd->status.base_level, job_name(pl_sd->status.class), pl_sd->status.job_level); + clif_displaymessage(fd, output); + g = guild_search(pl_sd->status.guild_id); + if (g == NULL) + sprintf(temp1, "None"); + else + sprintf(temp1, "%s", g->name); + p = party_search(pl_sd->status.party_id); + if (p == NULL) + sprintf(temp0, "None"); + else + sprintf(temp0, "%s", p->name); + sprintf(output, " Party: '%s' | Guild: '%s'", temp0, temp1); + clif_displaymessage(fd, output); + count++; + } + } + } + } + } + + if (count == 0) + clif_displaymessage(fd, msg_table[150]); // No GM found. + else if (count == 1) + clif_displaymessage(fd, msg_table[151]); // 1 GM found. + else { + sprintf(output, msg_table[152], count); // %d GMs found. + clif_displaymessage(fd, output); + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_save( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + pc_setsavepoint(sd, sd->mapname, sd->bl.x, sd->bl.y); + if (sd->status.pet_id > 0 && sd->pd) + intif_save_petdata(sd->status.account_id, &sd->pet); + pc_makesavestatus(sd); + chrif_save(sd); + storage_storage_save(sd); + clif_displaymessage(fd, msg_table[6]); // Character data respawn point saved. + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_load( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + int m; + + m = map_mapname2mapid(sd->status.save_point.map); + if (m >= 0 && map[m].flag.nowarpto && battle_config.any_warp_GM_min_level > pc_isGM(sd)) { + clif_displaymessage(fd, "You are not authorised to warp you to your save map."); + return -1; + } + if (sd->bl.m >= 0 && map[sd->bl.m].flag.nowarp && battle_config.any_warp_GM_min_level > pc_isGM(sd)) { + clif_displaymessage(fd, "You are not authorised to warp you from your actual map."); + return -1; + } + + pc_setpos(sd, sd->status.save_point.map, sd->status.save_point.x, sd->status.save_point.y, 0); + clif_displaymessage(fd, msg_table[7]); // Warping to respawn point. + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_speed( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char output[200]; + int speed; + + memset(output, '\0', sizeof(output)); + + if (!message || !*message) { + sprintf(output, "Please, enter a speed value (usage: @speed <%d-%d>).", MIN_WALK_SPEED, MAX_WALK_SPEED); + clif_displaymessage(fd, output); + return -1; + } + + speed = atoi(message); + if (speed >= MIN_WALK_SPEED && speed <= MAX_WALK_SPEED) { + sd->speed = speed; + //sd->walktimer = x; + //この文を追加 by れ + clif_updatestatus(sd, SP_SPEED); + clif_displaymessage(fd, msg_table[8]); // Speed changed. + } else { + sprintf(output, "Please, enter a valid speed value (usage: @speed <%d-%d>).", MIN_WALK_SPEED, MAX_WALK_SPEED); + clif_displaymessage(fd, output); + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_storage( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + storage_storageopen(sd); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_guildstorage( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + if (sd->status.guild_id > 0) + storage_guild_storageopen(sd); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_option( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + int param1 = 0, param2 = 0, param3 = 0; + + if (!message || !*message || sscanf(message, "%d %d %d", ¶m1, ¶m2, ¶m3) < 1 || param1 < 0 || param2 < 0 || param3 < 0) { + clif_displaymessage(fd, "Please, enter at least a option (usage: @option <param1:0+> <param2:0+> <param3:0+>)."); + return -1; + } + + sd->opt1 = param1; + sd->opt2 = param2; + if (!(sd->status.option & CART_MASK) && param3 & CART_MASK) { + clif_cart_itemlist(sd); + clif_cart_equiplist(sd); + clif_updatestatus(sd, SP_CARTINFO); + } + sd->status.option = param3; + // fix pecopeco display + if (sd->status.class == 13 || sd->status.class == 21 || sd->status.class == 4014 || sd->status.class == 4022) { + if (!pc_isriding(sd)) { // sd have the new value... + if (sd->status.class == 13) + sd->status.class = sd->view_class = 7; + else if (sd->status.class == 21) + sd->status.class = sd->view_class = 14; + else if (sd->status.class == 4014) + sd->status.class = sd->view_class = 4008; + else if (sd->status.class == 4022) + sd->status.class = sd->view_class = 4015; + } + } else { + if (pc_isriding(sd)) { // sd have the new value... + if (sd->disguise > 0) { // temporary prevention of crash caused by peco + disguise, will look into a better solution [Valaris] (code added by [Yor]) + sd->status.option &= ~0x0020; + } else { + if (sd->status.class == 7) + sd->status.class = sd->view_class = 13; + else if (sd->status.class == 14) + sd->status.class = sd->view_class = 21; + else if (sd->status.class == 4008) + sd->status.class = sd->view_class = 4014; + else if (sd->status.class == 4015) + sd->status.class = sd->view_class = 4022; + else + sd->status.option &= ~0x0020; + } + } + } + + clif_changeoption(&sd->bl); + pc_calcstatus(sd, 0); + clif_displaymessage(fd, msg_table[9]); // Options changed. + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_hide( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + if (sd->status.option & OPTION_HIDE) { + sd->status.option &= ~OPTION_HIDE; + clif_displaymessage(fd, msg_table[10]); // Invisible: Off + } else { + sd->status.option |= OPTION_HIDE; + clif_displaymessage(fd, msg_table[11]); // Invisible: On + } + clif_changeoption(&sd->bl); + + return 0; +} + +/*========================================== + * 転職する upperを指定すると転生や養子にもなれる + *------------------------------------------ + */ +int atcommand_jobchange( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + int job = 0, upper = -1; + + if (!message || !*message || sscanf(message, "%d %d", &job, &upper) < 1) { + clif_displaymessage(fd, "Please, enter job ID (usage: @job/@jobchange <job ID>)."); + return -1; + } + + if (job == 37 ||job == 45) + return 0; + + if ((job >= 0 && job < MAX_PC_CLASS)) { + + // fix pecopeco display + if ((job != 13 && job != 21 && job != 4014 && job != 4022)) { + if (pc_isriding(sd)) { + if (sd->status.class == 13) + sd->status.class = sd->view_class = 7; + if (sd->status.class == 21) + sd->status.class = sd->view_class = 14; + if (sd->status.class == 4014) + sd->status.class = sd->view_class = 4008; + if (sd->status.class == 4022) + sd->status.class = sd->view_class = 4015; + sd->status.option &= ~0x0020; + clif_changeoption(&sd->bl); + pc_calcstatus(sd, 0); + } + } else { + if (!pc_isriding(sd)) { + if (job == 13) + job = 7; + if (job == 21) + job = 14; + if (job == 4014) + job = 4008; + if (job == 4022) + job = 4015; + } + } + + if (pc_jobchange(sd, job, upper) == 0) + clif_displaymessage(fd, msg_table[12]); // Your job has been changed. + else { + clif_displaymessage(fd, msg_table[155]); // Impossible to change your job. + return -1; + } + } else { + clif_displaymessage(fd, "Please, enter a valid job ID (usage: @job/@jobchange <job ID>)."); + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_die( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + pc_damage(NULL, sd, sd->status.hp + 1); + clif_displaymessage(fd, msg_table[13]); // A pity! You've died. + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_kill( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char character[100]; + struct map_session_data *pl_sd; + + memset(character, '\0', sizeof(character)); + + if (!message || !*message || sscanf(message, "%99[^\n]", character) < 1) { + clif_displaymessage(fd, "Please, enter a player name (usage: @kill <char name>)."); + return -1; + } + + if ((pl_sd = map_nick2sd(character)) != NULL) { + if (pc_isGM(sd) >= pc_isGM(pl_sd)) { // you can kill only lower or same level + pc_damage(NULL, pl_sd, pl_sd->status.hp + 1); + clif_displaymessage(fd, msg_table[14]); // Character killed. + } else { + clif_displaymessage(fd, msg_table[81]); // Your GM level don't authorise you to do this action on this player. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[3]); // Character not found. + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_alive( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + sd->status.hp = sd->status.max_hp; + sd->status.sp = sd->status.max_sp; + pc_setstand(sd); + if (battle_config.pc_invincible_time > 0) + pc_setinvincibletimer(sd, battle_config.pc_invincible_time); + clif_updatestatus(sd, SP_HP); + clif_updatestatus(sd, SP_SP); + clif_resurrection(&sd->bl, 1); + clif_displaymessage(fd, msg_table[16]); // You've been revived! It's a miracle! + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_kami( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char output[200]; + + memset(output, '\0', sizeof(output)); + + if (!message || !*message) { + clif_displaymessage(fd, "Please, enter a message (usage: @kami <message>)."); + return -1; + } + + sscanf(message, "%199[^\n]", output); + intif_GMmessage(output, strlen(output) + 1, (*(command + 5) == 'b') ? 0x10 : 0); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_heal( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + int hp = 0, sp = 0; // [Valaris] thanks to fov + + sscanf(message, "%d %d", &hp, &sp); + + if (hp == 0 && sp == 0) { + hp = sd->status.max_hp - sd->status.hp; + sp = sd->status.max_sp - sd->status.sp; + } else { + if (hp > 0 && (hp > sd->status.max_hp || hp > (sd->status.max_hp - sd->status.hp))) // fix positiv overflow + hp = sd->status.max_hp - sd->status.hp; + else if (hp < 0 && (hp < -sd->status.max_hp || hp < (1 - sd->status.hp))) // fix negativ overflow + hp = 1 - sd->status.hp; + if (sp > 0 && (sp > sd->status.max_sp || sp > (sd->status.max_sp - sd->status.sp))) // fix positiv overflow + sp = sd->status.max_sp - sd->status.sp; + else if (sp < 0 && (sp < -sd->status.max_sp || sp < (1 - sd->status.sp))) // fix negativ overflow + sp = 1 - sd->status.sp; + } + + if (hp > 0) // display like heal + clif_heal(fd, SP_HP, hp); + else if (hp < 0) // display like damage + clif_damage(&sd->bl,&sd->bl, gettick(), 0, 0, -hp, 0 , 4, 0); + if (sp > 0) // no display when we lost SP + clif_heal(fd, SP_SP, sp); + + if (hp != 0 || sp != 0) { + pc_heal(sd, hp, sp); + if (hp >= 0 && sp >= 0) + clif_displaymessage(fd, msg_table[17]); // HP, SP recovered. + else + clif_displaymessage(fd, msg_table[156]); // HP or/and SP modified. + } else { + clif_displaymessage(fd, msg_table[157]); // HP and SP are already with the good value. + return -1; + } + + return 0; +} + +/*========================================== + * @item command (usage: @item <name/id_of_item> <quantity>) (modified by [Yor] for pet_egg) + *------------------------------------------ + */ +int atcommand_item( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char item_name[100]; + int number = 0, item_id, flag; + struct item item_tmp; + struct item_data *item_data; + int get_count, i, pet_id; + + memset(item_name, '\0', sizeof(item_name)); + + if (!message || !*message || sscanf(message, "%99s %d", item_name, &number) < 1) { + clif_displaymessage(fd, "Please, enter an item name/id (usage: @item <item name or ID> [quantity])."); + return -1; + } + + if (number <= 0) + number = 1; + + item_id = 0; + if ((item_data = itemdb_searchname(item_name)) != NULL || + (item_data = itemdb_exists(atoi(item_name))) != NULL) + item_id = item_data->nameid; + + if (item_id >= 500) { + get_count = number; + // check pet egg + pet_id = search_petDB_index(item_id, PET_EGG); + if (item_data->type == 4 || item_data->type == 5 || + item_data->type == 7 || item_data->type == 8) { + get_count = 1; + } + for (i = 0; i < number; i += get_count) { + // if pet egg + if (pet_id >= 0) { + sd->catch_target_class = pet_db[pet_id].class; + intif_create_pet(sd->status.account_id, sd->status.char_id, + pet_db[pet_id].class, mob_db[pet_db[pet_id].class].lv, + pet_db[pet_id].EggID, 0, pet_db[pet_id].intimate, + 100, 0, 1, pet_db[pet_id].jname); + // if not pet egg + } else { + memset(&item_tmp, 0, sizeof(item_tmp)); + item_tmp.nameid = item_id; + item_tmp.identify = 1; + if ((flag = pc_additem((struct map_session_data*)sd, &item_tmp, get_count))) + clif_additem((struct map_session_data*)sd, 0, 0, flag); + } + } + clif_displaymessage(fd, msg_table[18]); // Item created. + } else { + clif_displaymessage(fd, msg_table[19]); // Invalid item ID or name. + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_item2( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + struct item item_tmp; + struct item_data *item_data; + char item_name[100]; + int item_id, number = 0; + int identify = 0, refine = 0, attr = 0; + int c1 = 0, c2 = 0, c3 = 0, c4 = 0; + int flag; + int loop, get_count, i; + + memset(item_name, '\0', sizeof(item_name)); + + if (!message || !*message || sscanf(message, "%99s %d %d %d %d %d %d %d %d", item_name, &number, &identify, &refine, &attr, &c1, &c2, &c3, &c4) < 9) { + clif_displaymessage(fd, "Please, enter all informations (usage: @item2 <item name or ID> <quantity>"); + clif_displaymessage(fd, " <Identify_flag> <refine> <attribut> <Card1> <Card2> <Card3> <Card4>)."); + return -1; + } + + if (number <= 0) + number = 1; + + item_id = 0; + if ((item_data = itemdb_searchname(item_name)) != NULL || + (item_data = itemdb_exists(atoi(item_name))) != NULL) + item_id = item_data->nameid; + + if (item_id > 500) { + loop = 1; + get_count = number; + if (item_data->type == 4 || item_data->type == 5 || + item_data->type == 7 || item_data->type == 8) { + loop = number; + get_count = 1; + if (item_data->type == 7) { + identify = 1; + refine = 0; + } + if (item_data->type == 8) + refine = 0; + if (refine > 10) + refine = 10; + } else { + identify = 1; + refine = attr = 0; + } + for (i = 0; i < loop; i++) { + memset(&item_tmp, 0, sizeof(item_tmp)); + item_tmp.nameid = item_id; + item_tmp.identify = identify; + item_tmp.refine = refine; + item_tmp.attribute = attr; + item_tmp.card[0] = c1; + item_tmp.card[1] = c2; + item_tmp.card[2] = c3; + item_tmp.card[3] = c4; + if ((flag = pc_additem(sd, &item_tmp, get_count))) + clif_additem(sd, 0, 0, flag); + } + clif_displaymessage(fd, msg_table[18]); // Item created. + } else { + clif_displaymessage(fd, msg_table[19]); // Invalid item ID or name. + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_itemreset( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + int i; + + for (i = 0; i < MAX_INVENTORY; i++) { + if (sd->status.inventory[i].amount && sd->status.inventory[i].equip == 0) + pc_delitem(sd, i, sd->status.inventory[i].amount, 0); + } + clif_displaymessage(fd, msg_table[20]); // All of your items have been removed. + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_itemcheck( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + pc_checkitem(sd); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_baselevelup( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + int level, i; + + if (!message || !*message || (level = atoi(message)) == 0) { + clif_displaymessage(fd, "Please, enter a level adjustement (usage: @lvup/@blevel/@baselvlup <number of levels>)."); + return -1; + } + + if (level > 0) { + if (sd->status.base_level == battle_config.maximum_level) { // check for max level by Valaris + clif_displaymessage(fd, msg_table[47]); // Base level can't go any higher. + return -1; + } // End Addition + if (level > battle_config.maximum_level || level > (battle_config.maximum_level - sd->status.base_level)) // fix positiv overflow + level = battle_config.maximum_level - sd->status.base_level; + for (i = 1; i <= level; i++) + sd->status.status_point += (sd->status.base_level + i + 14) / 5; + sd->status.base_level += level; + clif_updatestatus(sd, SP_BASELEVEL); + clif_updatestatus(sd, SP_NEXTBASEEXP); + clif_updatestatus(sd, SP_STATUSPOINT); + pc_calcstatus(sd, 0); + pc_heal(sd, sd->status.max_hp, sd->status.max_sp); + clif_misceffect(&sd->bl, 0); + clif_displaymessage(fd, msg_table[21]); // Base level raised. + } else { + if (sd->status.base_level == 1) { + clif_displaymessage(fd, msg_table[158]); // Base level can't go any lower. + return -1; + } + if (level < -battle_config.maximum_level || level < (1 - sd->status.base_level)) // fix negativ overflow + level = 1 - sd->status.base_level; + if (sd->status.status_point > 0) { + for (i = 0; i > level; i--) + sd->status.status_point -= (sd->status.base_level + i + 14) / 5; + if (sd->status.status_point < 0) + sd->status.status_point = 0; + clif_updatestatus(sd, SP_STATUSPOINT); + } // to add: remove status points from stats + sd->status.base_level += level; + clif_updatestatus(sd, SP_BASELEVEL); + clif_updatestatus(sd, SP_NEXTBASEEXP); + pc_calcstatus(sd, 0); + clif_displaymessage(fd, msg_table[22]); // Base level lowered. + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_joblevelup( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + int up_level = 50, level; + + if (!message || !*message || (level = atoi(message)) == 0) { + clif_displaymessage(fd, "Please, enter a level adjustement (usage: @joblvup/@jlevel/@joblvlup <number of levels>)."); + return -1; + } + + if (sd->status.class == 0 || sd->status.class == 4001) + up_level -= 40; + else if ((sd->status.class > 4007 && sd->status.class < 4024) || sd->status.class == 23) + up_level += 20; + + if (level > 0) { + if (sd->status.job_level == up_level) { + clif_displaymessage(fd, msg_table[23]); // Job level can't go any higher. + return -1; + } + if (level > up_level || level > (up_level - sd->status.job_level)) // fix positiv overflow + level = up_level - sd->status.job_level; + sd->status.job_level += level; + clif_updatestatus(sd, SP_JOBLEVEL); + clif_updatestatus(sd, SP_NEXTJOBEXP); + sd->status.skill_point += level; + clif_updatestatus(sd, SP_SKILLPOINT); + pc_calcstatus(sd, 0); + clif_misceffect(&sd->bl, 1); + clif_displaymessage(fd, msg_table[24]); // Job level raised. + } else { + if (sd->status.job_level == 1) { + clif_displaymessage(fd, msg_table[159]); // Job level can't go any lower. + return -1; + } + if (level < -up_level || level < (1 - sd->status.job_level)) // fix negativ overflow + level = 1 - sd->status.job_level; + sd->status.job_level += level; + clif_updatestatus(sd, SP_JOBLEVEL); + clif_updatestatus(sd, SP_NEXTJOBEXP); + if (sd->status.skill_point > 0) { + sd->status.skill_point += level; + if (sd->status.skill_point < 0) + sd->status.skill_point = 0; + clif_updatestatus(sd, SP_SKILLPOINT); + } // to add: remove status points from skills + pc_calcstatus(sd, 0); + clif_displaymessage(fd, msg_table[25]); // Job level lowered. + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_help( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char buf[2048], w1[2048], w2[2048]; + int i, gm_level; + FILE* fp; + + memset(buf, '\0', sizeof(buf)); + + if ((fp = fopen(help_txt, "r")) != NULL) { + clif_displaymessage(fd, msg_table[26]); // Help commands: + gm_level = pc_isGM(sd); + while(fgets(buf, sizeof(buf) - 1, fp) != NULL) { + if (buf[0] == '/' && buf[1] == '/') + continue; + for (i = 0; buf[i] != '\0'; i++) { + if (buf[i] == '\r' || buf[i] == '\n') { + buf[i] = '\0'; + break; + } + } + if (sscanf(buf, "%2047[^:]:%2047[^\n]", w1, w2) < 2) + clif_displaymessage(fd, buf); + else if (gm_level >= atoi(w1)) + clif_displaymessage(fd, w2); + } + fclose(fp); + } else { + clif_displaymessage(fd, msg_table[27]); // File help.txt not found. + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_gm( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char password[100]; + + memset(password, '\0', sizeof(password)); + + if (!message || !*message || sscanf(message, "%99[^\n]", password) < 1) { + clif_displaymessage(fd, "Please, enter a password (usage: @gm <password>)."); + return -1; + } + + if (pc_isGM(sd)) { // a GM can not use this function. only a normal player (become gm is not for gm!) + clif_displaymessage(fd, msg_table[50]); // You already have some GM powers. + return -1; + } else + chrif_changegm(sd->status.account_id, password, strlen(password) + 1); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_pvpoff( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + struct map_session_data *pl_sd; + int i; + + if (battle_config.pk_mode) { //disable command if server is in PK mode [Valaris] + clif_displaymessage(fd, msg_table[52]); // This option cannot be used in PK Mode. + return -1; + } + + if (map[sd->bl.m].flag.pvp) { + map[sd->bl.m].flag.pvp = 0; + clif_send0199(sd->bl.m, 0); + for (i = 0; i < fd_max; i++) { //人数分ループ + if (session[i] && (pl_sd = session[i]->session_data) && pl_sd->state.auth) { + if (sd->bl.m == pl_sd->bl.m) { + clif_pvpset(pl_sd, 0, 0, 2); + if (pl_sd->pvp_timer != -1) { + delete_timer(pl_sd->pvp_timer, pc_calc_pvprank_timer); + pl_sd->pvp_timer = -1; + } + } + } + } + clif_displaymessage(fd, msg_table[31]); // PvP: Off. + } else { + clif_displaymessage(fd, msg_table[160]); // PvP is already Off. + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_pvpon( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + struct map_session_data *pl_sd; + int i; + + if (battle_config.pk_mode) { //disable command if server is in PK mode [Valaris] + clif_displaymessage(fd, msg_table[52]); // This option cannot be used in PK Mode. + return -1; + } + + if (!map[sd->bl.m].flag.pvp && !map[sd->bl.m].flag.nopvp) { + map[sd->bl.m].flag.pvp = 1; + clif_send0199(sd->bl.m, 1); + for (i = 0; i < fd_max; i++) { + if (session[i] && (pl_sd = session[i]->session_data) && pl_sd->state.auth) { + if (sd->bl.m == pl_sd->bl.m && pl_sd->pvp_timer == -1) { + pl_sd->pvp_timer = add_timer(gettick() + 200, + pc_calc_pvprank_timer, pl_sd->bl.id, 0); + pl_sd->pvp_rank = 0; + pl_sd->pvp_lastusers = 0; + pl_sd->pvp_point = 5; + } + } + } + clif_displaymessage(fd, msg_table[32]); // PvP: On. + } else { + clif_displaymessage(fd, msg_table[161]); // PvP is already On. + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_gvgoff( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + if (map[sd->bl.m].flag.gvg) { + map[sd->bl.m].flag.gvg = 0; + clif_send0199(sd->bl.m, 0); + clif_displaymessage(fd, msg_table[33]); // GvG: Off. + } else { + clif_displaymessage(fd, msg_table[162]); // GvG is already Off. + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_gvgon( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + if (!map[sd->bl.m].flag.gvg) { + map[sd->bl.m].flag.gvg = 1; + clif_send0199(sd->bl.m, 3); + clif_displaymessage(fd, msg_table[34]); // GvG: On. + } else { + clif_displaymessage(fd, msg_table[163]); // GvG is already On. + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_model( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + int hair_style = 0, hair_color = 0, cloth_color = 0; + char output[200]; + + memset(output, '\0', sizeof(output)); + + if (!message || !*message || sscanf(message, "%d %d %d", &hair_style, &hair_color, &cloth_color) < 1) { + sprintf(output, "Please, enter at least a value (usage: @model <hair ID: %d-%d> <hair color: %d-%d> <clothes color: %d-%d>).", + MIN_HAIR_STYLE, MAX_HAIR_STYLE, MIN_HAIR_COLOR, MAX_HAIR_COLOR, MIN_CLOTH_COLOR, MAX_CLOTH_COLOR); + clif_displaymessage(fd, output); + return -1; + } + + if (hair_style >= MIN_HAIR_STYLE && hair_style <= MAX_HAIR_STYLE && + hair_color >= MIN_HAIR_COLOR && hair_color <= MAX_HAIR_COLOR && + cloth_color >= MIN_CLOTH_COLOR && cloth_color <= MAX_CLOTH_COLOR) { + //服の色変更 + if (cloth_color != 0 && sd->status.sex == 1 && (sd->status.class == 12 || sd->status.class == 17)) { + //服の色未実装職の判定 + clif_displaymessage(fd, msg_table[35]); // You can't use this command with this class. + return -1; + } else { + pc_changelook(sd, LOOK_HAIR, hair_style); + pc_changelook(sd, LOOK_HAIR_COLOR, hair_color); + pc_changelook(sd, LOOK_CLOTHES_COLOR, cloth_color); + clif_displaymessage(fd, msg_table[36]); // Appearence changed. + } + } else { + clif_displaymessage(fd, msg_table[37]); // An invalid number was specified. + return -1; + } + + return 0; +} + +/*========================================== + * @dye && @ccolor + *------------------------------------------ + */ +int atcommand_dye(const int fd, struct map_session_data* sd, const char* command, const char* message) +{ + int cloth_color = 0; + char output[200]; + + memset(output, '\0', sizeof(output)); + + if (!message || !*message || sscanf(message, "%d", &cloth_color) < 1) { + sprintf(output, "Please, enter a clothes color (usage: @dye/@ccolor <clothes color: %d-%d>).", MIN_CLOTH_COLOR, MAX_CLOTH_COLOR); + clif_displaymessage(fd, output); + return -1; + } + + if (cloth_color >= MIN_CLOTH_COLOR && cloth_color <= MAX_CLOTH_COLOR) { + if (cloth_color != 0 && sd->status.sex == 1 && (sd->status.class == 12 || sd->status.class == 17)) { + clif_displaymessage(fd, msg_table[35]); // You can't use this command with this class. + return -1; + } else { + pc_changelook(sd, LOOK_CLOTHES_COLOR, cloth_color); + clif_displaymessage(fd, msg_table[36]); // Appearence changed. + } + } else { + clif_displaymessage(fd, msg_table[37]); // An invalid number was specified. + return -1; + } + + return 0; +} + +/*========================================== + * @chardye by [MouseJstr] + *------------------------------------------ + */ +int +atcommand_chardye(const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + return 0; +} + +/*========================================== + * @hairstyle && @hstyle + *------------------------------------------ + */ +int atcommand_hair_style(const int fd, struct map_session_data* sd, const char* command, const char* message) +{ + int hair_style = 0; + char output[200]; + + memset(output, '\0', sizeof(output)); + + if (!message || !*message || sscanf(message, "%d", &hair_style) < 1) { + sprintf(output, "Please, enter a hair style (usage: @hairstyle/@hstyle <hair ID: %d-%d>).", MIN_HAIR_STYLE, MAX_HAIR_STYLE); + clif_displaymessage(fd, output); + return -1; + } + + if (hair_style >= MIN_HAIR_STYLE && hair_style <= MAX_HAIR_STYLE) { + if (hair_style != 0 && sd->status.sex == 1 && (sd->status.class == 12 || sd->status.class == 17)) { + clif_displaymessage(fd, msg_table[35]); // You can't use this command with this class. + return -1; + } else { + pc_changelook(sd, LOOK_HAIR, hair_style); + clif_displaymessage(fd, msg_table[36]); // Appearence changed. + } + } else { + clif_displaymessage(fd, msg_table[37]); // An invalid number was specified. + return -1; + } + + return 0; +} + +/*========================================== + * @charhairstyle by [MouseJstr] + *------------------------------------------ + */ +int +atcommand_charhairstyle(const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + return 0; +} + +/*========================================== + * @haircolor && @hcolor + *------------------------------------------ + */ +int atcommand_hair_color(const int fd, struct map_session_data* sd, const char* command, const char* message) +{ + int hair_color = 0; + char output[200]; + + memset(output, '\0', sizeof(output)); + + if (!message || !*message || sscanf(message, "%d", &hair_color) < 1) { + sprintf(output, "Please, enter a hair color (usage: @haircolor/@hcolor <hair color: %d-%d>).", MIN_HAIR_COLOR, MAX_HAIR_COLOR); + clif_displaymessage(fd, output); + return -1; + } + + if (hair_color >= MIN_HAIR_COLOR && hair_color <= MAX_HAIR_COLOR) { + if (hair_color != 0 && sd->status.sex == 1 && (sd->status.class == 12 || sd->status.class == 17)) { + clif_displaymessage(fd, msg_table[35]); // You can't use this command with this class. + return -1; + } else { + pc_changelook(sd, LOOK_HAIR_COLOR, hair_color); + clif_displaymessage(fd, msg_table[36]); // Appearence changed. + } + } else { + clif_displaymessage(fd, msg_table[37]); // An invalid number was specified. + return -1; + } + + return 0; +} + +/*========================================== + * @charhaircolor by [MouseJstr] + *------------------------------------------ + */ +int +atcommand_charhaircolor(const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + return 0; +} + +/*========================================== + * @go [city_number/city_name]: improved by [yor] to add city names and help + *------------------------------------------ + */ +int atcommand_go( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + int i; + int town; + char map_name[100]; + char output[200]; + int m; + + struct { char map[16]; int x, y; } data[] = { + { "prontera.gat", 156, 191 }, // 0=Prontera + { "morocc.gat", 156, 93 }, // 1=Morroc + { "geffen.gat", 119, 59 }, // 2=Geffen + { "payon.gat", 162, 233 }, // 3=Payon + { "alberta.gat", 192, 147 }, // 4=Alberta + { "izlude.gat", 128, 114 }, // 5=Izlude + { "aldebaran.gat",140, 131 }, // 6=Al de Baran + { "xmas.gat", 147, 134 }, // 7=Lutie + { "comodo.gat", 209, 143 }, // 8=Comodo + { "yuno.gat", 157, 51 }, // 9=Yuno + { "amatsu.gat", 198, 84 }, // 10=Amatsu + { "gonryun.gat", 160, 120 }, // 11=Gon Ryun + { "umbala.gat", 89, 157 }, // 12=Umbala + { "niflheim.gat", 21, 153 }, // 13=Niflheim + { "louyang.gat", 217, 40 }, // 14=Lou Yang + { "new_1-1.gat", 53, 111 }, // 15=Start point + { "sec_pri.gat", 23, 61 }, // 16=Prison + }; + + memset(map_name, '\0', sizeof(map_name)); + memset(output, '\0', sizeof(output)); + + // get the number + town = atoi(message); + + // if no value, display all value + if (!message || !*message || sscanf(message, "%99s", map_name) < 1 || town < -3 || town >= (int)(sizeof(data) / sizeof(data[0]))) { + clif_displaymessage(fd, msg_table[38]); // Invalid location number or name. + clif_displaymessage(fd, msg_table[82]); // Please, use one of this number/name: + clif_displaymessage(fd, "-3=(Memo point 2) 4=Alberta 11=Gon Ryun"); + clif_displaymessage(fd, "-2=(Memo point 1) 5=Izlude 12=Umbala"); + clif_displaymessage(fd, "-1=(Memo point 0) 6=Al de Baran 13=Niflheim"); + clif_displaymessage(fd, " 0=Prontera 7=Lutie 14=Lou Yang"); + clif_displaymessage(fd, " 1=Morroc 8=Comodo 15=Start point"); + clif_displaymessage(fd, " 2=Geffen 9=Yuno 16=Prison"); + clif_displaymessage(fd, " 3=Payon 10=Amatsu"); + return -1; + } else { + // get possible name of the city and add .gat if not in the name + map_name[sizeof(map_name)-1] = '\0'; + for (i = 0; map_name[i]; i++) + map_name[i] = tolower(map_name[i]); + if (strstr(map_name, ".gat") == NULL && strstr(map_name, ".afm") == NULL && strlen(map_name) < 13) // 16 - 4 (.gat) + strcat(map_name, ".gat"); + // try to see if it's a name, and not a number (try a lot of possibilities, write errors and abbreviations too) + if (strncmp(map_name, "prontera.gat", 3) == 0) { // 3 first characters + town = 0; + } else if (strncmp(map_name, "morocc.gat", 3) == 0) { // 3 first characters + town = 1; + } else if (strncmp(map_name, "geffen.gat", 3) == 0) { // 3 first characters + town = 2; + } else if (strncmp(map_name, "payon.gat", 3) == 0 || // 3 first characters + strncmp(map_name, "paion.gat", 3) == 0) { // writing error (3 first characters) + town = 3; + } else if (strncmp(map_name, "alberta.gat", 3) == 0) { // 3 first characters + town = 4; + } else if (strncmp(map_name, "izlude.gat", 3) == 0 || // 3 first characters + strncmp(map_name, "islude.gat", 3) == 0) { // writing error (3 first characters) + town = 5; + } else if (strncmp(map_name, "aldebaran.gat", 3) == 0 || // 3 first characters + strcmp(map_name, "al.gat") == 0) { // al (de baran) + town = 6; + } else if (strncmp(map_name, "lutie.gat", 3) == 0 || // name of the city, not name of the map (3 first characters) + strcmp(map_name, "christmas.gat") == 0 || // name of the symbol + strncmp(map_name, "xmas.gat", 3) == 0 || // 3 first characters + strncmp(map_name, "x-mas.gat", 3) == 0) { // writing error (3 first characters) + town = 7; + } else if (strncmp(map_name, "comodo.gat", 3) == 0) { // 3 first characters + town = 8; + } else if (strncmp(map_name, "yuno.gat", 3) == 0) { // 3 first characters + town = 9; + } else if (strncmp(map_name, "amatsu.gat", 3) == 0 || // 3 first characters + strncmp(map_name, "ammatsu.gat", 3) == 0) { // writing error (3 first characters) + town = 10; + } else if (strncmp(map_name, "gonryun.gat", 3) == 0) { // 3 first characters + town = 11; + } else if (strncmp(map_name, "umbala.gat", 3) == 0) { // 3 first characters + town = 12; + } else if (strncmp(map_name, "niflheim.gat", 3) == 0) { // 3 first characters + town = 13; + } else if (strncmp(map_name, "louyang.gat", 3) == 0) { // 3 first characters + town = 14; + } else if (strncmp(map_name, "new_1-1.gat", 3) == 0 || // 3 first characters (or "newbies") + strncmp(map_name, "startpoint.gat", 3) == 0 || // name of the position (3 first characters) + strncmp(map_name, "begining.gat", 3) == 0) { // name of the position (3 first characters) + town = 15; + } else if (strncmp(map_name, "sec_pri.gat", 3) == 0 || // 3 first characters + strncmp(map_name, "prison.gat", 3) == 0 || // name of the position (3 first characters) + strncmp(map_name, "jails.gat", 3) == 0) { // name of the position + town = 16; + } + + if (town >= -3 && town <= -1) { + if (sd->status.memo_point[-town-1].map[0]) { + m = map_mapname2mapid(sd->status.memo_point[-town-1].map); + if (m >= 0 && map[m].flag.nowarpto && battle_config.any_warp_GM_min_level > pc_isGM(sd)) { + clif_displaymessage(fd, "You are not authorised to warp you to this memo map."); + return -1; + } + if (sd->bl.m >= 0 && map[sd->bl.m].flag.nowarp && battle_config.any_warp_GM_min_level > pc_isGM(sd)) { + clif_displaymessage(fd, "You are not authorised to warp you from your actual map."); + return -1; + } + if (pc_setpos(sd, sd->status.memo_point[-town-1].map, sd->status.memo_point[-town-1].x, sd->status.memo_point[-town-1].y, 3) == 0) { + clif_displaymessage(fd, msg_table[0]); // Warped. + } else { + clif_displaymessage(fd, msg_table[1]); // Map not found. + return -1; + } + } else { + sprintf(output, msg_table[164], -town-1); // Your memo point #%d doesn't exist. + clif_displaymessage(fd, output); + return -1; + } + } else if (town >= 0 && town < (int)(sizeof(data) / sizeof(data[0]))) { + m = map_mapname2mapid(data[town].map); + if (m >= 0 && map[m].flag.nowarpto && battle_config.any_warp_GM_min_level > pc_isGM(sd)) { + clif_displaymessage(fd, "You are not authorised to warp you to this destination map."); + return -1; + } + if (sd->bl.m >= 0 && map[sd->bl.m].flag.nowarp && battle_config.any_warp_GM_min_level > pc_isGM(sd)) { + clif_displaymessage(fd, "You are not authorised to warp you from your actual map."); + return -1; + } + if (pc_setpos(sd, data[town].map, data[town].x, data[town].y, 3) == 0) { + clif_displaymessage(fd, msg_table[0]); // Warped. + } else { + clif_displaymessage(fd, msg_table[1]); // Map not found. + return -1; + } + } else { // if you arrive here, you have an error in town variable when reading of names + clif_displaymessage(fd, msg_table[38]); // Invalid location number or name. + return -1; + } + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_monster( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char name[100]; + char monster[100]; + char output[200]; + int mob_id; + int number = 0; + int x = 0, y = 0; + int count; + int i, j, k; + int mx, my, range; + + memset(name, '\0', sizeof(name)); + memset(monster, '\0', sizeof(monster)); + memset(output, '\0', sizeof(output)); + + if (!message || !*message || + (sscanf(message, "\"%[^\"]\" %99s %d %d %d", name, monster, &number, &x, &y) < 2 && + sscanf(message, "%99s \"%[^\"]\" %d %d %d", monster, name, &number, &x, &y) < 2 && + sscanf(message, "%99s %99s %d %d %d", name, monster, &number, &x, &y) < 2)) { + clif_displaymessage(fd, msg_table[80]); // Give a display name and monster name/id please. + return -1; + } + + if ((mob_id = mobdb_searchname(monster)) == 0) // check name first (to avoid possible name begining by a number) + mob_id = mobdb_checkid(atoi(monster)); + + if (mob_id == 0) { + clif_displaymessage(fd, msg_table[40]); // Invalid monster ID or name. + return -1; + } + + if (mob_id == 1288) { + clif_displaymessage(fd, msg_table[83]); // Cannot spawn emperium. + return -1; + } + + if (number <= 0) + number = 1; + + if (strlen(name) < 1) + strcpy(name, "--ja--"); + + // If value of atcommand_spawn_quantity_limit directive is greater than or equal to 1 and quantity of monsters is greater than value of the directive + if (battle_config.atc_spawn_quantity_limit >= 1 && number > battle_config.atc_spawn_quantity_limit) + number = battle_config.atc_spawn_quantity_limit; + + if (battle_config.etc_log) + printf("%s monster='%s' name='%s' id=%d count=%d (%d,%d)\n", command, monster, name, mob_id, number, x, y); + + count = 0; + range = sqrt(number) / 2; + range = range * 2 + 5; // calculation of an odd number (+ 4 area around) + for (i = 0; i < number; i++) { + j = 0; + k = 0; + while(j++ < 8 && k == 0) { // try 8 times to spawn the monster (needed for close area) + if (x <= 0) + mx = sd->bl.x + (rand() % range - (range / 2)); + else + mx = x; + if (y <= 0) + my = sd->bl.y + (rand() % range - (range / 2)); + else + my = y; + k = mob_once_spawn((struct map_session_data*)sd, "this", mx, my, name, mob_id, 1, ""); + } + count += (k != 0) ? 1 : 0; + } + + if (count != 0) + if (number == count) + clif_displaymessage(fd, msg_table[39]); // All monster summoned! + else { + sprintf(output, msg_table[240], count); // %d monster(s) summoned! + clif_displaymessage(fd, output); + } + else { + clif_displaymessage(fd, msg_table[40]); // Invalid monster ID or name. + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_spawn( + const int fd, struct map_session_data* sd, + const char* command, const char* message) { + char name[100]; + char monster[100]; + char output[200]; + int mob_id; + int number = 0; + int x = 0, y = 0; + int count; + int i, j, k; + int mx, my, range; + + memset(name, '\0', sizeof(name)); + memset(monster, '\0', sizeof(monster)); + memset(output, '\0', sizeof(output)); + + if (!message || !*message || + (sscanf(message, "\"%[^\"]\" %99s %d %d %d", name, monster, &number, &x, &y) < 2 && + sscanf(message, "%99s \"%[^\"]\" %d %d %d", monster, name, &number, &x, &y) < 2 && + sscanf(message, "%99s %d %99s %d %d", monster, &number, name, &x, &y) < 1)) { + clif_displaymessage(fd, msg_table[143]); // Give a monster name/id please. + return -1; + } + + // If monster identifier/name argument is a name + if ((mob_id = mobdb_searchname(monster)) == 0) // check name first (to avoid possible name begining by a number) + mob_id = mobdb_checkid(atoi(monster)); + + if (mob_id == 0) { + clif_displaymessage(fd, msg_table[40]); // Invalid monster ID or name. + return -1; + } + + if (mob_id == 1288) { + clif_displaymessage(fd, msg_table[83]); // Cannot spawn emperium. + return -1; + } + + if (number <= 0) + number = 1; + + if (strlen(name) < 1) + strcpy(name, "--ja--"); + + // If value of atcommand_spawn_quantity_limit directive is greater than or equal to 1 and quantity of monsters is greater than value of the directive + if (battle_config.atc_spawn_quantity_limit >= 1 && number > battle_config.atc_spawn_quantity_limit) + number = battle_config.atc_spawn_quantity_limit; + + if (battle_config.etc_log) + printf("%s monster='%s' name='%s' id=%d count=%d (%d,%d)\n", command, monster, name, mob_id, number, x, y); + + count = 0; + range = sqrt(number) / 2; + range = range * 2 + 5; // calculation of an odd number (+ 4 area around) + for (i = 0; i < number; i++) { + j = 0; + k = 0; + while(j++ < 8 && k == 0) { // try 8 times to spawn the monster (needed for close area) + if (x <= 0) + mx = sd->bl.x + (rand() % range - (range / 2)); + else + mx = x; + if (y <= 0) + my = sd->bl.y + (rand() % range - (range / 2)); + else + my = y; + k = mob_once_spawn((struct map_session_data*)sd, "this", mx, my, name, mob_id, 1, ""); + } + count += (k != 0) ? 1 : 0; + } + + if (count != 0) + if (number == count) + clif_displaymessage(fd, msg_table[39]); // All monster summoned! + else { + sprintf(output, msg_table[240], count); // %d monster(s) summoned! + clif_displaymessage(fd, output); + } + else { + clif_displaymessage(fd, msg_table[40]); // Invalid monster ID or name. + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +void atcommand_killmonster_sub( + const int fd, struct map_session_data* sd, const char* message, + const int drop) +{ + int map_id; + char map_name[100]; + + memset(map_name, '\0', sizeof(map_name)); + + if (!message || !*message || sscanf(message, "%99s", map_name) < 1) + map_id = sd->bl.m; + else { + if (strstr(map_name, ".gat") == NULL && strstr(map_name, ".afm") == NULL && strlen(map_name) < 13) // 16 - 4 (.gat) + strcat(map_name, ".gat"); + if ((map_id = map_mapname2mapid(map_name)) < 0) + map_id = sd->bl.m; + } + + map_foreachinarea(atkillmonster_sub, map_id, 0, 0, map[map_id].xs, map[map_id].ys, BL_MOB, drop); + + clif_displaymessage(fd, msg_table[165]); // All monsters killed! + + return; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_killmonster( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + atcommand_killmonster_sub(fd, sd, message, 1); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_killmonster2( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + atcommand_killmonster_sub(fd, sd, message, 0); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_refine( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + int i, position = 0, refine = 0, current_position, final_refine; + int count; + char output[200]; + + memset(output, '\0', sizeof(output)); + + if (!message || !*message || sscanf(message, "%d %d", &position, &refine) < 2) { + clif_displaymessage(fd, "Please, enter a position and a amount (usage: @refine <equip position> <+/- amount>)."); + return -1; + } + + if (refine < -10) + refine = -10; + else if (refine > 10) + refine = 10; + else if (refine == 0) + refine = 1; + + count = 0; + for (i = 0; i < MAX_INVENTORY; i++) { + if (sd->status.inventory[i].nameid && // 該当個所の装備を精錬する + (sd->status.inventory[i].equip & position || + (sd->status.inventory[i].equip && !position))) { + final_refine = sd->status.inventory[i].refine + refine; + if (final_refine > 10) + final_refine = 10; + else if (final_refine < 0) + final_refine = 0; + if (sd->status.inventory[i].refine != final_refine) { + sd->status.inventory[i].refine = final_refine; + current_position = sd->status.inventory[i].equip; + pc_unequipitem(sd, i, 0); + clif_refine(fd, sd, 0, i, sd->status.inventory[i].refine); + clif_delitem(sd, i, 1); + clif_additem(sd, i, 1, 0); + pc_equipitem(sd, i, current_position); + clif_misceffect((struct block_list*)&sd->bl, 3); + count++; + } + } + } + + if (count == 0) + clif_displaymessage(fd, msg_table[166]); // No item has been refined! + else if (count == 1) + clif_displaymessage(fd, msg_table[167]); // 1 item has been refined! + else { + sprintf(output, msg_table[168], count); // %d items have been refined! + clif_displaymessage(fd, output); + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_produce( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char item_name[100]; + int item_id, attribute = 0, star = 0; + int flag = 0; + struct item_data *item_data; + struct item tmp_item; + char output[200]; + + memset(output, '\0', sizeof(output)); + memset(item_name, '\0', sizeof(item_name)); + + if (!message || !*message || sscanf(message, "%99s %d %d", item_name, &attribute, &star) < 1) { + clif_displaymessage(fd, "Please, enter at least an item name/id (usage: @produce <equip name or equip ID> <element> <# of very's>)."); + return -1; + } + + item_id = 0; + if ((item_data = itemdb_searchname(item_name)) != NULL || + (item_data = itemdb_exists(atoi(item_name))) != NULL) + item_id = item_data->nameid; + + if (itemdb_exists(item_id) && + (item_id <= 500 || item_id > 1099) && + (item_id < 4001 || item_id > 4148) && + (item_id < 7001 || item_id > 10019) && + itemdb_isequip(item_id)) { + if (attribute < MIN_ATTRIBUTE || attribute > MAX_ATTRIBUTE) + attribute = ATTRIBUTE_NORMAL; + if (star < MIN_STAR || star > MAX_STAR) + star = 0; + memset(&tmp_item, 0, sizeof tmp_item); + tmp_item.nameid = item_id; + tmp_item.amount = 1; + tmp_item.identify = 1; + tmp_item.card[0] = 0x00ff; + tmp_item.card[1] = ((star * 5) << 8) + attribute; + *((unsigned long *)(&tmp_item.card[2])) = sd->char_id; + clif_produceeffect(sd, 0, item_id); // 製造エフェクトパケット + clif_misceffect(&sd->bl, 3); // 他人にも成功を通知 + if ((flag = pc_additem(sd, &tmp_item, 1))) + clif_additem(sd, 0, 0, flag); + } else { + if (battle_config.error_log) + printf("@produce NOT WEAPON [%d]\n", item_id); + if (item_id != 0 && itemdb_exists(item_id)) + sprintf(output, msg_table[169], item_id, item_data->name); // This item (%d: '%s') is not an equipment. + else + sprintf(output, msg_table[170]); // This item is not an equipment. + clif_displaymessage(fd, output); + return -1; + } + + return 0; +} + +/*========================================== + * Sub-function to display actual memo points + *------------------------------------------ + */ +void atcommand_memo_sub(struct map_session_data* sd) { + int i; + char output[200]; + + memset(output, '\0', sizeof(output)); + + clif_displaymessage(sd->fd, "Your actual memo positions are (except respawn point):"); + for (i = MIN_PORTAL_MEMO; i <= MAX_PORTAL_MEMO; i++) { + if (sd->status.memo_point[i].map[0]) + sprintf(output, "%d - %s (%d,%d)", i, sd->status.memo_point[i].map, sd->status.memo_point[i].x, sd->status.memo_point[i].y); + else + sprintf(output, msg_table[171], i); // %d - void + clif_displaymessage(sd->fd, output); + } + + return; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_memo( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + int position = 0; + char output[200]; + + memset(output, '\0', sizeof(output)); + + if (!message || !*message || sscanf(message, "%d", &position) < 1) + atcommand_memo_sub(sd); + else { + if (position >= MIN_PORTAL_MEMO && position <= MAX_PORTAL_MEMO) { + if (sd->bl.m >= 0 && map[sd->bl.m].flag.nowarpto && battle_config.any_warp_GM_min_level > pc_isGM(sd)) { + clif_displaymessage(fd, "You are not authorised to memo this map."); + return -1; + } + if (sd->status.memo_point[position].map[0]) { + sprintf(output, msg_table[172], position, sd->status.memo_point[position].map, sd->status.memo_point[position].x, sd->status.memo_point[position].y); // You replace previous memo position %d - %s (%d,%d). + clif_displaymessage(fd, output); + } + memcpy(sd->status.memo_point[position].map, map[sd->bl.m].name, 24); + sd->status.memo_point[position].x = sd->bl.x; + sd->status.memo_point[position].y = sd->bl.y; + clif_skill_memo(sd, 0); + if (pc_checkskill(sd, AL_WARP) <= (position + 1)) + clif_displaymessage(fd, msg_table[173]); // Note: you don't have the 'Warp' skill level to use it. + atcommand_memo_sub(sd); + } else { + sprintf(output, "Please, enter a valid position (usage: @memo <memo_position:%d-%d>).", MIN_PORTAL_MEMO, MAX_PORTAL_MEMO); + clif_displaymessage(fd, output); + atcommand_memo_sub(sd); + return -1; + } + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_gat( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char output[200]; + int y; + + memset(output, '\0', sizeof(output)); + + for (y = 2; y >= -2; y--) { + sprintf(output, "%s (x= %d, y= %d) %02X %02X %02X %02X %02X", + map[sd->bl.m].name, sd->bl.x - 2, sd->bl.y + y, + map_getcell(sd->bl.m, sd->bl.x - 2, sd->bl.y + y), + map_getcell(sd->bl.m, sd->bl.x - 1, sd->bl.y + y), + map_getcell(sd->bl.m, sd->bl.x, sd->bl.y + y), + map_getcell(sd->bl.m, sd->bl.x + 1, sd->bl.y + y), + map_getcell(sd->bl.m, sd->bl.x + 2, sd->bl.y + y)); + clif_displaymessage(fd, output); + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_packet( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + int x = 0, y = 0; + + if (!message || !*message || sscanf(message, "%d %d", &x, &y) < 2) { + clif_displaymessage(fd, "Please, enter a status type/flag (usage: @packet <status type> <flag>)."); + return -1; + } + + clif_status_change(&sd->bl, x, y); + + return 0; +} + +/*========================================== + * @stpoint (Rewritten by [Yor]) + *------------------------------------------ + */ +int atcommand_statuspoint( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + int point, new_status_point; + + if (!message || !*message || (point = atoi(message)) == 0) { + clif_displaymessage(fd, "Please, enter a number (usage: @stpoint <number of points>)."); + return -1; + } + + new_status_point = (int)sd->status.status_point + point; + if (point > 0 && (point > 0x7FFF || new_status_point > 0x7FFF)) // fix positiv overflow + new_status_point = 0x7FFF; + else if (point < 0 && (point < -0x7FFF || new_status_point < 0)) // fix negativ overflow + new_status_point = 0; + + if (new_status_point != (int)sd->status.status_point) { + sd->status.status_point = (short)new_status_point; + clif_updatestatus(sd, SP_STATUSPOINT); + clif_displaymessage(fd, msg_table[174]); // Number of status points changed! + } else { + if (point < 0) + clif_displaymessage(fd, msg_table[41]); // Impossible to decrease the number/value. + else + clif_displaymessage(fd, msg_table[149]); // Impossible to increase the number/value. + return -1; + } + + return 0; +} + +/*========================================== + * @skpoint (Rewritten by [Yor]) + *------------------------------------------ + */ +int atcommand_skillpoint( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + int point, new_skill_point; + + if (!message || !*message || (point = atoi(message)) == 0) { + clif_displaymessage(fd, "Please, enter a number (usage: @skpoint <number of points>)."); + return -1; + } + + new_skill_point = (int)sd->status.skill_point + point; + if (point > 0 && (point > 0x7FFF || new_skill_point > 0x7FFF)) // fix positiv overflow + new_skill_point = 0x7FFF; + else if (point < 0 && (point < -0x7FFF || new_skill_point < 0)) // fix negativ overflow + new_skill_point = 0; + + if (new_skill_point != (int)sd->status.skill_point) { + sd->status.skill_point = (short)new_skill_point; + clif_updatestatus(sd, SP_SKILLPOINT); + clif_displaymessage(fd, msg_table[175]); // Number of skill points changed! + } else { + if (point < 0) + clif_displaymessage(fd, msg_table[41]); // Impossible to decrease the number/value. + else + clif_displaymessage(fd, msg_table[149]); // Impossible to increase the number/value. + return -1; + } + + return 0; +} + +/*========================================== + * @zeny (Rewritten by [Yor]) + *------------------------------------------ + */ +int atcommand_zeny( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + int zeny, new_zeny; + + if (!message || !*message || (zeny = atoi(message)) == 0) { + clif_displaymessage(fd, "Please, enter an amount (usage: @zeny <amount>)."); + return -1; + } + + new_zeny = sd->status.zeny + zeny; + if (zeny > 0 && (zeny > MAX_ZENY || new_zeny > MAX_ZENY)) // fix positiv overflow + new_zeny = MAX_ZENY; + else if (zeny < 0 && (zeny < -MAX_ZENY || new_zeny < 0)) // fix negativ overflow + new_zeny = 0; + + if (new_zeny != sd->status.zeny) { + sd->status.zeny = new_zeny; + clif_updatestatus(sd, SP_ZENY); + clif_displaymessage(fd, msg_table[176]); // Number of zenys changed! + } else { + if (zeny < 0) + clif_displaymessage(fd, msg_table[41]); // Impossible to decrease the number/value. + else + clif_displaymessage(fd, msg_table[149]); // Impossible to increase the number/value. + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_param( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + int i, index, value = 0, new_value; + const char* param[] = { "@str", "@agi", "@vit", "@int", "@dex", "@luk", NULL }; + short* status[] = { + &sd->status.str, &sd->status.agi, &sd->status.vit, + &sd->status.int_, &sd->status.dex, &sd->status.luk + }; + char output[200]; + + memset(output, '\0', sizeof(output)); + + if (!message || !*message || sscanf(message, "%d", &value) < 1 || value == 0) { + sprintf(output, "Please, enter a valid value (usage: @str,@agi,@vit,@int,@dex,@luk <+/-adjustement>)."); + clif_displaymessage(fd, output); + return -1; + } + + index = -1; + for (i = 0; param[i] != NULL; i++) { + if (strcmpi(command, param[i]) == 0) { + index = i; + break; + } + } + if (index < 0 || index > MAX_STATUS_TYPE) { // normaly impossible... + sprintf(output, "Please, enter a valid value (usage: @str,@agi,@vit,@int,@dex,@luk <+/-adjustement>)."); + clif_displaymessage(fd, output); + return -1; + } + + new_value = (int)*status[index] + value; + if (value > 0 && (value > battle_config.max_parameter || new_value > battle_config.max_parameter)) // fix positiv overflow + new_value = battle_config.max_parameter; + else if (value < 0 && (value < -battle_config.max_parameter || new_value < 1)) // fix negativ overflow + new_value = 1; + + if (new_value != (int)*status[index]) { + *status[index] = new_value; + clif_updatestatus(sd, SP_STR + index); + clif_updatestatus(sd, SP_USTR + index); + pc_calcstatus(sd, 0); + clif_displaymessage(fd, msg_table[42]); // Stat changed. + } else { + if (value < 0) + clif_displaymessage(fd, msg_table[41]); // Impossible to decrease the number/value. + else + clif_displaymessage(fd, msg_table[149]); // Impossible to increase the number/value. + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +//** Stat all by fritz (rewritten by [Yor]) +int atcommand_stat_all( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + int index, count, value = 0, new_value; + short* status[] = { + &sd->status.str, &sd->status.agi, &sd->status.vit, + &sd->status.int_, &sd->status.dex, &sd->status.luk + }; + + if (!message || !*message || sscanf(message, "%d", &value) < 1 || value == 0) + value = battle_config.max_parameter; + + count = 0; + for (index = 0; index < (int)(sizeof(status) / sizeof(status[0])); index++) { + + new_value = (int)*status[index] + value; + if (value > 0 && (value > battle_config.max_parameter || new_value > battle_config.max_parameter)) // fix positiv overflow + new_value = battle_config.max_parameter; + else if (value < 0 && (value < -battle_config.max_parameter || new_value < 1)) // fix negativ overflow + new_value = 1; + + if (new_value != (int)*status[index]) { + *status[index] = new_value; + clif_updatestatus(sd, SP_STR + index); + clif_updatestatus(sd, SP_USTR + index); + pc_calcstatus(sd, 0); + count++; + } + } + + if (count > 0) // if at least 1 stat modified + clif_displaymessage(fd, msg_table[84]); // All stats changed! + else { + if (value < 0) + clif_displaymessage(fd, msg_table[177]); // Impossible to decrease a stat. + else + clif_displaymessage(fd, msg_table[178]); // Impossible to increase a stat. + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_guildlevelup( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + int level = 0; + short added_level; + struct guild *guild_info; + + if (!message || !*message || sscanf(message, "%d", &level) < 1 || level == 0) { + clif_displaymessage(fd, "Please, enter a valid level (usage: @guildlvup/@guildlvlup <# of levels>)."); + return -1; + } + + if (sd->status.guild_id <= 0 || (guild_info = guild_search(sd->status.guild_id)) == NULL) { + clif_displaymessage(fd, msg_table[43]); // You're not in a guild. + return -1; + } + if (strcmp(sd->status.name, guild_info->master) != 0) { + clif_displaymessage(fd, msg_table[44]); // You're not the master of your guild. + return -1; + } + + added_level = (short)level; + if (level > 0 && (level > MAX_GUILDLEVEL || added_level > ((short)MAX_GUILDLEVEL - guild_info->guild_lv))) // fix positiv overflow + added_level = (short)MAX_GUILDLEVEL - guild_info->guild_lv; + else if (level < 0 && (level < -MAX_GUILDLEVEL || added_level < (1 - guild_info->guild_lv))) // fix negativ overflow + added_level = 1 - guild_info->guild_lv; + + if (added_level != 0) { + intif_guild_change_basicinfo(guild_info->guild_id, GBI_GUILDLV, &added_level, 2); + clif_displaymessage(fd, msg_table[179]); // Guild level changed. + } else { + clif_displaymessage(fd, msg_table[45]); // Guild level change failed. + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_makeegg( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + struct item_data *item_data; + int id, pet_id; + + if (!message || !*message) { + clif_displaymessage(fd, "Please, enter a monter/egg name/id (usage: @makeegg <pet_id>)."); + return -1; + } + + if ((item_data = itemdb_searchname(message)) != NULL) // for egg name + id = item_data->nameid; + else if ((id = mobdb_searchname(message)) == 0) // for monster name + id = atoi(message); + + pet_id = search_petDB_index(id, PET_CLASS); + if (pet_id < 0) + pet_id = search_petDB_index(id, PET_EGG); + if (pet_id >= 0) { + sd->catch_target_class = pet_db[pet_id].class; + intif_create_pet( + sd->status.account_id, sd->status.char_id, + pet_db[pet_id].class, mob_db[pet_db[pet_id].class].lv, + pet_db[pet_id].EggID, 0, pet_db[pet_id].intimate, + 100, 0, 1, pet_db[pet_id].jname); + } else { + clif_displaymessage(fd, msg_table[180]); // The monter/egg name/id doesn't exist. + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_hatch( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + if (sd->status.pet_id <= 0) + clif_sendegg(sd); + else { + clif_displaymessage(fd, msg_table[181]); // You already have a pet. + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_petfriendly( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + int friendly; + int t; + + if (!message || !*message || (friendly = atoi(message)) < 0) { + clif_displaymessage(fd, "Please, enter a valid value (usage: @petfriendly <0-1000>)."); + return -1; + } + + if (sd->status.pet_id > 0 && sd->pd) { + if (friendly >= 0 && friendly <= 1000) { + if (friendly != sd->pet.intimate) { + t = sd->pet.intimate; + sd->pet.intimate = friendly; + clif_send_petstatus(sd); + if (battle_config.pet_status_support) { + if ((sd->pet.intimate > 0 && t <= 0) || + (sd->pet.intimate <= 0 && t > 0)) { + if (sd->bl.prev != NULL) + pc_calcstatus(sd, 0); + else + pc_calcstatus(sd, 2); + } + } + clif_displaymessage(fd, msg_table[182]); // Pet friendly value changed! + } else { + clif_displaymessage(fd, msg_table[183]); // Pet friendly is already the good value. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[37]); // An invalid number was specified. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[184]); // Sorry, but you have no pet. + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_pethungry( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + int hungry; + + if (!message || !*message || (hungry = atoi(message)) < 0) { + clif_displaymessage(fd, "Please, enter a valid number (usage: @pethungry <0-100>)."); + return -1; + } + + if (sd->status.pet_id > 0 && sd->pd) { + if (hungry >= 0 && hungry <= 100) { + if (hungry != sd->pet.hungry) { + sd->pet.hungry = hungry; + clif_send_petstatus(sd); + clif_displaymessage(fd, msg_table[185]); // Pet hungry value changed! + } else { + clif_displaymessage(fd, msg_table[186]); // Pet hungry is already the good value. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[37]); // An invalid number was specified. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[184]); // Sorry, but you have no pet. + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_petrename( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + if (sd->status.pet_id > 0 && sd->pd) { + if (sd->pet.rename_flag != 0) { + sd->pet.rename_flag = 0; + intif_save_petdata(sd->status.account_id, &sd->pet); + clif_send_petstatus(sd); + clif_displaymessage(fd, msg_table[187]); // You can now rename your pet. + } else { + clif_displaymessage(fd, msg_table[188]); // You can already rename your pet. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[184]); // Sorry, but you have no pet. + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_charpetrename( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char character[100]; + struct map_session_data *pl_sd; + + memset(character, '\0', sizeof(character)); + + if (!message || !*message || sscanf(message, "%99[^\n]", character) < 1) { + clif_displaymessage(fd, "Please, enter a player name (usage: @charpetrename <char name>)."); + return -1; + } + + if ((pl_sd = map_nick2sd(character)) != NULL) { + if (pl_sd->status.pet_id > 0 && pl_sd->pd) { + if (pl_sd->pet.rename_flag != 0) { + pl_sd->pet.rename_flag = 0; + intif_save_petdata(pl_sd->status.account_id, &pl_sd->pet); + clif_send_petstatus(pl_sd); + clif_displaymessage(fd, msg_table[189]); // This player can now rename his/her pet. + } else { + clif_displaymessage(fd, msg_table[190]); // This player can already rename his/her pet. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[191]); // Sorry, but this player has no pet. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[3]); // Character not found. + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_recall( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char character[100]; + char output[200]; + struct map_session_data *pl_sd; + + memset(character, '\0', sizeof(character)); + memset(output, '\0', sizeof(output)); + + if (!message || !*message || sscanf(message, "%99[^\n]", character) < 1) { + clif_displaymessage(fd, "Please, enter a player name (usage: @recall <char name>)."); + return -1; + } + + if ((pl_sd = map_nick2sd(character)) != NULL) { + if (pc_isGM(sd) >= pc_isGM(pl_sd)) { // you can recall only lower or same level + if (sd->bl.m >= 0 && map[sd->bl.m].flag.nowarpto && battle_config.any_warp_GM_min_level > pc_isGM(sd)) { + clif_displaymessage(fd, "You are not authorised to warp somenone to your actual map."); + return -1; + } + if (pl_sd->bl.m >= 0 && map[pl_sd->bl.m].flag.nowarp && battle_config.any_warp_GM_min_level > pc_isGM(sd)) { + clif_displaymessage(fd, "You are not authorised to warp this player from its actual map."); + return -1; + } + pc_setpos(pl_sd, sd->mapname, sd->bl.x, sd->bl.y, 2); + sprintf(output, msg_table[46], character); // %s recalled! + clif_displaymessage(fd, output); + } else { + clif_displaymessage(fd, msg_table[81]); // Your GM level don't authorise you to do this action on this player. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[3]); // Character not found. + return -1; + } + + return 0; +} + +/*========================================== + * 対象キャラクターを転職させる upper指定で転生や養子も可能 + *------------------------------------------ + */ +int atcommand_character_job( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char character[100]; + struct map_session_data* pl_sd; + int job = 0, upper = -1; + + memset(character, '\0', sizeof(character)); + + if (!message || !*message) { + clif_displaymessage(fd, "Please, enter a job and a player name (usage: @charjob/@charjobchange <job ID> <char name>)."); + return -1; + } + + if (sscanf(message, "%d %d %99[^\n]", &job, &upper, character) < 3) { //upper指定してある + upper = -1; + if (sscanf(message, "%d %99[^\n]", &job, character) < 2) { //upper指定してない上に何か足りない + clif_displaymessage(fd, "Please, enter a job and a player name (usage: @charjob/@charjobchange <job ID> <char name>)."); + return -1; + } + } + + if ((pl_sd = map_nick2sd(character)) != NULL) { + if (pc_isGM(sd) >= pc_isGM(pl_sd)) { // you can change job only to lower or same level + if ((job >= 0 && job < MAX_PC_CLASS)) { + + // fix pecopeco display + if ((job != 13 && job != 21 && job != 4014 && job != 4022)) { + if (pc_isriding(sd)) { + if (pl_sd->status.class == 13) + pl_sd->status.class = pl_sd->view_class = 7; + if (pl_sd->status.class == 21) + pl_sd->status.class = pl_sd->view_class = 14; + if (pl_sd->status.class == 4014) + pl_sd->status.class = pl_sd->view_class = 4008; + if (pl_sd->status.class == 4022) + pl_sd->status.class = pl_sd->view_class = 4015; + pl_sd->status.option &= ~0x0020; + clif_changeoption(&pl_sd->bl); + pc_calcstatus(pl_sd, 0); + } + } else { + if (!pc_isriding(sd)) { + if (job == 13) + job = 7; + if (job == 21) + job = 14; + if (job == 4014) + job = 4008; + if (job == 4022) + job = 4015; + } + } + + if (pc_jobchange(pl_sd, job, upper) == 0) + clif_displaymessage(fd, msg_table[48]); // Character's job changed. + else { + clif_displaymessage(fd, msg_table[192]); // Impossible to change the character's job. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[49]); // Invalid job ID. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[81]); // Your GM level don't authorise you to do this action on this player. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[3]); // Character not found. + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_revive( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char character[100]; + struct map_session_data *pl_sd; + + memset(character, '\0', sizeof(character)); + + if (!message || !*message || sscanf(message, "%99[^\n]", character) < 1) { + clif_displaymessage(fd, "Please, enter a player name (usage: @revive <char name>)."); + return -1; + } + + if ((pl_sd = map_nick2sd(character)) != NULL) { + pl_sd->status.hp = pl_sd->status.max_hp; + pc_setstand(pl_sd); + if (battle_config.pc_invincible_time > 0) + pc_setinvincibletimer(sd, battle_config.pc_invincible_time); + clif_updatestatus(pl_sd, SP_HP); + clif_updatestatus(pl_sd, SP_SP); + clif_resurrection(&pl_sd->bl, 1); + clif_displaymessage(fd, msg_table[51]); // Character revived. + } else { + clif_displaymessage(fd, msg_table[3]); // Character not found. + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_character_stats( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char character[100]; + char job_jobname[100]; + char output[200]; + struct map_session_data *pl_sd; + int i; + + memset(character, '\0', sizeof(character)); + memset(job_jobname, '\0', sizeof(job_jobname)); + memset(output, '\0', sizeof(output)); + + if (!message || !*message || sscanf(message, "%99[^\n]", character) < 1) { + clif_displaymessage(fd, "Please, enter a player name (usage: @charstats <char name>)."); + return -1; + } + + if ((pl_sd = map_nick2sd(character)) != NULL) { + struct { + const char* format; + int value; + } output_table[] = { + { "Base Level - %d", pl_sd->status.base_level }, + { job_jobname, pl_sd->status.job_level }, + { "Hp - %d", pl_sd->status.hp }, + { "MaxHp - %d", pl_sd->status.max_hp }, + { "Sp - %d", pl_sd->status.sp }, + { "MaxSp - %d", pl_sd->status.max_sp }, + { "Str - %3d", pl_sd->status.str }, + { "Agi - %3d", pl_sd->status.agi }, + { "Vit - %3d", pl_sd->status.vit }, + { "Int - %3d", pl_sd->status.int_ }, + { "Dex - %3d", pl_sd->status.dex }, + { "Luk - %3d", pl_sd->status.luk }, + { "Zeny - %d", pl_sd->status.zeny }, + { NULL, 0 } + }; + sprintf(job_jobname, "Job - %s %s", job_name(pl_sd->status.class), "(level %d)"); + sprintf(output, msg_table[53], pl_sd->status.name); // '%s' stats: + clif_displaymessage(fd, output); + for (i = 0; output_table[i].format != NULL; i++) { + sprintf(output, output_table[i].format, output_table[i].value); + clif_displaymessage(fd, output); + } + } else { + clif_displaymessage(fd, msg_table[3]); // Character not found. + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +//** Character Stats All by fritz +int atcommand_character_stats_all(const int fd, struct map_session_data* sd, const char* command, const char* message) +{ + char output[1024], gmlevel[1024]; + int i; + int count; + struct map_session_data *pl_sd; + + memset(output, '\0', sizeof(output)); + memset(gmlevel, '\0', sizeof(gmlevel)); + + count = 0; + for(i = 0; i < fd_max; i++) { + if (session[i] && (pl_sd = session[i]->session_data) && pl_sd->state.auth) { + + if (pc_isGM(pl_sd) > 0) + sprintf(gmlevel, "| GM Lvl: %d", pc_isGM(pl_sd)); + else + sprintf(gmlevel, " "); + + sprintf(output, "Name: %s | BLvl: %d | Job: %s (Lvl: %d) | HP: %d/%d | SP: %d/%d", pl_sd->status.name, pl_sd->status.base_level, job_name(pl_sd->status.class), pl_sd->status.job_level, pl_sd->status.hp, pl_sd->status.max_hp, pl_sd->status.sp, pl_sd->status.max_sp); + clif_displaymessage(fd, output); + sprintf(output, "STR: %d | AGI: %d | VIT: %d | INT: %d | DEX: %d | LUK: %d | Zeny: %d %s", pl_sd->status.str, pl_sd->status.agi, pl_sd->status.vit, pl_sd->status.int_, pl_sd->status.dex, pl_sd->status.luk, pl_sd->status.zeny, gmlevel); + clif_displaymessage(fd, output); + clif_displaymessage(fd, "--------"); + count++; + } + } + + if (count == 0) + clif_displaymessage(fd, msg_table[28]); // No player found. + else if (count == 1) + clif_displaymessage(fd, msg_table[29]); // 1 player found. + else { + sprintf(output, msg_table[30], count); // %d players found. + clif_displaymessage(fd, output); + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_character_option( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char character[100]; + int opt1 = 0, opt2 = 0, opt3 = 0; + struct map_session_data* pl_sd; + + memset(character, '\0', sizeof(character)); + + if (!message || !*message || sscanf(message, "%d %d %d %99[^\n]", &opt1, &opt2, &opt3, character) < 4 || opt1 < 0 || opt2 < 0 || opt3 < 0) { + clif_displaymessage(fd, "Please, enter valid options and a player name (usage: @charoption <param1> <param2> <param3> <charname>)."); + return -1; + } + + if ((pl_sd = map_nick2sd(character)) != NULL) { + if (pc_isGM(sd) >= pc_isGM(pl_sd)) { // you can change option only to lower or same level + pl_sd->opt1 = opt1; + pl_sd->opt2 = opt2; + pl_sd->status.option = opt3; + // fix pecopeco display + if (pl_sd->status.class == 13 || pl_sd->status.class == 21 || pl_sd->status.class == 4014 || pl_sd->status.class == 4022) { + if (!pc_isriding(pl_sd)) { // pl_sd have the new value... + if (pl_sd->status.class == 13) + pl_sd->status.class = pl_sd->view_class = 7; + else if (pl_sd->status.class == 21) + pl_sd->status.class = pl_sd->view_class = 14; + else if (pl_sd->status.class == 4014) + pl_sd->status.class = pl_sd->view_class = 4008; + else if (pl_sd->status.class == 4022) + pl_sd->status.class = pl_sd->view_class = 4015; + } + } else { + if (pc_isriding(pl_sd)) { // pl_sd have the new value... + if (pl_sd->disguise > 0) { // temporary prevention of crash caused by peco + disguise, will look into a better solution [Valaris] (code added by [Yor]) + pl_sd->status.option &= ~0x0020; + } else { + if (pl_sd->status.class == 7) + pl_sd->status.class = pl_sd->view_class = 13; + else if (pl_sd->status.class == 14) + pl_sd->status.class = pl_sd->view_class = 21; + else if (pl_sd->status.class == 4008) + pl_sd->status.class = pl_sd->view_class = 4014; + else if (pl_sd->status.class == 4015) + pl_sd->status.class = pl_sd->view_class = 4022; + else + pl_sd->status.option &= ~0x0020; + } + } + } + clif_changeoption(&pl_sd->bl); + pc_calcstatus(pl_sd, 0); + clif_displaymessage(fd, msg_table[58]); // Character's options changed. + } else { + clif_displaymessage(fd, msg_table[81]); // Your GM level don't authorise you to do this action on this player. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[3]); // Character not found. + return -1; + } + + return 0; +} + +/*========================================== + * charchangesex command (usage: charchangesex <player_name>) + *------------------------------------------ + */ +int atcommand_char_change_sex( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char character[100]; + + memset(character, '\0', sizeof(character)); + + if (!message || !*message || sscanf(message, "%99[^\n]", character) < 1) { + clif_displaymessage(fd, "Please, enter a player name (usage: @charchangesex <name>)."); + return -1; + } + + // check player name + if (strlen(character) < 4) { + clif_displaymessage(fd, msg_table[86]); // Sorry, but a player name have at least 4 characters. + return -1; + } else if (strlen(character) > 23) { + clif_displaymessage(fd, msg_table[87]); // Sorry, but a player name have 23 characters maximum. + return -1; + } else { + chrif_char_ask_name(sd->status.account_id, character, 5, 0, 0, 0, 0, 0, 0); // type: 5 - changesex + clif_displaymessage(fd, msg_table[88]); // Character name sends to char-server to ask it. + } + + return 0; +} + +/*========================================== + * charblock command (usage: charblock <player_name>) + * This command do a definitiv ban on a player + *------------------------------------------ + */ +int atcommand_char_block( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char character[100]; + + memset(character, '\0', sizeof(character)); + + if (!message || !*message || sscanf(message, "%99[^\n]", character) < 1) { + clif_displaymessage(fd, "Please, enter a player name (usage: @charblock/@block <name>)."); + return -1; + } + + // check player name + if (strlen(character) < 4) { + clif_displaymessage(fd, msg_table[86]); // Sorry, but a player name have at least 4 characters. + return -1; + } else if (strlen(character) > 23) { + clif_displaymessage(fd, msg_table[87]); // Sorry, but a player name have 23 characters maximum. + return -1; + } else { + chrif_char_ask_name(sd->status.account_id, character, 1, 0, 0, 0, 0, 0, 0); // type: 1 - block + clif_displaymessage(fd, msg_table[88]); // Character name sends to char-server to ask it. + } + + return 0; +} + +/*========================================== + * charban command (usage: charban <time> <player_name>) + * This command do a limited ban on a player + * Time is done as follows: + * Adjustment value (-1, 1, +1, etc...) + * Modified element: + * a or y: year + * m: month + * j or d: day + * h: hour + * mn: minute + * s: second + * <example> @ban +1m-2mn1s-6y test_player + * this example adds 1 month and 1 second, and substracts 2 minutes and 6 years at the same time. + *------------------------------------------ + */ +int atcommand_char_ban( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char modif[100], character[100]; + char * modif_p; + int year, month, day, hour, minute, second, value; + + memset(modif, '\0', sizeof(modif)); + memset(character, '\0', sizeof(character)); + + if (!message || !*message || sscanf(message, "%s %99[^\n]", modif, character) < 2) { + clif_displaymessage(fd, "Please, enter ban time and a player name (usage: @charban/@ban/@banish/@charbanish <time> <name>)."); + return -1; + } + + modif[sizeof(modif)-1] = '\0'; + character[sizeof(character)-1] = '\0'; + + modif_p = modif; + year = month = day = hour = minute = second = 0; + while (modif_p[0] != '\0') { + value = atoi(modif_p); + if (value == 0) + modif_p++; + else { + if (modif_p[0] == '-' || modif_p[0] == '+') + modif_p++; + while (modif_p[0] >= '0' && modif_p[0] <= '9') + modif_p++; + if (modif_p[0] == 's') { + second = value; + modif_p++; + } else if (modif_p[0] == 'm' && modif_p[1] == 'n') { + minute = value; + modif_p = modif_p + 2; + } else if (modif_p[0] == 'h') { + hour = value; + modif_p++; + } else if (modif_p[0] == 'd' || modif_p[0] == 'j') { + day = value; + modif_p++; + } else if (modif_p[0] == 'm') { + month = value; + modif_p++; + } else if (modif_p[0] == 'y' || modif_p[0] == 'a') { + year = value; + modif_p++; + } else if (modif_p[0] != '\0') { + modif_p++; + } + } + } + if (year == 0 && month == 0 && day == 0 && hour == 0 && minute == 0 && second == 0) { + clif_displaymessage(fd, msg_table[85]); // Invalid time for ban command. + return -1; + } + + // check player name + if (strlen(character) < 4) { + clif_displaymessage(fd, msg_table[86]); // Sorry, but a player name have at least 4 characters. + return -1; + } else if (strlen(character) > 23) { + clif_displaymessage(fd, msg_table[87]); // Sorry, but a player name have 23 characters maximum. + return -1; + } else { + chrif_char_ask_name(sd->status.account_id, character, 2, year, month, day, hour, minute, second); // type: 2 - ban + clif_displaymessage(fd, msg_table[88]); // Character name sends to char-server to ask it. + } + + return 0; +} + +/*========================================== + * charunblock command (usage: charunblock <player_name>) + *------------------------------------------ + */ +int atcommand_char_unblock( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char character[100]; + + memset(character, '\0', sizeof(character)); + + if (!message || !*message || sscanf(message, "%99[^\n]", character) < 1) { + clif_displaymessage(fd, "Please, enter a player name (usage: @charunblock <player_name>)."); + return -1; + } + + // check player name + if (strlen(character) < 4) { + clif_displaymessage(fd, msg_table[86]); // Sorry, but a player name have at least 4 characters. + return -1; + } else if (strlen(character) > 23) { + clif_displaymessage(fd, msg_table[87]); // Sorry, but a player name have 23 characters maximum. + return -1; + } else { + // send answer to login server via char-server + chrif_char_ask_name(sd->status.account_id, character, 3, 0, 0, 0, 0, 0, 0); // type: 3 - unblock + clif_displaymessage(fd, msg_table[88]); // Character name sends to char-server to ask it. + } + + return 0; +} + +/*========================================== + * charunban command (usage: charunban <player_name>) + *------------------------------------------ + */ +int atcommand_char_unban( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char character[100]; + + memset(character, '\0', sizeof(character)); + + if (!message || !*message || sscanf(message, "%99[^\n]", character) < 1) { + clif_displaymessage(fd, "Please, enter a player name (usage: @charunban <player_name>)."); + return -1; + } + + // check player name + if (strlen(character) < 4) { + clif_displaymessage(fd, msg_table[86]); // Sorry, but a player name have at least 4 characters. + return -1; + } else if (strlen(character) > 23) { + clif_displaymessage(fd, msg_table[87]); // Sorry, but a player name have 23 characters maximum. + return -1; + } else { + // send answer to login server via char-server + chrif_char_ask_name(sd->status.account_id, character, 4, 0, 0, 0, 0, 0, 0); // type: 4 - unban + clif_displaymessage(fd, msg_table[88]); // Character name sends to char-server to ask it. + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_character_save( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char map_name[100]; + char character[100]; + struct map_session_data* pl_sd; + int x = 0, y = 0; + int m; + + memset(map_name, '\0', sizeof(map_name)); + memset(character, '\0', sizeof(character)); + + if (!message || !*message || sscanf(message, "%99s %d %d %99[^\n]", map_name, &x, &y, character) < 4 || x < 0 || y < 0) { + clif_displaymessage(fd, "Please, enter a valid save point and a player name (usage: @charsave <map> <x> <y> <charname>)."); + return -1; + } + + if (strstr(map_name, ".gat") == NULL && strstr(map_name, ".afm") == NULL && strlen(map_name) < 13) // 16 - 4 (.gat) + strcat(map_name, ".gat"); + + if ((pl_sd = map_nick2sd(character)) != NULL) { + if (pc_isGM(sd) >= pc_isGM(pl_sd)) { // you can change save point only to lower or same gm level + m = map_mapname2mapid(map_name); + if (m < 0) { + clif_displaymessage(fd, msg_table[1]); // Map not found. + return -1; + } else { + if (m >= 0 && map[m].flag.nowarpto && battle_config.any_warp_GM_min_level > pc_isGM(sd)) { + clif_displaymessage(fd, "You are not authorised to set this map as a save map."); + return -1; + } + pc_setsavepoint(pl_sd, map_name, x, y); + clif_displaymessage(fd, msg_table[57]); // Character's respawn point changed. + } + } else { + clif_displaymessage(fd, msg_table[81]); // Your GM level don't authorise you to do this action on this player. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[3]); // Character not found. + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_night( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + struct map_session_data *pl_sd; + int i; + + if (night_flag != 1) { + night_flag = 1; // 0=day, 1=night [Yor] + for(i = 0; i < fd_max; i++) { + if (session[i] && (pl_sd = session[i]->session_data) && pl_sd->state.auth) { + pl_sd->opt2 |= STATE_BLIND; + clif_changeoption(&pl_sd->bl); + clif_displaymessage(pl_sd->fd, msg_table[59]); // Night has fallen. + } + } + } else { + clif_displaymessage(fd, msg_table[89]); // Sorry, it's already the night. Impossible to execute the command. + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_day( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + struct map_session_data *pl_sd; + int i; + + if (night_flag != 0) { + night_flag = 0; // 0=day, 1=night [Yor] + for(i = 0; i < fd_max; i++) { + if (session[i] && (pl_sd = session[i]->session_data) && pl_sd->state.auth) { + pl_sd->opt2 &= ~STATE_BLIND; + clif_changeoption(&pl_sd->bl); + clif_displaymessage(pl_sd->fd, msg_table[60]); // Day has arrived. + } + } + } else { + clif_displaymessage(fd, msg_table[90]); // Sorry, it's already the day. Impossible to execute the command. + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_doom( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + struct map_session_data *pl_sd; + int i; + + for(i = 0; i < fd_max; i++) { + if (session[i] && (pl_sd = session[i]->session_data) && pl_sd->state.auth && i != fd && + pc_isGM(sd) >= pc_isGM(pl_sd)) { // you can doom only lower or same gm level + pc_damage(NULL, pl_sd, pl_sd->status.hp + 1); + clif_displaymessage(pl_sd->fd, msg_table[61]); // The holy messenger has given judgement. + } + } + clif_displaymessage(fd, msg_table[62]); // Judgement was made. + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_doommap( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + struct map_session_data *pl_sd; + int i; + + for (i = 0; i < fd_max; i++) { + if (session[i] && (pl_sd = session[i]->session_data) && pl_sd->state.auth && i != fd && sd->bl.m == pl_sd->bl.m && + pc_isGM(sd) >= pc_isGM(pl_sd)) { // you can doom only lower or same gm level + pc_damage(NULL, pl_sd, pl_sd->status.hp + 1); + clif_displaymessage(pl_sd->fd, msg_table[61]); // The holy messenger has given judgement. + } + } + clif_displaymessage(fd, msg_table[62]); // Judgement was made. + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +static void atcommand_raise_sub(struct map_session_data* sd) +{ + if (sd && sd->state.auth && pc_isdead(sd)) { + sd->status.hp = sd->status.max_hp; + sd->status.sp = sd->status.max_sp; + pc_setstand(sd); + clif_updatestatus(sd, SP_HP); + clif_updatestatus(sd, SP_SP); + clif_resurrection(&sd->bl, 1); + clif_displaymessage(sd->fd, msg_table[63]); // Mercy has been shown. + } +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_raise( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + int i; + + for (i = 0; i < fd_max; i++) { + if (session[i]) + atcommand_raise_sub(session[i]->session_data); + } + clif_displaymessage(fd, msg_table[64]); // Mercy has been granted. + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_raisemap( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + struct map_session_data *pl_sd; + int i; + + for (i = 0; i < fd_max; i++) { + if (session[i] && (pl_sd = session[i]->session_data) && pl_sd->state.auth && sd->bl.m == pl_sd->bl.m) + atcommand_raise_sub(pl_sd); + } + clif_displaymessage(fd, msg_table[64]); // Mercy has been granted. + + return 0; +} + +/*========================================== + * atcommand_character_baselevel @charbaselvlで対象キャラのレベルを上げる + *------------------------------------------ +*/ +int atcommand_character_baselevel( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + struct map_session_data *pl_sd; + char character[100]; + int level = 0, i; + + memset(character, '\0', sizeof(character)); + + if (!message || !*message || sscanf(message, "%d %99[^\n]", &level, character) < 2 || level == 0) { + clif_displaymessage(fd, "Please, enter a level adjustement and a player name (usage: @charbaselvl <#> <nickname>)."); + return -1; + } + + if ((pl_sd = map_nick2sd(character)) != NULL) { + if (pc_isGM(sd) >= pc_isGM(pl_sd)) { // you can change base level only lower or same gm level + + if (level > 0) { + if (pl_sd->status.base_level == battle_config.maximum_level) { // check for max level by Valaris + clif_displaymessage(fd, msg_table[91]); // Character's base level can't go any higher. + return 0; + } // End Addition + if (level > battle_config.maximum_level || level > (battle_config.maximum_level - pl_sd->status.base_level)) // fix positiv overflow + level = battle_config.maximum_level - pl_sd->status.base_level; + for (i = 1; i <= level; i++) + pl_sd->status.status_point += (pl_sd->status.base_level + i + 14) / 5; + pl_sd->status.base_level += level; + clif_updatestatus(pl_sd, SP_BASELEVEL); + clif_updatestatus(pl_sd, SP_NEXTBASEEXP); + clif_updatestatus(pl_sd, SP_STATUSPOINT); + pc_calcstatus(pl_sd, 0); + pc_heal(pl_sd, pl_sd->status.max_hp, pl_sd->status.max_sp); + clif_misceffect(&pl_sd->bl, 0); + clif_displaymessage(fd, msg_table[65]); // Character's base level raised. + } else { + if (pl_sd->status.base_level == 1) { + clif_displaymessage(fd, msg_table[193]); // Character's base level can't go any lower. + return -1; + } + if (level < -battle_config.maximum_level || level < (1 - pl_sd->status.base_level)) // fix negativ overflow + level = 1 - pl_sd->status.base_level; + if (pl_sd->status.status_point > 0) { + for (i = 0; i > level; i--) + pl_sd->status.status_point -= (pl_sd->status.base_level + i + 14) / 5; + if (pl_sd->status.status_point < 0) + pl_sd->status.status_point = 0; + clif_updatestatus(pl_sd, SP_STATUSPOINT); + } // to add: remove status points from stats + pl_sd->status.base_level += level; + clif_updatestatus(pl_sd, SP_BASELEVEL); + clif_updatestatus(pl_sd, SP_NEXTBASEEXP); + pc_calcstatus(pl_sd, 0); + clif_displaymessage(fd, msg_table[66]); // Character's base level lowered. + } + } else { + clif_displaymessage(fd, msg_table[81]); // Your GM level don't authorise you to do this action on this player. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[3]); // Character not found. + return -1; + } + + return 0; //正常終了 +} + +/*========================================== + * atcommand_character_joblevel @charjoblvlで対象キャラのJobレベルを上げる + *------------------------------------------ + */ +int atcommand_character_joblevel( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + struct map_session_data *pl_sd; + char character[100]; + int max_level = 50, level = 0; + //転生や養子の場合の元の職業を算出する + struct pc_base_job pl_s_class; + + memset(character, '\0', sizeof(character)); + + if (!message || !*message || sscanf(message, "%d %99[^\n]", &level, character) < 2 || level == 0) { + clif_displaymessage(fd, "Please, enter a level adjustement and a player name (usage: @charjlvl <#> <nickname>)."); + return -1; + } + + if ((pl_sd = map_nick2sd(character)) != NULL) { + pl_s_class = pc_calc_base_job(pl_sd->status.class); + if (pc_isGM(sd) >= pc_isGM(pl_sd)) { // you can change job level only lower or same gm level + if (pl_s_class.job == 0) + max_level -= 40; + if ((pl_s_class.job == 23) || (pl_s_class.upper == 1 && pl_s_class.type == 2)) //スパノビと転生職はJobレベルの最高が70 + max_level += 20; + + if (level > 0) { + if (pl_sd->status.job_level == max_level) { + clif_displaymessage(fd, msg_table[67]); // Character's job level can't go any higher. + return -1; + } + if (pl_sd->status.job_level + level > max_level) + level = max_level - pl_sd->status.job_level; + pl_sd->status.job_level += level; + clif_updatestatus(pl_sd, SP_JOBLEVEL); + clif_updatestatus(pl_sd, SP_NEXTJOBEXP); + pl_sd->status.skill_point += level; + clif_updatestatus(pl_sd, SP_SKILLPOINT); + pc_calcstatus(pl_sd, 0); + clif_misceffect(&pl_sd->bl, 1); + clif_displaymessage(fd, msg_table[68]); // character's job level raised. + } else { + if (pl_sd->status.job_level == 1) { + clif_displaymessage(fd, msg_table[194]); // Character's job level can't go any lower. + return -1; + } + if (pl_sd->status.job_level + level < 1) + level = 1 - pl_sd->status.job_level; + pl_sd->status.job_level += level; + clif_updatestatus(pl_sd, SP_JOBLEVEL); + clif_updatestatus(pl_sd, SP_NEXTJOBEXP); + if (pl_sd->status.skill_point > 0) { + pl_sd->status.skill_point += level; + if (pl_sd->status.skill_point < 0) + pl_sd->status.skill_point = 0; + clif_updatestatus(pl_sd, SP_SKILLPOINT); + } // to add: remove status points from skills + pc_calcstatus(pl_sd, 0); + clif_displaymessage(fd, msg_table[69]); // Character's job level lowered. + } + } else { + clif_displaymessage(fd, msg_table[81]); // Your GM level don't authorise you to do this action on this player. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[3]); // Character not found. + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_kick( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + struct map_session_data *pl_sd; + char character[100]; + + memset(character, '\0', sizeof(character)); + + if (!message || !*message || sscanf(message, "%99[^\n]", character) < 1) { + clif_displaymessage(fd, "Please, enter a player name (usage: @kick <charname>)."); + return -1; + } + + if ((pl_sd = map_nick2sd(character)) != NULL) { + if (pc_isGM(sd) >= pc_isGM(pl_sd)) // you can kick only lower or same gm level + clif_GM_kick(sd, pl_sd, 1); + else { + clif_displaymessage(fd, msg_table[81]); // Your GM level don't authorise you to do this action on this player. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[3]); // Character not found. + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_kickall( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + struct map_session_data *pl_sd; + int i; + + for (i = 0; i < fd_max; i++) { + if (session[i] && (pl_sd = session[i]->session_data) && pl_sd->state.auth && + pc_isGM(sd) >= pc_isGM(pl_sd)) { // you can kick only lower or same gm level + if (sd->status.account_id != pl_sd->status.account_id) + clif_GM_kick(sd, pl_sd, 0); + } + } + + clif_displaymessage(fd, msg_table[195]); // All players have been kicked! + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_allskill( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + pc_allskillup(sd); // all skills + sd->status.skill_point = 0; // 0 skill points + clif_updatestatus(sd, SP_SKILLPOINT); // update + clif_displaymessage(fd, msg_table[76]); // You have received all skills. + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_questskill( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + int skill_id; + + if (!message || !*message || (skill_id = atoi(message)) < 0) { + clif_displaymessage(fd, "Please, enter a quest skill number (usage: @questskill <#:0+>)."); + return -1; + } + + if (skill_id >= 0 && skill_id < MAX_SKILL_DB) { + if (skill_get_inf2(skill_id) & 0x01) { + if (pc_checkskill(sd, skill_id) == 0) { + pc_skill(sd, skill_id, 1, 0); + clif_displaymessage(fd, msg_table[70]); // You have learned the skill. + } else { + clif_displaymessage(fd, msg_table[196]); // You already have this quest skill. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[197]); // This skill number doesn't exist or isn't a quest skill. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[198]); // This skill number doesn't exist. + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_charquestskill( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char character[100]; + struct map_session_data *pl_sd; + int skill_id = 0; + + memset(character, '\0', sizeof(character)); + + if (!message || !*message || sscanf(message, "%d %99[^\n]", &skill_id, character) < 2 || skill_id < 0) { + clif_displaymessage(fd, "Please, enter a quest skill number and a player name (usage: @charquestskill <#:0+> <char_name>)."); + return -1; + } + + if (skill_id >= 0 && skill_id < MAX_SKILL_DB) { + if (skill_get_inf2(skill_id) & 0x01) { + if ((pl_sd = map_nick2sd(character)) != NULL) { + if (pc_checkskill(pl_sd, skill_id) == 0) { + pc_skill(pl_sd, skill_id, 1, 0); + clif_displaymessage(fd, msg_table[199]); // This player has learned the skill. + } else { + clif_displaymessage(fd, msg_table[200]); // This player already has this quest skill. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[3]); // Character not found. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[197]); // This skill number doesn't exist or isn't a quest skill. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[198]); // This skill number doesn't exist. + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_lostskill( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + int skill_id; + + if (!message || !*message || (skill_id = atoi(message)) < 0) { + clif_displaymessage(fd, "Please, enter a quest skill number (usage: @lostskill <#:0+>)."); + return -1; + } + + if (skill_id >= 0 && skill_id < MAX_SKILL) { + if (skill_get_inf2(skill_id) & 0x01) { + if (pc_checkskill(sd, skill_id) > 0) { + sd->status.skill[skill_id].lv = 0; + sd->status.skill[skill_id].flag = 0; + clif_skillinfoblock(sd); + clif_displaymessage(fd, msg_table[71]); // You have forgotten the skill. + } else { + clif_displaymessage(fd, msg_table[201]); // You don't have this quest skill. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[197]); // This skill number doesn't exist or isn't a quest skill. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[198]); // This skill number doesn't exist. + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_charlostskill( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char character[100]; + struct map_session_data *pl_sd; + int skill_id = 0; + + memset(character, '\0', sizeof(character)); + + if (!message || !*message || sscanf(message, "%d %99[^\n]", &skill_id, character) < 2 || skill_id < 0) { + clif_displaymessage(fd, "Please, enter a quest skill number and a player name (usage: @charlostskill <#:0+> <char_name>)."); + return -1; + } + + if (skill_id >= 0 && skill_id < MAX_SKILL) { + if (skill_get_inf2(skill_id) & 0x01) { + if ((pl_sd = map_nick2sd(character)) != NULL) { + if (pc_checkskill(pl_sd, skill_id) > 0) { + pl_sd->status.skill[skill_id].lv = 0; + pl_sd->status.skill[skill_id].flag = 0; + clif_skillinfoblock(pl_sd); + clif_displaymessage(fd, msg_table[202]); // This player has forgotten the skill. + } else { + clif_displaymessage(fd, msg_table[203]); // This player doesn't have this quest skill. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[3]); // Character not found. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[197]); // This skill number doesn't exist or isn't a quest skill. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[198]); // This skill number doesn't exist. + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_spiritball( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + int number; + + if (!message || !*message || (number = atoi(message)) < 0) { + clif_displaymessage(fd, "Please, enter a spirit ball number (usage: @spiritball <number: 0-1000>)."); + return -1; + } + + // set max number to avoid server/client crash (500 create big balls of several balls: no visial difference with more) + if (number > 500) + number = 500; + + if (number >= 0 && number <= 0x7FFF) { + if (sd->spiritball != number || number > 499) { + if (sd->spiritball > 0) + pc_delspiritball(sd, sd->spiritball, 1); + sd->spiritball = number; + clif_spiritball(sd); + // no message, player can look the difference + if (number > 1000) + clif_displaymessage(fd, msg_table[204]); // WARNING: more than 1000 spiritballs can CRASH your server and/or client! + } else { + clif_displaymessage(fd, msg_table[205]); // You already have this number of spiritballs. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[37]); // An invalid number was specified. + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_party( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char party[100]; + + memset(party, '\0', sizeof(party)); + + if (!message || !*message || sscanf(message, "%99[^\n]", party) < 1) { + clif_displaymessage(fd, "Please, enter a party name (usage: @party <party_name>)."); + return -1; + } + + party_create(sd, party); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_guild( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char guild[100]; + int prev; + + memset(guild, '\0', sizeof(guild)); + + if (!message || !*message || sscanf(message, "%99[^\n]", guild) < 1) { + clif_displaymessage(fd, "Please, enter a guild name (usage: @guild <guild_name>)."); + return -1; + } + + prev = battle_config.guild_emperium_check; + battle_config.guild_emperium_check = 0; + guild_create(sd, guild); + battle_config.guild_emperium_check = prev; + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_agitstart( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + if (agit_flag == 1) { + clif_displaymessage(fd, msg_table[73]); // Already it has started siege warfare. + return -1; + } + + agit_flag = 1; + guild_agit_start(); + clif_displaymessage(fd, msg_table[72]); // Guild siege warfare start! + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_agitend( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + if (agit_flag == 0) { + clif_displaymessage(fd, msg_table[75]); // Siege warfare hasn't started yet. + return -1; + } + + agit_flag = 0; + guild_agit_end(); + clif_displaymessage(fd, msg_table[74]); // Guild siege warfare end! + + return 0; +} + +/*========================================== + * @mapexitでマップサーバーを終了させる + *------------------------------------------ + */ +int atcommand_mapexit( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + struct map_session_data *pl_sd; + int i; + + for (i = 0; i < fd_max; i++) { + if (session[i] && (pl_sd = session[i]->session_data) && pl_sd->state.auth) { + if (sd->status.account_id != pl_sd->status.account_id) + clif_GM_kick(sd, pl_sd, 0); + } + } + clif_GM_kick(sd, sd, 0); + + runflag = 0; + + return 0; +} + +/*========================================== + * idsearch <part_of_name>: revrited by [Yor] + *------------------------------------------ + */ +int atcommand_idsearch( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char item_name[100]; + char output[200]; + int i, match; + struct item_data *item; + + memset(item_name, '\0', sizeof(item_name)); + memset(output, '\0', sizeof(output)); + + if (!message || !*message || sscanf(message, "%99s", item_name) < 0) { + clif_displaymessage(fd, "Please, enter a part of item name (usage: @idsearch <part_of_item_name>)."); + return -1; + } + + sprintf(output, msg_table[77], item_name); // The reference result of '%s' (name: id): + clif_displaymessage(fd, output); + match = 0; + for(i = 0; i < 20000; i++) { + if ((item = itemdb_exists(i)) != NULL && strstr(item->jname, item_name) != NULL) { + match++; + sprintf(output, msg_table[78], item->jname, item->nameid); // %s: %d + clif_displaymessage(fd, output); + } + } + sprintf(output, msg_table[79], match); // It is %d affair above. + clif_displaymessage(fd, output); + + return 0; +} + +/*========================================== + * Character Skill Reset + *------------------------------------------ + */ +int atcommand_charskreset( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char character[100]; + char output[200]; + struct map_session_data *pl_sd; + + memset(character, '\0', sizeof(character)); + memset(output, '\0', sizeof(output)); + + if (!message || !*message || sscanf(message, "%99[^\n]", character) < 1) { + clif_displaymessage(fd, "Please, enter a player name (usage: @charskreset <charname>)."); + return -1; + } + + if ((pl_sd = map_nick2sd(character)) != NULL) { + if (pc_isGM(sd) >= pc_isGM(pl_sd)) { // you can reset skill points only lower or same gm level + pc_resetskill(pl_sd); + sprintf(output, msg_table[206], character); // '%s' skill points reseted! + clif_displaymessage(fd, output); + } else { + clif_displaymessage(fd, msg_table[81]); // Your GM level don't authorise you to do this action on this player. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[3]); // Character not found. + return -1; + } + + return 0; +} + +/*========================================== + * Character Stat Reset + *------------------------------------------ + */ +int atcommand_charstreset( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char character[100]; + char output[200]; + struct map_session_data *pl_sd; + + memset(character, '\0', sizeof(character)); + memset(output, '\0', sizeof(output)); + + if (!message || !*message || sscanf(message, "%99[^\n]", character) < 1) { + clif_displaymessage(fd, "Please, enter a player name (usage: @charstreset <charname>)."); + return -1; + } + + if ((pl_sd = map_nick2sd(character)) != NULL) { + if (pc_isGM(sd) >= pc_isGM(pl_sd)) { // you can reset stats points only lower or same gm level + pc_resetstate(pl_sd); + sprintf(output, msg_table[207], character); // '%s' stats points reseted! + clif_displaymessage(fd, output); + } else { + clif_displaymessage(fd, msg_table[81]); // Your GM level don't authorise you to do this action on this player. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[3]); // Character not found. + return -1; + } + + return 0; +} + +/*========================================== + * Character Reset + *------------------------------------------ + */ +int atcommand_charreset( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char character[100]; + char output[200]; + struct map_session_data *pl_sd; + + memset(character, '\0', sizeof(character)); + memset(output, '\0', sizeof(output)); + + if (!message || !*message || sscanf(message, "%99[^\n]", character) < 1) { + clif_displaymessage(fd, "Please, enter a player name (usage: @charreset <charname>)."); + return -1; + } + + if ((pl_sd = map_nick2sd(character)) != NULL) { + if (pc_isGM(sd) >= pc_isGM(pl_sd)) { // you can reset a character only for lower or same GM level + pc_resetstate(pl_sd); + pc_resetskill(pl_sd); + sprintf(output, msg_table[208], character); // '%s' skill and stats points reseted! + clif_displaymessage(fd, output); + } else { + clif_displaymessage(fd, msg_table[81]); // Your GM level don't authorise you to do this action on this player. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[3]); // Character not found. + return -1; + } + + return 0; +} + +/*========================================== + * Character Model by chbrules + *------------------------------------------ + */ +int atcommand_charmodel( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + int hair_style = 0, hair_color = 0, cloth_color = 0; + struct map_session_data *pl_sd; + char character[100]; + char output[200]; + + memset(character, '\0', sizeof(character)); + memset(output, '\0', sizeof(output)); + + if (!message || !*message || sscanf(message, "%d %d %d %99[^\n]", &hair_style, &hair_color, &cloth_color, character) < 4 || hair_style < 0 || hair_color < 0 || cloth_color < 0) { + sprintf(output, "Please, enter a valid model and a player name (usage: @charmodel <hair ID: %d-%d> <hair color: %d-%d> <clothes color: %d-%d> <name>).", + MIN_HAIR_STYLE, MAX_HAIR_STYLE, MIN_HAIR_COLOR, MAX_HAIR_COLOR, MIN_CLOTH_COLOR, MAX_CLOTH_COLOR); + clif_displaymessage(fd, output); + return -1; + } + + if ((pl_sd = map_nick2sd(character)) != NULL) { + if (hair_style >= MIN_HAIR_STYLE && hair_style <= MAX_HAIR_STYLE && + hair_color >= MIN_HAIR_COLOR && hair_color <= MAX_HAIR_COLOR && + cloth_color >= MIN_CLOTH_COLOR && cloth_color <= MAX_CLOTH_COLOR) { + + if (cloth_color != 0 && + pl_sd->status.sex == 1 && + (pl_sd->status.class == 12 || pl_sd->status.class == 17)) { + clif_displaymessage(fd, msg_table[35]); // You can't use this command with this class. + return -1; + } else { + pc_changelook(pl_sd, LOOK_HAIR, hair_style); + pc_changelook(pl_sd, LOOK_HAIR_COLOR, hair_color); + pc_changelook(pl_sd, LOOK_CLOTHES_COLOR, cloth_color); + clif_displaymessage(fd, msg_table[36]); // Appearence changed. + } + } else { + clif_displaymessage(fd, msg_table[37]); // An invalid number was specified. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[3]); // Character not found. + return -1; + } + + return 0; +} + +/*========================================== + * Character Skill Point (Rewritten by [Yor]) + *------------------------------------------ + */ +int atcommand_charskpoint( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + struct map_session_data *pl_sd; + char character[100]; + int new_skill_point; + int point = 0; + + memset(character, '\0', sizeof(character)); + + if (!message || !*message || sscanf(message, "%d %99[^\n]", &point, character) < 2 || point == 0) { + clif_displaymessage(fd, "Please, enter a number and a player name (usage: @charskpoint <amount> <name>)."); + return -1; + } + + if ((pl_sd = map_nick2sd(character)) != NULL) { + new_skill_point = (int)pl_sd->status.skill_point + point; + if (point > 0 && (point > 0x7FFF || new_skill_point > 0x7FFF)) // fix positiv overflow + new_skill_point = 0x7FFF; + else if (point < 0 && (point < -0x7FFF || new_skill_point < 0)) // fix negativ overflow + new_skill_point = 0; + if (new_skill_point != (int)pl_sd->status.skill_point) { + pl_sd->status.skill_point = new_skill_point; + clif_updatestatus(pl_sd, SP_SKILLPOINT); + clif_displaymessage(fd, msg_table[209]); // Character's number of skill points changed! + } else { + if (point < 0) + clif_displaymessage(fd, msg_table[41]); // Impossible to decrease the number/value. + else + clif_displaymessage(fd, msg_table[149]); // Impossible to increase the number/value. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[3]); // Character not found. + return -1; + } + + return 0; +} + +/*========================================== + * Character Status Point (rewritten by [Yor]) + *------------------------------------------ + */ +int atcommand_charstpoint( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + struct map_session_data *pl_sd; + char character[100]; + int new_status_point; + int point = 0; + + memset(character, '\0', sizeof(character)); + + if (!message || !*message || sscanf(message, "%d %99[^\n]", &point, character) < 2 || point == 0) { + clif_displaymessage(fd, "Please, enter a number and a player name (usage: @charstpoint <amount> <name>)."); + return -1; + } + + if ((pl_sd = map_nick2sd(character)) != NULL) { + new_status_point = (int)pl_sd->status.status_point + point; + if (point > 0 && (point > 0x7FFF || new_status_point > 0x7FFF)) // fix positiv overflow + new_status_point = 0x7FFF; + else if (point < 0 && (point < -0x7FFF || new_status_point < 0)) // fix negativ overflow + new_status_point = 0; + if (new_status_point != (int)pl_sd->status.status_point) { + pl_sd->status.status_point = new_status_point; + clif_updatestatus(pl_sd, SP_STATUSPOINT); + clif_displaymessage(fd, msg_table[210]); // Character's number of status points changed! + } else { + if (point < 0) + clif_displaymessage(fd, msg_table[41]); // Impossible to decrease the number/value. + else + clif_displaymessage(fd, msg_table[149]); // Impossible to increase the number/value. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[3]); // Character not found. + return -1; + } + + return 0; +} + +/*========================================== + * Character Zeny Point (Rewritten by [Yor]) + *------------------------------------------ + */ +int atcommand_charzeny( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + struct map_session_data *pl_sd; + char character[100]; + int zeny = 0, new_zeny; + + memset(character, '\0', sizeof(character)); + + if (!message || !*message || sscanf(message, "%d %99[^\n]", &zeny, character) < 2 || zeny == 0) { + clif_displaymessage(fd, "Please, enter a number and a player name (usage: @charzeny <zeny> <name>)."); + return -1; + } + + if ((pl_sd = map_nick2sd(character)) != NULL) { + new_zeny = pl_sd->status.zeny + zeny; + if (zeny > 0 && (zeny > MAX_ZENY || new_zeny > MAX_ZENY)) // fix positiv overflow + new_zeny = MAX_ZENY; + else if (zeny < 0 && (zeny < -MAX_ZENY || new_zeny < 0)) // fix negativ overflow + new_zeny = 0; + if (new_zeny != pl_sd->status.zeny) { + pl_sd->status.zeny = new_zeny; + clif_updatestatus(pl_sd, SP_ZENY); + clif_displaymessage(fd, msg_table[211]); // Character's number of zenys changed! + } else { + if (zeny < 0) + clif_displaymessage(fd, msg_table[41]); // Impossible to decrease the number/value. + else + clif_displaymessage(fd, msg_table[149]); // Impossible to increase the number/value. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[3]); // Character not found. + return -1; + } + + return 0; +} + +/*========================================== + * Recall All Characters Online To Your Location + *------------------------------------------ + */ +int atcommand_recallall( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + struct map_session_data *pl_sd; + int i; + int count; + char output[200]; + + memset(output, '\0', sizeof(output)); + + if (sd->bl.m >= 0 && map[sd->bl.m].flag.nowarpto && battle_config.any_warp_GM_min_level > pc_isGM(sd)) { + clif_displaymessage(fd, "You are not authorised to warp somenone to your actual map."); + return -1; + } + + count = 0; + for (i = 0; i < fd_max; i++) { + if (session[i] && (pl_sd = session[i]->session_data) && pl_sd->state.auth && sd->status.account_id != pl_sd->status.account_id && + pc_isGM(sd) >= pc_isGM(pl_sd)) { // you can recall only lower or same level + if (pl_sd->bl.m >= 0 && map[pl_sd->bl.m].flag.nowarp && battle_config.any_warp_GM_min_level > pc_isGM(sd)) + count++; + else + pc_setpos(pl_sd, sd->mapname, sd->bl.x, sd->bl.y, 2); + } + } + + clif_displaymessage(fd, msg_table[92]); // All characters recalled! + if (count) { + sprintf(output, "Because you are not authorised to warp from some maps, %d player(s) have not been recalled.", count); + clif_displaymessage(fd, output); + } + + return 0; +} + +/*========================================== + * Recall online characters of a guild to your location + *------------------------------------------ + */ +int atcommand_guildrecall( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + struct map_session_data *pl_sd; + int i; + char guild_name[100]; + char output[200]; + struct guild *g; + int count; + + memset(guild_name, '\0', sizeof(guild_name)); + memset(output, '\0', sizeof(output)); + + if (!message || !*message || sscanf(message, "%99[^\n]", guild_name) < 1) { + clif_displaymessage(fd, "Please, enter a guild name/id (usage: @guildrecall <guild_name/id>)."); + return -1; + } + + if (sd->bl.m >= 0 && map[sd->bl.m].flag.nowarpto && battle_config.any_warp_GM_min_level > pc_isGM(sd)) { + clif_displaymessage(fd, "You are not authorised to warp somenone to your actual map."); + return -1; + } + + if ((g = guild_searchname(guild_name)) != NULL || // name first to avoid error when name begin with a number + (g = guild_search(atoi(message))) != NULL) { + count = 0; + for (i = 0; i < fd_max; i++) { + if (session[i] && (pl_sd = session[i]->session_data) && pl_sd->state.auth && + sd->status.account_id != pl_sd->status.account_id && + pl_sd->status.guild_id == g->guild_id) { + if (pl_sd->bl.m >= 0 && map[pl_sd->bl.m].flag.nowarp && battle_config.any_warp_GM_min_level > pc_isGM(sd)) + count++; + else + pc_setpos(pl_sd, sd->mapname, sd->bl.x, sd->bl.y, 2); + } + } + sprintf(output, msg_table[93], g->name); // All online characters of the %s guild are near you. + clif_displaymessage(fd, output); + if (count) { + sprintf(output, "Because you are not authorised to warp from some maps, %d player(s) have not been recalled.", count); + clif_displaymessage(fd, output); + } + } else { + clif_displaymessage(fd, msg_table[94]); // Incorrect name/ID, or no one from the guild is online. + return -1; + } + + return 0; +} + +/*========================================== + * Recall online characters of a party to your location + *------------------------------------------ + */ +int atcommand_partyrecall( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + int i; + struct map_session_data *pl_sd; + char party_name[100]; + char output[200]; + struct party *p; + int count; + + memset(party_name, '\0', sizeof(party_name)); + memset(output, '\0', sizeof(output)); + + if (!message || !*message || sscanf(message, "%99[^\n]", party_name) < 1) { + clif_displaymessage(fd, "Please, enter a party name/id (usage: @partyrecall <party_name/id>)."); + return -1; + } + + if (sd->bl.m >= 0 && map[sd->bl.m].flag.nowarpto && battle_config.any_warp_GM_min_level > pc_isGM(sd)) { + clif_displaymessage(fd, "You are not authorised to warp somenone to your actual map."); + return -1; + } + + if ((p = party_searchname(party_name)) != NULL || // name first to avoid error when name begin with a number + (p = party_search(atoi(message))) != NULL) { + count = 0; + for (i = 0; i < fd_max; i++) { + if (session[i] && (pl_sd = session[i]->session_data) && pl_sd->state.auth && + sd->status.account_id != pl_sd->status.account_id && + pl_sd->status.party_id == p->party_id) { + if (pl_sd->bl.m >= 0 && map[pl_sd->bl.m].flag.nowarp && battle_config.any_warp_GM_min_level > pc_isGM(sd)) + count++; + else + pc_setpos(pl_sd, sd->mapname, sd->bl.x, sd->bl.y, 2); + } + } + sprintf(output, msg_table[95], p->name); // All online characters of the %s party are near you. + clif_displaymessage(fd, output); + if (count) { + sprintf(output, "Because you are not authorised to warp from some maps, %d player(s) have not been recalled.", count); + clif_displaymessage(fd, output); + } + } else { + clif_displaymessage(fd, msg_table[96]); // Incorrect name or ID, or no one from the party is online. + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_reloaditemdb( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + itemdb_reload(); + clif_displaymessage(fd, msg_table[97]); // Item database reloaded. + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_reloadmobdb( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + mob_reload(); + clif_displaymessage(fd, msg_table[98]); // Monster database reloaded. + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_reloadskilldb( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + skill_reload(); + clif_displaymessage(fd, msg_table[99]); // Skill database reloaded. + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +#ifndef TXT_ONLY +int atcommand_rehash( +#else /* TXT_ONLY */ +int atcommand_reloadscript( +#endif /* TXT_ONLY */ + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ +#ifndef TXT_ONLY + atcommand_broadcast( fd, sd, "@broadcast", "eAthena SQL Server is Rehashing..." ); + atcommand_broadcast( fd, sd, "@broadcast", "You will feel a bit of lag at this point !" ); + + rehash( fd, sd ); + + atcommand_broadcast( fd, sd, "@broadcast", "Reloading NPCs..." ); +#endif /* not TXT_ONLY */ + do_init_npc(); + do_init_script(); + + npc_event_do_oninit(); + + clif_displaymessage(fd, msg_table[100]); // Scripts reloaded. + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_reloadgmdb( // by [Yor] + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + chrif_reloadGMdb(); + + clif_displaymessage(fd, msg_table[101]); // Login-server asked to reload GM accounts and their level. + + return 0; +} + +/*========================================== + * @mapinfo <map name> [0-3] by MC_Cameri + * => Shows information about the map [map name] + * 0 = no additional information + * 1 = Show users in that map and their location + * 2 = Shows NPCs in that map + * 3 = Shows the shops/chats in that map (not implemented) + *------------------------------------------ + */ +int atcommand_mapinfo( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + struct map_session_data *pl_sd; + struct npc_data *nd = NULL; + struct chat_data *cd = NULL; + char output[200], map_name[100]; + char direction[12]; + int m_id, i, chat_num, list = 0; + + memset(output, '\0', sizeof(output)); + memset(map_name, '\0', sizeof(map_name)); + memset(direction, '\0', sizeof(direction)); + + sscanf(message, "%d %99[^\n]", &list, map_name); + + if (list < 0 || list > 3) { + clif_displaymessage(fd, "Please, enter at least a valid list number (usage: @mapinfo <0-3> [map])."); + return -1; + } + + if (map_name[0] == '\0') + strcpy(map_name, sd->mapname); + if (strstr(map_name, ".gat") == NULL && strstr(map_name, ".afm") == NULL && strlen(map_name) < 13) // 16 - 4 (.gat) + strcat(map_name, ".gat"); + + if ((m_id = map_mapname2mapid(map_name)) < 0) { + clif_displaymessage(fd, msg_table[1]); // Map not found. + return -1; + } + + clif_displaymessage(fd, "------ Map Info ------"); + sprintf(output, "Map Name: %s", map_name); + clif_displaymessage(fd, output); + sprintf(output, "Players In Map: %d", map[m_id].users); + clif_displaymessage(fd, output); + sprintf(output, "NPCs In Map: %d", map[m_id].npc_num); + clif_displaymessage(fd, output); + chat_num = 0; + for (i = 0; i < fd_max; i++) { + if (session[i] && (pl_sd = session[i]->session_data) && pl_sd->state.auth && + (cd = (struct chat_data*)map_id2bl(pl_sd->chatID))) { + chat_num++; + } + } + sprintf(output, "Chats In Map: %d", chat_num); + clif_displaymessage(fd, output); + clif_displaymessage(fd, "------ Map Flags ------"); + sprintf(output, "Player vs Player: %s | No Guild: %s | No Party: %s", + (map[m_id].flag.pvp) ? "True" : "False", + (map[m_id].flag.pvp_noguild) ? "True" : "False", + (map[m_id].flag.pvp_noparty) ? "True" : "False"); + clif_displaymessage(fd, output); + sprintf(output, "Guild vs Guild: %s | No Party: %s", (map[m_id].flag.gvg) ? "True" : "False", (map[m_id].flag.gvg_noparty) ? "True" : "False"); + clif_displaymessage(fd, output); + sprintf(output, "No Dead Branch: %s", (map[m_id].flag.nobranch) ? "True" : "False"); + clif_displaymessage(fd, output); + sprintf(output, "No Memo: %s", (map[m_id].flag.nomemo) ? "True" : "False"); + clif_displaymessage(fd, output); + sprintf(output, "No Penalty: %s", (map[m_id].flag.nopenalty) ? "True" : "False"); + clif_displaymessage(fd, output); + sprintf(output, "No Return: %s", (map[m_id].flag.noreturn) ? "True" : "False"); + clif_displaymessage(fd, output); + sprintf(output, "No Save: %s", (map[m_id].flag.nosave) ? "True" : "False"); + clif_displaymessage(fd, output); + sprintf(output, "No Teleport: %s", (map[m_id].flag.noteleport) ? "True" : "False"); + clif_displaymessage(fd, output); + sprintf(output, "No Monster Teleport: %s", (map[m_id].flag.monster_noteleport) ? "True" : "False"); + clif_displaymessage(fd, output); + sprintf(output, "No Zeny Penalty: %s", (map[m_id].flag.nozenypenalty) ? "True" : "False"); + clif_displaymessage(fd, output); + + switch (list) { + case 0: + // Do nothing. It's list 0, no additional display. + break; + case 1: + clif_displaymessage(fd, "----- Players in Map -----"); + for (i = 0; i < fd_max; i++) { + if (session[i] && (pl_sd = session[i]->session_data) && pl_sd->state.auth && strcmp(pl_sd->mapname, map_name) == 0) { + sprintf(output, "Player '%s' (session #%d) | Location: %d,%d", + pl_sd->status.name, i, pl_sd->bl.x, pl_sd->bl.y); + clif_displaymessage(fd, output); + } + } + break; + case 2: + clif_displaymessage(fd, "----- NPCs in Map -----"); + for (i = 0; i < map[m_id].npc_num;) { + nd = map[m_id].npc[i]; + switch(nd->dir) { + case 0: strcpy(direction, "North"); break; + case 1: strcpy(direction, "North West"); break; + case 2: strcpy(direction, "West"); break; + case 3: strcpy(direction, "South West"); break; + case 4: strcpy(direction, "South"); break; + case 5: strcpy(direction, "South East"); break; + case 6: strcpy(direction, "East"); break; + case 7: strcpy(direction, "North East"); break; + case 9: strcpy(direction, "North"); break; + default: strcpy(direction, "Unknown"); break; + } + sprintf(output, "NPC %d: %s | Direction: %s | Sprite: %d | Location: %d %d", + ++i, nd->name, direction, nd->class, nd->bl.x, nd->bl.y); + clif_displaymessage(fd, output); + } + break; + case 3: + clif_displaymessage(fd, "----- Chats in Map -----"); + for (i = 0; i < fd_max; i++) { + if (session[i] && (pl_sd = session[i]->session_data) && pl_sd->state.auth && + (cd = (struct chat_data*)map_id2bl(pl_sd->chatID)) && + strcmp(pl_sd->mapname, map_name) == 0 && + cd->usersd[0] == pl_sd) { + sprintf(output, "Chat %d: %s | Player: %s | Location: %d %d", + i, cd->title, pl_sd->status.name, cd->bl.x, cd->bl.y); + clif_displaymessage(fd, output); + sprintf(output, " Users: %d/%d | Password: %s | Public: %s", + cd->users, cd->limit, cd->pass, (cd->pub) ? "Yes" : "No"); + clif_displaymessage(fd, output); + } + } + break; + default: // normally impossible to arrive here + clif_displaymessage(fd, "Please, enter at least a valid list number (usage: @mapinfo <0-3> [map])."); + return -1; + break; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_mount_peco( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + if (sd->disguise > 0) { // temporary prevention of crash caused by peco + disguise, will look into a better solution [Valaris] + clif_displaymessage(fd, msg_table[212]); // Cannot mount a Peco while in disguise. + return -1; + } + + if (!pc_isriding(sd)) { // if actually no peco + if (sd->status.class == 7 || sd->status.class == 14 || sd->status.class == 4008 || sd->status.class == 4015) { + if (sd->status.class == 7) + sd->status.class = sd->view_class = 13; + else if (sd->status.class == 14) + sd->status.class = sd->view_class = 21; + else if (sd->status.class == 4008) + sd->status.class = sd->view_class = 4014; + else if (sd->status.class == 4015) + sd->status.class = sd->view_class = 4022; + pc_setoption(sd, sd->status.option | 0x0020); + clif_displaymessage(fd, msg_table[102]); // Mounted Peco. + } else { + clif_displaymessage(fd, msg_table[213]); // You can not mount a peco with your job. + return -1; + } + } else { + if (sd->status.class == 13) + sd->status.class = sd->view_class = 7; + else if (sd->status.class == 21) + sd->status.class = sd->view_class = 14; + else if (sd->status.class == 4014) + sd->status.class = sd->view_class = 4008; + else if (sd->status.class == 4022) + sd->status.class = sd->view_class = 4015; + pc_setoption(sd, sd->status.option & ~0x0020); + clif_displaymessage(fd, msg_table[214]); // Unmounted Peco. + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_char_mount_peco( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char character[100]; + struct map_session_data *pl_sd; + + memset(character, '\0', sizeof(character)); + + if (!message || !*message || sscanf(message, "%99[^\n]", character) < 1) { + clif_displaymessage(fd, "Please, enter a player name (usage: @charmountpeco <char_name>)."); + return -1; + } + + if ((pl_sd = map_nick2sd(character)) != NULL) { + if (pl_sd->disguise > 0) { // temporary prevention of crash caused by peco + disguise, will look into a better solution [Valaris] + clif_displaymessage(fd, msg_table[215]); // This player cannot mount a Peco while in disguise. + return -1; + } + + if (!pc_isriding(pl_sd)) { // if actually no peco + if (pl_sd->status.class == 7 || pl_sd->status.class == 14 || pl_sd->status.class == 4008 || pl_sd->status.class == 4015) { + if (pl_sd->status.class == 7) + pl_sd->status.class = pl_sd->view_class = 13; + else if (pl_sd->status.class == 14) + pl_sd->status.class = pl_sd->view_class = 21; + else if (pl_sd->status.class == 4008) + pl_sd->status.class = pl_sd->view_class = 4014; + else if (pl_sd->status.class == 4015) + pl_sd->status.class = pl_sd->view_class = 4022; + pc_setoption(pl_sd, pl_sd->status.option | 0x0020); + clif_displaymessage(fd, msg_table[216]); // Now, this player mounts a peco. + } else { + clif_displaymessage(fd, msg_table[217]); // This player can not mount a peco with his/her job. + return -1; + } + } else { + if (pl_sd->status.class == 13) + pl_sd->status.class = pl_sd->view_class = 7; + else if (pl_sd->status.class == 21) + pl_sd->status.class = pl_sd->view_class = 14; + else if (pl_sd->status.class == 4014) + pl_sd->status.class = pl_sd->view_class = 4008; + else if (pl_sd->status.class == 4022) + pl_sd->status.class = pl_sd->view_class = 4015; + pc_setoption(pl_sd, pl_sd->status.option & ~0x0020); + clif_displaymessage(fd, msg_table[218]); // Now, this player has not more peco. + } + } else { + clif_displaymessage(fd, msg_table[3]); // Character not found. + return -1; + } + + return 0; +} + +/*========================================== + *Spy Commands by Syrus22 + *------------------------------------------ + */ +int atcommand_guildspy( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char guild_name[100]; + char output[200]; + struct guild *g; + + memset(guild_name, '\0', sizeof(guild_name)); + memset(output, '\0', sizeof(output)); + + if (!message || !*message || sscanf(message, "%99[^\n]", guild_name) < 1) { + clif_displaymessage(fd, "Please, enter a guild name/id (usage: @guildspy <guild_name/id>)."); + return -1; + } + + if ((g = guild_searchname(guild_name)) != NULL || // name first to avoid error when name begin with a number + (g = guild_search(atoi(message))) != NULL) { + if (sd->guildspy == g->guild_id) { + sd->guildspy = 0; + sprintf(output, msg_table[103], g->name); // No longer spying on the %s guild. + clif_displaymessage(fd, output); + } else { + sd->guildspy = g->guild_id; + sprintf(output, msg_table[104], g->name); // Spying on the %s guild. + clif_displaymessage(fd, output); + } + } else { + clif_displaymessage(fd, msg_table[94]); // Incorrect name/ID, or no one from the guild is online. + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_partyspy( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char party_name[100]; + char output[200]; + struct party *p; + + memset(party_name, '\0', sizeof(party_name)); + memset(output, '\0', sizeof(output)); + + if (!message || !*message || sscanf(message, "%99[^\n]", party_name) < 1) { + clif_displaymessage(fd, "Please, enter a party name/id (usage: @partyspy <party_name/id>)."); + return -1; + } + + if ((p = party_searchname(party_name)) != NULL || // name first to avoid error when name begin with a number + (p = party_search(atoi(message))) != NULL) { + if (sd->partyspy == p->party_id) { + sd->partyspy = 0; + sprintf(output, msg_table[105], p->name); // No longer spying on the %s party. + clif_displaymessage(fd, output); + } else { + sd->partyspy = p->party_id; + sprintf(output, msg_table[106], p->name); // Spying on the %s party. + clif_displaymessage(fd, output); + } + } else { + clif_displaymessage(fd, msg_table[96]); // Incorrect name or ID, or no one from the party is online. + return -1; + } + + return 0; +} + +/*========================================== + * @repairall [Valaris] + *------------------------------------------ + */ +int atcommand_repairall( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + int count, i; + + count = 0; + for (i = 0; i < MAX_INVENTORY; i++) { + if (sd->status.inventory[i].nameid && sd->status.inventory[i].broken == 1) { + sd->status.inventory[i].broken = 0; + clif_produceeffect(sd, 0, sd->status.inventory[i].nameid); + count++; + } + } + + if (count > 0) { + clif_misceffect(&sd->bl, 3); + clif_equiplist(sd); + clif_displaymessage(fd, msg_table[107]); // All items have been repaired. + } else { + clif_displaymessage(fd, msg_table[108]); // No item need to be repaired. + return -1; + } + + return 0; +} + +/* Removed @nuke for now in favor of alchemist marine sphere skill [Valaris] +int atcommand_nuke( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char character[100]; + struct map_session_data *pl_sd; + + memset(character, '\0', sizeof(character)); + + if (!message || !*message || sscanf(message, "%99[^\n]", character) < 1) { + clif_displaymessage(fd, "Please, enter a player name (usage: @nuke <char name>)."); + return -1; + } + + if ((pl_sd = map_nick2sd(character)) != NULL) { + if (pc_isGM(sd) >= pc_isGM(pl_sd)) { // you can kill only lower or same GM level + skill_castend_damage_id(&pl_sd->bl, &pl_sd->bl, NPC_SELFDESTRUCTION, 99, gettick(), 0); + clif_displaymessage(fd, msg_table[109]); // Player has been nuked! + } else { + clif_displaymessage(fd, msg_table[81]); // Your GM level don't authorise you to do this action on this player. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[3]); // Character not found. + return -1; + } + + return 0; +} +*/ + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_enablenpc(const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char NPCname[100]; + + memset(NPCname, '\0', sizeof(NPCname)); + + if (!message || !*message || sscanf(message, "%99[^\n]", NPCname) < 1) { + clif_displaymessage(fd, "Please, enter a NPC name (usage: @npcon <NPC_name>)."); + return -1; + } + + if (npc_name2id(NPCname) != NULL) { + npc_enable(NPCname, 1); + clif_displaymessage(fd, msg_table[110]); // Npc Enabled. + } else { + clif_displaymessage(fd, msg_table[111]); // This NPC doesn't exist. + return -1; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int atcommand_disablenpc(const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char NPCname[100]; + + memset(NPCname, '\0', sizeof(NPCname)); + + if (!message || !*message || sscanf(message, "%99[^\n]", NPCname) < 1) { + clif_displaymessage(fd, "Please, enter a NPC name (usage: @npcoff <NPC_name>)."); + return -1; + } + + if (npc_name2id(NPCname) != NULL) { + npc_enable(NPCname, 0); + clif_displaymessage(fd, msg_table[112]); // Npc Disabled. + } else { + clif_displaymessage(fd, msg_table[111]); // This NPC doesn't exist. + return -1; + } + + return 0; +} + +/*========================================== + * time in txt for time command (by [Yor]) + *------------------------------------------ + */ +char * txt_time(unsigned int duration) { + int days, hours, minutes, seconds; + char temp[256]; + static char temp1[256]; + + memset(temp, '\0', sizeof(temp)); + memset(temp1, '\0', sizeof(temp1)); + + if (duration < 0) + duration = 0; + + days = duration / (60 * 60 * 24); + duration = duration - (60 * 60 * 24 * days); + hours = duration / (60 * 60); + duration = duration - (60 * 60 * hours); + minutes = duration / 60; + seconds = duration - (60 * minutes); + + if (days < 2) + sprintf(temp, msg_table[219], days); // %d day + else + sprintf(temp, msg_table[220], days); // %d days + if (hours < 2) + sprintf(temp1, msg_table[221], temp, hours); // %s %d hour + else + sprintf(temp1, msg_table[222], temp, hours); // %s %d hours + if (minutes < 2) + sprintf(temp, msg_table[223], temp1, minutes); // %s %d minute + else + sprintf(temp, msg_table[224], temp1, minutes); // %s %d minutes + if (seconds < 2) + sprintf(temp1, msg_table[225], temp, seconds); // %s and %d second + else + sprintf(temp1, msg_table[226], temp, seconds); // %s and %d seconds + + return temp1; +} + +/*========================================== + * @time/@date/@server_date/@serverdate/@server_time/@servertime: Display the date/time of the server (by [Yor] + * Calculation management of GM modification (@day/@night GM commands) is done + *------------------------------------------ + */ +int atcommand_servertime(const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + struct TimerData * timer_data; + struct TimerData * timer_data2; + time_t time_server; // variable for number of seconds (used with time() function) + struct tm *datetime; // variable for time in structure ->tm_mday, ->tm_sec, ... + char temp[256]; + + memset(temp, '\0', sizeof(temp)); + + time(&time_server); // get time in seconds since 1/1/1970 + datetime = localtime(&time_server); // convert seconds in structure + // like sprintf, but only for date/time (Sunday, November 02 2003 15:12:52) + strftime(temp, sizeof(temp)-1, msg_table[230], datetime); // Server time (normal time): %A, %B %d %Y %X. + clif_displaymessage(fd, temp); + + if (battle_config.night_duration == 0 && battle_config.day_duration == 0) { + if (night_flag == 0) + clif_displaymessage(fd, msg_table[231]); // Game time: The game is in permanent daylight. + else + clif_displaymessage(fd, msg_table[232]); // Game time: The game is in permanent night. + } else if (battle_config.night_duration == 0) + if (night_flag == 1) { // we start with night + timer_data = get_timer(day_timer_tid); + sprintf(temp, msg_table[233], txt_time((timer_data->tick - gettick()) / 1000)); // Game time: The game is actualy in night for %s. + clif_displaymessage(fd, temp); + clif_displaymessage(fd, msg_table[234]); // Game time: After, the game will be in permanent daylight. + } else + clif_displaymessage(fd, msg_table[231]); // Game time: The game is in permanent daylight. + else if (battle_config.day_duration == 0) + if (night_flag == 0) { // we start with day + timer_data = get_timer(night_timer_tid); + sprintf(temp, msg_table[235], txt_time((timer_data->tick - gettick()) / 1000)); // Game time: The game is actualy in daylight for %s. + clif_displaymessage(fd, temp); + clif_displaymessage(fd, msg_table[236]); // Game time: After, the game will be in permanent night. + } else + clif_displaymessage(fd, msg_table[232]); // Game time: The game is in permanent night. + else { + if (night_flag == 0) { + timer_data = get_timer(night_timer_tid); + timer_data2 = get_timer(day_timer_tid); + sprintf(temp, msg_table[235], txt_time((timer_data->tick - gettick()) / 1000)); // Game time: The game is actualy in daylight for %s. + clif_displaymessage(fd, temp); + if (timer_data->tick > timer_data2->tick) + sprintf(temp, msg_table[237], txt_time((timer_data->interval - abs(timer_data->tick - timer_data2->tick)) / 1000)); // Game time: After, the game will be in night for %s. + else + sprintf(temp, msg_table[237], txt_time(abs(timer_data->tick - timer_data2->tick) / 1000)); // Game time: After, the game will be in night for %s. + clif_displaymessage(fd, temp); + sprintf(temp, msg_table[238], txt_time(timer_data->interval / 1000)); // Game time: A day cycle has a normal duration of %s. + clif_displaymessage(fd, temp); + } else { + timer_data = get_timer(day_timer_tid); + timer_data2 = get_timer(night_timer_tid); + sprintf(temp, msg_table[233], txt_time((timer_data->tick - gettick()) / 1000)); // Game time: The game is actualy in night for %s. + clif_displaymessage(fd, temp); + if (timer_data->tick > timer_data2->tick) + sprintf(temp, msg_table[239], txt_time((timer_data->interval - abs(timer_data->tick - timer_data2->tick)) / 1000)); // Game time: After, the game will be in daylight for %s. + else + sprintf(temp, msg_table[239], txt_time(abs(timer_data->tick - timer_data2->tick) / 1000)); // Game time: After, the game will be in daylight for %s. + clif_displaymessage(fd, temp); + sprintf(temp, msg_table[238], txt_time(timer_data->interval / 1000)); // Game time: A day cycle has a normal duration of %s. + clif_displaymessage(fd, temp); + } + } + + return 0; +} + +/*========================================== + * @chardelitem <item_name_or_ID> <quantity> <player> (by [Yor] + * removes <quantity> item from a character + * item can be equiped or not. + * Inspired from a old command created by RoVeRT + *------------------------------------------ + */ +int atcommand_chardelitem(const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + struct map_session_data *pl_sd; + char character[100]; + char item_name[100]; + int i, number = 0, item_id, item_position, count; + char output[200]; + struct item_data *item_data; + + memset(character, '\0', sizeof(character)); + memset(item_name, '\0', sizeof(item_name)); + memset(output, '\0', sizeof(output)); + + if (!message || !*message || sscanf(message, "%s %d %99[^\n]", item_name, &number, character) < 3 || number < 1) { + clif_displaymessage(fd, "Please, enter an item name/id, a quantity and a player name (usage: @chardelitem <item_name_or_ID> <quantity> <player>)."); + return -1; + } + + item_id = 0; + if ((item_data = itemdb_searchname(item_name)) != NULL || + (item_data = itemdb_exists(atoi(item_name))) != NULL) + item_id = item_data->nameid; + + if (item_id > 500) { + if ((pl_sd = map_nick2sd(character)) != NULL) { + if (pc_isGM(sd) >= pc_isGM(pl_sd)) { // you can kill only lower or same level + item_position = pc_search_inventory(pl_sd, item_id); + if (item_position >= 0) { + count = 0; + for(i = 0; i < number && item_position >= 0; i++) { + pc_delitem(pl_sd, item_position, 1, 0); + count++; + item_position = pc_search_inventory(pl_sd, item_id); // for next loop + } + sprintf(output, msg_table[113], count); // %d item(s) removed by a GM. + clif_displaymessage(pl_sd->fd, output); + if (number == count) + sprintf(output, msg_table[114], count); // %d item(s) removed from the player. + else + sprintf(output, msg_table[115], count, count, number); // %d item(s) removed. Player had only %d on %d items. + clif_displaymessage(fd, output); + } else { + clif_displaymessage(fd, msg_table[116]); // Character does not have the item. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[81]); // Your GM level don't authorise you to do this action on this player. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[3]); // Character not found. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[19]); // Invalid item ID or name. + return -1; + } + + return 0; +} + +/*========================================== + * @jail <char_name> by [Yor] + * Special warp! No check with nowarp and nowarpto flag + *------------------------------------------ + */ +int atcommand_jail( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char character[100]; + struct map_session_data *pl_sd; + int x, y; + + memset(character, '\0', sizeof(character)); + + if (!message || !*message || sscanf(message, "%99[^\n]", character) < 1) { + clif_displaymessage(fd, "Please, enter a player name (usage: @jail <char_name>)."); + return -1; + } + + if ((pl_sd = map_nick2sd(character)) != NULL) { + if (pc_isGM(sd) >= pc_isGM(pl_sd)) { // you can jail only lower or same GM + switch(rand() % 2) { + case 0: + x = 24; + y = 75; + break; + default: + x = 49; + y = 75; + break; + } + if (pc_setpos(pl_sd, "sec_pri.gat", x, y, 3) == 0) { + pc_setsavepoint(pl_sd, "sec_pri.gat", x, y); // Save Char Respawn Point in the jail room [Lupus] + clif_displaymessage(pl_sd->fd, msg_table[117]); // GM has send you in jails. + clif_displaymessage(fd, msg_table[118]); // Player warped in jails. + } else { + clif_displaymessage(fd, msg_table[1]); // Map not found. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[81]); // Your GM level don't authorise you to do this action on this player. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[3]); // Character not found. + return -1; + } + + return 0; +} + +/*========================================== + * @unjail/@discharge <char_name> by [Yor] + * Special warp! No check with nowarp and nowarpto flag + *------------------------------------------ + */ +int atcommand_unjail( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char character[100]; + struct map_session_data *pl_sd; + + memset(character, '\0', sizeof(character)); + + if (!message || !*message || sscanf(message, "%99[^\n]", character) < 1) { + clif_displaymessage(fd, "Please, enter a player name (usage: @unjail/@discharge <char_name>)."); + return -1; + } + + if ((pl_sd = map_nick2sd(character)) != NULL) { + if (pc_isGM(sd) >= pc_isGM(pl_sd)) { // you can jail only lower or same GM + if (pl_sd->bl.m != map_mapname2mapid("sec_pri.gat")) { + clif_displaymessage(fd, msg_table[119]); // This player is not in jails. + return -1; + } else if (pc_setpos(pl_sd, "prontera.gat", 156, 191, 3) == 0) { + pc_setsavepoint(pl_sd, "prontera.gat", 156, 191); // Save char respawn point in Prontera + clif_displaymessage(pl_sd->fd, msg_table[120]); // GM has discharge you. + clif_displaymessage(fd, msg_table[121]); // Player warped to Prontera. + } else { + clif_displaymessage(fd, msg_table[1]); // Map not found. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[81]); // Your GM level don't authorise you to do this action on this player. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[3]); // Character not found. + return -1; + } + + return 0; +} + +/*========================================== + * @disguise <mob_id> by [Valaris] (simplified by [Yor]) + *------------------------------------------ + */ +int atcommand_disguise( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + int mob_id; + + if (!message || !*message) { + clif_displaymessage(fd, "Please, enter a Monster/NPC name/id (usage: @disguise <monster_name_or_monster_ID>)."); + return -1; + } + + if ((mob_id = mobdb_searchname(message)) == 0) // check name first (to avoid possible name begining by a number) + mob_id = atoi(message); + + if ((mob_id >= 46 && mob_id <= 125) || (mob_id >= 700 && mob_id <= 718) || // NPC + (mob_id >= 721 && mob_id <= 755) || (mob_id >= 757 && mob_id <= 811) || // NPC + (mob_id >= 813 && mob_id <= 834) || // NPC + (mob_id > 1000 && mob_id < 1521)) { // monsters + if (pc_isriding(sd)) { // temporary prevention of crash caused by peco + disguise, will look into a better solution [Valaris] + clif_displaymessage(fd, msg_table[227]); // Cannot wear disguise while riding a Peco. + return -1; + } + sd->disguiseflag = 1; // set to override items with disguise script [Valaris] + sd->disguise = mob_id; + pc_setpos(sd, sd->mapname, sd->bl.x, sd->bl.y, 3); + clif_displaymessage(fd, msg_table[122]); // Disguise applied. + } else { + clif_displaymessage(fd, msg_table[123]); // Monster/NPC name/id hasn't been found. + return -1; + } + + return 0; +} + +/*========================================== + * @undisguise by [Yor] + *------------------------------------------ + */ +int atcommand_undisguise( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + if (sd->disguise) { + clif_clearchar(&sd->bl, 9); + sd->disguise = 0; + pc_setpos(sd, sd->mapname, sd->bl.x, sd->bl.y, 3); + clif_displaymessage(fd, msg_table[124]); // Undisguise applied. + } else { + clif_displaymessage(fd, msg_table[125]); // You're not disguised. + return -1; + } + + return 0; +} + +/*========================================== + * @broadcast by [Valaris] + *------------------------------------------ + */ +int atcommand_broadcast( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char output[200]; + + memset(output, '\0', sizeof(output)); + + if (!message || !*message) { + clif_displaymessage(fd, "Please, enter a message (usage: @broadcast <message>)."); + return -1; + } + + sprintf(output, "%s : %s", sd->status.name, message); + intif_GMmessage(output, strlen(output) + 1, 0); + + return 0; +} + +/*========================================== + * @localbroadcast by [Valaris] + *------------------------------------------ + */ +int atcommand_localbroadcast( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char output[200]; + + memset(output, '\0', sizeof(output)); + + if (!message || !*message) { + clif_displaymessage(fd, "Please, enter a message (usage: @localbroadcast <message>)."); + return -1; + } + + sprintf(output, "%s : %s", sd->status.name, message); + + clif_GMmessage(&sd->bl, output, strlen(output) + 1, 1); // 1: ALL_SAMEMAP + + return 0; +} + +/*========================================== + * @ignorelist by [Yor] + *------------------------------------------ + */ +int atcommand_ignorelist( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char output[200]; + int count; + int i; + + memset(output, '\0', sizeof(output)); + + count = 0; + for(i = 0; i < (int)(sizeof(sd->ignore) / sizeof(sd->ignore[0])); i++) + if (sd->ignore[i].name[0]) + count++; + + if (sd->ignoreAll == 0) + if (count == 0) + clif_displaymessage(fd, msg_table[126]); // You accept any wisp (no wisper is refused). + else { + sprintf(output, msg_table[127], count); // You accept any wisp, except thoses from %d player(s): + clif_displaymessage(fd, output); + } + else + if (count == 0) + clif_displaymessage(fd, msg_table[128]); // You refuse all wisps (no specifical wisper is refused). + else { + sprintf(output, msg_table[129], count); // You refuse all wisps, AND refuse wisps from %d player(s): + clif_displaymessage(fd, output); + } + + if (count > 0) + for(i = 0; i < (int)(sizeof(sd->ignore) / sizeof(sd->ignore[0])); i++) + if (sd->ignore[i].name[0]) + clif_displaymessage(fd, sd->ignore[i].name); + + return 0; +} + +/*========================================== + * @charignorelist <player_name> by [Yor] + *------------------------------------------ + */ +int atcommand_charignorelist( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char character[100]; + struct map_session_data *pl_sd; + char output[200]; + int count; + int i; + + memset(character, '\0', sizeof(character)); + memset(output, '\0', sizeof(output)); + + if (!message || !*message || sscanf(message, "%99[^\n]", character) < 1) { + clif_displaymessage(fd, "Please, enter a player name (usage: @charignorelist <char name>)."); + return -1; + } + + if ((pl_sd = map_nick2sd(character)) != NULL) { + count = 0; + for(i = 0; i < (int)(sizeof(pl_sd->ignore) / sizeof(pl_sd->ignore[0])); i++) + if (pl_sd->ignore[i].name[0]) + count++; + + if (pl_sd->ignoreAll == 0) + if (count == 0) { + sprintf(output, msg_table[130], pl_sd->status.name); // '%s' accept any wisp (no wisper is refused). + clif_displaymessage(fd, output); + } else { + sprintf(output, msg_table[131], pl_sd->status.name, count); // '%s' accept any wisp, except thoses from %d player(s): + clif_displaymessage(fd, output); + } + else + if (count == 0) { + sprintf(output, msg_table[132], pl_sd->status.name); // '%s' refuse all wisps (no specifical wisper is refused). + clif_displaymessage(fd, output); + } else { + sprintf(output, msg_table[133], pl_sd->status.name, count); // '%s' refuse all wisps, AND refuse wisps from %d player(s): + clif_displaymessage(fd, output); + } + + if (count > 0) + for(i = 0; i < (int)(sizeof(pl_sd->ignore) / sizeof(pl_sd->ignore[0])); i++) + if (pl_sd->ignore[i].name[0]) + clif_displaymessage(fd, pl_sd->ignore[i].name); + + } else { + clif_displaymessage(fd, msg_table[3]); // Character not found. + return -1; + } + + return 0; +} + +/*========================================== + * @inall <player_name> by [Yor] + *------------------------------------------ + */ +int atcommand_inall( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char character[100]; + char output[200]; + struct map_session_data *pl_sd; + + memset(character, '\0', sizeof(character)); + memset(output, '\0', sizeof(output)); + + if (!message || !*message || sscanf(message, "%99[^\n]", character) < 1) { + clif_displaymessage(fd, "Please, enter a player name (usage: @inall <char name>)."); + return -1; + } + + if ((pl_sd = map_nick2sd(character)) != NULL) { + if (pc_isGM(sd) >= pc_isGM(pl_sd)) { // you can change wisp option only to lower or same level + if (pl_sd->ignoreAll == 0) { + sprintf(output, msg_table[134], pl_sd->status.name); // '%s' already accepts all wispers. + clif_displaymessage(fd, output); + return -1; + } else { + pl_sd->ignoreAll = 0; + sprintf(output, msg_table[135], pl_sd->status.name); // '%s' now accepts all wispers. + clif_displaymessage(fd, output); + // message to player + clif_displaymessage(pl_sd->fd, msg_table[136]); // A GM has authorised all wispers for you. + WFIFOW(pl_sd->fd,0) = 0x0d2; // R 00d2 <type>.B <fail>.B: type: 0: deny, 1: allow, fail: 0: success, 1: fail + WFIFOB(pl_sd->fd,2) = 1; + WFIFOB(pl_sd->fd,3) = 0; // success + WFIFOSET(pl_sd->fd, 4); // packet_len_table[0x0d2] + } + } else { + clif_displaymessage(fd, msg_table[81]); // Your GM level don't authorise you to do this action on this player. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[3]); // Character not found. + return -1; + } + + return 0; +} + +/*========================================== + * @exall <player_name> by [Yor] + *------------------------------------------ + */ +int atcommand_exall( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char character[100]; + char output[200]; + struct map_session_data *pl_sd; + + memset(character, '\0', sizeof(character)); + memset(output, '\0', sizeof(output)); + + if (!message || !*message || sscanf(message, "%99[^\n]", character) < 1) { + clif_displaymessage(fd, "Please, enter a player name (usage: @exall <char name>)."); + return -1; + } + + if ((pl_sd = map_nick2sd(character)) != NULL) { + if (pc_isGM(sd) >= pc_isGM(pl_sd)) { // you can change wisp option only to lower or same level + if (pl_sd->ignoreAll == 1) { + sprintf(output, msg_table[137], pl_sd->status.name); // '%s' already blocks all wispers. + clif_displaymessage(fd, output); + return -1; + } else { + pl_sd->ignoreAll = 1; + sprintf(output, msg_table[138], pl_sd->status.name); // '%s' blocks now all wispers. + clif_displaymessage(fd, output); + // message to player + clif_displaymessage(pl_sd->fd, msg_table[139]); // A GM has blocked all wispers for you. + WFIFOW(pl_sd->fd,0) = 0x0d2; // R 00d2 <type>.B <fail>.B: type: 0: deny, 1: allow, fail: 0: success, 1: fail + WFIFOB(pl_sd->fd,2) = 0; + WFIFOB(pl_sd->fd,3) = 0; // success + WFIFOSET(pl_sd->fd,4); // packet_len_table[0x0d2] + } + } else { + clif_displaymessage(fd, msg_table[81]); // Your GM level don't authorise you to do this action on this player. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[3]); // Character not found. + return -1; + } + + return 0; +} + +/*========================================== + * @chardisguise <mob_id> <character> by Kalaspuff (based off Valaris' and Yor's work) + *------------------------------------------ + */ +int atcommand_chardisguise( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + int mob_id; + char character[100]; + char mob_name[100]; + struct map_session_data* pl_sd; + + memset(character, '\0', sizeof(character)); + memset(mob_name, '\0', sizeof(mob_name)); + + if (!message || !*message || sscanf(message, "%s %99[^\n]", mob_name, character) < 2) { + clif_displaymessage(fd, "Please, enter a Monster/NPC name/id and a player name (usage: @chardisguise <monster_name_or_monster_ID> <char name>)."); + return -1; + } + + if ((mob_id = mobdb_searchname(mob_name)) == 0) // check name first (to avoid possible name begining by a number) + mob_id = atoi(mob_name); + + if ((pl_sd = map_nick2sd(character)) != NULL) { + if (pc_isGM(sd) >= pc_isGM(pl_sd)) { // you can disguise only lower or same level + if ((mob_id >= 46 && mob_id <= 125) || (mob_id >= 700 && mob_id <= 718) || // NPC + (mob_id >= 721 && mob_id <= 755) || (mob_id >= 757 && mob_id <= 811) || // NPC + (mob_id >= 813 && mob_id <= 834) || // NPC + (mob_id > 1000 && mob_id < 1521)) { // monsters + if (pc_isriding(pl_sd)) { // temporary prevention of crash caused by peco + disguise, will look into a better solution [Valaris] + clif_displaymessage(fd, msg_table[228]); // Character cannot wear disguise while riding a Peco. + return -1; + } + pl_sd->disguiseflag = 1; // set to override items with disguise script [Valaris] + pl_sd->disguise = mob_id; + pc_setpos(pl_sd, pl_sd->mapname, pl_sd->bl.x, pl_sd->bl.y, 3); + clif_displaymessage(fd, msg_table[140]); // Character's disguise applied. + } else { + clif_displaymessage(fd, msg_table[123]); // Monster/NPC name/id hasn't been found. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[81]); // Your GM level don't authorise you to do this action on this player. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[3]); // Character not found. + return -1; + } + + return 0; +} + +/*========================================== + * @charundisguise <character> by Kalaspuff (based off Yor's work) + *------------------------------------------ + */ +int atcommand_charundisguise( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char character[100]; + struct map_session_data* pl_sd; + + memset(character, '\0', sizeof(character)); + + if (!message || !*message || sscanf(message, "%99[^\n]", character) < 1) { + clif_displaymessage(fd, "Please, enter a player name (usage: @charundisguise <char name>)."); + return -1; + } + + if ((pl_sd = map_nick2sd(character)) != NULL) { + if (pc_isGM(sd) >= pc_isGM(pl_sd)) { // you can undisguise only lower or same level + if (pl_sd->disguise) { + clif_clearchar(&pl_sd->bl, 9); + pl_sd->disguise = 0; + pc_setpos(pl_sd, pl_sd->mapname, pl_sd->bl.x, pl_sd->bl.y, 3); + clif_displaymessage(fd, msg_table[141]); // Character's undisguise applied. + } else { + clif_displaymessage(fd, msg_table[142]); // Character is not disguised. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[81]); // Your GM level don't authorise you to do this action on this player. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[3]); // Character not found. + return -1; + } + + return 0; +} + +/*========================================== + * @email <actual@email> <new@email> by [Yor] + *------------------------------------------ + */ +int atcommand_email( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char actual_email[100]; + char new_email[100]; + + memset(actual_email, '\0', sizeof(actual_email)); + memset(new_email, '\0', sizeof(new_email)); + + if (!message || !*message || sscanf(message, "%99s %99s", actual_email, new_email) < 2) { + clif_displaymessage(fd, "Please enter 2 emails (usage: @email <actual@email> <new@email>)."); + return -1; + } + + if (e_mail_check(actual_email) == 0) { + clif_displaymessage(fd, msg_table[144]); // Invalid actual email. If you have default e-mail, give a@a.com. + return -1; + } else if (e_mail_check(new_email) == 0) { + clif_displaymessage(fd, msg_table[145]); // Invalid new email. Please enter a real e-mail. + return -1; + } else if (strcmpi(new_email, "a@a.com") == 0) { + clif_displaymessage(fd, msg_table[146]); // New email must be a real e-mail. + return -1; + } else if (strcmpi(actual_email, new_email) == 0) { + clif_displaymessage(fd, msg_table[147]); // New email must be different of the actual e-mail. + return -1; + } else { + chrif_changeemail(sd->status.account_id, actual_email, new_email); + clif_displaymessage(fd, msg_table[148]); // Information sended to login-server via char-server. + } + + return 0; +} + +/*========================================== + *@effect + *------------------------------------------ + */ +int atcommand_effect( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + struct map_session_data *pl_sd; + int type = 0, flag = 0, i; + + if (!message || !*message || sscanf(message, "%d %d", &type,&flag) < 2) { + clif_displaymessage(fd, "Please, enter at least a option (usage: @effect <type+>)."); + return -1; + } + if(flag <=0){ + clif_specialeffect(&sd->bl, type, flag); + clif_displaymessage(fd, msg_table[229]); // Your effect has changed. + } + else{ + for (i = 0; i < fd_max; i++) { + if (session[i] && (pl_sd = session[i]->session_data) && pl_sd->state.auth) { + clif_specialeffect(&pl_sd->bl, type, flag); + clif_displaymessage(pl_sd->fd, msg_table[229]); // Your effect has changed. + } + } + } + + return 0; +} + +/*========================================== + * @charitemlist <character>: Displays the list of a player's items. + *------------------------------------------ + */ +int +atcommand_character_item_list( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + struct map_session_data *pl_sd; + struct item_data *item_data, *item_temp; + int i, j, equip, count, counter, counter2; + char character[100], output[200], equipstr[100], outputtmp[200]; + + memset(character, '\0', sizeof(character)); + memset(output, '\0', sizeof(output)); + memset(equipstr, '\0', sizeof(equipstr)); + memset(outputtmp, '\0', sizeof(outputtmp)); + + if (!message || !*message || sscanf(message, "%99[^\n]", character) < 1) { + clif_displaymessage(fd, "Please, enter a player name (usage: @charitemlist <char name>)."); + return -1; + } + + if ((pl_sd = map_nick2sd(character)) != NULL) { + if (pc_isGM(sd) >= pc_isGM(pl_sd)) { // you can look items only lower or same level + counter = 0; + count = 0; + for (i = 0; i < MAX_INVENTORY; i++) { + if (pl_sd->status.inventory[i].nameid > 0 && (item_data = itemdb_search(pl_sd->status.inventory[i].nameid)) != NULL) { + counter = counter + pl_sd->status.inventory[i].amount; + count++; + if (count == 1) { + sprintf(output, "------ Items list of '%s' ------", pl_sd->status.name); + clif_displaymessage(fd, output); + } + if ((equip = pl_sd->status.inventory[i].equip)) { + strcpy(equipstr, "| equiped: "); + if (equip & 4) + strcat(equipstr, "robe/gargment, "); + if (equip & 8) + strcat(equipstr, "left accessory, "); + if (equip & 16) + strcat(equipstr, "body/armor, "); + if ((equip & 34) == 2) + strcat(equipstr, "right hand, "); + if ((equip & 34) == 32) + strcat(equipstr, "left hand, "); + if ((equip & 34) == 34) + strcat(equipstr, "both hands, "); + if (equip & 64) + strcat(equipstr, "feet, "); + if (equip & 128) + strcat(equipstr, "right accessory, "); + if ((equip & 769) == 1) + strcat(equipstr, "lower head, "); + if ((equip & 769) == 256) + strcat(equipstr, "top head, "); + if ((equip & 769) == 257) + strcat(equipstr, "lower/top head, "); + if ((equip & 769) == 512) + strcat(equipstr, "mid head, "); + if ((equip & 769) == 512) + strcat(equipstr, "lower/mid head, "); + if ((equip & 769) == 769) + strcat(equipstr, "lower/mid/top head, "); + // remove final ', ' + equipstr[strlen(equipstr) - 2] = '\0'; + } else + memset(equipstr, '\0', sizeof(equipstr)); + if (sd->status.inventory[i].refine) + sprintf(output, "%d %s %+d (%s %+d, id: %d) %s", pl_sd->status.inventory[i].amount, item_data->name, pl_sd->status.inventory[i].refine, item_data->jname, pl_sd->status.inventory[i].refine, pl_sd->status.inventory[i].nameid, equipstr); + else + sprintf(output, "%d %s (%s, id: %d) %s", pl_sd->status.inventory[i].amount, item_data->name, item_data->jname, pl_sd->status.inventory[i].nameid, equipstr); + clif_displaymessage(fd, output); + memset(output, '\0', sizeof(output)); + counter2 = 0; + for (j = 0; j < item_data->slot; j++) { + if (pl_sd->status.inventory[i].card[j]) { + if ((item_temp = itemdb_search(pl_sd->status.inventory[i].card[j])) != NULL) { + if (output[0] == '\0') + sprintf(outputtmp, " -> (card(s): #%d %s (%s), ", ++counter2, item_temp->name, item_temp->jname); + else + sprintf(outputtmp, "#%d %s (%s), ", ++counter2, item_temp->name, item_temp->jname); + strcat(output, outputtmp); + } + } + } + if (output[0] != '\0') { + output[strlen(output) - 2] = ')'; + output[strlen(output) - 1] = '\0'; + clif_displaymessage(fd, output); + } + } + } + if (count == 0) + clif_displaymessage(fd, "No item found on this player."); + else { + sprintf(output, "%d item(s) found in %d kind(s) of items.", counter, count); + clif_displaymessage(fd, output); + } + } else { + clif_displaymessage(fd, msg_table[81]); // Your GM level don't authorise you to do this action on this player. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[3]); // Character not found. + return -1; + } + + return 0; +} + +/*========================================== + * @charstoragelist <character>: Displays the items list of a player's storage. + *------------------------------------------ + */ +int +atcommand_character_storage_list( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + struct storage *stor; + struct map_session_data *pl_sd; + struct item_data *item_data, *item_temp; + int i, j, count, counter, counter2; + char character[100], output[200], outputtmp[200]; + + memset(character, '\0', sizeof(character)); + memset(output, '\0', sizeof(output)); + memset(outputtmp, '\0', sizeof(outputtmp)); + + if (!message || !*message || sscanf(message, "%99[^\n]", character) < 1) { + clif_displaymessage(fd, "Please, enter a player name (usage: @charitemlist <char name>)."); + return -1; + } + + if ((pl_sd = map_nick2sd(character)) != NULL) { + if (pc_isGM(sd) >= pc_isGM(pl_sd)) { // you can look items only lower or same level + if((stor = account2storage2(pl_sd->status.account_id)) != NULL) { + counter = 0; + count = 0; + for (i = 0; i < MAX_STORAGE; i++) { + if (stor->storage[i].nameid > 0 && (item_data = itemdb_search(stor->storage[i].nameid)) != NULL) { + counter = counter + stor->storage[i].amount; + count++; + if (count == 1) { + sprintf(output, "------ Storage items list of '%s' ------", pl_sd->status.name); + clif_displaymessage(fd, output); + } + if (stor->storage[i].refine) + sprintf(output, "%d %s %+d (%s %+d, id: %d)", stor->storage[i].amount, item_data->name, stor->storage[i].refine, item_data->jname, stor->storage[i].refine, stor->storage[i].nameid); + else + sprintf(output, "%d %s (%s, id: %d)", stor->storage[i].amount, item_data->name, item_data->jname, stor->storage[i].nameid); + clif_displaymessage(fd, output); + memset(output, '\0', sizeof(output)); + counter2 = 0; + for (j = 0; j < item_data->slot; j++) { + if (stor->storage[i].card[j]) { + if ((item_temp = itemdb_search(stor->storage[i].card[j])) != NULL) { + if (output[0] == '\0') + sprintf(outputtmp, " -> (card(s): #%d %s (%s), ", ++counter2, item_temp->name, item_temp->jname); + else + sprintf(outputtmp, "#%d %s (%s), ", ++counter2, item_temp->name, item_temp->jname); + strcat(output, outputtmp); + } + } + } + if (output[0] != '\0') { + output[strlen(output) - 2] = ')'; + output[strlen(output) - 1] = '\0'; + clif_displaymessage(fd, output); + } + } + } + if (count == 0) + clif_displaymessage(fd, "No item found in the storage of this player."); + else { + sprintf(output, "%d item(s) found in %d kind(s) of items.", counter, count); + clif_displaymessage(fd, output); + } + } else { + clif_displaymessage(fd, "This player has no storage."); + return -1; + } + } else { + clif_displaymessage(fd, msg_table[81]); // Your GM level don't authorise you to do this action on this player. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[3]); // Character not found. + return -1; + } + + return 0; +} + +/*========================================== + * @charcartlist <character>: Displays the items list of a player's cart. + *------------------------------------------ + */ +int +atcommand_character_cart_list( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + struct map_session_data *pl_sd; + struct item_data *item_data, *item_temp; + int i, j, count, counter, counter2; + char character[100], output[200], outputtmp[200]; + + memset(character, '\0', sizeof(character)); + memset(output, '\0', sizeof(output)); + memset(outputtmp, '\0', sizeof(outputtmp)); + + if (!message || !*message || sscanf(message, "%99[^\n]", character) < 1) { + clif_displaymessage(fd, "Please, enter a player name (usage: @charitemlist <char name>)."); + return -1; + } + + if ((pl_sd = map_nick2sd(character)) != NULL) { + if (pc_isGM(sd) >= pc_isGM(pl_sd)) { // you can look items only lower or same level + counter = 0; + count = 0; + for (i = 0; i < MAX_CART; i++) { + if (pl_sd->status.cart[i].nameid > 0 && (item_data = itemdb_search(pl_sd->status.cart[i].nameid)) != NULL) { + counter = counter + pl_sd->status.cart[i].amount; + count++; + if (count == 1) { + sprintf(output, "------ Cart items list of '%s' ------", pl_sd->status.name); + clif_displaymessage(fd, output); + } + if (pl_sd->status.cart[i].refine) + sprintf(output, "%d %s %+d (%s %+d, id: %d)", pl_sd->status.cart[i].amount, item_data->name, pl_sd->status.cart[i].refine, item_data->jname, pl_sd->status.cart[i].refine, pl_sd->status.cart[i].nameid); + else + sprintf(output, "%d %s (%s, id: %d)", pl_sd->status.cart[i].amount, item_data->name, item_data->jname, pl_sd->status.cart[i].nameid); + clif_displaymessage(fd, output); + memset(output, '\0', sizeof(output)); + counter2 = 0; + for (j = 0; j < item_data->slot; j++) { + if (pl_sd->status.cart[i].card[j]) { + if ((item_temp = itemdb_search(pl_sd->status.cart[i].card[j])) != NULL) { + if (output[0] == '\0') + sprintf(outputtmp, " -> (card(s): #%d %s (%s), ", ++counter2, item_temp->name, item_temp->jname); + else + sprintf(outputtmp, "#%d %s (%s), ", ++counter2, item_temp->name, item_temp->jname); + strcat(output, outputtmp); + } + } + } + if (output[0] != '\0') { + output[strlen(output) - 2] = ')'; + output[strlen(output) - 1] = '\0'; + clif_displaymessage(fd, output); + } + } + } + if (count == 0) + clif_displaymessage(fd, "No item found in the cart of this player."); + else { + sprintf(output, "%d item(s) found in %d kind(s) of items.", counter, count); + clif_displaymessage(fd, output); + } + } else { + clif_displaymessage(fd, msg_table[81]); // Your GM level don't authorise you to do this action on this player. + return -1; + } + } else { + clif_displaymessage(fd, msg_table[3]); // Character not found. + return -1; + } + + return 0; +} + +/*========================================== + * @killer by MouseJstr + * enable killing players even when not in pvp + *------------------------------------------ + */ +int +atcommand_killer( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + sd->special_state.killer = !sd->special_state.killer; + + if(sd->special_state.killer) + clif_displaymessage(fd, msg_table[241]); + else + clif_displaymessage(fd, msg_table[242]); + + return 0; +} + +/*========================================== + * @killable by MouseJstr + * enable other people killing you + *------------------------------------------ + */ +int +atcommand_killable( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + sd->special_state.killable = !sd->special_state.killable; + + if(sd->special_state.killable) + clif_displaymessage(fd, msg_table[242]); + else + clif_displaymessage(fd, msg_table[241]); + + return 0; +} + +/*========================================== + * @charkillable by MouseJstr + * enable another player to be killed + *------------------------------------------ + */ +int +atcommand_charkillable( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + struct map_session_data *pl_sd = NULL; + + if (!message || !*message) + return -1; + + if((pl_sd=map_nick2sd((char *) message)) == NULL) + return -1; + + pl_sd->special_state.killable = !pl_sd->special_state.killable; + + if(pl_sd->special_state.killable) + clif_displaymessage(fd, "The player is now killable"); + else + clif_displaymessage(fd, "The player is no longer killable"); + + return 0; +} + + +/*========================================== + * @skillon by MouseJstr + * turn skills on for the map + *------------------------------------------ + */ +int +atcommand_skillon( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + map[sd->bl.m].flag.noskill = 0; + clif_displaymessage(fd, msg_table[244]); + return 0; +} + +/*========================================== + * @skilloff by MouseJstr + * Turn skills off on the map + *------------------------------------------ + */ +int +atcommand_skilloff( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + map[sd->bl.m].flag.noskill = 1; + clif_displaymessage(fd, msg_table[243]); + return 0; +} + +/*========================================== + * @npcmove by MouseJstr + * + * move a npc + *------------------------------------------ + */ +int +atcommand_npcmove(const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char character[100]; + int x = 0, y = 0; + struct npc_data *nd = 0; + + if( sd == NULL ) + return -1; + + if (!message || !*message) + return -1; + + memset(character, '\0', sizeof character); + + if (sscanf(message, "%d %d %99[^\n]", &x, &y, character) < 4) + return -1; + + nd=npc_name2id(character); + if (nd==NULL) + return -1; + + npc_enable(character, 0); + nd->bl.x = x; + nd->bl.y = y; + npc_enable(character, 1); + + return 0; +} + +/*========================================== + * @addwarp by MouseJstr + * + * Create a new static warp point. + *------------------------------------------ + */ +int +atcommand_addwarp(const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char w1[64], w3[64], w4[64]; + char map[30], output[200]; + int x,y,ret; + + if (!message || !*message) + return -1; + + if (sscanf(message, "%99s %d %d[^\n]", map, &x, &y ) < 3) + return -1; + + sprintf(w1,"%s,%d,%d", sd->mapname, sd->bl.x, sd->bl.y); + sprintf(w3,"%s%d%d%d%d", map,sd->bl.x, sd->bl.y, x, y); + sprintf(w4,"1,1,%s.gat,%d,%d", map, x, y); + + ret = npc_parse_warp(w1, "warp", w3, w4); + + sprintf(output, "New warp NPC => %s",w3); + + clif_displaymessage(fd, output); + + return ret; +} + +/*========================================== + * @follow by [MouseJstr] + * + * Follow a player .. staying no more then 5 spaces away + *------------------------------------------ + */ +int +atcommand_follow(const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + struct map_session_data *pl_sd = NULL; + + if (!message || !*message) + return -1; + if((pl_sd=map_nick2sd((char *) message)) != NULL) + pc_follow(sd, pl_sd->bl.id); + else + return 1; + return 0; +} + + +/*========================================== + * @chareffect by [MouseJstr] + * + * Create a effect localized on another character + *------------------------------------------ + */ +int +atcommand_chareffect(const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + struct map_session_data *pl_sd = NULL; + char target[255]; + int type = 0; + + if (!message || !*message || sscanf(message, "%d %s", &type, target) != 2) { + clif_displaymessage(fd, "usage: @chareffect <type+> <target>."); + return -1; + } + + if((pl_sd=map_nick2sd((char *) target)) == NULL) + return -1; + + clif_specialeffect(&pl_sd->bl, type, 0); + clif_displaymessage(fd, msg_table[229]); // Your effect has changed. + + return 0; +} +/*========================================== + * @dropall by [MouseJstr] + * + * Drop all your possession on the ground + *------------------------------------------ + */ +int +atcommand_dropall(const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + int i; + for (i = 0; i < MAX_INVENTORY; i++) { + if (sd->status.inventory[i].amount) { + if(sd->status.inventory[i].equip != 0) + pc_unequipitem(sd, i, 0); + pc_dropitem(sd, i, sd->status.inventory[i].amount); + } + } + return 0; +} +/*========================================== + * @chardropall by [MouseJstr] + * + * Throw all the characters possessions on the ground. Normally + * done in response to them being disrespectful of a GM + *------------------------------------------ + */ +int +atcommand_chardropall(const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + int i; + struct map_session_data *pl_sd = NULL; + + if (!message || !*message) + return -1; + if((pl_sd=map_nick2sd((char *) message)) == NULL) + return -1; + for (i = 0; i < MAX_INVENTORY; i++) { + if (pl_sd->status.inventory[i].amount) { + if(pl_sd->status.inventory[i].equip != 0) + pc_unequipitem(pl_sd, i, 0); + pc_dropitem(pl_sd, i, pl_sd->status.inventory[i].amount); + } + } + + clif_displaymessage(pl_sd->fd, "Ever play 52 card pickup?"); + clif_displaymessage(fd, "It is done"); + //clif_displaymessage(fd, "It is offical.. your a jerk"); + + return 0; +} +/*========================================== + * @storeall by [MouseJstr] + * + * Put everything into storage to simplify your inventory to make + * debugging easie + *------------------------------------------ + */ +int +atcommand_storeall(const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + int i; + if (storage_storageopen(sd) == 1) { + clif_displaymessage(fd, "run this command again.."); + return 0; + } + for (i = 0; i < MAX_INVENTORY; i++) { + if (sd->status.inventory[i].amount) { + if(sd->status.inventory[i].equip != 0) + pc_unequipitem(sd, i, 0); + storage_storageadd(sd, i, sd->status.inventory[i].amount); + } + } + storage_storageclose(sd); + + clif_displaymessage(fd, "It is done"); + return 0; +} +/*========================================== + * @charstoreall by [MouseJstr] + * + * A way to screw with players who piss you off + *------------------------------------------ + */ +int +atcommand_charstoreall(const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + int i; + struct map_session_data *pl_sd = NULL; + + if (!message || !*message) + return -1; + if((pl_sd=map_nick2sd((char *) message)) == NULL) + return -1; + + if (storage_storageopen(pl_sd) == 1) { + clif_displaymessage(fd, "Had to open the characters storage window..."); + clif_displaymessage(fd, "run this command again.."); + return 0; + } + for (i = 0; i < MAX_INVENTORY; i++) { + if (pl_sd->status.inventory[i].amount) { + if(pl_sd->status.inventory[i].equip != 0) + pc_unequipitem(pl_sd, i, 0); + storage_storageadd(pl_sd, i, sd->status.inventory[i].amount); + } + } + storage_storageclose(pl_sd); + + clif_displaymessage(pl_sd->fd, "Everything you own has been put away for safe keeping."); + clif_displaymessage(pl_sd->fd, "go to the nearest kafka to retrieve it.."); + clif_displaymessage(pl_sd->fd, " -- the management"); + + clif_displaymessage(fd, "It is done"); + + return 0; +} +/*========================================== + * @skillid by [MouseJstr] + * + * lookup a skill by name + *------------------------------------------ + */ +int +atcommand_skillid(const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + int skillen = 0, idx = 0; + if (!message || !*message) + return -1; + skillen = strlen(message); + while (skill_names[idx].id != 0) { + if ((strnicmp(skill_names[idx].name, message, skillen) == 0) || + (strnicmp(skill_names[idx].desc, message, skillen) == 0)) { + char output[255]; + sprintf(output, "skill %d: %s", skill_names[idx].id, skill_names[idx].desc); + clif_displaymessage(fd, output); + } + idx++; + } + return 0; +} +/*========================================== + * @useskill by [MouseJstr] + * + * A way of using skills without having to find them in the skills menu + *------------------------------------------ + */ +int +atcommand_useskill(const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + struct map_session_data *pl_sd = NULL; + int skillnum; + int skilllv; + int inf; + char target[255]; + + if (!message || !*message) + return -1; + if(sscanf(message, "%d %d %s", &skillnum, &skilllv, target) != 3) { + clif_displaymessage(fd, "Usage: @useskill <skillnum> <skillv> <target>"); + return -1; + } + if((pl_sd=map_nick2sd(target)) == NULL) { + return -1; + } + + inf = skill_get_inf(skillnum); + + if ((inf == 2) || (inf == 1)) + skill_use_pos(sd, pl_sd->bl.x, pl_sd->bl.y, skillnum, skilllv); + else + skill_use_id(sd, pl_sd->bl.id, skillnum, skilllv); + + return 0; +} +/*========================================== + * It is made to rain. + *------------------------------------------ + */ +int +atcommand_rain( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + int effno = 0; + effno = 161; + nullpo_retr(-1, sd); + if (effno < 0 || map[sd->bl.m].flag.rain) + return -1; + + map[sd->bl.m].flag.rain=1; + clif_specialeffect(&sd->bl,effno,2); + return 0; +} +/*========================================== + * It is made to snow. + *------------------------------------------ + */ +int +atcommand_snow( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + int effno = 0; + effno = 162; + nullpo_retr(-1, sd); + if (effno < 0 || map[sd->bl.m].flag.snow) + return -1; + + map[sd->bl.m].flag.snow=1; + clif_specialeffect(&sd->bl,effno,2); + return 0; +} + +/*========================================== + * Cherry tree snowstorm is made to fall. (Sakura) + *------------------------------------------ + */ +int +atcommand_sakura( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + int effno = 0; + effno = 163; + nullpo_retr(-1, sd); + if (effno < 0 || map[sd->bl.m].flag.sakura) + return -1; + + map[sd->bl.m].flag.sakura=1; + clif_specialeffect(&sd->bl,effno,2); + return 0; +} + +/*========================================== + * Fog hangs over. + *------------------------------------------ + */ +int +atcommand_fog( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + int effno = 0; + effno = 233; + nullpo_retr(-1, sd); + if (effno < 0 || map[sd->bl.m].flag.fog) + return -1; + + map[sd->bl.m].flag.fog=1; + clif_specialeffect(&sd->bl,effno,2); + + return 0; +} + +/*========================================== + * Fallen leaves fall. + *------------------------------------------ + */ +int +atcommand_leaves( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + int effno = 0; + effno = 333; + nullpo_retr(-1, sd); + if (effno < 0 || map[sd->bl.m].flag.leaves) + return -1; + + map[sd->bl.m].flag.leaves=1; + clif_specialeffect(&sd->bl,effno,2); + return 0; +} +/*========================================== + * + *------------------------------------------ + */ +int +atcommand_summon( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + char name[100]; + int mob_id = 0; + int x = 0; + int y = 0; + int id = 0; + struct mob_data *md; + unsigned int tick=gettick(); + + nullpo_retr(-1, sd); + + if (!message || !*message) + return -1; + if (sscanf(message, "%99s", name) < 1) + return -1; + + if ((mob_id = atoi(name)) == 0) + mob_id = mobdb_searchname(name); + if(mob_id == 0) + return -1; + + x = sd->bl.x + (rand() % 10 - 5); + y = sd->bl.y + (rand() % 10 - 5); + + id = mob_once_spawn(sd,"this", x, y, "--ja--", mob_id, 1, ""); + if((md=(struct mob_data *)map_id2bl(id))){ + md->master_id=sd->bl.id; + md->state.special_mob_ai=1; + md->mode=mob_db[md->class].mode|0x04; + md->deletetimer=add_timer(tick+60000,mob_timer_delete,id,0); + clif_misceffect2(&md->bl,344); + } + clif_skill_poseffect(&sd->bl,AM_CALLHOMUN,1,x,y,tick); + + return 0; +} + + +/*========================================== + * @adjcmdlvl by [MouseJstr] + * + * Temp adjust the GM level required to use a GM command + * + * Used during beta testing to allow players to use GM commands + * for short periods of time + *------------------------------------------ + */ +int +atcommand_adjcmdlvl( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + int i, newlev; + char cmd[100]; + + if (!message || !*message || sscanf(message, "%d %s", &newlev, cmd) != 2) { + clif_displaymessage(fd, "usage: @adjcmdlvl <lvl> <command>."); + return -1; + } + + for (i = 0; atcommand_info[i].type != AtCommand_None; i++) + if (strcmpi(cmd, atcommand_info[i].command+1) == 0) { + atcommand_info[i].level = newlev; + clif_displaymessage(fd, "@command level changed."); + return 0; + } + + clif_displaymessage(fd, "@command not found."); + return -1; +} + +/*========================================== + * @adjgmlvl by [MouseJstr] + * + * Create a temp GM + * + * Used during beta testing to allow players to use GM commands + * for short periods of time + *------------------------------------------ + */ +int +atcommand_adjgmlvl( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + int newlev; + char user[100]; + struct map_session_data *pl_sd; + + if (!message || !*message || sscanf(message, "%d %s", &newlev, user) != 2) { + clif_displaymessage(fd, "usage: @adjgmlvl <lvl> <user>."); + return -1; + } + + if((pl_sd=map_nick2sd((char *) user)) == NULL) + return -1; + + pc_set_gm_level(pl_sd->status.account_id, newlev); + + return 0; +} + + +/*========================================== + * @trade by [MouseJstr] + * + * Open a trade window with a remote player + * + * If I have to jump to a remote player one more time, I am + * gonna scream! + *------------------------------------------ + */ +int +atcommand_trade( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + struct map_session_data *pl_sd = NULL; + + if (!message || !*message) + return -1; + if((pl_sd=map_nick2sd((char *) message)) != NULL) { + trade_traderequest(sd, pl_sd->bl.id); + return 0; + } + return -1; +} + + +/*=========================== + * @unmute [Valaris] + *=========================== +*/ +int atcommand_unmute( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + struct map_session_data *pl_sd = NULL; + if (!message || !*message) + return -1; + + if((pl_sd=map_nick2sd((char *) message)) != NULL) { + if(pl_sd->sc_data[SC_NOCHAT].timer!=-1) { + skill_status_change_end(&pl_sd->bl,SC_NOCHAT,-1); + clif_displaymessage(sd->fd,"Player unmuted"); + } + else + clif_displaymessage(sd->fd,"Player is not muted"); + } + + return 0; +} + +#ifndef TXT_ONLY /* Begin SQL-Only commands */ + +/*========================================== + * Mail System commands by [Valaris] + *------------------------------------------ + */ +int atcommand_listmail( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + if(!battle_config.mail_system) + return 0; + + nullpo_retr(-1, sd); + + if(strlen(command)==12) + mail_check(sd,3); + else if(strlen(command)==9) + mail_check(sd,2); + else + mail_check(sd,1); + return 0; +} + +int atcommand_readmail( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + if(!battle_config.mail_system) + return 0; + + nullpo_retr(-1, sd); + + if (!message || !*message) { + clif_displaymessage(sd->fd,"You must specify a message number."); + return 0; + } + + if(strlen(command)==11) + mail_delete(sd,atoi(message)); + else + mail_read(sd,atoi(message)); + + return 0; +} + +int atcommand_sendmail( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + if(!battle_config.mail_system) + return 0; + + char name[24],text[80]; + + nullpo_retr(-1, sd); + + if (!message || !*message) { + clif_displaymessage(sd->fd,"You must specify a recipient and a message."); + return 0; + } + + if ((sscanf(message, "\"%[^\"]\" %79[^\n]", name, text) < 2) && + (sscanf(message, "%23s %79[^\n]", name, text) < 2)) { + clif_displaymessage(sd->fd,"You must specify a recipient and a message."); + return 0; + } + + if(strlen(command)==17) + mail_send(sd,name,text,1); + else + mail_send(sd,name,text,0); + + return 0; +} + +/*========================================== + * Refresh online command for SQL [Valaris] + * Will refresh and check online column of + * players and set correctly. + *------------------------------------------ + */ +int atcommand_refreshonline( + const int fd, struct map_session_data* sd, + const char* command, const char* message) +{ + nullpo_retr(-1, sd); + + char_online_check(); + + return 0; +} + +#endif /* end sql only */ diff --git a/src/map/atcommand.h b/src/map/atcommand.h new file mode 100644 index 0000000..06029a5 --- /dev/null +++ b/src/map/atcommand.h @@ -0,0 +1,245 @@ +// $Id: atcommand.h 148 2004-09-30 14:05:37Z MouseJstr $ +#ifndef _ATCOMMAND_H_ +#define _ATCOMMAND_H_ + +enum AtCommandType { + AtCommand_None = -1, + AtCommand_Broadcast = 0, + AtCommand_LocalBroadcast, + AtCommand_MapMove, + AtCommand_ResetState, + AtCommand_RuraP, + AtCommand_Rura, + AtCommand_Warp, + AtCommand_Where, + AtCommand_JumpTo, + AtCommand_Jump, + AtCommand_Who, + AtCommand_Who2, + AtCommand_Who3, + AtCommand_WhoMap, + AtCommand_WhoMap2, + AtCommand_WhoMap3, + AtCommand_WhoGM, + AtCommand_Save, + AtCommand_Load, + AtCommand_Speed, + AtCommand_Storage, + AtCommand_GuildStorage, + AtCommand_Option, + AtCommand_Hide, + AtCommand_JobChange, + AtCommand_JobChange2, + AtCommand_JobChange3, + AtCommand_Die, + AtCommand_Kill, + AtCommand_Alive, + AtCommand_Kami, + AtCommand_KamiB, + AtCommand_Heal, + AtCommand_Item, + AtCommand_Item2, + AtCommand_ItemReset, + AtCommand_ItemCheck, + AtCommand_BaseLevelUp, + AtCommand_JobLevelUp, + AtCommand_H, + AtCommand_Help, + AtCommand_GM, + AtCommand_PvPOff, + AtCommand_PvPOn, + AtCommand_GvGOff, + AtCommand_GvGOn, + AtCommand_Model, + AtCommand_Go, + AtCommand_Spawn, + AtCommand_Monster, + AtCommand_KillMonster, + AtCommand_KillMonster2, + AtCommand_Refine, + AtCommand_Produce, + AtCommand_Memo, + AtCommand_GAT, + AtCommand_Packet, + AtCommand_StatusPoint, + AtCommand_SkillPoint, + AtCommand_Zeny, + AtCommand_Param, + AtCommand_Strength, + AtCommand_Agility, + AtCommand_Vitality, + AtCommand_Intelligence, + AtCommand_Dexterity, + AtCommand_Luck, + AtCommand_GuildLevelUp, + AtCommand_MakeEgg, + AtCommand_PetFriendly, + AtCommand_PetHungry, + AtCommand_PetRename, + AtCommand_CharPetRename, // by Yor + AtCommand_Recall, + AtCommand_CharacterJob, + AtCommand_CharacterJob2, + AtCommand_CharacterJob3, + AtCommand_Revive, + AtCommand_CharacterStats, + AtCommand_CharacterStatsAll, + AtCommand_CharacterOption, + AtCommand_CharacterSave, + AtCommand_CharacterLoad, + AtCommand_Night, + AtCommand_Day, + AtCommand_Doom, + AtCommand_DoomMap, + AtCommand_Raise, + AtCommand_RaiseMap, + AtCommand_CharacterBaseLevel, + AtCommand_CharacterJobLevel, + AtCommand_Kick, + AtCommand_KickAll, + AtCommand_AllSkill, + AtCommand_QuestSkill, + AtCommand_CharQuestSkill, + AtCommand_LostSkill, + AtCommand_CharLostSkill, + AtCommand_SpiritBall, + AtCommand_Party, + AtCommand_Guild, + AtCommand_AgitStart, + AtCommand_AgitEnd, + AtCommand_MapExit, + AtCommand_IDSearch, + AtCommand_CharSkReset, + AtCommand_CharStReset, + AtCommand_CharReset, + //by chbrules + AtCommand_CharModel, + AtCommand_CharSKPoint, + AtCommand_CharSTPoint, + AtCommand_CharZeny, + AtCommand_RecallAll, + AtCommand_ReloadItemDB, + AtCommand_ReloadMobDB, + AtCommand_ReloadSkillDB, +#ifndef TXT_ONLY + AtCommand_Rehash, +#else /* TXT_ONLY */ + AtCommand_ReloadScript, +#endif /* TXT_ONLY */ + AtCommand_ReloadGMDB, + AtCommand_MapInfo, + AtCommand_Dye, + AtCommand_Hstyle, + AtCommand_Hcolor, + AtCommand_StatAll, + AtCommand_CharChangeSex, // by Yor + AtCommand_CharBlock, // by Yor + AtCommand_CharBan, // by Yor + AtCommand_CharUnBlock, // by Yor + AtCommand_CharUnBan, // by Yor + AtCommand_MountPeco, // by Valaris + AtCommand_CharMountPeco, // by Yor + AtCommand_GuildSpy, // [Syrus22] + AtCommand_PartySpy, // [Syrus22] + AtCommand_RepairAll, // [Valaris] + AtCommand_GuildRecall, // by Yor + AtCommand_PartyRecall, // by Yor +// AtCommand_Nuke, // [Valaris] + AtCommand_Enablenpc, + AtCommand_Disablenpc, + AtCommand_ServerTime, // by Yor + AtCommand_CharDelItem, // by Yor + AtCommand_Jail, // by Yor + AtCommand_UnJail, // by Yor + AtCommand_Disguise, // [Valaris] + AtCommand_UnDisguise, // by Yor + AtCommand_IgnoreList, // by Yor + AtCommand_CharIgnoreList, // by Yor + AtCommand_InAll, // by Yor + AtCommand_ExAll, // by Yor + AtCommand_CharDisguise, // Kalaspuff + AtCommand_CharUnDisguise, // Kalaspuff + AtCommand_EMail, // by Yor + AtCommand_Hatch, + AtCommand_Effect, // by Apple + AtCommand_Char_Item_List, // by Yor + AtCommand_Char_Storage_List, // by Yor + AtCommand_Char_Cart_List, // by Yor + AtCommand_AddWarp, // by MouseJstr + AtCommand_Follow, // by MouseJstr + AtCommand_SkillOn, // by MouseJstr + AtCommand_SkillOff, // by MouseJstr + AtCommand_Killer, // by MouseJstr + AtCommand_NpcMove, // by MouseJstr + AtCommand_Killable, // by MouseJstr + AtCommand_CharKillable, // by MouseJstr + AtCommand_Chareffect, // by MouseJstr + AtCommand_Chardye, // by MouseJstr + AtCommand_Charhairstyle, // by MouseJstr + AtCommand_Charhaircolor, // by MouseJstr + AtCommand_Dropall, // by MouseJstr + AtCommand_Chardropall, // by MouseJstr + AtCommand_Storeall, // by MouseJstr + AtCommand_Charstoreall, // by MouseJstr + AtCommand_Skillid, // by MouseJstr + AtCommand_Useskill, // by MouseJstr + AtCommand_Summon, + AtCommand_Rain, + AtCommand_Snow, + AtCommand_Sakura, + AtCommand_Fog, + AtCommand_Leaves, + AtCommand_AdjGmLvl, + AtCommand_AdjCmdLvl, + AtCommand_Trade, + AtCommand_UnMute, + + // SQL-only commands start +#ifndef TXT_ONLY + AtCommand_CheckMail, // [Valaris] + AtCommand_ListMail, // [Valaris] + AtCommand_ListNewMail, // [Valaris] + AtCommand_ReadMail, // [Valaris] + AtCommand_SendMail, // [Valaris] + AtCommand_DeleteMail, // [Valaris] + AtCommand_SendPriorityMail, // [Valaris] + AtCommand_Sound, // [Valaris] + AtCommand_RefreshOnline, // [Valaris] + // SQL-only commands end +#endif + + // end + AtCommand_Unknown, + AtCommand_MAX +}; + +typedef enum AtCommandType AtCommandType; + +typedef struct AtCommandInfo { + AtCommandType type; + const char* command; + int level; + int (*proc)(const int, struct map_session_data*, + const char* command, const char* message); +} AtCommandInfo; + +AtCommandType +is_atcommand(const int fd, struct map_session_data* sd, const char* message, int gmlvl); + +AtCommandType atcommand( + const int level, const char* message, AtCommandInfo* info); +int get_atcommand_level(const AtCommandType type); + +char * msg_txt(int msg_number); // [Yor] + +int atcommand_item(const int fd, struct map_session_data* sd,const char* command, const char* message); // [Valaris] +int atcommand_rura(const int fd, struct map_session_data* sd,const char* command, const char* message); // [Yor] +int atcommand_spawn(const int fd, struct map_session_data* sd, const char* command, const char* message); // [Valaris] +int atcommand_jumpto(const int fd, struct map_session_data* sd, const char* command, const char* message); // [Yor] +int atcommand_recall(const int fd, struct map_session_data* sd, const char* command, const char* message); // [Yor] + +int atcommand_config_read(const char *cfgName); +int msg_config_read(const char *cfgName); + +#endif + diff --git a/src/map/battle.c b/src/map/battle.c new file mode 100644 index 0000000..7a6dd47 --- /dev/null +++ b/src/map/battle.c @@ -0,0 +1,5374 @@ +// $Id: battle.c,v 1.10 2004/09/29 21:08:17 Akitasha Exp $ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <math.h> + +#include "battle.h" + +#include "timer.h" +#include "nullpo.h" +#include "malloc.h" + +#include "map.h" +#include "pc.h" +#include "skill.h" +#include "mob.h" +#include "itemdb.h" +#include "clif.h" +#include "pet.h" +#include "guild.h" + +#ifdef MEMWATCH +#include "memwatch.h" +#endif + +int attr_fix_table[4][10][10]; + +struct Battle_Config battle_config; + +/*========================================== + * 二点間の距離を返す + * 戻りは整数で0以上 + *------------------------------------------ + */ +static int distance(int x0,int y0,int x1,int y1) +{ + int dx,dy; + + dx=abs(x0-x1); + dy=abs(y0-y1); + return dx>dy ? dx : dy; +} + +/*========================================== + * 自分をロックしている対象の数を返す(汎用) + * 戻りは整数で0以上 + *------------------------------------------ + */ +int battle_counttargeted(struct block_list *bl,struct block_list *src,int target_lv) +{ + nullpo_retr(0, bl); + if(bl->type == BL_PC) + return pc_counttargeted((struct map_session_data *)bl,src,target_lv); + else if(bl->type == BL_MOB) + return mob_counttargeted((struct mob_data *)bl,src,target_lv); + return 0; +} +/*========================================== + * 対象のClassを返す(汎用) + * 戻りは整数で0以上 + *------------------------------------------ + */ +int battle_get_class(struct block_list *bl) +{ + nullpo_retr(0, bl); + if(bl->type==BL_MOB && (struct mob_data *)bl) + return ((struct mob_data *)bl)->class; + else if(bl->type==BL_PC && (struct map_session_data *)bl) + return ((struct map_session_data *)bl)->status.class; + else if(bl->type==BL_PET && (struct pet_data *)bl) + return ((struct pet_data *)bl)->class; + else + return 0; +} +/*========================================== + * 対象の方向を返す(汎用) + * 戻りは整数で0以上 + *------------------------------------------ + */ +int battle_get_dir(struct block_list *bl) +{ + nullpo_retr(0, bl); + if(bl->type==BL_MOB && (struct mob_data *)bl) + return ((struct mob_data *)bl)->dir; + else if(bl->type==BL_PC && (struct map_session_data *)bl) + return ((struct map_session_data *)bl)->dir; + else if(bl->type==BL_PET && (struct pet_data *)bl) + return ((struct pet_data *)bl)->dir; + else + return 0; +} +/*========================================== + * 対象のレベルを返す(汎用) + * 戻りは整数で0以上 + *------------------------------------------ + */ +int battle_get_lv(struct block_list *bl) +{ + nullpo_retr(0, bl); + if(bl->type==BL_MOB && (struct mob_data *)bl) + return mob_db[((struct mob_data *)bl)->class].lv; + else if(bl->type==BL_PC && (struct map_session_data *)bl) + return ((struct map_session_data *)bl)->status.base_level; + else if(bl->type==BL_PET && (struct pet_data *)bl) + return ((struct pet_data *)bl)->msd->pet.level; + else + return 0; +} +/*========================================== + * 対象の射程を返す(汎用) + * 戻りは整数で0以上 + *------------------------------------------ + */ +int battle_get_range(struct block_list *bl) +{ + nullpo_retr(0, bl); + if(bl->type==BL_MOB && (struct mob_data *)bl) + return mob_db[((struct mob_data *)bl)->class].range; + else if(bl->type==BL_PC && (struct map_session_data *)bl) + return ((struct map_session_data *)bl)->attackrange; + else if(bl->type==BL_PET && (struct pet_data *)bl) + return mob_db[((struct pet_data *)bl)->class].range; + else + return 0; +} +/*========================================== + * 対象のHPを返す(汎用) + * 戻りは整数で0以上 + *------------------------------------------ + */ +int battle_get_hp(struct block_list *bl) +{ + nullpo_retr(1, bl); + if(bl->type==BL_MOB && (struct mob_data *)bl) + return ((struct mob_data *)bl)->hp; + else if(bl->type==BL_PC && (struct map_session_data *)bl) + return ((struct map_session_data *)bl)->status.hp; + else + return 1; +} +/*========================================== + * 対象のMHPを返す(汎用) + * 戻りは整数で0以上 + *------------------------------------------ + */ +int battle_get_max_hp(struct block_list *bl) +{ + nullpo_retr(1, bl); + if(bl->type==BL_PC && ((struct map_session_data *)bl)) + return ((struct map_session_data *)bl)->status.max_hp; + else { + struct status_change *sc_data=battle_get_sc_data(bl); + int max_hp=1; + if(bl->type==BL_MOB && ((struct mob_data*)bl)) { + max_hp = mob_db[((struct mob_data*)bl)->class].max_hp; + if(mob_db[((struct mob_data*)bl)->class].mexp > 0) { + if(battle_config.mvp_hp_rate != 100) + max_hp = (max_hp * battle_config.mvp_hp_rate)/100; + } + else { + if(battle_config.monster_hp_rate != 100) + max_hp = (max_hp * battle_config.monster_hp_rate)/100; + } + } + else if(bl->type==BL_PET && ((struct pet_data*)bl)) { + max_hp = mob_db[((struct pet_data*)bl)->class].max_hp; + if(mob_db[((struct pet_data*)bl)->class].mexp > 0) { + if(battle_config.mvp_hp_rate != 100) + max_hp = (max_hp * battle_config.mvp_hp_rate)/100; + } + else { + if(battle_config.monster_hp_rate != 100) + max_hp = (max_hp * battle_config.monster_hp_rate)/100; + } + } + if(sc_data) { + if(sc_data[SC_APPLEIDUN].timer!=-1) + max_hp += ((5+sc_data[SC_APPLEIDUN].val1*2+((sc_data[SC_APPLEIDUN].val2+1)>>1) + +sc_data[SC_APPLEIDUN].val3/10) * max_hp)/100; + } + if(max_hp < 1) max_hp = 1; + return max_hp; + } + return 1; +} +/*========================================== + * 対象のStrを返す(汎用) + * 戻りは整数で0以上 + *------------------------------------------ + */ +int battle_get_str(struct block_list *bl) +{ + int str=0; + struct status_change *sc_data; + + nullpo_retr(0, bl); + sc_data=battle_get_sc_data(bl); + if(bl->type==BL_MOB && ((struct mob_data *)bl)) + str = mob_db[((struct mob_data *)bl)->class].str; + else if(bl->type==BL_PC && ((struct map_session_data *)bl)) + return ((struct map_session_data *)bl)->paramc[0]; + else if(bl->type==BL_PET && ((struct pet_data *)bl)) + str = mob_db[((struct pet_data *)bl)->class].str; + + if(sc_data) { + if(sc_data[SC_LOUD].timer!=-1 && sc_data[SC_QUAGMIRE].timer == -1 && bl->type != BL_PC) + str += 4; + if( sc_data[SC_BLESSING].timer != -1 && bl->type != BL_PC){ // ブレッシング + int race=battle_get_race(bl); + if(battle_check_undead(race,battle_get_elem_type(bl)) || race==6 ) str >>= 1; // 悪 魔/不死 + else str += sc_data[SC_BLESSING].val1; // その他 + } + if(sc_data[SC_TRUESIGHT].timer!=-1 && bl->type != BL_PC) // トゥルーサイト + str += 5; + } + if(str < 0) str = 0; + return str; +} +/*========================================== + * 対象のAgiを返す(汎用) + * 戻りは整数で0以上 + *------------------------------------------ + */ + +int battle_get_agi(struct block_list *bl) +{ + int agi=0; + struct status_change *sc_data; + + nullpo_retr(0, bl); + sc_data=battle_get_sc_data(bl); + if(bl->type==BL_MOB && (struct mob_data *)bl) + agi=mob_db[((struct mob_data *)bl)->class].agi; + else if(bl->type==BL_PC && (struct map_session_data *)bl) + agi=((struct map_session_data *)bl)->paramc[1]; + else if(bl->type==BL_PET && (struct pet_data *)bl) + agi=mob_db[((struct pet_data *)bl)->class].agi; + + if(sc_data) { + if( sc_data[SC_INCREASEAGI].timer!=-1 && sc_data[SC_QUAGMIRE].timer == -1 && sc_data[SC_DONTFORGETME].timer == -1 && + bl->type != BL_PC) // 速度増加(PCはpc.cで) + agi += 2+sc_data[SC_INCREASEAGI].val1; + + if(sc_data[SC_CONCENTRATE].timer!=-1 && sc_data[SC_QUAGMIRE].timer == -1 && bl->type != BL_PC) + agi += agi*(2+sc_data[SC_CONCENTRATE].val1)/100; + + if(sc_data[SC_DECREASEAGI].timer!=-1) // 速度減少 + agi -= 2+sc_data[SC_DECREASEAGI].val1; + + if(sc_data[SC_QUAGMIRE].timer!=-1 ) // クァグマイア + agi >>= 1; + if(sc_data[SC_TRUESIGHT].timer!=-1 && bl->type != BL_PC) // トゥルーサイト + agi += 5; + } + if(agi < 0) agi = 0; + return agi; +} +/*========================================== + * 対象のVitを返す(汎用) + * 戻りは整数で0以上 + *------------------------------------------ + */ +int battle_get_vit(struct block_list *bl) +{ + int vit=0; + struct status_change *sc_data; + + nullpo_retr(0, bl); + sc_data=battle_get_sc_data(bl); + if(bl->type==BL_MOB && (struct mob_data *)bl) + vit=mob_db[((struct mob_data *)bl)->class].vit; + else if(bl->type==BL_PC && (struct map_session_data *)bl) + vit=((struct map_session_data *)bl)->paramc[2]; + else if(bl->type==BL_PET && (struct pet_data *)bl) + vit=mob_db[((struct pet_data *)bl)->class].vit; + if(sc_data) { + if(sc_data[SC_STRIPARMOR].timer != -1 && bl->type!=BL_PC) + vit = vit*60/100; + if(sc_data[SC_TRUESIGHT].timer!=-1 && bl->type != BL_PC) // トゥルーサイト + vit += 5; + } + + if(vit < 0) vit = 0; + return vit; +} +/*========================================== + * 対象のIntを返す(汎用) + * 戻りは整数で0以上 + *------------------------------------------ + */ +int battle_get_int(struct block_list *bl) +{ + int int_=0; + struct status_change *sc_data; + + nullpo_retr(0, bl); + sc_data=battle_get_sc_data(bl); + if(bl->type==BL_MOB && (struct mob_data *)bl) + int_=mob_db[((struct mob_data *)bl)->class].int_; + else if(bl->type==BL_PC && (struct map_session_data *)bl) + int_=((struct map_session_data *)bl)->paramc[3]; + else if(bl->type==BL_PET && (struct pet_data *)bl) + int_=mob_db[((struct pet_data *)bl)->class].int_; + + if(sc_data) { + if( sc_data[SC_BLESSING].timer != -1 && bl->type != BL_PC){ // ブレッシング + int race=battle_get_race(bl); + if(battle_check_undead(race,battle_get_elem_type(bl)) || race==6 ) int_ >>= 1; // 悪 魔/不死 + else int_ += sc_data[SC_BLESSING].val1; // その他 + } + if( sc_data[SC_STRIPHELM].timer != -1 && bl->type != BL_PC) + int_ = int_*60/100; + if(sc_data[SC_TRUESIGHT].timer!=-1 && bl->type != BL_PC) // トゥルーサイト + int_ += 5; + } + if(int_ < 0) int_ = 0; + return int_; +} +/*========================================== + * 対象のDexを返す(汎用) + * 戻りは整数で0以上 + *------------------------------------------ + */ +int battle_get_dex(struct block_list *bl) +{ + int dex=0; + struct status_change *sc_data; + + nullpo_retr(0, bl); + sc_data=battle_get_sc_data(bl); + if(bl->type==BL_MOB && (struct mob_data *)bl) + dex=mob_db[((struct mob_data *)bl)->class].dex; + else if(bl->type==BL_PC && (struct map_session_data *)bl) + dex=((struct map_session_data *)bl)->paramc[4]; + else if(bl->type==BL_PET && (struct pet_data *)bl) + dex=mob_db[((struct pet_data *)bl)->class].dex; + + if(sc_data) { + if(sc_data[SC_CONCENTRATE].timer!=-1 && sc_data[SC_QUAGMIRE].timer == -1 && bl->type != BL_PC) + dex += dex*(2+sc_data[SC_CONCENTRATE].val1)/100; + + if( sc_data[SC_BLESSING].timer != -1 && bl->type != BL_PC){ // ブレッシング + int race=battle_get_race(bl); + if(battle_check_undead(race,battle_get_elem_type(bl)) || race==6 ) dex >>= 1; // 悪 魔/不死 + else dex += sc_data[SC_BLESSING].val1; // その他 + } + + if(sc_data[SC_QUAGMIRE].timer!=-1 ) // クァグマイア + dex >>= 1; + if(sc_data[SC_TRUESIGHT].timer!=-1 && bl->type != BL_PC) // トゥルーサイト + dex += 5; + } + if(dex < 0) dex = 0; + return dex; +} +/*========================================== + * 対象のLukを返す(汎用) + * 戻りは整数で0以上 + *------------------------------------------ + */ +int battle_get_luk(struct block_list *bl) +{ + int luk=0; + struct status_change *sc_data; + + nullpo_retr(0, bl); + sc_data=battle_get_sc_data(bl); + if(bl->type==BL_MOB && (struct mob_data *)bl) + luk=mob_db[((struct mob_data *)bl)->class].luk; + else if(bl->type==BL_PC && (struct map_session_data *)bl) + luk=((struct map_session_data *)bl)->paramc[5]; + else if(bl->type==BL_PET && (struct pet_data *)bl) + luk=mob_db[((struct pet_data *)bl)->class].luk; + + if(sc_data) { + if(sc_data[SC_GLORIA].timer!=-1 && bl->type != BL_PC) // グロリア(PCはpc.cで) + luk += 30; + if(sc_data[SC_CURSE].timer!=-1 ) // 呪い + luk=0; + if(sc_data[SC_TRUESIGHT].timer!=-1 && bl->type != BL_PC) // トゥルーサイト + luk += 5; + } + if(luk < 0) luk = 0; + return luk; +} + +/*========================================== + * 対象のFleeを返す(汎用) + * 戻りは整数で1以上 + *------------------------------------------ + */ +int battle_get_flee(struct block_list *bl) +{ + int flee=1; + struct status_change *sc_data; + + nullpo_retr(1, bl); + sc_data=battle_get_sc_data(bl); + if(bl->type==BL_PC && (struct map_session_data *)bl) + flee=((struct map_session_data *)bl)->flee; + else + flee=battle_get_agi(bl) + battle_get_lv(bl); + + if(sc_data) { + if(sc_data[SC_WHISTLE].timer!=-1 && bl->type != BL_PC) + flee += flee*(sc_data[SC_WHISTLE].val1+sc_data[SC_WHISTLE].val2 + +(sc_data[SC_WHISTLE].val3>>16))/100; + if(sc_data[SC_BLIND].timer!=-1 && bl->type != BL_PC) + flee -= flee*25/100; + if(sc_data[SC_WINDWALK].timer!=-1 && bl->type != BL_PC) // ウィンドウォーク + flee += flee*(sc_data[SC_WINDWALK].val2)/100; + if(sc_data[SC_SPIDERWEB].timer!=-1 && bl->type != BL_PC) //スパイダーウェブ + flee -= flee*50/100; + } + if(flee < 1) flee = 1; + return flee; +} +/*========================================== + * 対象のHitを返す(汎用) + * 戻りは整数で1以上 + *------------------------------------------ + */ +int battle_get_hit(struct block_list *bl) +{ + int hit=1; + struct status_change *sc_data; + + nullpo_retr(1, bl); + sc_data=battle_get_sc_data(bl); + if(bl->type==BL_PC && (struct map_session_data *)bl) + hit=((struct map_session_data *)bl)->hit; + else + hit=battle_get_dex(bl) + battle_get_lv(bl); + + if(sc_data) { + if(sc_data[SC_HUMMING].timer!=-1 && bl->type != BL_PC) // + hit += hit*(sc_data[SC_HUMMING].val1*2+sc_data[SC_HUMMING].val2 + +sc_data[SC_HUMMING].val3)/100; + if(sc_data[SC_BLIND].timer!=-1 && bl->type != BL_PC) // 呪い + hit -= hit*25/100; + if(sc_data[SC_TRUESIGHT].timer!=-1 && bl->type != BL_PC) // トゥルーサイト + hit += 3*(sc_data[SC_TRUESIGHT].val1); + if(sc_data[SC_CONCENTRATION].timer!=-1 && bl->type != BL_PC) //コンセントレーション + hit += (hit*(10*(sc_data[SC_CONCENTRATION].val1)))/100; + } + if(hit < 1) hit = 1; + return hit; +} +/*========================================== + * 対象の完全回避を返す(汎用) + * 戻りは整数で1以上 + *------------------------------------------ + */ +int battle_get_flee2(struct block_list *bl) +{ + int flee2=1; + struct status_change *sc_data; + + nullpo_retr(1, bl); + sc_data=battle_get_sc_data(bl); + if(bl->type==BL_PC && (struct map_session_data *)bl){ + flee2 = battle_get_luk(bl) + 10; + flee2 += ((struct map_session_data *)bl)->flee2 - (((struct map_session_data *)bl)->paramc[5] + 10); + } + else + flee2=battle_get_luk(bl)+1; + + if(sc_data) { + if(sc_data[SC_WHISTLE].timer!=-1 && bl->type != BL_PC) + flee2 += (sc_data[SC_WHISTLE].val1+sc_data[SC_WHISTLE].val2 + +(sc_data[SC_WHISTLE].val3&0xffff))*10; + } + if(flee2 < 1) flee2 = 1; + return flee2; +} +/*========================================== + * 対象のクリティカルを返す(汎用) + * 戻りは整数で1以上 + *------------------------------------------ + */ +int battle_get_critical(struct block_list *bl) +{ + int critical=1; + struct status_change *sc_data; + + nullpo_retr(1, bl); + sc_data=battle_get_sc_data(bl); + if(bl->type==BL_PC && (struct map_session_data *)bl){ + critical = battle_get_luk(bl)*3 + 10; + critical += ((struct map_session_data *)bl)->critical - ((((struct map_session_data *)bl)->paramc[5]*3) + 10); + } + else + critical=battle_get_luk(bl)*3 + 1; + + if(sc_data) { + if(sc_data[SC_FORTUNE].timer!=-1 && bl->type != BL_PC) + critical += (10+sc_data[SC_FORTUNE].val1+sc_data[SC_FORTUNE].val2 + +sc_data[SC_FORTUNE].val3)*10; + if(sc_data[SC_EXPLOSIONSPIRITS].timer!=-1 && bl->type != BL_PC) + critical += sc_data[SC_EXPLOSIONSPIRITS].val2; + if(sc_data[SC_TRUESIGHT].timer!=-1 && bl->type != BL_PC) //トゥルーサイト + critical += critical*sc_data[SC_TRUESIGHT].val1/100; + } + if(critical < 1) critical = 1; + return critical; +} +/*========================================== + * base_atkの取得 + * 戻りは整数で1以上 + *------------------------------------------ + */ +int battle_get_baseatk(struct block_list *bl) +{ + struct status_change *sc_data; + int batk=1; + + nullpo_retr(1, bl); + sc_data=battle_get_sc_data(bl); + if(bl->type==BL_PC && (struct map_session_data *)bl) + batk = ((struct map_session_data *)bl)->base_atk; //設定されているbase_atk + else { //それ以外なら + int str,dstr; + str = battle_get_str(bl); //STR + dstr = str/10; + batk = dstr*dstr + str; //base_atkを計算する + } + if(sc_data) { //状態異常あり + if(sc_data[SC_PROVOKE].timer!=-1 && bl->type != BL_PC) //PCでプロボック(SM_PROVOKE)状態 + batk = batk*(100+2*sc_data[SC_PROVOKE].val1)/100; //base_atk増加 + if(sc_data[SC_CURSE].timer!=-1 ) //呪われていたら + batk -= batk*25/100; //base_atkが25%減少 + if(sc_data[SC_CONCENTRATION].timer!=-1 && bl->type != BL_PC) //コンセントレーション + batk += batk*(5*sc_data[SC_CONCENTRATION].val1)/100; + } + if(batk < 1) batk = 1; //base_atkは最低でも1 + return batk; +} +/*========================================== + * 対象のAtkを返す(汎用) + * 戻りは整数で0以上 + *------------------------------------------ + */ +int battle_get_atk(struct block_list *bl) +{ + struct status_change *sc_data; + int atk=0; + + nullpo_retr(0, bl); + sc_data=battle_get_sc_data(bl); + if(bl->type==BL_PC && (struct map_session_data *)bl) + atk = ((struct map_session_data*)bl)->watk; + else if(bl->type==BL_MOB && (struct mob_data *)bl) + atk = mob_db[((struct mob_data*)bl)->class].atk1; + else if(bl->type==BL_PET && (struct pet_data *)bl) + atk = mob_db[((struct pet_data*)bl)->class].atk1; + + if(sc_data) { + if(sc_data[SC_PROVOKE].timer!=-1 && bl->type != BL_PC) + atk = atk*(100+2*sc_data[SC_PROVOKE].val1)/100; + if(sc_data[SC_CURSE].timer!=-1 ) + atk -= atk*25/100; + if(sc_data[SC_CONCENTRATION].timer!=-1 && bl->type != BL_PC) //コンセントレーション + atk += atk*(5*sc_data[SC_CONCENTRATION].val1)/100; + } + if(atk < 0) atk = 0; + return atk; +} +/*========================================== + * 対象の左手Atkを返す(汎用) + * 戻りは整数で0以上 + *------------------------------------------ + */ +int battle_get_atk_(struct block_list *bl) +{ + nullpo_retr(0, bl); + if(bl->type==BL_PC && (struct map_session_data *)bl){ + int atk=((struct map_session_data*)bl)->watk_; + + if(((struct map_session_data *)bl)->sc_data[SC_CURSE].timer!=-1 ) + atk -= atk*25/100; + return atk; + } + else + return 0; +} +/*========================================== + * 対象のAtk2を返す(汎用) + * 戻りは整数で0以上 + *------------------------------------------ + */ +int battle_get_atk2(struct block_list *bl) +{ + nullpo_retr(0, bl); + if(bl->type==BL_PC && (struct map_session_data *)bl) + return ((struct map_session_data*)bl)->watk2; + else { + struct status_change *sc_data=battle_get_sc_data(bl); + int atk2=0; + if(bl->type==BL_MOB && (struct mob_data *)bl) + atk2 = mob_db[((struct mob_data*)bl)->class].atk2; + else if(bl->type==BL_PET && (struct pet_data *)bl) + atk2 = mob_db[((struct pet_data*)bl)->class].atk2; + if(sc_data) { + if( sc_data[SC_IMPOSITIO].timer!=-1) + atk2 += sc_data[SC_IMPOSITIO].val1*5; + if( sc_data[SC_PROVOKE].timer!=-1 ) + atk2 = atk2*(100+2*sc_data[SC_PROVOKE].val1)/100; + if( sc_data[SC_CURSE].timer!=-1 ) + atk2 -= atk2*25/100; + if(sc_data[SC_DRUMBATTLE].timer!=-1) + atk2 += sc_data[SC_DRUMBATTLE].val2; + if(sc_data[SC_NIBELUNGEN].timer!=-1 && (battle_get_element(bl)/10) >= 8 ) + atk2 += sc_data[SC_NIBELUNGEN].val2; + if(sc_data[SC_STRIPWEAPON].timer!=-1) + atk2 = atk2*90/100; + if(sc_data[SC_CONCENTRATION].timer!=-1) //コンセントレーション + atk2 += atk2*(5*sc_data[SC_CONCENTRATION].val1)/100; + } + if(atk2 < 0) atk2 = 0; + return atk2; + } + return 0; +} +/*========================================== + * 対象の左手Atk2を返す(汎用) + * 戻りは整数で0以上 + *------------------------------------------ + */ +int battle_get_atk_2(struct block_list *bl) +{ + nullpo_retr(0, bl); + if(bl->type==BL_PC && (struct map_session_data *)bl) + return ((struct map_session_data*)bl)->watk_2; + else + return 0; +} +/*========================================== + * 対象のMAtk1を返す(汎用) + * 戻りは整数で0以上 + *------------------------------------------ + */ +int battle_get_matk1(struct block_list *bl) +{ + struct status_change *sc_data; + nullpo_retr(0, bl); + sc_data=battle_get_sc_data(bl); + if(bl->type==BL_MOB){ + int matk,int_=battle_get_int(bl); + matk = int_+(int_/5)*(int_/5); + + if(sc_data) + if(sc_data[SC_MINDBREAKER].timer!=-1 && bl->type != BL_PC) + matk = matk*(100+2*sc_data[SC_MINDBREAKER].val1)/100; + return matk; + } + else if(bl->type==BL_PC && (struct map_session_data *)bl) + return ((struct map_session_data *)bl)->matk1; + else if(bl->type==BL_PET){ + int matk,int_=battle_get_int(bl); + matk = int_+(int_/5)*(int_/5); + + if(sc_data) + if(sc_data[SC_MINDBREAKER].timer!=-1 && bl->type != BL_PC) + matk = matk*(100+2*sc_data[SC_MINDBREAKER].val1)/100; + return matk; + } + else + return 0; +} +/*========================================== + * 対象のMAtk2を返す(汎用) + * 戻りは整数で0以上 + *------------------------------------------ + */ +int battle_get_matk2(struct block_list *bl) +{ + struct status_change *sc_data=battle_get_sc_data(bl); + nullpo_retr(0, bl); + if(bl->type==BL_MOB){ + int matk,int_=battle_get_int(bl); + matk = int_+(int_/7)*(int_/7); + + if(sc_data) + if(sc_data[SC_MINDBREAKER].timer!=-1 && bl->type != BL_PC) + matk = matk*(100+2*sc_data[SC_MINDBREAKER].val1)/100; + return matk; + } + else if(bl->type==BL_PC && (struct map_session_data *)bl) + return ((struct map_session_data *)bl)->matk2; + else if(bl->type==BL_PET){ + int matk,int_=battle_get_int(bl); + matk = int_+(int_/7)*(int_/7); + if(sc_data) + if(sc_data[SC_MINDBREAKER].timer!=-1 && bl->type != BL_PC) + matk = matk*(100+2*sc_data[SC_MINDBREAKER].val1)/100; + return matk; + } + else + return 0; +} +/*========================================== + * 対象のDefを返す(汎用) + * 戻りは整数で0以上 + *------------------------------------------ + */ +int battle_get_def(struct block_list *bl) +{ + struct status_change *sc_data; + int def=0,skilltimer=-1,skillid=0; + + nullpo_retr(0, bl); + sc_data=battle_get_sc_data(bl); + if(bl->type==BL_PC && (struct map_session_data *)bl){ + def = ((struct map_session_data *)bl)->def; + skilltimer = ((struct map_session_data *)bl)->skilltimer; + skillid = ((struct map_session_data *)bl)->skillid; + } + else if(bl->type==BL_MOB && (struct mob_data *)bl) { + def = mob_db[((struct mob_data *)bl)->class].def; + skilltimer = ((struct mob_data *)bl)->skilltimer; + skillid = ((struct mob_data *)bl)->skillid; + } + else if(bl->type==BL_PET && (struct pet_data *)bl) + def = mob_db[((struct pet_data *)bl)->class].def; + + if(def < 1000000) { + if(sc_data) { + //キーピング時はDEF100 + if( sc_data[SC_KEEPING].timer!=-1) + def = 100; + //プロボック時は減算 + if( sc_data[SC_PROVOKE].timer!=-1 && bl->type != BL_PC) + def = (def*(100 - 6*sc_data[SC_PROVOKE].val1)+50)/100; + //戦太鼓の響き時は加算 + if( sc_data[SC_DRUMBATTLE].timer!=-1 && bl->type != BL_PC) + def += sc_data[SC_DRUMBATTLE].val3; + //毒にかかっている時は減算 + if(sc_data[SC_POISON].timer!=-1 && bl->type != BL_PC) + def = def*75/100; + //ストリップシールド時は減算 + if(sc_data[SC_STRIPSHIELD].timer!=-1 && bl->type != BL_PC) + def = def*85/100; + //シグナムクルシス時は減算 + if(sc_data[SC_SIGNUMCRUCIS].timer!=-1 && bl->type != BL_PC) + def = def * (100 - sc_data[SC_SIGNUMCRUCIS].val2)/100; + //永遠の混沌時はDEF0になる + if(sc_data[SC_ETERNALCHAOS].timer!=-1 && bl->type != BL_PC) + def = 0; + //凍結、石化時は右シフト + if(sc_data[SC_FREEZE].timer != -1 || (sc_data[SC_STONE].timer != -1 && sc_data[SC_STONE].val2 == 0)) + def >>= 1; + //コンセントレーション時は減算 + if( sc_data[SC_CONCENTRATION].timer!=-1 && bl->type != BL_PC) + def = (def*(100 - 5*sc_data[SC_CONCENTRATION].val1))/100; + } + //詠唱中は詠唱時減算率に基づいて減算 + if(skilltimer != -1) { + int def_rate = skill_get_castdef(skillid); + if(def_rate != 0) + def = (def * (100 - def_rate))/100; + } + } + if(def < 0) def = 0; + return def; +} +/*========================================== + * 対象のMDefを返す(汎用) + * 戻りは整数で0以上 + *------------------------------------------ + */ +int battle_get_mdef(struct block_list *bl) +{ + struct status_change *sc_data; + int mdef=0; + + nullpo_retr(0, bl); + sc_data=battle_get_sc_data(bl); + if(bl->type==BL_PC && (struct map_session_data *)bl) + mdef = ((struct map_session_data *)bl)->mdef; + else if(bl->type==BL_MOB && (struct mob_data *)bl) + mdef = mob_db[((struct mob_data *)bl)->class].mdef; + else if(bl->type==BL_PET && (struct pet_data *)bl) + mdef = mob_db[((struct pet_data *)bl)->class].mdef; + + if(mdef < 1000000) { + if(sc_data) { + //バリアー状態時はMDEF100 + if(sc_data[SC_BARRIER].timer != -1) + mdef = 100; + //凍結、石化時は1.25倍 + if(sc_data[SC_FREEZE].timer != -1 || (sc_data[SC_STONE].timer != -1 && sc_data[SC_STONE].val2 == 0)) + mdef = mdef*125/100; + if( sc_data[SC_MINDBREAKER].timer!=-1 && bl->type != BL_PC) + mdef -= (mdef*6*sc_data[SC_MINDBREAKER].val1)/100; + } + } + if(mdef < 0) mdef = 0; + return mdef; +} +/*========================================== + * 対象のDef2を返す(汎用) + * 戻りは整数で1以上 + *------------------------------------------ + */ +int battle_get_def2(struct block_list *bl) +{ + struct status_change *sc_data; + int def2=1; + + nullpo_retr(1, bl); + sc_data=battle_get_sc_data(bl); + if(bl->type==BL_PC) + def2 = ((struct map_session_data *)bl)->def2; + else if(bl->type==BL_MOB) + def2 = mob_db[((struct mob_data *)bl)->class].vit; + else if(bl->type==BL_PET) + def2 = mob_db[((struct pet_data *)bl)->class].vit; + + if(sc_data) { + if( sc_data[SC_ANGELUS].timer!=-1 && bl->type != BL_PC) + def2 = def2*(110+5*sc_data[SC_ANGELUS].val1)/100; + if( sc_data[SC_PROVOKE].timer!=-1 && bl->type != BL_PC) + def2 = (def2*(100 - 6*sc_data[SC_PROVOKE].val1)+50)/100; + if(sc_data[SC_POISON].timer!=-1 && bl->type != BL_PC) + def2 = def2*75/100; + //コンセントレーション時は減算 + if( sc_data[SC_CONCENTRATION].timer!=-1 && bl->type != BL_PC) + def2 = def2*(100 - 5*sc_data[SC_CONCENTRATION].val1)/100; + } + if(def2 < 1) def2 = 1; + return def2; +} +/*========================================== + * 対象のMDef2を返す(汎用) + * 戻りは整数で0以上 + *------------------------------------------ + */ +int battle_get_mdef2(struct block_list *bl) +{ + int mdef2=0; + struct status_change *sc_data=battle_get_sc_data(bl); + + nullpo_retr(0, bl); + if(bl->type==BL_MOB) + mdef2 = mob_db[((struct mob_data *)bl)->class].int_ + (mob_db[((struct mob_data *)bl)->class].vit>>1); + else if(bl->type==BL_PC) + mdef2 = ((struct map_session_data *)bl)->mdef2 + (((struct map_session_data *)bl)->paramc[2]>>1); + else if(bl->type==BL_PET) + mdef2 = mob_db[((struct pet_data *)bl)->class].int_ + (mob_db[((struct pet_data *)bl)->class].vit>>1); + if(sc_data) { + if( sc_data[SC_MINDBREAKER].timer!=-1 && bl->type != BL_PC) + mdef2 -= (mdef2*6*sc_data[SC_MINDBREAKER].val1)/100; + } + if(mdef2 < 0) mdef2 = 0; + return mdef2; +} +/*========================================== + * 対象のSpeed(移動速度)を返す(汎用) + * 戻りは整数で1以上 + * Speedは小さいほうが移動速度が速い + *------------------------------------------ + */ +int battle_get_speed(struct block_list *bl) +{ + nullpo_retr(1000, bl); + if(bl->type==BL_PC && (struct map_session_data *)bl) + return ((struct map_session_data *)bl)->speed; + else { + struct status_change *sc_data=battle_get_sc_data(bl); + int speed = 1000; + if(bl->type==BL_MOB && (struct mob_data *)bl) +// speed = mob_db[((struct mob_data *)bl)->class].speed; + speed = ((struct mob_data *)bl)->speed; + else if(bl->type==BL_PET && (struct pet_data *)bl) + speed = ((struct pet_data *)bl)->msd->petDB->speed; + + if(sc_data) { + //速度増加時は25%減算 + if(sc_data[SC_INCREASEAGI].timer!=-1 && sc_data[SC_DONTFORGETME].timer == -1) + speed -= speed*25/100; + //速度減少時は25%加算 + if(sc_data[SC_DECREASEAGI].timer!=-1) + speed = speed*125/100; + //クァグマイア時は50%加算 + if(sc_data[SC_QUAGMIRE].timer!=-1) + speed = speed*3/2; + //私を忘れないで…時は加算 + if(sc_data[SC_DONTFORGETME].timer!=-1) + speed = speed*(100+sc_data[SC_DONTFORGETME].val1*2 + sc_data[SC_DONTFORGETME].val2 + (sc_data[SC_DONTFORGETME].val3&0xffff))/100; + //金剛時は25%加算 + if(sc_data[SC_STEELBODY].timer!=-1) + speed = speed*125/100; + //ディフェンダー時は加算 + if(sc_data[SC_DEFENDER].timer!=-1) + speed = (speed * (155 - sc_data[SC_DEFENDER].val1*5)) / 100; + //踊り状態は4倍遅い + if(sc_data[SC_DANCING].timer!=-1 ) + speed*=4; + //呪い時は450加算 + if(sc_data[SC_CURSE].timer!=-1) + speed = speed + 450; + //ウィンドウォーク時はLv*2%減算 + if(sc_data[SC_WINDWALK].timer!=-1) + speed -= (speed*(sc_data[SC_WINDWALK].val1*2))/100; + } + if(speed < 1) speed = 1; + return speed; + } + + return 1000; +} +/*========================================== + * 対象のaDelay(攻撃時ディレイ)を返す(汎用) + * aDelayは小さいほうが攻撃速度が速い + *------------------------------------------ + */ +int battle_get_adelay(struct block_list *bl) +{ + nullpo_retr(4000, bl); + if(bl->type==BL_PC && (struct map_session_data *)bl) + return (((struct map_session_data *)bl)->aspd<<1); + else { + struct status_change *sc_data=battle_get_sc_data(bl); + int adelay=4000,aspd_rate = 100,i; + if(bl->type==BL_MOB && (struct mob_data *)bl) + adelay = mob_db[((struct mob_data *)bl)->class].adelay; + else if(bl->type==BL_PET && (struct pet_data *)bl) + adelay = mob_db[((struct pet_data *)bl)->class].adelay; + + if(sc_data) { + //ツーハンドクイッケン使用時でクァグマイアでも私を忘れないで…でもない時は3割減算 + if(sc_data[SC_TWOHANDQUICKEN].timer != -1 && sc_data[SC_QUAGMIRE].timer == -1 && sc_data[SC_DONTFORGETME].timer == -1) // 2HQ + aspd_rate -= 30; + //アドレナリンラッシュ使用時でツーハンドクイッケンでもクァグマイアでも私を忘れないで…でもない時は + if(sc_data[SC_ADRENALINE].timer != -1 && sc_data[SC_TWOHANDQUICKEN].timer == -1 && + sc_data[SC_QUAGMIRE].timer == -1 && sc_data[SC_DONTFORGETME].timer == -1) { // アドレナリンラッシュ + //使用者とパーティメンバーで格差が出る設定でなければ3割減算 + if(sc_data[SC_ADRENALINE].val2 || !battle_config.party_skill_penaly) + aspd_rate -= 30; + //そうでなければ2.5割減算 + else + aspd_rate -= 25; + } + //スピアクィッケン時は減算 + if(sc_data[SC_SPEARSQUICKEN].timer != -1 && sc_data[SC_ADRENALINE].timer == -1 && + sc_data[SC_TWOHANDQUICKEN].timer == -1 && sc_data[SC_QUAGMIRE].timer == -1 && sc_data[SC_DONTFORGETME].timer == -1) // スピアクィッケン + aspd_rate -= sc_data[SC_SPEARSQUICKEN].val2; + //夕日のアサシンクロス時は減算 + if(sc_data[SC_ASSNCROS].timer!=-1 && // 夕陽のアサシンクロス + sc_data[SC_TWOHANDQUICKEN].timer==-1 && sc_data[SC_ADRENALINE].timer==-1 && sc_data[SC_SPEARSQUICKEN].timer==-1 && + sc_data[SC_DONTFORGETME].timer == -1) + aspd_rate -= 5+sc_data[SC_ASSNCROS].val1+sc_data[SC_ASSNCROS].val2+sc_data[SC_ASSNCROS].val3; + //私を忘れないで…時は加算 + if(sc_data[SC_DONTFORGETME].timer!=-1) // 私を忘れないで + aspd_rate += sc_data[SC_DONTFORGETME].val1*3 + sc_data[SC_DONTFORGETME].val2 + (sc_data[SC_DONTFORGETME].val3>>16); + //金剛時25%加算 + if(sc_data[SC_STEELBODY].timer!=-1) // 金剛 + aspd_rate += 25; + //増速ポーション使用時は減算 + if( sc_data[i=SC_SPEEDPOTION2].timer!=-1 || sc_data[i=SC_SPEEDPOTION1].timer!=-1 || sc_data[i=SC_SPEEDPOTION0].timer!=-1) + aspd_rate -= sc_data[i].val2; + //ディフェンダー時は加算 + if(sc_data[SC_DEFENDER].timer != -1) + adelay += (1100 - sc_data[SC_DEFENDER].val1*100); + } + if(aspd_rate != 100) + adelay = adelay*aspd_rate/100; + if(adelay < battle_config.monster_max_aspd<<1) adelay = battle_config.monster_max_aspd<<1; + return adelay; + } + return 4000; +} +int battle_get_amotion(struct block_list *bl) +{ + nullpo_retr(2000, bl); + if(bl->type==BL_PC && (struct map_session_data *)bl) + return ((struct map_session_data *)bl)->amotion; + else { + struct status_change *sc_data=battle_get_sc_data(bl); + int amotion=2000,aspd_rate = 100,i; + if(bl->type==BL_MOB && (struct mob_data *)bl) + amotion = mob_db[((struct mob_data *)bl)->class].amotion; + else if(bl->type==BL_PET && (struct pet_data *)bl) + amotion = mob_db[((struct pet_data *)bl)->class].amotion; + + if(sc_data) { + if(sc_data[SC_TWOHANDQUICKEN].timer != -1 && sc_data[SC_QUAGMIRE].timer == -1 && sc_data[SC_DONTFORGETME].timer == -1) // 2HQ + aspd_rate -= 30; + if(sc_data[SC_ADRENALINE].timer != -1 && sc_data[SC_TWOHANDQUICKEN].timer == -1 && + sc_data[SC_QUAGMIRE].timer == -1 && sc_data[SC_DONTFORGETME].timer == -1) { // アドレナリンラッシュ + if(sc_data[SC_ADRENALINE].val2 || !battle_config.party_skill_penaly) + aspd_rate -= 30; + else + aspd_rate -= 25; + } + if(sc_data[SC_SPEARSQUICKEN].timer != -1 && sc_data[SC_ADRENALINE].timer == -1 && + sc_data[SC_TWOHANDQUICKEN].timer == -1 && sc_data[SC_QUAGMIRE].timer == -1 && sc_data[SC_DONTFORGETME].timer == -1) // スピアクィッケン + aspd_rate -= sc_data[SC_SPEARSQUICKEN].val2; + if(sc_data[SC_ASSNCROS].timer!=-1 && // 夕陽のアサシンクロス + sc_data[SC_TWOHANDQUICKEN].timer==-1 && sc_data[SC_ADRENALINE].timer==-1 && sc_data[SC_SPEARSQUICKEN].timer==-1 && + sc_data[SC_DONTFORGETME].timer == -1) + aspd_rate -= 5+sc_data[SC_ASSNCROS].val1+sc_data[SC_ASSNCROS].val2+sc_data[SC_ASSNCROS].val3; + if(sc_data[SC_DONTFORGETME].timer!=-1) // 私を忘れないで + aspd_rate += sc_data[SC_DONTFORGETME].val1*3 + sc_data[SC_DONTFORGETME].val2 + (sc_data[SC_DONTFORGETME].val3>>16); + if(sc_data[SC_STEELBODY].timer!=-1) // 金剛 + aspd_rate += 25; + if( sc_data[i=SC_SPEEDPOTION2].timer!=-1 || sc_data[i=SC_SPEEDPOTION1].timer!=-1 || sc_data[i=SC_SPEEDPOTION0].timer!=-1) + aspd_rate -= sc_data[i].val2; + if(sc_data[SC_DEFENDER].timer != -1) + amotion += (550 - sc_data[SC_DEFENDER].val1*50); + } + if(aspd_rate != 100) + amotion = amotion*aspd_rate/100; + if(amotion < battle_config.monster_max_aspd) amotion = battle_config.monster_max_aspd; + return amotion; + } + return 2000; +} +int battle_get_dmotion(struct block_list *bl) +{ + int ret; + struct status_change *sc_data; + + nullpo_retr(0, bl); + sc_data = battle_get_sc_data(bl); + if(bl->type==BL_MOB && (struct mob_data *)bl){ + ret=mob_db[((struct mob_data *)bl)->class].dmotion; + if(battle_config.monster_damage_delay_rate != 100) + ret = ret*battle_config.monster_damage_delay_rate/400; + } + else if(bl->type==BL_PC && (struct map_session_data *)bl){ + ret=((struct map_session_data *)bl)->dmotion; + if(battle_config.pc_damage_delay_rate != 100) + ret = ret*battle_config.pc_damage_delay_rate/400; + } + else if(bl->type==BL_PET && (struct pet_data *)bl) + ret=mob_db[((struct pet_data *)bl)->class].dmotion; + else + return 2000; + + if((sc_data && sc_data[SC_ENDURE].timer!=-1) || + (bl->type == BL_PC && ((struct map_session_data *)bl)->special_state.infinite_endure)) + ret=0; + + return ret; +} +int battle_get_element(struct block_list *bl) +{ + int ret = 20; + struct status_change *sc_data; + + nullpo_retr(ret, bl); + sc_data = battle_get_sc_data(bl); + if(bl->type==BL_MOB && (struct mob_data *)bl) // 10の位=Lv*2、1の位=属性 + ret=((struct mob_data *)bl)->def_ele; + else if(bl->type==BL_PC && (struct map_session_data *)bl) + ret=20+((struct map_session_data *)bl)->def_ele; // 防御属性Lv1 + else if(bl->type==BL_PET && (struct pet_data *)bl) + ret = mob_db[((struct pet_data *)bl)->class].element; + + if(sc_data) { + if( sc_data[SC_BENEDICTIO].timer!=-1 ) // 聖体降福 + ret=26; + if( sc_data[SC_FREEZE].timer!=-1 ) // 凍結 + ret=21; + if( sc_data[SC_STONE].timer!=-1 && sc_data[SC_STONE].val2==0) + ret=22; + } + + return ret; +} +int battle_get_attack_element(struct block_list *bl) +{ + int ret = 0; + struct status_change *sc_data=battle_get_sc_data(bl); + + nullpo_retr(0, bl); + if(bl->type==BL_MOB && (struct mob_data *)bl) + ret=0; + else if(bl->type==BL_PC && (struct map_session_data *)bl) + ret=((struct map_session_data *)bl)->atk_ele; + else if(bl->type==BL_PET && (struct pet_data *)bl) + ret=0; + + if(sc_data) { + if( sc_data[SC_FROSTWEAPON].timer!=-1) // フロストウェポン + ret=1; + if( sc_data[SC_SEISMICWEAPON].timer!=-1) // サイズミックウェポン + ret=2; + if( sc_data[SC_FLAMELAUNCHER].timer!=-1) // フレームランチャー + ret=3; + if( sc_data[SC_LIGHTNINGLOADER].timer!=-1) // ライトニングローダー + ret=4; + if( sc_data[SC_ENCPOISON].timer!=-1) // エンチャントポイズン + ret=5; + if( sc_data[SC_ASPERSIO].timer!=-1) // アスペルシオ + ret=6; + } + + return ret; +} +int battle_get_attack_element2(struct block_list *bl) +{ + nullpo_retr(0, bl); + if(bl->type==BL_PC && (struct map_session_data *)bl) { + int ret = ((struct map_session_data *)bl)->atk_ele_; + struct status_change *sc_data = ((struct map_session_data *)bl)->sc_data; + + if(sc_data) { + if( sc_data[SC_FROSTWEAPON].timer!=-1) // フロストウェポン + ret=1; + if( sc_data[SC_SEISMICWEAPON].timer!=-1) // サイズミックウェポン + ret=2; + if( sc_data[SC_FLAMELAUNCHER].timer!=-1) // フレームランチャー + ret=3; + if( sc_data[SC_LIGHTNINGLOADER].timer!=-1) // ライトニングローダー + ret=4; + if( sc_data[SC_ENCPOISON].timer!=-1) // エンチャントポイズン + ret=5; + if( sc_data[SC_ASPERSIO].timer!=-1) // アスペルシオ + ret=6; + } + return ret; + } + return 0; +} +int battle_get_party_id(struct block_list *bl) +{ + nullpo_retr(0, bl); + if(bl->type==BL_PC && (struct map_session_data *)bl) + return ((struct map_session_data *)bl)->status.party_id; + else if(bl->type==BL_MOB && (struct mob_data *)bl){ + struct mob_data *md=(struct mob_data *)bl; + if( md->master_id>0 ) + return -md->master_id; + return -md->bl.id; + } + else if(bl->type==BL_SKILL && (struct skill_unit *)bl) + return ((struct skill_unit *)bl)->group->party_id; + else + return 0; +} +int battle_get_guild_id(struct block_list *bl) +{ + nullpo_retr(0, bl); + if(bl->type==BL_PC && (struct map_session_data *)bl) + return ((struct map_session_data *)bl)->status.guild_id; + else if(bl->type==BL_MOB && (struct mob_data *)bl) + return ((struct mob_data *)bl)->class; + else if(bl->type==BL_SKILL && (struct skill_unit *)bl) + return ((struct skill_unit *)bl)->group->guild_id; + else + return 0; +} +int battle_get_race(struct block_list *bl) +{ + nullpo_retr(0, bl); + if(bl->type==BL_MOB && (struct mob_data *)bl) + return mob_db[((struct mob_data *)bl)->class].race; + else if(bl->type==BL_PC && (struct map_session_data *)bl) + return 7; + else if(bl->type==BL_PET && (struct pet_data *)bl) + return mob_db[((struct pet_data *)bl)->class].race; + else + return 0; +} +int battle_get_size(struct block_list *bl) +{ + nullpo_retr(1, bl); + if(bl->type==BL_MOB && (struct mob_data *)bl) + return mob_db[((struct mob_data *)bl)->class].size; + else if(bl->type==BL_PC && (struct map_session_data *)bl) + return 1; + else if(bl->type==BL_PET && (struct pet_data *)bl) + return mob_db[((struct pet_data *)bl)->class].size; + else + return 1; +} +int battle_get_mode(struct block_list *bl) +{ + nullpo_retr(0x01, bl); + if(bl->type==BL_MOB && (struct mob_data *)bl) + return mob_db[((struct mob_data *)bl)->class].mode; + else if(bl->type==BL_PET && (struct pet_data *)bl) + return mob_db[((struct pet_data *)bl)->class].mode; + else + return 0x01; // とりあえず動くということで1 +} + +int battle_get_mexp(struct block_list *bl) +{ + nullpo_retr(0, bl); + if(bl->type==BL_MOB && (struct mob_data *)bl) + return mob_db[((struct mob_data *)bl)->class].mexp; + else if(bl->type==BL_PET && (struct pet_data *)bl) + return mob_db[((struct pet_data *)bl)->class].mexp; + else + return 0; +} + +// StatusChange系の所得 +struct status_change *battle_get_sc_data(struct block_list *bl) +{ + nullpo_retr(NULL, bl); + if(bl->type==BL_MOB && (struct mob_data *)bl) + return ((struct mob_data*)bl)->sc_data; + else if(bl->type==BL_PC && (struct map_session_data *)bl) + return ((struct map_session_data*)bl)->sc_data; + return NULL; +} +short *battle_get_sc_count(struct block_list *bl) +{ + nullpo_retr(NULL, bl); + if(bl->type==BL_MOB && (struct mob_data *)bl) + return &((struct mob_data*)bl)->sc_count; + else if(bl->type==BL_PC && (struct map_session_data *)bl) + return &((struct map_session_data*)bl)->sc_count; + return NULL; +} +short *battle_get_opt1(struct block_list *bl) +{ + nullpo_retr(0, bl); + if(bl->type==BL_MOB && (struct mob_data *)bl) + return &((struct mob_data*)bl)->opt1; + else if(bl->type==BL_PC && (struct map_session_data *)bl) + return &((struct map_session_data*)bl)->opt1; + else if(bl->type==BL_NPC && (struct npc_data *)bl) + return &((struct npc_data*)bl)->opt1; + return 0; +} +short *battle_get_opt2(struct block_list *bl) +{ + nullpo_retr(0, bl); + if(bl->type==BL_MOB && (struct mob_data *)bl) + return &((struct mob_data*)bl)->opt2; + else if(bl->type==BL_PC && (struct map_session_data *)bl) + return &((struct map_session_data*)bl)->opt2; + else if(bl->type==BL_NPC && (struct npc_data *)bl) + return &((struct npc_data*)bl)->opt2; + return 0; +} +short *battle_get_opt3(struct block_list *bl) +{ + nullpo_retr(0, bl); + if(bl->type==BL_MOB && (struct mob_data *)bl) + return &((struct mob_data*)bl)->opt3; + else if(bl->type==BL_PC && (struct map_session_data *)bl) + return &((struct map_session_data*)bl)->opt3; + else if(bl->type==BL_NPC && (struct npc_data *)bl) + return &((struct npc_data*)bl)->opt3; + return 0; +} +short *battle_get_option(struct block_list *bl) +{ + nullpo_retr(0, bl); + if(bl->type==BL_MOB && (struct mob_data *)bl) + return &((struct mob_data*)bl)->option; + else if(bl->type==BL_PC && (struct map_session_data *)bl) + return &((struct map_session_data*)bl)->status.option; + else if(bl->type==BL_NPC && (struct npc_data *)bl) + return &((struct npc_data*)bl)->option; + return 0; +} + +//------------------------------------------------------------------- + +// ダメージの遅延 +struct battle_delay_damage_ { + struct block_list *src,*target; + int damage; + int flag; +}; +int battle_delay_damage_sub(int tid,unsigned int tick,int id,int data) +{ + struct battle_delay_damage_ *dat=(struct battle_delay_damage_ *)data; + if( dat && map_id2bl(id)==dat->src && dat->target->prev!=NULL) + battle_damage(dat->src,dat->target,dat->damage,dat->flag); + free(dat); + return 0; +} +int battle_delay_damage(unsigned int tick,struct block_list *src,struct block_list *target,int damage,int flag) +{ + struct battle_delay_damage_ *dat = (struct battle_delay_damage_*)aCalloc(1,sizeof(struct battle_delay_damage_)); + + nullpo_retr(0, src); + nullpo_retr(0, target); + + + dat->src=src; + dat->target=target; + dat->damage=damage; + dat->flag=flag; + add_timer(tick,battle_delay_damage_sub,src->id,(int)dat); + return 0; +} + +// 実際にHPを操作 +int battle_damage(struct block_list *bl,struct block_list *target,int damage,int flag) +{ + struct map_session_data *sd=NULL; + struct status_change *sc_data=battle_get_sc_data(target); + short *sc_count; + int i; + + nullpo_retr(0, target); //blはNULLで呼ばれることがあるので他でチェック + + if(damage==0 || target->type == BL_PET) + return 0; + + if(target->prev == NULL) + return 0; + + if(bl) { + if(bl->prev==NULL) + return 0; + + if(bl->type==BL_PC) + sd=(struct map_session_data *)bl; + } + + if(damage<0) + return battle_heal(bl,target,-damage,0,flag); + + if(!flag && (sc_count=battle_get_sc_count(target))!=NULL && *sc_count>0){ + // 凍結、石化、睡眠を消去 + if(sc_data[SC_FREEZE].timer!=-1) + skill_status_change_end(target,SC_FREEZE,-1); + if(sc_data[SC_STONE].timer!=-1 && sc_data[SC_STONE].val2==0) + skill_status_change_end(target,SC_STONE,-1); + if(sc_data[SC_SLEEP].timer!=-1) + skill_status_change_end(target,SC_SLEEP,-1); + } + + if(target->type==BL_MOB){ // MOB + struct mob_data *md=(struct mob_data *)target; + if(md && md->skilltimer!=-1 && md->state.skillcastcancel) // 詠唱妨害 + skill_castcancel(target,0); + return mob_damage(bl,md,damage,0); + } + else if(target->type==BL_PC){ // PC + + struct map_session_data *tsd=(struct map_session_data *)target; + + if(tsd && tsd->sc_data && tsd->sc_data[SC_DEVOTION].val1){ // ディボーションをかけられている + struct map_session_data *md = map_id2sd(tsd->sc_data[SC_DEVOTION].val1); + if(md && skill_devotion3(&md->bl,target->id)){ + skill_devotion(md,target->id); + } + else if(md && bl) + for(i=0;i<5;i++) + if(md->dev.val1[i] == target->id){ + clif_damage(bl,&md->bl, gettick(), 0, 0, + damage, 0 , 0, 0); + pc_damage(&md->bl,md,damage); + + return 0; + } + } + + if(tsd && tsd->skilltimer!=-1){ // 詠唱妨害 + // フェンカードや妨害されないスキルかの検査 + if( (!tsd->special_state.no_castcancel || map[bl->m].flag.gvg) && tsd->state.skillcastcancel && + !tsd->special_state.no_castcancel2) + skill_castcancel(target,0); + } + + return pc_damage(bl,tsd,damage); + + } + else if(target->type==BL_SKILL) + return skill_unit_ondamaged((struct skill_unit *)target,bl,damage,gettick()); + return 0; +} +int battle_heal(struct block_list *bl,struct block_list *target,int hp,int sp,int flag) +{ + nullpo_retr(0, target); //blはNULLで呼ばれることがあるので他でチェック + + if(target->type == BL_PET) + return 0; + if( target->type ==BL_PC && pc_isdead((struct map_session_data *)target) ) + return 0; + if(hp==0 && sp==0) + return 0; + + if(hp<0) + return battle_damage(bl,target,-hp,flag); + + if(target->type==BL_MOB) + return mob_heal((struct mob_data *)target,hp); + else if(target->type==BL_PC) + return pc_heal((struct map_session_data *)target,hp,sp); + return 0; +} + +// 攻撃停止 +int battle_stopattack(struct block_list *bl) +{ + nullpo_retr(0, bl); + if(bl->type==BL_MOB) + return mob_stopattack((struct mob_data*)bl); + else if(bl->type==BL_PC) + return pc_stopattack((struct map_session_data*)bl); + else if(bl->type==BL_PET) + return pet_stopattack((struct pet_data*)bl); + return 0; +} +// 移動停止 +int battle_stopwalking(struct block_list *bl,int type) +{ + nullpo_retr(0, bl); + if(bl->type==BL_MOB) + return mob_stop_walking((struct mob_data*)bl,type); + else if(bl->type==BL_PC) + return pc_stop_walking((struct map_session_data*)bl,type); + else if(bl->type==BL_PET) + return pet_stop_walking((struct pet_data*)bl,type); + return 0; +} + + +/*========================================== + * ダメージの属性修正 + *------------------------------------------ + */ +int battle_attr_fix(int damage,int atk_elem,int def_elem) +{ + int def_type= def_elem%10, def_lv=def_elem/10/2; + + if( atk_elem<0 || atk_elem>9 || def_type<0 || def_type>9 || + def_lv<1 || def_lv>4){ // 属 性値がおかしいのでとりあえずそのまま返す + if(battle_config.error_log) + printf("battle_attr_fix: unknown attr type: atk=%d def_type=%d def_lv=%d\n",atk_elem,def_type,def_lv); + return damage; + } + + return damage*attr_fix_table[def_lv-1][atk_elem][def_type]/100; +} + + +/*========================================== + * ダメージ最終計算 + *------------------------------------------ + */ +int battle_calc_damage(struct block_list *src,struct block_list *bl,int damage,int div_,int skill_num,int skill_lv,int flag) +{ + struct map_session_data *sd=NULL; + struct mob_data *md=NULL; + struct status_change *sc_data,*sc; + short *sc_count; + int class; + + nullpo_retr(0, bl); + + class = battle_get_class(bl); + if(bl->type==BL_MOB) md=(struct mob_data *)bl; + else sd=(struct map_session_data *)bl; + + sc_data=battle_get_sc_data(bl); + sc_count=battle_get_sc_count(bl); + + if(sc_count!=NULL && *sc_count>0){ + + if(sc_data[SC_SAFETYWALL].timer!=-1 && damage>0 && flag&BF_WEAPON && flag&BF_SHORT && skill_num != NPC_GUIDEDATTACK){ + // セーフティウォール + struct skill_unit *unit=(struct skill_unit*)sc_data[SC_SAFETYWALL].val2; + if( unit && unit->alive && (--unit->group->val2)<=0 ) + skill_delunit(unit); + skill_unit_move(bl,gettick(),1); // 重ね掛けチェック + damage=0; + } + if(sc_data[SC_PNEUMA].timer!=-1 && damage>0 && flag&BF_WEAPON && flag&BF_LONG && skill_num != NPC_GUIDEDATTACK){ + // ニューマ + damage=0; + } + + if(sc_data[SC_ROKISWEIL].timer!=-1 && damage>0 && + flag&BF_MAGIC ){ + // ニューマ + damage=0; + } + + if(sc_data[SC_AETERNA].timer!=-1 && damage>0){ // レックスエーテルナ + damage<<=1; + skill_status_change_end( bl,SC_AETERNA,-1 ); + } + + //属性場のダメージ増加 + if(sc_data[SC_VOLCANO].timer!=-1){ // ボルケーノ + if(flag&BF_SKILL && skill_get_pl(skill_num)==3) + damage += damage*sc_data[SC_VOLCANO].val4/100; + else if(!flag&BF_SKILL && battle_get_attack_element(bl)==3) + damage += damage*sc_data[SC_VOLCANO].val4/100; + } + + if(sc_data[SC_VIOLENTGALE].timer!=-1){ // バイオレントゲイル + if(flag&BF_SKILL && skill_get_pl(skill_num)==4) + damage += damage*sc_data[SC_VIOLENTGALE].val4/100; + else if(!flag&BF_SKILL && battle_get_attack_element(bl)==4) + damage += damage*sc_data[SC_VIOLENTGALE].val4/100; + } + + if(sc_data[SC_DELUGE].timer!=-1){ // デリュージ + if(flag&BF_SKILL && skill_get_pl(skill_num)==1) + damage += damage*sc_data[SC_DELUGE].val4/100; + else if(!flag&BF_SKILL && battle_get_attack_element(bl)==1) + damage += damage*sc_data[SC_DELUGE].val4/100; + } + + if(sc_data[SC_ENERGYCOAT].timer!=-1 && damage>0 && flag&BF_WEAPON){ // エナジーコート + if(sd){ + if(sd->status.sp>0){ + int per = sd->status.sp * 5 / (sd->status.max_sp + 1); + sd->status.sp -= sd->status.sp * (per * 5 + 10) / 1000; + if( sd->status.sp < 0 ) sd->status.sp = 0; + damage -= damage * ((per+1) * 6) / 100; + clif_updatestatus(sd,SP_SP); + } + if(sd->status.sp<=0) + skill_status_change_end( bl,SC_ENERGYCOAT,-1 ); + } + else + damage -= damage * (sc_data[SC_ENERGYCOAT].val1 * 6) / 100; + } + + if(sc_data[SC_KYRIE].timer!=-1 && damage > 0){ // キリエエレイソン + sc=&sc_data[SC_KYRIE]; + sc->val2-=damage; + if(flag&BF_WEAPON){ + if(sc->val2>=0) damage=0; + else damage=-sc->val2; + } + if((--sc->val3)<=0 || (sc->val2<=0) || skill_num == AL_HOLYLIGHT) + skill_status_change_end(bl, SC_KYRIE, -1); + } + + if(sc_data[SC_BASILICA].timer!=-1 && damage > 0){ + // ニューマ + damage=0; + } + if(sc_data[SC_LANDPROTECTOR].timer!=-1 && damage>0 && flag&BF_MAGIC){ + // ニューマ + damage=0; + } + + if(sc_data[SC_AUTOGUARD].timer != -1 && damage > 0 && flag&BF_WEAPON) { + if(rand()%100 < sc_data[SC_AUTOGUARD].val2) { + damage = 0; + clif_skill_nodamage(bl,bl,CR_AUTOGUARD,sc_data[SC_AUTOGUARD].val1,1); + if(sd) + sd->canmove_tick = gettick() + 300; + else if(md) + md->canmove_tick = gettick() + 300; + } + } +// -- moonsoul (chance to block attacks with new Lord Knight skill parrying) +// + if(sc_data[SC_PARRYING].timer != -1 && damage > 0 && flag&BF_WEAPON) { + if(rand()%100 < sc_data[SC_PARRYING].val2) { + damage = 0; + clif_skill_nodamage(bl,bl,LK_PARRYING,sc_data[SC_PARRYING].val1,1); + } + } + // リジェクトソード + if(sc_data[SC_REJECTSWORD].timer!=-1 && damage > 0 && flag&BF_WEAPON && + ((src->type==BL_PC && ((struct map_session_data *)src)->status.weapon == (1 || 2 || 3)) || src->type==BL_MOB )){ + if(rand()%100 < (10+5*sc_data[SC_REJECTSWORD].val1)){ //反射確率は10+5*Lv + damage = damage*50/100; + battle_damage(bl,src,damage,0); + //ダメージを与えたのは良いんだが、ここからどうして表示するんだかわかんねぇ + //エフェクトもこれでいいのかわかんねぇ + clif_skill_nodamage(bl,bl,ST_REJECTSWORD,sc_data[SC_REJECTSWORD].val1,1); + if((--sc_data[SC_REJECTSWORD].val2)<=0) + skill_status_change_end(bl, SC_REJECTSWORD, -1); + } + } + } + + if(class == 1288 || class == 1287 || class == 1286 || class == 1285) { +// if(class == 1288) { + if(class == 1288 && flag&BF_SKILL) + damage=0; + if(src->type == BL_PC) { + struct guild *g=guild_search(((struct map_session_data *)src)->status.guild_id); + struct guild_castle *gc=guild_mapname2gc(map[bl->m].name); + if(!((struct map_session_data *)src)->status.guild_id) + damage=0; + if(gc && agit_flag==0 && class != 1288) // guardians cannot be damaged during non-woe [Valaris] + damage=0; // end woe check [Valaris] + if(g == NULL) + damage=0;//ギルド未加入ならダメージ無し + else if((gc != NULL) && guild_isallied(g, gc)) + damage=0;//自占領ギルドのエンペならダメージ無し + else if(g && guild_checkskill(g,GD_APPROVAL) <= 0) + damage=0;//正規ギルド承認がないとダメージ無し + else if (battle_config.guild_max_castles != 0 && guild_checkcastles(g)>=battle_config.guild_max_castles) + damage = 0; // [MouseJstr] + } + else damage = 0; + } + + if(map[bl->m].flag.gvg && damage > 0) { //GvG + if(flag&BF_WEAPON) { + if(flag&BF_SHORT) + damage=damage*battle_config.gvg_short_damage_rate/100; + if(flag&BF_LONG) + damage=damage*battle_config.gvg_long_damage_rate/100; + } + if(flag&BF_MAGIC) + damage = damage*battle_config.gvg_magic_damage_rate/100; + if(flag&BF_MISC) + damage=damage*battle_config.gvg_misc_damage_rate/100; + if(damage < 1) damage = 1; + } + + if(battle_config.skill_min_damage || flag&BF_MISC) { + if(div_ < 255) { + if(damage > 0 && damage < div_) + damage = div_; + } + else if(damage > 0 && damage < 3) + damage = 3; + } + + if( md!=NULL && md->hp>0 && damage > 0 ) // 反撃などのMOBスキル判定 + mobskill_event(md,flag); + + return damage; +} + +/*========================================== + * 修練ダメージ + *------------------------------------------ + */ +int battle_addmastery(struct map_session_data *sd,struct block_list *target,int dmg,int type) +{ + int damage,skill; + int race=battle_get_race(target); + int weapon; + damage = 0; + + nullpo_retr(0, sd); + + // デーモンベイン(+3 〜 +30) vs 不死 or 悪魔 (死人は含めない?) + if((skill = pc_checkskill(sd,AL_DEMONBANE)) > 0 && (battle_check_undead(race,battle_get_elem_type(target)) || race==6) ) + damage += (skill * 3); + + // ビーストベイン(+4 〜 +40) vs 動物 or 昆虫 + if((skill = pc_checkskill(sd,HT_BEASTBANE)) > 0 && (race==2 || race==4) ) + damage += (skill * 4); + + if(type == 0) + weapon = sd->weapontype1; + else + weapon = sd->weapontype2; + switch(weapon) + { + case 0x01: // 短剣 (Updated By AppleGirl) + case 0x02: // 1HS + { + // 剣修練(+4 〜 +40) 片手剣 短剣含む + if((skill = pc_checkskill(sd,SM_SWORD)) > 0) { + damage += (skill * 4); + } + break; + } + case 0x03: // 2HS + { + // 両手剣修練(+4 〜 +40) 両手剣 + if((skill = pc_checkskill(sd,SM_TWOHAND)) > 0) { + damage += (skill * 4); + } + break; + } + case 0x04: // 1HL + { + // 槍修練(+4 〜 +40,+5 〜 +50) 槍 + if((skill = pc_checkskill(sd,KN_SPEARMASTERY)) > 0) { + if(!pc_isriding(sd)) + damage += (skill * 4); // ペコに乗ってない + else + damage += (skill * 5); // ペコに乗ってる + } + break; + } + case 0x05: // 2HL + { + // 槍修練(+4 〜 +40,+5 〜 +50) 槍 + if((skill = pc_checkskill(sd,KN_SPEARMASTERY)) > 0) { + if(!pc_isriding(sd)) + damage += (skill * 4); // ペコに乗ってない + else + damage += (skill * 5); // ペコに乗ってる + } + break; + } + case 0x06: // 片手斧 + { + if((skill = pc_checkskill(sd,AM_AXEMASTERY)) > 0) { + damage += (skill * 3); + } + break; + } + case 0x07: // Axe by Tato + { + if((skill = pc_checkskill(sd,AM_AXEMASTERY)) > 0) { + damage += (skill * 3); + } + break; + } + case 0x08: // メイス + { + // メイス修練(+3 〜 +30) メイス + if((skill = pc_checkskill(sd,PR_MACEMASTERY)) > 0) { + damage += (skill * 3); + } + break; + } + case 0x09: // なし? + break; + case 0x0a: // 杖 + break; + case 0x0b: // 弓 + break; + case 0x00: // 素手 + case 0x0c: // Knuckles + { + // 鉄拳(+3 〜 +30) 素手,ナックル + if((skill = pc_checkskill(sd,MO_IRONHAND)) > 0) { + damage += (skill * 3); + } + break; + } + case 0x0d: // Musical Instrument + { + // 楽器の練習(+3 〜 +30) 楽器 + if((skill = pc_checkskill(sd,BA_MUSICALLESSON)) > 0) { + damage += (skill * 3); + } + break; + } + case 0x0e: // Dance Mastery + { + // Dance Lesson Skill Effect(+3 damage for every lvl = +30) 鞭 + if((skill = pc_checkskill(sd,DC_DANCINGLESSON)) > 0) { + damage += (skill * 3); + } + break; + } + case 0x0f: // Book + { + // Advance Book Skill Effect(+3 damage for every lvl = +30) { + if((skill = pc_checkskill(sd,SA_ADVANCEDBOOK)) > 0) { + damage += (skill * 3); + } + break; + } + case 0x10: // Katars + { + // カタール修練(+3 〜 +30) カタール + if((skill = pc_checkskill(sd,AS_KATAR)) > 0) { + //ソニックブロー時は別処理(1撃に付き1/8適応) + damage += (skill * 3); + } + break; + } + } + damage = dmg + damage; + return (damage); +} + +static struct Damage battle_calc_pet_weapon_attack( + struct block_list *src,struct block_list *target,int skill_num,int skill_lv,int wflag) +{ + struct pet_data *pd = (struct pet_data *)src; + struct mob_data *tmd=NULL; + int hitrate,flee,cri = 0,atkmin,atkmax; + int luk,target_count = 1; + int def1 = battle_get_def(target); + int def2 = battle_get_def2(target); + int t_vit = battle_get_vit(target); + struct Damage wd; + int damage,damage2=0,type,div_,blewcount=skill_get_blewcount(skill_num,skill_lv); + int flag,dmg_lv=0; + int t_mode=0,t_race=0,t_size=1,s_race=0,s_ele=0; + struct status_change *t_sc_data; + + //return前の処理があるので情報出力部のみ変更 + if( target == NULL || pd == NULL ){ //srcは内容に直接触れていないのでスルーしてみる + nullpo_info(NLP_MARK); + memset(&wd,0,sizeof(wd)); + return wd; + } + + s_race=battle_get_race(src); + s_ele=battle_get_attack_element(src); + + // ターゲット + if(target->type == BL_MOB) + tmd=(struct mob_data *)target; + else { + memset(&wd,0,sizeof(wd)); + return wd; + } + t_race=battle_get_race( target ); + t_size=battle_get_size( target ); + t_mode=battle_get_mode( target ); + t_sc_data=battle_get_sc_data( target ); + + flag=BF_SHORT|BF_WEAPON|BF_NORMAL; // 攻撃の種類の設定 + + // 回避率計算、回避判定は後で + flee = battle_get_flee(target); + if(battle_config.agi_penaly_type > 0 || battle_config.vit_penaly_type > 0) + target_count += battle_counttargeted(target,src,battle_config.agi_penaly_count_lv); + if(battle_config.agi_penaly_type > 0) { + if(target_count >= battle_config.agi_penaly_count) { + if(battle_config.agi_penaly_type == 1) + flee = (flee * (100 - (target_count - (battle_config.agi_penaly_count - 1))*battle_config.agi_penaly_num))/100; + else if(battle_config.agi_penaly_type == 2) + flee -= (target_count - (battle_config.agi_penaly_count - 1))*battle_config.agi_penaly_num; + if(flee < 1) flee = 1; + } + } + hitrate=battle_get_hit(src) - flee + 80; + + type=0; // normal + div_ = 1; // single attack + + luk=battle_get_luk(src); + + if(battle_config.pet_str) + damage = battle_get_baseatk(src); + else + damage = 0; + + if(skill_num==HW_MAGICCRASHER){ /* マジッククラッシャーはMATKで殴る */ + atkmin = battle_get_matk1(src); + atkmax = battle_get_matk2(src); + }else{ + atkmin = battle_get_atk(src); + atkmax = battle_get_atk2(src); + } + if(mob_db[pd->class].range>3 ) + flag=(flag&~BF_RANGEMASK)|BF_LONG; + + if(atkmin > atkmax) atkmin = atkmax; + + cri = battle_get_critical(src); + cri -= battle_get_luk(target) * 2; // luk/5*10 => target_luk*2 not target_luk*3 + if(battle_config.enemy_critical_rate != 100) { + cri = cri*battle_config.enemy_critical_rate/100; + if(cri < 1) + cri = 1; + } + if(t_sc_data != NULL && t_sc_data[SC_SLEEP].timer!=-1 ) + cri <<=1; + + if(skill_num == 0 && skill_lv >= 0 && battle_config.enemy_critical && (rand() % 1000) < cri) + { + damage += atkmax; + type = 0x0a; + } + else { + int vitbonusmax; + + if(atkmax > atkmin) + damage += atkmin + rand() % (atkmax-atkmin + 1); + else + damage += atkmin ; + // スキル修正1(攻撃力倍化系) + // オーバートラスト(+5% 〜 +25%),他攻撃系スキルの場合ここで補正 + // バッシュ,マグナムブレイク, + // ボーリングバッシュ,スピアブーメラン,ブランディッシュスピア,スピアスタッブ, + // メマーナイト,カートレボリューション + // ダブルストレイフィング,アローシャワー,チャージアロー, + // ソニックブロー + if(skill_num>0){ + int i; + if( (i=skill_get_pl(skill_num))>0 ) + s_ele=i; + + flag=(flag&~BF_SKILLMASK)|BF_SKILL; + switch( skill_num ){ + case SM_BASH: // バッシュ + damage = damage*(100+ 30*skill_lv)/100; + hitrate = (hitrate*(100+5*skill_lv))/100; + break; + case SM_MAGNUM: // マグナムブレイク + damage = damage*(5*skill_lv +(wflag)?65:115 )/100; + break; + case MC_MAMMONITE: // メマーナイト + damage = damage*(100+ 50*skill_lv)/100; + break; + case AC_DOUBLE: // ダブルストレイフィング + damage = damage*(180+ 20*skill_lv)/100; + div_=2; + flag=(flag&~BF_RANGEMASK)|BF_LONG; + break; + case AC_SHOWER: // アローシャワー + damage = damage*(75 + 5*skill_lv)/100; + flag=(flag&~BF_RANGEMASK)|BF_LONG; + break; + case AC_CHARGEARROW: // チャージアロー + damage = damage*150/100; + flag=(flag&~BF_RANGEMASK)|BF_LONG; + break; + case KN_PIERCE: // ピアース + damage = damage*(100+ 10*skill_lv)/100; + hitrate = hitrate*(100+5*skill_lv)/100; + div_=t_size+1; + damage*=div_; + break; + case KN_SPEARSTAB: // スピアスタブ + damage = damage*(100+ 15*skill_lv)/100; + break; + case KN_SPEARBOOMERANG: // スピアブーメラン + damage = damage*(100+ 50*skill_lv)/100; + flag=(flag&~BF_RANGEMASK)|BF_LONG; + break; + case KN_BRANDISHSPEAR: // ブランディッシュスピア + damage = damage*(100+ 20*skill_lv)/100; + if(skill_lv>3 && wflag==1) damage2+=damage/2; + if(skill_lv>6 && wflag==1) damage2+=damage/4; + if(skill_lv>9 && wflag==1) damage2+=damage/8; + if(skill_lv>6 && wflag==2) damage2+=damage/2; + if(skill_lv>9 && wflag==2) damage2+=damage/4; + if(skill_lv>9 && wflag==3) damage2+=damage/2; + damage +=damage2; + blewcount=0; + break; + case KN_BOWLINGBASH: // ボウリングバッシュ + damage = damage*(100+ 50*skill_lv)/100; + blewcount=0; + break; + case AS_SONICBLOW: // ソニックブロウ + damage = damage*(300+ 50*skill_lv)/100; + div_=8; + break; + case TF_SPRINKLESAND: // 砂まき + damage = damage*125/100; + break; + case MC_CARTREVOLUTION: // カートレボリューション + damage = (damage*150)/100; + break; + // 以下MOB + case NPC_COMBOATTACK: // 多段攻撃 + div_=skill_get_num(skill_num,skill_lv); + damage *= div_; + break; + case NPC_RANDOMATTACK: // ランダムATK攻撃 + damage = damage*(50+rand()%150)/100; + break; + // 属性攻撃(適当) + case NPC_WATERATTACK: + case NPC_GROUNDATTACK: + case NPC_FIREATTACK: + case NPC_WINDATTACK: + case NPC_POISONATTACK: + case NPC_HOLYATTACK: + case NPC_DARKNESSATTACK: + case NPC_TELEKINESISATTACK: + div_= pd->skillduration; // [Valaris] + break; + case NPC_GUIDEDATTACK: + hitrate = 1000000; + break; + case NPC_RANGEATTACK: + flag=(flag&~BF_RANGEMASK)|BF_LONG; + break; + case NPC_PIERCINGATT: + flag=(flag&~BF_RANGEMASK)|BF_SHORT; + break; + case RG_BACKSTAP: // バックスタブ + damage = damage*(300+ 40*skill_lv)/100; + hitrate = 1000000; + break; + case RG_RAID: // サプライズアタック + damage = damage*(100+ 40*skill_lv)/100; + break; + case RG_INTIMIDATE: // インティミデイト + damage = damage*(100+ 30*skill_lv)/100; + break; + case CR_SHIELDCHARGE: // シールドチャージ + damage = damage*(100+ 20*skill_lv)/100; + flag=(flag&~BF_RANGEMASK)|BF_SHORT; + s_ele = 0; + break; + case CR_SHIELDBOOMERANG: // シールドブーメラン + damage = damage*(100+ 30*skill_lv)/100; + flag=(flag&~BF_RANGEMASK)|BF_LONG; + s_ele = 0; + break; + case CR_HOLYCROSS: // ホーリークロス + damage = damage*(100+ 35*skill_lv)/100; + div_=2; + break; + case CR_GRANDCROSS: + hitrate= 1000000; + break; + case AM_DEMONSTRATION: // デモンストレーション + damage = damage*(100+ 20*skill_lv)/100; + damage2 = damage2*(100+ 20*skill_lv)/100; + break; + case AM_ACIDTERROR: // アシッドテラー + damage = damage*(100+ 40*skill_lv)/100; + damage2 = damage2*(100+ 40*skill_lv)/100; + break; + case MO_FINGEROFFENSIVE: //指弾 + damage = damage * (100 + 50 * skill_lv) / 100; + div_ = 1; + break; + case MO_INVESTIGATE: // 発 勁 + if(def1 < 1000000) + damage = damage*(100+ 75*skill_lv)/100 * (def1 + def2)/100; + hitrate = 1000000; + s_ele = 0; + break; + case MO_EXTREMITYFIST: // 阿修羅覇鳳拳 + damage = damage * 8 + 250 + (skill_lv * 150); + hitrate = 1000000; + s_ele = 0; + break; + case MO_CHAINCOMBO: // 連打掌 + damage = damage*(150+ 50*skill_lv)/100; + div_=4; + break; + case MO_COMBOFINISH: // 猛龍拳 + damage = damage*(240+ 60*skill_lv)/100; + break; + case DC_THROWARROW: // 矢撃ち + damage = damage*(100+ 50 * skill_lv)/100; + flag=(flag&~BF_RANGEMASK)|BF_LONG; + break; + case BA_MUSICALSTRIKE: // ミュージカルストライク + damage = damage*(100+ 50 * skill_lv)/100; + flag=(flag&~BF_RANGEMASK)|BF_LONG; + break; + case CH_TIGERFIST: // 伏虎拳 + damage = damage*(100+ 20*skill_lv)/100; + break; + case CH_CHAINCRUSH: // 連柱崩撃 + damage = damage*(100+ 20*skill_lv)/100; + div_=skill_get_num(skill_num,skill_lv); + break; + case CH_PALMSTRIKE: // 猛虎硬派山 + damage = damage*(50+ 100*skill_lv)/100; + break; + case LK_SPIRALPIERCE: /* スパイラルピアース */ + damage = damage*(100+ 50*skill_lv)/100; //増加量が分からないので適当に + div_=5; + if(target->type == BL_PC) + ((struct map_session_data *)target)->canmove_tick = gettick() + 1000; + else if(target->type == BL_MOB) + ((struct mob_data *)target)->canmove_tick = gettick() + 1000; + break; + case LK_HEADCRUSH: /* ヘッドクラッシュ */ + damage = damage*(100+ 20*skill_lv)/100; + break; + case LK_JOINTBEAT: /* ジョイントビート */ + damage = damage*(50+ 10*skill_lv)/100; + break; + case ASC_METEORASSAULT: /* メテオアサルト */ + damage = damage*(40+ 40*skill_lv)/100; + break; + case SN_SHARPSHOOTING: /* シャープシューティング */ + damage += damage*(30*skill_lv)/100; + break; + case CG_ARROWVULCAN: /* アローバルカン */ + damage = damage*(160+40*skill_lv)/100; + div_=9; + break; + case AS_SPLASHER: /* ベナムスプラッシャー */ + damage = damage*(200+20*skill_lv)/100; + break; + } + } + + if( skill_num!=NPC_CRITICALSLASH ){ + // 対 象の防御力によるダメージの減少 + // ディバインプロテクション(ここでいいのかな?) + if ( skill_num != MO_INVESTIGATE && skill_num != MO_EXTREMITYFIST && skill_num != KN_AUTOCOUNTER && def1 < 1000000 ) { //DEF, VIT無視 + int t_def; + target_count = 1 + battle_counttargeted(target,src,battle_config.vit_penaly_count_lv); + if(battle_config.vit_penaly_type > 0) { + if(target_count >= battle_config.vit_penaly_count) { + if(battle_config.vit_penaly_type == 1) { + def1 = (def1 * (100 - (target_count - (battle_config.vit_penaly_count - 1))*battle_config.vit_penaly_num))/100; + def2 = (def2 * (100 - (target_count - (battle_config.vit_penaly_count - 1))*battle_config.vit_penaly_num))/100; + t_vit = (t_vit * (100 - (target_count - (battle_config.vit_penaly_count - 1))*battle_config.vit_penaly_num))/100; + } + else if(battle_config.vit_penaly_type == 2) { + def1 -= (target_count - (battle_config.vit_penaly_count - 1))*battle_config.vit_penaly_num; + def2 -= (target_count - (battle_config.vit_penaly_count - 1))*battle_config.vit_penaly_num; + t_vit -= (target_count - (battle_config.vit_penaly_count - 1))*battle_config.vit_penaly_num; + } + if(def1 < 0) def1 = 0; + if(def2 < 1) def2 = 1; + if(t_vit < 1) t_vit = 1; + } + } + t_def = def2*8/10; + vitbonusmax = (t_vit/20)*(t_vit/20)-1; + if(battle_config.pet_defense_type) { + damage = damage - (def1 * battle_config.pet_defense_type) - t_def - ((vitbonusmax < 1)?0: rand()%(vitbonusmax+1) ); + } + else{ + damage = damage * (100 - def1) /100 - t_def - ((vitbonusmax < 1)?0: rand()%(vitbonusmax+1) ); + } + } + } + } + + // 0未満だった場合1に補正 + if(damage<1) damage=1; + + // 回避修正 + if(hitrate < 1000000) + hitrate = ( (hitrate>95)?95: ((hitrate<5)?5:hitrate) ); + if( hitrate < 1000000 && // 必中攻撃 + (t_sc_data != NULL && (t_sc_data[SC_SLEEP].timer!=-1 || // 睡眠は必中 + t_sc_data[SC_STAN].timer!=-1 || // スタンは必中 + t_sc_data[SC_FREEZE].timer!=-1 || (t_sc_data[SC_STONE].timer!=-1 && t_sc_data[SC_STONE].val2==0) ) ) ) // 凍結は必中 + hitrate = 1000000; + if(type == 0 && rand()%100 >= hitrate) { + damage = damage2 = 0; + dmg_lv = ATK_FLEE; + } else { + dmg_lv = ATK_DEF; + } + + + if(t_sc_data) { + int cardfix=100; + if(t_sc_data[SC_DEFENDER].timer != -1 && flag&BF_LONG) + cardfix=cardfix*(100-t_sc_data[SC_DEFENDER].val2)/100; + if(cardfix != 100) + damage=damage*cardfix/100; + } + if(damage < 0) damage = 0; + + // 属 性の適用 + if(skill_num != 0 || s_ele != 0 || !battle_config.pet_attack_attr_none) + damage=battle_attr_fix(damage, s_ele, battle_get_element(target) ); + + if(skill_num==PA_PRESSURE) /* プレッシャー 必中? */ + damage = 700+100*skill_lv; + + // インベナム修正 + if(skill_num==TF_POISON){ + damage = battle_attr_fix(damage + 15*skill_lv, s_ele, battle_get_element(target) ); + } + if(skill_num==MC_CARTREVOLUTION){ + damage = battle_attr_fix(damage, 0, battle_get_element(target) ); + } + + // 完全回避の判定 + if(battle_config.enemy_perfect_flee) { + if(skill_num == 0 && skill_lv >= 0 && tmd!=NULL && rand()%1000 < battle_get_flee2(target) ){ + damage=0; + type=0x0b; + dmg_lv = ATK_LUCKY; + } + } + +// if(def1 >= 1000000 && damage > 0) + if(t_mode&0x40 && damage > 0) + damage = 1; + + if(skill_num != CR_GRANDCROSS) + damage=battle_calc_damage(src,target,damage,div_,skill_num,skill_lv,flag); + + wd.damage=damage; + wd.damage2=0; + wd.type=type; + wd.div_=div_; + wd.amotion=battle_get_amotion(src); + if(skill_num == KN_AUTOCOUNTER) + wd.amotion >>= 1; + wd.dmotion=battle_get_dmotion(target); + wd.blewcount=blewcount; + wd.flag=flag; + wd.dmg_lv=dmg_lv; + + return wd; +} + +static struct Damage battle_calc_mob_weapon_attack( + struct block_list *src,struct block_list *target,int skill_num,int skill_lv,int wflag) +{ + struct map_session_data *tsd=NULL; + struct mob_data* md=(struct mob_data *)src,*tmd=NULL; + int hitrate,flee,cri = 0,atkmin,atkmax; + int luk,target_count = 1; + int def1 = battle_get_def(target); + int def2 = battle_get_def2(target); + int t_vit = battle_get_vit(target); + struct Damage wd; + int damage,damage2=0,type,div_,blewcount=skill_get_blewcount(skill_num,skill_lv); + int flag,skill,ac_flag = 0,dmg_lv = 0; + int t_mode=0,t_race=0,t_size=1,s_race=0,s_ele=0; + struct status_change *sc_data,*t_sc_data; + short *sc_count; + short *option, *opt1, *opt2; + + //return前の処理があるので情報出力部のみ変更 + if( src == NULL || target == NULL || md == NULL ){ + nullpo_info(NLP_MARK); + memset(&wd,0,sizeof(wd)); + return wd; + } + + s_race=battle_get_race(src); + s_ele=battle_get_attack_element(src); + sc_data=battle_get_sc_data(src); + sc_count=battle_get_sc_count(src); + option=battle_get_option(src); + opt1=battle_get_opt1(src); + opt2=battle_get_opt2(src); + + // ターゲット + if(target->type==BL_PC) + tsd=(struct map_session_data *)target; + else if(target->type==BL_MOB) + tmd=(struct mob_data *)target; + t_race=battle_get_race( target ); + t_size=battle_get_size( target ); + t_mode=battle_get_mode( target ); + t_sc_data=battle_get_sc_data( target ); + + if((skill_num == 0 || (target->type == BL_PC && battle_config.pc_auto_counter_type&2) || + (target->type == BL_MOB && battle_config.monster_auto_counter_type&2)) && skill_lv >= 0) { + if(skill_num != CR_GRANDCROSS && t_sc_data && t_sc_data[SC_AUTOCOUNTER].timer != -1) { + int dir = map_calc_dir(src,target->x,target->y),t_dir = battle_get_dir(target); + int dist = distance(src->x,src->y,target->x,target->y); + if(dist <= 0 || map_check_dir(dir,t_dir) ) { + memset(&wd,0,sizeof(wd)); + t_sc_data[SC_AUTOCOUNTER].val3 = 0; + t_sc_data[SC_AUTOCOUNTER].val4 = 1; + if(sc_data && sc_data[SC_AUTOCOUNTER].timer == -1) { + int range = battle_get_range(target); + if((target->type == BL_PC && ((struct map_session_data *)target)->status.weapon != 11 && dist <= range+1) || + (target->type == BL_MOB && range <= 3 && dist <= range+1) ) + t_sc_data[SC_AUTOCOUNTER].val3 = src->id; + } + return wd; + } + else ac_flag = 1; + } + } + flag=BF_SHORT|BF_WEAPON|BF_NORMAL; // 攻撃の種類の設定 + + // 回避率計算、回避判定は後で + flee = battle_get_flee(target); + if(battle_config.agi_penaly_type > 0 || battle_config.vit_penaly_type > 0) + target_count += battle_counttargeted(target,src,battle_config.agi_penaly_count_lv); + if(battle_config.agi_penaly_type > 0) { + if(target_count >= battle_config.agi_penaly_count) { + if(battle_config.agi_penaly_type == 1) + flee = (flee * (100 - (target_count - (battle_config.agi_penaly_count - 1))*battle_config.agi_penaly_num))/100; + else if(battle_config.agi_penaly_type == 2) + flee -= (target_count - (battle_config.agi_penaly_count - 1))*battle_config.agi_penaly_num; + if(flee < 1) flee = 1; + } + } + hitrate=battle_get_hit(src) - flee + 80; + + type=0; // normal + div_ = 1; // single attack + + luk=battle_get_luk(src); + + if(battle_config.enemy_str) + damage = battle_get_baseatk(src); + else + damage = 0; + if(skill_num==HW_MAGICCRASHER){ /* マジッククラッシャーはMATKで殴る */ + atkmin = battle_get_matk1(src); + atkmax = battle_get_matk2(src); + }else{ + atkmin = battle_get_atk(src); + atkmax = battle_get_atk2(src); + } + if(mob_db[md->class].range>3 ) + flag=(flag&~BF_RANGEMASK)|BF_LONG; + + if(atkmin > atkmax) atkmin = atkmax; + + if(sc_data != NULL && sc_data[SC_MAXIMIZEPOWER].timer!=-1 ){ // マキシマイズパワー + atkmin=atkmax; + } + + cri = battle_get_critical(src); + cri -= battle_get_luk(target) * 3; + if(battle_config.enemy_critical_rate != 100) { + cri = cri*battle_config.enemy_critical_rate/100; + if(cri < 1) + cri = 1; + } + if(t_sc_data != NULL && t_sc_data[SC_SLEEP].timer!=-1 ) // 睡眠中はクリティカルが倍に + cri <<=1; + + if(ac_flag) cri = 1000; + + if(skill_num == KN_AUTOCOUNTER) { + if(!(battle_config.monster_auto_counter_type&1)) + cri = 1000; + else + cri <<= 1; + } + + if(tsd && tsd->critical_def) + cri = cri * (100 - tsd->critical_def) / 100; + + if((skill_num == 0 || skill_num == KN_AUTOCOUNTER) && skill_lv >= 0 && battle_config.enemy_critical && (rand() % 1000) < cri) // 判定(スキルの場合は無視) + // 敵の判定 + { + damage += atkmax; + type = 0x0a; + } + else { + int vitbonusmax; + + if(atkmax > atkmin) + damage += atkmin + rand() % (atkmax-atkmin + 1); + else + damage += atkmin ; + // スキル修正1(攻撃力倍化系) + // オーバートラスト(+5% 〜 +25%),他攻撃系スキルの場合ここで補正 + // バッシュ,マグナムブレイク, + // ボーリングバッシュ,スピアブーメラン,ブランディッシュスピア,スピアスタッブ, + // メマーナイト,カートレボリューション + // ダブルストレイフィング,アローシャワー,チャージアロー, + // ソニックブロー + if(sc_data){ //状態異常中のダメージ追加 + if(sc_data[SC_OVERTHRUST].timer!=-1) // オーバートラスト + damage += damage*(5*sc_data[SC_OVERTHRUST].val1)/100; + if(sc_data[SC_TRUESIGHT].timer!=-1) // トゥルーサイト + damage += damage*(2*sc_data[SC_TRUESIGHT].val1)/100; + if(sc_data[SC_BERSERK].timer!=-1) // バーサーク + damage += damage*50/100; + } + + if(skill_num>0){ + int i; + if( (i=skill_get_pl(skill_num))>0 ) + s_ele=i; + + flag=(flag&~BF_SKILLMASK)|BF_SKILL; + switch( skill_num ){ + case SM_BASH: // バッシュ + damage = damage*(100+ 30*skill_lv)/100; + hitrate = (hitrate*(100+5*skill_lv))/100; + break; + case SM_MAGNUM: // マグナムブレイク + damage = damage*(5*skill_lv +(wflag)?65:115 )/100; + break; + case MC_MAMMONITE: // メマーナイト + damage = damage*(100+ 50*skill_lv)/100; + break; + case AC_DOUBLE: // ダブルストレイフィング + damage = damage*(180+ 20*skill_lv)/100; + div_=2; + flag=(flag&~BF_RANGEMASK)|BF_LONG; + break; + case AC_SHOWER: // アローシャワー + damage = damage*(75 + 5*skill_lv)/100; + flag=(flag&~BF_RANGEMASK)|BF_LONG; + break; + case AC_CHARGEARROW: // チャージアロー + damage = damage*150/100; + flag=(flag&~BF_RANGEMASK)|BF_LONG; + break; + case KN_PIERCE: // ピアース + damage = damage*(100+ 10*skill_lv)/100; + hitrate=hitrate*(100+5*skill_lv)/100; + div_=t_size+1; + damage*=div_; + break; + case KN_SPEARSTAB: // スピアスタブ + damage = damage*(100+ 15*skill_lv)/100; + break; + case KN_SPEARBOOMERANG: // スピアブーメラン + damage = damage*(100+ 50*skill_lv)/100; + flag=(flag&~BF_RANGEMASK)|BF_LONG; + break; + case KN_BRANDISHSPEAR: // ブランディッシュスピア + damage = damage*(100+ 20*skill_lv)/100; + if(skill_lv>3 && wflag==1) damage2+=damage/2; + if(skill_lv>6 && wflag==1) damage2+=damage/4; + if(skill_lv>9 && wflag==1) damage2+=damage/8; + if(skill_lv>6 && wflag==2) damage2+=damage/2; + if(skill_lv>9 && wflag==2) damage2+=damage/4; + if(skill_lv>9 && wflag==3) damage2+=damage/2; + damage +=damage2; + blewcount=0; + break; + case KN_BOWLINGBASH: // ボウリングバッシュ + damage = damage*(100+ 50*skill_lv)/100; + blewcount=0; + break; + case KN_AUTOCOUNTER: + if(battle_config.monster_auto_counter_type&1) + hitrate += 20; + else + hitrate = 1000000; + flag=(flag&~BF_SKILLMASK)|BF_NORMAL; + break; + case AS_SONICBLOW: // ソニックブロウ + damage = damage*(300+ 50*skill_lv)/100; + div_=8; + break; + case TF_SPRINKLESAND: // 砂まき + damage = damage*125/100; + break; + case MC_CARTREVOLUTION: // カートレボリューション + damage = (damage*150)/100; + break; + // 以下MOB + case NPC_COMBOATTACK: // 多段攻撃 + div_=skill_get_num(skill_num,skill_lv); + damage *= div_; + break; + case NPC_RANDOMATTACK: // ランダムATK攻撃 + damage = damage*(50+rand()%150)/100; + break; + // 属性攻撃(適当) + case NPC_WATERATTACK: + case NPC_GROUNDATTACK: + case NPC_FIREATTACK: + case NPC_WINDATTACK: + case NPC_POISONATTACK: + case NPC_HOLYATTACK: + case NPC_DARKNESSATTACK: + case NPC_TELEKINESISATTACK: + damage = damage*(100+25*(skill_lv-1))/100; + break; + case NPC_GUIDEDATTACK: + hitrate = 1000000; + break; + case NPC_RANGEATTACK: + flag=(flag&~BF_RANGEMASK)|BF_LONG; + break; + case NPC_PIERCINGATT: + flag=(flag&~BF_RANGEMASK)|BF_SHORT; + break; + case RG_BACKSTAP: // バックスタブ + damage = damage*(300+ 40*skill_lv)/100; + hitrate = 1000000; + break; + case RG_RAID: // サプライズアタック + damage = damage*(100+ 40*skill_lv)/100; + break; + case RG_INTIMIDATE: // インティミデイト + damage = damage*(100+ 30*skill_lv)/100; + break; + case CR_SHIELDCHARGE: // シールドチャージ + damage = damage*(100+ 20*skill_lv)/100; + flag=(flag&~BF_RANGEMASK)|BF_SHORT; + s_ele = 0; + break; + case CR_SHIELDBOOMERANG: // シールドブーメラン + damage = damage*(100+ 30*skill_lv)/100; + flag=(flag&~BF_RANGEMASK)|BF_LONG; + s_ele = 0; + break; + case CR_HOLYCROSS: // ホーリークロス + damage = damage*(100+ 35*skill_lv)/100; + div_=2; + break; + case CR_GRANDCROSS: + hitrate= 1000000; + break; + case AM_DEMONSTRATION: // デモンストレーション + damage = damage*(100+ 20*skill_lv)/100; + damage2 = damage2*(100+ 20*skill_lv)/100; + break; + case AM_ACIDTERROR: // アシッドテラー + damage = damage*(100+ 40*skill_lv)/100; + damage2 = damage2*(100+ 40*skill_lv)/100; + break; + case MO_FINGEROFFENSIVE: //指弾 + damage = damage * (100 + 50 * skill_lv) / 100; + div_ = 1; + break; + case MO_INVESTIGATE: // 発 勁 + if(def1 < 1000000) + damage = damage*(100+ 75*skill_lv)/100 * (def1 + def2)/100; + hitrate = 1000000; + s_ele = 0; + break; + case MO_EXTREMITYFIST: // 阿修羅覇鳳拳 + damage = damage * 8 + 250 + (skill_lv * 150); + hitrate = 1000000; + s_ele = 0; + break; + case MO_CHAINCOMBO: // 連打掌 + damage = damage*(150+ 50*skill_lv)/100; + div_=4; + break; + case BA_MUSICALSTRIKE: // ミュージカルストライク + damage = damage*(100+ 50 * skill_lv)/100; + flag=(flag&~BF_RANGEMASK)|BF_LONG; + break; + case DC_THROWARROW: // 矢撃ち + damage = damage*(100+ 50 * skill_lv)/100; + flag=(flag&~BF_RANGEMASK)|BF_LONG; + break; + case MO_COMBOFINISH: // 猛龍拳 + damage = damage*(240+ 60*skill_lv)/100; + break; + case CH_TIGERFIST: // 伏虎拳 + damage = damage*(100+ 20*skill_lv)/100; + break; + case CH_CHAINCRUSH: // 連柱崩撃 + damage = damage*(100+ 20*skill_lv)/100; + div_=skill_get_num(skill_num,skill_lv); + break; + case CH_PALMSTRIKE: // 猛虎硬派山 + damage = damage*(50+ 100*skill_lv)/100; + break; + case LK_SPIRALPIERCE: /* スパイラルピアース */ + damage = damage*(100+ 50*skill_lv)/100; //増加量が分からないので適当に + div_=5; + if(tsd) + tsd->canmove_tick = gettick() + 1000; + else if(tmd) + tmd->canmove_tick = gettick() + 1000; + break; + case LK_HEADCRUSH: /* ヘッドクラッシュ */ + damage = damage*(100+ 20*skill_lv)/100; + break; + case LK_JOINTBEAT: /* ジョイントビート */ + damage = damage*(50+ 10*skill_lv)/100; + break; + case ASC_METEORASSAULT: /* メテオアサルト */ + damage = damage*(40+ 40*skill_lv)/100; + break; + case SN_SHARPSHOOTING: /* シャープシューティング */ + damage += damage*(30*skill_lv)/100; + break; + case CG_ARROWVULCAN: /* アローバルカン */ + damage = damage*(160+40*skill_lv)/100; + div_=9; + break; + case AS_SPLASHER: /* ベナムスプラッシャー */ + damage = damage*(200+20*skill_lv)/100; + break; + } + } + + if( skill_num!=NPC_CRITICALSLASH ){ + // 対 象の防御力によるダメージの減少 + // ディバインプロテクション(ここでいいのかな?) + if ( skill_num != MO_INVESTIGATE && skill_num != MO_EXTREMITYFIST && skill_num != KN_AUTOCOUNTER && def1 < 1000000) { //DEF, VIT無視 + int t_def; + target_count = 1 + battle_counttargeted(target,src,battle_config.vit_penaly_count_lv); + if(battle_config.vit_penaly_type > 0) { + if(target_count >= battle_config.vit_penaly_count) { + if(battle_config.vit_penaly_type == 1) { + def1 = (def1 * (100 - (target_count - (battle_config.vit_penaly_count - 1))*battle_config.vit_penaly_num))/100; + def2 = (def2 * (100 - (target_count - (battle_config.vit_penaly_count - 1))*battle_config.vit_penaly_num))/100; + t_vit = (t_vit * (100 - (target_count - (battle_config.vit_penaly_count - 1))*battle_config.vit_penaly_num))/100; + } + else if(battle_config.vit_penaly_type == 2) { + def1 -= (target_count - (battle_config.vit_penaly_count - 1))*battle_config.vit_penaly_num; + def2 -= (target_count - (battle_config.vit_penaly_count - 1))*battle_config.vit_penaly_num; + t_vit -= (target_count - (battle_config.vit_penaly_count - 1))*battle_config.vit_penaly_num; + } + if(def1 < 0) def1 = 0; + if(def2 < 1) def2 = 1; + if(t_vit < 1) t_vit = 1; + } + } + t_def = def2*8/10; + if(battle_check_undead(s_race,battle_get_elem_type(src)) || s_race==6) + if(tsd && (skill=pc_checkskill(tsd,AL_DP)) > 0 ) + t_def += skill*3; + + vitbonusmax = (t_vit/20)*(t_vit/20)-1; + if(battle_config.monster_defense_type) { + damage = damage - (def1 * battle_config.monster_defense_type) - t_def - ((vitbonusmax < 1)?0: rand()%(vitbonusmax+1) ); + } + else{ + damage = damage * (100 - def1) /100 - t_def - ((vitbonusmax < 1)?0: rand()%(vitbonusmax+1) ); + } + } + } + } + + // 0未満だった場合1に補正 + if(damage<1) damage=1; + + // 回避修正 + if(hitrate < 1000000) + hitrate = ( (hitrate>95)?95: ((hitrate<5)?5:hitrate) ); + if( hitrate < 1000000 && // 必中攻撃 + (t_sc_data != NULL && (t_sc_data[SC_SLEEP].timer!=-1 || // 睡眠は必中 + t_sc_data[SC_STAN].timer!=-1 || // スタンは必中 + t_sc_data[SC_FREEZE].timer!=-1 || (t_sc_data[SC_STONE].timer!=-1 && t_sc_data[SC_STONE].val2==0) ) ) ) // 凍結は必中 + hitrate = 1000000; + if(type == 0 && rand()%100 >= hitrate) { + damage = damage2 = 0; + dmg_lv = ATK_FLEE; + } else { + dmg_lv = ATK_DEF; + } + + if(tsd){ + int cardfix=100,i; + cardfix=cardfix*(100-tsd->subele[s_ele])/100; // 属 性によるダメージ耐性 + cardfix=cardfix*(100-tsd->subrace[s_race])/100; // 種族によるダメージ耐性 + if(mob_db[md->class].mode & 0x20) + cardfix=cardfix*(100-tsd->subrace[10])/100; + else + cardfix=cardfix*(100-tsd->subrace[11])/100; + for(i=0;i<tsd->add_def_class_count;i++) { + if(tsd->add_def_classid[i] == md->class) { + cardfix=cardfix*(100-tsd->add_def_classrate[i])/100; + break; + } + } + if(flag&BF_LONG) + cardfix=cardfix*(100-tsd->long_attack_def_rate)/100; + if(flag&BF_SHORT) + cardfix=cardfix*(100-tsd->near_attack_def_rate)/100; + damage=damage*cardfix/100; + } + if(t_sc_data) { + int cardfix=100; + if(t_sc_data[SC_DEFENDER].timer != -1 && flag&BF_LONG) + cardfix=cardfix*(100-t_sc_data[SC_DEFENDER].val2)/100; + if(cardfix != 100) + damage=damage*cardfix/100; + } + if(t_sc_data && t_sc_data[SC_ASSUMPTIO].timer != -1){ //アシャンプティオ + if(!map[target->m].flag.pvp) + damage=damage/3; + else + damage=damage/2; + } + + if(damage < 0) damage = 0; + + // 属 性の適用 + if (!((battle_config.mob_ghostring_fix == 1) && + (battle_get_element(target) == 8) && + (target->type==BL_PC))) // [MouseJstr] + if(skill_num != 0 || s_ele != 0 || !battle_config.mob_attack_attr_none) + damage=battle_attr_fix(damage, s_ele, battle_get_element(target) ); + + if(sc_data && sc_data[SC_AURABLADE].timer!=-1) /* オーラブレード 必中 */ + damage += sc_data[SC_AURABLADE].val1 * 10; + if(skill_num==PA_PRESSURE) /* プレッシャー 必中? */ + damage = 700+100*skill_lv; + + // インベナム修正 + if(skill_num==TF_POISON){ + damage = battle_attr_fix(damage + 15*skill_lv, s_ele, battle_get_element(target) ); + } + if(skill_num==MC_CARTREVOLUTION){ + damage = battle_attr_fix(damage, 0, battle_get_element(target) ); + } + + // 完全回避の判定 + if(skill_num == 0 && skill_lv >= 0 && tsd!=NULL && rand()%1000 < battle_get_flee2(target) ){ + damage=0; + type=0x0b; + dmg_lv = ATK_LUCKY; + } + + if(battle_config.enemy_perfect_flee) { + if(skill_num == 0 && skill_lv >= 0 && tmd!=NULL && rand()%1000 < battle_get_flee2(target) ){ + damage=0; + type=0x0b; + dmg_lv = ATK_LUCKY; + } + } + +// if(def1 >= 1000000 && damage > 0) + if(t_mode&0x40 && damage > 0) + damage = 1; + + if( tsd && tsd->special_state.no_weapon_damage) + damage = 0; + + if(skill_num != CR_GRANDCROSS) + damage=battle_calc_damage(src,target,damage,div_,skill_num,skill_lv,flag); + + wd.damage=damage; + wd.damage2=0; + wd.type=type; + wd.div_=div_; + wd.amotion=battle_get_amotion(src); + if(skill_num == KN_AUTOCOUNTER) + wd.amotion >>= 1; + wd.dmotion=battle_get_dmotion(target); + wd.blewcount=blewcount; + wd.flag=flag; + wd.dmg_lv=dmg_lv; + return wd; +} +/* + * ========================================================================= + * PCの武器による攻撃 + *------------------------------------------------------------------------- + */ +static struct Damage battle_calc_pc_weapon_attack( + struct block_list *src,struct block_list *target,int skill_num,int skill_lv,int wflag) +{ + struct map_session_data *sd=(struct map_session_data *)src,*tsd=NULL; + struct mob_data *tmd=NULL; + int hitrate,flee,cri = 0,atkmin,atkmax; + int dex,luk,target_count = 1; + int def1 = battle_get_def(target); + int def2 = battle_get_def2(target); + int t_vit = battle_get_vit(target); + struct Damage wd; + int damage,damage2,damage3=0,damage4=0,type,div_,blewcount=skill_get_blewcount(skill_num,skill_lv); + int flag,skill,dmg_lv = 0; + int t_mode=0,t_race=0,t_size=1,s_race=7,s_ele=0; + struct status_change *sc_data,*t_sc_data; + short *sc_count; + short *option, *opt1, *opt2; + int atkmax_=0, atkmin_=0, s_ele_; //二刀流用 + int watk,watk_,cardfix,t_ele; + int da=0,i,t_class,ac_flag = 0; + int idef_flag=0,idef_flag_=0; + + //return前の処理があるので情報出力部のみ変更 + if( src == NULL || target == NULL || sd == NULL ){ + nullpo_info(NLP_MARK); + memset(&wd,0,sizeof(wd)); + return wd; + } + + + // アタッカー + s_race=battle_get_race(src); //種族 + s_ele=battle_get_attack_element(src); //属性 + s_ele_=battle_get_attack_element2(src); //左手属性 + sc_data=battle_get_sc_data(src); //ステータス異常 + sc_count=battle_get_sc_count(src); //ステータス異常の数 + option=battle_get_option(src); //鷹とかペコとかカートとか + opt1=battle_get_opt1(src); //石化、凍結、スタン、睡眠、暗闇 + opt2=battle_get_opt2(src); //毒、呪い、沈黙、暗闇? + + if(skill_num != CR_GRANDCROSS) //グランドクロスでないなら + sd->state.attack_type = BF_WEAPON; //攻撃タイプは武器攻撃 + + // ターゲット + if(target->type==BL_PC) //対象がPCなら + tsd=(struct map_session_data *)target; //tsdに代入(tmdはNULL) + else if(target->type==BL_MOB) //対象がMobなら + tmd=(struct mob_data *)target; //tmdに代入(tsdはNULL) + t_race=battle_get_race( target ); //対象の種族 + t_ele=battle_get_elem_type(target); //対象の属性 + t_size=battle_get_size( target ); //対象のサイズ + t_mode=battle_get_mode( target ); //対象のMode + t_sc_data=battle_get_sc_data( target ); //対象のステータス異常 + +//オートカウンター処理ここから + if((skill_num == 0 || (target->type == BL_PC && battle_config.pc_auto_counter_type&2) || + (target->type == BL_MOB && battle_config.monster_auto_counter_type&2)) && skill_lv >= 0) { + if(skill_num != CR_GRANDCROSS && t_sc_data && t_sc_data[SC_AUTOCOUNTER].timer != -1) { //グランドクロスでなく、対象がオートカウンター状態の場合 + int dir = map_calc_dir(src,target->x,target->y),t_dir = battle_get_dir(target); + int dist = distance(src->x,src->y,target->x,target->y); + if(dist <= 0 || map_check_dir(dir,t_dir) ) { //対象との距離が0以下、または対象の正面? + memset(&wd,0,sizeof(wd)); + t_sc_data[SC_AUTOCOUNTER].val3 = 0; + t_sc_data[SC_AUTOCOUNTER].val4 = 1; + if(sc_data && sc_data[SC_AUTOCOUNTER].timer == -1) { //自分がオートカウンター状態 + int range = battle_get_range(target); + if((target->type == BL_PC && ((struct map_session_data *)target)->status.weapon != 11 && dist <= range+1) || //対象がPCで武器が弓矢でなく射程内 + (target->type == BL_MOB && range <= 3 && dist <= range+1) ) //または対象がMobで射程が3以下で射程内 + t_sc_data[SC_AUTOCOUNTER].val3 = src->id; + } + return wd; //ダメージ構造体を返して終了 + } + else ac_flag = 1; + } + } +//オートカウンター処理ここまで + + flag=BF_SHORT|BF_WEAPON|BF_NORMAL; // 攻撃の種類の設定 + + // 回避率計算、回避判定は後で + flee = battle_get_flee(target); + if(battle_config.agi_penaly_type > 0 || battle_config.vit_penaly_type > 0) //AGI、VITペナルティ設定が有効 + target_count += battle_counttargeted(target,src,battle_config.agi_penaly_count_lv); //対象の数を算出 + if(battle_config.agi_penaly_type > 0) { + if(target_count >= battle_config.agi_penaly_count) { //ペナルティ設定より対象が多い + if(battle_config.agi_penaly_type == 1) //回避率がagi_penaly_num%ずつ減少 + flee = (flee * (100 - (target_count - (battle_config.agi_penaly_count - 1))*battle_config.agi_penaly_num))/100; + else if(battle_config.agi_penaly_type == 2) //回避率がagi_penaly_num分減少 + flee -= (target_count - (battle_config.agi_penaly_count - 1))*battle_config.agi_penaly_num; + if(flee < 1) flee = 1; //回避率は最低でも1 + } + } + hitrate=battle_get_hit(src) - flee + 80; //命中率計算 + + type=0; // normal + div_ = 1; // single attack + + dex=battle_get_dex(src); //DEX + luk=battle_get_luk(src); //LUK + watk = battle_get_atk(src); //ATK + watk_ = battle_get_atk_(src); //ATK左手 + + if(skill_num==HW_MAGICCRASHER){ /* マジッククラッシャーはMATKで殴る */ + damage = damage2 = battle_get_matk1(src); //damega,damega2初登場、base_atkの取得 + }else{ + damage = damage2 = battle_get_baseatk(&sd->bl); //damega,damega2初登場、base_atkの取得 + } + atkmin = atkmin_ = dex; //最低ATKはDEXで初期化? + sd->state.arrow_atk = 0; //arrow_atk初期化 + if(sd->equip_index[9] >= 0 && sd->inventory_data[sd->equip_index[9]]) + atkmin = atkmin*(80 + sd->inventory_data[sd->equip_index[9]]->wlv*20)/100; + if(sd->equip_index[8] >= 0 && sd->inventory_data[sd->equip_index[8]]) + atkmin_ = atkmin_*(80 + sd->inventory_data[sd->equip_index[8]]->wlv*20)/100; + if(sd->status.weapon == 11) { //武器が弓矢の場合 + atkmin = watk * ((atkmin<watk)? atkmin:watk)/100; //弓用最低ATK計算 + flag=(flag&~BF_RANGEMASK)|BF_LONG; //遠距離攻撃フラグを有効 + if(sd->arrow_ele > 0) //属性矢なら属性を矢の属性に変更 + s_ele = sd->arrow_ele; + sd->state.arrow_atk = 1; //arrow_atk有効化 + } + + // サイズ修正 + // ペコ騎乗していて、槍で攻撃した場合は中型のサイズ修正を100にする + // ウェポンパーフェクション,ドレイクC + if(((sd->special_state.no_sizefix) || (pc_isriding(sd) && (sd->status.weapon==4 || sd->status.weapon==5) && t_size==1) || skill_num == MO_EXTREMITYFIST)){ //ペコ騎乗していて、槍で中型を攻撃 + atkmax = watk; + atkmax_ = watk_; + } else { + atkmax = (watk * sd->atkmods[ t_size ]) / 100; + atkmin = (atkmin * sd->atkmods[ t_size ]) / 100; + atkmax_ = (watk_ * sd->atkmods_[ t_size ]) / 100; + atkmin_ = (atkmin_ * sd->atkmods[ t_size ]) / 100; + } + if( (sc_data != NULL && sc_data[SC_WEAPONPERFECTION].timer!=-1) || (sd->special_state.no_sizefix)) { // ウェポンパーフェクション || ドレイクカード + atkmax = watk; + atkmax_ = watk_; + } + + if(atkmin > atkmax && !(sd->state.arrow_atk)) atkmin = atkmax; //弓は最低が上回る場合あり + if(atkmin_ > atkmax_) atkmin_ = atkmax_; + + if(sc_data != NULL && sc_data[SC_MAXIMIZEPOWER].timer!=-1 ){ // マキシマイズパワー + atkmin=atkmax; + atkmin_=atkmax_; + } + + //ダブルアタック判定 + if(sd->weapontype1 == 0x01) { + if(skill_num == 0 && skill_lv >= 0 && (skill = pc_checkskill(sd,TF_DOUBLE)) > 0) + da = (rand()%100 < (skill*5)) ? 1:0; + } + + //三段掌 + if(skill_num == 0 && skill_lv >= 0 && (skill = pc_checkskill(sd,MO_TRIPLEATTACK)) > 0 && sd->status.weapon <= 16 && !sd->state.arrow_atk) { + da = (rand()%100 < (30 - skill)) ? 2:0; + } + + if(sd->double_rate > 0 && da == 0 && skill_num == 0 && skill_lv >= 0) + da = (rand()%100 < sd->double_rate) ? 1:0; + + // 過剰精錬ボーナス + if(sd->overrefine>0 ) + damage+=(rand()%sd->overrefine)+1; + if(sd->overrefine_>0 ) + damage2+=(rand()%sd->overrefine_)+1; + + if(da == 0){ //ダブルアタックが発動していない + // クリティカル計算 + cri = battle_get_critical(src); + + if(sd->state.arrow_atk) + cri += sd->arrow_cri; + if(sd->status.weapon == 16) + // カタールの場合、クリティカルを倍に + cri <<=1; + cri -= battle_get_luk(target) * 3; + if(t_sc_data != NULL && t_sc_data[SC_SLEEP].timer!=-1 ) // 睡眠中はクリティカルが倍に + cri <<=1; + if(ac_flag) cri = 1000; + + if(skill_num == KN_AUTOCOUNTER) { + if(!(battle_config.pc_auto_counter_type&1)) + cri = 1000; + else + cri <<= 1; + } + + if(skill_num == SN_SHARPSHOOTING && rand()%100 < 50) + cri = 1000; + } + + if(tsd && tsd->critical_def) + cri = cri * (100-tsd->critical_def) / 100; + + if(da == 0 && (skill_num==0 || skill_num == KN_AUTOCOUNTER || skill_num == SN_SHARPSHOOTING) && skill_lv >= 0 && //ダブルアタックが発動していない + (rand() % 1000) < cri) // 判定(スキルの場合は無視) + { + damage += atkmax; + damage2 += atkmax_; + if(sd->atk_rate != 100) { + damage = (damage * sd->atk_rate)/100; + damage2 = (damage2 * sd->atk_rate)/100; + } + if(sd->state.arrow_atk) + damage += sd->arrow_atk; + type = 0x0a; + +/* if(def1 < 1000000) { + if(sd->def_ratio_atk_ele & (1<<t_ele) || sd->def_ratio_atk_race & (1<<t_race)) { + damage = (damage * (def1 + def2))/100; + idef_flag = 1; + } + if(sd->def_ratio_atk_ele_ & (1<<t_ele) || sd->def_ratio_atk_race_ & (1<<t_race)) { + damage2 = (damage2 * (def1 + def2))/100; + idef_flag_ = 1; + } + if(t_mode & 0x20) { + if(!idef_flag && sd->def_ratio_atk_race & (1<<10)) { + damage = (damage * (def1 + def2))/100; + idef_flag = 1; + } + if(!idef_flag_ && sd->def_ratio_atk_race_ & (1<<10)) { + damage2 = (damage2 * (def1 + def2))/100; + idef_flag_ = 1; + } + } + else { + if(!idef_flag && sd->def_ratio_atk_race & (1<<11)) { + damage = (damage * (def1 + def2))/100; + idef_flag = 1; + } + if(!idef_flag_ && sd->def_ratio_atk_race_ & (1<<11)) { + damage2 = (damage2 * (def1 + def2))/100; + idef_flag_ = 1; + } + } + }*/ + } + else { + int vitbonusmax; + + if(atkmax > atkmin) + damage += atkmin + rand() % (atkmax-atkmin + 1); + else + damage += atkmin ; + if(atkmax_ > atkmin_) + damage2 += atkmin_ + rand() % (atkmax_-atkmin_ + 1); + else + damage2 += atkmin_ ; + if(sd->atk_rate != 100) { + damage = (damage * sd->atk_rate)/100; + damage2 = (damage2 * sd->atk_rate)/100; + } + + if(sd->state.arrow_atk) { + if(sd->arrow_atk > 0) + damage += rand()%(sd->arrow_atk+1); + hitrate += sd->arrow_hit; + } + + if(skill_num != MO_INVESTIGATE && def1 < 1000000) { + if(sd->def_ratio_atk_ele & (1<<t_ele) || sd->def_ratio_atk_race & (1<<t_race)) { + damage = (damage * (def1 + def2))/100; + idef_flag = 1; + } + if(sd->def_ratio_atk_ele_ & (1<<t_ele) || sd->def_ratio_atk_race_ & (1<<t_race)) { + damage2 = (damage2 * (def1 + def2))/100; + idef_flag_ = 1; + } + if(t_mode & 0x20) { + if(!idef_flag && sd->def_ratio_atk_race & (1<<10)) { + damage = (damage * (def1 + def2))/100; + idef_flag = 1; + } + if(!idef_flag_ && sd->def_ratio_atk_race_ & (1<<10)) { + damage2 = (damage2 * (def1 + def2))/100; + idef_flag_ = 1; + } + } + else { + if(!idef_flag && sd->def_ratio_atk_race & (1<<11)) { + damage = (damage * (def1 + def2))/100; + idef_flag = 1; + } + if(!idef_flag_ && sd->def_ratio_atk_race_ & (1<<11)) { + damage2 = (damage2 * (def1 + def2))/100; + idef_flag_ = 1; + } + } + } + + // スキル修正1(攻撃力倍化系) + // オーバートラスト(+5% 〜 +25%),他攻撃系スキルの場合ここで補正 + // バッシュ,マグナムブレイク, + // ボーリングバッシュ,スピアブーメラン,ブランディッシュスピア,スピアスタッブ, + // メマーナイト,カートレボリューション + // ダブルストレイフィング,アローシャワー,チャージアロー, + // ソニックブロー + if(sc_data){ //状態異常中のダメージ追加 + if(sc_data[SC_OVERTHRUST].timer!=-1){ // オーバートラスト + damage += damage*(5*sc_data[SC_OVERTHRUST].val1)/100; + damage2 += damage2*(5*sc_data[SC_OVERTHRUST].val1)/100; + } + if(sc_data[SC_TRUESIGHT].timer!=-1){ // トゥルーサイト + damage += damage*(2*sc_data[SC_TRUESIGHT].val1)/100; + damage2 += damage2*(2*sc_data[SC_TRUESIGHT].val1)/100; + } + if(sc_data[SC_BERSERK].timer!=-1){ // バーサーク + damage += damage*50/100; + damage2 += damage2*50/100; + } + } + + if(skill_num>0){ + int i; + if( (i=skill_get_pl(skill_num))>0 ) + s_ele=s_ele_=i; + + flag=(flag&~BF_SKILLMASK)|BF_SKILL; + switch( skill_num ){ + case SM_BASH: // バッシュ + damage = damage*(100+ 30*skill_lv)/100; + damage2 = damage2*(100+ 30*skill_lv)/100; + hitrate = (hitrate*(100+5*skill_lv))/100; + break; + case SM_MAGNUM: // マグナムブレイク + damage = damage*(5*skill_lv +(wflag)?65:115 )/100; + damage2 = damage2*(5*skill_lv +(wflag)?65:115 )/100; + break; + case MC_MAMMONITE: // メマーナイト + damage = damage*(100+ 50*skill_lv)/100; + damage2 = damage2*(100+ 50*skill_lv)/100; + break; + case AC_DOUBLE: // ダブルストレイフィング + if(!sd->state.arrow_atk && sd->arrow_atk > 0) { + int arr = rand()%(sd->arrow_atk+1); + damage += arr; + damage2 += arr; + } + damage = damage*(180+ 20*skill_lv)/100; + damage2 = damage2*(180+ 20*skill_lv)/100; + div_=2; + if(sd->arrow_ele > 0) { + s_ele = sd->arrow_ele; + s_ele_ = sd->arrow_ele; + } + flag=(flag&~BF_RANGEMASK)|BF_LONG; + sd->state.arrow_atk = 1; + break; + case AC_SHOWER: // アローシャワー + if(!sd->state.arrow_atk && sd->arrow_atk > 0) { + int arr = rand()%(sd->arrow_atk+1); + damage += arr; + damage2 += arr; + } + damage = damage*(75 + 5*skill_lv)/100; + damage2 = damage2*(75 + 5*skill_lv)/100; + if(sd->arrow_ele > 0) { + s_ele = sd->arrow_ele; + s_ele_ = sd->arrow_ele; + } + flag=(flag&~BF_RANGEMASK)|BF_LONG; + sd->state.arrow_atk = 1; + break; + case AC_CHARGEARROW: // チャージアロー + if(!sd->state.arrow_atk && sd->arrow_atk > 0) { + int arr = rand()%(sd->arrow_atk+1); + damage += arr; + damage2 += arr; + } + damage = damage*150/100; + damage2 = damage2*150/100; + if(sd->arrow_ele > 0) { + s_ele = sd->arrow_ele; + s_ele_ = sd->arrow_ele; + } + flag=(flag&~BF_RANGEMASK)|BF_LONG; + sd->state.arrow_atk = 1; + break; + case KN_PIERCE: // ピアース + damage = damage*(100+ 10*skill_lv)/100; + damage2 = damage2*(100+ 10*skill_lv)/100; + hitrate=hitrate*(100+5*skill_lv)/100; + div_=t_size+1; + damage*=div_; + damage2*=div_; + break; + case KN_SPEARSTAB: // スピアスタブ + damage = damage*(100+ 15*skill_lv)/100; + damage2 = damage2*(100+ 15*skill_lv)/100; + break; + case KN_SPEARBOOMERANG: // スピアブーメラン + damage = damage*(100+ 50*skill_lv)/100; + damage2 = damage2*(100+ 50*skill_lv)/100; + flag=(flag&~BF_RANGEMASK)|BF_LONG; + break; + case KN_BRANDISHSPEAR: // ブランディッシュスピア + damage = damage*(100+ 20*skill_lv)/100; + damage2 = damage2*(100+ 20*skill_lv)/100; + if(skill_lv>3 && wflag==1) damage3+=damage/2; + if(skill_lv>6 && wflag==1) damage3+=damage/4; + if(skill_lv>9 && wflag==1) damage3+=damage/8; + if(skill_lv>6 && wflag==2) damage3+=damage/2; + if(skill_lv>9 && wflag==2) damage3+=damage/4; + if(skill_lv>9 && wflag==3) damage3+=damage/2; + damage +=damage3; + if(skill_lv>3 && wflag==1) damage4+=damage2/2; + if(skill_lv>6 && wflag==1) damage4+=damage2/4; + if(skill_lv>9 && wflag==1) damage4+=damage2/8; + if(skill_lv>6 && wflag==2) damage4+=damage2/2; + if(skill_lv>9 && wflag==2) damage4+=damage2/4; + if(skill_lv>9 && wflag==3) damage4+=damage2/2; + damage2 +=damage4; + blewcount=0; + break; + case KN_BOWLINGBASH: // ボウリングバッシュ + damage = damage*(100+ 50*skill_lv)/100; + damage2 = damage2*(100+ 50*skill_lv)/100; + blewcount=0; + break; + case KN_AUTOCOUNTER: + if(battle_config.pc_auto_counter_type&1) + hitrate += 20; + else + hitrate = 1000000; + flag=(flag&~BF_SKILLMASK)|BF_NORMAL; + break; + case AS_SONICBLOW: // ソニックブロウ + hitrate+=30; // hitrate +30, thanks to midas + damage = damage*(300+ 50*skill_lv)/100; + damage2 = damage2*(300+ 50*skill_lv)/100; + div_=8; + break; + case TF_SPRINKLESAND: // 砂まき + damage = damage*125/100; + damage2 = damage2*125/100; + break; + case MC_CARTREVOLUTION: // カートレボリューション + if(sd->cart_max_weight > 0 && sd->cart_weight > 0) { + damage = (damage*(150 + pc_checkskill(sd,BS_WEAPONRESEARCH) + (sd->cart_weight*100/sd->cart_max_weight) ) )/100; + damage2 = (damage2*(150 + pc_checkskill(sd,BS_WEAPONRESEARCH) + (sd->cart_weight*100/sd->cart_max_weight) ) )/100; + } + else { + damage = (damage*150)/100; + damage2 = (damage2*150)/100; + } + break; + // 以下MOB + case NPC_COMBOATTACK: // 多段攻撃 + div_=skill_get_num(skill_num,skill_lv); + damage *= div_; + damage2 *= div_; + break; + case NPC_RANDOMATTACK: // ランダムATK攻撃 + damage = damage*(50+rand()%150)/100; + damage2 = damage2*(50+rand()%150)/100; + break; + // 属性攻撃(適当) + case NPC_WATERATTACK: + case NPC_GROUNDATTACK: + case NPC_FIREATTACK: + case NPC_WINDATTACK: + case NPC_POISONATTACK: + case NPC_HOLYATTACK: + case NPC_DARKNESSATTACK: + case NPC_TELEKINESISATTACK: + damage = damage*(100+25*skill_lv)/100; + damage2 = damage2*(100+25*skill_lv)/100; + break; + case NPC_GUIDEDATTACK: + hitrate = 1000000; + break; + case NPC_RANGEATTACK: + flag=(flag&~BF_RANGEMASK)|BF_LONG; + break; + case NPC_PIERCINGATT: + flag=(flag&~BF_RANGEMASK)|BF_SHORT; + break; + case RG_BACKSTAP: // バックスタブ + if(battle_config.backstab_bow_penalty == 1 && sd->status.weapon == 11){ + damage = (damage*(300+ 40*skill_lv)/100)/2; + damage2 = (damage2*(300+ 40*skill_lv)/100)/2; + }else{ + damage = damage*(300+ 40*skill_lv)/100; + damage2 = damage2*(300+ 40*skill_lv)/100; + } + hitrate = 1000000; + break; + case RG_RAID: // サプライズアタック + damage = damage*(100+ 40*skill_lv)/100; + damage2 = damage2*(100+ 40*skill_lv)/100; + break; + case RG_INTIMIDATE: // インティミデイト + damage = damage*(100+ 30*skill_lv)/100; + damage2 = damage2*(100+ 30*skill_lv)/100; + break; + case CR_SHIELDCHARGE: // シールドチャージ + damage = damage*(100+ 20*skill_lv)/100; + damage2 = damage2*(100+ 20*skill_lv)/100; + flag=(flag&~BF_RANGEMASK)|BF_SHORT; + s_ele = 0; + break; + case CR_SHIELDBOOMERANG: // シールドブーメラン + damage = damage*(100+ 30*skill_lv)/100; + damage2 = damage2*(100+ 30*skill_lv)/100; + flag=(flag&~BF_RANGEMASK)|BF_LONG; + s_ele = 0; + break; + case CR_HOLYCROSS: // ホーリークロス + damage = damage*(100+ 35*skill_lv)/100; + damage2 = damage2*(100+ 35*skill_lv)/100; + div_=2; + break; + case CR_GRANDCROSS: + hitrate= 1000000; + break; + case AM_DEMONSTRATION: // デモンストレーション + damage = damage*(100+ 20*skill_lv)/100; + damage2 = damage2*(100+ 20*skill_lv)/100; + break; + case AM_ACIDTERROR: // アシッドテラー + damage = damage*(100+ 40*skill_lv)/100; + damage2 = damage2*(100+ 40*skill_lv)/100; + break; + case MO_FINGEROFFENSIVE: //指弾 + if(battle_config.finger_offensive_type == 0) { + damage = damage * (100 + 50 * skill_lv) / 100 * sd->spiritball_old; + damage2 = damage2 * (100 + 50 * skill_lv) / 100 * sd->spiritball_old; + div_ = sd->spiritball_old; + } + else { + damage = damage * (100 + 50 * skill_lv) / 100; + damage2 = damage2 * (100 + 50 * skill_lv) / 100; + div_ = 1; + } + break; + case MO_INVESTIGATE: // 発 勁 + if(def1 < 1000000) { + damage = damage*(100+ 75*skill_lv)/100 * (def1 + def2)/100; + damage2 = damage2*(100+ 75*skill_lv)/100 * (def1 + def2)/100; + } + hitrate = 1000000; + s_ele = 0; + s_ele_ = 0; + break; + case MO_EXTREMITYFIST: // 阿修羅覇鳳拳 + damage = damage * (8 + ((sd->status.sp)/10)) + 250 + (skill_lv * 150); + damage2 = damage2 * (8 + ((sd->status.sp)/10)) + 250 + (skill_lv * 150); + sd->status.sp = 0; + clif_updatestatus(sd,SP_SP); + hitrate = 1000000; + s_ele = 0; + s_ele_ = 0; + break; + case MO_CHAINCOMBO: // 連打掌 + damage = damage*(150+ 50*skill_lv)/100; + damage2 = damage2*(150+ 50*skill_lv)/100; + div_=4; + break; + case MO_COMBOFINISH: // 猛龍拳 + damage = damage*(240+ 60*skill_lv)/100; + damage2 = damage2*(240+ 60*skill_lv)/100; + break; + case BA_MUSICALSTRIKE: // ミュージカルストライク + if(!sd->state.arrow_atk && sd->arrow_atk > 0) { + int arr = rand()%(sd->arrow_atk+1); + damage += arr; + damage2 += arr; + } + damage = damage*(100+ 50 * skill_lv)/100; + damage2 = damage2*(100+ 50 * skill_lv)/100; + if(sd->arrow_ele > 0) { + s_ele = sd->arrow_ele; + s_ele_ = sd->arrow_ele; + } + flag=(flag&~BF_RANGEMASK)|BF_LONG; + sd->state.arrow_atk = 1; + break; + case DC_THROWARROW: // 矢撃ち + if(!sd->state.arrow_atk && sd->arrow_atk > 0) { + int arr = rand()%(sd->arrow_atk+1); + damage += arr; + damage2 += arr; + } + damage = damage*(100+ 50 * skill_lv)/100; + damage2 = damage2*(100+ 50 * skill_lv)/100; + if(sd->arrow_ele > 0) { + s_ele = sd->arrow_ele; + s_ele_ = sd->arrow_ele; + } + flag=(flag&~BF_RANGEMASK)|BF_LONG; + sd->state.arrow_atk = 1; + break; + case CH_TIGERFIST: // 伏虎拳 + damage = damage*(100+ 20*skill_lv)/100; + damage2 = damage2*(100+ 20*skill_lv)/100; + break; + case CH_CHAINCRUSH: // 連柱崩撃 + damage = damage*(100+ 20*skill_lv)/100; + damage2 = damage2*(100+ 20*skill_lv)/100; + div_=skill_get_num(skill_num,skill_lv); + break; + case CH_PALMSTRIKE: // 猛虎硬派山 + damage = damage*(50+ 100*skill_lv)/100; + damage2 = damage2*(50+ 100*skill_lv)/100; + break; + case LK_SPIRALPIERCE: /* スパイラルピアース */ + damage = damage*(100+ 50*skill_lv)/100; //増加量が分からないので適当に + damage2 = damage2*(100+ 50*skill_lv)/100; //増加量が分からないので適当に + div_=5; + if(tsd) + tsd->canmove_tick = gettick() + 1000; + else if(tmd) + tmd->canmove_tick = gettick() + 1000; + break; + case LK_HEADCRUSH: /* ヘッドクラッシュ */ + damage = damage*(100+ 20*skill_lv)/100; + damage2 = damage2*(100+ 20*skill_lv)/100; + break; + case LK_JOINTBEAT: /* ジョイントビート */ + damage = damage*(50+ 10*skill_lv)/100; + damage2 = damage2*(50+ 10*skill_lv)/100; + break; + case ASC_METEORASSAULT: /* メテオアサルト */ + damage = damage*(40+ 40*skill_lv)/100; + damage2 = damage2*(40+ 40*skill_lv)/100; + break; + case SN_SHARPSHOOTING: /* シャープシューティング */ + damage += damage*(30*skill_lv)/100; + damage2 += damage2*(30*skill_lv)/100; + break; + case CG_ARROWVULCAN: /* アローバルカン */ + damage = damage*(160+40*skill_lv)/100; + damage2 = damage2*(160+40*skill_lv)/100; + div_=9; + break; + case AS_SPLASHER: /* ベナムスプラッシャー */ + damage = damage*(200+20*skill_lv+20*pc_checkskill(sd,AS_POISONREACT))/100; + damage2 = damage2*(200+20*skill_lv+20*pc_checkskill(sd,AS_POISONREACT))/100; + break; + case PA_SACRIFICE: + if(sd){ + int hp, mhp, damage3; + hp = battle_get_hp(src); + mhp = battle_get_max_hp(src); + damage3 = mhp*((skill_lv/2)+(50/100))/100; + damage = (((skill_lv*15)+90)/100)*damage3/100; + damage2 = (((skill_lv*15)+90)/100)*damage3/100; + } + break; + case ASC_BREAKER: // -- moonsoul (special damage for ASC_BREAKER skill) + if(sd){ + int damage3; + int mdef1=battle_get_mdef(target); + int mdef2=battle_get_mdef2(target); + int imdef_flag=0; + + damage = ((damage * 5) + (skill_lv * battle_get_int(src) * 5) + rand()%500 + 500) /2; + damage2 = ((damage2 * 5) + (skill_lv * battle_get_int(src) * 5) + rand()%500 + 500) /2; + damage3 = damage; + hitrate = 1000000; + + if(sd->ignore_mdef_ele & (1<<t_ele) || sd->ignore_mdef_race & (1<<t_race)) + imdef_flag = 1; + if(t_mode & 0x20) { + if(sd->ignore_mdef_race & (1<<10)) + imdef_flag = 1; + } + else { + if(sd->ignore_mdef_race & (1<<11)) + imdef_flag = 1; + } + if(!imdef_flag){ + if(battle_config.magic_defense_type) { + damage3 = damage3 - (mdef1 * battle_config.magic_defense_type) - mdef2; + } + else{ + damage3 = (damage3*(100-mdef1))/100 - mdef2; + } + } + + if(damage3<1) + damage3=1; + + damage3=battle_attr_fix(damage2,s_ele_, battle_get_element(target) ); + } + break; + } + } + if(da == 2) { //三段掌が発動しているか + type = 0x08; + div_ = 255; //三段掌用に… + damage = damage * (100 + 20 * pc_checkskill(sd, MO_TRIPLEATTACK)) / 100; + } + + if( skill_num!=NPC_CRITICALSLASH ){ + // 対 象の防御力によるダメージの減少 + // ディバインプロテクション(ここでいいのかな?) + if ( skill_num != MO_INVESTIGATE && skill_num != MO_EXTREMITYFIST && skill_num != KN_AUTOCOUNTER && def1 < 1000000) { //DEF, VIT無視 + int t_def; + target_count = 1 + battle_counttargeted(target,src,battle_config.vit_penaly_count_lv); + if(battle_config.vit_penaly_type > 0) { + if(target_count >= battle_config.vit_penaly_count) { + if(battle_config.vit_penaly_type == 1) { + def1 = (def1 * (100 - (target_count - (battle_config.vit_penaly_count - 1))*battle_config.vit_penaly_num))/100; + def2 = (def2 * (100 - (target_count - (battle_config.vit_penaly_count - 1))*battle_config.vit_penaly_num))/100; + t_vit = (t_vit * (100 - (target_count - (battle_config.vit_penaly_count - 1))*battle_config.vit_penaly_num))/100; + } + else if(battle_config.vit_penaly_type == 2) { + def1 -= (target_count - (battle_config.vit_penaly_count - 1))*battle_config.vit_penaly_num; + def2 -= (target_count - (battle_config.vit_penaly_count - 1))*battle_config.vit_penaly_num; + t_vit -= (target_count - (battle_config.vit_penaly_count - 1))*battle_config.vit_penaly_num; + } + if(def1 < 0) def1 = 0; + if(def2 < 1) def2 = 1; + if(t_vit < 1) t_vit = 1; + } + } + t_def = def2*8/10; + vitbonusmax = (t_vit/20)*(t_vit/20)-1; + if(sd->ignore_def_ele & (1<<t_ele) || sd->ignore_def_race & (1<<t_race)) + idef_flag = 1; + if(sd->ignore_def_ele_ & (1<<t_ele) || sd->ignore_def_race_ & (1<<t_race)) + idef_flag_ = 1; + if(t_mode & 0x20) { + if(sd->ignore_def_race & (1<<10)) + idef_flag = 1; + if(sd->ignore_def_race_ & (1<<10)) + idef_flag_ = 1; + } + else { + if(sd->ignore_def_race & (1<<11)) + idef_flag = 1; + if(sd->ignore_def_race_ & (1<<11)) + idef_flag_ = 1; + } + + if(!idef_flag){ + if(battle_config.player_defense_type) { + damage = damage - (def1 * battle_config.player_defense_type) - t_def - ((vitbonusmax < 1)?0: rand()%(vitbonusmax+1) ); + } + else{ + damage = damage * (100 - def1) /100 - t_def - ((vitbonusmax < 1)?0: rand()%(vitbonusmax+1) ); + } + } + if(!idef_flag_){ + if(battle_config.player_defense_type) { + damage2 = damage2 - (def1 * battle_config.player_defense_type) - t_def - ((vitbonusmax < 1)?0: rand()%(vitbonusmax+1) ); + } + else{ + damage2 = damage2 * (100 - def1) /100 - t_def - ((vitbonusmax < 1)?0: rand()%(vitbonusmax+1) ); + } + } + } + } + } + // 精錬ダメージの追加 + if( skill_num != MO_INVESTIGATE && skill_num != MO_EXTREMITYFIST) { //DEF, VIT無視 + damage += battle_get_atk2(src); + damage2 += battle_get_atk_2(src); + } + if(skill_num == CR_SHIELDBOOMERANG) { + if(sd->equip_index[8] >= 0) { + int index = sd->equip_index[8]; + if(sd->inventory_data[index] && sd->inventory_data[index]->type == 5) { + damage += sd->inventory_data[index]->weight/10; + damage += sd->status.inventory[index].refine * pc_getrefinebonus(0,1); + } + } + } + if(skill_num == LK_SPIRALPIERCE) { /* スパイラルピアース */ + if(sd->equip_index[9] >= 0) { //重量で追加ダメージらしいのでシールドブーメランを参考に追加 + int index = sd->equip_index[9]; + if(sd->inventory_data[index] && sd->inventory_data[index]->type == 4) { + damage += (int)(double)(sd->inventory_data[index]->weight*(0.8*skill_lv*4/10)); + damage += sd->status.inventory[index].refine * pc_getrefinebonus(0,1); + } + } + } + + // 0未満だった場合1に補正 + if(damage<1) damage=1; + if(damage2<1) damage2=1; + + // スキル修正2(修練系) + // 修練ダメージ(右手のみ) ソニックブロー時は別処理(1撃に付き1/8適応) + if( skill_num != MO_INVESTIGATE && skill_num != MO_EXTREMITYFIST && skill_num != CR_GRANDCROSS) { //修練ダメージ無視 + damage = battle_addmastery(sd,target,damage,0); + damage2 = battle_addmastery(sd,target,damage2,1); + } + + if(sd->perfect_hit > 0) { + if(rand()%100 < sd->perfect_hit) + hitrate = 1000000; + } + + // 回避修正 + hitrate = (hitrate<5)?5:hitrate; + if( hitrate < 1000000 && // 必中攻撃 + (t_sc_data != NULL && (t_sc_data[SC_SLEEP].timer!=-1 || // 睡眠は必中 + t_sc_data[SC_STAN].timer!=-1 || // スタンは必中 + t_sc_data[SC_FREEZE].timer!=-1 || (t_sc_data[SC_STONE].timer!=-1 && t_sc_data[SC_STONE].val2==0) ) ) ) // 凍結は必中 + hitrate = 1000000; + if(type == 0 && rand()%100 >= hitrate) { + damage = damage2 = 0; + dmg_lv = ATK_FLEE; + } else { + dmg_lv = ATK_DEF; + } + // スキル修正3(武器研究) + if( (skill=pc_checkskill(sd,BS_WEAPONRESEARCH)) > 0) { + damage+= skill*2; + damage2+= skill*2; + } + //Advanced Katar Research by zanetheinsane + if(sd->weapontype1 == 0x10 || sd->weapontype2 == 0x10){ + if((skill = pc_checkskill(sd,ASC_KATAR)) > 0) { + damage += (damage*((skill*2)+10)) / 100 ; + } + } + +//スキルによるダメージ補正ここまで + +//カードによるダメージ追加処理ここから + cardfix=100; + if(!sd->state.arrow_atk) { //弓矢以外 + if(!battle_config.left_cardfix_to_right) { //左手カード補正設定無し + cardfix=cardfix*(100+sd->addrace[t_race])/100; // 種族によるダメージ修正 + cardfix=cardfix*(100+sd->addele[t_ele])/100; // 属性によるダメージ修正 + cardfix=cardfix*(100+sd->addsize[t_size])/100; // サイズによるダメージ修正 + } + else { + cardfix=cardfix*(100+sd->addrace[t_race]+sd->addrace_[t_race])/100; // 種族によるダメージ修正(左手による追加あり) + cardfix=cardfix*(100+sd->addele[t_ele]+sd->addele_[t_ele])/100; // 属性によるダメージ修正(左手による追加あり) + cardfix=cardfix*(100+sd->addsize[t_size]+sd->addsize_[t_size])/100; // サイズによるダメージ修正(左手による追加あり) + } + } + else { //弓矢 + cardfix=cardfix*(100+sd->addrace[t_race]+sd->arrow_addrace[t_race])/100; // 種族によるダメージ修正(弓矢による追加あり) + cardfix=cardfix*(100+sd->addele[t_ele]+sd->arrow_addele[t_ele])/100; // 属性によるダメージ修正(弓矢による追加あり) + cardfix=cardfix*(100+sd->addsize[t_size]+sd->arrow_addsize[t_size])/100; // サイズによるダメージ修正(弓矢による追加あり) + } + if(t_mode & 0x20) { //ボス + if(!sd->state.arrow_atk) { //弓矢攻撃以外なら + if(!battle_config.left_cardfix_to_right) //左手カード補正設定無し + cardfix=cardfix*(100+sd->addrace[10])/100; //ボスモンスターに追加ダメージ + else //左手カード補正設定あり + cardfix=cardfix*(100+sd->addrace[10]+sd->addrace_[10])/100; //ボスモンスターに追加ダメージ(左手による追加あり) + } + else //弓矢攻撃 + cardfix=cardfix*(100+sd->addrace[10]+sd->arrow_addrace[10])/100; //ボスモンスターに追加ダメージ(弓矢による追加あり) + } + else { //ボスじゃない + if(!sd->state.arrow_atk) { //弓矢攻撃以外 + if(!battle_config.left_cardfix_to_right) //左手カード補正設定無し + cardfix=cardfix*(100+sd->addrace[11])/100; //ボス以外モンスターに追加ダメージ + else //左手カード補正設定あり + cardfix=cardfix*(100+sd->addrace[11]+sd->addrace_[11])/100; //ボス以外モンスターに追加ダメージ(左手による追加あり) + } + else + cardfix=cardfix*(100+sd->addrace[11]+sd->arrow_addrace[11])/100; //ボス以外モンスターに追加ダメージ(弓矢による追加あり) + } + //特定Class用補正処理(少女の日記→ボンゴン用?) + t_class = battle_get_class(target); + for(i=0;i<sd->add_damage_class_count;i++) { + if(sd->add_damage_classid[i] == t_class) { + cardfix=cardfix*(100+sd->add_damage_classrate[i])/100; + break; + } + } + if(skill_num != CR_GRANDCROSS || !battle_config.gx_cardfix) + damage=damage*cardfix/100; //カード補正によるダメージ増加 +//カードによるダメージ増加処理ここまで + +//カードによるダメージ追加処理(左手)ここから + cardfix=100; + if(!battle_config.left_cardfix_to_right) { //左手カード補正設定無し + cardfix=cardfix*(100+sd->addrace_[t_race])/100; // 種族によるダメージ修正左手 + cardfix=cardfix*(100+sd->addele_[t_ele])/100; // 属 性によるダメージ修正左手 + cardfix=cardfix*(100+sd->addsize_[t_size])/100; // サイズによるダメージ修正左手 + if(t_mode & 0x20) //ボス + cardfix=cardfix*(100+sd->addrace_[10])/100; //ボスモンスターに追加ダメージ左手 + else + cardfix=cardfix*(100+sd->addrace_[11])/100; //ボス以外モンスターに追加ダメージ左手 + } + //特定Class用補正処理左手(少女の日記→ボンゴン用?) + for(i=0;i<sd->add_damage_class_count_;i++) { + if(sd->add_damage_classid_[i] == t_class) { + cardfix=cardfix*(100+sd->add_damage_classrate_[i])/100; + break; + } + } + if(skill_num != CR_GRANDCROSS) damage2=damage2*cardfix/100; //カード補正による左手ダメージ増加 +//カードによるダメージ増加処理(左手)ここまで + +// -- moonsoul (cardfix for magic damage portion of ASC_BREAKER) + if(skill_num == ASC_BREAKER) + damage3 = damage3 * cardfix / 100; + +//カードによるダメージ減衰処理ここから + if(tsd){ //対象がPCの場合 + cardfix=100; + cardfix=cardfix*(100-tsd->subrace[s_race])/100; // 種族によるダメージ耐性 + cardfix=cardfix*(100-tsd->subele[s_ele])/100; // 属性によるダメージ耐性 + if(battle_get_mode(src) & 0x20) + cardfix=cardfix*(100-tsd->subrace[10])/100; //ボスからの攻撃はダメージ減少 + else + cardfix=cardfix*(100-tsd->subrace[11])/100; //ボス以外からの攻撃はダメージ減少 + //特定Class用補正処理左手(少女の日記→ボンゴン用?) + for(i=0;i<tsd->add_def_class_count;i++) { + if(tsd->add_def_classid[i] == sd->status.class) { + cardfix=cardfix*(100-tsd->add_def_classrate[i])/100; + break; + } + } + if(flag&BF_LONG) + cardfix=cardfix*(100-tsd->long_attack_def_rate)/100; //遠距離攻撃はダメージ減少(ホルンCとか) + if(flag&BF_SHORT) + cardfix=cardfix*(100-tsd->near_attack_def_rate)/100; //近距離攻撃はダメージ減少(該当無し?) + damage=damage*cardfix/100; //カード補正によるダメージ減少 + damage2=damage2*cardfix/100; //カード補正による左手ダメージ減少 + } +//カードによるダメージ減衰処理ここまで + +//対象にステータス異常がある場合のダメージ減算処理ここから + if(t_sc_data) { + cardfix=100; + if(t_sc_data[SC_DEFENDER].timer != -1 && flag&BF_LONG) //ディフェンダー状態で遠距離攻撃 + cardfix=cardfix*(100-t_sc_data[SC_DEFENDER].val2)/100; //ディフェンダーによる減衰 + if(cardfix != 100) { + damage=damage*cardfix/100; //ディフェンダー補正によるダメージ減少 + damage2=damage2*cardfix/100; //ディフェンダー補正による左手ダメージ減少 + } + if(t_sc_data[SC_ASSUMPTIO].timer != -1){ //アスムプティオ + if(!map[target->m].flag.pvp){ + damage=damage/3; + damage2=damage2/3; + }else{ + damage=damage/2; + damage2=damage2/2; + } + } + } +//対象にステータス異常がある場合のダメージ減算処理ここまで + + if(damage < 0) damage = 0; + if(damage2 < 0) damage2 = 0; + + // 属 性の適用 + damage=battle_attr_fix(damage,s_ele, battle_get_element(target) ); + damage2=battle_attr_fix(damage2,s_ele_, battle_get_element(target) ); + + // 星のかけら、気球の適用 + damage += sd->star; + damage2 += sd->star_; + damage += sd->spiritball*3; + damage2 += sd->spiritball*3; + + if(sc_data && sc_data[SC_AURABLADE].timer!=-1){ /* オーラブレード 必中 */ + damage += sc_data[SC_AURABLADE].val1 * 10; + damage2 += sc_data[SC_AURABLADE].val1 * 10; + } + if(skill_num==PA_PRESSURE){ /* プレッシャー 必中? */ + damage = 700+100*skill_lv; + damage2 = 700+100*skill_lv; + } + + // >二刀流の左右ダメージ計算誰かやってくれぇぇぇぇえええ! + // >map_session_data に左手ダメージ(atk,atk2)追加して + // >pc_calcstatus()でやるべきかな? + // map_session_data に左手武器(atk,atk2,ele,star,atkmods)追加して + // pc_calcstatus()でデータを入力しています + + //左手のみ武器装備 + if(sd->weapontype1 == 0 && sd->weapontype2 > 0) { + damage = damage2; + damage2 = 0; + } + // 右手、左手修練の適用 + if(sd->status.weapon > 16) {// 二刀流か? + int dmg = damage, dmg2 = damage2; + // 右手修練(60% 〜 100%) 右手全般 + skill = pc_checkskill(sd,AS_RIGHT); + damage = damage * (50 + (skill * 10))/100; + if(dmg > 0 && damage < 1) damage = 1; + // 左手修練(40% 〜 80%) 左手全般 + skill = pc_checkskill(sd,AS_LEFT); + damage2 = damage2 * (30 + (skill * 10))/100; + if(dmg2 > 0 && damage2 < 1) damage2 = 1; + } + else //二刀流でなければ左手ダメージは0 + damage2 = 0; + + // 右手,短剣のみ + if(da == 1) { //ダブルアタックが発動しているか + div_ = 2; + damage += damage; + type = 0x08; + } + + if(sd->status.weapon == 16) { + // カタール追撃ダメージ + skill = pc_checkskill(sd,TF_DOUBLE); + damage2 = damage * (1 + (skill * 2))/100; + if(damage > 0 && damage2 < 1) damage2 = 1; + } + + // インベナム修正 + if(skill_num==TF_POISON){ + damage = battle_attr_fix(damage + 15*skill_lv, s_ele, battle_get_element(target) ); + } + if(skill_num==MC_CARTREVOLUTION){ + damage = battle_attr_fix(damage, 0, battle_get_element(target) ); + } + + // 完全回避の判定 + if(skill_num == 0 && skill_lv >= 0 && tsd!=NULL && div_ < 255 && rand()%1000 < battle_get_flee2(target) ){ + damage=damage2=0; + type=0x0b; + dmg_lv = ATK_LUCKY; + } + + // 対象が完全回避をする設定がONなら + if(battle_config.enemy_perfect_flee) { + if(skill_num == 0 && skill_lv >= 0 && tmd!=NULL && div_ < 255 && rand()%1000 < battle_get_flee2(target) ) { + damage=damage2=0; + type=0x0b; + dmg_lv = ATK_LUCKY; + } + } + + //MobのModeに頑強フラグが立っているときの処理 + if(t_mode&0x40){ + if(damage > 0) + damage = 1; + if(damage2 > 0) + damage2 = 1; + } + + //bNoWeaponDamage(設定アイテム無し?)でグランドクロスじゃない場合はダメージが0 + if( tsd && tsd->special_state.no_weapon_damage && skill_num != CR_GRANDCROSS) + damage = damage2 = 0; + + if(skill_num != CR_GRANDCROSS && (damage > 0 || damage2 > 0) ) { + if(damage2<1) // ダメージ最終修正 + damage=battle_calc_damage(src,target,damage,div_,skill_num,skill_lv,flag); + else if(damage<1) // 右手がミス? + damage2=battle_calc_damage(src,target,damage2,div_,skill_num,skill_lv,flag); + else { // 両 手/カタールの場合はちょっと計算ややこしい + int d1=damage+damage2,d2=damage2; + damage=battle_calc_damage(src,target,damage+damage2,div_,skill_num,skill_lv,flag); + damage2=(d2*100/d1)*damage/100; + if(damage > 1 && damage2 < 1) damage2=1; + damage-=damage2; + } + } + + /* For executioner card [Valaris] */ + if(src->type == BL_PC && sd->random_attack_increase_add > 0 && sd->random_attack_increase_per > 0 && skill_num == 0 ){ + if(rand()%100 < sd->random_attack_increase_per){ + if(damage >0) damage*=sd->random_attack_increase_add/100; + if(damage2 >0) damage2*=sd->random_attack_increase_add/100; + } + } + /* End addition */ + +// -- moonsoul (final combination of phys, mag damage for ASC_BREAKER) + if(skill_num == ASC_BREAKER) { + damage += damage3; + damage2 += damage3; + } + + wd.damage=damage; + wd.damage2=damage2; + wd.type=type; + wd.div_=div_; + wd.amotion=battle_get_amotion(src); + if(skill_num == KN_AUTOCOUNTER) + wd.amotion >>= 1; + wd.dmotion=battle_get_dmotion(target); + wd.blewcount=blewcount; + wd.flag=flag; + wd.dmg_lv=dmg_lv; + + return wd; +} + +/*========================================== + * 武器ダメージ計算 + *------------------------------------------ + */ +struct Damage battle_calc_weapon_attack( + struct block_list *src,struct block_list *target,int skill_num,int skill_lv,int wflag) +{ + struct Damage wd; + + //return前の処理があるので情報出力部のみ変更 + if (src == NULL || target == NULL) { + nullpo_info(NLP_MARK); + memset(&wd,0,sizeof(wd)); + return wd; + } + + if(target->type == BL_PET) + memset(&wd,0,sizeof(wd)); + + else if(src->type == BL_PC) + wd = battle_calc_pc_weapon_attack(src,target,skill_num,skill_lv,wflag); // weapon breaking [Valaris] + else if(src->type == BL_MOB) + wd = battle_calc_mob_weapon_attack(src,target,skill_num,skill_lv,wflag); + else if(src->type == BL_PET) + wd = battle_calc_pet_weapon_attack(src,target,skill_num,skill_lv,wflag); + else + memset(&wd,0,sizeof(wd)); + + if(battle_config.equipment_breaking && src->type==BL_PC && (wd.damage > 0 || wd.damage2 > 0)) { + struct map_session_data *sd=(struct map_session_data *)src; + if(sd->status.weapon && sd->status.weapon!=11) { + int breakrate=1; + if(target->type == BL_PC && sd->sc_data[SC_MELTDOWN].timer!=-1){ + breakrate+=100*sd->sc_data[SC_MELTDOWN].val1; + if(rand()%10000 < breakrate*battle_config.equipment_break_rate/100 || breakrate >= 10000) + pc_breakweapon((struct map_session_data *)target); + } + if(sd->sc_data[SC_OVERTHRUST].timer!=-1) + breakrate+=20*sd->sc_data[SC_OVERTHRUST].val1; + if(wd.type==0x0a) + breakrate*=2; + if(rand()%10000 < breakrate*battle_config.equipment_break_rate/100 || breakrate >= 10000) { + pc_breakweapon(sd); + memset(&wd,0,sizeof(wd)); + } + } + } + + if (battle_config.equipment_breaking && target->type == BL_PC && (wd.damage > 0 || wd.damage2 > 0)) { + int breakrate=1; + if(src->type==BL_PC && ((struct map_session_data *)src)->sc_data[SC_MELTDOWN].timer!=-1) breakrate+=70*((struct map_session_data *)src)->sc_data[SC_MELTDOWN].val1; + if (wd.type==0x0a) + breakrate*=2; + if (rand()%10000 < breakrate*battle_config.equipment_break_rate/100 || breakrate >= 10000) { + pc_breakarmor((struct map_session_data *)target); + } + } + + return wd; +} + +/*========================================== + * 魔法ダメージ計算 + *------------------------------------------ + */ +struct Damage battle_calc_magic_attack( + struct block_list *bl,struct block_list *target,int skill_num,int skill_lv,int flag) + { + int mdef1=battle_get_mdef(target); + int mdef2=battle_get_mdef2(target); + int matk1,matk2,damage=0,div_=1,blewcount=skill_get_blewcount(skill_num,skill_lv),rdamage = 0; + struct Damage md; + int aflag; + int normalmagic_flag=1; + int ele=0,race=7,t_ele=0,t_race=7,t_mode = 0,cardfix,t_class,i; + struct map_session_data *sd=NULL,*tsd=NULL; + struct mob_data *tmd = NULL; + + + //return前の処理があるので情報出力部のみ変更 + if( bl == NULL || target == NULL ){ + nullpo_info(NLP_MARK); + memset(&md,0,sizeof(md)); + return md; + } + + if(target->type == BL_PET) { + memset(&md,0,sizeof(md)); + return md; + } + + matk1=battle_get_matk1(bl); + matk2=battle_get_matk2(bl); + ele = skill_get_pl(skill_num); + race = battle_get_race(bl); + t_ele = battle_get_elem_type(target); + t_race = battle_get_race(target); + t_mode = battle_get_mode(target); + +#define MATK_FIX( a,b ) { matk1=matk1*(a)/(b); matk2=matk2*(a)/(b); } + + if( bl->type==BL_PC && (sd=(struct map_session_data *)bl) ){ + sd->state.attack_type = BF_MAGIC; + if(sd->matk_rate != 100) + MATK_FIX(sd->matk_rate,100); + sd->state.arrow_atk = 0; + } + if( target->type==BL_PC ) + tsd=(struct map_session_data *)target; + else if( target->type==BL_MOB ) + tmd=(struct mob_data *)target; + + aflag=BF_MAGIC|BF_LONG|BF_SKILL; + + if(skill_num > 0){ + switch(skill_num){ // 基本ダメージ計算(スキルごとに処理) + // ヒールor聖体 + case AL_HEAL: + case PR_BENEDICTIO: + damage = skill_calc_heal(bl,skill_lv)/2; + normalmagic_flag=0; + break; + case PR_ASPERSIO: /* アスペルシオ */ + damage = 40; //固定ダメージ + normalmagic_flag=0; + break; + case PR_SANCTUARY: // サンクチュアリ + damage = (skill_lv>6)?388:skill_lv*50; + normalmagic_flag=0; + blewcount|=0x10000; + break; + case ALL_RESURRECTION: + case PR_TURNUNDEAD: // 攻撃リザレクションとターンアンデッド + if(target->type != BL_PC && battle_check_undead(t_race,t_ele)){ + int hp, mhp, thres; + hp = battle_get_hp(target); + mhp = battle_get_max_hp(target); + thres = (skill_lv * 20) + battle_get_luk(bl)+ + battle_get_int(bl) + battle_get_lv(bl)+ + ((200 - hp * 200 / mhp)); + if(thres > 700) thres = 700; +// if(battle_config.battle_log) +// printf("ターンアンデッド! 確率%d ‰(千分率)\n", thres); + if(rand()%1000 < thres && !(t_mode&0x20)) // 成功 + damage = hp; + else // 失敗 + damage = battle_get_lv(bl) + battle_get_int(bl) + skill_lv * 10; + } + normalmagic_flag=0; + break; + + case MG_NAPALMBEAT: // ナパームビート(分散計算込み) + MATK_FIX(70+ skill_lv*10,100); + if(flag>0){ + MATK_FIX(1,flag); + }else { + if(battle_config.error_log) + printf("battle_calc_magic_attack(): napam enemy count=0 !\n"); + } + break; + case MG_FIREBALL: // ファイヤーボール + { + const int drate[]={100,90,70}; + if(flag>2) + matk1=matk2=0; + else + MATK_FIX( (95+skill_lv*5)*drate[flag] ,10000 ); + } + break; + case MG_FIREWALL: // ファイヤーウォール +/* + if( (t_ele!=3 && !battle_check_undead(t_race,t_ele)) || target->type==BL_PC ) //PCは火属性でも飛ぶ?そもそもダメージ受ける? + blewcount |= 0x10000; + else + blewcount = 0; +*/ + if((t_ele==3 || battle_check_undead(t_race,t_ele)) && target->type!=BL_PC) + blewcount = 0; + else + blewcount |= 0x10000; + MATK_FIX( 1,2 ); + break; + case MG_THUNDERSTORM: // サンダーストーム + MATK_FIX( 80,100 ); + break; + case MG_FROSTDIVER: // フロストダイバ + MATK_FIX( 100+skill_lv*10, 100); + break; + case WZ_FROSTNOVA: // フロストダイバ + MATK_FIX( ((100+skill_lv*10)*(2/3)), 100); + break; + case WZ_FIREPILLAR: // ファイヤーピラー + if(mdef1 < 1000000) + mdef1=mdef2=0; // MDEF無視 + MATK_FIX( 1,5 ); + matk1+=50; + matk2+=50; + break; + case WZ_SIGHTRASHER: + MATK_FIX( 100+skill_lv*20, 100); + break; + case WZ_METEOR: + case WZ_JUPITEL: // ユピテルサンダー + break; + case WZ_VERMILION: // ロードオブバーミリオン + MATK_FIX( skill_lv*20+80, 100 ); + break; + case WZ_WATERBALL: // ウォーターボール + matk1+= skill_lv*30; + matk2+= skill_lv*30; + break; + case WZ_STORMGUST: // ストームガスト + MATK_FIX( skill_lv*40+100 ,100 ); + blewcount|=0x10000; + break; + case AL_HOLYLIGHT: // ホーリーライト + MATK_FIX( 125,100 ); + break; + case AL_RUWACH: + MATK_FIX( 145,100 ); + break; + case HW_NAPALMVULCAN: // ナパームビート(分散計算込み) + MATK_FIX(70+ skill_lv*10,100); + if(flag>0){ + MATK_FIX(1,flag); + }else { + if(battle_config.error_log) + printf("battle_calc_magic_attack(): napalmvulcan enemy count=0 !\n"); + } + break; + } + } + + if(normalmagic_flag){ // 一般魔法ダメージ計算 + int imdef_flag=0; + if(matk1>matk2) + damage= matk2+rand()%(matk1-matk2+1); + else + damage= matk2; + if(sd) { + if(sd->ignore_mdef_ele & (1<<t_ele) || sd->ignore_mdef_race & (1<<t_race)) + imdef_flag = 1; + if(t_mode & 0x20) { + if(sd->ignore_mdef_race & (1<<10)) + imdef_flag = 1; + } + else { + if(sd->ignore_mdef_race & (1<<11)) + imdef_flag = 1; + } + } + if(!imdef_flag){ + if(battle_config.magic_defense_type) { + damage = damage - (mdef1 * battle_config.magic_defense_type) - mdef2; + } + else{ + damage = (damage*(100-mdef1))/100 - mdef2; + } + } + + if(damage<1) + damage=1; + } + + if(sd) { + cardfix=100; + cardfix=cardfix*(100+sd->magic_addrace[t_race])/100; + cardfix=cardfix*(100+sd->magic_addele[t_ele])/100; + if(t_mode & 0x20) + cardfix=cardfix*(100+sd->magic_addrace[10])/100; + else + cardfix=cardfix*(100+sd->magic_addrace[11])/100; + t_class = battle_get_class(target); + for(i=0;i<sd->add_magic_damage_class_count;i++) { + if(sd->add_magic_damage_classid[i] == t_class) { + cardfix=cardfix*(100+sd->add_magic_damage_classrate[i])/100; + break; + } + } + damage=damage*cardfix/100; + } + + if( tsd ){ + int s_class = battle_get_class(bl); + cardfix=100; + cardfix=cardfix*(100-tsd->subele[ele])/100; // 属 性によるダメージ耐性 + cardfix=cardfix*(100-tsd->subrace[race])/100; // 種族によるダメージ耐性 + cardfix=cardfix*(100-tsd->magic_subrace[race])/100; + if(battle_get_mode(bl) & 0x20) + cardfix=cardfix*(100-tsd->magic_subrace[10])/100; + else + cardfix=cardfix*(100-tsd->magic_subrace[11])/100; + for(i=0;i<tsd->add_mdef_class_count;i++) { + if(tsd->add_mdef_classid[i] == s_class) { + cardfix=cardfix*(100-tsd->add_mdef_classrate[i])/100; + break; + } + } + cardfix=cardfix*(100-tsd->magic_def_rate)/100; + damage=damage*cardfix/100; + } + if(damage < 0) damage = 0; + + damage=battle_attr_fix(damage, ele, battle_get_element(target) ); // 属 性修正 + + if(skill_num == CR_GRANDCROSS) { // グランドクロス + struct Damage wd; + wd=battle_calc_weapon_attack(bl,target,skill_num,skill_lv,flag); + damage = (damage + wd.damage) * (100 + 40*skill_lv)/100; + if(battle_config.gx_dupele) damage=battle_attr_fix(damage, ele, battle_get_element(target) ); //属性2回かかる + if(bl==target) damage=damage/2; //反動は半分 + } + + div_=skill_get_num( skill_num,skill_lv ); + + if(div_>1 && skill_num != WZ_VERMILION) + damage*=div_; + +// if(mdef1 >= 1000000 && damage > 0) + if(t_mode&0x40 && damage > 0) + damage = 1; + + if( tsd && tsd->special_state.no_magic_damage) { + if (battle_config.gtb_pvp_only != 0) { // [MouseJstr] + if ((map[target->m].flag.pvp || map[target->m].flag.gvg) && target->type==BL_PC) + damage = (damage * (100 - battle_config.gtb_pvp_only)) / 100; + } else + damage=0; // 黄 金蟲カード(魔法ダメージ0) + } + + damage=battle_calc_damage(bl,target,damage,div_,skill_num,skill_lv,aflag); // 最終修正 + + /* magic_damage_return by [AppleGirl] and [Valaris] */ + if( target->type==BL_PC && tsd && tsd->magic_damage_return > 0 ){ + rdamage += damage * tsd->magic_damage_return / 100; + if(rdamage < 1) rdamage = 1; + clif_damage(target,bl,gettick(),0,0,rdamage,0,0,0); + battle_damage(target,bl,rdamage,0); + } + /* end magic_damage_return */ + + md.damage=damage; + md.div_=div_; + md.amotion=battle_get_amotion(bl); + md.dmotion=battle_get_dmotion(target); + md.damage2=0; + md.type=0; + md.blewcount=blewcount; + md.flag=aflag; + + return md; +} + +/*========================================== + * その他ダメージ計算 + *------------------------------------------ + */ +struct Damage battle_calc_misc_attack( + struct block_list *bl,struct block_list *target,int skill_num,int skill_lv,int flag) +{ + int int_=battle_get_int(bl); +// int luk=battle_get_luk(bl); + int dex=battle_get_dex(bl); + int skill,ele,race,cardfix; + struct map_session_data *sd=NULL,*tsd=NULL; + int damage=0,div_=1,blewcount=skill_get_blewcount(skill_num,skill_lv); + struct Damage md; + int damagefix=1; + + int aflag=BF_MISC|BF_LONG|BF_SKILL; + + //return前の処理があるので情報出力部のみ変更 + if( bl == NULL || target == NULL ){ + nullpo_info(NLP_MARK); + memset(&md,0,sizeof(md)); + return md; + } + + if(target->type == BL_PET) { + memset(&md,0,sizeof(md)); + return md; + } + + if( bl->type == BL_PC && (sd=(struct map_session_data *)bl) ) { + sd->state.attack_type = BF_MISC; + sd->state.arrow_atk = 0; + } + + if( target->type==BL_PC ) + tsd=(struct map_session_data *)target; + + switch(skill_num){ + + case HT_LANDMINE: // ランドマイン + damage=skill_lv*(dex+75)*(100+int_)/100; + break; + + case HT_BLASTMINE: // ブラストマイン + damage=skill_lv*(dex/2+50)*(100+int_)/100; + break; + + case HT_CLAYMORETRAP: // クレイモアートラップ + damage=skill_lv*(dex/2+75)*(100+int_)/100; + break; + + case HT_BLITZBEAT: // ブリッツビート + if( sd==NULL || (skill = pc_checkskill(sd,HT_STEELCROW)) <= 0) + skill=0; + damage=(dex/10+int_/2+skill*3+40)*2; + if(flag > 1) + damage /= flag; + break; + + case TF_THROWSTONE: // 石投げ + damage=30; + damagefix=0; + break; + + case BA_DISSONANCE: // 不協和音 + damage=(skill_lv)*20+pc_checkskill(sd,BA_MUSICALLESSON)*3; + break; + + case NPC_SELFDESTRUCTION: // 自爆 + damage=battle_get_hp(bl)-(bl==target?1:0); + damagefix=0; + break; + + case NPC_SMOKING: // タバコを吸う + damage=3; + damagefix=0; + break; + + case NPC_DARKBREATH: + { + struct status_change *sc_data = battle_get_sc_data(target); + int hitrate=battle_get_hit(bl) - battle_get_flee(target) + 80; + hitrate = ( (hitrate>95)?95: ((hitrate<5)?5:hitrate) ); + if(sc_data && (sc_data[SC_SLEEP].timer!=-1 || sc_data[SC_STAN].timer!=-1 || + sc_data[SC_FREEZE].timer!=-1 || (sc_data[SC_STONE].timer!=-1 && sc_data[SC_STONE].val2==0) ) ) + hitrate = 1000000; + if(rand()%100 < hitrate) { + damage = 500 + (skill_lv-1)*1000 + rand()%1000; + if(damage > 9999) damage = 9999; + } + } + break; + case SN_FALCONASSAULT: /* ファルコンアサルト */ + skill = pc_checkskill(sd,HT_BLITZBEAT); + damage=(100+50*skill_lv+(dex/10+int_/2+skill*3+40)*2); + break; + } + + ele = skill_get_pl(skill_num); + race = battle_get_race(bl); + + if(damagefix){ + if(damage<1 && skill_num != NPC_DARKBREATH) + damage=1; + + if( tsd ){ + cardfix=100; + cardfix=cardfix*(100-tsd->subele[ele])/100; // 属性によるダメージ耐性 + cardfix=cardfix*(100-tsd->subrace[race])/100; // 種族によるダメージ耐性 + cardfix=cardfix*(100-tsd->misc_def_rate)/100; + damage=damage*cardfix/100; + } + if(damage < 0) damage = 0; + damage=battle_attr_fix(damage, ele, battle_get_element(target) ); // 属性修正 + } + + div_=skill_get_num( skill_num,skill_lv ); + if(div_>1) + damage*=div_; + + if(damage > 0 && (damage < div_ || (battle_get_def(target) >= 1000000 && battle_get_mdef(target) >= 1000000) ) ) { + damage = div_; + } + + damage=battle_calc_damage(bl,target,damage,div_,skill_num,skill_lv,aflag); // 最終修正 + + md.damage=damage; + md.div_=div_; + md.amotion=battle_get_amotion(bl); + md.dmotion=battle_get_dmotion(target); + md.damage2=0; + md.type=0; + md.blewcount=blewcount; + md.flag=aflag; + return md; + +} +/*========================================== + * ダメージ計算一括処理用 + *------------------------------------------ + */ +struct Damage battle_calc_attack( int attack_type, + struct block_list *bl,struct block_list *target,int skill_num,int skill_lv,int flag) +{ + struct Damage d; + switch(attack_type){ + case BF_WEAPON: + return battle_calc_weapon_attack(bl,target,skill_num,skill_lv,flag); + case BF_MAGIC: + return battle_calc_magic_attack(bl,target,skill_num,skill_lv,flag); + case BF_MISC: + return battle_calc_misc_attack(bl,target,skill_num,skill_lv,flag); + default: + if(battle_config.error_log) + printf("battle_calc_attack: unknwon attack type ! %d\n",attack_type); + break; + } + return d; +} +/*========================================== + * 通常攻撃処理まとめ + *------------------------------------------ + */ +int battle_weapon_attack( struct block_list *src,struct block_list *target, + unsigned int tick,int flag) +{ + struct map_session_data *sd=NULL; + struct status_change *sc_data = battle_get_sc_data(src),*t_sc_data=battle_get_sc_data(target); + short *opt1; + int race = 7, ele = 0; + int damage,rdamage = 0; + struct Damage wd; + + nullpo_retr(0, src); + nullpo_retr(0, target); + + if(src->type == BL_PC) + sd = (struct map_session_data *)src; + + if(src->prev == NULL || target->prev == NULL) + return 0; + if(src->type == BL_PC && pc_isdead(sd)) + return 0; + if(target->type == BL_PC && pc_isdead((struct map_session_data *)target)) + return 0; + + opt1=battle_get_opt1(src); + if(opt1 && *opt1 > 0) { + battle_stopattack(src); + return 0; + } + if(sc_data && sc_data[SC_BLADESTOP].timer!=-1){ + battle_stopattack(src); + return 0; + } + + race = battle_get_race(target); + ele = battle_get_elem_type(target); + if(battle_check_target(src,target,BCT_ENEMY) > 0 && + battle_check_range(src,target,0)){ + // 攻撃対象となりうるので攻撃 + if(sd && sd->status.weapon == 11) { + if(sd->equip_index[10] >= 0) { + if(battle_config.arrow_decrement) + pc_delitem(sd,sd->equip_index[10],1,0); + } + else { + clif_arrow_fail(sd,0); + return 0; + } + } + if(flag&0x8000) { + if(sd && battle_config.pc_attack_direction_change) + sd->dir = sd->head_dir = map_calc_dir(src, target->x,target->y ); + else if(src->type == BL_MOB && battle_config.monster_attack_direction_change) + ((struct mob_data *)src)->dir = map_calc_dir(src, target->x,target->y ); + wd=battle_calc_weapon_attack(src,target,KN_AUTOCOUNTER,flag&0xff,0); + } + else + wd=battle_calc_weapon_attack(src,target,0,0,0); + if((damage = wd.damage + wd.damage2) > 0 && src != target) { + if(wd.flag&BF_SHORT) { + if(target->type == BL_PC) { + struct map_session_data *tsd = (struct map_session_data *)target; + if(tsd && tsd->short_weapon_damage_return > 0) { + rdamage += damage * tsd->short_weapon_damage_return / 100; + if(rdamage < 1) rdamage = 1; + } + } + if(t_sc_data && t_sc_data[SC_REFLECTSHIELD].timer != -1) { + rdamage += damage * t_sc_data[SC_REFLECTSHIELD].val2 / 100; + if(rdamage < 1) rdamage = 1; + } + } + else if(wd.flag&BF_LONG) { + if(target->type == BL_PC) { + struct map_session_data *tsd = (struct map_session_data *)target; + if(tsd && tsd->long_weapon_damage_return > 0) { + rdamage += damage * tsd->long_weapon_damage_return / 100; + if(rdamage < 1) rdamage = 1; + } + } + } + if(rdamage > 0) + clif_damage(src,src,tick, wd.amotion,0,rdamage,1,4,0); + } + + if (wd.div_ == 255 && sd) { //三段掌 + int delay = 1000 - 4 * battle_get_agi(src) - 2 * battle_get_dex(src); + int skilllv; + if(wd.damage+wd.damage2 < battle_get_hp(target)) { + if((skilllv = pc_checkskill(sd, MO_CHAINCOMBO)) > 0) + delay += 300 * battle_config.combo_delay_rate /100; //追加ディレイをconfにより調整 + + skill_status_change_start(src,SC_COMBO,MO_TRIPLEATTACK,skilllv,0,0,delay,0); + } + sd->attackabletime = sd->canmove_tick = tick + delay; + clif_combo_delay(src,delay); + clif_skill_damage(src , target , tick , wd.amotion , wd.dmotion , + wd.damage , 3 , MO_TRIPLEATTACK, pc_checkskill(sd,MO_TRIPLEATTACK) , -1 ); + } + else { + clif_damage(src,target,tick, wd.amotion, wd.dmotion, + wd.damage, wd.div_ , wd.type, wd.damage2); + //二刀流左手とカタール追撃のミス表示(無理やり〜) + if(sd && sd->status.weapon >= 16 && wd.damage2 == 0) + clif_damage(src,target,tick+10, wd.amotion, wd.dmotion,0, 1, 0, 0); + } + if(sd && sd->splash_range > 0 && (wd.damage > 0 || wd.damage2 > 0) ) + skill_castend_damage_id(src,target,0,-1,tick,0); + map_freeblock_lock(); + battle_damage(src,target,(wd.damage+wd.damage2),0); + if(target->prev != NULL && + (target->type != BL_PC || (target->type == BL_PC && !pc_isdead((struct map_session_data *)target) ) ) ) { + if(wd.damage > 0 || wd.damage2 > 0) { + skill_additional_effect(src,target,0,0,BF_WEAPON,tick); + if(sd) { + if(sd->weapon_coma_ele[ele] > 0 && rand()%10000 < sd->weapon_coma_ele[ele]) + battle_damage(src,target,battle_get_max_hp(target),1); + if(sd->weapon_coma_race[race] > 0 && rand()%10000 < sd->weapon_coma_race[race]) + battle_damage(src,target,battle_get_max_hp(target),1); + if(battle_get_mode(target) & 0x20) { + if(sd->weapon_coma_race[10] > 0 && rand()%10000 < sd->weapon_coma_race[10]) + battle_damage(src,target,battle_get_max_hp(target),1); + } + else { + if(sd->weapon_coma_race[11] > 0 && rand()%10000 < sd->weapon_coma_race[11]) + battle_damage(src,target,battle_get_max_hp(target),1); + } + } + } + } + if(sc_data && sc_data[SC_AUTOSPELL].timer != -1 && rand()%100 < sc_data[SC_AUTOSPELL].val4) { + int skilllv=sc_data[SC_AUTOSPELL].val3,i,f=0; + i = rand()%100; + if(i >= 50) skilllv -= 2; + else if(i >= 15) skilllv--; + if(skilllv < 1) skilllv = 1; + if(sd) { + int sp = skill_get_sp(sc_data[SC_AUTOSPELL].val2,skilllv)*2/3; + if(sd->status.sp >= sp) { + if((i=skill_get_inf(sc_data[SC_AUTOSPELL].val2) == 2) || i == 32) + f = skill_castend_pos2(src,target->x,target->y,sc_data[SC_AUTOSPELL].val2,skilllv,tick,flag); + else { + switch( skill_get_nk(sc_data[SC_AUTOSPELL].val2) ) { + case 0: case 2: + f = skill_castend_damage_id(src,target,sc_data[SC_AUTOSPELL].val2,skilllv,tick,flag); + break; + case 1:/* 支援系 */ + if((sc_data[SC_AUTOSPELL].val2==AL_HEAL || (sc_data[SC_AUTOSPELL].val2==ALL_RESURRECTION && target->type != BL_PC)) && battle_check_undead(race,ele)) + f = skill_castend_damage_id(src,target,sc_data[SC_AUTOSPELL].val2,skilllv,tick,flag); + else + f = skill_castend_nodamage_id(src,target,sc_data[SC_AUTOSPELL].val2,skilllv,tick,flag); + break; + } + } + if(!f) pc_heal(sd,0,-sp); + } + } + else { + if((i=skill_get_inf(sc_data[SC_AUTOSPELL].val2) == 2) || i == 32) + skill_castend_pos2(src,target->x,target->y,sc_data[SC_AUTOSPELL].val2,skilllv,tick,flag); + else { + switch( skill_get_nk(sc_data[SC_AUTOSPELL].val2) ) { + case 0: case 2: + skill_castend_damage_id(src,target,sc_data[SC_AUTOSPELL].val2,skilllv,tick,flag); + break; + case 1:/* 支援系 */ + if((sc_data[SC_AUTOSPELL].val2==AL_HEAL || (sc_data[SC_AUTOSPELL].val2==ALL_RESURRECTION && target->type != BL_PC)) && battle_check_undead(race,ele)) + skill_castend_damage_id(src,target,sc_data[SC_AUTOSPELL].val2,skilllv,tick,flag); + else + skill_castend_nodamage_id(src,target,sc_data[SC_AUTOSPELL].val2,skilllv,tick,flag); + break; + } + } + } + } + if(sd) { + if(sd->autospell_id > 0 && sd->autospell_lv > 0 && rand()%100 < sd->autospell_rate) { + int skilllv=sd->autospell_lv,i,f=0,sp; + i = rand()%100; + if(i >= 50) skilllv -= 2; + else if(i >= 15) skilllv--; + if(skilllv < 1) skilllv = 1; + sp = skill_get_sp(sd->autospell_id,skilllv)*2/3; + if(sd->status.sp >= sp) { + if((i=skill_get_inf(sd->autospell_id) == 2) || i == 32) + f = skill_castend_pos2(src,target->x,target->y,sd->autospell_id,skilllv,tick,flag); + else { + switch( skill_get_nk(sd->autospell_id) ) { + case 0: case 2: + f = skill_castend_damage_id(src,target,sd->autospell_id,skilllv,tick,flag); + break; + case 1:/* 支援系 */ + if((sd->autospell_id==AL_HEAL || (sd->autospell_id==ALL_RESURRECTION && target->type != BL_PC)) && battle_check_undead(race,ele)) + f = skill_castend_damage_id(src,target,sd->autospell_id,skilllv,tick,flag); + else + f = skill_castend_nodamage_id(src,target,sd->autospell_id,skilllv,tick,flag); + break; + } + } + if(!f) pc_heal(sd,0,-sp); + } + } + if(wd.flag&BF_WEAPON && src != target && (wd.damage > 0 || wd.damage2 > 0)) { + int hp = 0,sp = 0; + if(sd->hp_drain_rate && sd->hp_drain_per > 0 && wd.damage > 0 && rand()%100 < sd->hp_drain_rate) { + hp += (wd.damage * sd->hp_drain_per)/100; + if(sd->hp_drain_rate > 0 && hp < 1) hp = 1; + else if(sd->hp_drain_rate < 0 && hp > -1) hp = -1; + } + if(sd->hp_drain_rate_ && sd->hp_drain_per_ > 0 && wd.damage2 > 0 && rand()%100 < sd->hp_drain_rate_) { + hp += (wd.damage2 * sd->hp_drain_per_)/100; + if(sd->hp_drain_rate_ > 0 && hp < 1) hp = 1; + else if(sd->hp_drain_rate_ < 0 && hp > -1) hp = -1; + } + if(sd->sp_drain_rate && sd->sp_drain_per > 0 && wd.damage > 0 && rand()%100 < sd->sp_drain_rate) { + sp += (wd.damage * sd->sp_drain_per)/100; + if(sd->sp_drain_rate > 0 && sp < 1) sp = 1; + else if(sd->sp_drain_rate < 0 && sp > -1) sp = -1; + } + if(sd->sp_drain_rate_ && sd->sp_drain_per_ > 0 && wd.damage2 > 0 && rand()%100 < sd->sp_drain_rate_) { + sp += (wd.damage2 * sd->sp_drain_per_)/100; + if(sd->sp_drain_rate_ > 0 && sp < 1) sp = 1; + else if(sd->sp_drain_rate_ < 0 && sp > -1) sp = -1; + } + if(hp || sp) pc_heal(sd,hp,sp); + } + } + + if(rdamage > 0) + battle_damage(target,src,rdamage,0); + if(t_sc_data && t_sc_data[SC_AUTOCOUNTER].timer != -1 && t_sc_data[SC_AUTOCOUNTER].val4 > 0) { + if(t_sc_data[SC_AUTOCOUNTER].val3 == src->id) + battle_weapon_attack(target,src,tick,0x8000|t_sc_data[SC_AUTOCOUNTER].val1); + skill_status_change_end(target,SC_AUTOCOUNTER,-1); + } + if(t_sc_data && t_sc_data[SC_BLADESTOP_WAIT].timer != -1){ + int lv = t_sc_data[SC_BLADESTOP_WAIT].val1; + skill_status_change_end(target,SC_BLADESTOP_WAIT,-1); + skill_status_change_start(src,SC_BLADESTOP,lv,1,(int)src,(int)target,skill_get_time2(MO_BLADESTOP,lv),0); + skill_status_change_start(target,SC_BLADESTOP,lv,2,(int)target,(int)src,skill_get_time2(MO_BLADESTOP,lv),0); + } + if(t_sc_data && t_sc_data[SC_SPLASHER].timer!=-1) //殴ったので対象のベナムスプラッシャー状態を解除 + skill_status_change_end(target,SC_SPLASHER,-1); + + map_freeblock_unlock(); + } + return wd.dmg_lv; +} + +int battle_check_undead(int race,int element) +{ + if(battle_config.undead_detect_type == 0) { + if(element == 9) + return 1; + } + else if(battle_config.undead_detect_type == 1) { + if(race == 1) + return 1; + } + else { + if(element == 9 || race == 1) + return 1; + } + return 0; +} + +/*========================================== + * 敵味方判定(1=肯定,0=否定,-1=エラー) + * flag&0xf0000 = 0x00000:敵じゃないか判定(ret:1=敵ではない) + * = 0x10000:パーティー判定(ret:1=パーティーメンバ) + * = 0x20000:全て(ret:1=敵味方両方) + * = 0x40000:敵か判定(ret:1=敵) + * = 0x50000:パーティーじゃないか判定(ret:1=パーティでない) + *------------------------------------------ + */ +int battle_check_target( struct block_list *src, struct block_list *target,int flag) +{ + int s_p,s_g,t_p,t_g; + struct block_list *ss=src; + + nullpo_retr(0, src); + nullpo_retr(0, target); + + if( flag&0x40000 ){ // 反転フラグ + int ret=battle_check_target(src,target,flag&0x30000); + if(ret!=-1) + return !ret; + return -1; + } + + if( flag&0x20000 ){ + if( target->type==BL_MOB || target->type==BL_PC ) + return 1; + else + return -1; + } + + if(src->type == BL_SKILL && target->type == BL_SKILL) // 対象がスキルユニットなら無条件肯定 + return -1; + + if(target->type == BL_PC && ((struct map_session_data *)target)->invincible_timer != -1) + return -1; + + if(target->type == BL_SKILL) { + switch(((struct skill_unit *)target)->group->unit_id){ + case 0x8d: + case 0x8f: + case 0x98: + return 0; + break; + } + } + + if(target->type == BL_PET) + return -1; + + // スキルユニットの場合、親を求める + if( src->type==BL_SKILL) { + int inf2 = skill_get_inf2(((struct skill_unit *)src)->group->skill_id); + if( (ss=map_id2bl( ((struct skill_unit *)src)->group->src_id))==NULL ) + return -1; + if(ss->prev == NULL) + return -1; + if(inf2&0x80 && + (map[src->m].flag.pvp || pc_iskiller((struct map_session_data *)src, (struct map_session_data *)target)) && // [MouseJstr] + !(target->type == BL_PC && pc_isinvisible((struct map_session_data *)target))) + return 0; + if(ss == target) { + if(inf2&0x100) + return 0; + if(inf2&0x200) + return -1; + } + } + // Mobでmaster_idがあってspecial_mob_aiなら、召喚主を求める + if( src->type==BL_MOB ){ + struct mob_data *md=(struct mob_data *)src; + if(md && md->master_id>0){ + if(md->master_id==target->id) // 主なら肯定 + return 1; + if(md->state.special_mob_ai){ + if(target->type==BL_MOB){ //special_mob_aiで対象がMob + struct mob_data *tmd=(struct mob_data *)target; + if(tmd){ + if(tmd->master_id != md->master_id) //召喚主が一緒でなければ否定 + return 0; + else{ //召喚主が一緒なので肯定したいけど自爆は否定 + if(md->state.special_mob_ai>2) + return 0; + else + return 1; + } + } + } + } + if((ss=map_id2bl(md->master_id))==NULL) + return -1; + } + } + + if( src==target || ss==target ) // 同じなら肯定 + return 1; + + if(target->type == BL_PC && pc_isinvisible((struct map_session_data *)target)) + return -1; + + if( src->prev==NULL || // 死んでるならエラー + (src->type==BL_PC && pc_isdead((struct map_session_data *)src) ) ) + return -1; + + if( (ss->type == BL_PC && target->type==BL_MOB) || + (ss->type == BL_MOB && target->type==BL_PC) ) + return 0; // PCvsMOBなら否定 + + if(ss->type == BL_PET && target->type==BL_MOB) + return 0; + + s_p=battle_get_party_id(ss); + s_g=battle_get_guild_id(ss); + + t_p=battle_get_party_id(target); + t_g=battle_get_guild_id(target); + + if(flag&0x10000) { + if(s_p && t_p && s_p == t_p) // 同じパーティなら肯定(味方) + return 1; + else // パーティ検索なら同じパーティじゃない時点で否定 + return 0; + } + + if(ss->type == BL_MOB && s_g > 0 && t_g > 0 && s_g == t_g ) // 同じギルド/mobクラスなら肯定(味方) + return 1; + +//printf("ss:%d src:%d target:%d flag:0x%x %d %d ",ss->id,src->id,target->id,flag,src->type,target->type); +//printf("p:%d %d g:%d %d\n",s_p,t_p,s_g,t_g); + + if( ss->type==BL_PC && target->type==BL_PC) { // 両方PVPモードなら否定(敵) + struct skill_unit *su=NULL; + if(src->type==BL_SKILL) + su=(struct skill_unit *)src; + if(map[ss->m].flag.pvp || pc_iskiller((struct map_session_data *)ss, (struct map_session_data*)target)) { // [MouseJstr] + if(su && su->group->target_flag==BCT_NOENEMY) + return 1; + else if(battle_config.pk_mode && (((struct map_session_data*)ss)->status.class==0 || ((struct map_session_data*)target)->status.class==0)) + return 1; // prevent novice engagement in pk_mode [Valaris] + else if(map[ss->m].flag.pvp_noparty && s_p > 0 && t_p > 0 && s_p == t_p) + return 1; + else if(map[ss->m].flag.pvp_noguild && s_g > 0 && t_g > 0 && s_g == t_g) + return 1; + return 0; + } + if(map[src->m].flag.gvg) { + struct guild *g=NULL; + if(su && su->group->target_flag==BCT_NOENEMY) + return 1; + if( s_g > 0 && s_g == t_g) + return 1; + if(map[src->m].flag.gvg_noparty && s_p > 0 && t_p > 0 && s_p == t_p) + return 1; + if((g = guild_search(s_g))) { + int i; + for(i=0;i<MAX_GUILDALLIANCE;i++){ + if(g->alliance[i].guild_id > 0 && g->alliance[i].guild_id == t_g) { + if(g->alliance[i].opposition) + return 0;//敵対ギルドなら無条件に敵 + else + return 1;//同盟ギルドなら無条件に味方 + } + } + } + return 0; + } + } + + return 1; // 該当しないので無関係人物(まあ敵じゃないので味方) +} +/*========================================== + * 射程判定 + *------------------------------------------ + */ +int battle_check_range(struct block_list *src,struct block_list *bl,int range) +{ + + int dx,dy; + struct walkpath_data wpd; + int arange; + + nullpo_retr(0, src); + nullpo_retr(0, bl); + + dx=abs(bl->x-src->x); + dy=abs(bl->y-src->y); + arange=((dx>dy)?dx:dy); + + if(src->m != bl->m) // 違うマップ + return 0; + + if( range>0 && range < arange ) // 遠すぎる + return 0; + + if( arange<2 ) // 同じマスか隣接 + return 1; + +// if(bl->type == BL_SKILL && ((struct skill_unit *)bl)->group->unit_id == 0x8d) +// return 1; + + // 障害物判定 + wpd.path_len=0; + wpd.path_pos=0; + wpd.path_half=0; + if(path_search(&wpd,src->m,src->x,src->y,bl->x,bl->y,0x10001)!=-1) + return 1; + + dx=(dx>0)?1:((dx<0)?-1:0); + dy=(dy>0)?1:((dy<0)?-1:0); + return (path_search(&wpd,src->m,src->x+dx,src->y+dy, + bl->x-dx,bl->y-dy,0x10001)!=-1)?1:0; +} + +/*========================================== + * Return numerical value of a switch configuration (modified by [Yor]) + * on/off, english, fran軋is, deutsch, espaol + *------------------------------------------ + */ +int battle_config_switch(const char *str) { + if (strcmpi(str, "on") == 0 || strcmpi(str, "yes") == 0 || strcmpi(str, "oui") == 0 || strcmpi(str, "ja") == 0 || strcmpi(str, "si") == 0) + return 1; + if (strcmpi(str, "off") == 0 || strcmpi(str, "no") == 0 || strcmpi(str, "non") == 0 || strcmpi(str, "nein") == 0) + return 0; + return atoi(str); +} +/*========================================== + * 設定ファイルを読み込む + *------------------------------------------ + */ +int battle_config_read(const char *cfgName) +{ + int i; + char line[1024], w1[1024], w2[1024]; + FILE *fp; + static int count = 0; + + if ((count++) == 0) { + battle_config.warp_point_debug=0; + battle_config.enemy_critical=0; + battle_config.enemy_critical_rate=100; + battle_config.enemy_str=1; + battle_config.enemy_perfect_flee=0; + battle_config.cast_rate=100; + battle_config.delay_rate=100; + battle_config.delay_dependon_dex=0; + battle_config.sdelay_attack_enable=0; + battle_config.left_cardfix_to_right=0; + battle_config.pc_skill_add_range=0; + battle_config.skill_out_range_consume=1; + battle_config.mob_skill_add_range=0; + battle_config.pc_damage_delay=1; + battle_config.pc_damage_delay_rate=100; + battle_config.defnotenemy=1; + battle_config.random_monster_checklv=1; + battle_config.attr_recover=1; + battle_config.flooritem_lifetime=LIFETIME_FLOORITEM*1000; + battle_config.item_auto_get=0; + battle_config.item_first_get_time=3000; + battle_config.item_second_get_time=1000; + battle_config.item_third_get_time=1000; + battle_config.mvp_item_first_get_time=10000; + battle_config.mvp_item_second_get_time=10000; + battle_config.mvp_item_third_get_time=2000; + + battle_config.drop_rate0item=0; + battle_config.base_exp_rate=100; + battle_config.job_exp_rate=100; + battle_config.pvp_exp=1; + battle_config.gtb_pvp_only=0; + battle_config.death_penalty_type=0; + battle_config.death_penalty_base=0; + battle_config.death_penalty_job=0; + battle_config.zeny_penalty=0; + battle_config.restart_hp_rate=0; + battle_config.restart_sp_rate=0; + battle_config.mvp_item_rate=100; + battle_config.mvp_exp_rate=100; + battle_config.mvp_hp_rate=100; + battle_config.monster_hp_rate=100; + battle_config.monster_max_aspd=199; + battle_config.atc_gmonly=0; + battle_config.gm_allskill=0; + battle_config.gm_allequip=0; + battle_config.gm_skilluncond=0; + battle_config.guild_max_castles=0; + battle_config.skillfree = 0; + battle_config.skillup_limit = 0; + battle_config.wp_rate=100; + battle_config.pp_rate=100; + battle_config.monster_active_enable=1; + battle_config.monster_damage_delay_rate=100; + battle_config.monster_loot_type=0; + battle_config.mob_skill_use=1; + battle_config.mob_count_rate=100; + battle_config.quest_skill_learn=0; + battle_config.quest_skill_reset=1; + battle_config.basic_skill_check=1; + battle_config.guild_emperium_check=1; + battle_config.guild_exp_limit=50; + battle_config.pc_invincible_time = 5000; + battle_config.pet_catch_rate=100; + battle_config.pet_rename=0; + battle_config.pet_friendly_rate=100; + battle_config.pet_hungry_delay_rate=100; + battle_config.pet_hungry_friendly_decrease=5; + battle_config.pet_str=1; + battle_config.pet_status_support=0; + battle_config.pet_attack_support=0; + battle_config.pet_damage_support=0; + battle_config.pet_support_rate=100; + battle_config.pet_attack_exp_to_master=0; + battle_config.pet_attack_exp_rate=100; + battle_config.skill_min_damage=0; + battle_config.finger_offensive_type=0; + battle_config.heal_exp=0; + battle_config.resurrection_exp=0; + battle_config.shop_exp=0; + battle_config.combo_delay_rate=100; + battle_config.item_check=1; + battle_config.wedding_modifydisplay=0; + battle_config.natural_healhp_interval=6000; + battle_config.natural_healsp_interval=8000; + battle_config.natural_heal_skill_interval=10000; + battle_config.natural_heal_weight_rate=50; + battle_config.item_name_override_grffile=1; + battle_config.arrow_decrement=1; + battle_config.max_aspd = 199; + battle_config.max_hp = 32500; + battle_config.max_sp = 32500; + battle_config.max_lv = 99; // [MouseJstr] + battle_config.max_parameter = 99; + battle_config.max_cart_weight = 8000; + battle_config.pc_skill_log = 0; + battle_config.mob_skill_log = 0; + battle_config.battle_log = 0; + battle_config.save_log = 0; + battle_config.error_log = 1; + battle_config.etc_log = 1; + battle_config.save_clothcolor = 0; + battle_config.undead_detect_type = 0; + battle_config.pc_auto_counter_type = 1; + battle_config.monster_auto_counter_type = 1; + battle_config.agi_penaly_type = 0; + battle_config.agi_penaly_count = 3; + battle_config.agi_penaly_num = 0; + battle_config.agi_penaly_count_lv = ATK_FLEE; + battle_config.vit_penaly_type = 0; + battle_config.vit_penaly_count = 3; + battle_config.vit_penaly_num = 0; + battle_config.vit_penaly_count_lv = ATK_DEF; + battle_config.player_defense_type = 0; + battle_config.monster_defense_type = 0; + battle_config.pet_defense_type = 0; + battle_config.magic_defense_type = 0; + battle_config.pc_skill_reiteration = 0; + battle_config.monster_skill_reiteration = 0; + battle_config.pc_skill_nofootset = 0; + battle_config.monster_skill_nofootset = 0; + battle_config.pc_cloak_check_type = 0; + battle_config.monster_cloak_check_type = 0; + battle_config.gvg_short_damage_rate = 100; + battle_config.gvg_long_damage_rate = 100; + battle_config.gvg_magic_damage_rate = 100; + battle_config.gvg_misc_damage_rate = 100; + battle_config.gvg_eliminate_time = 7000; + battle_config.mob_changetarget_byskill = 0; + battle_config.pc_attack_direction_change = 1; + battle_config.monster_attack_direction_change = 1; + battle_config.pc_undead_nofreeze = 0; + battle_config.pc_land_skill_limit = 1; + battle_config.monster_land_skill_limit = 1; + battle_config.party_skill_penaly = 1; + battle_config.monster_class_change_full_recover = 0; + battle_config.produce_item_name_input = 1; + battle_config.produce_potion_name_input = 1; + battle_config.making_arrow_name_input = 1; + battle_config.holywater_name_input = 1; + battle_config.display_delay_skill_fail = 1; + battle_config.chat_warpportal = 0; + battle_config.mob_warpportal = 0; + battle_config.dead_branch_active = 0; + battle_config.vending_max_value = 10000000; + battle_config.show_steal_in_same_party = 0; + battle_config.enable_upper_class = 0; + battle_config.pet_attack_attr_none = 0; + battle_config.pc_attack_attr_none = 0; + battle_config.mob_attack_attr_none = 1; + battle_config.mob_ghostring_fix = 0; + battle_config.gx_allhit = 0; + battle_config.gx_cardfix = 0; + battle_config.gx_dupele = 1; + battle_config.gx_disptype = 1; + battle_config.player_skill_partner_check = 1; + battle_config.hide_GM_session = 0; + battle_config.unit_movement_type = 0; + battle_config.invite_request_check = 1; + battle_config.skill_removetrap_type = 0; + battle_config.disp_experience = 0; + battle_config.item_rate_common = 100; + battle_config.item_rate_equip = 100; + battle_config.item_rate_card = 100; + battle_config.item_rate_heal = 100; // Added by Valaris + battle_config.item_rate_use = 100; // End + battle_config.item_drop_common_min=1; // Added by TyrNemesis^ + battle_config.item_drop_common_max=10000; + battle_config.item_drop_equip_min=1; + battle_config.item_drop_equip_max=10000; + battle_config.item_drop_card_min=1; + battle_config.item_drop_card_max=10000; + battle_config.item_drop_mvp_min=1; + battle_config.item_drop_mvp_max=10000; // End Addition + battle_config.item_drop_heal_min=1; // Added by Valaris + battle_config.item_drop_heal_max=10000; + battle_config.item_drop_use_min=1; + battle_config.item_drop_use_max=10000; // End + battle_config.prevent_logout = 1; // Added by RoVeRT + battle_config.maximum_level = 255; // Added by Valaris + battle_config.drops_by_luk = 0; // [Valaris] + battle_config.equipment_breaking = 0; // [Valaris] + battle_config.equipment_break_rate = 100; // [Valaris] + battle_config.pk_mode = 0; // [Valaris] + battle_config.pet_equip_required = 0; // [Valaris] + battle_config.multi_level_up = 0; // [Valaris] + battle_config.backstab_bow_penalty = 0; // Akaru + battle_config.night_at_start = 0; // added by [Yor] + battle_config.day_duration = 2*60*60*1000; // added by [Yor] (2 hours) + battle_config.night_duration = 30*60*1000; // added by [Yor] (30 minutes) + battle_config.show_mob_hp = 0; // [Valaris] + battle_config.ban_spoof_namer = 5; // added by [Yor] (default: 5 minutes) + battle_config.hack_info_GM_level = 60; // added by [Yor] (default: 60, GM level) + battle_config.any_warp_GM_min_level = 20; // added by [Yor] + battle_config.packet_ver_flag = 63; // added by [Yor] + battle_config.min_hair_style = 0; + battle_config.max_hair_style = 20; + battle_config.min_hair_color = 0; + battle_config.max_hair_color = 9; + battle_config.min_cloth_color = 0; + battle_config.max_cloth_color = 4; + + battle_config.castrate_dex_scale = 150; + + battle_config.area_size = 14; + +//SQL-only options start +#ifndef TXT_ONLY + battle_config.mail_system = 0; +//SQL-only options end +#endif + +} + + fp = fopen(cfgName,"r"); + if (fp == NULL) { + printf("file not found: %s\n", cfgName); + return 1; + } + while(fgets(line,1020,fp)){ + const struct { + char str[128]; + int *val; + } data[] = { + { "warp_point_debug", &battle_config.warp_point_debug }, + { "enemy_critical", &battle_config.enemy_critical }, + { "enemy_critical_rate", &battle_config.enemy_critical_rate }, + { "enemy_str", &battle_config.enemy_str }, + { "enemy_perfect_flee", &battle_config.enemy_perfect_flee }, + { "casting_rate", &battle_config.cast_rate }, + { "delay_rate", &battle_config.delay_rate }, + { "delay_dependon_dex", &battle_config.delay_dependon_dex }, + { "skill_delay_attack_enable", &battle_config.sdelay_attack_enable }, + { "left_cardfix_to_right", &battle_config.left_cardfix_to_right }, + { "player_skill_add_range", &battle_config.pc_skill_add_range }, + { "skill_out_range_consume", &battle_config.skill_out_range_consume }, + { "monster_skill_add_range", &battle_config.mob_skill_add_range }, + { "player_damage_delay", &battle_config.pc_damage_delay }, + { "player_damage_delay_rate", &battle_config.pc_damage_delay_rate }, + { "defunit_not_enemy", &battle_config.defnotenemy }, + { "random_monster_checklv", &battle_config.random_monster_checklv }, + { "attribute_recover", &battle_config.attr_recover }, + { "flooritem_lifetime", &battle_config.flooritem_lifetime }, + { "item_auto_get", &battle_config.item_auto_get }, + { "item_first_get_time", &battle_config.item_first_get_time }, + { "item_second_get_time", &battle_config.item_second_get_time }, + { "item_third_get_time", &battle_config.item_third_get_time }, + { "mvp_item_first_get_time", &battle_config.mvp_item_first_get_time }, + { "mvp_item_second_get_time", &battle_config.mvp_item_second_get_time }, + { "mvp_item_third_get_time", &battle_config.mvp_item_third_get_time }, + { "item_rate", &battle_config.item_rate }, + { "drop_rate0item", &battle_config.drop_rate0item }, + { "base_exp_rate", &battle_config.base_exp_rate }, + { "job_exp_rate", &battle_config.job_exp_rate }, + { "pvp_exp", &battle_config.pvp_exp }, + { "gtb_pvp_only", &battle_config.gtb_pvp_only }, + { "guild_max_castles", &battle_config.guild_max_castles }, + { "death_penalty_type", &battle_config.death_penalty_type }, + { "death_penalty_base", &battle_config.death_penalty_base }, + { "death_penalty_job", &battle_config.death_penalty_job }, + { "zeny_penalty", &battle_config.zeny_penalty }, + { "restart_hp_rate", &battle_config.restart_hp_rate }, + { "restart_sp_rate", &battle_config.restart_sp_rate }, + { "mvp_hp_rate", &battle_config.mvp_hp_rate }, + { "mvp_item_rate", &battle_config.mvp_item_rate }, + { "mvp_exp_rate", &battle_config.mvp_exp_rate }, + { "monster_hp_rate", &battle_config.monster_hp_rate }, + { "monster_max_aspd", &battle_config.monster_max_aspd }, + { "atcommand_gm_only", &battle_config.atc_gmonly }, + { "atcommand_spawn_quantity_limit", &battle_config.atc_spawn_quantity_limit }, + { "gm_all_skill", &battle_config.gm_allskill }, + { "gm_all_skill_add_abra", &battle_config.gm_allskill_addabra }, + { "gm_all_equipment", &battle_config.gm_allequip }, + { "gm_skill_unconditional", &battle_config.gm_skilluncond }, + { "player_skillfree", &battle_config.skillfree }, + { "player_skillup_limit", &battle_config.skillup_limit }, + { "weapon_produce_rate", &battle_config.wp_rate }, + { "potion_produce_rate", &battle_config.pp_rate }, + { "monster_active_enable", &battle_config.monster_active_enable }, + { "monster_damage_delay_rate", &battle_config.monster_damage_delay_rate}, + { "monster_loot_type", &battle_config.monster_loot_type }, + { "mob_skill_use", &battle_config.mob_skill_use }, + { "mob_count_rate", &battle_config.mob_count_rate }, + { "quest_skill_learn", &battle_config.quest_skill_learn }, + { "quest_skill_reset", &battle_config.quest_skill_reset }, + { "basic_skill_check", &battle_config.basic_skill_check }, + { "guild_emperium_check", &battle_config.guild_emperium_check }, + { "guild_exp_limit", &battle_config.guild_exp_limit }, + { "player_invincible_time", &battle_config.pc_invincible_time }, + { "pet_catch_rate", &battle_config.pet_catch_rate }, + { "pet_rename", &battle_config.pet_rename }, + { "pet_friendly_rate", &battle_config.pet_friendly_rate }, + { "pet_hungry_delay_rate", &battle_config.pet_hungry_delay_rate }, + { "pet_hungry_friendly_decrease", &battle_config.pet_hungry_friendly_decrease}, + { "pet_str", &battle_config.pet_str }, + { "pet_status_support", &battle_config.pet_status_support }, + { "pet_attack_support", &battle_config.pet_attack_support }, + { "pet_damage_support", &battle_config.pet_damage_support }, + { "pet_support_rate", &battle_config.pet_support_rate }, + { "pet_attack_exp_to_master", &battle_config.pet_attack_exp_to_master }, + { "pet_attack_exp_rate", &battle_config.pet_attack_exp_rate }, + { "skill_min_damage", &battle_config.skill_min_damage }, + { "finger_offensive_type", &battle_config.finger_offensive_type }, + { "heal_exp", &battle_config.heal_exp }, + { "resurrection_exp", &battle_config.resurrection_exp }, + { "shop_exp", &battle_config.shop_exp }, + { "combo_delay_rate", &battle_config.combo_delay_rate }, + { "item_check", &battle_config.item_check }, + { "wedding_modifydisplay", &battle_config.wedding_modifydisplay }, + { "natural_healhp_interval", &battle_config.natural_healhp_interval }, + { "natural_healsp_interval", &battle_config.natural_healsp_interval }, + { "natural_heal_skill_interval", &battle_config.natural_heal_skill_interval}, + { "natural_heal_weight_rate", &battle_config.natural_heal_weight_rate }, + { "item_name_override_grffile", &battle_config.item_name_override_grffile}, + { "arrow_decrement", &battle_config.arrow_decrement }, + { "max_aspd", &battle_config.max_aspd }, + { "max_hp", &battle_config.max_hp }, + { "max_sp", &battle_config.max_sp }, + { "max_lv", &battle_config.max_lv }, + { "max_parameter", &battle_config.max_parameter }, + { "max_cart_weight", &battle_config.max_cart_weight }, + { "player_skill_log", &battle_config.pc_skill_log }, + { "monster_skill_log", &battle_config.mob_skill_log }, + { "battle_log", &battle_config.battle_log }, + { "save_log", &battle_config.save_log }, + { "error_log", &battle_config.error_log }, + { "etc_log", &battle_config.etc_log }, + { "save_clothcolor", &battle_config.save_clothcolor }, + { "undead_detect_type", &battle_config.undead_detect_type }, + { "player_auto_counter_type", &battle_config.pc_auto_counter_type }, + { "monster_auto_counter_type", &battle_config.monster_auto_counter_type}, + { "agi_penaly_type", &battle_config.agi_penaly_type }, + { "agi_penaly_count", &battle_config.agi_penaly_count }, + { "agi_penaly_num", &battle_config.agi_penaly_num }, + { "agi_penaly_count_lv", &battle_config.agi_penaly_count_lv }, + { "vit_penaly_type", &battle_config.vit_penaly_type }, + { "vit_penaly_count", &battle_config.vit_penaly_count }, + { "vit_penaly_num", &battle_config.vit_penaly_num }, + { "vit_penaly_count_lv", &battle_config.vit_penaly_count_lv }, + { "player_defense_type", &battle_config.player_defense_type }, + { "monster_defense_type", &battle_config.monster_defense_type }, + { "pet_defense_type", &battle_config.pet_defense_type }, + { "magic_defense_type", &battle_config.magic_defense_type }, + { "player_skill_reiteration", &battle_config.pc_skill_reiteration }, + { "monster_skill_reiteration", &battle_config.monster_skill_reiteration}, + { "player_skill_nofootset", &battle_config.pc_skill_nofootset }, + { "monster_skill_nofootset", &battle_config.monster_skill_nofootset }, + { "player_cloak_check_type", &battle_config.pc_cloak_check_type }, + { "monster_cloak_check_type", &battle_config.monster_cloak_check_type }, + { "gvg_short_attack_damage_rate", &battle_config.gvg_short_damage_rate }, + { "gvg_long_attack_damage_rate", &battle_config.gvg_long_damage_rate }, + { "gvg_magic_attack_damage_rate", &battle_config.gvg_magic_damage_rate }, + { "gvg_misc_attack_damage_rate", &battle_config.gvg_misc_damage_rate }, + { "gvg_eliminate_time", &battle_config.gvg_eliminate_time }, + { "mob_changetarget_byskill", &battle_config.mob_changetarget_byskill}, + { "player_attack_direction_change", &battle_config.pc_attack_direction_change }, + { "monster_attack_direction_change", &battle_config.monster_attack_direction_change }, + { "player_land_skill_limit", &battle_config.pc_land_skill_limit }, + { "monster_land_skill_limit", &battle_config.monster_land_skill_limit}, + { "party_skill_penaly", &battle_config.party_skill_penaly }, + { "monster_class_change_full_recover", &battle_config.monster_class_change_full_recover }, + { "produce_item_name_input", &battle_config.produce_item_name_input }, + { "produce_potion_name_input", &battle_config.produce_potion_name_input}, + { "making_arrow_name_input", &battle_config.making_arrow_name_input }, + { "holywater_name_input", &battle_config.holywater_name_input }, + { "display_delay_skill_fail", &battle_config.display_delay_skill_fail }, + { "chat_warpportal", &battle_config.chat_warpportal }, + { "mob_warpportal", &battle_config.mob_warpportal }, + { "dead_branch_active", &battle_config.dead_branch_active }, + { "vending_max_value", &battle_config.vending_max_value }, + { "show_steal_in_same_party", &battle_config.show_steal_in_same_party }, + { "enable_upper_class", &battle_config.enable_upper_class }, + { "pet_attack_attr_none", &battle_config.pet_attack_attr_none }, + { "mob_attack_attr_none", &battle_config.mob_attack_attr_none }, + { "mob_ghostring_fix", &battle_config.mob_ghostring_fix }, + { "pc_attack_attr_none", &battle_config.pc_attack_attr_none }, + { "gx_allhit", &battle_config.gx_allhit }, + { "gx_cardfix", &battle_config.gx_cardfix }, + { "gx_dupele", &battle_config.gx_dupele }, + { "gx_disptype", &battle_config.gx_disptype }, + { "player_skill_partner_check", &battle_config.player_skill_partner_check}, + { "hide_GM_session", &battle_config.hide_GM_session }, + { "unit_movement_type", &battle_config.unit_movement_type }, + { "invite_request_check", &battle_config.invite_request_check }, + { "skill_removetrap_type", &battle_config.skill_removetrap_type }, + { "disp_experience", &battle_config.disp_experience }, + { "castle_defense_rate", &battle_config.castle_defense_rate }, + { "riding_weight", &battle_config.riding_weight }, + { "item_rate_common", &battle_config.item_rate_common }, // Added by RoVeRT + { "item_rate_equip", &battle_config.item_rate_equip }, + { "item_rate_card", &battle_config.item_rate_card }, // End Addition + { "item_rate_heal", &battle_config.item_rate_heal }, // Added by Valaris + { "item_rate_use", &battle_config.item_rate_use }, // End + { "item_drop_common_min", &battle_config.item_drop_common_min }, // Added by TyrNemesis^ + { "item_drop_common_max", &battle_config.item_drop_common_max }, + { "item_drop_equip_min", &battle_config.item_drop_equip_min }, + { "item_drop_equip_max", &battle_config.item_drop_equip_max }, + { "item_drop_card_min", &battle_config.item_drop_card_min }, + { "item_drop_card_max", &battle_config.item_drop_card_max }, + { "item_drop_mvp_min", &battle_config.item_drop_mvp_min }, + { "item_drop_mvp_max", &battle_config.item_drop_mvp_max }, // End Addition + { "prevent_logout", &battle_config.prevent_logout }, // Added by RoVeRT + { "alchemist_summon_reward", &battle_config.alchemist_summon_reward }, // [Valaris] + { "maximum_level", &battle_config.maximum_level }, // [Valaris] + { "drops_by_luk", &battle_config.drops_by_luk }, // [Valaris] + { "monsters_ignore_gm", &battle_config.monsters_ignore_gm }, // [Valaris] + { "equipment_breaking", &battle_config.equipment_breaking }, // [Valaris] + { "equipment_break_rate", &battle_config.equipment_break_rate }, // [Valaris] + { "pk_mode", &battle_config.pk_mode }, // [Valaris] + { "pet_equip_required", &battle_config.pet_equip_required }, // [Valaris] + { "multi_level_up", &battle_config.multi_level_up }, // [Valaris] + { "backstab_bow_penalty", &battle_config.backstab_bow_penalty }, + { "night_at_start", &battle_config.night_at_start }, // added by [Yor] + { "day_duration", &battle_config.day_duration }, // added by [Yor] + { "night_duration", &battle_config.night_duration }, // added by [Yor] + { "show_mob_hp", &battle_config.show_mob_hp }, // [Valaris] + { "ban_spoof_namer", &battle_config.ban_spoof_namer }, // added by [Yor] + { "hack_info_GM_level", &battle_config.hack_info_GM_level }, // added by [Yor] + { "any_warp_GM_min_level", &battle_config.any_warp_GM_min_level }, // added by [Yor] + { "packet_ver_flag", &battle_config.packet_ver_flag }, // added by [Yor] + { "min_hair_style", &battle_config.min_hair_style }, // added by [MouseJstr] + { "max_hair_style", &battle_config.max_hair_style }, // added by [MouseJstr] + { "min_hair_color", &battle_config.min_hair_color }, // added by [MouseJstr] + { "max_hair_color", &battle_config.max_hair_color }, // added by [MouseJstr] + { "min_cloth_color", &battle_config.min_cloth_color }, // added by [MouseJstr] + { "max_cloth_color", &battle_config.max_cloth_color }, // added by [MouseJstr] + { "castrate_dex_scale", &battle_config.castrate_dex_scale }, // added by [MouseJstr] + { "area_size", &battle_config.area_size }, // added by [MouseJstr] + { "muting_players", &battle_config.muting_players}, // added by [Apple] +//SQL-only options start +#ifndef TXT_ONLY + { "mail_system", &battle_config.mail_system }, // added by [Valaris] +//SQL-only options end +#endif + }; + + if (line[0] == '/' && line[1] == '/') + continue; + if (sscanf(line, "%[^:]:%s", w1, w2) != 2) + continue; + for(i = 0; i < sizeof(data) / (sizeof(data[0])); i++) + if (strcmpi(w1, data[i].str) == 0) + *data[i].val = battle_config_switch(w2); + + if (strcmpi(w1, "import") == 0) + battle_config_read(w2); + } + fclose(fp); + + if (--count == 0) { + if(battle_config.flooritem_lifetime < 1000) + battle_config.flooritem_lifetime = LIFETIME_FLOORITEM*1000; + if(battle_config.restart_hp_rate < 0) + battle_config.restart_hp_rate = 0; + else if(battle_config.restart_hp_rate > 100) + battle_config.restart_hp_rate = 100; + if(battle_config.restart_sp_rate < 0) + battle_config.restart_sp_rate = 0; + else if(battle_config.restart_sp_rate > 100) + battle_config.restart_sp_rate = 100; + if(battle_config.natural_healhp_interval < NATURAL_HEAL_INTERVAL) + battle_config.natural_healhp_interval=NATURAL_HEAL_INTERVAL; + if(battle_config.natural_healsp_interval < NATURAL_HEAL_INTERVAL) + battle_config.natural_healsp_interval=NATURAL_HEAL_INTERVAL; + if(battle_config.natural_heal_skill_interval < NATURAL_HEAL_INTERVAL) + battle_config.natural_heal_skill_interval=NATURAL_HEAL_INTERVAL; + if(battle_config.natural_heal_weight_rate < 50) + battle_config.natural_heal_weight_rate = 50; + if(battle_config.natural_heal_weight_rate > 101) + battle_config.natural_heal_weight_rate = 101; + battle_config.monster_max_aspd = 2000 - battle_config.monster_max_aspd*10; + if(battle_config.monster_max_aspd < 10) + battle_config.monster_max_aspd = 10; + if(battle_config.monster_max_aspd > 1000) + battle_config.monster_max_aspd = 1000; + battle_config.max_aspd = 2000 - battle_config.max_aspd*10; + if(battle_config.max_aspd < 10) + battle_config.max_aspd = 10; + if(battle_config.max_aspd > 1000) + battle_config.max_aspd = 1000; + if(battle_config.max_hp > 1000000) + battle_config.max_hp = 1000000; + if(battle_config.max_hp < 100) + battle_config.max_hp = 100; + if(battle_config.max_sp > 1000000) + battle_config.max_sp = 1000000; + if(battle_config.max_sp < 100) + battle_config.max_sp = 100; + if(battle_config.max_parameter < 10) + battle_config.max_parameter = 10; + if(battle_config.max_parameter > 10000) + battle_config.max_parameter = 10000; + if(battle_config.max_cart_weight > 1000000) + battle_config.max_cart_weight = 1000000; + if(battle_config.max_cart_weight < 100) + battle_config.max_cart_weight = 100; + battle_config.max_cart_weight *= 10; + + if(battle_config.agi_penaly_count < 2) + battle_config.agi_penaly_count = 2; + if(battle_config.vit_penaly_count < 2) + battle_config.vit_penaly_count = 2; + + if(battle_config.guild_exp_limit > 99) + battle_config.guild_exp_limit = 99; + if(battle_config.guild_exp_limit < 0) + battle_config.guild_exp_limit = 0; + + if(battle_config.castle_defense_rate < 0) + battle_config.castle_defense_rate = 0; + if(battle_config.castle_defense_rate > 100) + battle_config.castle_defense_rate = 100; + if(battle_config.item_drop_common_min < 1) // Added by TyrNemesis^ + battle_config.item_drop_common_min = 1; + if(battle_config.item_drop_common_max > 10000) + battle_config.item_drop_common_max = 10000; + if(battle_config.item_drop_equip_min < 1) + battle_config.item_drop_equip_min = 1; + if(battle_config.item_drop_equip_max > 10000) + battle_config.item_drop_equip_max = 10000; + if(battle_config.item_drop_card_min < 1) + battle_config.item_drop_card_min = 1; + if(battle_config.item_drop_card_max > 10000) + battle_config.item_drop_card_max = 10000; + if(battle_config.item_drop_mvp_min < 1) + battle_config.item_drop_mvp_min = 1; + if(battle_config.item_drop_mvp_max > 10000) + battle_config.item_drop_mvp_max = 10000; // End Addition + + if (battle_config.night_at_start < 0) // added by [Yor] + battle_config.night_at_start = 0; + else if (battle_config.night_at_start > 1) // added by [Yor] + battle_config.night_at_start = 1; + if (battle_config.day_duration < 0) // added by [Yor] + battle_config.day_duration = 0; + if (battle_config.night_duration < 0) // added by [Yor] + battle_config.night_duration = 0; + + if (battle_config.ban_spoof_namer < 0) // added by [Yor] + battle_config.ban_spoof_namer = 0; + else if (battle_config.ban_spoof_namer > 32767) + battle_config.ban_spoof_namer = 32767; + + if (battle_config.hack_info_GM_level < 0) // added by [Yor] + battle_config.hack_info_GM_level = 0; + else if (battle_config.hack_info_GM_level > 100) + battle_config.hack_info_GM_level = 100; + + if (battle_config.any_warp_GM_min_level < 0) // added by [Yor] + battle_config.any_warp_GM_min_level = 0; + else if (battle_config.any_warp_GM_min_level > 100) + battle_config.any_warp_GM_min_level = 100; + + // at least 1 client must be accepted + if ((battle_config.packet_ver_flag & 63) == 0) // added by [Yor] + battle_config.packet_ver_flag = 63; // accept all clients + + add_timer_func_list(battle_delay_damage_sub, "battle_delay_damage_sub"); + } + + return 0; +} diff --git a/src/map/battle.h b/src/map/battle.h new file mode 100644 index 0000000..8f09d22 --- /dev/null +++ b/src/map/battle.h @@ -0,0 +1,342 @@ +// $Id: battle.h,v 1.6 2004/09/29 21:08:17 Akitasha Exp $ +#ifndef _BATTLE_H_ +#define _BATTLE_H_ + +// ダメージ +struct Damage { + int damage,damage2; + int type,div_; + int amotion,dmotion; + int blewcount; + int flag; + int dmg_lv; //囲まれ減算計算用 0:スキル攻撃 ATK_LUCKY,ATK_FLEE,ATK_DEF +}; + +// 属性表(読み込みはpc.c、battle_attr_fixで使用) +extern int attr_fix_table[4][10][10]; + +struct map_session_data; +struct mob_data; +struct block_list; + +// ダメージ計算 + +struct Damage battle_calc_attack( int attack_type, + struct block_list *bl,struct block_list *target,int skill_num,int skill_lv,int flag); +struct Damage battle_calc_weapon_attack( + struct block_list *bl,struct block_list *target,int skill_num,int skill_lv,int flag); +struct Damage battle_calc_magic_attack( + struct block_list *bl,struct block_list *target,int skill_num,int skill_lv,int flag); +struct Damage battle_calc_misc_attack( + struct block_list *bl,struct block_list *target,int skill_num,int skill_lv,int flag); + +// 属性修正計算 +int battle_attr_fix(int damage,int atk_elem,int def_elem); + +// ダメージ最終計算 +int battle_calc_damage(struct block_list *src,struct block_list *bl,int damage,int div_,int skill_num,int skill_lv,int flag); +enum { // 最終計算のフラグ + BF_WEAPON = 0x0001, + BF_MAGIC = 0x0002, + BF_MISC = 0x0004, + BF_SHORT = 0x0010, + BF_LONG = 0x0040, + BF_SKILL = 0x0100, + BF_NORMAL = 0x0200, + BF_WEAPONMASK=0x000f, + BF_RANGEMASK= 0x00f0, + BF_SKILLMASK= 0x0f00, +}; + +// 実際にHPを増減 +int battle_delay_damage(unsigned int tick,struct block_list *src,struct block_list *target,int damage,int flag); +int battle_damage(struct block_list *bl,struct block_list *target,int damage,int flag); +int battle_heal(struct block_list *bl,struct block_list *target,int hp,int sp,int flag); + +// 攻撃や移動を止める +int battle_stopattack(struct block_list *bl); +int battle_stopwalking(struct block_list *bl,int type); + +// 通常攻撃処理まとめ +int battle_weapon_attack( struct block_list *bl,struct block_list *target, + unsigned int tick,int flag); + +// 各種パラメータを得る +int battle_counttargeted(struct block_list *bl,struct block_list *src,int target_lv); +int battle_get_class(struct block_list *bl); +int battle_get_dir(struct block_list *bl); +int battle_get_lv(struct block_list *bl); +int battle_get_range(struct block_list *bl); +int battle_get_hp(struct block_list *bl); +int battle_get_max_hp(struct block_list *bl); +int battle_get_str(struct block_list *bl); +int battle_get_agi(struct block_list *bl); +int battle_get_vit(struct block_list *bl); +int battle_get_int(struct block_list *bl); +int battle_get_dex(struct block_list *bl); +int battle_get_luk(struct block_list *bl); +int battle_get_hit(struct block_list *bl); +int battle_get_flee(struct block_list *bl); +int battle_get_def(struct block_list *bl); +int battle_get_mdef(struct block_list *bl); +int battle_get_flee2(struct block_list *bl); +int battle_get_def2(struct block_list *bl); +int battle_get_mdef2(struct block_list *bl); +int battle_get_baseatk(struct block_list *bl); +int battle_get_atk(struct block_list *bl); +int battle_get_atk2(struct block_list *bl); +int battle_get_speed(struct block_list *bl); +int battle_get_adelay(struct block_list *bl); +int battle_get_amotion(struct block_list *bl); +int battle_get_dmotion(struct block_list *bl); +int battle_get_element(struct block_list *bl); +int battle_get_attack_element(struct block_list *bl); +int battle_get_attack_element2(struct block_list *bl); //左手武器属性取得 +#define battle_get_elem_type(bl) (battle_get_element(bl)%10) +#define battle_get_elem_level(bl) (battle_get_element(bl)/10/2) +int battle_get_party_id(struct block_list *bl); +int battle_get_guild_id(struct block_list *bl); +int battle_get_race(struct block_list *bl); +int battle_get_size(struct block_list *bl); +int battle_get_mode(struct block_list *bl); +int battle_get_mexp(struct block_list *bl); + +struct status_change *battle_get_sc_data(struct block_list *bl); +short *battle_get_sc_count(struct block_list *bl); +short *battle_get_opt1(struct block_list *bl); +short *battle_get_opt2(struct block_list *bl); +short *battle_get_opt3(struct block_list *bl); +short *battle_get_option(struct block_list *bl); + +enum { + BCT_NOENEMY =0x00000, + BCT_PARTY =0x10000, + BCT_ENEMY =0x40000, + BCT_NOPARTY =0x50000, + BCT_ALL =0x20000, + BCT_NOONE =0x60000, +}; + +int battle_check_undead(int race,int element); +int battle_check_target( struct block_list *src, struct block_list *target,int flag); +int battle_check_range(struct block_list *src,struct block_list *bl,int range); + + +// 設定 + +int battle_config_switch(const char *str); // [Valaris] + +extern struct Battle_Config { + int warp_point_debug; + int enemy_critical; + int enemy_critical_rate; + int enemy_str; + int enemy_perfect_flee; + int cast_rate,delay_rate,delay_dependon_dex; + int sdelay_attack_enable; + int left_cardfix_to_right; + int pc_skill_add_range; + int skill_out_range_consume; + int mob_skill_add_range; + int pc_damage_delay; + int pc_damage_delay_rate; + int defnotenemy; + int random_monster_checklv; + int attr_recover; + int flooritem_lifetime; + int item_auto_get; + int item_first_get_time; + int item_second_get_time; + int item_third_get_time; + int mvp_item_first_get_time; + int mvp_item_second_get_time; + int mvp_item_third_get_time; + int item_rate,base_exp_rate,job_exp_rate; // removed item rate, depreciated + int drop_rate0item; + int death_penalty_type; + int death_penalty_base,death_penalty_job; + int pvp_exp; // [MouseJstr] + int gtb_pvp_only; // [MouseJstr] + int zeny_penalty; + int restart_hp_rate; + int restart_sp_rate; + int mvp_item_rate,mvp_exp_rate; + int mvp_hp_rate; + int monster_hp_rate; + int monster_max_aspd; + int atc_gmonly; + int atc_spawn_quantity_limit; + int gm_allskill; + int gm_allskill_addabra; + int gm_allequip; + int gm_skilluncond; + int skillfree; + int skillup_limit; + int wp_rate; + int pp_rate; + int monster_active_enable; + int monster_damage_delay_rate; + int monster_loot_type; + int mob_skill_use; + int mob_count_rate; + int quest_skill_learn; + int quest_skill_reset; + int basic_skill_check; + int guild_emperium_check; + int guild_exp_limit; + int guild_max_castles; + int pc_invincible_time; + int pet_catch_rate; + int pet_rename; + int pet_friendly_rate; + int pet_hungry_delay_rate; + int pet_hungry_friendly_decrease; + int pet_str; + int pet_status_support; + int pet_attack_support; + int pet_damage_support; + int pet_support_rate; + int pet_attack_exp_to_master; + int pet_attack_exp_rate; + int skill_min_damage; + int finger_offensive_type; + int heal_exp; + int resurrection_exp; + int shop_exp; + int combo_delay_rate; + int item_check; + int wedding_modifydisplay; + int natural_healhp_interval; + int natural_healsp_interval; + int natural_heal_skill_interval; + int natural_heal_weight_rate; + int item_name_override_grffile; + int arrow_decrement; + int max_aspd; + int max_hp; + int max_sp; + int max_lv; + int max_parameter; + int max_cart_weight; + int pc_skill_log; + int mob_skill_log; + int battle_log; + int save_log; + int error_log; + int etc_log; + int save_clothcolor; + int undead_detect_type; + int pc_auto_counter_type; + int monster_auto_counter_type; + int agi_penaly_type; + int agi_penaly_count; + int agi_penaly_num; + int vit_penaly_type; + int vit_penaly_count; + int vit_penaly_num; + int player_defense_type; + int monster_defense_type; + int pet_defense_type; + int magic_defense_type; + int pc_skill_reiteration; + int monster_skill_reiteration; + int pc_skill_nofootset; + int monster_skill_nofootset; + int pc_cloak_check_type; + int monster_cloak_check_type; + int gvg_short_damage_rate; + int gvg_long_damage_rate; + int gvg_magic_damage_rate; + int gvg_misc_damage_rate; + int gvg_eliminate_time; + int mob_changetarget_byskill; + int pc_attack_direction_change; + int monster_attack_direction_change; + int pc_undead_nofreeze; + int pc_land_skill_limit; + int monster_land_skill_limit; + int party_skill_penaly; + int monster_class_change_full_recover; + int produce_item_name_input; + int produce_potion_name_input; + int making_arrow_name_input; + int holywater_name_input; + int display_delay_skill_fail; + int chat_warpportal; + int mob_warpportal; + int dead_branch_active; + int vending_max_value; +// int pet_lootitem; // removed [Valaris] +// int pet_weight; // removed [Valaris] + int show_steal_in_same_party; + int enable_upper_class; + int pet_attack_attr_none; + int mob_attack_attr_none; + int mob_ghostring_fix; + int pc_attack_attr_none; + int item_rate_common,item_rate_card,item_rate_equip,item_rate_heal,item_rate_use; // Added by RoVeRT, Additional Heal and Usable item rate by Val + int item_drop_common_min,item_drop_common_max; // Added by TyrNemesis^ + int item_drop_card_min,item_drop_card_max; + int item_drop_equip_min,item_drop_equip_max; + int item_drop_mvp_min,item_drop_mvp_max; // End Addition + int item_drop_heal_min,item_drop_heal_max; // Added by Valatris + int item_drop_use_min,item_drop_use_max; //End + + int prevent_logout; // Added by RoVeRT + + int alchemist_summon_reward; // [Valaris] + int maximum_level; + int drops_by_luk; + int monsters_ignore_gm; + int equipment_breaking; + int equipment_break_rate; + int pet_equip_required; + int multi_level_up; + int pk_mode; + int show_mob_hp; // end additions [Valaris] + + int agi_penaly_count_lv; + int vit_penaly_count_lv; + + int gx_allhit; + int gx_cardfix; + int gx_dupele; + int gx_disptype; + int player_skill_partner_check; + int hide_GM_session; + int unit_movement_type; + int invite_request_check; + int skill_removetrap_type; + int disp_experience; + int castle_defense_rate; + int riding_weight; + int backstab_bow_penalty; + + int night_at_start; // added by [Yor] + int day_duration; // added by [Yor] + int night_duration; // added by [Yor] + int ban_spoof_namer; // added by [Yor] + int hack_info_GM_level; // added by [Yor] + int any_warp_GM_min_level; // added by [Yor] + int packet_ver_flag; // added by [Yor] + int muting_players; // added by [Apple] + + int min_hair_style; // added by [MouseJstr] + int max_hair_style; // added by [MouseJstr] + int min_hair_color; // added by [MouseJstr] + int max_hair_color; // added by [MouseJstr] + int min_cloth_color; // added by [MouseJstr] + int max_cloth_color; // added by [MouseJstr] + + int castrate_dex_scale; // added by [MouseJstr] + int area_size; // added by [MouseJstr] + +#ifndef TXT_ONLY /* SQL-only options */ + int mail_system; // [Valaris] +#endif + +} battle_config; + +int battle_config_read(const char *cfgName); + +#endif diff --git a/src/map/chat.c b/src/map/chat.c new file mode 100644 index 0000000..ade4dcb --- /dev/null +++ b/src/map/chat.c @@ -0,0 +1,373 @@ +// $Id: chat.c,v 1.2 2004/09/22 02:59:47 Akitasha Exp $ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "db.h" +#include "nullpo.h" +#include "malloc.h" +#include "map.h" +#include "clif.h" +#include "pc.h" +#include "chat.h" +#include "npc.h" + +#ifdef MEMWATCH +#include "memwatch.h" +#endif + +int chat_triggerevent(struct chat_data *cd); + + +/*========================================== + * チャットルーム作成 + *------------------------------------------ + */ +int chat_createchat(struct map_session_data *sd,int limit,int pub,char* pass,char* title,int titlelen) +{ + struct chat_data *cd; + + nullpo_retr(0, sd); + + cd = aCalloc(1,sizeof(struct chat_data)); + + cd->limit = limit; + cd->pub = pub; + cd->users = 1; + memcpy(cd->pass,pass,8); + if(titlelen>=sizeof(cd->title)-1) titlelen=sizeof(cd->title)-1; + memcpy(cd->title,title,titlelen); + cd->title[titlelen]=0; + + cd->owner = (struct block_list **)(&cd->usersd[0]); + cd->usersd[0] = sd; + cd->bl.m = sd->bl.m; + cd->bl.x = sd->bl.x; + cd->bl.y = sd->bl.y; + cd->bl.type = BL_CHAT; + + cd->bl.id = map_addobject(&cd->bl); + if(cd->bl.id==0){ + clif_createchat(sd,1); + free(cd); + return 0; + } + pc_setchatid(sd,cd->bl.id); + + clif_createchat(sd,0); + clif_dispchat(cd,0); + + return 0; +} + +/*========================================== + * 既存チャットルームに参加 + *------------------------------------------ + */ +int chat_joinchat(struct map_session_data *sd,int chatid,char* pass) +{ + struct chat_data *cd; + + nullpo_retr(0, sd); + + cd=(struct chat_data*)map_id2bl(chatid); + if(cd==NULL) + return 1; + + if(cd->bl.m != sd->bl.m || cd->limit <= cd->users){ + clif_joinchatfail(sd,0); + return 0; + } + if(cd->pub==0 && strncmp(pass,cd->pass,8)){ + clif_joinchatfail(sd,1); + return 0; + } + + cd->usersd[cd->users] = sd; + cd->users++; + + pc_setchatid(sd,cd->bl.id); + + + clif_joinchatok(sd,cd); // 新たに参加した人には全員のリスト + clif_addchat(cd,sd); // 既に中に居た人には追加した人の報告 + clif_dispchat(cd,0); // 周囲の人には人数変化報告 + + chat_triggerevent(cd); // イベント + + return 0; +} + +/*========================================== + * チャットルームから抜ける + *------------------------------------------ + */ +int chat_leavechat(struct map_session_data *sd) +{ + struct chat_data *cd; + int i,leavechar; + + nullpo_retr(1, sd); + + cd=(struct chat_data*)map_id2bl(sd->chatID); + if(cd==NULL) + return 1; + + for(i = 0,leavechar=-1;i < cd->users;i++){ + if(cd->usersd[i] == sd){ + leavechar=i; + break; + } + } + if(leavechar<0) // そのchatに所属していないらしい (バグ時のみ) + return -1; + + if(leavechar==0 && cd->users>1 && (*cd->owner)->type==BL_PC){ + // 所有者だった&他に人が居る&PCのチャット + clif_changechatowner(cd,cd->usersd[1]); + clif_clearchat(cd,0); + } + + // 抜けるPCにも送るのでusersを減らす前に実行 + clif_leavechat(cd,sd); + + cd->users--; + pc_setchatid(sd,0); + + if(cd->users == 0 && (*cd->owner)->type==BL_PC){ + // 全員居なくなった&PCのチャットなので消す + clif_clearchat(cd,0); + map_delobject(cd->bl.id); // freeまでしてくれる + } else { + for(i=leavechar;i < cd->users;i++) + cd->usersd[i] = cd->usersd[i+1]; + if(leavechar==0 && (*cd->owner)->type==BL_PC){ + // PCのチャットなので所有者が抜けたので位置変更 + cd->bl.x=cd->usersd[0]->bl.x; + cd->bl.y=cd->usersd[0]->bl.y; + } + clif_dispchat(cd,0); + } + + return 0; +} + +/*========================================== + * チャットルームの持ち主を譲る + *------------------------------------------ + */ +int chat_changechatowner(struct map_session_data *sd,char *nextownername) +{ + struct chat_data *cd; + struct map_session_data *tmp_sd; + int i,nextowner; + + nullpo_retr(1, sd); + + cd=(struct chat_data*)map_id2bl(sd->chatID); + if(cd==NULL || (struct block_list *)sd!=(*cd->owner)) + return 1; + + for(i = 1,nextowner=-1;i < cd->users;i++){ + if(strcmp(cd->usersd[i]->status.name,nextownername)==0){ + nextowner=i; + break; + } + } + if(nextowner<0) // そんな人は居ない + return -1; + + clif_changechatowner(cd,cd->usersd[nextowner]); + // 一旦消す + clif_clearchat(cd,0); + + // userlistの順番変更 (0が所有者なので) + if( (tmp_sd = cd->usersd[0]) == NULL ) + return 1; //ありえるのかな? + cd->usersd[0] = cd->usersd[nextowner]; + cd->usersd[nextowner] = tmp_sd; + + // 新しい所有者の位置へ変更 + cd->bl.x=cd->usersd[0]->bl.x; + cd->bl.y=cd->usersd[0]->bl.y; + + // 再度表示 + clif_dispchat(cd,0); + + return 0; +} + +/*========================================== + * チャットの状態(タイトル等)を変更 + *------------------------------------------ + */ +int chat_changechatstatus(struct map_session_data *sd,int limit,int pub,char* pass,char* title,int titlelen) +{ + struct chat_data *cd; + + nullpo_retr(1, sd); + + cd=(struct chat_data*)map_id2bl(sd->chatID); + if(cd==NULL || (struct block_list *)sd!=(*cd->owner)) + return 1; + + cd->limit = limit; + cd->pub = pub; + memcpy(cd->pass,pass,8); + if(titlelen>=sizeof(cd->title)-1) titlelen=sizeof(cd->title)-1; + memcpy(cd->title,title,titlelen); + cd->title[titlelen]=0; + + clif_changechatstatus(cd); + clif_dispchat(cd,0); + + return 0; +} + +/*========================================== + * チャットルームから蹴り出す + *------------------------------------------ + */ +int chat_kickchat(struct map_session_data *sd,char *kickusername) +{ + struct chat_data *cd; + int i,kickuser; + + nullpo_retr(1, sd); + + cd=(struct chat_data*)map_id2bl(sd->chatID); + if(cd==NULL || (struct block_list *)sd!=(*cd->owner)) + return 1; + + for(i = 0,kickuser=-1;i < cd->users;i++){ + if(strcmp(cd->usersd[i]->status.name,kickusername)==0){ + kickuser=i; + break; + } + } + if(kickuser<0) // そんな人は居ない + return -1; + + chat_leavechat(cd->usersd[kickuser]); + + return 0; +} + +/*========================================== + * npcチャットルーム作成 + *------------------------------------------ + */ +int chat_createnpcchat(struct npc_data *nd,int limit,int pub,int trigger,char* title,int titlelen,const char *ev) +{ + struct chat_data *cd; + + nullpo_retr(1, nd); + + cd = aCalloc(1,sizeof(struct chat_data)); + + cd->limit = cd->trigger = limit; + if(trigger>0) + cd->trigger = trigger; + cd->pub = pub; + cd->users = 0; + memcpy(cd->pass,"",8); + if(titlelen>=sizeof(cd->title)-1) titlelen=sizeof(cd->title)-1; + memcpy(cd->title,title,titlelen); + cd->title[titlelen]=0; + + cd->bl.m = nd->bl.m; + cd->bl.x = nd->bl.x; + cd->bl.y = nd->bl.y; + cd->bl.type = BL_CHAT; + cd->owner_ = (struct block_list *)nd; + cd->owner = &cd->owner_; + memcpy(cd->npc_event,ev,sizeof(cd->npc_event)); + + cd->bl.id = map_addobject(&cd->bl); + if(cd->bl.id==0){ + free(cd); + return 0; + } + nd->chat_id=cd->bl.id; + + clif_dispchat(cd,0); + + return 0; +} +/*========================================== + * npcチャットルーム削除 + *------------------------------------------ + */ +int chat_deletenpcchat(struct npc_data *nd) +{ + struct chat_data *cd; + + nullpo_retr(0, nd); + nullpo_retr(0, cd=(struct chat_data*)map_id2bl(nd->chat_id)); + + chat_npckickall(cd); + clif_clearchat(cd,0); + map_delobject(cd->bl.id); // freeまでしてくれる + nd->chat_id=0; + + return 0; +} + +/*========================================== + * 規定人数以上でイベントが定義されてるなら実行 + *------------------------------------------ + */ +int chat_triggerevent(struct chat_data *cd) +{ + nullpo_retr(0, cd); + + if(cd->users>=cd->trigger && cd->npc_event[0]) + npc_event_do(cd->npc_event); + return 0; +} + +/*========================================== + * イベントの有効化 + *------------------------------------------ + */ +int chat_enableevent(struct chat_data *cd) +{ + nullpo_retr(0, cd); + + cd->trigger&=0x7f; + chat_triggerevent(cd); + return 0; +} +/*========================================== + * イベントの無効化 + *------------------------------------------ + */ +int chat_disableevent(struct chat_data *cd) +{ + nullpo_retr(0, cd); + + cd->trigger|=0x80; + return 0; +} +/*========================================== + * チャットルームから全員蹴り出す + *------------------------------------------ + */ +int chat_npckickall(struct chat_data *cd) +{ + nullpo_retr(0, cd); + + while(cd->users>0){ + chat_leavechat(cd->usersd[cd->users-1]); + } + return 0; +} + +/*========================================== + * 終了 + *------------------------------------------ + */ +int do_final_chat(void) +{ + return 0; +} diff --git a/src/map/chat.h b/src/map/chat.h new file mode 100644 index 0000000..2761602 --- /dev/null +++ b/src/map/chat.h @@ -0,0 +1,22 @@ +// $Id: chat.h,v 1.3 2004/09/25 05:32:18 MouseJstr Exp $ +#ifndef _CHAT_H_ +#define _CHAT_H_ + +#include "map.h" + +int chat_createchat(struct map_session_data *,int,int,char*,char*,int); +int chat_joinchat(struct map_session_data *,int,char*); +int chat_leavechat(struct map_session_data* ); +int chat_changechatowner(struct map_session_data *,char *); +int chat_changechatstatus(struct map_session_data *,int,int,char*,char*,int); +int chat_kickchat(struct map_session_data *,char *); + +int chat_createnpcchat(struct npc_data *nd,int limit,int pub,int trigger,char* title,int titlelen,const char *ev); +int chat_deletenpcchat(struct npc_data *nd); +int chat_enableevent(struct chat_data *cd); +int chat_disableevent(struct chat_data *cd); +int chat_npckickall(struct chat_data *cd); + +int do_final_chat(void); + +#endif diff --git a/src/map/chrif.c b/src/map/chrif.c new file mode 100644 index 0000000..dc401b6 --- /dev/null +++ b/src/map/chrif.c @@ -0,0 +1,1016 @@ +// $Id: chrif.c,v 1.6 2004/09/25 11:39:17 MouseJstr Exp $ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#ifdef LCCWIN32 +#include <winsock.h> +#else +#include <unistd.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#endif +#include <sys/types.h> +#include <time.h> + +#include "socket.h" +#include "timer.h" +#include "map.h" +#include "battle.h" +#include "chrif.h" +#include "clif.h" +#include "intif.h" +#include "npc.h" +#include "pc.h" +#include "nullpo.h" + +#ifdef MEMWATCH +#include "memwatch.h" +#endif + +static const int packet_len_table[0x20] = { + 60, 3,-1,27,22,-1, 6,-1, // 2af8-2aff + 6,-1,18, 7,-1,49,44, 0, // 2b00-2b07 + 6,30,-1,10,86, 7,44,34, // 2b08-2b0f + -1,-1,10, 6,11,-1, 0, 0, // 2b10-2b17 +}; + +int char_fd; +int srvinfo; +static char char_ip_str[16]; +static int char_ip; +static int char_port = 6121; +static char userid[24], passwd[24]; +static int chrif_state; + +// 設定ファイル読み込み関係 +/*========================================== + * + *------------------------------------------ + */ +void chrif_setuserid(char *id) +{ + strncpy(userid, id, 24); +} + +/*========================================== + * + *------------------------------------------ + */ +void chrif_setpasswd(char *pwd) +{ + strncpy(passwd, pwd, 24); +} + +/*========================================== + * + *------------------------------------------ + */ +void chrif_setip(char *ip) +{ + strncpy(char_ip_str, ip, 16); + char_ip = inet_addr(char_ip_str); +} + +/*========================================== + * + *------------------------------------------ + */ +void chrif_setport(int port) +{ + char_port = port; +} + +/*========================================== + * + *------------------------------------------ + */ +int chrif_isconnect(void) +{ + return chrif_state == 2; +} + +/*========================================== + * + *------------------------------------------ + */ +int chrif_save(struct map_session_data *sd) +{ + nullpo_retr(-1, sd); + + if (char_fd < 0) + return -1; + + pc_makesavestatus(sd); + + WFIFOW(char_fd,0) = 0x2b01; + WFIFOW(char_fd,2) = sizeof(sd->status) + 12; + WFIFOL(char_fd,4) = sd->bl.id; + WFIFOL(char_fd,8) = sd->char_id; + memcpy(WFIFOP(char_fd,12), &sd->status, sizeof(sd->status)); + WFIFOSET(char_fd, WFIFOW(char_fd,2)); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int chrif_connect(int fd) +{ + WFIFOW(fd,0) = 0x2af8; + memcpy(WFIFOP(fd,2), userid, 24); + memcpy(WFIFOP(fd,26), passwd, 24); + WFIFOL(fd,50) = 0; + WFIFOL(fd,54) = clif_getip(); + WFIFOW(fd,58) = clif_getport(); // [Valaris] thanks to fov + WFIFOSET(fd,60); + + return 0; +} + +/*========================================== + * マップ送信 + *------------------------------------------ + */ +int chrif_sendmap(int fd) +{ + int i; + + WFIFOW(fd,0) = 0x2afa; + for(i = 0; i < map_num; i++) + if (map[i].alias[0] != '\0') // [MouseJstr] map aliasing + memcpy(WFIFOP(fd,4+i*16), map[i].alias, 16); + else + memcpy(WFIFOP(fd,4+i*16), map[i].name, 16); + WFIFOW(fd,2) = 4 + i * 16; + WFIFOSET(fd,WFIFOW(fd,2)); + + return 0; +} + +/*========================================== + * マップ受信 + *------------------------------------------ + */ +int chrif_recvmap(int fd) +{ + int i, j, ip, port; + unsigned char *p = (unsigned char *)&ip; + + if (chrif_state < 2) // まだ準備中 + return -1; + + ip = RFIFOL(fd,4); + port = RFIFOW(fd,8); + for(i = 10, j = 0; i < RFIFOW(fd,2); i += 16, j++) { + map_setipport(RFIFOP(fd,i), ip, port); +// if (battle_config.etc_log) +// printf("recv map %d %s\n", j, RFIFOP(fd,i)); + } + if (battle_config.etc_log) + printf("recv map on %d.%d.%d.%d:%d (%d maps)\n", p[0], p[1], p[2], p[3], port, j); + + return 0; +} + +/*========================================== + * マップ鯖間移動のためのデータ準備要求 + *------------------------------------------ + */ +int chrif_changemapserver(struct map_session_data *sd, char *name, int x, int y, int ip, short port) +{ + int i, s_ip; + + nullpo_retr(-1, sd); + + s_ip = 0; + for(i = 0; i < fd_max; i++) + if (session[i] && session[i]->session_data == sd) { + s_ip = session[i]->client_addr.sin_addr.s_addr; + break; + } + + WFIFOW(char_fd, 0) = 0x2b05; + WFIFOL(char_fd, 2) = sd->bl.id; + WFIFOL(char_fd, 6) = sd->login_id1; + WFIFOL(char_fd,10) = sd->login_id2; + WFIFOL(char_fd,14) = sd->status.char_id; + memcpy(WFIFOP(char_fd,18), name, 16); + WFIFOW(char_fd,34) = x; + WFIFOW(char_fd,36) = y; + WFIFOL(char_fd,38) = ip; + WFIFOL(char_fd,42) = port; + WFIFOB(char_fd,44) = sd->status.sex; + WFIFOL(char_fd,45) = s_ip; + WFIFOSET(char_fd,49); + + return 0; +} + +/*========================================== + * マップ鯖間移動ack + *------------------------------------------ + */ +int chrif_changemapserverack(int fd) +{ + struct map_session_data *sd = map_id2sd(RFIFOL(fd,2)); + + if (sd == NULL || sd->status.char_id != RFIFOL(fd,14)) + return -1; + + if (RFIFOL(fd,6) == 1) { + if (battle_config.error_log) + printf("map server change failed.\n"); + pc_authfail(sd->fd); + return 0; + } + clif_changemapserver(sd, RFIFOP(fd,18), RFIFOW(fd,34), RFIFOW(fd,36), RFIFOL(fd,38), RFIFOW(fd,42)); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int chrif_connectack(int fd) +{ + if (RFIFOB(fd,2)) { + printf("Connected to char-server failed %d.\n", RFIFOB(fd,2)); + exit(1); + } + printf("Connected to char-server (connection #%d).\n", fd); + chrif_state = 1; + + chrif_sendmap(fd); + + printf("chrif: OnCharIfInit event done. (%d events)\n", npc_event_doall("OnCharIfInit")); + printf("chrif: OnInterIfInit event done. (%d events)\n", npc_event_doall("OnInterIfInit")); + + // <Agit> Run Event [AgitInit] +// printf("NPC_Event:[OnAgitInit] do (%d) events (Agit Initialize).\n", npc_event_doall("OnAgitInit")); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int chrif_sendmapack(int fd) +{ + if (RFIFOB(fd,2)) { + printf("chrif : send map list to char server failed %d\n", RFIFOB(fd,2)); + exit(1); + } + + memcpy(wisp_server_name, RFIFOP(fd,3), 24); + + chrif_state = 2; + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int chrif_authreq(struct map_session_data *sd) +{ + int i; + + nullpo_retr(-1, sd); + + if (!sd || !char_fd || !sd->bl.id || !sd->login_id1) + return -1; + + for(i = 0; i < fd_max; i++) + if (session[i] && session[i]->session_data == sd) { + WFIFOW(char_fd, 0) = 0x2afc; + WFIFOL(char_fd, 2) = sd->bl.id; + WFIFOL(char_fd, 6) = sd->char_id; + WFIFOL(char_fd,10) = sd->login_id1; + WFIFOL(char_fd,14) = sd->login_id2; + WFIFOL(char_fd,18) = session[i]->client_addr.sin_addr.s_addr; + WFIFOSET(char_fd,22); + break; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int chrif_charselectreq(struct map_session_data *sd) +{ + int i, s_ip; + + nullpo_retr(-1, sd); + + if(!sd || !char_fd || !sd->bl.id || !sd->login_id1) + return -1; + + s_ip = 0; + for(i = 0; i < fd_max; i++) + if (session[i] && session[i]->session_data == sd) { + s_ip = session[i]->client_addr.sin_addr.s_addr; + break; + } + + WFIFOW(char_fd, 0) = 0x2b02; + WFIFOL(char_fd, 2) = sd->bl.id; + WFIFOL(char_fd, 6) = sd->login_id1; + WFIFOL(char_fd,10) = sd->login_id2; + WFIFOL(char_fd,14) = s_ip; + WFIFOSET(char_fd,18); + + return 0; +} + +/*========================================== + * キャラ名問い合わせ + *------------------------------------------ + */ +int chrif_searchcharid(int char_id) +{ + if (!char_id) + return -1; + + WFIFOW(char_fd,0) = 0x2b08; + WFIFOL(char_fd,2) = char_id; + WFIFOSET(char_fd,6); + + return 0; +} + +/*========================================== + * GMに変化要求 + *------------------------------------------ + */ +int chrif_changegm(int id, const char *pass, int len) +{ + if (battle_config.etc_log) + printf("chrif_changegm: account: %d, password: '%s'.\n", id, pass); + + WFIFOW(char_fd,0) = 0x2b0a; + WFIFOW(char_fd,2) = len + 8; + WFIFOL(char_fd,4) = id; + memcpy(WFIFOP(char_fd,8), pass, len); + WFIFOSET(char_fd, len + 8); + + return 0; +} + +/*========================================== + * Change Email + *------------------------------------------ + */ +int chrif_changeemail(int id, const char *actual_email, const char *new_email) +{ + if (battle_config.etc_log) + printf("chrif_changeemail: account: %d, actual_email: '%s', new_email: '%s'.\n", id, actual_email, new_email); + + WFIFOW(char_fd,0) = 0x2b0c; + WFIFOL(char_fd,2) = id; + memcpy(WFIFOP(char_fd,6), actual_email, 40); + memcpy(WFIFOP(char_fd,46), new_email, 40); + WFIFOSET(char_fd,86); + + return 0; +} + +/*========================================== + * Send message to char-server with a character name to do some operations (by Yor) + * Used to ask Char-server about a character name to have the account number to modify account file in login-server. + * type of operation: + * 1: block + * 2: ban + * 3: unblock + * 4: unban + * 5: changesex + *------------------------------------------ + */ +int chrif_char_ask_name(int id, char * character_name, short operation_type, int year, int month, int day, int hour, int minute, int second) +{ + WFIFOW(char_fd, 0) = 0x2b0e; + WFIFOL(char_fd, 2) = id; // account_id of who ask (for answer) -1 if nobody + memcpy(WFIFOP(char_fd,6), character_name, 24); + WFIFOW(char_fd, 30) = operation_type; // type of operation + if (operation_type == 2) { + WFIFOW(char_fd, 32) = year; + WFIFOW(char_fd, 34) = month; + WFIFOW(char_fd, 36) = day; + WFIFOW(char_fd, 38) = hour; + WFIFOW(char_fd, 40) = minute; + WFIFOW(char_fd, 42) = second; + } + printf("chrif : sended 0x2b0e\n"); + WFIFOSET(char_fd,44); + + return 0; +} + +/*========================================== + * Answer after a request about a character name to do some operations (by Yor) + * Used to answer of chrif_char_ask_name. + * type of operation: + * 1: block + * 2: ban + * 3: unblock + * 4: unban + * 5: changesex + * type of answer: + * 0: login-server resquest done + * 1: player not found + * 2: gm level too low + * 3: login-server offline + *------------------------------------------ + */ +int chrif_char_ask_name_answer(int fd) +{ + int acc; + struct map_session_data *sd; + char output[256]; + char player_name[24]; + + acc = RFIFOL(fd,2); // account_id of who has asked (-1 if nobody) + memcpy(player_name, RFIFOP(fd,6), sizeof(player_name)); + player_name[sizeof(player_name)-1] = '\0'; + + sd = map_id2sd(acc); + if (acc >= 0 && sd != NULL) { + if (RFIFOW(fd, 32) == 1) // player not found + sprintf(output, "The player '%s' doesn't exist.", player_name); + else { + switch(RFIFOW(fd, 30)) { + case 1: // block + switch(RFIFOW(fd, 32)) { + case 0: // login-server resquest done + sprintf(output, "Login-server has been asked to block the player '%s'.", player_name); + break; + //case 1: // player not found + case 2: // gm level too low + sprintf(output, "Your GM level don't authorise you to block the player '%s'.", player_name); + break; + case 3: // login-server offline + sprintf(output, "Login-server is offline. Impossible to block the the player '%s'.", player_name); + break; + } + break; + case 2: // ban + switch(RFIFOW(fd, 32)) { + case 0: // login-server resquest done + sprintf(output, "Login-server has been asked to ban the player '%s'.", player_name); + break; + //case 1: // player not found + case 2: // gm level too low + sprintf(output, "Your GM level don't authorise you to ban the player '%s'.", player_name); + break; + case 3: // login-server offline + sprintf(output, "Login-server is offline. Impossible to ban the the player '%s'.", player_name); + break; + } + break; + case 3: // unblock + switch(RFIFOW(fd, 32)) { + case 0: // login-server resquest done + sprintf(output, "Login-server has been asked to unblock the player '%s'.", player_name); + break; + //case 1: // player not found + case 2: // gm level too low + sprintf(output, "Your GM level don't authorise you to unblock the player '%s'.", player_name); + break; + case 3: // login-server offline + sprintf(output, "Login-server is offline. Impossible to unblock the the player '%s'.", player_name); + break; + } + break; + case 4: // unban + switch(RFIFOW(fd, 32)) { + case 0: // login-server resquest done + sprintf(output, "Login-server has been asked to unban the player '%s'.", player_name); + break; + //case 1: // player not found + case 2: // gm level too low + sprintf(output, "Your GM level don't authorise you to unban the player '%s'.", player_name); + break; + case 3: // login-server offline + sprintf(output, "Login-server is offline. Impossible to unban the the player '%s'.", player_name); + break; + } + break; + case 5: // changesex + switch(RFIFOW(fd, 32)) { + case 0: // login-server resquest done + sprintf(output, "Login-server has been asked to change the sex of the player '%s'.", player_name); + break; + //case 1: // player not found + case 2: // gm level too low + sprintf(output, "Your GM level don't authorise you to change the sex of the player '%s'.", player_name); + break; + case 3: // login-server offline + sprintf(output, "Login-server is offline. Impossible to change the sex of the the player '%s'.", player_name); + break; + } + break; + } + } + if (output[0] != '\0') { + output[sizeof(output)-1] = '\0'; + clif_displaymessage(sd->fd, output); + } + } else + printf("chrif_char_ask_name_answer failed - player not online.\n"); + + return 0; +} + +/*========================================== + * End of GM change (@GM) (modified by Yor) + *------------------------------------------ + */ +int chrif_changedgm(int fd) +{ + int acc, level; + struct map_session_data *sd = NULL; + + acc = RFIFOL(fd,2); + level = RFIFOL(fd,6); + + sd = map_id2sd(acc); + + if (battle_config.etc_log) + printf("chrif_changedgm: account: %d, GM level 0 -> %d.\n", acc, level); + if (sd != NULL) { + if (level > 0) + clif_displaymessage(sd->fd, "GM modification success."); + else + clif_displaymessage(sd->fd, "Failure of GM modification."); + } + + return 0; +} + +/*========================================== + * 性別変化終了 (modified by Yor) + *------------------------------------------ + */ +int chrif_changedsex(int fd) +{ + int acc, sex, i; + struct map_session_data *sd; + struct pc_base_job s_class; + + acc = RFIFOL(fd,2); + sex = RFIFOL(fd,6); + if (battle_config.etc_log) + printf("chrif_changedsex %d.\n", acc); + sd = map_id2sd(acc); + if (acc > 0) { + if (sd != NULL && sd->status.sex != sex) { + s_class = pc_calc_base_job(sd->status.class); + if (sd->status.sex == 0) { + sd->status.sex = 1; + sd->sex = 1; + } else if (sd->status.sex == 1) { + sd->status.sex = 0; + sd->sex = 0; + } + // to avoid any problem with equipment and invalid sex, equipment is unequiped. + for (i = 0; i < MAX_INVENTORY; i++) { + if (sd->status.inventory[i].nameid && sd->status.inventory[i].equip) + pc_unequipitem((struct map_session_data*)sd, i, 0); + } + // reset skill of some job + if (s_class.job == 19 || s_class.job == 4020 || s_class.job == 4042 || + s_class.job == 20 || s_class.job == 4021 || s_class.job == 4043) { + // remove specifical skills of classes 19, 4020 and 4042 + for(i = 315; i <= 322; i++) { + if (sd->status.skill[i].id > 0 && !sd->status.skill[i].flag) { + sd->status.skill_point += sd->status.skill[i].lv; + sd->status.skill[i].id = 0; + sd->status.skill[i].lv = 0; + } + } + // remove specifical skills of classes 20, 4021 and 4043 + for(i = 323; i <= 330; i++) { + if (sd->status.skill[i].id > 0 && !sd->status.skill[i].flag) { + sd->status.skill_point += sd->status.skill[i].lv; + sd->status.skill[i].id = 0; + sd->status.skill[i].lv = 0; + } + } + clif_updatestatus(sd, SP_SKILLPOINT); + // change job if necessary + if (s_class.job == 20 || s_class.job == 4021 || s_class.job == 4043) + sd->status.class -= 1; + else if (s_class.job == 19 || s_class.job == 4020 || s_class.job == 4042) + sd->status.class += 1; + } + // save character + chrif_save(sd); + sd->login_id1++; // change identify, because if player come back in char within the 5 seconds, he can change its characters + // do same modify in login-server for the account, but no in char-server (it ask again login_id1 to login, and don't remember it) + clif_displaymessage(sd->fd, "Your sex has been changed (need disconexion by the server)..."); + clif_setwaitclose(sd->fd); // forced to disconnect for the change + } + } else { + if (sd != NULL) { + printf("chrif_changedsex failed.\n"); + } + } + + return 0; +} + +/*========================================== + * アカウント変数保存要求 + *------------------------------------------ + */ +int chrif_saveaccountreg2(struct map_session_data *sd) +{ + int p, j; + nullpo_retr(-1, sd); + + p = 8; + for(j = 0; j < sd->status.account_reg2_num; j++) { + struct global_reg *reg = &sd->status.account_reg2[j]; + if (reg->str[0] && reg->value != 0) { + memcpy(WFIFOP(char_fd,p), reg->str, 32); + WFIFOL(char_fd,p+32) = reg->value; + p += 36; + } + } + WFIFOW(char_fd,0) = 0x2b10; + WFIFOW(char_fd,2) = p; + WFIFOL(char_fd,4) = sd->bl.id; + WFIFOSET(char_fd,p); + + return 0; +} + +/*========================================== + * アカウント変数通知 + *------------------------------------------ + */ +int chrif_accountreg2(int fd) +{ + int j, p; + struct map_session_data *sd; + + if ((sd = map_id2sd(RFIFOL(fd,4))) == NULL) + return 1; + + for(p = 8, j = 0; p < RFIFOW(fd,2) && j < ACCOUNT_REG2_NUM; p += 36, j++) { + memcpy(sd->status.account_reg2[j].str, RFIFOP(fd,p), 32); + sd->status.account_reg2[j].value = RFIFOL(fd, p + 32); + } + sd->status.account_reg2_num = j; +// printf("chrif: accountreg2\n"); + + return 0; +} + +/*========================================== + * 離婚情報同期要求 + *------------------------------------------ + */ +int chrif_divorce(int char_id, int partner_id) +{ + struct map_session_data *sd = NULL; + + if (!char_id || !partner_id) + return 0; + + nullpo_retr(0, sd = map_nick2sd(map_charid2nick(partner_id))); + if (sd->status.partner_id == char_id) { + int i; + //離婚(相方は既にキャラが消えている筈なので) + sd->status.partner_id = 0; + + //相方の結婚指輪を剥奪 + for(i = 0; i < MAX_INVENTORY; i++) + if (sd->status.inventory[i].nameid == WEDDING_RING_M || sd->status.inventory[i].nameid == WEDDING_RING_F) + pc_delitem(sd, i, 1, 0); + } + + return 0; +} + +/*========================================== + * Disconnection of a player (account has been deleted in login-server) by [Yor] + *------------------------------------------ + */ +int chrif_accountdeletion(int fd) +{ + int acc; + struct map_session_data *sd; + + acc = RFIFOL(fd,2); + if (battle_config.etc_log) + printf("chrif_accountdeletion %d.\n", acc); + sd = map_id2sd(acc); + if (acc > 0) { + if (sd != NULL) { + sd->login_id1++; // change identify, because if player come back in char within the 5 seconds, he can change its characters + clif_displaymessage(sd->fd, "Your account has been deleted (disconnexion)..."); + clif_setwaitclose(sd->fd); // forced to disconnect for the change + } + } else { + if (sd != NULL) + printf("chrif_accountdeletion failed - player not online.\n"); + } + + return 0; +} + +/*========================================== + * Disconnection of a player (account has been banned of has a status, from login-server) by [Yor] + *------------------------------------------ + */ +int chrif_accountban(int fd) +{ + int acc; + struct map_session_data *sd; + + acc = RFIFOL(fd,2); + if (battle_config.etc_log) + printf("chrif_accountban %d.\n", acc); + sd = map_id2sd(acc); + if (acc > 0) { + if (sd != NULL) { + sd->login_id1++; // change identify, because if player come back in char within the 5 seconds, he can change its characters + if (RFIFOB(fd,6) == 0) { // 0: change of statut, 1: ban + switch (RFIFOL(fd,7)) { // status or final date of a banishment + case 1: // 0 = Unregistered ID + clif_displaymessage(sd->fd, "Your account has 'Unregistered'."); + break; + case 2: // 1 = Incorrect Password + clif_displaymessage(sd->fd, "Your account has an 'Incorrect Password'..."); + break; + case 3: // 2 = This ID is expired + clif_displaymessage(sd->fd, "Your account has expired."); + break; + case 4: // 3 = Rejected from Server + clif_displaymessage(sd->fd, "Your account has been rejected from server."); + break; + case 5: // 4 = You have been blocked by the GM Team + clif_displaymessage(sd->fd, "Your account has been blocked by the GM Team."); + break; + case 6: // 5 = Your Game's EXE file is not the latest version + clif_displaymessage(sd->fd, "Your Game's EXE file is not the latest version."); + break; + case 7: // 6 = Your are Prohibited to log in until %s + clif_displaymessage(sd->fd, "Your account has been prohibited to log in."); + break; + case 8: // 7 = Server is jammed due to over populated + clif_displaymessage(sd->fd, "Server is jammed due to over populated."); + break; + case 9: // 8 = No MSG (actually, all states after 9 except 99 are No MSG, use only this) + clif_displaymessage(sd->fd, "Your account has not more authorised."); + break; + case 100: // 99 = This ID has been totally erased + clif_displaymessage(sd->fd, "Your account has been totally erased."); + break; + default: + clif_displaymessage(sd->fd, "Your account has not more authorised."); + break; + } + } else if (RFIFOB(fd,6) == 1) { // 0: change of statut, 1: ban + time_t timestamp; + char tmpstr[2048]; + timestamp = (time_t)RFIFOL(fd,7); // status or final date of a banishment + strcpy(tmpstr, "Your account has been banished until "); + strftime(tmpstr + strlen(tmpstr), 24, "%d-%m-%Y %H:%M:%S", localtime(×tamp)); + clif_displaymessage(sd->fd, tmpstr); + } + clif_setwaitclose(sd->fd); // forced to disconnect for the change + } + } else { + if (sd != NULL) + printf("chrif_accountban failed - player not online.\n"); + } + + return 0; +} + +/*========================================== + * Receiving GM accounts and their levels from char-server by [Yor] + *------------------------------------------ + */ +int chrif_recvgmaccounts(int fd) +{ + printf("From login-server: receiving of %d GM accounts information.\n", pc_read_gm_account(fd)); + + return 0; +} + +/*========================================== + * Request to reload GM accounts and their levels: send to char-server by [Yor] + *------------------------------------------ + */ +int chrif_reloadGMdb(void) +{ + + WFIFOW(char_fd,0) = 0x2af7; + WFIFOSET(char_fd, 2); + + return 0; +} + +/*========================================== + * Send rates and motd to char server [Wizputer] + *------------------------------------------ + */ + int chrif_ragsrvinfo(int base_rate, int job_rate, int drop_rate) +{ + char buf[256]; + FILE *fp; + int i; + + WFIFOW(char_fd,0) = 0x2b16; + WFIFOW(char_fd,2) = base_rate; + WFIFOW(char_fd,4) = job_rate; + WFIFOW(char_fd,6) = drop_rate; + + if ((fp = fopen(motd_txt, "r")) != NULL) { + if (fgets(buf, 250, fp) != NULL) { + for(i = 0; buf[i]; i++) { + if (buf[i] == '\r' || buf[i] == '\n') { + buf[i] = 0; + break; + } + } + WFIFOW(char_fd,8) = sizeof(buf) + 10; + memcpy(WFIFOP(char_fd,10), buf, sizeof(buf)); + } + fclose(fp); + } else { + WFIFOW(char_fd,8) = sizeof(buf) + 10; + memcpy(WFIFOP(char_fd,10), buf, sizeof(buf)); + } + WFIFOSET(char_fd,WFIFOW(char_fd,8)); + + return 0; +} + +/*========================================= + * Tell char-server charcter disconnected [Wizputer] + *----------------------------------------- + */ + +int chrif_char_offline(struct map_session_data *sd) +{ + if (char_fd < 0) + return -1; + + WFIFOW(char_fd,0) = 0x2b17; + WFIFOL(char_fd,2) = sd->status.char_id; + WFIFOSET(char_fd,6); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int chrif_parse(int fd) +{ + int packet_len, cmd; + + // only char-server can have an access to here. + // so, if it isn't the char-server, we disconnect the session (fd != char_fd). + if (fd != char_fd || session[fd]->eof) { + if (fd == char_fd) { + printf("Map-server can't connect to char-server (connection #%d).\n", fd); + char_fd = -1; + } + close(fd); + delete_session(fd); + return 0; + } + + while (RFIFOREST(fd) >= 2) { + cmd = RFIFOW(fd,0); + if (cmd < 0x2af8 || cmd >= 0x2af8 + (sizeof(packet_len_table) / sizeof(packet_len_table[0])) || + packet_len_table[cmd-0x2af8] == 0) { + + int r = intif_parse(fd); // intifに渡す + + if (r == 1) continue; // intifで処理した + if (r == 2) return 0; // intifで処理したが、データが足りない + + session[fd]->eof = 1; + return 0; + } + packet_len = packet_len_table[cmd-0x2af8]; + if (packet_len == -1) { + if (RFIFOREST(fd) < 4) + return 0; + packet_len = RFIFOW(fd,2); + } + if (RFIFOREST(fd) < packet_len) + return 0; + + switch(cmd) { + case 0x2af9: chrif_connectack(fd); break; + case 0x2afb: chrif_sendmapack(fd); break; + case 0x2afd: pc_authok(RFIFOL(fd,4), RFIFOL(fd,8), (time_t)RFIFOL(fd,12), (struct mmo_charstatus*)RFIFOP(fd,16)); break; + case 0x2afe: pc_authfail(RFIFOL(fd,2)); break; + case 0x2b00: map_setusers(RFIFOL(fd,2)); break; + case 0x2b03: clif_charselectok(RFIFOL(fd,2)); break; + case 0x2b04: chrif_recvmap(fd); break; + case 0x2b06: chrif_changemapserverack(fd); break; + case 0x2b09: map_addchariddb(RFIFOL(fd,2), RFIFOP(fd,6)); break; + case 0x2b0b: chrif_changedgm(fd); break; + case 0x2b0d: chrif_changedsex(fd); break; + case 0x2b0f: chrif_char_ask_name_answer(fd); break; + case 0x2b11: chrif_accountreg2(fd); break; + case 0x2b12: chrif_divorce(RFIFOL(fd,2), RFIFOL(fd,6)); break; + case 0x2b13: chrif_accountdeletion(fd); break; + case 0x2b14: chrif_accountban(fd); break; + case 0x2b15: chrif_recvgmaccounts(fd); break; + + default: + if (battle_config.error_log) + printf("chrif_parse : unknown packet %d %d\n", fd, RFIFOW(fd,0)); + session[fd]->eof = 1; + return 0; + } + RFIFOSKIP(fd, packet_len); + } + + return 0; +} + +/*========================================== + * timer関数 + * 今このmap鯖に繋がっているクライアント人数をchar鯖へ送る + *------------------------------------------ + */ +int send_users_tochar(int tid, unsigned int tick, int id, int data) { + int users = 0, i; + struct map_session_data *sd; + + if (char_fd <= 0 || session[char_fd] == NULL) + return 0; + + WFIFOW(char_fd,0) = 0x2aff; + for (i = 0; i < fd_max; i++) { + if (session[i] && (sd = session[i]->session_data) && sd->state.auth && + !((battle_config.hide_GM_session || (sd->status.option & OPTION_HIDE)) && pc_isGM(sd))) { + WFIFOL(char_fd,6+4*users) = sd->status.char_id; + users++; + } + } + WFIFOW(char_fd,2) = 6 + 4 * users; + WFIFOW(char_fd,4) = users; + WFIFOSET(char_fd,6+4*users); + + return 0; +} + +/*========================================== + * timer関数 + * char鯖との接続を確認し、もし切れていたら再度接続する + *------------------------------------------ + */ +int check_connect_char_server(int tid, unsigned int tick, int id, int data) { + if (char_fd <= 0 || session[char_fd] == NULL) { + printf("Attempt to connect to char-server...\n"); + chrif_state = 0; + char_fd = make_connection(char_ip, char_port); + session[char_fd]->func_parse = chrif_parse; + realloc_fifo(char_fd, FIFOSIZE_SERVERLINK, FIFOSIZE_SERVERLINK); + + chrif_connect(char_fd); +#ifndef TXT_ONLY + srvinfo = 0; + } else { + if (srvinfo == 0) { + chrif_ragsrvinfo(battle_config.base_exp_rate, battle_config.job_exp_rate, battle_config.item_rate_common); + srvinfo = 1; + } +#endif /* not TXT_ONLY */ + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int do_init_chrif(void) { + add_timer_func_list(check_connect_char_server, "check_connect_char_server"); + add_timer_func_list(send_users_tochar, "send_users_tochar"); + add_timer_interval(gettick() + 1000, check_connect_char_server, 0, 0, 10 * 1000); + add_timer_interval(gettick() + 1000, send_users_tochar, 0, 0, 5 * 1000); + + return 0; +} diff --git a/src/map/chrif.h b/src/map/chrif.h new file mode 100644 index 0000000..19d725d --- /dev/null +++ b/src/map/chrif.h @@ -0,0 +1,29 @@ +// $Id: chrif.h,v 1.3 2004/09/25 11:39:17 MouseJstr Exp $ +#ifndef _CHRIF_H_ +#define _CHRIF_H_ + +void chrif_setuserid(char*); +void chrif_setpasswd(char*); +void chrif_setip(char*); +void chrif_setport(int); + +int chrif_isconnect(void); + +int chrif_authreq(struct map_session_data *); +int chrif_save(struct map_session_data*); +int chrif_charselectreq(struct map_session_data *); + +int chrif_changemapserver(struct map_session_data *sd,char *name,int x,int y,int ip,short port); + +int chrif_searchcharid(int char_id); +int chrif_changegm(int id,const char *pass,int len); +int chrif_changeemail(int id, const char *actual_email, const char *new_email); +int chrif_char_ask_name(int id, char * character_name, short operation_type, int year, int month, int day, int hour, int minute, int second); +int chrif_saveaccountreg2(struct map_session_data *sd); +int chrif_reloadGMdb(void); +int chrif_ragsrvinfo(int base_rate,int job_rate, int drop_rate); +int chrif_char_offline(struct map_session_data *sd); + +int do_init_chrif(void); + +#endif diff --git a/src/map/clif.c b/src/map/clif.c new file mode 100644 index 0000000..1f320b2 --- /dev/null +++ b/src/map/clif.c @@ -0,0 +1,9826 @@ +// $Id: clif.c 164 2004-10-01 16:46:58Z $ + +#define DUMP_UNKNOWN_PACKET 1 + +#include <stdio.h> +#include <ctype.h> +#include <stdlib.h> +#include <string.h> +#include <stdarg.h> +#include <sys/types.h> +#ifdef LCCWIN32 +#include <winsock.h> +#else +#include <unistd.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#endif +#include <time.h> + +#include "socket.h" +#include "timer.h" +#include "malloc.h" +#include "version.h" +#include "nullpo.h" + +#include "map.h" +#include "chrif.h" +#include "clif.h" +#include "pc.h" +#include "npc.h" +#include "itemdb.h" +#include "chat.h" +#include "trade.h" +#include "storage.h" +#include "script.h" +#include "skill.h" +#include "atcommand.h" +#include "intif.h" +#include "battle.h" +#include "mob.h" +#include "party.h" +#include "guild.h" +#include "vending.h" +#include "pet.h" + +#ifdef MEMWATCH +#include "memwatch.h" +#endif + +#define STATE_BLIND 0x10 + +static const int packet_len_table[0x220] = { + 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +//#0x0040 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 55, 17, 3, 37, 46, -1, 23, -1, 3,108, 3, 2, +#if PACKETVER < 2 + 3, 28, 19, 11, 3, -1, 9, 5, 52, 51, 56, 58, 41, 2, 6, 6, +#else // 78-7b 亀島以降 lv99エフェクト用 + 3, 28, 19, 11, 3, -1, 9, 5, 54, 53, 58, 60, 41, 2, 6, 6, +#endif +//#0x0080 + 7, 3, 2, 2, 2, 5, 16, 12, 10, 7, 29, 23, -1, -1, -1, 0, // 0x8b unknown... size 2 or 23? + 7, 22, 28, 2, 6, 30, -1, -1, 3, -1, -1, 5, 9, 17, 17, 6, + 23, 6, 6, -1, -1, -1, -1, 8, 7, 6, 7, 4, 7, 0, -1, 6, + 8, 8, 3, 3, -1, 6, 6, -1, 7, 6, 2, 5, 6, 44, 5, 3, +//#0x00C0 + 7, 2, 6, 8, 6, 7, -1, -1, -1, -1, 3, 3, 6, 6, 2, 27, + 3, 4, 4, 2, -1, -1, 3, -1, 6, 14, 3, -1, 28, 29, -1, -1, + 30, 30, 26, 2, 6, 26, 3, 3, 8, 19, 5, 2, 3, 2, 2, 2, + 3, 2, 6, 8, 21, 8, 8, 2, 2, 26, 3, -1, 6, 27, 30, 10, + +//#0x0100 + 2, 6, 6, 30, 79, 31, 10, 10, -1, -1, 4, 6, 6, 2, 11, -1, + 10, 39, 4, 10, 31, 35, 10, 18, 2, 13, 15, 20, 68, 2, 3, 16, + 6, 14, -1, -1, 21, 8, 8, 8, 8, 8, 2, 2, 3, 4, 2, -1, + 6, 86, 6, -1, -1, 7, -1, 6, 3, 16, 4, 4, 4, 6, 24, 26, +//#0x0140 + 22, 14, 6, 10, 23, 19, 6, 39, 8, 9, 6, 27, -1, 2, 6, 6, + 110, 6, -1, -1, -1, -1, -1, 6, -1, 54, 66, 54, 90, 42, 6, 42, + -1, -1, -1, -1, -1, 30, -1, 3, 14, 3, 30, 10, 43, 14,186,182, + 14, 30, 10, 3, -1, 6,106, -1, 4, 5, 4, -1, 6, 7, -1, -1, +//#0x0180 + 6, 3,106, 10, 10, 34, 0, 6, 8, 4, 4, 4, 29, -1, 10, 6, +#if PACKETVER < 1 + 90, 86, 24, 6, 30,102, 8, 4, 8, 4, 14, 10, -1, 6, 2, 6, +#else // 196 comodo以降 状態表示アイコン用 + 90, 86, 24, 6, 30,102, 9, 4, 8, 4, 14, 10, -1, 6, 2, 6, +#endif + 3, 3, 35, 5, 11, 26, -1, 4, 4, 6, 10, 12, 6, -1, 4, 4, + 11, 7, -1, 67, 12, 18,114, 6, 3, 6, 26, 26, 26, 26, 2, 3, +//#0x01C0, Set 0x1d5=-1 + 2, 14, 10, -1, 22, 22, 4, 2, 13, 97, 0, 9, 9, 30, 6, 28, + 8, 14, 10, 35, 6, -1, 4, 11, 54, 53, 60, 2, -1, 47, 33, 6, + 30, 8, 34, 14, 2, 6, 26, 2, 28, 81, 6, 10, 26, 2, -1, -1, + -1, -1, 20, 10, 32, 9, 34, 14, 2, 6, 48, 56, -1, 4, 5, 10, +//#0x200 + 26, -1, 26, 10, 18, 26, 11, 34, 14, 36, 10, 19, 0, -1, 24, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +}; + +// size list for each packet version after packet version 4. +static int packet_size_table[6][0x220]; + +// local define +enum { + ALL_CLIENT, + ALL_SAMEMAP, + AREA, + AREA_WOS, + AREA_WOC, + AREA_WOSC, + AREA_CHAT_WOC, + CHAT, + CHAT_WOS, + PARTY, + PARTY_WOS, + PARTY_SAMEMAP, + PARTY_SAMEMAP_WOS, + PARTY_AREA, + PARTY_AREA_WOS, + GUILD, + GUILD_WOS, + GUILD_SAMEMAP, // [Valaris] + GUILD_SAMEMAP_WOS, + GUILD_AREA, + GUILD_AREA_WOS, // end additions [Valaris] + SELF +}; + +#define WBUFPOS(p,pos,x,y) { unsigned char *__p = (p); __p+=(pos); __p[0] = (x)>>2; __p[1] = ((x)<<6) | (((y)>>4)&0x3f); __p[2] = (y)<<4; } +#define WBUFPOS2(p,pos,x0,y0,x1,y1) { unsigned char *__p = (p); __p+=(pos); __p[0] = (x0)>>2; __p[1] = ((x0)<<6) | (((y0)>>4)&0x3f); __p[2] = ((y0)<<4) | (((x1)>>6)&0x0f); __p[3]=((x1)<<2) | (((y1)>>8)&0x03); __p[4]=(y1); } + +#define WFIFOPOS(fd,pos,x,y) { WBUFPOS (WFIFOP(fd,pos),0,x,y); } +#define WFIFOPOS2(fd,pos,x0,y0,x1,y1) { WBUFPOS2(WFIFOP(fd,pos),0,x0,y0,x1,y1); } + +static char map_ip_str[16]; +static in_addr_t map_ip; +static int map_port = 5121; +int map_fd; +char talkie_mes[80]; + +/*========================================== + * map鯖のip設定 + *------------------------------------------ + */ +void clif_setip(char *ip) +{ + memcpy(map_ip_str, ip, 16); + map_ip = inet_addr(map_ip_str); +} + +/*========================================== + * map鯖のport設定 + *------------------------------------------ + */ +void clif_setport(int port) +{ + map_port = port; +} + +/*========================================== + * map鯖のip読み出し + *------------------------------------------ + */ +in_addr_t clif_getip(void) +{ + return map_ip; +} + +/*========================================== + * map鯖のport読み出し + *------------------------------------------ + */ +int clif_getport(void) +{ + return map_port; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_countusers(void) +{ + int users = 0, i; + struct map_session_data *sd; + + for(i = 0; i < fd_max; i++) { + if (session[i] && (sd = session[i]->session_data) && sd && sd->state.auth && + !(battle_config.hide_GM_session && pc_isGM(sd))) + users++; + } + return users; +} + +/*========================================== + * 全てのclientに対してfunc()実行 + *------------------------------------------ + */ +int clif_foreachclient(int (*func)(struct map_session_data*, va_list),...) +{ + int i; + va_list ap; + struct map_session_data *sd; + + va_start(ap,func); + for(i = 0; i < fd_max; i++) { + if (session[i] && (sd = session[i]->session_data) && sd && sd->state.auth) + func(sd, ap); + } + va_end(ap); + return 0; +} + +/*========================================== + * clif_sendでAREA*指定時用 + *------------------------------------------ + */ +int clif_send_sub(struct block_list *bl, va_list ap) +{ + unsigned char *buf; + int len; + struct block_list *src_bl; + int type; + struct map_session_data *sd; + + nullpo_retr(0, bl); + nullpo_retr(0, ap); + nullpo_retr(0, sd = (struct map_session_data *)bl); + + buf = va_arg(ap,unsigned char*); + len = va_arg(ap,int); + nullpo_retr(0, src_bl = va_arg(ap,struct block_list*)); + type = va_arg(ap,int); + + switch(type) { + case AREA_WOS: + if (bl && bl == src_bl) + return 0; + break; + case AREA_WOC: + if ((sd && sd->chatID) || (bl && bl == src_bl)) + return 0; + break; + case AREA_WOSC: + if ((sd) && sd->chatID && sd->chatID == ((struct map_session_data*)src_bl)->chatID) + return 0; + break; + } + + if (sd) { + if (WFIFOP(sd->fd,0) == buf) { + printf("WARNING: Invalid use of clif_send function\n"); + printf(" Packet x%4x use a WFIFO of a player instead of to use a buffer.\n", WBUFW(buf,0)); + printf(" Please correct your code.\n"); + // don't send to not move the pointer of the packet for next sessions in the loop + } else { + if (packet_size_table[sd->packet_ver-5][RBUFW(buf,0)]) { // packet must exist for the client version + memcpy(WFIFOP(sd->fd,0), buf, len); + WFIFOSET(sd->fd,len); + } + } + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_send(unsigned char *buf, int len, struct block_list *bl, int type) { + int i; + struct map_session_data *sd; + struct chat_data *cd; + struct party *p = NULL; + struct guild *g = NULL; + int x0 = 0, x1 = 0, y0 = 0, y1 = 0; + + if (type != ALL_CLIENT) { + nullpo_retr(0, bl); + } + + switch(type) { + case ALL_CLIENT: // 全クライアントに送信 + for(i = 0; i < fd_max; i++) { + if (session[i] && (sd = session[i]->session_data) != NULL && sd->state.auth) { + if (packet_size_table[sd->packet_ver-5][RBUFW(buf,0)]) { // packet must exist for the client version + memcpy(WFIFOP(i,0), buf, len); + WFIFOSET(i,len); + } + } + } + break; + case ALL_SAMEMAP: // 同じマップの全クライアントに送信 + for(i = 0; i < fd_max; i++) { + if (session[i] && (sd = session[i]->session_data) != NULL && sd->state.auth && sd->bl.m == bl->m) { + if (packet_size_table[sd->packet_ver-5][RBUFW(buf,0)]) { // packet must exist for the client version + memcpy(WFIFOP(i,0), buf, len); + WFIFOSET(i,len); + } + } + } + break; + case AREA: + case AREA_WOS: + case AREA_WOC: + case AREA_WOSC: + map_foreachinarea(clif_send_sub, bl->m, bl->x-AREA_SIZE, bl->y-AREA_SIZE, bl->x+AREA_SIZE, bl->y+AREA_SIZE, BL_PC, buf, len, bl, type); + break; + case AREA_CHAT_WOC: + map_foreachinarea(clif_send_sub, bl->m, bl->x-(AREA_SIZE-5), bl->y-(AREA_SIZE-5), bl->x+(AREA_SIZE-5), bl->y+(AREA_SIZE-5), BL_PC, buf, len, bl, AREA_WOC); + break; + case CHAT: + case CHAT_WOS: + cd = (struct chat_data*)bl; + if (bl->type == BL_PC) { + sd = (struct map_session_data*)bl; + cd = (struct chat_data*)map_id2bl(sd->chatID); + } else if (bl->type != BL_CHAT) + break; + if (cd == NULL) + break; + for(i = 0; i < cd->users; i++) { + if (type == CHAT_WOS && cd->usersd[i] == (struct map_session_data*)bl) + continue; + if (packet_size_table[cd->usersd[i]->packet_ver-5][RBUFW(buf,0)]) { // packet must exist for the client version + memcpy(WFIFOP(cd->usersd[i]->fd,0), buf, len); + WFIFOSET(cd->usersd[i]->fd,len); + } + } + break; + + case PARTY_AREA: // 同じ画面内の全パーティーメンバに送信 + case PARTY_AREA_WOS: // 自分以外の同じ画面内の全パーティーメンバに送信 + x0 = bl->x - AREA_SIZE; + y0 = bl->y - AREA_SIZE; + x1 = bl->x + AREA_SIZE; + y1 = bl->y + AREA_SIZE; + case PARTY: // 全パーティーメンバに送信 + case PARTY_WOS: // 自分以外の全パーティーメンバに送信 + case PARTY_SAMEMAP: // 同じマップの全パーティーメンバに送信 + case PARTY_SAMEMAP_WOS: // 自分以外の同じマップの全パーティーメンバに送信 + if (bl->type == BL_PC) { + sd = (struct map_session_data *)bl; + if (sd->partyspy > 0) { + p = party_search(sd->partyspy); + } else { + if (sd->status.party_id > 0) + p = party_search(sd->status.party_id); + } + } + if (p) { + for(i=0;i<MAX_PARTY;i++){ + if ((sd = p->member[i].sd) != NULL) { + if (sd->bl.id == bl->id && (type == PARTY_WOS || + type == PARTY_SAMEMAP_WOS || type == PARTY_AREA_WOS)) + continue; + if (type != PARTY && type != PARTY_WOS && bl->m != sd->bl.m) // マップチェック + continue; + if ((type == PARTY_AREA || type == PARTY_AREA_WOS) && + (sd->bl.x < x0 || sd->bl.y < y0 || + sd->bl.x > x1 || sd->bl.y > y1)) + continue; + if (packet_size_table[sd->packet_ver-5][RBUFW(buf,0)]) { // packet must exist for the client version + memcpy(WFIFOP(sd->fd,0), buf, len); + WFIFOSET(sd->fd,len); + } +// if(battle_config.etc_log) +// printf("send party %d %d %d\n",p->party_id,i,flag) + + } + } + for (i = 0; i < fd_max; i++){ + if (session[i] && (sd = session[i]->session_data) != NULL && sd->state.auth) { + if (sd->partyspy == p->party_id) { + if (packet_size_table[sd->packet_ver-5][RBUFW(buf,0)]) { // packet must exist for the client version + memcpy(WFIFOP(sd->fd,0), buf, len); + WFIFOSET(sd->fd,len); + } + } + } + } + } + break; + case SELF: + sd = (struct map_session_data *)bl; + if (packet_size_table[sd->packet_ver-5][RBUFW(buf,0)]) { // packet must exist for the client version + memcpy(WFIFOP(sd->fd,0), buf, len); + WFIFOSET(sd->fd,len); + } + break; + +/* New definitions for guilds [Valaris] */ + + case GUILD_AREA: + case GUILD_AREA_WOS: + x0 = bl->x - AREA_SIZE; + y0 = bl->y - AREA_SIZE; + x1 = bl->x + AREA_SIZE; + y1 = bl->y + AREA_SIZE; + case GUILD: + case GUILD_WOS: + if (bl && bl->type == BL_PC) { // guildspy [Syrus22] + sd = (struct map_session_data *)bl; + if (sd->guildspy > 0) { + g = guild_search(sd->guildspy); + } else { + if (sd->status.guild_id > 0) + g = guild_search(sd->status.guild_id); + } + } + if (g) { + for(i = 0; i < g->max_member; i++) { + if ((sd = g->member[i].sd) != NULL) { + if (type == GUILD_WOS && sd->bl.id == bl->id) + continue; + if (packet_size_table[sd->packet_ver-5][RBUFW(buf,0)]) { // packet must exist for the client version + memcpy(WFIFOP(sd->fd,0), buf, len); + WFIFOSET(sd->fd,len); + } + } + } + for (i = 0; i < fd_max; i++){ + if (session[i] && (sd = session[i]->session_data) != NULL && sd->state.auth) { + if (sd->guildspy == g->guild_id) { + if (packet_size_table[sd->packet_ver-5][RBUFW(buf,0)]) { // packet must exist for the client version + memcpy(WFIFOP(sd->fd,0), buf, len); + WFIFOSET(sd->fd,len); + } + } + } + } + } + break; + case GUILD_SAMEMAP: + case GUILD_SAMEMAP_WOS: + if (bl->type == BL_PC) { + sd = (struct map_session_data *)bl; + if (sd->status.guild_id > 0) + g = guild_search(sd->status.guild_id); + } + if (g) { + for(i = 0; i < g->max_member; i++) { + if ((sd = g->member[i].sd) != NULL) { + if (sd->bl.id == bl->id && (type == GUILD_WOS || + type == GUILD_SAMEMAP_WOS || type == GUILD_AREA_WOS)) + continue; + if (type != GUILD && type != GUILD_WOS && bl->m != sd->bl.m) // マップチェック + continue; + if ((type == GUILD_AREA || type == GUILD_AREA_WOS) && + (sd->bl.x < x0 || sd->bl.y < y0 || + sd->bl.x > x1 || sd->bl.y > y1)) + continue; + if (packet_size_table[sd->packet_ver-5][RBUFW(buf,0)]) { // packet must exist for the client version + memcpy(WFIFOP(sd->fd,0), buf, len); + WFIFOSET(sd->fd,len); + } + } + } + } + break; +/* End [Valaris] */ + + default: + if (battle_config.error_log) + printf("clif_send まだ作ってないよー\n"); + return -1; + } + + return 0; +} + +// +// パケット作って送信 +// +/*========================================== + * + *------------------------------------------ + */ +int clif_authok(struct map_session_data *sd) { + int fd; + + nullpo_retr(0, sd); + + if (!sd) + return 0; + + if (!sd->fd) + return 0; + + fd = sd->fd; + + WFIFOW(fd, 0) = 0x73; + WFIFOL(fd, 2) = gettick(); + WFIFOPOS(fd, 6, sd->bl.x, sd->bl.y); + WFIFOB(fd, 9) = 5; + WFIFOB(fd,10) = 5; + WFIFOSET(fd,packet_len_table[0x73]); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_authfail_fd(int fd, int type) { + if (!fd || !session[fd]) + return 0; + + WFIFOW(fd,0) = 0x81; + WFIFOL(fd,2) = type; + WFIFOSET(fd,packet_len_table[0x81]); + + clif_setwaitclose(fd); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_charselectok(int id) { + struct map_session_data *sd; + int fd; + + if ((sd = map_id2sd(id)) == NULL) + return 1; + + if (!sd->fd) + return 1; + + fd = sd->fd; + WFIFOW(fd,0) = 0xb3; + WFIFOB(fd,2) = 1; + WFIFOSET(fd,packet_len_table[0xb3]); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +static int clif_set009e(struct flooritem_data *fitem,unsigned char *buf) { + int view; + + nullpo_retr(0, fitem); + + //009e <ID>.l <name ID>.w <identify flag>.B <X>.w <Y>.w <subX>.B <subY>.B <amount>.w + WBUFW(buf, 0) = 0x9e; + WBUFL(buf, 2) = fitem->bl.id; + if ((view = itemdb_viewid(fitem->item_data.nameid)) > 0) + WBUFW(buf, 6) = view; + else + WBUFW(buf, 6) = fitem->item_data.nameid; + WBUFB(buf, 8) = fitem->item_data.identify; + WBUFW(buf, 9) = fitem->bl.x; + WBUFW(buf,11) = fitem->bl.y; + WBUFB(buf,13) = fitem->subx; + WBUFB(buf,14) = fitem->suby; + WBUFW(buf,15) = fitem->item_data.amount; + + return packet_len_table[0x9e]; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_dropflooritem(struct flooritem_data *fitem) { + char buf[64]; + + nullpo_retr(0, fitem); + + if (fitem->item_data.nameid <= 0) + return 0; + clif_set009e(fitem, buf); + clif_send(buf, packet_len_table[0x9e], &fitem->bl, AREA); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_clearflooritem(struct flooritem_data *fitem, int fd) { + unsigned char buf[16]; + + nullpo_retr(0, fitem); + + WBUFW(buf,0) = 0xa1; + WBUFL(buf,2) = fitem->bl.id; + + if (fd == 0) { + clif_send(buf, packet_len_table[0xa1], &fitem->bl, AREA); + } else { + memcpy(WFIFOP(fd,0), buf, 6); + WFIFOSET(fd,packet_len_table[0xa1]); + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_clearchar(struct block_list *bl, int type) { + unsigned char buf[16]; + + nullpo_retr(0, bl); + + WBUFW(buf,0) = 0x80; + WBUFL(buf,2) = bl->id; + if (type == 9) { + WBUFB(buf,6) = 0; + clif_send(buf, packet_len_table[0x80], bl, AREA); + } else { + WBUFB(buf,6) = type; + clif_send(buf, packet_len_table[0x80], bl, type == 1 ? AREA : AREA_WOS); + } + + return 0; +} + +static int clif_clearchar_delay_sub(int tid, unsigned int tick, int id, int data) { + struct block_list *bl = (struct block_list *)id; + + clif_clearchar(bl,data); + map_freeblock(bl); + + return 0; +} + +int clif_clearchar_delay(unsigned int tick, struct block_list *bl, int type) { + struct block_list *tmpbl = calloc(sizeof(struct block_list), 1); + if (tmpbl == NULL) { + printf("clif_clearchar_delay: out of memory !\n"); + exit(1); + } + memcpy(tmpbl, bl, sizeof(struct block_list)); + add_timer(tick, clif_clearchar_delay_sub, (int)tmpbl, type); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_clearchar_id(int id, int type, int fd) { + unsigned char buf[16]; + + WBUFW(buf,0) = 0x80; + WBUFL(buf,2) = id; + WBUFB(buf,6) = type; + memcpy(WFIFOP(fd,0), buf, 7); + WFIFOSET(fd, packet_len_table[0x80]); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +static int clif_set0078(struct map_session_data *sd, unsigned char *buf) { + int level=0; + + nullpo_retr(0, sd); + + if (sd->disguise > 23 && sd->disguise < 4001) { // mob disguises [Valaris] + WBUFW(buf,0) = 0x78; + WBUFL(buf,2) = sd->bl.id; + WBUFW(buf,6) = battle_get_speed(&sd->bl); + WBUFW(buf,8) = sd->opt1; + WBUFW(buf,10) = sd->opt2; + WBUFW(buf,12) = sd->status.option; + WBUFW(buf,14) = sd->disguise; + WBUFW(buf,42) = 0; + WBUFB(buf,44) = 0; + WBUFPOS(buf, 46, sd->bl.x, sd->bl.y); + WBUFB(buf,48) |= sd->dir & 0x0f; + WBUFB(buf,49) = 5; + WBUFB(buf,50) = 5; + WBUFB(buf,51) = 0; + WBUFW(buf,52) = ((level = battle_get_lv(&sd->bl)) > battle_config.max_lv) ? battle_config.max_lv : level; + + return packet_len_table[0x78]; + } + +#if PACKETVER < 4 + WBUFW(buf,0)= 0x78; + WBUFL(buf,2)= sd->bl.id; + WBUFW(buf,6)= sd->speed; + WBUFW(buf,8)= sd->opt1; + WBUFW(buf,10)= sd->opt2; + WBUFW(buf,12)= sd->status.option; + WBUFW(buf,14)= sd->view_class; + WBUFW(buf,16)= sd->status.hair; + if (sd->view_class != 22) + WBUFW(buf,18) = sd->status.weapon; + else + WBUFW(buf,18)=0; + WBUFW(buf,20)=sd->status.head_bottom; + WBUFW(buf,22)=sd->status.shield; + WBUFW(buf,24)=sd->status.head_top; + WBUFW(buf,26)=sd->status.head_mid; + WBUFW(buf,28)=sd->status.hair_color; + WBUFW(buf,30)=sd->status.clothes_color; + WBUFW(buf,32)=sd->head_dir; + WBUFL(buf,34)=sd->status.guild_id; + WBUFL(buf,38)=sd->guild_emblem_id; + WBUFW(buf,42)=sd->status.manner; + WBUFB(buf,44)=sd->status.karma; + WBUFB(buf,45)=sd->sex; + WBUFPOS(buf,46,sd->bl.x,sd->bl.y); + WBUFB(buf,48)|=sd->dir&0x0f; + WBUFB(buf,49)=5; + WBUFB(buf,50)=5; + WBUFB(buf,51)=sd->state.dead_sit; + WBUFW(buf,52)=(sd->status.base_level>battle_config.max_lv)?battle_config.max_lv:sd->status.base_level; + + return packet_len_table[0x78]; +#else + WBUFW(buf,0) = 0x1d8; + WBUFL(buf,2) = sd->bl.id; + WBUFW(buf,6) = sd->speed; + WBUFW(buf,8) = sd->opt1; + WBUFW(buf,10) = sd->opt2; + WBUFW(buf,12) = sd->status.option; + WBUFW(buf,14) = sd->view_class; + WBUFW(buf,16) = sd->status.hair; + if (sd->equip_index[9] >= 0 && sd->inventory_data[sd->equip_index[9]] && sd->view_class != 22) { + if (sd->inventory_data[sd->equip_index[9]]->view_id > 0) + WBUFW(buf,18) = sd->inventory_data[sd->equip_index[9]]->view_id; + else + WBUFW(buf,18) = sd->status.inventory[sd->equip_index[9]].nameid; + } else + WBUFW(buf,18) = 0; + if (sd->equip_index[8] >= 0 && sd->equip_index[8] != sd->equip_index[9] && sd->inventory_data[sd->equip_index[8]] && sd->view_class != 22) { + if (sd->inventory_data[sd->equip_index[8]]->view_id > 0) + WBUFW(buf,20) = sd->inventory_data[sd->equip_index[8]]->view_id; + else + WBUFW(buf,20) = sd->status.inventory[sd->equip_index[8]].nameid; + } else + WBUFW(buf,20) = 0; + WBUFW(buf,22)=sd->status.head_bottom; + WBUFW(buf,24)=sd->status.head_top; + WBUFW(buf,26)=sd->status.head_mid; + WBUFW(buf,28)=sd->status.hair_color; + WBUFW(buf,30)=sd->status.clothes_color; + WBUFW(buf,32)=sd->head_dir; + WBUFL(buf,34)=sd->status.guild_id; + WBUFW(buf,38)=sd->guild_emblem_id; + WBUFW(buf,40)=sd->status.manner; + WBUFW(buf,42)=sd->opt3; + WBUFB(buf,44)=sd->status.karma; + WBUFB(buf,45)=sd->sex; + WBUFPOS(buf,46,sd->bl.x,sd->bl.y); + WBUFB(buf,48)|=sd->dir&0x0f; + WBUFB(buf,49)=5; + WBUFB(buf,50)=5; + WBUFB(buf,51)=sd->state.dead_sit; + WBUFW(buf,52)=((level = battle_get_lv(&sd->bl)) > battle_config.max_lv) ? battle_config.max_lv : level; + + return packet_len_table[0x1d8]; +#endif +} + +/*========================================== + * + *------------------------------------------ + */ +static int clif_set007b(struct map_session_data *sd,unsigned char *buf) { + int level=0; + + nullpo_retr(0, sd); + + if (sd->disguise > 23 && sd->disguise < 4001) { // mob disguises [Valaris] + WBUFW(buf,0)=0x7b; + WBUFL(buf,2)=sd->bl.id; + WBUFW(buf,6)=battle_get_speed(&sd->bl); + WBUFW(buf,8)=sd->opt1; + WBUFW(buf,10)=sd->opt2; + WBUFW(buf,12)=sd->status.option; + WBUFW(buf,14)=sd->disguise; + WBUFL(buf,22)=gettick(); + WBUFW(buf,46)=0; + WBUFB(buf,48)=0; + WBUFPOS2(buf,50,sd->bl.x,sd->bl.y,sd->to_x,sd->to_y); + WBUFB(buf,55)=0; + WBUFB(buf,56)=5; + WBUFB(buf,57)=5; + WBUFW(buf,58)=((level = battle_get_lv(&sd->bl))>battle_config.max_lv)? battle_config.max_lv:level; + + return packet_len_table[0x7b]; + } + +#if PACKETVER < 4 + WBUFW(buf,0)=0x7b; + WBUFL(buf,2)=sd->bl.id; + WBUFW(buf,6)=sd->speed; + WBUFW(buf,8)=sd->opt1; + WBUFW(buf,10)=sd->opt2; + WBUFW(buf,12)=sd->status.option; + WBUFW(buf,14)=sd->view_class; + WBUFW(buf,16)=sd->status.hair; + if(sd->view_class != 22) + WBUFW(buf,18)=sd->status.weapon; + else + WBUFW(buf,18)=0; + WBUFW(buf,20)=sd->status.head_bottom; + WBUFL(buf,22)=gettick(); + WBUFW(buf,26)=sd->status.shield; + WBUFW(buf,28)=sd->status.head_top; + WBUFW(buf,30)=sd->status.head_mid; + WBUFW(buf,32)=sd->status.hair_color; + WBUFW(buf,34)=sd->status.clothes_color; + WBUFW(buf,36)=sd->head_dir; + WBUFL(buf,38)=sd->status.guild_id; + WBUFL(buf,42)=sd->guild_emblem_id; + WBUFW(buf,46)=sd->status.manner; + WBUFB(buf,48)=sd->status.karma; + WBUFB(buf,49)=sd->sex; + WBUFPOS2(buf,50,sd->bl.x,sd->bl.y,sd->to_x,sd->to_y); + WBUFB(buf,55)=0; + WBUFB(buf,56)=5; + WBUFB(buf,57)=5; + WBUFW(buf,58)=(sd->status.base_level>battle_config.max_lv)?battle_config.max_lv:sd->status.base_level; + + return packet_len_table[0x7b]; +#else + WBUFW(buf,0)=0x1da; + WBUFL(buf,2)=sd->bl.id; + WBUFW(buf,6)=sd->speed; + WBUFW(buf,8)=sd->opt1; + WBUFW(buf,10)=sd->opt2; + WBUFW(buf,12)=sd->status.option; + WBUFW(buf,14)=sd->view_class; + WBUFW(buf,16)=sd->status.hair; + if(sd->equip_index[9] >= 0 && sd->inventory_data[sd->equip_index[9]] && sd->view_class != 22) { + if(sd->inventory_data[sd->equip_index[9]]->view_id > 0) + WBUFW(buf,18)=sd->inventory_data[sd->equip_index[9]]->view_id; + else + WBUFW(buf,18)=sd->status.inventory[sd->equip_index[9]].nameid; + } + else + WBUFW(buf,18)=0; + if(sd->equip_index[8] >= 0 && sd->equip_index[8] != sd->equip_index[9] && sd->inventory_data[sd->equip_index[8]] && sd->view_class != 22) { + if(sd->inventory_data[sd->equip_index[8]]->view_id > 0) + WBUFW(buf,20)=sd->inventory_data[sd->equip_index[8]]->view_id; + else + WBUFW(buf,20)=sd->status.inventory[sd->equip_index[8]].nameid; + } + else + WBUFW(buf,20)=0; + WBUFW(buf,22)=sd->status.head_bottom; + WBUFL(buf,24)=gettick(); + WBUFW(buf,28)=sd->status.head_top; + WBUFW(buf,30)=sd->status.head_mid; + WBUFW(buf,32)=sd->status.hair_color; + WBUFW(buf,34)=sd->status.clothes_color; + WBUFW(buf,36)=sd->head_dir; + WBUFL(buf,38)=sd->status.guild_id; + WBUFW(buf,42)=sd->guild_emblem_id; + WBUFW(buf,44)=sd->status.manner; + WBUFW(buf,46)=sd->opt3; + WBUFB(buf,48)=sd->status.karma; + WBUFB(buf,49)=sd->sex; + WBUFPOS2(buf,50,sd->bl.x,sd->bl.y,sd->to_x,sd->to_y); + WBUFB(buf,55)=0; + WBUFB(buf,56)=5; + WBUFB(buf,57)=5; + WBUFW(buf,58)=(sd->status.base_level>battle_config.max_lv)?battle_config.max_lv:sd->status.base_level; + + return packet_len_table[0x1da]; +#endif +} + +/*========================================== + * クラスチェンジ typeはMobの場合は1で他は0? + *------------------------------------------ + */ +int clif_class_change(struct block_list *bl,int class,int type) +{ + char buf[16]; + + nullpo_retr(0, bl); + + if(class >= MAX_PC_CLASS) { + WBUFW(buf,0)=0x1b0; + WBUFL(buf,2)=bl->id; + WBUFB(buf,6)=type; + WBUFL(buf,7)=class; + + clif_send(buf,packet_len_table[0x1b0],bl,AREA); + } + return 0; +} +/*========================================== + * + *------------------------------------------ + */ +int clif_mob_class_change(struct mob_data *md, int class) { + char buf[16]; + int view = mob_get_viewclass(class); + + nullpo_retr(0, md); + + if(view >= MAX_PC_CLASS) { + WBUFW(buf,0)=0x1b0; + WBUFL(buf,2)=md->bl.id; + WBUFB(buf,6)=1; + WBUFL(buf,7)=view; + + clif_send(buf,packet_len_table[0x1b0],&md->bl,AREA); + } + return 0; +} +// mob equipment [Valaris] + +int clif_mob_equip(struct mob_data *md, int nameid) { + unsigned char buf[16]; + + nullpo_retr(0, md); + + memset(buf,0,packet_len_table[0x1a4]); + + WBUFW(buf,0)=0x1a4; + WBUFB(buf,2)=3; + WBUFL(buf,3)=md->bl.id; + WBUFL(buf,7)=nameid; + + clif_send(buf,packet_len_table[0x1a4],&md->bl,AREA); + + return 0; +} + +/*========================================== + * MOB表示1 + *------------------------------------------ + */ +static int clif_mob0078(struct mob_data *md, unsigned char *buf) +{ + int level; + + memset(buf,0,packet_len_table[0x78]); + + nullpo_retr(0, md); + + WBUFW(buf,0)=0x78; + WBUFL(buf,2)=md->bl.id; + WBUFW(buf,6)=battle_get_speed(&md->bl); + WBUFW(buf,8)=md->opt1; + WBUFW(buf,10)=md->opt2; + WBUFW(buf,12)=md->option; + WBUFW(buf,14)=mob_get_viewclass(md->class); + if((mob_get_viewclass(md->class) <= 23) || (mob_get_viewclass(md->class) == 812) || (mob_get_viewclass(md->class) >= 4001)) { + WBUFW(buf,12)|=mob_db[md->class].option; + WBUFW(buf,16)=mob_get_hair(md->class); + WBUFW(buf,18)=mob_get_weapon(md->class); + WBUFW(buf,20)=mob_get_head_buttom(md->class); + WBUFW(buf,22)=mob_get_shield(md->class); + WBUFW(buf,24)=mob_get_head_top(md->class); + WBUFW(buf,26)=mob_get_head_mid(md->class); + WBUFW(buf,28)=mob_get_hair_color(md->class); + WBUFW(buf,30)=mob_get_clothes_color(md->class); //Add for player monster dye - Valaris + WBUFB(buf,45)=mob_get_sex(md->class); + } + + if (md->class >= 1285 && md->class <= 1287) { // Added guardian emblems [Valaris] + struct guild *g; + struct guild_castle *gc=guild_mapname2gc(map[md->bl.m].name); + if (gc && gc->guild_id > 0) { + g=guild_search(gc->guild_id); + if (g) { + WBUFL(buf,26)=gc->guild_id; + WBUFL(buf,22)=g->emblem_id; + } + } + } // End addition + + WBUFPOS(buf,46,md->bl.x,md->bl.y); + WBUFB(buf,48)|=md->dir&0x0f; + WBUFB(buf,49)=5; + WBUFB(buf,50)=5; + WBUFW(buf,52)=((level = battle_get_lv(&md->bl))>battle_config.max_lv)? battle_config.max_lv:level; + + return packet_len_table[0x78]; +} + +/*========================================== + * MOB表示2 + *------------------------------------------ + */ +static int clif_mob007b(struct mob_data *md, unsigned char *buf) { + int level; + + memset(buf,0,packet_len_table[0x7b]); + + nullpo_retr(0, md); + + WBUFW(buf,0)=0x7b; + WBUFL(buf,2)=md->bl.id; + WBUFW(buf,6)=battle_get_speed(&md->bl); + WBUFW(buf,8)=md->opt1; + WBUFW(buf,10)=md->opt2; + WBUFW(buf,12)=md->option; + WBUFW(buf,14)=mob_get_viewclass(md->class); + if ((mob_get_viewclass(md->class) < 24) || (mob_get_viewclass(md->class) > 4000)) { + WBUFW(buf,12)|=mob_db[md->class].option; + WBUFW(buf,16)=mob_get_hair(md->class); + WBUFW(buf,18)=mob_get_weapon(md->class); + WBUFW(buf,20)=mob_get_head_buttom(md->class); + WBUFL(buf,22)=gettick(); + WBUFW(buf,26)=mob_get_shield(md->class); + WBUFW(buf,28)=mob_get_head_top(md->class); + WBUFW(buf,30)=mob_get_head_mid(md->class); + WBUFW(buf,32)=mob_get_hair_color(md->class); + WBUFW(buf,34)=mob_get_clothes_color(md->class); //Add for player monster dye - Valaris + WBUFB(buf,49)=mob_get_sex(md->class); + } else + WBUFL(buf,22)=gettick(); + + if(md->class >= 1285 && md->class <= 1287) { // Added guardian emblems [Valaris] + struct guild *g; + struct guild_castle *gc=guild_mapname2gc(map[md->bl.m].name); + if(gc && gc->guild_id > 0){ + g=guild_search(gc->guild_id); + if(g) { + WBUFL(buf,28)=gc->guild_id; + WBUFL(buf,24)=g->emblem_id; + } + } + } // End addition + + WBUFPOS2(buf,50,md->bl.x,md->bl.y,md->to_x,md->to_y); + WBUFB(buf,56)=5; + WBUFB(buf,57)=5; + WBUFW(buf,58)=((level = battle_get_lv(&md->bl))>battle_config.max_lv)? battle_config.max_lv:level; + + return packet_len_table[0x7b]; +} + +/*========================================== + * + *------------------------------------------ + */ +static int clif_npc0078(struct npc_data *nd, unsigned char *buf) { + struct guild *g; + + nullpo_retr(0, nd); + + memset(buf,0,packet_len_table[0x78]); + + WBUFW(buf,0)=0x78; + WBUFL(buf,2)=nd->bl.id; + WBUFW(buf,6)=nd->speed; + WBUFW(buf,14)=nd->class; + if ((nd->class == 722) && (nd->u.scr.guild_id > 0) && ((g=guild_search(nd->u.scr.guild_id)) != NULL)) { + WBUFL(buf,22)=g->emblem_id; + WBUFL(buf,26)=g->guild_id; + } + WBUFPOS(buf,46,nd->bl.x,nd->bl.y); + WBUFB(buf,48)|=nd->dir&0x0f; + WBUFB(buf,49)=5; + WBUFB(buf,50)=5; + + return packet_len_table[0x78]; +} + +/*========================================== + * + *------------------------------------------ + */ +static int clif_pet0078(struct pet_data *pd, unsigned char *buf) { + int view,level; + + nullpo_retr(0, pd); + + memset(buf,0,packet_len_table[0x78]); + + WBUFW(buf,0)=0x78; + WBUFL(buf,2)=pd->bl.id; + WBUFW(buf,6)=pd->speed; + WBUFW(buf,14)=mob_get_viewclass(pd->class); + if((mob_get_viewclass(pd->class) < 24) || (mob_get_viewclass(pd->class) > 4000)) { + WBUFW(buf,12)=mob_db[pd->class].option; + WBUFW(buf,16)=mob_get_hair(pd->class); + WBUFW(buf,18)=mob_get_weapon(pd->class); + WBUFW(buf,20)=mob_get_head_buttom(pd->class); + WBUFW(buf,22)=mob_get_shield(pd->class); + WBUFW(buf,24)=mob_get_head_top(pd->class); + WBUFW(buf,26)=mob_get_head_mid(pd->class); + WBUFW(buf,28)=mob_get_hair_color(pd->class); + WBUFW(buf,30)=mob_get_clothes_color(pd->class); //Add for player pet dye - Valaris + WBUFB(buf,45)=mob_get_sex(pd->class); + } else { + WBUFW(buf,16)=0x14; + if((view = itemdb_viewid(pd->equip)) > 0) + WBUFW(buf,20)=view; + else + WBUFW(buf,20)=pd->equip; + } + WBUFPOS(buf,46,pd->bl.x,pd->bl.y); + WBUFB(buf,48)|=pd->dir&0x0f; + WBUFB(buf,49)=0; + WBUFB(buf,50)=0; + WBUFW(buf,52)=((level = battle_get_lv(&pd->bl))>battle_config.max_lv)? battle_config.max_lv:level; + + return packet_len_table[0x78]; +} + +/*========================================== + * + *------------------------------------------ + */ +static int clif_pet007b(struct pet_data *pd, unsigned char *buf) { + int view,level; + + nullpo_retr(0, pd); + + memset(buf,0,packet_len_table[0x7b]); + + WBUFW(buf,0)=0x7b; + WBUFL(buf,2)=pd->bl.id; + WBUFW(buf,6)=pd->speed; + WBUFW(buf,14)=mob_get_viewclass(pd->class); + if((mob_get_viewclass(pd->class) < 24) || (mob_get_viewclass(pd->class) > 4000)) { + WBUFW(buf,12)=mob_db[pd->class].option; + WBUFW(buf,16)=mob_get_hair(pd->class); + WBUFW(buf,18)=mob_get_weapon(pd->class); + WBUFW(buf,20)=mob_get_head_buttom(pd->class); + WBUFL(buf,22)=gettick(); + WBUFW(buf,26)=mob_get_shield(pd->class); + WBUFW(buf,28)=mob_get_head_top(pd->class); + WBUFW(buf,30)=mob_get_head_mid(pd->class); + WBUFW(buf,32)=mob_get_hair_color(pd->class); + WBUFW(buf,34)=mob_get_clothes_color(pd->class); //Add for player pet dye - Valaris + WBUFB(buf,49)=mob_get_sex(pd->class); + } else { + WBUFW(buf,16)=0x14; + if ((view = itemdb_viewid(pd->equip)) > 0) + WBUFW(buf,20)=view; + else + WBUFW(buf,20)=pd->equip; + WBUFL(buf,22)=gettick(); + } + WBUFPOS2(buf,50,pd->bl.x,pd->bl.y,pd->to_x,pd->to_y); + WBUFB(buf,56)=0; + WBUFB(buf,57)=0; + WBUFW(buf,58)=((level = battle_get_lv(&pd->bl))>battle_config.max_lv)? battle_config.max_lv:level; + + return packet_len_table[0x7b]; +} + +/*========================================== + * + *------------------------------------------ + */ +static int clif_set01e1(struct map_session_data *sd, unsigned char *buf) { + nullpo_retr(0, sd); + + WBUFW(buf,0)=0x1e1; + WBUFL(buf,2)=sd->bl.id; + WBUFW(buf,6)=sd->spiritball; + + return packet_len_table[0x1e1]; +} + +/*========================================== + * + *------------------------------------------ + */ +static int clif_set0192(int fd, int m, int x, int y, int type) { + WFIFOW(fd,0) = 0x192; + WFIFOW(fd,2) = x; + WFIFOW(fd,4) = y; + WFIFOW(fd,6) = type; + memcpy(WFIFOP(fd,8),map[m].name,16); + WFIFOSET(fd,packet_len_table[0x192]); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_spawnpc(struct map_session_data *sd) { + unsigned char buf[128]; + + nullpo_retr(0, sd); + + if (sd->disguise > 23 && sd->disguise < 4001) { // mob disguises [Valaris] + clif_clearchar(&sd->bl, 9); + + memset(buf, 0, packet_len_table[0x119]); + + WBUFW(buf, 0) = 0x119; + WBUFL(buf, 2) = sd->bl.id; + WBUFW(buf, 6) = 0; + WBUFW(buf, 8) = 0; + WBUFW(buf,10) = 0x40; + WBUFB(buf,12) = 0; + + clif_send(buf, packet_len_table[0x119], &sd->bl, SELF); + + memset(buf, 0, packet_len_table[0x7c]); + + WBUFW(buf, 0) = 0x7c; + WBUFL(buf, 2) = sd->bl.id; + WBUFW(buf, 6) = sd->speed; + WBUFW(buf, 8) = sd->opt1; + WBUFW(buf,10) = sd->opt2; + WBUFW(buf,12) = sd->status.option; + WBUFW(buf,20) = sd->disguise; + WBUFPOS(buf, 36, sd->bl.x, sd->bl.y); + clif_send(buf, packet_len_table[0x7c], &sd->bl, AREA); + } + + clif_set0078(sd, buf); + +#if PACKETVER < 4 + WBUFW(buf, 0) = 0x79; + WBUFW(buf,51) = (sd->status.base_level > battle_config.max_lv) ? battle_config.max_lv : sd->status.base_level; + clif_send(buf, packet_len_table[0x79], &sd->bl, AREA_WOS); +#else + WBUFW(buf, 0) = 0x1d9; + WBUFW(buf,51) = (sd->status.base_level > battle_config.max_lv) ? battle_config.max_lv : sd->status.base_level; + clif_send(buf, packet_len_table[0x1d9], &sd->bl, AREA_WOS); +#endif + + + if (sd->spiritball > 0) + clif_spiritball(sd); + + if (sd->status.guild_id > 0) { // force display of guild emblem [Valaris] + struct guild *g = guild_search(sd->status.guild_id); + if (g) + clif_guild_emblem(sd,g); + } // end addition [Valaris] + + if (sd->status.class==13 || sd->status.class==21 || sd->status.class==4014 || sd->status.class==4022) + pc_setoption(sd,sd->status.option|0x0020); // [Valaris] + + if ((pc_isriding(sd) && pc_checkskill(sd,KN_RIDING)>0) && (sd->status.class==7 || + sd->status.class==14 || sd->status.class==4008 || sd->status.class==4015)) + pc_setriding(sd); // update peco riders for people upgrading athena [Valaris] + + + if (map[sd->bl.m].flag.snow) + clif_specialeffect(&sd->bl, 162, 1); + if (map[sd->bl.m].flag.fog) + clif_specialeffect(&sd->bl, 233, 1); + if (map[sd->bl.m].flag.sakura) + clif_specialeffect(&sd->bl, 163, 1); + if (map[sd->bl.m].flag.leaves) + clif_specialeffect(&sd->bl, 333, 1); + if (map[sd->bl.m].flag.rain) + clif_specialeffect(&sd->bl, 161, 1); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_spawnnpc(struct npc_data *nd) +{ + unsigned char buf[64]; + int len; + + nullpo_retr(0, nd); + + if(nd->class < 0 || nd->flag&1 || nd->class == INVISIBLE_CLASS) + return 0; + + memset(buf,0,packet_len_table[0x7c]); + + WBUFW(buf,0)=0x7c; + WBUFL(buf,2)=nd->bl.id; + WBUFW(buf,6)=nd->speed; + WBUFW(buf,20)=nd->class; + WBUFPOS(buf,36,nd->bl.x,nd->bl.y); + + clif_send(buf,packet_len_table[0x7c],&nd->bl,AREA); + + len = clif_npc0078(nd,buf); + clif_send(buf,len,&nd->bl,AREA); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_spawnmob(struct mob_data *md) +{ + unsigned char buf[64]; + int len; + + nullpo_retr(0, md); + + if (mob_get_viewclass(md->class) > 23 ) { + memset(buf,0,packet_len_table[0x7c]); + + WBUFW(buf,0)=0x7c; + WBUFL(buf,2)=md->bl.id; + WBUFW(buf,6)=md->speed; + WBUFW(buf,8)=md->opt1; + WBUFW(buf,10)=md->opt2; + WBUFW(buf,12)=md->option; + WBUFW(buf,20)=mob_get_viewclass(md->class); + WBUFPOS(buf,36,md->bl.x,md->bl.y); + clif_send(buf,packet_len_table[0x7c],&md->bl,AREA); + } + + len = clif_mob0078(md,buf); + clif_send(buf,len,&md->bl,AREA); + + if (mob_get_equip(md->class) > 0) // mob equipment [Valaris] + clif_mob_equip(md,mob_get_equip(md->class)); + + return 0; +} + +// pet + +/*========================================== + * + *------------------------------------------ + */ +int clif_spawnpet(struct pet_data *pd) +{ + unsigned char buf[64]; + int len; + + nullpo_retr(0, pd); + + if (mob_get_viewclass(pd->class) >= MAX_PC_CLASS) { + memset(buf,0,packet_len_table[0x7c]); + + WBUFW(buf,0)=0x7c; + WBUFL(buf,2)=pd->bl.id; + WBUFW(buf,6)=pd->speed; + WBUFW(buf,20)=mob_get_viewclass(pd->class); + WBUFPOS(buf,36,pd->bl.x,pd->bl.y); + + clif_send(buf,packet_len_table[0x7c],&pd->bl,AREA); + } + + len = clif_pet0078(pd,buf); + clif_send(buf,len,&pd->bl,AREA); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_movepet(struct pet_data *pd) { + unsigned char buf[256]; + int len; + + nullpo_retr(0, pd); + + len = clif_pet007b(pd,buf); + clif_send(buf,len,&pd->bl,AREA); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_servertick(struct map_session_data *sd) +{ + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd,0)=0x7f; + WFIFOL(fd,2)=sd->server_tick; + WFIFOSET(fd,packet_len_table[0x7f]); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_walkok(struct map_session_data *sd) +{ + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd,0)=0x87; + WFIFOL(fd,2)=gettick();; + WFIFOPOS2(fd,6,sd->bl.x,sd->bl.y,sd->to_x,sd->to_y); + WFIFOB(fd,11)=0; + WFIFOSET(fd,packet_len_table[0x87]); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_movechar(struct map_session_data *sd) { + int fd; + int len; + unsigned char buf[256]; + + nullpo_retr(0, sd); + + fd = sd->fd; + + len = clif_set007b(sd, buf); + + if (sd->disguise > 23 && sd->disguise < 4001) { + clif_send(buf, len, &sd->bl, AREA); + return 0; + } else + clif_send(buf, len, &sd->bl, AREA_WOS); + + if (battle_config.save_clothcolor == 1 && sd->status.clothes_color > 0) + clif_changelook(&sd->bl, LOOK_CLOTHES_COLOR, sd->status.clothes_color); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +void clif_quitsave(int fd,struct map_session_data *sd) +{ + map_quit(sd); +} + +/*========================================== + * + *------------------------------------------ + */ +static int clif_waitclose(int tid, unsigned int tick, int id, int data) { + if (session[id]) + session[id]->eof = 1; + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +void clif_setwaitclose(int fd) { + add_timer(gettick() + 5000, clif_waitclose, fd, 0); +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_changemap(struct map_session_data *sd, char *mapname, int x, int y) { + int fd; + + nullpo_retr(0, sd); + + fd = sd->fd; + + WFIFOW(fd,0) = 0x91; + memcpy(WFIFOP(fd,2), mapname, 16); + WFIFOW(fd,18) = x; + WFIFOW(fd,20) = y; + WFIFOSET(fd, packet_len_table[0x91]); + + if (sd->disguise > 23 && sd->disguise < 4001) // mob disguises [Valaris] + clif_spawnpc(sd); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_changemapserver(struct map_session_data *sd, char *mapname, int x, int y, int ip, int port) { + int fd; + + nullpo_retr(0, sd); + + fd = sd->fd; + WFIFOW(fd,0) = 0x92; + memcpy(WFIFOP(fd,2), mapname, 16); + WFIFOW(fd,18) = x; + WFIFOW(fd,20) = y; + WFIFOL(fd,22) = ip; + WFIFOW(fd,26) = port; + WFIFOSET(fd, packet_len_table[0x92]); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_fixpos(struct block_list *bl) { + char buf[16]; + + nullpo_retr(0, bl); + + WBUFW(buf,0)=0x88; + WBUFL(buf,2)=bl->id; + WBUFW(buf,6)=bl->x; + WBUFW(buf,8)=bl->y; + + clif_send(buf, packet_len_table[0x88], bl, AREA); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_npcbuysell(struct map_session_data* sd, int id) { + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd,0)=0xc4; + WFIFOL(fd,2)=id; + WFIFOSET(fd,packet_len_table[0xc4]); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_buylist(struct map_session_data *sd, struct npc_data *nd) { + struct item_data *id; + int fd,i,val; + + nullpo_retr(0, sd); + nullpo_retr(0, nd); + + fd=sd->fd; + WFIFOW(fd,0)=0xc6; + for(i=0;nd->u.shop_item[i].nameid > 0;i++){ + id = itemdb_search(nd->u.shop_item[i].nameid); + val=nd->u.shop_item[i].value; + WFIFOL(fd,4+i*11)=val; + if (!id->flag.value_notdc) + val=pc_modifybuyvalue(sd,val); + WFIFOL(fd,8+i*11)=val; + WFIFOB(fd,12+i*11)=id->type; + if (id->view_id > 0) + WFIFOW(fd,13+i*11)=id->view_id; + else + WFIFOW(fd,13+i*11)=nd->u.shop_item[i].nameid; + } + WFIFOW(fd,2)=i*11+4; + WFIFOSET(fd,WFIFOW(fd,2)); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_selllist(struct map_session_data *sd) { + int fd,i,c=0,val; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd,0)=0xc7; + for(i=0;i<MAX_INVENTORY;i++) { + if(sd->status.inventory[i].nameid > 0 && sd->inventory_data[i]) { + val=sd->inventory_data[i]->value_sell; + if (val < 0) + continue; + WFIFOW(fd,4+c*10)=i+2; + WFIFOL(fd,6+c*10)=val; + if (!sd->inventory_data[i]->flag.value_notoc) + val=pc_modifysellvalue(sd,val); + WFIFOL(fd,10+c*10)=val; + c++; + } + } + WFIFOW(fd,2)=c*10+4; + WFIFOSET(fd,WFIFOW(fd,2)); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_scriptmes(struct map_session_data *sd, int npcid, char *mes) { + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd,0)=0xb4; + WFIFOW(fd,2)=strlen(mes)+9; + WFIFOL(fd,4)=npcid; + strcpy(WFIFOP(fd,8),mes); + WFIFOSET(fd,WFIFOW(fd,2)); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_scriptnext(struct map_session_data *sd,int npcid) { + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd,0)=0xb5; + WFIFOL(fd,2)=npcid; + WFIFOSET(fd,packet_len_table[0xb5]); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_scriptclose(struct map_session_data *sd, int npcid) { + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd,0)=0xb6; + WFIFOL(fd,2)=npcid; + WFIFOSET(fd,packet_len_table[0xb6]); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_scriptmenu(struct map_session_data *sd, int npcid, char *mes) { + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd,0)=0xb7; + WFIFOW(fd,2)=strlen(mes)+8; + WFIFOL(fd,4)=npcid; + strcpy(WFIFOP(fd,8),mes); + WFIFOSET(fd,WFIFOW(fd,2)); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_scriptinput(struct map_session_data *sd, int npcid) { + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd,0)=0x142; + WFIFOL(fd,2)=npcid; + WFIFOSET(fd,packet_len_table[0x142]); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_scriptinputstr(struct map_session_data *sd, int npcid) { + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd,0)=0x1d4; + WFIFOL(fd,2)=npcid; + WFIFOSET(fd,packet_len_table[0x1d4]); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_viewpoint(struct map_session_data *sd, int npc_id, int type, int x, int y, int id, int color) { + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd,0)=0x144; + WFIFOL(fd,2)=npc_id; + WFIFOL(fd,6)=type; + WFIFOL(fd,10)=x; + WFIFOL(fd,14)=y; + WFIFOB(fd,18)=id; + WFIFOL(fd,19)=color; + WFIFOSET(fd,packet_len_table[0x144]); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_cutin(struct map_session_data *sd, char *image, int type) { + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd,0)=0x1b3; + memcpy(WFIFOP(fd,2),image,64); + WFIFOB(fd,66)=type; + WFIFOSET(fd,packet_len_table[0x1b3]); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_additem(struct map_session_data *sd, int n, int amount, int fail) { + int fd,j; + unsigned char *buf; + + nullpo_retr(0, sd); + + fd=sd->fd; + buf=WFIFOP(fd,0); + if(fail) { + WBUFW(buf,0)=0xa0; + WBUFW(buf,2)=n+2; + WBUFW(buf,4)=amount; + WBUFW(buf,6)=0; + WBUFB(buf,8)=0; + WBUFB(buf,9)=0; + WBUFB(buf,10)=0; + WBUFW(buf,11)=0; + WBUFW(buf,13)=0; + WBUFW(buf,15)=0; + WBUFW(buf,17)=0; + WBUFW(buf,19)=0; + WBUFB(buf,21)=0; + WBUFB(buf,22)=fail; + } else { + if (n<0 || n>=MAX_INVENTORY || sd->status.inventory[n].nameid <=0 || sd->inventory_data[n] == NULL) + return 1; + + WBUFW(buf,0)=0xa0; + WBUFW(buf,2)=n+2; + WBUFW(buf,4)=amount; + if (sd->inventory_data[n]->view_id > 0) + WBUFW(buf,6)=sd->inventory_data[n]->view_id; + else + WBUFW(buf,6)=sd->status.inventory[n].nameid; + WBUFB(buf,8)=sd->status.inventory[n].identify; + if (sd->status.inventory[n].broken==1) + WBUFB(buf,9)=1; // is weapon broken [Valaris] + else + WBUFB(buf,9)=sd->status.inventory[n].attribute; + WBUFB(buf,10)=sd->status.inventory[n].refine; + if(sd->status.inventory[n].card[0]==0x00ff || sd->status.inventory[n].card[0]==0x00fe || sd->status.inventory[n].card[0]==(short)0xff00) { + WBUFW(buf,11)=sd->status.inventory[n].card[0]; + WBUFW(buf,13)=sd->status.inventory[n].card[1]; + WBUFW(buf,15)=sd->status.inventory[n].card[2]; + WBUFW(buf,17)=sd->status.inventory[n].card[3]; + } else { + if (sd->status.inventory[n].card[0] > 0 && (j=itemdb_viewid(sd->status.inventory[n].card[0])) > 0) + WBUFW(buf,11)=j; + else + WBUFW(buf,11)=sd->status.inventory[n].card[0]; + if (sd->status.inventory[n].card[1] > 0 && (j=itemdb_viewid(sd->status.inventory[n].card[1])) > 0) + WBUFW(buf,13)=j; + else + WBUFW(buf,13)=sd->status.inventory[n].card[1]; + if (sd->status.inventory[n].card[2] > 0 && (j=itemdb_viewid(sd->status.inventory[n].card[2])) > 0) + WBUFW(buf,15)=j; + else + WBUFW(buf,15)=sd->status.inventory[n].card[2]; + if (sd->status.inventory[n].card[3] > 0 && (j=itemdb_viewid(sd->status.inventory[n].card[3])) > 0) + WBUFW(buf,17)=j; + else + WBUFW(buf,17)=sd->status.inventory[n].card[3]; + } + WBUFW(buf,19)=pc_equippoint(sd,n); + WBUFB(buf,21)=(sd->inventory_data[n]->type == 7)? 4:sd->inventory_data[n]->type; + WBUFB(buf,22)=fail; + } + + WFIFOSET(fd,packet_len_table[0xa0]); + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_delitem(struct map_session_data *sd,int n,int amount) +{ + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd,0)=0xaf; + WFIFOW(fd,2)=n+2; + WFIFOW(fd,4)=amount; + + WFIFOSET(fd,packet_len_table[0xaf]); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_itemlist(struct map_session_data *sd) +{ + int i,n,fd,arrow=-1; + unsigned char *buf; + + nullpo_retr(0, sd); + + fd=sd->fd; + buf = WFIFOP(fd,0); +#if PACKETVER < 5 + WBUFW(buf,0)=0xa3; + for(i=0,n=0;i<MAX_INVENTORY;i++){ + if (sd->status.inventory[i].nameid <=0 || sd->inventory_data[i] == NULL || itemdb_isequip2(sd->inventory_data[i])) + continue; + WBUFW(buf,n*10+4)=i+2; + if (sd->inventory_data[i]->view_id > 0) + WBUFW(buf,n*10+6)=sd->inventory_data[i]->view_id; + else + WBUFW(buf,n*10+6)=sd->status.inventory[i].nameid; + WBUFB(buf,n*10+8)=sd->inventory_data[i]->type; + WBUFB(buf,n*10+9)=sd->status.inventory[i].identify; + WBUFW(buf,n*10+10)=sd->status.inventory[i].amount; + if (sd->inventory_data[i]->equip == 0x8000) { + WBUFW(buf,n*10+12)=0x8000; + if (sd->status.inventory[i].equip) + arrow=i; // ついでに矢装備チェック + } else + WBUFW(buf,n*10+12)=0; + n++; + } + if (n) { + WBUFW(buf,2)=4+n*10; + WFIFOSET(fd,WFIFOW(fd,2)); + } +#else + WBUFW(buf,0)=0x1ee; + for(i=0,n=0;i<MAX_INVENTORY;i++){ + if(sd->status.inventory[i].nameid <=0 || sd->inventory_data[i] == NULL || itemdb_isequip2(sd->inventory_data[i])) + continue; + WBUFW(buf,n*18+4)=i+2; + if(sd->inventory_data[i]->view_id > 0) + WBUFW(buf,n*18+6)=sd->inventory_data[i]->view_id; + else + WBUFW(buf,n*18+6)=sd->status.inventory[i].nameid; + WBUFB(buf,n*18+8)=sd->inventory_data[i]->type; + WBUFB(buf,n*18+9)=sd->status.inventory[i].identify; + WBUFW(buf,n*18+10)=sd->status.inventory[i].amount; + if (sd->inventory_data[i]->equip == 0x8000) { + WBUFW(buf,n*18+12)=0x8000; + if(sd->status.inventory[i].equip) + arrow=i; // ついでに矢装備チェック + } else + WBUFW(buf,n*18+12)=0; + WBUFW(buf,n*18+14)=sd->status.inventory[i].card[0]; + WBUFW(buf,n*18+16)=sd->status.inventory[i].card[1]; + WBUFW(buf,n*18+18)=sd->status.inventory[i].card[2]; + WBUFW(buf,n*18+20)=sd->status.inventory[i].card[3]; + n++; + } + if (n) { + WBUFW(buf,2)=4+n*18; + WFIFOSET(fd,WFIFOW(fd,2)); + } +#endif + if(arrow >= 0) + clif_arrowequip(sd,arrow); + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_equiplist(struct map_session_data *sd) +{ + int i,j,n,fd; + unsigned char *buf; + + nullpo_retr(0, sd); + + fd=sd->fd; + buf = WFIFOP(fd,0); + WBUFW(buf,0)=0xa4; + for(i=0,n=0;i<MAX_INVENTORY;i++){ + if(sd->status.inventory[i].nameid<=0 || sd->inventory_data[i] == NULL || !itemdb_isequip2(sd->inventory_data[i])) + continue; + WBUFW(buf,n*20+4)=i+2; + if(sd->inventory_data[i]->view_id > 0) + WBUFW(buf,n*20+6)=sd->inventory_data[i]->view_id; + else + WBUFW(buf,n*20+6)=sd->status.inventory[i].nameid; + WBUFB(buf,n*20+8)=(sd->inventory_data[i]->type == 7)? 4:sd->inventory_data[i]->type; + WBUFB(buf,n*20+9)=sd->status.inventory[i].identify; + WBUFW(buf,n*20+10)=pc_equippoint(sd,i); + WBUFW(buf,n*20+12)=sd->status.inventory[i].equip; + if(sd->status.inventory[i].broken==1) + WBUFB(buf,n*20+14)=1; // is weapon broken [Valaris] + else + WBUFB(buf,n*20+14)=sd->status.inventory[i].attribute; + WBUFB(buf,n*20+15)=sd->status.inventory[i].refine; + if(sd->status.inventory[i].card[0]==0x00ff || sd->status.inventory[i].card[0]==0x00fe || sd->status.inventory[i].card[0]==(short)0xff00) { + WBUFW(buf,n*20+16)=sd->status.inventory[i].card[0]; + WBUFW(buf,n*20+18)=sd->status.inventory[i].card[1]; + WBUFW(buf,n*20+20)=sd->status.inventory[i].card[2]; + WBUFW(buf,n*20+22)=sd->status.inventory[i].card[3]; + } else { + if(sd->status.inventory[i].card[0] > 0 && (j=itemdb_viewid(sd->status.inventory[i].card[0])) > 0) + WBUFW(buf,n*20+16)=j; + else + WBUFW(buf,n*20+16)=sd->status.inventory[i].card[0]; + if(sd->status.inventory[i].card[1] > 0 && (j=itemdb_viewid(sd->status.inventory[i].card[1])) > 0) + WBUFW(buf,n*20+18)=j; + else + WBUFW(buf,n*20+18)=sd->status.inventory[i].card[1]; + if(sd->status.inventory[i].card[2] > 0 && (j=itemdb_viewid(sd->status.inventory[i].card[2])) > 0) + WBUFW(buf,n*20+20)=j; + else + WBUFW(buf,n*20+20)=sd->status.inventory[i].card[2]; + if(sd->status.inventory[i].card[3] > 0 && (j=itemdb_viewid(sd->status.inventory[i].card[3])) > 0) + WBUFW(buf,n*20+22)=j; + else + WBUFW(buf,n*20+22)=sd->status.inventory[i].card[3]; + } + n++; + } + if(n){ + WBUFW(buf,2)=4+n*20; + WFIFOSET(fd,WFIFOW(fd,2)); + } + return 0; +} + +/*========================================== + * カプラさんに預けてある消耗品&収集品リスト + *------------------------------------------ + */ +int clif_storageitemlist(struct map_session_data *sd,struct storage *stor) +{ + struct item_data *id; + int i,n,fd; + unsigned char *buf; + + nullpo_retr(0, sd); + nullpo_retr(0, stor); + + fd=sd->fd; + buf = WFIFOP(fd,0); +#if PACKETVER < 5 + WBUFW(buf,0)=0xa5; + for(i=0,n=0;i<MAX_STORAGE;i++){ + if(stor->storage[i].nameid<=0) + continue; + nullpo_retr(0, id = itemdb_search(stor->storage[i].nameid)); + if(itemdb_isequip2(id)) + continue; + + WBUFW(buf,n*10+4)=i+1; + if(id->view_id > 0) + WBUFW(buf,n*10+6)=id->view_id; + else + WBUFW(buf,n*10+6)=stor->storage[i].nameid; + WBUFB(buf,n*10+8)=id->type;; + WBUFB(buf,n*10+9)=stor->storage[i].identify; + WBUFW(buf,n*10+10)=stor->storage[i].amount; + WBUFW(buf,n*10+12)=0; + n++; + } + if(n){ + WBUFW(buf,2)=4+n*10; + WFIFOSET(fd,WFIFOW(fd,2)); + } +#else + WBUFW(buf,0)=0x1f0; + for(i=0,n=0;i<MAX_STORAGE;i++){ + if(stor->storage[i].nameid<=0) + continue; + nullpo_retr(0, id = itemdb_search(stor->storage[i].nameid)); + if(itemdb_isequip2(id)) + continue; + + WBUFW(buf,n*18+4)=i+1; + if(id->view_id > 0) + WBUFW(buf,n*18+6)=id->view_id; + else + WBUFW(buf,n*18+6)=stor->storage[i].nameid; + WBUFB(buf,n*18+8)=id->type;; + WBUFB(buf,n*18+9)=stor->storage[i].identify; + WBUFW(buf,n*18+10)=stor->storage[i].amount; + WBUFW(buf,n*18+12)=0; + WBUFW(buf,n*18+14)=stor->storage[i].card[0]; + WBUFW(buf,n*18+16)=stor->storage[i].card[1]; + WBUFW(buf,n*18+18)=stor->storage[i].card[2]; + WBUFW(buf,n*18+20)=stor->storage[i].card[3]; + n++; + } + if(n){ + WBUFW(buf,2)=4+n*18; + WFIFOSET(fd,WFIFOW(fd,2)); + } +#endif + return 0; +} + +/*========================================== + * カプラさんに預けてある装備リスト + *------------------------------------------ + */ +int clif_storageequiplist(struct map_session_data *sd,struct storage *stor) +{ + struct item_data *id; + int i,j,n,fd; + unsigned char *buf; + + nullpo_retr(0, sd); + nullpo_retr(0, stor); + + fd=sd->fd; + buf = WFIFOP(fd,0); + WBUFW(buf,0)=0xa6; + for(i=0,n=0;i<MAX_STORAGE;i++){ + if(stor->storage[i].nameid<=0) + continue; + nullpo_retr(0, id = itemdb_search(stor->storage[i].nameid)); + if(!itemdb_isequip2(id)) + continue; + WBUFW(buf,n*20+4)=i+1; + if(id->view_id > 0) + WBUFW(buf,n*20+6)=id->view_id; + else + WBUFW(buf,n*20+6)=stor->storage[i].nameid; + WBUFB(buf,n*20+8)=id->type; + WBUFB(buf,n*20+9)=stor->storage[i].identify; + WBUFW(buf,n*20+10)=id->equip; + WBUFW(buf,n*20+12)=stor->storage[i].equip; + if(stor->storage[i].broken==1) + WBUFB(buf,n*20+14)=1; //is weapon broken [Valaris] + else + WBUFB(buf,n*20+14)=stor->storage[i].attribute; + WBUFB(buf,n*20+15)=stor->storage[i].refine; + if(stor->storage[i].card[0]==0x00ff || stor->storage[i].card[0]==0x00fe || stor->storage[i].card[0]==(short)0xff00) { + WBUFW(buf,n*20+16)=stor->storage[i].card[0]; + WBUFW(buf,n*20+18)=stor->storage[i].card[1]; + WBUFW(buf,n*20+20)=stor->storage[i].card[2]; + WBUFW(buf,n*20+22)=stor->storage[i].card[3]; + } else { + if(stor->storage[i].card[0] > 0 && (j=itemdb_viewid(stor->storage[i].card[0])) > 0) + WBUFW(buf,n*20+16)=j; + else + WBUFW(buf,n*20+16)=stor->storage[i].card[0]; + if(stor->storage[i].card[1] > 0 && (j=itemdb_viewid(stor->storage[i].card[1])) > 0) + WBUFW(buf,n*20+18)=j; + else + WBUFW(buf,n*20+18)=stor->storage[i].card[1]; + if(stor->storage[i].card[2] > 0 && (j=itemdb_viewid(stor->storage[i].card[2])) > 0) + WBUFW(buf,n*20+20)=j; + else + WBUFW(buf,n*20+20)=stor->storage[i].card[2]; + if(stor->storage[i].card[3] > 0 && (j=itemdb_viewid(stor->storage[i].card[3])) > 0) + WBUFW(buf,n*20+22)=j; + else + WBUFW(buf,n*20+22)=stor->storage[i].card[3]; + } + n++; + } + if(n){ + WBUFW(buf,2)=4+n*20; + WFIFOSET(fd,WFIFOW(fd,2)); + } + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_guildstorageitemlist(struct map_session_data *sd,struct guild_storage *stor) +{ + struct item_data *id; + int i,n,fd; + unsigned char *buf; + + nullpo_retr(0, sd); + nullpo_retr(0, stor); + + fd=sd->fd; + buf=WFIFOP(fd,0); + +#if PACKETVER < 5 + WBUFW(buf,0)=0xa5; + for(i=0,n=0;i<MAX_GUILD_STORAGE;i++){ + if(stor->storage[i].nameid<=0) + continue; + nullpo_retr(0, id = itemdb_search(stor->storage[i].nameid)); + if(itemdb_isequip2(id)) + continue; + + WBUFW(buf,n*10+4)=i+1; + if(id->view_id > 0) + WBUFW(buf,n*10+6)=id->view_id; + else + WBUFW(buf,n*10+6)=stor->storage[i].nameid; + WBUFB(buf,n*10+8)=id->type;; + WBUFB(buf,n*10+9)=stor->storage[i].identify; + WBUFW(buf,n*10+10)=stor->storage[i].amount; + WBUFW(buf,n*10+12)=0; + n++; + } + if(n){ + WBUFW(buf,2)=4+n*10; + WFIFOSET(fd,WFIFOW(fd,2)); + } +#else + WBUFW(buf,0)=0x1f0; + for(i=0,n=0;i<MAX_GUILD_STORAGE;i++){ + if(stor->storage[i].nameid<=0) + continue; + nullpo_retr(0, id = itemdb_search(stor->storage[i].nameid)); + if(itemdb_isequip2(id)) + continue; + + WBUFW(buf,n*18+4)=i+1; + if(id->view_id > 0) + WBUFW(buf,n*18+6)=id->view_id; + else + WBUFW(buf,n*18+6)=stor->storage[i].nameid; + WBUFB(buf,n*18+8)=id->type;; + WBUFB(buf,n*18+9)=stor->storage[i].identify; + WBUFW(buf,n*18+10)=stor->storage[i].amount; + WBUFW(buf,n*18+12)=0; + WBUFW(buf,n*18+14)=stor->storage[i].card[0]; + WBUFW(buf,n*18+16)=stor->storage[i].card[1]; + WBUFW(buf,n*18+18)=stor->storage[i].card[2]; + WBUFW(buf,n*18+20)=stor->storage[i].card[3]; + n++; + } + if(n){ + WBUFW(buf,2)=4+n*18; + WFIFOSET(fd,WFIFOW(fd,2)); + } +#endif + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_guildstorageequiplist(struct map_session_data *sd,struct guild_storage *stor) +{ + struct item_data *id; + int i,j,n,fd; + unsigned char *buf; + + nullpo_retr(0, sd); + + fd=sd->fd; + buf=WFIFOP(fd,0); + + WBUFW(buf,0)=0xa6; + for(i=0,n=0;i<MAX_GUILD_STORAGE;i++){ + if(stor->storage[i].nameid<=0) + continue; + nullpo_retr(0, id = itemdb_search(stor->storage[i].nameid)); + if(!itemdb_isequip2(id)) + continue; + WBUFW(buf,n*20+4)=i+1; + if(id->view_id > 0) + WBUFW(buf,n*20+6)=id->view_id; + else + WBUFW(buf,n*20+6)=stor->storage[i].nameid; + WBUFB(buf,n*20+8)=id->type; + WBUFB(buf,n*20+9)=stor->storage[i].identify; + WBUFW(buf,n*20+10)=id->equip; + WBUFW(buf,n*20+12)=stor->storage[i].equip; + if(stor->storage[i].broken==1) + WBUFB(buf,n*20+14)=1; // is weapon broken [Valaris] + else + WBUFB(buf,n*20+14)=stor->storage[i].attribute; + WBUFB(buf,n*20+15)=stor->storage[i].refine; + if(stor->storage[i].card[0]==0x00ff || stor->storage[i].card[0]==0x00fe || stor->storage[i].card[0]==(short)0xff00) { + WBUFW(buf,n*20+16)=stor->storage[i].card[0]; + WBUFW(buf,n*20+18)=stor->storage[i].card[1]; + WBUFW(buf,n*20+20)=stor->storage[i].card[2]; + WBUFW(buf,n*20+22)=stor->storage[i].card[3]; + } else { + if(stor->storage[i].card[0] > 0 && (j=itemdb_viewid(stor->storage[i].card[0])) > 0) + WBUFW(buf,n*20+16)=j; + else + WBUFW(buf,n*20+16)=stor->storage[i].card[0]; + if(stor->storage[i].card[1] > 0 && (j=itemdb_viewid(stor->storage[i].card[1])) > 0) + WBUFW(buf,n*20+18)=j; + else + WBUFW(buf,n*20+18)=stor->storage[i].card[1]; + if(stor->storage[i].card[2] > 0 && (j=itemdb_viewid(stor->storage[i].card[2])) > 0) + WBUFW(buf,n*20+20)=j; + else + WBUFW(buf,n*20+20)=stor->storage[i].card[2]; + if(stor->storage[i].card[3] > 0 && (j=itemdb_viewid(stor->storage[i].card[3])) > 0) + WBUFW(buf,n*20+22)=j; + else + WBUFW(buf,n*20+22)=stor->storage[i].card[3]; + } + n++; + } + if(n){ + WBUFW(buf,2)=4+n*20; + WFIFOSET(fd,WFIFOW(fd,2)); + } + return 0; +} + +/*========================================== + * ステータスを送りつける + * 表示専用数字はこの中で計算して送る + *------------------------------------------ + */ +int clif_updatestatus(struct map_session_data *sd,int type) +{ + int fd,len=8; + + nullpo_retr(0, sd); + + fd=sd->fd; + + WFIFOW(fd,0)=0xb0; + WFIFOW(fd,2)=type; + switch(type){ + // 00b0 + case SP_WEIGHT: + pc_checkweighticon(sd); + WFIFOW(fd,0)=0xb0; + WFIFOW(fd,2)=type; + WFIFOL(fd,4)=sd->weight; + break; + case SP_MAXWEIGHT: + WFIFOL(fd,4)=sd->max_weight; + break; + case SP_SPEED: + WFIFOL(fd,4)=sd->speed; + break; + case SP_BASELEVEL: + WFIFOL(fd,4)=sd->status.base_level; + break; + case SP_JOBLEVEL: + WFIFOL(fd,4)=sd->status.job_level; + break; + case SP_MANNER: + WFIFOL(fd,4)=sd->status.manner; + clif_changestatus(&sd->bl,SP_MANNER,sd->status.manner); + break; + case SP_STATUSPOINT: + WFIFOL(fd,4)=sd->status.status_point; + break; + case SP_SKILLPOINT: + WFIFOL(fd,4)=sd->status.skill_point; + break; + case SP_HIT: + WFIFOL(fd,4)=sd->hit; + break; + case SP_FLEE1: + WFIFOL(fd,4)=sd->flee; + break; + case SP_FLEE2: + WFIFOL(fd,4)=sd->flee2/10; + break; + case SP_MAXHP: + WFIFOL(fd,4)=sd->status.max_hp; + break; + case SP_MAXSP: + WFIFOL(fd,4)=sd->status.max_sp; + break; + case SP_HP: + WFIFOL(fd,4)=sd->status.hp; + break; + case SP_SP: + WFIFOL(fd,4)=sd->status.sp; + break; + case SP_ASPD: + WFIFOL(fd,4)=sd->aspd; + break; + case SP_ATK1: + WFIFOL(fd,4)=sd->base_atk+sd->watk; + break; + case SP_DEF1: + WFIFOL(fd,4)=sd->def; + break; + case SP_MDEF1: + WFIFOL(fd,4)=sd->mdef; + break; + case SP_ATK2: + WFIFOL(fd,4)=sd->watk2; + break; + case SP_DEF2: + WFIFOL(fd,4)=sd->def2; + break; + case SP_MDEF2: + WFIFOL(fd,4)=sd->mdef2; + break; + case SP_CRITICAL: + WFIFOL(fd,4)=sd->critical/10; + break; + case SP_MATK1: + WFIFOL(fd,4)=sd->matk1; + break; + case SP_MATK2: + WFIFOL(fd,4)=sd->matk2; + break; + + + case SP_ZENY: + WFIFOW(fd,0)=0xb1; + if(sd->status.zeny < 0) + sd->status.zeny = 0; + WFIFOL(fd,4)=sd->status.zeny; + break; + case SP_BASEEXP: + WFIFOW(fd,0)=0xb1; + WFIFOL(fd,4)=sd->status.base_exp; + break; + case SP_JOBEXP: + WFIFOW(fd,0)=0xb1; + WFIFOL(fd,4)=sd->status.job_exp; + break; + case SP_NEXTBASEEXP: + WFIFOW(fd,0)=0xb1; + WFIFOL(fd,4)=pc_nextbaseexp(sd); + break; + case SP_NEXTJOBEXP: + WFIFOW(fd,0)=0xb1; + WFIFOL(fd,4)=pc_nextjobexp(sd); + break; + + // 00be 終了 + case SP_USTR: + case SP_UAGI: + case SP_UVIT: + case SP_UINT: + case SP_UDEX: + case SP_ULUK: + WFIFOW(fd,0)=0xbe; + WFIFOB(fd,4)=pc_need_status_point(sd,type-SP_USTR+SP_STR); + len=5; + break; + + // 013a 終了 + case SP_ATTACKRANGE: + WFIFOW(fd,0)=0x13a; + WFIFOW(fd,2)=sd->attackrange; + len=4; + break; + + // 0141 終了 + case SP_STR: + WFIFOW(fd,0)=0x141; + WFIFOL(fd,2)=type; + WFIFOL(fd,6)=sd->status.str; + WFIFOL(fd,10)=sd->paramb[0] + sd->parame[0]; + len=14; + break; + case SP_AGI: + WFIFOW(fd,0)=0x141; + WFIFOL(fd,2)=type; + WFIFOL(fd,6)=sd->status.agi; + WFIFOL(fd,10)=sd->paramb[1] + sd->parame[1]; + len=14; + break; + case SP_VIT: + WFIFOW(fd,0)=0x141; + WFIFOL(fd,2)=type; + WFIFOL(fd,6)=sd->status.vit; + WFIFOL(fd,10)=sd->paramb[2] + sd->parame[2]; + len=14; + break; + case SP_INT: + WFIFOW(fd,0)=0x141; + WFIFOL(fd,2)=type; + WFIFOL(fd,6)=sd->status.int_; + WFIFOL(fd,10)=sd->paramb[3] + sd->parame[3]; + len=14; + break; + case SP_DEX: + WFIFOW(fd,0)=0x141; + WFIFOL(fd,2)=type; + WFIFOL(fd,6)=sd->status.dex; + WFIFOL(fd,10)=sd->paramb[4] + sd->parame[4]; + len=14; + break; + case SP_LUK: + WFIFOW(fd,0)=0x141; + WFIFOL(fd,2)=type; + WFIFOL(fd,6)=sd->status.luk; + WFIFOL(fd,10)=sd->paramb[5] + sd->parame[5]; + len=14; + break; + + case SP_CARTINFO: + WFIFOW(fd,0)=0x121; + WFIFOW(fd,2)=sd->cart_num; + WFIFOW(fd,4)=sd->cart_max_num; + WFIFOL(fd,6)=sd->cart_weight; + WFIFOL(fd,10)=sd->cart_max_weight; + len=14; + break; + + default: + if(battle_config.error_log) + printf("clif_updatestatus : make %d routine\n",type); + return 1; + } + WFIFOSET(fd,len); + + return 0; +} +int clif_changestatus(struct block_list *bl,int type,int val) +{ + unsigned char buf[12]; + struct map_session_data *sd = NULL; + + nullpo_retr(0, bl); + + if(bl->type == BL_PC) + sd = (struct map_session_data *)bl; + +//printf("clif_changestatus id:%d type:%d val:%d\n",bl->id,type,val); + if(sd){ + WBUFW(buf,0)=0x1ab; + WBUFL(buf,2)=bl->id; + WBUFW(buf,6)=type; + switch(type){ + case SP_MANNER: + WBUFL(buf,8)=val; + break; + default: + if(battle_config.error_log) + printf("clif_changestatus : make %d routine\n",type); + return 1; + } + clif_send(buf,packet_len_table[0x1ab],bl,AREA_WOS); + } + return 0; +} +/*========================================== + * + *------------------------------------------ + */ +int clif_changelook(struct block_list *bl,int type,int val) +{ + + unsigned char buf[32]; + struct map_session_data *sd = NULL; + + nullpo_retr(0, bl); + + if(bl->type == BL_PC) + sd = (struct map_session_data *)bl; + + if(sd && sd->disguise > 23 && sd->disguise < 4001) // mob disguises [Valaris] + return 0; + +#if PACKETVER < 4 + if(sd && (type == LOOK_WEAPON || type == LOOK_SHIELD) && sd->view_class == 22) + val =0; + WBUFW(buf,0)=0xc3; + WBUFL(buf,2)=bl->id; + WBUFB(buf,6)=type; + WBUFB(buf,7)=val; + clif_send(buf,packet_len_table[0xc3],bl,AREA); +#else + if(sd && (type == LOOK_WEAPON || type == LOOK_SHIELD || type == LOOK_SHOES)) { + WBUFW(buf,0)=0x1d7; + WBUFL(buf,2)=bl->id; + if(type == LOOK_SHOES) { + WBUFB(buf,6)=9; + if(sd->equip_index[2] >= 0 && sd->inventory_data[sd->equip_index[2]]) { + if(sd->inventory_data[sd->equip_index[2]]->view_id > 0) + WBUFW(buf,7)=sd->inventory_data[sd->equip_index[2]]->view_id; + else + WBUFW(buf,7)=sd->status.inventory[sd->equip_index[2]].nameid; + } else + WBUFW(buf,7)=0; + WBUFW(buf,9)=0; + } + else { + WBUFB(buf,6)=2; + if(sd->equip_index[9] >= 0 && sd->inventory_data[sd->equip_index[9]] && sd->view_class != 22) { + if(sd->inventory_data[sd->equip_index[9]]->view_id > 0) + WBUFW(buf,7)=sd->inventory_data[sd->equip_index[9]]->view_id; + else + WBUFW(buf,7)=sd->status.inventory[sd->equip_index[9]].nameid; + } else + WBUFW(buf,7)=0; + if(sd->equip_index[8] >= 0 && sd->equip_index[8] != sd->equip_index[9] && sd->inventory_data[sd->equip_index[8]] && + sd->view_class != 22) { + if(sd->inventory_data[sd->equip_index[8]]->view_id > 0) + WBUFW(buf,9)=sd->inventory_data[sd->equip_index[8]]->view_id; + else + WBUFW(buf,9)=sd->status.inventory[sd->equip_index[8]].nameid; + } else + WBUFW(buf,9)=0; + } + clif_send(buf,packet_len_table[0x1d7],bl,AREA); + } + else if(sd && (type == LOOK_BASE) && (val > 255)) + { + WBUFW(buf,0)=0x1d7; + WBUFL(buf,2)=bl->id; + WBUFB(buf,6)=type; + WBUFW(buf,7)=val; + WBUFW(buf,9)=0; + clif_send(buf,packet_len_table[0x1d7],bl,AREA); + } else { + WBUFW(buf,0)=0xc3; + WBUFL(buf,2)=bl->id; + WBUFB(buf,6)=type; + WBUFB(buf,7)=val; + clif_send(buf,packet_len_table[0xc3],bl,AREA); + } +#endif + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_initialstatus(struct map_session_data *sd) +{ + int fd; + unsigned char *buf; + + nullpo_retr(0, sd); + + fd=sd->fd; + buf=WFIFOP(fd,0); + + WBUFW(buf,0)=0xbd; + WBUFW(buf,2)=sd->status.status_point; + WBUFB(buf,4)=(sd->status.str > 255)? 255:sd->status.str; + WBUFB(buf,5)=pc_need_status_point(sd,SP_STR); + WBUFB(buf,6)=(sd->status.agi > 255)? 255:sd->status.agi; + WBUFB(buf,7)=pc_need_status_point(sd,SP_AGI); + WBUFB(buf,8)=(sd->status.vit > 255)? 255:sd->status.vit; + WBUFB(buf,9)=pc_need_status_point(sd,SP_VIT); + WBUFB(buf,10)=(sd->status.int_ > 255)? 255:sd->status.int_; + WBUFB(buf,11)=pc_need_status_point(sd,SP_INT); + WBUFB(buf,12)=(sd->status.dex > 255)? 255:sd->status.dex; + WBUFB(buf,13)=pc_need_status_point(sd,SP_DEX); + WBUFB(buf,14)=(sd->status.luk > 255)? 255:sd->status.luk; + WBUFB(buf,15)=pc_need_status_point(sd,SP_LUK); + + WBUFW(buf,16) = sd->base_atk + sd->watk; + WBUFW(buf,18) = sd->watk2; //atk bonus + WBUFW(buf,20) = sd->matk1; + WBUFW(buf,22) = sd->matk2; + WBUFW(buf,24) = sd->def; // def + WBUFW(buf,26) = sd->def2; + WBUFW(buf,28) = sd->mdef; // mdef + WBUFW(buf,30) = sd->mdef2; + WBUFW(buf,32) = sd->hit; + WBUFW(buf,34) = sd->flee; + WBUFW(buf,36) = sd->flee2/10; + WBUFW(buf,38) = sd->critical/10; + WBUFW(buf,40) = sd->status.karma; + WBUFW(buf,42) = sd->status.manner; + + WFIFOSET(fd,packet_len_table[0xbd]); + + clif_updatestatus(sd,SP_STR); + clif_updatestatus(sd,SP_AGI); + clif_updatestatus(sd,SP_VIT); + clif_updatestatus(sd,SP_INT); + clif_updatestatus(sd,SP_DEX); + clif_updatestatus(sd,SP_LUK); + + clif_updatestatus(sd,SP_ATTACKRANGE); + clif_updatestatus(sd,SP_ASPD); + + return 0; +} + +/*========================================== + *矢装備 + *------------------------------------------ + */ +int clif_arrowequip(struct map_session_data *sd,int val) +{ + int fd; + + nullpo_retr(0, sd); + + if(sd->attacktarget && sd->attacktarget > 0) // [Valaris] + sd->attacktarget = 0; + + fd=sd->fd; + WFIFOW(fd,0)=0x013c; + WFIFOW(fd,2)=val+2;//矢のアイテムID + + WFIFOSET(fd,packet_len_table[0x013c]); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_arrow_fail(struct map_session_data *sd,int type) +{ + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd,0)=0x013b; + WFIFOW(fd,2)=type; + + WFIFOSET(fd,packet_len_table[0x013b]); + + return 0; +} + +/*========================================== + * 作成可能 矢リスト送信 + *------------------------------------------ + */ +int clif_arrow_create_list(struct map_session_data *sd) +{ + int i,c,view; + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd,0)=0x1ad; + + for(i=0,c=0;i<MAX_SKILL_ARROW_DB;i++){ + if(skill_arrow_db[i].nameid > 0 && pc_search_inventory(sd,skill_arrow_db[i].nameid)>=0){ + if((view = itemdb_viewid(skill_arrow_db[i].nameid)) > 0) + WFIFOW(fd,c*2+4) = view; + else + WFIFOW(fd,c*2+4) = skill_arrow_db[i].nameid; + c++; + } + } + WFIFOW(fd,2)=c*2+4; + WFIFOSET(fd,WFIFOW(fd,2)); + if(c > 0) sd->state.make_arrow_flag = 1; + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_statusupack(struct map_session_data *sd,int type,int ok,int val) +{ + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd,0)=0xbc; + WFIFOW(fd,2)=type; + WFIFOB(fd,4)=ok; + WFIFOB(fd,5)=val; + WFIFOSET(fd,packet_len_table[0xbc]); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_equipitemack(struct map_session_data *sd,int n,int pos,int ok) +{ + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd,0)=0xaa; + WFIFOW(fd,2)=n+2; + WFIFOW(fd,4)=pos; + WFIFOB(fd,6)=ok; + WFIFOSET(fd,packet_len_table[0xaa]); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_unequipitemack(struct map_session_data *sd,int n,int pos,int ok) +{ + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd,0)=0xac; + WFIFOW(fd,2)=n+2; + WFIFOW(fd,4)=pos; + WFIFOB(fd,6)=ok; + WFIFOSET(fd,packet_len_table[0xac]); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_misceffect(struct block_list* bl,int type) +{ + char buf[32]; + + nullpo_retr(0, bl); + + WBUFW(buf,0) = 0x19b; + WBUFL(buf,2) = bl->id; + WBUFL(buf,6) = type; + + clif_send(buf,packet_len_table[0x19b],bl,AREA); + + return 0; +} +int clif_misceffect2(struct block_list *bl, int type) { + unsigned char buf[24]; + + nullpo_retr(0, bl); + + memset(buf, 0, packet_len_table[0x1f3]); + + WBUFW(buf,0) = 0x1f3; + WBUFL(buf,2) = bl->id; + WBUFL(buf,6) = type; + + clif_send(buf, packet_len_table[0x1f3], bl, AREA); + + return 0; + +} +/*========================================== + * 表示オプション変更 + *------------------------------------------ + */ +int clif_changeoption(struct block_list* bl) +{ + char buf[32]; + short option; + struct status_change *sc_data; + static const int omask[]={ 0x10,0x20 }; + static const int scnum[]={ SC_FALCON, SC_RIDING }; + int i; + + nullpo_retr(0, bl); + + option = *battle_get_option(bl); + sc_data = battle_get_sc_data(bl); + + WBUFW(buf,0) = 0x119; + WBUFL(buf,2) = bl->id; + WBUFW(buf,6) = *battle_get_opt1(bl); + WBUFW(buf,8) = *battle_get_opt2(bl); + WBUFW(buf,10) = option; + WBUFB(buf,12) = 0; // ?? + + if(bl->type==BL_PC) { // disguises [Valaris] + struct map_session_data *sd=((struct map_session_data *)bl); + if(sd && sd->disguise > 23 && sd->disguise < 4001) { + clif_send(buf,packet_len_table[0x119],bl,AREA_WOS); + clif_spawnpc(sd); + } else + clif_send(buf,packet_len_table[0x119],bl,AREA); + } else + clif_send(buf,packet_len_table[0x119],bl,AREA); + + // アイコンの表示 + for(i=0;i<sizeof(omask)/sizeof(omask[0]);i++){ + if( option&omask[i] ){ + if( sc_data[scnum[i]].timer==-1) + skill_status_change_start(bl,scnum[i],0,0,0,0,0,0); + } else { + skill_status_change_end(bl,scnum[i],-1); + } + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_useitemack(struct map_session_data *sd,int index,int amount,int ok) +{ + nullpo_retr(0, sd); + + if(!ok) { + int fd=sd->fd; + WFIFOW(fd,0)=0xa8; + WFIFOW(fd,2)=index+2; + WFIFOW(fd,4)=amount; + WFIFOB(fd,6)=ok; + WFIFOSET(fd,packet_len_table[0xa8]); + } + else { +#if PACKETVER < 3 + int fd=sd->fd; + WFIFOW(fd,0)=0xa8; + WFIFOW(fd,2)=index+2; + WFIFOW(fd,4)=amount; + WFIFOB(fd,6)=ok; + WFIFOSET(fd,packet_len_table[0xa8]); +#else + char buf[32]; + + WBUFW(buf,0)=0x1c8; + WBUFW(buf,2)=index+2; + if(sd->inventory_data[index] && sd->inventory_data[index]->view_id > 0) + WBUFW(buf,4)=sd->inventory_data[index]->view_id; + else + WBUFW(buf,4)=sd->status.inventory[index].nameid; + WBUFL(buf,6)=sd->bl.id; + WBUFW(buf,10)=amount; + WBUFB(buf,12)=ok; + clif_send(buf,packet_len_table[0x1c8],&sd->bl,AREA); +#endif + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_createchat(struct map_session_data *sd,int fail) +{ + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd,0)=0xd6; + WFIFOB(fd,2)=fail; + WFIFOSET(fd,packet_len_table[0xd6]); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_dispchat(struct chat_data *cd,int fd) +{ + char buf[128]; // 最大title(60バイト)+17 + + if(cd==NULL || *cd->owner==NULL) + return 1; + + WBUFW(buf,0)=0xd7; + WBUFW(buf,2)=strlen(cd->title)+17; + WBUFL(buf,4)=(*cd->owner)->id; + WBUFL(buf,8)=cd->bl.id; + WBUFW(buf,12)=cd->limit; + WBUFW(buf,14)=cd->users; + WBUFB(buf,16)=cd->pub; + strcpy(WBUFP(buf,17),cd->title); + if(fd){ + memcpy(WFIFOP(fd,0),buf,WBUFW(buf,2)); + WFIFOSET(fd,WBUFW(buf,2)); + } else { + clif_send(buf,WBUFW(buf,2),*cd->owner,AREA_WOSC); + } + + return 0; +} + +/*========================================== + * chatの状態変更成功 + * 外部の人用と命令コード(d7->df)が違うだけ + *------------------------------------------ + */ +int clif_changechatstatus(struct chat_data *cd) +{ + char buf[128]; // 最大title(60バイト)+17 + + if(cd==NULL || cd->usersd[0]==NULL) + return 1; + + WBUFW(buf,0)=0xdf; + WBUFW(buf,2)=strlen(cd->title)+17; + WBUFL(buf,4)=cd->usersd[0]->bl.id; + WBUFL(buf,8)=cd->bl.id; + WBUFW(buf,12)=cd->limit; + WBUFW(buf,14)=cd->users; + WBUFB(buf,16)=cd->pub; + strcpy(WBUFP(buf,17),cd->title); + clif_send(buf,WBUFW(buf,2),&cd->usersd[0]->bl,CHAT); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_clearchat(struct chat_data *cd,int fd) +{ + char buf[32]; + + nullpo_retr(0, cd); + + WBUFW(buf,0)=0xd8; + WBUFL(buf,2)=cd->bl.id; + if(fd){ + memcpy(WFIFOP(fd,0),buf,packet_len_table[0xd8]); + WFIFOSET(fd,packet_len_table[0xd8]); + } else { + clif_send(buf,packet_len_table[0xd8],*cd->owner,AREA_WOSC); + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_joinchatfail(struct map_session_data *sd,int fail) +{ + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + + WFIFOW(fd,0)=0xda; + WFIFOB(fd,2)=fail; + WFIFOSET(fd,packet_len_table[0xda]); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_joinchatok(struct map_session_data *sd,struct chat_data* cd) +{ + int fd; + int i; + + nullpo_retr(0, sd); + nullpo_retr(0, cd); + + fd=sd->fd; + WFIFOW(fd,0)=0xdb; + WFIFOW(fd,2)=8+(28*cd->users); + WFIFOL(fd,4)=cd->bl.id; + for(i = 0;i < cd->users;i++){ + WFIFOL(fd,8+i*28) = (i!=0)||((*cd->owner)->type==BL_NPC); + memcpy(WFIFOP(fd,8+i*28+4),cd->usersd[i]->status.name,24); + } + WFIFOSET(fd,WFIFOW(fd,2)); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_addchat(struct chat_data* cd,struct map_session_data *sd) +{ + char buf[32]; + + nullpo_retr(0, sd); + nullpo_retr(0, cd); + + WBUFW(buf, 0) = 0x0dc; + WBUFW(buf, 2) = cd->users; + memcpy(WBUFP(buf, 4),sd->status.name,24); + clif_send(buf,packet_len_table[0xdc],&sd->bl,CHAT_WOS); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_changechatowner(struct chat_data* cd,struct map_session_data *sd) +{ + char buf[64]; + + nullpo_retr(0, sd); + nullpo_retr(0, cd); + + WBUFW(buf, 0) = 0xe1; + WBUFL(buf, 2) = 1; + memcpy(WBUFP(buf,6),cd->usersd[0]->status.name,24); + WBUFW(buf,30) = 0xe1; + WBUFL(buf,32) = 0; + memcpy(WBUFP(buf,36),sd->status.name,24); + + clif_send(buf,packet_len_table[0xe1]*2,&sd->bl,CHAT); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_leavechat(struct chat_data* cd,struct map_session_data *sd) +{ + char buf[32]; + + nullpo_retr(0, sd); + nullpo_retr(0, cd); + + WBUFW(buf, 0) = 0xdd; + WBUFW(buf, 2) = cd->users-1; + memcpy(WBUFP(buf,4),sd->status.name,24); + WBUFB(buf,28) = 0; + + clif_send(buf,packet_len_table[0xdd],&sd->bl,CHAT); + + return 0; +} + +/*========================================== + * 取り引き要請受け + *------------------------------------------ + */ +int clif_traderequest(struct map_session_data *sd,char *name) +{ + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd,0)=0xe5; + strcpy(WFIFOP(fd,2),name); + WFIFOSET(fd,packet_len_table[0xe5]); + + return 0; +} + +/*========================================== + * 取り引き要求応答 + *------------------------------------------ + */ +int clif_tradestart(struct map_session_data *sd,int type) +{ + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd,0)=0xe7; + WFIFOB(fd,2)=type; + WFIFOSET(fd,packet_len_table[0xe7]); + + return 0; +} + +/*========================================== + * 相手方からのアイテム追加 + *------------------------------------------ + */ +int clif_tradeadditem(struct map_session_data *sd,struct map_session_data *tsd,int index,int amount) +{ + int fd,j; + + nullpo_retr(0, sd); + nullpo_retr(0, tsd); + + fd=tsd->fd; + WFIFOW(fd,0)=0xe9; + WFIFOL(fd,2)=amount; + if(index==0){ + WFIFOW(fd,6) = 0; // type id + WFIFOB(fd,8) = 0; //identify flag + WFIFOB(fd,9) = 0; // attribute + WFIFOB(fd,10)= 0; //refine + WFIFOW(fd,11)= 0; //card (4w) + WFIFOW(fd,13)= 0; //card (4w) + WFIFOW(fd,15)= 0; //card (4w) + WFIFOW(fd,17)= 0; //card (4w) + } + else{ + index -= 2; + if(sd->inventory_data[index] && sd->inventory_data[index]->view_id > 0) + WFIFOW(fd,6) = sd->inventory_data[index]->view_id; + else + WFIFOW(fd,6) = sd->status.inventory[index].nameid; // type id + WFIFOB(fd,8) = sd->status.inventory[index].identify; //identify flag + if(sd->status.inventory[index].broken==1) + WFIFOB(fd,9) = 1; // is broke weapon [Valaris] + else + WFIFOB(fd,9) = sd->status.inventory[index].attribute; // attribute + WFIFOB(fd,10)= sd->status.inventory[index].refine; //refine + if(sd->status.inventory[index].card[0]==0x00ff || sd->status.inventory[index].card[0]==0x00fe || sd->status.inventory[index].card[0]==(short)0xff00) { + WFIFOW(fd,11)= sd->status.inventory[index].card[0]; //card (4w) + WFIFOW(fd,13)= sd->status.inventory[index].card[1]; //card (4w) + WFIFOW(fd,15)= sd->status.inventory[index].card[2]; //card (4w) + WFIFOW(fd,17)= sd->status.inventory[index].card[3]; //card (4w) + } else { + if(sd->status.inventory[index].card[0] > 0 && (j=itemdb_viewid(sd->status.inventory[index].card[0])) > 0) + WFIFOW(fd,11)= j; + else + WFIFOW(fd,11)= sd->status.inventory[index].card[0]; + if(sd->status.inventory[index].card[1] > 0 && (j=itemdb_viewid(sd->status.inventory[index].card[1])) > 0) + WFIFOW(fd,13)= j; + else + WFIFOW(fd,13)= sd->status.inventory[index].card[1]; + if(sd->status.inventory[index].card[2] > 0 && (j=itemdb_viewid(sd->status.inventory[index].card[2])) > 0) + WFIFOW(fd,15)= j; + else + WFIFOW(fd,15)= sd->status.inventory[index].card[2]; + if(sd->status.inventory[index].card[3] > 0 && (j=itemdb_viewid(sd->status.inventory[index].card[3])) > 0) + WFIFOW(fd,17)= j; + else + WFIFOW(fd,17)= sd->status.inventory[index].card[3]; + } + } + WFIFOSET(fd,packet_len_table[0xe9]); + + return 0; +} + +/*========================================== + * アイテム追加成功/失敗 + *------------------------------------------ + */ +int clif_tradeitemok(struct map_session_data *sd,int index,int amount,int fail) +{ + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd,0)=0x1b1; + //WFIFOW(fd,0)=0xea; + WFIFOW(fd,2)=index; + WFIFOW(fd,4)=amount; + WFIFOB(fd,6)=fail; + WFIFOSET(fd,packet_len_table[0x1b1]); + + return 0; +} + +/*========================================== + * 取り引きok押し + *------------------------------------------ + */ +int clif_tradedeal_lock(struct map_session_data *sd,int fail) +{ + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd,0)=0xec; + WFIFOB(fd,2)=fail; // 0=you 1=the other person + WFIFOSET(fd,packet_len_table[0xec]); + + return 0; +} + +/*========================================== + * 取り引きがキャンセルされました + *------------------------------------------ + */ +int clif_tradecancelled(struct map_session_data *sd) +{ + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd,0)=0xee; + WFIFOSET(fd,packet_len_table[0xee]); + + return 0; +} + +/*========================================== + * 取り引き完了 + *------------------------------------------ + */ +int clif_tradecompleted(struct map_session_data *sd,int fail) +{ + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd,0)=0xf0; + WFIFOB(fd,2)=fail; + WFIFOSET(fd,packet_len_table[0xf0]); + + return 0; +} + +/*========================================== + * カプラ倉庫のアイテム数を更新 + *------------------------------------------ + */ +int clif_updatestorageamount(struct map_session_data *sd,struct storage *stor) +{ + int fd; + + nullpo_retr(0, sd); + nullpo_retr(0, stor); + + fd=sd->fd; + WFIFOW(fd,0) = 0xf2; // update storage amount + WFIFOW(fd,2) = stor->storage_amount; //items + WFIFOW(fd,4) = MAX_STORAGE; //items max + WFIFOSET(fd,packet_len_table[0xf2]); + + return 0; +} + +/*========================================== + * カプラ倉庫にアイテムを追加する + *------------------------------------------ + */ +int clif_storageitemadded(struct map_session_data *sd,struct storage *stor,int index,int amount) +{ + int view,fd,j; + + nullpo_retr(0, sd); + nullpo_retr(0, stor); + + fd=sd->fd; + WFIFOW(fd,0) =0xf4; // Storage item added + WFIFOW(fd,2) =index+1; // index + WFIFOL(fd,4) =amount; // amount + if((view = itemdb_viewid(stor->storage[index].nameid)) > 0) + WFIFOW(fd,8) =view; + else + WFIFOW(fd,8) =stor->storage[index].nameid; // id + WFIFOB(fd,10)=stor->storage[index].identify; //identify flag + if(stor->storage[index].broken==1) + WFIFOB(fd,11)=1; // is weapon broken [Valaris] + else + WFIFOB(fd,11)=stor->storage[index].attribute; // attribute + WFIFOB(fd,12)=stor->storage[index].refine; //refine + if(stor->storage[index].card[0]==0x00ff || stor->storage[index].card[0]==0x00fe || stor->storage[index].card[0]==(short)0xff00) { + WFIFOW(fd,13)=stor->storage[index].card[0]; //card (4w) + WFIFOW(fd,15)=stor->storage[index].card[1]; //card (4w) + WFIFOW(fd,17)=stor->storage[index].card[2]; //card (4w) + WFIFOW(fd,19)=stor->storage[index].card[3]; //card (4w) + } else { + if(stor->storage[index].card[0] > 0 && (j=itemdb_viewid(stor->storage[index].card[0])) > 0) + WFIFOW(fd,13)= j; + else + WFIFOW(fd,13)= stor->storage[index].card[0]; + if(stor->storage[index].card[1] > 0 && (j=itemdb_viewid(stor->storage[index].card[1])) > 0) + WFIFOW(fd,15)= j; + else + WFIFOW(fd,15)= stor->storage[index].card[1]; + if(stor->storage[index].card[2] > 0 && (j=itemdb_viewid(stor->storage[index].card[2])) > 0) + WFIFOW(fd,17)= j; + else + WFIFOW(fd,17)= stor->storage[index].card[2]; + if(stor->storage[index].card[3] > 0 && (j=itemdb_viewid(stor->storage[index].card[3])) > 0) + WFIFOW(fd,19)= j; + else + WFIFOW(fd,19)= stor->storage[index].card[3]; + } + WFIFOSET(fd,packet_len_table[0xf4]); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_updateguildstorageamount(struct map_session_data *sd,struct guild_storage *stor) +{ + int fd; + + nullpo_retr(0, sd); + nullpo_retr(0, stor); + + fd=sd->fd; + WFIFOW(fd,0) = 0xf2; // update storage amount + WFIFOW(fd,2) = stor->storage_amount; //items + WFIFOW(fd,4) = MAX_GUILD_STORAGE; //items max + WFIFOSET(fd,packet_len_table[0xf2]); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_guildstorageitemadded(struct map_session_data *sd,struct guild_storage *stor,int index,int amount) +{ + int view,fd,j; + + nullpo_retr(0, sd); + nullpo_retr(0, stor); + + fd=sd->fd; + WFIFOW(fd,0) =0xf4; // Storage item added + WFIFOW(fd,2) =index+1; // index + WFIFOL(fd,4) =amount; // amount + if((view = itemdb_viewid(stor->storage[index].nameid)) > 0) + WFIFOW(fd,8) =view; + else + WFIFOW(fd,8) =stor->storage[index].nameid; // id + WFIFOB(fd,10)=stor->storage[index].identify; //identify flag + if(stor->storage[index].broken==1) + WFIFOB(fd,11)=1; // is weapon broken [Valaris] + else + WFIFOB(fd,11)=stor->storage[index].attribute; // attribute + WFIFOB(fd,12)=stor->storage[index].refine; //refine + if(stor->storage[index].card[0]==0x00ff || stor->storage[index].card[0]==0x00fe || stor->storage[index].card[0]==(short)0xff00) { + WFIFOW(fd,13)=stor->storage[index].card[0]; //card (4w) + WFIFOW(fd,15)=stor->storage[index].card[1]; //card (4w) + WFIFOW(fd,17)=stor->storage[index].card[2]; //card (4w) + WFIFOW(fd,19)=stor->storage[index].card[3]; //card (4w) + } else { + if(stor->storage[index].card[0] > 0 && (j=itemdb_viewid(stor->storage[index].card[0])) > 0) + WFIFOW(fd,13)= j; + else + WFIFOW(fd,13)= stor->storage[index].card[0]; + if(stor->storage[index].card[1] > 0 && (j=itemdb_viewid(stor->storage[index].card[1])) > 0) + WFIFOW(fd,15)= j; + else + WFIFOW(fd,15)= stor->storage[index].card[1]; + if(stor->storage[index].card[2] > 0 && (j=itemdb_viewid(stor->storage[index].card[2])) > 0) + WFIFOW(fd,17)= j; + else + WFIFOW(fd,17)= stor->storage[index].card[2]; + if(stor->storage[index].card[3] > 0 && (j=itemdb_viewid(stor->storage[index].card[3])) > 0) + WFIFOW(fd,19)= j; + else + WFIFOW(fd,19)= stor->storage[index].card[3]; + } + WFIFOSET(fd,packet_len_table[0xf4]); + + return 0; +} + +/*========================================== + * カプラ倉庫からアイテムを取り去る + *------------------------------------------ + */ +int clif_storageitemremoved(struct map_session_data *sd,int index,int amount) +{ + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd,0)=0xf6; // Storage item removed + WFIFOW(fd,2)=index+1; + WFIFOL(fd,4)=amount; + WFIFOSET(fd,packet_len_table[0xf6]); + + return 0; +} + +/*========================================== + * カプラ倉庫を閉じる + *------------------------------------------ + */ +int clif_storageclose(struct map_session_data *sd) +{ + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd,0)=0xf8; // Storage Closed + WFIFOSET(fd,packet_len_table[0xf8]); + + return 0; +} + +// +// callback系 ? +// +/*========================================== + * PC表示 + *------------------------------------------ + */ +void clif_getareachar_pc(struct map_session_data* sd,struct map_session_data* dstsd) +{ + int len; + + nullpo_retv(sd); + nullpo_retv(dstsd); + + if(dstsd->walktimer != -1){ + len = clif_set007b(dstsd,WFIFOP(sd->fd,0)); + WFIFOSET(sd->fd,len); + } else { + len = clif_set0078(dstsd,WFIFOP(sd->fd,0)); + WFIFOSET(sd->fd,len); + } + + if(dstsd->chatID){ + struct chat_data *cd; + cd=(struct chat_data*)map_id2bl(dstsd->chatID); + if(cd->usersd[0]==dstsd) + clif_dispchat(cd,sd->fd); + } + if(dstsd->vender_id){ + clif_showvendingboard(&dstsd->bl,dstsd->message,sd->fd); + } + if(dstsd->spiritball > 0) { + clif_set01e1(dstsd,WFIFOP(sd->fd,0)); + WFIFOSET(sd->fd,packet_len_table[0x1e1]); + } + if(battle_config.save_clothcolor==1 && dstsd->status.clothes_color > 0) + clif_changelook(&dstsd->bl,LOOK_CLOTHES_COLOR,dstsd->status.clothes_color); + + if(sd->status.manner < 0) + clif_changestatus(&sd->bl,SP_MANNER,sd->status.manner); + +} + +/*========================================== + * NPC表示 + *------------------------------------------ + */ +void clif_getareachar_npc(struct map_session_data* sd,struct npc_data* nd) +{ + int len; + + nullpo_retv(sd); + nullpo_retv(nd); + + if(nd->class < 0 || nd->flag&1 || nd->class == INVISIBLE_CLASS) + return; + + len = clif_npc0078(nd,WFIFOP(sd->fd,0)); + WFIFOSET(sd->fd,len); + + if(nd->chat_id){ + clif_dispchat((struct chat_data*)map_id2bl(nd->chat_id),sd->fd); + } + +} + +/*========================================== + * 移動停止 + *------------------------------------------ + */ +int clif_movemob(struct mob_data *md) +{ + unsigned char buf[256]; + int len; + + nullpo_retr(0, md); + + len = clif_mob007b(md,buf); + clif_send(buf,len,&md->bl,AREA); + + if(mob_get_equip(md->class) > 0) // mob equipment [Valaris] + clif_mob_equip(md,mob_get_equip(md->class)); + + return 0; +} + +/*========================================== + * モンスターの位置修正 + *------------------------------------------ + */ +int clif_fixmobpos(struct mob_data *md) +{ + unsigned char buf[256]; + int len; + + nullpo_retr(0, md); + + if(md->state.state == MS_WALK){ + len = clif_mob007b(md,buf); + clif_send(buf,len,&md->bl,AREA); + } else { + len = clif_mob0078(md,buf); + clif_send(buf,len,&md->bl,AREA); + } + + return 0; +} + +/*========================================== + * PCの位置修正 + *------------------------------------------ + */ +int clif_fixpcpos(struct map_session_data *sd) +{ + unsigned char buf[256]; + int len; + + nullpo_retr(0, sd); + + if(sd->walktimer != -1){ + len = clif_set007b(sd,buf); + clif_send(buf,len,&sd->bl,AREA); + } else { + len = clif_set0078(sd,buf); + clif_send(buf,len,&sd->bl,AREA); + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_fixpetpos(struct pet_data *pd) +{ + unsigned char buf[256]; + int len; + + nullpo_retr(0, pd); + + if(pd->state.state == MS_WALK){ + len = clif_pet007b(pd,buf); + clif_send(buf,len,&pd->bl,AREA); + } else { + len = clif_pet0078(pd,buf); + clif_send(buf,len,&pd->bl,AREA); + } + + return 0; +} + +/*========================================== + * 通常攻撃エフェクト&ダメージ + *------------------------------------------ + */ +int clif_damage(struct block_list *src,struct block_list *dst,unsigned int tick,int sdelay,int ddelay,int damage,int div,int type,int damage2) +{ + unsigned char buf[256]; + struct status_change *sc_data; + + nullpo_retr(0, src); + nullpo_retr(0, dst); + + sc_data = battle_get_sc_data(dst); + + if(type != 4 && dst->type == BL_PC && ((struct map_session_data *)dst)->special_state.infinite_endure) + type = 9; + if(sc_data) { + if(type != 4 && sc_data[SC_ENDURE].timer != -1) + type = 9; + if(sc_data[SC_HALLUCINATION].timer != -1) { + if(damage > 0) + damage = damage*(5+sc_data[SC_HALLUCINATION].val1) + rand()%100; + if(damage2 > 0) + damage2 = damage2*(5+sc_data[SC_HALLUCINATION].val1) + rand()%100; + } + } + + WBUFW(buf,0)=0x8a; + WBUFL(buf,2)=src->id; + WBUFL(buf,6)=dst->id; + WBUFL(buf,10)=tick; + WBUFL(buf,14)=sdelay; + WBUFL(buf,18)=ddelay; + WBUFW(buf,22)=(damage > 0x7fff)? 0x7fff:damage; + WBUFW(buf,24)=div; + WBUFB(buf,26)=type; + WBUFW(buf,27)=damage2; + clif_send(buf,packet_len_table[0x8a],src,AREA); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +void clif_getareachar_mob(struct map_session_data* sd,struct mob_data* md) +{ + int len; + nullpo_retv(sd); + nullpo_retv(md); + + if(md->state.state == MS_WALK){ + len = clif_mob007b(md,WFIFOP(sd->fd,0)); + WFIFOSET(sd->fd,len); + } else { + len = clif_mob0078(md,WFIFOP(sd->fd,0)); + WFIFOSET(sd->fd,len); + } + + if(mob_get_equip(md->class) > 0) // mob equipment [Valaris] + clif_mob_equip(md,mob_get_equip(md->class)); +} + +/*========================================== + * + *------------------------------------------ + */ +void clif_getareachar_pet(struct map_session_data* sd,struct pet_data* pd) +{ + int len; + + nullpo_retv(sd); + nullpo_retv(pd); + + if(pd->state.state == MS_WALK){ + len = clif_pet007b(pd,WFIFOP(sd->fd,0)); + WFIFOSET(sd->fd,len); + } else { + len = clif_pet0078(pd,WFIFOP(sd->fd,0)); + WFIFOSET(sd->fd,len); + } +} + +/*========================================== + * + *------------------------------------------ + */ +void clif_getareachar_item(struct map_session_data* sd,struct flooritem_data* fitem) +{ + int view,fd; + + nullpo_retv(sd); + nullpo_retv(fitem); + + fd=sd->fd; + //009d <ID>.l <item ID>.w <identify flag>.B <X>.w <Y>.w <amount>.w <subX>.B <subY>.B + WFIFOW(fd,0)=0x9d; + WFIFOL(fd,2)=fitem->bl.id; + if((view = itemdb_viewid(fitem->item_data.nameid)) > 0) + WFIFOW(fd,6)=view; + else + WFIFOW(fd,6)=fitem->item_data.nameid; + WFIFOB(fd,8)=fitem->item_data.identify; + WFIFOW(fd,9)=fitem->bl.x; + WFIFOW(fd,11)=fitem->bl.y; + WFIFOW(fd,13)=fitem->item_data.amount; + WFIFOB(fd,15)=fitem->subx; + WFIFOB(fd,16)=fitem->suby; + + WFIFOSET(fd,packet_len_table[0x9d]); +} +/*========================================== + * 場所スキルエフェクトが視界に入る + *------------------------------------------ + */ +int clif_getareachar_skillunit(struct map_session_data *sd,struct skill_unit *unit) +{ + int fd; + struct block_list *bl; + + nullpo_retr(0, unit); + + fd=sd->fd; + bl=map_id2bl(unit->group->src_id); +#if PACKETVER < 3 + memset(WFIFOP(fd,0),0,packet_len_table[0x11f]); + WFIFOW(fd, 0)=0x11f; + WFIFOL(fd, 2)=unit->bl.id; + WFIFOL(fd, 6)=unit->group->src_id; + WFIFOW(fd,10)=unit->bl.x; + WFIFOW(fd,12)=unit->bl.y; + WFIFOB(fd,14)=unit->group->unit_id; + WFIFOB(fd,15)=0; + WFIFOSET(fd,packet_len_table[0x11f]); +#else + memset(WFIFOP(fd,0),0,packet_len_table[0x1c9]); + WFIFOW(fd, 0)=0x1c9; + WFIFOL(fd, 2)=unit->bl.id; + WFIFOL(fd, 6)=unit->group->src_id; + WFIFOW(fd,10)=unit->bl.x; + WFIFOW(fd,12)=unit->bl.y; + WFIFOB(fd,14)=unit->group->unit_id; + WFIFOB(fd,15)=1; + WFIFOL(fd,15+1)=0; //1-4調べた限り固定 + WFIFOL(fd,15+5)=0; //5-8調べた限り固定 + //9-12マップごとで一定の77-80とはまた違う4バイトのかなり大きな数字 + WFIFOL(fd,15+13)=unit->bl.y - 0x12; //13-16ユニットのY座標-18っぽい(Y:17でFF FF FF FF) + WFIFOL(fd,15+17)=0x004f37dd; //17-20調べた限り固定 + WFIFOL(fd,15+21)=0x0012f674; //21-24調べた限り固定 + WFIFOL(fd,15+25)=0x0012f664; //25-28調べた限り固定 + WFIFOL(fd,15+29)=0x0012f654; //29-32調べた限り固定 + WFIFOL(fd,15+33)=0x77527bbc; //33-36調べた限り固定 + //37-39 + WFIFOB(fd,15+40)=0x2d; //40調べた限り固定 + WFIFOL(fd,15+41)=0; //41-44調べた限り0固定 + WFIFOL(fd,15+45)=0; //45-48調べた限り0固定 + WFIFOL(fd,15+49)=0; //49-52調べた限り0固定 + WFIFOL(fd,15+53)=0x0048d919; //53-56調べた限り固定 + WFIFOL(fd,15+57)=0x0000003e; //57-60調べた限り固定 + WFIFOL(fd,15+61)=0x0012f66c; //61-64調べた限り固定 + //65-68 + //69-72 + if(bl) WFIFOL(fd,15+73)=bl->y; //73-76術者のY座標 + WFIFOL(fd,15+77)=unit->bl.m; //77-80マップIDかなぁ?かなり2バイトで足りそうな数字 + WFIFOB(fd,15+81)=0xaa; //81終端文字0xaa + + /* Graffiti [Valaris] */ + if(unit->group->unit_id==0xb0) { + WFIFOL(fd,15)=1; + WFIFOL(fd,16)=1; + memcpy(WFIFOP(fd,17),unit->group->valstr,80); + } + + WFIFOSET(fd,packet_len_table[0x1c9]); +#endif + if(unit->group->skill_id == WZ_ICEWALL) + clif_set0192(fd,unit->bl.m,unit->bl.x,unit->bl.y,5); + + return 0; +} +/*========================================== + * 場所スキルエフェクトが視界から消える + *------------------------------------------ + */ +int clif_clearchar_skillunit(struct skill_unit *unit,int fd) +{ + nullpo_retr(0, unit); + + WFIFOW(fd, 0)=0x120; + WFIFOL(fd, 2)=unit->bl.id; + WFIFOSET(fd,packet_len_table[0x120]); + if(unit->group->skill_id == WZ_ICEWALL) + clif_set0192(fd,unit->bl.m,unit->bl.x,unit->bl.y,unit->val2); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_01ac(struct block_list *bl) +{ + char buf[32]; + + nullpo_retr(0, bl); + + WBUFW(buf, 0) = 0x1ac; + WBUFL(buf, 2) = bl->id; + + clif_send(buf,packet_len_table[0x1ac],bl,AREA); + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ + int clif_getareachar(struct block_list* bl,va_list ap) +{ + struct map_session_data *sd; + + nullpo_retr(0, bl); + nullpo_retr(0, ap); + + sd=va_arg(ap,struct map_session_data*); + + switch(bl->type){ + case BL_PC: + if(sd==(struct map_session_data*)bl) + break; + clif_getareachar_pc(sd,(struct map_session_data*) bl); + break; + case BL_NPC: + clif_getareachar_npc(sd,(struct npc_data*) bl); + break; + case BL_MOB: + clif_getareachar_mob(sd,(struct mob_data*) bl); + break; + case BL_PET: + clif_getareachar_pet(sd,(struct pet_data*) bl); + break; + case BL_ITEM: + clif_getareachar_item(sd,(struct flooritem_data*) bl); + break; + case BL_SKILL: + clif_getareachar_skillunit(sd,(struct skill_unit *)bl); + break; + default: + if(battle_config.error_log) + printf("get area char ??? %d\n",bl->type); + break; + } + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_pcoutsight(struct block_list *bl,va_list ap) +{ + struct map_session_data *sd,*dstsd; + + nullpo_retr(0, bl); + nullpo_retr(0, ap); + nullpo_retr(0, sd=va_arg(ap,struct map_session_data*)); + + switch(bl->type){ + case BL_PC: + dstsd = (struct map_session_data*) bl; + if(sd != dstsd) { + clif_clearchar_id(dstsd->bl.id,0,sd->fd); + clif_clearchar_id(sd->bl.id,0,dstsd->fd); + if(dstsd->chatID){ + struct chat_data *cd; + cd=(struct chat_data*)map_id2bl(dstsd->chatID); + if(cd->usersd[0]==dstsd) + clif_dispchat(cd,sd->fd); + } + if(dstsd->vender_id){ + clif_closevendingboard(&dstsd->bl,sd->fd); + } + } + break; + case BL_NPC: + if( ((struct npc_data *)bl)->class != INVISIBLE_CLASS ) + clif_clearchar_id(bl->id,0,sd->fd); + break; + case BL_MOB: + case BL_PET: + clif_clearchar_id(bl->id,0,sd->fd); + break; + case BL_ITEM: + clif_clearflooritem((struct flooritem_data*)bl,sd->fd); + break; + case BL_SKILL: + clif_clearchar_skillunit((struct skill_unit *)bl,sd->fd); + break; + } + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_pcinsight(struct block_list *bl,va_list ap) +{ + struct map_session_data *sd,*dstsd; + + nullpo_retr(0, bl); + nullpo_retr(0, ap); + nullpo_retr(0, sd=va_arg(ap,struct map_session_data*)); + + switch(bl->type){ + case BL_PC: + dstsd = (struct map_session_data *)bl; + if(sd != dstsd) { + clif_getareachar_pc(sd,dstsd); + clif_getareachar_pc(dstsd,sd); + } + break; + case BL_NPC: + clif_getareachar_npc(sd,(struct npc_data*)bl); + break; + case BL_MOB: + clif_getareachar_mob(sd,(struct mob_data*)bl); + break; + case BL_PET: + clif_getareachar_pet(sd,(struct pet_data*)bl); + break; + case BL_ITEM: + clif_getareachar_item(sd,(struct flooritem_data*)bl); + break; + case BL_SKILL: + clif_getareachar_skillunit(sd,(struct skill_unit *)bl); + break; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_moboutsight(struct block_list *bl,va_list ap) +{ + struct map_session_data *sd; + struct mob_data *md; + + nullpo_retr(0, bl); + nullpo_retr(0, ap); + nullpo_retr(0, md=va_arg(ap,struct mob_data*)); + + if(bl->type==BL_PC && (sd = (struct map_session_data*) bl)){ + clif_clearchar_id(md->bl.id,0,sd->fd); + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_mobinsight(struct block_list *bl,va_list ap) +{ + struct map_session_data *sd; + struct mob_data *md; + + nullpo_retr(0, bl); + nullpo_retr(0, ap); + + md=va_arg(ap,struct mob_data*); + if(bl->type==BL_PC && (sd = (struct map_session_data *)bl)){ + clif_getareachar_mob(sd,md); + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_petoutsight(struct block_list *bl,va_list ap) +{ + struct map_session_data *sd; + struct pet_data *pd; + + nullpo_retr(0, bl); + nullpo_retr(0, ap); + nullpo_retr(0, pd=va_arg(ap,struct pet_data*)); + + if(bl->type==BL_PC && (sd = (struct map_session_data*) bl)){ + clif_clearchar_id(pd->bl.id,0,sd->fd); + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_petinsight(struct block_list *bl,va_list ap) +{ + struct map_session_data *sd; + struct pet_data *pd; + + nullpo_retr(0, bl); + nullpo_retr(0, ap); + + pd=va_arg(ap,struct pet_data*); + if(bl->type==BL_PC && (sd = (struct map_session_data *)bl)){ + clif_getareachar_pet(sd,pd); + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_skillinfo(struct map_session_data *sd,int skillid,int type,int range) +{ + int fd,id; + + nullpo_retr(0, sd); + + fd=sd->fd; + if( (id=sd->status.skill[skillid].id) <= 0 ) + return 0; + WFIFOW(fd,0)=0x147; + WFIFOW(fd,2) = id; + if(type < 0) + WFIFOW(fd,4) = skill_get_inf(id); + else + WFIFOW(fd,4) = type; + WFIFOW(fd,6) = 0; + WFIFOW(fd,8) = sd->status.skill[skillid].lv; + WFIFOW(fd,10) = skill_get_sp(id,sd->status.skill[skillid].lv); + if(range < 0) { + range = skill_get_range(id,sd->status.skill[skillid].lv); + if(range < 0) + range = battle_get_range(&sd->bl) - (range + 1); + WFIFOW(fd,12)= range; + } else + WFIFOW(fd,12)= range; + memset(WFIFOP(fd,14),0,24); + if(!(skill_get_inf2(id)&0x01) || battle_config.quest_skill_learn == 1 || (battle_config.gm_allskill > 0 && pc_isGM(sd) >= battle_config.gm_allskill) ) + WFIFOB(fd,38)= (sd->status.skill[skillid].lv < skill_get_max(id) && sd->status.skill[skillid].flag ==0 )? 1:0; + else + WFIFOB(fd,38) = 0; + WFIFOSET(fd,packet_len_table[0x147]); + + return 0; +} + +/*========================================== + * スキルリストを送信する + *------------------------------------------ + */ +int clif_skillinfoblock(struct map_session_data *sd) +{ + int fd; + int i,c,len=4,id,range; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd,0)=0x10f; + for ( i = c = 0; i < MAX_SKILL; i++){ + if( (id=sd->status.skill[i].id)!=0 ){ + WFIFOW(fd,len ) = id; + WFIFOW(fd,len+2) = skill_get_inf(id); + WFIFOW(fd,len+4) = 0; + WFIFOW(fd,len+6) = sd->status.skill[i].lv; + WFIFOW(fd,len+8) = skill_get_sp(id,sd->status.skill[i].lv); + range = skill_get_range(id,sd->status.skill[i].lv); + if(range < 0) + range = battle_get_range(&sd->bl) - (range + 1); + WFIFOW(fd,len+10)= range; + memset(WFIFOP(fd,len+12),0,24); + if(!(skill_get_inf2(id)&0x01) || battle_config.quest_skill_learn == 1 || (battle_config.gm_allskill > 0 && pc_isGM(sd) >= battle_config.gm_allskill) ) + WFIFOB(fd,len+36)= (sd->status.skill[i].lv < skill_get_max(id) && sd->status.skill[i].flag ==0 )? 1:0; + else + WFIFOB(fd,len+36) = 0; + len+=37; + c++; + } + } + WFIFOW(fd,2)=len; + WFIFOSET(fd,len); + + return 0; +} + +/*========================================== + * スキル割り振り通知 + *------------------------------------------ + */ +int clif_skillup(struct map_session_data *sd,int skill_num) +{ + int range,fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd,0) = 0x10e; + WFIFOW(fd,2) = skill_num; + WFIFOW(fd,4) = sd->status.skill[skill_num].lv; + WFIFOW(fd,6) = skill_get_sp(skill_num,sd->status.skill[skill_num].lv); + range = skill_get_range(skill_num,sd->status.skill[skill_num].lv); + if(range < 0) + range = battle_get_range(&sd->bl) - (range + 1); + WFIFOW(fd,8) = range; + WFIFOB(fd,10) = (sd->status.skill[skill_num].lv < skill_get_max(sd->status.skill[skill_num].id)) ? 1 : 0; + WFIFOSET(fd,packet_len_table[0x10e]); + + return 0; +} + +/*========================================== + * スキル詠唱エフェクトを送信する + *------------------------------------------ + */ +int clif_skillcasting(struct block_list* bl, + int src_id,int dst_id,int dst_x,int dst_y,int skill_num,int casttime) +{ + unsigned char buf[32]; + WBUFW(buf,0) = 0x13e; + WBUFL(buf,2) = src_id; + WBUFL(buf,6) = dst_id; + WBUFW(buf,10) = dst_x; + WBUFW(buf,12) = dst_y; + WBUFW(buf,14) = skill_num;//魔法詠唱スキル + WBUFL(buf,16) = skill_get_pl(skill_num);//属性 + WBUFL(buf,20) = casttime;//skill詠唱時間 + clif_send(buf,packet_len_table[0x13e], bl, AREA); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_skillcastcancel(struct block_list* bl) +{ + unsigned char buf[16]; + + nullpo_retr(0, bl); + + WBUFW(buf,0) = 0x1b9; + WBUFL(buf,2) = bl->id; + clif_send(buf,packet_len_table[0x1b9], bl, AREA); + + return 0; +} + +/*========================================== + * スキル詠唱失敗 + *------------------------------------------ + */ +int clif_skill_fail(struct map_session_data *sd,int skill_id,int type,int btype) +{ + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + + if(type==0x4 && battle_config.display_delay_skill_fail==0){ + return 0; + } + + WFIFOW(fd,0) = 0x110; + WFIFOW(fd,2) = skill_id; + WFIFOW(fd,4) = btype; + WFIFOW(fd,6) = 0; + WFIFOB(fd,8) = 0; + WFIFOB(fd,9) = type; + WFIFOSET(fd,packet_len_table[0x110]); + + return 0; +} + +/*========================================== + * スキル攻撃エフェクト&ダメージ + *------------------------------------------ + */ +int clif_skill_damage(struct block_list *src,struct block_list *dst, + unsigned int tick,int sdelay,int ddelay,int damage,int div,int skill_id,int skill_lv,int type) +{ + unsigned char buf[64]; + struct status_change *sc_data; + + nullpo_retr(0, src); + nullpo_retr(0, dst); + + sc_data = battle_get_sc_data(dst); + + if(type != 5 && dst->type == BL_PC && ((struct map_session_data *)dst)->special_state.infinite_endure) + type = 9; + if(sc_data) { + if(type != 5 && sc_data[SC_ENDURE].timer != -1) + type = 9; + if(sc_data[SC_HALLUCINATION].timer != -1 && damage > 0) + damage = damage*(5+sc_data[SC_HALLUCINATION].val1) + rand()%100; + } + +#if PACKETVER < 3 + WBUFW(buf,0)=0x114; + WBUFW(buf,2)=skill_id; + WBUFL(buf,4)=src->id; + WBUFL(buf,8)=dst->id; + WBUFL(buf,12)=tick; + WBUFL(buf,16)=sdelay; + WBUFL(buf,20)=ddelay; + WBUFW(buf,24)=damage; + WBUFW(buf,26)=skill_lv; + WBUFW(buf,28)=div; + WBUFB(buf,30)=(type>0)?type:skill_get_hit(skill_id); + clif_send(buf,packet_len_table[0x114],src,AREA); +#else + WBUFW(buf,0)=0x1de; + WBUFW(buf,2)=skill_id; + WBUFL(buf,4)=src->id; + WBUFL(buf,8)=dst->id; + WBUFL(buf,12)=tick; + WBUFL(buf,16)=sdelay; + WBUFL(buf,20)=ddelay; + WBUFL(buf,24)=damage; + WBUFW(buf,28)=skill_lv; + WBUFW(buf,30)=div; + WBUFB(buf,32)=(type>0)?type:skill_get_hit(skill_id); + clif_send(buf,packet_len_table[0x1de],src,AREA); +#endif + + return 0; +} + +/*========================================== + * 吹き飛ばしスキル攻撃エフェクト&ダメージ + *------------------------------------------ + */ +int clif_skill_damage2(struct block_list *src,struct block_list *dst, + unsigned int tick,int sdelay,int ddelay,int damage,int div,int skill_id,int skill_lv,int type) +{ + unsigned char buf[64]; + struct status_change *sc_data; + + nullpo_retr(0, src); + nullpo_retr(0, dst); + + sc_data = battle_get_sc_data(dst); + + if(type != 5 && dst->type == BL_PC && ((struct map_session_data *)dst)->special_state.infinite_endure) + type = 9; + if(sc_data) { + if(type != 5 && sc_data[SC_ENDURE].timer != -1) + type = 9; + if(sc_data[SC_HALLUCINATION].timer != -1 && damage > 0) + damage = damage*(5+sc_data[SC_HALLUCINATION].val1) + rand()%100; + } + + WBUFW(buf,0)=0x115; + WBUFW(buf,2)=skill_id; + WBUFL(buf,4)=src->id; + WBUFL(buf,8)=dst->id; + WBUFL(buf,12)=tick; + WBUFL(buf,16)=sdelay; + WBUFL(buf,20)=ddelay; + WBUFW(buf,24)=dst->x; + WBUFW(buf,26)=dst->y; + WBUFW(buf,28)=damage; + WBUFW(buf,30)=skill_lv; + WBUFW(buf,32)=div; + WBUFB(buf,34)=(type>0)?type:skill_get_hit(skill_id); + clif_send(buf,packet_len_table[0x115],src,AREA); + + return 0; +} + +/*========================================== + * 支援/回復スキルエフェクト + *------------------------------------------ + */ +int clif_skill_nodamage(struct block_list *src,struct block_list *dst, + int skill_id,int heal,int fail) +{ + unsigned char buf[32]; + + nullpo_retr(0, src); + nullpo_retr(0, dst); + + WBUFW(buf,0)=0x11a; + WBUFW(buf,2)=skill_id; + WBUFW(buf,4)=(heal > 0x7fff)? 0x7fff:heal; + WBUFL(buf,6)=dst->id; + WBUFL(buf,10)=src->id; + WBUFB(buf,14)=fail; + clif_send(buf,packet_len_table[0x11a],src,AREA); + + return 0; +} + +/*========================================== + * 場所スキルエフェクト + *------------------------------------------ + */ +int clif_skill_poseffect(struct block_list *src,int skill_id,int val,int x,int y,int tick) +{ + unsigned char buf[32]; + + nullpo_retr(0, src); + + WBUFW(buf,0)=0x117; + WBUFW(buf,2)=skill_id; + WBUFL(buf,4)=src->id; + WBUFW(buf,8)=val; + WBUFW(buf,10)=x; + WBUFW(buf,12)=y; + WBUFL(buf,14)=tick; + clif_send(buf,packet_len_table[0x117],src,AREA); + + return 0; +} + +/*========================================== + * 場所スキルエフェクト表示 + *------------------------------------------ + */ +int clif_skill_setunit(struct skill_unit *unit) +{ + unsigned char buf[128]; + struct block_list *bl; + + nullpo_retr(0, unit); + + bl=map_id2bl(unit->group->src_id); + +#if PACKETVER < 3 + memset(WBUFP(buf, 0),0,packet_len_table[0x11f]); + WBUFW(buf, 0)=0x11f; + WBUFL(buf, 2)=unit->bl.id; + WBUFL(buf, 6)=unit->group->src_id; + WBUFW(buf,10)=unit->bl.x; + WBUFW(buf,12)=unit->bl.y; + WBUFB(buf,14)=unit->group->unit_id; + WBUFB(buf,15)=0; + clif_send(buf,packet_len_table[0x11f],&unit->bl,AREA); +#else + memset(WBUFP(buf, 0),0,packet_len_table[0x1c9]); + WBUFW(buf, 0)=0x1c9; + WBUFL(buf, 2)=unit->bl.id; + WBUFL(buf, 6)=unit->group->src_id; + WBUFW(buf,10)=unit->bl.x; + WBUFW(buf,12)=unit->bl.y; + WBUFB(buf,14)=unit->group->unit_id; + WBUFB(buf,15)=1; + WBUFL(buf,15+1)=0; //1-4調べた限り固定 + WBUFL(buf,15+5)=0; //5-8調べた限り固定 + //9-12マップごとで一定の77-80とはまた違う4バイトのかなり大きな数字 + WBUFL(buf,15+13)=unit->bl.y - 0x12; //13-16ユニットのY座標-18っぽい(Y:17でFF FF FF FF) + WBUFL(buf,15+17)=0x004f37dd; //17-20調べた限り固定(0x1b2で0x004fdbddだった) + WBUFL(buf,15+21)=0x0012f674; //21-24調べた限り固定 + WBUFL(buf,15+25)=0x0012f664; //25-28調べた限り固定 + WBUFL(buf,15+29)=0x0012f654; //29-32調べた限り固定 + WBUFL(buf,15+33)=0x77527bbc; //33-36調べた限り固定 + //37-39 + WBUFB(buf,15+40)=0x2d; //40調べた限り固定 + WBUFL(buf,15+41)=0; //41-44調べた限り0固定 + WBUFL(buf,15+45)=0; //45-48調べた限り0固定 + WBUFL(buf,15+49)=0; //49-52調べた限り0固定 + WBUFL(buf,15+53)=0x0048d919; //53-56調べた限り固定(0x01b2で0x00495119だった) + WBUFL(buf,15+57)=0x0000003e; //57-60調べた限り固定 + WBUFL(buf,15+61)=0x0012f66c; //61-64調べた限り固定 + //65-68 + //69-72 + if(bl) WBUFL(buf,15+73)=bl->y; //73-76術者のY座標 + WBUFL(buf,15+77)=unit->bl.m; //77-80マップIDかなぁ?かなり2バイトで足りそうな数字 + WBUFB(buf,15+81)=0xaa; //81終端文字0xaa + + /* Graffiti [Valaris] */ + if(unit->group->unit_id==0xb0) { + WBUFL(buf,15)=1; + WBUFL(buf,16)=1; + memcpy(WBUFP(buf,17),unit->group->valstr,80); + } + + clif_send(buf,packet_len_table[0x1c9],&unit->bl,AREA); +#endif + return 0; +} +/*========================================== + * 場所スキルエフェクト削除 + *------------------------------------------ + */ +int clif_skill_delunit(struct skill_unit *unit) +{ + unsigned char buf[16]; + + nullpo_retr(0, unit); + + WBUFW(buf, 0)=0x120; + WBUFL(buf, 2)=unit->bl.id; + clif_send(buf,packet_len_table[0x120],&unit->bl,AREA); + return 0; +} +/*========================================== + * ワープ場所選択 + *------------------------------------------ + */ +int clif_skill_warppoint(struct map_session_data *sd,int skill_num, + const char *map1,const char *map2,const char *map3,const char *map4) +{ + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd,0)=0x11c; + WFIFOW(fd,2)=skill_num; + memcpy(WFIFOP(fd, 4),map1,16); + memcpy(WFIFOP(fd,20),map2,16); + memcpy(WFIFOP(fd,36),map3,16); + memcpy(WFIFOP(fd,52),map4,16); + WFIFOSET(fd,packet_len_table[0x11c]); + return 0; +} +/*========================================== + * メモ応答 + *------------------------------------------ + */ +int clif_skill_memo(struct map_session_data *sd,int flag) +{ + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + + WFIFOW(fd,0)=0x11e; + WFIFOB(fd,2)=flag; + WFIFOSET(fd,packet_len_table[0x11e]); + return 0; +} +int clif_skill_teleportmessage(struct map_session_data *sd,int flag) +{ + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd,0)=0x189; + WFIFOW(fd,2)=flag; + WFIFOSET(fd,packet_len_table[0x189]); + return 0; +} + +/*========================================== + * モンスター情報 + *------------------------------------------ + */ +int clif_skill_estimation(struct map_session_data *sd,struct block_list *dst) +{ + struct mob_data *md; + unsigned char buf[64]; + int i; + + nullpo_retr(0, sd); + nullpo_retr(0, dst); + + if(dst->type!=BL_MOB ) + return 0; + if((md=(struct mob_data *)dst) == NULL) + return 0; + + WBUFW(buf, 0)=0x18c; + WBUFW(buf, 2)=mob_get_viewclass(md->class); + WBUFW(buf, 4)=mob_db[md->class].lv; + WBUFW(buf, 6)=mob_db[md->class].size; + WBUFL(buf, 8)=md->hp; + WBUFW(buf,12)=battle_get_def2(&md->bl); + WBUFW(buf,14)=mob_db[md->class].race; + WBUFW(buf,16)=battle_get_mdef2(&md->bl) - (mob_db[md->class].vit>>1); + WBUFW(buf,18)=battle_get_elem_type(&md->bl); + for(i=0;i<9;i++) + WBUFB(buf,20+i)= battle_attr_fix(100,i+1,md->def_ele); + + if(sd->status.party_id>0) + clif_send(buf,packet_len_table[0x18c],&sd->bl,PARTY_AREA); + else{ + memcpy(WFIFOP(sd->fd,0),buf,packet_len_table[0x18c]); + WFIFOSET(sd->fd,packet_len_table[0x18c]); + } + return 0; +} +/*========================================== + * アイテム合成可能リスト + *------------------------------------------ + */ +int clif_skill_produce_mix_list(struct map_session_data *sd,int trigger) +{ + int i,c,view,fd; + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd, 0)=0x18d; + + for(i=0,c=0;i<MAX_SKILL_PRODUCE_DB;i++){ + if( skill_can_produce_mix(sd,skill_produce_db[i].nameid,trigger) ){ + if((view = itemdb_viewid(skill_produce_db[i].nameid)) > 0) + WFIFOW(fd,c*8+ 4)= view; + else + WFIFOW(fd,c*8+ 4)= skill_produce_db[i].nameid; + WFIFOW(fd,c*8+ 6)= 0x0012; + WFIFOL(fd,c*8+ 8)= sd->status.char_id; + c++; + } + } + WFIFOW(fd, 2)=c*8+8; + WFIFOSET(fd,WFIFOW(fd,2)); + if(c > 0) sd->state.produce_flag = 1; + return 0; +} + +/*========================================== + * 状態異常アイコン/メッセージ表示 + *------------------------------------------ + */ +int clif_status_change(struct block_list *bl,int type,int flag) +{ + unsigned char buf[16]; + + nullpo_retr(0, bl); + + WBUFW(buf,0)=0x0196; + WBUFW(buf,2)=type; + WBUFL(buf,4)=bl->id; + WBUFB(buf,8)=flag; + clif_send(buf,packet_len_table[0x196],bl,AREA); + return 0; +} + +/*========================================== + * Send message (modified by [Yor]) + *------------------------------------------ + */ +int clif_displaymessage(const int fd, char* mes) +{ + int len_mes = strlen(mes); + + if (len_mes > 0) { // don't send a void message (it's not displaying on the client chat). @help can send void line. + WFIFOW(fd,0) = 0x8e; + WFIFOW(fd,2) = 5 + len_mes; // 4 + len + NULL teminate + memcpy(WFIFOP(fd,4), mes, len_mes + 1); + WFIFOSET(fd, 5 + len_mes); + } + + return 0; +} + +/*========================================== + * 天の声を送信する + *------------------------------------------ + */ +int clif_GMmessage(struct block_list *bl, char* mes, int len, int flag) +{ + unsigned char lbuf[255]; + unsigned char *buf = ((len + 16) >= sizeof(lbuf)) ? malloc(len+16) : lbuf; + int lp = (flag&0x10) ? 8 : 4; + + WBUFW(buf,0) = 0x9a; + WBUFW(buf,2) = len + lp; + WBUFL(buf,4) = 0x65756c62; + memcpy(WBUFP(buf,lp), mes, len); + flag &= 0x07; + clif_send(buf, WBUFW(buf,2), bl, + (flag==1) ? ALL_SAMEMAP: + (flag==2) ? AREA: + (flag==3) ? SELF: + ALL_CLIENT); + if (buf != lbuf) + free(buf); + return 0; +} + +/*========================================== + * HPSP回復エフェクトを送信する + *------------------------------------------ + */ +int clif_heal(int fd,int type,int val) +{ + WFIFOW(fd,0)=0x13d; + WFIFOW(fd,2)=type; + WFIFOW(fd,4)=val; + WFIFOSET(fd,packet_len_table[0x13d]); + + return 0; +} + +/*========================================== + * 復活する + *------------------------------------------ + */ +int clif_resurrection(struct block_list *bl,int type) +{ + unsigned char buf[16]; + + nullpo_retr(0, bl); + + if(bl->type==BL_PC) { // disguises [Valaris] + struct map_session_data *sd=((struct map_session_data *)bl); + if(sd && sd->disguise > 23 && sd->disguise < 4001) + clif_spawnpc(sd); + } + + WBUFW(buf,0)=0x148; + WBUFL(buf,2)=bl->id; + WBUFW(buf,6)=type; + + clif_send(buf,packet_len_table[0x148],bl,type==1 ? AREA : AREA_WOS); + + return 0; +} + +/*========================================== + * PVP実装?(仮) + *------------------------------------------ + */ +int clif_set0199(int fd,int type) +{ + WFIFOW(fd,0)=0x199; + WFIFOW(fd,2)=type; + WFIFOSET(fd,packet_len_table[0x199]); + + return 0; +} + +/*========================================== + * PVP実装?(仮) + *------------------------------------------ + */ +int clif_pvpset(struct map_session_data *sd,int pvprank,int pvpnum,int type) +{ + nullpo_retr(0, sd); + + if(map[sd->bl.m].flag.nopvp) + return 0; + + if(type == 2) { + WFIFOW(sd->fd,0) = 0x19a; + WFIFOL(sd->fd,2) = sd->bl.id; + if(pvprank<=0) + pc_calc_pvprank(sd); + WFIFOL(sd->fd,6) = pvprank; + WFIFOL(sd->fd,10) = pvpnum; + WFIFOSET(sd->fd,packet_len_table[0x19a]); + } else { + char buf[32]; + + WBUFW(buf,0) = 0x19a; + WBUFL(buf,2) = sd->bl.id; + if(sd->status.option&0x46) + WBUFL(buf,6) = -1; + else + if(pvprank<=0) + pc_calc_pvprank(sd); + WBUFL(buf,6) = pvprank; + WBUFL(buf,10) = pvpnum; + if(!type) + clif_send(buf,packet_len_table[0x19a],&sd->bl,AREA); + else + clif_send(buf,packet_len_table[0x19a],&sd->bl,ALL_SAMEMAP); + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_send0199(int map,int type) +{ + struct block_list bl; + char buf[16]; + + bl.m = map; + WBUFW(buf,0)=0x199; + WBUFW(buf,2)=type; + clif_send(buf,packet_len_table[0x199],&bl,ALL_SAMEMAP); + + return 0; +} + +/*========================================== + * 精錬エフェクトを送信する + *------------------------------------------ + */ +int clif_refine(int fd,struct map_session_data *sd,int fail,int index,int val) +{ + WFIFOW(fd,0)=0x188; + WFIFOW(fd,2)=fail; + WFIFOW(fd,4)=index+2; + WFIFOW(fd,6)=val; + WFIFOSET(fd,packet_len_table[0x188]); + + return 0; +} + +/*========================================== + * Wisp/page is transmitted to the destination player + *------------------------------------------ + */ +int clif_wis_message(int fd, char *nick, char *mes, int mes_len) // R 0097 <len>.w <nick>.24B <message>.?B +{ + WFIFOW(fd,0) = 0x97; + WFIFOW(fd,2) = mes_len + 24 + 4; + memcpy(WFIFOP(fd,4), nick, 24); + memcpy(WFIFOP(fd,28), mes, mes_len); + WFIFOSET(fd,WFIFOW(fd,2)); + return 0; +} + +/*========================================== + * The transmission result of Wisp/page is transmitted to the source player + *------------------------------------------ + */ +int clif_wis_end(int fd, int flag) // R 0098 <type>.B: 0: success to send wisper, 1: target character is not loged in?, 2: ignored by target +{ + WFIFOW(fd,0) = 0x98; + WFIFOW(fd,2) = flag; + WFIFOSET(fd,packet_len_table[0x98]); + return 0; +} + +/*========================================== + * キャラID名前引き結果を送信する + *------------------------------------------ + */ +int clif_solved_charname(struct map_session_data *sd,int char_id) +{ + char *p= map_charid2nick(char_id); + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + if(p!=NULL){ + WFIFOW(fd,0)=0x194; + WFIFOL(fd,2)=char_id; + memcpy(WFIFOP(fd,6), p,24 ); + WFIFOSET(fd,packet_len_table[0x194]); + }else{ + map_reqchariddb(sd,char_id); + chrif_searchcharid(char_id); + } + return 0; +} + +/*========================================== + * カードの挿入可能リストを返す + *------------------------------------------ + */ +int clif_use_card(struct map_session_data *sd,int idx) +{ + nullpo_retr(0, sd); + + if(sd->inventory_data[idx]) { + int i,c; + int ep=sd->inventory_data[idx]->equip; + int fd=sd->fd; + WFIFOW(fd,0)=0x017b; + + for(i=c=0;i<MAX_INVENTORY;i++){ + int j; + + if(sd->inventory_data[i] == NULL) + continue; + if(sd->inventory_data[i]->type!=4 && sd->inventory_data[i]->type!=5) // 武器防具じゃない + continue; + if(sd->status.inventory[i].card[0]==0x00ff) // 製造武器 + continue; + if(sd->status.inventory[i].card[0]==(short)0xff00 || sd->status.inventory[i].card[0]==0x00fe) + continue; + if(sd->status.inventory[i].identify==0 ) // 未鑑定 + continue; + + if((sd->inventory_data[i]->equip&ep)==0) // 装備個所が違う + continue; + if(sd->inventory_data[i]->type==4 && ep==32) // 盾カードと両手武器 + continue; + + for(j=0;j<sd->inventory_data[i]->slot;j++){ + if( sd->status.inventory[i].card[j]==0 ) + break; + } + if(j==sd->inventory_data[i]->slot) // すでにカードが一杯 + continue; + + WFIFOW(fd,4+c*2)=i+2; + c++; + } + WFIFOW(fd,2)=4+c*2; + WFIFOSET(fd,WFIFOW(fd,2)); + } + + return 0; +} +/*========================================== + * カードの挿入終了 + *------------------------------------------ + */ +int clif_insert_card(struct map_session_data *sd,int idx_equip,int idx_card,int flag) +{ + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd,0)=0x17d; + WFIFOW(fd,2)=idx_equip+2; + WFIFOW(fd,4)=idx_card+2; + WFIFOB(fd,6)=flag; + WFIFOSET(fd,packet_len_table[0x17d]); + return 0; +} + +/*========================================== + * 鑑定可能アイテムリスト送信 + *------------------------------------------ + */ +int clif_item_identify_list(struct map_session_data *sd) +{ + int i,c; + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + + WFIFOW(fd,0)=0x177; + for(i=c=0;i<MAX_INVENTORY;i++){ + if(sd->status.inventory[i].nameid > 0 && sd->status.inventory[i].identify!=1){ + WFIFOW(fd,c*2+4)=i+2; + c++; + } + } + if(c > 0) { + WFIFOW(fd,2)=c*2+4; + WFIFOSET(fd,WFIFOW(fd,2)); + } + return 0; +} + +/*========================================== + * 鑑定結果 + *------------------------------------------ + */ +int clif_item_identified(struct map_session_data *sd,int idx,int flag) +{ + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd, 0)=0x179; + WFIFOW(fd, 2)=idx+2; + WFIFOB(fd, 4)=flag; + WFIFOSET(fd,packet_len_table[0x179]); + return 0; +} + +/*========================================== + * 修理可能アイテムリスト送信 + * ※実際のパケットがわからないので動作しません + *------------------------------------------ + */ +int clif_item_repair_list(struct map_session_data *sd) +{ + int i,c; + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + + WFIFOW(fd,0)=0x0; + for(i=c=0;i<MAX_INVENTORY;i++){ + if(sd->status.inventory[i].nameid > 0 && sd->status.inventory[i].broken!=0){ + WFIFOW(fd,c*2+4)=i+2; + c++; + } + } + if(c > 0) { + WFIFOW(fd,2)=c*2+4; + WFIFOSET(fd,WFIFOW(fd,2)); + } + return 0; +} + +/*========================================== + * アイテムによる一時的なスキル効果 + *------------------------------------------ + */ +int clif_item_skill(struct map_session_data *sd,int skillid,int skilllv,const char *name) +{ + int range,fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd, 0)=0x147; + WFIFOW(fd, 2)=skillid; + WFIFOW(fd, 4)=skill_get_inf(skillid); + WFIFOW(fd, 6)=0; + WFIFOW(fd, 8)=skilllv; + WFIFOW(fd,10)=skill_get_sp(skillid,skilllv); + range = skill_get_range(skillid,skilllv); + if(range < 0) + range = battle_get_range(&sd->bl) - (range + 1); + WFIFOW(fd,12)=range; + memcpy(WFIFOP(fd,14),name,24); + WFIFOB(fd,38)=0; + WFIFOSET(fd,packet_len_table[0x147]); + return 0; +} + +/*========================================== + * カートにアイテム追加 + *------------------------------------------ + */ +int clif_cart_additem(struct map_session_data *sd,int n,int amount,int fail) +{ + int view,j,fd; + unsigned char *buf; + + nullpo_retr(0, sd); + + fd=sd->fd; + buf=WFIFOP(fd,0); + if(n<0 || n>=MAX_CART || sd->status.cart[n].nameid<=0) + return 1; + + WBUFW(buf,0)=0x124; + WBUFW(buf,2)=n+2; + WBUFL(buf,4)=amount; + if((view = itemdb_viewid(sd->status.cart[n].nameid)) > 0) + WBUFW(buf,8)=view; + else + WBUFW(buf,8)=sd->status.cart[n].nameid; + WBUFB(buf,10)=sd->status.cart[n].identify; + if(sd->status.cart[n].broken==1) //is weapon broken [Valaris] + WBUFB(buf,11)=1; + else + WBUFB(buf,11)=sd->status.cart[n].attribute; + WBUFB(buf,12)=sd->status.cart[n].refine; + if(sd->status.cart[n].card[0]==0x00ff || sd->status.cart[n].card[0]==0x00fe || sd->status.cart[n].card[0]==(short)0xff00) { + WBUFW(buf,13)=sd->status.cart[n].card[0]; + WBUFW(buf,15)=sd->status.cart[n].card[1]; + WBUFW(buf,17)=sd->status.cart[n].card[2]; + WBUFW(buf,19)=sd->status.cart[n].card[3]; + } else { + if(sd->status.cart[n].card[0] > 0 && (j=itemdb_viewid(sd->status.cart[n].card[0])) > 0) + WBUFW(buf,13)= j; + else + WBUFW(buf,13)= sd->status.cart[n].card[0]; + if(sd->status.cart[n].card[1] > 0 && (j=itemdb_viewid(sd->status.cart[n].card[1])) > 0) + WBUFW(buf,15)= j; + else + WBUFW(buf,15)= sd->status.cart[n].card[1]; + if(sd->status.cart[n].card[2] > 0 && (j=itemdb_viewid(sd->status.cart[n].card[2])) > 0) + WBUFW(buf,17)= j; + else + WBUFW(buf,17)= sd->status.cart[n].card[2]; + if(sd->status.cart[n].card[3] > 0 && (j=itemdb_viewid(sd->status.cart[n].card[3])) > 0) + WBUFW(buf,19)= j; + else + WBUFW(buf,19)= sd->status.cart[n].card[3]; + } + WFIFOSET(fd,packet_len_table[0x124]); + return 0; +} + +/*========================================== + * カートからアイテム削除 + *------------------------------------------ + */ +int clif_cart_delitem(struct map_session_data *sd,int n,int amount) +{ + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + + WFIFOW(fd,0)=0x125; + WFIFOW(fd,2)=n+2; + WFIFOL(fd,4)=amount; + + WFIFOSET(fd,packet_len_table[0x125]); + + return 0; +} + +/*========================================== + * カートのアイテムリスト + *------------------------------------------ + */ +int clif_cart_itemlist(struct map_session_data *sd) +{ + struct item_data *id; + int i,n,fd; + unsigned char *buf; + + nullpo_retr(0, sd); + + fd=sd->fd; + buf = WFIFOP(fd,0); +#if PACKETVER < 5 + WBUFW(buf,0)=0x123; + for(i=0,n=0;i<MAX_CART;i++){ + if(sd->status.cart[i].nameid<=0) + continue; + id = itemdb_search(sd->status.cart[i].nameid); + if(itemdb_isequip2(id)) + continue; + WBUFW(buf,n*10+4)=i+2; + if(id->view_id > 0) + WBUFW(buf,n*10+6)=id->view_id; + else + WBUFW(buf,n*10+6)=sd->status.cart[i].nameid; + WBUFB(buf,n*10+8)=id->type; + WBUFB(buf,n*10+9)=sd->status.cart[i].identify; + WBUFW(buf,n*10+10)=sd->status.cart[i].amount; + WBUFW(buf,n*10+12)=0; + n++; + } + if(n){ + WBUFW(buf,2)=4+n*10; + WFIFOSET(fd,WFIFOW(fd,2)); + } +#else + WBUFW(buf,0)=0x1ef; + for(i=0,n=0;i<MAX_CART;i++){ + if(sd->status.cart[i].nameid<=0) + continue; + id = itemdb_search(sd->status.cart[i].nameid); + if(itemdb_isequip2(id)) + continue; + WBUFW(buf,n*18+4)=i+2; + if(id->view_id > 0) + WBUFW(buf,n*18+6)=id->view_id; + else + WBUFW(buf,n*18+6)=sd->status.cart[i].nameid; + WBUFB(buf,n*18+8)=id->type; + WBUFB(buf,n*18+9)=sd->status.cart[i].identify; + WBUFW(buf,n*18+10)=sd->status.cart[i].amount; + WBUFW(buf,n*18+12)=0; + WBUFW(buf,n*18+14)=sd->status.cart[i].card[0]; + WBUFW(buf,n*18+16)=sd->status.cart[i].card[1]; + WBUFW(buf,n*18+18)=sd->status.cart[i].card[2]; + WBUFW(buf,n*18+20)=sd->status.cart[i].card[3]; + n++; + } + if(n){ + WBUFW(buf,2)=4+n*18; + WFIFOSET(fd,WFIFOW(fd,2)); + } +#endif + return 0; +} + +/*========================================== + * カートの装備品リスト + *------------------------------------------ + */ +int clif_cart_equiplist(struct map_session_data *sd) +{ + struct item_data *id; + int i,j,n,fd; + unsigned char *buf; + + nullpo_retr(0, sd); + + fd=sd->fd; + buf = WFIFOP(fd,0); + + WBUFW(buf,0)=0x122; + for(i=0,n=0;i<MAX_INVENTORY;i++){ + if(sd->status.cart[i].nameid<=0) + continue; + id = itemdb_search(sd->status.cart[i].nameid); + if(!itemdb_isequip2(id)) + continue; + WBUFW(buf,n*20+4)=i+2; + if(id->view_id > 0) + WBUFW(buf,n*20+6)=id->view_id; + else + WBUFW(buf,n*20+6)=sd->status.cart[i].nameid; + WBUFB(buf,n*20+8)=id->type; + WBUFB(buf,n*20+9)=sd->status.cart[i].identify; + WBUFW(buf,n*20+10)=id->equip; + WBUFW(buf,n*20+12)=sd->status.cart[i].equip; + if(sd->status.cart[i].broken==1) + WBUFB(buf,n*20+14)=1; //is weapon broken [Valaris] + else + WBUFB(buf,n*20+14)=sd->status.cart[i].attribute; + WBUFB(buf,n*20+15)=sd->status.cart[i].refine; + if(sd->status.cart[i].card[0]==0x00ff || sd->status.cart[i].card[0]==0x00fe || sd->status.cart[i].card[0]==(short)0xff00) { + WBUFW(buf,n*20+16)=sd->status.cart[i].card[0]; + WBUFW(buf,n*20+18)=sd->status.cart[i].card[1]; + WBUFW(buf,n*20+20)=sd->status.cart[i].card[2]; + WBUFW(buf,n*20+22)=sd->status.cart[i].card[3]; + } else { + if(sd->status.cart[i].card[0] > 0 && (j=itemdb_viewid(sd->status.cart[i].card[0])) > 0) + WBUFW(buf,n*20+16)= j; + else + WBUFW(buf,n*20+16)= sd->status.cart[i].card[0]; + if(sd->status.cart[i].card[1] > 0 && (j=itemdb_viewid(sd->status.cart[i].card[1])) > 0) + WBUFW(buf,n*20+18)= j; + else + WBUFW(buf,n*20+18)= sd->status.cart[i].card[1]; + if(sd->status.cart[i].card[2] > 0 && (j=itemdb_viewid(sd->status.cart[i].card[2])) > 0) + WBUFW(buf,n*20+20)= j; + else + WBUFW(buf,n*20+20)= sd->status.cart[i].card[2]; + if(sd->status.cart[i].card[3] > 0 && (j=itemdb_viewid(sd->status.cart[i].card[3])) > 0) + WBUFW(buf,n*20+22)= j; + else + WBUFW(buf,n*20+22)= sd->status.cart[i].card[3]; + } + n++; + } + if(n){ + WBUFW(buf,2)=4+n*20; + WFIFOSET(fd,WFIFOW(fd,2)); + } + return 0; +} + +/*========================================== + * 露店開設 + *------------------------------------------ + */ +int clif_openvendingreq(struct map_session_data *sd,int num) +{ + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd,0)=0x12d; + WFIFOW(fd,2)=num; + WFIFOSET(fd,packet_len_table[0x12d]); + + return 0; +} + +/*========================================== + * 露店看板表示 + *------------------------------------------ + */ +int clif_showvendingboard(struct block_list* bl,char *message,int fd) +{ + unsigned char buf[128]; + + nullpo_retr(0, bl); + + WBUFW(buf,0)=0x131; + WBUFL(buf,2)=bl->id; + strncpy(WBUFP(buf,6),message,80); + if(fd){ + memcpy(WFIFOP(fd,0),buf,packet_len_table[0x131]); + WFIFOSET(fd,packet_len_table[0x131]); + }else{ + clif_send(buf,packet_len_table[0x131],bl,AREA_WOS); + } + return 0; +} + +/*========================================== + * 露店看板消去 + *------------------------------------------ + */ +int clif_closevendingboard(struct block_list* bl,int fd) +{ + unsigned char buf[16]; + + nullpo_retr(0, bl); + + WBUFW(buf,0)=0x132; + WBUFL(buf,2)=bl->id; + if(fd){ + memcpy(WFIFOP(fd,0),buf,packet_len_table[0x132]); + WFIFOSET(fd,packet_len_table[0x132]); + }else{ + clif_send(buf,packet_len_table[0x132],bl,AREA_WOS); + } + + return 0; +} +/*========================================== + * 露店アイテムリスト + *------------------------------------------ + */ +int clif_vendinglist(struct map_session_data *sd,int id,struct vending *vending) +{ + struct item_data *data; + int i,j,n,index,fd; + struct map_session_data *vsd; + unsigned char *buf; + + nullpo_retr(0, sd); + nullpo_retr(0, vending); + nullpo_retr(0, vsd=map_id2sd(id)); + + fd=sd->fd; + buf = WFIFOP(fd,0); + WBUFW(buf,0)=0x133; + WBUFL(buf,4)=id; + for(i=0,n=0;i<vsd->vend_num;i++){ + if(vending[i].amount<=0) + continue; + WBUFL(buf,8+n*22)=vending[i].value; + WBUFW(buf,12+n*22)=vending[i].amount; + WBUFW(buf,14+n*22)=(index=vending[i].index)+2; + if(vsd->status.cart[index].nameid <= 0 || vsd->status.cart[index].amount <= 0) + continue; + data = itemdb_search(vsd->status.cart[index].nameid); + WBUFB(buf,16+n*22)=data->type; + if(data->view_id > 0) + WBUFW(buf,17+n*22)=data->view_id; + else + WBUFW(buf,17+n*22)=vsd->status.cart[index].nameid; + WBUFB(buf,19+n*22)=vsd->status.cart[index].identify; + if(vsd->status.cart[index].broken==1) + WBUFB(buf,20+n*22)=1; //is weapon broken [Valaris] + else + WBUFB(buf,20+n*22)=vsd->status.cart[index].attribute; + WBUFB(buf,21+n*22)=vsd->status.cart[index].refine; + if(vsd->status.cart[index].card[0]==0x00ff || vsd->status.cart[index].card[0]==0x00fe || vsd->status.cart[index].card[0]==(short)0xff00) { + WBUFW(buf,22+n*22)=vsd->status.cart[index].card[0]; + WBUFW(buf,24+n*22)=vsd->status.cart[index].card[1]; + WBUFW(buf,26+n*22)=vsd->status.cart[index].card[2]; + WBUFW(buf,28+n*22)=vsd->status.cart[index].card[3]; + } else { + if(vsd->status.cart[index].card[0] > 0 && (j=itemdb_viewid(vsd->status.cart[index].card[0])) > 0) + WBUFW(buf,22+n*22)= j; + else + WBUFW(buf,22+n*22)= vsd->status.cart[index].card[0]; + if(vsd->status.cart[index].card[1] > 0 && (j=itemdb_viewid(vsd->status.cart[index].card[1])) > 0) + WBUFW(buf,24+n*22)= j; + else + WBUFW(buf,24+n*22)= vsd->status.cart[index].card[1]; + if(vsd->status.cart[index].card[2] > 0 && (j=itemdb_viewid(vsd->status.cart[index].card[2])) > 0) + WBUFW(buf,26+n*22)= j; + else + WBUFW(buf,26+n*22)= vsd->status.cart[index].card[2]; + if(vsd->status.cart[index].card[3] > 0 && (j=itemdb_viewid(vsd->status.cart[index].card[3])) > 0) + WBUFW(buf,28+n*22)= j; + else + WBUFW(buf,28+n*22)= vsd->status.cart[index].card[3]; + } + n++; + } + if(n > 0){ + WBUFW(buf,2)=8+n*22; + WFIFOSET(fd,WFIFOW(fd,2)); + } + + return 0; +} + +/*========================================== + * 露店アイテム購入失敗 + *------------------------------------------ +*/ +int clif_buyvending(struct map_session_data *sd,int index,int amount,int fail) +{ + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd,0)=0x135; + WFIFOW(fd,2)=index+2; + WFIFOW(fd,4)=amount; + WFIFOB(fd,6)=fail; + WFIFOSET(fd,packet_len_table[0x135]); + + return 0; +} + +/*========================================== + * 露店開設成功 + *------------------------------------------ +*/ +int clif_openvending(struct map_session_data *sd,int id,struct vending *vending) +{ + struct item_data *data; + int i,j,n,index,fd; + unsigned char *buf; + + nullpo_retr(0, sd); + + fd=sd->fd; + buf = WFIFOP(fd,0); + + WBUFW(buf,0)=0x136; + WBUFL(buf,4)=id; + for(i=0,n=0;i<sd->vend_num;i++){ + if (sd->vend_num > 2+pc_checkskill(sd,MC_VENDING)) return 0; + WBUFL(buf,8+n*22)=vending[i].value; + WBUFW(buf,12+n*22)=(index=vending[i].index)+2; + WBUFW(buf,14+n*22)=vending[i].amount; + if(sd->status.cart[index].nameid <= 0 || sd->status.cart[index].amount <= 0 || sd->status.cart[index].identify==0 || + sd->status.cart[index].broken==1) // Prevent unidentified and broken items from being sold [Valaris] + continue; + data = itemdb_search(sd->status.cart[index].nameid); + WBUFB(buf,16+n*22)=data->type; + if(data->view_id > 0) + WBUFW(buf,17+n*22)=data->view_id; + else + WBUFW(buf,17+n*22)=sd->status.cart[index].nameid; + WBUFB(buf,19+n*22)=sd->status.cart[index].identify; + if(sd->status.cart[index].broken==1) + WBUFB(buf,20+n*22)=1; // is weapon broken [Valaris] + else + WBUFB(buf,20+n*22)=sd->status.cart[index].attribute; + WBUFB(buf,21+n*22)=sd->status.cart[index].refine; + if(sd->status.cart[index].card[0]==0x00ff || sd->status.cart[index].card[0]==0x00fe || sd->status.cart[index].card[0]==(short)0xff00) { + WBUFW(buf,22+n*22)=sd->status.cart[index].card[0]; + WBUFW(buf,24+n*22)=sd->status.cart[index].card[1]; + WBUFW(buf,26+n*22)=sd->status.cart[index].card[2]; + WBUFW(buf,28+n*22)=sd->status.cart[index].card[3]; + } else { + if(sd->status.cart[index].card[0] > 0 && (j=itemdb_viewid(sd->status.cart[index].card[0])) > 0) + WBUFW(buf,22+n*22)= j; + else + WBUFW(buf,22+n*22)= sd->status.cart[index].card[0]; + if(sd->status.cart[index].card[1] > 0 && (j=itemdb_viewid(sd->status.cart[index].card[1])) > 0) + WBUFW(buf,24+n*22)= j; + else + WBUFW(buf,24+n*22)= sd->status.cart[index].card[1]; + if(sd->status.cart[index].card[2] > 0 && (j=itemdb_viewid(sd->status.cart[index].card[2])) > 0) + WBUFW(buf,26+n*22)= j; + else + WBUFW(buf,26+n*22)= sd->status.cart[index].card[2]; + if(sd->status.cart[index].card[3] > 0 && (j=itemdb_viewid(sd->status.cart[index].card[3])) > 0) + WBUFW(buf,28+n*22)= j; + else + WBUFW(buf,28+n*22)= sd->status.cart[index].card[3]; + } + n++; + } + if(n > 0){ + WBUFW(buf,2)=8+n*22; + WFIFOSET(fd,WFIFOW(fd,2)); + } + + return n; +} + +/*========================================== + * 露店アイテム販売報告 + *------------------------------------------ +*/ +int clif_vendingreport(struct map_session_data *sd,int index,int amount) +{ + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd,0)=0x137; + WFIFOW(fd,2)=index+2; + WFIFOW(fd,4)=amount; + WFIFOSET(fd,packet_len_table[0x137]); + + return 0; +} + +/*========================================== + * パーティ作成完了 + *------------------------------------------ + */ +int clif_party_created(struct map_session_data *sd,int flag) +{ + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd,0)=0xfa; + WFIFOB(fd,2)=flag; + WFIFOSET(fd,packet_len_table[0xfa]); + return 0; +} +/*========================================== + * パーティ情報送信 + *------------------------------------------ + */ +int clif_party_info(struct party *p,int fd) +{ + unsigned char buf[1024]; + int i,c; + struct map_session_data *sd=NULL; + + nullpo_retr(0, p); + + WBUFW(buf,0)=0xfb; + memcpy(WBUFP(buf,4),p->name,24); + for(i=c=0;i<MAX_PARTY;i++){ + struct party_member *m=&p->member[i]; + if(m->account_id>0){ + if(sd==NULL) sd=m->sd; + WBUFL(buf,28+c*46)=m->account_id; + memcpy(WBUFP(buf,28+c*46+ 4),m->name,24); + memcpy(WBUFP(buf,28+c*46+28),m->map,16); + WBUFB(buf,28+c*46+44)=(m->leader)?0:1; + WBUFB(buf,28+c*46+45)=(m->online)?0:1; + c++; + } + } + WBUFW(buf,2)=28+c*46; + if(fd>=0){ // fdが設定されてるならそれに送る + memcpy(WFIFOP(fd,0),buf,WBUFW(buf,2)); + WFIFOSET(fd,WFIFOW(fd,2)); + return 9; + } + if(sd!=NULL) + clif_send(buf,WBUFW(buf,2),&sd->bl,PARTY); + return 0; +} +/*========================================== + * パーティ勧誘 + *------------------------------------------ + */ +int clif_party_invite(struct map_session_data *sd,struct map_session_data *tsd) +{ + int fd; + struct party *p; + + nullpo_retr(0, sd); + nullpo_retr(0, tsd); + + fd=tsd->fd; + + if( (p=party_search(sd->status.party_id))==NULL ) + return 0; + + WFIFOW(fd,0)=0xfe; + WFIFOL(fd,2)=sd->status.account_id; + memcpy(WFIFOP(fd,6),p->name,24); + WFIFOSET(fd,packet_len_table[0xfe]); + return 0; +} + +/*========================================== + * パーティ勧誘結果 + *------------------------------------------ + */ +int clif_party_inviteack(struct map_session_data *sd,char *nick,int flag) +{ + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd,0)=0xfd; + memcpy(WFIFOP(fd,2),nick,24); + WFIFOB(fd,26)=flag; + WFIFOSET(fd,packet_len_table[0xfd]); + return 0; +} + +/*========================================== + * パーティ設定送信 + * flag & 0x001=exp変更ミス + * 0x010=item変更ミス + * 0x100=一人にのみ送信 + *------------------------------------------ + */ +int clif_party_option(struct party *p,struct map_session_data *sd,int flag) +{ + unsigned char buf[16]; + + nullpo_retr(0, p); + +// if(battle_config.etc_log) +// printf("clif_party_option: %d %d %d\n",p->exp,p->item,flag); + if(sd==NULL && flag==0){ + int i; + for(i=0;i<MAX_PARTY;i++) + if((sd=map_id2sd(p->member[i].account_id))!=NULL) + break; + } + if(sd==NULL) + return 0; + WBUFW(buf,0)=0x101; + WBUFW(buf,2)=((flag&0x01)?2:p->exp); + WBUFW(buf,4)=((flag&0x10)?2:p->item); + if(flag==0) + clif_send(buf,packet_len_table[0x101],&sd->bl,PARTY); + else { + memcpy(WFIFOP(sd->fd,0),buf,packet_len_table[0x101]); + WFIFOSET(sd->fd,packet_len_table[0x101]); + } + return 0; +} +/*========================================== + * パーティ脱退(脱退前に呼ぶこと) + *------------------------------------------ + */ +int clif_party_leaved(struct party *p,struct map_session_data *sd,int account_id,char *name,int flag) +{ + unsigned char buf[64]; + int i; + + nullpo_retr(0, p); + + WBUFW(buf,0)=0x105; + WBUFL(buf,2)=account_id; + memcpy(WBUFP(buf,6),name,24); + WBUFB(buf,30)=flag&0x0f; + + if((flag&0xf0)==0){ + if(sd==NULL) + for(i=0;i<MAX_PARTY;i++) + if((sd=p->member[i].sd)!=NULL) + break; + if (sd!=NULL) + clif_send(buf,packet_len_table[0x105],&sd->bl,PARTY); + } else if (sd!=NULL) { + memcpy(WFIFOP(sd->fd,0),buf,packet_len_table[0x105]); + WFIFOSET(sd->fd,packet_len_table[0x105]); + } + return 0; +} +/*========================================== + * パーティメッセージ送信 + *------------------------------------------ + */ +int clif_party_message(struct party *p,int account_id,char *mes,int len) +{ + struct map_session_data *sd; + int i; + + nullpo_retr(0, p); + + for(i=0;i<MAX_PARTY;i++){ + if((sd=p->member[i].sd)!=NULL) + break; + } + if(sd!=NULL){ + unsigned char buf[1024]; + WBUFW(buf,0)=0x109; + WBUFW(buf,2)=len+8; + WBUFL(buf,4)=account_id; + memcpy(WBUFP(buf,8),mes,len); + clif_send(buf,len+8,&sd->bl,PARTY); + } + return 0; +} +/*========================================== + * パーティ座標通知 + *------------------------------------------ + */ +int clif_party_xy(struct party *p,struct map_session_data *sd) +{ + unsigned char buf[16]; + + nullpo_retr(0, sd); + + WBUFW(buf,0)=0x107; + WBUFL(buf,2)=sd->status.account_id; + WBUFW(buf,6)=sd->bl.x; + WBUFW(buf,8)=sd->bl.y; + clif_send(buf,packet_len_table[0x107],&sd->bl,PARTY_SAMEMAP_WOS); +// if(battle_config.etc_log) +// printf("clif_party_xy %d\n",sd->status.account_id); + return 0; +} +/*========================================== + * パーティHP通知 + *------------------------------------------ + */ +int clif_party_hp(struct party *p,struct map_session_data *sd) +{ + unsigned char buf[16]; + + nullpo_retr(0, sd); + + WBUFW(buf,0)=0x106; + WBUFL(buf,2)=sd->status.account_id; + WBUFW(buf,6)=(sd->status.hp > 0x7fff)? 0x7fff:sd->status.hp; + WBUFW(buf,8)=(sd->status.max_hp > 0x7fff)? 0x7fff:sd->status.max_hp; + clif_send(buf,packet_len_table[0x106],&sd->bl,PARTY_AREA_WOS); +// if(battle_config.etc_log) +// printf("clif_party_hp %d\n",sd->status.account_id); + return 0; +} +/*========================================== + * パーティ場所移動(未使用) + *------------------------------------------ + */ +int clif_party_move(struct party *p,struct map_session_data *sd,int online) +{ + unsigned char buf[128]; + + nullpo_retr(0, sd); + nullpo_retr(0, p); + + WBUFW(buf, 0)=0x104; + WBUFL(buf, 2)=sd->status.account_id; + WBUFL(buf, 6)=0; + WBUFW(buf,10)=sd->bl.x; + WBUFW(buf,12)=sd->bl.y; + WBUFB(buf,14)=!online; + memcpy(WBUFP(buf,15),p->name,24); + memcpy(WBUFP(buf,39),sd->status.name,24); + memcpy(WBUFP(buf,63),map[sd->bl.m].name,16); + clif_send(buf,packet_len_table[0x104],&sd->bl,PARTY); + return 0; +} +/*========================================== + * 攻撃するために移動が必要 + *------------------------------------------ + */ +int clif_movetoattack(struct map_session_data *sd,struct block_list *bl) +{ + int fd; + + nullpo_retr(0, sd); + nullpo_retr(0, bl); + + fd=sd->fd; + WFIFOW(fd, 0)=0x139; + WFIFOL(fd, 2)=bl->id; + WFIFOW(fd, 6)=bl->x; + WFIFOW(fd, 8)=bl->y; + WFIFOW(fd,10)=sd->bl.x; + WFIFOW(fd,12)=sd->bl.y; + WFIFOW(fd,14)=sd->attackrange; + WFIFOSET(fd,packet_len_table[0x139]); + return 0; +} +/*========================================== + * 製造エフェクト + *------------------------------------------ + */ +int clif_produceeffect(struct map_session_data *sd,int flag,int nameid) +{ + int view,fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + // 名前の登録と送信を先にしておく + if( map_charid2nick(sd->status.char_id)==NULL ) + map_addchariddb(sd->status.char_id,sd->status.name); + clif_solved_charname(sd,sd->status.char_id); + + WFIFOW(fd, 0)=0x18f; + WFIFOW(fd, 2)=flag; + if((view = itemdb_viewid(nameid)) > 0) + WFIFOW(fd, 4)=view; + else + WFIFOW(fd, 4)=nameid; + WFIFOSET(fd,packet_len_table[0x18f]); + return 0; +} + +// pet +int clif_catch_process(struct map_session_data *sd) +{ + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd,0)=0x19e; + WFIFOSET(fd,packet_len_table[0x19e]); + + return 0; +} + +int clif_pet_rulet(struct map_session_data *sd,int data) +{ + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd,0)=0x1a0; + WFIFOB(fd,2)=data; + WFIFOSET(fd,packet_len_table[0x1a0]); + + return 0; +} + +/*========================================== + * pet卵リスト作成 + *------------------------------------------ + */ +int clif_sendegg(struct map_session_data *sd) +{ + //R 01a6 <len>.w <index>.w* + int i,n=0,fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd,0)=0x1a6; + if(sd->status.pet_id <= 0) { + for(i=0,n=0;i<MAX_INVENTORY;i++){ + if(sd->status.inventory[i].nameid<=0 || sd->inventory_data[i] == NULL || + sd->inventory_data[i]->type!=7 || + sd->status.inventory[i].amount<=0) + continue; + WFIFOW(fd,n*2+4)=i+2; + n++; + } + } + WFIFOW(fd,2)=4+n*2; + WFIFOSET(fd,WFIFOW(fd,2)); + + return 0; +} + +int clif_send_petdata(struct map_session_data *sd,int type,int param) +{ + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd,0)=0x1a4; + WFIFOB(fd,2)=type; + WFIFOL(fd,3)=sd->pd->bl.id; + WFIFOL(fd,7)=param; + WFIFOSET(fd,packet_len_table[0x1a4]); + + return 0; +} + +int clif_send_petstatus(struct map_session_data *sd) +{ + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd,0)=0x1a2; + memcpy(WFIFOP(fd,2),sd->pet.name,24); + WFIFOB(fd,26)=(battle_config.pet_rename == 1)? 0:sd->pet.rename_flag; + WFIFOW(fd,27)=sd->pet.level; + WFIFOW(fd,29)=sd->pet.hungry; + WFIFOW(fd,31)=sd->pet.intimate; + WFIFOW(fd,33)=sd->pet.equip; + WFIFOSET(fd,packet_len_table[0x1a2]); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_pet_emotion(struct pet_data *pd,int param) +{ + unsigned char buf[16]; + struct map_session_data *sd; + + nullpo_retr(0, pd); + nullpo_retr(0, sd = pd->msd); + + memset(buf,0,packet_len_table[0x1aa]); + + WBUFW(buf,0)=0x1aa; + WBUFL(buf,2)=pd->bl.id; + if(param >= 100 && sd->petDB->talk_convert_class) { + if(sd->petDB->talk_convert_class < 0) + return 0; + else if(sd->petDB->talk_convert_class > 0) { + param -= (pd->class - 100)*100; + param += (sd->petDB->talk_convert_class - 100)*100; + } + } + WBUFL(buf,6)=param; + + clif_send(buf,packet_len_table[0x1aa],&pd->bl,AREA); + + return 0; +} + +int clif_pet_performance(struct block_list *bl,int param) +{ + unsigned char buf[16]; + + nullpo_retr(0, bl); + + memset(buf,0,packet_len_table[0x1a4]); + + WBUFW(buf,0)=0x1a4; + WBUFB(buf,2)=4; + WBUFL(buf,3)=bl->id; + WBUFL(buf,7)=param; + + clif_send(buf,packet_len_table[0x1a4],bl,AREA); + + return 0; +} + +int clif_pet_equip(struct pet_data *pd,int nameid) +{ + unsigned char buf[16]; + int view; + + nullpo_retr(0, pd); + + memset(buf,0,packet_len_table[0x1a4]); + + WBUFW(buf,0)=0x1a4; + WBUFB(buf,2)=3; + WBUFL(buf,3)=pd->bl.id; + if((view = itemdb_viewid(nameid)) > 0) + WBUFL(buf,7)=view; + else + WBUFL(buf,7)=nameid; + + clif_send(buf,packet_len_table[0x1a4],&pd->bl,AREA); + + return 0; +} + +int clif_pet_food(struct map_session_data *sd,int foodid,int fail) +{ + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd,0)=0x1a3; + WFIFOB(fd,2)=fail; + WFIFOW(fd,3)=foodid; + WFIFOSET(fd,packet_len_table[0x1a3]); + + return 0; +} + +/*========================================== + * オートスペル リスト送信 + *------------------------------------------ + */ +int clif_autospell(struct map_session_data *sd,int skilllv) +{ + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd, 0)=0x1cd; + + if(skilllv>0 && pc_checkskill(sd,MG_NAPALMBEAT)>0) + WFIFOL(fd,2)= MG_NAPALMBEAT; + else + WFIFOL(fd,2)= 0x00000000; + if(skilllv>1 && pc_checkskill(sd,MG_COLDBOLT)>0) + WFIFOL(fd,6)= MG_COLDBOLT; + else + WFIFOL(fd,6)= 0x00000000; + if(skilllv>1 && pc_checkskill(sd,MG_FIREBOLT)>0) + WFIFOL(fd,10)= MG_FIREBOLT; + else + WFIFOL(fd,10)= 0x00000000; + if(skilllv>1 && pc_checkskill(sd,MG_LIGHTNINGBOLT)>0) + WFIFOL(fd,14)= MG_LIGHTNINGBOLT; + else + WFIFOL(fd,14)= 0x00000000; + if(skilllv>4 && pc_checkskill(sd,MG_SOULSTRIKE)>0) + WFIFOL(fd,18)= MG_SOULSTRIKE; + else + WFIFOL(fd,18)= 0x00000000; + if(skilllv>7 && pc_checkskill(sd,MG_FIREBALL)>0) + WFIFOL(fd,22)= MG_FIREBALL; + else + WFIFOL(fd,22)= 0x00000000; + if(skilllv>9 && pc_checkskill(sd,MG_FROSTDIVER)>0) + WFIFOL(fd,26)= MG_FROSTDIVER; + else + WFIFOL(fd,26)= 0x00000000; + + WFIFOSET(fd,packet_len_table[0x1cd]); + return 0; +} + +/*========================================== + * ディボーションの青い糸 + *------------------------------------------ + */ +int clif_devotion(struct map_session_data *sd,int target) +{ + unsigned char buf[56]; + int n; + + nullpo_retr(0, sd); + + WBUFW(buf,0)=0x1cf; + WBUFL(buf,2)=sd->bl.id; +// WBUFL(buf,6)=target; + for(n=0;n<5;n++) + WBUFL(buf,6+4*n)=sd->dev.val2[n]; +// WBUFL(buf,10+4*n)=0; + WBUFB(buf,26)=8; + WBUFB(buf,27)=0; + + clif_send(buf,packet_len_table[0x1cf],&sd->bl,AREA); + return 0; +} + +/*========================================== + * 氣球 + *------------------------------------------ + */ +int clif_spiritball(struct map_session_data *sd) +{ + unsigned char buf[16]; + + nullpo_retr(0, sd); + + WBUFW(buf,0)=0x1d0; + WBUFL(buf,2)=sd->bl.id; + WBUFW(buf,6)=sd->spiritball; + clif_send(buf,packet_len_table[0x1d0],&sd->bl,AREA); + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_combo_delay(struct block_list *bl,int wait) +{ + unsigned char buf[32]; + + nullpo_retr(0, bl); + + WBUFW(buf,0)=0x1d2; + WBUFL(buf,2)=bl->id; + WBUFL(buf,6)=wait; + clif_send(buf,packet_len_table[0x1d2],bl,AREA); + + return 0; +} +/*========================================== + *白刃取り + *------------------------------------------ + */ +int clif_bladestop(struct block_list *src,struct block_list *dst, + int bool) +{ + unsigned char buf[32]; + + nullpo_retr(0, src); + nullpo_retr(0, dst); + + WBUFW(buf,0)=0x1d1; + WBUFL(buf,2)=src->id; + WBUFL(buf,6)=dst->id; + WBUFL(buf,10)=bool; + + clif_send(buf,packet_len_table[0x1d1],src,AREA); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_changemapcell(int m,int x,int y,int cell_type,int type) +{ + struct block_list bl; + char buf[32]; + + bl.m = m; + bl.x = x; + bl.y = y; + WBUFW(buf,0) = 0x192; + WBUFW(buf,2) = x; + WBUFW(buf,4) = y; + WBUFW(buf,6) = cell_type; + memcpy(WBUFP(buf,8),map[m].name,16); + if(!type) + clif_send(buf,packet_len_table[0x192],&bl,AREA); + else + clif_send(buf,packet_len_table[0x192],&bl,ALL_SAMEMAP); + + return 0; +} + +/*========================================== + * MVPエフェクト + *------------------------------------------ + */ +int clif_mvp_effect(struct map_session_data *sd) +{ + unsigned char buf[16]; + + nullpo_retr(0, sd); + + WBUFW(buf,0)=0x10c; + WBUFL(buf,2)=sd->bl.id; + clif_send(buf,packet_len_table[0x10c],&sd->bl,AREA); + return 0; +} +/*========================================== + * MVPアイテム所得 + *------------------------------------------ + */ +int clif_mvp_item(struct map_session_data *sd,int nameid) +{ + int view,fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd,0)=0x10a; + if((view = itemdb_viewid(nameid)) > 0) + WFIFOW(fd,2)=view; + else + WFIFOW(fd,2)=nameid; + WFIFOSET(fd,packet_len_table[0x10a]); + return 0; +} +/*========================================== + * MVP経験値所得 + *------------------------------------------ + */ +int clif_mvp_exp(struct map_session_data *sd,int exp) +{ + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd,0)=0x10b; + WFIFOL(fd,2)=exp; + WFIFOSET(fd,packet_len_table[0x10b]); + return 0; +} + +/*========================================== + * ギルド作成可否通知 + *------------------------------------------ + */ +int clif_guild_created(struct map_session_data *sd,int flag) +{ + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd,0)=0x167; + WFIFOB(fd,2)=flag; + WFIFOSET(fd,packet_len_table[0x167]); + return 0; +} +/*========================================== + * ギルド所属通知 + *------------------------------------------ + */ +int clif_guild_belonginfo(struct map_session_data *sd,struct guild *g) +{ + int ps,fd; + + nullpo_retr(0, sd); + nullpo_retr(0, g); + + fd=sd->fd; + ps=guild_getposition(sd,g); + + memset(WFIFOP(fd,0),0,packet_len_table[0x16c]); + WFIFOW(fd,0)=0x16c; + WFIFOL(fd,2)=g->guild_id; + WFIFOL(fd,6)=g->emblem_id; + WFIFOL(fd,10)=g->position[ps].mode; + memcpy(WFIFOP(fd,19),g->name,24); + WFIFOSET(fd,packet_len_table[0x16c]); + return 0; +} +/*========================================== + * ギルドメンバログイン通知 + *------------------------------------------ + */ +int clif_guild_memberlogin_notice(struct guild *g,int idx,int flag) +{ + unsigned char buf[64]; + + nullpo_retr(0, g); + + WBUFW(buf, 0)=0x16d; + WBUFL(buf, 2)=g->member[idx].account_id; + WBUFL(buf, 6)=g->member[idx].char_id; + WBUFL(buf,10)=flag; + if(g->member[idx].sd==NULL){ + struct map_session_data *sd=guild_getavailablesd(g); + if(sd!=NULL) + clif_send(buf,packet_len_table[0x16d],&sd->bl,GUILD); + }else + clif_send(buf,packet_len_table[0x16d],&g->member[idx].sd->bl,GUILD_WOS); + return 0; +} +/*========================================== + * ギルドマスター通知(14dへの応答) + *------------------------------------------ + */ +int clif_guild_masterormember(struct map_session_data *sd) +{ + int type=0x57,fd; + struct guild *g; + + nullpo_retr(0, sd); + + fd=sd->fd; + g=guild_search(sd->status.guild_id); + if(g!=NULL && strcmp(g->master,sd->status.name)==0) + type=0xd7; + WFIFOW(fd,0)=0x14e; + WFIFOL(fd,2)=type; + WFIFOSET(fd,packet_len_table[0x14e]); + return 0; +} +/*========================================== + * Basic Info (Territories [Valaris]) + *------------------------------------------ + */ +int clif_guild_basicinfo(struct map_session_data *sd) +{ + int fd,i,t=0; + struct guild *g; + struct guild_castle *gc=NULL; + + nullpo_retr(0, sd); + + fd=sd->fd; + g=guild_search(sd->status.guild_id); + if(g==NULL) + return 0; + + WFIFOW(fd, 0)=0x1b6;//0x150; + WFIFOL(fd, 2)=g->guild_id; + WFIFOL(fd, 6)=g->guild_lv; + WFIFOL(fd,10)=g->connect_member; + WFIFOL(fd,14)=g->max_member; + WFIFOL(fd,18)=g->average_lv; + WFIFOL(fd,22)=g->exp; + WFIFOL(fd,26)=g->next_exp; + WFIFOL(fd,30)=0; // 上納 + WFIFOL(fd,34)=0; // VW(性格の悪さ?:性向グラフ左右) + WFIFOL(fd,38)=0; // RF(正義の度合い?:性向グラフ上下) + WFIFOL(fd,42)=0; // 人数? + memcpy(WFIFOP(fd,46),g->name,24); + memcpy(WFIFOP(fd,70),g->master,24); + + for(i=0;i<MAX_GUILDCASTLE;i++){ + gc=guild_castle_search(i); + if(!gc) continue; + if(g->guild_id == gc->guild_id) t++; + } + + if (t==1) memcpy(WFIFOP(fd,94),"One Castle",20); + else if (t==2) memcpy(WFIFOP(fd,94),"Two Castles",20); + else if (t==3) memcpy(WFIFOP(fd,94),"Three Castles",20); + else if (t==4) memcpy(WFIFOP(fd,94),"Four Castles",20); + else if (t==5) memcpy(WFIFOP(fd,94),"Five Castles",20); + else if (t==6) memcpy(WFIFOP(fd,94),"Six Castles",20); + else if (t==7) memcpy(WFIFOP(fd,94),"Seven Castles",20); + else if (t==8) memcpy(WFIFOP(fd,94),"Eight Castles",20); + else if (t==9) memcpy(WFIFOP(fd,94),"Nine Castles",20); + else if (t==10) memcpy(WFIFOP(fd,94),"Ten Castles",20); + else if (t==11) memcpy(WFIFOP(fd,94),"Eleven Castles",20); + else if (t==12) memcpy(WFIFOP(fd,94),"Twelve Castles",20); + else if (t==13) memcpy(WFIFOP(fd,94),"Thirteen Castles",20); + else if (t==14) memcpy(WFIFOP(fd,94),"Fourteen Castles",20); + else if (t==15) memcpy(WFIFOP(fd,94),"Fifteen Castles",20); + else if (t==16) memcpy(WFIFOP(fd,94),"Sixteen Castles",20); + else if (t==17) memcpy(WFIFOP(fd,94),"Seventeen Castles",20); + else if (t==18) memcpy(WFIFOP(fd,94),"Eighteen Castles",20); + else if (t==19) memcpy(WFIFOP(fd,94),"Nineteen Castles",20); + else if (t==20) memcpy(WFIFOP(fd,94),"Twenty Castles",20); + else if (t==21) memcpy(WFIFOP(fd,94),"Twenty One Castles",20); + else if (t==22) memcpy(WFIFOP(fd,94),"Twenty Two Castles",20); + else if (t==23) memcpy(WFIFOP(fd,94),"Twenty Three Castles",20); + else if (t==24) memcpy(WFIFOP(fd,94),"Twenty Four Castles",20); + else if (t==MAX_GUILDCASTLE) memcpy(WFIFOP(fd,94),"Total Domination",20); + else memcpy(WFIFOP(fd,94),"None Taken",20); + + WFIFOSET(fd,packet_len_table[WFIFOW(fd,0)]); + clif_guild_emblem(sd,g); // Guild emblem vanish fix [Valaris] + return 0; +} + +/*========================================== + * ギルド同盟/敵対情報 + *------------------------------------------ + */ +int clif_guild_allianceinfo(struct map_session_data *sd) +{ + int fd,i,c; + struct guild *g; + + nullpo_retr(0, sd); + + fd=sd->fd; + g=guild_search(sd->status.guild_id); + if(g==NULL) + return 0; + WFIFOW(fd, 0)=0x14c; + for(i=c=0;i<MAX_GUILDALLIANCE;i++){ + struct guild_alliance *a=&g->alliance[i]; + if(a->guild_id>0){ + WFIFOL(fd,c*32+4)=a->opposition; + WFIFOL(fd,c*32+8)=a->guild_id; + memcpy(WFIFOP(fd,c*32+12),a->name,24); + c++; + } + } + WFIFOW(fd, 2)=c*32+4; + WFIFOSET(fd,WFIFOW(fd,2)); + return 0; +} + +/*========================================== + * ギルドメンバーリスト + *------------------------------------------ + */ +int clif_guild_memberlist(struct map_session_data *sd) +{ + int fd; + int i,c; + struct guild *g; + + nullpo_retr(0, sd); + + fd=sd->fd; + g=guild_search(sd->status.guild_id); + if(g==NULL) + return 0; + + WFIFOW(fd, 0)=0x154; + for(i=0,c=0;i<g->max_member;i++){ + struct guild_member *m=&g->member[i]; + if(m->account_id==0) + continue; + WFIFOL(fd,c*104+ 4)=m->account_id; + WFIFOL(fd,c*104+ 8)=m->char_id; + WFIFOW(fd,c*104+12)=m->hair; + WFIFOW(fd,c*104+14)=m->hair_color; + WFIFOW(fd,c*104+16)=m->gender; + WFIFOW(fd,c*104+18)=m->class; + WFIFOW(fd,c*104+20)=m->lv; + WFIFOL(fd,c*104+22)=m->exp; + WFIFOL(fd,c*104+26)=m->online; + WFIFOL(fd,c*104+30)=m->position; + memset(WFIFOP(fd,c*104+34),0,50); // メモ? + memcpy(WFIFOP(fd,c*104+84),m->name,24); + c++; + } + WFIFOW(fd, 2)=c*104+4; + WFIFOSET(fd,WFIFOW(fd,2)); + return 0; +} +/*========================================== + * ギルド役職名リスト + *------------------------------------------ + */ +int clif_guild_positionnamelist(struct map_session_data *sd) +{ + int i,fd; + struct guild *g; + + nullpo_retr(0, sd); + + fd=sd->fd; + g=guild_search(sd->status.guild_id); + if(g==NULL) + return 0; + WFIFOW(fd, 0)=0x166; + for(i=0;i<MAX_GUILDPOSITION;i++){ + WFIFOL(fd,i*28+4)=i; + memcpy(WFIFOP(fd,i*28+8),g->position[i].name,24); + } + WFIFOW(fd,2)=i*28+4; + WFIFOSET(fd,WFIFOW(fd,2)); + return 0; +} +/*========================================== + * ギルド役職情報リスト + *------------------------------------------ + */ +int clif_guild_positioninfolist(struct map_session_data *sd) +{ + int i,fd; + struct guild *g; + + nullpo_retr(0, sd); + + fd=sd->fd; + g=guild_search(sd->status.guild_id); + if(g==NULL) + return 0; + WFIFOW(fd, 0)=0x160; + for(i=0;i<MAX_GUILDPOSITION;i++){ + struct guild_position *p=&g->position[i]; + WFIFOL(fd,i*16+ 4)=i; + WFIFOL(fd,i*16+ 8)=p->mode; + WFIFOL(fd,i*16+12)=i; + WFIFOL(fd,i*16+16)=p->exp_mode; + } + WFIFOW(fd, 2)=i*16+4; + WFIFOSET(fd,WFIFOW(fd,2)); + return 0; +} +/*========================================== + * ギルド役職変更通知 + *------------------------------------------ + */ +int clif_guild_positionchanged(struct guild *g,int idx) +{ + struct map_session_data *sd; + unsigned char buf[128]; + + nullpo_retr(0, g); + + WBUFW(buf, 0)=0x174; + WBUFW(buf, 2)=44; + WBUFL(buf, 4)=idx; + WBUFL(buf, 8)=g->position[idx].mode; + WBUFL(buf,12)=idx; + WBUFL(buf,16)=g->position[idx].exp_mode; + memcpy(WBUFP(buf,20),g->position[idx].name,24); + if( (sd=guild_getavailablesd(g))!=NULL ) + clif_send(buf,WBUFW(buf,2),&sd->bl,GUILD); + return 0; +} +/*========================================== + * ギルドメンバ変更通知 + *------------------------------------------ + */ +int clif_guild_memberpositionchanged(struct guild *g,int idx) +{ + struct map_session_data *sd; + unsigned char buf[64]; + + nullpo_retr(0, g); + + WBUFW(buf, 0)=0x156; + WBUFW(buf, 2)=16; + WBUFL(buf, 4)=g->member[idx].account_id; + WBUFL(buf, 8)=g->member[idx].char_id; + WBUFL(buf,12)=g->member[idx].position; + if( (sd=guild_getavailablesd(g))!=NULL ) + clif_send(buf,WBUFW(buf,2),&sd->bl,GUILD); + return 0; +} +/*========================================== + * ギルドエンブレム送信 + *------------------------------------------ + */ +int clif_guild_emblem(struct map_session_data *sd,struct guild *g) +{ + int fd; + + nullpo_retr(0, sd); + nullpo_retr(0, g); + + fd=sd->fd; + + if(g->emblem_len<=0) + return 0; + WFIFOW(fd,0)=0x152; + WFIFOW(fd,2)=g->emblem_len+12; + WFIFOL(fd,4)=g->guild_id; + WFIFOL(fd,8)=g->emblem_id; + memcpy(WFIFOP(fd,12),g->emblem_data,g->emblem_len); + WFIFOSET(fd,WFIFOW(fd,2)); + return 0; +} +/*========================================== + * ギルドスキル送信 + *------------------------------------------ + */ +int clif_guild_skillinfo(struct map_session_data *sd) +{ + int fd; + int i,id,c; + struct guild *g; + + nullpo_retr(0, sd); + + fd=sd->fd; + g=guild_search(sd->status.guild_id); + if(g==NULL) + return 0; + WFIFOW(fd,0)=0x0162; + WFIFOW(fd,4)=g->skill_point; + for(i=c=0;i<MAX_GUILDSKILL;i++){ + if(g->skill[i].id>0){ + WFIFOW(fd,c*37+ 6) = id = g->skill[i].id; + WFIFOW(fd,c*37+ 8) = guild_skill_get_inf(id); + WFIFOW(fd,c*37+10) = 0; + WFIFOW(fd,c*37+12) = g->skill[i].lv; + WFIFOW(fd,c*37+14) = guild_skill_get_sp(id,g->skill[i].lv); + WFIFOW(fd,c*37+16) = guild_skill_get_range(id); + memset(WFIFOP(fd,c*37+18),0,24); + WFIFOB(fd,c*37+42)= //up; + (g->skill[i].lv < guild_skill_get_max(id))? 1:0; + c++; + } + } + WFIFOW(fd,2)=c*37+6; + WFIFOSET(fd,WFIFOW(fd,2)); + return 0; +} +/*========================================== + * ギルド告知送信 + *------------------------------------------ + */ +int clif_guild_notice(struct map_session_data *sd,struct guild *g) +{ + int fd; + + nullpo_retr(0, sd); + nullpo_retr(0, g); + + fd=sd->fd; + if(*g->mes1==0 && *g->mes2==0) + return 0; + WFIFOW(fd,0)=0x16f; + memcpy(WFIFOP(fd,2),g->mes1,60); + memcpy(WFIFOP(fd,62),g->mes2,120); + WFIFOSET(fd,packet_len_table[0x16f]); + return 0; +} + +/*========================================== + * ギルドメンバ勧誘 + *------------------------------------------ + */ +int clif_guild_invite(struct map_session_data *sd,struct guild *g) +{ + int fd; + + nullpo_retr(0, sd); + nullpo_retr(0, g); + + fd=sd->fd; + WFIFOW(fd,0)=0x16a; + WFIFOL(fd,2)=g->guild_id; + memcpy(WFIFOP(fd,6),g->name,24); + WFIFOSET(fd,packet_len_table[0x16a]); + return 0; +} +/*========================================== + * ギルドメンバ勧誘結果 + *------------------------------------------ + */ +int clif_guild_inviteack(struct map_session_data *sd,int flag) +{ + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd,0)=0x169; + WFIFOB(fd,2)=flag; + WFIFOSET(fd,packet_len_table[0x169]); + return 0; +} +/*========================================== + * ギルドメンバ脱退通知 + *------------------------------------------ + */ +int clif_guild_leave(struct map_session_data *sd,const char *name,const char *mes) +{ + unsigned char buf[128]; + + nullpo_retr(0, sd); + + WBUFW(buf, 0)=0x15a; + memcpy(WBUFP(buf, 2),name,24); + memcpy(WBUFP(buf,26),mes,40); + clif_send(buf,packet_len_table[0x15a],&sd->bl,GUILD); + return 0; +} +/*========================================== + * ギルドメンバ追放通知 + *------------------------------------------ + */ +int clif_guild_explusion(struct map_session_data *sd,const char *name,const char *mes, + int account_id) +{ + unsigned char buf[128]; + + nullpo_retr(0, sd); + + WBUFW(buf, 0)=0x15c; + memcpy(WBUFP(buf, 2),name,24); + memcpy(WBUFP(buf,26),mes,40); + memcpy(WBUFP(buf,66),"dummy",24); + clif_send(buf,packet_len_table[0x15c],&sd->bl,GUILD); + return 0; +} +/*========================================== + * ギルド追放メンバリスト + *------------------------------------------ + */ +int clif_guild_explusionlist(struct map_session_data *sd) +{ + int fd; + int i,c; + struct guild *g; + + nullpo_retr(0, sd); + + fd=sd->fd; + g=guild_search(sd->status.guild_id); + if(g==NULL) + return 0; + WFIFOW(fd,0)=0x163; + for(i=c=0;i<MAX_GUILDEXPLUSION;i++){ + struct guild_explusion *e=&g->explusion[i]; + if(e->account_id>0){ + memcpy(WFIFOP(fd,c*88+ 4),e->name,24); + memcpy(WFIFOP(fd,c*88+28),e->acc,24); + memcpy(WFIFOP(fd,c*88+52),e->mes,44); + c++; + } + } + WFIFOW(fd,2)=c*88+4; + WFIFOSET(fd,WFIFOW(fd,2)); + return 0; +} + +/*========================================== + * ギルド会話 + *------------------------------------------ + */ +int clif_guild_message(struct guild *g,int account_id,const char *mes,int len) +{ + struct map_session_data *sd; + unsigned char lbuf[255]; + unsigned char *buf = lbuf; + if (len + 32 >= sizeof(lbuf)) + buf = malloc(len + 32); + WBUFW(buf, 0)=0x17f; + WBUFW(buf, 2)=len+4; + memcpy(WBUFP(buf,4),mes,len); + + if( (sd=guild_getavailablesd(g))!=NULL ) + clif_send(buf,WBUFW(buf,2),&sd->bl,GUILD); + if ( buf != lbuf) + free(buf); + return 0; +} +/*========================================== + * ギルドスキル割り振り通知 + *------------------------------------------ + */ +int clif_guild_skillup(struct map_session_data *sd,int skill_num,int lv) +{ + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd,0) = 0x10e; + WFIFOW(fd,2) = skill_num; + WFIFOW(fd,4) = lv; + WFIFOW(fd,6) = guild_skill_get_sp(skill_num,lv); + WFIFOW(fd,8) = guild_skill_get_range(skill_num); + WFIFOB(fd,10) = 1; + WFIFOSET(fd,11); + return 0; +} +/*========================================== + * ギルド同盟要請 + *------------------------------------------ + */ +int clif_guild_reqalliance(struct map_session_data *sd,int account_id,const char *name) +{ + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd,0)=0x171; + WFIFOL(fd,2)=account_id; + memcpy(WFIFOP(fd,6),name,24); + WFIFOSET(fd,packet_len_table[0x171]); + return 0; +} +/*========================================== + * ギルド同盟結果 + *------------------------------------------ + */ +int clif_guild_allianceack(struct map_session_data *sd,int flag) +{ + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd,0)=0x173; + WFIFOL(fd,2)=flag; + WFIFOSET(fd,packet_len_table[0x173]); + return 0; +} +/*========================================== + * ギルド関係解消通知 + *------------------------------------------ + */ +int clif_guild_delalliance(struct map_session_data *sd,int guild_id,int flag) +{ + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd,0)=0x184; + WFIFOL(fd,2)=guild_id; + WFIFOL(fd,6)=flag; + WFIFOSET(fd,packet_len_table[0x184]); + return 0; +} +/*========================================== + * ギルド敵対結果 + *------------------------------------------ + */ +int clif_guild_oppositionack(struct map_session_data *sd,int flag) +{ + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd,0)=0x181; + WFIFOB(fd,2)=flag; + WFIFOSET(fd,packet_len_table[0x181]); + return 0; +} +/*========================================== + * ギルド関係追加 + *------------------------------------------ + */ +/*int clif_guild_allianceadded(struct guild *g,int idx) +{ + unsigned char buf[64]; + WBUFW(fd,0)=0x185; + WBUFL(fd,2)=g->alliance[idx].opposition; + WBUFL(fd,6)=g->alliance[idx].guild_id; + memcpy(WBUFP(fd,10),g->alliance[idx].name,24); + clif_send(buf,packet_len_table[0x185],guild_getavailablesd(g),GUILD); + return 0; +}*/ + +/*========================================== + * ギルド解散通知 + *------------------------------------------ + */ +int clif_guild_broken(struct map_session_data *sd,int flag) +{ + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd,0)=0x15e; + WFIFOL(fd,2)=flag; + WFIFOSET(fd,packet_len_table[0x15e]); + return 0; +} + +/*========================================== + * エモーション + *------------------------------------------ + */ +void clif_emotion(struct block_list *bl,int type) +{ + unsigned char buf[8]; + + nullpo_retv(bl); + + WBUFW(buf,0)=0xc0; + WBUFL(buf,2)=bl->id; + WBUFB(buf,6)=type; + clif_send(buf,packet_len_table[0xc0],bl,AREA); +} + +/*========================================== + * トーキーボックス + *------------------------------------------ + */ +void clif_talkiebox(struct block_list *bl,char* talkie) +{ + unsigned char buf[86]; + + nullpo_retv(bl); + + WBUFW(buf,0)=0x191; + WBUFL(buf,2)=bl->id; + memcpy(WBUFP(buf,6),talkie,80); + clif_send(buf,packet_len_table[0x191],bl,AREA); +} + +/*========================================== + * 結婚エフェクト + *------------------------------------------ + */ +void clif_wedding_effect(struct block_list *bl) { + unsigned char buf[6]; + + nullpo_retv(bl); + + WBUFW(buf,0) = 0x1ea; + WBUFL(buf,2) = bl->id; + clif_send(buf, packet_len_table[0x1ea], bl, AREA); +} +/*========================================== + * あなたに逢いたい使用時名前叫び + *------------------------------------------ + +void clif_callpartner(struct map_session_data *sd) +{ + unsigned char buf[26]; + char *p; + + nullpo_retv(sd); + + if(sd->status.partner_id){ + WBUFW(buf,0)=0x1e6; + p = map_charid2nick(sd->status.partner_id); + if(p){ + memcpy(WBUFP(buf,2),p,24); + }else{ + map_reqchariddb(sd,sd->status.partner_id); + chrif_searchcharid(sd->status.partner_id); + WBUFB(buf,2) = 0; + } + clif_send(buf,packet_len_table[0x1e6]&sd->bl,AREA); + } + return; +} +*/ +/*========================================== + * 座る + *------------------------------------------ + */ +void clif_sitting(int fd, struct map_session_data *sd) +{ + unsigned char buf[64]; + + nullpo_retv(sd); + + WBUFW(buf, 0) = 0x8a; + WBUFL(buf, 2) = sd->bl.id; + WBUFB(buf,26) = 2; + clif_send(buf, packet_len_table[0x8a], &sd->bl, AREA); +} + +/*========================================== + * + *------------------------------------------ + */ +int clif_disp_onlyself(struct map_session_data *sd, char *mes, int len) +{ + unsigned char lbuf[255]; + unsigned char *buf = (len + 32 >= sizeof(lbuf)) ? malloc(len + 32) : lbuf; + + nullpo_retr(0, sd); + + WBUFW(buf, 0) = 0x17f; + WBUFW(buf, 2) = len + 8; + memcpy(WBUFP(buf,4), mes, len + 4); + + clif_send(buf, WBUFW(buf,2), &sd->bl, SELF); + + if (buf != lbuf) + free(buf); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ + +int clif_GM_kickack(struct map_session_data *sd, int id) +{ + int fd; + + nullpo_retr(0, sd); + + fd = sd->fd; + WFIFOW(fd,0) = 0xcd; + WFIFOL(fd,2) = id; + WFIFOSET(fd, packet_len_table[0xcd]); + return 0; +} + +void clif_parse_QuitGame(int fd,struct map_session_data *sd); + +int clif_GM_kick(struct map_session_data *sd,struct map_session_data *tsd,int type) +{ + nullpo_retr(0, tsd); + + if(type) + clif_GM_kickack(sd,tsd->status.account_id); + tsd->opt1 = tsd->opt2 = 0; + clif_parse_QuitGame(tsd->fd,tsd); + + return 0; +} +/*========================================== + * Wis拒否許可応答 + *------------------------------------------ + */ +int clif_wisexin(struct map_session_data *sd,int type,int flag) +{ + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd,0)=0xd1; + WFIFOB(fd,2)=type; + WFIFOB(fd,3)=flag; + WFIFOSET(fd,packet_len_table[0xd1]); + + return 0; +} +/*========================================== + * Wis全拒否許可応答 + *------------------------------------------ + */ +int clif_wisall(struct map_session_data *sd,int type,int flag) +{ + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; + WFIFOW(fd,0)=0xd2; + WFIFOB(fd,2)=type; + WFIFOB(fd,3)=flag; + WFIFOSET(fd,packet_len_table[0xd2]); + + return 0; +} +/*========================================== + * サウンドエフェクト + *------------------------------------------ + */ +void clif_soundeffect(struct map_session_data *sd,struct block_list *bl,char *name,int type) +{ + int fd; + + nullpo_retv(sd); + nullpo_retv(bl); + + fd=sd->fd; + WFIFOW(fd,0)=0x1d3; + memcpy(WFIFOP(fd,2),name,24); + WFIFOB(fd,26)=type; + WFIFOL(fd,27)=0; + WFIFOL(fd,31)=bl->id; + WFIFOSET(fd,packet_len_table[0x1d3]); + + return; +} +// displaying special effects (npcs, weather, etc) [Valaris] +int clif_specialeffect(struct block_list *bl, int type, int flag) { + unsigned char buf[24]; + + nullpo_retr(0, bl); + + memset(buf, 0, packet_len_table[0x1f3]); + + WBUFW(buf,0) = 0x1f3; + WBUFL(buf,2) = bl->id; + WBUFL(buf,6) = type; + + if (flag==2) { + struct map_session_data *sd=NULL; + int i; + for(i=0; i < fd_max; i++) { + if (session[i] && (sd = session[i]->session_data) != NULL && sd->state.auth && sd->bl.m == bl->m) + clif_specialeffect(&sd->bl,type,1); + } + } + + else if (flag==1) + clif_send(buf, packet_len_table[0x1f3], bl, SELF); + else if (!flag) + clif_send(buf, packet_len_table[0x1f3], bl, AREA); + + return 0; + +} +// ------------ +// clif_parse_* +// ------------ +// パケット読み取って色々操作 +/*========================================== + * + *------------------------------------------ + */ +void clif_parse_WantToConnection(int fd, struct map_session_data *sd) +{ + struct map_session_data *old_sd; + int account_id; // account_id in the packet 0x72 or 0x7E + + if (sd) { + if (battle_config.error_log) + printf("clif_parse_WantToConnection : invalid request?\n"); + return; + } + + // 0x72 + if (RFIFOW(fd,0) == 0x72) { + //printf("Received bytes %d with packet 0x72.\n", RFIFOREST(fd)); + if (RFIFOREST(fd) >= 39 && (RFIFOB(fd,38) == 0 || RFIFOB(fd,38) == 1)) // 00 = Female, 01 = Male + account_id = RFIFOL(fd,12); + else if (RFIFOREST(fd) >= 22 && (RFIFOB(fd,21) == 0 || RFIFOB(fd,21) == 1)) // 00 = Female, 01 = Male + account_id = RFIFOL(fd,5); + else // old packet version + account_id = RFIFOL(fd,2); + // 0x7E + } else if (RFIFOW(fd,0) == 0x7E) { + //printf("Received bytes %d with packet 0x7E.\n", RFIFOREST(fd)); + if (RFIFOREST(fd) >= 37 && (RFIFOB(fd,36) == 0 || RFIFOB(fd,36) == 1)) // 00 = Female, 01 = Male + account_id = RFIFOL(fd,9); + else + account_id = RFIFOL(fd,12); + // 0xF5 + } else { + //printf("Received bytes %d with packet 0xF5.\n", RFIFOREST(fd)); + account_id = RFIFOL(fd,7); + } + + // if same account already connected, we disconnect the 2 sessions + if ((old_sd = map_id2sd(account_id)) != NULL) { + clif_authfail_fd(fd, 2); // same id + clif_authfail_fd(old_sd->fd, 2); // same id + printf("clif_parse_WantToConnection: Double connection for account %d (sessions: #%d (new) and #%d (old)).\n", account_id, fd, old_sd->fd); + } else { + sd = session[fd]->session_data = calloc(sizeof(*sd), 1); + if (sd == NULL) { + printf("out of memory : clif_parse_WantToConnection\n"); + exit(1); + } + sd->fd = fd; + + // 0x72 + if (RFIFOW(fd,0) == 0x72) { + if (RFIFOREST(fd) >= 39 && (RFIFOB(fd,38) == 0 || RFIFOB(fd,38) == 1)) { // 00 = Female, 01 = Male + sd->packet_ver = 7; // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + pc_setnewpc(sd, account_id, RFIFOL(fd,22), RFIFOL(fd,30), RFIFOL(fd,34), RFIFOB(fd,38), fd); + } else if (RFIFOREST(fd) >= 22 && (RFIFOB(fd,21) == 0 || RFIFOB(fd,21) == 1)) { // 00 = Female, 01 = Male + sd->packet_ver = 6; // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + pc_setnewpc(sd, account_id, RFIFOL(fd,9), RFIFOL(fd,13), RFIFOL(fd,17), RFIFOB(fd,21), fd); + } else { // old packet version + sd->packet_ver = 5; // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + pc_setnewpc(sd, account_id, RFIFOL(fd,6), RFIFOL(fd,10), RFIFOL(fd,14), RFIFOB(fd,18), fd); + } + // 0x7E + } else if (RFIFOW(fd,0) == 0x7E) { + if (RFIFOREST(fd) >= 37 && (RFIFOB(fd,36) == 0 || RFIFOB(fd,36) == 1)) { // 00 = Female, 01 = Male + sd->packet_ver = 9; // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + pc_setnewpc(sd, account_id, RFIFOL(fd,21), RFIFOL(fd,28), RFIFOL(fd,32), RFIFOB(fd,36), fd); + } else { + sd->packet_ver = 8; // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + pc_setnewpc(sd, account_id, RFIFOL(fd,18), RFIFOL(fd,24), RFIFOL(fd,28), RFIFOB(fd,32), fd); + } + // 0xF5 + } else { + sd->packet_ver = 10; // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + pc_setnewpc(sd, account_id, RFIFOL(fd,15), RFIFOL(fd,25), RFIFOL(fd,29), RFIFOB(fd,33), fd); + } + + WFIFOL(fd,0) = sd->bl.id; + WFIFOSET(fd,4); + + map_addiddb(&sd->bl); + + chrif_authreq(sd); + } + + return; +} + +/*========================================== + * 007d クライアント側マップ読み込み完了 + * map侵入時に必要なデータを全て送りつける + *------------------------------------------ + */ +void clif_parse_LoadEndAck(int fd,struct map_session_data *sd) +{ +// struct item_data* item; + int i; + nullpo_retv(sd); + + if(sd->bl.prev != NULL) + return; + + // 接続ok時 + //clif_authok(); + if(sd->npc_id) npc_event_dequeue(sd); + clif_skillinfoblock(sd); + pc_checkitem(sd); + //guild_info(); + + // loadendack時 + // next exp + clif_updatestatus(sd,SP_NEXTBASEEXP); + clif_updatestatus(sd,SP_NEXTJOBEXP); + // skill point + clif_updatestatus(sd,SP_SKILLPOINT); + // item + clif_itemlist(sd); + clif_equiplist(sd); + // cart + if(pc_iscarton(sd)){ + clif_cart_itemlist(sd); + clif_cart_equiplist(sd); + clif_updatestatus(sd,SP_CARTINFO); + } + // param all + clif_initialstatus(sd); + // party + party_send_movemap(sd); + // guild + guild_send_memberinfoshort(sd,1); + // 119 + // 78 + + if(battle_config.pc_invincible_time > 0) { + if(map[sd->bl.m].flag.gvg) + pc_setinvincibletimer(sd,battle_config.pc_invincible_time<<1); + else + pc_setinvincibletimer(sd,battle_config.pc_invincible_time); + } + + map_addblock(&sd->bl); // ブロック登録 + clif_spawnpc(sd); // spawn + + // weight max , now + clif_updatestatus(sd,SP_MAXWEIGHT); + clif_updatestatus(sd,SP_WEIGHT); + + // pvp + if(sd->pvp_timer!=-1 && !battle_config.pk_mode) + delete_timer(sd->pvp_timer,pc_calc_pvprank_timer); + if(map[sd->bl.m].flag.pvp){ + if(!battle_config.pk_mode) { // remove pvp stuff for pk_mode [Valaris] + sd->pvp_timer=add_timer(gettick()+200,pc_calc_pvprank_timer,sd->bl.id,0); + sd->pvp_rank=0; + sd->pvp_lastusers=0; + sd->pvp_point=5; + } + clif_set0199(sd->fd,1); + } else { + sd->pvp_timer=-1; + } + if(map[sd->bl.m].flag.gvg) { + clif_set0199(sd->fd,3); + } + + // pet + if(sd->status.pet_id > 0 && sd->pd && sd->pet.intimate > 0) { + map_addblock(&sd->pd->bl); + clif_spawnpet(sd->pd); + clif_send_petdata(sd,0,0); + clif_send_petdata(sd,5,0x14); + clif_send_petstatus(sd); + } + + if(sd->state.connect_new) { + sd->state.connect_new = 0; + if(sd->status.class != sd->view_class) + clif_changelook(&sd->bl,LOOK_BASE,sd->view_class); + if(sd->status.pet_id > 0 && sd->pd && sd->pet.intimate > 900) + clif_pet_emotion(sd->pd,(sd->pd->class - 100)*100 + 50 + pet_hungry_val(sd)); + +/* Stop players from spawning inside castles [Valaris] */ + + { + struct guild_castle *gc=guild_mapname2gc(map[sd->bl.m].name); + if (gc) + pc_setpos(sd,sd->status.save_point.map,sd->status.save_point.x,sd->status.save_point.y,2); + } + +/* End Addition [Valaris] */ + + } + + // view equipment item +#if PACKETVER < 4 + clif_changelook(&sd->bl,LOOK_WEAPON,sd->status.weapon); + clif_changelook(&sd->bl,LOOK_SHIELD,sd->status.shield); +#else + clif_changelook(&sd->bl,LOOK_WEAPON,0); +#endif + if(battle_config.save_clothcolor==1 && sd->status.clothes_color > 0) + clif_changelook(&sd->bl,LOOK_CLOTHES_COLOR,sd->status.clothes_color); + + if(sd->status.hp<sd->status.max_hp>>2 && pc_checkskill(sd,SM_AUTOBERSERK)>0 && + (sd->sc_data[SC_PROVOKE].timer==-1 || sd->sc_data[SC_PROVOKE].val2==0 )) + // オートバーサーク発動 + skill_status_change_start(&sd->bl,SC_PROVOKE,10,1,0,0,0,0); + +// if(time(&timer) < ((weddingtime=pc_readglobalreg(sd,"PC_WEDDING_TIME")) + 3600)) +// skill_status_change_start(&sd->bl,SC_WEDDING,0,weddingtime,0,0,36000,0); + + if(battle_config.muting_players && sd->status.manner < 0) + skill_status_change_start(&sd->bl,SC_NOCHAT,0,0,0,0,0,0); + + // option + clif_changeoption(&sd->bl); + if(sd->sc_data[SC_TRICKDEAD].timer != -1) + skill_status_change_end(&sd->bl,SC_TRICKDEAD,-1); + if(sd->sc_data[SC_SIGNUMCRUCIS].timer != -1 && !battle_check_undead(7,sd->def_ele)) + skill_status_change_end(&sd->bl,SC_SIGNUMCRUCIS,-1); + if(sd->special_state.infinite_endure && sd->sc_data[SC_ENDURE].timer == -1) + skill_status_change_start(&sd->bl,SC_ENDURE,10,1,0,0,0,0); + for(i=0;i<MAX_INVENTORY;i++){ + if(sd->status.inventory[i].equip && sd->status.inventory[i].equip & 0x0002 && sd->status.inventory[i].broken==1) + skill_status_change_start(&sd->bl,SC_BROKNWEAPON,0,0,0,0,0,0); + if(sd->status.inventory[i].equip && sd->status.inventory[i].equip & 0x0010 && sd->status.inventory[i].broken==1) + skill_status_change_start(&sd->bl,SC_BROKNARMOR,0,0,0,0,0,0); + } + + map_foreachinarea(clif_getareachar,sd->bl.m,sd->bl.x-AREA_SIZE,sd->bl.y-AREA_SIZE,sd->bl.x+AREA_SIZE,sd->bl.y+AREA_SIZE,0,sd); +} + +/*========================================== + * + *------------------------------------------ + */ +void clif_parse_TickSend(int fd, struct map_session_data *sd) { + nullpo_retv(sd); + + if (sd->packet_ver == 8) // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + sd->client_tick = RFIFOL(fd,6); + else if (sd->packet_ver == 9) // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + sd->client_tick = RFIFOL(fd,9); + else if (sd->packet_ver == 10) // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + sd->client_tick = RFIFOL(fd,7); + else // old version by default (and version 6 + 7) + sd->client_tick = RFIFOL(fd,2); + sd->server_tick = gettick(); + clif_servertick(sd); +} + +/*========================================== + * + *------------------------------------------ + */ +void clif_parse_WalkToXY(int fd, struct map_session_data *sd) { + int x, y; + + nullpo_retv(sd); + + if (pc_isdead(sd)) { + clif_clearchar_area(&sd->bl, 1); + return; + } + + if (sd->npc_id != 0 || sd->vender_id != 0) + return; + + if (sd->skilltimer != -1 && pc_checkskill(sd, SA_FREECAST) <= 0) // フリーキャスト + return; + + if (sd->chatID) + return; + + if (sd->canmove_tick > gettick()) + return; + + // ステータス異常やハイディング中(トンネルドライブ無)で動けない + if ((sd->opt1 > 0 && sd->opt1 != 6) || + sd->sc_data[SC_ANKLE].timer !=-1 || //アンクルスネア + sd->sc_data[SC_AUTOCOUNTER].timer !=-1 || //オートカウンター + sd->sc_data[SC_TRICKDEAD].timer !=-1 || //死んだふり + sd->sc_data[SC_BLADESTOP].timer !=-1 || //白刃取り + sd->sc_data[SC_SPIDERWEB].timer !=-1 || //スパイダーウェッブ + (sd->sc_data[SC_DANCING].timer !=-1 && sd->sc_data[SC_DANCING].val4)) //合奏スキル演奏中は動けない + return; + if ((sd->status.option & 2) && pc_checkskill(sd, RG_TUNNELDRIVE) <= 0) + return; + + if (sd->invincible_timer != -1) + pc_delinvincibletimer(sd); + + pc_stopattack(sd); + + if (sd->packet_ver == 6) { // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + x = RFIFOB(fd,5) * 4 + (RFIFOB(fd,6) >> 6); + y = ((RFIFOB(fd,6) & 0x3f) << 4) + (RFIFOB(fd,7) >> 4); + } else if (sd->packet_ver == 7) { // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + x = RFIFOB(fd,6) * 4 + (RFIFOB(fd,7) >> 6); + y = ((RFIFOB(fd,7) & 0x3f) << 4) + (RFIFOB(fd,8) >> 4); + } else if (sd->packet_ver == 8) { // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + x = RFIFOB(fd,3) * 4 + (RFIFOB(fd,4) >> 6); + y = ((RFIFOB(fd,4) & 0x3f) << 4) + (RFIFOB(fd,5) >> 4); + } else if (sd->packet_ver == 9) { // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + x = RFIFOB(fd,12) * 4 + (RFIFOB(fd,13) >> 6); + y = ((RFIFOB(fd,13) & 0x3f) << 4) + (RFIFOB(fd,14) >> 4); + } else if (sd->packet_ver == 10) { // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + x = RFIFOB(fd,6) * 4 + (RFIFOB(fd,7) >> 6); + y = ((RFIFOB(fd,7) & 0x3f) << 4) + (RFIFOB(fd,8) >> 4); + } else { // old version by default + x = RFIFOB(fd,2) * 4 + (RFIFOB(fd,3) >> 6); + y = ((RFIFOB(fd,3) & 0x3f) << 4) + (RFIFOB(fd,4) >> 4); + } + pc_walktoxy(sd, x, y); + +} + +/*========================================== + * + *------------------------------------------ + */ +void clif_parse_QuitGame(int fd, struct map_session_data *sd) { + unsigned int tick=gettick(); + struct skill_unit_group* sg; + + nullpo_retv(sd); + + WFIFOW(fd,0) = 0x18b; + if ((!pc_isdead(sd) && (sd->opt1 || (sd->opt2 && !(night_flag == 1 && sd->opt2 == STATE_BLIND)))) || + sd->skilltimer != -1 || + (DIFF_TICK(tick , sd->canact_tick) < 0) || + (sd->sc_data && sd->sc_data[SC_DANCING].timer!=-1 && sd->sc_data[SC_DANCING].val4 && (sg=(struct skill_unit_group *)sd->sc_data[SC_DANCING].val2) && sg->src_id == sd->bl.id)) { + WFIFOW(fd,2)=1; + WFIFOSET(fd,packet_len_table[0x18b]); + return; + } + + /* Rovert's prevent logout option fixed [Valaris] */ + if ((battle_config.prevent_logout && (gettick() - sd->canlog_tick) >= 10000) || (!battle_config.prevent_logout)) { + clif_setwaitclose(fd); + WFIFOW(fd,2)=0; + } else { + WFIFOW(fd,2)=1; + } + WFIFOSET(fd,packet_len_table[0x18b]); + +} + +/*========================================== + * + *------------------------------------------ + */ +void clif_parse_GetCharNameRequest(int fd, struct map_session_data *sd) { + struct block_list *bl; + int account_id; + + if (sd->packet_ver == 8) { // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + account_id = RFIFOL(fd,11); + } else if (sd->packet_ver == 9) { // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + account_id = RFIFOL(fd,8); + } else if (sd->packet_ver == 10) { // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + account_id = RFIFOL(fd,10); + } else { // old version by default (+ packet version 6 and 7) + account_id = RFIFOL(fd,2); + } + bl = map_id2bl(account_id); + if (bl == NULL) + return; + + WFIFOW(fd,0) = 0x95; + WFIFOL(fd,2) = account_id; + + switch(bl->type) { + case BL_PC: + { + struct map_session_data *ssd = (struct map_session_data *)bl; + struct party *p = NULL; + struct guild *g = NULL; + + nullpo_retv(ssd); + + memcpy(WFIFOP(fd,6), ssd->status.name, 24); + if (ssd->status.guild_id > 0 && (g = guild_search(ssd->status.guild_id)) != NULL && + (ssd->status.party_id == 0 || (p = party_search(ssd->status.party_id)) != NULL)) { + // ギルド所属ならパケット0195を返す + int i, ps = -1; + for(i = 0; i < g->max_member; i++) { + if (g->member[i].account_id == ssd->status.account_id && + g->member[i].char_id == ssd->status.char_id ) + ps = g->member[i].position; + } + if (ps >= 0 && ps < MAX_GUILDPOSITION) { + WFIFOW(fd, 0) = 0x195; + if (p) + memcpy(WFIFOP(fd,30), p->name, 24); + else + WFIFOB(fd,30) = 0; + memcpy(WFIFOP(fd,54), g->name,24); + memcpy(WFIFOP(fd,78), g->position[ps].name, 24); + WFIFOSET(fd,packet_len_table[0x195]); + break; + } + } + WFIFOSET(fd,packet_len_table[0x95]); + } + break; + case BL_PET: + memcpy(WFIFOP(fd,6), ((struct pet_data*)bl)->name, 24); + WFIFOSET(fd,packet_len_table[0x95]); + break; + case BL_NPC: + memcpy(WFIFOP(fd,6), ((struct npc_data*)bl)->name, 24); + WFIFOSET(fd,packet_len_table[0x95]); + break; + case BL_MOB: + { + struct mob_data *md = (struct mob_data *)bl; + + nullpo_retv(md); + + memcpy(WFIFOP(fd,6), md->name, 24); + if (md->class >= 1285 && md->class <= 1288) { + struct guild *g; + struct guild_castle *gc = guild_mapname2gc(map[md->bl.m].name); + if (gc && gc->guild_id > 0 && (g = guild_search(gc->guild_id)) != NULL) { + WFIFOW(fd, 0) = 0x195; + WFIFOB(fd,30) = 0; + memcpy(WFIFOP(fd,54), g->name, 24); + memcpy(WFIFOP(fd,78), gc->castle_name, 24); + WFIFOSET(fd,packet_len_table[0x195]); + } else { + WFIFOSET(fd,packet_len_table[0x95]); + } + } else if (battle_config.show_mob_hp == 1) { + char mobhp[50]; + sprintf(mobhp, "hp: %d/%d", md->hp, mob_db[md->class].max_hp); + WFIFOW(fd, 0) = 0x195; + memcpy(WFIFOP(fd,30), mobhp, 24); + WFIFOB(fd,54) = 0; + WFIFOB(fd,78) = 0; + WFIFOSET(fd,packet_len_table[0x195]); + } else { + WFIFOSET(fd,packet_len_table[0x95]); + } + } + break; + default: + if (battle_config.error_log) + printf("clif_parse_GetCharNameRequest : bad type %d(%d)\n", bl->type, account_id); + break; + } +} + +/*========================================== + * + *------------------------------------------ + */ +void clif_parse_GlobalMessage(int fd, struct map_session_data *sd) { // S 008c <len>.w <str>.?B + char *message = (char *) malloc(RFIFOW(fd,2) + 128); + char *buf = (char *) malloc(RFIFOW(fd,2) + 4); + + nullpo_retv(sd); + + memset(message, '\0', RFIFOW(fd,2) + 128); + memset(buf, '\0', RFIFOW(fd,2) + 4); + + if ((is_atcommand(fd, sd, RFIFOP(fd,4), 0) != AtCommand_None) || + ( sd->sc_data && + (sd->sc_data[SC_BERSERK].timer!=-1 || //バーサーク時は会話も不可 + sd->sc_data[SC_NOCHAT].timer!=-1 ) )) //チャット禁止 + { + free(message); + free(buf); + return; + } + + //printf("clif_parse_GlobalMessage: message: '%s'.\n", RFIFOP(fd,4)); + if (strncmp(RFIFOP(fd,4), sd->status.name, strlen(sd->status.name)) != 0) { + printf("Hack on global message: character '%s' (account: %d), use an other name to send a (normal) message.\n", sd->status.name, sd->status.account_id); + + // information is sended to all online GM + sprintf(message, "Hack on global message (normal message): character '%s' (account: %d) uses an other name.", sd->status.name, sd->status.account_id); + intif_wis_message_to_gm(wisp_server_name, battle_config.hack_info_GM_level, message, strlen(message) + 1); + if (strlen(RFIFOP(fd,4)) == 0) + strcpy(message, " This player sends a void name and a void message."); + else + sprintf(message, " This player sends (name:message): '%s'.", RFIFOP(fd,4)); + intif_wis_message_to_gm(wisp_server_name, battle_config.hack_info_GM_level, message, strlen(message) + 1); + // message about the ban + if (battle_config.ban_spoof_namer > 0) + sprintf(message, " This player has been banned for %d minute(s).", battle_config.ban_spoof_namer); + else + sprintf(message, " This player hasn't been banned (Ban option is disabled)."); + intif_wis_message_to_gm(wisp_server_name, battle_config.hack_info_GM_level, message, strlen(message) + 1); + + // if we ban people + if (battle_config.ban_spoof_namer > 0) { + chrif_char_ask_name(-1, sd->status.name, 2, 0, 0, 0, 0, battle_config.ban_spoof_namer, 0); // type: 2 - ban (year, month, day, hour, minute, second) + clif_setwaitclose(fd); // forced to disconnect because of the hack + } + // but for the hacker, we display on his screen (he see/look no difference). + } else { + // send message to others + WBUFW(buf,0) = 0x8d; + WBUFW(buf,2) = RFIFOW(fd,2) + 4; // len of message - 4 + 8 + WBUFL(buf,4) = sd->bl.id; + memcpy(WBUFP(buf,8), RFIFOP(fd,4), RFIFOW(fd,2) - 4); + clif_send(buf, WBUFW(buf,2), &sd->bl, sd->chatID ? CHAT_WOS : AREA_CHAT_WOC); + } + + // send back message to the speaker + memcpy(WFIFOP(fd,0), RFIFOP(fd,0), RFIFOW(fd,2)); + WFIFOW(fd,0) = 0x8e; + WFIFOSET(fd, WFIFOW(fd,2)); + + free(message); + free(buf); + + return; +} + +int clif_message(struct block_list *bl, char* msg) +{ + unsigned short msg_len = strlen(msg) + 1; + unsigned char buf[256]; + + nullpo_retr(0, bl); + + WBUFW(buf, 0) = 0x8d; + WBUFW(buf, 2) = msg_len + 8; + WBUFL(buf, 4) = bl->id; + memcpy(WBUFP(buf, 8), msg, msg_len); + + clif_send(buf, WBUFW(buf,2), bl, AREA); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +void clif_parse_MapMove(int fd, struct map_session_data *sd) { +// /m /mapmove (as @rura GM command) + char output[100]; + char map_name[17]; + + nullpo_retv(sd); + + memset(output, '\0', sizeof(output)); + memset(map_name, '\0', sizeof(map_name)); + + if ((battle_config.atc_gmonly == 0 || pc_isGM(sd)) && + (pc_isGM(sd) >= get_atcommand_level(AtCommand_MapMove))) { + memcpy(map_name, RFIFOP(fd,2), 16); + sprintf(output, "%s %d %d", map_name, RFIFOW(fd,18), RFIFOW(fd,20)); + atcommand_rura(fd, sd, "@rura", output); + } + + return; +} + +/*========================================== + * + *------------------------------------------ + */ +void clif_parse_ChangeDir(int fd, struct map_session_data *sd) { + unsigned char buf[64]; + short headdir, dir; + + nullpo_retv(sd); + + if (sd->packet_ver == 7) { // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + headdir = RFIFOW(fd,5); + dir = RFIFOB(fd,12); + } else if (sd->packet_ver == 8) { // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + headdir = RFIFOW(fd,5); + dir = RFIFOB(fd,12); + } else if (sd->packet_ver == 9) { // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + headdir = RFIFOW(fd,7); + dir = RFIFOB(fd,11); + } else if (sd->packet_ver == 10) { // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + headdir = RFIFOW(fd,4); + dir = RFIFOB(fd,9); + } else { // old version by default (and packet version 6) + headdir = RFIFOW(fd,2); + dir = RFIFOB(fd,4); + } + + pc_setdir(sd, dir, headdir); + + WBUFW(buf,0) = 0x9c; + WBUFL(buf,2) = sd->bl.id; + WBUFW(buf,6) = headdir; + WBUFB(buf,8) = dir; + if (sd->disguise > 23 && sd->disguise < 4001) // mob disguises [Valaris] + clif_send(buf, packet_len_table[0x9c], &sd->bl, AREA); + else + clif_send(buf, packet_len_table[0x9c], &sd->bl, AREA_WOS); + +} + +/*========================================== + * + *------------------------------------------ + */ +void clif_parse_Emotion(int fd, struct map_session_data *sd) { + unsigned char buf[64]; + + nullpo_retv(sd); + + if (battle_config.basic_skill_check == 0 || pc_checkskill(sd, NV_BASIC) >= 2) { + WBUFW(buf,0) = 0xc0; + WBUFL(buf,2) = sd->bl.id; + WBUFB(buf,6) = RFIFOB(fd,2); + clif_send(buf, packet_len_table[0xc0], &sd->bl, AREA); + } else + clif_skill_fail(sd, 1, 0, 1); +} + +/*========================================== + * + *------------------------------------------ + */ +void clif_parse_HowManyConnections(int fd, struct map_session_data *sd) { + WFIFOW(fd,0) = 0xc2; + WFIFOL(fd,2) = map_getusers(); + WFIFOSET(fd,packet_len_table[0xc2]); +} + +/*========================================== + * + *------------------------------------------ + */ +void clif_parse_ActionRequest(int fd, struct map_session_data *sd) { + unsigned int tick; + unsigned char buf[64]; + int action_type, target_id; + + nullpo_retv(sd); + + if (pc_isdead(sd)) { + clif_clearchar_area(&sd->bl, 1); + return; + } + if (sd->npc_id != 0 || sd->opt1 > 0 || sd->status.option & 2 || + (sd->sc_data && + (sd->sc_data[SC_AUTOCOUNTER].timer != -1 || //オートカウンター + sd->sc_data[SC_BLADESTOP].timer != -1 || //白刃取り + sd->sc_data[SC_DANCING].timer != -1))) + return; + + tick = gettick(); + + pc_stop_walking(sd, 0); + pc_stopattack(sd); + + if (sd->packet_ver == 8) { // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + target_id = RFIFOL(fd,3); + action_type = RFIFOB(fd,8); + } else if (sd->packet_ver == 9) { // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + target_id = RFIFOL(fd,7); + action_type = RFIFOB(fd,17); + } else if (sd->packet_ver == 10) { // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + target_id = RFIFOL(fd,9); + action_type = RFIFOB(fd,22); + } else { // old version by default (and packet version 6 and 7) + target_id = RFIFOL(fd,2); + action_type = RFIFOB(fd,6); + } + + switch(action_type) { + case 0x00: // once attack + case 0x07: // continuous attack + if(sd->sc_data[SC_WEDDING].timer != -1 || sd->view_class==22) + return; + if (sd->vender_id != 0) + return; + if (!battle_config.sdelay_attack_enable && pc_checkskill(sd, SA_FREECAST) <= 0) { + if (DIFF_TICK(tick, sd->canact_tick) < 0) { + clif_skill_fail(sd, 1, 4, 0); + return; + } + } + if (sd->invincible_timer != -1) + pc_delinvincibletimer(sd); + if (sd->attacktarget > 0) // [Valaris] + sd->attacktarget = 0; + pc_attack(sd, target_id, action_type != 0); + break; + case 0x02: // sitdown + if (battle_config.basic_skill_check == 0 || pc_checkskill(sd, NV_BASIC) >= 3) { + pc_stop_walking(sd, 1); + skill_gangsterparadise(sd, 1); // ギャングスターパラダイス設定 + pc_setsit(sd); + clif_sitting(fd, sd); + } else + clif_skill_fail(sd, 1, 0, 2); + break; + case 0x03: // standup + skill_gangsterparadise(sd, 0); // ギャングスターパラダイス解除 + pc_setstand(sd); + WBUFW(buf, 0) = 0x8a; + WBUFL(buf, 2) = sd->bl.id; + WBUFB(buf,26) = 3; + clif_send(buf, packet_len_table[0x8a], &sd->bl, AREA); + break; + } +} + +/*========================================== + * + *------------------------------------------ + */ +void clif_parse_Restart(int fd, struct map_session_data *sd) { + nullpo_retv(sd); + + switch(RFIFOB(fd,2)) { + case 0x00: + if (pc_isdead(sd)) { + pc_setstand(sd); + pc_setrestartvalue(sd, 3); + pc_setpos(sd, sd->status.save_point.map, sd->status.save_point.x, sd->status.save_point.y, 2); + } + break; + case 0x01: + if(!pc_isdead(sd) && (sd->opt1 || (sd->opt2 && !(night_flag == 1 && sd->opt2 == STATE_BLIND)))) + return; + + /* Rovert's Prevent logout option - Fixed [Valaris] */ + if ((battle_config.prevent_logout && (gettick() - sd->canlog_tick) >= 10000) || (!battle_config.prevent_logout)) { + chrif_charselectreq(sd); + } else { + WFIFOW(fd,0)=0x18b; + WFIFOW(fd,2)=1; + + WFIFOSET(fd,packet_len_table[0x018b]); + } + break; + } +} + +/*========================================== + * Transmission of a wisp (S 0096 <len>.w <nick>.24B <message>.?B) + *------------------------------------------ + */ +void clif_parse_Wis(int fd, struct map_session_data *sd) { // S 0096 <len>.w <nick>.24B <message>.?B // rewritten by [Yor] + struct map_session_data *dstsd; + int i; + int gmlen = strlen(RFIFOP(fd,28)); + char gmbuf[512]; + char *gm_command = ((gmlen+28) > sizeof(gmbuf)) ? (char *) malloc(gmlen + 28) : gmbuf; + // 24+3+(RFIFOW(fd,2)-28)+1 or 24+3+(strlen(RFIFOP(fd,28))+1 (size can be wrong with hacker) + + //printf("clif_parse_Wis: message: '%s'.\n", RFIFOP(fd,28)); + memset(gm_command, 0, gmlen); + sprintf(gm_command, "%s : %s", sd->status.name, RFIFOP(fd,28)); + if ((is_atcommand(fd, sd, gm_command, 0) != AtCommand_None) || + ( sd && sd->sc_data && + (sd->sc_data[SC_BERSERK].timer!=-1 || //バーサーク時は会話も不可 + sd->sc_data[SC_NOCHAT].timer!=-1 ) )) //チャット禁止 + { + if (gm_command != gmbuf) + free(gm_command); + return; + } + + // searching destination character + dstsd = map_nick2sd(RFIFOP(fd,4)); + // player is not on this map-server + if (dstsd == NULL || + // At this point, don't send wisp/page if it's not exactly the same name, because (example) + // if there are 'Test' player on an other map-server and 'test' player on this map-server, + // and if we ask for 'Test', we must not contact 'test' player + // so, we send information to inter-server, which is the only one which decide (and copy correct name). + strcmp(dstsd->status.name, RFIFOP(fd,4)) != 0) // not exactly same name + // send message to inter-server + intif_wis_message(sd, RFIFOP(fd,4), RFIFOP(fd,28), RFIFOW(fd,2)-28); + // player is on this map-server + else { + // if you send to your self, don't send anything to others + if (dstsd->fd == fd) // but, normaly, it's impossible! + clif_wis_message(fd, wisp_server_name, "You can not page yourself. Sorry.", strlen("You can not page yourself. Sorry.") + 1); + // otherwise, send message and answer immediatly + else { + if (dstsd->ignoreAll == 1) + clif_wis_end(fd, 2); // type: 0: success to send wisper, 1: target character is not loged in?, 2: ignored by target + else { + // if player ignore the source character + for(i = 0; i < (sizeof(dstsd->ignore) / sizeof(dstsd->ignore[0])); i++) + if (strcmp(dstsd->ignore[i].name, sd->status.name) == 0) { + clif_wis_end(fd, 2); // flag: 0: success to send wisper, 1: target character is not loged in?, 2: ignored by target + break; + } + // if source player not found in ignore list + if (i == (sizeof(sd->ignore) / sizeof(sd->ignore[0]))) { + clif_wis_message(dstsd->fd, sd->status.name, RFIFOP(fd,28), RFIFOW(fd,2) - 28); + clif_wis_end(fd, 0); // type: 0: success to send wisper, 1: target character is not loged in?, 2: ignored by target + } + } + } + } + + if (gm_command != gmbuf) + free(gm_command); + + return; +} + +/*========================================== + * + *------------------------------------------ + */ +void clif_parse_GMmessage(int fd, struct map_session_data *sd) { +// /b + nullpo_retv(sd); + + if ((battle_config.atc_gmonly == 0 || pc_isGM(sd)) && + (pc_isGM(sd) >= get_atcommand_level(AtCommand_Broadcast))) + intif_GMmessage(RFIFOP(fd,4), RFIFOW(fd,2)-4, 0); +} + +/*========================================== + * + *------------------------------------------ + */ +void clif_parse_TakeItem(int fd, struct map_session_data *sd) { + struct flooritem_data *fitem; + int map_object_id; + + nullpo_retv(sd); + + if (sd->packet_ver == 7) // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + map_object_id = RFIFOL(fd,6); + else if (sd->packet_ver == 8) // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + map_object_id = RFIFOL(fd,6); + else if (sd->packet_ver == 9) // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + map_object_id = RFIFOL(fd,9); + else if (sd->packet_ver == 10) // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + map_object_id = RFIFOL(fd,7); + else // old version by default (and parcket version 6) + map_object_id = RFIFOL(fd,2); + fitem = (struct flooritem_data*)map_id2bl(map_object_id); + + if (pc_isdead(sd)) { + clif_clearchar_area(&sd->bl, 1); + return; + } + + if( sd->npc_id!=0 || sd->vender_id != 0 || sd->opt1 > 0 || + (sd->sc_data && (sd->sc_data[SC_TRICKDEAD].timer != -1 || //死んだふり + sd->sc_data[SC_BLADESTOP].timer != -1 || //白刃取り + sd->sc_data[SC_BERSERK].timer!=-1 || //バーサーク + sd->sc_data[SC_NOCHAT].timer!=-1 )) ) //会話禁止 + return; + + if (fitem == NULL || fitem->bl.m != sd->bl.m) + return; + + pc_takeitem(sd, fitem); +} + +/*========================================== + * + *------------------------------------------ + */ +void clif_parse_DropItem(int fd, struct map_session_data *sd) { + int item_index, item_amount; + + nullpo_retv(sd); + + if (pc_isdead(sd)) { + clif_clearchar_area(&sd->bl, 1); + return; + } + if (sd->npc_id != 0 || sd->vender_id != 0 || sd->opt1 > 0 || + (sd->sc_data && (sd->sc_data[SC_AUTOCOUNTER].timer != -1 || //オートカウンター + sd->sc_data[SC_BLADESTOP].timer != -1 || //白刃取り + sd->sc_data[SC_BERSERK].timer != -1)) ) //バーサーク + return; + + if (sd->packet_ver == 8) { // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + item_index = RFIFOW(fd,5) - 2; + item_amount = RFIFOW(fd,12); + } else if (sd->packet_ver == 9) { // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + item_index = RFIFOW(fd,8) - 2; + item_amount = RFIFOW(fd,15); + } else if (sd->packet_ver == 10) { // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + item_index = RFIFOW(fd,6) - 2; + item_amount = RFIFOW(fd,15); + } else { // old version by default (+ packet version 6 and 7) + item_index = RFIFOW(fd,2) - 2; + item_amount = RFIFOW(fd,4); + } + + pc_dropitem(sd, item_index, item_amount); +} + +/*========================================== + * + *------------------------------------------ + */ +void clif_parse_UseItem(int fd, struct map_session_data *sd) { + nullpo_retv(sd); + + if (pc_isdead(sd)) { + clif_clearchar_area(&sd->bl, 1); + return; + } + if (sd->npc_id!=0 || sd->vender_id != 0 || sd->opt1 > 0 || + (sd->sc_data && (sd->sc_data[SC_TRICKDEAD].timer != -1 || //死んだふり + sd->sc_data[SC_BLADESTOP].timer != -1 || //白刃取り + sd->sc_data[SC_BERSERK].timer!=-1 || //バーサーク + sd->sc_data[SC_NOCHAT].timer!=-1 )) ) //会話禁止 + return; + + if (sd->invincible_timer != -1) + pc_delinvincibletimer(sd); + + if (sd->packet_ver == 6) // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + pc_useitem(sd,RFIFOW(fd,5)-2); + else if (sd->packet_ver == 7) // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + pc_useitem(sd,RFIFOW(fd,6)-2); + else if (sd->packet_ver == 8) // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + pc_useitem(sd,RFIFOW(fd,6)-2); + else if (sd->packet_ver == 9) // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + pc_useitem(sd,RFIFOW(fd,9)-2); + else if (sd->packet_ver == 10) // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + pc_useitem(sd,RFIFOW(fd,7)-2); + else // old version by default + pc_useitem(sd,RFIFOW(fd,2)-2); +} + +/*========================================== + * + *------------------------------------------ + */ +void clif_parse_EquipItem(int fd,struct map_session_data *sd) +{ + int index; + + nullpo_retv(sd); + + if(pc_isdead(sd)) { + clif_clearchar_area(&sd->bl,1); + return; + } + index = RFIFOW(fd,2)-2; + if(sd->npc_id!=0 || sd->vender_id != 0) return; + if(sd->sc_data && ( sd->sc_data[SC_BLADESTOP].timer!=-1 || sd->sc_data[SC_BERSERK].timer!=-1 )) return; + + if(sd->status.inventory[index].identify != 1) { // 未鑑定 + // Bjorn: Auto-identify items when equipping them as there + // is no nice way to do this in the client yet. + sd->status.inventory[index].identify = 1; + //clif_equipitemack(sd,index,0,0); // fail + //return; + } + //ペット用装備であるかないか + if(sd->inventory_data[index]) { + if(sd->inventory_data[index]->type != 8){ + if(sd->inventory_data[index]->type == 10) + RFIFOW(fd,4)=0x8000; // 矢を無理やり装備できるように(−−; + pc_equipitem(sd,index,RFIFOW(fd,4)); + } else + pet_equipitem(sd,index); + } +} + +/*========================================== + * + *------------------------------------------ + */ +void clif_parse_UnequipItem(int fd,struct map_session_data *sd) +{ + int index; + + nullpo_retv(sd); + + if(pc_isdead(sd)) { + clif_clearchar_area(&sd->bl,1); + return; + } + index = RFIFOW(fd,2)-2; + if(sd->status.inventory[index].broken == 1 && sd->sc_data && sd->sc_data[SC_BROKNWEAPON].timer!=-1) + skill_status_change_end(&sd->bl,SC_BROKNWEAPON,-1); + if(sd->status.inventory[index].broken == 1 && sd->sc_data && sd->sc_data[SC_BROKNARMOR].timer!=-1) + skill_status_change_end(&sd->bl,SC_BROKNARMOR,-1); + if(sd->sc_data && ( sd->sc_data[SC_BLADESTOP].timer!=-1 || sd->sc_data[SC_BERSERK].timer!=-1 )) + return; + + if(sd->npc_id!=0 || sd->vender_id != 0 || sd->opt1 > 0) + return; + pc_unequipitem(sd,index,0); +} + +/*========================================== + * + *------------------------------------------ + */ +void clif_parse_NpcClicked(int fd,struct map_session_data *sd) +{ + nullpo_retv(sd); + + if(pc_isdead(sd)) { + clif_clearchar_area(&sd->bl,1); + return; + } + if(sd->npc_id!=0 || sd->vender_id != 0) + return; + npc_click(sd,RFIFOL(fd,2)); +} + +/*========================================== + * + *------------------------------------------ + */ +void clif_parse_NpcBuySellSelected(int fd,struct map_session_data *sd) +{ + npc_buysellsel(sd,RFIFOL(fd,2),RFIFOB(fd,6)); +} + +/*========================================== + * + *------------------------------------------ + */ +void clif_parse_NpcBuyListSend(int fd,struct map_session_data *sd) +{ + int fail=0,n; + unsigned short *item_list; + + n = (RFIFOW(fd,2)-4) /4; + item_list = (unsigned short*)RFIFOP(fd,4); + + fail = npc_buylist(sd,n,item_list); + + WFIFOW(fd,0)=0xca; + WFIFOB(fd,2)=fail; + WFIFOSET(fd,packet_len_table[0xca]); +} + +/*========================================== + * + *------------------------------------------ + */ +void clif_parse_NpcSellListSend(int fd,struct map_session_data *sd) +{ + int fail=0,n; + unsigned short *item_list; + + n = (RFIFOW(fd,2)-4) /4; + item_list = (unsigned short*)RFIFOP(fd,4); + + fail = npc_selllist(sd,n,item_list); + + WFIFOW(fd,0)=0xcb; + WFIFOB(fd,2)=fail; + WFIFOSET(fd,packet_len_table[0xcb]); +} + +/*========================================== + * + *------------------------------------------ + */ +void clif_parse_CreateChatRoom(int fd,struct map_session_data *sd) +{ + if(battle_config.basic_skill_check == 0 || pc_checkskill(sd,NV_BASIC) >= 4){ + chat_createchat(sd,RFIFOW(fd,4),RFIFOB(fd,6),RFIFOP(fd,7),RFIFOP(fd,15),RFIFOW(fd,2)-15); + } else + clif_skill_fail(sd,1,0,3); +} + +/*========================================== + * + *------------------------------------------ + */ +void clif_parse_ChatAddMember(int fd,struct map_session_data *sd) +{ + chat_joinchat(sd,RFIFOL(fd,2),RFIFOP(fd,6)); +} + +/*========================================== + * + *------------------------------------------ + */ +void clif_parse_ChatRoomStatusChange(int fd,struct map_session_data *sd) +{ + chat_changechatstatus(sd,RFIFOW(fd,4),RFIFOB(fd,6),RFIFOP(fd,7),RFIFOP(fd,15),RFIFOW(fd,2)-15); +} + +/*========================================== + * + *------------------------------------------ + */ +void clif_parse_ChangeChatOwner(int fd,struct map_session_data *sd) +{ + chat_changechatowner(sd,RFIFOP(fd,6)); +} + +/*========================================== + * + *------------------------------------------ + */ +void clif_parse_KickFromChat(int fd,struct map_session_data *sd) +{ + chat_kickchat(sd,RFIFOP(fd,2)); +} + +/*========================================== + * + *------------------------------------------ + */ +void clif_parse_ChatLeave(int fd,struct map_session_data *sd) +{ + chat_leavechat(sd); +} + +/*========================================== + * 取引要請を相手に送る + *------------------------------------------ + */ +void clif_parse_TradeRequest(int fd,struct map_session_data *sd) +{ + nullpo_retv(sd); + + if(battle_config.basic_skill_check == 0 || pc_checkskill(sd,NV_BASIC) >= 1){ + trade_traderequest(sd,RFIFOL(sd->fd,2)); + } else + clif_skill_fail(sd,1,0,0); +} + +/*========================================== + * 取引要請 + *------------------------------------------ + */ +void clif_parse_TradeAck(int fd,struct map_session_data *sd) +{ + nullpo_retv(sd); + + trade_tradeack(sd,RFIFOB(sd->fd,2)); +} + +/*========================================== + * アイテム追加 + *------------------------------------------ + */ +void clif_parse_TradeAddItem(int fd,struct map_session_data *sd) +{ + nullpo_retv(sd); + + trade_tradeadditem(sd,RFIFOW(sd->fd,2),RFIFOL(sd->fd,4)); +} + +/*========================================== + * アイテム追加完了(ok押し) + *------------------------------------------ + */ +void clif_parse_TradeOk(int fd,struct map_session_data *sd) +{ + trade_tradeok(sd); +} + +/*========================================== + * 取引キャンセル + *------------------------------------------ + */ +void clif_parse_TradeCansel(int fd,struct map_session_data *sd) +{ + trade_tradecancel(sd); +} + +/*========================================== + * 取引許諾(trade押し) + *------------------------------------------ + */ +void clif_parse_TradeCommit(int fd,struct map_session_data *sd) +{ + trade_tradecommit(sd); +} + +/*========================================== + * + *------------------------------------------ + */ +void clif_parse_StopAttack(int fd,struct map_session_data *sd) +{ + pc_stopattack(sd); +} + +/*========================================== + * カートへアイテムを移す + *------------------------------------------ + */ +void clif_parse_PutItemToCart(int fd,struct map_session_data *sd) +{ + nullpo_retv(sd); + + if(sd->npc_id!=0 || sd->vender_id != 0) + return; + pc_putitemtocart(sd,RFIFOW(fd,2)-2,RFIFOL(fd,4)); +} +/*========================================== + * カートからアイテムを出す + *------------------------------------------ + */ +void clif_parse_GetItemFromCart(int fd,struct map_session_data *sd) +{ + nullpo_retv(sd); + + if(sd->npc_id!=0 || sd->vender_id != 0) return; + pc_getitemfromcart(sd,RFIFOW(fd,2)-2,RFIFOL(fd,4)); +} + +/*========================================== + * 付属品(鷹,ペコ,カート)をはずす + *------------------------------------------ + */ +void clif_parse_RemoveOption(int fd,struct map_session_data *sd) +{ + if(pc_isriding(sd)) { // jobchange when removing peco [Valaris] + if(sd->status.class==13) + sd->status.class=sd->view_class=7; + + if(sd->status.class==21) + sd->status.class=sd->view_class=14; + + if(sd->status.class==4014) + sd->status.class=sd->view_class=4008; + + if(sd->status.class==4022) + sd->status.class=sd->view_class=4015; + } + + pc_setoption(sd,0); +} + +/*========================================== + * チェンジカート + *------------------------------------------ + */ +void clif_parse_ChangeCart(int fd,struct map_session_data *sd) +{ + pc_setcart(sd,RFIFOW(fd,2)); +} + +/*========================================== + * ステータスアップ + *------------------------------------------ + */ +void clif_parse_StatusUp(int fd,struct map_session_data *sd) +{ + pc_statusup(sd,RFIFOW(fd,2)); +} + +/*========================================== + * スキルレベルアップ + *------------------------------------------ + */ +void clif_parse_SkillUp(int fd,struct map_session_data *sd) +{ + pc_skillup(sd,RFIFOW(fd,2)); +} + +/*========================================== + * スキル使用(ID指定) + *------------------------------------------ + */ +void clif_parse_UseSkillToId(int fd, struct map_session_data *sd) { + int skillnum, skilllv, lv, target_id; + unsigned int tick = gettick(); + + nullpo_retv(sd); + + if(map[sd->bl.m].flag.noskill) return; + if (sd->chatID || sd->npc_id != 0 || sd->vender_id != 0) + return; + + if (sd->packet_ver == 6) { // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + skilllv = RFIFOW(fd,4); + skillnum = RFIFOW(fd,9); + target_id = RFIFOL(fd,11); + } else if (sd->packet_ver == 7) { // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + skilllv = RFIFOW(fd,7); + skillnum = RFIFOW(fd,9); + target_id = RFIFOL(fd,15); + } else if (sd->packet_ver == 8) { // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + skilllv = RFIFOW(fd,7); + skillnum = RFIFOW(fd,12); + target_id = RFIFOL(fd,16); + } else if (sd->packet_ver == 9) { // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + skilllv = RFIFOW(fd,11); + skillnum = RFIFOW(fd,18); + target_id = RFIFOL(fd,22); + } else if (sd->packet_ver == 10) { // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + skilllv = RFIFOW(fd,9); + skillnum = RFIFOW(fd,15); + target_id = RFIFOL(fd,18); + } else { // old version by default + skilllv = RFIFOW(fd,2); + skillnum = RFIFOW(fd,4); + target_id = RFIFOL(fd,6); + } + + if (sd->skilltimer != -1) { + if (skillnum != SA_CASTCANCEL) + return; + } else if (DIFF_TICK(tick, sd->canact_tick) < 0) { + clif_skill_fail(sd, skillnum, 4, 0); + return; + } + + if ((sd->sc_data[SC_TRICKDEAD].timer != -1 && skillnum != NV_TRICKDEAD) || + sd->sc_data[SC_BERSERK].timer != -1 || sd->sc_data[SC_NOCHAT].timer!=-1 || + sd->sc_data[SC_WEDDING].timer!=-1 || sd->view_class==22) + return; + if (sd->invincible_timer != -1) + pc_delinvincibletimer(sd); + if (sd->skillitem >= 0 && sd->skillitem == skillnum) { + if (skilllv != sd->skillitemlv) + skilllv = sd->skillitemlv; + skill_use_id(sd, target_id, skillnum, skilllv); + } else { + sd->skillitem = sd->skillitemlv = -1; + if (skillnum == MO_EXTREMITYFIST) { + if ((sd->sc_data[SC_COMBO].timer == -1 || (sd->sc_data[SC_COMBO].val1 != MO_COMBOFINISH && sd->sc_data[SC_COMBO].val1 != CH_CHAINCRUSH))) { + if (!sd->state.skill_flag ) { + sd->state.skill_flag = 1; + clif_skillinfo(sd, MO_EXTREMITYFIST, 1, -1); + return; + } else if (sd->bl.id == target_id) { + clif_skillinfo(sd, MO_EXTREMITYFIST, 1, -1); + return; + } + } + } + if ((lv = pc_checkskill(sd, skillnum)) > 0) { + if (skilllv > lv) + skilllv = lv; + skill_use_id(sd, target_id, skillnum, skilllv); + if (sd->state.skill_flag) + sd->state.skill_flag = 0; + } + } +} + +/*========================================== + * スキル使用(場所指定) + *------------------------------------------ + */ +void clif_parse_UseSkillToPos(int fd, struct map_session_data *sd) { + int skillnum, skilllv, lv, x, y; + unsigned int tick = gettick(); + int skillmoreinfo; + + nullpo_retv(sd); + + if(map[sd->bl.m].flag.noskill) return; + if (sd->npc_id != 0 || sd->vender_id != 0) return; + if(sd->chatID) return; + + skillmoreinfo = -1; + if (sd->packet_ver == 6) { // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + skilllv = RFIFOW(fd,4); + skillnum = RFIFOW(fd,9); + x = RFIFOW(fd,11); + y = RFIFOW(fd,13); + if (RFIFOW(fd,0) == 0x190) + skillmoreinfo = 15; + } else if (sd->packet_ver == 7) { // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + skilllv = RFIFOW(fd,7); + skillnum = RFIFOW(fd,9); + x = RFIFOW(fd,15); + y = RFIFOW(fd,17); + if (RFIFOW(fd,0) == 0x190) + skillmoreinfo = 19; + } else if (sd->packet_ver == 8) { // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + skilllv = RFIFOW(fd,3); + skillnum = RFIFOW(fd,6); + x = RFIFOW(fd,17); + y = RFIFOW(fd,21); + if (RFIFOW(fd,0) == 0x0a2) + skillmoreinfo = 23; + } else if (sd->packet_ver == 9) { // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + skilllv = RFIFOW(fd,5); + skillnum = RFIFOW(fd,15); + x = RFIFOW(fd,29); + y = RFIFOW(fd,38); + if (RFIFOW(fd,0) == 0x0a2) + skillmoreinfo = 40; + } else if (sd->packet_ver == 10) { // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + skilllv = RFIFOW(fd,10); + skillnum = RFIFOW(fd,14); + x = RFIFOW(fd,18); + y = RFIFOW(fd,23); + if (RFIFOW(fd,0) == 0x0a7) + skillmoreinfo = 25; + } else { // old version by default + skilllv = RFIFOW(fd,2); + skillnum = RFIFOW(fd,4); + x = RFIFOW(fd,6); + y = RFIFOW(fd,8); + if (RFIFOW(fd,0) == 0x190) + skillmoreinfo = 10; + } + + if (skillmoreinfo != -1) { + if (pc_issit(sd)) { + clif_skill_fail(sd, skillnum, 0, 0); + return; + } + memcpy(talkie_mes, RFIFOP(fd,skillmoreinfo), 80); + } + + if (sd->skilltimer != -1) + return; + else if (DIFF_TICK(tick, sd->canact_tick) < 0) { + clif_skill_fail(sd, skillnum, 4, 0); + return; + } + + if((sd->sc_data[SC_TRICKDEAD].timer != -1 && skillnum != NV_TRICKDEAD) || + sd->sc_data[SC_BERSERK].timer!=-1 || sd->sc_data[SC_NOCHAT].timer!=-1 || + sd->sc_data[SC_WEDDING].timer!=-1 || sd->view_class==22) + return; + if (sd->invincible_timer != -1) + pc_delinvincibletimer(sd); + if (sd->skillitem >= 0 && sd->skillitem == skillnum) { + if (skilllv != sd->skillitemlv) + skilllv = sd->skillitemlv; + skill_use_pos(sd, x, y, skillnum, skilllv); + } else { + sd->skillitem = sd->skillitemlv = -1; + if ((lv = pc_checkskill(sd, skillnum)) > 0) { + if (skilllv > lv) + skilllv = lv; + skill_use_pos(sd, x, y, skillnum,skilllv); + } + } +} + +/*========================================== + * スキル使用(map指定) + *------------------------------------------ + */ +void clif_parse_UseSkillMap(int fd,struct map_session_data *sd) +{ + nullpo_retv(sd); + + if(map[sd->bl.m].flag.noskill) return; + if(sd->chatID) return; + + if(sd->npc_id!=0 || sd->vender_id != 0 || (sd->sc_data && + (sd->sc_data[SC_TRICKDEAD].timer != -1 || + sd->sc_data[SC_BERSERK].timer!=-1 || + sd->sc_data[SC_NOCHAT].timer!=-1 || + sd->sc_data[SC_WEDDING].timer!=-1 || + sd->view_class==22))) + return; + + if(sd->invincible_timer != -1) + pc_delinvincibletimer(sd); + + skill_castend_map(sd,RFIFOW(fd,2),RFIFOP(fd,4)); +} +/*========================================== + * メモ要求 + *------------------------------------------ + */ +void clif_parse_RequestMemo(int fd,struct map_session_data *sd) +{ + pc_memo(sd,-1); +} +/*========================================== + * アイテム合成 + *------------------------------------------ + */ +void clif_parse_ProduceMix(int fd,struct map_session_data *sd) +{ + nullpo_retv(sd); + + sd->state.produce_flag = 0; + skill_produce_mix(sd,RFIFOW(fd,2),RFIFOW(fd,4),RFIFOW(fd,6),RFIFOW(fd,8)); +} + +/*========================================== + * + *------------------------------------------ + */ +void clif_parse_NpcSelectMenu(int fd,struct map_session_data *sd) +{ + nullpo_retv(sd); + + sd->npc_menu=RFIFOB(fd,6); + npc_scriptcont(sd,RFIFOL(fd,2)); +} + +/*========================================== + * + *------------------------------------------ + */ +void clif_parse_NpcNextClicked(int fd,struct map_session_data *sd) +{ + npc_scriptcont(sd,RFIFOL(fd,2)); +} + +/*========================================== + * + *------------------------------------------ + */ +void clif_parse_NpcAmountInput(int fd,struct map_session_data *sd) +{ + nullpo_retv(sd); + +#define RFIFOL_(fd,pos) (*(int*)(session[fd]->rdata+session[fd]->rdata_pos+(pos))) + //Input Value overflow Exploit FIX + sd->npc_amount=RFIFOL_(fd,6); //fixed by Lupus. npc_amount is (int) but was RFIFOL changing it to (unsigned int) + +#undef RFIFOL_ + + npc_scriptcont(sd,RFIFOL(fd,2)); +} + +/*========================================== + * + *------------------------------------------ + */ +void clif_parse_NpcStringInput(int fd,struct map_session_data *sd) +{ + nullpo_retv(sd); + + if(RFIFOW(fd,2)-7 >= sizeof(sd->npc_str)){ + printf("clif: input string too long !\n"); + memcpy(sd->npc_str,RFIFOP(fd,8),sizeof(sd->npc_str)); + sd->npc_str[sizeof(sd->npc_str)-1]=0; + } else + strcpy(sd->npc_str,RFIFOP(fd,8)); + npc_scriptcont(sd,RFIFOL(fd,4)); +} + +/*========================================== + * + *------------------------------------------ + */ +void clif_parse_NpcCloseClicked(int fd,struct map_session_data *sd) +{ + npc_scriptcont(sd,RFIFOL(fd,2)); +} + +/*========================================== + * アイテム鑑定 + *------------------------------------------ + */ +void clif_parse_ItemIdentify(int fd,struct map_session_data *sd) +{ + pc_item_identify(sd,RFIFOW(fd,2)-2); +} +/*========================================== + * 矢作成 + *------------------------------------------ + */ +void clif_parse_SelectArrow(int fd,struct map_session_data *sd) +{ + nullpo_retv(sd); + + sd->state.make_arrow_flag = 0; + skill_arrow_create(sd,RFIFOW(fd,2)); +} +/*========================================== + * オートスペル受信 + *------------------------------------------ + */ +void clif_parse_AutoSpell(int fd,struct map_session_data *sd) +{ + skill_autospell(sd,RFIFOW(fd,2)); +} +/*========================================== + * カード使用 + *------------------------------------------ + */ +void clif_parse_UseCard(int fd,struct map_session_data *sd) +{ + clif_use_card(sd,RFIFOW(fd,2)-2); +} +/*========================================== + * カード挿入装備選択 + *------------------------------------------ + */ +void clif_parse_InsertCard(int fd,struct map_session_data *sd) +{ + pc_insert_card(sd,RFIFOW(fd,2)-2,RFIFOW(fd,4)-2); +} + +/*========================================== + * 0193 キャラID名前引き + *------------------------------------------ + */ +void clif_parse_SolveCharName(int fd, struct map_session_data *sd) { + int char_id; + + if (sd->packet_ver == 8) { // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + char_id = RFIFOL(fd,8); + } else if (sd->packet_ver == 9) { // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + char_id = RFIFOL(fd,7); + } else if (sd->packet_ver == 10) { // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + char_id = RFIFOL(fd,10); + } else { // old version by default (+ packet version 6 and 7) + char_id = RFIFOL(fd,2); + } + clif_solved_charname(sd, char_id); +} + +/*========================================== + * 0197 /resetskill /resetstate + *------------------------------------------ + */ +void clif_parse_ResetChar(int fd, struct map_session_data *sd) { + nullpo_retv(sd); + + if (battle_config.atc_gmonly == 0 || pc_isGM(sd)) { + switch(RFIFOW(fd,2)){ + case 0: + if (pc_isGM(sd) >= get_atcommand_level(AtCommand_ResetState)) + pc_resetstate(sd); + break; + case 1: + if (pc_isGM(sd) >= get_atcommand_level(AtCommand_ResetState)) + pc_resetskill(sd); + break; + } + } +} + +/*========================================== + * 019c /lb等 + *------------------------------------------ + */ +void clif_parse_LGMmessage(int fd, struct map_session_data *sd) { + unsigned char buf[64]; + + nullpo_retv(sd); + + if ((battle_config.atc_gmonly == 0 || pc_isGM(sd)) && + (pc_isGM(sd) >= get_atcommand_level(AtCommand_LocalBroadcast))) { + WBUFW(buf,0) = 0x9a; + WBUFW(buf,2) = RFIFOW(fd,2); + memcpy(WBUFP(buf,4), RFIFOP(fd,4), RFIFOW(fd,2) - 4); + clif_send(buf, RFIFOW(fd,2), &sd->bl, ALL_SAMEMAP); + } +} + +/*========================================== + * カプラ倉庫へ入れる + *------------------------------------------ + */ +void clif_parse_MoveToKafra(int fd, struct map_session_data *sd) { + int item_index, item_amount; + + nullpo_retv(sd); + + if (sd->packet_ver == 8) { // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + item_index = RFIFOW(fd,5) - 2; + item_amount = RFIFOL(fd,12); + } else if (sd->packet_ver == 9) { // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + item_index = RFIFOW(fd,5) - 2; + item_amount = RFIFOL(fd,19); + } else if (sd->packet_ver == 10) { // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + item_index = RFIFOW(fd,3) - 2; + item_amount = RFIFOL(fd,15); + } else { // old version by default (+ packet version 6 and 7) + item_index = RFIFOW(fd,2) - 2; + item_amount = RFIFOL(fd,4); + } + + if (sd->npc_id != 0 || sd->vender_id != 0) + return; + + if (sd->state.storage_flag) + storage_guild_storageadd(sd, item_index, item_amount); + else + storage_storageadd(sd, item_index, item_amount); +} + +/*========================================== + * カプラ倉庫から出す + *------------------------------------------ + */ +void clif_parse_MoveFromKafra(int fd,struct map_session_data *sd) { + int item_index, item_amount; + + nullpo_retv(sd); + + if (sd->packet_ver == 8) { // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + item_index = RFIFOW(fd,10) - 1; + item_amount = RFIFOL(fd,22); + } else if (sd->packet_ver == 9) { // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + item_index = RFIFOW(fd,11) - 1; + item_amount = RFIFOL(fd,22); + } else if (sd->packet_ver == 10) { // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + item_index = RFIFOW(fd,3) - 1; + item_amount = RFIFOL(fd,13); + } else { // old version by default (+ packet version 6 and 7) + item_index = RFIFOW(fd,2) - 1; + item_amount = RFIFOL(fd,4); + } + + if (sd->npc_id != 0 || sd->vender_id != 0) + return; + + if (sd->state.storage_flag) + storage_guild_storageget(sd, item_index, item_amount); + else + storage_storageget(sd, item_index, item_amount); +} + +/*========================================== + * カプラ倉庫へカートから入れる + *------------------------------------------ + */ +void clif_parse_MoveToKafraFromCart(int fd, struct map_session_data *sd) { + nullpo_retv(sd); + + if (sd->npc_id != 0 || sd->vender_id != 0) + return; + if (sd->state.storage_flag) + storage_guild_storageaddfromcart(sd, RFIFOW(fd,2) - 2, RFIFOL(fd,4)); + else + storage_storageaddfromcart(sd, RFIFOW(fd,2) - 2, RFIFOL(fd,4)); +} + +/*========================================== + * カプラ倉庫から出す + *------------------------------------------ + */ +void clif_parse_MoveFromKafraToCart(int fd, struct map_session_data *sd) { + nullpo_retv(sd); + + if (sd->npc_id != 0 || sd->vender_id != 0) + return; + if (sd->state.storage_flag) + storage_guild_storagegettocart(sd, RFIFOW(fd,2)-1, RFIFOL(fd,4)); + else + storage_storagegettocart(sd, RFIFOW(fd,2)-1, RFIFOL(fd,4)); +} + +/*========================================== + * カプラ倉庫を閉じる + *------------------------------------------ + */ +void clif_parse_CloseKafra(int fd, struct map_session_data *sd) { + nullpo_retv(sd); + + if (sd->state.storage_flag) + storage_guild_storageclose(sd); + else + storage_storageclose(sd); +} + +/*========================================== + * パーティを作る + *------------------------------------------ + */ +void clif_parse_CreateParty(int fd, struct map_session_data *sd) { + if (battle_config.basic_skill_check == 0 || pc_checkskill(sd,NV_BASIC) >= 7) { + party_create(sd,RFIFOP(fd,2)); + } else + clif_skill_fail(sd,1,0,4); +} + +/*========================================== + * パーティを作る + *------------------------------------------ + */ +void clif_parse_CreateParty2(int fd, struct map_session_data *sd) { + if (battle_config.basic_skill_check == 0 || pc_checkskill(sd,NV_BASIC) >= 7){ + party_create(sd, RFIFOP(fd,2)); + } else + clif_skill_fail(sd, 1, 0, 4); +} + +/*========================================== + * パーティに勧誘 + *------------------------------------------ + */ +void clif_parse_PartyInvite(int fd, struct map_session_data *sd) { + party_invite(sd, RFIFOL(fd,2)); +} + +/*========================================== + * パーティ勧誘返答 + *------------------------------------------ + */ +void clif_parse_ReplyPartyInvite(int fd,struct map_session_data *sd) { + if(battle_config.basic_skill_check == 0 || pc_checkskill(sd,NV_BASIC) >= 5){ + party_reply_invite(sd,RFIFOL(fd,2),RFIFOL(fd,6)); + } else { + party_reply_invite(sd,RFIFOL(fd,2),-1); + clif_skill_fail(sd,1,0,4); + } +} + +/*========================================== + * パーティ脱退要求 + *------------------------------------------ + */ +void clif_parse_LeaveParty(int fd, struct map_session_data *sd) { + party_leave(sd); +} + +/*========================================== + * パーティ除名要求 + *------------------------------------------ + */ +void clif_parse_RemovePartyMember(int fd, struct map_session_data *sd) { + party_removemember(sd,RFIFOL(fd,2),RFIFOP(fd,6)); +} + +/*========================================== + * パーティ設定変更要求 + *------------------------------------------ + */ +void clif_parse_PartyChangeOption(int fd, struct map_session_data *sd) { + party_changeoption(sd, RFIFOW(fd,2), RFIFOW(fd,4)); +} + +/*========================================== + * パーティメッセージ送信要求 + *------------------------------------------ + */ +void clif_parse_PartyMessage(int fd, struct map_session_data *sd) { + nullpo_retv(sd); + + if (is_atcommand(fd, sd, RFIFOP(fd,4), 0) != AtCommand_None) + return; + if(sd->sc_data && + (sd->sc_data[SC_BERSERK].timer!=-1 || //バーサーク時は会話も不可 + sd->sc_data[SC_NOCHAT].timer!=-1)) //チャット禁止 + return; + + party_send_message(sd, RFIFOP(fd,4), RFIFOW(fd,2)-4); +} + +/*========================================== + * 露店閉鎖 + *------------------------------------------ + */ +void clif_parse_CloseVending(int fd, struct map_session_data *sd) { + vending_closevending(sd); +} + +/*========================================== + * 露店アイテムリスト要求 + *------------------------------------------ + */ +void clif_parse_VendingListReq(int fd, struct map_session_data *sd) { + nullpo_retv(sd); + + vending_vendinglistreq(sd,RFIFOL(fd,2)); + if(sd->npc_id) + npc_event_dequeue(sd); +} + +/*========================================== + * 露店アイテム購入 + *------------------------------------------ + */ +void clif_parse_PurchaseReq(int fd, struct map_session_data *sd) { + vending_purchasereq(sd, RFIFOW(fd,2), RFIFOL(fd,4), RFIFOP(fd,8)); +} + +/*========================================== + * 露店開設 + *------------------------------------------ + */ +void clif_parse_OpenVending(int fd,struct map_session_data *sd) { + vending_openvending(sd, RFIFOW(fd,2), RFIFOP(fd,4), RFIFOB(fd,84), RFIFOP(fd,85)); +} + +/*========================================== + * /monster /item rewriten by [Yor] + *------------------------------------------ + */ +void clif_parse_GM_Monster_Item(int fd, struct map_session_data *sd) { + char monster_item_name[25]; + + nullpo_retv(sd); + + memset(monster_item_name, '\0', sizeof(monster_item_name)); + + if (battle_config.atc_gmonly == 0 || pc_isGM(sd)) { + memcpy(monster_item_name, RFIFOP(fd,2), 24); + + if (mobdb_searchname(monster_item_name) != 0) { + if (pc_isGM(sd) >= get_atcommand_level(AtCommand_Monster)) + atcommand_spawn(fd, sd, "@spawn", monster_item_name); // as @spawn + } else if (itemdb_searchname(monster_item_name) != NULL) { + if (pc_isGM(sd) >= get_atcommand_level(AtCommand_Item)) + atcommand_item(fd, sd, "@item", monster_item_name); // as @item + } + + } +} + +/*========================================== + * ギルドを作る + *------------------------------------------ + */ +void clif_parse_CreateGuild(int fd,struct map_session_data *sd) { + guild_create(sd, RFIFOP(fd,6)); +} + +/*========================================== + * ギルドマスターかどうか確認 + *------------------------------------------ + */ +void clif_parse_GuildCheckMaster(int fd, struct map_session_data *sd) { + clif_guild_masterormember(sd); +} + +/*========================================== + * ギルド情報要求 + *------------------------------------------ + */ +void clif_parse_GuildReqeustInfo(int fd, struct map_session_data *sd) { + switch(RFIFOL(fd,2)){ + case 0: // ギルド基本情報、同盟敵対情報 + clif_guild_basicinfo(sd); + clif_guild_allianceinfo(sd); + break; + case 1: // メンバーリスト、役職名リスト + clif_guild_positionnamelist(sd); + clif_guild_memberlist(sd); + break; + case 2: // 役職名リスト、役職情報リスト + clif_guild_positionnamelist(sd); + clif_guild_positioninfolist(sd); + break; + case 3: // スキルリスト + clif_guild_skillinfo(sd); + break; + case 4: // 追放リスト + clif_guild_explusionlist(sd); + break; + default: + if (battle_config.error_log) + printf("clif: guild request info: unknown type %d\n", RFIFOL(fd,2)); + break; + } +} + +/*========================================== + * ギルド役職変更 + *------------------------------------------ + */ +void clif_parse_GuildChangePositionInfo(int fd, struct map_session_data *sd) { + int i; + + for(i = 4; i < RFIFOW(fd,2); i += 40 ){ + guild_change_position(sd, RFIFOL(fd,i), RFIFOL(fd,i+4), RFIFOL(fd,i+12), RFIFOP(fd,i+16)); + } +} + +/*========================================== + * ギルドメンバ役職変更 + *------------------------------------------ + */ +void clif_parse_GuildChangeMemberPosition(int fd, struct map_session_data *sd) { + int i; + + nullpo_retv(sd); + + for(i=4;i<RFIFOW(fd,2);i+=12){ + guild_change_memberposition(sd->status.guild_id, + RFIFOL(fd,i),RFIFOL(fd,i+4),RFIFOL(fd,i+8)); + } +} + +/*========================================== + * ギルドエンブレム要求 + *------------------------------------------ + */ +void clif_parse_GuildRequestEmblem(int fd,struct map_session_data *sd) { + struct guild *g=guild_search(RFIFOL(fd,2)); + if(g!=NULL) + clif_guild_emblem(sd,g); +} + +/*========================================== + * ギルドエンブレム変更 + *------------------------------------------ + */ +void clif_parse_GuildChangeEmblem(int fd,struct map_session_data *sd) { + guild_change_emblem(sd,RFIFOW(fd,2)-4,RFIFOP(fd,4)); +} + +/*========================================== + * ギルド告知変更 + *------------------------------------------ + */ +void clif_parse_GuildChangeNotice(int fd,struct map_session_data *sd) { + guild_change_notice(sd,RFIFOL(fd,2),RFIFOP(fd,6),RFIFOP(fd,66)); +} + +/*========================================== + * ギルド勧誘 + *------------------------------------------ + */ +void clif_parse_GuildInvite(int fd,struct map_session_data *sd) { + guild_invite(sd,RFIFOL(fd,2)); +} + +/*========================================== + * ギルド勧誘返信 + *------------------------------------------ + */ +void clif_parse_GuildReplyInvite(int fd,struct map_session_data *sd) { + guild_reply_invite(sd,RFIFOL(fd,2),RFIFOB(fd,6)); +} + +/*========================================== + * ギルド脱退 + *------------------------------------------ + */ +void clif_parse_GuildLeave(int fd,struct map_session_data *sd) { + guild_leave(sd,RFIFOL(fd,2),RFIFOL(fd,6),RFIFOL(fd,10),RFIFOP(fd,14)); +} + +/*========================================== + * ギルド追放 + *------------------------------------------ + */ +void clif_parse_GuildExplusion(int fd,struct map_session_data *sd) { + guild_explusion(sd,RFIFOL(fd,2),RFIFOL(fd,6),RFIFOL(fd,10),RFIFOP(fd,14)); +} + +/*========================================== + * ギルド会話 + *------------------------------------------ + */ +void clif_parse_GuildMessage(int fd,struct map_session_data *sd) { + nullpo_retv(sd); + + if (is_atcommand(fd, sd, RFIFOP(fd, 4), 0) != AtCommand_None) + return; + if(sd->sc_data && + (sd->sc_data[SC_BERSERK].timer!=-1 || //バーサーク時は会話も不可 + sd->sc_data[SC_NOCHAT].timer!=-1)) //チャット禁止 + return; + + guild_send_message(sd, RFIFOP(fd,4), RFIFOW(fd,2)-4); +} + +/*========================================== + * ギルド同盟要求 + *------------------------------------------ + */ +void clif_parse_GuildRequestAlliance(int fd, struct map_session_data *sd) { + guild_reqalliance(sd,RFIFOL(fd,2)); +} + +/*========================================== + * ギルド同盟要求返信 + *------------------------------------------ + */ +void clif_parse_GuildReplyAlliance(int fd, struct map_session_data *sd) { + guild_reply_reqalliance(sd,RFIFOL(fd,2),RFIFOL(fd,6)); +} + +/*========================================== + * ギルド関係解消 + *------------------------------------------ + */ +void clif_parse_GuildDelAlliance(int fd, struct map_session_data *sd) { + guild_delalliance(sd,RFIFOL(fd,2),RFIFOL(fd,6)); +} + +/*========================================== + * ギルド敵対 + *------------------------------------------ + */ +void clif_parse_GuildOpposition(int fd, struct map_session_data *sd) { + guild_opposition(sd,RFIFOL(fd,2)); +} + +/*========================================== + * ギルド解散 + *------------------------------------------ + */ +void clif_parse_GuildBreak(int fd, struct map_session_data *sd) { + guild_break(sd,RFIFOP(fd,2)); +} + +// pet +void clif_parse_PetMenu(int fd, struct map_session_data *sd) { + pet_menu(sd,RFIFOB(fd,2)); +} + +void clif_parse_CatchPet(int fd, struct map_session_data *sd) { + pet_catch_process2(sd,RFIFOL(fd,2)); +} + +void clif_parse_SelectEgg(int fd, struct map_session_data *sd) { + pet_select_egg(sd,RFIFOW(fd,2)-2); +} + +void clif_parse_SendEmotion(int fd, struct map_session_data *sd) { + nullpo_retv(sd); + + if(sd->pd) + clif_pet_emotion(sd->pd,RFIFOL(fd,2)); +} + +void clif_parse_ChangePetName(int fd, struct map_session_data *sd) { + pet_change_name(sd,RFIFOP(fd,2)); +} + +// Kick (right click menu for GM "(name) force to quit") +void clif_parse_GMKick(int fd, struct map_session_data *sd) { + struct block_list *target; + int tid = RFIFOL(fd,2); + + nullpo_retv(sd); + + if ((battle_config.atc_gmonly == 0 || pc_isGM(sd)) && + (pc_isGM(sd) >= get_atcommand_level(AtCommand_Kick))) { + target = map_id2bl(tid); + if (target) { + if (target->type == BL_PC) { + struct map_session_data *tsd = (struct map_session_data *)target; + if (pc_isGM(sd) > pc_isGM(tsd)) + clif_GM_kick(sd, tsd, 1); + else + clif_GM_kickack(sd, 0); + } else if (target->type == BL_MOB) { + struct mob_data *md = (struct mob_data *)target; + sd->state.attack_type = 0; + mob_damage(&sd->bl, md, md->hp, 2); + } else + clif_GM_kickack(sd, 0); + } else + clif_GM_kickack(sd, 0); + } +} + +/*========================================== + * /shift + *------------------------------------------ + */ +void clif_parse_Shift(int fd, struct map_session_data *sd) { // Rewriten by [Yor] + char player_name[25]; + + nullpo_retv(sd); + + memset(player_name, '\0', sizeof(player_name)); + + if ((battle_config.atc_gmonly == 0 || pc_isGM(sd)) && + (pc_isGM(sd) >= get_atcommand_level(AtCommand_JumpTo))) { + memcpy(player_name, RFIFOP(fd,2), 24); + atcommand_jumpto(fd, sd, "@jumpto", player_name); // as @jumpto + } + + return; +} + +/*========================================== + * /recall + *------------------------------------------ + */ +void clif_parse_Recall(int fd, struct map_session_data *sd) { // Added by RoVeRT + char player_name[25]; + + nullpo_retv(sd); + + memset(player_name, '\0', sizeof(player_name)); + + if ((battle_config.atc_gmonly == 0 || pc_isGM(sd)) && + (pc_isGM(sd) >= get_atcommand_level(AtCommand_Recall))) { + memcpy(player_name, RFIFOP(fd,2), 24); + atcommand_recall(fd, sd, "@recall", player_name); // as @recall + } + + return; +} + +void clif_parse_GMHide(int fd, struct map_session_data *sd) { // Modified by [Yor] + nullpo_retv(sd); + + //printf("%2x %2x %2x\n", RFIFOW(fd,0), RFIFOW(fd,2), RFIFOW(fd,4)); // R 019d <Option_value>.2B <flag>.2B + if ((battle_config.atc_gmonly == 0 || pc_isGM(sd)) && + (pc_isGM(sd) >= get_atcommand_level(AtCommand_Hide))) { + if (sd->status.option & OPTION_HIDE) { // OPTION_HIDE = 0x40 + sd->status.option &= ~OPTION_HIDE; // OPTION_HIDE = 0x40 + clif_displaymessage(fd, "Invisible: Off."); + } else { + sd->status.option |= OPTION_HIDE; // OPTION_HIDE = 0x40 + clif_displaymessage(fd, "Invisible: On."); + } + clif_changeoption(&sd->bl); + } +} + +/*========================================== + * GMによるチャット禁止時間付与 + *------------------------------------------ + */ +void clif_parse_GMReqNoChat(int fd,struct map_session_data *sd) +{ + int tid = RFIFOL(fd,2); + int type = RFIFOB(fd,6); + int limit = RFIFOW(fd,7); + struct block_list *bl = map_id2bl(tid); + struct map_session_data *dstsd; + int dstfd; + + nullpo_retv(sd); + + if(!battle_config.muting_players) { + clif_displaymessage(fd, "Muting is disabled."); + return; + } + + if(type == 0) + limit = 0 - limit; + if(bl->type == BL_PC && (dstsd =(struct map_session_data *)bl)){ + if((tid == bl->id && type == 2 && !pc_isGM(sd)) || (pc_isGM(sd) > pc_isGM(dstsd)) ){ + dstfd = dstsd->fd; + WFIFOW(dstfd,0)=0x14b; + WFIFOB(dstfd,2)=(type==2)?1:type; + memcpy(WFIFOP(dstfd,3),sd->status.name,24); + WFIFOSET(dstfd,packet_len_table[0x14b]); + dstsd->status.manner -= limit; + if(dstsd->status.manner < 0) + skill_status_change_start(bl,SC_NOCHAT,0,0,0,0,0,0); + else{ + dstsd->status.manner = 0; + skill_status_change_end(bl,SC_NOCHAT,-1); + } + printf("name:%s type:%d limit:%d manner:%d\n",dstsd->status.name,type,limit,dstsd->status.manner); + } + } + + return; +} +/*========================================== + * GMによるチャット禁止時間参照(?) + *------------------------------------------ + */ +void clif_parse_GMReqNoChatCount(int fd,struct map_session_data *sd) +{ + int tid = RFIFOL(fd,2); + + WFIFOW(fd,0)=0x1e0; + WFIFOL(fd,2)=tid; + sprintf(WFIFOP(fd,6),"%d",tid); +// memcpy(WFIFOP(fd,6),"TESTNAME",24); + WFIFOSET(fd,packet_len_table[0x1e0]); + + return; +} + +void clif_parse_PMIgnore(int fd, struct map_session_data *sd) { // Rewritten by [Yor] + char output[1024]; + char *nick; // S 00cf <nick>.24B <type>.B: 00 (/ex nick) deny speech from nick, 01 (/in nick) allow speech from nick + int i; + int pos; + + memset(output, '\0', sizeof(output)); + + nick = RFIFOP(fd,2); // speed up + //printf("Ignore: char '%s' state: %d\n", nick, RFIFOB(fd,26)); + // we ask for deny (we add nick only if it's not already exist + if (RFIFOB(fd,26) == 0) { // type + if (strlen(nick) >= 4 && strlen(nick) < 24) { // do something only if nick can be exist + pos = -1; + for(i = 0; i < (sizeof(sd->ignore) / sizeof(sd->ignore[0])); i++) { + if (strcmp(sd->ignore[i].name, nick) == 0) + break; + else if (pos == -1 && sd->ignore[i].name[0] == '\0') + pos = i; + } + WFIFOW(fd,0) = 0x0d1; // R 00d1 <type>.B <fail>.B: type: 0: deny, 1: allow, fail: 0: success, 1: fail + WFIFOB(fd,2) = 0; + // if a position is found and name not found, we add it in the list + if (pos != -1 && i == (sizeof(sd->ignore) / sizeof(sd->ignore[0]))) { + memcpy(sd->ignore[pos].name, nick, 24); + WFIFOB(fd,3) = 0; // success + WFIFOSET(fd, packet_len_table[0x0d1]); + if (strcmp(wisp_server_name, nick) == 0) { // to found possible bot users that automaticaly ignores people. + sprintf(output, "Character '%s' (account: %d) has tried to block wisps from '%s' (wisp name of the server). Bot user?", sd->status.name, sd->status.account_id, wisp_server_name); + intif_wis_message_to_gm(wisp_server_name, battle_config.hack_info_GM_level, output, strlen(output) + 1); + // send something to be inform and force bot to ignore twice... If GM receiving block + block again, it's a bot :) + clif_wis_message(fd, wisp_server_name, "Add me in your ignore list, doesn't block my wisps.", strlen("Add me in your ignore list, doesn't block my wisps.") + 1); + } + } else { + WFIFOB(fd,3) = 1; // fail + if (i == (sizeof(sd->ignore) / sizeof(sd->ignore[0]))) { + clif_wis_message(fd, wisp_server_name, "You can not block more people.", strlen("You can not block more people.") + 1); + if (strcmp(wisp_server_name, nick) == 0) { // to found possible bot users that automaticaly ignores people. + sprintf(output, "Character '%s' (account: %d) has tried to block wisps from '%s' (wisp name of the server). Bot user?", sd->status.name, sd->status.account_id, wisp_server_name); + intif_wis_message_to_gm(wisp_server_name, battle_config.hack_info_GM_level, output, strlen(output) + 1); + } + } else { + clif_wis_message(fd, wisp_server_name, "This player is already blocked.", strlen("This player is already blocked.") + 1); + if (strcmp(wisp_server_name, nick) == 0) { // to found possible bot users that automaticaly ignores people. + sprintf(output, "Character '%s' (account: %d) has tried AGAIN to block wisps from '%s' (wisp name of the server). Bot user?", sd->status.name, sd->status.account_id, wisp_server_name); + intif_wis_message_to_gm(wisp_server_name, battle_config.hack_info_GM_level, output, strlen(output) + 1); + } + } + } + } else + clif_wis_message(fd, wisp_server_name, "It's impossible to block this player.", strlen("It's impossible to block this player.") + 1); + // we ask for allow (we remove all same nick if exist) + } else { + if (strlen(nick) >= 4 && strlen(nick) < 24) { // do something only if nick can be exist + WFIFOW(fd,0) = 0x0d1; // R 00d1 <type>.B <fail>.B: type: 0: deny, 1: allow, fail: 0: success, 1: fail + WFIFOB(fd,2) = 1; + for(i = 0; i < (sizeof(sd->ignore) / sizeof(sd->ignore[0])); i++) + if (strcmp(sd->ignore[i].name, nick) == 0) { + memset(sd->ignore[i].name, 0, sizeof(sd->ignore[i].name)); + WFIFOB(fd,3) = 0; // success + WFIFOSET(fd, packet_len_table[0x0d1]); + break; + } + if (i == (sizeof(sd->ignore) / sizeof(sd->ignore[0]))) { + WFIFOB(fd,3) = 1; // fail + WFIFOSET(fd, packet_len_table[0x0d1]); + clif_wis_message(fd, wisp_server_name, "This player is not blocked by you.", strlen("This player is not blocked by you.") + 1); + } + } else + clif_wis_message(fd, wisp_server_name, "It's impossible to unblock this player.", strlen("It's impossible to unblock this player.") + 1); + } + +// for(i = 0; i < (sizeof(sd->ignore) / sizeof(sd->ignore[0])); i++) // for debug only +// if (sd->ignore[i].name[0] != '\0') +// printf("Ignored player: '%s'\n", sd->ignore[i].name); + + return; +} + +void clif_parse_PMIgnoreAll(int fd, struct map_session_data *sd) { // Rewritten by [Yor] + //printf("Ignore all: state: %d\n", RFIFOB(fd,2)); + if (RFIFOB(fd,2) == 0) {// S 00d0 <type>len.B: 00 (/exall) deny all speech, 01 (/inall) allow all speech + WFIFOW(fd,0) = 0x0d2; // R 00d2 <type>.B <fail>.B: type: 0: deny, 1: allow, fail: 0: success, 1: fail + WFIFOB(fd,2) = 0; + if (sd->ignoreAll == 0) { + sd->ignoreAll = 1; + WFIFOB(fd,3) = 0; // success + WFIFOSET(fd, packet_len_table[0x0d2]); + } else { + WFIFOB(fd,3) = 1; // fail + WFIFOSET(fd, packet_len_table[0x0d2]); + clif_wis_message(fd, wisp_server_name, "You already block everyone.", strlen("You already block everyone.") + 1); + } + } else { + WFIFOW(fd,0) = 0x0d2; // R 00d2 <type>.B <fail>.B: type: 0: deny, 1: allow, fail: 0: success, 1: fail + WFIFOB(fd,2) = 1; + if (sd->ignoreAll == 1) { + sd->ignoreAll = 0; + WFIFOB(fd,3) = 0; // success + WFIFOSET(fd, packet_len_table[0x0d2]); + } else { + WFIFOB(fd,3) = 1; // fail + WFIFOSET(fd, packet_len_table[0x0d2]); + clif_wis_message(fd, wisp_server_name, "You already allow everyone.", strlen("You already allow everyone.") + 1); + } + } + + return; +} + +void clif_parse_skillMessage(int fd, struct map_session_data *sd) { // Added by RoVeRT + int skillid,skilllv, x, y; + char *mes; + + skilllv = RFIFOW(fd,2); + skillid = RFIFOW(fd,4); + + y = RFIFOB(fd,6); + x = RFIFOB(fd,8); + + mes = RFIFOP(fd,10); + + // skill 220 = graffiti +// printf("skill: %d %d location: %3d %3d message: %s\n", skillid, skilllv, x, y, (char*)mes); +} + +int monk(struct map_session_data *sd, struct block_list *target, int type) { +//R 01d1 <Monk id>L <Target monster id>L <Bool>L + int fd=sd->fd; + WFIFOW(fd,0)=0x1d1; + WFIFOL(fd,2)=sd->bl.id; + WFIFOL(fd,6)=target->id; + WFIFOL(fd,10)=type; + WFIFOSET(fd,packet_len_table[0x1d1]); + + return 0; +} + +/*========================================== + * スパノビの/doridoriによるSPR2倍 + *------------------------------------------ + */ +void clif_parse_sn_doridori(int fd, struct map_session_data *sd) { + if (sd) + sd->doridori_counter = 1; + + return; +} +/*========================================== + * スパノビの爆裂波動 + *------------------------------------------ + */ +void clif_parse_sn_explosionspirits(int fd, struct map_session_data *sd) +{ + if(sd){ + int nextbaseexp=pc_nextbaseexp(sd); + struct pc_base_job s_class = pc_calc_base_job(sd->status.class); + if (battle_config.etc_log){ + if(nextbaseexp != 0) + printf("SuperNovice explosionspirits!! %d %d %d %d\n",sd->bl.id,s_class.job,sd->status.base_exp,(int)((double)1000*sd->status.base_exp/nextbaseexp)); + else + printf("SuperNovice explosionspirits!! %d %d %d 000\n",sd->bl.id,s_class.job,sd->status.base_exp); + } + if(s_class.job == 23 && sd->status.base_exp > 0 && nextbaseexp > 0 && (int)((double)1000*sd->status.base_exp/nextbaseexp)%100==0){ + clif_skill_nodamage(&sd->bl,&sd->bl,MO_EXPLOSIONSPIRITS,5,1); + skill_status_change_start(&sd->bl,SkillStatusChangeTable[MO_EXPLOSIONSPIRITS],5,0,0,0,skill_get_time(MO_EXPLOSIONSPIRITS,5),0 ); + } + } + return; +} + +// functions list +static void (*clif_parse_func_table[4][0x220])() = { + { + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + + // 40 + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + + // 70 + NULL, NULL, clif_parse_WantToConnection, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, clif_parse_LoadEndAck, clif_parse_TickSend, NULL, + + // 80 + NULL, NULL, NULL, NULL, NULL, clif_parse_WalkToXY, NULL, NULL, + NULL, clif_parse_ActionRequest, NULL, NULL, clif_parse_GlobalMessage, NULL, NULL, NULL, + // 90 + clif_parse_NpcClicked, NULL, NULL, NULL, clif_parse_GetCharNameRequest, NULL, clif_parse_Wis, NULL, + NULL, clif_parse_GMmessage, NULL, clif_parse_ChangeDir, NULL, NULL, NULL, clif_parse_TakeItem, + // a0 + NULL, NULL, clif_parse_DropItem, NULL, NULL, NULL, NULL, clif_parse_UseItem, + NULL, clif_parse_EquipItem, NULL, clif_parse_UnequipItem, NULL, NULL, NULL, NULL, + // b0 + NULL, NULL, clif_parse_Restart, NULL, NULL, NULL, NULL, NULL, + clif_parse_NpcSelectMenu, clif_parse_NpcNextClicked, NULL, clif_parse_StatusUp, NULL, NULL, NULL, clif_parse_Emotion, + + // c0 + NULL, clif_parse_HowManyConnections, NULL, NULL, NULL, clif_parse_NpcBuySellSelected, NULL, NULL, + clif_parse_NpcBuyListSend, clif_parse_NpcSellListSend, NULL, NULL, clif_parse_GMKick, NULL, NULL, clif_parse_PMIgnore, + // d0 + clif_parse_PMIgnoreAll, NULL, NULL, NULL, NULL, clif_parse_CreateChatRoom, NULL, NULL, + NULL, clif_parse_ChatAddMember, NULL, NULL, NULL, NULL, clif_parse_ChatRoomStatusChange, NULL, + // e0 + clif_parse_ChangeChatOwner, NULL, clif_parse_KickFromChat, clif_parse_ChatLeave, clif_parse_TradeRequest, NULL, clif_parse_TradeAck, NULL, + clif_parse_TradeAddItem, NULL, NULL, clif_parse_TradeOk, NULL, clif_parse_TradeCansel, NULL, clif_parse_TradeCommit, + // f0 + NULL, NULL, NULL, clif_parse_MoveToKafra, NULL, clif_parse_MoveFromKafra, NULL, clif_parse_CloseKafra, + NULL, clif_parse_CreateParty, NULL, NULL, clif_parse_PartyInvite, NULL, NULL, clif_parse_ReplyPartyInvite, + + // 100 + clif_parse_LeaveParty, NULL, clif_parse_PartyChangeOption, clif_parse_RemovePartyMember, NULL, NULL, NULL, NULL, + clif_parse_PartyMessage, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + // 110 + NULL, NULL, clif_parse_SkillUp, clif_parse_UseSkillToId, NULL, NULL, clif_parse_UseSkillToPos, NULL, + clif_parse_StopAttack, NULL, NULL, clif_parse_UseSkillMap, NULL, clif_parse_RequestMemo, NULL, NULL, + // 120 + NULL, NULL, NULL, NULL, NULL, NULL, clif_parse_PutItemToCart, clif_parse_GetItemFromCart, + clif_parse_MoveFromKafraToCart, clif_parse_MoveToKafraFromCart, clif_parse_RemoveOption, NULL, NULL, NULL, clif_parse_CloseVending, NULL, + // 130 + clif_parse_VendingListReq, NULL, NULL, NULL, clif_parse_PurchaseReq, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, clif_parse_GM_Monster_Item, + + // 140 + clif_parse_MapMove, NULL, NULL, clif_parse_NpcAmountInput, NULL, NULL, clif_parse_NpcCloseClicked, NULL, + NULL, clif_parse_GMReqNoChat, NULL, NULL, NULL, clif_parse_GuildCheckMaster, NULL, clif_parse_GuildReqeustInfo, + // 150 + NULL, clif_parse_GuildRequestEmblem, NULL, clif_parse_GuildChangeEmblem, NULL, clif_parse_GuildChangeMemberPosition, NULL, NULL, + NULL, clif_parse_GuildLeave, NULL, clif_parse_GuildExplusion, NULL, clif_parse_GuildBreak, NULL, NULL, + // 160 + NULL, clif_parse_GuildChangePositionInfo, NULL, NULL, NULL, clif_parse_CreateGuild, NULL, NULL, + clif_parse_GuildInvite, NULL, NULL, clif_parse_GuildReplyInvite, NULL, NULL, clif_parse_GuildChangeNotice, NULL, + // 170 + clif_parse_GuildRequestAlliance, NULL, clif_parse_GuildReplyAlliance, NULL, NULL, NULL, NULL, NULL, + clif_parse_ItemIdentify, NULL, clif_parse_UseCard, NULL, clif_parse_InsertCard, NULL, clif_parse_GuildMessage, NULL, + + // 180 + clif_parse_GuildOpposition, NULL, NULL, clif_parse_GuildDelAlliance, NULL, NULL, NULL, NULL, + NULL, NULL, clif_parse_QuitGame, NULL, NULL, NULL, clif_parse_ProduceMix, NULL, + // 190 + clif_parse_UseSkillToPos, NULL, NULL, clif_parse_SolveCharName, NULL, NULL, NULL, clif_parse_ResetChar, + NULL, NULL, NULL, NULL, clif_parse_LGMmessage, clif_parse_GMHide, NULL, clif_parse_CatchPet, + // 1a0 + NULL, clif_parse_PetMenu, NULL, NULL, NULL, clif_parse_ChangePetName, NULL, clif_parse_SelectEgg, + NULL, clif_parse_SendEmotion, NULL, NULL, NULL, NULL, clif_parse_SelectArrow, clif_parse_ChangeCart, + // 1b0 + NULL, NULL, clif_parse_OpenVending, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, clif_parse_Shift, clif_parse_Shift, clif_parse_Recall, clif_parse_Recall, NULL, NULL, + + // 1c0 + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, clif_parse_AutoSpell, + NULL, + // 1d0 + NULL, NULL, NULL, NULL, NULL, clif_parse_NpcStringInput, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, clif_parse_GMReqNoChatCount, + // 1e0 + NULL, NULL, NULL, NULL, NULL, NULL, NULL, clif_parse_sn_doridori, + clif_parse_CreateParty2, NULL, NULL, NULL, NULL, clif_parse_sn_explosionspirits, NULL, NULL, + // 1f0 + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + + // 200 + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + // 210 + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, +#if 0 + case 0xce: clif_parse_GMkillall + case 0xd3: clif_parse_IgnoreList +#endif + }, + {NULL}, + {NULL}, + {NULL} +}; + +/*========================================== + * クライアントからのパケット解析 + * socket.cのdo_parsepacketから呼び出される + *------------------------------------------ + */ +static int clif_parse(int fd) { + int packet_len = 0, cmd=0, packet_ver =0; + struct map_session_data *sd=NULL; + + sd = session[fd]->session_data; + + // 接続が切れてるので後始末 + if (!chrif_isconnect() || session[fd]->eof) { // char鯖に繋がってない間は接続禁止 (!chrif_isconnect()) + if (sd && sd->state.auth) { + clif_quitsave(fd, sd); + if (sd->status.name != NULL) + printf("Player [%s] has logged off your server.\n", sd->status.name); // Player logout display [Valaris] + else + printf("Player with account [%d] has logged off your server.\n", sd->bl.id); // Player logout display [Yor] + } else if (sd) { // not authentified! (refused by char-server or disconnect before to be authentified) + printf("Player with account [%d] has logged off your server (not auth account).\n", sd->bl.id); // Player logout display [Yor] + map_deliddb(&sd->bl); // account_id has been included in the DB before auth answer + } + if(fd) close(fd); + if(fd) delete_session(fd); + return 0; + } + + if (RFIFOREST(fd) < 2) + return 0; + + cmd = RFIFOW(fd,0); + + // 管理用パケット処理 + if (cmd >= 30000) { + switch(cmd) { + case 0x7530: // Athena情報所得 + WFIFOW(fd,0) = 0x7531; + WFIFOB(fd,2) = ATHENA_MAJOR_VERSION; + WFIFOB(fd,3) = ATHENA_MINOR_VERSION; + WFIFOB(fd,4) = ATHENA_REVISION; + WFIFOB(fd,5) = ATHENA_RELEASE_FLAG; + WFIFOB(fd,6) = ATHENA_OFFICIAL_FLAG; + WFIFOB(fd,7) = ATHENA_SERVER_MAP; + WFIFOW(fd,8) = ATHENA_MOD_VERSION; + WFIFOSET(fd,10); + RFIFOSKIP(fd,2); + break; + case 0x7532: // 接続の切断 + session[fd]->eof = 1; + break; + } + return 0; + } + + // get packet version before to parse + packet_ver = 0; + if (sd) + packet_ver = sd->packet_ver; + // check authentification packet to know packet version + else { + // 0x72 + if (cmd == 0x72) { + if (RFIFOREST(fd) >= 39 && (RFIFOB(fd,38) == 0 || RFIFOB(fd,38) == 1)) // 00 = Female, 01 = Male + packet_ver = 7; // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + else if (RFIFOREST(fd) >= 22 && (RFIFOB(fd,21) == 0 || RFIFOB(fd,21) == 1)) // 00 = Female, 01 = Male + packet_ver = 6; // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + else if (RFIFOREST(fd) >= 19 && (RFIFOB(fd,18) == 0 || RFIFOB(fd,18) == 1)) // 00 = Female, 01 = Male + packet_ver = 5; // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + // else probably incomplete packet + else if (RFIFOREST(fd) < 19) + return 0; + // 0x7E + } else if (cmd == 0x7E) { + if (RFIFOREST(fd) >= 37 && (RFIFOB(fd,36) == 0 || RFIFOB(fd,36) == 1)) // 00 = Female, 01 = Male + packet_ver = 9; // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + else if (RFIFOREST(fd) >= 33 && (RFIFOB(fd,32) == 0 || RFIFOB(fd,32) == 1)) // 00 = Female, 01 = Male + packet_ver = 8; // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + // else probably incomplete packet + else if (RFIFOREST(fd) < 33) + return 0; + // 0xF5 + } else { + if (RFIFOREST(fd) >= 34 && (RFIFOB(fd,33) == 0 || RFIFOB(fd,33) == 1)) // 00 = Female, 01 = Male + packet_ver = 10; // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + // else probably incomplete packet + else if (RFIFOREST(fd) < 34) + return 0; + } + // check if version is accepted + if ((packet_ver == 5 && (battle_config.packet_ver_flag & 1) == 0) || + (packet_ver == 6 && (battle_config.packet_ver_flag & 2) == 0) || + (packet_ver == 7 && (battle_config.packet_ver_flag & 4) == 0) || + (packet_ver == 8 && (battle_config.packet_ver_flag & 8) == 0) || + (packet_ver == 9 && (battle_config.packet_ver_flag & 16) == 0) || + (packet_ver == 10 && (battle_config.packet_ver_flag & 32) == 0)) { + WFIFOW(fd,0) = 0x6a; + WFIFOB(fd,2) = 5; // 05 = Game's EXE is not the latest version + WFIFOSET(fd,23); + session[fd]->eof = 1; + return 0; + } + } + + // ゲーム用以外パケットか、認証を終える前に0072以外が来たら、切断する + if (packet_ver < 5 || packet_ver > 10 || // if packet is not inside these values: session is incorrect?? or auth packet is unknown + cmd >= 0x220 || packet_size_table[packet_ver-5][cmd] == 0) { + if (!fd) + return 0; + session[fd]->eof = 1; + printf("clif_parse: session #%d, packet 0x%x (%d bytes received) -> disconnected.\n", fd, cmd, RFIFOREST(fd)); + return 0; + } + + // パケット長を計算 + packet_len = packet_size_table[packet_ver-5][cmd]; + if (packet_len == -1) { + if (RFIFOREST(fd) < 4) + return 0; // 可変長パケットで長さの所までデータが来てない + packet_len = RFIFOW(fd,2); + if (packet_len < 4 || packet_len > 32768) { + session[fd]->eof = 1; + return 0; + } + } + if (RFIFOREST(fd) < packet_len) + return 0; // まだ1パケット分データが揃ってない + + if (sd && sd->state.auth == 1 && sd->state.waitingdisconnect == 1) { // 切断待ちの場合パケットを処理しない + + } else if (packet_ver < 8 && clif_parse_func_table[0][cmd]) { // packet version 5-6-7 use same functions, but size are different + // パケット処理 + clif_parse_func_table[0][cmd](fd, sd); + } else if (packet_ver >= 8 && clif_parse_func_table[packet_ver - 7][cmd]) { + // パケット処理 + clif_parse_func_table[packet_ver - 7][cmd](fd, sd); + } else { + // 不明なパケット + if (battle_config.error_log) { + if (fd) + printf("\nclif_parse: session #%d, packet 0x%x, lenght %d\n", fd, cmd, packet_len); +#ifdef DUMP_UNKNOWN_PACKET + { + int i; + FILE *fp; + char packet_txt[256] = "save/packet.txt"; + time_t now; + printf("---- 00-01-02-03-04-05-06-07-08-09-0A-0B-0C-0D-0E-0F"); + for(i = 0; i < packet_len; i++) { + if ((i & 15) == 0) + printf("\n%04X ",i); + printf("%02X ", RFIFOB(fd,i)); + } + if (sd && sd->state.auth) { + if (sd->status.name != NULL) + printf("\nAccount ID %d, character ID %d, player name %s.\n", + sd->status.account_id, sd->status.char_id, sd->status.name); + else + printf("\nAccount ID %d.\n", sd->bl.id); + } else if (sd) // not authentified! (refused by char-server or disconnect before to be authentified) + printf("\nAccount ID %d.\n", sd->bl.id); + + if ((fp = fopen(packet_txt, "a")) == NULL) { + printf("clif.c: cant write [%s] !!! data is lost !!!\n", packet_txt); + return 1; + } else { + time(&now); + if (sd && sd->state.auth) { + if (sd->status.name != NULL) + fprintf(fp, "%sPlayer with account ID %d (character ID %d, player name %s) sent wrong packet:\n", + asctime(localtime(&now)), sd->status.account_id, sd->status.char_id, sd->status.name); + else + fprintf(fp, "%sPlayer with account ID %d sent wrong packet:\n", asctime(localtime(&now)), sd->bl.id); + } else if (sd) // not authentified! (refused by char-server or disconnect before to be authentified) + fprintf(fp, "%sPlayer with account ID %d sent wrong packet:\n", asctime(localtime(&now)), sd->bl.id); + + fprintf(fp, "\t---- 00-01-02-03-04-05-06-07-08-09-0A-0B-0C-0D-0E-0F"); + for(i = 0; i < packet_len; i++) { + if ((i & 15) == 0) + fprintf(fp, "\n\t%04X ", i); + fprintf(fp, "%02X ", RFIFOB(fd,i)); + } + fprintf(fp, "\n\n"); + fclose(fp); + } + } +#endif + } + } + RFIFOSKIP(fd, packet_len); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int do_init_clif(void) { + int i; + + // functions of packet version 5-6-7 are same, but size are different + // init packet function calls for packet ver 8 + memcpy(clif_parse_func_table[1], clif_parse_func_table[0], sizeof(clif_parse_func_table[0])); + clif_parse_func_table[1][0x072] = clif_parse_DropItem; + clif_parse_func_table[1][0x07e] = clif_parse_WantToConnection; + clif_parse_func_table[1][0x085] = clif_parse_UseSkillToId; + clif_parse_func_table[1][0x089] = clif_parse_GetCharNameRequest; + clif_parse_func_table[1][0x08c] = clif_parse_UseSkillToPos; + clif_parse_func_table[1][0x094] = clif_parse_TakeItem; + clif_parse_func_table[1][0x09b] = clif_parse_WalkToXY; + clif_parse_func_table[1][0x09f] = clif_parse_ChangeDir; + clif_parse_func_table[1][0x0a2] = clif_parse_UseSkillToPos; + clif_parse_func_table[1][0x0a7] = clif_parse_SolveCharName; + clif_parse_func_table[1][0x0f3] = clif_parse_GlobalMessage; + clif_parse_func_table[1][0x0f5] = clif_parse_UseItem; + clif_parse_func_table[1][0x0f7] = clif_parse_TickSend; + clif_parse_func_table[1][0x113] = clif_parse_MoveToKafra; + clif_parse_func_table[1][0x116] = clif_parse_CloseKafra; + clif_parse_func_table[1][0x190] = clif_parse_MoveFromKafra; + clif_parse_func_table[1][0x193] = clif_parse_ActionRequest; + // init packet function calls for packet ver 9 (same function of packet version 8, but size are different) + memcpy(clif_parse_func_table[2], clif_parse_func_table[1], sizeof(clif_parse_func_table[0])); + // init packet function calls for packet ver 10 + memcpy(clif_parse_func_table[3], clif_parse_func_table[2], sizeof(clif_parse_func_table[0])); + clif_parse_func_table[3][0x072] = clif_parse_UseItem; + clif_parse_func_table[3][0x07e] = clif_parse_MoveToKafra; + clif_parse_func_table[3][0x085] = clif_parse_ActionRequest; + clif_parse_func_table[3][0x089] = clif_parse_WalkToXY; + clif_parse_func_table[3][0x08c] = clif_parse_UseSkillToPos; + clif_parse_func_table[3][0x094] = clif_parse_DropItem; + clif_parse_func_table[3][0x09b] = clif_parse_GetCharNameRequest; + clif_parse_func_table[3][0x09f] = clif_parse_GlobalMessage; + clif_parse_func_table[3][0x0a2] = clif_parse_SolveCharName; + clif_parse_func_table[3][0x0a7] = clif_parse_UseSkillToPos; + clif_parse_func_table[3][0x0f3] = clif_parse_ChangeDir; + clif_parse_func_table[3][0x0f5] = clif_parse_WantToConnection; + clif_parse_func_table[3][0x0f7] = clif_parse_CloseKafra; + clif_parse_func_table[3][0x113] = clif_parse_TakeItem; + clif_parse_func_table[3][0x116] = clif_parse_TickSend; + clif_parse_func_table[3][0x190] = clif_parse_UseSkillToId; + clif_parse_func_table[3][0x193] = clif_parse_MoveFromKafra; + + // size of packet version 5 + memcpy(&packet_size_table[0], &packet_len_table, sizeof(packet_len_table)); + // size of packet version 6 + memcpy(&packet_size_table[1], &packet_size_table[0], sizeof(packet_len_table)); + packet_size_table[1][0x072] = 22; + packet_size_table[1][0x085] = 8; + packet_size_table[1][0x0a7] = 13; + packet_size_table[1][0x113] = 15; + packet_size_table[1][0x116] = 15; + packet_size_table[1][0x190] = 95; + // size of packet version 7 + memcpy(&packet_size_table[2], &packet_size_table[1], sizeof(packet_len_table)); + packet_size_table[2][0x072] = 39; + packet_size_table[2][0x085] = 9; + packet_size_table[2][0x09b] = 13; + packet_size_table[2][0x09f] = 10; + packet_size_table[2][0x0a7] = 17; + packet_size_table[2][0x113] = 19; + packet_size_table[2][0x116] = 19; + packet_size_table[2][0x190] = 99; + // size of packet version 8 + memcpy(&packet_size_table[3], &packet_size_table[2], sizeof(packet_len_table)); + packet_size_table[3][0x072] = 14; + packet_size_table[3][0x07e] = 33; + packet_size_table[3][0x085] = 20; + packet_size_table[3][0x089] = 15; + packet_size_table[3][0x08c] = 23; + packet_size_table[3][0x094] = 10; + packet_size_table[3][0x09b] = 6; + packet_size_table[3][0x09f] = 13; + packet_size_table[3][0x0a2] = 103; + packet_size_table[3][0x0a7] = 12; + packet_size_table[3][0x0f3] = -1; + packet_size_table[3][0x0f5] = 17; + packet_size_table[3][0x0f7] = 10; + packet_size_table[3][0x113] = 16; + packet_size_table[3][0x116] = 2; + packet_size_table[3][0x190] = 26; + packet_size_table[3][0x193] = 9; + // size of packet version 9 + memcpy(&packet_size_table[4], &packet_size_table[3], sizeof(packet_len_table)); + packet_size_table[4][0x072] = 17; + packet_size_table[4][0x07e] = 37; + packet_size_table[4][0x085] = 26; + packet_size_table[4][0x089] = 12; + packet_size_table[4][0x08c] = 40; + packet_size_table[4][0x094] = 13; + packet_size_table[4][0x09b] = 15; + packet_size_table[4][0x09f] = 12; + packet_size_table[4][0x0a2] = 120; + packet_size_table[4][0x0a7] = 11; +// packet_size_table[4][0x0f3] = -1; + packet_size_table[4][0x0f5] = 24; + packet_size_table[4][0x0f7] = 13; + packet_size_table[4][0x113] = 23; +// packet_size_table[4][0x116] = 2; + packet_size_table[4][0x190] = 26; + packet_size_table[4][0x193] = 18; + // new packet + packet_size_table[4][0x20f] = 10; + packet_size_table[4][0x210] = 22; + packet_size_table[4][0x212] = 26; + packet_size_table[4][0x213] = 26; + packet_size_table[4][0x214] = 42; + // size of packet version 9 + memcpy(&packet_size_table[5], &packet_size_table[4], sizeof(packet_len_table)); + packet_size_table[5][0x072] = 20; + packet_size_table[5][0x07e] = 19; + packet_size_table[5][0x085] = 23; + packet_size_table[5][0x089] = 9; + packet_size_table[5][0x08c] = 105; + packet_size_table[5][0x094] = 17; + packet_size_table[5][0x09b] = 14; + packet_size_table[5][0x09f] = -1; + packet_size_table[5][0x0a2] = 14; + packet_size_table[5][0x0a7] = 25; + packet_size_table[5][0x0f3] = 10; + packet_size_table[5][0x0f5] = 34; + packet_size_table[5][0x0f7] = 2; + packet_size_table[5][0x113] = 11; + packet_size_table[5][0x116] = 11; + packet_size_table[5][0x190] = 22; + packet_size_table[5][0x193] = 17; + + set_defaultparse(clif_parse); + for(i = 0; i < 10; i++) { + if (make_listen_port(map_port)) + break; +#ifdef LCCWIN32 + Sleep(20000); +#else + sleep(20); +#endif + } + if (i == 10) { + printf("cant bind game port\n"); + exit(1); + } + + add_timer_func_list(clif_waitclose, "clif_waitclose"); + add_timer_func_list(clif_clearchar_delay_sub, "clif_clearchar_delay_sub"); + + return 0; +} + diff --git a/src/map/clif.h b/src/map/clif.h new file mode 100644 index 0000000..cdddd8b --- /dev/null +++ b/src/map/clif.h @@ -0,0 +1,280 @@ +// $Id: clif.h,v 1.4 2004/09/25 05:32:18 MouseJstr Exp $ +#ifndef _CLIF_H_ +#define _CLIF_H_ + +#include <sys/types.h> + +#ifdef LCCWIN32 +#include <winsock.h> +typedef unsigned int in_addr_t; +#else +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#endif + +#include "map.h" + +void clif_setip(char*); +void clif_setport(int); + +in_addr_t clif_getip(void); +int clif_getport(void); +int clif_countusers(void); +void clif_setwaitclose(int); + +int clif_authok(struct map_session_data *); +int clif_authfail_fd(int,int); +int clif_charselectok(int); +int clif_dropflooritem(struct flooritem_data *); +int clif_clearflooritem(struct flooritem_data *,int); +int clif_clearchar(struct block_list*,int); // area or fd +int clif_clearchar_delay(unsigned int,struct block_list *,int); +#define clif_clearchar_area(bl,type) clif_clearchar(bl,type) +int clif_clearchar_id(int,int,int); +int clif_spawnpc(struct map_session_data*); //area +int clif_spawnnpc(struct npc_data*); // area +int clif_spawnmob(struct mob_data*); // area +int clif_spawnpet(struct pet_data*); // area +int clif_walkok(struct map_session_data*); // self +int clif_movechar(struct map_session_data*); // area +int clif_movemob(struct mob_data*); //area +int clif_movepet(struct pet_data *pd); //area +int clif_changemap(struct map_session_data*,char*,int,int); //self +int clif_changemapserver(struct map_session_data*,char*,int,int,int,int); //self +int clif_fixpos(struct block_list *); // area +int clif_fixmobpos(struct mob_data *md); +int clif_fixpcpos(struct map_session_data *sd); +int clif_fixpetpos(struct pet_data *pd); +int clif_npcbuysell(struct map_session_data*,int); //self +int clif_buylist(struct map_session_data*,struct npc_data*); //self +int clif_selllist(struct map_session_data*); //self +int clif_scriptmes(struct map_session_data*,int,char*); //self +int clif_scriptnext(struct map_session_data*,int); //self +int clif_scriptclose(struct map_session_data*,int); //self +int clif_scriptmenu(struct map_session_data*,int,char*); //self +int clif_scriptinput(struct map_session_data*,int); //self +int clif_scriptinputstr(struct map_session_data *sd,int npcid); // self +int clif_cutin(struct map_session_data*,char*,int); //self +int clif_viewpoint(struct map_session_data*,int,int,int,int,int,int); //self +int clif_additem(struct map_session_data*,int,int,int); //self +int clif_delitem(struct map_session_data*,int,int); //self +int clif_updatestatus(struct map_session_data*,int); //self +int clif_changestatus(struct block_list*,int,int); //area +int clif_damage(struct block_list *,struct block_list *,unsigned int,int,int,int,int,int,int); // area +#define clif_takeitem(src,dst) clif_damage(src,dst,0,0,0,0,0,1,0) +int clif_changelook(struct block_list *,int,int); // area +int clif_arrowequip(struct map_session_data *sd,int val); //self +int clif_arrow_fail(struct map_session_data *sd,int type); //self +int clif_arrow_create_list(struct map_session_data *sd); //self +int clif_statusupack(struct map_session_data *,int,int,int); // self +int clif_equipitemack(struct map_session_data *,int,int,int); // self +int clif_unequipitemack(struct map_session_data *,int,int,int); // self +int clif_misceffect(struct block_list*,int); // area +int clif_misceffect2(struct block_list *bl,int type); +int clif_changeoption(struct block_list*); // area +int clif_useitemack(struct map_session_data*,int,int,int); // self + +int clif_createchat(struct map_session_data*,int); // self +int clif_dispchat(struct chat_data*,int); // area or fd +int clif_joinchatfail(struct map_session_data*,int); // self +int clif_joinchatok(struct map_session_data*,struct chat_data*); // self +int clif_addchat(struct chat_data*,struct map_session_data*); // chat +int clif_changechatowner(struct chat_data*,struct map_session_data*); // chat +int clif_clearchat(struct chat_data*,int); // area or fd +int clif_leavechat(struct chat_data*,struct map_session_data*); // chat +int clif_changechatstatus(struct chat_data*); // chat + +void clif_emotion(struct block_list *bl,int type); +void clif_talkiebox(struct block_list *bl,char* talkie); +void clif_wedding_effect(struct block_list *bl); +void clif_sitting(int fd, struct map_session_data *sd); +//void clif_callpartner(struct map_session_data *sd); +//void clif_sitting(struct map_session_data *sd); +void clif_soundeffect(struct map_session_data *sd,struct block_list *bl,char *name,int type); + +// trade +int clif_traderequest(struct map_session_data *sd,char *name); +int clif_tradestart(struct map_session_data *sd,int type); +int clif_tradeadditem(struct map_session_data *sd,struct map_session_data *tsd,int index,int amount); +int clif_tradeitemok(struct map_session_data *sd,int index,int amount,int fail); +int clif_tradedeal_lock(struct map_session_data *sd,int fail); +int clif_tradecancelled(struct map_session_data *sd); +int clif_tradecompleted(struct map_session_data *sd,int fail); + +// storage +#include "storage.h" +int clif_storageitemlist(struct map_session_data *sd,struct storage *stor); +int clif_storageequiplist(struct map_session_data *sd,struct storage *stor); +int clif_updatestorageamount(struct map_session_data *sd,struct storage *stor); +int clif_storageitemadded(struct map_session_data *sd,struct storage *stor,int index,int amount); +int clif_storageitemremoved(struct map_session_data *sd,int index,int amount); +int clif_storageclose(struct map_session_data *sd); +int clif_guildstorageitemlist(struct map_session_data *sd,struct guild_storage *stor); +int clif_guildstorageequiplist(struct map_session_data *sd,struct guild_storage *stor); +int clif_updateguildstorageamount(struct map_session_data *sd,struct guild_storage *stor); +int clif_guildstorageitemadded(struct map_session_data *sd,struct guild_storage *stor,int index,int amount); + +int clif_pcinsight(struct block_list *,va_list); // map_forallinmovearea callback +int clif_pcoutsight(struct block_list *,va_list); // map_forallinmovearea callback +int clif_mobinsight(struct block_list *,va_list); // map_forallinmovearea callback +int clif_moboutsight(struct block_list *,va_list); // map_forallinmovearea callback +int clif_petoutsight(struct block_list *bl,va_list ap); +int clif_petinsight(struct block_list *bl,va_list ap); + +int clif_class_change(struct block_list *bl,int class,int type); +int clif_mob_class_change(struct mob_data *md,int class); +int clif_mob_equip(struct mob_data *md,int nameid); // [Valaris] + +int clif_skillinfo(struct map_session_data *sd,int skillid,int type,int range); +int clif_skillinfoblock(struct map_session_data *sd); +int clif_skillup(struct map_session_data *sd,int skill_num); + +int clif_skillcasting(struct block_list* bl, + int src_id,int dst_id,int dst_x,int dst_y,int skill_num,int casttime); +int clif_skillcastcancel(struct block_list* bl); +int clif_skill_fail(struct map_session_data *sd,int skill_id,int type,int btype); +int clif_skill_damage(struct block_list *src,struct block_list *dst, + unsigned int tick,int sdelay,int ddelay,int damage,int div, + int skill_id,int skill_lv,int type); +int clif_skill_damage2(struct block_list *src,struct block_list *dst, + unsigned int tick,int sdelay,int ddelay,int damage,int div, + int skill_id,int skill_lv,int type); +int clif_skill_nodamage(struct block_list *src,struct block_list *dst, + int skill_id,int heal,int fail); +int clif_skill_poseffect(struct block_list *src,int skill_id, + int val,int x,int y,int tick); +int clif_skill_estimation(struct map_session_data *sd,struct block_list *dst); +int clif_skill_warppoint(struct map_session_data *sd,int skill_num, + const char *map1,const char *map2,const char *map3,const char *map4); +int clif_skill_memo(struct map_session_data *sd,int flag); +int clif_skill_teleportmessage(struct map_session_data *sd,int flag); +int clif_skill_produce_mix_list(struct map_session_data *sd,int trigger); + +int clif_produceeffect(struct map_session_data *sd,int flag,int nameid); + +int clif_skill_setunit(struct skill_unit *unit); +int clif_skill_delunit(struct skill_unit *unit); + +int clif_01ac(struct block_list *bl); + +int clif_autospell(struct map_session_data *sd,int skilllv); +int clif_devotion(struct map_session_data *sd,int target); +int clif_spiritball(struct map_session_data *sd); +int clif_combo_delay(struct block_list *src,int wait); +int clif_bladestop(struct block_list *src,struct block_list *dst,int bool); +int clif_changemapcell(int m,int x,int y,int cell_type,int type); + +int clif_status_change(struct block_list *bl,int type,int flag); + +int clif_wis_message(int fd,char *nick,char *mes,int mes_len); +int clif_wis_end(int fd,int flag); + +int clif_solved_charname(struct map_session_data *sd,int char_id); + +int clif_use_card(struct map_session_data *sd,int idx); +int clif_insert_card(struct map_session_data *sd,int idx_equip,int idx_card,int flag); + +int clif_itemlist(struct map_session_data *sd); +int clif_equiplist(struct map_session_data *sd); + +int clif_cart_additem(struct map_session_data*,int,int,int); +int clif_cart_delitem(struct map_session_data*,int,int); +int clif_cart_itemlist(struct map_session_data *sd); +int clif_cart_equiplist(struct map_session_data *sd); + +int clif_item_identify_list(struct map_session_data *sd); +int clif_item_identified(struct map_session_data *sd,int idx,int flag); +int clif_item_repair_list(struct map_session_data *sd); + +int clif_item_skill(struct map_session_data *sd,int skillid,int skilllv,const char *name); + +int clif_mvp_effect(struct map_session_data *sd); +int clif_mvp_item(struct map_session_data *sd,int nameid); +int clif_mvp_exp(struct map_session_data *sd,int exp); + +// vending +int clif_openvendingreq(struct map_session_data *sd,int num); +int clif_showvendingboard(struct block_list* bl,char *message,int fd); +int clif_closevendingboard(struct block_list* bl,int fd); +int clif_vendinglist(struct map_session_data *sd,int id,struct vending *vending); +int clif_buyvending(struct map_session_data *sd,int index,int amount,int fail); +int clif_openvending(struct map_session_data *sd,int id,struct vending *vending); +int clif_vendingreport(struct map_session_data *sd,int index,int amount); + +int clif_movetoattack(struct map_session_data *sd,struct block_list *bl); + +// party +int clif_party_created(struct map_session_data *sd,int flag); +int clif_party_info(struct party *p,int fd); +int clif_party_invite(struct map_session_data *sd,struct map_session_data *tsd); +int clif_party_inviteack(struct map_session_data *sd,char *nick,int flag); +int clif_party_option(struct party *p,struct map_session_data *sd,int flag); +int clif_party_leaved(struct party *p,struct map_session_data *sd,int account_id,char *name,int flag); +int clif_party_message(struct party *p,int account_id,char *mes,int len); +int clif_party_move(struct party *p,struct map_session_data *sd,int online); +int clif_party_xy(struct party *p,struct map_session_data *sd); +int clif_party_hp(struct party *p,struct map_session_data *sd); + +// guild +int clif_guild_created(struct map_session_data *sd,int flag); +int clif_guild_belonginfo(struct map_session_data *sd,struct guild *g); +int clif_guild_basicinfo(struct map_session_data *sd); +int clif_guild_allianceinfo(struct map_session_data *sd); +int clif_guild_memberlist(struct map_session_data *sd); +int clif_guild_skillinfo(struct map_session_data *sd); +int clif_guild_memberlogin_notice(struct guild *g,int idx,int flag); +int clif_guild_invite(struct map_session_data *sd,struct guild *g); +int clif_guild_inviteack(struct map_session_data *sd,int flag); +int clif_guild_leave(struct map_session_data *sd,const char *name,const char *mes); +int clif_guild_explusion(struct map_session_data *sd,const char *name,const char *mes, + int account_id); +int clif_guild_positionchanged(struct guild *g,int idx); +int clif_guild_memberpositionchanged(struct guild *g,int idx); +int clif_guild_emblem(struct map_session_data *sd,struct guild *g); +int clif_guild_notice(struct map_session_data *sd,struct guild *g); +int clif_guild_message(struct guild *g,int account_id,const char *mes,int len); +int clif_guild_skillup(struct map_session_data *sd,int skill_num,int lv); +int clif_guild_reqalliance(struct map_session_data *sd,int account_id,const char *name); +int clif_guild_allianceack(struct map_session_data *sd,int flag); +int clif_guild_delalliance(struct map_session_data *sd,int guild_id,int flag); +int clif_guild_oppositionack(struct map_session_data *sd,int flag); +int clif_guild_broken(struct map_session_data *sd,int flag); + + +// atcommand +int clif_displaymessage(const int fd,char* mes); +int clif_disp_onlyself(struct map_session_data *sd,char *mes,int len); +int clif_GMmessage(struct block_list *bl,char* mes,int len,int flag); +int clif_heal(int fd,int type,int val); +int clif_resurrection(struct block_list *bl,int type); +int clif_set0199(int fd,int type); +int clif_pvpset(struct map_session_data *sd, int pvprank, int pvpnum,int type); +int clif_send0199(int map,int type); +int clif_refine(int fd,struct map_session_data *sd,int fail,int index,int val); + +//petsystem +int clif_catch_process(struct map_session_data *sd); +int clif_pet_rulet(struct map_session_data *sd,int data); +int clif_sendegg(struct map_session_data *sd); +int clif_send_petdata(struct map_session_data *sd,int type,int param); +int clif_send_petstatus(struct map_session_data *sd); +int clif_pet_emotion(struct pet_data *pd,int param); +int clif_pet_performance(struct block_list *bl,int param); +int clif_pet_equip(struct pet_data *pd,int nameid); +int clif_pet_food(struct map_session_data *sd,int foodid,int fail); + +int clif_specialeffect(struct block_list *bl,int type, int flag); // special effects [Valaris] +int clif_message(struct block_list *bl, char* msg); // messages (from mobs/npcs) [Valaris] + +int clif_GM_kickack(struct map_session_data *sd,int id); +int clif_GM_kick(struct map_session_data *sd,struct map_session_data *tsd,int type); + +int clif_foreachclient(int (*)(struct map_session_data*,va_list),...); + +int do_final_clif(void); +int do_init_clif(void); + +#endif + + diff --git a/src/map/guild.c b/src/map/guild.c new file mode 100644 index 0000000..c32ebb4 --- /dev/null +++ b/src/map/guild.c @@ -0,0 +1,1543 @@ +// $Id: guild.c,v 1.5 2004/09/25 05:32:18 MouseJstr Exp $ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "guild.h" +#include "storage.h" +#include "db.h" +#include "timer.h" +#include "socket.h" +#include "nullpo.h" +#include "malloc.h" +#include "battle.h" +#include "npc.h" +#include "pc.h" +#include "map.h" +#include "mob.h" +#include "intif.h" +#include "clif.h" + +#ifdef MEMWATCH +#include "memwatch.h" +#endif + +static struct dbt *guild_db; +static struct dbt *castle_db; +static struct dbt *guild_expcache_db; +static struct dbt *guild_infoevent_db; +static struct dbt *guild_castleinfoevent_db; + +struct eventlist { + char name[50]; + struct eventlist *next; +}; + +// ギルドのEXPキャッシュのフラッシュに関連する定数 +#define GUILD_PAYEXP_INVERVAL 10000 // 間隔(キャッシュの最大生存時間、ミリ秒) +#define GUILD_PAYEXP_LIST 8192 // キャッシュの最大数 + +// ギルドのEXPキャッシュ +struct guild_expcache { + int guild_id, account_id, char_id, exp; +}; + +// ギルドスキルdbのアクセサ(今は直打ちで代用) +int guild_skill_get_inf(int id){ return 0; } +int guild_skill_get_sp(int id,int lv){ return 0; } +int guild_skill_get_range(int id){ return 0; } +int guild_skill_get_max(int id){ return (id==10004)?10:1; } + +// ギルドスキルがあるか確認 +int guild_checkskill(struct guild *g,int id){ return g->skill[id-10000].lv; } + + +int guild_payexp_timer(int tid,unsigned int tick,int id,int data); +int guild_gvg_eliminate_timer(int tid,unsigned int tick,int id,int data); + + +static int guild_read_castledb(void) +{ + FILE *fp; + char line[1024]; + int j,ln=0; + char *str[32],*p; + struct guild_castle *gc; + + if( (fp=fopen("db/castle_db.txt","r"))==NULL){ + printf("can't read db/castle_db.txt\n"); + return -1; + } + + while(fgets(line,1020,fp)){ + if(line[0]=='/' && line[1]=='/') + continue; + memset(str,0,sizeof(str)); + gc=(struct guild_castle *)aCalloc(1,sizeof(struct guild_castle)); + for(j=0,p=line;j<6 && p;j++){ + str[j]=p; + p=strchr(p,','); + if(p) *p++=0; + } + + gc->guild_id=0; // <Agit> Clear Data for Initialize + gc->economy=0; gc->defense=0; gc->triggerE=0; gc->triggerD=0; gc->nextTime=0; gc->payTime=0; + gc->createTime=0; gc->visibleC=0; gc->visibleG0=0; gc->visibleG1=0; gc->visibleG2=0; + gc->visibleG3=0; gc->visibleG4=0; gc->visibleG5=0; gc->visibleG6=0; gc->visibleG7=0; + gc->Ghp0=0; gc->Ghp1=0; gc->Ghp2=0; gc->Ghp3=0; gc->Ghp4=0; gc->Ghp5=0; gc->Ghp6=0; gc->Ghp7=0; // guardian HP [Valaris] + + gc->castle_id=atoi(str[0]); + memcpy(gc->map_name,str[1],24); + memcpy(gc->castle_name,str[2],24); + memcpy(gc->castle_event,str[3],24); + + numdb_insert(castle_db,gc->castle_id,gc); + + //intif_guild_castle_info(gc->castle_id); + + ln++; + } + fclose(fp); + printf("read db/castle_db.txt done (count=%d)\n",ln); + return 0; +} + +// 初期化 +void do_init_guild(void) +{ + guild_db=numdb_init(); + castle_db=numdb_init(); + guild_expcache_db=numdb_init(); + guild_infoevent_db=numdb_init(); + guild_castleinfoevent_db=numdb_init(); + + guild_read_castledb(); + + add_timer_func_list(guild_gvg_eliminate_timer,"guild_gvg_eliminate_timer"); + add_timer_func_list(guild_payexp_timer,"guild_payexp_timer"); + add_timer_interval(gettick()+GUILD_PAYEXP_INVERVAL,guild_payexp_timer,0,0,GUILD_PAYEXP_INVERVAL); +} + + +// 検索 +struct guild *guild_search(int guild_id) +{ + return numdb_search(guild_db,guild_id); +} +int guild_searchname_sub(void *key,void *data,va_list ap) +{ + struct guild *g=(struct guild *)data,**dst; + char *str; + str=va_arg(ap,char *); + dst=va_arg(ap,struct guild **); + if(strcmpi(g->name,str)==0) + *dst=g; + return 0; +} +// ギルド名検索 +struct guild* guild_searchname(char *str) +{ + struct guild *g=NULL; + numdb_foreach(guild_db,guild_searchname_sub,str,&g); + return g; +} +struct guild_castle *guild_castle_search(int gcid) +{ + return numdb_search(castle_db,gcid); +} + +// mapnameに対応したアジトのgcを返す +struct guild_castle *guild_mapname2gc(char *mapname) +{ + int i; + struct guild_castle *gc=NULL; + for(i=0;i<MAX_GUILDCASTLE;i++){ + gc=guild_castle_search(i); + if(!gc) continue; + if(strcmp(gc->map_name,mapname)==0) return gc; + } + return NULL; +} + +// ログイン中のギルドメンバーの1人のsdを返す +struct map_session_data *guild_getavailablesd(struct guild *g) +{ + int i; + + nullpo_retr(NULL, g); + + for(i=0;i<g->max_member;i++) + if(g->member[i].sd!=NULL) + return g->member[i].sd; + return NULL; +} + +// ギルドメンバーのインデックスを返す +int guild_getindex(struct guild *g,int account_id,int char_id) +{ + int i; + if(g==NULL) + return -1; + for(i=0;i<g->max_member;i++) + if( g->member[i].account_id==account_id && + g->member[i].char_id==char_id ) + return i; + return -1; +} +// ギルドメンバーの役職を返す +int guild_getposition(struct map_session_data *sd,struct guild *g) +{ + int i; + + nullpo_retr(-1, sd); + + if(g==NULL && (g=guild_search(sd->status.guild_id))==NULL) + return -1; + for(i=0;i<g->max_member;i++) + if( g->member[i].account_id==sd->status.account_id && + g->member[i].char_id==sd->status.char_id ) + return g->member[i].position; + return -1; +} + +// メンバー情報の作成 +void guild_makemember(struct guild_member *m,struct map_session_data *sd) +{ + nullpo_retv(sd); + + memset(m,0,sizeof(struct guild_member)); + m->account_id =sd->status.account_id; + m->char_id =sd->status.char_id; + m->hair =sd->status.hair; + m->hair_color =sd->status.hair_color; + m->gender =sd->sex; + m->class =sd->status.class; + m->lv =sd->status.base_level; + m->exp =0; + m->exp_payper =0; + m->online =1; + m->position =MAX_GUILDPOSITION-1; + memcpy(m->name,sd->status.name,24); + return; +} +// ギルド競合確認 +int guild_check_conflict(struct map_session_data *sd) +{ + nullpo_retr(0, sd); + + intif_guild_checkconflict(sd->status.guild_id, + sd->status.account_id,sd->status.char_id); + return 0; +} + +// ギルドのEXPキャッシュをinter鯖にフラッシュする +int guild_payexp_timer_sub(void *key,void *data,va_list ap) +{ + int i, *dellist,*delp, dataid=(int)key; + struct guild_expcache *c; + struct guild *g; + + nullpo_retr(0, ap); + nullpo_retr(0, c=(struct guild_expcache *)data); + nullpo_retr(0, dellist=va_arg(ap,int *)); + nullpo_retr(0, delp=va_arg(ap,int *)); + + if( *delp>=GUILD_PAYEXP_LIST || (g=guild_search(c->guild_id))==NULL ) + return 0; + if( ( i=guild_getindex(g,c->account_id,c->char_id) )<0 ) + return 0; + + g->member[i].exp+=c->exp; + intif_guild_change_memberinfo(g->guild_id,c->account_id,c->char_id, + GMI_EXP,&g->member[i].exp,sizeof(g->member[i].exp)); + c->exp=0; + + dellist[(*delp)++]=dataid; + free(c); + return 0; +} +int guild_payexp_timer(int tid,unsigned int tick,int id,int data) +{ + int dellist[GUILD_PAYEXP_LIST],delp=0,i; + numdb_foreach(guild_expcache_db,guild_payexp_timer_sub, + dellist,&delp); + for(i=0;i<delp;i++) + numdb_erase(guild_expcache_db,dellist[i]); +// if(battle_config.etc_log) +// printf("guild exp %d charactor's exp flushed !\n",delp); + return 0; +} + +//------------------------------------------------------------------------ + +// 作成要求 +int guild_create(struct map_session_data *sd,char *name) +{ + nullpo_retr(0, sd); + + if(sd->status.guild_id==0){ + if(!battle_config.guild_emperium_check || pc_search_inventory(sd,714) >= 0) { + struct guild_member m; + guild_makemember(&m,sd); + m.position=0; + intif_guild_create(name,&m); + } else + clif_guild_created(sd,3); // エンペリウムがいない + }else + clif_guild_created(sd,1); // すでに所属している + + return 0; +} + +// 作成可否 +int guild_created(int account_id,int guild_id) +{ + struct map_session_data *sd=map_id2sd(account_id); + + if(sd==NULL) + return 0; + if(guild_id>0) { + struct guild *g; + sd->status.guild_id=guild_id; + sd->guild_sended=0; + if((g=numdb_search(guild_db,guild_id))!=NULL){ + printf("guild: id already exists!\n"); + exit(1); + } + clif_guild_created(sd,0); + if(battle_config.guild_emperium_check) + pc_delitem(sd,pc_search_inventory(sd,714),1,0); // エンペリウム消耗 + } else { + clif_guild_created(sd,2); // 作成失敗(同名ギルド存在) + } + return 0; +} + +// 情報要求 +int guild_request_info(int guild_id) +{ +// if(battle_config.etc_log) +// printf("guild_request_info\n"); + return intif_guild_request_info(guild_id); +} +// イベント付き情報要求 +int guild_npc_request_info(int guild_id,const char *event) +{ + struct eventlist *ev; + + if( guild_search(guild_id) ){ + if(event && *event) + npc_event_do(event); + return 0; + } + + if(event==NULL || *event==0) + return guild_request_info(guild_id); + + ev=(struct eventlist *)aCalloc(1,sizeof(struct eventlist)); + memcpy(ev->name,event,sizeof(ev->name)); + ev->next=(struct eventlist *)numdb_search(guild_infoevent_db,guild_id); + numdb_insert(guild_infoevent_db,guild_id,ev); + return guild_request_info(guild_id); +} + +// 所属キャラの確認 +int guild_check_member(const struct guild *g) +{ + int i; + struct map_session_data *sd; + + nullpo_retr(0, g); + + for(i=0;i<fd_max;i++){ + if(session[i] && (sd=session[i]->session_data) && sd->state.auth){ + if(sd->status.guild_id==g->guild_id){ + int j,f=1; + for(j=0;j<MAX_GUILD;j++){ // データがあるか + if( g->member[j].account_id==sd->status.account_id && + g->member[j].char_id==sd->status.char_id) + f=0; + } + if(f){ + sd->status.guild_id=0; + sd->guild_sended=0; + sd->guild_emblem_id=0; + if(battle_config.error_log) + printf("guild: check_member %d[%s] is not member\n",sd->status.account_id,sd->status.name); + } + } + } + } + return 0; +} +// 情報所得失敗(そのIDのキャラを全部未所属にする) +int guild_recv_noinfo(int guild_id) +{ + int i; + struct map_session_data *sd; + for(i=0;i<fd_max;i++){ + if(session[i] && (sd=session[i]->session_data) && sd->state.auth){ + if(sd->status.guild_id==guild_id) + sd->status.guild_id=0; + } + } + return 0; +} +// 情報所得 +int guild_recv_info(struct guild *sg) +{ + struct guild *g,before; + int i,bm,m; + struct eventlist *ev,*ev2; + + nullpo_retr(0, sg); + + if((g=numdb_search(guild_db,sg->guild_id))==NULL){ + g=(struct guild *)aCalloc(1,sizeof(struct guild)); + numdb_insert(guild_db,sg->guild_id,g); + before=*sg; + + // 最初のロードなのでユーザーのチェックを行う + guild_check_member(sg); + }else + before=*g; + memcpy(g,sg,sizeof(struct guild)); + + for(i=bm=m=0;i<g->max_member;i++){ // sdの設定と人数の確認 + if(g->member[i].account_id>0){ + struct map_session_data *sd = map_id2sd(g->member[i].account_id); + g->member[i].sd=(sd!=NULL && + sd->status.char_id==g->member[i].char_id && + sd->status.guild_id==g->guild_id)? sd:NULL; + m++; + }else + g->member[i].sd=NULL; + if(before.member[i].account_id>0) + bm++; + } + + for(i=0;i<g->max_member;i++){ // 情報の送信 + struct map_session_data *sd = g->member[i].sd; + if( sd==NULL ) + continue; + + if( before.guild_lv!=g->guild_lv || bm!=m || + before.max_member!=g->max_member ){ + clif_guild_basicinfo(sd); // 基本情報送信 + clif_guild_emblem(sd,g); // エンブレム送信 + } + + if(bm!=m){ // メンバー情報送信 + clif_guild_memberlist(g->member[i].sd); + } + + if( before.skill_point!=g->skill_point) + clif_guild_skillinfo(sd); // スキル情報送信 + + if( sd->guild_sended==0){ // 未送信なら所属情報も送る + clif_guild_belonginfo(sd,g); + clif_guild_notice(sd,g); + sd->guild_emblem_id=g->emblem_id; + sd->guild_sended=1; + } + } + + // イベントの発生 + if( (ev=numdb_search(guild_infoevent_db,sg->guild_id))!=NULL ){ + numdb_erase(guild_infoevent_db,sg->guild_id); + for(;ev;ev2=ev->next,free(ev),ev=ev2){ + npc_event_do(ev->name); + } + } + + return 0; +} + + +// ギルドへの勧誘 +int guild_invite(struct map_session_data *sd,int account_id) +{ + struct map_session_data *tsd; + struct guild *g; + int i; + + nullpo_retr(0, sd); + + tsd= map_id2sd(account_id); + g=guild_search(sd->status.guild_id); + + if(tsd==NULL || g==NULL) + return 0; + if(!battle_config.invite_request_check) { + if (tsd->party_invite>0 || tsd->trade_partner) { // 相手が取引中かどうか + clif_guild_inviteack(sd,0); + return 0; + } + } + if( tsd->status.guild_id>0 || tsd->guild_invite>0 ){ // 相手の所属確認 + clif_guild_inviteack(sd,0); + return 0; + } + + // 定員確認 + for(i=0;i<g->max_member;i++) + if(g->member[i].account_id==0) + break; + if(i==g->max_member){ + clif_guild_inviteack(sd,3); + return 0; + } + + tsd->guild_invite=sd->status.guild_id; + tsd->guild_invite_account=sd->status.account_id; + + clif_guild_invite(tsd,g); + return 0; +} +// ギルド勧誘への返答 +int guild_reply_invite(struct map_session_data *sd,int guild_id,int flag) +{ + struct map_session_data *tsd; + + nullpo_retr(0, sd); + nullpo_retr(0, tsd= map_id2sd( sd->guild_invite_account )); + + if(sd->guild_invite!=guild_id) // 勧誘とギルドIDが違う + return 0; + + if(flag==1){ // 承諾 + struct guild_member m; + struct guild *g; + int i; + + // 定員確認 + if( (g=guild_search(tsd->status.guild_id))==NULL ){ + sd->guild_invite=0; + sd->guild_invite_account=0; + return 0; + } + for(i=0;i<g->max_member;i++) + if(g->member[i].account_id==0) + break; + if(i==g->max_member){ + sd->guild_invite=0; + sd->guild_invite_account=0; + clif_guild_inviteack(tsd,3); + return 0; + } + + + //inter鯖へ追加要求 + guild_makemember(&m,sd); + intif_guild_addmember( sd->guild_invite, &m ); + return 0; + }else{ // 拒否 + sd->guild_invite=0; + sd->guild_invite_account=0; + if(tsd==NULL) + return 0; + clif_guild_inviteack(tsd,1); + } + return 0; +} +// ギルドメンバが追加された +int guild_member_added(int guild_id,int account_id,int char_id,int flag) +{ + struct map_session_data *sd= map_id2sd(account_id),*sd2; + struct guild *g; + + if( (g=guild_search(guild_id))==NULL ) + return 0; + + if((sd==NULL || sd->guild_invite==0) && flag==0){ + // キャラ側に登録できなかったため脱退要求を出す + if(battle_config.error_log) + printf("guild: member added error %d is not online\n",account_id); + intif_guild_leave(guild_id,account_id,char_id,0,"**登録失敗**"); + return 0; + } + sd->guild_invite=0; + sd->guild_invite_account=0; + + sd2=map_id2sd(sd->guild_invite_account); + + if(flag==1){ // 失敗 + if( sd2!=NULL ) + clif_guild_inviteack(sd2,3); + return 0; + } + + // 成功 + sd->guild_sended=0; + sd->status.guild_id=guild_id; + + if( sd2!=NULL ) + clif_guild_inviteack(sd2,2); + + // いちおう競合確認 + guild_check_conflict(sd); + + return 0; +} + +// ギルド脱退要求 +int guild_leave(struct map_session_data *sd,int guild_id, + int account_id,int char_id,const char *mes) +{ + struct guild *g; + int i; + + nullpo_retr(0, sd); + + g = guild_search(sd->status.guild_id); + + if(g==NULL) + return 0; + + if( sd->status.account_id!=account_id || + sd->status.char_id!=char_id || sd->status.guild_id!=guild_id) + return 0; + + for(i=0;i<g->max_member;i++){ // 所属しているか + if( g->member[i].account_id==sd->status.account_id && + g->member[i].char_id==sd->status.char_id ){ + intif_guild_leave(g->guild_id,sd->status.account_id,sd->status.char_id,0,mes); + return 0; + } + } + return 0; +} +// ギルド追放要求 +int guild_explusion(struct map_session_data *sd,int guild_id, + int account_id,int char_id,const char *mes) +{ + struct guild *g; + int i,ps; + + nullpo_retr(0, sd); + + g = guild_search(sd->status.guild_id); + + if(g==NULL) + return 0; + + if( sd->status.guild_id!=guild_id) + return 0; + + if( (ps=guild_getposition(sd,g))<0 || !(g->position[ps].mode&0x0010) ) + return 0; // 処罰権限無し + + for(i=0;i<g->max_member;i++){ // 所属しているか + if( g->member[i].account_id==account_id && + g->member[i].char_id==char_id ){ + intif_guild_leave(g->guild_id,account_id,char_id,1,mes); + return 0; + } + } + return 0; +} +// ギルドメンバが脱退した +int guild_member_leaved(int guild_id,int account_id,int char_id,int flag, + const char *name,const char *mes) +{ + struct map_session_data *sd=map_id2sd(account_id); + struct guild *g=guild_search(guild_id); + int i; + + if(g!=NULL){ + int i; + for(i=0;i<g->max_member;i++) + if( g->member[i].account_id==account_id && + g->member[i].char_id==char_id ){ + struct map_session_data *sd2=sd; + if(sd2==NULL) + sd2=guild_getavailablesd(g); + else + { + if(flag==0) + clif_guild_leave(sd2,name,mes); + else + clif_guild_explusion(sd2,name,mes,account_id); + } + g->member[i].account_id=0; + g->member[i].sd=NULL; + } + } + if(sd!=NULL && sd->status.guild_id==guild_id){ + sd->status.guild_id=0; + sd->guild_emblem_id=0; + sd->guild_sended=0; + } + + // メンバーリストを全員に再通知 + for(i=0;i<g->max_member;i++){ + if( g->member[i].sd!=NULL ) + clif_guild_memberlist(g->member[i].sd); + } + + return 0; +} +// ギルドメンバのオンライン状態/Lv更新送信 +int guild_send_memberinfoshort(struct map_session_data *sd,int online) +{ + struct guild *g; + + nullpo_retr(0, sd); + + if(sd->status.guild_id<=0) + return 0; + g=guild_search(sd->status.guild_id); + if(g==NULL) + return 0; + + intif_guild_memberinfoshort(g->guild_id, + sd->status.account_id,sd->status.char_id,online,sd->status.base_level,sd->status.class); + + if( !online ){ // ログアウトするならsdをクリアして終了 + int i=guild_getindex(g,sd->status.account_id,sd->status.char_id); + if(i>=0) + g->member[i].sd=NULL; + return 0; + } + + if( sd->guild_sended!=0 ) // ギルド初期送信データは送信済み + return 0; + + // 競合確認 + guild_check_conflict(sd); + + // あるならギルド初期送信データ送信 + if( (g=guild_search(sd->status.guild_id))!=NULL ){ + guild_check_member(g); // 所属を確認する + if(sd->status.guild_id==g->guild_id){ + clif_guild_belonginfo(sd,g); + clif_guild_notice(sd,g); + sd->guild_sended=1; + sd->guild_emblem_id=g->emblem_id; + } + } + return 0; +} +// ギルドメンバのオンライン状態/Lv更新通知 +int guild_recv_memberinfoshort(int guild_id,int account_id,int char_id,int online,int lv,int class) +{ + int i,alv,c,idx=0,om=0,oldonline=-1; + struct guild *g=guild_search(guild_id); + if(g==NULL) + return 0; + for(i=0,alv=0,c=0,om=0;i<g->max_member;i++){ + struct guild_member *m=&g->member[i]; + if(m->account_id==account_id && m->char_id==char_id ){ + oldonline=m->online; + m->online=online; + m->lv=lv; + m->class=class; + idx=i; + } + if(m->account_id>0){ + alv+=m->lv; + c++; + } + if(m->online) + om++; + } + if(idx==g->max_member){ + if(battle_config.error_log) + printf("guild: not found member %d,%d on %d[%s]\n", account_id,char_id,guild_id,g->name); + return 0; + } + g->average_lv=alv/c; + g->connect_member=om; + + if(oldonline!=online) // オンライン状態が変わったので通知 + clif_guild_memberlogin_notice(g,idx,online); + + for(i=0;i<g->max_member;i++){ // sd再設定 + struct map_session_data *sd= map_id2sd(g->member[i].account_id); + g->member[i].sd=(sd!=NULL && + sd->status.char_id==g->member[i].char_id && + sd->status.guild_id==guild_id)?sd:NULL; + } + + // ここにクライアントに送信処理が必要 + + return 0; +} +// ギルド会話送信 +int guild_send_message(struct map_session_data *sd,char *mes,int len) +{ + nullpo_retr(0, sd); + + if(sd->status.guild_id==0) + return 0; + intif_guild_message(sd->status.guild_id,sd->status.account_id,mes,len); + return 0; +} +// ギルド会話受信 +int guild_recv_message(int guild_id,int account_id,char *mes,int len) +{ + struct guild *g; + if( (g=guild_search(guild_id))==NULL) + return 0; + clif_guild_message(g,account_id,mes,len); + return 0; +} +// ギルドメンバの役職変更 +int guild_change_memberposition(int guild_id,int account_id,int char_id,int idx) +{ + return intif_guild_change_memberinfo( + guild_id,account_id,char_id,GMI_POSITION,&idx,sizeof(idx)); +} +// ギルドメンバの役職変更通知 +int guild_memberposition_changed(struct guild *g,int idx,int pos) +{ + nullpo_retr(0, g); + + g->member[idx].position=pos; + clif_guild_memberpositionchanged(g,idx); + return 0; +} +// ギルド役職変更 +int guild_change_position(struct map_session_data *sd,int idx, + int mode,int exp_mode,const char *name) +{ + struct guild_position p; + + nullpo_retr(0, sd); + + if(exp_mode>battle_config.guild_exp_limit) + exp_mode=battle_config.guild_exp_limit; + if(exp_mode<0)exp_mode=0; + p.mode=mode; + p.exp_mode=exp_mode; + memcpy(p.name,name,24); + return intif_guild_position(sd->status.guild_id,idx,&p); +} +// ギルド役職変更通知 +int guild_position_changed(int guild_id,int idx,struct guild_position *p) +{ + struct guild *g=guild_search(guild_id); + if(g==NULL) + return 0; + memcpy(&g->position[idx],p,sizeof(struct guild_position)); + clif_guild_positionchanged(g,idx); + return 0; +} +// ギルド告知変更 +int guild_change_notice(struct map_session_data *sd,int guild_id,const char *mes1,const char *mes2) +{ + nullpo_retr(0, sd); + + if(guild_id!=sd->status.guild_id) + return 0; + return intif_guild_notice(guild_id,mes1,mes2); +} +// ギルド告知変更通知 +int guild_notice_changed(int guild_id,const char *mes1,const char *mes2) +{ + int i; + struct map_session_data *sd; + struct guild *g=guild_search(guild_id); + if(g==NULL) + return 0; + + memcpy(g->mes1,mes1,60); + memcpy(g->mes2,mes2,120); + + for(i=0;i<g->max_member;i++){ + if((sd=g->member[i].sd)!=NULL) + clif_guild_notice(sd,g); + } + return 0; +} +// ギルドエンブレム変更 +int guild_change_emblem(struct map_session_data *sd,int len,const char *data) +{ + nullpo_retr(0, sd); + + return intif_guild_emblem(sd->status.guild_id,len,data); +} +// ギルドエンブレム変更通知 +int guild_emblem_changed(int len,int guild_id,int emblem_id,const char *data) +{ + int i; + struct map_session_data *sd; + struct guild *g=guild_search(guild_id); + if(g==NULL) + return 0; + + memcpy(g->emblem_data,data,len); + g->emblem_len=len; + g->emblem_id=emblem_id; + + for(i=0;i<g->max_member;i++){ + if((sd=g->member[i].sd)!=NULL){ + sd->guild_emblem_id=emblem_id; + clif_guild_belonginfo(sd,g); + clif_guild_emblem(sd,g); + } + } + return 0; +} + +// ギルドのEXP上納 +int guild_payexp(struct map_session_data *sd,int exp) +{ + struct guild *g; + struct guild_expcache *c; + int per,exp2; + + nullpo_retr(0, sd); + + if(sd->status.guild_id==0 || (g=guild_search(sd->status.guild_id))==NULL ) + return 0; + if( (per=g->position[guild_getposition(sd,g)].exp_mode)<=0 ) + return 0; + if( per>100 )per=100; + + if( (exp2=exp*per/100)<=0 ) + return 0; + + if( (c=numdb_search(guild_expcache_db,sd->status.char_id))==NULL ){ + c=(struct guild_expcache *)aCalloc(1,sizeof(struct guild_expcache)); + c->guild_id=sd->status.guild_id; + c->account_id=sd->status.account_id; + c->char_id=sd->status.char_id; + c->exp=exp2; + numdb_insert(guild_expcache_db,c->char_id,c); + }else{ + c->exp+=exp2; + } + return exp2; +} + +// スキルポイント割り振り +int guild_skillup(struct map_session_data *sd,int skill_num) +{ + struct guild *g; + int idx; + + nullpo_retr(0, sd); + + if(sd->status.guild_id==0 || (g=guild_search(sd->status.guild_id))==NULL) + return 0; + if(strcmp(sd->status.name,g->master)) + return 0; + + if( g->skill_point>0 && + g->skill[(idx=skill_num-10000)].id!=0 && + g->skill[idx].lv < guild_skill_get_max(skill_num) ){ + intif_guild_skillup(g->guild_id,skill_num,sd->status.account_id); + } + return 0; +} +// スキルポイント割り振り通知 +int guild_skillupack(int guild_id,int skill_num,int account_id) +{ + struct map_session_data *sd=map_id2sd(account_id); + struct guild *g=guild_search(guild_id); + int i; + if(g==NULL) + return 0; + if(sd!=NULL) + clif_guild_skillup(sd,skill_num,g->skill[skill_num-10000].lv); + // 全員に通知 + for(i=0;i<g->max_member;i++) + if((sd=g->member[i].sd)!=NULL) + clif_guild_skillinfo(sd); + return 0; +} + +// ギルド同盟数所得 +int guild_get_alliance_count(struct guild *g,int flag) +{ + int i,c; + + nullpo_retr(0, g); + + for(i=c=0;i<MAX_GUILDALLIANCE;i++){ + if( g->alliance[i].guild_id>0 && + g->alliance[i].opposition==flag ) + c++; + } + return c; +} +// ギルド同盟要求 +int guild_reqalliance(struct map_session_data *sd,int account_id) +{ + struct map_session_data *tsd= map_id2sd(account_id); + struct guild *g[2]; + int i; + + if(agit_flag) { // Disable alliance creation during woe [Valaris] + clif_displaymessage(sd->fd,"Alliances cannot be made during Guild Wars!"); + return 0; + } // end addition [Valaris] + + + nullpo_retr(0, sd); + + if(tsd==NULL || tsd->status.guild_id<=0) + return 0; + + g[0]=guild_search(sd->status.guild_id); + g[1]=guild_search(tsd->status.guild_id); + + if(g[0]==NULL || g[1]==NULL) + return 0; + + if( guild_get_alliance_count(g[0],0)>3 ) // 同盟数確認 + clif_guild_allianceack(sd,4); + if( guild_get_alliance_count(g[1],0)>3 ) + clif_guild_allianceack(sd,3); + + if( tsd->guild_alliance>0 ){ // 相手が同盟要請状態かどうか確認 + clif_guild_allianceack(sd,1); + return 0; + } + + for(i=0;i<MAX_GUILDALLIANCE;i++){ // すでに同盟状態か確認 + if( g[0]->alliance[i].guild_id==tsd->status.guild_id && + g[0]->alliance[i].opposition==0){ + clif_guild_allianceack(sd,0); + return 0; + } + } + + tsd->guild_alliance=sd->status.guild_id; + tsd->guild_alliance_account=sd->status.account_id; + + clif_guild_reqalliance(tsd,sd->status.account_id,g[0]->name); + return 0; +} +// ギルド勧誘への返答 +int guild_reply_reqalliance(struct map_session_data *sd,int account_id,int flag) +{ + struct map_session_data *tsd; + + nullpo_retr(0, sd); + nullpo_retr(0, tsd= map_id2sd( account_id )); + + if(sd->guild_alliance!=tsd->status.guild_id) // 勧誘とギルドIDが違う + return 0; + + if(flag==1){ // 承諾 + int i; + + struct guild *g; // 同盟数再確認 + if( (g=guild_search(sd->status.guild_id))==NULL || + guild_get_alliance_count(g,0)>3 ){ + clif_guild_allianceack(sd,4); + clif_guild_allianceack(tsd,3); + return 0; + } + if( (g=guild_search(tsd->status.guild_id))==NULL || + guild_get_alliance_count(g,0)>3 ){ + clif_guild_allianceack(sd,3); + clif_guild_allianceack(tsd,4); + return 0; + } + + // 敵対関係なら敵対を止める + if((g=guild_search(sd->status.guild_id)) == NULL) + return 0; + for(i=0;i<MAX_GUILDALLIANCE;i++){ + if( g->alliance[i].guild_id==tsd->status.guild_id && + g->alliance[i].opposition==1) + intif_guild_alliance( sd->status.guild_id,tsd->status.guild_id, + sd->status.account_id,tsd->status.account_id,9 ); + } + if((g=guild_search(tsd->status.guild_id)) == NULL) + return 0; + for(i=0;i<MAX_GUILDALLIANCE;i++){ + if( g->alliance[i].guild_id==sd->status.guild_id && + g->alliance[i].opposition==1) + intif_guild_alliance( tsd->status.guild_id,sd->status.guild_id, + tsd->status.account_id,sd->status.account_id,9 ); + } + + // inter鯖へ同盟要請 + intif_guild_alliance( sd->status.guild_id,tsd->status.guild_id, + sd->status.account_id,tsd->status.account_id,0 ); + return 0; + }else{ // 拒否 + sd->guild_alliance=0; + sd->guild_alliance_account=0; + if(tsd!=NULL) + clif_guild_allianceack(tsd,3); + } + return 0; +} +// ギルド関係解消 +int guild_delalliance(struct map_session_data *sd,int guild_id,int flag) +{ + if(agit_flag) { // Disable alliance breaking during woe [Valaris] + clif_displaymessage(sd->fd,"Alliances cannot be broken during Guild Wars!"); + return 0; + } // end addition [Valaris] + + nullpo_retr(0, sd); + + intif_guild_alliance( sd->status.guild_id,guild_id, + sd->status.account_id,0,flag|8 ); + return 0; +} +// ギルド敵対 +int guild_opposition(struct map_session_data *sd,int char_id) +{ + struct map_session_data *tsd=map_id2sd(char_id); + struct guild *g; + int i; + + nullpo_retr(0, sd); + + g=guild_search(sd->status.guild_id); + if(g==NULL || tsd==NULL) + return 0; + + if( guild_get_alliance_count(g,1)>3 ) // 敵対数確認 + clif_guild_oppositionack(sd,1); + + for(i=0;i<MAX_GUILDALLIANCE;i++){ // すでに関係を持っているか確認 + if(g->alliance[i].guild_id==tsd->status.guild_id){ + if(g->alliance[i].opposition==1){ // すでに敵対 + clif_guild_oppositionack(sd,2); + return 0; + }else // 同盟破棄 + intif_guild_alliance( sd->status.guild_id,tsd->status.guild_id, + sd->status.account_id,tsd->status.account_id,8 ); + } + } + + // inter鯖に敵対要請 + intif_guild_alliance( sd->status.guild_id,tsd->status.guild_id, + sd->status.account_id,tsd->status.account_id,1 ); + return 0; +} +// ギルド同盟/敵対通知 +int guild_allianceack(int guild_id1,int guild_id2,int account_id1,int account_id2, + int flag,const char *name1,const char *name2) +{ + struct guild *g[2]; + int guild_id[2]={guild_id1,guild_id2}; + const char *guild_name[2]={name1,name2}; + struct map_session_data *sd[2]={map_id2sd(account_id1),map_id2sd(account_id2)}; + int j,i; + + g[0]=guild_search(guild_id1); + g[1]=guild_search(guild_id2); + + if(sd[0]!=NULL && (flag&0x0f)==0){ + sd[0]->guild_alliance=0; + sd[0]->guild_alliance_account=0; + } + + if(flag&0x70){ // 失敗 + for(i=0;i<2-(flag&1);i++) + if( sd[i]!=NULL ) + clif_guild_allianceack(sd[i],((flag>>4)==i+1)?3:4); + return 0; + } +// if(battle_config.etc_log) +// printf("guild alliance_ack %d %d %d %d %d %s %s\n",guild_id1,guild_id2,account_id1,account_id2,flag,name1,name2); + + if(!(flag&0x08)){ // 関係追加 + for(i=0;i<2-(flag&1);i++) + if(g[i]!=NULL) + for(j=0;j<MAX_GUILDALLIANCE;j++) + if(g[i]->alliance[j].guild_id==0){ + g[i]->alliance[j].guild_id=guild_id[1-i]; + memcpy(g[i]->alliance[j].name,guild_name[1-i],24); + g[i]->alliance[j].opposition=flag&1; + break; + } + }else{ // 関係解消 + for(i=0;i<2-(flag&1);i++){ + if(g[i]!=NULL) + for(j=0;j<MAX_GUILDALLIANCE;j++) + if( g[i]->alliance[j].guild_id==guild_id[1-i] && + g[i]->alliance[j].opposition==(flag&1)){ + g[i]->alliance[j].guild_id=0; + break; + } + if( sd[i]!=NULL ) // 解消通知 + clif_guild_delalliance(sd[i],guild_id[1-i],(flag&1)); + } + } + + if((flag&0x0f)==0){ // 同盟通知 + if( sd[1]!=NULL ) + clif_guild_allianceack(sd[1],2); + }else if((flag&0x0f)==1){ // 敵対通知 + if( sd[0]!=NULL ) + clif_guild_oppositionack(sd[0],0); + } + + + for(i=0;i<2-(flag&1);i++){ // 同盟/敵対リストの再送信 + struct map_session_data *sd; + if(g[i]!=NULL) + for(j=0;j<g[i]->max_member;j++) + if((sd=g[i]->member[j].sd)!=NULL) + clif_guild_allianceinfo(sd); + } + return 0; +} +// ギルド解散通知用 +int guild_broken_sub(void *key,void *data,va_list ap) +{ + struct guild *g=(struct guild *)data; + int guild_id=va_arg(ap,int); + int i,j; + struct map_session_data *sd=NULL; + + nullpo_retr(0, g); + + for(i=0;i<MAX_GUILDALLIANCE;i++){ // 関係を破棄 + if(g->alliance[i].guild_id==guild_id){ + for(j=0;j<g->max_member;j++) + if( (sd=g->member[j].sd)!=NULL ) + clif_guild_delalliance(sd,guild_id,g->alliance[i].opposition); + g->alliance[i].guild_id=0; + } + } + return 0; +} +// ギルド解散通知 +int guild_broken(int guild_id,int flag) +{ + struct guild *g=guild_search(guild_id); + struct map_session_data *sd; + int i; + if(flag!=0 || g==NULL) + return 0; + + for(i=0;i<g->max_member;i++){ // ギルド解散を通知 + if((sd=g->member[i].sd)!=NULL){ + if(sd->state.storage_flag) + storage_guild_storage_quit(sd,1); + sd->status.guild_id=0; + sd->guild_sended=0; + clif_guild_broken(g->member[i].sd,0); + } + } + + numdb_foreach(guild_db,guild_broken_sub,guild_id); + numdb_erase(guild_db,guild_id); + guild_storage_delete(guild_id); + free(g); + return 0; +} + +// ギルド解散 +int guild_break(struct map_session_data *sd,char *name) +{ + struct guild *g; + int i; + + nullpo_retr(0, sd); + + if( (g=guild_search(sd->status.guild_id))==NULL ) + return 0; + if(strcmp(g->name,name)!=0) + return 0; + if(strcmp(sd->status.name,g->master)!=0) + return 0; + for(i=0;i<g->max_member;i++){ + if( g->member[i].account_id>0 && ( + g->member[i].account_id!=sd->status.account_id || + g->member[i].char_id!=sd->status.char_id )) + break; + } + if(i<g->max_member){ + clif_guild_broken(sd,2); + return 0; + } + + intif_guild_break(g->guild_id); + return 0; +} + +// ギルド城データ要求 +int guild_castledataload(int castle_id,int index) +{ + return intif_guild_castle_dataload(castle_id,index); +} +// ギルド城情報所得時イベント追加 +int guild_addcastleinfoevent(int castle_id,int index,const char *name) +{ + struct eventlist *ev; + int code=castle_id|(index<<16); + + if( name==NULL || *name==0 ) + return 0; + + ev=(struct eventlist *)aCalloc(1,sizeof(struct eventlist)); + memcpy(ev->name,name,sizeof(ev->name)); + ev->next=numdb_search(guild_castleinfoevent_db,code); + numdb_insert(guild_castleinfoevent_db,code,ev); + return 0; +} + +// ギルド城データ要求返信 +int guild_castledataloadack(int castle_id,int index,int value) +{ + struct guild_castle *gc=guild_castle_search(castle_id); + int code=castle_id|(index<<16); + struct eventlist *ev,*ev2; + + if(gc==NULL){ + return 0; + } + switch(index){ + case 1: gc->guild_id = value; break; + case 2: gc->economy = value; break; + case 3: gc->defense = value; break; + case 4: gc->triggerE = value; break; + case 5: gc->triggerD = value; break; + case 6: gc->nextTime = value; break; + case 7: gc->payTime = value; break; + case 8: gc->createTime = value; break; + case 9: gc->visibleC = value; break; + case 10: gc->visibleG0 = value; break; + case 11: gc->visibleG1 = value; break; + case 12: gc->visibleG2 = value; break; + case 13: gc->visibleG3 = value; break; + case 14: gc->visibleG4 = value; break; + case 15: gc->visibleG5 = value; break; + case 16: gc->visibleG6 = value; break; + case 17: gc->visibleG7 = value; break; + case 18: gc->Ghp0 = value; break; // guardian HP [Valaris] + case 19: gc->Ghp1 = value; break; + case 20: gc->Ghp2 = value; break; + case 21: gc->Ghp3 = value; break; + case 22: gc->Ghp4 = value; break; + case 23: gc->Ghp5 = value; break; + case 24: gc->Ghp6 = value; break; + case 25: gc->Ghp7 = value; break; // end additions [Valaris] + default: + printf("guild_castledataloadack ERROR!! (Not found index=%d)\n", index); + return 0; + } + if( (ev=numdb_search(guild_castleinfoevent_db,code))!=NULL ){ + numdb_erase(guild_castleinfoevent_db,code); + for(;ev;ev2=ev->next,free(ev),ev=ev2){ + npc_event_do(ev->name); + } + } + return 1; +} +// ギルド城データ変更要求 +int guild_castledatasave(int castle_id,int index,int value) +{ + return intif_guild_castle_datasave(castle_id,index,value); +} + +// ギルド城データ変更通知 +int guild_castledatasaveack(int castle_id,int index,int value) +{ + struct guild_castle *gc=guild_castle_search(castle_id); + if(gc==NULL){ + return 0; + } + switch(index){ + case 1: gc->guild_id = value; break; + case 2: gc->economy = value; break; + case 3: gc->defense = value; break; + case 4: gc->triggerE = value; break; + case 5: gc->triggerD = value; break; + case 6: gc->nextTime = value; break; + case 7: gc->payTime = value; break; + case 8: gc->createTime = value; break; + case 9: gc->visibleC = value; break; + case 10: gc->visibleG0 = value; break; + case 11: gc->visibleG1 = value; break; + case 12: gc->visibleG2 = value; break; + case 13: gc->visibleG3 = value; break; + case 14: gc->visibleG4 = value; break; + case 15: gc->visibleG5 = value; break; + case 16: gc->visibleG6 = value; break; + case 17: gc->visibleG7 = value; break; + case 18: gc->Ghp0 = value; break; // guardian HP [Valaris] + case 19: gc->Ghp1 = value; break; + case 20: gc->Ghp2 = value; break; + case 21: gc->Ghp3 = value; break; + case 22: gc->Ghp4 = value; break; + case 23: gc->Ghp5 = value; break; + case 24: gc->Ghp6 = value; break; + case 25: gc->Ghp7 = value; break; // end additions [Valaris] + default: + printf("guild_castledatasaveack ERROR!! (Not found index=%d)\n", index); + return 0; + } + return 1; +} + +// ギルドデータ一括受信(初期化時) +int guild_castlealldataload(int len,struct guild_castle *gc) +{ + int i; + int n = (len-4) / sizeof(struct guild_castle), ev = -1; + + nullpo_retr(0, gc); + + // イベント付きで要求するデータ位置を探す(最後の占拠データ) + for(i = 0; i < n; i++) { + if ((gc + i)->guild_id) + ev = i; + } + + // 城データ格納とギルド情報要求 + for(i = 0; i < n; i++, gc++) { + struct guild_castle *c = guild_castle_search(gc->castle_id); + if (!c) { + printf("guild_castlealldataload ??\n"); + continue; + } + memcpy(&c->guild_id,&gc->guild_id, + sizeof(struct guild_castle) - ((int)&c->guild_id - (int)c) ); + if( c->guild_id ){ + if(i!=ev) + guild_request_info(c->guild_id); + else + guild_npc_request_info(c->guild_id, "::OnAgitInit"); + } + } + if (ev == -1) + npc_event_doall("OnAgitInit"); + return 0; +} + +int guild_agit_start(void) +{ // Run All NPC_Event[OnAgitStart] + int c = npc_event_doall("OnAgitStart"); + printf("NPC_Event:[OnAgitStart] Run (%d) Events by @AgitStart.\n",c); + return 0; +} + +int guild_agit_end(void) +{ // Run All NPC_Event[OnAgitEnd] + int c = npc_event_doall("OnAgitEnd"); + printf("NPC_Event:[OnAgitEnd] Run (%d) Events by @AgitEnd.\n",c); + return 0; +} + +int guild_gvg_eliminate_timer(int tid,unsigned int tick,int id,int data) +{ // Run One NPC_Event[OnAgitEliminate] + size_t len = strlen((const char*)data); + char *evname=(char*)aCalloc(len + 4,sizeof(char)); + int c=0; + + if(!agit_flag) return 0; // Agit already End + memcpy(evname,(const char *)data,len - 5); + strcpy(evname + len - 5,"Eliminate"); + c = npc_event_do(evname); + printf("NPC_Event:[%s] Run (%d) Events.\n",evname,c); + return 0; +} + +int guild_agit_break(struct mob_data *md) +{ // Run One NPC_Event[OnAgitBreak] + char *evname; + + nullpo_retr(0, md); + + evname=(char *)aCalloc(strlen(md->npc_event) + 1, sizeof(char)); + + strcpy(evname,md->npc_event); +// Now By User to Run [OnAgitBreak] NPC Event... +// It's a little impossible to null point with player disconnect in this! +// But Script will be stop, so nothing... +// Maybe will be changed in the futher.. +// int c = npc_event_do(evname); + if(!agit_flag) return 0; // Agit already End + add_timer(gettick()+battle_config.gvg_eliminate_time,guild_gvg_eliminate_timer,md->bl.m,(int)evname); + return 0; +} + +// [MouseJstr] +// How many castles does this guild have? +int guild_checkcastles(struct guild *g) { + int i,nb_cas=0, id,cas_id=0; + struct guild_castle *gc; + id=g->guild_id; + for(i=0;i<MAX_GUILDCASTLE;i++){ + gc=guild_castle_search(i); + cas_id=gc->guild_id; + if(g->guild_id==cas_id) + nb_cas=nb_cas+1; + } //end for + return nb_cas; +} + +// [MouseJstr] +// is this guild allied with this castle? +int guild_isallied(struct guild *g, struct guild_castle *gc) +{ + int i; + + nullpo_retr(0, g); + + if(g->guild_id == gc->guild_id) + return 1; + + if (gc->guild_id == 0) + return 0; + + + for(i=0;i<MAX_GUILDALLIANCE;i++) + if(g->alliance[i].guild_id == gc->guild_id) { + if(g->alliance[i].opposition == 0) + return 1; + else + return 0; + } + + return 0; +} + +static int guild_db_final(void *key,void *data,va_list ap) +{ + struct guild *g=data; + + free(g); + + return 0; +} +static int castle_db_final(void *key,void *data,va_list ap) +{ + struct guild_castle *gc=data; + + free(gc); + + return 0; +} +static int guild_expcache_db_final(void *key,void *data,va_list ap) +{ + struct guild_expcache *c=data; + + free(c); + + return 0; +} +static int guild_infoevent_db_final(void *key,void *data,va_list ap) +{ + struct eventlist *ev=data; + + free(ev); + + return 0; +} +void do_final_guild(void) +{ + if(guild_db) + numdb_final(guild_db,guild_db_final); + if(castle_db) + numdb_final(castle_db,castle_db_final); + if(guild_expcache_db) + numdb_final(guild_expcache_db,guild_expcache_db_final); + if(guild_infoevent_db) + numdb_final(guild_infoevent_db,guild_infoevent_db_final); + if(guild_castleinfoevent_db) + numdb_final(guild_castleinfoevent_db,guild_infoevent_db_final); +} diff --git a/src/map/guild.h b/src/map/guild.h new file mode 100644 index 0000000..53e91f0 --- /dev/null +++ b/src/map/guild.h @@ -0,0 +1,87 @@ +// $Id: guild.h,v 1.4 2004/09/25 05:32:18 MouseJstr Exp $ +#ifndef _GUILD_H_ +#define _GUILD_H_ + +struct map_session_data; +struct mob_data; +struct guild; +struct guild_member; +struct guild_position; +struct guild_castle; + +int guild_skill_get_inf(int id); +int guild_skill_get_sp(int id,int lv); +int guild_skill_get_range(int id); +int guild_skill_get_max(int id); + +int guild_checkskill(struct guild *g,int id); +int guild_checkcastles(struct guild *g); // [MouseJstr] +int guild_isallied(struct guild *g, struct guild_castle *gc); + +void do_init_guild(void); +struct guild *guild_search(int guild_id); +struct guild *guild_searchname(char *str); +struct guild_castle *guild_castle_search(int gcid); + +struct guild_castle *guild_mapname2gc(char *mapname); + +struct map_session_data *guild_getavailablesd(struct guild *g); +int guild_getindex(struct guild *g,int account_id,int char_id); +int guild_getposition(struct map_session_data *sd,struct guild *g); +int guild_payexp(struct map_session_data *sd,int exp); + +int guild_create(struct map_session_data *sd,char *name); +int guild_created(int account_id,int guild_id); +int guild_request_info(int guild_id); +int guild_recv_noinfo(int guild_id); +int guild_recv_info(struct guild *sg); +int guild_npc_request_info(int guild_id,const char *ev); +int guild_invite(struct map_session_data *sd,int account_id); +int guild_reply_invite(struct map_session_data *sd,int guild_id,int flag); +int guild_member_added(int guild_id,int account_id,int char_id,int flag); +int guild_leave(struct map_session_data *sd,int guild_id, + int account_id,int char_id,const char *mes); +int guild_member_leaved(int guild_id,int account_id,int char_id,int flag, + const char *name,const char *mes); +int guild_explusion(struct map_session_data *sd,int guild_id, + int account_id,int char_id,const char *mes); +int guild_skillup(struct map_session_data *sd,int skill_num); +int guild_reqalliance(struct map_session_data *sd,int account_id); +int guild_reply_reqalliance(struct map_session_data *sd,int account_id,int flag); +int guild_alliance(int guild_id1,int guild_id2,int account_id1,int account_id2); +int guild_allianceack(int guild_id1,int guild_id2,int account_id1,int account_id2, + int flag,const char *name1,const char *name2); +int guild_delalliance(struct map_session_data *sd,int guild_id,int flag); +int guild_opposition(struct map_session_data *sd,int char_id); + +int guild_send_memberinfoshort(struct map_session_data *sd,int online); +int guild_recv_memberinfoshort(int guild_id,int account_id,int char_id,int online,int lv,int class); +int guild_change_memberposition(int guild_id,int account_id,int char_id,int idx); +int guild_memberposition_changed(struct guild *g,int idx,int pos); +int guild_change_position(struct map_session_data *sd,int idx, + int mode,int exp_mode,const char *name); +int guild_position_changed(int guild_id,int idx,struct guild_position *p); +int guild_change_notice(struct map_session_data *sd,int guild_id,const char *mes1,const char *mes2); +int guild_notice_changed(int guild_id,const char *mes1,const char *mes2); +int guild_change_emblem(struct map_session_data *sd,int len,const char *data); +int guild_emblem_changed(int len,int guild_id,int emblem_id,const char *data); +int guild_send_message(struct map_session_data *sd,char *mes,int len); +int guild_recv_message(int guild_id,int account_id,char *mes,int len); +int guild_skillupack(int guild_id,int skill_num,int account_id); +int guild_break(struct map_session_data *sd,char *name); +int guild_broken(int guild_id,int flag); + +int guild_addcastleinfoevent(int castle_id,int index,const char *name); +int guild_castledataload(int castle_id,int index); +int guild_castledataloadack(int castle_id,int index,int value); +int guild_castledatasave(int castle_id,int index,int value); +int guild_castledatasaveack(int castle_id,int index,int value); +int guild_castlealldataload(int len,struct guild_castle *gc); + +int guild_agit_start(void); +int guild_agit_end(void); +int guild_agit_break(struct mob_data *md); + +void do_final_guild(void); + +#endif diff --git a/src/map/intif.c b/src/map/intif.c new file mode 100644 index 0000000..ace0187 --- /dev/null +++ b/src/map/intif.c @@ -0,0 +1,1042 @@ +// $Id: intif.c,v 1.2 2004/09/25 05:32:18 MouseJstr Exp $ +#include <sys/types.h> +#ifdef LCCWIN32 +#include <winsock.h> +#else +#include <sys/socket.h> +#include <netinet/in.h> +#endif +#include <stdio.h> +#include <stdlib.h> +#ifndef LCCWIN32 +#include <sys/time.h> +#include <sys/ioctl.h> +#include <unistd.h> +#include <arpa/inet.h> +#endif +#include <signal.h> +#include <fcntl.h> +#include <string.h> + +#include "socket.h" +#include "timer.h" +#include "map.h" +#include "battle.h" +#include "chrif.h" +#include "clif.h" +#include "pc.h" +#include "intif.h" +#include "storage.h" +#include "party.h" +#include "guild.h" +#include "pet.h" +#include "nullpo.h" + +#ifdef MEMWATCH +#include "memwatch.h" +#endif + +static const int packet_len_table[]={ + -1,-1,27,-1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + -1, 7, 0, 0, 0, 0, 0, 0, -1,11, 0, 0, 0, 0, 0, 0, + 35,-1,11,15, 34,29, 7,-1, 0, 0, 0, 0, 0, 0, 0, 0, + 10,-1,15, 0, 79,19, 7,-1, 0,-1,-1,-1, 14,67,186,-1, + 9, 9,-1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 11,-1, 7, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +}; + +extern int char_fd; // inter serverのfdはchar_fdを使う +#define inter_fd (char_fd) // エイリアス + +//----------------------------------------------------------------- +// inter serverへの送信 + +// pet +int intif_create_pet(int account_id,int char_id,short pet_class,short pet_lv,short pet_egg_id, + short pet_equip,short intimate,short hungry,char rename_flag,char incuvate,char *pet_name) +{ + WFIFOW(inter_fd,0) = 0x3080; + WFIFOL(inter_fd,2) = account_id; + WFIFOL(inter_fd,6) = char_id; + WFIFOW(inter_fd,10) = pet_class; + WFIFOW(inter_fd,12) = pet_lv; + WFIFOW(inter_fd,14) = pet_egg_id; + WFIFOW(inter_fd,16) = pet_equip; + WFIFOW(inter_fd,18) = intimate; + WFIFOW(inter_fd,20) = hungry; + WFIFOB(inter_fd,22) = rename_flag; + WFIFOB(inter_fd,23) = incuvate; + memcpy(WFIFOP(inter_fd,24),pet_name,24); + WFIFOSET(inter_fd,48); + + return 0; +} + +int intif_request_petdata(int account_id,int char_id,int pet_id) +{ + WFIFOW(inter_fd,0) = 0x3081; + WFIFOL(inter_fd,2) = account_id; + WFIFOL(inter_fd,6) = char_id; + WFIFOL(inter_fd,10) = pet_id; + WFIFOSET(inter_fd,14); + + return 0; +} + +int intif_save_petdata(int account_id,struct s_pet *p) +{ + WFIFOW(inter_fd,0) = 0x3082; + WFIFOW(inter_fd,2) = sizeof(struct s_pet) + 8; + WFIFOL(inter_fd,4) = account_id; + memcpy(WFIFOP(inter_fd,8),p,sizeof(struct s_pet)); + WFIFOSET(inter_fd,WFIFOW(inter_fd,2)); + + return 0; +} + +int intif_delete_petdata(int pet_id) +{ + WFIFOW(inter_fd,0) = 0x3083; + WFIFOL(inter_fd,2) = pet_id; + WFIFOSET(inter_fd,6); + + return 0; +} + +// GMメッセージを送信 +int intif_GMmessage(char* mes,int len,int flag) +{ + int lp = (flag&0x10) ? 8 : 4; + WFIFOW(inter_fd,0) = 0x3000; + WFIFOW(inter_fd,2) = lp + len; + WFIFOL(inter_fd,4) = 0x65756c62; + memcpy(WFIFOP(inter_fd,lp), mes, len); + WFIFOSET(inter_fd, WFIFOW(inter_fd,2)); + + return 0; +} + +// The transmission of Wisp/Page to inter-server (player not found on this server) +int intif_wis_message(struct map_session_data *sd, char *nick, char *mes, int mes_len) { + nullpo_retr(0, sd); + + WFIFOW(inter_fd,0) = 0x3001; + WFIFOW(inter_fd,2) = mes_len + 52; + memcpy(WFIFOP(inter_fd,4), sd->status.name, 24); + memcpy(WFIFOP(inter_fd,28), nick, 24); + memcpy(WFIFOP(inter_fd,52), mes, mes_len); + WFIFOSET(inter_fd, WFIFOW(inter_fd,2)); + + if (battle_config.etc_log) + printf("intif_wis_message from %s to %s (message: '%s')\n", sd->status.name, nick, mes); + + return 0; +} + +// The reply of Wisp/page +int intif_wis_replay(int id, int flag) { + WFIFOW(inter_fd,0) = 0x3002; + WFIFOL(inter_fd,2) = id; + WFIFOB(inter_fd,6) = flag; // flag: 0: success to send wisper, 1: target character is not loged in?, 2: ignored by target + WFIFOSET(inter_fd,7); + + if (battle_config.etc_log) + printf("intif_wis_replay: id: %d, flag:%d\n", id, flag); + + return 0; +} + +// The transmission of GM only Wisp/Page from server to inter-server +int intif_wis_message_to_gm(char *Wisp_name, int min_gm_level, char *mes, int mes_len) { + WFIFOW(inter_fd,0) = 0x3003; + WFIFOW(inter_fd,2) = mes_len + 30; + memcpy(WFIFOP(inter_fd,4), Wisp_name, 24); + WFIFOW(inter_fd,28) = (short)min_gm_level; + memcpy(WFIFOP(inter_fd,30), mes, mes_len); + WFIFOSET(inter_fd, WFIFOW(inter_fd,2)); + + if (battle_config.etc_log) + printf("intif_wis_message_to_gm: from: '%s', min level: %d, message: '%s'.\n", Wisp_name, min_gm_level, mes); + + return 0; +} + +// アカウント変数送信 +int intif_saveaccountreg(struct map_session_data *sd) { + int j,p; + + nullpo_retr(0, sd); + + WFIFOW(inter_fd,0) = 0x3004; + WFIFOL(inter_fd,4) = sd->bl.id; + for(j=0,p=8;j<sd->status.account_reg_num;j++,p+=36){ + memcpy(WFIFOP(inter_fd,p),sd->status.account_reg[j].str,32); + WFIFOL(inter_fd,p+32)=sd->status.account_reg[j].value; + } + WFIFOW(inter_fd,2)=p; + WFIFOSET(inter_fd,p); + return 0; +} +// アカウント変数要求 +int intif_request_accountreg(struct map_session_data *sd) +{ + nullpo_retr(0, sd); + + WFIFOW(inter_fd,0) = 0x3005; + WFIFOL(inter_fd,2) = sd->bl.id; + WFIFOSET(inter_fd,6); + return 0; +} + +// 倉庫データ要求 +int intif_request_storage(int account_id) +{ + WFIFOW(inter_fd,0) = 0x3010; + WFIFOL(inter_fd,2) = account_id; + WFIFOSET(inter_fd,6); + return 0; +} +// 倉庫データ送信 +int intif_send_storage(struct storage *stor) +{ + nullpo_retr(0, stor); + WFIFOW(inter_fd,0) = 0x3011; + WFIFOW(inter_fd,2) = sizeof(struct storage)+8; + WFIFOL(inter_fd,4) = stor->account_id; + memcpy( WFIFOP(inter_fd,8),stor, sizeof(struct storage) ); + WFIFOSET(inter_fd,WFIFOW(inter_fd,2)); + return 0; +} + +int intif_request_guild_storage(int account_id,int guild_id) +{ + WFIFOW(inter_fd,0) = 0x3018; + WFIFOL(inter_fd,2) = account_id; + WFIFOL(inter_fd,6) = guild_id; + WFIFOSET(inter_fd,10); + return 0; +} +int intif_send_guild_storage(int account_id,struct guild_storage *gstor) +{ + WFIFOW(inter_fd,0) = 0x3019; + WFIFOW(inter_fd,2) = sizeof(struct guild_storage)+12; + WFIFOL(inter_fd,4) = account_id; + WFIFOL(inter_fd,8) = gstor->guild_id; + memcpy( WFIFOP(inter_fd,12),gstor, sizeof(struct guild_storage) ); + WFIFOSET(inter_fd,WFIFOW(inter_fd,2)); + return 0; +} + +// パーティ作成要求 +int intif_create_party(struct map_session_data *sd,char *name) +{ + nullpo_retr(0, sd); + + WFIFOW(inter_fd,0) = 0x3020; + WFIFOL(inter_fd,2) = sd->status.account_id; + memcpy(WFIFOP(inter_fd, 6),name,24); + memcpy(WFIFOP(inter_fd,30),sd->status.name,24); + memcpy(WFIFOP(inter_fd,54),map[sd->bl.m].name,16); + WFIFOW(inter_fd,70)= sd->status.base_level; + WFIFOSET(inter_fd,72); +// if(battle_config.etc_log) +// printf("intif: create party\n"); + return 0; +} +// パーティ情報要求 +int intif_request_partyinfo(int party_id) +{ + WFIFOW(inter_fd,0) = 0x3021; + WFIFOL(inter_fd,2) = party_id; + WFIFOSET(inter_fd,6); +// if(battle_config.etc_log) +// printf("intif: request party info\n"); + return 0; +} +// パーティ追加要求 +int intif_party_addmember(int party_id,int account_id) +{ + struct map_session_data *sd; + sd=map_id2sd(account_id); +// if(battle_config.etc_log) +// printf("intif: party add member %d %d\n",party_id,account_id); + if(sd!=NULL){ + WFIFOW(inter_fd,0)=0x3022; + WFIFOL(inter_fd,2)=party_id; + WFIFOL(inter_fd,6)=account_id; + memcpy(WFIFOP(inter_fd,10),sd->status.name,24); + memcpy(WFIFOP(inter_fd,34),map[sd->bl.m].name,16); + WFIFOW(inter_fd,50)=sd->status.base_level; + WFIFOSET(inter_fd,52); + } + return 0; +} +// パーティ設定変更 +int intif_party_changeoption(int party_id,int account_id,int exp,int item) +{ + WFIFOW(inter_fd,0)=0x3023; + WFIFOL(inter_fd,2)=party_id; + WFIFOL(inter_fd,6)=account_id; + WFIFOW(inter_fd,10)=exp; + WFIFOW(inter_fd,12)=item; + WFIFOSET(inter_fd,14); + return 0; +} +// パーティ脱退要求 +int intif_party_leave(int party_id,int account_id) +{ +// if(battle_config.etc_log) +// printf("intif: party leave %d %d\n",party_id,account_id); + WFIFOW(inter_fd,0)=0x3024; + WFIFOL(inter_fd,2)=party_id; + WFIFOL(inter_fd,6)=account_id; + WFIFOSET(inter_fd,10); + return 0; +} +// パーティ移動要求 +int intif_party_changemap(struct map_session_data *sd,int online) +{ + if(sd!=NULL){ + WFIFOW(inter_fd,0)=0x3025; + WFIFOL(inter_fd,2)=sd->status.party_id; + WFIFOL(inter_fd,6)=sd->status.account_id; + memcpy(WFIFOP(inter_fd,10),map[sd->bl.m].name,16); + WFIFOB(inter_fd,26)=online; + WFIFOW(inter_fd,27)=sd->status.base_level; + WFIFOSET(inter_fd,29); + } +// if(battle_config.etc_log) +// printf("party: change map\n"); + return 0; +} +// パーティー解散要求 +int intif_break_party(int party_id) +{ + WFIFOW(inter_fd,0)=0x3026; + WFIFOL(inter_fd,2)=party_id; + WFIFOSET(inter_fd,6); + return 0; +} +// パーティ会話送信 +int intif_party_message(int party_id,int account_id,char *mes,int len) +{ +// if(battle_config.etc_log) +// printf("intif_party_message: %s\n",mes); + WFIFOW(inter_fd,0)=0x3027; + WFIFOW(inter_fd,2)=len+12; + WFIFOL(inter_fd,4)=party_id; + WFIFOL(inter_fd,8)=account_id; + memcpy(WFIFOP(inter_fd,12),mes,len); + WFIFOSET(inter_fd,len+12); + return 0; +} +// パーティ競合チェック要求 +int intif_party_checkconflict(int party_id,int account_id,char *nick) +{ + WFIFOW(inter_fd,0)=0x3028; + WFIFOL(inter_fd,2)=party_id; + WFIFOL(inter_fd,6)=account_id; + memcpy(WFIFOP(inter_fd,10),nick,24); + WFIFOSET(inter_fd,34); + return 0; +} + +// ギルド作成要求 +int intif_guild_create(const char *name,const struct guild_member *master) +{ + nullpo_retr(0, master); + + WFIFOW(inter_fd,0)=0x3030; + WFIFOW(inter_fd,2)=sizeof(struct guild_member)+32; + WFIFOL(inter_fd,4)=master->account_id; + memcpy(WFIFOP(inter_fd,8),name,24); + memcpy(WFIFOP(inter_fd,32),master,sizeof(struct guild_member)); + WFIFOSET(inter_fd,WFIFOW(inter_fd,2)); + return 0; +} +// ギルド情報要求 +int intif_guild_request_info(int guild_id) +{ + WFIFOW(inter_fd,0) = 0x3031; + WFIFOL(inter_fd,2) = guild_id; + WFIFOSET(inter_fd,6); + return 0; +} +// ギルドメンバ追加要求 +int intif_guild_addmember(int guild_id,struct guild_member *m) +{ + WFIFOW(inter_fd,0) = 0x3032; + WFIFOW(inter_fd,2) = sizeof(struct guild_member)+8; + WFIFOL(inter_fd,4) = guild_id; + memcpy(WFIFOP(inter_fd,8),m,sizeof(struct guild_member)); + WFIFOSET(inter_fd,WFIFOW(inter_fd,2)); + return 0; +} +// ギルドメンバ脱退/追放要求 +int intif_guild_leave(int guild_id,int account_id,int char_id,int flag,const char *mes) +{ + WFIFOW(inter_fd, 0) = 0x3034; + WFIFOL(inter_fd, 2) = guild_id; + WFIFOL(inter_fd, 6) = account_id; + WFIFOL(inter_fd,10) = char_id; + WFIFOB(inter_fd,14) = flag; + memcpy(WFIFOP(inter_fd,15),mes,40); + WFIFOSET(inter_fd,55); + return 0; +} +// ギルドメンバのオンライン状況/Lv更新要求 +int intif_guild_memberinfoshort(int guild_id, + int account_id,int char_id,int online,int lv,int class) +{ + WFIFOW(inter_fd, 0) = 0x3035; + WFIFOL(inter_fd, 2) = guild_id; + WFIFOL(inter_fd, 6) = account_id; + WFIFOL(inter_fd,10) = char_id; + WFIFOB(inter_fd,14) = online; + WFIFOW(inter_fd,15) = lv; + WFIFOW(inter_fd,17) = class; + WFIFOSET(inter_fd,19); + return 0; +} +// ギルド解散通知 +int intif_guild_break(int guild_id) +{ + WFIFOW(inter_fd, 0) = 0x3036; + WFIFOL(inter_fd, 2) = guild_id; + WFIFOSET(inter_fd,6); + return 0; +} +// ギルド会話送信 +int intif_guild_message(int guild_id,int account_id,char *mes,int len) +{ + WFIFOW(inter_fd,0)=0x3037; + WFIFOW(inter_fd,2)=len+12; + WFIFOL(inter_fd,4)=guild_id; + WFIFOL(inter_fd,8)=account_id; + memcpy(WFIFOP(inter_fd,12),mes,len); + WFIFOSET(inter_fd,len+12); + return 0; +} +// ギルド競合チェック要求 +int intif_guild_checkconflict(int guild_id,int account_id,int char_id) +{ + WFIFOW(inter_fd, 0)=0x3038; + WFIFOL(inter_fd, 2)=guild_id; + WFIFOL(inter_fd, 6)=account_id; + WFIFOL(inter_fd,10)=char_id; + WFIFOSET(inter_fd,14); + return 0; +} +// ギルド基本情報変更要求 +int intif_guild_change_basicinfo(int guild_id,int type,const void *data,int len) +{ + WFIFOW(inter_fd,0)=0x3039; + WFIFOW(inter_fd,2)=len+10; + WFIFOL(inter_fd,4)=guild_id; + WFIFOW(inter_fd,8)=type; + memcpy(WFIFOP(inter_fd,10),data,len); + WFIFOSET(inter_fd,len+10); + return 0; +} +// ギルドメンバ情報変更要求 +int intif_guild_change_memberinfo(int guild_id,int account_id,int char_id, + int type,const void *data,int len) +{ + WFIFOW(inter_fd, 0)=0x303a; + WFIFOW(inter_fd, 2)=len+18; + WFIFOL(inter_fd, 4)=guild_id; + WFIFOL(inter_fd, 8)=account_id; + WFIFOL(inter_fd,12)=char_id; + WFIFOW(inter_fd,16)=type; + memcpy(WFIFOP(inter_fd,18),data,len); + WFIFOSET(inter_fd,len+18); + return 0; +} +// ギルド役職変更要求 +int intif_guild_position(int guild_id,int idx,struct guild_position *p) +{ + WFIFOW(inter_fd,0)=0x303b; + WFIFOW(inter_fd,2)=sizeof(struct guild_position)+12; + WFIFOL(inter_fd,4)=guild_id; + WFIFOL(inter_fd,8)=idx; + memcpy(WFIFOP(inter_fd,12),p,sizeof(struct guild_position)); + WFIFOSET(inter_fd,WFIFOW(inter_fd,2)); + return 0; +} +// ギルドスキルアップ要求 +int intif_guild_skillup(int guild_id,int skill_num,int account_id) +{ + WFIFOW(inter_fd, 0)=0x303c; + WFIFOL(inter_fd, 2)=guild_id; + WFIFOL(inter_fd, 6)=skill_num; + WFIFOL(inter_fd,10)=account_id; + WFIFOSET(inter_fd,14); + return 0; +} +// ギルド同盟/敵対要求 +int intif_guild_alliance(int guild_id1,int guild_id2,int account_id1,int account_id2,int flag) +{ + WFIFOW(inter_fd, 0)=0x303d; + WFIFOL(inter_fd, 2)=guild_id1; + WFIFOL(inter_fd, 6)=guild_id2; + WFIFOL(inter_fd,10)=account_id1; + WFIFOL(inter_fd,14)=account_id2; + WFIFOB(inter_fd,18)=flag; + WFIFOSET(inter_fd,19); + return 0; +} +// ギルド告知変更要求 +int intif_guild_notice(int guild_id,const char *mes1,const char *mes2) +{ + WFIFOW(inter_fd,0)=0x303e; + WFIFOL(inter_fd,2)=guild_id; + memcpy(WFIFOP(inter_fd,6),mes1,60); + memcpy(WFIFOP(inter_fd,66),mes2,120); + WFIFOSET(inter_fd,186); + return 0; +} +// ギルドエンブレム変更要求 +int intif_guild_emblem(int guild_id,int len,const char *data) +{ + if(guild_id<=0 || len<0 || len>2000) + return 0; + WFIFOW(inter_fd,0)=0x303f; + WFIFOW(inter_fd,2)=len+12; + WFIFOL(inter_fd,4)=guild_id; + WFIFOL(inter_fd,8)=0; + memcpy(WFIFOP(inter_fd,12),data,len); + WFIFOSET(inter_fd,len+12); + return 0; +} +//現在のギルド城占領ギルドを調べる +int intif_guild_castle_dataload(int castle_id,int index) +{ + WFIFOW(inter_fd,0)=0x3040; + WFIFOW(inter_fd,2)=castle_id; + WFIFOB(inter_fd,4)=index; + WFIFOSET(inter_fd,5); + return 0; +} + +//ギルド城占領ギルド変更要求 +int intif_guild_castle_datasave(int castle_id,int index, int value) +{ + WFIFOW(inter_fd,0)=0x3041; + WFIFOW(inter_fd,2)=castle_id; + WFIFOB(inter_fd,4)=index; + WFIFOL(inter_fd,5)=value; + WFIFOSET(inter_fd,9); + return 0; +} +//----------------------------------------------------------------- +// Packets receive from inter server + +// Wisp/Page reception +int intif_parse_WisMessage(int fd) { // rewritten by [Yor] + struct map_session_data* sd; + int i; + char *wisp_source; + + if (battle_config.etc_log) + printf("intif_parse_wismessage: id: %d, from: %s, to: %s, message: '%s'\n", RFIFOL(fd,4), RFIFOP(fd,8), RFIFOP(fd,32), RFIFOP(fd,56)); + sd = map_nick2sd(RFIFOP(fd,32)); // Searching destination player + if (sd != NULL && strcmp(sd->status.name, RFIFOP(fd,32)) == 0) { // exactly same name (inter-server have checked the name before) + // if player ignore all + if (sd->ignoreAll == 1) + intif_wis_replay(RFIFOL(fd,4), 2); // flag: 0: success to send wisper, 1: target character is not loged in?, 2: ignored by target + else { + wisp_source = RFIFOP(fd,8); // speed up + // if player ignore the source character + for(i = 0; i < (sizeof(sd->ignore) / sizeof(sd->ignore[0])); i++) + if (strcmp(sd->ignore[i].name, wisp_source) == 0) { + intif_wis_replay(RFIFOL(fd,4), 2); // flag: 0: success to send wisper, 1: target character is not loged in?, 2: ignored by target + break; + } + // if source player not found in ignore list + if (i == (sizeof(sd->ignore) / sizeof(sd->ignore[0]))) { + clif_wis_message(sd->fd,RFIFOP(fd,8),RFIFOP(fd,56),RFIFOW(fd,2)-56); + intif_wis_replay(RFIFOL(fd,4), 0); // flag: 0: success to send wisper, 1: target character is not loged in?, 2: ignored by target + } + } + } else + intif_wis_replay(RFIFOL(fd,4), 1); // flag: 0: success to send wisper, 1: target character is not loged in?, 2: ignored by target + return 0; +} + +// Wisp/page transmission result reception +int intif_parse_WisEnd(int fd) { + struct map_session_data* sd; + + if (battle_config.etc_log) + printf("intif_parse_wisend: player: %s, flag: %d\n", RFIFOP(fd,2), RFIFOB(fd,26)); // flag: 0: success to send wisper, 1: target character is not loged in?, 2: ignored by target + sd = map_nick2sd(RFIFOP(fd,2)); + if (sd != NULL) + clif_wis_end(sd->fd, RFIFOB(fd,26)); + + return 0; +} + +// Received wisp message from map-server via char-server for ALL gm +int mapif_parse_WisToGM(int fd) { // 0x3003/0x3803 <packet_len>.w <wispname>.24B <min_gm_level>.w <message>.?B + int i, min_gm_level; + struct map_session_data *pl_sd; + char Wisp_name[24]; + char mbuf[255]; + char *message = ((RFIFOW(fd,2) - 30) >= sizeof(mbuf)) ? (char *) malloc((RFIFOW(fd,2) - 30)) : mbuf; + + min_gm_level = (int)RFIFOW(fd,28); + memcpy(Wisp_name, RFIFOP(fd,4), 24); + Wisp_name[23] = '\0'; + memcpy(message, RFIFOP(fd,30), RFIFOW(fd,2) - 30); + message[sizeof(message) - 1] = '\0'; + // information is sended to all online GM + for (i = 0; i < fd_max; i++) + if (session[i] && (pl_sd = session[i]->session_data) && pl_sd->state.auth) + if (pc_isGM(pl_sd) >= min_gm_level) + clif_wis_message(i, Wisp_name, message, strlen(message) + 1); + + if (message != mbuf) + free(message); + + return 0; +} + +// アカウント変数通知 +int intif_parse_AccountReg(int fd) { + int j,p; + struct map_session_data *sd; + + if( (sd=map_id2sd(RFIFOL(fd,4)))==NULL ) + return 1; + for(p=8,j=0;p<RFIFOW(fd,2) && j<ACCOUNT_REG_NUM;p+=36,j++){ + memcpy(sd->status.account_reg[j].str,RFIFOP(fd,p),32); + sd->status.account_reg[j].value=RFIFOL(fd,p+32); + } + sd->status.account_reg_num = j; +// printf("intif: accountreg\n"); + + return 0; +} + +// 倉庫データ受信 +int intif_parse_LoadStorage(int fd) { + struct storage *stor; + struct map_session_data *sd; + + stor = account2storage( RFIFOL(fd,4)); + if (RFIFOW(fd,2)-8 != sizeof(struct storage)) { + if (battle_config.error_log) + printf("intif_parse_LoadStorage: data size error %d %d\n", RFIFOW(fd,2)-8, sizeof(struct storage)); + return 1; + } + sd=map_id2sd( RFIFOL(fd,4) ); + if(sd==NULL){ + if(battle_config.error_log) + printf("intif_parse_LoadStorage: user not found %d\n",RFIFOL(fd,4)); + return 1; + } + if(battle_config.save_log) + printf("intif_openstorage: %d\n",RFIFOL(fd,4) ); + memcpy(stor,RFIFOP(fd,8),sizeof(struct storage)); + stor->storage_status=1; + sd->state.storage_flag = 0; + clif_storageitemlist(sd,stor); + clif_storageequiplist(sd,stor); + clif_updatestorageamount(sd,stor); + + return 0; +} + +// 倉庫データ送信成功 +int intif_parse_SaveStorage(int fd) +{ + if(battle_config.save_log) + printf("intif_savestorage: done %d %d\n",RFIFOL(fd,2),RFIFOB(fd,6) ); + return 0; +} + +int intif_parse_LoadGuildStorage(int fd) +{ + struct guild_storage *gstor; + struct map_session_data *sd; + int guild_id = RFIFOL(fd,8); + if(guild_id > 0) { + gstor=guild2storage(guild_id); + if(!gstor) { + if(battle_config.error_log) + printf("intif_parse_LoadGuildStorage: error guild_id %d not exist\n",guild_id); + return 1; + } + if( RFIFOW(fd,2)-12 != sizeof(struct guild_storage) ){ + gstor->storage_status = 0; + if(battle_config.error_log) + printf("intif_parse_LoadGuildStorage: data size error %d %d\n",RFIFOW(fd,2)-12 , sizeof(struct guild_storage)); + return 1; + } + sd=map_id2sd( RFIFOL(fd,4) ); + if(sd==NULL){ + if(battle_config.error_log) + printf("intif_parse_LoadGuildStorage: user not found %d\n",RFIFOL(fd,4)); + return 1; + } + if(battle_config.save_log) + printf("intif_open_guild_storage: %d\n",RFIFOL(fd,4) ); + memcpy(gstor,RFIFOP(fd,12),sizeof(struct guild_storage)); + gstor->storage_status = 1; + sd->state.storage_flag = 1; + clif_guildstorageitemlist(sd,gstor); + clif_guildstorageequiplist(sd,gstor); + clif_updateguildstorageamount(sd,gstor); + } + return 0; +} +int intif_parse_SaveGuildStorage(int fd) +{ + if(battle_config.save_log) { + printf("intif_save_guild_storage: done %d %d %d\n",RFIFOL(fd,2),RFIFOL(fd,6),RFIFOB(fd,10) ); + } + return 0; +} + +// パーティ作成可否 +int intif_parse_PartyCreated(int fd) +{ + if(battle_config.etc_log) + printf("intif: party created\n"); + party_created(RFIFOL(fd,2),RFIFOB(fd,6),RFIFOL(fd,7),RFIFOP(fd,11)); + return 0; +} +// パーティ情報 +int intif_parse_PartyInfo(int fd) +{ + if( RFIFOW(fd,2)==8){ + if(battle_config.error_log) + printf("intif: party noinfo %d\n",RFIFOL(fd,4)); + party_recv_noinfo(RFIFOL(fd,4)); + return 0; + } + +// printf("intif: party info %d\n",RFIFOL(fd,4)); + if( RFIFOW(fd,2)!=sizeof(struct party)+4 ){ + if(battle_config.error_log) + printf("intif: party info : data size error %d %d %d\n",RFIFOL(fd,4),RFIFOW(fd,2),sizeof(struct party)+4); + } + party_recv_info((struct party *)RFIFOP(fd,4)); + return 0; +} +// パーティ追加通知 +int intif_parse_PartyMemberAdded(int fd) +{ + if(battle_config.etc_log) + printf("intif: party member added %d %d %d\n",RFIFOL(fd,2),RFIFOL(fd,6),RFIFOB(fd,10)); + party_member_added(RFIFOL(fd,2),RFIFOL(fd,6),RFIFOB(fd,10)); + return 0; +} +// パーティ設定変更通知 +int intif_parse_PartyOptionChanged(int fd) +{ + party_optionchanged(RFIFOL(fd,2),RFIFOL(fd,6),RFIFOW(fd,10),RFIFOW(fd,12),RFIFOB(fd,14)); + return 0; +} +// パーティ脱退通知 +int intif_parse_PartyMemberLeaved(int fd) +{ + if(battle_config.etc_log) + printf("intif: party member leaved %d %d %s\n",RFIFOL(fd,2),RFIFOL(fd,6),RFIFOP(fd,10)); + party_member_leaved(RFIFOL(fd,2),RFIFOL(fd,6),RFIFOP(fd,10)); + return 0; +} +// パーティ解散通知 +int intif_parse_PartyBroken(int fd) +{ + party_broken(RFIFOL(fd,2)); + return 0; +} +// パーティ移動通知 +int intif_parse_PartyMove(int fd) +{ +// if(battle_config.etc_log) +// printf("intif: party move %d %d %s %d %d\n",RFIFOL(fd,2),RFIFOL(fd,6),RFIFOP(fd,10),RFIFOB(fd,26),RFIFOW(fd,27)); + party_recv_movemap(RFIFOL(fd,2),RFIFOL(fd,6),RFIFOP(fd,10),RFIFOB(fd,26),RFIFOW(fd,27)); + return 0; +} +// パーティメッセージ +int intif_parse_PartyMessage(int fd) +{ +// if(battle_config.etc_log) +// printf("intif_parse_PartyMessage: %s\n",RFIFOP(fd,12)); + party_recv_message(RFIFOL(fd,4),RFIFOL(fd,8),RFIFOP(fd,12),RFIFOW(fd,2)-12); + return 0; +} + +// ギルド作成可否 +int intif_parse_GuildCreated(int fd) +{ + guild_created(RFIFOL(fd,2),RFIFOL(fd,6)); + return 0; +} +// ギルド情報 +int intif_parse_GuildInfo(int fd) +{ + if( RFIFOW(fd,2)==8){ + if(battle_config.error_log) + printf("intif: guild noinfo %d\n",RFIFOL(fd,4)); + guild_recv_noinfo(RFIFOL(fd,4)); + return 0; + } + +// if(battle_config.etc_log) +// printf("intif: guild info %d\n",RFIFOL(fd,4)); + if( RFIFOW(fd,2)!=sizeof(struct guild)+4 ){ + if(battle_config.error_log) + printf("intif: guild info : data size error\n %d %d %d",RFIFOL(fd,4),RFIFOW(fd,2),sizeof(struct guild)+4); + } + guild_recv_info((struct guild *)RFIFOP(fd,4)); + return 0; +} +// ギルドメンバ追加通知 +int intif_parse_GuildMemberAdded(int fd) +{ + if(battle_config.etc_log) + printf("intif: guild member added %d %d %d %d\n",RFIFOL(fd,2),RFIFOL(fd,6),RFIFOL(fd,10),RFIFOB(fd,14)); + guild_member_added(RFIFOL(fd,2),RFIFOL(fd,6),RFIFOL(fd,10),RFIFOB(fd,14)); + return 0; +} +// ギルドメンバ脱退/追放通知 +int intif_parse_GuildMemberLeaved(int fd) +{ + guild_member_leaved(RFIFOL(fd,2),RFIFOL(fd,6),RFIFOL(fd,10),RFIFOB(fd,14), + RFIFOP(fd,55),RFIFOP(fd,15)); + return 0; +} + +// ギルドメンバオンライン状態/Lv変更通知 +int intif_parse_GuildMemberInfoShort(int fd) +{ + guild_recv_memberinfoshort(RFIFOL(fd,2),RFIFOL(fd,6),RFIFOL(fd,10),RFIFOB(fd,14),RFIFOW(fd,15),RFIFOW(fd,17)); + return 0; +} +// ギルド解散通知 +int intif_parse_GuildBroken(int fd) +{ + guild_broken(RFIFOL(fd,2),RFIFOB(fd,6)); + return 0; +} + +// ギルド基本情報変更通知 +int intif_parse_GuildBasicInfoChanged(int fd) +{ + int type=RFIFOW(fd,8),guild_id=RFIFOL(fd,4); + void *data=RFIFOP(fd,10); + struct guild *g=guild_search(guild_id); + short dw=*((short *)data); + int dd=*((int *)data); + if( g==NULL ) + return 0; + switch(type){ + case GBI_EXP: g->exp=dd; break; + case GBI_GUILDLV: g->guild_lv=dw; break; + case GBI_SKILLPOINT: g->skill_point=dd; break; + } + return 0; +} +// ギルドメンバ情報変更通知 +int intif_parse_GuildMemberInfoChanged(int fd) +{ + int type=RFIFOW(fd,16),guild_id=RFIFOL(fd,4); + int account_id=RFIFOL(fd,8),char_id=RFIFOL(fd,12); + void *data=RFIFOP(fd,18); + struct guild *g=guild_search(guild_id); + int idx,dd=*((int *)data); + if( g==NULL ) + return 0; + idx=guild_getindex(g,account_id,char_id); + switch(type){ + case GMI_POSITION: + g->member[idx].position=dd; + guild_memberposition_changed(g,idx,dd); + break; + case GMI_EXP: + g->member[idx].exp=dd; + break; + } + return 0; +} + +// ギルド役職変更通知 +int intif_parse_GuildPosition(int fd) +{ + if( RFIFOW(fd,2)!=sizeof(struct guild_position)+12 ){ + if(battle_config.error_log) + printf("intif: guild info : data size error\n %d %d %d",RFIFOL(fd,4),RFIFOW(fd,2),sizeof(struct guild_position)+12); + } + guild_position_changed(RFIFOL(fd,4),RFIFOL(fd,8),(struct guild_position *)RFIFOP(fd,12)); + return 0; +} +// ギルドスキル割り振り通知 +int intif_parse_GuildSkillUp(int fd) +{ + guild_skillupack(RFIFOL(fd,2),RFIFOL(fd,6),RFIFOL(fd,10)); + return 0; +} +// ギルド同盟/敵対通知 +int intif_parse_GuildAlliance(int fd) +{ + guild_allianceack(RFIFOL(fd,2),RFIFOL(fd,6),RFIFOL(fd,10),RFIFOL(fd,14), + RFIFOB(fd,18),RFIFOP(fd,19),RFIFOP(fd,43)); + return 0; +} +// ギルド告知変更通知 +int intif_parse_GuildNotice(int fd) +{ + guild_notice_changed(RFIFOL(fd,2),RFIFOP(fd,6),RFIFOP(fd,66)); + return 0; +} +// ギルドエンブレム変更通知 +int intif_parse_GuildEmblem(int fd) +{ + guild_emblem_changed(RFIFOW(fd,2)-12,RFIFOL(fd,4),RFIFOL(fd,8),RFIFOP(fd,12)); + return 0; +} +// ギルド会話受信 +int intif_parse_GuildMessage(int fd) +{ + guild_recv_message(RFIFOL(fd,4),RFIFOL(fd,8),RFIFOP(fd,12),RFIFOW(fd,2)-12); + return 0; +} +// ギルド城データ要求返信 +int intif_parse_GuildCastleDataLoad(int fd) +{ + return guild_castledataloadack(RFIFOW(fd,2),RFIFOB(fd,4),RFIFOL(fd,5)); +} +// ギルド城データ変更通知 +int intif_parse_GuildCastleDataSave(int fd) +{ + return guild_castledatasaveack(RFIFOW(fd,2),RFIFOB(fd,4),RFIFOL(fd,5)); +} + +// ギルド城データ一括受信(初期化時) +int intif_parse_GuildCastleAllDataLoad(int fd) +{ + return guild_castlealldataload(RFIFOW(fd,2),(struct guild_castle *)RFIFOP(fd,4)); +} + +// pet +int intif_parse_CreatePet(int fd) +{ + pet_get_egg(RFIFOL(fd,2),RFIFOL(fd,7),RFIFOB(fd,6)); + + return 0; +} + +int intif_parse_RecvPetData(int fd) +{ + struct s_pet p; + int len=RFIFOW(fd,2); + if(sizeof(struct s_pet)!=len-9) { + if(battle_config.etc_log) + printf("intif: pet data: data size error %d %d\n",sizeof(struct s_pet),len-9); + } + else{ + memcpy(&p,RFIFOP(fd,9),sizeof(struct s_pet)); + pet_recv_petdata(RFIFOL(fd,4),&p,RFIFOB(fd,8)); + } + + return 0; +} +int intif_parse_SavePetOk(int fd) +{ + if(RFIFOB(fd,6) == 1) { + if(battle_config.error_log) + printf("pet data save failure\n"); + } + + return 0; +} + +int intif_parse_DeletePetOk(int fd) +{ + if(RFIFOB(fd,2) == 1) { + if(battle_config.error_log) + printf("pet data delete failure\n"); + } + + return 0; +} +//----------------------------------------------------------------- +// inter serverからの通信 +// エラーがあれば0(false)を返すこと +// パケットが処理できれば1,パケット長が足りなければ2を返すこと +int intif_parse(int fd) +{ + int packet_len; + int cmd = RFIFOW(fd,0); + // パケットのID確認 + if(cmd<0x3800 || cmd>=0x3800+(sizeof(packet_len_table)/sizeof(packet_len_table[0])) || + packet_len_table[cmd-0x3800]==0){ + return 0; + } + // パケットの長さ確認 + packet_len = packet_len_table[cmd-0x3800]; + if(packet_len==-1){ + if(RFIFOREST(fd)<4) + return 2; + packet_len = RFIFOW(fd,2); + } +// if(battle_config.etc_log) +// printf("intif_parse %d %x %d %d\n",fd,cmd,packet_len,RFIFOREST(fd)); + if(RFIFOREST(fd)<packet_len){ + return 2; + } + // 処理分岐 + switch(cmd){ + case 0x3800: clif_GMmessage(NULL,RFIFOP(fd,4),packet_len-4,0); break; + case 0x3801: intif_parse_WisMessage(fd); break; + case 0x3802: intif_parse_WisEnd(fd); break; + case 0x3803: mapif_parse_WisToGM(fd); break; + case 0x3804: intif_parse_AccountReg(fd); break; + case 0x3810: intif_parse_LoadStorage(fd); break; + case 0x3811: intif_parse_SaveStorage(fd); break; + case 0x3818: intif_parse_LoadGuildStorage(fd); break; + case 0x3819: intif_parse_SaveGuildStorage(fd); break; + case 0x3820: intif_parse_PartyCreated(fd); break; + case 0x3821: intif_parse_PartyInfo(fd); break; + case 0x3822: intif_parse_PartyMemberAdded(fd); break; + case 0x3823: intif_parse_PartyOptionChanged(fd); break; + case 0x3824: intif_parse_PartyMemberLeaved(fd); break; + case 0x3825: intif_parse_PartyMove(fd); break; + case 0x3826: intif_parse_PartyBroken(fd); break; + case 0x3827: intif_parse_PartyMessage(fd); break; + case 0x3830: intif_parse_GuildCreated(fd); break; + case 0x3831: intif_parse_GuildInfo(fd); break; + case 0x3832: intif_parse_GuildMemberAdded(fd); break; + case 0x3834: intif_parse_GuildMemberLeaved(fd); break; + case 0x3835: intif_parse_GuildMemberInfoShort(fd); break; + case 0x3836: intif_parse_GuildBroken(fd); break; + case 0x3837: intif_parse_GuildMessage(fd); break; + case 0x3839: intif_parse_GuildBasicInfoChanged(fd); break; + case 0x383a: intif_parse_GuildMemberInfoChanged(fd); break; + case 0x383b: intif_parse_GuildPosition(fd); break; + case 0x383c: intif_parse_GuildSkillUp(fd); break; + case 0x383d: intif_parse_GuildAlliance(fd); break; + case 0x383e: intif_parse_GuildNotice(fd); break; + case 0x383f: intif_parse_GuildEmblem(fd); break; + case 0x3840: intif_parse_GuildCastleDataLoad(fd); break; + case 0x3841: intif_parse_GuildCastleDataSave(fd); break; + case 0x3842: intif_parse_GuildCastleAllDataLoad(fd); break; + case 0x3880: intif_parse_CreatePet(fd); break; + case 0x3881: intif_parse_RecvPetData(fd); break; + case 0x3882: intif_parse_SavePetOk(fd); break; + case 0x3883: intif_parse_DeletePetOk(fd); break; + default: + if(battle_config.error_log) + printf("intif_parse : unknown packet %d %x\n",fd,RFIFOW(fd,0)); + return 0; + } + // パケット読み飛ばし + RFIFOSKIP(fd,packet_len); + return 1; +} diff --git a/src/map/intif.h b/src/map/intif.h new file mode 100644 index 0000000..85e1914 --- /dev/null +++ b/src/map/intif.h @@ -0,0 +1,56 @@ +// $Id: intif.h,v 1.2 2004/09/25 05:32:18 MouseJstr Exp $ +#ifndef _INTIF_H_ +#define _INFIF_H_ + +int intif_parse(int fd); + +int intif_GMmessage(char* mes,int len,int flag); + +int intif_wis_message(struct map_session_data *sd,char *nick,char *mes,int mes_len); +int intif_wis_message_to_gm(char *Wisp_name, int min_gm_level, char *mes, int mes_len); + +int intif_saveaccountreg(struct map_session_data *sd); +int intif_request_accountreg(struct map_session_data *sd); + +int intif_request_storage(int account_id); +int intif_send_storage(struct storage *stor); +int intif_request_guild_storage(int account_id, int guild_id); +int intif_send_guild_storage(int account_id, struct guild_storage *gstor); + + +int intif_create_party(struct map_session_data *sd,char *name); +int intif_request_partyinfo(int party_id); +int intif_party_addmember(int party_id, int account_id); +int intif_party_changeoption(int party_id, int account_id, int exp, int item); +int intif_party_leave(int party_id, int accound_id); +int intif_party_changemap(struct map_session_data *sd, int online); +int intif_break_party(int party_id); +int intif_party_message(int party_id, int account_id, char *mes,int len); +int intif_party_checkconflict(int party_id, int account_id, char *nick); + + +int intif_guild_create(const char *name, const struct guild_member *master); +int intif_guild_request_info(int guild_id); +int intif_guild_addmember(int guild_id, struct guild_member *m); +int intif_guild_leave(int guild_id, int account_id, int char_id, int flag, const char *mes); +int intif_guild_memberinfoshort(int guild_id, int account_id, int char_id, int online, int lv, int class); +int intif_guild_break(int guild_id); +int intif_guild_message(int guild_id, int account_id, char *mes, int len); +int intif_guild_checkconflict(int guild_id, int account_id, int char_id); +int intif_guild_change_basicinfo(int guild_id, int type, const void *data, int len); +int intif_guild_change_memberinfo(int guild_id, int account_id, int char_id, int type, const void *data, int len); +int intif_guild_position(int guild_id, int idx, struct guild_position *p); +int intif_guild_skillup(int guild_id, int skill_num, int account_id); +int intif_guild_alliance(int guild_id1, int guild_id2, int account_id1, int account_id2, int flag); +int intif_guild_notice(int guild_id, const char *mes1, const char *mes2); +int intif_guild_emblem(int guild_id, int len, const char *data); +int intif_guild_castle_dataload(int castle_id, int index); +int intif_guild_castle_datasave(int castle_id, int index, int value); + +int intif_create_pet(int account_id, int char_id, short pet_type, short pet_lv, short pet_egg_id, + short pet_equip, short intimate, short hungry, char rename_flag, char incuvate, char *pet_name); +int intif_request_petdata(int account_id, int char_id, int pet_id); +int intif_save_petdata(int account_id, struct s_pet *p); +int intif_delete_petdata(int pet_id); + +#endif diff --git a/src/map/itemdb.c b/src/map/itemdb.c new file mode 100644 index 0000000..a225cff --- /dev/null +++ b/src/map/itemdb.c @@ -0,0 +1,882 @@ +// $Id: itemdb.c,v 1.3 2004/09/25 05:32:18 MouseJstr Exp $ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "db.h" +#include "grfio.h" +#include "nullpo.h" +#include "malloc.h" +#include "map.h" +#include "battle.h" +#include "itemdb.h" +#include "script.h" +#include "pc.h" + +#ifdef MEMWATCH +#include "memwatch.h" +#endif + +#define MAX_RANDITEM 2000 + +// ** ITEMDB_OVERRIDE_NAME_VERBOSE ** +// 定義すると、itemdb.txtとgrfで名前が異なる場合、表示します. +//#define ITEMDB_OVERRIDE_NAME_VERBOSE 1 + +static struct dbt* item_db; + +static struct random_item_data blue_box[MAX_RANDITEM],violet_box[MAX_RANDITEM],card_album[MAX_RANDITEM],gift_box[MAX_RANDITEM],scroll[MAX_RANDITEM]; +static int blue_box_count=0,violet_box_count=0,card_album_count=0,gift_box_count=0,scroll_count=0; +static int blue_box_default=0,violet_box_default=0,card_album_default=0,gift_box_default=0,scroll_default=0; + +// Function declarations + +static void itemdb_read(void); +static int itemdb_readdb(void); +#ifndef TXT_ONLY +static int itemdb_read_sqldb(void); +#endif /* not TXT_ONLY */ +static int itemdb_read_randomitem(); +static int itemdb_read_itemavail(void); +static int itemdb_read_itemnametable(void); +static int itemdb_read_noequip(void); +void itemdb_reload(void); + +/*========================================== + * 名前で検索用 + *------------------------------------------ + */ +// name = item alias, so we should find items aliases first. if not found then look for "jname" (full name) +int itemdb_searchname_sub(void *key,void *data,va_list ap) +{ + struct item_data *item=(struct item_data *)data,**dst; + char *str; + str=va_arg(ap,char *); + dst=va_arg(ap,struct item_data **); +// if( strcmpi(item->name,str)==0 || strcmp(item->jname,str)==0 || +// memcmp(item->name,str,24)==0 || memcmp(item->jname,str,24)==0 ) + if( strcmpi(item->name,str)==0 ) //by lupus + *dst=item; + return 0; +} + +/*========================================== + * 名前で検索用 + *------------------------------------------ + */ +int itemdb_searchjname_sub(void *key,void *data,va_list ap) +{ + struct item_data *item=(struct item_data *)data,**dst; + char *str; + str=va_arg(ap,char *); + dst=va_arg(ap,struct item_data **); + if( strcmpi(item->jname,str)==0 ) + *dst=item; + return 0; +} +/*========================================== + * 名前で検索 + *------------------------------------------ + */ +struct item_data* itemdb_searchname(const char *str) +{ + struct item_data *item=NULL; + numdb_foreach(item_db,itemdb_searchname_sub,str,&item); + return item; +} + +/*========================================== + * 箱系アイテム検索 + *------------------------------------------ + */ +int itemdb_searchrandomid(int flags) +{ + int nameid=0,i,index,count; + struct random_item_data *list=NULL; + + struct { + int nameid,count; + struct random_item_data *list; + } data[] ={ + { 0,0,NULL }, + { blue_box_default ,blue_box_count ,blue_box }, + { violet_box_default,violet_box_count ,violet_box }, + { card_album_default,card_album_count ,card_album }, + { gift_box_default ,gift_box_count ,gift_box }, + { scroll_default ,scroll_count ,scroll }, + }; + + if(flags>=1 && flags<=5){ + nameid=data[flags].nameid; + count=data[flags].count; + list=data[flags].list; + + if(count > 0) { + for(i=0;i<1000;i++) { + index = rand()%count; + if( rand()%1000000 < list[index].per) { + nameid = list[index].nameid; + break; + } + } + } + } + return nameid; +} + +/*========================================== + * DBの存在確認 + *------------------------------------------ + */ +struct item_data* itemdb_exists(int nameid) +{ + return numdb_search(item_db,nameid); +} +/*========================================== + * DBの検索 + *------------------------------------------ + */ +struct item_data* itemdb_search(int nameid) +{ + struct item_data *id; + + id=numdb_search(item_db,nameid); + if(id) return id; + + id=(struct item_data *)aCalloc(1,sizeof(struct item_data)); + numdb_insert(item_db,nameid,id); + + id->nameid=nameid; + id->value_buy=10; + id->value_sell=id->value_buy/2; + id->weight=10; + id->sex=2; + id->elv=0; + id->class=0xffffffff; + id->flag.available=0; + id->flag.value_notdc=0; //一応・・・ + id->flag.value_notoc=0; + id->flag.no_equip=0; + id->view_id=0; + + if(nameid>500 && nameid<600) + id->type=0; //heal item + else if(nameid>600 && nameid<700) + id->type=2; //use item + else if((nameid>700 && nameid<1100) || + (nameid>7000 && nameid<8000)) + id->type=3; //correction + else if(nameid>=1750 && nameid<1771) + id->type=10; //arrow + else if(nameid>1100 && nameid<2000) + id->type=4; //weapon + else if((nameid>2100 && nameid<3000) || + (nameid>5000 && nameid<6000)) + id->type=5; //armor + else if(nameid>4000 && nameid<5000) + id->type=6; //card + else if(nameid>9000 && nameid<10000) + id->type=7; //egg + else if(nameid>10000) + id->type=8; //petequip + + return id; +} + +/*========================================== + * + *------------------------------------------ + */ +int itemdb_isequip(int nameid) +{ + int type=itemdb_type(nameid); + if(type==0 || type==2 || type==3 || type==6 || type==10) + return 0; + return 1; +} +/*========================================== + * + *------------------------------------------ + */ +int itemdb_isequip2(struct item_data *data) +{ + if(data) { + int type=data->type; + if(type==0 || type==2 || type==3 || type==6 || type==10) + return 0; + else + return 1; + } + return 0; +} +/*========================================== + * + *------------------------------------------ + */ +int itemdb_isequip3(int nameid) +{ + int type=itemdb_type(nameid); + if(type==4 || type==5 || type == 8) + return 1; + return 0; +} + +/*========================================== + * 捨てられるアイテムは1、そうでないアイテムは0 + *------------------------------------------ + */ +int itemdb_isdropable(int nameid) +{ + //結婚指輪は捨てられない + switch(nameid){ + case 2634: //結婚指輪 + case 2635: //結婚指輪 + return 0; + } + + return 1; +} + +// +// 初期化 +// +/*========================================== + * + *------------------------------------------ + */ +static int itemdb_read_itemslottable(void) +{ + char *buf,*p; + int s; + + buf=grfio_read("data\\itemslottable.txt"); + if(buf==NULL) + return -1; + s=grfio_size("data\\itemslottable.txt"); + buf[s]=0; + for(p=buf;p-buf<s;){ + int nameid,equip; + sscanf(p,"%d#%d#",&nameid,&equip); + itemdb_search(nameid)->equip=equip; + p=strchr(p,10); + if(!p) break; + p++; + p=strchr(p,10); + if(!p) break; + p++; + } + free(buf); + + return 0; +} + +#ifndef TXT_ONLY +/*==================================== + * Removed item_value_db, don't re-add + *------------------------------------ + */ +static void itemdb_read(void) +{ + itemdb_read_itemslottable(); + + if (db_use_sqldbs) + { + itemdb_read_sqldb(); + } + else + { + itemdb_readdb(); + } + + itemdb_read_randomitem(); + itemdb_read_itemavail(); + itemdb_read_noequip(); + + if (!battle_config.item_name_override_grffile) + itemdb_read_itemnametable(); +} + +#endif /* not TXT_ONLY */ +/*========================================== + * アイテムデータベースの読み込み + *------------------------------------------ + */ +static int itemdb_readdb(void) +{ + FILE *fp; + char line[1024]; + int ln=0,lines=0; + int nameid,j; + char *str[32],*p,*np; + struct item_data *id; + int i=0; + char *filename[]={ "db/item_db.txt","db/item_db2.txt" }; + + for(i=0;i<2;i++){ + + fp=fopen(filename[i],"r"); + if(fp==NULL){ + if(i>0) + continue; + printf("can't read %s\n",filename[i]); + exit(1); + } + + lines=0; + while(fgets(line,1020,fp)){ + lines++; + if(line[0]=='/' && line[1]=='/') + continue; + memset(str,0,sizeof(str)); + for(j=0,np=p=line;j<17 && p;j++){ + str[j]=p; + p=strchr(p,','); + if(p){ *p++=0; np=p; } + } + if(str[0]==NULL) + continue; + + nameid=atoi(str[0]); + if(nameid<=0 || nameid>=20000) + continue; + ln++; + + //ID,Name,Jname,Type,Price,Sell,Weight,ATK,DEF,Range,Slot,Job,Gender,Loc,wLV,eLV,View + id=itemdb_search(nameid); + memcpy(id->name,str[1],24); + memcpy(id->jname,str[2],24); + id->type=atoi(str[3]); + // buy≠sell*2 は item_value_db.txt で指定してください。 + if (atoi(str[5])) { // sell値を優先とする + id->value_buy=atoi(str[5])*2; + id->value_sell=atoi(str[5]); + } else { + id->value_buy=atoi(str[4]); + id->value_sell=atoi(str[4])/2; + } + id->weight=atoi(str[6]); + id->atk=atoi(str[7]); + id->def=atoi(str[8]); + id->range=atoi(str[9]); + id->slot=atoi(str[10]); + id->class=atoi(str[11]); + id->sex=atoi(str[12]); + if(id->equip != atoi(str[13])){ + id->equip=atoi(str[13]); + } + id->wlv=atoi(str[14]); + id->elv=atoi(str[15]); + id->look=atoi(str[16]); + id->flag.available=1; + id->flag.value_notdc=0; + id->flag.value_notoc=0; + id->view_id=0; + + id->use_script=NULL; + id->equip_script=NULL; + + if((p=strchr(np,'{'))==NULL) + continue; + id->use_script = parse_script(p,lines); + if((p=strchr(p+1,'{'))==NULL) + continue; + id->equip_script = parse_script(p,lines); + } + fclose(fp); + printf("read %s done (count=%d)\n",filename[i],ln); + } + return 0; +} + +// Removed item_value_db, don't re-add! + +/*========================================== + * ランダムアイテム出現データの読み込み + *------------------------------------------ + */ +static int itemdb_read_randomitem() +{ + FILE *fp; + char line[1024]; + int ln=0; + int nameid,i,j; + char *str[10],*p; + + const struct { + char filename[64]; + struct random_item_data *pdata; + int *pcount,*pdefault; + } data[] = { + {"db/item_bluebox.txt", blue_box, &blue_box_count, &blue_box_default }, + {"db/item_violetbox.txt", violet_box, &violet_box_count, &violet_box_default }, + {"db/item_cardalbum.txt", card_album, &card_album_count, &card_album_default }, + {"db/item_giftbox.txt", gift_box, &gift_box_count, &gift_box_default }, + {"db/item_scroll.txt", scroll, &scroll_count, &scroll_default }, + }; + + for(i=0;i<sizeof(data)/sizeof(data[0]);i++){ + struct random_item_data *pd=data[i].pdata; + int *pc=data[i].pcount; + int *pdefault=data[i].pdefault; + char *fn=data[i].filename; + + *pdefault = 0; + if( (fp=fopen(fn,"r"))==NULL ){ + printf("can't read %s\n",fn); + continue; + } + + while(fgets(line,1020,fp)){ + if(line[0]=='/' && line[1]=='/') + continue; + memset(str,0,sizeof(str)); + for(j=0,p=line;j<3 && p;j++){ + str[j]=p; + p=strchr(p,','); + if(p) *p++=0; + } + + if(str[0]==NULL) + continue; + + nameid=atoi(str[0]); + if(nameid<0 || nameid>=20000) + continue; + if(nameid == 0) { + if(str[2]) + *pdefault = atoi(str[2]); + continue; + } + + if(str[2]){ + pd[ *pc ].nameid = nameid; + pd[(*pc)++].per = atoi(str[2]); + } + + if(ln >= MAX_RANDITEM) + break; + ln++; + } + fclose(fp); + printf("read %s done (count=%d)\n",fn,*pc); + } + + return 0; +} +/*========================================== + * アイテム使用可能フラグのオーバーライド + *------------------------------------------ + */ +static int itemdb_read_itemavail(void) +{ + FILE *fp; + char line[1024]; + int ln=0; + int nameid,j,k; + char *str[10],*p; + + if( (fp=fopen("db/item_avail.txt","r"))==NULL ){ + printf("can't read db/item_avail.txt\n"); + return -1; + } + + while(fgets(line,1020,fp)){ + struct item_data *id; + if(line[0]=='/' && line[1]=='/') + continue; + memset(str,0,sizeof(str)); + for(j=0,p=line;j<2 && p;j++){ + str[j]=p; + p=strchr(p,','); + if(p) *p++=0; + } + + if(str[0]==NULL) + continue; + + nameid=atoi(str[0]); + if(nameid<0 || nameid>=20000 || !(id=itemdb_exists(nameid)) ) + continue; + k=atoi(str[1]); + if(k > 0) { + id->flag.available = 1; + id->view_id = k; + } + else + id->flag.available = 0; + ln++; + } + fclose(fp); + printf("read db/item_avail.txt done (count=%d)\n",ln); + return 0; +} + +/*========================================== + * アイテムの名前テーブルを読み込む + *------------------------------------------ + */ +static int itemdb_read_itemnametable(void) +{ + char *buf,*p; + int s; + + buf=grfio_reads("data\\idnum2itemdisplaynametable.txt",&s); + + if(buf==NULL) + return -1; + + buf[s]=0; + for(p=buf;p-buf<s;){ + int nameid; + char buf2[64]; + + if( sscanf(p,"%d#%[^#]#",&nameid,buf2)==2 ){ + +#ifdef ITEMDB_OVERRIDE_NAME_VERBOSE + if( itemdb_exists(nameid) && + strncmp(itemdb_search(nameid)->jname,buf2,24)!=0 ){ + printf("[override] %d %s => %s\n",nameid + ,itemdb_search(nameid)->jname,buf2); + } +#endif + + memcpy(itemdb_search(nameid)->jname,buf2,24); + } + + p=strchr(p,10); + if(!p) break; + p++; + } + free(buf); + printf("read data\\idnum2itemdisplaynametable.txt done.\n"); + + return 0; +} +#ifdef TXT_ONLY +/*========================================== + * カードイラストのリソース名前テーブルを読み込む + *------------------------------------------ + */ +static int itemdb_read_cardillustnametable(void) +{ + char *buf,*p; + int s; + + buf=grfio_reads("data\\num2cardillustnametable.txt",&s); + + if(buf==NULL) + return -1; + + buf[s]=0; + for(p=buf;p-buf<s;){ + int nameid; + char buf2[64]; + + if( sscanf(p,"%d#%[^#]#",&nameid,buf2)==2 ){ + strcat(buf2,".bmp"); + memcpy(itemdb_search(nameid)->cardillustname,buf2,64); +// printf("%d %s\n",nameid,itemdb_search(nameid)->cardillustname); + } + + p=strchr(p,10); + if(!p) break; + p++; + } + free(buf); + printf("read data\\num2cardillustnametable.txt done.\n"); + + return 0; +} +#endif /* TXT_ONLY */ +/*========================================== + * 装備制限ファイル読み出し + *------------------------------------------ + */ +static int itemdb_read_noequip(void) +{ + FILE *fp; + char line[1024]; + int ln=0; + int nameid,j; + char *str[32],*p; + struct item_data *id; + + if( (fp=fopen("db/item_noequip.txt","r"))==NULL ){ + printf("can't read db/item_noequip.txt\n"); + return -1; + } + while(fgets(line,1020,fp)){ + if(line[0]=='/' && line[1]=='/') + continue; + memset(str,0,sizeof(str)); + for(j=0,p=line;j<2 && p;j++){ + str[j]=p; + p=strchr(p,','); + if(p) *p++=0; + } + if(str[0]==NULL) + continue; + + nameid=atoi(str[0]); + if(nameid<=0 || nameid>=20000 || !(id=itemdb_exists(nameid))) + continue; + + id->flag.no_equip=atoi(str[1]); + + ln++; + + } + fclose(fp); + printf("read db/item_noequip.txt done (count=%d)\n",ln); + return 0; +} +#ifndef TXT_ONLY + +/*====================================== +* SQL +*=================================== +*/ +static int itemdb_read_sqldb(void) +{ + unsigned short nameid; + struct item_data *id; + char script[65535 + 2 + 1]; // Maximum length of MySQL TEXT type (65535) + 2 bytes for curly brackets + 1 byte for terminator + + // ---------- + + sprintf(tmp_sql, "SELECT * FROM `%s`", item_db_db); + + // Execute the query; if the query execution succeeded... + if (mysql_query(&mmysql_handle, tmp_sql) == 0) + { + sql_res = mysql_store_result(&mmysql_handle); + + // If the storage of the query result succeeded... + if (sql_res) + { + // Parse each row in the query result into sql_row + while ((sql_row = mysql_fetch_row(sql_res))) + { + /* +----+--------------+---------------+------+-----------+------------+--------+--------+---------+-------+-------+------------+---------------+-----------------+--------------+-------------+------+------------+--------------+ + | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | + +----+--------------+---------------+------+-----------+------------+--------+--------+---------+-------+-------+------------+---------------+-----------------+--------------+-------------+------+------------+--------------+ + | id | name_english | name_japanese | type | price_buy | price_sell | weight | attack | defence | range | slots | equip_jobs | equip_genders | equip_locations | weapon_level | equip_level | view | script_use | script_equip | + +----+--------------+---------------+------+-----------+------------+--------+--------+---------+-------+-------+------------+---------------+-----------------+--------------+-------------+------+------------+--------------+ */ + + nameid = atoi(sql_row[0]); + + // If the identifier is not within the valid range, process the next row + if (nameid == 0 || nameid >= 20000) + { + continue; + } + + // Insert a new row into the item database + + /*id = calloc(sizeof(struct item_data), 1); + + if (id == NULL) + { + printf("out of memory : itemdb_read_sqldb\n"); + exit(1); + } + + memset(id, 0, sizeof(struct item_data)); + numdb_insert(item_db, (int) nameid, id);*/ + + // ---------- + id=itemdb_search(nameid); + + memcpy(id->name, sql_row[1], 25); + memcpy(id->jname, sql_row[2], 25); + + id->type = atoi(sql_row[3]); + + // If price_buy is not NULL and price_sell is not NULL... + if ((sql_row[4] != NULL) && (sql_row[5] != NULL)) + { + id->value_buy = atoi(sql_row[4]); + id->value_sell = atoi(sql_row[5]); + } + // If price_buy is not NULL and price_sell is NULL... + else if ((sql_row[4] != NULL) && (sql_row[5] == NULL)) + { + id->value_buy = atoi(sql_row[4]); + id->value_sell = atoi(sql_row[4]) / 2; + } + // If price_buy is NULL and price_sell is not NULL... + else if ((sql_row[4] == NULL) && (sql_row[5] != NULL)) + { + id->value_buy = atoi(sql_row[5]) * 2; + id->value_sell = atoi(sql_row[5]); + } + // If price_buy is NULL and price_sell is NULL... + if ((sql_row[4] == NULL) && (sql_row[5] == NULL)) + { + id->value_buy = 0; + id->value_sell = 0; + } + + id->weight = atoi(sql_row[6]); + + id->atk = (sql_row[7] != NULL) ? atoi(sql_row[7]) : 0; + id->def = (sql_row[8] != NULL) ? atoi(sql_row[8]) : 0; + id->range = (sql_row[9] != NULL) ? atoi(sql_row[9]) : 0; + id->slot = (sql_row[10] != NULL) ? atoi(sql_row[10]) : 0; + id->class = (sql_row[11] != NULL) ? atoi(sql_row[11]) : 0; + id->sex = (sql_row[12] != NULL) ? atoi(sql_row[12]) : 0; + id->equip = (sql_row[13] != NULL) ? atoi(sql_row[13]) : 0; + id->wlv = (sql_row[14] != NULL) ? atoi(sql_row[14]) : 0; + id->elv = (sql_row[15] != NULL) ? atoi(sql_row[15]) : 0; + id->look = (sql_row[16] != NULL) ? atoi(sql_row[16]) : 0; + + id->view_id = 0; + + // ---------- + + if (sql_row[17] != NULL) + { + if (sql_row[17][0] == '{') + id->use_script = parse_script(sql_row[17], 0); + else { + sprintf(script, "{%s}", sql_row[17]); + id->use_script = parse_script(script, 0); + } + } + else + { + id->use_script = NULL; + } + + if (sql_row[18] != NULL) + { + if (sql_row[18][0] == '{') + id->equip_script = parse_script(sql_row[18], 0); + else { + sprintf(script, "{%s}", sql_row[18]); + id->equip_script = parse_script(script, 0); + } + } + else + { + id->equip_script = NULL; + } + + // ---------- + + id->flag.available = 1; + id->flag.value_notdc = 0; + id->flag.value_notoc = 0; + } + + // If the retrieval failed, output an error + if (mysql_errno(&mmysql_handle)) + { + printf("Database server error (retrieving rows from %s): %s\n", item_db_db, mysql_error(&mmysql_handle)); + } + + printf("read %s done (count = %lu)\n", item_db_db, (unsigned long) mysql_num_rows(sql_res)); + } + else + { + printf("MySQL error (storing query result for %s): %s\n", item_db_db, mysql_error(&mmysql_handle)); + } + + // Free the query result + mysql_free_result(sql_res); + } + else + { + printf("Database server error (executing query for %s): %s\n", item_db_db, mysql_error(&mmysql_handle)); + } + + return 0; +} + +#endif /* not TXT_ONLY */ +/*========================================== + * + *------------------------------------------ + */ +static int itemdb_final(void *key,void *data,va_list ap) +{ + struct item_data *id; + + nullpo_retr(0, id=data); + + if(id->use_script) + free(id->use_script); + if(id->equip_script) + free(id->equip_script); + free(id); + + return 0; +} + +void itemdb_reload(void) +{ + /* + + <empty item databases> + itemdb_read(); + + */ + + do_init_itemdb(); +} + +/*========================================== + * + *------------------------------------------ + */ +void do_final_itemdb(void) +{ + if(item_db){ + numdb_final(item_db,itemdb_final); + item_db=NULL; + } +} + +/* +static FILE *dfp; +static int itemdebug(void *key,void *data,va_list ap){ +// struct item_data *id=(struct item_data *)data; + fprintf(dfp,"%6d",(int)key); + return 0; +} +void itemdebugtxt() +{ + dfp=fopen("itemdebug.txt","wt"); + numdb_foreach(item_db,itemdebug); + fclose(dfp); +} +*/ +#ifdef TXT_ONLY +/*==================================== + * Removed item_value_db, don't re-add + *------------------------------------ + */ +static void itemdb_read(void) +{ + itemdb_read_itemslottable(); + itemdb_readdb(); + itemdb_read_randomitem(); + itemdb_read_itemavail(); + itemdb_read_noequip(); + itemdb_read_cardillustnametable(); + if (!battle_config.item_name_override_grffile) + itemdb_read_itemnametable(); +} +#endif /* TXT_ONLY */ +/*========================================== + * + *------------------------------------------ + */ +int do_init_itemdb(void) +{ + item_db = numdb_init(); + + itemdb_read(); + + return 0; +} diff --git a/src/map/itemdb.h b/src/map/itemdb.h new file mode 100644 index 0000000..0edfad2 --- /dev/null +++ b/src/map/itemdb.h @@ -0,0 +1,84 @@ +// $Id: itemdb.h,v 1.3 2004/09/25 05:32:18 MouseJstr Exp $ +#ifndef _ITEMDB_H_ +#define _ITEMDB_H_ + +#include "map.h" + +struct item_data { + int nameid; + char name[24],jname[24]; + char prefix[24],suffix[24]; + char cardillustname[64]; + int value_buy; + int value_sell; + int type; + int class; + int sex; + int equip; + int weight; + int atk; + int def; + int range; + int slot; + int look; + int elv; + int wlv; + int refine; + char *use_script; // 回復とかも全部この中でやろうかなと + char *equip_script; // 攻撃,防御の属性設定もこの中で可能かな? + struct { + unsigned available : 1; + unsigned value_notdc : 1; + unsigned value_notoc : 1; + unsigned no_equip : 3; + unsigned no_drop : 1; + unsigned no_use : 1; + } flag; + int view_id; +}; + +struct random_item_data { + int nameid; + int per; +}; + +struct item_data* itemdb_searchname(const char *name); +struct item_data* itemdb_search(int nameid); +struct item_data* itemdb_exists(int nameid); +#define itemdb_type(n) itemdb_search(n)->type +#define itemdb_atk(n) itemdb_search(n)->atk +#define itemdb_def(n) itemdb_search(n)->def +#define itemdb_look(n) itemdb_search(n)->look +#define itemdb_weight(n) itemdb_search(n)->weight +#define itemdb_equip(n) itemdb_search(n)->equip +#define itemdb_usescript(n) itemdb_search(n)->use_script +#define itemdb_equipscript(n) itemdb_search(n)->equip_script +#define itemdb_wlv(n) itemdb_search(n)->wlv +#define itemdb_range(n) itemdb_search(n)->range +#define itemdb_slot(n) itemdb_search(n)->slot +#define itemdb_available(n) (itemdb_exists(n) && itemdb_search(n)->flag.available) +#define itemdb_viewid(n) (itemdb_search(n)->view_id) + +int itemdb_searchrandomid(int flags); + +#define itemdb_value_buy(n) itemdb_search(n)->value_buy +#define itemdb_value_sell(n) itemdb_search(n)->value_sell +#define itemdb_value_notdc(n) itemdb_search(n)->flag.value_notdc +#define itemdb_value_notoc(n) itemdb_search(n)->flag.value_notoc + +int itemdb_isequip(int); +int itemdb_isequip2(struct item_data *); +int itemdb_isequip3(int); +int itemdb_isdropable(int nameid); + +// itemdb_equipマクロとitemdb_equippointとの違いは +// 前者が鯖側dbで定義された値そのものを返すのに対し +// 後者はsessiondataを考慮した鞍側での装備可能場所 +// すべての組み合わせを返す + +void itemdb_reload(void); + +void do_final_itemdb(void); +int do_init_itemdb(void); + +#endif diff --git a/src/map/mail.c b/src/map/mail.c new file mode 100644 index 0000000..e50b1ba --- /dev/null +++ b/src/map/mail.c @@ -0,0 +1,324 @@ +// Mail System for eAthena SQL +// Created by Valaris + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "socket.h" +#include "timer.h" +#include "nullpo.h" + +#include "map.h" +#include "clif.h" +#include "chrif.h" +#include "intif.h" +#include "pc.h" +#include "mail.h" + +char mail_db[32] = "mail"; + +int MAIL_CHECK_TIME = 120000; +int mail_timer; + +#ifdef MEMWATCH +#include "memwatch.h" +#endif + +int mail_check(struct map_session_data *sd,int type) +{ + int i=0,new=0,priority=0; + char message[50]; + + if(sd==NULL) + return 0; + + sprintf(tmp_msql,"SELECT `message_id`,`to_account_id`,`from_char_name`,`read_flag`,`priority`,`check_flag` FROM `%s` WHERE `to_account_id` = \"%d\" ORDER by `message_id`", mail_db, sd->status.account_id); + + if (mysql_query(&mail_handle, tmp_msql)) { + printf("Database server error (executing query for %s): %s\n", mail_db, mysql_error(&mail_handle)); + return 0; + } + + mail_res = mysql_store_result(&mail_handle); + if(mail_res) { + if (mysql_num_rows(mail_res) == 0) { + clif_displaymessage(sd->fd,"You have no messages."); + mysql_free_result(mail_res); + return 0; + } + + while ((mail_row = mysql_fetch_row(mail_res))) { + i++; + + if(!atoi(mail_row[5])) { + sprintf(tmp_msql,"UPDATE `%s` SET `check_flag`='1' WHERE `message_id`= \"%d\"", mail_db, atoi(mail_row[0])); + if(mysql_query(&mail_handle, tmp_msql) ) { + printf("DB server Error (update Read `%s`)- %s\n", mail_db, mysql_error(&mail_handle) ); + } + } + + if(!atoi(mail_row[3])) { + new++; + if(atoi(mail_row[4])) + priority++; + if(type==2 || type==3) { + if(atoi(mail_row[4])) { + sprintf(message, "%d - From : %s (New - Priority)", i, mail_row[2]); + clif_displaymessage(sd->fd, message); + } + + else { + sprintf(message, "%d - From : %s (New)", i, mail_row[2]); + clif_displaymessage(sd->fd, message); + } + } + } + + else if(type==2){ + sprintf(message, "%d - From : %s", i, mail_row[2]); + clif_displaymessage(sd->fd, message); + } + + } + + mysql_free_result(mail_res); + + } else { + printf("MySQL error (storing query result for %s): %s\n", mail_db, mysql_error(&mail_handle)); + return 0; + } + + if(i>0 && new>0 && type==1) { + sprintf(message, "You have %d new messages.", new); + clif_displaymessage(sd->fd, message); + } + if(i>0 && new>0 && priority>0 && type==1) { + sprintf(message, "You have %d unread priority messages.", priority); + clif_displaymessage(sd->fd, message); + } + if(!new) { + clif_displaymessage(sd->fd, "You have no new messages."); + } + + return 0; +} + +int mail_read(struct map_session_data *sd, int message_id) +{ + + char message[80]; + + if(sd==NULL) + return 0; + + sprintf(tmp_msql,"SELECT `message_id`,`to_account_id`,`from_char_name`,`message`,`read_flag`,`priority`,`check_flag` from `%s` WHERE `to_account_id` = \"%d\" ORDER by `message_id` LIMIT %d, 1",mail_db,sd->status.account_id,message_id-1); + + if (mysql_query(&mail_handle, tmp_msql)) { + printf("Database server error (executing query for %s): %s\n", mail_db, mysql_error(&mail_handle)); + return 0; + } + + mail_res = mysql_store_result(&mail_handle); + if(mail_res) { + if (mysql_num_rows(mail_res) == 0) { + mysql_free_result(mail_res); + clif_displaymessage(sd->fd, "Message not found."); + return 0; + } + + if ((mail_row = mysql_fetch_row(mail_res))) { + + if(!atoi(mail_row[6])) { + sprintf(tmp_msql,"UPDATE `%s` SET `check_flag`='1' WHERE `message_id`= \"%d\"", mail_db, atoi(mail_row[0])); + if(mysql_query(&mail_handle, tmp_msql) ) { + printf("DB server Error (update Read `%s`)- %s\n", mail_db, mysql_error(&mail_handle) ); + } + } + + sprintf(message, "Reading message from %s", mail_row[2]); + clif_displaymessage(sd->fd, message); + + sprintf(message, "%s", mail_row[3]); + clif_displaymessage(sd->fd, message); + + sprintf(tmp_msql,"UPDATE `%s` SET `read_flag`='1' WHERE `message_id`= \"%d\"", mail_db, atoi(mail_row[0])); + if(mysql_query(&mail_handle, tmp_msql) ) { + printf("DB server Error (update Read `%s`)- %s\n", mail_db, mysql_error(&mail_handle) ); + } + } + + mysql_free_result(mail_res); + + } else { + printf("MySQL error (storing query result for %s): %s\n", mail_db, mysql_error(&mail_handle)); + return 0; + } + + return 0; +} + +int mail_delete(struct map_session_data *sd, int message_id) +{ + if(sd==NULL) + return 0; + + sprintf(tmp_msql,"SELECT `message_id`,`to_account_id`,`read_flag`,`priority`,`check_flag` from `%s` WHERE `to_account_id` = \"%d\" ORDER by `message_id` LIMIT %d, 1",mail_db,sd->status.account_id,message_id-1); + + if (mysql_query(&mail_handle, tmp_msql)) { + printf("Database server error (executing query for %s): %s\n", mail_db, mysql_error(&mail_handle)); + return 0; + } + + mail_res = mysql_store_result(&mail_handle); + if(mail_res) { + if (mysql_num_rows(mail_res) == 0) { + mysql_free_result(mail_res); + clif_displaymessage(sd->fd, "Message not found."); + return 0; + } + + if ((mail_row = mysql_fetch_row(mail_res))) { + if(!atoi(mail_row[2]) && atoi(mail_row[3])) { + mysql_free_result(mail_res); + clif_displaymessage(sd->fd,"Cannot delete unread priority mail."); + return 0; + } + if(!atoi(mail_row[4])) { + mysql_free_result(mail_res); + clif_displaymessage(sd->fd,"You have recieved new mail, use @listmail before deleting."); + return 0; + } + sprintf(tmp_msql,"DELETE FROM `%s` WHERE `message_id` = \"%d\"", mail_db, atoi(mail_row[0])); + if(mysql_query(&mail_handle, tmp_msql) ) { + mysql_free_result(mail_res); + printf("DB server Error (update Read `%s`)- %s\n", mail_db, mysql_error(&mail_handle) ); + return 0; + } + else clif_displaymessage(sd->fd,"Message deleted."); + } + + mysql_free_result(mail_res); + + } else { + printf("MySQL error (delete query result for %s): %s\n", mail_db, mysql_error(&mail_handle)); + return 0; + } + + return 0; +} + +int mail_send(struct map_session_data *sd, char *name, char *message, int flag) +{ + if(sd==NULL) + return 0; + + if(pc_isGM(sd) < 80 && sd->mail_counter > 0) { + clif_displaymessage(sd->fd,"You must wait 10 minutes before sending another message"); + return 0; + } + + if(strcmp(name,"*")==0) { + if(pc_isGM(sd) < 80) { + clif_displaymessage(sd->fd, "Access Denied."); + return 0; + } + else + sprintf(tmp_msql,"SELECT DISTINCT `account_id` FROM `%s` WHERE `account_id` <> '%d' ORDER BY `account_id`", char_db, sd->status.account_id); + } + else + sprintf(tmp_msql,"SELECT `account_id`,`name` FROM `%s` WHERE `name` = \"%s\"", char_db, name); + + if (mysql_query(&mail_handle, tmp_msql)) { + printf("Database server error (executing query for %s): %s\n", char_db, mysql_error(&mail_handle)); + return 0; + } + + mail_res = mysql_store_result(&mail_handle); + if(mail_res) { + if (mysql_num_rows(mail_res) == 0) { + mysql_free_result(mail_res); + clif_displaymessage(sd->fd,"Character does not exist."); + return 0; + } + + while ((mail_row = mysql_fetch_row(mail_res))) { + if(strcmp(name,"*")==0) { + sprintf(tmp_msql, "INSERT INTO `%s` (`to_account_id`,`from_account_id`,`from_char_name`,`message`,`priority`)" + " VALUES ('%d', '%d', '%s', '%s', '%d')",mail_db, atoi(mail_row[0]), sd->status.account_id, sd->status.name, message, flag); + } + else { + sprintf(tmp_msql, "INSERT INTO `%s` (`to_account_id`,`to_char_name`,`from_account_id`,`from_char_name`,`message`,`priority`)" + " VALUES ('%d', '%s', '%d', '%s', '%s', '%d')",mail_db, atoi(mail_row[0]), mail_row[1], sd->status.account_id, sd->status.name, message, flag); + if(pc_isGM(sd) < 80) + sd->mail_counter=5; + } + + if(mysql_query(&mail_handle, tmp_msql) ) { + mysql_free_result(mail_res); + printf("DB server Error (insert `mail_db`)- %s\n", mysql_error(&mail_handle) ); + return 0; + } + + } + } + + clif_displaymessage(sd->fd,"Mail has been sent."); + + return 0; +} + +int mail_check_timer(int tid,unsigned int tick,int id,int data) +{ + if(mail_timer != tid) + return 0; + + sprintf(tmp_msql,"SELECT DISTINCT `to_account_id` FROM `%s` WHERE `read_flag` = '0' AND `check_flag` = '0'", mail_db); + + if (mysql_query(&mail_handle, tmp_msql)) { + printf("Database server error (executing query for %s): %s\n", char_db, mysql_error(&mail_handle)); + mail_timer=add_timer(gettick()+MAIL_CHECK_TIME,mail_check_timer,0,0); + return 0; + } + + struct map_session_data *sd = NULL; + int i; + + mail_res = mysql_store_result(&mail_handle); + + if (mail_res) { + + if (mysql_num_rows(mail_res) == 0) { + mysql_free_result(mail_res); + mail_timer=add_timer(gettick()+MAIL_CHECK_TIME,mail_check_timer,0,0); + return 0; + } + + while ((mail_row = mysql_fetch_row(mail_res))) { + for (i = 0; i < fd_max; i++) { + if (session[i] && (sd = session[i]->session_data) && sd->state.auth){ + if(pc_isGM(sd) < 80 && sd->mail_counter > 0) + sd->mail_counter--; + if(sd->status.account_id==atoi(mail_row[0])) + clif_displaymessage(sd->fd, "You have new mail."); + } + } + } + } + + sprintf(tmp_msql,"UPDATE `%s` SET `check_flag`='1' WHERE `check_flag`= '0' ", mail_db); + if(mysql_query(&mail_handle, tmp_msql) ) { + printf("DB server Error (update Read `%s`)- %s\n", mail_db, mysql_error(&mail_handle) ); + } + + mail_timer=add_timer(gettick()+MAIL_CHECK_TIME,mail_check_timer,0,0); + return 0; +} + +int do_init_mail(void) +{ + add_timer_func_list(mail_check_timer,"mail_check_timer"); + mail_timer=add_timer(gettick()+MAIL_CHECK_TIME,mail_check_timer,0,0); + return 0; +} + diff --git a/src/map/mail.h b/src/map/mail.h new file mode 100644 index 0000000..6bd8e51 --- /dev/null +++ b/src/map/mail.h @@ -0,0 +1,9 @@ +// Mail System for eAthena +// Created by Valaris + +int mail_check(struct map_session_data *sd, int type); +int mail_read(struct map_session_data *sd, int message_id); +int mail_delete(struct map_session_data *sd, int message_id); +int mail_send(struct map_session_data *sd, char *name, char *message, int flag); + +int do_init_mail(void); diff --git a/src/map/map.c b/src/map/map.c new file mode 100644 index 0000000..040e180 --- /dev/null +++ b/src/map/map.c @@ -0,0 +1,2009 @@ +// $Id: map.c,v 1.6 2004/09/25 17:37:01 MouseJstr Exp $ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdarg.h> +#ifdef LCCWIN32 +#include <winsock.h> +#else +#include <netdb.h> +#endif + +#include "core.h" +#include "timer.h" +#include "db.h" +#include "grfio.h" +#include "malloc.h" + +#include "map.h" +#include "chrif.h" +#include "clif.h" +#include "intif.h" +#include "npc.h" +#include "pc.h" +#include "mob.h" +#include "chat.h" +#include "itemdb.h" +#include "storage.h" +#include "skill.h" +#include "trade.h" +#include "party.h" +#include "battle.h" +#include "script.h" +#include "guild.h" +#include "pet.h" +#include "atcommand.h" +#include "nullpo.h" +#include "socket.h" + +#ifdef MEMWATCH +#include "memwatch.h" +#endif + +#ifndef TXT_ONLY + +#include "mail.h" // mail system [Valaris] + +MYSQL mmysql_handle; +MYSQL_RES* sql_res ; +MYSQL_ROW sql_row ; +char tmp_sql[65535]=""; + +MYSQL lmysql_handle; +MYSQL_RES* lsql_res ; +MYSQL_ROW lsql_row ; +char tmp_lsql[65535]=""; + +MYSQL mail_handle; // mail system [Valaris] +MYSQL_RES* mail_res ; +MYSQL_ROW mail_row ; +char tmp_msql[65535]=""; + +int map_server_port = 3306; +char map_server_ip[16] = "127.0.0.1"; +char map_server_id[32] = "ragnarok"; +char map_server_pw[32] = "ragnarok"; +char map_server_db[32] = "ragnarok"; +int db_use_sqldbs = 0; + +int login_server_port = 3306; +char login_server_ip[16] = "127.0.0.1"; +char login_server_id[32] = "ragnarok"; +char login_server_pw[32] = "ragnarok"; +char login_server_db[32] = "ragnarok"; + +char item_db_db[32] = "item_db"; +char mob_db_db[32] = "mob_db"; +char login_db[32] = "login"; +char login_db_level[32] = "level"; +char login_db_account_id[32] = "account_id"; + +int lowest_gm_level = 1; +int read_gm_interval = 600000; + +char char_db[32] = "char"; + +static int online_timer(int,unsigned int,int,int); + +int CHECK_INTERVAL = 3600000; // [Valaris] +int check_online_timer=0; // [Valaris] + +#endif /* not TXT_ONLY */ +// 極力 staticでローカルに収める +static struct dbt * id_db=NULL; +static struct dbt * map_db=NULL; +static struct dbt * nick_db=NULL; +static struct dbt * charid_db=NULL; + +static int users=0; +static struct block_list *object[MAX_FLOORITEM]; +static int first_free_object_id=0,last_object_id=0; + +#define block_free_max 1048576 +static void *block_free[block_free_max]; +static int block_free_count = 0, block_free_lock = 0; + +#define BL_LIST_MAX 1048576 +static struct block_list *bl_list[BL_LIST_MAX]; +static int bl_list_count = 0; + +struct map_data map[MAX_MAP_PER_SERVER]; +int map_num = 0; + +int map_port=0; + +int autosave_interval = DEFAULT_AUTOSAVE_INTERVAL; +int agit_flag = 0; +int night_flag = 0; // 0=day, 1=night [Yor] + +struct charid2nick { + char nick[24]; + int req_id; +}; + +char motd_txt[256] = "conf/motd.txt"; +char help_txt[256] = "conf/help.txt"; + +char wisp_server_name[24] = "Server"; // can be modified in char-server configuration file + + +/*========================================== + * 全map鯖総計での接続数設定 + * (char鯖から送られてくる) + *------------------------------------------ + */ +void map_setusers(int n) { + users = n; +} + +/*========================================== + * 全map鯖総計での接続数取得 (/wへの応答用) + *------------------------------------------ + */ +int map_getusers(void) { + return users; +} + +// +// block削除の安全性確保処理 +// + +/*========================================== + * blockをfreeするときfreeの変わりに呼ぶ + * ロックされているときはバッファにためる + *------------------------------------------ + */ +int map_freeblock( void *bl ) +{ + if(block_free_lock==0){ + free(bl); + bl = NULL; + } + else{ + if( block_free_count>=block_free_max ) { + if(battle_config.error_log) + printf("map_freeblock: *WARNING* too many free block! %d %d\n", + block_free_count,block_free_lock); + } + else + block_free[block_free_count++]=bl; + } + return block_free_lock; +} +/*========================================== + * blockのfreeを一時的に禁止する + *------------------------------------------ + */ +int map_freeblock_lock(void) { + return ++block_free_lock; +} + +/*========================================== + * blockのfreeのロックを解除する + * このとき、ロックが完全になくなると + * バッファにたまっていたblockを全部削除 + *------------------------------------------ + */ +int map_freeblock_unlock(void) { + if ((--block_free_lock) == 0) { + int i; +// if(block_free_count>0) { +// if(battle_config.error_log) +// printf("map_freeblock_unlock: free %d object\n",block_free_count); +// } + for(i=0;i<block_free_count;i++){ + free(block_free[i]); + block_free[i] = NULL; + } + block_free_count=0; + }else if(block_free_lock<0){ + if(battle_config.error_log) + printf("map_freeblock_unlock: lock count < 0 !\n"); + } + return block_free_lock; +} + + +// +// block化処理 +// +/*========================================== + * map[]のblock_listから繋がっている場合に + * bl->prevにbl_headのアドレスを入れておく + *------------------------------------------ + */ +static struct block_list bl_head; + +/*========================================== + * map[]のblock_listに追加 + * mobは数が多いので別リスト + * + * 既にlink済みかの確認が無い。危険かも + *------------------------------------------ + */ +int map_addblock(struct block_list *bl) +{ + int m,x,y; + + nullpo_retr(0, bl); + + if(bl->prev != NULL){ + if(battle_config.error_log) + printf("map_addblock error : bl->prev!=NULL\n"); + return 0; + } + + m=bl->m; + x=bl->x; + y=bl->y; + if(m<0 || m>=map_num || + x<0 || x>=map[m].xs || + y<0 || y>=map[m].ys) + return 1; + + if(bl->type==BL_MOB){ + bl->next = map[m].block_mob[x/BLOCK_SIZE+(y/BLOCK_SIZE)*map[m].bxs]; + bl->prev = &bl_head; + if(bl->next) bl->next->prev = bl; + map[m].block_mob[x/BLOCK_SIZE+(y/BLOCK_SIZE)*map[m].bxs] = bl; + map[m].block_mob_count[x/BLOCK_SIZE+(y/BLOCK_SIZE)*map[m].bxs]++; + } else { + bl->next = map[m].block[x/BLOCK_SIZE+(y/BLOCK_SIZE)*map[m].bxs]; + bl->prev = &bl_head; + if(bl->next) bl->next->prev = bl; + map[m].block[x/BLOCK_SIZE+(y/BLOCK_SIZE)*map[m].bxs] = bl; + map[m].block_count[x/BLOCK_SIZE+(y/BLOCK_SIZE)*map[m].bxs]++; + if(bl->type==BL_PC) + map[m].users++; + } + + return 0; +} + +/*========================================== + * map[]のblock_listから外す + * prevがNULLの場合listに繋がってない + *------------------------------------------ + */ +int map_delblock(struct block_list *bl) +{ + int b; + nullpo_retr(0, bl); + + // 既にblocklistから抜けている + if(bl->prev==NULL){ + if(bl->next!=NULL){ + // prevがNULLでnextがNULLでないのは有ってはならない + if(battle_config.error_log) + printf("map_delblock error : bl->next!=NULL\n"); + } + return 0; + } + + b = bl->x/BLOCK_SIZE+(bl->y/BLOCK_SIZE)*map[bl->m].bxs; + + if(bl->type==BL_PC) + map[bl->m].users--; + if(bl->next) bl->next->prev = bl->prev; + if(bl->prev==&bl_head){ + // リストの頭なので、map[]のblock_listを更新する + if(bl->type==BL_MOB){ + map[bl->m].block_mob[b] = bl->next; + if((map[bl->m].block_mob_count[b]--) < 0) + map[bl->m].block_mob_count[b] = 0; + } else { + map[bl->m].block[b] = bl->next; + if((map[bl->m].block_count[b]--) < 0) + map[bl->m].block_count[b] = 0; + } + } else { + bl->prev->next = bl->next; + } + bl->next = NULL; + bl->prev = NULL; + + return 0; +} + +/*========================================== + * 周囲のPC人数を数える (現在未使用) + *------------------------------------------ + */ +int map_countnearpc(int m, int x, int y) { + int bx,by,c=0; + struct block_list *bl=NULL; + + if(map[m].users==0) + return 0; + for(by=y/BLOCK_SIZE-AREA_SIZE/BLOCK_SIZE-1;by<=y/BLOCK_SIZE+AREA_SIZE/BLOCK_SIZE+1;by++){ + if(by<0 || by>=map[m].bys) + continue; + for(bx=x/BLOCK_SIZE-AREA_SIZE/BLOCK_SIZE-1;bx<=x/BLOCK_SIZE+AREA_SIZE/BLOCK_SIZE+1;bx++){ + if(bx<0 || bx>=map[m].bxs) + continue; + bl = map[m].block[bx+by*map[m].bxs]; + for(;bl;bl=bl->next){ + if(bl->type==BL_PC) + c++; + } + } + } + return c; +} + +/*========================================== + * セル上のPCとMOBの数を数える (グランドクロス用) + *------------------------------------------ + */ +int map_count_oncell(int m, int x, int y) { + int bx,by; + struct block_list *bl=NULL; + int i,c; + int count = 0; + + if (x < 0 || y < 0 || (x >= map[m].xs) || (y >= map[m].ys)) + return 1; + bx = x/BLOCK_SIZE; + by = y/BLOCK_SIZE; + + bl = map[m].block[bx+by*map[m].bxs]; + c = map[m].block_count[bx+by*map[m].bxs]; + for(i=0;i<c && bl;i++,bl=bl->next){ + if(bl->x == x && bl->y == y && bl->type == BL_PC) count++; + } + bl = map[m].block_mob[bx+by*map[m].bxs]; + c = map[m].block_mob_count[bx+by*map[m].bxs]; + for(i=0;i<c && bl;i++,bl=bl->next){ + if(bl->x == x && bl->y == y) count++; + } + if(!count) count = 1; + return count; +} + + +/*========================================== + * map m (x0,y0)-(x1,y1)内の全objに対して + * funcを呼ぶ + * type!=0 ならその種類のみ + *------------------------------------------ + */ +void map_foreachinarea(int (*func)(struct block_list*,va_list),int m,int x0,int y0,int x1,int y1,int type,...) { + int bx,by; + struct block_list *bl=NULL; + va_list ap=NULL; + int blockcount=bl_list_count,i,c; + + if(m < 0) + return; + va_start(ap,type); + if (x0 < 0) x0 = 0; + if (y0 < 0) y0 = 0; + if (x1 >= map[m].xs) x1 = map[m].xs-1; + if (y1 >= map[m].ys) y1 = map[m].ys-1; + if (type == 0 || type != BL_MOB) + for (by = y0 / BLOCK_SIZE; by <= y1 / BLOCK_SIZE; by++) { + for(bx=x0/BLOCK_SIZE;bx<=x1/BLOCK_SIZE;bx++){ + bl = map[m].block[bx+by*map[m].bxs]; + c = map[m].block_count[bx+by*map[m].bxs]; + for(i=0;i<c && bl;i++,bl=bl->next){ + if(bl && type && bl->type!=type) + continue; + if(bl && bl->x>=x0 && bl->x<=x1 && bl->y>=y0 && bl->y<=y1 && bl_list_count<BL_LIST_MAX) + bl_list[bl_list_count++]=bl; + } + } + } + if(type==0 || type==BL_MOB) + for(by=y0/BLOCK_SIZE;by<=y1/BLOCK_SIZE;by++){ + for(bx=x0/BLOCK_SIZE;bx<=x1/BLOCK_SIZE;bx++){ + bl = map[m].block_mob[bx+by*map[m].bxs]; + c = map[m].block_mob_count[bx+by*map[m].bxs]; + for(i=0;i<c && bl;i++,bl=bl->next){ + if(bl && bl->x>=x0 && bl->x<=x1 && bl->y>=y0 && bl->y<=y1 && bl_list_count<BL_LIST_MAX) + bl_list[bl_list_count++]=bl; + } + } + } + + if(bl_list_count>=BL_LIST_MAX) { + if(battle_config.error_log) + printf("map_foreachinarea: *WARNING* block count too many!\n"); + } + + map_freeblock_lock(); // メモリからの解放を禁止する + + for(i=blockcount;i<bl_list_count;i++) + if(bl_list[i]->prev) // 有効かどうかチェック + func(bl_list[i],ap); + + map_freeblock_unlock(); // 解放を許可する + + va_end(ap); + bl_list_count = blockcount; +} + +/*========================================== + * 矩形(x0,y0)-(x1,y1)が(dx,dy)移動した時の + * 領域外になる領域(矩形かL字形)内のobjに + * 対してfuncを呼ぶ + * + * dx,dyは-1,0,1のみとする(どんな値でもいいっぽい?) + *------------------------------------------ + */ +void map_foreachinmovearea(int (*func)(struct block_list*,va_list),int m,int x0,int y0,int x1,int y1,int dx,int dy,int type,...) { + int bx,by; + struct block_list *bl=NULL; + va_list ap=NULL; + int blockcount=bl_list_count,i,c; + + va_start(ap,type); + if(dx==0 || dy==0){ + // 矩形領域の場合 + if(dx==0){ + if(dy<0){ + y0=y1+dy+1; + } else { + y1=y0+dy-1; + } + } else if(dy==0){ + if(dx<0){ + x0=x1+dx+1; + } else { + x1=x0+dx-1; + } + } + if(x0<0) x0=0; + if(y0<0) y0=0; + if(x1>=map[m].xs) x1=map[m].xs-1; + if(y1>=map[m].ys) y1=map[m].ys-1; + for(by=y0/BLOCK_SIZE;by<=y1/BLOCK_SIZE;by++){ + for(bx=x0/BLOCK_SIZE;bx<=x1/BLOCK_SIZE;bx++){ + bl = map[m].block[bx+by*map[m].bxs]; + c = map[m].block_count[bx+by*map[m].bxs]; + for(i=0;i<c && bl;i++,bl=bl->next){ + if(bl && type && bl->type!=type) + continue; + if(bl && bl->x>=x0 && bl->x<=x1 && bl->y>=y0 && bl->y<=y1 && bl_list_count<BL_LIST_MAX) + bl_list[bl_list_count++]=bl; + } + bl = map[m].block_mob[bx+by*map[m].bxs]; + c = map[m].block_mob_count[bx+by*map[m].bxs]; + for(i=0;i<c && bl;i++,bl=bl->next){ + if(bl && type && bl->type!=type) + continue; + if(bl && bl->x>=x0 && bl->x<=x1 && bl->y>=y0 && bl->y<=y1 && bl_list_count<BL_LIST_MAX) + bl_list[bl_list_count++]=bl; + } + } + } + }else{ + // L字領域の場合 + + if(x0<0) x0=0; + if(y0<0) y0=0; + if(x1>=map[m].xs) x1=map[m].xs-1; + if(y1>=map[m].ys) y1=map[m].ys-1; + for(by=y0/BLOCK_SIZE;by<=y1/BLOCK_SIZE;by++){ + for(bx=x0/BLOCK_SIZE;bx<=x1/BLOCK_SIZE;bx++){ + bl = map[m].block[bx+by*map[m].bxs]; + c = map[m].block_count[bx+by*map[m].bxs]; + for(i=0;i<c && bl;i++,bl=bl->next){ + if(bl && type && bl->type!=type) + continue; + if((bl) && !(bl->x>=x0 && bl->x<=x1 && bl->y>=y0 && bl->y<=y1)) + continue; + if((bl) && ((dx>0 && bl->x<x0+dx) || (dx<0 && bl->x>x1+dx) || + (dy>0 && bl->y<y0+dy) || (dy<0 && bl->y>y1+dy)) && + bl_list_count<BL_LIST_MAX) + bl_list[bl_list_count++]=bl; + } + bl = map[m].block_mob[bx+by*map[m].bxs]; + c = map[m].block_mob_count[bx+by*map[m].bxs]; + for(i=0;i<c && bl;i++,bl=bl->next){ + if(bl && type && bl->type!=type) + continue; + if((bl) && !(bl->x>=x0 && bl->x<=x1 && bl->y>=y0 && bl->y<=y1)) + continue; + if((bl) && ((dx>0 && bl->x<x0+dx) || (dx<0 && bl->x>x1+dx) || + (dy>0 && bl->y<y0+dy) || (dy<0 && bl->y>y1+dy)) && + bl_list_count<BL_LIST_MAX) + bl_list[bl_list_count++]=bl; + } + } + } + + } + + if(bl_list_count>=BL_LIST_MAX) { + if(battle_config.error_log) + printf("map_foreachinarea: *WARNING* block count too many!\n"); + } + + map_freeblock_lock(); // メモリからの解放を禁止する + + for(i=blockcount;i<bl_list_count;i++) + if(bl_list[i]->prev) // 有効かどうかチェック + func(bl_list[i],ap); + + map_freeblock_unlock(); // 解放を許可する + + va_end(ap); + bl_list_count = blockcount; +} + +// -- moonsoul (added map_foreachincell which is a rework of map_foreachinarea but +// which only checks the exact single x/y passed to it rather than an +// area radius - may be more useful in some instances) +// +void map_foreachincell(int (*func)(struct block_list*,va_list),int m,int x,int y,int type,...) { + int bx,by; + struct block_list *bl=NULL; + va_list ap=NULL; + int blockcount=bl_list_count,i,c; + + va_start(ap,type); + + by=y/BLOCK_SIZE; + bx=x/BLOCK_SIZE; + + if(type==0 || type!=BL_MOB) + { + bl = map[m].block[bx+by*map[m].bxs]; + c = map[m].block_count[bx+by*map[m].bxs]; + for(i=0;i<c && bl;i++,bl=bl->next) + { + if(type && bl && bl->type!=type) + continue; + if(bl && bl->x==x && bl->y==y && bl_list_count<BL_LIST_MAX) + bl_list[bl_list_count++]=bl; + } + } + + if(type==0 || type==BL_MOB) + { + bl = map[m].block_mob[bx+by*map[m].bxs]; + c = map[m].block_mob_count[bx+by*map[m].bxs]; + for(i=0;i<c && bl;i++,bl=bl->next) + { + if(bl && bl->x==x && bl->y==y && bl_list_count<BL_LIST_MAX) + bl_list[bl_list_count++]=bl; + } + } + + if(bl_list_count>=BL_LIST_MAX) { + if(battle_config.error_log) + printf("map_foreachincell: *WARNING* block count too many!\n"); + } + + map_freeblock_lock(); // メモリからの解放を禁止する + + for(i=blockcount;i<bl_list_count;i++) + if(bl_list[i]->prev) // 有効かどうかチェック + func(bl_list[i],ap); + + map_freeblock_unlock(); // 解放を許可する + + va_end(ap); + bl_list_count = blockcount; +} + +/*========================================== + * 床アイテムやエフェクト用の一時obj割り当て + * object[]への保存とid_db登録まで + * + * bl->idもこの中で設定して問題無い? + *------------------------------------------ + */ +int map_addobject(struct block_list *bl) { + int i; + if( bl == NULL ){ + printf("map_addobject nullpo?\n"); + return 0; + } + if(first_free_object_id<2 || first_free_object_id>=MAX_FLOORITEM) + first_free_object_id=2; + for(i=first_free_object_id;i<MAX_FLOORITEM;i++) + if(object[i]==NULL) + break; + if(i>=MAX_FLOORITEM){ + if(battle_config.error_log) + printf("no free object id\n"); + return 0; + } + first_free_object_id=i; + if(last_object_id<i) + last_object_id=i; + object[i]=bl; + numdb_insert(id_db,i,bl); + return i; +} + +/*========================================== + * 一時objectの解放 + * map_delobjectのfreeしないバージョン + *------------------------------------------ + */ +int map_delobjectnofree(int id) { + if(object[id]==NULL) + return 0; + + map_delblock(object[id]); + numdb_erase(id_db,id); +// map_freeblock(object[id]); + object[id]=NULL; + + if(first_free_object_id>id) + first_free_object_id=id; + + while(last_object_id>2 && object[last_object_id]==NULL) + last_object_id--; + + return 0; +} + +/*========================================== + * 一時objectの解放 + * block_listからの削除、id_dbからの削除 + * object dataのfree、object[]へのNULL代入 + * + * addとの対称性が無いのが気になる + *------------------------------------------ + */ +int map_delobject(int id) { + struct block_list *obj = object[id]; + + if(obj==NULL) + return 0; + + map_delobjectnofree(id); + map_freeblock(obj); + + return 0; +} + +/*========================================== + * 全一時obj相手にfuncを呼ぶ + * + *------------------------------------------ + */ +void map_foreachobject(int (*func)(struct block_list*,va_list),int type,...) { + int i; + int blockcount=bl_list_count; + va_list ap=NULL; + + va_start(ap,type); + + for(i=2;i<=last_object_id;i++){ + if(object[i]){ + if(type && object[i]->type!=type) + continue; + if(bl_list_count>=BL_LIST_MAX) { + if(battle_config.error_log) + printf("map_foreachobject: too many block !\n"); + } + else + bl_list[bl_list_count++]=object[i]; + } + } + + map_freeblock_lock(); + + for(i=blockcount;i<bl_list_count;i++) + if( bl_list[i]->prev || bl_list[i]->next ) + func(bl_list[i],ap); + + map_freeblock_unlock(); + + va_end(ap); + bl_list_count = blockcount; +} + +/*========================================== + * 床アイテムを消す + * + * data==0の時はtimerで消えた時 + * data!=0の時は拾う等で消えた時として動作 + * + * 後者は、map_clearflooritem(id)へ + * map.h内で#defineしてある + *------------------------------------------ + */ +int map_clearflooritem_timer(int tid,unsigned int tick,int id,int data) { + struct flooritem_data *fitem=NULL; + + fitem = (struct flooritem_data *)object[id]; + if(fitem==NULL || fitem->bl.type!=BL_ITEM || (!data && fitem->cleartimer != tid)){ + if(battle_config.error_log) + printf("map_clearflooritem_timer : error\n"); + return 1; + } + if(data) + delete_timer(fitem->cleartimer,map_clearflooritem_timer); + else if(fitem->item_data.card[0] == (short)0xff00) + intif_delete_petdata(*((long *)(&fitem->item_data.card[1]))); + clif_clearflooritem(fitem,0); + map_delobject(fitem->bl.id); + + return 0; +} + +/*========================================== + * (m,x,y)の周囲rangeマス内の空き(=侵入可能)cellの + * 内から適当なマス目の座標をx+(y<<16)で返す + * + * 現状range=1でアイテムドロップ用途のみ + *------------------------------------------ + */ +int map_searchrandfreecell(int m,int x,int y,int range) { + int free_cell,i,j,c; + + for(free_cell=0,i=-range;i<=range;i++){ + if(i+y<0 || i+y>=map[m].ys) + continue; + for(j=-range;j<=range;j++){ + if(j+x<0 || j+x>=map[m].xs) + continue; + if((c=read_gat(m,j+x,i+y))==1 || c==5) + continue; + free_cell++; + } + } + if(free_cell==0) + return -1; + free_cell=rand()%free_cell; + for(i=-range;i<=range;i++){ + if(i+y<0 || i+y>=map[m].ys) + continue; + for(j=-range;j<=range;j++){ + if(j+x<0 || j+x>=map[m].xs) + continue; + if((c=read_gat(m,j+x,i+y))==1 || c==5) + continue; + if(free_cell==0){ + x+=j; + y+=i; + i=range+1; + break; + } + free_cell--; + } + } + + return x+(y<<16); +} + +/*========================================== + * (m,x,y)を中心に3x3以内に床アイテム設置 + * + * item_dataはamount以外をcopyする + *------------------------------------------ + */ +int map_addflooritem(struct item *item_data,int amount,int m,int x,int y,struct map_session_data *first_sd, + struct map_session_data *second_sd,struct map_session_data *third_sd,int type) { + int xy,r; + unsigned int tick; + struct flooritem_data *fitem=NULL; + + nullpo_retr(0, item_data); + + if((xy=map_searchrandfreecell(m,x,y,1))<0) + return 0; + r=rand(); + + fitem = (struct flooritem_data *)aCalloc(1,sizeof(*fitem)); + fitem->bl.type=BL_ITEM; + fitem->bl.prev = fitem->bl.next = NULL; + fitem->bl.m=m; + fitem->bl.x=xy&0xffff; + fitem->bl.y=(xy>>16)&0xffff; + fitem->first_get_id = 0; + fitem->first_get_tick = 0; + fitem->second_get_id = 0; + fitem->second_get_tick = 0; + fitem->third_get_id = 0; + fitem->third_get_tick = 0; + + fitem->bl.id = map_addobject(&fitem->bl); + if(fitem->bl.id==0){ + free(fitem); + return 0; + } + + tick = gettick(); + if(first_sd) { + fitem->first_get_id = first_sd->bl.id; + if(type) + fitem->first_get_tick = tick + battle_config.mvp_item_first_get_time; + else + fitem->first_get_tick = tick + battle_config.item_first_get_time; + } + if(second_sd) { + fitem->second_get_id = second_sd->bl.id; + if(type) + fitem->second_get_tick = tick + battle_config.mvp_item_first_get_time + battle_config.mvp_item_second_get_time; + else + fitem->second_get_tick = tick + battle_config.item_first_get_time + battle_config.item_second_get_time; + } + if(third_sd) { + fitem->third_get_id = third_sd->bl.id; + if(type) + fitem->third_get_tick = tick + battle_config.mvp_item_first_get_time + battle_config.mvp_item_second_get_time + battle_config.mvp_item_third_get_time; + else + fitem->third_get_tick = tick + battle_config.item_first_get_time + battle_config.item_second_get_time + battle_config.item_third_get_time; + } + + memcpy(&fitem->item_data,item_data,sizeof(*item_data)); + fitem->item_data.amount=amount; + fitem->subx=(r&3)*3+3; + fitem->suby=((r>>2)&3)*3+3; + fitem->cleartimer=add_timer(gettick()+battle_config.flooritem_lifetime,map_clearflooritem_timer,fitem->bl.id,0); + + map_addblock(&fitem->bl); + clif_dropflooritem(fitem); + + return fitem->bl.id; +} + +/*========================================== + * charid_dbへ追加(返信待ちがあれば返信) + *------------------------------------------ + */ +void map_addchariddb(int charid, char *name) { + struct charid2nick *p=NULL; + int req=0; + + p=numdb_search(charid_db,charid); + if(p==NULL){ // データベースにない + p = (struct charid2nick *)aCalloc(1,sizeof(struct charid2nick)); + p->req_id=0; + }else + numdb_erase(charid_db,charid); + + req=p->req_id; + memcpy(p->nick,name,24); + p->req_id=0; + numdb_insert(charid_db,charid,p); + if(req){ // 返信待ちがあれば返信 + struct map_session_data *sd = map_id2sd(req); + if(sd!=NULL) + clif_solved_charname(sd,charid); + } +} + +/*========================================== + * charid_dbへ追加(返信要求のみ) + *------------------------------------------ + */ +int map_reqchariddb(struct map_session_data * sd,int charid) { + struct charid2nick *p=NULL; + + nullpo_retr(0, sd); + + p=numdb_search(charid_db,charid); + if(p!=NULL) // データベースにすでにある + return 0; + p = (struct charid2nick *)aCalloc(1,sizeof(struct charid2nick)); + p->req_id=sd->bl.id; + numdb_insert(charid_db,charid,p); + return 0; +} + +/*========================================== + * id_dbへblを追加 + *------------------------------------------ + */ +void map_addiddb(struct block_list *bl) { + nullpo_retv(bl); + + numdb_insert(id_db,bl->id,bl); +} + +/*========================================== + * id_dbからblを削除 + *------------------------------------------ + */ +void map_deliddb(struct block_list *bl) { + nullpo_retv(bl); + + numdb_erase(id_db,bl->id); +} + +/*========================================== + * nick_dbへsdを追加 + *------------------------------------------ + */ +void map_addnickdb(struct map_session_data *sd) { + nullpo_retv(sd); + + strdb_insert(nick_db,sd->status.name,sd); +} + +/*========================================== + * PCのquit処理 map.c内分 + * + * quit処理の主体が違うような気もしてきた + *------------------------------------------ + */ +int map_quit(struct map_session_data *sd) { + int i; + + nullpo_retr(0, sd); + + if(sd->chatID) // チャットから出る + chat_leavechat(sd); + + if(sd->trade_partner) // 取引を中断する + trade_tradecancel(sd); + + if(sd->party_invite>0) // パーティ勧誘を拒否する + party_reply_invite(sd,sd->party_invite_account,0); + + if(sd->guild_invite>0) // ギルド勧誘を拒否する + guild_reply_invite(sd,sd->guild_invite,0); + if(sd->guild_alliance>0) // ギルド同盟勧誘を拒否する + guild_reply_reqalliance(sd,sd->guild_alliance_account,0); + + party_send_logout(sd); // パーティのログアウトメッセージ送信 + + guild_send_memberinfoshort(sd,0); // ギルドのログアウトメッセージ送信 + + pc_cleareventtimer(sd); // イベントタイマを破棄する + + if(sd->state.storage_flag) + storage_guild_storage_quit(sd,0); + else + storage_storage_quit(sd); // 倉庫を開いてるなら保存する + + skill_castcancel(&sd->bl,0); // 詠唱を中断する + skill_stop_dancing(&sd->bl,1);// ダンス/演奏中断 + + if(sd->sc_data && sd->sc_data[SC_BERSERK].timer!=-1) //バーサーク中の終了はHPを100に + sd->status.hp = 100; + + skill_status_change_clear(&sd->bl,1); // ステータス異常を解除する + skill_clear_unitgroup(&sd->bl); // スキルユニットグループの削除 + skill_cleartimerskill(&sd->bl); + pc_stop_walking(sd,0); + pc_stopattack(sd); + pc_delinvincibletimer(sd); + pc_delspiritball(sd,sd->spiritball,1); + skill_gangsterparadise(sd,0); + + pc_calcstatus(sd,4); + + clif_clearchar_area(&sd->bl,2); + + if(sd->status.pet_id && sd->pd) { + pet_lootitem_drop(sd->pd,sd); + pet_remove_map(sd); + if(sd->pet.intimate <= 0) { + intif_delete_petdata(sd->status.pet_id); + sd->status.pet_id = 0; + sd->pd = NULL; + sd->petDB = NULL; + } + else + intif_save_petdata(sd->status.account_id,&sd->pet); + } + + if(pc_isdead(sd)) + pc_setrestartvalue(sd,2); + pc_makesavestatus(sd); + //クローンスキルで覚えたスキルは消す + for(i=0;i<MAX_SKILL;i++){ + if(sd->status.skill[i].flag == 13){ + sd->status.skill[i].id=0; + sd->status.skill[i].lv=0; + sd->status.skill[i].flag=0; + } + } + chrif_save(sd); + storage_storage_save(sd); + + if( sd->npc_stackbuf && sd->npc_stackbuf != NULL) + free( sd->npc_stackbuf ); + + map_delblock(&sd->bl); + +#ifndef TXT_ONLY + chrif_char_offline(sd); +#endif + + numdb_erase(id_db,sd->bl.id); + strdb_erase(nick_db,sd->status.name); + numdb_erase(charid_db,sd->status.char_id); + + return 0; +} + +/*========================================== + * id番号のPCを探す。居なければNULL + *------------------------------------------ + */ +struct map_session_data * map_id2sd(int id) { +// remove search from db, because: +// 1 - all players, npc, items and mob are in this db (to search, it's not speed, and search in session is more sure) +// 2 - DB seems not always correct. Sometimes, when a player disconnects, its id (account value) is not removed and structure +// point to a memory area that is not more a session_data and value are incorrect (or out of available memory) -> crash +// replaced by searching in all session. +// by searching in session, we are sure that fd, session, and account exist. +/* + struct block_list *bl; + + bl=numdb_search(id_db,id); + if(bl && bl->type==BL_PC) + return (struct map_session_data*)bl; + return NULL; +*/ + int i; + struct map_session_data *sd=NULL; + + for(i = 0; i < fd_max; i++) + if (session[i] && (sd = session[i]->session_data) && sd->bl.id == id) + return sd; + + return NULL; +} + +/*========================================== + * char_id番号の名前を探す + *------------------------------------------ + */ +char * map_charid2nick(int id) { + struct charid2nick *p=numdb_search(charid_db,id); + + if(p==NULL) + return NULL; + if(p->req_id!=0) + return NULL; + return p->nick; +} + + +/*========================================== + * Search session data from a nick name + * (without sensitive case if necessary) + * return map_session_data pointer or NULL + *------------------------------------------ + */ +struct map_session_data * map_nick2sd(char *nick) { + int i, quantity=0, nicklen; + struct map_session_data *sd = NULL; + struct map_session_data *pl_sd = NULL; + + if (nick == NULL) + return NULL; + + nicklen = strlen(nick); + + for (i = 0; i < fd_max; i++) { + if (session[i] && (pl_sd = session[i]->session_data) && pl_sd->state.auth) + // Without case sensitive check (increase the number of similar character names found) + if (strnicmp(pl_sd->status.name, nick, nicklen) == 0) { + // Strict comparison (if found, we finish the function immediatly with correct value) + if (strcmp(pl_sd->status.name, nick) == 0) + return pl_sd; + quantity++; + sd = pl_sd; + } + } + // Here, the exact character name is not found + // We return the found index of a similar account ONLY if there is 1 similar character + if (quantity == 1) + return sd; + + // Exact character name is not found and 0 or more than 1 similar characters have been found ==> we say not found + return NULL; +} + +/*========================================== + * id番号の物を探す + * 一時objectの場合は配列を引くのみ + *------------------------------------------ + */ +struct block_list * map_id2bl(int id) +{ + struct block_list *bl=NULL; + if(id<sizeof(object)/sizeof(object[0])) + bl = object[id]; + else + bl = numdb_search(id_db,id); + + return bl; +} + +/*========================================== + * id_db内の全てにfuncを実行 + *------------------------------------------ + */ +int map_foreachiddb(int (*func)(void*,void*,va_list),...) { + va_list ap=NULL; + + va_start(ap,func); + numdb_foreach(id_db,func,ap); + va_end(ap); + return 0; +} + +/*========================================== + * map.npcへ追加 (warp等の領域持ちのみ) + *------------------------------------------ + */ +int map_addnpc(int m,struct npc_data *nd) { + int i; + if(m<0 || m>=map_num) + return -1; + for(i=0;i<map[m].npc_num && i<MAX_NPC_PER_MAP;i++) + if(map[m].npc[i]==NULL) + break; + if(i==MAX_NPC_PER_MAP){ + if(battle_config.error_log) + printf("too many NPCs in one map %s\n",map[m].name); + return -1; + } + if(i==map[m].npc_num){ + map[m].npc_num++; + } + + nullpo_retr(0, nd); + + map[m].npc[i]=nd; + nd->n = i; + numdb_insert(id_db,nd->bl.id,nd); + + return i; +} + +void map_removenpc(void) { + int i,m,n=0; + + for(m=0;m<map_num;m++) { + for(i=0;i<map[m].npc_num && i<MAX_NPC_PER_MAP;i++) { + if(map[m].npc[i]!=NULL) { + clif_clearchar_area(&map[m].npc[i]->bl,2); + map_delblock(&map[m].npc[i]->bl); + numdb_erase(id_db,map[m].npc[i]->bl.id); + if(map[m].npc[i]->bl.subtype==SCRIPT) { +// free(map[m].npc[i]->u.scr.script); +// free(map[m].npc[i]->u.scr.label_list); + } + free(map[m].npc[i]); + map[m].npc[i] = NULL; + n++; + } + } + } + printf("%d NPCs removed.\n",n); +} + +/*========================================== + * map名からmap番号へ変換 + *------------------------------------------ + */ +int map_mapname2mapid(char *name) { + struct map_data *md=NULL; + + md=strdb_search(map_db,name); + if(md==NULL || md->gat==NULL) + return -1; + return md->m; +} + +/*========================================== + * 他鯖map名からip,port変換 + *------------------------------------------ + */ +int map_mapname2ipport(char *name,int *ip,int *port) { + struct map_data_other_server *mdos=NULL; + + mdos=strdb_search(map_db,name); + if(mdos==NULL || mdos->gat) + return -1; + *ip=mdos->ip; + *port=mdos->port; + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int map_check_dir(int s_dir,int t_dir) { + if(s_dir == t_dir) + return 0; + switch(s_dir) { + case 0: + if(t_dir == 7 || t_dir == 1 || t_dir == 0) + return 0; + break; + case 1: + if(t_dir == 0 || t_dir == 2 || t_dir == 1) + return 0; + break; + case 2: + if(t_dir == 1 || t_dir == 3 || t_dir == 2) + return 0; + break; + case 3: + if(t_dir == 2 || t_dir == 4 || t_dir == 3) + return 0; + break; + case 4: + if(t_dir == 3 || t_dir == 5 || t_dir == 4) + return 0; + break; + case 5: + if(t_dir == 4 || t_dir == 6 || t_dir == 5) + return 0; + break; + case 6: + if(t_dir == 5 || t_dir == 7 || t_dir == 6) + return 0; + break; + case 7: + if(t_dir == 6 || t_dir == 0 || t_dir == 7) + return 0; + break; + } + return 1; +} + +/*========================================== + * 彼我の方向を計算 + *------------------------------------------ + */ +int map_calc_dir( struct block_list *src,int x,int y) { + int dir=0; + int dx,dy; + + nullpo_retr(0, src); + + dx=x-src->x; + dy=y-src->y; + if( dx==0 && dy==0 ){ // 彼我の場所一致 + dir=0; // 上 + }else if( dx>=0 && dy>=0 ){ // 方向的に右上 + dir=7; // 右上 + if( dx*3-1<dy ) dir=0; // 上 + if( dx>dy*3 ) dir=6; // 右 + }else if( dx>=0 && dy<=0 ){ // 方向的に右下 + dir=5; // 右下 + if( dx*3-1<-dy ) dir=4; // 下 + if( dx>-dy*3 ) dir=6; // 右 + }else if( dx<=0 && dy<=0 ){ // 方向的に左下 + dir=3; // 左下 + if( dx*3+1>dy ) dir=4; // 下 + if( dx<dy*3 ) dir=2; // 左 + }else{ // 方向的に左上 + dir=1; // 左上 + if( -dx*3-1<dy ) dir=0; // 上 + if( -dx>dy*3 ) dir=2; // 左 + } + return dir; +} + +// gat系 +/*========================================== + * (m,x,y)の状態を調べる + *------------------------------------------ + */ +int map_getcell(int m,int x,int y) { + if(x<0 || x>=map[m].xs-1 || y<0 || y>=map[m].ys-1) + return 1; + return map[m].gat[x+y*map[m].xs]; +} + +/*========================================== + * (m,x,y)の状態をtにする + *------------------------------------------ + */ +int map_setcell(int m,int x,int y,int t) { + if(x<0 || x>=map[m].xs || y<0 || y>=map[m].ys) + return t; + return map[m].gat[x+y*map[m].xs]=t; +} + +/*========================================== + * 他鯖管理のマップをdbに追加 + *------------------------------------------ + */ +int map_setipport(char *name,unsigned long ip,int port) { + struct map_data *md=NULL; + struct map_data_other_server *mdos=NULL; + + md=strdb_search(map_db,name); + if(md==NULL){ // not exist -> add new data + mdos=(struct map_data_other_server *)aCalloc(1,sizeof(struct map_data_other_server)); + memcpy(mdos->name,name,24); + mdos->gat = NULL; + mdos->ip = ip; + mdos->port = port; + strdb_insert(map_db,mdos->name,mdos); + } else { + if(md->gat){ // local -> check data + if(ip!=clif_getip() || port!=clif_getport()){ + printf("from char server : %s -> %08lx:%d\n",name,ip,port); + return 1; + } + } else { // update + mdos=(struct map_data_other_server *)md; + mdos->ip = ip; + mdos->port = port; + } + } + return 0; +} + +// 初期化周り +/*========================================== + * 水場高さ設定 + *------------------------------------------ + */ +static struct { + char mapname[24]; + int waterheight; +} *waterlist=NULL; + +#define NO_WATER 1000000 + +static int map_waterheight(char *mapname) { + if(waterlist){ + int i; + for(i=0;waterlist[i].mapname[0] && i < MAX_MAP_PER_SERVER;i++) + if(strcmp(waterlist[i].mapname,mapname)==0) + return waterlist[i].waterheight; + } + return NO_WATER; +} + +static void map_readwater(char *watertxt) { + char line[1024],w1[1024]; + FILE *fp=NULL; + int n=0; + + fp=fopen(watertxt,"r"); + if(fp==NULL){ + printf("file not found: %s\n",watertxt); + return; + } + if(waterlist==NULL) + waterlist=aCalloc(MAX_MAP_PER_SERVER,sizeof(*waterlist)); + while(fgets(line,1020,fp) && n < MAX_MAP_PER_SERVER){ + int wh,count; + if(line[0] == '/' && line[1] == '/') + continue; + if((count=sscanf(line,"%s%d",w1,&wh)) < 1){ + continue; + } + strcpy(waterlist[n].mapname,w1); + if(count >= 2) + waterlist[n].waterheight = wh; + else + waterlist[n].waterheight = 3; + n++; + } + fclose(fp); +} + +/*========================================== + * マップ1枚読み込み + *------------------------------------------ + */ +static int map_readmap(int m,char *fn, char *alias) { + unsigned char *gat = ""; + int s; + int x,y,xs,ys; + struct gat_1cell {char type;} *p; + int wh; + size_t size; + + // read & convert fn + gat=grfio_read(fn); + if(gat==NULL) + return -1; + + printf("\rLoading Maps [%d/%d]: %-50s ",m,map_num,fn); + fflush(stdout); + + map[m].m=m; + xs=map[m].xs=*(short*)(gat); + ys=map[m].ys=*(short*)(gat+2); + printf("\n%i %i\n", xs, ys); + map[m].gat = calloc(s = map[m].xs * map[m].ys, 1); + if(map[m].gat==NULL){ + printf("out of memory : map_readmap gat\n"); + exit(1); + } + + map[m].npc_num=0; + map[m].users=0; + memset(&map[m].flag,0,sizeof(map[m].flag)); + if(battle_config.pk_mode) map[m].flag.pvp = 1; // make all maps pvp for pk_mode [Valaris] + wh=map_waterheight(map[m].name); + for(y=0;y<ys;y++){ + p=(struct gat_1cell*)(gat+y*xs+4); + for(x=0;x<xs;x++){ + /*if(wh!=NO_WATER && p->type==0){ + // 水場判定 + map[m].gat[x+y*xs]=(p->high[0]>wh || p->high[1]>wh || p->high[2]>wh || p->high[3]>wh) ? 3 : 0; + } else {*/ + map[m].gat[x+y*xs]=p->type; + //} + p++; + } + } + free(gat); + + map[m].bxs=(xs+BLOCK_SIZE-1)/BLOCK_SIZE; + map[m].bys=(ys+BLOCK_SIZE-1)/BLOCK_SIZE; + size = map[m].bxs * map[m].bys * sizeof(struct block_list*); + + map[m].block = calloc(size, 1); + if(map[m].block == NULL){ + printf("out of memory : map_readmap block\n"); + exit(1); + } + + map[m].block_mob = calloc(size, 1); + if (map[m].block_mob == NULL) { + printf("out of memory : map_readmap block_mob\n"); + exit(1); + } + + size = map[m].bxs*map[m].bys*sizeof(int); + + map[m].block_count = calloc(size, 1); + if(map[m].block_count==NULL){ + printf("out of memory : map_readmap block\n"); + exit(1); + } + memset(map[m].block_count,0,size); + + map[m].block_mob_count=calloc(size, 1); + if(map[m].block_mob_count==NULL){ + printf("out of memory : map_readmap block_mob\n"); + exit(1); + } + memset(map[m].block_mob_count,0,size); + + strdb_insert(map_db,map[m].name,&map[m]); + +// printf("%s read done\n",fn); + + return 0; +} + +/*========================================== + * 全てのmapデータを読み込む + *------------------------------------------ + */ +int map_readallmap(void) { + int i,maps_removed=0; + char fn[256]=""; + + // 先に全部のャbプの存在を確認 + for(i=0;i<map_num;i++){ + if(strstr(map[i].name,".gat")==NULL) + continue; + sprintf(fn,"data\\%s",map[i].name); + if(grfio_size(fn) == -1) { + map_delmap(map[i].name); + maps_removed++; + } + } + for(i=0;i<map_num;i++){ + if(strstr(map[i].name,".gat")!=NULL) { + char *p = strstr(map[i].name, ">"); // [MouseJstr] + if (p != NULL) { + char alias[64]; + *p = '\0'; + strcpy(alias, map[i].name); + strcpy(map[i].name, p + 1); + sprintf(fn,"data\\%s",map[i].name); + if(map_readmap(i,fn, alias) == -1) { + map_delmap(map[i].name); + maps_removed++; + } + } else { + sprintf(fn,"data\\%s",map[i].name); + if(map_readmap(i,fn, NULL) == -1) { + map_delmap(map[i].name); + maps_removed++; + } + } + } + } + + free(waterlist); + printf("\rMaps Loaded: %d %60s\n",map_num,""); + printf("\rMaps Removed: %d \n",maps_removed); + return 0; +} + +/*========================================== + * 読み込むmapを追加する + *------------------------------------------ + */ +int map_addmap(char *mapname) { + if (strcmpi(mapname,"clear")==0) { + map_num=0; + return 0; + } + + if (map_num >= MAX_MAP_PER_SERVER - 1) { + printf("too many map\n"); + return 1; + } + memcpy(map[map_num].name, mapname, 24); + map_num++; + return 0; +} + +/*========================================== + * 読み込むmapを削除する + *------------------------------------------ + */ +int map_delmap(char *mapname) { + int i; + + if (strcmpi(mapname, "all") == 0) { + map_num = 0; + return 0; + } + + for(i = 0; i < map_num; i++) { + if (strcmp(map[i].name, mapname) == 0) { + printf("Removing map [ %s ] from maplist\n",map[i].name); + memmove(map+i, map+i+1, sizeof(map[0])*(map_num-i-1)); + map_num--; + } + } + return 0; +} + +/*========================================== + * 設定ファイルを読み込む + *------------------------------------------ + */ +int map_config_read(char *cfgName) { + char line[1024], w1[1024], w2[1024]; + FILE *fp; + struct hostent *h = NULL; + + fp = fopen(cfgName,"r"); + if (fp == NULL) { + printf("Map configuration file not found at: %s\n", cfgName); + exit(1); + } + while(fgets(line, sizeof(line) -1, fp)) { + if (line[0] == '/' && line[1] == '/') + continue; + if (sscanf(line, "%[^:]: %[^\r\n]", w1, w2) == 2) { + if (strcmpi(w1, "userid")==0){ + chrif_setuserid(w2); + } else if (strcmpi(w1, "passwd") == 0) { + chrif_setpasswd(w2); + } else if (strcmpi(w1, "char_ip") == 0) { + h = gethostbyname (w2); + if(h != NULL) { + printf("Character server IP address : %s -> %d.%d.%d.%d\n", w2, (unsigned char)h->h_addr[0], (unsigned char)h->h_addr[1], (unsigned char)h->h_addr[2], (unsigned char)h->h_addr[3]); + sprintf(w2,"%d.%d.%d.%d", (unsigned char)h->h_addr[0], (unsigned char)h->h_addr[1], (unsigned char)h->h_addr[2], (unsigned char)h->h_addr[3]); + } + chrif_setip(w2); + } else if (strcmpi(w1, "char_port") == 0) { + chrif_setport(atoi(w2)); + } else if (strcmpi(w1, "map_ip") == 0) { + h = gethostbyname (w2); + if (h != NULL) { + printf("Map server IP address : %s -> %d.%d.%d.%d\n", w2, (unsigned char)h->h_addr[0], (unsigned char)h->h_addr[1], (unsigned char)h->h_addr[2], (unsigned char)h->h_addr[3]); + sprintf(w2, "%d.%d.%d.%d", (unsigned char)h->h_addr[0], (unsigned char)h->h_addr[1], (unsigned char)h->h_addr[2], (unsigned char)h->h_addr[3]); + } + clif_setip(w2); + } else if (strcmpi(w1, "map_port") == 0) { + clif_setport(atoi(w2)); + map_port = (atoi(w2)); + } else if (strcmpi(w1, "water_height") == 0) { + map_readwater(w2); + } else if (strcmpi(w1, "map") == 0) { + map_addmap(w2); + } else if (strcmpi(w1, "delmap") == 0) { + map_delmap(w2); + } else if (strcmpi(w1, "npc") == 0) { + npc_addsrcfile(w2); + } else if (strcmpi(w1, "delnpc") == 0) { + npc_delsrcfile(w2); + } else if (strcmpi(w1, "data_grf") == 0) { + grfio_setdatafile(w2); + } else if (strcmpi(w1, "sdata_grf") == 0) { + grfio_setsdatafile(w2); + } else if (strcmpi(w1, "adata_grf") == 0) { + grfio_setadatafile(w2); + } else if (strcmpi(w1, "autosave_time") == 0) { + autosave_interval = atoi(w2) * 1000; + if (autosave_interval <= 0) + autosave_interval = DEFAULT_AUTOSAVE_INTERVAL; + } else if (strcmpi(w1, "motd_txt") == 0) { + strcpy(motd_txt, w2); + } else if (strcmpi(w1, "help_txt") == 0) { + strcpy(help_txt, w2); + } else if (strcmpi(w1, "mapreg_txt") == 0) { + strcpy(mapreg_txt, w2); + } else if (strcmpi(w1, "import") == 0) { + map_config_read(w2); + } + } + } + fclose(fp); + + return 0; +} + +#ifndef TXT_ONLY +/*======================================= + * MySQL Init + *--------------------------------------- + */ + +int map_sql_init(void){ + + mysql_init(&mmysql_handle); + + //DB connection start + printf("Connect Map DB Server....\n"); + if(!mysql_real_connect(&mmysql_handle, map_server_ip, map_server_id, map_server_pw, + map_server_db ,map_server_port, (char *)NULL, 0)) { + //pointer check + printf("%s\n",mysql_error(&mmysql_handle)); + exit(1); + } + else { + printf ("connect success! (Map Server Connection)\n"); + } + + mysql_init(&lmysql_handle); + + //DB connection start + printf("Connect Login DB Server....\n"); + if(!mysql_real_connect(&lmysql_handle, login_server_ip, login_server_id, login_server_pw, + login_server_db ,login_server_port, (char *)NULL, 0)) { + //pointer check + printf("%s\n",mysql_error(&lmysql_handle)); + exit(1); + } + else { + printf ("connect success! (Login Server Connection)\n"); + } + + if(battle_config.mail_system) { // mail system [Valaris] + mysql_init(&mail_handle); + if(!mysql_real_connect(&mail_handle, map_server_ip, map_server_id, map_server_pw, + map_server_db ,map_server_port, (char *)NULL, 0)) { + printf("%s\n",mysql_error(&mail_handle)); + exit(1); + } + } + + return 0; +} + +int map_sql_close(void){ + mysql_close(&mmysql_handle); + printf("Close Map DB Connection....\n"); + + mysql_close(&lmysql_handle); + printf("Close Login DB Connection....\n"); + return 0; +} + +int sql_config_read(char *cfgName) +{ + int i; + char line[1024],w1[1024],w2[1024]; + FILE *fp; + + fp=fopen(cfgName,"r"); + if(fp==NULL){ + printf("file not found: %s\n",cfgName); + return 1; + } + while(fgets(line,1020,fp)){ + if(line[0] == '/' && line[1] == '/') + continue; + i=sscanf(line,"%[^:]: %[^\r\n]",w1,w2); + if(i!=2) + continue; + if(strcmpi(w1,"item_db_db")==0){ + strcpy(item_db_db,w2); + } else if(strcmpi(w1,"mob_db_db")==0){ + strcpy(mob_db_db,w2); + } else if(strcmpi(w1,"login_db_level")==0){ + strcpy(login_db_level,w2); + } else if(strcmpi(w1,"login_db_account_id")==0){ + strcpy(login_db_account_id,w2); + } else if(strcmpi(w1,"login_db")==0){ + strcpy(login_db,w2); + } else if (strcmpi(w1, "char_db") == 0) { + strcpy(char_db, w2); + //Map Server SQL DB + } else if(strcmpi(w1,"map_server_ip")==0){ + strcpy(map_server_ip, w2); + printf ("set map_server_ip : %s\n",w2); + } else if(strcmpi(w1,"map_server_port")==0){ + map_server_port=atoi(w2); + printf ("set map_server_port : %s\n",w2); + } else if(strcmpi(w1,"map_server_id")==0){ + strcpy(map_server_id, w2); + printf ("set map_server_id : %s\n",w2); + } else if(strcmpi(w1,"map_server_pw")==0){ + strcpy(map_server_pw, w2); + printf ("set map_server_pw : %s\n",w2); + } else if(strcmpi(w1,"map_server_db")==0){ + strcpy(map_server_db, w2); + printf ("set map_server_db : %s\n",w2); + //Map server option to use SQL db or not + } else if(strcmpi(w1,"use_sql_db")==0){ + if (strcmpi(w2,"yes")){db_use_sqldbs=0;} else if (strcmpi(w2,"no")){db_use_sqldbs=1;} + printf ("Using SQL dbs: %s\n",w2); + //Login Server SQL DB + } else if(strcmpi(w1,"login_server_ip")==0){ + strcpy(login_server_ip, w2); + printf ("set login_server_ip : %s\n",w2); + } else if(strcmpi(w1,"login_server_port")==0){ + login_server_port = atoi(w2); + printf ("set login_server_port : %s\n",w2); + } else if(strcmpi(w1,"login_server_id")==0){ + strcpy(login_server_id, w2); + printf ("set login_server_id : %s\n",w2); + } else if(strcmpi(w1,"login_server_pw")==0){ + strcpy(login_server_pw, w2); + printf ("set login_server_pw : %s\n",w2); + } else if(strcmpi(w1,"login_server_db")==0){ + strcpy(login_server_db, w2); + printf ("set login_server_db : %s\n",w2); + } else if(strcmpi(w1,"lowest_gm_level")==0){ + lowest_gm_level = atoi(w2); + printf ("set lowest_gm_level : %s\n",w2); + } else if(strcmpi(w1,"read_gm_interval")==0){ + read_gm_interval = ( atoi(w2) * 60 * 1000 ); // Minutes multiplied by 60 secs per min by 1000 milliseconds per second + printf ("set read_gm_interval : %s\n",w2); + } + } + fclose(fp); + + return 0; +} + +// sql online status checking [Valaris] +void char_offline(struct map_session_data *sd) +{ + if(sd && sd->status.char_id) { + sprintf(tmp_sql,"UPDATE `%s` SET `online`='0' WHERE `char_id`='%d'", char_db, sd->status.char_id); + if(mysql_query(&mmysql_handle, tmp_sql) ) { + printf("DB server Error (update online `%s`)- %s\n", char_db, mysql_error(&mmysql_handle) ); + } + } +} + +void do_reset_online(void) +{ + sprintf(tmp_sql,"UPDATE `%s` SET `online`='0' WHERE `online`='1'", char_db); + if(mysql_query(&mmysql_handle, tmp_sql) ) { + printf("DB server Error (reset_online `%s`)- %s\n", char_db, mysql_error(&mmysql_handle) ); + } +} + +int online_timer(int tid,unsigned int tick,int id,int data) +{ + if(check_online_timer != tid) + return 0; + + char_online_check(); + + check_online_timer=add_timer(gettick()+CHECK_INTERVAL,online_timer,0,0); + + return 0; +} + +void char_online_check(void) +{ + int i; + struct map_session_data *sd=NULL; + + do_reset_online(); + + for(i=0;i<fd_max;i++){ + if (session[i] && (sd = session[i]->session_data) && sd && sd->state.auth && + !(battle_config.hide_GM_session && pc_isGM(sd))) + if(sd->status.char_id) { + sprintf(tmp_sql,"UPDATE `%s` SET `online`='1' WHERE `char_id`='%d'", char_db, sd->status.char_id); + if(mysql_query(&mmysql_handle, tmp_sql) ) { + printf("DB server Error (update online `%s`)- %s\n", char_db, mysql_error(&mmysql_handle) ); + } + } + } + + + if(check_online_timer && check_online_timer != -1) { + delete_timer(check_online_timer,online_timer); + add_timer(gettick()+CHECK_INTERVAL,online_timer,0,0); + } + +} + +#endif /* not TXT_ONLY */ + +int id_db_final(void *k,void *d,va_list ap){ return 0; } +int map_db_final(void *k,void *d,va_list ap){ return 0; } +int nick_db_final(void *k,void *d,va_list ap){ return 0; } +int charid_db_final(void *k,void *d,va_list ap){ return 0; } + +static int cleanup_sub(struct block_list *bl, va_list ap) { + nullpo_retr(0, bl); + + switch(bl->type) { + case BL_PC: + map_delblock(bl); // There is something better... + break; + case BL_NPC: + npc_delete((struct npc_data *)bl); + break; + case BL_MOB: + mob_delete((struct mob_data *)bl); + break; + case BL_PET: + pet_remove_map((struct map_session_data *)bl); + break; + case BL_ITEM: + map_clearflooritem(bl->id); + break; + case BL_SKILL: + skill_delunit((struct skill_unit *) bl); + break; + } + + return 0; +} + +/*========================================== + * map鯖終了時処理 + *------------------------------------------ + */ +void do_final(void) { + int map_id, i; + + for (map_id = 0; map_id < map_num;map_id++) { + if(map[map_id].m) + map_foreachinarea(cleanup_sub, map_id, 0, 0, map[map_id].xs, map[map_id].ys, 0, 0); + } + + for (i = 0; i < fd_max; i++) + delete_session(i); + + map_removenpc(); + timer_final(); + + numdb_final(id_db, id_db_final); + strdb_final(map_db, map_db_final); + strdb_final(nick_db, nick_db_final); + numdb_final(charid_db, charid_db_final); + + for(i=0;i<=map_num;i++){ + if(map[i].gat) free(map[i].gat); + if(map[i].block) free(map[i].block); + if(map[i].block_mob) free(map[i].block_mob); + if(map[i].block_count) free(map[i].block_count); + if(map[i].block_mob_count) free(map[i].block_mob_count); + } + do_final_script(); + do_final_itemdb(); + do_final_storage(); + do_final_guild(); +#ifndef TXT_ONLY + do_reset_online(); + map_sql_close(); +#endif /* not TXT_ONLY */ +} + +void map_helpscreen() { + exit(1); +} + +/*====================================================== + * Map-Server Init and Command-line Arguments [Valaris] + *------------------------------------------------------ + */ +int do_init(int argc, char *argv[]) { + int i; + +#ifndef TXT_ONLY + unsigned char *SQL_CONF_NAME="conf/inter_athena.conf"; +#endif + unsigned char *MAP_CONF_NAME = "conf/map_athena.conf"; + unsigned char *BATTLE_CONF_FILENAME = "conf/battle_athena.conf"; + unsigned char *ATCOMMAND_CONF_FILENAME = "conf/atcommand_athena.conf"; + unsigned char *SCRIPT_CONF_NAME = "conf/script_athena.conf"; + unsigned char *MSG_CONF_NAME = "conf/msg_athena.conf"; + unsigned char *GRF_PATH_FILENAME = "conf/grf-files.txt"; + + srand(gettick()); + + for (i = 1; i < argc ; i++) { + + if (strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "--h") == 0 || strcmp(argv[i], "--?") == 0 || strcmp(argv[i], "/?") == 0) + map_helpscreen(); + else if (strcmp(argv[i], "--map_config") == 0) + MAP_CONF_NAME=argv[i+1]; + else if (strcmp(argv[i],"--battle_config") == 0) + BATTLE_CONF_FILENAME = argv[i+1]; + else if (strcmp(argv[i],"--atcommand_config") == 0) + ATCOMMAND_CONF_FILENAME = argv[i+1]; + else if (strcmp(argv[i],"--script_config") == 0) + SCRIPT_CONF_NAME = argv[i+1]; + else if (strcmp(argv[i],"--msg_config") == 0) + MSG_CONF_NAME = argv[i+1]; + else if (strcmp(argv[i],"--grf_path_file") == 0) + GRF_PATH_FILENAME = argv[i+1]; +#ifndef TXT_ONLY + else if (strcmp(argv[i],"--sql_config") == 0) + SQL_CONF_NAME = argv[i+1]; +#endif /* not TXT_ONLY */ + } + + map_config_read(MAP_CONF_NAME); + battle_config_read(BATTLE_CONF_FILENAME); + atcommand_config_read(ATCOMMAND_CONF_FILENAME); + script_config_read(SCRIPT_CONF_NAME); + msg_config_read(MSG_CONF_NAME); +#ifndef TXT_ONLY + sql_config_read(SQL_CONF_NAME); +#endif /* not TXT_ONLY */ + + atexit(do_final); + + id_db = numdb_init(); + map_db = strdb_init(16); + nick_db = strdb_init(24); + charid_db = numdb_init(); +#ifndef TXT_ONLY + map_sql_init(); +#endif /* not TXT_ONLY */ + + grfio_init(GRF_PATH_FILENAME); + + map_readallmap(); + + add_timer_func_list(map_clearflooritem_timer, "map_clearflooritem_timer"); + +#ifndef TXT_ONLY // online status timer, checks every hour [Valaris] + add_timer_func_list(online_timer, "online_timer"); + check_online_timer=add_timer(gettick()+CHECK_INTERVAL,online_timer,0,0); +#endif /* not TXT_ONLY */ + + do_init_chrif(); + do_init_clif(); + do_init_itemdb(); + do_init_mob(); // npcの初期化時内でmob_spawnして、mob_dbを参照するのでinit_npcより先 + do_init_script(); + do_init_npc(); + do_init_pc(); + do_init_party(); + do_init_guild(); + do_init_storage(); + do_init_skill(); + do_init_pet(); + +#ifndef TXT_ONLY /* mail system [Valaris] */ + if(battle_config.mail_system) + do_init_mail(); +#endif /* not TXT_ONLY */ + + npc_event_do_oninit(); // npcのOnInitイベント実行 + + if (battle_config.pk_mode == 1) + printf("The server is running in \033[1;31mPK Mode\033[0m.\n"); + + printf("The map-server is \033[1;32mready\033[0m (Server is listening on the port %d).\n\n", map_port); + + return 0; +} + diff --git a/src/map/map.h b/src/map/map.h new file mode 100644 index 0000000..703bbb6 --- /dev/null +++ b/src/map/map.h @@ -0,0 +1,705 @@ +// $Id: map.h,v 1.8 2004/09/25 11:39:17 MouseJstr Exp $ +#ifndef _MAP_H_ +#define _MAP_H_ + +#include <stdarg.h> +#include "mmo.h" + +#define MAX_PC_CLASS (1+6+6+1+6+1+1+1+1+4023) +#define PC_CLASS_BASE 0 +#define PC_CLASS_BASE2 (PC_CLASS_BASE + 4001) +#define PC_CLASS_BASE3 (PC_CLASS_BASE2 + 22) +#define MAX_NPC_PER_MAP 512 +#define BLOCK_SIZE 8 +#define AREA_SIZE battle_config.area_size +#define LOCAL_REG_NUM 16 +#define LIFETIME_FLOORITEM 60 +#define DAMAGELOG_SIZE 30 +#define LOOTITEM_SIZE 10 +#define MAX_SKILL_LEVEL 100 +#define MAX_STATUSCHANGE 200 +#define MAX_SKILLUNITGROUP 32 +#define MAX_MOBSKILLUNITGROUP 8 +#define MAX_SKILLUNITGROUPTICKSET 128 +#define MAX_SKILLTIMERSKILL 32 +#define MAX_MOBSKILLTIMERSKILL 10 +#define MAX_MOBSKILL 32 +#define MAX_EVENTQUEUE 2 +#define MAX_EVENTTIMER 32 +#define NATURAL_HEAL_INTERVAL 500 +#define MAX_FLOORITEM 500000 +#define MAX_LEVEL 255 +#define MAX_WALKPATH 48 +#define MAX_DROP_PER_MAP 48 + +#define DEFAULT_AUTOSAVE_INTERVAL 60*1000 + +#define OPTION_HIDE 0x40 + +enum { BL_NUL, BL_PC, BL_NPC, BL_MOB, BL_ITEM, BL_CHAT, BL_SKILL , BL_PET }; +enum { WARP, SHOP, SCRIPT, MONS }; +struct block_list { + struct block_list *next,*prev; + int id; + short m,x,y; + unsigned char type; + unsigned char subtype; +}; + +struct walkpath_data { + unsigned char path_len,path_pos,path_half; + unsigned char path[MAX_WALKPATH]; +}; +struct script_reg { + int index; + int data; +}; +struct script_regstr { + int index; + char data[256]; +}; +struct status_change { + int timer; + int val1,val2,val3,val4; +}; +struct vending { + short index; + short amount; + int value; +}; + +struct skill_unit_group; +struct skill_unit { + struct block_list bl; + + struct skill_unit_group *group; + + int limit; + int val1,val2; + short alive,range; +}; +struct skill_unit_group { + int src_id; + int party_id; + int guild_id; + int map,range; + int target_flag; + unsigned int tick; + int limit,interval; + + int skill_id,skill_lv; + int val1,val2; + char *valstr; + int unit_id; + int group_id; + int unit_count,alive_count; + struct skill_unit *unit; +}; +struct skill_unit_group_tickset { + unsigned int tick; + int group_id; +}; +struct skill_timerskill { + int timer; + int src_id; + int target_id; + int map; + short x,y; + short skill_id,skill_lv; + int type; + int flag; +}; + +struct npc_data; +struct pet_db; +struct item_data; +struct square; + +struct map_session_data { + struct block_list bl; + struct { + unsigned auth : 1; + unsigned change_walk_target : 1; + unsigned attack_continue : 1; + unsigned menu_or_input : 1; + unsigned dead_sit : 2; + unsigned skillcastcancel : 1; + unsigned waitingdisconnect : 1; + unsigned lr_flag : 2; + unsigned connect_new : 1; + unsigned arrow_atk : 1; + unsigned attack_type : 3; + unsigned skill_flag : 1; + unsigned gangsterparadise : 1; + unsigned produce_flag : 1; + unsigned make_arrow_flag : 1; + unsigned potionpitcher_flag : 1; + unsigned storage_flag : 1; + } state; + struct { + unsigned killer : 1; + unsigned killable : 1; + unsigned restart_full_recover : 1; + unsigned no_castcancel : 1; + unsigned no_castcancel2 : 1; + unsigned no_sizefix : 1; + unsigned no_magic_damage : 1; + unsigned no_weapon_damage : 1; + unsigned no_gemstone : 1; + unsigned infinite_endure : 1; + unsigned unbreakable_weapon : 1; + unsigned unbreakable_armor : 1; + unsigned infinite_autospell : 1; + } special_state; + int char_id, login_id1, login_id2, sex; + int packet_ver; // 5: old, 6: 7july04, 7: 13july04, 8: 26july04, 9: 9aug04/16aug04/17aug04, 10: 6sept04 (by [Yor]) + struct mmo_charstatus status; + struct item_data *inventory_data[MAX_INVENTORY]; + short equip_index[11]; + int weight,max_weight; + int cart_weight,cart_max_weight,cart_num,cart_max_num; + char mapname[24]; + int fd,new_fd; + short to_x,to_y; + short speed,prev_speed; + short opt1,opt2,opt3; + char dir,head_dir; + unsigned int client_tick,server_tick; + struct walkpath_data walkpath; + int walktimer; + int npc_id,areanpc_id,npc_shopid; + int npc_pos; + int npc_menu; + int npc_amount; + int npc_stack,npc_stackmax; + char *npc_script,*npc_scriptroot; + char *npc_stackbuf; + char npc_str[256]; + unsigned int chatID; + + int attacktimer; + int attacktarget; + short attacktarget_lv; + unsigned int attackabletime; + + int followtimer; // [MouseJstr] + int followtarget; + + short attackrange,attackrange_; + int skilltimer; + int skilltarget; + short skillx,skilly; + short skillid,skilllv; + short skillitem,skillitemlv; + short skillid_old,skilllv_old; + short skillid_dance,skilllv_dance; + struct skill_unit_group skillunit[MAX_SKILLUNITGROUP]; + struct skill_unit_group_tickset skillunittick[MAX_SKILLUNITGROUPTICKSET]; + struct skill_timerskill skilltimerskill[MAX_SKILLTIMERSKILL]; + int cloneskill_id,cloneskill_lv; + int potion_hp,potion_sp,potion_per_hp,potion_per_sp; + + int invincible_timer; + unsigned int canact_tick; + unsigned int canmove_tick; + unsigned int canlog_tick; + int hp_sub,sp_sub; + int inchealhptick,inchealsptick,inchealspirithptick,inchealspiritsptick; +// -- moonsoul (new tick for berserk self-damage) + int berserkdamagetick; + int fame; + + short view_class; + short weapontype1,weapontype2; + short disguiseflag,disguise; // [Valaris] + int paramb[6],paramc[6],parame[6],paramcard[6]; + int hit,flee,flee2,aspd,amotion,dmotion; + int watk,watk2,atkmods[3]; + int def,def2,mdef,mdef2,critical,matk1,matk2; + int atk_ele,def_ele,star,overrefine; + int castrate,hprate,sprate,dsprate; + int addele[10],addrace[12],addsize[3],subele[10],subrace[12]; + int addeff[10],addeff2[10],reseff[10]; + int watk_,watk_2,atkmods_[3],addele_[10],addrace_[12],addsize_[3]; //二刀流のために追加 + int atk_ele_,star_,overrefine_; //二刀流のために追加 + int base_atk,atk_rate; + int arrow_atk,arrow_ele,arrow_cri,arrow_hit,arrow_range; + int arrow_addele[10],arrow_addrace[12],arrow_addsize[3],arrow_addeff[10],arrow_addeff2[10]; + int nhealhp,nhealsp,nshealhp,nshealsp,nsshealhp,nsshealsp; + int aspd_rate,speed_rate,hprecov_rate,sprecov_rate,critical_def,double_rate; + int near_attack_def_rate,long_attack_def_rate,magic_def_rate,misc_def_rate; + int matk_rate,ignore_def_ele,ignore_def_race,ignore_def_ele_,ignore_def_race_; + int ignore_mdef_ele,ignore_mdef_race; + int magic_addele[10],magic_addrace[12],magic_subrace[12]; + int perfect_hit,get_zeny_num; + int critical_rate,hit_rate,flee_rate,flee2_rate,def_rate,def2_rate,mdef_rate,mdef2_rate; + int def_ratio_atk_ele,def_ratio_atk_ele_,def_ratio_atk_race,def_ratio_atk_race_; + int add_damage_class_count,add_damage_class_count_,add_magic_damage_class_count; + short add_damage_classid[10],add_damage_classid_[10],add_magic_damage_classid[10]; + int add_damage_classrate[10],add_damage_classrate_[10],add_magic_damage_classrate[10]; + short add_def_class_count,add_mdef_class_count; + short add_def_classid[10],add_mdef_classid[10]; + int add_def_classrate[10],add_mdef_classrate[10]; + short monster_drop_item_count; + short monster_drop_itemid[10]; + int monster_drop_race[10],monster_drop_itemrate[10]; + int double_add_rate,speed_add_rate,aspd_add_rate,perfect_hit_add, get_zeny_add_num; + short splash_range,splash_add_range; + short autospell_id,autospell_lv,autospell_rate; + short hp_drain_rate,hp_drain_per,sp_drain_rate,sp_drain_per; + short hp_drain_rate_,hp_drain_per_,sp_drain_rate_,sp_drain_per_; + int short_weapon_damage_return,long_weapon_damage_return; + int weapon_coma_ele[10],weapon_coma_race[12]; + short break_weapon_rate,break_armor_rate; + short add_steal_rate; + + short spiritball, spiritball_old; + int spirit_timer[MAX_SKILL_LEVEL]; + int magic_damage_return; // AppleGirl Was Here + int random_attack_increase_add,random_attack_increase_per; // [Valaris] + int perfect_hiding; // [Valaris] + int unbreakable; + + int die_counter; + short doridori_counter; + + int reg_num; + struct script_reg *reg; + int regstr_num; + struct script_regstr *regstr; + + struct status_change sc_data[MAX_STATUSCHANGE]; + short sc_count; + struct square dev; + + int trade_partner; + int deal_item_index[10]; + int deal_item_amount[10]; + int deal_zeny; + short deal_locked; + + int party_sended,party_invite,party_invite_account; + int party_hp,party_x,party_y; + + int guild_sended,guild_invite,guild_invite_account; + int guild_emblem_id,guild_alliance,guild_alliance_account; + int guildspy; // [Syrus22] + int partyspy; // [Syrus22] + + int vender_id; + int vend_num; + char message[80]; + struct vending vending[12]; + + int catch_target_class; + struct s_pet pet; + struct pet_db *petDB; + struct pet_data *pd; + int pet_hungry_timer; + + int pvp_point,pvp_rank,pvp_timer,pvp_lastusers; + + char eventqueue[MAX_EVENTQUEUE][50]; + int eventtimer[MAX_EVENTTIMER]; + + int last_skillid,last_skilllv; // Added by RoVeRT + struct{ + char name[24]; + } ignore[80]; + int ignoreAll; + short sg_count; + +#ifndef TXT_ONLY + int mail_counter; // mail counter for mail system [Valaris] +#endif + +}; + +struct npc_timerevent_list { + int timer,pos; +}; +struct npc_label_list { + char name[24]; + int pos; +}; +struct npc_item_list { + int nameid,value; +}; +struct npc_data { + struct block_list bl; + short n; + short class,dir; + short speed; + char name[24]; + char exname[24]; + int chat_id; + short opt1,opt2,opt3,option; + short flag; + union { + struct { + char *script; + short xs,ys; + int guild_id; + int timer,timerid,timeramount,nexttimer; + unsigned int timertick; + struct npc_timerevent_list *timer_event; + int label_list_num; + struct npc_label_list *label_list; + int src_id; + } scr; + struct npc_item_list shop_item[1]; + struct { + short xs,ys; + short x,y; + char name[16]; + } warp; + } u; + // ここにメンバを追加してはならない(shop_itemが可変長の為) + + char eventqueue[MAX_EVENTQUEUE][50]; + int eventtimer[MAX_EVENTTIMER]; + short arenaflag; +}; +struct mob_data { + struct block_list bl; + short n; + short base_class,class,dir,mode; + short m,x0,y0,xs,ys; + char name[24]; + int spawndelay1,spawndelay2; + struct { + unsigned state : 8; + unsigned skillstate : 8; + unsigned targettype : 1; + unsigned steal_flag : 1; + unsigned steal_coin_flag : 1; + unsigned skillcastcancel : 1; + unsigned master_check : 1; + unsigned change_walk_target : 1; + unsigned walk_easy : 1; + unsigned special_mob_ai : 3; + } state; + int timer; + short to_x,to_y; + short speed; + int hp; + int target_id,attacked_id; + short target_lv; + struct walkpath_data walkpath; + unsigned int next_walktime; + unsigned int attackabletime; + unsigned int last_deadtime,last_spawntime,last_thinktime; + unsigned int canmove_tick; + short move_fail_count; + struct { + int id; + int dmg; + } dmglog[DAMAGELOG_SIZE]; + struct item *lootitem; + short lootitem_count; + + struct status_change sc_data[MAX_STATUSCHANGE]; + short sc_count; + short opt1,opt2,opt3,option; + short min_chase; + short sg_count; + int guild_id; + int deletetimer; + + int skilltimer; + int skilltarget; + short skillx,skilly; + short skillid,skilllv,skillidx; + unsigned int skilldelay[MAX_MOBSKILL]; + int def_ele; + int master_id,master_dist; + int exclusion_src,exclusion_party,exclusion_guild; + struct skill_timerskill skilltimerskill[MAX_MOBSKILLTIMERSKILL]; + struct skill_unit_group skillunit[MAX_MOBSKILLUNITGROUP]; + struct skill_unit_group_tickset skillunittick[MAX_SKILLUNITGROUPTICKSET]; + char npc_event[50]; + short size; +}; +struct pet_data { + struct block_list bl; + short n; + short class,dir; + short speed; + char name[24]; + struct { + unsigned state : 8 ; + unsigned skillstate : 8 ; + unsigned change_walk_target : 1 ; + } state; + int timer; + short to_x,to_y; + short equip; + struct walkpath_data walkpath; + int target_id; + short target_lv; + int move_fail_count; + unsigned int attackabletime,next_walktime,last_thinktime; + int skilltype,skillval,skilltimer,skillduration; // [Valaris] + int skillbonustype,skillbonusval,skillbonustimer,skillbonusduration; // [Valaris] + struct item *lootitem; + short loot; // [Valaris] + short lootmax; // [Valaris] + short lootitem_count; + short lootitem_weight; + int lootitem_timer; + struct skill_timerskill skilltimerskill[MAX_MOBSKILLTIMERSKILL]; // [Valaris] + struct skill_unit_group skillunit[MAX_MOBSKILLUNITGROUP]; // [Valaris] + struct skill_unit_group_tickset skillunittick[MAX_SKILLUNITGROUPTICKSET]; // [Valaris] + struct map_session_data *msd; +}; + +enum { MS_IDLE,MS_WALK,MS_ATTACK,MS_DEAD,MS_DELAY }; + +enum { NONE_ATTACKABLE,ATTACKABLE }; + +enum { ATK_LUCKY=1,ATK_FLEE,ATK_DEF}; // 囲まれペナルティ計算用 + +struct map_data { + char name[24]; + char alias[24]; // [MouseJstr] + unsigned char *gat; // NULLなら下のmap_data_other_serverとして扱う + struct block_list **block; + struct block_list **block_mob; + int *block_count,*block_mob_count; + int m; + short xs,ys; + short bxs,bys; + int npc_num; + int users; + struct { + unsigned alias : 1; + unsigned nomemo : 1; + unsigned noteleport : 1; + unsigned noreturn : 1; + unsigned monster_noteleport : 1; + unsigned nosave : 1; + unsigned nobranch : 1; + unsigned nopenalty : 1; + unsigned pvp : 1; + unsigned pvp_noparty : 1; + unsigned pvp_noguild : 1; + unsigned pvp_nightmaredrop :1; + unsigned pvp_nocalcrank : 1; + unsigned gvg : 1; + unsigned gvg_noparty : 1; + unsigned nozenypenalty : 1; + unsigned notrade : 1; + unsigned noskill : 1; + unsigned nowarp : 1; + unsigned nowarpto : 1; + unsigned nopvp : 1; // [Valaris] + unsigned noicewall : 1; // [Valaris] + unsigned snow : 1; // [Valaris] + unsigned fog : 1; // [Valaris] + unsigned sakura : 1; // [Valaris] + unsigned leaves : 1; // [Valaris] + unsigned rain : 1; // [Valaris] + } flag; + struct point save; + struct npc_data *npc[MAX_NPC_PER_MAP]; + struct { + int drop_id; + int drop_type; + int drop_per; + } drop_list[MAX_DROP_PER_MAP]; +}; +struct map_data_other_server { + char name[24]; + unsigned char *gat; // NULL固定にして判断 + unsigned long ip; + unsigned int port; +}; +#define read_gat(m,x,y) (map[m].gat[(x)+(y)*map[m].xs]) +#define read_gatp(m,x,y) (m->gat[(x)+(y)*m->xs]) + +struct flooritem_data { + struct block_list bl; + short subx,suby; + int cleartimer; + int first_get_id,second_get_id,third_get_id; + unsigned int first_get_tick,second_get_tick,third_get_tick; + struct item item_data; +}; + +enum { + SP_SPEED,SP_BASEEXP,SP_JOBEXP,SP_KARMA,SP_MANNER,SP_HP,SP_MAXHP,SP_SP, // 0-7 + SP_MAXSP,SP_STATUSPOINT,SP_0a,SP_BASELEVEL,SP_SKILLPOINT,SP_STR,SP_AGI,SP_VIT, // 8-15 + SP_INT,SP_DEX,SP_LUK,SP_CLASS,SP_ZENY,SP_SEX,SP_NEXTBASEEXP,SP_NEXTJOBEXP, // 16-23 + SP_WEIGHT,SP_MAXWEIGHT,SP_1a,SP_1b,SP_1c,SP_1d,SP_1e,SP_1f, // 24-31 + SP_USTR,SP_UAGI,SP_UVIT,SP_UINT,SP_UDEX,SP_ULUK,SP_26,SP_27, // 32-39 + SP_28,SP_ATK1,SP_ATK2,SP_MATK1,SP_MATK2,SP_DEF1,SP_DEF2,SP_MDEF1, // 40-47 + SP_MDEF2,SP_HIT,SP_FLEE1,SP_FLEE2,SP_CRITICAL,SP_ASPD,SP_36,SP_JOBLEVEL, // 48-55 + SP_UPPER,SP_PARTNER,SP_CART,SP_FAME,SP_UNBREAKABLE, //56-58 + SP_CARTINFO=99, // 99 + + // original 1000- + SP_ATTACKRANGE=1000, SP_ATKELE,SP_DEFELE, // 1000-1002 + SP_CASTRATE, SP_MAXHPRATE, SP_MAXSPRATE, SP_SPRATE, // 1003-1006 + SP_ADDELE, SP_ADDRACE, SP_ADDSIZE, SP_SUBELE, SP_SUBRACE, // 1007-1011 + SP_ADDEFF, SP_RESEFF, // 1012-1013 + SP_BASE_ATK,SP_ASPD_RATE,SP_HP_RECOV_RATE,SP_SP_RECOV_RATE,SP_SPEED_RATE, // 1014-1018 + SP_CRITICAL_DEF,SP_NEAR_ATK_DEF,SP_LONG_ATK_DEF, // 1019-1021 + SP_DOUBLE_RATE, SP_DOUBLE_ADD_RATE, SP_MATK, SP_MATK_RATE, // 1022-1025 + SP_IGNORE_DEF_ELE,SP_IGNORE_DEF_RACE, // 1026-1027 + SP_ATK_RATE,SP_SPEED_ADDRATE,SP_ASPD_ADDRATE, // 1028-1030 + SP_MAGIC_ATK_DEF,SP_MISC_ATK_DEF, // 1031-1032 + SP_IGNORE_MDEF_ELE,SP_IGNORE_MDEF_RACE, // 1033-1034 + SP_MAGIC_ADDELE,SP_MAGIC_ADDRACE,SP_MAGIC_SUBRACE, // 1035-1037 + SP_PERFECT_HIT_RATE,SP_PERFECT_HIT_ADD_RATE,SP_CRITICAL_RATE,SP_GET_ZENY_NUM,SP_ADD_GET_ZENY_NUM, // 1038-1042 + SP_ADD_DAMAGE_CLASS,SP_ADD_MAGIC_DAMAGE_CLASS,SP_ADD_DEF_CLASS,SP_ADD_MDEF_CLASS, // 1043-1046 + SP_ADD_MONSTER_DROP_ITEM,SP_DEF_RATIO_ATK_ELE,SP_DEF_RATIO_ATK_RACE,SP_ADD_SPEED, // 1047-1050 + SP_HIT_RATE,SP_FLEE_RATE,SP_FLEE2_RATE,SP_DEF_RATE,SP_DEF2_RATE,SP_MDEF_RATE,SP_MDEF2_RATE, // 1051-1057 + SP_SPLASH_RANGE,SP_SPLASH_ADD_RANGE,SP_AUTOSPELL,SP_HP_DRAIN_RATE,SP_SP_DRAIN_RATE, // 1058-1062 + SP_SHORT_WEAPON_DAMAGE_RETURN,SP_LONG_WEAPON_DAMAGE_RETURN,SP_WEAPON_COMA_ELE,SP_WEAPON_COMA_RACE, // 1063-1066 + SP_ADDEFF2,SP_BREAK_WEAPON_RATE,SP_BREAK_ARMOR_RATE,SP_ADD_STEAL_RATE, // 1067-1070 + SP_MAGIC_DAMAGE_RETURN,SP_RANDOM_ATTACK_INCREASE,SP_ALL_STATS,SP_AGI_VIT,SP_AGI_DEX_STR,SP_PERFECT_HIDE, // 1071-1077 + SP_DISGUISE, // 1077 + + SP_RESTART_FULL_RECORVER=2000,SP_NO_CASTCANCEL,SP_NO_SIZEFIX,SP_NO_MAGIC_DAMAGE,SP_NO_WEAPON_DAMAGE,SP_NO_GEMSTONE, // 2000-2005 + SP_NO_CASTCANCEL2,SP_INFINITE_ENDURE,SP_UNBREAKABLE_WEAPON,SP_UNBREAKABLE_ARMOR // 2006-2009 +}; + +enum { + LOOK_BASE,LOOK_HAIR,LOOK_WEAPON,LOOK_HEAD_BOTTOM,LOOK_HEAD_TOP,LOOK_HEAD_MID,LOOK_HAIR_COLOR,LOOK_CLOTHES_COLOR,LOOK_SHIELD,LOOK_SHOES +}; + +struct chat_data { + struct block_list bl; + + unsigned char pass[8]; /* password */ + unsigned char title[61]; /* room title MAX 60 */ + unsigned char limit; /* join limit */ + unsigned char trigger; + unsigned char users; /* current users */ + unsigned char pub; /* room attribute */ + struct map_session_data *usersd[20]; + struct block_list *owner_; + struct block_list **owner; + char npc_event[50]; +}; + +extern struct map_data map[]; +extern int map_num; +extern int autosave_interval; +extern int agit_flag; +extern int night_flag; // 0=day, 1=night [Yor] + +extern char motd_txt[]; +extern char help_txt[]; + +extern char talkie_mes[]; + +extern char wisp_server_name[]; + +// 鯖全体情報 +void map_setusers(int); +int map_getusers(void); +// block削除関連 +int map_freeblock( void *bl ); +int map_freeblock_lock(void); +int map_freeblock_unlock(void); +// block関連 +int map_addblock(struct block_list *); +int map_delblock(struct block_list *); +void map_foreachinarea(int (*)(struct block_list*,va_list),int,int,int,int,int,int,...); +// -- moonsoul (added map_foreachincell) +void map_foreachincell(int (*)(struct block_list*,va_list),int,int,int,int,...); +void map_foreachinmovearea(int (*)(struct block_list*,va_list),int,int,int,int,int,int,int,int,...); +int map_countnearpc(int,int,int); +//block関連に追加 +int map_count_oncell(int m,int x,int y); +// 一時的object関連 +int map_addobject(struct block_list *); +int map_delobject(int); +int map_delobjectnofree(int id); +void map_foreachobject(int (*)(struct block_list*,va_list),int,...); +// +int map_quit(struct map_session_data *); +// npc +int map_addnpc(int,struct npc_data *); + +// 床アイテム関連 +int map_clearflooritem_timer(int,unsigned int,int,int); +#define map_clearflooritem(id) map_clearflooritem_timer(0,0,id,1) +int map_addflooritem(struct item *,int,int,int,int,struct map_session_data *,struct map_session_data *,struct map_session_data *,int); +int map_searchrandfreecell(int,int,int,int); + +// キャラid=>キャラ名 変換関連 +void map_addchariddb(int charid,char *name); +void map_delchariddb(int charid); +int map_reqchariddb(struct map_session_data * sd,int charid); +char * map_charid2nick(int); + +struct map_session_data * map_id2sd(int); +struct block_list * map_id2bl(int); +int map_mapname2mapid(char*); +int map_mapname2ipport(char*,int*,int*); +int map_setipport(char *name,unsigned long ip,int port); +int map_eraseipport(char *name,unsigned long ip,int port); +void map_addiddb(struct block_list *); +void map_deliddb(struct block_list *bl); +int map_foreachiddb(int (*)(void*,void*,va_list),...); +void map_addnickdb(struct map_session_data *); +struct map_session_data * map_nick2sd(char*); + +// gat関連 +int map_getcell(int,int,int); +int map_setcell(int,int,int,int); + +// その他 +int map_check_dir(int s_dir,int t_dir); +int map_calc_dir( struct block_list *src,int x,int y); + +// path.cより +int path_search(struct walkpath_data*,int,int,int,int,int,int); +int path_blownpos(int m,int x0,int y0,int dx,int dy,int count); + +int map_who(int fd); + +void map_helpscreen(); // [Valaris] +int map_delmap(char *mapname); + +#ifndef TXT_ONLY + +// MySQL +#include <mysql.h> + +void char_online_check(void); // [Valaris] +void char_offline(struct map_session_data *sd); + +extern MYSQL mmysql_handle; +extern char tmp_sql[65535]; +extern MYSQL_RES* sql_res ; +extern MYSQL_ROW sql_row ; + +extern MYSQL lmysql_handle; +extern char tmp_lsql[65535]; +extern MYSQL_RES* lsql_res ; +extern MYSQL_ROW lsql_row ; + +extern MYSQL mail_handle; +extern MYSQL_RES* mail_res ; +extern MYSQL_ROW mail_row ; +extern char tmp_msql[65535]; + +extern int db_use_sqldbs; + +extern char item_db_db[32]; +extern char mob_db_db[32]; +extern char login_db[32]; + +extern char login_db_level[32]; +extern char login_db_account_id[32]; + +extern int lowest_gm_level; +extern int read_gm_interval; + +extern char char_db[32]; +#endif /* not TXT_ONLY */ + +#endif diff --git a/src/map/mob.c b/src/map/mob.c new file mode 100644 index 0000000..969eed2 --- /dev/null +++ b/src/map/mob.c @@ -0,0 +1,4216 @@ +// $Id: mob.c,v 1.7 2004/09/25 05:32:18 MouseJstr Exp $ +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <string.h> + +#include "timer.h" +#include "socket.h" +#include "db.h" +#include "nullpo.h" +#include "malloc.h" +#include "map.h" +#include "clif.h" +#include "intif.h" +#include "pc.h" +#include "mob.h" +#include "guild.h" +#include "itemdb.h" +#include "skill.h" +#include "battle.h" +#include "party.h" +#include "npc.h" + +#ifdef MEMWATCH +#include "memwatch.h" +#endif + +#define MIN_MOBTHINKTIME 100 + +#define MOB_LAZYMOVEPERC 50 // Move probability in the negligent mode MOB (rate of 1000 minute) +#define MOB_LAZYWARPPERC 20 // Warp probability in the negligent mode MOB (rate of 1000 minute) + +struct mob_db mob_db[2001]; + +/*========================================== + * Local prototype declaration (only required thing) + *------------------------------------------ + */ +static int distance(int,int,int,int); +static int mob_makedummymobdb(int); +static int mob_timer(int,unsigned int,int,int); +int mobskill_use(struct mob_data *md,unsigned int tick,int event); +int mobskill_deltimer(struct mob_data *md ); +int mob_skillid2skillidx(int class,int skillid); +int mobskill_use_id(struct mob_data *md,struct block_list *target,int skill_idx); +static int mob_unlocktarget(struct mob_data *md,int tick); + +/*========================================== + * Mob is searched with a name. + *------------------------------------------ + */ +int mobdb_searchname(const char *str) +{ + int i; + + for(i = 0; i < sizeof(mob_db) / sizeof(mob_db[0]); i++) { + if (strcmpi(mob_db[i].name, str) == 0 || strcmp(mob_db[i].jname, str) == 0 || + memcmp(mob_db[i].name, str, 24) == 0 || memcmp(mob_db[i].jname, str, 24) == 0) + return i; + } + + return 0; +} + +/*========================================== + * Id Mob is checked. + *------------------------------------------ + */ +int mobdb_checkid(const int id) +{ + if (id <= 0 || id >= (sizeof(mob_db) / sizeof(mob_db[0])) || mob_db[id].name[0] == '\0') + return 0; + + return id; +} + +/*========================================== + * The minimum data set for MOB spawning + *------------------------------------------ + */ +int mob_spawn_dataset(struct mob_data *md,const char *mobname,int class) +{ + nullpo_retr(0, md); + + md->bl.prev=NULL; + md->bl.next=NULL; + if(strcmp(mobname,"--en--")==0) + memcpy(md->name,mob_db[class].name,24); + else if(strcmp(mobname,"--ja--")==0) + memcpy(md->name,mob_db[class].jname,24); + else + memcpy(md->name,mobname,24); + + md->n = 0; + md->base_class = md->class = class; + md->bl.id= npc_get_new_npc_id(); + + memset(&md->state,0,sizeof(md->state)); + md->timer = -1; + md->target_id=0; + md->attacked_id=0; + md->speed=mob_db[class].speed; + + return 0; +} + + +/*========================================== + * The MOB appearance for one time (for scripts) + *------------------------------------------ + */ +int mob_once_spawn(struct map_session_data *sd,char *mapname, + int x,int y,const char *mobname,int class,int amount,const char *event) +{ + struct mob_data *md=NULL; + int m,count,lv=255,r=class; + + if( sd ) + lv=sd->status.base_level; + + if( sd && strcmp(mapname,"this")==0) + m=sd->bl.m; + else + m=map_mapname2mapid(mapname); + + if(m<0 || amount<=0 || (class>=0 && class<=1000) || class>2000) // 値が異常なら召喚を止める + return 0; + + if(class<0){ // ランダムに召喚 + int i=0; + int j=-class-1; + int k; + if(j>=0 && j<MAX_RANDOMMONSTER){ + do{ + class=rand()%1000+1001; + k=rand()%1000000; + }while((mob_db[class].max_hp <= 0 || mob_db[class].summonper[j] <= k || + (lv<mob_db[class].lv && battle_config.random_monster_checklv==1)) && (i++) < 2000); + if(i>=2000){ + class=mob_db[0].summonper[j]; + } + }else{ + return 0; + } +// if(battle_config.etc_log==1) +// printf("mobclass=%d try=%d\n",class,i); + } + if(sd){ + if(x<=0) x=sd->bl.x; + if(y<=0) y=sd->bl.y; + }else if(x<=0 || y<=0){ + printf("mob_once_spawn: ??\n"); + } + + for(count=0;count<amount;count++){ + md=(struct mob_data *)aCalloc(1,sizeof(struct mob_data)); + memset(md, '\0', sizeof *md); + if(mob_db[class].mode&0x02) + md->lootitem=(struct item *)aCalloc(LOOTITEM_SIZE,sizeof(struct item)); + else + md->lootitem=NULL; + + mob_spawn_dataset(md,mobname,class); + md->bl.m=m; + md->bl.x=x; + md->bl.y=y; + if(r<0&&battle_config.dead_branch_active==1) md->mode=0x1+0x4+0x80; //移動してアクティブで反撃する + md->m =m; + md->x0=x; + md->y0=y; + md->xs=0; + md->ys=0; + md->spawndelay1=-1; // Only once is a flag. + md->spawndelay2=-1; // Only once is a flag. + + memcpy(md->npc_event,event,sizeof(md->npc_event)); + + md->bl.type=BL_MOB; + map_addiddb(&md->bl); + mob_spawn(md->bl.id); + + if(class==1288) { // emperium hp based on defense level [Valaris] + struct guild_castle *gc=guild_mapname2gc(map[md->bl.m].name); + if(gc) { + mob_db[class].max_hp+=2000*gc->defense; + md->hp=mob_db[class].max_hp; + } + } // end addition [Valaris] + + + } + return (amount>0)?md->bl.id:0; +} +/*========================================== + * The MOB appearance for one time (& area specification for scripts) + *------------------------------------------ + */ +int mob_once_spawn_area(struct map_session_data *sd,char *mapname, + int x0,int y0,int x1,int y1, + const char *mobname,int class,int amount,const char *event) +{ + int x,y,i,c,max,lx=-1,ly=-1,id=0; + int m; + + if(strcmp(mapname,"this")==0) + m=sd->bl.m; + else + m=map_mapname2mapid(mapname); + + max=(y1-y0+1)*(x1-x0+1)*3; + if(max>1000)max=1000; + + if(m<0 || amount<=0 || (class>=0 && class<=1000) || class>2000) // A summon is stopped if a value is unusual + return 0; + + for(i=0;i<amount;i++){ + int j=0; + do{ + x=rand()%(x1-x0+1)+x0; + y=rand()%(y1-y0+1)+y0; + }while( ( (c=map_getcell(m,x,y))==1 || c==5)&& (++j)<max ); + if(j>=max){ + if(lx>=0){ // Since reference went wrong, the place which boiled before is used. + x=lx; + y=ly; + }else + return 0; // Since reference of the place which boils first went wrong, it stops. + } + id=mob_once_spawn(sd,mapname,x,y,mobname,class,1,event); + lx=x; + ly=y; + } + return id; +} + +/*========================================== + * Summoning Guardians [Valaris] + *------------------------------------------ + */ +int mob_spawn_guardian(struct map_session_data *sd,char *mapname, + int x,int y,const char *mobname,int class,int amount,const char *event,int guardian) +{ + struct mob_data *md=NULL; + int m,count=1,lv=255; + + if( sd ) + lv=sd->status.base_level; + + if( sd && strcmp(mapname,"this")==0) + m=sd->bl.m; + else + m=map_mapname2mapid(mapname); + + if(m<0 || amount<=0 || (class>=0 && class<=1000) || class>2000) // 値が異常なら召喚を止める + return 0; + + if(class<0) + return 0; + + if(sd){ + if(x<=0) x=sd->bl.x; + if(y<=0) y=sd->bl.y; + } + + else if(x<=0 || y<=0) + printf("mob_spawn_guardian: ??\n"); + + + for(count=0;count<amount;count++){ + struct guild_castle *gc; + md=calloc(sizeof(struct mob_data), 1); + if(md==NULL){ + printf("mob_spawn_guardian: out of memory !\n"); + exit(1); + } + memset(md, '\0', sizeof *md); + + + + mob_spawn_dataset(md,mobname,class); + md->bl.m=m; + md->bl.x=x; + md->bl.y=y; + md->m =m; + md->x0=x; + md->y0=y; + md->xs=0; + md->ys=0; + md->spawndelay1=-1; // Only once is a flag. + md->spawndelay2=-1; // Only once is a flag. + + memcpy(md->npc_event,event,sizeof(md->npc_event)); + + md->bl.type=BL_MOB; + map_addiddb(&md->bl); + mob_spawn(md->bl.id); + + gc=guild_mapname2gc(map[md->bl.m].name); + if(gc) { + mob_db[class].max_hp+=2000*gc->defense; + if(guardian==0) { md->hp=gc->Ghp0; gc->GID0=md->bl.id; } + if(guardian==1) { md->hp=gc->Ghp1; gc->GID1=md->bl.id; } + if(guardian==2) { md->hp=gc->Ghp2; gc->GID2=md->bl.id; } + if(guardian==3) { md->hp=gc->Ghp3; gc->GID3=md->bl.id; } + if(guardian==4) { md->hp=gc->Ghp4; gc->GID4=md->bl.id; } + if(guardian==5) { md->hp=gc->Ghp5; gc->GID5=md->bl.id; } + if(guardian==6) { md->hp=gc->Ghp6; gc->GID6=md->bl.id; } + if(guardian==7) { md->hp=gc->Ghp7; gc->GID7=md->bl.id; } + + } + } + + return (amount>0)?md->bl.id:0; +} + + +/*========================================== + * Appearance income of mob + *------------------------------------------ + */ +int mob_get_viewclass(int class) +{ + return mob_db[class].view_class; +} +int mob_get_sex(int class) +{ + return mob_db[class].sex; +} +short mob_get_hair(int class) +{ + return mob_db[class].hair; +} +short mob_get_hair_color(int class) +{ + return mob_db[class].hair_color; +} +short mob_get_weapon(int class) +{ + return mob_db[class].weapon; +} +short mob_get_shield(int class) +{ + return mob_db[class].shield; +} +short mob_get_head_top(int class) +{ + return mob_db[class].head_top; +} +short mob_get_head_mid(int class) +{ + return mob_db[class].head_mid; +} +short mob_get_head_buttom(int class) +{ + return mob_db[class].head_buttom; +} +short mob_get_clothes_color(int class) // Add for player monster dye - Valaris +{ + return mob_db[class].clothes_color; // End +} +int mob_get_equip(int class) // mob equip [Valaris] +{ + return mob_db[class].equip; +} +/*========================================== + * Is MOB in the state in which the present movement is possible or not? + *------------------------------------------ + */ +int mob_can_move(struct mob_data *md) +{ + nullpo_retr(0, md); + + if(md->canmove_tick > gettick() || (md->opt1 > 0 && md->opt1 != 6) || md->option&2) + return 0; + // アンクル中で動けないとか + if( md->sc_data[SC_ANKLE].timer != -1 || //アンクルスネア + md->sc_data[SC_AUTOCOUNTER].timer != -1 || //オートカウンター + md->sc_data[SC_BLADESTOP].timer != -1 || //白刃取り + md->sc_data[SC_SPIDERWEB].timer != -1 //スパイダーウェッブ + ) + return 0; + + return 1; +} + +/*========================================== + * Time calculation concerning one step next to mob + *------------------------------------------ + */ +static int calc_next_walk_step(struct mob_data *md) +{ + nullpo_retr(0, md); + + if(md->walkpath.path_pos>=md->walkpath.path_len) + return -1; + if(md->walkpath.path[md->walkpath.path_pos]&1) + return battle_get_speed(&md->bl)*14/10; + return battle_get_speed(&md->bl); +} + +static int mob_walktoxy_sub(struct mob_data *md); + +/*========================================== + * Mob Walk processing + *------------------------------------------ + */ +static int mob_walk(struct mob_data *md,unsigned int tick,int data) +{ + int moveblock; + int i,ctype; + static int dirx[8]={0,-1,-1,-1,0,1,1,1}; + static int diry[8]={1,1,0,-1,-1,-1,0,1}; + int x,y,dx,dy; + + nullpo_retr(0, md); + + md->state.state=MS_IDLE; + if(md->walkpath.path_pos>=md->walkpath.path_len || md->walkpath.path_pos!=data) + return 0; + + md->walkpath.path_half ^= 1; + if(md->walkpath.path_half==0){ + md->walkpath.path_pos++; + if(md->state.change_walk_target){ + mob_walktoxy_sub(md); + return 0; + } + } + else { + if(md->walkpath.path[md->walkpath.path_pos]>=8) + return 1; + + x = md->bl.x; + y = md->bl.y; + ctype = map_getcell(md->bl.m,x,y); + if(ctype == 1 || ctype == 5) { + mob_stop_walking(md,1); + return 0; + } + md->dir=md->walkpath.path[md->walkpath.path_pos]; + dx = dirx[md->dir]; + dy = diry[md->dir]; + + ctype = map_getcell(md->bl.m,x+dx,y+dy); + if(ctype == 1 || ctype == 5) { + mob_walktoxy_sub(md); + return 0; + } + + moveblock = ( x/BLOCK_SIZE != (x+dx)/BLOCK_SIZE || y/BLOCK_SIZE != (y+dy)/BLOCK_SIZE); + + md->state.state=MS_WALK; + map_foreachinmovearea(clif_moboutsight,md->bl.m,x-AREA_SIZE,y-AREA_SIZE,x+AREA_SIZE,y+AREA_SIZE,dx,dy,BL_PC,md); + + x += dx; + y += dy; + if(md->min_chase>13) + md->min_chase--; + + if(moveblock) map_delblock(&md->bl); + md->bl.x = x; + md->bl.y = y; + if(moveblock) map_addblock(&md->bl); + + map_foreachinmovearea(clif_mobinsight,md->bl.m,x-AREA_SIZE,y-AREA_SIZE,x+AREA_SIZE,y+AREA_SIZE,-dx,-dy,BL_PC,md); + md->state.state=MS_IDLE; + + if(md->option&4) + skill_check_cloaking(&md->bl); + + skill_unit_move(&md->bl,tick,1); // Inspection of a skill unit + } + if((i=calc_next_walk_step(md))>0){ + i = i>>1; + if(i < 1 && md->walkpath.path_half == 0) + i = 1; + md->timer=add_timer(tick+i,mob_timer,md->bl.id,md->walkpath.path_pos); + md->state.state=MS_WALK; + + if(md->walkpath.path_pos>=md->walkpath.path_len) + clif_fixmobpos(md); // When mob stops, retransmission current of a position. + } + return 0; +} + +/*========================================== + * Attack processing of mob + *------------------------------------------ + */ +static int mob_attack(struct mob_data *md,unsigned int tick,int data) +{ + struct block_list *tbl=NULL; + struct map_session_data *tsd=NULL; + struct mob_data *tmd=NULL; + + int mode,race,range; + + nullpo_retr(0, md); + + md->min_chase=13; + md->state.state=MS_IDLE; + md->state.skillstate=MSS_IDLE; + + if( md->skilltimer!=-1 ) // スキル使用中 + return 0; + + if(md->opt1>0 || md->option&2) + return 0; + + if(md->sc_data[SC_AUTOCOUNTER].timer != -1) + return 0; + + if(md->sc_data[SC_BLADESTOP].timer != -1) + return 0; + + if((tbl=map_id2bl(md->target_id))==NULL){ + md->target_id=0; + md->state.targettype = NONE_ATTACKABLE; + return 0; + } + + if(tbl->type==BL_PC) + tsd=(struct map_session_data *)tbl; + else if(tbl->type==BL_MOB) + tmd=(struct mob_data *)tbl; + else + return 0; + + if(tsd){ + if( pc_isdead(tsd) || tsd->invincible_timer != -1 || pc_isinvisible(tsd) || md->bl.m != tbl->m || tbl->prev == NULL || distance(md->bl.x,md->bl.y,tbl->x,tbl->y)>=13 ){ + md->target_id=0; + md->state.targettype = NONE_ATTACKABLE; + return 0; + } + } + if(tmd){ + if(md->bl.m != tbl->m || tbl->prev == NULL || distance(md->bl.x,md->bl.y,tbl->x,tbl->y)>=13){ + md->target_id=0; + md->state.targettype = NONE_ATTACKABLE; + return 0; + } + } + + + if(!md->mode) + mode=mob_db[md->class].mode; + else + mode=md->mode; + + race=mob_db[md->class].race; + if(!(mode&0x80)){ + md->target_id=0; + md->state.targettype = NONE_ATTACKABLE; + return 0; + } + if(tsd && !(mode&0x20) && (tsd->sc_data[SC_TRICKDEAD].timer != -1 || + ((pc_ishiding(tsd) || tsd->state.gangsterparadise) && race!=4 && race!=6) ) ) { + md->target_id=0; + md->state.targettype = NONE_ATTACKABLE; + return 0; + } + + range = mob_db[md->class].range; + if(mode&1) + range++; + if(distance(md->bl.x,md->bl.y,tbl->x,tbl->y) > range) + return 0; + if(battle_config.monster_attack_direction_change) + md->dir=map_calc_dir(&md->bl, tbl->x,tbl->y ); // 向き設定 + + //clif_fixmobpos(md); + + md->state.skillstate=MSS_ATTACK; + if( mobskill_use(md,tick,-2) ) // スキル使用 + return 0; + + md->target_lv = battle_weapon_attack(&md->bl,tbl,tick,0); + + if(!(battle_config.monster_cloak_check_type&2) && md->sc_data[SC_CLOAKING].timer != -1) + skill_status_change_end(&md->bl,SC_CLOAKING,-1); + + md->attackabletime = tick + battle_get_adelay(&md->bl); + + md->timer=add_timer(md->attackabletime,mob_timer,md->bl.id,0); + md->state.state=MS_ATTACK; + + return 0; +} + + +/*========================================== + * The attack of PC which is attacking id is stopped. + * The callback function of clif_foreachclient + *------------------------------------------ + */ +int mob_stopattacked(struct map_session_data *sd,va_list ap) +{ + int id; + + nullpo_retr(0, sd); + nullpo_retr(0, ap); + + id=va_arg(ap,int); + if(sd->attacktarget==id) + pc_stopattack(sd); + return 0; +} +/*========================================== + * The timer in which the mob's states changes + *------------------------------------------ + */ +int mob_changestate(struct mob_data *md,int state,int type) +{ + unsigned int tick; + int i; + + nullpo_retr(0, md); + + if(md->timer != -1) + delete_timer(md->timer,mob_timer); + md->timer=-1; + md->state.state=state; + + switch(state){ + case MS_WALK: + if((i=calc_next_walk_step(md))>0){ + i = i>>2; + md->timer=add_timer(gettick()+i,mob_timer,md->bl.id,0); + } + else + md->state.state=MS_IDLE; + break; + case MS_ATTACK: + tick = gettick(); + i=DIFF_TICK(md->attackabletime,tick); + if(i>0 && i<2000) + md->timer=add_timer(md->attackabletime,mob_timer,md->bl.id,0); + else if(type) { + md->attackabletime = tick + battle_get_amotion(&md->bl); + md->timer=add_timer(md->attackabletime,mob_timer,md->bl.id,0); + } + else { + md->attackabletime = tick + 1; + md->timer=add_timer(md->attackabletime,mob_timer,md->bl.id,0); + } + break; + case MS_DELAY: + md->timer=add_timer(gettick()+type,mob_timer,md->bl.id,0); + break; + case MS_DEAD: + skill_castcancel(&md->bl,0); +// mobskill_deltimer(md); + md->state.skillstate=MSS_DEAD; + md->last_deadtime=gettick(); + // Since it died, all aggressors' attack to this mob is stopped. + clif_foreachclient(mob_stopattacked,md->bl.id); + skill_unit_out_all(&md->bl,gettick(),1); + skill_status_change_clear(&md->bl,2); // The abnormalities in status are canceled. + skill_clear_unitgroup(&md->bl); // All skill unit groups are deleted. + skill_cleartimerskill(&md->bl); + if(md->deletetimer!=-1) + delete_timer(md->deletetimer,mob_timer_delete); + md->deletetimer=-1; + md->hp=md->target_id=md->attacked_id=0; + md->state.targettype = NONE_ATTACKABLE; + break; + } + + return 0; +} + +/*========================================== + * timer processing of mob (timer function) + * It branches to a walk and an attack. + *------------------------------------------ + */ +static int mob_timer(int tid,unsigned int tick,int id,int data) +{ + struct mob_data *md; + struct block_list *bl; + + if( (bl=map_id2bl(id)) == NULL ){ //攻撃してきた敵がもういないのは正常のようだ + return 1; + } + + if(!bl || !bl->type || bl->type!=BL_MOB) + return 1; + + nullpo_retr(1, md=(struct mob_data*)bl); + + if(!md->bl.type || md->bl.type!=BL_MOB) + return 1; + + if(md->timer != tid){ + if(battle_config.error_log==1) + printf("mob_timer %d != %d\n",md->timer,tid); + return 0; + } + md->timer=-1; + if(md->bl.prev == NULL || md->state.state == MS_DEAD) + return 1; + + map_freeblock_lock(); + switch(md->state.state){ + case MS_WALK: + mob_walk(md,tick,data); + break; + case MS_ATTACK: + mob_attack(md,tick,data); + break; + case MS_DELAY: + mob_changestate(md,MS_IDLE,0); + break; + default: + if(battle_config.error_log==1) + printf("mob_timer : %d ?\n",md->state.state); + break; + } + map_freeblock_unlock(); + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +static int mob_walktoxy_sub(struct mob_data *md) +{ + struct walkpath_data wpd; + + nullpo_retr(0, md); + + if(path_search(&wpd,md->bl.m,md->bl.x,md->bl.y,md->to_x,md->to_y,md->state.walk_easy)) + return 1; + memcpy(&md->walkpath,&wpd,sizeof(wpd)); + + md->state.change_walk_target=0; + mob_changestate(md,MS_WALK,0); + clif_movemob(md); + + return 0; +} + +/*========================================== + * mob move start + *------------------------------------------ + */ +int mob_walktoxy(struct mob_data *md,int x,int y,int easy) +{ + struct walkpath_data wpd; + + nullpo_retr(0, md); + + if(md->state.state == MS_WALK && path_search(&wpd,md->bl.m,md->bl.x,md->bl.y,x,y,easy) ) + return 1; + + md->state.walk_easy = easy; + md->to_x=x; + md->to_y=y; + if(md->state.state == MS_WALK) { + md->state.change_walk_target=1; + } else { + return mob_walktoxy_sub(md); + } + + return 0; +} + +/*========================================== + * mob spawn with delay (timer function) + *------------------------------------------ + */ +static int mob_delayspawn(int tid,unsigned int tick,int m,int n) +{ + mob_spawn(m); + return 0; +} + +/*========================================== + * spawn timing calculation + *------------------------------------------ + */ +int mob_setdelayspawn(int id) +{ + unsigned int spawntime,spawntime1,spawntime2,spawntime3; + struct mob_data *md; + struct block_list *bl; + + if((bl=map_id2bl(id)) == NULL) + return -1; + + if(!bl || !bl->type || bl->type!=BL_MOB) + return -1; + + nullpo_retr(-1, md=(struct mob_data*)bl); + + if(!md || md->bl.type!=BL_MOB) + return -1; + + // Processing of MOB which is not revitalized + if(md->spawndelay1==-1 && md->spawndelay2==-1 && md->n==0){ + map_deliddb(&md->bl); + if(md->lootitem) { + map_freeblock(md->lootitem); + md->lootitem=NULL; + } + map_freeblock(md); // Instead of [ of free ] + return 0; + } + + spawntime1=md->last_spawntime+md->spawndelay1; + spawntime2=md->last_deadtime+md->spawndelay2; + spawntime3=gettick()+5000; + // spawntime = max(spawntime1,spawntime2,spawntime3); + if(DIFF_TICK(spawntime1,spawntime2)>0){ + spawntime=spawntime1; + } else { + spawntime=spawntime2; + } + if(DIFF_TICK(spawntime3,spawntime)>0){ + spawntime=spawntime3; + } + + add_timer(spawntime,mob_delayspawn,id,0); + return 0; +} + +/*========================================== + * Mob spawning. Initialization is also variously here. + *------------------------------------------ + */ +int mob_spawn(int id) +{ + int x=0,y=0,i=0,c; + unsigned int tick = gettick(); + struct mob_data *md; + struct block_list *bl; + + nullpo_retr(-1, bl=map_id2bl(id)); + + if(!bl || !bl->type || bl->type!=BL_MOB) + return -1; + + nullpo_retr(-1, md=(struct mob_data*)bl); + + if(!md || !md->bl.type || md->bl.type!=BL_MOB) + return -1; + + md->last_spawntime=tick; + if( md->bl.prev!=NULL ){ +// clif_clearchar_area(&md->bl,3); + skill_unit_out_all(&md->bl,gettick(),1); + map_delblock(&md->bl); + } + else + md->class = md->base_class; + + md->bl.m =md->m; + do { + if(md->x0==0 && md->y0==0){ + x=rand()%(map[md->bl.m].xs-2)+1; + y=rand()%(map[md->bl.m].ys-2)+1; + } else { + x=md->x0+rand()%(md->xs+1)-md->xs/2; + y=md->y0+rand()%(md->ys+1)-md->ys/2; + } + i++; + } while(((c=map_getcell(md->bl.m,x,y))==1 || c==5) && i<50); + + if(i>=50){ +// if(battle_config.error_log==1) +// printf("MOB spawn error %d @ %s\n",id,map[md->bl.m].name); + add_timer(tick+5000,mob_delayspawn,id,0); + return 1; + } + + md->to_x=md->bl.x=x; + md->to_y=md->bl.y=y; + md->dir=0; + + map_addblock(&md->bl); + + memset(&md->state,0,sizeof(md->state)); + md->attacked_id = 0; + md->target_id = 0; + md->move_fail_count = 0; + + if(!md->speed) + md->speed = mob_db[md->class].speed; + md->def_ele = mob_db[md->class].element; + md->master_id=0; + md->master_dist=0; + + md->state.state = MS_IDLE; + md->state.skillstate = MSS_IDLE; + md->timer = -1; + md->last_thinktime = tick; + md->next_walktime = tick+rand()%50+5000; + md->attackabletime = tick; + md->canmove_tick = tick; + + md->sg_count=0; + md->deletetimer=-1; + + md->skilltimer=-1; + for(i=0,c=tick-1000*3600*10;i<MAX_MOBSKILL;i++) + md->skilldelay[i] = c; + md->skillid=0; + md->skilllv=0; + + memset(md->dmglog,0,sizeof(md->dmglog)); + if(md->lootitem) + memset(md->lootitem,0,sizeof(md->lootitem)); + md->lootitem_count = 0; + + for(i=0;i<MAX_MOBSKILLTIMERSKILL;i++) + md->skilltimerskill[i].timer = -1; + + for(i=0;i<MAX_STATUSCHANGE;i++) { + md->sc_data[i].timer=-1; + md->sc_data[i].val1 = md->sc_data[i].val2 = md->sc_data[i].val3 = md->sc_data[i].val4 =0; + } + md->sc_count=0; + md->opt1=md->opt2=md->opt3=md->option=0; + + memset(md->skillunit,0,sizeof(md->skillunit)); + memset(md->skillunittick,0,sizeof(md->skillunittick)); + + md->hp = battle_get_max_hp(&md->bl); + if(md->hp<=0){ + mob_makedummymobdb(md->class); + md->hp = battle_get_max_hp(&md->bl); + } + + clif_spawnmob(md); + + return 0; +} + +/*========================================== + * Distance calculation between two points + *------------------------------------------ + */ +static int distance(int x0,int y0,int x1,int y1) +{ + int dx,dy; + + dx=abs(x0-x1); + dy=abs(y0-y1); + return dx>dy ? dx : dy; +} + +/*========================================== + * The stop of MOB's attack + *------------------------------------------ + */ +int mob_stopattack(struct mob_data *md) +{ + md->target_id=0; + md->state.targettype = NONE_ATTACKABLE; + md->attacked_id=0; + return 0; +} +/*========================================== + * The stop of MOB's walking + *------------------------------------------ + */ +int mob_stop_walking(struct mob_data *md,int type) +{ + nullpo_retr(0, md); + + + if(md->state.state == MS_WALK || md->state.state == MS_IDLE) { + int dx=0,dy=0; + + md->walkpath.path_len=0; + if(type&4){ + dx=md->to_x-md->bl.x; + if(dx<0) + dx=-1; + else if(dx>0) + dx=1; + dy=md->to_y-md->bl.y; + if(dy<0) + dy=-1; + else if(dy>0) + dy=1; + } + md->to_x=md->bl.x+dx; + md->to_y=md->bl.y+dy; + if(dx!=0 || dy!=0){ + mob_walktoxy_sub(md); + return 0; + } + mob_changestate(md,MS_IDLE,0); + } + if(type&0x01) + clif_fixmobpos(md); + if(type&0x02) { + int delay=battle_get_dmotion(&md->bl); + unsigned int tick = gettick(); + if(md->canmove_tick < tick) + md->canmove_tick = tick + delay; + } + + return 0; +} + +/*========================================== + * Reachability to a Specification ID existence place + *------------------------------------------ + */ +int mob_can_reach(struct mob_data *md,struct block_list *bl,int range) +{ + int dx,dy; + struct walkpath_data wpd; + int i; + + nullpo_retr(0, md); + nullpo_retr(0, bl); + + dx=abs(bl->x - md->bl.x); + dy=abs(bl->y - md->bl.y); + + //=========== guildcastle guardian no search start=========== + //when players are the guild castle member not attack them ! + if(md->class == 1285 || md->class == 1286 || md->class == 1287){ + struct map_session_data *sd; + struct guild *g=NULL; + struct guild_castle *gc=guild_mapname2gc(map[bl->m].name); + + if(gc && agit_flag==0) // Guardians will not attack during non-woe time [Valaris] + return 0; // end addition [Valaris] + + if(bl && bl->type == BL_PC){ + if((sd=(struct map_session_data *)bl) == NULL){ + printf("mob_can_reach nullpo\n"); + return 0; + } + + if(gc && sd && sd->status.guild_id && sd->status.guild_id>0) { + g=guild_search(sd->status.guild_id); // don't attack guild members [Valaris] + if(g && g->guild_id > 0 && g->guild_id == gc->guild_id) + return 0; + if(g && gc && guild_isallied(g,gc)) + return 0; + + } + } + } + //========== guildcastle guardian no search eof============== + + if(bl && bl->type == BL_PC && battle_config.monsters_ignore_gm==1) { // option to have monsters ignore GMs [Valaris] + struct map_session_data *sd; + if((sd=(struct map_session_data *)bl) != NULL && pc_isGM(sd)) + return 0; + } + + if( md->bl.m != bl->m) // 違うャbプ + return 0; + + if( range>0 && range < ((dx>dy)?dx:dy) ) // 遠すぎる + return 0; + + if( md->bl.x==bl->x && md->bl.y==bl->y ) // 同じャX + return 1; + + // Obstacle judging + wpd.path_len=0; + wpd.path_pos=0; + wpd.path_half=0; + if(path_search(&wpd,md->bl.m,md->bl.x,md->bl.y,bl->x,bl->y,0)!=-1) + return 1; + + if(bl->type!=BL_PC && bl->type!=BL_MOB) + return 0; + + // It judges whether it can adjoin or not. + dx=(dx>0)?1:((dx<0)?-1:0); + dy=(dy>0)?1:((dy<0)?-1:0); + if(path_search(&wpd,md->bl.m,md->bl.x,md->bl.y,bl->x-dx,bl->y-dy,0)!=-1) + return 1; + for(i=0;i<9;i++){ + if(path_search(&wpd,md->bl.m,md->bl.x,md->bl.y,bl->x-1+i/3,bl->y-1+i%3,0)!=-1) + return 1; + } + return 0; +} + +/*========================================== + * Determination for an attack of a monster + *------------------------------------------ + */ +int mob_target(struct mob_data *md,struct block_list *bl,int dist) +{ + struct map_session_data *sd; + struct status_change *sc_data; + short *option; + int mode,race; + + nullpo_retr(0, md); + nullpo_retr(0, bl); + + sc_data = battle_get_sc_data(bl); + option = battle_get_option(bl); + race=mob_db[md->class].race; + + if(!md->mode){ + mode=mob_db[md->class].mode; + }else{ + mode=md->mode; + } + if(!(mode&0x80)) { + md->target_id = 0; + return 0; + } + // Nothing will be carried out if there is no mind of changing TAGE by TAGE ending. + if( (md->target_id > 0 && md->state.targettype == ATTACKABLE) && ( !(mode&0x04) || rand()%100>25) ) + return 0; + + if(mode&0x20 || // Coercion is exerted if it is MVPMOB. + (sc_data && sc_data[SC_TRICKDEAD].timer == -1 && + ( (option && !(*option&0x06) ) || race==4 || race==6) ) ){ + if(bl->type == BL_PC) { + nullpo_retr(0, sd = (struct map_session_data *)bl); + if(sd->invincible_timer != -1 || pc_isinvisible(sd)) + return 0; + if(!(mode&0x20) && race!=4 && race!=6 && sd->state.gangsterparadise) + return 0; + } + + md->target_id=bl->id; // Since there was no disturbance, it locks on to target. + if(bl->type == BL_PC || bl->type == BL_MOB) + md->state.targettype = ATTACKABLE; + else + md->state.targettype = NONE_ATTACKABLE; + md->min_chase=dist+13; + if(md->min_chase>26) + md->min_chase=26; + } + return 0; +} + +/*========================================== + * The ?? routine of an active monster + *------------------------------------------ + */ +static int mob_ai_sub_hard_activesearch(struct block_list *bl,va_list ap) +{ + struct map_session_data *tsd=NULL; + struct mob_data *smd,*tmd=NULL; + int mode,race,dist,*pcc; + + nullpo_retr(0, bl); + nullpo_retr(0, ap); + nullpo_retr(0, smd=va_arg(ap,struct mob_data *)); + nullpo_retr(0, pcc=va_arg(ap,int *)); + + if(bl->type==BL_PC) + tsd=(struct map_session_data *)bl; + else if(bl->type==BL_MOB) + tmd=(struct mob_data *)bl; + else + return 0; + + //敵味方判定 + if(battle_check_target(&smd->bl,bl,BCT_ENEMY)==0) + return 0; + + if(!smd->mode) + mode=mob_db[smd->class].mode; + else + mode=smd->mode; + + // アクティブでターゲット射程内にいるなら、ロックする + if( mode&0x04 ){ + race=mob_db[smd->class].race; + //対象がPCの場合 + if(tsd && + !pc_isdead(tsd) && + tsd->bl.m == smd->bl.m && + tsd->invincible_timer == -1 && + !pc_isinvisible(tsd) && + (dist=distance(smd->bl.x,smd->bl.y,tsd->bl.x,tsd->bl.y))<9 + ) + { + if(mode&0x20 || + (tsd->sc_data[SC_TRICKDEAD].timer == -1 && + ((!pc_ishiding(tsd) && !tsd->state.gangsterparadise) || race==4 || race==6))){ // 妨害がないか判定 + if( mob_can_reach(smd,bl,12) && // 到達可能性判定 + rand()%1000<1000/(++(*pcc)) ){ // 範囲内PCで等確率にする + smd->target_id=tsd->bl.id; + smd->state.targettype = ATTACKABLE; + smd->min_chase=13; + } + } + } + //対象がMobの場合 + else if(tmd && + tmd->bl.m == smd->bl.m && + (dist=distance(smd->bl.x,smd->bl.y,tmd->bl.x,tmd->bl.y))<9 + ) + { + if( mob_can_reach(smd,bl,12) && // 到達可能性判定 + rand()%1000<1000/(++(*pcc)) ){ // 範囲内で等確率にする + smd->target_id=bl->id; + smd->state.targettype = ATTACKABLE; + smd->min_chase=13; + } + } + } + return 0; +} + +/*========================================== + * loot monster item search + *------------------------------------------ + */ +static int mob_ai_sub_hard_lootsearch(struct block_list *bl,va_list ap) +{ + struct mob_data* md; + int mode,dist,*itc; + + nullpo_retr(0, bl); + nullpo_retr(0, ap); + nullpo_retr(0, md=va_arg(ap,struct mob_data *)); + nullpo_retr(0, itc=va_arg(ap,int *)); + + if(!md->mode){ + mode=mob_db[md->class].mode; + }else{ + mode=md->mode; + } + + if( !md->target_id && mode&0x02){ + if(!md->lootitem || (battle_config.monster_loot_type == 1 && md->lootitem_count >= LOOTITEM_SIZE) ) + return 0; + if(bl->m == md->bl.m && (dist=distance(md->bl.x,md->bl.y,bl->x,bl->y))<9){ + if( mob_can_reach(md,bl,12) && // Reachability judging + rand()%1000<1000/(++(*itc)) ){ // It is made a probability, such as within the limits PC. + md->target_id=bl->id; + md->state.targettype = NONE_ATTACKABLE; + md->min_chase=13; + } + } + } + return 0; +} + +/*========================================== + * The ?? routine of a link monster + *------------------------------------------ + */ +static int mob_ai_sub_hard_linksearch(struct block_list *bl,va_list ap) +{ + struct mob_data *tmd; + struct mob_data* md; + struct block_list *target; + + nullpo_retr(0, bl); + nullpo_retr(0, ap); + nullpo_retr(0, tmd=(struct mob_data *)bl); + nullpo_retr(0, md=va_arg(ap,struct mob_data *)); + nullpo_retr(0, target=va_arg(ap,struct block_list *)); + + // same family free in a range at a link monster -- it will be made to lock if MOB is +/* if( (md->target_id > 0 && md->state.targettype == ATTACKABLE) && mob_db[md->class].mode&0x08){ + if( tmd->class==md->class && (!tmd->target_id || md->state.targettype == NONE_ATTACKABLE) && tmd->bl.m == md->bl.m){ + if( mob_can_reach(tmd,target,12) ){ // Reachability judging + tmd->target_id=md->target_id; + tmd->state.targettype = ATTACKABLE; + tmd->min_chase=13; + } + } + }*/ + if( md->attacked_id > 0 && mob_db[md->class].mode&0x08){ + if( tmd->class==md->class && tmd->bl.m == md->bl.m && (!tmd->target_id || md->state.targettype == NONE_ATTACKABLE)){ + if( mob_can_reach(tmd,target,12) ){ // Reachability judging + tmd->target_id=md->attacked_id; + tmd->state.targettype = ATTACKABLE; + tmd->min_chase=13; + } + } + } + + return 0; +} +/*========================================== + * Processing of slave monsters + *------------------------------------------ + */ +static int mob_ai_sub_hard_slavemob(struct mob_data *md,unsigned int tick) +{ + struct mob_data *mmd=NULL; + struct block_list *bl; + int mode,race,old_dist; + + nullpo_retr(0, md); + + if((bl=map_id2bl(md->master_id)) != NULL ) + mmd=(struct mob_data *)bl; + + mode=mob_db[md->class].mode; + + // It is not main monster/leader. + if(!mmd || mmd->bl.type!=BL_MOB || mmd->bl.id!=md->master_id) + return 0; + + // Since it is in the map on which the master is not, teleport is carried out and it pursues. + if( mmd->bl.m != md->bl.m ){ + mob_warp(md,mmd->bl.m,mmd->bl.x,mmd->bl.y,3); + md->state.master_check = 1; + return 0; + } + + // Distance with between slave and master is measured. + old_dist=md->master_dist; + md->master_dist=distance(md->bl.x,md->bl.y,mmd->bl.x,mmd->bl.y); + + // Since the master was in near immediately before, teleport is carried out and it pursues. + if( old_dist<10 && md->master_dist>18){ + mob_warp(md,-1,mmd->bl.x,mmd->bl.y,3); + md->state.master_check = 1; + return 0; + } + + // Although there is the master, since it is somewhat far, it approaches. + if((!md->target_id || md->state.targettype == NONE_ATTACKABLE) && mob_can_move(md) && + (md->walkpath.path_pos>=md->walkpath.path_len || md->walkpath.path_len==0) && md->master_dist<15){ + int i=0,dx,dy,ret; + if(md->master_dist>4) { + do { + if(i<=5){ + dx=mmd->bl.x - md->bl.x; + dy=mmd->bl.y - md->bl.y; + if(dx<0) dx+=(rand()%( (dx<-3)?3:-dx )+1); + else if(dx>0) dx-=(rand()%( (dx>3)?3:dx )+1); + if(dy<0) dy+=(rand()%( (dy<-3)?3:-dy )+1); + else if(dy>0) dy-=(rand()%( (dy>3)?3:dy )+1); + }else{ + dx=mmd->bl.x - md->bl.x + rand()%7 - 3; + dy=mmd->bl.y - md->bl.y + rand()%7 - 3; + } + + ret=mob_walktoxy(md,md->bl.x+dx,md->bl.y+dy,0); + i++; + } while(ret && i<10); + } + else { + do { + dx = rand()%9 - 5; + dy = rand()%9 - 5; + if( dx == 0 && dy == 0) { + dx = (rand()%1)? 1:-1; + dy = (rand()%1)? 1:-1; + } + dx += mmd->bl.x; + dy += mmd->bl.y; + + ret=mob_walktoxy(md,mmd->bl.x+dx,mmd->bl.y+dy,0); + i++; + } while(ret && i<10); + } + + md->next_walktime=tick+500; + md->state.master_check = 1; + } + + // There is the master, the master locks a target and he does not lock. + if( (mmd->target_id>0 && mmd->state.targettype == ATTACKABLE) && (!md->target_id || md->state.targettype == NONE_ATTACKABLE) ){ + struct map_session_data *sd=map_id2sd(mmd->target_id); + if(sd!=NULL && !pc_isdead(sd) && sd->invincible_timer == -1 && !pc_isinvisible(sd)){ + + race=mob_db[md->class].race; + if(mode&0x20 || + (sd->sc_data[SC_TRICKDEAD].timer == -1 && + ( (!pc_ishiding(sd) && !sd->state.gangsterparadise) || race==4 || race==6) ) ){ // 妨害がないか判定 + + md->target_id=sd->bl.id; + md->state.targettype = ATTACKABLE; + md->min_chase=5+distance(md->bl.x,md->bl.y,sd->bl.x,sd->bl.y); + md->state.master_check = 1; + } + } + } + + // There is the master, the master locks a target and he does not lock. +/* if( (md->target_id>0 && mmd->state.targettype == ATTACKABLE) && (!mmd->target_id || mmd->state.targettype == NONE_ATTACKABLE) ){ + struct map_session_data *sd=map_id2sd(md->target_id); + if(sd!=NULL && !pc_isdead(sd) && sd->invincible_timer == -1 && !pc_isinvisible(sd)){ + + race=mob_db[mmd->class].race; + if(mode&0x20 || + (sd->sc_data[SC_TRICKDEAD].timer == -1 && + (!(sd->status.option&0x06) || race==4 || race==6) + ) ){ // It judges whether there is any disturbance. + + mmd->target_id=sd->bl.id; + mmd->state.targettype = ATTACKABLE; + mmd->min_chase=5+distance(mmd->bl.x,mmd->bl.y,sd->bl.x,sd->bl.y); + } + } + }*/ + + return 0; +} + +/*========================================== + * A lock of target is stopped and mob moves to a standby state. + *------------------------------------------ + */ +static int mob_unlocktarget(struct mob_data *md,int tick) +{ + nullpo_retr(0, md); + + md->target_id=0; + md->state.targettype = NONE_ATTACKABLE; + md->state.skillstate=MSS_IDLE; + md->next_walktime=tick+rand()%3000+3000; + return 0; +} +/*========================================== + * Random walk + *------------------------------------------ + */ +static int mob_randomwalk(struct mob_data *md,int tick) +{ + const int retrycount=20; + int speed; + + nullpo_retr(0, md); + + speed=battle_get_speed(&md->bl); + if(DIFF_TICK(md->next_walktime,tick)<0){ + int i,x,y,c,d=12-md->move_fail_count; + if(d<5) d=5; + for(i=0;i<retrycount;i++){ // Search of a movable place + int r=rand(); + x=md->bl.x+r%(d*2+1)-d; + y=md->bl.y+r/(d*2+1)%(d*2+1)-d; + if((c=map_getcell(md->bl.m,x,y))!=1 && c!=5 && mob_walktoxy(md,x,y,1)==0){ + md->move_fail_count=0; + break; + } + if(i+1>=retrycount){ + md->move_fail_count++; + if(md->move_fail_count>1000){ + if(battle_config.error_log==1) + printf("MOB cant move. random spawn %d, class = %d\n",md->bl.id,md->class); + md->move_fail_count=0; + mob_spawn(md->bl.id); + } + } + } + for(i=c=0;i<md->walkpath.path_len;i++){ // The next walk start time is calculated. + if(md->walkpath.path[i]&1) + c+=speed*14/10; + else + c+=speed; + } + md->next_walktime = tick+rand()%3000+3000+c; + md->state.skillstate=MSS_WALK; + return 1; + } + return 0; +} + +/*========================================== + * AI of MOB whose is near a Player + *------------------------------------------ + */ +static int mob_ai_sub_hard(struct block_list *bl,va_list ap) +{ + struct mob_data *md,*tmd=NULL; + struct map_session_data *tsd=NULL; + struct block_list *tbl=NULL; + struct flooritem_data *fitem; + unsigned int tick; + int i,dx,dy,ret,dist; + int attack_type=0; + int mode,race; + + nullpo_retr(0, bl); + nullpo_retr(0, ap); + nullpo_retr(0, md=(struct mob_data*)bl); + + tick=va_arg(ap,unsigned int); + + + if(DIFF_TICK(tick,md->last_thinktime)<MIN_MOBTHINKTIME) + return 0; + md->last_thinktime=tick; + + if( md->skilltimer!=-1 || md->bl.prev==NULL ){ // Under a skill aria and death + if(DIFF_TICK(tick,md->next_walktime)>MIN_MOBTHINKTIME) + md->next_walktime=tick; + return 0; + } + + if(!md->mode) + mode=mob_db[md->class].mode; + else + mode=md->mode; + + race=mob_db[md->class].race; + + // Abnormalities + if((md->opt1 > 0 && md->opt1 != 6) || md->state.state==MS_DELAY || md->sc_data[SC_BLADESTOP].timer != -1) + return 0; + + if(!(mode&0x80) && md->target_id > 0) + md->target_id = 0; + + if(md->attacked_id > 0 && mode&0x08){ // Link monster + struct map_session_data *asd=map_id2sd(md->attacked_id); + if(asd){ + if(asd->invincible_timer == -1 && !pc_isinvisible(asd)){ + map_foreachinarea(mob_ai_sub_hard_linksearch,md->bl.m, + md->bl.x-13,md->bl.y-13, + md->bl.x+13,md->bl.y+13, + BL_MOB,md,&asd->bl); + } + } + } + + // It checks to see it was attacked first (if active, it is target change at 25% of probability). + if( mode>0 && md->attacked_id>0 && (!md->target_id || md->state.targettype == NONE_ATTACKABLE + || (mode&0x04 && rand()%100<25 ) ) ){ + struct block_list *abl=map_id2bl(md->attacked_id); + struct map_session_data *asd=NULL; + if(abl){ + if(abl->type==BL_PC) + asd=(struct map_session_data *)abl; + if(asd==NULL || md->bl.m != abl->m || abl->prev == NULL || asd->invincible_timer != -1 || pc_isinvisible(asd) || + (dist=distance(md->bl.x,md->bl.y,abl->x,abl->y))>=32 || battle_check_target(bl,abl,BCT_ENEMY)==0) + md->attacked_id=0; + else { + md->target_id=md->attacked_id; // set target + md->state.targettype = ATTACKABLE; + attack_type = 1; + md->attacked_id=0; + md->min_chase=dist+13; + if(md->min_chase>26) + md->min_chase=26; + } + } + } + + md->state.master_check = 0; + // Processing of slave monster + if( md->master_id > 0 && md->state.special_mob_ai==0) + mob_ai_sub_hard_slavemob(md,tick); + + // アクティヴモンスターの策敵 (?? of a bitter taste TIVU monster) + if( (!md->target_id || md->state.targettype == NONE_ATTACKABLE) && mode&0x04 && !md->state.master_check && + battle_config.monster_active_enable==1){ + i=0; + if(md->state.special_mob_ai){ + map_foreachinarea(mob_ai_sub_hard_activesearch,md->bl.m, + md->bl.x-AREA_SIZE*2,md->bl.y-AREA_SIZE*2, + md->bl.x+AREA_SIZE*2,md->bl.y+AREA_SIZE*2, + 0,md,&i); + }else{ + map_foreachinarea(mob_ai_sub_hard_activesearch,md->bl.m, + md->bl.x-AREA_SIZE*2,md->bl.y-AREA_SIZE*2, + md->bl.x+AREA_SIZE*2,md->bl.y+AREA_SIZE*2, + BL_PC,md,&i); + } + } + + // The item search of a route monster + if( !md->target_id && mode&0x02 && !md->state.master_check){ + i=0; + map_foreachinarea(mob_ai_sub_hard_lootsearch,md->bl.m, + md->bl.x-AREA_SIZE*2,md->bl.y-AREA_SIZE*2, + md->bl.x+AREA_SIZE*2,md->bl.y+AREA_SIZE*2, + BL_ITEM,md,&i); + } + + // It will attack, if the candidate for an attack is. + if(md->target_id > 0){ + if((tbl=map_id2bl(md->target_id))){ + if(tbl->type==BL_PC) + tsd=(struct map_session_data *)tbl; + else if(tbl->type==BL_MOB) + tmd=(struct mob_data *)tbl; + if(tsd || tmd) { + if(tbl->m != md->bl.m || tbl->prev == NULL || (dist=distance(md->bl.x,md->bl.y,tbl->x,tbl->y))>=md->min_chase) + mob_unlocktarget(md,tick); // 別マップか、視界外 + else if( tsd && !(mode&0x20) && (tsd->sc_data[SC_TRICKDEAD].timer != -1 || ((pc_ishiding(tsd) || tsd->state.gangsterparadise) && race!=4 && race!=6)) ) + mob_unlocktarget(md,tick); // スキルなどによる策敵妨害 + else if(!battle_check_range(&md->bl,tbl,mob_db[md->class].range)){ + // 攻撃範囲外なので移動 + if(!(mode&1)){ // 移動しないモード + mob_unlocktarget(md,tick); + return 0; + } + if( !mob_can_move(md) ) // 動けない状態にある + return 0; + md->state.skillstate=MSS_CHASE; // 突撃時スキル + mobskill_use(md,tick,-1); +// if(md->timer != -1 && (DIFF_TICK(md->next_walktime,tick)<0 || distance(md->to_x,md->to_y,tsd->bl.x,tsd->bl.y)<2) ) + if(md->timer != -1 && md->state.state!=MS_ATTACK && (DIFF_TICK(md->next_walktime,tick)<0 || distance(md->to_x,md->to_y,tbl->x,tbl->y)<2) ) + return 0; // 既に移動中 + if( !mob_can_reach(md,tbl,(md->min_chase>13)?md->min_chase:13) ) + mob_unlocktarget(md,tick); // 移動できないのでタゲ解除(IWとか?) + else{ + // 追跡 + md->next_walktime=tick+500; + i=0; + do { + if(i==0){ // 最初はAEGISと同じ方法で検索 + dx=tbl->x - md->bl.x; + dy=tbl->y - md->bl.y; + if(dx<0) dx++; + else if(dx>0) dx--; + if(dy<0) dy++; + else if(dy>0) dy--; + }else{ // だめならAthena式(ランダム) + dx=tbl->x - md->bl.x + rand()%3 - 1; + dy=tbl->y - md->bl.y + rand()%3 - 1; + } + /* if(path_search(&md->walkpath,md->bl.m,md->bl.x,md->bl.y,md->bl.x+dx,md->bl.y+dy,0)){ + dx=tsd->bl.x - md->bl.x; + dy=tsd->bl.y - md->bl.y; + if(dx<0) dx--; + else if(dx>0) dx++; + if(dy<0) dy--; + else if(dy>0) dy++; + }*/ + ret=mob_walktoxy(md,md->bl.x+dx,md->bl.y+dy,0); + i++; + } while(ret && i<5); + + if(ret){ // 移動不可能な所からの攻撃なら2歩下る + if(dx<0) dx=2; + else if(dx>0) dx=-2; + if(dy<0) dy=2; + else if(dy>0) dy=-2; + mob_walktoxy(md,md->bl.x+dx,md->bl.y+dy,0); + } + } + } else { // 攻撃射程範囲内 + md->state.skillstate=MSS_ATTACK; + if(md->state.state==MS_WALK) + mob_stop_walking(md,1); // 歩行中なら停止 + if(md->state.state==MS_ATTACK) + return 0; // 既に攻撃中 + mob_changestate(md,MS_ATTACK,attack_type); + +/* if(mode&0x08){ // リンクモンスター + map_foreachinarea(mob_ai_sub_hard_linksearch,md->bl.m, + md->bl.x-13,md->bl.y-13, + md->bl.x+13,md->bl.y+13, + BL_MOB,md,&tsd->bl); + }*/ + } + return 0; + }else{ // ルートモンスター処理 + if(tbl == NULL || tbl->type != BL_ITEM ||tbl->m != md->bl.m || + (dist=distance(md->bl.x,md->bl.y,tbl->x,tbl->y))>=md->min_chase || !md->lootitem){ + // 遠すぎるかアイテムがなくなった + mob_unlocktarget(md,tick); + if(md->state.state==MS_WALK) + mob_stop_walking(md,1); // 歩行中なら停止 + }else if(dist){ + if(!(mode&1)){ // 移動しないモード + mob_unlocktarget(md,tick); + return 0; + } + if( !mob_can_move(md) ) // 動けない状態にある + return 0; + md->state.skillstate=MSS_LOOT; // ルート時スキル使用 + mobskill_use(md,tick,-1); +// if(md->timer != -1 && (DIFF_TICK(md->next_walktime,tick)<0 || distance(md->to_x,md->to_y,tbl->x,tbl->y)<2) ) + if(md->timer != -1 && md->state.state!=MS_ATTACK && (DIFF_TICK(md->next_walktime,tick)<0 || distance(md->to_x,md->to_y,tbl->x,tbl->y) <= 0)) + return 0; // 既に移動中 + md->next_walktime=tick+500; + dx=tbl->x - md->bl.x; + dy=tbl->y - md->bl.y; +/* if(path_search(&md->walkpath,md->bl.m,md->bl.x,md->bl.y,md->bl.x+dx,md->bl.y+dy,0)){ + dx=tbl->x - md->bl.x; + dy=tbl->y - md->bl.y; + }*/ + ret=mob_walktoxy(md,md->bl.x+dx,md->bl.y+dy,0); + if(ret) + mob_unlocktarget(md,tick);// 移動できないのでタゲ解除(IWとか?) + }else{ // アイテムまでたどり着いた + if(md->state.state==MS_ATTACK) + return 0; // 攻撃中 + if(md->state.state==MS_WALK) + mob_stop_walking(md,1); // 歩行中なら停止 + fitem = (struct flooritem_data *)tbl; + if(md->lootitem_count < LOOTITEM_SIZE) + memcpy(&md->lootitem[md->lootitem_count++],&fitem->item_data,sizeof(md->lootitem[0])); + else if(battle_config.monster_loot_type == 1 && md->lootitem_count >= LOOTITEM_SIZE) { + mob_unlocktarget(md,tick); + return 0; + } + else { + if(md->lootitem[0].card[0] == (short)0xff00) + intif_delete_petdata(*((long *)(&md->lootitem[0].card[1]))); + for(i=0;i<LOOTITEM_SIZE-1;i++) + memcpy(&md->lootitem[i],&md->lootitem[i+1],sizeof(md->lootitem[0])); + memcpy(&md->lootitem[LOOTITEM_SIZE-1],&fitem->item_data,sizeof(md->lootitem[0])); + } + map_clearflooritem(tbl->id); + mob_unlocktarget(md,tick); + } + return 0; + } + }else{ + mob_unlocktarget(md,tick); + if(md->state.state==MS_WALK) + mob_stop_walking(md,4); // 歩行中なら停止 + return 0; + } + } + + // It is skill use at the time of /standby at the time of a walk. + if( mobskill_use(md,tick,-1) ) + return 0; + + // 歩行処理 + if( mode&1 && mob_can_move(md) && // 移動可能MOB&動ける状態にある + (md->master_id==0 || md->state.special_mob_ai || md->master_dist>10) ){ //取り巻きMOBじゃない + + if( DIFF_TICK(md->next_walktime,tick) > + 7000 && + (md->walkpath.path_len==0 || md->walkpath.path_pos>=md->walkpath.path_len) ){ + md->next_walktime = tick + 3000*rand()%2000; + } + + // Random movement + if( mob_randomwalk(md,tick) ) + return 0; + } + + // Since he has finished walking, it stands by. + if( md->walkpath.path_len==0 || md->walkpath.path_pos>=md->walkpath.path_len ) + md->state.skillstate=MSS_IDLE; + return 0; +} + +/*========================================== + * Serious processing for mob in PC field of view (foreachclient) + *------------------------------------------ + */ +static int mob_ai_sub_foreachclient(struct map_session_data *sd,va_list ap) +{ + unsigned int tick; + nullpo_retr(0, sd); + nullpo_retr(0, ap); + + tick=va_arg(ap,unsigned int); + map_foreachinarea(mob_ai_sub_hard,sd->bl.m, + sd->bl.x-AREA_SIZE*2,sd->bl.y-AREA_SIZE*2, + sd->bl.x+AREA_SIZE*2,sd->bl.y+AREA_SIZE*2, + BL_MOB,tick); + + return 0; +} + +/*========================================== + * Serious processing for mob in PC field of view (interval timer function) + *------------------------------------------ + */ +static int mob_ai_hard(int tid,unsigned int tick,int id,int data) +{ + clif_foreachclient(mob_ai_sub_foreachclient,tick); + + return 0; +} + +/*========================================== + * Negligent mode MOB AI (PC is not in near) + *------------------------------------------ + */ +static int mob_ai_sub_lazy(void * key,void * data,va_list app) +{ + struct mob_data *md=data; + unsigned int tick; + va_list ap; + + nullpo_retr(0, md); + nullpo_retr(0, app); + nullpo_retr(0, ap=va_arg(app,va_list)); + + if(md==NULL) + return 0; + + if(!md->bl.type || md->bl.type!=BL_MOB) + return 0; + + tick=va_arg(ap,unsigned int); + + if(DIFF_TICK(tick,md->last_thinktime)<MIN_MOBTHINKTIME*10) + return 0; + md->last_thinktime=tick; + + if(md->bl.prev==NULL || md->skilltimer!=-1){ + if(DIFF_TICK(tick,md->next_walktime)>MIN_MOBTHINKTIME*10) + md->next_walktime=tick; + return 0; + } + + if(DIFF_TICK(md->next_walktime,tick)<0 && + (mob_db[md->class].mode&1) && mob_can_move(md) ){ + + if( map[md->bl.m].users>0 ){ + // Since PC is in the same map, somewhat better negligent processing is carried out. + + // It sometimes moves. + if(rand()%1000<MOB_LAZYMOVEPERC) + mob_randomwalk(md,tick); + + // MOB which is not not the summons MOB but BOSS, either sometimes reboils. + else if( rand()%1000<MOB_LAZYWARPPERC && md->x0<=0 && md->master_id!=0 && + mob_db[md->class].mexp <= 0 && !(mob_db[md->class].mode & 0x20)) + mob_spawn(md->bl.id); + + }else{ + // Since PC is not even in the same map, suitable processing is carried out even if it takes. + + // MOB which is not BOSS which is not Summons MOB, either -- a case -- sometimes -- leaping + if( rand()%1000<MOB_LAZYWARPPERC && md->x0<=0 && md->master_id!=0 && + mob_db[md->class].mexp <= 0 && !(mob_db[md->class].mode & 0x20)) + mob_warp(md,-1,-1,-1,-1); + } + + md->next_walktime = tick+rand()%10000+5000; + } + return 0; +} + +/*========================================== + * Negligent processing for mob outside PC field of view (interval timer function) + *------------------------------------------ + */ +static int mob_ai_lazy(int tid,unsigned int tick,int id,int data) +{ + map_foreachiddb(mob_ai_sub_lazy,tick); + + return 0; +} + + +/*========================================== + * The structure object for item drop with delay + * Since it is only two being able to pass [ int ] a timer function + * Data is put in and passed to this structure object. + *------------------------------------------ + */ +struct delay_item_drop { + int m,x,y; + int nameid,amount; + struct map_session_data *first_sd,*second_sd,*third_sd; +}; + +struct delay_item_drop2 { + int m,x,y; + struct item item_data; + struct map_session_data *first_sd,*second_sd,*third_sd; +}; + +/*========================================== + * item drop with delay (timer function) + *------------------------------------------ + */ +static int mob_delay_item_drop(int tid,unsigned int tick,int id,int data) +{ + struct delay_item_drop *ditem; + struct item temp_item; + int flag; + + nullpo_retr(0, ditem=(struct delay_item_drop *)id); + + memset(&temp_item,0,sizeof(temp_item)); + temp_item.nameid = ditem->nameid; + temp_item.amount = ditem->amount; + temp_item.identify = !itemdb_isequip3(temp_item.nameid); + + if(battle_config.item_auto_get==1){ + if(ditem->first_sd && (flag = pc_additem(ditem->first_sd,&temp_item,ditem->amount))){ + clif_additem(ditem->first_sd,0,0,flag); + map_addflooritem(&temp_item,1,ditem->m,ditem->x,ditem->y,ditem->first_sd,ditem->second_sd,ditem->third_sd,0); + } + free(ditem); + return 0; + } + + map_addflooritem(&temp_item,1,ditem->m,ditem->x,ditem->y,ditem->first_sd,ditem->second_sd,ditem->third_sd,0); + + free(ditem); + return 0; +} + +/*========================================== + * item drop (timer function)-lootitem with delay + *------------------------------------------ + */ +static int mob_delay_item_drop2(int tid,unsigned int tick,int id,int data) +{ + struct delay_item_drop2 *ditem; + int flag; + + nullpo_retr(0, ditem=(struct delay_item_drop2 *)id); + + if(battle_config.item_auto_get==1){ + if(ditem->first_sd && (flag = pc_additem(ditem->first_sd,&ditem->item_data,ditem->item_data.amount))){ + clif_additem(ditem->first_sd,0,0,flag); + map_addflooritem(&ditem->item_data,ditem->item_data.amount,ditem->m,ditem->x,ditem->y,ditem->first_sd,ditem->second_sd,ditem->third_sd,0); + } + free(ditem); + return 0; + } + + map_addflooritem(&ditem->item_data,ditem->item_data.amount,ditem->m,ditem->x,ditem->y,ditem->first_sd,ditem->second_sd,ditem->third_sd,0); + + free(ditem); + return 0; +} + +/*========================================== + * mob data is erased. + *------------------------------------------ + */ +int mob_delete(struct mob_data *md) +{ + nullpo_retr(1, md); + + if(md->bl.prev == NULL) + return 1; + mob_changestate(md,MS_DEAD,0); + clif_clearchar_area(&md->bl,1); + map_delblock(&md->bl); + if(mob_get_viewclass(md->class) <= 1000) + clif_clearchar_delay(gettick()+3000,&md->bl,0); + mob_deleteslave(md); + mob_setdelayspawn(md->bl.id); + return 0; +} + +int mob_catch_delete(struct mob_data *md,int type) +{ + nullpo_retr(1, md); + + if(md->bl.prev == NULL) + return 1; + mob_changestate(md,MS_DEAD,0); + clif_clearchar_area(&md->bl,type); + map_delblock(&md->bl); + mob_setdelayspawn(md->bl.id); + return 0; +} + +int mob_timer_delete(int tid, unsigned int tick, int id, int data) +{ + struct block_list *bl=map_id2bl(id); + struct mob_data *md; + + nullpo_retr(0, bl); + + md = (struct mob_data *)bl; + mob_catch_delete(md,3); + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int mob_deleteslave_sub(struct block_list *bl,va_list ap) +{ + struct mob_data *md; + int id; + + nullpo_retr(0, bl); + nullpo_retr(0, ap); + nullpo_retr(0, md = (struct mob_data *)bl); + + id=va_arg(ap,int); + if(md->master_id > 0 && md->master_id == id ) + mob_damage(NULL,md,md->hp,1); + return 0; +} +/*========================================== + * + *------------------------------------------ + */ +int mob_deleteslave(struct mob_data *md) +{ + nullpo_retr(0, md); + + map_foreachinarea(mob_deleteslave_sub, md->bl.m, + 0,0,map[md->bl.m].xs,map[md->bl.m].ys, + BL_MOB,md->bl.id); + return 0; +} + +/*========================================== + * It is the damage of sd to damage to md. + *------------------------------------------ + */ +int mob_damage(struct block_list *src,struct mob_data *md,int damage,int type) +{ + int i,count,minpos,mindmg; + struct map_session_data *sd = NULL,*tmpsd[DAMAGELOG_SIZE]; + struct { + struct party *p; + int id,base_exp,job_exp; + } pt[DAMAGELOG_SIZE]; + int pnum=0; + int mvp_damage,max_hp; + unsigned int tick = gettick(); + struct map_session_data *mvp_sd = NULL, *second_sd = NULL,*third_sd = NULL; + double dmg_rate,tdmg,temp; + struct item item; + int ret; + int drop_rate; + int skill,sp; + + nullpo_retr(0, md); //srcはNULLで呼ばれる場合もあるので、他でチェック + + max_hp = battle_get_max_hp(&md->bl); + + if(src && src->type == BL_PC) { + sd = (struct map_session_data *)src; + mvp_sd = sd; + } + +// if(battle_config.battle_log) +// printf("mob_damage %d %d %d\n",md->hp,max_hp,damage); + if(md->bl.prev==NULL){ + if(battle_config.error_log==1) + printf("mob_damage : BlockError!!\n"); + return 0; + } + + if(md->state.state==MS_DEAD || md->hp<=0) { + if(md->bl.prev != NULL) { + mob_changestate(md,MS_DEAD,0); + mobskill_use(md,tick,-1); // It is skill at the time of death. + clif_clearchar_area(&md->bl,1); + map_delblock(&md->bl); + mob_setdelayspawn(md->bl.id); + } + return 0; + } + + if(md->sc_data[SC_ENDURE].timer == -1) + mob_stop_walking(md,3); + if(damage > max_hp>>2) + skill_stop_dancing(&md->bl,0); + + if(md->hp > max_hp) + md->hp = max_hp; + + // The amount of overkill rounds to hp. + if(damage>md->hp) + damage=md->hp; + + if(!(type&2)) { + if(sd!=NULL){ + for(i=0,minpos=0,mindmg=0x7fffffff;i<DAMAGELOG_SIZE;i++){ + if(md->dmglog[i].id==sd->bl.id) + break; + if(md->dmglog[i].id==0){ + minpos=i; + mindmg=0; + } + else if(md->dmglog[i].dmg<mindmg){ + minpos=i; + mindmg=md->dmglog[i].dmg; + } + } + if(i<DAMAGELOG_SIZE) + md->dmglog[i].dmg+=damage; + else { + md->dmglog[minpos].id=sd->bl.id; + md->dmglog[minpos].dmg=damage; + } + + if(md->attacked_id <= 0 && md->state.special_mob_ai==0) + md->attacked_id = sd->bl.id; + } + if(src && src->type == BL_PET && battle_config.pet_attack_exp_to_master==1) { + struct pet_data *pd = (struct pet_data *)src; + nullpo_retr(0, pd); + for(i=0,minpos=0,mindmg=0x7fffffff;i<DAMAGELOG_SIZE;i++){ + if(md->dmglog[i].id==pd->msd->bl.id) + break; + if(md->dmglog[i].id==0){ + minpos=i; + mindmg=0; + } + else if(md->dmglog[i].dmg<mindmg){ + minpos=i; + mindmg=md->dmglog[i].dmg; + } + } + if(i<DAMAGELOG_SIZE) + md->dmglog[i].dmg+=(damage*battle_config.pet_attack_exp_rate)/100; + else { + md->dmglog[minpos].id=pd->msd->bl.id; + md->dmglog[minpos].dmg=(damage*battle_config.pet_attack_exp_rate)/100; + } + } + if(src && src->type == BL_MOB && ((struct mob_data*)src)->state.special_mob_ai){ + struct mob_data *md2 = (struct mob_data *)src; + nullpo_retr(0, md2); + for(i=0,minpos=0,mindmg=0x7fffffff;i<DAMAGELOG_SIZE;i++){ + if(md->dmglog[i].id==md2->master_id) + break; + if(md->dmglog[i].id==0){ + minpos=i; + mindmg=0; + } + else if(md->dmglog[i].dmg<mindmg){ + minpos=i; + mindmg=md->dmglog[i].dmg; + } + } + if(i<DAMAGELOG_SIZE) + md->dmglog[i].dmg+=damage; + else { + md->dmglog[minpos].id=md2->master_id; + md->dmglog[minpos].dmg=damage; + + if(md->attacked_id <= 0 && md->state.special_mob_ai==0) + md->attacked_id = md2->master_id; + } + } + + } + + md->hp-=damage; + + if(md->class >= 1285 && md->class <=1287) { // guardian hp update [Valaris] + struct guild_castle *gc=guild_mapname2gc(map[md->bl.m].name); + if(gc) { + + if(md->bl.id==gc->GID0) { + gc->Ghp0=md->hp; + if(gc->Ghp0<=0) { + guild_castledatasave(gc->castle_id,10,0); + guild_castledatasave(gc->castle_id,18,0); + } + } + if(md->bl.id==gc->GID1) { + gc->Ghp1=md->hp; + if(gc->Ghp1<=0) { + guild_castledatasave(gc->castle_id,11,0); + guild_castledatasave(gc->castle_id,19,0); + } + } + if(md->bl.id==gc->GID2) { + gc->Ghp2=md->hp; + if(gc->Ghp2<=0) { + guild_castledatasave(gc->castle_id,12,0); + guild_castledatasave(gc->castle_id,20,0); + } + } + if(md->bl.id==gc->GID3) { + gc->Ghp3=md->hp; + if(gc->Ghp3<=0) { + guild_castledatasave(gc->castle_id,13,0); + guild_castledatasave(gc->castle_id,21,0); + } + } + if(md->bl.id==gc->GID4) { + gc->Ghp4=md->hp; + if(gc->Ghp4<=0) { + guild_castledatasave(gc->castle_id,14,0); + guild_castledatasave(gc->castle_id,22,0); + } + } + if(md->bl.id==gc->GID5) { + gc->Ghp5=md->hp; + if(gc->Ghp5<=0) { + guild_castledatasave(gc->castle_id,15,0); + guild_castledatasave(gc->castle_id,23,0); + } + } + if(md->bl.id==gc->GID6) { + gc->Ghp6=md->hp; + if(gc->Ghp6<=0) { + guild_castledatasave(gc->castle_id,16,0); + guild_castledatasave(gc->castle_id,24,0); + } + } + if(md->bl.id==gc->GID7) { + gc->Ghp7=md->hp; + if(gc->Ghp7<=0) { + guild_castledatasave(gc->castle_id,17,0); + guild_castledatasave(gc->castle_id,25,0); + + } + } + } + } // end addition [Valaris] + + if(md->option&2 ) + skill_status_change_end(&md->bl, SC_HIDING, -1); + if(md->option&4 ) + skill_status_change_end(&md->bl, SC_CLOAKING, -1); + + if(md->state.special_mob_ai == 2){//スフィアーマイン + int skillidx=0; + + if((skillidx=mob_skillid2skillidx(md->class,NPC_SELFDESTRUCTION2))>=0){ + md->mode |= 0x1; + md->next_walktime=tick; + mobskill_use_id(md,&md->bl,skillidx);//自爆詠唱開始 + md->state.special_mob_ai++; + } + } + + if(md->hp>0){ + return 0; + } + + // ----- ここから死亡処理 ----- + + map_freeblock_lock(); + mob_changestate(md,MS_DEAD,0); + mobskill_use(md,tick,-1); // 死亡時スキル + + memset(tmpsd,0,sizeof(tmpsd)); + memset(pt,0,sizeof(pt)); + + max_hp = battle_get_max_hp(&md->bl); + + if(src && src->type == BL_MOB) + mob_unlocktarget((struct mob_data *)src,tick); + + /* ソウルドレイン */ + if(sd && (skill=pc_checkskill(sd,HW_SOULDRAIN))>0){ + clif_skill_nodamage(src,&md->bl,HW_SOULDRAIN,skill,1); + sp = (battle_get_lv(&md->bl))*(65+15*skill)/100; + if(sd->status.sp + sp > sd->status.max_sp) + sp = sd->status.max_sp - sd->status.sp; + sd->status.sp += sp; + clif_heal(sd->fd,SP_SP,sp); + } + + // map外に消えた人は計算から除くので + // overkill分は無いけどsumはmax_hpとは違う + + tdmg = 0; + for(i=0,count=0,mvp_damage=0;i<DAMAGELOG_SIZE;i++){ + if(md->dmglog[i].id==0) + continue; + tmpsd[i] = map_id2sd(md->dmglog[i].id); + if(tmpsd[i] == NULL) + continue; + count++; + if(tmpsd[i]->bl.m != md->bl.m || pc_isdead(tmpsd[i])) + continue; + + tdmg += (double)md->dmglog[i].dmg; + if(mvp_damage<md->dmglog[i].dmg){ + third_sd = second_sd; + second_sd = mvp_sd; + mvp_sd=tmpsd[i]; + mvp_damage=md->dmglog[i].dmg; + } + } + + // [MouseJstr] + if((map[md->bl.m].flag.pvp == 0) || (battle_config.pvp_exp == 1)) { + + if((double)max_hp < tdmg) + dmg_rate = ((double)max_hp) / tdmg; + else dmg_rate = 1; + + // 経験値の分配 + for(i=0;i<DAMAGELOG_SIZE;i++){ + int pid,base_exp,job_exp,flag=1; + double per; + struct party *p; + if(tmpsd[i]==NULL || tmpsd[i]->bl.m != md->bl.m) + continue; +/* jAthena's exp formula + per = ((double)md->dmglog[i].dmg)*(9.+(double)((count > 6)? 6:count))/10./((double)max_hp) * dmg_rate; + temp = ((double)mob_db[md->class].base_exp * (double)battle_config.base_exp_rate / 100. * per); + base_exp = (temp > 2147483647.)? 0x7fffffff:(int)temp; + if(mob_db[md->class].base_exp > 0 && base_exp < 1) base_exp = 1; + if(base_exp < 0) base_exp = 0; + temp = ((double)mob_db[md->class].job_exp * (double)battle_config.job_exp_rate / 100. * per); + job_exp = (temp > 2147483647.)? 0x7fffffff:(int)temp; + if(mob_db[md->class].job_exp > 0 && job_exp < 1) job_exp = 1; + if(job_exp < 0) job_exp = 0; +*/ +//eAthena's exp formula rather than jAthena's + per=(double)md->dmglog[i].dmg*256*(9+(double)((count > 6)? 6:count))/10/(double)max_hp; + if(per>512) per=512; + if(per<1) per=1; + base_exp=mob_db[md->class].base_exp*per/256; + if(base_exp < 1) base_exp = 1; + if(sd && md && battle_config.pk_mode==1 && (mob_db[md->class].lv - sd->status.base_level >= 20)) { + base_exp*=1.15; // pk_mode additional exp if monster >20 levels [Valaris] + } + if(md->state.special_mob_ai >= 1 && battle_config.alchemist_summon_reward != 1) base_exp = 0; // Added [Valaris] + job_exp=mob_db[md->class].job_exp*per/256; + if(job_exp < 1) job_exp = 1; + if(sd && md && battle_config.pk_mode==1 && (mob_db[md->class].lv - sd->status.base_level >= 20)) { + job_exp*=1.15; // pk_mode additional exp if monster >20 levels [Valaris] + } + if(md->state.special_mob_ai >= 1 && battle_config.alchemist_summon_reward != 1) job_exp = 0; // Added [Valaris] + + if((pid=tmpsd[i]->status.party_id)>0){ // パーティに入っている + int j=0; + for(j=0;j<pnum;j++) // 公平パーティリストにいるかどうか + if(pt[j].id==pid) + break; + if(j==pnum){ // いないときは公平かどうか確認 + if((p=party_search(pid))!=NULL && p->exp!=0){ + pt[pnum].id=pid; + pt[pnum].p=p; + pt[pnum].base_exp=base_exp; + pt[pnum].job_exp=job_exp; + pnum++; + flag=0; + } + }else{ // いるときは公平 + pt[j].base_exp+=base_exp; + pt[j].job_exp+=job_exp; + flag=0; + } + } + if(flag) // 各自所得 + pc_gainexp(tmpsd[i],base_exp,job_exp); + } + // 公平分配 + for(i=0;i<pnum;i++) + party_exp_share(pt[i].p,md->bl.m,pt[i].base_exp,pt[i].job_exp); + + // item drop + if(!(type&1)) { + for(i=0;i<8;i++){ + struct delay_item_drop *ditem; + int drop_rate; + + if(md->state.special_mob_ai >= 1 && battle_config.alchemist_summon_reward != 1) // Added [Valaris] + break; // End + + if(mob_db[md->class].dropitem[i].nameid <= 0) + continue; + drop_rate = mob_db[md->class].dropitem[i].p; + if(drop_rate <= 0 && battle_config.drop_rate0item==1) + drop_rate = 1; + if(battle_config.drops_by_luk>0 && sd && md) drop_rate+=(sd->status.luk*battle_config.drops_by_luk)/100; // drops affected by luk [Valaris] + if(sd && md && battle_config.pk_mode==1 && (mob_db[md->class].lv - sd->status.base_level >= 20)) drop_rate*=1.25; // pk_mode increase drops if 20 level difference [Valaris] + if(drop_rate <= rand()%10000) + continue; + + ditem=(struct delay_item_drop *)aCalloc(1,sizeof(struct delay_item_drop)); + ditem->nameid = mob_db[md->class].dropitem[i].nameid; + ditem->amount = 1; + ditem->m = md->bl.m; + ditem->x = md->bl.x; + ditem->y = md->bl.y; + ditem->first_sd = mvp_sd; + ditem->second_sd = second_sd; + ditem->third_sd = third_sd; + add_timer(tick+500+i,mob_delay_item_drop,(int)ditem,0); + } + if(sd && sd->state.attack_type == BF_WEAPON) { + for(i=0;i<sd->monster_drop_item_count;i++) { + struct delay_item_drop *ditem; + int race = battle_get_race(&md->bl); + if(sd->monster_drop_itemid[i] <= 0) + continue; + if(sd->monster_drop_race[i] & (1<<race) || + (mob_db[md->class].mode & 0x20 && sd->monster_drop_race[i] & 1<<10) || + (!(mob_db[md->class].mode & 0x20) && sd->monster_drop_race[i] & 1<<11) ) { + if(sd->monster_drop_itemrate[i] <= rand()%10000) + continue; + + ditem=(struct delay_item_drop *)aCalloc(1,sizeof(struct delay_item_drop)); + ditem->nameid = sd->monster_drop_itemid[i]; + ditem->amount = 1; + ditem->m = md->bl.m; + ditem->x = md->bl.x; + ditem->y = md->bl.y; + ditem->first_sd = mvp_sd; + ditem->second_sd = second_sd; + ditem->third_sd = third_sd; + add_timer(tick+520+i,mob_delay_item_drop,(int)ditem,0); + } + } + if(sd->get_zeny_num > 0) + pc_getzeny(sd,mob_db[md->class].lv*10 + rand()%(sd->get_zeny_num+1)); + } + if(md->lootitem) { + for(i=0;i<md->lootitem_count;i++) { + struct delay_item_drop2 *ditem; + + ditem=(struct delay_item_drop2 *)aCalloc(1,sizeof(struct delay_item_drop2)); + memcpy(&ditem->item_data,&md->lootitem[i],sizeof(md->lootitem[0])); + ditem->m = md->bl.m; + ditem->x = md->bl.x; + ditem->y = md->bl.y; + ditem->first_sd = mvp_sd; + ditem->second_sd = second_sd; + ditem->third_sd = third_sd; + add_timer(tick+540+i,mob_delay_item_drop2,(int)ditem,0); + } + } + } + + // mvp処理 + if(mvp_sd && mob_db[md->class].mexp > 0 ){ + int j; + int mexp; + temp = ((double)mob_db[md->class].mexp * (double)battle_config.mvp_exp_rate * (9.+(double)count)/1000.); + mexp = (temp > 2147483647.)? 0x7fffffff:(int)temp; + if(mexp < 1) mexp = 1; + clif_mvp_effect(mvp_sd); // エフェクト + clif_mvp_exp(mvp_sd,mexp); + pc_gainexp(mvp_sd,mexp,0); + for(j=0;j<3;j++){ + i = rand() % 3; + if(mob_db[md->class].mvpitem[i].nameid <= 0) + continue; + drop_rate = mob_db[md->class].mvpitem[i].p; + if(drop_rate <= 0 && battle_config.drop_rate0item==1) + drop_rate = 1; + if(drop_rate < battle_config.item_drop_mvp_min) + drop_rate = battle_config.item_drop_mvp_min; + if(drop_rate > battle_config.item_drop_mvp_max) + drop_rate = battle_config.item_drop_mvp_max; + if(drop_rate <= rand()%10000) + continue; + memset(&item,0,sizeof(item)); + item.nameid=mob_db[md->class].mvpitem[i].nameid; + item.identify=!itemdb_isequip3(item.nameid); + clif_mvp_item(mvp_sd,item.nameid); + if(mvp_sd->weight*2 > mvp_sd->max_weight) + map_addflooritem(&item,1,mvp_sd->bl.m,mvp_sd->bl.x,mvp_sd->bl.y,mvp_sd,second_sd,third_sd,1); + else if((ret = pc_additem(mvp_sd,&item,1))) { + clif_additem(sd,0,0,ret); + map_addflooritem(&item,1,mvp_sd->bl.m,mvp_sd->bl.x,mvp_sd->bl.y,mvp_sd,second_sd,third_sd,1); + } + break; + } + } + + } // [MouseJstr] + + // <Agit> NPC Event [OnAgitBreak] + if(md->npc_event[0] && strcmp(((md->npc_event)+strlen(md->npc_event)-13),"::OnAgitBreak") == 0) { + printf("MOB.C: Run NPC_Event[OnAgitBreak].\n"); + if (agit_flag == 1) //Call to Run NPC_Event[OnAgitBreak] + guild_agit_break(md); + } + + // SCRIPT実行 + if(md->npc_event[0]){ +// if(battle_config.battle_log==1) +// printf("mob_damage : run event : %s\n",md->npc_event); + if(src && src->type == BL_PET) + sd = ((struct pet_data *)src)->msd; + if(sd == NULL) { + if(mvp_sd != NULL) + sd = mvp_sd; + else { + struct map_session_data *tmpsd; + int i; + for(i=0;i<fd_max;i++){ + if(session[i] && (tmpsd=session[i]->session_data) && tmpsd->state.auth) { + if(md->bl.m == tmpsd->bl.m) { + sd = tmpsd; + break; + } + } + } + } + } + if(sd) + npc_event(sd,md->npc_event,0); + } + + clif_clearchar_area(&md->bl,1); + map_delblock(&md->bl); + if(mob_get_viewclass(md->class) <= 1000) + clif_clearchar_delay(tick+3000,&md->bl,0); + mob_deleteslave(md); + mob_setdelayspawn(md->bl.id); + map_freeblock_unlock(); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int mob_class_change(struct mob_data *md,int *value) +{ + unsigned int tick = gettick(); + int i,c,hp_rate,max_hp,class,count = 0; + + nullpo_retr(0, md); + nullpo_retr(0, value); + + if(value[0]<=1000 || value[0]>2000) + return 0; + if(md->bl.prev == NULL) return 0; + + while(count < 5 && value[count] > 1000 && value[count] <= 2000) count++; + if(count < 1) return 0; + + class = value[rand()%count]; + if(class<=1000 || class>2000) return 0; + + max_hp = battle_get_max_hp(&md->bl); + hp_rate = md->hp*100/max_hp; + clif_mob_class_change(md,class); + md->class = class; + max_hp = battle_get_max_hp(&md->bl); + if(battle_config.monster_class_change_full_recover==1) { + md->hp = max_hp; + memset(md->dmglog,0,sizeof(md->dmglog)); + } + else + md->hp = max_hp*hp_rate/100; + if(md->hp > max_hp) md->hp = max_hp; + else if(md->hp < 1) md->hp = 1; + + memcpy(md->name,mob_db[class].jname,24); + memset(&md->state,0,sizeof(md->state)); + md->attacked_id = 0; + md->target_id = 0; + md->move_fail_count = 0; + + md->speed = mob_db[md->class].speed; + md->def_ele = mob_db[md->class].element; + + mob_changestate(md,MS_IDLE,0); + skill_castcancel(&md->bl,0); + md->state.skillstate = MSS_IDLE; + md->last_thinktime = tick; + md->next_walktime = tick+rand()%50+5000; + md->attackabletime = tick; + md->canmove_tick = tick; + md->sg_count=0; + + for(i=0,c=tick-1000*3600*10;i<MAX_MOBSKILL;i++) + md->skilldelay[i] = c; + md->skillid=0; + md->skilllv=0; + + if(md->lootitem == NULL && mob_db[class].mode&0x02) + md->lootitem=(struct item *)aCalloc(LOOTITEM_SIZE,sizeof(struct item)); + + skill_clear_unitgroup(&md->bl); + skill_cleartimerskill(&md->bl); + + clif_clearchar_area(&md->bl,0); + clif_spawnmob(md); + + return 0; +} + +/*========================================== + * mob回復 + *------------------------------------------ + */ +int mob_heal(struct mob_data *md,int heal) +{ + int max_hp = battle_get_max_hp(&md->bl); + + nullpo_retr(0, md); + + md->hp += heal; + if( max_hp < md->hp ) + md->hp = max_hp; + + if(md->class >= 1285 && md->class <=1287) { // guardian hp update [Valaris] + struct guild_castle *gc=guild_mapname2gc(map[md->bl.m].name); + if(gc) { + if(md->bl.id==gc->GID0) gc->Ghp0=md->hp; + if(md->bl.id==gc->GID1) gc->Ghp1=md->hp; + if(md->bl.id==gc->GID2) gc->Ghp2=md->hp; + if(md->bl.id==gc->GID3) gc->Ghp3=md->hp; + if(md->bl.id==gc->GID4) gc->Ghp4=md->hp; + if(md->bl.id==gc->GID5) gc->Ghp5=md->hp; + if(md->bl.id==gc->GID6) gc->Ghp6=md->hp; + if(md->bl.id==gc->GID7) gc->Ghp7=md->hp; + } + } // end addition [Valaris] + + return 0; +} + + +/*========================================== + * Added by RoVeRT + *------------------------------------------ + */ +int mob_warpslave_sub(struct block_list *bl,va_list ap) +{ + struct mob_data *md=(struct mob_data *)bl; + int id,x,y; + id=va_arg(ap,int); + x=va_arg(ap,int); + y=va_arg(ap,int); + if( md->master_id==id ) { + mob_warp(md,-1,x,y,2); + } + return 0; +} + +/*========================================== + * Added by RoVeRT + *------------------------------------------ + */ +int mob_warpslave(struct mob_data *md,int x, int y) +{ +//printf("warp slave\n"); + map_foreachinarea(mob_warpslave_sub, md->bl.m, + x-AREA_SIZE,y-AREA_SIZE, + x+AREA_SIZE,y+AREA_SIZE,BL_MOB, + md->bl.id, md->bl.x, md->bl.y ); + return 0; +} + +/*========================================== + * mobワープ + *------------------------------------------ + */ +int mob_warp(struct mob_data *md,int m,int x,int y,int type) +{ + int i=0,c,xs=0,ys=0,bx=x,by=y; + + nullpo_retr(0, md); + + if( md->bl.prev==NULL ) + return 0; + + if( m<0 ) m=md->bl.m; + + if(type >= 0) { + if(map[md->bl.m].flag.monster_noteleport) + return 0; + clif_clearchar_area(&md->bl,type); + } + skill_unit_out_all(&md->bl,gettick(),1); + map_delblock(&md->bl); + + if(bx>0 && by>0){ // 位置指定の場合周囲9セルを探索 + xs=ys=9; + } + + while( ( x<0 || y<0 || ((c=read_gat(m,x,y))==1 || c==5) ) && (i++)<1000 ){ + if( xs>0 && ys>0 && i<250 ){ // 指定位置付近の探索 + x=bx+rand()%xs-xs/2; + y=by+rand()%ys-ys/2; + }else{ // 完全ランダム探索 + x=rand()%(map[m].xs-2)+1; + y=rand()%(map[m].ys-2)+1; + } + } + md->dir=0; + if(i<1000){ + md->bl.x=md->to_x=x; + md->bl.y=md->to_y=y; + md->bl.m=m; + }else { + m=md->bl.m; + if(battle_config.error_log==1) + printf("MOB %d warp failed, class = %d\n",md->bl.id,md->class); + } + + md->target_id=0; // タゲを解除する + md->state.targettype=NONE_ATTACKABLE; + md->attacked_id=0; + md->state.skillstate=MSS_IDLE; + mob_changestate(md,MS_IDLE,0); + + if(type>0 && i==1000) { + if(battle_config.battle_log==1) + printf("MOB %d warp to (%d,%d), class = %d\n",md->bl.id,x,y,md->class); + } + + map_addblock(&md->bl); + if(type>0) { + clif_spawnmob(md); + mob_warpslave(md,md->bl.x,md->bl.y); + } + + return 0; +} + +/*========================================== + * 画面内の取り巻きの数計算用(foreachinarea) + *------------------------------------------ + */ +int mob_countslave_sub(struct block_list *bl,va_list ap) +{ + int id,*c; + struct mob_data *md; + + id=va_arg(ap,int); + + nullpo_retr(0, bl); + nullpo_retr(0, ap); + nullpo_retr(0, c=va_arg(ap,int *)); + nullpo_retr(0, md = (struct mob_data *)bl); + + + if( md->master_id==id ) + (*c)++; + return 0; +} +/*========================================== + * 画面内の取り巻きの数計算 + *------------------------------------------ + */ +int mob_countslave(struct mob_data *md) +{ + int c=0; + + nullpo_retr(0, md); + + map_foreachinarea(mob_countslave_sub, md->bl.m, + 0,0,map[md->bl.m].xs-1,map[md->bl.m].ys-1, + BL_MOB,md->bl.id,&c); + return c; +} +/*========================================== + * 手下MOB召喚 + *------------------------------------------ + */ +int mob_summonslave(struct mob_data *md2,int *value,int amount,int flag) +{ + struct mob_data *md; + int bx,by,m,count = 0,class,k,a = amount; + + nullpo_retr(0, md2); + nullpo_retr(0, value); + + bx=md2->bl.x; + by=md2->bl.y; + m=md2->bl.m; + + if(value[0]<=1000 || value[0]>2000) // 値が異常なら召喚を止める + return 0; + while(count < 5 && value[count] > 1000 && value[count] <= 2000) count++; + if(count < 1) return 0; + + for(k=0;k<count;k++) { + amount = a; + class = value[k]; + if(class<=1000 || class>2000) continue; + for(;amount>0;amount--){ + int x=0,y=0,c=0,i=0; + md=(struct mob_data *)aCalloc(1,sizeof(struct mob_data)); + if(mob_db[class].mode&0x02) + md->lootitem=(struct item *)aCalloc(LOOTITEM_SIZE,sizeof(struct item)); + else + md->lootitem=NULL; + + while((x<=0 || y<=0 || (c=map_getcell(m,x,y))==1 || c==5 ) && (i++)<100){ + x=rand()%9-4+bx; + y=rand()%9-4+by; + } + if(i>=100){ + x=bx; + y=by; + } + + mob_spawn_dataset(md,"--ja--",class); + md->bl.m=m; + md->bl.x=x; + md->bl.y=y; + + md->m =m; + md->x0=x; + md->y0=y; + md->xs=0; + md->ys=0; + md->speed=md2->speed; + md->spawndelay1=-1; // 一度のみフラグ + md->spawndelay2=-1; // 一度のみフラグ + + memset(md->npc_event,0,sizeof(md->npc_event)); + md->bl.type=BL_MOB; + map_addiddb(&md->bl); + mob_spawn(md->bl.id); + clif_skill_nodamage(&md->bl,&md->bl,(flag)? NPC_SUMMONSLAVE:NPC_SUMMONMONSTER,a,1); + + if(flag) + md->master_id=md2->bl.id; + } + } + return 0; +} + +/*========================================== + * 自分をロックしているPCの数を数える(foreachclient) + *------------------------------------------ + */ +static int mob_counttargeted_sub(struct block_list *bl,va_list ap) +{ + int id,*c,target_lv; + struct block_list *src; + + id=va_arg(ap,int); + nullpo_retr(0, bl); + nullpo_retr(0, ap); + nullpo_retr(0, c=va_arg(ap,int *)); + + src=va_arg(ap,struct block_list *); + target_lv=va_arg(ap,int); + if(id == bl->id || (src && id == src->id)) return 0; + if(bl->type == BL_PC) { + struct map_session_data *sd = (struct map_session_data *)bl; + if(sd && sd->attacktarget == id && sd->attacktimer != -1 && sd->attacktarget_lv >= target_lv) + (*c)++; + } + else if(bl->type == BL_MOB) { + struct mob_data *md = (struct mob_data *)bl; + if(md && md->target_id == id && md->timer != -1 && md->state.state == MS_ATTACK && md->target_lv >= target_lv) + (*c)++; + } + else if(bl->type == BL_PET) { + struct pet_data *pd = (struct pet_data *)bl; + if(pd->target_id == id && pd->timer != -1 && pd->state.state == MS_ATTACK && pd->target_lv >= target_lv) + (*c)++; + } + return 0; +} +/*========================================== + * 自分をロックしているPCの数を数える + *------------------------------------------ + */ +int mob_counttargeted(struct mob_data *md,struct block_list *src,int target_lv) +{ + int c=0; + + nullpo_retr(0, md); + + map_foreachinarea(mob_counttargeted_sub, md->bl.m, + md->bl.x-AREA_SIZE,md->bl.y-AREA_SIZE, + md->bl.x+AREA_SIZE,md->bl.y+AREA_SIZE,0,md->bl.id,&c,src,target_lv); + return c; +} + +/*========================================== + *MOBskillから該当skillidのskillidxを返す + *------------------------------------------ + */ +int mob_skillid2skillidx(int class,int skillid) +{ + int i; + struct mob_skill *ms=mob_db[class].skill; + + if(ms==NULL) + return -1; + + for(i=0;i<mob_db[class].maxskill;i++){ + if(ms[i].skill_id == skillid) + return i; + } + return -1; + +} + +// +// MOBスキル +// + +/*========================================== + * スキル使用(詠唱完了、ID指定) + *------------------------------------------ + */ +int mobskill_castend_id( int tid, unsigned int tick, int id,int data ) +{ + struct mob_data* md=NULL; + struct block_list *bl; + struct block_list *mbl; + int range; + + if((mbl = map_id2bl(id)) == NULL ) //詠唱したMobがもういないというのは良くある正常処理 + return 0; + if((md=(struct mob_data *)mbl) == NULL ){ + printf("mobskill_castend_id nullpo mbl->id:%d\n",mbl->id); + return 0; + } + + if( md->bl.type!=BL_MOB || md->bl.prev==NULL ) + return 0; + + if( md->skilltimer != tid ) // タイマIDの確認 + return 0; + + md->skilltimer=-1; + //沈黙や状態異常など + if(md->sc_data){ + if(md->opt1>0 || md->sc_data[SC_DIVINA].timer != -1 || md->sc_data[SC_ROKISWEIL].timer != -1 || md->sc_data[SC_STEELBODY].timer != -1) + return 0; + if(md->sc_data[SC_AUTOCOUNTER].timer != -1 && md->skillid != KN_AUTOCOUNTER) //オートカウンター + return 0; + if(md->sc_data[SC_BLADESTOP].timer != -1) //白刃取り + return 0; + if(md->sc_data[SC_BERSERK].timer != -1) //バーサーク + return 0; + } + if(md->skillid != NPC_EMOTION) + md->last_thinktime=tick + battle_get_adelay(&md->bl); + + if((bl = map_id2bl(md->skilltarget)) == NULL || bl->prev==NULL){ //スキルターゲットが存在しない + //printf("mobskill_castend_id nullpo\n");//ターゲットがいないときはnullpoじゃなくて普通に終了 + return 0; + } + if(md->bl.m != bl->m) + return 0; + + if(md->skillid == PR_LEXAETERNA) { + struct status_change *sc_data = battle_get_sc_data(bl); + if(sc_data && (sc_data[SC_FREEZE].timer != -1 || (sc_data[SC_STONE].timer != -1 && sc_data[SC_STONE].val2 == 0))) + return 0; + } + else if(md->skillid == RG_BACKSTAP) { + int dir = map_calc_dir(&md->bl,bl->x,bl->y),t_dir = battle_get_dir(bl); + int dist = distance(md->bl.x,md->bl.y,bl->x,bl->y); + if(bl->type != BL_SKILL && (dist == 0 || map_check_dir(dir,t_dir))) + return 0; + } + if( ( (skill_get_inf(md->skillid)&1) || (skill_get_inf2(md->skillid)&4) ) && // 彼我敵対関係チェック + battle_check_target(&md->bl,bl, BCT_ENEMY)<=0 ) + return 0; + range = skill_get_range(md->skillid,md->skilllv); + if(range < 0) + range = battle_get_range(&md->bl) - (range + 1); + if(range + battle_config.mob_skill_add_range < distance(md->bl.x,md->bl.y,bl->x,bl->y)) + return 0; + + md->skilldelay[md->skillidx]=tick; + + if(battle_config.mob_skill_log==1) + printf("MOB skill castend skill=%d, class = %d\n",md->skillid,md->class); + mob_stop_walking(md,0); + + switch( skill_get_nk(md->skillid) ) + { + // 攻撃系/吹き飛ばし系 + case 0: case 2: + skill_castend_damage_id(&md->bl,bl,md->skillid,md->skilllv,tick,0); + break; + case 1:// 支援系 + if(!mob_db[md->class].skill[md->skillidx].val[0] && + (md->skillid==AL_HEAL || (md->skillid==ALL_RESURRECTION && bl->type != BL_PC)) && battle_check_undead(battle_get_race(bl),battle_get_elem_type(bl)) ) + skill_castend_damage_id(&md->bl,bl,md->skillid,md->skilllv,tick,0); + else + skill_castend_nodamage_id(&md->bl,bl,md->skillid,md->skilllv,tick,0); + break; + } + + + return 0; +} + +/*========================================== + * スキル使用(詠唱完了、場所指定) + *------------------------------------------ + */ +int mobskill_castend_pos( int tid, unsigned int tick, int id,int data ) +{ + struct mob_data* md=NULL; + struct block_list *bl; + int range,maxcount; + + //mobskill_castend_id同様詠唱したMobが詠唱完了時にもういないというのはありそうなのでnullpoから除外 + if((bl=map_id2bl(id))==NULL) + return 0; + + nullpo_retr(0, md=(struct mob_data *)bl); + + if( md->bl.type!=BL_MOB || md->bl.prev==NULL ) + return 0; + + if( md->skilltimer != tid ) // タイマIDの確認 + return 0; + + md->skilltimer=-1; + if(md->sc_data){ + if(md->opt1>0 || md->sc_data[SC_DIVINA].timer != -1 || md->sc_data[SC_ROKISWEIL].timer != -1 || md->sc_data[SC_STEELBODY].timer != -1) + return 0; + if(md->sc_data[SC_AUTOCOUNTER].timer != -1 && md->skillid != KN_AUTOCOUNTER) //オートカウンター + return 0; + if(md->sc_data[SC_BLADESTOP].timer != -1) //白刃取り + return 0; + if(md->sc_data[SC_BERSERK].timer != -1) //バーサーク + return 0; + } + + if(battle_config.monster_skill_reiteration == 0) { + range = -1; + switch(md->skillid) { + case MG_SAFETYWALL: + case WZ_FIREPILLAR: + case HT_SKIDTRAP: + case HT_LANDMINE: + case HT_ANKLESNARE: + case HT_SHOCKWAVE: + case HT_SANDMAN: + case HT_FLASHER: + case HT_FREEZINGTRAP: + case HT_BLASTMINE: + case HT_CLAYMORETRAP: + case PF_SPIDERWEB: /* スパイダーウェッブ */ + range = 0; + break; + case AL_PNEUMA: + case AL_WARP: + range = 1; + break; + } + if(range >= 0) { + if(skill_check_unit_range(md->bl.m,md->skillx,md->skilly,range,md->skillid) > 0) + return 0; + } + } + if(battle_config.monster_skill_nofootset==1) { + range = -1; + switch(md->skillid) { + case WZ_FIREPILLAR: + case HT_SKIDTRAP: + case HT_LANDMINE: + case HT_ANKLESNARE: + case HT_SHOCKWAVE: + case HT_SANDMAN: + case HT_FLASHER: + case HT_FREEZINGTRAP: + case HT_BLASTMINE: + case HT_CLAYMORETRAP: + case AM_DEMONSTRATION: + case PF_SPIDERWEB: /* スパイダーウェッブ */ + range = 1; + break; + case AL_WARP: + range = 0; + break; + } + if(range >= 0) { + if(skill_check_unit_range2(md->bl.m,md->skillx,md->skilly,range) > 0) + return 0; + } + } + + if(battle_config.monster_land_skill_limit==1) { + maxcount = skill_get_maxcount(md->skillid); + if(maxcount > 0) { + int i,c; + for(i=c=0;i<MAX_MOBSKILLUNITGROUP;i++) { + if(md->skillunit[i].alive_count > 0 && md->skillunit[i].skill_id == md->skillid) + c++; + } + if(c >= maxcount) + return 0; + } + } + + range = skill_get_range(md->skillid,md->skilllv); + if(range < 0) + range = battle_get_range(&md->bl) - (range + 1); + if(range + battle_config.mob_skill_add_range < distance(md->bl.x,md->bl.y,md->skillx,md->skilly)) + return 0; + md->skilldelay[md->skillidx]=tick; + + if(battle_config.mob_skill_log==1) + printf("MOB skill castend skill=%d, class = %d\n",md->skillid,md->class); + mob_stop_walking(md,0); + + skill_castend_pos2(&md->bl,md->skillx,md->skilly,md->skillid,md->skilllv,tick,0); + + return 0; +} + + +/*========================================== + * Skill use (an aria start, ID specification) + *------------------------------------------ + */ +int mobskill_use_id(struct mob_data *md,struct block_list *target,int skill_idx) +{ + int casttime,range; + struct mob_skill *ms; + int skill_id, skill_lv, forcecast = 0; + + nullpo_retr(0, md); + nullpo_retr(0, ms=&mob_db[md->class].skill[skill_idx]); + + if( target==NULL && (target=map_id2bl(md->target_id))==NULL ) + return 0; + + if( target->prev==NULL || md->bl.prev==NULL ) + return 0; + + skill_id=ms->skill_id; + skill_lv=ms->skill_lv; + + // 沈黙や異常 + if(md->sc_data){ + if(md->opt1>0 || md->sc_data[SC_DIVINA].timer != -1 || md->sc_data[SC_ROKISWEIL].timer != -1 || md->sc_data[SC_STEELBODY].timer != -1) + return 0; + if(md->sc_data[SC_AUTOCOUNTER].timer != -1 && md->skillid != KN_AUTOCOUNTER) //オートカウンター + return 0; + if(md->sc_data[SC_BLADESTOP].timer != -1) //白刃取り + return 0; + if(md->sc_data[SC_BERSERK].timer != -1) //バーサーク + return 0; + } + + if(md->option&4 && skill_id==TF_HIDING) + return 0; + if(md->option&2 && skill_id!=TF_HIDING && skill_id!=AS_GRIMTOOTH && skill_id!=RG_BACKSTAP && skill_id!=RG_RAID) + return 0; + + if(map[md->bl.m].flag.gvg && (skill_id == SM_ENDURE || skill_id == AL_TELEPORT || skill_id == AL_WARP || + skill_id == WZ_ICEWALL || skill_id == TF_BACKSLIDING)) + return 0; + + if(skill_get_inf2(skill_id)&0x200 && md->bl.id == target->id) + return 0; + + // 射程と障害物チェック + range = skill_get_range(skill_id,skill_lv); + if(range < 0) + range = battle_get_range(&md->bl) - (range + 1); + if(!battle_check_range(&md->bl,target,range)) + return 0; + +// delay=skill_delayfix(&md->bl, skill_get_delay( skill_id,skill_lv) ); + + casttime=skill_castfix(&md->bl,ms->casttime); + md->state.skillcastcancel=ms->cancel; + md->skilldelay[skill_idx]=gettick(); + + switch(skill_id){ /* 何か特殊な処理が必要 */ + case ALL_RESURRECTION: /* リザレクション */ + if(target->type != BL_PC && battle_check_undead(battle_get_race(target),battle_get_elem_type(target))){ /* 敵がアンデッドなら */ + forcecast=1; /* ターンアンデットと同じ詠唱時間 */ + casttime=skill_castfix(&md->bl, skill_get_cast(PR_TURNUNDEAD,skill_lv) ); + } + break; + case MO_EXTREMITYFIST: /*阿修羅覇鳳拳*/ + case SA_MAGICROD: + case SA_SPELLBREAKER: + forcecast=1; + break; + } + + if(battle_config.mob_skill_log==1) + printf("MOB skill use target_id=%d skill=%d lv=%d cast=%d, class = %d\n",target->id,skill_id,skill_lv,casttime,md->class); + + if(casttime>0 || forcecast){ // 詠唱が必要 +// struct mob_data *md2; + clif_skillcasting( &md->bl, + md->bl.id, target->id, 0,0, skill_id,casttime); + + // 詠唱反応モンスター +/* if( target->type==BL_MOB && mob_db[(md2=(struct mob_data *)target)->class].mode&0x10 && + md2->state.state!=MS_ATTACK){ + md2->target_id=md->bl.id; + md->state.targettype = ATTACKABLE; + md2->min_chase=13; + }*/ + } + + if( casttime<=0 ) // 詠唱の無いものはキャンセルされない + md->state.skillcastcancel=0; + + md->skilltarget = target->id; + md->skillx = 0; + md->skilly = 0; + md->skillid = skill_id; + md->skilllv = skill_lv; + md->skillidx = skill_idx; + + if(!(battle_config.monster_cloak_check_type&2) && md->sc_data[SC_CLOAKING].timer != -1 && md->skillid != AS_CLOAKING) + skill_status_change_end(&md->bl,SC_CLOAKING,-1); + + if( casttime>0 ){ + md->skilltimer = + add_timer( gettick()+casttime, mobskill_castend_id, md->bl.id, 0 ); + }else{ + md->skilltimer = -1; + mobskill_castend_id(md->skilltimer,gettick(),md->bl.id, 0); + } + + return 1; +} +/*========================================== + * スキル使用(場所指定) + *------------------------------------------ + */ +int mobskill_use_pos( struct mob_data *md, + int skill_x, int skill_y, int skill_idx) +{ + int casttime=0,range; + struct mob_skill *ms; + struct block_list bl; + int skill_id, skill_lv; + + nullpo_retr(0, md); + nullpo_retr(0, ms=&mob_db[md->class].skill[skill_idx]); + + if( md->bl.prev==NULL ) + return 0; + + skill_id=ms->skill_id; + skill_lv=ms->skill_lv; + + //沈黙や状態異常など + if(md->sc_data){ + if(md->opt1>0 || md->sc_data[SC_DIVINA].timer != -1 || md->sc_data[SC_ROKISWEIL].timer != -1 || md->sc_data[SC_STEELBODY].timer != -1) + return 0; + if(md->sc_data[SC_AUTOCOUNTER].timer != -1 && md->skillid != KN_AUTOCOUNTER) //オートカウンター + return 0; + if(md->sc_data[SC_BLADESTOP].timer != -1) //白刃取り + return 0; + if(md->sc_data[SC_BERSERK].timer != -1) //バーサーク + return 0; + } + + if(md->option&2) + return 0; + + if(map[md->bl.m].flag.gvg && (skill_id == SM_ENDURE || skill_id == AL_TELEPORT || skill_id == AL_WARP || + skill_id == WZ_ICEWALL || skill_id == TF_BACKSLIDING)) + return 0; + + // 射程と障害物チェック + bl.type = BL_NUL; + bl.m = md->bl.m; + bl.x = skill_x; + bl.y = skill_y; + range = skill_get_range(skill_id,skill_lv); + if(range < 0) + range = battle_get_range(&md->bl) - (range + 1); + if(!battle_check_range(&md->bl,&bl,range)) + return 0; + +// delay=skill_delayfix(&sd->bl, skill_get_delay( skill_id,skill_lv) ); + casttime=skill_castfix(&md->bl,ms->casttime); + md->skilldelay[skill_idx]=gettick(); + md->state.skillcastcancel=ms->cancel; + + if(battle_config.mob_skill_log==1) + printf("MOB skill use target_pos=(%d,%d) skill=%d lv=%d cast=%d, class = %d\n", + skill_x,skill_y,skill_id,skill_lv,casttime,md->class); + + if( casttime>0 ) // A cast time is required. + clif_skillcasting( &md->bl, + md->bl.id, 0, skill_x,skill_y, skill_id,casttime); + + if( casttime<=0 ) // A skill without a cast time wont be cancelled. + md->state.skillcastcancel=0; + + + md->skillx = skill_x; + md->skilly = skill_y; + md->skilltarget = 0; + md->skillid = skill_id; + md->skilllv = skill_lv; + md->skillidx = skill_idx; + if(!(battle_config.monster_cloak_check_type&2) && md->sc_data[SC_CLOAKING].timer != -1) + skill_status_change_end(&md->bl,SC_CLOAKING,-1); + if( casttime>0 ){ + md->skilltimer = + add_timer( gettick()+casttime, mobskill_castend_pos, md->bl.id, 0 ); + }else{ + md->skilltimer = -1; + mobskill_castend_pos(md->skilltimer,gettick(),md->bl.id, 0); + } + + return 1; +} + + +/*========================================== + * Friendly Mob whose HP is decreasing by a nearby MOB is looked for. + *------------------------------------------ + */ +int mob_getfriendhpltmaxrate_sub(struct block_list *bl,va_list ap) +{ + int rate; + struct mob_data **fr, *md, *mmd; + + nullpo_retr(0, bl); + nullpo_retr(0, ap); + nullpo_retr(0, mmd=va_arg(ap,struct mob_data *)); + + md=(struct mob_data *)bl; + + if( mmd->bl.id == bl->id ) + return 0; + rate=va_arg(ap,int); + fr=va_arg(ap,struct mob_data **); + if( md->hp < mob_db[md->class].max_hp*rate/100 ) + (*fr)=md; + return 0; +} +struct mob_data *mob_getfriendhpltmaxrate(struct mob_data *md,int rate) +{ + struct mob_data *fr=NULL; + const int r=8; + + nullpo_retr(NULL, md); + + map_foreachinarea(mob_getfriendhpltmaxrate_sub, md->bl.m, + md->bl.x-r ,md->bl.y-r, md->bl.x+r, md->bl.y+r, + BL_MOB,md,rate,&fr); + return fr; +} +/*========================================== + * What a status state suits by nearby MOB is looked for. + *------------------------------------------ + */ +int mob_getfriendstatus_sub(struct block_list *bl,va_list ap) +{ + int cond1,cond2; + struct mob_data **fr, *md, *mmd; + int flag=0; + + nullpo_retr(0, bl); + nullpo_retr(0, ap); + nullpo_retr(0, md=(struct mob_data *)bl); + nullpo_retr(0, mmd=va_arg(ap,struct mob_data *)); + + if( mmd->bl.id == bl->id ) + return 0; + cond1=va_arg(ap,int); + cond2=va_arg(ap,int); + fr=va_arg(ap,struct mob_data **); + if( cond2==-1 ){ + int j; + for(j=SC_STONE;j<=SC_BLIND && !flag;j++){ + flag=(md->sc_data[j].timer!=-1 ); + } + }else + flag=( md->sc_data[cond2].timer!=-1 ); + if( flag^( cond1==MSC_FRIENDSTATUSOFF ) ) + (*fr)=md; + + return 0; +} +struct mob_data *mob_getfriendstatus(struct mob_data *md,int cond1,int cond2) +{ + struct mob_data *fr=NULL; + const int r=8; + + nullpo_retr(0, md); + + map_foreachinarea(mob_getfriendstatus_sub, md->bl.m, + md->bl.x-r ,md->bl.y-r, md->bl.x+r, md->bl.y+r, + BL_MOB,md,cond1,cond2,&fr); + return fr; +} + +/*========================================== + * Skill use judging + *------------------------------------------ + */ +int mobskill_use(struct mob_data *md,unsigned int tick,int event) +{ + struct mob_skill *ms; +// struct block_list *target=NULL; + int i,max_hp; + + nullpo_retr(0, md); + nullpo_retr(0, ms = mob_db[md->class].skill); + + max_hp = battle_get_max_hp(&md->bl); + + if(battle_config.mob_skill_use == 0 || md->skilltimer != -1) + return 0; + + if(md->state.special_mob_ai) + return 0; + + if(md->sc_data[SC_SELFDESTRUCTION].timer!=-1) //自爆中はスキルを使わない + return 0; + + for(i=0;i<mob_db[md->class].maxskill;i++){ + int c2=ms[i].cond2,flag=0; + struct mob_data *fmd=NULL; + + // ディレイ中 + if( DIFF_TICK(tick,md->skilldelay[i])<ms[i].delay ) + continue; + + // 状態判定 + if( ms[i].state>=0 && ms[i].state!=md->state.skillstate ) + continue; + + // 条件判定 + flag=(event==ms[i].cond1); + if(!flag){ + switch( ms[i].cond1 ){ + case MSC_ALWAYS: + flag=1; break; + case MSC_MYHPLTMAXRATE: // HP< maxhp% + flag=( md->hp < max_hp*c2/100 ); break; + case MSC_MYSTATUSON: // status[num] on + case MSC_MYSTATUSOFF: // status[num] off + if( ms[i].cond2==-1 ){ + int j; + for(j=SC_STONE;j<=SC_BLIND && !flag;j++){ + flag=(md->sc_data[j].timer!=-1 ); + } + }else + flag=( md->sc_data[ms[i].cond2].timer!=-1 ); + flag^=( ms[i].cond1==MSC_MYSTATUSOFF ); break; + case MSC_FRIENDHPLTMAXRATE: // friend HP < maxhp% + flag=(( fmd=mob_getfriendhpltmaxrate(md,ms[i].cond2) )!=NULL ); break; + case MSC_FRIENDSTATUSON: // friend status[num] on + case MSC_FRIENDSTATUSOFF: // friend status[num] off + flag=(( fmd=mob_getfriendstatus(md,ms[i].cond1,ms[i].cond2) )!=NULL ); break; + case MSC_SLAVELT: // slave < num + flag=( mob_countslave(md) < c2 ); break; + case MSC_ATTACKPCGT: // attack pc > num + flag=( mob_counttargeted(md,NULL,0) > c2 ); break; + case MSC_SLAVELE: // slave <= num + flag=( mob_countslave(md) <= c2 ); break; + case MSC_ATTACKPCGE: // attack pc >= num + flag=( mob_counttargeted(md,NULL,0) >= c2 ); break; + case MSC_SKILLUSED: // specificated skill used + flag=( (event&0xffff)==MSC_SKILLUSED && ((event>>16)==c2 || c2==0)); break; + } + } + + // 確率判定 + if( flag && rand()%10000 < ms[i].permillage ){ + + if( skill_get_inf(ms[i].skill_id)&2 ){ + // 場所指定 + struct block_list *bl = NULL; + int x=0,y=0; + if( ms[i].target<=MST_AROUND ){ + bl= ((ms[i].target==MST_TARGET || ms[i].target==MST_AROUND5)? map_id2bl(md->target_id): + (ms[i].target==MST_FRIEND)? &fmd->bl : &md->bl); + if(bl!=NULL){ + x=bl->x; y=bl->y; + } + } + if( x<=0 || y<=0 ) + continue; + // 自分の周囲 + if( ms[i].target>=MST_AROUND1 ){ + int bx=x, by=y, i=0, c, m=bl->m, r=ms[i].target-MST_AROUND1; + do{ + bx=x + rand()%(r*2+3) - r; + by=y + rand()%(r*2+3) - r; + }while( ( bx<=0 || by<=0 || bx>=map[m].xs || by>=map[m].ys || + ((c=read_gat(m,bx,by))==1 || c==5) ) && (i++)<1000); + if(i<1000){ + x=bx; y=by; + } + } + // 相手の周囲 + if( ms[i].target>=MST_AROUND5 ){ + int bx=x, by=y, i=0, c, m=bl->m, r=(ms[i].target-MST_AROUND5)+1; + do{ + bx=x + rand()%(r*2+1) - r; + by=y + rand()%(r*2+1) - r; + }while( ( bx<=0 || by<=0 || bx>=map[m].xs || by>=map[m].ys || + ((c=read_gat(m,bx,by))==1 || c==5) ) && (i++)<1000); + if(i<1000){ + x=bx; y=by; + } + } + if(!mobskill_use_pos(md,x,y,i)) + return 0; + + }else{ + // ID指定 + if( ms[i].target<=MST_FRIEND ){ + struct block_list *bl = NULL; + bl= ((ms[i].target==MST_TARGET)? map_id2bl(md->target_id): + (ms[i].target==MST_FRIEND)? &fmd->bl : &md->bl); + if(bl && !mobskill_use_id(md,bl,i)) + return 0; + } + } + if(ms[i].emotion >= 0) + clif_emotion(&md->bl,ms[i].emotion); + return 1; + } + } + + return 0; +} +/*========================================== + * Skill use event processing + *------------------------------------------ + */ +int mobskill_event(struct mob_data *md,int flag) +{ + nullpo_retr(0, md); + + if(flag==-1 && mobskill_use(md,gettick(),MSC_CASTTARGETED)) + return 1; + if( (flag&BF_SHORT) && mobskill_use(md,gettick(),MSC_CLOSEDATTACKED)) + return 1; + if( (flag&BF_LONG) && mobskill_use(md,gettick(),MSC_LONGRANGEATTACKED)) + return 1; + return 0; +} +/*========================================== + * Mobがエンペリウムなどの場合の判定 + *------------------------------------------ + */ +int mob_gvmobcheck(struct map_session_data *sd, struct block_list *bl) +{ + struct mob_data *md=NULL; + + nullpo_retr(0,sd); + nullpo_retr(0,bl); + + if(bl->type==BL_MOB && (md=(struct mob_data *)bl) && + (md->class == 1288 || md->class == 1287 || md->class == 1286 || md->class == 1285)) + { + struct guild_castle *gc=guild_mapname2gc(map[sd->bl.m].name); + struct guild *g=guild_search(sd->status.guild_id); + + if(g == NULL && md->class == 1288) + return 0;//ギルド未加入ならダメージ無し + else if(gc != NULL && !map[sd->bl.m].flag.gvg) + return 0;//砦内でGvじゃないときはダメージなし + else if(g && gc != NULL && g->guild_id == gc->guild_id) + return 0;//自占領ギルドのエンペならダメージ無し + else if(g && guild_checkskill(g,GD_APPROVAL) <= 0 && md->class == 1288) + return 0;//正規ギルド承認がないとダメージ無し + + } + + return 1; +} +/*========================================== + * スキル用タイマー削除 + *------------------------------------------ + */ +int mobskill_deltimer(struct mob_data *md ) +{ + nullpo_retr(0, md); + + if( md->skilltimer!=-1 ){ + if( skill_get_inf( md->skillid )&2 ) + delete_timer( md->skilltimer, mobskill_castend_pos ); + else + delete_timer( md->skilltimer, mobskill_castend_id ); + md->skilltimer=-1; + } + return 0; +} +// +// 初期化 +// +/*========================================== + * Since un-setting [ mob ] up was used, it is an initial provisional value setup. + *------------------------------------------ + */ +static int mob_makedummymobdb(int class) +{ + int i; + + sprintf(mob_db[class].name,"mob%d",class); + sprintf(mob_db[class].jname,"mob%d",class); + mob_db[class].lv=1; + mob_db[class].max_hp=1000; + mob_db[class].max_sp=1; + mob_db[class].base_exp=2; + mob_db[class].job_exp=1; + mob_db[class].range=1; + mob_db[class].atk1=7; + mob_db[class].atk2=10; + mob_db[class].def=0; + mob_db[class].mdef=0; + mob_db[class].str=1; + mob_db[class].agi=1; + mob_db[class].vit=1; + mob_db[class].int_=1; + mob_db[class].dex=6; + mob_db[class].luk=2; + mob_db[class].range2=10; + mob_db[class].range3=10; + mob_db[class].size=0; + mob_db[class].race=0; + mob_db[class].element=0; + mob_db[class].mode=0; + mob_db[class].speed=300; + mob_db[class].adelay=1000; + mob_db[class].amotion=500; + mob_db[class].dmotion=500; + mob_db[class].dropitem[0].nameid=909; // Jellopy + mob_db[class].dropitem[0].p=1000; + for(i=1;i<8;i++){ + mob_db[class].dropitem[i].nameid=0; + mob_db[class].dropitem[i].p=0; + } + // Item1,Item2 + mob_db[class].mexp=0; + mob_db[class].mexpper=0; + for(i=0;i<3;i++){ + mob_db[class].mvpitem[i].nameid=0; + mob_db[class].mvpitem[i].p=0; + } + for(i=0;i<MAX_RANDOMMONSTER;i++) + mob_db[class].summonper[i]=0; + return 0; +} + +/*========================================== + * db/mob_db.txt reading + *------------------------------------------ + */ +static int mob_readdb(void) +{ + FILE *fp; + char line[1024]; + char *filename[]={ "db/mob_db.txt","db/mob_db2.txt" }; + int i; + + memset(mob_db,0,sizeof(mob_db)); + + for(i=0;i<2;i++){ + + fp=fopen(filename[i],"r"); + if(fp==NULL){ + if(i>0) + continue; + return -1; + } + while(fgets(line,1020,fp)){ + int class,i; + char *str[55],*p,*np; + + if(line[0] == '/' && line[1] == '/') + continue; + + for(i=0,p=line;i<55;i++){ + if((np=strchr(p,','))!=NULL){ + str[i]=p; + *np=0; + p=np+1; + } else + str[i]=p; + } + + class=atoi(str[0]); + if(class<=1000 || class>2000) + continue; + + mob_db[class].view_class=class; + memcpy(mob_db[class].name,str[1],24); + memcpy(mob_db[class].jname,str[2],24); + mob_db[class].lv=atoi(str[3]); + mob_db[class].max_hp=atoi(str[4]); + mob_db[class].max_sp=atoi(str[5]); + + mob_db[class].base_exp=atoi(str[6]); + if(mob_db[class].base_exp < 0) + mob_db[class].base_exp = 0; + else if(mob_db[class].base_exp > 0 && (mob_db[class].base_exp*battle_config.base_exp_rate/100 > 1000000000 || + mob_db[class].base_exp*battle_config.base_exp_rate/100 < 0)) + mob_db[class].base_exp=1000000000; + else + mob_db[class].base_exp*= battle_config.base_exp_rate/100; + + mob_db[class].job_exp=atoi(str[7]); + if(mob_db[class].job_exp < 0) + mob_db[class].job_exp = 0; + else if(mob_db[class].job_exp > 0 && (mob_db[class].job_exp*battle_config.job_exp_rate/100 > 1000000000 || + mob_db[class].job_exp*battle_config.job_exp_rate/100 < 0)) + mob_db[class].job_exp=1000000000; + else + mob_db[class].job_exp*=battle_config.job_exp_rate/100; + + mob_db[class].range=atoi(str[8]); + mob_db[class].atk1=atoi(str[9]); + mob_db[class].atk2=atoi(str[10]); + mob_db[class].def=atoi(str[11]); + mob_db[class].mdef=atoi(str[12]); + mob_db[class].str=atoi(str[13]); + mob_db[class].agi=atoi(str[14]); + mob_db[class].vit=atoi(str[15]); + mob_db[class].int_=atoi(str[16]); + mob_db[class].dex=atoi(str[17]); + mob_db[class].luk=atoi(str[18]); + mob_db[class].range2=atoi(str[19]); + mob_db[class].range3=atoi(str[20]); + mob_db[class].size=atoi(str[21]); + mob_db[class].race=atoi(str[22]); + mob_db[class].element=atoi(str[23]); + mob_db[class].mode=atoi(str[24]); + mob_db[class].speed=atoi(str[25]); + mob_db[class].adelay=atoi(str[26]); + mob_db[class].amotion=atoi(str[27]); + mob_db[class].dmotion=atoi(str[28]); + + for(i=0;i<8;i++){ + int rate = 0,type,ratemin,ratemax; + mob_db[class].dropitem[i].nameid=atoi(str[29+i*2]); + type = itemdb_type(mob_db[class].dropitem[i].nameid); + if (type == 0) { // Added [Valaris] + rate = battle_config.item_rate_heal; + ratemin = battle_config.item_drop_heal_min; + ratemax = battle_config.item_drop_heal_max; + } + else if (type == 2) { + rate = battle_config.item_rate_use; + ratemin = battle_config.item_drop_use_min; + ratemax = battle_config.item_drop_use_max; // End + } + else if (type == 4 || type == 5 || type == 8) { // Changed to include Pet Equip + rate = battle_config.item_rate_equip; + ratemin = battle_config.item_drop_equip_min; + ratemax = battle_config.item_drop_equip_max; + } + else if (type == 6) { + rate = battle_config.item_rate_card; + ratemin = battle_config.item_drop_card_min; + ratemax = battle_config.item_drop_card_max; + } + else { + rate = battle_config.item_rate_common; + ratemin = battle_config.item_drop_common_min; + ratemax = battle_config.item_drop_common_max; + } + rate = (rate / 100) * atoi(str[30+i*2]); + rate = (rate < ratemin)? ratemin: (rate > ratemax)? ratemax: rate; + mob_db[class].dropitem[i].p = rate; + } + // Item1,Item2 + mob_db[class].mexp=atoi(str[45])*battle_config.mvp_exp_rate/100; + mob_db[class].mexpper=atoi(str[46]); + for(i=0;i<3;i++){ + mob_db[class].mvpitem[i].nameid=atoi(str[47+i*2]); + mob_db[class].mvpitem[i].p=atoi(str[48+i*2])*battle_config.mvp_item_rate/100; + } + for(i=0;i<MAX_RANDOMMONSTER;i++) + mob_db[class].summonper[i]=0; + mob_db[class].maxskill=0; + + mob_db[class].sex=0; + mob_db[class].hair=0; + mob_db[class].hair_color=0; + mob_db[class].weapon=0; + mob_db[class].shield=0; + mob_db[class].head_top=0; + mob_db[class].head_mid=0; + mob_db[class].head_buttom=0; + mob_db[class].clothes_color=0; //Add for player monster dye - Valaris + } + fclose(fp); + printf("read %s done\n",filename[i]); + } + return 0; +} + +/*========================================== + * MOB display graphic change data reading + *------------------------------------------ + */ +static int mob_readdb_mobavail(void) +{ + FILE *fp; + char line[1024]; + int ln=0; + int class,j,k; + char *str[20],*p,*np; + + if( (fp=fopen("db/mob_avail.txt","r"))==NULL ){ + printf("can't read db/mob_avail.txt\n"); + return -1; + } + + while(fgets(line,1020,fp)){ + if(line[0]=='/' && line[1]=='/') + continue; + memset(str,0,sizeof(str)); + + for(j=0,p=line;j<12;j++){ + if((np=strchr(p,','))!=NULL){ + str[j]=p; + *np=0; + p=np+1; + } else + str[j]=p; + } + + if(str[0]==NULL) + continue; + + class=atoi(str[0]); + + if(class<=1000 || class>2000) // 値が異常なら処理しない。 + continue; + k=atoi(str[1]); + if(k >= 0) + mob_db[class].view_class=k; + + if((mob_db[class].view_class < 24) || (mob_db[class].view_class > 4000)) { + mob_db[class].sex=atoi(str[2]); + mob_db[class].hair=atoi(str[3]); + mob_db[class].hair_color=atoi(str[4]); + mob_db[class].weapon=atoi(str[5]); + mob_db[class].shield=atoi(str[6]); + mob_db[class].head_top=atoi(str[7]); + mob_db[class].head_mid=atoi(str[8]); + mob_db[class].head_buttom=atoi(str[9]); + mob_db[class].option=atoi(str[10])&~0x46; + mob_db[class].clothes_color=atoi(str[11]); // Monster player dye option - Valaris + } + + else if(atoi(str[2]) > 0) mob_db[class].equip=atoi(str[2]); // mob equipment [Valaris] + + ln++; + } + fclose(fp); + printf("read db/mob_avail.txt done (count=%d)\n",ln); + return 0; +} + +/*========================================== + * Reading of random monster data + *------------------------------------------ + */ +static int mob_read_randommonster(void) +{ + FILE *fp; + char line[1024]; + char *str[10],*p; + int i,j; + + const char* mobfile[] = { + "db/mob_branch.txt", + "db/mob_poring.txt", + "db/mob_boss.txt" }; + + for(i=0;i<MAX_RANDOMMONSTER;i++){ + mob_db[0].summonper[i] = 1002; // 設定し忘れた場合はポリンが出るようにしておく + fp=fopen(mobfile[i],"r"); + if(fp==NULL){ + printf("can't read %s\n",mobfile[i]); + return -1; + } + while(fgets(line,1020,fp)){ + int class,per; + if(line[0] == '/' && line[1] == '/') + continue; + memset(str,0,sizeof(str)); + for(j=0,p=line;j<3 && p;j++){ + str[j]=p; + p=strchr(p,','); + if(p) *p++=0; + } + + if(str[0]==NULL || str[2]==NULL) + continue; + + class = atoi(str[0]); + per=atoi(str[2]); + if((class>1000 && class<=2000) || class==0) + mob_db[class].summonper[i]=per; + } + fclose(fp); + printf("read %s done\n",mobfile[i]); + } + return 0; +} +/*========================================== + * db/mob_skill_db.txt reading + *------------------------------------------ + */ +static int mob_readskilldb(void) +{ + FILE *fp; + char line[1024]; + int i; + + const struct { + char str[32]; + int id; + } cond1[] = { + { "always", MSC_ALWAYS }, + { "myhpltmaxrate", MSC_MYHPLTMAXRATE }, + { "friendhpltmaxrate",MSC_FRIENDHPLTMAXRATE }, + { "mystatuson", MSC_MYSTATUSON }, + { "mystatusoff", MSC_MYSTATUSOFF }, + { "friendstatuson", MSC_FRIENDSTATUSON }, + { "friendstatusoff", MSC_FRIENDSTATUSOFF }, + { "attackpcgt", MSC_ATTACKPCGT }, + { "attackpcge", MSC_ATTACKPCGE }, + { "slavelt", MSC_SLAVELT }, + { "slavele", MSC_SLAVELE }, + { "closedattacked", MSC_CLOSEDATTACKED }, + { "longrangeattacked",MSC_LONGRANGEATTACKED }, + { "skillused", MSC_SKILLUSED }, + { "casttargeted", MSC_CASTTARGETED }, + }, cond2[] ={ + { "anybad", -1 }, + { "stone", SC_STONE }, + { "freeze", SC_FREEZE }, + { "stan", SC_STAN }, + { "sleep", SC_SLEEP }, + { "poison", SC_POISON }, + { "curse", SC_CURSE }, + { "silence", SC_SILENCE }, + { "confusion", SC_CONFUSION }, + { "blind", SC_BLIND }, + { "hiding", SC_HIDING }, + { "sight", SC_SIGHT }, + }, state[] = { + { "any", -1 }, + { "idle", MSS_IDLE }, + { "walk", MSS_WALK }, + { "attack", MSS_ATTACK }, + { "dead", MSS_DEAD }, + { "loot", MSS_LOOT }, + { "chase", MSS_CHASE }, + }, target[] = { + { "target", MST_TARGET }, + { "self", MST_SELF }, + { "friend", MST_FRIEND }, + { "around5", MST_AROUND5 }, + { "around6", MST_AROUND6 }, + { "around7", MST_AROUND7 }, + { "around8", MST_AROUND8 }, + { "around1", MST_AROUND1 }, + { "around2", MST_AROUND2 }, + { "around3", MST_AROUND3 }, + { "around4", MST_AROUND4 }, + { "around", MST_AROUND }, + }; + + int x; + char *filename[]={ "db/mob_skill_db.txt","db/mob_skill_db2.txt" }; + + for(x=0;x<2;x++){ + + fp=fopen(filename[x],"r"); + if(fp==NULL){ + if(x==0) + printf("can't read %s\n",filename[x]); + continue; + } + while(fgets(line,1020,fp)){ + char *sp[20],*p; + int mob_id; + struct mob_skill *ms; + int j=0; + + if(line[0] == '/' && line[1] == '/') + continue; + + memset(sp,0,sizeof(sp)); + for(i=0,p=line;i<18 && p;i++){ + sp[i]=p; + if((p=strchr(p,','))!=NULL) + *p++=0; + } + if( (mob_id=atoi(sp[0]))<=0 ) + continue; + + if( strcmp(sp[1],"clear")==0 ){ + memset(mob_db[mob_id].skill,0,sizeof(mob_db[mob_id].skill)); + mob_db[mob_id].maxskill=0; + continue; + } + + for(i=0;i<MAX_MOBSKILL;i++) + if( (ms=&mob_db[mob_id].skill[i])->skill_id == 0) + break; + if(i==MAX_MOBSKILL){ + printf("mob_skill: readdb: too many skill ! [%s] in %d[%s]\n", + sp[1],mob_id,mob_db[mob_id].jname); + continue; + } + + ms->state=atoi(sp[2]); + for(j=0;j<sizeof(state)/sizeof(state[0]);j++){ + if( strcmp(sp[2],state[j].str)==0) + ms->state=state[j].id; + } + ms->skill_id=atoi(sp[3]); + ms->skill_lv=atoi(sp[4]); + ms->permillage=atoi(sp[5]); + ms->casttime=atoi(sp[6]); + ms->delay=atoi(sp[7]); + ms->cancel=atoi(sp[8]); + if( strcmp(sp[8],"yes")==0 ) ms->cancel=1; + ms->target=atoi(sp[9]); + for(j=0;j<sizeof(target)/sizeof(target[0]);j++){ + if( strcmp(sp[9],target[j].str)==0) + ms->target=target[j].id; + } + ms->cond1=-1; + for(j=0;j<sizeof(cond1)/sizeof(cond1[0]);j++){ + if( strcmp(sp[10],cond1[j].str)==0) + ms->cond1=cond1[j].id; + } + ms->cond2=atoi(sp[11]); + for(j=0;j<sizeof(cond2)/sizeof(cond2[0]);j++){ + if( strcmp(sp[11],cond2[j].str)==0) + ms->cond2=cond2[j].id; + } + ms->val[0]=atoi(sp[12]); + ms->val[1]=atoi(sp[13]); + ms->val[2]=atoi(sp[14]); + ms->val[3]=atoi(sp[15]); + ms->val[4]=atoi(sp[16]); + if(sp[17] != NULL && strlen(sp[17])>2) + ms->emotion=atoi(sp[17]); + else + ms->emotion=-1; + mob_db[mob_id].maxskill=i+1; + } + fclose(fp); + printf("read %s done\n",filename[x]); + } + return 0; +} + +void mob_reload(void) +{ + /* + + <empty monster database> + mob_read(); + + */ + + do_init_mob(); +} + +#ifndef TXT_ONLY +/*========================================== + * SQL reading + *------------------------------------------ + */ +static int mob_read_sqldb(void) +{ + char line[1024]; + int i,class,ln=0; + char *str[55],*p,*np; + + memset(mob_db,0,sizeof(mob_db)); + + sprintf (tmp_sql, "SELECT * FROM `%s`",mob_db_db); + if(mysql_query(&mmysql_handle, tmp_sql) ) { + printf("DB server Error (select %s to Memory)- %s\n",mob_db_db,mysql_error(&mmysql_handle) ); + } + sql_res = mysql_store_result(&mmysql_handle); + if (sql_res) { + while((sql_row = mysql_fetch_row(sql_res))){ + sprintf(line,"%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s", + sql_row[0],sql_row[1],sql_row[2],sql_row[3],sql_row[4], + sql_row[5],sql_row[6],sql_row[7],sql_row[8],sql_row[9], + sql_row[10],sql_row[11],sql_row[12],sql_row[13],sql_row[14], + sql_row[15],sql_row[16],sql_row[17],sql_row[18],sql_row[19], + sql_row[20],sql_row[21],sql_row[22],sql_row[23],sql_row[24], + sql_row[25],sql_row[26],sql_row[27],sql_row[28],sql_row[29], + sql_row[30],sql_row[31],sql_row[32],sql_row[33],sql_row[34], + sql_row[35],sql_row[36],sql_row[37],sql_row[38],sql_row[39], + sql_row[40],sql_row[41],sql_row[42],sql_row[43],sql_row[44], + sql_row[45],sql_row[46],sql_row[47],sql_row[48],sql_row[49], + sql_row[50],sql_row[51],sql_row[52]); + + for(i=0,p=line;i<55;i++){ + if((np=strchr(p,','))!=NULL){ + str[i]=p; + *np=0; + p=np+1; + } else + str[i]=p; + } + + class=atoi(str[0]); + if(class<=1000 || class>2000) + continue; + + ln++; + + mob_db[class].view_class=class; + memcpy(mob_db[class].name,str[1],24); + memcpy(mob_db[class].jname,str[2],24); + mob_db[class].lv=atoi(str[3]); + mob_db[class].max_hp=atoi(str[4]); + mob_db[class].max_sp=atoi(str[5]); + mob_db[class].base_exp=atoi(str[6])* + battle_config.base_exp_rate/100; + if(mob_db[class].base_exp <= 0) + mob_db[class].base_exp = 1; + mob_db[class].job_exp=atoi(str[7])* + battle_config.job_exp_rate/100; + if(mob_db[class].job_exp <= 0) + mob_db[class].job_exp = 1; + mob_db[class].range=atoi(str[8]); + mob_db[class].atk1=atoi(str[9]); + mob_db[class].atk2=atoi(str[10]); + mob_db[class].def=atoi(str[11]); + mob_db[class].mdef=atoi(str[12]); + mob_db[class].str=atoi(str[13]); + mob_db[class].agi=atoi(str[14]); + mob_db[class].vit=atoi(str[15]); + mob_db[class].int_=atoi(str[16]); + mob_db[class].dex=atoi(str[17]); + mob_db[class].luk=atoi(str[18]); + mob_db[class].range2=atoi(str[19]); + mob_db[class].range3=atoi(str[20]); + mob_db[class].size=atoi(str[21]); + mob_db[class].race=atoi(str[22]); + mob_db[class].element=atoi(str[23]); + mob_db[class].mode=atoi(str[24]); + mob_db[class].speed=atoi(str[25]); + mob_db[class].adelay=atoi(str[26]); + mob_db[class].amotion=atoi(str[27]); + mob_db[class].dmotion=atoi(str[28]); + + for(i=0;i<8;i++){ + int rate = 0,type,ratemin,ratemax; + mob_db[class].dropitem[i].nameid=atoi(str[29+i*2]); + type = itemdb_type(mob_db[class].dropitem[i].nameid); + if (type == 0) { // Added by Valaris + rate = battle_config.item_rate_heal; + ratemin = battle_config.item_drop_heal_min; + ratemax = battle_config.item_drop_heal_max; + } + else if (type == 2) { + rate = battle_config.item_rate_use; + ratemin = battle_config.item_drop_use_min; + ratemax = battle_config.item_drop_use_max; // End + } + else if (type == 4 || type == 5 || type == 8) { // Changed to include Pet Equip + rate = battle_config.item_rate_equip; + ratemin = battle_config.item_drop_equip_min; + ratemax = battle_config.item_drop_equip_max; + } + else if (type == 6) { + rate = battle_config.item_rate_card; + ratemin = battle_config.item_drop_card_min; + ratemax = battle_config.item_drop_card_max; + } + else { + rate = battle_config.item_rate_common; + ratemin = battle_config.item_drop_common_min; + ratemax = battle_config.item_drop_common_max; + } + rate = (rate / 100) * atoi(str[30+i*2]); + rate = (rate < ratemin)? ratemin: (rate > ratemax)? ratemax: rate; + mob_db[class].dropitem[i].p = rate; + } + + mob_db[class].mexp=atoi(str[45])*battle_config.mvp_exp_rate/100; + mob_db[class].mexpper=atoi(str[46]); + for(i=0;i<3;i++){ + mob_db[class].mvpitem[i].nameid=atoi(str[47+i*2]); + mob_db[class].mvpitem[i].p=atoi(str[48+i*2])*battle_config.mvp_item_rate/100; + } + for(i=0;i<MAX_RANDOMMONSTER;i++) + mob_db[class].summonper[i]=0; + mob_db[class].maxskill=0; + + mob_db[class].sex=0; + mob_db[class].hair=0; + mob_db[class].hair_color=0; + mob_db[class].weapon=0; + mob_db[class].shield=0; + mob_db[class].head_top=0; + mob_db[class].head_mid=0; + mob_db[class].head_buttom=0; + } + mysql_free_result(sql_res); + printf("read %s done (count=%d)\n",mob_db_db,ln); + } + return 0; +} + +#endif /* not TXT_ONLY */ +/*========================================== + * Circumference initialization of mob + *------------------------------------------ + */ +int do_init_mob(void) +{ +#ifndef TXT_ONLY + if(db_use_sqldbs) + mob_read_sqldb(); + else +#endif /* TXT_ONLY */ + mob_readdb(); + + mob_readdb_mobavail(); + mob_read_randommonster(); + mob_readskilldb(); + + add_timer_func_list(mob_timer,"mob_timer"); + add_timer_func_list(mob_delayspawn,"mob_delayspawn"); + add_timer_func_list(mob_delay_item_drop,"mob_delay_item_drop"); + add_timer_func_list(mob_delay_item_drop2,"mob_delay_item_drop2"); + add_timer_func_list(mob_ai_hard,"mob_ai_hard"); + add_timer_func_list(mob_ai_lazy,"mob_ai_lazy"); + add_timer_func_list(mobskill_castend_id,"mobskill_castend_id"); + add_timer_func_list(mobskill_castend_pos,"mobskill_castend_pos"); + add_timer_func_list(mob_timer_delete,"mob_timer_delete"); + add_timer_interval(gettick()+MIN_MOBTHINKTIME,mob_ai_hard,0,0,MIN_MOBTHINKTIME); + add_timer_interval(gettick()+MIN_MOBTHINKTIME*10,mob_ai_lazy,0,0,MIN_MOBTHINKTIME*10); + + return 0; +} diff --git a/src/map/mob.h b/src/map/mob.h new file mode 100644 index 0000000..7c1467b --- /dev/null +++ b/src/map/mob.h @@ -0,0 +1,137 @@ +// $Id: mob.h,v 1.4 2004/09/25 05:32:18 MouseJstr Exp $ +#ifndef _MOB_H_ +#define _MOB_H_ + +#define MAX_RANDOMMONSTER 3 + +struct mob_skill { + short state; + short skill_id,skill_lv; + short permillage; + int casttime,delay; + short cancel; + short cond1,cond2; + short target; + int val[5]; + short emotion; +}; + +struct mob_db { + char name[24],jname[24]; + int lv; + int max_hp,max_sp; + int base_exp,job_exp; + int atk1,atk2; + int def,mdef; + int str,agi,vit,int_,dex,luk; + int range,range2,range3; + int size,race,element,mode; + int speed,adelay,amotion,dmotion; + int mexp,mexpper; + struct { int nameid,p; } dropitem[8]; + struct { int nameid,p; } mvpitem[3]; + int view_class,sex; + short hair,hair_color,weapon,shield,head_top,head_mid,head_buttom,option,clothes_color; // [Valaris] + int equip; // [Valaris] + int summonper[MAX_RANDOMMONSTER]; + int maxskill; + struct mob_skill skill[MAX_MOBSKILL]; +}; +extern struct mob_db mob_db[]; + +enum { + MST_TARGET = 0, + MST_SELF = 1, + MST_FRIEND = 2, + MST_AROUND5 = 3, + MST_AROUND6 = 4, + MST_AROUND7 = 5, + MST_AROUND8 = 6, + MST_AROUND1 = 7, + MST_AROUND2 = 8, + MST_AROUND3 = 9, + MST_AROUND4 = 10, + MST_AROUND = MST_AROUND4, + + MSC_ALWAYS = 0x0000, + MSC_MYHPLTMAXRATE = 0x0001, + MSC_FRIENDHPLTMAXRATE= 0x0010, + MSC_MYSTATUSON = 0x0020, + MSC_MYSTATUSOFF = 0x0021, + MSC_FRIENDSTATUSON = 0x0030, + MSC_FRIENDSTATUSOFF = 0x0031, + + MSC_ATTACKPCGT = 0x0100, + MSC_ATTACKPCGE = 0x0101, + MSC_SLAVELT = 0x0110, + MSC_SLAVELE = 0x0111, + MSC_CLOSEDATTACKED = 0x1000, + MSC_LONGRANGEATTACKED= 0x1001, + MSC_SKILLUSED = 0x1010, + MSC_CASTTARGETED = 0x1011, +}; + +enum { + MSS_IDLE, // 待機 + MSS_WALK, // 移動 + MSS_ATTACK, // 攻撃 + MSS_DEAD, // 死亡 + MSS_LOOT, // ルート + MSS_CHASE, // 突撃 +}; + +int mobdb_searchname(const char *str); +int mobdb_checkid(const int id); +int mob_once_spawn(struct map_session_data *sd,char *mapname, + int x,int y,const char *mobname,int class,int amount,const char *event); +int mob_once_spawn_area(struct map_session_data *sd,char *mapname, + int x0,int y0,int x1,int y1, + const char *mobname,int class,int amount,const char *event); + +int mob_spawn_guardian(struct map_session_data *sd,char *mapname, // Spawning Guardians [Valaris] + int x,int y,const char *mobname,int class,int amount,const char *event,int guardian); // Spawning Guardians [Valaris] + + +int mob_walktoxy(struct mob_data *md,int x,int y,int easy); + +int mob_target(struct mob_data *md,struct block_list *bl,int dist); +int mob_stop_walking(struct mob_data *md,int type); +int mob_stopattack(struct mob_data *); +int mob_spawn(int); +int mob_damage(struct block_list *,struct mob_data*,int,int); +int mob_changestate(struct mob_data *md,int state,int type); +int mob_heal(struct mob_data*,int); +int mob_get_viewclass(int); +int mob_get_sex(int); +short mob_get_hair(int); +short mob_get_hair_color(int); +short mob_get_weapon(int); +short mob_get_shield(int); +short mob_get_head_top(int); +short mob_get_head_mid(int); +short mob_get_head_buttom(int); +short mob_get_clothes_color(int); //player mob dye [Valaris] +int mob_get_equip(int); // mob equip [Valaris] +int do_init_mob(void); + +int mob_delete(struct mob_data *md); +int mob_catch_delete(struct mob_data *md,int type); +int mob_timer_delete(int tid, unsigned int tick, int id, int data); + +int mob_deleteslave(struct mob_data *md); + +int mob_counttargeted(struct mob_data *md,struct block_list *src,int target_lv); + +int mob_class_change(struct mob_data *md,int *value); +int mob_warp(struct mob_data *md,int m,int x,int y,int type); + +int mobskill_use(struct mob_data *md,unsigned int tick,int event); +int mobskill_event(struct mob_data *md,int flag); +int mobskill_castend_id( int tid, unsigned int tick, int id,int data ); +int mobskill_castend_pos( int tid, unsigned int tick, int id,int data ); +int mob_summonslave(struct mob_data *md2,int *value,int amount,int flag); + +int mob_gvmobcheck(struct map_session_data *sd, struct block_list *bl); +void mob_reload(void); + +#endif diff --git a/src/map/npc.c b/src/map/npc.c new file mode 100644 index 0000000..05a5dc4 --- /dev/null +++ b/src/map/npc.c @@ -0,0 +1,2035 @@ +// $Id: npc.c,v 1.5 2004/09/25 05:32:18 MouseJstr Exp $ +#include <stdio.h> +#include <stdlib.h> +#include <ctype.h> +#include <string.h> +#include <math.h> +#include <time.h> + +#include "db.h" +#include "timer.h" +#include "nullpo.h" +#include "malloc.h" +#include "map.h" +#include "npc.h" +#include "clif.h" +#include "intif.h" +#include "pc.h" +#include "itemdb.h" +#include "script.h" +#include "mob.h" +#include "pet.h" +#include "battle.h" +#include "skill.h" + +#ifdef MEMWATCH +#include "memwatch.h" +#endif + + + +struct npc_src_list { + struct npc_src_list * next; + struct npc_src_list * prev; + char name[4]; +} ; + +static struct npc_src_list *npc_src_first,*npc_src_last; +static int npc_id=START_NPC_NUM; +static int npc_warp,npc_shop,npc_script,npc_mob; + +int npc_get_new_npc_id(void){ return npc_id++; } + +static struct dbt *ev_db; +static struct dbt *npcname_db; + +struct event_data { + struct npc_data *nd; + int pos; +}; +static struct tm ev_tm_b; // 時計イベント用 + + +/*========================================== + * NPCの無効化/有効化 + * npc_enable + * npc_enable_sub 有効時にOnTouchイベントを実行 + *------------------------------------------ + */ +int npc_enable_sub( struct block_list *bl, va_list ap ) +{ + struct map_session_data *sd; + struct npc_data *nd; + char *name=(char *)aCalloc(50,sizeof(char)); + + nullpo_retr(0, bl); + nullpo_retr(0, ap); + nullpo_retr(0, nd=va_arg(ap,struct npc_data *)); + if(bl->type == BL_PC && (sd=(struct map_session_data *)bl)){ + + if (nd->flag&1) // 無効化されている + return 1; + + memcpy(name,nd->name,50); + if(sd->areanpc_id==nd->bl.id) + return 1; + sd->areanpc_id=nd->bl.id; + npc_event(sd,strcat(name,"::OnTouch"),0); + } + free(name); + return 0; +} +int npc_enable(const char *name,int flag) +{ + struct npc_data *nd=strdb_search(npcname_db,name); + if (nd==NULL) + return 0; + + if (flag&1) { // 有効化 + nd->flag&=~1; + clif_spawnnpc(nd); + }else if (flag&2){ + nd->flag&=~1; + nd->option = 0x0000; + clif_changeoption(&nd->bl); + }else if (flag&4){ + nd->flag|=1; + nd->option = 0x0002; + clif_changeoption(&nd->bl); + }else{ // 無効化 + nd->flag|=1; + clif_clearchar(&nd->bl,0); + } + if(flag&3 && (nd->u.scr.xs > 0 || nd->u.scr.ys >0)) + map_foreachinarea( npc_enable_sub,nd->bl.m,nd->bl.x-nd->u.scr.xs,nd->bl.y-nd->u.scr.ys,nd->bl.x+nd->u.scr.xs,nd->bl.y+nd->u.scr.ys,BL_PC,nd); + + return 0; +} + +/*========================================== + * NPCを名前で探す + *------------------------------------------ + */ +struct npc_data* npc_name2id(const char *name) +{ + return strdb_search(npcname_db,name); +} +/*========================================== + * イベントキューのイベント処理 + *------------------------------------------ + */ +int npc_event_dequeue(struct map_session_data *sd) +{ + nullpo_retr(0, sd); + + sd->npc_id=0; + if (sd->eventqueue[0][0]) { // キューのイベント処理 + char *name=(char *)aCalloc(50,sizeof(char)); + int i; + + memcpy(name,sd->eventqueue[0],50); + for(i=MAX_EVENTQUEUE-2;i>=0;i--) + memcpy(sd->eventqueue[i],sd->eventqueue[i+1],50); + add_timer(gettick()+100,npc_event_timer,sd->bl.id,(int)name); + } + return 0; +} + +int npc_delete(struct npc_data *nd) +{ + nullpo_retr(1, nd); + + if(nd->bl.prev == NULL) + return 1; + + clif_clearchar_area(&nd->bl,1); + map_delblock(&nd->bl); + return 0; +} + +/*========================================== + * イベントの遅延実行 + *------------------------------------------ + */ +int npc_event_timer(int tid,unsigned int tick,int id,int data) +{ + struct map_session_data *sd=map_id2sd(id); + if (sd==NULL) + return 0; + + npc_event(sd,(const char *)data,0); + free((void*)data); + return 0; +} + +int npc_timer_event(const char *eventname) // Added by RoVeRT +{ + struct event_data *ev=strdb_search(ev_db,eventname); + struct npc_data *nd; +// int xs,ys; + + if((ev==NULL || (nd=ev->nd)==NULL)){ + printf("npc_event: event not found [%s]\n",eventname); + return 0; + } + + run_script(nd->u.scr.script,ev->pos,nd->bl.id,nd->bl.id); + + return 0; +} +/* +int npc_timer_sub_sub(void *key,void *data,va_list ap) // Added by RoVeRT +{ + char *p=(char *)key; + struct event_data *ev=(struct event_data *)data; + int *c=va_arg(ap,int *); + int tick=0,ctick=gettick(); + char temp[10]; + char event[100]; + + if(ev->nd->bl.id==(int)*c && (p=strchr(p,':')) && p && strncasecmp("::OnTimer",p,8)==0 ){ + sscanf(&p[9],"%s",temp); + tick=atoi(temp); + + strcpy( event, ev->nd->name); + strcat( event, p); + + if (ctick >= ev->nd->lastaction && ctick - ev->nd->timer >= tick) { + npc_timer_event(event); + ev->nd->lastaction = ctick; + } + } + return 0; +} + +int npc_timer_sub(void *key,void *data,va_list ap) // Added by RoVeRT +{ + struct npc_data *nd=(struct npc_data*)data; + + if(nd->timer == -1) + return 0; + + strdb_foreach(ev_db,npc_timer_sub_sub,&nd->bl.id); + + return 0; +} + +int npc_timer(int tid,unsigned int tick,int id,int data) // Added by RoVeRT +{ + strdb_foreach(npcname_db,npc_timer_sub); + + free((void*)data); + return 0; +}*/ +/*========================================== + * イベント用ラベルのエクスポート + * npc_parse_script->strdb_foreachから呼ばれる + *------------------------------------------ + */ +int npc_event_export(void *key,void *data,va_list ap) +{ + char *lname=(char *)key; + int pos=(int)data; + struct npc_data *nd=va_arg(ap,struct npc_data *); + + if ((lname[0]=='O' || lname[0]=='o')&&(lname[1]=='N' || lname[1]=='n')) { + struct event_data *ev; + char *buf; + char *p=strchr(lname,':'); + // エクスポートされる + ev=calloc(sizeof(struct event_data), 1); + buf=calloc(50, 1); + if (ev==NULL || buf==NULL) { + printf("npc_event_export: out of memory !\n"); + exit(1); + }else if (p==NULL || (p-lname)>24) { + printf("npc_event_export: label name error !\n"); + exit(1); + }else{ + ev->nd=nd; + ev->pos=pos; + *p='\0'; + sprintf(buf,"%s::%s",nd->exname,lname); + *p=':'; + strdb_insert(ev_db,buf,ev); +// if (battle_config.etc_log) +// printf("npc_event_export: export [%s]\n",buf); + } + } + return 0; +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +/*========================================== + * 全てのNPCのOn*イベント実行 + *------------------------------------------ + */ +int npc_event_doall_sub(void *key,void *data,va_list ap) +{ + char *p=(char *)key; + struct event_data *ev; + int *c; + const char *name; + + nullpo_retr(0, ev=(struct event_data *)data); + nullpo_retr(0, ap); + nullpo_retr(0, c=va_arg(ap,int *)); + + name=va_arg(ap,const char *); + + if( (p=strchr(p,':')) && p && strcasecmp(name,p)==0 ){ + run_script(ev->nd->u.scr.script,ev->pos,0,ev->nd->bl.id); + (*c)++; + } + + return 0; +} +int npc_event_doall(const char *name) +{ + int c=0; + char buf[64]="::"; + + strncpy(buf+2,name,62); + strdb_foreach(ev_db,npc_event_doall_sub,&c,buf); + return c; +} + +int npc_event_do_sub(void *key,void *data,va_list ap) +{ + char *p=(char *)key; + struct event_data *ev; + int *c; + const char *name; + + nullpo_retr(0, ev=(struct event_data *)data); + nullpo_retr(0, ap); + nullpo_retr(0, c=va_arg(ap,int *)); + + name=va_arg(ap,const char *); + + if (p && strcasecmp(name,p)==0 ) { + run_script(ev->nd->u.scr.script,ev->pos,0,ev->nd->bl.id); + (*c)++; + } + + return 0; +} +int npc_event_do(const char *name) +{ + int c=0; + + if (*name==':' && name[1]==':') { + return npc_event_doall(name+2); + } + + strdb_foreach(ev_db,npc_event_do_sub,&c,name); + return c; +} + +/*========================================== + * 時計イベント実行 + *------------------------------------------ + */ +int npc_event_do_clock(int tid,unsigned int tick,int id,int data) +{ + time_t timer; + struct tm *t; + char buf[64]; + int c=0; + + time(&timer); + t=localtime(&timer); + + if (t->tm_min != ev_tm_b.tm_min ) { + sprintf(buf,"OnMinute%02d",t->tm_min); + c+=npc_event_doall(buf); + sprintf(buf,"OnClock%02d%02d",t->tm_hour,t->tm_min); + c+=npc_event_doall(buf); + } + if (t->tm_hour!= ev_tm_b.tm_hour) { + sprintf(buf,"OnHour%02d",t->tm_hour); + c+=npc_event_doall(buf); + } + if (t->tm_mday!= ev_tm_b.tm_mday) { + sprintf(buf,"OnDay%02d%02d",t->tm_mon+1,t->tm_mday); + c+=npc_event_doall(buf); + } + memcpy(&ev_tm_b,t,sizeof(ev_tm_b)); + return c; +} +/*========================================== + * OnInitイベント実行(&時計イベント開始) + *------------------------------------------ + */ +int npc_event_do_oninit(void) +{ + int c = npc_event_doall("OnInit"); + printf("npc: OnInit Event done. (%d npc)\n",c); + + add_timer_interval(gettick()+100, + npc_event_do_clock,0,0,1000); + + return 0; +} +/*========================================== + * OnTimer NPC event - by RoVeRT + *------------------------------------------ + */ +int npc_addeventtimer(struct npc_data *nd,int tick,const char *name) +{ + int i; + for(i=0;i<MAX_EVENTTIMER;i++) + if( nd->eventtimer[i]==-1 ) + break; + if(i<MAX_EVENTTIMER){ + char *evname=malloc(24); + if(evname==NULL){ + printf("npc_addeventtimer: out of memory !\n");exit(1); + } + memcpy(evname,name,24); + nd->eventtimer[i]=add_timer(gettick()+tick, + npc_event_timer,nd->bl.id,(int)evname); + }else + printf("npc_addtimer: event timer is full !\n"); + + return 0; +} + +int npc_deleventtimer(struct npc_data *nd,const char *name) +{ + int i; + for(i=0;i<MAX_EVENTTIMER;i++) + if( nd->eventtimer[i]!=-1 && strcmp( + (char *)(get_timer(nd->eventtimer[i])->data), name)==0 ){ + delete_timer(nd->eventtimer[i],npc_event_timer); + nd->eventtimer[i]=-1; + break; + } + + return 0; +} + +int npc_cleareventtimer(struct npc_data *nd) +{ + int i; + for(i=0;i<MAX_EVENTTIMER;i++) + if( nd->eventtimer[i]!=-1 ){ + delete_timer(nd->eventtimer[i],npc_event_timer); + nd->eventtimer[i]=-1; + } + + return 0; +} + +int npc_do_ontimer_sub(void *key,void *data,va_list ap) +{ + char *p=(char *)key; + struct event_data *ev=(struct event_data *)data; + int *c=va_arg(ap,int *); +// struct map_session_data *sd=va_arg(ap,struct map_session_data *); + int option=va_arg(ap,int); + int tick=0; + char temp[10]; + char event[50]; + + if(ev->nd->bl.id==(int)*c && (p=strchr(p,':')) && p && strncasecmp("::OnTimer",p,8)==0 ){ + sscanf(&p[9],"%s",temp); + tick=atoi(temp); + + strcpy( event, ev->nd->name); + strcat( event, p); + + if (option!=0) { + npc_addeventtimer(ev->nd,tick,event); + } else { + npc_deleventtimer(ev->nd,event); + } + } + return 0; +} +int npc_do_ontimer(int npc_id, struct map_session_data *sd, int option) +{ + strdb_foreach(ev_db,npc_do_ontimer_sub,&npc_id,sd,option); + return 0; +} +/*========================================== + * タイマーイベント用ラベルの取り込み + * npc_parse_script->strdb_foreachから呼ばれる + *------------------------------------------ + */ +int npc_timerevent_import(void *key,void *data,va_list ap) +{ + char *lname=(char *)key; + int pos=(int)data; + struct npc_data *nd=va_arg(ap,struct npc_data *); + int t=0,i=0; + + if(sscanf(lname,"OnTimer%d%n",&t,&i)==1 && lname[i]==':') { + // タイマーイベント + struct npc_timerevent_list *te=nd->u.scr.timer_event; + int j,i=nd->u.scr.timeramount; + if(te==NULL) te=malloc(sizeof(struct npc_timerevent_list)); + else te=realloc( te, sizeof(struct npc_timerevent_list) * (i+1) ); + if(te==NULL){ + printf("npc_timerevent_import: out of memory !\n"); + exit(1); + } + for(j=0;j<i;j++){ + if(te[j].timer>t){ + memmove(te+j+1,te+j,sizeof(struct npc_timerevent_list)*(i-j)); + break; + } + } + te[j].timer=t; + te[j].pos=pos; + nd->u.scr.timer_event=te; + nd->u.scr.timeramount=i+1; + } + return 0; +} +/*========================================== + * タイマーイベント実行 + *------------------------------------------ + */ +int npc_timerevent(int tid,unsigned int tick,int id,int data) +{ + int next,t; + struct npc_data* nd=(struct npc_data *)map_id2bl(id); + struct npc_timerevent_list *te; + if( nd==NULL || nd->u.scr.nexttimer<0 ){ + printf("npc_timerevent: ??\n"); + return 0; + } + nd->u.scr.timertick=tick; + te=nd->u.scr.timer_event+ nd->u.scr.nexttimer; + nd->u.scr.timerid = -1; + + t = nd->u.scr.timer+=data; + nd->u.scr.nexttimer++; + if( nd->u.scr.timeramount>nd->u.scr.nexttimer ){ + next= nd->u.scr.timer_event[ nd->u.scr.nexttimer ].timer - t; + nd->u.scr.timerid = add_timer(tick+next,npc_timerevent,id,next); + } + + run_script(nd->u.scr.script,te->pos,0,nd->bl.id); + return 0; +} +/*========================================== + * タイマーイベント開始 + *------------------------------------------ + */ +int npc_timerevent_start(struct npc_data *nd) +{ + int j,n, next; + + nullpo_retr(0, nd); + + n=nd->u.scr.timeramount; + if( nd->u.scr.nexttimer>=0 || n==0 ) + return 0; + + for(j=0;j<n;j++){ + if( nd->u.scr.timer_event[j].timer > nd->u.scr.timer ) + break; + } + nd->u.scr.nexttimer=j; + nd->u.scr.timertick=gettick(); + + if(j>=n) + return 0; + + next = nd->u.scr.timer_event[j].timer - nd->u.scr.timer; + nd->u.scr.timerid = add_timer(gettick()+next,npc_timerevent,nd->bl.id,next); + return 0; +} +/*========================================== + * タイマーイベント終了 + *------------------------------------------ + */ +int npc_timerevent_stop(struct npc_data *nd) +{ + nullpo_retr(0, nd); + + if( nd->u.scr.nexttimer>=0 ){ + nd->u.scr.nexttimer = -1; + nd->u.scr.timer += (int)(gettick() - nd->u.scr.timertick); + if(nd->u.scr.timerid!=-1) + delete_timer(nd->u.scr.timerid,npc_timerevent); + nd->u.scr.timerid = -1; + } + return 0; +} +/*========================================== + * タイマー値の所得 + *------------------------------------------ + */ +int npc_gettimerevent_tick(struct npc_data *nd) +{ + int tick; + + nullpo_retr(0, nd); + + tick=nd->u.scr.timer; + + if( nd->u.scr.nexttimer>=0 ) + tick += (int)(gettick() - nd->u.scr.timertick); + return tick; +} +/*========================================== + * タイマー値の設定 + *------------------------------------------ + */ +int npc_settimerevent_tick(struct npc_data *nd,int newtimer) +{ + int flag; + + nullpo_retr(0, nd); + + flag= nd->u.scr.nexttimer; + + npc_timerevent_stop(nd); + nd->u.scr.timer=newtimer; + if(flag>=0) + npc_timerevent_start(nd); + return 0; +} + +/*========================================== + * イベント型のNPC処理 + *------------------------------------------ + */ +int npc_event(struct map_session_data *sd,const char *eventname,int mob_kill) +{ + struct event_data *ev=strdb_search(ev_db,eventname); + struct npc_data *nd; + int xs,ys; + char mobevent[100]; + + if( sd == NULL ){ + printf("npc_event nullpo?\n"); + } + + if(ev==NULL && eventname && strcmp(((eventname)+strlen(eventname)-9),"::OnTouch") == 0) + return 1; + + if(ev==NULL || (nd=ev->nd)==NULL){ + if(mob_kill && (ev==NULL || (nd=ev->nd)==NULL)){ + strcpy( mobevent, eventname); + strcat( mobevent, "::OnMyMobDead"); + ev=strdb_search(ev_db,mobevent); + if (ev==NULL || (nd=ev->nd)==NULL) { + if (strncasecmp(eventname,"GM_MONSTER",10)!=0) + printf("npc_event: event not found [%s]\n",mobevent); + return 0; + } + } + else { + if(battle_config.error_log) + printf("npc_event: event not found [%s]\n",eventname); + return 0; + } + } + + xs=nd->u.scr.xs; + ys=nd->u.scr.ys; + if (xs>=0 && ys>=0 ) { + if (nd->bl.m != sd->bl.m ) + return 1; + if ( xs>0 && (sd->bl.x<nd->bl.x-xs/2 || nd->bl.x+xs/2<sd->bl.x) ) + return 1; + if ( ys>0 && (sd->bl.y<nd->bl.y-ys/2 || nd->bl.y+ys/2<sd->bl.y) ) + return 1; + } + + if ( sd->npc_id!=0) { +// if (battle_config.error_log) +// printf("npc_event: npc_id != 0\n"); + int i; + for(i=0;i<MAX_EVENTQUEUE;i++) + if (!sd->eventqueue[i][0]) + break; + if (i==MAX_EVENTQUEUE) { + if (battle_config.error_log) + printf("npc_event: event queue is full !\n"); + }else{ +// if (battle_config.etc_log) +// printf("npc_event: enqueue\n"); + memcpy(sd->eventqueue[i],eventname,50); + } + return 1; + } + if (nd->flag&1) { // 無効化されている + npc_event_dequeue(sd); + return 0; + } + + sd->npc_id=nd->bl.id; + sd->npc_pos=run_script(nd->u.scr.script,ev->pos,sd->bl.id,nd->bl.id); + return 0; +} + + +int npc_command_sub(void *key,void *data,va_list ap) +{ + char *p=(char *)key; + struct event_data *ev=(struct event_data *)data; + char *npcname=va_arg(ap,char *); + char *command=va_arg(ap,char *); + char temp[100]; + + if(strcmp(ev->nd->name,npcname)==0 && (p=strchr(p,':')) && p && strncasecmp("::OnCommand",p,10)==0 ){ + sscanf(&p[11],"%s",temp); + + if (strcmp(command,temp)==0) + run_script(ev->nd->u.scr.script,ev->pos,0,ev->nd->bl.id); + } + + return 0; +} + +int npc_command(struct map_session_data *sd,char *npcname,char *command) +{ + strdb_foreach(ev_db,npc_command_sub,npcname,command); + + return 0; +} +/*========================================== + * 接触型のNPC処理 + *------------------------------------------ + */ +int npc_touch_areanpc(struct map_session_data *sd,int m,int x,int y) +{ + int i,f=1; + int xs,ys; + + nullpo_retr(1, sd); + + if(sd->npc_id) + return 1; + + for(i=0;i<map[m].npc_num;i++) { + if (map[m].npc[i]->flag&1) { // 無効化されている + f=0; + continue; + } + + switch(map[m].npc[i]->bl.subtype) { + case WARP: + xs=map[m].npc[i]->u.warp.xs; + ys=map[m].npc[i]->u.warp.ys; + break; + case SCRIPT: + xs=map[m].npc[i]->u.scr.xs; + ys=map[m].npc[i]->u.scr.ys; + break; + default: + continue; + } + if (x >= map[m].npc[i]->bl.x-xs/2 && x < map[m].npc[i]->bl.x-xs/2+xs && + y >= map[m].npc[i]->bl.y-ys/2 && y < map[m].npc[i]->bl.y-ys/2+ys) + break; + } + if (i==map[m].npc_num) { + if (f) { + if (battle_config.error_log) + printf("npc_touch_areanpc : some bug \n"); + } + return 1; + } + switch(map[m].npc[i]->bl.subtype) { + case WARP: + skill_stop_dancing(&sd->bl,0); + pc_setpos(sd,map[m].npc[i]->u.warp.name,map[m].npc[i]->u.warp.x,map[m].npc[i]->u.warp.y,0); + break; + case SCRIPT: + { + char *name=(char *)aCalloc(50,sizeof(char)); + + memcpy(name,map[m].npc[i]->name,50); + if(sd->areanpc_id==map[m].npc[i]->bl.id) + return 1; + sd->areanpc_id=map[m].npc[i]->bl.id; + if(npc_event(sd,strcat(name,"::OnTouch"),0)>0) + npc_click(sd,map[m].npc[i]->bl.id); + free(name); + break; + } + } + return 0; +} + +/*========================================== + * 近くかどうかの判定 + *------------------------------------------ + */ +int npc_checknear(struct map_session_data *sd,int id) +{ + struct npc_data *nd; + + nullpo_retr(0, sd); + + nd=(struct npc_data *)map_id2bl(id); + if (nd==NULL || nd->bl.type!=BL_NPC) { + if (battle_config.error_log) + printf("no such npc : %d\n",id); + return 1; + } + + if (nd->class<0) // イベント系は常にOK + return 0; + + // エリア判定 + if (nd->bl.m!=sd->bl.m || + nd->bl.x<sd->bl.x-AREA_SIZE-1 || nd->bl.x>sd->bl.x+AREA_SIZE+1 || + nd->bl.y<sd->bl.y-AREA_SIZE-1 || nd->bl.y>sd->bl.y+AREA_SIZE+1) + return 1; + + return 0; +} + +/*========================================== + * クリック時のNPC処理 + *------------------------------------------ + */ +int npc_click(struct map_session_data *sd,int id) +{ + struct npc_data *nd; + + nullpo_retr(1, sd); + + if (sd->npc_id != 0) { + if (battle_config.error_log) + printf("npc_click: npc_id != 0\n"); + return 1; + } + + if (npc_checknear(sd,id)) + return 1; + + nd=(struct npc_data *)map_id2bl(id); + + if (nd->flag&1) // 無効化されている + return 1; + + sd->npc_id=id; + switch(nd->bl.subtype) { + case SHOP: + clif_npcbuysell(sd,id); + npc_event_dequeue(sd); + break; + case SCRIPT: + sd->npc_pos=run_script(nd->u.scr.script,0,sd->bl.id,id); + break; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int npc_scriptcont(struct map_session_data *sd,int id) +{ + struct npc_data *nd; + + nullpo_retr(1, sd); + + if (id!=sd->npc_id) + return 1; + if (npc_checknear(sd,id)) + return 1; + + nd=(struct npc_data *)map_id2bl(id); + + sd->npc_pos=run_script(nd->u.scr.script,sd->npc_pos,sd->bl.id,id); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int npc_buysellsel(struct map_session_data *sd,int id,int type) +{ + struct npc_data *nd; + + nullpo_retr(1, sd); + + if (npc_checknear(sd,id)) + return 1; + + nd=(struct npc_data *)map_id2bl(id); + if (nd->bl.subtype!=SHOP) { + if (battle_config.error_log) + printf("no such shop npc : %d\n",id); + sd->npc_id=0; + return 1; + } + if (nd->flag&1) // 無効化されている + return 1; + + sd->npc_shopid=id; + if (type==0) { + clif_buylist(sd,nd); + } else { + clif_selllist(sd); + } + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int npc_buylist(struct map_session_data *sd,int n,unsigned short *item_list) +{ + struct npc_data *nd; + double z; + int i,j,w,skill,itemamount=0,new=0; + + nullpo_retr(3, sd); + nullpo_retr(3, item_list); + + if (npc_checknear(sd,sd->npc_shopid)) + return 3; + + nd=(struct npc_data*)map_id2bl(sd->npc_shopid); + if (nd->bl.subtype!=SHOP) + return 3; + + for(i=0,w=0,z=0;i<n;i++) { + for(j=0;nd->u.shop_item[j].nameid;j++) { + if (nd->u.shop_item[j].nameid==item_list[i*2+1]) + break; + } + if (nd->u.shop_item[j].nameid==0) + return 3; + + if (itemdb_value_notdc(nd->u.shop_item[j].nameid)) + z+=(double)nd->u.shop_item[j].value * item_list[i*2]; + else + z+=(double)pc_modifybuyvalue(sd,nd->u.shop_item[j].value) * item_list[i*2]; + itemamount+=item_list[i*2]; + + switch(pc_checkadditem(sd,item_list[i*2+1],item_list[i*2])) { + case ADDITEM_EXIST: + break; + case ADDITEM_NEW: + new++; + break; + case ADDITEM_OVERAMOUNT: + return 2; + } + + w+=itemdb_weight(item_list[i*2+1]) * item_list[i*2]; + } + if (z > (double)sd->status.zeny) + return 1; // zeny不足 + if (w+sd->weight > sd->max_weight) + return 2; // 重量超過 + if (pc_inventoryblank(sd)<new) + return 3; // 種類数超過 + + pc_payzeny(sd,(int)z); + for(i=0;i<n;i++) { + struct item item_tmp; + + memset(&item_tmp,0,sizeof(item_tmp)); + item_tmp.nameid = item_list[i*2+1]; + item_tmp.identify = 1; // npc販売アイテムは鑑定済み + + pc_additem(sd,&item_tmp,item_list[i*2]); + } + + //商人経験値 +/* if ((sd->status.class == 5) || (sd->status.class == 10) || (sd->status.class == 18)) { + z = z * pc_checkskill(sd,MC_DISCOUNT) / ((1 + 300 / itemamount) * 4000) * battle_config.shop_exp; + pc_gainexp(sd,0,z); + }*/ + if (battle_config.shop_exp > 0 && z > 0 && (skill = pc_checkskill(sd,MC_DISCOUNT)) > 0) { + if (sd->status.skill[MC_DISCOUNT].flag != 0) + skill = sd->status.skill[MC_DISCOUNT].flag - 2; + if (skill > 0) { + z = (log(z * (double)skill) * (double)battle_config.shop_exp/100.); + if (z < 1) + z = 1; + pc_gainexp(sd,0,(int)z); + } + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int npc_selllist(struct map_session_data *sd,int n,unsigned short *item_list) +{ + double z; + int i,skill,itemamount=0; + + nullpo_retr(1, sd); + nullpo_retr(1, item_list); + + if (npc_checknear(sd,sd->npc_shopid)) + return 1; + for(i=0,z=0;i<n;i++) { + int nameid; + if (item_list[i*2]-2 <0 || item_list[i*2]-2 >=MAX_INVENTORY) + return 1; + nameid=sd->status.inventory[item_list[i*2]-2].nameid; + if (nameid == 0 || + sd->status.inventory[item_list[i*2]-2].amount < item_list[i*2+1]) + return 1; + if (itemdb_value_notoc(nameid)) + z+=(double)itemdb_value_sell(nameid) * item_list[i*2+1]; + else + z+=(double)pc_modifysellvalue(sd,itemdb_value_sell(nameid)) * item_list[i*2+1]; + itemamount+=item_list[i*2+1]; + } + + if (z > MAX_ZENY) z = MAX_ZENY; + pc_getzeny(sd,(int)z); + for(i=0;i<n;i++) { + int item_id=item_list[i*2]-2; + if( sd->status.inventory[item_id].nameid>0 && sd->inventory_data[item_id] != NULL && + sd->inventory_data[item_id]->type==7 && sd->status.inventory[item_id].amount>0 && + sd->status.inventory[item_id].card[0] == (short)0xff00) + if(search_petDB_index(sd->status.inventory[item_id].nameid, PET_EGG) >= 0) + intif_delete_petdata((*(long *)(&sd->status.inventory[item_id].card[1]))); + pc_delitem(sd,item_id,item_list[i*2+1],0); + } + + //商人経験値 +/* if ((sd->status.class == 5) || (sd->status.class == 10) || (sd->status.class == 18)) { + z = z * pc_checkskill(sd,MC_OVERCHARGE) / ((1 + 500 / itemamount) * 4000) * battle_config.shop_exp ; + pc_gainexp(sd,0,z); + }*/ + if (battle_config.shop_exp > 0 && z > 0 && (skill = pc_checkskill(sd,MC_OVERCHARGE)) > 0) { + if (sd->status.skill[MC_OVERCHARGE].flag != 0) + skill = sd->status.skill[MC_OVERCHARGE].flag - 2; + if (skill > 0) { + z = (log(z * (double)skill) * (double)battle_config.shop_exp/100.); + if (z < 1) + z = 1; + pc_gainexp(sd,0,(int)z); + } + } + + return 0; + +} + +// +// 初期化関係 +// + +/*========================================== + * 読み込むnpcファイルのクリア + *------------------------------------------ + */ +void npc_clearsrcfile() +{ + struct npc_src_list *p=npc_src_first; + + while( p ) { + struct npc_src_list *p2=p; + p=p->next; + free(p2); + } + npc_src_first=NULL; + npc_src_last=NULL; +} +/*========================================== + * 読み込むnpcファイルの追加 + *------------------------------------------ + */ +void npc_addsrcfile(char *name) +{ + struct npc_src_list *new; + size_t len; + + if ( strcmpi(name,"clear")==0 ) { + npc_clearsrcfile(); + return; + } + + len = sizeof(*new) + strlen(name); + new=(struct npc_src_list *)aCalloc(1,len); + new->next = NULL; + strncpy(new->name,name,strlen(name)+1); + if (npc_src_first==NULL) + npc_src_first = new; + if (npc_src_last) + npc_src_last->next = new; + + npc_src_last=new; +} +/*========================================== + * 読み込むnpcファイルの削除 + *------------------------------------------ + */ +void npc_delsrcfile(char *name) +{ + struct npc_src_list *p=npc_src_first,*pp=NULL,**lp=&npc_src_first; + + if ( strcmpi(name,"all")==0 ) { + npc_clearsrcfile(); + return; + } + + for( ; p; lp=&p->next,pp=p,p=p->next ) { + if ( strcmp(p->name,name)==0 ) { + *lp=p->next; + if ( npc_src_last==p ) + npc_src_last=pp; + free(p); + break; + } + } +} + +/*========================================== + * warp行解析 + *------------------------------------------ + */ +int npc_parse_warp(char *w1,char *w2,char *w3,char *w4) +{ + int x,y,xs,ys,to_x,to_y,m; + int i,j; + char mapname[24],to_mapname[24]; + struct npc_data *nd; + + // 引数の個数チェック + if (sscanf(w1,"%[^,],%d,%d",mapname,&x,&y) != 3 || + sscanf(w4,"%d,%d,%[^,],%d,%d",&xs,&ys,to_mapname,&to_x,&to_y) != 5) { + printf("bad warp line : %s\n",w3); + return 1; + } + + m=map_mapname2mapid(mapname); + + nd=(struct npc_data *)aCalloc(1,sizeof(struct npc_data)); + nd->bl.id=npc_get_new_npc_id(); + nd->n=map_addnpc(m,nd); + + nd->bl.prev = nd->bl.next = NULL; + nd->bl.m=m; + nd->bl.x=x; + nd->bl.y=y; + nd->dir=0; + nd->flag=0; + memcpy(nd->name,w3,24); + memcpy(nd->exname,w3,24); + + nd->chat_id=0; + if (!battle_config.warp_point_debug) + nd->class=WARP_CLASS; + else + nd->class=WARP_DEBUG_CLASS; + nd->speed=200; + nd->option = 0; + nd->opt1 = 0; + nd->opt2 = 0; + nd->opt3 = 0; + memcpy(nd->u.warp.name,to_mapname,16); + xs+=2; ys+=2; + nd->u.warp.x=to_x; + nd->u.warp.y=to_y; + nd->u.warp.xs=xs; + nd->u.warp.ys=ys; + + for(i=0;i<ys;i++) { + for(j=0;j<xs;j++) { + int t; + t=map_getcell(m,x-xs/2+j,y-ys/2+i); + if (t==1 || t==5) + continue; + map_setcell(m,x-xs/2+j,y-ys/2+i,t|0x80); + } + } + +// printf("warp npc %s %d read done\n",mapname,nd->bl.id); + npc_warp++; + nd->bl.type=BL_NPC; + nd->bl.subtype=WARP; + map_addblock(&nd->bl); + clif_spawnnpc(nd); + strdb_insert(npcname_db,nd->name,nd); + + return 0; +} + +/*========================================== + * shop行解析 + *------------------------------------------ + */ +static int npc_parse_shop(char *w1,char *w2,char *w3,char *w4) +{ + char *p; + int x, y, dir, m; + int max = 100, pos = 0; + char mapname[24]; + struct npc_data *nd; + + // 引数の個数チェック + if (sscanf(w1, "%[^,],%d,%d,%d", mapname, &x, &y, &dir) != 4 || + strchr(w4, ',') == NULL) { + printf("bad shop line : %s\n", w3); + return 1; + } + m = map_mapname2mapid(mapname); + + nd = (struct npc_data *)aCalloc(1,sizeof(struct npc_data) + + sizeof(nd->u.shop_item[0]) * (max + 1)); + p = strchr(w4, ','); + + while (p && pos < max) { + int nameid,value; + p++; + if (sscanf(p, "%d:%d", &nameid, &value) != 2) + break; + nd->u.shop_item[pos].nameid = nameid; + if (value < 0) { + struct item_data *id = itemdb_search(nameid); + value = id->value_buy; + } + nd->u.shop_item[pos].value = value; + pos++; + p=strchr(p,','); + } + if (pos == 0) { + free(nd); + return 1; + } + nd->u.shop_item[pos++].nameid = 0; + + nd->bl.prev = nd->bl.next = NULL; + nd->bl.m = m; + nd->bl.x = x; + nd->bl.y = y; + nd->bl.id = npc_get_new_npc_id(); + nd->dir = dir; + nd->flag = 0; + memcpy(nd->name, w3, 24); + nd->class = atoi(w4); + nd->speed = 200; + nd->chat_id = 0; + nd->option = 0; + nd->opt1 = 0; + nd->opt2 = 0; + nd->opt3 = 0; + + nd = (struct npc_data *)aRealloc(nd, + sizeof(struct npc_data) + sizeof(nd->u.shop_item[0]) * pos); + + //printf("shop npc %s %d read done\n",mapname,nd->bl.id); + npc_shop++; + nd->bl.type=BL_NPC; + nd->bl.subtype=SHOP; + nd->n=map_addnpc(m,nd); + map_addblock(&nd->bl); + clif_spawnnpc(nd); + strdb_insert(npcname_db,nd->name,nd); + + return 0; +} +/*========================================== + * NPCのラベルデータコンバート + *------------------------------------------ + */ +int npc_convertlabel_db(void *key,void *data,va_list ap) +{ + char *lname=(char *)key; + int pos=(int)data; + struct npc_data *nd; + struct npc_label_list *lst; + int num; + char *p=strchr(lname,':'); + + nullpo_retr(0, ap); + nullpo_retr(0, nd=va_arg(ap,struct npc_data *)); + + lst=nd->u.scr.label_list; + num=nd->u.scr.label_list_num; + if(!lst){ + lst=(struct npc_label_list *)aCalloc(1,sizeof(struct npc_label_list)); + num=0; + }else + lst=(struct npc_label_list *)aRealloc(lst,sizeof(struct npc_label_list)*(num+1)); + + *p='\0'; + strncpy(lst[num].name,lname,24); + *p=':'; + lst[num].pos=pos; + nd->u.scr.label_list=lst; + nd->u.scr.label_list_num=num+1; + return 0; +} +/*========================================== + * script行解析 + *------------------------------------------ + */ +static int npc_parse_script(char *w1,char *w2,char *w3,char *w4,char *first_line,FILE *fp,int *lines) +{ + int x,y,dir=0,m,xs=0,ys=0,class=0; // [Valaris] thanks to fov + char mapname[24]; + unsigned char *srcbuf=NULL,*script; + int srcsize=65536; + int startline=0; + unsigned char line[1024]; + int i; + struct npc_data *nd; + int evflag=0; + struct dbt *label_db; + char *p; + struct npc_label_list *label_dup=NULL; + int label_dupnum=0; + int src_id=0; + + if(strcmp(w1,"-")==0){ + x=0;y=0;m=-1; + }else{ + // 引数の個数チェック + if (sscanf(w1,"%[^,],%d,%d,%d",mapname,&x,&y,&dir) != 4 || + ( strcmp(w2,"script")==0 && strchr(w4,',')==NULL) ) { + printf("bad script line : %s\n",w3); + return 1; + } + m = map_mapname2mapid(mapname); + } + + if(strcmp(w2,"script")==0){ + // スクリプトの解析 + srcbuf=(char *)aCalloc(srcsize,sizeof(char)); + if (strchr(first_line,'{')) { + strcpy(srcbuf,strchr(first_line,'{')); + startline=*lines; + } else + srcbuf[0]=0; + while(1) { + for(i=strlen(srcbuf)-1;i>=0 && isspace(srcbuf[i]);i--); + if (i>=0 && srcbuf[i]=='}') + break; + fgets(line,1020,fp); + (*lines)++; + if (feof(fp)) + break; + if (strlen(srcbuf)+strlen(line)+1>=srcsize) { + srcsize += 65536; + srcbuf = (char *)aRealloc(srcbuf, srcsize); + memset(srcbuf + srcsize - 65536, '\0', 65536); + } + if (srcbuf[0]!='{') { + if (strchr(line,'{')) { + strcpy(srcbuf,strchr(line,'{')); + startline=*lines; + } + } else + strcat(srcbuf,line); + } + script=parse_script(srcbuf,startline); + if (script==NULL) { + // script parse error? + free(srcbuf); + return 1; + } + + }else{ + // duplicateする + + char srcname[128]; + struct npc_data *nd2; + if( sscanf(w2,"duplicate(%[^)])",srcname)!=1 ){ + printf("bad duplicate name! : %s",w2); + return 0; + } + if( (nd2=npc_name2id(srcname))==NULL ){ + printf("bad duplicate name! (not exist) : %s\n",srcname); + return 0; + } + script=nd2->u.scr.script; + label_dup=nd2->u.scr.label_list; + label_dupnum=nd2->u.scr.label_list_num; + src_id=nd2->bl.id; + + }// end of スクリプト解析 + + nd=(struct npc_data *)aCalloc(1,sizeof(struct npc_data)); + + if(m==-1){ + // スクリプトコピー用のダミーNPC + + }else if( sscanf(w4,"%d,%d,%d",&class,&xs,&ys)==3) { + // 接触型NPC + int i,j; + + if (xs>=0)xs=xs*2+1; + if (ys>=0)ys=ys*2+1; + + if (class>=0) { + + for(i=0;i<ys;i++) { + for(j=0;j<xs;j++) { + int t; + t=map_getcell(m,x-xs/2+j,y-ys/2+i); + if (t==1 || t==5) + continue; + map_setcell(m,x-xs/2+j,y-ys/2+i,t|0x80); + } + } + } + + nd->u.scr.xs=xs; + nd->u.scr.ys=ys; + } else { // クリック型NPC + class=atoi(w4); + nd->u.scr.xs=0; + nd->u.scr.ys=0; + } + + if (class<0 && m>=0) { // イベント型NPC + evflag=1; + } + + while((p=strchr(w3,':'))) { + if (p[1]==':') break; + } + if (p) { + *p=0; + memcpy(nd->name,w3,24); + memcpy(nd->exname,p+2,24); + }else{ + memcpy(nd->name,w3,24); + memcpy(nd->exname,w3,24); + } + + nd->bl.prev = nd->bl.next = NULL; + nd->bl.m = m; + nd->bl.x = x; + nd->bl.y = y; + nd->bl.id=npc_get_new_npc_id(); + nd->dir = dir; + nd->flag=0; + nd->class=class; + nd->speed=200; + nd->u.scr.script=script; + nd->u.scr.src_id=src_id; + nd->chat_id=0; + nd->option = 0; + nd->opt1 = 0; + nd->opt2 = 0; + nd->opt3 = 0; + + //printf("script npc %s %d %d read done\n",mapname,nd->bl.id,nd->class); + npc_script++; + nd->bl.type=BL_NPC; + nd->bl.subtype=SCRIPT; + if(m>=0){ + nd->n=map_addnpc(m,nd); + map_addblock(&nd->bl); + + if (evflag) { // イベント型 + struct event_data *ev=(struct event_data *)aCalloc(1,sizeof(struct event_data)); + ev->nd=nd; + ev->pos=0; + strdb_insert(ev_db,nd->exname,ev); + }else + clif_spawnnpc(nd); + } + strdb_insert(npcname_db,nd->exname,nd); + + + //----------------------------------------- + // ラベルデータの準備 + if(srcbuf){ + // script本体がある場合の処理 + + // ラベルデータのコンバート + label_db=script_get_label_db(); + strdb_foreach(label_db,npc_convertlabel_db,nd); + + // もう使わないのでバッファ解放 + free(srcbuf); + + }else{ + // duplicate + +// nd->u.scr.label_list=malloc(sizeof(struct npc_label_list)*label_dupnum); +// memcpy(nd->u.scr.label_list,label_dup,sizeof(struct npc_label_list)*label_dupnum); + + nd->u.scr.label_list=label_dup; // ラベルデータ共有 + nd->u.scr.label_list_num=label_dupnum; + } + + //----------------------------------------- + // イベント用ラベルデータのエクスポート + for(i=0;i<nd->u.scr.label_list_num;i++){ + char *lname=nd->u.scr.label_list[i].name; + int pos=nd->u.scr.label_list[i].pos; + + if ((lname[0]=='O' || lname[0]=='o')&&(lname[1]=='N' || lname[1]=='n')) { + struct event_data *ev; + char *buf; + // エクスポートされる + ev=(struct event_data *)aCalloc(1,sizeof(struct event_data)); + buf=(char *)aCalloc(50,sizeof(char)); + if (strlen(lname)>24) { + printf("npc_parse_script: label name error !\n"); + exit(1); + }else{ + ev->nd=nd; + ev->pos=pos; + sprintf(buf,"%s::%s",nd->exname,lname); + strdb_insert(ev_db,buf,ev); + } + } + } + + //----------------------------------------- + // ラベルデータからタイマーイベント取り込み + for(i=0;i<nd->u.scr.label_list_num;i++){ + int t=0,k=0; + char *lname=nd->u.scr.label_list[i].name; + int pos=nd->u.scr.label_list[i].pos; + if(sscanf(lname,"OnTimer%d%n",&t,&k)==1 && lname[k]=='\0') { + // タイマーイベント + struct npc_timerevent_list *te=nd->u.scr.timer_event; + int j,k=nd->u.scr.timeramount; + if(te==NULL) + te=(struct npc_timerevent_list *)aCalloc(1,sizeof(struct npc_timerevent_list)); + else + te=(struct npc_timerevent_list *)aRealloc( te, sizeof(struct npc_timerevent_list) * (k+1) ); + for(j=0;j<k;j++){ + if(te[j].timer>t){ + memmove(te+j+1,te+j,sizeof(struct npc_timerevent_list)*(k-j)); + break; + } + } + te[j].timer=t; + te[j].pos=pos; + nd->u.scr.timer_event=te; + nd->u.scr.timeramount=k+1; + } + } + nd->u.scr.nexttimer=-1; + nd->u.scr.timerid=-1; + + + return 0; +} + +/*========================================== + * function行解析 + *------------------------------------------ + */ +static int npc_parse_function(char *w1,char *w2,char *w3,char *w4,char *first_line,FILE *fp,int *lines) +{ + char *srcbuf=NULL,*script; + int srcsize=65536; + int startline=0; + char line[1024]; + int i; +// struct dbt *label_db; + char *p; + + // スクリプトの解析 + srcbuf=(char *)aCalloc(srcsize,sizeof(char)); + if (strchr(first_line,'{')) { + strcpy(srcbuf,strchr(first_line,'{')); + startline=*lines; + } else + srcbuf[0]=0; + while(1) { + for(i=strlen(srcbuf)-1;i>=0 && isspace(srcbuf[i]);i--); + if (i>=0 && srcbuf[i]=='}') + break; + fgets(line,1020,fp); + (*lines)++; + if (feof(fp)) + break; + if (strlen(srcbuf)+strlen(line)+1>=srcsize) { + srcsize += 65536; + srcbuf = (char *)aRealloc(srcbuf, srcsize); + memset(srcbuf + srcsize - 65536, '\0', 65536); + } + if (srcbuf[0]!='{') { + if (strchr(line,'{')) { + strcpy(srcbuf,strchr(line,'{')); + startline=*lines; + } + } else + strcat(srcbuf,line); + } + script=parse_script(srcbuf,startline); + if (script==NULL) { + // script parse error? + free(srcbuf); + return 1; + } + + p=(char *)aCalloc(50,sizeof(char)); + + strncpy(p,w3,50); + strdb_insert(script_get_userfunc_db(),p,script); + +// label_db=script_get_label_db(); + + // もう使わないのでバッファ解放 + free(srcbuf); + +// printf("function %s => %p\n",p,script); + + return 0; +} + + +/*========================================== + * mob行解析 + *------------------------------------------ + */ +int npc_parse_mob(char *w1,char *w2,char *w3,char *w4) +{ + int m,x,y,xs,ys,class,num,delay1,delay2; + int i; + char mapname[24]; + char eventname[24]=""; + struct mob_data *md; + + xs=ys=0; + delay1=delay2=0; + // 引数の個数チェック + if (sscanf(w1,"%[^,],%d,%d,%d,%d",mapname,&x,&y,&xs,&ys) < 3 || + sscanf(w4,"%d,%d,%d,%d,%s",&class,&num,&delay1,&delay2,eventname) < 2 ) { + printf("bad monster line : %s\n",w3); + return 1; + } + + m=map_mapname2mapid(mapname); + + if ( num>1 && battle_config.mob_count_rate!=100) { + if ( (num=num*battle_config.mob_count_rate/100)<1 ) + num=1; + } + + for(i=0;i<num;i++) { + md=(struct mob_data *)aCalloc(1,sizeof(struct mob_data)); + + md->bl.prev=NULL; + md->bl.next=NULL; + md->bl.m=m; + md->bl.x=x; + md->bl.y=y; + if(strcmp(w3,"--en--")==0) + memcpy(md->name,mob_db[class].name,24); + else if(strcmp(w3,"--ja--")==0) + memcpy(md->name,mob_db[class].jname,24); + else + memcpy(md->name,w3,24); + + md->n = i; + md->base_class = md->class = class; + md->bl.id=npc_get_new_npc_id(); + md->m =m; + md->x0=x; + md->y0=y; + md->xs=xs; + md->ys=ys; + md->spawndelay1=delay1; + md->spawndelay2=delay2; + + memset(&md->state,0,sizeof(md->state)); + md->timer = -1; + md->target_id=0; + md->attacked_id=0; + md->speed=mob_db[class].speed; + + if (mob_db[class].mode&0x02) + md->lootitem=(struct item *)aCalloc(LOOTITEM_SIZE,sizeof(struct item)); + else + md->lootitem=NULL; + + if (strlen(eventname)>=4) { + memcpy(md->npc_event,eventname,24); + }else + memset(md->npc_event,0,24); + + md->bl.type=BL_MOB; + map_addiddb(&md->bl); + mob_spawn(md->bl.id); + + npc_mob++; + } + //printf("warp npc %s %d read done\n",mapname,nd->bl.id); + + return 0; +} + +/*========================================== + * マップフラグ行の解析 + *------------------------------------------ + */ +static int npc_parse_mapflag(char *w1,char *w2,char *w3,char *w4) +{ + int m; + char mapname[24],savemap[16]; + int savex,savey; + char drop_arg1[16],drop_arg2[16]; + int drop_id=0,drop_type=0,drop_per=0; + + // 引数の個数チェック +// if ( sscanf(w1,"%[^,],%d,%d,%d",mapname,&x,&y,&dir) != 4 ) + if ( sscanf(w1,"%[^,]",mapname) != 1 ) + return 1; + + m=map_mapname2mapid(mapname); + if (m<0) + return 1; + +//マップフラグ + if ( strcmpi(w3,"nosave")==0) { + if (strcmp(w4,"SavePoint")==0) { + memcpy(map[m].save.map,"SavePoint",16); + map[m].save.x=-1; + map[m].save.y=-1; + }else if (sscanf(w4,"%[^,],%d,%d",savemap,&savex,&savey)==3) { + memcpy(map[m].save.map,savemap,16); + map[m].save.x=savex; + map[m].save.y=savey; + } + map[m].flag.nosave=1; + } + else if (strcmpi(w3,"nomemo")==0) { + map[m].flag.nomemo=1; + } + else if (strcmpi(w3,"noteleport")==0) { + map[m].flag.noteleport=1; + } + else if (strcmpi(w3,"nowarp")==0) { + map[m].flag.nowarp=1; + } + else if (strcmpi(w3,"nowarpto")==0) { + map[m].flag.nowarpto=1; + } + else if (strcmpi(w3,"noreturn")==0) { + map[m].flag.noreturn=1; + } + else if (strcmpi(w3,"monster_noteleport")==0) { + map[m].flag.monster_noteleport=1; + } + else if (strcmpi(w3,"nobranch")==0) { + map[m].flag.nobranch=1; + } + else if (strcmpi(w3,"nopenalty")==0) { + map[m].flag.nopenalty=1; + } + else if (strcmpi(w3,"pvp")==0) { + map[m].flag.pvp=1; + } + else if (strcmpi(w3,"pvp_noparty")==0) { + map[m].flag.pvp_noparty=1; + } + else if (strcmpi(w3,"pvp_noguild")==0) { + map[m].flag.pvp_noguild=1; + } + else if (strcmpi(w3,"pvp_nightmaredrop")==0) { + if (sscanf(w4,"%[^,],%[^,],%d",drop_arg1,drop_arg2,&drop_per)==3) { int i; + if(strcmp(drop_arg1,"random")==0) + drop_id = -1; + else if(itemdb_exists( (drop_id=atoi(drop_arg1)) )==NULL) + drop_id = 0; + if(strcmp(drop_arg2,"inventory")==0) + drop_type = 1; + else if(strcmp(drop_arg2,"equip")==0) + drop_type = 2; + else if(strcmp(drop_arg2,"all")==0) + drop_type = 3; + + if(drop_id != 0){ + for (i=0;i<MAX_DROP_PER_MAP;i++){ + if(map[m].drop_list[i].drop_id==0){ + map[m].drop_list[i].drop_id = drop_id; + map[m].drop_list[i].drop_type = drop_type; + map[m].drop_list[i].drop_per = drop_per; + break; + } + } + map[m].flag.pvp_nightmaredrop=1; + } + } + } + else if (strcmpi(w3,"pvp_nocalcrank")==0) { + map[m].flag.pvp_nocalcrank=1; + } + else if (strcmpi(w3,"gvg")==0) { + map[m].flag.gvg=1; + } + else if (strcmpi(w3,"gvg_noparty")==0) { + map[m].flag.gvg_noparty=1; + } + else if (strcmpi(w3,"nozenypenalty")==0) { + map[m].flag.nozenypenalty=1; + } + else if (strcmpi(w3,"notrade")==0) { + map[m].flag.notrade=1; + } + else if (strcmpi(w3,"noskill")==0) { + map[m].flag.noskill=1; + } + else if (battle_config.pk_mode && strcmpi(w3,"nopvp")==0) { // nopvp for pk mode [Valaris] + map[m].flag.nopvp=1; + map[m].flag.pvp=0; + } + else if (strcmpi(w3,"noicewall")==0) { // noicewall [Valaris] + map[m].flag.noicewall=1; + } + else if (strcmpi(w3,"snow")==0) { // snow [Valaris] + map[m].flag.snow=1; + } + else if (strcmpi(w3,"fog")==0) { // fog [Valaris] + map[m].flag.fog=1; + } + else if (strcmpi(w3,"sakura")==0) { // sakura [Valaris] + map[m].flag.sakura=1; + } + else if (strcmpi(w3,"leaves")==0) { // leaves [Valaris] + map[m].flag.leaves=1; + } + else if (strcmpi(w3,"rain")==0) { // rain [Valaris] + map[m].flag.rain=1; + } + + return 0; +} + +static int ev_db_final(void *key,void *data,va_list ap) +{ + free(data); + if(strstr(key,"::")!=NULL) + free(key); + return 0; +} +static int npcname_db_final(void *key,void *data,va_list ap) +{ + return 0; +} +/*========================================== + * 終了 + *------------------------------------------ + */ +int do_final_npc(void) +{ + int i; + struct block_list *bl; + struct npc_data *nd; + struct mob_data *md; + struct chat_data *cd; + struct pet_data *pd; + + if(ev_db) + strdb_final(ev_db,ev_db_final); + if(npcname_db) + strdb_final(npcname_db,npcname_db_final); + + for(i=START_NPC_NUM;i<npc_id;i++){ + if((bl=map_id2bl(i))){ + if(bl->type == BL_NPC && (nd = (struct npc_data *)bl)){ + if(nd->chat_id && (cd=(struct chat_data*)map_id2bl(nd->chat_id))){ + free(cd); + cd = NULL; + } + if(nd->bl.subtype == SCRIPT){ + if(nd->u.scr.timer_event) + free(nd->u.scr.timer_event); + if(nd->u.scr.src_id==0){ + if(nd->u.scr.script){ + free(nd->u.scr.script); + nd->u.scr.script=NULL; + } + if(nd->u.scr.label_list){ + free(nd->u.scr.label_list); + nd->u.scr.label_list = NULL; + } + } + } + free(nd); + nd = NULL; + }else if(bl->type == BL_MOB && (md = (struct mob_data *)bl)){ + if(md->lootitem){ + free(md->lootitem); + md->lootitem = NULL; + } + free(md); + md = NULL; + }else if(bl->type == BL_PET && (pd = (struct pet_data *)bl)){ + free(pd); + pd = NULL; + } + } + } + + return 0; +} + + +void ev_release(struct dbn *db, int which) +{ + if (which & 0x1) + free(db->key); + if (which & 0x2) + free(db->data); +} + +/*========================================== + * npc初期化 + *------------------------------------------ + */ +int do_init_npc(void) +{ + struct npc_src_list *nsl; + FILE *fp; + char line[1024]; + int m,lines; + + ev_db=strdb_init(24); + npcname_db=strdb_init(24); + + ev_db->release = ev_release; + + memset(&ev_tm_b,-1,sizeof(ev_tm_b)); + + for(nsl=npc_src_first;nsl;nsl=nsl->next) { + if(nsl->prev){ + free(nsl->prev); + nsl->prev = NULL; + } + fp=fopen(nsl->name,"r"); + if (fp==NULL) { + printf("file not found : %s\n",nsl->name); + exit(1); + } + lines=0; + while(fgets(line,1020,fp)) { + char w1[1024],w2[1024],w3[1024],w4[1024],mapname[1024]; + int i,j,w4pos,count; + lines++; + + if (line[0] == '/' && line[1] == '/') + continue; + // 不要なスペースやタブの連続は詰める + for(i=j=0;line[i];i++) { + if (line[i]==' ') { + if (!((line[i+1] && (isspace(line[i+1]) || line[i+1]==',')) || + (j && line[j-1]==','))) + line[j++]=' '; + } else if (line[i]=='\t') { + if (!(j && line[j-1]=='\t')) + line[j++]='\t'; + } else + line[j++]=line[i]; + } + // 最初はタブ区切りでチェックしてみて、ダメならスペース区切りで確認 + if ((count=sscanf(line,"%[^\t]\t%[^\t]\t%[^\t\r\n]\t%n%[^\t\r\n]",w1,w2,w3,&w4pos,w4)) < 3 && + (count=sscanf(line,"%s%s%s%n%s",w1,w2,w3,&w4pos,w4)) < 3) { + continue; + } + // マップの存在確認 + if( strcmp(w1,"-")!=0 && strcmpi(w1,"function")!=0 ){ + sscanf(w1,"%[^,]",mapname); + m = map_mapname2mapid(mapname); + if (strlen(mapname)>16 || m<0) { + // "mapname" is not assigned to this server + continue; + } + } + if (strcmpi(w2,"warp")==0 && count > 3) { + npc_parse_warp(w1,w2,w3,w4); + } else if (strcmpi(w2,"shop")==0 && count > 3) { + npc_parse_shop(w1,w2,w3,w4); + } else if (strcmpi(w2,"script")==0 && count > 3) { + if( strcmpi(w1,"function")==0 ){ + npc_parse_function(w1,w2,w3,w4,line+w4pos,fp,&lines); + }else{ + npc_parse_script(w1,w2,w3,w4,line+w4pos,fp,&lines); + } + } else if ( (i=0,sscanf(w2,"duplicate%n",&i), (i>0 && w2[i]=='(')) && count > 3) { + npc_parse_script(w1,w2,w3,w4,line+w4pos,fp,&lines); + } else if (strcmpi(w2,"monster")==0 && count > 3) { + npc_parse_mob(w1,w2,w3,w4); + } else if (strcmpi(w2,"mapflag")==0 && count >= 3) { + npc_parse_mapflag(w1,w2,w3,w4); + } + } + fclose(fp); + printf("\rLoading NPCs [%d]: %-54s",npc_id-START_NPC_NUM,nsl->name); + fflush(stdout); + } + printf("\rNPCs Loaded: %d [Warps:%d Shops:%d Scripts:%d Mobs:%d]\n", + npc_id-START_NPC_NUM,npc_warp,npc_shop,npc_script,npc_mob); + + add_timer_func_list(npc_event_timer,"npc_event_timer"); + add_timer_func_list(npc_event_do_clock,"npc_event_do_clock"); + add_timer_func_list(npc_timerevent,"npc_timerevent"); + + //exit(1); + + return 0; +} diff --git a/src/map/npc.h b/src/map/npc.h new file mode 100644 index 0000000..63d7765 --- /dev/null +++ b/src/map/npc.h @@ -0,0 +1,48 @@ +// $Id: npc.h,v 1.5 2004/09/25 11:39:17 MouseJstr Exp $ +#ifndef _NPC_H_ +#define _NPC_H_ + +#define START_NPC_NUM 110000000 + +#define WARP_CLASS 45 +#define WARP_DEBUG_CLASS 722 +#define INVISIBLE_CLASS 32767 + +int npc_event_dequeue(struct map_session_data *sd); +int npc_event_timer(int tid,unsigned int tick,int id,int data); +int npc_event(struct map_session_data *sd,const char *npcname,int); +int npc_timer_event(const char *eventname); // Added by RoVeRT +int npc_command(struct map_session_data *sd,char *npcname,char *command); +int npc_touch_areanpc(struct map_session_data *,int,int,int); +int npc_click(struct map_session_data *,int); +int npc_scriptcont(struct map_session_data *,int); +int npc_checknear(struct map_session_data *,int); +int npc_buysellsel(struct map_session_data *,int,int); +int npc_buylist(struct map_session_data *,int,unsigned short *); +int npc_selllist(struct map_session_data *,int,unsigned short *); +int npc_parse_mob(char *w1,char *w2,char *w3,char *w4); +int npc_parse_warp(char *w1,char *w2,char *w3,char *w4); + +int npc_enable(const char *name,int flag); +struct npc_data* npc_name2id(const char *name); + +int npc_get_new_npc_id(void); + +void npc_addsrcfile(char *); +void npc_delsrcfile(char *); +int do_final_npc(void); +int do_init_npc(void); +int npc_event_do_oninit(void); +int npc_do_ontimer(int,struct map_session_data *,int); + +int npc_event_doall(const char *name); +int npc_event_do(const char *name); + +int npc_timerevent_start(struct npc_data *nd); +int npc_timerevent_stop(struct npc_data *nd); +int npc_gettimerevent_tick(struct npc_data *nd); +int npc_settimerevent_tick(struct npc_data *nd,int newtimer); +int npc_delete(struct npc_data *nd); + +#endif + diff --git a/src/map/party.c b/src/map/party.c new file mode 100644 index 0000000..7d8cdaf --- /dev/null +++ b/src/map/party.c @@ -0,0 +1,644 @@ +// $Id: party.c,v 1.2 2004/09/22 02:59:47 Akitasha Exp $ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "party.h" +#include "db.h" +#include "timer.h" +#include "socket.h" +#include "nullpo.h" +#include "malloc.h" +#include "pc.h" +#include "map.h" +#include "battle.h" +#include "intif.h" +#include "clif.h" + +#ifdef MEMWATCH +#include "memwatch.h" +#endif + +#define PARTY_SEND_XYHP_INVERVAL 1000 // 座標やHP送信の間隔 + +static struct dbt* party_db; + +int party_send_xyhp_timer(int tid,unsigned int tick,int id,int data); +/*========================================== + * 終了 + *------------------------------------------ + */ +static int party_db_final(void *key,void *data,va_list ap) +{ + free(data); + return 0; +} +void do_final_party(void) +{ + if(party_db) + numdb_final(party_db,party_db_final); +} +// 初期化 +void do_init_party(void) +{ + party_db=numdb_init(); + add_timer_func_list(party_send_xyhp_timer,"party_send_xyhp_timer"); + add_timer_interval(gettick()+PARTY_SEND_XYHP_INVERVAL,party_send_xyhp_timer,0,0,PARTY_SEND_XYHP_INVERVAL); +} + +// 検索 +struct party *party_search(int party_id) +{ + return numdb_search(party_db,party_id); +} +int party_searchname_sub(void *key,void *data,va_list ap) +{ + struct party *p=(struct party *)data,**dst; + char *str; + str=va_arg(ap,char *); + dst=va_arg(ap,struct party **); + if(strcmpi(p->name,str)==0) + *dst=p; + return 0; +} +// パーティ名検索 +struct party* party_searchname(char *str) +{ + struct party *p=NULL; + numdb_foreach(party_db,party_searchname_sub,str,&p); + return p; +} +// 作成要求 +int party_create(struct map_session_data *sd,char *name) +{ + nullpo_retr(0, sd); + + if(sd->status.party_id==0) + intif_create_party(sd,name); + else + clif_party_created(sd,2); + return 0; +} + +// 作成可否 +int party_created(int account_id,int fail,int party_id,char *name) +{ + struct map_session_data *sd; + sd=map_id2sd(account_id); + + nullpo_retr(0, sd); + + if(fail==0){ + struct party *p; + sd->status.party_id=party_id; + if((p=numdb_search(party_db,party_id))!=NULL){ + printf("party: id already exists!\n"); + exit(1); + } + p=(struct party *)aCalloc(1,sizeof(struct party)); + p->party_id=party_id; + memcpy(p->name,name,24); + numdb_insert(party_db,party_id,p); + clif_party_created(sd,0); + }else{ + clif_party_created(sd,1); + } + return 0; +} + +// 情報要求 +int party_request_info(int party_id) +{ + return intif_request_partyinfo(party_id); +} + +// 所属キャラの確認 +int party_check_member(struct party *p) +{ + int i; + struct map_session_data *sd; + + nullpo_retr(0, p); + + for(i=0;i<fd_max;i++){ + if(session[i] && (sd=session[i]->session_data) && sd->state.auth){ + if(sd->status.party_id==p->party_id){ + int j,f=1; + for(j=0;j<MAX_PARTY;j++){ // パーティにデータがあるか確認 + if( p->member[j].account_id==sd->status.account_id){ + if( strcmp(p->member[j].name,sd->status.name)==0 ) + f=0; // データがある + else + p->member[j].sd=NULL; // 同垢別キャラだった + } + } + if(f){ + sd->status.party_id=0; + if(battle_config.error_log) + printf("party: check_member %d[%s] is not member\n",sd->status.account_id,sd->status.name); + } + } + } + } + return 0; +} + +// 情報所得失敗(そのIDのキャラを全部未所属にする) +int party_recv_noinfo(int party_id) +{ + int i; + struct map_session_data *sd; + for(i=0;i<fd_max;i++){ + if(session[i] && (sd=session[i]->session_data) && sd->state.auth){ + if(sd->status.party_id==party_id) + sd->status.party_id=0; + } + } + return 0; +} +// 情報所得 +int party_recv_info(struct party *sp) +{ + struct party *p; + int i; + + nullpo_retr(0, sp); + + if((p=numdb_search(party_db,sp->party_id))==NULL){ + p=(struct party *)aCalloc(1,sizeof(struct party)); + numdb_insert(party_db,sp->party_id,p); + + // 最初のロードなのでユーザーのチェックを行う + party_check_member(sp); + } + memcpy(p,sp,sizeof(struct party)); + + for(i=0;i<MAX_PARTY;i++){ // sdの設定 + struct map_session_data *sd = map_id2sd(p->member[i].account_id); + p->member[i].sd=(sd!=NULL && sd->status.party_id==p->party_id)?sd:NULL; + } + + clif_party_info(p,-1); + + for(i=0;i<MAX_PARTY;i++){ // 設定情報の送信 +// struct map_session_data *sd = map_id2sd(p->member[i].account_id); + struct map_session_data *sd = p->member[i].sd; + if(sd!=NULL && sd->party_sended==0){ + clif_party_option(p,sd,0x100); + sd->party_sended=1; + } + } + + return 0; +} + +// パーティへの勧誘 +int party_invite(struct map_session_data *sd,int account_id) +{ + struct map_session_data *tsd= map_id2sd(account_id); + struct party *p=party_search(sd->status.party_id); + int i; + + nullpo_retr(0, sd); + + if(tsd==NULL || p==NULL) + return 0; + if(!battle_config.invite_request_check) { + if (tsd->guild_invite>0 || tsd->trade_partner) { // 相手が取引中かどうか + clif_party_inviteack(sd,tsd->status.name,0); + return 0; + } + } + if( tsd->status.party_id>0 || tsd->party_invite>0 ){ // 相手の所属確認 + clif_party_inviteack(sd,tsd->status.name,0); + return 0; + } + for(i=0;i<MAX_PARTY;i++){ // 同アカウント確認 + if(p->member[i].account_id==account_id){ + clif_party_inviteack(sd,tsd->status.name,0); + return 0; + } + } + + tsd->party_invite=sd->status.party_id; + tsd->party_invite_account=sd->status.account_id; + + clif_party_invite(sd,tsd); + return 0; +} +// パーティ勧誘への返答 +int party_reply_invite(struct map_session_data *sd,int account_id,int flag) +{ + struct map_session_data *tsd= map_id2sd(account_id); + + nullpo_retr(0, sd); + + if(flag==1){ // 承諾 + //inter鯖へ追加要求 + intif_party_addmember( sd->party_invite, sd->status.account_id ); + return 0; + } + else { // 拒否 + sd->party_invite=0; + sd->party_invite_account=0; + if(tsd==NULL) + return 0; + clif_party_inviteack(tsd,sd->status.name,1); + } + return 0; +} +// パーティが追加された +int party_member_added(int party_id,int account_id,int flag) +{ + struct map_session_data *sd= map_id2sd(account_id),*sd2; + if(sd==NULL && flag==0){ + if(battle_config.error_log) + printf("party: member added error %d is not online\n",account_id); + intif_party_leave(party_id,account_id); // キャラ側に登録できなかったため脱退要求を出す + return 0; + } + sd2=map_id2sd(sd->party_invite_account); + sd->party_invite=0; + sd->party_invite_account=0; + + if(flag==1){ // 失敗 + if( sd2!=NULL ) + clif_party_inviteack(sd2,sd->status.name,0); + return 0; + } + + // 成功 + sd->party_sended=0; + sd->status.party_id=party_id; + + if( sd2!=NULL) + clif_party_inviteack(sd2,sd->status.name,2); + + // いちおう競合確認 + party_check_conflict(sd); + + return 0; +} +// パーティ除名要求 +int party_removemember(struct map_session_data *sd,int account_id,char *name) +{ + struct party *p; + int i; + + nullpo_retr(0, sd); + + if( (p = party_search(sd->status.party_id)) == NULL ) + return 0; + + for(i=0;i<MAX_PARTY;i++){ // リーダーかどうかチェック + if(p->member[i].account_id==sd->status.account_id) + if(p->member[i].leader==0) + return 0; + } + + for(i=0;i<MAX_PARTY;i++){ // 所属しているか調べる + if(p->member[i].account_id==account_id){ + intif_party_leave(p->party_id,account_id); + return 0; + } + } + return 0; +} + +// パーティ脱退要求 +int party_leave(struct map_session_data *sd) +{ + struct party *p; + int i; + + nullpo_retr(0, sd); + + if( (p = party_search(sd->status.party_id)) == NULL ) + return 0; + + for(i=0;i<MAX_PARTY;i++){ // 所属しているか + if(p->member[i].account_id==sd->status.account_id){ + intif_party_leave(p->party_id,sd->status.account_id); + return 0; + } + } + return 0; +} +// パーティメンバが脱退した +int party_member_leaved(int party_id,int account_id,char *name) +{ + struct map_session_data *sd=map_id2sd(account_id); + struct party *p=party_search(party_id); + if(p!=NULL){ + int i; + for(i=0;i<MAX_PARTY;i++) + if(p->member[i].account_id==account_id){ + clif_party_leaved(p,sd,account_id,name,0x00); + p->member[i].account_id=0; + p->member[i].sd=NULL; + } + } + if(sd!=NULL && sd->status.party_id==party_id){ + sd->status.party_id=0; + sd->party_sended=0; + } + return 0; +} +// パーティ解散通知 +int party_broken(int party_id) +{ + struct party *p; + int i; + if( (p=party_search(party_id))==NULL ) + return 0; + + for(i=0;i<MAX_PARTY;i++){ + if(p->member[i].sd!=NULL){ + clif_party_leaved(p,p->member[i].sd, + p->member[i].account_id,p->member[i].name,0x10); + p->member[i].sd->status.party_id=0; + p->member[i].sd->party_sended=0; + } + } + numdb_erase(party_db,party_id); + return 0; +} +// パーティの設定変更要求 +int party_changeoption(struct map_session_data *sd,int exp,int item) +{ + struct party *p; + + nullpo_retr(0, sd); + + if( sd->status.party_id==0 || (p=party_search(sd->status.party_id))==NULL ) + return 0; + intif_party_changeoption(sd->status.party_id,sd->status.account_id,exp,item); + return 0; +} +// パーティの設定変更通知 +int party_optionchanged(int party_id,int account_id,int exp,int item,int flag) +{ + struct party *p; + struct map_session_data *sd=map_id2sd(account_id); + if( (p=party_search(party_id))==NULL) + return 0; + + if(!(flag&0x01)) p->exp=exp; + if(!(flag&0x10)) p->item=item; + clif_party_option(p,sd,flag); + return 0; +} + +// パーティメンバの移動通知 +int party_recv_movemap(int party_id,int account_id,char *map,int online,int lv) +{ + struct party *p; + int i; + if( (p=party_search(party_id))==NULL) + return 0; + for(i=0;i<MAX_PARTY;i++){ + struct party_member *m=&p->member[i]; + if( m == NULL ){ + printf("party_recv_movemap nullpo?\n"); + return 0; + } + if(m->account_id==account_id){ + memcpy(m->map,map,16); + m->online=online; + m->lv=lv; + break; + } + } + if(i==MAX_PARTY){ + if(battle_config.error_log) + printf("party: not found member %d on %d[%s]",account_id,party_id,p->name); + return 0; + } + + for(i=0;i<MAX_PARTY;i++){ // sd再設定 + struct map_session_data *sd= map_id2sd(p->member[i].account_id); + p->member[i].sd=(sd!=NULL && sd->status.party_id==p->party_id)?sd:NULL; + } + + party_send_xy_clear(p); // 座標再通知要請 + + clif_party_info(p,-1); + return 0; +} + +// パーティメンバの移動 +int party_send_movemap(struct map_session_data *sd) +{ + struct party *p; + + nullpo_retr(0, sd); + + if( sd->status.party_id==0 ) + return 0; + intif_party_changemap(sd,1); + + if( sd->party_sended!=0 ) // もうパーティデータは送信済み + return 0; + + // 競合確認 + party_check_conflict(sd); + + // あるならパーティ情報送信 + if( (p=party_search(sd->status.party_id))!=NULL ){ + party_check_member(p); // 所属を確認する + if(sd->status.party_id==p->party_id){ + clif_party_info(p,sd->fd); + clif_party_option(p,sd,0x100); + sd->party_sended=1; + } + } + + return 0; +} +// パーティメンバのログアウト +int party_send_logout(struct map_session_data *sd) +{ + struct party *p; + + nullpo_retr(0, sd); + + if( sd->status.party_id>0 ) + intif_party_changemap(sd,0); + + // sdが無効になるのでパーティ情報から削除 + if( (p=party_search(sd->status.party_id))!=NULL ){ + int i; + for(i=0;i<MAX_PARTY;i++) + if(p->member[i].sd==sd) + p->member[i].sd=NULL; + } + + return 0; +} +// パーティメッセージ送信 +int party_send_message(struct map_session_data *sd,char *mes,int len) +{ + if(sd->status.party_id==0) + return 0; + intif_party_message(sd->status.party_id,sd->status.account_id,mes,len); + return 0; +} + +// パーティメッセージ受信 +int party_recv_message(int party_id,int account_id,char *mes,int len) +{ + struct party *p; + if( (p=party_search(party_id))==NULL) + return 0; + clif_party_message(p,account_id,mes,len); + return 0; +} +// パーティ競合確認 +int party_check_conflict(struct map_session_data *sd) +{ + nullpo_retr(0, sd); + + intif_party_checkconflict(sd->status.party_id,sd->status.account_id,sd->status.name); + return 0; +} + + +// 位置やHP通知用 +int party_send_xyhp_timer_sub(void *key,void *data,va_list ap) +{ + struct party *p=(struct party *)data; + int i; + + nullpo_retr(0, p); + + for(i=0;i<MAX_PARTY;i++){ + struct map_session_data *sd; + if((sd=p->member[i].sd)!=NULL){ + // 座標通知 + if(sd->party_x!=sd->bl.x || sd->party_y!=sd->bl.y){ + clif_party_xy(p,sd); + sd->party_x=sd->bl.x; + sd->party_y=sd->bl.y; + } + // HP通知 + if(sd->party_hp!=sd->status.hp){ + clif_party_hp(p,sd); + sd->party_hp=sd->status.hp; + } + + } + } + return 0; +} +// 位置やHP通知 +int party_send_xyhp_timer(int tid,unsigned int tick,int id,int data) +{ + numdb_foreach(party_db,party_send_xyhp_timer_sub,tick); + return 0; +} + +// 位置通知クリア +int party_send_xy_clear(struct party *p) +{ + int i; + + nullpo_retr(0, p); + + for(i=0;i<MAX_PARTY;i++){ + struct map_session_data *sd; + if((sd=p->member[i].sd)!=NULL){ + sd->party_x=-1; + sd->party_y=-1; + sd->party_hp=-1; + } + } + return 0; +} +// HP通知の必要性検査用(map_foreachinmoveareaから呼ばれる) +int party_send_hp_check(struct block_list *bl,va_list ap) +{ + int party_id; + int *flag; + struct map_session_data *sd; + + nullpo_retr(0, bl); + nullpo_retr(0, ap); + nullpo_retr(0, sd=(struct map_session_data *)bl); + + party_id=va_arg(ap,int); + flag=va_arg(ap,int *); + + if(sd->status.party_id==party_id){ + *flag=1; + sd->party_hp=-1; + } + return 0; +} + +// 経験値公平分配 +int party_exp_share(struct party *p,int map,int base_exp,int job_exp) +{ + struct map_session_data *sd; + int i,c; + + nullpo_retr(0, p); + + for(i=c=0;i<MAX_PARTY;i++) + if((sd=p->member[i].sd)!=NULL && sd->bl.m==map) + c++; + if(c==0) + return 0; + for(i=0;i<MAX_PARTY;i++) + if((sd=p->member[i].sd)!=NULL && sd->bl.m==map) + pc_gainexp(sd,base_exp/c+1,job_exp/c+1); + return 0; +} + +// 同じマップのパーティメンバー全体に処理をかける +// type==0 同じマップ +// !=0 画面内 +void party_foreachsamemap(int (*func)(struct block_list*,va_list), + struct map_session_data *sd,int type,...) +{ + struct party *p; + va_list ap; + int i; + int x0,y0,x1,y1; + struct block_list *list[MAX_PARTY]; + int blockcount=0; + + nullpo_retv(sd); + + if((p=party_search(sd->status.party_id))==NULL) + return; + + x0=sd->bl.x-AREA_SIZE; + y0=sd->bl.y-AREA_SIZE; + x1=sd->bl.x+AREA_SIZE; + y1=sd->bl.y+AREA_SIZE; + + va_start(ap,type); + + for(i=0;i<MAX_PARTY;i++){ + struct party_member *m=&p->member[i]; + if(m->sd!=NULL){ + if(sd->bl.m!=m->sd->bl.m) + continue; + if(type!=0 && + (m->sd->bl.x<x0 || m->sd->bl.y<y0 || + m->sd->bl.x>x1 || m->sd->bl.y>y1 ) ) + continue; + list[blockcount++]=&m->sd->bl; + } + } + + map_freeblock_lock(); // メモリからの解放を禁止する + + for(i=0;i<blockcount;i++) + if(list[i]->prev) // 有効かどうかチェック + func(list[i],ap); + + map_freeblock_unlock(); // 解放を許可する + + va_end(ap); +} diff --git a/src/map/party.h b/src/map/party.h new file mode 100644 index 0000000..28d8096 --- /dev/null +++ b/src/map/party.h @@ -0,0 +1,47 @@ +// $Id: party.h,v 1.3 2004/09/25 05:32:18 MouseJstr Exp $ +#ifndef _PARTY_H_ +#define _PARTY_H_ + +#include <stdarg.h> + +struct party; +struct map_session_data; +struct block_list; + +void do_init_party(void); +void do_final_party(void); +struct party *party_search(int party_id); +struct party* party_searchname(char *str); + +int party_create(struct map_session_data *sd,char *name); +int party_created(int account_id,int fail,int party_id,char *name); +int party_request_info(int party_id); +int party_invite(struct map_session_data *sd,int account_id); +int party_member_added(int party_id,int account_id,int flag); +int party_leave(struct map_session_data *sd); +int party_removemember(struct map_session_data *sd,int account_id,char *name); +int party_member_leaved(int party_id,int account_id,char *name); +int party_reply_invite(struct map_session_data *sd,int account_id,int flag); +int party_recv_noinfo(int party_id); +int party_recv_info(struct party *sp); +int party_recv_movemap(int party_id,int account_id,char *map,int online,int lv); +int party_broken(int party_id); +int party_optionchanged(int party_id,int account_id,int exp,int item,int flag); +int party_changeoption(struct map_session_data *sd,int exp,int item); + +int party_send_movemap(struct map_session_data *sd); +int party_send_logout(struct map_session_data *sd); + +int party_send_message(struct map_session_data *sd,char *mes,int len); +int party_recv_message(int party_id,int account_id,char *mes,int len); + +int party_check_conflict(struct map_session_data *sd); + +int party_send_xy_clear(struct party *p); +int party_send_hp_check(struct block_list *bl,va_list ap); + +int party_exp_share(struct party *p,int map,int base_exp,int job_exp); + +void party_foreachsamemap(int (*func)(struct block_list *,va_list),struct map_session_data *sd,int type,...); + +#endif diff --git a/src/map/path.c b/src/map/path.c new file mode 100644 index 0000000..b2e0a78 --- /dev/null +++ b/src/map/path.c @@ -0,0 +1,404 @@ +// $Id: path.c,v 1.1.1.1 2004/09/10 17:27:00 MagicalTux Exp $ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "map.h" +#include "battle.h" +#include "nullpo.h" + +#ifdef MEMWATCH +#include "memwatch.h" +#endif + +//#define PATH_STANDALONETEST + +#define MAX_HEAP 150 +struct tmp_path { short x,y,dist,before,cost; char dir,flag;}; +#define calc_index(x,y) (((x)+(y)*MAX_WALKPATH) & (MAX_WALKPATH*MAX_WALKPATH-1)) + +/*========================================== + * 経路探索補助heap push + *------------------------------------------ + */ +static void push_heap_path(int *heap,struct tmp_path *tp,int index) +{ + int i,h; + + if( heap == NULL || tp == NULL ){ + printf("push_heap_path nullpo\n"); + return; + } + + heap[0]++; + + for(h=heap[0]-1,i=(h-1)/2; + h>0 && tp[index].cost<tp[heap[i+1]].cost; + i=(h-1)/2) + heap[h+1]=heap[i+1],h=i; + heap[h+1]=index; +} + +/*========================================== + * 経路探索補助heap update + * costが減ったので根の方へ移動 + *------------------------------------------ + */ +static void update_heap_path(int *heap,struct tmp_path *tp,int index) +{ + int i,h; + + nullpo_retv(heap); + nullpo_retv(tp); + + for(h=0;h<heap[0];h++) + if(heap[h+1]==index) + break; + if(h==heap[0]){ + fprintf(stderr,"update_heap_path bug\n"); + exit(1); + } + for(i=(h-1)/2; + h>0 && tp[index].cost<tp[heap[i+1]].cost; + i=(h-1)/2) + heap[h+1]=heap[i+1],h=i; + heap[h+1]=index; +} + +/*========================================== + * 経路探索補助heap pop + *------------------------------------------ + */ +static int pop_heap_path(int *heap,struct tmp_path *tp) +{ + int i,h,k; + int ret,last; + + nullpo_retr(-1, heap); + nullpo_retr(-1, tp); + + if(heap[0]<=0) + return -1; + ret=heap[1]; + last=heap[heap[0]]; + heap[0]--; + + for(h=0,k=2;k<heap[0];k=k*2+2){ + if(tp[heap[k+1]].cost>tp[heap[k]].cost) + k--; + heap[h+1]=heap[k+1], h=k; + } + if(k==heap[0]) + heap[h+1]=heap[k], h=k-1; + + for(i=(h-1)/2; + h>0 && tp[heap[i+1]].cost>tp[last].cost; + i=(h-1)/2) + heap[h+1]=heap[i+1],h=i; + heap[h+1]=last; + + return ret; +} + +/*========================================== + * 現在の点のcost計算 + *------------------------------------------ + */ +static int calc_cost(struct tmp_path *p,int x1,int y1) +{ + int xd,yd; + + nullpo_retr(0, p); + + xd=x1-p->x; + if(xd<0) xd=-xd; + yd=y1-p->y; + if(yd<0) yd=-yd; + return (xd+yd)*10+p->dist; +} + +/*========================================== + * 必要ならpathを追加/修正する + *------------------------------------------ + */ +static int add_path(int *heap,struct tmp_path *tp,int x,int y,int dist,int dir,int before,int x1,int y1) +{ + int i; + + nullpo_retr(0, heap); + nullpo_retr(0, tp); + + i=calc_index(x,y); + + if(tp[i].x==x && tp[i].y==y){ + if(tp[i].dist>dist){ + tp[i].dist=dist; + tp[i].dir=dir; + tp[i].before=before; + tp[i].cost=calc_cost(&tp[i],x1,y1); + if(tp[i].flag) + push_heap_path(heap,tp,i); + else + update_heap_path(heap,tp,i); + tp[i].flag=0; + } + return 0; + } + + if(tp[i].x || tp[i].y) + return 1; + + tp[i].x=x; + tp[i].y=y; + tp[i].dist=dist; + tp[i].dir=dir; + tp[i].before=before; + tp[i].cost=calc_cost(&tp[i],x1,y1); + tp[i].flag=0; + push_heap_path(heap,tp,i); + + return 0; +} + + +/*========================================== + * (x,y)が移動不可能地帯かどうか + * flag 0x10000 遠距離攻撃判定 + *------------------------------------------ + */ +static int can_place(struct map_data *m,int x,int y,int flag) +{ + int c; + + nullpo_retr(0, m); + + c=read_gatp(m,x,y); + + if(c==1) + return 0; + if(!(flag&0x10000) && c==5) + return 0; + return 1; +} + +/*========================================== + * (x0,y0)から(x1,y1)へ1歩で移動可能か計算 + *------------------------------------------ + */ +static int can_move(struct map_data *m,int x0,int y0,int x1,int y1,int flag) +{ + nullpo_retr(0, m); + + if(x0-x1<-1 || x0-x1>1 || y0-y1<-1 || y0-y1>1) + return 0; + if(x1<0 || y1<0 || x1>=m->xs || y1>=m->ys) + return 0; + if(!can_place(m,x0,y0,flag)) + return 0; + if(!can_place(m,x1,y1,flag)) + return 0; + if(x0==x1 || y0==y1) + return 1; + if(!can_place(m,x0,y1,flag) || !can_place(m,x1,y0,flag)) + return 0; + return 1; +} +/*========================================== + * (x0,y0)から(dx,dy)方向へcountセル分 + * 吹き飛ばしたあとの座標を所得 + *------------------------------------------ + */ +int path_blownpos(int m,int x0,int y0,int dx,int dy,int count) +{ + struct map_data *md; + + if(!map[m].gat) + return -1; + md=&map[m]; + + if(count>15){ // 最大10マスに制限 + if(battle_config.error_log) + printf("path_blownpos: count too many %d !\n",count); + count=15; + } + if(dx>1 || dx<-1 || dy>1 || dy<-1){ + if(battle_config.error_log) + printf("path_blownpos: illeagal dx=%d or dy=%d !\n",dx,dy); + dx=(dx>=0)?1:((dx<0)?-1:0); + dy=(dy>=0)?1:((dy<0)?-1:0); + } + + while( (count--)>0 && (dx!=0 || dy!=0) ){ + if( !can_move(md,x0,y0,x0+dx,y0+dy,0) ){ + int fx=(dx!=0 && can_move(md,x0,y0,x0+dx,y0,0)); + int fy=(dy!=0 && can_move(md,x0,y0,x0,y0+dy,0)); + if( fx && fy ){ + if(rand()&1) dx=0; + else dy=0; + } + if( !fx ) dx=0; + if( !fy ) dy=0; + } + x0+=dx; + y0+=dy; + } + return (x0<<16)|y0; +} + +/*========================================== + * path探索 (x0,y0)->(x1,y1) + *------------------------------------------ + */ +int path_search(struct walkpath_data *wpd,int m,int x0,int y0,int x1,int y1,int flag) +{ + int heap[MAX_HEAP+1]; + struct tmp_path tp[MAX_WALKPATH*MAX_WALKPATH]; + int i,rp,x,y; + struct map_data *md; + int dx,dy; + + nullpo_retr(0, wpd); + + if(!map[m].gat) + return -1; + md=&map[m]; + if(x1<0 || x1>=md->xs || y1<0 || y1>=md->ys || (i=read_gatp(md,x1,y1))==1 || i==5) + return -1; + + // easy + dx = (x1-x0<0) ? -1 : 1; + dy = (y1-y0<0) ? -1 : 1; + for(x=x0,y=y0,i=0;x!=x1 || y!=y1;){ + if(i>=sizeof(wpd->path)) + return -1; + if(x!=x1 && y!=y1){ + if(!can_move(md,x,y,x+dx,y+dy,flag)) + break; + x+=dx; + y+=dy; + wpd->path[i++]=(dx<0) ? ((dy>0)? 1 : 3) : ((dy<0)? 5 : 7); + } else if(x!=x1){ + if(!can_move(md,x,y,x+dx,y ,flag)) + break; + x+=dx; + wpd->path[i++]=(dx<0) ? 2 : 6; + } else { // y!=y1 + if(!can_move(md,x,y,x ,y+dy,flag)) + break; + y+=dy; + wpd->path[i++]=(dy>0) ? 0 : 4; + } + if(x==x1 && y==y1){ + wpd->path_len=i; + wpd->path_pos=0; + wpd->path_half=0; + return 0; + } + } + if(flag&1) + return -1; + + memset(tp,0,sizeof(tp)); + + i=calc_index(x0,y0); + tp[i].x=x0; + tp[i].y=y0; + tp[i].dist=0; + tp[i].dir=0; + tp[i].before=0; + tp[i].cost=calc_cost(&tp[i],x1,y1); + tp[i].flag=0; + heap[0]=0; + push_heap_path(heap,tp,calc_index(x0,y0)); + while(1){ + int e=0,fromdir; + + if(heap[0]==0) + return -1; + rp=pop_heap_path(heap,tp); + x=tp[rp].x; + y=tp[rp].y; + if(x==x1 && y==y1){ + int len,j; + + for(len=0,i=rp;len<100 && i!=calc_index(x0,y0);i=tp[i].before,len++); + if(len==100 || len>=sizeof(wpd->path)) + return -1; + wpd->path_len=len; + wpd->path_pos=0; + wpd->path_half=0; + for(i=rp,j=len-1;j>=0;i=tp[i].before,j--) + wpd->path[j]=tp[i].dir; + + return 0; + } + fromdir=tp[rp].dir; + if(can_move(md,x,y,x+1,y-1,flag)) + e+=add_path(heap,tp,x+1,y-1,tp[rp].dist+14,5,rp,x1,y1); + if(can_move(md,x,y,x+1,y ,flag)) + e+=add_path(heap,tp,x+1,y ,tp[rp].dist+10,6,rp,x1,y1); + if(can_move(md,x,y,x+1,y+1,flag)) + e+=add_path(heap,tp,x+1,y+1,tp[rp].dist+14,7,rp,x1,y1); + if(can_move(md,x,y,x ,y+1,flag)) + e+=add_path(heap,tp,x ,y+1,tp[rp].dist+10,0,rp,x1,y1); + if(can_move(md,x,y,x-1,y+1,flag)) + e+=add_path(heap,tp,x-1,y+1,tp[rp].dist+14,1,rp,x1,y1); + if(can_move(md,x,y,x-1,y ,flag)) + e+=add_path(heap,tp,x-1,y ,tp[rp].dist+10,2,rp,x1,y1); + if(can_move(md,x,y,x-1,y-1,flag)) + e+=add_path(heap,tp,x-1,y-1,tp[rp].dist+14,3,rp,x1,y1); + if(can_move(md,x,y,x ,y-1,flag)) + e+=add_path(heap,tp,x ,y-1,tp[rp].dist+10,4,rp,x1,y1); + tp[rp].flag=1; + if(e || heap[0]>=MAX_HEAP-5) + return -1; + } + return -1; +} + +#ifdef PATH_STANDALONETEST +char gat[64][64]={ + {0,0,0,0,0,0,0,0,0,0}, + {0,0,0,0,0,0,0,0,0,0}, + {0,0,0,0,0,0,0,0,0,0}, + {0,0,0,0,0,0,0,0,0,0}, + {0,0,0,0,1,0,0,0,0,0}, +}; +struct map_data map[1]; + +/*========================================== + * 経路探索ルーチン単体テスト用main関数 + *------------------------------------------ + */ +void main(int argc,char *argv[]) +{ + struct walkpath_data wpd; + + map[0].gat=gat; + map[0].xs=64; + map[0].ys=64; + + path_search(&wpd,0,3,4,5,4); + path_search(&wpd,0,5,4,3,4); + path_search(&wpd,0,6,4,3,4); + path_search(&wpd,0,7,4,3,4); + path_search(&wpd,0,4,3,4,5); + path_search(&wpd,0,4,2,4,5); + path_search(&wpd,0,4,1,4,5); + path_search(&wpd,0,4,5,4,3); + path_search(&wpd,0,4,6,4,3); + path_search(&wpd,0,4,7,4,3); + path_search(&wpd,0,7,4,3,4); + path_search(&wpd,0,8,4,3,4); + path_search(&wpd,0,9,4,3,4); + path_search(&wpd,0,10,4,3,4); + path_search(&wpd,0,11,4,3,4); + path_search(&wpd,0,12,4,3,4); + path_search(&wpd,0,13,4,3,4); + path_search(&wpd,0,14,4,3,4); + path_search(&wpd,0,15,4,3,4); + path_search(&wpd,0,16,4,3,4); + path_search(&wpd,0,17,4,3,4); + path_search(&wpd,0,18,4,3,4); +} +#endif diff --git a/src/map/pc.c b/src/map/pc.c new file mode 100644 index 0000000..4e702c0 --- /dev/null +++ b/src/map/pc.c @@ -0,0 +1,7485 @@ +// $Id: pc.c 101 2004-09-25 17:57:22Z Valaris $ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include <time.h> + +#include "socket.h" // [Valaris] +#include "timer.h" +#include "db.h" + +#include "malloc.h" +#include "map.h" +#include "chrif.h" +#include "clif.h" +#include "intif.h" +#include "pc.h" +#include "npc.h" +#include "mob.h" +#include "pet.h" +#include "itemdb.h" +#include "script.h" +#include "battle.h" +#include "skill.h" +#include "party.h" +#include "guild.h" +#include "chat.h" +#include "trade.h" +#include "storage.h" +#include "vending.h" +#include "nullpo.h" +#include "atcommand.h" + +#ifndef TXT_ONLY // mail system [Valaris] +#include "mail.h" +#endif + +#ifdef MEMWATCH +#include "memwatch.h" +#endif + +#define PVP_CALCRANK_INTERVAL 1000 // PVP順位計算の間隔 + +#define STATE_BLIND 0x10 + +static int max_weight_base[MAX_PC_CLASS]; +static int hp_coefficient[MAX_PC_CLASS]; +static int hp_coefficient2[MAX_PC_CLASS]; +static int hp_sigma_val[MAX_PC_CLASS][MAX_LEVEL]; +static int sp_coefficient[MAX_PC_CLASS]; +static int aspd_base[MAX_PC_CLASS][20]; +static char job_bonus[3][MAX_PC_CLASS][MAX_LEVEL]; +static int exp_table[14][MAX_LEVEL]; +static char statp[255][7]; +static struct { + int id; + int max; + struct { + short id,lv; + } need[6]; +} skill_tree[3][MAX_PC_CLASS][100]; + +static int atkmods[3][20]; // 武器ATKサイズ修正(size_fix.txt) +static int refinebonus[5][3]; // 精錬ボーナステーブル(refine_db.txt) +static int percentrefinery[5][10]; // 精錬成功率(refine_db.txt) + +static int dirx[8]={0,-1,-1,-1,0,1,1,1}; +static int diry[8]={1,1,0,-1,-1,-1,0,1}; + +static unsigned int equip_pos[11]={0x0080,0x0008,0x0040,0x0004,0x0001,0x0200,0x0100,0x0010,0x0020,0x0002,0x8000}; + +//static struct dbt *gm_account_db; +static struct gm_account *gm_account = NULL; +static int GM_num = 0; + +int pc_isGM(struct map_session_data *sd) { +// struct gm_account *p; + int i; + + nullpo_retr(0, sd); + +/* p = numdb_search(gm_account_db, sd->status.account_id); + if (p == NULL) + return 0; + return p->level;*/ + + for(i = 0; i < GM_num; i++) + if (gm_account[i].account_id == sd->status.account_id) + return gm_account[i].level; + return 0; + +} + +int pc_iskiller(struct map_session_data *src, struct map_session_data *target) { + nullpo_retr(0, src); + + if(src->bl.type!=BL_PC ) + return 0; + if (src->special_state.killer) + return 1; + + if(target->bl.type!=BL_PC ) + return 0; + if (target->special_state.killable) + return 1; + + return 0; +} + + +int pc_set_gm_level(int account_id, int level) { + int i; + for (i = 0; i < GM_num; i++) { + if (account_id == gm_account[i].account_id) { + gm_account[i].level = level; + return 0; + } + } + + GM_num++; + gm_account = realloc(gm_account, sizeof(struct gm_account) * GM_num); + gm_account[GM_num - 1].account_id = account_id; + gm_account[GM_num - 1].level = level; + return 0; +} + +int pc_getrefinebonus(int lv, int type) { + if (lv >= 0 && lv < 5 && type >= 0 && type < 3) + return refinebonus[lv][type]; + return 0; +} + +static int distance(int x0, int y0, int x1, int y1) { + int dx, dy; + + dx = abs(x0-x1); + dy = abs(y0-y1); + return dx>dy ? dx : dy; +} + +static int pc_invincible_timer(int tid,unsigned int tick,int id,int data) { + struct map_session_data *sd; + + if( (sd=(struct map_session_data *)map_id2sd(id)) == NULL || sd->bl.type!=BL_PC ) + return 1; + + if(sd->invincible_timer != tid){ + if(battle_config.error_log) + printf("invincible_timer %d != %d\n",sd->invincible_timer,tid); + return 0; + } + sd->invincible_timer=-1; + + return 0; +} + +int pc_setinvincibletimer(struct map_session_data *sd,int val) { + nullpo_retr(0, sd); + + if(sd->invincible_timer != -1) + delete_timer(sd->invincible_timer,pc_invincible_timer); + sd->invincible_timer = add_timer(gettick()+val,pc_invincible_timer,sd->bl.id,0); + return 0; +} + +int pc_delinvincibletimer(struct map_session_data *sd) { + nullpo_retr(0, sd); + + if(sd->invincible_timer != -1) { + delete_timer(sd->invincible_timer,pc_invincible_timer); + sd->invincible_timer = -1; + } + return 0; +} + +static int pc_spiritball_timer(int tid,unsigned int tick,int id,int data) { + struct map_session_data *sd; + int i; + + if( (sd=(struct map_session_data *)map_id2sd(id)) == NULL || sd->bl.type!=BL_PC ) + return 1; + + if(sd->spirit_timer[0] != tid){ + if(battle_config.error_log) + printf("spirit_timer %d != %d\n",sd->spirit_timer[0],tid); + return 0; + } + sd->spirit_timer[0]=-1; + for(i=1;i<sd->spiritball;i++) { + sd->spirit_timer[i-1] = sd->spirit_timer[i]; + sd->spirit_timer[i] = -1; + } + sd->spiritball--; + if(sd->spiritball < 0) + sd->spiritball = 0; + clif_spiritball(sd); + + return 0; +} + +int pc_addspiritball(struct map_session_data *sd,int interval,int max) { + int i; + + nullpo_retr(0, sd); + + if(max > MAX_SKILL_LEVEL) + max = MAX_SKILL_LEVEL; + if(sd->spiritball < 0) + sd->spiritball = 0; + + if(sd->spiritball >= max) { + if(sd->spirit_timer[0] != -1) { + delete_timer(sd->spirit_timer[0],pc_spiritball_timer); + sd->spirit_timer[0] = -1; + } + for(i=1;i<max;i++) { + sd->spirit_timer[i-1] = sd->spirit_timer[i]; + sd->spirit_timer[i] = -1; + } + } + else + sd->spiritball++; + + sd->spirit_timer[sd->spiritball-1] = add_timer(gettick()+interval,pc_spiritball_timer,sd->bl.id,0); + clif_spiritball(sd); + + return 0; +} + +int pc_delspiritball(struct map_session_data *sd,int count,int type) { + int i; + + nullpo_retr(0, sd); + + if(sd->spiritball <= 0) { + sd->spiritball = 0; + return 0; + } + + if(count > sd->spiritball) + count = sd->spiritball; + sd->spiritball -= count; + if(count > MAX_SKILL_LEVEL) + count = MAX_SKILL_LEVEL; + + for(i=0;i<count;i++) { + if(sd->spirit_timer[i] != -1) { + delete_timer(sd->spirit_timer[i],pc_spiritball_timer); + sd->spirit_timer[i] = -1; + } + } + for(i=count;i<MAX_SKILL_LEVEL;i++) { + sd->spirit_timer[i-count] = sd->spirit_timer[i]; + sd->spirit_timer[i] = -1; + } + + if(!type) + clif_spiritball(sd); + + return 0; +} + +int pc_setrestartvalue(struct map_session_data *sd,int type) { + //転生や養子の場合の元の職業を算出する + struct pc_base_job s_class; + + nullpo_retr(0, sd); + + s_class = pc_calc_base_job(sd->status.class); + + //----------------------- + // 死亡した + if(sd->special_state.restart_full_recover) { // オシリスカード + sd->status.hp=sd->status.max_hp; + sd->status.sp=sd->status.max_sp; + } + else { + if(s_class.job == 0 && battle_config.restart_hp_rate < 50) { //ノビは半分回復 + sd->status.hp=(sd->status.max_hp)/2; + } + else { + if(battle_config.restart_hp_rate <= 0) + sd->status.hp = 1; + else { + sd->status.hp = sd->status.max_hp * battle_config.restart_hp_rate /100; + if(sd->status.hp <= 0) + sd->status.hp = 1; + } + } + if(battle_config.restart_sp_rate > 0) { + int sp = sd->status.max_sp * battle_config.restart_sp_rate /100; + if(sd->status.sp < sp) + sd->status.sp = sp; + } + } + if(type&1) + clif_updatestatus(sd,SP_HP); + if(type&1) + clif_updatestatus(sd,SP_SP); + + /* removed exp penalty on spawn [Valaris] */ + + if(type&2 && sd->status.class != 0 && battle_config.zeny_penalty > 0 && !map[sd->bl.m].flag.nozenypenalty) { + int zeny = (int)((double)sd->status.zeny * (double)battle_config.zeny_penalty / 10000.); + if(zeny < 1) zeny = 1; + sd->status.zeny -= zeny; + if(sd->status.zeny < 0) sd->status.zeny = 0; + clif_updatestatus(sd,SP_ZENY); + } + + return 0; +} + +/*========================================== + * 自分をロックしているMOBの数を数える(foreachclient) + *------------------------------------------ + */ +static int pc_counttargeted_sub(struct block_list *bl,va_list ap) +{ + int id,*c,target_lv; + struct block_list *src; + + nullpo_retr(0, bl); + nullpo_retr(0, ap); + + id=va_arg(ap,int); + + nullpo_retr(0, c=va_arg(ap,int *)); + + src=va_arg(ap,struct block_list *); + target_lv=va_arg(ap,int); + if(id == bl->id || (src && id == src->id)) return 0; + if(bl->type == BL_PC) { + struct map_session_data *sd=(struct map_session_data *)bl; + if( sd && sd->attacktarget == id && sd->attacktimer != -1 && sd->attacktarget_lv >= target_lv) + (*c)++; + } + else if(bl->type == BL_MOB) { + struct mob_data *md = (struct mob_data *)bl; + if(md && md->target_id == id && md->timer != -1 && md->state.state == MS_ATTACK && md->target_lv >= target_lv) + + (*c)++; + //printf("md->target_lv:%d, target_lv:%d\n",((struct mob_data *)bl)->target_lv,target_lv); + } + return 0; +} + +int pc_counttargeted(struct map_session_data *sd,struct block_list *src,int target_lv) +{ + int c=0; + map_foreachinarea(pc_counttargeted_sub, sd->bl.m, + sd->bl.x-AREA_SIZE,sd->bl.y-AREA_SIZE, + sd->bl.x+AREA_SIZE,sd->bl.y+AREA_SIZE,0,sd->bl.id,&c,src,target_lv); + return c; +} + +/*========================================== + * ローカルプロトタイプ宣言 (必要な物のみ) + *------------------------------------------ + */ +static int pc_walktoxy_sub(struct map_session_data *); + +/*========================================== + * saveに必要なステータス修正を行なう + *------------------------------------------ + */ +int pc_makesavestatus(struct map_session_data *sd) +{ + nullpo_retr(0, sd); + + // 服の色は色々弊害が多いので保存対象にはしない + if(!battle_config.save_clothcolor) + sd->status.clothes_color=0; + + // 死亡状態だったのでhpを1、位置をセーブ場所に変更 + if(pc_isdead(sd)){ + pc_setrestartvalue(sd,0); + memcpy(&sd->status.last_point,&sd->status.save_point,sizeof(sd->status.last_point)); + } else { + memcpy(sd->status.last_point.map,sd->mapname,24); + sd->status.last_point.x = sd->bl.x; + sd->status.last_point.y = sd->bl.y; + } + + // セーブ禁止マップだったので指定位置に移動 + if(map[sd->bl.m].flag.nosave){ + struct map_data *m=&map[sd->bl.m]; + if(strcmp(m->save.map,"SavePoint")==0) + memcpy(&sd->status.last_point,&sd->status.save_point,sizeof(sd->status.last_point)); + else + memcpy(&sd->status.last_point,&m->save,sizeof(sd->status.last_point)); + } + + //マナーポイントがプラスだった場合0に + if(battle_config.muting_players && sd->status.manner > 0) + sd->status.manner = 0; + return 0; +} + +/*========================================== + * 接続時の初期化 + *------------------------------------------ + */ +int pc_setnewpc(struct map_session_data *sd, int account_id, int char_id, int login_id1, int client_tick, int sex, int fd) { + nullpo_retr(0, sd); + + sd->bl.id = account_id; + sd->char_id = char_id; + sd->login_id1 = login_id1; + sd->login_id2 = 0; // at this point, we can not know the value :( + sd->client_tick = client_tick; + sd->sex = sex; + sd->state.auth = 0; + sd->bl.type = BL_PC; + sd->canact_tick = sd->canmove_tick = gettick(); + sd->canlog_tick = gettick(); + sd->state.waitingdisconnect = 0; + + return 0; +} + +int pc_equippoint(struct map_session_data *sd,int n) +{ + int ep = 0; + //転生や養子の場合の元の職業を算出する + struct pc_base_job s_class; + + nullpo_retr(0, sd); + + s_class = pc_calc_base_job(sd->status.class); + + if(sd->inventory_data[n]) { + ep = sd->inventory_data[n]->equip; + if(sd->inventory_data[n]->look == 1 || sd->inventory_data[n]->look == 2 || sd->inventory_data[n]->look == 6) { + if(ep == 2 && (pc_checkskill(sd,AS_LEFT) > 0 || s_class.job == 12)) + return 34; + } + } + return ep; +} + +int pc_setinventorydata(struct map_session_data *sd) +{ + int i,id; + + nullpo_retr(0, sd); + + for(i=0;i<MAX_INVENTORY;i++) { + id = sd->status.inventory[i].nameid; + sd->inventory_data[i] = itemdb_search(id); + } + return 0; +} + +int pc_calcweapontype(struct map_session_data *sd) +{ + nullpo_retr(0, sd); + + if(sd->weapontype1 != 0 && sd->weapontype2 == 0) + sd->status.weapon = sd->weapontype1; + if(sd->weapontype1 == 0 && sd->weapontype2 != 0)// 左手武器 Only + sd->status.weapon = sd->weapontype2; + else if(sd->weapontype1 == 1 && sd->weapontype2 == 1)// 双短剣 + sd->status.weapon = 0x11; + else if(sd->weapontype1 == 2 && sd->weapontype2 == 2)// 双単手剣 + sd->status.weapon = 0x12; + else if(sd->weapontype1 == 6 && sd->weapontype2 == 6)// 双単手斧 + sd->status.weapon = 0x13; + else if( (sd->weapontype1 == 1 && sd->weapontype2 == 2) || + (sd->weapontype1 == 2 && sd->weapontype2 == 1) ) // 短剣 - 単手剣 + sd->status.weapon = 0x14; + else if( (sd->weapontype1 == 1 && sd->weapontype2 == 6) || + (sd->weapontype1 == 6 && sd->weapontype2 == 1) ) // 短剣 - 斧 + sd->status.weapon = 0x15; + else if( (sd->weapontype1 == 2 && sd->weapontype2 == 6) || + (sd->weapontype1 == 6 && sd->weapontype2 == 2) ) // 単手剣 - 斧 + sd->status.weapon = 0x16; + else + sd->status.weapon = sd->weapontype1; + + return 0; +} + +int pc_setequipindex(struct map_session_data *sd) +{ + int i,j; + + nullpo_retr(0, sd); + + for(i=0;i<11;i++) + sd->equip_index[i] = -1; + + for(i=0;i<MAX_INVENTORY;i++) { + if(sd->status.inventory[i].nameid <= 0) + continue; + if(sd->status.inventory[i].equip) { + for(j=0;j<11;j++) + if(sd->status.inventory[i].equip & equip_pos[j]) + sd->equip_index[j] = i; + if(sd->status.inventory[i].equip & 0x0002) { + if(sd->inventory_data[i]) + sd->weapontype1 = sd->inventory_data[i]->look; + else + sd->weapontype1 = 0; + } + if(sd->status.inventory[i].equip & 0x0020) { + if(sd->inventory_data[i]) { + if(sd->inventory_data[i]->type == 4) { + if(sd->status.inventory[i].equip == 0x0020) + sd->weapontype2 = sd->inventory_data[i]->look; + else + sd->weapontype2 = 0; + } + else + sd->weapontype2 = 0; + } + else + sd->weapontype2 = 0; + } + } + } + pc_calcweapontype(sd); + + return 0; +} + +int pc_isequip(struct map_session_data *sd,int n) +{ + struct item_data *item; + struct status_change *sc_data; + //転生や養子の場合の元の職業を算出する + + nullpo_retr(0, sd); + + item = sd->inventory_data[n]; + sc_data = battle_get_sc_data(&sd->bl); + //s_class = pc_calc_base_job(sd->status.class); + + if( battle_config.gm_allequip>0 && pc_isGM(sd)>=battle_config.gm_allequip ) + return 1; + + if(item == NULL) + return 0; + if(item->sex != 2 && sd->status.sex != item->sex) + return 0; + if(item->elv > 0 && sd->status.base_level < item->elv) + return 0; +// -- moonsoul (below statement substituted for commented out version further below +// as it allows all advanced classes to equip items their normal versions +// could equip) +// + if(((sd->status.class==13 || sd->status.class==4014) && ((1<<7)&item->class) == 0) || // have mounted classes use unmounted equipment [Valaris] + ((sd->status.class==21 || sd->status.class==4022) && ((1<<14)&item->class) == 0)) + return 0; + if(sd->status.class!=13 && sd->status.class!=4014 && sd->status.class!=21 && sd->status.class!=4022) + if((sd->status.class<=4000 && ((1<<sd->status.class)&item->class) == 0) || (sd->status.class>4000 && sd->status.class<4023 && ((1<<(sd->status.class-4001))&item->class) == 0) || + (sd->status.class>=4023 && ((1<<(sd->status.class-4023))&item->class) == 0)) + return 0; +// if(((1<<sd->status.class)&item->class) == 0) +// return 0; + if(map[sd->bl.m].flag.pvp && (item->flag.no_equip==1 || item->flag.no_equip==3)) + return 0; + if(map[sd->bl.m].flag.gvg && (item->flag.no_equip==2 || item->flag.no_equip==3)) + return 0; + if(item->equip & 0x0002 && sc_data && sc_data[SC_STRIPWEAPON].timer != -1) + return 0; + if(item->equip & 0x0020 && sc_data && sc_data[SC_STRIPSHIELD].timer != -1) + return 0; + if(item->equip & 0x0010 && sc_data && sc_data[SC_STRIPARMOR].timer != -1) + return 0; + if(item->equip & 0x0100 && sc_data && sc_data[SC_STRIPHELM].timer != -1) + return 0; + return 1; +} + +/*========================================== + * Weapon Breaking [Valaris] + *------------------------------------------ + */ +int pc_breakweapon(struct map_session_data *sd) +{ + struct item_data* item; + char output[255]; + int i; + + if(sd==NULL) + return -1; + if(sd->unbreakable>=rand()%100) + return 0; + if(sd->sc_data && sd->sc_data[SC_CP_WEAPON].timer != -1) + return 0; + + for(i=0;i<MAX_INVENTORY;i++){ + if(sd->status.inventory[i].equip && sd->status.inventory[i].equip & 0x0002 && !sd->status.inventory[i].broken){ + item=sd->inventory_data[i]; + sd->status.inventory[i].broken=1; + //pc_unequipitem(sd,i,0); + if(sd->status.inventory[i].equip && sd->status.inventory[i].equip & 0x0002 && + sd->status.inventory[i].broken==1){ + sprintf(output, "%s has broken.",item->jname); + clif_emotion(&sd->bl,23); + clif_displaymessage(sd->fd, output); + clif_equiplist(sd); + skill_status_change_start(&sd->bl,SC_BROKNWEAPON,0,0,0,0,0,0); + } + } + if(sd->status.inventory[i].broken==1) + return 0; + } + + return 0; +} +/*========================================== + * Armor Breaking [Valaris] + *------------------------------------------ + */ +int pc_breakarmor(struct map_session_data *sd) +{ + struct item_data* item; + char output[255]; + int i; + + if(sd==NULL) + return -1; + if(sd->unbreakable>=rand()%100) + return 0; + if(sd->sc_data && sd->sc_data[SC_CP_ARMOR].timer != -1) + return 0; + + for(i=0;i<MAX_INVENTORY;i++){ + if(sd->status.inventory[i].equip && sd->status.inventory[i].equip & 0x0010 && !sd->status.inventory[i].broken){ + item=sd->inventory_data[i]; + sd->status.inventory[i].broken=1; + //pc_unequipitem(sd,i,0); + if(sd->status.inventory[i].equip && sd->status.inventory[i].equip & 0x0010 && + sd->status.inventory[i].broken==1){ + sprintf(output, "%s has broken.",item->jname); + clif_emotion(&sd->bl,23); + clif_displaymessage(sd->fd, output); + clif_equiplist(sd); + skill_status_change_start(&sd->bl,SC_BROKNARMOR,0,0,0,0,0,0); + } + } + if(sd->status.inventory[i].broken==1) + return 0; + } + return 0; +} +/*========================================== + * session idに問題無し + * char鯖から送られてきたステータスを設定 + *------------------------------------------ + */ +int pc_authok(int id, int login_id2, time_t connect_until_time, struct mmo_charstatus *st) +{ + struct map_session_data *sd = NULL; + + struct party *p; + struct guild *g; + int i; + unsigned long tick = gettick(); + + sd = map_id2sd(id); + if(sd==NULL) + return 1; + + sd->login_id2 = login_id2; + + memcpy(&sd->status, st, sizeof(*st)); + + if (sd->status.sex != sd->sex) { + clif_authfail_fd(sd->fd, 0); + return 1; + } + + memset(&sd->state, 0, sizeof(sd->state)); + // 基本的な初期化 + sd->state.connect_new = 1; + sd->bl.prev = sd->bl.next = NULL; + + sd->weapontype1 = sd->weapontype2 = 0; + sd->view_class = sd->status.class; + sd->speed = DEFAULT_WALK_SPEED; + sd->state.dead_sit = 0; + sd->dir = 0; + sd->head_dir = 0; + sd->state.auth = 1; + sd->walktimer = -1; + sd->attacktimer = -1; + sd->followtimer = -1; // [MouseJstr] + sd->skilltimer = -1; + sd->skillitem = -1; + sd->skillitemlv = -1; + sd->invincible_timer = -1; + sd->sg_count = 0; + + sd->deal_locked = 0; + sd->trade_partner = 0; + + sd->inchealhptick = 0; + sd->inchealsptick = 0; + sd->hp_sub = 0; + sd->sp_sub = 0; + sd->inchealspirithptick = 0; + sd->inchealspiritsptick = 0; + sd->canact_tick = tick; + sd->canmove_tick = tick; + sd->attackabletime = tick; + + sd->doridori_counter = 0; + +#ifndef TXT_ONLY // mail system [Valaris] + if(battle_config.mail_system) + sd->mail_counter = 0; +#endif + sd->spiritball = 0; + for(i = 0; i < MAX_SKILL_LEVEL; i++) + sd->spirit_timer[i] = -1; + for(i = 0; i < MAX_SKILLTIMERSKILL; i++) + sd->skilltimerskill[i].timer = -1; + + memset(&sd->dev,0,sizeof(struct square)); + for(i = 0; i < 5; i++) { + sd->dev.val1[i] = 0; + sd->dev.val2[i] = 0; + } + + // アカウント変数の送信要求 + intif_request_accountreg(sd); + + // アイテムチェック + pc_setinventorydata(sd); + pc_checkitem(sd); + + // pet + sd->petDB = NULL; + sd->pd = NULL; + sd->pet_hungry_timer = -1; + memset(&sd->pet, 0, sizeof(struct s_pet)); + + // ステータス異常の初期化 + for(i = 0; i < MAX_STATUSCHANGE; i++) { + sd->sc_data[i].timer=-1; + sd->sc_data[i].val1 = sd->sc_data[i].val2 = sd->sc_data[i].val3 = sd->sc_data[i].val4 = 0; + } + sd->sc_count=0; + if ((battle_config.atc_gmonly == 0 || pc_isGM(sd)) && + (pc_isGM(sd) >= get_atcommand_level(AtCommand_Hide))) + sd->status.option &= (OPTION_MASK | OPTION_HIDE); + else + sd->status.option &= OPTION_MASK; + + // スキルユニット関係の初期化 + memset(sd->skillunit, 0, sizeof(sd->skillunit)); + memset(sd->skillunittick, 0, sizeof(sd->skillunittick)); + + // init ignore list + memset(sd->ignore, 0, sizeof(sd->ignore)); + + // パーティー関係の初期化 + sd->party_sended = 0; + sd->party_invite = 0; + sd->party_x = -1; + sd->party_y = -1; + sd->party_hp = -1; + + // ギルド関係の初期化 + sd->guild_sended = 0; + sd->guild_invite = 0; + sd->guild_alliance = 0; + + // イベント関係の初期化 + memset(sd->eventqueue, 0, sizeof(sd->eventqueue)); + for(i = 0; i < MAX_EVENTTIMER; i++) + sd->eventtimer[i] = -1; + + // 位置の設定 + pc_setpos(sd,sd->status.last_point.map, sd->status.last_point.x, sd->status.last_point.y, 0); + + // pet + if (sd->status.pet_id > 0) + intif_request_petdata(sd->status.account_id, sd->status.char_id, sd->status.pet_id); + + // パーティ、ギルドデータの要求 + if (sd->status.party_id > 0 && (p = party_search(sd->status.party_id)) == NULL) + party_request_info(sd->status.party_id); + if (sd->status.guild_id > 0 && (g = guild_search(sd->status.guild_id)) == NULL) + guild_request_info(sd->status.guild_id); + + // pvpの設定 + sd->pvp_rank = 0; + sd->pvp_point = 0; + sd->pvp_timer = -1; + + // 通知 + + clif_authok(sd); + map_addnickdb(sd); + if (map_charid2nick(sd->status.char_id) == NULL) + map_addchariddb(sd->status.char_id, sd->status.name); + + //スパノビ用死にカウンターのスクリプト変数からの読み出しとsdへのセット + sd->die_counter = pc_readglobalreg(sd,"PC_DIE_COUNTER"); + + if (night_flag == 1) { + char tmpstr[1024]; + strcpy(tmpstr, msg_txt(500)); // Actually, it's the night... + clif_wis_message(sd->fd, wisp_server_name, tmpstr, strlen(tmpstr)+1); + sd->opt2 |= STATE_BLIND; + } + + // ステータス初期計算など + pc_calcstatus(sd,1); + + if (pc_isGM(sd)) + printf("Connection accepted: character '%s' (account: %d; GM level %d).\n", sd->status.name, sd->status.account_id, pc_isGM(sd)); + else + printf("Connection accepted: Character '%s' (account: %d).\n", sd->status.name, sd->status.account_id); + + // Message of the Dayの送信 + { + char buf[256]; + FILE *fp; + if ((fp = fopen(motd_txt, "r")) != NULL) { + while (fgets(buf, sizeof(buf)-1, fp) != NULL) { + int i; + for(i=0; buf[i]; i++) { + if (buf[i] == '\r' || buf[i]== '\n') { + buf[i] = 0; + break; + } + } + clif_displaymessage(sd->fd, buf); + } + fclose(fp); + } + } + +#ifndef TXT_ONLY + if(battle_config.mail_system) + mail_check(sd,1); // check mail at login [Valaris] +#endif + + // message of the limited time of the account + if (connect_until_time != 0) { // don't display if it's unlimited or unknow value + char tmpstr[1024]; + strftime(tmpstr, sizeof(tmpstr) - 1, msg_txt(501), localtime(&connect_until_time)); // "Your account time limit is: %d-%m-%Y %H:%M:%S." + clif_wis_message(sd->fd, wisp_server_name, tmpstr, strlen(tmpstr)+1); + } + + return 0; +} + +/*========================================== + * session idに問題ありなので後始末 + *------------------------------------------ + */ +int pc_authfail(int id) { + struct map_session_data *sd; + + sd = map_id2sd(id); + if (sd == NULL) + return 1; + + clif_authfail_fd(sd->fd, 0); + + return 0; +} + +static int pc_calc_skillpoint(struct map_session_data* sd) +{ + int i,skill,skill_point=0; + + nullpo_retr(0, sd); + + for(i=1;i<MAX_SKILL;i++){ + if( (skill = pc_checkskill(sd,i)) > 0) { + if(!(skill_get_inf2(i)&0x01) || battle_config.quest_skill_learn) { + if(!sd->status.skill[i].flag) + skill_point += skill; + else if(sd->status.skill[i].flag > 2 && sd->status.skill[i].flag != 13) { + skill_point += (sd->status.skill[i].flag - 2); + } + } + } + } + + return skill_point; +} + +/*========================================== + * 覚えられるスキルの計算 + *------------------------------------------ + */ +int pc_calc_skilltree(struct map_session_data *sd) +{ + int i,id=0,flag; + int c=0, s=0; + //転生や養子の場合の元の職業を算出する + struct pc_base_job s_class; + + nullpo_retr(0, sd); + + s_class = pc_calc_base_job(sd->status.class); + c = s_class.job; + s = (s_class.upper==1) ? 1 : 0 ; //転生以外は通常のスキル? + + if((battle_config.skillup_limit) && ((c >= 0 && c < 23) || (c >= 4001 && c < 4023) || (c >= 4023 && c < 4045))) { + int skill_point = pc_calc_skillpoint(sd); + if(skill_point < 9) + c = 0; + else if((sd->status.skill_point >= sd->status.job_level && skill_point < 58) && ((c > 6 && c < 23) || (c > 4007 && c < 4023) || (c > 4029 && c < 4045))) { + switch(c) { + case 7: + case 14: + c = 1; + break; + case 8: + case 15: + c = 4; + break; + case 9: + case 16: + c = 2; + break; + case 10: + case 18: + c = 5; + break; + case 11: + case 19: + case 20: + c = 3; + break; + case 12: + case 17: + c = 6; + break; + case 4008: + case 4015: + c = 4002; + break; + case 4009: + case 4016: + c = 4005; + break; + case 4010: + case 4017: + c = 4003; + break; + case 4011: + case 4019: + c = 4006; + break; + case 4012: + case 4020: + case 4021: + c = 4004; + break; + case 4013: + case 4018: + c = 4007; + break; + case 4030: + case 4037: + c = 4024; + break; + case 4031: + case 4038: + c = 4027; + break; + case 4032: + case 4039: + c = 4025; + break; + case 4033: + case 4040: + c = 4028; + break; + case 4034: + case 4041: + case 4042: + c = 4026; + break; + case 4035: + case 4043: + c = 4029; + break; + + } + } + } + + for(i=0;i<MAX_SKILL;i++){ + if (sd->status.skill[i].flag != 13) sd->status.skill[i].id=0; + if (sd->status.skill[i].flag && sd->status.skill[i].flag != 13){ // cardスキルなら、 + sd->status.skill[i].lv=(sd->status.skill[i].flag==1)?0:sd->status.skill[i].flag-2; // 本当のlvに + sd->status.skill[i].flag=0; // flagは0にしておく + } + } + + if (battle_config.gm_allskill > 0 && pc_isGM(sd) >= battle_config.gm_allskill){ + // 全てのスキル + for(i=1;i<158;i++) + sd->status.skill[i].id=i; + for(i=210;i<291;i++) + sd->status.skill[i].id=i; + for(i=304;i<337;i++) + sd->status.skill[i].id=i; + if(battle_config.enable_upper_class){ //confで無効でなければ読み込む + for(i=355;i<MAX_SKILL;i++) + sd->status.skill[i].id=i; + } + }else{ + // 通常の計算 + do{ + flag=0; + for(i=0;(id=skill_tree[s][c][i].id)>0;i++){ + int j,f=1; + if(!battle_config.skillfree) { + for(j=0;j<5;j++) { + if( skill_tree[s][c][i].need[j].id && + pc_checkskill(sd,skill_tree[s][c][i].need[j].id) < skill_tree[s][c][i].need[j].lv) + f=0; + } + } + if(f && sd->status.skill[id].id==0 ){ + sd->status.skill[id].id=id; + flag=1; + } + } + }while(flag); + } +// if(battle_config.etc_log) +// printf("calc skill_tree\n"); + return 0; +} + +/*========================================== + * 重量アイコンの確認 + *------------------------------------------ + */ +int pc_checkweighticon(struct map_session_data *sd) +{ + int flag=0; + + nullpo_retr(0, sd); + + if(sd->weight*2 >= sd->max_weight) + flag=1; + if(sd->weight*10 >= sd->max_weight*9) + flag=2; + + if(flag==1){ + if(sd->sc_data[SC_WEIGHT50].timer==-1) + skill_status_change_start(&sd->bl,SC_WEIGHT50,0,0,0,0,0,0); + }else{ + skill_status_change_end(&sd->bl,SC_WEIGHT50,-1); + } + if(flag==2){ + if(sd->sc_data[SC_WEIGHT90].timer==-1) + skill_status_change_start(&sd->bl,SC_WEIGHT90,0,0,0,0,0,0); + }else{ + skill_status_change_end(&sd->bl,SC_WEIGHT90,-1); + } + return 0; +} + +/*========================================== + * パラメータ計算 + * first==0の時、計算対象のパラメータが呼び出し前から + * 変 化した場合自動でsendするが、 + * 能動的に変化させたパラメータは自前でsendするように + *------------------------------------------ + */ +int pc_calcstatus(struct map_session_data* sd,int first) +{ + int b_speed,b_max_hp,b_max_sp,b_hp,b_sp,b_weight,b_max_weight,b_paramb[6],b_parame[6],b_hit,b_flee; + int b_aspd,b_watk,b_def,b_watk2,b_def2,b_flee2,b_critical,b_attackrange,b_matk1,b_matk2,b_mdef,b_mdef2,b_class; + int b_base_atk; + struct skill b_skill[MAX_SKILL]; + int i,bl,index; + int skill,aspd_rate,wele,wele_,def_ele,refinedef=0; + int pele=0,pdef_ele=0; + int str,dstr,dex; + struct pc_base_job s_class; + + nullpo_retr(0, sd); + + //転生や養子の場合の元の職業を算出する + s_class = pc_calc_base_job(sd->status.class); + + b_speed = sd->speed; + b_max_hp = sd->status.max_hp; + b_max_sp = sd->status.max_sp; + b_hp = sd->status.hp; + b_sp = sd->status.sp; + b_weight = sd->weight; + b_max_weight = sd->max_weight; + memcpy(b_paramb,&sd->paramb,sizeof(b_paramb)); + memcpy(b_parame,&sd->paramc,sizeof(b_parame)); + memcpy(b_skill,&sd->status.skill,sizeof(b_skill)); + b_hit = sd->hit; + b_flee = sd->flee; + b_aspd = sd->aspd; + b_watk = sd->watk; + b_def = sd->def; + b_watk2 = sd->watk2; + b_def2 = sd->def2; + b_flee2 = sd->flee2; + b_critical = sd->critical; + b_attackrange = sd->attackrange; + b_matk1 = sd->matk1; + b_matk2 = sd->matk2; + b_mdef = sd->mdef; + b_mdef2 = sd->mdef2; + b_class = sd->view_class; + sd->view_class = sd->status.class; + b_base_atk = sd->base_atk; + + pc_calc_skilltree(sd); // スキルツリーの計算 + + sd->max_weight = max_weight_base[s_class.job]+sd->status.str*300; + + if(first&1) { + sd->weight=0; + for(i=0;i<MAX_INVENTORY;i++){ + if(sd->status.inventory[i].nameid==0 || sd->inventory_data[i] == NULL) + continue; + sd->weight += sd->inventory_data[i]->weight*sd->status.inventory[i].amount; + } + sd->cart_max_weight=battle_config.max_cart_weight; + sd->cart_weight=0; + sd->cart_max_num=MAX_CART; + sd->cart_num=0; + for(i=0;i<MAX_CART;i++){ + if(sd->status.cart[i].nameid==0) + continue; + sd->cart_weight+=itemdb_weight(sd->status.cart[i].nameid)*sd->status.cart[i].amount; + sd->cart_num++; + } + } + + memset(sd->paramb,0,sizeof(sd->paramb)); + memset(sd->parame,0,sizeof(sd->parame)); + sd->hit = 0; + sd->flee = 0; + sd->flee2 = 0; + sd->critical = 0; + sd->aspd = 0; + sd->watk = 0; + sd->def = 0; + sd->mdef = 0; + sd->watk2 = 0; + sd->def2 = 0; + sd->mdef2 = 0; + sd->status.max_hp = 0; + sd->status.max_sp = 0; + sd->attackrange = 0; + sd->attackrange_ = 0; + sd->atk_ele = 0; + sd->def_ele = 0; + sd->star =0; + sd->overrefine =0; + sd->matk1 =0; + sd->matk2 =0; + sd->speed = DEFAULT_WALK_SPEED ; + sd->hprate=100; + sd->sprate=100; + sd->castrate=100; + sd->dsprate=100; + sd->base_atk=0; + sd->arrow_atk=0; + sd->arrow_ele=0; + sd->arrow_hit=0; + sd->arrow_range=0; + sd->nhealhp=sd->nhealsp=sd->nshealhp=sd->nshealsp=sd->nsshealhp=sd->nsshealsp=0; + memset(sd->addele,0,sizeof(sd->addele)); + memset(sd->addrace,0,sizeof(sd->addrace)); + memset(sd->addsize,0,sizeof(sd->addsize)); + memset(sd->addele_,0,sizeof(sd->addele_)); + memset(sd->addrace_,0,sizeof(sd->addrace_)); + memset(sd->addsize_,0,sizeof(sd->addsize_)); + memset(sd->subele,0,sizeof(sd->subele)); + memset(sd->subrace,0,sizeof(sd->subrace)); + memset(sd->addeff,0,sizeof(sd->addeff)); + memset(sd->addeff2,0,sizeof(sd->addeff2)); + memset(sd->reseff,0,sizeof(sd->reseff)); + memset(&sd->special_state,0,sizeof(sd->special_state)); + memset(sd->weapon_coma_ele,0,sizeof(sd->weapon_coma_ele)); + memset(sd->weapon_coma_race,0,sizeof(sd->weapon_coma_race)); + + sd->watk_ = 0; //二刀流用(仮) + sd->watk_2 = 0; + sd->atk_ele_ = 0; + sd->star_ = 0; + sd->overrefine_ = 0; + + sd->aspd_rate = 100; + sd->speed_rate = 100; + sd->hprecov_rate = 100; + sd->sprecov_rate = 100; + sd->critical_def = 0; + sd->double_rate = 0; + sd->near_attack_def_rate = sd->long_attack_def_rate = 0; + sd->atk_rate = sd->matk_rate = 100; + sd->ignore_def_ele = sd->ignore_def_race = 0; + sd->ignore_def_ele_ = sd->ignore_def_race_ = 0; + sd->ignore_mdef_ele = sd->ignore_mdef_race = 0; + sd->arrow_cri = 0; + sd->magic_def_rate = sd->misc_def_rate = 0; + memset(sd->arrow_addele,0,sizeof(sd->arrow_addele)); + memset(sd->arrow_addrace,0,sizeof(sd->arrow_addrace)); + memset(sd->arrow_addsize,0,sizeof(sd->arrow_addsize)); + memset(sd->arrow_addeff,0,sizeof(sd->arrow_addeff)); + memset(sd->arrow_addeff2,0,sizeof(sd->arrow_addeff2)); + memset(sd->magic_addele,0,sizeof(sd->magic_addele)); + memset(sd->magic_addrace,0,sizeof(sd->magic_addrace)); + memset(sd->magic_subrace,0,sizeof(sd->magic_subrace)); + sd->perfect_hit = 0; + sd->critical_rate = sd->hit_rate = sd->flee_rate = sd->flee2_rate = 100; + sd->def_rate = sd->def2_rate = sd->mdef_rate = sd->mdef2_rate = 100; + sd->def_ratio_atk_ele = sd->def_ratio_atk_ele_ = 0; + sd->def_ratio_atk_race = sd->def_ratio_atk_race_ = 0; + sd->get_zeny_num = 0; + sd->add_damage_class_count = sd->add_damage_class_count_ = sd->add_magic_damage_class_count = 0; + sd->add_def_class_count = sd->add_mdef_class_count = 0; + sd->monster_drop_item_count = 0; + memset(sd->add_damage_classrate,0,sizeof(sd->add_damage_classrate)); + memset(sd->add_damage_classrate_,0,sizeof(sd->add_damage_classrate_)); + memset(sd->add_magic_damage_classrate,0,sizeof(sd->add_magic_damage_classrate)); + memset(sd->add_def_classrate,0,sizeof(sd->add_def_classrate)); + memset(sd->add_mdef_classrate,0,sizeof(sd->add_mdef_classrate)); + memset(sd->monster_drop_race,0,sizeof(sd->monster_drop_race)); + memset(sd->monster_drop_itemrate,0,sizeof(sd->monster_drop_itemrate)); + sd->speed_add_rate = sd->aspd_add_rate = 100; + sd->double_add_rate = sd->perfect_hit_add = sd->get_zeny_add_num = 0; + sd->splash_range = sd->splash_add_range = 0; + sd->autospell_id = sd->autospell_lv = sd->autospell_rate = 0; + sd->hp_drain_rate = sd->hp_drain_per = sd->sp_drain_rate = sd->sp_drain_per = 0; + sd->hp_drain_rate_ = sd->hp_drain_per_ = sd->sp_drain_rate_ = sd->sp_drain_per_ = 0; + sd->short_weapon_damage_return = sd->long_weapon_damage_return = 0; + sd->magic_damage_return = 0; //AppleGirl Was Here + sd->random_attack_increase_add = sd->random_attack_increase_per = 0; + + if(!sd->disguiseflag && sd->disguise) { + sd->disguise=0; + clif_changelook(&sd->bl,LOOK_WEAPON,sd->status.weapon); + clif_changelook(&sd->bl,LOOK_SHIELD,sd->status.shield); + clif_changelook(&sd->bl,LOOK_HEAD_BOTTOM,sd->status.head_bottom); + clif_changelook(&sd->bl,LOOK_HEAD_TOP,sd->status.head_top); + clif_changelook(&sd->bl,LOOK_HEAD_MID,sd->status.head_mid); + clif_clearchar(&sd->bl, 9); + pc_setpos(sd, sd->mapname, sd->bl.x, sd->bl.y, 3); + } + + for(i=0;i<10;i++) { + index = sd->equip_index[i]; + if(index < 0) + continue; + if(i == 9 && sd->equip_index[8] == index) + continue; + if(i == 5 && sd->equip_index[4] == index) + continue; + if(i == 6 && (sd->equip_index[5] == index || sd->equip_index[4] == index)) + continue; + + if(sd->inventory_data[index]) { + if(sd->inventory_data[index]->type == 4) { + if(sd->status.inventory[index].card[0]!=0x00ff && sd->status.inventory[index].card[0]!=0x00fe && sd->status.inventory[index].card[0]!=(short)0xff00) { + int j; + for(j=0;j<sd->inventory_data[index]->slot;j++){ // カード + int c=sd->status.inventory[index].card[j]; + if(c>0){ + if(i == 8 && sd->status.inventory[index].equip == 0x20) + sd->state.lr_flag = 1; + run_script(itemdb_equipscript(c),0,sd->bl.id,0); + sd->state.lr_flag = 0; + } + } + } + } + else if(sd->inventory_data[index]->type==5){ // 防具 + if(sd->status.inventory[index].card[0]!=0x00ff && sd->status.inventory[index].card[0]!=0x00fe && sd->status.inventory[index].card[0]!=(short)0xff00) { + int j; + for(j=0;j<sd->inventory_data[index]->slot;j++){ // カード + int c=sd->status.inventory[index].card[j]; + if(c>0) + run_script(itemdb_equipscript(c),0,sd->bl.id,0); + } + } + } + } + } + wele = sd->atk_ele; + wele_ = sd->atk_ele_; + def_ele = sd->def_ele; + if(sd->status.pet_id > 0) { + struct pet_data *pd=sd->pd; + if((pd && battle_config.pet_status_support==1) && (battle_config.pet_equip_required==0 || (battle_config.pet_equip_required && pd->equip > 0))) { + if(sd->status.pet_id > 0 && sd->petDB && sd->pet.intimate > 0) + run_script(sd->petDB->script,0,sd->bl.id,0); + pele = sd->atk_ele; + pdef_ele = sd->def_ele; + sd->atk_ele = sd->def_ele = 0; + } + } + memcpy(sd->paramcard,sd->parame,sizeof(sd->paramcard)); + + // 装備品によるステータス変化はここで実行 + for(i=0;i<10;i++) { + index = sd->equip_index[i]; + if(index < 0) + continue; + if(i == 9 && sd->equip_index[8] == index) + continue; + if(i == 5 && sd->equip_index[4] == index) + continue; + if(i == 6 && (sd->equip_index[5] == index || sd->equip_index[4] == index)) + continue; + if(sd->inventory_data[index]) { + sd->def += sd->inventory_data[index]->def; + if(sd->inventory_data[index]->type == 4) { + int r,wlv = sd->inventory_data[index]->wlv; + if(i == 8 && sd->status.inventory[index].equip == 0x20) { + //二刀流用データ入力 + sd->watk_ += sd->inventory_data[index]->atk; + sd->watk_2 = (r=sd->status.inventory[index].refine)* // 精錬攻撃力 + refinebonus[wlv][0]; + if( (r-=refinebonus[wlv][2])>0 ) // 過剰精錬ボーナス + sd->overrefine_ = r*refinebonus[wlv][1]; + + if(sd->status.inventory[index].card[0]==0x00ff){ // 製造武器 + sd->star_ = (sd->status.inventory[index].card[1]>>8); // 星のかけら + wele_= (sd->status.inventory[index].card[1]&0x0f); // 属 性 + } + sd->attackrange_ += sd->inventory_data[index]->range; + sd->state.lr_flag = 1; + run_script(sd->inventory_data[index]->equip_script,0,sd->bl.id,0); + sd->state.lr_flag = 0; + } + else { //二刀流武器以外 + sd->watk += sd->inventory_data[index]->atk; + sd->watk2 += (r=sd->status.inventory[index].refine)* // 精錬攻撃力 + refinebonus[wlv][0]; + if( (r-=refinebonus[wlv][2])>0 ) // 過剰精錬ボーナス + sd->overrefine += r*refinebonus[wlv][1]; + + if(sd->status.inventory[index].card[0]==0x00ff){ // 製造武器 + sd->star += (sd->status.inventory[index].card[1]>>8); // 星のかけら + wele = (sd->status.inventory[index].card[1]&0x0f); // 属 性 + } + sd->attackrange += sd->inventory_data[index]->range; + run_script(sd->inventory_data[index]->equip_script,0,sd->bl.id,0); + } + } + else if(sd->inventory_data[index]->type == 5) { + sd->watk += sd->inventory_data[index]->atk; + refinedef += sd->status.inventory[index].refine*refinebonus[0][0]; + run_script(sd->inventory_data[index]->equip_script,0,sd->bl.id,0); + } + } + } + + if(sd->equip_index[10] >= 0){ // 矢 + index = sd->equip_index[10]; + if(sd->inventory_data[index]){ //まだ属性が入っていない + sd->state.lr_flag = 2; + run_script(sd->inventory_data[index]->equip_script,0,sd->bl.id,0); + sd->state.lr_flag = 0; + sd->arrow_atk += sd->inventory_data[index]->atk; + } + } + sd->def += (refinedef+50)/100; + + if(sd->attackrange < 1) sd->attackrange = 1; + if(sd->attackrange_ < 1) sd->attackrange_ = 1; + if(sd->attackrange < sd->attackrange_) + sd->attackrange = sd->attackrange_; + if(sd->status.weapon == 11) + sd->attackrange += sd->arrow_range; + if(wele > 0) + sd->atk_ele = wele; + if(wele_ > 0) + sd->atk_ele_ = wele_; + if(def_ele > 0) + sd->def_ele = def_ele; + if(battle_config.pet_status_support) { + if(pele > 0 && !sd->atk_ele) + sd->atk_ele = pele; + if(pdef_ele > 0 && !sd->def_ele) + sd->def_ele = pdef_ele; + } + sd->double_rate += sd->double_add_rate; + sd->perfect_hit += sd->perfect_hit_add; + sd->get_zeny_num += sd->get_zeny_add_num; + sd->splash_range += sd->splash_add_range; + if(sd->speed_add_rate != 100) + sd->speed_rate += sd->speed_add_rate - 100; + if(sd->aspd_add_rate != 100) + sd->aspd_rate += sd->aspd_add_rate - 100; + + // 武器ATKサイズ補正 (右手) + sd->atkmods[0] = atkmods[0][sd->weapontype1]; + sd->atkmods[1] = atkmods[1][sd->weapontype1]; + sd->atkmods[2] = atkmods[2][sd->weapontype1]; + //武器ATKサイズ補正 (左手) + sd->atkmods_[0] = atkmods[0][sd->weapontype2]; + sd->atkmods_[1] = atkmods[1][sd->weapontype2]; + sd->atkmods_[2] = atkmods[2][sd->weapontype2]; + + // jobボーナス分 + for(i=0;i<sd->status.job_level && i<MAX_LEVEL;i++){ + if(job_bonus[s_class.upper][s_class.job][i]) + sd->paramb[job_bonus[s_class.upper][s_class.job][i]-1]++; + } + + if( (skill=pc_checkskill(sd,MC_INCCARRY))>0 ) // skill can be used with an item now, thanks to orn [Valaris] + sd->max_weight += skill*1000; + + if( (skill=pc_checkskill(sd,AC_OWL))>0 ) // ふくろうの目 + sd->paramb[4] += skill; + + // ステータス変化による基本パラメータ補正 + if(sd->sc_count){ + if(sd->sc_data[SC_CONCENTRATE].timer!=-1 && sd->sc_data[SC_QUAGMIRE].timer == -1){ // 集中力向上 + sd->paramb[1]+= (sd->status.agi+sd->paramb[1]+sd->parame[1]-sd->paramcard[1])*(2+sd->sc_data[SC_CONCENTRATE].val1)/100; + sd->paramb[4]+= (sd->status.dex+sd->paramb[4]+sd->parame[4]-sd->paramcard[4])*(2+sd->sc_data[SC_CONCENTRATE].val1)/100; + } + if(sd->sc_data[SC_INCREASEAGI].timer!=-1 && sd->sc_data[SC_QUAGMIRE].timer == -1 && sd->sc_data[SC_DONTFORGETME].timer == -1){ // 速度増加 + sd->paramb[1]+= 2+sd->sc_data[SC_INCREASEAGI].val1; + sd->speed -= sd->speed *25/100; + } + if(sd->sc_data[SC_DECREASEAGI].timer!=-1) // 速度減少(agiはbattle.cで) + sd->speed = sd->speed *125/100; + if(sd->sc_data[SC_CLOAKING].timer!=-1) + sd->speed = (sd->speed*(76+(sd->sc_data[SC_INCREASEAGI].val1*3)))/100; + if(sd->sc_data[SC_BLESSING].timer!=-1){ // ブレッシング + sd->paramb[0]+= sd->sc_data[SC_BLESSING].val1; + sd->paramb[3]+= sd->sc_data[SC_BLESSING].val1; + sd->paramb[4]+= sd->sc_data[SC_BLESSING].val1; + } + if(sd->sc_data[SC_GLORIA].timer!=-1) // グロリア + sd->paramb[5]+= 30; + if(sd->sc_data[SC_LOUD].timer!=-1 && sd->sc_data[SC_QUAGMIRE].timer == -1) // ラウドボイス + sd->paramb[0]+= 4; + if(sd->sc_data[SC_QUAGMIRE].timer!=-1){ // クァグマイア + sd->speed = sd->speed*3/2; + sd->paramb[1]-=(sd->status.agi+sd->paramb[1]+sd->parame[1])/2; + sd->paramb[4]-=(sd->status.dex+sd->paramb[4]+sd->parame[4])/2; + } + if(sd->sc_data[SC_TRUESIGHT].timer!=-1){ // トゥルーサイト + sd->paramb[0]+= 5; + sd->paramb[1]+= 5; + sd->paramb[2]+= 5; + sd->paramb[3]+= 5; + sd->paramb[4]+= 5; + sd->paramb[5]+= 5; + } + } + + //1度も死んでないJob70スパノビに+10 + if(s_class.job == 23 && sd->die_counter == 0 && sd->status.job_level >= 70){ + sd->paramb[0]+= 15; + sd->paramb[1]+= 15; + sd->paramb[2]+= 15; + sd->paramb[3]+= 15; + sd->paramb[4]+= 15; + sd->paramb[5]+= 15; + } + sd->paramc[0]=sd->status.str+sd->paramb[0]+sd->parame[0]; + sd->paramc[1]=sd->status.agi+sd->paramb[1]+sd->parame[1]; + sd->paramc[2]=sd->status.vit+sd->paramb[2]+sd->parame[2]; + sd->paramc[3]=sd->status.int_+sd->paramb[3]+sd->parame[3]; + sd->paramc[4]=sd->status.dex+sd->paramb[4]+sd->parame[4]; + sd->paramc[5]=sd->status.luk+sd->paramb[5]+sd->parame[5]; + for(i=0;i<6;i++) + if(sd->paramc[i] < 0) sd->paramc[i] = 0; + + if(sd->status.weapon == 11 || sd->status.weapon == 13 || sd->status.weapon == 14) { + str = sd->paramc[4]; + dex = sd->paramc[0]; + } + else { + str = sd->paramc[0]; + dex = sd->paramc[4]; + } + dstr = str/10; + sd->base_atk += str + dstr*dstr + dex/5 + sd->paramc[5]/5; + sd->matk1 += sd->paramc[3]+(sd->paramc[3]/5)*(sd->paramc[3]/5); + sd->matk2 += sd->paramc[3]+(sd->paramc[3]/7)*(sd->paramc[3]/7); + if(sd->matk1 < sd->matk2) { + int temp = sd->matk2; + sd->matk2 = sd->matk1; + sd->matk1 = temp; + } + sd->hit += sd->paramc[4] + sd->status.base_level; + sd->flee += sd->paramc[1] + sd->status.base_level; + sd->def2 += sd->paramc[2]; + sd->mdef2 += sd->paramc[3]; + sd->flee2 += sd->paramc[5]+10; + sd->critical += (sd->paramc[5]*3)+10; + + if(sd->base_atk < 1) + sd->base_atk = 1; + if(sd->critical_rate != 100) + sd->critical = (sd->critical*sd->critical_rate)/100; + if(sd->critical < 10) sd->critical = 10; + if(sd->hit_rate != 100) + sd->hit = (sd->hit*sd->hit_rate)/100; + if(sd->hit < 1) sd->hit = 1; + if(sd->flee_rate != 100) + sd->flee = (sd->flee*sd->flee_rate)/100; + if(sd->flee < 1) sd->flee = 1; + if(sd->flee2_rate != 100) + sd->flee2 = (sd->flee2*sd->flee2_rate)/100; + if(sd->flee2 < 10) sd->flee2 = 10; + if(sd->def_rate != 100) + sd->def = (sd->def*sd->def_rate)/100; + if(sd->def < 0) sd->def = 0; + if(sd->def2_rate != 100) + sd->def2 = (sd->def2*sd->def2_rate)/100; + if(sd->def2 < 1) sd->def2 = 1; + if(sd->mdef_rate != 100) + sd->mdef = (sd->mdef*sd->mdef_rate)/100; + if(sd->mdef < 0) sd->mdef = 0; + if(sd->mdef2_rate != 100) + sd->mdef2 = (sd->mdef2*sd->mdef2_rate)/100; + if(sd->mdef2 < 1) sd->mdef2 = 1; + + // 二刀流 ASPD 修正 + if (sd->status.weapon <= 16) + sd->aspd += aspd_base[s_class.job][sd->status.weapon]-(sd->paramc[1]*4+sd->paramc[4])*aspd_base[s_class.job][sd->status.weapon]/1000; + else + sd->aspd += ( + (aspd_base[s_class.job][sd->weapontype1]-(sd->paramc[1]*4+sd->paramc[4])*aspd_base[s_class.job][sd->weapontype1]/1000) + + (aspd_base[s_class.job][sd->weapontype2]-(sd->paramc[1]*4+sd->paramc[4])*aspd_base[s_class.job][sd->weapontype2]/1000) + ) * 140 / 200; + + aspd_rate = sd->aspd_rate; + + //攻撃速度増加 + + if( (skill=pc_checkskill(sd,AC_VULTURE))>0){ // ワシの目 + sd->hit += skill; + if(sd->status.weapon == 11) + sd->attackrange += skill; + } + + if( (skill=pc_checkskill(sd,BS_WEAPONRESEARCH))>0) // 武器研究の命中率増加 + sd->hit += skill*2; + if(sd->status.option&2 && (skill = pc_checkskill(sd,RG_TUNNELDRIVE))>0 ) // トンネルドライブ // トンネルドライブ + sd->speed += (1.2*DEFAULT_WALK_SPEED - skill*9); + if (pc_iscarton(sd) && (skill=pc_checkskill(sd,MC_PUSHCART))>0) // カートによる速度低下 + sd->speed += (10-skill) * (DEFAULT_WALK_SPEED * 0.1); + else if (pc_isriding(sd)) // ペコペコ乗りによる速度増加 + sd->speed -= (0.25 * DEFAULT_WALK_SPEED); + sd->max_weight += 1000; + if(sd->sc_count){ + if(sd->sc_data[SC_WINDWALK].timer!=-1) //ウィンドウォーク時はLv*2%減算 + sd->speed -= sd->speed *(sd->sc_data[SC_WINDWALK].val1*2)/100; + if(sd->sc_data[SC_CARTBOOST].timer!=-1) // カートブースト + sd->speed -= (DEFAULT_WALK_SPEED * 20)/100; + if(sd->sc_data[SC_BERSERK].timer!=-1) //バーサーク中はIAと同じぐらい速い? + sd->speed -= sd->speed *25/100; + if(sd->sc_data[SC_WEDDING].timer!=-1) //結婚中は歩くのが遅い + sd->speed = 2*DEFAULT_WALK_SPEED; + } + + if((skill=pc_checkskill(sd,CR_TRUST))>0) { // フェイス + sd->status.max_hp += skill*200; + sd->subele[6] += skill*5; + } + if((skill=pc_checkskill(sd,BS_SKINTEMPER))>0) + sd->subele[3] += skill*4; + + bl=sd->status.base_level; + + sd->status.max_hp += (3500 + bl*hp_coefficient2[s_class.job] + hp_sigma_val[s_class.job][(bl > 0)? bl-1:0])/100 * (100 + sd->paramc[2])/100 + (sd->parame[2] - sd->paramcard[2]); + if (s_class.upper==1) // [MouseJstr] + sd->status.max_hp = sd->status.max_hp * 130/100; + if(sd->hprate!=100) + sd->status.max_hp = sd->status.max_hp*sd->hprate/100; + + if(sd->sc_data && sd->sc_data[SC_BERSERK].timer!=-1){ // バーサーク + sd->status.max_hp = sd->status.max_hp * 3; + sd->status.hp = sd->status.hp * 3; + if(sd->status.max_hp > battle_config.max_hp) // removed negative max hp bug by Valaris + sd->status.max_hp = battle_config.max_hp; + if(sd->status.hp > battle_config.max_hp) // removed negative max hp bug by Valaris + sd->status.hp = battle_config.max_hp; + } + if(s_class.job == 23 && sd->status.base_level >= 99){ + sd->status.max_hp = sd->status.max_hp + 2000; + } + + if(sd->status.max_hp > battle_config.max_hp) // removed negative max hp bug by Valaris + sd->status.max_hp = battle_config.max_hp; + if(sd->status.max_hp <= 0) sd->status.max_hp = 1; // end + + // 最大SP計算 + sd->status.max_sp += ((sp_coefficient[s_class.job] * bl) + 1000)/100 * (100 + sd->paramc[3])/100 + (sd->parame[3] - sd->paramcard[3]); + if (s_class.upper==1) // [MouseJstr] + sd->status.max_sp = sd->status.max_sp * 130/100; + if(sd->sprate!=100) + sd->status.max_sp = sd->status.max_sp*sd->sprate/100; + + if((skill=pc_checkskill(sd,HP_MEDITATIO))>0) // メディテイティオ + sd->status.max_sp += sd->status.max_sp*skill/100; + if((skill=pc_checkskill(sd,HW_SOULDRAIN))>0) /* ソウルドレイン */ + sd->status.max_sp += sd->status.max_sp*2*skill/100; + + if(sd->status.max_sp < 0 || sd->status.max_sp > battle_config.max_sp) + sd->status.max_sp = battle_config.max_sp; + + //自然回復HP + sd->nhealhp = 1 + (sd->paramc[2]/5) + (sd->status.max_hp/200); + if((skill=pc_checkskill(sd,SM_RECOVERY)) > 0) { /* HP回復力向上 */ + sd->nshealhp = skill*5 + (sd->status.max_hp*skill/500); + if(sd->nshealhp > 0x7fff) sd->nshealhp = 0x7fff; + } + //自然回復SP + sd->nhealsp = 1 + (sd->paramc[3]/6) + (sd->status.max_sp/100); + if(sd->paramc[3] >= 120) + sd->nhealsp += ((sd->paramc[3]-120)>>1) + 4; + if((skill=pc_checkskill(sd,MG_SRECOVERY)) > 0) { /* SP回復力向上 */ + sd->nshealsp = skill*3 + (sd->status.max_sp*skill/500); + if(sd->nshealsp > 0x7fff) sd->nshealsp = 0x7fff; + } + + if((skill = pc_checkskill(sd,MO_SPIRITSRECOVERY)) > 0) { + sd->nsshealhp = skill*4 + (sd->status.max_hp*skill/500); + sd->nsshealsp = skill*2 + (sd->status.max_sp*skill/500); + if(sd->nsshealhp > 0x7fff) sd->nsshealhp = 0x7fff; + if(sd->nsshealsp > 0x7fff) sd->nsshealsp = 0x7fff; + } + if(sd->hprecov_rate != 100) { + sd->nhealhp = sd->nhealhp*sd->hprecov_rate/100; + if(sd->nhealhp < 1) sd->nhealhp = 1; + } + if(sd->sprecov_rate != 100) { + sd->nhealsp = sd->nhealsp*sd->sprecov_rate/100; + if(sd->nhealsp < 1) sd->nhealsp = 1; + } + if((skill=pc_checkskill(sd,HP_MEDITATIO)) > 0) { // メディテイティオはSPRではなく自然回復にかかる + sd->nhealsp += 3*skill*(sd->status.max_sp)/100; + if(sd->nhealsp > 0x7fff) sd->nhealsp = 0x7fff; + } + + // 種族耐性(これでいいの? ディバインプロテクションと同じ処理がいるかも) + if( (skill=pc_checkskill(sd,SA_DRAGONOLOGY))>0 ){ // ドラゴノロジー + skill = skill*4; + sd->addrace[9]+=skill; + sd->addrace_[9]+=skill; + sd->subrace[9]+=skill; + sd->magic_addrace[9]+=skill; + sd->magic_subrace[9]-=skill; + } + + //Flee上昇 + if( (skill=pc_checkskill(sd,TF_MISS))>0 ){ // 回避率増加 + if(sd->status.class==6||sd->status.class==4007 || sd->status.class==23){ + sd->flee += skill*3; + } + if(sd->status.class==12||sd->status.class==17||sd->status.class==4013||sd->status.class==4018) + sd->flee += skill*4; + if(sd->status.class==12||sd->status.class==4013) + sd->speed -= sd->speed *(skill*.5)/100; + } + if( (skill=pc_checkskill(sd,MO_DODGE))>0 ) // 見切り + sd->flee += (skill*3)>>1; + + // スキルやステータス異常による残りのパラメータ補正 + if(sd->sc_count){ + // ATK/DEF変化形 + if(sd->sc_data[SC_ANGELUS].timer!=-1) // エンジェラス + sd->def2 = sd->def2*(110+5*sd->sc_data[SC_ANGELUS].val1)/100; + if(sd->sc_data[SC_IMPOSITIO].timer!=-1) {// インポシティオマヌス + sd->watk += sd->sc_data[SC_IMPOSITIO].val1*5; + index = sd->equip_index[8]; + if(index >= 0 && sd->inventory_data[index] && sd->inventory_data[index]->type == 4) + sd->watk_ += sd->sc_data[SC_IMPOSITIO].val1*5; + } + if(sd->sc_data[SC_PROVOKE].timer!=-1){ // プロボック + sd->def2 = sd->def2*(100-6*sd->sc_data[SC_PROVOKE].val1)/100; + sd->base_atk = sd->base_atk*(100+2*sd->sc_data[SC_PROVOKE].val1)/100; + sd->watk = sd->watk*(100+2*sd->sc_data[SC_PROVOKE].val1)/100; + index = sd->equip_index[8]; + if(index >= 0 && sd->inventory_data[index] && sd->inventory_data[index]->type == 4) + sd->watk_ = sd->watk_*(100+2*sd->sc_data[SC_PROVOKE].val1)/100; + } + if(sd->sc_data[SC_ENDURE].timer!=-1) + sd->mdef2 += sd->sc_data[SC_ENDURE].val1; + if(sd->sc_data[SC_MINDBREAKER].timer!=-1){ // プロボック + sd->mdef2 = sd->mdef2*(100-6*sd->sc_data[SC_MINDBREAKER].val1)/100; + sd->matk1 = sd->matk1*(100+2*sd->sc_data[SC_MINDBREAKER].val1)/100; + sd->matk2 = sd->matk2*(100+2*sd->sc_data[SC_MINDBREAKER].val1)/100; + } + if(sd->sc_data[SC_POISON].timer!=-1) // 毒状態 + sd->def2 = sd->def2*75/100; + if(sd->sc_data[SC_DRUMBATTLE].timer!=-1){ // 戦太鼓の響き + sd->watk += sd->sc_data[SC_DRUMBATTLE].val2; + sd->def += sd->sc_data[SC_DRUMBATTLE].val3; + index = sd->equip_index[8]; + if(index >= 0 && sd->inventory_data[index] && sd->inventory_data[index]->type == 4) + sd->watk_ += sd->sc_data[SC_DRUMBATTLE].val2; + } + if(sd->sc_data[SC_NIBELUNGEN].timer!=-1) { // ニーベルングの指輪 + index = sd->equip_index[9]; + if(index >= 0 && sd->inventory_data[index] && sd->inventory_data[index]->wlv == 3) + sd->watk += sd->sc_data[SC_NIBELUNGEN].val3; + index = sd->equip_index[8]; + if(index >= 0 && sd->inventory_data[index] && sd->inventory_data[index]->wlv == 3) + sd->watk_ += sd->sc_data[SC_NIBELUNGEN].val3; + if(index >= 0 && sd->inventory_data[index] && sd->inventory_data[index]->wlv == 4) + sd->watk += sd->sc_data[SC_NIBELUNGEN].val2; + index = sd->equip_index[8]; + if(index >= 0 && sd->inventory_data[index] && sd->inventory_data[index]->wlv == 4) + sd->watk_ += sd->sc_data[SC_NIBELUNGEN].val2; + } + + if(sd->sc_data[SC_VOLCANO].timer!=-1 && sd->def_ele==3){ // ボルケーノ + sd->watk += sd->sc_data[SC_VIOLENTGALE].val3; + } + + if(sd->sc_data[SC_SIGNUMCRUCIS].timer!=-1) + sd->def = sd->def * (100 - sd->sc_data[SC_SIGNUMCRUCIS].val2)/100; + if(sd->sc_data[SC_ETERNALCHAOS].timer!=-1) // エターナルカオス + sd->def=0; + + if(sd->sc_data[SC_CONCENTRATION].timer!=-1){ //コンセントレーション + sd->watk = sd->watk * (100 + 5*sd->sc_data[SC_CONCENTRATION].val1)/100; + index = sd->equip_index[8]; + if(index >= 0 && sd->inventory_data[index] && sd->inventory_data[index]->type == 4) + sd->watk_ = sd->watk * (100 + 5*sd->sc_data[SC_CONCENTRATION].val1)/100; + sd->def = sd->def * (100 - 5*sd->sc_data[SC_CONCENTRATION].val1)/100; + } + + if(sd->sc_data[SC_MAGICPOWER].timer!=-1){ //魔法力増幅 + sd->matk1 = sd->matk1*(100+2*sd->sc_data[SC_MAGICPOWER].val1)/100; + sd->matk2 = sd->matk2*(100+2*sd->sc_data[SC_MAGICPOWER].val1)/100; + } + if(sd->sc_data[SC_ATKPOT].timer!=-1) + sd->watk += sd->sc_data[SC_ATKPOT].val1; + if(sd->sc_data[SC_MATKPOT].timer!=-1){ + sd->matk1 += sd->sc_data[SC_MATKPOT].val1; + sd->matk2 += sd->sc_data[SC_MATKPOT].val1; + } + + // ASPD/移動速度変化系 + if(sd->sc_data[SC_TWOHANDQUICKEN].timer != -1 && sd->sc_data[SC_QUAGMIRE].timer == -1 && sd->sc_data[SC_DONTFORGETME].timer == -1) // 2HQ + aspd_rate -= 30; + if(sd->sc_data[SC_ADRENALINE].timer != -1 && sd->sc_data[SC_TWOHANDQUICKEN].timer == -1 && + sd->sc_data[SC_QUAGMIRE].timer == -1 && sd->sc_data[SC_DONTFORGETME].timer == -1) { // アドレナリンラッシュ + if(sd->sc_data[SC_ADRENALINE].val2 || !battle_config.party_skill_penaly) + aspd_rate -= 30; + else + aspd_rate -= 25; + } + if(sd->sc_data[SC_SPEARSQUICKEN].timer != -1 && sd->sc_data[SC_ADRENALINE].timer == -1 && + sd->sc_data[SC_TWOHANDQUICKEN].timer == -1 && sd->sc_data[SC_QUAGMIRE].timer == -1 && sd->sc_data[SC_DONTFORGETME].timer == -1) // スピアクィッケン + aspd_rate -= sd->sc_data[SC_SPEARSQUICKEN].val2; + if(sd->sc_data[SC_ASSNCROS].timer!=-1 && // 夕陽のアサシンクロス + sd->sc_data[SC_TWOHANDQUICKEN].timer==-1 && sd->sc_data[SC_ADRENALINE].timer==-1 && sd->sc_data[SC_SPEARSQUICKEN].timer==-1 && + sd->sc_data[SC_DONTFORGETME].timer == -1) + aspd_rate -= 5+sd->sc_data[SC_ASSNCROS].val1+sd->sc_data[SC_ASSNCROS].val2+sd->sc_data[SC_ASSNCROS].val3; + if(sd->sc_data[SC_DONTFORGETME].timer!=-1){ // 私を忘れないで + aspd_rate += sd->sc_data[SC_DONTFORGETME].val1*3 + sd->sc_data[SC_DONTFORGETME].val2 + (sd->sc_data[SC_DONTFORGETME].val3>>16); + sd->speed= sd->speed*(100+sd->sc_data[SC_DONTFORGETME].val1*2 + sd->sc_data[SC_DONTFORGETME].val2 + (sd->sc_data[SC_DONTFORGETME].val3&0xffff))/100; + } + if( sd->sc_data[i=SC_SPEEDPOTION2].timer!=-1 || + sd->sc_data[i=SC_SPEEDPOTION1].timer!=-1 || + sd->sc_data[i=SC_SPEEDPOTION0].timer!=-1) // 増 速ポーション + aspd_rate -= sd->sc_data[i].val2; + + // HIT/FLEE変化系 + if(sd->sc_data[SC_WHISTLE].timer!=-1){ // 口笛 + sd->flee += sd->flee * (sd->sc_data[SC_WHISTLE].val1 + +sd->sc_data[SC_WHISTLE].val2+(sd->sc_data[SC_WHISTLE].val3>>16))/100; + sd->flee2+= (sd->sc_data[SC_WHISTLE].val1+sd->sc_data[SC_WHISTLE].val2+(sd->sc_data[SC_WHISTLE].val3&0xffff)) * 10; + } + if(sd->sc_data[SC_HUMMING].timer!=-1) // ハミング + sd->hit += (sd->sc_data[SC_HUMMING].val1*2+sd->sc_data[SC_HUMMING].val2 + +sd->sc_data[SC_HUMMING].val3) * sd->hit/100; + if(sd->sc_data[SC_VIOLENTGALE].timer!=-1 && sd->def_ele==4){ // バイオレントゲイル + sd->flee += sd->flee*sd->sc_data[SC_VIOLENTGALE].val3/100; + } + if(sd->sc_data[SC_BLIND].timer!=-1){ // 暗黒 + sd->hit -= sd->hit*25/100; + sd->flee -= sd->flee*25/100; + } + if(sd->sc_data[SC_WINDWALK].timer!=-1) // ウィンドウォーク + sd->flee += sd->flee*(sd->sc_data[SC_WINDWALK].val2)/100; + if(sd->sc_data[SC_SPIDERWEB].timer!=-1) //スパイダーウェブ + sd->flee -= sd->flee*50/100; + if(sd->sc_data[SC_TRUESIGHT].timer!=-1) //トゥルーサイト + sd->hit += 3*(sd->sc_data[SC_TRUESIGHT].val1); + if(sd->sc_data[SC_CONCENTRATION].timer!=-1) //コンセントレーション + sd->hit += (10*(sd->sc_data[SC_CONCENTRATION].val1)); + + // 耐性 + if(sd->sc_data[SC_SIEGFRIED].timer!=-1){ // 不死身のジークフリード + sd->subele[1] += sd->sc_data[SC_SIEGFRIED].val2; // 水 + sd->subele[2] += sd->sc_data[SC_SIEGFRIED].val2; // 水 + sd->subele[3] += sd->sc_data[SC_SIEGFRIED].val2; // 火 + sd->subele[4] += sd->sc_data[SC_SIEGFRIED].val2; // 水 + sd->subele[5] += sd->sc_data[SC_SIEGFRIED].val2; // 水 + sd->subele[6] += sd->sc_data[SC_SIEGFRIED].val2; // 水 + sd->subele[7] += sd->sc_data[SC_SIEGFRIED].val2; // 水 + sd->subele[8] += sd->sc_data[SC_SIEGFRIED].val2; // 水 + sd->subele[9] += sd->sc_data[SC_SIEGFRIED].val2; // 水 + } + if(sd->sc_data[SC_PROVIDENCE].timer!=-1){ // プロヴィデンス + sd->subele[6] += sd->sc_data[SC_PROVIDENCE].val2; // 対 聖属性 + sd->subrace[6] += sd->sc_data[SC_PROVIDENCE].val2; // 対 悪魔 + } + + // その他 + if(sd->sc_data[SC_APPLEIDUN].timer!=-1){ // イドゥンの林檎 + sd->status.max_hp += ((5+sd->sc_data[SC_APPLEIDUN].val1*2+((sd->sc_data[SC_APPLEIDUN].val2+1)>>1) + +sd->sc_data[SC_APPLEIDUN].val3/10) * sd->status.max_hp)/100; + if(sd->status.max_hp < 0 || sd->status.max_hp > battle_config.max_hp) + sd->status.max_hp = battle_config.max_hp; + } + if(sd->sc_data[SC_DELUGE].timer!=-1 && sd->def_ele==1){ // デリュージ + sd->status.max_hp += sd->status.max_hp*sd->sc_data[SC_DELUGE].val3/100; + if(sd->status.max_hp < 0 || sd->status.max_hp > battle_config.max_hp) + sd->status.max_hp = battle_config.max_hp; + } + if(sd->sc_data[SC_SERVICE4U].timer!=-1) { // サービスフォーユー + sd->status.max_sp += sd->status.max_sp*(10+sd->sc_data[SC_SERVICE4U].val1+sd->sc_data[SC_SERVICE4U].val2 + +sd->sc_data[SC_SERVICE4U].val3)/100; + if(sd->status.max_sp < 0 || sd->status.max_sp > battle_config.max_sp) + sd->status.max_sp = battle_config.max_sp; + sd->dsprate-=(10+sd->sc_data[SC_SERVICE4U].val1*3+sd->sc_data[SC_SERVICE4U].val2 + +sd->sc_data[SC_SERVICE4U].val3); + if(sd->dsprate<0)sd->dsprate=0; + } + + if(sd->sc_data[SC_FORTUNE].timer!=-1) // 幸運のキス + sd->critical += (10+sd->sc_data[SC_FORTUNE].val1+sd->sc_data[SC_FORTUNE].val2 + +sd->sc_data[SC_FORTUNE].val3)*10; + + if(sd->sc_data[SC_EXPLOSIONSPIRITS].timer!=-1){ // 爆裂波動 + if(s_class.job==23) + sd->critical += sd->sc_data[SC_EXPLOSIONSPIRITS].val1*100; + else + sd->critical += sd->sc_data[SC_EXPLOSIONSPIRITS].val2; + } + + if(sd->sc_data[SC_STEELBODY].timer!=-1){ // 金剛 + sd->def = 90; + sd->mdef = 90; + aspd_rate += 25; + sd->speed = (sd->speed * 125) / 100; + } + if(sd->sc_data[SC_DEFENDER].timer != -1) { + sd->aspd += (550 - sd->sc_data[SC_DEFENDER].val1*50); + sd->speed = (sd->speed * (155 - sd->sc_data[SC_DEFENDER].val1*5)) / 100; + } + if(sd->sc_data[SC_ENCPOISON].timer != -1) + sd->addeff[4] += sd->sc_data[SC_ENCPOISON].val2; + + if( sd->sc_data[SC_DANCING].timer!=-1 ){ // 演奏/ダンス使用中 + sd->speed*=4; + sd->nhealsp = 0; + sd->nshealsp = 0; + sd->nsshealsp = 0; + } + if(sd->sc_data[SC_CURSE].timer!=-1) + sd->speed += 450; + + if(sd->sc_data[SC_TRUESIGHT].timer!=-1) //トゥルーサイト + sd->critical += sd->critical*(sd->sc_data[SC_TRUESIGHT].val1)/100; + +/* if(sd->sc_data[SC_VOLCANO].timer!=-1) // エンチャントポイズン(属性はbattle.cで) + sd->addeff[2]+=sd->sc_data[SC_VOLCANO].val2;//% of granting + if(sd->sc_data[SC_DELUGE].timer!=-1) // エンチャントポイズン(属性はbattle.cで) + sd->addeff[0]+=sd->sc_data[SC_DELUGE].val2;//% of granting + */ + } + + if(sd->speed_rate != 100) + sd->speed = sd->speed*sd->speed_rate/100; + if(sd->speed < 1) sd->speed = 1; + if(aspd_rate != 100) + sd->aspd = sd->aspd*aspd_rate/100; + if(pc_isriding(sd)) // 騎兵修練 + sd->aspd = sd->aspd*(100 + 10*(5 - pc_checkskill(sd,KN_CAVALIERMASTERY)))/ 100; + if(sd->aspd < battle_config.max_aspd) sd->aspd = battle_config.max_aspd; + sd->amotion = sd->aspd; + sd->dmotion = 800-sd->paramc[1]*4; + if(sd->dmotion<400) + sd->dmotion = 400; + if(sd->skilltimer != -1 && (skill = pc_checkskill(sd,SA_FREECAST)) > 0) { + sd->prev_speed = sd->speed; + sd->speed = sd->speed*(175 - skill*5)/100; + } + + if(sd->status.hp>sd->status.max_hp) + sd->status.hp=sd->status.max_hp; + if(sd->status.sp>sd->status.max_sp) + sd->status.sp=sd->status.max_sp; + + if(first&4) + return 0; + if(first&3) { + clif_updatestatus(sd,SP_SPEED); + clif_updatestatus(sd,SP_MAXHP); + clif_updatestatus(sd,SP_MAXSP); + if(first&1) { + clif_updatestatus(sd,SP_HP); + clif_updatestatus(sd,SP_SP); + } + return 0; + } + + if(b_class != sd->view_class) { + clif_changelook(&sd->bl,LOOK_BASE,sd->view_class); +#if PACKETVER < 4 + clif_changelook(&sd->bl,LOOK_WEAPON,sd->status.weapon); + clif_changelook(&sd->bl,LOOK_SHIELD,sd->status.shield); +#else + clif_changelook(&sd->bl,LOOK_WEAPON,0); +#endif + } + + if( memcmp(b_skill,sd->status.skill,sizeof(sd->status.skill)) || b_attackrange != sd->attackrange) + clif_skillinfoblock(sd); // スキル送信 + + if(b_speed != sd->speed) + clif_updatestatus(sd,SP_SPEED); + if(b_weight != sd->weight) + clif_updatestatus(sd,SP_WEIGHT); + if(b_max_weight != sd->max_weight) { + clif_updatestatus(sd,SP_MAXWEIGHT); + pc_checkweighticon(sd); + } + for(i=0;i<6;i++) + if(b_paramb[i] + b_parame[i] != sd->paramb[i] + sd->parame[i]) + clif_updatestatus(sd,SP_STR+i); + if(b_hit != sd->hit) + clif_updatestatus(sd,SP_HIT); + if(b_flee != sd->flee) + clif_updatestatus(sd,SP_FLEE1); + if(b_aspd != sd->aspd) + clif_updatestatus(sd,SP_ASPD); + if(b_watk != sd->watk || b_base_atk != sd->base_atk) + clif_updatestatus(sd,SP_ATK1); + if(b_def != sd->def) + clif_updatestatus(sd,SP_DEF1); + if(b_watk2 != sd->watk2) + clif_updatestatus(sd,SP_ATK2); + if(b_def2 != sd->def2) + clif_updatestatus(sd,SP_DEF2); + if(b_flee2 != sd->flee2) + clif_updatestatus(sd,SP_FLEE2); + if(b_critical != sd->critical) + clif_updatestatus(sd,SP_CRITICAL); + if(b_matk1 != sd->matk1) + clif_updatestatus(sd,SP_MATK1); + if(b_matk2 != sd->matk2) + clif_updatestatus(sd,SP_MATK2); + if(b_mdef != sd->mdef) + clif_updatestatus(sd,SP_MDEF1); + if(b_mdef2 != sd->mdef2) + clif_updatestatus(sd,SP_MDEF2); + if(b_attackrange != sd->attackrange) + clif_updatestatus(sd,SP_ATTACKRANGE); + if(b_max_hp != sd->status.max_hp) + clif_updatestatus(sd,SP_MAXHP); + if(b_max_sp != sd->status.max_sp) + clif_updatestatus(sd,SP_MAXSP); + if(b_hp != sd->status.hp) + clif_updatestatus(sd,SP_HP); + if(b_sp != sd->status.sp) + clif_updatestatus(sd,SP_SP); + +/* if(before.cart_num != before.cart_num || before.cart_max_num != before.cart_max_num || + before.cart_weight != before.cart_weight || before.cart_max_weight != before.cart_max_weight ) + clif_updatestatus(sd,SP_CARTINFO);*/ + + if(sd->status.hp<sd->status.max_hp>>2 && pc_checkskill(sd,SM_AUTOBERSERK)>0 && + (sd->sc_data[SC_PROVOKE].timer==-1 || sd->sc_data[SC_PROVOKE].val2==0 ) && !pc_isdead(sd)) + // オートバーサーク発動 + skill_status_change_start(&sd->bl,SC_PROVOKE,10,1,0,0,0,0); + + return 0; +} + +/*========================================== + * 装 備品による能力等のボーナス設定 + *------------------------------------------ + */ +int pc_bonus(struct map_session_data *sd,int type,int val) +{ + nullpo_retr(0, sd); + + switch(type){ + case SP_STR: + case SP_AGI: + case SP_VIT: + case SP_INT: + case SP_DEX: + case SP_LUK: + if(sd->state.lr_flag != 2) + sd->parame[type-SP_STR]+=val; + break; + case SP_ATK1: + if(!sd->state.lr_flag) + sd->watk+=val; + else if(sd->state.lr_flag == 1) + sd->watk_+=val; + break; + case SP_ATK2: + if(!sd->state.lr_flag) + sd->watk2+=val; + else if(sd->state.lr_flag == 1) + sd->watk_2+=val; + break; + case SP_BASE_ATK: + if(sd->state.lr_flag != 2) + sd->base_atk+=val; + break; + case SP_MATK1: + if(sd->state.lr_flag != 2) + sd->matk1 += val; + break; + case SP_MATK2: + if(sd->state.lr_flag != 2) + sd->matk2 += val; + break; + case SP_MATK: + if(sd->state.lr_flag != 2) { + sd->matk1 += val; + sd->matk2 += val; + } + break; + case SP_DEF1: + if(sd->state.lr_flag != 2) + sd->def+=val; + break; + case SP_MDEF1: + if(sd->state.lr_flag != 2) + sd->mdef+=val; + break; + case SP_MDEF2: + if(sd->state.lr_flag != 2) + sd->mdef+=val; + break; + case SP_HIT: + if(sd->state.lr_flag != 2) + sd->hit+=val; + else + sd->arrow_hit+=val; + break; + case SP_FLEE1: + if(sd->state.lr_flag != 2) + sd->flee+=val; + break; + case SP_FLEE2: + if(sd->state.lr_flag != 2) + sd->flee2+=val*10; + break; + case SP_CRITICAL: + if(sd->state.lr_flag != 2) + sd->critical+=val*10; + else + sd->arrow_cri += val*10; + break; + case SP_ATKELE: + if(!sd->state.lr_flag) + sd->atk_ele=val; + else if(sd->state.lr_flag == 1) + sd->atk_ele_=val; + else if(sd->state.lr_flag == 2) + sd->arrow_ele=val; + break; + case SP_DEFELE: + if(sd->state.lr_flag != 2) + sd->def_ele=val; + break; + case SP_MAXHP: + if(sd->state.lr_flag != 2) + sd->status.max_hp+=val; + break; + case SP_MAXSP: + if(sd->state.lr_flag != 2) + sd->status.max_sp+=val; + break; + case SP_CASTRATE: + if(sd->state.lr_flag != 2) + sd->castrate+=val; + break; + case SP_MAXHPRATE: + if(sd->state.lr_flag != 2) + sd->hprate+=val; + break; + case SP_MAXSPRATE: + if(sd->state.lr_flag != 2) + sd->sprate+=val; + break; + case SP_SPRATE: + if(sd->state.lr_flag != 2) + sd->dsprate+=val; + break; + case SP_ATTACKRANGE: + if(!sd->state.lr_flag) + sd->attackrange += val; + else if(sd->state.lr_flag == 1) + sd->attackrange_ += val; + else if(sd->state.lr_flag == 2) + sd->arrow_range += val; + break; + case SP_ADD_SPEED: + if(sd->state.lr_flag != 2) + sd->speed -= val; + break; + case SP_SPEED_RATE: + if(sd->state.lr_flag != 2) { + if(sd->speed_rate > 100-val) + sd->speed_rate = 100-val; + } + break; + case SP_SPEED_ADDRATE: + if(sd->state.lr_flag != 2) + sd->speed_add_rate = sd->speed_add_rate * (100-val)/100; + break; + case SP_ASPD: + if(sd->state.lr_flag != 2) + sd->aspd -= val*10; + break; + case SP_ASPD_RATE: + if(sd->state.lr_flag != 2) { + if(sd->aspd_rate > 100-val) + sd->aspd_rate = 100-val; + } + break; + case SP_ASPD_ADDRATE: + if(sd->state.lr_flag != 2) + sd->aspd_add_rate = sd->aspd_add_rate * (100-val)/100; + break; + case SP_HP_RECOV_RATE: + if(sd->state.lr_flag != 2) + sd->hprecov_rate += val; + break; + case SP_SP_RECOV_RATE: + if(sd->state.lr_flag != 2) + sd->sprecov_rate += val; + break; + case SP_CRITICAL_DEF: + if(sd->state.lr_flag != 2) + sd->critical_def += val; + break; + case SP_NEAR_ATK_DEF: + if(sd->state.lr_flag != 2) + sd->near_attack_def_rate += val; + break; + case SP_LONG_ATK_DEF: + if(sd->state.lr_flag != 2) + sd->long_attack_def_rate += val; + break; + case SP_DOUBLE_RATE: + if(sd->state.lr_flag == 0 && sd->double_rate < val) + sd->double_rate = val; + break; + case SP_DOUBLE_ADD_RATE: + if(sd->state.lr_flag == 0) + sd->double_add_rate += val; + break; + case SP_MATK_RATE: + if(sd->state.lr_flag != 2) + sd->matk_rate += val; + break; + case SP_IGNORE_DEF_ELE: + if(!sd->state.lr_flag) + sd->ignore_def_ele |= 1<<val; + else if(sd->state.lr_flag == 1) + sd->ignore_def_ele_ |= 1<<val; + break; + case SP_IGNORE_DEF_RACE: + if(!sd->state.lr_flag) + sd->ignore_def_race |= 1<<val; + else if(sd->state.lr_flag == 1) + sd->ignore_def_race_ |= 1<<val; + break; + case SP_ATK_RATE: + if(sd->state.lr_flag != 2) + sd->atk_rate += val; + break; + case SP_MAGIC_ATK_DEF: + if(sd->state.lr_flag != 2) + sd->magic_def_rate += val; + break; + case SP_MISC_ATK_DEF: + if(sd->state.lr_flag != 2) + sd->misc_def_rate += val; + break; + case SP_IGNORE_MDEF_ELE: + if(sd->state.lr_flag != 2) + sd->ignore_mdef_ele |= 1<<val; + break; + case SP_IGNORE_MDEF_RACE: + if(sd->state.lr_flag != 2) + sd->ignore_mdef_race |= 1<<val; + break; + case SP_PERFECT_HIT_RATE: + if(sd->state.lr_flag != 2 && sd->perfect_hit < val) + sd->perfect_hit = val; + break; + case SP_PERFECT_HIT_ADD_RATE: + if(sd->state.lr_flag != 2) + sd->perfect_hit_add += val; + break; + case SP_CRITICAL_RATE: + if(sd->state.lr_flag != 2) + sd->critical_rate+=val; + break; + case SP_GET_ZENY_NUM: + if(sd->state.lr_flag != 2 && sd->get_zeny_num < val) + sd->get_zeny_num = val; + break; + case SP_ADD_GET_ZENY_NUM: + if(sd->state.lr_flag != 2) + sd->get_zeny_add_num += val; + break; + case SP_DEF_RATIO_ATK_ELE: + if(!sd->state.lr_flag) + sd->def_ratio_atk_ele |= 1<<val; + else if(sd->state.lr_flag == 1) + sd->def_ratio_atk_ele_ |= 1<<val; + break; + case SP_DEF_RATIO_ATK_RACE: + if(!sd->state.lr_flag) + sd->def_ratio_atk_race |= 1<<val; + else if(sd->state.lr_flag == 1) + sd->def_ratio_atk_race_ |= 1<<val; + break; + case SP_HIT_RATE: + if(sd->state.lr_flag != 2) + sd->hit_rate += val; + break; + case SP_FLEE_RATE: + if(sd->state.lr_flag != 2) + sd->flee_rate += val; + break; + case SP_FLEE2_RATE: + if(sd->state.lr_flag != 2) + sd->flee2_rate += val; + break; + case SP_DEF_RATE: + if(sd->state.lr_flag != 2) + sd->def_rate += val; + break; + case SP_DEF2_RATE: + if(sd->state.lr_flag != 2) + sd->def2_rate += val; + break; + case SP_MDEF_RATE: + if(sd->state.lr_flag != 2) + sd->mdef_rate += val; + break; + case SP_MDEF2_RATE: + if(sd->state.lr_flag != 2) + sd->mdef2_rate += val; + break; + case SP_RESTART_FULL_RECORVER: + if(sd->state.lr_flag != 2) + sd->special_state.restart_full_recover = 1; + break; + case SP_NO_CASTCANCEL: + if(sd->state.lr_flag != 2) + sd->special_state.no_castcancel = 1; + break; + case SP_NO_CASTCANCEL2: + if(sd->state.lr_flag != 2) + sd->special_state.no_castcancel2 = 1; + break; + case SP_NO_SIZEFIX: + if(sd->state.lr_flag != 2) + sd->special_state.no_sizefix = 1; + break; + case SP_NO_MAGIC_DAMAGE: + if(sd->state.lr_flag != 2) + sd->special_state.no_magic_damage = 1; + break; + case SP_NO_WEAPON_DAMAGE: + if(sd->state.lr_flag != 2) + sd->special_state.no_weapon_damage = 1; + break; + case SP_NO_GEMSTONE: + if(sd->state.lr_flag != 2) + sd->special_state.no_gemstone = 1; + break; + case SP_INFINITE_ENDURE: + if(sd->state.lr_flag != 2) + sd->special_state.infinite_endure = 1; + break; + case SP_SPLASH_RANGE: + if(sd->state.lr_flag != 2 && sd->splash_range < val) + sd->splash_range = val; + break; + case SP_SPLASH_ADD_RANGE: + if(sd->state.lr_flag != 2) + sd->splash_add_range += val; + break; + case SP_SHORT_WEAPON_DAMAGE_RETURN: + if(sd->state.lr_flag != 2) + sd->short_weapon_damage_return += val; + break; + case SP_LONG_WEAPON_DAMAGE_RETURN: + if(sd->state.lr_flag != 2) + sd->long_weapon_damage_return += val; + break; + case SP_MAGIC_DAMAGE_RETURN: //AppleGirl Was Here + if(sd->state.lr_flag != 2) + sd->magic_damage_return += val; + break; + case SP_ALL_STATS: // [Valaris] + if(sd->state.lr_flag!=2) { + sd->parame[SP_STR-SP_STR]+=val; + sd->parame[SP_AGI-SP_STR]+=val; + sd->parame[SP_VIT-SP_STR]+=val; + sd->parame[SP_INT-SP_STR]+=val; + sd->parame[SP_DEX-SP_STR]+=val; + sd->parame[SP_LUK-SP_STR]+=val; + clif_updatestatus(sd,13); + clif_updatestatus(sd,14); + clif_updatestatus(sd,15); + clif_updatestatus(sd,16); + clif_updatestatus(sd,17); + clif_updatestatus(sd,18); + } + break; + case SP_AGI_VIT: // [Valaris] + if(sd->state.lr_flag!=2) { + sd->parame[SP_AGI-SP_STR]+=val; + sd->parame[SP_VIT-SP_STR]+=val; + clif_updatestatus(sd,14); + clif_updatestatus(sd,15); + } + break; + case SP_AGI_DEX_STR: // [Valaris] + if(sd->state.lr_flag!=2) { + sd->parame[SP_AGI-SP_STR]+=val; + sd->parame[SP_DEX-SP_STR]+=val; + sd->parame[SP_STR-SP_STR]+=val; + clif_updatestatus(sd,14); + clif_updatestatus(sd,17); + clif_updatestatus(sd,13); + } + break; + case SP_PERFECT_HIDE: // [Valaris] + if(sd->state.lr_flag!=2) { + sd->perfect_hiding=1; + } + break; + case SP_DISGUISE: // Disguise script for items [Valaris] + if(sd->state.lr_flag!=2 && sd->disguiseflag==0) { + if(pc_isriding(sd)) { // temporary prevention of crash caused by peco + disguise, will look into a better solution [Valaris] + clif_displaymessage(sd->fd, "Cannot wear disguise when riding a Peco."); + break; + } + sd->disguise=val; + clif_clearchar(&sd->bl, 9); + pc_setpos(sd, sd->mapname, sd->bl.x, sd->bl.y, 3); + } + break; + case SP_UNBREAKABLE: + if(sd->state.lr_flag!=2) { + sd->unbreakable += val; + } + break; + default: + if(battle_config.error_log) + printf("pc_bonus: unknown type %d %d !\n",type,val); + break; + } + return 0; +} + +/*========================================== + * 装 備品による能力等のボーナス設定 + *------------------------------------------ + */ +int pc_bonus2(struct map_session_data *sd,int type,int type2,int val) +{ + int i; + + nullpo_retr(0, sd); + + switch(type){ + case SP_ADDELE: + if(!sd->state.lr_flag) + sd->addele[type2]+=val; + else if(sd->state.lr_flag == 1) + sd->addele_[type2]+=val; + else if(sd->state.lr_flag == 2) + sd->arrow_addele[type2]+=val; + break; + case SP_ADDRACE: + if(!sd->state.lr_flag) + sd->addrace[type2]+=val; + else if(sd->state.lr_flag == 1) + sd->addrace_[type2]+=val; + else if(sd->state.lr_flag == 2) + sd->arrow_addrace[type2]+=val; + break; + case SP_ADDSIZE: + if(!sd->state.lr_flag) + sd->addsize[type2]+=val; + else if(sd->state.lr_flag == 1) + sd->addsize_[type2]+=val; + else if(sd->state.lr_flag == 2) + sd->arrow_addsize[type2]+=val; + break; + case SP_SUBELE: + if(sd->state.lr_flag != 2) + sd->subele[type2]+=val; + break; + case SP_SUBRACE: + if(sd->state.lr_flag != 2) + sd->subrace[type2]+=val; + break; + case SP_ADDEFF: + if(sd->state.lr_flag != 2) + sd->addeff[type2]+=val; + else + sd->arrow_addeff[type2]+=val; + break; + case SP_ADDEFF2: + if(sd->state.lr_flag != 2) + sd->addeff2[type2]+=val; + else + sd->arrow_addeff2[type2]+=val; + break; + case SP_RESEFF: + if(sd->state.lr_flag != 2) + sd->reseff[type2]+=val; + break; + case SP_MAGIC_ADDELE: + if(sd->state.lr_flag != 2) + sd->magic_addele[type2]+=val; + break; + case SP_MAGIC_ADDRACE: + if(sd->state.lr_flag != 2) + sd->magic_addrace[type2]+=val; + break; + case SP_MAGIC_SUBRACE: + if(sd->state.lr_flag != 2) + sd->magic_subrace[type2]+=val; + break; + case SP_ADD_DAMAGE_CLASS: + if(!sd->state.lr_flag) { + for(i=0;i<sd->add_damage_class_count;i++) { + if(sd->add_damage_classid[i] == type2) { + sd->add_damage_classrate[i] += val; + break; + } + } + if(i >= sd->add_damage_class_count && sd->add_damage_class_count < 10) { + sd->add_damage_classid[sd->add_damage_class_count] = type2; + sd->add_damage_classrate[sd->add_damage_class_count] += val; + sd->add_damage_class_count++; + } + } + else if(sd->state.lr_flag == 1) { + for(i=0;i<sd->add_damage_class_count_;i++) { + if(sd->add_damage_classid_[i] == type2) { + sd->add_damage_classrate_[i] += val; + break; + } + } + if(i >= sd->add_damage_class_count_ && sd->add_damage_class_count_ < 10) { + sd->add_damage_classid_[sd->add_damage_class_count_] = type2; + sd->add_damage_classrate_[sd->add_damage_class_count_] += val; + sd->add_damage_class_count_++; + } + } + break; + case SP_ADD_MAGIC_DAMAGE_CLASS: + if(sd->state.lr_flag != 2) { + for(i=0;i<sd->add_magic_damage_class_count;i++) { + if(sd->add_magic_damage_classid[i] == type2) { + sd->add_magic_damage_classrate[i] += val; + break; + } + } + if(i >= sd->add_magic_damage_class_count && sd->add_magic_damage_class_count < 10) { + sd->add_magic_damage_classid[sd->add_magic_damage_class_count] = type2; + sd->add_magic_damage_classrate[sd->add_magic_damage_class_count] += val; + sd->add_magic_damage_class_count++; + } + } + break; + case SP_ADD_DEF_CLASS: + if(sd->state.lr_flag != 2) { + for(i=0;i<sd->add_def_class_count;i++) { + if(sd->add_def_classid[i] == type2) { + sd->add_def_classrate[i] += val; + break; + } + } + if(i >= sd->add_def_class_count && sd->add_def_class_count < 10) { + sd->add_def_classid[sd->add_def_class_count] = type2; + sd->add_def_classrate[sd->add_def_class_count] += val; + sd->add_def_class_count++; + } + } + break; + case SP_ADD_MDEF_CLASS: + if(sd->state.lr_flag != 2) { + for(i=0;i<sd->add_mdef_class_count;i++) { + if(sd->add_mdef_classid[i] == type2) { + sd->add_mdef_classrate[i] += val; + break; + } + } + if(i >= sd->add_mdef_class_count && sd->add_mdef_class_count < 10) { + sd->add_mdef_classid[sd->add_mdef_class_count] = type2; + sd->add_mdef_classrate[sd->add_mdef_class_count] += val; + sd->add_mdef_class_count++; + } + } + break; + case SP_HP_DRAIN_RATE: + if(!sd->state.lr_flag) { + sd->hp_drain_rate += type2; + sd->hp_drain_per += val; + } + else if(sd->state.lr_flag == 1) { + sd->hp_drain_rate_ += type2; + sd->hp_drain_per_ += val; + } + break; + case SP_SP_DRAIN_RATE: + if(!sd->state.lr_flag) { + sd->sp_drain_rate += type2; + sd->sp_drain_per += val; + } + else if(sd->state.lr_flag == 1) { + sd->sp_drain_rate_ += type2; + sd->sp_drain_per_ += val; + } + break; + case SP_WEAPON_COMA_ELE: + if(sd->state.lr_flag != 2) + sd->weapon_coma_ele[type2] += val; + break; + case SP_WEAPON_COMA_RACE: + if(sd->state.lr_flag != 2) + sd->weapon_coma_race[type2] += val; + break; + case SP_RANDOM_ATTACK_INCREASE: // [Valaris] + if(sd->state.lr_flag !=2){ + sd->random_attack_increase_add = type2; + sd->random_attack_increase_per += val; + break; + } // end addition + default: + if(battle_config.error_log) + printf("pc_bonus2: unknown type %d %d %d!\n",type,type2,val); + break; + } + return 0; +} + +int pc_bonus3(struct map_session_data *sd,int type,int type2,int type3,int val) +{ + int i; + switch(type){ + case SP_ADD_MONSTER_DROP_ITEM: + if(sd->state.lr_flag != 2) { + for(i=0;i<sd->monster_drop_item_count;i++) { + if(sd->monster_drop_itemid[i] == type2) { + sd->monster_drop_race[i] |= 1<<type3; + if(sd->monster_drop_itemrate[i] < val) + sd->monster_drop_itemrate[i] = val; + break; + } + } + if(i >= sd->monster_drop_item_count && sd->monster_drop_item_count < 10) { + sd->monster_drop_itemid[sd->monster_drop_item_count] = type2; + sd->monster_drop_race[sd->monster_drop_item_count] |= 1<<type3; + sd->monster_drop_itemrate[sd->monster_drop_item_count] = val; + sd->monster_drop_item_count++; + } + } + break; + case SP_AUTOSPELL: + if(sd->state.lr_flag != 2){ + sd->autospell_id = type2; + sd->autospell_lv = type3; + sd->autospell_rate = val; + } + break; + default: + if(battle_config.error_log) + printf("pc_bonus3: unknown type %d %d %d %d!\n",type,type2,type3,val); + break; + } + + return 0; +} + +/*========================================== + * スクリプトによるスキル所得 + *------------------------------------------ + */ +int pc_skill(struct map_session_data *sd,int id,int level,int flag) +{ + nullpo_retr(0, sd); + + if(level>MAX_SKILL_LEVEL){ + if(battle_config.error_log) + printf("support card skill only!\n"); + return 0; + } + if(!flag && (sd->status.skill[id].id == id || level == 0)){ // クエスト所得ならここで条件を確認して送信する + sd->status.skill[id].lv=level; + pc_calcstatus(sd,0); + clif_skillinfoblock(sd); + } + else if(sd->status.skill[id].lv < level){ // 覚えられるがlvが小さいなら + if(sd->status.skill[id].id==id) + sd->status.skill[id].flag=sd->status.skill[id].lv+2; // lvを記憶 + else { + sd->status.skill[id].id=id; + sd->status.skill[id].flag=1; // cardスキルとする + } + sd->status.skill[id].lv=level; + } + + return 0; +} + +/*========================================== + * カード挿入 + *------------------------------------------ + */ +int pc_insert_card(struct map_session_data *sd,int idx_card,int idx_equip) +{ + nullpo_retr(0, sd); + + if(idx_card >= 0 && idx_card < MAX_INVENTORY && idx_equip >= 0 && idx_equip < MAX_INVENTORY && sd->inventory_data[idx_card]) { + int i; + int nameid=sd->status.inventory[idx_equip].nameid; + int cardid=sd->status.inventory[idx_card].nameid; + int ep=sd->inventory_data[idx_card]->equip; + + if( nameid <= 0 || sd->inventory_data[idx_equip] == NULL || + (sd->inventory_data[idx_equip]->type!=4 && sd->inventory_data[idx_equip]->type!=5)|| // 装 備じゃない + ( sd->status.inventory[idx_equip].identify==0 ) || // 未鑑定 + ( sd->status.inventory[idx_equip].card[0]==0x00ff) || // 製造武器 + ( sd->status.inventory[idx_equip].card[0]==0x00fe) || + ( (sd->inventory_data[idx_equip]->equip&ep)==0 ) || // 装 備個所違い + ( sd->inventory_data[idx_equip]->type==4 && ep==32) || // 両 手武器と盾カード + ( sd->status.inventory[idx_equip].card[0]==(short)0xff00) || sd->status.inventory[idx_equip].equip){ + + clif_insert_card(sd,idx_equip,idx_card,1); + return 0; + } + for(i=0;i<sd->inventory_data[idx_equip]->slot;i++){ + if( sd->status.inventory[idx_equip].card[i] == 0){ + // 空きスロットがあったので差し込む + sd->status.inventory[idx_equip].card[i]=cardid; + + // カードは減らす + clif_insert_card(sd,idx_equip,idx_card,0); + pc_delitem(sd,idx_card,1,1); + return 0; + } + } + } + else + clif_insert_card(sd,idx_equip,idx_card,1); + + return 0; +} + +// +// アイテム物 +// + +/*========================================== + * スキルによる買い値修正 + *------------------------------------------ + */ +int pc_modifybuyvalue(struct map_session_data *sd,int orig_value) +{ + int skill,val = orig_value,rate1 = 0,rate2 = 0; + if((skill=pc_checkskill(sd,MC_DISCOUNT))>0) // ディスカウント + rate1 = 5+skill*2-((skill==10)? 1:0); + if((skill=pc_checkskill(sd,RG_COMPULSION))>0) // コムパルションディスカウント + rate2 = 5+skill*4; + if(rate1 < rate2) rate1 = rate2; + if(rate1) + val = (int)((double)orig_value*(double)(100-rate1)/100.); + if(val < 0) val = 0; + if(orig_value > 0 && val < 1) val = 1; + + return val; +} + +/*========================================== + * スキルによる売り値修正 + *------------------------------------------ + */ +int pc_modifysellvalue(struct map_session_data *sd,int orig_value) +{ + int skill,val = orig_value,rate = 0; + if((skill=pc_checkskill(sd,MC_OVERCHARGE))>0) // オーバーチャージ + rate = 5+skill*2-((skill==10)? 1:0); + if(rate) + val = (int)((double)orig_value*(double)(100+rate)/100.); + if(val < 0) val = 0; + if(orig_value > 0 && val < 1) val = 1; + + return val; +} + +/*========================================== + * アイテムを買った時に、新しいアイテム欄を使うか、 + * 3万個制限にかかるか確認 + *------------------------------------------ + */ +int pc_checkadditem(struct map_session_data *sd,int nameid,int amount) +{ + int i; + + nullpo_retr(0, sd); + + if(itemdb_isequip(nameid)) + return ADDITEM_NEW; + + for(i=0;i<MAX_INVENTORY;i++){ + if(sd->status.inventory[i].nameid==nameid){ + if(sd->status.inventory[i].amount+amount > MAX_AMOUNT) + return ADDITEM_OVERAMOUNT; + return ADDITEM_EXIST; + } + } + + if(amount > MAX_AMOUNT) + return ADDITEM_OVERAMOUNT; + return ADDITEM_NEW; +} + +/*========================================== + * 空きアイテム欄の個数 + *------------------------------------------ + */ +int pc_inventoryblank(struct map_session_data *sd) +{ + int i,b; + + nullpo_retr(0, sd); + + for(i=0,b=0;i<MAX_INVENTORY;i++){ + if(sd->status.inventory[i].nameid==0) + b++; + } + + return b; +} + +/*========================================== + * お金を払う + *------------------------------------------ + */ +int pc_payzeny(struct map_session_data *sd,int zeny) +{ + double z; + + nullpo_retr(0, sd); + + z = (double)sd->status.zeny; + if(sd->status.zeny<zeny || z - (double)zeny > MAX_ZENY) + return 1; + sd->status.zeny-=zeny; + clif_updatestatus(sd,SP_ZENY); + + return 0; +} + +/*========================================== + * お金を得る + *------------------------------------------ + */ +int pc_getzeny(struct map_session_data *sd,int zeny) +{ + double z; + + nullpo_retr(0, sd); + + z = (double)sd->status.zeny; + if(z + (double)zeny > MAX_ZENY) { + zeny = 0; + sd->status.zeny = MAX_ZENY; + } + sd->status.zeny+=zeny; + clif_updatestatus(sd,SP_ZENY); + + return 0; +} + +/*========================================== + * アイテムを探して、インデックスを返す + *------------------------------------------ + */ +int pc_search_inventory(struct map_session_data *sd,int item_id) +{ + int i; + + nullpo_retr(-1, sd); + + for(i=0;i<MAX_INVENTORY;i++) { + if(sd->status.inventory[i].nameid == item_id && + (sd->status.inventory[i].amount > 0 || item_id == 0)) + return i; + } + + return -1; +} + +/*========================================== + * アイテム追加。個数のみitem構造体の数字を無視 + *------------------------------------------ + */ +int pc_additem(struct map_session_data *sd,struct item *item_data,int amount) +{ + struct item_data *data; + int i,w; + + nullpo_retr(1, sd); + nullpo_retr(1, item_data); + + if(item_data->nameid <= 0 || amount <= 0) + return 1; + data = itemdb_search(item_data->nameid); + if((w = data->weight*amount) + sd->weight > sd->max_weight) + return 2; + + i = MAX_INVENTORY; + + if(!itemdb_isequip2(data)){ + // 装 備品ではないので、既所有品なら個数のみ変化させる + for(i=0;i<MAX_INVENTORY;i++) + if(sd->status.inventory[i].nameid == item_data->nameid && + sd->status.inventory[i].card[0] == item_data->card[0] && sd->status.inventory[i].card[1] == item_data->card[1] && + sd->status.inventory[i].card[2] == item_data->card[2] && sd->status.inventory[i].card[3] == item_data->card[3]) { + if(sd->status.inventory[i].amount+amount > MAX_AMOUNT) + return 5; + sd->status.inventory[i].amount+=amount; + clif_additem(sd,i,amount,0); + break; + } + } + if(i >= MAX_INVENTORY){ + // 装 備品か未所有品だったので空き欄へ追加 + i = pc_search_inventory(sd,0); + if(i >= 0) { + memcpy(&sd->status.inventory[i],item_data,sizeof(sd->status.inventory[0])); + sd->status.inventory[i].amount=amount; + sd->inventory_data[i]=data; + clif_additem(sd,i,amount,0); + } + else return 4; + } + sd->weight += w; + clif_updatestatus(sd,SP_WEIGHT); + + return 0; +} + +/*========================================== + * アイテムを減らす + *------------------------------------------ + */ +int pc_delitem(struct map_session_data *sd,int n,int amount,int type) +{ + nullpo_retr(1, sd); + + if(sd->status.inventory[n].nameid==0 || amount <= 0 || sd->status.inventory[n].amount<amount || sd->inventory_data[n] == NULL) + return 1; + + sd->status.inventory[n].amount -= amount; + sd->weight -= sd->inventory_data[n]->weight*amount ; + if(sd->status.inventory[n].amount<=0){ + if(sd->status.inventory[n].equip) + pc_unequipitem(sd,n,0); + memset(&sd->status.inventory[n],0,sizeof(sd->status.inventory[0])); + sd->inventory_data[n] = NULL; + } + if(!(type&1)) + clif_delitem(sd,n,amount); + if(!(type&2)) + clif_updatestatus(sd,SP_WEIGHT); + + return 0; +} + +/*========================================== + * アイテムを落す + *------------------------------------------ + */ +int pc_dropitem(struct map_session_data *sd,int n,int amount) +{ + nullpo_retr(1, sd); + + if (sd->status.inventory[n].nameid <= 0 || + sd->status.inventory[n].amount < amount || + sd->trade_partner != 0 || sd->vender_id != 0 || + sd->status.inventory[n].amount <= 0) + return 1; + map_addflooritem(&sd->status.inventory[n], amount, sd->bl.m, sd->bl.x, sd->bl.y, NULL, NULL, NULL, 0); + pc_delitem(sd, n, amount, 0); + + return 0; +} + +/*========================================== + * アイテムを拾う + *------------------------------------------ + */ +int pc_takeitem(struct map_session_data *sd,struct flooritem_data *fitem) +{ + int flag; + unsigned int tick = gettick(); + struct map_session_data *first_sd = NULL,*second_sd = NULL,*third_sd = NULL; + + nullpo_retr(0, sd); + nullpo_retr(0, fitem); + + if(fitem->first_get_id > 0) { + first_sd = map_id2sd(fitem->first_get_id); + if(tick < fitem->first_get_tick) { + if(fitem->first_get_id != sd->bl.id && !(first_sd && first_sd->status.party_id == sd->status.party_id)) { + clif_additem(sd,0,0,6); + return 0; + } + } + else if(fitem->second_get_id > 0) { + second_sd = map_id2sd(fitem->second_get_id); + if(tick < fitem->second_get_tick) { + if(fitem->first_get_id != sd->bl.id && fitem->second_get_id != sd->bl.id && + !(first_sd && first_sd->status.party_id == sd->status.party_id) && !(second_sd && second_sd->status.party_id == sd->status.party_id)) { + clif_additem(sd,0,0,6); + return 0; + } + } + else if(fitem->third_get_id > 0) { + third_sd = map_id2sd(fitem->third_get_id); + if(tick < fitem->third_get_tick) { + if(fitem->first_get_id != sd->bl.id && fitem->second_get_id != sd->bl.id && fitem->third_get_id != sd->bl.id && + !(first_sd && first_sd->status.party_id == sd->status.party_id) && !(second_sd && second_sd->status.party_id == sd->status.party_id) && + !(third_sd && third_sd->status.party_id == sd->status.party_id)) { + clif_additem(sd,0,0,6); + return 0; + } + } + } + } + } + if((flag = pc_additem(sd,&fitem->item_data,fitem->item_data.amount))) + // 重量overで取得失敗 + clif_additem(sd,0,0,flag); + else { + /* 取得成功 */ + if(sd->attacktimer != -1) + pc_stopattack(sd); + clif_takeitem(&sd->bl,&fitem->bl); + map_clearflooritem(fitem->bl.id); + } + return 0; +} + +int pc_isUseitem(struct map_session_data *sd,int n) +{ + struct item_data *item; + int nameid; + + nullpo_retr(0, sd); + + item = sd->inventory_data[n]; + nameid = sd->status.inventory[n].nameid; + + if(item == NULL) + return 0; + if((nameid == 605) && map[sd->bl.m].flag.gvg) + return 0; + if(nameid == 601 && (map[sd->bl.m].flag.noteleport || map[sd->bl.m].flag.gvg)) { + clif_skill_teleportmessage(sd,0); + return 0; + } + if(nameid == 602 && map[sd->bl.m].flag.noreturn) + return 0; + if(nameid == 604 && (map[sd->bl.m].flag.nobranch || map[sd->bl.m].flag.gvg)) + return 0; + if(item->sex != 2 && sd->status.sex != item->sex) + return 0; + if(item->elv > 0 && sd->status.base_level < item->elv) + return 0; + if(((sd->status.class==13 || sd->status.class==4014) && ((1<<7)&item->class) == 0) || // have mounted classes use unmounted items [Valaris] + ((sd->status.class==21 || sd->status.class==4022) && ((1<<14)&item->class) == 0)) + return 0; + if(sd->status.class!=13 && sd->status.class!=4014 && sd->status.class!=21 && sd->status.class!=4022) + if((sd->status.class<=4000 && ((1<<sd->status.class)&item->class) == 0) || (sd->status.class>4000 && sd->status.class<4023 && ((1<<(sd->status.class-4001))&item->class) == 0) || + (sd->status.class>=4023 && ((1<<(sd->status.class-4023))&item->class) == 0)) + return 0; + return 1; +} + +/*========================================== + * アイテムを使う + *------------------------------------------ + */ +int pc_useitem(struct map_session_data *sd,int n) +{ + int nameid,amount; + + nullpo_retr(1, sd); + + if(n >=0 && n < MAX_INVENTORY) { + nameid = sd->status.inventory[n].nameid; + amount = sd->status.inventory[n].amount; + if(sd->status.inventory[n].nameid <= 0 || + sd->status.inventory[n].amount <= 0 || + sd->sc_data[SC_BERSERK].timer!=-1 || + !pc_isUseitem(sd,n) ) { + clif_useitemack(sd,n,0,0); + return 1; + } + if(sd->inventory_data[n]) + run_script(sd->inventory_data[n]->use_script,0,sd->bl.id,0); + + clif_useitemack(sd,n,amount-1,1); + pc_delitem(sd,n,1,1); + } + + return 0; +} + +/*========================================== + * カートアイテム追加。個数のみitem構造体の数字を無視 + *------------------------------------------ + */ +int pc_cart_additem(struct map_session_data *sd,struct item *item_data,int amount) +{ + struct item_data *data; + int i,w; + + nullpo_retr(1, sd); + nullpo_retr(1, item_data); + + if(item_data->nameid <= 0 || amount <= 0) + return 1; + data = itemdb_search(item_data->nameid); + + if((w=data->weight*amount) + sd->cart_weight > sd->cart_max_weight) + return 1; + + i=MAX_CART; + if(!itemdb_isequip2(data)){ + // 装 備品ではないので、既所有品なら個数のみ変化させる + for(i=0;i<MAX_CART;i++){ + if(sd->status.cart[i].nameid==item_data->nameid && + sd->status.cart[i].card[0] == item_data->card[0] && sd->status.cart[i].card[1] == item_data->card[1] && + sd->status.cart[i].card[2] == item_data->card[2] && sd->status.cart[i].card[3] == item_data->card[3]){ + if(sd->status.cart[i].amount+amount > MAX_AMOUNT) + return 1; + sd->status.cart[i].amount+=amount; + clif_cart_additem(sd,i,amount,0); + break; + } + } + } + if(i >= MAX_CART){ + // 装 備品か未所有品だったので空き欄へ追加 + for(i=0;i<MAX_CART;i++){ + if(sd->status.cart[i].nameid==0){ + memcpy(&sd->status.cart[i],item_data,sizeof(sd->status.cart[0])); + sd->status.cart[i].amount=amount; + sd->cart_num++; + clif_cart_additem(sd,i,amount,0); + break; + } + } + if(i >= MAX_CART) + return 1; + } + sd->cart_weight += w; + clif_updatestatus(sd,SP_CARTINFO); + + return 0; +} + +/*========================================== + * カートアイテムを減らす + *------------------------------------------ + */ +int pc_cart_delitem(struct map_session_data *sd,int n,int amount,int type) +{ + nullpo_retr(1, sd); + + if(sd->status.cart[n].nameid==0 || + sd->status.cart[n].amount<amount) + return 1; + + sd->status.cart[n].amount -= amount; + sd->cart_weight -= itemdb_weight(sd->status.cart[n].nameid)*amount ; + if(sd->status.cart[n].amount <= 0){ + memset(&sd->status.cart[n],0,sizeof(sd->status.cart[0])); + sd->cart_num--; + } + if(!type) { + clif_cart_delitem(sd,n,amount); + clif_updatestatus(sd,SP_CARTINFO); + } + + return 0; +} + +/*========================================== + * カートへアイテム移動 + *------------------------------------------ + */ +int pc_putitemtocart(struct map_session_data *sd,int idx,int amount) { + struct item *item_data; + + nullpo_retr(0, sd); + nullpo_retr(0, item_data = &sd->status.inventory[idx]); + + if (item_data->nameid==0 || item_data->amount<amount || sd->vender_id) + return 1; + if (pc_cart_additem(sd,item_data,amount) == 0) + return pc_delitem(sd,idx,amount,0); + + return 1; +} + +/*========================================== + * カート内のアイテム数確認(個数の差分を返す) + *------------------------------------------ + */ +int pc_cartitem_amount(struct map_session_data *sd,int idx,int amount) +{ + struct item *item_data; + + nullpo_retr(-1, sd); + nullpo_retr(-1, item_data=&sd->status.cart[idx]); + + if( item_data->nameid==0 || !item_data->amount) + return -1; + return item_data->amount-amount; +} +/*========================================== + * カートからアイテム移動 + *------------------------------------------ + */ + +int pc_getitemfromcart(struct map_session_data *sd,int idx,int amount) +{ + struct item *item_data; + int flag; + + nullpo_retr(0, sd); + nullpo_retr(0, item_data=&sd->status.cart[idx]); + + if( item_data->nameid==0 || item_data->amount<amount || sd->vender_id ) + return 1; + if((flag = pc_additem(sd,item_data,amount)) == 0) + return pc_cart_delitem(sd,idx,amount,0); + + clif_additem(sd,0,0,flag); + return 1; +} + +/*========================================== + * アイテム鑑定 + *------------------------------------------ + */ +int pc_item_identify(struct map_session_data *sd,int idx) +{ + int flag=1; + + nullpo_retr(0, sd); + + if(idx >= 0 && idx < MAX_INVENTORY) { + if(sd->status.inventory[idx].nameid > 0 && sd->status.inventory[idx].identify == 0 ){ + flag=0; + sd->status.inventory[idx].identify=1; + } + clif_item_identified(sd,idx,flag); + } + else + clif_item_identified(sd,idx,flag); + + return !flag; +} + +/*========================================== + * スティル品公開 + *------------------------------------------ + */ +int pc_show_steal(struct block_list *bl,va_list ap) +{ + struct map_session_data *sd; + int itemid; + int type; + + struct item_data *item=NULL; + char output[100]; + + nullpo_retr(0, bl); + nullpo_retr(0, ap); + nullpo_retr(0, sd=va_arg(ap,struct map_session_data *)); + + itemid=va_arg(ap,int); + type=va_arg(ap,int); + + if(!type){ + if((item=itemdb_exists(itemid))==NULL) + sprintf(output,"%s stole an Unknown_Item.",sd->status.name); + else + sprintf(output,"%s stole %s.",sd->status.name,item->jname); + clif_displaymessage( ((struct map_session_data *)bl)->fd, output); + }else{ + sprintf(output,"%s has not stolen the item because of being overweight.",sd->status.name); + clif_displaymessage( ((struct map_session_data *)bl)->fd, output); + } + + return 0; +} +/*========================================== + * + *------------------------------------------ + */ +//** pc.c: Small Steal Item fix by fritz +int pc_steal_item(struct map_session_data *sd,struct block_list *bl) +{ + if(sd != NULL && bl != NULL && bl->type == BL_MOB) { + int i,skill,rate,itemid,flag, count; + struct mob_data *md; + md=(struct mob_data *)bl; + if(!md->state.steal_flag && mob_db[md->class].mexp <= 0 && !(mob_db[md->class].mode&0x20) && md->sc_data[SC_STONE].timer == -1 && md->sc_data[SC_FREEZE].timer == -1 && + (!(md->class>1324 && md->class<1364))) // prevent stealing from treasure boxes [Valaris] + { + skill = sd->paramc[4] - mob_db[md->class].dex + pc_checkskill(sd,TF_STEAL) + 10; + + if(0 < skill) + { + for(count = 8; count <= 8 && count != 0; count--) + { + i = rand()%8; + itemid = mob_db[md->class].dropitem[i].nameid; + + if(itemid > 0 && itemdb_type(itemid) != 6) + { + rate = (mob_db[md->class].dropitem[i].p / battle_config.item_rate_common * 100 * skill)/100; + + if(rand()%10000 < rate) + { + struct item tmp_item; + memset(&tmp_item,0,sizeof(tmp_item)); + tmp_item.nameid = itemid; + tmp_item.amount = 1; + tmp_item.identify = 1; + flag = pc_additem(sd,&tmp_item,1); + if(battle_config.show_steal_in_same_party) + { + party_foreachsamemap(pc_show_steal,sd,1,sd,tmp_item.nameid,0); + } + + if(flag) + { + if(battle_config.show_steal_in_same_party) + { + party_foreachsamemap(pc_show_steal,sd,1,sd,tmp_item.nameid,1); + } + + clif_additem(sd,0,0,flag); + } + md->state.steal_flag = 1; + return 1; + } + } + } + } + } + } + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int pc_steal_coin(struct map_session_data *sd,struct block_list *bl) +{ + if(sd != NULL && bl != NULL && bl->type == BL_MOB) { + int rate,skill; + struct mob_data *md=(struct mob_data *)bl; + if(md && !md->state.steal_coin_flag && md->sc_data && md->sc_data[SC_STONE].timer == -1 && md->sc_data[SC_FREEZE].timer == -1) { + skill = pc_checkskill(sd,RG_STEALCOIN)*10; + rate = skill + (sd->status.base_level - mob_db[md->class].lv)*3 + sd->paramc[4]*2 + sd->paramc[5]*2; + if(rand()%1000 < rate) { + pc_getzeny(sd,mob_db[md->class].lv*10 + rand()%100); + md->state.steal_coin_flag = 1; + return 1; + } + } + } + + return 0; +} +// +// +// +/*========================================== + * PCの位置設定 + *------------------------------------------ + */ +int pc_setpos(struct map_session_data *sd,char *mapname_org,int x,int y,int clrtype) +{ + char mapname[24]; + int m=0,c=0,disguise=0; + + nullpo_retr(0, sd); + + if(sd->chatID) // チャットから出る + chat_leavechat(sd); + if(sd->trade_partner) // 取引を中断する + trade_tradecancel(sd); + if(sd->state.storage_flag) + storage_guild_storage_quit(sd,0); + else + storage_storage_quit(sd); // 倉庫を開いてるなら保存する + + if(sd->party_invite>0) // パーティ勧誘を拒否する + party_reply_invite(sd,sd->party_invite_account,0); + if(sd->guild_invite>0) // ギルド勧誘を拒否する + guild_reply_invite(sd,sd->guild_invite,0); + if(sd->guild_alliance>0) // ギルド同盟勧誘を拒否する + guild_reply_reqalliance(sd,sd->guild_alliance_account,0); + + skill_castcancel(&sd->bl,0); // 詠唱中断 + pc_stop_walking(sd,0); // 歩行中断 + pc_stopattack(sd); // 攻撃中断 + + if(pc_issit(sd)) { + pc_setstand(sd); + skill_gangsterparadise(sd,0); + } + + if(sd->sc_data[SC_TRICKDEAD].timer != -1) + skill_status_change_end(&sd->bl, SC_TRICKDEAD, -1); + if(sd->status.option&2) + skill_status_change_end(&sd->bl, SC_HIDING, -1); + if(sd->status.option&4) + skill_status_change_end(&sd->bl, SC_CLOAKING, -1); + if(sd->status.option&16386) + skill_status_change_end(&sd->bl, SC_CHASEWALK, -1); + if(sd->sc_data[SC_BLADESTOP].timer!=-1) + skill_status_change_end(&sd->bl,SC_BLADESTOP,-1); + if(sd->sc_data[SC_DANCING].timer!=-1) // clear dance effect when warping [Valaris] + skill_stop_dancing(&sd->bl,0); + + if(sd->status.pet_id > 0 && sd->pd && sd->pet.intimate > 0) { + pet_stopattack(sd->pd); + pet_changestate(sd->pd,MS_IDLE,0); + } + + if(sd->disguise) { // clear disguises when warping [Valaris] + clif_clearchar(&sd->bl, 9); + disguise=sd->disguise; + sd->disguise=0; + } + + memcpy(mapname,mapname_org,24); + mapname[16]=0; + if(strstr(mapname,".gat")==NULL && strlen(mapname)<16){ + strcat(mapname,".gat"); + } + + m=map_mapname2mapid(mapname); + if(m<0){ + if(sd->mapname[0]){ + int ip,port; + if(map_mapname2ipport(mapname,&ip,&port)==0){ + skill_stop_dancing(&sd->bl,1); + skill_unit_out_all(&sd->bl,gettick(),1); + clif_clearchar_area(&sd->bl,clrtype&0xffff); + skill_gangsterparadise(sd,0); + map_delblock(&sd->bl); + if(sd->status.pet_id > 0 && sd->pd) { + if(sd->pd->bl.m != m && sd->pet.intimate <= 0) { + pet_remove_map(sd); + intif_delete_petdata(sd->status.pet_id); + sd->status.pet_id = 0; + sd->pd = NULL; + sd->petDB = NULL; + if(battle_config.pet_status_support) + pc_calcstatus(sd,2); + } + else if(sd->pet.intimate > 0) { + pet_stopattack(sd->pd); + pet_changestate(sd->pd,MS_IDLE,0); + clif_clearchar_area(&sd->pd->bl,clrtype&0xffff); + map_delblock(&sd->pd->bl); + } + } + memcpy(sd->mapname,mapname,24); + sd->bl.x=x; + sd->bl.y=y; + sd->state.waitingdisconnect=1; + pc_makesavestatus(sd); + if(sd->status.pet_id > 0 && sd->pd) + intif_save_petdata(sd->status.account_id,&sd->pet); + chrif_save(sd); + storage_storage_save(sd); + chrif_changemapserver(sd, mapname, x, y, ip, port); + return 0; + } + } +#if 0 + clif_authfail_fd(sd->fd,0); // cancel + clif_setwaitclose(sd->fd); +#endif + return 1; + } + + if(x <0 || x >= map[m].xs || y <0 || y >= map[m].ys) + x=y=0; + if((x==0 && y==0) || (c=read_gat(m,x,y))==1 || c==5){ + if(x||y) { + if(battle_config.error_log) + printf("stacked (%d,%d)\n",x,y); + } + do { + x=rand()%(map[m].xs-2)+1; + y=rand()%(map[m].ys-2)+1; + } while((c=read_gat(m,x,y))==1 || c==5); + } + + if(sd->mapname[0] && sd->bl.prev != NULL){ + skill_unit_out_all(&sd->bl,gettick(),1); + clif_clearchar_area(&sd->bl,clrtype&0xffff); + skill_gangsterparadise(sd,0); + map_delblock(&sd->bl); + // pet + if(sd->status.pet_id > 0 && sd->pd) { + if(sd->pd->bl.m != m && sd->pet.intimate <= 0) { + pet_remove_map(sd); + intif_delete_petdata(sd->status.pet_id); + sd->status.pet_id = 0; + sd->pd = NULL; + sd->petDB = NULL; + if(battle_config.pet_status_support) + pc_calcstatus(sd,2); + pc_makesavestatus(sd); + chrif_save(sd); + storage_storage_save(sd); + } + else if(sd->pet.intimate > 0) { + pet_stopattack(sd->pd); + pet_changestate(sd->pd,MS_IDLE,0); + clif_clearchar_area(&sd->pd->bl,clrtype&0xffff); + map_delblock(&sd->pd->bl); + } + } + clif_changemap(sd,map[m].name,x,y); // [MouseJstr] + } + + if(disguise) // disguise teleport fix [Valaris] + sd->disguise=disguise; + + memcpy(sd->mapname,mapname,24); + sd->bl.m = m; + sd->to_x = x; + sd->to_y = y; + + // moved and changed dance effect stopping + + sd->bl.x = x; + sd->bl.y = y; + + if(sd->status.pet_id > 0 && sd->pd && sd->pet.intimate > 0) { + sd->pd->bl.m = m; + sd->pd->bl.x = sd->pd->to_x = x; + sd->pd->bl.y = sd->pd->to_y = y; + sd->pd->dir = sd->dir; + } + +// map_addblock(&sd->bl); /// ブロック登録とspawnは +// clif_spawnpc(sd); + + return 0; +} + +/*========================================== + * PCのランダムワープ + *------------------------------------------ + */ +int pc_randomwarp(struct map_session_data *sd, int type) { + int x,y,c,i=0; + int m; + + nullpo_retr(0, sd); + + m=sd->bl.m; + + if (map[sd->bl.m].flag.noteleport) // テレポート禁止 + return 0; + + do{ + x=rand()%(map[m].xs-2)+1; + y=rand()%(map[m].ys-2)+1; + } while (((c=read_gat(m,x,y)) == 1 || c == 5) && (i++) < 1000); + + if (i < 1000) + pc_setpos(sd,map[m].name,x,y,type); + + return 0; +} + +/*========================================== + * 現在位置のメモ + *------------------------------------------ + */ +int pc_memo(struct map_session_data *sd, int i) { + int skill; + int j; + + nullpo_retr(0, sd); + + skill = pc_checkskill(sd, AL_WARP); + + if (i >= MIN_PORTAL_MEMO) + i -= MIN_PORTAL_MEMO; + else if (map[sd->bl.m].flag.nomemo || (map[sd->bl.m].flag.nowarpto && battle_config.any_warp_GM_min_level > pc_isGM(sd))) { + clif_skill_teleportmessage(sd, 1); + return 0; + } + + if (skill < 1) { + clif_skill_memo(sd,2); + } + + if (skill < 2 || i < -1 || i > 2) { + clif_skill_memo(sd, 1); + return 0; + } + + for(j = 0 ; j < 3; j++) { + if (strcmp(sd->status.memo_point[j].map, map[sd->bl.m].name) == 0) { + i = j; + break; + } + } + + if (i == -1) { + for(i = skill - 3; i >= 0; i--) { + memcpy(&sd->status.memo_point[i+1],&sd->status.memo_point[i], + sizeof(struct point)); + } + i = 0; + } + memcpy(sd->status.memo_point[i].map, map[sd->bl.m].name, 24); + sd->status.memo_point[i].x = sd->bl.x; + sd->status.memo_point[i].y = sd->bl.y; + + clif_skill_memo(sd, 0); + + return 1; +} + +/*========================================== + * + *------------------------------------------ + */ +int pc_can_reach(struct map_session_data *sd,int x,int y) +{ + struct walkpath_data wpd; + + nullpo_retr(0, sd); + + if( sd->bl.x==x && sd->bl.y==y ) // 同じマス + return 1; + + // 障害物判定 + wpd.path_len=0; + wpd.path_pos=0; + wpd.path_half=0; + return (path_search(&wpd,sd->bl.m,sd->bl.x,sd->bl.y,x,y,0)!=-1)?1:0; +} + +// +// 歩 行物 +// +/*========================================== + * 次の1歩にかかる時間を計算 + *------------------------------------------ + */ +static int calc_next_walk_step(struct map_session_data *sd) +{ + nullpo_retr(0, sd); + + if(sd->walkpath.path_pos>=sd->walkpath.path_len) + return -1; + if(sd->walkpath.path[sd->walkpath.path_pos]&1) + return sd->speed*14/10; + + return sd->speed; +} + +/*========================================== + * 半歩進む(timer関数) + *------------------------------------------ + */ +static int pc_walk(int tid,unsigned int tick,int id,int data) +{ + struct map_session_data *sd; + int i,ctype; + int moveblock; + int x,y,dx,dy; + + sd=map_id2sd(id); + if(sd==NULL) + return 0; + + if(sd->walktimer != tid){ + if(battle_config.error_log) + printf("pc_walk %d != %d\n",sd->walktimer,tid); + return 0; + } + sd->walktimer=-1; + if(sd->walkpath.path_pos>=sd->walkpath.path_len || sd->walkpath.path_pos!=data) + return 0; + + //歩いたので息吹のタイマーを初期化 + sd->inchealspirithptick = 0; + sd->inchealspiritsptick = 0; + + sd->walkpath.path_half ^= 1; + if(sd->walkpath.path_half==0){ // マス目中心へ到着 + sd->walkpath.path_pos++; + if(sd->state.change_walk_target){ + pc_walktoxy_sub(sd); + return 0; + } + } else { // マス目境界へ到着 + if(sd->walkpath.path[sd->walkpath.path_pos]>=8) + return 1; + + x = sd->bl.x; + y = sd->bl.y; + ctype = map_getcell(sd->bl.m,x,y); + if(ctype == 1 || ctype == 5) { + pc_stop_walking(sd,1); + return 0; + } + sd->dir=sd->head_dir=sd->walkpath.path[sd->walkpath.path_pos]; + dx = dirx[(int)sd->dir]; + dy = diry[(int)sd->dir]; + ctype = map_getcell(sd->bl.m,x+dx,y+dy); + if(ctype == 1 || ctype == 5) { + pc_walktoxy_sub(sd); + return 0; + } + + moveblock = ( x/BLOCK_SIZE != (x+dx)/BLOCK_SIZE || y/BLOCK_SIZE != (y+dy)/BLOCK_SIZE); + + sd->walktimer = 1; + map_foreachinmovearea(clif_pcoutsight,sd->bl.m,x-AREA_SIZE,y-AREA_SIZE,x+AREA_SIZE,y+AREA_SIZE,dx,dy,0,sd); + + x += dx; + y += dy; + + if(moveblock) map_delblock(&sd->bl); + sd->bl.x = x; + sd->bl.y = y; + if(moveblock) map_addblock(&sd->bl); + + if(sd->sc_data[SC_DANCING].timer!=-1) + skill_unit_move_unit_group((struct skill_unit_group *)sd->sc_data[SC_DANCING].val2,sd->bl.m,dx,dy); + + map_foreachinmovearea(clif_pcinsight,sd->bl.m,x-AREA_SIZE,y-AREA_SIZE,x+AREA_SIZE,y+AREA_SIZE,-dx,-dy,0,sd); + sd->walktimer = -1; + + if(sd->status.party_id>0){ // パーティのHP情報通知検査 + struct party *p=party_search(sd->status.party_id); + if(p!=NULL){ + int p_flag=0; + map_foreachinmovearea(party_send_hp_check,sd->bl.m,x-AREA_SIZE,y-AREA_SIZE,x+AREA_SIZE,y+AREA_SIZE,-dx,-dy,BL_PC,sd->status.party_id,&p_flag); + if(p_flag) + sd->party_hp=-1; + } + } + if(sd->status.option&4) // クローキングの消滅検査 + skill_check_cloaking(&sd->bl); + /* ディボーション検査 */ + for(i=0;i<5;i++) + if(sd->dev.val1[i]){ + skill_devotion3(&sd->bl,sd->dev.val1[i]); + break; + } + /* 被ディボーション検査 */ + if( sd->sc_data && sd->sc_data[SC_DEVOTION].val1){ + skill_devotion2(&sd->bl,sd->sc_data[SC_DEVOTION].val1); + } + + skill_unit_move(&sd->bl,tick,1); // スキルユニットの検査 + + if(map_getcell(sd->bl.m,x,y)&0x80) + npc_touch_areanpc(sd,sd->bl.m,x,y); + else + sd->areanpc_id=0; + } + if((i=calc_next_walk_step(sd))>0) { + i = i>>1; + if(i < 1 && sd->walkpath.path_half == 0) + i = 1; + sd->walktimer=add_timer(tick+i,pc_walk,id,sd->walkpath.path_pos); + } + + return 0; +} + +/*========================================== + * 移動可能か確認して、可能なら歩行開始 + *------------------------------------------ + */ +static int pc_walktoxy_sub(struct map_session_data *sd) +{ + struct walkpath_data wpd; + int i; + + nullpo_retr(1, sd); + + if(path_search(&wpd,sd->bl.m,sd->bl.x,sd->bl.y,sd->to_x,sd->to_y,0)) + return 1; + memcpy(&sd->walkpath,&wpd,sizeof(wpd)); + + clif_walkok(sd); + sd->state.change_walk_target=0; + + if((i=calc_next_walk_step(sd))>0){ + i = i>>2; + sd->walktimer=add_timer(gettick()+i,pc_walk,sd->bl.id,0); + } + clif_movechar(sd); + + return 0; +} + +/*========================================== + * pc歩 行要求 + *------------------------------------------ + */ +int pc_walktoxy(struct map_session_data *sd,int x,int y) +{ + + nullpo_retr(0, sd); + + sd->to_x=x; + sd->to_y=y; + + if(sd->walktimer != -1 && sd->state.change_walk_target==0){ + // 現在歩いている最中の目的地変更なのでマス目の中心に来た時に + // timer関数からpc_walktoxy_subを呼ぶようにする + sd->state.change_walk_target=1; + } else { + pc_walktoxy_sub(sd); + } + + return 0; +} + +/*========================================== + * 歩 行停止 + *------------------------------------------ + */ +int pc_stop_walking(struct map_session_data *sd,int type) +{ + nullpo_retr(0, sd); + + if(sd->walktimer != -1) { + delete_timer(sd->walktimer,pc_walk); + sd->walktimer=-1; + } + sd->walkpath.path_len=0; + sd->to_x = sd->bl.x; + sd->to_y = sd->bl.y; + if(type&0x01) + clif_fixpos(&sd->bl); + if(type&0x02 && battle_config.pc_damage_delay) { + unsigned int tick = gettick(); + int delay = battle_get_dmotion(&sd->bl); + if(sd->canmove_tick < tick) + sd->canmove_tick = tick + delay; + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int pc_movepos(struct map_session_data *sd,int dst_x,int dst_y) +{ + int moveblock; + int dx,dy,dist; + + struct walkpath_data wpd; + + nullpo_retr(0, sd); + + if(path_search(&wpd,sd->bl.m,sd->bl.x,sd->bl.y,dst_x,dst_y,0)) + return 1; + + sd->dir = sd->head_dir = map_calc_dir(&sd->bl, dst_x,dst_y); + + dx = dst_x - sd->bl.x; + dy = dst_y - sd->bl.y; + dist = distance(sd->bl.x,sd->bl.y,dst_x,dst_y); + + moveblock = ( sd->bl.x/BLOCK_SIZE != dst_x/BLOCK_SIZE || sd->bl.y/BLOCK_SIZE != dst_y/BLOCK_SIZE); + + map_foreachinmovearea(clif_pcoutsight,sd->bl.m,sd->bl.x-AREA_SIZE,sd->bl.y-AREA_SIZE,sd->bl.x+AREA_SIZE,sd->bl.y+AREA_SIZE,dx,dy,0,sd); + + if(moveblock) map_delblock(&sd->bl); + sd->bl.x = dst_x; + sd->bl.y = dst_y; + if(moveblock) map_addblock(&sd->bl); + + map_foreachinmovearea(clif_pcinsight,sd->bl.m,sd->bl.x-AREA_SIZE,sd->bl.y-AREA_SIZE,sd->bl.x+AREA_SIZE,sd->bl.y+AREA_SIZE,-dx,-dy,0,sd); + + if(sd->status.party_id>0){ // パーティのHP情報通知検査 + struct party *p=party_search(sd->status.party_id); + if(p!=NULL){ + int flag=0; + map_foreachinmovearea(party_send_hp_check,sd->bl.m,sd->bl.x-AREA_SIZE,sd->bl.y-AREA_SIZE,sd->bl.x+AREA_SIZE,sd->bl.y+AREA_SIZE,-dx,-dy,BL_PC,sd->status.party_id,&flag); + if(flag) + sd->party_hp=-1; + } + } + + if(sd->status.option&4) // クローキングの消滅検査 + skill_check_cloaking(&sd->bl); + + skill_unit_move(&sd->bl,gettick(),dist+7); // スキルユニットの検査 + + if(map_getcell(sd->bl.m,sd->bl.x,sd->bl.y)&0x80) + npc_touch_areanpc(sd,sd->bl.m,sd->bl.x,sd->bl.y); + else + sd->areanpc_id=0; + return 0; +} + +// +// 武器戦闘 +// +/*========================================== + * スキルの検索 所有していた場合Lvが返る + *------------------------------------------ + */ +int pc_checkskill(struct map_session_data *sd,int skill_id) +{ + if(sd == NULL) return 0; + if( skill_id>=10000 ){ + struct guild *g; + if( sd->status.guild_id>0 && (g=guild_search(sd->status.guild_id))!=NULL) + return guild_checkskill(g,skill_id); + return 0; + } + + if(sd->status.skill[skill_id].id == skill_id) + return (sd->status.skill[skill_id].lv); + + return 0; +} + +/*========================================== + * 武器変更によるスキルの継続チェック + * 引数: + * struct map_session_data *sd セッションデータ + * int nameid 装備品ID + * 返り値: + * 0 変更なし + * -1 スキルを解除 + *------------------------------------------ + */ +int pc_checkallowskill(struct map_session_data *sd) +{ + nullpo_retr(0, sd); + + if( sd->sc_data == NULL ) + return 0; + + if(!(skill_get_weapontype(KN_TWOHANDQUICKEN)&(1<<sd->status.weapon)) && sd->sc_data[SC_TWOHANDQUICKEN].timer!=-1) { // 2HQ + skill_status_change_end(&sd->bl,SC_TWOHANDQUICKEN,-1); // 2HQを解除 + return -1; + } + if(!(skill_get_weapontype(LK_AURABLADE)&(1<<sd->status.weapon)) && sd->sc_data[SC_AURABLADE].timer!=-1) { /* オーラブレード */ + skill_status_change_end(&sd->bl,SC_AURABLADE,-1); /* オーラブレードを解除 */ + return -1; + } + if(!(skill_get_weapontype(LK_PARRYING)&(1<<sd->status.weapon)) && sd->sc_data[SC_PARRYING].timer!=-1) { /* パリイング */ + skill_status_change_end(&sd->bl,SC_PARRYING,-1); /* パリイングを解除 */ + return -1; + } + if(!(skill_get_weapontype(LK_CONCENTRATION)&(1<<sd->status.weapon)) && sd->sc_data[SC_CONCENTRATION].timer!=-1) { /* コンセントレーション */ + skill_status_change_end(&sd->bl,SC_CONCENTRATION,-1); /* コンセントレーションを解除 */ + return -1; + } + if(!(skill_get_weapontype(CR_SPEARQUICKEN)&(1<<sd->status.weapon)) && sd->sc_data[SC_SPEARSQUICKEN].timer!=-1){ // スピアクィッケン + skill_status_change_end(&sd->bl,SC_SPEARSQUICKEN,-1); // スピアクイッケンを解除 + return -1; + } + if(!(skill_get_weapontype(BS_ADRENALINE)&(1<<sd->status.weapon)) && sd->sc_data[SC_ADRENALINE].timer!=-1){ // アドレナリンラッシュ + skill_status_change_end(&sd->bl,SC_ADRENALINE,-1); // アドレナリンラッシュを解除 + return -1; + } + + if(sd->status.shield <= 0) { + if(sd->sc_data[SC_AUTOGUARD].timer!=-1){ // オートガード + skill_status_change_end(&sd->bl,SC_AUTOGUARD,-1); + return -1; + } + if(sd->sc_data[SC_DEFENDER].timer!=-1){ // ディフェンダー + skill_status_change_end(&sd->bl,SC_DEFENDER,-1); + return -1; + } + if(sd->sc_data[SC_REFLECTSHIELD].timer!=-1){ //リフレクトシールド + skill_status_change_end(&sd->bl,SC_REFLECTSHIELD,-1); + return -1; + } + } + + return 0; +} + +/*========================================== + * 装 備品のチェック + *------------------------------------------ + */ +int pc_checkequip(struct map_session_data *sd,int pos) +{ + int i; + + nullpo_retr(-1, sd); + + for(i=0;i<11;i++){ + if(pos & equip_pos[i]) + return sd->equip_index[i]; + } + + return -1; +} + +/*========================================== + * 転生職や養子職の元の職業を返す + *------------------------------------------ + */ +struct pc_base_job pc_calc_base_job(int b_class) +{ + struct pc_base_job bj; + //転生や養子の場合の元の職業を算出する + if(b_class < MAX_PC_CLASS){ //通常 + bj.job = b_class; + bj.upper = 0; + }else if(b_class >= 4001 && b_class < 4023){ //転生職 + bj.job = b_class - 4001; + bj.upper = 1; + }else if(b_class == 23 + 4023 -1){ //養子スパノビ + bj.job = b_class - (4023 - 1); + bj.upper = 2; + }else{ //養子スパノビ以外の養子 + bj.job = b_class - 4023; + bj.upper = 2; + } + + if(battle_config.enable_upper_class==0){ //confで無効になっていたらupper=0 + bj.upper = 0; + } + + if(bj.job == 0){ + bj.type = 0; + }else if(bj.job < 7){ + bj.type = 1; + }else{ + bj.type = 2; + } + + return bj; +} + +/*========================================== + * PCの攻撃 (timer関数) + *------------------------------------------ + */ +int pc_attack_timer(int tid,unsigned int tick,int id,int data) +{ + struct map_session_data *sd; + struct block_list *bl; + struct status_change *sc_data; + short *opt; + int dist,skill,range; + + sd=map_id2sd(id); + if(sd == NULL) + return 0; + if(sd->attacktimer != tid){ + if(battle_config.error_log) + printf("pc_attack_timer %d != %d\n",sd->attacktimer,tid); + return 0; + } + sd->attacktimer=-1; + + if(sd->bl.prev == NULL) + return 0; + + bl=map_id2bl(sd->attacktarget); + if(bl==NULL || bl->prev == NULL) + return 0; + + if(bl->type == BL_PC && pc_isdead((struct map_session_data *)bl)) + return 0; + + // 同じmapでないなら攻撃しない + // PCが死んでても攻撃しない + if(sd->bl.m != bl->m || pc_isdead(sd)) + return 0; + + if( sd->opt1>0 || sd->status.option&2 || sd->status.option&16388) // 異常などで攻撃できない + return 0; + + if(sd->sc_data[SC_AUTOCOUNTER].timer != -1) + return 0; + if(sd->sc_data[SC_BLADESTOP].timer != -1) + return 0; + + if((opt = battle_get_option(bl)) != NULL && *opt&0x46) + return 0; + if(((sc_data = battle_get_sc_data(bl)) != NULL && sc_data[SC_TRICKDEAD].timer != -1) || + ((sc_data = battle_get_sc_data(bl)) != NULL && sc_data[SC_BASILICA].timer != -1 )) + return 0; + + if(sd->skilltimer != -1 && pc_checkskill(sd,SA_FREECAST) <= 0) + return 0; + + if(!battle_config.sdelay_attack_enable && pc_checkskill(sd,SA_FREECAST) <= 0) { + if(DIFF_TICK(tick , sd->canact_tick) < 0) { + clif_skill_fail(sd,1,4,0); + return 0; + } + } + + dist = distance(sd->bl.x,sd->bl.y,bl->x,bl->y); + range = sd->attackrange; + if(sd->status.weapon != 11) range++; + if( dist > range ){ // 届 かないので移動 + if(pc_can_reach(sd,bl->x,bl->y)) + clif_movetoattack(sd,bl); + return 0; + } + + if(dist <= range && !battle_check_range(&sd->bl,bl,range) ) { + if(pc_can_reach(sd,bl->x,bl->y) && sd->canmove_tick < tick && (sd->sc_data[SC_ANKLE].timer == -1 || sd->sc_data[SC_SPIDERWEB].timer == -1)) + pc_walktoxy(sd,bl->x,bl->y); + sd->attackabletime = tick + (sd->aspd<<1); + } + else { + if(battle_config.pc_attack_direction_change) + sd->dir=sd->head_dir=map_calc_dir(&sd->bl, bl->x,bl->y ); // 向き設定 + + if(sd->walktimer != -1) + pc_stop_walking(sd,1); + + if(sd->sc_data[SC_COMBO].timer == -1) { + map_freeblock_lock(); + pc_stop_walking(sd,0); + sd->attacktarget_lv = battle_weapon_attack(&sd->bl,bl,tick,0); + if(!(battle_config.pc_cloak_check_type&2) && sd->sc_data[SC_CLOAKING].timer != -1) + skill_status_change_end(&sd->bl,SC_CLOAKING,-1); + if(sd->status.pet_id > 0 && sd->pd && sd->petDB && battle_config.pet_attack_support) + pet_target_check(sd,bl,0); + map_freeblock_unlock(); + if(sd->skilltimer != -1 && (skill = pc_checkskill(sd,SA_FREECAST)) > 0 ) // フリーキャスト + sd->attackabletime = tick + ((sd->aspd<<1)*(150 - skill*5)/100); + else + sd->attackabletime = tick + (sd->aspd<<1); + } + else if(sd->attackabletime <= tick) { + if(sd->skilltimer != -1 && (skill = pc_checkskill(sd,SA_FREECAST)) > 0 ) // フリーキャスト + sd->attackabletime = tick + ((sd->aspd<<1)*(150 - skill*5)/100); + else + sd->attackabletime = tick + (sd->aspd<<1); + } + if(sd->attackabletime <= tick) sd->attackabletime = tick + (battle_config.max_aspd<<1); + } + + if(sd->state.attack_continue) { + sd->attacktimer=add_timer(sd->attackabletime,pc_attack_timer,sd->bl.id,0); + } + + return 0; +} + +/*========================================== + * 攻撃要求 + * typeが1なら継続攻撃 + *------------------------------------------ + */ +int pc_attack(struct map_session_data *sd,int target_id,int type) +{ + struct block_list *bl; + int d; + + nullpo_retr(0, sd); + + bl=map_id2bl(target_id); + if(bl==NULL) + return 1; + + if(bl->type==BL_NPC) { // monster npcs [Valaris] + npc_click(sd,RFIFOL(sd->fd,2)); + return 0; + } + + if(!battle_check_target(&sd->bl,bl,BCT_ENEMY)) + return 1; + if(sd->attacktimer != -1) + pc_stopattack(sd); + sd->attacktarget=target_id; + sd->state.attack_continue=type; + + d=DIFF_TICK(sd->attackabletime,gettick()); + if(d>0 && d<2000){ // 攻撃delay中 + sd->attacktimer=add_timer(sd->attackabletime,pc_attack_timer,sd->bl.id,0); + } else { + // 本来timer関数なので引数を合わせる + pc_attack_timer(-1,gettick(),sd->bl.id,0); + } + + return 0; +} + +/*========================================== + * 継続攻撃停止 + *------------------------------------------ + */ +int pc_stopattack(struct map_session_data *sd) +{ + nullpo_retr(0, sd); + + if(sd->attacktimer != -1) { + delete_timer(sd->attacktimer,pc_attack_timer); + sd->attacktimer=-1; + } + sd->attacktarget=0; + sd->state.attack_continue=0; + + return 0; +} + +int pc_follow_timer(int tid,unsigned int tick,int id,int data) +{ + struct map_session_data *sd, *bl; + + sd=map_id2sd(id); + if(sd == NULL || sd->followtimer != tid) + return 0; + + sd->followtimer=-1; + + do { + if(sd->bl.prev == NULL) + break; + + bl=(struct map_session_data *) map_id2bl(sd->followtarget); + + if(bl==NULL) + return 0; + + if(bl->bl.prev == NULL) + break; + + if(bl->bl.type == BL_PC && pc_isdead((struct map_session_data *)bl)) + return 0; + + if (sd->skilltimer == -1 && sd->attacktimer == -1 && sd->walktimer == -1) { + if((sd->bl.m == bl->bl.m) && pc_can_reach(sd,bl->bl.x,bl->bl.y)) { + if (distance(sd->bl.x,sd->bl.y,bl->bl.x,bl->bl.y) > 5) + pc_walktoxy(sd,bl->bl.x,bl->bl.y); + } else + pc_setpos((struct map_session_data*)sd, bl->mapname, bl->bl.x, bl->bl.y, 3); + } + } while (0); + + sd->followtimer=add_timer(tick + sd->aspd,pc_follow_timer,sd->bl.id,0); + + return 0; +} + +int pc_follow(struct map_session_data *sd,int target_id) +{ + struct block_list *bl; + + bl=map_id2bl(target_id); + if(bl==NULL) + return 1; + sd->followtarget=target_id; + if(sd->followtimer != -1) { + delete_timer(sd->followtimer,pc_follow_timer); + sd->followtimer = -1; + } + + pc_follow_timer(-1,gettick(),sd->bl.id,0); + + return 0; +} + +int pc_checkbaselevelup(struct map_session_data *sd) +{ + int next = pc_nextbaseexp(sd); + + nullpo_retr(0, sd); + + if(sd->status.base_exp >= next && next > 0){ + struct pc_base_job s_class = pc_calc_base_job(sd->status.class); + + // base側レベルアップ処理 + sd->status.base_exp -= next; + + sd->status.base_level ++; + sd->status.status_point += (sd->status.base_level+14) / 5 ; + clif_updatestatus(sd,SP_STATUSPOINT); + clif_updatestatus(sd,SP_BASELEVEL); + clif_updatestatus(sd,SP_NEXTBASEEXP); + pc_calcstatus(sd,0); + pc_heal(sd,sd->status.max_hp,sd->status.max_sp); + + //スパノビはキリエ、イムポ、マニピ、グロ、サフラLv1がかかる + if(s_class.job == 23){ + skill_status_change_start(&sd->bl,SkillStatusChangeTable[PR_KYRIE],1,0,0,0,skill_get_time(PR_KYRIE,1),0 ); + skill_status_change_start(&sd->bl,SkillStatusChangeTable[PR_IMPOSITIO],1,0,0,0,skill_get_time(PR_IMPOSITIO,1),0 ); + skill_status_change_start(&sd->bl,SkillStatusChangeTable[PR_MAGNIFICAT],1,0,0,0,skill_get_time(PR_MAGNIFICAT,1),0 ); + skill_status_change_start(&sd->bl,SkillStatusChangeTable[PR_GLORIA],1,0,0,0,skill_get_time(PR_GLORIA,1),0 ); + skill_status_change_start(&sd->bl,SkillStatusChangeTable[PR_SUFFRAGIUM],1,0,0,0,skill_get_time(PR_SUFFRAGIUM,1),0 ); + } + + clif_misceffect(&sd->bl,0); + //レベルアップしたのでパーティー情報を更新する + //(公平範囲チェック) + party_send_movemap(sd); + return 1; + } + + return 0; +} + +int pc_checkjoblevelup(struct map_session_data *sd) +{ + int next = pc_nextjobexp(sd); + + nullpo_retr(0, sd); + + if(sd->status.job_exp >= next && next > 0){ + // job側レベルアップ処理 + sd->status.job_exp -= next; + sd->status.job_level ++; + clif_updatestatus(sd,SP_JOBLEVEL); + clif_updatestatus(sd,SP_NEXTJOBEXP); + sd->status.skill_point ++; + clif_updatestatus(sd,SP_SKILLPOINT); + pc_calcstatus(sd,0); + + clif_misceffect(&sd->bl,1); + return 1; + } + + return 0; +} + +/*========================================== + * 経験値取得 + *------------------------------------------ + */ +int pc_gainexp(struct map_session_data *sd,int base_exp,int job_exp) +{ + char output[256]; + nullpo_retr(0, sd); + + if(sd->bl.prev == NULL || pc_isdead(sd)) + return 0; + + if((battle_config.pvp_exp == 0) && map[sd->bl.m].flag.pvp) // [MouseJstr] + return 0; // no exp on pvp maps + + if(sd->sc_data[SC_RICHMANKIM].timer != -1) { // added bounds checking [Vaalris] + base_exp += base_exp*(25 + sd->sc_data[SC_RICHMANKIM].val1*25)/100; + job_exp += job_exp*(25 + sd->sc_data[SC_RICHMANKIM].val1*25)/100; + } + + if(sd->status.guild_id>0){ // ギルドに上納 + base_exp-=guild_payexp(sd,base_exp); + if(base_exp < 0) + base_exp = 0; + } + + if(!battle_config.multi_level_up && pc_nextbaseafter(sd)) { + while(sd->status.base_exp+base_exp >=pc_nextbaseafter(sd) && sd->status.base_exp <= pc_nextbaseexp(sd) && pc_nextbaseafter(sd) > 0) { + base_exp*=.90; + } + } + + sd->status.base_exp += base_exp; + if(sd->status.base_exp < 0) + sd->status.base_exp = 0; + + while(pc_checkbaselevelup(sd)) ; + + clif_updatestatus(sd,SP_BASEEXP); + if(!battle_config.multi_level_up && pc_nextjobafter(sd)) { + while(sd->status.job_exp+job_exp >= pc_nextjobafter(sd) && sd->status.job_exp <= pc_nextjobexp(sd) && pc_nextjobafter(sd) > 0) { + job_exp*=.90; + } + } + + sd->status.job_exp += job_exp; + if(sd->status.job_exp < 0) + sd->status.job_exp = 0; + + while(pc_checkjoblevelup(sd)) ; + + clif_updatestatus(sd,SP_JOBEXP); + + if(battle_config.disp_experience){ + sprintf(output, + "Experienced Gained Base:%d Job:%d",base_exp,job_exp); + clif_disp_onlyself(sd,output,strlen(output)); + } + + return 0; +} + +/*========================================== + * base level側必要経験値計算 + *------------------------------------------ + */ +int pc_nextbaseexp(struct map_session_data *sd) +{ + int i; + + nullpo_retr(0, sd); + + if(sd->status.base_level>=MAX_LEVEL || sd->status.base_level<=0) + return 0; + + if(sd->status.class==0) i=0; + else if(sd->status.class<=6) i=1; + else if(sd->status.class<=22) i=2; + else if(sd->status.class==23) i=3; + else if(sd->status.class==4001) i=4; + else if(sd->status.class<=4007) i=5; + else i=6; + + return exp_table[i][sd->status.base_level-1]; +} + +/*========================================== + * job level側必要経験値計算 + *------------------------------------------ + */ +int pc_nextjobexp(struct map_session_data *sd) +{ + int i; + + nullpo_retr(0, sd); + + if(sd->status.job_level>=MAX_LEVEL || sd->status.job_level<=0) + return 0; + + if(sd->status.class==0) i=7; + else if(sd->status.class<=6) i=8; + else if(sd->status.class<=22) i=9; + else if(sd->status.class==23) i=10; + else if(sd->status.class==4001) i=11; + else if(sd->status.class<=4007) i=12; + else i=13; + + return exp_table[i][sd->status.job_level-1]; +} + +/*========================================== + * base level after next [Valaris] + *------------------------------------------ + */ +int pc_nextbaseafter(struct map_session_data *sd) +{ + int i; + + nullpo_retr(0, sd); + + if(sd->status.base_level>=MAX_LEVEL || sd->status.base_level<=0) + return 0; + + if(sd->status.class==0) i=0; + else if(sd->status.class<=6) i=1; + else if(sd->status.class<=22) i=2; + else if(sd->status.class==23) i=3; + else if(sd->status.class==4001) i=4; + else if(sd->status.class<=4007) i=5; + else i=6; + + return exp_table[i][sd->status.base_level]; +} + +/*========================================== + * job level after next [Valaris] + *------------------------------------------ + */ +int pc_nextjobafter(struct map_session_data *sd) +{ + int i; + + nullpo_retr(0, sd); + + if(sd->status.job_level>=MAX_LEVEL || sd->status.job_level<=0) + return 0; + + if(sd->status.class==0) i=7; + else if(sd->status.class<=6) i=8; + else if(sd->status.class<=22) i=9; + else if(sd->status.class==23) i=10; + else if(sd->status.class==4001) i=11; + else if(sd->status.class<=4007) i=12; + else i=13; + + return exp_table[i][sd->status.job_level]; +} +/*========================================== + + * 必要ステータスポイント計算 + *------------------------------------------ + */ +int pc_need_status_point(struct map_session_data *sd,int type) +{ + int val; + + nullpo_retr(-1, sd); + + if(type<SP_STR || type>SP_LUK) + return -1; + val = + type==SP_STR ? sd->status.str : + type==SP_AGI ? sd->status.agi : + type==SP_VIT ? sd->status.vit : + type==SP_INT ? sd->status.int_: + type==SP_DEX ? sd->status.dex : sd->status.luk; + + return (val+9)/10+1; +} + +/*========================================== + * 能力値成長 + *------------------------------------------ + */ +int pc_statusup(struct map_session_data *sd,int type) +{ + int need,val = 0; + + nullpo_retr(0, sd); + + need=pc_need_status_point(sd,type); + if(type<SP_STR || type>SP_LUK || need<0 || need>sd->status.status_point){ + clif_statusupack(sd,type,0,0); + return 1; + } + switch(type){ + case SP_STR: + if(sd->status.str >= battle_config.max_parameter) { + clif_statusupack(sd,type,0,0); + return 1; + } + val= ++sd->status.str; + break; + case SP_AGI: + if(sd->status.agi >= battle_config.max_parameter) { + clif_statusupack(sd,type,0,0); + return 1; + } + val= ++sd->status.agi; + break; + case SP_VIT: + if(sd->status.vit >= battle_config.max_parameter) { + clif_statusupack(sd,type,0,0); + return 1; + } + val= ++sd->status.vit; + break; + case SP_INT: + if(sd->status.int_ >= battle_config.max_parameter) { + clif_statusupack(sd,type,0,0); + return 1; + } + val= ++sd->status.int_; + break; + case SP_DEX: + if(sd->status.dex >= battle_config.max_parameter) { + clif_statusupack(sd,type,0,0); + return 1; + } + val= ++sd->status.dex; + break; + case SP_LUK: + if(sd->status.luk >= battle_config.max_parameter) { + clif_statusupack(sd,type,0,0); + return 1; + } + val= ++sd->status.luk; + break; + } + sd->status.status_point-=need; + if(need!=pc_need_status_point(sd,type)){ + clif_updatestatus(sd,type-SP_STR+SP_USTR); + } + clif_updatestatus(sd,SP_STATUSPOINT); + clif_updatestatus(sd,type); + pc_calcstatus(sd,0); + clif_statusupack(sd,type,1,val); + + return 0; +} + +/*========================================== + * 能力値成長 + *------------------------------------------ + */ +int pc_statusup2(struct map_session_data *sd,int type,int val) +{ + nullpo_retr(0, sd); + + if(type<SP_STR || type>SP_LUK){ + clif_statusupack(sd,type,0,0); + return 1; + } + switch(type){ + case SP_STR: + if(sd->status.str + val >= battle_config.max_parameter) + val = battle_config.max_parameter; + else if(sd->status.str + val < 1) + val = 1; + else + val += sd->status.str; + sd->status.str = val; + break; + case SP_AGI: + if(sd->status.agi + val >= battle_config.max_parameter) + val = battle_config.max_parameter; + else if(sd->status.agi + val < 1) + val = 1; + else + val += sd->status.agi; + sd->status.agi = val; + break; + case SP_VIT: + if(sd->status.vit + val >= battle_config.max_parameter) + val = battle_config.max_parameter; + else if(sd->status.vit + val < 1) + val = 1; + else + val += sd->status.vit; + sd->status.vit = val; + break; + case SP_INT: + if(sd->status.int_ + val >= battle_config.max_parameter) + val = battle_config.max_parameter; + else if(sd->status.int_ + val < 1) + val = 1; + else + val += sd->status.int_; + sd->status.int_ = val; + break; + case SP_DEX: + if(sd->status.dex + val >= battle_config.max_parameter) + val = battle_config.max_parameter; + else if(sd->status.dex + val < 1) + val = 1; + else + val += sd->status.dex; + sd->status.dex = val; + break; + case SP_LUK: + if(sd->status.luk + val >= battle_config.max_parameter) + val = battle_config.max_parameter; + else if(sd->status.luk + val < 1) + val = 1; + else + val = sd->status.luk + val; + sd->status.luk = val; + break; + } + clif_updatestatus(sd,type-SP_STR+SP_USTR); + clif_updatestatus(sd,type); + pc_calcstatus(sd,0); + clif_statusupack(sd,type,1,val); + + return 0; +} + +/*========================================== + * スキルポイント割り振り + *------------------------------------------ + */ +int pc_skillup(struct map_session_data *sd,int skill_num) +{ + nullpo_retr(0, sd); + + if( skill_num>=10000 ){ + guild_skillup(sd,skill_num); + return 0; + } + + if( sd->status.skill_point>0 && + sd->status.skill[skill_num].id!=0 && + sd->status.skill[skill_num].lv < skill_get_max(skill_num) ) + { + sd->status.skill[skill_num].lv++; + sd->status.skill_point--; + pc_calcstatus(sd,0); + clif_skillup(sd,skill_num); + clif_updatestatus(sd,SP_SKILLPOINT); + clif_skillinfoblock(sd); + } + + return 0; +} + +/*========================================== + * /allskill + *------------------------------------------ + */ +int pc_allskillup(struct map_session_data *sd) +{ + int i,id; + int c=0, s=0; + //転生や養子の場合の元の職業を算出する + struct pc_base_job s_class; + + nullpo_retr(0, sd); + + s_class = pc_calc_base_job(sd->status.class); + c = s_class.job; + s = (s_class.upper==1) ? 1 : 0 ; //転生以外は通常のスキル? + + for(i=0;i<MAX_SKILL;i++){ + sd->status.skill[i].id=0; + if (sd->status.skill[i].flag && sd->status.skill[i].flag != 13){ // cardスキルなら、 + sd->status.skill[i].lv=(sd->status.skill[i].flag==1)?0:sd->status.skill[i].flag-2; // 本当のlvに + sd->status.skill[i].flag=0; // flagは0にしておく + } + } + + if (battle_config.gm_allskill > 0 && pc_isGM(sd) >= battle_config.gm_allskill){ + // 全てのスキル + for(i=1;i<158;i++) + sd->status.skill[i].lv=skill_get_max(i); + for(i=210;i<291;i++) + sd->status.skill[i].lv=skill_get_max(i); + for(i=304;i<MAX_SKILL;i++) + sd->status.skill[i].lv=skill_get_max(i); + } + else { + for(i=0;(id=skill_tree[s][c][i].id)>0;i++){ + if(sd->status.skill[id].id==0 && (!(skill_get_inf2(id)&0x01) || battle_config.quest_skill_learn) ) + sd->status.skill[id].lv=skill_get_max(id); + } + } + pc_calcstatus(sd,0); + + return 0; +} + +/*========================================== + * /resetlvl + *------------------------------------------ + */ +int pc_resetlvl(struct map_session_data* sd,int type) +{ + int i; + + nullpo_retr(0, sd); + + for(i=1;i<MAX_SKILL;i++){ + sd->status.skill[i].lv = 0; + } + + if(type == 1){ + sd->status.skill_point=0; + sd->status.base_level=1; + sd->status.job_level=1; + sd->status.base_exp=sd->status.base_exp=0; + sd->status.job_exp=sd->status.job_exp=0; + if(sd->status.option !=0) + sd->status.option = 0; + + sd->status.str=1; + sd->status.agi=1; + sd->status.vit=1; + sd->status.int_=1; + sd->status.dex=1; + sd->status.luk=1; + if(sd->status.class == 4001) + sd->status.status_point=100; + } + + if(type == 2){ + sd->status.skill_point=0; + sd->status.base_level=1; + sd->status.job_level=1; + sd->status.base_exp=0; + sd->status.job_exp=0; + } + if(type == 3){ + sd->status.base_level=1; + sd->status.base_exp=0; + } + if(type == 4){ + sd->status.job_level=1; + sd->status.job_exp=0; + } + + clif_updatestatus(sd,SP_STATUSPOINT); + clif_updatestatus(sd,SP_STR); + clif_updatestatus(sd,SP_AGI); + clif_updatestatus(sd,SP_VIT); + clif_updatestatus(sd,SP_INT); + clif_updatestatus(sd,SP_DEX); + clif_updatestatus(sd,SP_LUK); + clif_updatestatus(sd,SP_BASELEVEL); + clif_updatestatus(sd,SP_JOBLEVEL); + clif_updatestatus(sd,SP_STATUSPOINT); + clif_updatestatus(sd,SP_NEXTBASEEXP); + clif_updatestatus(sd,SP_NEXTJOBEXP); + clif_updatestatus(sd,SP_SKILLPOINT); + + clif_updatestatus(sd,SP_USTR); // Updates needed stat points - Valaris + clif_updatestatus(sd,SP_UAGI); + clif_updatestatus(sd,SP_UVIT); + clif_updatestatus(sd,SP_UINT); + clif_updatestatus(sd,SP_UDEX); + clif_updatestatus(sd,SP_ULUK); // End Addition + + for(i=0;i<11;i++) { // unequip items that can't be equipped by base 1 [Valaris] + if(sd->equip_index[i] >= 0) + if(!pc_isequip(sd,sd->equip_index[i])) + pc_unequipitem(sd,sd->equip_index[i],1); + } + + clif_skillinfoblock(sd); + pc_calcstatus(sd,0); + + return 0; +} +/*========================================== + * /resetstate + *------------------------------------------ + */ +int pc_resetstate(struct map_session_data* sd) +{ + #define sumsp(a) ((a)*((a-2)/10+2) - 5*((a-2)/10)*((a-2)/10) - 6*((a-2)/10) -2) +// int add=0; // Removed by Dexity + + nullpo_retr(0, sd); + +// New statpoint table used here - Dexity + sd->status.status_point = atoi (statp[sd->status.base_level - 1]); +// End addition + +// Removed by Dexity - old count +// add += sumsp(sd->status.str); +// add += sumsp(sd->status.agi); +// add += sumsp(sd->status.vit); +// add += sumsp(sd->status.int_); +// add += sumsp(sd->status.dex); +// add += sumsp(sd->status.luk); +// sd->status.status_point+=add; + + clif_updatestatus(sd,SP_STATUSPOINT); + + sd->status.str=1; + sd->status.agi=1; + sd->status.vit=1; + sd->status.int_=1; + sd->status.dex=1; + sd->status.luk=1; + + clif_updatestatus(sd,SP_STR); + clif_updatestatus(sd,SP_AGI); + clif_updatestatus(sd,SP_VIT); + clif_updatestatus(sd,SP_INT); + clif_updatestatus(sd,SP_DEX); + clif_updatestatus(sd,SP_LUK); + + clif_updatestatus(sd,SP_USTR); // Updates needed stat points - Valaris + clif_updatestatus(sd,SP_UAGI); + clif_updatestatus(sd,SP_UVIT); + clif_updatestatus(sd,SP_UINT); + clif_updatestatus(sd,SP_UDEX); + clif_updatestatus(sd,SP_ULUK); // End Addition + + pc_calcstatus(sd,0); + + return 0; +} + +/*========================================== + * /resetskill + *------------------------------------------ + */ +int pc_resetskill(struct map_session_data* sd) +{ + int i,skill; + + nullpo_retr(0, sd); + + for(i=1;i<MAX_SKILL;i++){ + if( (skill = pc_checkskill(sd,i)) > 0) { + if(!(skill_get_inf2(i)&0x01) || battle_config.quest_skill_learn) { + if(!sd->status.skill[i].flag) + sd->status.skill_point += skill; + else if(sd->status.skill[i].flag > 2 && sd->status.skill[i].flag != 13) { + sd->status.skill_point += (sd->status.skill[i].flag - 2); + } + sd->status.skill[i].lv = 0; + } + else if(battle_config.quest_skill_reset) + sd->status.skill[i].lv = 0; + sd->status.skill[i].flag = 0; + } + else + sd->status.skill[i].lv = 0; + } + clif_updatestatus(sd,SP_SKILLPOINT); + clif_skillinfoblock(sd); + pc_calcstatus(sd,0); + + return 0; +} + +/*========================================== + * pcにダメージを与える + *------------------------------------------ + */ +int pc_damage(struct block_list *src,struct map_session_data *sd,int damage) +{ + int i=0,j=0; + struct pc_base_job s_class; + + nullpo_retr(0, sd); + + //転生や養子の場合の元の職業を算出する + s_class = pc_calc_base_job(sd->status.class); + // 既に死んでいたら無効 + if(pc_isdead(sd)) + return 0; + // 座ってたら立ち上がる + if(pc_issit(sd)) { + pc_setstand(sd); + skill_gangsterparadise(sd,0); + } + + // 歩 いていたら足を止める + if(sd->sc_data[SC_ENDURE].timer == -1 && !sd->special_state.infinite_endure) + pc_stop_walking(sd,3); + // 演奏/ダンスの中断 + if(damage > sd->status.max_hp>>2) + skill_stop_dancing(&sd->bl,0); + + sd->status.hp-=damage; + if(sd->status.pet_id > 0 && sd->pd && sd->petDB && battle_config.pet_damage_support) + pet_target_check(sd,src,1); + + if (sd->sc_data[SC_TRICKDEAD].timer != -1) + skill_status_change_end(&sd->bl, SC_TRICKDEAD, -1); + if(sd->status.option&2) + skill_status_change_end(&sd->bl, SC_HIDING, -1); + if(sd->status.option&4) + skill_status_change_end(&sd->bl, SC_CLOAKING, -1); + if(sd->status.option&16386) + skill_status_change_end(&sd->bl, SC_CHASEWALK, -1); + + if(sd->status.hp>0){ + // まだ生きているならHP更新 + clif_updatestatus(sd,SP_HP); + + if(sd->status.hp<sd->status.max_hp>>2 && pc_checkskill(sd,SM_AUTOBERSERK)>0 && + (sd->sc_data[SC_PROVOKE].timer==-1 || sd->sc_data[SC_PROVOKE].val2==0 )) + // オートバーサーク発動 + skill_status_change_start(&sd->bl,SC_PROVOKE,10,1,0,0,0,0); + + sd->canlog_tick = gettick(); + + if(sd->status.party_id>0) { // on-the-fly party hp updates [Valaris] + struct party *p=party_search(sd->status.party_id); + if(p!=NULL) clif_party_hp(p,sd); + } // end addition [Valaris] + + return 0; + } + sd->status.hp = 0; + pc_setdead(sd); + if(sd->vender_id) + vending_closevending(sd); + + if(sd->status.pet_id > 0 && sd->pd) { + if(sd->petDB) { + sd->pet.intimate -= sd->petDB->die; + if(sd->pet.intimate < 0) + sd->pet.intimate = 0; + clif_send_petdata(sd,1,sd->pet.intimate); + } + } + + pc_stop_walking(sd,0); + skill_castcancel(&sd->bl,0); // 詠唱の中止 + clif_clearchar_area(&sd->bl,1); + skill_unit_out_all(&sd->bl,gettick(),1); + if(sd->sc_data[SC_BLADESTOP].timer!=-1)//白刃は事前に解除 + skill_status_change_end(&sd->bl,SC_BLADESTOP,-1); + pc_setglobalreg(sd,"PC_DIE_COUNTER",++sd->die_counter); //死にカウンター書き込み + skill_status_change_clear(&sd->bl,0); // ステータス異常を解除する + clif_updatestatus(sd,SP_HP); + pc_calcstatus(sd,0); + + for(i=0;i<5;i++) + if(sd->dev.val1[i]){ + skill_status_change_end(&map_id2sd(sd->dev.val1[i])->bl,SC_DEVOTION,-1); + sd->dev.val1[i] = sd->dev.val2[i]=0; + } + + if(battle_config.death_penalty_type>0) { // changed penalty options, added death by player if pk_mode [Valaris] + if(sd->status.class != 0 && !map[sd->bl.m].flag.nopenalty && !map[sd->bl.m].flag.gvg){ // only novices will recieve no penalty + if(battle_config.death_penalty_type==1 && battle_config.death_penalty_base > 0) + sd->status.base_exp -= (double)pc_nextbaseexp(sd) * (double)battle_config.death_penalty_base/10000; + if(battle_config.pk_mode && src && src->type==BL_PC) + sd->status.base_exp -= (double)pc_nextbaseexp(sd) * (double)battle_config.death_penalty_base/10000; + else if(battle_config.death_penalty_type==2 && battle_config.death_penalty_base > 0) { + if(pc_nextbaseexp(sd) > 0) + sd->status.base_exp -= (double)sd->status.base_exp * (double)battle_config.death_penalty_base/10000; + if(battle_config.pk_mode && src && src->type==BL_PC) + sd->status.base_exp -= (double)sd->status.base_exp * (double)battle_config.death_penalty_base/10000; + } + if(sd->status.base_exp < 0) + sd->status.base_exp = 0; + clif_updatestatus(sd,SP_BASEEXP); + + if(battle_config.death_penalty_type==1 && battle_config.death_penalty_job > 0) + sd->status.job_exp -= (double)pc_nextjobexp(sd) * (double)battle_config.death_penalty_job/10000; + if(battle_config.pk_mode && src && src->type==BL_PC) + sd->status.job_exp -= (double)pc_nextjobexp(sd) * (double)battle_config.death_penalty_job/10000; + else if(battle_config.death_penalty_type==2 && battle_config.death_penalty_job > 0) { + if(pc_nextjobexp(sd) > 0) + sd->status.job_exp -= (double)sd->status.job_exp * (double)battle_config.death_penalty_job/10000; + if(battle_config.pk_mode && src && src->type==BL_PC) + sd->status.job_exp -= (double)sd->status.job_exp * (double)battle_config.death_penalty_job/10000; + } + if(sd->status.job_exp < 0) + sd->status.job_exp = 0; + clif_updatestatus(sd,SP_JOBEXP); + } + } + //ナイトメアモードアイテムドロップ + if(map[sd->bl.m].flag.pvp_nightmaredrop){ // Moved this outside so it works when PVP isnt enabled and during pk mode [Ancyker] + for(j=0;j<MAX_DROP_PER_MAP;j++){ + int id = map[sd->bl.m].drop_list[j].drop_id; + int type = map[sd->bl.m].drop_list[j].drop_type; + int per = map[sd->bl.m].drop_list[j].drop_per; + if(id == 0) + continue; + if(id == -1){//ランダムドロップ + int eq_num=0,eq_n[MAX_INVENTORY]; + memset(eq_n,0,sizeof(eq_n)); + //先ず装備しているアイテム数をカウント + for(i=0;i<MAX_INVENTORY;i++){ + int k; + if( (type == 1 && !sd->status.inventory[i].equip) + || (type == 2 && sd->status.inventory[i].equip) + || type == 3){ + //InventoryIndexを格納 + for(k=0;k<MAX_INVENTORY;k++){ + if(eq_n[k] <= 0){ + eq_n[k]=i; + break; + } + } + eq_num++; + } + } + if(eq_num > 0){ + int n = eq_n[rand()%eq_num];//該当アイテムの中からランダム + if(rand()%10000 < per){ + if(sd->status.inventory[n].equip) + pc_unequipitem(sd,n,0); + pc_dropitem(sd,n,1); + } + } + } + else if(id > 0){ + for(i=0;i<MAX_INVENTORY;i++){ + if(sd->status.inventory[i].nameid == id//ItemIDが一致していて + && rand()%10000 < per//ドロップ率判定もOKで + && ((type == 1 && !sd->status.inventory[i].equip)//タイプ判定もOKならドロップ + || (type == 2 && sd->status.inventory[i].equip) + || type == 3) ){ + if(sd->status.inventory[i].equip) + pc_unequipitem(sd,i,0); + pc_dropitem(sd,i,1); + break; + } + } + } + } + } + // pvp + if( map[sd->bl.m].flag.pvp && !battle_config.pk_mode){ // disable certain pvp functions on pk_mode [Valaris] + //ランキング計算 + if(!map[sd->bl.m].flag.pvp_nocalcrank){ + sd->pvp_point-=5; + if(src && src->type==BL_PC ) + ((struct map_session_data *)src)->pvp_point++; + //} //fixed wrong '{' placement by Lupus + pc_setdead(sd); + } + // 強制送還 + if( sd->pvp_point < 0 ){ + sd->pvp_point=0; + pc_setstand(sd); + pc_setrestartvalue(sd,3); + pc_setpos(sd,sd->status.save_point.map,sd->status.save_point.x,sd->status.save_point.y,0); + } + } + //GvG + if(map[sd->bl.m].flag.gvg){ + pc_setstand(sd); + pc_setrestartvalue(sd,3); + pc_setpos(sd,sd->status.save_point.map,sd->status.save_point.x,sd->status.save_point.y,0); + } + + return 0; +} + +// +// script関 連 +// +/*========================================== + * script用PCステータス読み出し + *------------------------------------------ + */ +int pc_readparam(struct map_session_data *sd,int type) +{ + int val=0; + struct pc_base_job s_class; + + s_class = pc_calc_base_job(sd->status.class); + + nullpo_retr(0, sd); + + switch(type){ + case SP_SKILLPOINT: + val= sd->status.skill_point; + break; + case SP_STATUSPOINT: + val= sd->status.status_point; + break; + case SP_ZENY: + val= sd->status.zeny; + break; + case SP_BASELEVEL: + val= sd->status.base_level; + break; + case SP_JOBLEVEL: + val= sd->status.job_level; + break; + case SP_CLASS: + if(val>=24 && val < 45) + val+=3978; + else + val= sd->status.class; + break; + case SP_UPPER: + val= s_class.upper; + break; + case SP_SEX: + val= sd->sex; + break; + case SP_WEIGHT: + val= sd->weight; + break; + case SP_MAXWEIGHT: + val= sd->max_weight; + break; + case SP_BASEEXP: + val= sd->status.base_exp; + break; + case SP_JOBEXP: + val= sd->status.job_exp; + break; + case SP_NEXTBASEEXP: + val= pc_nextbaseexp(sd); + break; + case SP_NEXTJOBEXP: + val= pc_nextjobexp(sd); + break; + case SP_HP: + val= sd->status.hp; + break; + case SP_MAXHP: + val= sd->status.max_hp; + break; + case SP_SP: + val= sd->status.sp; + break; + case SP_MAXSP: + val= sd->status.max_sp; + break; + case SP_STR: + val= sd->status.str; + break; + case SP_AGI: + val= sd->status.agi; + break; + case SP_VIT: + val= sd->status.vit; + break; + case SP_INT: + val= sd->status.int_; + break; + case SP_DEX: + val= sd->status.dex; + break; + case SP_LUK: + val= sd->status.luk; + break; + case SP_FAME: + val= sd->fame; + break; + } + + return val; +} + +/*========================================== + * script用PCステータス設定 + *------------------------------------------ + */ +int pc_setparam(struct map_session_data *sd,int type,int val) +{ + int i = 0,up_level = 50; + struct pc_base_job s_class; + + nullpo_retr(0, sd); + + s_class = pc_calc_base_job(sd->status.class); + + switch(type){ + case SP_BASELEVEL: + if (val > sd->status.base_level) { + for (i = 1; i <= (val - sd->status.base_level); i++) + sd->status.status_point += (sd->status.base_level + i + 14) / 5 ; + } + sd->status.base_level = val; + sd->status.base_exp = 0; + clif_updatestatus(sd, SP_BASELEVEL); + clif_updatestatus(sd, SP_NEXTBASEEXP); + clif_updatestatus(sd, SP_STATUSPOINT); + clif_updatestatus(sd, SP_BASEEXP); + pc_calcstatus(sd, 0); + pc_heal(sd, sd->status.max_hp, sd->status.max_sp); + break; + case SP_JOBLEVEL: + if (sd->status.class == 0) + up_level -= 40; + if ((sd->status.class == 23) || (sd->status.class >= 4001 && sd->status.class <= 4022)) + up_level += 20; + if (val >= sd->status.job_level) { + if (val > up_level)val = up_level; + sd->status.skill_point += (val-sd->status.job_level); + sd->status.job_level = val; + sd->status.job_exp = 0; + clif_updatestatus(sd, SP_JOBLEVEL); + clif_updatestatus(sd, SP_NEXTJOBEXP); + clif_updatestatus(sd, SP_JOBEXP); + clif_updatestatus(sd, SP_SKILLPOINT); + pc_calcstatus(sd, 0); + clif_misceffect(&sd->bl, 1); + } else { + sd->status.job_level = val; + sd->status.job_exp = 0; + clif_updatestatus(sd, SP_JOBLEVEL); + clif_updatestatus(sd, SP_NEXTJOBEXP); + clif_updatestatus(sd, SP_JOBEXP); + pc_calcstatus(sd, 0); + } + clif_updatestatus(sd,type); + break; + case SP_SKILLPOINT: + sd->status.skill_point = val; + break; + case SP_STATUSPOINT: + sd->status.status_point = val; + break; + case SP_ZENY: + sd->status.zeny = val; + break; + case SP_BASEEXP: + if(pc_nextbaseexp(sd) > 0) { + sd->status.base_exp = val; + if(sd->status.base_exp < 0) + sd->status.base_exp=0; + pc_checkbaselevelup(sd); + } + break; + case SP_JOBEXP: + if(pc_nextjobexp(sd) > 0) { + sd->status.job_exp = val; + if(sd->status.job_exp < 0) + sd->status.job_exp=0; + pc_checkjoblevelup(sd); + } + break; + case SP_SEX: + sd->sex = val; + break; + case SP_WEIGHT: + sd->weight = val; + break; + case SP_MAXWEIGHT: + sd->max_weight = val; + break; + case SP_HP: + sd->status.hp = val; + break; + case SP_MAXHP: + sd->status.max_hp = val; + break; + case SP_SP: + sd->status.sp = val; + break; + case SP_MAXSP: + sd->status.max_sp = val; + break; + case SP_STR: + sd->status.str = val; + break; + case SP_AGI: + sd->status.agi = val; + break; + case SP_VIT: + sd->status.vit = val; + break; + case SP_INT: + sd->status.int_ = val; + break; + case SP_DEX: + sd->status.dex = val; + break; + case SP_LUK: + sd->status.luk = val; + break; + case SP_FAME: + sd->fame = val; + break; + } + clif_updatestatus(sd,type); + + return 0; +} + +/*========================================== + * HP/SP回復 + *------------------------------------------ + */ +int pc_heal(struct map_session_data *sd,int hp,int sp) +{ +// if(battle_config.battle_log) +// printf("heal %d %d\n",hp,sp); + + nullpo_retr(0, sd); + + if(pc_checkoverhp(sd)) { + if(hp > 0) + hp = 0; + } + if(pc_checkoversp(sd)) { + if(sp > 0) + sp = 0; + } + + if(sd->sc_data && sd->sc_data[SC_BERSERK].timer!=-1) //バーサーク中は回復させないらしい + return 0; + + if(hp+sd->status.hp>sd->status.max_hp) + hp=sd->status.max_hp-sd->status.hp; + if(sp+sd->status.sp>sd->status.max_sp) + sp=sd->status.max_sp-sd->status.sp; + sd->status.hp+=hp; + if(sd->status.hp <= 0) { + sd->status.hp = 0; + pc_damage(NULL,sd,1); + hp = 0; + } + sd->status.sp+=sp; + if(sd->status.sp <= 0) + sd->status.sp = 0; + if(hp) + clif_updatestatus(sd,SP_HP); + if(sp) + clif_updatestatus(sd,SP_SP); + + if(sd->status.party_id>0) { // on-the-fly party hp updates [Valaris] + struct party *p=party_search(sd->status.party_id); + if(p!=NULL) clif_party_hp(p,sd); + } // end addition [Valaris] + + return hp + sp; +} + +/*========================================== + * HP/SP回復 + *------------------------------------------ + */ +int pc_itemheal(struct map_session_data *sd,int hp,int sp) +{ + int bonus; +// if(battle_config.battle_log) +// printf("heal %d %d\n",hp,sp); + + nullpo_retr(0, sd); + + if(sd->sc_data && sd->sc_data[SC_GOSPEL].timer!=-1) //バーサーク中は回復させないらしい + return 0; + + if(sd->state.potionpitcher_flag) { + sd->potion_hp = hp; + sd->potion_sp = sp; + return 0; + } + + if(pc_checkoverhp(sd)) { + if(hp > 0) + hp = 0; + } + if(pc_checkoversp(sd)) { + if(sp > 0) + sp = 0; + } + if(hp > 0) { + bonus = (sd->paramc[2]<<1) + 100 + pc_checkskill(sd,SM_RECOVERY)*10; + if(bonus != 100) + hp = hp * bonus / 100; + bonus = 100 + pc_checkskill(sd,AM_LEARNINGPOTION)*5; + if(bonus != 100) + hp = hp * bonus / 100; + } + if(sp > 0) { + bonus = (sd->paramc[3]<<1) + 100 + pc_checkskill(sd,MG_SRECOVERY)*10; + if(bonus != 100) + sp = sp * bonus / 100; + bonus = 100 + pc_checkskill(sd,AM_LEARNINGPOTION)*5; + if(bonus != 100) + sp = sp * bonus / 100; + } + if(hp+sd->status.hp>sd->status.max_hp) + hp=sd->status.max_hp-sd->status.hp; + if(sp+sd->status.sp>sd->status.max_sp) + sp=sd->status.max_sp-sd->status.sp; + sd->status.hp+=hp; + if(sd->status.hp <= 0) { + sd->status.hp = 0; + pc_damage(NULL,sd,1); + hp = 0; + } + sd->status.sp+=sp; + if(sd->status.sp <= 0) + sd->status.sp = 0; + if(hp) + clif_updatestatus(sd,SP_HP); + if(sp) + clif_updatestatus(sd,SP_SP); + + return 0; +} + +/*========================================== + * HP/SP回復 + *------------------------------------------ + */ +int pc_percentheal(struct map_session_data *sd,int hp,int sp) +{ + nullpo_retr(0, sd); + + if(sd->state.potionpitcher_flag) { + sd->potion_per_hp = hp; + sd->potion_per_sp = sp; + return 0; + } + + if(pc_checkoverhp(sd)) { + if(hp > 0) + hp = 0; + } + if(pc_checkoversp(sd)) { + if(sp > 0) + sp = 0; + } + if(hp) { + if(hp >= 100) { + sd->status.hp = sd->status.max_hp; + } + else if(hp <= -100) { + sd->status.hp = 0; + pc_damage(NULL,sd,1); + } + else { + sd->status.hp += sd->status.max_hp*hp/100; + if(sd->status.hp > sd->status.max_hp) + sd->status.hp = sd->status.max_hp; + if(sd->status.hp <= 0) { + sd->status.hp = 0; + pc_damage(NULL,sd,1); + hp = 0; + } + } + } + if(sp) { + if(sp >= 100) { + sd->status.sp = sd->status.max_sp; + } + else if(sp <= -100) { + sd->status.sp = 0; + } + else { + sd->status.sp += sd->status.max_sp*sp/100; + if(sd->status.sp > sd->status.max_sp) + sd->status.sp = sd->status.max_sp; + if(sd->status.sp < 0) + sd->status.sp = 0; + } + } + if(hp) + clif_updatestatus(sd,SP_HP); + if(sp) + clif_updatestatus(sd,SP_SP); + + return 0; +} + +/*========================================== + * 職変更 + * 引数 job 職業 0〜23 + * upper 通常 0, 転生 1, 養子 2, そのまま -1 + *------------------------------------------ + */ +int pc_jobchange(struct map_session_data *sd,int job, int upper) +{ + int i; + int b_class = 0; + //転生や養子の場合の元の職業を算出する + struct pc_base_job s_class = pc_calc_base_job(sd->status.class); + + nullpo_retr(0, sd); + + if((job > 23) && (job < 68)) + job += 3977; + + if((job > 69) && (job < 4000)) + return 1; + + if(upper < 0) //現在転生かどうかを判断する + upper = s_class.upper; + + if(upper == 0){ //通常職ならjobそのまんま + b_class = job; + }else if(upper == 1){ + if(job == 23){ //転生にスパノビは存在しないのでお断り + return 1; + }else{ + b_class = job + 4001; + } + }else if(upper == 2){ //養子に結婚はないけどどうせ次で蹴られるからいいや + b_class = (job==23)?job + 4022:job + 4023; + }else{ + return 1; + } + + if((sd->status.sex == 0 && job == 19) || (sd->status.sex == 1 && job == 20) || + (sd->status.sex == 0 && job == 4020) || (sd->status.sex == 1 && job == 4021) || + job ==22 || sd->status.class == b_class) //♀はバードになれない、♂はダンサーになれない、結婚衣裳もお断り + return 1; + + sd->status.class = sd->view_class = b_class; + + sd->status.job_level=1; + sd->status.job_exp=0; + clif_updatestatus(sd,SP_JOBLEVEL); + clif_updatestatus(sd,SP_JOBEXP); + clif_updatestatus(sd,SP_NEXTJOBEXP); + + for(i=0;i<11;i++) { + if(sd->equip_index[i] >= 0) + if(!pc_isequip(sd,sd->equip_index[i])) + pc_unequipitem(sd,sd->equip_index[i],1); // 装備外し + } + + clif_changelook(&sd->bl,LOOK_BASE,sd->view_class); // move sprite update to prevent client crashes with incompatible equipment [Valaris] + if(sd->status.clothes_color > 0) + clif_changelook(&sd->bl,LOOK_CLOTHES_COLOR,sd->status.clothes_color); + if(battle_config.muting_players && sd->status.manner < 0) + clif_changestatus(&sd->bl,SP_MANNER,sd->status.manner); + + pc_calcstatus(sd,0); + pc_checkallowskill(sd); + pc_equiplookall(sd); + clif_equiplist(sd); + + if(pc_isriding(sd)) { // remove peco status if changing into invalid class [Valaris] + if(!(pc_checkskill(sd,KN_RIDING))) + pc_setoption(sd,sd->status.option|-0x0000); + if(pc_checkskill(sd,KN_RIDING)>0) + pc_setriding(sd); + } + + return 0; +} + +/*========================================== + * 見た目変更 + *------------------------------------------ + */ +int pc_equiplookall(struct map_session_data *sd) +{ + nullpo_retr(0, sd); + +#if PACKETVER < 4 + clif_changelook(&sd->bl,LOOK_WEAPON,sd->status.weapon); + clif_changelook(&sd->bl,LOOK_SHIELD,sd->status.shield); +#else + clif_changelook(&sd->bl,LOOK_WEAPON,0); + clif_changelook(&sd->bl,LOOK_SHOES,0); +#endif + clif_changelook(&sd->bl,LOOK_HEAD_BOTTOM,sd->status.head_bottom); + clif_changelook(&sd->bl,LOOK_HEAD_TOP,sd->status.head_top); + clif_changelook(&sd->bl,LOOK_HEAD_MID,sd->status.head_mid); + + return 0; +} + +/*========================================== + * 見た目変更 + *------------------------------------------ + */ +int pc_changelook(struct map_session_data *sd,int type,int val) +{ + nullpo_retr(0, sd); + + switch(type){ + case LOOK_HAIR: + sd->status.hair=val; + break; + case LOOK_WEAPON: + sd->status.weapon=val; + break; + case LOOK_HEAD_BOTTOM: + sd->status.head_bottom=val; + break; + case LOOK_HEAD_TOP: + sd->status.head_top=val; + break; + case LOOK_HEAD_MID: + sd->status.head_mid=val; + break; + case LOOK_HAIR_COLOR: + sd->status.hair_color=val; + break; + case LOOK_CLOTHES_COLOR: + sd->status.clothes_color=val; + break; + case LOOK_SHIELD: + sd->status.shield=val; + break; + case LOOK_SHOES: + break; + } + clif_changelook(&sd->bl,type,val); + + return 0; +} + +/*========================================== + * 付属品(鷹,ペコ,カート)設定 + *------------------------------------------ + */ +int pc_setoption(struct map_session_data *sd,int type) +{ + nullpo_retr(0, sd); + + sd->status.option=type; + clif_changeoption(&sd->bl); + pc_calcstatus(sd,0); + + return 0; +} + +/*========================================== + * カート設定 + *------------------------------------------ + */ +int pc_setcart(struct map_session_data *sd,int type) +{ + int cart[6]={0x0000,0x0008,0x0080,0x0100,0x0200,0x0400}; + + nullpo_retr(0, sd); + + if(pc_checkskill(sd,MC_PUSHCART)>0){ // プッシュカートスキル所持 + if(!pc_iscarton(sd)){ // カートを付けていない + pc_setoption(sd,cart[type]); + clif_cart_itemlist(sd); + clif_cart_equiplist(sd); + clif_updatestatus(sd,SP_CARTINFO); + clif_status_change(&sd->bl,0x0c,0); + } + else{ + pc_setoption(sd,cart[type]); + } + } + + return 0; +} + +/*========================================== + * 鷹設定 + *------------------------------------------ + */ +int pc_setfalcon(struct map_session_data *sd) +{ + if(pc_checkskill(sd,HT_FALCON)>0){ // ファルコンマスタリースキル所持 + pc_setoption(sd,sd->status.option|0x0010); + } + + return 0; +} + +/*========================================== + * ペコペコ設定 + *------------------------------------------ + */ +int pc_setriding(struct map_session_data *sd) +{ + if(sd->disguise > 0) { // temporary prevention of crash caused by peco + disguise, will look into a better solution [Valaris] + clif_displaymessage(sd->fd, "Cannot mount a Peco while in disguise."); + return 0; + } + + if((pc_checkskill(sd,KN_RIDING)>0)){ // ライディングスキル所持 + pc_setoption(sd,sd->status.option|0x0020); + + if(sd->status.class==7) + sd->status.class=sd->view_class=13; + + if(sd->status.class==14) + sd->status.class=sd->view_class=21; + + if(sd->status.class==4008) + sd->status.class=sd->view_class=4014; + + if(sd->status.class==4015) + sd->status.class=sd->view_class=4022; + } + + return 0; +} + +/*========================================== + * script用変数の値を読む + *------------------------------------------ + */ +int pc_readreg(struct map_session_data *sd,int reg) +{ + int i; + + nullpo_retr(0, sd); + + for(i=0;i<sd->reg_num;i++) + if(sd->reg[i].index==reg) + return sd->reg[i].data; + + return 0; +} +/*========================================== + * script用変数の値を設定 + *------------------------------------------ + */ +int pc_setreg(struct map_session_data *sd,int reg,int val) +{ + int i; + + nullpo_retr(0, sd); + + for (i = 0; i < sd->reg_num; i++) { + if (sd->reg[i].index == reg){ + sd->reg[i].data = val; + return 0; + } + } + sd->reg_num++; + sd->reg = realloc(sd->reg, sizeof(*(sd->reg)) * sd->reg_num); + if (sd->reg == NULL){ + printf("out of memory : pc_setreg\n"); + exit(1); + } +/* memset(sd->reg + (sd->reg_num - 1) * sizeof(*(sd->reg)), 0, + sizeof(*(sd->reg))); +*/ + sd->reg[i].index = reg; + sd->reg[i].data = val; + + return 0; +} + +/*========================================== + * script用文字列変数の値を読む + *------------------------------------------ + */ +char *pc_readregstr(struct map_session_data *sd,int reg) +{ + int i; + + nullpo_retr(0, sd); + + for(i=0;i<sd->regstr_num;i++) + if(sd->regstr[i].index==reg) + return sd->regstr[i].data; + + return NULL; +} +/*========================================== + * script用文字列変数の値を設定 + *------------------------------------------ + */ +int pc_setregstr(struct map_session_data *sd,int reg,char *str) +{ + int i; + + nullpo_retr(0, sd); + + if(strlen(str)+1 >= sizeof(sd->regstr[0].data)){ + printf("pc_setregstr: string too long !\n"); + return 0; + } + + for(i=0;i<sd->regstr_num;i++) + if(sd->regstr[i].index==reg){ + strcpy(sd->regstr[i].data,str); + return 0; + } + sd->regstr_num++; + sd->regstr = realloc(sd->regstr, sizeof(sd->regstr[0]) * sd->regstr_num); + if(sd->regstr==NULL){ + printf("out of memory : pc_setreg\n"); + exit(1); + } +/* memset(sd->reg + (sd->reg_num - 1) * sizeof(*(sd->reg)), 0, + sizeof(*(sd->reg))); +*/ + sd->regstr[i].index=reg; + strcpy(sd->regstr[i].data,str); + + return 0; +} + +/*========================================== + * script用グローバル変数の値を読む + *------------------------------------------ + */ +int pc_readglobalreg(struct map_session_data *sd,char *reg) +{ + int i; + + nullpo_retr(0, sd); + + for(i=0;i<sd->status.global_reg_num;i++){ + if(strcmp(sd->status.global_reg[i].str,reg)==0) + return sd->status.global_reg[i].value; + } + + return 0; +} + +/*========================================== + * script用グローバル変数の値を設定 + *------------------------------------------ + */ +int pc_setglobalreg(struct map_session_data *sd,char *reg,int val) +{ + int i; + + nullpo_retr(0, sd); + + //PC_DIE_COUNTERがスクリプトなどで変更された時の処理 + if(strcmp(reg,"PC_DIE_COUNTER") == 0 && sd->die_counter != val){ + sd->die_counter = val; + pc_calcstatus(sd,0); + } + if(val==0){ + for(i=0;i<sd->status.global_reg_num;i++){ + if(strcmp(sd->status.global_reg[i].str,reg)==0){ + sd->status.global_reg[i]=sd->status.global_reg[sd->status.global_reg_num-1]; + sd->status.global_reg_num--; + break; + } + } + return 0; + } + for(i=0;i<sd->status.global_reg_num;i++){ + if(strcmp(sd->status.global_reg[i].str,reg)==0){ + sd->status.global_reg[i].value=val; + return 0; + } + } + if(sd->status.global_reg_num<GLOBAL_REG_NUM){ + strcpy(sd->status.global_reg[i].str,reg); + sd->status.global_reg[i].value=val; + sd->status.global_reg_num++; + return 0; + } + if(battle_config.error_log) + printf("pc_setglobalreg : couldn't set %s (GLOBAL_REG_NUM = %d)\n", reg, GLOBAL_REG_NUM); + + return 1; +} + +/*========================================== + * script用アカウント変数の値を読む + *------------------------------------------ + */ +int pc_readaccountreg(struct map_session_data *sd,char *reg) +{ + int i; + + nullpo_retr(0, sd); + + for(i=0;i<sd->status.account_reg_num;i++){ + if(strcmp(sd->status.account_reg[i].str,reg)==0) + return sd->status.account_reg[i].value; + } + + return 0; +} +/*========================================== + * script用アカウント変数の値を設定 + *------------------------------------------ + */ +int pc_setaccountreg(struct map_session_data *sd,char *reg,int val) +{ + int i; + + nullpo_retr(0, sd); + + if(val==0){ + for(i=0;i<sd->status.account_reg_num;i++){ + if(strcmp(sd->status.account_reg[i].str,reg)==0){ + sd->status.account_reg[i]=sd->status.account_reg[sd->status.account_reg_num-1]; + sd->status.account_reg_num--; + break; + } + } + intif_saveaccountreg(sd); + return 0; + } + for(i=0;i<sd->status.account_reg_num;i++){ + if(strcmp(sd->status.account_reg[i].str,reg)==0){ + sd->status.account_reg[i].value=val; + intif_saveaccountreg(sd); + return 0; + } + } + if(sd->status.account_reg_num<ACCOUNT_REG_NUM){ + strcpy(sd->status.account_reg[i].str,reg); + sd->status.account_reg[i].value=val; + sd->status.account_reg_num++; + intif_saveaccountreg(sd); + return 0; + } + if(battle_config.error_log) + printf("pc_setaccountreg : couldn't set %s (ACCOUNT_REG_NUM = %d)\n", reg, ACCOUNT_REG_NUM); + + return 1; +} +/*========================================== + * script用アカウント変数2の値を読む + *------------------------------------------ + */ +int pc_readaccountreg2(struct map_session_data *sd,char *reg) +{ + int i; + + nullpo_retr(0, sd); + + for(i=0;i<sd->status.account_reg2_num;i++){ + if(strcmp(sd->status.account_reg2[i].str,reg)==0) + return sd->status.account_reg2[i].value; + } + + return 0; +} +/*========================================== + * script用アカウント変数2の値を設定 + *------------------------------------------ + */ +int pc_setaccountreg2(struct map_session_data *sd,char *reg,int val) +{ + int i; + + nullpo_retr(1, sd); + + if(val==0){ + for(i=0;i<sd->status.account_reg2_num;i++){ + if(strcmp(sd->status.account_reg2[i].str,reg)==0){ + sd->status.account_reg2[i]=sd->status.account_reg2[sd->status.account_reg2_num-1]; + sd->status.account_reg2_num--; + break; + } + } + chrif_saveaccountreg2(sd); + return 0; + } + for(i=0;i<sd->status.account_reg2_num;i++){ + if(strcmp(sd->status.account_reg2[i].str,reg)==0){ + sd->status.account_reg2[i].value=val; + chrif_saveaccountreg2(sd); + return 0; + } + } + if(sd->status.account_reg2_num<ACCOUNT_REG2_NUM){ + strcpy(sd->status.account_reg2[i].str,reg); + sd->status.account_reg2[i].value=val; + sd->status.account_reg2_num++; + chrif_saveaccountreg2(sd); + return 0; + } + if(battle_config.error_log) + printf("pc_setaccountreg2 : couldn't set %s (ACCOUNT_REG2_NUM = %d)\n", reg, ACCOUNT_REG2_NUM); + + return 1; +} +/*========================================== + * 精錬成功率 + *------------------------------------------ + */ +int pc_percentrefinery(struct map_session_data *sd,struct item *item) +{ + int percent; + + nullpo_retr(0, item); + percent=percentrefinery[itemdb_wlv(item->nameid)][(int)item->refine]; + + percent += pc_checkskill(sd,BS_WEAPONRESEARCH); // 武器研究スキル所持 + + // 確率の有効範囲チェック + if( percent > 100 ){ + percent = 100; + } + if( percent < 0 ){ + percent = 0; + } + + return percent; +} + +/*========================================== + * イベントタイマー処理 + *------------------------------------------ + */ +int pc_eventtimer(int tid,unsigned int tick,int id,int data) +{ + struct map_session_data *sd=map_id2sd(id); + int i; + if(sd==NULL) + return 0; + + for(i=0;i<MAX_EVENTTIMER;i++){ + if( sd->eventtimer[i]==tid ){ + sd->eventtimer[i]=-1; + npc_event(sd,(const char *)data,0); + break; + } + } + free((void *)data); + if(i==MAX_EVENTTIMER) { + if(battle_config.error_log) + printf("pc_eventtimer: no such event timer\n"); + } + + return 0; +} + +/*========================================== + * イベントタイマー追加 + *------------------------------------------ + */ +int pc_addeventtimer(struct map_session_data *sd,int tick,const char *name) +{ + int i; + + nullpo_retr(0, sd); + + for(i=0;i<MAX_EVENTTIMER;i++) + if( sd->eventtimer[i]==-1 ) + break; + if(i<MAX_EVENTTIMER){ + char *evname=(char *)aCalloc(24,sizeof(char)); + memcpy(evname,name,24); + sd->eventtimer[i]=add_timer(gettick()+tick, + pc_eventtimer,sd->bl.id,(int)evname); + } + + return 0; +} + +/*========================================== + * イベントタイマー削除 + *------------------------------------------ + */ +int pc_deleventtimer(struct map_session_data *sd,const char *name) +{ + int i; + + nullpo_retr(0, sd); + + for(i=0;i<MAX_EVENTTIMER;i++) + if( sd->eventtimer[i]!=-1 && strcmp( + (char *)(get_timer(sd->eventtimer[i])->data), name)==0 ){ + delete_timer(sd->eventtimer[i],pc_eventtimer); + sd->eventtimer[i]=-1; + break; + } + + return 0; +} + +/*========================================== + * イベントタイマーカウント値追加 + *------------------------------------------ + */ +int pc_addeventtimercount(struct map_session_data *sd,const char *name,int tick) +{ + int i; + + nullpo_retr(0, sd); + + for(i=0;i<MAX_EVENTTIMER;i++) + if( sd->eventtimer[i]!=-1 && strcmp( + (char *)(get_timer(sd->eventtimer[i])->data), name)==0 ){ + addtick_timer(sd->eventtimer[i],tick); + break; + } + + return 0; +} + +/*========================================== + * イベントタイマー全削除 + *------------------------------------------ + */ +int pc_cleareventtimer(struct map_session_data *sd) +{ + int i; + + nullpo_retr(0, sd); + + for(i=0;i<MAX_EVENTTIMER;i++) + if( sd->eventtimer[i]!=-1 ){ + delete_timer(sd->eventtimer[i],pc_eventtimer); + sd->eventtimer[i]=-1; + } + + return 0; +} + +// +// 装 備物 +// +/*========================================== + * アイテムを装備する + *------------------------------------------ + */ +int pc_equipitem(struct map_session_data *sd,int n,int pos) +{ + int i,nameid, arrow; + struct item_data *id; + //転生や養子の場合の元の職業を算出する + + nullpo_retr(0, sd); + + nameid = sd->status.inventory[n].nameid; + id = sd->inventory_data[n]; + pos = pc_equippoint(sd,n); + + if(battle_config.battle_log) + printf("equip %d(%d) %x:%x\n",nameid,n,id->equip,pos); + if(!pc_isequip(sd,n) || !pos || sd->status.inventory[n].broken==1 ) { // [Valaris] + clif_equipitemack(sd,n,0,0); // fail + return 0; + } + +// -- moonsoul (if player is berserk then cannot equip) +// + if(sd->sc_data[SC_BERSERK].timer!=-1){ + clif_equipitemack(sd,n,0,0); // fail + return 0; + } + + if(pos==0x88){ // アクセサリ用例外処理 + int epor=0; + if(sd->equip_index[0] >= 0) + epor |= sd->status.inventory[sd->equip_index[0]].equip; + if(sd->equip_index[1] >= 0) + epor |= sd->status.inventory[sd->equip_index[1]].equip; + epor &= 0x88; + pos = epor == 0x08 ? 0x80 : 0x08; + } + + // 二刀流処理 + if ((pos==0x22) // 一応、装備要求箇所が二刀流武器かチェックする + && (id->equip==2) // 単 手武器 + && (pc_checkskill(sd, AS_LEFT) > 0 || sd->status.class == 12) ) // 左手修錬有 + { + int tpos=0; + if(sd->equip_index[8] >= 0) + tpos |= sd->status.inventory[sd->equip_index[8]].equip; + if(sd->equip_index[9] >= 0) + tpos |= sd->status.inventory[sd->equip_index[9]].equip; + tpos &= 0x02; + pos = tpos == 0x02 ? 0x20 : 0x02; + } + + arrow=pc_search_inventory(sd,pc_checkequip(sd,9)); // Added by RoVeRT + for(i=0;i<11;i++) { + if(sd->equip_index[i] >= 0 && sd->status.inventory[sd->equip_index[i]].equip&pos) { + pc_unequipitem(sd,sd->equip_index[i],1); + } + } + // 弓矢装備 + if(pos==0x8000){ + clif_arrowequip(sd,n); + clif_arrow_fail(sd,3); // 3=矢が装備できました + } + else + clif_equipitemack(sd,n,pos,1); + + for(i=0;i<11;i++) { + if(pos & equip_pos[i]) + sd->equip_index[i] = n; + } + sd->status.inventory[n].equip=pos; + + if(sd->status.inventory[n].equip & 0x0002) { + if(sd->inventory_data[n]) + sd->weapontype1 = sd->inventory_data[n]->look; + else + sd->weapontype1 = 0; + pc_calcweapontype(sd); + clif_changelook(&sd->bl,LOOK_WEAPON,sd->status.weapon); + } + if(sd->status.inventory[n].equip & 0x0020) { + if(sd->inventory_data[n]) { + if(sd->inventory_data[n]->type == 4) { + sd->status.shield = 0; + if(sd->status.inventory[n].equip == 0x0020) + sd->weapontype2 = sd->inventory_data[n]->look; + else + sd->weapontype2 = 0; + } + else if(sd->inventory_data[n]->type == 5) { + sd->status.shield = sd->inventory_data[n]->look; + sd->weapontype2 = 0; + } + } + else + sd->status.shield = sd->weapontype2 = 0; + pc_calcweapontype(sd); + clif_changelook(&sd->bl,LOOK_SHIELD,sd->status.shield); + } + if(sd->status.inventory[n].equip & 0x0001) { + if(sd->inventory_data[n]) + sd->status.head_bottom = sd->inventory_data[n]->look; + else + sd->status.head_bottom = 0; + clif_changelook(&sd->bl,LOOK_HEAD_BOTTOM,sd->status.head_bottom); + } + if(sd->status.inventory[n].equip & 0x0100) { + if(sd->inventory_data[n]) + sd->status.head_top = sd->inventory_data[n]->look; + else + sd->status.head_top = 0; + clif_changelook(&sd->bl,LOOK_HEAD_TOP,sd->status.head_top); + } + if(sd->status.inventory[n].equip & 0x0200) { + if(sd->inventory_data[n]) + sd->status.head_mid = sd->inventory_data[n]->look; + else + sd->status.head_mid = 0; + clif_changelook(&sd->bl,LOOK_HEAD_MID,sd->status.head_mid); + } + if(sd->status.inventory[n].equip & 0x0040) + clif_changelook(&sd->bl,LOOK_SHOES,0); + + pc_checkallowskill(sd); // 装備品でスキルか解除されるかチェック + if (itemdb_look(sd->status.inventory[n].nameid) == 11 && arrow){ // Added by RoVeRT + clif_arrowequip(sd,arrow); + sd->status.inventory[arrow].equip=32768; + } + pc_calcstatus(sd,0); + + if(sd->special_state.infinite_endure) { + if(sd->sc_data[SC_ENDURE].timer == -1) + skill_status_change_start(&sd->bl,SC_ENDURE,10,1,0,0,0,0); + } + else { + if(sd->sc_data[SC_ENDURE].timer != -1 && sd->sc_data[SC_ENDURE].val2) + skill_status_change_end(&sd->bl,SC_ENDURE,-1); + } + + if(sd->sc_data[SC_SIGNUMCRUCIS].timer != -1 && !battle_check_undead(7,sd->def_ele)) + skill_status_change_end(&sd->bl,SC_SIGNUMCRUCIS,-1); + if(sd->sc_data[SC_DANCING].timer!=-1 && (sd->status.weapon != 13 && sd->status.weapon !=14)) + skill_stop_dancing(&sd->bl,0); + + return 0; +} + +/*========================================== + * 装 備した物を外す + *------------------------------------------ + */ +int pc_unequipitem(struct map_session_data *sd,int n,int type) +{ + nullpo_retr(0, sd); + +// -- moonsoul (if player is berserk then cannot unequip) +// + if(sd->sc_data[SC_BERSERK].timer!=-1){ + clif_unequipitemack(sd,n,0,0); + return 0; + } + + if(battle_config.battle_log) + printf("unequip %d %x:%x\n",n,pc_equippoint(sd,n),sd->status.inventory[n].equip); + if(sd->status.inventory[n].equip){ + int i; + for(i=0;i<11;i++) { + if(sd->status.inventory[n].equip & equip_pos[i]) + sd->equip_index[i] = -1; + } + if(sd->status.inventory[n].equip & 0x0002) { + sd->weapontype1 = 0; + sd->status.weapon = sd->weapontype2; + pc_calcweapontype(sd); + clif_changelook(&sd->bl,LOOK_WEAPON,sd->status.weapon); + } + if(sd->status.inventory[n].equip & 0x0020) { + sd->status.shield = sd->weapontype2 = 0; + pc_calcweapontype(sd); + clif_changelook(&sd->bl,LOOK_SHIELD,sd->status.shield); + } + if(sd->status.inventory[n].equip & 0x0001) { + sd->status.head_bottom = 0; + clif_changelook(&sd->bl,LOOK_HEAD_BOTTOM,sd->status.head_bottom); + } + if(sd->status.inventory[n].equip & 0x0100) { + sd->status.head_top = 0; + clif_changelook(&sd->bl,LOOK_HEAD_TOP,sd->status.head_top); + } + if(sd->status.inventory[n].equip & 0x0200) { + sd->status.head_mid = 0; + clif_changelook(&sd->bl,LOOK_HEAD_MID,sd->status.head_mid); + } + if(sd->status.inventory[n].equip & 0x0040) + clif_changelook(&sd->bl,LOOK_SHOES,0); + + if(sd->sc_data[SC_BROKNWEAPON].timer != -1 && sd->status.inventory[n].equip & 0x0002 && + sd->status.inventory[i].broken==1) + skill_status_change_end(&sd->bl,SC_BROKNWEAPON,-1); + + clif_unequipitemack(sd,n,sd->status.inventory[n].equip,1); + sd->status.inventory[n].equip=0; + if(!type) + pc_checkallowskill(sd); + if(sd->weapontype1 == 0 && sd->weapontype2 == 0) + skill_encchant_eremental_end(&sd->bl,-1); //武器持ち誓えは無条件で属性付与解除 + } else { + clif_unequipitemack(sd,n,0,0); + } + if(!type) { + pc_calcstatus(sd,0); + if(sd->sc_data[SC_SIGNUMCRUCIS].timer != -1 && !battle_check_undead(7,sd->def_ele)) + skill_status_change_end(&sd->bl,SC_SIGNUMCRUCIS,-1); + } + + return 0; +} + +/*========================================== + * アイテムのindex番号を詰めたり + * 装 備品の装備可能チェックを行なう + *------------------------------------------ + */ +int pc_checkitem(struct map_session_data *sd) +{ + int i,j,k,id,calc_flag = 0; + struct item_data *it=NULL; + + nullpo_retr(0, sd); + + // 所持品空き詰め + for(i=j=0;i<MAX_INVENTORY;i++){ + if( (id=sd->status.inventory[i].nameid)==0) + continue; + if( battle_config.item_check && !itemdb_available(id) ){ + if(battle_config.error_log) + printf("illeagal item id %d in %d[%s] inventory.\n",id,sd->bl.id,sd->status.name); + pc_delitem(sd,i,sd->status.inventory[i].amount,3); + continue; + } + if(i>j){ + memcpy(&sd->status.inventory[j],&sd->status.inventory[i],sizeof(struct item)); + sd->inventory_data[j] = sd->inventory_data[i]; + } + j++; + } + if(j < MAX_INVENTORY) + memset(&sd->status.inventory[j],0,sizeof(struct item)*(MAX_INVENTORY-j)); + for(k=j;k<MAX_INVENTORY;k++) + sd->inventory_data[k] = NULL; + + // カート内空き詰め + for(i=j=0;i<MAX_CART;i++){ + if( (id=sd->status.cart[i].nameid)==0 ) + continue; + if( battle_config.item_check && !itemdb_available(id) ){ + if(battle_config.error_log) + printf("illeagal item id %d in %d[%s] cart.\n",id,sd->bl.id,sd->status.name); + pc_cart_delitem(sd,i,sd->status.cart[i].amount,1); + continue; + } + if(i>j){ + memcpy(&sd->status.cart[j],&sd->status.cart[i],sizeof(struct item)); + } + j++; + } + if(j < MAX_CART) + memset(&sd->status.cart[j],0,sizeof(struct item)*(MAX_CART-j)); + + // 装 備位置チェック + + for(i=0;i<MAX_INVENTORY;i++){ + + it=sd->inventory_data[i]; + + if(sd->status.inventory[i].nameid==0) + continue; + if(sd->status.inventory[i].equip & ~pc_equippoint(sd,i)) { + sd->status.inventory[i].equip=0; + calc_flag = 1; + } + //装備制限チェック + if(sd->status.inventory[i].equip && map[sd->bl.m].flag.pvp && (it->flag.no_equip==1 || it->flag.no_equip==3)){//PvP制限 + sd->status.inventory[i].equip=0; + calc_flag = 1; + }else if(sd->status.inventory[i].equip && map[sd->bl.m].flag.gvg && (it->flag.no_equip==2 || it->flag.no_equip==3)){//GvG制限 + sd->status.inventory[i].equip=0; + calc_flag = 1; + } + } + + pc_setequipindex(sd); + if(calc_flag) + pc_calcstatus(sd,2); + + return 0; +} + +int pc_checkoverhp(struct map_session_data *sd) +{ + nullpo_retr(0, sd); + + if(sd->status.hp == sd->status.max_hp) + return 1; + if(sd->status.hp > sd->status.max_hp) { + sd->status.hp = sd->status.max_hp; + clif_updatestatus(sd,SP_HP); + return 2; + } + + return 0; +} + +int pc_checkoversp(struct map_session_data *sd) +{ + nullpo_retr(0, sd); + + if(sd->status.sp == sd->status.max_sp) + return 1; + if(sd->status.sp > sd->status.max_sp) { + sd->status.sp = sd->status.max_sp; + clif_updatestatus(sd,SP_SP); + return 2; + } + + return 0; +} + +/*========================================== + * PVP順位計算用(foreachinarea) + *------------------------------------------ + */ +int pc_calc_pvprank_sub(struct block_list *bl,va_list ap) +{ + struct map_session_data *sd1,*sd2=NULL; + + nullpo_retr(0, bl); + nullpo_retr(0, ap); + nullpo_retr(0, sd1=(struct map_session_data *)bl); + nullpo_retr(0, sd2=va_arg(ap,struct map_session_data *)); + + if( sd1->pvp_point > sd2->pvp_point ) + sd2->pvp_rank++; + return 0; +} +/*========================================== + * PVP順位計算 + *------------------------------------------ + */ +int pc_calc_pvprank(struct map_session_data *sd) +{ + int old; + struct map_data *m; + + nullpo_retr(0, sd); + nullpo_retr(0, m=&map[sd->bl.m]); + + old=sd->pvp_rank; + + if( !(m->flag.pvp) ) + return 0; + sd->pvp_rank=1; + map_foreachinarea(pc_calc_pvprank_sub,sd->bl.m,0,0,m->xs,m->ys,BL_PC,sd); + if(old!=sd->pvp_rank || sd->pvp_lastusers!=m->users) + clif_pvpset(sd,sd->pvp_rank,sd->pvp_lastusers=m->users,0); + return sd->pvp_rank; +} +/*========================================== + * PVP順位計算(timer) + *------------------------------------------ + */ +int pc_calc_pvprank_timer(int tid,unsigned int tick,int id,int data) +{ + struct map_session_data *sd=NULL; + if(battle_config.pk_mode) // disable pvp ranking if pk_mode on [Valaris] + return 0; + + sd=map_id2sd(id); + if(sd==NULL) + return 0; + sd->pvp_timer=-1; + if( pc_calc_pvprank(sd)>0 ) + sd->pvp_timer=add_timer( + gettick()+PVP_CALCRANK_INTERVAL, + pc_calc_pvprank_timer,id,data); + return 0; +} + +/*========================================== + * sdは結婚しているか(既婚の場合は相方のchar_idを返す) + *------------------------------------------ + */ +int pc_ismarried(struct map_session_data *sd) +{ + if(sd == NULL) + return -1; + if(sd->status.partner_id > 0) + return sd->status.partner_id; + else + return 0; +} +/*========================================== + * sdがdstsdと結婚(dstsd→sdの結婚処理も同時に行う) + *------------------------------------------ + */ +int pc_marriage(struct map_session_data *sd,struct map_session_data *dstsd) +{ + if(sd == NULL || dstsd == NULL || sd->status.partner_id > 0 || dstsd->status.partner_id > 0) + return -1; + sd->status.partner_id=dstsd->status.char_id; + dstsd->status.partner_id=sd->status.char_id; + return 0; +} + +/*========================================== + * sdが離婚(相手はsd->status.partner_idに依る)(相手も同時に離婚・結婚指輪自動剥奪) + *------------------------------------------ + */ +int pc_divorce(struct map_session_data *sd) +{ + struct map_session_data *p_sd=NULL; + if(sd == NULL || !pc_ismarried(sd)) + return -1; + + if( (p_sd=map_nick2sd(map_charid2nick(sd->status.partner_id))) !=NULL){ + int i; + if(p_sd->status.partner_id != sd->status.char_id || sd->status.partner_id != p_sd->status.char_id){ + printf("pc_divorce: Illegal partner_id sd=%d p_sd=%d\n",sd->status.partner_id,p_sd->status.partner_id); + return -1; + } + sd->status.partner_id=0; + p_sd->status.partner_id=0; + for(i=0;i<MAX_INVENTORY;i++) + if(sd->status.inventory[i].nameid == WEDDING_RING_M || sd->status.inventory[i].nameid == WEDDING_RING_F) + pc_delitem(sd,i,1,0); + for(i=0;i<MAX_INVENTORY;i++) + if(p_sd->status.inventory[i].nameid == WEDDING_RING_M || p_sd->status.inventory[i].nameid == WEDDING_RING_F) + pc_delitem(p_sd,i,1,0); + + }else{ + printf("pc_divorce: p_sd nullpo\n"); + return -1; + } + return 0; +} + +/*========================================== + * sdの相方のmap_session_dataを返す + *------------------------------------------ + */ +struct map_session_data *pc_get_partner(struct map_session_data *sd) +{ + struct map_session_data *p_sd = NULL; + char *nick; + if(sd == NULL || !pc_ismarried(sd)) + return NULL; + + nick=map_charid2nick(sd->status.partner_id); + + if (nick==NULL) + return NULL; + + if((p_sd=map_nick2sd(nick)) == NULL ) + return NULL; + + return p_sd; +} + +// +// 自然回復物 +// +/*========================================== + * SP回復量計算 + *------------------------------------------ + */ +static int natural_heal_tick,natural_heal_prev_tick,natural_heal_diff_tick; +static int pc_spheal(struct map_session_data *sd) +{ + int a; + struct guild_castle *gc = NULL; + + nullpo_retr(0, sd); + + a = natural_heal_diff_tick; + if(pc_issit(sd)) a += a; + if( sd->sc_data[SC_MAGNIFICAT].timer!=-1 ) // マグニフィカート + a += a; + + gc=guild_mapname2gc(sd->mapname); // Increased guild castle regen [Valaris] + if(gc) { + struct guild *g; + g=guild_search(sd->status.guild_id); + if(g && g->guild_id == gc->guild_id) + a += a; + } // end addition [Valaris] + + return a; +} + +/*========================================== + * HP回復量計算 + *------------------------------------------ + */ +static int pc_hpheal(struct map_session_data *sd) +{ + int a; + struct guild_castle *gc; + + nullpo_retr(0, sd); + + a = natural_heal_diff_tick; + if(pc_issit(sd)) a += a; + if( sd->sc_data[SC_MAGNIFICAT].timer!=-1 ) // Modified by RoVeRT + a += a; + + gc=guild_mapname2gc(sd->mapname); // Increased guild castle regen [Valaris] + if(gc) { + struct guild *g; + g=guild_search(sd->status.guild_id); + if(g && g->guild_id == gc->guild_id) + a += a; + } // end addition [Valaris] + + return a; +} + +static int pc_natural_heal_hp(struct map_session_data *sd) +{ + int bhp; + int inc_num,bonus,skill,hp_flag; + + nullpo_retr(0, sd); + + if (sd->sc_data[SC_TRICKDEAD].timer != -1) // Modified by RoVeRT + return 0; + + if(pc_checkoverhp(sd)) { + sd->hp_sub = sd->inchealhptick = 0; + return 0; + } + + bhp=sd->status.hp; + hp_flag = (pc_checkskill(sd,SM_MOVINGRECOVERY) > 0 && sd->walktimer != -1); + + if(sd->walktimer == -1) { + inc_num = pc_hpheal(sd); + if( sd->sc_data[SC_TENSIONRELAX].timer!=-1 ){ // テンションリラックス + sd->hp_sub += 2*inc_num; + sd->inchealhptick += 3*natural_heal_diff_tick; + }else{ + sd->hp_sub += inc_num; + sd->inchealhptick += natural_heal_diff_tick; + } + } + else if(hp_flag) { + inc_num = pc_hpheal(sd); + sd->hp_sub += inc_num; + sd->inchealhptick = 0; + } + else { + sd->hp_sub = sd->inchealhptick = 0; + return 0; + } + + if(sd->hp_sub >= battle_config.natural_healhp_interval) { + bonus = sd->nhealhp; + if(hp_flag) { + bonus >>= 2; + if(bonus <= 0) bonus = 1; + } + while(sd->hp_sub >= battle_config.natural_healhp_interval) { + sd->hp_sub -= battle_config.natural_healhp_interval; + if(sd->status.hp + bonus <= sd->status.max_hp) + sd->status.hp += bonus; + else { + sd->status.hp = sd->status.max_hp; + sd->hp_sub = sd->inchealhptick = 0; + } + } + } + if(bhp!=sd->status.hp) + clif_updatestatus(sd,SP_HP); + + if(sd->nshealhp > 0) { + if(sd->inchealhptick >= battle_config.natural_heal_skill_interval && sd->status.hp < sd->status.max_hp) { + bonus = sd->nshealhp; + while(sd->inchealhptick >= battle_config.natural_heal_skill_interval) { + sd->inchealhptick -= battle_config.natural_heal_skill_interval; + if(sd->status.hp + bonus <= sd->status.max_hp) + sd->status.hp += bonus; + else { + bonus = sd->status.max_hp - sd->status.hp; + sd->status.hp = sd->status.max_hp; + sd->hp_sub = sd->inchealhptick = 0; + } + clif_heal(sd->fd,SP_HP,bonus); + } + } + } + else sd->inchealhptick = 0; + + return 0; + + if(sd->sc_data[SC_APPLEIDUN].timer!=-1) { // Apple of Idun + if(sd->inchealhptick >= 6000 && sd->status.hp < sd->status.max_hp) { + bonus = skill*20; + while(sd->inchealhptick >= 6000) { + sd->inchealhptick -= 6000; + if(sd->status.hp + bonus <= sd->status.max_hp) + sd->status.hp += bonus; + else { + bonus = sd->status.max_hp - sd->status.hp; + sd->status.hp = sd->status.max_hp; + sd->hp_sub = sd->inchealhptick = 0; + } + clif_heal(sd->fd,SP_HP,bonus); + } + } + } + else sd->inchealhptick = 0; + + return 0; +} + +static int pc_natural_heal_sp(struct map_session_data *sd) +{ + int bsp; + int inc_num,bonus; + + nullpo_retr(0, sd); + + if (sd->sc_data[SC_TRICKDEAD].timer != -1) // Modified by RoVeRT + return 0; + + if(pc_checkoversp(sd)) { + sd->sp_sub = sd->inchealsptick = 0; + return 0; + } + + bsp=sd->status.sp; + + inc_num = pc_spheal(sd); + if(sd->sc_data[SC_EXPLOSIONSPIRITS].timer == -1) + sd->sp_sub += inc_num; + if(sd->walktimer == -1) + sd->inchealsptick += natural_heal_diff_tick; + else sd->inchealsptick = 0; + + if(sd->sp_sub >= battle_config.natural_healsp_interval){ + bonus = sd->nhealsp;; + while(sd->sp_sub >= battle_config.natural_healsp_interval){ + sd->sp_sub -= battle_config.natural_healsp_interval; + if(sd->status.sp + bonus <= sd->status.max_sp) + sd->status.sp += bonus; + else { + sd->status.sp = sd->status.max_sp; + sd->sp_sub = sd->inchealsptick = 0; + } + } + } + + if(bsp != sd->status.sp) + clif_updatestatus(sd,SP_SP); + + if(sd->nshealsp > 0) { + if(sd->inchealsptick >= battle_config.natural_heal_skill_interval && sd->status.sp < sd->status.max_sp) { + struct pc_base_job s_class = pc_calc_base_job(sd->status.class); + if(sd->doridori_counter && s_class.job == 23) + bonus = sd->nshealsp*2; + else + bonus = sd->nshealsp; + sd->doridori_counter = 0; + while(sd->inchealsptick >= battle_config.natural_heal_skill_interval) { + sd->inchealsptick -= battle_config.natural_heal_skill_interval; + if(sd->status.sp + bonus <= sd->status.max_sp) + sd->status.sp += bonus; + else { + bonus = sd->status.max_sp - sd->status.sp; + sd->status.sp = sd->status.max_sp; + sd->sp_sub = sd->inchealsptick = 0; + } + clif_heal(sd->fd,SP_SP,bonus); + } + } + } + else sd->inchealsptick = 0; + + return 0; +} + +static int pc_spirit_heal_hp(struct map_session_data *sd,int level) +{ + int bonus_hp,interval = battle_config.natural_heal_skill_interval; + + nullpo_retr(0, sd); + + if(pc_checkoverhp(sd)) { + sd->inchealspirithptick = 0; + return 0; + } + + sd->inchealspirithptick += natural_heal_diff_tick; + + if(sd->weight*100/sd->max_weight >= battle_config.natural_heal_weight_rate) + interval += interval; + + if(sd->inchealspirithptick >= interval) { + bonus_hp = sd->nsshealhp; + while(sd->inchealspirithptick >= interval) { + if(pc_issit(sd)) { + sd->inchealspirithptick -= interval; + if(sd->status.hp < sd->status.max_hp) { + if(sd->status.hp + bonus_hp <= sd->status.max_hp) + sd->status.hp += bonus_hp; + else { + bonus_hp = sd->status.max_hp - sd->status.hp; + sd->status.hp = sd->status.max_hp; + } + clif_heal(sd->fd,SP_HP,bonus_hp); + sd->inchealspirithptick = 0; + } + }else{ + sd->inchealspirithptick -= natural_heal_diff_tick; + break; + } + } + } + + return 0; +} +static int pc_spirit_heal_sp(struct map_session_data *sd,int level) +{ + int bonus_sp,interval = battle_config.natural_heal_skill_interval; + + nullpo_retr(0, sd); + + if(pc_checkoversp(sd)) { + sd->inchealspiritsptick = 0; + return 0; + } + + sd->inchealspiritsptick += natural_heal_diff_tick; + + if(sd->weight*100/sd->max_weight >= battle_config.natural_heal_weight_rate) + interval += interval; + + if(sd->inchealspiritsptick >= interval) { + bonus_sp = sd->nsshealsp; + while(sd->inchealspiritsptick >= interval) { + if(pc_issit(sd)) { + sd->inchealspiritsptick -= interval; + if(sd->status.sp < sd->status.max_sp) { + if(sd->status.sp + bonus_sp <= sd->status.max_sp) + sd->status.sp += bonus_sp; + else { + bonus_sp = sd->status.max_sp - sd->status.sp; + sd->status.sp = sd->status.max_sp; + } + clif_heal(sd->fd,SP_SP,bonus_sp); + sd->inchealspiritsptick = 0; + } + }else{ + sd->inchealspiritsptick -= natural_heal_diff_tick; + break; + } + } + } + + return 0; +} + +/*========================================== + * HP/SP 自然回復 各クライアント + *------------------------------------------ + */ + +static int pc_natural_heal_sub(struct map_session_data *sd,va_list ap) { + int skill; + + nullpo_retr(0, sd); + +// -- moonsoul (if conditions below altered to disallow natural healing if under berserk status) + if ((battle_config.natural_heal_weight_rate > 100 || sd->weight*100/sd->max_weight < battle_config.natural_heal_weight_rate) && + !pc_isdead(sd) && + !pc_ishiding(sd) && + sd->sc_data[SC_POISON].timer == -1 + ) { + pc_natural_heal_hp(sd); + if( sd->sc_data && sd->sc_data[SC_EXTREMITYFIST].timer == -1 && //阿修羅状態ではSPが回復しない + sd->sc_data[SC_DANCING].timer == -1 && //ダンス状態ではSPが回復しない + sd->sc_data[SC_BERSERK].timer == -1 //バーサーク状態ではSPが回復しない + ) + pc_natural_heal_sp(sd); + } else { + sd->hp_sub = sd->inchealhptick = 0; + sd->sp_sub = sd->inchealsptick = 0; + } + if((skill = pc_checkskill(sd,MO_SPIRITSRECOVERY)) > 0 && !pc_ishiding(sd) && sd->sc_data[SC_POISON].timer == -1 && sd->sc_data[SC_BERSERK].timer == -1){ + pc_spirit_heal_hp(sd,skill); + pc_spirit_heal_sp(sd,skill); + } + else { + sd->inchealspirithptick = 0; + sd->inchealspiritsptick = 0; + } + return 0; +} + +/*========================================== + * HP/SP自然回復 (interval timer関数) + *------------------------------------------ + */ +int pc_natural_heal(int tid,unsigned int tick,int id,int data) +{ + natural_heal_tick = tick; + natural_heal_diff_tick = DIFF_TICK(natural_heal_tick,natural_heal_prev_tick); + clif_foreachclient(pc_natural_heal_sub); + + natural_heal_prev_tick = tick; + return 0; +} + +/*========================================== + * セーブポイントの保存 + *------------------------------------------ + */ +int pc_setsavepoint(struct map_session_data *sd,char *mapname,int x,int y) +{ + nullpo_retr(0, sd); + + strncpy(sd->status.save_point.map,mapname,24); + sd->status.save_point.x = x; + sd->status.save_point.y = y; + + return 0; +} + +/*========================================== + * 自動セーブ 各クライアント + *------------------------------------------ + */ +static int last_save_fd,save_flag; +static int pc_autosave_sub(struct map_session_data *sd,va_list ap) +{ + nullpo_retr(0, sd); + + if(save_flag==0 && sd->fd>last_save_fd){ + struct guild_castle *gc=NULL; + int i; +// if(battle_config.save_log) +// printf("autosave %d\n",sd->fd); + // pet + if(sd->status.pet_id > 0 && sd->pd) + intif_save_petdata(sd->status.account_id,&sd->pet); + pc_makesavestatus(sd); + chrif_save(sd); + storage_storage_save(sd); + + for(i=0;i<MAX_GUILDCASTLE;i++){ + gc=guild_castle_search(i); + if(!gc) continue; + if(gc->visibleG0==1) guild_castledatasave(gc->castle_id,18,gc->Ghp0); + if(gc->visibleG1==1) guild_castledatasave(gc->castle_id,19,gc->Ghp1); + if(gc->visibleG2==1) guild_castledatasave(gc->castle_id,20,gc->Ghp2); + if(gc->visibleG3==1) guild_castledatasave(gc->castle_id,21,gc->Ghp3); + if(gc->visibleG4==1) guild_castledatasave(gc->castle_id,22,gc->Ghp4); + if(gc->visibleG5==1) guild_castledatasave(gc->castle_id,23,gc->Ghp5); + if(gc->visibleG6==1) guild_castledatasave(gc->castle_id,24,gc->Ghp6); + if(gc->visibleG7==1) guild_castledatasave(gc->castle_id,25,gc->Ghp7); + } + + save_flag=1; + last_save_fd = sd->fd; + } + + return 0; +} + +/*========================================== + * 自動セーブ (timer関数) + *------------------------------------------ + */ +int pc_autosave(int tid,unsigned int tick,int id,int data) +{ + int interval; + + save_flag=0; + clif_foreachclient(pc_autosave_sub); + if(save_flag==0) + last_save_fd=0; + + interval = autosave_interval/(clif_countusers()+1); + if(interval <= 0) + interval = 1; + add_timer(gettick()+interval,pc_autosave,0,0); + + return 0; +} + +int pc_read_gm_account(int fd) +{ +#ifdef TXT_ONLY + int i = 0; +#endif + if (gm_account != NULL) + free(gm_account); + GM_num = 0; +#ifdef TXT_ONLY + gm_account = calloc(sizeof(struct gm_account) * ((RFIFOW(fd,2) - 4) / 5), 1); + for (i = 4; i < RFIFOW(fd,2); i = i + 5) { + gm_account[GM_num].account_id = RFIFOL(fd,i); + gm_account[GM_num].level = (int)RFIFOB(fd,i+4); + //printf("GM account: %d -> level %d\n", gm_account[GM_num].account_id, gm_account[GM_num].level); + GM_num++; + } +#else + sprintf (tmp_lsql, "SELECT `%s`,`%s` FROM `%s` WHERE `%s`>='%d'",login_db_account_id,login_db_level,login_db,login_db_level,lowest_gm_level); + if(mysql_query(&lmysql_handle, tmp_lsql) ) { + printf("DB server Error (select %s to Memory)- %s\n",login_db,mysql_error(&lmysql_handle) ); + } + lsql_res = mysql_store_result(&lmysql_handle); + if (lsql_res) { + gm_account = calloc(sizeof(struct gm_account) * mysql_num_rows(lsql_res), 1); + while ((lsql_row = mysql_fetch_row(lsql_res))) { + gm_account[GM_num].account_id = atoi(lsql_row[0]); + gm_account[GM_num].level = atoi(lsql_row[1]); + printf("GM account: %d -> level %d\n", gm_account[GM_num].account_id, gm_account[GM_num].level); + GM_num++; + } + } + + mysql_free_result(lsql_res); +#endif /* TXT_ONLY */ + return GM_num; +} + +/*========================================== + * timer to do the day + *------------------------------------------ + */ +int map_day_timer(int tid, unsigned int tick, int id, int data) { // by [yor] + struct map_session_data *pl_sd = NULL; + int i; + char tmpstr[1024]; + + if (battle_config.day_duration > 0) { // if we want a day + if (night_flag != 0) { + strcpy(tmpstr, msg_txt(502)); // The day has arrived! + night_flag = 0; // 0=day, 1=night [Yor] + for(i = 0; i < fd_max; i++) { + if (session[i] && (pl_sd = session[i]->session_data) && pl_sd->state.auth) { + pl_sd->opt2 &= ~STATE_BLIND; + clif_changeoption(&pl_sd->bl); + clif_wis_message(pl_sd->fd, wisp_server_name, tmpstr, strlen(tmpstr)+1); + } + } + } + } + + return 0; +} + +/*========================================== + * timer to do the night + *------------------------------------------ + */ +int map_night_timer(int tid, unsigned int tick, int id, int data) { // by [yor] + struct map_session_data *pl_sd = NULL; + int i; + char tmpstr[1024]; + + if (battle_config.night_duration > 0) { // if we want a night + if (night_flag == 0) { + strcpy(tmpstr, msg_txt(503)); // The night has fallen... + night_flag = 1; // 0=day, 1=night [Yor] + for(i = 0; i < fd_max; i++) { + if (session[i] && (pl_sd = session[i]->session_data) && pl_sd->state.auth) { + pl_sd->opt2 |= STATE_BLIND; + clif_changeoption(&pl_sd->bl); + clif_wis_message(pl_sd->fd, wisp_server_name, tmpstr, strlen(tmpstr)+1); + } + } + } + } + + return 0; +} + +void pc_setstand(struct map_session_data *sd){ + nullpo_retv(sd); + + if(sd->sc_data && sd->sc_data[SC_TENSIONRELAX].timer!=-1) + skill_status_change_end(&sd->bl,SC_TENSIONRELAX,-1); + + sd->state.dead_sit = 0; +} + +// +// 初期化物 +// +/*========================================== + * 設定ファイル読み込む + * exp.txt 必要経験値 + * job_db1.txt 重量,hp,sp,攻撃速度 + * job_db2.txt job能力値ボーナス + * skill_tree.txt 各職毎のスキルツリー + * attr_fix.txt 属性修正テーブル + * size_fix.txt サイズ補正テーブル + * refine_db.txt 精錬データテーブル + *------------------------------------------ + */ +int pc_readdb(void) +{ + int i,j,k; + FILE *fp; + char line[1024],*p; + + // 必要経験値読み込み + + fp=fopen("db/exp.txt","r"); + if(fp==NULL){ + printf("can't read db/exp.txt\n"); + return 1; + } + i=0; + while(fgets(line, sizeof(line)-1, fp)){ + int bn,b1,b2,b3,b4,b5,b6,jn,j1,j2,j3,j4,j5,j6; + if(line[0]=='/' && line[1]=='/') + continue; + if(sscanf(line,"%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d",&bn,&b1,&b2,&b3,&b4,&b5,&b6,&jn,&j1,&j2,&j3,&j4,&j5,&j6)!=14) + continue; + exp_table[0][i]=bn; + exp_table[1][i]=b1; + exp_table[2][i]=b2; + exp_table[3][i]=b3; + exp_table[4][i]=b4; + exp_table[5][i]=b5; + exp_table[6][i]=b6; + exp_table[7][i]=jn; + exp_table[8][i]=j1; + exp_table[9][i]=j2; + exp_table[10][i]=j3; + exp_table[11][i]=j4; + exp_table[12][i]=j5; + exp_table[13][i]=j6; + i++; + if(i >= battle_config.maximum_level) + break; + } + fclose(fp); + printf("read db/exp.txt done\n"); + + // JOB補正数値1 + fp=fopen("db/job_db1.txt","r"); + if(fp==NULL){ + printf("can't read db/job_db1.txt\n"); + return 1; + } + i=0; + while(fgets(line, sizeof(line)-1, fp)){ + char *split[50]; + if(line[0]=='/' && line[1]=='/') + continue; + for(j=0,p=line;j<21 && p;j++){ + split[j]=p; + p=strchr(p,','); + if(p) *p++=0; + } + if(j<21) + continue; + max_weight_base[i]=atoi(split[0]); + hp_coefficient[i]=atoi(split[1]); + hp_coefficient2[i]=atoi(split[2]); + sp_coefficient[i]=atoi(split[3]); + for(j=0;j<17;j++) + aspd_base[i][j]=atoi(split[j+4]); + i++; +// -- moonsoul (below two lines added to accommodate high numbered new class ids) + if(i==24) + i=4001; + if(i==MAX_PC_CLASS) + break; + } + fclose(fp); + printf("read db/job_db1.txt done\n"); + + // JOBボーナス + fp=fopen("db/job_db2.txt","r"); + if(fp==NULL){ + printf("can't read db/job_db2.txt\n"); + return 1; + } + i=0; + while(fgets(line, sizeof(line)-1, fp)){ + if(line[0]=='/' && line[1]=='/') + continue; + for(j=0,p=line;j<MAX_LEVEL && p;j++){ + if(sscanf(p,"%d",&k)==0) + break; + job_bonus[0][i][j]=k; + job_bonus[2][i][j]=k; //養子職のボーナスは分からないので仮 + p=strchr(p,','); + if(p) p++; + } + i++; +// -- moonsoul (below two lines added to accommodate high numbered new class ids) + if(i==24) + i=4001; + if(i==MAX_PC_CLASS) + break; + } + fclose(fp); + printf("read db/job_db2.txt done\n"); + + // JOBボーナス2 転生職用 + fp=fopen("db/job_db2-2.txt","r"); + if(fp==NULL){ + printf("can't read db/job_db2-2.txt\n"); + return 1; + } + i=0; + while(fgets(line, sizeof(line)-1, fp)){ + if(line[0]=='/' && line[1]=='/') + continue; + for(j=0,p=line;j<MAX_LEVEL && p;j++){ + if(sscanf(p,"%d",&k)==0) + break; + job_bonus[1][i][j]=k; + p=strchr(p,','); + if(p) p++; + } + i++; + if(i==MAX_PC_CLASS) + break; + } + fclose(fp); + printf("read db/job_db2-2.txt done\n"); + + // スキルツリー + memset(skill_tree,0,sizeof(skill_tree)); + fp=fopen("db/skill_tree.txt","r"); + if(fp==NULL){ + printf("can't read db/skill_tree.txt\n"); + return 1; + } + while(fgets(line, sizeof(line)-1, fp)){ + char *split[50]; + if(line[0]=='/' && line[1]=='/') + continue; + for(j=0,p=line;j<13 && p;j++){ + split[j]=p; + p=strchr(p,','); + if(p) *p++=0; + } + if(j<13) + continue; + i=atoi(split[0]); + for(j=0;skill_tree[0][i][j].id;j++); + skill_tree[0][i][j].id=atoi(split[1]); + skill_tree[0][i][j].max=atoi(split[2]); + skill_tree[2][i][j].id=atoi(split[1]); //養子職は良く分からないので暫定 + skill_tree[2][i][j].max=atoi(split[2]); //養子職は良く分からないので暫定 + for(k=0;k<5;k++){ + skill_tree[0][i][j].need[k].id=atoi(split[k*2+3]); + skill_tree[0][i][j].need[k].lv=atoi(split[k*2+4]); + skill_tree[2][i][j].need[k].id=atoi(split[k*2+3]); //養子職は良く分からないので暫定 + skill_tree[2][i][j].need[k].lv=atoi(split[k*2+4]); //養子職は良く分からないので暫定 + } + } + fclose(fp); + printf("read db/skill_tree.txt done\n"); + + // 属性修正テーブル + for(i=0;i<4;i++) + for(j=0;j<10;j++) + for(k=0;k<10;k++) + attr_fix_table[i][j][k]=100; + fp=fopen("db/attr_fix.txt","r"); + if(fp==NULL){ + printf("can't read db/attr_fix.txt\n"); + return 1; + } + while(fgets(line, sizeof(line)-1, fp)){ + char *split[10]; + int lv,n; + if(line[0]=='/' && line[1]=='/') + continue; + for(j=0,p=line;j<3 && p;j++){ + split[j]=p; + p=strchr(p,','); + if(p) *p++=0; + } + lv=atoi(split[0]); + n=atoi(split[1]); +// printf("%d %d\n",lv,n); + + for(i=0;i<n;){ + if( !fgets(line, sizeof(line)-1, fp) ) + break; + if(line[0]=='/' && line[1]=='/') + continue; + + for(j=0,p=line;j<n && p;j++){ + while(*p==32 && *p>0) + p++; + attr_fix_table[lv-1][i][j]=atoi(p); + if(battle_config.attr_recover == 0 && attr_fix_table[lv-1][i][j] < 0) + attr_fix_table[lv-1][i][j] = 0; + p=strchr(p,','); + if(p) *p++=0; + } + + i++; + } + } + fclose(fp); + printf("read db/attr_fix.txt done\n"); + + // サイズ補正テーブル + for(i=0;i<3;i++) + for(j=0;j<20;j++) + atkmods[i][j]=100; + fp=fopen("db/size_fix.txt","r"); + if(fp==NULL){ + printf("can't read db/size_fix.txt\n"); + return 1; + } + i=0; + while(fgets(line, sizeof(line)-1, fp)){ + char *split[20]; + if(line[0]=='/' && line[1]=='/') + continue; + if(atoi(line)<=0) + continue; + memset(split,0,sizeof(split)); + for(j=0,p=line;j<20 && p;j++){ + split[j]=p; + p=strchr(p,','); + if(p) *p++=0; + } + for(j=0;j<20 && split[j];j++) + atkmods[i][j]=atoi(split[j]); + i++; + } + fclose(fp); + printf("read db/size_fix.txt done\n"); + + // 精錬データテーブル + for(i=0;i<5;i++){ + for(j=0;j<10;j++) + percentrefinery[i][j]=100; + refinebonus[i][0]=0; + refinebonus[i][1]=0; + refinebonus[i][2]=10; + } + fp=fopen("db/refine_db.txt","r"); + if(fp==NULL){ + printf("can't read db/refine_db.txt\n"); + return 1; + } + i=0; + while(fgets(line, sizeof(line)-1, fp)){ + char *split[16]; + if(line[0]=='/' && line[1]=='/') + continue; + if(atoi(line)<=0) + continue; + memset(split,0,sizeof(split)); + for(j=0,p=line;j<16 && p;j++){ + split[j]=p; + p=strchr(p,','); + if(p) *p++=0; + } + refinebonus[i][0]=atoi(split[0]); // 精錬ボーナス + refinebonus[i][1]=atoi(split[1]); // 過剰精錬ボーナス + refinebonus[i][2]=atoi(split[2]); // 安全精錬限界 + for(j=0;j<10 && split[j];j++) + percentrefinery[i][j]=atoi(split[j+3]); + i++; + } + fclose(fp); //Lupus. close this file!!! + printf("read db/refine_db.txt done\n"); + + return 0; +} + +static int pc_calc_sigma(void) +{ + int i,j,k; + + for(i=0;i<MAX_PC_CLASS;i++) { + memset(hp_sigma_val[i],0,sizeof(hp_sigma_val[i])); + for(k=0,j=2;j<=MAX_LEVEL;j++) { + k += hp_coefficient[i]*j + 50; + k -= k%100; + hp_sigma_val[i][j-1] = k; + } + } + return 0; +} + +static void pc_statpointdb(void) +{ + char * buf_stat; + int i=0,j=0,k=0,l=0, end = 0; + + FILE *stp; + + stp=fopen("db/statpoint.txt","r"); + + if(stp==NULL){ + printf("can't read db/statpoint.txt\n"); + return; + } + + fseek(stp, 0, SEEK_END); + end = ftell(stp); + rewind(stp); + + buf_stat = (char *) malloc (end + 1); + l = fread(buf_stat,1,end,stp); + fclose(stp); + printf("read db/statpoint.txt done (size=%d)\n",l); + + for(i=0;i<255;i++) { + j=0; + while (*(buf_stat+k)!='\n') { + statp[i][j]=*(buf_stat+k); + j++;k++; + } + statp[i][j+1]='\0'; + k++; + } + + free(buf_stat); +} + +/*========================================== + * pc関 係初期化 + *------------------------------------------ + */ +int do_init_pc(void) { + pc_readdb(); + pc_statpointdb(); + pc_calc_sigma(); + +// gm_account_db = numdb_init(); + + add_timer_func_list(pc_walk, "pc_walk"); + add_timer_func_list(pc_attack_timer, "pc_attack_timer"); + add_timer_func_list(pc_natural_heal, "pc_natural_heal"); + add_timer_func_list(pc_invincible_timer, "pc_invincible_timer"); + add_timer_func_list(pc_eventtimer, "pc_eventtimer"); + add_timer_func_list(pc_calc_pvprank_timer, "pc_calc_pvprank_timer"); + add_timer_func_list(pc_autosave, "pc_autosave"); + add_timer_func_list(pc_spiritball_timer, "pc_spiritball_timer"); + add_timer_interval((natural_heal_prev_tick = gettick() + NATURAL_HEAL_INTERVAL), pc_natural_heal, 0, 0, NATURAL_HEAL_INTERVAL); + add_timer(gettick() + autosave_interval, pc_autosave, 0, 0); + +#ifndef TXT_ONLY + pc_read_gm_account(0); +#endif /* not TXT_ONLY */ + + // add night/day timer (by [yor]) + add_timer_func_list(map_day_timer, "map_day_timer"); // by [yor] + add_timer_func_list(map_night_timer, "map_night_timer"); // by [yor] + { + int day_duration = battle_config.day_duration; + int night_duration = battle_config.night_duration; + if (day_duration < 60000) + day_duration = 60000; + if (night_duration < 60000) + night_duration = 60000; + if (battle_config.night_at_start == 0) { + night_flag = 0; // 0=day, 1=night [Yor] + day_timer_tid = add_timer_interval(gettick() + day_duration + night_duration, map_day_timer, 0, 0, day_duration + night_duration); + night_timer_tid = add_timer_interval(gettick() + day_duration, map_night_timer, 0, 0, day_duration + night_duration); + } else { + night_flag = 1; // 0=day, 1=night [Yor] + day_timer_tid = add_timer_interval(gettick() + night_duration, map_day_timer, 0, 0, day_duration + night_duration); + night_timer_tid = add_timer_interval(gettick() + day_duration + night_duration, map_night_timer, 0, 0, day_duration + night_duration); + } + } + + return 0; +} diff --git a/src/map/pc.h b/src/map/pc.h new file mode 100644 index 0000000..1919007 --- /dev/null +++ b/src/map/pc.h @@ -0,0 +1,186 @@ +// $Id: pc.h,v 1.4 2004/09/25 05:32:18 MouseJstr Exp $ + +#ifndef _PC_H_ +#define _PC_H_ + +#include "map.h" + +#define OPTION_MASK 0xd7b8 +#define CART_MASK 0x788 + +#define pc_setdead(sd) ((sd)->state.dead_sit = 1) +#define pc_setsit(sd) ((sd)->state.dead_sit = 2) +//#define pc_setstand(sd) ((sd)->state.dead_sit = 0) +#define pc_isdead(sd) ((sd)->state.dead_sit == 1) +#define pc_issit(sd) ((sd)->state.dead_sit == 2) +#define pc_setdir(sd,b,h) ((sd)->dir = (b) ,(sd)->head_dir = (h) ) +#define pc_setchatid(sd,n) ((sd)->chatID = n) +#define pc_ishiding(sd) ((sd)->status.option&0x4006) +#define pc_iscarton(sd) ((sd)->status.option&CART_MASK) +#define pc_isfalcon(sd) ((sd)->status.option&0x0010) +#define pc_isriding(sd) ((sd)->status.option&0x0020) +#define pc_isinvisible(sd) ((sd)->status.option&0x0040) +#define pc_is50overweight(sd) (sd->weight*2 >= sd->max_weight) +#define pc_is90overweight(sd) (sd->weight*10 >= sd->max_weight*9) + +int pc_isGM(struct map_session_data *sd); +int pc_iskiller(struct map_session_data *src, struct map_session_data *target); // [MouseJstr] +int pc_getrefinebonus(int lv,int type); + +int pc_counttargeted(struct map_session_data *sd,struct block_list *src,int target_lv); +int pc_setrestartvalue(struct map_session_data *sd,int type); +int pc_makesavestatus(struct map_session_data *); +int pc_setnewpc(struct map_session_data*,int,int,int,int,int,int); +int pc_authok(int, int, time_t, struct mmo_charstatus *); +int pc_authfail(int); + +int pc_isequip(struct map_session_data *sd,int n); +int pc_equippoint(struct map_session_data *sd,int n); + +int pc_breakweapon(struct map_session_data *sd); // weapon breaking [Valaris] +int pc_breakarmor(struct map_session_data *sd); // armor breaking [Valaris] + +int pc_checkskill(struct map_session_data *sd,int skill_id); +int pc_checkallowskill(struct map_session_data *sd); +int pc_checkequip(struct map_session_data *sd,int pos); + +int pc_checkoverhp(struct map_session_data*); +int pc_checkoversp(struct map_session_data*); + +int pc_can_reach(struct map_session_data*,int,int); +int pc_walktoxy(struct map_session_data*,int,int); +int pc_stop_walking(struct map_session_data*,int); +int pc_movepos(struct map_session_data*,int,int); +int pc_setpos(struct map_session_data*,char*,int,int,int); +int pc_setsavepoint(struct map_session_data*,char*,int,int); +int pc_randomwarp(struct map_session_data *sd,int type); +int pc_memo(struct map_session_data *sd,int i); + +int pc_checkadditem(struct map_session_data*,int,int); +int pc_inventoryblank(struct map_session_data*); +int pc_search_inventory(struct map_session_data *sd,int item_id); +int pc_payzeny(struct map_session_data*,int); +int pc_additem(struct map_session_data*,struct item*,int); +int pc_getzeny(struct map_session_data*,int); +int pc_delitem(struct map_session_data*,int,int,int); +int pc_checkitem(struct map_session_data*); + +int pc_cart_additem(struct map_session_data *sd,struct item *item_data,int amount); +int pc_cart_delitem(struct map_session_data *sd,int n,int amount,int type); +int pc_putitemtocart(struct map_session_data *sd,int idx,int amount); +int pc_getitemfromcart(struct map_session_data *sd,int idx,int amount); +int pc_cartitem_amount(struct map_session_data *sd,int idx,int amount); + +int pc_takeitem(struct map_session_data*,struct flooritem_data*); +int pc_dropitem(struct map_session_data*,int,int); + +int pc_checkweighticon(struct map_session_data *sd); + +int pc_calcstatus(struct map_session_data*,int); +int pc_bonus(struct map_session_data*,int,int); +int pc_bonus2(struct map_session_data *sd,int,int,int); +int pc_bonus3(struct map_session_data *sd,int,int,int,int); +int pc_skill(struct map_session_data*,int,int,int); + +int pc_insert_card(struct map_session_data *sd,int idx_card,int idx_equip); + +int pc_item_identify(struct map_session_data *sd,int idx); +int pc_steal_item(struct map_session_data *sd,struct block_list *bl); +int pc_steal_coin(struct map_session_data *sd,struct block_list *bl); + +int pc_modifybuyvalue(struct map_session_data*,int); +int pc_modifysellvalue(struct map_session_data*,int); + +int pc_attack(struct map_session_data*,int,int); +int pc_stopattack(struct map_session_data*); + +int pc_follow(struct map_session_data*, int); // [MouseJstr] + +int pc_checkbaselevelup(struct map_session_data *sd); +int pc_checkjoblevelup(struct map_session_data *sd); +int pc_gainexp(struct map_session_data*,int,int); +int pc_nextbaseexp(struct map_session_data *); +int pc_nextbaseafter(struct map_session_data *); // [Valaris] +int pc_nextjobexp(struct map_session_data *); +int pc_nextjobafter(struct map_session_data *); // [Valaris] +int pc_need_status_point(struct map_session_data *,int); +int pc_statusup(struct map_session_data*,int); +int pc_statusup2(struct map_session_data*,int,int); +int pc_skillup(struct map_session_data*,int); +int pc_allskillup(struct map_session_data*); +int pc_resetlvl(struct map_session_data*,int type); +int pc_resetstate(struct map_session_data*); +int pc_resetskill(struct map_session_data*); +int pc_equipitem(struct map_session_data*,int,int); +int pc_unequipitem(struct map_session_data*,int,int); +int pc_checkitem(struct map_session_data*); +int pc_useitem(struct map_session_data*,int); + +int pc_damage(struct block_list *,struct map_session_data*,int); +int pc_heal(struct map_session_data *,int,int); +int pc_itemheal(struct map_session_data *sd,int hp,int sp); +int pc_percentheal(struct map_session_data *sd,int,int); +int pc_jobchange(struct map_session_data *,int, int); +int pc_setoption(struct map_session_data *,int); +int pc_setcart(struct map_session_data *sd,int type); +int pc_setfalcon(struct map_session_data *sd); +int pc_setriding(struct map_session_data *sd); +int pc_changelook(struct map_session_data *,int,int); +int pc_equiplookall(struct map_session_data *sd); + +int pc_readparam(struct map_session_data*,int); +int pc_setparam(struct map_session_data*,int,int); +int pc_readreg(struct map_session_data*,int); +int pc_setreg(struct map_session_data*,int,int); +char *pc_readregstr(struct map_session_data *sd,int reg); +int pc_setregstr(struct map_session_data *sd,int reg,char *str); +int pc_readglobalreg(struct map_session_data*,char*); +int pc_setglobalreg(struct map_session_data*,char*,int); +int pc_readaccountreg(struct map_session_data*,char*); +int pc_setaccountreg(struct map_session_data*,char*,int); +int pc_readaccountreg2(struct map_session_data*,char*); +int pc_setaccountreg2(struct map_session_data*,char*,int); +int pc_percentrefinery(struct map_session_data *sd,struct item *item); + +int pc_addeventtimer(struct map_session_data *sd,int tick,const char *name); +int pc_deleventtimer(struct map_session_data *sd,const char *name); +int pc_cleareventtimer(struct map_session_data *sd); +int pc_addeventtimercount(struct map_session_data *sd,const char *name,int tick); + +int pc_calc_pvprank(struct map_session_data *sd); +int pc_calc_pvprank_timer(int tid,unsigned int tick,int id,int data); + +int pc_ismarried(struct map_session_data *sd); +int pc_marriage(struct map_session_data *sd,struct map_session_data *dstsd); +int pc_divorce(struct map_session_data *sd); +struct map_session_data *pc_get_partner(struct map_session_data *sd); +int pc_set_gm_level(int account_id, int level); +void pc_setstand(struct map_session_data *sd); + + +struct pc_base_job{ + int job; //職業、ただし転生職や養子職の場合は元の職業を返す(廃プリ→プリ) + int type; //ノビ 0, 一次職 1, 二次職 2, スパノビ 3 + int upper; //通常 0, 転生 1, 養子 2 +}; + +struct pc_base_job pc_calc_base_job(int b_class);//転生や養子職の元の職業を返す + +int pc_read_gm_account(int fd); +int pc_setinvincibletimer(struct map_session_data *sd,int); +int pc_delinvincibletimer(struct map_session_data *sd); +int pc_addspiritball(struct map_session_data *sd,int,int); +int pc_delspiritball(struct map_session_data *sd,int,int); + +int do_init_pc(void); + +enum {ADDITEM_EXIST,ADDITEM_NEW,ADDITEM_OVERAMOUNT}; + +// timer for night.day +int day_timer_tid; +int night_timer_tid; +int map_day_timer(int,unsigned int,int,int); // by [yor] +int map_night_timer(int,unsigned int,int,int); // by [yor] + +#endif + diff --git a/src/map/pet.c b/src/map/pet.c new file mode 100644 index 0000000..6026b1e --- /dev/null +++ b/src/map/pet.c @@ -0,0 +1,1651 @@ +// $Id: pet.c,v 1.4 2004/09/25 05:32:18 MouseJstr Exp $ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "db.h" +#include "timer.h" +#include "socket.h" +#include "nullpo.h" +#include "malloc.h" +#include "pc.h" +#include "map.h" +#include "intif.h" +#include "clif.h" +#include "chrif.h" +#include "pet.h" +#include "itemdb.h" +#include "battle.h" +#include "mob.h" +#include "npc.h" +#include "script.h" +#include "skill.h" + +#ifdef MEMWATCH +#include "memwatch.h" +#endif + +#define MIN_PETTHINKTIME 100 + +struct pet_db pet_db[MAX_PET_DB]; + +static int dirx[8]={0,-1,-1,-1,0,1,1,1}; +static int diry[8]={1,1,0,-1,-1,-1,0,1}; + +static int pet_timer(int tid,unsigned int tick,int id,int data); +static int pet_walktoxy_sub(struct pet_data *pd); + +static int distance(int x0,int y0,int x1,int y1) +{ + int dx,dy; + + dx=abs(x0-x1); + dy=abs(y0-y1); + return dx>dy ? dx : dy; +} + +static int calc_next_walk_step(struct pet_data *pd) +{ + nullpo_retr(0, pd); + + if(pd->walkpath.path_pos>=pd->walkpath.path_len) + return -1; + if(pd->walkpath.path[pd->walkpath.path_pos]&1) + return pd->speed*14/10; + return pd->speed; +} + +static int pet_performance_val(struct map_session_data *sd) +{ + nullpo_retr(0, sd); + + if(sd->pet.intimate > 900) + return (sd->petDB->s_perfor > 0)? 4:3; + else if(sd->pet.intimate > 750) + return 2; + else + return 1; +} + +int pet_hungry_val(struct map_session_data *sd) +{ + nullpo_retr(0, sd); + + if(sd->pet.hungry > 90) + return 4; + else if(sd->pet.hungry > 75) + return 3; + else if(sd->pet.hungry > 25) + return 2; + else if(sd->pet.hungry > 10) + return 1; + else + return 0; +} + +static int pet_can_reach(struct pet_data *pd,int x,int y) +{ + struct walkpath_data wpd; + + nullpo_retr(0, pd); + + if( pd->bl.x==x && pd->bl.y==y ) // 同じマス + return 1; + + // 障害物判定 + wpd.path_len=0; + wpd.path_pos=0; + wpd.path_half=0; + return (path_search(&wpd,pd->bl.m,pd->bl.x,pd->bl.y,x,y,0)!=-1)?1:0; +} + +static int pet_calc_pos(struct pet_data *pd,int tx,int ty,int dir) +{ + int x,y,dx,dy; + int i,j=0,k; + + nullpo_retr(0, pd); + + pd->to_x = tx; + pd->to_y = ty; + + if(dir >= 0 && dir < 8) { + dx = -dirx[dir]*2; + dy = -diry[dir]*2; + x = tx + dx; + y = ty + dy; + if(!(j=pet_can_reach(pd,x,y))) { + if(dx > 0) x--; + else if(dx < 0) x++; + if(dy > 0) y--; + else if(dy < 0) y++; + if(!(j=pet_can_reach(pd,x,y))) { + for(i=0;i<12;i++) { + k = rand()%8; + dx = -dirx[k]*2; + dy = -diry[k]*2; + x = tx + dx; + y = ty + dy; + if((j=pet_can_reach(pd,x,y))) + break; + else { + if(dx > 0) x--; + else if(dx < 0) x++; + if(dy > 0) y--; + else if(dy < 0) y++; + if((j=pet_can_reach(pd,x,y))) + break; + } + } + if(!j) { + x = tx; + y = ty; + if(!pet_can_reach(pd,x,y)) + return 1; + } + } + } + } + else + return 1; + + pd->to_x = x; + pd->to_y = y; + return 0; +} + +static int pet_attack(struct pet_data *pd,unsigned int tick,int data) +{ + struct mob_data *md; + int mode,race,range; + + nullpo_retr(0, pd); + + pd->state.state=MS_IDLE; + + md=(struct mob_data *)map_id2bl(pd->target_id); + if(md == NULL || md->bl.type != BL_MOB || pd->bl.m != md->bl.m || md->bl.prev == NULL || + distance(pd->bl.x,pd->bl.y,md->bl.x,md->bl.y) > 13) { + pd->target_id=0; + return 0; + } + + mode=mob_db[pd->class].mode; + race=mob_db[pd->class].race; + if(mob_db[pd->class].mexp <= 0 && !(mode&0x20) && (md->option & 0x06 && race != 4 && race != 6) ) { + pd->target_id=0; + return 0; + } + + range = mob_db[pd->class].range + 1; + if(distance(pd->bl.x,pd->bl.y,md->bl.x,md->bl.y) > range) + return 0; + if(battle_config.monster_attack_direction_change) + pd->dir=map_calc_dir(&pd->bl, md->bl.x,md->bl.y ); + + clif_fixpetpos(pd); + + pd->target_lv = battle_weapon_attack(&pd->bl,&md->bl,tick,0); + + pd->attackabletime = tick + battle_get_adelay(&pd->bl); + + pd->timer=add_timer(pd->attackabletime,pet_timer,pd->bl.id,0); + pd->state.state=MS_ATTACK; + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +static int pet_walk(struct pet_data *pd,unsigned int tick,int data) +{ + int moveblock; + int i,ctype; + int x,y,dx,dy; + + nullpo_retr(0, pd); + + pd->state.state=MS_IDLE; + if(pd->walkpath.path_pos >= pd->walkpath.path_len || pd->walkpath.path_pos != data) + return 0; + + pd->walkpath.path_half ^= 1; + if(pd->walkpath.path_half==0){ + pd->walkpath.path_pos++; + if(pd->state.change_walk_target){ + pet_walktoxy_sub(pd); + return 0; + } + } + else { + if(pd->walkpath.path[pd->walkpath.path_pos] >= 8) + return 1; + + x = pd->bl.x; + y = pd->bl.y; +/* ctype = map_getcell(pd->bl.m,x,y); + if(ctype == 1 || ctype == 5) { + pet_stop_walking(pd,1); + return 0; + }*/ + pd->dir=pd->walkpath.path[pd->walkpath.path_pos]; + dx = dirx[pd->dir]; + dy = diry[pd->dir]; + + ctype = map_getcell(pd->bl.m,x+dx,y+dy); + if(ctype == 1 || ctype == 5) { + pet_walktoxy_sub(pd); + return 0; + } + + moveblock = ( x/BLOCK_SIZE != (x+dx)/BLOCK_SIZE || y/BLOCK_SIZE != (y+dy)/BLOCK_SIZE); + + pd->state.state=MS_WALK; + map_foreachinmovearea(clif_petoutsight,pd->bl.m,x-AREA_SIZE,y-AREA_SIZE,x+AREA_SIZE,y+AREA_SIZE,dx,dy,BL_PC,pd); + + x += dx; + y += dy; + + if(moveblock) map_delblock(&pd->bl); + pd->bl.x = x; + pd->bl.y = y; + if(moveblock) map_addblock(&pd->bl); + + map_foreachinmovearea(clif_petinsight,pd->bl.m,x-AREA_SIZE,y-AREA_SIZE,x+AREA_SIZE,y+AREA_SIZE,-dx,-dy,BL_PC,pd); + pd->state.state=MS_IDLE; + } + if((i=calc_next_walk_step(pd))>0){ + i = i>>1; + if(i < 1 && pd->walkpath.path_half == 0) + i = 1; + pd->timer=add_timer(tick+i,pet_timer,pd->bl.id,pd->walkpath.path_pos); + pd->state.state=MS_WALK; + + if(pd->walkpath.path_pos >= pd->walkpath.path_len) + clif_fixpetpos(pd); + } + return 0; +} + +int pet_stopattack(struct pet_data *pd) +{ + nullpo_retr(0, pd); + + pd->target_id=0; + if(pd->state.state == MS_ATTACK) + pet_changestate(pd,MS_IDLE,0); + + return 0; +} + +int pet_target_check(struct map_session_data *sd,struct block_list *bl,int type) +{ + struct pet_data *pd; + struct mob_data *md; + int rate,mode,race; + + nullpo_retr(0, sd); + + pd = sd->pd; + + if(bl && pd && bl->type == BL_MOB && sd->pet.intimate > 900 && sd->pet.hungry > 0 && pd->class != battle_get_class(bl) + && pd->state.state != MS_DELAY) { + mode=mob_db[pd->class].mode; + race=mob_db[pd->class].race; + md=(struct mob_data *)bl; + if(md->bl.type != BL_MOB || pd->bl.m != md->bl.m || md->bl.prev == NULL || + distance(pd->bl.x,pd->bl.y,md->bl.x,md->bl.y) > 13) + return 0; + if(mob_db[pd->class].mexp <= 0 && !(mode&0x20) && (md->option & 0x06 && race!=4 && race!=6) ) + return 0; + if(!type) { + rate = sd->petDB->attack_rate; + rate = rate * (150 - (sd->pet.intimate - 1000))/100; + if(battle_config.pet_support_rate != 100) + rate = rate*battle_config.pet_support_rate/100; + if(sd->petDB->attack_rate > 0 && rate <= 0) + rate = 1; + } + else { + rate = sd->petDB->defence_attack_rate; + rate = rate * (150 - (sd->pet.intimate - 1000))/100; + if(battle_config.pet_support_rate != 100) + rate = rate*battle_config.pet_support_rate/100; + if(sd->petDB->defence_attack_rate > 0 && rate <= 0) + rate = 1; + } + if(rand()%10000 < rate) { + if(pd->target_id == 0 || rand()%10000 < sd->petDB->change_target_rate) + pd->target_id = bl->id; + } + } + return 0; +} + +int pet_changestate(struct pet_data *pd,int state,int type) +{ + unsigned int tick; + int i; + + nullpo_retr(0, pd); + + if(pd->timer != -1) + delete_timer(pd->timer,pet_timer); + pd->timer=-1; + pd->state.state=state; + + switch(state) { + case MS_WALK: + if((i=calc_next_walk_step(pd)) > 0){ + i = i>>2; + pd->timer=add_timer(gettick()+i,pet_timer,pd->bl.id,0); + } else + pd->state.state=MS_IDLE; + break; + case MS_ATTACK: + tick = gettick(); + i=DIFF_TICK(pd->attackabletime,tick); + if(i>0 && i<2000) + pd->timer=add_timer(pd->attackabletime,pet_timer,pd->bl.id,0); + else + pd->timer=add_timer(tick+1,pet_timer,pd->bl.id,0); + break; + case MS_DELAY: + pd->timer=add_timer(gettick()+type,pet_timer,pd->bl.id,0); + break; + } + + return 0; +} + +static int pet_timer(int tid,unsigned int tick,int id,int data) +{ + struct pet_data *pd; + + pd=(struct pet_data*)map_id2bl(id); + if(pd == NULL || pd->bl.type != BL_PET) + return 1; + + if(pd->timer != tid){ + if(battle_config.error_log) + printf("pet_timer %d != %d\n",pd->timer,tid); + return 0; + } + pd->timer=-1; + + if(pd->bl.prev == NULL) + return 1; + + switch(pd->state.state){ + case MS_WALK: + pet_walk(pd,tick,data); + break; + case MS_ATTACK: + pet_attack(pd,tick,data); + break; + case MS_DELAY: + pet_changestate(pd,MS_IDLE,0); + break; + default: + if(battle_config.error_log) + printf("pet_timer : %d ?\n",pd->state.state); + break; + } + + return 0; +} + +static int pet_walktoxy_sub(struct pet_data *pd) +{ + struct walkpath_data wpd; + + nullpo_retr(0, pd); + + if(path_search(&wpd,pd->bl.m,pd->bl.x,pd->bl.y,pd->to_x,pd->to_y,0)) + return 1; + memcpy(&pd->walkpath,&wpd,sizeof(wpd)); + + pd->state.change_walk_target=0; + pet_changestate(pd,MS_WALK,0); + clif_movepet(pd); +// if(battle_config.etc_log) +// printf("walkstart\n"); + + return 0; +} + +int pet_walktoxy(struct pet_data *pd,int x,int y) +{ + struct walkpath_data wpd; + + nullpo_retr(0, pd); + + if(pd->state.state == MS_WALK && path_search(&wpd,pd->bl.m,pd->bl.x,pd->bl.y,x,y,0)) + return 1; + + pd->to_x=x; + pd->to_y=y; + + if(pd->state.state == MS_WALK) { + pd->state.change_walk_target=1; + } else { + return pet_walktoxy_sub(pd); + } + + return 0; +} + +int pet_stop_walking(struct pet_data *pd,int type) +{ + nullpo_retr(0, pd); + + if(pd->state.state == MS_WALK || pd->state.state == MS_IDLE) { + pd->walkpath.path_len=0; + pd->to_x=pd->bl.x; + pd->to_y=pd->bl.y; + } + if(type&0x01) + clif_fixpetpos(pd); + if(type&~0xff) + pet_changestate(pd,MS_DELAY,type>>8); + else + pet_changestate(pd,MS_IDLE,0); + + return 0; +} + +static int pet_hungry(int tid,unsigned int tick,int id,int data) +{ + struct map_session_data *sd; + int interval,t; + + sd=map_id2sd(id); + if(sd==NULL) + return 1; + + if(sd->pet_hungry_timer != tid){ + if(battle_config.error_log) + printf("pet_hungry_timer %d != %d\n",sd->pet_hungry_timer,tid); + return 0; + } + sd->pet_hungry_timer = -1; + if(!sd->status.pet_id || !sd->pd || !sd->petDB) + return 1; + + sd->pet.hungry--; + t = sd->pet.intimate; + if(sd->pet.hungry < 0) { + if(sd->pd->target_id > 0) + pet_stopattack(sd->pd); + sd->pet.hungry = 0; + sd->pet.intimate -= battle_config.pet_hungry_friendly_decrease; + if(sd->pet.intimate <= 0) { + sd->pet.intimate = 0; + if(battle_config.pet_status_support && t > 0) { + if(sd->bl.prev != NULL) + pc_calcstatus(sd,0); + else + pc_calcstatus(sd,2); + } + } + clif_send_petdata(sd,1,sd->pet.intimate); + } + clif_send_petdata(sd,2,sd->pet.hungry); + + if(battle_config.pet_hungry_delay_rate != 100) + interval = (sd->petDB->hungry_delay*battle_config.pet_hungry_delay_rate)/100; + else + interval = sd->petDB->hungry_delay; + if(interval <= 0) + interval = 1; + sd->pet_hungry_timer = add_timer(tick+interval,pet_hungry,sd->bl.id,0); + + return 0; +} + +int search_petDB_index(int key,int type) +{ + int i; + + for(i=0;i<MAX_PET_DB;i++) { + if(pet_db[i].class <= 0) + continue; + switch(type) { + case PET_CLASS: + if(pet_db[i].class == key) + return i; + break; + case PET_CATCH: + if(pet_db[i].itemID == key) + return i; + break; + case PET_EGG: + if(pet_db[i].EggID == key) + return i; + break; + case PET_EQUIP: + if(pet_db[i].AcceID == key) + return i; + break; + case PET_FOOD: + if(pet_db[i].FoodID == key) + return i; + break; + default: + return -1; + } + } + return -1; +} + +int pet_hungry_timer_delete(struct map_session_data *sd) +{ + nullpo_retr(0, sd); + + if(sd->pet_hungry_timer != -1) { + delete_timer(sd->pet_hungry_timer,pet_hungry); + sd->pet_hungry_timer = -1; + } + + return 0; +} + +int pet_remove_map(struct map_session_data *sd) +{ + nullpo_retr(0, sd); + + if(sd->status.pet_id && sd->pd) { + + struct pet_data *pd=sd->pd; // [Valaris] + if(pd->skillbonustimer!=-1) pd->skillbonustimer=-1; + if(pd->skillbonusduration!=-1) pd->skillbonusduration=-1; + if(pd->skilltype !=-1) pd->skilltype=-1; + if(pd->skillval !=-1) pd->skillval=-1; + if(pd->skilltimer!=-1) pd->skilltimer=-1; + if(pd->skillduration!=-1) pd->skillduration=-1; + if(pd->skillbonustype!=-1) pd->skillbonustype=-1; + if(pd->skillbonusval!=-1) pd->skillbonusval=-1; + if(sd->perfect_hiding==1) sd->perfect_hiding=0; // end additions + + pet_changestate(sd->pd,MS_IDLE,0); + if(sd->pet_hungry_timer != -1) + pet_hungry_timer_delete(sd); + clif_clearchar_area(&sd->pd->bl,0); + map_delblock(&sd->pd->bl); + map_deliddb(&sd->pd->bl); + map_freeblock(sd->pd); + } + return 0; +} +struct delay_item_drop { + int m,x,y; + int nameid,amount; + struct map_session_data *first_sd,*second_sd,*third_sd; +}; + +struct delay_item_drop2 { + int m,x,y; + struct item item_data; + struct map_session_data *first_sd,*second_sd,*third_sd; +}; + +int pet_performance(struct map_session_data *sd) +{ + struct pet_data *pd; + + nullpo_retr(0, sd); + nullpo_retr(0, pd=sd->pd); + + pet_stop_walking(pd,2000<<8); + clif_pet_performance(&pd->bl,rand()%pet_performance_val(sd) + 1); + // ルートしたItemを落とさせる + pet_lootitem_drop(pd,NULL); + + return 0; +} + +int pet_return_egg(struct map_session_data *sd) +{ + struct item tmp_item; + int flag; + + nullpo_retr(0, sd); + + if(sd->status.pet_id && sd->pd) { + struct pet_data *pd=sd->pd; + pet_remove_map(sd); + sd->status.pet_id = 0; + sd->pd = NULL; + + if(sd->petDB == NULL) + return 1; + sd->pet.incuvate = 1; + memset(&tmp_item,0,sizeof(tmp_item)); + tmp_item.nameid = sd->petDB->EggID; + tmp_item.identify = 1; + tmp_item.card[0] = 0xff00; + *((long *)(&tmp_item.card[1])) = sd->pet.pet_id; + tmp_item.card[3] = sd->pet.rename_flag; + if((flag = pc_additem(sd,&tmp_item,1))) { + clif_additem(sd,0,0,flag); + map_addflooritem(&tmp_item,1,sd->bl.m,sd->bl.x,sd->bl.y,NULL,NULL,NULL,0); + } + if(battle_config.pet_status_support && sd->pet.intimate > 0) { + if(sd->bl.prev != NULL) + pc_calcstatus(sd,0); + else + pc_calcstatus(sd,2); + } + // ルートしたItemを落とさせる + pet_lootitem_drop(pd,sd); + + intif_save_petdata(sd->status.account_id,&sd->pet); + pc_makesavestatus(sd); + chrif_save(sd); + storage_storage_save(sd); + + sd->petDB = NULL; + } + + return 0; +} + +int pet_data_init(struct map_session_data *sd) +{ + struct pet_data *pd; + int i=0,interval=0; + + nullpo_retr(1, sd); + + if(sd->status.account_id != sd->pet.account_id || sd->status.char_id != sd->pet.char_id || + sd->status.pet_id != sd->pet.pet_id) { + sd->status.pet_id = 0; + return 1; + } + + i = search_petDB_index(sd->pet.class,PET_CLASS); + if(i < 0) { + sd->status.pet_id = 0; + return 1; + } + sd->petDB = &pet_db[i]; + sd->pd = pd = (struct pet_data *)aCalloc(1,sizeof(struct pet_data)); + + pd->bl.m = sd->bl.m; + pd->bl.prev = pd->bl.next = NULL; + pd->bl.x = pd->to_x = sd->bl.x; + pd->bl.y = pd->to_y = sd->bl.y; + pet_calc_pos(pd,sd->bl.x,sd->bl.y,sd->dir); + pd->bl.x = pd->to_x; + pd->bl.y = pd->to_y; + pd->bl.id = npc_get_new_npc_id(); + memcpy(pd->name,sd->pet.name,24); + pd->class = sd->pet.class; + pd->equip = sd->pet.equip; + pd->dir = sd->dir; + pd->speed = sd->petDB->speed; + pd->bl.subtype = MONS; + pd->bl.type = BL_PET; + memset(&pd->state,0,sizeof(pd->state)); + pd->state.state = MS_IDLE; + pd->state.change_walk_target = 0; + pd->timer = -1; + pd->target_id = 0; + pd->move_fail_count = 0; + pd->next_walktime = pd->attackabletime = pd->last_thinktime = gettick(); + pd->msd = sd; + + map_addiddb(&pd->bl); + + if(sd->pet_hungry_timer != -1) + pet_hungry_timer_delete(sd); + if(battle_config.pet_hungry_delay_rate != 100) + interval = (sd->petDB->hungry_delay*battle_config.pet_hungry_delay_rate)/100; + else + interval = sd->petDB->hungry_delay; + if(interval <= 0) + interval = 1; + sd->pet_hungry_timer = add_timer(gettick()+interval,pet_hungry,sd->bl.id,0); + pd->lootitem=(struct item *)aCalloc(PETLOOT_SIZE,sizeof(struct item)); + pd->lootitem_count = 0; + pd->lootitem_weight = 0; + pd->lootitem_timer = gettick(); + return 0; +} + +int pet_birth_process(struct map_session_data *sd) +{ + nullpo_retr(1, sd); + + if(sd->status.pet_id && sd->pet.incuvate == 1) { + sd->status.pet_id = 0; + return 1; + } + + sd->pet.incuvate = 0; + sd->pet.account_id = sd->status.account_id; + sd->pet.char_id = sd->status.char_id; + sd->status.pet_id = sd->pet.pet_id; + if(pet_data_init(sd)) { + sd->status.pet_id = 0; + sd->pet.incuvate = 1; + sd->pet.account_id = 0; + sd->pet.char_id = 0; + return 1; + } + + intif_save_petdata(sd->status.account_id,&sd->pet); + pc_makesavestatus(sd); + chrif_save(sd); + storage_storage_save(sd); + map_addblock(&sd->pd->bl); + clif_spawnpet(sd->pd); + clif_send_petdata(sd,0,0); + clif_send_petdata(sd,5,0x14); + clif_pet_equip(sd->pd,sd->pet.equip); + clif_send_petstatus(sd); + + return 0; +} + +int pet_recv_petdata(int account_id,struct s_pet *p,int flag) +{ + struct map_session_data *sd; + + sd = map_id2sd(account_id); + if(sd == NULL) + return 1; + if(flag == 1) { + sd->status.pet_id = 0; + return 1; + } + memcpy(&sd->pet,p,sizeof(struct s_pet)); + if(sd->pet.incuvate == 1) + pet_birth_process(sd); + else { + pet_data_init(sd); + if(sd->bl.prev != NULL) { + map_addblock(&sd->pd->bl); + clif_spawnpet(sd->pd); + clif_send_petdata(sd,0,0); + clif_send_petdata(sd,5,0x14); +// clif_pet_equip(sd->pd,sd->pet.equip); + clif_send_petstatus(sd); + } + } + if(battle_config.pet_status_support && sd->pet.intimate > 0) { + if(sd->bl.prev != NULL) + pc_calcstatus(sd,0); + else + pc_calcstatus(sd,2); + } + + return 0; +} + +int pet_select_egg(struct map_session_data *sd,short egg_index) +{ + nullpo_retr(0, sd); + + if(sd->status.inventory[egg_index].card[0] == (short)0xff00) + intif_request_petdata(sd->status.account_id,sd->status.char_id,*((long *)&sd->status.inventory[egg_index].card[1])); + else { + if(battle_config.error_log) + printf("wrong egg item inventory %d\n",egg_index); + } + pc_delitem(sd,egg_index,1,0); + + return 0; +} + +int pet_catch_process1(struct map_session_data *sd,int target_class) +{ + nullpo_retr(0, sd); + + sd->catch_target_class = target_class; + clif_catch_process(sd); + + return 0; +} + +int pet_catch_process2(struct map_session_data *sd,int target_id) +{ + struct mob_data *md; + int i=0,pet_catch_rate=0; + + nullpo_retr(1, sd); + + md=(struct mob_data*)map_id2bl(target_id); + if(!md){ + clif_pet_rulet(sd,0); + return 1; + } + + i = search_petDB_index(md->class,PET_CLASS); + if(md == NULL || md->bl.type != BL_MOB || md->bl.prev == NULL || i < 0 || sd->catch_target_class != md->class) { + clif_pet_rulet(sd,0); + return 1; + } + + //target_idによる敵→卵判定 +// if(battle_config.etc_log) +// printf("mob_id = %d, mob_class = %d\n",md->bl.id,md->class); + //成功の場合 + pet_catch_rate = (pet_db[i].capture + (sd->status.base_level - mob_db[md->class].lv)*30 + sd->paramc[5]*20)*(200 - md->hp*100/mob_db[md->class].max_hp)/100; + if(pet_catch_rate < 1) pet_catch_rate = 1; + if(battle_config.pet_catch_rate != 100) + pet_catch_rate = (pet_catch_rate*battle_config.pet_catch_rate)/100; + + if(rand()%10000 < pet_catch_rate) { + mob_catch_delete(md,0); + clif_pet_rulet(sd,1); +// if(battle_config.etc_log) +// printf("rulet success %d\n",target_id); + intif_create_pet(sd->status.account_id,sd->status.char_id,pet_db[i].class,mob_db[pet_db[i].class].lv, + pet_db[i].EggID,0,pet_db[i].intimate,100,0,1,pet_db[i].jname); + } + else + clif_pet_rulet(sd,0); + + return 0; +} + +int pet_get_egg(int account_id,int pet_id,int flag) +{ + struct map_session_data *sd; + struct item tmp_item; + int i=0,ret=0; + + if(!flag) { + sd = map_id2sd(account_id); + if(sd == NULL) + return 1; + + i = search_petDB_index(sd->catch_target_class,PET_CLASS); + if(i >= 0) { + memset(&tmp_item,0,sizeof(tmp_item)); + tmp_item.nameid = pet_db[i].EggID; + tmp_item.identify = 1; + tmp_item.card[0] = 0xff00; + *((long *)(&tmp_item.card[1])) = pet_id; + tmp_item.card[3] = sd->pet.rename_flag; + if((ret = pc_additem(sd,&tmp_item,1))) { + clif_additem(sd,0,0,ret); + map_addflooritem(&tmp_item,1,sd->bl.m,sd->bl.x,sd->bl.y,NULL,NULL,NULL,0); + } + } + else + intif_delete_petdata(pet_id); + } + + return 0; +} + +int pet_menu(struct map_session_data *sd,int menunum) +{ + nullpo_retr(0, sd); + + switch(menunum) { + case 0: + clif_send_petstatus(sd); + break; + case 1: + pet_food(sd); + break; + case 2: + pet_performance(sd); + break; + case 3: + pet_return_egg(sd); + break; + case 4: + pet_unequipitem(sd); + break; + } + return 0; +} + +int pet_change_name(struct map_session_data *sd,char *name) +{ + int i; + + nullpo_retr(1, sd); + + if(sd->pet.rename_flag == 1 && battle_config.pet_rename == 0) + return 1; + + for(i=0;i<24 && name[i];i++){ + if( !(name[i]&0xe0) || name[i]==0x7f) + return 1; + } + + pet_stop_walking(sd->pd,1); + memcpy(sd->pet.name,name,24); + memcpy(sd->pd->name,name,24); + clif_clearchar_area(&sd->pd->bl,0); + clif_spawnpet(sd->pd); + clif_send_petdata(sd,0,0); + clif_send_petdata(sd,5,0x14); + sd->pet.rename_flag = 1; + clif_pet_equip(sd->pd,sd->pet.equip); + clif_send_petstatus(sd); + + return 0; +} + +int pet_equipitem(struct map_session_data *sd,int index) +{ + int nameid; + + nullpo_retr(1, sd); + + nameid = sd->status.inventory[index].nameid; + if(sd->petDB == NULL) + return 1; + if(sd->petDB->AcceID == 0 || nameid != sd->petDB->AcceID || sd->pet.equip != 0) { + clif_equipitemack(sd,0,0,0); + return 1; + } + else { + pc_delitem(sd,index,1,0); + sd->pet.equip = sd->pd->equip = nameid; + pc_calcstatus(sd,0); + clif_pet_equip(sd->pd,nameid); + } + + return 0; +} + +int pet_unequipitem(struct map_session_data *sd) +{ + struct item tmp_item; + int nameid,flag; + + nullpo_retr(1, sd); + + if(sd->petDB == NULL) + return 1; + if(sd->pet.equip == 0) + return 1; + + nameid = sd->pet.equip; + sd->pet.equip = sd->pd->equip = 0; + pc_calcstatus(sd,0); + clif_pet_equip(sd->pd,0); + memset(&tmp_item,0,sizeof(tmp_item)); + tmp_item.nameid = nameid; + tmp_item.identify = 1; + if((flag = pc_additem(sd,&tmp_item,1))) { + clif_additem(sd,0,0,flag); + map_addflooritem(&tmp_item,1,sd->bl.m,sd->bl.x,sd->bl.y,NULL,NULL,NULL,0); + } + + return 0; +} + +int pet_food(struct map_session_data *sd) +{ + int i,k,t; + + nullpo_retr(1, sd); + + if(sd->petDB == NULL) + return 1; + i=pc_search_inventory(sd,sd->petDB->FoodID); + if(i < 0) { + clif_pet_food(sd,sd->petDB->FoodID,0); + return 1; + } + pc_delitem(sd,i,1,0); + t = sd->pet.intimate; + if(sd->pet.hungry > 90) + sd->pet.intimate -= sd->petDB->r_full; + else if(sd->pet.hungry > 75) { + if(battle_config.pet_friendly_rate != 100) + k = (sd->petDB->r_hungry * battle_config.pet_friendly_rate)/100; + else + k = sd->petDB->r_hungry; + k = k >> 1; + if(k <= 0) + k = 1; + sd->pet.intimate += k; + } + else { + if(battle_config.pet_friendly_rate != 100) + k = (sd->petDB->r_hungry * battle_config.pet_friendly_rate)/100; + else + k = sd->petDB->r_hungry; + sd->pet.intimate += k; + } + if(sd->pet.intimate <= 0) { + sd->pet.intimate = 0; + if(battle_config.pet_status_support && t > 0) { + if(sd->bl.prev != NULL) + pc_calcstatus(sd,0); + else + pc_calcstatus(sd,2); + } + } + else if(sd->pet.intimate > 1000) + sd->pet.intimate = 1000; + sd->pet.hungry += sd->petDB->fullness; + if(sd->pet.hungry > 100) + sd->pet.hungry = 100; + + clif_send_petdata(sd,2,sd->pet.hungry); + clif_send_petdata(sd,1,sd->pet.intimate); + clif_pet_food(sd,sd->petDB->FoodID,1); + + return 0; +} + +static int pet_randomwalk(struct pet_data *pd,int tick) +{ + const int retrycount=20; + int speed; + + nullpo_retr(0, pd); + + speed = battle_get_speed(&pd->bl); + + if(DIFF_TICK(pd->next_walktime,tick) < 0){ + int i,x,y,c,d=12-pd->move_fail_count; + if(d<5) d=5; + for(i=0;i<retrycount;i++){ + int r=rand(); + x=pd->bl.x+r%(d*2+1)-d; + y=pd->bl.y+r/(d*2+1)%(d*2+1)-d; + if((c=map_getcell(pd->bl.m,x,y))!=1 && c!=5 && pet_walktoxy(pd,x,y)==0){ + pd->move_fail_count=0; + break; + } + if(i+1>=retrycount){ + pd->move_fail_count++; + if(pd->move_fail_count>1000){ + if(battle_config.error_log) + printf("PET cant move. hold position %d, class = %d\n",pd->bl.id,pd->class); + pd->move_fail_count=0; + pet_changestate(pd,MS_DELAY,60000); + return 0; + } + } + } + for(i=c=0;i<pd->walkpath.path_len;i++){ + if(pd->walkpath.path[i]&1) + c+=speed*14/10; + else + c+=speed; + } + pd->next_walktime = tick+rand()%3000+3000+c; + + return 1; + } + return 0; +} + +static int pet_unlocktarget(struct pet_data *pd) +{ + nullpo_retr(0, pd); + + pd->target_id=0; + + return 0; +} + +static int pet_ai_sub_hard(struct pet_data *pd,unsigned int tick) +{ + struct map_session_data *sd = pd->msd; + struct mob_data *md = NULL; + int dist,i=0,dx,dy,ret; + int mode,race; + + nullpo_retr(0, pd); + + sd = pd->msd; + + if(pd->bl.prev == NULL || sd == NULL || sd->bl.prev == NULL) + return 0; + + if(DIFF_TICK(tick,pd->last_thinktime) < MIN_PETTHINKTIME) + return 0; + pd->last_thinktime=tick; + + if(pd->state.state == MS_DELAY || pd->bl.m != sd->bl.m) + return 0; + // ペットによるルート + if(!pd->target_id && pd->lootitem_count < PETLOOT_SIZE && pd->lootitem_count < pd->lootmax && pd->loot==1 && DIFF_TICK(gettick(),pd->lootitem_timer)>0) + map_foreachinarea(pet_ai_sub_hard_lootsearch,pd->bl.m, + pd->bl.x-AREA_SIZE*2,pd->bl.y-AREA_SIZE*2, + pd->bl.x+AREA_SIZE*2,pd->bl.y+AREA_SIZE*2, + BL_ITEM,pd,&i); + + if(sd->pet.intimate > 0) { + dist = distance(sd->bl.x,sd->bl.y,pd->bl.x,pd->bl.y); + if(dist > 12) { + if(pd->target_id > 0) + pet_unlocktarget(pd); + if(pd->timer != -1 && pd->state.state == MS_WALK && distance(pd->to_x,pd->to_y,sd->bl.x,sd->bl.y) < 3) + return 0; + pd->speed = (sd->speed>>1); + if(pd->speed <= 0) + pd->speed = 1; + pet_calc_pos(pd,sd->bl.x,sd->bl.y,sd->dir); + if(pet_walktoxy(pd,pd->to_x,pd->to_y)) + pet_randomwalk(pd,tick); + } + else if(pd->target_id - MAX_FLOORITEM > 0) { + mode=mob_db[pd->class].mode; + race=mob_db[pd->class].race; + md=(struct mob_data *)map_id2bl(pd->target_id); + if(md == NULL || md->bl.type != BL_MOB || pd->bl.m != md->bl.m || md->bl.prev == NULL || + distance(pd->bl.x,pd->bl.y,md->bl.x,md->bl.y) > 13) + pet_unlocktarget(pd); + else if(mob_db[pd->class].mexp <= 0 && !(mode&0x20) && (md->option & 0x06 && race!=4 && race!=6) ) + pet_unlocktarget(pd); + else if(!battle_check_range(&pd->bl,&md->bl,mob_db[pd->class].range)){ + if(pd->timer != -1 && pd->state.state == MS_WALK && distance(pd->to_x,pd->to_y,md->bl.x,md->bl.y) < 2) + return 0; + if( !pet_can_reach(pd,md->bl.x,md->bl.y)) + pet_unlocktarget(pd); + else { + i=0; + pd->speed = battle_get_speed(&pd->bl); + do { + if(i==0) { // 最初はAEGISと同じ方法で検索 + dx=md->bl.x - pd->bl.x; + dy=md->bl.y - pd->bl.y; + if(dx<0) dx++; + else if(dx>0) dx--; + if(dy<0) dy++; + else if(dy>0) dy--; + } + else { // だめならAthena式(ランダム) + dx=md->bl.x - pd->bl.x + rand()%3 - 1; + dy=md->bl.y - pd->bl.y + rand()%3 - 1; + } + ret=pet_walktoxy(pd,pd->bl.x+dx,pd->bl.y+dy); + i++; + } while(ret && i<5); + + if(ret) { // 移動不可能な所からの攻撃なら2歩下る + if(dx<0) dx=2; + else if(dx>0) dx=-2; + if(dy<0) dy=2; + else if(dy>0) dy=-2; + pet_walktoxy(pd,pd->bl.x+dx,pd->bl.y+dy); + } + } + } + else { + if(pd->state.state==MS_WALK) + pet_stop_walking(pd,1); + if(pd->state.state==MS_ATTACK) + return 0; + pet_changestate(pd,MS_ATTACK,0); + } + } + else if(pd->target_id > 0){ // ルート処理 + struct block_list *bl_item; + struct flooritem_data *fitem; + + bl_item = map_id2bl(pd->target_id); + if(bl_item == NULL || bl_item->type != BL_ITEM ||bl_item->m != pd->bl.m || + (dist=distance(pd->bl.x,pd->bl.y,bl_item->x,bl_item->y))>=5){ + // 遠すぎるかアイテムがなくなった + pet_unlocktarget(pd); + } + else if(dist){ + if(pd->timer != -1 && pd->state.state!=MS_ATTACK && (DIFF_TICK(pd->next_walktime,tick)<0 || distance(pd->to_x,pd->to_y,bl_item->x,bl_item->y) <= 0)) + return 0; // 既に移動中 + + pd->next_walktime=tick+500; + dx=bl_item->x - pd->bl.x; + dy=bl_item->y - pd->bl.y; + + ret=pet_walktoxy(pd,pd->bl.x+dx,pd->bl.y+dy); + } + else{ // アイテムまでたどり着いた + fitem = (struct flooritem_data *)bl_item; + if(pd->state.state==MS_ATTACK) + return 0; // 攻撃中 + if(pd->state.state==MS_WALK){ // 歩行中なら停止 + pet_stop_walking(pd,1); + } + if(pd->lootitem_count < PETLOOT_SIZE && pd->lootitem_count < pd->lootmax){ + memcpy(&pd->lootitem[pd->lootitem_count++],&fitem->item_data,sizeof(pd->lootitem[0])); + pd->lootitem_weight += itemdb_search(fitem->item_data.nameid)->weight*fitem->item_data.amount; + } + else if(pd->lootitem_count >= PETLOOT_SIZE || pd->lootitem_count >=pd->lootmax) { + pet_unlocktarget(pd); + return 0; + } + else { + if(pd->lootitem[0].card[0] == (short)0xff00) + intif_delete_petdata(*((long *)(&pd->lootitem[0].card[1]))); + for(i=0;i<PETLOOT_SIZE-1;i++) + memcpy(&pd->lootitem[i],&pd->lootitem[i+1],sizeof(pd->lootitem[0])); + memcpy(&pd->lootitem[PETLOOT_SIZE-1],&fitem->item_data,sizeof(pd->lootitem[0])); + } + map_clearflooritem(bl_item->id); + pet_unlocktarget(pd); + } + } + else { + if(dist <= 3 || (pd->timer != -1 && pd->state.state == MS_WALK && distance(pd->to_x,pd->to_y,sd->bl.x,sd->bl.y) < 3) ) + return 0; + pd->speed = battle_get_speed(&pd->bl); + pet_calc_pos(pd,sd->bl.x,sd->bl.y,sd->dir); + if(pet_walktoxy(pd,pd->to_x,pd->to_y)) + pet_randomwalk(pd,tick); + } + } + else { + pd->speed = battle_get_speed(&pd->bl); + if(pd->state.state == MS_ATTACK) + pet_stopattack(pd); + pet_randomwalk(pd,tick); + } + + return 0; +} + +static int pet_ai_sub_foreachclient(struct map_session_data *sd,va_list ap) +{ + unsigned int tick; + + nullpo_retr(0, sd); + nullpo_retr(0, ap); + + tick=va_arg(ap,unsigned int); + if(sd->status.pet_id && sd->pd && sd->petDB) + pet_ai_sub_hard(sd->pd,tick); + + return 0; +} + +static int pet_ai_hard(int tid,unsigned int tick,int id,int data) +{ + clif_foreachclient(pet_ai_sub_foreachclient,tick); + + return 0; +} + +int pet_ai_sub_hard_lootsearch(struct block_list *bl,va_list ap) +{ + struct pet_data* pd; + int dist,*itc; + + nullpo_retr(0, bl); + nullpo_retr(0, ap); + nullpo_retr(0, pd=va_arg(ap,struct pet_data *)); + nullpo_retr(0, itc=va_arg(ap,int *)); + + if(!pd->target_id){ + struct flooritem_data *fitem = (struct flooritem_data *)bl; + struct map_session_data *sd = NULL; + // ルート権無し + if(fitem && fitem->first_get_id>0) + sd = map_id2sd(fitem->first_get_id); + // Removed [Valaris] + //if((pd->lootitem_weight + (itemdb_search(fitem->item_data.))->weight * fitem->item_data.amount) > battle_config.pet_weight) + // return 0; + + if(!pd->lootitem || (pd->lootitem_count >= PETLOOT_SIZE) || (pd->lootitem_count >= pd->lootmax) || (sd && sd->pd != pd)) + return 0; + if(bl->m == pd->bl.m && (dist=distance(pd->bl.x,pd->bl.y,bl->x,bl->y))<5){ + if( pet_can_reach(pd,bl->x,bl->y) // 到達可能性判定 + && rand()%1000<1000/(++(*itc)) ){ // 範囲内PCで等確率にする + pd->target_id=bl->id; + } + } + } + return 0; +} +int pet_lootitem_drop(struct pet_data *pd,struct map_session_data *sd) +{ + int i,flag=0; + + if(pd){ + if(pd->lootitem) { + for(i=0;i<pd->lootitem_count;i++) { + struct delay_item_drop2 *ditem; + + ditem=(struct delay_item_drop2 *)aCalloc(1,sizeof(struct delay_item_drop2)); + memcpy(&ditem->item_data,&pd->lootitem[i],sizeof(pd->lootitem[0])); + ditem->m = pd->bl.m; + ditem->x = pd->bl.x; + ditem->y = pd->bl.y; + ditem->first_sd = 0; + ditem->second_sd = 0; + ditem->third_sd = 0; + // 落とさないで直接PCのItem欄へ + if(sd){ + if((flag = pc_additem(sd,&ditem->item_data,ditem->item_data.amount))){ + clif_additem(sd,0,0,flag); + map_addflooritem(&ditem->item_data,ditem->item_data.amount,ditem->m,ditem->x,ditem->y,ditem->first_sd,ditem->second_sd,ditem->third_sd,0); + } + free(ditem); + } + else + add_timer(gettick()+540+i,pet_delay_item_drop2,(int)ditem,0); + } + pd->lootitem=NULL; + pd->lootitem=(struct item *)aCalloc(PETLOOT_SIZE,sizeof(struct item)); + pd->lootitem_count = 0; + pd->lootitem_weight = 0; + pd->lootitem_timer = gettick()+10000; // 10*1000msの間拾わない + } + } + return 1; +} + +int pet_delay_item_drop2(int tid,unsigned int tick,int id,int data) +{ + struct delay_item_drop2 *ditem; + + ditem=(struct delay_item_drop2 *)id; + + map_addflooritem(&ditem->item_data,ditem->item_data.amount,ditem->m,ditem->x,ditem->y,ditem->first_sd,ditem->second_sd,ditem->third_sd,0); + + free(ditem); + return 0; +} + +/*========================================== + * pet bonus giving skills [Valaris] + *------------------------------------------ + */ + +int pet_skill_bonus(struct map_session_data *sd,struct pet_data *pd,int type,int val,int duration,int timer,int data) +{ + if(pd==NULL || sd==NULL) + return 1; + + pd->skillbonustype=type; + pd->skillbonusval=val; + pd->skillduration=duration; + pd->skilltimer=timer; + + pd->skillbonustimer=add_timer(gettick()+pd->skilltimer*1000,pet_skill_bonus_timer,sd->bl.id,0); + + return 0; + +} + +int pet_skill_bonus_timer(int tid,unsigned int tick,int id,int data) +{ + struct map_session_data *sd=(struct map_session_data*)map_id2bl(id); + struct pet_data *pd; + + if(sd==NULL || sd->bl.type!=BL_PC) + return 1; + + pd=sd->pd; + + if(pd==NULL || pd->bl.type!=BL_PET) + return 1; + + if(pd->skillbonustimer != tid) + return 0; + + pd->skillbonustimer=-1; + + pc_bonus(sd,pd->skillbonustype,pd->skillbonusval); + if(pd->skillbonustype < 56) clif_updatestatus(sd,pd->skillbonustype); + pd->skillbonusduration=add_timer(gettick()+pd->skillduration*1000,pet_skill_bonus_duration,sd->bl.id,0); + + return 0; +} + +int pet_skill_bonus_duration(int tid,unsigned int tick,int id,int data) +{ + struct map_session_data *sd=(struct map_session_data*)map_id2bl(id); + struct pet_data *pd; + + if(sd==NULL || sd->bl.type!=BL_PC) + return 1; + + pd=sd->pd; + + if(pd==NULL || pd->bl.type!=BL_PET) + return 1; + + if(pd->skillbonusduration != tid) + return 0; + + pd->skillbonusduration=-1; + + pc_bonus(sd,pd->skillbonustype,-pd->skillbonusval); + if(pd->skillbonustype < 56) clif_updatestatus(sd,pd->skillbonustype); + + pet_skill_bonus(sd,pd,pd->skillbonustype,pd->skillbonusval,pd->skillduration,pd->skilltimer,0); + + return 0; +} + +int pet_recovery_timer(int tid,unsigned int tick,int id,int data) +{ + struct map_session_data *sd=(struct map_session_data*)map_id2bl(id); + struct pet_data *pd; + + if(sd==NULL || sd->bl.type!=BL_PC) + return 1; + + pd=sd->pd; + + if(pd==NULL || pd->bl.type!=BL_PET) + return 1; + + if(pd->skillbonustimer != tid) + return 0; + + if(sd->sc_data[pd->skilltype].timer != -1) + skill_status_change_end(&sd->bl,pd->skilltype,-1); + + pd->skillbonustimer=add_timer(gettick()+pd->skilltimer*1000,pet_recovery_timer,sd->bl.id,0); + + return 0; +} + +int pet_heal_timer(int tid,unsigned int tick,int id,int data) +{ + struct map_session_data *sd=(struct map_session_data*)map_id2bl(id); + struct pet_data *pd; + + if(sd==NULL || sd->bl.type!=BL_PC) + return 1; + + pd=sd->pd; + + if(pd==NULL || pd->bl.type!=BL_PET) + return 1; + + if(pd->skillbonustimer != tid) + return 0; + + if(sd->status.hp < sd->status.max_hp * pd->skilltype/100) { + clif_skill_nodamage(&pd->bl,&sd->bl,AL_HEAL,pd->skillval,1); + pc_heal(sd,pd->skillval,0); + } + + pd->skillbonustimer=add_timer(gettick()+pd->skilltimer*1000,pet_heal_timer,sd->bl.id,0); + + return 0; +} + +int pet_mag_timer(int tid,unsigned int tick,int id,int data) +{ + struct pet_data *pd; + struct map_session_data *sd=(struct map_session_data*)map_id2bl(id); + + if(sd==NULL || sd->bl.type!=BL_PC) + return 1; + + pd=sd->pd; + + if(pd==NULL || pd->bl.type!=BL_PET) + return 1; + + if(pd->skillbonustimer != tid) + return 0; + + if(sd->status.hp < sd->status.max_hp * pd->skilltype/100 && sd->status.sp < sd->status.max_sp * pd->skillduration/100) { + clif_skill_nodamage(&pd->bl,&sd->bl,PR_MAGNIFICAT,pd->skillval,1); + skill_status_change_start(&sd->bl,SkillStatusChangeTable[PR_MAGNIFICAT],pd->skillval,0,0,0,skill_get_time(PR_MAGNIFICAT,pd->skillval),0 ); + } + + pd->skillbonustimer=add_timer(gettick()+pd->skilltimer*1000,pet_mag_timer,sd->bl.id,0); + + return 0; +} + +int pet_skillattack_timer(int tid,unsigned int tick,int id,int data) +{ + struct mob_data *md; + struct map_session_data *sd=(struct map_session_data*)map_id2bl(id); + struct pet_data *pd; + + if(sd==NULL || sd->bl.type!=BL_PC) + return 1; + + pd=sd->pd; + + if(pd==NULL || pd->bl.type!=BL_PET) + return 1; + + if(pd->skillbonustimer != tid) + return 0; + + md=(struct mob_data *)map_id2bl(sd->attacktarget); + if(md == NULL || md->bl.type != BL_MOB || pd->bl.m != md->bl.m || md->bl.prev == NULL || + distance(pd->bl.x,pd->bl.y,md->bl.x,md->bl.y) > 6) { + pd->target_id=0; + pd->skillbonustimer=add_timer(gettick()+100,pet_skillattack_timer,sd->bl.id,pd->skillduration); + return 0; + } + + if(md && rand()%100 < sd->pet.intimate*pd->skilltimer/100 ) { + if(pd->skilltype==6 || pd->skilltype==176) { + skill_castend_nodamage_id(&pd->bl,&md->bl,pd->skilltype,pd->skillval,tick,0); + } + + else if(pd->skilltype==110){ + skill_castend_pos2(&pd->bl,md->bl.x,md->bl.y,pd->skilltype,pd->skillval,tick,0); + } + + else if(pd->skilltype==91) { + skill_castend_pos2(&pd->bl,md->bl.x,md->bl.y,pd->skilltype,pd->skillval+rand()%100,tick,0); + } + else + skill_castend_damage_id(&pd->bl,&md->bl,pd->skilltype,pd->skillval,tick,0); + pd->skillbonustimer=add_timer(gettick()+1000,pet_skillattack_timer,sd->bl.id,0); + return 0; + } + + pd->skillbonustimer=add_timer(gettick()+100,pet_skillattack_timer,sd->bl.id,0); + + return 0; +} + +/*========================================== + *ペットデータ読み込み + *------------------------------------------ + */ +int read_petdb() +{ + FILE *fp; + char line[1024]; + int i; + int j=0; + char *filename[]={"db/pet_db.txt","db/pet_db2.txt"}; + + memset(pet_db,0,sizeof(pet_db)); + for(i=0;i<2;i++){ + fp=fopen(filename[i],"r"); + if(fp==NULL){ + if(i>0) + continue; + printf("can't read %s\n",filename[i]); + return -1; + } + while(fgets(line,1020,fp)){ + int nameid,i; + char *str[32],*p,*np; + + if(line[0] == '/' && line[1] == '/') + continue; + + for(i=0,p=line;i<20;i++){ + if((np=strchr(p,','))!=NULL){ + str[i]=p; + *np=0; + p=np+1; + } else { + str[i]=p; + p+=strlen(p); + } + } + + nameid=atoi(str[0]); + if(nameid<=0 || nameid>2000) + continue; + + //MobID,Name,JName,ItemID,EggID,AcceID,FoodID,"Fullness (1回の餌での満腹度増加率%)","HungryDeray (/min)","R_Hungry (空腹時餌やり親密度増加率%)","R_Full (とても満腹時餌やり親密度減少率%)","Intimate (捕獲時親密度%)","Die (死亡時親密度減少率%)","Capture (捕獲率%)",(Name) + pet_db[j].class = nameid; + memcpy(pet_db[j].name,str[1],24); + memcpy(pet_db[j].jname,str[2],24); + pet_db[j].itemID=atoi(str[3]); + pet_db[j].EggID=atoi(str[4]); + pet_db[j].AcceID=atoi(str[5]); + pet_db[j].FoodID=atoi(str[6]); + pet_db[j].fullness=atoi(str[7]); + pet_db[j].hungry_delay=atoi(str[8])*1000; + pet_db[j].r_hungry=atoi(str[9]); + if(pet_db[j].r_hungry <= 0) + pet_db[j].r_hungry=1; + pet_db[j].r_full=atoi(str[10]); + pet_db[j].intimate=atoi(str[11]); + pet_db[j].die=atoi(str[12]); + pet_db[j].capture=atoi(str[13]); + pet_db[j].speed=atoi(str[14]); + pet_db[j].s_perfor=(char)atoi(str[15]); + pet_db[j].talk_convert_class=atoi(str[16]); + pet_db[j].attack_rate=atoi(str[17]); + pet_db[j].defence_attack_rate=atoi(str[18]); + pet_db[j].change_target_rate=atoi(str[19]); + pet_db[j].script = NULL; + if((np=strchr(p,'{'))==NULL) + continue; + pet_db[j].script = parse_script(np,0); + j++; + } + fclose(fp); + printf("read %s done (count=%d)\n",filename[i],j); + } + return 0; +} + +/*========================================== + * スキル関係初期化処理 + *------------------------------------------ + */ +int do_init_pet(void) +{ + read_petdb(); + + add_timer_func_list(pet_timer,"pet_timer"); + add_timer_func_list(pet_hungry,"pet_hungry"); + add_timer_func_list(pet_ai_hard,"pet_ai_hard"); + add_timer_func_list(pet_skill_bonus_timer,"pet_skill_bonus_timer"); // [Valaris] + add_timer_func_list(pet_skill_bonus_duration,"pet_skill_bonus_duration"); // [Valaris] + add_timer_func_list(pet_recovery_timer,"pet_recovery_timer"); // [Valaris] + add_timer_func_list(pet_mag_timer,"pet_mag_timer"); // [Valaris] + add_timer_func_list(pet_heal_timer,"pet_heal_timer"); // [Valaris] + add_timer_func_list(pet_skillattack_timer,"pet_skillattack_timer"); // [Valaris] + add_timer_interval(gettick()+MIN_PETTHINKTIME,pet_ai_hard,0,0,MIN_PETTHINKTIME); + + return 0; +} + diff --git a/src/map/pet.h b/src/map/pet.h new file mode 100644 index 0000000..365a449 --- /dev/null +++ b/src/map/pet.h @@ -0,0 +1,69 @@ +// $Id: pet.h,v 1.2 2004/09/25 05:32:18 MouseJstr Exp $ +#ifndef _PET_H_ +#define _PET_H_ + +#define MAX_PET_DB 100 +#define PETLOOT_SIZE 20 // [Valaris] + +struct pet_db { + int class; + char name[24],jname[24]; + int itemID; + int EggID; + int AcceID; + int FoodID; + int fullness; + int hungry_delay; + int r_hungry; + int r_full; + int intimate; + int die; + int capture; + int speed; + char s_perfor; + int talk_convert_class; + int attack_rate; + int defence_attack_rate; + int change_target_rate; + char *script; +}; +extern struct pet_db pet_db[MAX_PET_DB]; + +enum { PET_CLASS,PET_CATCH,PET_EGG,PET_EQUIP,PET_FOOD }; + +int pet_hungry_val(struct map_session_data *sd); +int pet_target_check(struct map_session_data *sd,struct block_list *bl,int type); +int pet_stopattack(struct pet_data *pd); +int pet_changestate(struct pet_data *pd,int state,int type); +int pet_walktoxy(struct pet_data *pd,int x,int y); +int pet_stop_walking(struct pet_data *pd,int type); +int search_petDB_index(int key,int type); +int pet_hungry_timer_delete(struct map_session_data *sd); +int pet_remove_map(struct map_session_data *sd); +int pet_data_init(struct map_session_data *sd); +int pet_birth_process(struct map_session_data *sd); +int pet_recv_petdata(int account_id,struct s_pet *p,int flag); +int pet_select_egg(struct map_session_data *sd,short egg_index); +int pet_catch_process1(struct map_session_data *sd,int target_class); +int pet_catch_process2(struct map_session_data *sd,int target_id); +int pet_get_egg(int account_id,int pet_id,int flag); +int pet_menu(struct map_session_data *sd,int menunum); +int pet_change_name(struct map_session_data *sd,char *name); +int pet_equipitem(struct map_session_data *sd,int index); +int pet_unequipitem(struct map_session_data *sd); +int pet_food(struct map_session_data *sd); +int pet_lootitem_drop(struct pet_data *pd,struct map_session_data *sd); +int pet_delay_item_drop2(int tid,unsigned int tick,int id,int data); +int pet_ai_sub_hard_lootsearch(struct block_list *bl,va_list ap); +int pet_skill_bonus(struct map_session_data *sd,struct pet_data *pd,int type,int val,int duration,int timer,int data); +int pet_skill_bonus_timer(int tid,unsigned int tick,int id,int data); // [Valaris] +int pet_skill_bonus_duration(int tid,unsigned int tick,int id,int data); // [Valaris] +int pet_recovery_timer(int tid,unsigned int tick,int id,int data); // [Valaris] +int pet_mag_timer(int tid,unsigned int tick,int id,int data); // [Valaris] +int pet_heal_timer(int tid,unsigned int tick,int id,int data); // [Valaris] +int pet_skillattack_timer(int tid,unsigned int tick,int id,int data); // [Valaris] + +int do_init_pet(void); + +#endif + diff --git a/src/map/script.c b/src/map/script.c new file mode 100644 index 0000000..a9a171b --- /dev/null +++ b/src/map/script.c @@ -0,0 +1,6700 @@ +// $Id: script.c 148 2004-09-30 14:05:37Z MouseJstr $ +//#define DEBUG_FUNCIN +//#define DEBUG_DISP +//#define DEBUG_RUN + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> + +#ifndef LCCWIN32 +#include <sys/time.h> +#endif + +#include <time.h> + +#include "socket.h" +#include "timer.h" +#include "malloc.h" +#include "lock.h" + +#include "map.h" +#include "clif.h" +#include "chrif.h" +#include "itemdb.h" +#include "pc.h" +#include "script.h" +#include "storage.h" +#include "mob.h" +#include "npc.h" +#include "pet.h" +#include "intif.h" +#include "db.h" +#include "skill.h" +#include "chat.h" +#include "battle.h" +#include "party.h" +#include "guild.h" +#include "lock.h" +#include "atcommand.h" + +#ifdef MEMWATCH +#include "memwatch.h" +#endif + +#define SCRIPT_BLOCK_SIZE 256 +enum { LABEL_NEXTLINE=1,LABEL_START }; +static unsigned char * script_buf; +static int script_pos,script_size; + +char *str_buf; +int str_pos,str_size; +static struct { + int type; + int str; + int backpatch; + int label; + int (*func)(); + int val; + int next; +} *str_data; +int str_num=LABEL_START,str_data_size; +int str_hash[16]; + +static struct dbt *mapreg_db=NULL; +static struct dbt *mapregstr_db=NULL; +static int mapreg_dirty=-1; +char mapreg_txt[256]="save/mapreg.txt"; +#define MAPREG_AUTOSAVE_INTERVAL (10*1000) + +static struct dbt *scriptlabel_db=NULL; +static struct dbt *userfunc_db=NULL; + +struct dbt* script_get_label_db(){ return scriptlabel_db; } +struct dbt* script_get_userfunc_db(){ if(!userfunc_db) userfunc_db=strdb_init(50); return userfunc_db; } + +int scriptlabel_final(void *k,void *d,va_list ap){ return 0; } +static char pos[11][100] = {"頭","体","左手","右手","ローブ","靴","アクセサリー1","アクセサリー2","頭2","頭3","装着していない"}; + +static struct Script_Config { + int warn_func_no_comma; + int warn_cmd_no_comma; + int warn_func_mismatch_paramnum; + int warn_cmd_mismatch_paramnum; + int check_cmdcount; + int check_gotocount; +} script_config; +static int parse_cmd_if=0; +static int parse_cmd; + +/*========================================== + * ローカルプロトタイプ宣言 (必要な物のみ) + *------------------------------------------ + */ +unsigned char* parse_subexpr(unsigned char *,int); +int buildin_mes(struct script_state *st); +int buildin_goto(struct script_state *st); +int buildin_callsub(struct script_state *st); +int buildin_callfunc(struct script_state *st); +int buildin_return(struct script_state *st); +int buildin_getarg(struct script_state *st); +int buildin_next(struct script_state *st); +int buildin_close(struct script_state *st); +int buildin_close2(struct script_state *st); +int buildin_menu(struct script_state *st); +int buildin_rand(struct script_state *st); +int buildin_warp(struct script_state *st); +int buildin_areawarp(struct script_state *st); +int buildin_heal(struct script_state *st); +int buildin_itemheal(struct script_state *st); +int buildin_percentheal(struct script_state *st); +int buildin_jobchange(struct script_state *st); +int buildin_input(struct script_state *st); +int buildin_setlook(struct script_state *st); +int buildin_set(struct script_state *st); +int buildin_setarray(struct script_state *st); +int buildin_cleararray(struct script_state *st); +int buildin_copyarray(struct script_state *st); +int buildin_getarraysize(struct script_state *st); +int buildin_deletearray(struct script_state *st); +int buildin_getelementofarray(struct script_state *st); +int buildin_if(struct script_state *st); +int buildin_getitem(struct script_state *st); +int buildin_getitem2(struct script_state *st); +int buildin_makeitem(struct script_state *st); +int buildin_delitem(struct script_state *st); +int buildin_viewpoint(struct script_state *st); +int buildin_countitem(struct script_state *st); +int buildin_checkweight(struct script_state *st); +int buildin_readparam(struct script_state *st); +int buildin_getcharid(struct script_state *st); +int buildin_getpartyname(struct script_state *st); +int buildin_getpartymember(struct script_state *st); +int buildin_getguildname(struct script_state *st); +int buildin_getguildmaster(struct script_state *st); +int buildin_getguildmasterid(struct script_state *st); +int buildin_strcharinfo(struct script_state *st); +int buildin_getequipid(struct script_state *st); +int buildin_getequipname(struct script_state *st); +int buildin_getbrokenid(struct script_state *st); // [Valaris] +int buildin_repair(struct script_state *st); // [Valaris] +int buildin_getequipisequiped(struct script_state *st); +int buildin_getequipisenableref(struct script_state *st); +int buildin_getequipisidentify(struct script_state *st); +int buildin_getequiprefinerycnt(struct script_state *st); +int buildin_getequipweaponlv(struct script_state *st); +int buildin_getequippercentrefinery(struct script_state *st); +int buildin_successrefitem(struct script_state *st); +int buildin_failedrefitem(struct script_state *st); +int buildin_cutin(struct script_state *st); +int buildin_cutincard(struct script_state *st); +int buildin_statusup(struct script_state *st); +int buildin_statusup2(struct script_state *st); +int buildin_bonus(struct script_state *st); +int buildin_bonus2(struct script_state *st); +int buildin_bonus3(struct script_state *st); +int buildin_skill(struct script_state *st); +int buildin_guildskill(struct script_state *st); +int buildin_getskilllv(struct script_state *st); +int buildin_getgdskilllv(struct script_state *st); +int buildin_basicskillcheck(struct script_state *st); +int buildin_getgmlevel(struct script_state *st); +int buildin_end(struct script_state *st); +int buildin_checkoption(struct script_state *st); +int buildin_setoption(struct script_state *st); +int buildin_setcart(struct script_state *st); +int buildin_checkcart(struct script_state *st); // check cart [Valaris] +int buildin_setfalcon(struct script_state *st); +int buildin_checkfalcon(struct script_state *st); // check falcon [Valaris] +int buildin_setriding(struct script_state *st); +int buildin_checkriding(struct script_state *st); // check for pecopeco [Valaris] +int buildin_savepoint(struct script_state *st); +int buildin_gettimetick(struct script_state *st); +int buildin_gettime(struct script_state *st); +int buildin_gettimestr(struct script_state *st); +int buildin_openstorage(struct script_state *st); +int buildin_guildopenstorage(struct script_state *st); +int buildin_itemskill(struct script_state *st); +int buildin_produce(struct script_state *st); +int buildin_monster(struct script_state *st); +int buildin_areamonster(struct script_state *st); +int buildin_killmonster(struct script_state *st); +int buildin_killmonsterall(struct script_state *st); +int buildin_doevent(struct script_state *st); +int buildin_donpcevent(struct script_state *st); +int buildin_addtimer(struct script_state *st); +int buildin_deltimer(struct script_state *st); +int buildin_addtimercount(struct script_state *st); +int buildin_initnpctimer(struct script_state *st); +int buildin_stopnpctimer(struct script_state *st); +int buildin_startnpctimer(struct script_state *st); +int buildin_setnpctimer(struct script_state *st); +int buildin_getnpctimer(struct script_state *st); +int buildin_announce(struct script_state *st); +int buildin_mapannounce(struct script_state *st); +int buildin_areaannounce(struct script_state *st); +int buildin_getusers(struct script_state *st); +int buildin_getmapusers(struct script_state *st); +int buildin_getareausers(struct script_state *st); +int buildin_getareadropitem(struct script_state *st); +int buildin_enablenpc(struct script_state *st); +int buildin_disablenpc(struct script_state *st); +int buildin_enablearena(struct script_state *st); // Added by RoVeRT +int buildin_disablearena(struct script_state *st); // Added by RoVeRT +int buildin_hideoffnpc(struct script_state *st); +int buildin_hideonnpc(struct script_state *st); +int buildin_sc_start(struct script_state *st); +int buildin_sc_start2(struct script_state *st); +int buildin_sc_end(struct script_state *st); +int buildin_getscrate(struct script_state *st); +int buildin_debugmes(struct script_state *st); +int buildin_catchpet(struct script_state *st); +int buildin_birthpet(struct script_state *st); +int buildin_resetlvl(struct script_state *st); +int buildin_resetstatus(struct script_state *st); +int buildin_resetskill(struct script_state *st); +int buildin_changebase(struct script_state *st); +int buildin_changesex(struct script_state *st); +int buildin_waitingroom(struct script_state *st); +int buildin_delwaitingroom(struct script_state *st); +int buildin_enablewaitingroomevent(struct script_state *st); +int buildin_disablewaitingroomevent(struct script_state *st); +int buildin_getwaitingroomstate(struct script_state *st); +int buildin_warpwaitingpc(struct script_state *st); +int buildin_attachrid(struct script_state *st); +int buildin_detachrid(struct script_state *st); +int buildin_isloggedin(struct script_state *st); +int buildin_setmapflagnosave(struct script_state *st); +int buildin_setmapflag(struct script_state *st); +int buildin_removemapflag(struct script_state *st); +int buildin_pvpon(struct script_state *st); +int buildin_pvpoff(struct script_state *st); +int buildin_gvgon(struct script_state *st); +int buildin_gvgoff(struct script_state *st); +int buildin_emotion(struct script_state *st); +int buildin_maprespawnguildid(struct script_state *st); +int buildin_agitstart(struct script_state *st); // <Agit> +int buildin_agitend(struct script_state *st); +int buildin_agitcheck(struct script_state *st); // <Agitcheck> +int buildin_flagemblem(struct script_state *st); // Flag Emblem +int buildin_getcastlename(struct script_state *st); +int buildin_getcastledata(struct script_state *st); +int buildin_setcastledata(struct script_state *st); +int buildin_requestguildinfo(struct script_state *st); +int buildin_getequipcardcnt(struct script_state *st); +int buildin_successremovecards(struct script_state *st); +int buildin_failedremovecards(struct script_state *st); +int buildin_marriage(struct script_state *st); +int buildin_wedding_effect(struct script_state *st); +int buildin_divorce(struct script_state *st); +int buildin_getitemname(struct script_state *st); +int buildin_makepet(struct script_state *st); +int buildin_getexp(struct script_state *st); +int buildin_getinventorylist(struct script_state *st); +int buildin_getskilllist(struct script_state *st); +int buildin_clearitem(struct script_state *st); +int buildin_classchange(struct script_state *st); +int buildin_misceffect(struct script_state *st); +int buildin_soundeffect(struct script_state *st); +int buildin_setcastledata(struct script_state *st); +int buildin_mapwarp(struct script_state *st); +int buildin_inittimer(struct script_state *st); +int buildin_stoptimer(struct script_state *st); +int buildin_cmdothernpc(struct script_state *st); +int buildin_mobcount(struct script_state *st); +int buildin_strmobinfo(struct script_state *st); // Script for displaying mob info [Valaris] +int buildin_guardian(struct script_state *st); // Script for displaying mob info [Valaris] +int buildin_guardianinfo(struct script_state *st); // Script for displaying mob info [Valaris] +int buildin_petskillbonus(struct script_state *st); // petskillbonus [Valaris] +int buildin_petrecovery(struct script_state *st); // pet skill for curing status [Valaris] +int buildin_petloot(struct script_state *st); // pet looting [Valaris] +int buildin_petheal(struct script_state *st); // pet healing [Valaris] +int buildin_petmag(struct script_state *st); // pet magnificat [Valaris] +int buildin_petskillattack(struct script_state *st); // pet skill attacks [Valaris] +int buildin_npcskilleffect(struct script_state *st); // skill effects for npcs [Valaris] +int buildin_specialeffect(struct script_state *st); // special effect script [Valaris] +int buildin_specialeffect2(struct script_state *st); // special effect script [Valaris] +int buildin_nude(struct script_state *st); // nude [Valaris] +int buildin_gmcommand(struct script_state *st); // [MouseJstr] +int buildin_movenpc(struct script_state *st); // [MouseJstr] +int buildin_message(struct script_state *st); // [MouseJstr] +int buildin_npctalk(struct script_state *st); // [Valaris] +int buildin_hasitems(struct script_state *st); // [Valaris] +int buildin_getlook(struct script_state *st); //Lorky [Lupus] +int buildin_getsavepoint(struct script_state *st); //Lorky [Lupus] + + +void push_val(struct script_stack *stack,int type,int val); +int run_func(struct script_state *st); + +int mapreg_setreg(int num,int val); +int mapreg_setregstr(int num,const char *str); + +struct { + int (*func)(); + char *name; + char *arg; +} buildin_func[]={ + {buildin_mes,"mes","s"}, + {buildin_next,"next",""}, + {buildin_close,"close",""}, + {buildin_close2,"close2",""}, + {buildin_menu,"menu","*"}, + {buildin_goto,"goto","l"}, + {buildin_callsub,"callsub","i*"}, + {buildin_callfunc,"callfunc","s*"}, + {buildin_return,"return","*"}, + {buildin_getarg,"getarg","i"}, + {buildin_jobchange,"jobchange","i*"}, + {buildin_input,"input","*"}, + {buildin_warp,"warp","sii"}, + {buildin_areawarp,"areawarp","siiiisii"}, + {buildin_setlook,"setlook","ii"}, + {buildin_set,"set","ii"}, + {buildin_setarray,"setarray","ii*"}, + {buildin_cleararray,"cleararray","iii"}, + {buildin_copyarray,"copyarray","iii"}, + {buildin_getarraysize,"getarraysize","i"}, + {buildin_deletearray,"deletearray","ii"}, + {buildin_getelementofarray,"getelementofarray","ii"}, + {buildin_if,"if","i*"}, + {buildin_getitem,"getitem","ii**"}, + {buildin_getitem2,"getitem2","iiiiiiiii*"}, + {buildin_makeitem,"makeitem","iisii"}, + {buildin_delitem,"delitem","ii"}, + {buildin_cutin,"cutin","si"}, + {buildin_cutincard,"cutincard","i"}, + {buildin_viewpoint,"viewpoint","iiiii"}, + {buildin_heal,"heal","ii"}, + {buildin_itemheal,"itemheal","ii"}, + {buildin_percentheal,"percentheal","ii"}, + {buildin_rand,"rand","i*"}, + {buildin_countitem,"countitem","i"}, + {buildin_checkweight,"checkweight","ii"}, + {buildin_readparam,"readparam","i*"}, + {buildin_getcharid,"getcharid","i*"}, + {buildin_getpartyname,"getpartyname","i"}, + {buildin_getpartymember,"getpartymember","i"}, + {buildin_getguildname,"getguildname","i"}, + {buildin_getguildmaster,"getguildmaster","i"}, + {buildin_getguildmasterid,"getguildmasterid","i"}, + {buildin_strcharinfo,"strcharinfo","i"}, + {buildin_getequipid,"getequipid","i"}, + {buildin_getequipname,"getequipname","i"}, + {buildin_getbrokenid,"getbrokenid","i"}, // [Valaris] + {buildin_repair,"repair","i"}, // [Valaris] + {buildin_getequipisequiped,"getequipisequiped","i"}, + {buildin_getequipisenableref,"getequipisenableref","i"}, + {buildin_getequipisidentify,"getequipisidentify","i"}, + {buildin_getequiprefinerycnt,"getequiprefinerycnt","i"}, + {buildin_getequipweaponlv,"getequipweaponlv","i"}, + {buildin_getequippercentrefinery,"getequippercentrefinery","i"}, + {buildin_successrefitem,"successrefitem","i"}, + {buildin_failedrefitem,"failedrefitem","i"}, + {buildin_statusup,"statusup","i"}, + {buildin_statusup2,"statusup2","ii"}, + {buildin_bonus,"bonus","ii"}, + {buildin_bonus2,"bonus2","iii"}, + {buildin_bonus3,"bonus3","iiii"}, + {buildin_skill,"skill","ii*"}, + {buildin_guildskill,"guildskill","ii"}, + {buildin_getskilllv,"getskilllv","i"}, + {buildin_getgdskilllv,"getgdskilllv","ii"}, + {buildin_basicskillcheck,"basicskillcheck","*"}, + {buildin_getgmlevel,"getgmlevel","*"}, + {buildin_end,"end",""}, + {buildin_end,"break",""}, + {buildin_checkoption,"checkoption","i"}, + {buildin_setoption,"setoption","i"}, + {buildin_setcart,"setcart",""}, + {buildin_checkcart,"checkcart","*"}, //fixed by Lupus (added '*') + {buildin_setfalcon,"setfalcon",""}, + {buildin_checkfalcon,"checkfalcon","*"}, //fixed by Lupus (fixed wrong pointer, added '*') + {buildin_setriding,"setriding",""}, + {buildin_checkriding,"checkriding","*"}, //fixed by Lupus (fixed wrong pointer, added '*') + {buildin_savepoint,"save","sii"}, + {buildin_savepoint,"savepoint","sii"}, + {buildin_gettimetick,"gettimetick","i"}, + {buildin_gettime,"gettime","i"}, + {buildin_gettimestr,"gettimestr","si"}, + {buildin_openstorage,"openstorage",""}, + {buildin_guildopenstorage,"guildopenstorage","*"}, + {buildin_itemskill,"itemskill","iis"}, + {buildin_produce,"produce","i"}, + {buildin_monster,"monster","siisii*"}, + {buildin_areamonster,"areamonster","siiiisii*"}, + {buildin_killmonster,"killmonster","ss"}, + {buildin_killmonsterall,"killmonsterall","s"}, + {buildin_doevent,"doevent","s"}, + {buildin_donpcevent,"donpcevent","s"}, + {buildin_addtimer,"addtimer","is"}, + {buildin_deltimer,"deltimer","s"}, + {buildin_addtimercount,"addtimercount","si"}, + {buildin_initnpctimer,"initnpctimer","*"}, + {buildin_stopnpctimer,"stopnpctimer","*"}, + {buildin_startnpctimer,"startnpctimer","*"}, + {buildin_setnpctimer,"setnpctimer","*"}, + {buildin_getnpctimer,"getnpctimer","i*"}, + {buildin_announce,"announce","si"}, + {buildin_mapannounce,"mapannounce","ssi"}, + {buildin_areaannounce,"areaannounce","siiiisi"}, + {buildin_getusers,"getusers","i"}, + {buildin_getmapusers,"getmapusers","s"}, + {buildin_getareausers,"getareausers","siiii"}, + {buildin_getareadropitem,"getareadropitem","siiiii"}, + {buildin_enablenpc,"enablenpc","s"}, + {buildin_disablenpc,"disablenpc","s"}, + {buildin_enablearena,"enablearena",""}, // Added by RoVeRT + {buildin_disablearena,"disablearena",""}, // Added by RoVeRT + {buildin_hideoffnpc,"hideoffnpc","s"}, + {buildin_hideonnpc,"hideonnpc","s"}, + {buildin_sc_start,"sc_start","iii*"}, + {buildin_sc_start2,"sc_start2","iiii*"}, + {buildin_sc_end,"sc_end","i"}, + {buildin_getscrate,"getscrate","ii*"}, + {buildin_debugmes,"debugmes","s"}, + {buildin_catchpet,"pet","i"}, + {buildin_birthpet,"bpet",""}, + {buildin_resetlvl,"resetlvl","i"}, + {buildin_resetstatus,"resetstatus",""}, + {buildin_resetskill,"resetskill",""}, + {buildin_changebase,"changebase","i"}, + {buildin_changesex,"changesex",""}, + {buildin_waitingroom,"waitingroom","si*"}, + {buildin_warpwaitingpc,"warpwaitingpc","sii"}, + {buildin_delwaitingroom,"delwaitingroom","*"}, + {buildin_enablewaitingroomevent,"enablewaitingroomevent","*"}, + {buildin_disablewaitingroomevent,"disablewaitingroomevent","*"}, + {buildin_getwaitingroomstate,"getwaitingroomstate","i*"}, + {buildin_warpwaitingpc,"warpwaitingpc","sii*"}, + {buildin_attachrid,"attachrid","i"}, + {buildin_detachrid,"detachrid",""}, + {buildin_isloggedin,"isloggedin","i"}, + {buildin_setmapflagnosave,"setmapflagnosave","ssii"}, + {buildin_setmapflag,"setmapflag","si"}, + {buildin_removemapflag,"removemapflag","si"}, + {buildin_pvpon,"pvpon","s"}, + {buildin_pvpoff,"pvpoff","s"}, + {buildin_gvgon,"gvgon","s"}, + {buildin_gvgoff,"gvgoff","s"}, + {buildin_emotion,"emotion","i"}, + {buildin_maprespawnguildid,"maprespawnguildid","sii"}, + {buildin_agitstart,"agitstart",""}, // <Agit> + {buildin_agitend,"agitend",""}, + {buildin_agitcheck,"agitcheck","i"}, // <Agitcheck> + {buildin_flagemblem,"flagemblem","i"}, // Flag Emblem + {buildin_getcastlename,"getcastlename","s"}, + {buildin_getcastledata,"getcastledata","si*"}, + {buildin_setcastledata,"setcastledata","sii"}, + {buildin_requestguildinfo,"requestguildinfo","i*"}, + {buildin_getequipcardcnt,"getequipcardcnt","i"}, + {buildin_successremovecards,"successremovecards","i"}, + {buildin_failedremovecards,"failedremovecards","ii"}, + {buildin_marriage,"marriage","s"}, + {buildin_wedding_effect,"wedding",""}, + {buildin_divorce,"divorce",""}, + {buildin_getitemname,"getitemname","i"}, + {buildin_makepet,"makepet","i"}, + {buildin_getexp,"getexp","ii"}, + {buildin_getinventorylist,"getinventorylist",""}, + {buildin_getskilllist,"getskilllist",""}, + {buildin_clearitem,"clearitem",""}, + {buildin_classchange,"classchange","ii"}, + {buildin_misceffect,"misceffect","i"}, + {buildin_soundeffect,"soundeffect","si"}, + {buildin_strmobinfo,"strmobinfo","ii"}, // display mob data [Valaris] + {buildin_guardian,"guardian","siisii*i"}, // summon guardians + {buildin_guardianinfo,"guardianinfo","i"}, // display guardian data [Valaris] + {buildin_petskillbonus,"petskillbonus","iiii"}, // [Valaris] + {buildin_petrecovery,"petrecovery","ii"}, // [Valaris] + {buildin_petloot,"petloot","i"}, // [Valaris] + {buildin_petheal,"petheal","iii"}, // [Valaris] + {buildin_petmag,"petmag","iiii"}, // [Valaris] + {buildin_petskillattack,"petskillattack","iiii"}, // [Valaris] + {buildin_npcskilleffect,"npcskilleffect","iiii"}, // npc skill effect [Valaris] + {buildin_specialeffect,"specialeffect","i"}, // npc skill effect [Valaris] + {buildin_specialeffect2,"specialeffect2","i"}, // skill effect on players[Valaris] + {buildin_nude,"nude",""}, // nude command [Valaris] + {buildin_mapwarp,"mapwarp","ssii"}, // Added by RoVeRT + {buildin_inittimer,"inittimer",""}, + {buildin_stoptimer,"stoptimer",""}, + {buildin_cmdothernpc,"cmdothernpc","ss"}, + {buildin_gmcommand,"gmcommand","*"}, // [MouseJstr] +// {buildin_movenpc,"movenpc","siis"}, // [MouseJstr] + {buildin_message,"message","s*"}, // [MouseJstr] + {buildin_npctalk,"npctalk","*"}, // [Valaris] + {buildin_hasitems,"hasitems","*"}, // [Valaris] + {buildin_mobcount,"mobcount","ss"}, + {buildin_getlook,"getlook","i"}, + {buildin_getsavepoint,"getsavepoint","i"}, // End Additions + {NULL,NULL,NULL}, +}; +int buildin_message(struct script_state *st); // [MouseJstr] + + +enum { + C_NOP,C_POS,C_INT,C_PARAM,C_FUNC,C_STR,C_CONSTSTR,C_ARG, + C_NAME,C_EOL, C_RETINFO, + + C_LOR,C_LAND,C_LE,C_LT,C_GE,C_GT,C_EQ,C_NE, //operator + C_XOR,C_OR,C_AND,C_ADD,C_SUB,C_MUL,C_DIV,C_MOD,C_NEG,C_LNOT,C_NOT,C_R_SHIFT,C_L_SHIFT +}; + +/*========================================== + * 文字列のハッシュを計算 + *------------------------------------------ + */ +static int calc_hash(const unsigned char *p) +{ + int h=0; + while(*p){ + h=(h<<1)+(h>>3)+(h>>5)+(h>>8); + h+=*p++; + } + return h&15; +} + +/*========================================== + * str_dataの中に名前があるか検索する + *------------------------------------------ + */ +// 既存のであれば番号、無ければ-1 +static int search_str(const unsigned char *p) +{ + int i; + i=str_hash[calc_hash(p)]; + while(i){ + if(strcmp(str_buf+str_data[i].str,p)==0){ + return i; + } + i=str_data[i].next; + } + return -1; +} + +/*========================================== + * str_dataに名前を登録 + *------------------------------------------ + */ +// 既存のであれば番号、無ければ登録して新規番号 +static int add_str(const unsigned char *p) +{ + int i; + char *lowcase; + + lowcase=strdup(p); + for(i=0;lowcase[i];i++) + lowcase[i]=tolower(lowcase[i]); + if((i=search_str(lowcase))>=0){ + free(lowcase); + return i; + } + free(lowcase); + + i=calc_hash(p); + if(str_hash[i]==0){ + str_hash[i]=str_num; + } else { + i=str_hash[i]; + for(;;){ + if(strcmp(str_buf+str_data[i].str,p)==0){ + return i; + } + if(str_data[i].next==0) + break; + i=str_data[i].next; + } + str_data[i].next=str_num; + } + if(str_num>=str_data_size){ + str_data_size+=128; + str_data=aRealloc(str_data,sizeof(str_data[0])*str_data_size); + memset(str_data + (str_data_size - 128), '\0', 128); + } + while(str_pos+strlen(p)+1>=str_size){ + str_size+=256; + str_buf=(char *)aRealloc(str_buf,str_size); + memset(str_buf + (str_size - 256), '\0', 256); + } + strcpy(str_buf+str_pos,p); + str_data[str_num].type=C_NOP; + str_data[str_num].str=str_pos; + str_data[str_num].next=0; + str_data[str_num].func=NULL; + str_data[str_num].backpatch=-1; + str_data[str_num].label=-1; + str_pos+=strlen(p)+1; + return str_num++; +} + + +/*========================================== + * スクリプトバッファサイズの確認と拡張 + *------------------------------------------ + */ +static void check_script_buf(int size) +{ + if(script_pos+size>=script_size){ + script_size+=SCRIPT_BLOCK_SIZE; + script_buf=(char *)aRealloc(script_buf,script_size); + memset(script_buf + script_size - SCRIPT_BLOCK_SIZE, '\0', + SCRIPT_BLOCK_SIZE); + } +} + +/*========================================== + * スクリプトバッファに1バイト書き込む + *------------------------------------------ + */ +static void add_scriptb(int a) +{ + check_script_buf(1); + script_buf[script_pos++]=a; +} + +/*========================================== + * スクリプトバッファにデータタイプを書き込む + *------------------------------------------ + */ +static void add_scriptc(int a) +{ + while(a>=0x40){ + add_scriptb((a&0x3f)|0x40); + a=(a-0x40)>>6; + } + add_scriptb(a&0x3f); +} + +/*========================================== + * スクリプトバッファに整数を書き込む + *------------------------------------------ + */ +static void add_scripti(int a) +{ + while(a>=0x40){ + add_scriptb(a|0xc0); + a=(a-0x40)>>6; + } + add_scriptb(a|0x80); +} + +/*========================================== + * スクリプトバッファにラベル/変数/関数を書き込む + *------------------------------------------ + */ +// 最大16Mまで +static void add_scriptl(int l) +{ + int backpatch = str_data[l].backpatch; + + switch(str_data[l].type){ + case C_POS: + add_scriptc(C_POS); + add_scriptb(str_data[l].label); + add_scriptb(str_data[l].label>>8); + add_scriptb(str_data[l].label>>16); + break; + case C_NOP: + // ラベルの可能性があるのでbackpatch用データ埋め込み + add_scriptc(C_NAME); + str_data[l].backpatch=script_pos; + add_scriptb(backpatch); + add_scriptb(backpatch>>8); + add_scriptb(backpatch>>16); + break; + case C_INT: + add_scripti(str_data[l].val); + break; + default: + // もう他の用途と確定してるので数字をそのまま + add_scriptc(C_NAME); + add_scriptb(l); + add_scriptb(l>>8); + add_scriptb(l>>16); + break; + } +} + +/*========================================== + * ラベルを解決する + *------------------------------------------ + */ +void set_label(int l,int pos) +{ + int i,next; + + str_data[l].type=C_POS; + str_data[l].label=pos; + for(i=str_data[l].backpatch;i>=0 && i!=0x00ffffff;){ + next=(*(int*)(script_buf+i)) & 0x00ffffff; + script_buf[i-1]=C_POS; + script_buf[i]=pos; + script_buf[i+1]=pos>>8; + script_buf[i+2]=pos>>16; + i=next; + } +} + +/*========================================== + * スペース/コメント読み飛ばし + *------------------------------------------ + */ +static unsigned char *skip_space(unsigned char *p) +{ + while(1){ + while(isspace(*p)) + p++; + if(p[0]=='/' && p[1]=='/'){ + while(*p && *p!='\n') + p++; + } else if(p[0]=='/' && p[1]=='*'){ + p++; + while(*p && (p[-1]!='*' || p[0]!='/')) + p++; + if(*p) p++; + } else + break; + } + return p; +} + +/*========================================== + * 1単語スキップ + *------------------------------------------ + */ +static unsigned char *skip_word(unsigned char *p) +{ + // prefix + if(*p=='$') p++; // MAP鯖内共有変数用 + if(*p=='@') p++; // 一時的変数用(like weiss) + if(*p=='#') p++; // account変数用 + if(*p=='#') p++; // ワールドaccount変数用 + if(*p=='l') p++; // 一時的変数用(like weiss) + + while(isalnum(*p)||*p=='_'|| *p>=0x81) + if(*p>=0x81 && p[1]){ + p+=2; + } else + p++; + + // postfix + if(*p=='$') p++; // 文字列変数 + + return p; +} + +static unsigned char *startptr; +static int startline; + +/*========================================== + * エラーメッセージ出力 + *------------------------------------------ + */ +static void disp_error_message(const char *mes,const unsigned char *pos) +{ + int line,c=0,i; + unsigned char *p,*linestart,*lineend; + + for(line=startline,p=startptr;p && *p;line++){ + linestart=p; + lineend=strchr(p,'\n'); + if(lineend){ + c=*lineend; + *lineend=0; + } + if(lineend==NULL || pos<lineend){ + printf("%s line %d : ",mes,line); + for(i=0;(linestart[i]!='\r') && (linestart[i]!='\n') && linestart[i];i++){ + if(linestart+i!=pos) + printf("%c",linestart[i]); + else + printf("\'%c\'",linestart[i]); + } + printf("\a\n"); + if(lineend) + *lineend=c; + return; + } + *lineend=c; + p=lineend+1; + } +} + +/*========================================== + * 項の解析 + *------------------------------------------ + */ +unsigned char* parse_simpleexpr(unsigned char *p) +{ + int i; + p=skip_space(p); + +#ifdef DEBUG_FUNCIN + if(battle_config.etc_log) + printf("parse_simpleexpr %s\n",p); +#endif + if(*p==';' || *p==','){ + disp_error_message("unexpected expr end",p); + exit(1); + } + if(*p=='('){ + + p=parse_subexpr(p+1,-1); + p=skip_space(p); + if((*p++)!=')'){ + disp_error_message("unmatch ')'",p); + exit(1); + } + } else if(isdigit(*p) || ((*p=='-' || *p=='+') && isdigit(p[1]))){ + char *np; + i=strtoul(p,&np,0); + add_scripti(i); + p=np; + } else if(*p=='"'){ + add_scriptc(C_STR); + p++; + while(*p && *p!='"'){ + if(p[-1]<=0x7e && *p=='\\') + p++; + else if(*p=='\n'){ + disp_error_message("unexpected newline @ string",p); + exit(1); + } + add_scriptb(*p++); + } + if(!*p){ + disp_error_message("unexpected eof @ string",p); + exit(1); + } + add_scriptb(0); + p++; //'"' + } else { + int c,l; + char *p2; + // label , register , function etc + if(skip_word(p)==p){ + disp_error_message("unexpected character",p); + exit(1); + } + p2=skip_word(p); + c=*p2; *p2=0; // 名前をadd_strする + l=add_str(p); + + parse_cmd=l; // warn_*_mismatch_paramnumのために必要 + if(l==search_str("if")) // warn_cmd_no_commaのために必要 + parse_cmd_if++; +/* + // 廃止予定のl14/l15,およびプレフィックスlの警告 + if( strcmp(str_buf+str_data[l].str,"l14")==0 || + strcmp(str_buf+str_data[l].str,"l15")==0 ){ + disp_error_message("l14 and l15 is DEPRECATED. use @menu instead of l15.",p); + }else if(str_buf[str_data[l].str]=='l'){ + disp_error_message("prefix 'l' is DEPRECATED. use prefix '@' instead.",p2); + } +*/ + *p2=c; p=p2; + + if(str_data[l].type!=C_FUNC && c=='['){ + // array(name[i] => getelementofarray(name,i) ) + add_scriptl(search_str("getelementofarray")); + add_scriptc(C_ARG); + add_scriptl(l); + p=parse_subexpr(p+1,-1); + p=skip_space(p); + if((*p++)!=']'){ + disp_error_message("unmatch ']'",p); + exit(1); + } + add_scriptc(C_FUNC); + }else + add_scriptl(l); + + } + +#ifdef DEBUG_FUNCIN + if(battle_config.etc_log) + printf("parse_simpleexpr end %s\n",p); +#endif + return p; +} + +/*========================================== + * 式の解析 + *------------------------------------------ + */ +unsigned char* parse_subexpr(unsigned char *p,int limit) +{ + int op,opl,len; + char *tmpp; + +#ifdef DEBUG_FUNCIN + if(battle_config.etc_log) + printf("parse_subexpr %s\n",p); +#endif + p=skip_space(p); + + if(*p=='-'){ + tmpp=skip_space(p+1); + if(*tmpp==';' || *tmpp==','){ + add_scriptl(LABEL_NEXTLINE); + p++; + return p; + } + } + tmpp=p; + if((op=C_NEG,*p=='-') || (op=C_LNOT,*p=='!') || (op=C_NOT,*p=='~')){ + p=parse_subexpr(p+1,100); + add_scriptc(op); + } else + p=parse_simpleexpr(p); + p=skip_space(p); + while(((op=C_ADD,opl=6,len=1,*p=='+') || + (op=C_SUB,opl=6,len=1,*p=='-') || + (op=C_MUL,opl=7,len=1,*p=='*') || + (op=C_DIV,opl=7,len=1,*p=='/') || + (op=C_MOD,opl=7,len=1,*p=='%') || + (op=C_FUNC,opl=8,len=1,*p=='(') || + (op=C_LAND,opl=1,len=2,*p=='&' && p[1]=='&') || + (op=C_AND,opl=5,len=1,*p=='&') || + (op=C_LOR,opl=0,len=2,*p=='|' && p[1]=='|') || + (op=C_OR,opl=4,len=1,*p=='|') || + (op=C_XOR,opl=3,len=1,*p=='^') || + (op=C_EQ,opl=2,len=2,*p=='=' && p[1]=='=') || + (op=C_NE,opl=2,len=2,*p=='!' && p[1]=='=') || + (op=C_R_SHIFT,opl=5,len=2,*p=='>' && p[1]=='>') || + (op=C_GE,opl=2,len=2,*p=='>' && p[1]=='=') || + (op=C_GT,opl=2,len=1,*p=='>') || + (op=C_L_SHIFT,opl=5,len=2,*p=='<' && p[1]=='<') || + (op=C_LE,opl=2,len=2,*p=='<' && p[1]=='=') || + (op=C_LT,opl=2,len=1,*p=='<')) && opl>limit){ + p+=len; + if(op==C_FUNC){ + int i=0,func=parse_cmd; + const char *plist[128]; + + if( str_data[func].type!=C_FUNC ){ + disp_error_message("expect function",tmpp); + exit(0); + } + + add_scriptc(C_ARG); + do { + plist[i]=p; + p=parse_subexpr(p,-1); + p=skip_space(p); + if(*p==',') p++; + else if(*p!=')' && script_config.warn_func_no_comma){ + disp_error_message("expect ',' or ')' at func params",p); + } + p=skip_space(p); + i++; + } while(*p && *p!=')' && i<128); + plist[i]=p; + if(*(p++)!=')'){ + disp_error_message("func request '(' ')'",p); + exit(1); + } + + if( str_data[func].type==C_FUNC && script_config.warn_func_mismatch_paramnum){ + const char *arg=buildin_func[str_data[func].val].arg; + int j=0; + for(j=0;arg[j];j++) if(arg[j]=='*')break; + if( (arg[j]==0 && i!=j) || (arg[j]=='*' && i<j) ){ + disp_error_message("illegal number of parameters",plist[(i<j)?i:j]); + } + } + } else { + p=parse_subexpr(p,opl); + } + add_scriptc(op); + p=skip_space(p); + } +#ifdef DEBUG_FUNCIN + if(battle_config.etc_log) + printf("parse_subexpr end %s\n",p); +#endif + return p; /* return first untreated operator */ +} + +/*========================================== + * 式の評価 + *------------------------------------------ + */ +unsigned char* parse_expr(unsigned char *p) +{ +#ifdef DEBUG_FUNCIN + if(battle_config.etc_log) + printf("parse_expr %s\n",p); +#endif + switch(*p){ + case ')': case ';': case ':': case '[': case ']': + case '}': + disp_error_message("unexpected char",p); + exit(1); + } + p=parse_subexpr(p,-1); +#ifdef DEBUG_FUNCIN + if(battle_config.etc_log) + printf("parse_expr end %s\n",p); +#endif + return p; +} + +/*========================================== + * 行の解析 + *------------------------------------------ + */ +unsigned char* parse_line(unsigned char *p) +{ + int i=0,cmd; + const char *plist[128]; + char *p2; + + p=skip_space(p); + if(*p==';') + return p; + + parse_cmd_if=0; // warn_cmd_no_commaのために必要 + + // 最初は関数名 + p2=p; + p=parse_simpleexpr(p); + p=skip_space(p); + + cmd=parse_cmd; + if( str_data[cmd].type!=C_FUNC ){ + disp_error_message("expect command",p2); +// exit(0); + } + + add_scriptc(C_ARG); + while(p && *p && *p!=';' && i<128){ + plist[i]=p; + + p=parse_expr(p); + p=skip_space(p); + // 引数区切りの,処理 + if(*p==',') p++; + else if(*p!=';' && script_config.warn_cmd_no_comma && parse_cmd_if*2<=i ){ + disp_error_message("expect ',' or ';' at cmd params",p); + } + p=skip_space(p); + i++; + } + plist[i]=p; + if(!p || *(p++)!=';'){ + disp_error_message("need ';'",p); + exit(1); + } + add_scriptc(C_FUNC); + + if( str_data[cmd].type==C_FUNC && script_config.warn_cmd_mismatch_paramnum){ + const char *arg=buildin_func[str_data[cmd].val].arg; + int j=0; + for(j=0;arg[j];j++) if(arg[j]=='*')break; + if( (arg[j]==0 && i!=j) || (arg[j]=='*' && i<j) ){ + disp_error_message("illegal number of parameters",plist[(i<j)?i:j]); + } + } + + + return p; +} + +/*========================================== + * 組み込み関数の追加 + *------------------------------------------ + */ +static void add_buildin_func(void) +{ + int i,n; + for(i=0;buildin_func[i].func;i++){ + n=add_str(buildin_func[i].name); + str_data[n].type=C_FUNC; + str_data[n].val=i; + str_data[n].func=buildin_func[i].func; + } +} + +/*========================================== + * 定数データベースの読み込み + *------------------------------------------ + */ +static void read_constdb(void) +{ + FILE *fp; + char line[1024],name[1024]; + int val,n,i,type; + + fp=fopen("db/const.txt","r"); + if(fp==NULL){ + printf("can't read db/const.txt\n"); + return ; + } + while(fgets(line,1020,fp)){ + if(line[0]=='/' && line[1]=='/') + continue; + type=0; + if(sscanf(line,"%[A-Za-z0-9_],%d,%d",name,&val,&type)>=2 || + sscanf(line,"%[A-Za-z0-9_] %d %d",name,&val,&type)>=2){ + for(i=0;name[i];i++) + name[i]=tolower(name[i]); + n=add_str(name); + if(type==0) + str_data[n].type=C_INT; + else + str_data[n].type=C_PARAM; + str_data[n].val=val; + } + } + fclose(fp); +} + +/*========================================== + * スクリプトの解析 + *------------------------------------------ + */ +unsigned char* parse_script(unsigned char *src,int line) +{ + unsigned char *p,*tmpp; + int i; + static int first=1; + + if(first){ + add_buildin_func(); + read_constdb(); + } + first=0; + script_buf=(unsigned char *)aCalloc(SCRIPT_BLOCK_SIZE,sizeof(unsigned char)); + script_pos=0; + script_size=SCRIPT_BLOCK_SIZE; + str_data[LABEL_NEXTLINE].type=C_NOP; + str_data[LABEL_NEXTLINE].backpatch=-1; + str_data[LABEL_NEXTLINE].label=-1; + for(i=LABEL_START;i<str_num;i++){ + if(str_data[i].type==C_POS || str_data[i].type==C_NAME){ + str_data[i].type=C_NOP; + str_data[i].backpatch=-1; + str_data[i].label=-1; + } + } + + // 外部用label dbの初期化 + if(scriptlabel_db!=NULL) + strdb_final(scriptlabel_db,scriptlabel_final); + scriptlabel_db=strdb_init(50); + + // for error message + startptr = src; + startline = line; + + p=src; + p=skip_space(p); + if(*p!='{'){ + disp_error_message("not found '{'",p); + return NULL; + } + for(p++;p && *p && *p!='}';){ + p=skip_space(p); + // labelだけ特殊処理 + tmpp=skip_space(skip_word(p)); + if(*tmpp==':'){ + int l,c; + + c=*skip_word(p); + *skip_word(p)=0; + l=add_str(p); + if(str_data[l].label!=-1){ + *skip_word(p)=c; + disp_error_message("dup label ",p); + exit(1); + } + set_label(l,script_pos); + strdb_insert(scriptlabel_db,p,script_pos); // 外部用label db登録 + *skip_word(p)=c; + p=tmpp+1; + continue; + } + + // 他は全部一緒くた + p=parse_line(p); + p=skip_space(p); + add_scriptc(C_EOL); + + set_label(LABEL_NEXTLINE,script_pos); + str_data[LABEL_NEXTLINE].type=C_NOP; + str_data[LABEL_NEXTLINE].backpatch=-1; + str_data[LABEL_NEXTLINE].label=-1; + } + + add_scriptc(C_NOP); + + script_size = script_pos; + script_buf=(char *)aRealloc(script_buf,script_pos + 1); + + // 未解決のラベルを解決 + for(i=LABEL_START;i<str_num;i++){ + if(str_data[i].type==C_NOP){ + int j,next; + str_data[i].type=C_NAME; + str_data[i].label=i; + for(j=str_data[i].backpatch;j>=0 && j!=0x00ffffff;){ + next=(*(int*)(script_buf+j)) & 0x00ffffff; + script_buf[j]=i; + script_buf[j+1]=i>>8; + script_buf[j+2]=i>>16; + j=next; + } + } + } + +#ifdef DEBUG_DISP + for(i=0;i<script_pos;i++){ + if((i&15)==0) printf("%04x : ",i); + printf("%02x ",script_buf[i]); + if((i&15)==15) printf("\n"); + } + printf("\n"); +#endif + + return script_buf; +} + +// +// 実行系 +// +enum {STOP=1,END,RERUNLINE,GOTO,RETFUNC}; + +/*========================================== + * ridからsdへの解決 + *------------------------------------------ + */ +struct map_session_data *script_rid2sd(struct script_state *st) +{ + struct map_session_data *sd=map_id2sd(st->rid); + if(!sd){ + printf("script_rid2sd: fatal error ! player not attached!\n"); + } + return sd; +} + + +/*========================================== + * 変数の読み取り + *------------------------------------------ + */ +int get_val(struct script_state*st,struct script_data* data) +{ + struct map_session_data *sd=NULL; + if(data->type==C_NAME){ + char *name=str_buf+str_data[data->u.num&0x00ffffff].str; + char prefix=*name; + char postfix=name[strlen(name)-1]; + + if(prefix!='$'){ + if((sd=script_rid2sd(st))==NULL) + printf("get_val error name?:%s\n",name); + } + if(postfix=='$'){ + + data->type=C_CONSTSTR; + if( prefix=='@' || prefix=='l' ){ + if(sd) + data->u.str = pc_readregstr(sd,data->u.num); + }else if(prefix=='$'){ + data->u.str = (char *)numdb_search(mapregstr_db,data->u.num); + }else{ + printf("script: get_val: illegal scope string variable.\n"); + data->u.str = "!!ERROR!!"; + } + if( data->u.str == NULL ) + data->u.str =""; + + }else{ + + data->type=C_INT; + if(str_data[data->u.num&0x00ffffff].type==C_INT){ + data->u.num = str_data[data->u.num&0x00ffffff].val; + }else if(str_data[data->u.num&0x00ffffff].type==C_PARAM){ + if(sd) + data->u.num = pc_readparam(sd,str_data[data->u.num&0x00ffffff].val); + }else if(prefix=='@' || prefix=='l'){ + if(sd) + data->u.num = pc_readreg(sd,data->u.num); + }else if(prefix=='$'){ + data->u.num = (int)numdb_search(mapreg_db,data->u.num); + }else if(prefix=='#'){ + if( name[1]=='#'){ + if(sd) + data->u.num = pc_readaccountreg2(sd,name); + }else{ + if(sd) + data->u.num = pc_readaccountreg(sd,name); + } + }else{ + if(sd) + data->u.num = pc_readglobalreg(sd,name); + } + } + } + return 0; +} +/*========================================== + * 変数の読み取り2 + *------------------------------------------ + */ +void* get_val2(struct script_state*st,int num) +{ + struct script_data dat; + dat.type=C_NAME; + dat.u.num=num; + get_val(st,&dat); + if( dat.type==C_INT ) return (void*)dat.u.num; + else return (void*)dat.u.str; +} + +/*========================================== + * 変数設定用 + *------------------------------------------ + */ +static int set_reg(struct map_session_data *sd,int num,char *name,void *v) +{ + char prefix=*name; + char postfix=name[strlen(name)-1]; + + if( postfix=='$' ){ + char *str=(char*)v; + if( prefix=='@' || prefix=='l'){ + pc_setregstr(sd,num,str); + }else if(prefix=='$') { + mapreg_setregstr(num,str); + }else{ + printf("script: set_reg: illegal scope string variable !"); + } + }else{ + // 数値 + int val = (int)v; + if(str_data[num&0x00ffffff].type==C_PARAM){ + pc_setparam(sd,str_data[num&0x00ffffff].val,val); + }else if(prefix=='@' || prefix=='l') { + pc_setreg(sd,num,val); + }else if(prefix=='$') { + mapreg_setreg(num,val); + }else if(prefix=='#') { + if( name[1]=='#' ) + pc_setaccountreg2(sd,name,val); + else + pc_setaccountreg(sd,name,val); + }else{ + pc_setglobalreg(sd,name,val); + } + } + return 0; +} + +/*========================================== + * 文字列への変換 + *------------------------------------------ + */ +char* conv_str(struct script_state *st,struct script_data *data) +{ + get_val(st,data); + if(data->type==C_INT){ + char *buf; + buf=(char *)aCalloc(16,sizeof(char)); + sprintf(buf,"%d",data->u.num); + data->type=C_STR; + data->u.str=buf; +#if 1 + } else if(data->type==C_NAME){ + // テンポラリ。本来無いはず + data->type=C_CONSTSTR; + data->u.str=str_buf+str_data[data->u.num].str; +#endif + } + return data->u.str; +} + +/*========================================== + * 数値へ変換 + *------------------------------------------ + */ +int conv_num(struct script_state *st,struct script_data *data) +{ + char *p; + get_val(st,data); + if(data->type==C_STR || data->type==C_CONSTSTR){ + p=data->u.str; + data->u.num = atoi(p); + if(data->type==C_STR) + free(p); + data->type=C_INT; + } + return data->u.num; +} + +/*========================================== + * スタックへ数値をプッシュ + *------------------------------------------ + */ +void push_val(struct script_stack *stack,int type,int val) +{ + if(stack->sp >= stack->sp_max){ + stack->sp_max += 64; + stack->stack_data = (struct script_data *)aRealloc(stack->stack_data, + sizeof(stack->stack_data[0]) * stack->sp_max); + memset(stack->stack_data + (stack->sp_max - 64), 0, + 64 * sizeof(*(stack->stack_data))); + } +// if(battle_config.etc_log) +// printf("push (%d,%d)-> %d\n",type,val,stack->sp); + stack->stack_data[stack->sp].type=type; + stack->stack_data[stack->sp].u.num=val; + stack->sp++; +} + +/*========================================== + * スタックへ文字列をプッシュ + *------------------------------------------ + */ +void push_str(struct script_stack *stack,int type,unsigned char *str) +{ + if(stack->sp>=stack->sp_max){ + stack->sp_max += 64; + stack->stack_data = (struct script_data *)aRealloc(stack->stack_data, + sizeof(stack->stack_data[0]) * stack->sp_max); + memset(stack->stack_data + (stack->sp_max - 64), '\0', + 64 * sizeof(*(stack->stack_data))); + } +// if(battle_config.etc_log) +// printf("push (%d,%x)-> %d\n",type,str,stack->sp); + stack->stack_data[stack->sp].type=type; + stack->stack_data[stack->sp].u.str=str; + stack->sp++; +} + +/*========================================== + * スタックへ複製をプッシュ + *------------------------------------------ + */ +void push_copy(struct script_stack *stack,int pos) +{ + switch(stack->stack_data[pos].type){ + case C_CONSTSTR: + push_str(stack,C_CONSTSTR,stack->stack_data[pos].u.str); + break; + case C_STR: + push_str(stack,C_STR,strdup(stack->stack_data[pos].u.str)); + break; + default: + push_val(stack,stack->stack_data[pos].type,stack->stack_data[pos].u.num); + break; + } +} + +/*========================================== + * スタックからポップ + *------------------------------------------ + */ +void pop_stack(struct script_stack* stack,int start,int end) +{ + int i; + for(i=start;i<end;i++){ + if(stack->stack_data[i].type==C_STR){ + free(stack->stack_data[i].u.str); + } + } + if(stack->sp>end){ + memmove(&stack->stack_data[start],&stack->stack_data[end],sizeof(stack->stack_data[0])*(stack->sp-end)); + } + stack->sp-=end-start; +} + +// +// 埋め込み関数 +// +/*========================================== + * + *------------------------------------------ + */ +int buildin_mes(struct script_state *st) +{ + conv_str(st,& (st->stack->stack_data[st->start+2])); + clif_scriptmes(script_rid2sd(st),st->oid,st->stack->stack_data[st->start+2].u.str); + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int buildin_goto(struct script_state *st) +{ + int pos; + + if( st->stack->stack_data[st->start+2].type!=C_POS ){ + printf("script: goto: not label !\n"); + st->state=END; + return 0; + } + + pos=conv_num(st,& (st->stack->stack_data[st->start+2])); + st->pos=pos; + st->state=GOTO; + return 0; +} + +/*========================================== + * ユーザー定義関数の呼び出し + *------------------------------------------ + */ +int buildin_callfunc(struct script_state *st) +{ + char *scr; + char *str=conv_str(st,& (st->stack->stack_data[st->start+2])); + + if( (scr=strdb_search(script_get_userfunc_db(),str)) ){ + int i,j; + for(i=st->start+3,j=0;i<st->end;i++,j++) + push_copy(st->stack,i); + + push_val(st->stack,C_INT,j); // 引数の数をプッシュ + push_val(st->stack,C_INT,st->defsp); // 現在の基準スタックポインタをプッシュ + push_val(st->stack,C_INT,(int)st->script); // 現在のスクリプトをプッシュ + push_val(st->stack,C_RETINFO,st->pos); // 現在のスクリプト位置をプッシュ + + st->pos=0; + st->script=scr; + st->defsp=st->start+4+j; + st->state=GOTO; + }else{ + printf("script:callfunc: function not found! [%s]\n",str); + st->state=END; + } + return 0; +} +/*========================================== + * サブルーティンの呼び出し + *------------------------------------------ + */ +int buildin_callsub(struct script_state *st) +{ + int pos=conv_num(st,& (st->stack->stack_data[st->start+2])); + int i,j; + for(i=st->start+3,j=0;i<st->end;i++,j++) + push_copy(st->stack,i); + + push_val(st->stack,C_INT,j); // 引数の数をプッシュ + push_val(st->stack,C_INT,st->defsp); // 現在の基準スタックポインタをプッシュ + push_val(st->stack,C_INT,(int)st->script); // 現在のスクリプトをプッシュ + push_val(st->stack,C_RETINFO,st->pos); // 現在のスクリプト位置をプッシュ + + st->pos=pos; + st->defsp=st->start+4+j; + st->state=GOTO; + return 0; +} + +/*========================================== + * 引数の所得 + *------------------------------------------ + */ +int buildin_getarg(struct script_state *st) +{ + int num=conv_num(st,& (st->stack->stack_data[st->start+2])); + int max,stsp; + if( st->defsp<4 || st->stack->stack_data[st->defsp-1].type!=C_RETINFO ){ + printf("script:getarg without callfunc or callsub!\n"); + st->state=END; + return 0; + } + max=conv_num(st,& (st->stack->stack_data[st->defsp-4])); + stsp=st->defsp - max -4; + if( num >= max ){ + printf("script:getarg arg1(%d) out of range(%d) !\n",num,max); + st->state=END; + return 0; + } + push_copy(st->stack,stsp+num); + return 0; +} + +/*========================================== + * サブルーチン/ユーザー定義関数の終了 + *------------------------------------------ + */ +int buildin_return(struct script_state *st) +{ + if(st->end>st->start+2){ // 戻り値有り + push_copy(st->stack,st->start+2); + } + st->state=RETFUNC; + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int buildin_next(struct script_state *st) +{ + st->state=STOP; + clif_scriptnext(script_rid2sd(st),st->oid); + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int buildin_close(struct script_state *st) +{ + st->state=END; + clif_scriptclose(script_rid2sd(st),st->oid); + return 0; +} +int buildin_close2(struct script_state *st) +{ + st->state=STOP; + clif_scriptclose(script_rid2sd(st),st->oid); + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int buildin_menu(struct script_state *st) +{ + char *buf; + int len,i; + struct map_session_data *sd; + + sd=script_rid2sd(st); + + if(sd->state.menu_or_input==0){ + st->state=RERUNLINE; + sd->state.menu_or_input=1; + for(i=st->start+2,len=16;i<st->end;i+=2){ + conv_str(st,& (st->stack->stack_data[i])); + len+=strlen(st->stack->stack_data[i].u.str)+1; + } + buf=(char *)aCalloc(len,sizeof(char)); + buf[0]=0; + for(i=st->start+2,len=0;i<st->end;i+=2){ + strcat(buf,st->stack->stack_data[i].u.str); + strcat(buf,":"); + } + clif_scriptmenu(script_rid2sd(st),st->oid,buf); + free(buf); + } else if(sd->npc_menu==0xff){ // cansel + sd->state.menu_or_input=0; + st->state=END; + } else { // goto動作 + // ragemu互換のため + pc_setreg(sd,add_str("l15"),sd->npc_menu); + pc_setreg(sd,add_str("@menu"),sd->npc_menu); + sd->state.menu_or_input=0; + if(sd->npc_menu>0 && sd->npc_menu<(st->end-st->start)/2){ + int pos; + if( st->stack->stack_data[st->start+sd->npc_menu*2+1].type!=C_POS ){ + printf("script: menu: not label !\n"); + st->state=END; + return 0; + } + pos=conv_num(st,& (st->stack->stack_data[st->start+sd->npc_menu*2+1])); + st->pos=pos; + st->state=GOTO; + } + } + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int buildin_rand(struct script_state *st) +{ + int range,min,max; + + if(st->end>st->start+3){ + min=conv_num(st,& (st->stack->stack_data[st->start+2])); + max=conv_num(st,& (st->stack->stack_data[st->start+3])); + if(max<min){ + int tmp; + tmp=min; + min=max; + max=tmp; + } + range=max-min+1; + push_val(st->stack,C_INT,rand()%range+min); + } else { + range=conv_num(st,& (st->stack->stack_data[st->start+2])); + push_val(st->stack,C_INT,rand()%range); + } + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int buildin_warp(struct script_state *st) +{ + int x,y; + char *str; + struct map_session_data *sd=script_rid2sd(st); + + str=conv_str(st,& (st->stack->stack_data[st->start+2])); + x=conv_num(st,& (st->stack->stack_data[st->start+3])); + y=conv_num(st,& (st->stack->stack_data[st->start+4])); + if(strcmp(str,"Random")==0) + pc_randomwarp(sd,3); + else if(strcmp(str,"SavePoint")==0){ + if(map[sd->bl.m].flag.noreturn) // 蝶禁止 + return 0; + + pc_setpos(sd,sd->status.save_point.map, + sd->status.save_point.x,sd->status.save_point.y,3); + }else if(strcmp(str,"Save")==0){ + if(map[sd->bl.m].flag.noreturn) // 蝶禁止 + return 0; + + pc_setpos(sd,sd->status.save_point.map, + sd->status.save_point.x,sd->status.save_point.y,3); + }else + pc_setpos(sd,str,x,y,0); + return 0; +} +/*========================================== + * エリア指定ワープ + *------------------------------------------ + */ +int buildin_areawarp_sub(struct block_list *bl,va_list ap) +{ + int x,y; + char *map; + map=va_arg(ap, char *); + x=va_arg(ap,int); + y=va_arg(ap,int); + if(strcmp(map,"Random")==0) + pc_randomwarp((struct map_session_data *)bl,3); + else + pc_setpos((struct map_session_data *)bl,map,x,y,0); + return 0; +} +int buildin_areawarp(struct script_state *st) +{ + int x,y,m; + char *str; + char *mapname; + int x0,y0,x1,y1; + + mapname=conv_str(st,& (st->stack->stack_data[st->start+2])); + x0=conv_num(st,& (st->stack->stack_data[st->start+3])); + y0=conv_num(st,& (st->stack->stack_data[st->start+4])); + x1=conv_num(st,& (st->stack->stack_data[st->start+5])); + y1=conv_num(st,& (st->stack->stack_data[st->start+6])); + str=conv_str(st,& (st->stack->stack_data[st->start+7])); + x=conv_num(st,& (st->stack->stack_data[st->start+8])); + y=conv_num(st,& (st->stack->stack_data[st->start+9])); + + if( (m=map_mapname2mapid(mapname))< 0) + return 0; + + map_foreachinarea(buildin_areawarp_sub, + m,x0,y0,x1,y1,BL_PC, str,x,y ); + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int buildin_heal(struct script_state *st) +{ + int hp,sp; + + hp=conv_num(st,& (st->stack->stack_data[st->start+2])); + sp=conv_num(st,& (st->stack->stack_data[st->start+3])); + pc_heal(script_rid2sd(st),hp,sp); + return 0; +} +/*========================================== + * + *------------------------------------------ + */ +int buildin_itemheal(struct script_state *st) +{ + int hp,sp; + + hp=conv_num(st,& (st->stack->stack_data[st->start+2])); + sp=conv_num(st,& (st->stack->stack_data[st->start+3])); + pc_itemheal(script_rid2sd(st),hp,sp); + return 0; +} +/*========================================== + * + *------------------------------------------ + */ +int buildin_percentheal(struct script_state *st) +{ + int hp,sp; + + hp=conv_num(st,& (st->stack->stack_data[st->start+2])); + sp=conv_num(st,& (st->stack->stack_data[st->start+3])); + pc_percentheal(script_rid2sd(st),hp,sp); + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int buildin_jobchange(struct script_state *st) +{ + int job, upper=-1; + + job=conv_num(st,& (st->stack->stack_data[st->start+2])); + if( st->end>st->start+3 ) + upper=conv_num(st,& (st->stack->stack_data[st->start+3])); + + if ((job >= 0 && job < MAX_PC_CLASS)) + pc_jobchange(script_rid2sd(st),job, upper); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int buildin_input(struct script_state *st) +{ + struct map_session_data *sd=NULL; + int num=(st->end>st->start+2)?st->stack->stack_data[st->start+2].u.num:0; + char *name=(st->end>st->start+2)?str_buf+str_data[num&0x00ffffff].str:""; +// char prefix=*name; + char postfix=name[strlen(name)-1]; + + sd=script_rid2sd(st); + if(sd->state.menu_or_input){ + sd->state.menu_or_input=0; + if( postfix=='$' ){ + // 文字列 + if(st->end>st->start+2){ // 引数1個 + set_reg(sd,num,name,(void*)sd->npc_str); + }else{ + printf("buildin_input: string discarded !!\n"); + } + }else{ + + //commented by Lupus (check Value Number Input fix in clif.c) + //** Fix by fritz :X keeps people from abusing old input bugs + if(sd->npc_amount < 0) //** If input amount is less then 0 + { + clif_tradecancelled(sd); // added "Deal has been cancelled" message by Valaris + buildin_close(st); //** close + } + + // 数値 + if(st->end>st->start+2){ // 引数1個 + set_reg(sd,num,name,(void*)sd->npc_amount); + } else { + // ragemu互換のため + pc_setreg(sd,add_str("l14"),sd->npc_amount); + } + } + } else { + st->state=RERUNLINE; + if(postfix=='$')clif_scriptinputstr(sd,st->oid); + else clif_scriptinput(sd,st->oid); + sd->state.menu_or_input=1; + } + return 0; +} + + +/*========================================== + * + *------------------------------------------ + */ +int buildin_if(struct script_state *st) +{ + int sel,i; + + sel=conv_num(st,& (st->stack->stack_data[st->start+2])); + if(!sel) + return 0; + + // 関数名をコピー + push_copy(st->stack,st->start+3); + // 間に引数マーカを入れて + push_val(st->stack,C_ARG,0); + // 残りの引数をコピー + for(i=st->start+4;i<st->end;i++){ + push_copy(st->stack,i); + } + run_func(st); + + return 0; +} + + +/*========================================== + * 変数設定 + *------------------------------------------ + */ +int buildin_set(struct script_state *st) +{ + struct map_session_data *sd=NULL; + int num=st->stack->stack_data[st->start+2].u.num; + char *name=str_buf+str_data[num&0x00ffffff].str; + char prefix=*name; + char postfix=name[strlen(name)-1]; + + if( st->stack->stack_data[st->start+2].type!=C_NAME ){ + printf("script: buildin_set: not name\n"); + return 0; + } + + if( prefix!='$' ) + sd=script_rid2sd(st); + + + if( postfix=='$' ){ + // 文字列 + char *str = conv_str(st,& (st->stack->stack_data[st->start+3])); + set_reg(sd,num,name,(void*)str); + }else{ + // 数値 + int val = conv_num(st,& (st->stack->stack_data[st->start+3])); + set_reg(sd,num,name,(void*)val); + } + + return 0; +} +/*========================================== + * 配列変数設定 + *------------------------------------------ + */ +int buildin_setarray(struct script_state *st) +{ + struct map_session_data *sd=NULL; + int num=st->stack->stack_data[st->start+2].u.num; + char *name=str_buf+str_data[num&0x00ffffff].str; + char prefix=*name; + char postfix=name[strlen(name)-1]; + int i,j; + + if( prefix!='$' && prefix!='@' ){ + printf("buildin_setarray: illegal scope !\n"); + return 0; + } + if( prefix!='$' ) + sd=script_rid2sd(st); + + for(j=0,i=st->start+3; i<st->end && j<128;i++,j++){ + void *v; + if( postfix=='$' ) + v=(void*)conv_str(st,& (st->stack->stack_data[i])); + else + v=(void*)conv_num(st,& (st->stack->stack_data[i])); + set_reg( sd, num+(j<<24), name, v); + } + return 0; +} +/*========================================== + * 配列変数クリア + *------------------------------------------ + */ +int buildin_cleararray(struct script_state *st) +{ + struct map_session_data *sd=NULL; + int num=st->stack->stack_data[st->start+2].u.num; + char *name=str_buf+str_data[num&0x00ffffff].str; + char prefix=*name; + char postfix=name[strlen(name)-1]; + int sz=conv_num(st,& (st->stack->stack_data[st->start+4])); + int i; + void *v; + + if( prefix!='$' && prefix!='@' ){ + printf("buildin_cleararray: illegal scope !\n"); + return 0; + } + if( prefix!='$' ) + sd=script_rid2sd(st); + + if( postfix=='$' ) + v=(void*)conv_str(st,& (st->stack->stack_data[st->start+3])); + else + v=(void*)conv_num(st,& (st->stack->stack_data[st->start+3])); + + for(i=0;i<sz;i++) + set_reg(sd,num+(i<<24),name,v); + return 0; +} +/*========================================== + * 配列変数コピー + *------------------------------------------ + */ +int buildin_copyarray(struct script_state *st) +{ + struct map_session_data *sd=NULL; + int num=st->stack->stack_data[st->start+2].u.num; + char *name=str_buf+str_data[num&0x00ffffff].str; + char prefix=*name; + char postfix=name[strlen(name)-1]; + int num2=st->stack->stack_data[st->start+3].u.num; + char *name2=str_buf+str_data[num2&0x00ffffff].str; + char prefix2=*name2; + char postfix2=name2[strlen(name2)-1]; + int sz=conv_num(st,& (st->stack->stack_data[st->start+4])); + int i; + + if( prefix!='$' && prefix!='@' && prefix2!='$' && prefix2!='@' ){ + printf("buildin_copyarray: illegal scope !\n"); + return 0; + } + if( (postfix=='$' || postfix2=='$') && postfix!=postfix2 ){ + printf("buildin_copyarray: type mismatch !\n"); + return 0; + } + if( prefix!='$' || prefix2!='$' ) + sd=script_rid2sd(st); + + + for(i=0;i<sz;i++) + set_reg(sd,num+(i<<24),name, get_val2(st,num2+(i<<24)) ); + return 0; +} +/*========================================== + * 配列変数のサイズ所得 + *------------------------------------------ + */ +static int getarraysize(struct script_state *st,int num,int postfix) +{ + int i=(num>>24),c=i; + for(;i<128;i++){ + void *v=get_val2(st,num+(i<<24)); + if(postfix=='$' && *((char*)v) ) c=i; + if(postfix!='$' && (int)v )c=i; + } + return c+1; +} +int buildin_getarraysize(struct script_state *st) +{ + int num=st->stack->stack_data[st->start+2].u.num; + char *name=str_buf+str_data[num&0x00ffffff].str; + char prefix=*name; + char postfix=name[strlen(name)-1]; + + if( prefix!='$' && prefix!='@' ){ + printf("buildin_copyarray: illegal scope !\n"); + return 0; + } + + push_val(st->stack,C_INT,getarraysize(st,num,postfix) ); + return 0; +} +/*========================================== + * 配列変数から要素削除 + *------------------------------------------ + */ +int buildin_deletearray(struct script_state *st) +{ + struct map_session_data *sd=NULL; + int num=st->stack->stack_data[st->start+2].u.num; + char *name=str_buf+str_data[num&0x00ffffff].str; + char prefix=*name; + char postfix=name[strlen(name)-1]; + int count=1; + int i,sz=getarraysize(st,num,postfix)-(num>>24)-count+1; + + + if( (st->end > st->start+3) ) + count=conv_num(st,& (st->stack->stack_data[st->start+3])); + + if( prefix!='$' && prefix!='@' ){ + printf("buildin_deletearray: illegal scope !\n"); + return 0; + } + if( prefix!='$' ) + sd=script_rid2sd(st); + + for(i=0;i<sz;i++){ + set_reg(sd,num+(i<<24),name, get_val2(st,num+((i+count)<<24) ) ); + } + for(;i<(128-(num>>24));i++){ + if( postfix!='$' ) set_reg(sd,num+(i<<24),name, 0); + if( postfix=='$' ) set_reg(sd,num+(i<<24),name, ""); + } + return 0; +} + +/*========================================== + * 指定要素を表す値(キー)を所得する + *------------------------------------------ + */ +int buildin_getelementofarray(struct script_state *st) +{ + if( st->stack->stack_data[st->start+2].type==C_NAME ){ + int i=conv_num(st,& (st->stack->stack_data[st->start+3])); + if(i>127 || i<0){ + printf("script: getelementofarray (operator[]): param2 illegal number %d\n",i); + push_val(st->stack,C_INT,0); + }else{ + push_val(st->stack,C_NAME, + (i<<24) | st->stack->stack_data[st->start+2].u.num ); + } + }else{ + printf("script: getelementofarray (operator[]): param1 not name !\n"); + push_val(st->stack,C_INT,0); + } + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int buildin_setlook(struct script_state *st) +{ + int type,val; + + type=conv_num(st,& (st->stack->stack_data[st->start+2])); + val=conv_num(st,& (st->stack->stack_data[st->start+3])); + + pc_changelook(script_rid2sd(st),type,val); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int buildin_cutin(struct script_state *st) +{ + int type; + + conv_str(st,& (st->stack->stack_data[st->start+2])); + type=conv_num(st,& (st->stack->stack_data[st->start+3])); + + clif_cutin(script_rid2sd(st),st->stack->stack_data[st->start+2].u.str,type); + + return 0; +} +/*========================================== + * カードのイラストを表示する + *------------------------------------------ + */ +int buildin_cutincard(struct script_state *st) +{ + int itemid; + + itemid=conv_num(st,& (st->stack->stack_data[st->start+2])); + + clif_cutin(script_rid2sd(st),itemdb_search(itemid)->cardillustname,4); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int buildin_viewpoint(struct script_state *st) +{ + int type,x,y,id,color; + + type=conv_num(st,& (st->stack->stack_data[st->start+2])); + x=conv_num(st,& (st->stack->stack_data[st->start+3])); + y=conv_num(st,& (st->stack->stack_data[st->start+4])); + id=conv_num(st,& (st->stack->stack_data[st->start+5])); + color=conv_num(st,& (st->stack->stack_data[st->start+6])); + + clif_viewpoint(script_rid2sd(st),st->oid,type,x,y,id,color); + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int buildin_countitem(struct script_state *st) +{ + int nameid=0,count=0,i; + struct map_session_data *sd; + + struct script_data *data; + + sd = script_rid2sd(st); + + data=&(st->stack->stack_data[st->start+2]); + get_val(st,data); + if( data->type==C_STR || data->type==C_CONSTSTR ){ + const char *name=conv_str(st,data); + struct item_data *item_data; + if( (item_data = itemdb_searchname(name)) != NULL) + nameid=item_data->nameid; + }else + nameid=conv_num(st,data); + + if (nameid>=500) //if no such ID then skip this iteration + for(i=0;i<MAX_INVENTORY;i++){ + if(sd->status.inventory[i].nameid==nameid) + count+=sd->status.inventory[i].amount; + } + else{ + if(battle_config.error_log) + printf("wrong item ID : countitem(%i)\n",nameid); + } + push_val(st->stack,C_INT,count); + + return 0; +} + +/*========================================== + * 重量チェック + *------------------------------------------ + */ +int buildin_checkweight(struct script_state *st) +{ + int nameid=0,amount; + struct map_session_data *sd; + struct script_data *data; + + sd = script_rid2sd(st); + + data=&(st->stack->stack_data[st->start+2]); + get_val(st,data); + if( data->type==C_STR || data->type==C_CONSTSTR ){ + const char *name=conv_str(st,data); + struct item_data *item_data = itemdb_searchname(name); + if( item_data ) + nameid=item_data->nameid; + }else + nameid=conv_num(st,data); + + amount=conv_num(st,& (st->stack->stack_data[st->start+3])); + if ( amount<=0 || nameid<500 ) { //if get wrong item ID or amount<=0, don't count weight of non existing items + push_val(st->stack,C_INT,0); + } + + sd=script_rid2sd(st); + if(itemdb_weight(nameid)*amount + sd->weight > sd->max_weight){ + push_val(st->stack,C_INT,0); + } else { + push_val(st->stack,C_INT,1); + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int buildin_getitem(struct script_state *st) +{ + int nameid,amount,flag = 0; + struct item item_tmp; + struct map_session_data *sd; + struct script_data *data; + + sd = script_rid2sd(st); + + data=&(st->stack->stack_data[st->start+2]); + get_val(st,data); + if( data->type==C_STR || data->type==C_CONSTSTR ){ + const char *name=conv_str(st,data); + struct item_data *item_data = itemdb_searchname(name); + nameid=512; //Apple item ID + if( item_data != NULL) + nameid=item_data->nameid; + }else + nameid=conv_num(st,data); + + if ( ( amount=conv_num(st,& (st->stack->stack_data[st->start+3])) ) <= 0) { + return 0; //return if amount <=0, skip the useles iteration + } + //Violet Box, Blue Box, etc - random item pick + if(nameid<0) { // ランダム + nameid=itemdb_searchrandomid(-nameid); + flag = 1; + } + + if(nameid > 0) { + memset(&item_tmp,0,sizeof(item_tmp)); + item_tmp.nameid=nameid; + if(!flag) + item_tmp.identify=1; + else + item_tmp.identify=!itemdb_isequip3(nameid); + if( st->end>st->start+5 ) //アイテムを指定したIDに渡す + sd=map_id2sd(conv_num(st,& (st->stack->stack_data[st->start+5]))); + if(sd == NULL) //アイテムを渡す相手がいなかったらお帰り + return 0; + if((flag = pc_additem(sd,&item_tmp,amount))) { + clif_additem(sd,0,0,flag); + map_addflooritem(&item_tmp,amount,sd->bl.m,sd->bl.x,sd->bl.y,NULL,NULL,NULL,0); + } + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int buildin_getitem2(struct script_state *st) +{ + int nameid,amount,flag = 0; + int iden,ref,attr,c1,c2,c3,c4; + struct item_data *item_data; + struct item item_tmp; + struct map_session_data *sd; + struct script_data *data; + + sd = script_rid2sd(st); + + data=&(st->stack->stack_data[st->start+2]); + get_val(st,data); + if( data->type==C_STR || data->type==C_CONSTSTR ){ + const char *name=conv_str(st,data); + struct item_data *item_data = itemdb_searchname(name); + nameid=512; //Apple item ID + if( item_data ) + nameid=item_data->nameid; + }else + nameid=conv_num(st,data); + + amount=conv_num(st,& (st->stack->stack_data[st->start+3])); + iden=conv_num(st,& (st->stack->stack_data[st->start+4])); + ref=conv_num(st,& (st->stack->stack_data[st->start+5])); + attr=conv_num(st,& (st->stack->stack_data[st->start+6])); + c1=conv_num(st,& (st->stack->stack_data[st->start+7])); + c2=conv_num(st,& (st->stack->stack_data[st->start+8])); + c3=conv_num(st,& (st->stack->stack_data[st->start+9])); + c4=conv_num(st,& (st->stack->stack_data[st->start+10])); + if( st->end>st->start+11 ) //アイテムを指定したIDに渡す + sd=map_id2sd(conv_num(st,& (st->stack->stack_data[st->start+11]))); + if(sd == NULL) //アイテムを渡す相手がいなかったらお帰り + return 0; + + if(nameid<0) { // ランダム + nameid=itemdb_searchrandomid(-nameid); + flag = 1; + } + + if(nameid > 0) { + memset(&item_tmp,0,sizeof(item_tmp)); + item_data=itemdb_search(nameid); + if(item_data->type==4 || item_data->type==5){ + if(ref > 10) ref = 10; + } + else if(item_data->type==7) { + iden = 1; + ref = 0; + } + else { + iden = 1; + ref = attr = 0; + } + + item_tmp.nameid=nameid; + if(!flag) + item_tmp.identify=iden; + else if(item_data->type==4 || item_data->type==5) + item_tmp.identify=0; + item_tmp.refine=ref; + item_tmp.attribute=attr; + item_tmp.card[0]=c1; + item_tmp.card[1]=c2; + item_tmp.card[2]=c3; + item_tmp.card[3]=c4; + if((flag = pc_additem(sd,&item_tmp,amount))) { + clif_additem(sd,0,0,flag); + map_addflooritem(&item_tmp,amount,sd->bl.m,sd->bl.x,sd->bl.y,NULL,NULL,NULL,0); + } + } + + return 0; +} +/*========================================== + * + *------------------------------------------ + */ +int buildin_makeitem(struct script_state *st) +{ + int nameid,amount,flag = 0; + int x,y,m; + char *mapname; + struct item item_tmp; + struct map_session_data *sd; + struct script_data *data; + + sd = script_rid2sd(st); + + data=&(st->stack->stack_data[st->start+2]); + get_val(st,data); + if( data->type==C_STR || data->type==C_CONSTSTR ){ + const char *name=conv_str(st,data); + struct item_data *item_data = itemdb_searchname(name); + nameid=512; //Apple Item ID + if( item_data ) + nameid=item_data->nameid; + }else + nameid=conv_num(st,data); + + amount=conv_num(st,& (st->stack->stack_data[st->start+3])); + mapname =conv_str(st,& (st->stack->stack_data[st->start+4])); + x =conv_num(st,& (st->stack->stack_data[st->start+5])); + y =conv_num(st,& (st->stack->stack_data[st->start+6])); + + if( sd && strcmp(mapname,"this")==0) + m=sd->bl.m; + else + m=map_mapname2mapid(mapname); + + if(nameid<0) { // ランダム + nameid=itemdb_searchrandomid(-nameid); + flag = 1; + } + + if(nameid > 0) { + memset(&item_tmp,0,sizeof(item_tmp)); + item_tmp.nameid=nameid; + if(!flag) + item_tmp.identify=1; + else + item_tmp.identify=!itemdb_isequip3(nameid); + +// clif_additem(sd,0,0,flag); + map_addflooritem(&item_tmp,amount,m,x,y,NULL,NULL,NULL,0); + } + + return 0; +} +/*========================================== + * + *------------------------------------------ + */ +int buildin_delitem(struct script_state *st) +{ + int nameid=0,amount,i; + struct map_session_data *sd; + struct script_data *data; + + sd = script_rid2sd(st); + + data=&(st->stack->stack_data[st->start+2]); + get_val(st,data); + if( data->type==C_STR || data->type==C_CONSTSTR ){ + const char *name=conv_str(st,data); + struct item_data *item_data = itemdb_searchname(name); + //nameid=512; + if( item_data ) + nameid=item_data->nameid; + }else + nameid=conv_num(st,data); + + amount=conv_num(st,& (st->stack->stack_data[st->start+3])); + + if (nameid<500 || amount<=0 ) {//by Lupus. Don't run FOR if u got wrong item ID or amount<=0 + //printf("wrong item ID or amount<=0 : delitem %i,\n",nameid,amount); + return 0; + } + sd=script_rid2sd(st); + + for(i=0;i<MAX_INVENTORY;i++){ + if(sd->status.inventory[i].nameid<=0 || sd->inventory_data[i] == NULL || + sd->inventory_data[i]->type!=7 || + sd->status.inventory[i].amount<=0) + continue; + if(sd->status.inventory[i].nameid == nameid){ + if(sd->status.inventory[i].card[0] == (short)0xff00){ + if(search_petDB_index(nameid, PET_EGG) >= 0){ + intif_delete_petdata(*((long *)(&sd->status.inventory[i].card[1]))); + break; + } + } + } + } + for(i=0;i<MAX_INVENTORY;i++){ + if(sd->status.inventory[i].nameid==nameid){ + if(sd->status.inventory[i].amount>=amount){ + pc_delitem(sd,i,amount,0); + break; + } else { + amount-=sd->status.inventory[i].amount; + if(amount==0) + amount=sd->status.inventory[i].amount; + pc_delitem(sd,i,amount,0); + break; + } + } + } + + return 0; +} + +/*========================================== + *キャラ関係のパラメータ取得 + *------------------------------------------ + */ +int buildin_readparam(struct script_state *st) +{ + int type; + struct map_session_data *sd; + + type=conv_num(st,& (st->stack->stack_data[st->start+2])); + if( st->end>st->start+3 ) + sd=map_nick2sd(conv_str(st,& (st->stack->stack_data[st->start+3]))); + else + sd=script_rid2sd(st); + + if(sd==NULL){ + push_val(st->stack,C_INT,-1); + return 0; + } + + push_val(st->stack,C_INT,pc_readparam(sd,type)); + + return 0; +} +/*========================================== + *キャラ関係のID取得 + *------------------------------------------ + */ +int buildin_getcharid(struct script_state *st) +{ + int num; + struct map_session_data *sd; + + num=conv_num(st,& (st->stack->stack_data[st->start+2])); + if( st->end>st->start+3 ) + sd=map_nick2sd(conv_str(st,& (st->stack->stack_data[st->start+3]))); + else + sd=script_rid2sd(st); + if(sd==NULL){ + push_val(st->stack,C_INT,-1); + return 0; + } + if(num==0) + push_val(st->stack,C_INT,sd->status.char_id); + if(num==1) + push_val(st->stack,C_INT,sd->status.party_id); + if(num==2) + push_val(st->stack,C_INT,sd->status.guild_id); + if(num==3) + push_val(st->stack,C_INT,sd->status.account_id); + return 0; +} +/*========================================== + *指定IDのPT名取得 + *------------------------------------------ + */ +char *buildin_getpartyname_sub(int party_id) +{ + struct party *p; + + p=NULL; + p=party_search(party_id); + + if(p!=NULL){ + char *buf; + buf=(char *)aCalloc(24,sizeof(char)); + strcpy(buf,p->name); + return buf; + } + + return 0; +} +int buildin_getpartyname(struct script_state *st) +{ + char *name; + int party_id; + + party_id=conv_num(st,& (st->stack->stack_data[st->start+2])); + name=buildin_getpartyname_sub(party_id); + if(name!=0) + push_str(st->stack,C_STR,name); + else + push_str(st->stack,C_CONSTSTR,"null"); + + return 0; +} +/*========================================== + *指定IDのPT人数とメンバーID取得 + *------------------------------------------ + */ +int buildin_getpartymember(struct script_state *st) +{ + struct party *p; + int i,j=0; + + p=NULL; + p=party_search(conv_num(st,& (st->stack->stack_data[st->start+2]))); + + if(p!=NULL){ + for(i=0;i<MAX_PARTY;i++){ + if(p->member[i].account_id){ +// printf("name:%s %d\n",p->member[i].name,i); + mapreg_setregstr(add_str("$@partymembername$")+(i<<24),p->member[i].name); + j++; + } + } + } + mapreg_setreg(add_str("$@partymembercount"),j); + + return 0; +} +/*========================================== + *指定IDのギルド名取得 + *------------------------------------------ + */ +char *buildin_getguildname_sub(int guild_id) +{ + struct guild *g=NULL; + g=guild_search(guild_id); + + if(g!=NULL){ + char *buf; + buf=(char *)aCalloc(24,sizeof(char)); + strcpy(buf,g->name); + return buf; + } + return 0; +} +int buildin_getguildname(struct script_state *st) +{ + char *name; + int guild_id=conv_num(st,& (st->stack->stack_data[st->start+2])); + name=buildin_getguildname_sub(guild_id); + if(name!=0) + push_str(st->stack,C_STR,name); + else + push_str(st->stack,C_CONSTSTR,"null"); + return 0; +} + +/*========================================== + *指定IDのGuildMaster名取得 + *------------------------------------------ + */ +char *buildin_getguildmaster_sub(int guild_id) +{ + struct guild *g=NULL; + g=guild_search(guild_id); + + if(g!=NULL){ + char *buf; + buf=(char *)aCalloc(24,sizeof(char)); + strncpy(buf,g->master, 23); + return buf; + } + + return 0; +} +int buildin_getguildmaster(struct script_state *st) +{ + char *master; + int guild_id=conv_num(st,& (st->stack->stack_data[st->start+2])); + master=buildin_getguildmaster_sub(guild_id); + if(master!=0) + push_str(st->stack,C_STR,master); + else + push_str(st->stack,C_CONSTSTR,"null"); + return 0; +} + +int buildin_getguildmasterid(struct script_state *st) +{ + char *master; + struct map_session_data *sd=NULL; + int guild_id=conv_num(st,& (st->stack->stack_data[st->start+2])); + master=buildin_getguildmaster_sub(guild_id); + if(master!=0){ + if((sd=map_nick2sd(master)) == NULL){ + push_val(st->stack,C_INT,0); + return 0; + } + push_val(st->stack,C_INT,sd->status.char_id); + }else{ + push_val(st->stack,C_INT,0); + } + return 0; +} + +/*========================================== + * キャラクタの名前 + *------------------------------------------ + */ +int buildin_strcharinfo(struct script_state *st) +{ + struct map_session_data *sd; + int num; + + sd=script_rid2sd(st); + num=conv_num(st,& (st->stack->stack_data[st->start+2])); + if(num==0){ + char *buf; + buf=(char *)aCalloc(24,sizeof(char)); + strncpy(buf,sd->status.name, 23); + push_str(st->stack,C_STR,buf); + } + if(num==1){ + char *buf; + buf=buildin_getpartyname_sub(sd->status.party_id); + if(buf!=0) + push_str(st->stack,C_STR,buf); + else + push_str(st->stack,C_CONSTSTR,""); + } + if(num==2){ + char *buf; + buf=buildin_getguildname_sub(sd->status.guild_id); + if(buf!=0) + push_str(st->stack,C_STR,buf); + else + push_str(st->stack,C_CONSTSTR,""); + } + + return 0; +} + +unsigned int equip[10]={0x0100,0x0010,0x0020,0x0002,0x0004,0x0040,0x0008,0x0080,0x0200,0x0001}; + +/*========================================== + * GetEquipID(Pos); Pos: 1-10 + *------------------------------------------ + */ +int buildin_getequipid(struct script_state *st) +{ + int i,num; + struct map_session_data *sd; + struct item_data* item; + + sd=script_rid2sd(st); + if(sd == NULL) + { + printf("getequipid: sd == NULL\n"); + return 0; + } + num=conv_num(st,& (st->stack->stack_data[st->start+2])); + i=pc_checkequip(sd,equip[num-1]); + if(i >= 0){ + item=sd->inventory_data[i]; + if(item) + push_val(st->stack,C_INT,item->nameid); + else + push_val(st->stack,C_INT,0); + }else{ + push_val(st->stack,C_INT,-1); + } + return 0; +} + +/*========================================== + * 装備名文字列(精錬メニュー用) + *------------------------------------------ + */ +int buildin_getequipname(struct script_state *st) +{ + int i,num; + struct map_session_data *sd; + struct item_data* item; + char *buf; + + buf=(char *)aCalloc(64,sizeof(char)); + sd=script_rid2sd(st); + num=conv_num(st,& (st->stack->stack_data[st->start+2])); + i=pc_checkequip(sd,equip[num-1]); + if(i >= 0){ + item=sd->inventory_data[i]; + if(item) + sprintf(buf,"%s-[%s]",pos[num-1],item->jname); + else + sprintf(buf,"%s-[%s]",pos[num-1],pos[10]); + }else{ + sprintf(buf,"%s-[%s]",pos[num-1],pos[10]); + } + push_str(st->stack,C_STR,buf); + + return 0; +} + +/*========================================== + * getbrokenid [Valaris] + *------------------------------------------ + */ +int buildin_getbrokenid(struct script_state *st) +{ + int i,num,id=0,brokencounter=0; + struct map_session_data *sd; + + sd=script_rid2sd(st); + + num=conv_num(st,& (st->stack->stack_data[st->start+2])); + for(i=0; i<MAX_INVENTORY; i++) { + if(sd->status.inventory[i].broken==1){ + brokencounter++; + if(num==brokencounter){ + id=sd->status.inventory[i].nameid; + break; + } + } + } + + push_val(st->stack,C_INT,id); + + return 0; +} + +/*========================================== + * repair [Valaris] + *------------------------------------------ + */ +int buildin_repair(struct script_state *st) +{ + int i,num; + int repaircounter=0; + struct map_session_data *sd; + + + sd=script_rid2sd(st); + + num=conv_num(st,& (st->stack->stack_data[st->start+2])); + for(i=0; i<MAX_INVENTORY; i++) { + if(sd->status.inventory[i].broken==1){ + repaircounter++; + if(num==repaircounter){ + sd->status.inventory[i].broken=0; + clif_equiplist(sd); + clif_produceeffect(sd, 0, sd->status.inventory[i].nameid); + clif_misceffect(&sd->bl, 3); + clif_displaymessage(sd->fd,"Item has been repaired."); + break; + } + } + } + + return 0; +} + +/*========================================== + * 装備チェック + *------------------------------------------ + */ +int buildin_getequipisequiped(struct script_state *st) +{ + int i,num; + struct map_session_data *sd; + + num=conv_num(st,& (st->stack->stack_data[st->start+2])); + sd=script_rid2sd(st); + i=pc_checkequip(sd,equip[num-1]); + if(i >= 0){ + push_val(st->stack,C_INT,1); + }else{ + push_val(st->stack,C_INT,0); + } + + return 0; +} + +/*========================================== + * 装備品精錬可能チェック + *------------------------------------------ + */ +int buildin_getequipisenableref(struct script_state *st) +{ + int i,num; + struct map_session_data *sd; + + num=conv_num(st,& (st->stack->stack_data[st->start+2])); + sd=script_rid2sd(st); + i=pc_checkequip(sd,equip[num-1]); + if(i >= 0 && num<7 && sd->inventory_data[i] && (num!=1 || sd->inventory_data[i]->def > 1 + || (sd->inventory_data[i]->def==1 && sd->inventory_data[i]->equip_script==NULL) + || (sd->inventory_data[i]->def<=0 && sd->inventory_data[i]->equip_script!=NULL)) + ){ + push_val(st->stack,C_INT,1); + }else{ + push_val(st->stack,C_INT,0); + } + + return 0; +} + +/*========================================== + * 装備品鑑定チェック + *------------------------------------------ + */ +int buildin_getequipisidentify(struct script_state *st) +{ + int i,num; + struct map_session_data *sd; + + num=conv_num(st,& (st->stack->stack_data[st->start+2])); + sd=script_rid2sd(st); + i=pc_checkequip(sd,equip[num-1]); + if(i >= 0) + push_val(st->stack,C_INT,sd->status.inventory[i].identify); + else + push_val(st->stack,C_INT,0); + + return 0; +} + +/*========================================== + * 装備品精錬度 + *------------------------------------------ + */ +int buildin_getequiprefinerycnt(struct script_state *st) +{ + int i,num; + struct map_session_data *sd; + + num=conv_num(st,& (st->stack->stack_data[st->start+2])); + sd=script_rid2sd(st); + i=pc_checkequip(sd,equip[num-1]); + if(i >= 0) + push_val(st->stack,C_INT,sd->status.inventory[i].refine); + else + push_val(st->stack,C_INT,0); + + return 0; +} + +/*========================================== + * 装備品武器LV + *------------------------------------------ + */ +int buildin_getequipweaponlv(struct script_state *st) +{ + int i,num; + struct map_session_data *sd; + + num=conv_num(st,& (st->stack->stack_data[st->start+2])); + sd=script_rid2sd(st); + i=pc_checkequip(sd,equip[num-1]); + if(i >= 0 && sd->inventory_data[i]) + push_val(st->stack,C_INT,sd->inventory_data[i]->wlv); + else + push_val(st->stack,C_INT,0); + + return 0; +} + +/*========================================== + * 装備品精錬成功率 + *------------------------------------------ + */ +int buildin_getequippercentrefinery(struct script_state *st) +{ + int i,num; + struct map_session_data *sd; + + num=conv_num(st,& (st->stack->stack_data[st->start+2])); + sd=script_rid2sd(st); + i=pc_checkequip(sd,equip[num-1]); + if(i >= 0) + push_val(st->stack,C_INT,pc_percentrefinery(sd,&sd->status.inventory[i])); + else + push_val(st->stack,C_INT,0); + + return 0; +} + +/*========================================== + * 精錬成功 + *------------------------------------------ + */ +int buildin_successrefitem(struct script_state *st) +{ + int i,num,ep; + struct map_session_data *sd; + + num=conv_num(st,& (st->stack->stack_data[st->start+2])); + sd=script_rid2sd(st); + i=pc_checkequip(sd,equip[num-1]); + if(i >= 0) { + ep=sd->status.inventory[i].equip; + + sd->status.inventory[i].refine++; + pc_unequipitem(sd,i,0); + clif_refine(sd->fd,sd,0,i,sd->status.inventory[i].refine); + clif_delitem(sd,i,1); + clif_additem(sd,i,1,0); + pc_equipitem(sd,i,ep); + clif_misceffect(&sd->bl,3); + } + + return 0; +} + +/*========================================== + * 精錬失敗 + *------------------------------------------ + */ +int buildin_failedrefitem(struct script_state *st) +{ + int i,num; + struct map_session_data *sd; + + num=conv_num(st,& (st->stack->stack_data[st->start+2])); + sd=script_rid2sd(st); + i=pc_checkequip(sd,equip[num-1]); + if(i >= 0) { + sd->status.inventory[i].refine = 0; + pc_unequipitem(sd,i,0); + // 精錬失敗エフェクトのパケット + clif_refine(sd->fd,sd,1,i,sd->status.inventory[i].refine); + pc_delitem(sd,i,1,0); + // 他の人にも失敗を通知 + clif_misceffect(&sd->bl,2); + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int buildin_statusup(struct script_state *st) +{ + int type; + struct map_session_data *sd; + + type=conv_num(st,& (st->stack->stack_data[st->start+2])); + sd=script_rid2sd(st); + pc_statusup(sd,type); + + return 0; +} +/*========================================== + * + *------------------------------------------ + */ +int buildin_statusup2(struct script_state *st) +{ + int type,val; + struct map_session_data *sd; + + type=conv_num(st,& (st->stack->stack_data[st->start+2])); + val=conv_num(st,& (st->stack->stack_data[st->start+3])); + sd=script_rid2sd(st); + pc_statusup2(sd,type,val); + + return 0; +} +/*========================================== + * 装備品による能力値ボーナス + *------------------------------------------ + */ +int buildin_bonus(struct script_state *st) +{ + int type,val; + struct map_session_data *sd; + + type=conv_num(st,& (st->stack->stack_data[st->start+2])); + val=conv_num(st,& (st->stack->stack_data[st->start+3])); + sd=script_rid2sd(st); + pc_bonus(sd,type,val); + + return 0; +} +/*========================================== + * 装備品による能力値ボーナス + *------------------------------------------ + */ +int buildin_bonus2(struct script_state *st) +{ + int type,type2,val; + struct map_session_data *sd; + + type=conv_num(st,& (st->stack->stack_data[st->start+2])); + type2=conv_num(st,& (st->stack->stack_data[st->start+3])); + val=conv_num(st,& (st->stack->stack_data[st->start+4])); + sd=script_rid2sd(st); + pc_bonus2(sd,type,type2,val); + + return 0; +} +/*========================================== + * 装備品による能力値ボーナス + *------------------------------------------ + */ +int buildin_bonus3(struct script_state *st) +{ + int type,type2,type3,val; + struct map_session_data *sd; + + type=conv_num(st,& (st->stack->stack_data[st->start+2])); + type2=conv_num(st,& (st->stack->stack_data[st->start+3])); + type3=conv_num(st,& (st->stack->stack_data[st->start+4])); + val=conv_num(st,& (st->stack->stack_data[st->start+5])); + sd=script_rid2sd(st); + pc_bonus3(sd,type,type2,type3,val); + + return 0; +} +/*========================================== + * スキル所得 + *------------------------------------------ + */ +int buildin_skill(struct script_state *st) +{ + int id,level,flag=1; + struct map_session_data *sd; + + id=conv_num(st,& (st->stack->stack_data[st->start+2])); + level=conv_num(st,& (st->stack->stack_data[st->start+3])); + if( st->end>st->start+4 ) + flag=conv_num(st,&(st->stack->stack_data[st->start+4]) ); + sd=script_rid2sd(st); + pc_skill(sd,id,level,flag); + + return 0; +} +/*========================================== + * ギルドスキル取得 + *------------------------------------------ + */ +int buildin_guildskill(struct script_state *st) +{ + int id,level; + struct map_session_data *sd; + int i=0; + + id=conv_num(st,& (st->stack->stack_data[st->start+2])); + level=conv_num(st,& (st->stack->stack_data[st->start+3])); +// if( st->end>st->start+4 ) +// flag=conv_num(st,&(st->stack->stack_data[st->start+4]) ); + sd=script_rid2sd(st); + for(i=0;i<level;i++) + guild_skillup(sd,id); + + return 0; +} +/*========================================== + * スキルレベル所得 + *------------------------------------------ + */ +int buildin_getskilllv(struct script_state *st) +{ + int id=conv_num(st,& (st->stack->stack_data[st->start+2])); + push_val(st->stack,C_INT, pc_checkskill( script_rid2sd(st) ,id) ); + return 0; +} +/*========================================== + * getgdskilllv(Guild_ID, Skill_ID); + * skill_id = 10000 : GD_APPROVAL + * 10001 : GD_KAFRACONTACT + * 10002 : GD_GUARDIANRESEARCH + * 10003 : GD_CHARISMA + * 10004 : GD_EXTENSION + *------------------------------------------ + */ +int buildin_getgdskilllv(struct script_state *st) +{ + int guild_id=conv_num(st,& (st->stack->stack_data[st->start+2])); + int skill_id=conv_num(st,& (st->stack->stack_data[st->start+3])); + struct guild *g=guild_search(guild_id); + push_val(st->stack,C_INT, (g==NULL)?-1:guild_checkskill(g,skill_id) ); + return 0; +/* + struct map_session_data *sd=NULL; + struct guild *g=NULL; + int skill_id; + + skill_id=conv_num(st,& (st->stack->stack_data[st->start+2])); + sd=script_rid2sd(st); + if(sd && sd->status.guild_id > 0) g=guild_search(sd->status.guild_id); + if(sd && g) { + push_val(st->stack,C_INT, guild_checkskill(g,skill_id+9999) ); + } else { + push_val(st->stack,C_INT,-1); + } + return 0; +*/ +} +/*========================================== + * + *------------------------------------------ + */ +int buildin_basicskillcheck(struct script_state *st) +{ + push_val(st->stack,C_INT, battle_config.basic_skill_check); + return 0; +} +/*========================================== + * + *------------------------------------------ + */ +int buildin_getgmlevel(struct script_state *st) +{ + push_val(st->stack,C_INT, pc_isGM(script_rid2sd(st))); + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int buildin_end(struct script_state *st) +{ + st->state = END; + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int buildin_checkoption(struct script_state *st) +{ + int type; + struct map_session_data *sd; + + type=conv_num(st,& (st->stack->stack_data[st->start+2])); + sd=script_rid2sd(st); + + if(sd->status.option & type){ + push_val(st->stack,C_INT,1); + } else { + push_val(st->stack,C_INT,0); + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int buildin_setoption(struct script_state *st) +{ + int type; + struct map_session_data *sd; + + type=conv_num(st,& (st->stack->stack_data[st->start+2])); + sd=script_rid2sd(st); + pc_setoption(sd,type); + + return 0; +} + +/*========================================== + * Checkcart [Valaris] + *------------------------------------------ + */ + +int buildin_checkcart(struct script_state *st) +{ + struct map_session_data *sd; + + sd=script_rid2sd(st); + + if(pc_iscarton(sd)){ + push_val(st->stack,C_INT,1); + } else { + push_val(st->stack,C_INT,0); + } + return 0; +} + +/*========================================== + * カートを付ける + *------------------------------------------ + */ +int buildin_setcart(struct script_state *st) +{ + struct map_session_data *sd; + + sd=script_rid2sd(st); + pc_setcart(sd,1); + + return 0; +} + +/*========================================== + * checkfalcon [Valaris] + *------------------------------------------ + */ + +int buildin_checkfalcon(struct script_state *st) +{ + struct map_session_data *sd; + + sd=script_rid2sd(st); + + if(pc_isfalcon(sd)){ + push_val(st->stack,C_INT,1); + } else { + push_val(st->stack,C_INT,0); + } + + return 0; +} + + +/*========================================== + * 鷹を付ける + *------------------------------------------ + */ +int buildin_setfalcon(struct script_state *st) +{ + struct map_session_data *sd; + + sd=script_rid2sd(st); + pc_setfalcon(sd); + + return 0; +} + +/*========================================== + * Checkcart [Valaris] + *------------------------------------------ + */ + +int buildin_checkriding(struct script_state *st) +{ + struct map_session_data *sd; + + sd=script_rid2sd(st); + + if(pc_isriding(sd)){ + push_val(st->stack,C_INT,1); + } else { + push_val(st->stack,C_INT,0); + } + + return 0; +} + + +/*========================================== + * ペコペコ乗り + *------------------------------------------ + */ +int buildin_setriding(struct script_state *st) +{ + struct map_session_data *sd; + + sd=script_rid2sd(st); + pc_setriding(sd); + + return 0; +} + +/*========================================== + * セーブポイントの保存 + *------------------------------------------ + */ +int buildin_savepoint(struct script_state *st) +{ + int x,y; + char *str; + + str=conv_str(st,& (st->stack->stack_data[st->start+2])); + x=conv_num(st,& (st->stack->stack_data[st->start+3])); + y=conv_num(st,& (st->stack->stack_data[st->start+4])); + pc_setsavepoint(script_rid2sd(st),str,x,y); + return 0; +} + +/*========================================== + * GetTimeTick(0: System Tick, 1: Time Second Tick) + *------------------------------------------ + */ +int buildin_gettimetick(struct script_state *st) /* Asgard Version */ +{ + int type; + time_t timer; + struct tm *t; + + type=conv_num(st,& (st->stack->stack_data[st->start+2])); + + switch(type){ + case 1: + //type 1:(Second Ticks: 0-86399, 00:00:00-23:59:59) + time(&timer); + t=localtime(&timer); + push_val(st->stack,C_INT,((t->tm_hour)*3600+(t->tm_min)*60+t->tm_sec)); + break; + case 0: + default: + //type 0:(System Ticks) + push_val(st->stack,C_INT,gettick()); + break; + } + return 0; +} + +/*========================================== + * GetTime(Type); + * 1: Sec 2: Min 3: Hour + * 4: WeekDay 5: MonthDay 6: Month + * 7: Year + *------------------------------------------ + */ +int buildin_gettime(struct script_state *st) /* Asgard Version */ +{ + int type; + time_t timer; + struct tm *t; + + type=conv_num(st,& (st->stack->stack_data[st->start+2])); + + time(&timer); + t=localtime(&timer); + + switch(type){ + case 1://Sec(0~59) + push_val(st->stack,C_INT,t->tm_sec); + break; + case 2://Min(0~59) + push_val(st->stack,C_INT,t->tm_min); + break; + case 3://Hour(0~23) + push_val(st->stack,C_INT,t->tm_hour); + break; + case 4://WeekDay(0~6) + push_val(st->stack,C_INT,t->tm_wday); + break; + case 5://MonthDay(01~31) + push_val(st->stack,C_INT,t->tm_mday); + break; + case 6://Month(01~12) + push_val(st->stack,C_INT,t->tm_mon+1); + break; + case 7://Year(20xx) + push_val(st->stack,C_INT,t->tm_year+1900); + break; + default://(format error) + push_val(st->stack,C_INT,-1); + break; + } + return 0; +} + +/*========================================== + * GetTimeStr("TimeFMT", Length); + *------------------------------------------ + */ +int buildin_gettimestr(struct script_state *st) +{ + char *tmpstr; + char *fmtstr; + int maxlen; + time_t now = time(NULL); + + fmtstr=conv_str(st,& (st->stack->stack_data[st->start+2])); + maxlen=conv_num(st,& (st->stack->stack_data[st->start+3])); + + tmpstr=(char *)aCalloc(maxlen+1,sizeof(char)); + strftime(tmpstr,maxlen,fmtstr,localtime(&now)); + tmpstr[maxlen]='\0'; + + push_str(st->stack,C_STR,tmpstr); + return 0; +} + +/*========================================== + * カプラ倉庫を開く + *------------------------------------------ + */ +int buildin_openstorage(struct script_state *st) +{ + storage_storageopen(script_rid2sd(st)); + return 0; +} + +int buildin_guildopenstorage(struct script_state *st) +{ + struct map_session_data *sd=script_rid2sd(st); + int ret; + ret = storage_guild_storageopen(sd); + push_val(st->stack,C_INT,ret); + return 0; +} + +/*========================================== + * アイテムによるスキル発動 + *------------------------------------------ + */ +int buildin_itemskill(struct script_state *st) +{ + int id,lv; + char *str; + struct map_session_data *sd=script_rid2sd(st); + + id=conv_num(st,& (st->stack->stack_data[st->start+2])); + lv=conv_num(st,& (st->stack->stack_data[st->start+3])); + str=conv_str(st,& (st->stack->stack_data[st->start+4])); + + // 詠唱中にスキルアイテムは使用できない + if(sd->skilltimer != -1) + return 0; + + sd->skillitem=id; + sd->skillitemlv=lv; + clif_item_skill(sd,id,lv,str); + return 0; +} +/*========================================== + * アイテム作成 + *------------------------------------------ + */ +int buildin_produce(struct script_state *st) +{ + int trigger; + struct map_session_data *sd=script_rid2sd(st); + + if( sd->state.produce_flag == 1) return 0; + trigger=conv_num(st,& (st->stack->stack_data[st->start+2])); + clif_skill_produce_mix_list(sd,trigger); + return 0; +} +/*========================================== + * NPCでペット作る + *------------------------------------------ + */ +int buildin_makepet(struct script_state *st) +{ + struct map_session_data *sd = script_rid2sd(st); + struct script_data *data; + int id,pet_id; + + data=&(st->stack->stack_data[st->start+2]); + get_val(st,data); + + id=conv_num(st,data); + + pet_id = search_petDB_index(id, PET_CLASS); + + if (pet_id < 0) + pet_id = search_petDB_index(id, PET_EGG); + if (pet_id >= 0 && sd) { + sd->catch_target_class = pet_db[pet_id].class; + intif_create_pet( + sd->status.account_id, sd->status.char_id, + pet_db[pet_id].class, mob_db[pet_db[pet_id].class].lv, + pet_db[pet_id].EggID, 0, pet_db[pet_id].intimate, + 100, 0, 1, pet_db[pet_id].jname); + } + + return 0; +} +/*========================================== + * NPCで経験値上げる + *------------------------------------------ + */ +int buildin_getexp(struct script_state *st) +{ + struct map_session_data *sd = script_rid2sd(st); + int base=0,job=0; + + base=conv_num(st,& (st->stack->stack_data[st->start+2])); + job =conv_num(st,& (st->stack->stack_data[st->start+3])); + if(base<0 || job<0) + return 0; + if(sd) + pc_gainexp(sd,base,job); + + return 0; +} + +/*========================================== + * モンスター発生 + *------------------------------------------ + */ +int buildin_monster(struct script_state *st) +{ + int class,amount,x,y; + char *str,*map,*event=""; + + map =conv_str(st,& (st->stack->stack_data[st->start+2])); + x =conv_num(st,& (st->stack->stack_data[st->start+3])); + y =conv_num(st,& (st->stack->stack_data[st->start+4])); + str =conv_str(st,& (st->stack->stack_data[st->start+5])); + class=conv_num(st,& (st->stack->stack_data[st->start+6])); + amount=conv_num(st,& (st->stack->stack_data[st->start+7])); + if( st->end>st->start+8 ) + event=conv_str(st,& (st->stack->stack_data[st->start+8])); + + mob_once_spawn(map_id2sd(st->rid),map,x,y,str,class,amount,event); + return 0; +} +/*========================================== + * モンスター発生 + *------------------------------------------ + */ +int buildin_areamonster(struct script_state *st) +{ + int class,amount,x0,y0,x1,y1; + char *str,*map,*event=""; + + map =conv_str(st,& (st->stack->stack_data[st->start+2])); + x0 =conv_num(st,& (st->stack->stack_data[st->start+3])); + y0 =conv_num(st,& (st->stack->stack_data[st->start+4])); + x1 =conv_num(st,& (st->stack->stack_data[st->start+5])); + y1 =conv_num(st,& (st->stack->stack_data[st->start+6])); + str =conv_str(st,& (st->stack->stack_data[st->start+7])); + class=conv_num(st,& (st->stack->stack_data[st->start+8])); + amount=conv_num(st,& (st->stack->stack_data[st->start+9])); + if( st->end>st->start+10 ) + event=conv_str(st,& (st->stack->stack_data[st->start+10])); + + mob_once_spawn_area(map_id2sd(st->rid),map,x0,y0,x1,y1,str,class,amount,event); + return 0; +} +/*========================================== + * モンスター削除 + *------------------------------------------ + */ +int buildin_killmonster_sub(struct block_list *bl,va_list ap) +{ + char *event=va_arg(ap,char *); + int allflag=va_arg(ap,int); + + if(!allflag){ + if(strcmp(event,((struct mob_data *)bl)->npc_event)==0) + mob_delete((struct mob_data *)bl); + return 0; + }else if(allflag){ + if(((struct mob_data *)bl)->spawndelay1==-1 && ((struct mob_data *)bl)->spawndelay2==-1) + mob_delete((struct mob_data *)bl); + return 0; + } + return 0; +} +int buildin_killmonster(struct script_state *st) +{ + char *mapname,*event; + int m,allflag=0; + mapname=conv_str(st,& (st->stack->stack_data[st->start+2])); + event=conv_str(st,& (st->stack->stack_data[st->start+3])); + if(strcmp(event,"All")==0) + allflag = 1; + + if( (m=map_mapname2mapid(mapname))<0 ) + return 0; + map_foreachinarea(buildin_killmonster_sub, + m,0,0,map[m].xs,map[m].ys,BL_MOB, event ,allflag); + return 0; +} + +int buildin_killmonsterall_sub(struct block_list *bl,va_list ap) +{ + mob_delete((struct mob_data *)bl); + return 0; +} +int buildin_killmonsterall(struct script_state *st) +{ + char *mapname; + int m; + mapname=conv_str(st,& (st->stack->stack_data[st->start+2])); + + if( (m=map_mapname2mapid(mapname))<0 ) + return 0; + map_foreachinarea(buildin_killmonsterall_sub, + m,0,0,map[m].xs,map[m].ys,BL_MOB); + return 0; +} + +/*========================================== + * イベント実行 + *------------------------------------------ + */ +int buildin_doevent(struct script_state *st) +{ + char *event; + event=conv_str(st,& (st->stack->stack_data[st->start+2])); + npc_event(map_id2sd(st->rid),event,0); + return 0; +} +/*========================================== + * NPC主体イベント実行 + *------------------------------------------ + */ +int buildin_donpcevent(struct script_state *st) +{ + char *event; + event=conv_str(st,& (st->stack->stack_data[st->start+2])); + npc_event_do(event); + return 0; +} +/*========================================== + * イベントタイマー追加 + *------------------------------------------ + */ +int buildin_addtimer(struct script_state *st) +{ + char *event; + int tick; + tick=conv_num(st,& (st->stack->stack_data[st->start+2])); + event=conv_str(st,& (st->stack->stack_data[st->start+3])); + pc_addeventtimer(script_rid2sd(st),tick,event); + return 0; +} +/*========================================== + * イベントタイマー削除 + *------------------------------------------ + */ +int buildin_deltimer(struct script_state *st) +{ + char *event; + event=conv_str(st,& (st->stack->stack_data[st->start+2])); + pc_deleventtimer(script_rid2sd(st),event); + return 0; +} +/*========================================== + * イベントタイマーのカウント値追加 + *------------------------------------------ + */ +int buildin_addtimercount(struct script_state *st) +{ + char *event; + int tick; + event=conv_str(st,& (st->stack->stack_data[st->start+2])); + tick=conv_num(st,& (st->stack->stack_data[st->start+3])); + pc_addeventtimercount(script_rid2sd(st),event,tick); + return 0; +} + +/*========================================== + * NPCタイマー初期化 + *------------------------------------------ + */ +int buildin_initnpctimer(struct script_state *st) +{ + struct npc_data *nd; + if( st->end > st->start+2 ) + nd=npc_name2id(conv_str(st,& (st->stack->stack_data[st->start+2]))); + else + nd=(struct npc_data *)map_id2bl(st->oid); + + npc_settimerevent_tick(nd,0); + npc_timerevent_start(nd); + return 0; +} +/*========================================== + * NPCタイマー開始 + *------------------------------------------ + */ +int buildin_startnpctimer(struct script_state *st) +{ + struct npc_data *nd; + if( st->end > st->start+2 ) + nd=npc_name2id(conv_str(st,& (st->stack->stack_data[st->start+2]))); + else + nd=(struct npc_data *)map_id2bl(st->oid); + + npc_timerevent_start(nd); + return 0; +} +/*========================================== + * NPCタイマー停止 + *------------------------------------------ + */ +int buildin_stopnpctimer(struct script_state *st) +{ + struct npc_data *nd; + if( st->end > st->start+2 ) + nd=npc_name2id(conv_str(st,& (st->stack->stack_data[st->start+2]))); + else + nd=(struct npc_data *)map_id2bl(st->oid); + + npc_timerevent_stop(nd); + return 0; +} +/*========================================== + * NPCタイマー情報所得 + *------------------------------------------ + */ +int buildin_getnpctimer(struct script_state *st) +{ + struct npc_data *nd; + int type=conv_num(st,& (st->stack->stack_data[st->start+2])); + int val=0; + if( st->end > st->start+3 ) + nd=npc_name2id(conv_str(st,& (st->stack->stack_data[st->start+3]))); + else + nd=(struct npc_data *)map_id2bl(st->oid); + + switch(type){ + case 0: val=npc_gettimerevent_tick(nd); break; + case 1: val= (nd->u.scr.nexttimer>=0); break; + case 2: val= nd->u.scr.timeramount; break; + } + push_val(st->stack,C_INT,val); + return 0; +} +/*========================================== + * NPCタイマー値設定 + *------------------------------------------ + */ +int buildin_setnpctimer(struct script_state *st) +{ + int tick; + struct npc_data *nd; + tick=conv_num(st,& (st->stack->stack_data[st->start+2])); + if( st->end > st->start+3 ) + nd=npc_name2id(conv_str(st,& (st->stack->stack_data[st->start+3]))); + else + nd=(struct npc_data *)map_id2bl(st->oid); + + npc_settimerevent_tick(nd,tick); + return 0; +} + +/*========================================== + * 天の声アナウンス + *------------------------------------------ + */ +int buildin_announce(struct script_state *st) +{ + char *str; + int flag; + str=conv_str(st,& (st->stack->stack_data[st->start+2])); + flag=conv_num(st,& (st->stack->stack_data[st->start+3])); + + if(flag&0x0f){ + struct block_list *bl=(flag&0x08)? map_id2bl(st->oid) : + (struct block_list *)script_rid2sd(st); + clif_GMmessage(bl,str,strlen(str)+1,flag); + }else + intif_GMmessage(str,strlen(str)+1,flag); + return 0; +} +/*========================================== + * 天の声アナウンス(特定マップ) + *------------------------------------------ + */ +int buildin_mapannounce_sub(struct block_list *bl,va_list ap) +{ + char *str; + int len,flag; + str=va_arg(ap,char *); + len=va_arg(ap,int); + flag=va_arg(ap,int); + clif_GMmessage(bl,str,len,flag|3); + return 0; +} +int buildin_mapannounce(struct script_state *st) +{ + char *mapname,*str; + int flag,m; + + mapname=conv_str(st,& (st->stack->stack_data[st->start+2])); + str=conv_str(st,& (st->stack->stack_data[st->start+3])); + flag=conv_num(st,& (st->stack->stack_data[st->start+4])); + + if( (m=map_mapname2mapid(mapname))<0 ) + return 0; + map_foreachinarea(buildin_mapannounce_sub, + m,0,0,map[m].xs,map[m].ys,BL_PC, str,strlen(str)+1,flag&0x10); + return 0; +} +/*========================================== + * 天の声アナウンス(特定エリア) + *------------------------------------------ + */ +int buildin_areaannounce(struct script_state *st) +{ + char *map,*str; + int flag,m; + int x0,y0,x1,y1; + + map=conv_str(st,& (st->stack->stack_data[st->start+2])); + x0=conv_num(st,& (st->stack->stack_data[st->start+3])); + y0=conv_num(st,& (st->stack->stack_data[st->start+4])); + x1=conv_num(st,& (st->stack->stack_data[st->start+5])); + y1=conv_num(st,& (st->stack->stack_data[st->start+6])); + str=conv_str(st,& (st->stack->stack_data[st->start+7])); + flag=conv_num(st,& (st->stack->stack_data[st->start+8])); + + if( (m=map_mapname2mapid(map))<0 ) + return 0; + + map_foreachinarea(buildin_mapannounce_sub, + m,x0,y0,x1,y1,BL_PC, str,strlen(str)+1,flag&0x10 ); + return 0; +} +/*========================================== + * ユーザー数所得 + *------------------------------------------ + */ +int buildin_getusers(struct script_state *st) +{ + int flag=conv_num(st,& (st->stack->stack_data[st->start+2])); + struct block_list *bl=map_id2bl((flag&0x08)?st->oid:st->rid); + int val=0; + switch(flag&0x07){ + case 0: val=map[bl->m].users; break; + case 1: val=map_getusers(); break; + } + push_val(st->stack,C_INT,val); + return 0; +} +/*========================================== + * マップ指定ユーザー数所得 + *------------------------------------------ + */ +int buildin_getmapusers(struct script_state *st) +{ + char *str; + int m; + str=conv_str(st,& (st->stack->stack_data[st->start+2])); + if( (m=map_mapname2mapid(str))< 0){ + push_val(st->stack,C_INT,-1); + return 0; + } + push_val(st->stack,C_INT,map[m].users); + return 0; +} +/*========================================== + * エリア指定ユーザー数所得 + *------------------------------------------ + */ +int buildin_getareausers_sub(struct block_list *bl,va_list ap) +{ + int *users=va_arg(ap,int *); + (*users)++; + return 0; +} +int buildin_getareausers(struct script_state *st) +{ + char *str; + int m,x0,y0,x1,y1,users=0; + str=conv_str(st,& (st->stack->stack_data[st->start+2])); + x0=conv_num(st,& (st->stack->stack_data[st->start+3])); + y0=conv_num(st,& (st->stack->stack_data[st->start+4])); + x1=conv_num(st,& (st->stack->stack_data[st->start+5])); + y1=conv_num(st,& (st->stack->stack_data[st->start+6])); + if( (m=map_mapname2mapid(str))< 0){ + push_val(st->stack,C_INT,-1); + return 0; + } + map_foreachinarea(buildin_getareausers_sub, + m,x0,y0,x1,y1,BL_PC,&users); + push_val(st->stack,C_INT,users); + return 0; +} + +/*========================================== + * エリア指定ドロップアイテム数所得 + *------------------------------------------ + */ +int buildin_getareadropitem_sub(struct block_list *bl,va_list ap) +{ + int item=va_arg(ap,int); + int *amount=va_arg(ap,int *); + struct flooritem_data *drop=(struct flooritem_data *)bl; + + if(drop->item_data.nameid==item) + (*amount)+=drop->item_data.amount; + + return 0; +} +int buildin_getareadropitem(struct script_state *st) +{ + char *str; + int m,x0,y0,x1,y1,item,amount=0; + struct script_data *data; + + str=conv_str(st,& (st->stack->stack_data[st->start+2])); + x0=conv_num(st,& (st->stack->stack_data[st->start+3])); + y0=conv_num(st,& (st->stack->stack_data[st->start+4])); + x1=conv_num(st,& (st->stack->stack_data[st->start+5])); + y1=conv_num(st,& (st->stack->stack_data[st->start+6])); + + data=&(st->stack->stack_data[st->start+7]); + get_val(st,data); + if( data->type==C_STR || data->type==C_CONSTSTR ){ + const char *name=conv_str(st,data); + struct item_data *item_data = itemdb_searchname(name); + item=512; + if( item_data ) + item=item_data->nameid; + }else + item=conv_num(st,data); + + if( (m=map_mapname2mapid(str))< 0){ + push_val(st->stack,C_INT,-1); + return 0; + } + map_foreachinarea(buildin_getareadropitem_sub, + m,x0,y0,x1,y1,BL_ITEM,item,&amount); + push_val(st->stack,C_INT,amount); + return 0; +} +/*========================================== + * NPCの有効化 + *------------------------------------------ + */ +int buildin_enablenpc(struct script_state *st) +{ + char *str; + str=conv_str(st,& (st->stack->stack_data[st->start+2])); + npc_enable(str,1); + return 0; +} +/*========================================== + * NPCの無効化 + *------------------------------------------ + */ +int buildin_disablenpc(struct script_state *st) +{ + char *str; + str=conv_str(st,& (st->stack->stack_data[st->start+2])); + npc_enable(str,0); + return 0; +} + +int buildin_enablearena(struct script_state *st) // Added by RoVeRT +{ + struct npc_data *nd=(struct npc_data *)map_id2bl(st->oid); + struct chat_data *cd; + + + if(nd==NULL || (cd=(struct chat_data *)map_id2bl(nd->chat_id))==NULL) + return 0; + + npc_enable(nd->name,1); + nd->arenaflag=1; + + if(cd->users>=cd->trigger && cd->npc_event[0]) + npc_timer_event(cd->npc_event); + + return 0; +} +int buildin_disablearena(struct script_state *st) // Added by RoVeRT +{ + struct npc_data *nd=(struct npc_data *)map_id2bl(st->oid); + nd->arenaflag=0; + + return 0; +} +/*========================================== + * 隠れているNPCの表示 + *------------------------------------------ + */ +int buildin_hideoffnpc(struct script_state *st) +{ + char *str; + str=conv_str(st,& (st->stack->stack_data[st->start+2])); + npc_enable(str,2); + return 0; +} +/*========================================== + * NPCをハイディング + *------------------------------------------ + */ +int buildin_hideonnpc(struct script_state *st) +{ + char *str; + str=conv_str(st,& (st->stack->stack_data[st->start+2])); + npc_enable(str,4); + return 0; +} +/*========================================== + * 状態異常にかかる + *------------------------------------------ + */ +int buildin_sc_start(struct script_state *st) +{ + struct block_list *bl; + int type,tick,val1; + type=conv_num(st,& (st->stack->stack_data[st->start+2])); + tick=conv_num(st,& (st->stack->stack_data[st->start+3])); + val1=conv_num(st,& (st->stack->stack_data[st->start+4])); + if( st->end>st->start+5 ) //指定したキャラを状態異常にする + bl = map_id2bl(conv_num(st,& (st->stack->stack_data[st->start+5]))); + else + bl = map_id2bl(st->rid); + if(bl->type == BL_PC && ((struct map_session_data *)bl)->state.potionpitcher_flag) + bl = map_id2bl(((struct map_session_data *)bl)->skilltarget); + skill_status_change_start(bl,type,val1,0,0,0,tick,0); + return 0; +} + +/*========================================== + * 状態異常にかかる(確率指定) + *------------------------------------------ + */ +int buildin_sc_start2(struct script_state *st) +{ + struct block_list *bl; + int type,tick,val1,per; + type=conv_num(st,& (st->stack->stack_data[st->start+2])); + tick=conv_num(st,& (st->stack->stack_data[st->start+3])); + val1=conv_num(st,& (st->stack->stack_data[st->start+4])); + per=conv_num(st,& (st->stack->stack_data[st->start+5])); + if( st->end>st->start+6 ) //指定したキャラを状態異常にする + bl = map_id2bl(conv_num(st,& (st->stack->stack_data[st->start+6]))); + else + bl = map_id2bl(st->rid); + if(bl->type == BL_PC && ((struct map_session_data *)bl)->state.potionpitcher_flag) + bl = map_id2bl(((struct map_session_data *)bl)->skilltarget); + if(rand()%10000 < per) + skill_status_change_start(bl,type,val1,0,0,0,tick,0); + return 0; +} + +/*========================================== + * 状態異常が直る + *------------------------------------------ + */ +int buildin_sc_end(struct script_state *st) +{ + struct block_list *bl; + int type; + type=conv_num(st,& (st->stack->stack_data[st->start+2])); + bl = map_id2bl(st->rid); + if(bl->type == BL_PC && ((struct map_session_data *)bl)->state.potionpitcher_flag) + bl = map_id2bl(((struct map_session_data *)bl)->skilltarget); + skill_status_change_end(bl,type,-1); +// if(battle_config.etc_log) +// printf("sc_end : %d %d\n",st->rid,type); + return 0; +} +/*========================================== + * 状態異常耐性を計算した確率を返す + *------------------------------------------ + */ +int buildin_getscrate(struct script_state *st) +{ + struct block_list *bl; + int sc_def=100,sc_def_mdef2,sc_def_vit2,sc_def_int2,sc_def_luk2; + int type,rate,luk; + + type=conv_num(st,& (st->stack->stack_data[st->start+2])); + rate=conv_num(st,& (st->stack->stack_data[st->start+3])); + if( st->end>st->start+4 ) //指定したキャラの耐性を計算する + bl = map_id2bl(conv_num(st,& (st->stack->stack_data[st->start+6]))); + else + bl = map_id2bl(st->rid); + + luk = battle_get_luk(bl); + sc_def_mdef2=100 - (3 + battle_get_mdef(bl) + luk/3); + sc_def_vit2=100 - (3 + battle_get_vit(bl) + luk/3); + sc_def_int2=100 - (3 + battle_get_int(bl) + luk/3); + sc_def_luk2=100 - (3 + luk); + + if(type==SC_STONE || type==SC_FREEZE) + sc_def=sc_def_mdef2; + else if(type==SC_STAN || type==SC_POISON || type==SC_SILENCE) + sc_def=sc_def_vit2; + else if(type==SC_SLEEP || type==SC_CONFUSION || type==SC_BLIND) + sc_def=sc_def_int2; + else if(type==SC_CURSE) + sc_def=sc_def_luk2; + + rate=rate*sc_def/100; + push_val(st->stack,C_INT,rate); + + return 0; + +} + +/*========================================== + * + *------------------------------------------ + */ +int buildin_debugmes(struct script_state *st) +{ + conv_str(st,& (st->stack->stack_data[st->start+2])); + printf("script debug : %d %d : %s\n",st->rid,st->oid,st->stack->stack_data[st->start+2].u.str); + return 0; +} + +/*========================================== + *捕獲アイテム使用 + *------------------------------------------ + */ +int buildin_catchpet(struct script_state *st) +{ + int pet_id; + struct map_session_data *sd; + pet_id= conv_num(st,& (st->stack->stack_data[st->start+2])); + sd=script_rid2sd(st); + pet_catch_process1(sd,pet_id); + return 0; +} + +/*========================================== + *携帯卵孵化機使用 + *------------------------------------------ + */ +int buildin_birthpet(struct script_state *st) +{ + struct map_session_data *sd; + sd=script_rid2sd(st); + clif_sendegg(sd); + return 0; +} + +/*========================================== + * Added - AppleGirl For Advanced Classes, (Updated for Cleaner Script Purposes) + *------------------------------------------ + */ +int buildin_resetlvl(struct script_state *st) +{ + struct map_session_data *sd; + + int type=conv_num(st,& (st->stack->stack_data[st->start+2])); + + sd=script_rid2sd(st); + pc_resetlvl(sd,type); + return 0; +} +/*========================================== + * ステータスリセット + *------------------------------------------ + */ +int buildin_resetstatus(struct script_state *st) +{ + struct map_session_data *sd; + sd=script_rid2sd(st); + pc_resetstate(sd); + return 0; +} + +/*========================================== + * スキルリセット + *------------------------------------------ + */ +int buildin_resetskill(struct script_state *st) +{ + struct map_session_data *sd; + sd=script_rid2sd(st); + pc_resetskill(sd); + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int buildin_changebase(struct script_state *st) +{ + struct map_session_data *sd=NULL; + int vclass; + + if( st->end>st->start+3 ) + sd=map_id2sd(conv_num(st,& (st->stack->stack_data[st->start+3]))); + else + sd=script_rid2sd(st); + + if(sd == NULL) + return 0; + + vclass = conv_num(st,& (st->stack->stack_data[st->start+2])); + if(vclass == 22 && !battle_config.wedding_modifydisplay) + return 0; + +// if(vclass==22) { +// pc_unequipitem(sd,sd->equip_index[9],0); // 装備外 +// } + + sd->view_class = vclass; + + return 0; +} + +/*========================================== + * 性別変換 + *------------------------------------------ + */ +int buildin_changesex(struct script_state *st) { + struct map_session_data *sd = NULL; + sd = script_rid2sd(st); + + if (sd->status.sex == 0) { + sd->status.sex = 1; + sd->sex = 1; + if (sd->status.class == 20 || sd->status.class == 4021) + sd->status.class -= 1; + } else if (sd->status.sex == 1) { + sd->status.sex = 0; + sd->sex = 0; + if(sd->status.class == 19 || sd->status.class == 4020) + sd->status.class += 1; + } + chrif_char_ask_name(-1, sd->status.name, 5, 0, 0, 0, 0, 0, 0); // type: 5 - changesex + chrif_save(sd); + return 0; +} + +/*========================================== + * npcチャット作成 + *------------------------------------------ + */ +int buildin_waitingroom(struct script_state *st) +{ + char *name,*ev=""; + int limit, trigger = 0,pub=1; + name=conv_str(st,& (st->stack->stack_data[st->start+2])); + limit= conv_num(st,& (st->stack->stack_data[st->start+3])); + if(limit==0) + pub=3; + + if( (st->end > st->start+5) ){ + struct script_data* data=&(st->stack->stack_data[st->start+5]); + get_val(st,data); + if(data->type==C_INT){ + // 新Athena仕様(旧Athena仕様と互換性あり) + ev=conv_str(st,& (st->stack->stack_data[st->start+4])); + trigger=conv_num(st,& (st->stack->stack_data[st->start+5])); + }else{ + // eathena仕様 + trigger=conv_num(st,& (st->stack->stack_data[st->start+4])); + ev=conv_str(st,& (st->stack->stack_data[st->start+5])); + } + }else{ + // 旧Athena仕様 + if( st->end > st->start+4 ) + ev=conv_str(st,& (st->stack->stack_data[st->start+4])); + } + chat_createnpcchat( (struct npc_data *)map_id2bl(st->oid), + limit,pub,trigger,name,strlen(name)+1,ev); + return 0; +} +/*========================================== + * npcチャット削除 + *------------------------------------------ + */ +int buildin_delwaitingroom(struct script_state *st) +{ + struct npc_data *nd; + if( st->end > st->start+2 ) + nd=npc_name2id(conv_str(st,& (st->stack->stack_data[st->start+2]))); + else + nd=(struct npc_data *)map_id2bl(st->oid); + chat_deletenpcchat(nd); + return 0; +} +/*========================================== + * npcチャット全員蹴り出す + *------------------------------------------ + */ +int buildin_waitingroomkickall(struct script_state *st) +{ + struct npc_data *nd; + struct chat_data *cd; + + if( st->end > st->start+2 ) + nd=npc_name2id(conv_str(st,& (st->stack->stack_data[st->start+2]))); + else + nd=(struct npc_data *)map_id2bl(st->oid); + + if(nd==NULL || (cd=(struct chat_data *)map_id2bl(nd->chat_id))==NULL ) + return 0; + chat_npckickall(cd); + return 0; +} + +/*========================================== + * npcチャットイベント有効化 + *------------------------------------------ + */ +int buildin_enablewaitingroomevent(struct script_state *st) +{ + struct npc_data *nd; + struct chat_data *cd; + + if( st->end > st->start+2 ) + nd=npc_name2id(conv_str(st,& (st->stack->stack_data[st->start+2]))); + else + nd=(struct npc_data *)map_id2bl(st->oid); + + if(nd==NULL || (cd=(struct chat_data *)map_id2bl(nd->chat_id))==NULL ) + return 0; + chat_enableevent(cd); + return 0; +} + +/*========================================== + * npcチャットイベント無効化 + *------------------------------------------ + */ +int buildin_disablewaitingroomevent(struct script_state *st) +{ + struct npc_data *nd; + struct chat_data *cd; + + if( st->end > st->start+2 ) + nd=npc_name2id(conv_str(st,& (st->stack->stack_data[st->start+2]))); + else + nd=(struct npc_data *)map_id2bl(st->oid); + + if(nd==NULL || (cd=(struct chat_data *)map_id2bl(nd->chat_id))==NULL ) + return 0; + chat_disableevent(cd); + return 0; +} +/*========================================== + * npcチャット状態所得 + *------------------------------------------ + */ +int buildin_getwaitingroomstate(struct script_state *st) +{ + struct npc_data *nd; + struct chat_data *cd; + int val=0,type; + type=conv_num(st,& (st->stack->stack_data[st->start+2])); + if( st->end > st->start+3 ) + nd=npc_name2id(conv_str(st,& (st->stack->stack_data[st->start+3]))); + else + nd=(struct npc_data *)map_id2bl(st->oid); + + if(nd==NULL || (cd=(struct chat_data *)map_id2bl(nd->chat_id))==NULL ){ + push_val(st->stack,C_INT,-1); + return 0; + } + + switch(type){ + case 0: val=cd->users; break; + case 1: val=cd->limit; break; + case 2: val=cd->trigger&0x7f; break; + case 3: val=((cd->trigger&0x80)>0); break; + case 32: val=(cd->users >= cd->limit); break; + case 33: val=(cd->users >= cd->trigger); break; + + case 4: + push_str(st->stack,C_CONSTSTR,cd->title); + return 0; + case 5: + push_str(st->stack,C_CONSTSTR,cd->pass); + return 0; + case 16: + push_str(st->stack,C_CONSTSTR,cd->npc_event); + return 0; + } + push_val(st->stack,C_INT,val); + return 0; +} + +/*========================================== + * チャットメンバー(規定人数)ワープ + *------------------------------------------ + */ +int buildin_warpwaitingpc(struct script_state *st) +{ + int x,y,i,n; + char *str; + struct npc_data *nd=(struct npc_data *)map_id2bl(st->oid); + struct chat_data *cd; + + if(nd==NULL || (cd=(struct chat_data *)map_id2bl(nd->chat_id))==NULL ) + return 0; + + n=cd->trigger&0x7f; + str=conv_str(st,& (st->stack->stack_data[st->start+2])); + x=conv_num(st,& (st->stack->stack_data[st->start+3])); + y=conv_num(st,& (st->stack->stack_data[st->start+4])); + + if( st->end > st->start+5 ) + n=conv_num(st,& (st->stack->stack_data[st->start+5])); + + for(i=0;i<n;i++){ + struct map_session_data *sd=cd->usersd[0]; // リスト先頭のPCを次々に。 + + mapreg_setreg(add_str("$@warpwaitingpc")+(i<<24),sd->bl.id); + + if(strcmp(str,"Random")==0) + pc_randomwarp(sd,3); + else if(strcmp(str,"SavePoint")==0){ + if(map[sd->bl.m].flag.noteleport) // テレポ禁止 + return 0; + + pc_setpos(sd,sd->status.save_point.map, + sd->status.save_point.x,sd->status.save_point.y,3); + }else + pc_setpos(sd,str,x,y,0); + } + mapreg_setreg(add_str("$@warpwaitingpcnum"),n); + return 0; +} +/*========================================== + * RIDのアタッチ + *------------------------------------------ + */ +int buildin_attachrid(struct script_state *st) +{ + st->rid=conv_num(st,& (st->stack->stack_data[st->start+2])); + push_val(st->stack,C_INT, (map_id2sd(st->rid)!=NULL)); + return 0; +} +/*========================================== + * RIDのデタッチ + *------------------------------------------ + */ +int buildin_detachrid(struct script_state *st) +{ + st->rid=0; + return 0; +} +/*========================================== + * 存在チェック + *------------------------------------------ + */ +int buildin_isloggedin(struct script_state *st) +{ + push_val(st->stack,C_INT, map_id2sd( + conv_num(st,& (st->stack->stack_data[st->start+2])) )!=NULL ); + return 0; +} + + +/*========================================== + * + *------------------------------------------ + */ +enum { MF_NOMEMO,MF_NOTELEPORT,MF_NOSAVE,MF_NOBRANCH,MF_NOPENALTY,MF_NOZENYPENALTY,MF_PVP,MF_PVP_NOPARTY,MF_PVP_NOGUILD,MF_GVG,MF_GVG_NOPARTY,MF_NOTRADE,MF_NOSKILL, MF_NOWARP,MF_NOPVP,MF_NOICEWALL, + MF_SNOW, MF_FOG, MF_SAKURA, MF_LEAVES, MF_RAIN }; + +int buildin_setmapflagnosave(struct script_state *st) +{ + int m,x,y; + char *str,*str2; + + str=conv_str(st,& (st->stack->stack_data[st->start+2])); + str2=conv_str(st,& (st->stack->stack_data[st->start+3])); + x=conv_num(st,& (st->stack->stack_data[st->start+4])); + y=conv_num(st,& (st->stack->stack_data[st->start+5])); + m = map_mapname2mapid(str); + if(m >= 0) { + map[m].flag.nosave=1; + memcpy(map[m].save.map,str2,16); + map[m].save.x=x; + map[m].save.y=y; + } + + return 0; +} + +int buildin_setmapflag(struct script_state *st) +{ + int m,i; + char *str; + + str=conv_str(st,& (st->stack->stack_data[st->start+2])); + i=conv_num(st,& (st->stack->stack_data[st->start+3])); + m = map_mapname2mapid(str); + if(m >= 0) { + switch(i) { + case MF_NOMEMO: + map[m].flag.nomemo=1; + break; + case MF_NOTELEPORT: + map[m].flag.noteleport=1; + break; + case MF_NOBRANCH: + map[m].flag.nobranch=1; + break; + case MF_NOPENALTY: + map[m].flag.nopenalty=1; + break; + case MF_PVP_NOPARTY: + map[m].flag.pvp_noparty=1; + break; + case MF_PVP_NOGUILD: + map[m].flag.pvp_noguild=1; + break; + case MF_GVG_NOPARTY: + map[m].flag.gvg_noparty=1; + break; + case MF_NOZENYPENALTY: + map[m].flag.nozenypenalty=1; + break; + case MF_NOTRADE: + map[m].flag.notrade=1; + break; + case MF_NOSKILL: + map[m].flag.noskill=1; + break; + case MF_NOWARP: + map[m].flag.nowarp=1; + break; + case MF_NOPVP: + map[m].flag.nopvp=1; + break; + case MF_NOICEWALL: // [Valaris] + map[m].flag.noicewall=1; + break; + case MF_SNOW: // [Valaris] + map[m].flag.snow=1; + break; + case MF_FOG: // [Valaris] + map[m].flag.fog=1; + break; + case MF_SAKURA: // [Valaris] + map[m].flag.sakura=1; + break; + case MF_LEAVES: // [Valaris] + map[m].flag.leaves=1; + break; + case MF_RAIN: // [Valaris] + map[m].flag.rain=1; + break; + } + } + + return 0; +} + +int buildin_removemapflag(struct script_state *st) +{ + int m,i; + char *str; + + str=conv_str(st,& (st->stack->stack_data[st->start+2])); + i=conv_num(st,& (st->stack->stack_data[st->start+3])); + m = map_mapname2mapid(str); + if(m >= 0) { + switch(i) { + case MF_NOMEMO: + map[m].flag.nomemo=0; + break; + case MF_NOTELEPORT: + map[m].flag.noteleport=0; + break; + case MF_NOSAVE: + map[m].flag.nosave=0; + break; + case MF_NOBRANCH: + map[m].flag.nobranch=0; + break; + case MF_NOPENALTY: + map[m].flag.nopenalty=0; + break; + case MF_PVP_NOPARTY: + map[m].flag.pvp_noparty=0; + break; + case MF_PVP_NOGUILD: + map[m].flag.pvp_noguild=0; + break; + case MF_GVG_NOPARTY: + map[m].flag.gvg_noparty=0; + break; + case MF_NOZENYPENALTY: + map[m].flag.nozenypenalty=0; + break; + case MF_NOSKILL: + map[m].flag.noskill=0; + break; + case MF_NOWARP: + map[m].flag.nowarp=0; + break; + case MF_NOPVP: + map[m].flag.nopvp=0; + break; + case MF_NOICEWALL: // [Valaris] + map[m].flag.noicewall=0; + break; + case MF_SNOW: // [Valaris] + map[m].flag.snow=0; + break; + case MF_FOG: // [Valaris] + map[m].flag.fog=0; + break; + case MF_SAKURA: // [Valaris] + map[m].flag.sakura=0; + break; + case MF_LEAVES: // [Valaris] + map[m].flag.leaves=0; + break; + case MF_RAIN: // [Valaris] + map[m].flag.rain=0; + break; + + } + } + + return 0; +} + +int buildin_pvpon(struct script_state *st) +{ + int m,i; + char *str; + struct map_session_data *pl_sd=NULL; + + str=conv_str(st,& (st->stack->stack_data[st->start+2])); + m = map_mapname2mapid(str); + if(m >= 0 && !map[m].flag.pvp && !map[m].flag.nopvp) { + map[m].flag.pvp = 1; + clif_send0199(m,1); + + if(battle_config.pk_mode) // disable ranking functions if pk_mode is on [Valaris] + return 0; + + for(i=0;i<fd_max;i++){ //人数分ループ + if(session[i] && (pl_sd=session[i]->session_data) && pl_sd->state.auth){ + if(m == pl_sd->bl.m && pl_sd->pvp_timer == -1) { + pl_sd->pvp_timer=add_timer(gettick()+200,pc_calc_pvprank_timer,pl_sd->bl.id,0); + pl_sd->pvp_rank=0; + pl_sd->pvp_lastusers=0; + pl_sd->pvp_point=5; + } + } + } + } + + return 0; +} + +int buildin_pvpoff(struct script_state *st) +{ + int m,i; + char *str; + struct map_session_data *pl_sd=NULL; + + str=conv_str(st,& (st->stack->stack_data[st->start+2])); + m = map_mapname2mapid(str); + if(m >= 0 && map[m].flag.pvp && map[m].flag.nopvp) { + map[m].flag.pvp = 0; + clif_send0199(m,0); + + if(battle_config.pk_mode) // disable ranking options if pk_mode is on [Valaris] + return 0; + + for(i=0;i<fd_max;i++){ //人数分ループ + if(session[i] && (pl_sd=session[i]->session_data) && pl_sd->state.auth){ + if(m == pl_sd->bl.m) { + clif_pvpset(pl_sd,0,0,2); + if(pl_sd->pvp_timer != -1) { + delete_timer(pl_sd->pvp_timer,pc_calc_pvprank_timer); + pl_sd->pvp_timer = -1; + } + } + } + } + } + + return 0; +} + +int buildin_gvgon(struct script_state *st) +{ + int m; + char *str; + + str=conv_str(st,& (st->stack->stack_data[st->start+2])); + m = map_mapname2mapid(str); + if(m >= 0 && !map[m].flag.gvg) { + map[m].flag.gvg = 1; + clif_send0199(m,3); + } + + return 0; +} +int buildin_gvgoff(struct script_state *st) +{ + int m; + char *str; + + str=conv_str(st,& (st->stack->stack_data[st->start+2])); + m = map_mapname2mapid(str); + if(m >= 0 && map[m].flag.gvg) { + map[m].flag.gvg = 0; + clif_send0199(m,0); + } + + return 0; +} +/*========================================== + * NPCエモーション + *------------------------------------------ + */ + +int buildin_emotion(struct script_state *st) +{ + int type; + type=conv_num(st,& (st->stack->stack_data[st->start+2])); + if(type < 0 || type > 100) + return 0; + clif_emotion(map_id2bl(st->oid),type); + return 0; +} + +int buildin_maprespawnguildid_sub(struct block_list *bl,va_list ap) +{ + int g_id=va_arg(ap,int); + int flag=va_arg(ap,int); + struct map_session_data *sd=NULL; + struct mob_data *md=NULL; + + if(bl->type == BL_PC) + sd=(struct map_session_data*)bl; + if(bl->type == BL_MOB) + md=(struct mob_data *)bl; + + if(sd){ + if((sd->status.guild_id == g_id) && (flag&1)) + pc_setpos(sd,sd->status.save_point.map,sd->status.save_point.x,sd->status.save_point.y,3); + else if((sd->status.guild_id != g_id) && (flag&2)) + pc_setpos(sd,sd->status.save_point.map,sd->status.save_point.x,sd->status.save_point.y,3); + else if (sd->status.guild_id == 0) // Warp out players not in guild [Valaris] + pc_setpos(sd,sd->status.save_point.map,sd->status.save_point.x,sd->status.save_point.y,3); // end addition [Valaris] + } + if(md && flag&4){ + if(md->class < 1285 || md->class > 1288) + mob_delete(md); + } + return 0; +} +int buildin_maprespawnguildid(struct script_state *st) +{ + char *mapname=conv_str(st,& (st->stack->stack_data[st->start+2])); + int g_id=conv_num(st,& (st->stack->stack_data[st->start+3])); + int flag=conv_num(st,& (st->stack->stack_data[st->start+4])); + + int m=map_mapname2mapid(mapname); + + if(m) map_foreachinarea(buildin_maprespawnguildid_sub,m,0,0,map[m].xs-1,map[m].ys-1,BL_NUL,g_id,flag); + return 0; +} + +int buildin_agitstart(struct script_state *st) +{ + if(agit_flag==1) return 1; // Agit already Start. + agit_flag=1; + guild_agit_start(); + return 0; +} + +int buildin_agitend(struct script_state *st) +{ + if(agit_flag==0) return 1; // Agit already End. + agit_flag=0; + guild_agit_end(); + return 0; +} +/*========================================== + * agitcheck 1; // choice script + * if(@agit_flag == 1) goto agit; + * if(agitcheck(0) == 1) goto agit; + *------------------------------------------ + */ +int buildin_agitcheck(struct script_state *st) +{ + struct map_session_data *sd; + int cond; + + sd=script_rid2sd(st); + cond=conv_num(st,& (st->stack->stack_data[st->start+2])); + + if(cond == 0) { + if (agit_flag==1) push_val(st->stack,C_INT,1); + if (agit_flag==0) push_val(st->stack,C_INT,0); + } else { + if (agit_flag==1) pc_setreg(sd,add_str("@agit_flag"),1); + if (agit_flag==0) pc_setreg(sd,add_str("@agit_flag"),0); + } + return 0; +} +int buildin_flagemblem(struct script_state *st) +{ + int g_id=conv_num(st,& (st->stack->stack_data[st->start+2])); + + if(g_id < 0) return 0; + +// printf("Script.c: [FlagEmblem] GuildID=%d, Emblem=%d.\n", g->guild_id, g->emblem_id); + ((struct npc_data *)map_id2bl(st->oid))->u.scr.guild_id = g_id; + return 1; +} + +int buildin_getcastlename(struct script_state *st) +{ + char *mapname=conv_str(st,& (st->stack->stack_data[st->start+2])); + struct guild_castle *gc; + int i; + char *buf=NULL; + for(i=0;i<MAX_GUILDCASTLE;i++){ + if( (gc=guild_castle_search(i)) != NULL ){ + if(strcmp(mapname,gc->map_name)==0){ + buf=(char *)aCalloc(24,sizeof(char)); + strncpy(buf,gc->castle_name,24); + break; + } + } + } + if(buf) + push_str(st->stack,C_STR,buf); + else + push_str(st->stack,C_CONSTSTR,""); + return 0; +} + +int buildin_getcastledata(struct script_state *st) +{ + char *mapname=conv_str(st,& (st->stack->stack_data[st->start+2])); + int index=conv_num(st,& (st->stack->stack_data[st->start+3])); + char *event=NULL; + struct guild_castle *gc; + int i,j; + + if( st->end>st->start+4 && index==0){ + for(i=0,j=-1;i<MAX_GUILDCASTLE;i++) + if( (gc=guild_castle_search(i)) != NULL && + strcmp(mapname,gc->map_name)==0 ) + j=i; + if(j>=0){ + event=conv_str(st,& (st->stack->stack_data[st->start+4])); + guild_addcastleinfoevent(j,17,event); + } + } + + for(i=0;i<MAX_GUILDCASTLE;i++){ + if( (gc=guild_castle_search(i)) != NULL ){ + if(strcmp(mapname,gc->map_name)==0){ + switch(index){ + case 0: for(j=1;j<26;j++) guild_castledataload(gc->castle_id,j); break; // Initialize[AgitInit] + case 1: push_val(st->stack,C_INT,gc->guild_id); break; + case 2: push_val(st->stack,C_INT,gc->economy); break; + case 3: push_val(st->stack,C_INT,gc->defense); break; + case 4: push_val(st->stack,C_INT,gc->triggerE); break; + case 5: push_val(st->stack,C_INT,gc->triggerD); break; + case 6: push_val(st->stack,C_INT,gc->nextTime); break; + case 7: push_val(st->stack,C_INT,gc->payTime); break; + case 8: push_val(st->stack,C_INT,gc->createTime); break; + case 9: push_val(st->stack,C_INT,gc->visibleC); break; + case 10: push_val(st->stack,C_INT,gc->visibleG0); break; + case 11: push_val(st->stack,C_INT,gc->visibleG1); break; + case 12: push_val(st->stack,C_INT,gc->visibleG2); break; + case 13: push_val(st->stack,C_INT,gc->visibleG3); break; + case 14: push_val(st->stack,C_INT,gc->visibleG4); break; + case 15: push_val(st->stack,C_INT,gc->visibleG5); break; + case 16: push_val(st->stack,C_INT,gc->visibleG6); break; + case 17: push_val(st->stack,C_INT,gc->visibleG7); break; + case 18: push_val(st->stack,C_INT,gc->Ghp0); break; + case 19: push_val(st->stack,C_INT,gc->Ghp1); break; + case 20: push_val(st->stack,C_INT,gc->Ghp2); break; + case 21: push_val(st->stack,C_INT,gc->Ghp3); break; + case 22: push_val(st->stack,C_INT,gc->Ghp4); break; + case 23: push_val(st->stack,C_INT,gc->Ghp5); break; + case 24: push_val(st->stack,C_INT,gc->Ghp6); break; + case 25: push_val(st->stack,C_INT,gc->Ghp7); break; + default: + push_val(st->stack,C_INT,0); break; + } + return 0; + } + } + } + push_val(st->stack,C_INT,0); + return 0; +} + +int buildin_setcastledata(struct script_state *st) +{ + char *mapname=conv_str(st,& (st->stack->stack_data[st->start+2])); + int index=conv_num(st,& (st->stack->stack_data[st->start+3])); + int value=conv_num(st,& (st->stack->stack_data[st->start+4])); + struct guild_castle *gc; + int i; + + for(i=0;i<MAX_GUILDCASTLE;i++){ + if( (gc=guild_castle_search(i)) != NULL ){ + if(strcmp(mapname,gc->map_name)==0){ + // Save Data byself First + switch(index){ + case 1: gc->guild_id = value; break; + case 2: gc->economy = value; break; + case 3: gc->defense = value; break; + case 4: gc->triggerE = value; break; + case 5: gc->triggerD = value; break; + case 6: gc->nextTime = value; break; + case 7: gc->payTime = value; break; + case 8: gc->createTime = value; break; + case 9: gc->visibleC = value; break; + case 10: gc->visibleG0 = value; break; + case 11: gc->visibleG1 = value; break; + case 12: gc->visibleG2 = value; break; + case 13: gc->visibleG3 = value; break; + case 14: gc->visibleG4 = value; break; + case 15: gc->visibleG5 = value; break; + case 16: gc->visibleG6 = value; break; + case 17: gc->visibleG7 = value; break; + case 18: gc->Ghp0 = value; break; + case 19: gc->Ghp1 = value; break; + case 20: gc->Ghp2 = value; break; + case 21: gc->Ghp3 = value; break; + case 22: gc->Ghp4 = value; break; + case 23: gc->Ghp5 = value; break; + case 24: gc->Ghp6 = value; break; + case 25: gc->Ghp7 = value; break; + default: return 0; + } + guild_castledatasave(gc->castle_id,index,value); + return 0; + } + } + } + return 0; +} + +/* ===================================================================== + * ギルド情報を要求する + * --------------------------------------------------------------------- + */ +int buildin_requestguildinfo(struct script_state *st) +{ + int guild_id=conv_num(st,& (st->stack->stack_data[st->start+2])); + char *event=NULL; + + if( st->end>st->start+3 ) + event=conv_str(st,& (st->stack->stack_data[st->start+3])); + + if(guild_id>0) + guild_npc_request_info(guild_id,event); + return 0; +} + +/* ===================================================================== + * カードの数を得る + * --------------------------------------------------------------------- + */ +int buildin_getequipcardcnt(struct script_state *st) +{ + int i,num; + struct map_session_data *sd; + int c=4; + + num=conv_num(st,& (st->stack->stack_data[st->start+2])); + sd=script_rid2sd(st); + i=pc_checkequip(sd,equip[num-1]); + if(sd->status.inventory[i].card[0] == 0x00ff){ // 製造武器はカードなし + push_val(st->stack,C_INT,0); + return 0; + } + do{ + if( (sd->status.inventory[i].card[c-1] > 4000) && + (sd->status.inventory[i].card[c-1] < 5000)){ + + push_val(st->stack,C_INT,(c)); + return 0; + } + }while(c--); + push_val(st->stack,C_INT,0); + return 0; +} + +/* ================================================================ + * カード取り外し成功 + * ---------------------------------------------------------------- + */ +int buildin_successremovecards(struct script_state *st) +{ + int i,num,cardflag=0,flag; + struct map_session_data *sd; + struct item item_tmp; + int c=4; + + num=conv_num(st,& (st->stack->stack_data[st->start+2])); + sd=script_rid2sd(st); + i=pc_checkequip(sd,equip[num-1]); + if(sd->status.inventory[i].card[0]==0x00ff){ // 製造武器は処理しない + return 0; + } + do{ + if( (sd->status.inventory[i].card[c-1] > 4000) && + (sd->status.inventory[i].card[c-1] < 5000)){ + + cardflag = 1; + item_tmp.id=0,item_tmp.nameid=sd->status.inventory[i].card[c-1]; + item_tmp.equip=0,item_tmp.identify=1,item_tmp.refine=0; + item_tmp.attribute=0; + item_tmp.card[0]=0,item_tmp.card[1]=0,item_tmp.card[2]=0,item_tmp.card[3]=0; + + if((flag=pc_additem(sd,&item_tmp,1))){ // 持てないならドロップ + clif_additem(sd,0,0,flag); + map_addflooritem(&item_tmp,1,sd->bl.m,sd->bl.x,sd->bl.y,NULL,NULL,NULL,0); + } + } + }while(c--); + + if(cardflag == 1){ // カードを取り除いたアイテム所得 + flag=0; + item_tmp.id=0,item_tmp.nameid=sd->status.inventory[i].nameid; + item_tmp.equip=0,item_tmp.identify=1,item_tmp.refine=sd->status.inventory[i].refine; + item_tmp.attribute=sd->status.inventory[i].attribute; + item_tmp.card[0]=0,item_tmp.card[1]=0,item_tmp.card[2]=0,item_tmp.card[3]=0; + pc_delitem(sd,i,1,0); + if((flag=pc_additem(sd,&item_tmp,1))){ // もてないならドロップ + clif_additem(sd,0,0,flag); + map_addflooritem(&item_tmp,1,sd->bl.m,sd->bl.x,sd->bl.y,NULL,NULL,NULL,0); + } + clif_misceffect(&sd->bl,3); + return 0; + } + return 0; +} + +/* ================================================================ + * カード取り外し失敗 slot,type + * type=0: 両方損失、1:カード損失、2:武具損失、3:損失無し + * ---------------------------------------------------------------- + */ +int buildin_failedremovecards(struct script_state *st) +{ + int i,num,cardflag=0,flag,typefail; + struct map_session_data *sd; + struct item item_tmp; + int c=4; + + num=conv_num(st,& (st->stack->stack_data[st->start+2])); + typefail=conv_num(st,& (st->stack->stack_data[st->start+3])); + sd=script_rid2sd(st); + i=pc_checkequip(sd,equip[num-1]); + if(sd->status.inventory[i].card[0]==0x00ff){ // 製造武器は処理しない + return 0; + } + do{ + if(( sd->status.inventory[i].card[c-1] > 4000) && + (sd->status.inventory[i].card[c-1] < 5000)){ + + cardflag = 1; + + if(typefail == 2){ // 武具のみ損失なら、カードは受け取らせる + item_tmp.id=0,item_tmp.nameid=sd->status.inventory[i].card[c-1]; + item_tmp.equip=0,item_tmp.identify=1,item_tmp.refine=0; + item_tmp.attribute=0; + item_tmp.card[0]=0,item_tmp.card[1]=0,item_tmp.card[2]=0,item_tmp.card[3]=0; + if((flag=pc_additem(sd,&item_tmp,1))){ + clif_additem(sd,0,0,flag); + map_addflooritem(&item_tmp,1,sd->bl.m,sd->bl.x,sd->bl.y,NULL,NULL,NULL,0); + } + } + } + }while(c--); + + if(cardflag == 1){ + + if(typefail == 0 || typefail == 2){ // 武具損失 + pc_delitem(sd,i,1,0); + clif_misceffect(&sd->bl,2); + return 0; + } + if(typefail == 1){ // カードのみ損失(武具を返す) + flag=0; + item_tmp.id=0,item_tmp.nameid=sd->status.inventory[i].nameid; + item_tmp.equip=0,item_tmp.identify=1,item_tmp.refine=sd->status.inventory[i].refine; + item_tmp.attribute=sd->status.inventory[i].attribute; + item_tmp.card[0]=0,item_tmp.card[1]=0,item_tmp.card[2]=0,item_tmp.card[3]=0; + pc_delitem(sd,i,1,0); + if((flag=pc_additem(sd,&item_tmp,1))){ + clif_additem(sd,0,0,flag); + map_addflooritem(&item_tmp,1,sd->bl.m,sd->bl.x,sd->bl.y,NULL,NULL,NULL,0); + } + } + clif_misceffect(&sd->bl,2); + return 0; + } + return 0; +} + +int buildin_mapwarp(struct script_state *st) // Added by RoVeRT +{ + int x,y,m; + char *str; + char *mapname; + int x0,y0,x1,y1; + + mapname=conv_str(st,& (st->stack->stack_data[st->start+2])); + x0=0; + y0=0; + x1=map[map_mapname2mapid(mapname)].xs; + y1=map[map_mapname2mapid(mapname)].ys; + str=conv_str(st,& (st->stack->stack_data[st->start+3])); + x=conv_num(st,& (st->stack->stack_data[st->start+4])); + y=conv_num(st,& (st->stack->stack_data[st->start+5])); + + if( (m=map_mapname2mapid(mapname))< 0) + return 0; + + map_foreachinarea(buildin_areawarp_sub, + m,x0,y0,x1,y1,BL_PC, str,x,y ); + return 0; +} + +int buildin_cmdothernpc(struct script_state *st) // Added by RoVeRT +{ + char *npc,*command; + + npc=conv_str(st,& (st->stack->stack_data[st->start+2])); + command=conv_str(st,& (st->stack->stack_data[st->start+3])); + + npc_command(map_id2sd(st->rid),npc,command); + return 0; +} + +int buildin_inittimer(struct script_state *st) // Added by RoVeRT +{ +// struct npc_data *nd=(struct npc_data*)map_id2bl(st->oid); + +// nd->lastaction=nd->timer=gettick(); + npc_do_ontimer(st->oid, map_id2sd(st->rid), 1); + + return 0; +} + +int buildin_stoptimer(struct script_state *st) // Added by RoVeRT +{ +// struct npc_data *nd=(struct npc_data*)map_id2bl(st->oid); + +// nd->lastaction=nd->timer=-1; + npc_do_ontimer(st->oid, map_id2sd(st->rid), 0); + + return 0; +} + +int buildin_mobcount_sub(struct block_list *bl,va_list ap) // Added by RoVeRT +{ + char *event=va_arg(ap,char *); + int *c=va_arg(ap,int *); + + if(strcmp(event,((struct mob_data *)bl)->npc_event)==0) + (*c)++; + return 0; +} + +int buildin_mobcount(struct script_state *st) // Added by RoVeRT +{ + char *mapname,*event; + int m,c=0; + mapname=conv_str(st,& (st->stack->stack_data[st->start+2])); + event=conv_str(st,& (st->stack->stack_data[st->start+3])); + + if( (m=map_mapname2mapid(mapname))<0 ) { + push_val(st->stack,C_INT,-1); + return 0; + } + map_foreachinarea(buildin_mobcount_sub, + m,0,0,map[m].xs,map[m].ys,BL_MOB, event,&c ); + + push_val(st->stack,C_INT, (c - 1)); + + return 0; +} +int buildin_marriage(struct script_state *st) +{ + char *partner=conv_str(st,& (st->stack->stack_data[st->start+2])); + struct map_session_data *sd=script_rid2sd(st); + struct map_session_data *p_sd=map_nick2sd(partner); + + if(sd==NULL || p_sd==NULL || pc_marriage(sd,p_sd) < 0){ + push_val(st->stack,C_INT,0); + return 0; + } + push_val(st->stack,C_INT,1); + return 0; +} +int buildin_wedding_effect(struct script_state *st) +{ + struct map_session_data *sd=script_rid2sd(st); + + if(sd==NULL) + return 0; + clif_wedding_effect(&sd->bl); + return 0; +} +int buildin_divorce(struct script_state *st) +{ + struct map_session_data *sd=script_rid2sd(st); + if(sd==NULL || pc_divorce(sd) < 0){ + push_val(st->stack,C_INT,0); + return 0; + } + push_val(st->stack,C_INT,1); + return 0; +} + +/*================================================ + * Script for Displaying MOB Information [Valaris] + *------------------------------------------------ + */ +int buildin_strmobinfo(struct script_state *st) +{ + + int num=conv_num(st,& (st->stack->stack_data[st->start+2])); + int class=conv_num(st,& (st->stack->stack_data[st->start+3])); + + if(num<=0 || num>=8 || (class>=0 && class<=1000) || class >2000) + return 0; + + if(num==1) { + char *buf; + buf=calloc(24, 1); + buf=mob_db[class].name; + push_str(st->stack,C_STR,buf); + return 0; + } + else if(num==2) { + char *buf; + buf=calloc(24, 1); + buf=mob_db[class].jname; + push_str(st->stack,C_STR,buf); + return 0; + } + else if(num==3) + push_val(st->stack,C_INT,mob_db[class].lv); + else if(num==4) + push_val(st->stack,C_INT,mob_db[class].max_hp); + else if(num==5) + push_val(st->stack,C_INT,mob_db[class].max_sp); + else if(num==6) + push_val(st->stack,C_INT,mob_db[class].base_exp); + else if(num==7) + push_val(st->stack,C_INT,mob_db[class].job_exp); + return 0; +} + +/*========================================== + * Summon guardians [Valaris] + *------------------------------------------ + */ +int buildin_guardian(struct script_state *st) +{ + int class=0,amount=1,x=0,y=0,guardian=0; + char *str,*map,*event=""; + + map =conv_str(st,& (st->stack->stack_data[st->start+2])); + x =conv_num(st,& (st->stack->stack_data[st->start+3])); + y =conv_num(st,& (st->stack->stack_data[st->start+4])); + str =conv_str(st,& (st->stack->stack_data[st->start+5])); + class=conv_num(st,& (st->stack->stack_data[st->start+6])); + amount=conv_num(st,& (st->stack->stack_data[st->start+7])); + event=conv_str(st,& (st->stack->stack_data[st->start+8])); + if( st->end>st->start+9 ) + guardian=conv_num(st,& (st->stack->stack_data[st->start+9])); + + mob_spawn_guardian(map_id2sd(st->rid),map,x,y,str,class,amount,event,guardian); + + return 0; +} + +/*================================================ + * Script for Displaying Guardian Info [Valaris] + *------------------------------------------------ + */ +int buildin_guardianinfo(struct script_state *st) +{ + int guardian=conv_num(st,& (st->stack->stack_data[st->start+2])); + struct map_session_data *sd=script_rid2sd(st); + struct guild_castle *gc=guild_mapname2gc(map[sd->bl.m].name); + + if(guardian==0 && gc->visibleG0 == 1) push_val(st->stack,C_INT,gc->Ghp0); + if(guardian==1 && gc->visibleG1 == 1) push_val(st->stack,C_INT,gc->Ghp1); + if(guardian==2 && gc->visibleG2 == 1) push_val(st->stack,C_INT,gc->Ghp2); + if(guardian==3 && gc->visibleG3 == 1) push_val(st->stack,C_INT,gc->Ghp3); + if(guardian==4 && gc->visibleG4 == 1) push_val(st->stack,C_INT,gc->Ghp4); + if(guardian==5 && gc->visibleG5 == 1) push_val(st->stack,C_INT,gc->Ghp5); + if(guardian==6 && gc->visibleG6 == 1) push_val(st->stack,C_INT,gc->Ghp6); + if(guardian==7 && gc->visibleG7 == 1) push_val(st->stack,C_INT,gc->Ghp7); + else push_val(st->stack,C_INT,-1); + + return 0; +} +/*========================================== + * IDからItem名 + *------------------------------------------ + */ +int buildin_getitemname(struct script_state *st) +{ + int item_id; + struct item_data *i_data; + char *item_name; + + item_id=conv_num(st,& (st->stack->stack_data[st->start+2])); + + i_data = NULL; + i_data = itemdb_search(item_id); + item_name=(char *)aCalloc(24,sizeof(char)); + + strncpy(item_name,i_data->jname,23); + push_str(st->stack,C_STR,item_name); + return 0; +} + +/*========================================== + * petskillbonus [Valaris] + *------------------------------------------ + */ + +int buildin_petskillbonus(struct script_state *st) +{ + int type,val,duration,timer; + struct pet_data *pd; + + struct map_session_data *sd=script_rid2sd(st); + + if(sd==NULL || sd->pd==NULL) + return 0; + + pd=sd->pd; + + if(pd==NULL) + return 0; + + type=conv_num(st,& (st->stack->stack_data[st->start+2])); + val=conv_num(st,& (st->stack->stack_data[st->start+3])); + duration=conv_num(st,& (st->stack->stack_data[st->start+4])); + timer=conv_num(st,& (st->stack->stack_data[st->start+5])); + + pd->skillbonusduration=-1; + pd->skillbonustimer=-1; + + pet_skill_bonus(sd,pd,type,val,duration,timer,0); + + return 0; +} + +/*========================================== + * pet looting [Valaris] + *------------------------------------------ + */ +int buildin_petloot(struct script_state *st) +{ + int max; + struct pet_data *pd; + + struct map_session_data *sd=script_rid2sd(st); + if(sd==NULL || sd->pd==NULL) + return 0; + + pd=sd->pd; + + if(pd==NULL) + return 0; + + max=conv_num(st,& (st->stack->stack_data[st->start+2])); + + if(!max) + return 0; + + pd->loot=1; + pd->lootmax=max; + + return 0; +} +/*========================================== + * PCの所持品情報読み取り + *------------------------------------------ + */ +int buildin_getinventorylist(struct script_state *st) +{ + struct map_session_data *sd=script_rid2sd(st); + int i,j=0; + if(!sd) return 0; + for(i=0;i<MAX_INVENTORY;i++){ + if(sd->status.inventory[i].nameid > 0 && sd->status.inventory[i].amount > 0){ + pc_setreg(sd,add_str("@inventorylist_id")+(j<<24),sd->status.inventory[i].nameid); + pc_setreg(sd,add_str("@inventorylist_amount")+(j<<24),sd->status.inventory[i].amount); + pc_setreg(sd,add_str("@inventorylist_equip")+(j<<24),sd->status.inventory[i].equip); + pc_setreg(sd,add_str("@inventorylist_refine")+(j<<24),sd->status.inventory[i].refine); + pc_setreg(sd,add_str("@inventorylist_identify")+(j<<24),sd->status.inventory[i].identify); + pc_setreg(sd,add_str("@inventorylist_attribute")+(j<<24),sd->status.inventory[i].attribute); + pc_setreg(sd,add_str("@inventorylist_card1")+(j<<24),sd->status.inventory[i].card[0]); + pc_setreg(sd,add_str("@inventorylist_card2")+(j<<24),sd->status.inventory[i].card[1]); + pc_setreg(sd,add_str("@inventorylist_card3")+(j<<24),sd->status.inventory[i].card[2]); + pc_setreg(sd,add_str("@inventorylist_card4")+(j<<24),sd->status.inventory[i].card[3]); + j++; + } + } + pc_setreg(sd,add_str("@inventorylist_count"),j); + return 0; +} + +int buildin_getskilllist(struct script_state *st) +{ + struct map_session_data *sd=script_rid2sd(st); + int i,j=0; + if(!sd) return 0; + for(i=0;i<MAX_SKILL;i++){ + if(sd->status.skill[i].id > 0 && sd->status.skill[i].lv > 0){ + pc_setreg(sd,add_str("@skilllist_id")+(j<<24),sd->status.skill[i].id); + pc_setreg(sd,add_str("@skilllist_lv")+(j<<24),sd->status.skill[i].lv); + pc_setreg(sd,add_str("@skilllist_flag")+(j<<24),sd->status.skill[i].flag); + j++; + } + } + pc_setreg(sd,add_str("@skilllist_count"),j); + return 0; +} + +int buildin_clearitem(struct script_state *st) +{ + struct map_session_data *sd=script_rid2sd(st); + int i; + if(sd==NULL) return 0; + for (i=0; i<MAX_INVENTORY; i++) { + if (sd->status.inventory[i].amount) + pc_delitem(sd, i, sd->status.inventory[i].amount, 0); + } + return 0; +} + +/*========================================== + * NPCクラスチェンジ + * classは変わりたいclass + * typeは通常0なのかな? + *------------------------------------------ + */ +int buildin_classchange(struct script_state *st) +{ + int class,type; + struct block_list *bl=map_id2bl(st->oid); + + if(bl==NULL) return 0; + + class=conv_num(st,& (st->stack->stack_data[st->start+2])); + type=conv_num(st,& (st->stack->stack_data[st->start+3])); + clif_class_change(bl,class,type); + return 0; +} + +/*========================================== + * NPCから発生するエフェクト + *------------------------------------------ + */ +int buildin_misceffect(struct script_state *st) +{ + int type; + + type=conv_num(st,& (st->stack->stack_data[st->start+2])); + if(st->oid) + clif_misceffect2(map_id2bl(st->oid),type); + else{ + struct map_session_data *sd=script_rid2sd(st); + if(sd) + clif_misceffect2(&sd->bl,type); + } + return 0; +} +/*========================================== + * サウンドエフェクト + *------------------------------------------ + */ +int buildin_soundeffect(struct script_state *st) +{ + struct map_session_data *sd=script_rid2sd(st); + char *name; + int type=0; + + + name=conv_str(st,& (st->stack->stack_data[st->start+2])); + type=conv_num(st,& (st->stack->stack_data[st->start+3])); + if(sd){ + if(st->oid) + clif_soundeffect(sd,map_id2bl(st->oid),name,type); + else{ + clif_soundeffect(sd,&sd->bl,name,type); + } + } + return 0; +} +/*========================================== + * pet status recovery [Valaris] + *------------------------------------------ + */ +int buildin_petrecovery(struct script_state *st) +{ + struct pet_data *pd; + + struct map_session_data *sd=script_rid2sd(st); + + if(sd==NULL || sd->pd==NULL) + return 0; + + pd=sd->pd; + + if(pd==NULL) + return 0; + + pd->skilltype=conv_num(st,& (st->stack->stack_data[st->start+2])); + pd->skilltimer=conv_num(st,& (st->stack->stack_data[st->start+3])); + + pd->skillbonustimer=add_timer(gettick()+pd->skilltimer*1000,pet_recovery_timer,sd->bl.id,0); + + return 0; +} + +/*========================================== + * pet healing [Valaris] + *------------------------------------------ + */ +int buildin_petheal(struct script_state *st) + +{ + struct pet_data *pd; + struct map_session_data *sd=script_rid2sd(st); + + if(sd==NULL || sd->pd==NULL) + return 0; + + pd=sd->pd; + + if(pd==NULL) + return 0; + + pd->skilltype=conv_num(st,& (st->stack->stack_data[st->start+2])); + pd->skillval=conv_num(st,& (st->stack->stack_data[st->start+3])); + pd->skilltimer=conv_num(st,& (st->stack->stack_data[st->start+4])); + + pd->skillbonustimer=add_timer(gettick()+pd->skilltimer*1000,pet_heal_timer,sd->bl.id,0); + + return 0; +} + +/*========================================== + * pet magnificat [Valaris] + *------------------------------------------ + */ +int buildin_petmag(struct script_state *st) +{ + struct pet_data *pd; + struct map_session_data *sd=script_rid2sd(st); + + if(sd==NULL || sd->pd==NULL) + return 0; + + pd=sd->pd; + + if(pd==NULL) + return 0; + + pd->skilltype=conv_num(st,& (st->stack->stack_data[st->start+2])); + pd->skillduration=conv_num(st,& (st->stack->stack_data[st->start+3])); + pd->skillval=conv_num(st,& (st->stack->stack_data[st->start+4])); + pd->skilltimer=conv_num(st,& (st->stack->stack_data[st->start+5])); + + pd->skillbonustimer=add_timer(gettick()+pd->skilltimer*1000,pet_mag_timer,sd->bl.id,0); + + return 0; +} + +/*========================================== + * pet attack skills [Valaris] + *------------------------------------------ + */ +int buildin_petskillattack(struct script_state *st) +{ + struct pet_data *pd; + struct map_session_data *sd=script_rid2sd(st); + + if(sd==NULL || sd->pd==NULL) + return 0; + + pd=sd->pd; + + if(pd==NULL) + return 0; + + pd->skilltype=conv_num(st,& (st->stack->stack_data[st->start+2])); + pd->skillval=conv_num(st,& (st->stack->stack_data[st->start+3])); + pd->skillduration=conv_num(st,& (st->stack->stack_data[st->start+4])); + pd->skilltimer=conv_num(st,& (st->stack->stack_data[st->start+5])); + + pd->skillbonustimer=add_timer(gettick()+100,pet_skillattack_timer,sd->bl.id,0); + + return 0; +} +/*========================================== + * NPC skill effects [Valaris] + *------------------------------------------ + */ +int buildin_npcskilleffect(struct script_state *st) +{ + struct npc_data *nd=(struct npc_data *)map_id2bl(st->oid); + + int skillid=conv_num(st,& (st->stack->stack_data[st->start+2])); + int skilllv=conv_num(st,& (st->stack->stack_data[st->start+3])); + int x=conv_num(st,& (st->stack->stack_data[st->start+4])); + int y=conv_num(st,& (st->stack->stack_data[st->start+5])); + + clif_skill_poseffect(&nd->bl,skillid,skilllv,x,y,gettick()); + + return 0; +} + +/*========================================== + * Special effects [Valaris] + *------------------------------------------ + */ +int buildin_specialeffect(struct script_state *st) +{ + struct block_list *bl=map_id2bl(st->oid); + + if(bl==NULL) + return 0; + + clif_specialeffect(bl,conv_num(st,& (st->stack->stack_data[st->start+2])), 0); + + return 0; +} + +int buildin_specialeffect2(struct script_state *st) +{ + struct map_session_data *sd=script_rid2sd(st); + + if(sd==NULL) + return 0; + + clif_specialeffect(&sd->bl,conv_num(st,& (st->stack->stack_data[st->start+2])), 0); + + return 0; +} + +/*========================================== + * Nude [Valaris] + *------------------------------------------ + */ + +int buildin_nude(struct script_state *st) +{ + struct map_session_data *sd=script_rid2sd(st); + int i; + + if(sd==NULL) + return 0; + + for(i=0;i<11;i++) + if(sd->equip_index[i] >= 0) + pc_unequipitem(sd,sd->equip_index[i],1); + + return 0; +} + +/*========================================== + * gmcommand [MouseJstr] + * + * suggested on the forums... + *------------------------------------------ + */ + +int buildin_gmcommand(struct script_state *st) +{ + struct map_session_data *sd; + char *cmd; + + sd = script_rid2sd(st); + cmd = conv_str(st,& (st->stack->stack_data[st->start+2])); + + is_atcommand(sd->fd, sd, cmd, 99); + + return 0; +} + +/*========================================== + * movenpc [MouseJstr] + *------------------------------------------ + */ + +int buildin_movenpc(struct script_state *st) +{ + struct map_session_data *sd; + char *map,*npc; + int x,y; + + sd = script_rid2sd(st); + + map = conv_str(st,& (st->stack->stack_data[st->start+2])); + x = conv_num(st,& (st->stack->stack_data[st->start+3])); + y = conv_num(st,& (st->stack->stack_data[st->start+4])); + npc = conv_str(st,& (st->stack->stack_data[st->start+5])); + + return 0; +} + +/*========================================== + * message [MouseJstr] + *------------------------------------------ + */ + +int buildin_message(struct script_state *st) +{ + struct map_session_data *sd; + char *msg,*player; + struct map_session_data *pl_sd = NULL; + + sd = script_rid2sd(st); + + player = conv_str(st,& (st->stack->stack_data[st->start+2])); + msg = conv_str(st,& (st->stack->stack_data[st->start+3])); + + if((pl_sd=map_nick2sd((char *) player)) == NULL) + return 1; + clif_displaymessage(pl_sd->fd, msg); + + return 0; +} + +/*========================================== + * npctalk (sends message to surrounding + * area) [Valaris] + *------------------------------------------ + */ + +int buildin_npctalk(struct script_state *st) +{ + char *str; + char message[255]; + + struct npc_data *nd=(struct npc_data *)map_id2bl(st->oid); + str=conv_str(st,& (st->stack->stack_data[st->start+2])); + + if(nd) { + memcpy(message,nd->name,24); + strcat(message," : "); + strcat(message,str); + clif_message(&(nd->bl), message); + } + + return 0; +} + +/*========================================== + * hasitems (checks to see if player has any + * items on them, if so will return a 1) + * [Valaris] + *------------------------------------------ + */ + +int buildin_hasitems(struct script_state *st) +{ + int i; + struct map_session_data *sd; + + sd=script_rid2sd(st); + + for(i=0; i<MAX_INVENTORY; i++) { + if(sd->status.inventory[i].amount) { + push_val(st->stack,C_INT,1); + return 0; + } + } + + push_val(st->stack,C_INT,0); + + return 0; +} + +/*========================================== + * getlook char info. getlook(arg) + *------------------------------------------ + */ +int buildin_getlook(struct script_state *st){ + int type,val; + struct map_session_data *sd; + sd=script_rid2sd(st); + + type=conv_num(st,& (st->stack->stack_data[st->start+2])); + val=-1; + switch(type){ + case LOOK_HAIR: //1 + val=sd->status.hair; + break; + case LOOK_WEAPON: //2 + val=sd->status.weapon; + break; + case LOOK_HEAD_BOTTOM: //3 + val=sd->status.head_bottom; + break; + case LOOK_HEAD_TOP: //4 + val=sd->status.head_top; + break; + case LOOK_HEAD_MID: //5 + val=sd->status.head_mid; + break; + case LOOK_HAIR_COLOR: //6 + val=sd->status.hair_color; + break; + case LOOK_CLOTHES_COLOR: //7 + val=sd->status.clothes_color; + break; + case LOOK_SHIELD: //8 + val=sd->status.shield; + break; + case LOOK_SHOES: //9 + break; + } + + push_val(st->stack,C_INT,val); + return 0; +} + +/*========================================== + * get char save point. argument: 0- map name, 1- x, 2- y + *------------------------------------------ +*/ +int buildin_getsavepoint(struct script_state *st) +{ + int x,y,type; + char *mapname; + struct map_session_data *sd; + + sd=script_rid2sd(st); + + type=conv_num(st,& (st->stack->stack_data[st->start+2])); + mapname=calloc(24, 1); + + x=sd->status.save_point.x; + y=sd->status.save_point.y; + strncpy(mapname,sd->status.save_point.map,24); + switch(type){ + case 0: + push_str(st->stack,C_STR,mapname); + break; + case 1: + push_val(st->stack,C_INT,x); + break; + case 2: + push_val(st->stack,C_INT,y); + break; + } + return 0; +} + + +// +// 実行部main +// +/*========================================== + * コマンドの読み取り + *------------------------------------------ + */ +static int unget_com_data=-1; +int get_com(unsigned char *script,int *pos) +{ + int i,j; + if(unget_com_data>=0){ + i=unget_com_data; + unget_com_data=-1; + return i; + } + if(script[*pos]>=0x80){ + return C_INT; + } + i=0; j=0; + while(script[*pos]>=0x40){ + i=script[(*pos)++]<<j; + j+=6; + } + return i+(script[(*pos)++]<<j); +} + +/*========================================== + * コマンドのプッシュバック + *------------------------------------------ + */ +void unget_com(int c) +{ + if(unget_com_data!=-1){ + if(battle_config.error_log) + printf("unget_com can back only 1 data\n"); + } + unget_com_data=c; +} + +/*========================================== + * 数値の所得 + *------------------------------------------ + */ +int get_num(unsigned char *script,int *pos) +{ + int i,j; + i=0; j=0; + while(script[*pos]>=0xc0){ + i+=(script[(*pos)++]&0x7f)<<j; + j+=6; + } + return i+((script[(*pos)++]&0x7f)<<j); +} + +/*========================================== + * スタックから値を取り出す + *------------------------------------------ + */ +int pop_val(struct script_state* st) +{ + if(st->stack->sp<=0) + return 0; + st->stack->sp--; + get_val(st,&(st->stack->stack_data[st->stack->sp])); + if(st->stack->stack_data[st->stack->sp].type==C_INT) + return st->stack->stack_data[st->stack->sp].u.num; + return 0; +} + +#define isstr(c) ((c).type==C_STR || (c).type==C_CONSTSTR) + +/*========================================== + * 加算演算子 + *------------------------------------------ + */ +void op_add(struct script_state* st) +{ + st->stack->sp--; + get_val(st,&(st->stack->stack_data[st->stack->sp])); + get_val(st,&(st->stack->stack_data[st->stack->sp-1])); + + if(isstr(st->stack->stack_data[st->stack->sp]) || isstr(st->stack->stack_data[st->stack->sp-1])){ + conv_str(st,&(st->stack->stack_data[st->stack->sp])); + conv_str(st,&(st->stack->stack_data[st->stack->sp-1])); + } + if(st->stack->stack_data[st->stack->sp].type==C_INT){ // ii + st->stack->stack_data[st->stack->sp-1].u.num += st->stack->stack_data[st->stack->sp].u.num; + } else { // ssの予定 + char *buf; + buf=(char *)aCalloc(strlen(st->stack->stack_data[st->stack->sp-1].u.str)+ + strlen(st->stack->stack_data[st->stack->sp].u.str)+1,sizeof(char)); + strcpy(buf,st->stack->stack_data[st->stack->sp-1].u.str); + strcat(buf,st->stack->stack_data[st->stack->sp].u.str); + if(st->stack->stack_data[st->stack->sp-1].type==C_STR) + free(st->stack->stack_data[st->stack->sp-1].u.str); + if(st->stack->stack_data[st->stack->sp].type==C_STR) + free(st->stack->stack_data[st->stack->sp].u.str); + st->stack->stack_data[st->stack->sp-1].type=C_STR; + st->stack->stack_data[st->stack->sp-1].u.str=buf; + } +} + +/*========================================== + * 二項演算子(文字列) + *------------------------------------------ + */ +void op_2str(struct script_state *st,int op,int sp1,int sp2) +{ + char *s1=st->stack->stack_data[sp1].u.str, + *s2=st->stack->stack_data[sp2].u.str; + int a=0; + + switch(op){ + case C_EQ: + a= (strcmp(s1,s2)==0); + break; + case C_NE: + a= (strcmp(s1,s2)!=0); + break; + case C_GT: + a= (strcmp(s1,s2)> 0); + break; + case C_GE: + a= (strcmp(s1,s2)>=0); + break; + case C_LT: + a= (strcmp(s1,s2)< 0); + break; + case C_LE: + a= (strcmp(s1,s2)<=0); + break; + default: + printf("illegal string operater\n"); + break; + } + + push_val(st->stack,C_INT,a); + + if(st->stack->stack_data[sp1].type==C_STR) free(s1); + if(st->stack->stack_data[sp2].type==C_STR) free(s2); +} +/*========================================== + * 二項演算子(数値) + *------------------------------------------ + */ +void op_2num(struct script_state *st,int op,int i1,int i2) +{ + switch(op){ + case C_SUB: + i1-=i2; + break; + case C_MUL: + i1*=i2; + break; + case C_DIV: + i1/=i2; + break; + case C_MOD: + i1%=i2; + break; + case C_AND: + i1&=i2; + break; + case C_OR: + i1|=i2; + break; + case C_XOR: + i1^=i2; + break; + case C_LAND: + i1=i1&&i2; + break; + case C_LOR: + i1=i1||i2; + break; + case C_EQ: + i1=i1==i2; + break; + case C_NE: + i1=i1!=i2; + break; + case C_GT: + i1=i1>i2; + break; + case C_GE: + i1=i1>=i2; + break; + case C_LT: + i1=i1<i2; + break; + case C_LE: + i1=i1<=i2; + break; + case C_R_SHIFT: + i1=i1>>i2; + break; + case C_L_SHIFT: + i1=i1<<i2; + break; + } + push_val(st->stack,C_INT,i1); +} +/*========================================== + * 二項演算子 + *------------------------------------------ + */ +void op_2(struct script_state *st,int op) +{ + int i1,i2; + char *s1=NULL,*s2=NULL; + + i2=pop_val(st); + if( isstr(st->stack->stack_data[st->stack->sp]) ) + s2=st->stack->stack_data[st->stack->sp].u.str; + + i1=pop_val(st); + if( isstr(st->stack->stack_data[st->stack->sp]) ) + s1=st->stack->stack_data[st->stack->sp].u.str; + + if( s1!=NULL && s2!=NULL ){ + // ss => op_2str + op_2str(st,op,st->stack->sp,st->stack->sp+1); + }else if( s1==NULL && s2==NULL ){ + // ii => op_2num + op_2num(st,op,i1,i2); + }else{ + // si,is => error + printf("script: op_2: int&str, str&int not allow."); + push_val(st->stack,C_INT,0); + } +} + +/*========================================== + * 単項演算子 + *------------------------------------------ + */ +void op_1num(struct script_state *st,int op) +{ + int i1; + i1=pop_val(st); + switch(op){ + case C_NEG: + i1=-i1; + break; + case C_NOT: + i1=~i1; + break; + case C_LNOT: + i1=!i1; + break; + } + push_val(st->stack,C_INT,i1); +} + + +/*========================================== + * 関数の実行 + *------------------------------------------ + */ +int run_func(struct script_state *st) +{ + int i,start_sp,end_sp,func; + + end_sp=st->stack->sp; + for(i=end_sp-1;i>=0 && st->stack->stack_data[i].type!=C_ARG;i--); + if(i==0){ + if(battle_config.error_log) + printf("function not found\n"); +// st->stack->sp=0; + st->state=END; + return 0; + } + start_sp=i-1; + st->start=i-1; + st->end=end_sp; + + func=st->stack->stack_data[st->start].u.num; + if( st->stack->stack_data[st->start].type!=C_NAME || str_data[func].type!=C_FUNC ){ + printf("run_func: not function and command! \n"); +// st->stack->sp=0; + st->state=END; + return 0; + } +#ifdef DEBUG_RUN + if(battle_config.etc_log) { + printf("run_func : %s? (%d(%d))\n",str_buf+str_data[func].str,func,str_data[func].type); + printf("stack dump :"); + for(i=0;i<end_sp;i++){ + switch(st->stack->stack_data[i].type){ + case C_INT: + printf(" int(%d)",st->stack->stack_data[i].u.num); + break; + case C_NAME: + printf(" name(%s)",str_buf+str_data[st->stack->stack_data[i].u.num].str); + break; + case C_ARG: + printf(" arg"); + break; + case C_POS: + printf(" pos(%d)",st->stack->stack_data[i].u.num); + break; + default: + printf(" %d,%d",st->stack->stack_data[i].type,st->stack->stack_data[i].u.num); + } + } + printf("\n"); + } +#endif + if(str_data[func].func){ + str_data[func].func(st); + } else { + if(battle_config.error_log) + printf("run_func : %s? (%d(%d))\n",str_buf+str_data[func].str,func,str_data[func].type); + push_val(st->stack,C_INT,0); + } + + pop_stack(st->stack,start_sp,end_sp); + + if(st->state==RETFUNC){ + // ユーザー定義関数からの復帰 + int olddefsp=st->defsp; + int i; + + pop_stack(st->stack,st->defsp,start_sp); // 復帰に邪魔なスタック削除 + if(st->defsp<4 || st->stack->stack_data[st->defsp-1].type!=C_RETINFO){ + printf("script:run_func(return) return without callfunc or callsub!\n"); + st->state=END; + return 0; + } + i = conv_num(st,& (st->stack->stack_data[st->defsp-4])); // 引数の数所得 + st->pos=conv_num(st,& (st->stack->stack_data[st->defsp-1])); // スクリプト位置の復元 + st->script=(char*)conv_num(st,& (st->stack->stack_data[st->defsp-2])); // スクリプトを復元 + st->defsp=conv_num(st,& (st->stack->stack_data[st->defsp-3])); // 基準スタックポインタを復元 + + pop_stack(st->stack,olddefsp-4-i,olddefsp); // 要らなくなったスタック(引数と復帰用データ)削除 + + st->state=GOTO; + } + + return 0; +} + +/*========================================== + * スクリプトの実行メイン部分 + *------------------------------------------ + */ +int run_script_main(unsigned char *script,int pos,int rid,int oid,struct script_state *st,unsigned char *rootscript) +{ + int c,rerun_pos; + int cmdcount=script_config.check_cmdcount; + int gotocount=script_config.check_gotocount; + struct script_stack *stack=st->stack; + + st->defsp=stack->sp; + st->script=script; + + rerun_pos=st->pos; + for(st->state=0;st->state==0;){ + switch(c=get_com(script,&st->pos)){ + case C_EOL: + if(stack->sp!=st->defsp){ + if(battle_config.error_log) + printf("stack.sp(%d) != default(%d)\n",stack->sp,st->defsp); + stack->sp=st->defsp; + } + rerun_pos=st->pos; + break; + case C_INT: + push_val(stack,C_INT,get_num(script,&st->pos)); + break; + case C_POS: + case C_NAME: + push_val(stack,c,(*(int*)(script+st->pos))&0xffffff); + st->pos+=3; + break; + case C_ARG: + push_val(stack,c,0); + break; + case C_STR: + push_str(stack,C_CONSTSTR,script+st->pos); + while(script[st->pos++]); + break; + case C_FUNC: + run_func(st); + if(st->state==GOTO){ + rerun_pos=st->pos; + script=st->script; + st->state=0; + if( gotocount>0 && (--gotocount)<=0 ){ + printf("run_script: infinity loop !\n"); + st->state=END; + } + } + break; + + case C_ADD: + op_add(st); + break; + + case C_SUB: + case C_MUL: + case C_DIV: + case C_MOD: + case C_EQ: + case C_NE: + case C_GT: + case C_GE: + case C_LT: + case C_LE: + case C_AND: + case C_OR: + case C_XOR: + case C_LAND: + case C_LOR: + case C_R_SHIFT: + case C_L_SHIFT: + op_2(st,c); + break; + + case C_NEG: + case C_NOT: + case C_LNOT: + op_1num(st,c); + break; + + case C_NOP: + st->state=END; + break; + + default: + if(battle_config.error_log) + printf("unknown command : %d @ %d\n",c,pos); + st->state=END; + break; + } + if( cmdcount>0 && (--cmdcount)<=0 ){ + printf("run_script: infinity loop !\n"); + st->state=END; + } + } + switch(st->state){ + case STOP: + break; + case END: + { + struct map_session_data *sd=map_id2sd(st->rid); + st->pos=-1; + if(sd && sd->npc_id==st->oid) + npc_event_dequeue(sd); + } + break; + case RERUNLINE: + { + st->pos=rerun_pos; + } + break; + } + + if( st->state!=END){ + // 再開するためにスタック情報を保存 + struct map_session_data *sd=map_id2sd(st->rid); + if(sd/* && sd->npc_stackbuf==NULL*/){ + if( sd->npc_stackbuf ) + free( sd->npc_stackbuf ); + sd->npc_stackbuf = (char *)aCalloc(sizeof(stack->stack_data[0])*stack->sp_max,sizeof(char)); + memcpy(sd->npc_stackbuf, stack->stack_data, sizeof(stack->stack_data[0]) * stack->sp_max); + sd->npc_stack = stack->sp; + sd->npc_stackmax = stack->sp_max; + sd->npc_script=script; + sd->npc_scriptroot=rootscript; + } + } + + return 0; +} + +/*========================================== + * スクリプトの実行 + *------------------------------------------ + */ +int run_script(unsigned char *script,int pos,int rid,int oid) +{ + struct script_stack stack; + struct script_state st; + struct map_session_data *sd=map_id2sd(rid); + unsigned char *rootscript=script; + + if(script==NULL || pos<0) + return -1; + + if(sd && sd->npc_stackbuf && sd->npc_scriptroot==(char*)rootscript){ + // 前回のスタックを復帰 + script=sd->npc_script; + stack.sp=sd->npc_stack; + stack.sp_max=sd->npc_stackmax; + stack.stack_data=(struct script_data *)aCalloc(stack.sp_max,sizeof(stack.stack_data[0])); + memcpy(stack.stack_data,sd->npc_stackbuf,sizeof(stack.stack_data[0])*stack.sp_max); + free(sd->npc_stackbuf); + sd->npc_stackbuf=NULL; + }else{ + // スタック初期化 + stack.sp=0; + stack.sp_max=64; + stack.stack_data=(struct script_data *)aCalloc(stack.sp_max,sizeof(stack.stack_data[0])); + } + st.stack=&stack; + st.pos=pos; + st.rid=rid; + st.oid=oid; + run_script_main(script,pos,rid,oid,&st,rootscript); + + free(stack.stack_data); + stack.stack_data=NULL; + return st.pos; +} + + +/*========================================== + * マップ変数の変更 + *------------------------------------------ + */ +int mapreg_setreg(int num,int val) +{ + if(val!=0) + numdb_insert(mapreg_db,num,val); + else + numdb_erase(mapreg_db,num); + + mapreg_dirty=1; + return 0; +} +/*========================================== + * 文字列型マップ変数の変更 + *------------------------------------------ + */ +int mapreg_setregstr(int num,const char *str) +{ + char *p; + + if( (p=numdb_search(mapregstr_db,num))!=NULL ) + free(p); + + if( str==NULL || *str==0 ){ + numdb_erase(mapregstr_db,num); + mapreg_dirty=1; + return 0; + } + p=(char *)aCalloc(strlen(str)+1, sizeof(char)); + strcpy(p,str); + numdb_insert(mapregstr_db,num,p); + mapreg_dirty=1; + return 0; +} + +/*========================================== + * 永続的マップ変数の読み込み + *------------------------------------------ + */ +static int script_load_mapreg() +{ + FILE *fp; + char line[1024]; + + if( (fp=fopen(mapreg_txt,"rt"))==NULL ) + return -1; + + while(fgets(line,sizeof(line),fp)){ + char buf1[256],buf2[1024],*p; + int n,v,s,i; + if( sscanf(line,"%255[^,],%d\t%n",buf1,&i,&n)!=2 && + (i=0,sscanf(line,"%[^\t]\t%n",buf1,&n)!=1) ) + continue; + if( buf1[strlen(buf1)-1]=='$' ){ + if( sscanf(line+n,"%[^\n\r]",buf2)!=1 ){ + printf("%s: %s broken data !\n",mapreg_txt,buf1); + continue; + } + p=(char *)aCalloc(strlen(buf2) + 1,sizeof(char)); + strcpy(p,buf2); + s=add_str(buf1); + numdb_insert(mapregstr_db,(i<<24)|s,p); + }else{ + if( sscanf(line+n,"%d",&v)!=1 ){ + printf("%s: %s broken data !\n",mapreg_txt,buf1); + continue; + } + s=add_str(buf1); + numdb_insert(mapreg_db,(i<<24)|s,v); + } + } + fclose(fp); + mapreg_dirty=0; + return 0; +} +/*========================================== + * 永続的マップ変数の書き込み + *------------------------------------------ + */ +static int script_save_mapreg_intsub(void *key,void *data,va_list ap) +{ + FILE *fp=va_arg(ap,FILE*); + int num=((int)key)&0x00ffffff, i=((int)key)>>24; + char *name=str_buf+str_data[num].str; + if( name[1]!='@' ){ + if(i==0) + fprintf(fp,"%s\t%d\n", name, (int)data); + else + fprintf(fp,"%s,%d\t%d\n", name, i, (int)data); + } + return 0; +} +static int script_save_mapreg_strsub(void *key,void *data,va_list ap) +{ + FILE *fp=va_arg(ap,FILE*); + int num=((int)key)&0x00ffffff, i=((int)key)>>24; + char *name=str_buf+str_data[num].str; + if( name[1]!='@' ){ + if(i==0) + fprintf(fp,"%s\t%s\n", name, (char *)data); + else + fprintf(fp,"%s,%d\t%s\n", name, i, (char *)data); + } + return 0; +} +static int script_save_mapreg() +{ + FILE *fp; + int lock; + + if( (fp=lock_fopen(mapreg_txt,&lock))==NULL ) + return -1; + numdb_foreach(mapreg_db,script_save_mapreg_intsub,fp); + numdb_foreach(mapregstr_db,script_save_mapreg_strsub,fp); + lock_fclose(fp,mapreg_txt,&lock); + mapreg_dirty=0; + return 0; +} +static int script_autosave_mapreg(int tid,unsigned int tick,int id,int data) +{ + if(mapreg_dirty) + script_save_mapreg(); + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +static int set_posword(char *p) +{ + char* np,* str[15]; + int i=0; + for(i=0;i<11;i++) { + if((np=strchr(p,','))!=NULL) { + str[i]=p; + *np=0; + p=np+1; + } else { + str[i]=p; + p+=strlen(p); + } + if(str[i]) + strcpy(pos[i],str[i]); + } + return 0; +} + +int script_config_read(char *cfgName) +{ + int i; + char line[1024],w1[1024],w2[1024]; + FILE *fp; + + script_config.warn_func_no_comma=1; + script_config.warn_cmd_no_comma=1; + script_config.warn_func_mismatch_paramnum=1; + script_config.warn_cmd_mismatch_paramnum=1; + script_config.check_cmdcount=8192; + script_config.check_gotocount=512; + + fp=fopen(cfgName,"r"); + if(fp==NULL){ + printf("file not found: %s\n",cfgName); + return 1; + } + while(fgets(line,1020,fp)){ + if(line[0] == '/' && line[1] == '/') + continue; + i=sscanf(line,"%[^:]: %[^\r\n]",w1,w2); + if(i!=2) + continue; + if(strcmpi(w1,"refine_posword")==0) { + set_posword(w2); + } + if(strcmpi(w1,"import")==0){ + script_config_read(w2); + } + } + fclose(fp); + + return 0; +} + +/*========================================== + * 終了 + *------------------------------------------ + */ +static int mapreg_db_final(void *key,void *data,va_list ap) +{ + return 0; +} +static int mapregstr_db_final(void *key,void *data,va_list ap) +{ + free(data); + return 0; +} +static int scriptlabel_db_final(void *key,void *data,va_list ap) +{ + return 0; +} +static int userfunc_db_final(void *key,void *data,va_list ap) +{ + free(key); + free(data); + return 0; +} +int do_final_script() +{ + if(mapreg_dirty>=0) + script_save_mapreg(); + if(script_buf) + free(script_buf); + + if(mapreg_db) + numdb_final(mapreg_db,mapreg_db_final); + if(mapregstr_db) + strdb_final(mapregstr_db,mapregstr_db_final); + if(scriptlabel_db) + strdb_final(scriptlabel_db,scriptlabel_db_final); + if(userfunc_db) + strdb_final(userfunc_db,userfunc_db_final); + + if (str_data) + free(str_data); + if (str_buf) + free(str_buf); + + return 0; +} +/*========================================== + * 初期化 + *------------------------------------------ + */ +int do_init_script() +{ + mapreg_db=numdb_init(); + mapregstr_db=numdb_init(); + script_load_mapreg(); + + add_timer_func_list(script_autosave_mapreg,"script_autosave_mapreg"); + add_timer_interval(gettick()+MAPREG_AUTOSAVE_INTERVAL, + script_autosave_mapreg,0,0,MAPREG_AUTOSAVE_INTERVAL); + + scriptlabel_db=strdb_init(50); + return 0; +} diff --git a/src/map/script.h b/src/map/script.h new file mode 100644 index 0000000..b50c466 --- /dev/null +++ b/src/map/script.h @@ -0,0 +1,39 @@ +// $Id: script.h,v 1.2 2004/09/25 05:32:19 MouseJstr Exp $ +#ifndef _SCRIPT_H_ +#define _SCRIPT_H_ + +struct script_data { + int type; + union { + int num; + char *str; + } u; +}; + +struct script_stack { + int sp,sp_max; + struct script_data *stack_data; +}; +struct script_state { + struct script_stack *stack; + int start,end; + int pos,state; + int rid,oid; + char *script,*new_script; + int defsp,new_pos,new_defsp; +}; + +unsigned char * parse_script(unsigned char *,int); +int run_script(unsigned char *,int,int,int); + +struct dbt* script_get_label_db(); +struct dbt* script_get_userfunc_db(); + +int script_config_read(char *cfgName); +int do_init_script(); +int do_final_script(); + +extern char mapreg_txt[]; + +#endif + diff --git a/src/map/skill.c b/src/map/skill.c new file mode 100644 index 0000000..1e92b3f --- /dev/null +++ b/src/map/skill.c @@ -0,0 +1,10637 @@ +// $Id: skill.c,v 1.8 2004/09/25 05:32:19 MouseJstr Exp $ +/* スキル関係 */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> + +#include "timer.h" +#include "nullpo.h" +#include "malloc.h" + +#include "skill.h" +#include "map.h" +#include "clif.h" +#include "pc.h" +#include "pet.h" +#include "mob.h" +#include "battle.h" +#include "party.h" +#include "itemdb.h" +#include "script.h" +#include "intif.h" + +#ifdef MEMWATCH +#include "memwatch.h" +#endif + +#define SKILLUNITTIMER_INVERVAL 100 + +#define STATE_BLIND 0x10 + +/* スキル番号=>ステータス異常番号変換テーブル */ +int SkillStatusChangeTable[]={ /* skill.hのenumのSC_***とあわせること */ +/* 0- */ + -1,-1,-1,-1,-1,-1, + SC_PROVOKE, /* プロボック */ + -1, 1,-1, +/* 10- */ + SC_SIGHT, /* サイト */ + -1,-1,-1,-1, + SC_FREEZE, /* フロストダイバー */ + SC_STONE, /* ストーンカース */ + -1,-1,-1, +/* 20- */ + -1,-1,-1,-1, + SC_RUWACH, /* ルアフ */ + -1,-1,-1,-1, + SC_INCREASEAGI, /* 速度増加 */ +/* 30- */ + SC_DECREASEAGI, /* 速度減少 */ + -1, + SC_SIGNUMCRUCIS, /* シグナムクルシス */ + SC_ANGELUS, /* エンジェラス */ + SC_BLESSING, /* ブレッシング */ + -1,-1,-1,-1,-1, +/* 40- */ + -1,-1,-1,-1,-1, + SC_CONCENTRATE, /* 集中力向上 */ + -1,-1,-1,-1, +/* 50- */ + -1, + SC_HIDING, /* ハイディング */ + -1,-1,-1,-1,-1,-1,-1,-1, +/* 60- */ + SC_TWOHANDQUICKEN, /* 2HQ */ + SC_AUTOCOUNTER, + -1,-1,-1,-1, + SC_IMPOSITIO, /* インポシティオマヌス */ + SC_SUFFRAGIUM, /* サフラギウム */ + SC_ASPERSIO, /* アスペルシオ */ + SC_BENEDICTIO, /* 聖体降福 */ +/* 70- */ + -1, + SC_SLOWPOISON, + -1, + SC_KYRIE, /* キリエエレイソン */ + SC_MAGNIFICAT, /* マグニフィカート */ + SC_GLORIA, /* グロリア */ + SC_DIVINA, /* レックスディビーナ */ + -1, + SC_AETERNA, /* レックスエーテルナ */ + -1, +/* 80- */ + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1, +/* 90- */ + -1,-1, + SC_QUAGMIRE, /* クァグマイア */ + -1,-1,-1,-1,-1,-1,-1, +/* 100- */ + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1, +/* 110- */ + -1, + SC_ADRENALINE, /* アドレナリンラッシュ */ + SC_WEAPONPERFECTION,/* ウェポンパーフェクション */ + SC_OVERTHRUST, /* オーバートラスト */ + SC_MAXIMIZEPOWER, /* マキシマイズパワー */ + -1,-1,-1,-1,-1, +/* 120- */ + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1, +/* 130- */ + -1,-1,-1,-1,-1, + SC_CLOAKING, /* クローキング */ + SC_STAN, /* ソニックブロー */ + -1, + SC_ENCPOISON, /* エンチャントポイズン */ + SC_POISONREACT, /* ポイズンリアクト */ +/* 140- */ + SC_POISON, /* ベノムダスト */ + SC_SPLASHER, /* ベナムスプラッシャー */ + -1, + SC_TRICKDEAD, /* 死んだふり */ + -1,-1,-1,-1,-1,-1, +/* 150- */ + -1,-1,-1,-1,-1, + SC_LOUD, /* ラウドボイス */ + -1, + SC_ENERGYCOAT, /* エナジーコート */ + -1,-1, +/* 160- */ + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1, + SC_SELFDESTRUCTION, + -1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1, + SC_KEEPING, + -1,-1, + SC_BARRIER, + -1,-1, + SC_HALLUCINATION, + -1,-1, +/* 210- */ + -1,-1,-1,-1,-1, + SC_STRIPWEAPON, + SC_STRIPSHIELD, + SC_STRIPARMOR, + SC_STRIPHELM, + -1, +/* 220- */ + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1, +/* 230- */ + -1,-1,-1,-1, + SC_CP_WEAPON, + SC_CP_SHIELD, + SC_CP_ARMOR, + SC_CP_HELM, + -1,-1, +/* 240- */ + -1,-1,-1,-1,-1,-1,-1,-1,-1, + SC_AUTOGUARD, +/* 250- */ + -1,-1, + SC_REFLECTSHIELD, + -1,-1, + SC_DEVOTION, + SC_PROVIDENCE, + SC_DEFENDER, + SC_SPEARSQUICKEN, + -1, +/* 260- */ + -1,-1,-1,-1,-1,-1,-1,-1, + SC_STEELBODY, + SC_BLADESTOP_WAIT, +/* 270- */ + SC_EXPLOSIONSPIRITS, + SC_EXTREMITYFIST, + -1,-1,-1,-1, + SC_MAGICROD, + -1,-1,-1, +/* 280- */ + SC_FLAMELAUNCHER, + SC_FROSTWEAPON, + SC_LIGHTNINGLOADER, + SC_SEISMICWEAPON, + -1, + SC_VOLCANO, + SC_DELUGE, + SC_VIOLENTGALE, + SC_LANDPROTECTOR, + -1, +/* 290- */ + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1, +/* 300- */ + -1,-1,-1,-1,-1,-1, + SC_LULLABY, + SC_RICHMANKIM, + SC_ETERNALCHAOS, + SC_DRUMBATTLE, +/* 310- */ + SC_NIBELUNGEN, + SC_ROKISWEIL, + SC_INTOABYSS, + SC_SIEGFRIED, + -1,-1,-1, + SC_DISSONANCE, + -1, + SC_WHISTLE, +/* 320- */ + SC_ASSNCROS, + SC_POEMBRAGI, + SC_APPLEIDUN, + -1,-1, + SC_UGLYDANCE, + -1, + SC_HUMMING, + SC_DONTFORGETME, + SC_FORTUNE, +/* 330- */ + SC_SERVICE4U, + SC_SELFDESTRUCTION, + -1,-1,-1,-1,-1,-1,-1,-1, +/* 340- */ + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1, +/* 350- */ + -1,-1,-1,-1,-1, + SC_AURABLADE, + SC_PARRYING, + SC_CONCENTRATION, + SC_TENSIONRELAX, + SC_BERSERK, +/* 360- */ + SC_BERSERK, + SC_ASSUMPTIO, + SC_BASILICA, + -1,-1,-1, + SC_MAGICPOWER, + -1,-1, + SC_GOSPEL, +/* 370- */ + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1, +/* 380- */ + SC_TRUESIGHT, + -1,-1, + SC_WINDWALK, + SC_MELTDOWN, + -1,-1, + SC_CARTBOOST, + -1, + SC_CHASEWALK, +/* 390- */ + SC_REJECTSWORD, + -1,-1,-1,-1,-1, + SC_MARIONETTE, + -1, + SC_HEADCRUSH, + SC_JOINTBEAT, +/* 400 */ + -1,-1, + SC_MINDBREAKER, + SC_MEMORIZE, + SC_FOGWALL, + SC_SPIDERWEB, + -1,-1,-1,-1, +/* 410- */ + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1, +}; + +struct skill_name_db skill_names[] = { + { AC_CHARGEARROW, "CHARGEARROW", "Charge_Arrow" } , + { AC_CONCENTRATION, "CONCENTRATION", "Improve_Concentration" } , + { AC_DOUBLE, "DOUBLE", "Double_Strafe" } , + { AC_MAKINGARROW, "MAKINGARROW", "Arrow_Creation" } , + { AC_OWL, "OWL", "Owl's_Eye" } , + { AC_SHOWER, "SHOWER", "Arrow_Shower" } , + { AC_VULTURE, "VULTURE", "Vulture's_Eye" } , + { ALL_RESURRECTION, "RESURRECTION", "Resurrection" } , + { AL_ANGELUS, "ANGELUS", "Angelus" } , + { AL_BLESSING, "BLESSING", "Blessing" } , + { AL_CRUCIS, "CRUCIS", "Signum_Crusis" } , + { AL_CURE, "CURE", "Cure" } , + { AL_DECAGI, "DECAGI", "Decrease_AGI" } , + { AL_DEMONBANE, "DEMONBANE", "Demon_Bane" } , + { AL_DP, "DP", "Divine_Protection" } , + { AL_HEAL, "HEAL", "Heal" } , + { AL_HOLYLIGHT, "HOLYLIGHT", "Holy_Light" } , + { AL_HOLYWATER, "HOLYWATER", "Aqua_Benedicta" } , + { AL_INCAGI, "INCAGI", "Increase_AGI" } , + { AL_PNEUMA, "PNEUMA", "Pneuma" } , + { AL_RUWACH, "RUWACH", "Ruwach" } , + { AL_TELEPORT, "TELEPORT", "Teleport" } , + { AL_WARP, "WARP", "Warp_Portal" } , + { AM_ACIDTERROR, "ACIDTERROR", "Acid_Terror" } , + { AM_AXEMASTERY, "AXEMASTERY", "Axe_Mastery" } , + { AM_BERSERKPITCHER, "BERSERKPITCHER", "Berserk Pitcher" } , + { AM_BIOETHICS, "BIOETHICS", "Bioethics" } , + { AM_BIOTECHNOLOGY, "BIOTECHNOLOGY", "Biotechnology" } , + { AM_CALLHOMUN, "CALLHOMUN", "Call_Homunculus" } , + { AM_CANNIBALIZE, "CANNIBALIZE", "Bio_Cannibalize" } , + { AM_CP_ARMOR, "ARMOR", "Chemical_Protection_Armor" } , + { AM_CP_HELM, "HELM", "Chemical_Protection_Helm" } , + { AM_CP_SHIELD, "SHIELD", "Chemical_Protection_Shield" } , + { AM_CP_WEAPON, "WEAPON", "Chemical_Protection_Weapon" } , + { AM_CREATECREATURE, "CREATECREATURE", "Life_Creation" } , + { AM_CULTIVATION, "CULTIVATION", "Cultivation" } , + { AM_DEMONSTRATION, "DEMONSTRATION", "Demonstration" } , + { AM_DRILLMASTER, "DRILLMASTER", "Drillmaster" } , + { AM_FLAMECONTROL, "FLAMECONTROL", "Flame_Control" } , + { AM_HEALHOMUN, "HEALHOMUN", "Heal_Homunculus" } , + { AM_LEARNINGPOTION, "LEARNINGPOTION", "AM_LEARNINGPOTION" } , + { AM_PHARMACY, "PHARMACY", "Pharmacy" } , + { AM_POTIONPITCHER, "POTIONPITCHER", "Potion_Pitcher" } , + { AM_REST, "REST", "Sabbath" } , + { AM_RESURRECTHOMUN, "RESURRECTHOMUN", "Ressurect_Homunculus" } , + { AM_SPHEREMINE, "SPHEREMINE", "Sphere_Mine" } , + { ASC_BREAKER, "BREAKER", "Breaker" } , + { ASC_CDP, "CDP", "Create_Deadly_Poison" } , + { ASC_EDP, "EDP", "Deadly_Poison_Enchantment" } , + { ASC_HALLUCINATION, "HALLUCINATION", "Hallucination_Walk" } , + { ASC_KATAR, "KATAR", "Advanced_Katar_Mastery" } , + { ASC_METEORASSAULT, "METEORASSAULT", "Meteor_Assault" } , + { AS_CLOAKING, "CLOAKING", "Cloaking" } , + { AS_ENCHANTPOISON, "ENCHANTPOISON", "Enchant_Poison" } , + { AS_GRIMTOOTH, "GRIMTOOTH", "Grimtooth" } , + { AS_KATAR, "KATAR", "Katar_Mastery" } , + { AS_LEFT, "LEFT", "Lefthand_Mastery" } , + { AS_POISONREACT, "POISONREACT", "Poison_React" } , + { AS_RIGHT, "RIGHT", "Righthand_Mastery" } , + { AS_SONICBLOW, "SONICBLOW", "Sonic_Blow" } , + { AS_SPLASHER, "SPLASHER", "Venom_Splasher" } , + { AS_VENOMDUST, "VENOMDUST", "Venom_Dust" } , + { BA_APPLEIDUN, "APPLEIDUN", "Apple_of_Idun" } , + { BA_ASSASSINCROSS, "ASSASSINCROSS", "Assassin_Cross" } , + { BA_DISSONANCE, "DISSONANCE", "Dissonance" } , + { BA_FROSTJOKE, "FROSTJOKE", "Dumb_Joke" } , + { BA_MUSICALLESSON, "MUSICALLESSON", "Musical_Lesson" } , + { BA_MUSICALSTRIKE, "MUSICALSTRIKE", "Musical_Strike" } , + { BA_POEMBRAGI, "POEMBRAGI", "Poem_of_Bragi" } , + { BA_WHISTLE, "WHISTLE", "Whistle" } , + { BD_ADAPTATION, "ADAPTATION", "Adaption" } , + { BD_DRUMBATTLEFIELD, "DRUMBATTLEFIELD", "Drumb_BattleField" } , + { BD_ENCORE, "ENCORE", "Encore" } , + { BD_ETERNALCHAOS, "ETERNALCHAOS", "Eternal_Chaos" } , + { BD_INTOABYSS, "INTOABYSS", "Into_the_Abyss" } , + { BD_LULLABY, "LULLABY", "Lullaby" } , + { BD_RAGNAROK, "RAGNAROK", "Ragnarok" } , + { BD_RICHMANKIM, "RICHMANKIM", "Rich_Mankim" } , + { BD_RINGNIBELUNGEN, "RINGNIBELUNGEN", "Ring_of_Nibelugen" } , + { BD_ROKISWEIL, "ROKISWEIL", "Loki's_Wail" } , + { BD_SIEGFRIED, "SIEGFRIED", "Invulnerable_Siegfried" } , + { BS_ADRENALINE, "ADRENALINE", "Adrenaline_Rush" } , + { BS_ADRENALINE2, "ADRENALINE2", "Adrenaline Rush 2" } , + { BS_AXE, "AXE", "Smith_Axe" } , + { BS_DAGGER, "DAGGER", "Smith_Dagger" } , + { BS_ENCHANTEDSTONE, "ENCHANTEDSTONE", "Enchantedstone_Craft" } , + { BS_FINDINGORE, "FINDINGORE", "Ore_Discovery" } , + { BS_HAMMERFALL, "HAMMERFALL", "Hammer_Fall" } , + { BS_HILTBINDING, "HILTBINDING", "Hilt_Binding" } , + { BS_IRON, "IRON", "Iron_Tempering" } , + { BS_KNUCKLE, "KNUCKLE", "Smith_Knucklebrace" } , + { BS_MACE, "MACE", "Smith_Mace" } , + { BS_MAXIMIZE, "MAXIMIZE", "Power_Maximize" } , + { BS_ORIDEOCON, "ORIDEOCON", "Orideocon_Research" } , + { BS_OVERTHRUST, "OVERTHRUST", "Power-Thrust" } , + { BS_REPAIRWEAPON, "REPAIRWEAPON", "Weapon_Repair" } , + { BS_SKINTEMPER, "SKINTEMPER", "Skin_Tempering" } , + { BS_SPEAR, "SPEAR", "Smith_Spear" } , + { BS_STEEL, "STEEL", "Steel_Tempering" } , + { BS_SWORD, "SWORD", "Smith_Sword" } , + { BS_TWOHANDSWORD, "TWOHANDSWORD", "Smith_Two-handed_Sword" } , + { BS_WEAPONPERFECT, "WEAPONPERFECT", "Weapon_Perfection" } , + { BS_WEAPONRESEARCH, "WEAPONRESEARCH", "Weaponry_Research" } , + { CG_ARROWVULCAN, "ARROWVULCAN", "Vulcan_Arrow" } , + { CG_MARIONETTE, "MARIONETTE", "Marionette_Control" } , + { CG_MOONLIT, "MOONLIT", "Moonlight_Petals" } , + { CH_CHAINCRUSH, "CHAINCRUSH", "Chain_Crush_Combo" } , + { CH_PALMSTRIKE, "PALMSTRIKE", "Palm_Push_Strike" } , + { CH_SOULCOLLECT, "SOULCOLLECT", "Collect_Soul" } , + { CH_TIGERFIST, "TIGERFIST", "Tiger_Knuckle_Fist" } , + { CR_ALCHEMY, "ALCHEMY", "Alchemy" } , + { CR_AUTOGUARD, "AUTOGUARD", "Guard" } , + { CR_DEFENDER, "DEFENDER", "Defender" } , + { CR_DEVOTION, "DEVOTION", "Sacrifice" } , + { CR_GRANDCROSS, "GRANDCROSS", "Grand_Cross" } , + { CR_HOLYCROSS, "HOLYCROSS", "Holy_Cross" } , + { CR_PROVIDENCE, "PROVIDENCE", "Providence" } , + { CR_REFLECTSHIELD, "REFLECTSHIELD", "Shield_Reflect" } , + { CR_SHIELDBOOMERANG, "SHIELDBOOMERANG", "Shield_Boomerang" } , + { CR_SHIELDCHARGE, "SHIELDCHARGE", "Shield_Charge" } , + { CR_SPEARQUICKEN, "SPEARQUICKEN", "Spear_Quicken" } , + { CR_SYNTHESISPOTION, "SYNTHESISPOTION", "Potion_Synthesis" } , + { CR_TRUST, "TRUST", "Faith" } , + { DC_DANCINGLESSON, "DANCINGLESSON", "Dancing_Lesson" } , + { DC_DONTFORGETME, "DONTFORGETME", "Don't_Forget_Me" } , + { DC_FORTUNEKISS, "FORTUNEKISS", "Fortune_Kiss" } , + { DC_HUMMING, "HUMMING", "Humming" } , + { DC_SCREAM, "SCREAM", "Scream" } , + { DC_SERVICEFORYOU, "SERVICEFORYOU", "Prostitute" } , + { DC_THROWARROW, "THROWARROW", "Throw_Arrow" } , + { DC_UGLYDANCE, "UGLYDANCE", "Ugly_Dance" } , + { HP_ASSUMPTIO, "ASSUMPTIO", "Assumptio" } , + { HP_BASILICA, "BASILICA", "Basilica" } , + { HP_MEDITATIO, "MEDITATIO", "Meditation" } , + { HT_ANKLESNARE, "ANKLESNARE", "Ankle_Snare" } , + { HT_BEASTBANE, "BEASTBANE", "Beast_Bane" } , + { HT_BLASTMINE, "BLASTMINE", "Blast_Mine" } , + { HT_BLITZBEAT, "BLITZBEAT", "Blitz_Beat" } , + { HT_CLAYMORETRAP, "CLAYMORETRAP", "Claymore_Trap" } , + { HT_DETECTING, "DETECTING", "Detect" } , + { HT_FALCON, "FALCON", "Falconry_Mastery" } , + { HT_FLASHER, "FLASHER", "Flasher" } , + { HT_FREEZINGTRAP, "FREEZINGTRAP", "Freezing_Trap" } , + { HT_LANDMINE, "LANDMINE", "Land_Mine" } , + { HT_REMOVETRAP, "REMOVETRAP", "Remove_Trap" } , + { HT_SANDMAN, "SANDMAN", "Sandman" } , + { HT_SHOCKWAVE, "SHOCKWAVE", "Shockwave_Trap" } , + { HT_SKIDTRAP, "SKIDTRAP", "Skid_Trap" } , + { HT_SPRINGTRAP, "SPRINGTRAP", "Spring_Trap" } , + { HT_STEELCROW, "STEELCROW", "Steel_Crow" } , + { HT_TALKIEBOX, "TALKIEBOX", "Talkie_Box" } , + { HW_MAGICCRASHER, "MAGICCRASHER", "Magic_Crasher" } , + { HW_MAGICPOWER, "MAGICPOWER", "Magic_Power" } , + { HW_NAPALMVULCAN, "NAPALMVULCAN", "Napalm_Vulcan" } , + { HW_SOULDRAIN, "SOULDRAIN", "Soul_Drain" } , + { KN_AUTOCOUNTER, "AUTOCOUNTER", "Counter_Attack" } , + { KN_BOWLINGBASH, "BOWLINGBASH", "Bowling_Bash" } , + { KN_BRANDISHSPEAR, "BRANDISHSPEAR", "Brandish_Spear" } , + { KN_CAVALIERMASTERY, "CAVALIERMASTERY", "Cavalier_Mastery" } , + { KN_PIERCE, "PIERCE", "Pierce" } , + { KN_RIDING, "RIDING", "Peco_Peco_Ride" } , + { KN_SPEARBOOMERANG, "SPEARBOOMERANG", "Spear_Boomerang" } , + { KN_SPEARMASTERY, "SPEARMASTERY", "Spear_Mastery" } , + { KN_SPEARSTAB, "SPEARSTAB", "Spear_Stab" } , + { KN_TWOHANDQUICKEN, "TWOHANDQUICKEN", "Twohand_Quicken" } , + { LK_AURABLADE, "AURABLADE", "Aura_Blade" } , + { LK_BERSERK, "BERSERK", "Berserk" } , + { LK_CONCENTRATION, "CONCENTRATION", "Concentration" } , + { LK_FURY, "FURY", "LK_FURY" } , + { LK_HEADCRUSH, "HEADCRUSH", "Head_Crusher" } , + { LK_JOINTBEAT, "JOINTBEAT", "Joint_Beat" } , + { LK_PARRYING, "PARRYING", "Parrying" } , + { LK_SPIRALPIERCE, "SPIRALPIERCE", "Spiral_Pierce" } , + { LK_TENSIONRELAX, "TENSIONRELAX", "Tension_Relax" } , + { MC_CARTREVOLUTION, "CARTREVOLUTION", "Cart_Revolution" } , + { MC_CHANGECART, "CHANGECART", "Change_Cart" } , + { MC_DISCOUNT, "DISCOUNT", "Discount" } , + { MC_IDENTIFY, "IDENTIFY", "Item_Appraisal" } , + { MC_INCCARRY, "INCCARRY", "Enlarge_Weight_Limit" } , + { MC_LOUD, "LOUD", "Lord_Exclamation" } , + { MC_MAMMONITE, "MAMMONITE", "Mammonite" } , + { MC_OVERCHARGE, "OVERCHARGE", "Overcharge" } , + { MC_PUSHCART, "PUSHCART", "Pushcart" } , + { MC_VENDING, "VENDING", "Vending" } , + { MG_COLDBOLT, "COLDBOLT", "Cold_Bolt" } , + { MG_ENERGYCOAT, "ENERGYCOAT", "Energy_Coat" } , + { MG_FIREBALL, "FIREBALL", "Fire_Ball" } , + { MG_FIREBOLT, "FIREBOLT", "Fire_Bolt" } , + { MG_FIREWALL, "FIREWALL", "Fire_Wall" } , + { MG_FROSTDIVER, "FROSTDIVER", "Frost_Diver" } , + { MG_LIGHTNINGBOLT, "LIGHTNINGBOLT", "Lightening_Bolt" } , + { MG_NAPALMBEAT, "NAPALMBEAT", "Napalm_Beat" } , + { MG_SAFETYWALL, "SAFETYWALL", "Safety_Wall" } , + { MG_SIGHT, "SIGHT", "Sight" } , + { MG_SOULSTRIKE, "SOULSTRIKE", "Soul_Strike" } , + { MG_SRECOVERY, "SRECOVERY", "Increase_SP_Recovery" } , + { MG_STONECURSE, "STONECURSE", "Stone_Curse" } , + { MG_THUNDERSTORM, "THUNDERSTORM", "Thunderstorm" } , + { MO_ABSORBSPIRITS, "ABSORBSPIRITS", "Absorb_Spirits" } , + { MO_BLADESTOP, "BLADESTOP", "Blade_Stop" } , + { MO_BODYRELOCATION, "BODYRELOCATION", "Body_Relocation" } , + { MO_CALLSPIRITS, "CALLSPIRITS", "Call_Spirits" } , + { MO_CHAINCOMBO, "CHAINCOMBO", "Chain_Combo" } , + { MO_COMBOFINISH, "COMBOFINISH", "Combo_Finish" } , + { MO_DODGE, "DODGE", "Dodge" } , + { MO_EXPLOSIONSPIRITS, "EXPLOSIONSPIRITS", "Explosion_Spirits" } , + { MO_EXTREMITYFIST, "EXTREMITYFIST", "Extremity_Fist" } , + { MO_FINGEROFFENSIVE, "FINGEROFFENSIVE", "Finger_Offensive" } , + { MO_INVESTIGATE, "INVESTIGATE", "Investigate" } , + { MO_IRONHAND, "IRONHAND", "Iron_Hand" } , + { MO_SPIRITSRECOVERY, "SPIRITSRECOVERY", "Spirit_Recovery" } , + { MO_STEELBODY, "STEELBODY", "Steel_Body" } , + { MO_TRIPLEATTACK, "TRIPLEATTACK", "Triple_Blows" } , + { NPC_ATTRICHANGE, "ATTRICHANGE", "NPC_ATTRICHANGE" } , + { NPC_BARRIER, "BARRIER", "NPC_BARRIER" } , + { NPC_BLINDATTACK, "BLINDATTACK", "NPC_BLINDATTACK" } , + { NPC_BLOODDRAIN, "BLOODDRAIN", "NPC_BLOODDRAIN" } , + { NPC_CHANGEDARKNESS, "CHANGEDARKNESS", "NPC_CHANGEDARKNESS" } , + { NPC_CHANGEFIRE, "CHANGEFIRE", "NPC_CHANGEFIRE" } , + { NPC_CHANGEGROUND, "CHANGEGROUND", "NPC_CHANGEGROUND" } , + { NPC_CHANGEHOLY, "CHANGEHOLY", "NPC_CHANGEHOLY" } , + { NPC_CHANGEPOISON, "CHANGEPOISON", "NPC_CHANGEPOISON" } , + { NPC_CHANGETELEKINESIS, "CHANGETELEKINESIS", "NPC_CHANGETELEKINESIS" } , + { NPC_CHANGEWATER, "CHANGEWATER", "NPC_CHANGEWATER" } , + { NPC_CHANGEWIND, "CHANGEWIND", "NPC_CHANGEWIND" } , + { NPC_COMBOATTACK, "COMBOATTACK", "NPC_COMBOATTACK" } , + { NPC_CRITICALSLASH, "CRITICALSLASH", "NPC_CRITICALSLASH" } , + { NPC_CURSEATTACK, "CURSEATTACK", "NPC_CURSEATTACK" } , + { NPC_DARKBLESSING, "DARKBLESSING", "NPC_DARKBLESSING" } , + { NPC_DARKBREATH, "DARKBREATH", "NPC_DARKBREATH" } , + { NPC_DARKCROSS, "DARKCROSS", "NPC_DARKCROSS" } , + { NPC_DARKNESSATTACK, "DARKNESSATTACK", "NPC_DARKNESSATTACK" } , + { NPC_DEFENDER, "DEFENDER", "NPC_DEFENDER" } , + { NPC_EMOTION, "EMOTION", "NPC_EMOTION" } , + { NPC_ENERGYDRAIN, "ENERGYDRAIN", "NPC_ENERGYDRAIN" } , + { NPC_FIREATTACK, "FIREATTACK", "NPC_FIREATTACK" } , + { NPC_GROUNDATTACK, "GROUNDATTACK", "NPC_GROUNDATTACK" } , + { NPC_GUIDEDATTACK, "GUIDEDATTACK", "NPC_GUIDEDATTACK" } , + { NPC_HALLUCINATION, "HALLUCINATION", "NPC_HALLUCINATION" } , + { NPC_HOLYATTACK, "HOLYATTACK", "NPC_HOLYATTACK" } , + { NPC_KEEPING, "KEEPING", "NPC_KEEPING" } , + { NPC_LICK, "LICK", "NPC_LICK" } , + { NPC_MAGICALATTACK, "MAGICALATTACK", "NPC_MAGICALATTACK" } , + { NPC_MENTALBREAKER, "MENTALBREAKER", "NPC_MENTALBREAKER" } , + { NPC_METAMORPHOSIS, "METAMORPHOSIS", "NPC_METAMORPHOSIS" } , + { NPC_PETRIFYATTACK, "PETRIFYATTACK", "NPC_PETRIFYATTACK" } , + { NPC_PIERCINGATT, "PIERCINGATT", "NPC_PIERCINGATT" } , + { NPC_POISON, "POISON", "NPC_POISON" } , + { NPC_POISONATTACK, "POISONATTACK", "NPC_POISONATTACK" } , + { NPC_PROVOCATION, "PROVOCATION", "NPC_PROVOCATION" } , + { NPC_RANDOMATTACK, "RANDOMATTACK", "NPC_RANDOMATTACK" } , + { NPC_RANGEATTACK, "RANGEATTACK", "NPC_RANGEATTACK" } , + { NPC_REBIRTH, "REBIRTH", "NPC_REBIRTH" } , + { NPC_SELFDESTRUCTION, "SELFDESTRUCTION", "Kabooooom!" } , + { NPC_SELFDESTRUCTION2, "SELFDESTRUCTION2", "NPC_SELFDESTRUCTION2" } , + { NPC_SILENCEATTACK, "SILENCEATTACK", "NPC_SILENCEATTACK" } , + { NPC_SLEEPATTACK, "SLEEPATTACK", "NPC_SLEEPATTACK" } , + { NPC_SMOKING, "SMOKING", "NPC_SMOKING" } , + { NPC_SPLASHATTACK, "SPLASHATTACK", "NPC_SPLASHATTACK" } , + { NPC_STUNATTACK, "STUNATTACK", "NPC_STUNATTACK" } , + { NPC_SUICIDE, "SUICIDE", "NPC_SUICIDE" } , + { NPC_SUMMONMONSTER, "SUMMONMONSTER", "NPC_SUMMONMONSTER" } , + { NPC_SUMMONSLAVE, "SUMMONSLAVE", "NPC_SUMMONSLAVE" } , + { NPC_TELEKINESISATTACK, "TELEKINESISATTACK", "NPC_TELEKINESISATTACK" } , + { NPC_TRANSFORMATION, "TRANSFORMATION", "NPC_TRANSFORMATION" } , + { NPC_WATERATTACK, "WATERATTACK", "NPC_WATERATTACK" } , + { NPC_WINDATTACK, "WINDATTACK", "NPC_WINDATTACK" } , + { NV_BASIC, "BASIC", "Basic_Skill" } , + { NV_FIRSTAID, "FIRSTAID", "First Aid" } , + { NV_TRICKDEAD, "TRICKDEAD", "Play_Dead" } , + { PA_GOSPEL, "GOSPEL", "Gospel" } , + { PA_PRESSURE, "PRESSURE", "Pressure" } , + { PA_SACRIFICE, "SACRIFICE", "Sacrificial_Ritual" } , + { PF_FOGWALL, "FOGWALL", "Wall_of_Fog" } , + { PF_HPCONVERSION, "HPCONVERSION", "Health_Conversion" } , + { PF_MEMORIZE, "MEMORIZE", "Memorize" } , + { PF_MINDBREAKER, "MINDBREAKER", "Mind_Breaker" } , + { PF_SOULBURN, "SOULBURN", "Soul_Burn" } , + { PF_SOULCHANGE, "SOULCHANGE", "Soul_Change" } , + { PF_SPIDERWEB, "SPIDERWEB", "Spider_Web" } , + { PR_ASPERSIO, "ASPERSIO", "Aspersio" } , + { PR_BENEDICTIO, "BENEDICTIO", "B.S_Sacramenti" } , + { PR_GLORIA, "GLORIA", "Gloria" } , + { PR_IMPOSITIO, "IMPOSITIO", "Impositio_Manus" } , + { PR_KYRIE, "KYRIE", "Kyrie_Eleison" } , + { PR_LEXAETERNA, "LEXAETERNA", "Lex_Aeterna" } , + { PR_LEXDIVINA, "LEXDIVINA", "Lex_Divina" } , + { PR_MACEMASTERY, "MACEMASTERY", "Mace_Mastery" } , + { PR_MAGNIFICAT, "MAGNIFICAT", "Magnificat" } , + { PR_MAGNUS, "MAGNUS", "Magnus_Exorcismus" } , + { PR_SANCTUARY, "SANCTUARY", "Santuary" } , + { PR_SLOWPOISON, "SLOWPOISON", "Slow_Poison" } , + { PR_STRECOVERY, "STRECOVERY", "Status_Recovery" } , + { PR_SUFFRAGIUM, "SUFFRAGIUM", "Suffragium" } , + { PR_TURNUNDEAD, "TURNUNDEAD", "Turn_Undead" } , + { RG_BACKSTAP, "BACKSTAP", "Back_Stab" } , + { RG_CLEANER, "CLEANER", "Remover" } , + { RG_COMPULSION, "COMPULSION", "Compulsion_Discount" } , + { RG_FLAGGRAFFITI, "FLAGGRAFFITI", "Flag_Graffity" } , + { RG_GANGSTER, "GANGSTER", "Gangster's_Paradise" } , + { RG_GRAFFITI, "GRAFFITI", "Graffiti" } , + { RG_INTIMIDATE, "INTIMIDATE", "Intimidate" } , + { RG_PLAGIARISM, "PLAGIARISM", "Plagiarism" } , + { RG_RAID, "RAID", "Raid" } , + { RG_SNATCHER, "SNATCHER", "Snatcher" } , + { RG_STEALCOIN, "STEALCOIN", "Steal_Coin" } , + { RG_STRIPARMOR, "STRIPARMOR", "Strip_Armor" } , + { RG_STRIPHELM, "STRIPHELM", "Strip_Helm" } , + { RG_STRIPSHIELD, "STRIPSHIELD", "Strip_Shield" } , + { RG_STRIPWEAPON, "STRIPWEAPON", "Strip_Weapon" } , + { RG_TUNNELDRIVE, "TUNNELDRIVE", "Tunnel_Drive" } , + { SA_ABRACADABRA, "ABRACADABRA", "Hocus-pocus" } , + { SA_ADVANCEDBOOK, "ADVANCEDBOOK", "Advanced_Book" } , + { SA_AUTOSPELL, "AUTOSPELL", "Auto_Cast" } , + { SA_CASTCANCEL, "CASTCANCEL", "Cast_Cancel" } , + { SA_CLASSCHANGE, "CLASSCHANGE", "Class_Change" } , + { SA_COMA, "COMA", "Coma" } , + { SA_DEATH, "DEATH", "Death" } , + { SA_DELUGE, "DELUGE", "Deluge" } , + { SA_DISPELL, "DISPELL", "Dispel" } , + { SA_DRAGONOLOGY, "DRAGONOLOGY", "Dragonology" } , + { SA_FLAMELAUNCHER, "FLAMELAUNCHER", "Flame_Launcher" } , + { SA_FORTUNE, "FORTUNE", "Fortune" } , + { SA_FREECAST, "FREECAST", "Cast_Freedom" } , + { SA_FROSTWEAPON, "FROSTWEAPON", "Frost_Weapon" } , + { SA_FULLRECOVERY, "FULLRECOVERY", "Full_Recovery" } , + { SA_GRAVITY, "GRAVITY", "Gravity" } , + { SA_INSTANTDEATH, "INSTANTDEATH", "Instant_Death" } , + { SA_LANDPROTECTOR, "LANDPROTECTOR", "Land_Protector" } , + { SA_LEVELUP, "LEVELUP", "Level_Up" } , + { SA_LIGHTNINGLOADER, "LIGHTNINGLOADER", "Lightning_Loader" } , + { SA_MAGICROD, "MAGICROD", "Magic_Rod" } , + { SA_MONOCELL, "MONOCELL", "Monocell" } , + { SA_QUESTION, "QUESTION", "Question?" } , + { SA_REVERSEORCISH, "REVERSEORCISH", "Reverse_Orcish" } , + { SA_SEISMICWEAPON, "SEISMICWEAPON", "Seismic_Weapon" } , + { SA_SPELLBREAKER, "SPELLBREAKER", "Break_Spell" } , + { SA_SUMMONMONSTER, "SUMMONMONSTER", "Summon_Monster" } , + { SA_TAMINGMONSTER, "TAMINGMONSTER", "Taming_Monster" } , + { SA_VIOLENTGALE, "VIOLENTGALE", "Violent_Gale" } , + { SA_VOLCANO, "VOLCANO", "Volcano" } , + { SG_DEVIL, "DEVIL", "Devil" } , + { SG_FEEL, "FEEL", "Feel" } , + { SG_FRIEND, "FRIEND", "Friend" } , + { SG_FUSION, "FUSION", "Fusion" } , + { SG_HATE, "HATE", "Hate" } , + { SG_KNOWLEDGE, "KNOWLEDGE", "Knowledge" } , + { SG_MOON_ANGER, "ANGER", "Moon Anger" } , + { SG_MOON_BLESS, "BLESS", "Moon Bless" } , + { SG_MOON_COMFORT, "COMFORT", "Moon Comfort" } , + { SG_MOON_WARM, "WARM", "Moon Warm" } , + { SG_STAR_ANGER, "ANGER", "Star Anger" } , + { SG_STAR_BLESS, "BLESS", "Star Bless" } , + { SG_STAR_COMFORT, "COMFORT", "Star Comfort" } , + { SG_STAR_WARM, "WARM", "Star Warm" } , + { SG_SUN_ANGER, "ANGER", "Sun Anger" } , + { SG_SUN_BLESS, "BLESS", "Sun Bless" } , + { SG_SUN_COMFORT, "COMFORT", "Sun Comfort" } , + { SG_SUN_WARM, "WARM", "Sun Warm" } , + { SL_ALCHEMIST, "ALCHEMIST", "Alchemist" } , + { SL_ASSASIN, "ASSASIN", "Assasin" } , + { SL_BARDDANCER, "BARDDANCER", "Bard Dancer" } , + { SL_BLACKSMITH, "BLACKSMITH", "Black Smith" } , + { SL_CRUSADER, "CRUSADER", "Crusader" } , + { SL_HUNTER, "HUNTER", "Hunter" } , + { SL_KAAHI, "KAAHI", "Kaahi" } , + { SL_KAINA, "KAINA", "Kaina" } , + { SL_KAITE, "KAITE", "Kaite" } , + { SL_KAIZEL, "KAIZEL", "Kaizel" } , + { SL_KAUPE, "KAUPE", "Kaupe" } , + { SL_KNIGHT, "KNIGHT", "Knight" } , + { SL_MONK, "MONK", "Monk" } , + { SL_PRIEST, "PRIEST", "Priest" } , + { SL_ROGUE, "ROGUE", "Rogue" } , + { SL_SAGE, "SAGE", "Sage" } , + { SL_SKA, "SKA", "SKA" } , + { SL_SKE, "SKE", "SKE" } , + { SL_SMA, "SMA", "SMA" } , + { SL_SOULLINKER, "SOULLINKER", "Soul Linker" } , + { SL_STAR, "STAR", "Star" } , + { SL_STIN, "STIN", "Stin" } , + { SL_STUN, "STUN", "Stun" } , + { SL_SUPERNOVICE, "SUPERNOVICE", "Super Novice" } , + { SL_SWOO, "SWOO", "Swoo" } , + { SL_WIZARD, "WIZARD", "Wizard" } , + { SM_AUTOBERSERK, "AUTOBERSERK", "Auto_Berserk" } , + { SM_BASH, "BASH", "Bash" } , + { SM_ENDURE, "ENDURE", "Endure" } , + { SM_FATALBLOW, "FATALBLOW", "Attack_Weak_Point" } , + { SM_MAGNUM, "MAGNUM", "Magnum_Break" } , + { SM_MOVINGRECOVERY, "MOVINGRECOVERY", "Moving_HP_Recovery" } , + { SM_PROVOKE, "PROVOKE", "Provoke" } , + { SM_RECOVERY, "RECOVERY", "Increase_HP_Recovery" } , + { SM_SWORD, "SWORD", "Sword_Mastery" } , + { SM_TWOHAND, "TWOHAND", "Two-Handed_Sword_Mastery" } , + { SN_FALCONASSAULT, "FALCONASSAULT", "Falcon_Assault" } , + { SN_SHARPSHOOTING, "SHARPSHOOTING", "Sharpshooting" } , + { SN_SIGHT, "SIGHT", "True_Sight" } , + { SN_WINDWALK, "WINDWALK", "Wind_Walk" } , + { ST_CHASEWALK, "CHASEWALK", "Chase_Walk" } , + { ST_REJECTSWORD, "REJECTSWORD", "Reject_Sword" } , + { ST_STEALBACKPACK, "STEALBACKPACK", "Steal_Backpack" } , + { TF_BACKSLIDING, "BACKSLIDING", "Back_Sliding" } , + { TF_DETOXIFY, "DETOXIFY", "Detoxify" } , + { TF_DOUBLE, "DOUBLE", "Double_Attack" } , + { TF_HIDING, "HIDING", "Hiding" } , + { TF_MISS, "MISS", "Improve_Dodge" } , + { TF_PICKSTONE, "PICKSTONE", "Take_Stone" } , + { TF_POISON, "POISON", "Envenom" } , + { TF_SPRINKLESAND, "SPRINKLESAND", "Throw_Sand" } , + { TF_STEAL, "STEAL", "Steal" } , + { TF_THROWSTONE, "THROWSTONE", "Throw_Stone" } , + { TK_COUNTER, "COUNTER", "Counter" } , + { TK_DODGE, "DODGE", "Dodge" } , + { TK_DOWNKICK, "DOWNKICK", "Down Kick" } , + { TK_HIGHJUMP, "HIGHJUMP", "High Jump" } , + { TK_HPTIME, "HPTIME", "HP Time" } , + { TK_JUMPKICK, "JUMPKICK", "Jump Kick" } , + { TK_POWER, "POWER", "Power" } , + { TK_READYCOUNTER, "READYCOUNTER", "Ready Counter" } , + { TK_READYDOWN, "READYDOWN", "Ready Down" } , + { TK_READYSTORM, "READYSTORM", "Ready Storm" } , + { TK_READYTURN, "READYTURN", "Ready Turn" } , + { TK_RUN, "RUN", "TK_RUN" } , + { TK_SEVENWIND, "SEVENWIND", "Seven Wind" } , + { TK_SPTIME, "SPTIME", "SP Time" } , + { TK_STORMKICK, "STORMKICK", "Storm Kick" } , + { TK_TURNKICK, "TURNKICK", "Turn Kick" } , + { WE_BABY, "BABY", "Adopt_Baby" } , + { WE_CALLBABY, "CALLBABY", "Call_Baby" } , + { WE_CALLPARENT, "CALLPARENT", "Call_Parent" } , + { WE_CALLPARTNER, "CALLPARTNER", "I Want to See You" } , + { WE_FEMALE, "FEMALE", "I Only Look Up to You" } , + { WE_MALE, "MALE", "I Will Protect You" } , + { WS_CARTBOOST, "CARTBOOST", "Cart_Boost" } , + { WS_CREATECOIN, "CREATECOIN", "Create_Coins" } , + { WS_CREATENUGGET, "CREATENUGGET", "Create_Nuggets" } , + { WS_MELTDOWN, "MELTDOWN", "Meltdown" } , + { WS_SYSTEMCREATE, "SYSTEMCREATE", "Create_System_tower" } , + { WZ_EARTHSPIKE, "EARTHSPIKE", "Earth_Spike" } , + { WZ_ESTIMATION, "ESTIMATION", "Sense" } , + { WZ_FIREIVY, "FIREIVY", "Fire_Ivy" } , + { WZ_FIREPILLAR, "FIREPILLAR", "Fire_Pillar" } , + { WZ_FROSTNOVA, "FROSTNOVA", "Frost_Nova" } , + { WZ_HEAVENDRIVE, "HEAVENDRIVE", "Heaven's_Drive" } , + { WZ_ICEWALL, "ICEWALL", "Ice_Wall" } , + { WZ_JUPITEL, "JUPITEL", "Jupitel_Thunder" } , + { WZ_METEOR, "METEOR", "Meteor_Storm" } , + { WZ_QUAGMIRE, "QUAGMIRE", "Quagmire" } , + { WZ_SIGHTRASHER, "SIGHTRASHER", "Sightrasher" } , + { WZ_STORMGUST, "STORMGUST", "Storm_Gust" } , + { WZ_VERMILION, "VERMILION", "Lord_of_Vermilion" } , + { WZ_WATERBALL, "WATERBALL", "Water_Ball" } , + { 0, 0, 0 } +}; + +static const int dirx[8]={0,-1,-1,-1,0,1,1,1}; +static const int diry[8]={1,1,0,-1,-1,-1,0,1}; + +static int rdamage; + +/* スキルデータベース */ +struct skill_db skill_db[MAX_SKILL_DB]; + +/* アイテム作成データベース */ +struct skill_produce_db skill_produce_db[MAX_SKILL_PRODUCE_DB]; + +/* 矢作成スキルデータベース */ +struct skill_arrow_db skill_arrow_db[MAX_SKILL_ARROW_DB]; + +/* アブラカダブラ発動スキルデータベース */ +struct skill_abra_db skill_abra_db[MAX_SKILL_ABRA_DB]; + +int skill_get_hit( int id ){ return skill_db[id].hit; } +int skill_get_inf( int id ){ return skill_db[id].inf; } +int skill_get_pl( int id ){ return skill_db[id].pl; } +int skill_get_nk( int id ){ return skill_db[id].nk; } +int skill_get_max( int id ){ return skill_db[id].max; } +int skill_get_range( int id , int lv ){ return (lv <= 0) ? 0:skill_db[id].range[lv-1]; } +int skill_get_hp( int id ,int lv ){ return (lv <= 0) ? 0:skill_db[id].hp[lv-1]; } +int skill_get_sp( int id ,int lv ){ return (lv <= 0) ? 0:skill_db[id].sp[lv-1]; } +int skill_get_zeny( int id ,int lv ){ return (lv <= 0) ? 0:skill_db[id].zeny[lv-1]; } +int skill_get_num( int id ,int lv ){ return (lv <= 0) ? 0:skill_db[id].num[lv-1]; } +int skill_get_cast( int id ,int lv ){ return (lv <= 0) ? 0:skill_db[id].cast[lv-1]; } +int skill_get_delay( int id ,int lv ){ return (lv <= 0) ? 0:skill_db[id].delay[lv-1]; } +int skill_get_time( int id ,int lv ){ return (lv <= 0) ? 0:skill_db[id].upkeep_time[lv-1]; } +int skill_get_time2( int id ,int lv ){ return (lv <= 0) ? 0:skill_db[id].upkeep_time2[lv-1]; } +int skill_get_castdef( int id ){ return skill_db[id].cast_def_rate; } +int skill_get_weapontype( int id ){ return skill_db[id].weapon; } +int skill_get_inf2( int id ){ return skill_db[id].inf2; } +int skill_get_maxcount( int id ){ return skill_db[id].maxcount; } +int skill_get_blewcount( int id ,int lv ){ return (lv <= 0) ? 0:skill_db[id].blewcount[lv-1]; } +int skill_get_mhp( int id ,int lv ){ return (lv <= 0) ? 0:skill_db[id].mhp[lv-1]; } +int skill_get_castnodex( int id ,int lv ){ return (lv <= 0) ? 0:skill_db[id].castnodex[lv-1]; } + +/* プロトタイプ */ +struct skill_unit_group *skill_unitsetting( struct block_list *src, int skillid,int skilllv,int x,int y,int flag); +int skill_check_condition( struct map_session_data *sd,int type); +int skill_castend_damage_id( struct block_list* src, struct block_list *bl,int skillid,int skilllv,unsigned int tick,int flag ); +int skill_frostjoke_scream(struct block_list *bl,va_list ap); +int skill_status_change_timer_sub(struct block_list *bl, va_list ap ); +int skill_attack_area(struct block_list *bl,va_list ap); +int skill_abra_dataset(int skilllv); +int skill_clear_element_field(struct block_list *bl); +int skill_landprotector(struct block_list *bl, va_list ap ); +int skill_trap_splash(struct block_list *bl, va_list ap ); +int skill_count_target(struct block_list *bl, va_list ap ); + +// [MouseJstr] - skill ok to cast? and when? +static int skillnotok(int skillid, struct map_session_data *sd) { + if (sd == 0) + return 0; + if (pc_isGM(sd) >= 20) + return 0; // gm's can do anything damn thing they want + switch (skillid) { + case AL_WARP: + case AL_TELEPORT: + case MC_VENDING: + case MC_IDENTIFY: + return 0; // always allowed + default: + return(map[sd->bl.m].flag.noskill); + } +} + + +static int distance(int x0,int y0,int x1,int y1) +{ + int dx,dy; + + dx=abs(x0-x1); + dy=abs(y0-y1); + return dx>dy ? dx : dy; +} + +/* スキルユニットIDを返す(これもデータベースに入れたいな) */ +int skill_get_unit_id(int id,int flag) +{ + + switch(id){ + case MG_SAFETYWALL: return 0x7e; /* セイフティウォール */ + case MG_FIREWALL: return 0x7f; /* ファイアーウォール */ + case AL_WARP: return (flag==0)?0x81:0x80; /* ワープポータル */ + case PR_BENEDICTIO: return 0x82; /* 聖体降福 */ + case PR_SANCTUARY: return 0x83; /* サンクチュアリ */ + case PR_MAGNUS: return 0x84; /* マグヌスエクソシズム */ + case AL_PNEUMA: return 0x85; /* ニューマ */ + case MG_THUNDERSTORM: return 0x86; /* サンダーストーム */ + case WZ_HEAVENDRIVE: return 0x86; /* ヘヴンズドライブ */ + case WZ_SIGHTRASHER: return 0x86; /* サイトラッシャー */ + case WZ_METEOR: return 0x86; /* メテオストーム */ + case WZ_VERMILION: return 0x86; /* ロードオブヴァーミリオン */ + case WZ_FROSTNOVA: return 0x86; /* フロストノヴァ */ + case WZ_STORMGUST: return 0x86; /* ストームガスト(とりあえずLoVと同じで処理) */ + case CR_GRANDCROSS: return 0x86; /* グランドクロス */ + case WZ_FIREPILLAR: return (flag==0)?0x87:0x88; /* ファイアーピラー */ + case HT_TALKIEBOX: return 0x99; /* トーキーボックス */ + case WZ_ICEWALL: return 0x8d; /* アイスウォール */ + case WZ_QUAGMIRE: return 0x8e; /* クァグマイア */ + case HT_BLASTMINE: return 0x8f; /* ブラストマイン */ + case HT_SKIDTRAP: return 0x90; /* スキッドトラップ */ + case HT_ANKLESNARE: return 0x91; /* アンクルスネア */ + case AS_VENOMDUST: return 0x92; /* ベノムダスト */ + case HT_LANDMINE: return 0x93; /* ランドマイン */ + case HT_SHOCKWAVE: return 0x94; /* ショックウェーブトラップ */ + case HT_SANDMAN: return 0x95; /* サンドマン */ + case HT_FLASHER: return 0x96; /* フラッシャー */ + case HT_FREEZINGTRAP: return 0x97; /* フリージングトラップ */ + case HT_CLAYMORETRAP: return 0x98; /* クレイモアートラップ */ + case SA_VOLCANO: return 0x9a; /* ボルケーノ */ + case SA_DELUGE: return 0x9b; /* デリュージ */ + case SA_VIOLENTGALE: return 0x9c; /* バイオレントゲイル */ + case SA_LANDPROTECTOR: return 0x9d; /* ランドプロテクター */ + case BD_LULLABY: return 0x9e; /* 子守歌 */ + case BD_RICHMANKIM: return 0x9f; /* ニヨルドの宴 */ + case BD_ETERNALCHAOS: return 0xa0; /* 永遠の混沌 */ + case BD_DRUMBATTLEFIELD:return 0xa1; /* 戦太鼓の響き */ + case BD_RINGNIBELUNGEN: return 0xa2; /* ニーベルングの指輪 */ + case BD_ROKISWEIL: return 0xa3; /* ロキの叫び */ + case BD_INTOABYSS: return 0xa4; /* 深淵の中に */ + case BD_SIEGFRIED: return 0xa5; /* 不死身のジークフリード */ + case BA_DISSONANCE: return 0xa6; /* 不協和音 */ + case BA_WHISTLE: return 0xa7; /* 口笛 */ + case BA_ASSASSINCROSS: return 0xa8; /* 夕陽のアサシンクロス */ + case BA_POEMBRAGI: return 0xa9; /* ブラギの詩 */ + case BA_APPLEIDUN: return 0xaa; /* イドゥンの林檎 */ + case DC_UGLYDANCE: return 0xab; /* 自分勝手なダンス */ + case DC_HUMMING: return 0xac; /* ハミング */ + case DC_DONTFORGETME: return 0xad; /* 私を忘れないで… */ + case DC_FORTUNEKISS: return 0xae; /* 幸運のキス */ + case DC_SERVICEFORYOU: return 0xaf; /* サービスフォーユー */ + case RG_GRAFFITI: return 0xb0; /* グラフィティ */ + case AM_DEMONSTRATION: return 0xb1; /* デモンストレーション */ + case WE_CALLPARTNER: return 0xb2; /* あなたに逢いたい */ + case PA_GOSPEL: return 0xb3; /* ゴスペル */ + case HP_BASILICA: return 0xb4; /* バジリカ */ + case PF_FOGWALL: return 0xb6; /* フォグウォール */ + case PF_SPIDERWEB: return 0xb7; /* スパイダーウェッブ */ + } + return 0; + /* + 0x89,0x8a,0x8b 表示無し + 0x9a 炎属性の詠唱みたいなエフェクト + 0x9b 水属性の詠唱みたいなエフェクト + 0x9c 風属性の詠唱みたいなエフェクト + 0x9d 白い小さなエフェクト + 0xb1 Alchemist Demonstration + 0xb2 = Pink Warp Portal + 0xb3 = Gospel For Paladin + 0xb4 = Basilica + 0xb5 = Empty + 0xb6 = Fog Wall for Professor + 0xb7 = Spider Web for Professor + 0xb8 = Empty + 0xb9 = + */ +} + +/*========================================== + * スキル追加効果 + *------------------------------------------ + */ +int skill_additional_effect( struct block_list* src, struct block_list *bl,int skillid,int skilllv,int attack_type,unsigned int tick) +{ + /* MOB追加効果スキル用 */ + const int sc[]={ + SC_POISON, SC_BLIND, SC_SILENCE, SC_STAN, + SC_STONE, SC_CURSE, SC_SLEEP + }; + const int sc2[]={ + MG_STONECURSE,MG_FROSTDIVER,NPC_STUNATTACK, + NPC_SLEEPATTACK,TF_POISON,NPC_CURSEATTACK, + NPC_SILENCEATTACK,0,NPC_BLINDATTACK + }; + + struct map_session_data *sd=NULL; + struct map_session_data *dstsd=NULL; + struct mob_data *md=NULL; + struct mob_data *dstmd=NULL; + struct pet_data *pd=NULL; + + int skill,skill2; + int rate,luk; + + int sc_def_mdef,sc_def_vit,sc_def_int,sc_def_luk; + int sc_def_mdef2,sc_def_vit2,sc_def_int2,sc_def_luk2; + + nullpo_retr(0, src); + nullpo_retr(0, bl); + + if(skilllv < 0) return 0; + + if(src->type==BL_PC){ + nullpo_retr(0, sd=(struct map_session_data *)src); + }else if(src->type==BL_MOB){ + nullpo_retr(0, md=(struct mob_data *)src); //未使用? + }else if(src->type==BL_PET){ + nullpo_retr(0, pd=(struct pet_data *)src); // [Valaris] + } + + //対象の耐性 + luk = battle_get_luk(bl); + sc_def_mdef=100 - (3 + battle_get_mdef(bl) + luk/3); + sc_def_vit=100 - (3 + battle_get_vit(bl) + luk/3); + sc_def_int=100 - (3 + battle_get_int(bl) + luk/3); + sc_def_luk=100 - (3 + luk); + //自分の耐性 + luk = battle_get_luk(src); + sc_def_mdef2=100 - (3 + battle_get_mdef(src) + luk/3); + sc_def_vit2=100 - (3 + battle_get_vit(src) + luk/3); + sc_def_int2=100 - (3 + battle_get_int(src) + luk/3); + sc_def_luk2=100 - (3 + luk); + if(bl->type==BL_PC) + dstsd=(struct map_session_data *)bl; + else if(bl->type==BL_MOB){ + dstmd=(struct mob_data *)bl; //未使用? + if(sc_def_mdef>50) + sc_def_mdef=50; + if(sc_def_vit>50) + sc_def_vit=50; + if(sc_def_int>50) + sc_def_int=50; + if(sc_def_luk>50) + sc_def_luk=50; + } + if(sc_def_mdef<0) + sc_def_mdef=0; + if(sc_def_vit<0) + sc_def_vit=0; + if(sc_def_int<0) + sc_def_int=0; + + switch(skillid){ + case 0: /* 通常攻撃 */ + /* 自動鷹 */ + if( sd && pc_isfalcon(sd) && sd->status.weapon == 11 && (skill=pc_checkskill(sd,HT_BLITZBEAT))>0 && + rand()%1000 <= sd->paramc[5]*10/3+1 ) { + int lv=(sd->status.job_level+9)/10; + skill_castend_damage_id(src,bl,HT_BLITZBEAT,(skill<lv)?skill:lv,tick,0xf00000); + } + // スナッチャー + if(sd && sd->status.weapon != 11 && (skill=pc_checkskill(sd,RG_SNATCHER)) > 0) + if((skill*15 + 55) + (skill2 = pc_checkskill(sd,TF_STEAL))*10 > rand()%1000) { + if(pc_steal_item(sd,bl)) + clif_skill_nodamage(src,bl,TF_STEAL,skill2,1); + else + clif_skill_fail(sd,skillid,0,0); + } + break; + + case SM_BASH: /* バッシュ(急所攻撃) */ + if( sd && (skill=pc_checkskill(sd,SM_FATALBLOW))>0 ){ + if( rand()%100 < 6*(skilllv-5)*sc_def_vit/100 ) + skill_status_change_start(bl,SC_STAN,skilllv,0,0,0,skill_get_time2(SM_FATALBLOW,skilllv),0); + } + break; + + case TF_POISON: /* インベナム */ + case AS_SPLASHER: /* ベナムスプラッシャー */ + if(rand()%100< (2*skilllv+10)*sc_def_vit/100 ) + skill_status_change_start(bl,SC_POISON,skilllv,0,0,0,skill_get_time2(skillid,skilllv),0); + else{ + if(sd && skillid==TF_POISON) + clif_skill_fail(sd,skillid,0,0); + } + break; + + case AS_SONICBLOW: /* ソニックブロー */ + if( rand()%100 < (2*skilllv+10)*sc_def_vit/100 ) + skill_status_change_start(bl,SC_STAN,skilllv,0,0,0,skill_get_time2(skillid,skilllv),0); + break; + + + case HT_FREEZINGTRAP: /* フリージングトラップ */ + rate=skilllv*3+35; + if(rand()%100 < rate*sc_def_mdef/100) + skill_status_change_start(bl,SC_FREEZE,skilllv,0,0,0,skill_get_time2(skillid,skilllv),0); + break; + + case MG_FROSTDIVER: /* フロストダイバー */ + case WZ_FROSTNOVA: /* フロストノヴァ */ + rate=(skilllv*3+35)*sc_def_mdef/100-(battle_get_int(bl)+battle_get_luk(bl))/15; + rate=rate<=5?5:rate; + if(rand()%100 < rate) + skill_status_change_start(bl,SC_FREEZE,skilllv,0,0,0,skill_get_time2(skillid,skilllv),0); + else if(sd) + clif_skill_fail(sd,skillid,0,0); + break; + + case WZ_STORMGUST: /* ストームガスト */ + { + struct status_change *sc_data = battle_get_sc_data(bl); + if(sc_data) { + sc_data[SC_FREEZE].val3++; + if(sc_data[SC_FREEZE].val3 >= 3) + skill_status_change_start(bl,SC_FREEZE,skilllv,0,0,0,skill_get_time2(skillid,skilllv),0); + } + } + break; + + case HT_LANDMINE: /* ランドマイン */ + if( rand()%100 < (5*skilllv+30)*sc_def_vit/100 ) + skill_status_change_start(bl,SC_STAN,skilllv,0,0,0,skill_get_time2(skillid,skilllv),0); + break; + + case HT_SHOCKWAVE: /* ショックウェーブトラップ */ + if(map[bl->m].flag.pvp && dstsd){ + dstsd->status.sp -= dstsd->status.sp*(5+15*skilllv)/100; + pc_calcstatus(dstsd,0); + } + break; + case HT_SANDMAN: /* サンドマン */ + if( rand()%100 < (5*skilllv+30)*sc_def_int/100 ) + skill_status_change_start(bl,SC_SLEEP,skilllv,0,0,0,skill_get_time2(skillid,skilllv),0); + break; + case TF_SPRINKLESAND: /* 砂まき */ + if( rand()%100 < 15*sc_def_int/100 ) + skill_status_change_start(bl,SC_BLIND,skilllv,0,0,0,skill_get_time2(skillid,skilllv),0); + break; + + case TF_THROWSTONE: /* 石投げ */ + if( rand()%100 < 5*sc_def_vit/100 ) + skill_status_change_start(bl,SC_STAN,skilllv,0,0,0,skill_get_time2(skillid,skilllv),0); + break; + + case CR_HOLYCROSS: /* ホーリークロス */ + if( rand()%100 < 3*skilllv*sc_def_int/100 ) + skill_status_change_start(bl,SC_BLIND,skilllv,0,0,0,skill_get_time2(skillid,skilllv),0); + break; + + case CR_GRANDCROSS: /* グランドクロス */ + { + int race = battle_get_race(bl); + if( (battle_check_undead(race,battle_get_elem_type(bl)) || race == 6) && rand()%100 < 100000*sc_def_int/100) //強制付与だが完全耐性には無効 + skill_status_change_start(bl,SC_BLIND,skilllv,0,0,0,skill_get_time2(skillid,skilllv),0); + } + break; + + case CR_SHIELDCHARGE: /* シールドチャージ */ + if( rand()%100 < (15 + skilllv*5)*sc_def_vit/100 ) + skill_status_change_start(bl,SC_STAN,skilllv,0,0,0,skill_get_time2(skillid,skilllv),0); + break; + + case RG_RAID: /* サプライズアタック */ + if( rand()%100 < (10+3*skilllv)*sc_def_vit/100 ) + skill_status_change_start(bl,SC_STAN,skilllv,0,0,0,skill_get_time2(skillid,skilllv),0); + if( rand()%100 < (10+3*skilllv)*sc_def_int/100 ) + skill_status_change_start(bl,SC_BLIND,skilllv,0,0,0,skill_get_time2(skillid,skilllv),0); + break; + case BA_FROSTJOKE: + if(rand()%100 < (15+5*skilllv)*sc_def_mdef/100) + skill_status_change_start(bl,SC_FREEZE,skilllv,0,0,0,skill_get_time2(skillid,skilllv),0); + break; + + case DC_SCREAM: + if( rand()%100 < (25+5*skilllv)*sc_def_vit/100 ) + skill_status_change_start(bl,SC_STAN,skilllv,0,0,0,skill_get_time2(skillid,skilllv),0); + break; + + case BD_LULLABY: /* 子守唄 */ + if( rand()%100 < 15*sc_def_int/100 ) + skill_status_change_start(bl,SC_SLEEP,skilllv,0,0,0,skill_get_time2(skillid,skilllv),0); + break; + + /* MOBの追加効果付きスキル */ + + case NPC_PETRIFYATTACK: + if(rand()%100 < sc_def_mdef) + skill_status_change_start(bl,sc[skillid-NPC_POISON],skilllv,0,0,0,skill_get_time2(skillid,skilllv),0); + break; + case NPC_POISON: + case NPC_SILENCEATTACK: + case NPC_STUNATTACK: + if(rand()%100 < sc_def_vit && src->type!=BL_PET) + skill_status_change_start(bl,sc[skillid-NPC_POISON],skilllv,0,0,0,skill_get_time2(skillid,skilllv),0); + if(src->type==BL_PET) + skill_status_change_start(bl,sc[skillid-NPC_POISON],skilllv,0,0,0,skilllv*1000,0); + break; + case NPC_CURSEATTACK: + if(rand()%100 < sc_def_luk) + skill_status_change_start(bl,sc[skillid-NPC_POISON],skilllv,0,0,0,skill_get_time2(skillid,skilllv),0); + break; + case NPC_SLEEPATTACK: + case NPC_BLINDATTACK: + if(rand()%100 < sc_def_int) + skill_status_change_start(bl,sc[skillid-NPC_POISON],skilllv,0,0,0,skill_get_time2(skillid,skilllv),0); + break; + case NPC_MENTALBREAKER: + if(dstsd) { + int sp = dstsd->status.max_sp*(10+skilllv)/100; + if(sp < 1) sp = 1; + pc_heal(dstsd,0,-sp); + } + break; + +// -- moonsoul (adding status effect chance given to wizard aoe skills meteor and vermillion) +// + case WZ_METEOR: + if(rand()%100 < sc_def_vit) + skill_status_change_start(bl,SC_STAN,skilllv,0,0,0,skill_get_time2(skillid,skilllv),0); + break; + case WZ_VERMILION: + if(rand()%100 < sc_def_int) + skill_status_change_start(bl,SC_BLIND,skilllv,0,0,0,skill_get_time2(skillid,skilllv),0); + break; + +// -- moonsoul (stun ability of new champion skill tigerfist) +// + case CH_TIGERFIST: + if( rand()%100 < (5 + skilllv*5)*sc_def_vit/100 ) + skill_status_change_start(bl,SC_STAN,skilllv,0,0,0,skill_get_time2(skillid,skilllv),0); + break; + + case LK_SPIRALPIERCE: + if( rand()%100 < (15 + skilllv*5)*sc_def_vit/100 ) + skill_status_change_start(bl,SC_STAN,skilllv,0,0,0,skill_get_time2(skillid,skilllv),0); + break; + case ST_REJECTSWORD: /* フリージングトラップ */ + if( rand()%100 < (10 + skilllv*5) ) + skill_status_change_start(bl,SC_AUTOCOUNTER,skilllv,0,0,0,skill_get_time2(skillid,skilllv),0); + break; + case PF_FOGWALL: /* ホーリークロス */ + if( rand()%100 < 3*skilllv*sc_def_int/100 ) + skill_status_change_start(bl,SC_BLIND,skilllv,0,0,0,skill_get_time2(skillid,skilllv),0); + break; + case LK_HEADCRUSH: /* ヘッドクラッシュ */ + {//条件が良く分からないので適当に + int race=battle_get_race(bl); + if( !(battle_check_undead(race,battle_get_elem_type(bl)) || race == 6) && rand()%100 < (2*skilllv+10)*sc_def_vit/100 ) + skill_status_change_start(bl,SC_HEADCRUSH,skilllv,0,0,0,skill_get_time2(skillid,skilllv),0); + } + break; + case LK_JOINTBEAT: /* ジョイントビート */ + //条件が良く分からないので適当に + if( rand()%100 < (2*skilllv+10)*sc_def_vit/100 ) + skill_status_change_start(bl,SC_JOINTBEAT,skilllv,0,0,0,skill_get_time2(skillid,skilllv),0); + break; + case PF_SPIDERWEB: /* スパイダーウェッブ */ + { + int sec=skill_get_time2(skillid,skilllv); + if(map[src->m].flag.pvp) //PvPでは拘束時間半減? + sec = sec/2; + battle_stopwalking(bl,1); + skill_status_change_start(bl,SC_SPIDERWEB,skilllv,0,0,0,sec,0); + } + break; + case ASC_METEORASSAULT: /* メテオアサルト */ + if( rand()%100 < (15 + skilllv*5)*sc_def_vit/100 ) //状態異常は詳細が分からないので適当に + skill_status_change_start(bl,SC_STAN,skilllv,0,0,0,skill_get_time2(skillid,skilllv),0); + if( rand()%100 < (10+3*skilllv)*sc_def_int/100 ) + skill_status_change_start(bl,SC_BLIND,skilllv,0,0,0,skill_get_time2(skillid,skilllv),0); + break; + case MO_EXTREMITYFIST: /* 阿修羅覇凰拳 */ + //阿修羅を使うと5分間自然回復しないようになる + skill_status_change_start(src,SkillStatusChangeTable[skillid],skilllv,0,0,0,skill_get_time2(skillid,skilllv),0 ); + break; + } + + if(sd && skillid != MC_CARTREVOLUTION && attack_type&BF_WEAPON){ /* カードによる追加効果 */ + int i; + int sc_def_card=100; + + for(i=SC_STONE;i<=SC_BLIND;i++){ + //対象に状態異常 + if(i==SC_STONE || i==SC_FREEZE) + sc_def_card=sc_def_mdef; + else if(i==SC_STAN || i==SC_POISON || i==SC_SILENCE) + sc_def_card=sc_def_vit; + else if(i==SC_SLEEP || i==SC_CONFUSION || i==SC_BLIND) + sc_def_card=sc_def_int; + else if(i==SC_CURSE) + sc_def_card=sc_def_luk; + + if(!sd->state.arrow_atk) { + if(rand()%10000 < (sd->addeff[i-SC_STONE])*sc_def_card/100 ){ + if(battle_config.battle_log) + printf("PC %d skill_addeff: cardによる異常発動 %d %d\n",sd->bl.id,i,sd->addeff[i-SC_STONE]); + skill_status_change_start(bl,i,7,0,0,0,(i==SC_CONFUSION)? 10000+7000:skill_get_time2(sc2[i-SC_STONE],7),0); + } + } + else { + if(rand()%10000 < (sd->addeff[i-SC_STONE]+sd->arrow_addeff[i-SC_STONE])*sc_def_card/100 ){ + if(battle_config.battle_log) + printf("PC %d skill_addeff: cardによる異常発動 %d %d\n",sd->bl.id,i,sd->addeff[i-SC_STONE]); + skill_status_change_start(bl,i,7,0,0,0,(i==SC_CONFUSION)? 10000+7000:skill_get_time2(sc2[i-SC_STONE],7),0); + } + } + //自分に状態異常 + if(i==SC_STONE || i==SC_FREEZE) + sc_def_card=sc_def_mdef2; + else if(i==SC_STAN || i==SC_POISON || i==SC_SILENCE) + sc_def_card=sc_def_vit2; + else if(i==SC_SLEEP || i==SC_CONFUSION || i==SC_BLIND) + sc_def_card=sc_def_int2; + else if(i==SC_CURSE) + sc_def_card=sc_def_luk2; + + if(!sd->state.arrow_atk) { + if(rand()%10000 < (sd->addeff2[i-SC_STONE])*sc_def_card/100 ){ + if(battle_config.battle_log) + printf("PC %d skill_addeff: cardによる異常発動 %d %d\n",src->id,i,sd->addeff2[i-SC_STONE]); + skill_status_change_start(src,i,7,0,0,0,(i==SC_CONFUSION)? 10000+7000:skill_get_time2(sc2[i-SC_STONE],7),0); + } + } + else { + if(rand()%10000 < (sd->addeff2[i-SC_STONE]+sd->arrow_addeff2[i-SC_STONE])*sc_def_card/100 ){ + if(battle_config.battle_log) + printf("PC %d skill_addeff: cardによる異常発動 %d %d\n",src->id,i,sd->addeff2[i-SC_STONE]); + skill_status_change_start(src,i,7,0,0,0,(i==SC_CONFUSION)? 10000+7000:skill_get_time2(sc2[i-SC_STONE],7),0); + } + } + } + } + return 0; +} + +/*========================================================================= + スキル攻撃吹き飛ばし処理 +-------------------------------------------------------------------------*/ +int skill_blown( struct block_list *src, struct block_list *target,int count) +{ + int dx=0,dy=0,nx,ny; + int x=target->x,y=target->y; + int ret,prev_state=MS_IDLE; + int moveblock; + struct map_session_data *sd=NULL; + struct mob_data *md=NULL; + struct pet_data *pd=NULL; + struct skill_unit *su=NULL; + + nullpo_retr(0, src); + nullpo_retr(0, target); + + if(target->type==BL_PC){ + nullpo_retr(0, sd=(struct map_session_data *)target); + }else if(target->type==BL_MOB){ + nullpo_retr(0, md=(struct mob_data *)target); + }else if(target->type==BL_PET){ + nullpo_retr(0, pd=(struct pet_data *)target); + }else if(target->type==BL_SKILL){ + nullpo_retr(0, su=(struct skill_unit *)target); + }else return 0; + + if(!(count&0x10000 && (sd||md||pd||su))){ /* 指定なしなら位置関係から方向を求める */ + dx=target->x-src->x; dx=(dx>0)?1:((dx<0)?-1: 0); + dy=target->y-src->y; dy=(dy>0)?1:((dy<0)?-1: 0); + } + if(dx==0 && dy==0){ + int dir=battle_get_dir(target); + if(dir>=0 && dir<8){ + dx=-dirx[dir]; + dy=-diry[dir]; + } + } + + ret=path_blownpos(target->m,x,y,dx,dy,count&0xffff); + nx=ret>>16; + ny=ret&0xffff; + moveblock=( x/BLOCK_SIZE != nx/BLOCK_SIZE || y/BLOCK_SIZE != ny/BLOCK_SIZE); + + if(count&0x20000) { + battle_stopwalking(target,1); + if(sd){ + sd->to_x=nx; + sd->to_y=ny; + sd->walktimer = 1; + clif_walkok(sd); + clif_movechar(sd); + } + else if(md) { + md->to_x=nx; + md->to_y=ny; + prev_state = md->state.state; + md->state.state = MS_WALK; + clif_fixmobpos(md); + } + else if(pd) { + pd->to_x=nx; + pd->to_y=ny; + prev_state = pd->state.state; + pd->state.state = MS_WALK; + clif_fixpetpos(pd); + } + } + else + battle_stopwalking(target,2); + + dx = nx - x; + dy = ny - y; + + if(sd) /* 画面外に出たので消去 */ + map_foreachinmovearea(clif_pcoutsight,target->m,x-AREA_SIZE,y-AREA_SIZE,x+AREA_SIZE,y+AREA_SIZE,dx,dy,0,sd); + else if(md) + map_foreachinmovearea(clif_moboutsight,target->m,x-AREA_SIZE,y-AREA_SIZE,x+AREA_SIZE,y+AREA_SIZE,dx,dy,BL_PC,md); + else if(pd) + map_foreachinmovearea(clif_petoutsight,target->m,x-AREA_SIZE,y-AREA_SIZE,x+AREA_SIZE,y+AREA_SIZE,dx,dy,BL_PC,pd); + + if(su){ + skill_unit_move_unit_group(su->group,target->m,dx,dy); + }else{ +// struct status_change *sc_data=battle_get_sc_data(target); + if(moveblock) map_delblock(target); + target->x=nx; + target->y=ny; + if(moveblock) map_addblock(target); +/*ダンス中にエフェクトは移動しないらしい + if(sc_data && sc_data[SC_DANCING].timer!=-1){ //対象がダンス中なのでエフェクトも移動 + struct skill_unit_group *sg=(struct skill_unit_group *)sc_data[SC_DANCING].val2; + if(sg) + skill_unit_move_unit_group(sg,target->m,dx,dy); + } +*/ + } + + if(sd) { /* 画面内に入ってきたので表示 */ + map_foreachinmovearea(clif_pcinsight,target->m,nx-AREA_SIZE,ny-AREA_SIZE,nx+AREA_SIZE,ny+AREA_SIZE,-dx,-dy,0,sd); + if(count&0x20000) + sd->walktimer = -1; + } + else if(md) { + map_foreachinmovearea(clif_mobinsight,target->m,nx-AREA_SIZE,ny-AREA_SIZE,nx+AREA_SIZE,ny+AREA_SIZE,-dx,-dy,BL_PC,md); + if(count&0x20000) + md->state.state = prev_state; + } + else if(pd) { + map_foreachinmovearea(clif_petinsight,target->m,nx-AREA_SIZE,ny-AREA_SIZE,nx+AREA_SIZE,ny+AREA_SIZE,-dx,-dy,BL_PC,pd); + if(count&0x20000) + pd->state.state = prev_state; + } + + skill_unit_move(target,gettick(),(count&0xffff)+7); /* スキルユニットの判定 */ + + return 0; +} + + +/* + * ========================================================================= + * スキル攻撃効果処理まとめ + * flagの説明。16進図 + * 00XRTTff + * ff = magicで計算に渡される) + * TT = パケットのtype部分(0でデフォルト) + * X = パケットのスキルLv + * R = 予約(skill_area_subで使用する) + *------------------------------------------------------------------------- + */ + +int skill_attack( int attack_type, struct block_list* src, struct block_list *dsrc, + struct block_list *bl,int skillid,int skilllv,unsigned int tick,int flag ) +{ + struct Damage dmg; + struct status_change *sc_data; + int type,lv,damage; + + rdamage = 0; + nullpo_retr(0, src); + nullpo_retr(0, dsrc); + nullpo_retr(0, bl); + + sc_data = battle_get_sc_data(bl); + +//何もしない判定ここから + if(dsrc->m != bl->m) //対象が同じマップにいなければ何もしない + return 0; + if(src->prev == NULL || dsrc->prev == NULL || bl->prev == NULL) //prevよくわからない※ + return 0; + if(src->type == BL_PC && pc_isdead((struct map_session_data *)src)) //術者?がPCですでに死んでいたら何もしない + return 0; + if(dsrc->type == BL_PC && pc_isdead((struct map_session_data *)dsrc)) //術者?がPCですでに死んでいたら何もしない + return 0; + if(bl->type == BL_PC && pc_isdead((struct map_session_data *)bl)) //対象がPCですでに死んでいたら何もしない + return 0; + if(skillnotok(skillid, (struct map_session_data *) bl)) + return 0; // [MouseJstr] + if(sc_data && sc_data[SC_HIDING].timer != -1) { //ハイディング状態で + if(skill_get_pl(skillid) != 2) //スキルの属性が地属性でなければ何もしない + return 0; + } + if(sc_data && sc_data[SC_TRICKDEAD].timer != -1) //死んだふり中は何もしない + return 0; + if(skillid == WZ_STORMGUST) { //使用スキルがストームガストで + if(sc_data && sc_data[SC_FREEZE].timer != -1) //凍結状態なら何もしない + return 0; + } + if(skillid == WZ_FROSTNOVA && dsrc->x == bl->x && dsrc->y == bl->y) //使用スキルがフロストノヴァで、dsrcとblが同じ場所なら何もしない + return 0; + if(src->type == BL_PC && ((struct map_session_data *)src)->chatID) //術者がPCでチャット中なら何もしない + return 0; + if(dsrc->type == BL_PC && ((struct map_session_data *)dsrc)->chatID) //術者がPCでチャット中なら何もしない + return 0; + if(src->type == BL_PC && bl && mob_gvmobcheck(((struct map_session_data *)src),bl)==0) + return 0; + +//何もしない判定ここまで + + type=-1; + lv=(flag>>20)&0xf; + dmg=battle_calc_attack(attack_type,src,bl,skillid,skilllv,flag&0xff ); //ダメージ計算 + +//マジックロッド処理ここから + if(attack_type&BF_MAGIC && sc_data && sc_data[SC_MAGICROD].timer != -1 && src == dsrc) { //魔法攻撃でマジックロッド状態でsrc=dsrcなら + dmg.damage = dmg.damage2 = 0; //ダメージ0 + if(bl->type == BL_PC) { //対象がPCの場合 + int sp = skill_get_sp(skillid,skilllv); //使用されたスキルのSPを吸収 + sp = sp * sc_data[SC_MAGICROD].val2 / 100; //吸収率計算 + if(skillid == WZ_WATERBALL && skilllv > 1) //ウォーターボールLv1以上 + sp = sp/((skilllv|1)*(skilllv|1)); //さらに計算? + if(sp > 0x7fff) sp = 0x7fff; //SP多すぎの場合は理論最大値 + else if(sp < 1) sp = 1; //1以下の場合は1 + if(((struct map_session_data *)bl)->status.sp + sp > ((struct map_session_data *)bl)->status.max_sp) { //回復SP+現在のSPがMSPより大きい場合 + sp = ((struct map_session_data *)bl)->status.max_sp - ((struct map_session_data *)bl)->status.sp; //SPをMSP-現在SPにする + ((struct map_session_data *)bl)->status.sp = ((struct map_session_data *)bl)->status.max_sp; //現在のSPにMSPを代入 + } + else //回復SP+現在のSPがMSPより小さい場合は回復SPを加算 + ((struct map_session_data *)bl)->status.sp += sp; + clif_heal(((struct map_session_data *)bl)->fd,SP_SP,sp); //SP回復エフェクトの表示 + ((struct map_session_data *)bl)->canact_tick = tick + skill_delayfix(bl, skill_get_delay(SA_MAGICROD,sc_data[SC_MAGICROD].val1)); // + } + clif_skill_nodamage(bl,bl,SA_MAGICROD,sc_data[SC_MAGICROD].val1,1); //マジックロッドエフェクトを表示 + } +//マジックロッド処理ここまで + + if(src->type==BL_PET) { // [Valaris] + dmg.damage=battle_attr_fix(skilllv, skill_get_pl(skillid), battle_get_element(bl) ); + dmg.damage2=0; + } + + damage = dmg.damage + dmg.damage2; + + if(lv==15) + lv=-1; + + if( flag&0xff00 ) + type=(flag&0xff00)>>8; + + if(damage <= 0 || damage < dmg.div_) //吹き飛ばし判定?※ + dmg.blewcount = 0; + + if(skillid == CR_GRANDCROSS) {//グランドクロス + if(battle_config.gx_disptype) dsrc = src; // 敵ダメージ白文字表示 + if( src == bl) type = 4; // 反動はダメージモーションなし + } + +//使用者がPCの場合の処理ここから + if(src->type == BL_PC) { + struct map_session_data *sd = (struct map_session_data *)src; + nullpo_retr(0, sd); +//連打掌(MO_CHAINCOMBO)ここから + if(skillid == MO_CHAINCOMBO) { + int delay = 1000 - 4 * battle_get_agi(src) - 2 * battle_get_dex(src); //基本ディレイの計算 + if(damage < battle_get_hp(bl)) { //ダメージが対象のHPより小さい場合 + if(pc_checkskill(sd, MO_COMBOFINISH) > 0 && sd->spiritball > 0) //猛龍拳(MO_COMBOFINISH)取得&気球保持時は+300ms + delay += 300 * battle_config.combo_delay_rate /100; //追加ディレイをconfにより調整 + + skill_status_change_start(src,SC_COMBO,MO_CHAINCOMBO,skilllv,0,0,delay,0); //コンボ状態に + } + sd->attackabletime = sd->canmove_tick = tick + delay; + clif_combo_delay(src,delay); //コンボディレイパケットの送信 + } +//連打掌(MO_CHAINCOMBO)ここまで +//猛龍拳(MO_COMBOFINISH)ここから + else if(skillid == MO_COMBOFINISH) { + int delay = 700 - 4 * battle_get_agi(src) - 2 * battle_get_dex(src); + if(damage < battle_get_hp(bl)) { + //阿修羅覇凰拳(MO_EXTREMITYFIST)取得&気球4個保持&爆裂波動(MO_EXPLOSIONSPIRITS)状態時は+300ms + //伏虎拳(CH_TIGERFIST)取得時も+300ms + if((pc_checkskill(sd, MO_EXTREMITYFIST) > 0 && sd->spiritball >= 4 && sd->sc_data[SC_EXPLOSIONSPIRITS].timer != -1) || + (pc_checkskill(sd, CH_TIGERFIST) > 0 && sd->spiritball > 0) || + (pc_checkskill(sd, CH_CHAINCRUSH) > 0 && sd->spiritball > 1)) + delay += 300 * battle_config.combo_delay_rate /100; //追加ディレイをconfにより調整 + + skill_status_change_start(src,SC_COMBO,MO_COMBOFINISH,skilllv,0,0,delay,0); //コンボ状態に + } + sd->attackabletime = sd->canmove_tick = tick + delay; + clif_combo_delay(src,delay); //コンボディレイパケットの送信 + } +//猛龍拳(MO_COMBOFINISH)ここまで +//伏虎拳(CH_TIGERFIST)ここから + else if(skillid == CH_TIGERFIST) { + int delay = 1000 - 4 * battle_get_agi(src) - 2 * battle_get_dex(src); + if(damage < battle_get_hp(bl)) { + if(pc_checkskill(sd, CH_CHAINCRUSH) > 0) //連柱崩撃(CH_CHAINCRUSH)取得時は+300ms + delay += 300 * battle_config.combo_delay_rate /100; //追加ディレイをconfにより調整 + + skill_status_change_start(src,SC_COMBO,CH_TIGERFIST,skilllv,0,0,delay,0); //コンボ状態に + } + sd->attackabletime = sd->canmove_tick = tick + delay; + clif_combo_delay(src,delay); //コンボディレイパケットの送信 + } +//伏虎拳(CH_TIGERFIST)ここまで +//連柱崩撃(CH_CHAINCRUSH)ここから + else if(skillid == CH_CHAINCRUSH) { + int delay = 1000 - 4 * battle_get_agi(src) - 2 * battle_get_dex(src); + if(damage < battle_get_hp(bl)) { + //阿修羅覇凰拳(MO_EXTREMITYFIST)取得&気球4個保持&爆裂波動(MO_EXPLOSIONSPIRITS)状態時は+300ms + if(pc_checkskill(sd, MO_EXTREMITYFIST) > 0 && sd->spiritball >= 4 && sd->sc_data[SC_EXPLOSIONSPIRITS].timer != -1) + delay += 300 * battle_config.combo_delay_rate /100; //追加ディレイをconfにより調整 + + skill_status_change_start(src,SC_COMBO,CH_CHAINCRUSH,skilllv,0,0,delay,0); //コンボ状態に + } + sd->attackabletime = sd->canmove_tick = tick + delay; + clif_combo_delay(src,delay); //コンボディレイパケットの送信 + } +//連柱崩撃(CH_CHAINCRUSH)ここまで + } +//使用者がPCの場合の処理ここまで +//武器スキル?ここから + //AppleGirl Was Here + if(attack_type&BF_MAGIC && damage > 0 && src != bl && src == dsrc) { //Blah Blah + if(bl->type == BL_PC) { //Blah Blah + struct map_session_data *tsd = (struct map_session_data *)bl; + if(tsd->magic_damage_return > 0) { //More Blah + rdamage += damage * tsd->magic_damage_return / 100; + if(rdamage < 1) rdamage = 1; + } + } + } + //Stop Here + if(attack_type&BF_WEAPON && damage > 0 && src != bl && src == dsrc) { //武器スキル&ダメージあり&使用者と対象者が違う&src=dsrc + if(dmg.flag&BF_SHORT) { //近距離攻撃時?※ + if(bl->type == BL_PC) { //対象がPCの時 + struct map_session_data *tsd = (struct map_session_data *)bl; + nullpo_retr(0, tsd); + if(tsd->short_weapon_damage_return > 0) { //近距離攻撃跳ね返し?※ + rdamage += damage * tsd->short_weapon_damage_return / 100; + if(rdamage < 1) rdamage = 1; + } + } + if(sc_data && sc_data[SC_REFLECTSHIELD].timer != -1) { //リフレクトシールド時 + rdamage += damage * sc_data[SC_REFLECTSHIELD].val2 / 100; //跳ね返し計算 + if(rdamage < 1) rdamage = 1; + } + } + else if(dmg.flag&BF_LONG) { //遠距離攻撃時?※ + if(bl->type == BL_PC) { //対象がPCの時 + struct map_session_data *tsd = (struct map_session_data *)bl; + nullpo_retr(0, tsd); + if(tsd->long_weapon_damage_return > 0) { //遠距離攻撃跳ね返し?※ + rdamage += damage * tsd->long_weapon_damage_return / 100; + if(rdamage < 1) rdamage = 1; + } + } + } + if(rdamage > 0) + clif_damage(src,src,tick, dmg.amotion,0,rdamage,1,4,0); + } +//武器スキル?ここまで + + switch(skillid){ + case WZ_SIGHTRASHER: + clif_skill_damage(src,bl,tick,dmg.amotion,dmg.dmotion, damage, dmg.div_, skillid, (lv!=0)?lv:skilllv, 5); + break; + case AS_SPLASHER: + clif_skill_damage(dsrc,bl,tick,dmg.amotion,dmg.dmotion, damage, dmg.div_, skillid, -1, 5); + break; + case NPC_SELFDESTRUCTION: + case NPC_SELFDESTRUCTION2: + break; + default: + clif_skill_damage(dsrc,bl,tick,dmg.amotion,dmg.dmotion, damage, dmg.div_, skillid, (lv!=0)?lv:skilllv, (skillid==0)? 5:type ); + } + if(dmg.blewcount > 0 && !map[src->m].flag.gvg) { /* 吹き飛ばし処理とそのパケット */ + if(skillid == WZ_SIGHTRASHER) + skill_blown(src,bl,dmg.blewcount); + else + skill_blown(dsrc,bl,dmg.blewcount); + if(bl->type == BL_MOB) + clif_fixmobpos((struct mob_data *)bl); + else if(bl->type == BL_PET) + clif_fixpetpos((struct pet_data *)bl); + else + clif_fixpos(bl); + } + + map_freeblock_lock(); + /* 実際にダメージ処理を行う */ + if(skillid != KN_BOWLINGBASH || flag) + battle_damage(src,bl,damage,0); + if(skillid == RG_INTIMIDATE && damage > 0 && !(battle_get_mode(bl)&0x20) && !map[src->m].flag.gvg ) { + int s_lv = battle_get_lv(src),t_lv = battle_get_lv(bl); + int rate = 50 + skilllv * 5; + rate = rate + (s_lv - t_lv); + if(rand()%100 < rate) + skill_addtimerskill(src,tick + 800,bl->id,0,0,skillid,skilllv,0,flag); + } + if(damage > 0 && dmg.flag&BF_SKILL && bl->type==BL_PC && pc_checkskill((struct map_session_data *)bl,RG_PLAGIARISM)){ + struct map_session_data *tsd = (struct map_session_data *)bl; + nullpo_retr(0, tsd); + if(!tsd->status.skill[skillid].id && !tsd->status.skill[skillid].id + && !(skillid > NPC_PIERCINGATT && skillid < NPC_SUMMONMONSTER) ){ + //既に盗んでいるスキルがあれば該当スキルを消す + if (tsd->cloneskill_id && tsd->cloneskill_lv && tsd->status.skill[tsd->cloneskill_id].flag==13){ + tsd->status.skill[tsd->cloneskill_id].id=0; + tsd->status.skill[tsd->cloneskill_id].lv=0; + tsd->status.skill[tsd->cloneskill_id].flag=0; + } + tsd->cloneskill_id=skillid; + tsd->cloneskill_lv=skilllv; + tsd->status.skill[skillid].id=skillid; + tsd->status.skill[skillid].lv=(pc_checkskill(tsd,RG_PLAGIARISM) > skill_get_max(skillid))? + skill_get_max(skillid):pc_checkskill(tsd,RG_PLAGIARISM); + tsd->status.skill[skillid].flag=13;//cloneskill flag + clif_skillinfoblock(tsd); + } + } + /* ダメージがあるなら追加効果判定 */ + if(bl->prev != NULL){ + struct map_session_data *sd = (struct map_session_data *)bl; + nullpo_retr(0, sd); + if( bl->type != BL_PC || (sd && !pc_isdead(sd)) ) { + if(damage > 0) + skill_additional_effect(src,bl,skillid,skilllv,attack_type,tick); + if(bl->type==BL_MOB && src!=bl) /* スキル使用条件のMOBスキル */ + { + struct mob_data *md=(struct mob_data *)bl; + nullpo_retr(0, md); + if(battle_config.mob_changetarget_byskill == 1) + { + int target; + target=md->target_id; + if(src->type == BL_PC) + md->target_id=src->id; + mobskill_use(md,tick,MSC_SKILLUSED|(skillid<<16)); + md->target_id=target; + } + else + mobskill_use(md,tick,MSC_SKILLUSED|(skillid<<16)); + } + } + } + + if(src->type == BL_PC && dmg.flag&BF_WEAPON && src != bl && src == dsrc && damage > 0) { + struct map_session_data *sd = (struct map_session_data *)src; + int hp = 0,sp = 0; + nullpo_retr(0, sd); + if(sd->hp_drain_rate && sd->hp_drain_per > 0 && dmg.damage > 0 && rand()%100 < sd->hp_drain_rate) { + hp += (dmg.damage * sd->hp_drain_per)/100; + if(sd->hp_drain_rate > 0 && hp < 1) hp = 1; + else if(sd->hp_drain_rate < 0 && hp > -1) hp = -1; + } + if(sd->hp_drain_rate_ && sd->hp_drain_per_ > 0 && dmg.damage2 > 0 && rand()%100 < sd->hp_drain_rate_) { + hp += (dmg.damage2 * sd->hp_drain_per_)/100; + if(sd->hp_drain_rate_ > 0 && hp < 1) hp = 1; + else if(sd->hp_drain_rate_ < 0 && hp > -1) hp = -1; + } + if(sd->sp_drain_rate > 0 && sd->sp_drain_per > 0 && dmg.damage > 0 && rand()%100 < sd->sp_drain_rate) { + sp += (dmg.damage * sd->sp_drain_per)/100; + if(sd->sp_drain_rate > 0 && sp < 1) sp = 1; + else if(sd->sp_drain_rate < 0 && sp > -1) sp = -1; + } + if(sd->sp_drain_rate_ > 0 && sd->sp_drain_per_ > 0 && dmg.damage2 > 0 && rand()%100 < sd->sp_drain_rate_) { + sp += (dmg.damage2 * sd->sp_drain_per_)/100; + if(sd->sp_drain_rate_ > 0 && sp < 1) sp = 1; + else if(sd->sp_drain_rate_ < 0 && sp > -1) sp = -1; + } + if(hp || sp) pc_heal(sd,hp,sp); + } + + if((skillid != KN_BOWLINGBASH || flag) && rdamage > 0) + battle_damage(bl,src,rdamage,0); + + if(attack_type&BF_WEAPON && sc_data && sc_data[SC_AUTOCOUNTER].timer != -1 && sc_data[SC_AUTOCOUNTER].val4 > 0) { + if(sc_data[SC_AUTOCOUNTER].val3 == dsrc->id) + battle_weapon_attack(bl,dsrc,tick,0x8000|sc_data[SC_AUTOCOUNTER].val1); + skill_status_change_end(bl,SC_AUTOCOUNTER,-1); + } + + map_freeblock_unlock(); + + return (dmg.damage+dmg.damage2); /* 与ダメを返す */ +} + +/*========================================== + * スキル範囲攻撃用(map_foreachinareaから呼ばれる) + * flagについて:16進図を確認 + * MSB <- 00fTffff ->LSB + * T =ターゲット選択用(BCT_*) + * ffff=自由に使用可能 + * 0 =予約。0に固定 + *------------------------------------------ + */ +static int skill_area_temp[8]; /* 一時変数。必要なら使う。 */ +typedef int (*SkillFunc)(struct block_list *,struct block_list *,int,int,unsigned int,int); +int skill_area_sub( struct block_list *bl,va_list ap ) +{ + struct block_list *src; + int skill_id,skill_lv,flag; + unsigned int tick; + SkillFunc func; + + nullpo_retr(0, bl); + nullpo_retr(0, ap); + + if(bl->type!=BL_PC && bl->type!=BL_MOB && bl->type!=BL_SKILL) + return 0; + + src=va_arg(ap,struct block_list *); //ここではsrcの値を参照していないのでNULLチェックはしない + skill_id=va_arg(ap,int); + skill_lv=va_arg(ap,int); + tick=va_arg(ap,unsigned int); + flag=va_arg(ap,int); + func=va_arg(ap,SkillFunc); + + if(battle_check_target(src,bl,flag) > 0) + func(src,bl,skill_id,skill_lv,tick,flag); + return 0; +} + +static int skill_check_unit_range_sub( struct block_list *bl,va_list ap ) +{ + struct skill_unit *unit; + int *c,x,y,range,sx[4],sy[4]; + int t_range,tx[4],ty[4]; + int i,r_flag,skillid; + + nullpo_retr(0, bl); + nullpo_retr(0, ap); + nullpo_retr(0, unit = (struct skill_unit *)bl); + nullpo_retr(0, c = va_arg(ap,int *)); + + if(bl->prev == NULL || bl->type != BL_SKILL) + return 0; + + if(!unit->alive) + return 0; + + x = va_arg(ap,int); + y = va_arg(ap,int); + range = va_arg(ap,int); + skillid = va_arg(ap,int); + + if(skillid == MG_SAFETYWALL || skillid == AL_PNEUMA) { + if(unit->group->unit_id != 0x7e && unit->group->unit_id != 0x85) + return 0; + } + else if(skillid == AL_WARP) { + if((unit->group->unit_id < 0x8f || unit->group->unit_id > 0x99) && unit->group->unit_id != 0x92) + return 0; + } + else if((skillid >= HT_SKIDTRAP && skillid <= HT_CLAYMORETRAP) || skillid == HT_TALKIEBOX) { + if((unit->group->unit_id < 0x8f || unit->group->unit_id > 0x99) && unit->group->unit_id != 0x92) + return 0; + } + else if(skillid == WZ_FIREPILLAR) { + if(unit->group->unit_id != 0x87) + return 0; + } + else return 0; + t_range=(unit->range!=0)? unit->range:unit->group->range; + tx[0] = tx[3] = unit->bl.x - t_range; + tx[1] = tx[2] = unit->bl.x + t_range; + ty[0] = ty[1] = unit->bl.y - t_range; + ty[2] = ty[3] = unit->bl.y + t_range; + sx[0] = sx[3] = x - range; + sx[1] = sx[2] = x + range; + sy[0] = sy[1] = y - range; + sy[2] = sy[3] = y + range; + for(i=r_flag=0;i<4;i++) { + if(sx[i] >= tx[0] && sx[i] <= tx[1] && sy[i] >= ty[0] && sy[i] <= ty[2]) { + r_flag = 1; + break; + } + if(tx[i] >= sx[0] && tx[i] <= sx[1] && ty[i] >= sy[0] && ty[i] <= sy[2]) { + r_flag = 1; + break; + } + } + if(r_flag) (*c)++; + + return 0; +} + +int skill_check_unit_range(int m,int x,int y,int range,int skillid) +{ + int c = 0; + + map_foreachinarea(skill_check_unit_range_sub,m,x-10,y-10,x+10,y+10,BL_SKILL,&c,x,y,range,skillid); + + return c; +} + +static int skill_check_unit_range2_sub( struct block_list *bl,va_list ap ) +{ + int *c; + + nullpo_retr(0, bl); + nullpo_retr(0, ap); + nullpo_retr(0, c = va_arg(ap,int *)); + + if(bl->prev == NULL || (bl->type != BL_PC && bl->type != BL_MOB)) + return 0; + + if(bl->type == BL_PC && pc_isdead((struct map_session_data *)bl)) + return 0; + + (*c)++; + + return 0; +} + +int skill_check_unit_range2(int m,int x,int y,int range) +{ + int c = 0; + + map_foreachinarea(skill_check_unit_range2_sub,m,x-range,y-range,x+range,y+range,0,&c); + + return c; +} + +/*========================================================================= + * 範囲スキル使用処理小分けここから + */ +/* 対象の数をカウントする。(skill_area_temp[0]を初期化しておくこと) */ +int skill_area_sub_count(struct block_list *src,struct block_list *target,int skillid,int skilllv,unsigned int tick,int flag) +{ + if(skill_area_temp[0] < 0xffff) + skill_area_temp[0]++; + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +static int skill_timerskill(int tid, unsigned int tick, int id,int data ) +{ + struct map_session_data *sd = NULL; + struct mob_data *md = NULL; + struct pet_data *pd = NULL; + struct block_list *src = map_id2bl(id),*target; + struct skill_timerskill *skl = NULL; + int range; + + nullpo_retr(0, src); + + if(src->prev == NULL) + return 0; + + if(src->type == BL_PC) { + nullpo_retr(0, sd = (struct map_session_data *)src); + skl = &sd->skilltimerskill[data]; + } + else if(src->type == BL_MOB) { + nullpo_retr(0, md = (struct mob_data *)src); + skl = &md->skilltimerskill[data]; + } + else if(src->type == BL_PET) { // [Valaris] + nullpo_retr(0, pd = (struct pet_data *)src); + skl = &pd->skilltimerskill[data]; + } + + else + return 0; + + nullpo_retr(0, skl); + + skl->timer = -1; + if(skl->target_id) { + struct block_list tbl; + target = map_id2bl(skl->target_id); + if(skl->skill_id == RG_INTIMIDATE) { + if(target == NULL) { + target = &tbl; //初期化してないのにアドレス突っ込んでいいのかな? + target->type = BL_NUL; + target->m = src->m; + target->prev = target->next = NULL; + } + } + if(target == NULL) + return 0; + if(target->prev == NULL && skl->skill_id != RG_INTIMIDATE) + return 0; + if(src->m != target->m) + return 0; + if(sd && pc_isdead(sd)) + return 0; + if(target->type == BL_PC && pc_isdead((struct map_session_data *)target) && skl->skill_id != RG_INTIMIDATE) + return 0; + + switch(skl->skill_id) { + case TF_BACKSLIDING: + clif_skill_nodamage(src,src,skl->skill_id,skl->skill_lv,1); + break; + case RG_INTIMIDATE: + if(sd && !map[src->m].flag.noteleport) { + int x,y,i,j,c; + pc_randomwarp(sd,3); + for(i=0;i<16;i++) { + j = rand()%8; + x = sd->bl.x + dirx[j]; + y = sd->bl.y + diry[j]; + if((c=map_getcell(sd->bl.m,x,y)) != 1 && c != 5) + break; + } + if(i >= 16) { + x = sd->bl.x; + y = sd->bl.y; + } + if(target->prev != NULL) { + if(target->type == BL_PC && !pc_isdead((struct map_session_data *)target)) + pc_setpos((struct map_session_data *)target,map[sd->bl.m].name,x,y,3); + else if(target->type == BL_MOB) + mob_warp((struct mob_data *)target,-1,x,y,3); + } + } + else if(md && !map[src->m].flag.monster_noteleport) { + int x,y,i,j,c; + mob_warp(md,-1,-1,-1,3); + for(i=0;i<16;i++) { + j = rand()%8; + x = md->bl.x + dirx[j]; + y = md->bl.y + diry[j]; + if((c=map_getcell(md->bl.m,x,y)) != 1 && c != 5) + break; + } + if(i >= 16) { + x = md->bl.x; + y = md->bl.y; + } + if(target->prev != NULL) { + if(target->type == BL_PC && !pc_isdead((struct map_session_data *)target)) + pc_setpos((struct map_session_data *)target,map[md->bl.m].name,x,y,3); + else if(target->type == BL_MOB) + mob_warp((struct mob_data *)target,-1,x,y,3); + } + } + break; + + case BA_FROSTJOKE: /* 寒いジョーク */ + case DC_SCREAM: /* スクリーム */ + range=15; //視界全体 + map_foreachinarea(skill_frostjoke_scream,src->m,src->x-range,src->y-range, + src->x+range,src->y+range,0,src,skl->skill_id,skl->skill_lv,tick); + break; + + default: + skill_attack(skl->type,src,src,target,skl->skill_id,skl->skill_lv,tick,skl->flag); + break; + } + } + else { + if(src->m != skl->map) + return 0; + switch(skl->skill_id) { + case WZ_METEOR: + if(skl->type >= 0) { + skill_unitsetting(src,skl->skill_id,skl->skill_lv,skl->type>>16,skl->type&0xFFFF,0); + clif_skill_poseffect(src,skl->skill_id,skl->skill_lv,skl->x,skl->y,tick); + } + else + skill_unitsetting(src,skl->skill_id,skl->skill_lv,skl->x,skl->y,0); + break; + } + } + + return 0; +} + +/*========================================== + * + *------------------------------------------ + */ +int skill_addtimerskill(struct block_list *src,unsigned int tick,int target,int x,int y,int skill_id,int skill_lv,int type,int flag) +{ + int i; + + nullpo_retr(1, src); + + if(src->type == BL_PC) { + struct map_session_data *sd = (struct map_session_data *)src; + nullpo_retr(1, sd); + for(i=0;i<MAX_SKILLTIMERSKILL;i++) { + if(sd->skilltimerskill[i].timer == -1) { + sd->skilltimerskill[i].timer = add_timer(tick, skill_timerskill, src->id, i); + sd->skilltimerskill[i].src_id = src->id; + sd->skilltimerskill[i].target_id = target; + sd->skilltimerskill[i].skill_id = skill_id; + sd->skilltimerskill[i].skill_lv = skill_lv; + sd->skilltimerskill[i].map = src->m; + sd->skilltimerskill[i].x = x; + sd->skilltimerskill[i].y = y; + sd->skilltimerskill[i].type = type; + sd->skilltimerskill[i].flag = flag; + + return 0; + } + } + return 1; + } + else if(src->type == BL_MOB) { + struct mob_data *md = (struct mob_data *)src; + nullpo_retr(1, md); + for(i=0;i<MAX_MOBSKILLTIMERSKILL;i++) { + if(md->skilltimerskill[i].timer == -1) { + md->skilltimerskill[i].timer = add_timer(tick, skill_timerskill, src->id, i); + md->skilltimerskill[i].src_id = src->id; + md->skilltimerskill[i].target_id = target; + md->skilltimerskill[i].skill_id = skill_id; + md->skilltimerskill[i].skill_lv = skill_lv; + md->skilltimerskill[i].map = src->m; + md->skilltimerskill[i].x = x; + md->skilltimerskill[i].y = y; + md->skilltimerskill[i].type = type; + md->skilltimerskill[i].flag = flag; + + return 0; + } + } + return 1; + } + else if(src->type == BL_PET) { // [Valaris] + struct pet_data *pd = (struct pet_data *)src; + nullpo_retr(1, pd); + for(i=0;i<MAX_MOBSKILLTIMERSKILL;i++) { + if(pd->skilltimerskill[i].timer == -1) { + pd->skilltimerskill[i].timer = add_timer(tick, skill_timerskill, src->id, i); + pd->skilltimerskill[i].src_id = src->id; + pd->skilltimerskill[i].target_id = target; + pd->skilltimerskill[i].skill_id = skill_id; + pd->skilltimerskill[i].skill_lv = skill_lv; + pd->skilltimerskill[i].map = src->m; + pd->skilltimerskill[i].x = x; + pd->skilltimerskill[i].y = y; + pd->skilltimerskill[i].type = type; + pd->skilltimerskill[i].flag = flag; + + return 0; + } + } + return 1; + } + return 1; +} + +/*========================================== + * + *------------------------------------------ + */ +int skill_cleartimerskill(struct block_list *src) +{ + int i; + + nullpo_retr(0, src); + + if(src->type == BL_PC) { + struct map_session_data *sd = (struct map_session_data *)src; + nullpo_retr(0, sd); + for(i=0;i<MAX_SKILLTIMERSKILL;i++) { + if(sd->skilltimerskill[i].timer != -1) { + delete_timer(sd->skilltimerskill[i].timer, skill_timerskill); + sd->skilltimerskill[i].timer = -1; + } + } + } + else if(src->type == BL_MOB) { + struct mob_data *md = (struct mob_data *)src; + nullpo_retr(0, md); + for(i=0;i<MAX_MOBSKILLTIMERSKILL;i++) { + if(md->skilltimerskill[i].timer != -1) { + delete_timer(md->skilltimerskill[i].timer, skill_timerskill); + md->skilltimerskill[i].timer = -1; + } + } + } + + return 0; +} + +/* 範囲スキル使用処理小分けここまで + * ------------------------------------------------------------------------- + */ + +/*========================================== + * スキル使用(詠唱完了、ID指定攻撃系) + * (スパゲッティに向けて1歩前進!(ダメポ)) + *------------------------------------------ + */ +int skill_castend_damage_id( struct block_list* src, struct block_list *bl,int skillid,int skilllv,unsigned int tick,int flag ) +{ + struct map_session_data *sd=NULL; + int i; + + nullpo_retr(1, src); + nullpo_retr(1, bl); + + if(src->type==BL_PC) + sd=(struct map_session_data *)src; + if(sd && pc_isdead(sd)) + return 1; + + if((skillid == WZ_SIGHTRASHER || skillid == CR_GRANDCROSS) && src != bl) + bl = src; + if(bl->prev == NULL) + return 1; + if(bl->type == BL_PC && pc_isdead((struct map_session_data *)bl)) + return 1; + map_freeblock_lock(); + switch(skillid) + { + /* 武器攻撃系スキル */ + case SM_BASH: /* バッシュ */ + case MC_MAMMONITE: /* メマーナイト */ + case AC_DOUBLE: /* ダブルストレイフィング */ + case AS_SONICBLOW: /* ソニックブロー */ + case KN_PIERCE: /* ピアース */ + case KN_SPEARBOOMERANG: /* スピアブーメラン */ + case TF_POISON: /* インベナム */ + case TF_SPRINKLESAND: /* 砂まき */ + case AC_CHARGEARROW: /* チャージアロー */ + case KN_SPEARSTAB: /* スピアスタブ */ + case RG_RAID: /* サプライズアタック */ + case RG_INTIMIDATE: /* インティミデイト */ + case BA_MUSICALSTRIKE: /* ミュージカルストライク */ + case DC_THROWARROW: /* 矢撃ち */ + case BA_DISSONANCE: /* 不協和音 */ + case CR_HOLYCROSS: /* ホーリークロス */ + case CR_SHIELDCHARGE: + case CR_SHIELDBOOMERANG: + + /* 以下MOB専用 */ + /* 単体攻撃、SP減少攻撃、遠距離攻撃、防御無視攻撃、多段攻撃 */ + case NPC_PIERCINGATT: + case NPC_MENTALBREAKER: + case NPC_RANGEATTACK: + case NPC_CRITICALSLASH: + case NPC_COMBOATTACK: + /* 必中攻撃、毒攻撃、暗黒攻撃、沈黙攻撃、スタン攻撃 */ + case NPC_GUIDEDATTACK: + case NPC_POISON: + case NPC_BLINDATTACK: + case NPC_SILENCEATTACK: + case NPC_STUNATTACK: + /* 石化攻撃、呪い攻撃、睡眠攻撃、ランダムATK攻撃 */ + case NPC_PETRIFYATTACK: + case NPC_CURSEATTACK: + case NPC_SLEEPATTACK: + case NPC_RANDOMATTACK: + /* 水属性攻撃、地属性攻撃、火属性攻撃、風属性攻撃 */ + case NPC_WATERATTACK: + case NPC_GROUNDATTACK: + case NPC_FIREATTACK: + case NPC_WINDATTACK: + /* 毒属性攻撃、聖属性攻撃、闇属性攻撃、念属性攻撃、SP減少攻撃 */ + case NPC_POISONATTACK: + case NPC_HOLYATTACK: + case NPC_DARKNESSATTACK: + case NPC_TELEKINESISATTACK: + case LK_AURABLADE: /* オーラブレード */ + case LK_SPIRALPIERCE: /* スパイラルピアース */ + case LK_HEADCRUSH: /* ヘッドクラッシュ */ + case LK_JOINTBEAT: /* ジョイントビート */ + case PA_PRESSURE: /* プレッシャー */ + case PA_SACRIFICE: /* サクリファイス */ + case SN_SHARPSHOOTING: /* シャープシューティング */ + case CG_ARROWVULCAN: /* アローバルカン */ + case ASC_BREAKER: /* ソウルブレーカー */ + case HW_MAGICCRASHER: /* マジッククラッシャー */ + skill_attack(BF_WEAPON,src,src,bl,skillid,skilllv,tick,flag); + break; + case NPC_DARKBREATH: + clif_emotion(src,7); + skill_attack(BF_MISC,src,src,bl,skillid,skilllv,tick,flag); + break; + case MO_INVESTIGATE: /* 発勁 */ + { + struct status_change *sc_data = battle_get_sc_data(src); + skill_attack(BF_WEAPON,src,src,bl,skillid,skilllv,tick,flag); + if(sc_data[SC_BLADESTOP].timer != -1) + skill_status_change_end(src,SC_BLADESTOP,-1); + } + break; + case SN_FALCONASSAULT: /* ファルコンアサルト */ + skill_attack(BF_MISC,src,src,bl,skillid,skilllv,tick,flag); + break; + case KN_BRANDISHSPEAR: /* ブランディッシュスピア */ + { + struct mob_data *md = (struct mob_data *)bl; + nullpo_retr(1, md); + skill_attack(BF_WEAPON,src,src,bl,skillid,skilllv,tick,flag); + if(md->hp > 0){ + skill_blown(src,bl,skill_get_blewcount(skillid,skilllv)); + if(bl->type == BL_MOB) + clif_fixmobpos((struct mob_data *)bl); + else if(bl->type == BL_PET) + clif_fixpetpos((struct pet_data *)bl); + else + clif_fixpos(bl); + } + } + break; + case RG_BACKSTAP: /* バックスタブ */ + { + int dir = map_calc_dir(src,bl->x,bl->y),t_dir = battle_get_dir(bl); + int dist = distance(src->x,src->y,bl->x,bl->y); + if((dist > 0 && !map_check_dir(dir,t_dir)) || bl->type == BL_SKILL) { + struct status_change *sc_data = battle_get_sc_data(src); + if(sc_data && sc_data[SC_HIDING].timer != -1) + skill_status_change_end(src, SC_HIDING, -1); // ハイディング解除 + skill_attack(BF_WEAPON,src,src,bl,skillid,skilllv,tick,flag); + skill_blown(src,bl,skill_get_blewcount(skillid,skilllv)); + } + else if(src->type == BL_PC) + clif_skill_fail(sd,sd->skillid,0,0); + } + break; + + case AM_ACIDTERROR: /* アシッドテラー */ + skill_attack(BF_WEAPON,src,src,bl,skillid,skilllv,tick,flag); + if(bl->type == BL_PC && rand()%100 < skill_get_time(skillid,skilllv) && battle_config.equipment_breaking) + pc_breakarmor((struct map_session_data *)bl); + break; + case MO_FINGEROFFENSIVE: /* 指弾 */ + { + struct status_change *sc_data = battle_get_sc_data(src); + + if(!battle_config.finger_offensive_type) + skill_attack(BF_WEAPON,src,src,bl,skillid,skilllv,tick,flag); + else { + skill_attack(BF_WEAPON,src,src,bl,skillid,skilllv,tick,flag); + if(sd) { + for(i=1;i<sd->spiritball_old;i++) + skill_addtimerskill(src,tick+i*200,bl->id,0,0,skillid,skilllv,BF_WEAPON,flag); + sd->canmove_tick = tick + (sd->spiritball_old-1)*200; + } + } + if(sc_data && sc_data[SC_BLADESTOP].timer != -1) + skill_status_change_end(src,SC_BLADESTOP,-1); + } + break; + case MO_CHAINCOMBO: /* 連打掌 */ + { + struct status_change *sc_data = battle_get_sc_data(src); + skill_attack(BF_WEAPON,src,src,bl,skillid,skilllv,tick,flag); + if(sc_data && sc_data[SC_BLADESTOP].timer != -1) + skill_status_change_end(src,SC_BLADESTOP,-1); + } + break; + case MO_COMBOFINISH: /* 猛龍拳 */ + case CH_TIGERFIST: /* 伏虎拳 */ + case CH_CHAINCRUSH: /* 連柱崩撃 */ + case CH_PALMSTRIKE: /* 猛虎硬派山 */ + skill_attack(BF_WEAPON,src,src,bl,skillid,skilllv,tick,flag); + break; + case MO_EXTREMITYFIST: /* 阿修羅覇鳳拳 */ + { + struct status_change *sc_data = battle_get_sc_data(src); + + if(sd) { + struct walkpath_data wpd; + int dx,dy; + + dx = bl->x - sd->bl.x; + dy = bl->y - sd->bl.y; + if(dx > 0) dx++; + else if(dx < 0) dx--; + if(dy > 0) dy++; + else if(dy < 0) dy--; + if(dx == 0 && dy == 0) dx++; + if(path_search(&wpd,src->m,sd->bl.x,sd->bl.y,sd->bl.x+dx,sd->bl.y+dy,1) == -1) { + dx = bl->x - sd->bl.x; + dy = bl->y - sd->bl.y; + if(path_search(&wpd,src->m,sd->bl.x,sd->bl.y,sd->bl.x+dx,sd->bl.y+dy,1) == -1) { + clif_skill_fail(sd,sd->skillid,0,0); + break; + } + } + sd->to_x = sd->bl.x + dx; + sd->to_y = sd->bl.y + dy; + skill_attack(BF_WEAPON,src,src,bl,skillid,skilllv,tick,flag); + clif_walkok(sd); + clif_movechar(sd); + if(dx < 0) dx = -dx; + if(dy < 0) dy = -dy; + sd->attackabletime = sd->canmove_tick = tick + 100 + sd->speed * ((dx > dy)? dx:dy); + if(sd->canact_tick < sd->canmove_tick) + sd->canact_tick = sd->canmove_tick; + pc_movepos(sd,sd->to_x,sd->to_y); + skill_status_change_end(&sd->bl,SC_COMBO,-1); + } + else + skill_attack(BF_WEAPON,src,src,bl,skillid,skilllv,tick,flag); + skill_status_change_end(src, SC_EXPLOSIONSPIRITS, -1); + if(sc_data && sc_data[SC_BLADESTOP].timer != -1) + skill_status_change_end(src,SC_BLADESTOP,-1); + } + break; + /* 武器系範囲攻撃スキル */ + case AC_SHOWER: /* アローシャワー */ + case SM_MAGNUM: /* マグナムブレイク */ + case AS_GRIMTOOTH: /* グリムトゥース */ + case MC_CARTREVOLUTION: /* カートレヴォリューション */ + case NPC_SPLASHATTACK: /* スプラッシュアタック */ + case ASC_METEORASSAULT: /* メテオアサルト */ + case AS_SPLASHER: /* [Valaris] */ + if(flag&1){ + /* 個別にダメージを与える */ + if(bl->id!=skill_area_temp[1]){ + int dist=0; + if(skillid==SM_MAGNUM){ /* マグナムブレイクなら中心からの距離を計算 */ + int dx=abs( bl->x - skill_area_temp[2] ); + int dy=abs( bl->y - skill_area_temp[3] ); + dist=((dx>dy)?dx:dy); + } + skill_attack(BF_WEAPON,src,src,bl,skillid,skilllv,tick, + 0x0500|dist ); + } + }else{ + int ar=1; + int x=bl->x,y=bl->y; + if( skillid==SM_MAGNUM){ + x=src->x; + y=src->y; + }else if(skillid==AC_SHOWER || skillid==ASC_METEORASSAULT) /* アローシャワー、メテオアサルト範囲5*5 */ + ar=2; + else if(skillid==AS_SPLASHER) /* ベナムスプラッシャー範囲3*3 */ + ar=1; + else if(skillid==NPC_SPLASHATTACK) /* スプラッシュアタックは範囲7*7 */ + ar=3; + skill_area_temp[1]=bl->id; + skill_area_temp[2]=x; + skill_area_temp[3]=y; + /* まずターゲットに攻撃を加える */ + skill_attack(BF_WEAPON,src,src,bl,skillid,skilllv,tick,0); + /* その後ターゲット以外の範囲内の敵全体に処理を行う */ + map_foreachinarea(skill_area_sub, + bl->m,x-ar,y-ar,x+ar,y+ar,0, + src,skillid,skilllv,tick, flag|BCT_ENEMY|1, + skill_castend_damage_id); + } + break; + + case KN_BOWLINGBASH: /* ボウリングバッシュ */ + if(flag&1){ + /* 個別にダメージを与える */ + if(bl->id!=skill_area_temp[1]) + skill_attack(BF_WEAPON,src,src,bl,skillid,skilllv,tick,0x0500); + } + else { + int damage; + map_freeblock_lock(); + damage = skill_attack(BF_WEAPON,src,src,bl,skillid,skilllv,tick,0); + if(damage > 0) { + int i,c; /* 他人から聞いた動きなので間違ってる可能性大&効率が悪いっす>< */ + c = skill_get_blewcount(skillid,skilllv); + if(map[bl->m].flag.gvg) c = 0; + for(i=0;i<c;i++){ + skill_blown(src,bl,1); + if(bl->type == BL_MOB) + clif_fixmobpos((struct mob_data *)bl); + else if(bl->type == BL_PET) + clif_fixpetpos((struct pet_data *)bl); + else + clif_fixpos(bl); + skill_area_temp[0]=0; + map_foreachinarea(skill_area_sub, + bl->m,bl->x-1,bl->y-1,bl->x+1,bl->y+1,0, + src,skillid,skilllv,tick, flag|BCT_ENEMY , + skill_area_sub_count); + if(skill_area_temp[0]>1) break; + } + skill_area_temp[1]=bl->id; + skill_area_temp[2]=bl->x; + skill_area_temp[3]=bl->y; + /* その後ターゲット以外の範囲内の敵全体に処理を行う */ + map_foreachinarea(skill_area_sub, + bl->m,bl->x-1,bl->y-1,bl->x+1,bl->y+1,0, + src,skillid,skilllv,tick, flag|BCT_ENEMY|1, + skill_castend_damage_id); + battle_damage(src,bl,damage,1); + if(rdamage > 0) + battle_damage(bl,src,rdamage,0); + } + map_freeblock_unlock(); + } + break; + + case ALL_RESURRECTION: /* リザレクション */ + case PR_TURNUNDEAD: /* ターンアンデッド */ + if(bl->type != BL_PC && battle_check_undead(battle_get_race(bl),battle_get_elem_type(bl))) + skill_attack(BF_MAGIC,src,src,bl,skillid,skilllv,tick,flag); + else { + map_freeblock_unlock(); + return 1; + } + break; + + /* 魔法系スキル */ + case MG_SOULSTRIKE: /* ソウルストライク */ + case MG_COLDBOLT: /* コールドボルト */ + case MG_FIREBOLT: /* ファイアーボルト */ + case MG_LIGHTNINGBOLT: /* ライトニングボルト */ + case WZ_EARTHSPIKE: /* アーススパイク */ + case AL_HEAL: /* ヒール */ + case AL_HOLYLIGHT: /* ホーリーライト */ + case MG_FROSTDIVER: /* フロストダイバー */ + case WZ_JUPITEL: /* ユピテルサンダー */ + case NPC_MAGICALATTACK: /* MOB:魔法打撃攻撃 */ + case PR_ASPERSIO: /* アスペルシオ */ + skill_attack(BF_MAGIC,src,src,bl,skillid,skilllv,tick,flag); + break; + + case WZ_WATERBALL: /* ウォーターボール */ + skill_attack(BF_MAGIC,src,src,bl,skillid,skilllv,tick,flag); + if(skilllv>1) + skill_status_change_start(src,SC_WATERBALL,skilllv,bl->id,0,0,0,0); + break; + + case PR_BENEDICTIO: /* 聖体降福 */ + if(battle_get_race(bl)==1 || battle_get_race(bl)==6) + skill_attack(BF_MAGIC,src,src,bl,skillid,skilllv,tick,flag); + break; + + /* 魔法系範囲攻撃スキル */ + case MG_NAPALMBEAT: /* ナパームビート */ + case MG_FIREBALL: /* ファイヤーボール */ + if(flag&1){ + /* 個別にダメージを与える */ + if(bl->id!=skill_area_temp[1]){ + if(skillid==MG_FIREBALL){ /* ファイヤーボールなら中心からの距離を計算 */ + int dx=abs( bl->x - skill_area_temp[2] ); + int dy=abs( bl->y - skill_area_temp[3] ); + skill_area_temp[0]=((dx>dy)?dx:dy); + } + skill_attack(BF_MAGIC,src,src,bl,skillid,skilllv,tick, + skill_area_temp[0]| 0x0500); + } + }else{ + int ar=(skillid==MG_NAPALMBEAT)?1:2; + skill_area_temp[1]=bl->id; + if(skillid==MG_NAPALMBEAT){ /* ナパームでは先に数える */ + skill_area_temp[0]=0; + map_foreachinarea(skill_area_sub, + bl->m,bl->x-1,bl->y-1,bl->x+1,bl->y+1,0, + src,skillid,skilllv,tick, flag|BCT_ENEMY , + skill_area_sub_count); + }else{ + skill_area_temp[0]=0; + skill_area_temp[2]=bl->x; + skill_area_temp[3]=bl->y; + } + /* まずターゲットに攻撃を加える */ + skill_attack(BF_MAGIC,src,src,bl,skillid,skilllv,tick, + skill_area_temp[0] ); + /* その後ターゲット以外の範囲内の敵全体に処理を行う */ + map_foreachinarea(skill_area_sub, + bl->m,bl->x-ar,bl->y-ar,bl->x+ar,bl->y+ar,0, + src,skillid,skilllv,tick, flag|BCT_ENEMY|1, + skill_castend_damage_id); + } + break; + + case HW_NAPALMVULCAN: // Fixed By SteelViruZ + if(flag&1){ + if(bl->id!=skill_area_temp[1]){ + skill_attack(BF_MAGIC,src,src,bl,skillid,skilllv,tick, + skill_area_temp[0]); + } + }else{ + int ar=(skillid==HW_NAPALMVULCAN)?1:2; + skill_area_temp[1]=bl->id; + if(skillid==HW_NAPALMVULCAN){ + skill_area_temp[0]=0; + map_foreachinarea(skill_area_sub, + bl->m,bl->x-1,bl->y-1,bl->x+1,bl->y+1,0, + src,skillid,skilllv,tick, flag|BCT_ENEMY , + skill_area_sub_count); + }else{ + skill_area_temp[0]=0; + skill_area_temp[2]=bl->x; + skill_area_temp[3]=bl->y; + } + skill_attack(BF_MAGIC,src,src,bl,skillid,skilllv,tick, + skill_area_temp[0] ); + map_foreachinarea(skill_area_sub, + bl->m,bl->x-ar,bl->y-ar,bl->x+ar,bl->y+ar,0, + src,skillid,skilllv,tick, flag|BCT_ENEMY|1, + skill_castend_damage_id); + } + break; + + case WZ_FROSTNOVA: /* フロストノヴァ */ + skill_castend_pos2(src,bl->x,bl->y,skillid,skilllv,tick,0); + skill_attack(BF_MAGIC,src,src,bl,skillid,skilllv,tick,flag); + break; + + case WZ_SIGHTRASHER: + clif_skill_nodamage(src,bl,skillid,skilllv,1); + skill_castend_pos2(src,bl->x,bl->y,skillid,skilllv,tick,0); + skill_status_change_end(src,SC_SIGHT,-1); + break; + + /* その他 */ + case HT_BLITZBEAT: /* ブリッツビート */ + if(flag&1){ + /* 個別にダメージを与える */ + if(bl->id!=skill_area_temp[1]) + skill_attack(BF_MISC,src,src,bl,skillid,skilllv,tick,skill_area_temp[0]|(flag&0xf00000)); + }else{ + skill_area_temp[0]=0; + skill_area_temp[1]=bl->id; + if(flag&0xf00000) + map_foreachinarea(skill_area_sub,bl->m,bl->x-1,bl->y-1,bl->x+1,bl->y+1,0, + src,skillid,skilllv,tick, flag|BCT_ENEMY ,skill_area_sub_count); + /* まずターゲットに攻撃を加える */ + skill_attack(BF_MISC,src,src,bl,skillid,skilllv,tick,skill_area_temp[0]|(flag&0xf00000)); + /* その後ターゲット以外の範囲内の敵全体に処理を行う */ + map_foreachinarea(skill_area_sub, + bl->m,bl->x-1,bl->y-1,bl->x+1,bl->y+1,0, + src,skillid,skilllv,tick, flag|BCT_ENEMY|1, + skill_castend_damage_id); + } + break; + + case CR_GRANDCROSS: /* グランドクロス */ + /* スキルユニット配置 */ + skill_castend_pos2(src,bl->x,bl->y,skillid,skilllv,tick,0); + if(sd) + sd->canmove_tick = tick + 1000; + else if(src->type == BL_MOB) + mob_changestate((struct mob_data *)src,MS_DELAY,1000); + break; + + case TF_THROWSTONE: /* 石投げ */ + case NPC_SMOKING: /* スモーキング */ + skill_attack(BF_MISC,src,src,bl,skillid,skilllv,tick,0 ); + break; + + case NPC_SELFDESTRUCTION: /* 自爆 */ + case NPC_SELFDESTRUCTION2: /* 自爆2 */ + if(flag&1){ + /* 個別にダメージを与える */ + if(src->type==BL_MOB){ + struct mob_data* mb = (struct mob_data*)src; + nullpo_retr(1, mb); + mb->hp=skill_area_temp[2]; + if(bl->id!=skill_area_temp[1]) + skill_attack(BF_MISC,src,src,bl,NPC_SELFDESTRUCTION,skilllv,tick,flag ); + mb->hp=1; + } + }else{ + struct mob_data *md; + if((md=(struct mob_data *)src)){ + skill_area_temp[1]=bl->id; + skill_area_temp[2]=battle_get_hp(src); + clif_skill_nodamage(src,src,NPC_SELFDESTRUCTION,-1,1); + map_foreachinarea(skill_area_sub, + bl->m,bl->x-5,bl->y-5,bl->x+5,bl->y+5,0, + src,skillid,skilllv,tick, flag|BCT_ENEMY|1, + skill_castend_damage_id); + battle_damage(src,src,md->hp,0); + } + } + break; + + /* HP吸収/HP吸収魔法 */ + case NPC_BLOODDRAIN: + case NPC_ENERGYDRAIN: + { + int heal; + heal = skill_attack((skillid==NPC_BLOODDRAIN)?BF_WEAPON:BF_MAGIC,src,src,bl,skillid,skilllv,tick,flag); + if( heal > 0 ){ + struct block_list tbl; + tbl.id = 0; + tbl.m = src->m; + tbl.x = src->x; + tbl.y = src->y; + clif_skill_nodamage(&tbl,src,AL_HEAL,heal,1); + battle_heal(NULL,src,heal,0,0); + } + } + break; + case 0: + if(sd) { + if(flag&3){ + if(bl->id!=skill_area_temp[1]) + skill_attack(BF_WEAPON,src,src,bl,skillid,skilllv,tick,0x0500); + } + else{ + int ar=sd->splash_range; + skill_area_temp[1]=bl->id; + map_foreachinarea(skill_area_sub, + bl->m, bl->x - ar, bl->y - ar, bl->x + ar, bl->y + ar, 0, + src, skillid, skilllv, tick, flag | BCT_ENEMY | 1, + skill_castend_damage_id); + } + } + break; + + default: + map_freeblock_unlock(); + return 1; + } + map_freeblock_unlock(); + + return 0; +} + +/*========================================== + * スキル使用(詠唱完了、ID指定支援系) + *------------------------------------------ + */ +int skill_castend_nodamage_id( struct block_list *src, struct block_list *bl,int skillid,int skilllv,unsigned int tick,int flag ) +{ + struct map_session_data *sd=NULL; + struct map_session_data *dstsd=NULL; + struct mob_data *md=NULL; + struct mob_data *dstmd=NULL; + int i,abra_skillid=0,abra_skilllv; + int sc_def_vit,sc_def_mdef,strip_fix,strip_time,strip_per; + int sc_dex,sc_luk; + //クラスチェンジ用ボスモンスターID + int changeclass[]={1038,1039,1046,1059,1086,1087,1112,1115 + ,1157,1159,1190,1272,1312,1373,1492}; + int poringclass[]={1002}; + + nullpo_retr(1, src); + nullpo_retr(1, bl); + + if(src->type==BL_PC) + sd=(struct map_session_data *)src; + else if(src->type==BL_MOB) + md=(struct mob_data *)src; + + sc_dex=battle_get_mdef(bl); + sc_luk=battle_get_luk(bl); + sc_def_vit = 100 - (3 + battle_get_vit(bl) + battle_get_luk(bl)/3); + sc_def_vit = 100 - (3 + battle_get_vit(bl) + battle_get_luk(bl)/3); + sc_def_mdef = 100 - (3 + battle_get_mdef(bl) + battle_get_luk(bl)/3); + strip_fix = battle_get_dex(src) - battle_get_dex(bl); + + if(bl->type==BL_PC){ + nullpo_retr(1, dstsd=(struct map_session_data *)bl); + }else if(bl->type==BL_MOB){ + nullpo_retr(1, dstmd=(struct mob_data *)bl); + if(sc_def_vit>50) + sc_def_vit=50; + if(sc_def_mdef>50) + sc_def_mdef=50; + } + if(sc_def_vit < 0) + sc_def_vit=0; + if(sc_def_mdef < 0) + sc_def_mdef=0; + if(strip_fix < 0) + strip_fix=0; + + if(bl == NULL || bl->prev == NULL) + return 1; + if(sd && pc_isdead(sd)) + return 1; + if(dstsd && pc_isdead(dstsd) && skillid != ALL_RESURRECTION) + return 1; + if(battle_get_class(bl) == 1288) + return 1; + if (skillnotok(skillid, (struct map_session_data *)bl)) // [MouseJstr] + return 0; + + map_freeblock_lock(); + switch(skillid) + { + case AL_HEAL: /* ヒール */ + { + int heal=skill_calc_heal( src, skilllv ); + int heal_get_jobexp; + int skill; + struct pc_base_job s_class; + + if( dstsd && dstsd->special_state.no_magic_damage ) + heal=0; /* 黄金蟲カード(ヒール量0) */ + if (sd){ + s_class = pc_calc_base_job(sd->status.class); + if((skill=pc_checkskill(sd,HP_MEDITATIO))>0) // メディテイティオ + heal += heal*(skill*2/100); + if(sd && dstsd && sd->status.partner_id == dstsd->status.char_id && s_class.job == 23 && sd->status.sex == 0) //自分も対象もPC、対象が自分のパートナー、自分がスパノビ、自分が♀なら + heal = heal*2; //スパノビの嫁が旦那にヒールすると2倍になる + } + + + clif_skill_nodamage(src,bl,skillid,heal,1); + heal_get_jobexp = battle_heal(NULL,bl,heal,0,0); + + // JOB経験値獲得 + if(src->type == BL_PC && bl->type==BL_PC && heal > 0 && src != bl && battle_config.heal_exp > 0){ + heal_get_jobexp = heal_get_jobexp * battle_config.heal_exp / 100; + if(heal_get_jobexp <= 0) + heal_get_jobexp = 1; + pc_gainexp((struct map_session_data *)src,0,heal_get_jobexp); + } + } + break; + + case ALL_RESURRECTION: /* リザレクション */ + if(bl->type==BL_PC){ + int per=0; + struct map_session_data *tsd = (struct map_session_data*)bl; + nullpo_retr(1, tsd); + if( (map[bl->m].flag.pvp) && tsd->pvp_point<0 ) + break; /* PVPで復活不可能状態 */ + + if(pc_isdead(tsd)){ /* 死亡判定 */ + clif_skill_nodamage(src,bl,skillid,skilllv,1); + switch(skilllv){ + case 1: per=10; break; + case 2: per=30; break; + case 3: per=50; break; + case 4: per=80; break; + } + tsd->status.hp=tsd->status.max_hp*per/100; + if(tsd->status.hp<=0) tsd->status.hp=1; + if(tsd->special_state.restart_full_recover ){ /* オシリスカード */ + tsd->status.hp=tsd->status.max_hp; + tsd->status.sp=tsd->status.max_sp; + } + pc_setstand(tsd); + if(battle_config.pc_invincible_time > 0) + pc_setinvincibletimer(tsd,battle_config.pc_invincible_time); + clif_updatestatus(tsd,SP_HP); + clif_resurrection(&tsd->bl,1); + if(src != bl && sd && battle_config.resurrection_exp > 0) { + int exp = 0,jexp = 0; + int lv = tsd->status.base_level - sd->status.base_level, jlv = tsd->status.job_level - sd->status.job_level; + if(lv > 0) { + exp = (int)((double)tsd->status.base_exp * (double)lv * (double)battle_config.resurrection_exp / 1000000.); + if(exp < 1) exp = 1; + } + if(jlv > 0) { + jexp = (int)((double)tsd->status.job_exp * (double)lv * (double)battle_config.resurrection_exp / 1000000.); + if(jexp < 1) jexp = 1; + } + if(exp > 0 || jexp > 0) + pc_gainexp(sd,exp,jexp); + } + } + } + break; + + case AL_DECAGI: /* 速度減少 */ + if( bl->type==BL_PC && ((struct map_session_data *)bl)->special_state.no_magic_damage ) + break; + if( rand()%100 < (50+skilllv*3+(battle_get_lv(src)+battle_get_int(src)/5)-sc_def_mdef) ) { + clif_skill_nodamage(src,bl,skillid,skilllv,1); + skill_status_change_start(bl,SkillStatusChangeTable[skillid],skilllv,0,0,0,skill_get_time(skillid,skilllv),0); + } + break; + + case AL_CRUCIS: + if(flag&1) { + int race = battle_get_race(bl),ele = battle_get_elem_type(bl); + if(battle_check_target(src,bl,BCT_ENEMY) && (race == 6 || battle_check_undead(race,ele))) { + int slv=battle_get_lv(src),tlv=battle_get_lv(bl),rate; + rate = 25 + skilllv*2 + slv - tlv; + if(rand()%100 < rate) + skill_status_change_start(bl,SkillStatusChangeTable[skillid],skilllv,0,0,0,0,0); + } + } + else { + int range = 15; + clif_skill_nodamage(src,bl,skillid,skilllv,1); + map_foreachinarea(skill_area_sub, + src->m,src->x-range,src->y-range,src->x+range,src->y+range,0, + src,skillid,skilllv,tick, flag|BCT_ENEMY|1, + skill_castend_nodamage_id); + } + break; + + case PR_LEXDIVINA: /* レックスディビーナ */ + { + struct status_change *sc_data = battle_get_sc_data(bl); + clif_skill_nodamage(src,bl,skillid,skilllv,1); + if( bl->type==BL_PC && ((struct map_session_data *)bl)->special_state.no_magic_damage ) + break; + if(sc_data && sc_data[SC_DIVINA].timer != -1) + skill_status_change_end(bl,SC_DIVINA,-1); + else if( rand()%100 < sc_def_vit ) { + skill_status_change_start(bl,SkillStatusChangeTable[skillid],skilllv,0,0,0,skill_get_time(skillid,skilllv),0); + } + } + break; + case SA_ABRACADABRA: + //require 1 yellow gemstone even with mistress card or Into the Abyss + if (pc_search_inventory(sd, 715) <= 0 ) { + clif_skill_fail(sd,sd->skillid,0,0); + break; + } + pc_delitem(sd, pc_search_inventory(sd, 715), 1, 0); + // + do{ + abra_skillid=skill_abra_dataset(skilllv); + }while(abra_skillid == 0); + abra_skilllv=skill_get_max(abra_skillid)>pc_checkskill(sd,SA_ABRACADABRA)?pc_checkskill(sd,SA_ABRACADABRA):skill_get_max(abra_skillid); + clif_skill_nodamage(src,bl,skillid,skilllv,1); + sd->skillitem=abra_skillid; + sd->skillitemlv=abra_skilllv; + clif_item_skill(sd,abra_skillid,abra_skilllv,"アブラカダブラ"); + break; + case SA_COMA: + clif_skill_nodamage(src,bl,skillid,skilllv,1); + if( bl->type==BL_PC && ((struct map_session_data *)bl)->special_state.no_magic_damage ) + break; + if(dstsd){ + dstsd->status.hp=1; + dstsd->status.sp=1; + clif_updatestatus(dstsd,SP_HP); + clif_updatestatus(dstsd,SP_SP); + } + if(dstmd) dstmd->hp=1; + break; + case SA_FULLRECOVERY: + clif_skill_nodamage(src,bl,skillid,skilllv,1); + if( bl->type==BL_PC && ((struct map_session_data *)bl)->special_state.no_magic_damage ) + break; + if(dstsd) pc_heal(dstsd,dstsd->status.max_hp,dstsd->status.max_sp); + if(dstmd) dstmd->hp=battle_get_max_hp(&dstmd->bl); + break; + case SA_SUMMONMONSTER: + clif_skill_nodamage(src,bl,skillid,skilllv,1); + if (sd) mob_once_spawn(sd,map[sd->bl.m].name,sd->bl.x,sd->bl.y,"--ja--",-1,1,""); + break; + case SA_LEVELUP: + clif_skill_nodamage(src,bl,skillid,skilllv,1); + if (sd && pc_nextbaseexp(sd)) pc_gainexp(sd,pc_nextbaseexp(sd)*10/100,0); + break; + + case SA_INSTANTDEATH: + clif_skill_nodamage(src,bl,skillid,skilllv,1); + if (sd) pc_damage(NULL,sd,sd->status.max_hp); + break; + + case SA_QUESTION: + case SA_GRAVITY: + clif_skill_nodamage(src,bl,skillid,skilllv,1); + break; + case SA_CLASSCHANGE: + clif_skill_nodamage(src,bl,skillid,skilllv,1); + if(dstmd) mob_class_change(dstmd,changeclass); + break; + case SA_MONOCELL: + clif_skill_nodamage(src,bl,skillid,skilllv,1); + if(dstmd) mob_class_change(dstmd,poringclass); + break; + case SA_DEATH: + clif_skill_nodamage(src,bl,skillid,skilllv,1); + if (dstsd) pc_damage(NULL,dstsd,dstsd->status.max_hp); + if (dstmd) mob_damage(NULL,dstmd,dstmd->hp,1); + break; + case SA_REVERSEORCISH: + clif_skill_nodamage(src,bl,skillid,skilllv,1); + if (dstsd) pc_setoption(dstsd,dstsd->status.option|0x0800); + break; + case SA_FORTUNE: + clif_skill_nodamage(src,bl,skillid,skilllv,1); + if(sd) pc_getzeny(sd,battle_get_lv(bl)*100); + break; + case SA_TAMINGMONSTER: + clif_skill_nodamage(src,bl,skillid,skilllv,1); + if (dstmd){ + for(i=0;i<MAX_PET_DB;i++){ + if(dstmd->class == pet_db[i].class){ + pet_catch_process1(sd,dstmd->class); + break; + } + } + } + break; + case AL_INCAGI: /* 速度増加 */ + case AL_BLESSING: /* ブレッシング */ + case PR_SLOWPOISON: + case PR_IMPOSITIO: /* イムポシティオマヌス */ + case PR_LEXAETERNA: /* レックスエーテルナ */ + case PR_SUFFRAGIUM: /* サフラギウム */ + case PR_BENEDICTIO: /* 聖体降福 */ + case CR_PROVIDENCE: /* プロヴィデンス */ + case CG_MARIONETTE: /* マリオネットコントロール */ + if( bl->type==BL_PC && ((struct map_session_data *)bl)->special_state.no_magic_damage ){ + clif_skill_nodamage(src,bl,skillid,skilllv,1); + }else{ + skill_status_change_start(bl,SkillStatusChangeTable[skillid],skilllv,0,0,0,skill_get_time(skillid,skilllv),0 ); + clif_skill_nodamage(src,bl,skillid,skilllv,1); + } + break; + + case SA_FLAMELAUNCHER: // added failure chance and chance to break weapon if turned on [Valaris] + case SA_FROSTWEAPON: + case SA_LIGHTNINGLOADER: + case SA_SEISMICWEAPON: + if(bl->type==BL_PC && ((struct map_session_data *)bl)->special_state.no_magic_damage ){ + clif_skill_nodamage(src,bl,skillid,skilllv,0); + break; + } + if(bl->type==BL_PC) { + struct map_session_data *sd2=(struct map_session_data *)bl; + if(sd2->status.weapon==0 || sd2->sc_data[SC_FLAMELAUNCHER].timer!=-1 || sd2->sc_data[SC_FROSTWEAPON].timer!=-1 || + sd2->sc_data[SC_LIGHTNINGLOADER].timer!=-1 || sd2->sc_data[SC_SEISMICWEAPON].timer!=-1 || + sd2->sc_data[SC_ENCPOISON].timer!=-1) { + clif_skill_fail(sd,skillid,0,0); + clif_skill_nodamage(src,bl,skillid,skilllv,0); + break; + } + } + if(rand()%100 > (75+skilllv*1) && (skilllv != 5)) { + clif_skill_fail(sd,skillid,0,0); + clif_skill_nodamage(src,bl,skillid,skilllv,0); + if(bl->type==BL_PC && battle_config.equipment_breaking) { + struct map_session_data *sd2=(struct map_session_data *)bl; + if(sd!=sd2) clif_displaymessage(sd->fd,"You broke target's weapon"); + pc_breakweapon(sd2); + } + break; + } + else { + skill_status_change_start(bl,SkillStatusChangeTable[skillid],skilllv,0,0,0,skill_get_time(skillid,skilllv),0 ); + clif_skill_nodamage(src,bl,skillid,skilllv,1); + } + break; + + case PR_ASPERSIO: /* アスペルシオ */ + clif_skill_nodamage(src,bl,skillid,skilllv,1); + if( bl->type==BL_PC && ((struct map_session_data *)bl)->special_state.no_magic_damage ) + break; + if(bl->type==BL_MOB) + break; + skill_status_change_start(bl,SkillStatusChangeTable[skillid],skilllv,0,0,0,skill_get_time(skillid,skilllv),0 ); + break; + case PR_KYRIE: /* キリエエレイソン */ + clif_skill_nodamage(bl,bl,skillid,skilllv,1); + if( bl->type==BL_PC && ((struct map_session_data *)bl)->special_state.no_magic_damage ) + break; + skill_status_change_start(bl,SkillStatusChangeTable[skillid],skilllv,0,0,0,skill_get_time(skillid,skilllv),0 ); + break; + case KN_AUTOCOUNTER: /* オートカウンター */ + case KN_TWOHANDQUICKEN: /* ツーハンドクイッケン */ + case CR_SPEARQUICKEN: /* スピアクイッケン */ + case CR_REFLECTSHIELD: + case AS_POISONREACT: /* ポイズンリアクト */ + case MC_LOUD: /* ラウドボイス */ + case MG_ENERGYCOAT: /* エナジーコート */ + case SM_ENDURE: /* インデュア */ + case MG_SIGHT: /* サイト */ + case AL_RUWACH: /* ルアフ */ + case MO_EXPLOSIONSPIRITS: // 爆裂波動 + case MO_STEELBODY: // 金剛 + case LK_AURABLADE: /* オーラブレード */ + case LK_PARRYING: /* パリイング */ + case LK_CONCENTRATION: /* コンセントレーション */ + case LK_BERSERK: /* バーサーク */ + case HP_ASSUMPTIO: /* */ + case WS_CARTBOOST: /* カートブースト */ + case SN_SIGHT: /* トゥルーサイト */ + case WS_MELTDOWN: /* メルトダウン */ + case ST_REJECTSWORD: /* リジェクトソード */ + case HW_MAGICPOWER: /* 魔法力増幅 */ + case PF_MEMORIZE: /* メモライズ */ + clif_skill_nodamage(src,bl,skillid,skilllv,1); + skill_status_change_start(bl,SkillStatusChangeTable[skillid],skilllv,0,0,0,skill_get_time(skillid,skilllv),0 ); + break; + case AS_ENCHANTPOISON: // Prevent spamming [Valaris] + if(bl->type==BL_PC) { + struct map_session_data *sd2=(struct map_session_data *)bl; + if(sd2->sc_data[SC_FLAMELAUNCHER].timer!=-1 || sd2->sc_data[SC_FROSTWEAPON].timer!=-1 || + sd2->sc_data[SC_LIGHTNINGLOADER].timer!=-1 || sd2->sc_data[SC_SEISMICWEAPON].timer!=-1 || + sd2->sc_data[SC_ENCPOISON].timer!=-1) { + clif_skill_nodamage(src,bl,skillid,skilllv,0); + clif_skill_fail(sd,skillid,0,0); + break; + } + } + clif_skill_nodamage(src,bl,skillid,skilllv,1); + skill_status_change_start(bl,SkillStatusChangeTable[skillid],skilllv,0,0,0,skill_get_time(skillid,skilllv),0 ); + break; + case LK_TENSIONRELAX: /* テンションリラックス */ + clif_skill_nodamage(src,bl,skillid,skilllv,1); + pc_setsit(sd); + clif_sitting(sd->fd,sd); + skill_status_change_start(bl,SkillStatusChangeTable[skillid],skilllv,0,0,0,skill_get_time(skillid,skilllv),0 ); + break; + case MC_CHANGECART: + clif_skill_nodamage(src,bl,skillid,skilllv,1); + break; + case AC_CONCENTRATION: /* 集中力向上 */ + { + int range = 1; + clif_skill_nodamage(src,bl,skillid,skilllv,1); + skill_status_change_start(bl,SkillStatusChangeTable[skillid],skilllv,0,0,0,skill_get_time(skillid,skilllv),0 ); + map_foreachinarea( skill_status_change_timer_sub, + src->m, src->x-range, src->y-range, src->x+range,src->y+range,0, + src,SkillStatusChangeTable[skillid],tick); + } + break; + case SM_PROVOKE: /* プロボック */ + { + struct status_change *sc_data = battle_get_sc_data(bl); + + /* MVPmobと不死には効かない */ + if((bl->type==BL_MOB && battle_get_mode(bl)&0x20) || battle_check_undead(battle_get_race(bl),battle_get_elem_type(bl))) //不死には効かない + { + map_freeblock_unlock(); + return 1; + } + + clif_skill_nodamage(src,bl,skillid,skilllv,1); + skill_status_change_start(bl,SkillStatusChangeTable[skillid],skilllv,0,0,0,skill_get_time(skillid,skilllv),0 ); + + if(dstmd && dstmd->skilltimer!=-1 && dstmd->state.skillcastcancel) // 詠唱妨害 + skill_castcancel(bl,0); + if(dstsd && dstsd->skilltimer!=-1 && (!dstsd->special_state.no_castcancel || map[bl->m].flag.gvg) + && dstsd->state.skillcastcancel && !dstsd->special_state.no_castcancel2) + skill_castcancel(bl,0); + + if(sc_data){ + if(sc_data[SC_FREEZE].timer!=-1) + skill_status_change_end(bl,SC_FREEZE,-1); + if(sc_data[SC_STONE].timer!=-1 && sc_data[SC_STONE].val2==0) + skill_status_change_end(bl,SC_STONE,-1); + if(sc_data[SC_SLEEP].timer!=-1) + skill_status_change_end(bl,SC_SLEEP,-1); + } + + if(bl->type==BL_MOB) { + int range = skill_get_range(skillid,skilllv); + if(range < 0) + range = battle_get_range(src) - (range + 1); + mob_target((struct mob_data *)bl,src,range); + } + } + break; + + case CR_DEVOTION: /* ディボーション */ + if(sd && dstsd){ + //転生や養子の場合の元の職業を算出する + + int lv = sd->status.base_level-dstsd->status.base_level; + lv = (lv<0)?-lv:lv; + if((dstsd->bl.type!=BL_PC) // 相手はPCじゃないとだめ + ||(sd->bl.id == dstsd->bl.id) // 相手が自分はだめ + ||(lv > 10) // レベル差±10まで + ||(!sd->status.party_id && !sd->status.guild_id) // PTにもギルドにも所属無しはだめ + ||((sd->status.party_id != dstsd->status.party_id) // 同じパーティーか、 + ||(sd->status.guild_id != dstsd->status.guild_id)) // 同じギルドじゃないとだめ + ||(dstsd->status.class==14||dstsd->status.class==21 + ||dstsd->status.class==4015||dstsd->status.class==4022)){ // クルセだめ + clif_skill_fail(sd,skillid,0,0); + map_freeblock_unlock(); + return 1; + } + for(i=0;i<skilllv;i++){ + if(!sd->dev.val1[i]){ // 空きがあったら入れる + sd->dev.val1[i] = bl->id; + sd->dev.val2[i] = bl->id; + break; + }else if(i==skilllv-1){ // 空きがなかった + clif_skill_fail(sd,skillid,0,0); + map_freeblock_unlock(); + return 1; + } + } + clif_skill_nodamage(src,bl,skillid,skilllv,1); + clif_devotion(sd,bl->id); + skill_status_change_start(bl,SkillStatusChangeTable[skillid],src->id,1,0,0,1000*(15+15*skilllv),0 ); + } + else clif_skill_fail(sd,skillid,0,0); + break; + case MO_CALLSPIRITS: // 気功 + if(sd) { + clif_skill_nodamage(src,bl,skillid,skilllv,1); + pc_addspiritball(sd,skill_get_time(skillid,skilllv),skilllv); + } + break; + case CH_SOULCOLLECT: // 狂気功 + if(sd) { + clif_skill_nodamage(src,bl,skillid,skilllv,1); + for(i=0;i<5;i++) + pc_addspiritball(sd,skill_get_time(skillid,skilllv),5); + } + break; + case MO_BLADESTOP: // 白刃取り + clif_skill_nodamage(src,bl,skillid,skilllv,1); + skill_status_change_start(src,SkillStatusChangeTable[skillid],skilllv,0,0,0,skill_get_time(skillid,skilllv),0 ); + break; + case MO_ABSORBSPIRITS: // 気奪 + i=0; + if(sd && dstsd) { + if(sd == dstsd || map[sd->bl.m].flag.pvp || map[sd->bl.m].flag.gvg) { + if(dstsd->spiritball > 0) { + clif_skill_nodamage(src,bl,skillid,skilllv,1); + i = dstsd->spiritball * 7; + pc_delspiritball(dstsd,dstsd->spiritball,0); + if(i > 0x7FFF) + i = 0x7FFF; + if(sd->status.sp + i > sd->status.max_sp) + i = sd->status.max_sp - sd->status.sp; + } + } + }else if(sd && dstmd){ //対象がモンスターの場合 + //20%の確率で対象のLv*2のSPを回復する。成功したときはターゲット(σ゚Д゚)σゲッツ!! + if(rand()%100<20){ + i=2*mob_db[dstmd->class].lv; + mob_target(dstmd,src,0); + } + } + if(i){ + sd->status.sp += i; + clif_heal(sd->fd,SP_SP,i); + } + else + clif_skill_nodamage(src,bl,skillid,skilllv,0); + break; + + case AC_MAKINGARROW: /* 矢作成 */ + if(sd) { + clif_arrow_create_list(sd); + clif_skill_nodamage(src,bl,skillid,skilllv,1); + } + break; + + case AM_PHARMACY: /* ポーション作成 */ + if(sd) { + clif_skill_produce_mix_list(sd,32); + clif_skill_nodamage(src,bl,skillid,skilllv,1); + } + break; + case WS_CREATECOIN: /* クリエイトコイン */ + if(sd) { + clif_skill_produce_mix_list(sd,64); + clif_skill_nodamage(src,bl,skillid,skilllv,1); + } + break; + case WS_CREATENUGGET: /* 塊製造 */ + if(sd) { + clif_skill_produce_mix_list(sd,128); + clif_skill_nodamage(src,bl,skillid,skilllv,1); + } + break; + case BS_HAMMERFALL: /* ハンマーフォール */ + clif_skill_nodamage(src,bl,skillid,skilllv,1); + if( bl->type==BL_PC && ((struct map_session_data *)bl)->special_state.no_weapon_damage ) + break; + if( rand()%100 < (20+ 10*skilllv)*sc_def_vit/100 ) { + skill_status_change_start(bl,SC_STAN,skilllv,0,0,0,skill_get_time2(skillid,skilllv),0); + } + break; + + case RG_RAID: /* サプライズアタック */ + clif_skill_nodamage(src,bl,skillid,skilllv,1); + { + int x=bl->x,y=bl->y; + skill_area_temp[1]=bl->id; + skill_area_temp[2]=x; + skill_area_temp[3]=y; + map_foreachinarea(skill_area_sub, + bl->m,x-1,y-1,x+1,y+1,0, + src,skillid,skilllv,tick, flag|BCT_ENEMY|1, + skill_castend_damage_id); + } + skill_status_change_end(src, SC_HIDING, -1); // ハイディング解除 + break; + + case KN_BRANDISHSPEAR: /*ブランディッシュスピア*/ + { + int c,n=4,ar; + int dir = map_calc_dir(src,bl->x,bl->y); + struct square tc; + int x=bl->x,y=bl->y; + ar=skilllv/3; + skill_brandishspear_first(&tc,dir,x,y); + skill_brandishspear_dir(&tc,dir,4); + /* 範囲C */ + if(skilllv == 10){ + for(c=1;c<4;c++){ + map_foreachinarea(skill_area_sub, + bl->m,tc.val1[c],tc.val2[c],tc.val1[c],tc.val2[c],0, + src,skillid,skilllv,tick, flag|BCT_ENEMY|n, + skill_castend_damage_id); + } + } + /* 範囲BA */ + if(skilllv > 6){ + skill_brandishspear_dir(&tc,dir,-1); + n--; + }else{ + skill_brandishspear_dir(&tc,dir,-2); + n-=2; + } + + if(skilllv > 3){ + for(c=0;c<5;c++){ + map_foreachinarea(skill_area_sub, + bl->m,tc.val1[c],tc.val2[c],tc.val1[c],tc.val2[c],0, + src,skillid,skilllv,tick, flag|BCT_ENEMY|n, + skill_castend_damage_id); + if(skilllv > 6 && n==3 && c==4){ + skill_brandishspear_dir(&tc,dir,-1); + n--;c=-1; + } + } + } + /* 範囲@ */ + for(c=0;c<10;c++){ + if(c==0||c==5) skill_brandishspear_dir(&tc,dir,-1); + map_foreachinarea(skill_area_sub, + bl->m,tc.val1[c%5],tc.val2[c%5],tc.val1[c%5],tc.val2[c%5],0, + src,skillid,skilllv,tick, flag|BCT_ENEMY|1, + skill_castend_damage_id); + } + } + break; + + /* パーティスキル */ + case AL_ANGELUS: /* エンジェラス */ + case PR_MAGNIFICAT: /* マグニフィカート */ + case PR_GLORIA: /* グロリア */ + case SN_WINDWALK: /* ウインドウォーク */ + if(sd == NULL || sd->status.party_id==0 || (flag&1) ){ + /* 個別の処理 */ + clif_skill_nodamage(bl,bl,skillid,skilllv,1); + if( bl->type==BL_PC && ((struct map_session_data *)bl)->special_state.no_magic_damage ) + break; + skill_status_change_start(bl,SkillStatusChangeTable[skillid],skilllv,0,0,0,skill_get_time(skillid,skilllv),0); + } + else{ + /* パーティ全体への処理 */ + party_foreachsamemap(skill_area_sub, + sd,1, + src,skillid,skilllv,tick, flag|BCT_PARTY|1, + skill_castend_nodamage_id); + } + break; + case BS_ADRENALINE: /* アドレナリンラッシュ */ + case BS_WEAPONPERFECT: /* ウェポンパーフェクション */ + case BS_OVERTHRUST: /* オーバートラスト */ + if(sd == NULL || sd->status.party_id==0 || (flag&1) ){ + /* 個別の処理 */ + clif_skill_nodamage(bl,bl,skillid,skilllv,1); + skill_status_change_start(bl,SkillStatusChangeTable[skillid],skilllv,(src == bl)? 1:0,0,0,skill_get_time(skillid,skilllv),0); + } + else{ + /* パーティ全体への処理 */ + party_foreachsamemap(skill_area_sub, + sd,1, + src,skillid,skilllv,tick, flag|BCT_PARTY|1, + skill_castend_nodamage_id); + } + break; + + /*(付加と解除が必要) */ + case BS_MAXIMIZE: /* マキシマイズパワー */ + case NV_TRICKDEAD: /* 死んだふり */ + case CR_DEFENDER: /* ディフェンダー */ + case CR_AUTOGUARD: /* オートガード */ + { + struct status_change *tsc_data = battle_get_sc_data(bl); + int sc=SkillStatusChangeTable[skillid]; + clif_skill_nodamage(src,bl,skillid,skilllv,1); + if( tsc_data ){ + if( tsc_data[sc].timer==-1 ) + /* 付加する */ + skill_status_change_start(bl,sc,skilllv,0,0,0,skill_get_time(skillid,skilllv),0); + else + /* 解除する */ + skill_status_change_end(bl, sc, -1); + } + } + break; + + case TF_HIDING: /* ハイディング */ + { + struct status_change *tsc_data = battle_get_sc_data(bl); + int sc=SkillStatusChangeTable[skillid]; + clif_skill_nodamage(src,bl,skillid,-1,1); + if( tsc_data ){ + if( tsc_data[sc].timer==-1 ) + /* 付加する */ + skill_status_change_start(bl,sc,skilllv,0,0,0,skill_get_time(skillid,skilllv),0); + else + /* 解除する */ + skill_status_change_end(bl, sc, -1); + } + } + break; + + case AS_CLOAKING: /* クローキング */ + { + struct status_change *tsc_data = battle_get_sc_data(bl); + int sc=SkillStatusChangeTable[skillid]; + clif_skill_nodamage(src,bl,skillid,-1,1); + if( tsc_data ){ + if( tsc_data[sc].timer==-1 ) + /* 付加する */ + skill_status_change_start(bl,sc,skilllv,0,0,0,skill_get_time(skillid,skilllv),0); + else + /* 解除する */ + skill_status_change_end(bl, sc, -1); + } + + skill_check_cloaking(bl); + } + break; + + case ST_CHASEWALK: /* ハイディング */ + { + struct status_change *tsc_data = battle_get_sc_data(bl); + int sc=SkillStatusChangeTable[skillid]; + clif_skill_nodamage(src,bl,skillid,-1,1); + if( tsc_data ){ + if( tsc_data[sc].timer==-1 ) + /* 付加する */ + skill_status_change_start(bl,sc,skilllv,0,0,0,skill_get_time(skillid,skilllv),0); + else + /* 解除する */ + skill_status_change_end(bl, sc, -1); + } + } + break; + + /* 対地スキル */ + case BD_LULLABY: /* 子守唄 */ + case BD_RICHMANKIM: /* ニヨルドの宴 */ + case BD_ETERNALCHAOS: /* 永遠の混沌 */ + case BD_DRUMBATTLEFIELD: /* 戦太鼓の響き */ + case BD_RINGNIBELUNGEN: /* ニーベルングの指輪 */ + case BD_ROKISWEIL: /* ロキの叫び */ + case BD_INTOABYSS: /* 深淵の中に */ + case BD_SIEGFRIED: /* 不死身のジークフリード */ + case BA_DISSONANCE: /* 不協和音 */ + case BA_POEMBRAGI: /* ブラギの詩 */ + case BA_WHISTLE: /* 口笛 */ + case BA_ASSASSINCROSS: /* 夕陽のアサシンクロス */ + case BA_APPLEIDUN: /* イドゥンの林檎 */ + case DC_UGLYDANCE: /* 自分勝手なダンス */ + case DC_HUMMING: /* ハミング */ + case DC_DONTFORGETME: /* 私を忘れないで… */ + case DC_FORTUNEKISS: /* 幸運のキス */ + case DC_SERVICEFORYOU: /* サービスフォーユー */ + case CG_MOONLIT: /* 月明りの泉に落ちる花びら */ + clif_skill_nodamage(src,bl,skillid,skilllv,1); + skill_unitsetting(src,skillid,skilllv,src->x,src->y,0); + break; + + case HP_BASILICA: /* バジリカ */ + case PA_GOSPEL: /* ゴスペル */ + skill_clear_unitgroup(src); + clif_skill_nodamage(src,bl,skillid,skilllv,1); + skill_unitsetting(src,skillid,skilllv,src->x,src->y,0); + break; + + case BD_ADAPTATION: /* アドリブ */ + { + struct status_change *sc_data = battle_get_sc_data(src); + if(sc_data && sc_data[SC_DANCING].timer!=-1){ + clif_skill_nodamage(src,bl,skillid,skilllv,1); + skill_stop_dancing(src,0); + } + } + break; + + case BA_FROSTJOKE: /* 寒いジョーク */ + case DC_SCREAM: /* スクリーム */ + clif_skill_nodamage(src,bl,skillid,skilllv,1); + skill_addtimerskill(src,tick+3000,bl->id,0,0,skillid,skilllv,0,flag); + break; + + case TF_STEAL: // スティール + if(sd) { + if(pc_steal_item(sd,bl)) + clif_skill_nodamage(src,bl,skillid,skilllv,1); + else + clif_skill_nodamage(src,bl,skillid,skilllv,0); + } + break; + + case RG_STEALCOIN: // スティールコイン + if(sd) { + if(pc_steal_coin(sd,bl)) { + int range = skill_get_range(skillid,skilllv); + if(range < 0) + range = battle_get_range(src) - (range + 1); + clif_skill_nodamage(src,bl,skillid,skilllv,1); + mob_target((struct mob_data *)bl,src,range); + } + else + clif_skill_nodamage(src,bl,skillid,skilllv,0); + } + break; + + case MG_STONECURSE: /* ストーンカース */ + if (bl->type==BL_MOB && battle_get_mode(bl)&0x20) { + clif_skill_fail(sd,sd->skillid,0,0); + break; + } + clif_skill_nodamage(src,bl,skillid,skilllv,1); + if( bl->type==BL_PC && ((struct map_session_data *)bl)->special_state.no_magic_damage ) + break; + if( rand()%100 < skilllv*4+20 && !battle_check_undead(battle_get_race(bl),battle_get_elem_type(bl))) + skill_status_change_start(bl,SC_STONE,skilllv,0,0,0,skill_get_time2(skillid,skilllv),0); + else if(sd) + clif_skill_fail(sd,skillid,0,0); + break; + + case NV_FIRSTAID: /* 応急手当 */ + clif_skill_nodamage(src,bl,skillid,5,1); + battle_heal(NULL,bl,5,0,0); + break; + + case AL_CURE: /* キュアー */ + clif_skill_nodamage(src,bl,skillid,skilllv,1); + if( bl->type==BL_PC && ((struct map_session_data *)bl)->special_state.no_magic_damage ) + break; + skill_status_change_end(bl, SC_SILENCE , -1 ); + skill_status_change_end(bl, SC_BLIND , -1 ); + skill_status_change_end(bl, SC_CONFUSION, -1 ); + if( battle_check_undead(battle_get_race(bl),battle_get_elem_type(bl)) ){//アンデッドなら暗闇効果 + skill_status_change_start(bl, SC_CONFUSION,1,0,0,0,6000,0); + } + break; + + case TF_DETOXIFY: /* 解毒 */ + clif_skill_nodamage(src,bl,skillid,skilllv,1); + skill_status_change_end(bl, SC_POISON , -1 ); + break; + + case PR_STRECOVERY: /* リカバリー */ + { + clif_skill_nodamage(src,bl,skillid,skilllv,1); + if( bl->type==BL_PC && ((struct map_session_data *)bl)->special_state.no_magic_damage ) + break; + skill_status_change_end(bl, SC_FREEZE , -1 ); + skill_status_change_end(bl, SC_STONE , -1 ); + skill_status_change_end(bl, SC_SLEEP , -1 ); + skill_status_change_end(bl, SC_STAN , -1 ); + if( battle_check_undead(battle_get_race(bl),battle_get_elem_type(bl)) ){//アンデッドなら暗闇効果 + int blind_time; + //blind_time=30-battle_get_vit(bl)/10-battle_get_int/15; + blind_time=30*(100-(battle_get_int(bl)+battle_get_vit(bl))/2)/100; + if(rand()%100 < (100-(battle_get_int(bl)/2+battle_get_vit(bl)/3+battle_get_luk(bl)/10))) + skill_status_change_start(bl, SC_BLIND,1,0,0,0,blind_time,0); + } + if(dstmd){ + dstmd->attacked_id=0; + dstmd->target_id=0; + dstmd->state.targettype = NONE_ATTACKABLE; + dstmd->state.skillstate=MSS_IDLE; + dstmd->next_walktime=tick+rand()%3000+3000; + } + } + break; + + case WZ_ESTIMATION: /* モンスター情報 */ + if(src->type==BL_PC){ + clif_skill_nodamage(src,bl,skillid,skilllv,1); + clif_skill_estimation((struct map_session_data *)src,bl); + } + break; + + case MC_IDENTIFY: /* アイテム鑑定 */ + if(sd) + clif_item_identify_list(sd); + break; + + case BS_REPAIRWEAPON: /* 武器修理 */ + if(sd) +//動作しないのでとりあえずコメントアウト +// clif_item_repair_list(sd); + break; + + case MC_VENDING: /* 露店開設 */ + if(sd) + clif_openvendingreq(sd,2+sd->skilllv); + break; + + case AL_TELEPORT: /* テレポート */ + if( sd ){ + if(map[sd->bl.m].flag.noteleport){ /* テレポ禁止 */ + clif_skill_teleportmessage(sd,0); + break; + } + clif_skill_nodamage(src,bl,skillid,skilllv,1); + if( sd->skilllv==1 ) + clif_skill_warppoint(sd,sd->skillid,"Random","","",""); + else{ + clif_skill_warppoint(sd,sd->skillid,"Random", + sd->status.save_point.map,"",""); + } + }else if( bl->type==BL_MOB ) + mob_warp((struct mob_data *)bl,-1,-1,-1,3); + break; + + case AL_HOLYWATER: /* アクアベネディクタ */ + if(sd) { + int eflag; + struct item item_tmp; + clif_skill_nodamage(src,bl,skillid,skilllv,1); + memset(&item_tmp,0,sizeof(item_tmp)); + item_tmp.nameid = 523; + item_tmp.identify = 1; + if(battle_config.holywater_name_input) { + item_tmp.card[0] = 0xfe; + item_tmp.card[1] = 0; + *((unsigned long *)(&item_tmp.card[2]))=sd->char_id; /* キャラID */ + } + eflag = pc_additem(sd,&item_tmp,1); + if(eflag) { + clif_additem(sd,0,0,eflag); + map_addflooritem(&item_tmp,1,sd->bl.m,sd->bl.x,sd->bl.y,NULL,NULL,NULL,0); + } + } + break; + case TF_PICKSTONE: + if(sd) { + int eflag; + struct item item_tmp; + struct block_list tbl; + clif_skill_nodamage(src,bl,skillid,skilllv,1); + memset(&item_tmp,0,sizeof(item_tmp)); + memset(&tbl,0,sizeof(tbl)); // [MouseJstr] + item_tmp.nameid = 7049; + item_tmp.identify = 1; + tbl.id = 0; + clif_takeitem(&sd->bl,&tbl); + eflag = pc_additem(sd,&item_tmp,1); + if(eflag) { + clif_additem(sd,0,0,eflag); + map_addflooritem(&item_tmp,1,sd->bl.m,sd->bl.x,sd->bl.y,NULL,NULL,NULL,0); + } + } + break; + + case RG_STRIPWEAPON: /* ストリップウェポン */ + { + struct status_change *tsc_data = battle_get_sc_data(bl); + + if(tsc_data && tsc_data[SC_CP_WEAPON].timer != -1 ) + break; + strip_per = 5+2*skilllv+strip_fix/5; + strip_time = skill_get_time(skillid,skilllv)+strip_fix/2; + if(rand()%100 < strip_per){ + clif_skill_nodamage(src,bl,skillid,skilllv,1); + skill_status_change_start(bl,SkillStatusChangeTable[skillid],skilllv,0,0,0,strip_time,0 ); + if(dstsd){ + for(i=0;i<MAX_INVENTORY;i++){ + if(dstsd->status.inventory[i].equip && dstsd->status.inventory[i].equip & 0x0002){ + pc_unequipitem(dstsd,i,0); + break; + } + } + } + } + } + break; + + case RG_STRIPSHIELD: /* ストリップシールド */ + { + struct status_change *tsc_data = battle_get_sc_data(bl); + + if(tsc_data && tsc_data[SC_CP_SHIELD].timer != -1 ) + break; + strip_per = 5+2*skilllv+strip_fix/5; + strip_time = skill_get_time(skillid,skilllv)+strip_fix/2; + if(rand()%100 < strip_per){ + clif_skill_nodamage(src,bl,skillid,skilllv,1); + skill_status_change_start(bl,SkillStatusChangeTable[skillid],skilllv,0,0,0,strip_time,0 ); + if(dstsd){ + for(i=0;i<MAX_INVENTORY;i++){ + if(dstsd->status.inventory[i].equip && dstsd->status.inventory[i].equip & 0x0020){ + pc_unequipitem(dstsd,i,0); + break; + } + } + } + } + } + break; + + case RG_STRIPARMOR: /* ストリップアーマー */ + { + struct status_change *tsc_data = battle_get_sc_data(bl); + + if(tsc_data && tsc_data[SC_CP_ARMOR].timer != -1 ) + break; + strip_per = 5+2*skilllv+strip_fix/5; + strip_time = skill_get_time(skillid,skilllv)+strip_fix/2; + if(rand()%100 < strip_per){ + clif_skill_nodamage(src,bl,skillid,skilllv,1); + skill_status_change_start(bl,SkillStatusChangeTable[skillid],skilllv,0,0,0,strip_time,0 ); + if(dstsd){ + for(i=0;i<MAX_INVENTORY;i++){ + if(dstsd->status.inventory[i].equip && dstsd->status.inventory[i].equip & 0x0010){ + pc_unequipitem(dstsd,i,0); + break; + } + } + } + } + } + break; + case RG_STRIPHELM: /* ストリップヘルム */ + { + struct status_change *tsc_data = battle_get_sc_data(bl); + + if(tsc_data && tsc_data[SC_CP_HELM].timer != -1 ) + break; + strip_per = 5+2*skilllv+strip_fix/5; + strip_time = skill_get_time(skillid,skilllv)+strip_fix/2; + if(rand()%100 < strip_per){ + clif_skill_nodamage(src,bl,skillid,skilllv,1); + skill_status_change_start(bl,SkillStatusChangeTable[skillid],skilllv,0,0,0,strip_time,0 ); + if(dstsd){ + for(i=0;i<MAX_INVENTORY;i++){ + if(dstsd->status.inventory[i].equip && dstsd->status.inventory[i].equip & 0x0100){ + pc_unequipitem(dstsd,i,0); + break; + } + } + } + } + } + break; + /* PotionPitcher */ + case AM_POTIONPITCHER: /* ポーションピッチャー */ + { + struct block_list tbl; + int i,x,hp = 0,sp = 0; + if(sd) { + if(sd==dstsd) { // cancel use on oneself + map_freeblock_unlock(); + return 1; + } + x = skilllv%11 - 1; + i = pc_search_inventory(sd,skill_db[skillid].itemid[x]); + if(i < 0 || skill_db[skillid].itemid[x] <= 0) { + clif_skill_fail(sd,skillid,0,0); + map_freeblock_unlock(); + return 1; + } + if(sd->inventory_data[i] == NULL || sd->status.inventory[i].amount < skill_db[skillid].amount[x]) { + clif_skill_fail(sd,skillid,0,0); + map_freeblock_unlock(); + return 1; + } + sd->state.potionpitcher_flag = 1; + sd->potion_hp = sd->potion_sp = sd->potion_per_hp = sd->potion_per_sp = 0; + sd->skilltarget = bl->id; + run_script(sd->inventory_data[i]->use_script,0,sd->bl.id,0); + pc_delitem(sd,i,skill_db[skillid].amount[x],0); + sd->state.potionpitcher_flag = 0; + if(sd->potion_per_hp > 0 || sd->potion_per_sp > 0) { + hp = battle_get_max_hp(bl) * sd->potion_per_hp / 100; + hp = hp * (100 + pc_checkskill(sd,AM_POTIONPITCHER)*10 + pc_checkskill(sd,AM_LEARNINGPOTION)*5)/100; + if(dstsd) { + sp = dstsd->status.max_sp * sd->potion_per_sp / 100; + sp = sp * (100 + pc_checkskill(sd,AM_POTIONPITCHER) + pc_checkskill(sd,AM_LEARNINGPOTION)*5)/100; + } + } + else { + if(sd->potion_hp > 0) { + hp = sd->potion_hp * (100 + pc_checkskill(sd,AM_POTIONPITCHER)*10 + pc_checkskill(sd,AM_LEARNINGPOTION)*5)/100; + hp = hp * (100 + (battle_get_vit(bl)<<1)) / 100; + if(dstsd) + hp = hp * (100 + pc_checkskill(dstsd,SM_RECOVERY)*10) / 100; + } + if(sd->potion_sp > 0) { + sp = sd->potion_sp * (100 + pc_checkskill(sd,AM_POTIONPITCHER) + pc_checkskill(sd,AM_LEARNINGPOTION)*5)/100; + sp = sp * (100 + (battle_get_int(bl)<<1)) / 100; + if(dstsd) + sp = sp * (100 + pc_checkskill(dstsd,MG_SRECOVERY)*10) / 100; + } + } + } + else { + hp = (1 + rand()%400) * (100 + skilllv*10) / 100; + hp = hp * (100 + (battle_get_vit(bl)<<1)) / 100; + if(dstsd) + hp = hp * (100 + pc_checkskill(dstsd,SM_RECOVERY)*10) / 100; + } + tbl.id = 0; + tbl.m = src->m; + tbl.x = src->x; + tbl.y = src->y; + clif_skill_nodamage(src,bl,skillid,skilllv,1); + if(hp > 0 || (hp <= 0 && sp <= 0)) + clif_skill_nodamage(&tbl,bl,AL_HEAL,hp,1); + if(sp > 0) + clif_skill_nodamage(&tbl,bl,MG_SRECOVERY,sp,1); + battle_heal(src,bl,hp,sp,0); + } + break; + case AM_CP_WEAPON: + { + struct status_change *tsc_data = battle_get_sc_data(bl); + clif_skill_nodamage(src,bl,skillid,skilllv,1); + if(tsc_data && tsc_data[SC_STRIPWEAPON].timer != -1) + skill_status_change_end(bl, SC_STRIPWEAPON, -1 ); + skill_status_change_start(bl,SkillStatusChangeTable[skillid],skilllv,0,0,0,skill_get_time(skillid,skilllv),0 ); + } + break; + case AM_CP_SHIELD: + { + struct status_change *tsc_data = battle_get_sc_data(bl); + clif_skill_nodamage(src,bl,skillid,skilllv,1); + if(tsc_data && tsc_data[SC_STRIPSHIELD].timer != -1) + skill_status_change_end(bl, SC_STRIPSHIELD, -1 ); + skill_status_change_start(bl,SkillStatusChangeTable[skillid],skilllv,0,0,0,skill_get_time(skillid,skilllv),0 ); + } + break; + case AM_CP_ARMOR: + { + struct status_change *tsc_data = battle_get_sc_data(bl); + clif_skill_nodamage(src,bl,skillid,skilllv,1); + if(tsc_data && tsc_data[SC_STRIPARMOR].timer != -1) + skill_status_change_end(bl, SC_STRIPARMOR, -1 ); + skill_status_change_start(bl,SkillStatusChangeTable[skillid],skilllv,0,0,0,skill_get_time(skillid,skilllv),0 ); + } + break; + case AM_CP_HELM: + { + struct status_change *tsc_data = battle_get_sc_data(bl); + clif_skill_nodamage(src,bl,skillid,skilllv,1); + if(tsc_data && tsc_data[SC_STRIPHELM].timer != -1) + skill_status_change_end(bl, SC_STRIPHELM, -1 ); + skill_status_change_start(bl,SkillStatusChangeTable[skillid],skilllv,0,0,0,skill_get_time(skillid,skilllv),0 ); + } + break; + case SA_DISPELL: /* ディスペル */ + { + int i; + clif_skill_nodamage(src,bl,skillid,skilllv,1); + if( bl->type==BL_PC && ((struct map_session_data *)bl)->special_state.no_magic_damage ) + break; + for(i=0;i<136;i++){ + if(i==SC_RIDING || i== SC_FALCON || i==SC_HALLUCINATION || i==SC_WEIGHT50 + || i==SC_WEIGHT90 || i==SC_STRIPWEAPON || i==SC_STRIPSHIELD || i==SC_STRIPARMOR + || i==SC_STRIPHELM || i==SC_CP_WEAPON || i==SC_CP_SHIELD || i==SC_CP_ARMOR + || i==SC_CP_HELM || i==SC_COMBO) + continue; + skill_status_change_end(bl,i,-1); + } + } + break; + + case TF_BACKSLIDING: /* バックステップ */ + battle_stopwalking(src,1); + skill_blown(src,bl,skill_get_blewcount(skillid,skilllv)|0x10000); + if(src->type == BL_MOB) + clif_fixmobpos((struct mob_data *)src); + else if(src->type == BL_PET) + clif_fixpetpos((struct pet_data *)src); + else if(src->type == BL_PC) + clif_fixpos(src); + skill_addtimerskill(src,tick + 200,src->id,0,0,skillid,skilllv,0,flag); + break; + + case SA_CASTCANCEL: + clif_skill_nodamage(src,bl,skillid,skilllv,1); + skill_castcancel(src,1); + if(sd) { + int sp = skill_get_sp(sd->skillid_old,sd->skilllv_old); + sp = sp * (90 - (skilllv-1)*20) / 100; + if(sp < 0) sp = 0; + pc_heal(sd,0,-sp); + } + break; + case SA_SPELLBREAKER: // スペルブレイカー + { + struct status_change *sc_data = battle_get_sc_data(bl); + int sp; + if(sc_data && sc_data[SC_MAGICROD].timer != -1) { + if(dstsd) { + sp = skill_get_sp(skillid,skilllv); + sp = sp * sc_data[SC_MAGICROD].val2 / 100; + if(sp > 0x7fff) sp = 0x7fff; + else if(sp < 1) sp = 1; + if(dstsd->status.sp + sp > dstsd->status.max_sp) { + sp = dstsd->status.max_sp - dstsd->status.sp; + dstsd->status.sp = dstsd->status.max_sp; + } + else + dstsd->status.sp += sp; + clif_heal(dstsd->fd,SP_SP,sp); + } + clif_skill_nodamage(bl,bl,SA_MAGICROD,sc_data[SC_MAGICROD].val1,1); + if(sd) { + sp = sd->status.max_sp/5; + if(sp < 1) sp = 1; + pc_heal(sd,0,-sp); + } + } + else { + int bl_skillid=0,bl_skilllv=0; + if(bl->type == BL_PC) { + if(dstsd && dstsd->skilltimer != -1) { + bl_skillid = dstsd->skillid; + bl_skilllv = dstsd->skilllv; + } + } + else if(bl->type == BL_MOB) { + if(dstmd && dstmd->skilltimer != -1) { + bl_skillid = dstmd->skillid; + bl_skilllv = dstmd->skilllv; + } + } + if(bl_skillid > 0 && skill_db[bl_skillid].skill_type == BF_MAGIC) { + clif_skill_nodamage(src,bl,skillid,skilllv,1); + skill_castcancel(bl,0); + sp = skill_get_sp(bl_skillid,bl_skilllv); + if(dstsd) + pc_heal(dstsd,0,-sp); + if(sd) { + sp = sp*(25*(skilllv-1))/100; + if(skilllv > 1 && sp < 1) sp = 1; + if(sp > 0x7fff) sp = 0x7fff; + else if(sp < 1) sp = 1; + if(sd->status.sp + sp > sd->status.max_sp) { + sp = sd->status.max_sp - sd->status.sp; + sd->status.sp = sd->status.max_sp; + } + else + sd->status.sp += sp; + clif_heal(sd->fd,SP_SP,sp); + } + } + else if(sd) + clif_skill_fail(sd,skillid,0,0); + } + } + break; + case SA_MAGICROD: + if( bl->type==BL_PC && ((struct map_session_data *)bl)->special_state.no_magic_damage ) + break; + skill_status_change_start(bl,SkillStatusChangeTable[skillid],skilllv,0,0,0,skill_get_time(skillid,skilllv),0 ); + break; + case SA_AUTOSPELL: /* オートスペル */ + clif_skill_nodamage(src,bl,skillid,skilllv,1); + if(sd) + clif_autospell(sd,skilllv); + else { + int maxlv=1,spellid=0; + static const int spellarray[3] = { MG_COLDBOLT,MG_FIREBOLT,MG_LIGHTNINGBOLT }; + if(skilllv >= 10) { + spellid = MG_FROSTDIVER; + maxlv = skilllv - 9; + } + else if(skilllv >=8) { + spellid = MG_FIREBALL; + maxlv = skilllv - 7; + } + else if(skilllv >=5) { + spellid = MG_SOULSTRIKE; + maxlv = skilllv - 4; + } + else if(skilllv >=2) { + int i = rand()%3; + spellid = spellarray[i]; + maxlv = skilllv - 1; + } + else if(skilllv > 0) { + spellid = MG_NAPALMBEAT; + maxlv = 3; + } + if(spellid > 0) + skill_status_change_start(src,SC_AUTOSPELL,skilllv,spellid,maxlv,0, + skill_get_time(SA_AUTOSPELL,skilllv),0); + } + break; + + /* ランダム属性変化、水属性変化、地、火、風 */ + case NPC_ATTRICHANGE: + case NPC_CHANGEWATER: + case NPC_CHANGEGROUND: + case NPC_CHANGEFIRE: + case NPC_CHANGEWIND: + /* 毒、聖、念、闇 */ + case NPC_CHANGEPOISON: + case NPC_CHANGEHOLY: + case NPC_CHANGEDARKNESS: + case NPC_CHANGETELEKINESIS: + if(md){ + clif_skill_nodamage(src,bl,skillid,skilllv,1); + md->def_ele=skill_get_pl(skillid); + if(md->def_ele==0) /* ランダム変化、ただし、*/ + md->def_ele=rand()%10; /* 不死属性は除く */ + md->def_ele+=(1+rand()%4)*20; /* 属性レベルはランダム */ + } + break; + + case NPC_PROVOCATION: + clif_skill_nodamage(src,bl,skillid,skilllv,1); + if(md) + clif_pet_performance(src,mob_db[md->class].skill[md->skillidx].val[0]); + break; + + case NPC_HALLUCINATION: + clif_skill_nodamage(src,bl,skillid,skilllv,1); + if( bl->type==BL_PC && ((struct map_session_data *)bl)->special_state.no_magic_damage ) + break; + skill_status_change_start(bl,SkillStatusChangeTable[skillid],skilllv,0,0,0,skill_get_time(skillid,skilllv),0 ); + break; + + case NPC_KEEPING: + case NPC_BARRIER: + { + int skill_time = skill_get_time(skillid,skilllv); + clif_skill_nodamage(src,bl,skillid,skilllv,1); + skill_status_change_start(bl,SkillStatusChangeTable[skillid],skilllv,0,0,0,skill_time,0 ); + mob_changestate((struct mob_data *)src,MS_DELAY,skill_time); + } + break; + + case NPC_DARKBLESSING: + { + int sc_def = 100 - battle_get_mdef(bl); + clif_skill_nodamage(src,bl,skillid,skilllv,1); + if( bl->type==BL_PC && ((struct map_session_data *)bl)->special_state.no_magic_damage ) + break; + if(battle_get_elem_type(bl) == 7 || battle_get_race(bl) == 6) + break; + if(rand()%100 < sc_def*(50+skilllv*5)/100) { + if(dstsd) { + int hp = battle_get_hp(bl)-1; + pc_heal(dstsd,-hp,0); + } + else if(dstmd) + dstmd->hp = 1; + } + } + break; + + case NPC_SELFDESTRUCTION: /* 自爆 */ + case NPC_SELFDESTRUCTION2: /* 自爆2 */ + skill_status_change_start(bl,SkillStatusChangeTable[skillid],skilllv,skillid,0,0,skill_get_time(skillid,skilllv),0); + break; + case NPC_LICK: + clif_skill_nodamage(src,bl,skillid,skilllv,1); + if( bl->type==BL_PC && ((struct map_session_data *)bl)->special_state.no_weapon_damage ) + break; + if(dstsd) + pc_heal(dstsd,0,-100); + if(rand()%100 < (skilllv*5)*sc_def_vit/100) + skill_status_change_start(bl,SC_STAN,skilllv,0,0,0,skill_get_time2(skillid,skilllv),0); + break; + + case NPC_SUICIDE: /* 自決 */ + if(src && bl && md){ + clif_skill_nodamage(src,bl,skillid,skilllv,1); + mob_damage(NULL,md,md->hp,0); + } + break; + + case NPC_SUMMONSLAVE: /* 手下召喚 */ + case NPC_SUMMONMONSTER: /* MOB召喚 */ + if(md && !md->master_id){ + mob_summonslave(md,mob_db[md->class].skill[md->skillidx].val,skilllv,(skillid==NPC_SUMMONSLAVE)?1:0); + } + break; + + case NPC_TRANSFORMATION: + case NPC_METAMORPHOSIS: + if(md) + mob_class_change(md,mob_db[md->class].skill[md->skillidx].val); + break; + + case NPC_EMOTION: /* エモーション */ + if(md) + clif_emotion(&md->bl,mob_db[md->class].skill[md->skillidx].val[0]); + break; + + case NPC_DEFENDER: + clif_skill_nodamage(src,bl,skillid,skilllv,1); + break; + + case WE_MALE: /* 君だけは護るよ */ + if(sd && dstsd){ + int hp_rate=(skilllv <= 0)? 0:skill_db[skillid].hp_rate[skilllv-1]; + int gain_hp=sd->status.max_hp*abs(hp_rate)/100;// 15% + clif_skill_nodamage(src,bl,skillid,gain_hp,1); + battle_heal(NULL,bl,gain_hp,0,0); + } + break; + case WE_FEMALE: /* あなたの為に犠牲になります */ + if(sd && dstsd){ + int sp_rate=(skilllv <= 0)? 0:skill_db[skillid].sp_rate[skilllv-1]; + int gain_sp=sd->status.max_sp*abs(sp_rate)/100;// 15% + clif_skill_nodamage(src,bl,skillid,gain_sp,1); + battle_heal(NULL,bl,0,gain_sp,0); + } + break; + + case WE_CALLPARTNER: /* あなたに会いたい */ + if(sd && dstsd){ + if(map[sd->bl.m].flag.nomemo){ + clif_skill_teleportmessage(sd,1); + return 0; + } + if((dstsd = pc_get_partner(sd)) == NULL){ + clif_skill_fail(sd,skillid,0,0); + return 0; + } + skill_unitsetting(src,skillid,skilllv,sd->bl.x,sd->bl.y,0); + } + break; + + case PF_HPCONVERSION: /* ライフ置き換え */ + clif_skill_nodamage(src,bl,skillid,skilllv,1); + if(sd){ + int conv_hp=0,conv_sp=0; + conv_hp=sd->status.hp/10; //基本はHPの10% + sd->status.hp -= conv_hp; //HPを減らす + conv_sp=conv_hp*20*skilllv/100; + conv_sp=(sd->status.sp+conv_sp>sd->status.max_sp)?sd->status.max_sp-sd->status.sp:conv_sp; + sd->status.sp += conv_sp; //SPを増やす + pc_heal(sd,-conv_hp,conv_sp); + clif_heal(sd->fd,SP_SP,conv_sp); + } + break; + case HT_REMOVETRAP: /* リムーブトラップ */ + clif_skill_nodamage(src,bl,skillid,skilllv,1); + { + struct skill_unit *su=NULL; + struct item item_tmp; + int flag; + if((bl->type==BL_SKILL) && + (su=(struct skill_unit *)bl) && + (su->group->src_id == src->id || map[bl->m].flag.pvp || map[bl->m].flag.gvg) && + (su->group->unit_id >= 0x8f && su->group->unit_id <= 0x99) && + (su->group->unit_id != 0x92)){ //罠を取り返す + if(sd){ + if(battle_config.skill_removetrap_type == 1){ + for(i=0;i<10;i++) { + if(skill_db[su->group->skill_id].itemid[i] > 0){ + memset(&item_tmp,0,sizeof(item_tmp)); + item_tmp.nameid = skill_db[su->group->skill_id].itemid[i]; + item_tmp.identify = 1; + if(item_tmp.nameid && (flag=pc_additem(sd,&item_tmp,skill_db[su->group->skill_id].amount[i]))){ + clif_additem(sd,0,0,flag); + map_addflooritem(&item_tmp,skill_db[su->group->skill_id].amount[i],sd->bl.m,sd->bl.x,sd->bl.y,NULL,NULL,NULL,0); + } + } + } + }else{ + memset(&item_tmp,0,sizeof(item_tmp)); + item_tmp.nameid = 1065; + item_tmp.identify = 1; + if(item_tmp.nameid && (flag=pc_additem(sd,&item_tmp,1))){ + clif_additem(sd,0,0,flag); + map_addflooritem(&item_tmp,1,sd->bl.m,sd->bl.x,sd->bl.y,NULL,NULL,NULL,0); + } + } + + } + if(su->group->unit_id == 0x91 && su->group->val2){ + struct block_list *target=map_id2bl(su->group->val2); + if(target && (target->type == BL_PC || target->type == BL_MOB)) + skill_status_change_end(target,SC_ANKLE,-1); + } + skill_delunit(su); + } + } + break; + case HT_SPRINGTRAP: /* スプリングトラップ */ + clif_skill_nodamage(src,bl,skillid,skilllv,1); + { + struct skill_unit *su=NULL; + if((bl->type==BL_SKILL) && (su=(struct skill_unit *)bl) && (su->group) ){ + switch(su->group->unit_id){ + case 0x8f: /* ブラストマイン */ + case 0x90: /* スキッドトラップ */ + case 0x93: /* ランドマイン */ + case 0x94: /* ショックウェーブトラップ */ + case 0x95: /* サンドマン */ + case 0x96: /* フラッシャー */ + case 0x97: /* フリージングトラップ */ + case 0x98: /* クレイモアートラップ */ + case 0x99: /* トーキーボックス */ + su->group->unit_id = 0x8c; + clif_changelook(bl,LOOK_BASE,su->group->unit_id); + su->group->limit=DIFF_TICK(tick+1500,su->group->tick); + su->limit=DIFF_TICK(tick+1500,su->group->tick); + } + } + } + break; + case BD_ENCORE: /* アンコール */ + clif_skill_nodamage(src,bl,skillid,skilllv,1); + if(sd) + skill_use_id(sd,src->id,sd->skillid_dance,sd->skilllv_dance); + break; + case AS_SPLASHER: /* ベナムスプラッシャー */ + if((double)battle_get_max_hp(bl)*2/3 < battle_get_hp(bl)) //HPが2/3以上残っていたら失敗 + return 1; + clif_skill_nodamage(src,bl,skillid,skilllv,1); + skill_status_change_start(bl,SkillStatusChangeTable[skillid],skilllv,skillid,src->id,0,skill_get_time(skillid,skilllv),0 ); + break; + case PF_MINDBREAKER: /* プロボック */ + { + struct status_change *sc_data = battle_get_sc_data(bl); + + /* MVPmobと不死には効かない */ + if((bl->type==BL_MOB && battle_get_mode(bl)&0x20) || battle_check_undead(battle_get_race(bl),battle_get_elem_type(bl))) //不死には効かない + { + map_freeblock_unlock(); + return 1; + } + + clif_skill_nodamage(src,bl,skillid,skilllv,1); + skill_status_change_start(bl,SkillStatusChangeTable[skillid],skilllv,0,0,0,skill_get_time(skillid,skilllv),0 ); + + if(dstmd && dstmd->skilltimer!=-1 && dstmd->state.skillcastcancel) // 詠唱妨害 + skill_castcancel(bl,0); + if(dstsd && dstsd->skilltimer!=-1 && (!dstsd->special_state.no_castcancel || map[bl->m].flag.gvg) + && dstsd->state.skillcastcancel && !dstsd->special_state.no_castcancel2) + skill_castcancel(bl,0); + + if(sc_data){ + if(sc_data[SC_FREEZE].timer!=-1) + skill_status_change_end(bl,SC_FREEZE,-1); + if(sc_data[SC_STONE].timer!=-1 && sc_data[SC_STONE].val2==0) + skill_status_change_end(bl,SC_STONE,-1); + if(sc_data[SC_SLEEP].timer!=-1) + skill_status_change_end(bl,SC_SLEEP,-1); + } + + if(bl->type==BL_MOB) { + int range = skill_get_range(skillid,skilllv); + if(range < 0) + range = battle_get_range(src) - (range + 1); + mob_target((struct mob_data *)bl,src,range); + } + } + break; + + + + + + + case RG_CLEANER: //AppleGirl + clif_skill_nodamage(src,bl,skillid,skilllv,1); + { + struct skill_unit *su=NULL; + if((bl->type==BL_SKILL) && + (su=(struct skill_unit *)bl) && + (su->group->src_id == src->id || map[bl->m].flag.pvp || map[bl->m].flag.gvg) && + (su->group->unit_id == 0xb0)){ //罠を取り返す + if(sd) + skill_delunit(su); + } + } + break; + default: + printf("Unknown skill used:%d\n",skillid); + map_freeblock_unlock(); + return 1; + } + + map_freeblock_unlock(); + return 0; +} + +/*========================================== + * スキル使用(詠唱完了、ID指定) + *------------------------------------------ + */ +int skill_castend_id( int tid, unsigned int tick, int id,int data ) +{ + struct map_session_data* sd = map_id2sd(id)/*,*target_sd=NULL*/; + struct block_list *bl; + int range,inf2; + + nullpo_retr(0, sd); + + if( sd->bl.prev == NULL ) //prevが無いのはありなの? + return 0; + + if(sd->skillid != SA_CASTCANCEL && sd->skilltimer != tid ) /* タイマIDの確認 */ + return 0; + if(sd->skillid != SA_CASTCANCEL && sd->skilltimer != -1 && pc_checkskill(sd,SA_FREECAST) > 0) { + sd->speed = sd->prev_speed; + clif_updatestatus(sd,SP_SPEED); + } + if(sd->skillid != SA_CASTCANCEL) + sd->skilltimer=-1; + + if((bl=map_id2bl(sd->skilltarget))==NULL || bl->prev==NULL) { + sd->canact_tick = tick; + sd->canmove_tick = tick; + sd->skillitem = sd->skillitemlv = -1; + return 0; + } + if(sd->bl.m != bl->m || pc_isdead(sd)) { //マップが違うか自分が死んでいる + sd->canact_tick = tick; + sd->canmove_tick = tick; + sd->skillitem = sd->skillitemlv = -1; + return 0; + } + + if(sd->skillid == PR_LEXAETERNA) { + struct status_change *sc_data = battle_get_sc_data(bl); + if(sc_data && (sc_data[SC_FREEZE].timer != -1 || (sc_data[SC_STONE].timer != -1 && sc_data[SC_STONE].val2 == 0))) { + clif_skill_fail(sd,sd->skillid,0,0); + sd->canact_tick = tick; + sd->canmove_tick = tick; + sd->skillitem = sd->skillitemlv = -1; + return 0; + } + } + else if(sd->skillid == RG_BACKSTAP) { + int dir = map_calc_dir(&sd->bl,bl->x,bl->y),t_dir = battle_get_dir(bl); + int dist = distance(sd->bl.x,sd->bl.y,bl->x,bl->y); + if(bl->type != BL_SKILL && (dist == 0 || map_check_dir(dir,t_dir))) { + clif_skill_fail(sd,sd->skillid,0,0); + sd->canact_tick = tick; + sd->canmove_tick = tick; + sd->skillitem = sd->skillitemlv = -1; + return 0; + } + } + + inf2 = skill_get_inf2(sd->skillid); + if( ( (skill_get_inf(sd->skillid)&1) || inf2&4 ) && // 彼我敵対関係チェック + battle_check_target(&sd->bl,bl, BCT_ENEMY)<=0 ) { + sd->canact_tick = tick; + sd->canmove_tick = tick; + sd->skillitem = sd->skillitemlv = -1; + return 0; + } + if(inf2 & 0xC00 && sd->bl.id != bl->id) { + int fail_flag = 1; + if(inf2 & 0x400 && battle_check_target(&sd->bl,bl, BCT_PARTY) > 0) + fail_flag = 0; + if(inf2 & 0x800 && sd->status.guild_id > 0 && sd->status.guild_id == battle_get_guild_id(bl)) + fail_flag = 0; + if(fail_flag) { + clif_skill_fail(sd,sd->skillid,0,0); + sd->canact_tick = tick; + sd->canmove_tick = tick; + sd->skillitem = sd->skillitemlv = -1; + return 0; + } + } + + range = skill_get_range(sd->skillid,sd->skilllv); + if(range < 0) + range = battle_get_range(&sd->bl) - (range + 1); + range += battle_config.pc_skill_add_range; + if((sd->skillid == MO_EXTREMITYFIST && sd->sc_data[SC_COMBO].timer != -1 && sd->sc_data[SC_COMBO].val1 == MO_COMBOFINISH) || + (sd->skillid == CH_TIGERFIST && sd->sc_data[SC_COMBO].timer != -1 && sd->sc_data[SC_COMBO].val1 == MO_COMBOFINISH) || + (sd->skillid == CH_CHAINCRUSH && sd->sc_data[SC_COMBO].timer != -1 && sd->sc_data[SC_COMBO].val1 == MO_COMBOFINISH) || + (sd->skillid == CH_CHAINCRUSH && sd->sc_data[SC_COMBO].timer != -1 && sd->sc_data[SC_COMBO].val1 == CH_TIGERFIST)) + range += skill_get_blewcount(MO_COMBOFINISH,sd->sc_data[SC_COMBO].val2); + if(battle_config.skill_out_range_consume) { // changed to allow casting when target walks out of range [Valaris] + if(range < distance(sd->bl.x,sd->bl.y,bl->x,bl->y)) { + clif_skill_fail(sd,sd->skillid,0,0); + sd->canact_tick = tick; + sd->canmove_tick = tick; + sd->skillitem = sd->skillitemlv = -1; + return 0; + } + } + if(!skill_check_condition(sd,1)) { /* 使用条件チェック */ + sd->canact_tick = tick; + sd->canmove_tick = tick; + sd->skillitem = sd->skillitemlv = -1; + return 0; + } + sd->skillitem = sd->skillitemlv = -1; + if(battle_config.skill_out_range_consume) { + if(range < distance(sd->bl.x,sd->bl.y,bl->x,bl->y)) { + clif_skill_fail(sd,sd->skillid,0,0); + sd->canact_tick = tick; + sd->canmove_tick = tick; + return 0; + } + } + + if(battle_config.pc_skill_log) + printf("PC %d skill castend skill=%d\n",sd->bl.id,sd->skillid); + pc_stop_walking(sd,0); + + switch( skill_get_nk(sd->skillid) ) + { + /* 攻撃系/吹き飛ばし系 */ + case 0: case 2: + skill_castend_damage_id(&sd->bl,bl,sd->skillid,sd->skilllv,tick,0); + break; + case 1:/* 支援系 */ + if( (sd->skillid==AL_HEAL || (sd->skillid==ALL_RESURRECTION && bl->type != BL_PC) || sd->skillid==PR_ASPERSIO) && battle_check_undead(battle_get_race(bl),battle_get_elem_type(bl))) + skill_castend_damage_id(&sd->bl,bl,sd->skillid,sd->skilllv,tick,0); + else + skill_castend_nodamage_id(&sd->bl,bl,sd->skillid,sd->skilllv,tick,0); + break; + } + + return 0; +} + +/*========================================== + * スキル使用(詠唱完了、場所指定の実際の処理) + *------------------------------------------ + */ +int skill_castend_pos2( struct block_list *src, int x,int y,int skillid,int skilllv,unsigned int tick,int flag) +{ + struct map_session_data *sd=NULL; + int i,tmpx = 0,tmpy = 0, x1 = 0, y1 = 0; + + nullpo_retr(0, src); + + if(src->type==BL_PC){ + nullpo_retr(0, sd=(struct map_session_data *)src); + } + if( skillid != WZ_METEOR && + skillid != WZ_SIGHTRASHER && + skillid != AM_CANNIBALIZE && + skillid != AM_SPHEREMINE) + clif_skill_poseffect(src,skillid,skilllv,x,y,tick); + + if (skillnotok(skillid, sd)) // [MouseJstr] + return 0; + + switch(skillid) + { + case PR_BENEDICTIO: /* 聖体降福 */ + skill_area_temp[1]=src->id; + map_foreachinarea(skill_area_sub, + src->m,x-1,y-1,x+1,y+1,0, + src,skillid,skilllv,tick, flag|BCT_NOENEMY|1, + skill_castend_nodamage_id); + map_foreachinarea(skill_area_sub, + src->m,x-1,y-1,x+1,y+1,0, + src,skillid,skilllv,tick, flag|BCT_ENEMY|1, + skill_castend_damage_id); + break; + + case BS_HAMMERFALL: /* ハンマーフォール */ + skill_area_temp[1]=src->id; + skill_area_temp[2]=x; + skill_area_temp[3]=y; + map_foreachinarea(skill_area_sub, + src->m,x-2,y-2,x+2,y+2,0, + src,skillid,skilllv,tick, flag|BCT_ENEMY|2, + skill_castend_nodamage_id); + break; + + case HT_DETECTING: /* ディテクティング */ + { + const int range=7; + map_foreachinarea( skill_status_change_timer_sub, + src->m, src->x-range, src->y-range, src->x+range,src->y+range,0, + src,SC_SIGHT,tick); + } + break; + + case MG_SAFETYWALL: /* セイフティウォール */ + case MG_FIREWALL: /* ファイヤーウォール */ + case MG_THUNDERSTORM: /* サンダーストーム */ + case AL_PNEUMA: /* ニューマ */ + case WZ_ICEWALL: /* アイスウォール */ + case WZ_FIREPILLAR: /* ファイアピラー */ + case WZ_SIGHTRASHER: + case WZ_QUAGMIRE: /* クァグマイア */ + case WZ_VERMILION: /* ロードオブヴァーミリオン */ + case WZ_FROSTNOVA: /* フロストノヴァ */ + case WZ_STORMGUST: /* ストームガスト */ + case WZ_HEAVENDRIVE: /* ヘヴンズドライブ */ + case PR_SANCTUARY: /* サンクチュアリ */ + case PR_MAGNUS: /* マグヌスエクソシズム */ + case CR_GRANDCROSS: /* グランドクロス */ + case HT_SKIDTRAP: /* スキッドトラップ */ + case HT_LANDMINE: /* ランドマイン */ + case HT_ANKLESNARE: /* アンクルスネア */ + case HT_SHOCKWAVE: /* ショックウェーブトラップ */ + case HT_SANDMAN: /* サンドマン */ + case HT_FLASHER: /* フラッシャー */ + case HT_FREEZINGTRAP: /* フリージングトラップ */ + case HT_BLASTMINE: /* ブラストマイン */ + case HT_CLAYMORETRAP: /* クレイモアートラップ */ + case AS_VENOMDUST: /* ベノムダスト */ + case AM_DEMONSTRATION: /* デモンストレーション */ + case PF_SPIDERWEB: /* スパイダーウェッブ */ + case PF_FOGWALL: /* フォグウォール */ + case HT_TALKIEBOX: /* トーキーボックス */ + skill_unitsetting(src,skillid,skilllv,x,y,0); + break; + + case RG_GRAFFITI: /* Graffiti [Valaris] */ + skill_clear_unitgroup(src); + skill_unitsetting(src,skillid,skilllv,x,y,0); + break; + + case SA_VOLCANO: /* ボルケーノ */ + case SA_DELUGE: /* デリュージ */ + case SA_VIOLENTGALE: /* バイオレントゲイル */ + case SA_LANDPROTECTOR: /* ランドプロテクター */ + skill_clear_element_field(src);//既に自分が発動している属性場をクリア + skill_unitsetting(src,skillid,skilllv,x,y,0); + break; + + case WZ_METEOR: //メテオストーム + { + int flag=0; + for(i=0;i<2+(skilllv>>1);i++) { + int j=0, c; + do { + tmpx = x + (rand()%7 - 3); + tmpy = y + (rand()%7 - 3); + if(tmpx < 0) + tmpx = 0; + else if(tmpx >= map[src->m].xs) + tmpx = map[src->m].xs - 1; + if(tmpy < 0) + tmpy = 0; + else if(tmpy >= map[src->m].ys) + tmpy = map[src->m].ys - 1; + j++; + } while(((c=map_getcell(src->m,tmpx,tmpy))==1 || c==5) && j<100); + if(j >= 100) + continue; + if(flag==0){ + clif_skill_poseffect(src,skillid,skilllv,tmpx,tmpy,tick); + flag=1; + } + if(i > 0) + skill_addtimerskill(src,tick+i*1000,0,tmpx,tmpy,skillid,skilllv,(x1<<16)|y1,flag); + x1 = tmpx; + y1 = tmpy; + } + skill_addtimerskill(src,tick+i*1000,0,tmpx,tmpy,skillid,skilllv,-1,flag); + } + break; + + case AL_WARP: /* ワープポータル */ + if(sd) { + if(map[sd->bl.m].flag.noteleport) /* テレポ禁止 */ + break; + clif_skill_warppoint(sd,sd->skillid,sd->status.save_point.map, + (sd->skilllv>1)?sd->status.memo_point[0].map:"", + (sd->skilllv>2)?sd->status.memo_point[1].map:"", + (sd->skilllv>3)?sd->status.memo_point[2].map:""); + } + break; + case MO_BODYRELOCATION: + if(sd){ + pc_movepos(sd,x,y); + }else if( src->type==BL_MOB ) + mob_warp((struct mob_data *)src,-1,x,y,0); + break; + case AM_CANNIBALIZE: // バイオプラント + if(sd){ + int mx,my,id=0; + struct mob_data *md; + + mx = x;// + (rand()%10 - 5); + my = y;// + (rand()%10 - 5); + id=mob_once_spawn(sd,"this",mx,my,"--ja--",1118,1,""); + if( (md=(struct mob_data *)map_id2bl(id)) !=NULL ){ + md->master_id=sd->bl.id; + md->hp=2210+skilllv*200; + md->state.special_mob_ai=1; + md->deletetimer=add_timer(gettick()+skill_get_time(skillid,skilllv),mob_timer_delete,id,0); + } + clif_skill_poseffect(src,skillid,skilllv,x,y,tick); + } + break; + case AM_SPHEREMINE: // スフィアーマイン + if(sd){ + int mx,my,id=0; + struct mob_data *md; + + mx = x;// + (rand()%10 - 5); + my = y;// + (rand()%10 - 5); + id=mob_once_spawn(sd,"this",mx,my,"--ja--",1142,1,""); + if( (md=(struct mob_data *)map_id2bl(id)) !=NULL ){ + md->master_id=sd->bl.id; + md->hp=1000+skilllv*200; + md->state.special_mob_ai=2; + md->deletetimer=add_timer(gettick()+skill_get_time(skillid,skilllv),mob_timer_delete,id,0); + } + clif_skill_poseffect(src,skillid,skilllv,x,y,tick); + } + break; + } + + return 0; +} + +/*========================================== + * スキル使用(詠唱完了、map指定) + *------------------------------------------ + */ +int skill_castend_map( struct map_session_data *sd,int skill_num, const char *map) +{ + int x=0,y=0; + + nullpo_retr(0, sd); + if( sd->bl.prev == NULL || pc_isdead(sd) ) + return 0; + + if( sd->opt1>0 || sd->status.option&2 ) + return 0; + //スキルが使えない状態異常中 + if(sd->sc_data){ + if( sd->sc_data[SC_DIVINA].timer!=-1 || + sd->sc_data[SC_ROKISWEIL].timer!=-1 || + sd->sc_data[SC_AUTOCOUNTER].timer != -1 || + sd->sc_data[SC_STEELBODY].timer != -1 || + sd->sc_data[SC_DANCING].timer!=-1 || + sd->sc_data[SC_BERSERK].timer != -1 ) + return 0; + } + + if( skill_num != sd->skillid) /* 不正パケットらしい */ + return 0; + + pc_stopattack(sd); + + if(battle_config.pc_skill_log) + printf("PC %d skill castend skill =%d map=%s\n",sd->bl.id,skill_num,map); + pc_stop_walking(sd,0); + + if(strcmp(map,"cancel")==0) + return 0; + + switch(skill_num){ + case AL_TELEPORT: /* テレポート */ + if(strcmp(map,"Random")==0) + pc_randomwarp(sd,3); + else + pc_setpos(sd,sd->status.save_point.map, + sd->status.save_point.x,sd->status.save_point.y,3); + break; + + case AL_WARP: /* ワープポータル */ + { + const struct point *p[]={ + &sd->status.save_point,&sd->status.memo_point[0], + &sd->status.memo_point[1],&sd->status.memo_point[2], + }; + struct skill_unit_group *group; + int i; + int maxcount=0; + + if((maxcount = skill_get_maxcount(sd->skillid)) > 0) { + int c; + for(i=c=0;i<MAX_SKILLUNITGROUP;i++) { + if(sd->skillunit[i].alive_count > 0 && sd->skillunit[i].skill_id == sd->skillid) + c++; + } + if(c >= maxcount) { + clif_skill_fail(sd,sd->skillid,0,0); + sd->canact_tick = gettick(); + sd->canmove_tick = gettick(); + sd->skillitem = sd->skillitemlv = -1; + return 0; + } + } + + for(i=0;i<sd->skilllv;i++){ + if(strcmp(map,p[i]->map)==0){ + x=p[i]->x; + y=p[i]->y; + break; + } + } + if(x==0 || y==0) /* 不正パケット? */ + return 0; + + if(!skill_check_condition(sd,3)) + return 0; + if((group=skill_unitsetting(&sd->bl,sd->skillid,sd->skilllv,sd->skillx,sd->skilly,0))==NULL) + return 0; + group->valstr=(char *)aCalloc(24,sizeof(char)); + memcpy(group->valstr,map,24); + group->val2=(x<<16)|y; + } + break; + } + + return 0; +} + +/*========================================== + * スキルユニット設定処理 + *------------------------------------------ + */ +struct skill_unit_group *skill_unitsetting( struct block_list *src, int skillid,int skilllv,int x,int y,int flag) +{ + struct skill_unit_group *group; + int i,count=1,limit=10000,val1=0,val2=0; + int target=BCT_ENEMY,interval=1000,range=0; + int dir=0,aoe_diameter=0; // -- aoe_diameter (moonsoul) added for sage Area Of Effect skills + + nullpo_retr(0, src); + + switch(skillid){ /* 設定 */ + + case MG_SAFETYWALL: /* セイフティウォール */ + limit=skill_get_time(skillid,skilllv); + val2=skilllv+1; + interval = -1; + target=(battle_config.defnotenemy)?BCT_NOENEMY:BCT_ALL; + break; + + case MG_FIREWALL: /* ファイヤーウォール */ + if(src->x == x && src->y == y) + dir = 2; + else + dir=map_calc_dir(src,x,y); + if(dir&1) count=5; + else count=3; + limit=skill_get_time(skillid,skilllv); + val2=4+skilllv; + interval=1; + break; + + case AL_PNEUMA: /* ニューマ */ + limit=skill_get_time(skillid,skilllv); + interval = -1; + target=(battle_config.defnotenemy)?BCT_NOENEMY:BCT_ALL; + count = 9; + break; + + case AL_WARP: /* ワープポータル */ + target=BCT_ALL; + val1=skilllv+6; + if(flag==0) + limit=2000; + else + limit=skill_get_time(skillid,skilllv); + break; + + case PR_SANCTUARY: /* サンクチュアリ */ + count=21; + limit=skill_get_time(skillid,skilllv); + val1=skilllv+3; + val2=(skilllv>6)?777:skilllv*100; + target=BCT_ALL; + range=1; + break; + + case PR_MAGNUS: /* マグヌスエクソシズム */ + count=33; + limit=skill_get_time(skillid,skilllv); + interval=3000; + break; + + case WZ_FIREPILLAR: /* ファイアーピラー */ + if(flag==0) + limit=skill_get_time(skillid,skilllv); + else + limit=1000; + interval=2000; + val1=skilllv+2; + range=1; + break; + + case MG_THUNDERSTORM: /* サンダーストーム */ + limit=500; + range=1; + break; + + case WZ_FROSTNOVA: /* フロストノヴァ */ + limit=500; + range=5; + break; + case WZ_HEAVENDRIVE: /* ヘヴンズドライブ */ + limit=500; + range=2; + break; + + case WZ_METEOR: /* メテオストーム */ + limit=500; + range=3; + break; + + case WZ_SIGHTRASHER: + limit=500; + count=41; + break; + + case WZ_VERMILION: /* ロードオブヴァーミリオン */ + limit=4100; + interval=1000; + range=6; + break; + + case WZ_ICEWALL: /* アイスウォール */ + limit=skill_get_time(skillid,skilllv); + count=5; + break; + + case WZ_STORMGUST: /* ストームガスト */ + limit=4600; + interval=450; + range=5; + break; + + case WZ_QUAGMIRE: /* クァグマイア */ + limit=skill_get_time(skillid,skilllv); + interval=200; + count=25; + break; + + case HT_SKIDTRAP: /* スキッドトラップ */ + case HT_LANDMINE: /* ランドマイン */ + case HT_ANKLESNARE: /* アンクルスネア */ + case HT_SANDMAN: /* サンドマン */ + case PF_SPIDERWEB: /* スパイダーウェッブ */ + case HT_FLASHER: /* フラッシャー */ + case HT_FREEZINGTRAP: /* フリージングトラップ */ + case HT_BLASTMINE: /* ブラストマイン */ + case HT_CLAYMORETRAP: /* クレイモアートラップ */ + limit=skill_get_time(skillid,skilllv); + range=1; + break; + + case HT_TALKIEBOX: /* トーキーボックス */ + limit=skill_get_time(skillid,skilllv); + range=1; + target=BCT_ALL; + break; + + case HT_SHOCKWAVE: /* ショックウェーブトラップ */ + limit=skill_get_time(skillid,skilllv); + range=1; + val1=skilllv*15+10; + break; + + case AS_VENOMDUST: /* ベノムダスト */ + limit=skill_get_time(skillid,skilllv); + interval=1000; + count=5; + break; + + case CR_GRANDCROSS: /* グランドクロス */ + count=29; + limit=1000; + interval=300; + break; + + case SA_VOLCANO: /* ボルケーノ */ + case SA_DELUGE: /* デリュージ */ + case SA_VIOLENTGALE: /* バイオレントゲイル */ + limit=skill_get_time(skillid,skilllv); + count=skilllv<=2?25:(skilllv<=4?49:81); + target=BCT_ALL; + break; + + case SA_LANDPROTECTOR: /* グランドクロス */ + limit=skill_get_time(skillid,skilllv); // changed to get duration from cast_db (moonsoul) + val1=skilllv*15+10; + aoe_diameter=skilllv+skilllv%2+5; + target=BCT_ALL; + count=aoe_diameter*aoe_diameter; // -- this will not function if changed to ^2 (moonsoul) + break; + + case BD_LULLABY: /* 子守唄 */ + case BD_ETERNALCHAOS: /* エターナルカオス */ + case BD_ROKISWEIL: /* ロキの叫び */ + count=81; + limit=skill_get_time(skillid,skilllv); + range=5; + target=BCT_ALL; + break; + case BD_RICHMANKIM: + case BD_DRUMBATTLEFIELD: /* 戦太鼓の響き */ + case BD_RINGNIBELUNGEN: /* ニーベルングの指輪 */ + case BD_INTOABYSS: /* 深淵の中に */ + case BD_SIEGFRIED: /* 不死身のジークフリード */ + count=81; + limit=skill_get_time(skillid,skilllv); + range=5; + target=BCT_PARTY; + break; + + case BA_WHISTLE: /* 口笛 */ + count=49; + limit=skill_get_time(skillid,skilllv); + range=5; + target=BCT_NOENEMY; + if(src->type == BL_PC) + val1 = (pc_checkskill((struct map_session_data *)src,BA_MUSICALLESSON)+1)>>1; + val2 = ((battle_get_agi(src)/10)&0xffff)<<16; + val2 |= (battle_get_luk(src)/10)&0xffff; + break; + case DC_HUMMING: /* ハミング */ + count=49; + limit=skill_get_time(skillid,skilllv); + range=5; + target=BCT_NOENEMY; + if(src->type == BL_PC) + val1 = (pc_checkskill((struct map_session_data *)src,DC_DANCINGLESSON)+1)>>1; + val2 = battle_get_dex(src)/10; + break; + + case BA_DISSONANCE: /* 不協和音 */ + case DC_UGLYDANCE: /* 自分勝手なダンス */ + count=49; + limit=skill_get_time(skillid,skilllv); + range=5; + target=BCT_ENEMY; + break; + + case DC_DONTFORGETME: /* 私を忘れないで… */ + count=49; + limit=skill_get_time(skillid,skilllv); + range=5; + target=BCT_ENEMY; + if(src->type == BL_PC) + val1 = (pc_checkskill((struct map_session_data *)src,DC_DANCINGLESSON)+1)>>1; + val2 = ((battle_get_str(src)/20)&0xffff)<<16; + val2 |= (battle_get_agi(src)/10)&0xffff; + break; + case BA_POEMBRAGI: /* ブラギの詩 */ + count=49; + limit=skill_get_time(skillid,skilllv); + range=5; + target=BCT_NOENEMY; + if(src->type == BL_PC) + val1 = pc_checkskill((struct map_session_data *)src,BA_MUSICALLESSON); + val2 = ((battle_get_dex(src)/10)&0xffff)<<16; + val2 |= (battle_get_int(src)/5)&0xffff; + break; + case BA_APPLEIDUN: /* イドゥンの林檎 */ + count=49; + limit=skill_get_time(skillid,skilllv); + range=5; + target=BCT_NOENEMY; + if(src->type == BL_PC) + val1 = ((pc_checkskill((struct map_session_data *)src,BA_MUSICALLESSON))&0xffff)<<16; + else + val1 = 0; + val1 |= (battle_get_vit(src))&0xffff; + val2 = 0;//回復用タイムカウンタ(6秒毎に1増加) + break; + case DC_SERVICEFORYOU: /* サービスフォーユー */ + count=49; + limit=skill_get_time(skillid,skilllv); + range=5; + target=BCT_PARTY; + if(src->type == BL_PC) + val1 = (pc_checkskill((struct map_session_data *)src,DC_DANCINGLESSON)+1)>>1; + val2 = battle_get_int(src)/10; + break; + case BA_ASSASSINCROSS: /* 夕陽のアサシンクロス */ + count=49; + limit=skill_get_time(skillid,skilllv); + range=5; + target=BCT_NOENEMY; + if(src->type == BL_PC) + val1 = (pc_checkskill((struct map_session_data *)src,BA_MUSICALLESSON)+1)>>1; + val2 = battle_get_agi(src)/20; + break; + case DC_FORTUNEKISS: /* 幸運のキス */ + count=49; + limit=skill_get_time(skillid,skilllv); + range=5; + target=BCT_NOENEMY; + if(src->type == BL_PC) + val1 = (pc_checkskill((struct map_session_data *)src,DC_DANCINGLESSON)+1)>>1; + val2 = battle_get_luk(src)/10; + break; + case AM_DEMONSTRATION: /* デモンストレーション */ + limit=skill_get_time(skillid,skilllv); + interval=1000; + range=1; + target=BCT_ENEMY; + break; + case WE_CALLPARTNER: /* あなたに逢いたい */ + limit=skill_get_time(skillid,skilllv); + range=-1; + break; + + case HP_BASILICA: /* バジリカ */ + limit=skill_get_time(skillid,skilllv); + target=BCT_ALL; + range=3; + //Fix to prevent the priest from walking while Basilica is up. + battle_stopwalking(src,1); + skill_status_change_start(src,SC_ANKLE,skilllv,0,0,0,limit,0); + break; + case PA_GOSPEL: /* ゴスペル */ + count=49; + target=BCT_PARTY; + limit=skill_get_time(skillid,skilllv); + break; + case PF_FOGWALL: /* フォグウォール */ + count=15; + limit=skill_get_time(skillid,skilllv); + break; + case RG_GRAFFITI: /* Graffiti */ + count=1; // Leave this at 1 [Valaris] + limit=600000; // Time length [Valaris] + break; + }; + + nullpo_retr(NULL, group=skill_initunitgroup(src,count,skillid,skilllv,skill_get_unit_id(skillid,flag&1))); + group->limit=limit; + group->val1=val1; + group->val2=val2; + group->target_flag=target; + group->interval=interval; + group->range=range; + if(skillid==HT_TALKIEBOX || + skillid==RG_GRAFFITI){ + group->valstr=calloc(80, 1); + if(group->valstr==NULL){ + printf("skill_castend_map: out of memory !\n"); + exit(1); + } + memcpy(group->valstr,talkie_mes,80); + } + for(i=0;i<count;i++){ + struct skill_unit *unit; + int ux=x,uy=y,val1=skilllv,val2=0,limit=group->limit,alive=1; + int range=group->range; + switch(skillid){ /* 設定 */ + case AL_PNEUMA: /* ニューマ */ + { + static const int dx[9]={-1, 0, 1,-1, 0, 1,-1, 0, 1}; + static const int dy[9]={-1,-1,-1, 0, 0, 0, 1, 1, 1}; + ux+=dx[i]; + uy+=dy[i]; + } + break; + case MG_FIREWALL: /* ファイヤーウォール */ + { + if(dir&1){ /* 斜め配置 */ + static const int dx[][5]={ + { 1,1,0,0,-1 }, { -1,-1,0,0,1 }, + },dy[][5]={ + { 1,0,0,-1,-1 }, { 1,0,0,-1,-1 }, + }; + ux+=dx[(dir>>1)&1][i]; + uy+=dy[(dir>>1)&1][i]; + }else{ /* 上下配置 */ + if(dir%4==0) /* 上下 */ + ux+=i-1; + else /* 左右 */ + uy+=i-1; + } + val2=group->val2; + } + break; + + case PR_SANCTUARY: /* サンクチュアリ */ + { + static const int dx[]={ + -1,0,1, -2,-1,0,1,2, -2,-1,0,1,2, -2,-1,0,1,2, -1,0,1 }; + static const int dy[]={ + -2,-2,-2, -1,-1,-1,-1,-1, 0,0,0,0,0, 1,1,1,1,1, 2,2,2, }; + ux+=dx[i]; + uy+=dy[i]; + } + break; + + case PR_MAGNUS: /* マグヌスエクソシズム */ + { + static const int dx[]={ -1,0,1, -1,0,1, -3,-2,-1,0,1,2,3, + -3,-2,-1,0,1,2,3, -3,-2,-1,0,1,2,3, -1,0,1, -1,0,1, }; + static const int dy[]={ + -3,-3,-3, -2,-2,-2, -1,-1,-1,-1,-1,-1,-1, + 0,0,0,0,0,0,0, 1,1,1,1,1,1,1, 2,2,2, 3,3,3 }; + ux+=dx[i]; + uy+=dy[i]; + } + break; + + case WZ_SIGHTRASHER: + { + static const int dx[]={ + -5, 0, 5, -4, 0, 4, -3, 0, 3, -2, 0, 2, -1, 0, 1, -5,-4,-3,-2,-1, 0, 1, 2, 3, 4, 5, -1, 0, 1, -2, 0, 2, -3, 0, 3, -4, 0, 4, -5, 0, 5 }; + static const int dy[]={ + -5,-5,-5, -4,-4,-4, -3,-3,-3, -2,-2,-2, -1,-1,-1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5 }; + ux+=dx[i]; + uy+=dy[i]; + } + break; + + case WZ_ICEWALL: /* アイスウォール */ + { + static const int dirx[8]={0,-1,-1,-1,0,1,1,1}; + static const int diry[8]={1,1,0,-1,-1,-1,0,1}; + if(skilllv <= 1) + val1 = 500; + else + val1 = 200 + 200*skilllv; + if(src->x == x && src->y == y) + dir = 2; + else + dir=map_calc_dir(src,x,y); + ux+=(2-i)*diry[dir]; + uy+=(i-2)*dirx[dir]; + } + break; + + case WZ_QUAGMIRE: /* クァグマイア */ + ux+=(i%5-2); + uy+=(i/5-2); + if(i==12) + range=2; + else + range=-1; + + break; + + case AS_VENOMDUST: /* ベノムダスト */ + { + static const int dx[]={-1,0,0,0,1}; + static const int dy[]={0,-1,0,1,0}; + ux+=dx[i]; + uy+=dy[i]; + } + break; + + case CR_GRANDCROSS: /* グランドクロス */ + { + static const int dx[]={ + 0, 0, -1,0,1, -2,-1,0,1,2, -4,-3,-2,-1,0,1,2,3,4, -2,-1,0,1,2, -1,0,1, 0, 0, }; + static const int dy[]={ + -4, -3, -2,-2,-2, -1,-1,-1,-1,-1, 0,0,0,0,0,0,0,0,0, 1,1,1,1,1, 2,2,2, 3, 4, }; + ux+=dx[i]; + uy+=dy[i]; + } + break; + case SA_VOLCANO: /* ボルケーノ */ + case SA_DELUGE: /* デリュージ */ + case SA_VIOLENTGALE: /* バイオレントゲイル */ + { + int u_range=0,central=0; + if(skilllv<=2){ + u_range=2; + central=12; + }else if(skilllv<=4){ + u_range=3; + central=24; + }else if(skilllv>=5){ + u_range=4; + central=40; + } + ux+=(i%(u_range*2+1)-u_range); + uy+=(i/(u_range*2+1)-u_range); + + if(i==central) + range=u_range;//中央のユニットの効果範囲は全範囲 + else + range=-1;//中央以外のユニットは飾り + } + break; + case SA_LANDPROTECTOR: /* ランドプロテクター */ + { + int u_range=0; + + if(skilllv<=2) u_range=3; + else if(skilllv<=4) u_range=4; + else if(skilllv>=5) u_range=5; + + ux+=(i%(u_range*2+1)-u_range); + uy+=(i/(u_range*2+1)-u_range); + + range=0; + } + break; + + /* ダンスなど */ + case BD_LULLABY: /* 子守歌 */ + case BD_RICHMANKIM: /* ニヨルドの宴 */ + case BD_ETERNALCHAOS: /* 永遠の混沌 */ + case BD_DRUMBATTLEFIELD:/* 戦太鼓の響き */ + case BD_RINGNIBELUNGEN: /* ニーベルングの指輪 */ + case BD_ROKISWEIL: /* ロキの叫び */ + case BD_INTOABYSS: /* 深淵の中に */ + case BD_SIEGFRIED: /* 不死身のジークフリード */ + ux+=(i%9-4); + uy+=(i/9-4); + if(i==40) + range=4; /* 中心の場合は範囲を4にオーバーライド */ + else + range=-1; /* 中心じゃない場合は範囲を-1にオーバーライド */ + break; + case BA_DISSONANCE: /* 不協和音 */ + case BA_WHISTLE: /* 口笛 */ + case BA_ASSASSINCROSS: /* 夕陽のアサシンクロス */ + case BA_POEMBRAGI: /* ブラギの詩 */ + case BA_APPLEIDUN: /* イドゥンの林檎 */ + case DC_UGLYDANCE: /* 自分勝手なダンス */ + case DC_HUMMING: /* ハミング */ + case DC_DONTFORGETME: /* 私を忘れないで… */ + case DC_FORTUNEKISS: /* 幸運のキス */ + case DC_SERVICEFORYOU: /* サービスフォーユー */ + ux+=(i%7-3); + uy+=(i/7-3); + if(i==40) + range=4; /* 中心の場合は範囲を4にオーバーライド */ + else + range=-1; /* 中心じゃない場合は範囲を-1にオーバーライド */ + break; + case PA_GOSPEL: /* ゴスペル */ + ux+=(i%7-3); + uy+=(i/7-3); + break; + case PF_FOGWALL: /* フォグウォール */ + ux+=(i%5-2); + uy+=(i/5-1); + break; + case RG_GRAFFITI: /* Graffiti [Valaris] */ + ux+=(i%5-2); + uy+=(i/5-2); + break; + } + //直上スキルの場合設置座標上にランドプロテクターがないかチェック + if(range<=0) + map_foreachinarea(skill_landprotector,src->m,ux,uy,ux,uy,BL_SKILL,skillid,&alive); + + if(skillid==WZ_ICEWALL && alive){ + val2=map_getcell(src->m,ux,uy); + if(val2==5 || val2==1) + alive=0; + else { + map_setcell(src->m,ux,uy,5); + clif_changemapcell(src->m,ux,uy,5,0); + } + } + + if(alive){ + nullpo_retr(NULL, unit=skill_initunit(group,i,ux,uy)); + unit->val1=val1; + unit->val2=val2; + unit->limit=limit; + unit->range=range; + } + } + return group; +} + +/*========================================== + * スキルユニットの発動イベント + *------------------------------------------ + */ +int skill_unit_onplace(struct skill_unit *src,struct block_list *bl,unsigned int tick) +{ + struct skill_unit_group *sg; + struct block_list *ss; + struct skill_unit_group_tickset *ts; + struct map_session_data *srcsd=NULL; + int diff,goflag,splash_count=0; + + nullpo_retr(0, src); + nullpo_retr(0, bl); + + if( bl->prev==NULL || !src->alive || (bl->type == BL_PC && pc_isdead((struct map_session_data *)bl) ) ) + return 0; + + nullpo_retr(0, sg=src->group); + nullpo_retr(0, ss=map_id2bl(sg->src_id)); + + if(ss->type == BL_PC) + nullpo_retr(0, srcsd=(struct map_session_data *)ss); + if(srcsd && srcsd->chatID) + return 0; + + if( bl->type!=BL_PC && bl->type!=BL_MOB ) + return 0; + nullpo_retr(0, ts=skill_unitgrouptickset_search( bl, sg->group_id)); + diff=DIFF_TICK(tick,ts->tick); + goflag=(diff>sg->interval || diff<0); + if (sg->skill_id == CR_GRANDCROSS && !battle_config.gx_allhit) // 重なっていたら3HITしない + goflag = (diff>sg->interval*map_count_oncell(bl->m,bl->x,bl->y) || diff<0); + + //対象がLP上に居る場合は無効 + map_foreachinarea(skill_landprotector,bl->m,bl->x,bl->y,bl->x,bl->y,BL_SKILL,0,&goflag); + + if(!goflag) + return 0; + ts->tick=tick; + ts->group_id=sg->group_id; + + switch(sg->unit_id){ + case 0x83: /* サンクチュアリ */ + { + int race=battle_get_race(bl); + int damage_flag = (battle_check_undead(race,battle_get_elem_type(bl)) || race == 6)? 1:0; + + if( battle_get_hp(bl)>=battle_get_max_hp(bl) && !damage_flag) + break; + + if((sg->val1--)<=0){ + skill_delunitgroup(sg); + return 0; + } + if(!damage_flag) { + int heal=sg->val2; + if( bl->type==BL_PC && ((struct map_session_data *)bl)->special_state.no_magic_damage) + heal=0; /* 黄金蟲カード(ヒール量0) */ + clif_skill_nodamage(&src->bl,bl,AL_HEAL,heal,1); + battle_heal(NULL,bl,heal,0,0); + } + else + skill_attack(BF_MAGIC,ss,&src->bl,bl,sg->skill_id,sg->skill_lv,tick,0); + } + break; + + case 0x84: /* マグヌスエクソシズム */ + { + int race=battle_get_race(bl); + int damage_flag = (battle_check_undead(race,battle_get_elem_type(bl)) || race == 6)? 1:0; + + if(!damage_flag) + return 0; + skill_attack(BF_MAGIC,ss,&src->bl,bl,sg->skill_id,sg->skill_lv,tick,0); + } + break; + + case 0x85: /* ニューマ */ + { + struct skill_unit *unit2; + struct status_change *sc_data=battle_get_sc_data(bl); + int type=SC_PNEUMA; + if(sc_data && sc_data[type].timer==-1) + skill_status_change_start(bl,type,sg->skill_lv,(int)src,0,0,0,0); + else if((unit2=(struct skill_unit *)sc_data[type].val2) && unit2 != src ){ + if(DIFF_TICK(sg->tick,unit2->group->tick)>0 ) + skill_status_change_start(bl,type,sg->skill_lv,(int)src,0,0,0,0); + ts->tick-=sg->interval; + } + } + break; + case 0x7e: /* セイフティウォール */ + { + struct skill_unit *unit2; + struct status_change *sc_data=battle_get_sc_data(bl); + int type=SC_SAFETYWALL; + if(sc_data && sc_data[type].timer==-1) + skill_status_change_start(bl,type,sg->skill_lv,(int)src,0,0,0,0); + else if((unit2=(struct skill_unit *)sc_data[type].val2) && unit2 != src ){ + if(sg->val1 < unit2->group->val1 ) + skill_status_change_start(bl,type,sg->skill_lv,(int)src,0,0,0,0); + ts->tick-=sg->interval; + } + } + break; + + case 0x86: /* ロードオブヴァーミリオン(&ストームガスト &グランドクロス) */ + skill_attack(BF_MAGIC,ss,&src->bl,bl,sg->skill_id,sg->skill_lv,tick,0); + break; + + case 0x7f: /* ファイヤーウォール */ + if( (src->val2--)>0) + skill_attack(BF_MAGIC,ss,&src->bl,bl, + sg->skill_id,sg->skill_lv,tick,0); + if( src->val2<=0 ) + skill_delunit(src); + break; + + case 0x87: /* ファイアーピラー(発動前) */ + skill_delunit(src); + skill_unitsetting(ss,sg->skill_id,sg->skill_lv,src->bl.x,src->bl.y,1); + break; + + case 0x88: /* ファイアーピラー(発動後) */ + if(DIFF_TICK(tick,sg->tick) < 150) + skill_attack(BF_MAGIC,ss,&src->bl,bl,sg->skill_id,sg->skill_lv,tick,0); + break; + + case 0x90: /* スキッドトラップ */ + { + int i,c = skill_get_blewcount(sg->skill_id,sg->skill_lv); + if(map[bl->m].flag.gvg) c = 0; + for(i=0;i<c;i++) + skill_blown(&src->bl,bl,1|0x30000); + sg->unit_id = 0x8c; + clif_changelook(&src->bl,LOOK_BASE,sg->unit_id); + sg->limit=DIFF_TICK(tick,sg->tick)+1500; + } + break; + + case 0x93: /* ランドマイン */ + skill_attack(BF_MISC,ss,&src->bl,bl,sg->skill_id,sg->skill_lv,tick,0); + sg->unit_id = 0x8c; + clif_changelook(&src->bl,LOOK_BASE,0x88); + sg->limit=DIFF_TICK(tick,sg->tick)+1500; + break; + + case 0x8f: /* ブラストマイン */ + case 0x94: /* ショックウェーブトラップ */ + case 0x95: /* サンドマン */ + case 0x96: /* フラッシャー */ + case 0x97: /* フリージングトラップ */ + case 0x98: /* クレイモアートラップ */ + map_foreachinarea(skill_count_target,src->bl.m + ,src->bl.x-src->range,src->bl.y-src->range + ,src->bl.x+src->range,src->bl.y+src->range + ,0,&src->bl,&splash_count); + map_foreachinarea(skill_trap_splash,src->bl.m + ,src->bl.x-src->range,src->bl.y-src->range + ,src->bl.x+src->range,src->bl.y+src->range + ,0,&src->bl,tick,splash_count); + sg->unit_id = 0x8c; + clif_changelook(&src->bl,LOOK_BASE,sg->unit_id); + sg->limit=DIFF_TICK(tick,sg->tick)+1500; + break; + + case 0x91: /* アンクルスネア */ + { + struct status_change *sc_data=battle_get_sc_data(bl); + if(sg->val2==0 && sc_data && sc_data[SC_ANKLE].timer==-1){ + int moveblock = ( bl->x/BLOCK_SIZE != src->bl.x/BLOCK_SIZE || bl->y/BLOCK_SIZE != src->bl.y/BLOCK_SIZE); + int sec=skill_get_time2(sg->skill_id,sg->skill_lv) - (double)battle_get_agi(bl)*0.1; + if(battle_get_mode(bl)&0x20) + sec = sec/5; + battle_stopwalking(bl,1); + skill_status_change_start(bl,SC_ANKLE,sg->skill_lv,0,0,0,sec,0); + + if(moveblock) map_delblock(bl); + bl->x = src->bl.x; + bl->y = src->bl.y; + if(moveblock) map_addblock(bl); + if(bl->type == BL_MOB) + clif_fixmobpos((struct mob_data *)bl); + else if(bl->type == BL_PET) + clif_fixpetpos((struct pet_data *)bl); + else + clif_fixpos(bl); + clif_01ac(&src->bl); + sg->limit=DIFF_TICK(tick,sg->tick) + sec; + sg->val2=bl->id; + } + } + break; + + case 0x80: /* ワープポータル(発動後) */ + if(bl->type==BL_PC){ + struct map_session_data *sd = (struct map_session_data *)bl; + if(sd && src->bl.m == bl->m && src->bl.x == bl->x && src->bl.y == bl->y && src->bl.x == sd->to_x && src->bl.y == sd->to_y) { + if( battle_config.chat_warpportal || !sd->chatID ){ + if((sg->val1--)>0){ + pc_setpos(sd,sg->valstr,sg->val2>>16,sg->val2&0xffff,3); + if(sg->src_id == bl->id ||( strcmp(map[src->bl.m].name,sg->valstr) == 0 && src->bl.x == (sg->val2>>16) && src->bl.y == (sg->val2&0xffff) )) + skill_delunitgroup(sg); + }else + skill_delunitgroup(sg); + } + } + }else if(bl->type==BL_MOB && battle_config.mob_warpportal){ + int m=map_mapname2mapid(sg->valstr); + struct mob_data *md; + md=(struct mob_data *)bl; + mob_warp((struct mob_data *)bl,m,sg->val2>>16,sg->val2&0xffff,3); + } + break; + + case 0x8e: /* クァグマイア */ + { + int type=SkillStatusChangeTable[sg->skill_id]; + if( bl->type==BL_PC && ((struct map_session_data *)bl)->special_state.no_magic_damage ) + break; + if( battle_get_sc_data(bl)[type].timer==-1 ) + skill_status_change_start(bl,type,sg->skill_lv,(int)src,0,0,skill_get_time2(sg->skill_id,sg->skill_lv),0); + } + break; + case 0x92: /* ベノムダスト */ + { + struct status_change *sc_data=battle_get_sc_data(bl); + int type=SkillStatusChangeTable[sg->skill_id]; + if( sc_data && sc_data[type].timer==-1 ) + skill_status_change_start(bl,type,sg->skill_lv,(int)src,0,0,skill_get_time2(sg->skill_id,sg->skill_lv),0); + } + break; + case 0x9a: /* ボルケーノ */ + case 0x9b: /* デリュージ */ + case 0x9c: /* バイオレントゲイル */ + { + struct skill_unit *unit2; + struct status_change *sc_data=battle_get_sc_data(bl); + int type=SkillStatusChangeTable[sg->skill_id]; + if(sc_data && sc_data[type].timer==-1) + skill_status_change_start(bl,type,sg->skill_lv,(int)src,0,0,skill_get_time2(sg->skill_id,sg->skill_lv),0); + else if((unit2=(struct skill_unit *)sc_data[type].val2) && unit2 != src ){ + if( DIFF_TICK(sg->tick,unit2->group->tick)>0 ) + skill_status_change_start(bl,type,sg->skill_lv,(int)src,0,0,skill_get_time2(sg->skill_id,sg->skill_lv),0); + ts->tick-=sg->interval; + } + } break; + + case 0x9e: /* 子守唄 */ + case 0x9f: /* ニヨルドの宴 */ + case 0xa0: /* 永遠の混沌 */ + case 0xa1: /* 戦太鼓の響き */ + case 0xa2: /* ニーベルングの指輪 */ + case 0xa3: /* ロキの叫び */ + case 0xa4: /* 深淵の中に */ + case 0xa5: /* 不死身のジークフリード */ + case 0xa6: /* 不協和音 */ + case 0xa7: /* 口笛 */ + case 0xa8: /* 夕陽のアサシンクロス */ + case 0xa9: /* ブラギの詩 */ + case 0xab: /* 自分勝手なダンス */ + case 0xac: /* ハミング */ + case 0xad: /* 私を忘れないで… */ + case 0xae: /* 幸運のキス */ + case 0xaf: /* サービスフォーユー */ + case 0xb4: + { + struct skill_unit *unit2; + struct status_change *sc_data=battle_get_sc_data(bl); + int type=SkillStatusChangeTable[sg->skill_id]; + if(sg->src_id == bl->id) + break; + if(sc_data && sc_data[type].timer==-1) + skill_status_change_start(bl,type,sg->skill_lv,sg->val1,sg->val2, + (int)src,skill_get_time2(sg->skill_id,sg->skill_lv),0); + else if( (unit2=(struct skill_unit *)sc_data[type].val4) && unit2 != src ){ + if( unit2->group && DIFF_TICK(sg->tick,unit2->group->tick)>0 ) + skill_status_change_start(bl,type,sg->skill_lv,sg->val1,sg->val2, + (int)src,skill_get_time2(sg->skill_id,sg->skill_lv),0); + ts->tick-=sg->interval; + } + } break; + + case 0xaa: /* イドゥンの林檎 */ + { + struct skill_unit *unit2; + struct status_change *sc_data=battle_get_sc_data(bl); + int type=SkillStatusChangeTable[sg->skill_id]; + if(sg->src_id == bl->id) + break; + if( sc_data && sc_data[type].timer==-1) + skill_status_change_start(bl,type,sg->skill_lv,(sg->val1)>>16,(sg->val1)&0xffff, + (int)src,skill_get_time2(sg->skill_id,sg->skill_lv),0); + else if((unit2=(struct skill_unit *)sc_data[type].val4) && unit2 != src ){ + if( DIFF_TICK(sg->tick,unit2->group->tick)>0 ) + skill_status_change_start(bl,type,sg->skill_lv,(sg->val1)>>16,(sg->val1)&0xffff, + (int)src,skill_get_time2(sg->skill_id,sg->skill_lv),0); + ts->tick-=sg->interval; + } + } break; + + case 0xb1: /* デモンストレーション */ + skill_attack(BF_WEAPON,ss,&src->bl,bl,sg->skill_id,sg->skill_lv,tick,0); + if(bl->type == BL_PC && rand()%100 < sg->skill_lv && battle_config.equipment_breaking) + pc_breakweapon((struct map_session_data *)bl); + break; + case 0x99: /* トーキーボックス */ + if(sg->src_id == bl->id) //自分が踏んでも発動しない + break; + if(sg->val2==0){ + clif_talkiebox(&src->bl,sg->valstr); + sg->unit_id = 0x8c; + clif_changelook(&src->bl,LOOK_BASE,sg->unit_id); + sg->limit=DIFF_TICK(tick,sg->tick)+5000; + sg->val2=-1; //踏んだ + } + break; + case 0xb2: /* あなたを_会いたいです */ + case 0xb3: /* ゴスペル */ + case 0xb6: /* フォグウォール */ + //とりあえず何もしない + break; + + + + + + + case 0xb7: /* スパイダーウェッブ */ + if(sg->val2==0){ + int moveblock = ( bl->x/BLOCK_SIZE != src->bl.x/BLOCK_SIZE || bl->y/BLOCK_SIZE != src->bl.y/BLOCK_SIZE); + skill_additional_effect(ss,bl,sg->skill_id,sg->skill_lv,BF_MISC,tick); + if(moveblock) map_delblock(bl); + bl->x = (&src->bl)->x; + bl->y = (&src->bl)->y; + if(moveblock) map_addblock(bl); + if(bl->type == BL_MOB) + clif_fixmobpos((struct mob_data *)bl); + else if(bl->type == BL_PET) + clif_fixpetpos((struct pet_data *)bl); + else + clif_fixpos(bl); + clif_01ac(&src->bl); + sg->limit=DIFF_TICK(tick,sg->tick) + skill_get_time2(sg->skill_id,sg->skill_lv); + sg->val2=bl->id; + } + break; + +/* default: + if(battle_config.error_log) + printf("skill_unit_onplace: Unknown skill unit id=%d block=%d\n",sg->unit_id,bl->id); + break;*/ + } + if(bl->type==BL_MOB && ss!=bl) /* スキル使用条件のMOBスキル */ + { + if(battle_config.mob_changetarget_byskill == 1) + { + int target=((struct mob_data *)bl)->target_id; + if(ss->type == BL_PC) + ((struct mob_data *)bl)->target_id=ss->id; + mobskill_use((struct mob_data *)bl,tick,MSC_SKILLUSED|(sg->skill_id<<16)); + ((struct mob_data *)bl)->target_id=target; + } + else + mobskill_use((struct mob_data *)bl,tick,MSC_SKILLUSED|(sg->skill_id<<16)); + } + + return 0; +} +/*========================================== + * スキルユニットから離脱する(もしくはしている)場合 + *------------------------------------------ + */ +int skill_unit_onout(struct skill_unit *src,struct block_list *bl,unsigned int tick) +{ + struct skill_unit_group *sg; + + nullpo_retr(0, src); + nullpo_retr(0, bl); + nullpo_retr(0, sg=src->group); + + if( bl->prev==NULL || !src->alive ) + return 0; + + if( bl->type!=BL_PC && bl->type!=BL_MOB ) + return 0; + + switch(sg->unit_id){ + case 0x7e: /* セイフティウォール */ + case 0x85: /* ニューマ */ + case 0x8e: /* クァグマイア */ + { + struct status_change *sc_data=battle_get_sc_data(bl); + int type= + (sg->unit_id==0x85)?SC_PNEUMA: + ((sg->unit_id==0x7e)?SC_SAFETYWALL: + SC_QUAGMIRE); + if((type != SC_QUAGMIRE || bl->type != BL_MOB) && + sc_data && sc_data[type].timer!=-1 && ((struct skill_unit *)sc_data[type].val2)==src){ + skill_status_change_end(bl,type,-1); + } + } break; + + case 0x91: /* アンクルスネア */ + { + struct block_list *target=map_id2bl(sg->val2); + if( target && target==bl ){ + skill_status_change_end(bl,SC_ANKLE,-1); + sg->limit=DIFF_TICK(tick,sg->tick)+1000; + } + } + break; + case 0xb5: + case 0xb8: + { + struct block_list *target=map_id2bl(sg->val2); + if( target==bl ) + skill_status_change_end(bl,SC_SPIDERWEB,-1); + sg->limit=DIFF_TICK(tick,sg->tick)+1000; + } + break; + case 0xb6: + { + struct block_list *target=map_id2bl(sg->val2); + if( target==bl ) + skill_status_change_end(bl,SC_FOGWALL,-1); + sg->limit=DIFF_TICK(tick,sg->tick)+1000; + } + break; + case 0x9a: /* ボルケーノ */ + case 0x9b: /* デリュージ */ + case 0x9c: /* バイオレントゲイル */ + { + struct status_change *sc_data=battle_get_sc_data(bl); + struct skill_unit *su; + int type=SkillStatusChangeTable[sg->skill_id]; + if( sc_data && sc_data[type].timer!=-1 && (su=((struct skill_unit *)sc_data[type].val2)) && su == src ){ + skill_status_change_end(bl,type,-1); + } + } + break; + + case 0x9e: /* 子守唄 */ + case 0x9f: /* ニヨルドの宴 */ + case 0xa0: /* 永遠の混沌 */ + case 0xa1: /* 戦太鼓の響き */ + case 0xa2: /* ニーベルングの指輪 */ + case 0xa3: /* ロキの叫び */ + case 0xa4: /* 深淵の中に */ + case 0xa5: /* 不死身のジークフリード */ + case 0xa6: /* 不協和音 */ + case 0xa7: /* 口笛 */ + case 0xa8: /* 夕陽のアサシンクロス */ + case 0xa9: /* ブラギの詩 */ + case 0xaa: /* イドゥンの林檎 */ + case 0xab: /* 自分勝手なダンス */ + case 0xac: /* ハミング */ + case 0xad: /* 私を忘れないで… */ + case 0xae: /* 幸運のキス */ + case 0xaf: /* サービスフォーユー */ + case 0xb4: + { + struct status_change *sc_data=battle_get_sc_data(bl); + struct skill_unit *su; + int type=SkillStatusChangeTable[sg->skill_id]; + if( sc_data && sc_data[type].timer!=-1 && (su=((struct skill_unit *)sc_data[type].val4)) && su == src ){ + skill_status_change_end(bl,type,-1); + } + } + break; + case 0xb7: /* スパイダーウェッブ */ + { + struct block_list *target=map_id2bl(sg->val2); + if( target && target==bl ) + skill_status_change_end(bl,SC_SPIDERWEB,-1); + sg->limit=DIFF_TICK(tick,sg->tick)+1000; + } + break; + +/* default: + if(battle_config.error_log) + printf("skill_unit_onout: Unknown skill unit id=%d block=%d\n",sg->unit_id,bl->id); + break;*/ + } + skill_unitgrouptickset_delete(bl,sg->group_id); + return 0; +} +/*========================================== + * スキルユニットの削除イベント + *------------------------------------------ + */ +int skill_unit_ondelete(struct skill_unit *src,struct block_list *bl,unsigned int tick) +{ + struct skill_unit_group *sg; + + nullpo_retr(0, src); + nullpo_retr(0, bl); + nullpo_retr(0, sg = src->group); + + if( bl->prev==NULL || !src->alive ) + return 0; + + if( bl->type!=BL_PC && bl->type!=BL_MOB ) + return 0; + + switch(sg->unit_id){ + case 0x85: /* ニューマ */ + case 0x7e: /* セイフティウォール */ + case 0x8e: /* クァグマイヤ */ + case 0x9a: /* ボルケーノ */ + case 0x9b: /* デリュージ */ + case 0x9c: /* バイオレントゲイル */ + case 0x9e: /* 子守唄 */ + case 0x9f: /* ニヨルドの宴 */ + case 0xa0: /* 永遠の混沌 */ + case 0xa1: /* 戦太鼓の響き */ + case 0xa2: /* ニーベルングの指輪 */ + case 0xa3: /* ロキの叫び */ + case 0xa4: /* 深淵の中に */ + case 0xa5: /* 不死身のジークフリード */ + case 0xa6: /* 不協和音 */ + case 0xa7: /* 口笛 */ + case 0xa8: /* 夕陽のアサシンクロス */ + case 0xa9: /* ブラギの詩 */ + case 0xaa: /* イドゥンの林檎 */ + case 0xab: /* 自分勝手なダンス */ + case 0xac: /* ハミング */ + case 0xad: /* 私を忘れないで… */ + case 0xae: /* 幸運のキス */ + case 0xaf: /* サービスフォーユー */ + case 0xb4: + return skill_unit_onout(src,bl,tick); + +/* default: + if(battle_config.error_log) + printf("skill_unit_ondelete: Unknown skill unit id=%d block=%d\n",sg->unit_id,bl->id); + break;*/ + } + skill_unitgrouptickset_delete(bl,sg->group_id); + return 0; +} +/*========================================== + * スキルユニットの限界イベント + *------------------------------------------ + */ +int skill_unit_onlimit(struct skill_unit *src,unsigned int tick) +{ + struct skill_unit_group *sg; + + nullpo_retr(0, src); + nullpo_retr(0, sg=src->group); + + switch(sg->unit_id){ + case 0x81: /* ワープポータル(発動前) */ + { + struct skill_unit_group *group= + skill_unitsetting(map_id2bl(sg->src_id),sg->skill_id,sg->skill_lv, + src->bl.x,src->bl.y,1); + if(group == NULL) + return 0; + group->valstr=calloc(24, 1); + if(group->valstr==NULL){ + printf("skill_unit_onlimit: out of memory !\n"); + exit(1); + } + memcpy(group->valstr,sg->valstr,24); + group->val2=sg->val2; + } + break; + + case 0x8d: /* アイスウォール */ + map_setcell(src->bl.m,src->bl.x,src->bl.y,src->val2); + clif_changemapcell(src->bl.m,src->bl.x,src->bl.y,src->val2,1); + break; + case 0xb2: /* あなたに会いたい */ + { + struct map_session_data *sd = NULL; + struct map_session_data *p_sd = NULL; + if((sd = (struct map_session_data *)(map_id2bl(sg->src_id))) == NULL) + return 0; + if((p_sd = pc_get_partner(sd)) == NULL) + return 0; + + pc_setpos(p_sd,map[src->bl.m].name,src->bl.x,src->bl.y,3); + } + break; + } + return 0; +} +/*========================================== + * スキルユニットのダメージイベント + *------------------------------------------ + */ +int skill_unit_ondamaged(struct skill_unit *src,struct block_list *bl, + int damage,unsigned int tick) +{ + struct skill_unit_group *sg; + + nullpo_retr(0, src); + nullpo_retr(0, sg=src->group); + + switch(sg->unit_id){ + case 0x8d: /* アイスウォール */ + src->val1-=damage; + break; + case 0x8f: /* ブラストマイン */ + case 0x98: /* クレイモアートラップ */ + skill_blown(bl,&src->bl,2); //吹き飛ばしてみる + break; + default: + damage = 0; + break; + } + return damage; +} + + +/*---------------------------------------------------------------------------- */ + +/*========================================== + * スキル使用(詠唱完了、場所指定) + *------------------------------------------ + */ +int skill_castend_pos( int tid, unsigned int tick, int id,int data ) +{ + struct map_session_data* sd=map_id2sd(id)/*,*target_sd=NULL*/; + int range,maxcount; + + nullpo_retr(0, sd); + + if( sd->bl.prev == NULL ) + return 0; + if( sd->skilltimer != tid ) /* タイマIDの確認 */ + return 0; + if(sd->skilltimer != -1 && pc_checkskill(sd,SA_FREECAST) > 0) { + sd->speed = sd->prev_speed; + clif_updatestatus(sd,SP_SPEED); + } + sd->skilltimer=-1; + if(pc_isdead(sd)) { + sd->canact_tick = tick; + sd->canmove_tick = tick; + sd->skillitem = sd->skillitemlv = -1; + return 0; + } + + if(battle_config.pc_skill_reiteration == 0) { + range = -1; + switch(sd->skillid) { + case MG_SAFETYWALL: + case WZ_FIREPILLAR: + case HT_SKIDTRAP: + case HT_LANDMINE: + case HT_ANKLESNARE: + case HT_SHOCKWAVE: + case HT_SANDMAN: + case HT_FLASHER: + case HT_FREEZINGTRAP: + case HT_BLASTMINE: + case HT_CLAYMORETRAP: + case HT_TALKIEBOX: + case AL_WARP: + case PF_SPIDERWEB: /* スパイダーウェッブ */ + case RG_GRAFFITI: /* グラフィティ */ + range = 0; + break; + case AL_PNEUMA: + range = 1; + break; + } + if(range >= 0) { + if(skill_check_unit_range(sd->bl.m,sd->skillx,sd->skilly,range,sd->skillid) > 0) { + clif_skill_fail(sd,sd->skillid,0,0); + sd->canact_tick = tick; + sd->canmove_tick = tick; + sd->skillitem = sd->skillitemlv = -1; + return 0; + } + } + } + if(battle_config.pc_skill_nofootset) { + range = -1; + switch(sd->skillid) { + case WZ_FIREPILLAR: + case HT_SKIDTRAP: + case HT_LANDMINE: + case HT_ANKLESNARE: + case HT_SHOCKWAVE: + case HT_SANDMAN: + case HT_FLASHER: + case HT_FREEZINGTRAP: + case HT_BLASTMINE: + case HT_CLAYMORETRAP: + case HT_TALKIEBOX: + case PF_SPIDERWEB: /* スパイダーウェッブ */ + case WZ_ICEWALL: + range = 1; + break; + case AL_WARP: + range = 0; + break; + } + if(range >= 0) { + if(skill_check_unit_range2(sd->bl.m,sd->skillx,sd->skilly,range) > 0) { + clif_skill_fail(sd,sd->skillid,0,0); + sd->canact_tick = tick; + sd->canmove_tick = tick; + sd->skillitem = sd->skillitemlv = -1; + return 0; + } + } + } + + if(battle_config.pc_land_skill_limit) { + maxcount = skill_get_maxcount(sd->skillid); + if(maxcount > 0) { + int i,c; + for(i=c=0;i<MAX_SKILLUNITGROUP;i++) { + if(sd->skillunit[i].alive_count > 0 && sd->skillunit[i].skill_id == sd->skillid) + c++; + } + if(c >= maxcount) { + clif_skill_fail(sd,sd->skillid,0,0); + sd->canact_tick = tick; + sd->canmove_tick = tick; + sd->skillitem = sd->skillitemlv = -1; + return 0; + } + } + } + + range = skill_get_range(sd->skillid,sd->skilllv); + if(range < 0) + range = battle_get_range(&sd->bl) - (range + 1); + range += battle_config.pc_skill_add_range; + if(battle_config.skill_out_range_consume) { // changed to allow casting when target walks out of range [Valaris] + if(range < distance(sd->bl.x,sd->bl.y,sd->skillx,sd->skilly)) { + clif_skill_fail(sd,sd->skillid,0,0); + sd->canact_tick = tick; + sd->canmove_tick = tick; + sd->skillitem = sd->skillitemlv = -1; + return 0; + } + } + if(!skill_check_condition(sd,1)) { /* 使用条件チェック */ + sd->canact_tick = tick; + sd->canmove_tick = tick; + sd->skillitem = sd->skillitemlv = -1; + return 0; + } + sd->skillitem = sd->skillitemlv = -1; + if(battle_config.skill_out_range_consume) { + if(range < distance(sd->bl.x,sd->bl.y,sd->skillx,sd->skilly)) { + clif_skill_fail(sd,sd->skillid,0,0); + sd->canact_tick = tick; + sd->canmove_tick = tick; + return 0; + } + } + + if(battle_config.pc_skill_log) + printf("PC %d skill castend skill=%d\n",sd->bl.id,sd->skillid); + pc_stop_walking(sd,0); + + skill_castend_pos2(&sd->bl,sd->skillx,sd->skilly,sd->skillid,sd->skilllv,tick,0); + + return 0; +} + +/*========================================== + * 範囲内キャラ存在確認判定処理(foreachinarea) + *------------------------------------------ + */ + +static int skill_check_condition_char_sub(struct block_list *bl,va_list ap) +{ + int *c; + struct block_list *src; + struct map_session_data *sd; + struct map_session_data *ssd; + struct pc_base_job s_class; + struct pc_base_job ss_class; + + nullpo_retr(0, bl); + nullpo_retr(0, ap); + nullpo_retr(0, sd=(struct map_session_data*)bl); + nullpo_retr(0, src=va_arg(ap,struct block_list *)); + nullpo_retr(0, c=va_arg(ap,int *)); + nullpo_retr(0, ssd=(struct map_session_data*)src); + + s_class = pc_calc_base_job(sd->status.class); + //チェックしない設定ならcにありえない大きな数字を返して終了 + if(!battle_config.player_skill_partner_check){ //本当はforeachの前にやりたいけど設定適用箇所をまとめるためにここへ + (*c)=99; + return 0; + } + + ; + ss_class = pc_calc_base_job(ssd->status.class); + + switch(ssd->skillid){ + case PR_BENEDICTIO: /* 聖体降福 */ + if(sd != ssd && (sd->status.class == 4 || sd->status.class == 8 || sd->status.class == 15 || + sd->status.class == 4005 || sd->status.class == 4009 || sd->status.class == 4016) && + (sd->bl.x == ssd->bl.x - 1 || sd->bl.x == ssd->bl.x + 1) && sd->status.sp >= 10) + (*c)++; + break; + case BD_LULLABY: /* 子守歌 */ + case BD_RICHMANKIM: /* ニヨルドの宴 */ + case BD_ETERNALCHAOS: /* 永遠の混沌 */ + case BD_DRUMBATTLEFIELD: /* 戦太鼓の響き */ + case BD_RINGNIBELUNGEN: /* ニーベルングの指輪 */ + case BD_ROKISWEIL: /* ロキの叫び */ + case BD_INTOABYSS: /* 深淵の中に */ + case BD_SIEGFRIED: /* 不死身のジークフリード */ + case BD_RAGNAROK: /* 神々の黄昏 */ + case CG_MOONLIT: /* 月明りの泉に落ちる花びら */ + if(sd != ssd && + ((ssd->status.class==19 && sd->status.class==20) || + (ssd->status.class==20 && sd->status.class==19) || + (ssd->status.class==4020 && sd->status.class==4021) || + (ssd->status.class==4021 && sd->status.class==4020) || + (ssd->status.class==20 && sd->status.class==4020) || + (ssd->status.class==19 && sd->status.class==4021)) && + pc_checkskill(sd,ssd->skillid) > 0 && + (*c)==0 && + sd->status.party_id == ssd->status.party_id && + !pc_issit(sd) && + sd->sc_data[SC_DANCING].timer==-1 + ) + (*c)=pc_checkskill(sd,ssd->skillid); + break; + } + return 0; +} +/*========================================== + * 範囲内キャラ存在確認判定後スキル使用処理(foreachinarea) + *------------------------------------------ + */ + +static int skill_check_condition_use_sub(struct block_list *bl,va_list ap) +{ + int *c; + struct block_list *src; + struct map_session_data *sd; + struct map_session_data *ssd; + struct pc_base_job s_class; + struct pc_base_job ss_class; + int skillid,skilllv; + + nullpo_retr(0, bl); + nullpo_retr(0, ap); + nullpo_retr(0, sd=(struct map_session_data*)bl); + nullpo_retr(0, src=va_arg(ap,struct block_list *)); + nullpo_retr(0, c=va_arg(ap,int *)); + nullpo_retr(0, ssd=(struct map_session_data*)src); + + s_class = pc_calc_base_job(sd->status.class); + + //チェックしない設定ならcにありえない大きな数字を返して終了 + if(!battle_config.player_skill_partner_check){ //本当はforeachの前にやりたいけど設定適用箇所をまとめるためにここへ + (*c)=99; + return 0; + } + + ss_class = pc_calc_base_job(ssd->status.class); + skillid=ssd->skillid; + skilllv=ssd->skilllv; + switch(skillid){ + case PR_BENEDICTIO: /* 聖体降福 */ + if(sd != ssd && (sd->status.class == 4 || sd->status.class == 8 || sd->status.class == 15 || + sd->status.class == 4005 || sd->status.class == 4009 || sd->status.class == 4016) && + (sd->bl.x == ssd->bl.x - 1 || sd->bl.x == ssd->bl.x + 1) && sd->status.sp >= 10){ + sd->status.sp -= 10; + pc_calcstatus(sd,0); + (*c)++; + } + break; + case BD_LULLABY: /* 子守歌 */ + case BD_RICHMANKIM: /* ニヨルドの宴 */ + case BD_ETERNALCHAOS: /* 永遠の混沌 */ + case BD_DRUMBATTLEFIELD: /* 戦太鼓の響き */ + case BD_RINGNIBELUNGEN: /* ニーベルングの指輪 */ + case BD_ROKISWEIL: /* ロキの叫び */ + case BD_INTOABYSS: /* 深淵の中に */ + case BD_SIEGFRIED: /* 不死身のジークフリード */ + case BD_RAGNAROK: /* 神々の黄昏 */ + case CG_MOONLIT: /* 月明りの泉に落ちる花びら */ + if(sd != ssd && //本人以外で + ((ssd->status.class==19 && sd->status.class==20) || + (ssd->status.class==20 && sd->status.class==19) || + (ssd->status.class==4020 && sd->status.class==4021) || + (ssd->status.class==4021 && sd->status.class==4020) || + (ssd->status.class==20 && sd->status.class==4020) || + (ssd->status.class==19 && sd->status.class==4021)) && //自分がダンサーならバードで + pc_checkskill(sd,skillid) > 0 && //スキルを持っていて + (*c)==0 && //最初の一人で + sd->status.party_id == ssd->status.party_id && //パーティーが同じで + !pc_issit(sd) && //座ってない + sd->sc_data[SC_DANCING].timer==-1 //ダンス中じゃない + ){ + ssd->sc_data[SC_DANCING].val4=bl->id; + clif_skill_nodamage(bl,src,skillid,skilllv,1); + skill_status_change_start(bl,SC_DANCING,skillid,ssd->sc_data[SC_DANCING].val2,0,src->id,skill_get_time(skillid,skilllv)+1000,0); + sd->skillid_dance=sd->skillid=skillid; + sd->skilllv_dance=sd->skilllv=skilllv; + (*c)++; + } + break; + } + return 0; +} +/*========================================== + * 範囲内バイオプラント、スフィアマイン用Mob存在確認判定処理(foreachinarea) + *------------------------------------------ + */ + +static int skill_check_condition_mob_master_sub(struct block_list *bl,va_list ap) +{ + int *c,src_id=0,mob_class=0; + struct mob_data *md; + + nullpo_retr(0, bl); + nullpo_retr(0, ap); + nullpo_retr(0, md=(struct mob_data*)bl); + nullpo_retr(0, src_id=va_arg(ap,int)); + nullpo_retr(0, mob_class=va_arg(ap,int)); + nullpo_retr(0, c=va_arg(ap,int *)); + + if(md->class==mob_class && md->master_id==src_id) + (*c)++; + return 0; +} + +/*========================================== + * スキル使用条件(偽で使用失敗) + *------------------------------------------ + */ +int skill_check_condition(struct map_session_data *sd,int type) +{ + int i,hp,sp,hp_rate,sp_rate,zeny,weapon,state,spiritball,skill,lv,mhp; + int index[10],itemid[10],amount[10]; + + nullpo_retr(0, sd); + + if( battle_config.gm_skilluncond>0 && pc_isGM(sd)>= battle_config.gm_skilluncond ) { + sd->skillitem = sd->skillitemlv = -1; + return 1; + } + + if( sd->opt1>0) { + clif_skill_fail(sd,sd->skillid,0,0); + sd->skillitem = sd->skillitemlv = -1; + return 0; + } + if(pc_is90overweight(sd)) { + clif_skill_fail(sd,sd->skillid,9,0); + sd->skillitem = sd->skillitemlv = -1; + return 0; + } + + if(sd->skillid == AC_MAKINGARROW && sd->state.make_arrow_flag == 1) { + sd->skillitem = sd->skillitemlv = -1; + return 0; + } + if(sd->skillid == AM_PHARMACY && sd->state.produce_flag == 1) { + sd->skillitem = sd->skillitemlv = -1; + return 0; + } + + if(sd->skillitem == sd->skillid) { /* アイテムの場合無条件成功 */ + if(type&1) + sd->skillitem = sd->skillitemlv = -1; + return 1; + } + if( sd->opt1>0 ){ + clif_skill_fail(sd,sd->skillid,0,0); + return 0; + } + if(sd->sc_data){ + if( sd->sc_data[SC_DIVINA].timer!=-1 || + sd->sc_data[SC_ROKISWEIL].timer!=-1 || + (sd->sc_data[SC_AUTOCOUNTER].timer != -1 && sd->skillid != KN_AUTOCOUNTER) || + sd->sc_data[SC_STEELBODY].timer != -1 || + sd->sc_data[SC_BERSERK].timer != -1 + ){ + clif_skill_fail(sd,sd->skillid,0,0); + return 0; /* 状態異常や沈黙など */ + } + } + skill = sd->skillid; + lv = sd->skilllv; + hp=skill_get_hp(skill, lv); /* 消費HP */ + sp=skill_get_sp(skill, lv); /* 消費SP */ + if((sd->skillid_old == BD_ENCORE) && skill==sd->skillid_dance) + sp=sp/2; //アンコール時はSP消費が半分 + hp_rate = (lv <= 0)? 0:skill_db[skill].hp_rate[lv-1]; + sp_rate = (lv <= 0)? 0:skill_db[skill].sp_rate[lv-1]; + zeny = skill_get_zeny(skill,lv); + weapon = skill_db[skill].weapon; + state = skill_db[skill].state; + spiritball = (lv <= 0)? 0:skill_db[skill].spiritball[lv-1]; + mhp=skill_get_mhp(skill, lv); /* 消費HP */ + for(i=0;i<10;i++) { + itemid[i] = skill_db[skill].itemid[i]; + amount[i] = skill_db[skill].amount[i]; + } + if(mhp > 0) + hp += (sd->status.max_hp * mhp)/100; + if(hp_rate > 0) + hp += (sd->status.hp * hp_rate)/100; + else + hp += (sd->status.max_hp * abs(hp_rate))/100; + if(sp_rate > 0) + sp += (sd->status.sp * sp_rate)/100; + else + sp += (sd->status.max_sp * abs(sp_rate))/100; + if(sd->dsprate!=100) + sp=sp*sd->dsprate/100; /* 消費SP修正 */ + + switch(skill) { + case SA_CASTCANCEL: + if(sd->skilltimer == -1) { + clif_skill_fail(sd,skill,0,0); + return 0; + } + break; + case BS_MAXIMIZE: /* マキシマイズパワー */ + case NV_TRICKDEAD: /* 死んだふり */ + case TF_HIDING: /* ハイディング */ + case AS_CLOAKING: /* クローキング */ + case CR_AUTOGUARD: /* オートガード */ + case CR_DEFENDER: /* ディフェンダー */ + case ST_CHASEWALK: + if(sd->sc_data[SkillStatusChangeTable[skill]].timer!=-1) + return 1; /* 解除する場合はSP消費しない */ + break; + case AL_TELEPORT: + case AL_WARP: + if(map[sd->bl.m].flag.noteleport) { + clif_skill_teleportmessage(sd,0); + return 0; + } + break; + case MO_CALLSPIRITS: /* 気功 */ + if(sd->spiritball >= lv) { + clif_skill_fail(sd,skill,0,0); + return 0; + } + break; + case CH_SOULCOLLECT: /* 狂気功 */ + if(sd->spiritball >= 5) { + clif_skill_fail(sd,skill,0,0); + return 0; + } + break; + case MO_FINGEROFFENSIVE: //指弾 + if (sd->spiritball > 0 && sd->spiritball < spiritball) { + spiritball = sd->spiritball; + sd->spiritball_old = sd->spiritball; + } + else sd->spiritball_old = lv; + break; + case MO_CHAINCOMBO: //連打掌 + if(sd->sc_data[SC_BLADESTOP].timer==-1){ + if(sd->sc_data[SC_COMBO].timer == -1 || sd->sc_data[SC_COMBO].val1 != MO_TRIPLEATTACK) + return 0; + } + break; + case MO_COMBOFINISH: //猛龍拳 + if(sd->sc_data[SC_COMBO].timer == -1 || sd->sc_data[SC_COMBO].val1 != MO_CHAINCOMBO) + return 0; + break; + case CH_TIGERFIST: //伏虎拳 + if(sd->sc_data[SC_COMBO].timer == -1 || sd->sc_data[SC_COMBO].val1 != MO_COMBOFINISH) + return 0; + break; + case CH_CHAINCRUSH: //連柱崩撃 + if(sd->sc_data[SC_COMBO].timer == -1) + return 0; + if(sd->sc_data[SC_COMBO].val1 != MO_COMBOFINISH && sd->sc_data[SC_COMBO].val1 != CH_TIGERFIST) + return 0; + break; + case MO_EXTREMITYFIST: // 阿修羅覇鳳拳 + if((sd->sc_data[SC_COMBO].timer != -1 && (sd->sc_data[SC_COMBO].val1 == MO_COMBOFINISH || sd->sc_data[SC_COMBO].val1 == CH_CHAINCRUSH)) || sd->sc_data[SC_BLADESTOP].timer!=-1) + spiritball--; + break; + case BD_ADAPTATION: /* アドリブ */ + { + struct skill_unit_group *group=NULL; + if(sd->sc_data[SC_DANCING].timer==-1 || ((group=(struct skill_unit_group*)sd->sc_data[SC_DANCING].val2) && (skill_get_time(sd->sc_data[SC_DANCING].val1,group->skill_lv) - sd->sc_data[SC_DANCING].val3*1000) <= skill_get_time2(skill,lv))){ //ダンス中で使用後5秒以上のみ? + clif_skill_fail(sd,skill,0,0); + return 0; + } + } + break; + case PR_BENEDICTIO: /* 聖体降福 */ + { + int range=1; + int c=0; + if(!(type&1)){ + map_foreachinarea(skill_check_condition_char_sub,sd->bl.m, + sd->bl.x-range,sd->bl.y-range, + sd->bl.x+range,sd->bl.y+range,BL_PC,&sd->bl,&c); + if(c<2){ + clif_skill_fail(sd,skill,0,0); + return 0; + } + }else{ + map_foreachinarea(skill_check_condition_use_sub,sd->bl.m, + sd->bl.x-range,sd->bl.y-range, + sd->bl.x+range,sd->bl.y+range,BL_PC,&sd->bl,&c); + } + } + break; + case WE_CALLPARTNER: /* あなたに逢いたい */ + if(!sd->status.partner_id){ + clif_skill_fail(sd,skill,0,0); + return 0; + } + break; + case AM_CANNIBALIZE: /* バイオプラント */ + case AM_SPHEREMINE: /* スフィアーマイン */ + if(type&1){ + int c=0; + int maxcount=skill_get_maxcount(skill); + int mob_class=(skill==AM_CANNIBALIZE)?1118:1142; + if(battle_config.pc_land_skill_limit && maxcount>0) { + map_foreachinarea(skill_check_condition_mob_master_sub ,sd->bl.m, 0, 0, map[sd->bl.m].xs, map[sd->bl.m].ys, BL_MOB, sd->bl.id, mob_class,&c ); + if(c >= maxcount){ + clif_skill_fail(sd,skill,0,0); + return 0; + } + } + } + break; + case MG_FIREWALL: /* ファイアーウォール */ + /* 数制限 */ + if(battle_config.pc_land_skill_limit) { + int maxcount = skill_get_maxcount(skill); + if(maxcount > 0) { + int i,c; + for(i=c=0;i<MAX_SKILLUNITGROUP;i++) { + if(sd->skillunit[i].alive_count > 0 && sd->skillunit[i].skill_id == skill) + c++; + } + if(c >= maxcount) { + clif_skill_fail(sd,skill,0,0); + return 0; + } + } + } + break; + } + + if(!(type&2)){ + if( hp>0 && sd->status.hp < hp) { /* HPチェック */ + clif_skill_fail(sd,skill,2,0); /* HP不足:失敗通知 */ + return 0; + } + if( sp>0 && sd->status.sp < sp) { /* SPチェック */ + clif_skill_fail(sd,skill,1,0); /* SP不足:失敗通知 */ + return 0; + } + if( zeny>0 && sd->status.zeny < zeny) { + clif_skill_fail(sd,skill,5,0); + return 0; + } + if(!(weapon & (1<<sd->status.weapon) ) ) { + clif_skill_fail(sd,skill,6,0); + return 0; + } + if( spiritball > 0 && sd->spiritball < spiritball) { + clif_skill_fail(sd,skill,0,0); // 氣球不足 + return 0; + } + } + + switch(state) { + case ST_HIDING: + if(!(sd->status.option&2)) { + clif_skill_fail(sd,skill,0,0); + return 0; + } + break; + case ST_CLOAKING: + if(!(sd->status.option&4)) { + clif_skill_fail(sd,skill,0,0); + return 0; + } + break; + case ST_HIDDEN: + if(!pc_ishiding(sd)) { + clif_skill_fail(sd,skill,0,0); + return 0; + } + break; + case ST_RIDING: + if(!pc_isriding(sd)) { + clif_skill_fail(sd,skill,0,0); + return 0; + } + break; + case ST_FALCON: + if(!pc_isfalcon(sd)) { + clif_skill_fail(sd,skill,0,0); + return 0; + } + break; + case ST_CART: + if(!pc_iscarton(sd)) { + clif_skill_fail(sd,skill,0,0); + return 0; + } + break; + case ST_SHIELD: + if(sd->status.shield <= 0) { + clif_skill_fail(sd,skill,0,0); + return 0; + } + break; + case ST_SIGHT: + if(sd->sc_data[SC_SIGHT].timer == -1 && type&1) { + clif_skill_fail(sd,skill,0,0); + return 0; + } + break; + case ST_EXPLOSIONSPIRITS: + if(sd->sc_data[SC_EXPLOSIONSPIRITS].timer == -1) { + clif_skill_fail(sd,skill,0,0); + return 0; + } + break; + case ST_RECOV_WEIGHT_RATE: + if(battle_config.natural_heal_weight_rate <= 100 && sd->weight*100/sd->max_weight >= battle_config.natural_heal_weight_rate) { + clif_skill_fail(sd,skill,0,0); + return 0; + } + break; + case ST_MOVE_ENABLE: + { + struct walkpath_data wpd; + if(path_search(&wpd,sd->bl.m,sd->bl.x,sd->bl.y,sd->skillx,sd->skilly,1)==-1) { + clif_skill_fail(sd,skill,0,0); + return 0; + } + } + break; + case ST_WATER: + if(map_getcell(sd->bl.m,sd->bl.x,sd->bl.y) != 3 && (sd->sc_data[SC_DELUGE].timer==-1)){ //水場判定 + clif_skill_fail(sd,skill,0,0); + return 0; + } + break; + } + + for(i=0;i<10;i++) { + int x = lv%11 - 1; + index[i] = -1; + if(itemid[i] <= 0) + continue; + if(itemid[i] >= 715 && itemid[i] <= 717 && sd->special_state.no_gemstone) + continue; + if(((itemid[i] >= 715 && itemid[i] <= 717) || itemid[i] == 1065) && sd->sc_data[SC_INTOABYSS].timer != -1) + continue; + if(skill == AM_POTIONPITCHER && i != x) + continue; + + index[i] = pc_search_inventory(sd,itemid[i]); + if(index[i] < 0 || sd->status.inventory[index[i]].amount < amount[i]) { + if(itemid[i] == 716 || itemid[i] == 717) + clif_skill_fail(sd,skill,(7+(itemid[i]-716)),0); + else + clif_skill_fail(sd,skill,0,0); + return 0; + } + } + + if(!(type&1)) + return 1; + + if(skill != AM_POTIONPITCHER) { + if(skill == AL_WARP && !(type&2)) + return 1; + for(i=0;i<10;i++) { + if(index[i] >= 0) + pc_delitem(sd,index[i],amount[i],0); // アイテム消費 + } + } + + if(type&2) + return 1; + + if(sp > 0) { // SP消費 + sd->status.sp-=sp; + clif_updatestatus(sd,SP_SP); + } + if(hp > 0) { // HP消費 + sd->status.hp-=hp; + clif_updatestatus(sd,SP_HP); + } + if(zeny > 0) // Zeny消費 + pc_payzeny(sd,zeny); + if(spiritball > 0) // 氣球消費 + pc_delspiritball(sd,spiritball,0); + + + return 1; +} + +/*========================================== + * 詠唱時間計算 + *------------------------------------------ + */ +int skill_castfix( struct block_list *bl, int time ) +{ + struct map_session_data *sd; + struct mob_data *md; // [Valaris] + struct status_change *sc_data; + int dex; + int castrate=100; + int skill,lv,castnodex; + + nullpo_retr(0, bl); + + if(bl->type==BL_MOB){ // Crash fix [Valaris] + md=(struct mob_data*)bl; + skill = md->skillid; + lv = md->skilllv; + } + + else { + sd=(struct map_session_data*)bl; + skill = sd->skillid; + lv = sd->skilllv; + } + + sc_data = battle_get_sc_data(bl); + dex=battle_get_dex(bl); + + if (skill > MAX_SKILL_DB || skill < 0) + return 0; + + castnodex=skill_get_castnodex(skill, lv); + + if(time==0) + return 0; + if(castnodex > 0 && bl->type==BL_PC) + castrate=((struct map_session_data *)bl)->castrate; + else if (castnodex <= 0 && bl->type==BL_PC) { + castrate=((struct map_session_data *)bl)->castrate; + time=time*castrate*(battle_config.castrate_dex_scale - dex)/(battle_config.castrate_dex_scale * 100); + time=time*battle_config.cast_rate/100; + } + + /* サフラギウム */ + if(sc_data && sc_data[SC_SUFFRAGIUM].timer!=-1 ){ + time=time*(100-sc_data[SC_SUFFRAGIUM].val1*15)/100; + skill_status_change_end( bl, SC_SUFFRAGIUM, -1); + } + /* ブラギの詩 */ + if(sc_data && sc_data[SC_POEMBRAGI].timer!=-1 ) + time=time*(100-(sc_data[SC_POEMBRAGI].val1*3+sc_data[SC_POEMBRAGI].val2 + +(sc_data[SC_POEMBRAGI].val3>>16)))/100; + + return (time>0)?time:0; +} +/*========================================== + * ディレイ計算 + *------------------------------------------ + */ +int skill_delayfix( struct block_list *bl, int time ) +{ + struct status_change *sc_data; + + nullpo_retr(0, bl); + + sc_data = battle_get_sc_data(bl); + if(time<=0) + return 0; + + if(bl->type == BL_PC) { + if( battle_config.delay_dependon_dex ) /* dexの影響を計算する */ + time=time*(battle_config.castrate_dex_scale - battle_get_dex(bl))/battle_config.castrate_dex_scale; + time=time*battle_config.delay_rate/100; + } + + /* ブラギの詩 */ + if(sc_data && sc_data[SC_POEMBRAGI].timer!=-1 ) + time=time*(100-(sc_data[SC_POEMBRAGI].val1*3+sc_data[SC_POEMBRAGI].val2 + +(sc_data[SC_POEMBRAGI].val3&0xffff)))/100; + + return (time>0)?time:0; +} + +/*========================================== + * スキル使用(ID指定) + *------------------------------------------ + */ +int skill_use_id( struct map_session_data *sd, int target_id, + int skill_num, int skill_lv) +{ + unsigned int tick; + int casttime=0,delay=0,skill,range; + struct map_session_data* target_sd=NULL; + int forcecast=0; + struct block_list *bl; + struct status_change *sc_data; + tick=gettick(); + + nullpo_retr(0, sd); + + if( (bl=map_id2bl(target_id)) == NULL ){ +/* if(battle_config.error_log) + printf("skill target not found %d\n",target_id); */ + return 0; + } + if(sd->bl.m != bl->m || pc_isdead(sd)) + return 0; + + if(skillnotok(skill_num, sd)) // [MouseJstr] + return 0; + + if(sd->skillid==WZ_ICEWALL && map[sd->bl.m].flag.noicewall && !map[sd->bl.m].flag.pvp) { // noicewall flag [Valaris] + clif_skill_fail(sd,sd->skillid,0,0); + return 0; + } + sc_data=sd->sc_data; + + /* 沈黙や異常(ただし、グリムなどの判定をする) */ + if( sd->opt1>0 ) + return 0; + if(sd->sc_data){ + if(sc_data[SC_CHASEWALK].timer != -1) return 0; + if(sc_data[SC_VOLCANO].timer != -1){ + if(skill_num==WZ_ICEWALL) return 0; + } + if(sc_data[SC_ROKISWEIL].timer!=-1){ + if(skill_num==BD_ADAPTATION) return 0; + } + if( sd->sc_data[SC_DIVINA].timer!=-1 || + sd->sc_data[SC_ROKISWEIL].timer!=-1 || + (sd->sc_data[SC_AUTOCOUNTER].timer != -1 && sd->skillid != KN_AUTOCOUNTER) || + sd->sc_data[SC_STEELBODY].timer != -1 || + sd->sc_data[SC_BERSERK].timer != -1 ){ + return 0; /* 状態異常や沈黙など */ + } + + if(sc_data[SC_BLADESTOP].timer != -1){ + int lv = sc_data[SC_BLADESTOP].val1; + if(sc_data[SC_BLADESTOP].val2==1) return 0;//白羽された側なのでダメ + if(lv==1) return 0; + if(lv==2 && skill_num!=MO_FINGEROFFENSIVE) return 0; + if(lv==3 && skill_num!=MO_FINGEROFFENSIVE && skill_num!=MO_INVESTIGATE) return 0; + if(lv==4 && skill_num!=MO_FINGEROFFENSIVE && skill_num!=MO_INVESTIGATE && skill_num!=MO_CHAINCOMBO) return 0; + if(lv==5 && skill_num!=MO_FINGEROFFENSIVE && skill_num!=MO_INVESTIGATE && skill_num!=MO_CHAINCOMBO && skill_num!=MO_EXTREMITYFIST) return 0; + } + } + + if(sd->status.option&4 && skill_num==TF_HIDING) + return 0; + if(sd->status.option&2 && skill_num!=TF_HIDING && skill_num!=AS_GRIMTOOTH && skill_num!=RG_BACKSTAP && skill_num!=RG_RAID ) + return 0; + + if(map[sd->bl.m].flag.gvg){ //GvGで使用できないスキル + switch(skill_num){ + case SM_ENDURE: + case AL_TELEPORT: + case AL_WARP: + case WZ_ICEWALL: + case TF_BACKSLIDING: + case LK_BERSERK: + case HP_BASILICA: + case ST_CHASEWALK: + return 0; + } + } + + /* 演奏/ダンス中 */ + if( sc_data && sc_data[SC_DANCING].timer!=-1 ){ +// if(battle_config.pc_skill_log) +// printf("dancing! %d\n",skill_num); + if( sc_data[SC_DANCING].val4 && skill_num!=BD_ADAPTATION ) //合奏中はアドリブ以外不可 + return 0; + if(skill_num!=BD_ADAPTATION && skill_num!=BA_MUSICALSTRIKE && skill_num!=DC_THROWARROW){ + return 0; + } + } + + if(skill_get_inf2(skill_num)&0x200 && sd->bl.id == target_id) + return 0; + //直前のスキルが何か覚える必要のあるスキル + switch(skill_num){ + case SA_CASTCANCEL: + if(sd->skillid != skill_num){ //キャストキャンセル自体は覚えない + sd->skillid_old = sd->skillid; + sd->skilllv_old = sd->skilllv; + break; + } + case BD_ENCORE: /* アンコール */ + if(!sd->skillid_dance){ //前回使用した踊りがないとだめ + clif_skill_fail(sd,skill_num,0,0); + return 0; + }else{ + sd->skillid_old = skill_num; + } + break; + } + + sd->skillid = skill_num; + sd->skilllv = skill_lv; + + switch(skill_num){ //事前にレベルが変わったりするスキル + case BD_LULLABY: /* 子守歌 */ + case BD_RICHMANKIM: /* ニヨルドの宴 */ + case BD_ETERNALCHAOS: /* 永遠の混沌 */ + case BD_DRUMBATTLEFIELD: /* 戦太鼓の響き */ + case BD_RINGNIBELUNGEN: /* ニーベルングの指輪 */ + case BD_ROKISWEIL: /* ロキの叫び */ + case BD_INTOABYSS: /* 深淵の中に */ + case BD_SIEGFRIED: /* 不死身のジークフリード */ + case BD_RAGNAROK: /* 神々の黄昏 */ + case CG_MOONLIT: /* 月明りの泉に落ちる花びら */ + { + int range=1; + int c=0; + map_foreachinarea(skill_check_condition_char_sub,sd->bl.m, + sd->bl.x-range,sd->bl.y-range, + sd->bl.x+range,sd->bl.y+range,BL_PC,&sd->bl,&c); + if(c<1){ + clif_skill_fail(sd,skill_num,0,0); + return 0; + }else if(c==99){ //相方不要設定だった + ; + }else{ + sd->skilllv=(c + skill_lv)/2; + } + } + break; + } + + if(!skill_check_condition(sd,0)) return 0; + + /* 射程と障害物チェック */ + range = skill_get_range(skill_num,skill_lv); + if(range < 0) + range = battle_get_range(&sd->bl) - (range + 1); + if(!battle_check_range(&sd->bl,bl,range) ) + return 0; + + if(bl->type==BL_PC) { + target_sd=(struct map_session_data*)bl; + if(target_sd && skill_num == ALL_RESURRECTION && !pc_isdead(target_sd)) + return 0; + } + if((skill_num != MO_CHAINCOMBO && + skill_num != MO_COMBOFINISH && + skill_num != MO_EXTREMITYFIST && + skill_num != CH_TIGERFIST && + skill_num != CH_CHAINCRUSH) || + (skill_num == MO_EXTREMITYFIST && sd->state.skill_flag) ) + pc_stopattack(sd); + + casttime=skill_castfix(&sd->bl, skill_get_cast( skill_num,skill_lv) ); + if(skill_num != SA_MAGICROD) + delay=skill_delayfix(&sd->bl, skill_get_delay( skill_num,skill_lv) ); + sd->state.skillcastcancel = skill_db[skill_num].castcancel; + + switch(skill_num){ /* 何か特殊な処理が必要 */ +// case AL_HEAL: /* ヒール */ +// if(battle_check_undead(battle_get_race(bl),battle_get_elem_type(bl))) +// forcecast=1; /* ヒールアタックなら詠唱エフェクト有り */ +// break; + case ALL_RESURRECTION: /* リザレクション */ + if(bl->type != BL_PC && battle_check_undead(battle_get_race(bl),battle_get_elem_type(bl))){ /* 敵がアンデッドなら */ + forcecast=1; /* ターンアンデットと同じ詠唱時間 */ + casttime=skill_castfix(&sd->bl, skill_get_cast(PR_TURNUNDEAD,skill_lv) ); + } + break; + case MO_FINGEROFFENSIVE: /* 指弾 */ + casttime += casttime * ((skill_lv > sd->spiritball)? sd->spiritball:skill_lv); + break; + case MO_CHAINCOMBO: /*連打掌*/ + target_id = sd->attacktarget; + if( sc_data && sc_data[SC_BLADESTOP].timer!=-1 ){ + struct block_list *tbl; + if((tbl=(struct block_list *)sc_data[SC_BLADESTOP].val4) == NULL) //ターゲットがいない? + return 0; + target_id = tbl->id; + } + break; + case MO_COMBOFINISH: /*猛龍拳*/ + case CH_TIGERFIST: /* 伏虎拳 */ + case CH_CHAINCRUSH: /* 連柱崩撃 */ + target_id = sd->attacktarget; + break; + +// -- moonsoul (altered to allow proper usage of extremity from new champion combos) +// + case MO_EXTREMITYFIST: /*阿修羅覇鳳拳*/ + if(sc_data && sc_data[SC_COMBO].timer != -1 && (sc_data[SC_COMBO].val1 == MO_COMBOFINISH || sc_data[SC_COMBO].val1 == CH_CHAINCRUSH)) { + casttime = 0; + target_id = sd->attacktarget; + } + forcecast=1; + break; + case SA_MAGICROD: + case SA_SPELLBREAKER: + forcecast=1; + break; + case WE_MALE: + case WE_FEMALE: + { + struct map_session_data *p_sd = NULL; + if((p_sd = pc_get_partner(sd)) == NULL) + return 0; + target_id = p_sd->bl.id; + //rangeをもう1回検査 + range = skill_get_range(skill_num,skill_lv); + if(range < 0) + range = battle_get_range(&sd->bl) - (range + 1); + if(!battle_check_range(&sd->bl,&p_sd->bl,range) ){ + return 0; + } + } + break; + case AS_SPLASHER: /* ベナムスプラッシャー */ + { + struct status_change *t_sc_data = battle_get_sc_data(bl); + if(t_sc_data && t_sc_data[SC_POISON].timer==-1){ + clif_skill_fail(sd,skill_num,0,10); + return 0; + } + } + break; + case PF_MEMORIZE: /* メモライズ */ + casttime = 12000; + break; + + } + + //メモライズ状態ならキャストタイムが1/3 + if(sc_data && sc_data[SC_MEMORIZE].timer != -1 && casttime > 0){ + casttime = casttime/3; + if((--sc_data[SC_MEMORIZE].val2)<=0) + skill_status_change_end(&sd->bl, SC_MEMORIZE, -1); + } + + if(battle_config.pc_skill_log) + printf("PC %d skill use target_id=%d skill=%d lv=%d cast=%d\n",sd->bl.id,target_id,skill_num,skill_lv,casttime); + +// if(sd->skillitem == skill_num) +// casttime = delay = 0; + + if( casttime>0 || forcecast ){ /* 詠唱が必要 */ + struct mob_data *md; + clif_skillcasting( &sd->bl, + sd->bl.id, target_id, 0,0, skill_num,casttime); + + /* 詠唱反応モンスター */ + if( bl->type==BL_MOB && (md=(struct mob_data *)bl) && mob_db[md->class].mode&0x10 && + md->state.state!=MS_ATTACK && sd->invincible_timer == -1){ + md->target_id=sd->bl.id; + md->state.targettype = ATTACKABLE; + md->min_chase=13; + } + } + + if( casttime<=0 ) /* 詠唱の無いものはキャンセルされない */ + sd->state.skillcastcancel=0; + + sd->skilltarget = target_id; +/* sd->cast_target_bl = bl; */ + sd->skillx = 0; + sd->skilly = 0; + sd->canact_tick = tick + casttime + delay; + sd->canmove_tick = tick; + if(!(battle_config.pc_cloak_check_type&2) && sc_data && sc_data[SC_CLOAKING].timer != -1 && sd->skillid != AS_CLOAKING) + skill_status_change_end(&sd->bl,SC_CLOAKING,-1); + if(casttime > 0) { + sd->skilltimer = add_timer( tick+casttime, skill_castend_id, sd->bl.id, 0 ); + if((skill = pc_checkskill(sd,SA_FREECAST)) > 0) { + sd->prev_speed = sd->speed; + sd->speed = sd->speed*(175 - skill*5)/100; + clif_updatestatus(sd,SP_SPEED); + } + else + pc_stop_walking(sd,0); + } + else { + if(skill_num != SA_CASTCANCEL) + sd->skilltimer = -1; + skill_castend_id(sd->skilltimer,tick,sd->bl.id,0); + } + + //マジックパワーの効果終了 + if(sc_data && sc_data[SC_MAGICPOWER].timer != -1 && skill_num != HW_MAGICPOWER) + skill_status_change_end(&sd->bl,SC_MAGICPOWER,-1); + + return 0; +} + +/*========================================== + * スキル使用(場所指定) + *------------------------------------------ + */ +int skill_use_pos( struct map_session_data *sd, + int skill_x, int skill_y, int skill_num, int skill_lv) +{ + struct block_list bl; + struct status_change *sc_data; + unsigned int tick; + int casttime=0,delay=0,skill,range; + + nullpo_retr(0, sd); + + if(pc_isdead(sd)) + return 0; + + if (skillnotok(skill_num, sd)) // [MoueJstr] + return 0; + + sc_data=sd->sc_data; + + if( sd->opt1>0 ) + return 0; + if(sc_data){ + if( sc_data[SC_DIVINA].timer!=-1 || + sc_data[SC_ROKISWEIL].timer!=-1 || + sc_data[SC_AUTOCOUNTER].timer != -1 || + sc_data[SC_STEELBODY].timer != -1 || + sc_data[SC_DANCING].timer!=-1 || + sc_data[SC_BERSERK].timer != -1 ) + return 0; /* 状態異常や沈黙など */ + } + + if(sd->status.option&2) + return 0; + + if(map[sd->bl.m].flag.gvg && (skill_num == SM_ENDURE || skill_num == AL_TELEPORT || skill_num == AL_WARP || + skill_num == WZ_ICEWALL || skill_num == TF_BACKSLIDING)) + return 0; + + sd->skillid = skill_num; + sd->skilllv = skill_lv; + sd->skillx = skill_x; + sd->skilly = skill_y; + if(!skill_check_condition(sd,0)) return 0; + + /* 射程と障害物チェック */ + bl.type = BL_NUL; + bl.m = sd->bl.m; + bl.x = skill_x; + bl.y = skill_y; + range = skill_get_range(skill_num,skill_lv); + if(range < 0) + range = battle_get_range(&sd->bl) - (range + 1); + if(!battle_check_range(&sd->bl,&bl,range) ) + return 0; + + pc_stopattack(sd); + + casttime=skill_castfix(&sd->bl, skill_get_cast( skill_num,skill_lv) ); + delay=skill_delayfix(&sd->bl, skill_get_delay( skill_num,skill_lv) ); + sd->state.skillcastcancel = skill_db[skill_num].castcancel; + + if(battle_config.pc_skill_log) + printf("PC %d skill use target_pos=(%d,%d) skill=%d lv=%d cast=%d\n",sd->bl.id,skill_x,skill_y,skill_num,skill_lv,casttime); + +// if(sd->skillitem == skill_num) +// casttime = delay = 0; + //メモライズ状態ならキャストタイムが1/3 + if(sc_data && sc_data[SC_MEMORIZE].timer != -1 && casttime > 0){ + casttime = casttime/3; + if((--sc_data[SC_MEMORIZE].val2)<=0) + skill_status_change_end(&sd->bl, SC_MEMORIZE, -1); + } + + if( casttime>0 ) /* 詠唱が必要 */ + clif_skillcasting( &sd->bl, + sd->bl.id, 0, skill_x,skill_y, skill_num,casttime); + + if( casttime<=0 ) /* 詠唱の無いものはキャンセルされない */ + sd->state.skillcastcancel=0; + + sd->skilltarget = 0; +/* sd->cast_target_bl = NULL; */ + tick=gettick(); + sd->canact_tick = tick + casttime + delay; + sd->canmove_tick = tick; + if(!(battle_config.pc_cloak_check_type&2) && sc_data && sc_data[SC_CLOAKING].timer != -1) + skill_status_change_end(&sd->bl,SC_CLOAKING,-1); + if(casttime > 0) { + sd->skilltimer = add_timer( tick+casttime, skill_castend_pos, sd->bl.id, 0 ); + if((skill = pc_checkskill(sd,SA_FREECAST)) > 0) { + sd->prev_speed = sd->speed; + sd->speed = sd->speed*(175 - skill*5)/100; + clif_updatestatus(sd,SP_SPEED); + } + else + pc_stop_walking(sd,0); + } + else { + sd->skilltimer = -1; + skill_castend_pos(sd->skilltimer,tick,sd->bl.id,0); + } + //マジックパワーの効果終了 + if(sc_data && sc_data[SC_MAGICPOWER].timer != -1 && skill_num != HW_MAGICPOWER) + skill_status_change_end(&sd->bl,SC_MAGICPOWER,-1); + + return 0; +} + +/*========================================== + * スキル詠唱キャンセル + *------------------------------------------ + */ +int skill_castcancel(struct block_list *bl,int type) +{ + int inf; + int ret=0; + + nullpo_retr(0, bl); + + if(bl->type==BL_PC){ + struct map_session_data *sd=(struct map_session_data *)bl; + unsigned long tick=gettick(); + nullpo_retr(0, sd); + sd->canact_tick=tick; + sd->canmove_tick = tick; + if( sd->skilltimer!=-1){ + if(pc_checkskill(sd,SA_FREECAST) > 0) { + sd->speed = sd->prev_speed; + clif_updatestatus(sd,SP_SPEED); + } + if(!type) { + if((inf = skill_get_inf( sd->skillid )) == 2 || inf == 32) + ret=delete_timer( sd->skilltimer, skill_castend_pos ); + else + ret=delete_timer( sd->skilltimer, skill_castend_id ); + if(ret<0) + printf("delete timer error : skillid : %d\n",sd->skillid); + } + else { + if((inf = skill_get_inf( sd->skillid_old )) == 2 || inf == 32) + ret=delete_timer( sd->skilltimer, skill_castend_pos ); + else + ret=delete_timer( sd->skilltimer, skill_castend_id ); + if(ret<0) + printf("delete timer error : skillid : %d\n",sd->skillid_old); + } + sd->skilltimer=-1; + clif_skillcastcancel(bl); + } + + return 0; + }else if(bl->type==BL_MOB){ + struct mob_data *md=(struct mob_data *)bl; + nullpo_retr(0, md); + if( md->skilltimer!=-1 ){ + if((inf = skill_get_inf( md->skillid )) == 2 || inf == 32) + ret=delete_timer( md->skilltimer, mobskill_castend_pos ); + else + ret=delete_timer( md->skilltimer, mobskill_castend_id ); + md->skilltimer=-1; + clif_skillcastcancel(bl); + } + if(ret<0) + printf("delete timer error : skillid : %d\n",md->skillid); + return 0; + } + return 1; +} +/*========================================= + * ブランディッシュスピア 初期範囲決定 + *---------------------------------------- + */ +void skill_brandishspear_first(struct square *tc,int dir,int x,int y){ + + nullpo_retv(tc); + + if(dir == 0){ + tc->val1[0]=x-2; + tc->val1[1]=x-1; + tc->val1[2]=x; + tc->val1[3]=x+1; + tc->val1[4]=x+2; + tc->val2[0]= + tc->val2[1]= + tc->val2[2]= + tc->val2[3]= + tc->val2[4]=y-1; + } + else if(dir==2){ + tc->val1[0]= + tc->val1[1]= + tc->val1[2]= + tc->val1[3]= + tc->val1[4]=x+1; + tc->val2[0]=y+2; + tc->val2[1]=y+1; + tc->val2[2]=y; + tc->val2[3]=y-1; + tc->val2[4]=y-2; + } + else if(dir==4){ + tc->val1[0]=x-2; + tc->val1[1]=x-1; + tc->val1[2]=x; + tc->val1[3]=x+1; + tc->val1[4]=x+2; + tc->val2[0]= + tc->val2[1]= + tc->val2[2]= + tc->val2[3]= + tc->val2[4]=y+1; + } + else if(dir==6){ + tc->val1[0]= + tc->val1[1]= + tc->val1[2]= + tc->val1[3]= + tc->val1[4]=x-1; + tc->val2[0]=y+2; + tc->val2[1]=y+1; + tc->val2[2]=y; + tc->val2[3]=y-1; + tc->val2[4]=y-2; + } + else if(dir==1){ + tc->val1[0]=x-1; + tc->val1[1]=x; + tc->val1[2]=x+1; + tc->val1[3]=x+2; + tc->val1[4]=x+3; + tc->val2[0]=y-4; + tc->val2[1]=y-3; + tc->val2[2]=y-1; + tc->val2[3]=y; + tc->val2[4]=y+1; + } + else if(dir==3){ + tc->val1[0]=x+3; + tc->val1[1]=x+2; + tc->val1[2]=x+1; + tc->val1[3]=x; + tc->val1[4]=x-1; + tc->val2[0]=y-1; + tc->val2[1]=y; + tc->val2[2]=y+1; + tc->val2[3]=y+2; + tc->val2[4]=y+3; + } + else if(dir==5){ + tc->val1[0]=x+1; + tc->val1[1]=x; + tc->val1[2]=x-1; + tc->val1[3]=x-2; + tc->val1[4]=x-3; + tc->val2[0]=y+3; + tc->val2[1]=y+2; + tc->val2[2]=y+1; + tc->val2[3]=y; + tc->val2[4]=y-1; + } + else if(dir==7){ + tc->val1[0]=x-3; + tc->val1[1]=x-2; + tc->val1[2]=x-1; + tc->val1[3]=x; + tc->val1[4]=x+1; + tc->val2[1]=y; + tc->val2[0]=y+1; + tc->val2[2]=y-1; + tc->val2[3]=y-2; + tc->val2[4]=y-3; + } + +} + +/*========================================= + * ブランディッシュスピア 方向判定 範囲拡張 + *----------------------------------------- + */ +void skill_brandishspear_dir(struct square *tc,int dir,int are){ + + int c; + + nullpo_retv(tc); + + for(c=0;c<5;c++){ + if(dir==0){ + tc->val2[c]+=are; + }else if(dir==1){ + tc->val1[c]-=are; tc->val2[c]+=are; + }else if(dir==2){ + tc->val1[c]-=are; + }else if(dir==3){ + tc->val1[c]-=are; tc->val2[c]-=are; + }else if(dir==4){ + tc->val2[c]-=are; + }else if(dir==5){ + tc->val1[c]+=are; tc->val2[c]-=are; + }else if(dir==6){ + tc->val1[c]+=are; + }else if(dir==7){ + tc->val1[c]+=are; tc->val2[c]+=are; + } + } +} + +/*========================================== + * ディボーション 有効確認 + *------------------------------------------ + */ +void skill_devotion(struct map_session_data *md,int target) +{ + // 総確認 + int n; + + nullpo_retv(md); + + for(n=0;n<5;n++){ + if(md->dev.val1[n]){ + struct map_session_data *sd = map_id2sd(md->dev.val1[n]); + // 相手が見つからない // 相手をディボしてるのが自分じゃない // 距離が離れてる + if( sd == NULL || (sd->sc_data && (md->bl.id != sd->sc_data[SC_DEVOTION].val1)) || skill_devotion3(&md->bl,md->dev.val1[n])){ + skill_devotion_end(md,sd,n); + } + } + } +} +void skill_devotion2(struct block_list *bl,int crusader) +{ + // 被ディボーションが歩いた時の距離チェック + struct map_session_data *sd = map_id2sd(crusader); + + nullpo_retv(bl); + + if(sd) skill_devotion3(&sd->bl,bl->id); +} +int skill_devotion3(struct block_list *bl,int target) +{ + // クルセが歩いた時の距離チェック + struct map_session_data *md; + struct map_session_data *sd; + int n,r=0; + + nullpo_retr(1, bl); + + if( (md = (struct map_session_data *)bl) == NULL || (sd = map_id2sd(target)) == NULL ) + return 1; + else + r = distance(bl->x,bl->y,sd->bl.x,sd->bl.y); + + if(pc_checkskill(sd,CR_DEVOTION)+6 < r){ // 許容範囲を超えてた + for(n=0;n<5;n++) + if(md->dev.val1[n]==target) + md->dev.val2[n]=0; // 離れた時は、糸を切るだけ + clif_devotion(md,sd->bl.id); + return 1; + } + return 0; +} + +void skill_devotion_end(struct map_session_data *md,struct map_session_data *sd,int target) +{ + // クルセと被ディボキャラのリセット + nullpo_retv(md); + nullpo_retv(sd); + + md->dev.val1[target]=md->dev.val2[target]=0; + if(sd && sd->sc_data){ + // skill_status_change_end(sd->bl,SC_DEVOTION,-1); + sd->sc_data[SC_DEVOTION].val1=0; + sd->sc_data[SC_DEVOTION].val2=0; + clif_status_change(&sd->bl,SC_DEVOTION,0); + clif_devotion(md,sd->bl.id); + } +} +/*========================================== + * オートスペル + *------------------------------------------ + */ +int skill_autospell(struct map_session_data *sd,int skillid) +{ + int skilllv; + int maxlv=1,lv; + + nullpo_retr(0, sd); + + skilllv = pc_checkskill(sd,SA_AUTOSPELL); + + if(skillid==MG_NAPALMBEAT) maxlv=3; + else if(skillid==MG_COLDBOLT || skillid==MG_FIREBOLT || skillid==MG_LIGHTNINGBOLT){ + if(skilllv==2) maxlv=1; + else if(skilllv==3) maxlv=2; + else if(skilllv>=4) maxlv=3; + } + else if(skillid==MG_SOULSTRIKE){ + if(skilllv==5) maxlv=1; + else if(skilllv==6) maxlv=2; + else if(skilllv>=7) maxlv=3; + } + else if(skillid==MG_FIREBALL){ + if(skilllv==8) maxlv=1; + else if(skilllv>=9) maxlv=2; + } + else if(skillid==MG_FROSTDIVER) maxlv=1; + else return 0; + + if(maxlv > (lv=pc_checkskill(sd,skillid))) + maxlv = lv; + + skill_status_change_start(&sd->bl,SC_AUTOSPELL,skilllv,skillid,maxlv,0, // val1:スキルID val2:使用最大Lv + skill_get_time(SA_AUTOSPELL,skilllv),0);// にしてみたけどbscriptが書き易い・・・? + return 0; +} + +/*========================================== + * ギャングスターパラダイス判定処理(foreachinarea) + *------------------------------------------ + */ + +static int skill_gangster_count(struct block_list *bl,va_list ap) +{ + int *c; + struct map_session_data *sd; + + nullpo_retr(0, bl); + nullpo_retr(0, ap); + + sd=(struct map_session_data*)bl; + c=va_arg(ap,int *); + + if(sd && c && pc_issit(sd) && pc_checkskill(sd,RG_GANGSTER) > 0) + (*c)++; + return 0; +} + +static int skill_gangster_in(struct block_list *bl,va_list ap) +{ + struct map_session_data *sd; + + nullpo_retr(0, bl); + nullpo_retr(0, ap); + + sd=(struct map_session_data*)bl; + if(sd && pc_issit(sd) && pc_checkskill(sd,RG_GANGSTER) > 0) + sd->state.gangsterparadise=1; + return 0; +} + +static int skill_gangster_out(struct block_list *bl,va_list ap) +{ + struct map_session_data *sd; + + nullpo_retr(0, bl); + nullpo_retr(0, ap); + + sd=(struct map_session_data*)bl; + if(sd && sd->state.gangsterparadise) + sd->state.gangsterparadise=0; + return 0; +} + +int skill_gangsterparadise(struct map_session_data *sd ,int type) +{ + int range=1; + int c=0; + + nullpo_retr(0, sd); + + if(pc_checkskill(sd,RG_GANGSTER) <= 0) + return 0; + + if(type==1) {/* 座った時の処理 */ + map_foreachinarea(skill_gangster_count,sd->bl.m, + sd->bl.x-range,sd->bl.y-range, + sd->bl.x+range,sd->bl.y+range,BL_PC,&c); + if(c > 0) {/*ギャングスター成功したら自分にもギャングスター属性付与*/ + map_foreachinarea(skill_gangster_in,sd->bl.m, + sd->bl.x-range,sd->bl.y-range, + sd->bl.x+range,sd->bl.y+range,BL_PC); + sd->state.gangsterparadise = 1; + } + return 0; + } + else if(type==0) {/* 立ち上がったときの処理 */ + map_foreachinarea(skill_gangster_count,sd->bl.m, + sd->bl.x-range,sd->bl.y-range, + sd->bl.x+range,sd->bl.y+range,BL_PC,&c); + if(c < 1) + map_foreachinarea(skill_gangster_out,sd->bl.m, + sd->bl.x-range,sd->bl.y-range, + sd->bl.x+range,sd->bl.y+range,BL_PC); + sd->state.gangsterparadise = 0; + return 0; + } + return 0; +} +/*========================================== + * 寒いジョーク・スクリーム判定処理(foreachinarea) + *------------------------------------------ + */ +int skill_frostjoke_scream(struct block_list *bl,va_list ap) +{ + struct block_list *src; + int skillnum,skilllv; + unsigned int tick; + + nullpo_retr(0, bl); + nullpo_retr(0, ap); + nullpo_retr(0, src=va_arg(ap,struct block_list*)); + + skillnum=va_arg(ap,int); + skilllv=va_arg(ap,int); + tick=va_arg(ap,unsigned int); + + if(src == bl)//自分には効かない + return 0; + + if(battle_check_target(src,bl,BCT_ENEMY) > 0) + skill_additional_effect(src,bl,skillnum,skilllv,BF_MISC,tick); + else if(battle_check_target(src,bl,BCT_PARTY) > 0) { + if(rand()%100 < 10)//PTメンバにも低確率でかかる(とりあえず10%) + skill_additional_effect(src,bl,skillnum,skilllv,BF_MISC,tick); + } + + return 0; +} + +/*========================================== + *アブラカダブラの使用スキル決定(決定スキルがダメなら0を返す) + *------------------------------------------ + */ +int skill_abra_dataset(int skilllv) +{ + int skill = rand()%331; + //dbに基づくレベル・確率判定 + if(skill_abra_db[skill].req_lv > skilllv || rand()%10000 >= skill_abra_db[skill].per) return 0; + //NPCスキルはダメ + if(skill >= NPC_PIERCINGATT && skill <= NPC_SUMMONMONSTER) return 0; + //演奏スキルはダメ + if(skill_is_danceskill(skill)) return 0; + + return skill; +} + +/*========================================== + * + *------------------------------------------ + */ +int skill_attack_area(struct block_list *bl,va_list ap) +{ + struct block_list *src,*dsrc; + int atk_type,skillid,skilllv,flag,type; + unsigned int tick; + + nullpo_retr(0, bl); + nullpo_retr(0, ap); + + atk_type = va_arg(ap,int); + if((src=va_arg(ap,struct block_list*)) == NULL) + return 0; + if((dsrc=va_arg(ap,struct block_list*)) == NULL) + return 0; + skillid=va_arg(ap,int); + skilllv=va_arg(ap,int); + tick=va_arg(ap,unsigned int); + flag=va_arg(ap,int); + type=va_arg(ap,int); + + if(battle_check_target(dsrc,bl,type) > 0) + skill_attack(atk_type,src,dsrc,bl,skillid,skilllv,tick,flag); + + return 0; +} +/*========================================== + * + *------------------------------------------ + */ +int skill_clear_element_field(struct block_list *bl) +{ + struct mob_data *md=NULL; + struct map_session_data *sd=NULL; + int i,skillid; + + nullpo_retr(0, bl); + + if(bl->type==BL_MOB) + md=(struct mob_data *)bl; + if(bl->type==BL_PC) + sd=(struct map_session_data *)bl; + + for(i=0;i<MAX_MOBSKILLUNITGROUP;i++){ + if(sd){ + skillid=sd->skillunit[i].skill_id; + if(skillid==SA_DELUGE||skillid==SA_VOLCANO||skillid==SA_VIOLENTGALE||skillid==SA_LANDPROTECTOR) + skill_delunitgroup(&sd->skillunit[i]); + }else if(md){ + skillid=md->skillunit[i].skill_id; + if(skillid==SA_DELUGE||skillid==SA_VOLCANO||skillid==SA_VIOLENTGALE||skillid==SA_LANDPROTECTOR) + skill_delunitgroup(&md->skillunit[i]); + } + } + return 0; +} +/*========================================== + * ランドプロテクターチェック(foreachinarea) + *------------------------------------------ + */ +int skill_landprotector(struct block_list *bl, va_list ap ) +{ + int skillid; + int *alive; + struct skill_unit *unit; + + nullpo_retr(0, bl); + nullpo_retr(0, ap); + + skillid=va_arg(ap,int); + alive=va_arg(ap,int *); + if((unit=(struct skill_unit *)bl) == NULL) + return 0; + + if(skillid==SA_LANDPROTECTOR){ + skill_delunit(unit); + }else{ + if(alive && unit->group->skill_id==SA_LANDPROTECTOR) + (*alive)=0; + } + return 0; +} +/*========================================== + * イドゥンの林檎の回復処理(foreachinarea) + *------------------------------------------ + */ +int skill_idun_heal(struct block_list *bl, va_list ap ) +{ + struct skill_unit *unit; + struct skill_unit_group *sg; + int heal; + + nullpo_retr(0, bl); + nullpo_retr(0, ap); + nullpo_retr(0, unit = va_arg(ap,struct skill_unit *)); + nullpo_retr(0, sg = unit->group); + + heal=30+sg->skill_lv*5+((sg->val1)>>16)*5+((sg->val1)&0xfff)/2; + + if(bl->type == BL_SKILL || bl->id == sg->src_id) + return 0; + + if(bl->type == BL_PC || bl->type == BL_MOB){ + clif_skill_nodamage(&unit->bl,bl,AL_HEAL,heal,1); + battle_heal(NULL,bl,heal,0,0); + } + return 0; +} + +/*========================================== + * 指定範囲内でsrcに対して有効なターゲットのblの数を数える(foreachinarea) + *------------------------------------------ + */ +int skill_count_target(struct block_list *bl, va_list ap ){ + struct block_list *src; + int *c; + + nullpo_retr(0, bl); + nullpo_retr(0, ap); + + if((src = va_arg(ap,struct block_list *)) == NULL) + return 0; + if((c = va_arg(ap,int *)) == NULL) + return 0; + if(battle_check_target(src,bl,BCT_ENEMY) > 0) + (*c)++; + return 0; +} +/*========================================== + * トラップ範囲処理(foreachinarea) + *------------------------------------------ + */ +int skill_trap_splash(struct block_list *bl, va_list ap ) +{ + struct block_list *src; + int tick; + int splash_count; + struct skill_unit *unit; + struct skill_unit_group *sg; + struct block_list *ss; + int i; + + nullpo_retr(0, bl); + nullpo_retr(0, ap); + nullpo_retr(0, src = va_arg(ap,struct block_list *)); + nullpo_retr(0, unit = (struct skill_unit *)src); + nullpo_retr(0, sg = unit->group); + nullpo_retr(0, ss = map_id2bl(sg->src_id)); + + tick = va_arg(ap,int); + splash_count = va_arg(ap,int); + + if(battle_check_target(src,bl,BCT_ENEMY) > 0){ + switch(sg->unit_id){ + case 0x95: /* サンドマン */ + case 0x96: /* フラッシャー */ + case 0x94: /* ショックウェーブトラップ */ + skill_additional_effect(ss,bl,sg->skill_id,sg->skill_lv,BF_MISC,tick); + break; + case 0x8f: /* ブラストマイン */ + case 0x98: /* クレイモアートラップ */ + for(i=0;i<splash_count;i++){ + skill_attack(BF_MISC,ss,src,bl,sg->skill_id,sg->skill_lv,tick,(sg->val2)?0x0500:0); + } + case 0x97: /* フリージングトラップ */ + skill_attack(BF_WEAPON, ss,src,bl,sg->skill_id,sg->skill_lv,tick,(sg->val2)?0x0500:0); + break; + default: + break; + } + } + + return 0; +} +/*---------------------------------------------------------------------------- + * ステータス異常 + *---------------------------------------------------------------------------- + */ + +/*========================================== + * ステータス異常タイマー範囲処理 + *------------------------------------------ + */ +int skill_status_change_timer_sub(struct block_list *bl, va_list ap ) +{ + struct block_list *src; + int type; + unsigned int tick; + + nullpo_retr(0, bl); + nullpo_retr(0, ap); + nullpo_retr(0, src=va_arg(ap,struct block_list*)); + type=va_arg(ap,int); + tick=va_arg(ap,unsigned int); + + if(bl->type!=BL_PC && bl->type!=BL_MOB) + return 0; + + switch( type ){ + case SC_SIGHT: /* サイト */ + case SC_CONCENTRATE: + if( (*battle_get_option(bl))&6 ){ + skill_status_change_end( bl, SC_HIDING, -1); + skill_status_change_end( bl, SC_CLOAKING, -1); + } + break; + case SC_RUWACH: /* ルアフ */ + if( (*battle_get_option(bl))&6 ){ + skill_status_change_end( bl, SC_HIDING, -1); + skill_status_change_end( bl, SC_CLOAKING, -1); + if(battle_check_target( src,bl, BCT_ENEMY ) > 0) { + struct status_change *sc_data = battle_get_sc_data(bl); + skill_attack(BF_MAGIC,src,src,bl,AL_RUWACH,sc_data[type].val1,tick,0); + } + } + break; + } + return 0; +} + +/*========================================== + * ステータス異常終了 + *------------------------------------------ + */ +int skill_status_change_end(struct block_list* bl, int type, int tid) +{ + struct status_change* sc_data; + int opt_flag=0, calc_flag = 0; + short *sc_count, *option, *opt1, *opt2, *opt3; + + nullpo_retr(0, bl); + if(bl->type!=BL_PC && bl->type!=BL_MOB) { + if(battle_config.error_log) + printf("skill_status_change_end: neither MOB nor PC !\n"); + return 0; + } + nullpo_retr(0, sc_data = battle_get_sc_data(bl)); + nullpo_retr(0, sc_count = battle_get_sc_count(bl)); + nullpo_retr(0, option = battle_get_option(bl)); + nullpo_retr(0, opt1 = battle_get_opt1(bl)); + nullpo_retr(0, opt2 = battle_get_opt2(bl)); + nullpo_retr(0, opt3 = battle_get_opt3(bl)); + + if ((*sc_count) > 0 && sc_data[type].timer != -1 && (sc_data[type].timer == tid || tid == -1)) { + + if (tid == -1) // タイマから呼ばれていないならタイマ削除をする + delete_timer(sc_data[type].timer,skill_status_change_timer); + + /* 該当の異常を正常に戻す */ + sc_data[type].timer=-1; + (*sc_count)--; + + switch(type){ /* 異常の種類ごとの処理 */ + case SC_PROVOKE: /* プロボック */ + case SC_CONCENTRATE: /* 集中力向上 */ + case SC_BLESSING: /* ブレッシング */ + case SC_ANGELUS: /* アンゼルス */ + case SC_INCREASEAGI: /* 速度上昇 */ + case SC_DECREASEAGI: /* 速度減少 */ + case SC_SIGNUMCRUCIS: /* シグナムクルシス */ + case SC_HIDING: + case SC_TWOHANDQUICKEN: /* 2HQ */ + case SC_ADRENALINE: /* アドレナリンラッシュ */ + case SC_ENCPOISON: /* エンチャントポイズン */ + case SC_IMPOSITIO: /* インポシティオマヌス */ + case SC_GLORIA: /* グロリア */ + case SC_LOUD: /* ラウドボイス */ + case SC_QUAGMIRE: /* クァグマイア */ + case SC_PROVIDENCE: /* プロヴィデンス */ + case SC_SPEARSQUICKEN: /* スピアクイッケン */ + case SC_VOLCANO: + case SC_DELUGE: + case SC_VIOLENTGALE: + case SC_ETERNALCHAOS: /* エターナルカオス */ + case SC_DRUMBATTLE: /* 戦太鼓の響き */ + case SC_NIBELUNGEN: /* ニーベルングの指輪 */ + case SC_SIEGFRIED: /* 不死身のジークフリード */ + case SC_WHISTLE: /* 口笛 */ + case SC_ASSNCROS: /* 夕陽のアサシンクロス */ + case SC_HUMMING: /* ハミング */ + case SC_DONTFORGETME: /* 私を忘れないで */ + case SC_FORTUNE: /* 幸運のキス */ + case SC_SERVICE4U: /* サービスフォーユー */ + case SC_EXPLOSIONSPIRITS: // 爆裂波動 + case SC_STEELBODY: // 金剛 + case SC_DEFENDER: + case SC_SPEEDPOTION0: /* 増速ポーション */ + case SC_SPEEDPOTION1: + case SC_SPEEDPOTION2: + case SC_APPLEIDUN: /* イドゥンの林檎 */ + case SC_RIDING: + case SC_BLADESTOP_WAIT: + case SC_AURABLADE: /* オーラブレード */ + case SC_PARRYING: /* パリイング */ + case SC_CONCENTRATION: /* コンセントレーション */ + case SC_TENSIONRELAX: /* テンションリラックス */ + case SC_ASSUMPTIO: /* アシャンプティオ */ + case SC_WINDWALK: /* ウインドウォーク */ + case SC_TRUESIGHT: /* トゥルーサイト */ + case SC_SPIDERWEB: /* スパイダーウェッブ */ + case SC_MAGICPOWER: /* 魔法力増幅 */ + case SC_CHASEWALK: + case SC_ATKPOT: /* attack potion [Valaris] */ + case SC_MATKPOT: /* magic attack potion [Valaris] */ + case SC_WEDDING: //結婚用(結婚衣裳になって歩くのが遅いとか) + case SC_MELTDOWN: /* メルトダウン */ + calc_flag = 1; + break; + case SC_BERSERK: /* バーサーク */ + calc_flag = 1; + clif_status_change(bl,SC_INCREASEAGI,0); /* アイコン消去 */ + break; + case SC_DEVOTION: /* ディボーション */ + { + struct map_session_data *md = map_id2sd(sc_data[type].val1); + sc_data[type].val1=sc_data[type].val2=0; + skill_devotion(md,bl->id); + calc_flag = 1; + } + break; + case SC_BLADESTOP: + { + struct status_change *t_sc_data = battle_get_sc_data((struct block_list *)sc_data[type].val4); + //片方が切れたので相手の白刃状態が切れてないのなら解除 + if(t_sc_data && t_sc_data[SC_BLADESTOP].timer!=-1) + skill_status_change_end((struct block_list *)sc_data[type].val4,SC_BLADESTOP,-1); + + if(sc_data[type].val2==2) + clif_bladestop((struct block_list *)sc_data[type].val3,(struct block_list *)sc_data[type].val4,0); + } + break; + case SC_DANCING: + { + struct map_session_data *dsd; + struct status_change *d_sc_data; + if(sc_data[type].val4 && (dsd=map_id2sd(sc_data[type].val4))){ + d_sc_data = dsd->sc_data; + //合奏で相手がいる場合相手のval4を0にする + if(d_sc_data && d_sc_data[type].timer!=-1) + d_sc_data[type].val4=0; + } + } + calc_flag = 1; + break; + case SC_GRAFFITI: + { + struct skill_unit_group *sg=(struct skill_unit_group *)sc_data[type].val4; //val4がグラフィティのgroup_id + if(sg) + skill_delunitgroup(sg); + } + break; + case SC_NOCHAT: //チャット禁止状態 + { + struct map_session_data *sd=NULL; + if(bl->type == BL_PC && (sd=(struct map_session_data *)bl)){ + sd->status.manner = 0; + clif_updatestatus(sd,SP_MANNER); + } + } + break; + case SC_SPLASHER: /* ベナムスプラッシャー */ + { + struct block_list *src=map_id2bl(sc_data[type].val3); + if(src && tid!=-1){ + //自分にダメージ&周囲3*3にダメージ + skill_castend_damage_id(src, bl,sc_data[type].val2,sc_data[type].val1,gettick(),0 ); + } + } + break; + case SC_SELFDESTRUCTION: /* 自爆 */ + { + //自分のダメージは0にして + struct mob_data *md=NULL; + if(bl->type == BL_MOB && (md=(struct mob_data*)bl)) + skill_castend_damage_id(bl, bl,sc_data[type].val2,sc_data[type].val1,gettick(),0 ); + } + break; + /* option1 */ + case SC_FREEZE: + sc_data[type].val3 = 0; + break; + + /* option2 */ + case SC_POISON: /* 毒 */ + case SC_BLIND: /* 暗黒 */ + case SC_CURSE: + calc_flag = 1; + break; + } + + if(bl->type==BL_PC && type<SC_SENDMAX) + clif_status_change(bl,type,0); /* アイコン消去 */ + + switch(type){ /* 正常に戻るときなにか処理が必要 */ + case SC_STONE: + case SC_FREEZE: + case SC_STAN: + case SC_SLEEP: + *opt1 = 0; + opt_flag = 1; + break; + + case SC_POISON: + case SC_CURSE: + case SC_SILENCE: + case SC_BLIND: + *opt2 &= ~(1<<(type-SC_POISON)); + opt_flag = 1; + break; + + case SC_SIGNUMCRUCIS: + *opt2 &= ~0x40; + opt_flag = 1; + break; + + case SC_HIDING: + case SC_CLOAKING: + *option &= ~((type == SC_HIDING) ? 2 : 4); + opt_flag = 1 ; + break; + + case SC_CHASEWALK: + *option &= ~16388; + opt_flag = 1 ; + break; + + case SC_SIGHT: + *option &= ~1; + opt_flag = 1; + break; + case SC_WEDDING: //結婚用(結婚衣裳になって歩くのが遅いとか) + *option &= ~4096; + opt_flag = 1; + break; + case SC_RUWACH: + *option &= ~8192; + opt_flag = 1; + break; + + //opt3 + case SC_TWOHANDQUICKEN: /* 2HQ */ + case SC_SPEARSQUICKEN: /* スピアクイッケン */ + case SC_CONCENTRATION: /* コンセントレーション */ + *opt3 &= ~1; + break; + case SC_OVERTHRUST: /* オーバースラスト */ + *opt3 &= ~2; + break; + case SC_ENERGYCOAT: /* エナジーコート */ + *opt3 &= ~4; + break; + case SC_EXPLOSIONSPIRITS: // 爆裂波動 + *opt3 &= ~8; + break; + case SC_STEELBODY: // 金剛 + *opt3 &= ~16; + break; + case SC_BLADESTOP: /* 白刃取り */ + *opt3 &= ~32; + break; + case SC_BERSERK: /* バーサーク */ + *opt3 &= ~128; + break; + case SC_MARIONETTE: /* マリオネットコントロール */ + *opt3 &= ~1024; + break; + case SC_ASSUMPTIO: /* アスムプティオ */ + *opt3 &= ~2048; + break; + } + + if (night_flag == 1 && (*opt2 & STATE_BLIND) == 0 && bl->type == BL_PC) { // by [Yor] + *opt2 |= STATE_BLIND; + opt_flag = 1; + } + + if(opt_flag) /* optionの変更を伝える */ + clif_changeoption(bl); + + if (bl->type == BL_PC && calc_flag) + pc_calcstatus((struct map_session_data *)bl,0); /* ステータス再計算 */ + } + + return 0; +} +/*========================================== + * ステータス異常終了タイマー + *------------------------------------------ + */ +int skill_status_change_timer(int tid, unsigned int tick, int id, int data) +{ + int type=data; + struct block_list *bl; + struct map_session_data *sd=NULL; + struct status_change *sc_data; + //short *sc_count; //使ってない? + + if( (bl=map_id2bl(id)) == NULL ) + return 0; //該当IDがすでに消滅しているというのはいかにもありそうなのでスルーしてみる + nullpo_retr(0, sc_data=battle_get_sc_data(bl)); + + if(bl->type==BL_PC) + sd=(struct map_session_data *)bl; + + //sc_count=battle_get_sc_count(bl); //使ってない? + + if(sc_data[type].timer != tid) { + if(battle_config.error_log) + printf("skill_status_change_timer %d != %d\n",tid,sc_data[type].timer); + } + + switch(type){ /* 特殊な処理になる場合 */ + case SC_MAXIMIZEPOWER: /* マキシマイズパワー */ + case SC_CLOAKING: /* クローキング */ + case SC_CHASEWALK: + if(sd){ + if( sd->status.sp > 0 ){ /* SP切れるまで持続 */ + sd->status.sp--; + clif_updatestatus(sd,SP_SP); + sc_data[type].timer=add_timer( /* タイマー再設定 */ + sc_data[type].val2+tick, skill_status_change_timer, + bl->id, data); + return 0; + } + } + break; + + case SC_HIDING: /* ハイディング */ + if(sd){ /* SPがあって、時間制限の間は持続 */ + if( sd->status.sp > 0 && (--sc_data[type].val2)>0 ){ + if(sc_data[type].val2 % (sc_data[type].val1+3) ==0 ){ + sd->status.sp--; + clif_updatestatus(sd,SP_SP); + } + sc_data[type].timer=add_timer( /* タイマー再設定 */ + 1000+tick, skill_status_change_timer, + bl->id, data); + return 0; + } + } + break; + + case SC_SIGHT: /* サイト */ + { + const int range=7; + map_foreachinarea( skill_status_change_timer_sub, + bl->m, bl->x-range, bl->y-range, bl->x+range,bl->y+range,0, + bl,type,tick); + + if( (--sc_data[type].val2)>0 ){ + sc_data[type].timer=add_timer( /* タイマー再設定 */ + 250+tick, skill_status_change_timer, + bl->id, data); + return 0; + } + } + break; + case SC_RUWACH: /* ルアフ */ + { + const int range=5; + map_foreachinarea( skill_status_change_timer_sub, + bl->m, bl->x-range, bl->y-range, bl->x+range,bl->y+range,0, + bl,type,tick); + + if( (--sc_data[type].val2)>0 ){ + sc_data[type].timer=add_timer( /* タイマー再設定 */ + 250+tick, skill_status_change_timer, + bl->id, data); + return 0; + } + } + break; + + case SC_SIGNUMCRUCIS: /* シグナムクルシス */ + { + int race = battle_get_race(bl); + if(race == 6 || battle_check_undead(race,battle_get_elem_type(bl))) { + sc_data[type].timer=add_timer(1000*600+tick,skill_status_change_timer, bl->id, data ); + return 0; + } + } + break; + + case SC_PROVOKE: /* プロボック/オートバーサーク */ + if(sc_data[type].val2!=0){ /* オートバーサーク(1秒ごとにHPチェック) */ + if(sd && sd->status.hp>sd->status.max_hp>>2) /* 停止 */ + break; + sc_data[type].timer=add_timer( 1000+tick,skill_status_change_timer, bl->id, data ); + return 0; + } + break; + + case SC_WATERBALL: /* ウォーターボール */ + { + struct block_list *target=map_id2bl(sc_data[type].val2); + if(target==NULL || target->prev==NULL) + break; + skill_attack(BF_MAGIC,bl,bl,target,WZ_WATERBALL,sc_data[type].val1,tick,0); + if((--sc_data[type].val3)>0) { + sc_data[type].timer=add_timer( 150+tick,skill_status_change_timer, bl->id, data ); + return 0; + } + } + break; + + case SC_ENDURE: /* インデュア */ + if(sd && sd->special_state.infinite_endure) { + sc_data[type].timer=add_timer( 1000*600+tick,skill_status_change_timer, bl->id, data ); + sc_data[type].val2=1; + return 0; + } + break; + + case SC_DISSONANCE: /* 不協和音 */ + if( (--sc_data[type].val2)>0){ + struct skill_unit *unit= + (struct skill_unit *)sc_data[type].val4; + struct block_list *src; + + if(!unit || !unit->group) + break; + src=map_id2bl(unit->group->src_id); + if(!src) + break; + skill_attack(BF_MISC,src,&unit->bl,bl,unit->group->skill_id,sc_data[type].val1,tick,0); + sc_data[type].timer=add_timer(skill_get_time2(unit->group->skill_id,unit->group->skill_lv)+tick, + skill_status_change_timer, bl->id, data ); + return 0; + } + break; + + case SC_LULLABY: /* 子守唄 */ + if( (--sc_data[type].val2)>0){ + struct skill_unit *unit= + (struct skill_unit *)sc_data[type].val4; + if(!unit || !unit->group || unit->group->src_id==bl->id) + break; + skill_additional_effect(bl,bl,unit->group->skill_id,sc_data[type].val1,BF_LONG|BF_SKILL|BF_MISC,tick); + sc_data[type].timer=add_timer(skill_get_time(unit->group->skill_id,unit->group->skill_lv)/10+tick, + skill_status_change_timer, bl->id, data ); + return 0; + } + break; + + case SC_STONE: + if(sc_data[type].val2 != 0) { + short *opt1 = battle_get_opt1(bl); + sc_data[type].val2 = 0; + sc_data[type].val4 = 0; + battle_stopwalking(bl,1); + if(opt1) { + *opt1 = 1; + clif_changeoption(bl); + } + sc_data[type].timer=add_timer(1000+tick,skill_status_change_timer, bl->id, data ); + return 0; + } + else if( (--sc_data[type].val3) > 0) { + int hp = battle_get_max_hp(bl); + if((++sc_data[type].val4)%5 == 0 && battle_get_hp(bl) > hp>>2) { + hp = hp/100; + if(hp < 1) hp = 1; + if(bl->type == BL_PC) + pc_heal((struct map_session_data *)bl,-hp,0); + else if(bl->type == BL_MOB){ + struct mob_data *md; + if((md=((struct mob_data *)bl)) == NULL) + break; + md->hp -= hp; + } + } + sc_data[type].timer=add_timer(1000+tick,skill_status_change_timer, bl->id, data ); + return 0; + } + break; + case SC_POISON: + if(sc_data[SC_SLOWPOISON].timer == -1) { + if( (--sc_data[type].val3) > 0) { + int hp = battle_get_max_hp(bl); + if(battle_get_hp(bl) > hp>>2) { + if(bl->type == BL_PC) { + hp = 3 + hp*3/200; + pc_heal((struct map_session_data *)bl,-hp,0); + } + else if(bl->type == BL_MOB) { + struct mob_data *md; + if((md=((struct mob_data *)bl)) == NULL) + break; + hp = 3 + hp/200; + md->hp -= hp; + } + } + sc_data[type].timer=add_timer(1000+tick,skill_status_change_timer, bl->id, data ); + } + } + else + sc_data[type].timer=add_timer(1000+tick,skill_status_change_timer, bl->id, data ); + break; + case SC_TENSIONRELAX: /* テンションリラックス */ + if(sd){ /* SPがあって、HPが満タンでなければ継続 */ + if( sd->status.sp > 12 && sd->status.max_hp > sd->status.hp ){ + if(sc_data[type].val2 % (sc_data[type].val1+3) ==0 ){ + sd->status.sp -= 12; + clif_updatestatus(sd,SP_SP); + } + sc_data[type].timer=add_timer( /* タイマー再設定 */ + 10000+tick, skill_status_change_timer, + bl->id, data); + return 0; + } + if(sd->status.max_hp <= sd->status.hp) + skill_status_change_end(&sd->bl,SC_TENSIONRELAX,-1); + } + break; + + /* 時間切れ無し?? */ + case SC_AETERNA: + case SC_TRICKDEAD: + case SC_RIDING: + case SC_FALCON: + case SC_WEIGHT50: + case SC_WEIGHT90: + case SC_MAGICPOWER: /* 魔法力増幅 */ + case SC_REJECTSWORD: /* リジェクトソード */ + case SC_MEMORIZE: /* メモライズ */ + case SC_BROKNWEAPON: + case SC_BROKNARMOR: + if(sc_data[type].timer==tid) + sc_data[type].timer=add_timer( 1000*600+tick,skill_status_change_timer, bl->id, data ); + return 0; + + case SC_DANCING: //ダンススキルの時間SP消費 + { + int s=0; + if(sd){ + if(sd->status.sp > 0 && (--sc_data[type].val3)>0){ + switch(sc_data[type].val1){ + case BD_RICHMANKIM: /* ニヨルドの宴 3秒にSP1 */ + case BD_DRUMBATTLEFIELD: /* 戦太鼓の響き 3秒にSP1 */ + case BD_RINGNIBELUNGEN: /* ニーベルングの指輪 3秒にSP1 */ + case BD_SIEGFRIED: /* 不死身のジークフリード 3秒にSP1 */ + case BA_DISSONANCE: /* 不協和音 3秒でSP1 */ + case BA_ASSASSINCROSS: /* 夕陽のアサシンクロス 3秒でSP1 */ + case DC_UGLYDANCE: /* 自分勝手なダンス 3秒でSP1 */ + s=3; + break; + case BD_LULLABY: /* 子守歌 4秒にSP1 */ + case BD_ETERNALCHAOS: /* 永遠の混沌 4秒にSP1 */ + case BD_ROKISWEIL: /* ロキの叫び 4秒にSP1 */ + case DC_FORTUNEKISS: /* 幸運のキス 4秒でSP1 */ + s=4; + break; + case BD_INTOABYSS: /* 深淵の中に 5秒にSP1 */ + case BA_WHISTLE: /* 口笛 5秒でSP1 */ + case DC_HUMMING: /* ハミング 5秒でSP1 */ + case BA_POEMBRAGI: /* ブラギの詩 5秒でSP1 */ + case DC_SERVICEFORYOU: /* サービスフォーユー 5秒でSP1 */ + s=5; + break; + case BA_APPLEIDUN: /* イドゥンの林檎 6秒でSP1 */ + s=6; + break; + case DC_DONTFORGETME: /* 私を忘れないで… 10秒でSP1 */ + case CG_MOONLIT: /* 月明りの泉に落ちる花びら 10秒でSP1? */ + s=10; + break; + } + if(s && ((sc_data[type].val3 % s) == 0)){ + sd->status.sp--; + clif_updatestatus(sd,SP_SP); + } + sc_data[type].timer=add_timer( /* タイマー再設定 */ + 1000+tick, skill_status_change_timer, + bl->id, data); + return 0; + } + } + } + break; + case SC_BERSERK: /* バーサーク */ + if(sd){ /* HPが100以上なら継続 */ + if( (sd->status.hp - sd->status.hp/100) > 100 ){ + sd->status.hp -= sd->status.hp/100; + clif_updatestatus(sd,SP_HP); + sc_data[type].timer=add_timer( /* タイマー再設定 */ + 15000+tick, skill_status_change_timer, + bl->id, data); + return 0; + } + } + break; + case SC_WEDDING: //結婚用(結婚衣裳になって歩くのが遅いとか) + if(sd){ + time_t timer; + if(time(&timer) < ((sc_data[type].val2) + 3600)){ //1時間たっていないので継続 + sc_data[type].timer=add_timer( /* タイマー再設定 */ + 10000+tick, skill_status_change_timer, + bl->id, data); + return 0; + } + } + break; + case SC_NOCHAT: //チャット禁止状態 + if(sd && battle_config.muting_players){ + time_t timer; + if((++sd->status.manner) && time(&timer) < ((sc_data[type].val2) + 60*(0-sd->status.manner))){ //開始からstatus.manner分経ってないので継続 + clif_updatestatus(sd,SP_MANNER); + sc_data[type].timer=add_timer( /* タイマー再設定(60秒) */ + 60000+tick, skill_status_change_timer, + bl->id, data); + return 0; + } + } + break; + case SC_SELFDESTRUCTION: /* 自爆 */ + if(--sc_data[type].val3>0){ + struct mob_data *md; + if(bl->type==BL_MOB && (md=(struct mob_data *)bl) && md->speed > 250){ + md->speed -= 250; + md->next_walktime=tick; + } + sc_data[type].timer=add_timer( /* タイマー再設定 */ + 1000+tick, skill_status_change_timer, + bl->id, data); + return 0; + } + break; + } + + return skill_status_change_end( bl,type,tid ); +} + +/*========================================== + * ステータス異常終了 + *------------------------------------------ + */ +int skill_encchant_eremental_end(struct block_list *bl,int type) +{ + struct status_change *sc_data; + + nullpo_retr(0, bl); + nullpo_retr(0, sc_data=battle_get_sc_data(bl)); + + if( type!=SC_ENCPOISON && sc_data[SC_ENCPOISON].timer!=-1 ) /* エンチャントポイズン解除 */ + skill_status_change_end(bl,SC_ENCPOISON,-1); + if( type!=SC_ASPERSIO && sc_data[SC_ASPERSIO].timer!=-1 ) /* アスペルシオ解除 */ + skill_status_change_end(bl,SC_ASPERSIO,-1); + if( type!=SC_FLAMELAUNCHER && sc_data[SC_FLAMELAUNCHER].timer!=-1 ) /* フレイムランチャ解除 */ + skill_status_change_end(bl,SC_FLAMELAUNCHER,-1); + if( type!=SC_FROSTWEAPON && sc_data[SC_FROSTWEAPON].timer!=-1 ) /* フロストウェポン解除 */ + skill_status_change_end(bl,SC_FROSTWEAPON,-1); + if( type!=SC_LIGHTNINGLOADER && sc_data[SC_LIGHTNINGLOADER].timer!=-1 ) /* ライトニングローダー解除 */ + skill_status_change_end(bl,SC_LIGHTNINGLOADER,-1); + if( type!=SC_SEISMICWEAPON && sc_data[SC_SEISMICWEAPON].timer!=-1 ) /* サイスミックウェポン解除 */ + skill_status_change_end(bl,SC_SEISMICWEAPON,-1); + + return 0; +} +/*========================================== + * ステータス異常開始 + *------------------------------------------ + */ +int skill_status_change_start(struct block_list *bl, int type, int val1, int val2, int val3, int val4, int tick, int flag) +{ + struct map_session_data *sd = NULL; + struct status_change* sc_data; + short *sc_count, *option, *opt1, *opt2, *opt3; + int opt_flag = 0, calc_flag = 0,updateflag = 0, race, mode, elem, undead_flag; + int scdef=0; + + nullpo_retr(0, bl); + if(bl->type == BL_SKILL) + return 0; + nullpo_retr(0, sc_data=battle_get_sc_data(bl)); + nullpo_retr(0, sc_count=battle_get_sc_count(bl)); + nullpo_retr(0, option=battle_get_option(bl)); + nullpo_retr(0, opt1=battle_get_opt1(bl)); + nullpo_retr(0, opt2=battle_get_opt2(bl)); + nullpo_retr(0, opt3=battle_get_opt3(bl)); + + + race=battle_get_race(bl); + mode=battle_get_mode(bl); + elem=battle_get_elem_type(bl); + undead_flag=battle_check_undead(race,elem); + + if(type == SC_AETERNA && (sc_data[SC_STONE].timer != -1 || sc_data[SC_FREEZE].timer != -1) ) + return 0; + + switch(type){ + case SC_STONE: + case SC_FREEZE: + scdef=3+battle_get_mdef(bl)+battle_get_luk(bl)/3; + break; + case SC_STAN: + case SC_SILENCE: + case SC_POISON: + scdef=3+battle_get_vit(bl)+battle_get_luk(bl)/3; + break; + case SC_SLEEP: + case SC_BLIND: + scdef=3+battle_get_int(bl)+battle_get_luk(bl)/3; + break; + case SC_CURSE: + scdef=3+battle_get_luk(bl); + break; + +// case SC_CONFUSION: + default: + scdef=0; + } + if(scdef>=100) + return 0; + if(bl->type==BL_PC){ + sd=(struct map_session_data *)bl; + if( sd && type == SC_ADRENALINE && !(skill_get_weapontype(BS_ADRENALINE)&(1<<sd->status.weapon))) + return 0; + + if(SC_STONE<=type && type<=SC_BLIND){ /* カードによる耐性 */ + if( sd && sd->reseff[type-SC_STONE] > 0 && rand()%10000<sd->reseff[type-SC_STONE]){ + if(battle_config.battle_log) + printf("PC %d skill_sc_start: cardによる異常耐性発動\n",sd->bl.id); + return 0; + } + } + } + else if(bl->type == BL_MOB) { + } + else { + if(battle_config.error_log) + printf("skill_status_change_start: neither MOB nor PC !\n"); + return 0; + } + + if(type==SC_FREEZE && undead_flag && !(flag&1)) + return 0; + + if((type == SC_ADRENALINE || type == SC_WEAPONPERFECTION || type == SC_OVERTHRUST) && + sc_data[type].timer != -1 && sc_data[type].val2 && !val2) + return 0; + + if(mode & 0x20 && (type==SC_STONE || type==SC_FREEZE || + type==SC_STAN || type==SC_SLEEP || type==SC_SILENCE || type==SC_QUAGMIRE || type == SC_DECREASEAGI || type == SC_SIGNUMCRUCIS || type == SC_PROVOKE || + (type == SC_BLESSING && (undead_flag || race == 6))) && !(flag&1)){ + /* ボスには効かない(ただしカードによる効果は適用される) */ + return 0; + } + if(type==SC_FREEZE || type==SC_STAN || type==SC_SLEEP) + battle_stopwalking(bl,1); + + if(sc_data[type].timer != -1){ /* すでに同じ異常になっている場合タイマ解除 */ + if(sc_data[type].val1 > val1 && type != SC_COMBO && type != SC_DANCING && type != SC_DEVOTION && + type != SC_SPEEDPOTION0 && type != SC_SPEEDPOTION1 && type != SC_SPEEDPOTION2 + && type != SC_ATKPOT && type != SC_MATKPOT) // added atk and matk potions [Valaris] + return 0; + if(type >=SC_STAN && type <= SC_BLIND) + return 0;/* 継ぎ足しができない状態異常である時は状態異常を行わない */ + if(type == SC_GRAFFITI){ //異常中にもう一度状態異常になった時に解除してから再度かかる + skill_status_change_end(bl,type,-1); + }else{ + (*sc_count)--; + delete_timer(sc_data[type].timer, skill_status_change_timer); + sc_data[type].timer = -1; + } + } + + switch(type){ /* 異常の種類ごとの処理 */ + case SC_PROVOKE: /* プロボック */ + calc_flag = 1; + if(tick <= 0) tick = 1000; /* (オートバーサーク) */ + break; + case SC_ENDURE: /* インデュア */ + if(tick <= 0) tick = 1000 * 60; + break; + case SC_CONCENTRATE: /* 集中力向上 */ + calc_flag = 1; + break; + case SC_BLESSING: /* ブレッシング */ + { + if(bl->type == BL_PC || (!undead_flag && race != 6)) { + if(sc_data[SC_CURSE].timer!=-1 ) + skill_status_change_end(bl,SC_CURSE,-1); + if(sc_data[SC_STONE].timer!=-1 && sc_data[SC_STONE].val2 == 0) + skill_status_change_end(bl,SC_STONE,-1); + } + calc_flag = 1; + } + break; + case SC_ANGELUS: /* アンゼルス */ + calc_flag = 1; + break; + case SC_INCREASEAGI: /* 速度上昇 */ + calc_flag = 1; + if(sc_data[SC_DECREASEAGI].timer!=-1 ) + skill_status_change_end(bl,SC_DECREASEAGI,-1); + if(sc_data[SC_WINDWALK].timer!=-1 ) /* ウインドウォーク */ + skill_status_change_end(bl,SC_WINDWALK,-1); + break; + case SC_DECREASEAGI: /* 速度減少 */ + calc_flag = 1; + if(sc_data[SC_INCREASEAGI].timer!=-1 ) + skill_status_change_end(bl,SC_INCREASEAGI,-1); + break; + case SC_SIGNUMCRUCIS: /* シグナムクルシス */ + calc_flag = 1; +// val2 = 14 + val1; + val2 = 10 + val1*2; + tick = 600*1000; + clif_emotion(bl,4); + break; + case SC_SLOWPOISON: + if(sc_data[SC_POISON].timer == -1 ) + return 0; + break; + case SC_TWOHANDQUICKEN: /* 2HQ */ + *opt3 |= 1; + calc_flag = 1; + break; + case SC_ADRENALINE: /* アドレナリンラッシュ */ + calc_flag = 1; + break; + case SC_WEAPONPERFECTION: /* ウェポンパーフェクション */ + if(battle_config.party_skill_penaly && !val2) tick /= 5; + break; + case SC_OVERTHRUST: /* オーバースラスト */ + *opt3 |= 2; + if(battle_config.party_skill_penaly && !val2) tick /= 10; + break; + case SC_MAXIMIZEPOWER: /* マキシマイズパワー(SPが1減る時間,val2にも) */ + if(bl->type == BL_PC) + val2 = tick; + else + tick = 5000*val1; + break; + case SC_ENCPOISON: /* エンチャントポイズン */ + calc_flag = 1; + val2=(((val1 - 1) / 2) + 3)*100; /* 毒付与確率 */ + skill_encchant_eremental_end(bl,SC_ENCPOISON); + break; + case SC_POISONREACT: /* ポイズンリアクト */ + break; + case SC_IMPOSITIO: /* インポシティオマヌス */ + calc_flag = 1; + break; + case SC_ASPERSIO: /* アスペルシオ */ + skill_encchant_eremental_end(bl,SC_ASPERSIO); + break; + case SC_SUFFRAGIUM: /* サフラギム */ + case SC_BENEDICTIO: /* 聖体 */ + case SC_MAGNIFICAT: /* マグニフィカート */ + case SC_AETERNA: /* エーテルナ */ + break; + case SC_ENERGYCOAT: /* エナジーコート */ + *opt3 |= 4; + break; + case SC_MAGICROD: + val2 = val1*20; + break; + case SC_KYRIE: /* キリエエレイソン */ + val2 = battle_get_max_hp(bl) * (val1 * 2 + 10) / 100;/* 耐久度 */ + val3 = (val1 / 2 + 5); /* 回数 */ +// -- moonsoul (added to undo assumptio status if target has it) + if(sc_data[SC_ASSUMPTIO].timer!=-1 ) + skill_status_change_end(bl,SC_ASSUMPTIO,-1); + break; + case SC_MINDBREAKER: + calc_flag = 1; + if(tick <= 0) tick = 1000; /* (オートバーサーク) */ + case SC_GLORIA: /* グロリア */ + calc_flag = 1; + break; + case SC_LOUD: /* ラウドボイス */ + calc_flag = 1; + break; + case SC_TRICKDEAD: /* 死んだふり */ + break; + case SC_QUAGMIRE: /* クァグマイア */ + calc_flag = 1; + if(sc_data[SC_CONCENTRATE].timer!=-1 ) /* 集中力向上解除 */ + skill_status_change_end(bl,SC_CONCENTRATE,-1); + if(sc_data[SC_INCREASEAGI].timer!=-1 ) /* 速度上昇解除 */ + skill_status_change_end(bl,SC_INCREASEAGI,-1); + if(sc_data[SC_TWOHANDQUICKEN].timer!=-1 ) + skill_status_change_end(bl,SC_TWOHANDQUICKEN,-1); + if(sc_data[SC_SPEARSQUICKEN].timer!=-1 ) + skill_status_change_end(bl,SC_SPEARSQUICKEN,-1); + if(sc_data[SC_ADRENALINE].timer!=-1 ) + skill_status_change_end(bl,SC_ADRENALINE,-1); + if(sc_data[SC_LOUD].timer!=-1 ) + skill_status_change_end(bl,SC_LOUD,-1); + if(sc_data[SC_TRUESIGHT].timer!=-1 ) /* トゥルーサイト */ + skill_status_change_end(bl,SC_TRUESIGHT,-1); + if(sc_data[SC_WINDWALK].timer!=-1 ) /* ウインドウォーク */ + skill_status_change_end(bl,SC_WINDWALK,-1); + if(sc_data[SC_CARTBOOST].timer!=-1 ) /* カートブースト */ + skill_status_change_end(bl,SC_CARTBOOST,-1); + break; + case SC_FLAMELAUNCHER: /* フレームランチャー */ + skill_encchant_eremental_end(bl,SC_FLAMELAUNCHER); + break; + case SC_FROSTWEAPON: /* フロストウェポン */ + skill_encchant_eremental_end(bl,SC_FROSTWEAPON); + break; + case SC_LIGHTNINGLOADER: /* ライトニングローダー */ + skill_encchant_eremental_end(bl,SC_LIGHTNINGLOADER); + break; + case SC_SEISMICWEAPON: /* サイズミックウェポン */ + skill_encchant_eremental_end(bl,SC_SEISMICWEAPON); + break; + case SC_DEVOTION: /* ディボーション */ + calc_flag = 1; + break; + case SC_PROVIDENCE: /* プロヴィデンス */ + calc_flag = 1; + val2=val1*5; + break; + case SC_REFLECTSHIELD: + val2=10+val1*3; + break; + case SC_STRIPWEAPON: + case SC_STRIPSHIELD: + case SC_STRIPARMOR: + case SC_STRIPHELM: + case SC_CP_WEAPON: + case SC_CP_SHIELD: + case SC_CP_ARMOR: + case SC_CP_HELM: + break; + + case SC_AUTOSPELL: /* オートスペル */ + val4 = 5 + val1*2; + break; + + case SC_VOLCANO: + calc_flag = 1; + val3 = val1*10; + val4 = val1>=5?20: (val1==4?19: (val1==3?17: ( val1==2?14:10 ) ) ); + break; + case SC_DELUGE: + calc_flag = 1; + val3 = val1>=5?15: (val1==4?14: (val1==3?12: ( val1==2?9:5 ) ) ); + val4 = val1>=5?20: (val1==4?19: (val1==3?17: ( val1==2?14:10 ) ) ); + break; + case SC_VIOLENTGALE: + calc_flag = 1; + val3 = val1*3; + val4 = val1>=5?20: (val1==4?19: (val1==3?17: ( val1==2?14:10 ) ) ); + break; + + case SC_SPEARSQUICKEN: /* スピアクイッケン */ + calc_flag = 1; + val2 = 20+val1; + *opt3 |= 1; + break; + case SC_COMBO: + break; + case SC_BLADESTOP_WAIT: /* 白刃取り(待ち) */ + break; + case SC_BLADESTOP: /* 白刃取り */ + if(val2==2) clif_bladestop((struct block_list *)val3,(struct block_list *)val4,1); + *opt3 |= 32; + break; + + case SC_LULLABY: /* 子守唄 */ + val2 = 11; + break; + case SC_RICHMANKIM: + break; + case SC_ETERNALCHAOS: /* エターナルカオス */ + calc_flag = 1; + break; + case SC_DRUMBATTLE: /* 戦太鼓の響き */ + calc_flag = 1; + val2 = (val1+1)*25; + val3 = (val1+1)*2; + break; + case SC_NIBELUNGEN: /* ニーベルングの指輪 */ + calc_flag = 1; + val2 = (val1+2)*50; + val3 = (val1+2)*25; + break; + case SC_ROKISWEIL: /* ロキの叫び */ + break; + case SC_INTOABYSS: /* 深淵の中に */ + break; + case SC_SIEGFRIED: /* 不死身のジークフリード */ + calc_flag = 1; + val2 = 40 + val1*5; + val3 = val1*10; + break; + case SC_DISSONANCE: /* 不協和音 */ + val2 = 10; + break; + case SC_WHISTLE: /* 口笛 */ + calc_flag = 1; + break; + case SC_ASSNCROS: /* 夕陽のアサシンクロス */ + calc_flag = 1; + break; + case SC_POEMBRAGI: /* ブラギの詩 */ + break; + case SC_APPLEIDUN: /* イドゥンの林檎 */ + calc_flag = 1; + break; + case SC_UGLYDANCE: /* 自分勝手なダンス */ + val2 = 10; + break; + case SC_HUMMING: /* ハミング */ + calc_flag = 1; + break; + case SC_DONTFORGETME: /* 私を忘れないで */ + calc_flag = 1; + if(sc_data[SC_INCREASEAGI].timer!=-1 ) /* 速度上昇解除 */ + skill_status_change_end(bl,SC_INCREASEAGI,-1); + if(sc_data[SC_TWOHANDQUICKEN].timer!=-1 ) + skill_status_change_end(bl,SC_TWOHANDQUICKEN,-1); + if(sc_data[SC_SPEARSQUICKEN].timer!=-1 ) + skill_status_change_end(bl,SC_SPEARSQUICKEN,-1); + if(sc_data[SC_ADRENALINE].timer!=-1 ) + skill_status_change_end(bl,SC_ADRENALINE,-1); + if(sc_data[SC_ASSNCROS].timer!=-1 ) + skill_status_change_end(bl,SC_ASSNCROS,-1); + if(sc_data[SC_TRUESIGHT].timer!=-1 ) /* トゥルーサイト */ + skill_status_change_end(bl,SC_TRUESIGHT,-1); + if(sc_data[SC_WINDWALK].timer!=-1 ) /* ウインドウォーク */ + skill_status_change_end(bl,SC_WINDWALK,-1); + if(sc_data[SC_CARTBOOST].timer!=-1 ) /* カートブースト */ + skill_status_change_end(bl,SC_CARTBOOST,-1); + break; + case SC_FORTUNE: /* 幸運のキス */ + calc_flag = 1; + break; + case SC_SERVICE4U: /* サービスフォーユー */ + calc_flag = 1; + break; + case SC_DANCING: /* ダンス/演奏中 */ + calc_flag = 1; + val3= tick / 1000; + tick = 1000; + break; + + case SC_EXPLOSIONSPIRITS: // 爆裂波動 + calc_flag = 1; + val2 = 75 + 25*val1; + *opt3 |= 8; + break; + case SC_STEELBODY: // 金剛 + calc_flag = 1; + *opt3 |= 16; + break; + case SC_EXTREMITYFIST: /* 阿修羅覇凰拳 */ + break; + case SC_AUTOCOUNTER: + val3 = val4 = 0; + break; + + case SC_SPEEDPOTION0: /* 増速ポーション */ + case SC_SPEEDPOTION1: + case SC_SPEEDPOTION2: + calc_flag = 1; + tick = 1000 * tick; + val2 = 5*(2+type-SC_SPEEDPOTION0); + break; + + /* atk & matk potions [Valaris] */ + case SC_ATKPOT: + case SC_MATKPOT: + calc_flag = 1; + tick = 1000 * tick; + break; + case SC_WEDDING: //結婚用(結婚衣裳になって歩くのが遅いとか) + { + time_t timer; + + calc_flag = 1; + tick = 10000; + if(!val2) + val2 = time(&timer); + } + break; + case SC_NOCHAT: //チャット禁止状態 + { + time_t timer; + + if(!battle_config.muting_players) + break; + + tick = 60000; + if(!val2) + val2 = time(&timer); + updateflag = SP_MANNER; + } + break; + case SC_SELFDESTRUCTION: //自爆 + clif_skillcasting(bl,bl->id, bl->id,0,0,331,skill_get_time(val2,val1)); + val3 = tick / 1000; + tick = 1000; + break; + + /* option1 */ + case SC_STONE: /* 石化 */ + if(!(flag&2)) { + int sc_def = battle_get_mdef(bl)*200; + tick = tick - sc_def; + } + val3 = tick/1000; + if(val3 < 1) val3 = 1; + tick = 5000; + val2 = 1; + break; + case SC_SLEEP: /* 睡眠 */ + if(!(flag&2)) { +// int sc_def = 100 - (battle_get_int(bl) + battle_get_luk(bl)/3); +// tick = tick * sc_def / 100; +// if(tick < 1000) tick = 1000; + tick = 30000;//睡眠はステータス耐性に関わらず30秒 + } + break; + case SC_FREEZE: /* 凍結 */ + if(!(flag&2)) { + int sc_def = 100 - battle_get_mdef(bl); + tick = tick * sc_def / 100; + } + break; + case SC_STAN: /* スタン(val2にミリ秒セット) */ + if(!(flag&2)) { + int sc_def = 100 - (battle_get_vit(bl) + battle_get_luk(bl)/3); + tick = tick * sc_def / 100; + } + break; + + /* option2 */ + case SC_POISON: /* 毒 */ + calc_flag = 1; + if(!(flag&2)) { + int sc_def = 100 - (battle_get_vit(bl) + battle_get_luk(bl)/5); + tick = tick * sc_def / 100; + } + val3 = tick/1000; + if(val3 < 1) val3 = 1; + tick = 1000; + break; + case SC_SILENCE: /* 沈黙(レックスデビーナ) */ + if(!(flag&2)) { + int sc_def = 100 - battle_get_vit(bl); + tick = tick * sc_def / 100; + } + break; + case SC_BLIND: /* 暗黒 */ + calc_flag = 1; + if(!(flag&2)) { + int sc_def = battle_get_lv(bl)/10 + battle_get_int(bl)/15; + tick = 30000 - sc_def; + } + break; + case SC_CURSE: + calc_flag = 1; + if(!(flag&2)) { + int sc_def = 100 - battle_get_vit(bl); + tick = tick * sc_def / 100; + } + break; + + /* option */ + case SC_HIDING: /* ハイディング */ + calc_flag = 1; + if(bl->type == BL_PC) { + val2 = tick / 1000; /* 持続時間 */ + tick = 1000; + } + break; + case SC_CHASEWALK: + case SC_CLOAKING: /* クローキング */ + if(bl->type == BL_PC) + val2 = tick; + else + tick = 5000*val1; + break; + case SC_SIGHT: /* サイト/ルアフ */ + case SC_RUWACH: + val2 = tick/250; + tick = 10; + break; + + /* セーフティウォール、ニューマ */ + case SC_SAFETYWALL: case SC_PNEUMA: + tick=((struct skill_unit *)val2)->group->limit; + break; + + /* アンクル */ + case SC_ANKLE: + break; + + /* ウォーターボール */ + case SC_WATERBALL: + tick=150; + if(val1>5) //レベルが5以上の場合は25発に制限(1発目はすでに打ってるので-1) + val3=5*5-1; + else + val3= (val1|1)*(val1|1)-1; + break; + + /* スキルじゃない/時間に関係しない */ + case SC_RIDING: + calc_flag = 1; + tick = 600*1000; + break; + case SC_FALCON: + case SC_WEIGHT50: + case SC_WEIGHT90: + case SC_BROKNWEAPON: + case SC_BROKNARMOR: + tick=600*1000; + break; + + case SC_AUTOGUARD: + { + int i,t; + for(i=val2=0;i<val1;i++) { + t = 5-(i>>1); + val2 += (t < 0)? 1:t; + } + } + break; + + case SC_DEFENDER: + calc_flag = 1; + val2 = 5 + val1*15; + break; + + case SC_KEEPING: + case SC_BARRIER: + case SC_HALLUCINATION: + break; + case SC_CONCENTRATION: /* コンセントレーション */ + *opt3 |= 1; + calc_flag = 1; + break; + case SC_TENSIONRELAX: /* テンションリラックス */ + calc_flag = 1; + if(bl->type == BL_PC) { + tick = 10000; + } + break; + case SC_AURABLADE: /* オーラブレード */ + case SC_PARRYING: /* パリイング */ +// case SC_ASSUMPTIO: /* */ + case SC_HEADCRUSH: /* ヘッドクラッシュ */ + case SC_JOINTBEAT: /* ジョイントビート */ +// case SC_MARIONETTE: /* マリオネットコントロール */ + + //とりあえず手抜き + break; + +// -- moonsoul (for new upper class related skill status effects) +/* + case SC_AURABLADE: + val2 = val1*10; + break; + case SC_PARRYING: + val2=val1*3; + break; + case SC_CONCENTRATION: + calc_flag=1; + val2=val1*10; + val3=val1*5; + break; + case SC_TENSIONRELAX: +// val2 = 10; +// val3 = 15; + break; + case SC_BERSERK: + calc_flag=1; + break; + case SC_ASSUMPTIO: + if(sc_data[SC_KYRIE].timer!=-1 ) + skill_status_change_end(bl,SC_KYRIE,-1); + break; +*/ + case SC_WINDWALK: /* ウインドウォーク */ + calc_flag = 1; + val2 = (val1 / 2); //Flee上昇率 + break; + case SC_BERSERK: /* バーサーク */ + if(sd){ + sd->status.sp = 0; + clif_updatestatus(sd,SP_SP); + clif_status_change(bl,SC_INCREASEAGI,1); /* アイコン表示 */ + } + *opt3 |= 128; + tick = 1000; + calc_flag = 1; + break; + case SC_ASSUMPTIO: /* アスムプティオ */ + *opt3 |= 2048; + break; + case SC_MARIONETTE: /* マリオネットコントロール */ + *opt3 |= 1024; + break; + case SC_MELTDOWN: /* メルトダウン */ + case SC_CARTBOOST: /* カートブースト */ + case SC_TRUESIGHT: /* トゥルーサイト */ + case SC_SPIDERWEB: /* スパイダーウェッブ */ + case SC_MAGICPOWER: /* 魔法力増幅 */ + calc_flag = 1; + break; + case SC_REJECTSWORD: /* リジェクトソード */ + val2 = 3; //3回攻撃を跳ね返す + break; + case SC_MEMORIZE: /* メモライズ */ + val2 = 3; //3回詠唱を1/3にする + break; + case SC_GRAFFITI: /* グラフィティ */ + { + struct skill_unit_group *sg = skill_unitsetting(bl,RG_GRAFFITI,val1,val2,val3,0); + if(sg) + val4 = (int)sg; + } + break; + case SC_SPLASHER: /* ベナムスプラッシャー */ + break; + default: + if(battle_config.error_log) + printf("UnknownStatusChange [%d]\n", type); + return 0; + } + + if(bl->type==BL_PC && type<SC_SENDMAX) + clif_status_change(bl,type,1); /* アイコン表示 */ + + /* optionの変更 */ + switch(type){ + case SC_STONE: + case SC_FREEZE: + case SC_STAN: + case SC_SLEEP: + battle_stopattack(bl); /* 攻撃停止 */ + skill_stop_dancing(bl,0); /* 演奏/ダンスの中断 */ + { /* 同時に掛からないステータス異常を解除 */ + int i; + for(i = SC_STONE; i <= SC_SLEEP; i++){ + if(sc_data[i].timer != -1){ + (*sc_count)--; + delete_timer(sc_data[i].timer, skill_status_change_timer); + sc_data[i].timer = -1; + } + } + } + if(type == SC_STONE) + *opt1 = 6; + else + *opt1 = type - SC_STONE + 1; + opt_flag = 1; + break; + case SC_POISON: + case SC_CURSE: + case SC_SILENCE: + case SC_BLIND: + *opt2 |= 1<<(type-SC_POISON); + opt_flag = 1; + break; + case SC_SIGNUMCRUCIS: + *opt2 |= 0x40; + opt_flag = 1; + break; + case SC_HIDING: + case SC_CLOAKING: + battle_stopattack(bl); /* 攻撃停止 */ + *option |= ((type==SC_HIDING)?2:4); + opt_flag =1 ; + break; + case SC_CHASEWALK: + battle_stopattack(bl); /* 攻撃停止 */ + *option |= 16388; + opt_flag =1 ; + break; + case SC_SIGHT: + *option |= 1; + opt_flag = 1; + break; + case SC_RUWACH: + *option |= 8192; + opt_flag = 1; + break; + case SC_WEDDING: + *option |= 4096; + opt_flag = 1; + } + + if(opt_flag) /* optionの変更 */ + clif_changeoption(bl); + + (*sc_count)++; /* ステータス異常の数 */ + + sc_data[type].val1 = val1; + sc_data[type].val2 = val2; + sc_data[type].val3 = val3; + sc_data[type].val4 = val4; + /* タイマー設定 */ + sc_data[type].timer = add_timer( + gettick() + tick, skill_status_change_timer, bl->id, type); + + if(bl->type==BL_PC && calc_flag) + pc_calcstatus(sd,0); /* ステータス再計算 */ + + if(bl->type==BL_PC && updateflag) + clif_updatestatus(sd,updateflag); /* ステータスをクライアントに送る */ + + return 0; +} +/*========================================== + * ステータス異常全解除 + *------------------------------------------ + */ +int skill_status_change_clear(struct block_list *bl, int type) +{ + struct status_change* sc_data; + short *sc_count, *option, *opt1, *opt2, *opt3; + int i; + + nullpo_retr(0, bl); + nullpo_retr(0, sc_data = battle_get_sc_data(bl)); + nullpo_retr(0, sc_count = battle_get_sc_count(bl)); + nullpo_retr(0, option = battle_get_option(bl)); + nullpo_retr(0, opt1 = battle_get_opt1(bl)); + nullpo_retr(0, opt2 = battle_get_opt2(bl)); + nullpo_retr(0, opt3 = battle_get_opt3(bl)); + + if (*sc_count == 0) + return 0; + for(i = 0; i < MAX_STATUSCHANGE; i++){ + if(sc_data[i].timer != -1){ /* 異常があるならタイマーを削除する */ +/* + delete_timer(sc_data[i].timer, skill_status_change_timer); + sc_data[i].timer = -1; + + if (!type && i < SC_SENDMAX) + clif_status_change(bl, i, 0); +*/ + + skill_status_change_end(bl, i, -1); + } + } + *sc_count = 0; + *opt1 = 0; + *opt2 = 0; + *opt3 = 0; + *option &= OPTION_MASK; + + if (night_flag == 1 && type == BL_PC) // by [Yor] + *opt2 |= STATE_BLIND; + + if(!type || type&2) + clif_changeoption(bl); + + return 0; +} + +/* クローキング検査(周りに移動不可能地帯があるか) */ +int skill_check_cloaking(struct block_list *bl) +{ + struct map_session_data *sd=NULL; + static int dx[]={-1, 0, 1,-1, 1,-1, 0, 1}; + static int dy[]={-1,-1,-1, 0, 0, 1, 1, 1}; + int end=1,i; + + nullpo_retr(0, bl); + + if(pc_checkskill(sd,AS_CLOAKING)>2) + return 0; + if(bl->type == BL_PC && battle_config.pc_cloak_check_type&1) + return 0; + if(bl->type == BL_MOB && battle_config.monster_cloak_check_type&1) + return 0; + for(i=0;i<sizeof(dx)/sizeof(dx[0]);i++){ + int c=map_getcell(bl->m,bl->x+dx[i],bl->y+dy[i]); + if(c==1 || c==5) end=0; + } + if(end){ + skill_status_change_end(bl, SC_CLOAKING, -1); + *battle_get_option(bl)&=~4; /* 念のための処理 */ + } + return end; +} + +/* + *---------------------------------------------------------------------------- + * スキルユニット + *---------------------------------------------------------------------------- + */ + +/*========================================== + * 演奏/ダンススキルかどうか判定 + * 引数 スキルID + * 戻り ダンスじゃない=0 合奏=2 それ以外のダンス=1 + *------------------------------------------ + */ +int skill_is_danceskill(int id) +{ + int i; + switch(id){ + case BD_LULLABY: /* 子守歌 */ + case BD_RICHMANKIM: /* ニヨルドの宴 */ + case BD_ETERNALCHAOS: /* 永遠の混沌 */ + case BD_DRUMBATTLEFIELD: /* 戦太鼓の響き */ + case BD_RINGNIBELUNGEN: /* ニーベルングの指輪 */ + case BD_ROKISWEIL: /* ロキの叫び */ + case BD_INTOABYSS: /* 深淵の中に */ + case BD_SIEGFRIED: /* 不死身のジークフリード */ + case BD_RAGNAROK: /* 神々の黄昏 */ + case CG_MOONLIT: /* 月明りの泉に落ちる花びら */ + i=2; + break; + case BA_DISSONANCE: /* 不協和音 */ + case BA_FROSTJOKE: /* 寒いジョーク */ + case BA_WHISTLE: /* 口笛 */ + case BA_ASSASSINCROSS: /* 夕陽のアサシンクロス */ + case BA_POEMBRAGI: /* ブラギの詩 */ + case BA_APPLEIDUN: /* イドゥンの林檎 */ + case DC_UGLYDANCE: /* 自分勝手なダンス */ + case DC_SCREAM: /* スクリーム */ + case DC_HUMMING: /* ハミング */ + case DC_DONTFORGETME: /* 私を忘れないで… */ + case DC_FORTUNEKISS: /* 幸運のキス */ + case DC_SERVICEFORYOU: /* サービスフォーユー */ + i=1; + break; + default: + i=0; + } + return i; +} + +/*========================================== + * 演奏/ダンスをやめる + * flag 1で合奏中なら相方にユニットを任せる + * + *------------------------------------------ + */ +void skill_stop_dancing(struct block_list *src, int flag) +{ + struct status_change* sc_data; + struct skill_unit_group* group; + + nullpo_retv(src); + + sc_data=battle_get_sc_data(src); + if(sc_data && sc_data[SC_DANCING].timer==-1) + return; + group=(struct skill_unit_group *)sc_data[SC_DANCING].val2; //ダンスのスキルユニットIDはval2に入ってる + if(group && src->type==BL_PC && sc_data && sc_data[SC_DANCING].val4){ //合奏中断 + struct map_session_data* dsd=map_id2sd(sc_data[SC_DANCING].val4); //相方のsd取得 + if(flag){ //ログアウトなど片方が落ちても演奏が継続される + if(dsd && src->id == group->src_id){ //グループを持ってるPCが落ちる + group->src_id=sc_data[SC_DANCING].val4; //相方にグループを任せる + if(flag&1) //ログアウト + dsd->sc_data[SC_DANCING].val4=0; //相方の相方を0にして合奏終了→通常のダンス状態 + if(flag&2) //ハエ飛びなど + return; //合奏もダンス状態も終了させない&スキルユニットは置いてけぼり + }else if(dsd && dsd->bl.id == group->src_id){ //相方がグループを持っているPCが落ちる(自分はグループを持っていない) + if(flag&1) //ログアウト + dsd->sc_data[SC_DANCING].val4=0; //相方の相方を0にして合奏終了→通常のダンス状態 + if(flag&2) //ハエ飛びなど + return; //合奏もダンス状態も終了させない&スキルユニットは置いてけぼり + } + skill_status_change_end(src,SC_DANCING,-1);//自分のステータスを終了させる + //そしてグループは消さない&消さないのでステータス計算もいらない? + return; + }else{ + if(dsd && src->id == group->src_id){ //グループを持ってるPCが止める + skill_status_change_end((struct block_list *)dsd,SC_DANCING,-1);//相手のステータスを終了させる + } + if(dsd && dsd->bl.id == group->src_id){ //相方がグループを持っているPCが止める(自分はグループを持っていない) + skill_status_change_end(src,SC_DANCING,-1);//自分のステータスを終了させる + } + } + } + if(flag&2 && group && src->type==BL_PC){ //ハエで飛んだときとかはユニットも飛ぶ + struct map_session_data *sd = (struct map_session_data *)src; + skill_unit_move_unit_group(group, sd->bl.m,(sd->to_x - sd->bl.x),(sd->to_y - sd->bl.y)); + return; + } + skill_delunitgroup(group); + if(src->type==BL_PC) + pc_calcstatus((struct map_session_data *)src,0); +} + +/*========================================== + * スキルユニット初期化 + *------------------------------------------ + */ +struct skill_unit *skill_initunit(struct skill_unit_group *group,int idx,int x,int y) +{ + struct skill_unit *unit; + + nullpo_retr(NULL, group); + nullpo_retr(NULL, unit=&group->unit[idx]); + + if(!unit->alive) + group->alive_count++; + + unit->bl.id=map_addobject(&unit->bl); + unit->bl.type=BL_SKILL; + unit->bl.m=group->map; + unit->bl.x=x; + unit->bl.y=y; + unit->group=group; + unit->val1=unit->val2=0; + unit->alive=1; + + map_addblock(&unit->bl); + clif_skill_setunit(unit); + return unit; +} + +int skill_unit_timer_sub_ondelete( struct block_list *bl, va_list ap ); +/*========================================== + * スキルユニット削除 + *------------------------------------------ + */ +int skill_delunit(struct skill_unit *unit) +{ + struct skill_unit_group *group; + int range; + + nullpo_retr(0, unit); + if(!unit->alive) + return 0; + nullpo_retr(0, group=unit->group); + + /* onlimitイベント呼び出し */ + skill_unit_onlimit( unit,gettick() ); + + /* ondeleteイベント呼び出し */ + range=group->range; + map_foreachinarea( skill_unit_timer_sub_ondelete, unit->bl.m, + unit->bl.x-range,unit->bl.y-range,unit->bl.x+range,unit->bl.y+range,0, + &unit->bl,gettick() ); + + clif_skill_delunit(unit); + + unit->group=NULL; + unit->alive=0; + map_delobjectnofree(unit->bl.id); + if(group->alive_count>0 && (--group->alive_count)<=0) + skill_delunitgroup(group); + + return 0; +} +/*========================================== + * スキルユニットグループ初期化 + *------------------------------------------ + */ +static int skill_unit_group_newid=10; +struct skill_unit_group *skill_initunitgroup(struct block_list *src, + int count,int skillid,int skilllv,int unit_id) +{ + int i; + struct skill_unit_group *group=NULL, *list=NULL; + int maxsug=0; + + nullpo_retr(NULL, src); + + if(src->type==BL_PC){ + list=((struct map_session_data *)src)->skillunit; + maxsug=MAX_SKILLUNITGROUP; + }else if(src->type==BL_MOB){ + list=((struct mob_data *)src)->skillunit; + maxsug=MAX_MOBSKILLUNITGROUP; + }else if(src->type==BL_PET){ + list=((struct pet_data *)src)->skillunit; + maxsug=MAX_MOBSKILLUNITGROUP; + } + if(list){ + for(i=0;i<maxsug;i++) /* 空いているもの検索 */ + if(list[i].group_id==0){ + group=&list[i]; + break; + } + + if(group==NULL){ /* 空いてないので古いもの検索 */ + int j=0; + unsigned maxdiff=0,x,tick=gettick(); + for(i=0;i<maxsug;i++) + if((x=DIFF_TICK(tick,list[i].tick))>maxdiff){ + maxdiff=x; + j=i; + } + skill_delunitgroup(&list[j]); + group=&list[j]; + } + } + + if(group==NULL){ + printf("skill_initunitgroup: error unit group !\n"); + exit(1); + } + + group->src_id=src->id; + group->party_id=battle_get_party_id(src); + group->guild_id=battle_get_guild_id(src); + group->group_id=skill_unit_group_newid++; + if(skill_unit_group_newid<=0) + skill_unit_group_newid=10; + group->unit=(struct skill_unit *)aCalloc(count,sizeof(struct skill_unit)); + group->unit_count=count; + group->val1=group->val2=0; + group->skill_id=skillid; + group->skill_lv=skilllv; + group->unit_id=unit_id; + group->map=src->m; + group->range=0; + group->limit=10000; + group->interval=1000; + group->tick=gettick(); + group->valstr=NULL; + + if( skill_is_danceskill(skillid) ){ + struct map_session_data *sd = NULL; + if(src->type==BL_PC && (sd=(struct map_session_data *)src) ){ + sd->skillid_dance=skillid; + sd->skilllv_dance=skilllv; + } + skill_status_change_start(src,SC_DANCING,skillid,(int)group,0,0,skill_get_time(skillid,skilllv)+1000,0); + switch(skillid){ //合奏スキルは相方をダンス状態にする + case BD_LULLABY: /* 子守歌 */ + case BD_RICHMANKIM: /* ニヨルドの宴 */ + case BD_ETERNALCHAOS: /* 永遠の混沌 */ + case BD_DRUMBATTLEFIELD: /* 戦太鼓の響き */ + case BD_RINGNIBELUNGEN: /* ニーベルングの指輪 */ + case BD_ROKISWEIL: /* ロキの叫び */ + case BD_INTOABYSS: /* 深淵の中に */ + case BD_SIEGFRIED: /* 不死身のジークフリード */ + case BD_RAGNAROK: /* 神々の黄昏 */ + case CG_MOONLIT: /* 月明りの泉に落ちる花びら */ + { + int range=1; + int c=0; + if(sd){ + map_foreachinarea(skill_check_condition_use_sub,sd->bl.m, + sd->bl.x-range,sd->bl.y-range, + sd->bl.x+range,sd->bl.y+range,BL_PC,&sd->bl,&c); + } + } + } + } + return group; +} + +/*========================================== + * スキルユニットグループ削除 + *------------------------------------------ + */ +int skill_delunitgroup(struct skill_unit_group *group) +{ + struct block_list *src; + int i; + + nullpo_retr(0, group); + if(group->unit_count<=0) + return 0; + + src=map_id2bl(group->src_id); + if( skill_is_danceskill(group->skill_id) ){ //ダンススキルはダンス状態を解除する + if(src) + skill_status_change_end(src,SC_DANCING,-1); + } + + group->alive_count=0; + if(group->unit!=NULL){ + for(i=0;i<group->unit_count;i++) + if(group->unit[i].alive) + skill_delunit(&group->unit[i]); + } + if(group->valstr!=NULL){ + map_freeblock(group->valstr); + group->valstr=NULL; + } + + map_freeblock(group->unit); /* free()の替わり */ + group->unit=NULL; + group->src_id=0; + group->group_id=0; + group->unit_count=0; + return 0; +} + +/*========================================== + * スキルユニットグループ全削除 + *------------------------------------------ + */ +int skill_clear_unitgroup(struct block_list *src) +{ + struct skill_unit_group *group=NULL; + int maxsug=0; + + nullpo_retr(0, src); + + if(src->type==BL_PC){ + group=((struct map_session_data *)src)->skillunit; + maxsug=MAX_SKILLUNITGROUP; + }else if(src->type==BL_MOB){ + group=((struct mob_data *)src)->skillunit; + maxsug=MAX_MOBSKILLUNITGROUP; + }else if(src->type==BL_PET){ // [Valaris] + group=((struct pet_data *)src)->skillunit; + maxsug=MAX_MOBSKILLUNITGROUP; + } + if(group){ + int i; + for(i=0;i<maxsug;i++) + if(group[i].group_id>0 && group[i].src_id == src->id) + skill_delunitgroup(&group[i]); + } + return 0; +} + +/*========================================== + * スキルユニットグループの被影響tick検索 + *------------------------------------------ + */ +struct skill_unit_group_tickset *skill_unitgrouptickset_search( + struct block_list *bl,int group_id) +{ + int i,j=0,k,s=group_id%MAX_SKILLUNITGROUPTICKSET; + struct skill_unit_group_tickset *set=NULL; + + nullpo_retr(0, bl); + + if(bl->type==BL_PC){ + set=((struct map_session_data *)bl)->skillunittick; + }else{ + set=((struct mob_data *)bl)->skillunittick; + } + if(set==NULL) + return 0; + for(i=0;i<MAX_SKILLUNITGROUPTICKSET;i++) + if( set[(k=(i+s)%MAX_SKILLUNITGROUPTICKSET)].group_id == group_id ) + return &set[k]; + else if( set[k].group_id==0 ) + j=k; + + return &set[j]; +} + +/*========================================== + * スキルユニットグループの被影響tick削除 + *------------------------------------------ + */ +int skill_unitgrouptickset_delete(struct block_list *bl,int group_id) +{ + int i,s=group_id%MAX_SKILLUNITGROUPTICKSET; + struct skill_unit_group_tickset *set=NULL,*ts; + + nullpo_retr(0, bl); + + if(bl->type==BL_PC){ + set=((struct map_session_data *)bl)->skillunittick; + }else{ + set=((struct mob_data *)bl)->skillunittick; + } + + if(set!=NULL){ + + for(i=0;i<MAX_SKILLUNITGROUPTICKSET;i++) + if( (ts=&set[(i+s)%MAX_SKILLUNITGROUPTICKSET])->group_id == group_id ) + ts->group_id=0; + + } + return 0; +} + +/*========================================== + * スキルユニットタイマー発動処理用(foreachinarea) + *------------------------------------------ + */ +int skill_unit_timer_sub_onplace( struct block_list *bl, va_list ap ) +{ + struct block_list *src; + struct skill_unit *su; + unsigned int tick; + + nullpo_retr(0, bl); + nullpo_retr(0, ap); + src=va_arg(ap,struct block_list*); + + tick=va_arg(ap,unsigned int); + su = (struct skill_unit *)src; + + if( su && su->alive ) { + struct skill_unit_group *sg; + sg = su->group; + if(sg && battle_check_target(src,bl,sg->target_flag )>0) + skill_unit_onplace( su, bl, tick ); + } + return 0; +} + +/*========================================== + * スキルユニットタイマー削除処理用(foreachinarea) + *------------------------------------------ + */ +int skill_unit_timer_sub_ondelete( struct block_list *bl, va_list ap ) +{ + struct block_list *src; + struct skill_unit *su; + unsigned int tick; + + nullpo_retr(0, bl); + nullpo_retr(0, ap); + src=va_arg(ap,struct block_list*); + + tick=va_arg(ap,unsigned int); + su = (struct skill_unit *)src; + + if( su && su->alive ){ + struct skill_unit_group *sg; + sg = su->group; + if( sg && battle_check_target(src,bl,sg->target_flag )>0 ) + skill_unit_ondelete( su, bl, tick ); + } + return 0; +} + +/*========================================== + * スキルユニットタイマー処理用(foreachobject) + *------------------------------------------ + */ +int skill_unit_timer_sub( struct block_list *bl, va_list ap ) +{ + struct skill_unit *unit; + struct skill_unit_group *group; + int range; + unsigned int tick; + + nullpo_retr(0, bl); + nullpo_retr(0, ap); + nullpo_retr(0, unit=(struct skill_unit *)bl); + nullpo_retr(0, group=unit->group); + tick=va_arg(ap,unsigned int); + + if(!unit->alive) + return 0; + + range=(unit->range!=0)?unit->range:group->range; + + /* onplaceイベント呼び出し */ + if(unit->alive && unit->range>=0){ + map_foreachinarea( skill_unit_timer_sub_onplace, bl->m, + bl->x-range,bl->y-range,bl->x+range,bl->y+range,0, + bl,tick); + if(group->unit_id == 0xaa && DIFF_TICK(tick,group->tick)>=6000*group->val2){ + map_foreachinarea( skill_idun_heal, bl->m, + bl->x-range,bl->y-range,bl->x+range,bl->y+range,0,unit); + group->val2++; + } + } + /* 時間切れ削除 */ + if(unit->alive && + (DIFF_TICK(tick,group->tick)>=group->limit || DIFF_TICK(tick,group->tick)>=unit->limit) ){ + switch(group->unit_id){ + + + + + + + case 0x8f: /* ブラストマイン */ + group->unit_id = 0x8c; + clif_changelook(bl,LOOK_BASE,group->unit_id); + group->limit=DIFF_TICK(tick+1500,group->tick); + unit->limit=DIFF_TICK(tick+1500,group->tick); + break; + case 0x90: /* スキッドトラップ */ + case 0x91: /* アンクルスネア */ + case 0x93: /* ランドマイン */ + case 0x94: /* ショックウェーブトラップ */ + case 0x95: /* サンドマン */ + case 0x96: /* フラッシャー */ + case 0x97: /* フリージングトラップ */ + case 0x98: /* クレイモアートラップ */ + case 0x99: /* トーキーボックス */ + { + struct block_list *src=map_id2bl(group->src_id); + if(group->unit_id == 0x91 && group->val2); + else{ + if(src && src->type==BL_PC){ + struct item item_tmp; + memset(&item_tmp,0,sizeof(item_tmp)); + item_tmp.nameid=1065; + item_tmp.identify=1; + map_addflooritem(&item_tmp,1,bl->m,bl->x,bl->y,NULL,NULL,NULL,0); // 罠返還 + } + } + } + default: + skill_delunit(unit); + } + } + + if(group->unit_id == 0x8d) { + unit->val1 -= 5; + if(unit->val1 <= 0 && unit->limit + group->tick > tick + 700) + unit->limit = DIFF_TICK(tick+700,group->tick); + } + + return 0; +} +/*========================================== + * スキルユニットタイマー処理 + *------------------------------------------ + */ +int skill_unit_timer( int tid,unsigned int tick,int id,int data) +{ + map_freeblock_lock(); + + map_foreachobject( skill_unit_timer_sub, BL_SKILL, tick ); + + map_freeblock_unlock(); + + return 0; +} + +/*========================================== + * スキルユニット移動時処理用(foreachinarea) + *------------------------------------------ + */ +int skill_unit_out_all_sub( struct block_list *bl, va_list ap ) +{ + struct skill_unit *unit; + struct skill_unit_group *group; + struct block_list *src; + int range; + unsigned int tick; + + nullpo_retr(0, bl); + nullpo_retr(0, ap); + nullpo_retr(0, src=va_arg(ap,struct block_list*)); + nullpo_retr(0, unit=(struct skill_unit *)bl); + nullpo_retr(0, group=unit->group); + + tick=va_arg(ap,unsigned int); + + if(!unit->alive || src->prev==NULL) + return 0; + + range=(unit->range!=0)?unit->range:group->range; + + if( range<0 || battle_check_target(bl,src,group->target_flag )<=0 ) + return 0; + + if( src->x >= bl->x-range && src->x <= bl->x+range && + src->y >= bl->y-range && src->y <= bl->y+range ) + skill_unit_onout( unit, src, tick ); + + return 0; +} + + +/*========================================== + * スキルユニット移動時処理 + *------------------------------------------ + */ +int skill_unit_out_all( struct block_list *bl,unsigned int tick,int range) +{ + nullpo_retr(0, bl); + + if( bl->prev==NULL ) + return 0; + + if(range<7) + range=7; + map_foreachinarea( skill_unit_out_all_sub, + bl->m,bl->x-range,bl->y-range,bl->x+range,bl->y+range,BL_SKILL, + bl,tick ); + + return 0; +} + +/*========================================== + * スキルユニット移動時処理用(foreachinarea) + *------------------------------------------ + */ +int skill_unit_move_sub( struct block_list *bl, va_list ap ) +{ + struct skill_unit *unit; + struct skill_unit_group *group; + struct block_list *src; + int range; + unsigned int tick; + + nullpo_retr(0, bl); + nullpo_retr(0, ap); + nullpo_retr(0, unit=(struct skill_unit *)bl); + nullpo_retr(0, src=va_arg(ap,struct block_list*)); + + tick=va_arg(ap,unsigned int); + + if(!unit->alive || src->prev==NULL) + return 0; + + if((group=unit->group) == NULL) + return 0; + range=(unit->range!=0)?unit->range:group->range; + + if( range<0 || battle_check_target(bl,src,group->target_flag )<=0 ) + return 0; + + if( src->x >= bl->x-range && src->x <= bl->x+range && + src->y >= bl->y-range && src->y <= bl->y+range ) + skill_unit_onplace( unit, src, tick ); + else + skill_unit_onout( unit, src, tick ); + + return 0; +} + +/*========================================== + * スキルユニット移動時処理 + *------------------------------------------ + */ +int skill_unit_move( struct block_list *bl,unsigned int tick,int range) +{ + nullpo_retr(0, bl); + + if( bl->prev==NULL ) + return 0; + + if(range<7) + range=7; + map_foreachinarea( skill_unit_move_sub, + bl->m,bl->x-range,bl->y-range,bl->x+range,bl->y+range,BL_SKILL, + bl,tick ); + + return 0; +} + +/*========================================== + * スキルユニット自体の移動時処理(foreachinarea) + *------------------------------------------ + */ +int skill_unit_move_unit_group_sub( struct block_list *bl, va_list ap ) +{ + struct skill_unit *unit; + struct skill_unit_group *group; + struct block_list *src; + int range; + unsigned int tick; + + nullpo_retr(0, bl); + nullpo_retr(0, ap); + nullpo_retr(0, src=va_arg(ap,struct block_list*)); + nullpo_retr(0, unit=(struct skill_unit *)src); + nullpo_retr(0, group=unit->group); + + tick=va_arg(ap,unsigned int); + + if(!unit->alive || bl->prev==NULL) + return 0; + + range=(unit->range!=0)?unit->range:group->range; + + if( range<0 || battle_check_target(src,bl,group->target_flag )<=0 ) + return 0; + if( bl->x >= src->x-range && bl->x <= src->x+range && + bl->y >= src->y-range && bl->y <= src->y+range ) + skill_unit_onplace( unit, bl, tick ); + else + skill_unit_onout( unit, bl, tick ); + return 0; +} + +/*========================================== + * スキルユニット自体の移動時処理 + * 引数はグループと移動量 + *------------------------------------------ + */ +int skill_unit_move_unit_group( struct skill_unit_group *group, int m,int dx,int dy) +{ + nullpo_retr(0, group); + + if( group->unit_count<=0) + return 0; + + if(group->unit!=NULL){ + if(!battle_config.unit_movement_type){ + int i; + for(i=0;i<group->unit_count;i++){ + struct skill_unit *unit=&group->unit[i]; + if(unit->alive && !(m==unit->bl.m && dx==0 && dy==0)){ + int range=unit->range; + map_delblock(&unit->bl); + unit->bl.m = m; + unit->bl.x += dx; + unit->bl.y += dy; + map_addblock(&unit->bl); + clif_skill_setunit(unit); + if(range>0){ + if(range<7) + range=7; + map_foreachinarea( skill_unit_move_unit_group_sub, unit->bl.m, + unit->bl.x-range,unit->bl.y-range,unit->bl.x+range,unit->bl.y+range,0, + &unit->bl,gettick() ); + } + } + } + }else{ + int i,j, *r_flag, *s_flag, *m_flag; + struct skill_unit *unit1; + struct skill_unit *unit2; + r_flag = (int *) malloc(sizeof(int) * group->unit_count); + s_flag = (int *) malloc(sizeof(int) * group->unit_count); + m_flag = (int *) malloc(sizeof(int) * group->unit_count); + memset(r_flag,0, sizeof(int) * group->unit_count);// 継承フラグ + memset(s_flag,0, sizeof(int) * group->unit_count);// 継承フラグ + memset(m_flag,0, sizeof(int) * group->unit_count);// 継承フラグ + + //先にフラグを全部決める + for(i=0;i<group->unit_count;i++){ + int move_check=0;// かぶりフラグ + unit1=&group->unit[i]; + for(j=0;j<group->unit_count;j++){ + unit2=&group->unit[j]; + if(unit1->bl.m==m && unit1->bl.x+dx==unit2->bl.x && unit1->bl.y+dy==unit2->bl.y){ + //移動先にユニットがかぶってたら + s_flag[i]=1;// 移動前のユニットナンバーの継承フラグon + r_flag[j]=1;// かぶるユニットナンバーの残留フラグon + move_check=1;//ユニットがかぶった。 + break; + } + } + if(!move_check)// ユニットがかぶってなかったら + m_flag[i]=1;// 移動前ユニットナンバーの移動フラグon + } + + //フラグに基づいてユニット移動 + for(i=0;i<group->unit_count;i++){ + unit1=&group->unit[i]; + if(m_flag[i]){// 移動フラグがonで + if(!r_flag[i]){// 残留フラグがoffなら + //単純移動(rangeも継承の必要無し) + int range=unit1->range; + map_delblock(&unit1->bl); + unit1->bl.m = m; + unit1->bl.x += dx; + unit1->bl.y += dy; + map_addblock(&unit1->bl); + clif_skill_setunit(unit1); + if(range > 0){ + if(range < 7) + range = 7; + map_foreachinarea( skill_unit_move_unit_group_sub, unit1->bl.m, + unit1->bl.x-range,unit1->bl.y-range,unit1->bl.x+range,unit1->bl.y+range,0, + &unit1->bl,gettick() ); + } + }else{// 残留フラグがonなら + //空ユニットになるので、継承可能なユニットを探す + for(j=0;j<group->unit_count;j++){ + unit2=&group->unit[j]; + if(s_flag[j] && !r_flag[j]){ + // 継承移動(range継承付き) + int range=unit1->range; + map_delblock(&unit2->bl); + unit2->bl.m = m; + unit2->bl.x = unit1->bl.x + dx; + unit2->bl.y = unit1->bl.y + dy; + unit2->range = unit1->range; + map_addblock(&unit2->bl); + clif_skill_setunit(unit2); + if(range > 0){ + if(range < 7) + range = 7; + map_foreachinarea( skill_unit_move_unit_group_sub, unit2->bl.m, + unit2->bl.x-range,unit2->bl.y-range,unit2->bl.x+range,unit2->bl.y+range,0, + &unit2->bl,gettick() ); + } + s_flag[j]=0;// 継承完了したのでoff + break; + } + } + } + } + } + free(r_flag); + free(s_flag); + free(m_flag); + } + } + return 0; +} + +/*---------------------------------------------------------------------------- + * アイテム合成 + *---------------------------------------------------------------------------- + */ + +/*========================================== + * アイテム合成可能判定 + *------------------------------------------ + */ +int skill_can_produce_mix( struct map_session_data *sd, int nameid, int trigger ) +{ + int i,j; + + nullpo_retr(0, sd); + + if(nameid<=0) + return 0; + + for(i=0;i<MAX_SKILL_PRODUCE_DB;i++){ + if(skill_produce_db[i].nameid == nameid ) + break; + } + if( i >= MAX_SKILL_PRODUCE_DB ) /* データベースにない */ + return 0; + + if(trigger>=0){ + if(trigger==32 || trigger==16 || trigger==64){ + if(skill_produce_db[i].itemlv!=trigger) /* ファーマシー*ポーション類と溶鉱炉*鉱石以外はだめ */ + return 0; + }else{ + if(skill_produce_db[i].itemlv>=16) /* 武器以外はだめ */ + return 0; + if( itemdb_wlv(nameid)>trigger ) /* 武器Lv判定 */ + return 0; + } + } + if( (j=skill_produce_db[i].req_skill)>0 && pc_checkskill(sd,j)<=0 ) + return 0; /* スキルが足りない */ + + for(j=0;j<5;j++){ + int id,x,y; + if( (id=skill_produce_db[i].mat_id[j]) <= 0 ) /* これ以上は材料要らない */ + continue; + if(skill_produce_db[i].mat_amount[j] <= 0) { + if(pc_search_inventory(sd,id) < 0) + return 0; + } + else { + for(y=0,x=0;y<MAX_INVENTORY;y++) + if( sd->status.inventory[y].nameid == id ) + x+=sd->status.inventory[y].amount; + if(x<skill_produce_db[i].mat_amount[j]) /* アイテムが足りない */ + return 0; + } + } + return i+1; +} + +/*========================================== + * アイテム合成可能判定 + *------------------------------------------ + */ +int skill_produce_mix( struct map_session_data *sd, + int nameid, int slot1, int slot2, int slot3 ) +{ + int slot[3]; + int i,sc,ele,idx,equip,wlv,make_per,flag; + + nullpo_retr(0, sd); + + if( !(idx=skill_can_produce_mix(sd,nameid,-1)) ) /* 条件不足 */ + return 0; + idx--; + slot[0]=slot1; + slot[1]=slot2; + slot[2]=slot3; + + /* 埋め込み処理 */ + for(i=0,sc=0,ele=0;i<3;i++){ + int j; + if( slot[i]<=0 ) + continue; + j = pc_search_inventory(sd,slot[i]); + if(j < 0) /* 不正パケット(アイテム存在)チェック */ + continue; + if(slot[i]==1000){ /* 星のかけら */ + pc_delitem(sd,j,1,1); + sc++; + } + if(slot[i]>=994 && slot[i]<=997 && ele==0){ /* 属性石 */ + static const int ele_table[4]={3,1,4,2}; + pc_delitem(sd,j,1,1); + ele=ele_table[slot[i]-994]; + } + } + + /* 材料消費 */ + for(i=0;i<5;i++){ + int j,id,x; + if( (id=skill_produce_db[idx].mat_id[i]) <= 0 ) + continue; + x=skill_produce_db[idx].mat_amount[i]; /* 必要な個数 */ + do{ /* 2つ以上のインデックスにまたがっているかもしれない */ + int y=0; + j = pc_search_inventory(sd,id); + + if(j >= 0){ + y = sd->status.inventory[j].amount; + if(y>x)y=x; /* 足りている */ + pc_delitem(sd,j,y,0); + }else { + if(battle_config.error_log) + printf("skill_produce_mix: material item error\n"); + } + + x-=y; /* まだ足りない個数を計算 */ + }while( j>=0 && x>0 ); /* 材料を消費するか、エラーになるまで繰り返す */ + } + + /* 確率判定 */ + equip = itemdb_isequip(nameid); + if(!equip) { + if(skill_produce_db[idx].req_skill==AM_PHARMACY) { + if((nameid >= 501 && nameid <= 506) || (nameid >= 545 && nameid <= 547) || nameid == 525) + make_per = 2000 + sd->status.base_level*30 + sd->paramc[3]*20 + sd->paramc[4]*15 + pc_checkskill(sd,AM_LEARNINGPOTION)*100 + pc_checkskill(sd,AM_PHARMACY)*300 + pc_checkskill(sd,AM_POTIONPITCHER)*100; + else if(nameid == 970) + make_per = 1500 + sd->status.base_level*30 + sd->paramc[3]*20 + sd->paramc[4]*15 + pc_checkskill(sd,AM_LEARNINGPOTION)*100 + pc_checkskill(sd,AM_PHARMACY)*300; + else if(nameid == 7135) + make_per = 1000 + sd->status.base_level*30 + sd->paramc[3]*20 + sd->paramc[4]*15 + pc_checkskill(sd,AM_LEARNINGPOTION)*100 + pc_checkskill(sd,AM_PHARMACY)*300 + pc_checkskill(sd,AM_DEMONSTRATION)*100; + else if(nameid == 7136) + make_per = 1000 + sd->status.base_level*30 + sd->paramc[3]*20 + sd->paramc[4]*15 + pc_checkskill(sd,AM_LEARNINGPOTION)*100 + pc_checkskill(sd,AM_PHARMACY)*300 + pc_checkskill(sd,AM_ACIDTERROR)*100; + else if(nameid == 7137) + make_per = 1000 + sd->status.base_level*30 + sd->paramc[3]*20 + sd->paramc[4]*15 + pc_checkskill(sd,AM_LEARNINGPOTION)*100 + pc_checkskill(sd,AM_PHARMACY)*300 + pc_checkskill(sd,AM_CANNIBALIZE)*100; + else if(nameid == 7138) + make_per = 1000 + sd->status.base_level*30 + sd->paramc[3]*20 + sd->paramc[4]*15 + pc_checkskill(sd,AM_LEARNINGPOTION)*100 + pc_checkskill(sd,AM_PHARMACY)*300 + pc_checkskill(sd,AM_SPHEREMINE)*100; + else if(nameid == 7139) + make_per = 1000 + sd->status.base_level*30 + sd->paramc[3]*20 + sd->paramc[4]*15 + pc_checkskill(sd,AM_LEARNINGPOTION)*100 + pc_checkskill(sd,AM_PHARMACY)*300 + pc_checkskill(sd,AM_CP_WEAPON)*100 + + pc_checkskill(sd,AM_CP_SHIELD)*100 + pc_checkskill(sd,AM_CP_ARMOR)*100 + pc_checkskill(sd,AM_CP_HELM)*100; + else + make_per = 1000 + sd->status.base_level*30 + sd->paramc[3]*20 + sd->paramc[4]*15 + pc_checkskill(sd,AM_LEARNINGPOTION)*100 + pc_checkskill(sd,AM_PHARMACY)*300; + } + else { + if(nameid == 998) + make_per = 2000 + sd->status.base_level*30 + sd->paramc[4]*20 + sd->paramc[5]*10 + pc_checkskill(sd,skill_produce_db[idx].req_skill)*600; + else if(nameid == 985) + make_per = 1000 + sd->status.base_level*30 + sd->paramc[4]*20 + sd->paramc[5]*10 + (pc_checkskill(sd,skill_produce_db[idx].req_skill)-1)*500; + else + make_per = 1000 + sd->status.base_level*30 + sd->paramc[4]*20 + sd->paramc[5]*10 + pc_checkskill(sd,skill_produce_db[idx].req_skill)*500; + } + } + else { + int add_per; + if(pc_search_inventory(sd,989) >= 0) add_per = 750; + else if(pc_search_inventory(sd,988) >= 0) add_per = 500; + else if(pc_search_inventory(sd,987) >= 0) add_per = 250; + else if(pc_search_inventory(sd,986) >= 0) add_per = 0; + else add_per = -500; + if(ele) add_per -= 500; + add_per -= sc*500; + wlv = itemdb_wlv(nameid); + make_per = ((250 + sd->status.base_level*15 + sd->paramc[4]*10 + sd->paramc[5]*5 + pc_checkskill(sd,skill_produce_db[idx].req_skill)*500 + + add_per) * (100 - (wlv - 1)*20))/100 + pc_checkskill(sd,BS_WEAPONRESEARCH)*100 + ((wlv >= 3)? pc_checkskill(sd,BS_ORIDEOCON)*100 : 0); + } + + if(make_per < 1) make_per = 1; + + if(skill_produce_db[idx].req_skill==AM_PHARMACY) { + if( battle_config.pp_rate!=100 ) + make_per=make_per*battle_config.pp_rate/100; + } + else { + if( battle_config.wp_rate!=100 ) /* 確率補正 */ + make_per=make_per*battle_config.wp_rate/100; + } + +// if(battle_config.etc_log) +// printf("make rate = %d\n",make_per); + + if(rand()%10000 < make_per){ + /* 成功 */ + struct item tmp_item; + memset(&tmp_item,0,sizeof(tmp_item)); + tmp_item.nameid=nameid; + tmp_item.amount=1; + tmp_item.identify=1; + if(equip){ /* 武器の場合 */ + tmp_item.card[0]=0x00ff; /* 製造武器フラグ */ + tmp_item.card[1]=((sc*5)<<8)+ele; /* 属性とつよさ */ + *((unsigned long *)(&tmp_item.card[2]))=sd->char_id; /* キャラID */ + } + else if((battle_config.produce_item_name_input && skill_produce_db[idx].req_skill!=AM_PHARMACY) || + (battle_config.produce_potion_name_input && skill_produce_db[idx].req_skill==AM_PHARMACY)) { + tmp_item.card[0]=0x00fe; + tmp_item.card[1]=0; + *((unsigned long *)(&tmp_item.card[2]))=sd->char_id; /* キャラID */ + } + + if(skill_produce_db[idx].req_skill!=AM_PHARMACY && skill_produce_db[idx].req_skill!=WS_CREATECOIN) { //武器製造の場合 + clif_produceeffect(sd,0,nameid);/* 武器製造エフェクトパケット */ + clif_misceffect(&sd->bl,3); /* 他人にも成功を通知(精錬成功エフェクトと同じでいいの?) */ + } + else if(skill_produce_db[idx].req_skill==AM_PHARMACY){ //ファーマシーの場合 + clif_produceeffect(sd,2,nameid);/* 製薬エフェクトパケット */ + clif_misceffect(&sd->bl,5); /* 他人にも成功を通知*/ + }else{ + clif_produceeffect(sd,0,nameid);/* 不明なのでとりあえず製造エフェクトパケット */ + clif_misceffect(&sd->bl,3); /* 他人にも成功を通知*/ + } + + if((flag = pc_additem(sd,&tmp_item,1))) { + clif_additem(sd,0,0,flag); + map_addflooritem(&tmp_item,1,sd->bl.m,sd->bl.x,sd->bl.y,NULL,NULL,NULL,0); + } + } + else { + if(skill_produce_db[idx].req_skill!=AM_PHARMACY) { //武器製造の場合 + clif_produceeffect(sd,1,nameid);/* 武器製造失敗エフェクトパケット */ + clif_misceffect(&sd->bl,2); /* 他人にも失敗を通知 */ + } + else if(skill_produce_db[idx].req_skill==AM_PHARMACY){ //ファーマシーの場合 + clif_produceeffect(sd,3,nameid);/* 製薬失敗エフェクトパケット */ + clif_misceffect(&sd->bl,6); /* 他人にも失敗を通知*/ + }else{ + clif_produceeffect(sd,1,nameid);/* 不明なのでとりあえず製造失敗エフェクトパケット */ + clif_misceffect(&sd->bl,2); /* 他人にも失敗を通知*/ + } + } + return 0; +} + +int skill_arrow_create( struct map_session_data *sd,int nameid) +{ + int i,j,flag,index=-1; + struct item tmp_item; + + nullpo_retr(0, sd); + + if(nameid <= 0) + return 1; + + for(i=0;i<MAX_SKILL_ARROW_DB;i++) + if(nameid == skill_arrow_db[i].nameid) { + index = i; + break; + } + + if(index < 0 || (j = pc_search_inventory(sd,nameid)) < 0) + return 1; + + pc_delitem(sd,j,1,0); + for(i=0;i<5;i++) { + memset(&tmp_item,0,sizeof(tmp_item)); + tmp_item.identify = 1; + tmp_item.nameid = skill_arrow_db[index].cre_id[i]; + tmp_item.amount = skill_arrow_db[index].cre_amount[i]; + if(battle_config.making_arrow_name_input) { + tmp_item.card[0]=0x00fe; + tmp_item.card[1]=0; + *((unsigned long *)(&tmp_item.card[2]))=sd->char_id; /* キャラID */ + } + if(tmp_item.nameid <= 0 || tmp_item.amount <= 0) + continue; + if((flag = pc_additem(sd,&tmp_item,tmp_item.amount))) { + clif_additem(sd,0,0,flag); + map_addflooritem(&tmp_item,tmp_item.amount,sd->bl.m,sd->bl.x,sd->bl.y,NULL,NULL,NULL,0); + } + } + + return 0; +} + +/*---------------------------------------------------------------------------- + * 初期化系 + */ + +/*========================================== + * スキル関係ファイル読み込み + * skill_db.txt スキルデータ + * skill_cast_db.txt スキルの詠唱時間とディレイデータ + * produce_db.txt アイテム作成スキル用データ + * create_arrow_db.txt 矢作成スキル用データ + * abra_db.txt アブラカダブラ発動スキルデータ + *------------------------------------------ + */ +int skill_readdb(void) +{ + int i,j,k,l,m; + FILE *fp; + char line[1024],*p; + char *filename[]={"db/produce_db.txt","db/produce_db2.txt"}; + + /* スキルデータベース */ + memset(skill_db,0,sizeof(skill_db)); + fp=fopen("db/skill_db.txt","r"); + if(fp==NULL){ + printf("can't read db/skill_db.txt\n"); + return 1; + } + while(fgets(line,1020,fp)){ + char *split[50], *split2[MAX_SKILL_LEVEL]; + if(line[0]=='/' && line[1]=='/') + continue; + for(j=0,p=line;j<14 && p;j++){ + split[j]=p; + p=strchr(p,','); + if(p) *p++=0; + } + if(split[13]==NULL || j<14) + continue; + + i=atoi(split[0]); + if(i<0 || i>MAX_SKILL_DB) + continue; + +/* printf("skill id=%d\n",i); */ + memset(split2,0,sizeof(split2)); + for(j=0,p=split[1];j<MAX_SKILL_LEVEL && p;j++){ + split2[j]=p; + p=strchr(p,':'); + if(p) *p++=0; + } + for(k=0;k<MAX_SKILL_LEVEL;k++) + skill_db[i].range[k]=(split2[k])? atoi(split2[k]):atoi(split2[0]); + skill_db[i].hit=atoi(split[2]); + skill_db[i].inf=atoi(split[3]); + skill_db[i].pl=atoi(split[4]); + skill_db[i].nk=atoi(split[5]); + skill_db[i].max=atoi(split[6]); + + memset(split2,0,sizeof(split2)); + for(j=0,p=split[7];j<MAX_SKILL_LEVEL && p;j++){ + split2[j]=p; + p=strchr(p,':'); + if(p) *p++=0; + } + for(k=0;k<MAX_SKILL_LEVEL;k++) + skill_db[i].num[k]=(split2[k])? atoi(split2[k]):atoi(split2[0]); + + if(strcmpi(split[8],"yes") == 0) + skill_db[i].castcancel=1; + else + skill_db[i].castcancel=0; + skill_db[i].cast_def_rate=atoi(split[9]); + skill_db[i].inf2=atoi(split[10]); + skill_db[i].maxcount=atoi(split[11]); + if(strcmpi(split[12],"weapon") == 0) + skill_db[i].skill_type=BF_WEAPON; + else if(strcmpi(split[12],"magic") == 0) + skill_db[i].skill_type=BF_MAGIC; + else if(strcmpi(split[12],"misc") == 0) + skill_db[i].skill_type=BF_MISC; + else + skill_db[i].skill_type=0; + memset(split2,0,sizeof(split2)); + for(j=0,p=split[13];j<MAX_SKILL_LEVEL && p;j++){ + split2[j]=p; + p=strchr(p,':'); + if(p) *p++=0; + } + for(k=0;k<MAX_SKILL_LEVEL;k++) + skill_db[i].blewcount[k]=(split2[k])? atoi(split2[k]):atoi(split2[0]); + } + fclose(fp); + printf("read db/skill_db.txt done\n"); + + fp=fopen("db/skill_require_db.txt","r"); + if(fp==NULL){ + printf("can't read db/skill_require_db.txt\n"); + return 1; + } + while(fgets(line,1020,fp)){ + char *split[51], *split2[MAX_SKILL_LEVEL]; + if(line[0]=='/' && line[1]=='/') + continue; + for(j=0,p=line;j<30 && p;j++){ + split[j]=p; + p=strchr(p,','); + if(p) *p++=0; + } + if(split[29]==NULL || j<30) + continue; + + i=atoi(split[0]); + if(i<0 || i>MAX_SKILL_DB) + continue; + + memset(split2,0,sizeof(split2)); + for(j=0,p=split[1];j<MAX_SKILL_LEVEL && p;j++){ + split2[j]=p; + p=strchr(p,':'); + if(p) *p++=0; + } + for(k=0;k<MAX_SKILL_LEVEL;k++) + skill_db[i].hp[k]=(split2[k])? atoi(split2[k]):atoi(split2[0]); + + memset(split2,0,sizeof(split2)); + for(j=0,p=split[2];j<MAX_SKILL_LEVEL && p;j++){ + split2[j]=p; + p=strchr(p,':'); + if(p) *p++=0; + } + for(k=0;k<MAX_SKILL_LEVEL;k++) + skill_db[i].mhp[k]=(split2[k])? atoi(split2[k]):atoi(split2[0]); + + memset(split2,0,sizeof(split2)); + for(j=0,p=split[3];j<MAX_SKILL_LEVEL && p;j++){ + split2[j]=p; + p=strchr(p,':'); + if(p) *p++=0; + } + for(k=0;k<MAX_SKILL_LEVEL;k++) + skill_db[i].sp[k]=(split2[k])? atoi(split2[k]):atoi(split2[0]); + + memset(split2,0,sizeof(split2)); + for(j=0,p=split[4];j<MAX_SKILL_LEVEL && p;j++){ + split2[j]=p; + p=strchr(p,':'); + if(p) *p++=0; + } + for(k=0;k<MAX_SKILL_LEVEL;k++) + skill_db[i].hp_rate[k]=(split2[k])? atoi(split2[k]):atoi(split2[0]); + + memset(split2,0,sizeof(split2)); + for(j=0,p=split[5];j<MAX_SKILL_LEVEL && p;j++){ + split2[j]=p; + p=strchr(p,':'); + if(p) *p++=0; + } + for(k=0;k<MAX_SKILL_LEVEL;k++) + skill_db[i].sp_rate[k]=(split2[k])? atoi(split2[k]):atoi(split2[0]); + + memset(split2,0,sizeof(split2)); + for(j=0,p=split[6];j<MAX_SKILL_LEVEL && p;j++){ + split2[j]=p; + p=strchr(p,':'); + if(p) *p++=0; + } + for(k=0;k<MAX_SKILL_LEVEL;k++) + skill_db[i].zeny[k]=(split2[k])? atoi(split2[k]):atoi(split2[0]); + + memset(split2,0,sizeof(split2)); + for(j=0,p=split[7];j<32 && p;j++){ + split2[j]=p; + p=strchr(p,':'); + if(p) *p++=0; + } + for(k=0;k<32 && split2[k];k++) { + l = atoi(split2[k]); + if(l == 99) { + skill_db[i].weapon = 0xffffffff; + break; + } + else + skill_db[i].weapon |= 1<<l; + } + + if( strcmpi(split[8],"hiding")==0 ) skill_db[i].state=ST_HIDING; + else if( strcmpi(split[8],"cloaking")==0 ) skill_db[i].state=ST_CLOAKING; + else if( strcmpi(split[8],"hidden")==0 ) skill_db[i].state=ST_HIDDEN; + else if( strcmpi(split[8],"riding")==0 ) skill_db[i].state=ST_RIDING; + else if( strcmpi(split[8],"falcon")==0 ) skill_db[i].state=ST_FALCON; + else if( strcmpi(split[8],"cart")==0 ) skill_db[i].state=ST_CART; + else if( strcmpi(split[8],"shield")==0 ) skill_db[i].state=ST_SHIELD; + else if( strcmpi(split[8],"sight")==0 ) skill_db[i].state=ST_SIGHT; + else if( strcmpi(split[8],"explosionspirits")==0 ) skill_db[i].state=ST_EXPLOSIONSPIRITS; + else if( strcmpi(split[8],"recover_weight_rate")==0 ) skill_db[i].state=ST_RECOV_WEIGHT_RATE; + else if( strcmpi(split[8],"move_enable")==0 ) skill_db[i].state=ST_MOVE_ENABLE; + else if( strcmpi(split[8],"water")==0 ) skill_db[i].state=ST_WATER; + else skill_db[i].state=ST_NONE; + + memset(split2,0,sizeof(split2)); + for(j=0,p=split[9];j<MAX_SKILL_LEVEL && p;j++){ + split2[j]=p; + p=strchr(p,':'); + if(p) *p++=0; + } + for(k=0;k<MAX_SKILL_LEVEL;k++) + skill_db[i].spiritball[k]=(split2[k])? atoi(split2[k]):atoi(split2[0]); + skill_db[i].itemid[0]=atoi(split[10]); + skill_db[i].amount[0]=atoi(split[11]); + skill_db[i].itemid[1]=atoi(split[12]); + skill_db[i].amount[1]=atoi(split[13]); + skill_db[i].itemid[2]=atoi(split[14]); + skill_db[i].amount[2]=atoi(split[15]); + skill_db[i].itemid[3]=atoi(split[16]); + skill_db[i].amount[3]=atoi(split[17]); + skill_db[i].itemid[4]=atoi(split[18]); + skill_db[i].amount[4]=atoi(split[19]); + skill_db[i].itemid[5]=atoi(split[20]); + skill_db[i].amount[5]=atoi(split[21]); + skill_db[i].itemid[6]=atoi(split[22]); + skill_db[i].amount[6]=atoi(split[23]); + skill_db[i].itemid[7]=atoi(split[24]); + skill_db[i].amount[7]=atoi(split[25]); + skill_db[i].itemid[8]=atoi(split[26]); + skill_db[i].amount[8]=atoi(split[27]); + skill_db[i].itemid[9]=atoi(split[28]); + skill_db[i].amount[9]=atoi(split[29]); + } + fclose(fp); + printf("read db/skill_require_db.txt done\n"); + + /* キャスティングデータベース */ + fp=fopen("db/skill_cast_db.txt","r"); + if(fp==NULL){ + printf("can't read db/skill_cast_db.txt\n"); + return 1; + } + while(fgets(line,1020,fp)){ + char *split[50], *split2[MAX_SKILL_LEVEL]; + memset(split,0,sizeof(split)); // [Valaris] thanks to fov + if(line[0]=='/' && line[1]=='/') + continue; + for(j=0,p=line;j<5 && p;j++){ + split[j]=p; + p=strchr(p,','); + if(p) *p++=0; + } + if(split[4]==NULL || j<5) + continue; + + i=atoi(split[0]); + if(i<0 || i>MAX_SKILL_DB) + continue; + + memset(split2,0,sizeof(split2)); + for(j=0,p=split[1];j<MAX_SKILL_LEVEL && p;j++){ + split2[j]=p; + p=strchr(p,':'); + if(p) *p++=0; + } + for(k=0;k<MAX_SKILL_LEVEL;k++) + skill_db[i].cast[k]=(split2[k])? atoi(split2[k]):atoi(split2[0]); + + memset(split2,0,sizeof(split2)); + for(j=0,p=split[2];j<MAX_SKILL_LEVEL && p;j++){ + split2[j]=p; + p=strchr(p,':'); + if(p) *p++=0; + } + for(k=0;k<MAX_SKILL_LEVEL;k++) + skill_db[i].delay[k]=(split2[k])? atoi(split2[k]):atoi(split2[0]); + + memset(split2,0,sizeof(split2)); + for(j=0,p=split[3];j<MAX_SKILL_LEVEL && p;j++){ + split2[j]=p; + p=strchr(p,':'); + if(p) *p++=0; + } + for(k=0;k<MAX_SKILL_LEVEL;k++) + skill_db[i].upkeep_time[k]=(split2[k])? atoi(split2[k]):atoi(split2[0]); + + memset(split2,0,sizeof(split2)); + for(j=0,p=split[4];j<MAX_SKILL_LEVEL && p;j++){ + split2[j]=p; + p=strchr(p,':'); + if(p) *p++=0; + } + for(k=0;k<MAX_SKILL_LEVEL;k++) + skill_db[i].upkeep_time2[k]=(split2[k])? atoi(split2[k]):atoi(split2[0]); + } + fclose(fp); + printf("read db/skill_cast_db.txt done\n"); + + /* 製造系スキルデータベース */ + memset(skill_produce_db,0,sizeof(skill_produce_db)); + for(m=0;m<2;m++){ + fp=fopen(filename[m],"r"); + if(fp==NULL){ + if(m>0) + continue; + printf("can't read %s\n",filename[m]); + return 1; + } + k=0; + while(fgets(line,1020,fp)){ + char *split[16]; + int x,y; + if(line[0]=='/' && line[1]=='/') + continue; + memset(split,0,sizeof(split)); + for(j=0,p=line;j<13 && p;j++){ + split[j]=p; + p=strchr(p,','); + if(p) *p++=0; + } + if(split[0]==NULL) + continue; + i=atoi(split[0]); + if(i<=0) + continue; + + skill_produce_db[k].nameid=i; + skill_produce_db[k].itemlv=atoi(split[1]); + skill_produce_db[k].req_skill=atoi(split[2]); + + for(x=3,y=0;split[x] && split[x+1] && y<5;x+=2,y++){ + skill_produce_db[k].mat_id[y]=atoi(split[x]); + skill_produce_db[k].mat_amount[y]=atoi(split[x+1]); + } + k++; + if(k >= MAX_SKILL_PRODUCE_DB) + break; + } + fclose(fp); + printf("read %s done (count=%d)\n",filename[m],k); + } + + memset(skill_arrow_db,0,sizeof(skill_arrow_db)); + fp=fopen("db/create_arrow_db.txt","r"); + if(fp==NULL){ + printf("can't read db/create_arrow_db.txt\n"); + return 1; + } + k=0; + while(fgets(line,1020,fp)){ + char *split[16]; + int x,y; + if(line[0]=='/' && line[1]=='/') + continue; + memset(split,0,sizeof(split)); + for(j=0,p=line;j<13 && p;j++){ + split[j]=p; + p=strchr(p,','); + if(p) *p++=0; + } + if(split[0]==NULL) + continue; + i=atoi(split[0]); + if(i<=0) + continue; + + skill_arrow_db[k].nameid=i; + + for(x=1,y=0;split[x] && split[x+1] && y<5;x+=2,y++){ + skill_arrow_db[k].cre_id[y]=atoi(split[x]); + skill_arrow_db[k].cre_amount[y]=atoi(split[x+1]); + } + k++; + if(k >= MAX_SKILL_ARROW_DB) + break; + } + fclose(fp); + printf("read db/create_arrow_db.txt done (count=%d)\n",k); + + memset(skill_abra_db,0,sizeof(skill_abra_db)); + fp=fopen("db/abra_db.txt","r"); + if(fp==NULL){ + printf("can't read db/abra_db.txt\n"); + return 1; + } + k=0; + while(fgets(line,1020,fp)){ + char *split[16]; + if(line[0]=='/' && line[1]=='/') + continue; + memset(split,0,sizeof(split)); + for(j=0,p=line;j<13 && p;j++){ + split[j]=p; + p=strchr(p,','); + if(p) *p++=0; + } + if(split[0]==NULL) + continue; + i=atoi(split[0]); + if(i<=0) + continue; + + skill_abra_db[i].req_lv=atoi(split[2]); + skill_abra_db[i].per=atoi(split[3]); + + k++; + if(k >= MAX_SKILL_ABRA_DB) + break; + } + fclose(fp); + printf("read db/abra_db.txt done (count=%d)\n",k); + + fp=fopen("db/skill_castnodex_db.txt","r"); + if(fp==NULL){ + printf("can't read db/skill_castnodex_db.txt\n"); + return 1; + } + while(fgets(line,1020,fp)){ + char *split[50], *split2[MAX_SKILL_LEVEL]; + memset(split,0,sizeof(split)); + if(line[0]=='/' && line[1]=='/') + continue; + for(j=0,p=line;j<2 && p;j++){ + split[j]=p; + p=strchr(p,','); + if(p) *p++=0; + } + + i=atoi(split[0]); + if(i<0 || i>MAX_SKILL_DB) + continue; + + memset(split2,0,sizeof(split2)); + for(j=0,p=split[1];j<MAX_SKILL_LEVEL && p;j++){ + split2[j]=p; + p=strchr(p,':'); + if(p) *p++=0; + } + for(k=0;k<MAX_SKILL_LEVEL;k++) + skill_db[i].castnodex[k]=(split2[k])? atoi(split2[k]):atoi(split2[0]); + } + fclose(fp); + printf("read db/skill_castnodex_db.txt done\n"); + + return 0; +} + +void skill_reload(void) +{ + /* + + <empty skill database> + <?> + + */ + + do_init_skill(); +} + +/*========================================== + * スキル関係初期化処理 + *------------------------------------------ + */ +int do_init_skill(void) +{ + skill_readdb(); + + add_timer_func_list(skill_unit_timer,"skill_unit_timer"); + add_timer_func_list(skill_castend_id,"skill_castend_id"); + add_timer_func_list(skill_castend_pos,"skill_castend_pos"); + add_timer_func_list(skill_timerskill,"skill_timerskill"); + add_timer_func_list(skill_status_change_timer,"skill_status_change_timer"); + add_timer_interval(gettick()+SKILLUNITTIMER_INVERVAL,skill_unit_timer,0,0,SKILLUNITTIMER_INVERVAL); + + return 0; +} diff --git a/src/map/skill.h b/src/map/skill.h new file mode 100644 index 0000000..6cb3d88 --- /dev/null +++ b/src/map/skill.h @@ -0,0 +1,842 @@ +// $Id: skill.h,v 1.5 2004/09/25 05:32:19 MouseJstr Exp $ +#ifndef _SKILL_H_ +#define _SKILL_H_ + +#include "map.h" + +#define MAX_SKILL_DB 450 +#define MAX_SKILL_PRODUCE_DB 150 +#define MAX_SKILL_ARROW_DB 150 +#define MAX_SKILL_ABRA_DB 350 + +// スキルデータベース +struct skill_db { + int range[MAX_SKILL_LEVEL],hit,inf,pl,nk,max; + int num[MAX_SKILL_LEVEL]; + int cast[MAX_SKILL_LEVEL],delay[MAX_SKILL_LEVEL]; + int upkeep_time[MAX_SKILL_LEVEL],upkeep_time2[MAX_SKILL_LEVEL]; + int castcancel,cast_def_rate; + int inf2,maxcount,skill_type; + int blewcount[MAX_SKILL_LEVEL]; + int hp[MAX_SKILL_LEVEL],sp[MAX_SKILL_LEVEL],mhp[MAX_SKILL_LEVEL],hp_rate[MAX_SKILL_LEVEL],sp_rate[MAX_SKILL_LEVEL],zeny[MAX_SKILL_LEVEL]; + int weapon,state,spiritball[MAX_SKILL_LEVEL]; + int itemid[10],amount[10]; + int castnodex[MAX_SKILL_LEVEL]; +}; +extern struct skill_db skill_db[MAX_SKILL_DB]; + +struct skill_name_db { + int id; // skill id + char *name; // search strings + char *desc; // description that shows up for search's +}; +extern struct skill_name_db skill_names[]; + +// アイテム作成データベース +struct skill_produce_db { + int nameid, trigger; + int req_skill,itemlv; + int mat_id[5],mat_amount[5]; +}; +extern struct skill_produce_db skill_produce_db[MAX_SKILL_PRODUCE_DB]; + +// 矢作成データベース +struct skill_arrow_db { + int nameid, trigger; + int cre_id[5],cre_amount[5]; +}; +extern struct skill_arrow_db skill_arrow_db[MAX_SKILL_ARROW_DB]; + +// アブラカダブラデータベース +struct skill_abra_db { + int nameid; + int req_lv; + int per; +}; +extern struct skill_abra_db skill_abra_db[MAX_SKILL_ABRA_DB]; + +struct block_list; +struct map_session_data; +struct skill_unit; +struct skill_unit_group; + +int do_init_skill(void); + +// スキルデータベースへのアクセサ +int skill_get_hit( int id ); +int skill_get_inf( int id ); +int skill_get_pl( int id ); +int skill_get_nk( int id ); +int skill_get_max( int id ); +int skill_get_range( int id , int lv ); +int skill_get_hp( int id ,int lv ); +int skill_get_mhp( int id ,int lv ); +int skill_get_sp( int id ,int lv ); +int skill_get_zeny( int id ,int lv ); +int skill_get_num( int id ,int lv ); +int skill_get_cast( int id ,int lv ); +int skill_get_delay( int id ,int lv ); +int skill_get_time( int id ,int lv ); +int skill_get_time2( int id ,int lv ); +int skill_get_castdef( int id ); +int skill_get_weapontype( int id ); +int skill_get_unit_id(int id,int flag); +int skill_get_inf2( int id ); +int skill_get_maxcount( int id ); +int skill_get_blewcount( int id ,int lv ); + +// スキルの使用 +int skill_use_id( struct map_session_data *sd, int target_id, + int skill_num,int skill_lv); +int skill_use_pos( struct map_session_data *sd, + int skill_x, int skill_y, int skill_num, int skill_lv); + +int skill_castend_map( struct map_session_data *sd,int skill_num, const char *map); + +int skill_cleartimerskill(struct block_list *src); +int skill_addtimerskill(struct block_list *src,unsigned int tick,int target,int x,int y,int skill_id,int skill_lv,int type,int flag); + +// 追加効果 +int skill_additional_effect( struct block_list* src, struct block_list *bl,int skillid,int skilllv,int attack_type,unsigned int tick); + +// ユニットスキル +struct skill_unit *skill_initunit(struct skill_unit_group *group,int idx,int x,int y); +int skill_delunit(struct skill_unit *unit); +struct skill_unit_group *skill_initunitgroup(struct block_list *src, + int count,int skillid,int skilllv,int unit_id); +int skill_delunitgroup(struct skill_unit_group *group); +struct skill_unit_group_tickset *skill_unitgrouptickset_search( + struct block_list *bl,int group_id); +int skill_unitgrouptickset_delete(struct block_list *bl,int group_id); +int skill_clear_unitgroup(struct block_list *src); + +int skill_unit_ondamaged(struct skill_unit *src,struct block_list *bl, + int damage,unsigned int tick); + +int skill_castfix( struct block_list *bl, int time ); +int skill_delayfix( struct block_list *bl, int time ); +int skill_check_unit_range(int m,int x,int y,int range,int skillid); +int skill_check_unit_range2(int m,int x,int y,int range); +// -- moonsoul (added skill_check_unit_cell) +int skill_check_unit_cell(int skillid,int m,int x,int y,int unit_id); +int skill_unit_out_all( struct block_list *bl,unsigned int tick,int range); +int skill_unit_move( struct block_list *bl,unsigned int tick,int range); +int skill_unit_move_unit_group( struct skill_unit_group *group, int m,int dx,int dy); + +struct skill_unit_group *skill_check_dancing( struct block_list *src ); +void skill_stop_dancing(struct block_list *src, int flag); + +// 詠唱キャンセル +int skill_castcancel(struct block_list *bl,int type); + +int skill_gangsterparadise(struct map_session_data *sd ,int type); +void skill_brandishspear_first(struct square *tc,int dir,int x,int y); +void skill_brandishspear_dir(struct square *tc,int dir,int are); +int skill_autospell(struct map_session_data *md,int skillid); +void skill_devotion(struct map_session_data *md,int target); +void skill_devotion2(struct block_list *bl,int crusader); +int skill_devotion3(struct block_list *bl,int target); +void skill_devotion_end(struct map_session_data *md,struct map_session_data *sd,int target); + +#define skill_calc_heal(bl,skill_lv) (( battle_get_lv(bl)+battle_get_int(bl) )/8 *(4+ skill_lv*8)) + +// その他 +int skill_check_cloaking(struct block_list *bl); +int skill_is_danceskill(int id); + +// ステータス異常 +int skill_status_change_start(struct block_list *bl,int type,int val1,int val2,int val3,int val4,int tick,int flag); +int skill_status_change_timer(int tid, unsigned int tick, int id, int data); +int skill_encchant_eremental_end(struct block_list *bl, int type); +int skill_status_change_end( struct block_list* bl , int type,int tid ); +int skill_status_change_clear(struct block_list *bl,int type); + + +// アイテム作成 +int skill_can_produce_mix( struct map_session_data *sd, int nameid, int trigger ); +int skill_produce_mix( struct map_session_data *sd, + int nameid, int slot1, int slot2, int slot3 ); + +int skill_arrow_create( struct map_session_data *sd,int nameid); + +// mobスキルのため +int skill_castend_nodamage_id( struct block_list *src, struct block_list *bl,int skillid,int skilllv,unsigned int tick,int flag ); +int skill_castend_damage_id( struct block_list* src, struct block_list *bl,int skillid,int skilllv,unsigned int tick,int flag ); +int skill_castend_pos2( struct block_list *src, int x,int y,int skillid,int skilllv,unsigned int tick,int flag); + +// スキル攻撃一括処理 +int skill_attack( int attack_type, struct block_list* src, struct block_list *dsrc, + struct block_list *bl,int skillid,int skilllv,unsigned int tick,int flag ); + +void skill_reload(void); + +enum { + ST_NONE,ST_HIDING,ST_CLOAKING,ST_HIDDEN,ST_RIDING,ST_FALCON,ST_CART,ST_SHIELD,ST_SIGHT,ST_EXPLOSIONSPIRITS, + ST_RECOV_WEIGHT_RATE,ST_MOVE_ENABLE,ST_WATER, +}; + +enum { // struct map_session_data の status_changeの番号テーブル +// SC_SENDMAX未満はクライアントへの通知あり。 +// 2-2次職の値はなんかめちゃくちゃっぽいので暫定。たぶん変更されます。 + SC_SENDMAX =128, + SC_PROVOKE = 0, + SC_ENDURE = 1, + SC_TWOHANDQUICKEN = 2, + SC_CONCENTRATE = 3, + SC_HIDING = 4, + SC_CLOAKING = 5, + SC_ENCPOISON = 6, + SC_POISONREACT = 7, + SC_QUAGMIRE = 8, + SC_ANGELUS = 9, + SC_BLESSING =10, + SC_SIGNUMCRUCIS =11, + SC_INCREASEAGI =12, + SC_DECREASEAGI =13, + SC_SLOWPOISON =14, + SC_IMPOSITIO =15, + SC_SUFFRAGIUM =16, + SC_ASPERSIO =17, + SC_BENEDICTIO =18, + SC_KYRIE =19, + SC_MAGNIFICAT =20, + SC_GLORIA =21, + SC_AETERNA =22, + SC_ADRENALINE =23, + SC_WEAPONPERFECTION =24, + SC_OVERTHRUST =25, + SC_MAXIMIZEPOWER =26, + SC_RIDING =27, + SC_FALCON =28, + SC_TRICKDEAD =29, + SC_LOUD =30, + SC_ENERGYCOAT =31, + SC_HALLUCINATION =34, + SC_WEIGHT50 =35, + SC_WEIGHT90 =36, + SC_SPEEDPOTION0 =37, + SC_SPEEDPOTION1 =38, + SC_SPEEDPOTION2 =39, + SC_STRIPWEAPON =50, + SC_STRIPSHIELD =51, + SC_STRIPARMOR =52, + SC_STRIPHELM =53, + SC_CP_WEAPON =54, + SC_CP_SHIELD =55, + SC_CP_ARMOR =56, + SC_CP_HELM =57, + SC_AUTOGUARD =58, + SC_REFLECTSHIELD =59, + SC_DEVOTION =60, + SC_PROVIDENCE =61, + SC_DEFENDER =62, + SC_AUTOSPELL =65, + SC_SPEARSQUICKEN =68, + SC_EXPLOSIONSPIRITS =86, + SC_STEELBODY =87, + SC_COMBO =89, + SC_FLAMELAUNCHER =90, + SC_FROSTWEAPON =91, + SC_LIGHTNINGLOADER =92, + SC_SEISMICWEAPON =93, + SC_AURABLADE =103, /* オーラブレード */ + SC_PARRYING =104, /* パリイング */ + SC_CONCENTRATION =105, /* コンセントレーション */ + SC_TENSIONRELAX =106, /* テンションリラックス */ + SC_BERSERK =107, /* バーサーク */ + SC_ASSUMPTIO =110, /* アシャンプティオ */ + SC_MAGICPOWER =113, /* 魔法力増幅 */ + SC_TRUESIGHT =115, /* トゥルーサイト */ + SC_WINDWALK =116, /* ウインドウォーク */ + SC_MELTDOWN =117, /* メルトダウン */ + SC_CARTBOOST =118, /* カートブースト */ + SC_REJECTSWORD =120, /* リジェクトソード */ + SC_MARIONETTE =121, /* マリオネットコントロール */ + SC_HEADCRUSH =124, /* ヘッドクラッシュ */ + SC_JOINTBEAT =125, /* ジョイントビート */ + + SC_STONE =128, + SC_FREEZE =129, + SC_STAN =130, + SC_SLEEP =131, + SC_POISON =132, + SC_CURSE =133, + SC_SILENCE =134, + SC_CONFUSION =135, + SC_BLIND =136, + SC_DIVINA = SC_SILENCE, + + SC_SAFETYWALL =140, + SC_PNEUMA =141, + SC_WATERBALL =142, + SC_ANKLE =143, + SC_DANCING =144, + SC_KEEPING =145, + SC_BARRIER =146, + + SC_MAGICROD =149, + SC_SIGHT =150, + SC_RUWACH =151, + SC_AUTOCOUNTER =152, + SC_VOLCANO =153, + SC_DELUGE =154, + SC_VIOLENTGALE =155, + SC_BLADESTOP_WAIT =156, + SC_BLADESTOP =157, + SC_EXTREMITYFIST =158, + SC_GRAFFITI =159, + + SC_LULLABY =160, + SC_RICHMANKIM =161, + SC_ETERNALCHAOS =162, + SC_DRUMBATTLE =163, + SC_NIBELUNGEN =164, + SC_ROKISWEIL =165, + SC_INTOABYSS =166, + SC_SIEGFRIED =167, + SC_DISSONANCE =168, + SC_WHISTLE =169, + SC_ASSNCROS =170, + SC_POEMBRAGI =171, + SC_APPLEIDUN =172, + SC_UGLYDANCE =173, + SC_HUMMING =174, + SC_DONTFORGETME =175, + SC_FORTUNE =176, + SC_SERVICE4U =177, + + SC_SPIDERWEB =180, /* スパイダーウェッブ */ + SC_MEMORIZE =181, /* メモライズ */ + + SC_WEDDING =187, //結婚用(結婚衣裳になって歩くのが遅いとか) + SC_NOCHAT =188, //赤エモ状態 + SC_SPLASHER =189, /* ベナムスプラッシャー */ + SC_SELFDESTRUCTION =190, /* 自爆 */ + + +// Used by English Team + SC_BROKNARMOR =32, + SC_BROKNWEAPON =33, + SC_SIGHTTRASHER =73, + SC_BASILICA =125, + SC_ENSEMBLE =159, + SC_FOGWALL =178, + SC_GOSPEL =179, + SC_LANDPROTECTOR =182, + SC_ADAPTATION =183, + SC_CHASEWALK =184, + SC_ATKPOT =185, // [Valaris] + SC_MATKPOT =186, // [Valaris] + SC_MINDBREAKER =191, + SC_SPELLBREAKER =192, + +// -- testing various SC effects +// SC_AURABLADE =81, +// SC_CONCENTRATION =83, +// SC_TENSIONRELAX =84, +// SC_BERSERK =85, +// SC_CALLSPIRITS =100, +// SC_PARRYING =100, +// SC_FREECAST =101, +// SC_ABSORBSPIRIT =102, +// SC_ASSUMPTIO =114, +// SC_SHARPSHOOT =127, +// SC_GANGSTER =184, +// SC_CANNIBALIZE =186, +// SC_SPHEREMINE =187, +// SC_METEOSTORM =189, +// SC_CASTCANCEL =190, +// SC_SPIDERWEB =191, +}; +extern int SkillStatusChangeTable[]; + +enum { + NV_BASIC = 1, + + SM_SWORD, + SM_TWOHAND, + SM_RECOVERY, + SM_BASH, + SM_PROVOKE, + SM_MAGNUM, + SM_ENDURE, + + MG_SRECOVERY, + MG_SIGHT, + MG_NAPALMBEAT, + MG_SAFETYWALL, + MG_SOULSTRIKE, + MG_COLDBOLT, + MG_FROSTDIVER, + MG_STONECURSE, + MG_FIREBALL, + MG_FIREWALL, + MG_FIREBOLT, + MG_LIGHTNINGBOLT, + MG_THUNDERSTORM, + + AL_DP, + AL_DEMONBANE, + AL_RUWACH, + AL_PNEUMA, + AL_TELEPORT, + AL_WARP, + AL_HEAL, + AL_INCAGI, + AL_DECAGI, + AL_HOLYWATER, + AL_CRUCIS, + AL_ANGELUS, + AL_BLESSING, + AL_CURE, + + MC_INCCARRY, + MC_DISCOUNT, + MC_OVERCHARGE, + MC_PUSHCART, + MC_IDENTIFY, + MC_VENDING, + MC_MAMMONITE, + + AC_OWL, + AC_VULTURE, + AC_CONCENTRATION, + AC_DOUBLE, + AC_SHOWER, + + TF_DOUBLE, + TF_MISS, + TF_STEAL, + TF_HIDING, + TF_POISON, + TF_DETOXIFY, + + ALL_RESURRECTION, + + KN_SPEARMASTERY, + KN_PIERCE, + KN_BRANDISHSPEAR, + KN_SPEARSTAB, + KN_SPEARBOOMERANG, + KN_TWOHANDQUICKEN, + KN_AUTOCOUNTER, + KN_BOWLINGBASH, + KN_RIDING, + KN_CAVALIERMASTERY, + + PR_MACEMASTERY, + PR_IMPOSITIO, + PR_SUFFRAGIUM, + PR_ASPERSIO, + PR_BENEDICTIO, + PR_SANCTUARY, + PR_SLOWPOISON, + PR_STRECOVERY, + PR_KYRIE, + PR_MAGNIFICAT, + PR_GLORIA, + PR_LEXDIVINA, + PR_TURNUNDEAD, + PR_LEXAETERNA, + PR_MAGNUS, + + WZ_FIREPILLAR, + WZ_SIGHTRASHER, + WZ_FIREIVY, + WZ_METEOR, + WZ_JUPITEL, + WZ_VERMILION, + WZ_WATERBALL, + WZ_ICEWALL, + WZ_FROSTNOVA, + WZ_STORMGUST, + WZ_EARTHSPIKE, + WZ_HEAVENDRIVE, + WZ_QUAGMIRE, + WZ_ESTIMATION, + + BS_IRON, + BS_STEEL, + BS_ENCHANTEDSTONE, + BS_ORIDEOCON, + BS_DAGGER, + BS_SWORD, + BS_TWOHANDSWORD, + BS_AXE, + BS_MACE, + BS_KNUCKLE, + BS_SPEAR, + BS_HILTBINDING, + BS_FINDINGORE, + BS_WEAPONRESEARCH, + BS_REPAIRWEAPON, + BS_SKINTEMPER, + BS_HAMMERFALL, + BS_ADRENALINE, + BS_WEAPONPERFECT, + BS_OVERTHRUST, + BS_MAXIMIZE, + + HT_SKIDTRAP, + HT_LANDMINE, + HT_ANKLESNARE, + HT_SHOCKWAVE, + HT_SANDMAN, + HT_FLASHER, + HT_FREEZINGTRAP, + HT_BLASTMINE, + HT_CLAYMORETRAP, + HT_REMOVETRAP, + HT_TALKIEBOX, + HT_BEASTBANE, + HT_FALCON, + HT_STEELCROW, + HT_BLITZBEAT, + HT_DETECTING, + HT_SPRINGTRAP, + + AS_RIGHT, + AS_LEFT, + AS_KATAR, + AS_CLOAKING, + AS_SONICBLOW, + AS_GRIMTOOTH, + AS_ENCHANTPOISON, + AS_POISONREACT, + AS_VENOMDUST, + AS_SPLASHER, + + NV_FIRSTAID, + NV_TRICKDEAD, + SM_MOVINGRECOVERY, + SM_FATALBLOW, + SM_AUTOBERSERK, + AC_MAKINGARROW, + AC_CHARGEARROW, + TF_SPRINKLESAND, + TF_BACKSLIDING, + TF_PICKSTONE, + TF_THROWSTONE, + MC_CARTREVOLUTION, + MC_CHANGECART, + MC_LOUD, + AL_HOLYLIGHT, + MG_ENERGYCOAT, + + NPC_PIERCINGATT, + NPC_MENTALBREAKER, + NPC_RANGEATTACK, + NPC_ATTRICHANGE, + NPC_CHANGEWATER, + NPC_CHANGEGROUND, + NPC_CHANGEFIRE, + NPC_CHANGEWIND, + NPC_CHANGEPOISON, + NPC_CHANGEHOLY, + NPC_CHANGEDARKNESS, + NPC_CHANGETELEKINESIS, + NPC_CRITICALSLASH, + NPC_COMBOATTACK, + NPC_GUIDEDATTACK, + NPC_SELFDESTRUCTION, + NPC_SPLASHATTACK, + NPC_SUICIDE, + NPC_POISON, + NPC_BLINDATTACK, + NPC_SILENCEATTACK, + NPC_STUNATTACK, + NPC_PETRIFYATTACK, + NPC_CURSEATTACK, + NPC_SLEEPATTACK, + NPC_RANDOMATTACK, + NPC_WATERATTACK, + NPC_GROUNDATTACK, + NPC_FIREATTACK, + NPC_WINDATTACK, + NPC_POISONATTACK, + NPC_HOLYATTACK, + NPC_DARKNESSATTACK, + NPC_TELEKINESISATTACK, + NPC_MAGICALATTACK, + NPC_METAMORPHOSIS, + NPC_PROVOCATION, + NPC_SMOKING, + NPC_SUMMONSLAVE, + NPC_EMOTION, + NPC_TRANSFORMATION, + NPC_BLOODDRAIN, + NPC_ENERGYDRAIN, + NPC_KEEPING, + NPC_DARKBREATH, + NPC_DARKBLESSING, + NPC_BARRIER, + NPC_DEFENDER, + NPC_LICK, + NPC_HALLUCINATION, + NPC_REBIRTH, + NPC_SUMMONMONSTER, + + RG_SNATCHER, + RG_STEALCOIN, + RG_BACKSTAP, + RG_TUNNELDRIVE, + RG_RAID, + RG_STRIPWEAPON, + RG_STRIPSHIELD, + RG_STRIPARMOR, + RG_STRIPHELM, + RG_INTIMIDATE, + RG_GRAFFITI, + RG_FLAGGRAFFITI, + RG_CLEANER, + RG_GANGSTER, + RG_COMPULSION, + RG_PLAGIARISM, + + AM_AXEMASTERY, + AM_LEARNINGPOTION, + AM_PHARMACY, + AM_DEMONSTRATION, + AM_ACIDTERROR, + AM_POTIONPITCHER, + AM_CANNIBALIZE, + AM_SPHEREMINE, + AM_CP_WEAPON, + AM_CP_SHIELD, + AM_CP_ARMOR, + AM_CP_HELM, + AM_BIOETHICS, + AM_BIOTECHNOLOGY, + AM_CREATECREATURE, + AM_CULTIVATION, + AM_FLAMECONTROL, + AM_CALLHOMUN, + AM_REST, + AM_DRILLMASTER, + AM_HEALHOMUN, + AM_RESURRECTHOMUN, + + CR_TRUST, + CR_AUTOGUARD, + CR_SHIELDCHARGE, + CR_SHIELDBOOMERANG, + CR_REFLECTSHIELD, + CR_HOLYCROSS, + CR_GRANDCROSS, + CR_DEVOTION, + CR_PROVIDENCE, + CR_DEFENDER, + CR_SPEARQUICKEN, + + MO_IRONHAND, + MO_SPIRITSRECOVERY, + MO_CALLSPIRITS, + MO_ABSORBSPIRITS, + MO_TRIPLEATTACK, + MO_BODYRELOCATION, + MO_DODGE, + MO_INVESTIGATE, + MO_FINGEROFFENSIVE, + MO_STEELBODY, + MO_BLADESTOP, + MO_EXPLOSIONSPIRITS, + MO_EXTREMITYFIST, + MO_CHAINCOMBO, + MO_COMBOFINISH, + + SA_ADVANCEDBOOK, + SA_CASTCANCEL, + SA_MAGICROD, + SA_SPELLBREAKER, + SA_FREECAST, + SA_AUTOSPELL, + SA_FLAMELAUNCHER, + SA_FROSTWEAPON, + SA_LIGHTNINGLOADER, + SA_SEISMICWEAPON, + SA_DRAGONOLOGY, + SA_VOLCANO, + SA_DELUGE, + SA_VIOLENTGALE, + SA_LANDPROTECTOR, + SA_DISPELL, + SA_ABRACADABRA, + SA_MONOCELL, + SA_CLASSCHANGE, + SA_SUMMONMONSTER, + SA_REVERSEORCISH, + SA_DEATH, + SA_FORTUNE, + SA_TAMINGMONSTER, + SA_QUESTION, + SA_GRAVITY, + SA_LEVELUP, + SA_INSTANTDEATH, + SA_FULLRECOVERY, + SA_COMA, + + BD_ADAPTATION, + BD_ENCORE, + BD_LULLABY, + BD_RICHMANKIM, + BD_ETERNALCHAOS, + BD_DRUMBATTLEFIELD, + BD_RINGNIBELUNGEN, + BD_ROKISWEIL, + BD_INTOABYSS, + BD_SIEGFRIED, + BD_RAGNAROK, + + BA_MUSICALLESSON, + BA_MUSICALSTRIKE, + BA_DISSONANCE, + BA_FROSTJOKE, + BA_WHISTLE, + BA_ASSASSINCROSS, + BA_POEMBRAGI, + BA_APPLEIDUN, + + DC_DANCINGLESSON, + DC_THROWARROW, + DC_UGLYDANCE, + DC_SCREAM, + DC_HUMMING, + DC_DONTFORGETME, + DC_FORTUNEKISS, + DC_SERVICEFORYOU, + + WE_MALE = 334, + WE_FEMALE, + WE_CALLPARTNER, + + NPC_SELFDESTRUCTION2 = 331, + NPC_DARKCROSS = 338, + + LK_AURABLADE = 355, + LK_PARRYING, + LK_CONCENTRATION, + LK_TENSIONRELAX, + LK_BERSERK, + LK_FURY, + HP_ASSUMPTIO, + HP_BASILICA, + HP_MEDITATIO, + HW_SOULDRAIN, + HW_MAGICCRASHER, + HW_MAGICPOWER, + PA_PRESSURE, + PA_SACRIFICE, + PA_GOSPEL, + CH_PALMSTRIKE, + CH_TIGERFIST, + CH_CHAINCRUSH, + PF_HPCONVERSION, + PF_SOULCHANGE, + PF_SOULBURN, + ASC_KATAR, + ASC_HALLUCINATION, + ASC_EDP, + ASC_BREAKER, + SN_SIGHT, + SN_FALCONASSAULT, + SN_SHARPSHOOTING, + SN_WINDWALK, + WS_MELTDOWN, + WS_CREATECOIN, + WS_CREATENUGGET, + WS_CARTBOOST, + WS_SYSTEMCREATE, + ST_CHASEWALK, + ST_REJECTSWORD, + ST_STEALBACKPACK, + CR_ALCHEMY, + CR_SYNTHESISPOTION, + CG_ARROWVULCAN, + CG_MOONLIT, + CG_MARIONETTE, + LK_SPIRALPIERCE, + LK_HEADCRUSH, + LK_JOINTBEAT, + HW_NAPALMVULCAN, + CH_SOULCOLLECT, + PF_MINDBREAKER, + PF_MEMORIZE, + PF_FOGWALL, + PF_SPIDERWEB, + ASC_METEORASSAULT, + ASC_CDP, + WE_BABY, + WE_CALLPARENT, + WE_CALLBABY, + TK_RUN, + TK_READYSTORM, + TK_STORMKICK, + TK_READYDOWN, + TK_DOWNKICK, + TK_READYTURN, + TK_TURNKICK, + TK_READYCOUNTER, + TK_COUNTER, + TK_DODGE, + TK_JUMPKICK, + TK_HPTIME, + TK_SPTIME, + TK_POWER, + TK_SEVENWIND, + TK_HIGHJUMP, + SG_FEEL, + SG_SUN_WARM, + SG_MOON_WARM, + SG_STAR_WARM, + SG_SUN_COMFORT, + SG_MOON_COMFORT, + SG_STAR_COMFORT, + SG_HATE, + SG_SUN_ANGER, + SG_MOON_ANGER, + SG_STAR_ANGER, + SG_SUN_BLESS, + SG_MOON_BLESS, + SG_STAR_BLESS, + SG_DEVIL, + SG_FRIEND, + SG_KNOWLEDGE, + SG_FUSION, + SL_ALCHEMIST, + AM_BERSERKPITCHER, + SL_MONK, + SL_STAR, + SL_SAGE, + SL_CRUSADER, + SL_SUPERNOVICE, + SL_KNIGHT, + SL_WIZARD, + SL_PRIEST, + SL_BARDDANCER, + SL_ROGUE, + SL_ASSASIN, + SL_BLACKSMITH, + BS_ADRENALINE2, + SL_HUNTER, + SL_SOULLINKER, + SL_KAIZEL, + SL_KAAHI, + SL_KAUPE, + SL_KAITE, + SL_KAINA, + SL_STIN, + SL_STUN, + SL_SMA, + SL_SWOO, + SL_SKE, + SL_SKA, + + GD_APPROVAL=10000, + GD_KAFRACONTACT, + GD_GUARDIANRESEARCH, + GD_CHARISMA, + GD_EXTENSION, +}; + +#endif + diff --git a/src/map/storage.c b/src/map/storage.c new file mode 100644 index 0000000..696a74e --- /dev/null +++ b/src/map/storage.c @@ -0,0 +1,576 @@ +// $Id: storage.c,v 1.3 2004/09/25 02:05:22 MouseJstr Exp $ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "db.h" +#include "itemdb.h" +#include "clif.h" +#include "intif.h" +#include "pc.h" +#include "storage.h" +#include "guild.h" +#include "nullpo.h" + +#ifdef MEMWATCH +#include "memwatch.h" +#endif + +static struct dbt *storage_db; +static struct dbt *guild_storage_db; + +/*========================================== + * 倉庫内アイテムソート + *------------------------------------------ + */ +int storage_comp_item(const void *_i1, const void *_i2){ +struct item *i1=(struct item *)_i1; +struct item *i2=(struct item *)_i2; + + if (i1->nameid == i2->nameid) { + return 0; + } else if (!(i1->nameid) || !(i1->amount)){ + return 1; + } else if (!(i2->nameid) || !(i2->amount)){ + return -1; + } else { + return i1->nameid - i2->nameid; + } +} + + +void sortage_sortitem(struct storage* stor){ + nullpo_retv(stor); + + qsort(stor->storage, MAX_STORAGE, sizeof(struct item), storage_comp_item); +} + +void sortage_gsortitem(struct guild_storage* gstor){ + nullpo_retv(gstor); + + qsort(gstor->storage, MAX_GUILD_STORAGE, sizeof(struct item), storage_comp_item); +} + +/*========================================== + * 初期化とか + *------------------------------------------ + */ +int do_init_storage(void) // map.c::do_init()から呼ばれる +{ + storage_db=numdb_init(); + guild_storage_db=numdb_init(); + return 1; +} + +void do_final_storage(void) // map.c::do_final()から呼ばれる +{ +} + +struct storage *account2storage(int account_id) +{ + struct storage *stor; + stor=numdb_search(storage_db,account_id); + if(stor == NULL) { + stor = calloc(sizeof(struct storage), 1); + if(stor == NULL){ + printf("storage: out of memory!\n"); + exit(0); + } + memset(stor,0,sizeof(struct storage)); + stor->account_id=account_id; + numdb_insert(storage_db,stor->account_id,stor); + } + return stor; +} + +// Just to ask storage, without creation +struct storage *account2storage2(int account_id) { + return numdb_search(storage_db, account_id); +} + +int storage_delete(int account_id) +{ + struct storage *stor = numdb_search(storage_db,account_id); + if(stor) { + numdb_erase(storage_db,account_id); + free(stor); + } + return 0; +} + +/*========================================== + * カプラ倉庫を開く + *------------------------------------------ + */ +int storage_storageopen(struct map_session_data *sd) +{ + struct storage *stor; + + nullpo_retr(0, sd); + + if((stor = numdb_search(storage_db,sd->status.account_id)) != NULL) { + stor->storage_status = 1; + sd->state.storage_flag = 0; + clif_storageitemlist(sd,stor); + clif_storageequiplist(sd,stor); + clif_updatestorageamount(sd,stor); + return 0; + } else + intif_request_storage(sd->status.account_id); + + return 1; +} + +/*========================================== + * カプラ倉庫へアイテム追加 + *------------------------------------------ + */ +int storage_additem(struct map_session_data *sd,struct storage *stor,struct item *item_data,int amount) +{ + struct item_data *data; + int i; + + nullpo_retr(1, sd); + nullpo_retr(1, stor); + nullpo_retr(1, item_data); + + if(item_data->nameid <= 0 || amount <= 0) + return 1; + nullpo_retr(1, data = itemdb_search(item_data->nameid)); + + i=MAX_STORAGE; + if(!itemdb_isequip2(data)){ + // 装備品ではないので、既所有品なら個数のみ変化させる + for(i=0;i<MAX_STORAGE;i++){ + if(stor->storage[i].nameid == item_data->nameid && + stor->storage[i].card[0] == item_data->card[0] && stor->storage[i].card[1] == item_data->card[1] && + stor->storage[i].card[2] == item_data->card[2] && stor->storage[i].card[3] == item_data->card[3]){ + if(stor->storage[i].amount+amount > MAX_AMOUNT) + return 1; + stor->storage[i].amount+=amount; + clif_storageitemadded(sd,stor,i,amount); + break; + } + } + } + if(i>=MAX_STORAGE){ + // 装備品か未所有品だったので空き欄へ追加 + for(i=0;i<MAX_STORAGE;i++){ + if(stor->storage[i].nameid==0){ + memcpy(&stor->storage[i],item_data,sizeof(stor->storage[0])); + stor->storage[i].amount=amount; + stor->storage_amount++; + clif_storageitemadded(sd,stor,i,amount); + clif_updatestorageamount(sd,stor); + break; + } + } + if(i>=MAX_STORAGE) + return 1; + } + return 0; +} +/*========================================== + * カプラ倉庫アイテムを減らす + *------------------------------------------ + */ +int storage_delitem(struct map_session_data *sd,struct storage *stor,int n,int amount) +{ + nullpo_retr(1, sd); + nullpo_retr(1, stor); + + if(stor->storage[n].nameid==0 || stor->storage[n].amount<amount) + return 1; + + stor->storage[n].amount-=amount; + if(stor->storage[n].amount==0){ + memset(&stor->storage[n],0,sizeof(stor->storage[0])); + stor->storage_amount--; + clif_updatestorageamount(sd,stor); + } + clif_storageitemremoved(sd,n,amount); + + return 0; +} +/*========================================== + * カプラ倉庫へ入れる + *------------------------------------------ + */ +int storage_storageadd(struct map_session_data *sd,int index,int amount) +{ + struct storage *stor; + + nullpo_retr(0, sd); + nullpo_retr(0, stor=account2storage(sd->status.account_id)); + + if( (stor->storage_amount <= MAX_STORAGE) && (stor->storage_status == 1) ) { // storage not full & storage open + if(index>=0 && index<MAX_INVENTORY) { // valid index + if( (amount <= sd->status.inventory[index].amount) && (amount > 0) ) { //valid amount + if(storage_additem(sd,stor,&sd->status.inventory[index],amount)==0) + // remove item from inventory + pc_delitem(sd,index,amount,0); + } // valid amount + }// valid index + }// storage not full & storage open + + return 0; +} + +/*========================================== + * カプラ倉庫から出す + *------------------------------------------ + */ +int storage_storageget(struct map_session_data *sd,int index,int amount) +{ + struct storage *stor; + int flag; + + nullpo_retr(0, sd); + nullpo_retr(0, stor=account2storage(sd->status.account_id)); + + if(stor->storage_status == 1) { // storage open + if(index>=0 && index<MAX_STORAGE) { // valid index + if( (amount <= stor->storage[index].amount) && (amount > 0) ) { //valid amount + if((flag = pc_additem(sd,&stor->storage[index],amount)) == 0) + storage_delitem(sd,stor,index,amount); + else + clif_additem(sd,0,0,flag); + } // valid amount + }// valid index + }// storage open + + return 0; +} +/*========================================== + * カプラ倉庫へカートから入れる + *------------------------------------------ + */ +int storage_storageaddfromcart(struct map_session_data *sd,int index,int amount) +{ + struct storage *stor; + + nullpo_retr(0, sd); + nullpo_retr(0, stor=account2storage(sd->status.account_id)); + + if( (stor->storage_amount <= MAX_STORAGE) && (stor->storage_status == 1) ) { // storage not full & storage open + if(index>=0 && index<MAX_INVENTORY) { // valid index + if( (amount <= sd->status.cart[index].amount) && (amount > 0) ) { //valid amount + if(storage_additem(sd,stor,&sd->status.cart[index],amount)==0) + pc_cart_delitem(sd,index,amount,0); + } // valid amount + }// valid index + }// storage not full & storage open + + return 0; +} + +/*========================================== + * カプラ倉庫からカートへ出す + *------------------------------------------ + */ +int storage_storagegettocart(struct map_session_data *sd,int index,int amount) +{ + struct storage *stor; + + nullpo_retr(0, sd); + nullpo_retr(0, stor=account2storage(sd->status.account_id)); + + if(stor->storage_status == 1) { // storage open + if(index>=0 && index<MAX_STORAGE) { // valid index + if( (amount <= stor->storage[index].amount) && (amount > 0) ) { //valid amount + if(pc_cart_additem(sd,&stor->storage[index],amount)==0){ + storage_delitem(sd,stor,index,amount); + } + } // valid amount + }// valid index + }// storage open + + return 0; +} + + +/*========================================== + * カプラ倉庫を閉じる + *------------------------------------------ + */ +int storage_storageclose(struct map_session_data *sd) +{ + struct storage *stor; + + nullpo_retr(0, sd); + nullpo_retr(0, stor=account2storage(sd->status.account_id)); + + stor->storage_status=0; + sd->state.storage_flag = 0; + clif_storageclose(sd); + + sortage_sortitem(stor); + return 0; +} + +/*========================================== + * ログアウト時開いているカプラ倉庫の保存 + *------------------------------------------ + */ +int storage_storage_quit(struct map_session_data *sd) +{ + struct storage *stor; + + nullpo_retr(0, sd); + + stor = numdb_search(storage_db,sd->status.account_id); + if(stor) stor->storage_status = 0; + + return 0; +} + +int storage_storage_save(struct map_session_data *sd) +{ + struct storage *stor; + + nullpo_retr(0, sd); + + stor=numdb_search(storage_db,sd->status.account_id); + if(stor) intif_send_storage(stor); + + return 0; +} + +struct guild_storage *guild2storage(int guild_id) +{ + struct guild_storage *gs = NULL; + if(guild_search(guild_id) != NULL) { + gs=numdb_search(guild_storage_db,guild_id); + if(gs == NULL) { + gs = calloc(sizeof(struct guild_storage), 1); + if(gs==NULL){ + printf("storage: out of memory!\n"); + exit(0); + } + gs->guild_id=guild_id; + numdb_insert(guild_storage_db,gs->guild_id,gs); + } + } + return gs; +} + +int guild_storage_delete(int guild_id) +{ + struct guild_storage *gstor = numdb_search(guild_storage_db,guild_id); + if(gstor) { + numdb_erase(guild_storage_db,guild_id); + free(gstor); + } + return 0; +} + +int storage_guild_storageopen(struct map_session_data *sd) +{ + struct guild_storage *gstor; + + nullpo_retr(0, sd); + + if(sd->status.guild_id <= 0) + return 2; + if((gstor = numdb_search(guild_storage_db,sd->status.guild_id)) != NULL) { + if(gstor->storage_status) + return 1; + gstor->storage_status = 1; + sd->state.storage_flag = 1; + clif_guildstorageitemlist(sd,gstor); + clif_guildstorageequiplist(sd,gstor); + clif_updateguildstorageamount(sd,gstor); + return 0; + } + else { + gstor = guild2storage(sd->status.guild_id); + gstor->storage_status = 1; + intif_request_guild_storage(sd->status.account_id,sd->status.guild_id); + } + + return 0; +} + +int guild_storage_additem(struct map_session_data *sd,struct guild_storage *stor,struct item *item_data,int amount) +{ + struct item_data *data; + int i; + + nullpo_retr(1, sd); + nullpo_retr(1, stor); + nullpo_retr(1, item_data); + nullpo_retr(1, data = itemdb_search(item_data->nameid)); + + if(item_data->nameid <= 0 || amount <= 0) + return 1; + + i=MAX_GUILD_STORAGE; + if(!itemdb_isequip2(data)){ + // 装備品ではないので、既所有品なら個数のみ変化させる + for(i=0;i<MAX_GUILD_STORAGE;i++){ + if(stor->storage[i].nameid == item_data->nameid && + stor->storage[i].card[0] == item_data->card[0] && stor->storage[i].card[1] == item_data->card[1] && + stor->storage[i].card[2] == item_data->card[2] && stor->storage[i].card[3] == item_data->card[3]){ + if(stor->storage[i].amount+amount > MAX_AMOUNT) + return 1; + stor->storage[i].amount+=amount; + clif_guildstorageitemadded(sd,stor,i,amount); + break; + } + } + } + if(i>=MAX_GUILD_STORAGE){ + // 装備品か未所有品だったので空き欄へ追加 + for(i=0;i<MAX_GUILD_STORAGE;i++){ + if(stor->storage[i].nameid==0){ + memcpy(&stor->storage[i],item_data,sizeof(stor->storage[0])); + stor->storage[i].amount=amount; + stor->storage_amount++; + clif_guildstorageitemadded(sd,stor,i,amount); + clif_updateguildstorageamount(sd,stor); + break; + } + } + if(i>=MAX_GUILD_STORAGE) + return 1; + } + return 0; +} + +int guild_storage_delitem(struct map_session_data *sd,struct guild_storage *stor,int n,int amount) +{ + nullpo_retr(1, sd); + nullpo_retr(1, stor); + + if(stor->storage[n].nameid==0 || stor->storage[n].amount<amount) + return 1; + + stor->storage[n].amount-=amount; + if(stor->storage[n].amount==0){ + memset(&stor->storage[n],0,sizeof(stor->storage[0])); + stor->storage_amount--; + clif_updateguildstorageamount(sd,stor); + } + clif_storageitemremoved(sd,n,amount); + + return 0; +} + +int storage_guild_storageadd(struct map_session_data *sd,int index,int amount) +{ + struct guild_storage *stor; + + nullpo_retr(0, sd); + + if((stor=guild2storage(sd->status.guild_id)) != NULL) { + if( (stor->storage_amount <= MAX_GUILD_STORAGE) && (stor->storage_status == 1) ) { // storage not full & storage open + if(index>=0 && index<MAX_INVENTORY) { // valid index + if( (amount <= sd->status.inventory[index].amount) && (amount > 0) ) { //valid amount + if(guild_storage_additem(sd,stor,&sd->status.inventory[index],amount)==0) + // remove item from inventory + pc_delitem(sd,index,amount,0); + } // valid amount + }// valid index + }// storage not full & storage open + } + + return 0; +} + +int storage_guild_storageget(struct map_session_data *sd,int index,int amount) +{ + struct guild_storage *stor; + int flag; + + nullpo_retr(0, sd); + + if((stor=guild2storage(sd->status.guild_id)) != NULL) { + if(stor->storage_status == 1) { // storage open + if(index>=0 && index<MAX_GUILD_STORAGE) { // valid index + if( (amount <= stor->storage[index].amount) && (amount > 0) ) { //valid amount + if((flag = pc_additem(sd,&stor->storage[index],amount)) == 0) + guild_storage_delitem(sd,stor,index,amount); + else + clif_additem(sd,0,0,flag); + } // valid amount + }// valid index + }// storage open + } + + return 0; +} + +int storage_guild_storageaddfromcart(struct map_session_data *sd,int index,int amount) +{ + struct guild_storage *stor; + + nullpo_retr(0, sd); + + if((stor=guild2storage(sd->status.guild_id)) != NULL) { + if( (stor->storage_amount <= MAX_GUILD_STORAGE) && (stor->storage_status == 1) ) { // storage not full & storage open + if(index>=0 && index<MAX_INVENTORY) { // valid index + if( (amount <= sd->status.cart[index].amount) && (amount > 0) ) { //valid amount + if(guild_storage_additem(sd,stor,&sd->status.cart[index],amount)==0) + pc_cart_delitem(sd,index,amount,0); + } // valid amount + }// valid index + }// storage not full & storage open + } + + return 0; +} + +int storage_guild_storagegettocart(struct map_session_data *sd,int index,int amount) +{ + struct guild_storage *stor; + + nullpo_retr(0, sd); + + if((stor=guild2storage(sd->status.guild_id)) != NULL) { + if(stor->storage_status == 1) { // storage open + if(index>=0 && index<MAX_GUILD_STORAGE) { // valid index + if( (amount <= stor->storage[index].amount) && (amount > 0) ) { //valid amount + if(pc_cart_additem(sd,&stor->storage[index],amount)==0){ + guild_storage_delitem(sd,stor,index,amount); + } + } // valid amount + }// valid index + }// storage open + } + + return 0; +} + +int storage_guild_storageclose(struct map_session_data *sd) +{ + struct guild_storage *stor; + + nullpo_retr(0, sd); + + if((stor=guild2storage(sd->status.guild_id)) != NULL) { + intif_send_guild_storage(sd->status.account_id,stor); + stor->storage_status = 0; + sd->state.storage_flag = 0; + sortage_gsortitem(stor); + } + clif_storageclose(sd); + + return 0; +} + +int storage_guild_storage_quit(struct map_session_data *sd,int flag) +{ + struct guild_storage *stor; + + nullpo_retr(0, sd); + + stor = numdb_search(guild_storage_db,sd->status.guild_id); + if(stor) { + if(!flag) + intif_send_guild_storage(sd->status.account_id,stor); + stor->storage_status = 0; + sd->state.storage_flag = 0; + } + + return 0; +} diff --git a/src/map/storage.h b/src/map/storage.h new file mode 100644 index 0000000..489741c --- /dev/null +++ b/src/map/storage.h @@ -0,0 +1,38 @@ +// $Id: storage.h,v 1.3 2004/09/25 05:32:19 MouseJstr Exp $ +#ifndef _STORAGE_H_ +#define _STORAGE_H_ + +#include "mmo.h" + +int storage_storageopen(struct map_session_data *sd); +int storage_storageadd(struct map_session_data *sd,int index,int amount); +int storage_storageget(struct map_session_data *sd,int index,int amount); +int storage_storageaddfromcart(struct map_session_data *sd,int index,int amount); +int storage_storagegettocart(struct map_session_data *sd,int index,int amount); +int storage_storageclose(struct map_session_data *sd); +int do_init_storage(void); +void do_final_storage(void); +struct storage *account2storage(int account_id); +struct storage *account2storage2(int account_id); +int storage_delete(int account_id); +int storage_storage_quit(struct map_session_data *sd); +int storage_storage_save(struct map_session_data *sd); + +struct guild_storage *guild2storage(int guild_id); +int guild_storage_delete(int guild_id); +int storage_guild_storageopen(struct map_session_data *sd); +int guild_storage_additem(struct map_session_data *sd,struct guild_storage *stor,struct item *item_data,int amount); +int guild_storage_delitem(struct map_session_data *sd,struct guild_storage *stor,int n,int amount); +int storage_guild_storageadd(struct map_session_data *sd,int index,int amount); +int storage_guild_storageget(struct map_session_data *sd,int index,int amount); +int storage_guild_storageaddfromcart(struct map_session_data *sd,int index,int amount); +int storage_guild_storagegettocart(struct map_session_data *sd,int index,int amount); +int storage_guild_storageclose(struct map_session_data *sd); +int storage_guild_storage_quit(struct map_session_data *sd,int flag); + +int storage_comp_item(const void *_i1, const void *_i2); +//int storage_comp_item(const struct item* i1, const struct item* i2); +void sortage_sortitem(struct storage* stor); +void sortage_gsortitem(struct guild_storage* gstor); + +#endif diff --git a/src/map/trade.c b/src/map/trade.c new file mode 100644 index 0000000..da43d67 --- /dev/null +++ b/src/map/trade.c @@ -0,0 +1,255 @@ +#include <stdio.h> +#include <string.h> + +#include "clif.h" +#include "itemdb.h" +#include "map.h" +#include "trade.h" +#include "pc.h" +#include "npc.h" +#include "battle.h" +#include "nullpo.h" + +/*========================================== + * 取引要請を相手に送る + *------------------------------------------ + */ +void trade_traderequest(struct map_session_data *sd,int target_id) +{ + struct map_session_data *target_sd; + + nullpo_retv(sd); + + if((target_sd = map_id2sd(target_id)) != NULL){ + if(!battle_config.invite_request_check) { + if(target_sd->guild_invite>0 || target_sd->party_invite>0){ + clif_tradestart(sd,2); // 相手はPT要請中かGuild要請中 + return; + } + } + if((target_sd->trade_partner !=0) || (sd->trade_partner !=0)) { + trade_tradecancel(sd); //person is in another trade + } + else{ + if(sd->bl.m != target_sd->bl.m + || (sd->bl.x - target_sd->bl.x <= -5 || sd->bl.x - target_sd->bl.x >= 5) + || (sd->bl.y - target_sd->bl.y <= -5 || sd->bl.y - target_sd->bl.y >= 5)) { + clif_tradestart(sd,0); //too far + } + else if(sd!=target_sd) { + target_sd->trade_partner = sd->status.account_id; + sd->trade_partner = target_sd->status.account_id; + clif_traderequest(target_sd,sd->status.name); + } + } + } + else{ + clif_tradestart(sd,1); //character does not exist + } +} + +/*========================================== + * 取引要請 + *------------------------------------------ + */ +void trade_tradeack(struct map_session_data *sd,int type) +{ + struct map_session_data *target_sd; + + nullpo_retv(sd); + + if((target_sd = map_id2sd(sd->trade_partner)) != NULL){ + clif_tradestart(target_sd,type); + clif_tradestart(sd,type); + if(type == 4){ // Cancel + sd->deal_locked =0; + sd->trade_partner=0; + target_sd->deal_locked=0; + target_sd->trade_partner=0; + } + if(sd->npc_id != 0) + npc_event_dequeue(sd); + if(target_sd->npc_id != 0) + npc_event_dequeue(target_sd); + } +} + +/*========================================== + * アイテム追加 + *------------------------------------------ + */ +void trade_tradeadditem(struct map_session_data *sd,int index,int amount) +{ + struct map_session_data *target_sd; + int trade_i; + int trade_weight=0; + int c; + + nullpo_retv(sd); + + if(((target_sd = map_id2sd(sd->trade_partner)) != NULL) && (sd->deal_locked < 1)){ + if(index<2 || index>=MAX_INVENTORY+2){ + if(index == 0 && amount > 0 && amount <= sd->status.zeny){ + sd->deal_zeny=amount; + clif_tradeadditem(sd,target_sd,0,amount); + } + }else if(amount <= sd->status.inventory[index-2].amount && amount > 0){ + for(trade_i=0; trade_i<10;trade_i++){ + if(sd->deal_item_amount[trade_i] == 0){ + trade_weight+=sd->inventory_data[index-2]->weight*amount; + if(target_sd->weight + trade_weight > target_sd->max_weight){ + clif_tradeitemok(sd,index,0,1); //fail to add item -- the player was over weighted. + amount = 0; // [MouseJstr] + }else{ + for(c=0; c==trade_i-1;c++){ // re-deal exploit protection [Valaris] + if(sd->deal_item_index[c]==index) { + trade_tradecancel(sd); + return; + } + } + sd->deal_item_index[trade_i] =index; + sd->deal_item_amount[trade_i]+=amount; + clif_tradeitemok(sd,index,amount,0); //success to add item + clif_tradeadditem(sd,target_sd,index,amount); + } + break; + }else{ + trade_weight+=sd->inventory_data[sd->deal_item_index[trade_i]-2]->weight*sd->deal_item_amount[trade_i]; + } + } + } + } +} + +/*========================================== + * アイテム追加完了(ok押し) + *------------------------------------------ + */ +void trade_tradeok(struct map_session_data *sd) +{ + struct map_session_data *target_sd; + int trade_i; + + nullpo_retv(sd); + + for(trade_i=0;trade_i<10;trade_i++) { + if(sd->deal_item_amount[trade_i]>sd->status.inventory[sd->deal_item_index[trade_i]-2].amount || + sd->deal_item_amount[trade_i]<0) { + trade_tradecancel(sd); + return; + } + + } + + if((target_sd = map_id2sd(sd->trade_partner)) != NULL){ + sd->deal_locked=1; + clif_tradeitemok(sd,0,0,0); + clif_tradedeal_lock(sd,0); + clif_tradedeal_lock(target_sd,1); + } +} + +/*========================================== + * 取引キャンセル + *------------------------------------------ + */ +void trade_tradecancel(struct map_session_data *sd) +{ + struct map_session_data *target_sd; + int trade_i; + + nullpo_retv(sd); + + if((target_sd = map_id2sd(sd->trade_partner)) != NULL){ + for(trade_i=0; trade_i<10;trade_i++) { //give items back (only virtual) + if(sd->deal_item_amount[trade_i] != 0) { + clif_additem(sd,sd->deal_item_index[trade_i]-2,sd->deal_item_amount[trade_i],0); + sd->deal_item_index[trade_i] =0; + sd->deal_item_amount[trade_i]=0; + } + if(target_sd->deal_item_amount[trade_i] != 0) { + clif_additem(target_sd,target_sd->deal_item_index[trade_i]-2,target_sd->deal_item_amount[trade_i],0); + target_sd->deal_item_index[trade_i] =0; + target_sd->deal_item_amount[trade_i]=0; + } + } + if(sd->deal_zeny) { + clif_updatestatus(sd,SP_ZENY); + sd->deal_zeny=0; + } + if(target_sd->deal_zeny) { + clif_updatestatus(target_sd,SP_ZENY); + target_sd->deal_zeny=0; + } + sd->deal_locked =0; + sd->trade_partner=0; + target_sd->deal_locked=0; + target_sd->trade_partner=0; + clif_tradecancelled(sd); + clif_tradecancelled(target_sd); + } +} + +/*========================================== + * 取引許諾(trade押し) + *------------------------------------------ + */ +void trade_tradecommit(struct map_session_data *sd) +{ + struct map_session_data *target_sd; + int trade_i; + + nullpo_retv(sd); + + if((target_sd = map_id2sd(sd->trade_partner)) != NULL){ + if( (sd->deal_locked >=1) && (target_sd->deal_locked >=1) ){ // both have pressed 'ok' + if(sd->deal_locked < 2) {sd->deal_locked=2;} // set locked to 2 + if(target_sd->deal_locked==2) { // the other one pressed 'trade' too + for(trade_i=0; trade_i<10;trade_i++) { + if(sd->deal_item_amount[trade_i] != 0) { + int n=sd->deal_item_index[trade_i]-2; + int flag; + flag = pc_additem(target_sd,&sd->status.inventory[n],sd->deal_item_amount[trade_i]); + if(flag==0) + pc_delitem(sd,n,sd->deal_item_amount[trade_i],1); + else + clif_additem(sd,n,sd->deal_item_amount[trade_i],0); + sd->deal_item_index[trade_i] =0; + sd->deal_item_amount[trade_i]=0; + } + if(target_sd->deal_item_amount[trade_i] != 0) { + int n=target_sd->deal_item_index[trade_i]-2; + int flag; + flag = pc_additem(sd,&target_sd->status.inventory[n],target_sd->deal_item_amount[trade_i]); + if(flag==0) + pc_delitem(target_sd,n,target_sd->deal_item_amount[trade_i],1); + else + clif_additem(target_sd,n,target_sd->deal_item_amount[trade_i],0); + target_sd->deal_item_index[trade_i] =0; + target_sd->deal_item_amount[trade_i]=0; + } + } + if(sd->deal_zeny) { + sd->status.zeny -= sd->deal_zeny; + clif_updatestatus(sd,SP_ZENY); + target_sd->status.zeny += sd->deal_zeny; + clif_updatestatus(target_sd,SP_ZENY); + sd->deal_zeny=0; + } + if(target_sd->deal_zeny) { + target_sd->status.zeny -= target_sd->deal_zeny; + clif_updatestatus(target_sd,SP_ZENY); + sd->status.zeny += target_sd->deal_zeny; + clif_updatestatus(sd,SP_ZENY); + target_sd->deal_zeny=0; + } + sd->deal_locked =0; + sd->trade_partner=0; + target_sd->deal_locked=0; + target_sd->trade_partner=0; + clif_tradecompleted(sd,0); + clif_tradecompleted(target_sd,0); + } + } + } +} diff --git a/src/map/trade.h b/src/map/trade.h new file mode 100644 index 0000000..01cbce7 --- /dev/null +++ b/src/map/trade.h @@ -0,0 +1,13 @@ +// $Id: trade.h,v 1.2 2004/09/25 05:32:19 MouseJstr Exp $ +#ifndef _TRADE_H_ +#define _TRADE_H_ + +#include "map.h" +void trade_traderequest(struct map_session_data *sd,int target_id); +void trade_tradeack(struct map_session_data *sd,int type); +void trade_tradeadditem(struct map_session_data *sd,int index,int amount); +void trade_tradeok(struct map_session_data *sd); +void trade_tradecancel(struct map_session_data *sd); +void trade_tradecommit(struct map_session_data *sd); + +#endif // _TRADE_H_ diff --git a/src/map/vending.c b/src/map/vending.c new file mode 100644 index 0000000..189584d --- /dev/null +++ b/src/map/vending.c @@ -0,0 +1,163 @@ +// $Id: vending.c,v 1.2 2004/09/25 05:32:19 MouseJstr Exp $ +#include <stdio.h> +#include <string.h> + +#include "clif.h" +#include "itemdb.h" +#include "map.h" +#include "vending.h" +#include "pc.h" +#include "skill.h" +#include "battle.h" +#include "nullpo.h" + +/*========================================== + * 露店閉鎖 + *------------------------------------------ +*/ +void vending_closevending(struct map_session_data *sd) +{ + + nullpo_retv(sd); + + sd->vender_id=0; + clif_closevendingboard(&sd->bl,0); +} + +/*========================================== + * 露店アイテムリスト要求 + *------------------------------------------ + */ +void vending_vendinglistreq(struct map_session_data *sd,int id) +{ + struct map_session_data *vsd; + + nullpo_retv(sd); + + if( (vsd=map_id2sd(id)) == NULL ) + return; + if(vsd->vender_id==0) + return; + clif_vendinglist(sd,id,vsd->vending); +} + +/*========================================== + * 露店アイテム購入 + *------------------------------------------ + */ +void vending_purchasereq(struct map_session_data *sd,int len,int id,unsigned char *p) +{ + int i,j,w,z,new=0,blank,vend_list[12]; + short amount,index; + struct map_session_data *vsd=map_id2sd(id); + + nullpo_retv(sd); + + blank=pc_inventoryblank(sd); + + if(vsd==NULL) + return; + if(vsd->vender_id==0) + return; + if(vsd->vender_id==sd->bl.id) + return; + for(i=0,w=z=0;8+4*i<len;i++){ + amount=*(short*)(p+4*i); + index=*(short*)(p+2+4*i)-2; +/* + if(amount < 0) return; //add + for(j=0;j<vsd->vend_num;j++) + if(0<vsd->vending[j].amount && amount<=vsd->vending[j].amount && vsd->vending[j].index==index) + break; +*/ +//ADD_start + for(j=0;j < vsd->vend_num;j++) { + if(0 < vsd->vending[j].amount && vsd->vending[j].index==index) { + if(amount > vsd->vending[j].amount || amount <= 0) { + clif_buyvending(sd,index,vsd->vending[j].amount,4); + return; + } + if(amount <= vsd->vending[j].amount) break; + } + } +//ADD_end + if(j==vsd->vend_num) + return; // 売り切れ + vend_list[i]=j; + z+=vsd->vending[j].value*amount; + if(z > sd->status.zeny){ + clif_buyvending(sd,index,amount,1); + return; // zeny不足 + } + w+=itemdb_weight(vsd->status.cart[index].nameid)*amount; + if(w+sd->weight > sd->max_weight){ + clif_buyvending(sd,index,amount,2); + return; // 重量超過 + } + switch(pc_checkadditem(sd,vsd->status.cart[index].nameid,amount)){ + case ADDITEM_EXIST: + break; + case ADDITEM_NEW: + new++; + if(new > blank) + return; // 種類数超過 + break; + case ADDITEM_OVERAMOUNT: + return; // アイテム数超過 + } + } + if(z < 0 || z > MAX_ZENY){ //Zeny Bug Fixed by Darkchild + clif_tradecancelled(sd); + clif_tradecancelled(vsd); + return; + } + pc_payzeny(sd,z); + pc_getzeny(vsd,z); + for(i=0;8+4*i<len;i++){ + amount=*(short*)(p+4*i); + index=*(short*)(p+2+4*i)-2; + if(amount < 0) break; //add + pc_additem(sd,&vsd->status.cart[index],amount); + vsd->vending[vend_list[i]].amount-=amount; + pc_cart_delitem(vsd,index,amount,0); + clif_vendingreport(vsd,index,amount); + } +} + +/*========================================== + * 露店開設 + *------------------------------------------ + */ +void vending_openvending(struct map_session_data *sd,int len,char *message,int flag,unsigned char *p) +{ + int i; + + nullpo_retv(sd); + + if(!pc_checkskill(sd,MC_VENDING) || !pc_iscarton(sd)) { // cart skill and cart check [Valaris] + clif_skill_fail(sd,MC_VENDING,0,0); + return; + } + + if(flag){ + for(i=0;85+8*i<len;i++){ + sd->vending[i].index=*(short*)(p+8*i)-2; + sd->vending[i].amount=*(short*)(p+2+8*i); + sd->vending[i].value=*(int*)(p+4+8*i); + if(sd->vending[i].value>battle_config.vending_max_value)sd->vending[i].value=battle_config.vending_max_value; + // カート内のアイテム数と販売するアイテム数に相違があったら中止 + if(pc_cartitem_amount(sd,sd->vending[i].index,sd->vending[i].amount)<0 || sd->vending[i].value < 0) { // fixes by Valaris and fritz + clif_skill_fail(sd,MC_VENDING,0,0); + return; + } + } + sd->vender_id=sd->bl.id; + sd->vend_num=i; + strcpy(sd->message,message); + if(clif_openvending(sd,sd->vender_id,sd->vending) > 0) + clif_showvendingboard(&sd->bl,message,0); + else + sd->vender_id=0; + } +} + diff --git a/src/map/vending.h b/src/map/vending.h new file mode 100644 index 0000000..41aa731 --- /dev/null +++ b/src/map/vending.h @@ -0,0 +1,12 @@ +// $Id: vending.h,v 1.2 2004/09/25 05:32:19 MouseJstr Exp $ +#ifndef _VENDING_H_ +#define _VENDING_H_ + +#include "map.h" + +void vending_closevending(struct map_session_data *sd); +void vending_openvending(struct map_session_data *sd,int len,char *message,int flag,unsigned char *p); +void vending_vendinglistreq(struct map_session_data *sd,int id); +void vending_purchasereq(struct map_session_data *sd,int len,int id,unsigned char *p); + +#endif // _VENDING_H_ diff --git a/src/mra.patch b/src/mra.patch new file mode 100644 index 0000000..b8ae365 --- /dev/null +++ b/src/mra.patch @@ -0,0 +1,69 @@ +diff -u -r athena/src/map/clif.c athenanew/src/map/clif.c +--- athena/src/map/clif.c 2005-04-16 17:07:03.000000000 +0000 ++++ athenanew/src/map/clif.c 2005-05-21 18:25:01.121659080 +0000 +@@ -3208,17 +3208,19 @@ + * アイテム追加成功/失敗 + *------------------------------------------ + */ +-int clif_tradeitemok(struct map_session_data *sd,int index,int fail) ++int clif_tradeitemok(struct map_session_data *sd,int index,int amount,int fail) + { + int fd; + + nullpo_retr(0, sd); + + fd=sd->fd; +- WFIFOW(fd,0)=0xea; ++ WFIFOW(fd,0)=0x1b1; ++ //WFIFOW(fd,0)=0xea; + WFIFOW(fd,2)=index; +- WFIFOB(fd,4)=fail; +- WFIFOSET(fd,packet_len_table[0xea]); ++ WFIFOW(fd,4)=amount; ++ WFIFOB(fd,6)=fail; ++ WFIFOSET(fd,packet_len_table[0x1b1]); + + return 0; + } +diff -u -r athena/src/map/clif.h athenanew/src/map/clif.h +--- athena/src/map/clif.h 2005-04-16 17:06:56.000000000 +0000 ++++ athenanew/src/map/clif.h 2005-05-21 18:25:33.040806632 +0000 +@@ -97,7 +97,7 @@ + int clif_traderequest(struct map_session_data *sd,char *name); + int clif_tradestart(struct map_session_data *sd,int type); + int clif_tradeadditem(struct map_session_data *sd,struct map_session_data *tsd,int index,int amount); +-int clif_tradeitemok(struct map_session_data *sd,int index,int fail); ++int clif_tradeitemok(struct map_session_data *sd,int index,int amount,int fail); + int clif_tradedeal_lock(struct map_session_data *sd,int fail); + int clif_tradecancelled(struct map_session_data *sd); + int clif_tradecompleted(struct map_session_data *sd,int fail); +diff -u -r athena/src/map/trade.c athenanew/src/map/trade.c +--- athena/src/map/trade.c 2005-04-16 17:08:06.000000000 +0000 ++++ athenanew/src/map/trade.c 2005-05-21 18:26:46.750601040 +0000 +@@ -98,7 +98,7 @@ + if(sd->deal_item_amount[trade_i] == 0){ + trade_weight+=sd->inventory_data[index-2]->weight*amount; + if(target_sd->weight + trade_weight > target_sd->max_weight){ +- clif_tradeitemok(sd,index,1); //fail to add item -- the player was over weighted. ++ clif_tradeitemok(sd,index,0,1); //fail to add item -- the player was over weighted. + amount = 0; // [MouseJstr] + }else{ + for(c=0; c==trade_i-1;c++){ // re-deal exploit protection [Valaris] +@@ -109,7 +109,7 @@ + } + sd->deal_item_index[trade_i] =index; + sd->deal_item_amount[trade_i]+=amount; +- clif_tradeitemok(sd,index,0); //success to add item ++ clif_tradeitemok(sd,index,amount,0); //success to add item + clif_tradeadditem(sd,target_sd,index,amount); + } + break; +@@ -143,7 +143,7 @@ + + if((target_sd = map_id2sd(sd->trade_partner)) != NULL){ + sd->deal_locked=1; +- clif_tradeitemok(sd,0,0); ++ clif_tradeitemok(sd,0,0,0); + clif_tradedeal_lock(sd,0); + clif_tradedeal_lock(target_sd,1); + } diff --git a/src/tmw-server.dev b/src/tmw-server.dev new file mode 100644 index 0000000..8098eb2 --- /dev/null +++ b/src/tmw-server.dev @@ -0,0 +1,169 @@ +[Project]
+FileName=tmw-server.dev
+Name=tmw-server
+UnitCount=12
+Type=0
+Ver=1
+ObjFiles=
+Includes=
+Libs=
+PrivateResource=
+ResourceIncludes=
+MakeIncludes=
+Compiler=
+CppCompiler=
+Linker=
+IsCpp=1
+Icon=
+ExeOutput=
+ObjectOutput=
+OverrideOutput=0
+OverrideOutputName=
+HostApplication=
+Folders=
+CommandLine=
+UseCustomMakefile=0
+CustomMakefile=
+IncludeVersionInfo=0
+SupportXPThemes=0
+CompilerSet=0
+CompilerSettings=
+
+[Unit1]
+FileName=src\char\char.c
+CompileCpp=1
+Folder=tmw-server
+Compile=1
+Link=1
+Priority=1000
+OverrideBuildCmd=0
+BuildCmd=
+
+[Unit2]
+FileName=src\char\char.h
+CompileCpp=1
+Folder=tmw-server
+Compile=1
+Link=1
+Priority=1000
+OverrideBuildCmd=0
+BuildCmd=
+
+[Unit3]
+FileName=src\char\int_guild.c
+CompileCpp=1
+Folder=tmw-server
+Compile=1
+Link=1
+Priority=1000
+OverrideBuildCmd=0
+BuildCmd=
+
+[Unit4]
+FileName=src\char\int_guild.h
+CompileCpp=1
+Folder=tmw-server
+Compile=1
+Link=1
+Priority=1000
+OverrideBuildCmd=0
+BuildCmd=
+
+[Unit5]
+FileName=src\char\int_party.c
+CompileCpp=1
+Folder=tmw-server
+Compile=1
+Link=1
+Priority=1000
+OverrideBuildCmd=0
+BuildCmd=
+
+[Unit6]
+FileName=src\char\int_party.h
+CompileCpp=1
+Folder=tmw-server
+Compile=1
+Link=1
+Priority=1000
+OverrideBuildCmd=0
+BuildCmd=
+
+[Unit7]
+FileName=src\char\int_pet.c
+CompileCpp=1
+Folder=tmw-server
+Compile=1
+Link=1
+Priority=1000
+OverrideBuildCmd=0
+BuildCmd=
+
+[Unit8]
+FileName=src\char\int_pet.h
+CompileCpp=1
+Folder=tmw-server
+Compile=1
+Link=1
+Priority=1000
+OverrideBuildCmd=0
+BuildCmd=
+
+[Unit9]
+FileName=src\char\int_storage.c
+CompileCpp=1
+Folder=tmw-server
+Compile=1
+Link=1
+Priority=1000
+OverrideBuildCmd=0
+BuildCmd=
+
+[Unit10]
+FileName=src\char\int_storage.h
+CompileCpp=1
+Folder=tmw-server
+Compile=1
+Link=1
+Priority=1000
+OverrideBuildCmd=0
+BuildCmd=
+
+[Unit11]
+FileName=src\char\inter.c
+CompileCpp=1
+Folder=tmw-server
+Compile=1
+Link=1
+Priority=1000
+OverrideBuildCmd=0
+BuildCmd=
+
+[Unit12]
+FileName=src\char\inter.h
+CompileCpp=1
+Folder=tmw-server
+Compile=1
+Link=1
+Priority=1000
+OverrideBuildCmd=0
+BuildCmd=
+
+[VersionInfo]
+Major=0
+Minor=1
+Release=1
+Build=1
+LanguageID=1033
+CharsetID=1252
+CompanyName=
+FileVersion=
+FileDescription=Developed using the Dev-C++ IDE
+InternalName=
+LegalCopyright=
+LegalTrademarks=
+OriginalFilename=
+ProductName=
+ProductVersion=
+AutoIncBuildNr=0
+
diff --git a/src/tmw-server.layout b/src/tmw-server.layout new file mode 100644 index 0000000..6a8b2b4 --- /dev/null +++ b/src/tmw-server.layout @@ -0,0 +1,43 @@ +[Editor_0]
+CursorCol=23
+CursorRow=94
+TopLine=85
+LeftChar=1
+Open=0
+Top=0
+[Editors]
+Focused=-1
+Order=
+[Editor_1]
+Open=0
+Top=0
+[Editor_2]
+Open=0
+Top=0
+[Editor_3]
+Open=0
+Top=0
+[Editor_4]
+Open=0
+Top=0
+[Editor_5]
+Open=0
+Top=0
+[Editor_6]
+Open=0
+Top=0
+[Editor_7]
+Open=0
+Top=0
+[Editor_8]
+Open=0
+Top=0
+[Editor_9]
+Open=0
+Top=0
+[Editor_10]
+Open=0
+Top=0
+[Editor_11]
+Open=0
+Top=0
diff --git a/src/tool/Makefile b/src/tool/Makefile new file mode 100644 index 0000000..8734041 --- /dev/null +++ b/src/tool/Makefile @@ -0,0 +1,6 @@ +all:
+ $(CC) -o adduser adduser.c
+
+clean:
+ rm -f adduser
+ rm -f *.exe
diff --git a/src/tool/adduser.c b/src/tool/adduser.c new file mode 100644 index 0000000..1219540 --- /dev/null +++ b/src/tool/adduser.c @@ -0,0 +1,96 @@ +/* + This program adds an user to account.txt + Don't usr it When login-sever is working. +*/ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +char *account_txt = "../save/account.txt"; + +//----------------------------------------------------- +// Function to suppress control characters in a string. +//----------------------------------------------------- +int remove_control_chars(unsigned char *str) { + int i; + int change = 0; + + for(i = 0; str[i]; i++) { + if (str[i] < 32) { + str[i] = '_'; + change = 1; + } + } + + return change; +} + +int main(int argc, char *argv[]) { + + char username[24]; + char password[24]; + char sex[2]; + + int next_id, id; + char line[1024]; + + // Check to see if account.txt exists. + printf("Checking if '%s' file exists...\n", account_txt); + FILE *FPaccin = fopen(account_txt, "r"); + if (FPaccin == NULL) { + printf("'%s' file not found!\n", account_txt); + printf("Run the setup wizard please.\n"); + exit(0); + } + + next_id = 2000000; + while(fgets(line, sizeof(line)-1, FPaccin)) { + if (line[0] == '/' && line[1] == '/') { continue; } + if (sscanf(line, "%d\t%%newid%%\n", &id) == 1) { + if (next_id < id) { + next_id = id; + } + } else { + sscanf(line,"%i%[^ ]", &id); + if (next_id <= id) { + next_id = id +1; + } + } + } + close(FPaccin); + printf("File exists.\n"); + + printf("Don't create an account if the login-server is online!!!\n"); + printf("If the login-server is online, press ctrl+C now to stop this software.\n"); + printf("\n"); + + strcpy(username, ""); + while (strlen(username) < 4 || strlen(username) > 23) { + printf("Enter an username (4-23 characters): "); + scanf("%s", &username); + username[23] = 0; + remove_control_chars(username); + } + + strcpy(password, ""); + while (strlen(password) < 4 || strlen(password) > 23) { + printf("Enter a password (4-23 characters): "); + scanf("%s", &password); + password[23] = 0; + remove_control_chars(password); + } + + strcpy(sex, ""); + while (strcmp(sex, "F") != 0 && strcmp(sex, "M") != 0) { + printf("Enter a gender (M for male, F for female): "); + scanf("%s", &sex); + } + + FILE *FPaccout = fopen(account_txt, "r+"); + fseek(FPaccout, 0, SEEK_END); + fprintf(FPaccout, "%i %s %s - %s -\r\n", next_id, username, password, sex); + close(FPaccout); + + printf("Account added.\n"); +} diff --git a/src/tool/backup b/src/tool/backup new file mode 100644 index 0000000..2b5a958 --- /dev/null +++ b/src/tool/backup @@ -0,0 +1,100 @@ +#!/usr/bin/perl
+
+##########################################################################
+# Athena用データバックアップツール
+#
+# Athenaの各種データファイル*.txtをバックアップするツール
+#
+#-------------------------------------------------------------------------
+# 設定方法
+# 実行する時のカレントフォルダからのデータへのパス、ファイルのリストを
+# 正しく設定します。バックアップ先のフォルダは自動作成されないので、
+# 自分で作成しておく必要があります。
+# フォルダの最後の「/」は省略できません。
+#
+# フォルダは引数でも指定できます。例>./backup ../save/ ./backup_data/
+# フォルダの最後の「/」は省略できません。
+#
+# 実行するとバックアップ先のフォルダへ、ファイル名に現在の日付と時刻を
+# つけてファイルをコピーします。
+#
+# * toolフォルダ内にbackup_dataフォルダを作成し、
+# athena.shの中に「./tool/backup ./save/ ./tool/backup_data/」
+# という行を追加すると、athenaを起動するたびにバックアップが取れます
+#
+# 復元するときは引数に「-r 日付と時刻」を指定します。
+# またその後ろにフォルダを指定することも出来ます
+# 例1> ./backup -r 200309191607
+# 例2> ./backup -r 200309191607 ../save ./backup_data/
+# この例では2003/09/19の16:07分にバックアップしたデータを復元しています
+#
+# 復元するとき、Athenaディレクトリにあるデータは *.bak に名前を変更して
+# 残しているので、いらない場合は rm *.bak などで消してください。
+#
+##########################################################################
+
+$sdir="../save/"; #バックアップ元(Athenaのディレクトリ/save/)
+$tdir="./backup_data/"; #バックアップ先
+
+@files=( #ファイルのリスト
+ "account","athena","storage","party","guild","castle","pet"
+);
+
+
+#-------------------------------設定ここまで-----------------------------
+
+
+
+
+
+
+
+
+
+
+
+if($ARGV[0]=~/^\-r$/i || $ARGV[0]=~/\-\-(recover|restore)/i){
+ #復元処理
+
+ $file=$ARGV[1];
+ $sdir=$ARGV[2]||$sdir;
+ $tdir=$ARGV[3]||$tdir;
+ &restorecopy($_) foreach @files;
+ exit(0);
+}
+
+#バックアップ処理
+$sdir=$ARGV[0]||$sdir;
+$tdir=$ARGV[1]||$tdir;
+
+unless( -d $tdir ){
+ print "$0: \"$tdir\" : No such directory\n";
+ exit(1);
+}
+
+(undef,$min,$hour,$day,$month,$year)=localtime;
+
+$file=sprintf("%04d%02d%02d%02d%02d",
+ $year+1900, $month+1, $day, $hour, $min );
+
+&backupcopy($_) foreach @files;
+exit(0);
+
+sub backupcopy {
+ my($name)= @_;
+ system("cp $sdir$name.txt $tdir$name$file.txt");
+}
+
+sub restorecopy {
+ my($name)= @_;
+ unless( -f "$sdir$name.txt" ){
+ printf("$0: \"$sdir$name.txt\" not found!\n");
+ return 0;
+ }
+ unless( -f "$tdir$name$file.txt" ){
+ printf("$0: \"$tdir$name$file.txt\" not found!\n");
+ return 0;
+ }
+ rename "$sdir$name.txt","$sdir$name.bak";
+ system("cp $tdir$name$file.txt $sdir$name.txt");
+}
diff --git a/src/tool/cgi/addaccount.cgi b/src/tool/cgi/addaccount.cgi new file mode 100644 index 0000000..7d1788c --- /dev/null +++ b/src/tool/cgi/addaccount.cgi @@ -0,0 +1,204 @@ +#!/usr/bin/perl + +#========================================================================= +# addaccount.cgi ver.1.00 +# ladminをラップした、アカウントを作成するCGI。 +# ladmin ver.1.04での動作を確認。 +# +# ** 設定方法 ** +# +# - 下の$ladmin変数にladminへのパスを設定すること。 +# - UNIX系OSで使用する場合はladminと共に改行コードを変換すること、また +# ファイル先頭行をperlの正しいパスにすること。例> $ which perl +# - サーバープログラムやブラウザによっては $cgiuri にこのファイルへの +# 完全なURIをセットしなければならない場合もある。 +# - perlにパスが通っていない場合は $perl をperlへの正しいパスにすること。 +# - 他は普通のCGIと同じです。(実行権やcgi-binフォルダなど) +# +# ** その他 ** +# addaccount.cgi をブラウザで開くとサンプルHTML(そのまま使えます)が +# 開きます。また、このcgiはブラウザから送られるAccept-Languageが +# jaで始まっていればメッセージの一部を日本語に変換します。 +# (IEならインターネットオプションの言語設定で一番上に日本語を置く) +# それ以外の場合は英語のまま出力します。 +#------------------------------------------------------------------------- + +my($ladmin) = "../ladmin"; # ladminのパス(おそらく変更が必要) + +my($cgiuri) = "./addaccount.cgi"; # このファイルのURI +my($perl) = "perl"; # perlのコマンド名 + + + +#--------------------------- 設定ここまで -------------------------------- + + + + + + +use strict; +use CGI; + +my($cgi)= new CGI; +my(%langconv)=( + 'Athena login-server administration tool.*' => '', + 'logged on.*' => '', +); + +# ----- 日本語環境なら変換テーブルをセット ----- +if($ENV{'HTTP_ACCEPT_LANGUAGE'}=~/^ja/){ + my(%tmp)=( + 'Account \[(.+)\] is successfully created.*' + => 'アカウント "$1" を作成しました.', + 'Account \[(.+)\] creation failed\. same account exists.*' + => 'アカウント "$1" は既に存在します.', + 'Illeagal charactor found in UserID.*' + => 'IDの中に不正な文字があります.', + 'Illeagal charactor found in Password.*' + => 'Passwordの中に不正な文字があります.', + 'input UserID 4-24 bytes.' + => 'IDは半角4〜24文字で入力してください.', + 'input Password 4-24 bytes.' + => 'Passwordは半角4〜24文字で入力してください.', + 'Illeagal gender.*' + => '性別がおかしいです.', + 'Cant connect to login server.*' + => 'ログインサーバーに接続できません.', + 'login error.*' + => 'ログインサーバーへの管理者権限ログインに失敗しました', + "Can't execute ladmin.*" + => 'ladminの実行に失敗しました', + 'UserID "(.+)" is already used.*' + => 'ID "$1" は既に使用されています.', + 'You can use UserID \"(.+)\".*' + => 'ID "$1" は使用可能です.', + + 'account making' =>'アカウント作成', + '\>UserID' =>'>ID', + '\>Password' =>'>パスワード', + '\>Gender' =>'>性別', + '\>Male' =>'>男性', + '\>Female' =>'>女性', + '\"Make Account\"' =>'"アカウント作成"', + '\"Check UserID\"' =>'"IDのチェック"', + ); + map { $langconv{$_}=$tmp{$_}; } keys (%tmp); +} + +# ----- 追加 ----- +if( $cgi->param("addaccount") ){ + my($userid)= $cgi->param("userid"); + my($passwd)= $cgi->param("passwd"); + my($gender)= lc(substr($cgi->param("gender"),0,1)); + if(length($userid)<4 || length($userid)>24){ + HttpError("input UserID 4-24 bytes."); + } + if(length($passwd)<4 || length($passwd)>24){ + HttpError("input Password 4-24 bytes."); + } + if($userid=~/[^0-9A-Za-z\@\_\-\']/){ + HttpError("Illeagal charactor found in UserID."); + } + if($passwd=~/[\x00-\x1f\x80-\xff\']/){ + HttpError("Illeagal charactor found in Password."); + } + if($gender!~/[mf]/){ + HttpError("Gender error."); + } + open PIPE,"$perl $ladmin --add $userid $gender $passwd |" + or HttpError("Can't execute ladmin."); + my(@msg)=<PIPE>; + close PIPE; + HttpMsg(@msg); +} +# ----- 存在チェック ----- +elsif( $cgi->param("check") ){ + my($userid)= $cgi->param("userid"); + if(length($userid)<4 || length($userid)>24){ + HttpError("input UserID 4-24 bytes."); + } + if($userid=~/[^0-9A-Za-z\@\_\-\']/){ + HttpError("Illeagal charactor found in UserID."); + } + open PIPE,"$perl $ladmin --search --regex \\b$userid\\b |" + or HttpError("Can't execute ladmin."); + my(@msg)=<PIPE>; + close PIPE; + if(scalar(@msg)==6 && (split /[\s\0]+/,substr($msg[4],11,24))[0] eq $userid){ + HttpMsg("NG : UserID \"$userid\" is already used."); + }elsif(scalar(@msg)==5){ + HttpMsg("OK : You can use UserID \"$userid\""); + } + HttpError("ladmin error ?\n---output---\n",@msg); +} + +# ----- フォーム ----- +else{ + print LangConv( <<"EOM" ); +Content-type: text/html\n +<html> + <head> + <title>Athena account making cgi</title> + </head> + <body> + <h1>Athena account making cgi</h1> + <form action="$cgiuri" method="post"> + <table border=2> + <tr> + <th>UserID</th> + <td><input name="userid" size=24 maxlength=24></td> + </tr> + <tr> + <th>Password</th> + <td><input name="passwd" size=24 maxlength=24 type="password"></td> + </tr> + <tr> + <th>Gender</th> + <td> + <input type="radio" name="gender" value="male">Male + <input type="radio" name="gender" value="female">Female + </td> + </tr> + <tr> + <td colspan=2> + <input type="submit" name="addaccount" value="Make Account"> + <input type="submit" name="check" value="Check UserID"> + </td> + </tr> + </table> + </form> + </body> +</html> +EOM + exit; +} + +sub LangConv { + my(@lst)= @_; + my($a,$b,@out)=(); + foreach $a(@lst){ + foreach $b(keys %langconv){ + $a=~s/$b/$langconv{$b}/g; + my($rep1)=$1; + $a=~s/\$1/$rep1/g; + } + push @out,$a; + } + return @out; +} + +sub HttpMsg { + my($msg)=join("", LangConv(@_)); + $msg=~s/\n/<br>\n/g; + print LangConv("Content-type: text/html\n\n"),$msg; + exit; +} + +sub HttpError { + my($msg)=join("", LangConv(@_)); + $msg=~s/\n/<br>\n/g; + print LangConv("Content-type: text/html\n\n"),$msg; + exit; +} + diff --git a/src/tool/checkversion b/src/tool/checkversion new file mode 100644 index 0000000..1351652 --- /dev/null +++ b/src/tool/checkversion @@ -0,0 +1,85 @@ +#!/usr/bin/perl -w
+
+##########################################################################
+# INFORMATION TOOL ABOUT THE SERVERS VERSION OF ATHENA
+#
+# By connection on a server, this software display the version of the
+# designed server.
+#-------------------------------------------------------------------------
+# Usages:
+# ./checkversion IP:port
+# ./checkversion IP port
+# perl checkversion IP:port
+# perl checkversion IP port
+#
+# note: default port: 6900
+#
+# When successfull, the software return the value 0.
+#
+##########################################################################
+
+#------------------------- start of configuration ------------------------
+
+$connecttimeout = 10; # Connection Timeout (in seconds)
+
+#-------------------------- End of configuration -------------------------
+
+use IO::Socket;
+
+unless($ARGV[0]) {
+ print "USAGE: $0 server_ip:port\n";
+ exit(1);
+}
+
+$server = $ARGV[0];
+$port = $ARGV[1];
+$port = $1 if ($server =~ s/:(\d+)//);
+$port ||= 6900;
+
+# Connection to the server
+my($so,$er) = ();
+eval{
+ $so = IO::Socket::INET->new(
+ PeerAddr=> $server,
+ PeerPort=> $port,
+ Proto => "tcp",
+ Timeout => $connecttimeout) or $er = 1;
+};
+
+if($er || $@) {
+ print "Can't not connect to server [$server:$port] !\n";
+ exit(2);
+}
+
+# Request for the server version
+print $so pack("v",30000); # 0x7530
+$so->flush();
+
+# Receiving of the answer of the server
+if (read($so,$buf,10) < 10) {
+ print "Invalid answer. It isn't an athena server or it is a too old version.\n";
+ exit(5);
+}
+
+# Sending end of connection to the server
+print $so pack("v",30002); # 0x7532
+$so->flush();
+
+# Analyse of the answer
+my($ret,$maver,$miver,$rev,$dev,$mod,$type,$mdver) = unpack("v c6 v",$buf);
+
+if ($ret != 30001) { # 0x7531
+ print "Invalid answer. It isn't an athena server or it is a too old version.\n";
+ exit(6);
+}
+
+my(@stype) = ();
+foreach $i(0..3) {
+ push @stype,(("login","char","inter","map")[$i]) if( $type & (1<<$i) );
+}
+print " ".join("/",@stype)." server [$server:$port].\n";
+printf " Athena version %s-%d.%d", ("stable","dev")[$dev], $maver,$miver;
+printf " revision %d",$rev if $rev;
+printf "%s%d\n",("","-mod")[$mod],$mdver;
+
+exit(0);
diff --git a/src/tool/convert.c b/src/tool/convert.c new file mode 100644 index 0000000..16631c9 --- /dev/null +++ b/src/tool/convert.c @@ -0,0 +1,296 @@ +#include <stdio.h> +#include <stdlib.h> + +#define RETCODE "\r\n" + +#define MAX_INVENTORY 100 +#define MAX_CART 100 +#define MAX_SKILL 350 +#define GLOBAL_REG_NUM 16 + +struct item { + int id; + short nameid; + short amount; + short equip; + char identify; + char refine; + char attribute; + short card[4]; +}; +struct point{ + char map[16]; + short x,y; +}; +struct skill { + unsigned short id,lv,flag; +}; +struct global_reg { + char str[16]; + int value; +}; + +struct mmo_charstatus { + int char_id; + int account_id; + int base_exp,job_exp,zeny; + + short class; + short status_point,skill_point; + short hp,max_hp,sp,max_sp; + short option,karma,manner; + short hair,hair_color,clothes_color; + int party_id,guild_id,pet_id; + + short weapon,shield; + short head_top,head_mid,head_bottom; + + char name[24]; + unsigned char base_level,job_level; + unsigned char str,agi,vit,int_,dex,luk,char_num,sex; + + struct point last_point,save_point,memo_point[3]; + struct item inventory[MAX_INVENTORY],cart[MAX_CART]; + struct skill skill[MAX_SKILL]; + int global_reg_num; + struct global_reg global_reg[GLOBAL_REG_NUM]; +}; + +int mmo_char_tostr(char *str,struct mmo_charstatus *p) +{ + int i; + sprintf(str,"%d\t%d,%d\t%s\t%d,%d,%d\t%d,%d,%d\t%d,%d,%d,%d\t%d,%d,%d,%d,%d,%d\t%d,%d" + "\t%d,%d,%d\t%d,%d,%d\t%d,%d,%d\t%d,%d,%d,%d,%d" + "\t%s,%d,%d\t%s,%d,%d", + p->char_id,p->account_id,p->char_num,p->name, // + p->class,p->base_level,p->job_level, + p->base_exp,p->job_exp,p->zeny, + p->hp,p->max_hp,p->sp,p->max_sp, + p->str,p->agi,p->vit,p->int_,p->dex,p->luk, + p->status_point,p->skill_point, + p->option,p->karma,p->manner, // + p->party_id,p->guild_id,p->pet_id, + p->hair,p->hair_color,p->clothes_color, + p->weapon,p->shield,p->head_top,p->head_mid,p->head_bottom, + p->last_point.map,p->last_point.x,p->last_point.y, // + p->save_point.map,p->save_point.x,p->save_point.y + ); + strcat(str,"\t"); + for(i=0;i<3;i++) + if(p->memo_point[i].map[0]){ + sprintf(str+strlen(str),"%s,%d,%d",p->memo_point[i].map,p->memo_point[i].x,p->memo_point[i].y); + } + strcat(str,"\t"); + for(i=0;i<MAX_INVENTORY;i++) + if(p->inventory[i].nameid){ + sprintf(str+strlen(str),"%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d ", + p->inventory[i].id,p->inventory[i].nameid,p->inventory[i].amount,p->inventory[i].equip, + p->inventory[i].identify,p->inventory[i].refine,p->inventory[i].attribute, + p->inventory[i].card[0],p->inventory[i].card[1],p->inventory[i].card[2],p->inventory[i].card[3]); + } + strcat(str,"\t"); + for(i=0;i<MAX_CART;i++) + if(p->cart[i].nameid){ + sprintf(str+strlen(str),"%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d ", + p->cart[i].id,p->cart[i].nameid,p->cart[i].amount,p->cart[i].equip, + p->cart[i].identify,p->cart[i].refine,p->cart[i].attribute, + p->cart[i].card[0],p->cart[i].card[1],p->cart[i].card[2],p->cart[i].card[3]); + } + strcat(str,"\t"); + for(i=0;i<MAX_SKILL;i++) + if(p->skill[i].id){ + sprintf(str+strlen(str),"%d,%d ",p->skill[i].id,p->skill[i].lv); + } + strcat(str,"\t"); + for(i=0;i<p->global_reg_num;i++) + sprintf(str+strlen(str),"%s,%d ",p->global_reg[i].str,p->global_reg[i].value); + strcat(str,"\t"); + return 0; +} + +int mmo_char_fromstr(char *str,struct mmo_charstatus *p) +{ + int tmp_int[256]; + int set,next,len,i; + + set=sscanf(str,"%d\t%d,%d\t%[^\t]\t%d,%d,%d\t%d,%d,%d\t%d,%d,%d,%d\t%d,%d,%d,%d,%d,%d\t%d,%d" + "\t%d,%d,%d\t%d,%d\t%d,%d,%d\t%d,%d,%d,%d,%d" + "\t%[^,],%d,%d\t%[^,],%d,%d%n", + &tmp_int[0],&tmp_int[1],&tmp_int[2],p->name, // + &tmp_int[3],&tmp_int[4],&tmp_int[5], + &tmp_int[6],&tmp_int[7],&tmp_int[8], + &tmp_int[9],&tmp_int[10],&tmp_int[11],&tmp_int[12], + &tmp_int[13],&tmp_int[14],&tmp_int[15],&tmp_int[16],&tmp_int[17],&tmp_int[18], + &tmp_int[19],&tmp_int[20], + &tmp_int[21],&tmp_int[22],&tmp_int[23], // + &tmp_int[24],&tmp_int[25], + &tmp_int[26],&tmp_int[27],&tmp_int[28], + &tmp_int[29],&tmp_int[30],&tmp_int[31],&tmp_int[32],&tmp_int[33], + p->last_point.map,&tmp_int[34],&tmp_int[35], // + p->save_point.map,&tmp_int[36],&tmp_int[37],&next + ); + p->char_id=tmp_int[0]; + p->account_id=tmp_int[1]; + p->char_num=tmp_int[2]; + p->class=tmp_int[3]; + p->base_level=tmp_int[4]; + p->job_level=tmp_int[5]; + p->base_exp=tmp_int[6]; + p->job_exp=tmp_int[7]; + p->zeny=tmp_int[8]; + p->hp=tmp_int[9]; + p->max_hp=tmp_int[10]; + p->sp=tmp_int[11]; + p->max_sp=tmp_int[12]; + p->str=tmp_int[13]; + p->agi=tmp_int[14]; + p->vit=tmp_int[15]; + p->int_=tmp_int[16]; + p->dex=tmp_int[17]; + p->luk=tmp_int[18]; + p->status_point=tmp_int[19]; + p->skill_point=tmp_int[20]; + p->option=tmp_int[21]; + p->karma=tmp_int[22]; + p->manner=tmp_int[23]; + p->party_id=tmp_int[24]; + p->guild_id=tmp_int[25]; + p->pet_id=0; + p->hair=tmp_int[26]; + p->hair_color=tmp_int[27]; + p->clothes_color=tmp_int[28]; + p->weapon=tmp_int[29]; + p->shield=tmp_int[30]; + p->head_top=tmp_int[31]; + p->head_mid=tmp_int[32]; + p->head_bottom=tmp_int[33]; + p->last_point.x=tmp_int[34]; + p->last_point.y=tmp_int[35]; + p->save_point.x=tmp_int[36]; + p->save_point.y=tmp_int[37]; + if(set!=41) + return 0; + if(str[next]=='\n' || str[next]=='\r') + return 1; // 新規データ + next++; + for(i=0;str[next] && str[next]!='\t';i++){ + set=sscanf(str+next,"%[^,],%d,%d%n",p->memo_point[i].map,&tmp_int[0],&tmp_int[1],&len); + if(set!=3) + return 0; + p->memo_point[i].x=tmp_int[0]; + p->memo_point[i].y=tmp_int[1]; + next+=len; + if(str[next]==' ') + next++; + } + next++; + for(i=0;str[next] && str[next]!='\t';i++){ + set=sscanf(str+next,"%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d%n", + &tmp_int[0],&tmp_int[1],&tmp_int[2],&tmp_int[3], + &tmp_int[4],&tmp_int[5],&tmp_int[6], + &tmp_int[7],&tmp_int[8],&tmp_int[9],&tmp_int[10],&len); + if(set!=11) + return 0; + p->inventory[i].id=tmp_int[0]; + p->inventory[i].nameid=tmp_int[1]; + p->inventory[i].amount=tmp_int[2]; + p->inventory[i].equip=tmp_int[3]; + p->inventory[i].identify=tmp_int[4]; + p->inventory[i].refine=tmp_int[5]; + p->inventory[i].attribute=tmp_int[6]; + p->inventory[i].card[0]=tmp_int[7]; + p->inventory[i].card[1]=tmp_int[8]; + p->inventory[i].card[2]=tmp_int[9]; + p->inventory[i].card[3]=tmp_int[10]; + next+=len; + if(str[next]==' ') + next++; + } + next++; + for(i=0;str[next] && str[next]!='\t';i++){ + set=sscanf(str+next,"%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d%n", + &tmp_int[0],&tmp_int[1],&tmp_int[2],&tmp_int[3], + &tmp_int[4],&tmp_int[5],&tmp_int[6], + &tmp_int[7],&tmp_int[8],&tmp_int[9],&tmp_int[10],&len); + if(set!=11) + return 0; + p->cart[i].id=tmp_int[0]; + p->cart[i].nameid=tmp_int[1]; + p->cart[i].amount=tmp_int[2]; + p->cart[i].equip=tmp_int[3]; + p->cart[i].identify=tmp_int[4]; + p->cart[i].refine=tmp_int[5]; + p->cart[i].attribute=tmp_int[6]; + p->cart[i].card[0]=tmp_int[7]; + p->cart[i].card[1]=tmp_int[8]; + p->cart[i].card[2]=tmp_int[9]; + p->cart[i].card[3]=tmp_int[10]; + next+=len; + if(str[next]==' ') + next++; + } + next++; + for(i=0;str[next] && str[next]!='\t';i++){ + set=sscanf(str+next,"%d,%d%n", + &tmp_int[0],&tmp_int[1],&len); + if(set!=2) + return 0; + p->skill[tmp_int[0]].id=tmp_int[0]; + p->skill[tmp_int[0]].lv=tmp_int[1]; + next+=len; + if(str[next]==' ') + next++; + } + next++; + for(i=0;str[next] && str[next]!='\t' && str[next]!='\n' && str[next]!='\r';i++){ //global_reg実装以前のathena.txt互換のため一応'\n'チェック + set=sscanf(str+next,"%[^,],%d%n", + p->global_reg[i].str,&p->global_reg[i].value,&len); + if(set!=2) + return 0; + next+=len; + if(str[next]==' ') + next++; + } + p->global_reg_num=i; + return 1; +} + +int mmo_char_convert(char *fname1,char *fname2) +{ + char line[65536]; + int ret; + struct mmo_charstatus char_dat; + FILE *ifp,*ofp; + + ifp=fopen(fname1,"r"); + ofp=fopen(fname2,"w"); + if(ifp==NULL) { + printf("file not found %s\n",fname1); + return 0; + } + if(ofp==NULL) { + printf("file open error %s\n",fname2); + return 0; + } + while(fgets(line,65535,ifp)){ + memset(&char_dat,0,sizeof(struct mmo_charstatus)); + ret=mmo_char_fromstr(line,&char_dat); + if(ret){ + mmo_char_tostr(line,&char_dat); + fprintf(ofp,"%s" RETCODE,line); + } + } + fcloseall(); + return 0; +} + +int main(int argc,char *argv[]) +{ + if(argc < 3) { + printf("Usage: convert <input filename> <output filename>\n"); + exit(0); + } + mmo_char_convert(argv[1],argv[2]); + + return 0; +} diff --git a/src/tool/getlogincount b/src/tool/getlogincount new file mode 100644 index 0000000..6a20992 --- /dev/null +++ b/src/tool/getlogincount @@ -0,0 +1,122 @@ +#!/usr/bin/perl -w
+
+##########################################################################
+# INFORMATION TOOL ABOUT THE # OF ONLINE PLAYERS ON ATHENA SERVERS
+#
+# By connection on the athena login-server, this software displays the
+# number of online players.
+#
+#-------------------------------------------------------------------------
+# Software usage:
+# Configure the IP, the port and a valid account of the server.
+# After, use at your choice:
+# ./getlogincount - display the number of online players on all servers.
+# ./getlogincount --premier or
+# ./getlogincount --first -- display the number of online players of the
+# first server in the received list.
+# ./getlogincount [servername] -- display the number of online players
+# of the specified server.
+#
+# If successfull, the software return the value 0.
+#
+##########################################################################
+
+#------------------------------ CONFIGURATION ----------------------------
+
+$loginserverip = "127.0.0.1"; # IP of the login-server
+$loginserverport = 6900; # port of the login-server
+$loginaccount = "s1"; # a valid account name
+$loginpasswd = "p1"; # the password of the valid account name
+
+$connecttimeout = 10; # Connection timeout (in seconds)
+
+#-------------------------------------------------------------------------
+
+use IO::Socket;
+
+my($sname) = $ARGV[0];
+if (!defined($sname)) {
+ $sname = "";
+}
+
+# Connection to the login-server
+my($so,$er) = ();
+eval{
+ $so = IO::Socket::INET->new(
+ PeerAddr=> $loginserverip,
+ PeerPort=> $loginserverport,
+ Proto => "tcp",
+ Timeout => $connecttimeout) or $er=1;
+};
+if($er || $@){
+ print "Can't not connect to the login-server [${loginserverip}:$loginserverport] !\n";
+ exit(2);
+}
+
+# Request to connect on login-server
+print $so pack("v V a24 a24 C",0x0064,9,$loginaccount,$loginpasswd,3);
+$so->flush();
+
+# Fail to connect
+if(unpack("v", &soread(\$so,2)) != 0x0069) {
+ print "Login error.\n";
+ exit(3);
+}
+
+# Get length of the received packet
+my($plen) = unpack("v",&soread(\$so,2))-4;
+
+# Suppress information of the account (we need only information about the servers)
+&soread(\$so,43);
+$plen -= 43;
+
+# Check about the number of online servers
+if ($plen < 32) {
+ printf "No server is connected to login-server.\n";
+ exit(1);
+}
+
+# Read information of the servers
+my(@slist) = ();
+for(;$plen > 0;$plen -= 32) {
+ my($name,$count) = unpack("x6 a20 V",&soread(\$so,32));
+ $name = substr($name,0,index($name,"\0"));
+ push @slist, [ $name, $count ];
+}
+
+# Display the result
+if($sname eq "--first" || $sname eq "--premier") { # If we ask only for the first server
+ printf "%-20s : %5d\n",$slist[0][0],$slist[0][1];
+} elsif ($sname eq "") { # If we ask for all servers
+ foreach $i(@slist) {
+ printf "%-20s : %5d\n",$i->[0],$i->[1];
+ }
+} else { # If we ask for a specified server (by its name)
+ my($flag) = 1;
+ foreach $i(@slist) {
+ if($i->[0] eq $sname) {
+ printf "%-20s : %5d\n",$i->[0],$i->[1];
+ $flag = 0;
+ }
+ }
+ if($flag) { # If the server doesn't exist
+ printf "The server [$sname] doesn't exist.\n";
+ exit(1);
+ }
+}
+
+# End of the software
+$so->shutdown(2);
+$so->close();
+exit(0);
+
+# Sub-function: get data from the socket
+sub soread {
+ my($so,$len) = @_;
+ my($sobuf);
+ if(read($$so,$sobuf,$len) < $len) {
+ print "Socket read error.\n";
+ exit(5);
+ }
+ return $sobuf;
+};
diff --git a/src/tool/ladmin b/src/tool/ladmin new file mode 100644 index 0000000..e3319d5 --- /dev/null +++ b/src/tool/ladmin @@ -0,0 +1,3793 @@ +#!/usr/bin/perl
+use POSIX;
+##########################################################################
+# EAthena login-server remote administration tool
+# New ladamin by [Yor]
+##########################################################################
+#-------------------------------INSTRUCTIONS------------------------------
+# Set the 4 variables below:
+# IP of the login server.
+# Port where the login-server listens incoming packets.
+# Password of administration (same of config_athena.conf).
+# Displayed language of the sofware (if not correct, english is used).
+# IMPORTANT:
+# Be sure that you authorize remote administration in login-server
+# (see login_athena.conf, 'admin_state' parameter)
+#-------------------------------------------------------------------------
+my($loginserverip) = "127.0.0.1"; # IP of login-server
+my($loginserverport) = 6900; # Port of login-server
+my($loginserveradminpassword) = "admin"; # Administration password
+my($connecttimeout) = 10; # Timeout of connection (in seconds)
+my($passenc) = 2; # Encoding type of the password
+my($defaultlanguage) = "E"; # Default language (F: Fran軋is/E: English)
+ # (if it's not 'F', default is English)
+
+#-------------------------------------------------------------------------
+# LIST of COMMANDs that you can type at the prompt:
+# To use these commands you can only type only the first letters.
+# You must type a minimum of letters (you can not type 'a',
+# because ladmin doesn't know if it's for 'aide' or for 'add')
+# <Example> q <= quit, li <= list, pass <= passwd, etc.
+#
+# Note: every time you must give a account_name, you can use "" or '' (spaces can be included)
+#
+# aide/help/?
+# Display the description of the commands
+# aide/help/? [command]
+# Display the description of the specified command
+#
+# add <account_name> <sex> <password>
+# Create an account with the default email (a@a.com).
+# Concerning the sex, only the first letter is used (F or M).
+# The e-mail is set to a@a.com (default e-mail). It's like to have no e-mail.
+# When the password is omitted, the input is done without displaying of the pressed keys.
+# <example> add testname Male testpass
+#
+# ban/banish yyyy/mm/dd hh:mm:ss <account name>
+# Changes the final date of a banishment of an account.
+# Same command of banset, except that account_name is at end
+#
+# banadd <account_name> <modifier>
+# Adds or substracts time from the final date of a banishment of an account.
+# Modifier is done as follows:
+# Adjustment value (-1, 1, +1, etc...)
+# Modified element:
+# a or y: year
+# m: month
+# j or d: day
+# h: hour
+# mn: minute
+# s: second
+# <example> banadd testname +1m-2mn1s-6y
+# this example adds 1 month and 1 second, and substracts 2 minutes and 6 years at the same time.
+# NOTE: If you modify the final date of a non-banished account,
+# you fix the final date to (actual time +- adjustments)
+#
+# banset <account_name> yyyy/mm/dd [hh:mm:ss]
+# Changes the final date of a banishment of an account.
+# Default time: 23:59:59
+# banset <account_name> 0
+# Set a non-banished account (0 = unbanished).
+#
+# block <account name>
+# Set state 5 (You have been blocked by the GM Team) to an account.
+# Same command of state <account_name> 5.
+#
+# check <account_name> <password>
+# Check the validity of a password for an account
+# NOTE: Server will never sends back a password.
+# It's the only method you have to know if a password is correct.
+# The other method is to have a ('physical') access to the accounts file.
+#
+# create <account_name> <sex> <email> <password>
+# Like the 'add' command, but with e-mail moreover.
+# <example> create testname Male my@mail.com testpass
+#
+# del <account name>
+# Remove an account.
+# This order requires confirmation. After confirmation, the account is deleted.
+#
+# email <account_name> <email>
+# Modify the e-mail of an account.
+#
+# getcount
+# Give the number of players online on all char-servers.
+#
+# gm <account_name> [GM_level]
+# Modify the GM level of an account.
+# Default value remove GM level (GM level = 0).
+# <example> gm testname 80
+#
+# id <account name>
+# Give the id of an account.
+#
+# info <account_id>
+# Display complete information of an account.
+#
+# kami <message>
+# Sends a broadcast message on all map-server (in yellow).
+# kamib <message>
+# Sends a broadcast message on all map-server (in blue).
+#
+# language <language>
+# Change the language of displaying.
+#
+# list/ls [start_id [end_id]]
+# Display a list of accounts.
+# 'start_id', 'end_id': indicate end and start identifiers.
+# Research by name is not possible with this command.
+# <example> list 10 9999999
+#
+# listBan/lsBan [start_id [end_id]]
+# Like list/ls, but only for accounts with state or banished
+#
+# listGM/lsGM [start_id [end_id]]
+# Like list/ls, but only for GM accounts
+#
+# listOK/lsOK [start_id [end_id]]
+# Like list/ls, but only for accounts without state and not banished
+#
+# memo <account_name> <memo>
+# Modify the memo of an account.
+# 'memo': it can have until 253 characters (with spaces or not).
+#
+# name <account_id>
+# Give the name of an account.
+#
+# passwd <account_name> <new_password>
+# Change the password of an account.
+# When new password is omitted, the input is done without displaying of the pressed keys.
+#
+# quit/end/exit
+# End of the program of administration
+#
+# reloadGM
+# Reload GM configuration file
+#
+# search <expression>
+# Seek accounts.
+# Displays the accounts whose names correspond.
+# search -r/-e/--expr/--regex <expression>
+# Seek accounts by regular expression.
+# Displays the accounts whose names correspond.
+#
+# sex <account_name> <sex>
+# Modify the sex of an account.
+# <example> sex testname Male
+#
+# state <account_name> <new_state> <error_message_#7>
+# Change the state of an account.
+# 'new_state': state is the state of the packet 0x006a + 1. The possibilities are:
+# 0 = Account ok 6 = Your Game's EXE file is not the latest version
+# 1 = Unregistered ID 7 = You are Prohibited to log in until %s
+# 2 = Incorrect Password 8 = Server is jammed due to over populated
+# 3 = This ID is expired 9 = No MSG
+# 4 = Rejected from Server 100 = This ID has been totally erased
+# 5 = You have been blocked by the GM Team
+# all other values are 'No MSG', then use state 9 please.
+# 'error_message_#7': message of the code error 6 = Your are Prohibited to log in until %s (packet 0x006a)
+#
+# timeadd <account_name> <modifier>
+# Adds or substracts time from the validity limit of an account.
+# Modifier is done as follows:
+# Adjustment value (-1, 1, +1, etc...)
+# Modified element:
+# a or y: year
+# m: month
+# j or d: day
+# h: hour
+# mn: minute
+# s: second
+# <example> timeadd testname +1m-2mn1s-6y
+# this example adds 1 month and 1 second, and substracts 2 minutes and 6 years at the same time.
+# NOTE: You can not modify a unlimited validity limit.
+# If you want modify it, you want probably create a limited validity limit.
+# So, at first, you must set the validity limit to a date/time.
+#
+# timeset <account_name> yyyy/mm/dd [hh:mm:ss]
+# Changes the validity limit of an account.
+# Default time: 23:59:59
+# timeset <account_name> 0
+# Gives an unlimited validity limit (0 = unlimited).
+#
+# unban/unbanish <account name>
+# Unban an account.
+# Same command of banset 0.
+#
+# unblock <account name>
+# Set state 0 (Account ok) to an account.
+# Same command of state <account_name> 0.
+#
+# version
+# Display the version of the login-server.
+#
+# who <account name>
+# Displays complete information of an account.
+#
+#-------------------------------------------------------------------------
+# Possibilities to execute ladmin in command line by usage of the software with a parameter:
+# ./ladmin --mode param1 ...
+#
+# --makesymlink -- Create the symbolic links for a use in shell
+# --add <account_name> <sex> <password> -- Create an account with the default email (or -a)
+# --ban yyyy/mm/dd hh:mm:ss <account_name> -- Change the final date of a banishment of an account (or -b)
+# --banadd <account_name> <modifier> -- Add or substract time from the final date of a banishment of an account (or - ba)
+# --banset <account_name> yyyy/mm/dd [hh:mm:ss] -- Change the final date of a banishment of an account (or -bs)
+# --banset <account_name> 0 -- Unbanish an account (or -bs)
+# --block <account_name> -- Set state 5 to an account (or -bl)
+# --check <account_name> <password> -- Check the validity of a password for an account (or -check)
+# --create <account_name> <sex> <email> <password> -- Create an account with email (or -c)
+# --del <account_name> -- Remove an account (or -d)
+# --email <account_name> <email> -- Modify an email of an account (or -e)
+# --getcount -- Give the number of players online on all char-servers (or -g)
+# --gm <account_name> <GM_level> -- Change the GM level of an account (or -gm)
+# --id <account_name> -- Give the id of an account (or -i)
+# --info <account_id> -- Display complete information of an account (or -info)
+# --kami <message> -- Sends a broadcast message on all map-server (in yellow).
+# --kamib <message> -- Sends a broadcast message on all map-server (in blue).
+# --language <language> -- Change the language of displaying (-lang).
+# --list [First_id [Last_id]] -- Display a list of accounts (or -l)
+# --listBan [start_id [end_id]] -- Display a list of accounts with state or banished (or -lBan)
+# --listGM [First_id [Last_id]] -- Display a list of GM accounts (or -lGM)
+# --listOK [start_id [end_id]] -- Display a list of accounts without state and not banished (or -lOK)
+# --memo <account_name> <memo> -- Modify the memo of an account (or -e)
+# --name <account_id> -- Give the name of an account (or -n)
+# --passwd <account_name> <new_password> -- Change the password of an account (or -p)
+# --reloadGM -- Reload GM configuration file (or -r)
+# --search <expression> -- Seek accounts (or -s)
+# --search -e/-r/--expr/--regex <expression> -- Seek accounts by REGEX (or -s)
+# --sex <account_name> <sex> -- Change the sex of an account (or -sex)
+# --state <account_name> <new_state> <error_message_#7> -- Change the state of an account (or -t)
+# --timeadd <account_name> <modifier> -- Add or substract time from the validity limit of an account (or - ta)
+# --timeset <account_name> yyyy/mm/dd [hh:mm:ss] -- Change the validify limit of an account (or -ts)
+# --timeset <account_name> 0 -- Give a unlimited validity limit (or -ts)
+# --unban/unbanish <account_name> -- Unban an account (or -uba)
+# --unblock <account_name> -- Set state 0 to an account (or -ubl)
+# --version -- Display the version of the login-server (or -v)
+# --who <account_name> -- Display complete information of an account (or -w)
+#
+# <example> ./ladmin --addaccount testname Male testpass
+#
+#-------------------------------------------------------------------------
+# Possibilities to execute ladmin with symbolic links in Shell
+# To create the symbolic links, execute ladmin with the '-- makesymlink' option.
+#
+# addaccount <account_name> <sex> <password> -- Create an account with the default email
+# banaccount yyyy/mm/dd hh:mm:ss <account_name> -- Change the final date of a banishment of an account
+# banaddaccount <account_name> <modifier> -- Add or substract time from the final date of a banishment of an account
+# bansetaccount <account_name> yyyy/mm/dd [hh:mm:ss] -- Change the final date of a banishment of an account
+# bansetaccount <account_name> 0 -- Unbanish an account
+# blockaccount <account_name> -- Set state 5 (blocked by the GM Team) to an account
+# checkaccount <account_name> <password> -- Check the validity of a password for an account
+# createaccount <account_name> <sex> <email> <password> -- Create an account with email
+# delaccount <account_name> -- Remove an account
+# emailaccount <account_name> <email> -- Modify an email of an account
+# getcount -- Give the number of players online on all char-servers
+# gmaccount <account_name> <GM_level> -- Change the GM level of an account
+# idaccount <account_name> -- Give the id of an account
+# infoaccount <account_id> -- Display complete information of an account
+# kami <message> -- Sends a broadcast message on all map-server (in yellow).
+# kamib <message> -- Sends a broadcast message on all map-server (in blue).
+# ladminlanguage <language> -- Change the language of displaying.
+# listaccount [First_id [Last_id]] -- Display a list of accounts
+# listBanaccount [start_id [end_id]] -- Display a list of accounts with state or banished
+# listGMaccount [First_id [Last_id]] -- Display a list of GM accounts
+# listOKaccount [start_id [end_id]] -- Display a list of accounts without state and not banished
+# loginserverversion -- Display the version of the login-server
+# memoaccount <account_name> <memo> -- Modify the memo of an account
+# nameaccount <account_id> -- Give the name of an account
+# passwdaccount <account_name> <new_password> -- Change the password of an account
+# reloadGM -- Reload GM configuration file
+# searchaccount <expression> -- Seek accounts
+# searchaccount -e/-r/--expr/--regex <expression> -- Seek accounts by REGEX
+# sexaccount <account_name> <sex> -- Change the sex of an account (or -sex)
+# stateaccount <account_name> <new_state> <error_message_#7> -- Change the state of an account
+# timeaddaccount <account_name> <modifier> -- Add or substract time from the validity limit of an account
+# timesetaccount <account_name> yyyy/mm/dd [hh:mm:ss] -- Change the validify limit of an account
+# timesetaccount <account_name> 0 -- Give a unlimited validity limit
+# unbanaccount <account_name> -- Unban an account
+# unblockaccount <account_name> -- Set state 0 (Account ok) to an account
+# whoaccount <account_name> -- Display complete information of an account
+# <exemple> ./addaccount testname Male testpass
+#
+#-------------------------------------------------------------------------
+# About the encoding:
+#
+# The Digest::MD5 module is necessary to use the encrypted password system.
+# When the software cannot found the Digest::MD5 module,
+# encoding is automatically disabled ($passenc=0), which allows
+# to use this program in any cases.
+#
+#-------------------------------------------------------------------------
+# How to use ladmin with UNIX:
+#
+# You excecute ladmin as a standard command.
+# <Example of preparation to have an access to ladmin>
+# $ mv ladmin ladmin_org
+# $ nkf -eLu ladmin_org > ladmin
+# $ chmod 700 ladmin
+# <Example to start directly ladmin>
+# $ perl ladmin
+#
+##########################################################################
+
+
+use strict;
+use IO::Socket;
+use Term::ReadLine;
+eval { use POSIX qw(:termios_h); };
+eval { use Digest::MD5 qw(md5); } if $passenc;
+$passenc = 0 if($@);
+
+my($ver) = "1.00";
+
+# Start of termios
+my($termios, $orgterml, $termlecho, $termlnoecho) = ();
+eval{
+ $termios = POSIX::Termios->new();
+ $termios->getattr(fileno(STDIN));
+ $orgterml = $termios->getlflag();
+ $termlecho = ECHO | ECHOK | ICANON;
+ $termlnoecho = $orgterml & ~$termlecho;
+};
+
+# Modification of termios for the displaying of passwords (no displays for pressed keys)
+sub cbreak() {
+ if ($termios) {
+ $termios->setlflag($termlnoecho);
+ $termios->setcc(VTIME, 1);
+ $termios->setattr(fileno(STDIN), TCSANOW);
+ }
+}
+# Modification of termios to return at the normal displaying (after input of the passwords)
+sub cooked() {
+ if ($termios) {
+ $termios->setlflag($orgterml);
+ $termios->setcc(VTIME,0);
+ $termios->setattr(fileno(STDIN),TCSANOW);
+ }
+}
+END{ cooked() }
+
+if ($defaultlanguage eq "F") {
+ print "Outil d'administration distance de eAthena V.$ver\n";
+} else {
+ print "EAthena login-server administration tool V.$ver\n";
+}
+
+# Creation of the symbolic links for call of the program in line command of the shell
+if ($ARGV[0] eq "--makesymlink") {
+ symlink $0, "loginserverversion";
+ symlink $0, "addaccount";
+ symlink $0, "banaccount";
+ symlink $0, "banaddaccount";
+ symlink $0, "bansetaccount";
+ symlink $0, "blockaccount";
+ symlink $0, "checkaccount";
+ symlink $0, "createaccount";
+ symlink $0, "delaccount";
+ symlink $0, "emailaccount";
+ symlink $0, "getcount";
+ symlink $0, "gmaccount";
+ symlink $0, "idaccount";
+ symlink $0, "infoaccount";
+ symlink $0, "kami";
+ symlink $0, "kamib";
+ symlink $0, "ladminlanguage";
+ symlink $0, "listaccount";
+ symlink $0, "listBanaccount";
+ symlink $0, "listGMaccount";
+ symlink $0, "listOKaccount";
+ symlink $0, "memoaccount";
+ symlink $0, "nameaccount";
+ symlink $0, "passwdaccount";
+ symlink $0, "reloadGM";
+ symlink $0, "searchaccount";
+ symlink $0, "sexaccount";
+ symlink $0, "stateaccount";
+ symlink $0, "timeaddaccount";
+ symlink $0, "timesetaccount";
+ symlink $0, "unbanaccount";
+ symlink $0, "unblockaccount";
+ symlink $0, "whoaccount";
+ if ($defaultlanguage eq "F") {
+ print "Liens symbliques cr鳬s.\n";
+ } else {
+ print "Symbolic links created.\n";
+ }
+ exit(0);
+}
+
+# Connection to the login-server
+my($so,$er) = ();
+eval{
+ $so = IO::Socket::INET->new(
+ PeerAddr=> $loginserverip,
+ PeerPort=> $loginserverport,
+# Proto => "tcp",
+ Timeout => $connecttimeout) or $er = 1;
+};
+if ($er || $@) {
+ if ($defaultlanguage eq "F") {
+ print "\nImpossible de se connecter au serveur de login [${loginserverip}:$loginserverport] !\n";
+ } else {
+ print "\nImpossible to have a connection with the login-server [${loginserverip}:$loginserverport] !\n";
+ }
+ print "$!\n"; # Displaying of the error
+ exit(2);
+}
+
+# Sending the administration password
+if ($passenc == 0) {
+ print $so pack("v2a24",0x7918,0,$loginserveradminpassword);
+ $so->flush();
+} else {
+ print $so pack("v",0x791a);
+ $so->flush();
+ my($buf) = readso(4);
+ if (unpack("v",$buf) != 0x01dc) {
+ if ($defaultlanguage eq "F") {
+ print "Erreur au login (馗hec de la cr饌tion de la clef md5).\n";
+ } else {
+ print "Error at login (failure of the md5 key creation).\n";
+ }
+ }
+ $buf = readso(unpack("x2v",$buf)-4);
+ my($md5bin) = md5(($passenc == 1) ? $buf.$loginserveradminpassword : $loginserveradminpassword.$buf);
+ print $so pack("v2a16",0x7918,$passenc,$md5bin);
+ $so->flush();
+}
+
+# Waiting of the server reply
+my($buf) = readso(3);
+
+if (unpack("v",$buf) != 0x7919 || unpack("x2c",$buf) != 0) {
+ if ($defaultlanguage eq "F") {
+ print "Erreur de login:\n";
+ print " - mot de passe incorrect,\n";
+ print " - syst鑪e d'administration non activ, ou\n";
+ print " - IP non autoris馥.\n";
+ } else {
+ print "Error at login:\n";
+ print " - incorrect password,\n";
+ print " - administration system not activated, or\n";
+ print " - unauthorised IP.\n";
+ }
+ quit();
+ exit(4);
+}
+
+if ($defaultlanguage eq "F") {
+ print "Connexion 騁ablie.\n";
+} else {
+ print "Established connection.\n";
+}
+
+#-------------------------------------------------------------------------
+# Here are checked the command lines with arguments and symbolic links (no prompt)
+
+if ($0 =~ /addaccount$/ ||
+ (($ARGV[0] eq "-a" || $ARGV[0] eq "--add") && ((shift @ARGV), 1))) {
+ my($r) = addaccount($ARGV[0], $ARGV[1], $ARGV[2]);
+ quit();
+ exit($r);
+} elsif ($0 =~ /banaccount$/ || $0 =~ /banishaccount$/ ||
+ (($ARGV[0] eq "-b" || $ARGV[0] eq "--ban" || $ARGV[0] eq "--banish") && ((shift @ARGV), 1))) {
+ my($r) = bansetaccount($ARGV[1], $ARGV[2], $ARGV[0]);
+ quit();
+ exit($r);
+} elsif ($0 =~ /banaddaccount$/ ||
+ (($ARGV[0] eq "-ba" || $ARGV[0] eq "--banadd") && ((shift @ARGV), 1))) {
+ my($r) = banaddaccount($ARGV[0], $ARGV[1]);
+ quit();
+ exit($r);
+} elsif ($0 =~ /bansetaccount$/ ||
+ (($ARGV[0] eq "-bs" || $ARGV[0] eq "--banset") && ((shift @ARGV), 1))) {
+ my($r) = bansetaccount($ARGV[0], $ARGV[1], $ARGV[2]);
+ quit();
+ exit($r);
+} elsif ($0 =~ /blockaccount$/ ||
+ (($ARGV[0] eq "-bl" || $ARGV[0] eq "--block") && ((shift @ARGV), 1))) {
+ my($r) = changestate($ARGV[0], 5, "");
+ quit();
+ exit($r);
+} elsif ($0 =~ /checkaccount$/ ||
+ (($ARGV[0] eq "-check" || $ARGV[0] eq "--check") && ((shift @ARGV), 1))) {
+ my($r) = checkaccount($ARGV[0], $ARGV[1]);
+ quit();
+ exit($r);
+} elsif ($0 =~ /createaccount$/ ||
+ (($ARGV[0] eq "-c" || $ARGV[0] eq "--create") && ((shift @ARGV), 1))) {
+ my($r) = createaccount($ARGV[0], $ARGV[1], $ARGV[2], $ARGV[3]);
+ quit();
+ exit($r);
+} elsif ($0 =~ /delaccount$/ ||
+ (($ARGV[0] eq "-d" || $ARGV[0] eq "--del") && ((shift @ARGV), 1))) {
+ my($r) = delaccount($ARGV[0]);
+ quit();
+ exit($r);
+} elsif ($0 =~ /emailaccount$/ ||
+ (($ARGV[0] eq "-e" || $ARGV[0] eq "--email") && ((shift @ARGV), 1))) {
+ my($r) = changeemail($ARGV[0], $ARGV[1]);
+ quit();
+ exit($r);
+} elsif ($0 =~ /getcount$/ ||
+ (($ARGV[0] eq "-g" || $ARGV[0] eq "--getcount") && ((shift @ARGV), 1))) {
+ my($r) = getlogincount();
+ quit();
+ exit($r);
+} elsif ($0 =~ /gmaccount$/ ||
+ (($ARGV[0] eq "-gm" || $ARGV[0] eq "--gm") && ((shift @ARGV), 1))) {
+ my($r) = changegmlevel($ARGV[0], $ARGV[1]);
+ quit();
+ exit($r);
+} elsif ($0 =~ /id$/ ||
+ (($ARGV[0] eq "-i" || $ARGV[0] eq "--id") && ((shift @ARGV), 1))) {
+ my($r) = idaccount($ARGV[0]);
+ quit();
+ exit($r);
+} elsif ($0 =~ /infoaccount$/ ||
+ (($ARGV[0] eq "-info" || $ARGV[0] eq "--info") && ((shift @ARGV), 1))) {
+ my($r) = infoaccount($ARGV[0]);
+ quit();
+ exit($r);
+} elsif ($0 =~ /kami$/ ||
+ (($ARGV[0] eq "-kami" || $ARGV[0] eq "--kami") && ((shift @ARGV), 1))) {
+ my($r) = sendbroadcast(0, $ARGV[0]);
+ quit();
+ exit($r);
+} elsif ($0 =~ /kamib$/ ||
+ (($ARGV[0] eq "-kamib" || $ARGV[0] eq "--kamib") && ((shift @ARGV), 1))) {
+ my($r) = sendbroadcast(0x10, $ARGV[0]);
+ quit();
+ exit($r);
+} elsif ($0 =~ /ladminlanguage$/ ||
+ (($ARGV[0] eq "-lang" || $ARGV[0] eq "--language") && ((shift @ARGV), 1))) {
+ my($r) = changelanguage($ARGV[0]);
+ quit();
+ exit($r);
+} elsif ($0 =~ /listaccount$/ ||
+ (($ARGV[0] eq "-l" || $ARGV[0] eq "--list") && ((shift @ARGV), 1))) {
+ my($r) = listaccount(int($ARGV[0]), int($ARGV[1]), 0); # 0: to list all
+ quit();
+ exit($r);
+} elsif ($0 =~ /listBanaccount$/ ||
+ (($ARGV[0] eq "-lBan" || $ARGV[0] eq "--listBan") && ((shift @ARGV), 1))) {
+ my($r) = listaccount(int($ARGV[0]), int($ARGV[1]), 3); # 3: to list only accounts with state or banished
+ quit();
+ exit($r);
+} elsif ($0 =~ /listGMaccount$/ ||
+ (($ARGV[0] eq "-lGM" || $ARGV[0] eq "--listGM") && ((shift @ARGV), 1))) {
+ my($r) = listaccount(int($ARGV[0]), int($ARGV[1]), 1); # 1: to list only GM
+ quit();
+ exit($r);
+} elsif ($0 =~ /listOKaccount$/ ||
+ (($ARGV[0] eq "-lOK" || $ARGV[0] eq "--listOK") && ((shift @ARGV), 1))) {
+ my($r) = listaccount(int($ARGV[0]), int($ARGV[1]), 4); # 4: to list only accounts without state and not banished
+ quit();
+ exit($r);
+} elsif ($0 =~ /loginserverversion$/ ||
+ (($ARGV[0] eq "-v" || $ARGV[0] eq "--version") && ((shift @ARGV), 1))) {
+ my($r) = checkloginversion();
+ quit();
+ exit($r);
+} elsif ($0 =~ /memoaccount$/ ||
+ (($ARGV[0] eq "-m" || $ARGV[0] eq "--memo") && ((shift @ARGV), 1))) {
+ my($r) = changememo($ARGV[0], $ARGV[1]);
+ quit();
+ exit($r);
+} elsif ($0 =~ /nameaccount$/ ||
+ (($ARGV[0] eq "-n" || $ARGV[0] eq "--name") && ((shift @ARGV), 1))) {
+ my($r) = nameaccount(int($ARGV[0]));
+ quit();
+ exit($r);
+} elsif ($0 =~ /passwdaccount$/ ||
+ (($ARGV[0] eq "-p" || $ARGV[0] eq "--passwd") && ((shift @ARGV), 1))) {
+ my($r) = changepasswd($ARGV[0], $ARGV[1]);
+ quit();
+ exit($r);
+} elsif ($0 =~ /reloadGM$/ ||
+ (($ARGV[0] eq "-r" || $ARGV[0] eq "--reloadGM") && ((shift @ARGV), 1))) {
+ my($r) = reloadGM();
+ quit();
+ exit($r);
+} elsif ($0 =~ /searchaccount$/ ||
+ (($ARGV[0] eq "-s" || $ARGV[0] eq "--search") && ((shift @ARGV), 1))) {
+ my($r) = searchaccount($ARGV[0], $ARGV[1]);
+ quit();
+ exit($r);
+} elsif ($0 =~ /sexaccount$/ ||
+ (($ARGV[0] eq "-sex" || $ARGV[0] eq "--sex") && ((shift @ARGV), 1))) {
+ my($r) = changesex($ARGV[0], $ARGV[1]);
+ quit();
+ exit($r);
+} elsif ($0 =~ /stateaccount$/ ||
+ (($ARGV[0] eq "-t" || $ARGV[0] eq "--state") && ((shift @ARGV), 1))) {
+ my($r) = changestate($ARGV[0], $ARGV[1], $ARGV[2]);
+ quit();
+ exit($r);
+} elsif ($0 =~ /timeaddaccount$/ ||
+ (($ARGV[0] eq "-ta" || $ARGV[0] eq "--timeadd") && ((shift @ARGV), 1))) {
+ my($r) = timeaddaccount($ARGV[0], $ARGV[1]);
+ quit();
+ exit($r);
+} elsif ($0 =~ /timesetaccount$/ ||
+ (($ARGV[0] eq "-ts" || $ARGV[0] eq "--timeset") && ((shift @ARGV), 1))) {
+ my($r) = timesetaccount($ARGV[0], $ARGV[1], $ARGV[2]);
+ quit();
+ exit($r);
+} elsif ($0 =~ /unbanaccount$/ || $0 =~ /unbanishaccount$/ ||
+ (($ARGV[0] eq "-uba" || $ARGV[0] eq "--unban" || $ARGV[0] eq "--unbanish") && ((shift @ARGV), 1))) {
+ my($r) = bansetaccount($ARGV[0], 0, "");
+ quit();
+ exit($r);
+} elsif ($0 =~ /unblockaccount$/ ||
+ (($ARGV[0] eq "-ubl" || $ARGV[0] eq "--unblock") && ((shift @ARGV), 1))) {
+ my($r) = changestate($ARGV[0], 0, "");
+ quit();
+ exit($r);
+} elsif ($0 =~ /whoaccount$/ ||
+ (($ARGV[0] eq "-w" || $ARGV[0] eq "--who") && ((shift @ARGV), 1))) {
+ my($r) = whoaccount($ARGV[0]);
+ quit();
+ exit($r);
+}
+
+#-------------------------------------------------------------------------
+if ($defaultlanguage eq "F") {
+ print "Lecture de la version du serveur de login...\n";
+} else {
+ print "Reading of the version of the login-server...\n";
+}
+checkloginversion();
+
+# Set the prompt line
+my($term) = new Term::ReadLine "ladmin";
+
+# Here begin the infinite loop to read prompts
+while(1) {
+ # Displaying of the prompt
+ print "\n";
+ if ($defaultlanguage eq "F") {
+ printf "\033[32mPour afficher les commandes, tapez 'Entr馥'.\033[0m\n";
+ } else {
+ printf "\033[32mTo list the commands, type 'enter'.\033[0m\n";
+ }
+ my($cmd) = $term->readline("ladmin> ");
+ # split and recovery of the input
+ chomp $cmd; # remove cariage return
+ $cmd =~ s/\x1b\[\d*\w//g; # remove (esc)[(number)(1alpha) = screen control sequence
+ $cmd =~ s/[\x00-\x1f]//g; # remove control char
+ my($command, $parameters) = split /\s+/,$cmd,2; # extract command and parameters
+ $command = lc($command); # command in lowercase
+ my(@paramlist) = split /\s+/,$parameters; # get list of parameters
+
+ if ($command eq "?" || $command eq "") {
+ $command = "aide" if ($defaultlanguage eq "F");
+ $command = "help" if ($defaultlanguage ne "F");
+ }
+
+ # Analyse of the command
+ eval {
+# help
+ if ("aide" =~ /^\Q$command/ && $command ne "a") { # check 1 letter command: 'aide' or 'add'?
+ displayhelp("aide", $paramlist[0]);
+ } elsif ("help" =~ /^\Q$command/) {
+ displayhelp("help", $paramlist[0]);
+
+# general commands
+ } elsif ("add" =~ /^\Q$command/ && $command ne "a") { # check 1 letter command: 'aide' or 'add'?
+ if (@paramlist = ($parameters =~ m/^"(.*)"\s+(\S+)\s+(.*)/)) {
+ addaccount($paramlist[0], $paramlist[1], $paramlist[2]); # <account_name> <sex> <password>
+ } elsif (@paramlist = ($parameters =~ m/^"(.*)"\s+(\S+)/)) {
+ addaccount($paramlist[0], $paramlist[1], ""); # <account_name> <sex> <password>
+ } elsif (@paramlist = ($parameters =~ m/^'(.*)'\s+(\S+)\s+(.*)/)) {
+ addaccount($paramlist[0], $paramlist[1], $paramlist[2]); # <account_name> <sex> <password>
+ } elsif (@paramlist = ($parameters =~ m/^'(.*)'\s+(\S+)/)) {
+ addaccount($paramlist[0], $paramlist[1], ""); # <account_name> <sex> <password>
+ } else {
+ @paramlist = split /\s+/,$parameters;
+ addaccount($paramlist[0], $paramlist[1], $paramlist[2]); # <account_name> <sex> <password>
+ }
+
+ } elsif ($command eq "ban" || ("banish" =~ /^\Q$command/ && length($command) >= 4)) {
+ if (@paramlist = ($parameters =~ m/^(\S+)\s+(\S+)\s+"(.*)"/)) { # yyyy/mm/dd hh:mm:ss <account_name>
+ bansetaccount($paramlist[2], $paramlist[0], $paramlist[1]); # <account_name> yyyy/mm/dd [hh:mm:ss]
+ } elsif (@paramlist = ($parameters =~ m/^(\S+)\s+(\S+)\s+'(.*)'/)) { # yyyy/mm/dd hh:mm:ss <account_name>
+ bansetaccount($paramlist[2], $paramlist[0], $paramlist[1]); # <account_name> yyyy/mm/dd [hh:mm:ss]
+ } else {
+ @paramlist = split /\s+/,$parameters,3; # yyyy/mm/dd hh:mm:ss <account_name>
+ bansetaccount($paramlist[2], $paramlist[0], $paramlist[1]); # <account_name> yyyy/mm/dd [hh:mm:ss]
+ }
+
+ } elsif (("banadd" =~ /^\Q$command/ || $command eq "ba") && $command ne "b") { # check 1 letter command: 'ba' or 'bs'?
+ if (@paramlist = ($parameters =~ m/^"(.*)"\s+(\S+)/)) {
+ banaddaccount($paramlist[0], $paramlist[1]); # <account_name> <modifier>
+ } elsif (@paramlist = ($parameters =~ m/^'(.*)'\s+(\S+)/)) {
+ banaddaccount($paramlist[0], $paramlist[1]); # <account_name> <modifier>
+ } else {
+ @paramlist = split /\s+/,$parameters;
+ banaddaccount($paramlist[0], $paramlist[1]); # <account_name> <modifier>
+ }
+
+ } elsif (("banset" =~ /^\Q$command/ || $command eq "bs") && $command ne "b") { # check 1 letter command: 'ba' or 'bs'?
+ if (@paramlist = ($parameters =~ m/^"(.*)"\s+(\S+)\s+(\S+)/)) {
+ bansetaccount($paramlist[0], $paramlist[1], $paramlist[2]); # <account_name> yyyy/mm/dd [hh:mm:ss]
+ } elsif (@paramlist = ($parameters =~ m/^"(.*)"\s+(\S+)/)) {
+ bansetaccount($paramlist[0], $paramlist[1], "23:59:59"); # <account_name> yyyy/mm/dd [hh:mm:ss]
+ } elsif (@paramlist = ($parameters =~ m/^'(.*)'\s+(\S+)\s+(\S+)/)) {
+ bansetaccount($paramlist[0], $paramlist[1], $paramlist[2]); # <account_name> yyyy/mm/dd [hh:mm:ss]
+ } elsif (@paramlist = ($parameters =~ m/^'(.*)'\s+(\S+)/)) {
+ bansetaccount($paramlist[0], $paramlist[1], "23:59:59"); # <account_name> yyyy/mm/dd [hh:mm:ss]
+ } else {
+ @paramlist = split /\s+/,$parameters;
+ bansetaccount($paramlist[0], $paramlist[1], $paramlist[2]); # <account_name> yyyy/mm/dd [hh:mm:ss]
+ }
+
+ } elsif ("block" =~ /^\Q$command/ && length($command) >= 2) {
+ if (@paramlist = ($parameters =~ m/^"(.*)"/)) {
+ changestate($paramlist[0], 5, ""); # <account_name> <new_state> <error_message_#7>
+ } elsif (@paramlist = ($parameters =~ m/^'(.*)'/)) {
+ changestate($paramlist[0], 5, ""); # <account_name> <new_state> <error_message_#7>
+ } else {
+ @paramlist = split /\s+/,$parameters,1;
+ changestate($paramlist[0], 5, ""); # <account_name> <new_state> <error_message_#7>
+ }
+
+ } elsif ("check" =~ /^\Q$command/ && $command ne "c") { # check 1 letter command: 'check' or 'create'?
+ if (@paramlist = ($parameters =~ m/^"(.*)"\s+(.*)/)) {
+ checkaccount($paramlist[0], $paramlist[1]); # <account_name> <password>
+ } elsif (@paramlist = ($parameters =~ m/^"(.*)"/)) {
+ checkaccount($paramlist[0], ""); # <account_name> <password>
+ } elsif (@paramlist = ($parameters =~ m/^'(.*)'\s+(.*)/)) {
+ checkaccount($paramlist[0], $paramlist[1]); # <account_name> <password>
+ } elsif (@paramlist = ($parameters =~ m/^'(.*)'/)) {
+ checkaccount($paramlist[0], ""); # <account_name> <password>
+ } else {
+ @paramlist = split /\s+/,$parameters;
+ checkaccount($paramlist[0], $paramlist[1]); # <account_name> <password>
+ }
+
+ } elsif ("create" =~ /^\Q$command/ && $command ne "c") { # check 1 letter command: 'check' or 'create'?
+ if (@paramlist = ($parameters =~ m/^"(.*)"\s+(\S+)\s+(\S+)\s+(.*)/)) {
+ createaccount($paramlist[0], $paramlist[1], $paramlist[2], $paramlist[3]); # <account_name> <sex> <email> <password>
+ } elsif (@paramlist = ($parameters =~ m/^"(.*)"\s+(\S+)\s+(\S+)/)) {
+ createaccount($paramlist[0], $paramlist[1], $paramlist[2], ""); # <account_name> <sex> <email> <password>
+ } elsif (@paramlist = ($parameters =~ m/^'(.*)'\s+(\S+)\s+(\S+)\s+(.*)/)) {
+ createaccount($paramlist[0], $paramlist[1], $paramlist[2], $paramlist[3]); # <account_name> <sex> <email> <password>
+ } elsif (@paramlist = ($parameters =~ m/^'(.*)'\s+(\S+)\s+(\S+)/)) {
+ createaccount($paramlist[0], $paramlist[1], $paramlist[2], ""); # <account_name> <sex> <email> <password>
+ } else {
+ @paramlist = split /\s+/,$parameters;
+ createaccount($paramlist[0], $paramlist[1], $paramlist[2], $paramlist[3]); # <account_name> <sex> <email> <password>
+ }
+
+ } elsif ("del" =~ /^\Q$command/ || "delete" =~ /^\Q$command/) {
+ if (@paramlist = ($parameters =~ m/^"(.*)"/)) {
+ delaccount($paramlist[0]); # <account_name>
+ } elsif (@paramlist = ($parameters =~ m/^'(.*)'/)) {
+ delaccount($paramlist[0]); # <account_name>
+ } else {
+ @paramlist = split /\s+/,$parameters,1;
+ delaccount($paramlist[0]); # <account_name>
+ }
+
+ } elsif ("email" =~ /^\Q$command/ && $command ne "e") { # check 1 letter command: 'email', 'end' or 'exit'?
+ if (@paramlist = ($parameters =~ m/^"(.*)"\s+(\S+)/)) {
+ changeemail($paramlist[0], $paramlist[1]); # <account_name> <email>
+ } elsif (@paramlist = ($parameters =~ m/^'(.*)'\s+(\S+)/)) {
+ changeemail($paramlist[0], $paramlist[1]); # <account_name> <email>
+ } else {
+ @paramlist = split /\s+/,$parameters;
+ changeemail($paramlist[0], $paramlist[1]); # <account_name> <email>
+ }
+
+ } elsif ("getcount" =~ /^\Q$command/ && $command ne "g") { # check 1 letter command: 'getcount' or 'gm'?
+ getlogincount();
+
+ } elsif ("gm" =~ /^\Q$command/ && $command ne "g") { # check 1 letter command: 'getcount' or 'gm'?
+ if (@paramlist = ($parameters =~ m/^"(.*)"\s+(\S+)/)) {
+ changegmlevel($paramlist[0], int($paramlist[1])); # <account_name> <GM_level>
+ } elsif (@paramlist = ($parameters =~ m/^"(.*)"/)) {
+ changegmlevel($paramlist[0], 0); # <account_name> <GM_level>
+ } elsif (@paramlist = ($parameters =~ m/^'(.*)'\s+(\S+)/)) {
+ changegmlevel($paramlist[0], int($paramlist[1])); # <account_name> <GM_level>
+ } elsif (@paramlist = ($parameters =~ m/^'(.*)'/)) {
+ changegmlevel($paramlist[0], 0); # <account_name> <GM_level>
+ } else {
+ @paramlist = split /\s+/,$parameters;
+ changegmlevel($paramlist[0], int($paramlist[1])); # <account_name> <GM_level>
+ }
+
+ } elsif ("id" =~ /^\Q$command/ && $command ne "i") { # check 1 letter command: 'id' or 'info'?
+ if (@paramlist = ($parameters =~ m/^"(.*)"/)) {
+ idaccount($paramlist[0]); # <account_name>
+ } elsif (@paramlist = ($parameters =~ m/^'(.*)'/)) {
+ idaccount($paramlist[0]); # <account_name>
+ } else {
+ @paramlist = split /\s+/,$parameters,1;
+ idaccount($paramlist[0]); # <account_name>
+ }
+
+ } elsif ("info" =~ /^\Q$command/ && $command ne "i") { # check 1 letter command: 'id' or 'info'?
+ infoaccount(int($paramlist[0])); # <account_id>
+
+ } elsif ($command eq "kami") { # check all letters command: 'kami' or 'kamib'?
+ @paramlist = split /\s+/,$parameters,1;
+ sendbroadcast(0, $paramlist[0]); # <type> <message>
+
+ } elsif ($command eq "kamib") { # check all letters command: 'kami' or 'kamib'?
+ @paramlist = split /\s+/,$parameters,1;
+ sendbroadcast(0x10, $paramlist[0]); # <type> <message>
+
+ } elsif ("language" =~ /^\Q$command/ && $command ne "l") { # check 1 letter command: 'list' or 'language'?
+ changelanguage($paramlist[0]); # <language>
+
+ } elsif (("list" =~ /^\Q$command/ || $command eq "ls") && $command ne "l") { # check 1 letter command: 'list' or 'language'?
+ listaccount(int($paramlist[0]), int($paramlist[1]), 0); # [start_id [end_id]] 0: to list all
+
+ } elsif (("listban" =~ /^\Q$command/ || $command eq "lsban") && $command ne "l") { # need to specificaly write Ban to have this list # check 1 letter command: 'list' or 'language'?
+ listaccount(int($paramlist[0]), int($paramlist[1]), 3); # [start_id [end_id]] 3: to list only accounts with state or banished
+
+ } elsif (("listgm" =~ /^\Q$command/ || $command eq "lsgm") && $command ne "l") { # need to specificaly write GM to have this list # check 1 letter command: 'list' or 'language'?
+ listaccount(int($paramlist[0]), int($paramlist[1]), 1); # [start_id [end_id]] 1: to list only GM
+
+ } elsif (("listok" =~ /^\Q$command/ || $command eq "lsok") && $command ne "l") { # need to specificaly write OK to have this list # check 1 letter command: 'list' or 'language'?
+ listaccount(int($paramlist[0]), int($paramlist[1]), 4); # [start_id [end_id]] 4: to list only accounts without state and not banished
+
+ } elsif ("memo" =~ /^\Q$command/) {
+ if (@paramlist = ($parameters =~ m/^"(.*)"\s+(.*)/)) {
+ changememo($paramlist[0], $paramlist[1]); # <account_name> <memo>
+ } elsif (@paramlist = ($parameters =~ m/^'(.*)'\s+(.*)/)) {
+ changememo($paramlist[0], $paramlist[1]); # <account_name> <memo>
+ } else {
+ @paramlist = split /\s+/,$parameters,2;
+ changememo($paramlist[0], $paramlist[1]); # <account_name> <memo>
+ }
+
+ } elsif ("name" =~ /^\Q$command/) {
+ nameaccount(int($paramlist[0])); # <account_id>
+
+ } elsif ("passwd" =~ /^\Q$command/ || "password" =~ /^\Q$command/) {
+ if (@paramlist = ($parameters =~ m/^"(.*)"\s+(.*)/)) {
+ changepasswd($paramlist[0], $paramlist[1]); # <account_name> <new_password>
+ } elsif (@paramlist = ($parameters =~ m/^"(.*)"/)) {
+ changepasswd($paramlist[0], ""); # <account_name> <new_password>
+ } elsif (@paramlist = ($parameters =~ m/^'(.*)'\s+(.*)/)) {
+ changepasswd($paramlist[0], $paramlist[1]); # <account_name> <new_password>
+ } elsif (@paramlist = ($parameters =~ m/^'(.*)'/)) {
+ changepasswd($paramlist[0], ""); # <account_name> <new_password>
+ } else {
+ @paramlist = split /\s+/,$parameters,2;
+ changepasswd($paramlist[0], $paramlist[1]); # <account_name> <new_password>
+ }
+
+ } elsif ("reloadgm" =~ /^\Q$command/) {
+ reloadGM();
+
+ } elsif ("search" =~ /^\Q$command/ && $command ne "s" && # check 1 letter command: 'search', 'state' or 'sex'?
+ $command ne "se") { # check 2 letters command: 'search' or 'sex'?
+ if (@paramlist = ($parameters =~ m/^(-{1,2}[re]\S*)\s+(.*)/)) {
+ searchaccount($paramlist[0], $paramlist[1]); # -r/-e/--expr/--regex <expression> | <expression>
+ } else {
+ @paramlist = split /\s+/,$parameters,1;
+ searchaccount($paramlist[0], ""); # -r/-e/--expr/--regex <expression> | <expression>
+ }
+
+ } elsif ("sex" =~ /^\Q$command/ && $command ne "s" && # check 1 letter command: 'search', 'state' or 'sex'?
+ $command ne "se") { # check 2 letters command: 'search' or 'sex'?
+ if (@paramlist = ($parameters =~ m/^"(.*)"\s+(\S+)/)) {
+ changesex($paramlist[0], $paramlist[1]); # <account_name> <sex>
+ } elsif (@paramlist = ($parameters =~ m/^'(.*)'\s+(\S+)/)) {
+ changesex($paramlist[0], $paramlist[1]); # <account_name> <sex>
+ } else {
+ @paramlist = split /\s+/,$parameters;
+ changesex($paramlist[0], $paramlist[1]); # <account_name> <sex>
+ }
+
+ } elsif ("state" =~ /^\Q$command/ && $command ne "s") { # check 1 letter command: 'search', 'state' or 'sex'?
+ if (@paramlist = ($parameters =~ m/^"(.*)"\s+(\d+)\s+(.*)/)) {
+ changestate($paramlist[0], int($paramlist[1]), $paramlist[2]); # <account_name> <new_state> <error_message_#7>
+ } elsif (@paramlist = ($parameters =~ m/^"(.*)"\s+(\d+)/)) {
+ changestate($paramlist[0], int($paramlist[1]), ""); # <account_name> <new_state> <error_message_#7>
+ } elsif (@paramlist = ($parameters =~ m/^'(.*)'\s+(\d+)\s+(.*)/)) {
+ changestate($paramlist[0], int($paramlist[1]), $paramlist[2]); # <account_name> <new_state> <error_message_#7>
+ } elsif (@paramlist = ($parameters =~ m/^'(.*)'\s+(\d+)/)) {
+ changestate($paramlist[0], int($paramlist[1]), ""); # <account_name> <new_state> <error_message_#7>
+ } else {
+ @paramlist = split /\s+/,$parameters,3;
+ changestate($paramlist[0], int($paramlist[1]), $paramlist[2]); # <account_name> <new_state> <error_message_#7>
+ }
+
+ } elsif (("timeadd" =~ /^\Q$command/ || $command eq "ta") && $command ne "t") { # check 1 letter command: 'ta' or 'ts'?
+ if (@paramlist = ($parameters =~ m/^"(.*)"\s+(\S+)/)) {
+ timeaddaccount($paramlist[0], $paramlist[1]); # <account_name> <modifier>
+ } elsif (@paramlist = ($parameters =~ m/^'(.*)'\s+(\S+)/)) {
+ timeaddaccount($paramlist[0], $paramlist[1]); # <account_name> <modifier>
+ } else {
+ @paramlist = split /\s+/,$parameters;
+ timeaddaccount($paramlist[0], $paramlist[1]); # <account_name> <modifier>
+ }
+
+ } elsif (("timeset" =~ /^\Q$command/ || $command eq "ts") && $command ne "t") { # check 1 letter command: 'ta' or 'ts'?
+ if (@paramlist = ($parameters =~ m/^"(.*)"\s+(\S+)\s+(\S+)/)) {
+ timesetaccount($paramlist[0], $paramlist[1], $paramlist[2]); # <account_name> yyyy/mm/dd [hh:mm:ss]
+ } elsif (@paramlist = ($parameters =~ m/^"(.*)"\s+(\S+)/)) {
+ timesetaccount($paramlist[0], $paramlist[1], "23:59:59"); # <account_name> yyyy/mm/dd [hh:mm:ss]
+ } elsif (@paramlist = ($parameters =~ m/^'(.*)'\s+(\S+)\s+(\S+)/)) {
+ timesetaccount($paramlist[0], $paramlist[1], $paramlist[2]); # <account_name> yyyy/mm/dd [hh:mm:ss]
+ } elsif (@paramlist = ($parameters =~ m/^'(.*)'\s+(\S+)/)) {
+ timesetaccount($paramlist[0], $paramlist[1], "23:59:59"); # <account_name> yyyy/mm/dd [hh:mm:ss]
+ } else {
+ @paramlist = split /\s+/,$parameters;
+ timesetaccount($paramlist[0], $paramlist[1], $paramlist[2]); # <account_name> yyyy/mm/dd [hh:mm:ss]
+ }
+
+ } elsif ($command eq "unban" || ("unbanish" =~ /^\Q$command/ && length($command) >= 4)) {
+ if (@paramlist = ($parameters =~ m/^"(.*)"/)) {
+ bansetaccount($paramlist[0], 0, ""); # <account_name> yyyy/mm/dd [hh:mm:ss]
+ } elsif (@paramlist = ($parameters =~ m/^'(.*)'/)) {
+ bansetaccount($paramlist[0], 0, ""); # <account_name> yyyy/mm/dd [hh:mm:ss]
+ } else {
+ @paramlist = split /\s+/,$parameters,1;
+ bansetaccount($paramlist[0], 0, ""); # <account_name> yyyy/mm/dd [hh:mm:ss]
+ }
+
+ } elsif ("unblock" =~ /^\Q$command/ && length($command) >= 4) {
+ if (@paramlist = ($parameters =~ m/^"(.*)"/)) {
+ changestate($paramlist[0], 0, ""); # <account_name> <new_state> <error_message_#7>
+ } elsif (@paramlist = ($parameters =~ m/^'(.*)'/)) {
+ changestate($paramlist[0], 0, ""); # <account_name> <new_state> <error_message_#7>
+ } else {
+ @paramlist = split /\s+/,$parameters,1;
+ changestate($paramlist[0], 0, ""); # <account_name> <new_state> <error_message_#7>
+ }
+
+ } elsif ("version" =~ /^\Q$command/) {
+ checkloginversion();
+
+ } elsif ("who" =~ /^\Q$command/) {
+ if (@paramlist = ($parameters =~ m/^"(.*)"/)) {
+ whoaccount($paramlist[0]); # <account_name>
+ } elsif (@paramlist = ($parameters =~ m/^'(.*)'/)) {
+ whoaccount($paramlist[0]); # <account_name>
+ } else {
+ @paramlist = split /\s+/,$parameters,1;
+ whoaccount($paramlist[0]); # <account_name>
+ }
+
+# quit
+ } elsif ("quit" =~ /^\Q$command/ ||
+ (("end" =~ /^\Q$command/ || "exit" =~ /^\Q$command/) && $command ne "e")) { # check 1 letter command: 'email', 'end' or 'exit'?
+ last;
+
+# unknown command
+ } elsif ($command) {
+ if ($defaultlanguage eq "F") {
+ print "Commande inconnue [".$command."]\n";
+ } else {
+ print "Unknown command [".$command."]\n";
+ }
+ }
+# $term->addhistory($cmd) if $command;
+ };
+ if ($@) {
+ if ($defaultlanguage eq "F") {
+ print "Erreur [".$command."]\n$@";
+ } else {
+ print "Error [".$command."]\n$@";
+ }
+ }
+};
+
+# End of the software
+quit();
+
+if ($defaultlanguage eq "F") {
+ print "Au revoir.\n";
+} else {
+ print "Bye.\n";
+}
+exit(0);
+
+#--------------------------------------------------------------------------
+
+# Sub-function: Displaying of the version of the login-server
+sub checkloginversion() {
+ print $so pack("v",30000); # 0x7530
+ $so->flush();
+ $buf = readso(10);
+ # Analyse du Packet
+ my($ret, $maver, $miver, $rev, $dev, $mod, $type, $mdver) = unpack("vc6v", $buf);
+ if ($ret != 30001) { #0x7531
+ if ($defaultlanguage eq "F") {
+ print "Probl鑪e de connexion au serveur (r駱onse incorrecte).\n";
+ } else {
+ print "Connection error to the server (incorrect answer).\n";
+ }
+ exit(6);
+ }
+
+ print " Login-Server [$loginserverip:$loginserverport]\n";
+ printf " eAthena version %s-%d.%d", ("stable", "dev")[$dev], $maver, $miver;
+ printf " revision %d", $rev if $rev;
+ printf "%s%d.\n", ("", "-mod")[$mod], $mdver;
+ return 0;
+}
+
+#--------------------------------------------------------------------------
+
+# Sub-function: Displaying of the help
+sub displayhelp() {
+ my($help, $receivedcommand) = @_;
+
+ my($command) = lc($receivedcommand); # command in lowercase
+
+ if ($command eq "") {
+ $command = "not a command"; # any value that is not a command
+ }
+
+ if ($command eq "?") {
+ $command = "aide" if ($defaultlanguage eq "F");
+ $command = "help" if ($defaultlanguage ne "F");
+ }
+
+ if ($help eq "aide") {
+ if ("aide" =~ /^\Q$command/ && $command ne "a") { # check 1 letter command: 'aide' or 'add'?
+ printf "aide/help/?\n";
+ printf " Affiche la description des commandes\n";
+ printf "aide/help/? [commande]\n";
+ printf " Affiche la description de la commande specifi馥\n";
+ } elsif ("help" =~ /^\Q$command/) {
+ printf "aide/help/?\n";
+ printf " Display the description of the commands\n";
+ printf "aide/help/? [command]\n";
+ printf " Display the description of the specified command\n";
+ } elsif ("add" =~ /^\Q$command/ && $command ne "a") { # check 1 letter command: 'aide' or 'add'?
+ printf "add <nomcompte> <sexe> <motdepasse>\n";
+ printf " Cr馥 un compte avec l'email par d馭aut (a\@a.com).\n";
+ printf " Concernant le sexe, seule la premi鑽e lettre compte (F ou M).\n";
+ printf " L'e-mail est a\@a.com (e-mail par d馭aut). C'est comme n'avoir aucun e-mail.\n";
+ printf " Lorsque motdepasse est omis, la saisie se fait sans que la frappe se voit.\n";
+ printf " <exemple> add testname Male testpass\n";
+ } elsif ($command eq "ban" || ("banish" =~ /^\Q$command/ && length($command) >= 4)) {
+ printf "ban/banish aaaa/mm/jj hh:mm:ss <nomcompte>\n";
+ printf " Change la date de fin de bannissement d'un compte.\n";
+ printf " La diff駻ence avec banset est la position du nom du compte.\n";
+ } elsif (("banadd" =~ /^\Q$command/ || $command eq "ba") && $command ne "b") { # check 1 letter command: 'ba' or 'bs'?
+ printf "banadd <nomcompte> <Modificateur>\n";
+ printf " Ajoute ou soustrait du temps la date de banissement d'un compte.\n";
+ printf " Les modificateurs sont construits comme suit:\n";
+ printf " Valeur d'ajustement (-1, 1, +1, etc...)\n";
+ printf " El駑ent modifi:\n";
+ printf " a ou y: ann馥\n";
+ printf " m: mois\n";
+ printf " j ou d: jour\n";
+ printf " h: heure\n";
+ printf " mn: minute\n";
+ printf " s: seconde\n";
+ printf " <exemple> banadd testname +1m-2mn1s-6a\n";
+ printf " Cette exemple ajoute 1 mois et une seconde, et soustrait 2 minutes\n";
+ printf " et 6 ans dans le m麥e temps.\n";
+ printf "NOTE: Si vous modifez la date de banissement d'un compte non bani,\n";
+ printf " vous indiquez comme date (le moment actuel +- les ajustements)\n";
+ } elsif (("banset" =~ /^\Q$command/ || $command eq "bs") && $command ne "b") { # check 1 letter command: 'ba' or 'bs'?
+ printf "banset <nomcompte> aaaa/mm/jj [hh:mm:ss]\n";
+ printf " Change la date de fin de bannissement d'un compte.\n";
+ printf " Heure par d馭aut: 23:59:59\n";
+ printf "banset <nomcompte> 0\n";
+ printf " D饕anni un compte (0 = de-banni).\n";
+ } elsif ("block" =~ /^\Q$command/ && length($command) >= 2) {
+ printf "block <nom compte>\n";
+ printf " Place le status d'un compte 5 (You have been blocked by the GM Team).\n";
+ printf " La commande est l'駲uivalent de state <nom_compte> 5.\n";
+ } elsif ("check" =~ /^\Q$command/ && $command ne "c") { # check 1 letter command: 'check' or 'create'?
+ printf "check <nomcompte> <motdepasse>\n";
+ printf " V駻ifie la validit d'un mot de passe pour un compte\n";
+ printf " NOTE: Le serveur n'enverra jamais un mot de passe.\n";
+ printf " C'est la seule m騁hode que vous poss馘ez pour savoir\n";
+ printf " si un mot de passe est le bon. L'autre m騁hode est\n";
+ printf " d'avoir un acc鑚 ('physique') au fichier des comptes.\n";
+ } elsif ("create" =~ /^\Q$command/ && $command ne "c") { # check 1 letter command: 'check' or 'create'?
+ printf "create <nomcompte> <sexe> <email> <motdepasse>\n";
+ printf " Comme la commande add, mais avec l'e-mail en plus.\n";
+ printf " <exemple> create testname Male mon\@mail.com testpass\n";
+ } elsif ("del" =~ /^\Q$command/ || "delete" =~ /^\Q$command/) {
+ printf "del <nomcompte>\n";
+ printf " Supprime un compte.\n";
+ printf " La commande demande confirmation. Apr鑚 confirmation, le compte est d騁ruit.\n";
+ } elsif ("email" =~ /^\Q$command/ && $command ne "e") { # check 1 letter command: 'email', 'end' or 'exit'?
+ printf "email <nomcompte> <email>\n";
+ printf " Modifie l'e-mail d'un compte.\n";
+ } elsif ("getcount" =~ /^\Q$command/ && $command ne "g") { # check 1 letter command: 'getcount' or 'gm'?
+ printf "getcount\n";
+ printf " Donne le nombre de joueurs en ligne par serveur de char.\n";
+ } elsif ("gm" =~ /^\Q$command/ && $command ne "g") { # check 1 letter command: 'getcount' or 'gm'?
+ printf "gm <nomcompte> [Niveau_GM]\n";
+ printf " Modifie le niveau de GM d'un compte.\n";
+ printf " Valeur par d馭aut: 0 (suppression du niveau de GM).\n";
+ printf " <exemple> gm nomtest 80\n";
+ } elsif ("id" =~ /^\Q$command/ && $command ne "i") { # check 1 letter command: 'id' or 'info'?
+ printf "id <nomcompte>\n";
+ printf " Donne l'id d'un compte.\n";
+ } elsif ("info" =~ /^\Q$command/ && $command ne "i") { # check 1 letter command: 'id' or 'info'?
+ printf "info <idcompte>\n";
+ printf " Affiche les informations sur un compte.\n";
+ } elsif ($command eq "kami") { # check all letters command: 'kami' or 'kamib'?
+ printf "kami <message>\n";
+ printf " Envoi un message g駭駻al sur tous les serveurs de map (en jaune).\n";
+ } elsif ($command eq "kamib") { # check all letters command: 'kami' or 'kamib'?
+ printf "kamib <message>\n";
+ printf " Envoi un message g駭駻al sur tous les serveurs de map (en bleu).\n";
+ } elsif ("language" =~ /^\Q$command/ && $command ne "l") { # check 1 letter command: 'list' or 'language'?
+ printf("language <langue>\n");
+ printf(" Change la langue d'affichage.\n");
+ printf(" Langues possibles: 'Fran軋is' ou 'English'.\n");
+ } elsif (("list" =~ /^\Q$command/ || $command eq "ls") && $command ne "l") { # check 1 letter command: 'list' or 'language'?
+ printf "list/ls [Premier_id [Dernier_id]]\n";
+ printf " Affiche une liste de comptes.\n";
+ printf " 'Premier_id', 'Dernier_id': indique les identifiants de d駱art et de fin.\n";
+ printf " La recherche par nom n'est pas possible avec cette commande.\n";
+ printf " <example> list 10 9999999\n";
+ } elsif (("listban" =~ /^\Q$command/ || $command eq "lsban") && $command ne "l") { # need to specificaly write Ban to have this list # check 1 letter command: 'list' or 'language'?
+ printf "listBan/lsBan [Premier_id [Dernier_id]]\n";
+ printf " Comme list/ls, mais seulement pour les comptes GM avec un statut ou bannis.\n";
+ } elsif (("listgm" =~ /^\Q$command/ || $command eq "lsgm") && $command ne "l") { # need to specificaly write GM to have this list # check 1 letter command: 'list' or 'language'?
+ printf "listGM/lsGM [Premier_id [Dernier_id]]\n";
+ printf " Comme list/ls, mais seulement pour les comptes GM.\n";
+ } elsif (("listok" =~ /^\Q$command/ || $command eq "lsok") && $command ne "l") { # need to specificaly write OK to have this list # check 1 letter command: 'list' or 'language'?
+ printf "listOK/lsOK [Premier_id [Dernier_id]]\n";
+ printf " Comme list/ls, mais seulement pour les comptes sans statut et non bannis.\n";
+ } elsif ("memo" =~ /^\Q$command/) {
+ printf "memo <nomcompte> <memo>\n";
+ printf " Modifie le m駑o d'un compte.\n";
+ printf " 'memo': Il peut avoir jusqu' 253 caract鑽es (avec des espaces ou non).\n";
+ } elsif ("name" =~ /^\Q$command/) {
+ printf "name <idcompte>\n";
+ printf " Donne le nom d'un compte.\n";
+ } elsif ("passwd" =~ /^\Q$command/ || "password" =~ /^\Q$command/) {
+ printf "passwd <nomcompte> <nouveaumotdepasse>\n";
+ printf " Change le mot de passe d'un compte.\n";
+ printf " Lorsque nouveaumotdepasse est omis,\n";
+ printf " la saisie se fait sans que la frappe ne se voit.\n";
+ } elsif ("reloadgm" =~ /^\Q$command/) {
+ printf "reloadGM\n";
+ printf " Reload GM configuration file\n";
+ } elsif ("search" =~ /^\Q$command/ && $command ne "s" && # check 1 letter command: 'search', 'state' or 'sex'?
+ $command ne "se") { # check 2 letters command: 'search' or 'sex'?
+ printf "search <expression>\n";
+ printf " Cherche des comptes.\n";
+ printf " Affiche les comptes dont les noms correspondent.\n";
+ printf "search -r/-e/--expr/--regex <expression>\n";
+ printf " Cherche des comptes par expression reguli鑽e.\n";
+ printf " Affiche les comptes dont les noms correspondent.\n";
+ } elsif ("sex" =~ /^\Q$command/ && $command ne "s" && # check 1 letter command: 'search', 'state' or 'sex'?
+ $command ne "se") { # check 2 letters command: 'search' or 'sex'?
+ printf "sex <nomcompte> <sexe>\n";
+ printf " Modifie le sexe d'un compte.\n";
+ printf " <exemple> sex testname Male\n";
+ } elsif ("state" =~ /^\Q$command/ && $command ne "s") { # check 1 letter command: 'search', 'state' or 'sex'?
+ printf "state <nomcompte> <nouveaustatut> <message_erreur_7>\n";
+ printf " Change le statut d'un compte.\n";
+ printf " 'nouveaustatut': Le statut est le m麥e que celui du packet 0x006a + 1.\n";
+ printf " les possibilit駸 sont:\n";
+ printf " 0 = Compte ok\n";
+ printf " 1 = Unregistered ID\n";
+ printf " 2 = Incorrect Password\n";
+ printf " 3 = This ID is expired\n";
+ printf " 4 = Rejected from Server\n";
+ printf " 5 = You have been blocked by the GM Team\n";
+ printf " 6 = Your Game's EXE file is not the latest version\n";
+ printf " 7 = You are Prohibited to log in until...\n";
+ printf " 8 = Server is jammed due to over populated\n";
+ printf " 9 = No MSG\n";
+ printf " 100 = This ID has been totally erased\n";
+ printf " all other values are 'No MSG', then use state 9 please.\n";
+ printf " 'message_erreur_7': message du code erreur 6 =\n";
+ printf " = Your are Prohibited to log in until... (packet 0x006a)\n";
+ } elsif (("timeadd" =~ /^\Q$command/ || $command eq "ta") && $command ne "t") { # check 1 letter command: 'ta' or 'ts'?
+ printf "timeadd <nomcompte> <modificateur>\n";
+ printf " Ajoute/soustrait du temps la limite de validit d'un compte.\n";
+ printf " Le modificateur est compos comme suit:\n";
+ printf " Valeur modificatrice (-1, 1, +1, etc...)\n";
+ printf " El駑ent modifi:\n";
+ printf " a ou y: ann馥\n";
+ printf " m: mois\n";
+ printf " j ou d: jour\n";
+ printf " h: heure\n";
+ printf " mn: minute\n";
+ printf " s: seconde\n";
+ printf " <exemple> timeadd testname +1m-2mn1s-6a\n";
+ printf " Cette exemple ajoute 1 mois et une seconde, et soustrait 2 minutes\n";
+ printf " et 6 ans dans le m麥e temps.\n";
+ printf "NOTE: Vous ne pouvez pas modifier une limite de validit illimit馥. Si vous\n";
+ printf " d駸irez le faire, c'est que vous voulez probablement cr馥r un limite de\n";
+ printf " validit limit馥. Donc, en premier, fix une limite de valitid.\n";
+ } elsif (("timeset" =~ /^\Q$command/ || $command eq "ts") && $command ne "t") { # check 1 letter command: 'ta' or 'ts'?
+ printf "timeset <nomcompte> aaaa/mm/jj [hh:mm:ss]\n";
+ printf " Change la limite de validit d'un compte.\n";
+ printf " Heure par d馭aut: 23:59:59\n";
+ printf "timeset <nomcompte> 0\n";
+ printf " Donne une limite de validit illimit馥 (0 = illimit馥).\n";
+ } elsif ($command eq "unban" || ("unbanish" =~ /^\Q$command/ && length($command) >= 4)) {
+ printf "unban/unbanish <nom compte>\n";
+ printf " Ote le banissement d'un compte.\n";
+ printf " La commande est l'駲uivalent de banset <nom_compte> 0.\n";
+ } elsif ("unblock" =~ /^\Q$command/ && length($command) >= 4) {
+ printf "unblock <nom compte>\n";
+ printf " Place le status d'un compte 0 (Compte ok).\n";
+ printf " La commande est l'駲uivalent de state <nom_compte> 0.\n";
+ } elsif ("version" =~ /^\Q$command/) {
+ printf "version\n";
+ printf " Affiche la version du login-serveur.\n";
+ } elsif ("who" =~ /^\Q$command/) {
+ printf "who <nomcompte>\n";
+ printf " Affiche les informations sur un compte.\n";
+ } elsif ("quit" =~ /^\Q$command/ ||
+ (("end" =~ /^\Q$command/ || "exit" =~ /^\Q$command/) && $command ne "e")) { # check 1 letter command: 'email', 'end' or 'exit'?\n";
+ printf "quit/end/exit\n";
+ printf " Fin du programme d'administration.\n";
+ } else {
+ if ($receivedcommand ne "") {
+ printf "Commande inconnue [%s] pour l'aide. Affichage de toutes les commandes.\n", $receivedcommand;
+ }
+ print << "ENDOFAIDE";
+ aide/help/? -- Affiche cet aide
+ aide/help/? [commande] -- Affiche l'aide de la commande
+ add <nomcompte> <sexe> <motdepasse> -- Cr馥 un compte (sans email)
+ ban/banish aaaa/mm/jj hh:mm:ss <nomcompte>-- Change la date finale de banismnt
+ banadd/ba <nomcompte> <modificateur> -- Ajout/soustrait du temps la
+ exemple: ba moncompte +1m-2mn1s-2y date finale de banissement
+ banset/bs <nomcompte> aaaa/mm/jj [hh:mm:ss] -- Change la date fin de banisemnt
+ banset/bs <nomcompte> 0 -- D-banis un compte.
+ block <nom compte> -- Mets le status d'un compte 5 (blocked by the GM Team)
+ check <nomcompte> <motdepasse> -- V駻ifie un mot de passe d'un compte
+ create <nomcompte> <sexe> <email> <motdepasse> -- Cr馥 un compte (avec email)
+ del <nomcompte> -- Supprime un compte
+ email <nomcompte> <email> -- Modifie l'e-mail d'un compte
+ getcount -- Donne le nb de joueurs en ligne
+ gm <nomcompte> [Niveau_GM] -- Modifie le niveau de GM d'un compte
+ id <nomcompte> -- Donne l'id d'un compte
+ info <idcompte> -- Affiche les infos sur un compte
+ kami <message> -- Envoi un message g駭駻al (en jaune)
+ kamib <message> -- Envoi un message g駭駻al (en bleu)
+ language <langue> -- Change la langue d'affichage.
+ list/ls [Premier_id [Dernier_id] ] -- Affiche une liste de comptes
+ listBan/lsBan [Premier_id [Dernier_id] ]-- Affiche une liste de comptes
+ avec un statut ou bannis
+ listGM/lsGM [Premier_id [Dernier_id] ] -- Affiche une liste de comptes GM
+ listOK/lsOK [Premier_id [Dernier_id] ] -- Affiche une liste de comptes
+ sans status et non bannis
+ memo <nomcompte> <memo> -- Modifie le memo d'un compte
+ name <idcompte> -- Donne le nom d'un compte
+ passwd <nomcompte> <nouveaumotdepasse> -- Change le mot de passe d'un compte
+ quit/end/exit -- Fin du programme d'administation
+ reloadGM -- Recharger le fichier de config des GM
+ search <expression> -- Cherche des comptes
+ search -e/-r/--expr/--regex <expression> -- Cherche des comptes par REGEX
+ sex <nomcompte> <sexe> -- Modifie le sexe d'un compte
+ state <nomcompte> <nouveaustatut> <messageerr7> -- Change le statut d'1 compte
+ timeadd/ta <nomcompte> <modificateur> -- Ajout/soustrait du temps la
+ exemple: ta moncompte +1m-2mn1s-2y limite de validit
+ timeset/ts <nomcompte> aaaa/mm/jj [hh:mm:ss] -- Change la limite de validit
+ timeset/ts <nomcompte> 0 -- limite de validit = illimit馥
+ unban/unbanish <nom compte> -- Ote le banissement d'un compte
+ unblock <nom compte> -- Mets le status d'un compte 0 (Compte ok)
+ version -- Donne la version du login-serveur
+ who <nomcompte> -- Affiche les infos sur un compte
+ENDOFAIDE
+ printf(" Note: Pour les noms de compte avec des espaces, tapez \"<nom compte>\" (ou ').\n");
+ }
+ } else {
+ if ("aide" =~ /^\Q$command/ && $command ne "a") { # check 1 letter command: 'aide' or 'add'?
+ printf "aide/help/?\n";
+ printf " Display the description of the commands\n";
+ printf "aide/help/? [command]\n";
+ printf " Display the description of the specified command\n";
+ } elsif ("help" =~ /^\Q$command/) {
+ printf "aide/help/?\n";
+ printf " Display the description of the commands\n";
+ printf "aide/help/? [command]\n";
+ printf " Display the description of the specified command\n";
+ } elsif ("add" =~ /^\Q$command/ && $command ne "a") { # check 1 letter command: 'aide' or 'add'?
+ printf "add <account_name> <sex> <password>\n";
+ printf " Create an account with the default email (a\@a.com).\n";
+ printf " Concerning the sex, only the first letter is used (F or M).\n";
+ printf " The e-mail is set to a\@a.com (default e-mail). It's like to have no e-mail.\n";
+ printf " When the password is omitted,\n";
+ printf " the input is done without displaying of the pressed keys.\n";
+ printf " <example> add testname Male testpass\n";
+ } elsif ($command eq "ban" || ("banish" =~ /^\Q$command/ && length($command) >= 4)) {
+ printf "ban/banish yyyy/mm/dd hh:mm:ss <account_name>\n";
+ printf " Changes the final date of a banishment of an account.\n";
+ printf " The difference with banset is the position of the account name.\n";
+ } elsif (("banadd" =~ /^\Q$command/ || $command eq "ba") && $command ne "b") { # check 1 letter command: 'ba' or 'bs'?
+ printf "banadd <account_name> <modifier>\n";
+ printf " Adds or substracts time from the final date of a banishment of an account.\n";
+ printf " Modifier is done as follows:\n";
+ printf " Adjustment value (-1, 1, +1, etc...)\n";
+ printf " Modified element:\n";
+ printf " a or y: year\n";
+ printf " m: month\n";
+ printf " j or d: day\n";
+ printf " h: hour\n";
+ printf " mn: minute\n";
+ printf " s: second\n";
+ printf " <example> banadd testname +1m-2mn1s-6y\n";
+ printf " this example adds 1 month and 1 second, and substracts 2 minutes\n";
+ printf " and 6 years at the same time.\n";
+ printf "NOTE: If you modify the final date of a non-banished account,\n";
+ printf " you fix the final date to (actual time +- adjustments)\n";
+ } elsif (("banset" =~ /^\Q$command/ || $command eq "bs") && $command ne "b") { # check 1 letter command: 'ba' or 'bs'?
+ printf "banset <account_name> yyyy/mm/dd [hh:mm:ss]\n";
+ printf " Changes the final date of a banishment of an account.\n";
+ printf " Default time: 23:59:59\n";
+ printf "banset <account_name> 0\n";
+ printf " Set a non-banished account (0 = unbanished).\n";
+ } elsif ("block" =~ /^\Q$command/ && length($command) >= 2) {
+ printf "block <account name>\n";
+ printf " Set state 5 (You have been blocked by the GM Team) to an account.\n";
+ printf " Same command of state <account_name> 5.\n";
+ } elsif ("check" =~ /^\Q$command/ && $command ne "c") { # check 1 letter command: 'check' or 'create'?
+ printf "check <account_name> <password>\n";
+ printf " Check the validity of a password for an account.\n";
+ printf " NOTE: Server will never sends back a password.\n";
+ printf " It's the only method you have to know if a password is correct.\n";
+ printf " The other method is to have a ('physical') access to the accounts file.\n";
+ } elsif ("create" =~ /^\Q$command/ && $command ne "c") { # check 1 letter command: 'check' or 'create'?
+ printf "create <account_name> <sex> <email> <password>\n";
+ printf " Like the 'add' command, but with e-mail moreover.\n";
+ printf " <example> create testname Male my\@mail.com testpass\n";
+ } elsif ("del" =~ /^\Q$command/ || "delete" =~ /^\Q$command/) {
+ printf "del <account_name>\n";
+ printf " Remove an account.\n";
+ printf " This order requires confirmation. After confirmation, the account is deleted.\n";
+ } elsif ("email" =~ /^\Q$command/ && $command ne "e") { # check 1 letter command: 'email', 'end' or 'exit'?
+ printf "email <account_name> <email>\n";
+ printf " Modify the e-mail of an account.\n";
+ } elsif ("getcount" =~ /^\Q$command/ && $command ne "g") { # check 1 letter command: 'getcount' or 'gm'?
+ printf "getcount\n";
+ printf " Give the number of players online on all char-servers.\n";
+ } elsif ("gm" =~ /^\Q$command/ && $command ne "g") { # check 1 letter command: 'getcount' or 'gm'?
+ printf "gm <account_name> [GM_level]\n";
+ printf " Modify the GM level of an account.\n";
+ printf " Default value remove GM level (GM level = 0).\n";
+ printf " <example> gm testname 80\n";
+ } elsif ("id" =~ /^\Q$command/ && $command ne "i") { # check 1 letter command: 'id' or 'info'?
+ printf "id <account_name>\n";
+ printf " Give the id of an account.\n";
+ } elsif ("info" =~ /^\Q$command/ && $command ne "i") { # check 1 letter command: 'id' or 'info'?
+ printf "info <account_id>\n";
+ printf " Display complete information of an account.\n";
+ } elsif ($command eq "kami") { # check all letters command: 'kami' or 'kamib'?
+ printf "kami <message>\n";
+ printf " Sends a broadcast message on all map-server (in yellow).\n";
+ } elsif ($command eq "kamib") { # check all letters command: 'kami' or 'kamib'?
+ printf "kamib <message>\n";
+ printf " Sends a broadcast message on all map-server (in blue).\n";
+ } elsif ("language" =~ /^\Q$command/ && $command ne "l") { # check 1 letter command: 'list' or 'language'?
+ printf("language <language>\n");
+ printf(" Change the language of displaying.\n");
+ printf(" Possible languages: Fran軋is or English.\n");
+ } elsif (("list" =~ /^\Q$command/ || $command eq "ls") && $command ne "l") { # check 1 letter command: 'list' or 'language'?
+ printf "list/ls [start_id [end_id]]\n";
+ printf " Display a list of accounts.\n";
+ printf " 'start_id', 'end_id': indicate end and start identifiers.\n";
+ printf " Research by name is not possible with this command.\n";
+ printf " <example> list 10 9999999\n";
+ } elsif (("listban" =~ /^\Q$command/ || $command eq "lsban") && $command ne "l") { # need to specificaly write Ban to have this list # check 1 letter command: 'list' or 'language'?
+ printf "listBan/lsBan [start_id [end_id]]\n";
+ printf " Like list/ls, but only for accounts with state or banished.\n";
+ } elsif (("listgm" =~ /^\Q$command/ || $command eq "lsgm") && $command ne "l") { # need to specificaly write GM to have this list # check 1 letter command: 'list' or 'language'?
+ printf "listGM/lsGM [start_id [end_id]]\n";
+ printf " Like list/ls, but only for GM accounts.\n";
+ } elsif (("listok" =~ /^\Q$command/ || $command eq "lsok") && $command ne "l") { # need to specificaly write OK to have this list # check 1 letter command: 'list' or 'language'?
+ printf "listOK/lsOK [start_id [end_id]]\n";
+ printf " Like list/ls, but only for accounts without state and not banished.\n";
+ } elsif ("memo" =~ /^\Q$command/) {
+ printf "memo <account_name> <memo>\n";
+ printf " Modify the memo of an account.\n";
+ printf " 'memo': it can have until 253 characters (with spaces or not).\n";
+ } elsif ("name" =~ /^\Q$command/) {
+ printf "name <account_id>\n";
+ printf " Give the name of an account.\n";
+ } elsif ("passwd" =~ /^\Q$command/ || "password" =~ /^\Q$command/) {
+ printf "passwd <account_name> <new_password>\n";
+ printf " Change the password of an account.\n";
+ printf " When new password is omitted,\n";
+ printf " the input is done without displaying of the pressed keys.\n";
+ } elsif ("reloadgm" =~ /^\Q$command/) {
+ printf "reloadGM\n";
+ printf " Reload GM configuration file\n";
+ } elsif ("search" =~ /^\Q$command/ && $command ne "s" && # check 1 letter command: 'search', 'state' or 'sex'?
+ $command ne "se") { # check 2 letters command: 'search' or 'sex'?
+ printf "search <expression>\n";
+ printf " Seek accounts.\n";
+ printf " Displays the accounts whose names correspond.\n";
+ printf "search -r/-e/--expr/--regex <expression>\n";
+ printf " Seek accounts by regular expression.\n";
+ printf " Displays the accounts whose names correspond.\n";
+ } elsif ("sex" =~ /^\Q$command/ && $command ne "s" && # check 1 letter command: 'search', 'state' or 'sex'?
+ $command ne "se") { # check 2 letters command: 'search' or 'sex'?
+ printf "sex <account_name> <sex>\n";
+ printf " Modify the sex of an account.\n";
+ printf " <example> sex testname Male\n";
+ } elsif ("state" =~ /^\Q$command/ && $command ne "s") { # check 1 letter command: 'search', 'state' or 'sex'?
+ printf "state <account_name> <new_state> <error_message_#7>\n";
+ printf " Change the state of an account.\n";
+ printf " 'new_state': state is the state of the packet 0x006a + 1.\n";
+ printf " The possibilities are:\n";
+ printf " 0 = Account ok\n";
+ printf " 1 = Unregistered ID\n";
+ printf " 2 = Incorrect Password\n";
+ printf " 3 = This ID is expired\n";
+ printf " 4 = Rejected from Server\n";
+ printf " 5 = You have been blocked by the GM Team\n";
+ printf " 6 = Your Game's EXE file is not the latest version\n";
+ printf " 7 = You are Prohibited to log in until...\n";
+ printf " 8 = Server is jammed due to over populated\n";
+ printf " 9 = No MSG\n";
+ printf " 100 = This ID has been totally erased\n";
+ printf " all other values are 'No MSG', then use state 9 please.\n";
+ printf " 'error_message_#7': message of the code error 6\n";
+ printf " = Your are Prohibited to log in until... (packet 0x006a)\n";
+ } elsif (("timeadd" =~ /^\Q$command/ || $command eq "ta") && $command ne "t") { # check 1 letter command: 'ta' or 'ts'?
+ printf "timeadd <account_name> <modifier>\n";
+ printf " Adds or substracts time from the validity limit of an account.\n";
+ printf " Modifier is done as follows:\n";
+ printf " Adjustment value (-1, 1, +1, etc...)\n";
+ printf " Modified element:\n";
+ printf " a or y: year\n";
+ printf " m: month\n";
+ printf " j or d: day\n";
+ printf " h: hour\n";
+ printf " mn: minute\n";
+ printf " s: second\n";
+ printf " <example> timeadd testname +1m-2mn1s-6y\n";
+ printf " this example adds 1 month and 1 second, and substracts 2 minutes\n";
+ printf " and 6 years at the same time.\n";
+ printf "NOTE: You can not modify a unlimited validity limit.\n";
+ printf " If you want modify it, you want probably create a limited validity limit.\n";
+ printf " So, at first, you must set the validity limit to a date/time.\n";
+ } elsif (("timeset" =~ /^\Q$command/ || $command eq "ts") && $command ne "t") { # check 1 letter command: 'ta' or 'ts'?
+ printf "timeset <account_name> yyyy/mm/dd [hh:mm:ss]\n";
+ printf " Changes the validity limit of an account.\n";
+ printf " Default time: 23:59:59\n";
+ printf "timeset <account_name> 0\n";
+ printf " Gives an unlimited validity limit (0 = unlimited).\n";
+ } elsif ($command eq "unban" || ("unbanish" =~ /^\Q$command/ && length($command) >= 4)) {
+ printf "unban/unbanish <account name>\n";
+ printf " Remove the banishment of an account.\n";
+ printf " This command works like banset <account_name> 0.\n";
+ } elsif ("unblock" =~ /^\Q$command/ && length($command) >= 4) {
+ printf "unblock <account name>\n";
+ printf " Set state 0 (Account ok) to an account.\n";
+ printf " This command works like state <account_name> 0.\n";
+ } elsif ("version" =~ /^\Q$command/) {
+ printf "version\n";
+ printf " Display the version of the login-server.\n";
+ } elsif ("who" =~ /^\Q$command/) {
+ printf "who <account_name>\n";
+ printf " Displays complete information of an account.\n";
+ } elsif ("quit" =~ /^\Q$command/ ||
+ (("end" =~ /^\Q$command/ || "exit" =~ /^\Q$command/) && $command ne "e")) { # check 1 letter command: 'email', 'end' or 'exit'?\n";
+ printf "quit/end/exit\n";
+ printf " End of the program of administration.\n";
+ } else {
+ if ($receivedcommand ne "") {
+ printf "Unknown command [%s] for help. Displaying of all commands.\n", $receivedcommand;
+ }
+ print << "ENDOFHELP";
+ aide/help/? -- Display this help
+ aide/help/? [command] -- Display the help of the command
+ add <account_name> <sex> <password> -- Create an account with default email
+ ban/banish yyyy/mm/dd hh:mm:ss <account_name> -- Change final date of a ban
+ banadd/ba <account_name> <modifier> -- Add or substract time from the final
+ example: ba apple +1m-2mn1s-2y date of a banishment of an account
+ banset/bs <account_name> yyyy/mm/dd [hh:mm:ss] -- Change final date of a ban
+ banset/bs <account_name> 0 -- Un-banish an account
+ block <account name> -- Set state 5 (blocked by the GM Team) to an account
+ check <account_name> <password> -- Check the validity of a password
+ create <account_name> <sex> <email> <passwrd> -- Create an account with email
+ del <account_name> -- Remove an account
+ email <account_name> <email> -- Modify an email of an account
+ getcount -- Give the number of players online
+ gm <account_name> [GM_level] -- Modify the GM level of an account
+ id <account_name> -- Give the id of an account
+ info <account_id> -- Display all information of an account
+ kami <message> -- Sends a broadcast message (in yellow)
+ kamib <message> -- Sends a broadcast message (in blue)
+ language <language> -- Change the language of displaying.
+ list/ls [First_id [Last_id]] -- Display a list of accounts
+ listBan/lsBan [First_id [Last_id]] -- Display a list of accounts
+ with state or banished
+ listGM/lsGM [First_id [Last_id]] -- Display a list of GM accounts
+ listOK/lsOK [First_id [Last_id]] -- Display a list of accounts
+ without state and not banished
+ memo <account_name> <memo> -- Modify the memo of an account
+ name <account_id> -- Give the name of an account
+ passwd <account_name> <new_password> -- Change the password of an account
+ quit/end/exit -- End of the program of administation
+ reloadGM -- Reload GM configuration file
+ search <expression> -- Seek accounts
+ search -e/-r/--expr/--regex <expressn> -- Seek accounts by regular-expression
+ sex <nomcompte> <sexe> -- Modify the sex of an account
+ state <account_name> <new_state> <error_message_#7> -- Change the state
+ timeadd/ta <account_name> <modifier> -- Add or substract time from the
+ example: ta apple +1m-2mn1s-2y validity limit of an account
+ timeset/ts <account_name> yyyy/mm/dd [hh:mm:ss] -- Change the validify limit
+ timeset/ts <account_name> 0 -- Give a unlimited validity limit
+ unban/unbanish <account name> -- Remove the banishment of an account
+ unblock <account name> -- Set state 0 (Account ok) to an account
+ version -- Gives the version of the login-server
+ who <account_name> -- Display all information of an account
+ENDOFHELP
+ printf(" Note: To use spaces in an account name, type \"<account name>\" (or ').\n");
+ }
+ }
+
+ return 0;
+}
+#--------------------------------------------------------------------------
+
+# Sub-function: Displaying of the accounts list
+sub listaccount() {
+ my($st, $ed, $listflag) = @_;
+ my($i);
+ my($n) = (0);
+ # 0123456789 01 01234567890123456789012301234 012345 0123456789012345678901234567
+ if ($defaultlanguage eq "F") {
+ print " id_compte GM nom_utilisateur sexe count statut\n";
+ } else {
+ print "account_id GM user_name sex count state\n";
+ }
+ print "-------------------------------------------------------------------------------\n";
+ while(1) {
+ print $so pack("vV2", 0x7920, $st, $ed);
+ $so->flush();
+ $buf = readso(4);
+ if (unpack("v", $buf) != 0x7921) {
+ if ($defaultlanguage eq "F") {
+ print "Probl鑪e de connexion au serveur (r駱onse incorrecte).\n";
+ } else {
+ print "Connection error to the server (incorrect answer).\n";
+ }
+ exit(10);
+ }
+ my($len) = unpack("x2v", $buf);
+ last if ($len <= 4);
+ for($i = 4; $i < $len; $i += 38) {
+ my(@dat) = unpack("VCa24cVV", readso(38));
+ $st = $dat[0] + 1;
+ if ($listflag == 0 ||
+ ($listflag == 1 && $dat[1] > 0) || # check GM flag
+ ($listflag == 3 && $dat[5] != 0) || # check with state or banished
+ ($listflag == 4 && $dat[5] == 0)) { # check without state and not banished
+ printf "%10d %2s %-24s%-5s %6d %-27s\n", $dat[0],
+ ($dat[1] == 0 ? " " : $dat[1]),
+ $dat[2],
+ ($defaultlanguage eq "F" ? ("Femme","Male","Servr")[$dat[3]] : ("Femal","Male","Servr")[$dat[3]]),
+ $dat[4],
+ (($defaultlanguage eq "F" ? "Compte Ok" : "Account OK"),
+ "Unregistered ID",
+ "Incorrect Password",
+ "This ID is expired",
+ "Rejected from Server",
+ "Blocked by the GM Team", # You have been blocked by the GM Team
+ "Your EXE file is too old", # Your Game's EXE file is not the latest version
+ "Banishement or\n Prohibited to login until %s", # You are Prohibited to log in until %s
+ "Server is over populated", # Server is jammed due to over populated
+ "No MSG",
+ "This ID is totally erased")[$dat[5] == 100 ? 10 : $dat[5]]; # This ID has been totally erased
+ $n++;
+ }
+ }
+ }
+ if ($defaultlanguage eq "F") {
+ if ($n == 0) {
+ print "Aucun compte trouv.\n";
+ } elsif ($n == 1) {
+ print "1 compte trouv.\n";
+ } else {
+ print "$n comptes trouv駸.\n";
+ }
+ } else {
+ if ($n == 0) {
+ print "No account found.\n";
+ } elsif ($n == 1) {
+ print "1 account found.\n";
+ } else {
+ print "$n accounts found.\n";
+ }
+ }
+ return 0;
+}
+
+#--------------------------------------------------------------------------
+
+# Sub-function: add an account with the default e-mail
+sub addaccount() {
+ my($userid, $sex, $passwd) = @_;
+ if ($userid eq "" || !defined($userid)) {
+ if ($defaultlanguage eq "F") {
+ print "Entrez un nom de compte svp.\n";
+ print "<exemple> add nomtest Male motdepassetest\n";
+ } else {
+ print "Please input an account name.\n";
+ print "<example> add testname Male testpass\n";
+ }
+ return 136;
+ }
+ if (verify_accountname($userid) == 0) {
+ return 102;
+ }
+# if ($userid =~ /[^A-Za-z0-9\@-_]/) {
+# if ($defaultlanguage eq "F") {
+# print "Caract鑽e interdit trouv dans le nom du compte ".$`."[${&}]${'}\n";
+# } else {
+# print "Illegal character found in the account name ".$`."[${&}]${'}\n";
+# }
+# return 101;
+# }
+ $sex = uc(substr($sex, 0, 1));
+ if ($sex !~ /^[MF]$/) {
+ if ($defaultlanguage eq "F") {
+ print "Sexe incorrect [$sex]. Entrez M ou F svp.\n";
+ } else {
+ print "Illegal gender [$sex]. Please input M or F.\n";
+ }
+ return 103;
+ }
+ if ($passwd eq "") {
+ return 108 if (($passwd = typepasswd()) eq "");
+ }
+ if (verify_password($passwd) == 0) {
+ return 104;
+ }
+ print $so pack("va24a24a1a40", 0x7930, $userid, $passwd, $sex, "");
+ $so->flush();
+ $buf = readso(2);
+ if (unpack("v", $buf) != 0x7931) {
+ if ($defaultlanguage eq "F") {
+ print "Probl鑪e de connexion au serveur (r駱onse incorrecte).\n";
+ } else {
+ print "Connection error to the server (incorrect answer).\n";
+ }
+ return 106;
+ }
+ $buf = readso(28);
+ if (unpack("V", $buf) == -1 || unpack("V", $buf) == 4294967295) {
+ if ($defaultlanguage eq "F") {
+ print "Echec la cr饌tion du compte [$userid]. Un compte identique existe d駛.\n";
+ } else {
+ print "Account [$userid] creation failed. Same account already exists.\n";
+ }
+ return 107;
+ } else {
+ if ($defaultlanguage eq "F") {
+ printf "Compte [$userid] cr鳬 avec succ鑚 [id: %d].\n", unpack("V",$buf);
+ } else {
+ printf "Account [$userid] is successfully created [id: %d].\n", unpack("V",$buf);
+ }
+ }
+ return 0;
+}
+
+#--------------------------------------------------------------------------
+
+# Sub-function: add an account with an e-mail
+sub createaccount() {
+ my($userid, $sex, $email, $passwd) = @_;
+ if ($userid eq "") {
+ if ($defaultlanguage eq "F") {
+ print "Entrez un nom de compte svp.\n";
+ print "<exemple> create nomtest Male mon\@email.com motdepassetest\n";
+ } else {
+ print "Please input an account name.\n";
+ print "<example> create testname Male my\@mail.com testpass\n";
+ }
+ return 136;
+ }
+ if (verify_accountname($userid) == 0) {
+ return 102;
+ }
+# if ($userid =~ /[^A-Za-z0-9\@-_]/) {
+# if ($defaultlanguage eq "F") {
+# print "Caract鑽e interdit trouv dans le nom du compte ".$`."[${&}]${'}\n";
+# } else {
+# print "Illegal character found in the account name ".$`."[${&}]${'}\n";
+# }
+# return 101;
+# }
+ $sex = uc(substr($sex, 0, 1));
+ if ($sex !~ /^[MF]$/) {
+ if ($defaultlanguage eq "F") {
+ print "Sexe incorrect [$sex]. Entrez M ou F svp.\n";
+ } else {
+ print "Illegal gender [$sex]. Please input M or F.\n";
+ }
+ return 103;
+ }
+ if (length($email) < 3) {
+ if ($defaultlanguage eq "F") {
+ print "Email trop courte [$email]. Entrez une e-mail valide svp.\n";
+ } else {
+ print "Email is too short [$email]. Please input a valid e-mail.\n";
+ }
+ return 109;
+ }
+ if (length($email) > 39) {
+ if ($defaultlanguage eq "F") {
+ print "Email trop longue [$email]. Entrez une e-mail de 39 caract鑽es maximum svp.\n";
+ } else {
+ print "Email is too long [$email]. Please input an e-mail with 39 bytes at the most.\n";
+ }
+ return 109;
+ }
+ if (verify_email($email) == 0) {
+ if ($defaultlanguage eq "F") {
+ print "Email incorrecte [$email]. Entrez une e-mail valide svp.\n";
+ } else {
+ print "Invalid email [$email]. Please input a valid e-mail.\n";
+ }
+ return 109;
+ }
+ if ($passwd eq "") {
+ return 108 if (($passwd = typepasswd()) eq "");
+ }
+ if (verify_password($passwd) == 0) {
+ return 104;
+ }
+ print $so pack("va24a24a1a40", 0x7930, $userid, $passwd, $sex, $email);
+ $so->flush();
+ $buf = readso(2);
+ if (unpack("v", $buf) != 0x7931) {
+ if ($defaultlanguage eq "F") {
+ print "Probl鑪e de connexion au serveur (r駱onse incorrecte).\n";
+ } else {
+ print "Connection error to the server (incorrect answer).\n";
+ }
+ return 106;
+ }
+ $buf = readso(28);
+ if (unpack("V", $buf) == -1 || unpack("V", $buf) == 4294967295) {
+ if ($defaultlanguage eq "F") {
+ print "Echec la cr饌tion du compte [$userid]. Un compte identique existe d駛.\n";
+ } else {
+ print "Account [$userid] creation failed. Same account already exists.\n";
+ }
+ return 107;
+ } else {
+ if ($defaultlanguage eq "F") {
+ printf "Compte [$userid] cr鳬 avec succ鑚 [id: %d].\n", unpack("V",$buf);
+ } else {
+ printf "Account [$userid] is successfully created [id: %d].\n", unpack("V",$buf);
+ }
+ }
+ return 0;
+}
+
+#--------------------------------------------------------------------------
+
+# Sub-function: deletion of an account
+sub delaccount() {
+ my($userid) = @_;
+ if ($userid eq "") {
+ if ($defaultlanguage eq "F") {
+ print "Entrez un nom de compte svp.\n";
+ print "<exemple> del nomtestasupprimer\n";
+ } else {
+ print "Please input an account name.\n";
+ print "<example> del testnametodelete\n";
+ }
+ return 136;
+ }
+ if (verify_accountname($userid) == 0) {
+ return 102;
+ }
+ if ($defaultlanguage eq "F") {
+ print "** Etes-vous vraiment sr de vouloir SUPPRIMER le compte [$userid]? (o/n) ";
+ } else {
+ print "** Are you really sure to DELETE account [$userid]? (y/n) ";
+ }
+ if (lc(substr(<STDIN>, 0, 1)) !~ /[oy]/) {
+ if ($defaultlanguage eq "F") {
+ print "Suppression annul馥\n.";
+ } else {
+ print "Deletion canceled\n";
+ }
+ return 121;
+ }
+ print $so pack("va24", 0x7932, $userid);
+ $so->flush();
+ $buf = readso(2);
+ if (unpack("v", $buf) != 0x7933) {
+ if ($defaultlanguage eq "F") {
+ print "Probl鑪e de connexion au serveur (r駱onse incorrecte).\n";
+ } else {
+ print "Connection error to the server (incorrect answer).\n";
+ }
+ return 122;
+ }
+ $buf = readso(28);
+ my($id2, $name) = unpack("Va24", $buf);
+ while (length($name) > 0 && substr($name, length($name)-1, 1) eq chr(0)) {
+ chop($name);
+ };
+ if ($id2 == -1 || $id2 == 4294967295) {
+ if ($defaultlanguage eq "F") {
+ print "Echec de la suppression du compte [$userid]. Le compte n'existe pas.\n";
+ } else {
+ print "Account [$userid] deletion failed. Account doesn't exist.\n";
+ }
+ return 123;
+ } else {
+ if ($defaultlanguage eq "F") {
+ print "Compte [$name][id: $id2] SUPPRIME avec succ鑚.\n";
+ } else {
+ print "Account [$name][id: $id2] is successfully DELETED.\n";
+ }
+ }
+ return 0;
+}
+
+#--------------------------------------------------------------------------
+
+# Sub-function: modification of a password
+sub changepasswd() {
+ my($userid, $passwd) = @_;
+ if ($userid eq "") {
+ if ($defaultlanguage eq "F") {
+ print "Entrez un nom de compte svp.\n";
+ print "<exemple> passwd nomtest nouveaumotdepasse\n";
+ } else {
+ print "Please input an account name.\n";
+ print "<example> passwd testname newpassword\n";
+ }
+ return 136;
+ }
+ if (verify_accountname($userid) == 0) {
+ return 102;
+ }
+ if ($passwd eq "") {
+ return 134 if (($passwd = typepasswd()) eq "");
+ }
+ if (verify_password($passwd) == 0) {
+ return 131;
+ }
+ print $so pack("va24a24", 0x7934, $userid,$passwd);
+ $so->flush();
+ $buf = readso(2);
+ if (unpack("v", $buf) != 0x7935) {
+ if ($defaultlanguage eq "F") {
+ print "Probl鑪e de connexion au serveur (r駱onse incorrecte).\n";
+ } else {
+ print "Connection error to the server (incorrect answer).\n";
+ }
+ return 132;
+ }
+ $buf = readso(28);
+ my($id2, $name) = unpack("Va24", $buf);
+ while (length($name) > 0 && substr($name, length($name)-1, 1) eq chr(0)) {
+ chop($name);
+ };
+ if ($id2 == -1 || $id2 == 4294967295) {
+ if ($defaultlanguage eq "F") {
+ print "Echec de la modification du mot de passe du compte [$userid].\n";
+ print "Le compte [$userid] n'existe pas.\n";
+ } else {
+ print "Account [$userid] password changing failed.\n";
+ print "Account [$userid] doesn't exist.\n";
+ }
+ return 133;
+ } else {
+ if ($defaultlanguage eq "F") {
+ print "Modification du mot de passe du compte [$name][id: $id2] r騏ssie.\n";
+ } else {
+ print "Account [$name][id: $id2] password successfully changed.\n";
+ }
+ }
+ return 130;
+}
+
+#--------------------------------------------------------------------------
+
+# Sub-function: modification of an account e-mail
+sub changeemail() {
+ my($userid, $email) = @_;
+ if ($userid eq "") {
+ if ($defaultlanguage eq "F") {
+ print "Entrez un nom de compte svp.\n";
+ print "<exemple> email testname nouveauemail\n";
+ } else {
+ print "Please input an account name.\n";
+ print "<example> email testname newemail\n";
+ }
+ return 136;
+ }
+ if (verify_accountname($userid) == 0) {
+ return 102;
+ }
+ if (length($email) < 3) {
+ if ($defaultlanguage eq "F") {
+ print "Email trop courte [$email]. Entrez une e-mail valide svp.\n";
+ } else {
+ print "Email is too short [$email]. Please input a valid e-mail.\n";
+ }
+ return 109;
+ }
+ if (length($email) > 39) {
+ if ($defaultlanguage eq "F") {
+ print "Email trop longue [$email]. Entrez une e-mail de 39 caract鑽es maximum svp.\n";
+ } else {
+ print "Email is too long [$email]. Please input an e-mail with 39 bytes at the most.\n";
+ }
+ return 109;
+ }
+ if (verify_email($email) == 0) {
+ if ($defaultlanguage eq "F") {
+ print "Email incorrect [$email]. Entrez une e-mail valide svp.\n";
+ } else {
+ print "Invalid email [$email]. Please input a valid e-mail.\n";
+ }
+ return 109;
+ }
+ print $so pack("va24a40", 0x7940, $userid, $email);
+ $so->flush();
+ $buf = readso(2);
+ if (unpack("v", $buf) != 0x7941) {
+ if ($defaultlanguage eq "F") {
+ print "Probl鑪e de connexion au serveur (r駱onse incorrecte).\n";
+ } else {
+ print "Connection error to the server (incorrect answer).\n";
+ }
+ return 162;
+ }
+ $buf = readso(28);
+ my($id2, $name) = unpack("Va24", $buf);
+ while (length($name) > 0 && substr($name, length($name)-1, 1) eq chr(0)) {
+ chop($name);
+ };
+ if ($id2 == -1 || $id2 == 4294967295) {
+ if ($defaultlanguage eq "F") {
+ print "Echec de la modification de l'e-mail du compte [$userid].\n";
+ print "Le compte [$userid] n'existe pas.\n";
+ } else {
+ print "Account [$userid] e-mail changing failed.\n";
+ print "Account [$userid] doesn't exist.\n";
+ }
+ return 133;
+ } else {
+ if ($defaultlanguage eq "F") {
+ print "Modification de l'e-mail du compte [$name][id: $id2] r騏ssie.\n";
+ } else {
+ print "Account [$name][id: $id2] e-mail successfully changed.\n";
+ }
+ }
+ return 160;
+}
+
+#--------------------------------------------------------------------------
+
+# Sub-function: search of accounts
+sub searchaccount() {
+ my($p1, $p2) = @_;
+ my($exp) = ("");
+ if ($p1 eq "-e" || $p1 eq "-r" || $p1 eq "--regex" || $p1 eq "--expr") {
+ if ($p2 eq "") {
+ if ($defaultlanguage eq "F") {
+ print "Entrez une expression r馮uli鑽e ou utilisez 'ls' pour avoir tous les comptes.\n";
+ } else {
+ print "Input a regular expression or use 'ls' to obtain all accounts.\n";
+ }
+ return 141;
+ }
+ $exp = $p2;
+ } else {
+ if ($p1 eq "") {
+ if ($defaultlanguage eq "F") {
+ print "Entrez une chane ou utilisez 'ls' pour avoir tous les comptes.\n";
+ } else {
+ print "Input a string or use 'ls' to obtain all accounts.\n";
+ }
+ return 141;
+ }
+ my($c) = 0;
+ $exp = lc($p1);
+ $exp =~ s/([\@])/\\$1/g;
+ $c += $exp =~ s/([\-\[\]])/\\$1/g;
+ $c += $exp =~ s/([\*\?])/.$1/g;
+ $c += $exp =~ s/\\\[(.)\\\-(.)\\\]/[$1-$2]/g;
+ $exp = "^$exp\$" if $c;
+ }
+ if (eval{ "" =~ /$exp/; }, $@) {
+ if ($defaultlanguage eq "F") {
+ print "Expression r馮uli鑽e non reconnue.\n";
+ } else {
+ print "Regular-Expression compiling failed.\n";
+ }
+ return 141;
+ }
+ my($i);
+ my($n, $st) = (0, 0);
+ # 0123456789 01 01234567890123456789012301234 012345 0123456789012345678901234567
+ if ($defaultlanguage eq "F") {
+ print " id_compte GM nom_utilisateur sexe count statut\n";
+ } else {
+ print "account_id GM user_name sex count state\n";
+ }
+ print "-------------------------------------------------------------------------------\n";
+ while(1) {
+ print $so pack("vV2", 0x7920, $st, 0);
+ $so->flush();
+ $buf = readso(4);
+ if (unpack("v", $buf) != 0x7921) {
+ if ($defaultlanguage eq "F") {
+ print "Probl鑪e de connexion au serveur (r駱onse incorrecte).\n";
+ } else {
+ print "Connection error to the server (incorrect answer).\n";
+ }
+ exit(10);
+ }
+ my($len) = unpack("x2v", $buf);
+ last if ($len <= 4);
+ for($i = 4; $i < $len; $i += 38) {
+ my(@dat) = unpack("VCa24cVV", readso(38));
+ $st = $dat[0] + 1;
+ next if (lc($dat[2]) !~ /$exp/);
+ printf "%10d %2s %-24s%-5s %6d %-27s\n", $dat[0],
+ ($dat[1] == 0 ? " " : $dat[1]),
+ $dat[2],
+ ($defaultlanguage eq "F" ? ("Femme","Male","Servr")[$dat[3]] : ("Femal","Male","Servr")[$dat[3]]),
+ $dat[4],
+ (($defaultlanguage eq "F" ? "Compte Ok" : "Account OK"),
+ "Unregistered ID",
+ "Incorrect Password",
+ "This ID is expired",
+ "Rejected from Server",
+ "Blocked by the GM Team", # You have been blocked by the GM Team
+ "Your EXE file is too old", # Your Game's EXE file is not the latest version
+ "Banishement or\n Prohibited to login until %s", # You are Prohibited to log in until %s
+ "Server is over populated", # Server is jammed due to over populated
+ "No MSG",
+ "This ID is totally erased")[$dat[5] == 100 ? 10 : $dat[5]]; # This ID has been totally erased
+ $n++;
+ }
+ }
+ if ($defaultlanguage eq "F") {
+ if ($n == 0) {
+ print "Aucun compte trouv.\n";
+ } elsif ($n == 1) {
+ print "1 compte trouv.\n";
+ } else {
+ print "$n comptes trouv駸.\n";
+ }
+ } else {
+ if ($n == 0) {
+ print "No account found.\n";
+ } elsif ($n == 1) {
+ print "1 account found.\n";
+ } else {
+ print "$n accounts found.\n";
+ }
+ }
+ return 0;
+}
+
+#--------------------------------------------------------------------------
+
+# Sub-function: modify the sex of an account
+sub changesex() {
+ my($userid, $sex) = @_;
+ if ($userid eq "" || !defined($userid)) {
+ if ($defaultlanguage eq "F") {
+ print "Entrez un nom de compte svp.\n";
+ print "<exemple> sex nomtest Male\n";
+ } else {
+ print "Please input an account name.\n";
+ print "<example> sex testname Male\n";
+ }
+ return 136;
+ }
+ if (verify_accountname($userid) == 0) {
+ return 102;
+ }
+# if ($userid =~ /[^A-Za-z0-9\@-_]/) {
+# if ($defaultlanguage eq "F") {
+# print "Caract鑽e interdit trouv dans le nom du compte ".$`."[${&}]${'}\n";
+# } else {
+# print "Illegal character found in the account name ".$`."[${&}]${'}\n";
+# }
+# return 101;
+# }
+ $sex = uc(substr($sex, 0, 1));
+ if ($sex !~ /^[MF]$/) {
+ if ($defaultlanguage eq "F") {
+ print "Sexe incorrect [$sex]. Entrez M ou F svp.\n";
+ } else {
+ print "Illegal gender [$sex]. Please input M or F.\n";
+ }
+ return 103;
+ }
+ print $so pack("va24a1", 0x793c, $userid, $sex);
+ $so->flush();
+ $buf = readso(2);
+ if (unpack("v", $buf) != 0x793d) {
+ if ($defaultlanguage eq "F") {
+ print "Probl鑪e de connexion au serveur (r駱onse incorrecte).\n";
+ } else {
+ print "Connection error to the server (incorrect answer).\n";
+ }
+ return 152;
+ }
+ $buf = readso(28);
+ my($id2, $name) = unpack("Va24", $buf);
+ while (length($name) > 0 && substr($name, length($name)-1, 1) eq chr(0)) {
+ chop($name);
+ };
+ if ($id2 == -1 || $id2 == 4294967295) {
+ if ($defaultlanguage eq "F") {
+ print "Echec du changement du sexe du compte [$userid].\n";
+ print "Le compte n'existe pas ou le sexe est d駛 celui demand.\n";
+ } else {
+ print "Account [$userid] sex changing failed.\n";
+ print "Account doesn't exist or the sex is already the good sex.\n";
+ }
+ } else {
+ if ($defaultlanguage eq "F") {
+ print "Sexe du compte [$name][id: $id2] chang avec succ鑚.\n";
+ } else {
+ print "Account [$name][id: $id2] sex successfully changed.\n";
+ }
+ }
+ return 0;
+}
+
+#--------------------------------------------------------------------------
+
+# Sub-function: modify the GM level of an account
+sub changegmlevel() {
+ my($userid, $gm_level) = @_;
+ if ($userid eq "" || !defined($userid)) {
+ if ($defaultlanguage eq "F") {
+ print "Entrez un nom de compte svp.\n";
+ print "<exemple> gm nomtest 80\n";
+ } else {
+ print "Please input an account name.\n";
+ print "<example> gm testname 80\n";
+ }
+ return 136;
+ }
+ if (verify_accountname($userid) == 0) {
+ return 102;
+ }
+# if ($userid =~ /[^A-Za-z0-9\@-_]/) {
+# if ($defaultlanguage eq "F") {
+# print "Caract鑽e interdit trouv dans le nom du compte ".$`."[${&}]${'}\n";
+# } else {
+# print "Illegal character found in the account name ".$`."[${&}]${'}\n";
+# }
+# return 101;
+# }
+ $gm_level = int($gm_level);
+ if ($gm_level < 0 || $gm_level > 99) {
+ if ($defaultlanguage eq "F") {
+ print "Niveau de GM incorrect [$gm_level]. Entrez une valeur de 0 99 svp.\n";
+ } else {
+ print "Illegal GM level [$gm_level]. Please input a value from 0 to 99.\n";
+ }
+ return 103;
+ }
+ print $so pack("va24C", 0x793e, $userid, $gm_level);
+ $so->flush();
+ $buf = readso(2);
+ if (unpack("v", $buf) != 0x793f) {
+ if ($defaultlanguage eq "F") {
+ print "Probl鑪e de connexion au serveur (r駱onse incorrecte).\n";
+ } else {
+ print "Connection error to the server (incorrect answer).\n";
+ }
+ return 152;
+ }
+ $buf = readso(28);
+ my($id2, $name) = unpack("Va24", $buf);
+ while (length($name) > 0 && substr($name, length($name)-1, 1) eq chr(0)) {
+ chop($name);
+ };
+ if ($id2 == -1 || $id2 == 4294967295) {
+ if ($defaultlanguage eq "F") {
+ print "Echec du changement du niveau de GM du compte [$userid].\n";
+ print "Le compte n'existe pas, le niveau de GM est d駛 celui demand,\n";
+ print "ou il est impossible de modifier le fichier des comptes GM.\n";
+ } else {
+ print "Account [$userid] GM level changing failed.\n";
+ print "Account doesn't exist, the GM level is already the good GM level,\n";
+ print "or it's impossible to modify the GM accounts file.\n";
+ }
+ } else {
+ if ($defaultlanguage eq "F") {
+ print "Niveau de GM du compte [$name][id: $id2] chang avec succ鑚.\n";
+ } else {
+ print "Account [$name][id: $id2] GM level successfully changed.\n";
+ }
+ }
+ return 0;
+}
+
+#--------------------------------------------------------------------------
+
+# Sub-function: Modification of a state
+sub changestate {
+ my($userid, $s, $error_message) = @_;
+ # Valid values: 0: ok, or value of the 0x006a packet + 1
+ if ($s eq "" || (($s < 0 || $s > 9) && $s != 100)) {
+ if ($defaultlanguage eq "F") {
+ print "Entrez une des valeurs suivantes svp:\n";
+ print " 0 = Compte ok 6 = Your Game's EXE file is not the latest version\n";
+ } else {
+ print "Please input one of these values:\n";
+ print " 0 = Account ok 6 = Your Game's EXE file is not the latest version\n";
+ }
+ print " 1 = Unregistered ID 7 = You are Prohibited to log in until %s\n";
+ print " 2 = Incorrect Password 8 = Server is jammed due to over populated\n";
+ print " 3 = This ID is expired 9 = No MSG\n";
+ print " 4 = Rejected from Server 100 = This ID has been totally erased\n";
+ print " 5 = You have been blocked by the GM Team\n";
+ if ($defaultlanguage eq "F") {
+ print "<exemples> state nomtest 5\n";
+ print " state nomtest 7 fin de votre ban\n";
+ print " block <nom du compte>\n";
+ print " unblock <nom du compte>\n";
+ } else {
+ print "<examples> state testname 5\n";
+ print " state testname 7 end of your ban\n";
+ print " block <account name>\n";
+ print " unblock <account name>\n";
+ }
+ return 151;
+ }
+ if ($userid eq "") {
+ if ($defaultlanguage eq "F") {
+ print "Entrez un nom de compte svp.\n";
+ print "<exemples> state nomtest 5\n";
+ print " state nomtest 7 fin de votre ban\n";
+ print " block <nom du compte>\n";
+ print " unblock <nom du compte>\n";
+ } else {
+ print "Please input an account name.\n";
+ print "<examples> state testname 5\n";
+ print " state testname 7 end of your ban\n";
+ print " block <account name>\n";
+ print " unblock <account name>\n";
+ }
+ return 136;
+ }
+ if (verify_accountname($userid) == 0) {
+ return 102;
+ }
+ if ($s != 7) {
+ $error_message = "-";
+ } else {
+ if (length($error_message) < 1) {
+ if ($defaultlanguage eq "F") {
+ print "Message d'erreur trop court. Entrez un message de 1-19 caract鑽es.\n";
+ } else {
+ print "Error message is too short. Please input a message of 1-19 bytes.\n";
+ }
+ return 102;
+ }
+ if (length($error_message) > 19) {
+ if ($defaultlanguage eq "F") {
+ print "Message d'erreur trop long. Entrez un message de 1-19 caract鑽es.\n";
+ } else {
+ print "Error message is too long. Please input a message of 1-19 bytes.\n";
+ }
+ return 102;
+ }
+ }
+ print $so pack("va24Va20", 0x7936, $userid, $s, $error_message);
+ $so->flush();
+ $buf = readso(2);
+ if (unpack("v", $buf) != 0x7937) {
+ if ($defaultlanguage eq "F") {
+ print "Probl鑪e de connexion au serveur (r駱onse incorrecte).\n";
+ } else {
+ print "Connection error to the server (incorrect answer).\n";
+ }
+ return 152;
+ }
+ $buf = readso(32);
+ my(@dat) = unpack("Va24V", $buf);
+ while (length($dat[1]) > 0 && substr($dat[1], length($dat[1])-1, 1) eq chr(0)) {
+ chop($dat[1]);
+ };
+ if ($dat[0] != -1 && $dat[0] != 4294967295) {
+ if ($defaultlanguage eq "F") {
+ print "Statut du compte [$dat[1]][id: $dat[0]] chang avec succ鑚 en [";
+ } else {
+ print "Account [$dat[1]][id: $dat[0]] state successfully changed in [";
+ }
+ print ((($defaultlanguage eq "F" ? "Compte Ok" : "Account OK"),
+ "Unregistered ID",
+ "Incorrect Password",
+ "This ID is expired",
+ "Rejected from Server",
+ "You have been blocked by the GM Team",
+ "Your Game's EXE file is not the latest version",
+ "You are Prohibited to log in until %s",
+ "Server is jammed due to over populated",
+ "No MSG",
+ "This ID has been totally erased")[$dat[2] == 100 ? 10 : $dat[2]]);
+ print "].\n";
+ } else {
+ if ($defaultlanguage eq "F") {
+ print "Echec du changement du statut du compte [$userid]. Le compte n'existe pas.\n";
+ } else {
+ print "Account [$userid] state changing failed. Account doesn't exist.\n";
+ }
+ }
+}
+
+#--------------------------------------------------------------------------
+
+# Sub-function: Displaying of the number of online players
+sub getlogincount {
+ # Request to the login-server
+ print $so pack("v", 0x7938);
+ $so->flush();
+
+ $buf = readso(4);
+ # Connection failed
+ if (unpack("v", $buf) != 0x7939) {
+ if ($defaultlanguage eq "F") {
+ print "Probl鑪e de connexion au serveur (r駱onse incorrecte).\n";
+ } else {
+ print "Connection error to the server (incorrect answer).\n";
+ }
+ exit(3);
+ }
+
+ # Get length of the received packet
+ my($len) = unpack("x2v", $buf) - 4;
+
+ # Read information of the servers
+ if ($len < 1) {
+ if ($defaultlanguage eq "F") {
+ printf " Aucun serveur n'est connect au login serveur.\n";
+ } else {
+ printf " No server is connected to the login-server.\n";
+ }
+ } else {
+ my(@slist) = ();
+ for(; $len > 0; $len -= 32) {
+ my($name, $count) = unpack("x6 a20 V", readso(32));
+ $name = substr($name, 0, index($name, "\0"));
+ push @slist, [ $name, $count ];
+ }
+ # Displaying of result
+ my($i);
+ if ($defaultlanguage eq "F") {
+ printf " Nombre de joueurs en ligne (serveur: nb):\n";
+ } else {
+ printf " Number of online players (server: number).\n";
+ }
+ foreach $i(@slist) {
+ printf " %-20s : %5d\n", $i->[0], $i->[1];
+ }
+ }
+}
+
+#--------------------------------------------------------------------------
+
+# Sub-function: Modification of a memo field
+sub changememo {
+ my($userid, $memo) = @_;
+ if ($userid eq "") {
+ if ($defaultlanguage eq "F") {
+ print "Entrez un nom de compte svp.\n";
+ print "<exemple> memo nomtest nouveau memo\n";
+ } else {
+ print "Please input an account name.\n";
+ print "<example> memo testname new memo\n";
+ }
+ return 136;
+ }
+ if (verify_accountname($userid) == 0) {
+ return 102;
+ }
+ if (length($memo) > 254) {
+ if ($defaultlanguage eq "F") {
+ print "M駑o trop long (".length($memo)." caract鑽es).\n";
+ print "Entrez un m駑o de 254 caract鑽es maximum svp.\n";
+ } else {
+ print "Memo is too long (".length($memo)." characters).\n";
+ print "Please input a memo of 254 bytes at the maximum.\n";
+ }
+ return 102;
+ }
+ if (length($memo) == 0) {
+ print $so pack("va24v", 0x7942, $userid, 0);
+ } else {
+ print $so pack("va24va".length($memo), 0x7942, $userid, length($memo), $memo);
+ }
+ $so->flush();
+ $buf = readso(2);
+ if (unpack("v", $buf) != 0x7943) {
+ if ($defaultlanguage eq "F") {
+ print "Probl鑪e de connexion au serveur (r駱onse incorrecte).\n";
+ } else {
+ print "Connection error to the server (incorrect answer).\n";
+ }
+ return 152;
+ }
+ $buf = readso(28);
+ my($id2, $name) = unpack("Va24", $buf);
+ while (length($name) > 0 && substr($name, length($name)-1, 1) eq chr(0)) {
+ chop($name);
+ };
+ if ($id2 == -1 || $id2 == 4294967295) {
+ if ($defaultlanguage eq "F") {
+ print "Echec du changement du m駑o du compte [$userid]. Le compte n'existe pas.\n";
+ } else {
+ print "Account [$userid] memo changing failed. Account doesn't exist.\n";
+ }
+ } else {
+ if ($defaultlanguage eq "F") {
+ print "M駑o du compte [$name][id: $id2] chang avec succ鑚.\n";
+ } else {
+ print "Account [$name][id: $id2] memo successfully changed.\n";
+ }
+ }
+}
+
+#--------------------------------------------------------------------------
+
+# Sub-function: Request to obtain an account id
+sub idaccount() {
+ my($userid) = @_;
+ if ($userid eq "") {
+ if ($defaultlanguage eq "F") {
+ print "Entrez un nom de compte svp.\n";
+ print "<exemple> id nomtest\n";
+ } else {
+ print "Please input an account name.\n";
+ print "<example> id testname\n";
+ }
+ return 136;
+ }
+ if (verify_accountname($userid) == 0) {
+ return 102;
+ }
+ print $so pack("va24", 0x7944, $userid);
+ $so->flush();
+ $buf = readso(2);
+ if (unpack("v", $buf) != 0x7945) {
+ if ($defaultlanguage eq "F") {
+ print "Probl鑪e de connexion au serveur (r駱onse incorrecte).\n";
+ } else {
+ print "Connection error to the server (incorrect answer).\n";
+ }
+ return 122;
+ }
+ $buf = readso(28);
+ my($id2, $name) = unpack("Va24", $buf);
+ while (length($name) > 0 && substr($name, length($name)-1, 1) eq chr(0)) {
+ chop($name);
+ };
+ if ($id2 == -1 || $id2 == 4294967295) {
+ if ($defaultlanguage eq "F") {
+ print "Impossible de trouver l'id du compte [$userid]. Le compte n'existe pas.\n";
+ } else {
+ print "Unabled to find the account [$userid] id. Account doesn't exist.\n";
+ }
+ return 123;
+ } else {
+ if ($defaultlanguage eq "F") {
+ print "Le compte [$name] a pour id: $id2.\n";
+ } else {
+ print "The account [$name] have the id: $id2.\n";
+ }
+ }
+ return 0;
+}
+
+#--------------------------------------------------------------------------
+
+# Sub-function: Request to obtain an account name
+sub nameaccount() {
+ my($id) = @_;
+ if ($id < 0) {
+ if ($defaultlanguage eq "F") {
+ print "Entrez un id ayant une valeur positive svp.\n";
+ } else {
+ print "Please input a positive value for the id.\n";
+ }
+ return 136;
+ }
+ print $so pack("vV", 0x7946, $id);
+ $so->flush();
+ $buf = readso(2);
+ if (unpack("v", $buf) != 0x7947) {
+ if ($defaultlanguage eq "F") {
+ print "Probl鑪e de connexion au serveur (r駱onse incorrecte).\n";
+ } else {
+ print "Connection error to the server (incorrect answer).\n";
+ }
+ return 122;
+ }
+ $buf = readso(28);
+ my($id2, $name) = unpack("Va24", $buf);
+ while (length($name) > 0 && substr($name, length($name)-1, 1) eq chr(0)) {
+ chop($name);
+ };
+ if (length($name) == 0 || $name eq "") {
+ if ($defaultlanguage eq "F") {
+ print "Impossible de trouver le nom du compte [id: $id2]. Le compte n'existe pas.\n";
+ } else {
+ print "Unabled to find the account [id: $id2] name. Account doesn't exist.\n";
+ }
+ return 123;
+ } else {
+ if ($defaultlanguage eq "F") {
+ print "Le compte [id: $id2] a pour nom: $name.\n";
+ } else {
+ print "The account [id: $id2] have the name: $name.\n";
+ }
+ }
+ return 0;
+}
+
+#--------------------------------------------------------------------------
+
+# Sub-function: Set a validity limit of an account
+sub timesetaccount() {
+ my($userid, $date, $time) = @_;
+ if ($userid eq "") {
+ if ($defaultlanguage eq "F") {
+ print "Entrez un nom de compte svp.\n";
+ print "<exemple>: timeset <nom_du_compte> aaaa/mm/jj [hh:mm:ss]\n";
+ print " timeset <nom_du_compte> 0 (0 = illimit)\n";
+ printf " Heure par d馭aut [hh:mm:ss]: 23:59:59\n";
+ } else {
+ print "Please input an account name.\n";
+ print "<example>: timeset <account_name> yyyy/mm/dd [hh:mm:ss]\n";
+ print " timeset <account_name> 0 (0 = unlimited)\n";
+ printf " Default time [hh:mm:ss]: 23:59:59\n";
+ }
+ return 136;
+ }
+ if (verify_accountname($userid) == 0) {
+ return 102;
+ }
+ my($year, $month, $day) = split(/[.\-\/]/, $date);
+ my($hour, $minute, $second) = split(/:/, $time);
+ if ($time eq "") {
+ $hour = 23;
+ $minute = 59;
+ $second = 59;
+ }
+ my($timestamp);
+ if ($year eq "" ||
+ ($year != 0 && ($month eq "" || $day eq "" || $hour eq "" || $minute eq "" || $second eq ""))) {
+ if ($defaultlanguage eq "F") {
+ print "Entrez 0 ou une date et une heure svp (format: 0 ou aaaa/mm/jj hh:mm:ss).\n";
+ } else {
+ print "Please input 0 or a date and a time (format: 0 or yyyy/mm/dd hh:mm:ss).\n";
+ }
+ return 102;
+ }
+ if ($year == 0) {
+ $timestamp = 0;
+ } else {
+ if ($year < 70) {
+ $year = $year + 100;
+ }
+ if ($year >= 1900) {
+ $year = $year - 1900;
+ }
+ if ($month < 1 || $month > 12) {
+ if ($defaultlanguage eq "F") {
+ print "Entrez un mois correct svp (entre 1 et 12).\n";
+ } else {
+ print "Please give a correct value for the month (from 1 to 12).\n";
+ }
+ return 102;
+ }
+ $month = $month - 1;
+ if ($day < 1 || $day > 31) {
+ if ($defaultlanguage eq "F") {
+ print "Entrez un jour correct svp (entre 1 et 31).\n";
+ } else {
+ print "Please give a correct value for the day (from 1 to 31).\n";
+ }
+ return 102;
+ }
+ if ((($month == 3 || $month == 5 || $month == 8 || $month == 10) && $day > 30) ||
+ ($month == 1 && $day > 29)) {
+ if ($defaultlanguage eq "F") {
+ print "Entrez un jour correct en fonction du mois svp.\n";
+ } else {
+ print "Please give a correct value for a day of this month.\n";
+ }
+ return 102;
+ }
+ if ($hour < 0 || $hour > 23) {
+ if ($defaultlanguage eq "F") {
+ print "Entrez une heure correcte svp (entre 0 et 23).\n";
+ } else {
+ print "Please give a correct value for the hour (from 0 to 23).\n";
+ }
+ return 102;
+ }
+ if ($minute < 0 || $minute > 59) {
+ if ($defaultlanguage eq "F") {
+ print "Entrez des minutes correctes svp (entre 0 et 59).\n";
+ } else {
+ print "Please give a correct value for the minutes (from 0 to 59).\n";
+ }
+ return 102;
+ }
+ if ($second < 0 || $second > 59) {
+ if ($defaultlanguage eq "F") {
+ print "Entrez des secondes correctes svp (entre 0 et 59).\n";
+ } else {
+ print "Please give a correct value for the seconds (from 0 to 59).\n";
+ }
+ return 102;
+ }
+ $timestamp = POSIX::mktime($second, $minute, $hour, $day, $month, $year, 0, 0, -1); # -1: no winter/summer time modification
+ if ($timestamp == undef) {
+ if ($defaultlanguage eq "F") {
+ print "Date incorrecte.\n";
+ print "Ajoutez 0 ou une date et une heure svp (format: 0 ou aaaa/mm/jj hh:mm:ss).\n";
+ } else {
+ print "Invalid date.\n";
+ print "Please add 0 or a date and a time (format: 0 or yyyy/mm/dd hh:mm:ss).\n";
+ }
+ return 102;
+ }
+ }
+
+ print $so pack("va24V", 0x7948, $userid, $timestamp);
+ $so->flush();
+ $buf = readso(2);
+ if (unpack("v", $buf) != 0x7949) {
+ if ($defaultlanguage eq "F") {
+ print "Probl鑪e de connexion au serveur (r駱onse incorrecte).\n";
+ } else {
+ print "Connection error to the server (incorrect answer).\n";
+ }
+ return 152;
+ }
+ $buf = readso(32);
+ my(@dat) = unpack("Va24V", $buf);
+ while (length($dat[1]) > 0 && substr($dat[1], length($dat[1])-1, 1) eq chr(0)) {
+ chop($dat[1]);
+ };
+ if ($dat[0] != -1 && $dat[0] != 4294967295) {
+ if ($defaultlanguage eq "F") {
+ print "Limite de validit du compte [$dat[1]][id: $dat[0]] chang馥 avec succ鑚 ".
+ ($dat[2] == 0 ? "en [illimit饐.\n" : "pour 黎re jusqu'au ".(POSIX::ctime($dat[2])));
+ } else {
+ print "Validity Limit of the account [$dat[1]][id: $dat[0]] successfully changed ".
+ ($dat[2] == 0 ? "to [unlimited].\n" : "to be until ".(POSIX::ctime($dat[2])));
+ }
+ # localtime($dat[2]) is also possible to display instead of POSIX::ctime.
+ } else {
+ if ($defaultlanguage eq "F") {
+ print "Echec du changement de la validit du compte [$userid]. Le compte n'existe pas.\n";
+ } else {
+ print "Account [$userid] validity limit changing failed. Account doesn't exist.\n";
+ }
+ }
+
+ return 0;
+}
+
+#--------------------------------------------------------------------------
+
+# Sub-function: Add/substract time to the validity limit of an account
+sub timeaddaccount() {
+ my($userid, $modif) = @_;
+ if ($userid eq "") {
+ if ($defaultlanguage eq "F") {
+ print "Entrez un nom de compte svp.\n";
+ print " <exemple> timeadd nomtest +1m-2mn1s-6y\n";
+ print " Cette exemple ajoute 1 mois et 1 seconde, et soustrait 2 minutes\n";
+ print " et 6 ans dans le m麥e temps.\n";
+ } else {
+ print "Please input an account name.\n";
+ print " <example> timeadd testname +1m-2mn1s-6y\n";
+ print " this example adds 1 month and 1 second, and substracts 2 minutes\n";
+ print " and 6 years at the same time.\n";
+ }
+ return 136;
+ }
+ if (verify_accountname($userid) == 0) {
+ return 102;
+ }
+ my($year, $month, $day) = (0, 0 ,0);
+ my($hour, $minute, $second) = (0, 0 ,0);
+
+ $modif = lc($modif);
+ while (length($modif) > 0) {
+ my($value) = int($modif);
+ if ($value == 0) {
+ $modif = substr($modif, 1);
+ } else {
+ if (substr($modif, 0, 1) =~ /[\-\+]/) {
+ $modif = substr($modif, 1);
+ }
+ while (length($modif) > 0 && substr($modif, 0, 1) =~ /[0-9]/) {
+ $modif = substr($modif, 1);
+ }
+ if (index($modif, "s") == 0) {
+ $second = $value;
+ $modif = substr($modif, 1);
+ } elsif (index($modif, "mn") == 0) {
+ $minute = $value;
+ $modif = substr($modif, 2);
+ } elsif (index($modif, "h") == 0) {
+ $hour = $value;
+ $modif = substr($modif, 1);
+ } elsif (index($modif, "d") == 0 || index($modif, "j") == 0) {
+ $day = $value;
+ $modif = substr($modif, 1);
+ } elsif (index($modif, "m") == 0) {
+ $month = $value;
+ $modif = substr($modif, 1);
+ } elsif (index($modif, "y") == 0 || index($modif, "a") == 0) {
+ $year = $value;
+ $modif = substr($modif, 1);
+ } else {
+ $modif = substr($modif, 1);
+ }
+ }
+ }
+
+ if ($defaultlanguage eq "F") {
+ print " ann馥: $year\n";
+ print " mois: $month\n";
+ print " jour: $day\n";
+ print " heure: $hour\n";
+ print " minute: $minute\n";
+ print " seconde: $second\n";
+ } else {
+ print " year: $year\n";
+ print " month: $month\n";
+ print " day: $day\n";
+ print " hour: $hour\n";
+ print " minute: $minute\n";
+ print " second: $second\n";
+ }
+
+ if ($year == 0 && $month == 0 && $day == 0 && $hour == 0 && $minute == 0 && $second == 0) {
+ if ($defaultlanguage eq "F") {
+ print "Vous devez entrer un ajustement avec cette commande, svp:\n";
+ print " Valeur d'ajustement (-1, 1, +1, etc...)\n";
+ print " Element modifi:\n";
+ print " a ou y: ann馥\n";
+ print " m: mois\n";
+ print " j ou d: jour\n";
+ print " h: heure\n";
+ print " mn: minute\n";
+ print " s: seconde\n";
+ print " <exemple> timeadd nomtest +1m-2mn1s-6y\n";
+ print " Cette exemple ajoute 1 mois et 1 seconde, et soustrait 2 minutes\n";
+ print " et 6 ans dans le m麥e temps.\n";
+ } else {
+ print "Please give an adjustment with this command:\n";
+ print " Adjustment value (-1, 1, +1, etc...)\n";
+ print " Modified element:\n";
+ print " a or y: year\n";
+ print " m: month\n";
+ print " j or d: day\n";
+ print " h: hour\n";
+ print " mn: minute\n";
+ print " s: second\n";
+ print " <example> timeadd testname +1m-2mn1s-6y\n";
+ print " this example adds 1 month and 1 second, and substracts 2 minutes\n";
+ print " and 6 years at the same time.\n";
+ }
+ return 137;
+ }
+ if ($year > 127 || $year < -127) {
+ if ($defaultlanguage eq "F") {
+ print "Entrez un ajustement d'ann馥s correct (de -127 127), svp.\n";
+ } else {
+ print "Please give a correct adjustment for the years (from -127 to 127).\n";
+ }
+ return 137;
+ }
+ if ($month > 255 || $month < -255) {
+ if ($defaultlanguage eq "F") {
+ print "Entrez un ajustement de mois correct (de -255 255), svp.\n";
+ } else {
+ print "Please give a correct adjustment for the months (from -255 to 255).\n";
+ }
+ return 137;
+ }
+ if ($day > 32767 || $day < -32767) {
+ if ($defaultlanguage eq "F") {
+ print "Entrez un ajustement de jours correct (de -32767 32767), svp.\n";
+ } else {
+ print "Please give a correct adjustment for the days (from -32767 to 32767).\n";
+ }
+ return 137;
+ }
+ if ($hour > 32767 || $hour < -32767) {
+ if ($defaultlanguage eq "F") {
+ print "Entrez un ajustement d'heures correct (de -32767 32767), svp.\n";
+ } else {
+ print "Please give a correct adjustment for the hours (from -32767 to 32767).\n";
+ }
+ return 137;
+ }
+ if ($minute > 32767 || $minute < -32767) {
+ if ($defaultlanguage eq "F") {
+ print "Entrez un ajustement de minutes correct (de -32767 32767), svp.\n";
+ } else {
+ print "Please give a correct adjustment for the minutes (from -32767 to 32767).\n";
+ }
+ return 137;
+ }
+ if ($second > 32767 || $second < -32767) {
+ if ($defaultlanguage eq "F") {
+ print "Entrez un ajustement de secondes correct (de -32767 32767), svp.\n";
+ } else {
+ print "Please give a correct adjustment for the seconds (from -32767 to 32767).\n";
+ }
+ return 137;
+ }
+
+ print $so pack("va24vvvvvv", 0x7950, $userid, $year, $month, $day, $hour, $minute, $second);
+ $so->flush();
+ $buf = readso(2);
+ if (unpack("v", $buf) != 0x7951) {
+ if ($defaultlanguage eq "F") {
+ print "Probl鑪e de connexion au serveur (r駱onse incorrecte).\n";
+ } else {
+ print "Connection error to the server (incorrect answer).\n";
+ }
+ return 152;
+ }
+ $buf = readso(32);
+ my(@dat) = unpack("Va24V", $buf);
+ while (length($dat[1]) > 0 && substr($dat[1], length($dat[1])-1, 1) eq chr(0)) {
+ chop($dat[1]);
+ };
+ if ($dat[0] == -1 || $dat[0] == 4294967295) {
+ if ($defaultlanguage eq "F") {
+ print "Echec du changement de la validit du compte [$userid]. Le compte n'existe pas.\n";
+ } else {
+ print "Account [$userid] validity limit changing failed. Account doesn't exist.\n";
+ }
+ } elsif ($dat[2] == 0) {
+ if ($defaultlanguage eq "F") {
+ print "Limite de validit du compte [$dat[1]][id: $dat[0]] inchang馥.\n";
+ print "Le compte a une validit illimit馥 ou\n";
+ print "la modification est impossible avec les ajustements demand駸.\n";
+ } else {
+ print "Validity limit of the account [$dat[1]][id: $dat[0]] unchanged.\n";
+ print "The account have an unlimited validity limit or\n";
+ print "the changing is impossible with the proposed adjustments.\n";
+ }
+ } else {
+ if ($defaultlanguage eq "F") {
+ print "Limite de validit du compte [$dat[1]][id: $dat[0]] chang馥 avec succ鑚 ".
+ ($dat[2] == 0 ? "en [illimit饐.\n" : "pour 黎re jusqu'au ".(POSIX::ctime($dat[2])));
+ } else {
+ print "Validity limit of the account [$dat[1]][id: $dat[0]] successfully changed ".
+ ($dat[2] == 0 ? "to [unlimited].\n" : "to be until ".(POSIX::ctime($dat[2])));
+ }
+ # localtime($dat[2]) is also possible to display instead of POSIX::ctime.
+ }
+
+ return 0;
+}
+
+#--------------------------------------------------------------------------
+
+# Sub-function: Set the final date of a banishment of an account
+sub bansetaccount() {
+ my($userid, $date, $time) = @_;
+ if ($userid eq "") {
+ if ($defaultlanguage eq "F") {
+ print "Entrez un nom de compte svp.\n";
+ print "<exemple>: banset <nom_du_compte> aaaa/mm/jj [hh:mm:ss]\n";
+ print " banset <nom_du_compte> 0 (0 = d-bani)\n";
+ print " ban/banish aaaa/mm/jj hh:mm:ss <nom du compte>\n";
+ print " unban/unbanish <nom du compte>\n";
+ printf " Heure par d馭aut [hh:mm:ss]: 23:59:59\n";
+ } else {
+ print "Please input an account name.\n";
+ print "<example>: banset <account_name> yyyy/mm/dd [hh:mm:ss]\n";
+ print " banset <account_name> 0 (0 = un-banished)\n";
+ print " ban/banish yyyy/mm/dd hh:mm:ss <account name>\n";
+ print " unban/unbanish <account name>\n";
+ printf " Default time [hh:mm:ss]: 23:59:59\n";
+ }
+ return 136;
+ }
+ if (verify_accountname($userid) == 0) {
+ return 102;
+ }
+ my($year, $month, $day) = split(/[.\-\/]/, $date);
+ my($hour, $minute, $second) = split(/:/, $time);
+ if ($time eq "") {
+ $hour = 23;
+ $minute = 59;
+ $second = 59;
+ }
+ my($timestamp);
+ if ($year eq "" ||
+ ($year != 0 && ($month eq "" || $day eq "" || $hour eq "" || $minute eq "" || $second eq ""))) {
+ if ($defaultlanguage eq "F") {
+ print "Entrez 0 ou une date et une heure svp (format: 0 ou aaaa/mm/jj hh:mm:ss).\n";
+ } else {
+ print "Please input 0 or a date and a time (format: 0 or yyyy/mm/dd hh:mm:ss).\n";
+ }
+ return 102;
+ }
+ if ($year == 0) {
+ $timestamp = 0;
+ } else {
+ if ($year < 70) {
+ $year = $year + 100;
+ }
+ if ($year >= 1900) {
+ $year = $year - 1900;
+ }
+ if ($month < 1 || $month > 12) {
+ if ($defaultlanguage eq "F") {
+ print "Entrez un mois correct svp (entre 1 et 12).\n";
+ } else {
+ print "Please give a correct value for the month (from 1 to 12).\n";
+ }
+ return 102;
+ }
+ $month = $month - 1;
+ if ($day < 1 || $day > 31) {
+ if ($defaultlanguage eq "F") {
+ print "Entrez un jour correct svp (entre 1 et 31).\n";
+ } else {
+ print "Please give a correct value for the day (from 1 to 31).\n";
+ }
+ return 102;
+ }
+ if ((($month == 3 || $month == 5 || $month == 8 || $month == 10) && $day > 30) ||
+ ($month == 1 && $day > 29)) {
+ if ($defaultlanguage eq "F") {
+ print "Entrez un jour correct en fonction du mois svp.\n";
+ } else {
+ print "Please give a correct value for a day of this month.\n";
+ }
+ return 102;
+ }
+ if ($hour < 0 || $hour > 23) {
+ if ($defaultlanguage eq "F") {
+ print "Entrez une heure correcte svp (entre 0 et 23).\n";
+ } else {
+ print "Please give a correct value for the hour (from 0 to 23).\n";
+ }
+ return 102;
+ }
+ if ($minute < 0 || $minute > 59) {
+ if ($defaultlanguage eq "F") {
+ print "Entrez des minutes correctes svp (entre 0 et 59).\n";
+ } else {
+ print "Please give a correct value for the minutes (from 0 to 59).\n";
+ }
+ return 102;
+ }
+ if ($second < 0 || $second > 59) {
+ if ($defaultlanguage eq "F") {
+ print "Entrez des secondes correctes svp (entre 0 et 59).\n";
+ } else {
+ print "Please give a correct value for the seconds (from 0 to 59).\n";
+ }
+ return 102;
+ }
+ $timestamp = POSIX::mktime($second, $minute, $hour, $day, $month, $year, 0, 0, -1); # -1: no winter/summer time modification
+ if ($timestamp == undef) {
+ if ($defaultlanguage eq "F") {
+ print "Date incorrecte.\n";
+ print "Ajoutez 0 ou une date et une heure svp (format: 0 ou aaaa/mm/jj hh:mm:ss).\n";
+ } else {
+ print "Invalid date.\n";
+ print "Please add 0 or a date and a time (format: 0 or yyyy/mm/dd hh:mm:ss).\n";
+ }
+ return 102;
+ }
+ }
+
+ print $so pack("va24V", 0x794a, $userid, $timestamp);
+ $so->flush();
+ $buf = readso(2);
+ if (unpack("v", $buf) != 0x794b) {
+ if ($defaultlanguage eq "F") {
+ print "Probl鑪e de connexion au serveur (r駱onse incorrecte).\n";
+ } else {
+ print "Connection error to the server (incorrect answer).\n";
+ }
+ return 152;
+ }
+ $buf = readso(32);
+ my(@dat) = unpack("Va24V", $buf);
+ while (length($dat[1]) > 0 && substr($dat[1], length($dat[1])-1, 1) eq chr(0)) {
+ chop($dat[1]);
+ };
+ if ($dat[0] != -1 && $dat[0] != 4294967295) {
+ if ($defaultlanguage eq "F") {
+ print "Date finale de banissement du compte [$dat[1]][id: $dat[0]] chang馥 avec succ鑚 ".
+ ($dat[2] == 0 ? "en [d-bannie].\n" : "pour 黎re jusqu'au ".(POSIX::ctime($dat[2])));
+ } else {
+ print "Final date of banishment of the account [$dat[1]][id: $dat[0]] successfully changed ".
+ ($dat[2] == 0 ? "to [unbanished].\n" : "to be until ".(POSIX::ctime($dat[2])));
+ }
+ # localtime($dat[2]) is also possible to display instead of POSIX::ctime.
+ } else {
+ if ($defaultlanguage eq "F") {
+ print "Echec du changement de la date finale de banissement du compte [$userid]. Le compte n'existe pas.\n";
+ } else {
+ print "Account [$userid] final date of banishment changing failed. Account doesn't exist.\n";
+ }
+ }
+
+ return 0;
+}
+
+#--------------------------------------------------------------------------
+
+# Sub-function: Add/substract time to the final date of a banishment of an account
+sub banaddaccount() {
+ my($userid, $modif) = @_;
+ if ($userid eq "") {
+ if ($defaultlanguage eq "F") {
+ print "Entrez un nom de compte svp.\n";
+ print " <exemple> banadd nomtest +1m-2mn1s-6y\n";
+ print " Cette exemple ajoute 1 mois et 1 seconde, et soustrait 2 minutes\n";
+ print " et 6 ans dans le m麥e temps.\n";
+ } else {
+ print "Please input an account name.\n";
+ print " <example> banadd testname +1m-2mn1s-6y\n";
+ print " this example adds 1 month and 1 second, and substracts 2 minutes\n";
+ print " and 6 years at the same time.\n";
+ }
+ return 136;
+ }
+ if (verify_accountname($userid) == 0) {
+ return 102;
+ }
+ my($year, $month, $day) = (0, 0 ,0);
+ my($hour, $minute, $second) = (0, 0 ,0);
+
+ $modif = lc($modif);
+ while (length($modif) > 0) {
+ my($value) = int($modif);
+ if ($value == 0) {
+ $modif = substr($modif, 1);
+ } else {
+ if (substr($modif, 0, 1) =~ /[\-\+]/) {
+ $modif = substr($modif, 1);
+ }
+ while (length($modif) > 0 && substr($modif, 0, 1) =~ /[0-9]/) {
+ $modif = substr($modif, 1);
+ }
+ if (index($modif, "s") == 0) {
+ $second = $value;
+ $modif = substr($modif, 1);
+ } elsif (index($modif, "mn") == 0) {
+ $minute = $value;
+ $modif = substr($modif, 2);
+ } elsif (index($modif, "h") == 0) {
+ $hour = $value;
+ $modif = substr($modif, 1);
+ } elsif (index($modif, "d") == 0 || index($modif, "j") == 0) {
+ $day = $value;
+ $modif = substr($modif, 1);
+ } elsif (index($modif, "m") == 0) {
+ $month = $value;
+ $modif = substr($modif, 1);
+ } elsif (index($modif, "y") == 0 || index($modif, "a") == 0) {
+ $year = $value;
+ $modif = substr($modif, 1);
+ } else {
+ $modif = substr($modif, 1);
+ }
+ }
+ }
+
+ if ($defaultlanguage eq "F") {
+ print " ann馥: $year\n";
+ print " mois: $month\n";
+ print " jour: $day\n";
+ print " heure: $hour\n";
+ print " minute: $minute\n";
+ print " seconde: $second\n";
+ } else {
+ print " year: $year\n";
+ print " month: $month\n";
+ print " day: $day\n";
+ print " hour: $hour\n";
+ print " minute: $minute\n";
+ print " second: $second\n";
+ }
+
+ if ($year == 0 && $month == 0 && $day == 0 && $hour == 0 && $minute == 0 && $second == 0) {
+ if ($defaultlanguage eq "F") {
+ print "Vous devez entrer un ajustement avec cette commande, svp:\n";
+ print " Valeur d'ajustement (-1, 1, +1, etc...)\n";
+ print " Element modifi:\n";
+ print " a ou y: ann馥\n";
+ print " m: mois\n";
+ print " j ou d: jour\n";
+ print " h: heure\n";
+ print " mn: minute\n";
+ print " s: seconde\n";
+ print " <exemple> banadd nomtest +1m-2mn1s-6y\n";
+ print " Cette exemple ajoute 1 mois et 1 seconde, et soustrait 2 minutes\n";
+ print " et 6 ans dans le m麥e temps.\n";
+ } else {
+ print "Please give an adjustment with this command:\n";
+ print " Adjustment value (-1, 1, +1, etc...)\n";
+ print " Modified element:\n";
+ print " a or y: year\n";
+ print " m: month\n";
+ print " j or d: day\n";
+ print " h: hour\n";
+ print " mn: minute\n";
+ print " s: second\n";
+ print " <example> banadd testname +1m-2mn1s-6y\n";
+ print " this example adds 1 month and 1 second, and substracts 2 minutes\n";
+ print " and 6 years at the same time.\n";
+ }
+ return 137;
+ }
+ if ($year > 127 || $year < -127) {
+ if ($defaultlanguage eq "F") {
+ print "Entrez un ajustement d'ann馥s correct (de -127 127), svp.\n";
+ } else {
+ print "Please give a correct adjustment for the years (from -127 to 127).\n";
+ }
+ return 137;
+ }
+ if ($month > 255 || $month < -255) {
+ if ($defaultlanguage eq "F") {
+ print "Entrez un ajustement de mois correct (de -255 255), svp.\n";
+ } else {
+ print "Please give a correct adjustment for the months (from -255 to 255).\n";
+ }
+ return 137;
+ }
+ if ($day > 32767 || $day < -32767) {
+ if ($defaultlanguage eq "F") {
+ print "Entrez un ajustement de jours correct (de -32767 32767), svp.\n";
+ } else {
+ print "Please give a correct adjustment for the days (from -32767 to 32767).\n";
+ }
+ return 137;
+ }
+ if ($hour > 32767 || $hour < -32767) {
+ if ($defaultlanguage eq "F") {
+ print "Entrez un ajustement d'heures correct (de -32767 32767), svp.\n";
+ } else {
+ print "Please give a correct adjustment for the hours (from -32767 to 32767).\n";
+ }
+ return 137;
+ }
+ if ($minute > 32767 || $minute < -32767) {
+ if ($defaultlanguage eq "F") {
+ print "Entrez un ajustement de minutes correct (de -32767 32767), svp.\n";
+ } else {
+ print "Please give a correct adjustment for the minutes (from -32767 to 32767).\n";
+ }
+ return 137;
+ }
+ if ($second > 32767 || $second < -32767) {
+ if ($defaultlanguage eq "F") {
+ print "Entrez un ajustement de secondes correct (de -32767 32767), svp.\n";
+ } else {
+ print "Please give a correct adjustment for the seconds (from -32767 to 32767).\n";
+ }
+ return 137;
+ }
+
+ print $so pack("va24vvvvvv", 0x794c, $userid, $year, $month, $day, $hour, $minute, $second);
+ $so->flush();
+ $buf = readso(2);
+ if (unpack("v", $buf) != 0x794d) {
+ if ($defaultlanguage eq "F") {
+ print "Probl鑪e de connexion au serveur (r駱onse incorrecte).\n";
+ } else {
+ print "Connection error to the server (incorrect answer).\n";
+ }
+ return 152;
+ }
+ $buf = readso(32);
+ my(@dat) = unpack("Va24V", $buf);
+ while (length($dat[1]) > 0 && substr($dat[1], length($dat[1])-1, 1) eq chr(0)) {
+ chop($dat[1]);
+ };
+ if ($dat[0] == -1 || $dat[0] == 4294967295) {
+ if ($defaultlanguage eq "F") {
+ print "Echec du changement de la date finale de banissement du compte [$userid]. Le compte n'existe pas.\n";
+ } else {
+ print "Account [$userid] final date of banishment changing failed. Account doesn't exist.\n";
+ }
+ } else {
+ if ($defaultlanguage eq "F") {
+ print "Date finale de banissement du compte [$dat[1]][id: $dat[0]] chang馥 avec succ鑚 ".
+ ($dat[2] == 0 ? "en [d-bannie].\n" : "pour 黎re jusqu'au ".(POSIX::ctime($dat[2])));
+ } else {
+ print "Final date of banishment of the account [$dat[1]][id: $dat[0]] successfully changed ".
+ ($dat[2] == 0 ? "to [unbanished].\n" : "to be until ".(POSIX::ctime($dat[2])));
+ }
+ # localtime($dat[2]) is also possible to display instead of POSIX::ctime.
+ }
+
+ return 0;
+}
+
+#--------------------------------------------------------------------------
+
+# Sub-function: Request to displaying information about an account (by its name)
+sub whoaccount() {
+ my($userid) = @_;
+ if ($userid eq "") {
+ if ($defaultlanguage eq "F") {
+ print "Entrez un nom de compte svp.\n";
+ print "<exemple> who nomtest\n";
+ } else {
+ print "Please input an account name.\n";
+ print "<example> who testname\n";
+ }
+ return 136;
+ }
+ if (verify_accountname($userid) == 0) {
+ return 102;
+ }
+
+ print $so pack("va24", 0x7952, $userid);
+ $so->flush();
+
+ $buf = readso(2);
+ if (unpack("v", $buf) != 0x7953) {
+ if ($defaultlanguage eq "F") {
+ print "Probl鑪e de connexion au serveur (r駱onse incorrecte).\n";
+ } else {
+ print "Connection error to the server (incorrect answer).\n";
+ }
+ return 122;
+ }
+ my($id2, $GM_level, $name, $sex, $count, $status, $error_message, $last_login, $last_ip, $email, $validite, $ban_date, $memo_size) = unpack("VCa24cVVa20a24a16a40VVv", readso(148));
+ my($memo) = "";
+ if ($memo_size > 0) {
+ $memo = unpack("a".$memo_size, readso($memo_size));
+ }
+ while (length($name) > 0 && substr($name, length($name)-1, 1) eq chr(0)) {
+ chop($name);
+ };
+ while (length($error_message) > 0 && substr($error_message, length($error_message)-1, 1) eq chr(0)) {
+ chop($error_message);
+ };
+ while (length($last_login) > 0 && substr($last_login, length($last_login)-1, 1) eq chr(0)) {
+ chop($last_login);
+ };
+ while (length($last_ip) > 0 && substr($last_ip, length($last_ip)-1, 1) eq chr(0)) {
+ chop($last_ip);
+ };
+ while (length($email) > 0 && substr($email, length($email)-1, 1) eq chr(0)) {
+ chop($email);
+ };
+ while (length($memo) > 0 && substr($memo, length($memo)-1, 1) eq chr(0)) {
+ chop($memo);
+ };
+
+ if ($id2 == -1 || $id2 == 4294967295) {
+ if ($defaultlanguage eq "F") {
+ print "Impossible de trouver le compte [$userid]. Le compte n'existe pas.\n";
+ } else {
+ print "Unabled to find the account [$userid]. Account doesn't exist.\n";
+ }
+ return 123;
+ } else {
+ if ($defaultlanguage eq "F") {
+ print "Le compte [$userid] a les caract駻istiques suivantes:\n";
+ } else {
+ print "The account [$userid] is set with:\n";
+ }
+ if ($GM_level == 0) {
+ print " Id: $id2 (non-GM)\n";
+ } else {
+ if ($defaultlanguage eq "F") {
+ print " Id: $id2 (GM niveau $GM_level)\n";
+ } else {
+ print " Id: $id2 (GM level $GM_level)\n";
+ }
+ }
+ if ($defaultlanguage eq "F") {
+ print " Nom: '$name'\n";
+ print " Sexe: ".("Femme", "Male", "Serveur")[$sex]."\n";
+ } else {
+ print " Name: '$name'\n";
+ print " Sex: ".("Female", "Male", "Server")[$sex]."\n";
+ }
+ print " E-mail: $email\n";
+ if ($status == 7) {
+ print " Statut: 7 [You are Prohibited to log in until $error_message]\n";
+ } else {
+ print " Statut: $status [".(
+ ($defaultlanguage eq "F" ? "Compte Ok" : "Account OK"),
+ "Unregistered ID",
+ "Incorrect Password",
+ "This ID is expired",
+ "Rejected from Server",
+ "You have been blocked by the GM Team",
+ "Your Game's EXE file is not the latest version",
+ "You are Prohibited to log in until %s",
+ "Server is jammed due to over populated",
+ "No MSG",
+ "This ID is totally erased")[$status == 100 ? 10 : $status]."]\n";
+ }
+ if ($defaultlanguage eq "F") {
+ print " Banissement: ".($ban_date == 0 ? "non banni.\n" : "jusqu'au ".(POSIX::ctime($ban_date)));
+ print " Compteur: $count connexion".("s", "")[$count > 1 ? 0 : 1]."\n";
+ print " Derni鑽e connexion le: $last_login (ip: $last_ip)\n";
+ print " Limite de validit: ".($validite == 0 ? "illimit.\n" : "jusqu'au ".(POSIX::ctime($validite)));
+ } else {
+ print " Banishment: ".($ban_date == 0 ? "not banished.\n" : "until ".(POSIX::ctime($ban_date)));
+ print " Count: $count connection".("s", "")[$count > 1 ? 0 : 1]."\n";
+ print " Last connection at: $last_login (ip: $last_ip)\n";
+ print " Validity limit: ".($validite == 0 ? "unlimited.\n" : "until ".(POSIX::ctime($validite)));
+ }
+ print " Memo: '$memo'\n";
+ }
+ return 0;
+}
+
+#--------------------------------------------------------------------------
+
+# Sub-function: Request to displaying information about an account (by its id)
+sub infoaccount() {
+ my($id) = @_;
+ if ($id < 0) {
+ if ($defaultlanguage eq "F") {
+ print "Entrez un id ayant une valeur positive svp.\n";
+ } else {
+ print "Please input a positive value for the id.\n";
+ }
+ return 136;
+ }
+
+ print $so pack("vV", 0x7954, $id);
+ $so->flush();
+
+ $buf = readso(2);
+ if (unpack("v", $buf) != 0x7953) {
+ if ($defaultlanguage eq "F") {
+ print "Probl鑪e de connexion au serveur (r駱onse incorrecte).\n";
+ } else {
+ print "Connection error to the server (incorrect answer).\n";
+ }
+ return 122;
+ }
+ my($id2, $GM_level, $name, $sex, $count, $status, $error_message, $last_login, $last_ip, $email, $validite, $ban_date, $memo_size) = unpack("VCa24cVVa20a24a16a40VVv", readso(148));
+ my($memo) = "";
+ if ($memo_size > 0) {
+ $memo = unpack("a".$memo_size, readso($memo_size));
+ }
+ while (length($name) > 0 && substr($name, length($name)-1, 1) eq chr(0)) {
+ chop($name);
+ };
+ while (length($error_message) > 0 && substr($error_message, length($error_message)-1, 1) eq chr(0)) {
+ chop($error_message);
+ };
+ while (length($last_login) > 0 && substr($last_login, length($last_login)-1, 1) eq chr(0)) {
+ chop($last_login);
+ };
+ while (length($last_ip) > 0 && substr($last_ip, length($last_ip)-1, 1) eq chr(0)) {
+ chop($last_ip);
+ };
+ while (length($email) > 0 && substr($email, length($email)-1, 1) eq chr(0)) {
+ chop($email);
+ };
+ while (length($memo) > 0 && substr($memo, length($memo)-1, 1) eq chr(0)) {
+ chop($memo);
+ };
+
+ if (length($name) == 0 || $name eq "") {
+ if ($defaultlanguage eq "F") {
+ print "Impossible de trouver le nom du compte [id: $id2]. Le compte n'existe pas.\n";
+ } else {
+ print "Unabled to find the account [id: $id2] name. Account doesn't exist.\n";
+ }
+ return 123;
+ } else {
+ if ($defaultlanguage eq "F") {
+ print "Le compte [id: $id2] a les caract駻istiques suivantes:\n";
+ } else {
+ print "The account [id: $id2] is set with:\n";
+ }
+ if ($GM_level == 0) {
+ print " Id: $id2 (non-GM)\n";
+ } else {
+ if ($defaultlanguage eq "F") {
+ print " Id: $id2 (GM niveau $GM_level)\n";
+ } else {
+ print " Id: $id2 (GM level $GM_level)\n";
+ }
+ }
+ if ($defaultlanguage eq "F") {
+ print " Nom: '$name'\n";
+ print " Sexe: ".("Femme", "Male", "Serveur")[$sex]."\n";
+ } else {
+ print " Name: '$name'\n";
+ print " Sex: ".("Female", "Male", "Server")[$sex]."\n";
+ }
+ print " E-mail: $email\n";
+ if ($status == 7) {
+ print " Statut: 7 [You are Prohibited to log in until $error_message]\n";
+ } else {
+ print " Statut: $status [".(
+ ($defaultlanguage eq "F" ? "Compte Ok" : "Account OK"),
+ "Unregistered ID",
+ "Incorrect Password",
+ "This ID is expired",
+ "Rejected from Server",
+ "You have been blocked by the GM Team",
+ "Your Game's EXE file is not the latest version",
+ "You are Prohibited to log in until %s",
+ "Server is jammed due to over populated",
+ "No MSG",
+ "This ID is totally erased")[$status == 100 ? 10 : $status]."]\n";
+ }
+ if ($defaultlanguage eq "F") {
+ print " Banissement: ".($ban_date == 0 ? "non banni.\n" : "jusqu'au ".(POSIX::ctime($ban_date)));
+ print " Compteur: $count connexion".("s", "")[$count > 1 ? 0 : 1]."\n";
+ print " Derni鑽e connexion le: $last_login (ip: $last_ip)\n";
+ print " Limite de validit: ".($validite == 0 ? "illimit.\n" : "jusqu'au ".(POSIX::ctime($validite)));
+ } else {
+ print " Banishment: ".($ban_date == 0 ? "not banished.\n" : "until ".(POSIX::ctime($ban_date)));
+ print " Count: $count connection".("s", "")[$count > 1 ? 0 : 1]."\n";
+ print " Last connection at: $last_login (ip: $last_ip)\n";
+ print " Validity limit: ".($validite == 0 ? "unlimited.\n" : "until ".(POSIX::ctime($validite)));
+ }
+ print " Memo: '$memo'\n";
+ }
+ return 0;
+}
+
+#--------------------------------------------------------------------------
+
+# Sub-function: Check the validity of a password
+# (Note: never send back a password with login-server!! security of passwords)
+sub checkaccount() {
+ my($userid, $passwd) = @_;
+ if ($userid eq "") {
+ if ($defaultlanguage eq "F") {
+ print "Entrez un nom de compte svp.\n";
+ print "<exemple> check testname motdepasse\n";
+ } else {
+ print "Please input an account name.\n";
+ print "<example> check testname password\n";
+ }
+ return 136;
+ }
+ if (verify_accountname($userid) == 0) {
+ return 102;
+ }
+ if ($passwd eq "") {
+ return 134 if (($passwd = typepasswd()) eq "");
+ }
+ if (verify_password($passwd) == 0) {
+ return 131;
+ }
+ print $so pack("va24a24", 0x793a, $userid,$passwd);
+ $so->flush();
+ $buf = readso(2);
+ if (unpack("v", $buf) != 0x793b) {
+ if ($defaultlanguage eq "F") {
+ print "Probl鑪e de connexion au serveur (r駱onse incorrecte).\n";
+ } else {
+ print "Connection error to the server (incorrect answer).\n";
+ }
+ return 132;
+ }
+ $buf = readso(28);
+ my($id2, $name) = unpack("Va24", $buf);
+ while (length($name) > 0 && substr($name, length($name)-1, 1) eq chr(0)) {
+ chop($name);
+ };
+ if ($id2 == -1 || $id2 == 4294967295) {
+ if ($defaultlanguage eq "F") {
+ print "Le compte [$userid] n'existe pas ou le mot de passe est incorrect.\n";
+ } else {
+ print "The account [$userid] doesn't exist or the password is incorrect.\n";
+ }
+ return 133;
+ } else {
+ if ($defaultlanguage eq "F") {
+ print "Le mot de passe donn correspond bien au compte [$name][id: $id2].\n";
+ } else {
+ print "The proposed password is correct for the account [$name][id: $id2].\n";
+ }
+ }
+ return 130;
+}
+
+#--------------------------------------------------------------------------
+
+# Sub-function: Request to login-server to reload GM configuration file
+sub reloadGM() {
+ print $so pack("v", 0x7955);
+ $so->flush();
+ if ($defaultlanguage eq "F") {
+ print "Demande de recharger le fichier de configuration des GM envoy馥.\n";
+ print "V駻ifiez les comptes GM actuels (apr鑚 rechargement):\n";
+ } else {
+ print "Request to reload the GM configuration file sended.\n";
+ print "Check the actual GM accounts (after reloading):\n";
+ }
+ &listaccount(0, 0, 1); # 1: to list only GM
+ return 180;
+}
+
+#--------------------------------------------------------------------------
+
+# Sub-function: Send a broadcast message
+sub sendbroadcast() {
+ my($type, $message) = @_;
+ if ($message eq "" || length($message) == 0) {
+ if ($defaultlanguage eq "F") {
+ print "Entrez un message svp.\n";
+ if ($type == 0) {
+ print "<exemple> kami un message\n";
+ } else {
+ print "<exemple> kamib un message\n";
+ }
+ } else {
+ print "Please input a message.\n";
+ if ($type == 0) {
+ print "<example> kami a message\n";
+ } else {
+ print "<example> kamib a message\n";
+ }
+ }
+ return 136;
+ }
+
+ print $so pack("vvVa".length($message), 0x794e, $type, length($message), $message);
+ $so->flush();
+ $buf = readso(2);
+ if (unpack("v", $buf) != 0x794f) {
+ if ($defaultlanguage eq "F") {
+ print "Probl鑪e de connexion au serveur (r駱onse incorrecte).\n";
+ } else {
+ print "Connection error to the server (incorrect answer).\n";
+ }
+ return 152;
+ }
+ $buf = readso(2);
+ my($answer) = unpack("v", $buf);
+ if ($answer == -1 || $answer == 65535) {
+ if ($defaultlanguage eq "F") {
+ print "Echec de l'envoi du message. Aucun server de char en ligne.\n";
+ } else {
+ print "Message sending failed. No online char-server.\n";
+ }
+ } else {
+ if ($defaultlanguage eq "F") {
+ print "Message transmis au server de logins avec succ鑚.\n";
+ } else {
+ print "Message successfully sended to login-server.\n";
+ }
+ }
+}
+
+#--------------------------------------------------------------------------
+
+# Sub-function: Change language of displaying
+sub changelanguage() {
+ my($language) = @_;
+ if ($language eq "" || length($language) == 0) {
+ if ($defaultlanguage == 'F') {
+ printf("Entrez une langue svp.\n");
+ printf("<exemple> language english\n");
+ printf(" language fran軋is\n");
+ } else {
+ printf("Please input a language.\n");
+ printf("<example> language english\n");
+ printf(" language fran軋is\n");
+ }
+ return 136;
+ }
+
+ $language = uc(substr($language, 0, 1));
+ if ($language =~ /^[EF]$/) {
+ $defaultlanguage = $language;
+ if ($defaultlanguage == 'F') {
+ printf("Changement de la langue d'affichage en Fran軋is.\n");
+ } else {
+ printf("Displaying language changed to English.\n");
+ }
+ } else {
+ if ($defaultlanguage == 'F') {
+ printf("Langue non param騁r馥 (langues possibles: 'Fran軋is' ou 'English').\n");
+ } else {
+ printf("Undefined language (possible languages: Fran軋is or English).\n");
+ }
+ }
+
+ return 0;
+}
+
+#--------------------------------------------------------------------------
+
+# Sub-function: sending 'end of connection' packet
+sub quit() {
+ print $so pack("v", 0x7532);
+ $so->flush();
+}
+
+#--------------------------------------------------------------------------
+
+# Sub-function: Get datas from the socket
+sub readso() {
+ my($len) = shift;
+ my($buf);
+ if (read($so, $buf, $len) < $len) {
+ if ($defaultlanguage eq "F") {
+ print "Erreur de lecture sur la Socket.\n";
+ } else {
+ print "Socket read error.\n";
+ }
+ exit(3);
+ }
+ return $buf;
+}
+
+#--------------------------------------------------------------------------
+
+# Sub-function: Input of a password
+sub typepasswd {
+ my($passwd1, $passwd2);
+ cbreak();
+ if ($defaultlanguage eq "F") {
+ print "Entrez le mot de passe > "; $passwd1 = <STDIN>; chomp($passwd1); print "\n";
+ print "R-entrez le mot de passe > "; $passwd2 = <STDIN>; chomp($passwd2); print "\n";
+ } else {
+ print "Type the password > "; $passwd1 = <STDIN>; chomp($passwd1); print "\n";
+ print "Verify the password > "; $passwd2 = <STDIN>; chomp($passwd2); print "\n";
+ }
+ cooked();
+ if ($passwd1 ne $passwd2) {
+ if ($defaultlanguage eq "F") {
+ print "Erreur de v駻ification du mot de passe: Saisissez le m麥e mot de passe svp.\n";
+ } else {
+ print "Password verification failed. Please input same password.\n";
+ }
+ return "";
+ }
+ return $passwd1;
+}
+
+#--------------------------------------------------------------------------
+
+# Sub-function: Return ordonal text of a number
+sub makeordinal {
+ my($c) = shift;
+ if ($defaultlanguage eq "F") {
+ if ($c < 1) {
+ return $c;
+ }
+ return $c.("er", "鑪e")[$c == 1 ? 0 : 1];
+ } else {
+ if ($c % 10 < 4 && $c % 10 != 0 && ($c < 10 || $c > 20)) {
+ return $c.("st","nd","rd")[$c % 10 - 1];
+ }
+ return $c."th";
+ }
+}
+
+#--------------------------------------------------------------------------
+
+# Sub-function: Test of the validity of an account name (return 0 if incorrect, and 1 if ok)
+sub verify_accountname {
+ my($account_name) = @_; # Get the account_name
+ if ($account_name =~ /[\x00-\x1f]/) { # remove control char
+ my($c) = length($`) + 1;
+ if ($defaultlanguage eq "F") {
+ print "Caract鑽e interdit trouv dans le nom du compte (".makeordinal($c)." caract鑽e).\n";
+ } else {
+ print "Illegal character found in the account name (".makeordinal($c)." character).\n";
+ }
+ return 0;
+ }
+ if (length($account_name) < 4) {
+ if ($defaultlanguage eq "F") {
+ print "Nom du compte trop court. Entrez un nom de compte de 4-23 caract鑽es.\n";
+ } else {
+ print "Account name is too short. Please input an account name of 4-23 bytes.\n";
+ }
+ return 0;
+ }
+ if (length($account_name) > 23) {
+ if ($defaultlanguage eq "F") {
+ print "Nom du compte trop long. Entrez un nom de compte de 4-23 caract鑽es.\n";
+ } else {
+ print "Account name is too long. Please input an account name of 4-23 bytes.\n";
+ }
+ return 0;
+ }
+ return 1;
+}
+
+#--------------------------------------------------------------------------
+
+# Sub-function: Test of the validity of password (return 0 if incorrect, and 1 if ok)
+sub verify_password {
+ my($password) = @_; # Get the password
+ if ($password =~ /[\x00-\x1f]/) {
+ my($c) = length($`) + 1;
+ if ($defaultlanguage eq "F") {
+ print "Caract鑽e interdit trouv dans le mot de passe (".makeordinal($c)." caract鑽e).\n";
+ } else {
+ print "Illegal character found in the password (".makeordinal($c)." character).\n";
+ }
+ return 0;
+ }
+ if (length($password) < 4) {
+ if ($defaultlanguage eq "F") {
+ print "Mot de passe trop court. Entrez un mot de passe de 4-23 caract鑽es.\n";
+ } else {
+ print "Password is too short. Please input a password of 4-23 bytes.\n";
+ }
+ return 0;
+ }
+ if (length($password) > 23) {
+ if ($defaultlanguage eq "F") {
+ print "Mot de passe trop long. Entrez un mot de passe de 4-23 caract鑽es.\n";
+ } else {
+ print "Password is too long. Please input a password of 4-23 bytes.\n";
+ }
+ return 0;
+ }
+ return 1;
+}
+
+#--------------------------------------------------------------------------
+
+# Sub-function: Test of the validity of an e-mail (return 0 if incorrect, and 1 if ok)
+sub verify_email {
+ my($email) = @_; # Get the e-mail
+ # To ignore a '.' before the @ (wanadoo, a provider, do that)
+ $email =~ s/\.\@/\@/;
+ # If the e-mail is void, it's not correct -> return 0
+ if ($email eq '') {
+ return(0);
+ }
+ # If the e-mail have no "@", it's not correct -> return 0
+ if ($email !~ /\@/) {
+ return(0);
+ }
+ # If the e-mail have a ",", a space, a tab or a ";", it's not correct -> return 0
+ if ($email =~ /[\,|\s|\;]/) {
+ return(0)
+ };
+ # IF
+ # (the e-mail contains 2 "@", or ".." or "@." or starts or finishes by a ".")
+ # OR IF
+ # (the e-mail doesn't contain "@localhost" AND
+ # - it doesn't contain characters followed by "@" itself followed by letters itself followed by "." and 2 or more letters
+ # - or an IP address)
+ # -> so, it's not good ! (finish !)
+ if ($email =~ /(@.*@)|(\.\.)|(@\.)|(\.@)|(^\.)|(\.$)/ ||
+ ($email !~ /^.+\@localhost$/ &&
+ $email !~ /^.+\@\[?(\w|[-.])+\.[a-zA-Z]{2,3}|[0-9]{1,3}\]?$/)) {
+ return(0); # non-valid email
+ } else {
+ # If not, the e-email address is correct
+ return(1); # valid email
+ }
+}
\ No newline at end of file diff --git a/src/tool/mapcheck.sh b/src/tool/mapcheck.sh new file mode 100644 index 0000000..337884c --- /dev/null +++ b/src/tool/mapcheck.sh @@ -0,0 +1,34 @@ +#!/bin/sh +echo "============================================" +echo "= map server status checker... =" +echo "============================================" +./map-server.exe & +sleep 40 + +while [ 0 ] +do + pcpu=` top -n 1| grep map-server | awk '{print $9}' | awk 'BEGIN{FS="."} {print $1}' ` + if [ "$pcpu" -gt 80 ];then + echo "============================================" + echo "map server is more than 80% (now $pcpu%)" + echo "============================================" + ppid=` ps -a | grep map-server | awk '{print $1}' ` + kill $ppid + ./map-server.exe & + sleep 40 + else + pmapct=` ps -a| grep map-server | wc -l ` + if [ "$pmapct" -eq 0 ];then + echo "============================================" + echo "map server is not running..." + echo "restart map server..." + echo "============================================" + ./map-server.exe & + sleep 40 + #echo "test" + else + echo "map server is ok (now $pcpu%)..." + sleep 5 + fi + fi +done
\ No newline at end of file diff --git a/src/tool/mapchecker.sh b/src/tool/mapchecker.sh new file mode 100644 index 0000000..7250c34 --- /dev/null +++ b/src/tool/mapchecker.sh @@ -0,0 +1,56 @@ +#!/bin/bash + +athena_dir="/home/athena/658/" + +while [ true ] ; do + +if [ ` ps fauxw | grep map-server | grep -v grep | wc -l ` -eq 0 ];then + #echo `date` " -- map-server crashed - restarting" + echo `date` " -- map-server crashed - restarting" >> /var/log/athena_status.log + killall -9 map-server + cd $athena_dir + nohup ./map-server ./conf/map_athena.conf ./inter_athena.conf & + sleep 240 + #sleep 40 #for fast pc's remove the "#" at the beginning of the line and delete the line above +fi + + +if [ ` ps fauxw | grep map-server | grep -v grep | awk '{print $3}' | awk 'BEGIN{FS="."} {print $1}' ` -gt 10 ];then + #echo `date` " -- mapserver cpuload over 10 - restarting" + echo `date` " -- mapserver cpuload over 10 - restarting" >> /var/log/athena_status.log + killall -9 map-server + cd $athena_dir + nohup ./map-server ./conf/map_athena.conf ./inter_athena.conf & + sleep 240 + #sleep 40 #for fast pc's remove the "#" at the beginning of the line and delete the line above + #echo `date` " -- restarted" + echo `date` " -- restarted" >> /var/log/athena_status.log +fi + +if [ ` ps fauxw | grep char-server | grep -v grep | wc -l ` -eq 0 ];then + #echo `date` " -- char server crashed - restarting" + echo `date` " -- char server crashed - restarting" >> /var/log/athena_status.log + killall -9 char-server + cd $athena_dir + nohup ./char-server ./conf/char_athena.conf ./conf/inter_athena.conf & + #echo `date` " -- restarted" + echo `date` " -- restarted" >> /var/log/athena_status.log + +fi + +if [ ` ps fauxw | grep login-server | grep -v grep | wc -l ` -eq 0 ];then + #echo `date` " -- login server crashed - restarting" + echo `date` " -- login server crashed - restarting" >> /var/log/athena_status.log + killall -9 login-server + cd $athena_dir + nohup ./login-server ./conf/login_athena.conf & + #echo `date` " -- restarted" + echo `date` " -- restarted" >> /var/log/athena_status.log + +fi + + +#echo `date` " -- everything is fine" +echo `date` " -- everything is fine" >> /var/log/athena_status.log +sleep 30 +done diff --git a/src/txt-converter/char/GNUmakefile b/src/txt-converter/char/GNUmakefile new file mode 100644 index 0000000..b88df26 --- /dev/null +++ b/src/txt-converter/char/GNUmakefile @@ -0,0 +1,13 @@ +all: char-converter
+sql: char-converter
+
+COMMON_OBJ = ../../common/core.o ../../common/socket.o ../../common/timer.o ../../common/db.o ../../common/malloc.o
+
+char-converter: char-converter.o strlib.o $(COMMON_OBJ)
+ $(CC) -o ../../../$@ $^ $(LIB_S)
+
+char-converter.o: char-converter.c char.h strlib.h
+strlib.o: strlib.c strlib.h
+clean:
+ rm -f *.o ../../../char-converter
+
diff --git a/src/txt-converter/char/Makefile b/src/txt-converter/char/Makefile new file mode 100644 index 0000000..b88df26 --- /dev/null +++ b/src/txt-converter/char/Makefile @@ -0,0 +1,13 @@ +all: char-converter
+sql: char-converter
+
+COMMON_OBJ = ../../common/core.o ../../common/socket.o ../../common/timer.o ../../common/db.o ../../common/malloc.o
+
+char-converter: char-converter.o strlib.o $(COMMON_OBJ)
+ $(CC) -o ../../../$@ $^ $(LIB_S)
+
+char-converter.o: char-converter.c char.h strlib.h
+strlib.o: strlib.c strlib.h
+clean:
+ rm -f *.o ../../../char-converter
+
diff --git a/src/txt-converter/char/char-converter.c b/src/txt-converter/char/char-converter.c new file mode 100644 index 0000000..44f6d29 --- /dev/null +++ b/src/txt-converter/char/char-converter.c @@ -0,0 +1,842 @@ +// $Id: char-converter.c,v 1.1.1.1 2004/09/10 17:45:03 MagicalTux Exp $ +// original : char2.c 2003/03/14 11:58:35 Rev.1.5 + +#include <sys/types.h> +#include <sys/socket.h> +#include <stdio.h> +#include <stdlib.h> +#include <netinet/in.h> +#include <sys/time.h> +#include <sys/ioctl.h> +#include <unistd.h> +#include <signal.h> +#include <fcntl.h> +#include <string.h> +#include <arpa/inet.h> + + +#define STORAGE_MEMINC 16 + +#include "char.h" +#include "strlib.h" + +#ifdef MEMWATCH +#include "memwatch.h" +#endif + +char pet_txt[256]="save/pet.txt"; +char storage_txt[256]="save/storage.txt"; + +MYSQL mysql_handle; +MYSQL_RES* sql_res ; +MYSQL_ROW sql_row ; +int sql_fields, sql_cnt; +char tmp_sql[65535]; + +int db_server_port = 3306; +char db_server_ip[16] = "127.0.0.1"; +char db_server_id[32] = "ragnarok"; +char db_server_pw[32] = "ragnarok"; +char db_server_logindb[32] = "ragnarok"; + +struct storage *storage=NULL; + +struct mmo_map_server server[MAX_MAP_SERVERS]; +int server_fd[MAX_MAP_SERVERS]; + +int login_fd; +char userid[24]; +char passwd[24]; +char server_name[20]; +char login_ip_str[16]; +int login_port = 6900; +char char_ip_str[16]; +int char_ip; +int char_port = 6121; +int char_maintenance; +int char_new; +char char_txt[256]; + +char t_name[256]; + +#define CHAR_STATE_WAITAUTH 0 +#define CHAR_STATE_AUTHOK 1 +struct char_session_data{ + int state; + int account_id, login_id1, login_id2, sex; + int found_char[9]; +}; + +#define AUTH_FIFO_SIZE 256 +struct { + int account_id, char_id, login_id1, char_pos, delflag, sex; +} auth_fifo[AUTH_FIFO_SIZE]; +int auth_fifo_pos=0; + +int char_id_count=100000; +struct mmo_charstatus *char_dat; +int char_num, char_max; +int max_connect_user=0; +int autosave_interval=DEFAULT_AUTOSAVE_INTERVAL; + +// テハア タァト。(conf ニトタマキホコホナヘ タ郛ウチ、 ー。エノ) +struct point start_point={"new_1-1.gat", 53,111}; + + + +int inter_pet_fromstr(char *str, struct s_pet *p) +{ + int s; + int tmp_int[16]; + char tmp_str[256]; + + memset(p, 0, sizeof(struct s_pet)); + +// printf("sscanf pet main info\n"); + s=sscanf(str,"%d, %d,%[^\t]\t%d, %d, %d, %d, %d, %d, %d, %d, %d", &tmp_int[0], &tmp_int[1], tmp_str, &tmp_int[2], + &tmp_int[3], &tmp_int[4], &tmp_int[5], &tmp_int[6], &tmp_int[7], &tmp_int[8], &tmp_int[9], &tmp_int[10]); + + if(s!=12) + return 1; + + p->pet_id = tmp_int[0]; + p->class = tmp_int[1]; + memcpy(p->name, tmp_str, 24); + p->account_id = tmp_int[2]; + p->char_id = tmp_int[3]; + p->level = tmp_int[4]; + p->egg_id = tmp_int[5]; + p->equip = tmp_int[6]; + p->intimate = tmp_int[7]; + p->hungry = tmp_int[8]; + p->rename_flag = tmp_int[9]; + p->incuvate = tmp_int[10]; + + if(p->hungry < 0) + p->hungry = 0; + else if(p->hungry > 100) + p->hungry = 100; + if(p->intimate < 0) + p->intimate = 0; + else if(p->intimate > 1000) + p->intimate = 1000; + + return 0; +} +//--------------------------------------------------------- +int inter_pet_tosql(int pet_id, struct s_pet *p) { + //`pet` (`pet_id`, `class`,`name`,`account_id`,`char_id`,`level`,`egg_id`,`equip`,`intimate`,`hungry`,`rename_flag`,`incuvate`) + + char tmp_sql[65535]; + MYSQL_RES* sql_res ; + MYSQL_ROW sql_row ; + + jstrescapecpy (t_name, p->name); + if(p->hungry < 0) + p->hungry = 0; + else if(p->hungry > 100) + p->hungry = 100; + if(p->intimate < 0) + p->intimate = 0; + else if(p->intimate > 1000) + p->intimate = 1000; + sprintf(tmp_sql,"SELECT * FROM `pet` WHERE `pet_id`='%d'",pet_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle) ); + } + sql_res = mysql_store_result(&mysql_handle) ; + sql_row = mysql_fetch_row(sql_res); //row fetching + if (!sql_row) //no row -> insert + sprintf(tmp_sql,"INSERT INTO `pet` (`pet_id`, `class`,`name`,`account_id`,`char_id`,`level`,`egg_id`,`equip`,`intimate`,`hungry`,`rename_flag`,`incuvate`) VALUES ('%d', '%d', '%s', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d')", + p->pet_id, p->class, t_name, p->account_id, p->char_id, p->level, p->egg_id, + p->equip, p->intimate, p->hungry, p->rename_flag, p->incuvate); + else //row reside -> updating + sprintf(tmp_sql, "UPDATE `pet` SET `class`='%d',`name`='%s',`account_id`='%d',`char_id`='%d',`level`='%d',`egg_id`='%d',`equip`='%d',`intimate`='%d',`hungry`='%d',`rename_flag`='%d',`incuvate`='%d' WHERE `pet_id`='%d'", + p->class, t_name, p->account_id, p->char_id, p->level, p->egg_id, + p->equip, p->intimate, p->hungry, p->rename_flag, p->incuvate, p->pet_id); + mysql_free_result(sql_res) ; //resource free + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle) ); + } + + printf ("pet dump success! - %d:%s\n", pet_id, p->name); + + return 0; +} + +int storage_tosql(int account_id,struct storage *p){ + // id -> DB dump + // storage {`account_id`/`id`/`nameid`/`amount`/`equip`/`identify`/`refine`/`attribute`/`card0`/`card1`/`card2`/`card3`} + int i,j; + + j=0; + + //printf ("starting storage dump to DB - id: %d\n", account_id); + + //delete old data. + sprintf(tmp_sql,"DELETE FROM `storage` WHERE `account_id`='%d'",account_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle) ); + } + + //printf ("all storage item was deleted ok\n"); + + for(i=0;i<MAX_STORAGE;i++) { + //printf ("save storage num: %d (%d:%d)\n",i, p->storage[i].nameid , p->storage[i].amount); + + if( (p->storage[i].nameid) && (p->storage[i].amount) ){ + sprintf(tmp_sql,"INSERT INTO `storage` (`account_id`,`nameid`,`amount`,`equip`,`identify`,`refine`,`attribute`,`card0`,`card1`,`card2`,`card3`,`broken`) VALUES ('%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d')", + p->account_id, p->storage[i].nameid, p->storage[i].amount, p->storage[i].equip, + p->storage[i].identify, p->storage[i].refine, p->storage[i].attribute, + p->storage[i].card[0], p->storage[i].card[1], p->storage[i].card[2], p->storage[i].card[3], p->storage[i].broken ); + //printf ("%s\n",tmp_sql); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle) ); + } + j++; + } + } + + printf ("storage dump to DB - id: %d (total: %d)\n", account_id, j); + return 0; +} +// char to storage +int storage_fromstr(char *str, struct storage *p) +{ + int tmp_int[256]; + int set, next, len, i; + + set=sscanf(str,"%d, %d%n", &tmp_int[0], &tmp_int[1], &next); + p->storage_amount=tmp_int[1]; + + if(set!=2) + return 0; + if(str[next]=='\n' || str[next]=='\r') + return 1; + next++; + for(i=0;str[next] && str[next]!='\t';i++){ + if(sscanf(str + next, "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d%n", + &tmp_int[0], &tmp_int[1], &tmp_int[2], &tmp_int[3], + &tmp_int[4], &tmp_int[5], &tmp_int[6], + &tmp_int[7], &tmp_int[8], &tmp_int[9], &tmp_int[10], &tmp_int[11], &len) == 12) { + p->storage[i].id = tmp_int[0]; + p->storage[i].nameid = tmp_int[1]; + p->storage[i].amount = tmp_int[2]; + p->storage[i].equip = tmp_int[3]; + p->storage[i].identify = tmp_int[4]; + p->storage[i].refine = tmp_int[5]; + p->storage[i].attribute = tmp_int[6]; + p->storage[i].card[0] = tmp_int[7]; + p->storage[i].card[1] = tmp_int[8]; + p->storage[i].card[2] = tmp_int[9]; + p->storage[i].card[3] = tmp_int[10]; + p->storage[i].broken = tmp_int[11]; + next += len; + if (str[next] == ' ') + next++; + } + + else if(sscanf(str + next, "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d%n", + &tmp_int[0], &tmp_int[1], &tmp_int[2], &tmp_int[3], + &tmp_int[4], &tmp_int[5], &tmp_int[6], + &tmp_int[7], &tmp_int[8], &tmp_int[9], &tmp_int[10], &len) == 11) { + p->storage[i].id = tmp_int[0]; + p->storage[i].nameid = tmp_int[1]; + p->storage[i].amount = tmp_int[2]; + p->storage[i].equip = tmp_int[3]; + p->storage[i].identify = tmp_int[4]; + p->storage[i].refine = tmp_int[5]; + p->storage[i].attribute = tmp_int[6]; + p->storage[i].card[0] = tmp_int[7]; + p->storage[i].card[1] = tmp_int[8]; + p->storage[i].card[2] = tmp_int[9]; + p->storage[i].card[3] = tmp_int[10]; + p->storage[i].broken = 0; + next += len; + if (str[next] == ' ') + next++; + } + + else return 0; + } + return 1; +} + +///////////////////////////////// +int mmo_char_fromstr(char *str, struct mmo_charstatus *p) { + int tmp_int[256]; + int set, next, len, i; + + // initilialise character + memset(p, '\0', sizeof(struct mmo_charstatus)); + + // If it's not char structure of version 1008 and after + if ((set = sscanf(str, "%d\t%d,%d\t%[^\t]\t%d,%d,%d\t%d,%d,%d\t%d,%d,%d,%d\t%d,%d,%d,%d,%d,%d\t%d,%d" + "\t%d,%d,%d\t%d,%d,%d\t%d,%d,%d\t%d,%d,%d,%d,%d" + "\t%[^,],%d,%d\t%[^,],%d,%d,%d%n", + &tmp_int[0], &tmp_int[1], &tmp_int[2], p->name, // + &tmp_int[3], &tmp_int[4], &tmp_int[5], + &tmp_int[6], &tmp_int[7], &tmp_int[8], + &tmp_int[9], &tmp_int[10], &tmp_int[11], &tmp_int[12], + &tmp_int[13], &tmp_int[14], &tmp_int[15], &tmp_int[16], &tmp_int[17], &tmp_int[18], + &tmp_int[19], &tmp_int[20], + &tmp_int[21], &tmp_int[22], &tmp_int[23], // + &tmp_int[24], &tmp_int[25], &tmp_int[26], + &tmp_int[27], &tmp_int[28], &tmp_int[29], + &tmp_int[30], &tmp_int[31], &tmp_int[32], &tmp_int[33], &tmp_int[34], + p->last_point.map, &tmp_int[35], &tmp_int[36], // + p->save_point.map, &tmp_int[37], &tmp_int[38], &tmp_int[39], &next)) != 43) { + tmp_int[39] = 0; // partner id + // If not char structure from version 384 to 1007 + if ((set = sscanf(str, "%d\t%d,%d\t%[^\t]\t%d,%d,%d\t%d,%d,%d\t%d,%d,%d,%d\t%d,%d,%d,%d,%d,%d\t%d,%d" + "\t%d,%d,%d\t%d,%d,%d\t%d,%d,%d\t%d,%d,%d,%d,%d" + "\t%[^,],%d,%d\t%[^,],%d,%d%n", + &tmp_int[0], &tmp_int[1], &tmp_int[2], p->name, // + &tmp_int[3], &tmp_int[4], &tmp_int[5], + &tmp_int[6], &tmp_int[7], &tmp_int[8], + &tmp_int[9], &tmp_int[10], &tmp_int[11], &tmp_int[12], + &tmp_int[13], &tmp_int[14], &tmp_int[15], &tmp_int[16], &tmp_int[17], &tmp_int[18], + &tmp_int[19], &tmp_int[20], + &tmp_int[21], &tmp_int[22], &tmp_int[23], // + &tmp_int[24], &tmp_int[25], &tmp_int[26], + &tmp_int[27], &tmp_int[28], &tmp_int[29], + &tmp_int[30], &tmp_int[31], &tmp_int[32], &tmp_int[33], &tmp_int[34], + p->last_point.map, &tmp_int[35], &tmp_int[36], // + p->save_point.map, &tmp_int[37], &tmp_int[38], &next)) != 42) { + // It's char structure of a version before 384 + tmp_int[26] = 0; // pet id + set = sscanf(str, "%d\t%d,%d\t%[^\t]\t%d,%d,%d\t%d,%d,%d\t%d,%d,%d,%d\t%d,%d,%d,%d,%d,%d\t%d,%d" + "\t%d,%d,%d\t%d,%d\t%d,%d,%d\t%d,%d,%d,%d,%d" + "\t%[^,],%d,%d\t%[^,],%d,%d%n", + &tmp_int[0], &tmp_int[1], &tmp_int[2], p->name, // + &tmp_int[3], &tmp_int[4], &tmp_int[5], + &tmp_int[6], &tmp_int[7], &tmp_int[8], + &tmp_int[9], &tmp_int[10], &tmp_int[11], &tmp_int[12], + &tmp_int[13], &tmp_int[14], &tmp_int[15], &tmp_int[16], &tmp_int[17], &tmp_int[18], + &tmp_int[19], &tmp_int[20], + &tmp_int[21], &tmp_int[22], &tmp_int[23], // + &tmp_int[24], &tmp_int[25], // + &tmp_int[27], &tmp_int[28], &tmp_int[29], + &tmp_int[30], &tmp_int[31], &tmp_int[32], &tmp_int[33], &tmp_int[34], + p->last_point.map, &tmp_int[35], &tmp_int[36], // + p->save_point.map, &tmp_int[37], &tmp_int[38], &next); + set += 2; + //printf("char: old char data ver.1\n"); + // Char structure of version 1007 or older + } else { + set++; + //printf("char: old char data ver.2\n"); + } + // Char structure of version 1008+ + } else { + //printf("char: new char data ver.3\n"); + } + if (set != 43) + return 0; + + p->char_id = tmp_int[0]; + p->account_id = tmp_int[1]; + p->char_num = tmp_int[2]; + p->class = tmp_int[3]; + p->base_level = tmp_int[4]; + p->job_level = tmp_int[5]; + p->base_exp = tmp_int[6]; + p->job_exp = tmp_int[7]; + p->zeny = tmp_int[8]; + p->hp = tmp_int[9]; + p->max_hp = tmp_int[10]; + p->sp = tmp_int[11]; + p->max_sp = tmp_int[12]; + p->str = tmp_int[13]; + p->agi = tmp_int[14]; + p->vit = tmp_int[15]; + p->int_ = tmp_int[16]; + p->dex = tmp_int[17]; + p->luk = tmp_int[18]; + p->status_point = tmp_int[19]; + p->skill_point = tmp_int[20]; + p->option = tmp_int[21]; + p->karma = tmp_int[22]; + p->manner = tmp_int[23]; + p->party_id = tmp_int[24]; + p->guild_id = tmp_int[25]; + p->pet_id = tmp_int[26]; + p->hair = tmp_int[27]; + p->hair_color = tmp_int[28]; + p->clothes_color = tmp_int[29]; + p->weapon = tmp_int[30]; + p->shield = tmp_int[31]; + p->head_top = tmp_int[32]; + p->head_mid = tmp_int[33]; + p->head_bottom = tmp_int[34]; + p->last_point.x = tmp_int[35]; + p->last_point.y = tmp_int[36]; + p->save_point.x = tmp_int[37]; + p->save_point.y = tmp_int[38]; + p->partner_id = tmp_int[39]; + + // Some checks + for(i = 0; i < char_num; i++) { + if (char_dat[i].char_id == p->char_id) { + printf("\033[1;31mmmo_auth_init: ******Error: a character has an identical id to another.\n"); + printf(" character id #%d -> new character not readed.\n", p->char_id); + printf(" Character saved in log file.\033[0m\n"); + return -1; + } else if (strcmp(char_dat[i].name, p->name) == 0) { + printf("\033[1;31mmmo_auth_init: ******Error: character name already exists.\n"); + printf(" character name '%s' -> new character not readed.\n", p->name); + printf(" Character saved in log file.\033[0m\n"); + return -2; + } + } + + if (str[next] == '\n' || str[next] == '\r') + return 1; // 新規データ + + next++; + + for(i = 0; str[next] && str[next] != '\t'; i++) { + set = sscanf(str+next, "%[^,],%d,%d%n", p->memo_point[i].map, &tmp_int[0], &tmp_int[1], &len); + if (set != 3) + return -3; + p->memo_point[i].x = tmp_int[0]; + p->memo_point[i].y = tmp_int[1]; + next += len; + if (str[next] == ' ') + next++; + } + + next++; + + for(i = 0; str[next] && str[next] != '\t'; i++) { + if (sscanf(str + next, "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d%n", + &tmp_int[0], &tmp_int[1], &tmp_int[2], &tmp_int[3], + &tmp_int[4], &tmp_int[5], &tmp_int[6], + &tmp_int[7], &tmp_int[8], &tmp_int[9], &tmp_int[10], &tmp_int[11], &len) == 12) { + // do nothing, it's ok + } else if(sscanf(str + next, "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d%n", + &tmp_int[0], &tmp_int[1], &tmp_int[2], &tmp_int[3], + &tmp_int[4], &tmp_int[5], &tmp_int[6], + &tmp_int[7], &tmp_int[8], &tmp_int[9], &tmp_int[10], &len) == 11) { + tmp_int[11] = 0; // broken doesn't exist in this version -> 0 + } else // invalid structure + return -4; + p->inventory[i].id = tmp_int[0]; + p->inventory[i].nameid = tmp_int[1]; + p->inventory[i].amount = tmp_int[2]; + p->inventory[i].equip = tmp_int[3]; + p->inventory[i].identify = tmp_int[4]; + p->inventory[i].refine = tmp_int[5]; + p->inventory[i].attribute = tmp_int[6]; + p->inventory[i].card[0] = tmp_int[7]; + p->inventory[i].card[1] = tmp_int[8]; + p->inventory[i].card[2] = tmp_int[9]; + p->inventory[i].card[3] = tmp_int[10]; + p->inventory[i].broken = tmp_int[11]; + next += len; + if (str[next] == ' ') + next++; + } + + next++; + + for(i = 0; str[next] && str[next] != '\t'; i++) { + if (sscanf(str + next, "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d%n", + &tmp_int[0], &tmp_int[1], &tmp_int[2], &tmp_int[3], + &tmp_int[4], &tmp_int[5], &tmp_int[6], + &tmp_int[7], &tmp_int[8], &tmp_int[9], &tmp_int[10], &tmp_int[11], &len) == 12) { + // do nothing, it's ok + } else if (sscanf(str + next, "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d%n", + &tmp_int[0], &tmp_int[1], &tmp_int[2], &tmp_int[3], + &tmp_int[4], &tmp_int[5], &tmp_int[6], + &tmp_int[7], &tmp_int[8], &tmp_int[9], &tmp_int[10], &len) == 11) { + tmp_int[11] = 0; // broken doesn't exist in this version -> 0 + } else // invalid structure + return -5; + p->cart[i].id = tmp_int[0]; + p->cart[i].nameid = tmp_int[1]; + p->cart[i].amount = tmp_int[2]; + p->cart[i].equip = tmp_int[3]; + p->cart[i].identify = tmp_int[4]; + p->cart[i].refine = tmp_int[5]; + p->cart[i].attribute = tmp_int[6]; + p->cart[i].card[0] = tmp_int[7]; + p->cart[i].card[1] = tmp_int[8]; + p->cart[i].card[2] = tmp_int[9]; + p->cart[i].card[3] = tmp_int[10]; + p->cart[i].broken = tmp_int[11]; + next += len; + if (str[next] == ' ') + next++; + } + + next++; + + for(i = 0; str[next] && str[next] != '\t'; i++) { + set = sscanf(str + next, "%d,%d%n", &tmp_int[0], &tmp_int[1], &len); + if (set != 2) + return -6; + p->skill[tmp_int[0]].id = tmp_int[0]; + p->skill[tmp_int[0]].lv = tmp_int[1]; + next += len; + if (str[next] == ' ') + next++; + } + + next++; + + for(i = 0; str[next] && str[next] != '\t' && str[next] != '\n' && str[next] != '\r'; i++) { // global_reg実装以前のathena.txt互換のため一応'\n'チェック + set = sscanf(str + next, "%[^,],%d%n", p->global_reg[i].str, &p->global_reg[i].value, &len); + if (set != 2) { + // because some scripts are not correct, the str can be "". So, we must check that. + // If it's, we must not refuse the character, but just this REG value. + // Character line will have something like: nov_2nd_cos,9 ,9 nov_1_2_cos_c,1 (here, ,9 is not good) + if (str[next] == ',' && sscanf(str + next, ",%d%n", &p->global_reg[i].value, &len) == 1) + i--; + else + return -7; + } + next += len; + if (str[next] == ' ') + next++; + } + p->global_reg_num = i; + + return 1; +} + +//========================================================================================================== +int mmo_char_tosql(int char_id, struct mmo_charstatus *p){ + int i,save_flag; + + save_flag = char_id; + printf("request save char data... (%d)\n",char_id); + + //`char`( `char_id`,`account_id`,`char_num`,`name`,`class`,`base_level`,`job_level`,`base_exp`,`job_exp`,`zeny`, //9 + //`str`,`agi`,`vit`,`int`,`dex`,`luk`, //15 + //`max_hp`,`hp`,`max_sp`,`sp`,`status_point`,`skill_point`, //21 + //`option`,`karma`,`manner`,`party_id`,`guild_id`,`pet_id`, //27 + //`hair`,`hair_color`,`clothes_color`,`weapon`,`shield`,`head_top`,`head_mid`,`head_bottom`, //35 + //`last_map`,`last_x`,`last_y`,`save_map`,`save_x`,`save_y`) + sprintf(tmp_sql ,"INSERT INTO `char` SET `char_id`='%d', `account_id`='%d', `char_num`='%d', `name`='%s', `class`='%d', `base_level`='%d', `job_level`='%d'," + "`base_exp`='%d', `job_exp`='%d', `zeny`='%d'," + "`max_hp`='%d',`hp`='%d',`max_sp`='%d',`sp`='%d',`status_point`='%d',`skill_point`='%d'," + "`str`='%d',`agi`='%d',`vit`='%d',`int`='%d',`dex`='%d',`luk`='%d'," + "`option`='%d',`karma`='%d',`manner`='%d',`party_id`='%d',`guild_id`='%d',`pet_id`='%d'," + "`hair`='%d',`hair_color`='%d',`clothes_color`='%d',`weapon`='%d',`shield`='%d',`head_top`='%d',`head_mid`='%d',`head_bottom`='%d'," + "`last_map`='%s',`last_x`='%d',`last_y`='%d',`save_map`='%s',`save_x`='%d',`save_y`='%d', `partner_id` = '%d'", + char_id,p->account_id,p->char_num,p->name,p->class, p->base_level, p->job_level, + p->base_exp, p->job_exp, p->zeny, + p->max_hp, p->hp, p->max_sp, p->sp, p->status_point, p->skill_point, + p->str, p->agi, p->vit, p->int_, p->dex, p->luk, + p->option, p->karma, p->manner, p->party_id, p->guild_id, p->pet_id, + p->hair, p->hair_color, p->clothes_color, + p->weapon, p->shield, p->head_top, p->head_mid, p->head_bottom, + p->last_point.map, p->last_point.x, p->last_point.y, + p->save_point.map, p->save_point.x, p->save_point.y, p->partner_id + ); + + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (update `char`)- %s\n", mysql_error(&mysql_handle) ); + } + + //`memo` (`memo_id`,`char_id`,`map`,`x`,`y`) + sprintf(tmp_sql,"DELETE FROM `memo` WHERE `char_id`='%d'",char_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (delete `memo`)- %s\n", mysql_error(&mysql_handle) ); + } + + //insert here. + for(i=0;i<10;i++){ + if(p->memo_point[i].map[0]){ + sprintf(tmp_sql,"INSERT INTO `memo`(`char_id`,`map`,`x`,`y`) VALUES ('%d', '%s', '%d', '%d')", + char_id, p->memo_point[i].map, p->memo_point[i].x, p->memo_point[i].y); + if(mysql_query(&mysql_handle, tmp_sql) ) + printf("DB server Error (insert `memo`)- %s\n", mysql_error(&mysql_handle) ); + } + } + //`inventory` (`id`,`char_id`, `nameid`, `amount`, `equip`, `identify`, `refine`, `attribute`, `card0`, `card1`, `card2`, `card3`, `broken`) + sprintf(tmp_sql,"DELETE FROM `inventory` WHERE `char_id`='%d'",char_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (delete `inventory`)- %s\n", mysql_error(&mysql_handle) ); + } + + //insert here. + for(i=0;i<MAX_INVENTORY;i++){ + if(p->inventory[i].nameid){ + sprintf(tmp_sql,"INSERT INTO `inventory`(`id`,`char_id`, `nameid`, `amount`, `equip`, `identify`, `refine`, `attribute`, `card0`, `card1`, `card2`, `card3`, `broken`)" + " VALUES ('%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d')", + p->inventory[i].id, char_id,p->inventory[i].nameid, p->inventory[i].amount, p->inventory[i].equip, + p->inventory[i].identify, p->inventory[i].refine, p->inventory[i].attribute, + p->inventory[i].card[0], p->inventory[i].card[1], p->inventory[i].card[2], p->inventory[i].card[3], p->inventory[i].broken); + if(mysql_query(&mysql_handle, tmp_sql) ) + printf("DB server Error (insert `inventory`)- %s\n", mysql_error(&mysql_handle) ); + } + } + + //`cart_inventory` (`id`,`char_id`, `nameid`, `amount`, `equip`, `identify`, `refine`, `attribute`, `card0`, `card1`, `card2`, `card3`, `broken`) + sprintf (tmp_sql, "DELETE FROM `cart_inventory` WHERE `char_id`='%d'", char_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (delete `cart_inventory`)- %s\n", mysql_error(&mysql_handle) ); + } + + //insert here. + for(i=0;i<MAX_CART;i++){ + if(p->cart[i].nameid){ + sprintf(tmp_sql,"INSERT INTO `cart_inventory`(`id`,`char_id`, `nameid`, `amount`, `equip`, `identify`, `refine`, `attribute`, `card0`, `card1`, `card2`, `card3`, `broken`)" + " VALUES ('%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d', '%d' )", + p->cart[i].id, char_id, p->cart[i].nameid, p->cart[i].amount, p->cart[i].equip, + p->cart[i].identify, p->cart[i].refine, p->cart[i].attribute, + p->cart[i].card[0], p->cart[i].card[1], p->cart[i].card[2], p->cart[i].card[3], p->cart[i].broken ); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (insert `cart_inventory`)- %s\n", mysql_error(&mysql_handle) ); + } + } + } + + + //`skill` (`char_id`, `id`, `lv`) + sprintf(tmp_sql,"DELETE FROM `skill` WHERE `char_id`='%d'",char_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (delete `skill`)- %s\n", mysql_error(&mysql_handle) ); + } + + //insert here. + for(i=0;i<MAX_SKILL;i++){ + if(p->skill[i].id){ + if (p->skill[i].id && p->skill[i].flag!=1) { + sprintf(tmp_sql,"INSERT INTO `skill`(`char_id`,`id`, `lv`) VALUES ('%d', '%d', '%d')", + char_id, p->skill[i].id, (p->skill[i].flag==0)?p->skill[i].lv:p->skill[i].flag-2); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (insert `skill`)- %s\n", mysql_error(&mysql_handle) ); + } + } + } + } + //`global_reg_value` (`char_id`, `str`, `value`) + sprintf(tmp_sql,"DELETE FROM `global_reg_value` WHERE `char_id`='%d'",char_id); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (delete `global_reg_value`)- %s\n", mysql_error(&mysql_handle) ); + } + + //insert here. + for(i=0;i<p->global_reg_num;i++){ + if(p->global_reg[i].value !=0){ + sprintf(tmp_sql,"INSERT INTO `global_reg_value` (`char_id`, `str`, `value`) VALUES ('%d', '%s','%d')", + char_id, p->global_reg[i].str, p->global_reg[i].value); + if(mysql_query(&mysql_handle, tmp_sql) ) { + printf("DB server Error (insert `global_reg_value`)- %s\n", mysql_error(&mysql_handle) ); + } + } + } + + printf("saving char is done... (%d)\n",char_id); + save_flag = 0; + + return 0; +} +//========================================================================================================== + +int mmo_char_init(void){ + char line[65536]; + struct s_pet *p; + int ret; + int i=0,set,tmp_int[2], c= 0; + char input; + FILE *fp; + + + //DB connection initialized + mysql_init(&mysql_handle); + printf("Connect DB server.... (inter server)\n"); + if(!mysql_real_connect(&mysql_handle, db_server_ip, db_server_id, db_server_pw, + db_server_logindb ,db_server_port, (char *)NULL, 0)) { + //pointer check + printf("%s\n",mysql_error(&mysql_handle)); + exit(1); + } + else { + printf ("connect success! (inter server)\n"); + } + + + + printf("Warning : Make sure you backup your databases before continuing!\n"); + printf("\nDo you wish to convert your Character Database to SQL? (y/n) : "); + input=getchar(); + if(input == 'y' || input == 'Y'){ + printf("\nConverting Character Database...\n"); + fp=fopen("save/athena.txt","r"); + char_dat=malloc(sizeof(char_dat[0])*256); + char_max=256; + if(fp==NULL) + return 0; + while(fgets(line, 65535, fp)){ + if(char_num>=char_max){ + char_max+=256; + char_dat=realloc(char_dat, sizeof(char_dat[0]) *char_max); + } + memset(&char_dat[char_num], 0, sizeof(char_dat[0])); + ret=mmo_char_fromstr(line, &char_dat[char_num]); + if(ret){ + mmo_char_tosql(char_dat[char_num].char_id , &char_dat[char_num]); + printf ("convert %d -> DB\n",char_dat[char_num].char_id); + if(char_dat[char_num].char_id>=char_id_count) + char_id_count=char_dat[char_num].char_id+1; + char_num++; + } + } + printf("char data convert end\n"); + fclose(fp); + } + + while(getchar() != '\n'); + printf("\nDo you wish to convert your Storage Database to SQL? (y/n) : "); + input=getchar(); + if(input == 'y' || input == 'Y') { + printf("\nConverting Storage Database...\n"); + fp=fopen(storage_txt,"r"); + if(fp==NULL){ + printf("cant't read : %s\n",storage_txt); + return 0; + } + + while(fgets(line,65535,fp)){ + set=sscanf(line,"%d,%d",&tmp_int[0],&tmp_int[1]); + if(set==2) { + if(i==0){ + storage=malloc(sizeof(struct storage)); + }else{ + storage=realloc(storage,sizeof(struct storage)*(i+1)); + } + memset(&storage[i],0,sizeof(struct storage)); + storage[i].account_id=tmp_int[0]; + storage_fromstr(line,&storage[i]); + storage_tosql(tmp_int[0],&storage[i]); //to sql. (dump) + i++; + } + } + fclose(fp); + } + + while(getchar() != '\n'); + printf("\nDo you wish to convert your Pet Database to SQL? (y/n) : "); + input=getchar(); + if(input == 'y' || input == 'Y') { + printf("\nConverting Pet Database...\n"); + if( (fp=fopen(pet_txt,"r")) ==NULL ) + return 1; + + p=malloc(sizeof(struct s_pet)); + while(fgets(line, sizeof(line), fp)){ + if(p==NULL){ + printf("int_pet: out of memory!\n"); + exit(0); + } + if(inter_pet_fromstr(line, p)==0 && p->pet_id>0){ + //pet dump + inter_pet_tosql(p->pet_id,p); + }else{ + printf("int_pet: broken data [%s] line %d\n", pet_txt, c); + } + c++; + } + fclose(fp); + } + + return 0; +} +int inter_config_read(const char *cfgName) { + printf ("start reading interserver configuration: %s\n",cfgName); + int i; + char line[1024], w1[1024], w2[1024]; + FILE *fp; + + fp=fopen(cfgName,"r"); + if(fp==NULL){ + printf("file not found: %s\n", cfgName); + return 1; + } + while(fgets(line, 1020, fp)){ + i=sscanf(line,"%[^:]:%s", w1, w2); + if(i!=2) + continue; + if(strcmpi(w1,"storage_txt")==0){ + printf ("set storage_txt : %s\n",w2); + strncpy(storage_txt, w2, sizeof(storage_txt)); + } else if(strcmpi(w1,"pet_txt")==0){ + printf ("set pet_txt : %s\n",w2); + strncpy(pet_txt, w2, sizeof(pet_txt)); + } + //add for DB connection + else if(strcmpi(w1,"db_server_ip")==0){ + strcpy(db_server_ip, w2); + printf ("set db_server_ip : %s\n",w2); + } + else if(strcmpi(w1,"db_server_port")==0){ + db_server_port=atoi(w2); + printf ("set db_server_port : %s\n",w2); + } + else if(strcmpi(w1,"db_server_id")==0){ + strcpy(db_server_id, w2); + printf ("set db_server_id : %s\n",w2); + } + else if(strcmpi(w1,"db_server_pw")==0){ + strcpy(db_server_pw, w2); + printf ("set db_server_pw : %s\n",w2); + } + else if(strcmpi(w1,"db_server_logindb")==0){ + strcpy(db_server_logindb, w2); + printf ("set db_server_logindb : %s\n",w2); + } + } + fclose(fp); + + printf ("success reading interserver configuration\n"); + + return 0; +} + +int char_config_read(const char *cfgName) { + printf ("start reading interserver configuration: %s\n",cfgName); + int i; + char line[1024], w1[1024], w2[1024]; + FILE *fp; + + fp=fopen(cfgName,"r"); + if(fp==NULL){ + printf("file not found: %s\n", cfgName); + return 1; + } + + while(fgets(line, 1020, fp)){ + if(line[0] == '/' && line[1] == '/') + continue; + + i=sscanf(line,"%[^:]:%s", w1, w2); + if(i!=2) + continue; + if(strcmpi(w1,"char_txt")==0){ + printf ("set char_txt : %s\n",w2); + strcpy(char_txt, w2); + } + } + fclose(fp); + printf("reading configure done.....\n"); + + return 0; +} + +int do_init(int argc, char **argv){ + + + char_config_read((argc>1)?argv[1]:CHAR_CONF_NAME); + inter_config_read((argc>2)?argv[2]:inter_cfgName); + + mmo_char_init(); + printf ("all conversion success!\n"); + exit (0); + return 0; +} + + diff --git a/src/txt-converter/char/char.h b/src/txt-converter/char/char.h new file mode 100644 index 0000000..4712b4d --- /dev/null +++ b/src/txt-converter/char/char.h @@ -0,0 +1,38 @@ +#include "../../common/core.h" +#include "../../common/socket.h" +#include "../../common/timer.h" +#include "../common/mmo.h" +#include "../../common/version.h" +#include "../../common/db.h" + +#ifndef _CHAR_H_ +#define _CHAR_H_ + +#define MAX_MAP_SERVERS 30 + +#define CHAR_CONF_NAME "conf/char_athena.conf" + +#define UNKNOWN_CHAR_NAME "Unknown" + +#define DEFAULT_AUTOSAVE_INTERVAL 300*1000 + +struct mmo_map_server{ + long ip; + short port; + int users; + char map[MAX_MAP_PER_SERVER][16]; +}; + +int mapif_sendall(unsigned char *buf,unsigned int len); +int mapif_sendallwos(int fd,unsigned char *buf,unsigned int len); +int mapif_send(int fd,unsigned char *buf,unsigned int len); + +extern int autosave_interval; + +#endif + +#include "inter.h" +#include "int_pet.h" +#include "int_guild.h" +#include "int_party.h" +#include "int_storage.h" diff --git a/src/txt-converter/char/int_guild.h b/src/txt-converter/char/int_guild.h new file mode 100644 index 0000000..2ea8594 --- /dev/null +++ b/src/txt-converter/char/int_guild.h @@ -0,0 +1,10 @@ +#ifndef _INT_GUILD_H_ +#define _INT_GUILD_H_ + +int inter_guild_init(); +int inter_guild_save(); +int inter_guild_parse_frommap(int fd); + +extern char guild_txt[256]; + +#endif diff --git a/src/txt-converter/char/int_party.h b/src/txt-converter/char/int_party.h new file mode 100644 index 0000000..036db1a --- /dev/null +++ b/src/txt-converter/char/int_party.h @@ -0,0 +1,11 @@ +#ifndef _INT_PARTY_H_ +#define _INT_PARTY_H_ + +int inter_party_init(); +int inter_party_save(); + +int inter_party_parse_frommap(int fd); + +extern char party_txt[256]; + +#endif diff --git a/src/txt-converter/char/int_pet.h b/src/txt-converter/char/int_pet.h new file mode 100644 index 0000000..27ba4fc --- /dev/null +++ b/src/txt-converter/char/int_pet.h @@ -0,0 +1,12 @@ +#ifndef _INT_PET_H_ +#define _INT_PET_H_ + +int inter_pet_init(); +int inter_pet_save(); +int inter_pet_delete(int pet_id); + +int inter_pet_parse_frommap(int fd); + +//extern char pet_txt[256]; + +#endif diff --git a/src/txt-converter/char/int_storage.h b/src/txt-converter/char/int_storage.h new file mode 100644 index 0000000..3572ae5 --- /dev/null +++ b/src/txt-converter/char/int_storage.h @@ -0,0 +1,11 @@ +#ifndef _INT_STORAGE_H_ +#define _INT_STORAGE_H_ + +int inter_storage_init(); +int inter_storage_save(); + +int inter_storage_parse_frommap(int fd); + +//extern char storage_txt[256]; + +#endif diff --git a/src/txt-converter/char/inter.h b/src/txt-converter/char/inter.h new file mode 100644 index 0000000..ee39944 --- /dev/null +++ b/src/txt-converter/char/inter.h @@ -0,0 +1,28 @@ +#ifndef _INTER_H_ +#define _INTER_H_ + +int inter_init(const char *file); +int inter_save(); +int inter_parse_frommap(int fd); + +int inter_check_length(int fd,int length); + +#define inter_cfgName "conf/inter_athena.conf" + + +//add include for DBMS(mysql) +#include <mysql.h> + +extern MYSQL mysql_handle; +extern char tmp_sql[65535]; +extern MYSQL_RES* sql_res ; +extern MYSQL_ROW sql_row ; +extern int sql_cnt; + +extern int db_server_port; +extern char db_server_ip[16]; +extern char db_server_id[32]; +extern char db_server_pw[32]; +extern char db_server_logindb[32]; + +#endif diff --git a/src/txt-converter/char/strlib.c b/src/txt-converter/char/strlib.c new file mode 100644 index 0000000..60803c1 --- /dev/null +++ b/src/txt-converter/char/strlib.c @@ -0,0 +1,66 @@ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "strlib.h" + +//----------------------------------------------- +// string lib. +unsigned char* jstrescape (unsigned char* pt) { + //copy from here + unsigned char * ptr; + int i =0, j=0; + + //copy string to temporary + ptr = malloc(J_MAX_MALLOC_SIZE); + strcpy (ptr,pt); + + while (ptr[i] != '\0') { + switch (ptr[i]) { + case '\'': + pt[j++] = '\\'; + pt[j++] = ptr[i++]; + break; + default: + pt[j++] = ptr[i++]; + } + } + pt[j++] = '\0'; + free (ptr); + return (unsigned char*) &pt[0]; +} + +unsigned char* jstrescapecpy (unsigned char* pt,unsigned char* spt) { + //copy from here + int i =0, j=0; + + while (spt[i] != '\0') { + switch (spt[i]) { + case '\'': + pt[j++] = '\\'; + pt[j++] = spt[i++]; + break; + default: + pt[j++] = spt[i++]; + } + } + pt[j++] = '\0'; + return (unsigned char*) &pt[0]; +} +int jmemescapecpy (unsigned char* pt,unsigned char* spt, int size) { + //copy from here + int i =0, j=0; + + while (i < size) { + switch (spt[i]) { + case '\'': + pt[j++] = '\\'; + pt[j++] = spt[i++]; + break; + default: + pt[j++] = spt[i++]; + } + } + // copy size is 0 ~ (j-1) + return j; +} diff --git a/src/txt-converter/char/strlib.h b/src/txt-converter/char/strlib.h new file mode 100644 index 0000000..442cfac --- /dev/null +++ b/src/txt-converter/char/strlib.h @@ -0,0 +1,9 @@ +#ifndef _J_STR_H_ +#define _J_STR_H_ +#define J_MAX_MALLOC_SIZE 65535 +//string functions. +//code by Jioh L. Jung +unsigned char* jstrescape (unsigned char* pt); +unsigned char* jstrescapecpy (unsigned char* pt,unsigned char* spt); +int jmemescapecpy (unsigned char* pt,unsigned char* spt, int size); +#endif diff --git a/src/txt-converter/common/mmo.h b/src/txt-converter/common/mmo.h new file mode 100644 index 0000000..df42783 --- /dev/null +++ b/src/txt-converter/common/mmo.h @@ -0,0 +1,280 @@ +// Original : mmo.h 2003/03/14 12:07:02 Rev.1.7 + +#ifndef _MMO_H_ +#define _MMO_H_ + +#include <time.h> + +#ifdef CYGWIN +// txtやlogなどの書き出すファイルの改行コード +#define RETCODE "\r\n" // (CR/LF:Windows系) +#else +#define RETCODE "\n" // (LF:Unix系) +#endif + +#define FIFOSIZE_SERVERLINK 128*1024 + +#define MAX_MAP_PER_SERVER 512 +#define MAX_INVENTORY 100 +#define MAX_AMOUNT 30000 +#define MAX_ZENY 1000000000 // 1G zeny +#define MAX_CART 100 +#define MAX_SKILL 450 +#define GLOBAL_REG_NUM 96 +#define ACCOUNT_REG_NUM 16 +#define ACCOUNT_REG2_NUM 16 +#define DEFAULT_WALK_SPEED 150 +#define MIN_WALK_SPEED 0 +#define MAX_WALK_SPEED 1000 +#define MAX_STORAGE 100 +#define MAX_GUILD_STORAGE 1000 +#define MAX_PARTY 12 +#define MAX_GUILD 56 // increased max guild members to accomodate for +2 increase for extension levels [Valaris] +#define MAX_GUILDPOSITION 56 // increased max guild positions to accomodate for all members [Valaris] +#define MAX_GUILDEXPLUSION 32 +#define MAX_GUILDALLIANCE 16 +#define MAX_GUILDSKILL 8 +#define MAX_GUILDCASTLE 24 // increased to include novice castles [Valaris] +#define MAX_GUILDLEVEL 50 + +#define MIN_HAIR_STYLE 0 +#define MAX_HAIR_STYLE 20 +#define MIN_HAIR_COLOR 0 +#define MAX_HAIR_COLOR 9 +#define MIN_CLOTH_COLOR 0 +#define MAX_CLOTH_COLOR 4 + +// for produce +#define MIN_ATTRIBUTE 0 +#define MAX_ATTRIBUTE 4 +#define ATTRIBUTE_NORMAL 0 +#define MIN_STAR 0 +#define MAX_STAR 3 + +#define MIN_PORTAL_MEMO 0 +#define MAX_PORTAL_MEMO 2 + +#define MAX_STATUS_TYPE 5 + +#define WEDDING_RING_M 2634 +#define WEDDING_RING_F 2635 + +#define CHAR_CONF_NAME "conf/char_athena.conf" + +struct item { + int id; + short nameid; + short amount; + unsigned short equip; + char identify; + char refine; + char attribute; + short card[4]; + short broken; +}; +struct point{ + char map[24]; + short x,y; +}; +struct skill { + unsigned short id,lv,flag; +}; +struct global_reg { + char str[32]; + int value; +}; +struct s_pet { + int account_id; + int char_id; + int pet_id; + short class; + short level; + short egg_id;//pet egg id + short equip;//pet equip name_id + short intimate;//pet friendly + short hungry;//pet hungry + char name[24]; + char rename_flag; + char incuvate; +}; + +struct mmo_charstatus { + int char_id; + int account_id; + int partner_id; + + int base_exp,job_exp,zeny; + + short class; + short status_point,skill_point; + int hp,max_hp,sp,max_sp; + short option,karma,manner; + short hair,hair_color,clothes_color; + int party_id,guild_id,pet_id; + + short weapon,shield; + short head_top,head_mid,head_bottom; + + char name[24]; + unsigned char base_level,job_level; + short str,agi,vit,int_,dex,luk; + unsigned char char_num,sex; + + struct point last_point,save_point,memo_point[10]; + struct item inventory[MAX_INVENTORY],cart[MAX_CART]; + struct skill skill[MAX_SKILL]; + int global_reg_num; + struct global_reg global_reg[GLOBAL_REG_NUM]; + int account_reg_num; + struct global_reg account_reg[ACCOUNT_REG_NUM]; + int account_reg2_num; + struct global_reg account_reg2[ACCOUNT_REG2_NUM]; +}; + +struct storage { + int account_id; + short storage_status; + short storage_amount; + struct item storage[MAX_STORAGE]; +}; + +struct guild_storage { + int guild_id; + short storage_status; + short storage_amount; + struct item storage[MAX_GUILD_STORAGE]; +}; + +struct map_session_data; + +struct gm_account { + int account_id; + int level; +}; + +struct party_member { + int account_id; + char name[24],map[24]; + int leader,online,lv; + struct map_session_data *sd; +}; +struct party { + int party_id; + char name[24]; + int exp; + int item; + struct party_member member[MAX_PARTY]; +}; + +struct guild_member { + int account_id, char_id; + short hair,hair_color,gender,class,lv; + int exp,exp_payper; + short online,position; + int rsv1,rsv2; + char name[24]; + struct map_session_data *sd; +}; +struct guild_position { + char name[24]; + int mode; + int exp_mode; +}; +struct guild_alliance { + int opposition; + int guild_id; + char name[24]; +}; +struct guild_explusion { + char name[24]; + char mes[40]; + char acc[40]; + int account_id; + int rsv1,rsv2,rsv3; +}; +struct guild_skill { + int id,lv; +}; +struct guild { + int guild_id; + short guild_lv, connect_member, max_member, average_lv; + int exp,next_exp,skill_point,castle_id; + char name[24],master[24]; + struct guild_member member[MAX_GUILD]; + struct guild_position position[MAX_GUILDPOSITION]; + char mes1[60],mes2[120]; + int emblem_len,emblem_id; + char emblem_data[2048]; + struct guild_alliance alliance[MAX_GUILDALLIANCE]; + struct guild_explusion explusion[MAX_GUILDEXPLUSION]; + struct guild_skill skill[MAX_GUILDSKILL]; +}; +struct guild_castle { + int castle_id; + char map_name[24]; + char castle_name[24]; + int guild_id; + int economy; + int defense; + int triggerE; + int triggerD; + int nextTime; + int payTime; + int createTime; + int visibleC; + int visibleG0; + int visibleG1; + int visibleG2; + int visibleG3; + int visibleG4; + int visibleG5; + int visibleG6; + int visibleG7; + int Ghp0; // added Guardian HP [Valaris] + int Ghp1; + int Ghp2; + int Ghp3; + int Ghp4; + int Ghp5; + int Ghp6; + int Ghp7; + int GID0; + int GID1; + int GID2; + int GID3; + int GID4; + int GID5; + int GID6; + int GID7; // end addition [Valaris] + +}; +struct square { + int val1[5]; + int val2[5]; +}; + +enum { + GBI_EXP =1, // ギルドのEXP + GBI_GUILDLV =2, // ギルドのLv + GBI_SKILLPOINT =3, // ギルドのスキルポイント + GBI_SKILLLV =4, // ギルドスキルLv + + GMI_POSITION =0, // メンバーの役職変更 + GMI_EXP =1, // メンバーのEXP + +}; + +#ifndef strcmpi +#define strcmpi strcasecmp +#endif +#ifndef stricmp +#define stricmp strcasecmp +#endif +#ifndef strncmpi +#define strncmpi strncasecmp +#endif +#ifndef strnicmp +#define strnicmp strncasecmp +#endif + +#endif // _MMO_H_ diff --git a/src/txt-converter/login/GNUmakefile b/src/txt-converter/login/GNUmakefile new file mode 100644 index 0000000..965a0e0 --- /dev/null +++ b/src/txt-converter/login/GNUmakefile @@ -0,0 +1,11 @@ +all: login-converter
+sql: login-converter
+
+COMMON_OBJ = ../../common/core.o ../../common/socket.o ../../common/timer.o ../../common/db.o ../../common/malloc.o
+COMMON_H = ../../common/core.h ../../common/socket.h ../../common/timer.h ../../common/mmo.h ../../common/version.h ../../common/db.h ../../common/malloc.h
+
+login-converter: login-converter.o ../../login_sql/md5calc.o ../../login_sql/strlib.o $(COMMON_OBJ)
+ $(CC) -o ../../../$@ $^ $(LIB_S)
+login-converter.o: login-converter.c ../../login_sql/login.h ../../login_sql/md5calc.h ../../login_sql/strlib.h $(COMMON_H)
+clean:
+ rm -f *.o ../../../login-converter
diff --git a/src/txt-converter/login/Makefile b/src/txt-converter/login/Makefile new file mode 100644 index 0000000..965a0e0 --- /dev/null +++ b/src/txt-converter/login/Makefile @@ -0,0 +1,11 @@ +all: login-converter
+sql: login-converter
+
+COMMON_OBJ = ../../common/core.o ../../common/socket.o ../../common/timer.o ../../common/db.o ../../common/malloc.o
+COMMON_H = ../../common/core.h ../../common/socket.h ../../common/timer.h ../../common/mmo.h ../../common/version.h ../../common/db.h ../../common/malloc.h
+
+login-converter: login-converter.o ../../login_sql/md5calc.o ../../login_sql/strlib.o $(COMMON_OBJ)
+ $(CC) -o ../../../$@ $^ $(LIB_S)
+login-converter.o: login-converter.c ../../login_sql/login.h ../../login_sql/md5calc.h ../../login_sql/strlib.h $(COMMON_H)
+clean:
+ rm -f *.o ../../../login-converter
diff --git a/src/txt-converter/login/login-converter.c b/src/txt-converter/login/login-converter.c new file mode 100644 index 0000000..c0055da --- /dev/null +++ b/src/txt-converter/login/login-converter.c @@ -0,0 +1,252 @@ +// $Id: login-converter.c,v 1.1.1.1 2004/09/10 17:45:03 MagicalTux Exp $ +// original : login2.c 2003/01/28 02:29:17 Rev.1.1.1.1 +// login data file to mysql conversion utility. +// +#include <sys/types.h> +#include <sys/socket.h> +#include <stdio.h> +#include <stdlib.h> +#include <netinet/in.h> +#include <sys/time.h> +#include <time.h> +#include <sys/ioctl.h> +#include <unistd.h> +#include <signal.h> +#include <fcntl.h> +#include <string.h> +#include <arpa/inet.h> + +#include <mysql.h> + +#include "../../common/core.h" +#include "../../common/socket.h" +#include "../../login/login.h" +#include "../../common/mmo.h" +#include "../../common/version.h" +#include "../../common/db.h" + +int account_id_count = START_ACCOUNT_NUM; +int server_num; +int new_account_flag = 0; +int login_port = 6900; + +struct mmo_char_server server[MAX_SERVERS]; +int server_fd[MAX_SERVERS]; + +#define AUTH_FIFO_SIZE 256 +struct { + int account_id,login_id1,login_id2; + int sex,delflag; +} auth_fifo[AUTH_FIFO_SIZE]; +int auth_fifo_pos=0; +struct { + int account_id, sex; + char userid[24], pass[24], lastlogin[24]; + int logincount; + int state; // packet 0x006a value + 1 (0: compte OK) + char email[40]; // e-mail (by default: a@a.com) + char error_message[20]; // Message of error code #6 = Your are Prohibited to log in until %s (packet 0x006a) + time_t ban_until_time; // # of seconds 1/1/1970 (timestamp): ban time limit of the account (0 = no ban) + time_t connect_until_time; // # of seconds 1/1/1970 (timestamp): Validity limit of the account (0 = unlimited) + char last_ip[16]; // save of last IP of connection + char memo[255]; // a memo field + int account_reg2_num; + struct global_reg account_reg2[ACCOUNT_REG2_NUM]; +} *auth_dat; +int auth_num=0,auth_max=0; + +char login_account_id[256]="account_id"; +char login_userid[256]="userid"; +char login_user_pass[256]="user_pass"; +char login_db[256]="login"; + +static struct dbt *gm_account_db; + +int db_server_port = 3306; +char db_server_ip[16] = "127.0.0.1"; +char db_server_id[32] = "ragnarok"; +char db_server_pw[32] = "ragnarok"; +char db_server_logindb[32] = "ragnarok"; + +int isGM(int account_id) +{ + struct gm_account *p; + p = numdb_search(gm_account_db,account_id); + if( p == NULL) + return 0; + return p->level; +} + +int read_gm_account() +{ + char line[8192]; + struct gm_account *p; + FILE *fp; + int c=0; + + gm_account_db = numdb_init(); + printf("gm_account: read start\n"); + + if( (fp=fopen("conf/GM_account.txt","r"))==NULL ) + return 1; + while(fgets(line,sizeof(line),fp)){ + if(line[0] == '/' && line[1] == '/') + continue; + p=malloc(sizeof(struct gm_account)); + if(p==NULL){ + printf("gm_account: out of memory!\n"); + exit(0); + } + if(sscanf(line,"%d %d",&p->account_id,&p->level) != 2 || p->level <= 0) { + printf("gm_account: broken data [conf/GM_account.txt] line %d\n",c); + } + else { + if(p->level > 99) + p->level = 99; + numdb_insert(gm_account_db,p->account_id,p); + } + c++; + } + fclose(fp); + printf("gm_account: read done (%d gm account ID)\n",c); + return 0; +} + +int mmo_auth_init(void) +{ + MYSQL mysql_handle; + char tmpsql[1024]; + MYSQL_RES* sql_res ; + MYSQL_ROW sql_row ; + + mysql_init(&mysql_handle); + if(!mysql_real_connect(&mysql_handle, db_server_ip, db_server_id, db_server_pw, + db_server_logindb ,db_server_port, (char *)NULL, 0)) { + //pointer check + printf("%s\n",mysql_error(&mysql_handle)); + exit(1); + } + else { + printf ("connect success!\n"); + } + printf ("convert start...\n"); + + FILE *fp; + int account_id, logincount, user_level, state, n, i; + char line[2048], userid[2048], pass[2048], lastlogin[2048], sex, email[2048], error_message[2048], last_ip[2048], memo[2048]; + time_t ban_until_time; + time_t connect_until_time; + char t_uid[256]; + + fp=fopen("save/account.txt","r"); + auth_dat=malloc(sizeof(auth_dat[0])*256); + auth_max=256; + if(fp==NULL) + return 0; + while(fgets(line,1023,fp)!=NULL){ + + if(line[0]=='/' && line[1]=='/') + continue; + + i = sscanf(line, "%d\t%[^\t]\t%[^\t]\t%[^\t]\t%c\t%d\t%d\t" + "%[^\t]\t%[^\t]\t%ld\t%[^\t]\t%[^\t]\t%ld%n", + &account_id, userid, pass, lastlogin, &sex, &logincount, &state, + email, error_message, &connect_until_time, last_ip, memo, &ban_until_time, &n); + + sprintf(tmpsql, "SELECT `%s`,`%s`,`%s`,`lastlogin`,`logincount`,`sex`,`connect_until`,`last_ip`,`ban_until`,`state`" + " FROM `%s` WHERE `%s`='%s'", login_account_id, login_userid, login_user_pass, login_db, login_userid, t_uid); + + if(mysql_query(&mysql_handle, tmpsql) ) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle) ); + } + user_level = isGM(account_id); + printf ("userlevel: %s (%d)- %d\n",userid, account_id, user_level); + sql_res = mysql_store_result(&mysql_handle) ; + sql_row = mysql_fetch_row(sql_res); //row fetching + if (!sql_row) //no row -> insert + sprintf(tmpsql, "INSERT INTO `login` (`account_id`, `userid`, `user_pass`, `lastlogin`, `sex`, `logincount`, `email`, `level`) VALUES (%d, '%s', '%s', '%s', '%c', %d, 'user@athena', %d);",account_id , userid, pass,lastlogin,sex,logincount, user_level); + else //row reside -> updating + sprintf(tmpsql, "UPDATE `login` SET `account_id`='%d', `userid`='%s', `user_pass`='%s', `lastlogin`='%s', `sex`='%c', `logincount`='%d', `email`='user@athena', `level`='%d'\nWHERE `account_id`='%d';",account_id , userid, pass,lastlogin,sex,logincount, user_level, account_id); + printf ("Query: %s\n",tmpsql); + mysql_free_result(sql_res) ; //resource free + if(mysql_query(&mysql_handle, tmpsql) ) { + printf("DB server Error - %s\n", mysql_error(&mysql_handle) ); + } + } + fclose(fp); + + printf ("convert end...\n"); + + return 0; +} + +// アカウントデ??ベ?スの書き込み +void nowork(void) +{ + //null +} + +int login_config_read(const char *cfgName){ + int i; + char line[1024], w1[1024], w2[1024]; + FILE *fp; + + fp=fopen(cfgName,"r"); + + if(fp==NULL){ + printf("file not found: %s\n", cfgName); + return 1; + } + printf ("start reading configuration...\n"); + while(fgets(line, 1020, fp)){ + if(line[0] == '/' && line[1] == '/') + continue; + + i=sscanf(line,"%[^:]:%s", w1, w2); + if(i!=2) + continue; + + + //add for DB connection + if(strcmpi(w1,"db_server_ip")==0){ + strcpy(db_server_ip, w2); + printf ("set db_server_ip : %s\n",w2); + } + else if(strcmpi(w1,"db_server_port")==0){ + db_server_port=atoi(w2); + printf ("set db_server_port : %s\n",w2); + } + else if(strcmpi(w1,"db_server_id")==0){ + strcpy(db_server_id, w2); + printf ("set db_server_id : %s\n",w2); + } + else if(strcmpi(w1,"db_server_pw")==0){ + strcpy(db_server_pw, w2); + printf ("set db_server_pw : %s\n",w2); + } + else if(strcmpi(w1,"db_server_logindb")==0){ + strcpy(db_server_logindb, w2); + printf ("set db_server_logindb : %s\n",w2); + } + } + fclose(fp); + printf ("End reading configuration...\n"); + return 0; +} + +int do_init(int argc,char **argv) +{ + char input; + login_config_read( (argc>1)?argv[1]:LOGIN_CONF_NAME ); + read_gm_account(); + + printf("\nWarning : Make sure you backup your databases before continuing!\n"); + printf("\nDo you wish to convert your Login Database to SQL? (y/n) : "); + input=getchar(); + if(input == 'y' || input == 'Y') + mmo_auth_init(); + + exit(0); +} + + diff --git a/src/webserver/Makefile b/src/webserver/Makefile new file mode 100644 index 0000000..f664a7d --- /dev/null +++ b/src/webserver/Makefile @@ -0,0 +1,26 @@ +cc = gcc
+
+all:
+ #Generate framework...
+ $(cc) -c parse.c
+ $(cc) -c generate.c
+ $(cc) -c htmlstyle.c
+ $(cc) -c logs.c
+
+ #Generate "pages"...
+ cd pages && $(cc) -c about.c && cd ..
+ cd pages && $(cc) -c sample.c && cd ..
+ cd pages && $(cc) -c notdone.c && cd ..
+
+ #Building the server...
+ $(cc) -o webserver main.c parse.o generate.o htmlstyle.o \
+ logs.o pages/about.o pages/sample.o pages/notdone.o
+
+
+
+
+clean:
+ rm -f *.o
+ rm -f pages/*.o
+ rm -f webserver
+
diff --git a/src/webserver/doc/API.txt b/src/webserver/doc/API.txt new file mode 100644 index 0000000..c80f7bd --- /dev/null +++ b/src/webserver/doc/API.txt @@ -0,0 +1,50 @@ +Here's the webserver API, so you can work on the webserver. + +My personal goal is to make this interface simple, so that coding it +will be like coding in some scripting language... + + + +char *get_param(char in_string[500], char swhat[500]); + +This function simply returns various data from the query string. + *Pass get_param NOTHING longer than 500 in length! + + What do I pass where in_string is? + The query string. + + What do I pass where swhat is? + One of two things... + Either 0 for the path of the 'page' + or you can pass it the param you wish to lookup. + + + + + + +char *get_query(char *inquery); + +This function simply returns a query string from the raw server request. +This is used once in main, I doubt you'll need it. + + + + + +void web_send(int sockin, char *in_data); + +Super easy way of sending data to a webpage! +Simply put in the socket name and then the data. + + Ex: + web_send(socket, "I like cheese!\n"); + + + + +char *html_header(char* title); +Easy way to print the eAthena header for the server. + + Ex: + web_send(sockethere, html_header("About")); diff --git a/src/webserver/doc/README b/src/webserver/doc/README new file mode 100644 index 0000000..edcabf1 --- /dev/null +++ b/src/webserver/doc/README @@ -0,0 +1,11 @@ +This readme is intended for the programmers of eAthena.
+
+This webserver's apis are in API.txt.
+
+To make this simple, generate.c should handle most of the work this sever does
+in terms of what people see.
+
+When a request is made the server shoots it off to generate.c.
+
+You are welcome to create more functions used by generate.c to generate pages
+though, so don't feel limited by that one file.
diff --git a/src/webserver/generate.c b/src/webserver/generate.c new file mode 100644 index 0000000..ad050db --- /dev/null +++ b/src/webserver/generate.c @@ -0,0 +1,38 @@ + +void generate_page(char password[25], int sock_in, char *query, char *ip) +{ + char *page = get_param(query, 0); + char *ppass = get_param(query, "password"); + + + if ( (ppass == 0) || (strcmp(password, ppass) != 0) ) + { + web_send(sock_in, html_header("Enter your password")); + web_send(sock_in, "<H1>NOT LOGGED IN!</H1><form action=\"/\" method=\"GET\">\n"); + web_send(sock_in, "Enter your password:<br>\n<input type=\"text\" name=\"password\">\n"); + web_send(sock_in, "<input type=\"submit\" value=\"Login\">\n"); + } + else + { + + + //To make this simple, we will have a bunch of if statements + //that then shoot out data off into functions. + + + //The 'index' + if ( strcmp(page, "/") == 0 ) + generate_notdone(sock_in, query, ip); + + + //About page: + if ( strcmp(page, "/about.html") == 0 ) + generate_about(sock_in, query, ip); + + + //Test page: + if ( strcmp(page, "/testing/") == 0 ) + generate_sample(sock_in, query, ip); + + } +} diff --git a/src/webserver/htmlstyle.c b/src/webserver/htmlstyle.c new file mode 100644 index 0000000..c3a4b92 --- /dev/null +++ b/src/webserver/htmlstyle.c @@ -0,0 +1,51 @@ +char output[10000]; + +char *html_header(char *title) +{ + memset(output, 0x0, 10000); + char *text = "<body text=\"#000000\" bgcolor=\"#939393\" link=\"#0033FF\">\n" + "<br><table width=\"92%\" cellspacing=\"1\" cellpadding=\"0\" border=\"0\"\n" + "align=\"center\" class=\"bordercolor\"><tbody><tr><td class=\"bordercolor\" width=\"100%\">\n" + "<table bgcolor=\"#ffffff\" width=\"100%\" cellspacing=\"0\" cellpadding=\"0\">\n" + "<tbody><tr><td><table border=\"0\" width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" bgcolor=\"#ffffff\">\n" + "<tbody><tr><img src=\"http://eathena.sourceforge.net/athena.jpg\" alt=\"Athena\">\n" + "<td bgcolor=\"#ffffff\"></td></tr></tbody></table></td></tr></tbody></table>\n" + "</td></tr><tr align=\"left\"><td class=\"bordercolor\"><table bgcolor=\"#c6c6c6\" width=\"100%\" cellspacing=\"0\"\n" + "cellpadding=\"0\" style=\"text-align: left; margin-right: auto; margin-left: 0px;\">\n"; + "<tbody><tr><td width=\"100%\" align=\"center\"><table border=\"0\" width=\"100%\" cellpadding=\"3\"\n" + "cellspacing=\"0\" bgcolor=\"#c6c6c6\" align=\"center\"><tbody><tr>" + "<td valign=\"middle\" bgcolor=\"#c6c6c6\" align=\"center\"><a href=\"/cgi-bin/forum/YaBB.cgi\">" + "<span style=\"text-decoration: underline;\"><span style=\"font-weight: bold;\">\n" + "To the Forum</span></span></a><br></td></tr></tbody></table></td></tr></tbody>\n" + "</table></td></tr><tr><td class=\"bordercolor\" align=\"center\">\n" + "<table bgcolor=\"#ffffff\" width=\"100%\" cellspacing=\"0\" cellpadding=\"0\" align=\"center\">\n" + "<tbody><tr><td width=\"100%\" align=\"center\"><table border=\"0\" width=\"100%\" cellpadding=\"5\"\n" + "cellspacing=\"0\" bgcolor=\"#ffffff\" align=\"center\"><tbody><tr>\n" + "<td valign=\"middle\" bgcolor=\"#ffffff\" align=\"center\"><font size=\"2\" color=\"#6e94b7\">\n" + "<b>Athena</b> « Portal »</font></td></tr></tbody></table></td></tr></tbody>" + "</table></td></tr></tbody></table>\n"; + + sprintf(output, "<title>%s</title>\n%s\n", title, text); + + return output; +} + + + +char *html_start_form(char *location, char *action) +{ + memset(output, 0x0, 10000); + sprintf(output, "<form action=\"%s\" method=\"%s\">", location, action); + return output; + + +} + + +char *html_end_forum(void) +{ + return "</form>"; +} + + + diff --git a/src/webserver/logs.c b/src/webserver/logs.c new file mode 100644 index 0000000..405b488 --- /dev/null +++ b/src/webserver/logs.c @@ -0,0 +1,8 @@ +#include <time.h> + +void log_visit(char *query, char *ip) +{ + time_t timer; + timer=time(NULL); + printf("%s - \"%s\" - %s", ip, query, asctime(localtime(&timer))); +} diff --git a/src/webserver/main.c b/src/webserver/main.c new file mode 100644 index 0000000..5936255 --- /dev/null +++ b/src/webserver/main.c @@ -0,0 +1,142 @@ +/*************************************************************************** + description + ------------------- + author : (C) 2004 by Michael J. Flickinger + email : mjflick@cpan.org + + ***************************************************************************/ + +/*************************************************************************** + * * + * 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 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> +#include <string.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <sys/wait.h> +#include <signal.h> + +#define BLOG 10 + +char *header = "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\n"; +char recvin[500], password[25]; +int s_port; + +void sigchld_handler(int s) +{ + while(wait(NULL) > 0); +} + +int main(int argc, char **argv) +{ + if (argc < 3) + { + printf("eAthena Web Server\n"); + printf("usage: %s [password] [port]\n", argv[0]); + exit(0); + } + + s_port = atoi(argv[2]); + + if ((s_port < 1) || (s_port > 65534)) + { + printf("Error: The port you choose is not valid port.\n"); + exit(0); + } + + if (strlen(argv[1]) > 25) + { + printf("Error: Your password is too long.\n"); + printf("It must be shorter than 25 characters.\n"); + exit(0); + } + + memset(password, 0x0, 25); + memcpy(password, argv[1], strlen(argv[1])); + + int sockfd, new_fd; + struct sockaddr_in my_addr; + struct sockaddr_in their_addr; + int sin_size; + + struct sigaction sa; + + int yes=1; + + if ((sockfd = socket(AF_INET, SOCK_STREAM,0)) == -1) + { + perror("Darn, this is broken."); + exit(0); + } + + if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1) + { + perror("Error... :-("); + } + + //Now we know we have a working socket. :-) + + my_addr.sin_family = AF_INET; + my_addr.sin_port = htons(s_port); + my_addr.sin_addr.s_addr = INADDR_ANY; + memset(&(my_addr.sin_zero), '\0', 8); + + if ( bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr)) == -1) + { + perror("can not bind to this port"); + exit(0); + } + + if ( listen(sockfd, BLOG) == -1) + { + perror("can not listen on port"); + exit(0); + } + + sa.sa_handler = sigchld_handler; + + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_RESTART; + + if (sigaction(SIGCHLD, &sa, NULL) == -1) + { + perror("sigaction sucks"); + exit(0); + } + + printf("The eAthena webserver is up and listening on port %i.\n", s_port); + + while(1) + { + sin_size = sizeof(struct sockaddr_in); + new_fd = accept(sockfd, (struct sockaddr *)&their_addr, &sin_size); + + if (!fork()) + { + close(sockfd); + memset(recvin, 0x0, 500); + recv(new_fd, recvin, 500, 0); + send(new_fd, header, strlen(header), 0); + generate_page(password, new_fd, get_query(recvin), inet_ntoa(their_addr.sin_addr)); + log_visit(get_query(recvin), inet_ntoa(their_addr.sin_addr)); + + close(new_fd); + exit(0); + } + close(new_fd); + } + + return 0; +} diff --git a/src/webserver/pages/about.c b/src/webserver/pages/about.c new file mode 100644 index 0000000..2b0002a --- /dev/null +++ b/src/webserver/pages/about.c @@ -0,0 +1,6 @@ +void generate_about(int sock_in, char *query, char *ip) +{ +//printf("%s", html_header("About")); + web_send(sock_in, html_header("About")); + web_send(sock_in, "<center>eAthena Web Server!</center>\n"); +} diff --git a/src/webserver/pages/notdone.c b/src/webserver/pages/notdone.c new file mode 100644 index 0000000..a6492e3 --- /dev/null +++ b/src/webserver/pages/notdone.c @@ -0,0 +1,5 @@ +void generate_notdone(int sock_in, char *query, char *ip) +{ + web_send(sock_in, "<title>Not here!</title>\n"); + web_send(sock_in, "<h2><center>This page/feature is not done yet.</center>\n</h2>"); +} diff --git a/src/webserver/pages/sample.c b/src/webserver/pages/sample.c new file mode 100644 index 0000000..be900a1 --- /dev/null +++ b/src/webserver/pages/sample.c @@ -0,0 +1,24 @@ + + +void generate_sample(int sock_in, char *query, char *ip) +{ + + char *name = get_param(query, "name"); + + web_send(sock_in, "<title>SAMPLE</title>\n"); + + + //If a name was not entered... + if ( name == '\0' ) + { + web_send(sock_in, "<form action=\"/testing/\" method=\"GET\">\n"); + web_send(sock_in, "<input type=\"text\" name=\"name\">\n"); + web_send(sock_in, "<input type=\"submit\">\n"); + } + else + { + web_send(sock_in, "Your name is: "); + web_send(sock_in, get_param(query, "name")); + } +printf("OK!\n"); +} diff --git a/src/webserver/parse.c b/src/webserver/parse.c new file mode 100644 index 0000000..8e54a81 --- /dev/null +++ b/src/webserver/parse.c @@ -0,0 +1,135 @@ +#include <stdlib.h> + +char filtered_query[2000]; +char rdata[500]; +char param_n[500]; +char param_d[500]; + + +char *get_query(char *inquery) +{ + memset(filtered_query, 0x0, 2000); + sscanf(inquery, "GET %s %[$]", filtered_query); + return(filtered_query); +} + +void web_send(int sockin, char *in_data) +{ + send(sockin, in_data, strlen(in_data), 0); +} + + +//THIS IS BAD CODE BE CAREFULL WITH IT! +//Watch out for buffer overflow... +//When using please make sure to check the string size. + +//Also note: +//I take no pride in this code, it is a really bad way of doing this... +char *get_param(char in_string[500], char swhat[500]) +{ + int i = 0; + int marker, iswitch, pint, dint; + char flux[500]; + memset(flux, 0x0, 500); + + //Get the path of out "page" + if (swhat == 0) + { + //while i is not equal to array size + while (i != 500) + { + //if there is a question mark, halt! + if (in_string[i] == '?') + { + i = 499; + } + else + rdata[i] = in_string[i]; + + i++; + } + return rdata; + } + else //so, we want a param... + { + //calculate where param begins + while (i != 500) + { + if (in_string[i] == '?') + { + marker = i + 1; + i = 499; + } + i++; + } + + i = 0; + + //keep morons from trying to crash this + if ((marker > 500)||(marker < 1)) + marker = 500; + + while(marker != 500) + { + if ((in_string[marker] != '&') && (in_string[marker] != '\0')) + { + flux[i] = in_string[marker]; + i++; + } + else + { + + //we have a param, now we must dig through it + + //clear temp vars + memset(param_n, 0x0, 500); + memset(param_d, 0x0, 500); + iswitch = 0; + pint = 0; + dint = 0; + i = 0; + + //split result into param_n and param_d + while(i != 500) + { + if ( (flux[i] != '=') && (flux[i] != '\0') ) + { + if (iswitch == 0) + { + param_n[pint] = flux[i]; + pint++; + } + else + { + param_d[dint] = flux[i]; + dint++; + } + } + else + { + iswitch = 1; + } + if (flux[i] == '\0') + i = 499; + + i++; + } + + if ( strcmp(param_n, swhat) == 0 ) + { + return param_d; + } + + i = 0; + } + + if (in_string[marker] == '\0') + { + marker = 499; + } + marker++; + } + return 0; + } +} + diff --git a/src/xand1_1.patch b/src/xand1_1.patch new file mode 100644 index 0000000..5715664 --- /dev/null +++ b/src/xand1_1.patch @@ -0,0 +1,132 @@ +--- npc/tulimshar/monster_guide.txt 2005-08-05 23:03:44.144096000 +0200 ++++ npc/tulimshar/monster_guide.txt.new 2005-08-08 14:03:51.154608000 +0200 +@@ -1,91 +1,3 @@ +-new_3-1.gat,53,185,0 script ConquestMob0 -1,{ +-OnInit: +-// all monsters ingame by 31.Jul 2005 sorted by map, monsterID +-areamonster "new_1-1.gat",15,17,105,103,"RedScorpion",1004, 1,"ConquestMob-new_1-1::OnGuardianDied1004"; +-areamonster "new_1-1.gat",15,17,105,103,"GreenSlime",1005, 50,"ConquestMob-new_1-1::OnGuardianDied1005"; +-areamonster "new_1-1.gat",15,17,105,103,"GiantMaggot",1006, 30,"ConquestMob-new_1-1::OnGuardianDied1006"; +-areamonster "new_2-1.gat",31,31,90,97,"RedSlime",1008, 24,"ConquestMob-new_2-1::OnGuardianDied1008"; +-areamonster "new_2-1.gat",53,34,96,36,"RedSlime",1008, 6,"ConquestMob-new_2-1::OnGuardianDied1008b"; +-areamonster "new_2-1.gat",31,31,90,97,"BlackScorpion",1009, 15,"ConquestMob-new_2-1::OnGuardianDied1009"; +-areamonster "new_2-1.gat",84,52,93,91,"BlackScorpion",1009, 5,"ConquestMob-new_2-1::OnGuardianDied1009a"; +-areamonster "new_3-1.gat",22,42,142,79,"Maggot",1002, 35,"ConquestMob-new_3-1::OnGuardianDied1002"; +-areamonster "new_3-1.gat",22,42,142,79,"Scorpion",1003, 10,"ConquestMob-new_3-1::OnGuardianDied1003"; +-areamonster "new_5-1.gat",32,32,90,100,"YellowSlime",1007, 20,"ConquestMob-new_5-1::OnGuardianDied1007"; +-areamonster "new_5-1.gat",88,33,98,42,"RedSlime",1008, 3,"ConquestMob-new_5-1::OnGuardianDied1008a"; +-areamonster "new_5-1.gat",32,32,90,100,"Spider",1012, 8,"ConquestMob-new_5-1::OnGuardianDied1012"; +-areamonster "new_5-1.gat",81,32,85,38,"Spider",1012, 2,"ConquestMob-new_5-1::OnGuardianDied1012a"; +-areamonster "new_7-1.gat",22,27,176,174,"Snake",1010, 15,"ConquestMob-new_7-1::OnGuardianDied1010"; +-break; +-} +-new_1-1.gat,53,185,0 script ConquestMob-new_1-1 -1,{ +-// event when mob dies +-OnGuardianDied1004: +- if (MPQUEST == 1) set Mobpt,Mobpt+42; +- areamonster "new_1-1.gat",15,17,105,103,"RedScorpion",1004, 1,"ConquestMob-new_1-1::OnGuardianDied1004"; +- break; +-OnGuardianDied1005: +- if (MPQUEST == 1) set Mobpt,Mobpt+5; +- areamonster "new_1-1.gat",15,17,105,103,"GreenSlime",1005, 1,"ConquestMob-new_1-1::OnGuardianDied1005"; +- break; +-OnGuardianDied1006: +- if (MPQUEST == 1) set Mobpt,Mobpt+14; +- areamonster "new_1-1.gat",15,17,105,103,"GiantMaggot",1006, 1,"ConquestMob-new_1-1::OnGuardianDied1006"; +- break; +-} +-new_2-1.gat,53,185,0 script ConquestMob-new_2-1 -1,{ +-OnGuardianDied1008: +- if (MPQUEST == 1) set Mobpt,Mobpt+18; +- areamonster "new_2-1.gat",31,31,90,97,"RedSlime",1008, 1,"ConquestMob-new_2-1::OnGuardianDied1008"; +- break; +-OnGuardianDied1008b: +- if (MPQUEST == 1) set Mobpt,Mobpt+18; +- areamonster "new_2-1.gat",53,34,96,36,"RedSlime",1008, 1,"ConquestMob-new_2-1::OnGuardianDied1008b"; +- break; +-OnGuardianDied1009: +- if (MPQUEST == 1) set Mobpt,Mobpt+45; +- areamonster "new_2-1.gat",31,31,90,97,"BlackScorpion",1009, 1,"ConquestMob-new_2-1::OnGuardianDied1009"; +- break; +-OnGuardianDied1009a: +- if (MPQUEST == 1) set Mobpt,Mobpt+45; +- areamonster "new_2-1.gat",84,52,93,91,"BlackScorpion",1009, 1,"ConquestMob-new_2-1::OnGuardianDied1009a"; +- break; +-} +-new_3-1.gat,53,185,0 script ConquestMob-new_3-1 -1,{ +-OnGuardianDied1002: +- if (MPQUEST == 1) set Mobpt,Mobpt+1; +- areamonster "new_3-1.gat",22,42,142,79,"Maggot",1002, 1,"ConquestMob-new_3-1::OnGuardianDied1002"; +- break; +-OnGuardianDied1003: +- if (MPQUEST == 1) set Mobpt,Mobpt+2; +- areamonster "new_3-1.gat",22,42,142,79,"Scorpion",1003, 1,"ConquestMob-new_3-1::OnGuardianDied1003"; +- break; +-} +-new_5-1.gat,53,185,0 script ConquestMob-new_5-1 -1,{ +-OnGuardianDied1007: +- if (MPQUEST == 1) set Mobpt,Mobpt+9; +- areamonster "new_5-1.gat",32,32,90,100,"YellowSlime",1007, 1,"ConquestMob-new_5-1::OnGuardianDied1007"; +- break; +-// 3 Red Slimes guard treasure +-OnGuardianDied1008a: +- if (MPQUEST == 1) set Mobpt,Mobpt+18; +- areamonster "new_5-1.gat",88,33,98,42,"RedSlime",1008, 1,"ConquestMob-new_5-1::OnGuardianDied1008a"; +- break; +-OnGuardianDied1012: +- if (MPQUEST == 1) set Mobpt,Mobpt+56; +- areamonster "new_5-1.gat",32,32,90,100,"Spider",1012, 1,"ConquestMob-new_5-1::OnGuardianDied1012"; +- break; +-// 2 spiders guard entrance to treasure +-OnGuardianDied1012a: +- if (MPQUEST == 1) set Mobpt,Mobpt+56; +- areamonster "new_5-1.gat",81,32,85,38,"Spider",1012, 1,"ConquestMob-new_5-1::OnGuardianDied1012a"; +- break; +-} +-new_7-1.gat,53,185,0 script ConquestMob-new_7-1 -1,{ +-OnGuardianDied1010: +- if (MPQUEST == 1) set Mobpt,Mobpt+51; +- areamonster "new_7-1.gat",22,27,176,174,"Snake",1010, 1,"ConquestMob-new_7-1::OnGuardianDied1010"; +- break; +-} + new_3-1.gat,46,66,0 script MonsterGuide 102,{ + if(MPQUEST == 0) goto Register; + mes "[Monster Guide]"; +--- conf/map_athena.conf 2005-07-18 14:57:41.000000000 +0200 ++++ conf/map_athena.conf.new 2005-08-08 14:03:04.888080000 +0200 +@@ -57,7 +57,6 @@ + + // NPCs + +-npc: npc/tulimshar/monsters.txt + npc: npc/tulimshar/barber.txt + npc: npc/tulimshar/monster_guide.txt + npc: npc/tulimshar/ptsrewards.txt +@@ -68,10 +67,15 @@ + npc: npc/tulimshar/warps.txt + npc: npc/tulimshar/shop.txt + ++npc: npc/monsters/monsters-new_1-1.txt ++npc: npc/monsters/monsters-new_2-1.txt ++npc: npc/monsters/monsters-new_3-1.txt ++npc: npc/monsters/monsters-new_5-1.txt ++npc: npc/monsters/monsters-new_7-1.txt ++ + npc: npc/tonori/cave.txt + npc: npc/tonori/miners.txt + npc: npc/tonori/warps.txt +-npc: npc/tonori/monsters.txt + + npc: npc/guide.txt + npc: npc/nekkio.txt +--- db/mob_db.txt 2005-07-15 10:25:21.000000000 +0200 ++++ db/mob_db.txt.new 2005-08-08 14:23:23.680617600 +0200 +@@ -10,6 +10,7 @@ + 1007,Yellow_slime,Yellow Slime,1,400,0,20,2,1,20,25,2,7,10,8,2,1,34,1,1,1,1,0,20,131,1400,1800,672,480,534,200,519,100,501,350,502,250,522,10,909,0,909,0,0,0,0,0,0,0,,,,,, + 1008,Red_slime,Red Slime,1,400,0,65,56,1,30,50,2,7,15,10,2,1,25,1,1,1,1,0,20,135,1300,1500,672,480,1201,300,509,110,521,200,523,40,525,80,535,750,528,250,531,150,0,0,0,0,,,,,, + 1009,BlackScorpion,Black Scorpion,1,1100,0,200,70,1,80,90,4,6,20,20,10,10,35,10,1,1,1,0,20,133,1000,1500,672,480,523,150,509,100,518,800,909,0,909,0,909,0,909,0,0,0,0,0,0,0,,,,,, +-1010,Snake,Snake,1,2000,0,172,70,1,150,80,4,6,20,20,10,10,35,10,2,1,1,0,20,133,1000,1500,672,480,0,0,0,0,0,0,909,0,909,0,909,0,909,0,0,0,0,0,0,0,,,,,, ++1010,Snake,Snake,1,2200,0,250,120,1,150,80,4,6,15,25,10,10,45,5,2,1,1,0,20,133,1000,1500,672,480,524,50,527,500,0,0,909,0,909,0,909,0,909,0,0,0,0,0,0,0,,,,,, + 1011,Fire_Goblin,Fire Goblin,1,50,0,3,2,1,7,10,0,5,1,1,1,0,6,30,1,1,1,3,21,129,800,1872,672,480,505,800,501,150,518,800,502,150,521,70,522,1,909,0,0,0,0,0,0,0,,,,,, + 1012,Spider,Spider,1,2000,0,230,90,1,150,80,4,6,20,20,10,10,35,10,2,1,1,0,25,175,1000,1500,672,480,537,500,535,100,0,0,909,0,909,0,909,0,909,0,0,0,0,0,0,0,,,,,, ++1013,SpiderRage,Raging Spider,1,3000,0,450,170,1,230,150,5,2,35,30,10,2,40,10,2,1,1,0,25,175,700,1500,672,480,537,1000,535,5000,0,0,909,0,909,0,909,0,909,0,0,0,0,0,0,0,,,,,, diff --git a/webserver b/webserver Binary files differnew file mode 100755 index 0000000..d535c8e --- /dev/null +++ b/webserver |